commit 77cdaaf97e8be694dd9be3fe9f0c10d443128bd3 Author: Shinovon Date: Wed Apr 22 07:30:27 2026 +0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..38ad5d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Bb]uild/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +vendor/glew-2.1.0/ +vendor/glfw-3.3.2.bin.WIN32/ +vendor/glfw-3.3.2.bin.WIN64/ + +sdk/ + +codewarrior/re3.mcp +codewarrior/re3_Data/ +codewarrior/Release/ +codewarrior/Debug/ + +src/extras/GitSHA1.cpp \ No newline at end of file diff --git a/gamefiles/TEXT/JAPANESE.gxt b/gamefiles/TEXT/JAPANESE.gxt new file mode 100644 index 0000000..d4b5438 Binary files /dev/null and b/gamefiles/TEXT/JAPANESE.gxt differ diff --git a/gamefiles/TEXT/american.gxt b/gamefiles/TEXT/american.gxt new file mode 100644 index 0000000..d7b7c95 Binary files /dev/null and b/gamefiles/TEXT/american.gxt differ diff --git a/gamefiles/TEXT/english.gxt b/gamefiles/TEXT/english.gxt new file mode 100644 index 0000000..71a42d1 Binary files /dev/null and b/gamefiles/TEXT/english.gxt differ diff --git a/gamefiles/TEXT/french.gxt b/gamefiles/TEXT/french.gxt new file mode 100644 index 0000000..3dece99 Binary files /dev/null and b/gamefiles/TEXT/french.gxt differ diff --git a/gamefiles/TEXT/german.gxt b/gamefiles/TEXT/german.gxt new file mode 100644 index 0000000..7f25bd0 Binary files /dev/null and b/gamefiles/TEXT/german.gxt differ diff --git a/gamefiles/TEXT/italian.gxt b/gamefiles/TEXT/italian.gxt new file mode 100644 index 0000000..5a060a9 Binary files /dev/null and b/gamefiles/TEXT/italian.gxt differ diff --git a/gamefiles/TEXT/polish.gxt b/gamefiles/TEXT/polish.gxt new file mode 100644 index 0000000..782f656 Binary files /dev/null and b/gamefiles/TEXT/polish.gxt differ diff --git a/gamefiles/TEXT/russian.gxt b/gamefiles/TEXT/russian.gxt new file mode 100644 index 0000000..914f5b5 Binary files /dev/null and b/gamefiles/TEXT/russian.gxt differ diff --git a/gamefiles/TEXT/spanish.gxt b/gamefiles/TEXT/spanish.gxt new file mode 100644 index 0000000..cb88468 Binary files /dev/null and b/gamefiles/TEXT/spanish.gxt differ diff --git a/gamefiles/data/PARTICLE.CFG b/gamefiles/data/PARTICLE.CFG new file mode 100644 index 0000000..8bb6d30 --- /dev/null +++ b/gamefiles/data/PARTICLE.CFG @@ -0,0 +1,363 @@ +; 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 diff --git a/gamefiles/data/main_d.scm b/gamefiles/data/main_d.scm new file mode 100644 index 0000000..7b46ca3 Binary files /dev/null and b/gamefiles/data/main_d.scm differ diff --git a/gamefiles/data/main_freeroam.scm b/gamefiles/data/main_freeroam.scm new file mode 100644 index 0000000..021b5c2 Binary files /dev/null and b/gamefiles/data/main_freeroam.scm differ diff --git a/gamefiles/gamecontrollerdb.txt b/gamefiles/gamecontrollerdb.txt new file mode 100644 index 0000000..728fddc --- /dev/null +++ b/gamefiles/gamecontrollerdb.txt @@ -0,0 +1,943 @@ +# Game Controller DB for SDL in 2.0.9 format +# Source: https://github.com/gabomdq/SDL_GameControllerDB + +# Windows +03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows, +03000000c82d00002038000000000000,8bitdo,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d000011ab000000000000,8BitDo F30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001038000000000000,8BitDo F30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000650000000000000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:a4,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00005106000000000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000310000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00002028000000000000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00008010000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00000190000000000000,8BitDo N30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00015900000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00065280000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000022000000090000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000203800000900000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000130000000000000,8BitDo SF30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000060000000000000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000061000000000000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d000021ab000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000102800000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00003028000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000030000000000000,8BitDo SN30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000351000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00001290000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d000020ab000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00004028000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00006228000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000260000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000261000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00000031000000000000,8BitDo Wireless Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c82d00001890000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows, +03000000c82d00003032000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows, +03000000a00500003232000000000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows, +030000008f0e00001200000000000000,Acme GA-02,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000fa190000f0ff000000000000,Acteck AGJ-3200,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000006f0e00001413000000000000,Afterglow,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000263000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001101000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001401000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001402000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001901000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001a01000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d62000001d57000000000000,Airflo PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000869800002400000000007801,Astro C40 TR,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000d6200000e557000000000000,Batarang,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows, +030000006f0e00003201000000000000,Battlefield 4 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d62000002a79000000000000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000bc2000006012000000000000,Betop 2126F,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000000055000000000000,Betop BFM Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000bc2000006312000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000006321000000000000,BETOP CONTROLLER,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bc2000006412000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c01100000555000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c01100000655000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000790000000700000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000808300000300000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +030000006b1400000055000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000006b1400000103000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows, +03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000457500000401000000000000,Cobra,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000a102000000000000,Controller (Xbox 360 Wireless Receiver for Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000ff02000000000000,Controller (Xbox One For Windows) - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000ea02000000000000,Controller (Xbox One For Windows) - Wireless,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows, +03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000007d0400000840000000000000,Destroyer Tiltpad,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,x:b0,y:b3,platform:Windows, +03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000bd12000002e0000000000000,Dual USB Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows, +030000008f0e00000910000000000000,DualShock 2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows, +030000006f0e00003001000000000000,EA SPORTS PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000b80500000410000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +03000000b80500000610000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +03000000120c0000f61c000000000000,Elite,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00000f31000000000000,EXEQ,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000006f0e00008401000000000000,Faceoff Deluxe+ Audio Wired Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00008001000000000000,Faceoff Wired Pro Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000852100000201000000000000,FF-GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008500000000000000,Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008400000000000000,Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008700000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows, +030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +78696e70757403000000000000000000,Fightstick TES,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows, +03000000790000002201000000000000,Game Controller for PC,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +0300000066f700000100000000000000,Game VIB Joystick,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows, +03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows, +03000000790000004618000000000000,GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000280400000140000000000000,GamePad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000ac0500003d03000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000ac0500004d04000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000c01100000140000000000000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000102000000007801,GameStop Xbox 360 Wired Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000009b2800003200000000000000,GC/N64 to USB v3.4,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows, +030000009b2800006000000000000000,GC/N64 to USB v3.6,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows, +030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000005c1a00003330000000000000,Genius MaxFire Grandias 12V,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000300f00000b01000000000000,GGE909 Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c283000000000000,Gioteck,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f025000021c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c383000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c483000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000007d0400000540000000000000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000001008000001e1000000000000,Havit HV-G60,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b0,platform:Windows, +03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows, +03000000632500002605000000000000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +030000000d0f00002d00000000000000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005400000000000000,Hori Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00000900000000000000,Hori Pad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00004d00000000000000,Hori Pad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00009200000000000000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00001600000000007803,HORI Real Arcade Pro EX-SE (Xbox 360),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00009c00000000000000,Hori TAC Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000c100000000000000,Horipad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005500000000000000,Horipad 4 FPS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows, +030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Windows, +03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Windows, +03000000b50700001403000000000000,Impact Black,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +030000006f0e00002401000000000000,INJUSTICE FightStick PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +03000000ac0500002c02000000000000,IPEGA,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000491900000304000000000000,Ipega PG-9087 - Bluetooth Gamepad,+righty:+a5,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows, +030000006e0500000a20000000000000,JC-DUX60 ELECOM MMO Gamepad,a:b2,b:b3,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b14,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b15,righttrigger:b13,rightx:a3,righty:a4,start:b20,x:b0,y:b1,platform:Windows, +030000006e0500000520000000000000,JC-P301U,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows, +030000006e0500000320000000000000,JC-U3613M (DInput),a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows, +030000006e0500000720000000000000,JC-W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows, +030000007e0500000620000000000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows, +030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows, +030000007e0500000720000000000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +03000000bd12000003c0000000000000,JY-P70UR,a:b1,b:b0,back:b5,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b11,righttrigger:b9,rightx:a3,righty:a2,start:b4,x:b3,y:b2,platform:Windows, +03000000242f00002d00000000000000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000242f00008a00000000000000,JYS Wireless Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, +03000000790000000200000000000000,King PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d040000d2ca000000000000,Logitech Cordless Precision,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows, +030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006d0400001ac2000000000000,Logitech Precision Gamepad,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000006d0400000ac2000000000000,Logitech WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Windows, +03000000380700006652000000000000,Mad Catz C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008433000000000000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008483000000000000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700006252000000000000,Mad Catz Micro C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows, +03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows, +03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b0,leftshoulder:b4,leftstick:b0,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000242f00007300000000000000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, +0300000079000000d218000000000000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d620000010a7000000000000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,platform:Windows, +0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, +03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000790000002418000000000000,Mega Drive,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b2,start:b9,x:b3,y:b4,platform:Windows, +03000000380700006382000000000000,MLG GamePad PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000c62400002a89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c62400002b89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c62400001a89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000c62400001b89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000efbe0000edfe000000000000,Monect Virtual Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +030000006b140000010c000000000000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows, +03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows, +030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows, +03000000550900001472000000000000,NVIDIA Controller v01.04,a:b11,b:b10,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b5,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b4,righttrigger:a5,rightx:a3,righty:a6,start:b3,x:b9,y:b8,platform:Windows, +030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,rightshoulder:b5,rightstick:a2,righttrigger:b7,start:b9,x:b2,y:b3,platform:Windows, +03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,platform:Windows, +03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows, +03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows, +03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000901000000000000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +030000004c050000da0c000000000000,PlayStation Classic Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows, +03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000100800000100000000000000,PS1 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +030000008f0e00007530000000000000,PS1 Controller,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000100800000300000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000250900008888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000666600006706000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Windows, +030000006b1400000303000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000009d0d00001330000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000250900000500000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows, +030000004c0500006802000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b8,x:b3,y:b0,platform:Windows, +03000000632500007505000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, +030000008f0e00001431000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000003807000056a8000000000000,PS3 RF pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004c050000e60c000000000000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows, +03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows, +03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows, +03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows, +03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000321500000204000000000000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000104000000000000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000507000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000321500000707000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000321500000011000000000000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000321500000009000000000000,Razer Serval,+lefty:+a2,-lefty:-a1,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,leftx:a0,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows, +03000000bd12000013d0000000000000,Retrolink USB SEGA Saturn Classic,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,lefttrigger:b6,rightshoulder:b2,righttrigger:b7,start:b8,x:b3,y:b4,platform:Windows, +0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows, +0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows, +030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006b140000020d000000000000,Revolution Pro Controller 2(1/2),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006b140000130d000000000000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000a30600000701000000000000,Saitek P220,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,x:b0,y:b1,platform:Windows, +03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b0,y:b1,platform:Windows, +03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows, +03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows, +03000000a30600002106000000000000,Saitek PS1000,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000a306000020f6000000000000,Saitek PS2700,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows, +03000000300f00001101000000000000,Saitek Rumble Pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +03000000730700000401000000000000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Windows, +0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows, +030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows, +030000005e0400008e02000000007801,ShanWan PS3/PC Wired GamePad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows, +03000000341a00000908000000000000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000008f0e00000800000000000000,SpeedLink Strike FX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000c01100000591000000000000,Speedlink Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000d11800000094000000000000,Stadia Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b11,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows, +03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000381000001214000000000000,SteelSeries Free,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows, +03000000110100003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,platform:Windows, +03000000790000001c18000000000000,STK-7024X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,platform:Windows, +03000000d620000011a7000000000000,Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000457500002211000000000000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000004f04000007d0000000000000,T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000004f0400000ab1000000000000,T.16000M,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b10,x:b2,y:b3,platform:Windows, +03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows, +030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +030000004f04000023b3000000000000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004f0400000ed0000000000000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, +030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows, +030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows, +03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, +03000000d62000006000000000000000,Tournament PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +030000004f04000087b6000000000000,TWCS Throttle,dpdown:b8,dpleft:b9,dpright:b7,dpup:b6,leftstick:b5,lefttrigger:-a5,leftx:a0,lefty:a1,righttrigger:+a5,platform:Windows, +03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +030000006e0500001320000000000000,U4113,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000300f00000701000000000000,USB 4-Axis 12-Button Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000341a00002308000000000000,USB gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +030000005509000000b4000000000000,USB gamepad,a:b10,b:b11,back:b5,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,guide:b14,leftshoulder:b8,leftstick:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b7,righttrigger:a5,rightx:a2,righty:a3,start:b4,x:b12,y:b13,platform:Windows, +030000006b1400000203000000000000,USB gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000790000000a00000000000000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows, +03000000f0250000c183000000000000,USB gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000ff1100004133000000000000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000790000001a18000000000000,Venom,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000302000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +030000006f0e00000702000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows, +0300000034120000adbe000000000000,vJoy Device,a:b0,b:b1,back:b15,dpdown:b6,dpleft:b7,dpright:b8,dpup:b5,guide:b16,leftshoulder:b9,leftstick:b13,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b14,righttrigger:b12,rightx:+a3,righty:+a4,start:b4,x:b2,y:b3,platform:Windows, +030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000ff02000000007801,Xbox One Elite Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +030000005e040000130b000000000000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000341a00000608000000000000,Xeox,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000ac0500005b05000000000000,Xiaoji Gamesir-G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows, +03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, +03000000786901006e70000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows, +03000000790000004f18000000000000,ZD-T Android,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, +03000000120c0000101e000000000000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, + +# Mac OS X +030000008f0e00000300000009010000,2In1 USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000650000001000000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +030000003512000012ab000001000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000022000000090000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000190000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000102800000900000000000000,8Bitdo SFC30 GamePad Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00001290000001000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000260000001000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00000031000001000000,8BitDo Wireless Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c82d00001890000001000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a31,start:b11,x:b4,y:b3,platform:Mac OS X, +03000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000a00500003232000009010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000c62400001a89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X, +03000000c62400001b89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000d62000002a79000000010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X, +03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000c01100000140000000010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006f0e00000102000000000000,GameStop Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000007d0400000540000001010000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00002d00000000100000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00009200000000010000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000000d0f0000ee00000000010000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,platform:Mac OS X, +03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X, +03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X, +030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Mac OS X, +030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000242f00002d00000007010000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000019c2000005030000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000006d04000018c2000000010000,Logitech RumblePad 2 USB,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700008433000000010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000380700008483000000010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000242f00007300000000020000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Mac OS X, +0300000079000000d218000026010000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000d620000010a7000003010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X, +03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X, +03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000005e0400002700000001010000,Microsoft SideWinder Plug & Play Game Pad,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,leftx:a0,lefty:a1,righttrigger:b5,x:b2,y:b3,platform:Mac OS X, +03000000d62000007162000001000000,Moga Pro 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X, +03000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000c62400002b89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000632500007505000000020000,NEOGEO mini PAD Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b2,y:b3,platform:Mac OS X, +030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000d620000011a7000000020000,Nintendo Switch Core (Plus) Wired Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +030000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, +03000000550900001472000025050000,NVIDIA Controller v01.04,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Mac OS X, +030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X, +030000004c050000da0c000000010000,Playstation Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000d62000006dca000000010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X, +030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X, +030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000321500000204000000010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000104000000010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000507000001010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000321500000011000000010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000321500000009000000020000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X, +030000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X, +0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006b140000130d000000010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000c6240000fefa000000000000,Rock Candy Gamepad for PS3,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000730700000401000000010000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Mac OS X, +03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X, +03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X, +030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X, +0300000000f00000f100000000000000,SNES RetroPort,a:b2,b:b3,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,rightshoulder:b7,start:b6,x:b0,y:b1,platform:Mac OS X, +030000004c050000e60c000000010000,Sony DualSense,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000d11800000094000000010000,Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X, +030000005e0400008e02000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Mac OS X, +03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X, +03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X, +03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X, +03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X, +03000000457500002211000000010000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X, +030000004f0400000ed0000000020000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X, +03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, +03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X, +030000006f0e00000302000025040000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +030000006f0e00000702000003060000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000791d00000103000009010000,Wii Classic Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X, +050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X, +050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X, +030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000050b000003090000,Xbox Elite Wireless Controller Series 2,a:b0,b:b1,back:b31,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b53,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X, +030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X, +030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, +030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X, +03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, +03000000120c0000101e000000010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, + +# Linux +03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00001038000000010000,8Bitdo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00005106000000010000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000310000011010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux, +05000000c82d00008010000000010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux, +03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000060000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000061000000010000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d000021ab000010010000,8BitDo SFC30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +030000003512000012ab000010010000,8Bitdo SFC30 GamePad,a:b2,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux, +05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00001290000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00006228000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +030000005e0400008e02000020010000,8BitDo Wireless Adapter (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c82d00000031000011010000,8BitDo Wireless Adapter (DInput),a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000c82d00001890000011010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux, +05000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux, +05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux, +05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux, +030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00003901000013020000,Afterglow Prismatic Wired Controller 048-007-NA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000007c1800000006000010010000,Alienware Dual Compatible Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000790000003018000011010000,Arcade Fightstick F300,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux, +05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux, +03000000120c00000500000010010000,AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux, +03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000c21100000791000011010000,Be1 GC101 Controller 1.03 mode,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000c31100000791000011010000,Be1 GC101 GAMEPAD 1.03 mode,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e0400008e02000003030000,Be1 GC101 Xbox 360 Controller mode,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux, +03000000ffff0000ffff000000010000,Chinese-made Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000000b0400003365000000010000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Linux, +03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux, +03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux, +03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux, +030000004f04000004b3000010010000,Dual Power 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000007d0400000540000000010000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux, +030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000f0250000c383000010010000,GT VX2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux, +03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000632500002605000010010000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,platform:Linux, +030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f0000c100000011010000,HORI CO. LTD. HORIPAD S,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00008500000010010000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00008600000002010000,Hori Fighting Commander,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000000d0f00009200000011010000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000000d0f0000d800000072056800,HORI Real Arcade Pro S,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +030000000d0f00001600000000010000,Hori Real Arcade Pro.EX-SE (Xbox 360),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f0000ee00000011010000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux, +03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux, +050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,platform:Linux, +03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,platform:Linux, +0500000049190000020400001b010000,Ipega PG-9069 - Bluetooth Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b161,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000632500007505000011010000,Ipega PG-9099 - Bluetooth Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux, +03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000300f00000b01000010010000,Jess Tech GGE909 PC Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux, +050000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux, +030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux, +050000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux, +03000000242f00002d00000011010000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000242f00008a00000011010000,JYS Wireless Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux, +030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d0400001ec2000019200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d0400000ac2000010010000,Logitech Inc. WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Linux, +030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux, +050000004d4f435554452d3035305800,M54-PC,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,platform:Linux, +05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux, +03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000120c00000500000000010000,Manta Dualshock 2,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +03000000790000004318000010010000,Mayflash GameCube Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux, +03000000242f00007300000011010000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux, +0300000079000000d218000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000d620000010a7000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +0300000025090000e803000001010000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux, +03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux, +030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000050b000003090000,Microsoft X-Box One Elite 2 pad,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e040000e302000003020000,Microsoft X-Box One Elite pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000dd02000003020000,Microsoft X-Box One pad (Firmware 2015),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +030000005e040000000b000008040000,Microsoft Xbox One Elite 2 pad - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000ea02000008040000,Microsoft Xbox One S pad - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c62400001a53000000010000,Mini PE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux, +05000000d6200000e589000001000000,Moga 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +05000000d62000007162000001000000,Moga Pro 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, +03000000c62400002b89000011010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000c62400001a89000000010000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, +030000006b140000010c000010010000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux, +060000007e0500000820000000000000,Nintendo Combined Joy-Cons (joycond),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +030000007e0500003703000000016800,Nintendo GameCube Controller,a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,platform:Linux, +03000000790000004618000010010000,Nintendo GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5~,righty:a2~,start:b9,x:b0,y:b3,platform:Linux, +050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +050000007e0500000920000001800000,Nintendo Switch Pro Controller (joycond),a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +030000007e0500000920000011810000,Nintendo Switch Pro Controller Wired (joycond),a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux, +050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux, +05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux, +03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux, +05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux, +03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +19000000010000000100000001010000,odroidgo2_joypad,a:b1,b:b0,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,guide:b10,leftshoulder:b4,leftstick:b12,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b14,start:b15,x:b2,y:b3,platform:Linux, +19000000010000000200000011000000,odroidgo2_joypad_v11,a:b1,b:b0,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b12,leftshoulder:b4,leftstick:b14,lefttrigger:b13,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b16,start:b17,x:b2,y:b3,platform:Linux, +030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux, +05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux, +05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux, +03000000830500005020000010010000,Padix Co. Ltd. Rockfire PSX/USB Bridge,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b2,y:b3,platform:Linux, +03000000790000001c18000011010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e0000b802000001010000,PDP AFTERGLOW Wired Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000b802000013020000,PDP AFTERGLOW Wired Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00008001000011010000,PDP CO. LTD. Faceoff Wired Pro Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00003101000000010000,PDP EA Sports Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e0000c802000012010000,PDP Kingdom Hearts Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00008701000011010000,PDP Rock Candy Wired Controller for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000006f0e00000901000011010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e0000a802000023020000,PDP Wired Controller for Xbox One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000006f0e00008501000011010000,PDP Wired Fight Pad Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +05000000491900000204000000000000,PG-9118,x:b76,a:b73,b:b74,y:b77,back:b83,start:b84,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b79,lefttrigger:b81,rightshoulder:b80,righttrigger:b82,leftstick:b86,rightstick:b87,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Linux, +0500000049190000030400001b010000,PG-9099,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000004c050000da0c000011010000,Playstation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c62400003a54000001010000,PowerA 1428124-01,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d62000006dca000011010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000c62400001a58000001010000,PowerA Xbox One Cabled,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, +030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +03000000c01100000140000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000c405000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux, +030000004c050000e60c000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux, +030000009b2800003200000001010000,Raphnet Technologies GC/N64 to USB v3.4,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux, +030000009b2800006000000001010000,Raphnet Technologies GC/N64 to USB v3.6,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux, +030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux, +030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000008916000000fd000024010000,Razer Onza Tournament Edition,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000810000011010000,Razer Panthera Evo Arcade Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000321500000011000011010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux, +0300000081170000990a000001010000,Retronic Adapter,a:b0,leftx:a0,lefty:a1,platform:Linux, +0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux, +030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000a30600001005000000010000,Saitek P150,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b2,righttrigger:b5,x:b3,y:b4,platform:Linux, +03000000a30600000701000000010000,Saitek P220,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,x:b0,y:b1,platform:Linux, +03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b0,y:b1,platform:Linux, +03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux, +03000000300f00001201000010010000,Saitek P380,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux, +03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux, +03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux, +03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000a306000020f6000011010000,Saitek PS2700 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux, +03000000d81d00000e00000010010000,Savior,a:b0,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,start:b9,x:b4,y:b5,platform:Linux, +03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,platform:Linux, +03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000bc2000000055000010010000,ShanWan PS3/PC Wired GamePad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, +030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d11800000094000011010000,Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800000211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux, +03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux, +03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux, +03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000381000003014000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000381000003114000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000003b07000004a1000000010000,Suncom SFX Plus for USB,a:b0,b:b2,back:b7,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Linux, +03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, +0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux, +03000000457500002211000010010000,SZMY-POWER CO. LTD. GAMEPAD,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +030000008f0e00000d31000010010000,SZMY-POWER CO. LTD. GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000008f0e00001431000010010000,SZMY-POWER CO.,LTD. PS3 gamepad,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux, +030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004f0400000ed0000011010000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000b50700000399000000010000,Thrustmaster Firestorm Digital 2,a:b2,b:b4,back:b11,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b0,righttrigger:b9,start:b1,x:b3,y:b5,platform:Linux, +030000004f04000003b3000010010000,Thrustmaster Firestorm Dual Analog 2,a:b0,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b9,rightx:a2,righty:a3,x:b1,y:b3,platform:Linux, +030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux, +030000004f04000026b3000002040000,Thrustmaster Gamepad GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c6240000025b000002020000,Thrustmaster GPX Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004f04000007d0000000010000,Thrustmaster T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, +030000004f04000012b3000010010000,Thrustmaster vibrating gamepad,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux, +03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux, +03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux, +030000005e0400008e02000070050000,Torid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000c01100000591000011010000,Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, +03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux, +03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux, +030000006f0e00000302000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +030000006f0e00000702000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, +05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux, +03000000791d00000103000010010000,Wii Classic Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +050000000d0f0000f600000001000000,Wireless HORIPAD Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux, +030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000d102000002010000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000fd02000030110000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000050b000002090000,Xbox One Elite Series 2,a:b0,b:b1,back:b136,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e040000ea02000000000000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e040000ea02000001030000,Xbox One Wireless Controller (Model 1708),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000001050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +030000005e0400008e02000000010000,xbox360 Wireless EasySMX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, +03000000ac0500005b05000010010000,Xiaoji Gamesir-G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux, +05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux, +03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux, +xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, +03000000c0160000dc27000001010000,OnyxSoft Dual JoyDivision,platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b6,leftshoulder:b4,rightshoulder:b5,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0, + +# Android +05000000c82d000006500000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a4,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000051060000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000002038000009000000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000000600000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000000610000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000012900000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000062280000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000001600000ffff3f00,8BitDo SN30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000002600000ffff0f00,8BitDo SN30 Pro+,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000002028000009000000ffff3f00,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android, +050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,platform:Android, +05000000c82d000018900000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000c82d000030320000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android, +05000000bc20000000550000ffff3f00,GameSir G3w,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +05000000d6020000e5890000dfff3f00,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android, +0500000031366332860c44aadfff0f00,GS Gamepad,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b3,y:b2,platform:Android, +64633436313965656664373634323364,Microsoft X-Box 360 pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android, +7573622067616d657061642020202020,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Android, +050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b17,y:b2,platform:Android, +37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005509000003720000cf7f3f00,NVIDIA Controller v01.01,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005509000010720000ffff3f00,NVIDIA Controller v01.03,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005509000014720000df7f3f00,NVIDIA Controller v01.04,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android, +050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +030000004c050000cc09000000006800,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000004c050000c4050000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +050000004c050000c4050000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000004c050000cc090000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +050000004c050000cc090000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +050000004c050000e60c0000fffe3f00,PS5 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android, +62653861643333663663383332396665,Razer Kishi,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000003215000005070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000003215000007070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000003215000000090000bf7f3f00,Razer Serval,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android, +32633532643734376632656664383733,Sony DualSense,a:b1,b:b19,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android, +61303162353165316365336436343139,Sony DualSense,a:b1,b:b19,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android, +05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android, +05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android, +050000004f0400000ed00000fffe3f00,ThrustMaster eSwap PRO Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +5477696e20555342204a6f7973746963,Twin USB Joystick,a:b22,b:b21,back:b28,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android, +30306539356238653637313730656134,Wireless HORIPAD Switch Pro Controller,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android, +050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000e00200000ffe3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b17,y:b2,platform:Android, +050000005e040000fd020000ffff3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e040000130b0000ffff3f00,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +65633038363832353634653836396239,Xbox Series Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +050000005e04000091020000ff073f00,Xbox Wireless Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android, +34356136633366613530316338376136,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android, +050000001727000044310000ffff3f00,XiaoMi Game Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android, + +# iOS +05000000ac0500000100000000006d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS, +05000000ac050000010000004f066d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS, +05000000ac05000001000000cf076d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS, +05000000ac05000001000000df076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +05000000ac0500000200000000006d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS, +05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS, +4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS, +4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS, +050000004c050000cc090000df070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +050000004c050000cc090000ff070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +050000004c050000cc090000ff870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,platform:iOS, +05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS, +05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS, +05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS, +05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS, +050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, +050000005e040000e0020000df070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS, +050000005e040000e0020000ff070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS, diff --git a/gamefiles/models/fonts_j.txd b/gamefiles/models/fonts_j.txd new file mode 100644 index 0000000..437e13f Binary files /dev/null and b/gamefiles/models/fonts_j.txd differ diff --git a/gamefiles/models/fonts_p.txd b/gamefiles/models/fonts_p.txd new file mode 100644 index 0000000..c05e402 Binary files /dev/null and b/gamefiles/models/fonts_p.txd differ diff --git a/gamefiles/models/fonts_r.txd b/gamefiles/models/fonts_r.txd new file mode 100644 index 0000000..4b89e44 Binary files /dev/null and b/gamefiles/models/fonts_r.txd differ diff --git a/gamefiles/models/frontend_ds3.txd b/gamefiles/models/frontend_ds3.txd new file mode 100644 index 0000000..7481e9b Binary files /dev/null and b/gamefiles/models/frontend_ds3.txd differ diff --git a/gamefiles/models/frontend_ds4.txd b/gamefiles/models/frontend_ds4.txd new file mode 100644 index 0000000..594de32 Binary files /dev/null and b/gamefiles/models/frontend_ds4.txd differ diff --git a/gamefiles/models/frontend_nsw.txd b/gamefiles/models/frontend_nsw.txd new file mode 100644 index 0000000..1379a1a Binary files /dev/null and b/gamefiles/models/frontend_nsw.txd differ diff --git a/gamefiles/models/frontend_x360.txd b/gamefiles/models/frontend_x360.txd new file mode 100644 index 0000000..a57b8d1 Binary files /dev/null and b/gamefiles/models/frontend_x360.txd differ diff --git a/gamefiles/models/frontend_xone.txd b/gamefiles/models/frontend_xone.txd new file mode 100644 index 0000000..03dfefd Binary files /dev/null and b/gamefiles/models/frontend_xone.txd differ diff --git a/gamefiles/models/generic.txd b/gamefiles/models/generic.txd new file mode 100644 index 0000000..8e0d60f Binary files /dev/null and b/gamefiles/models/generic.txd differ diff --git a/gamefiles/models/menu.txd b/gamefiles/models/menu.txd new file mode 100644 index 0000000..f617bcf Binary files /dev/null and b/gamefiles/models/menu.txd differ diff --git a/gamefiles/models/nswbtns.txd b/gamefiles/models/nswbtns.txd new file mode 100644 index 0000000..0b1756e Binary files /dev/null and b/gamefiles/models/nswbtns.txd differ diff --git a/gamefiles/models/particle.txd b/gamefiles/models/particle.txd new file mode 100644 index 0000000..e908ba4 Binary files /dev/null and b/gamefiles/models/particle.txd differ diff --git a/gamefiles/models/ps3btns.txd b/gamefiles/models/ps3btns.txd new file mode 100644 index 0000000..ed21626 Binary files /dev/null and b/gamefiles/models/ps3btns.txd differ diff --git a/gamefiles/models/x360btns.txd b/gamefiles/models/x360btns.txd new file mode 100644 index 0000000..95a68c8 Binary files /dev/null and b/gamefiles/models/x360btns.txd differ diff --git a/gamefiles/neo/carTweakingTable.dat b/gamefiles/neo/carTweakingTable.dat new file mode 100644 index 0000000..5e707ae --- /dev/null +++ b/gamefiles/neo/carTweakingTable.dat @@ -0,0 +1,104 @@ +# Fresnal RO Table +# SUNNY CLOUDY RAINY, FOGGY +0.400000 0.400000 0.400000 0.150000 # Midnight +0.400000 0.400000 0.400000 0.150000 # 1am +0.400000 0.400000 0.400000 0.150000 # 2am +0.400000 0.400000 0.400000 0.150000 # 3am +0.400000 0.400000 0.400000 0.150000 # 4am +0.400000 0.400000 0.400000 0.150000 # 5am +0.400000 0.400000 0.400000 0.150000 # 6am +0.400000 0.400000 0.400000 0.150000 # 7am +0.400000 0.400000 0.400000 0.150000 # 8am +0.400000 0.400000 0.400000 0.150000 # 9am +0.400000 0.400000 0.400000 0.150000 # 10am +0.400000 0.400000 0.400000 0.150000 # 11am +0.400000 0.400000 0.400000 0.150000 # Midday +0.400000 0.400000 0.400000 0.150000 # 1pm +0.400000 0.400000 0.400000 0.150000 # 2pm +0.400000 0.400000 0.400000 0.150000 # 3pm +0.400000 0.400000 0.400000 0.150000 # 4pm +0.400000 0.400000 0.400000 0.150000 # 5pm +0.400000 0.400000 0.400000 0.150000 # 6pm +0.400000 0.400000 0.400000 0.150000 # 7pm +0.400000 0.400000 0.400000 0.150000 # 8pm +0.400000 0.400000 0.400000 0.150000 # 9pm +0.400000 0.400000 0.400000 0.150000 # 10pm +0.400000 0.400000 0.400000 0.150000 # 11pm +# Specular Power Table +# SUNNY CLOUDY RAINY, FOGGY +128.000000 80.000000 30.000000 128.000000 # Midnight +128.000000 80.000000 30.000000 128.000000 # 1am +128.000000 80.000000 30.000000 128.000000 # 2am +128.000000 80.000000 30.000000 128.000000 # 3am +128.000000 80.000000 30.000000 128.000000 # 4am +80.000000 60.000000 30.000000 128.000000 # 5am +80.000000 60.000000 30.000000 128.000000 # 6am +80.000000 60.000000 30.000000 128.000000 # 7am +80.000000 60.000000 30.000000 128.000000 # 8am +80.000000 60.000000 30.000000 128.000000 # 9am +80.000000 60.000000 30.000000 128.000000 # 10am +80.000000 60.000000 30.000000 128.000000 # 11am +80.000000 60.000000 30.000000 128.000000 # Midday +80.000000 60.000000 30.000000 128.000000 # 1pm +80.000000 60.000000 30.000000 128.000000 # 2pm +80.000000 60.000000 30.000000 128.000000 # 3pm +80.000000 60.000000 30.000000 128.000000 # 4pm +128.000000 80.000000 30.000000 128.000000 # 5pm +128.000000 80.000000 30.000000 128.000000 # 6pm +128.000000 80.000000 30.000000 128.000000 # 7pm +128.000000 80.000000 30.000000 128.000000 # 8pm +128.000000 80.000000 30.000000 128.000000 # 9pm +128.000000 80.000000 30.000000 128.000000 # 10pm +128.000000 80.000000 30.000000 128.000000 # 11pm +# Diffuse Colour Modifier Table (Red,Green,Blue,Amount) +# SUNNY CLOUDY RAINY, FOGGY +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # Midnight +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 1am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 2am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 3am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 4am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 5am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 6am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 7am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 8am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 9am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 10am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 11am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # Midday +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 1pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 2pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 3pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 4pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 5pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 6pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 7pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 8pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 9pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 10pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 11pm +# Specular Colour Table (Red,Green,Blue,Amount) +# SUNNY CLOUDY RAINY, FOGGY + 81, 150, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # Midnight + 81, 150, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 1am + 81, 150, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 2am + 81, 150, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 3am + 81, 150, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 4am +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 5am +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 6am +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 7am +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 8am +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 9am +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 10am +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 11am +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # Midday +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 1pm +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 2pm +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 3pm +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 4pm +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 5pm +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 6pm +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 7pm +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 8pm +178, 178, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 9pm + 81, 150, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 10pm + 81, 150, 178, 100 178, 178, 178, 50 178, 178, 178, 75 178, 178, 178, 20 # 11pm diff --git a/gamefiles/neo/neo.txd b/gamefiles/neo/neo.txd new file mode 100644 index 0000000..d20215e Binary files /dev/null and b/gamefiles/neo/neo.txd differ diff --git a/gamefiles/neo/rimTweakingTable.dat b/gamefiles/neo/rimTweakingTable.dat new file mode 100644 index 0000000..058d55e --- /dev/null +++ b/gamefiles/neo/rimTweakingTable.dat @@ -0,0 +1,130 @@ +# Ramp Start Table +# SUNNY CLOUDY RAINY, FOGGY +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # Midnight +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 1am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 2am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 3am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 4am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 5am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 6am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 7am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 8am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 9am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 10am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 11am +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # Midday +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 1pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 2pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 3pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 4pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 5pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 6pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 7pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 8pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 9pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 10pm +60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 60, 55, 53, 100 # 11pm +# Ramp End Table +# SUNNY CLOUDY RAINY, FOGGY +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # Midnight +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 1am +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 2am +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 3am +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 4am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 5am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 6am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 7am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 8am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 9am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 10am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 11am +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # Midday +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 1pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 2pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 3pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 4pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 5pm +255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 255, 230, 224, 100 # 6pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 7pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 8pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 9pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 10pm +190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 190, 171, 167, 100 # 11pm +# Offset Table +# SUNNY CLOUDY RAINY, FOGGY +0 0 0 0 # Midnight +0 0 0 0 # 1am +0 0 0 0 # 2am +0 0 0 0 # 3am +0 0 0 0 # 4am +0 0 0 0 # 5am +0 0 0 0 # 6am +0 0 0 0 # 7am +0 0 0 0 # 8am +0 0 0 0 # 9am +0 0 0 0 # 10am +0 0 0 0 # 11am +0 0 0 0 # Midday +0 0 0 0 # 1pm +0 0 0 0 # 2pm +0 0 0 0 # 3pm +0 0 0 0 # 4pm +0 0 0 0 # 5pm +0 0 0 0 # 6pm +0 0 0 0 # 7pm +0 0 0 0 # 8pm +0 0 0 0 # 9pm +0 0 0 0 # 10pm +0 0 0 0 # 11pm +# Scale Table +# SUNNY CLOUDY RAINY, FOGGY +1.5 1.5 1.0 1.0 # Midnight +1.5 1.5 1.0 1.0 # 1am +1.5 1.5 1.0 1.0 # 2am +1.5 1.5 1.5 1.5 # 3am +2.0 2.0 2.0 2.0 # 4am +2.0 2.0 2.0 2.0 # 5am +2.0 2.0 2.0 2.0 # 6am +2.5 2.5 2.0 2.0 # 7am +2.5 2.5 2.0 2.0 # 8am +2.5 2.5 2.0 2.0 # 9am +2.5 2.5 2.0 2.0 # 10am +2.5 2.5 2.0 2.0 # 11am +2.5 2.5 2.0 2.0 # Midday +2.5 2.5 2.0 2.0 # 1pm +2.5 2.5 2.0 2.0 # 2pm +2.5 2.5 2.0 2.0 # 3pm +2.5 2.5 2.0 2.0 # 4pm +2.0 2.0 2.0 2.0 # 5pm +2.0 2.0 2.0 2.0 # 6pm +2.0 2.0 2.0 2.0 # 7pm +1.5 1.5 1.5 1.5 # 8pm +1.5 1.5 1.0 1.0 # 9pm +1.5 1.5 1.0 1.0 # 10pm +1.5 1.5 1.0 1.0 # 11pm +# Scaling Table +# SUNNY CLOUDY RAINY, FOGGY +0.2 0.2 0.1 0.1 # Midnight +0.2 0.2 0.1 0.1 # 1am +0.7 0.7 0.2 0.2 # 6am +0.7 0.7 0.2 0.2 # 3am +0.7 0.7 0.2 0.2 # 4am +2.0 2.0 0.3 0.3 # 5am +3.0 3.0 0.3 0.3 # 6am +4.0 4.0 0.3 0.3 # 7am +5.0 5.0 0.3 0.3 # 8am +6.0 6.0 1.3 1.3 # 9am +6.0 6.0 2.0 2.0 # 10am +6.0 6.0 2.0 2.0 # 11am +6.0 6.0 2.0 2.0 # Midday +6.0 6.0 2.0 2.0 # 1pm +6.0 6.0 1.3 1.3 # 6pm +5.0 5.0 0.3 0.3 # 3pm +4.0 4.0 0.3 0.3 # 4pm +3.0 3.0 0.3 0.3 # 5pm +2.0 2.0 0.3 0.3 # 6pm +0.7 0.7 0.2 0.2 # 7pm +0.7 0.7 0.2 0.2 # 8pm +0.7 0.7 0.2 0.2 # 9pm +0.2 0.2 0.1 0.1 # 10pm +0.2 0.2 0.1 0.1 # 11pm diff --git a/gamefiles/neo/worldTweakingTable.dat b/gamefiles/neo/worldTweakingTable.dat new file mode 100644 index 0000000..b054fa3 --- /dev/null +++ b/gamefiles/neo/worldTweakingTable.dat @@ -0,0 +1,26 @@ +# LM blend Table +# SUNNY CLOUDY RAINY FOGGY +0.700000 0.700000 0.700000 0.550000 # Midnight +0.700000 0.700000 0.700000 0.550000 # 1am +0.700000 0.700000 0.700000 0.550000 # 2am +0.700000 0.700000 0.700000 0.550000 # 3am +0.700000 0.700000 0.700000 0.550000 # 4am +0.750000 0.750000 0.700000 0.600000 # 5am +0.800000 0.800000 0.750000 0.600000 # 6am +0.850000 0.850000 0.800000 0.650000 # 7am +0.900000 0.900000 0.800000 0.700000 # 8am +0.950000 0.900000 0.800000 0.700000 # 9am +1.000000 0.900000 0.800000 0.700000 # 10am +1.000000 0.900000 0.800000 0.700000 # 11am +1.000000 0.900000 0.800000 0.700000 # Midday +1.000000 0.900000 0.800000 0.700000 # 1pm +1.000000 0.900000 0.800000 0.700000 # 2pm +0.950000 0.900000 0.800000 0.700000 # 3pm +0.900000 0.900000 0.800000 0.700000 # 4pm +0.850000 0.850000 0.800000 0.650000 # 5pm +0.800000 0.800000 0.750000 0.600000 # 6pm +0.750000 0.750000 0.700000 0.600000 # 7pm +0.700000 0.700000 0.700000 0.550000 # 8pm +0.700000 0.700000 0.700000 0.550000 # 9pm +0.700000 0.700000 0.700000 0.550000 # 10pm +0.700000 0.700000 0.700000 0.550000 # 11pm diff --git a/res/images/logo.svg b/res/images/logo.svg new file mode 100644 index 0000000..9db8447 --- /dev/null +++ b/res/images/logo.svg @@ -0,0 +1,88 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/res/images/logo_1024.png b/res/images/logo_1024.png new file mode 100644 index 0000000..50ae869 Binary files /dev/null and b/res/images/logo_1024.png differ diff --git a/res/images/logo_256.jpg b/res/images/logo_256.jpg new file mode 100644 index 0000000..595d2c3 Binary files /dev/null and b/res/images/logo_256.jpg differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..818d180 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,170 @@ +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 + $ + $ +) + +target_compile_definitions(${EXECUTABLE} + PRIVATE + $,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 $ DESTINATION "." OPTIONAL) + endif() +endif() + +re3_platform_target(${EXECUTABLE} INSTALL) diff --git a/src/animation/AnimBlendAssocGroup.cpp b/src/animation/AnimBlendAssocGroup.cpp new file mode 100644 index 0000000..295d6be --- /dev/null +++ b/src/animation/AnimBlendAssocGroup.cpp @@ -0,0 +1,174 @@ +#include "common.h" + +#if defined _WIN32 && !defined __MINGW32__ +#if defined __MWERKS__ +#include +#else +#include "ctype.h" +#endif +#else +#include +#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; +} diff --git a/src/animation/AnimBlendAssocGroup.h b/src/animation/AnimBlendAssocGroup.h new file mode 100644 index 0000000..aa58b0d --- /dev/null +++ b/src/animation/AnimBlendAssocGroup.h @@ -0,0 +1,20 @@ +#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); +}; diff --git a/src/animation/AnimBlendAssociation.cpp b/src/animation/AnimBlendAssociation.cpp new file mode 100644 index 0000000..b03571b --- /dev/null +++ b/src/animation/AnimBlendAssociation.cpp @@ -0,0 +1,205 @@ +#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; +} diff --git a/src/animation/AnimBlendAssociation.h b/src/animation/AnimBlendAssociation.h new file mode 100644 index 0000000..45720b6 --- /dev/null +++ b/src/animation/AnimBlendAssociation.h @@ -0,0 +1,91 @@ +#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); diff --git a/src/animation/AnimBlendClumpData.cpp b/src/animation/AnimBlendClumpData.cpp new file mode 100644 index 0000000..b333a44 --- /dev/null +++ b/src/animation/AnimBlendClumpData.cpp @@ -0,0 +1,36 @@ +#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); +} diff --git a/src/animation/AnimBlendClumpData.h b/src/animation/AnimBlendClumpData.h new file mode 100644 index 0000000..acfd006 --- /dev/null +++ b/src/animation/AnimBlendClumpData.h @@ -0,0 +1,57 @@ +#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 diff --git a/src/animation/AnimBlendHierarchy.cpp b/src/animation/AnimBlendHierarchy.cpp new file mode 100644 index 0000000..ea66999 --- /dev/null +++ b/src/animation/AnimBlendHierarchy.cpp @@ -0,0 +1,94 @@ +#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 diff --git a/src/animation/AnimBlendHierarchy.h b/src/animation/AnimBlendHierarchy.h new file mode 100644 index 0000000..40d2731 --- /dev/null +++ b/src/animation/AnimBlendHierarchy.h @@ -0,0 +1,33 @@ +#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 *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); \ No newline at end of file diff --git a/src/animation/AnimBlendList.h b/src/animation/AnimBlendList.h new file mode 100644 index 0000000..018b598 --- /dev/null +++ b/src/animation/AnimBlendList.h @@ -0,0 +1,28 @@ +#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(); + } +}; diff --git a/src/animation/AnimBlendNode.cpp b/src/animation/AnimBlendNode.cpp new file mode 100644 index 0000000..df6cd1d --- /dev/null +++ b/src/animation/AnimBlendNode.cpp @@ -0,0 +1,160 @@ +#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; + } +} diff --git a/src/animation/AnimBlendNode.h b/src/animation/AnimBlendNode.h new file mode 100644 index 0000000..89924d6 --- /dev/null +++ b/src/animation/AnimBlendNode.h @@ -0,0 +1,31 @@ +#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); diff --git a/src/animation/AnimBlendSequence.cpp b/src/animation/AnimBlendSequence.cpp new file mode 100644 index 0000000..2ae150c --- /dev/null +++ b/src/animation/AnimBlendSequence.cpp @@ -0,0 +1,200 @@ +#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 + diff --git a/src/animation/AnimBlendSequence.h b/src/animation/AnimBlendSequence.h new file mode 100644 index 0000000..c6e70f2 --- /dev/null +++ b/src/animation/AnimBlendSequence.h @@ -0,0 +1,68 @@ +#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 diff --git a/src/animation/AnimManager.cpp b/src/animation/AnimManager.cpp new file mode 100644 index 0000000..c66997c --- /dev/null +++ b/src/animation/AnimManager.cpp @@ -0,0 +1,941 @@ +#include "common.h" + +#include "General.h" +#include "RwHelper.h" +#include "ModelInfo.h" +#include "ModelIndices.h" +#include "FileMgr.h" +#include "RpAnimBlend.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendAssociation.h" +#include "AnimBlendAssocGroup.h" +#include "AnimManager.h" + +CAnimBlock CAnimManager::ms_aAnimBlocks[NUMANIMBLOCKS]; +CAnimBlendHierarchy CAnimManager::ms_aAnimations[NUMANIMATIONS]; +int32 CAnimManager::ms_numAnimBlocks; +int32 CAnimManager::ms_numAnimations; +CAnimBlendAssocGroup *CAnimManager::ms_aAnimAssocGroups; +CLinkList CAnimManager::ms_animCache; + +AnimAssocDesc aStdAnimDescs[] = { + { ANIM_STD_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK }, + { ANIM_STD_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK }, + { ANIM_STD_RUNFAST, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK }, + { ANIM_STD_IDLE, ASSOC_REPEAT }, + { ANIM_STD_STARTWALK, ASSOC_HAS_TRANSLATION }, + { ANIM_STD_RUNSTOP1, ASSOC_DELETEFADEDOUT | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_RUNSTOP2, ASSOC_DELETEFADEDOUT | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_IDLE_CAM, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_IDLE_HBHB, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_IDLE_TIRED, ASSOC_REPEAT }, + { ANIM_STD_IDLE_BIGGUN, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_CHAT, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_HAILTAXI, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_KO_FRONT, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_LEFT, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_BACK, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_RIGHT, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_SHOT_FACE, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_KO_SHOT_STOMACH, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_KO_SHOT_ARM_L, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_KO_SHOT_ARM_R, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_KO_SHOT_LEG_L, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_KO_SHOT_LEG_R, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_SPINFORWARD_LEFT, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_SPINFORWARD_RIGHT, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_HIGHIMPACT_FRONT, ASSOC_PARTIAL }, + { ANIM_STD_HIGHIMPACT_LEFT, ASSOC_PARTIAL }, + { ANIM_STD_HIGHIMPACT_BACK, ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_HIGHIMPACT_RIGHT, ASSOC_PARTIAL }, + { ANIM_STD_HITBYGUN_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HITBYGUN_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HITBYGUN_BACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HITBYGUN_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HIT_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_HIT_BACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_HIT_FLOOR, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, +#if GTA_VERSION <= GTA3_PS2_160 + { ANIM_STD_HIT_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +#endif + { ANIM_STD_HIT_BODYBLOW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_CHEST, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_HEAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_WALK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_WALL, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HIT_FLOOR_FRONT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_FRONTAL }, + { ANIM_STD_HIT_BEHIND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_KICKGROUND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_BAT_H, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_BAT_V, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_HGUN_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_WEAPON_AK_BODY, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_PUMP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_SNIPER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_WEAPON_THROW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_THROW_UNDER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_START_THROW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_DETONATE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_HGUN_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_AK_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, +#ifdef PC_PLAYER_CONTROLS + // maybe wrong define, but unused anyway + { ANIM_FPS_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_BAT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_UZI, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_PUMP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_AK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_M16, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_FPS_ROCKET, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +#endif + { ANIM_STD_FIGHT_IDLE, ASSOC_REPEAT }, + { ANIM_STD_FIGHT_2IDLE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_SHUFFLE_F, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_FIGHT_BODYBLOW, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_HEAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_KICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_KNEE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_LHOOK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_ROUNDHOUSE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_FIGHT_LONGKICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_PARTIAL_PUNCH, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_JACKEDCAR_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_JACKEDCAR_LO_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_JACKEDCAR_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_JACKEDCAR_LO_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_QUICKJACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_QUICKJACKED, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_ALIGN_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_ALIGNHI_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_OPEN_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CARDOOR_LOCKED_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_PULL_OUT_PED_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_PULL_OUT_PED_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_GET_IN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_GET_IN_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_GETOUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_GETOUT_LO_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_ALIGN_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_ALIGNHI_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_OPEN_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CARDOOR_LOCKED_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_PULL_OUT_PED_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_PULL_OUT_PED_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_GET_IN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_GET_IN_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_DOOR_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_SHUFFLE_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_SHUFFLE_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_SIT, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_CAR_SIT_LO, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_CAR_SIT_P, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_CAR_SIT_P_LO, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_CAR_DRIVE_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVE_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVE_LEFT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVE_RIGHT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVEBY_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_DRIVEBY_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_CAR_LOOKBEHIND, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_BOAT_DRIVE, ASSOC_DELETEFADEDOUT }, + { ANIM_STD_GETOUT_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_GETOUT_LO_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_CLOSE_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CAR_HOOKERTALK, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_COACH_OPEN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_COACH_OPEN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_COACH_GET_IN_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_COACH_GET_IN_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_COACH_GET_OUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_TRAIN_GETIN, ASSOC_PARTIAL }, + { ANIM_STD_TRAIN_GETOUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CRAWLOUT_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_CRAWLOUT_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_OPEN_DOOR_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_GET_IN_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_GET_OUT_REAR_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_OPEN_DOOR_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_GET_IN_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_VAN_GET_OUT_REAR_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_GET_UP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_GET_UP_LEFT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_GET_UP_RIGHT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_GET_UP_FRONT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_JUMP_LAUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_JUMP_GLIDE, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_JUMP_LAND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_FALL, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_FALL_GLIDE, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_FALL_LAND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_FALL_COLLAPSE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_EVADE_STEP, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_EVADE_DIVE, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_FRONTAL }, + { ANIM_STD_XPRESS_SCRATCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_IDLE }, + { ANIM_STD_ROADCROSS, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_STD_TURN180, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_ARREST, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_DROWN, ASSOC_PARTIAL }, + { ANIM_MEDIC_CPR, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_DUCK_DOWN, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_DUCK_LOW, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_RBLOCK_SHOOT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_THROW_UNDER2, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_HANDSUP, ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_HANDSCOWER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_PARTIAL_FUCKU, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_STD_PHONE_IN, ASSOC_PARTIAL }, + { ANIM_STD_PHONE_OUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_PHONE_TALK, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, +}; +#ifdef PC_PLAYER_CONTROLS +AnimAssocDesc aStdAnimDescsSide[] = { + { ANIM_STD_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK | ASSOC_HAS_X_TRANSLATION }, + { ANIM_STD_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK | ASSOC_HAS_X_TRANSLATION }, + { ANIM_STD_RUNFAST, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_WALK | ASSOC_HAS_X_TRANSLATION }, + { ANIM_STD_IDLE, ASSOC_REPEAT }, + { ANIM_STD_STARTWALK, ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION }, +}; +#endif +char const *aStdAnimations[] = { + "walk_civi", + "run_civi", + "sprint_panic", + "idle_stance", + "walk_start", + "run_stop", + "run_stopR", + "idle_cam", + "idle_hbhb", + "idle_tired", + "idle_armed", + "idle_chat", + "idle_taxi", + "KO_shot_front", + "KO_shot_front", + "KO_shot_front", + "KO_shot_front", + "KO_shot_face", + "KO_shot_stom", + "KO_shot_arml", + "KO_shot_armR", + "KO_shot_legl", + "KO_shot_legR", + "KD_left", + "KD_right", + "KO_skid_front", + "KO_spin_R", + "KO_skid_back", + "KO_spin_L", + "SHOT_partial", + "SHOT_leftP", + "SHOT_partial", + "SHOT_rightP", + "HIT_front", + "HIT_L", + "HIT_back", + "HIT_R", + "FLOOR_hit", +#if GTA_VERSION <= GTA3_PS2_160 + "HIT_body", +#endif + "HIT_bodyblow", + "HIT_chest", + "HIT_head", + "HIT_walk", + "HIT_wall", + "FLOOR_hit_f", + "HIT_behind", + "punchR", + "KICK_floor", + "WEAPON_bat_h", + "WEAPON_bat_v", + "WEAPON_hgun_body", + "WEAPON_AK_body", + "WEAPON_pump", + "WEAPON_sniper", + "WEAPON_throw", + "WEAPON_throwu", + "WEAPON_start_throw", + "bomber", + "WEAPON_hgun_rload", + "WEAPON_AK_rload", +#ifdef PC_PLAYER_CONTROLS + // maybe wrong define, but unused anyway + "FPS_PUNCH", + "FPS_BAT", + "FPS_UZI", + "FPS_PUMP", + "FPS_AK", + "FPS_M16", + "FPS_ROCKET", +#endif + "FIGHTIDLE", + "FIGHT2IDLE", + "FIGHTsh_F", + "FIGHTbodyblow", + "FIGHThead", + "FIGHTkick", + "FIGHTknee", + "FIGHTLhook", + "FIGHTpunch", + "FIGHTrndhse", + "FIGHTlngkck", + "FIGHTppunch", + "car_jackedRHS", + "car_LjackedRHS", + "car_jackedLHS", + "car_LjackedLHS", + "CAR_Qjack", + "CAR_Qjacked", + "CAR_align_LHS", + "CAR_alignHI_LHS", + "CAR_open_LHS", + "CAR_doorlocked_LHS", + "CAR_pullout_LHS", + "CAR_pulloutL_LHS", + "CAR_getin_LHS", + "CAR_getinL_LHS", + "CAR_closedoor_LHS", + "CAR_closedoorL_LHS", + "CAR_rolldoor", + "CAR_rolldoorLO", + "CAR_getout_LHS", + "CAR_getoutL_LHS", + "CAR_close_LHS", + "CAR_align_RHS", + "CAR_alignHI_RHS", + "CAR_open_RHS", + "CAR_doorlocked_RHS", + "CAR_pullout_RHS", + "CAR_pulloutL_RHS", + "CAR_getin_RHS", + "CAR_getinL_RHS", + "CAR_closedoor_RHS", + "CAR_closedoorL_RHS", + "CAR_shuffle_RHS", + "CAR_Lshuffle_RHS", + "CAR_sit", + "CAR_Lsit", + "CAR_sitp", + "CAR_sitpLO", + "DRIVE_L", + "Drive_R", + "Drive_LO_l", + "Drive_LO_R", + "Driveby_L", + "Driveby_R", + "CAR_LB", + "DRIVE_BOAT", + "CAR_getout_RHS", + "CAR_getoutL_RHS", + "CAR_close_RHS", + "car_hookertalk", + "COACH_opnL", + "COACH_opnR", + "COACH_inL", + "COACH_inR", + "COACH_outL", + "TRAIN_getin", + "TRAIN_getout", + "CAR_crawloutRHS", + "CAR_crawloutRHS", + "VAN_openL", + "VAN_getinL", + "VAN_closeL", + "VAN_getoutL", + "VAN_open", + "VAN_getin", + "VAN_close", + "VAN_getout", + "Getup", + "Getup", + "Getup", + "Getup_front", + "JUMP_launch", + "JUMP_glide", + "JUMP_land", + "FALL_fall", + "FALL_glide", + "FALL_land", + "FALL_collapse", + "EV_step", + "EV_dive", + "XPRESSscratch", + "roadcross", + "TURN_180", + "ARRESTgun", + "DROWN", + "CPR", + "DUCK_down", + "DUCK_low", + "RBLOCK_Cshoot", + "WEAPON_throwu", + "handsup", + "handsCOWER", + "FUCKU", + "PHONE_in", + "PHONE_out", + "PHONE_talk", +}; +char const *aPlayerAnimations[] = { + "walk_player", + "run_player", + "SPRINT_civi", + "IDLE_STANCE", + "walk_start", +}; +char const *aPlayerWithRocketAnimations[] = { + "walk_rocket", + "run_rocket", + "run_rocket", + "idle_rocket", + "walk_start_rocket", +}; +char const *aPlayer1ArmedAnimations[] = { + "walk_player", + "run_1armed", + "SPRINT_civi", + "IDLE_STANCE", + "walk_start", +}; +char const *aPlayer2ArmedAnimations[] = { + "walk_player", + "run_armed", + "run_armed", + "idle_stance", + "walk_start", +}; +char const *aPlayerBBBatAnimations[] = { + "walk_player", + "run_player", + "run_player", + "IDLE_STANCE", + "walk_start", +}; +char const *aShuffleAnimations[] = { + "WALK_shuffle", + "RUN_civi", + "SPRINT_civi", + "IDLE_STANCE", +}; +char const *aOldAnimations[] = { + "walk_old", + "run_civi", + "sprint_civi", + "idle_stance", +}; +char const *aGang1Animations[] = { + "walk_gang1", + "run_gang1", + "sprint_civi", + "idle_stance", +}; +char const *aGang2Animations[] = { + "walk_gang2", + "run_gang1", + "sprint_civi", + "idle_stance", +}; +char const *aFatAnimations[] = { + "walk_fat", + "run_civi", + "woman_runpanic", + "idle_stance", +}; +char const *aOldFatAnimations[] = { + "walk_fatold", + "run_fatold", + "woman_runpanic", + "idle_stance", +}; +char const *aStdWomanAnimations[] = { + "woman_walknorm", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aWomanShopAnimations[] = { + "woman_walkshop", + "woman_run", + "woman_run", + "woman_idlestance", +}; +char const *aBusyWomanAnimations[] = { + "woman_walkbusy", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aSexyWomanAnimations[] = { + "woman_walksexy", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aOldWomanAnimations[] = { + "woman_walkold", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aFatWomanAnimations[] = { + "walk_fat", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const *aPanicChunkyAnimations[] = { + "run_fatold", + "woman_runpanic", + "woman_runpanic", + "idle_stance", +}; +#ifdef PC_PLAYER_CONTROLS +char const *aPlayerStrafeBackAnimations[] = { + "walk_player_back", + "run_player_back", + "run_player_back", + "IDLE_STANCE", + "walk_start_back", +}; +char const *aPlayerStrafeLeftAnimations[] = { + "walk_player_left", + "run_left", + "run_left", + "IDLE_STANCE", + "walk_start_left", +}; +char const *aPlayerStrafeRightAnimations[] = { + "walk_player_right", + "run_right", + "run_right", + "IDLE_STANCE", + "walk_start_right", +}; +char const *aRocketStrafeBackAnimations[] = { + "walk_rocket_back", + "run_rocket_back", + "run_rocket_back", + "idle_rocket", + "walkst_rocket_back", +}; +char const *aRocketStrafeLeftAnimations[] = { + "walk_rocket_left", + "run_rocket_left", + "run_rocket_left", + "idle_rocket", + "walkst_rocket_left", +}; +char const *aRocketStrafeRightAnimations[] = { + "walk_rocket_right", + "run_rocket_right", + "run_rocket_right", + "idle_rocket", + "walkst_rocket_right", +}; +#endif + +#define awc(a) ARRAY_SIZE(a), a +const AnimAssocDefinition CAnimManager::ms_aAnimAssocDefinitions[NUM_ANIM_ASSOC_GROUPS] = { + { "man", "ped", MI_COP, awc(aStdAnimations), aStdAnimDescs }, + { "player", "ped", MI_COP, awc(aPlayerAnimations), aStdAnimDescs }, + { "playerrocket", "ped", MI_COP, awc(aPlayerWithRocketAnimations), aStdAnimDescs }, + { "player1armed", "ped", MI_COP, awc(aPlayer1ArmedAnimations), aStdAnimDescs }, + { "player2armed", "ped", MI_COP, awc(aPlayer2ArmedAnimations), aStdAnimDescs }, + { "playerBBBat", "ped", MI_COP, awc(aPlayerBBBatAnimations), aStdAnimDescs }, + { "shuffle", "ped", MI_COP, awc(aShuffleAnimations), aStdAnimDescs }, + { "oldman", "ped", MI_COP, awc(aOldAnimations), aStdAnimDescs }, + { "gang1", "ped", MI_COP, awc(aGang1Animations), aStdAnimDescs }, + { "gang2", "ped", MI_COP, awc(aGang2Animations), aStdAnimDescs }, + { "fatman", "ped", MI_COP, awc(aFatAnimations), aStdAnimDescs }, + { "oldfatman", "ped", MI_COP, awc(aOldFatAnimations), aStdAnimDescs }, + { "woman", "ped", MI_COP, awc(aStdWomanAnimations), aStdAnimDescs }, + { "shopping", "ped", MI_COP, awc(aWomanShopAnimations), aStdAnimDescs }, + { "busywoman", "ped", MI_COP, awc(aBusyWomanAnimations), aStdAnimDescs }, + { "sexywoman", "ped", MI_COP, awc(aSexyWomanAnimations), aStdAnimDescs }, + { "oldwoman", "ped", MI_COP, awc(aOldWomanAnimations), aStdAnimDescs }, + { "fatwoman", "ped", MI_COP, awc(aFatWomanAnimations), aStdAnimDescs }, + { "panicchunky", "ped", MI_COP, awc(aPanicChunkyAnimations), aStdAnimDescs }, +#ifdef PC_PLAYER_CONTROLS + { "playerback", "ped", MI_COP, awc(aPlayerStrafeBackAnimations), aStdAnimDescs }, + { "playerleft", "ped", MI_COP, awc(aPlayerStrafeLeftAnimations), aStdAnimDescsSide }, + { "playerright", "ped", MI_COP, awc(aPlayerStrafeRightAnimations), aStdAnimDescsSide }, + { "rocketback", "ped", MI_COP, awc(aRocketStrafeBackAnimations), aStdAnimDescs }, + { "rocketleft", "ped", MI_COP, awc(aRocketStrafeLeftAnimations), aStdAnimDescsSide }, + { "rocketright", "ped", MI_COP, awc(aRocketStrafeRightAnimations), aStdAnimDescsSide }, +#endif +}; +#undef awc + +void +CAnimManager::Initialise(void) +{ + ms_numAnimations = 0; + ms_numAnimBlocks = 0; + ms_animCache.Init(25); + +// dumpanimdata(); +} + +void +CAnimManager::Shutdown(void) +{ + int i; + + ms_animCache.Shutdown(); + + for(i = 0; i < ms_numAnimations; i++) + ms_aAnimations[i].Shutdown(); + + delete[] ms_aAnimAssocGroups; +} + +void +CAnimManager::UncompressAnimation(CAnimBlendHierarchy *hier) +{ + if(!hier->compressed){ + if(hier->linkPtr){ + hier->linkPtr->Remove(); + ms_animCache.head.Insert(hier->linkPtr); + } + }else{ + CLink *link = ms_animCache.Insert(hier); + if(link == nil){ + ms_animCache.tail.prev->item->RemoveUncompressedData(); + ms_animCache.Remove(ms_animCache.tail.prev); + link = ms_animCache.Insert(hier); + } + hier->linkPtr = link; + hier->Uncompress(); + } +} + +CAnimBlock* +CAnimManager::GetAnimationBlock(const char *name) +{ + int i; + + for(i = 0; i < ms_numAnimBlocks; i++) + if(strcasecmp(ms_aAnimBlocks[i].name, name) == 0) + return &ms_aAnimBlocks[i]; + return nil; +} + +CAnimBlendHierarchy* +CAnimManager::GetAnimation(const char *name, CAnimBlock *animBlock) +{ + int i; + CAnimBlendHierarchy *hier = &ms_aAnimations[animBlock->firstIndex]; + + for(i = 0; i < animBlock->numAnims; i++){ + if(!CGeneral::faststricmp(hier->name, name)) + return hier; + hier++; + } + return nil; +} + +const char* +CAnimManager::GetAnimGroupName(AssocGroupId groupId) +{ + return ms_aAnimAssocDefinitions[groupId].name; +} + +CAnimBlendAssociation* +CAnimManager::CreateAnimAssociation(AssocGroupId groupId, AnimationId animId) +{ + return ms_aAnimAssocGroups[groupId].CopyAnimation(animId); +} + +CAnimBlendAssociation* +CAnimManager::GetAnimAssociation(AssocGroupId groupId, AnimationId animId) +{ + return ms_aAnimAssocGroups[groupId].GetAnimation(animId); +} + +CAnimBlendAssociation* +CAnimManager::GetAnimAssociation(AssocGroupId groupId, const char *name) +{ + return ms_aAnimAssocGroups[groupId].GetAnimation(name); +} + +CAnimBlendAssociation* +CAnimManager::AddAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId) +{ + CAnimBlendAssociation *anim = CreateAnimAssociation(groupId, animId); + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + if(anim->IsMovement()){ + CAnimBlendAssociation *syncanim = nil; + CAnimBlendLink *link; + for(link = clumpData->link.next; link; link = link->next){ + syncanim = CAnimBlendAssociation::FromLink(link); + if(syncanim->IsMovement()) + break; + } + if(link){ + anim->SyncAnimation(syncanim); + anim->flags |= ASSOC_RUNNING; + }else + anim->Start(0.0f); + }else + anim->Start(0.0f); + + clumpData->link.Prepend(&anim->link); + return anim; +} + +CAnimBlendAssociation* +CAnimManager::AddAnimationAndSync(RpClump *clump, CAnimBlendAssociation *syncanim, AssocGroupId groupId, AnimationId animId) +{ + CAnimBlendAssociation *anim = CreateAnimAssociation(groupId, animId); + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + if (anim->IsMovement() && syncanim){ + anim->SyncAnimation(syncanim); + anim->flags |= ASSOC_RUNNING; + }else + anim->Start(0.0f); + + clumpData->link.Prepend(&anim->link); + return anim; +} + +CAnimBlendAssociation* +CAnimManager::BlendAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId, float delta) +{ + int removePrevAnim = 0; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + CAnimBlendAssociation *anim = GetAnimAssociation(groupId, animId); + bool isMovement = anim->IsMovement(); + bool isPartial = anim->IsPartial(); + CAnimBlendLink *link; + CAnimBlendAssociation *found = nil, *movementAnim = nil; + for(link = clumpData->link.next; link; link = link->next){ + anim = CAnimBlendAssociation::FromLink(link); + if(isMovement && anim->IsMovement()) + movementAnim = anim; + if(anim->animId == animId) + found = anim; + else{ + if(isPartial == anim->IsPartial()){ + if(anim->blendAmount > 0.0f){ + float blendDelta = -delta*anim->blendAmount; + if(blendDelta < anim->blendDelta || !isPartial) + anim->blendDelta = blendDelta; + }else{ + anim->blendDelta = -1.0f; + } + anim->flags |= ASSOC_DELETEFADEDOUT; + removePrevAnim = 1; + } + } + } + if(found){ + found->blendDelta = (1.0f - found->blendAmount)*delta; + if(!found->IsRunning() && found->currentTime == found->hierarchy->totalLength) + found->Start(0.0f); + }else{ + found = AddAnimationAndSync(clump, movementAnim, groupId, animId); + if(!removePrevAnim && !isPartial){ + found->blendAmount = 1.0f; + return found; + } + found->blendAmount = 0.0f; + found->blendDelta = delta; + } + UncompressAnimation(found->hierarchy); + return found; +} + +void +CAnimManager::LoadAnimFiles(void) +{ + int i, j; + + LoadAnimFile("ANIM\\PED.IFP"); + + // Create all assoc groups + ms_aAnimAssocGroups = new CAnimBlendAssocGroup[NUM_ANIM_ASSOC_GROUPS]; + for(i = 0; i < NUM_ANIM_ASSOC_GROUPS; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(ms_aAnimAssocDefinitions[i].modelIndex); + RpClump *clump = (RpClump*)mi->CreateInstance(); + RpAnimBlendClumpInit(clump); + CAnimBlendAssocGroup *group = &ms_aAnimAssocGroups[i]; + const AnimAssocDefinition *def = &ms_aAnimAssocDefinitions[i]; + group->CreateAssociations(def->blockName, clump, def->animNames, def->numAnims); + for(j = 0; j < group->numAssociations; j++) + group->GetAnimation(j)->flags |= def->animDescs[j].flags; +#ifdef PED_SKIN + // forgot on xbox/android + if(IsClumpSkinned(clump)) + RpClumpForAllAtomics(clump, AtomicRemoveAnimFromSkinCB, nil); +#endif + RpClumpDestroy(clump); + } +} + +void +CAnimManager::LoadAnimFile(const char *filename) +{ + int fd; + fd = CFileMgr::OpenFile(filename, "rb"); + assert(fd > 0); + LoadAnimFile(fd, true); + CFileMgr::CloseFile(fd); +} + +void +CAnimManager::LoadAnimFile(int fd, bool compress) +{ + #define ROUNDSIZE(x) if((x) & 3) (x) += 4 - ((x)&3) + struct IfpHeader { + char ident[4]; + uint32 size; + }; + IfpHeader anpk, info, name, dgan, cpan, anim; + int numANPK; + char buf[256]; + int i, j, k, l; + float *fbuf = (float*)buf; + + CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader)); + if(!CGeneral::faststrncmp(anpk.ident, "ANLF", 4)) { + ROUNDSIZE(anpk.size); + CFileMgr::Read(fd, buf, anpk.size); + numANPK = *(int*)buf; + } else if(!CGeneral::faststrncmp(anpk.ident, "ANPK", 4)) { + CFileMgr::Seek(fd, -8, 1); + numANPK = 1; + } + + for(i = 0; i < numANPK; i++){ + // block name + CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader)); + ROUNDSIZE(anpk.size); + CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader)); + ROUNDSIZE(info.size); + CFileMgr::Read(fd, buf, info.size); + CAnimBlock *animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++]; + strncpy(animBlock->name, buf+4, 24); + animBlock->numAnims = *(int*)buf; + + animBlock->firstIndex = ms_numAnimations; + + for(j = 0; j < animBlock->numAnims; j++){ + CAnimBlendHierarchy *hier = &ms_aAnimations[ms_numAnimations++]; + + // animation name + CFileMgr::Read(fd, (char*)&name, sizeof(IfpHeader)); + ROUNDSIZE(name.size); + CFileMgr::Read(fd, buf, name.size); + hier->SetName(buf); + + // DG info has number of nodes/sequences + CFileMgr::Read(fd, (char*)&dgan, sizeof(IfpHeader)); + ROUNDSIZE(dgan.size); + CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader)); + ROUNDSIZE(info.size); + CFileMgr::Read(fd, buf, info.size); + hier->numSequences = *(int*)buf; + hier->sequences = new CAnimBlendSequence[hier->numSequences]; + + CAnimBlendSequence *seq = hier->sequences; + for(k = 0; k < hier->numSequences; k++, seq++){ + // Each node has a name and key frames + CFileMgr::Read(fd, (char*)&cpan, sizeof(IfpHeader)); + ROUNDSIZE(dgan.size); + CFileMgr::Read(fd, (char*)&anim, sizeof(IfpHeader)); + ROUNDSIZE(anim.size); + CFileMgr::Read(fd, buf, anim.size); + int numFrames = *(int*)(buf+28); + seq->SetName(buf); +#ifdef PED_SKIN + if(anim.size == 44) + seq->SetBoneTag(*(int*)(buf+40)); +#endif + if(numFrames == 0) + continue; + + bool hasScale = false; + bool hasTranslation = false; + CFileMgr::Read(fd, (char*)&info, sizeof(info)); + if(!CGeneral::faststrncmp(info.ident, "KRTS", 4)) { + hasScale = true; + seq->SetNumFrames(numFrames, true); + }else if(!CGeneral::faststrncmp(info.ident, "KRT0", 4)) { + hasTranslation = true; + seq->SetNumFrames(numFrames, true); + }else if(!CGeneral::faststrncmp(info.ident, "KR00", 4)){ + seq->SetNumFrames(numFrames, false); + } + + for(l = 0; l < numFrames; l++){ + if(hasScale){ + CFileMgr::Read(fd, buf, 0x2C); + CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); + rot.Invert(); + CVector trans(fbuf[4], fbuf[5], fbuf[6]); + + KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l); + kf->rotation = rot; + kf->translation = trans; + // scaling ignored + kf->deltaTime = fbuf[10]; // absolute time here + }else if(hasTranslation){ + CFileMgr::Read(fd, buf, 0x20); + CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); + rot.Invert(); + CVector trans(fbuf[4], fbuf[5], fbuf[6]); + + KeyFrameTrans *kf = (KeyFrameTrans*)seq->GetKeyFrame(l); + kf->rotation = rot; + kf->translation = trans; + kf->deltaTime = fbuf[7]; // absolute time here + }else{ + CFileMgr::Read(fd, buf, 0x14); + CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); + rot.Invert(); + + KeyFrame *kf = (KeyFrame*)seq->GetKeyFrame(l); + kf->rotation = rot; + kf->deltaTime = fbuf[4]; // absolute time here + } + } + + // convert absolute time to deltas + for(l = seq->numFrames-1; l > 0; l--){ + KeyFrame *kf1 = seq->GetKeyFrame(l); + KeyFrame *kf2 = seq->GetKeyFrame(l-1); + kf1->deltaTime -= kf2->deltaTime; + } + } + + hier->RemoveQuaternionFlips(); + if(compress) + hier->RemoveUncompressedData(); + else + hier->CalcTotalTime(); + } + } +} + +void +CAnimManager::RemoveLastAnimFile(void) +{ + int i; + ms_numAnimBlocks--; + ms_numAnimations = ms_aAnimBlocks[ms_numAnimBlocks].firstIndex; + for(i = 0; i < ms_aAnimBlocks[ms_numAnimBlocks].numAnims; i++) + ms_aAnimations[ms_aAnimBlocks[ms_numAnimBlocks].firstIndex + i].RemoveAnimSequences(); +} diff --git a/src/animation/AnimManager.h b/src/animation/AnimManager.h new file mode 100644 index 0000000..92192c7 --- /dev/null +++ b/src/animation/AnimManager.h @@ -0,0 +1,94 @@ +#pragma once + +#include "AnimBlendHierarchy.h" +#include "AnimationId.h" + +enum AssocGroupId +{ + ASSOCGRP_STD, + ASSOCGRP_PLAYER, + ASSOCGRP_PLAYERROCKET, + ASSOCGRP_PLAYER1ARMED, + ASSOCGRP_PLAYER2ARMED, + ASSOCGRP_PLAYERBBBAT, + ASSOCGRP_SHUFFLE, + ASSOCGRP_OLD, + ASSOCGRP_GANG1, + ASSOCGRP_GANG2, + ASSOCGRP_FAT, + ASSOCGRP_OLDFAT, + ASSOCGRP_WOMAN, + ASSOCGRP_WOMANSHOP, + ASSOCGRP_BUSYWOMAN, + ASSOCGRP_SEXYWOMAN, + ASSOCGRP_OLDWOMAN, + ASSOCGRP_FATWOMAN, + ASSOCGRP_PANICCHUNKY, +#ifdef PC_PLAYER_CONTROLS + ASSOCGRP_PLAYERBACK, + ASSOCGRP_PLAYERLEFT, + ASSOCGRP_PLAYERRIGHT, + ASSOCGRP_ROCKETBACK, + ASSOCGRP_ROCKETLEFT, + ASSOCGRP_ROCKETRIGHT, +#endif + + NUM_ANIM_ASSOC_GROUPS +}; + +class CAnimBlendAssociation; +class CAnimBlendAssocGroup; + +// A block of hierarchies +struct CAnimBlock +{ + char name[24]; + int32 firstIndex; + int32 numAnims; +}; + +struct AnimAssocDesc +{ + int32 animId; + int32 flags; +}; + +struct AnimAssocDefinition +{ + char const *name; + char const *blockName; + int32 modelIndex; + int32 numAnims; + char const **animNames; + AnimAssocDesc *animDescs; +}; + +class CAnimManager +{ + static const AnimAssocDefinition ms_aAnimAssocDefinitions[NUM_ANIM_ASSOC_GROUPS]; + static CAnimBlock ms_aAnimBlocks[NUMANIMBLOCKS]; + static CAnimBlendHierarchy ms_aAnimations[NUMANIMATIONS]; + static int32 ms_numAnimBlocks; + static int32 ms_numAnimations; + static CAnimBlendAssocGroup *ms_aAnimAssocGroups; + static CLinkList ms_animCache; +public: + + static void Initialise(void); + static void Shutdown(void); + static void UncompressAnimation(CAnimBlendHierarchy *anim); + static CAnimBlock *GetAnimationBlock(const char *name); + static CAnimBlendHierarchy *GetAnimation(const char *name, CAnimBlock *animBlock); + static CAnimBlendHierarchy *GetAnimation(int32 n) { return &ms_aAnimations[n]; } + static const char *GetAnimGroupName(AssocGroupId groupId); + static CAnimBlendAssociation *CreateAnimAssociation(AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *GetAnimAssociation(AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *GetAnimAssociation(AssocGroupId groupId, const char *name); + static CAnimBlendAssociation *AddAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *AddAnimationAndSync(RpClump *clump, CAnimBlendAssociation *syncanim, AssocGroupId groupId, AnimationId animId); + static CAnimBlendAssociation *BlendAnimation(RpClump *clump, AssocGroupId groupId, AnimationId animId, float delta); + static void LoadAnimFiles(void); + static void LoadAnimFile(const char *filename); + static void LoadAnimFile(int fd, bool compress); + static void RemoveLastAnimFile(void); +}; diff --git a/src/animation/AnimationId.h b/src/animation/AnimationId.h new file mode 100644 index 0000000..baf6eb3 --- /dev/null +++ b/src/animation/AnimationId.h @@ -0,0 +1,210 @@ +#pragma once + +enum AnimationId +{ + ANIM_STD_WALK, + ANIM_STD_RUN, + ANIM_STD_RUNFAST, + ANIM_STD_IDLE, + ANIM_STD_STARTWALK, + ANIM_STD_RUNSTOP1, + ANIM_STD_RUNSTOP2, + ANIM_STD_IDLE_CAM, + ANIM_STD_IDLE_HBHB, + ANIM_STD_IDLE_TIRED, + ANIM_STD_IDLE_BIGGUN, + ANIM_STD_CHAT, + ANIM_STD_HAILTAXI, + ANIM_STD_KO_FRONT, + ANIM_STD_KO_LEFT, + ANIM_STD_KO_BACK, + ANIM_STD_KO_RIGHT, + ANIM_STD_KO_SHOT_FACE, + ANIM_STD_KO_SHOT_STOMACH, + ANIM_STD_KO_SHOT_ARM_L, + ANIM_STD_KO_SHOT_ARM_R, + ANIM_STD_KO_SHOT_LEG_L, + ANIM_STD_KO_SHOT_LEG_R, + ANIM_STD_SPINFORWARD_LEFT, + ANIM_STD_SPINFORWARD_RIGHT, + ANIM_STD_HIGHIMPACT_FRONT, + ANIM_STD_HIGHIMPACT_LEFT, + ANIM_STD_HIGHIMPACT_BACK, + ANIM_STD_HIGHIMPACT_RIGHT, + ANIM_STD_HITBYGUN_FRONT, + ANIM_STD_HITBYGUN_LEFT, + ANIM_STD_HITBYGUN_BACK, + ANIM_STD_HITBYGUN_RIGHT, + ANIM_STD_HIT_FRONT, + ANIM_STD_HIT_LEFT, + ANIM_STD_HIT_BACK, + ANIM_STD_HIT_RIGHT, + ANIM_STD_HIT_FLOOR, + + /* names made up */ +#if GTA_VERSION <= GTA3_PS2_160 + ANIM_STD_HIT_BODY, +#endif + ANIM_STD_HIT_BODYBLOW, + ANIM_STD_HIT_CHEST, + ANIM_STD_HIT_HEAD, + ANIM_STD_HIT_WALK, + /**/ + + ANIM_STD_HIT_WALL, + ANIM_STD_HIT_FLOOR_FRONT, + ANIM_STD_HIT_BEHIND, + ANIM_STD_PUNCH, + ANIM_STD_KICKGROUND, + + /* names made up */ + ANIM_STD_WEAPON_BAT_H, + ANIM_STD_WEAPON_BAT_V, + ANIM_STD_WEAPON_HGUN_BODY, + ANIM_STD_WEAPON_AK_BODY, + ANIM_STD_WEAPON_PUMP, + ANIM_STD_WEAPON_SNIPER, + ANIM_STD_WEAPON_THROW, + /**/ + + ANIM_STD_THROW_UNDER, + + /* names made up */ + ANIM_STD_START_THROW, + /**/ + + ANIM_STD_DETONATE, + + /* names made up */ + ANIM_STD_HGUN_RELOAD, + ANIM_STD_AK_RELOAD, +#ifdef PC_PLAYER_CONTROLS + // maybe wrong define, but unused anyway + ANIM_FPS_PUNCH, + ANIM_FPS_BAT, + ANIM_FPS_UZI, + ANIM_FPS_PUMP, + ANIM_FPS_AK, + ANIM_FPS_M16, + ANIM_FPS_ROCKET, +#endif + /**/ + + ANIM_STD_FIGHT_IDLE, + ANIM_STD_FIGHT_2IDLE, + ANIM_STD_FIGHT_SHUFFLE_F, + + /* names made up */ + ANIM_STD_FIGHT_BODYBLOW, + ANIM_STD_FIGHT_HEAD, + ANIM_STD_FIGHT_KICK, + ANIM_STD_FIGHT_KNEE, + ANIM_STD_FIGHT_LHOOK, + ANIM_STD_FIGHT_PUNCH, + ANIM_STD_FIGHT_ROUNDHOUSE, + ANIM_STD_FIGHT_LONGKICK, + /**/ + + ANIM_STD_PARTIAL_PUNCH, + ANIM_STD_JACKEDCAR_RHS, + ANIM_STD_JACKEDCAR_LO_RHS, + ANIM_STD_JACKEDCAR_LHS, + ANIM_STD_JACKEDCAR_LO_LHS, + ANIM_STD_QUICKJACK, + ANIM_STD_QUICKJACKED, + ANIM_STD_CAR_ALIGN_DOOR_LHS, + ANIM_STD_CAR_ALIGNHI_DOOR_LHS, + ANIM_STD_CAR_OPEN_DOOR_LHS, + ANIM_STD_CARDOOR_LOCKED_LHS, + ANIM_STD_CAR_PULL_OUT_PED_LHS, + ANIM_STD_CAR_PULL_OUT_PED_LO_LHS, + ANIM_STD_CAR_GET_IN_LHS, + ANIM_STD_CAR_GET_IN_LO_LHS, + ANIM_STD_CAR_CLOSE_DOOR_LHS, + ANIM_STD_CAR_CLOSE_DOOR_LO_LHS, + ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, + ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS, + ANIM_STD_GETOUT_LHS, + ANIM_STD_GETOUT_LO_LHS, + ANIM_STD_CAR_CLOSE_LHS, + ANIM_STD_CAR_ALIGN_DOOR_RHS, + ANIM_STD_CAR_ALIGNHI_DOOR_RHS, + ANIM_STD_CAR_OPEN_DOOR_RHS, + ANIM_STD_CARDOOR_LOCKED_RHS, + ANIM_STD_CAR_PULL_OUT_PED_RHS, + ANIM_STD_CAR_PULL_OUT_PED_LO_RHS, + ANIM_STD_CAR_GET_IN_RHS, + ANIM_STD_CAR_GET_IN_LO_RHS, + ANIM_STD_CAR_CLOSE_DOOR_RHS, + ANIM_STD_CAR_CLOSE_DOOR_LO_RHS, + ANIM_STD_CAR_SHUFFLE_RHS, + ANIM_STD_CAR_SHUFFLE_LO_RHS, + ANIM_STD_CAR_SIT, + ANIM_STD_CAR_SIT_LO, + ANIM_STD_CAR_SIT_P, + ANIM_STD_CAR_SIT_P_LO, + ANIM_STD_CAR_DRIVE_LEFT, + ANIM_STD_CAR_DRIVE_RIGHT, + ANIM_STD_CAR_DRIVE_LEFT_LO, + ANIM_STD_CAR_DRIVE_RIGHT_LO, + ANIM_STD_CAR_DRIVEBY_LEFT, + ANIM_STD_CAR_DRIVEBY_RIGHT, + ANIM_STD_CAR_LOOKBEHIND, + ANIM_STD_BOAT_DRIVE, + ANIM_STD_GETOUT_RHS, + ANIM_STD_GETOUT_LO_RHS, + ANIM_STD_CAR_CLOSE_RHS, + ANIM_STD_CAR_HOOKERTALK, + ANIM_STD_COACH_OPEN_LHS, + ANIM_STD_COACH_OPEN_RHS, + ANIM_STD_COACH_GET_IN_LHS, + ANIM_STD_COACH_GET_IN_RHS, + ANIM_STD_COACH_GET_OUT_LHS, + ANIM_STD_TRAIN_GETIN, + ANIM_STD_TRAIN_GETOUT, + ANIM_STD_CRAWLOUT_LHS, + ANIM_STD_CRAWLOUT_RHS, + ANIM_STD_VAN_OPEN_DOOR_REAR_LHS, + ANIM_STD_VAN_GET_IN_REAR_LHS, + ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS, + ANIM_STD_VAN_GET_OUT_REAR_LHS, + ANIM_STD_VAN_OPEN_DOOR_REAR_RHS, + ANIM_STD_VAN_GET_IN_REAR_RHS, + ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS, + ANIM_STD_VAN_GET_OUT_REAR_RHS, + ANIM_STD_GET_UP, + ANIM_STD_GET_UP_LEFT, + ANIM_STD_GET_UP_RIGHT, + ANIM_STD_GET_UP_FRONT, + ANIM_STD_JUMP_LAUNCH, + ANIM_STD_JUMP_GLIDE, + ANIM_STD_JUMP_LAND, + ANIM_STD_FALL, + ANIM_STD_FALL_GLIDE, + ANIM_STD_FALL_LAND, + ANIM_STD_FALL_COLLAPSE, + ANIM_STD_EVADE_STEP, + ANIM_STD_EVADE_DIVE, + ANIM_STD_XPRESS_SCRATCH, + ANIM_STD_ROADCROSS, + ANIM_STD_TURN180, + ANIM_STD_ARREST, + ANIM_STD_DROWN, + ANIM_MEDIC_CPR, + ANIM_STD_DUCK_DOWN, + ANIM_STD_DUCK_LOW, + ANIM_STD_RBLOCK_SHOOT, + + /* names made up */ + ANIM_STD_THROW_UNDER2, + /**/ + + ANIM_STD_HANDSUP, + ANIM_STD_HANDSCOWER, + ANIM_STD_PARTIAL_FUCKU, + ANIM_STD_PHONE_IN, + ANIM_STD_PHONE_OUT, + ANIM_STD_PHONE_TALK, + + ANIM_STD_NUM +}; \ No newline at end of file diff --git a/src/animation/Bones.cpp b/src/animation/Bones.cpp new file mode 100644 index 0000000..1608449 --- /dev/null +++ b/src/animation/Bones.cpp @@ -0,0 +1,52 @@ +#include "common.h" +#include "PedModelInfo.h" +#include "Bones.h" + +#ifdef PED_SKIN + +int +ConvertPedNode2BoneTag(int node) +{ + switch(node){ + case PED_TORSO: return BONE_waist; + case PED_MID: return BONE_torso; // this is what Xbox/Mobile use + // return BONE_mid; // this is what PS2/PC use + case PED_HEAD: return BONE_head; + case PED_UPPERARML: return BONE_upperarml; + case PED_UPPERARMR: return BONE_upperarmr; + case PED_HANDL: return BONE_Lhand; + case PED_HANDR: return BONE_Rhand; + case PED_UPPERLEGL: return BONE_upperlegl; + case PED_UPPERLEGR: return BONE_upperlegr; + case PED_FOOTL: return BONE_footl; + case PED_FOOTR: return BONE_footr; + case PED_LOWERLEGR: return BONE_lowerlegl; + } + return -1; +} + +const char* +ConvertBoneTag2BoneName(int tag) +{ + switch(tag){ + case BONE_waist: return "Swaist"; + case BONE_upperlegr: return "Supperlegr"; + case BONE_lowerlegr: return "Slowerlegr"; + case BONE_footr: return "Sfootr"; + case BONE_upperlegl: return "Supperlegl"; + case BONE_lowerlegl: return "Slowerlegl"; + case BONE_footl: return "Sfootl"; + case BONE_mid: return "Smid"; + case BONE_torso: return "Storso"; + case BONE_head: return "Shead"; + case BONE_upperarmr: return "Supperarmr"; + case BONE_lowerarmr: return "Slowerarmr"; + case BONE_Rhand: return "SRhand"; + case BONE_upperarml: return "Supperarml"; + case BONE_lowerarml: return "Slowerarml"; + case BONE_Lhand: return "SLhand"; + } + return nil; +} + +#endif diff --git a/src/animation/Bones.h b/src/animation/Bones.h new file mode 100644 index 0000000..38d91ba --- /dev/null +++ b/src/animation/Bones.h @@ -0,0 +1,24 @@ +#pragma once + +enum BoneTag +{ + BONE_waist, + BONE_upperlegr, + BONE_lowerlegr, + BONE_footr, + BONE_upperlegl, + BONE_lowerlegl, + BONE_footl, + BONE_mid, + BONE_torso, + BONE_head, + BONE_upperarmr, + BONE_lowerarmr, + BONE_Rhand, + BONE_upperarml, + BONE_lowerarml, + BONE_Lhand, +}; + +int ConvertPedNode2BoneTag(int node); +const char *ConvertBoneTag2BoneName(int tag); diff --git a/src/animation/CutsceneMgr.cpp b/src/animation/CutsceneMgr.cpp new file mode 100644 index 0000000..83c4dbc --- /dev/null +++ b/src/animation/CutsceneMgr.cpp @@ -0,0 +1,424 @@ +#include "common.h" + +#include "General.h" +#include "CutsceneMgr.h" +#include "Directory.h" +#include "Camera.h" +#include "Streaming.h" +#include "FileMgr.h" +#include "main.h" +#include "AnimManager.h" +#include "AnimBlendAssociation.h" +#include "AnimBlendAssocGroup.h" +#include "AnimBlendClumpData.h" +#include "Pad.h" +#include "DMAudio.h" +#include "World.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "CutsceneHead.h" +#include "RpAnimBlend.h" +#include "ModelIndices.h" +#include "TempColModels.h" + +const struct { + const char *szTrackName; + int iTrackId; +} musicNameIdAssoc[] = { + { "JB", STREAMED_SOUND_NEWS_INTRO }, + { "BET", STREAMED_SOUND_BANK_INTRO }, + { "L1_LG", STREAMED_SOUND_CUTSCENE_LUIGI1_LG }, + { "L2_DSB", STREAMED_SOUND_CUTSCENE_LUIGI2_DSB }, + { "L3_DM", STREAMED_SOUND_CUTSCENE_LUIGI3_DM }, + { "L4_PAP", STREAMED_SOUND_CUTSCENE_LUIGI4_PAP }, + { "L5_TFB", STREAMED_SOUND_CUTSCENE_LUIGI5_TFB }, + { "J0_DM2", STREAMED_SOUND_CUTSCENE_JOEY0_DM2 }, + { "J1_LFL", STREAMED_SOUND_CUTSCENE_JOEY1_LFL }, + { "J2_KCL", STREAMED_SOUND_CUTSCENE_JOEY2_KCL }, + { "J3_VH", STREAMED_SOUND_CUTSCENE_JOEY3_VH }, + { "J4_ETH", STREAMED_SOUND_CUTSCENE_JOEY4_ETH }, + { "J5_DST", STREAMED_SOUND_CUTSCENE_JOEY5_DST }, + { "J6_TBJ", STREAMED_SOUND_CUTSCENE_JOEY6_TBJ }, + { "T1_TOL", STREAMED_SOUND_CUTSCENE_TONI1_TOL }, + { "T2_TPU", STREAMED_SOUND_CUTSCENE_TONI2_TPU }, + { "T3_MAS", STREAMED_SOUND_CUTSCENE_TONI3_MAS }, + { "T4_TAT", STREAMED_SOUND_CUTSCENE_TONI4_TAT }, + { "T5_BF", STREAMED_SOUND_CUTSCENE_TONI5_BF }, + { "S0_MAS", STREAMED_SOUND_CUTSCENE_SAL0_MAS }, + { "S1_PF", STREAMED_SOUND_CUTSCENE_SAL1_PF }, + { "S2_CTG", STREAMED_SOUND_CUTSCENE_SAL2_CTG }, + { "S3_RTC", STREAMED_SOUND_CUTSCENE_SAL3_RTC }, + { "S5_LRQ", STREAMED_SOUND_CUTSCENE_SAL5_LRQ }, + { "S4_BDBA", STREAMED_SOUND_CUTSCENE_SAL4_BDBA }, + { "S4_BDBB", STREAMED_SOUND_CUTSCENE_SAL4_BDBB }, + { "S2_CTG2", STREAMED_SOUND_CUTSCENE_SAL2_CTG2 }, + { "S4_BDBD", STREAMED_SOUND_CUTSCENE_SAL4_BDBD }, + { "S5_LRQB", STREAMED_SOUND_CUTSCENE_SAL5_LRQB }, + { "S5_LRQC", STREAMED_SOUND_CUTSCENE_SAL5_LRQC }, + { "A1_SS0", STREAMED_SOUND_CUTSCENE_ASUKA_1_SSO }, + { "A2_PP", STREAMED_SOUND_CUTSCENE_ASUKA_2_PP }, + { "A3_SS", STREAMED_SOUND_CUTSCENE_ASUKA_3_SS }, + { "A4_PDR", STREAMED_SOUND_CUTSCENE_ASUKA_4_PDR }, + { "A5_K2FT", STREAMED_SOUND_CUTSCENE_ASUKA_5_K2FT}, + { "K1_KBO", STREAMED_SOUND_CUTSCENE_KENJI1_KBO }, + { "K2_GIS", STREAMED_SOUND_CUTSCENE_KENJI2_GIS }, + { "K3_DS", STREAMED_SOUND_CUTSCENE_KENJI3_DS }, + { "K4_SHI", STREAMED_SOUND_CUTSCENE_KENJI4_SHI }, + { "K5_SD", STREAMED_SOUND_CUTSCENE_KENJI5_SD }, + { "R0_PDR2", STREAMED_SOUND_CUTSCENE_RAY0_PDR2 }, + { "R1_SW", STREAMED_SOUND_CUTSCENE_RAY1_SW }, + { "R2_AP", STREAMED_SOUND_CUTSCENE_RAY2_AP }, + { "R3_ED", STREAMED_SOUND_CUTSCENE_RAY3_ED }, + { "R4_GF", STREAMED_SOUND_CUTSCENE_RAY4_GF }, + { "R5_PB", STREAMED_SOUND_CUTSCENE_RAY5_PB }, + { "R6_MM", STREAMED_SOUND_CUTSCENE_RAY6_MM }, + { "D1_STOG", STREAMED_SOUND_CUTSCENE_DONALD1_STOG }, + { "D2_KK", STREAMED_SOUND_CUTSCENE_DONALD2_KK }, + { "D3_ADO", STREAMED_SOUND_CUTSCENE_DONALD3_ADO }, + { "D5_ES", STREAMED_SOUND_CUTSCENE_DONALD5_ES }, + { "D7_MLD", STREAMED_SOUND_CUTSCENE_DONALD7_MLD }, + { "D4_GTA", STREAMED_SOUND_CUTSCENE_DONALD4_GTA }, + { "D4_GTA2", STREAMED_SOUND_CUTSCENE_DONALD4_GTA2 }, + { "D6_STS", STREAMED_SOUND_CUTSCENE_DONALD6_STS }, + { "A6_BAIT", STREAMED_SOUND_CUTSCENE_ASUKA6_BAIT }, + { "A7_ETG", STREAMED_SOUND_CUTSCENE_ASUKA7_ETG }, + { "A8_PS", STREAMED_SOUND_CUTSCENE_ASUKA8_PS }, + { "A9_ASD", STREAMED_SOUND_CUTSCENE_ASUKA9_ASD }, + { "K4_SHI2", STREAMED_SOUND_CUTSCENE_KENJI4_SHI2 }, + { "C1_TEX", STREAMED_SOUND_CUTSCENE_CATALINA1_TEX }, + { "EL_PH1", STREAMED_SOUND_CUTSCENE_ELBURRO1_PH1 }, + { "EL_PH2", STREAMED_SOUND_CUTSCENE_ELBURRO2_PH2 }, + { "EL_PH3", STREAMED_SOUND_CUTSCENE_ELBURRO3_PH3 }, + { "EL_PH4", STREAMED_SOUND_CUTSCENE_ELBURRO4_PH4 }, + { "YD_PH1", STREAMED_SOUND_CUTSCENE_YARDIE_PH1 }, + { "YD_PH2", STREAMED_SOUND_CUTSCENE_YARDIE_PH2 }, + { "YD_PH3", STREAMED_SOUND_CUTSCENE_YARDIE_PH3 }, + { "YD_PH4", STREAMED_SOUND_CUTSCENE_YARDIE_PH4 }, + { "HD_PH1", STREAMED_SOUND_CUTSCENE_HOODS_PH1 }, + { "HD_PH2", STREAMED_SOUND_CUTSCENE_HOODS_PH2 }, + { "HD_PH3", STREAMED_SOUND_CUTSCENE_HOODS_PH3 }, + { "HD_PH4", STREAMED_SOUND_CUTSCENE_HOODS_PH4 }, + { "HD_PH5", STREAMED_SOUND_CUTSCENE_HOODS_PH5 }, + { "MT_PH1", STREAMED_SOUND_CUTSCENE_MARTY_PH1 }, + { "MT_PH2", STREAMED_SOUND_CUTSCENE_MARTY_PH2 }, + { "MT_PH3", STREAMED_SOUND_CUTSCENE_MARTY_PH3 }, + { "MT_PH4", STREAMED_SOUND_CUTSCENE_MARTY_PH4 }, + { NULL, 0 } +}; + +int +FindCutsceneAudioTrackId(const char *szCutsceneName) +{ + for (int i = 0; musicNameIdAssoc[i].szTrackName; i++) { + if (!CGeneral::faststricmp(musicNameIdAssoc[i].szTrackName, szCutsceneName)) + return musicNameIdAssoc[i].iTrackId; + } + return -1; +} + +bool CCutsceneMgr::ms_running; +bool CCutsceneMgr::ms_cutsceneProcessing; +CDirectory *CCutsceneMgr::ms_pCutsceneDir; +CCutsceneObject *CCutsceneMgr::ms_pCutsceneObjects[NUMCUTSCENEOBJECTS]; +int32 CCutsceneMgr::ms_numCutsceneObjs; +bool CCutsceneMgr::ms_loaded; +bool CCutsceneMgr::ms_animLoaded; +bool CCutsceneMgr::ms_useLodMultiplier; +char CCutsceneMgr::ms_cutsceneName[CUTSCENENAMESIZE]; +CAnimBlendAssocGroup CCutsceneMgr::ms_cutsceneAssociations; +CVector CCutsceneMgr::ms_cutsceneOffset; +float CCutsceneMgr::ms_cutsceneTimer; +uint32 CCutsceneMgr::ms_cutsceneLoadStatus; + +RpAtomic * +CalculateBoundingSphereRadiusCB(RpAtomic *atomic, void *data) +{ + float radius = RpAtomicGetBoundingSphere(atomic)->radius; + RwV3d center = RpAtomicGetBoundingSphere(atomic)->center; + + for (RwFrame *frame = RpAtomicGetFrame(atomic); RwFrameGetParent(frame); frame = RwFrameGetParent(frame)) + RwV3dTransformPoints(¢er, ¢er, 1, RwFrameGetMatrix(frame)); + + float size = RwV3dLength(¢er) + radius; + if (size > *(float *)data) + *(float *)data = size; + return atomic; +} + +void +CCutsceneMgr::Initialise(void) +{ + ms_numCutsceneObjs = 0; + ms_loaded = false; + ms_running = false; + ms_animLoaded = false; + ms_cutsceneProcessing = false; + ms_useLodMultiplier = false; + + ms_pCutsceneDir = new CDirectory(CUTSCENEDIRSIZE); + ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR"); +} + +void +CCutsceneMgr::Shutdown(void) +{ + delete ms_pCutsceneDir; +} + +void +CCutsceneMgr::LoadCutsceneData(const char *szCutsceneName) +{ + int file; + uint32 size; + uint32 offset; + CPlayerPed *pPlayerPed; + + ms_cutsceneProcessing = true; + if (!strcasecmp(szCutsceneName, "jb")) + ms_useLodMultiplier = true; + CTimer::Stop(); + + ms_pCutsceneDir->numEntries = 0; + ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR"); + + CStreaming::RemoveUnusedModelsInLoadedList(); + CGame::DrasticTidyUpMemory(true); + + strcpy(ms_cutsceneName, szCutsceneName); + file = CFileMgr::OpenFile("ANIM\\CUTS.IMG", "rb"); + + // Load animations + sprintf(gString, "%s.IFP", szCutsceneName); + if (ms_pCutsceneDir->FindItem(gString, offset, size)) { + CStreaming::MakeSpaceFor(size << 11); + CStreaming::ImGonnaUseStreamingMemory(); + CFileMgr::Seek(file, offset << 11, SEEK_SET); + CAnimManager::LoadAnimFile(file, false); + ms_cutsceneAssociations.CreateAssociations(szCutsceneName); + CStreaming::IHaveUsedStreamingMemory(); + ms_animLoaded = true; + } else { + ms_animLoaded = false; + } + + // Load camera data + sprintf(gString, "%s.DAT", szCutsceneName); + if (ms_pCutsceneDir->FindItem(gString, offset, size)) { + CFileMgr::Seek(file, offset << 11, SEEK_SET); + TheCamera.LoadPathSplines(file); + } + + CFileMgr::CloseFile(file); + + if (CGeneral::faststricmp(ms_cutsceneName, "end")) { + DMAudio.ChangeMusicMode(MUSICMODE_CUTSCENE); + int trackId = FindCutsceneAudioTrackId(szCutsceneName); + if (trackId != -1) { + printf("Start preload audio %s\n", szCutsceneName); + DMAudio.PreloadCutSceneMusic(trackId); + printf("End preload audio %s\n", szCutsceneName); + } + } + + ms_cutsceneTimer = 0.0f; + ms_loaded = true; + ms_cutsceneOffset = CVector(0.0f, 0.0f, 0.0f); + + pPlayerPed = FindPlayerPed(); + CTimer::Update(); + + pPlayerPed->m_pWanted->ClearQdCrimes(); + pPlayerPed->bIsVisible = false; + pPlayerPed->m_fCurrentStamina = pPlayerPed->m_fMaxStamina; + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_CUTSCENE); + CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(true); +} + +void +CCutsceneMgr::SetHeadAnim(const char *animName, CObject *pObject) +{ + CCutsceneHead *pCutsceneHead = (CCutsceneHead*)pObject; + char szAnim[CUTSCENENAMESIZE * 2]; + + sprintf(szAnim, "%s_%s", ms_cutsceneName, animName); + pCutsceneHead->PlayAnimation(szAnim); +} + +void +CCutsceneMgr::FinishCutscene() +{ + CCutsceneMgr::ms_cutsceneTimer = TheCamera.GetCutSceneFinishTime() * 0.001f; + TheCamera.FinishCutscene(); + + FindPlayerPed()->bIsVisible = true; + CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(false); +} + +void +CCutsceneMgr::SetupCutsceneToStart(void) +{ + TheCamera.SetCamCutSceneOffSet(ms_cutsceneOffset); + TheCamera.TakeControlWithSpline(JUMP_CUT); + TheCamera.SetWideScreenOn(); + + ms_cutsceneOffset.z++; + + for (int i = ms_numCutsceneObjs - 1; i >= 0; i--) { + assert(RwObjectGetType(ms_pCutsceneObjects[i]->m_rwObject) == rpCLUMP); + if (CAnimBlendAssociation *pAnimBlendAssoc = RpAnimBlendClumpGetFirstAssociation((RpClump*)ms_pCutsceneObjects[i]->m_rwObject)) { + assert(pAnimBlendAssoc->hierarchy->sequences[0].HasTranslation()); + ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + ((KeyFrameTrans*)pAnimBlendAssoc->hierarchy->sequences[0].GetKeyFrame(0))->translation); + CWorld::Add(ms_pCutsceneObjects[i]); + pAnimBlendAssoc->SetRun(); + } else { + ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset); + } + } + + CTimer::Update(); + CTimer::Update(); + ms_running = true; + ms_cutsceneTimer = 0.0f; +} + +void +CCutsceneMgr::SetCutsceneAnim(const char *animName, CObject *pObject) +{ + CAnimBlendAssociation *pNewAnim; + CAnimBlendClumpData *pAnimBlendClumpData; + + assert(RwObjectGetType(pObject->m_rwObject) == rpCLUMP); + RpAnimBlendClumpRemoveAllAssociations((RpClump*)pObject->m_rwObject); + + pNewAnim = ms_cutsceneAssociations.CopyAnimation(animName); + pNewAnim->SetCurrentTime(0.0f); + pNewAnim->flags |= ASSOC_HAS_TRANSLATION; + pNewAnim->flags &= ~ASSOC_RUNNING; + + pAnimBlendClumpData = *RPANIMBLENDCLUMPDATA(pObject->m_rwObject); + pAnimBlendClumpData->link.Prepend(&pNewAnim->link); +} + +CCutsceneHead * +CCutsceneMgr::AddCutsceneHead(CObject *pObject, int modelId) +{ + CCutsceneHead *pHead = new CCutsceneHead(pObject); + pHead->SetModelIndex(modelId); + CWorld::Add(pHead); + ms_pCutsceneObjects[ms_numCutsceneObjs++] = pHead; + return pHead; +} + +CCutsceneObject * +CCutsceneMgr::CreateCutsceneObject(int modelId) +{ + CBaseModelInfo *pModelInfo; + CColModel *pColModel; + float radius; + RpClump *clump; + CCutsceneObject *pCutsceneObject; + + if (modelId >= MI_CUTOBJ01 && modelId <= MI_CUTOBJ05) { + pModelInfo = CModelInfo::GetModelInfo(modelId); + pColModel = &CTempColModels::ms_colModelCutObj[modelId - MI_CUTOBJ01]; + radius = 0.0f; + + pModelInfo->SetColModel(pColModel); + clump = (RpClump*)pModelInfo->GetRwObject(); + assert(RwObjectGetType((RwObject*)clump) == rpCLUMP); + RpClumpForAllAtomics(clump, CalculateBoundingSphereRadiusCB, &radius); + + pColModel->boundingSphere.radius = radius; + pColModel->boundingBox.min = CVector(-radius, -radius, -radius); + pColModel->boundingBox.max = CVector(radius, radius, radius); + } + + pCutsceneObject = new CCutsceneObject(); + pCutsceneObject->SetModelIndex(modelId); + ms_pCutsceneObjects[ms_numCutsceneObjs++] = pCutsceneObject; + return pCutsceneObject; +} + +void +CCutsceneMgr::DeleteCutsceneData(void) +{ + if (!ms_loaded) return; + + ms_cutsceneProcessing = false; + ms_useLodMultiplier = false; + + for (--ms_numCutsceneObjs; ms_numCutsceneObjs >= 0; ms_numCutsceneObjs--) { + CWorld::Remove(ms_pCutsceneObjects[ms_numCutsceneObjs]); + ms_pCutsceneObjects[ms_numCutsceneObjs]->DeleteRwObject(); + delete ms_pCutsceneObjects[ms_numCutsceneObjs]; + ms_pCutsceneObjects[ms_numCutsceneObjs] = nil; + } + ms_numCutsceneObjs = 0; + + if (ms_animLoaded) + CAnimManager::RemoveLastAnimFile(); + + ms_animLoaded = false; + TheCamera.RestoreWithJumpCut(); + TheCamera.SetWideScreenOff(); + ms_running = false; + ms_loaded = false; + + FindPlayerPed()->bIsVisible = true; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_CUTSCENE); + CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(false); + + if (CGeneral::faststricmp(ms_cutsceneName, "end")) { + DMAudio.StopCutSceneMusic(); + if (CGeneral::faststricmp(ms_cutsceneName, "bet")) + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + } + CTimer::Stop(); + CGame::DrasticTidyUpMemory(TheCamera.GetScreenFadeStatus() == FADE_2); + CTimer::Update(); +} + +void +CCutsceneMgr::Update(void) +{ + enum { + CUTSCENE_LOADING_0 = 0, + CUTSCENE_LOADING_AUDIO, + CUTSCENE_LOADING_2, + CUTSCENE_LOADING_3, + CUTSCENE_LOADING_4 + }; + + switch (ms_cutsceneLoadStatus) { + case CUTSCENE_LOADING_AUDIO: + SetupCutsceneToStart(); + if (CGeneral::faststricmp(ms_cutsceneName, "end")) + DMAudio.PlayPreloadedCutSceneMusic(); + ms_cutsceneLoadStatus++; + break; + case CUTSCENE_LOADING_2: + case CUTSCENE_LOADING_3: + ms_cutsceneLoadStatus++; + break; + case CUTSCENE_LOADING_4: + ms_cutsceneLoadStatus = CUTSCENE_LOADING_0; + break; + default: + break; + } + + if (!ms_running) return; + + ms_cutsceneTimer += CTimer::GetTimeStepNonClippedInSeconds(); + if (CGeneral::faststricmp(ms_cutsceneName, "end") && TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FLYBY && ms_cutsceneLoadStatus == CUTSCENE_LOADING_0) { + if (CPad::GetPad(0)->GetCrossJustDown() + || (CGame::playingIntro && CPad::GetPad(0)->GetStartJustDown()) + || CPad::GetPad(0)->GetLeftMouseJustDown() + || CPad::GetPad(0)->GetEnterJustDown() + || CPad::GetPad(0)->GetCharJustDown(' ')) + FinishCutscene(); + } +} + +bool CCutsceneMgr::HasCutsceneFinished(void) { return TheCamera.GetPositionAlongSpline() == 1.0f; } + diff --git a/src/animation/CutsceneMgr.h b/src/animation/CutsceneMgr.h new file mode 100644 index 0000000..bfdcdb5 --- /dev/null +++ b/src/animation/CutsceneMgr.h @@ -0,0 +1,51 @@ +#pragma once +#include "CutsceneObject.h" + +#define CUTSCENENAMESIZE 8 + +class CDirectory; +class CAnimBlendAssocGroup; +class CCutsceneHead; + +class CCutsceneMgr +{ + static bool ms_running; + static CCutsceneObject *ms_pCutsceneObjects[NUMCUTSCENEOBJECTS]; + + static int32 ms_numCutsceneObjs; + static bool ms_loaded; + static bool ms_animLoaded; + static bool ms_useLodMultiplier; + + static char ms_cutsceneName[CUTSCENENAMESIZE]; + static CAnimBlendAssocGroup ms_cutsceneAssociations; + static CVector ms_cutsceneOffset; + static float ms_cutsceneTimer; + static bool ms_cutsceneProcessing; +public: + static CDirectory *ms_pCutsceneDir; + static uint32 ms_cutsceneLoadStatus; + + static void StartCutsceneProcessing() { ms_cutsceneProcessing = true; } + static bool IsRunning(void) { return ms_running; } + static bool HasLoaded(void) { return ms_loaded; } + static bool IsCutsceneProcessing(void) { return ms_cutsceneProcessing; } + static bool UseLodMultiplier(void) { return ms_useLodMultiplier; } + static CCutsceneObject* GetCutsceneObject(int id) { return ms_pCutsceneObjects[id]; } + static int GetCutsceneTimeInMilleseconds(void) { return 1000.0f * ms_cutsceneTimer; } + static char *GetCutsceneName(void) { return ms_cutsceneName; } + static void SetCutsceneOffset(const CVector& vec) { ms_cutsceneOffset = vec; } + static bool HasCutsceneFinished(void); + + static void Initialise(void); + static void Shutdown(void); + static void LoadCutsceneData(const char *szCutsceneName); + static void FinishCutscene(void); + static void SetHeadAnim(const char *animName, CObject *pObject); + static void SetupCutsceneToStart(void); + static void SetCutsceneAnim(const char *animName, CObject *pObject); + static CCutsceneHead *AddCutsceneHead(CObject *pObject, int modelId); + static CCutsceneObject *CreateCutsceneObject(int modelId); + static void DeleteCutsceneData(void); + static void Update(void); +}; diff --git a/src/animation/FrameUpdate.cpp b/src/animation/FrameUpdate.cpp new file mode 100644 index 0000000..c7d347b --- /dev/null +++ b/src/animation/FrameUpdate.cpp @@ -0,0 +1,445 @@ +#include "common.h" + +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendAssociation.h" +#include "RpAnimBlend.h" + +CAnimBlendClumpData *gpAnimBlendClump; + +// PS2 names without "NonSkinned" +void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWithVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWith3dVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg); + +void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWithVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackWith3dVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg); + + +void +FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + RwMatrix *mat = RwFrameGetMatrix(frame->frame); + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION && + gpAnimBlendClump->velocity2d){ + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION_3D) + FrameUpdateCallBackWith3dVelocityExtractionNonSkinned(frame, arg); + else + FrameUpdateCallBackWithVelocityExtractionNonSkinned(frame, arg); + return; + } + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + (*node)->Update(vec, q, 1.0f-totalBlendAmount); + if((*node)->sequence->HasTranslation()) + pos += vec; +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + RwMatrixSetIdentity(mat); + rot.Normalise(); + rot.Get(mat); + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + mat->pos.x = pos.x; + mat->pos.y = pos.y; + mat->pos.z = pos.z; + mat->pos.x += frame->resetPos.x; + mat->pos.y += frame->resetPos.y; + mat->pos.z += frame->resetPos.z; + } + RwMatrixUpdate(mat); +} + +void +FrameUpdateCallBackWithVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + float transx = 0.0f, transy = 0.0f; + float curx = 0.0f, cury = 0.0f; + float endx = 0.0f, endy = 0.0f; + bool looped = false; + RwMatrix *mat = RwFrameGetMatrix(frame->frame); + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cury += vec.y; + if((*node)->association->HasXTranslation()) + curx += vec.x; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + transy += vec.y; + if((*node)->association->HasXTranslation()) + transx += vec.x; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + endy += vec.y; + if((*node)->association->HasXTranslation()) + endx += vec.x; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + RwMatrixSetIdentity(mat); + rot.Normalise(); + rot.Get(mat); + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + gpAnimBlendClump->velocity2d->x = transx - curx; + gpAnimBlendClump->velocity2d->y = transy - cury; + if(looped){ + gpAnimBlendClump->velocity2d->x += endx; + gpAnimBlendClump->velocity2d->y += endy; + } + mat->pos.x = pos.x - transx; + mat->pos.y = pos.y - transy; + mat->pos.z = pos.z; + if(mat->pos.z >= -0.8f) { + if(mat->pos.z < -0.4f) + mat->pos.z += (2.5f * mat->pos.z + 2.0f) * frame->resetPos.z; + else + mat->pos.z += frame->resetPos.z; + } + mat->pos.x += frame->resetPos.x; + mat->pos.y += frame->resetPos.y; + } + RwMatrixUpdate(mat); +} + +// original code uses do loops? +void +FrameUpdateCallBackWith3dVelocityExtractionNonSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + CVector trans(0.0f, 0.0f, 0.0f); + CVector cur(0.0f, 0.0f, 0.0f); + CVector end(0.0f, 0.0f, 0.0f); + bool looped = false; + RwMatrix *mat = RwFrameGetMatrix(frame->frame); + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cur += vec; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + trans += vec; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + end += vec; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + RwMatrixSetIdentity(mat); + rot.Normalise(); + rot.Get(mat); + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + *gpAnimBlendClump->velocity3d = trans - cur; + if(looped) + *gpAnimBlendClump->velocity3d += end; + mat->pos.x = (pos - trans).x + frame->resetPos.x; + mat->pos.y = (pos - trans).y + frame->resetPos.y; + mat->pos.z = (pos - trans).z + frame->resetPos.z; + } + RwMatrixUpdate(mat); +} + +#ifdef PED_SKIN + +void +FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + RpHAnimStdInterpFrame *xform = frame->hanimFrame; + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION && + gpAnimBlendClump->velocity2d){ + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION_3D) + FrameUpdateCallBackWith3dVelocityExtractionSkinned(frame, arg); + else + FrameUpdateCallBackWithVelocityExtractionSkinned(frame, arg); + return; + } + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + (*node)->Update(vec, q, 1.0f-totalBlendAmount); + if((*node)->sequence->HasTranslation()) + pos += vec; +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + rot.Normalise(); + xform->q.imag.x = rot.x; + xform->q.imag.y = rot.y; + xform->q.imag.z = rot.z; + xform->q.real = rot.w; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + xform->t.x = pos.x; + xform->t.y = pos.y; + xform->t.z = pos.z; + xform->t.x += frame->resetPos.x; + xform->t.y += frame->resetPos.y; + xform->t.z += frame->resetPos.z; + } +} + +void +FrameUpdateCallBackWithVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + float transx = 0.0f, transy = 0.0f; + float curx = 0.0f, cury = 0.0f; + float endx = 0.0f, endy = 0.0f; + bool looped = false; + RpHAnimStdInterpFrame *xform = frame->hanimFrame; + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cury += vec.y; + if((*node)->association->HasXTranslation()) + curx += vec.x; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + transy += vec.y; + if((*node)->association->HasXTranslation()) + transx += vec.x; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + endy += vec.y; + if((*node)->association->HasXTranslation()) + endx += vec.x; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + rot.Normalise(); + xform->q.imag.x = rot.x; + xform->q.imag.y = rot.y; + xform->q.imag.z = rot.z; + xform->q.real = rot.w; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + gpAnimBlendClump->velocity2d->x = transx - curx; + gpAnimBlendClump->velocity2d->y = transy - cury; + if(looped){ + gpAnimBlendClump->velocity2d->x += endx; + gpAnimBlendClump->velocity2d->y += endy; + } + xform->t.x = pos.x - transx; + xform->t.y = pos.y - transy; + xform->t.z = pos.z; + if(xform->t.z >= -0.8f) { + if(xform->t.z < -0.4f) + xform->t.z += (2.5f * xform->t.z + 2.0f) * frame->resetPos.z; + else + xform->t.z += frame->resetPos.z; + } + xform->t.x += frame->resetPos.x; + xform->t.y += frame->resetPos.y; + } +} + +void +FrameUpdateCallBackWith3dVelocityExtractionSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f); + float totalBlendAmount = 0.0f; + CVector trans(0.0f, 0.0f, 0.0f); + CVector cur(0.0f, 0.0f, 0.0f); + CVector end(0.0f, 0.0f, 0.0f); + bool looped = false; + RpHAnimStdInterpFrame *xform = frame->hanimFrame; + CAnimBlendNode **node; + AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg; + + if(updateData->foobar) + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->association->IsPartial()) + totalBlendAmount += (*node)->association->blendAmount; + + for(node = updateData->nodes; *node; node++) + if((*node)->sequence && (*node)->sequence->HasTranslation()){ + if((*node)->association->HasTranslation()){ + (*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount); + cur += vec; + } + } + + for(node = updateData->nodes; *node; node++){ + if((*node)->sequence){ + bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount); +#ifdef FIX_BUGS + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else +#endif + rot += q; + if((*node)->sequence->HasTranslation()){ + pos += vec; + if((*node)->association->HasTranslation()){ + trans += vec; + looped |= nodelooped; + if(nodelooped){ + (*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount); + end += vec; + } + } + } + } + ++*node; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){ + rot.Normalise(); + xform->q.imag.x = rot.x; + xform->q.imag.y = rot.y; + xform->q.imag.z = rot.z; + xform->q.real = rot.w; + } + + if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){ + *gpAnimBlendClump->velocity3d = trans - cur; + if(looped) + *gpAnimBlendClump->velocity3d += end; + xform->t.x = (pos - trans).x + frame->resetPos.x; + xform->t.y = (pos - trans).y + frame->resetPos.y; + xform->t.z = (pos - trans).z + frame->resetPos.z; + } +} + +#endif diff --git a/src/animation/RpAnimBlend.cpp b/src/animation/RpAnimBlend.cpp new file mode 100644 index 0000000..e430e52 --- /dev/null +++ b/src/animation/RpAnimBlend.cpp @@ -0,0 +1,469 @@ +#include "common.h" + +#include "RwHelper.h" +#include "General.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "Bones.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendHierarchy.h" +#include "AnimBlendAssociation.h" +#include "AnimManager.h" +#include "RpAnimBlend.h" +#ifdef PED_SKIN +#include "PedModelInfo.h" +#endif + +RwInt32 ClumpOffset; + +enum +{ + ID_RPANIMBLEND = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0xFD), +}; + +void* +AnimBlendClumpCreate(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + *RWPLUGINOFFSET(CAnimBlendClumpData*, object, offsetInObject) = nil; + return object; +} + +void* +AnimBlendClumpDestroy(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + CAnimBlendClumpData *data; + data = *RPANIMBLENDCLUMPDATA(object); + if(data){ + RpAnimBlendClumpRemoveAllAssociations((RpClump*)object); + delete data; + *RPANIMBLENDCLUMPDATA(object) = nil; + } + return object; +} + +void *AnimBlendClumpCopy(void *dstObject, const void *srcObject, RwInt32 offsetInObject, RwInt32 sizeInObject) { return nil; } + +bool +RpAnimBlendPluginAttach(void) +{ + ClumpOffset = RpClumpRegisterPlugin(sizeof(CAnimBlendClumpData*), ID_RPANIMBLEND, + AnimBlendClumpCreate, AnimBlendClumpDestroy, AnimBlendClumpCopy); + return ClumpOffset >= 0; +} + +CAnimBlendAssociation* +RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc) +{ + if(assoc->link.next) + return CAnimBlendAssociation::FromLink(assoc->link.next); + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc, uint32 mask) +{ + CAnimBlendLink *link; + for(link = assoc->link.next; link; link = link->next){ + assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->flags & mask) + return assoc; + } + return nil; +} + +void +RpAnimBlendAllocateData(RpClump *clump) +{ + *RPANIMBLENDCLUMPDATA(clump) = new CAnimBlendClumpData; +} + + +void +RpAnimBlendClumpSetBlendDeltas(RpClump *clump, uint32 mask, float delta) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(mask == 0 || (assoc->flags & mask)) + assoc->blendDelta = delta; + } +} + +void +RpAnimBlendClumpRemoveAllAssociations(RpClump *clump) +{ + RpAnimBlendClumpRemoveAssociations(clump, 0); +} + +void +RpAnimBlendClumpRemoveAssociations(RpClump *clump, uint32 mask) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + CAnimBlendLink *next; + for(CAnimBlendLink *link = clumpData->link.next; link; link = next){ + next = link->next; + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(mask == 0 || (assoc->flags & mask)) + if(assoc) + delete assoc; + } +} + +RwFrame* +FrameForAllChildrenCountCallBack(RwFrame *frame, void *data) +{ + int *numFrames = (int*)data; + (*numFrames)++; + RwFrameForAllChildren(frame, FrameForAllChildrenCountCallBack, data); + return frame; +} + +RwFrame* +FrameForAllChildrenFillFrameArrayCallBack(RwFrame *frame, void *data) +{ + AnimBlendFrameData **frames = (AnimBlendFrameData**)data; + (*frames)->frame = frame; + (*frames)++; + RwFrameForAllChildren(frame, FrameForAllChildrenFillFrameArrayCallBack, frames); + return frame; +} + +// FrameInitCallBack on PS2 +void +FrameInitCBnonskin(AnimBlendFrameData *frameData, void*) +{ + frameData->flag = 0; + frameData->resetPos = *RwMatrixGetPos(RwFrameGetMatrix(frameData->frame)); +} + +void +FrameInitCBskin(AnimBlendFrameData *frameData, void*) +{ + frameData->flag = 0; +} + +#ifdef PED_SKIN +void +RpAnimBlendClumpInitSkinned(RpClump *clump) +{ + int i; + RwV3d boneTab[64]; + CAnimBlendClumpData *clumpData; + RpAtomic *atomic; + RpSkin *skin; + RpHAnimHierarchy *hier; + int numBones; + + RpAnimBlendAllocateData(clump); + clumpData = *RPANIMBLENDCLUMPDATA(clump); + atomic = IsClumpSkinned(clump); + assert(atomic); + skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic)); + assert(skin); + numBones = RpSkinGetNumBones(skin); + clumpData->SetNumberOfBones(numBones); + hier = GetAnimHierarchyFromSkinClump(clump); + assert(hier); + memset(boneTab, 0, sizeof(boneTab)); + SkinGetBonePositionsToTable(clump, boneTab); + + AnimBlendFrameData *frames = clumpData->frames; + for(i = 0; i < numBones; i++){ + frames[i].nodeID = HIERNODEID(hier, i); + frames[i].resetPos = boneTab[i]; + frames[i].hanimFrame = (RpHAnimStdInterpFrame*)rpHANIMHIERARCHYGETINTERPFRAME(hier, i); + } + clumpData->ForAllFrames(FrameInitCBskin, nil); + clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION; +} +#endif + +void +RpAnimBlendClumpInitNotSkinned(RpClump *clump) +{ + int numFrames = 0; + CAnimBlendClumpData *clumpData; + RwFrame *root; + AnimBlendFrameData *frames; + + RpAnimBlendAllocateData(clump); + clumpData = *RPANIMBLENDCLUMPDATA(clump); + root = RpClumpGetFrame(clump); + RwFrameForAllChildren(root, FrameForAllChildrenCountCallBack, &numFrames); + clumpData->SetNumberOfFrames(numFrames); + frames = clumpData->frames; + RwFrameForAllChildren(root, FrameForAllChildrenFillFrameArrayCallBack, &frames); + clumpData->ForAllFrames(FrameInitCBnonskin, nil); + clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION; +} + +void +RpAnimBlendClumpInit(RpClump *clump) +{ +#ifdef PED_SKIN + if(IsClumpSkinned(clump)) + RpAnimBlendClumpInitSkinned(clump); + else +#endif + RpAnimBlendClumpInitNotSkinned(clump); +} + +bool +RpAnimBlendClumpIsInitialized(RpClump *clump) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + return clumpData && clumpData->numFrames != 0; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetAssociation(RpClump *clump, uint32 id) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->animId == id) + return assoc; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainAssociation(RpClump *clump, CAnimBlendAssociation **assocRet, float *blendRet) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + CAnimBlendAssociation *mainAssoc = nil; + CAnimBlendAssociation *secondAssoc = nil; + float mainBlend = 0.0f; + float secondBlend = 0.0f; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(assoc->IsPartial()) + continue; + + if(assoc->blendAmount > mainBlend){ + secondBlend = mainBlend; + mainBlend = assoc->blendAmount; + + secondAssoc = mainAssoc; + mainAssoc = assoc; + }else if(assoc->blendAmount > secondBlend){ + secondBlend = assoc->blendAmount; + secondAssoc = assoc; + } + } + if(assocRet) *assocRet = secondAssoc; + if(blendRet) *blendRet = secondBlend; + return mainAssoc; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainPartialAssociation(RpClump *clump) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + CAnimBlendAssociation *mainAssoc = nil; + float mainBlend = 0.0f; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(!assoc->IsPartial()) + continue; + + if(assoc->blendAmount > mainBlend){ + mainBlend = assoc->blendAmount; + mainAssoc = assoc; + } + } + return mainAssoc; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainAssociation_N(RpClump *clump, int n) +{ + int i; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + i = 0; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(assoc->IsPartial()) + continue; + + if(i == n) + return assoc; + i++; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetMainPartialAssociation_N(RpClump *clump, int n) +{ + int i; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + i = 0; + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + + if(!assoc->IsPartial()) + continue; + + if(i == n) + return assoc; + i++; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetFirstAssociation(RpClump *clump, uint32 mask) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + + if(clumpData == nil) return nil; + + for(CAnimBlendLink *link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->flags & mask) + return assoc; + } + return nil; +} + +CAnimBlendAssociation* +RpAnimBlendClumpGetFirstAssociation(RpClump *clump) +{ + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + if(!RpAnimBlendClumpIsInitialized(clump)) + return nil; + if(clumpData->link.next) + return CAnimBlendAssociation::FromLink(clumpData->link.next); + return nil; +} + +// FillFrameArrayCallBack on PS2 +void +FillFrameArrayCBnonskin(AnimBlendFrameData *frame, void *arg) +{ + AnimBlendFrameData **frames = (AnimBlendFrameData**)arg; + frames[CVisibilityPlugins::GetFrameHierarchyId(frame->frame)] = frame; +} + +#ifdef PED_SKIN +void +RpAnimBlendClumpFillFrameArraySkin(RpClump *clump, AnimBlendFrameData **frames) +{ + int i; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump); + for(i = PED_MID; i < PED_NODE_MAX; i++) + frames[i] = &clumpData->frames[RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(i))]; +} +#endif + +void +RpAnimBlendClumpFillFrameArray(RpClump *clump, AnimBlendFrameData **frames) +{ +#ifdef PED_SKIN + if(IsClumpSkinned(clump)) + RpAnimBlendClumpFillFrameArraySkin(clump, frames); + else +#endif + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FillFrameArrayCBnonskin, frames); +} + +AnimBlendFrameData *pFrameDataFound; + +// FrameFindCallBack on PS2 +void +FrameFindByNameCBnonskin(AnimBlendFrameData *frame, void *arg) +{ + char *nodename = GetFrameNodeName(frame->frame); + if(!CGeneral::faststricmp(nodename, (char*)arg)) + pFrameDataFound = frame; +} + +#ifdef PED_SKIN +void +FrameFindByNameCBskin(AnimBlendFrameData *frame, void *arg) +{ + const char *name = ConvertBoneTag2BoneName(frame->nodeID); + if(name && CGeneral::faststricmp(name, (char*)arg) == 0) + pFrameDataFound = frame; +} +#endif + +AnimBlendFrameData* +RpAnimBlendClumpFindFrame(RpClump *clump, const char *name) +{ + pFrameDataFound = nil; +#ifdef PED_SKIN + if(IsClumpSkinned(clump)) + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBskin, (void*)name); + else +#endif + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBnonskin, (void*)name); + return pFrameDataFound; +} + +void +RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta) +{ + int i; + AnimBlendFrameUpdateData updateData; + float totalLength = 0.0f; + float totalBlend = 0.0f; + CAnimBlendLink *link, *next; + CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump); + gpAnimBlendClump = clumpData; + + if(clumpData->link.next == nil) + return; + + // Update blend and get node array + i = 0; + updateData.foobar = 0; + for(link = clumpData->link.next; link; link = next){ + next = link->next; + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->UpdateBlend(timeDelta)){ + CAnimManager::UncompressAnimation(assoc->hierarchy); + updateData.nodes[i++] = assoc->GetNode(0); + if(assoc->flags & ASSOC_MOVEMENT){ + totalLength += assoc->hierarchy->totalLength/assoc->speed * assoc->blendAmount; + totalBlend += assoc->blendAmount; + }else + updateData.foobar = 1; + } + } + updateData.nodes[i] = nil; + +#ifdef PED_SKIN + if(IsClumpSkinned(clump)) + clumpData->ForAllFrames(FrameUpdateCallBackSkinned, &updateData); + else +#endif + clumpData->ForAllFrames(FrameUpdateCallBackNonSkinned, &updateData); + + for(link = clumpData->link.next; link; link = link->next){ + CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link); + float relSpeed = totalLength == 0.0f ? 1.0f : totalBlend/totalLength; + assoc->UpdateTime(timeDelta, relSpeed); + } + RwFrameUpdateObjects(RpClumpGetFrame(clump)); +} diff --git a/src/animation/RpAnimBlend.h b/src/animation/RpAnimBlend.h new file mode 100644 index 0000000..838c881 --- /dev/null +++ b/src/animation/RpAnimBlend.h @@ -0,0 +1,42 @@ +#pragma once + +class CAnimBlendNode; +class CAnimBlendAssociation; +class CAnimBlendClumpData; +struct AnimBlendFrameData; + +struct AnimBlendFrameUpdateData +{ + int foobar; // TODO: figure out what this actually means + CAnimBlendNode *nodes[16]; +}; + +extern RwInt32 ClumpOffset; +#define RPANIMBLENDCLUMPDATA(o) (RWPLUGINOFFSET(CAnimBlendClumpData*, o, ClumpOffset)) + +bool RpAnimBlendPluginAttach(void); +CAnimBlendAssociation *RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc); +CAnimBlendAssociation *RpAnimBlendGetNextAssociation(CAnimBlendAssociation *assoc, uint32 mask); +void RpAnimBlendAllocateData(RpClump *clump); + +void RpAnimBlendClumpSetBlendDeltas(RpClump *clump, uint32 mask, float delta); +void RpAnimBlendClumpRemoveAllAssociations(RpClump *clump); +void RpAnimBlendClumpRemoveAssociations(RpClump *clump, uint32 mask); +void RpAnimBlendClumpInit(RpClump *clump); +bool RpAnimBlendClumpIsInitialized(RpClump *clump); +void RpAnimBlendClumpFillFrameArray(RpClump* clump, AnimBlendFrameData** frames); +AnimBlendFrameData *RpAnimBlendClumpFindFrame(RpClump *clump, const char *name); +void FillFrameArrayCallBack(AnimBlendFrameData *frame, void *arg); +CAnimBlendAssociation *RpAnimBlendClumpGetAssociation(RpClump *clump, uint32 id); +CAnimBlendAssociation *RpAnimBlendClumpGetMainAssociation(RpClump *clump, CAnimBlendAssociation **assocRet, float *blendRet); +CAnimBlendAssociation *RpAnimBlendClumpGetMainPartialAssociation(RpClump *clump); +CAnimBlendAssociation *RpAnimBlendClumpGetMainAssociation_N(RpClump *clump, int n); +CAnimBlendAssociation *RpAnimBlendClumpGetMainPartialAssociation_N(RpClump *clump, int n); +CAnimBlendAssociation *RpAnimBlendClumpGetFirstAssociation(RpClump *clump, uint32 mask); +CAnimBlendAssociation *RpAnimBlendClumpGetFirstAssociation(RpClump *clump); +void RpAnimBlendClumpUpdateAnimations(RpClump* clump, float timeDelta); + + +extern CAnimBlendClumpData *gpAnimBlendClump; +void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg); diff --git a/src/audio/AudioCollision.cpp b/src/audio/AudioCollision.cpp new file mode 100644 index 0000000..d7f2f5a --- /dev/null +++ b/src/audio/AudioCollision.cpp @@ -0,0 +1,401 @@ +#include "common.h" + +#include "DMAudio.h" +#include "Entity.h" +#include "AudioCollision.h" +#include "AudioManager.h" +#include "AudioSamples.h" +#include "SurfaceTable.h" +#include "sampman.h" + +void +cAudioManager::ReportCollision(CEntity *entity1, CEntity *entity2, uint8 surface1, uint8 surface2, float collisionPower, + float velocity) +{ + float distSquared; + CVector v1; + CVector v2; + + if(!m_bIsInitialised || m_nCollisionEntity < 0 || m_bIsPaused || + (velocity < 0.0016f && collisionPower < 0.01f)) + return; + + if(entity1->IsBuilding()) { + v1 = v2 = entity2->GetPosition(); + } else if(entity2->IsBuilding()) { + v1 = v2 = entity1->GetPosition(); + } else { + v1 = entity1->GetPosition(); + v2 = entity2->GetPosition(); + } + CVector pos = (v1 + v2) * 0.5f; + distSquared = GetDistanceSquared(pos); + if(distSquared < SQR(COLLISION_MAX_DIST)) { + m_sCollisionManager.m_sQueue.m_pEntity1 = entity1; + m_sCollisionManager.m_sQueue.m_pEntity2 = entity2; + m_sCollisionManager.m_sQueue.m_bSurface1 = surface1; + m_sCollisionManager.m_sQueue.m_bSurface2 = surface2; + m_sCollisionManager.m_sQueue.m_fIntensity1 = collisionPower; + m_sCollisionManager.m_sQueue.m_fIntensity2 = velocity; + m_sCollisionManager.m_sQueue.m_vecPosition = pos; + m_sCollisionManager.m_sQueue.m_fDistance = distSquared; + m_sCollisionManager.AddCollisionToRequestedQueue(); + } +} + +void +cAudioCollisionManager::AddCollisionToRequestedQueue() +{ + uint32 collisionsIndex; + uint32 i; + + + if (m_bCollisionsInQueue < NUMAUDIOCOLLISIONS) + collisionsIndex = m_bCollisionsInQueue++; + else { + collisionsIndex = m_bIndicesTable[NUMAUDIOCOLLISIONS - 1]; + if (m_sQueue.m_fDistance >= m_asCollisions1[collisionsIndex].m_fDistance) return; + } + + m_asCollisions1[collisionsIndex] = m_sQueue; + + i = 0; + if(collisionsIndex) { + while(m_asCollisions1[m_bIndicesTable[i]].m_fDistance <= m_asCollisions1[collisionsIndex].m_fDistance) { + if(++i >= collisionsIndex) { + m_bIndicesTable[i] = collisionsIndex; + return; + } + } + memmove(&m_bIndicesTable[i + 1], &m_bIndicesTable[i], NUMAUDIOCOLLISIONS - 1 - i); + } + m_bIndicesTable[i] = collisionsIndex; +} + +void +cAudioManager::ServiceCollisions() +{ + int i, j; + bool8 abRepeatedCollision1[NUMAUDIOCOLLISIONS]; + bool8 abRepeatedCollision2[NUMAUDIOCOLLISIONS]; + + m_sQueueSample.m_nEntityIndex = m_nCollisionEntity; + + for (int i = 0; i < NUMAUDIOCOLLISIONS; i++) + abRepeatedCollision1[i] = abRepeatedCollision2[i] = FALSE; + + for (i = 0; i < m_sCollisionManager.m_bCollisionsInQueue; i++) { + for (j = 0; j < NUMAUDIOCOLLISIONS; j++) { + int index = m_sCollisionManager.m_bIndicesTable[i]; + if ((m_sCollisionManager.m_asCollisions1[index].m_pEntity1 == m_sCollisionManager.m_asCollisions2[j].m_pEntity1) + && (m_sCollisionManager.m_asCollisions1[index].m_pEntity2 == m_sCollisionManager.m_asCollisions2[j].m_pEntity2) + && (m_sCollisionManager.m_asCollisions1[index].m_bSurface1 == m_sCollisionManager.m_asCollisions2[j].m_bSurface1) + && (m_sCollisionManager.m_asCollisions1[index].m_bSurface2 == m_sCollisionManager.m_asCollisions2[j].m_bSurface2) + ) { + abRepeatedCollision1[index] = TRUE; + abRepeatedCollision2[j] = TRUE; + m_sCollisionManager.m_asCollisions1[index].m_nBaseVolume = ++m_sCollisionManager.m_asCollisions2[j].m_nBaseVolume; + SetUpLoopingCollisionSound(m_sCollisionManager.m_asCollisions1[index], j); + break; + } + } + } + + for (i = 0; i < NUMAUDIOCOLLISIONS; i++) { + if (!abRepeatedCollision2[i]) { + m_sCollisionManager.m_asCollisions2[i].m_pEntity1 = nil; + m_sCollisionManager.m_asCollisions2[i].m_pEntity2 = nil; + m_sCollisionManager.m_asCollisions2[i].m_bSurface1 = SURFACE_DEFAULT; + m_sCollisionManager.m_asCollisions2[i].m_bSurface2 = SURFACE_DEFAULT; + m_sCollisionManager.m_asCollisions2[i].m_fIntensity2 = 0.0f; + m_sCollisionManager.m_asCollisions2[i].m_fIntensity1 = 0.0f; + m_sCollisionManager.m_asCollisions2[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + m_sCollisionManager.m_asCollisions2[i].m_fDistance = 0.0f; + } + } + + for (i = 0; i < m_sCollisionManager.m_bCollisionsInQueue; i++) { + int index = m_sCollisionManager.m_bIndicesTable[i]; + if (!abRepeatedCollision1[index]) { + for (j = 0; j < NUMAUDIOCOLLISIONS; j++) { + if (!abRepeatedCollision2[j]) { + m_sCollisionManager.m_asCollisions2[j].m_nBaseVolume = 1; + m_sCollisionManager.m_asCollisions2[j].m_pEntity1 = m_sCollisionManager.m_asCollisions1[index].m_pEntity1; + m_sCollisionManager.m_asCollisions2[j].m_pEntity2 = m_sCollisionManager.m_asCollisions1[index].m_pEntity2; + m_sCollisionManager.m_asCollisions2[j].m_bSurface1 = m_sCollisionManager.m_asCollisions1[index].m_bSurface1; + m_sCollisionManager.m_asCollisions2[j].m_bSurface2 = m_sCollisionManager.m_asCollisions1[index].m_bSurface2; + break; + } + } + SetUpOneShotCollisionSound(m_sCollisionManager.m_asCollisions1[index]); + SetUpLoopingCollisionSound(m_sCollisionManager.m_asCollisions1[index], j); + } + } + + for (int i = 0; i < NUMAUDIOCOLLISIONS; i++) + m_sCollisionManager.m_bIndicesTable[i] = NUMAUDIOCOLLISIONS; + m_sCollisionManager.m_bCollisionsInQueue = 0; +} + +static const uint32 gOneShotCol[] = {SFX_COL_TARMAC_1, + SFX_COL_TARMAC_1, + SFX_COL_GRASS_1, + SFX_COL_GRAVEL_1, + SFX_COL_MUD_1, + SFX_COL_TARMAC_1, + SFX_COL_CAR_1, + SFX_COL_GRASS_1, + SFX_COL_SCAFFOLD_POLE_1, + SFX_COL_GARAGE_DOOR_1, + SFX_COL_CAR_PANEL_1, + SFX_COL_THICK_METAL_PLATE_1, + SFX_COL_SCAFFOLD_POLE_1, + SFX_COL_LAMP_POST_1, + SFX_COL_HYDRANT_1, + SFX_COL_HYDRANT_1, + SFX_COL_METAL_CHAIN_FENCE_1, + SFX_COL_PED_1, + SFX_COL_SAND_1, + SFX_SPLASH_1, + SFX_COL_WOOD_CRATES_1, + SFX_COL_WOOD_BENCH_1, + SFX_COL_WOOD_SOLID_1, + SFX_COL_GRASS_1, + SFX_COL_GRASS_1, + SFX_COL_VEG_1, + SFX_COL_TARMAC_1, + SFX_COL_CONTAINER_1, + SFX_COL_NEWS_VENDOR_1, + SFX_TYRE_BUMP, + SFX_COL_CARDBOARD_1, + SFX_COL_TARMAC_1, + SFX_COL_GATE}; + +void +cAudioManager::SetUpOneShotCollisionSound(const cAudioCollision &col) +{ + uint16 s1; + uint16 s2; + + uint32 emittingVol; + float ratio; + + static uint16 counter = 28; + + for(int32 i = 0; i < 2; i++) { + if(i) { + s1 = col.m_bSurface2; + s2 = col.m_bSurface1; + } else { + s1 = col.m_bSurface1; + s2 = col.m_bSurface2; + } + ratio = GetCollisionOneShotRatio(s1, col.m_fIntensity1); + if(s1 == SURFACE_CAR && s2 == SURFACE_PED) ratio /= 4.0f; + if(s1 == SURFACE_CAR && ratio < 0.6f) { + s1 = SURFACE_CAR_PANEL; + ratio = Min(1.f, 2.f * ratio); + } + emittingVol = 40 * ratio; + if(emittingVol) { + m_sQueueSample.m_fDistance = Sqrt(col.m_fDistance); + m_sQueueSample.m_nVolume = + ComputeVolume(emittingVol, COLLISION_MAX_DIST, m_sQueueSample.m_fDistance); + if(m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = gOneShotCol[s1]; + switch(m_sQueueSample.m_nSampleIndex) { + case SFX_COL_TARMAC_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[3] % 5; + break; + case SFX_COL_CAR_PANEL_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[0] % 6; + break; + case SFX_COL_LAMP_POST_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[1] % 2; + break; + case SFX_COL_METAL_CHAIN_FENCE_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[3] % 4; + break; + case SFX_COL_PED_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[4] % 5; + break; + case SFX_COL_WOOD_CRATES_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[4] % 4; + break; + case SFX_COL_WOOD_BENCH_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[1] % 4; + break; + case SFX_COL_VEG_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[2] % 5; + break; + case SFX_COL_NEWS_VENDOR_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[2] % 3; + break; + case SFX_COL_CAR_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[1] % 5; + break; + case SFX_COL_CARDBOARD_1: + m_sQueueSample.m_nSampleIndex += m_anRandomTable[3] % 2; + break; + default: break; + } + switch(s1) { + case SURFACE_GLASS: m_sQueueSample.m_nFrequency = 13500; break; + case SURFACE_GIRDER: m_sQueueSample.m_nFrequency = 8819; break; + case SURFACE_WATER: + m_sQueueSample.m_nFrequency = + 2 * SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + break; + case SURFACE_RUBBER: m_sQueueSample.m_nFrequency = 6000; break; + case SURFACE_PLASTIC: m_sQueueSample.m_nFrequency = 8000; break; + default: + m_sQueueSample.m_nFrequency = + SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + break; + } + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency / 16); + m_sQueueSample.m_nCounter = counter++; + if(counter >= 255) counter = 28; + m_sQueueSample.m_vecPos = col.m_vecPosition; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 11; + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(emittingVol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +void +cAudioManager::SetUpLoopingCollisionSound(const cAudioCollision &col, uint8 counter) +{ + if(col.m_fIntensity2 > 0.0016f) { + uint8 emittingVol = SetLoopingCollisionRequestedSfxFreqAndGetVol(col); + if(emittingVol) { + m_sQueueSample.m_fDistance = Sqrt(col.m_fDistance); + m_sQueueSample.m_nVolume = + ComputeVolume(emittingVol, COLLISION_MAX_DIST, m_sQueueSample.m_fDistance); + if(m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = counter; + m_sQueueSample.m_vecPos = col.m_vecPosition; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(emittingVol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +uint32 +cAudioManager::SetLoopingCollisionRequestedSfxFreqAndGetVol(const cAudioCollision &audioCollision) +{ + uint8 surface1 = audioCollision.m_bSurface1; + uint8 surface2 = audioCollision.m_bSurface2; + int32 vol; + float ratio; + + if(surface1 == SURFACE_GRASS || surface2 == SURFACE_GRASS || surface1 == SURFACE_HEDGE || + surface2 == SURFACE_HEDGE) { + ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f); + m_sQueueSample.m_nSampleIndex = SFX_RAIN; + m_sQueueSample.m_nFrequency = 13000.f * ratio + 35000; + vol = 50.f * ratio; + } else if(surface1 == SURFACE_WATER || surface2 == SURFACE_WATER) { + ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f); + m_sQueueSample.m_nSampleIndex = SFX_BOAT_WATER_LOOP; + m_sQueueSample.m_nFrequency = 6050.f * ratio + 16000; + vol = 30.f * ratio; + } else if(surface1 == SURFACE_GRAVEL || surface2 == SURFACE_GRAVEL || surface1 == SURFACE_MUD_DRY || + surface2 == SURFACE_MUD_DRY || surface1 == SURFACE_SAND || surface2 == SURFACE_SAND) { + ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f); + m_sQueueSample.m_nSampleIndex = SFX_GRAVEL_SKID; + m_sQueueSample.m_nFrequency = 6000.f * ratio + 10000; + vol = 50.f * ratio; + } else if(surface1 == SURFACE_PED || surface2 == SURFACE_PED) { + return 0; + } else { + ratio = GetCollisionRatio(audioCollision.m_fIntensity2, 0.0001f, 0.09f, 0.0899f); + m_sQueueSample.m_nSampleIndex = SFX_SCRAPE_CAR_1; + m_sQueueSample.m_nFrequency = 10000.f * ratio + 10000; + vol = 40.f * ratio; + } + if(audioCollision.m_nBaseVolume < 2) vol = audioCollision.m_nBaseVolume * vol / 2; + return vol; +} + +float +cAudioManager::GetCollisionOneShotRatio(uint32 a, float b) +{ + switch(a) { + case SURFACE_DEFAULT: + case SURFACE_TARMAC: + case SURFACE_PAVEMENT: + case SURFACE_STEEP_CLIFF: + case SURFACE_TRANSPARENT_STONE: return GetCollisionRatio(b, 10.f, 60.f, 50.f); + case SURFACE_GRASS: + case SURFACE_CARDBOARDBOX: + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: return GetCollisionRatio(b, 0.f, 2.f, 2.f); + case SURFACE_CAR: return GetCollisionRatio(b, 6.f, 50.f, 44.f); + case SURFACE_GLASS: + case SURFACE_METAL_CHAIN_FENCE: return GetCollisionRatio(b, 0.1f, 10.f, 9.9f); + case SURFACE_TRANSPARENT_CLOTH: + case SURFACE_THICK_METAL_PLATE: return GetCollisionRatio(b, 30.f, 130.f, 100.f); + case SURFACE_GARAGE_DOOR: return GetCollisionRatio(b, 20.f, 100.f, 80.f); + case SURFACE_CAR_PANEL: return GetCollisionRatio(b, 0.f, 4.f, 4.f); + case SURFACE_SCAFFOLD_POLE: + case SURFACE_METAL_GATE: + case SURFACE_LAMP_POST: return GetCollisionRatio(b, 1.f, 10.f, 9.f); + case SURFACE_FIRE_HYDRANT: return GetCollisionRatio(b, 1.f, 15.f, 14.f); + case SURFACE_GIRDER: return GetCollisionRatio(b, 8.f, 50.f, 42.f); + case SURFACE_PED: return GetCollisionRatio(b, 0.f, 20.f, 20.f); + case SURFACE_SAND: + case SURFACE_WATER: + case SURFACE_RUBBER: + case SURFACE_WHEELBASE: return GetCollisionRatio(b, 0.f, 10.f, 10.f); + case SURFACE_WOOD_CRATES: return GetCollisionRatio(b, 1.f, 4.f, 3.f); + case SURFACE_WOOD_BENCH: return GetCollisionRatio(b, 0.1f, 5.f, 4.9f); + case SURFACE_WOOD_SOLID: return GetCollisionRatio(b, 0.1f, 40.f, 39.9f); + case SURFACE_PLASTIC: return GetCollisionRatio(b, 0.1f, 4.f, 3.9f); + case SURFACE_HEDGE: return GetCollisionRatio(b, 0.f, 0.5f, 0.5f); + case SURFACE_CONTAINER: return GetCollisionRatio(b, 4.f, 40.f, 36.f); + case SURFACE_NEWS_VENDOR: return GetCollisionRatio(b, 0.f, 5.f, 5.f); + default: break; + } + + return 0.f; +} + +float +cAudioManager::GetCollisionLoopingRatio(uint32 a, uint32 b, float c) +{ + return GetCollisionRatio(c, 0.0f, 0.02f, 0.02f); +} + +float +cAudioManager::GetCollisionRatio(float a, float b, float c, float d) +{ + float e; + e = a; + if(a <= b) return 0.0f; + if(c <= a) e = c; + return (e - b) / d; +} diff --git a/src/audio/AudioCollision.h b/src/audio/AudioCollision.h new file mode 100644 index 0000000..a201d50 --- /dev/null +++ b/src/audio/AudioCollision.h @@ -0,0 +1,57 @@ +#pragma once + +#define NUMAUDIOCOLLISIONS 10 + +class CEntity; + +class cAudioCollision +{ +public: + CEntity *m_pEntity1; + CEntity *m_pEntity2; + uint8 m_bSurface1; + uint8 m_bSurface2; + float m_fIntensity1; + float m_fIntensity2; + CVector m_vecPosition; + float m_fDistance; + int32 m_nBaseVolume; + + cAudioCollision() { Reset(); } + + void Reset() + { + m_pEntity1 = nil; + m_pEntity2 = nil; + m_bSurface1 = 0; + m_bSurface2 = 0; + m_fIntensity1 = m_fIntensity2 = 0.0f; + m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + m_fDistance = 0.0f; + } +}; + +VALIDATE_SIZE(cAudioCollision, 40); + +class cAudioCollisionManager +{ +public: + cAudioCollision m_asCollisions1[NUMAUDIOCOLLISIONS]; + cAudioCollision m_asCollisions2[NUMAUDIOCOLLISIONS]; + uint8 m_bIndicesTable[NUMAUDIOCOLLISIONS]; + uint8 m_bCollisionsInQueue; + cAudioCollision m_sQueue; + + cAudioCollisionManager() + { + m_sQueue.Reset(); + + for(int i = 0; i < NUMAUDIOCOLLISIONS; i++) + m_bIndicesTable[i] = NUMAUDIOCOLLISIONS; + + m_bCollisionsInQueue = 0; + } + void AddCollisionToRequestedQueue(); +}; + +VALIDATE_SIZE(cAudioCollisionManager, 852); diff --git a/src/audio/AudioLogic.cpp b/src/audio/AudioLogic.cpp new file mode 100644 index 0000000..2860230 --- /dev/null +++ b/src/audio/AudioLogic.cpp @@ -0,0 +1,9038 @@ +#include "common.h" + +#include "AudioManager.h" +#include "audio_enums.h" + +#include "Automobile.h" +#include "Boat.h" +#include "Bridge.h" +#include "Camera.h" +#include "Cranes.h" +#include "DMAudio.h" +#include "Entity.h" +#include "Explosion.h" +#include "Fire.h" +#include "Garages.h" +#include "General.h" +#include "HandlingMgr.h" +#include "Heli.h" +#include "ModelIndices.h" +#include "MusicManager.h" +#include "Pad.h" +#include "ParticleObject.h" +#include "Ped.h" +#include "Physical.h" +#include "Placeable.h" +#include "Plane.h" +#include "PlayerPed.h" +#include "Pools.h" +#include "Projectile.h" +#include "ProjectileInfo.h" +#include "Replay.h" +#include "Stats.h" +#include "SurfaceTable.h" +#include "Train.h" +#include "Transmission.h" +#include "Vehicle.h" +#include "WaterCannon.h" +#include "Weather.h" +#include "ZoneCull.h" +#include "sampman.h" + +#ifndef GTA_PS2 +#define CHANNEL_PLAYER_VEHICLE_ENGINE m_nActiveSamples +#endif + +enum eVehicleModel { + LANDSTAL, + IDAHO, + STINGER, + LINERUN, + PEREN, + SENTINEL, + PATRIOT, + FIRETRUK, + TRASH, + STRETCH, + MANANA, + INFERNUS, + BLISTA, + PONY, + MULE, + CHEETAH, + AMBULAN, + FBICAR, + MOONBEAM, + ESPERANT, + TAXI, + KURUMA, + BOBCAT, + MRWHOOP, + BFINJECT, + CORPSE, + POLICE, + ENFORCER, + SECURICA, + BANSHEE, + PREDATOR, + BUS, + RHINO, + BARRACKS, + TRAIN, + CHOPPER, + DODO, + COACH, + CABBIE, + STALLION, + RUMPO, + RCBANDIT, + BELLYUP, + MRWONGS, + MAFIA, + YARDIE, + YAKUZA, + DIABLOS, + COLUMB, + HOODS, + AIRTRAIN, + DEADDODO, + SPEEDER, + REEFER, + PANLANT, + FLATBED, + YANKEE, + ESCAPE, + BORGNINE, + TOYZ, + GHOST, + CAR151, + CAR152, + CAR153, + CAR154, + CAR155, + CAR156, + CAR157, + CAR158, + CAR159, + MAX_CARS +}; + +enum +{ + OLD_DOOR = 0, + NEW_DOOR, + TRUCK_DOOR, + BUS_DOOR, +}; + + +struct tVehicleSampleData { + eSfxSample m_nAccelerationSampleIndex; + uint8 m_nBank; + eSfxSample m_nHornSample; + int32 m_nHornFrequency; + uint8 m_nSirenOrAlarmSample; + int32 m_nSirenOrAlarmFrequency; + uint8 m_bDoorType; +}; + +Const static tVehicleSampleData aVehicleSettings[MAX_CARS] = { + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 11487, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_COBRA, SFX_CAR_HORN_PORSCHE, 11025, SFX_CAR_ALARM_1, 10928, NEW_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 12893, SFX_CAR_ALARM_1, 8941, OLD_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_BMW328, 10706, SFX_CAR_ALARM_1, 11922, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_ALARM_1, 7948, TRUCK_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29711, SFX_POLICE_SIREN_SLOW, 11556, TRUCK_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 31478, SFX_CAR_ALARM_1, 8941, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_BMW328, 9538, SFX_CAR_ALARM_1, 12220, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10842, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_PORSCHE, SFX_CAR_HORN_BMW328, 12017, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_JEEP, 22295, SFX_CAR_ALARM_1, 12200, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS2, 18000, SFX_CAR_ALARM_1, 13400, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 18286, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_PORSCHE, SFX_CAR_HORN_PORSCHE, 11025, SFX_CAR_ALARM_1, 13600, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 22295, SFX_AMBULANCE_SIREN_SLOW, 8795, TRUCK_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_PORSCHE, 9271, SFX_POLICE_SIREN_SLOW, 16168, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 12170, SFX_CAR_ALARM_1, 8000, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_BUS2, 12345, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_BMW328, 10796, SFX_CAR_ALARM_1, 8543, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_PORSCHE, 9271, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_PICKUP, 10924, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_PICKUP, 11025, SFX_ICE_CREAM_TUNE, 11025, OLD_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_HOTROD, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 10000, OLD_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_BMW328, 10706, SFX_POLICE_SIREN_SLOW, 13596, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 17260, SFX_POLICE_SIREN_SLOW, 13000, TRUCK_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_PICKUP, 8670, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_COBRA, SFX_CAR_HORN_PORSCHE, 10400, SFX_CAR_ALARM_1, 10123, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 26513, SFX_POLICE_SIREN_SLOW, 13596, OLD_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_BUS2, 11652, SFX_CAR_ALARM_1, 10554, BUS_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_ALARM_1, 8000, TRUCK_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 28043, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_ALARM_1, 9935, BUS_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CESNA_IDLE, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_BUS, 16291, SFX_CAR_ALARM_1, 7500, BUS_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10842, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10233, SFX_CAR_ALARM_1, 8935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_PICKUP, 8670, SFX_CAR_ALARM_1, 8935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_PICKUP, 2000, SFX_CAR_ALARM_1, 17000, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_BMW328, 9003, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PATHFINDER, SFX_CAR_HORN_PORSCHE, 12375, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_MERC, SFX_CAR_HORN_BUS2, 15554, SFX_CAR_ALARM_1, 9935, NEW_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_HOTROD, SFX_CAR_HORN_BUS2, 13857, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_HOTROD, SFX_CAR_HORN_PICKUP, 10924, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 20143, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9000, OLD_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 28043, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 18286, SFX_CAR_ALARM_1, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10842, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS2, 18000, SFX_CAR_ALARM_1, 13400, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_ALARM_1, 9935, OLD_DOOR}}; + + +uint32 gPornNextTime; +uint32 gSawMillNextTime; +uint32 gShopNextTime; +uint32 gAirportNextTime; +uint32 gCinemaNextTime; +uint32 gDocksNextTime; +uint32 gHomeNextTime; +uint32 gCellNextTime; +uint32 gNextCryTime; + +void +cAudioManager::PreInitialiseGameSpecificSetup() +{ + BankStartOffset[SFX_BANK_0] = SAMPLEBANK_START; +#ifdef GTA_PS2 + BankStartOffset[SFX_BANK_PACARD] = SFX_CAR_ACCEL_1; + BankStartOffset[SFX_BANK_PATHFINDER] = SFX_CAR_ACCEL_2; + BankStartOffset[SFX_BANK_PORSCHE] = SFX_CAR_ACCEL_3; + BankStartOffset[SFX_BANK_SPIDER] = SFX_CAR_ACCEL_4; + BankStartOffset[SFX_BANK_MERC] = SFX_CAR_ACCEL_5; + BankStartOffset[SFX_BANK_TRUCK] = SFX_CAR_ACCEL_6; + BankStartOffset[SFX_BANK_HOTROD] = SFX_CAR_ACCEL_7; + BankStartOffset[SFX_BANK_COBRA] = SFX_CAR_ACCEL_8; + BankStartOffset[SFX_BANK_NONE] = SFX_CAR_ACCEL_9; + BankStartOffset[SFX_BANK_FRONT_END_MENU] = SFX_PAGE_CHANGE_AND_BACK_LEFT; + BankStartOffset[SFX_BANK_TRAIN] = SFX_TRAIN_STATION_AMBIENCE_LOOP; + BankStartOffset[SFX_BANK_BUILDING_CLUB_1] = SFX_CLUB_1; + BankStartOffset[SFX_BANK_BUILDING_CLUB_2] = SFX_CLUB_2; + BankStartOffset[SFX_BANK_BUILDING_CLUB_3] = SFX_CLUB_3; + BankStartOffset[SFX_BANK_BUILDING_CLUB_4] = SFX_CLUB_4; + BankStartOffset[SFX_BANK_BUILDING_CLUB_5] = SFX_CLUB_5; + BankStartOffset[SFX_BANK_BUILDING_CLUB_6] = SFX_CLUB_6; + BankStartOffset[SFX_BANK_BUILDING_CLUB_7] = SFX_CLUB_7; + BankStartOffset[SFX_BANK_BUILDING_CLUB_8] = SFX_CLUB_8; + BankStartOffset[SFX_BANK_BUILDING_CLUB_9] = SFX_CLUB_9; + BankStartOffset[SFX_BANK_BUILDING_CLUB_10] = SFX_CLUB_10; + BankStartOffset[SFX_BANK_BUILDING_CLUB_11] = SFX_CLUB_11; + BankStartOffset[SFX_BANK_BUILDING_CLUB_12] = SFX_CLUB_12; + BankStartOffset[SFX_BANK_BUILDING_CLUB_RAGGA] = SFX_CLUB_RAGGA; + BankStartOffset[SFX_BANK_BUILDING_STRIP_CLUB_1] = SFX_STRIP_CLUB_1; + BankStartOffset[SFX_BANK_BUILDING_STRIP_CLUB_2] = SFX_STRIP_CLUB_2; + BankStartOffset[SFX_BANK_BUILDING_WORKSHOP] = SFX_WORKSHOP_1; + BankStartOffset[SFX_BANK_BUILDING_PIANO_BAR] = SFX_PIANO_BAR_1; + BankStartOffset[SFX_BANK_BUILDING_SAWMILL] = SFX_SAWMILL_LOOP; + BankStartOffset[SFX_BANK_BUILDING_DOG_FOOD_FACTORY] = SFX_DOG_FOOD_FACTORY; + BankStartOffset[SFX_BANK_BUILDING_LAUNDERETTE] = SFX_LAUNDERETTE_LOOP; + BankStartOffset[SFX_BANK_BUILDING_RESTAURANT_CHINATOWN] = SFX_RESTAURANT_CHINATOWN; + BankStartOffset[SFX_BANK_BUILDING_RESTAURANT_ITALY] = SFX_RESTAURANT_ITALY; + BankStartOffset[SFX_BANK_BUILDING_RESTAURANT_GENERIC_1] = SFX_RESTAURANT_GENERIC_1; + BankStartOffset[SFX_BANK_BUILDING_RESTAURANT_GENERIC_2] = SFX_RESTAURANT_GENERIC_2; + BankStartOffset[SFX_BANK_BUILDING_AIRPORT] = SFX_AIRPORT_ANNOUNCEMENT_1; + BankStartOffset[SFX_BANK_BUILDING_SHOP] = SFX_SHOP_LOOP; + BankStartOffset[SFX_BANK_BUILDING_CINEMA] = SFX_CINEMA_BASS_1; + BankStartOffset[SFX_BANK_BUILDING_DOCKS] = SFX_DOCKS_FOGHORN; + BankStartOffset[SFX_BANK_BUILDING_HOME] = SFX_HOME_1; + BankStartOffset[SFX_BANK_BUILDING_PORN_1] = SFX_PORN_1_LOOP; + BankStartOffset[SFX_BANK_BUILDING_PORN_2] = SFX_PORN_2_LOOP; + BankStartOffset[SFX_BANK_BUILDING_PORN_3] = SFX_PORN_3_LOOP; + BankStartOffset[SFX_BANK_BUILDING_POLICE_BALL] = SFX_POLICE_BALL_1; + BankStartOffset[SFX_BANK_BUILDING_BANK_ALARM] = SFX_BANK_ALARM_1; + BankStartOffset[SFX_BANK_BUILDING_RAVE_INDUSTRIAL] = SFX_RAVE_INDUSTRIAL; + BankStartOffset[SFX_BANK_BUILDING_RAVE_COMMERCIAL] = SFX_RAVE_COMMERCIAL; + BankStartOffset[SFX_BANK_BUILDING_RAVE_SUBURBAN] = SFX_RAVE_SUBURBAN; + BankStartOffset[SFX_BANK_BUILDING_RAVE_COMMERCIAL_2] = SFX_RAVE_COMMERCIAL_2; + BankStartOffset[SFX_BANK_BUILDING_39] = SFX_CLUB_1_1; + BankStartOffset[SFX_BANK_BUILDING_40] = SFX_CLUB_1_2; + BankStartOffset[SFX_BANK_BUILDING_41] = SFX_CLUB_1_3; + BankStartOffset[SFX_BANK_BUILDING_42] = SFX_CLUB_1_4; + BankStartOffset[SFX_BANK_BUILDING_43] = SFX_CLUB_1_5; + BankStartOffset[SFX_BANK_BUILDING_44] = SFX_CLUB_1_6; + BankStartOffset[SFX_BANK_BUILDING_45] = SFX_CLUB_1_7; + BankStartOffset[SFX_BANK_BUILDING_46] = SFX_CLUB_1_8; + BankStartOffset[SFX_BANK_BUILDING_47] = SFX_CLUB_1_9; + BankStartOffset[SFX_BANK_GENERIC_EXTRA] = SFX_EXPLOSION_1; +#endif // GTA_PS2 + BankStartOffset[SFX_BANK_PED_COMMENTS] = SAMPLEBANK_PED_START; +} + +void +cAudioManager::PostInitialiseGameSpecificSetup() +{ + m_nFireAudioEntity = CreateEntity(AUDIOTYPE_FIRE, &gFireManager); + if (m_nFireAudioEntity >= 0) + SetEntityStatus(m_nFireAudioEntity, TRUE); + + m_nCollisionEntity = CreateEntity(AUDIOTYPE_COLLISION, (void *)1); + if (m_nCollisionEntity >= 0) + SetEntityStatus(m_nCollisionEntity, TRUE); + + m_nFrontEndEntity = CreateEntity(AUDIOTYPE_FRONTEND, (void *)1); + if (m_nFrontEndEntity >= 0) + SetEntityStatus(m_nFrontEndEntity, TRUE); + + m_nProjectileEntity = CreateEntity(AUDIOTYPE_PROJECTILE, (void *)1); + if (m_nProjectileEntity >= 0) + SetEntityStatus(m_nProjectileEntity, TRUE); + + m_nWaterCannonEntity = CreateEntity(AUDIOTYPE_WATERCANNON, (void *)1); + if (m_nWaterCannonEntity >= 0) + SetEntityStatus(m_nWaterCannonEntity, TRUE); + + m_nPoliceChannelEntity = CreateEntity(AUDIOTYPE_POLICERADIO, (void *)1); + if (m_nPoliceChannelEntity >= 0) + SetEntityStatus(m_nPoliceChannelEntity, TRUE); + + m_nBridgeEntity = CreateEntity(AUDIOTYPE_BRIDGE, (void *)1); + if (m_nBridgeEntity >= 0) + SetEntityStatus(m_nBridgeEntity, TRUE); + + m_nMissionAudioSampleIndex = NO_SAMPLE; + m_nMissionAudioLoadingStatus = LOADING_STATUS_NOT_LOADED; + m_nMissionAudioPlayStatus = PLAY_STATUS_STOPPED; + m_bIsMissionAudioPlaying = FALSE; + m_bIsMissionAudioAllowedToPlay = FALSE; + m_bIsMissionAudio2D = TRUE; + m_nMissionAudioFramesToPlay = 0; + ResetAudioLogicTimers(CTimer::GetTimeInMilliseconds()); +} + +void +cAudioManager::PreTerminateGameSpecificShutdown() +{ + if (m_nBridgeEntity >= 0) { + DestroyEntity(m_nBridgeEntity); + m_nBridgeEntity = AEHANDLE_NONE; + } + if (m_nPoliceChannelEntity >= 0) { + DestroyEntity(m_nPoliceChannelEntity); + m_nPoliceChannelEntity = AEHANDLE_NONE; + } + if (m_nWaterCannonEntity >= 0) { + DestroyEntity(m_nWaterCannonEntity); + m_nWaterCannonEntity = AEHANDLE_NONE; + } + if (m_nFireAudioEntity >= 0) { + DestroyEntity(m_nFireAudioEntity); + m_nFireAudioEntity = AEHANDLE_NONE; + } + if (m_nCollisionEntity >= 0) { + DestroyEntity(m_nCollisionEntity); + m_nCollisionEntity = AEHANDLE_NONE; + } + if (m_nFrontEndEntity >= 0) { + DestroyEntity(m_nFrontEndEntity); + m_nFrontEndEntity = AEHANDLE_NONE; + } + if (m_nProjectileEntity >= 0) { + DestroyEntity(m_nProjectileEntity); + m_nProjectileEntity = AEHANDLE_NONE; + } +} + +void +cAudioManager::PostTerminateGameSpecificShutdown() +{ + ; +} + +void +cAudioManager::ResetAudioLogicTimers(uint32 timer) +{ + gPornNextTime = timer; + gNextCryTime = timer; + gSawMillNextTime = timer; + gCellNextTime = timer; + gShopNextTime = timer; + gHomeNextTime = timer; + gAirportNextTime = timer; + gDocksNextTime = timer; + gCinemaNextTime = timer; + for (uint32 i = 0; i < m_nAudioEntitiesCount; i++) { + if (m_asAudioEntities[m_aAudioEntityOrderList[i]].m_nType == AUDIOTYPE_PHYSICAL) { + CPed *ped = (CPed *)m_asAudioEntities[m_aAudioEntityOrderList[i]].m_pEntity; + if (ped->IsPed()) { + ped->m_lastSoundStart = timer; + ped->m_soundStart = timer + m_anRandomTable[0] % 3000; + } + } + } + ClearMissionAudio(); + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); +} + +void +cAudioManager::ProcessReverb() +{ +#ifdef EXTERNAL_3D_SOUND + if (SampleManager.UpdateReverb() && m_bDynamicAcousticModelingStatus) { +#ifndef GTA_PS2 + for (uint32 i = 0; i < +#ifdef FIX_BUGS + NUM_CHANNELS_GENERIC +#else + NUM_CHANNELS_GENERIC+1 +#endif + ; + i++) { + if (m_asActiveSamples[i].m_bReverb) + SampleManager.SetChannelReverbFlag(i, TRUE); + } +#endif + } +#else + static uint8 OldVolL = 0; + static uint8 OldVolR = 0; + + uint8 VolL = Min(40, 3 * (20 - TheCamera.SoundDistLeft)) + 20; + uint8 VolR = Min(40, 3 * (20 - TheCamera.SoundDistRight)) + 20; + + uint8 VolUp = 5 * (20 - TheCamera.SoundDistUp); + + VolL = Min(MAX_VOLUME, VolL + VolUp); + VolR = Min(MAX_VOLUME, VolR + VolUp); + + if (OldVolL != VolL || OldVolR != VolR) { + SampleManager.UpdateReverb(VolL, VolR, 100, 15, 80); + OldVolL = VolL; + OldVolR = VolR; + } +#endif +} + +float +cAudioManager::GetDistanceSquared(const CVector &v) +{ + const CVector &c = TheCamera.GetPosition(); + return sq(v.x - c.x) + sq(v.y - c.y) + sq((v.z - c.z) * 0.2f); +} + +void +cAudioManager::CalculateDistance(bool8 &distCalculated, float dist) +{ + if (!distCalculated) { + m_sQueueSample.m_fDistance = Sqrt(dist); + distCalculated = TRUE; + } +} + +void +cAudioManager::ProcessSpecial() +{ + if (m_bIsPaused) { + if (!m_bWasPaused) { + MusicManager.ChangeMusicMode(MUSICMODE_FRONTEND); +#ifdef GTA_PS2 + if (SampleManager.IsSampleBankLoaded(SFX_BANK_FRONT_END_MENU) == LOADING_STATUS_NOT_LOADED) + SampleManager.LoadSampleBank(SFX_BANK_FRONT_END_MENU); +#else + SampleManager.SetEffectsFadeVolume(MAX_VOLUME); + SampleManager.SetMusicFadeVolume(MAX_VOLUME); +#endif + } +#ifdef GTA_PS2 + else { + int8 isBankLoaded = SampleManager.IsSampleBankLoaded(SFX_BANK_FRONT_END_MENU); + if (isBankLoaded != -1 && isBankLoaded == LOADING_STATUS_NOT_LOADED) // what a useless -1 check + SampleManager.LoadSampleBank(SFX_BANK_FRONT_END_MENU); + } +#endif + } else { + if (m_bWasPaused) { + MusicManager.StopFrontEndTrack(); + MusicManager.ChangeMusicMode(MUSICMODE_GAME); + } + CPlayerPed *playerPed = FindPlayerPed(); + if (playerPed) { + if(!playerPed->EnteringCar() && !playerPed->bInVehicle) + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); +#ifdef GTA_PS2 + else { + int8 isBankLoaded = SampleManager.IsSampleBankLoaded(aVehicleSettings[playerPed->m_pMyVehicle->GetModelIndex() - MI_FIRST_VEHICLE].m_nBank); + if (isBankLoaded != -1 && isBankLoaded == LOADING_STATUS_NOT_LOADED) { // again, useless -1 check + if (playerPed->m_pMyVehicle->GetType() == ENTITY_TYPE_VEHICLE // no shit, what else could it be? + && playerPed->m_pMyVehicle->IsCar()) + SampleManager.LoadSampleBank(aVehicleSettings[playerPed->m_pMyVehicle->GetModelIndex() - MI_FIRST_VEHICLE].m_nBank); + } + } +#endif + } + } +} + +void +cAudioManager::ProcessEntity(int32 id) +{ + if (m_asAudioEntities[id].m_bStatus) { + m_sQueueSample.m_nEntityIndex = id; + switch (m_asAudioEntities[id].m_nType) { + case AUDIOTYPE_PHYSICAL: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessPhysical(id); + } + break; + case AUDIOTYPE_EXPLOSION: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessExplosions(id); + } + break; + case AUDIOTYPE_FIRE: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessFires(id); + } + break; + case AUDIOTYPE_WEATHER: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessWeather(id); + } + break; + case AUDIOTYPE_CRANE: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessCrane(); + } + break; + case AUDIOTYPE_SCRIPTOBJECT: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessScriptObject(id); + } + break; + case AUDIOTYPE_BRIDGE: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessBridge(); + } + break; + case AUDIOTYPE_FRONTEND: + m_sQueueSample.m_bReverb = FALSE; + ProcessFrontEnd(); + break; + case AUDIOTYPE_PROJECTILE: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessProjectiles(); + } + break; + case AUDIOTYPE_GARAGE: + if (!m_bIsPaused) + ProcessGarages(); + break; + case AUDIOTYPE_FIREHYDRANT: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessFireHydrant(); + } + break; + case AUDIOTYPE_WATERCANNON: + if (!m_bIsPaused) { + m_sQueueSample.m_bReverb = TRUE; + ProcessWaterCannon(id); + } + break; + default: + return; + } + } +} + +void +cAudioManager::ProcessPhysical(int32 id) +{ + CPhysical *entity = (CPhysical *)m_asAudioEntities[id].m_pEntity; + if (entity) { + switch (entity->GetType()) { + case ENTITY_TYPE_VEHICLE: + ProcessVehicle((CVehicle *)entity); + break; + case ENTITY_TYPE_PED: + ProcessPed(entity); + break; + default: + return; + } + } +} + +enum +{ + RAIN_ON_VEHICLE_MAX_DIST = 22, + RAIN_ON_VEHICLE_VOLUME = 30, + + REVERSE_GEAR_MAX_DIST = 30, + REVERSE_GEAR_VOLUME = 24, + + MODEL_CAR_ENGINE_MAX_DIST = 30, + MODEL_CAR_ENGINE_VOLUME = 90, + + VEHICLE_ROAD_NOISE_MAX_DIST = 95, + VEHICLE_ROAD_NOISE_VOLUME = 30, + + WET_ROAD_NOISE_MAX_DIST = 30, + WET_ROAD_NOISE_VOLUME = 23, + + VEHICLE_ENGINE_MAX_DIST = 50, + VEHICLE_ENGINE_BASE_VOLUME = 80, + VEHICLE_ENGINE_FULL_VOLUME = 120, + + CESNA_IDLE_MAX_DIST = 200, + CESNA_REV_MAX_DIST = 90, + CESNA_VOLUME = 80, + + PLAYER_VEHICLE_ENGINE_VOLUME = 85, + + VEHICLE_SKIDDING_MAX_DIST = 40, + VEHICLE_SKIDDING_VOLUME = 50, + + VEHICLE_HORN_MAX_DIST = 40, + VEHICLE_HORN_VOLUME = 80, + + VEHICLE_SIREN_MAX_DIST = 110, + VEHICLE_SIREN_VOLUME = 80, + + VEHICLE_REVERSE_WARNING_MAX_DIST = 50, + VEHICLE_REVERSE_WARNING_VOLUME = 60, + + VEHICLE_DOORS_MAX_DIST = 40, + VEHICLE_DOORS_VOLUME = 100, + + AIR_BRAKES_MAX_DIST = 30, + AIR_BRAKES_VOLUME = 70, + + ENGINE_DAMAGE_MAX_DIST = 40, + ENGINE_DAMAGE_VOLUME = 6, + ENGINE_DAMAGE_ON_FIRE_VOLUME = 60, + + CAR_BOMB_TICK_MAX_DIST = 40, + CAR_BOMB_TICK_VOLUME = 60, + + VEHICLE_ONE_SHOT_DOOR_MAX_DIST = 50, + VEHICLE_ONE_SHOT_DOOR_OPEN_VOLUME = 122, + VEHICLE_ONE_SHOT_DOOR_CLOSE_VOLUME = 117, + + VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST = 30, + VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_VOLUME = 60, + + VEHICLE_ONE_SHOT_CAR_JUMP_MAX_DIST = 35, + VEHICLE_ONE_SHOT_CAR_JUMP_VOLUME = 80, + + VEHICLE_ONE_SHOT_CAR_ENGINE_START_MAX_DIST = 40, + VEHICLE_ONE_SHOT_CAR_ENGINE_START_VOLUME = 60, + + VEHICLE_ONE_SHOT_CAR_LIGHT_BREAK_VOLUME = 30, + + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST = 35, + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_VOLUME = 55, + + VEHICLE_ONE_SHOT_CAR_SPLASH_MAX_DIST = 40, + VEHICLE_ONE_SHOT_CAR_SPLASH_VOLUME = 55, + + VEHICLE_ONE_SHOT_BOAT_SLOWDOWN_MAX_DIST = 50, + + VEHICLE_ONE_SHOT_TRAIN_DOOR_MAX_DIST = 35, + VEHICLE_ONE_SHOT_TRAIN_DOOR_VOLUME = 70, + + VEHICLE_ONE_SHOT_CAR_TANK_TURRET_MAX_DIST = 40, + VEHICLE_ONE_SHOT_CAR_TANK_TURRET_VOLUME = 90, + + VEHICLE_ONE_SHOT_CAR_BOMB_TICK_MAX_DIST = 30, + VEHICLE_ONE_SHOT_CAR_BOMB_TICK_VOLUME = CAR_BOMB_TICK_VOLUME, + + VEHICLE_ONE_SHOT_PLANE_ON_GROUND_MAX_DIST = 180, + VEHICLE_ONE_SHOT_PLANE_ON_GROUND_VOLUME = 75, + + VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST = 120, + VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_VOLUME = 65, + + VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_MAX_DIST = 40, + VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_VOLUME = 90, + + VEHICLE_ONE_SHOT_BOMB_ARMED_MAX_DIST = 50, + VEHICLE_ONE_SHOT_BOMB_ARMED_VOLUME = 50, + + VEHICLE_ONE_SHOT_WATER_FALL_MAX_DIST = 40, + VEHICLE_ONE_SHOT_WATER_FALL_VOLUME = 90, + + VEHICLE_ONE_SHOT_SPLATTER_MAX_DIST = 40, + VEHICLE_ONE_SHOT_SPLATTER_VOLUME = 55, + + VEHICLE_ONE_SHOT_CAR_PED_COLLISION_MAX_DIST = 40, + + TRAIN_NOISE_FAR_MAX_DIST = 300, + TRAIN_NOISE_NEAR_MAX_DIST = 70, + TRAIN_NOISE_VOLUME = 75, + + BOAT_ENGINE_MAX_DIST = 50, + BOAT_ENGINE_REEFER_IDLE_VOLUME = 50, + BOAT_ENGINE_REEFER_ACCEL_MIN_VOLUME = 15, + BOAT_ENGINE_REEFER_ACCEL_VOLUME_MULT = 100, + + BOAT_ENGINE_LOW_ACCEL_VOLUME = 45, + BOAT_ENGINE_HIGH_ACCEL_MIN_VOLUME = 15, + BOAT_ENGINE_HIGH_ACCEL_VOLUME_MULT = 105, + + BOAT_MOVING_OVER_WATER_MAX_DIST = 50, + BOAT_MOVING_OVER_WATER_VOLUME = 30, + + JUMBO_MAX_DIST = 440, + JUMBO_RUMBLE_SOUND_MAX_DIST = 240, + JUMBO_ENGINE_SOUND_MAX_DIST = 180, + JUMBO_WHINE_SOUND_MAX_DIST = 170, + + PED_HEADPHONES_MAX_DIST = 7, + PED_HEADPHONES_VOLUME = 42, + PED_HEADPHONES_IN_CAR_VOLUME = 10, + + PED_ONE_SHOT_STEP_MAX_DIST = 20, + PED_ONE_SHOT_STEP_VOLUME = 45, + + PED_ONE_SHOT_FALL_MAX_DIST = 30, + PED_ONE_SHOT_FALL_VOLUME = 80, + + PED_ONE_SHOT_PUNCH_MAX_DIST = 30, + PED_ONE_SHOT_PUNCH_VOLUME = 100, + + PED_ONE_SHOT_WEAPON_COLT45_MAX_DIST = 50, + PED_ONE_SHOT_WEAPON_COLT45_VOLUME = 90, + + PED_ONE_SHOT_WEAPON_UZI_MAX_DIST = 80, + PED_ONE_SHOT_WEAPON_UZI_VOLUME = 70, + + PED_ONE_SHOT_WEAPON_SHOTGUN_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_SHOTGUN_VOLUME = 100, + + PED_ONE_SHOT_WEAPON_AK47_MAX_DIST = 80, + PED_ONE_SHOT_WEAPON_AK47_VOLUME = 70, + + PED_ONE_SHOT_WEAPON_M16_MAX_DIST = 80, + PED_ONE_SHOT_WEAPON_M16_VOLUME = 70, + + PED_ONE_SHOT_WEAPON_SNIPERRIFLE_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_SNIPERRIFLE_VOLUME = 110, + + PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_MAX_DIST = 90, + PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_VOLUME = 80, + + PED_ONE_SHOT_WEAPON_FLAMETHROWER_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_FLAMETHROWER_VOLUME = 90, + + PED_ONE_SHOT_WEAPON_RELOAD_MAX_DIST = 30, + PED_ONE_SHOT_WEAPON_RELOAD_VOLUME = 75, + + PED_ONE_SHOT_WEAPON_BULLET_ECHO_MAX_DIST = 80, + PED_ONE_SHOT_WEAPON_BULLET_ECHO_VOLUME = 40, + + PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_VOLUME = 70, + + PED_ONE_SHOT_WEAPON_HIT_PED_MAX_DIST = 30, + PED_ONE_SHOT_WEAPON_HIT_PED_VOLUME = 90, + + PED_ONE_SHOT_SPLASH_MAX_DIST = 40, + PED_ONE_SHOT_SPLASH_PED_VOLUME = 70, + + PED_COMMENT_MAX_DIST = 50, + PED_COMMENT_POLICE_HELI_MAX_DIST = 400, + + EXPLOSION_DEFAULT_MAX_DIST = 400, + EXPLOSION_MOLOTOV_MAX_DIST = 200, + EXPLOSION_MINE_MAX_DIST = 300, + + FIRE_DEFAULT_MAX_DIST = 50, + FIRE_DEFAULT_VOLUME = 80, + FIRE_BUILDING_MAX_DIST = 50, + FIRE_BUILDING_VOLUME = 100, + FIRE_PED_MAX_DIST = 25, + FIRE_PED_VOLUME = 60, + + WATER_CANNON_MAX_DIST = 30, + WATER_CANNON_VOLUME = 50, + + SCRIPT_OBJECT_GATE_MAX_DIST = 40, + + SCRIPT_OBJECT_BULLET_HIT_GROUND_MAX_DIST = 50, + SCRIPT_OBJECT_BULLET_HIT_GROUND_VOLUME = 90, + + SCRIPT_OBJECT_TRAIN_ANNOUNCEMENT_MAX_DIST = 80, + SCRIPT_OBJECT_TRAIN_ANNOUNCEMENT_VOLUME = MAX_VOLUME, + + SCRIPT_OBJECT_PAYPHONE_RINGING_MAX_DIST = 80, + SCRIPT_OBJECT_PAYPHONE_RINGING_VOLUME = 80, + + SCRIPT_OBJECT_GLASS_BREAK_MAX_DIST = 60, + SCRIPT_OBJECT_GLASS_BREAK_LONG_VOLUME = 70, + SCRIPT_OBJECT_GLASS_BREAK_SHORT_VOLUME = 60, + + SCRIPT_OBJECT_GLASS_LIGHT_BREAK_MAX_DIST = 55, + SCRIPT_OBJECT_GLASS_LIGHT_BREAK_VOLUME = 25, + + SCRIPT_OBJECT_BOX_DESTROYED_MAX_DIST = 60, + SCRIPT_OBJECT_BOX_DESTROYED_VOLUME = 80, + + SCRIPT_OBJECT_METAL_COLLISION_VOLUME = 70, + SCRIPT_OBJECT_TIRE_COLLISION_VOLUME = 60, + + SCRIPT_OBJECT_GUNSHELL_MAX_DIST = 20, + SCRIPT_OBJECT_GUNSHELL_VOLUME = 30, + + SCRIPT_OBJECT_SHORT_MAX_DIST = 30, + SCRIPT_OBJECT_LONG_MAX_DIST = 80, + SCRIPT_OBJECT_DEFAULT_VOLUME = MAX_VOLUME, + SCRIPT_OBJECT_RESAURANT_VOLUME = 110, + SCRIPT_OBJECT_BANK_ALARM_VOLUME = 90, + + PORN_CINEMA_SHORT_MAX_DIST = 20, + PORN_CINEMA_LONG_MAX_DIST = SCRIPT_OBJECT_LONG_MAX_DIST, + PORN_CINEMA_VOLUME = SCRIPT_OBJECT_DEFAULT_VOLUME, + PORN_CINEMA_MOAN_VOLUME = 90, + + WORK_SHOP_MAX_DIST = 20, + WORK_SHOP_VOLUME = 30, + + SAWMILL_VOLUME = 30, + SAWMILL_CUT_WOOD_VOLUME = 70, + + LAUNDERETTE_VOLUME = 45, + LAUNDERETTE_SONG_VOLUME = 110, + + SHOP_VOLUME = 30, + SHOP_TILL_VOLUME = 70, + + AIRPORT_VOLUME = 110, + + CINEMA_VOLUME = 30, + DOCKS_VOLUME = 40, + HOME_VOLUME = 40, + POLICE_CELL_BEATING_VOLUME = 55, + + FRONTEND_VOLUME = 110, + + CRANE_MAX_DIST = 80, + CRANE_VOLUME = 100, + + PROJECTILE_ROCKET_MAX_DIST = 90, + PROJECTILE_ROCKET_VOLUME = MAX_VOLUME, + + PROJECTILE_MOLOTOV_MAX_DIST = 30, + PROJECTILE_MOLOTOV_VOLUME = 50, + + GARAGES_MAX_DIST = 80, + GARAGES_VOLUME = 90, + GARAGES_DOOR_VOLUME = 60, + + FIRE_HYDRANT_MAX_DIST = 35, + FIRE_HYDRANT_VOLUME = 40, + + BRIDGE_MOTOR_MAX_DIST = 400, + BRIDGE_MOTOR_VOLUME = MAX_VOLUME, + BRIDGE_MAX_DIST = BRIDGE_MOTOR_MAX_DIST + 50, + + BRIDGE_WARNING_VOLUME = 100, + + MISSION_AUDIO_MAX_DIST = 50, +#ifdef GTA_PS2 + MISSION_AUDIO_VOLUME = MAX_VOLUME, +#else + MISSION_AUDIO_VOLUME = 80, +#endif +}; + +#pragma region VEHICLE AUDIO +bool8 bPlayerJustEnteredCar; + +Const static bool8 HornPattern[8][44] = { + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, + FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, + TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, + TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE}, + {FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE}, +}; + +void +cAudioManager::ProcessVehicle(CVehicle *veh) +{ + cVehicleParams params; + + m_sQueueSample.m_vecPos = veh->GetPosition(); + params.m_bDistanceCalculated = FALSE; + params.m_pVehicle = veh; + params.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + params.m_pTransmission = veh->pHandling != nil ? &veh->pHandling->Transmission : nil; + params.m_nIndex = veh->GetModelIndex() - MI_FIRST_VEHICLE; + if (params.m_pVehicle->GetStatus() == STATUS_SIMPLE) + params.m_fVelocityChange = params.m_pVehicle->AutoPilot.m_fMaxTrafficSpeed * 0.02f; + else + params.m_fVelocityChange = DotProduct(params.m_pVehicle->m_vecMoveSpeed, params.m_pVehicle->GetForward()); + switch (params.m_pVehicle->m_vehType) { + case VEHICLE_TYPE_CAR: + UpdateGasPedalAudio((CAutomobile *)veh); + if (params.m_nIndex == RCBANDIT) { + ProcessModelCarEngine(params); + ProcessVehicleOneShots(params); + ((CAutomobile *)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + } + if (params.m_nIndex == DODO) { + if (!ProcessVehicleRoadNoise(params)) { + ProcessVehicleOneShots(params); + ((CAutomobile *)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + } + if (CWeather::WetRoads > 0.0f) + ProcessWetRoadNoise(params); + ProcessVehicleSkidding(params); + } else { + if (!ProcessVehicleRoadNoise(params)) { + ProcessVehicleOneShots(params); + ((CAutomobile *)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + } + ProcessReverseGear(params); + if (CWeather::WetRoads > 0.0f) + ProcessWetRoadNoise(params); + ProcessVehicleSkidding(params); + ProcessVehicleHorn(params); + ProcessVehicleSirenOrAlarm(params); + if (UsesReverseWarning(params.m_nIndex)) + ProcessVehicleReverseWarning(params); + if (HasAirBrakes(params.m_nIndex)) + ProcessAirBrakes(params); + } + ProcessCarBombTick(params); + ProcessVehicleEngine(params); + ProcessEngineDamage(params); + ProcessVehicleDoors(params); + + ProcessVehicleOneShots(params); + ((CAutomobile *)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + case VEHICLE_TYPE_BOAT: + ProcessBoatEngine(params); + ProcessBoatMovingOverWater(params); + ProcessVehicleOneShots(params); + break; + case VEHICLE_TYPE_TRAIN: + ProcessTrainNoise(params); + ProcessVehicleOneShots(params); + break; + case VEHICLE_TYPE_HELI: + ProcessHelicopter(params); + ProcessVehicleOneShots(params); + break; + case VEHICLE_TYPE_PLANE: + ProcessPlane(params); + ProcessVehicleOneShots(params); + break; + default: + break; + } + ProcessRainOnVehicle(params); +} + +void +cAudioManager::ProcessRainOnVehicle(cVehicleParams& params) +{ + if (params.m_fDistance < SQR(RAIN_ON_VEHICLE_MAX_DIST) && CWeather::Rain > 0.01f && (!CCullZones::CamNoRain() || !CCullZones::PlayerNoRain())) { + CVehicle *veh = params.m_pVehicle; + veh->m_bRainAudioCounter++; + if (veh->m_bRainAudioCounter >= 2) { + veh->m_bRainAudioCounter = 0; + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + uint8 Vol = RAIN_ON_VEHICLE_VOLUME * CWeather::Rain; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, RAIN_ON_VEHICLE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = veh->m_bRainSamplesCounter++; + if (veh->m_bRainSamplesCounter > 4) + veh->m_bRainSamplesCounter = 68; + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[1] & 3) + SFX_CAR_RAIN_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 9; + m_sQueueSample.m_nFrequency = m_anRandomTable[1] % 4000 + 28000; + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = RAIN_ON_VEHICLE_MAX_DIST; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +bool8 +cAudioManager::ProcessReverseGear(cVehicleParams& params) +{ + CVehicle *veh; + CAutomobile *automobile; + uint8 Vol; + float modificator; + + if (params.m_fDistance < SQR(REVERSE_GEAR_MAX_DIST)) { + veh = params.m_pVehicle; + if (veh->bEngineOn && (veh->m_fGasPedal < 0.0f || veh->m_nCurrentGear == 0)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->m_nWheelsOnGround > 0) + modificator = params.m_fVelocityChange / params.m_pTransmission->fMaxReverseVelocity; + else { + if (automobile->m_nDriveWheelsOnGround > 0) + automobile->m_fGasPedalAudio *= 0.4f; + modificator = automobile->m_fGasPedalAudio; + } + modificator = ABS(modificator); + Vol = (REVERSE_GEAR_VOLUME * modificator); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, REVERSE_GEAR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (params.m_pVehicle->m_fGasPedal < 0.0f) { + m_sQueueSample.m_nCounter = 61; + m_sQueueSample.m_nSampleIndex = SFX_REVERSE_GEAR; + } else { + m_sQueueSample.m_nCounter = 62; + m_sQueueSample.m_nSampleIndex = SFX_REVERSE_GEAR_2; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFrequency = (6000 * modificator) + 7000; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = REVERSE_GEAR_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::ProcessModelCarEngine(cVehicleParams& params) +{ + CAutomobile *automobile; + float allowedVelocity; + uint8 Vol; + float velocityChange; + + if (params.m_fDistance < SQR(MODEL_CAR_ENGINE_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->bEngineOn) { + if (automobile->m_nWheelsOnGround > 0) + velocityChange = Abs(params.m_fVelocityChange); + else { + if (automobile->m_nDriveWheelsOnGround > 0) + automobile->m_fGasPedalAudio *= 0.4f; + velocityChange = automobile->m_fGasPedalAudio * params.m_pTransmission->fMaxVelocity; + } + if (velocityChange > 0.001f) { + allowedVelocity = 0.5f * params.m_pTransmission->fMaxVelocity; + if (velocityChange < allowedVelocity) + Vol = (MODEL_CAR_ENGINE_VOLUME * velocityChange / allowedVelocity); + else + Vol = MODEL_CAR_ENGINE_VOLUME; + if (Vol > 0) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, MODEL_CAR_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nSampleIndex = SFX_REMOTE_CONTROLLED_CAR; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = (11025 * velocityChange / params.m_pTransmission->fMaxVelocity + 11025); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = MODEL_CAR_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } + } +} + +bool8 +cAudioManager::ProcessVehicleRoadNoise(cVehicleParams& params) +{ + uint8 Vol; + uint32 freq; + float multiplier; + int sampleFreq; + float velocity; + + if (params.m_fDistance < SQR(VEHICLE_ROAD_NOISE_MAX_DIST)) { + if ((params.m_pTransmission != nil) && (((CAutomobile*)params.m_pVehicle)->m_nDriveWheelsOnGround > 0)) { + velocity = Abs(params.m_fVelocityChange); + if (velocity > 0.0f) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + Vol = VEHICLE_ROAD_NOISE_VOLUME * Min(1.0f, velocity / (0.5f * params.m_pTransmission->fMaxVelocity)); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_ROAD_NOISE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + if (params.m_pVehicle->m_nSurfaceTouched == SURFACE_WATER) { + m_sQueueSample.m_nSampleIndex = SFX_BOAT_WATER_LOOP; + freq = 6050 * Vol / VEHICLE_ROAD_NOISE_VOLUME + 16000; + } else { + m_sQueueSample.m_nSampleIndex = SFX_ROAD_NOISE; + multiplier = (m_sQueueSample.m_fDistance / VEHICLE_ROAD_NOISE_MAX_DIST) * 0.5f; + sampleFreq = SampleManager.GetSampleBaseFrequency(SFX_ROAD_NOISE); + freq = (sampleFreq * multiplier) + ((3 * sampleFreq) >> 2); + } + m_sQueueSample.m_nFrequency = freq; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ROAD_NOISE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessWetRoadNoise(cVehicleParams& params) +{ + float relativeVelocity; + uint8 Vol; + float multiplier; + int freq; + float velChange; + + if (params.m_fDistance < SQR(WET_ROAD_NOISE_MAX_DIST)) { + if ((params.m_pTransmission != nil) && (((CAutomobile*)params.m_pVehicle)->m_nDriveWheelsOnGround > 0)) { + velChange = Abs(params.m_fVelocityChange); + if (velChange > 0.0f) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + relativeVelocity = Min(1.0f, velChange / (0.5f * params.m_pTransmission->fMaxVelocity)); + Vol = WET_ROAD_NOISE_VOLUME * relativeVelocity * CWeather::WetRoads; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, WET_ROAD_NOISE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nSampleIndex = SFX_ROAD_NOISE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + multiplier = (m_sQueueSample.m_fDistance / WET_ROAD_NOISE_MAX_DIST) * 0.5f; + freq = SampleManager.GetSampleBaseFrequency(SFX_ROAD_NOISE); + m_sQueueSample.m_nFrequency = freq + freq * multiplier; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = WET_ROAD_NOISE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessVehicleEngine(cVehicleParams& params) +{ + CAutomobile *automobile; + float relativeGearChange; +#ifdef FIX_BUGS + uint32 freq = 0; // uninitialized variable +#else + uint32 freq; +#endif + uint8 Vol; + cTransmission *transmission; + uint8 currentGear; + float modificator; + float traction = 0.0f; + + if (params.m_fDistance < SQR(VEHICLE_ENGINE_MAX_DIST)) { + if (FindPlayerVehicle() == params.m_pVehicle && params.m_pVehicle->GetStatus() == STATUS_WRECKED) { + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + return TRUE; + } + if (params.m_pVehicle->bEngineOn) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + automobile = (CAutomobile *)params.m_pVehicle; + if (params.m_nIndex == DODO) + ProcessCesna(params); + else if (FindPlayerVehicle() == automobile) + ProcessPlayersVehicleEngine(params, automobile); + else { + transmission = params.m_pTransmission; + if (transmission != nil) { + currentGear = params.m_pVehicle->m_nCurrentGear; + if (automobile->m_nWheelsOnGround > 0) { + if (automobile->bIsHandbrakeOn) { + if (params.m_fVelocityChange == 0.0f) + traction = 0.9f; + } else if (params.m_pVehicle->GetStatus() == STATUS_SIMPLE) { + traction = 0.0f; + } else { + switch (transmission->nDriveType) { + case '4': + for (uint8 i = 0; i < ARRAY_SIZE(automobile->m_aWheelState); i++) { + if (automobile->m_aWheelState[i] == WHEEL_STATE_SPINNING) + traction += 0.05f; + } + break; + case 'F': + if (automobile->m_aWheelState[CARWHEEL_FRONT_LEFT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + if (automobile->m_aWheelState[CARWHEEL_FRONT_RIGHT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + break; + case 'R': + if (automobile->m_aWheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + if (automobile->m_aWheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + break; + default: + break; + } + } + if (transmission->fMaxVelocity > 0.0f) { + if (currentGear > 0) { + relativeGearChange = + Min(1.0f, (params.m_fVelocityChange - transmission->Gears[currentGear].fShiftDownVelocity) / transmission->fMaxVelocity * 2.5f); + if (traction == 0.0f && automobile->GetStatus() != STATUS_SIMPLE && + params.m_fVelocityChange < transmission->Gears[1].fShiftUpVelocity) { + traction = 0.7f; + } + modificator = traction * automobile->m_fGasPedalAudio * 0.95f + (1.0f - traction) * relativeGearChange; + } else + modificator = + Min(1.0f, 1.0f - Abs((params.m_fVelocityChange - transmission->Gears[0].fShiftDownVelocity) / transmission->fMaxReverseVelocity)); + } + else + modificator = 0.0f; + } else { + if (automobile->m_nDriveWheelsOnGround > 0) + automobile->m_fGasPedalAudio *= 0.4f; + modificator = automobile->m_fGasPedalAudio; + } + if (currentGear == 0 && automobile->m_nWheelsOnGround > 0) + freq = 13000 * modificator + 14000; + else + freq = 1200 * currentGear + 18000 * modificator + 14000; + if (modificator < 0.75f) + Vol = modificator / 0.75f * (VEHICLE_ENGINE_FULL_VOLUME-VEHICLE_ENGINE_BASE_VOLUME) + VEHICLE_ENGINE_BASE_VOLUME; + else + Vol = VEHICLE_ENGINE_FULL_VOLUME; + } else { + modificator = 0.0f; + Vol = VEHICLE_ENGINE_BASE_VOLUME; + } + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (automobile->GetStatus() == STATUS_SIMPLE) { + if (modificator < 0.02f) { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nBank - CAR_SFX_BANKS_OFFSET + SFX_CAR_IDLE_1; + freq = modificator * 10000 + 22050; + m_sQueueSample.m_nCounter = 52; + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nAccelerationSampleIndex; + m_sQueueSample.m_nCounter = 2; + } + } else { + if (automobile->m_fGasPedal < 0.05f) { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nBank - CAR_SFX_BANKS_OFFSET + SFX_CAR_IDLE_1; + freq = modificator * 10000 + 22050; + m_sQueueSample.m_nCounter = 52; + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nAccelerationSampleIndex; + m_sQueueSample.m_nCounter = 2; + } + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFrequency = freq + 100 * m_sQueueSample.m_nEntityIndex % 1000; + if (m_sQueueSample.m_nSampleIndex == SFX_CAR_IDLE_6 || m_sQueueSample.m_nSampleIndex == SFX_CAR_REV_6) + m_sQueueSample.m_nFrequency >>= 1; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 8; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::UpdateGasPedalAudio(CAutomobile *automobile) +{ + float gasPedal = Abs(automobile->m_fGasPedal); + float gasPedalAudio = automobile->m_fGasPedalAudio; + + if (gasPedalAudio < gasPedal) + automobile->m_fGasPedalAudio = Min(gasPedalAudio + 0.09f, gasPedal); + else + automobile->m_fGasPedalAudio = Max(gasPedalAudio - 0.07f, gasPedal); +} + +void +cAudioManager::PlayerJustGotInCar() +{ + if (m_bIsInitialised) + bPlayerJustEnteredCar = TRUE; +} + +void +cAudioManager::PlayerJustLeftCar(void) +{ + // UNUSED: This is a perfectly empty function. +} + +void +cAudioManager::AddPlayerCarSample(uint8 Vol, uint32 freq, uint32 sample, uint8 bank, uint8 counter, bool8 bLooping) +{ + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = counter; + m_sQueueSample.m_nSampleIndex = sample; +#ifdef GTA_PS2 + m_sQueueSample.m_nBankIndex = bank; +#else + m_sQueueSample.m_nBankIndex = SFX_BANK_0; +#endif // GTA_PS2 + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = freq; + if (bLooping) { + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 8; + } else + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } +} + +void +cAudioManager::ProcessCesna(cVehicleParams& params) +{ + static uint8 nAccel = 0; + +#ifdef THIS_IS_STUPID + ((CAutomobile *)params.m_pVehicle)->Damage.GetEngineStatus(); +#endif + + if (FindPlayerVehicle() == params.m_pVehicle) { + if (params.m_nIndex == DODO) { + if (Pads[0].GetAccelerate() > 0) { + if (nAccel < 60) + nAccel++; + } else + if (nAccel > 0) + nAccel--; + AddPlayerCarSample(PLAYER_VEHICLE_ENGINE_VOLUME * (60 - nAccel) / 60 + 20, 8500 * nAccel / 60 + 17000, SFX_CESNA_IDLE, SFX_BANK_0, 52, TRUE); + AddPlayerCarSample(PLAYER_VEHICLE_ENGINE_VOLUME * nAccel / 60 + 20, 8500 * nAccel / 60 + 17000, SFX_CESNA_REV, SFX_BANK_0, 2, TRUE); + } + } else if (params.m_nIndex == DODO) { + AddPlayerCarSample(PLAYER_VEHICLE_ENGINE_VOLUME + 20, 17000, SFX_CESNA_IDLE, SFX_BANK_0, 52, TRUE); + } else if (params.m_fDistance < SQR(CESNA_IDLE_MAX_DIST)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(CESNA_VOLUME, CESNA_IDLE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 52; + m_sQueueSample.m_nSampleIndex = SFX_CESNA_IDLE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = 12500; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 8; + SET_EMITTING_VOLUME(CESNA_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 8.0f; + m_sQueueSample.m_MaxDistance = CESNA_IDLE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + if (params.m_fDistance < SQR(CESNA_REV_MAX_DIST)) { + m_sQueueSample.m_nVolume = ComputeVolume(CESNA_VOLUME, CESNA_REV_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nSampleIndex = SFX_CESNA_REV; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = 25000; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 4; + SET_EMITTING_VOLUME(CESNA_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 8.0f; + m_sQueueSample.m_MaxDistance = CESNA_REV_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +void +cAudioManager::ProcessPlayersVehicleEngine(cVehicleParams& params, CAutomobile *automobile) +{ + static int32 GearFreqAdj[] = {6000, 6000, 3400, 1200, 0, -1000}; + + cTransmission *transmission; + float relativeVelocityChange; + float accelerationMultipler; + uint8 wheelInUseCounter; + float time; + int baseFreq; + uint8 vol; + int32 freq; + + int freqModifier; + uint32 soundOffset; + uint8 engineSoundType; + int16 accelerateState; + bool8 channelUsed; + uint8 currentGear; + float gasPedalAudio; + CVector pos; + bool8 slowingDown; + + static int16 LastAccel = 0; + static int16 LastBrake = 0; + static uint8 CurrentPretendGear = 1; + static bool8 bLostTractionLastFrame = FALSE; + static bool8 bHandbrakeOnLastFrame = FALSE; + static uint32 nCruising = 0; + static bool8 bAccelSampleStopped = TRUE; + + bool8 lostTraction = FALSE; + bool8 processedAccelSampleStopped = FALSE; + if (bPlayerJustEnteredCar) { + bAccelSampleStopped = TRUE; + bPlayerJustEnteredCar = FALSE; + nCruising = 0; + LastAccel = 0; + bLostTractionLastFrame = FALSE; + LastBrake = 0; + bHandbrakeOnLastFrame = FALSE; + CurrentPretendGear = 1; + } +#ifdef FIX_BUGS // fix acceleration sound on exiting the vehicle + if (CReplay::IsPlayingBack() || FindPlayerPed()->GetPedState() == PED_EXIT_CAR) +#else + if (CReplay::IsPlayingBack()) +#endif + accelerateState = 255 * Clamp(automobile->m_fGasPedal, 0.0f, 1.0f); + else + accelerateState = Pads[0].GetAccelerate(); + + slowingDown = params.m_fVelocityChange < -0.001f; + channelUsed = SampleManager.GetChannelUsedFlag(CHANNEL_PLAYER_VEHICLE_ENGINE); + transmission = params.m_pTransmission; + relativeVelocityChange = 2.0f * params.m_fVelocityChange / transmission->fMaxVelocity; + + accelerationMultipler = Clamp(relativeVelocityChange, 0.0f, 1.0f); + gasPedalAudio = accelerationMultipler; + currentGear = params.m_pVehicle->m_nCurrentGear; + + switch (transmission->nDriveType) + { + case '4': + wheelInUseCounter = 0; + for (uint8 i = 0; i < ARRAY_SIZE(automobile->m_aWheelState); i++) { + if (automobile->m_aWheelState[i] != WHEEL_STATE_NORMAL) + wheelInUseCounter++; + } + if (wheelInUseCounter > 2) + lostTraction = TRUE; + break; + case 'F': + if ((automobile->m_aWheelState[CARWHEEL_FRONT_LEFT] != WHEEL_STATE_NORMAL || automobile->m_aWheelState[CARWHEEL_FRONT_RIGHT] != WHEEL_STATE_NORMAL) && + (automobile->m_aWheelState[CARWHEEL_REAR_LEFT] != WHEEL_STATE_NORMAL || automobile->m_aWheelState[CARWHEEL_REAR_RIGHT] != WHEEL_STATE_NORMAL)) + lostTraction = TRUE; + break; + case 'R': + if ((automobile->m_aWheelState[CARWHEEL_REAR_LEFT] != WHEEL_STATE_NORMAL) || (automobile->m_aWheelState[CARWHEEL_REAR_RIGHT] != WHEEL_STATE_NORMAL)) + lostTraction = TRUE; + break; + default: + break; + } + + if (params.m_fVelocityChange != 0.0f) { + time = params.m_pVehicle->m_vecMoveSpeed.z / params.m_fVelocityChange; + if (time > 0.0f) + freqModifier = -(Min(0.2f, time) * 3000 * 5); + else + freqModifier = -(Max(-0.2f, time) * 3000 * 5); + if (slowingDown) + freqModifier = -freqModifier; + } else + freqModifier = 0; + + engineSoundType = aVehicleSettings[params.m_nIndex].m_nBank; + soundOffset = 3 * (engineSoundType - CAR_SFX_BANKS_OFFSET); + if (accelerateState > 0) { + if (nCruising > 0) { +PlayCruising: + bAccelSampleStopped = TRUE; + if (accelerateState < 150 || automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction || + currentGear < params.m_pTransmission->nNumberOfGears - 1) { + nCruising = 0; + } else { + if (accelerateState < 220 || params.m_fVelocityChange + 0.001f < automobile->m_fVelocityChangeForAudio) { + if (nCruising > 3) + nCruising--; + } else + if (nCruising < 800) + nCruising++; + freq = 27 * nCruising + freqModifier + 22050; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + AddPlayerCarSample(PLAYER_VEHICLE_ENGINE_VOLUME, freq, (soundOffset + SFX_CAR_AFTER_ACCEL_1), engineSoundType, 64, TRUE); + } + } else { + if (accelerateState < 150 || automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction || + currentGear < 2 && params.m_fVelocityChange - automobile->m_fVelocityChangeForAudio < 0.01f) { // here could be used abs + if (automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction) { + if (automobile->m_nWheelsOnGround == 0 && automobile->m_nDriveWheelsOnGround > 0 || + (automobile->bIsHandbrakeOn && !bHandbrakeOnLastFrame || lostTraction && !bLostTractionLastFrame) && automobile->m_nWheelsOnGround > 0) + automobile->m_fGasPedalAudio *= 0.6f; + freqModifier = 0; + baseFreq = (15000 * automobile->m_fGasPedalAudio) + 14000; + vol = (25 * automobile->m_fGasPedalAudio) + 60; + } else { + baseFreq = (8000 * accelerationMultipler) + 16000; + vol = (25 * accelerationMultipler) + 60; + automobile->m_fGasPedalAudio = accelerationMultipler; + } + freq = freqModifier + baseFreq; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + if (channelUsed) { + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + bAccelSampleStopped = TRUE; + } + AddPlayerCarSample(vol, freq, (engineSoundType - CAR_SFX_BANKS_OFFSET + SFX_CAR_REV_1), SFX_BANK_0, 2, TRUE); + } else { + TranslateEntity(&m_sQueueSample.m_vecPos, &pos); +#ifndef EXTERNAL_3D_SOUND + m_sQueueSample.m_nPan = ComputePan(m_sQueueSample.m_fDistance, &pos); +#endif + if (bAccelSampleStopped) { + if (CurrentPretendGear != 1 || currentGear != 2) + CurrentPretendGear = Max(1, currentGear - 1); + processedAccelSampleStopped = TRUE; + bAccelSampleStopped = FALSE; + } + + if (!channelUsed) { + if (!processedAccelSampleStopped) { + if (CurrentPretendGear < params.m_pTransmission->nNumberOfGears - 1) + CurrentPretendGear++; + else { + nCruising = 1; + goto PlayCruising; + } + } + +#ifdef GTA_PS2 + SampleManager.InitialiseChannel(CHANNEL_PLAYER_VEHICLE_ENGINE, soundOffset + SFX_CAR_ACCEL_1, SFX_BANK_0); +#else + if (!SampleManager.InitialiseChannel(CHANNEL_PLAYER_VEHICLE_ENGINE, soundOffset + SFX_CAR_ACCEL_1, SFX_BANK_0)) + return; +#endif + SampleManager.SetChannelLoopCount(CHANNEL_PLAYER_VEHICLE_ENGINE, 1); +#ifndef GTA_PS2 + SampleManager.SetChannelLoopPoints(CHANNEL_PLAYER_VEHICLE_ENGINE, 0, -1); +#endif + } + +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannelEmittingVolume(CHANNEL_PLAYER_VEHICLE_ENGINE, PLAYER_VEHICLE_ENGINE_VOLUME); + SampleManager.SetChannel3DPosition(CHANNEL_PLAYER_VEHICLE_ENGINE, pos.x, pos.y, pos.z); + SampleManager.SetChannel3DDistances(CHANNEL_PLAYER_VEHICLE_ENGINE, VEHICLE_ENGINE_MAX_DIST, VEHICLE_ENGINE_MAX_DIST / 4.0f); +#else + + SampleManager.SetChannelVolume(CHANNEL_PLAYER_VEHICLE_ENGINE, ComputeVolume(PLAYER_VEHICLE_ENGINE_VOLUME, VEHICLE_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance)); + SampleManager.SetChannelPan(CHANNEL_PLAYER_VEHICLE_ENGINE, m_sQueueSample.m_nPan); +#endif + freq = GearFreqAdj[CurrentPretendGear] + freqModifier + 22050; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(CHANNEL_PLAYER_VEHICLE_ENGINE, freq * CTimer::GetTimeScale()); +#else + SampleManager.SetChannelFrequency(CHANNEL_PLAYER_VEHICLE_ENGINE, freq); +#endif + if (!channelUsed) { +#if GTA_VERSION >= GTA3_PC_10 + SampleManager.SetChannelReverbFlag(CHANNEL_PLAYER_VEHICLE_ENGINE, m_bDynamicAcousticModelingStatus != FALSE); +#else + SampleManager.SetChannelReverbFlag(CHANNEL_PLAYER_VEHICLE_ENGINE, TRUE); +#endif + SampleManager.StartChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + } + } + } + } else { + if (slowingDown) { + if (channelUsed) { + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + bAccelSampleStopped = TRUE; + } + if (automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction) + gasPedalAudio = automobile->m_fGasPedalAudio; + else + gasPedalAudio = Min(1.0f, params.m_fVelocityChange / params.m_pTransmission->fMaxReverseVelocity); + + gasPedalAudio = Max(0.0f, gasPedalAudio); + automobile->m_fGasPedalAudio = gasPedalAudio; + } else if (LastAccel > 0) { + if (channelUsed) { + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + bAccelSampleStopped = TRUE; + } + nCruising = 0; + if (automobile->m_nWheelsOnGround == 0 || automobile->bIsHandbrakeOn || lostTraction || + params.m_fVelocityChange < 0.01f && automobile->m_fGasPedalAudio > 0.2f) { + automobile->m_fGasPedalAudio *= 0.6f; + gasPedalAudio = automobile->m_fGasPedalAudio; + } + if (gasPedalAudio > 0.05f) { + freq = (5000 * (gasPedalAudio - 0.05f) / 0.95f) + 19000; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + AddPlayerCarSample((25 * (gasPedalAudio - 0.05f) / 0.95f) + 40, freq, (soundOffset + SFX_CAR_FINGER_OFF_ACCEL_1), engineSoundType, 63, + FALSE); + } + } + freq = (10000 * gasPedalAudio) + 22050; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + AddPlayerCarSample(110 - (40 * gasPedalAudio), freq, (engineSoundType - CAR_SFX_BANKS_OFFSET + SFX_CAR_IDLE_1), SFX_BANK_0, 52, TRUE); + + CurrentPretendGear = Max(1, currentGear); + } + LastAccel = accelerateState; + + bHandbrakeOnLastFrame = !!automobile->bIsHandbrakeOn; + bLostTractionLastFrame = lostTraction; +} + +bool8 +cAudioManager::ProcessVehicleSkidding(cVehicleParams& params) +{ + CAutomobile *automobile; + cTransmission *transmission; + uint8 Vol; + float newSkidVal = 0.0f; + float skidVal = 0.0f; + + if (params.m_fDistance < SQR(VEHICLE_SKIDDING_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->m_nWheelsOnGround > 0) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + for (uint8 i = 0; i < ARRAY_SIZE(automobile->m_aWheelState); i++) { + if (automobile->m_aWheelState[i] == WHEEL_STATE_NORMAL || automobile->Damage.GetWheelStatus(i) == WHEEL_STATUS_MISSING) + continue; + transmission = params.m_pTransmission; + switch (transmission->nDriveType) { + case '4': + newSkidVal = GetVehicleDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + break; + case 'F': + if (i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT) + newSkidVal = GetVehicleDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + else + newSkidVal = GetVehicleNonDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + break; + case 'R': + if (i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT) + newSkidVal = GetVehicleDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + else + newSkidVal = GetVehicleNonDriveWheelSkidValue(i, automobile, transmission, params.m_fVelocityChange); + break; + default: + break; + } + skidVal = Max(skidVal, newSkidVal); + } + + if (skidVal > 0.0f) { + Vol = VEHICLE_SKIDDING_VOLUME * skidVal; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_SKIDDING_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 3; + switch (params.m_pVehicle->m_nSurfaceTouched) { + case SURFACE_GRASS: + case SURFACE_HEDGE: + m_sQueueSample.m_nSampleIndex = SFX_RAIN; + Vol >>= 2; + m_sQueueSample.m_nFrequency = 13000 * skidVal + 35000; + m_sQueueSample.m_nVolume >>= 2; + if (m_sQueueSample.m_nVolume == 0) + return TRUE; + break; + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: + case SURFACE_SAND: + case SURFACE_WATER: + m_sQueueSample.m_nSampleIndex = SFX_GRAVEL_SKID; + m_sQueueSample.m_nFrequency = 6000 * skidVal + 10000; + break; + + default: + m_sQueueSample.m_nSampleIndex = SFX_SKID; + m_sQueueSample.m_nFrequency = 5000 * skidVal + 11000; + break; + } + + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 8; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_SKIDDING_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +float +cAudioManager::GetVehicleDriveWheelSkidValue(uint8 wheel, CAutomobile *automobile, cTransmission *transmission, float velocityChange) +{ + float relativeVelChange = 0.0f; + float gasPedalAudio = automobile->m_fGasPedalAudio; + float velChange; + float relativeVel; + + switch (automobile->m_aWheelState[wheel]) + { + case WHEEL_STATE_SPINNING: + if (gasPedalAudio > 0.4f) + relativeVelChange = (gasPedalAudio - 0.4f) * (5.0f / 3.0f) * 0.75f; + break; + case WHEEL_STATE_SKIDDING: + relativeVelChange = Min(1.0f, Abs(velocityChange) / transmission->fMaxVelocity); + break; + case WHEEL_STATE_FIXED: + relativeVel = gasPedalAudio; + if (relativeVel > 0.4f) + relativeVel = (gasPedalAudio - 0.4f) * (5.0f / 3.0f); + + velChange = Abs(velocityChange); + if (velChange > 0.04f) + relativeVelChange = Min(1.0f, velChange / transmission->fMaxVelocity); + if (relativeVel > relativeVelChange) + relativeVelChange = relativeVel; + + break; + default: + break; + } + + return Max(relativeVelChange, Min(1.0f, Abs(automobile->m_vecTurnSpeed.z) * 20.0f)); +} + +float +cAudioManager::GetVehicleNonDriveWheelSkidValue(uint8 wheel, CAutomobile *automobile, cTransmission *transmission, float velocityChange) +{ + float relativeVelChange = 0.0f; + + if (automobile->m_aWheelState[wheel] == WHEEL_STATE_SKIDDING) + relativeVelChange = Min(1.0f, Abs(velocityChange) / transmission->fMaxVelocity); + + return Max(relativeVelChange, Min(1.0f, Abs(automobile->m_vecTurnSpeed.z) * 20.0f)); +} + +bool8 +cAudioManager::ProcessVehicleHorn(cVehicleParams& params) +{ + if (params.m_fDistance < SQR(VEHICLE_HORN_MAX_DIST)) { + if (params.m_pVehicle->m_bSirenOrAlarm && UsesSirenSwitching(params.m_nIndex)) + return TRUE; + + if (params.m_pVehicle->GetModelIndex() == MI_MRWHOOP) + return TRUE; + + if (params.m_pVehicle->m_nCarHornTimer > 0) { + if (params.m_pVehicle->GetStatus() != STATUS_PLAYER) { + params.m_pVehicle->m_nCarHornTimer = Min(44, params.m_pVehicle->m_nCarHornTimer); + if (params.m_pVehicle->m_nCarHornTimer == 44) + params.m_pVehicle->m_nCarHornPattern = (m_FrameCounter + m_sQueueSample.m_nEntityIndex) & 7; + if (!HornPattern[params.m_pVehicle->m_nCarHornPattern][44 - params.m_pVehicle->m_nCarHornTimer]) + return TRUE; + } + + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(VEHICLE_HORN_VOLUME, VEHICLE_HORN_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 4; + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nHornSample; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFrequency = aVehicleSettings[params.m_nIndex].m_nHornFrequency; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(VEHICLE_HORN_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 5.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_HORN_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::UsesSiren(uint32 model) +{ + switch (model) { + case FIRETRUK: + case AMBULAN: + case FBICAR: + case POLICE: + case ENFORCER: + case PREDATOR: + return TRUE; + default: + return FALSE; + } +} + +bool8 +cAudioManager::UsesSirenSwitching(uint32 model) +{ + switch (model) { + case AMBULAN: + case POLICE: + case ENFORCER: + case PREDATOR: + return TRUE; + default: + return FALSE; + } +} + +bool8 +cAudioManager::ProcessVehicleSirenOrAlarm(cVehicleParams& params) +{ + if (params.m_fDistance < SQR(VEHICLE_SIREN_MAX_DIST)) { + CVehicle *veh = params.m_pVehicle; + if (veh->m_bSirenOrAlarm || veh->IsAlarmOn()) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(VEHICLE_SIREN_VOLUME, VEHICLE_SIREN_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 5; + if (UsesSiren(params.m_nIndex)) { + if (params.m_pVehicle->GetStatus() == STATUS_ABANDONED) + return TRUE; + if (veh->m_nCarHornTimer > 0 && params.m_nIndex != FIRETRUK) { + m_sQueueSample.m_nSampleIndex = SFX_SIREN_FAST; + if (params.m_nIndex == FBICAR) + m_sQueueSample.m_nFrequency = 16113; + else + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SIREN_FAST); + m_sQueueSample.m_nCounter = 60; + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nSirenOrAlarmSample; + m_sQueueSample.m_nFrequency = aVehicleSettings[params.m_nIndex].m_nSirenOrAlarmFrequency; + } + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nSirenOrAlarmSample; + m_sQueueSample.m_nFrequency = aVehicleSettings[params.m_nIndex].m_nSirenOrAlarmFrequency; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(VEHICLE_SIREN_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 7.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_SIREN_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::UsesReverseWarning(uint32 model) +{ + return model == LINERUN || model == FIRETRUK || model == TRASH || model == BUS || model == COACH; +} + +bool8 +cAudioManager::ProcessVehicleReverseWarning(cVehicleParams& params) +{ + CVehicle *veh = params.m_pVehicle; + + if (params.m_fDistance < SQR(VEHICLE_REVERSE_WARNING_MAX_DIST)) { + if (veh->bEngineOn && veh->m_fGasPedal < 0.0f) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(VEHICLE_REVERSE_WARNING_VOLUME, VEHICLE_REVERSE_WARNING_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 12; + m_sQueueSample.m_nSampleIndex = SFX_REVERSE_WARNING; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFrequency = (100 * m_sQueueSample.m_nEntityIndex % 1024) + SampleManager.GetSampleBaseFrequency(SFX_REVERSE_WARNING); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(VEHICLE_REVERSE_WARNING_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_REVERSE_WARNING_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessVehicleDoors(cVehicleParams& params) +{ + CAutomobile *automobile; + int8 doorState; + uint8 Vol; + float velocity; + + if (params.m_fDistance < SQR(VEHICLE_DOORS_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + for (uint8 i = 0; i < ARRAY_SIZE(automobile->Doors); i++) { + if (automobile->Damage.GetDoorStatus(i) == DOOR_STATUS_SWINGING) { + doorState = automobile->Doors[i].m_nDoorState; + if (doorState == DOORST_OPEN || doorState == DOORST_CLOSED) { + velocity = Min(0.3f, Abs(automobile->Doors[i].m_fAngVel)); + if (velocity > 0.0035f) { + Vol = (VEHICLE_DOORS_VOLUME * velocity / 0.3f); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_DOORS_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i + 6; + m_sQueueSample.m_nSampleIndex = m_anRandomTable[1] % 6 + SFX_COL_CAR_PANEL_1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex) + RandomDisplacement(1000); + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 10; + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 1.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_DOORS_MAX_DIST; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + } + } + } + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessAirBrakes(cVehicleParams& params) +{ + CAutomobile *automobile; + uint8 Vol; + + if (params.m_fDistance < SQR(AIR_BRAKES_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->bEngineOn && (automobile->m_fVelocityChangeForAudio >= 0.025f && params.m_fVelocityChange < 0.025f || + automobile->m_fVelocityChangeForAudio <= -0.025f && params.m_fVelocityChange > 0.025f)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + Vol = m_anRandomTable[0] % 10 + AIR_BRAKES_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, AIR_BRAKES_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 13; + m_sQueueSample.m_nSampleIndex = SFX_AIR_BRAKES; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_AIR_BRAKES); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 10; + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = AIR_BRAKES_MAX_DIST; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::HasAirBrakes(uint32 model) +{ + return model == LINERUN || model == FIRETRUK || model == TRASH || model == BUS || model == COACH; +} + +bool8 +cAudioManager::ProcessEngineDamage(cVehicleParams& params) +{ + CAutomobile *veh; + uint8 engineStatus; + uint8 Vol; + + if (params.m_fDistance < SQR(ENGINE_DAMAGE_MAX_DIST)) { + veh = (CAutomobile *)params.m_pVehicle; + if (veh->bEngineOn) { + engineStatus = veh->Damage.GetEngineStatus(); + if (engineStatus > 250 || engineStatus < 100) + return TRUE; + if (engineStatus >= 225) { + Vol = ENGINE_DAMAGE_ON_FIRE_VOLUME; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE); + } else { + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_TAXI; + Vol = ENGINE_DAMAGE_VOLUME; + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_nFrequency = 40000; + } + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, ENGINE_DAMAGE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 28; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = ENGINE_DAMAGE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessCarBombTick(cVehicleParams& params) +{ + CAutomobile *automobile; + + if (params.m_fDistance < SQR(CAR_BOMB_TICK_MAX_DIST)) { + automobile = (CAutomobile *)params.m_pVehicle; + if (automobile->bEngineOn && automobile->m_bombType == CARBOMB_TIMEDACTIVE) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(CAR_BOMB_TICK_VOLUME, CAR_BOMB_TICK_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 35; + m_sQueueSample.m_nSampleIndex = SFX_COUNTDOWN; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_COUNTDOWN); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(CAR_BOMB_TICK_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = CAR_BOMB_TICK_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::ProcessVehicleOneShots(cVehicleParams& params) +{ + int16 event; + uint8 Vol; + float eventRelVol; + float eventVol; + bool8 bLoop; + float maxDist; + + static uint8 WaveIndex = 41; + static uint8 GunIndex = 53; + + for (uint16 i = 0; i < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; i++) { + bLoop = FALSE; + SET_SOUND_REFLECTION(FALSE); + event = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]; + switch (event) { + case SOUND_CAR_WINDSHIELD_CRACK: + maxDist = SQR(VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST); + m_sQueueSample.m_nSampleIndex = SFX_GLASS_CRACK; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 68; + Vol = m_anRandomTable[1] % 30 + VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_GLASS_CRACK); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST; + break; + case SOUND_CAR_DOOR_OPEN_BONNET: + case SOUND_CAR_DOOR_OPEN_BUMPER: + case SOUND_CAR_DOOR_OPEN_FRONT_LEFT: + case SOUND_CAR_DOOR_OPEN_FRONT_RIGHT: + case SOUND_CAR_DOOR_OPEN_BACK_LEFT: + case SOUND_CAR_DOOR_OPEN_BACK_RIGHT: + maxDist = SQR(VEHICLE_ONE_SHOT_DOOR_MAX_DIST); + Vol = m_anRandomTable[1] % (MAX_VOLUME - VEHICLE_ONE_SHOT_DOOR_CLOSE_VOLUME) + VEHICLE_ONE_SHOT_DOOR_CLOSE_VOLUME; + switch (aVehicleSettings[params.m_nIndex].m_bDoorType) { + case OLD_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_OLD_CAR_DOOR_OPEN; + break; + case NEW_DOOR: + default: + m_sQueueSample.m_nSampleIndex = SFX_NEW_CAR_DOOR_OPEN; + break; + case TRUCK_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_TRUCK_DOOR_OPEN; + break; + case BUS_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_AIR_BRAKES; + break; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; +#ifdef THIS_IS_STUPID + m_sQueueSample.m_nCounter = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i] + 10; +#else + m_sQueueSample.m_nCounter = event + 10; +#endif + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_DOOR_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_CAR_DOOR_CLOSE_BONNET: + case SOUND_CAR_DOOR_CLOSE_BUMPER: + case SOUND_CAR_DOOR_CLOSE_FRONT_LEFT: + case SOUND_CAR_DOOR_CLOSE_FRONT_RIGHT: + case SOUND_CAR_DOOR_CLOSE_BACK_LEFT: + case SOUND_CAR_DOOR_CLOSE_BACK_RIGHT: + maxDist = SQR(VEHICLE_ONE_SHOT_DOOR_MAX_DIST); + Vol = m_anRandomTable[2] % (MAX_VOLUME - VEHICLE_ONE_SHOT_DOOR_OPEN_VOLUME) + VEHICLE_ONE_SHOT_DOOR_OPEN_VOLUME; + switch (aVehicleSettings[params.m_nIndex].m_bDoorType) { + case OLD_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_OLD_CAR_DOOR_CLOSE; + break; + case NEW_DOOR: + default: + m_sQueueSample.m_nSampleIndex = SFX_NEW_CAR_DOOR_CLOSE; + break; + case TRUCK_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_TRUCK_DOOR_CLOSE; + break; + case BUS_DOOR: + m_sQueueSample.m_nSampleIndex = SFX_AIR_BRAKES; + break; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; +#ifdef THIS_IS_STUPID + m_sQueueSample.m_nCounter = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i] + 22; +#else + m_sQueueSample.m_nCounter = event + 22; +#endif + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_DOOR_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_CAR_ENGINE_START: + Vol = VEHICLE_ONE_SHOT_CAR_ENGINE_START_VOLUME; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_ENGINE_START_MAX_DIST); + m_sQueueSample.m_nSampleIndex = SFX_CAR_STARTER; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 33; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_STARTER); + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_ENGINE_START_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_WEAPON_HIT_VEHICLE: + m_sQueueSample.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % ARRAY_SIZE(m_anRandomTable)] % 6 + SFX_BULLET_CAR_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 34; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_MAX_DIST); + Vol = m_anRandomTable[3] % 20 + VEHICLE_ONE_SHOT_WEAPON_HIT_VEHICLE_VOLUME; + break; + case SOUND_BOMB_TIMED_ACTIVATED: + case SOUND_55: + case SOUND_BOMB_ONIGNITION_ACTIVATED: + case SOUND_BOMB_TICK: + m_sQueueSample.m_nSampleIndex = SFX_ARM_BOMB; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 36; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ARM_BOMB); + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_BOMB_ARMED_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + Vol = VEHICLE_ONE_SHOT_BOMB_ARMED_VOLUME; + maxDist = SQR(VEHICLE_ONE_SHOT_BOMB_ARMED_MAX_DIST); + break; + case SOUND_CAR_LIGHT_BREAK: + m_sQueueSample.m_nSampleIndex = SFX_GLASS_SHARD_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 37; + m_sQueueSample.m_nFrequency = 9 * SampleManager.GetSampleBaseFrequency(SFX_GLASS_SHARD_1) / 10; + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_MAX_DIST); + Vol = m_anRandomTable[4] % 10 + VEHICLE_ONE_SHOT_CAR_LIGHT_BREAK_VOLUME; + break; + case SOUND_PLANE_ON_GROUND: + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_LAND_WHEELS; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 81; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_JUMBO_LAND_WHEELS); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_PLANE_ON_GROUND_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_PLANE_ON_GROUND_MAX_DIST); + Vol = m_anRandomTable[4] % 25 + VEHICLE_ONE_SHOT_PLANE_ON_GROUND_VOLUME; + break; + case SOUND_CAR_JERK: + m_sQueueSample.m_nSampleIndex = SFX_SHAG_SUSPENSION; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 87; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SHAG_SUSPENSION); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST); + Vol = m_anRandomTable[1] % 15 + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_VOLUME; + break; + case SOUND_CAR_HYDRAULIC_1: + case SOUND_CAR_HYDRAULIC_2: + if (event == SOUND_CAR_HYDRAULIC_1) + m_sQueueSample.m_nFrequency = 15600; + else + m_sQueueSample.m_nFrequency = 13118; + m_sQueueSample.m_nSampleIndex = SFX_SUSPENSION_FAST_MOVE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 51; + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST); + Vol = m_anRandomTable[0] % 15 + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_VOLUME; + break; + case SOUND_CAR_HYDRAULIC_3: + m_sQueueSample.m_nSampleIndex = SFX_SUSPENSION_SLOW_MOVE_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 86; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SUSPENSION_SLOW_MOVE_LOOP); + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST; + m_sQueueSample.m_nFramesToPlay = 7; + bLoop = TRUE; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_HYDRAULIC_MAX_DIST); + Vol = m_anRandomTable[0] % 15 + VEHICLE_ONE_SHOT_CAR_HYDRAULIC_VOLUME; + break; + case SOUND_WATER_FALL: + m_sQueueSample.m_nSampleIndex = SFX_SPLASH_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 15; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 16000; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WATER_FALL_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_WATER_FALL_MAX_DIST); + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[4] % 20 + VEHICLE_ONE_SHOT_WATER_FALL_VOLUME; + break; + case SOUND_CAR_BOMB_TICK: + m_sQueueSample.m_nSampleIndex = SFX_BOMB_BEEP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 80; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BOMB_BEEP); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_BOMB_TICK_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_BOMB_TICK_MAX_DIST); + SET_SOUND_REFLECTION(TRUE); + Vol = VEHICLE_ONE_SHOT_CAR_BOMB_TICK_VOLUME; + break; + case SOUND_CAR_SPLASH: + eventVol = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + if (eventVol <= 300) + continue; + if (eventVol > 1200) + m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] = 1200; + eventRelVol = (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] - 300) / (1200 - 300); + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[0] % 2) + SFX_BOAT_SPLASH_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = WaveIndex++; + if (WaveIndex > 46) + WaveIndex = 41; + m_sQueueSample.m_nFrequency = (7000 * eventRelVol) + 6000; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_SPLASH_MAX_DIST; + Vol = (VEHICLE_ONE_SHOT_CAR_SPLASH_VOLUME * eventRelVol); + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_SPLASH_MAX_DIST); + break; + case SOUND_BOAT_SLOWDOWN: + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_THUMB_OFF; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 47; + m_sQueueSample.m_nFrequency = RandomDisplacement(600) + SampleManager.GetSampleBaseFrequency(SFX_POLICE_BOAT_THUMB_OFF); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_BOAT_SLOWDOWN_MAX_DIST; + Vol = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + maxDist = SQR(VEHICLE_ONE_SHOT_BOAT_SLOWDOWN_MAX_DIST); + break; + case SOUND_CAR_JUMP: + { + static uint8 iWheelIndex = 82; + Vol = Max(VEHICLE_ONE_SHOT_CAR_JUMP_VOLUME, 2 * (100 * m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i])); + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_JUMP_MAX_DIST); + m_sQueueSample.m_nSampleIndex = SFX_TYRE_BUMP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iWheelIndex++; + if (iWheelIndex > 85) + iWheelIndex = 82; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TYRE_BUMP); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + if (params.m_nIndex == RCBANDIT) { + m_sQueueSample.m_nFrequency <<= 1; + Vol >>= 1; + } + m_sQueueSample.m_nPriority = 6; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_JUMP_MAX_DIST; + break; + } + case SOUND_WEAPON_SHOT_FIRED: + Vol = m_anRandomTable[2]; + maxDist = SQR(VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST); + m_sQueueSample.m_nSampleIndex = SFX_UZI_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = GunIndex++; + Vol = Vol % 15 + VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_VOLUME; + if (GunIndex > 58) + GunIndex = 53; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_UZI_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST; + break; + case SOUND_TRAIN_DOOR_CLOSE: + case SOUND_TRAIN_DOOR_OPEN: + m_sQueueSample.m_nSampleIndex = SFX_AIR_BRAKES; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 59; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 11025; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 5.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_TRAIN_DOOR_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_TRAIN_DOOR_MAX_DIST); + Vol = m_anRandomTable[1] % 20 + VEHICLE_ONE_SHOT_TRAIN_DOOR_VOLUME; + break; + case SOUND_SPLATTER: + { + static uint8 CrunchOffset = 0; + m_sQueueSample.m_nSampleIndex = CrunchOffset + SFX_PED_CRUNCH_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 48; + m_sQueueSample.m_nFrequency = RandomDisplacement(600) + SampleManager.GetSampleBaseFrequency(SFX_PED_CRUNCH_1); + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_SPLATTER_MAX_DIST; + CrunchOffset++; + maxDist = SQR(VEHICLE_ONE_SHOT_SPLATTER_MAX_DIST); + Vol = m_anRandomTable[4] % 20 + VEHICLE_ONE_SHOT_SPLATTER_VOLUME; + CrunchOffset %= 2; + SET_SOUND_REFLECTION(TRUE); + break; + } + case SOUND_CAR_PED_COLLISION: + eventVol = Min(20.0f, m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]); + Vol = (eventVol / 20 * MAX_VOLUME); + if (Vol == 0) + continue; + + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[2] % 4) + SFX_FIGHT_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 50; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex) >> 1; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_PED_COLLISION_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_PED_COLLISION_MAX_DIST); + break; + case SOUND_PED_HELI_PLAYER_FOUND: + { + cPedParams pedParams; + pedParams.m_bDistanceCalculated = params.m_bDistanceCalculated; + pedParams.m_fDistance = params.m_fDistance; + SetupPedComments(pedParams, SOUND_PED_HELI_PLAYER_FOUND); + continue; + } + case SOUND_PED_BODYCAST_HIT: + { + cPedParams pedParams; + pedParams.m_bDistanceCalculated = params.m_bDistanceCalculated; + pedParams.m_fDistance = params.m_fDistance; + SetupPedComments(pedParams, SOUND_PED_BODYCAST_HIT); + continue; + } + case SOUND_CAR_TANK_TURRET_ROTATE: + eventVol = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + if (eventVol > 96.0f / 2500.0f) + eventVol = 96.0f / 2500.0f; + m_sQueueSample.m_nSampleIndex = SFX_TANK_TURRET; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 79; + m_sQueueSample.m_nFrequency = (3000 * eventVol / (96.0f / 2500.0f)) + 9000; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_TANK_TURRET_MAX_DIST; + Vol = (37 * eventVol / (96.0f / 2500.0f)) + VEHICLE_ONE_SHOT_CAR_TANK_TURRET_VOLUME; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_TANK_TURRET_MAX_DIST); + bLoop = TRUE; + break; + default: + continue; + } + if (params.m_fDistance < maxDist) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (bLoop) { + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + } else { + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + } + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bReverb = TRUE; + m_sQueueSample.m_bIs2D = FALSE; + AddSampleToRequestedQueue(); + } + } + } +} + +bool8 +cAudioManager::ProcessTrainNoise(cVehicleParams& params) +{ + CTrain *train; + uint8 Vol; + float speedMultipler; + + if (params.m_fDistance < SQR(TRAIN_NOISE_FAR_MAX_DIST)){ + if (params.m_fVelocityChange > 0.0f) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + train = (CTrain *)params.m_pVehicle; + speedMultipler = Min(1.0f, train->m_fSpeed * 250.0f / 51.0f); + Vol = (TRAIN_NOISE_VOLUME * speedMultipler); + if (train->m_fWagonPosition == 0.0f) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, TRAIN_NOISE_FAR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 32; + m_sQueueSample.m_nSampleIndex = SFX_TRAIN_FAR; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TRAIN_FAR); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = TRAIN_NOISE_FAR_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + if (params.m_fDistance < SQR(TRAIN_NOISE_NEAR_MAX_DIST)) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, TRAIN_NOISE_NEAR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 33; + m_sQueueSample.m_nSampleIndex = SFX_TRAIN_NEAR; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TRAIN_NEAR) + 100 * m_sQueueSample.m_nEntityIndex % 987; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = TRAIN_NOISE_NEAR_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessBoatEngine(cVehicleParams& params) +{ + CBoat *boat; + float padRelativeAccerate; + float gasPedal; + float padAccelerate; + uint8 Vol; + float oneShotVol; + + static uint16 LastAccel = 0; + static uint8 LastVol = 0; + + if (params.m_fDistance < SQR(BOAT_ENGINE_MAX_DIST)) { + boat = (CBoat *)params.m_pVehicle; + if (params.m_nIndex == REEFER) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(BOAT_ENGINE_REEFER_IDLE_VOLUME, BOAT_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 39; + m_sQueueSample.m_nSampleIndex = SFX_FISHING_BOAT_IDLE; + m_sQueueSample.m_nFrequency = 10386; + m_sQueueSample.m_nFrequency += (m_sQueueSample.m_nEntityIndex << 16) % 1000; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(BOAT_ENGINE_REEFER_IDLE_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BOAT_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 7; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + if (FindPlayerVehicle() == params.m_pVehicle) { + padAccelerate = Max(Pads[0].GetAccelerate(), Pads[0].GetBrake()); + padRelativeAccerate = padAccelerate / 255.0f; + Vol = (BOAT_ENGINE_REEFER_ACCEL_VOLUME_MULT * padRelativeAccerate) + BOAT_ENGINE_REEFER_ACCEL_MIN_VOLUME; + m_sQueueSample.m_nFrequency = (3000 * padRelativeAccerate) + 6000; + if (!boat->bPropellerInWater) + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + } else { + gasPedal = Abs(boat->m_fGasPedal); + if (gasPedal > 0.0f) { + Vol = (BOAT_ENGINE_REEFER_ACCEL_VOLUME_MULT * gasPedal) + BOAT_ENGINE_REEFER_ACCEL_MIN_VOLUME; + m_sQueueSample.m_nFrequency = (3000 * gasPedal) + 6000; + if (!boat->bPropellerInWater) + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + } else { + m_sQueueSample.m_nFrequency = 6000; + Vol = BOAT_ENGINE_REEFER_ACCEL_MIN_VOLUME; + } + } + m_sQueueSample.m_nVolume = ComputeVolume(Vol, BOAT_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 40; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_ACCEL; + m_sQueueSample.m_nFrequency += (m_sQueueSample.m_nEntityIndex << 16) % 1000; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BOAT_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 7; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } else { + if (FindPlayerVehicle() == params.m_pVehicle) { + padAccelerate = Max(Pads[0].GetAccelerate(), Pads[0].GetBrake()); + if (padAccelerate <= 20) { + Vol = BOAT_ENGINE_LOW_ACCEL_VOLUME - BOAT_ENGINE_LOW_ACCEL_VOLUME * padAccelerate / 40; + m_sQueueSample.m_nFrequency = 100 * padAccelerate + 11025; + m_sQueueSample.m_nCounter = 39; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_IDLE; + if (LastAccel > 20) { + oneShotVol = LastVol; + PlayOneShot(m_sQueueSample.m_nEntityIndex, SOUND_BOAT_SLOWDOWN, oneShotVol); + } + } else { + Vol = BOAT_ENGINE_HIGH_ACCEL_VOLUME_MULT * padAccelerate / 255 + BOAT_ENGINE_HIGH_ACCEL_MIN_VOLUME; + m_sQueueSample.m_nFrequency = 4000 * padAccelerate / 255 + 8000; + if (!boat->m_bIsAnchored) + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + m_sQueueSample.m_nCounter = 40; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_ACCEL; + } + LastVol = Vol; + LastAccel = padAccelerate; + } else { + gasPedal = Abs(boat->m_fGasPedal); + if (gasPedal > 0.0f) { + Vol = (BOAT_ENGINE_HIGH_ACCEL_VOLUME_MULT * gasPedal) + BOAT_ENGINE_HIGH_ACCEL_MIN_VOLUME; + m_sQueueSample.m_nFrequency = (4000 * gasPedal) + 8000; + if (!boat->m_bIsAnchored) + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + m_sQueueSample.m_nCounter = 40; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_ACCEL; + } else { + m_sQueueSample.m_nFrequency = 11025; + Vol = BOAT_ENGINE_LOW_ACCEL_VOLUME; + m_sQueueSample.m_nCounter = 39; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BOAT_IDLE; + } + } + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, BOAT_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nFrequency += (m_sQueueSample.m_nEntityIndex << 16) % 1000; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BOAT_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 7; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessBoatMovingOverWater(cVehicleParams& params) +{ + float velocityChange; + uint8 Vol; + float multiplier; + + if (params.m_fDistance < SQR(BOAT_MOVING_OVER_WATER_MAX_DIST)) { + velocityChange = Abs(params.m_fVelocityChange); + if (velocityChange > 0.0005f && ((CBoat*)params.m_pVehicle)->bBoatInWater) { + velocityChange = Min(0.75f, velocityChange); + multiplier = (velocityChange - 0.0005f) / (1499.0f / 2000.0f); + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + Vol = (BOAT_MOVING_OVER_WATER_VOLUME * multiplier); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, BOAT_MOVING_OVER_WATER_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 38; + m_sQueueSample.m_nSampleIndex = SFX_BOAT_WATER_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFrequency = (6050 * multiplier) + 16000; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BOAT_MOVING_OVER_WATER_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +struct tHelicopterSampleData { + float m_fMaxDistance; + float m_fBaseDistance; + uint8 m_bBaseVolume; +}; +static Const tHelicopterSampleData gHeliSfxRanges[3] = {{400, 380, 100}, {100, 70, MAX_VOLUME}, {60, 30, MAX_VOLUME}}; + +bool8 +cAudioManager::ProcessHelicopter(cVehicleParams& params) +{ + CHeli *heli; + float MaxDist; + float dist; + float baseDist; + uint8 Vol; + + if (params.m_fDistance < SQR(gHeliSfxRanges[0].m_fMaxDistance)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + heli = (CHeli *)params.m_pVehicle; + for (uint32 i = 0; i < ARRAY_SIZE(gHeliSfxRanges); i++) { + MaxDist = gHeliSfxRanges[i].m_fMaxDistance; + dist = m_sQueueSample.m_fDistance; + if (dist < MaxDist) { + baseDist = gHeliSfxRanges[i].m_fBaseDistance; + if (dist < baseDist) + Vol = gHeliSfxRanges[i].m_bBaseVolume; + else + Vol = (gHeliSfxRanges[i].m_bBaseVolume * ((MaxDist - dist) / (MaxDist - baseDist))); + } else + return TRUE; + + m_sQueueSample.m_nVolume = ComputeVolume(Vol, gHeliSfxRanges[i].m_fMaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i + 65; + m_sQueueSample.m_nSampleIndex = i + SFX_HELI_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nFrequency = 1200 * heli->m_nHeliId + SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = gHeliSfxRanges[i].m_fMaxDistance; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::ProcessPlane(cVehicleParams& params) +{ + switch (params.m_nIndex) { + case AIRTRAIN: + ProcessJumbo(params); + break; + case DEADDODO: + ProcessCesna(params); + break; + default: + debug("Plane Model Id is %d\n, ", params.m_pVehicle->GetModelIndex()); + break; + } +} + +#pragma region JUMBO +uint8 gJumboVolOffsetPercentage; + +void +DoJumboVolOffset() +{ + if (!(AudioManager.m_FrameCounter % (AudioManager.m_anRandomTable[0] % 6 + 3))) + gJumboVolOffsetPercentage = AudioManager.m_anRandomTable[1] % 60; +} + +void +cAudioManager::ProcessJumbo(cVehicleParams& params) +{ + CPlane *plane; + float position; + + if (params.m_fDistance < SQR(JUMBO_MAX_DIST)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + plane = (CPlane *)params.m_pVehicle; + DoJumboVolOffset(); + position = PlanePathPosition[plane->m_nPlaneId]; + if (position > TakeOffPoint) { + if (300.0f + TakeOffPoint < position) { + if (LandingPoint - 350.0f < position) { + if (position > LandingPoint) { + if (plane->m_fSpeed > 0.103344f) + ProcessJumboDecel(plane); + else + ProcessJumboTaxi(); + } + else + ProcessJumboLanding(plane); + } + else + ProcessJumboFlying(); + } else + ProcessJumboTakeOff(plane); + } else { + if (plane->m_fSpeed > 0.103344f) + ProcessJumboAccel(plane); + else + ProcessJumboTaxi(); + } + } +} + +void +cAudioManager::ProcessJumboTaxi() +{ + if (SetupJumboFlySound(20)) { + if (SetupJumboTaxiSound(75)) + SetupJumboWhineSound(18, 29500); + } +} + +void +cAudioManager::ProcessJumboAccel(CPlane *plane) +{ + uint32 engineFreq; + uint8 vol; + float modificator; + float freqMult; + + if (SetupJumboFlySound(20)) { + modificator = Min(1.0f, (plane->m_fSpeed - 0.103344f) * 1.6760077f); + if (SetupJumboRumbleSound(MAX_VOLUME * modificator) && SetupJumboTaxiSound((1.0f - modificator) * 75)) { + if (modificator >= 0.2f) { + freqMult = 1; + engineFreq = 22050; + vol = MAX_VOLUME; + } else { + freqMult = modificator * 5; + vol = freqMult * MAX_VOLUME; + engineFreq = freqMult * 6050 + 16000; + } + SetupJumboEngineSound(vol, engineFreq); + SetupJumboWhineSound(18, 14600 * freqMult + 29500); + } + } +} + +void +cAudioManager::ProcessJumboTakeOff(CPlane *plane) +{ + float modificator = (PlanePathPosition[plane->m_nPlaneId] - TakeOffPoint) / 300; + if (SetupJumboFlySound((107 * modificator) + 20) && SetupJumboRumbleSound(MAX_VOLUME * (1.0f - modificator))) { + if (SetupJumboEngineSound(MAX_VOLUME, 22050)) + SetupJumboWhineSound(18 * (1.0f - modificator), 44100); + } +} + +void +cAudioManager::ProcessJumboFlying() +{ + if (SetupJumboFlySound(MAX_VOLUME)) + SetupJumboEngineSound(63, 22050); +} + +void +cAudioManager::ProcessJumboLanding(CPlane *plane) +{ + float modificator = (LandingPoint - PlanePathPosition[plane->m_nPlaneId]) / 350; + if (SetupJumboFlySound(107 * modificator + 20)) { + if (SetupJumboTaxiSound(75 * (1.0f - modificator))) { + SetupJumboEngineSound(MAX_VOLUME, 22050); + SetupJumboWhineSound(18 * (1.0f - modificator), 14600 * modificator + 29500); + } + } +} + +void +cAudioManager::ProcessJumboDecel(CPlane *plane) +{ + if (SetupJumboFlySound(20) && SetupJumboTaxiSound(75)) { + float modificator = Min(1.0f, (plane->m_fSpeed - 0.103344f) * 1.6760077f); + SetupJumboEngineSound(MAX_VOLUME * modificator, 6050 * modificator + 16000); + SetupJumboWhineSound(18, 29500); + } +} + +bool8 +cAudioManager::SetupJumboTaxiSound(uint8 vol) +{ + if (m_sQueueSample.m_fDistance < JUMBO_ENGINE_SOUND_MAX_DIST) { + uint8 Vol = (vol >> 1) + ((vol >> 1) * m_sQueueSample.m_fDistance / JUMBO_ENGINE_SOUND_MAX_DIST); + + if (m_sQueueSample.m_fDistance / JUMBO_ENGINE_SOUND_MAX_DIST < 0.7f) + Vol -= Vol * gJumboVolOffsetPercentage / 100; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, JUMBO_ENGINE_SOUND_MAX_DIST, m_sQueueSample.m_fDistance); + + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_TAXI; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = GetJumboTaxiFreq(); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_ENGINE_SOUND_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::SetupJumboWhineSound(uint8 Vol, uint32 freq) +{ + if (m_sQueueSample.m_fDistance < JUMBO_WHINE_SOUND_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, JUMBO_WHINE_SOUND_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_WHINE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = freq; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_WHINE_SOUND_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::SetupJumboEngineSound(uint8 Vol, uint32 freq) +{ + if (m_sQueueSample.m_fDistance < JUMBO_ENGINE_SOUND_MAX_DIST) { + uint8 FinalVol = Vol - gJumboVolOffsetPercentage / 100; + m_sQueueSample.m_nVolume = ComputeVolume(FinalVol, JUMBO_ENGINE_SOUND_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 3; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_ENGINE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = freq; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(FinalVol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_ENGINE_SOUND_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::SetupJumboFlySound(uint8 Vol) +{ + if (m_sQueueSample.m_fDistance < JUMBO_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, JUMBO_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_DIST_FLY; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_JUMBO_DIST_FLY); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::SetupJumboRumbleSound(uint8 Vol) +{ + if (m_sQueueSample.m_fDistance < JUMBO_RUMBLE_SOUND_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, JUMBO_RUMBLE_SOUND_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 5; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_RUMBLE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_JUMBO_RUMBLE); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = JUMBO_RUMBLE_SOUND_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 12; + m_sQueueSample.m_nPan = 0; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + m_sQueueSample.m_nCounter = 6; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_RUMBLE; + m_sQueueSample.m_nFrequency += 200; + m_sQueueSample.m_nPan = 127; + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +int32 +cAudioManager::GetJumboTaxiFreq() +{ + return (1.0f / 180 * 10950 * m_sQueueSample.m_fDistance) + 22050; +} + +#pragma endregion Some jumbo crap + +#pragma endregion All the vehicle audio code + +#pragma region PED AUDIO +void +cAudioManager::ProcessPed(CPhysical *ped) +{ + cPedParams params; + + m_sQueueSample.m_vecPos = ped->GetPosition(); + + params.m_bDistanceCalculated = FALSE; + params.m_pPed = (CPed *)ped; + params.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (ped->GetModelIndex() == MI_FATMALE02) + ProcessPedHeadphones(params); + ProcessPedOneShots(params); +} + +void +cAudioManager::ProcessPedHeadphones(cPedParams ¶ms) +{ + CPed *ped; + CAutomobile *veh; + uint8 Vol; + + if (params.m_fDistance < SQR(PED_HEADPHONES_MAX_DIST)) { + ped = params.m_pPed; + if (ped->bBodyPartJustCameOff && ped->m_bodyPartBleeding == PED_HEAD) + return; + + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + if (ped->bInVehicle && ped->m_nPedState == PED_DRIVING) { + Vol = PED_HEADPHONES_IN_CAR_VOLUME; + veh = (CAutomobile *)ped->m_pMyVehicle; + if (veh && veh->IsCar()) { + for (int32 i = DOOR_FRONT_LEFT; i < ARRAY_SIZE(veh->Doors); i++) { + if (!veh->IsDoorClosed((eDoors)i) || veh->IsDoorMissing((eDoors)i)) { + Vol = PED_HEADPHONES_VOLUME; + break; + } + } + } + } else + Vol = PED_HEADPHONES_VOLUME; + + m_sQueueSample.m_nVolume = ComputeVolume(Vol, PED_HEADPHONES_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 64; + m_sQueueSample.m_nSampleIndex = SFX_HEADPHONES; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_HEADPHONES); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = PED_HEADPHONES_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessPedOneShots(cPedParams ¶ms) +{ + uint8 Vol; + uint32 sampleIndex; + + CPed *ped = params.m_pPed; + + bool8 narrowSoundRange; + int16 sound; + bool8 stereo; + CWeapon *weapon; +#ifdef FIX_BUGS + float maxDist = 0.0f; // uninitialized variable +#else + float maxDist; +#endif + + static uint8 iSound = 21; + + weapon = params.m_pPed->GetWeapon(); + for (uint32 i = 0; i < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; i++) { + stereo = FALSE; + narrowSoundRange = FALSE; + SET_SOUND_REFLECTION(FALSE); + sound = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]; + switch (sound) { + case SOUND_FALL_LAND: + case SOUND_FALL_COLLAPSE: + if (ped->bIsInTheAir) + continue; + maxDist = SQR(PED_ONE_SHOT_FALL_MAX_DIST); + Vol = m_anRandomTable[3] % 20 + PED_ONE_SHOT_FALL_VOLUME; + if (ped->m_nSurfaceTouched == SURFACE_WATER) + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[3] % 4) + SFX_FOOTSTEP_WATER_1; + else if (sound == SOUND_FALL_LAND) + m_sQueueSample.m_nSampleIndex = SFX_BODY_LAND; + else + m_sQueueSample.m_nSampleIndex = SFX_BODY_LAND_AND_FALL; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency / 17); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_FALL_MAX_DIST; + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_STEP_START: + case SOUND_STEP_END: + if (params.m_pPed->bIsInTheAir) + continue; + Vol = m_anRandomTable[3] % 15 + PED_ONE_SHOT_STEP_VOLUME; + if (FindPlayerPed() != m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_pEntity) + Vol >>= 1; + maxDist = SQR(PED_ONE_SHOT_STEP_MAX_DIST); + switch (params.m_pPed->m_nSurfaceTouched) { + case SURFACE_GRASS: + sampleIndex = m_anRandomTable[1] % 5 + SFX_FOOTSTEP_GRASS_1; + break; + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: + sampleIndex = m_anRandomTable[4] % 5 + SFX_FOOTSTEP_GRAVEL_1; + break; + case SURFACE_CAR: + case SURFACE_GARAGE_DOOR: + case SURFACE_CAR_PANEL: + case SURFACE_THICK_METAL_PLATE: + case SURFACE_SCAFFOLD_POLE: + case SURFACE_LAMP_POST: + case SURFACE_FIRE_HYDRANT: + case SURFACE_GIRDER: + case SURFACE_METAL_CHAIN_FENCE: + case SURFACE_CONTAINER: + case SURFACE_NEWS_VENDOR: + sampleIndex = m_anRandomTable[0] % 5 + SFX_FOOTSTEP_METAL_1; + break; + case SURFACE_SAND: + sampleIndex = (m_anRandomTable[4] & 3) + SFX_FOOTSTEP_SAND_1; + break; + case SURFACE_WATER: + sampleIndex = (m_anRandomTable[3] & 3) + SFX_FOOTSTEP_WATER_1; + break; + case SURFACE_WOOD_CRATES: + case SURFACE_WOOD_BENCH: + case SURFACE_WOOD_SOLID: + sampleIndex = m_anRandomTable[2] % 5 + SFX_FOOTSTEP_WOOD_1; + break; + case SURFACE_HEDGE: + sampleIndex = m_anRandomTable[2] % 5 + SFX_COL_VEG_1; + break; + default: + sampleIndex = m_anRandomTable[2] % 5 + SFX_FOOTSTEP_CONCRETE_1; + break; + } + m_sQueueSample.m_nSampleIndex = sampleIndex; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i] - SOUND_STEP_START + 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency / 17); + switch (params.m_pPed->m_nMoveState) { + case PEDMOVE_WALK: + Vol >>= 2; + m_sQueueSample.m_nFrequency = 9 * m_sQueueSample.m_nFrequency / 10; + break; + case PEDMOVE_RUN: + Vol >>= 1; + m_sQueueSample.m_nFrequency = 11 * m_sQueueSample.m_nFrequency / 10; + break; + case PEDMOVE_SPRINT: + m_sQueueSample.m_nFrequency = 12 * m_sQueueSample.m_nFrequency / 10; + break; + default: + break; + } + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_STEP_MAX_DIST; + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_WEAPON_AK47_BULLET_ECHO: + case SOUND_WEAPON_UZI_BULLET_ECHO: + case SOUND_WEAPON_M16_BULLET_ECHO: + m_sQueueSample.m_nSampleIndex = SFX_UZI_END_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_UZI_END_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_BULLET_ECHO_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_BULLET_ECHO_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[4] % 10 + PED_ONE_SHOT_WEAPON_BULLET_ECHO_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case SOUND_WEAPON_FLAMETHROWER_FIRE: + m_sQueueSample.m_nSampleIndex = SFX_FLAMETHROWER_START_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_FLAMETHROWER_START_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_VOLUME; + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + case SOUND_WEAPON_SHOT_FIRED: + weapon = ped->GetWeapon(); + switch (weapon->m_eWeaponType) { + case WEAPONTYPE_COLT45: + m_sQueueSample.m_nSampleIndex = SFX_COLT45_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_COLT45_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_COLT45_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_COLT45_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[1] % 10 + PED_ONE_SHOT_WEAPON_COLT45_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_ROCKETLAUNCHER: + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ROCKET_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[0] % 20 + PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_FLAMETHROWER: + m_sQueueSample.m_nSampleIndex = SFX_FLAMETHROWER_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 9; + Vol = PED_ONE_SHOT_WEAPON_FLAMETHROWER_VOLUME; + m_sQueueSample.m_nFrequency = (10 * m_sQueueSample.m_nEntityIndex % 2048) + SampleManager.GetSampleBaseFrequency(SFX_FLAMETHROWER_LEFT); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_FLAMETHROWER_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_FLAMETHROWER_MAX_DIST); + m_sQueueSample.m_nLoopCount = 0; + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_FLAMETHROWER_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 6; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_AK47: + m_sQueueSample.m_nSampleIndex = SFX_AK47_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_AK47_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_AK47_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_AK47_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[1] % 15 + PED_ONE_SHOT_WEAPON_AK47_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + case WEAPONTYPE_UZI: + m_sQueueSample.m_nSampleIndex = SFX_UZI_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_UZI_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_UZI_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_UZI_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[3] % 15 + PED_ONE_SHOT_WEAPON_UZI_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + case WEAPONTYPE_SNIPERRIFLE: + m_sQueueSample.m_nSampleIndex = SFX_SNIPER_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SNIPER_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_SNIPERRIFLE_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_SNIPERRIFLE_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[4] % 10 + PED_ONE_SHOT_WEAPON_SNIPERRIFLE_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_SHOTGUN: + m_sQueueSample.m_nSampleIndex = SFX_SHOTGUN_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SHOTGUN_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_SHOTGUN_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_SHOTGUN_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[2] % 10 + PED_ONE_SHOT_WEAPON_SHOTGUN_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case WEAPONTYPE_M16: + m_sQueueSample.m_nSampleIndex = SFX_M16_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_M16_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_M16_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_M16_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[4] % 15 + PED_ONE_SHOT_WEAPON_M16_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + default: + continue; + } + + break; + case SOUND_WEAPON_RELOAD: + weapon = &ped->m_weapons[ped->m_currentWeapon]; + switch (weapon->m_eWeaponType) { + case WEAPONTYPE_COLT45: + m_sQueueSample.m_nSampleIndex = SFX_PISTOL_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PISTOL_RELOAD) + RandomDisplacement(300); + break; + case WEAPONTYPE_UZI: + m_sQueueSample.m_nSampleIndex = SFX_M16_RELOAD; + m_sQueueSample.m_nFrequency = 39243; + break; + case WEAPONTYPE_AK47: + m_sQueueSample.m_nSampleIndex = SFX_AK47_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_AK47_RELOAD); + break; + case WEAPONTYPE_M16: + m_sQueueSample.m_nSampleIndex = SFX_M16_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_M16_RELOAD); + break; + case WEAPONTYPE_SHOTGUN: + m_sQueueSample.m_nSampleIndex = SFX_AK47_RELOAD; + m_sQueueSample.m_nFrequency = 30290; + break; + case WEAPONTYPE_ROCKETLAUNCHER: + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ROCKET_RELOAD); + break; + case WEAPONTYPE_SNIPERRIFLE: + m_sQueueSample.m_nSampleIndex = SFX_RIFLE_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RIFLE_RELOAD); + break; + default: + continue; + } + Vol = PED_ONE_SHOT_WEAPON_RELOAD_VOLUME; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency += RandomDisplacement(300); + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_RELOAD_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_RELOAD_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_RELOAD_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_WEAPON_HIT_PED: + m_sQueueSample.m_nSampleIndex = SFX_BULLET_PED; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BULLET_PED); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_HIT_PED_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_HIT_PED_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[0] % 20 + PED_ONE_SHOT_WEAPON_HIT_PED_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + break; + case SOUND_WEAPON_BAT_ATTACK: + m_sQueueSample.m_nSampleIndex = SFX_BAT_HIT_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = RandomDisplacement(2000) + 22000; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_PUNCH_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_PUNCH_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[2] % 20 + PED_ONE_SHOT_PUNCH_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; +#ifdef AUDIO_REFLECTIONS + if (m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReflections = TRUE; + else +#endif + stereo = TRUE; + break; + case SOUND_FIGHT_PUNCH_33: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_1; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_KICK_34: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_1; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_HEADBUTT_35: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_1; + m_sQueueSample.m_nFrequency = 20000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_36: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_2; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_37: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_2; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_CLOSE_PUNCH_38: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_2; + m_sQueueSample.m_nFrequency = 20000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_39: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_4; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_OR_KICK_BELOW_40: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_4; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_41: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_4; + m_sQueueSample.m_nFrequency = 20000; + goto AddFightSound; + case SOUND_FIGHT_PUNCH_FROM_BEHIND_42: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_5; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_KNEE_OR_KICK_43: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_5; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_KICK_44: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_5; + m_sQueueSample.m_nFrequency = 20000; + AddFightSound: + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound; + narrowSoundRange = TRUE; + iSound++; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_PUNCH_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_PUNCH_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[3] % 26 + PED_ONE_SHOT_PUNCH_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_SPLASH: + m_sQueueSample.m_nSampleIndex = SFX_SPLASH_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = RandomDisplacement(1400) + 20000; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_SPLASH_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_SPLASH_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = m_anRandomTable[2] % 30 + PED_ONE_SHOT_SPLASH_PED_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + default: + SetupPedComments(params, sound); + continue; + } + + if (narrowSoundRange && iSound > 60) + iSound = 21; + if (params.m_fDistance < maxDist) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (stereo) { + if (m_sQueueSample.m_fDistance < 0.2f * m_sQueueSample.m_MaxDistance) { + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nPan = 0; + } else + stereo = FALSE; + } + m_sQueueSample.m_bReverb = TRUE; + AddSampleToRequestedQueue(); + if (stereo) { + m_sQueueSample.m_nPan = 127; + m_sQueueSample.m_nSampleIndex++; + if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i] == SOUND_WEAPON_SHOT_FIRED && + weapon->m_eWeaponType == WEAPONTYPE_FLAMETHROWER) + m_sQueueSample.m_nCounter++; + else { + m_sQueueSample.m_nCounter = iSound++; + if (iSound > 60) + iSound = 21; + } + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::SetupPedComments(cPedParams ¶ms, uint16 sound) +{ + CPed *ped = params.m_pPed; + uint8 Vol; + float maxDist; + tPedComment pedComment; + + if (ped != nil) { + switch (sound) { + case SOUND_AMMUNATION_WELCOME_1: + pedComment.m_nSampleIndex = SFX_AMMU_D; + break; + case SOUND_AMMUNATION_WELCOME_2: + pedComment.m_nSampleIndex = SFX_AMMU_E; + break; + case SOUND_AMMUNATION_WELCOME_3: + pedComment.m_nSampleIndex = SFX_AMMU_F; + break; + default: + pedComment.m_nSampleIndex = GetPedCommentSfx(ped, sound); + if (pedComment.m_nSampleIndex == NO_SAMPLE) + return; + break; + } + + maxDist = PED_COMMENT_MAX_DIST; + } else { + switch (sound) { +#ifdef GTA_PS2 + case SOUND_PAGER: + maxDist = PED_COMMENT_MAX_DIST; + pedComment.m_nSampleIndex = SFX_PAGER; + break; +#endif + case SOUND_PED_HELI_PLAYER_FOUND: + maxDist = PED_COMMENT_POLICE_HELI_MAX_DIST; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 29 + SFX_POLICE_HELI_1; + break; + case SOUND_PED_BODYCAST_HIT: + if (CTimer::GetTimeInMilliseconds() <= gNextCryTime) + return; + maxDist = PED_COMMENT_MAX_DIST; + gNextCryTime = CTimer::GetTimeInMilliseconds() + 500; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 4 + SFX_PLASTER_BLOKE_1; + break; + case SOUND_INJURED_PED_MALE_OUCH: + case SOUND_INJURED_PED_MALE_PRISON: + maxDist = PED_COMMENT_MAX_DIST; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 15 + SFX_GENERIC_MALE_GRUNT_1; + break; + case SOUND_INJURED_PED_FEMALE: + maxDist = PED_COMMENT_MAX_DIST; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 11 + SFX_GENERIC_FEMALE_GRUNT_1; + break; + default: + return; + } + } + + if (params.m_fDistance < SQR(maxDist)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + if (sound != SOUND_PAGER) { + switch (sound) { + case SOUND_AMMUNATION_WELCOME_1: + case SOUND_AMMUNATION_WELCOME_2: + case SOUND_AMMUNATION_WELCOME_3: + Vol = PED_COMMENT_VOLUME; + break; + default: + if (CWorld::GetIsLineOfSightClear(TheCamera.GetPosition(), m_sQueueSample.m_vecPos, true, false, false, false, false, false)) + Vol = PED_COMMENT_VOLUME; + else + Vol = PED_COMMENT_VOLUME_BEHIND_WALL; + break; + } + m_sQueueSample.m_nVolume = ComputeVolume(Vol, maxDist, m_sQueueSample.m_fDistance); + pedComment.m_nLoadingTimeout = 10; + if (m_sQueueSample.m_nVolume > 0) { + pedComment.m_nEntityIndex = m_sQueueSample.m_nEntityIndex; + pedComment.m_vecPos = m_sQueueSample.m_vecPos; + pedComment.m_fDistance = m_sQueueSample.m_fDistance; + pedComment.m_nVolume = m_sQueueSample.m_nVolume; +#if defined(EXTERNAL_3D_SOUND) && defined(FIX_BUGS) + pedComment.m_nEmittingVolume = Vol; +#endif + m_sPedComments.Add(&pedComment); + } + } +#ifdef GTA_PS2 + else { + m_sQueueSample.m_nVolume = MAX_VOLUME; + pedComment.m_nLoadingTimeout = 40; + } +#endif + } +} + +int32 +cAudioManager::GetPedCommentSfx(CPed *ped, uint16 sound) +{ + if (ped->IsPlayer()) + return GetPlayerTalkSfx(sound); + + switch (ped->GetModelIndex()) { + case MI_COP: + return GetCopTalkSfx(sound); + case MI_SWAT: + return GetSwatTalkSfx(sound); + case MI_FBI: + return GetFBITalkSfx(sound); + case MI_ARMY: + return GetArmyTalkSfx(sound); + case MI_MEDIC: + return GetMedicTalkSfx(sound); + case MI_FIREMAN: + return GetFiremanTalkSfx(sound); + case MI_MALE01: + return GetNormalMaleTalkSfx(sound); + case MI_TAXI_D: + return GetAsianTaxiDriverTalkSfx(sound); + case MI_PIMP: + return GetPimpTalkSfx(sound); + case MI_GANG01: + case MI_GANG02: + return GetMafiaTalkSfx(sound); + case MI_GANG03: + case MI_GANG04: + return GetTriadTalkSfx(sound); + case MI_GANG05: + case MI_GANG06: + return GetDiabloTalkSfx(sound); + case MI_GANG07: + case MI_GANG08: + return GetYakuzaTalkSfx(sound); + case MI_GANG09: + case MI_GANG10: + return GetYardieTalkSfx(sound); + case MI_GANG11: + case MI_GANG12: + return GetColumbianTalkSfx(sound); + case MI_GANG13: + case MI_GANG14: + return GetHoodTalkSfx(sound); + case MI_CRIMINAL01: + return GetBlackCriminalTalkSfx(sound); + case MI_CRIMINAL02: + return GetWhiteCriminalTalkSfx(sound); + case MI_SPECIAL01: + case MI_SPECIAL02: + case MI_SPECIAL03: + case MI_SPECIAL04: + return GetSpecialCharacterTalkSfx(ped->GetModelIndex(), sound); + case MI_MALE02: + return GetCasualMaleOldTalkSfx(sound); + case MI_MALE03: + case MI_P_MAN1: + case MI_P_MAN2: + return GetBlackProjectMaleTalkSfx(sound, ped->GetModelIndex()); + case MI_FATMALE01: + return GetWhiteFatMaleTalkSfx(sound); + case MI_FATMALE02: + return GetBlackFatMaleTalkSfx(sound); + case MI_FEMALE01: + return GetBlackCasualFemaleTalkSfx(sound); + case MI_FEMALE02: + case MI_CAS_WOM: + return GetWhiteCasualFemaleTalkSfx(sound); + case MI_FEMALE03: + return GetFemaleNo3TalkSfx(sound); + case MI_FATFEMALE01: + return GetBlackFatFemaleTalkSfx(sound); + case MI_FATFEMALE02: + return GetWhiteFatFemaleTalkSfx(sound); + case MI_PROSTITUTE: + return GetBlackFemaleProstituteTalkSfx(sound); + case MI_PROSTITUTE2: + return GetWhiteFemaleProstituteTalkSfx(sound); + case MI_P_WOM1: + return GetBlackProjectFemaleOldTalkSfx(sound); + case MI_P_WOM2: + return GetBlackProjectFemaleYoungTalkSfx(sound); + case MI_CT_MAN1: + return GetChinatownMaleOldTalkSfx(sound); + case MI_CT_MAN2: + return GetChinatownMaleYoungTalkSfx(sound); + case MI_CT_WOM1: + return GetChinatownFemaleOldTalkSfx(sound); + case MI_CT_WOM2: + return GetChinatownFemaleYoungTalkSfx(sound); + case MI_LI_MAN1: + case MI_LI_MAN2: + return GetLittleItalyMaleTalkSfx(sound); + case MI_LI_WOM1: + return GetLittleItalyFemaleOldTalkSfx(sound); + case MI_LI_WOM2: + return GetLittleItalyFemaleYoungTalkSfx(sound); + case MI_DOCKER1: + return GetWhiteDockerMaleTalkSfx(sound); + case MI_DOCKER2: + return GetBlackDockerMaleTalkSfx(sound); + case MI_SCUM_MAN: + return GetScumMaleTalkSfx(sound); + case MI_SCUM_WOM: + return GetScumFemaleTalkSfx(sound); + case MI_WORKER1: + return GetWhiteWorkerMaleTalkSfx(sound); + case MI_WORKER2: + return GetBlackWorkerMaleTalkSfx(sound); + case MI_B_MAN1: + case MI_B_MAN3: + return GetBusinessMaleYoungTalkSfx(sound, ped->GetModelIndex()); + case MI_B_MAN2: + return GetBusinessMaleOldTalkSfx(sound); + case MI_B_WOM1: + case MI_B_WOM2: + return GetWhiteBusinessFemaleTalkSfx(sound, ped->GetModelIndex()); + case MI_B_WOM3: + return GetBlackBusinessFemaleTalkSfx(sound); + case MI_MOD_MAN: + return GetSupermodelMaleTalkSfx(sound); + case MI_MOD_WOM: + return GetSupermodelFemaleTalkSfx(sound); + case MI_ST_MAN: + return GetStewardMaleTalkSfx(sound); + case MI_ST_WOM: + return GetStewardFemaleTalkSfx(sound); + case MI_FAN_MAN1: + case MI_FAN_MAN2: + return GetFanMaleTalkSfx(sound, ped->GetModelIndex()); + case MI_FAN_WOM: + return GetFanFemaleTalkSfx(sound); + case MI_HOS_MAN: + return GetHospitalMaleTalkSfx(sound); + case MI_HOS_WOM: + return GetHospitalFemaleTalkSfx(sound); + case MI_CONST1: + return GetWhiteConstructionWorkerTalkSfx(sound); + case MI_CONST2: + return GetBlackConstructionWorkerTalkSfx(sound); + case MI_SHOPPER1: + case MI_SHOPPER2: + case MI_SHOPPER3: + return GetShopperFemaleTalkSfx(sound, ped->GetModelIndex()); + case MI_STUD_MAN: + return GetStudentMaleTalkSfx(sound); + case MI_STUD_WOM: + return GetStudentFemaleTalkSfx(sound); + case MI_CAS_MAN: + return GetCasualMaleYoungTalkSfx(sound); + default: + return GetGenericMaleTalkSfx(sound); + } +} + +void +cAudioManager::GetPhrase(uint32 &phrase, uint32 &prevPhrase, uint32 sample, uint32 maxOffset) +{ + phrase = sample + m_anRandomTable[m_sQueueSample.m_nEntityIndex & 3] % maxOffset; + + // check if the same sfx like last time, if yes, then try use next one, + // if exceeded range, then choose first available sample + if (phrase == prevPhrase && ++phrase >= sample + maxOffset) + phrase = sample; + prevPhrase = phrase; +} + +#pragma region PED_COMMENTS + +uint32 +cAudioManager::GetPlayerTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_DAMAGE: + GetPhrase(sfx, lastSfx, SFX_CLAUDE_HIGH_DAMAGE_GRUNT_1, 11); + break; + case SOUND_PED_HIT: + GetPhrase(sfx, lastSfx, SFX_CLAUDE_LOW_DAMAGE_GRUNT_1, 10); + break; + case SOUND_PED_LAND: + GetPhrase(sfx, lastSfx, SFX_CLAUDE_HIT_GROUND_GRUNT_1, 6); + break; + default: + sfx = NO_SAMPLE; + break; + } + return sfx; +} + +uint32 +cAudioManager::GetCopTalkSfx(uint16 sound) +{ + uint32 sfx; + PedState pedState; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ARREST_COP: + GetPhrase(sfx, lastSfx, SFX_COP_VOICE_1_ARREST_1, 6); + break; + case SOUND_PED_PURSUIT_COP: + pedState = FindPlayerPed()->m_nPedState; + if (pedState == PED_ARRESTED || pedState == PED_DEAD || pedState == PED_DIE) + return NO_SAMPLE; + GetPhrase(sfx, lastSfx, SFX_COP_VOICE_1_CHASE_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return (SFX_COP_VOICE_2_ARREST_1 - SFX_COP_VOICE_1_ARREST_1) * (m_sQueueSample.m_nEntityIndex % 5) + sfx; +} + +uint32 +cAudioManager::GetSwatTalkSfx(uint16 sound) +{ + uint32 sfx; + PedState pedState; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ARREST_SWAT: + GetPhrase(sfx, lastSfx, SFX_SWAT_VOICE_1_CHASE_1, 6); + break; + case SOUND_PED_PURSUIT_SWAT: + pedState = FindPlayerPed()->m_nPedState; + if (pedState == PED_ARRESTED || pedState == PED_DEAD || pedState == PED_DIE) + return NO_SAMPLE; + GetPhrase(sfx, lastSfx, SFX_SWAT_VOICE_1_CHASE_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return (SFX_SWAT_VOICE_2_CHASE_1 - SFX_SWAT_VOICE_1_CHASE_1) * (m_sQueueSample.m_nEntityIndex % 4) + sfx; +} + +uint32 +cAudioManager::GetFBITalkSfx(uint16 sound) +{ + uint32 sfx; + PedState pedState; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ARREST_FBI: + GetPhrase(sfx, lastSfx, SFX_FBI_VOICE_1_CHASE_1, 6); + break; + case SOUND_PED_PURSUIT_FBI: + pedState = FindPlayerPed()->m_nPedState; + if (pedState == PED_ARRESTED || pedState == PED_DEAD || pedState == PED_DIE) + return NO_SAMPLE; + GetPhrase(sfx, lastSfx, SFX_FBI_VOICE_1_CHASE_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return (SFX_FBI_VOICE_2_CHASE_1 - SFX_FBI_VOICE_1_CHASE_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetArmyTalkSfx(uint16 sound) +{ + uint32 sfx; + PedState pedState; + static uint32 lastSfx = NO_SAMPLE; + + switch(sound) { + case SOUND_PED_PURSUIT_ARMY: + pedState = FindPlayerPed()->m_nPedState; + if(pedState == PED_ARRESTED || pedState == PED_DEAD || pedState == PED_DIE) return NO_SAMPLE; + GetPhrase(sfx, lastSfx, SFX_ARMY_VOICE_1_CHASE_1, 15); + break; + default: return GetGenericMaleTalkSfx(sound); + } + + return (SFX_ARMY_VOICE_2_CHASE_1 - SFX_ARMY_VOICE_1_CHASE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetMedicTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_GUN_PANIC_1, 5); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_CARJACKED_1, 5); + break; + case SOUND_PED_HEALING: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_AT_VICTIM_1, 12); + break; + case SOUND_PED_LEAVE_VEHICLE: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_1, 9); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_MEDIC_VOICE_2_GUN_PANIC_1 - SFX_MEDIC_VOICE_1_GUN_PANIC_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetFiremanTalkSfx(uint16 sound) +{ + return GetGenericMaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetBusinessMaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBusinessMaleYoungTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + if (model == MI_B_MAN3) + sfx += (SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_1 - SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1); + return sfx; +} + +uint32 +cAudioManager::GetMafiaTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_MAFIA_MALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetTriadTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_GUN_COOL_1, 3); + break; + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_TRIAD_MALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetDiabloTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_GUN_COOL_1, 4); + break; + case SOUND_PED_HANDS_COWER: + sound = SOUND_PED_FLEE_SPRINT; + return GetGenericMaleTalkSfx(sound); + break; + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_EYING_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_DIABLO_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_DIABLO_MALE_VOICE_2_CHAT_1 - SFX_DIABLO_MALE_VOICE_1_CHAT_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetYakuzaTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_YAKUZA_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetYardieTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + sfx = SFX_YARDIE_MALE_VOICE_1_GUN_COOL_1; + break; + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + sfx = SFX_YARDIE_MALE_VOICE_1_CARJACKED_1; + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_EYING_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_YARDIE_MALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetColumbianTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_EYING_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_COLUMBIAN_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetHoodTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_GUN_COOL_1, 5); + break; + case SOUND_PED_CAR_JACKING: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_CARJACKING_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_EYING_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_HOOD_MALE_VOICE_1_CHAT_1, 6); + break; + + default: + return GetGenericMaleTalkSfx(sound); + break; + } + return (SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetBlackCriminalTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_1, 4); + break; + case SOUND_PED_CAR_JACKING: + sfx = SFX_BLACK_CRIMINAL_VOICE_1_CARJACKING_1; + break; + case SOUND_PED_MUGGING: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_MUGGING_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + break; + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteCriminalTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_GUN_COOL_1, 3); + break; + case SOUND_PED_CAR_JACKING: + sfx = SFX_WHITE_CRIMINAL_VOICE_1_CARJACKING_1; + break; + case SOUND_PED_MUGGING: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_MUGGING_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_1, 4); + break; + default: + return GetGenericMaleTalkSfx(sound); + break; + } + return sfx; +} + +uint32 +cAudioManager::GetCasualMaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_CARJACKED_1, 3); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_1, 4); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_EYING_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetCasualMaleYoungTalkSfx(uint16 sound) +{ + return GetGenericMaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetBlackCasualFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_RUN_FROM_FIGHT_1, 2); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_FEMALE_1_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteCasualFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + sfx = SFX_WHITE_CASUAL_FEMALE_VOICE_1_MUGGED_1; + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, 2); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_1, 8); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetFemaleNo3TalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_GUN_PANIC_1, 5); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_CARJACKED_1, 3); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_FEMALE_3_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteBusinessFemaleTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + + if (model == MI_B_WOM2) + sfx += (SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_1 - SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1); + return sfx; +} + +uint32 +cAudioManager::GetBlackFatFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteFatMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch(sound) { + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_CARJACKED_1, 3); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_MUGGED_1, 3); break; + case SOUND_PED_EVADE: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_DODGE_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_1, 9); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_LOST_1, 2); break; + case SOUND_PED_CHAT: GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_MALE_VOICE_1_CHAT_1, 9); break; + default: return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackFatMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_DODGE_1, 7); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_WAIT_DOUBLEBACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_LOST_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_FAT_MALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteFatFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_1, 8); + break; + case SOUND_PED_WAIT_DOUBLEBACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_LOST_1, 2); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackFemaleProstituteTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_1, 4); + break; + case SOUND_PED_ROBBED: + sfx = SFX_BLACK_PROSTITUTE_VOICE_1_MUGGED_1; + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_SOLICIT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_1, 8); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return (SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_1 - SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetWhiteFemaleProstituteTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_SOLICIT: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_1, 8); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return (SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_1 - SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetBlackProjectMaleTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_GUN_COOL_1, 3); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_CARJACKED_1, 2); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_MUGGED_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_1, 6); break; + case SOUND_PED_EVADE: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_1, 5); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_1, 7); break; + case SOUND_PED_CHAT_SEXY: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_EYING_1, 3); break; + case SOUND_PED_CHAT: GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_1, 6); break; + default: return GetGenericMaleTalkSfx(sound); + } + + if (model == MI_P_MAN2) + sfx += (SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_1); + return sfx; +} + +uint32 +cAudioManager::GetBlackProjectFemaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_1, 6); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_1, 10); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_1, 10); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackProjectFemaleYoungTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_CAR_JACKED: + sfx = SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CARJACKED_1; + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChinatownMaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChinatownMaleYoungTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChinatownFemaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + sfx = SFX_CHINATOWN_OLD_FEMALE_VOICE_1_SHOCKED_1; + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChinatownFemaleYoungTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetLittleItalyMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return (SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetLittleItalyFemaleOldTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetLittleItalyFemaleYoungTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_1, 7); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteDockerMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_FIGHT_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackDockerMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_DOCKER_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetScumMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_GUN_PANIC_1, 5); + break; + case SOUND_PED_ROBBED: + sfx = SFX_SCUM_MALE_VOICE_1_MUGGED_1; + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_FIGHT_1, 10); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_WAIT_DOUBLEBACK: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_LOST_1, 3); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_EYING_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_SCUM_MALE_VOICE_1_CHAT_1, 9); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetScumFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_DODGE_1, 8); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_SCUM_FEMALE_VOICE_1_CHAT_1, 13); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteWorkerMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_FIGHT_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_EYING_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackWorkerMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_FIGHT_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackBusinessFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_1, 5); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSupermodelMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_MODEL_MALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSupermodelFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_MUGGED_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_SHOCKED_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_MODEL_FEMALE_VOICE_1_CHAT_1, 8); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetStewardMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_DODGE_1, 3); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_STEWARD_MALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetStewardFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_STEWARD_FEMALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_STEWARD_FEMALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_STEWARD_FEMALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return (SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_1 - SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetFanMaleTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_FIGHT_1, 3); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_MALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + if (model == MI_FAN_MAN2) + sfx += (SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_1 - SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_1); + return sfx; +} + +uint32 +cAudioManager::GetFanFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_ROBBED: + sfx = SFX_FOOTBALL_FEMALE_VOICE_1_MUGGED_1; + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_FEMALE_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return (SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_1 - SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetHospitalMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHospitalFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_1, 6); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWhiteConstructionWorkerTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + sfx = SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CARJACKED_1; + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_EYING_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBlackConstructionWorkerTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_GUN_PANIC_1, 3); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_1, 5); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetShopperFemaleTalkSfx(uint16 sound, uint32 model) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_CARJACKED_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_1, 7); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_SHOCKED_1, 4); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_SHOPPER_VOICE_1_CHAT_1, 7); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + + if (model == MI_SHOPPER2) { + sfx += (SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_1 - SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_1); + } else if (model == MI_SHOPPER3) { + sfx += (SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_1 - SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_1); + } + return sfx; +} + +uint32 +cAudioManager::GetStudentMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_GUN_PANIC_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_SHOCKED_1, 3); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_STUDENT_MALE_VOICE_1_CHAT_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetStudentFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_DODGE_1, 4); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_1, 4); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_STUDENT_FEMALE_VOICE_1_CHAT_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSpecialCharacterTalkSfx(uint32 modelIndex, uint16 sound) +{ + char *modelName = CModelInfo::GetModelInfo(modelIndex)->GetModelName(); + if (!CGeneral::faststricmp(modelName, "eight") || !CGeneral::faststricmp(modelName, "eight2")) + return GetEightBallTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "frankie")) + return GetSalvatoreTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "misty")) + return GetMistyTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "ojg") || !CGeneral::faststricmp(modelName, "ojg_p")) + return GetOldJapTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "cat")) + return GetCatalinaTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "bomber")) + return GetBomberTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "s_guard")) + return GetSecurityGuardTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "chunky")) + return GetChunkyTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "asuka")) + return GetGenericFemaleTalkSfx(sound); + if (!CGeneral::faststricmp(modelName, "maria")) + return GetGenericFemaleTalkSfx(sound); + return GetGenericMaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetEightBallTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_8BALL_GUN_COOL_1, 2); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_8BALL_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_8BALL_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_8BALL_DODGE_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSalvatoreTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_SALVATORE_GUN_COOL_1, 4); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_SALVATORE_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_SALVATORE_FIGHT_1, 6); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_SALVATORE_DODGE_1, 3); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetMistyTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_MISTY_GUN_COOL_1, 5); + break; + case SOUND_PED_ROBBED: + GetPhrase(sfx, lastSfx, SFX_MISTY_MUGGED_1, 2); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_MISTY_FIGHT_1, 4); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_MISTY_DODGE_1, 5); + break; + case SOUND_PED_TAXI_CALL: + GetPhrase(sfx, lastSfx, SFX_MISTY_HERE_1, 4); + break; + default: + return GetGenericFemaleTalkSfx(sound); + break; + } + return sfx; +} + +uint32 +cAudioManager::GetOldJapTalkSfx(uint16 sound) +{ + return GetGenericMaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetCatalinaTalkSfx(uint16 sound) +{ + return GetGenericFemaleTalkSfx(sound); +} + +uint32 +cAudioManager::GetBomberTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) + { + case SOUND_PED_BOMBER: + GetPhrase(sfx, lastSfx, SFX_BOMBERMAN_1, 7); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSecurityGuardTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_SECURITY_GUARD_VOICE_1_GUN_COOL_1, 2); + break; + case SOUND_PED_HANDS_COWER: + sfx = SFX_SECURITY_GUARD_VOICE_1_GUN_PANIC_1; + break; + case SOUND_PED_CAR_JACKED: + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_1, 6); + break; + case SOUND_PED_ATTACK: + GetPhrase(sfx, lastSfx, SFX_SECURITY_GUARD_VOICE_1_FIGHT_1, 2); + break; + case SOUND_PED_FLEE_RUN: +#ifdef FIX_BUGS + sfx = SFX_SECURITY_GUARD_VOICE_1_RUN_FROM_FIGHT_1; +#else + GetPhrase(sfx, lastSfx, SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_1, 12); +#endif + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetChunkyTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) + { + case SOUND_PED_DEATH: + return SFX_CHUNKY_DEATH; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_CHUNKY_RUN_1, 5); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return sfx; +} + +uint32 +cAudioManager::GetAsianTaxiDriverTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_1, 7); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_1, 6); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + + return (SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_1 - SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_1) * (m_sQueueSample.m_nEntityIndex % 2) + sfx; +} + +uint32 +cAudioManager::GetPimpTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_UP: + GetPhrase(sfx, lastSfx, SFX_PIMP_GUN_COOL_1, 7); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_PIMP_CARJACKED_1, 4); + break; + case SOUND_PED_DEFEND: + GetPhrase(sfx, lastSfx, SFX_PIMP_FIGHT_1, 9); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_PIMP_DODGE_1, 6); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_PIMP_DRIVER_ABUSE_1, 5); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_PIMP_SHOCKED_1, 2); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_PIMP_CHAT_1, 17); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetNormalMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_HANDS_COWER: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_GUN_PANIC_1, 7); + break; + case SOUND_PED_CAR_JACKED: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_CARJACKED_1, 7); + break; + case SOUND_PED_EVADE: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_DODGE_1, 9); + break; + case SOUND_PED_FLEE_RUN: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_RUN_FROM_FIGHT_1, 5); + break; + case SOUND_PED_ANNOYED_DRIVER: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_DRIVER_ABUSE_1, 12); + break; + case SOUND_PED_CHAT_SEXY: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_EYING_1, 8); + break; + case SOUND_PED_CHAT_EVENT: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_SHOCKED_1, 10); + break; + case SOUND_PED_CHAT: + GetPhrase(sfx, lastSfx, SFX_NORMAL_MALE_CHAT_1, 25); + break; + default: + return GetGenericMaleTalkSfx(sound); + } + return sfx; +} + +uint32 +cAudioManager::GetGenericMaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_DEATH: + GetPhrase(sfx, lastSfx, SFX_GENERIC_MALE_DEATH_1, 8); + break; + case SOUND_PED_BULLET_HIT: + case SOUND_PED_DEFEND: + GetPhrase(sfx, lastSfx, SFX_GENERIC_MALE_GRUNT_1, 15); + break; + case SOUND_PED_BURNING: + GetPhrase(sfx, lastSfx, SFX_GENERIC_MALE_FIRE_1, 8); + break; + case SOUND_PED_FLEE_SPRINT: + GetPhrase(sfx, lastSfx, SFX_GENERIC_MALE_PANIC_1, 6); + break; + default: + return NO_SAMPLE; + } + return sfx; +} + +uint32 +cAudioManager::GetGenericFemaleTalkSfx(uint16 sound) +{ + uint32 sfx; + static uint32 lastSfx = NO_SAMPLE; + + switch (sound) { + case SOUND_PED_DEATH: + GetPhrase(sfx, lastSfx, SFX_GENERIC_FEMALE_DEATH_1, 10); + break; + case SOUND_PED_BULLET_HIT: + case SOUND_PED_DEFEND: + GetPhrase(sfx, lastSfx, SFX_GENERIC_FEMALE_GRUNT_1, 11); + break; + case SOUND_PED_BURNING: + GetPhrase(sfx, lastSfx, SFX_GENERIC_FEMALE_FIRE_1, 9); + break; + case SOUND_PED_FLEE_SPRINT: + GetPhrase(sfx, lastSfx, SFX_GENERIC_FEMALE_PANIC_1, 8); + break; + default: + return NO_SAMPLE; + } + return sfx; +} + +void +cPedComments::Add(tPedComment *com) +{ + uint8 index; + + // copypasted priority check from cAudioManager::AddSampleToRequestedQueue + + if (m_nPedCommentCount[m_nActiveQueue] >= NUM_PED_COMMENTS_SLOTS) { + index = m_aPedCommentOrderList[m_nActiveQueue][NUM_PED_COMMENTS_SLOTS - 1]; + if (m_aPedCommentQueue[m_nActiveQueue][index].m_nVolume > com->m_nVolume) + return; + } else + index = m_nPedCommentCount[m_nActiveQueue]++; + + m_aPedCommentQueue[m_nActiveQueue][index] = *com; + + // this bit is basically copypasted cAudioManager::AddDetailsToRequestedOrderList + uint8 i = 0; + if (index != 0) { + for (i = 0; i < index; i++) { + if (m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][i]].m_nVolume < m_aPedCommentQueue[m_nActiveQueue][index].m_nVolume) + break; + } + + if (i < index) + memmove(&m_aPedCommentOrderList[m_nActiveQueue][i + 1], &m_aPedCommentOrderList[m_nActiveQueue][i], NUM_PED_COMMENTS_SLOTS - 1 - i); + } + + m_aPedCommentOrderList[m_nActiveQueue][i] = index; +} + +void +cPedComments::Process() +{ + uint32 sampleIndex; + uint8 queue; + + if (AudioManager.m_bIsPaused) return; + + if (m_nPedCommentCount[m_nActiveQueue]) { + sampleIndex = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nSampleIndex; + switch (SampleManager.IsPedCommentLoaded(sampleIndex)) + { + case LOADING_STATUS_NOT_LOADED: + SampleManager.LoadPedComment(sampleIndex); +#ifdef GTA_PS2 // on PC ped comment is loaded at once + break; +#endif + case LOADING_STATUS_LOADED: + AudioManager.m_sQueueSample.m_nEntityIndex = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nEntityIndex; + AudioManager.m_sQueueSample.m_nCounter = 0; + AudioManager.m_sQueueSample.m_nSampleIndex = sampleIndex; + AudioManager.m_sQueueSample.m_nBankIndex = SFX_BANK_PED_COMMENTS; + AudioManager.m_sQueueSample.m_nPriority = 3; + AudioManager.m_sQueueSample.m_nVolume = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nVolume; + AudioManager.m_sQueueSample.m_fDistance = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_fDistance; + AudioManager.m_sQueueSample.m_nLoopCount = 1; +#ifndef GTA_PS2 + AudioManager.m_sQueueSample.m_nLoopStart = 0; + AudioManager.m_sQueueSample.m_nLoopEnd = -1; +#endif // !GTA_PS2 +#ifdef EXTERNAL_3D_SOUND + #ifdef FIX_BUGS + AudioManager.m_sQueueSample.m_nEmittingVolume = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nEmittingVolume; + #else + AudioManager.m_sQueueSample.m_nEmittingVolume = MAX_VOLUME; + #endif // FIX_BUGS +#endif // EXTERNAL_3D_SOUND +#ifdef ATTACH_RELEASING_SOUNDS_TO_ENTITIES + // let's disable doppler because if sounds funny as the sound moves + // originally position of ped comment doesn't change so this has no effect anyway + AudioManager.m_sQueueSample.m_fSpeedMultiplier = 0.0f; +#else + AudioManager.m_sQueueSample.m_fSpeedMultiplier = 3.0f; +#endif + switch (sampleIndex) { + case SFX_POLICE_HELI_1: + case SFX_POLICE_HELI_2: + case SFX_POLICE_HELI_3: +#ifdef FIX_BUGS + case SFX_POLICE_HELI_4: + case SFX_POLICE_HELI_5: + case SFX_POLICE_HELI_6: + case SFX_POLICE_HELI_7: + case SFX_POLICE_HELI_8: + case SFX_POLICE_HELI_9: + case SFX_POLICE_HELI_10: + case SFX_POLICE_HELI_11: + case SFX_POLICE_HELI_12: + case SFX_POLICE_HELI_13: + case SFX_POLICE_HELI_14: + case SFX_POLICE_HELI_15: + case SFX_POLICE_HELI_16: + case SFX_POLICE_HELI_17: + case SFX_POLICE_HELI_18: + case SFX_POLICE_HELI_19: + case SFX_POLICE_HELI_20: + case SFX_POLICE_HELI_21: + case SFX_POLICE_HELI_22: + case SFX_POLICE_HELI_23: + case SFX_POLICE_HELI_24: + case SFX_POLICE_HELI_25: + case SFX_POLICE_HELI_26: + case SFX_POLICE_HELI_27: + case SFX_POLICE_HELI_28: + case SFX_POLICE_HELI_29: +#endif + AudioManager.m_sQueueSample.m_MaxDistance = PED_COMMENT_POLICE_HELI_MAX_DIST; + break; + default: + AudioManager.m_sQueueSample.m_MaxDistance = PED_COMMENT_MAX_DIST; + break; + } + AudioManager.m_sQueueSample.m_bStatic = TRUE; + AudioManager.m_sQueueSample.m_vecPos = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_vecPos; + + if (sampleIndex >= SFX_AMMU_D && sampleIndex <= SFX_AMMU_F) { + AudioManager.m_sQueueSample.m_bReverb = FALSE; +#ifdef AUDIO_REFLECTIONS + AudioManager.m_sQueueSample.m_bReflections = FALSE; +#endif +#ifdef FIX_BUGS + } + else if (sampleIndex >= SFX_POLICE_HELI_1 && sampleIndex <= SFX_POLICE_HELI_29) { + AudioManager.m_sQueueSample.m_bReverb = TRUE; +#ifdef AUDIO_REFLECTIONS + AudioManager.m_sQueueSample.m_bReflections = FALSE; +#endif // AUDIO_REFLECTIONS +#endif // FIX_BUGS + } + else { + AudioManager.m_sQueueSample.m_bReverb = TRUE; +#ifdef AUDIO_REFLECTIONS + AudioManager.m_sQueueSample.m_bReflections = TRUE; +#endif + } + + AudioManager.m_sQueueSample.m_bIs2D = FALSE; + AudioManager.m_sQueueSample.m_nFrequency = + SampleManager.GetSampleBaseFrequency(AudioManager.m_sQueueSample.m_nSampleIndex) + AudioManager.RandomDisplacement(750); +#ifndef USE_TIME_SCALE_FOR_AUDIO + if (CTimer::GetIsSlowMotionActive()) + AudioManager.m_sQueueSample.m_nFrequency >>= 1; +#endif + m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nLoadingTimeout = -1; + AudioManager.AddSampleToRequestedQueue(); + break; + case LOADING_STATUS_LOADING: break; + default: break; + } + } + + // Switch queue + if (m_nActiveQueue == 0) { + queue = 0; + m_nActiveQueue = 1; + } else { + queue = 1; + m_nActiveQueue = 0; + } + for (uint8 i = 0; i < m_nPedCommentCount[queue]; i++) { + if (m_aPedCommentQueue[queue][m_aPedCommentOrderList[queue][i]].m_nLoadingTimeout > 0) { + m_aPedCommentQueue[queue][m_aPedCommentOrderList[queue][i]].m_nLoadingTimeout--; + Add(&m_aPedCommentQueue[queue][m_aPedCommentOrderList[queue][i]]); + } + } + + // clear queue + for (uint8 i = 0; i < NUM_PED_COMMENTS_SLOTS; i++) + m_aPedCommentOrderList[queue][i] = NUM_PED_COMMENTS_SLOTS; + m_nPedCommentCount[queue] = 0; +} + +#pragma endregion + +#pragma endregion All the ped audio code + +void +cAudioManager::ProcessExplosions(int32 id) +{ + uint8 type; + float distSquared; + + for (uint8 i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + if (CExplosion::GetExplosionActiveCounter(i) == 1) { + CExplosion::ResetExplosionActiveCounter(i); + type = CExplosion::GetExplosionType(i); + switch (type) { + case EXPLOSION_GRENADE: + case EXPLOSION_ROCKET: + case EXPLOSION_BARREL: + case EXPLOSION_TANK_GRENADE: + m_sQueueSample.m_MaxDistance = EXPLOSION_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_2; +#ifdef GTA_PS2 + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 19000; +#else + m_sQueueSample.m_nFrequency = RandomDisplacement(2000) + 38000; +#endif + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + break; + case EXPLOSION_MINE: + case EXPLOSION_HELI_BOMB: + m_sQueueSample.m_MaxDistance = EXPLOSION_MINE_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_LEFT; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 12347; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + break; + case EXPLOSION_MOLOTOV: + m_sQueueSample.m_MaxDistance = EXPLOSION_MOLOTOV_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_3; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 19000; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + break; + default: + m_sQueueSample.m_MaxDistance = EXPLOSION_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_1; +#ifdef GTA_PS2 + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 19000; +#else + m_sQueueSample.m_nFrequency = RandomDisplacement(2000) + 38000; +#endif + if (type == EXPLOSION_HELI) + m_sQueueSample.m_nFrequency = 8 * m_sQueueSample.m_nFrequency / 10; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_GENERIC_EXTRA; + break; + } + m_sQueueSample.m_vecPos = *CExplosion::GetExplosionPosition(i); + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(m_sQueueSample.m_MaxDistance)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(MAX_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_EMITTING_VOLUME(MAX_VOLUME); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::ProcessFires(int32 id) +{ + CEntity *entity; + uint8 Vol; + float distSquared; + + for (uint8 i = 0; i < NUM_FIRES; i++) { + if (gFireManager.m_aFires[i].m_bIsOngoing && gFireManager.m_aFires[i].m_bAudioSet) { + entity = gFireManager.m_aFires[i].m_pEntity; + if (entity) { + switch (entity->GetType()) { + case ENTITY_TYPE_BUILDING: + m_sQueueSample.m_MaxDistance = FIRE_BUILDING_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + Vol = FIRE_BUILDING_VOLUME; + m_sQueueSample.m_nFrequency = 8 * SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE) / 10; + m_sQueueSample.m_nFrequency += i * (m_sQueueSample.m_nFrequency >> 6); + m_sQueueSample.m_nPriority = 6; + break; + case ENTITY_TYPE_PED: + m_sQueueSample.m_MaxDistance = FIRE_PED_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_PED_ON_FIRE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PED_ON_FIRE); + Vol = FIRE_PED_VOLUME; + m_sQueueSample.m_nFrequency += i * (m_sQueueSample.m_nFrequency >> 6); + m_sQueueSample.m_nPriority = 10; + break; + default: + m_sQueueSample.m_MaxDistance = FIRE_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE); + m_sQueueSample.m_nFrequency += i * (m_sQueueSample.m_nFrequency >> 6); + Vol = FIRE_DEFAULT_VOLUME; + m_sQueueSample.m_nPriority = 8; + } + } else { + m_sQueueSample.m_MaxDistance = FIRE_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE); + m_sQueueSample.m_nFrequency += i * (m_sQueueSample.m_nFrequency >> 6); + Vol = FIRE_DEFAULT_VOLUME; + m_sQueueSample.m_nPriority = 8; + } + m_sQueueSample.m_vecPos = gFireManager.m_aFires[i].m_vecPos; + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(m_sQueueSample.m_MaxDistance)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_nFramesToPlay = 10; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::ProcessWaterCannon(int32 id) +{ + for (uint32 i = 0; i < NUM_WATERCANNONS; i++) { + if (CWaterCannons::aCannons[i].m_nId) { + m_sQueueSample.m_vecPos = CWaterCannons::aCannons[0].m_avecPos[CWaterCannons::aCannons[i].m_nCur]; + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(WATER_CANNON_MAX_DIST)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); +#ifdef FIX_BUGS + m_sQueueSample.m_nVolume = ComputeVolume(WATER_CANNON_VOLUME, WATER_CANNON_MAX_DIST, m_sQueueSample.m_fDistance); +#else + m_sQueueSample.m_nVolume = ComputeVolume(WATER_CANNON_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); +#endif + if (m_sQueueSample.m_nVolume > 0) { +#ifdef FIX_BUGS + m_sQueueSample.m_MaxDistance = WATER_CANNON_MAX_DIST; +#else + m_sQueueSample.m_MaxDistance = SQR(WATER_CANNON_MAX_DIST); +#endif + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_TAXI; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 15591; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_nFramesToPlay = 8; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + SET_EMITTING_VOLUME(WATER_CANNON_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +#pragma region SCRIPT_OBJECTS +void +cAudioManager::ProcessScriptObject(int32 id) +{ + cAudioScriptObject *entity = (cAudioScriptObject *)m_asAudioEntities[id].m_pEntity; + if (entity != nil) { + m_sQueueSample.m_vecPos = entity->Posn; + if (m_asAudioEntities[id].m_AudioEvents == 1) + ProcessOneShotScriptObject(m_asAudioEntities[id].m_awAudioEvent[0]); + else + ProcessLoopingScriptObject(entity->AudioId); + } +} + +void +cAudioManager::ProcessOneShotScriptObject(uint8 sound) +{ + CPlayerPed *playerPed; + uint8 Vol; + float distSquared; + + static uint8 iSound = 0; + + switch (sound) { + case SCRIPT_SOUND_BOX_DESTROYED_1: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_BOX_DESTROYED_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_WOODEN_BOX_SMASH; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = RandomDisplacement(1500) + 18600; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_BOX_DESTROYED_VOLUME; + break; + case SCRIPT_SOUND_BOX_DESTROYED_2: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_BOX_DESTROYED_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CARDBOARD_BOX_SMASH; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = RandomDisplacement(1500) + 18600; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_BOX_DESTROYED_VOLUME; + break; + case SCRIPT_SOUND_METAL_COLLISION: + m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST; + m_sQueueSample.m_nSampleIndex = m_anRandomTable[3] % 5 + SFX_COL_CAR_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[2] % 30 + SCRIPT_OBJECT_METAL_COLLISION_VOLUME; + break; + case SCRIPT_SOUND_TIRE_COLLISION: + m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_TYRE_BUMP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[2] % 30 + SCRIPT_OBJECT_TIRE_COLLISION_VOLUME; + break; + case SCRIPT_SOUND_GUNSHELL_DROP: + playerPed = FindPlayerPed(); + if (playerPed) { + switch (playerPed->m_nSurfaceTouched) { + case SURFACE_GRASS: + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: + case SURFACE_TRANSPARENT_CLOTH: + case SURFACE_PED: + case SURFACE_SAND: + case SURFACE_RUBBER: + case SURFACE_HEDGE: + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_2; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 11000; + m_sQueueSample.m_nPriority = 18; + break; + case SURFACE_WATER: + return; + default: + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_1; + m_sQueueSample.m_nFrequency = RandomDisplacement(750) + 18000; + m_sQueueSample.m_nPriority = 15; + break; + } + } else { + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_1; + m_sQueueSample.m_nFrequency = RandomDisplacement(750) + 18000; + m_sQueueSample.m_nPriority = 15; + } + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GUNSHELL_MAX_DIST; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_GUNSHELL_VOLUME; + break; + case SCRIPT_SOUND_GUNSHELL_DROP_SOFT: + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_2; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 11000; + m_sQueueSample.m_nPriority = 18; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GUNSHELL_MAX_DIST; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_GUNSHELL_VOLUME; + break; + case SCRIPT_SOUND_BULLET_HIT_GROUND_1: + case SCRIPT_SOUND_BULLET_HIT_GROUND_2: + case SCRIPT_SOUND_BULLET_HIT_GROUND_3: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_BULLET_HIT_GROUND_MAX_DIST; + m_sQueueSample.m_nSampleIndex = m_anRandomTable[iSound % 5] % 3 + SFX_BULLET_WALL_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 9; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + Vol = m_anRandomTable[2] % 20 + SCRIPT_OBJECT_BULLET_HIT_GROUND_VOLUME; + break; + case SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_1: + case SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_2: + if (SampleManager.IsSampleBankLoaded(SFX_BANK_TRAIN) != LOADING_STATUS_LOADED) + return; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_TRAIN_ANNOUNCEMENT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_TRAIN_STATION_ANNOUNCE; + m_sQueueSample.m_nBankIndex = SFX_BANK_TRAIN; + Vol = SCRIPT_OBJECT_TRAIN_ANNOUNCEMENT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TRAIN_STATION_ANNOUNCE); + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_PAYPHONE_RINGING: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_PAYPHONE_RINGING_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_PHONE_RING; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_PAYPHONE_RINGING_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PHONE_RING); + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(FALSE); + break; + case SCRIPT_SOUND_GLASS_BREAK_L: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GLASS_BREAK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_GLASS_SMASH; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_GLASS_BREAK_LONG_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_GLASS_SMASH); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_GLASS_BREAK_S: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GLASS_BREAK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_GLASS_SMASH; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_GLASS_BREAK_SHORT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_GLASS_SMASH); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_GLASS_CRACK: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GLASS_BREAK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_GLASS_CRACK; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_GLASS_BREAK_LONG_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_GLASS_CRACK); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + break; + case SCRIPT_SOUND_GLASS_LIGHT_BREAK: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GLASS_LIGHT_BREAK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[4] & 3) + SFX_GLASS_SHARD_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = RandomDisplacement(2000) + 19000; + m_sQueueSample.m_nPriority = 9; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + Vol = RandomDisplacement(11) + SCRIPT_OBJECT_GLASS_LIGHT_BREAK_VOLUME; + break; + case SCRIPT_SOUND_INJURED_PED_MALE_OUCH_S: + case SCRIPT_SOUND_INJURED_PED_MALE_OUCH_L: + { + cPedParams pedParams; + pedParams.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + SetupPedComments(pedParams, SOUND_INJURED_PED_MALE_OUCH); + return; + } + case SCRIPT_SOUND_INJURED_PED_FEMALE_OUCH_S: + case SCRIPT_SOUND_INJURED_PED_FEMALE_OUCH_L: + { + cPedParams pedParams; + pedParams.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + SetupPedComments(pedParams, SOUND_INJURED_PED_FEMALE); + return; + } + case SCRIPT_SOUND_GATE_START_CLUNK: + case SCRIPT_SOUND_GATE_STOP_CLUNK: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GATE_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_GATE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + if (sound == SCRIPT_SOUND_GATE_START_CLUNK) + m_sQueueSample.m_nFrequency = 10600; + else + m_sQueueSample.m_nFrequency = 9000; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = RandomDisplacement(10) + 50; + break; + default: + return; + } + + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(m_sQueueSample.m_MaxDistance)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessLoopingScriptObject(uint8 sound) +{ + uint8 Vol; + float distSquared; + float maxDistSquared; + bool8 bLoadBank = FALSE; + + switch (sound) { + case SCRIPT_SOUND_PORN_CINEMA_1_S: + case SCRIPT_SOUND_PORN_CINEMA_1_L: + case SCRIPT_SOUND_PORN_CINEMA_2_S: + case SCRIPT_SOUND_PORN_CINEMA_2_L: + case SCRIPT_SOUND_PORN_CINEMA_3_S: + case SCRIPT_SOUND_PORN_CINEMA_3_L: + case SCRIPT_SOUND_MISTY_SEX_S: + case SCRIPT_SOUND_MISTY_SEX_L: + ProcessPornCinema(sound); + return; + case SCRIPT_SOUND_WORK_SHOP_LOOP_S: + case SCRIPT_SOUND_WORK_SHOP_LOOP_L: + ProcessWorkShopScriptObject(sound); + return; + case SCRIPT_SOUND_SAWMILL_LOOP_S: + case SCRIPT_SOUND_SAWMILL_LOOP_L: + ProcessSawMillScriptObject(sound); + return; + case SCRIPT_SOUND_LAUNDERETTE_LOOP_S: + case SCRIPT_SOUND_LAUNDERETTE_LOOP_L: + ProcessLaunderetteScriptObject(sound); + return; + case SCRIPT_SOUND_SHOP_LOOP_S: + case SCRIPT_SOUND_SHOP_LOOP_L: + ProcessShopScriptObject(sound); + return; + case SCRIPT_SOUND_AIRPORT_LOOP_S: + case SCRIPT_SOUND_AIRPORT_LOOP_L: + ProcessAirportScriptObject(sound); + return; + case SCRIPT_SOUND_CINEMA_LOOP_S: + case SCRIPT_SOUND_CINEMA_LOOP_L: + ProcessCinemaScriptObject(sound); + return; + case SCRIPT_SOUND_DOCKS_LOOP_S: + case SCRIPT_SOUND_DOCKS_LOOP_L: + ProcessDocksScriptObject(sound); + return; + case SCRIPT_SOUND_HOME_LOOP_S: + case SCRIPT_SOUND_HOME_LOOP_L: + ProcessHomeScriptObject(sound); + return; + case SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_S: + case SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_L: + ProcessPoliceCellBeatingScriptObject(sound); + return; + case SCRIPT_SOUND_BANK_ALARM_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto BankAlarm; + case SCRIPT_SOUND_BANK_ALARM_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +BankAlarm: + m_sQueueSample.m_nSampleIndex = SFX_BANK_ALARM_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_BANK_ALARM; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_BANK_ALARM_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BANK_ALARM_1); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_POLICE_BALL_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto PoliceBall; + case SCRIPT_SOUND_POLICE_BALL_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +PoliceBall: + m_sQueueSample.m_nSampleIndex = SFX_POLICE_BALL_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_POLICE_BALL; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_POLICE_BALL_1); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_1_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party1; + case SCRIPT_SOUND_PARTY_1_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party1: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_1; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_1); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_2_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party2; + case SCRIPT_SOUND_PARTY_2_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party2: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_2; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_2); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_3_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party3; + case SCRIPT_SOUND_PARTY_3_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party3: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_3; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_3; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_3); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_4_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party4; + case SCRIPT_SOUND_PARTY_4_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party4: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_4; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_4; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_4); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_5_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party5; + case SCRIPT_SOUND_PARTY_5_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party5: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_5; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_5; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_5); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_6_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party6; + case SCRIPT_SOUND_PARTY_6_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party6: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_6; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_6; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_6); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_7_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party7; + case SCRIPT_SOUND_PARTY_7_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party7: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_7; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_7; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_7); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_8_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party8; + case SCRIPT_SOUND_PARTY_8_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party8: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_8; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_8; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_8); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_9_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party9; + case SCRIPT_SOUND_PARTY_9_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party9: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_9; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_9; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_9); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_10_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party10; + case SCRIPT_SOUND_PARTY_10_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party10: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_10; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_10; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_10); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_11_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party11; + case SCRIPT_SOUND_PARTY_11_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party11: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_11; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_11; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_11); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_12_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party12; + case SCRIPT_SOUND_PARTY_12_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party12: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_12; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_12; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_12); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_13_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party13; + case SCRIPT_SOUND_PARTY_13_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Party13: + m_sQueueSample.m_nSampleIndex = SFX_CLUB_RAGGA; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CLUB_RAGGA; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CLUB_RAGGA); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_STRIP_CLUB_LOOP_1_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto StripClub1; + case SCRIPT_SOUND_STRIP_CLUB_LOOP_1_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +StripClub1: + m_sQueueSample.m_nSampleIndex = SFX_STRIP_CLUB_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_STRIP_CLUB_1; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_STRIP_CLUB_1); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_STRIP_CLUB_LOOP_2_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto StripClub2; + case SCRIPT_SOUND_STRIP_CLUB_LOOP_2_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +StripClub2: + m_sQueueSample.m_nSampleIndex = SFX_STRIP_CLUB_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_STRIP_CLUB_2; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_STRIP_CLUB_2); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PARTY_1_LOOP: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Party1; // BUG? Shouldn't this be Frankie piano? + case SCRIPT_SOUND_FRANKIE_PIANO: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_PIANO_BAR_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PIANO_BAR; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PIANO_BAR_1); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_DOG_FOOD_FACTORY_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto DogFoodFactory; + case SCRIPT_SOUND_DOG_FOOD_FACTORY_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +DogFoodFactory: + m_sQueueSample.m_nSampleIndex = SFX_DOG_FOOD_FACTORY; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_DOG_FOOD_FACTORY; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_DOG_FOOD_FACTORY); + m_sQueueSample.m_nPriority = 6; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_CHINATOWN_RESTAURANT_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto ChinatownRestaurant; + case SCRIPT_SOUND_CHINATOWN_RESTAURANT_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +ChinatownRestaurant: + m_sQueueSample.m_nSampleIndex = SFX_RESTAURANT_CHINATOWN; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RESTAURANT_CHINATOWN; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RESTAURANT_CHINATOWN); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_CIPRIANI_RESAURANT_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto CiprianiRestaurant; + case SCRIPT_SOUND_CIPRIANI_RESAURANT_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +CiprianiRestaurant: + m_sQueueSample.m_nSampleIndex = SFX_RESTAURANT_ITALY; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RESTAURANT_ITALY; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RESTAURANT_ITALY); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_47_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto GenericRestaurant1; + case SCRIPT_SOUND_46_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +GenericRestaurant1: + m_sQueueSample.m_nSampleIndex = SFX_RESTAURANT_GENERIC_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RESTAURANT_GENERIC_1; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RESTAURANT_GENERIC_1); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_MARCO_BISTRO_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto GenericRestaurant2; + case SCRIPT_SOUND_MARCO_BISTRO_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +GenericRestaurant2: + m_sQueueSample.m_nSampleIndex = SFX_RESTAURANT_GENERIC_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RESTAURANT_GENERIC_2; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_RESAURANT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RESTAURANT_GENERIC_2); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_RAVE_LOOP_INDUSTRIAL_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto RaveLoop; + case SCRIPT_SOUND_RAVE_LOOP_INDUSTRIAL_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +RaveLoop: + m_sQueueSample.m_nSampleIndex = SFX_RAVE_INDUSTRIAL; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RAVE_INDUSTRIAL; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RAVE_INDUSTRIAL); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_RAVE_1_LOOP_L: + case SCRIPT_SOUND_RAVE_2_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Rave1; + case SCRIPT_SOUND_RAVE_1_LOOP_S: + case SCRIPT_SOUND_RAVE_2_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Rave1: + m_sQueueSample.m_nSampleIndex = SFX_RAVE_COMMERCIAL; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RAVE_COMMERCIAL; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_RAVE_3_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + goto Rave3; + case SCRIPT_SOUND_RAVE_3_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; +Rave3: + m_sQueueSample.m_nSampleIndex = SFX_RAVE_SUBURBAN; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_RAVE_SUBURBAN; + bLoadBank = TRUE; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RAVE_SUBURBAN); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + case SCRIPT_SOUND_PRETEND_FIRE_LOOP: + maxDistSquared = SQR(FIRE_DEFAULT_MAX_DIST); + m_sQueueSample.m_MaxDistance = FIRE_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = FIRE_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE); + m_sQueueSample.m_nPriority = 8; + m_sQueueSample.m_nFramesToPlay = 10; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + break; + default: + return; + } + + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { +#ifdef GTA_PS2 + if (bLoadBank && !LoadBankIfNecessary(m_sQueueSample.m_nBankIndex)) + return; +#endif + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessPornCinema(uint8 sound) +{ + eSfxSample sample; + uint32 time; + int32 rand; + float distSquared; + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_PORN_CINEMA_1_S: + case SCRIPT_SOUND_MISTY_SEX_S: + m_sQueueSample.m_nSampleIndex = SFX_PORN_1_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_1; + maxDistSquared = SQR(PORN_CINEMA_SHORT_MAX_DIST); + sample = SFX_PORN_1_GROAN_1; + m_sQueueSample.m_MaxDistance = PORN_CINEMA_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_PORN_CINEMA_1_L: + case SCRIPT_SOUND_MISTY_SEX_L: + m_sQueueSample.m_nSampleIndex = SFX_PORN_1_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_1; + maxDistSquared = SQR(PORN_CINEMA_LONG_MAX_DIST); + sample = SFX_PORN_1_GROAN_1; + m_sQueueSample.m_MaxDistance = PORN_CINEMA_LONG_MAX_DIST; + break; + case SCRIPT_SOUND_PORN_CINEMA_2_S: + m_sQueueSample.m_nSampleIndex = SFX_PORN_2_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_2; + maxDistSquared = SQR(PORN_CINEMA_SHORT_MAX_DIST); + sample = SFX_PORN_2_GROAN_1; + m_sQueueSample.m_MaxDistance = PORN_CINEMA_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_PORN_CINEMA_2_L: + m_sQueueSample.m_nSampleIndex = SFX_PORN_2_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_2; + maxDistSquared = SQR(PORN_CINEMA_LONG_MAX_DIST); + sample = SFX_PORN_2_GROAN_1; + m_sQueueSample.m_MaxDistance = PORN_CINEMA_LONG_MAX_DIST; + break; + case SCRIPT_SOUND_PORN_CINEMA_3_S: + m_sQueueSample.m_nSampleIndex = SFX_PORN_3_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_3; + maxDistSquared = SQR(PORN_CINEMA_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = PORN_CINEMA_SHORT_MAX_DIST; + sample = SFX_PORN_3_GROAN_1; + break; + case SCRIPT_SOUND_PORN_CINEMA_3_L: + m_sQueueSample.m_nSampleIndex = SFX_PORN_3_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_PORN_3; + maxDistSquared = SQR(PORN_CINEMA_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = PORN_CINEMA_LONG_MAX_DIST; + sample = SFX_PORN_3_GROAN_1; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(m_sQueueSample.m_nBankIndex)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + if (sound != SCRIPT_SOUND_MISTY_SEX_S && sound != SCRIPT_SOUND_MISTY_SEX_L) { + m_sQueueSample.m_nVolume = ComputeVolume(PORN_CINEMA_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(PORN_CINEMA_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + + time = CTimer::GetTimeInMilliseconds(); + if (time > gPornNextTime) { + m_sQueueSample.m_nVolume = ComputeVolume(PORN_CINEMA_MOAN_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + rand = m_anRandomTable[1] & 1; + m_sQueueSample.m_nSampleIndex = rand + sample; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nCounter = rand + 1; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 6; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; +#ifdef FIX_BUGS + SET_EMITTING_VOLUME(PORN_CINEMA_MOAN_VOLUME); +#endif + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gPornNextTime = time + 2000 + m_anRandomTable[3] % 6000; + } + } + } +} + +void +cAudioManager::ProcessWorkShopScriptObject(uint8 sound) +{ + float distSquared; + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_WORK_SHOP_LOOP_S: + case SCRIPT_SOUND_WORK_SHOP_LOOP_L: + maxDistSquared = SQR(WORK_SHOP_MAX_DIST); + m_sQueueSample.m_MaxDistance = WORK_SHOP_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_WORKSHOP)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(WORK_SHOP_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_WORKSHOP_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_WORKSHOP; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_WORKSHOP_1); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(WORK_SHOP_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessSawMillScriptObject(uint8 sound) +{ + uint32 time; + float distSquared; + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_SAWMILL_LOOP_S: + case SCRIPT_SOUND_SAWMILL_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_SAWMILL)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(SAWMILL_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_SAWMILL_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_SAWMILL; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SAWMILL_LOOP); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(SAWMILL_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + time = CTimer::GetTimeInMilliseconds(); + if (time > gSawMillNextTime) { + m_sQueueSample.m_nVolume = ComputeVolume(SAWMILL_CUT_WOOD_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_SAWMILL_CUT_WOOD; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_SAWMILL; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; +#ifdef FIX_BUGS + SET_EMITTING_VOLUME(SAWMILL_CUT_WOOD_VOLUME); +#endif + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gSawMillNextTime = time + 2000 + m_anRandomTable[3] % 4000; + } + } + } +} + +void +cAudioManager::ProcessLaunderetteScriptObject(uint8 sound) +{ + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_LAUNDERETTE_LOOP_S: + case SCRIPT_SOUND_LAUNDERETTE_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_LAUNDERETTE)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(LAUNDERETTE_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_LAUNDERETTE_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_LAUNDERETTE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_LAUNDERETTE_LOOP); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(LAUNDERETTE_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + m_sQueueSample.m_nVolume = ComputeVolume(LAUNDERETTE_SONG_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_LAUNDERETTE_SONG_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_LAUNDERETTE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_LAUNDERETTE_SONG_LOOP); + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(LAUNDERETTE_SONG_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessShopScriptObject(uint8 sound) +{ + uint32 time; + int32 rand; + float distSquared; + float maxDistSquared; + + switch (sound) { + case SCRIPT_SOUND_SHOP_LOOP_S: + case SCRIPT_SOUND_SHOP_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_SHOP)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(SHOP_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_SHOP_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_SHOP; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SHOP_LOOP); + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(SHOP_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + time = CTimer::GetTimeInMilliseconds(); + if (time > gShopNextTime) { + m_sQueueSample.m_nVolume = ComputeVolume(SHOP_TILL_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + rand = m_anRandomTable[1] & 1; + m_sQueueSample.m_nSampleIndex = rand + SFX_SHOP_TILL_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_SHOP; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nCounter = rand + 1; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(SHOP_TILL_VOLUME); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gShopNextTime = time + 3000 + m_anRandomTable[3] % 7000; + } + } + } +} + +void +cAudioManager::ProcessAirportScriptObject(uint8 sound) +{ + float maxDistSquared; + static uint8 iSound = 0; + + uint32 time = CTimer::GetTimeInMilliseconds(); + if (time > gAirportNextTime) { + switch (sound) { + case SCRIPT_SOUND_AIRPORT_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_AIRPORT_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_AIRPORT)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(AIRPORT_VOLUME, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[1] & 3) + SFX_AIRPORT_ANNOUNCEMENT_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_AIRPORT; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(AIRPORT_VOLUME); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gAirportNextTime = time + 10000 + m_anRandomTable[3] % 20000; + } + } + } +} + +void +cAudioManager::ProcessCinemaScriptObject(uint8 sound) +{ + float maxDistSquared; + uint8 Vol; + + static uint8 iSound = 0; + + uint32 time = CTimer::GetTimeInMilliseconds(); + if (time > gCinemaNextTime) { + switch (sound) { + case SCRIPT_SOUND_CINEMA_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_CINEMA_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_CINEMA)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + Vol = m_anRandomTable[0] % 90 + CINEMA_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = iSound % 3 + SFX_CINEMA_BASS_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CINEMA; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gCinemaNextTime = time + 1000 + m_anRandomTable[3] % 4000; + } + } + } +} + +void +cAudioManager::ProcessDocksScriptObject(uint8 sound) +{ + uint32 time; + uint8 Vol; + float distSquared; + float maxDistSquared; + + static uint8 iSound = 0; + + time = CTimer::GetTimeInMilliseconds(); + if (time > gDocksNextTime) { + switch (sound) { + case SCRIPT_SOUND_DOCKS_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_DOCKS_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_DOCKS)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(distSquared); + Vol = m_anRandomTable[0] % 60 + DOCKS_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_DOCKS_FOGHORN; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_DOCKS; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_DOCKS_FOGHORN); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 3); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + gDocksNextTime = time + 10000 + m_anRandomTable[3] % 40000; + } + } + } +} +void +cAudioManager::ProcessHomeScriptObject(uint8 sound) +{ + uint32 time; + uint8 Vol; + float dist; + float maxDistSquared; + + static uint8 iSound = 0; + + time = CTimer::GetTimeInMilliseconds(); + if (time > gHomeNextTime) { + switch (sound) { + case SCRIPT_SOUND_HOME_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_HOME_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + dist = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (dist < maxDistSquared) { +#ifdef GTA_PS2 + if (!LoadBankIfNecessary(SFX_BANK_BUILDING_HOME)) + return; +#endif + m_sQueueSample.m_fDistance = Sqrt(dist); + Vol = m_anRandomTable[0] % 30 + HOME_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = m_anRandomTable[0] % 5 + SFX_HOME_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_HOME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + gHomeNextTime = time + 1000 + m_anRandomTable[3] % 4000; + } + } + } +} +void +cAudioManager::ProcessPoliceCellBeatingScriptObject(uint8 sound) +{ + uint32 time = CTimer::GetTimeInMilliseconds(); + int32 sampleIndex; + uint8 Vol; + float distSquared; + float maxDistSquared; + + static uint8 iSound = 0; + + if (time > gCellNextTime) { + switch (sound) { + case SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_S: + maxDistSquared = SQR(SCRIPT_OBJECT_SHORT_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHORT_MAX_DIST; + break; + case SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_L: + maxDistSquared = SQR(SCRIPT_OBJECT_LONG_MAX_DIST); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + break; + default: +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < maxDistSquared) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + if (m_FrameCounter & 1) + sampleIndex = (m_anRandomTable[1] & 3) + SFX_FIGHT_1; + else + sampleIndex = (m_anRandomTable[3] & 1) + SFX_BAT_HIT_LEFT; + m_sQueueSample.m_nSampleIndex = sampleIndex; + Vol = m_anRandomTable[0] % 50 + POLICE_CELL_BEATING_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + SET_EMITTING_VOLUME(Vol); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + cPedParams params; + params.m_bDistanceCalculated = TRUE; + params.m_fDistance = distSquared; + SetupPedComments(params, SOUND_INJURED_PED_MALE_PRISON); + } + gCellNextTime = time + 500 + m_anRandomTable[3] % 1500; + } + } +} +#pragma endregion All the code for script object audio on the map + +void +cAudioManager::ProcessWeather(int32 id) +{ + uint8 Vol; + static uint8 iSound = 0; + + if (m_asAudioEntities[id].m_AudioEvents > 0 && m_asAudioEntities[id].m_awAudioEvent[0] == SOUND_LIGHTNING) { + if (m_asAudioEntities[id].m_afVolume[0] < 10) { + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 4000; + Vol = (m_asAudioEntities[id].m_afVolume[0] * 10.0f * 0.1f); + Vol += 35; + } else { + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_GENERIC_EXTRA; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 4000; + Vol = ((m_asAudioEntities[id].m_afVolume[0] - 10.0f) * 10.0f * 0.1f); + Vol += 40; + } + m_sQueueSample.m_nVolume = Vol; + if (TheCamera.SoundDistUp < 20.0f) + m_sQueueSample.m_nVolume >>= 1; + if (iSound == 4) + iSound = 0; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nPan = (m_anRandomTable[2] % 16) + 55; + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + RESET_LOOP_OFFSETS + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + if (CWeather::Rain > 0.0f && (!CCullZones::CamNoRain() || !CCullZones::PlayerNoRain())) { + m_sQueueSample.m_nSampleIndex = SFX_RAIN; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RAIN); + m_sQueueSample.m_nVolume = (uint8)(25.0f * CWeather::Rain); + m_sQueueSample.m_nCounter = 4; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nPan = 63; + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 30; + m_sQueueSample.m_bReverb = FALSE; + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } +} + +void +cAudioManager::ProcessFrontEnd() +{ + bool8 stereo; + bool8 processedPickup; + bool8 processedMission; + bool8 frontendBank; + int16 sample; + + static uint8 iSound = 0; + static uint32 cPickupNextFrame = 0; + static uint32 cPartMisComNextFrame = 0; + + for (uint32 i = 0; i < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; i++) { + processedPickup = FALSE; + stereo = FALSE; + processedMission = FALSE; + frontendBank = FALSE; + switch (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]) { + case SOUND_FRONTEND_RADIO_TURN_OFF: + case SOUND_FRONTEND_RADIO_CHANGE: + m_sQueueSample.m_nSampleIndex = SFX_RADIO_CLICK; + break; + case SOUND_HUD: + m_sQueueSample.m_nSampleIndex = SFX_INFO; + break; + case SOUND_FRONTEND_MENU_STARTING: + m_sQueueSample.m_nSampleIndex = SFX_START_BUTTON_LEFT; + stereo = TRUE; + break; + case SOUND_FRONTEND_MENU_NEW_PAGE: + m_sQueueSample.m_nSampleIndex = SFX_PAGE_CHANGE_AND_BACK_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_MENU_NAVIGATION: + m_sQueueSample.m_nSampleIndex = SFX_HIGHLIGHT_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_MENU_SETTING_CHANGE: + m_sQueueSample.m_nSampleIndex = SFX_SELECT_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_MENU_BACK: + m_sQueueSample.m_nSampleIndex = SFX_SUB_MENU_BACK_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_STEREO: + m_sQueueSample.m_nSampleIndex = SFX_STEREO_LEFT; + stereo = TRUE; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_MONO: + m_sQueueSample.m_nSampleIndex = SFX_MONO; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_AUDIO_TEST: + m_sQueueSample.m_nSampleIndex = m_anRandomTable[0] % 3 + SFX_NOISE_BURST_1; + frontendBank = TRUE; + break; + case SOUND_FRONTEND_FAIL: + m_sQueueSample.m_nSampleIndex = SFX_ERROR_LEFT; + frontendBank = TRUE; + stereo = TRUE; + break; + case SOUND_RACE_START_GO: + m_sQueueSample.m_nSampleIndex = SFX_PART_MISSION_COMPLETE; + break; + case SOUND_PART_MISSION_COMPLETE: + m_sQueueSample.m_nSampleIndex = SFX_PART_MISSION_COMPLETE; + processedMission = TRUE; + break; + case SOUND_RACE_START_3: + case SOUND_RACE_START_2: + case SOUND_RACE_START_1: + case SOUND_CLOCK_TICK: + m_sQueueSample.m_nSampleIndex = SFX_TIMER_BEEP; + break; + case SOUND_PAGER: +#ifdef GTA_PS2 + { + cPedParams pedParams; + pedParams.m_bDistanceCalculated = TRUE; + SetupPedComments(pedParams, SOUND_PAGER); + continue; + } +#else + m_sQueueSample.m_nSampleIndex = SFX_PAGER; + break; +#endif + case SOUND_WEAPON_SNIPER_SHOT_NO_ZOOM: + m_sQueueSample.m_nSampleIndex = SFX_ERROR_FIRE_RIFLE; + break; + case SOUND_WEAPON_ROCKET_SHOT_NO_ZOOM: + m_sQueueSample.m_nSampleIndex = SFX_ERROR_FIRE_ROCKET_LAUNCHER; + break; + case SOUND_PICKUP_WEAPON_BOUGHT: + case SOUND_PICKUP_WEAPON: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_1_LEFT; + processedPickup = TRUE; + stereo = TRUE; + break; + case SOUND_PICKUP_BONUS: + case SOUND_PICKUP_MONEY: + case SOUND_PICKUP_HIDDEN_PACKAGE: + case SOUND_PICKUP_PACMAN_PILL: + case SOUND_PICKUP_PACMAN_PACKAGE: + case SOUND_PICKUP_FLOAT_PACKAGE: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_3_LEFT; + processedPickup = TRUE; + stereo = TRUE; + break; + case SOUND_PICKUP_ERROR: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_ERROR_LEFT; + processedPickup = TRUE; + stereo = TRUE; + break; + case SOUND_GARAGE_NO_MONEY: + case SOUND_GARAGE_BAD_VEHICLE: + case SOUND_GARAGE_BOMB_ALREADY_SET: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_ERROR_LEFT; + stereo = TRUE; + break; + case SOUND_GARAGE_OPENING: + case SOUND_GARAGE_BOMB1_SET: + case SOUND_GARAGE_BOMB2_SET: + case SOUND_GARAGE_BOMB3_SET: + case SOUND_41: + case SOUND_GARAGE_VEHICLE_DECLINED: + case SOUND_GARAGE_VEHICLE_ACCEPTED: + case SOUND_PICKUP_HEALTH: + case SOUND_4B: + case SOUND_PICKUP_ADRENALINE: + case SOUND_PICKUP_ARMOUR: + case SOUND_EVIDENCE_PICKUP: + case SOUND_UNLOAD_GOLD: + m_sQueueSample.m_nSampleIndex = SFX_PICKUP_2_LEFT; + processedPickup = TRUE; + stereo = TRUE; + break; + default: + continue; + } + + if (processedPickup) { + if (m_FrameCounter <= cPickupNextFrame) + continue; + cPickupNextFrame = m_FrameCounter + 5; + } else if (processedMission) { + if (m_FrameCounter <= cPartMisComNextFrame) + continue; + cPartMisComNextFrame = m_FrameCounter + 5; + } + + sample = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]; + if (sample == SFX_RAIN) + m_sQueueSample.m_nFrequency = 28509; + else if (sample == SFX_PICKUP_1_LEFT) { + if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] == 1.0f) + m_sQueueSample.m_nFrequency = 48000; + else + m_sQueueSample.m_nFrequency = 32000; + } else + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nVolume = FRONTEND_VOLUME; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nBankIndex = frontendBank ? SFX_BANK_FRONT_END_MENU : SFX_BANK_0; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_bIs2D = TRUE; + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + RESET_LOOP_OFFSETS + if (stereo) + m_sQueueSample.m_nPan = m_anRandomTable[0] & 31; + else + m_sQueueSample.m_nPan = 63; + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + if (stereo) { + m_sQueueSample.m_nSampleIndex++; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nPan = 127 - m_sQueueSample.m_nPan; + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessCrane() +{ + CCrane *crane = (CCrane *)m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_pEntity; + float distSquared; + bool8 distCalculated = FALSE; + + if (crane) { + if (crane->m_nCraneStatus == CCrane::ACTIVATED) { + if (crane->m_nCraneState != CCrane::IDLE) { + m_sQueueSample.m_vecPos = crane->m_pCraneEntity->GetPosition(); + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(CRANE_MAX_DIST)) { + CalculateDistance(distCalculated, distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(CRANE_VOLUME, CRANE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_CRANE_MAGNET; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFrequency = 6000; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(CRANE_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_MaxDistance = CRANE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents > 0) { + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nSampleIndex = SFX_COL_CAR_2; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_COL_CAR_2); + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + } + } + } + } + } +} + +void +cAudioManager::ProcessProjectiles() +{ + uint8 Vol; + + for (uint8 i = 0; i < NUM_PROJECTILES; i++) { + if (CProjectileInfo::GetProjectileInfo(i)->m_bInUse) { + switch (CProjectileInfo::GetProjectileInfo(i)->m_eWeaponType) { + case WEAPONTYPE_ROCKETLAUNCHER: + Vol = PROJECTILE_ROCKET_VOLUME; + m_sQueueSample.m_MaxDistance = PROJECTILE_ROCKET_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_FLY; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ROCKET_FLY); + m_sQueueSample.m_nPriority = 3; + break; + case WEAPONTYPE_MOLOTOV: + Vol = PROJECTILE_MOLOTOV_VOLUME; + m_sQueueSample.m_MaxDistance = PROJECTILE_MOLOTOV_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_PED_ON_FIRE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 32 * SampleManager.GetSampleBaseFrequency(SFX_PED_ON_FIRE) / 25; + m_sQueueSample.m_nPriority = 7; + break; + default: + continue; + } + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_vecPos = CProjectileInfo::ms_apProjectile[i]->GetPosition(); + float distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(m_sQueueSample.m_MaxDistance)) { + m_sQueueSample.m_fDistance = Sqrt(distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::ProcessGarages() +{ + CEntity *entity; + uint8 state; + uint32 sampleIndex; + uint8 j; + float distSquared; + bool8 distCalculated; + + static uint8 iSound = 32; + +#ifdef FIX_BUGS + for (uint32 i = 0; i < CGarages::NumGarages; i++) { +#else + for (uint8 i = 0; i < CGarages::NumGarages; i++) { +#endif + if (CGarages::aGarages[i].m_eGarageType == GARAGE_NONE) + continue; + entity = CGarages::aGarages[i].m_pDoor1; + if (entity == nil) + continue; + m_sQueueSample.m_vecPos = entity->GetPosition(); + distCalculated = FALSE; + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(GARAGES_MAX_DIST)) { + state = CGarages::aGarages[i].m_eGarageState; + if (state == GS_OPENING || state == GS_CLOSING || state == GS_AFTERDROPOFF) { + CalculateDistance(distCalculated, distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(GARAGES_VOLUME, GARAGES_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (CGarages::aGarages[i].m_eGarageType == GARAGE_CRUSHER) { + if (CGarages::aGarages[i].m_eGarageState == GS_AFTERDROPOFF) { + if (m_FrameCounter & 1) { + if (m_anRandomTable[1] & 1) + sampleIndex = m_anRandomTable[2] % 5 + SFX_COL_CAR_1; + else + sampleIndex = m_anRandomTable[2] % 6 + SFX_COL_CAR_PANEL_1; + m_sQueueSample.m_nSampleIndex = sampleIndex; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex) >> 1; + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nCounter = iSound++; + if (iSound < 32) + iSound = 32; + } else + goto CheckGarageEvents; // premature exit to go straight to the for loop + } else { + m_sQueueSample.m_nSampleIndex = SFX_FISHING_BOAT_IDLE; + m_sQueueSample.m_nFrequency = 6543; + + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bStatic = FALSE; + } + } else { + m_sQueueSample.m_nSampleIndex = SFX_GARAGE_DOOR_LOOP; + m_sQueueSample.m_nFrequency = 13961; + + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bStatic = FALSE; + } + + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + SET_EMITTING_VOLUME(GARAGES_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = GARAGES_MAX_DIST; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +CheckGarageEvents: + for (j = 0; j < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; j++) { + switch (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[j]) { + case SOUND_GARAGE_DOOR_CLOSED: + case SOUND_GARAGE_DOOR_OPENED: + if (distSquared < SQR(GARAGES_MAX_DIST)) { + CalculateDistance(distCalculated, distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(GARAGES_DOOR_VOLUME, GARAGES_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (CGarages::aGarages[i].m_eGarageType == GARAGE_CRUSHER) { + m_sQueueSample.m_nSampleIndex = SFX_COL_CAR_PANEL_2; + m_sQueueSample.m_nFrequency = 6735; + } else if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[j] == SOUND_GARAGE_DOOR_OPENED) { + m_sQueueSample.m_nSampleIndex = SFX_COL_CAR_PANEL_2; + m_sQueueSample.m_nFrequency = 22000; + } else { + m_sQueueSample.m_nSampleIndex = SFX_COL_GARAGE_DOOR_1; + m_sQueueSample.m_nFrequency = 18000; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nPriority = 4; + SET_EMITTING_VOLUME(GARAGES_DOOR_VOLUME); + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = GARAGES_MAX_DIST; + m_sQueueSample.m_bReverb = TRUE; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + m_sQueueSample.m_nCounter = iSound++; + if (iSound < 32) + iSound = 32; + SET_SOUND_REFLECTION(TRUE); + AddSampleToRequestedQueue(); + } + } + break; + default: + break; + } + } + } +} + +void +cAudioManager::ProcessFireHydrant() +{ + float distSquared; + bool8 distCalculated = FALSE; + + m_sQueueSample.m_vecPos = ((CParticleObject*)m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_pEntity)->GetPosition(); + distSquared = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distSquared < SQR(FIRE_HYDRANT_MAX_DIST)) { + CalculateDistance(distCalculated, distSquared); + m_sQueueSample.m_nVolume = ComputeVolume(FIRE_HYDRANT_VOLUME, FIRE_HYDRANT_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_JUMBO_TAXI; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 4; + m_sQueueSample.m_nFrequency = 15591; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(FIRE_HYDRANT_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = FIRE_HYDRANT_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = TRUE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +#pragma region BRIDGE +void +cAudioManager::ProcessBridge() +{ + float dist; + bool8 distCalculated = FALSE; + + if (CBridge::pLiftRoad) { + m_sQueueSample.m_vecPos = CBridge::pLiftRoad->GetPosition(); + dist = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (dist < SQR(BRIDGE_MAX_DIST)) { + CalculateDistance(distCalculated, dist); + switch (CBridge::State) { + case STATE_BRIDGE_LOCKED: + case STATE_LIFT_PART_IS_UP: + case STATE_LIFT_PART_ABOUT_TO_MOVE_UP: + ProcessBridgeWarning(); + break; + case STATE_LIFT_PART_MOVING_DOWN: + case STATE_LIFT_PART_MOVING_UP: + ProcessBridgeWarning(); + ProcessBridgeMotor(); + break; + default: + break; + } + ProcessBridgeOneShots(); + } + } +} + +void +cAudioManager::ProcessBridgeWarning() +{ + if (!CStats::CommercialPassed) + return; + + if (m_sQueueSample.m_fDistance < BRIDGE_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(BRIDGE_WARNING_VOLUME, BRIDGE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_BRIDGE_OPEN_WARNING; + m_sQueueSample.m_nBankIndex = SFX_BANK_GENERIC_EXTRA; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BRIDGE_OPEN_WARNING); + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(BRIDGE_WARNING_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BRIDGE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 8; + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessBridgeMotor() +{ + if (m_sQueueSample.m_fDistance < BRIDGE_MOTOR_MAX_DIST) { + m_sQueueSample.m_nVolume = ComputeVolume(BRIDGE_MOTOR_VOLUME, BRIDGE_MOTOR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 1; + m_sQueueSample.m_nSampleIndex = SFX_FISHING_BOAT_IDLE; // todo check sfx name + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = 5500; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(BRIDGE_MOTOR_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_bReverb = FALSE; + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessBridgeOneShots() +{ + float maxDist; + + if (CBridge::State == STATE_LIFT_PART_IS_UP && CBridge::OldState == STATE_LIFT_PART_MOVING_UP) { + maxDist = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_CONTAINER_1; + } else if (CBridge::State == STATE_LIFT_PART_IS_DOWN && CBridge::OldState == STATE_LIFT_PART_MOVING_DOWN) { + maxDist = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_CONTAINER_1; + } else if (CBridge::State == STATE_LIFT_PART_MOVING_UP && CBridge::OldState == STATE_LIFT_PART_ABOUT_TO_MOVE_UP) { + maxDist = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_CONTAINER_1; + } else if (CBridge::State == STATE_LIFT_PART_MOVING_DOWN && CBridge::OldState == STATE_LIFT_PART_IS_UP) { + maxDist = BRIDGE_MOTOR_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_CONTAINER_1; + } else return; + + if (m_sQueueSample.m_fDistance < maxDist) { + m_sQueueSample.m_nVolume = ComputeVolume(BRIDGE_MOTOR_VOLUME, maxDist, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nLoopCount = 1; + SET_EMITTING_VOLUME(BRIDGE_MOTOR_VOLUME); + RESET_LOOP_OFFSETS + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = maxDist; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_bReverb = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} +#pragma endregion + +#pragma region MISSION_AUDIO +bool8 g_bMissionAudioLoadFailed; + +struct MissionAudioData { + const char *m_pName; + uint32 m_nId; +}; + +Const MissionAudioData MissionAudioNameSfxAssoc[] = { + {"lib_a1", SFX_MISSION_LIB_A1}, {"lib_a2", SFX_MISSION_LIB_A2}, {"lib_a", SFX_MISSION_LIB_A}, + {"lib_b", SFX_MISSION_LIB_B}, {"lib_c", SFX_MISSION_LIB_C}, {"lib_d", SFX_MISSION_LIB_D}, + {"l2_a", SFX_MISSION_L2_A}, {"j4t_1", SFX_MISSION_J4T_1}, {"j4t_2", SFX_MISSION_J4T_2}, + {"j4t_3", SFX_MISSION_J4T_3}, {"j4t_4", SFX_MISSION_J4T_4}, {"j4_a", SFX_MISSION_J4_A}, + {"j4_b", SFX_MISSION_J4_B}, {"j4_c", SFX_MISSION_J4_C}, {"j4_d", SFX_MISSION_J4_D}, + {"j4_e", SFX_MISSION_J4_E}, {"j4_f", SFX_MISSION_J4_F}, {"j6_1", SFX_MISSION_J6_1}, + {"j6_a", SFX_MISSION_J6_A}, {"j6_b", SFX_MISSION_J6_B}, {"j6_c", SFX_MISSION_J6_C}, + {"j6_d", SFX_MISSION_J6_D}, {"t4_a", SFX_MISSION_T4_A}, {"s1_a", SFX_MISSION_S1_A}, + {"s1_a1", SFX_MISSION_S1_A1}, {"s1_b", SFX_MISSION_S1_B}, {"s1_c", SFX_MISSION_S1_C}, + {"s1_c1", SFX_MISSION_S1_C1}, {"s1_d", SFX_MISSION_S1_D}, {"s1_e", SFX_MISSION_S1_E}, + {"s1_f", SFX_MISSION_S1_F}, {"s1_g", SFX_MISSION_S1_G}, {"s1_h", SFX_MISSION_S1_H}, + {"s1_i", SFX_MISSION_S1_I}, {"s1_j", SFX_MISSION_S1_J}, {"s1_k", SFX_MISSION_S1_K}, + {"s1_l", SFX_MISSION_S1_L}, {"s3_a", SFX_MISSION_S3_A}, {"s3_b", SFX_MISSION_S3_B}, + {"el3_a", SFX_MISSION_EL3_A}, {"mf1_a", SFX_MISSION_MF1_A}, {"mf2_a", SFX_MISSION_MF2_A}, + {"mf3_a", SFX_MISSION_MF3_A}, {"mf3_b", SFX_MISSION_MF3_B}, {"mf3_b1", SFX_MISSION_MF3_B1}, + {"mf3_c", SFX_MISSION_MF3_C}, {"mf4_a", SFX_MISSION_MF4_A}, {"mf4_b", SFX_MISSION_MF4_B}, + {"mf4_c", SFX_MISSION_MF4_C}, {"a1_a", SFX_MISSION_A1_A}, {"a3_a", SFX_MISSION_A3_A}, + {"a5_a", SFX_MISSION_A5_A}, {"a4_a", SFX_MISSION_A4_A}, {"a4_b", SFX_MISSION_A4_B}, + {"a4_c", SFX_MISSION_A4_C}, {"a4_d", SFX_MISSION_A4_D}, {"k1_a", SFX_MISSION_K1_A}, + {"k3_a", SFX_MISSION_K3_A}, {"r1_a", SFX_MISSION_R1_A}, {"r2_a", SFX_MISSION_R2_A}, + {"r2_b", SFX_MISSION_R2_B}, {"r2_c", SFX_MISSION_R2_C}, {"r2_d", SFX_MISSION_R2_D}, + {"r2_e", SFX_MISSION_R2_E}, {"r2_f", SFX_MISSION_R2_F}, {"r2_g", SFX_MISSION_R2_G}, + {"r2_h", SFX_MISSION_R2_H}, {"r5_a", SFX_MISSION_R5_A}, {"r6_a", SFX_MISSION_R6_A}, + {"r6_a1", SFX_MISSION_R6_A1}, {"r6_b", SFX_MISSION_R6_B}, {"lo2_a", SFX_MISSION_LO2_A}, + {"lo6_a", SFX_MISSION_LO6_A}, {"yd2_a", SFX_MISSION_YD2_A}, {"yd2_b", SFX_MISSION_YD2_B}, + {"yd2_c", SFX_MISSION_YD2_C}, {"yd2_c1", SFX_MISSION_YD2_C1}, {"yd2_d", SFX_MISSION_YD2_D}, + {"yd2_e", SFX_MISSION_YD2_E}, {"yd2_f", SFX_MISSION_YD2_F}, {"yd2_g", SFX_MISSION_YD2_G}, + {"yd2_h", SFX_MISSION_YD2_H}, {"yd2_ass", SFX_MISSION_YD2_ASS}, {"yd2_ok", SFX_MISSION_YD2_OK}, + {"h5_a", SFX_MISSION_H5_A}, {"h5_b", SFX_MISSION_H5_B}, {"h5_c", SFX_MISSION_H5_C}, + {"ammu_a", SFX_MISSION_AMMU_A}, {"ammu_b", SFX_MISSION_AMMU_B}, {"ammu_c", SFX_MISSION_AMMU_C}, +#if GTA_VERSION < GTA3_PC_10 + {"ammu_d", SFX_AMMU_D }, {"ammu_e", SFX_AMMU_E}, {"ammu_f", SFX_AMMU_F}, + {"ammu_g", SFX_MISSION_AMMU_A}, {"ammu_h", SFX_MISSION_AMMU_A}, {"ammu_i", SFX_MISSION_AMMU_A}, + {"ammu_j", SFX_MISSION_AMMU_A}, {"ammu_k", SFX_MISSION_AMMU_A}, +#endif + {"door_1", SFX_MISSION_DOOR_1}, {"door_2", SFX_MISSION_DOOR_2}, {"door_3", SFX_MISSION_DOOR_3}, + {"door_4", SFX_MISSION_DOOR_4}, {"door_5", SFX_MISSION_DOOR_5}, {"door_6", SFX_MISSION_DOOR_6}, + {"t3_a", SFX_MISSION_T3_A}, {"t3_b", SFX_MISSION_T3_B}, {"t3_c", SFX_MISSION_T3_C}, + {"k1_b", SFX_MISSION_K1_B}, {"c_1", SFX_MISSION_CAT1}, {nil, 0}}; + +uint32 +FindMissionAudioSfx(const char *name) +{ + for (uint32 i = 0; MissionAudioNameSfxAssoc[i].m_pName != nil; i++) { + if (!CGeneral::faststricmp(MissionAudioNameSfxAssoc[i].m_pName, name)) + return MissionAudioNameSfxAssoc[i].m_nId; + } + debug("Can't find mission audio %s", name); + return NO_SAMPLE; +} + +bool8 +cAudioManager::MissionScriptAudioUsesPoliceChannel(uint32 soundMission) +{ + switch (soundMission) { + case SFX_MISSION_J6_D: + case SFX_MISSION_T4_A: + case SFX_MISSION_S1_H: + case SFX_MISSION_S3_B: + case SFX_MISSION_EL3_A: + case SFX_MISSION_A3_A: + case SFX_MISSION_A5_A: + case SFX_MISSION_K1_A: + case SFX_MISSION_R1_A: + case SFX_MISSION_R5_A: + case SFX_MISSION_LO2_A: + case SFX_MISSION_LO6_A: + return TRUE; + default: + return FALSE; + } +} + +void +cAudioManager::PreloadMissionAudio(Const char *name) +{ + if (m_bIsInitialised) { + uint32 missionAudioSfx = FindMissionAudioSfx(name); + if (missionAudioSfx != NO_SAMPLE) { + m_nMissionAudioSampleIndex = missionAudioSfx; + m_nMissionAudioLoadingStatus = LOADING_STATUS_NOT_LOADED; + m_nMissionAudioPlayStatus = PLAY_STATUS_STOPPED; + m_bIsMissionAudioPlaying = FALSE; +#ifdef GTA_PS2 + m_nMissionAudioFramesToPlay = m_nTimeSpent * SampleManager.GetSampleLength(missionAudioSfx) / SampleManager.GetSampleBaseFrequency(missionAudioSfx); + m_nMissionAudioFramesToPlay = 11 * m_nMissionAudioFramesToPlay / 10; +#else + m_nMissionAudioFramesToPlay = m_nTimeSpent * SampleManager.GetStreamedFileLength(missionAudioSfx) / 1000; + m_nMissionAudioFramesToPlay *= 4; +#endif + m_bIsMissionAudioAllowedToPlay = FALSE; + m_bIsMissionAudio2D = TRUE; + g_bMissionAudioLoadFailed = FALSE; + } + } +} + +uint8 +cAudioManager::GetMissionAudioLoadingStatus() +{ + if (m_bIsInitialised) + return m_nMissionAudioLoadingStatus; + + return LOADING_STATUS_LOADED; +} + +void +cAudioManager::SetMissionAudioLocation(float x, float y, float z) +{ + if (m_bIsInitialised) { + m_bIsMissionAudio2D = FALSE; + m_vecMissionAudioPosition = CVector(x, y, z); + } +} + +void +cAudioManager::PlayLoadedMissionAudio() +{ + if (m_bIsInitialised && m_nMissionAudioSampleIndex != NO_SAMPLE && m_nMissionAudioLoadingStatus == LOADING_STATUS_LOADED && + m_nMissionAudioPlayStatus == PLAY_STATUS_STOPPED) + m_bIsMissionAudioAllowedToPlay = TRUE; +} + +bool8 +cAudioManager::IsMissionAudioSampleFinished() +{ + if (m_bIsInitialised) + return m_nMissionAudioPlayStatus == PLAY_STATUS_FINISHED; + + static uint32 cPretendFrame = 1; + + return (cPretendFrame++ & 63) == 0; +} + +void +cAudioManager::ClearMissionAudio() +{ + if (m_bIsInitialised) { + m_nMissionAudioSampleIndex = NO_SAMPLE; + m_nMissionAudioLoadingStatus = LOADING_STATUS_NOT_LOADED; + m_nMissionAudioPlayStatus = PLAY_STATUS_STOPPED; + m_bIsMissionAudioPlaying = FALSE; + m_bIsMissionAudioAllowedToPlay = FALSE; + m_bIsMissionAudio2D = TRUE; + m_nMissionAudioFramesToPlay = 0; + } +} + +void +cAudioManager::ProcessMissionAudio() +{ + float dist; + uint8 Vol; + uint8 pan; + float distSquared; + CVector vec; + + static uint8 nCheckPlayingDelay = 0; + static uint8 nFramesUntilFailedLoad = 0; + static uint8 nFramesForPretendPlaying = 0; + + if (m_bIsInitialised && m_nMissionAudioSampleIndex != NO_SAMPLE) { + switch (m_nMissionAudioLoadingStatus) { + case LOADING_STATUS_NOT_LOADED: +#ifdef GTA_PS2 + SampleManager.LoadPedComment(m_nMissionAudioSampleIndex); + m_nMissionAudioLoadingStatus = LOADING_STATUS_LOADING; +#else + SampleManager.PreloadStreamedFile(m_nMissionAudioSampleIndex, 1); + m_nMissionAudioLoadingStatus = LOADING_STATUS_LOADED; +#endif + nFramesUntilFailedLoad = 0; + break; + case LOADING_STATUS_LOADING: +#ifdef GTA_PS2 + if (SampleManager.IsPedCommentLoaded(m_nMissionAudioSampleIndex) == TRUE) + m_nMissionAudioLoadingStatus = LOADING_STATUS_LOADED; + // fallthrough + else +#endif + if (++nFramesUntilFailedLoad >= 90) { + nFramesForPretendPlaying = 0; + g_bMissionAudioLoadFailed = TRUE; + nFramesUntilFailedLoad = 0; + m_nMissionAudioLoadingStatus = LOADING_STATUS_LOADED; + return; + } +#ifdef GTA_PS2 + else { + // try loading again + SampleManager.LoadPedComment(m_nMissionAudioSampleIndex); + return; + } +#endif + case LOADING_STATUS_LOADED: + if (!m_bIsMissionAudioAllowedToPlay) + break; + if (g_bMissionAudioLoadFailed) { + if (m_bTimerJustReset) { + ClearMissionAudio(); +#ifdef GTA_PS2 + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); // why? + SampleManager.StopChannel(CHANNEL_MISSION_AUDIO); +#else + SampleManager.StopStreamedFile(1); +#endif + nFramesForPretendPlaying = 0; + nCheckPlayingDelay = 0; + nFramesUntilFailedLoad = 0; + } else if (!m_bIsPaused) { + if (++nFramesForPretendPlaying >= 120) { + m_nMissionAudioPlayStatus = PLAY_STATUS_FINISHED; + m_nMissionAudioSampleIndex = NO_SAMPLE; + } else + m_nMissionAudioPlayStatus = PLAY_STATUS_PLAYING; + } + break; + } + switch (m_nMissionAudioPlayStatus) { + case PLAY_STATUS_STOPPED: + if (MissionScriptAudioUsesPoliceChannel(m_nMissionAudioSampleIndex)) + SetMissionScriptPoliceAudio(m_nMissionAudioSampleIndex); + else { +#ifdef GTA_PS2 + SampleManager.InitialiseChannel(CHANNEL_MISSION_AUDIO, m_nMissionAudioSampleIndex, SFX_BANK_PED_COMMENTS); + if (m_bIsPaused) + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO, 0); + else + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO, SampleManager.GetSampleBaseFrequency(m_nMissionAudioSampleIndex)); +#else + if (m_bIsPaused) + SampleManager.PauseStream(TRUE, 1); +#endif + if (m_bIsMissionAudio2D) { +#ifdef GTA_PS2 + SampleManager.SetChannelVolume(CHANNEL_MISSION_AUDIO, MISSION_AUDIO_VOLUME); + SampleManager.SetChannelPan(CHANNEL_MISSION_AUDIO, 63); + SampleManager.SetChannelReverbFlag(CHANNEL_MISSION_AUDIO, FALSE); +#else + SampleManager.SetStreamedVolumeAndPan(MISSION_AUDIO_VOLUME, 63, TRUE, 1); +#endif + } else { + distSquared = GetDistanceSquared(m_vecMissionAudioPosition); + if (distSquared < SQR(MISSION_AUDIO_MAX_DIST)) { + dist = Sqrt(distSquared); + Vol = ComputeVolume(MISSION_AUDIO_VOLUME, MISSION_AUDIO_MAX_DIST, dist); + TranslateEntity(&m_vecMissionAudioPosition, &vec); + pan = ComputePan(MISSION_AUDIO_MAX_DIST, &vec); + } else { + Vol = 0; + pan = 63; + } +#ifdef GTA_PS2 + SampleManager.SetChannelVolume(CHANNEL_MISSION_AUDIO, Vol); + SampleManager.SetChannelPan(CHANNEL_MISSION_AUDIO, pan); + SampleManager.SetChannelReverbFlag(CHANNEL_MISSION_AUDIO, TRUE); +#else + SampleManager.SetStreamedVolumeAndPan(Vol, pan, TRUE, 1); +#endif + } +#ifdef GTA_PS2 + SampleManager.StartChannel(CHANNEL_MISSION_AUDIO); +#else + SampleManager.StartPreloadedStreamedFile(1); +#endif + } + m_nMissionAudioPlayStatus = PLAY_STATUS_PLAYING; + nCheckPlayingDelay = 30; + break; + case PLAY_STATUS_PLAYING: + if (m_bTimerJustReset) { + ClearMissionAudio(); +#ifdef GTA_PS2 + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); // why? + SampleManager.StopChannel(CHANNEL_MISSION_AUDIO); +#else + SampleManager.StopStreamedFile(1); +#endif + return; + } + if (MissionScriptAudioUsesPoliceChannel(m_nMissionAudioSampleIndex)) { + if (!m_bIsPaused) { + if (nCheckPlayingDelay > 0) { + nCheckPlayingDelay--; + } else if (GetMissionScriptPoliceAudioPlayingStatus() == PLAY_STATUS_FINISHED || m_nMissionAudioFramesToPlay-- == 0) { + m_nMissionAudioPlayStatus = PLAY_STATUS_FINISHED; + m_nMissionAudioSampleIndex = NO_SAMPLE; +#ifdef GTA_PS2 + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); +#else + SampleManager.StopStreamedFile(1); +#endif + m_nMissionAudioFramesToPlay = 0; + } + } + } else if (m_bIsMissionAudioPlaying) { +#ifdef GTA_PS2 + if (!SampleManager.GetChannelUsedFlag(CHANNEL_MISSION_AUDIO) && !m_bIsPaused && !m_bWasPaused) { +#else + if (!SampleManager.IsStreamPlaying(1) && !m_bIsPaused && !m_bWasPaused) { +#endif + m_nMissionAudioPlayStatus = PLAY_STATUS_FINISHED; + m_nMissionAudioSampleIndex = NO_SAMPLE; +#ifdef GTA_PS2 + SampleManager.StopChannel(CHANNEL_MISSION_AUDIO); +#else + SampleManager.StopStreamedFile(1); +#endif + m_nMissionAudioFramesToPlay = 0; + } else { +#ifdef GTA_PS2 + if (m_bIsPaused) + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO, 0); + else + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO, SampleManager.GetSampleBaseFrequency(m_nMissionAudioSampleIndex)); +#else + if (m_bIsPaused) + SampleManager.PauseStream(TRUE, 1); + else + SampleManager.PauseStream(FALSE, 1); +#endif + } + } else { + if (m_bIsPaused) + break; + if (nCheckPlayingDelay-- > 0) { +#ifdef GTA_PS2 + if (!SampleManager.GetChannelUsedFlag(CHANNEL_MISSION_AUDIO)) +#else + if (!SampleManager.IsStreamPlaying(1)) +#endif + break; + nCheckPlayingDelay = 0; + } + m_bIsMissionAudioPlaying = TRUE; + } + break; + default: + break; + } + break; + default: + return; + } + } +} +#pragma endregion All the mission audio stuff diff --git a/src/audio/AudioManager.cpp b/src/audio/AudioManager.cpp new file mode 100644 index 0000000..5d83269 --- /dev/null +++ b/src/audio/AudioManager.cpp @@ -0,0 +1,1245 @@ +#include "common.h" + +#include "AudioManager.h" +#include "audio_enums.h" + +#include "AudioScriptObject.h" +#include "MusicManager.h" +#include "Timer.h" +#include "DMAudio.h" +#include "sampman.h" +#include "Camera.h" +#include "World.h" +#include "Entity.h" + +cAudioManager AudioManager; + +#define SPEED_OF_SOUND 343.f +#ifdef GTA_PS2 +#define TIME_SPENT 40 +#else +#define TIME_SPENT 50 +#endif + +cAudioManager::cAudioManager() +{ + m_bIsInitialised = FALSE; + m_bIsSurround = TRUE; + m_fSpeedOfSound = SPEED_OF_SOUND / TIME_SPENT; + m_nTimeSpent = TIME_SPENT; + m_nActiveSamples = NUM_CHANNELS_GENERIC; + m_nActiveQueue = 1; + ClearRequestedQueue(); + m_nActiveQueue = 0; + ClearRequestedQueue(); + ClearActiveSamples(); + GenerateIntegerRandomNumberTable(); + m_bDoubleVolume = FALSE; +#ifdef AUDIO_REFLECTIONS + m_bDynamicAcousticModelingStatus = TRUE; +#endif + + for (uint32 i = 0; i < NUM_AUDIOENTITIES; i++) { + m_asAudioEntities[i].m_bIsUsed = FALSE; + m_aAudioEntityOrderList[i] = NUM_AUDIOENTITIES; + } + m_nAudioEntitiesCount = 0; + m_FrameCounter = 0; + m_bReduceReleasingPriority = FALSE; + m_bTimerJustReset = FALSE; + m_nTimer = 0; +} + +cAudioManager::~cAudioManager() +{ + if (m_bIsInitialised) + Terminate(); +} + +void +cAudioManager::Initialise() +{ + if (!m_bIsInitialised) { + PreInitialiseGameSpecificSetup(); + m_bIsInitialised = SampleManager.Initialise(); + if (m_bIsInitialised) { +#ifdef EXTERNAL_3D_SOUND + m_nActiveSamples = SampleManager.GetMaximumSupportedChannels(); + if (m_nActiveSamples <= 1) { + Terminate(); + } else { + m_nActiveSamples--; +#else + { + m_nActiveSamples = NUM_CHANNELS_GENERIC; +#endif + PostInitialiseGameSpecificSetup(); + InitialisePoliceRadioZones(); + InitialisePoliceRadio(); + MusicManager.Initialise(); + } + } + } +} + +void +cAudioManager::Terminate() +{ + if (m_bIsInitialised) { + MusicManager.Terminate(); + + for (uint32 i = 0; i < NUM_AUDIOENTITIES; i++) { + m_asAudioEntities[i].m_bIsUsed = FALSE; + m_aAudioEntityOrderList[i] = NUM_AUDIOENTITIES; + } + + m_nAudioEntitiesCount = 0; + m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal = 0; + PreTerminateGameSpecificShutdown(); + + for (uint32 i = 0; i < MAX_SFX_BANKS; i++) { + if (SampleManager.IsSampleBankLoaded(i)) + SampleManager.UnloadSampleBank(i); + } + + SampleManager.Terminate(); + + m_bIsInitialised = FALSE; + PostTerminateGameSpecificShutdown(); + } +} + +void +cAudioManager::Service() +{ + GenerateIntegerRandomNumberTable(); + if (m_bTimerJustReset) { + ResetAudioLogicTimers(m_nTimer); + MusicManager.ResetTimers(m_nTimer); + m_bTimerJustReset = FALSE; + } + if (m_bIsInitialised) { + m_bWasPaused = m_bIsPaused; + m_bIsPaused = CTimer::GetIsUserPaused(); +#ifdef AUDIO_REFLECTIONS + UpdateReflections(); +#endif + ServiceSoundEffects(); + MusicManager.Service(); + } +} + +int32 +cAudioManager::CreateEntity(eAudioType type, void *entity) +{ + if (!m_bIsInitialised) + return AEHANDLE_ERROR_NOAUDIOSYS; + if (!entity) + return AEHANDLE_ERROR_NOENTITY; + if (type >= TOTAL_AUDIO_TYPES) + return AEHANDLE_ERROR_BADAUDIOTYPE; + +#ifdef FIX_BUGS + // since sound could still play after entity deletion let's make sure we don't override one that is in use + // find all the free entity IDs that are being used by queued samples + int32 stillUsedEntities[NUM_CHANNELS_GENERIC * NUM_SOUND_QUEUES]; + uint32 stillUsedEntitiesCount = 0; + + for (uint8 i = 0; i < NUM_SOUND_QUEUES; i++) + for (uint8 j = 0; j < m_nRequestedCount[i]; j++) { + tSound &sound = m_aRequestedQueue[i][m_aRequestedOrderList[i][j]]; + if (sound.m_nEntityIndex < 0) continue; + if (!m_asAudioEntities[sound.m_nEntityIndex].m_bIsUsed) { + bool found = false; + for (uint8 k = 0; k < stillUsedEntitiesCount; k++) { + if (stillUsedEntities[k] == sound.m_nEntityIndex) { + found = true; + break; + } + } + if (!found) + stillUsedEntities[stillUsedEntitiesCount++] = sound.m_nEntityIndex; + } + } +#endif + + for (uint32 i = 0; i < NUM_AUDIOENTITIES; i++) { + if (!m_asAudioEntities[i].m_bIsUsed) { +#ifdef FIX_BUGS + // skip if ID is still used by queued sample + bool skip = false; + for (uint8 j = 0; j < stillUsedEntitiesCount; j++) { + if (stillUsedEntities[j] == i) { + //debug("audio entity %i still used, skipping\n", i); + skip = true; + break; + } + } + if (skip) + continue; +#endif + + m_asAudioEntities[i].m_bIsUsed = TRUE; + m_asAudioEntities[i].m_bStatus = FALSE; + m_asAudioEntities[i].m_nType = type; + m_asAudioEntities[i].m_pEntity = entity; + m_asAudioEntities[i].m_awAudioEvent[0] = SOUND_NO_SOUND; + m_asAudioEntities[i].m_awAudioEvent[1] = SOUND_NO_SOUND; + m_asAudioEntities[i].m_awAudioEvent[2] = SOUND_NO_SOUND; + m_asAudioEntities[i].m_awAudioEvent[3] = SOUND_NO_SOUND; + m_asAudioEntities[i].m_AudioEvents = 0; + m_aAudioEntityOrderList[m_nAudioEntitiesCount++] = i; + return i; + } + } + return AEHANDLE_ERROR_NOFREESLOT; +} + +void +cAudioManager::DestroyEntity(int32 id) +{ + if (m_bIsInitialised && id >= 0 && id < NUM_AUDIOENTITIES && m_asAudioEntities[id].m_bIsUsed) { + m_asAudioEntities[id].m_bIsUsed = FALSE; + for (uint32 i = 0; i < m_nAudioEntitiesCount; i++) { + if (id == m_aAudioEntityOrderList[i]) { + if (i < NUM_AUDIOENTITIES - 1) + memmove(&m_aAudioEntityOrderList[i], &m_aAudioEntityOrderList[i + 1], sizeof(uint32) * (m_nAudioEntitiesCount - (i + 1))); + m_aAudioEntityOrderList[--m_nAudioEntitiesCount] = NUM_AUDIOENTITIES; + return; + } + } + } +} + +bool8 +cAudioManager::GetEntityStatus(int32 id) +{ + if (m_bIsInitialised && id >= 0 && id < NUM_AUDIOENTITIES && m_asAudioEntities[id].m_bIsUsed) + return m_asAudioEntities[id].m_bStatus; + return FALSE; +} + +void +cAudioManager::SetEntityStatus(int32 id, bool8 status) +{ + if (m_bIsInitialised && id >= 0 && id < NUM_AUDIOENTITIES && m_asAudioEntities[id].m_bIsUsed) + m_asAudioEntities[id].m_bStatus = status; +} + +void * +cAudioManager::GetEntityPointer(int32 id) +{ + if (m_bIsInitialised && id >= 0 && id < NUM_AUDIOENTITIES && m_asAudioEntities[id].m_bIsUsed) + return m_asAudioEntities[id].m_pEntity; + return NULL; +} + +static Const uint8 OneShotPriority[] = { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 5, 5, 3, 5, 2, 2, 1, 1, 3, 1, 3, 3, 1, 1, 1, 4, 4, 3, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 3, 2, 2, 2, 2, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 3, 1, 1, 1, 9, + 2, 2, 0, 0, 0, 0, 3, 3, 5, 1, 1, 1, 1, 3, 4, 7, 6, 6, 6, 6, 1, 3, 4, 3, 4, 2, 1, 3, 5, 4, 6, 6, 1, 3, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + +void +cAudioManager::PlayOneShot(int32 index, uint16 sound, float vol) +{ + if (m_bIsInitialised) { + if (index >= 0 && index < NUM_AUDIOENTITIES) { + tAudioEntity &entity = m_asAudioEntities[index]; + if (entity.m_bIsUsed) { + if (sound < SOUND_TOTAL_SOUNDS) { + if (entity.m_nType == AUDIOTYPE_SCRIPTOBJECT) { + if (m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal < ARRAY_SIZE(m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices)) { + entity.m_awAudioEvent[0] = sound; + entity.m_AudioEvents = 1; + m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices[m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal++] = index; + } + } else { + int32 i = 0; + while (TRUE) { + if (i >= entity.m_AudioEvents) { + if (entity.m_AudioEvents < NUM_AUDIOENTITY_EVENTS) { + entity.m_awAudioEvent[i] = sound; + entity.m_afVolume[i] = vol; + entity.m_AudioEvents++; + } + return; + } + if (OneShotPriority[entity.m_awAudioEvent[i]] > OneShotPriority[sound]) + break; + i++; + } + if (i < NUM_AUDIOENTITY_EVENTS - 1) { + memmove(&entity.m_awAudioEvent[i + 1], &entity.m_awAudioEvent[i], (NUM_AUDIOENTITY_EVENTS - 1 - i) * sizeof(int16)); + memmove(&entity.m_afVolume[i + 1], &entity.m_afVolume[i], (NUM_AUDIOENTITY_EVENTS - 1 - i) * sizeof(float)); + } + entity.m_awAudioEvent[i] = sound; + entity.m_afVolume[i] = vol; + if (entity.m_AudioEvents < NUM_AUDIOENTITY_EVENTS) + entity.m_AudioEvents++; + } + } + } + } + } +} + +void +cAudioManager::SetEffectsMasterVolume(uint8 volume) +{ + SampleManager.SetEffectsMasterVolume(volume); +} + +void +cAudioManager::SetMusicMasterVolume(uint8 volume) +{ + SampleManager.SetMusicMasterVolume(volume); +} + +void +cAudioManager::SetEffectsFadeVol(uint8 volume) +{ + SampleManager.SetEffectsFadeVolume(volume); +} + +void +cAudioManager::SetMusicFadeVol(uint8 volume) +{ + SampleManager.SetMusicFadeVolume(volume); +} + +void +cAudioManager::SetMonoMode(bool8 mono) +{ + SampleManager.SetMonoMode(mono); +} + +void +cAudioManager::ResetTimers(uint32 time) +{ + if (m_bIsInitialised) { + m_bTimerJustReset = TRUE; + m_nTimer = time; + ClearRequestedQueue(); + if (m_nActiveQueue) { + m_nActiveQueue = 0; + ClearRequestedQueue(); + m_nActiveQueue = 1; + } else { + m_nActiveQueue = 1; + ClearRequestedQueue(); + m_nActiveQueue = 0; + } + ClearActiveSamples(); + ClearMissionAudio(); + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); + SampleManager.SetEffectsFadeVolume(0); + SampleManager.SetMusicFadeVolume(0); + MusicManager.ResetMusicAfterReload(); +#ifdef AUDIO_OAL + SampleManager.Service(); +#endif + } +} + +void +cAudioManager::DestroyAllGameCreatedEntities() +{ + cAudioScriptObject *entity; + + if (m_bIsInitialised) { + for (uint32 i = 0; i < NUM_AUDIOENTITIES; i++) { + if (m_asAudioEntities[i].m_bIsUsed) { + switch (m_asAudioEntities[i].m_nType) { + case AUDIOTYPE_PHYSICAL: + case AUDIOTYPE_EXPLOSION: + case AUDIOTYPE_WEATHER: + case AUDIOTYPE_CRANE: + case AUDIOTYPE_GARAGE: + case AUDIOTYPE_FIREHYDRANT: + DestroyEntity(i); + break; + case AUDIOTYPE_SCRIPTOBJECT: + entity = (cAudioScriptObject *)m_asAudioEntities[i].m_pEntity; + if (entity) { + delete entity; + m_asAudioEntities[i].m_pEntity = nil; + } + DestroyEntity(i); + break; + default: + break; + } + } + } + m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal = 0; + } +} + +#ifdef GTA_PC + +uint8 +cAudioManager::GetNum3DProvidersAvailable() +{ +#ifdef EXTERNAL_3D_SOUND + if (m_bIsInitialised) + return SampleManager.GetNum3DProvidersAvailable(); +#endif + return 0; +} + +char * +cAudioManager::Get3DProviderName(uint8 id) +{ +#ifndef EXTERNAL_3D_SOUND + return nil; +#else + if (!m_bIsInitialised) + return nil; +#ifdef AUDIO_OAL + id = Clamp(id, 0, SampleManager.GetNum3DProvidersAvailable() - 1); +#else + // We don't want that either since it will crash the game, but skipping for now + if (id >= SampleManager.GetNum3DProvidersAvailable()) + return nil; +#endif + return SampleManager.Get3DProviderName(id); +#endif +} + +int8 +cAudioManager::GetCurrent3DProviderIndex() +{ +#ifdef EXTERNAL_3D_SOUND + if (m_bIsInitialised) + return SampleManager.GetCurrent3DProviderIndex(); +#endif + + return -1; +} + +int8 +cAudioManager::SetCurrent3DProvider(uint8 which) +{ +#ifndef EXTERNAL_3D_SOUND + return -1; +#else + if (!m_bIsInitialised) + return -1; + for (uint8 i = 0; i < m_nActiveSamples + 1; i++) + SampleManager.StopChannel(i); + ClearRequestedQueue(); + if (m_nActiveQueue == 0) + m_nActiveQueue = 1; + else + m_nActiveQueue = 0; + ClearRequestedQueue(); + ClearActiveSamples(); + int8 current = SampleManager.SetCurrent3DProvider(which); + if (current > 0) { +#ifdef EXTERNAL_3D_SOUND + m_nActiveSamples = SampleManager.GetMaximumSupportedChannels(); + if (m_nActiveSamples > 1) + m_nActiveSamples--; +#endif + } + return current; +#endif +} + +void +cAudioManager::SetSpeakerConfig(int32 conf) +{ +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetSpeakerConfig(conf); +#endif +} + +bool8 +cAudioManager::IsMP3RadioChannelAvailable() +{ + if (m_bIsInitialised) + return SampleManager.IsMP3RadioChannelAvailable(); + + return FALSE; +} + +void +cAudioManager::ReleaseDigitalHandle() +{ + if (m_bIsInitialised) + SampleManager.ReleaseDigitalHandle(); +} + +void +cAudioManager::ReacquireDigitalHandle() +{ + if (m_bIsInitialised) + SampleManager.ReacquireDigitalHandle(); +} + +#ifdef AUDIO_REFLECTIONS +void +cAudioManager::SetDynamicAcousticModelingStatus(bool8 status) +{ + m_bDynamicAcousticModelingStatus = status; +} +#endif + +bool8 +cAudioManager::CheckForAnAudioFileOnCD() +{ + return SampleManager.CheckForAnAudioFileOnCD(); +} + +char +cAudioManager::GetCDAudioDriveLetter() +{ + if (m_bIsInitialised) + return SampleManager.GetCDAudioDriveLetter(); + + return '\0'; +} + +bool8 +cAudioManager::IsAudioInitialised() +{ + return m_bIsInitialised; +} + +#endif // GTA_PC + +void +cAudioManager::ServiceSoundEffects() +{ +#ifdef FIX_BUGS + if(CTimer::GetLogicalFramesPassed() != 0) +#endif + m_bReduceReleasingPriority = (m_FrameCounter++ % 5) == 0; + if (m_bIsPaused && !m_bWasPaused) { + for (int32 i = 0; i < NUM_CHANNELS; i++) + SampleManager.StopChannel(i); + + ClearRequestedQueue(); + if (m_nActiveQueue) { + m_nActiveQueue = 0; + ClearRequestedQueue(); + m_nActiveQueue = 1; + } else { + m_nActiveQueue = 1; + ClearRequestedQueue(); + m_nActiveQueue = 0; + } + ClearActiveSamples(); + } + m_nActiveQueue = m_nActiveQueue == 1 ? 0 : 1; + ProcessReverb(); + ProcessSpecial(); + ClearRequestedQueue(); + InterrogateAudioEntities(); + m_sPedComments.Process(); + ServicePoliceRadio(); + ServiceCollisions(); + AddReleasingSounds(); + ProcessMissionAudio(); +#ifdef EXTERNAL_3D_SOUND + AdjustSamplesVolume(); +#endif + ProcessActiveQueues(); +#ifdef AUDIO_OAL + SampleManager.Service(); +#endif + for (int32 i = 0; i < m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal; i++) { + cAudioScriptObject *object = (cAudioScriptObject *)m_asAudioEntities[m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices[i]].m_pEntity; + delete object; + m_asAudioEntities[m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices[i]].m_pEntity = nil; + DestroyEntity(m_sAudioScriptObjectManager.m_anScriptObjectEntityIndices[i]); + } + m_sAudioScriptObjectManager.m_nScriptObjectEntityTotal = 0; +} + +uint32 +cAudioManager::FL(float f) +{ + return SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex) * f; +} + +uint8 +cAudioManager::ComputeVolume(uint8 emittingVolume, float maxDistance, float distance) +{ + float minDistance; + if (maxDistance <= 0.0f) + return 0; + minDistance = maxDistance / 5.0f; + if (minDistance <= distance) + emittingVolume = sq((maxDistance - minDistance - (distance - minDistance)) / (maxDistance - minDistance)) * emittingVolume; + return emittingVolume; +} + +void +cAudioManager::TranslateEntity(Const CVector *in, CVector *out) +{ + *out = MultiplyInverse(TheCamera.GetMatrix(), *in); +} + +int32 +cAudioManager::ComputePan(float dist, CVector *vec) +{ + Const static uint8 PanTable[64] = {0, 3, 8, 12, 16, 19, 22, 24, 26, 28, 30, 31, 33, 34, 36, 37, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 49, 50, 51, 52, 53, 53, + 54, 55, 55, 56, 56, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63}; + + int32 index = vec->x / (dist / 64.0f); + index = Min(63, ABS(index)); + + if (vec->x > 0.f) + return Max(20, 63 - (int8)PanTable[index]); + return Min(107, PanTable[index] + 63); +} + +uint32 +cAudioManager::ComputeDopplerEffectedFrequency(uint32 oldFreq, float position1, float position2, float speedMultiplier) +{ + uint32 newFreq = oldFreq; + if (!TheCamera.Get_Just_Switched_Status() && speedMultiplier != 0.0f) { + float dist = position2 - position1; + if (dist != 0.0f) { + float speedOfSource = (dist / m_nTimeSpent) * speedMultiplier; + if (m_fSpeedOfSound > Abs(speedOfSource)) { + speedOfSource = Clamp2(speedOfSource, 0.0f, 1.5f); + newFreq = (oldFreq * m_fSpeedOfSound) / (speedOfSource + m_fSpeedOfSound); + } + } + } + return newFreq; +} + +int32 +cAudioManager::RandomDisplacement(uint32 seed) +{ + int32 value; + + static bool8 bPos = TRUE; + static uint32 Adjustment = 0; + + if (seed == 0) + return 0; + + value = m_anRandomTable[(Adjustment + seed) % 5] % seed; + Adjustment += value; + + if (value % 2) + bPos = !bPos; + + if (!bPos) + value = -value; + return value; +} + +void +cAudioManager::InterrogateAudioEntities() +{ + for (uint32 i = 0; i < m_nAudioEntitiesCount; i++) { + ProcessEntity(m_aAudioEntityOrderList[i]); + m_asAudioEntities[m_aAudioEntityOrderList[i]].m_AudioEvents = 0; + } +} + +void +cAudioManager::AddSampleToRequestedQueue() +{ + uint32 finalPriority; + uint8 sampleIndex; +#ifdef AUDIO_REFLECTIONS + bool8 bReflections; +#endif + + if (m_sQueueSample.m_nSampleIndex < TOTAL_AUDIO_SAMPLES) { + finalPriority = m_sQueueSample.m_nPriority * (MAX_VOLUME - m_sQueueSample.m_nVolume); + sampleIndex = m_nRequestedCount[m_nActiveQueue]; + if (sampleIndex >= m_nActiveSamples) { + sampleIndex = m_aRequestedOrderList[m_nActiveQueue][m_nActiveSamples - 1]; + if (m_aRequestedQueue[m_nActiveQueue][sampleIndex].m_nFinalPriority <= finalPriority) + return; + } else + m_nRequestedCount[m_nActiveQueue]++; +#if GTA_VERSION < GTA3_PC_10 + if (m_sQueueSample.m_bStatic) { + if (m_sQueueSample.m_nLoopCount > 0) + m_sQueueSample.unk = m_nTimeSpent * SampleManager.GetSampleLength(m_sQueueSample.m_nSampleIndex) / m_sQueueSample.m_nFrequency; + else + m_sQueueSample.unk = -3; + } +#endif + m_sQueueSample.m_nFinalPriority = finalPriority; + m_sQueueSample.m_bIsPlayingFinished = FALSE; +#ifdef AUDIO_REFLECTIONS + if (m_sQueueSample.m_bIs2D) { + m_sQueueSample.m_bReflections = FALSE; + m_sQueueSample.m_nReflectionDelay = 0; + } + if (m_bDynamicAcousticModelingStatus && m_sQueueSample.m_nLoopCount > 0) { + bReflections = m_sQueueSample.m_bReflections; + } else { + bReflections = FALSE; + m_sQueueSample.m_nReflectionDelay = 0; + } + m_sQueueSample.m_bReflections = FALSE; + + if (!m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReverb = FALSE; +#endif + + m_aRequestedQueue[m_nActiveQueue][sampleIndex] = m_sQueueSample; + + AddDetailsToRequestedOrderList(sampleIndex); +#ifdef AUDIO_REFLECTIONS + if (bReflections) + AddReflectionsToRequestedQueue(); +#endif + } +} + +void +cAudioManager::AddDetailsToRequestedOrderList(uint8 sample) +{ + uint32 i = 0; + if (sample != 0) { + for (; i < sample; i++) { + if (m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][i]].m_nFinalPriority > + m_aRequestedQueue[m_nActiveQueue][sample].m_nFinalPriority) + break; + } + if (i < sample) + memmove(&m_aRequestedOrderList[m_nActiveQueue][i + 1], &m_aRequestedOrderList[m_nActiveQueue][i], m_nActiveSamples - i - 1); + } + m_aRequestedOrderList[m_nActiveQueue][i] = sample; +} + +#ifdef AUDIO_REFLECTIONS +void +cAudioManager::AddReflectionsToRequestedQueue() +{ + float reflectionDistance; + int32 noise; + uint32 oldCounter = m_sQueueSample.m_nCounter; + uint8 emittingVolume = (m_sQueueSample.m_nVolume >> 1) + (m_sQueueSample.m_nVolume >> 3); + + for (uint32 i = 0; i < ARRAY_SIZE(m_afReflectionsDistances); i++) { + reflectionDistance = m_afReflectionsDistances[i]; + if (reflectionDistance > 0.0f && reflectionDistance < 100.0f && reflectionDistance < m_sQueueSample.m_MaxDistance) { + m_sQueueSample.m_nReflectionDelay = (reflectionDistance * 500.0f / 1029.0f); + if (m_sQueueSample.m_nReflectionDelay > 5) { + m_sQueueSample.m_fDistance = m_afReflectionsDistances[i]; + SET_EMITTING_VOLUME(emittingVolume); + m_sQueueSample.m_nVolume = ComputeVolume(emittingVolume, m_sQueueSample.m_MaxDistance, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > emittingVolume >> 4) { + m_sQueueSample.m_nCounter = oldCounter + ((i + 1) << 8); + if (m_sQueueSample.m_nLoopCount > 0) { + noise = RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + if (noise > 0) + m_sQueueSample.m_nFrequency -= noise; + else + m_sQueueSample.m_nFrequency += noise; + } + m_sQueueSample.m_nPriority += 20; + m_sQueueSample.m_vecPos = m_avecReflectionsPos[i]; + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::UpdateReflections() +{ + CVector camPos; + CColPoint colpoint; + CEntity *ent; + + if (m_FrameCounter % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[0] = camPos; + m_avecReflectionsPos[0].y += 50.0f; + if (CWorld::ProcessLineOfSight(camPos, m_avecReflectionsPos[0], colpoint, ent, true, false, false, true, false, true, true)) + m_afReflectionsDistances[0] = Distance(camPos, colpoint.point); + else + m_afReflectionsDistances[0] = 50.0f; + } else if ((m_FrameCounter + 1) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[1] = camPos; + m_avecReflectionsPos[1].y -= 50.0f; + if (CWorld::ProcessLineOfSight(camPos, m_avecReflectionsPos[1], colpoint, ent, true, false, false, true, false, true, true)) + m_afReflectionsDistances[1] = Distance(camPos, colpoint.point); + else + m_afReflectionsDistances[1] = 50.0f; + } else if ((m_FrameCounter + 2) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[2] = camPos; + m_avecReflectionsPos[2].x -= 50.0f; + if (CWorld::ProcessLineOfSight(camPos, m_avecReflectionsPos[2], colpoint, ent, true, false, false, true, false, true, true)) + m_afReflectionsDistances[2] = Distance(camPos, colpoint.point); + else + m_afReflectionsDistances[2] = 50.0f; + } else if ((m_FrameCounter + 3) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[3] = camPos; + m_avecReflectionsPos[3].x += 50.0f; + if (CWorld::ProcessLineOfSight(camPos, m_avecReflectionsPos[3], colpoint, ent, true, false, false, true, false, true, true)) + m_afReflectionsDistances[3] = Distance(camPos, colpoint.point); + else + m_afReflectionsDistances[3] = 50.0f; + } else if ((m_FrameCounter + 4) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[4] = camPos; + m_avecReflectionsPos[4].z += 50.0f; + if (CWorld::ProcessVerticalLine(camPos, m_avecReflectionsPos[4].z, colpoint, ent, true, false, false, false, true, false, nil)) + m_afReflectionsDistances[4] = colpoint.point.z - camPos.z; + else + m_afReflectionsDistances[4] = 50.0f; + } +} +#endif // AUDIO_REFLECTIONS + +void +cAudioManager::AddReleasingSounds() +{ + // in case someone would want to increase it +#ifdef FIX_BUGS + bool8 toProcess[NUM_CHANNELS_GENERIC]; +#else + bool8 toProcess[44]; +#endif + + uint8 queue = m_nActiveQueue == 0 ? 1 : 0; + + for (uint8 i = 0; i < m_nRequestedCount[queue]; i++) { + tSound &sample = m_aRequestedQueue[queue][m_aRequestedOrderList[queue][i]]; + if (sample.m_bIsPlayingFinished) + continue; + + toProcess[i] = FALSE; + for (uint8 j = 0; j < m_nRequestedCount[m_nActiveQueue]; j++) { + if (sample.m_nEntityIndex == m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][j]].m_nEntityIndex && + sample.m_nCounter == m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][j]].m_nCounter) { + toProcess[i] = TRUE; + break; + } + } + if (!toProcess[i]) { +#ifdef AUDIO_REFLECTIONS + if (sample.m_nCounter <= 255 || sample.m_nReflectionDelay == 0) // check if not delayed reflection +#endif + { +#ifdef ATTACH_RELEASING_SOUNDS_TO_ENTITIES + if (sample.m_nCounter <= 255 && !sample.m_bIs2D) { // check if not reflection and is a 3D sound + CEntity* entity = (CEntity*)GetEntityPointer(sample.m_nEntityIndex); + if (entity && m_asAudioEntities[sample.m_nEntityIndex].m_nType == AUDIOTYPE_PHYSICAL) { + sample.m_vecPos = entity->GetPosition(); + float oldDistance = sample.m_fDistance; + sample.m_fDistance = Sqrt(GetDistanceSquared(sample.m_vecPos)); + if (sample.m_nSampleIndex >= SAMPLEBANK_PED_START && sample.m_nSampleIndex <= SAMPLEBANK_PED_END) { // check if it's ped comment + uint8 vol; + if (CWorld::GetIsLineOfSightClear(TheCamera.GetPosition(), sample.m_vecPos, true, false, false, false, false, false)) + vol = PED_COMMENT_VOLUME; + else + vol = PED_COMMENT_VOLUME_BEHIND_WALL; +#ifdef EXTERNAL_3D_SOUND + sample.m_nEmittingVolume = vol; +#endif + sample.m_nVolume = ComputeVolume(vol, sample.m_MaxDistance, sample.m_fDistance); + } else { + // calculate new volume with changed distance + float volumeDiff = sq((sample.m_MaxDistance - sample.m_fDistance) / (sample.m_MaxDistance - oldDistance)); + if (volumeDiff > 0.0f) { + uint8 newVolume = volumeDiff * sample.m_nVolume; + if (sample.m_nVolumeChange > 0) + sample.m_nVolumeChange = volumeDiff * sample.m_nVolumeChange; +#if defined(FIX_BUGS) && defined(EXTERNAL_3D_SOUND) + if (sample.m_nEmittingVolumeChange > 0) + sample.m_nEmittingVolumeChange = volumeDiff * sample.m_nEmittingVolumeChange; +#endif + sample.m_nVolume = Min(MAX_VOLUME, newVolume); + } + } + if (sample.m_nVolume == 0) + sample.m_nFramesToPlay = 0; + } + } +#endif +#ifdef FIX_BUGS + // fixing emitting volume not being lowered and high fps bugs + if (sample.m_nFramesToPlay <= 0) + continue; + if (sample.m_nLoopCount == 0) { + if (sample.m_nVolumeChange == -1) { + sample.m_nVolumeChange = sample.m_nVolume / sample.m_nFramesToPlay; + if (sample.m_nVolumeChange <= 0) + sample.m_nVolumeChange = 1; +#ifdef EXTERNAL_3D_SOUND + sample.m_nEmittingVolumeChange = sample.m_nEmittingVolume / sample.m_nFramesToPlay; + if (sample.m_nEmittingVolumeChange <= 0) + sample.m_nEmittingVolumeChange = 1; +#endif + } + if (sample.m_nVolume <= sample.m_nVolumeChange * CTimer::GetTimeStepFix()) { + sample.m_nFramesToPlay = 0; + continue; + } + sample.m_nVolume -= sample.m_nVolumeChange * CTimer::GetTimeStepFix(); +#ifdef EXTERNAL_3D_SOUND + if (sample.m_nEmittingVolume <= sample.m_nEmittingVolumeChange * CTimer::GetTimeStepFix()) { + sample.m_nFramesToPlay = 0; + continue; + } + sample.m_nEmittingVolume -= sample.m_nEmittingVolumeChange * CTimer::GetTimeStepFix(); +#endif + } + sample.m_nFramesToPlay -= CTimer::GetTimeStepFix(); + if (sample.m_nFramesToPlay < 0) + sample.m_nFramesToPlay = 0; +#else + if (sample.m_nFramesToPlay == 0) + continue; + if (sample.m_nLoopCount == 0) { + if (sample.m_nVolumeChange == -1) { + sample.m_nVolumeChange = sample.m_nVolume / sample.m_nFramesToPlay; + if (sample.m_nVolumeChange <= 0) + sample.m_nVolumeChange = 1; + } + if (sample.m_nVolume <= sample.m_nVolumeChange) { + sample.m_nFramesToPlay = 0; + continue; + } + sample.m_nVolume -= sample.m_nVolumeChange; + } + sample.m_nFramesToPlay--; +#endif + if (m_bReduceReleasingPriority) { + if (sample.m_nPriority < 20) + sample.m_nPriority++; + } + sample.m_bStatic = FALSE; + } + memcpy(&m_sQueueSample, &sample, sizeof(tSound)); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessActiveQueues() +{ + bool8 flag; + float position2; + float position1; + + uint32 samplesPerFrame; + uint32 samplesToPlay; + +#ifdef EXTERNAL_3D_SOUND + float x; + float usedX; + float usedY; + float usedZ; +#endif + + uint8 vol; + uint8 emittingVol; + CVector position; + +#ifdef EXTERNAL_3D_SOUND + #define WORKING_VOLUME_FIELD m_nEmittingVolume +#else + #define WORKING_VOLUME_FIELD m_nVolume +#endif + +#ifdef USE_TIME_SCALE_FOR_AUDIO + float timeScale = m_bIsPaused ? 1.0f : CTimer::GetTimeScale(); +#endif + + for (uint8 i = 0; i < m_nActiveSamples; i++) { + m_aRequestedQueue[m_nActiveQueue][i].m_bIsBeingPlayed = FALSE; + m_asActiveSamples[i].m_bIsBeingPlayed = FALSE; + } + + for (uint8 i = 0; i < m_nRequestedCount[m_nActiveQueue]; i++) { + tSound &sample = m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][i]]; + if (sample.m_nSampleIndex != NO_SAMPLE) { + for (uint8 j = 0; j < m_nActiveSamples; j++) { + if (sample.m_nEntityIndex == m_asActiveSamples[j].m_nEntityIndex && sample.m_nCounter == m_asActiveSamples[j].m_nCounter && + sample.m_nSampleIndex == m_asActiveSamples[j].m_nSampleIndex) { + if (sample.m_nLoopCount > 0) { +#if GTA_VERSION >= GTA3_PC_10 + if (m_FrameCounter & 1) + flag = !!(j & 1); + else + flag = !(j & 1); + if (flag && !SampleManager.GetChannelUsedFlag(j)) { +#else + if (m_asActiveSamples[j].unk != 0) + m_asActiveSamples[j].unk--; + else if (SampleManager.GetChannelUsedFlag(j)) + m_asActiveSamples[j].unk = m_nTimeSpent * SampleManager.GetSampleLength(m_asActiveSamples[j].m_nSampleIndex) / m_asActiveSamples[j].m_nFrequency; + else { +#endif + sample.m_bIsPlayingFinished = TRUE; + m_asActiveSamples[j].m_bIsPlayingFinished = TRUE; + m_asActiveSamples[j].m_nSampleIndex = NO_SAMPLE; + m_asActiveSamples[j].m_nEntityIndex = AEHANDLE_NONE; + continue; + } + } + sample.m_bIsBeingPlayed = TRUE; + m_asActiveSamples[j].m_bIsBeingPlayed = TRUE; + sample.m_nVolumeChange = -1; + if (!sample.m_bStatic) { + if (sample.m_bIs2D) { + emittingVol = m_bDoubleVolume ? 2 * Min(63, sample.WORKING_VOLUME_FIELD) : sample.WORKING_VOLUME_FIELD; +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(j, sample.m_nFrequency * timeScale); +#else + SampleManager.SetChannelFrequency(j, sample.m_nFrequency); +#endif +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannelEmittingVolume(j, emittingVol); +#else + SampleManager.SetChannelPan(j, sample.m_nPan); + SampleManager.SetChannelVolume(j, sample.m_nVolume); +#endif + } else { + position2 = sample.m_fDistance; + position1 = m_asActiveSamples[j].m_fDistance; + m_asActiveSamples[j].m_fDistance = sample.m_fDistance; + sample.m_nFrequency = ComputeDopplerEffectedFrequency(sample.m_nFrequency, position1, position2, sample.m_fSpeedMultiplier); + if (sample.m_nFrequency != m_asActiveSamples[j].m_nFrequency) { + uint32 freq = Clamp2((int32)sample.m_nFrequency, (int32)m_asActiveSamples[j].m_nFrequency, 6000); + m_asActiveSamples[j].m_nFrequency = freq; +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(j, freq * timeScale); +#else + SampleManager.SetChannelFrequency(j, freq); +#endif + } + +#ifdef EXTERNAL_3D_SOUND + if (sample.m_nEmittingVolume != m_asActiveSamples[j].m_nEmittingVolume) { + vol = Clamp2((int8)sample.m_nEmittingVolume, (int8)m_asActiveSamples[j].m_nEmittingVolume, 10); + SampleManager.SetChannelEmittingVolume(j, m_bDoubleVolume ? 2 * Min(63, vol) : vol); + m_asActiveSamples[j].m_nEmittingVolume = vol; + } +#else + if (sample.m_nVolume != m_asActiveSamples[j].m_nVolume) { + vol = Clamp2((int8)sample.m_nVolume, (int8)m_asActiveSamples[j].m_nVolume, 10); + m_asActiveSamples[j].m_nVolume = vol; + } + SampleManager.SetChannelVolume(j, m_bDoubleVolume ? 2 * Min(63, m_asActiveSamples[j].m_nVolume) : m_asActiveSamples[j].m_nVolume); +#endif + TranslateEntity(&sample.m_vecPos, &position); +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannel3DPosition(j, position.x, position.y, position.z); + SampleManager.SetChannel3DDistances(j, sample.m_MaxDistance, 0.25f * sample.m_MaxDistance); +#else + sample.m_nPan = ComputePan(sample.m_fDistance, &position); + SampleManager.SetChannelPan(j, sample.m_nPan); +#endif + } + SampleManager.SetChannelReverbFlag(j, sample.m_bReverb); + break; + } + sample.m_bIsBeingPlayed = FALSE; + m_asActiveSamples[j].m_bIsBeingPlayed = FALSE; + } + } + } + } + for (uint8 i = 0; i < m_nActiveSamples; i++) { + if (m_asActiveSamples[i].m_nSampleIndex != NO_SAMPLE && !m_asActiveSamples[i].m_bIsBeingPlayed) { + SampleManager.StopChannel(i); + m_asActiveSamples[i].m_nSampleIndex = NO_SAMPLE; + m_asActiveSamples[i].m_nEntityIndex = AEHANDLE_NONE; + } + } + for (uint8 i = 0; i < m_nRequestedCount[m_nActiveQueue]; i++) { + tSound &sample = m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][i]]; + if (!sample.m_bIsBeingPlayed && !sample.m_bIsPlayingFinished && m_asAudioEntities[sample.m_nEntityIndex].m_bIsUsed && sample.m_nSampleIndex < NO_SAMPLE) { +#ifdef AUDIO_REFLECTIONS + if (sample.m_nCounter > 255 && sample.m_nLoopCount > 0 && sample.m_nReflectionDelay > 0) { // check if reflection + sample.m_nReflectionDelay--; + sample.m_nFramesToPlay = 1; + } else +#endif + { + for (uint8 j = 0; j < m_nActiveSamples; j++) { + if (!m_asActiveSamples[j].m_bIsBeingPlayed) { + if (sample.m_nLoopCount > 0) { + samplesPerFrame = sample.m_nFrequency / m_nTimeSpent; + samplesToPlay = sample.m_nLoopCount * SampleManager.GetSampleLength(sample.m_nSampleIndex); + if (samplesPerFrame == 0) + continue; + sample.m_nFramesToPlay = samplesToPlay / samplesPerFrame + 1; + } + memcpy(&m_asActiveSamples[j], &sample, sizeof(tSound)); + if (!m_asActiveSamples[j].m_bIs2D) { + TranslateEntity(&m_asActiveSamples[j].m_vecPos, &position); +#ifndef EXTERNAL_3D_SOUND + m_asActiveSamples[j].m_nPan = ComputePan(m_asActiveSamples[j].m_fDistance, &position); +#endif + } + emittingVol = m_bDoubleVolume ? 2 * Min(63, m_asActiveSamples[j].WORKING_VOLUME_FIELD) : m_asActiveSamples[j].WORKING_VOLUME_FIELD; +#ifdef GTA_PS2 + { + SampleManager.InitialiseChannel(j, m_asActiveSamples[j].m_nSampleIndex, m_asActiveSamples[j].m_nBankIndex); +#else + if (SampleManager.InitialiseChannel(j, m_asActiveSamples[j].m_nSampleIndex, m_asActiveSamples[j].m_nBankIndex)) { +#endif +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(j, m_asActiveSamples[j].m_nFrequency * timeScale); +#else + SampleManager.SetChannelFrequency(j, m_asActiveSamples[j].m_nFrequency); +#endif +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannelEmittingVolume(j, emittingVol); +#else + SampleManager.SetChannelVolume(j, emittingVol); + SampleManager.SetChannelPan(j, m_asActiveSamples[j].m_nPan); +#endif +#ifndef GTA_PS2 + SampleManager.SetChannelLoopPoints(j, m_asActiveSamples[j].m_nLoopStart, m_asActiveSamples[j].m_nLoopEnd); +#endif + SampleManager.SetChannelLoopCount(j, m_asActiveSamples[j].m_nLoopCount); + SampleManager.SetChannelReverbFlag(j, m_asActiveSamples[j].m_bReverb); +#ifdef EXTERNAL_3D_SOUND + if (m_asActiveSamples[j].m_bIs2D) { + uint8 offset = m_asActiveSamples[j].m_nPan; + if (offset == 63) + x = 0.f; + else if (offset >= 63) + x = (offset - 63) * 1000.0f / 63; + else + x = -(63 - offset) * 1000.0f / 63; + usedX = x; + usedY = 0.0f; + usedZ = 0.0f; + m_asActiveSamples[j].m_MaxDistance = 100000.0f; + } else { + usedX = position.x; + usedY = position.y; + usedZ = position.z; + } + SampleManager.SetChannel3DPosition(j, usedX, usedY, usedZ); + SampleManager.SetChannel3DDistances(j, m_asActiveSamples[j].m_MaxDistance, 0.25f * m_asActiveSamples[j].m_MaxDistance); +#endif + SampleManager.StartChannel(j); + } + m_asActiveSamples[j].m_bIsBeingPlayed = TRUE; + sample.m_bIsBeingPlayed = TRUE; + sample.m_nVolumeChange = -1; + break; + } + } + } + } + } + +#ifdef USE_TIME_SCALE_FOR_AUDIO + for (uint8 i = 0; i < m_nActiveSamples; i++) { + if (m_asActiveSamples[i].m_nSampleIndex != NO_SAMPLE && m_asActiveSamples[i].m_bIsBeingPlayed) + SampleManager.SetChannelFrequency(i, m_asActiveSamples[i].m_nFrequency * timeScale); + } +#endif + + #undef WORKING_VOLUME_FIELD +} + +void +cAudioManager::ClearRequestedQueue() +{ + for (uint8 i = 0; i < m_nActiveSamples; i++) + m_aRequestedOrderList[m_nActiveQueue][i] = m_nActiveSamples; + m_nRequestedCount[m_nActiveQueue] = 0; +} + +void +cAudioManager::ClearActiveSamples() +{ + for (uint8 i = 0; i < m_nActiveSamples; i++) { + m_asActiveSamples[i].m_nEntityIndex = AEHANDLE_NONE; + m_asActiveSamples[i].m_nCounter = 0; + m_asActiveSamples[i].m_nSampleIndex = NO_SAMPLE; + m_asActiveSamples[i].m_nBankIndex = INVALID_SFX_BANK; + m_asActiveSamples[i].m_bIs2D = FALSE; + m_asActiveSamples[i].m_nPriority = 5; + m_asActiveSamples[i].m_nFrequency = 0; + m_asActiveSamples[i].m_nVolume = 0; +#ifdef EXTERNAL_3D_SOUND + m_asActiveSamples[i].m_nEmittingVolume = 0; +#endif + m_asActiveSamples[i].m_fDistance = 0.0f; + m_asActiveSamples[i].m_bIsBeingPlayed = FALSE; + m_asActiveSamples[i].m_bIsPlayingFinished = FALSE; + m_asActiveSamples[i].m_nLoopCount = 1; +#ifndef GTA_PS2 + m_asActiveSamples[i].m_nLoopStart = 0; + m_asActiveSamples[i].m_nLoopEnd = -1; +#endif + m_asActiveSamples[i].m_fSpeedMultiplier = 0.0f; + m_asActiveSamples[i].m_MaxDistance = 200.0f; + m_asActiveSamples[i].m_nPan = 63; + m_asActiveSamples[i].m_bStatic = FALSE; +#if GTA_VERSION < GTA3_PC_10 + m_asActiveSamples[i].unk = -3; +#endif + m_asActiveSamples[i].m_nFinalPriority = 0; + m_asActiveSamples[i].m_nFramesToPlay = 0; + m_asActiveSamples[i].m_nVolumeChange = -1; + m_asActiveSamples[i].m_vecPos = CVector(0.0f, 0.0f, 0.0f); + m_asActiveSamples[i].m_bReverb = FALSE; +#ifdef AUDIO_REFLECTIONS + m_asActiveSamples[i].m_nReflectionDelay = 0; + m_asActiveSamples[i].m_bReflections = FALSE; +#endif + } +} + +void +cAudioManager::GenerateIntegerRandomNumberTable() +{ + for (uint32 i = 0; i < ARRAY_SIZE(m_anRandomTable); i++) + m_anRandomTable[i] = myrand(); +} + +#ifdef GTA_PS2 +bool8 +cAudioManager::LoadBankIfNecessary(uint8 bank) +{ + if(!SampleManager.IsSampleBankLoaded(bank)) + return SampleManager.LoadSampleBank(bank); + return FALSE; +} +#endif + +#ifdef EXTERNAL_3D_SOUND +void +cAudioManager::AdjustSamplesVolume() +{ + for (uint8 i = 0; i < m_nRequestedCount[m_nActiveQueue]; i++) { + tSound *pSample = &m_aRequestedQueue[m_nActiveQueue][m_aRequestedOrderList[m_nActiveQueue][i]]; + + if (!pSample->m_bIs2D) + pSample->m_nEmittingVolume = ComputeEmittingVolume(pSample->m_nEmittingVolume, pSample->m_MaxDistance, pSample->m_fDistance); + } +} + +uint8 +cAudioManager::ComputeEmittingVolume(uint8 emittingVolume, float maxDistance, float distance) +{ + float minDistance = maxDistance / 4.0f; + float diffDistance = maxDistance - minDistance; + if (distance > diffDistance) + return (minDistance - (distance - diffDistance)) * (float)emittingVolume / minDistance; + return emittingVolume; +} +#endif diff --git a/src/audio/AudioManager.h b/src/audio/AudioManager.h new file mode 100644 index 0000000..7983987 --- /dev/null +++ b/src/audio/AudioManager.h @@ -0,0 +1,587 @@ +#pragma once + +#include "audio_enums.h" +#include "AudioCollision.h" +#include "PolRadio.h" + +class tSound +{ +public: + int32 m_nEntityIndex; // audio entity index +#if GTA_VERSION >= GTA3_PC_10 + uint32 m_nCounter; // I'm not sure what this is but it looks like a virtual counter to determine the same sound in queue + // Values higher than 255 are used by reflections +#else + uint8 m_nCounter; +#endif + uint32 m_nSampleIndex; // An index of sample from AudioSamples.h + uint8 m_nBankIndex; // A sound bank index. IDK what's the point of it here since samples are hardcoded anyway + bool8 m_bIs2D; // If TRUE then sound is played in 2D space (such as frontend or police radio) + uint32 m_nPriority; // The multiplier for the sound priority (see m_nFinalPriority below). Lesser value means higher priority + uint32 m_nFrequency; // Sound frequency, plain and simple + uint8 m_nVolume; // Sound volume (0..127), only used as an actual volume without EXTERNAL_3D_SOUND (see m_nEmittingVolume) + float m_fDistance; // Distance to camera (useless if m_bIs2D == TRUE) + uint32 m_nLoopCount; // 0 - always loop, 1 - don't loop, other values never seen +#ifndef GTA_PS2 + // Loop offsets + uint32 m_nLoopStart; + int32 m_nLoopEnd; +#endif +#ifdef EXTERNAL_3D_SOUND + uint8 m_nEmittingVolume; // The volume in 3D space, provided to 3D audio engine +#endif + float m_fSpeedMultiplier; // Used for doppler effect. 0.0f - unaffected by doppler +#if GTA_VERSION >= GTA3_PC_10 + float m_MaxDistance; // The maximum distance at which sound could be heard. Minimum distance = MaxDistance / 5 or MaxDistance / 4 in case of emitting volume (useless if m_bIs2D == TRUE) +#else + uint32 m_MaxDistance; +#endif + bool8 m_bStatic; // If TRUE then sound parameters cannot be changed during playback (frequency, position, etc.) + CVector m_vecPos; // Position of sound in 3D space. Unused if m_bIs2D == TRUE + bool8 m_bReverb; // Toggles reverb effect +#ifdef AUDIO_REFLECTIONS + uint8 m_nReflectionDelay; // Number of frames before reflection could be played. This is calculated internally by AudioManager and shouldn't be set by queued sample + bool8 m_bReflections; // Add sound reflections +#endif + uint8 m_nPan; // Sound panning (0-127). Controls the volume of the playback coming from left and right speaker. Calculated internally unless m_bIs2D==TRUE. + // 0 = L 100% R 0% + // 63 = L 100% R 100% + // 127 = L 0% R 100% +#ifndef FIX_BUGS + uint32 m_nFramesToPlay; // Number of frames the sound would be played (if it stops being queued). + // This one is being set by queued sample for looping sounds, otherwise calculated inside AudioManager +#else + float m_nFramesToPlay; // Made into float for high fps fix +#endif + + // all fields below are internal to AudioManager calculations and aren't set by queued sample + bool8 m_bIsBeingPlayed; // Set to TRUE when the sound was added or changed on current frame to avoid it being overwritten + bool8 m_bIsPlayingFinished; // Not sure about the name. Set to TRUE when sampman channel becomes free +#if GTA_VERSION < GTA3_PC_10 + int32 unk; // (inherited from GTA 2) Only on PS2, used by static non-looped sounds (AFAIK) + // Looks like it's keeping a number of frames left to play with the purpose of setting m_bIsPlayingFinished=TRUE once value reaches 0 + // Default value is -3 for whatever reason +#endif + uint32 m_nFinalPriority; // Actual value used to compare priority, calculated using volume and m_nPriority. Lesser value means higher priority + int8 m_nVolumeChange; // How much m_nVolume should reduce per each frame. +#if defined(FIX_BUGS) && defined(EXTERNAL_3D_SOUND) + int8 m_nEmittingVolumeChange; // same as above but for m_nEmittingVolume +#endif +}; + +VALIDATE_SIZE(tSound, 92); + +class CPhysical; +class CAutomobile; + +class tAudioEntity +{ +public: + eAudioType m_nType; + void *m_pEntity; + bool8 m_bIsUsed; + bool8 m_bStatus; + int16 m_awAudioEvent[NUM_AUDIOENTITY_EVENTS]; + float m_afVolume[NUM_AUDIOENTITY_EVENTS]; + uint8 m_AudioEvents; +}; + +VALIDATE_SIZE(tAudioEntity, 40); + +class tPedComment +{ +public: + uint32 m_nSampleIndex; + int32 m_nEntityIndex; + CVector m_vecPos; + float m_fDistance; + uint8 m_nVolume; + int8 m_nLoadingTimeout; // how many iterations we gonna wait until dropping the sample if it's still not loaded (only useful on PS2) +#if defined(EXTERNAL_3D_SOUND) && defined(FIX_BUGS) + uint8 m_nEmittingVolume; +#endif +}; + +VALIDATE_SIZE(tPedComment, 28); + +class cPedComments +{ +public: + tPedComment m_aPedCommentQueue[NUM_SOUND_QUEUES][NUM_PED_COMMENTS_SLOTS]; + uint8 m_aPedCommentOrderList[NUM_SOUND_QUEUES][NUM_PED_COMMENTS_SLOTS]; + uint8 m_nPedCommentCount[NUM_SOUND_QUEUES]; + uint8 m_nActiveQueue; + + cPedComments() + { + for (int i = 0; i < NUM_PED_COMMENTS_SLOTS; i++) + for (int j = 0; j < NUM_SOUND_QUEUES; j++) { + m_aPedCommentQueue[j][i].m_nLoadingTimeout = -1; + m_aPedCommentOrderList[j][i] = NUM_PED_COMMENTS_SLOTS; + } + + for (int i = 0; i < NUM_SOUND_QUEUES; i++) + m_nPedCommentCount[i] = 0; + m_nActiveQueue = 0; + } + void Add(tPedComment *com); + void Process(); +}; + +VALIDATE_SIZE(cPedComments, 1164); + +// name made up +class cAudioScriptObjectManager +{ +public: + int32 m_anScriptObjectEntityIndices[NUM_SCRIPT_MAX_ENTITIES]; + int32 m_nScriptObjectEntityTotal; + + cAudioScriptObjectManager() { m_nScriptObjectEntityTotal = 0; } + ~cAudioScriptObjectManager() { m_nScriptObjectEntityTotal = 0; } +}; + + +class cTransmission; +class CEntity; +class CPlane; +class CVehicle; +class CPed; + +class cPedParams +{ +public: + bool8 m_bDistanceCalculated; + float m_fDistance; + CPed *m_pPed; + + cPedParams() + { + m_bDistanceCalculated = false; + m_fDistance = 0.0f; + m_pPed = nil; + } +}; + +class cVehicleParams +{ +public: + bool8 m_bDistanceCalculated; + float m_fDistance; + CVehicle *m_pVehicle; + cTransmission *m_pTransmission; + uint32 m_nIndex; + float m_fVelocityChange; + + cVehicleParams() + { + m_bDistanceCalculated = false; + m_fDistance = 0.0f; + m_pVehicle = nil; + m_pTransmission = nil; + m_nIndex = 0; + m_fVelocityChange = 0.0f; + } +}; + +VALIDATE_SIZE(cVehicleParams, 0x18); + +enum { + /* + REFLECTION_YMAX = 0, top + REFLECTION_YMIN = 1, bottom + REFLECTION_XMIN = 2, left + REFLECTION_XMAX = 3, right + REFLECTION_ZMAX = 4, + */ + + REFLECTION_TOP = 0, + REFLECTION_BOTTOM, + REFLECTION_LEFT, + REFLECTION_RIGHT, + REFLECTION_UP, + MAX_REFLECTIONS, +}; + +enum PLAY_STATUS { PLAY_STATUS_STOPPED = 0, PLAY_STATUS_PLAYING, PLAY_STATUS_FINISHED }; +enum LOADING_STATUS { LOADING_STATUS_NOT_LOADED = 0, LOADING_STATUS_LOADED, LOADING_STATUS_LOADING }; + +class cAudioManager +{ +public: + bool8 m_bIsInitialised; + bool8 m_bIsSurround; // unused until VC + bool8 m_bReduceReleasingPriority; + uint8 m_nActiveSamples; + bool8 m_bDoubleVolume; // unused +#if GTA_VERSION >= GTA3_PC_10 + bool8 m_bDynamicAcousticModelingStatus; +#endif + float m_fSpeedOfSound; + bool8 m_bTimerJustReset; + uint32 m_nTimer; + tSound m_sQueueSample; + uint8 m_nActiveQueue; + tSound m_aRequestedQueue[NUM_SOUND_QUEUES][NUM_CHANNELS_GENERIC]; + uint8 m_aRequestedOrderList[NUM_SOUND_QUEUES][NUM_CHANNELS_GENERIC]; + uint8 m_nRequestedCount[NUM_SOUND_QUEUES]; + tSound m_asActiveSamples[NUM_CHANNELS_GENERIC]; + tAudioEntity m_asAudioEntities[NUM_AUDIOENTITIES]; + uint32 m_aAudioEntityOrderList[NUM_AUDIOENTITIES]; + uint32 m_nAudioEntitiesCount; +#ifdef AUDIO_REFLECTIONS + CVector m_avecReflectionsPos[MAX_REFLECTIONS]; + float m_afReflectionsDistances[MAX_REFLECTIONS]; +#endif + cAudioScriptObjectManager m_sAudioScriptObjectManager; + cPedComments m_sPedComments; + int32 m_nFireAudioEntity; + int32 m_nWaterCannonEntity; + int32 m_nPoliceChannelEntity; + cPoliceRadioQueue m_sPoliceRadioQueue; + cAMCrime m_aCrimes[10]; + int32 m_nFrontEndEntity; + int32 m_nCollisionEntity; + cAudioCollisionManager m_sCollisionManager; + int32 m_nProjectileEntity; + int32 m_nBridgeEntity; + + // Mission audio stuff + CVector m_vecMissionAudioPosition; + bool8 m_bIsMissionAudio2D; + uint32 m_nMissionAudioSampleIndex; + uint8 m_nMissionAudioLoadingStatus; + uint8 m_nMissionAudioPlayStatus; + bool8 m_bIsMissionAudioPlaying; + int32 m_nMissionAudioFramesToPlay; // possibly unsigned + bool8 m_bIsMissionAudioAllowedToPlay; + + int32 m_anRandomTable[5]; + uint8 m_nTimeSpent; + bool8 m_bIsPaused; + bool8 m_bWasPaused; + uint32 m_FrameCounter; + + cAudioManager(); + ~cAudioManager(); + + void Initialise(); + void Terminate(); + void Service(); + int32 CreateEntity(eAudioType type, void *entity); + void DestroyEntity(int32 id); + bool8 GetEntityStatus(int32 id); + void SetEntityStatus(int32 id, bool8 status); + void *GetEntityPointer(int32 id); + void PlayOneShot(int32 index, uint16 sound, float vol); + void SetEffectsMasterVolume(uint8 volume); + void SetMusicMasterVolume(uint8 volume); + void SetEffectsFadeVol(uint8 volume); + void SetMusicFadeVol(uint8 volume); + void SetMonoMode(bool8 mono); + void ResetTimers(uint32 time); + void DestroyAllGameCreatedEntities(); + +#ifdef GTA_PC + uint8 GetNum3DProvidersAvailable(); + char *Get3DProviderName(uint8 id); + int8 GetCurrent3DProviderIndex(); + int8 SetCurrent3DProvider(uint8 which); + void SetSpeakerConfig(int32 conf); + bool8 IsMP3RadioChannelAvailable(); + void ReleaseDigitalHandle(); + void ReacquireDigitalHandle(); +#ifdef AUDIO_REFLECTIONS + void SetDynamicAcousticModelingStatus(bool8 status); +#endif + bool8 CheckForAnAudioFileOnCD(); + char GetCDAudioDriveLetter(); + bool8 IsAudioInitialised(); +#endif + + void ServiceSoundEffects(); + uint32 FL(float f); // not used + uint8 ComputeVolume(uint8 emittingVolume, float maxDistance, float distance); + void TranslateEntity(Const CVector *v1, CVector *v2); + int32 ComputePan(float, CVector *); + uint32 ComputeDopplerEffectedFrequency(uint32 oldFreq, float position1, float position2, float speedMultiplier); // inlined on PS2 + int32 RandomDisplacement(uint32 seed); + void InterrogateAudioEntities(); // inlined on PS2 + void AddSampleToRequestedQueue(); + void AddDetailsToRequestedOrderList(uint8 sample); // inlined on PS2 +#ifdef AUDIO_REFLECTIONS + void AddReflectionsToRequestedQueue(); + void UpdateReflections(); +#endif + void AddReleasingSounds(); + void ProcessActiveQueues(); + void ClearRequestedQueue(); // inlined on PS2 + void ClearActiveSamples(); + void GenerateIntegerRandomNumberTable(); // inlined on PS2 +#ifdef GTA_PS2 + bool8 LoadBankIfNecessary(uint8 bank); // this is used only on PS2 but technically not a platform code +#endif + +#ifdef EXTERNAL_3D_SOUND // actually must have been && AUDIO_MSS as well + void AdjustSamplesVolume(); + uint8 ComputeEmittingVolume(uint8 emittingVolume, float maxDistance, float distance); +#endif + + // audio logic + void PreInitialiseGameSpecificSetup(); + void PostInitialiseGameSpecificSetup(); + void PreTerminateGameSpecificShutdown(); + void PostTerminateGameSpecificShutdown(); + void ResetAudioLogicTimers(uint32 timer); + void ProcessReverb(); + float GetDistanceSquared(const CVector &v); + void CalculateDistance(bool8 &condition, float dist); + void ProcessSpecial(); + void ProcessEntity(int32 id); + void ProcessPhysical(int32 id); + + // vehicles + void ProcessVehicle(CVehicle *vehicle); + void ProcessRainOnVehicle(cVehicleParams ¶ms); + bool8 ProcessReverseGear(cVehicleParams ¶ms); + void ProcessModelCarEngine(cVehicleParams ¶ms); + bool8 ProcessVehicleRoadNoise(cVehicleParams ¶ms); + bool8 ProcessWetRoadNoise(cVehicleParams ¶ms); + bool8 ProcessVehicleEngine(cVehicleParams ¶ms); + void UpdateGasPedalAudio(CAutomobile *automobile); // inlined on PS2 + void PlayerJustGotInCar(); + void PlayerJustLeftCar(); + void AddPlayerCarSample(uint8 emittingVolume, uint32 freq, uint32 sample, uint8 bank, uint8 counter, bool8 notLooping); + void ProcessCesna(cVehicleParams ¶ms); + void ProcessPlayersVehicleEngine(cVehicleParams ¶ms, CAutomobile *automobile); + bool8 ProcessVehicleSkidding(cVehicleParams ¶ms); + float GetVehicleDriveWheelSkidValue(uint8 wheel, CAutomobile *automobile, cTransmission *transmission, float velocityChange); + float GetVehicleNonDriveWheelSkidValue(uint8 wheel, CAutomobile *automobile, cTransmission *transmission, float velocityChange); // inlined on PS2 + bool8 ProcessVehicleHorn(cVehicleParams ¶ms); + bool8 UsesSiren(uint32 model); // inlined on PS2 + bool8 UsesSirenSwitching(uint32 model); // inlined on PS2 + bool8 ProcessVehicleSirenOrAlarm(cVehicleParams ¶ms); + bool8 UsesReverseWarning(uint32 model); // inlined on PS2 + bool8 ProcessVehicleReverseWarning(cVehicleParams ¶ms); + bool8 ProcessVehicleDoors(cVehicleParams ¶ms); + bool8 ProcessAirBrakes(cVehicleParams ¶ms); + bool8 HasAirBrakes(uint32 model); // inlined on PS2 + bool8 ProcessEngineDamage(cVehicleParams ¶ms); + bool8 ProcessCarBombTick(cVehicleParams ¶ms); + void ProcessVehicleOneShots(cVehicleParams ¶ms); + bool8 ProcessTrainNoise(cVehicleParams ¶ms); + bool8 ProcessBoatEngine(cVehicleParams ¶ms); + bool8 ProcessBoatMovingOverWater(cVehicleParams ¶ms); + bool8 ProcessHelicopter(cVehicleParams ¶ms); + void ProcessPlane(cVehicleParams ¶ms); // inlined on PS2 + void ProcessJumbo(cVehicleParams ¶ms); + void ProcessJumboTaxi(); // inlined on PS2 + void ProcessJumboAccel(CPlane *plane); + void ProcessJumboTakeOff(CPlane *plane); // inlined on PS2 + void ProcessJumboFlying(); // inlined on PS2 + void ProcessJumboLanding(CPlane *plane); // inlined on PS2 + void ProcessJumboDecel(CPlane *plane); // inlined on PS2 + bool8 SetupJumboTaxiSound(uint8 vol); + bool8 SetupJumboWhineSound(uint8 emittingVol, uint32 freq); + bool8 SetupJumboEngineSound(uint8 vol, uint32 freq); + bool8 SetupJumboFlySound(uint8 emittingVol); + bool8 SetupJumboRumbleSound(uint8 emittingVol); + int32 GetJumboTaxiFreq(); // inlined on PS2 + + // peds + void ProcessPed(CPhysical *ped); // inlined on PS2 + void ProcessPedHeadphones(cPedParams ¶ms); + void ProcessPedOneShots(cPedParams ¶ms); + + // ped comments + void SetupPedComments(cPedParams ¶ms, uint16 sound); + int32 GetPedCommentSfx(CPed *ped, uint16 sound); + void GetPhrase(uint32 &phrase, uint32 &prevPhrase, uint32 sample, uint32 maxOffset); // inlined on PS2 + uint32 GetPlayerTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetCopTalkSfx(uint16 sound); + uint32 GetSwatTalkSfx(uint16 sound); + uint32 GetFBITalkSfx(uint16 sound); + uint32 GetArmyTalkSfx(uint16 sound); + uint32 GetMedicTalkSfx(uint16 sound); + uint32 GetFiremanTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetBusinessMaleOldTalkSfx(uint16 sound); + uint32 GetBusinessMaleYoungTalkSfx(uint16 sound, uint32 model); + uint32 GetMafiaTalkSfx(uint16 sound); + uint32 GetTriadTalkSfx(uint16 sound); + uint32 GetDiabloTalkSfx(uint16 sound); + uint32 GetYakuzaTalkSfx(uint16 sound); + uint32 GetYardieTalkSfx(uint16 sound); + uint32 GetColumbianTalkSfx(uint16 sound); + uint32 GetHoodTalkSfx(uint16 sound); + uint32 GetBlackCriminalTalkSfx(uint16 sound); + uint32 GetWhiteCriminalTalkSfx(uint16 sound); + uint32 GetCasualMaleOldTalkSfx(uint16 sound); + uint32 GetCasualMaleYoungTalkSfx(uint16 sound); + uint32 GetBlackCasualFemaleTalkSfx(uint16 sound); + uint32 GetWhiteCasualFemaleTalkSfx(uint16 sound); + uint32 GetFemaleNo3TalkSfx(uint16 sound); + uint32 GetWhiteBusinessFemaleTalkSfx(uint16 sound, uint32 model); + uint32 GetBlackFatFemaleTalkSfx(uint16 sound); + uint32 GetWhiteFatMaleTalkSfx(uint16 sound); + uint32 GetBlackFatMaleTalkSfx(uint16 sound); + uint32 GetWhiteFatFemaleTalkSfx(uint16 sound); + uint32 GetBlackFemaleProstituteTalkSfx(uint16 sound); + uint32 GetWhiteFemaleProstituteTalkSfx(uint16 sound); + uint32 GetBlackProjectMaleTalkSfx(uint16 sound, uint32 model); + uint32 GetBlackProjectFemaleOldTalkSfx(uint16 sound); + uint32 GetBlackProjectFemaleYoungTalkSfx(uint16 sound); + uint32 GetChinatownMaleOldTalkSfx(uint16 sound); + uint32 GetChinatownMaleYoungTalkSfx(uint16 sound); + uint32 GetChinatownFemaleOldTalkSfx(uint16 sound); + uint32 GetChinatownFemaleYoungTalkSfx(uint16 sound); + uint32 GetLittleItalyMaleTalkSfx(uint16 sound); + uint32 GetLittleItalyFemaleOldTalkSfx(uint16 sound); + uint32 GetLittleItalyFemaleYoungTalkSfx(uint16 sound); + uint32 GetWhiteDockerMaleTalkSfx(uint16 sound); + uint32 GetBlackDockerMaleTalkSfx(uint16 sound); + uint32 GetScumMaleTalkSfx(uint16 sound); + uint32 GetScumFemaleTalkSfx(uint16 sound); + uint32 GetWhiteWorkerMaleTalkSfx(uint16 sound); + uint32 GetBlackWorkerMaleTalkSfx(uint16 sound); + uint32 GetBlackBusinessFemaleTalkSfx(uint16 sound); + uint32 GetSupermodelMaleTalkSfx(uint16 sound); + uint32 GetSupermodelFemaleTalkSfx(uint16 sound); + uint32 GetStewardMaleTalkSfx(uint16 sound); + uint32 GetStewardFemaleTalkSfx(uint16 sound); + uint32 GetFanMaleTalkSfx(uint16 sound, uint32 model); + uint32 GetFanFemaleTalkSfx(uint16 sound); + uint32 GetHospitalMaleTalkSfx(uint16 sound); + uint32 GetHospitalFemaleTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetWhiteConstructionWorkerTalkSfx(uint16 sound); + uint32 GetBlackConstructionWorkerTalkSfx(uint16 sound); + uint32 GetShopperFemaleTalkSfx(uint16 sound, uint32 model); + uint32 GetStudentMaleTalkSfx(uint16 sound); + uint32 GetStudentFemaleTalkSfx(uint16 sound); + + uint32 GetSpecialCharacterTalkSfx(uint32 modelIndex, uint16 sound); + uint32 GetEightBallTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetSalvatoreTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetMistyTalkSfx(uint16 sound); + uint32 GetOldJapTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetCatalinaTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetBomberTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetSecurityGuardTalkSfx(uint16 sound); + uint32 GetChunkyTalkSfx(uint16 sound); // inlined on PS2 + + uint32 GetAsianTaxiDriverTalkSfx(uint16 sound); // inlined on PS2 + uint32 GetPimpTalkSfx(uint16 sound); + uint32 GetNormalMaleTalkSfx(uint16 sound); + uint32 GetGenericMaleTalkSfx(uint16 sound); + uint32 GetGenericFemaleTalkSfx(uint16 sound); + + // particles + void ProcessExplosions(int32 id); + void ProcessFires(int32 id); + void ProcessWaterCannon(int32 id); + + // script objects + void ProcessScriptObject(int32 id); // inlined on PS2 + void ProcessOneShotScriptObject(uint8 sound); + void ProcessLoopingScriptObject(uint8 sound); + void ProcessPornCinema(uint8 sound); + void ProcessWorkShopScriptObject(uint8 sound); + void ProcessSawMillScriptObject(uint8 sound); + void ProcessLaunderetteScriptObject(uint8 sound); + void ProcessShopScriptObject(uint8 sound); + void ProcessAirportScriptObject(uint8 sound); + void ProcessCinemaScriptObject(uint8 sound); + void ProcessDocksScriptObject(uint8 sound); + void ProcessHomeScriptObject(uint8 sound); + void ProcessPoliceCellBeatingScriptObject(uint8 sound); + + // misc + void ProcessWeather(int32 id); + void ProcessFrontEnd(); + void ProcessCrane(); + void ProcessProjectiles(); + void ProcessGarages(); + void ProcessFireHydrant(); + + // bridge + void ProcessBridge(); // inlined on PS2 + void ProcessBridgeWarning(); + void ProcessBridgeMotor(); + void ProcessBridgeOneShots(); + + // mission audio + bool8 MissionScriptAudioUsesPoliceChannel(uint32 soundMission); + void PreloadMissionAudio(Const char *name); + uint8 GetMissionAudioLoadingStatus(); + void SetMissionAudioLocation(float x, float y, float z); + void PlayLoadedMissionAudio(); + bool8 IsMissionAudioSampleFinished(); + bool8 IsMissionAudioSamplePlaying() { return m_nMissionAudioPlayStatus == PLAY_STATUS_PLAYING; } + bool8 ShouldDuckMissionAudio() { return IsMissionAudioSamplePlaying(); } + void ClearMissionAudio(); + void ProcessMissionAudio(); + + // police radio + void InitialisePoliceRadioZones(); + void InitialisePoliceRadio(); + void ResetPoliceRadio(); + void SetMissionScriptPoliceAudio(uint32 sfx); + int8 GetMissionScriptPoliceAudioPlayingStatus(); + void DoPoliceRadioCrackle(); + void ServicePoliceRadio(); + void ServicePoliceRadioChannel(uint8 wantedLevel); + bool8 SetupCrimeReport(); + void SetupSuspectLastSeenReport(); + void ReportCrime(eCrimeType crime, const CVector &pos); + void PlaySuspectLastSeen(float x, float y, float z); + void AgeCrimes(); // inlined on PS2 + + // collision stuff + void ReportCollision(CEntity *entity1, CEntity *entity2, uint8 surface1, uint8 surface2, float collisionPower, float intensity2); + void ServiceCollisions(); + void SetUpOneShotCollisionSound(const cAudioCollision &col); + void SetUpLoopingCollisionSound(const cAudioCollision &col, uint8 counter); + uint32 SetLoopingCollisionRequestedSfxFreqAndGetVol(const cAudioCollision &audioCollision); + float GetCollisionOneShotRatio(uint32 a, float b); + float GetCollisionLoopingRatio(uint32 a, uint32 b, float c); // not used + float GetCollisionRatio(float a, float b, float c, float d); // inlined on PS2 +}; + +/* + Manual loop points are not on PS2 so let's have these macros to avoid massive ifndefs. + Setting these manually was pointless anyway since they never change from sdt values. + What were they thinking? +*/ +#ifndef GTA_PS2 +#define RESET_LOOP_OFFSETS \ + m_sQueueSample.m_nLoopStart = 0; \ + m_sQueueSample.m_nLoopEnd = -1; +#define SET_LOOP_OFFSETS(sample) \ + m_sQueueSample.m_nLoopStart = SampleManager.GetSampleLoopStartOffset(sample); \ + m_sQueueSample.m_nLoopEnd = SampleManager.GetSampleLoopEndOffset(sample); +#else +#define RESET_LOOP_OFFSETS +#define SET_LOOP_OFFSETS(sample) +#endif +#ifdef EXTERNAL_3D_SOUND +#define SET_EMITTING_VOLUME(vol) m_sQueueSample.m_nEmittingVolume = vol +#else +#define SET_EMITTING_VOLUME(vol) +#endif +#ifdef AUDIO_REFLECTIONS +#define SET_SOUND_REFLECTION(b) m_sQueueSample.m_bReflections = b +#else +#define SET_SOUND_REFLECTION(b) +#endif + +#if defined(AUDIO_MSS) && !defined(PS2_AUDIO_CHANNELS) +static_assert(sizeof(cAudioManager) == 19220, "cAudioManager: error"); +#endif + +extern cAudioManager AudioManager; + +enum +{ + PED_COMMENT_VOLUME = 127, + PED_COMMENT_VOLUME_BEHIND_WALL = 31, + COLLISION_MAX_DIST = 60, +}; diff --git a/src/audio/AudioSamples.h b/src/audio/AudioSamples.h new file mode 100644 index 0000000..1e7ba68 --- /dev/null +++ b/src/audio/AudioSamples.h @@ -0,0 +1,3273 @@ +#pragma once + +#include "common.h" + +enum eSfxSample +{ + SFX_CAR_HORN_JEEP = 0, + SFX_CAR_HORN_BMW328, + SFX_CAR_HORN_BUS, + SFX_CAR_HORN_BUS2, + SFX_CAR_HORN_56CHEV, + SFX_CAR_HORN_PICKUP, + SFX_CAR_HORN_PORSCHE, + SFX_CAR_HORN_TRUCK, + SFX_OLD_CAR_DOOR_OPEN, + SFX_OLD_CAR_DOOR_CLOSE, + SFX_NEW_CAR_DOOR_OPEN, + SFX_NEW_CAR_DOOR_CLOSE, + SFX_TRUCK_DOOR_OPEN, + SFX_TRUCK_DOOR_CLOSE, + SFX_REMOTE_CONTROLLED_CAR, + SFX_REVERSE_GEAR, + SFX_REVERSE_GEAR_2, + SFX_CAR_STARTER, + SFX_ROAD_NOISE, + SFX_SKID, + SFX_GRAVEL_SKID, + SFX_POLICE_SIREN_SLOW, + SFX_SIREN_FAST, + SFX_AMBULANCE_SIREN_SLOW, + SFX_REVERSE_WARNING, + SFX_ICE_CREAM_TUNE, + SFX_CAR_ALARM_1, + SFX_AIR_BRAKES, + SFX_SQUEAKY_BRAKES, + SFX_TYRE_BUMP, + SFX_TRAIN_FAR, + SFX_TRAIN_NEAR, + SFX_FOOTSTEP_CONCRETE_1, + SFX_FOOTSTEP_CONCRETE_2, + SFX_FOOTSTEP_CONCRETE_3, + SFX_FOOTSTEP_CONCRETE_4, + SFX_FOOTSTEP_CONCRETE_5, + SFX_FOOTSTEP_GRASS_1, + SFX_FOOTSTEP_GRASS_2, + SFX_FOOTSTEP_GRASS_3, + SFX_FOOTSTEP_GRASS_4, + SFX_FOOTSTEP_GRASS_5, + SFX_FOOTSTEP_GRAVEL_1, + SFX_FOOTSTEP_GRAVEL_2, + SFX_FOOTSTEP_GRAVEL_3, + SFX_FOOTSTEP_GRAVEL_4, + SFX_FOOTSTEP_GRAVEL_5, + SFX_FOOTSTEP_WOOD_1, + SFX_FOOTSTEP_WOOD_2, + SFX_FOOTSTEP_WOOD_3, + SFX_FOOTSTEP_WOOD_4, + SFX_FOOTSTEP_WOOD_5, + SFX_FOOTSTEP_METAL_1, + SFX_FOOTSTEP_METAL_2, + SFX_FOOTSTEP_METAL_3, + SFX_FOOTSTEP_METAL_4, + SFX_FOOTSTEP_METAL_5, + SFX_FOOTSTEP_WATER_1, + SFX_FOOTSTEP_WATER_2, + SFX_FOOTSTEP_WATER_3, + SFX_FOOTSTEP_WATER_4, + SFX_FOOTSTEP_SAND_1, + SFX_FOOTSTEP_SAND_2, + SFX_FOOTSTEP_SAND_3, + SFX_FOOTSTEP_SAND_4, + SFX_EXPLOSION_2, + SFX_EXPLOSION_3, + SFX_COLT45_LEFT, + SFX_COLT45_RIGHT, + SFX_M16_LEFT, + SFX_M16_RIGHT, + SFX_AK47_LEFT, + SFX_AK47_RIGHT, + SFX_UZI_LEFT, + SFX_UZI_RIGHT, + SFX_UZI_END_LEFT, + SFX_UZI_END_RIGHT, + SFX_SNIPER_LEFT, + SFX_SNIPER_RIGHT, + SFX_ROCKET_LEFT, + SFX_ROCKET_RIGHT, + SFX_ROCKET_FLY, + SFX_FLAMETHROWER_LEFT, + SFX_FLAMETHROWER_RIGHT, + SFX_FLAMETHROWER_START_LEFT, + SFX_FLAMETHROWER_START_RIGHT, + SFX_SHOTGUN_LEFT, + SFX_SHOTGUN_RIGHT, + SFX_PISTOL_RELOAD, + SFX_AK47_RELOAD, + SFX_M16_RELOAD, + SFX_ROCKET_RELOAD, + SFX_RIFLE_RELOAD, + SFX_COL_TARMAC_1, + SFX_COL_TARMAC_2, + SFX_COL_TARMAC_3, + SFX_COL_TARMAC_4, + SFX_COL_TARMAC_5, + SFX_COL_GRASS_1, + SFX_COL_GRAVEL_1, + SFX_COL_MUD_1, + SFX_COL_GARAGE_DOOR_1, + SFX_COL_CAR_PANEL_1, + SFX_COL_CAR_PANEL_2, + SFX_COL_CAR_PANEL_3, + SFX_COL_CAR_PANEL_4, + SFX_COL_CAR_PANEL_5, + SFX_COL_CAR_PANEL_6, + SFX_COL_THICK_METAL_PLATE_1, + SFX_COL_SCAFFOLD_POLE_1, + SFX_COL_LAMP_POST_1, + SFX_COL_HYDRANT_1, + SFX_COL_METAL_CHAIN_FENCE_1, + SFX_COL_METAL_CHAIN_FENCE_2, + SFX_COL_METAL_CHAIN_FENCE_3, + SFX_COL_METAL_CHAIN_FENCE_4, + SFX_COL_PED_1, + SFX_COL_PED_2, + SFX_COL_PED_3, + SFX_COL_PED_4, + SFX_COL_PED_5, + SFX_COL_SAND_1, + SFX_COL_WOOD_CRATES_1, + SFX_COL_WOOD_CRATES_2, + SFX_COL_WOOD_CRATES_3, + SFX_COL_WOOD_CRATES_4, + SFX_COL_WOOD_BENCH_1, + SFX_COL_WOOD_BENCH_2, + SFX_COL_WOOD_BENCH_3, + SFX_COL_WOOD_BENCH_4, + SFX_COL_WOOD_SOLID_1, + SFX_COL_VEG_1, + SFX_COL_VEG_2, + SFX_COL_VEG_3, + SFX_COL_VEG_4, + SFX_COL_VEG_5, + SFX_COL_CONTAINER_1, + SFX_COL_NEWS_VENDOR_1, + SFX_COL_NEWS_VENDOR_2, + SFX_COL_NEWS_VENDOR_3, + SFX_COL_CAR_1, + SFX_COL_CAR_2, + SFX_COL_CAR_3, + SFX_COL_CAR_4, + SFX_COL_CAR_5, + SFX_COL_CARDBOARD_1, + SFX_COL_CARDBOARD_2, + SFX_COL_GATE, + SFX_SCRAPE_CAR_1, + SFX_CRATE_SMASH, + SFX_GLASS_CRACK, + SFX_GLASS_SMASH, + SFX_GLASS_SHARD_1, + SFX_GLASS_SHARD_2, + SFX_GLASS_SHARD_3, + SFX_GLASS_SHARD_4, + SFX_PED_ON_FIRE, + SFX_CAR_ON_FIRE, + SFX_RAIN, + SFX_PICKUP_1_LEFT, + SFX_PICKUP_1_RIGHT, + SFX_PICKUP_2_LEFT, + SFX_PICKUP_2_RIGHT, + SFX_PICKUP_3_LEFT, + SFX_PICKUP_3_RIGHT, + SFX_PICKUP_ERROR_LEFT, + SFX_PICKUP_ERROR_RIGHT, + SFX_BULLET_SHELL_HIT_GROUND_1, + SFX_BULLET_SHELL_HIT_GROUND_2, + SFX_BULLET_PED, + SFX_BULLET_CAR_1, + SFX_BULLET_CAR_2, + SFX_BULLET_CAR_3, + SFX_BULLET_CAR_4, + SFX_BULLET_CAR_5, + SFX_BULLET_CAR_6, + SFX_BULLET_WALL_1, + SFX_BULLET_WALL_2, + SFX_BULLET_WALL_3, + SFX_BAT_HIT_LEFT, + SFX_BAT_HIT_RIGHT, + SFX_FIGHT_1, + SFX_FIGHT_2, + SFX_FIGHT_4, + SFX_FIGHT_5, + SFX_GARAGE_DOOR_LOOP, + SFX_COUNTDOWN, + SFX_ARM_BOMB, + SFX_POLICE_RADIO_CRACKLE, + SFX_WEVE_GOT, + SFX_THERES, + SFX_RESPOND_TO, + SFX_A_10_1, + SFX_A_10_2, + SFX_CRIME_1, + SFX_CRIME_2, + SFX_CRIME_3, + SFX_CRIME_4, + SFX_CRIME_5, + SFX_CRIME_6, + SFX_CRIME_7, + SFX_CRIME_8, + SFX_CRIME_9, + SFX_CRIME_10, + SFX_CRIME_11, + SFX_CRIME_12, + SFX_IN, + SFX_NORTH, + SFX_EAST, + SFX_SOUTH, + SFX_WEST, + SFX_CENTRAL, + SFX_POLICE_RADIO_MESSAGE_NOISE_1, + SFX_POLICE_RADIO_MESSAGE_NOISE_2, + SFX_POLICE_RADIO_MESSAGE_NOISE_3, + SFX_POLICE_RADIO_LIBERTY_CITY, + SFX_POLICE_RADIO_PORTLAND, + SFX_POLICE_RADIO_STAUNTON_ISLAND, + SFX_POLICE_RADIO_SHORESIDE_VALE, + SFX_POLICE_RADIO_ROCKFORD, + SFX_POLICE_RADIO_FORT_STAUNTON, + SFX_POLICE_RADIO_ASPATRIA, + SFX_POLICE_RADIO_TORRINGTON, + SFX_POLICE_RADIO_BEDFORD_POINT, + SFX_POLICE_RADIO_NEWPORT, + SFX_POLICE_RADIO_BELLEVILLE_PARK, + SFX_POLICE_RADIO_LIBERTY_CAMPUS, + SFX_POLICE_RADIO_COCHRANE_DAM, + SFX_POLICE_RADIO_PIKE_CREEK, + SFX_POLICE_RADIO_CEDAR_GROVE, + SFX_POLICE_RADIO_WICHITA_GARDENS, + SFX_POLICE_RADIO_FRANCIS_INTERNATIONAL_AIRPORT, + SFX_POLICE_RADIO_CALLAHAN_POINT, + SFX_POLICE_RADIO_ATLANTIC_QUAYS, + SFX_POLICE_RADIO_PORTLAND_HARBOUR, + SFX_POLICE_RADIO_TRENTON, + SFX_POLICE_RADIO_CHINATOWN, + SFX_POLICE_RADIO_RED_LIGHT_DISTRICT, + SFX_POLICE_RADIO_HEPBURN_HEIGHTS, + SFX_POLICE_RADIO_SAINT_MARKS, + SFX_POLICE_RADIO_HARWOOD, + SFX_POLICE_RADIO_PORTLAND_BEACH, + SFX_POLICE_RADIO_PORTLAND_STRAIGHTS, // shouldn't be used anymore + SFX_POLICE_RADIO_SUSPECT, + SFX_POLICE_RADIO_LAST_SEEN, + SFX_POLICE_RADIO_ON_FOOT, + SFX_POLICE_RADIO_IN_A, + SFX_POLICE_RADIO_IN_AN, + SFX_POLICE_RADIO_BLACK, + SFX_POLICE_RADIO_WHITE, + SFX_POLICE_RADIO_BLUE, + SFX_POLICE_RADIO_RED, + SFX_POLICE_RADIO_PURPLE, + SFX_POLICE_RADIO_YELLOW, + SFX_POLICE_RADIO_GREY, + SFX_POLICE_RADIO_ORANGE, + SFX_POLICE_RADIO_GREEN, + SFX_POLICE_RADIO_SILVER, + SFX_POLICE_RADIO_DARK, + SFX_POLICE_RADIO_LIGHT, + SFX_POLICE_RADIO_BRIGHT, + SFX_POLICE_RADIO_AMBULANCE, + SFX_POLICE_RADIO_VAN, + SFX_POLICE_RADIO_TRUCK, + SFX_POLICE_RADIO_SALOON, + SFX_POLICE_RADIO_SPORTS_CAR, + SFX_POLICE_RADIO_BUGGY, + SFX_POLICE_RADIO_TAXI, + SFX_POLICE_RADIO_CRUISER, + SFX_POLICE_RADIO_BUS, + SFX_POLICE_RADIO_2_DOOR, + SFX_POLICE_RADIO_FIRE_TRUCK, + SFX_POLICE_RADIO_BOAT, + SFX_POLICE_RADIO_PICKUP, + SFX_POLICE_RADIO_ICE_CREAM_VAN, + SFX_POLICE_RADIO_LIMO, + SFX_POLICE_RADIO_POLICE_CAR, + SFX_POLICE_RADIO_CONVERTIBLE, + SFX_POLICE_RADIO_SUBWAY_CAR, + SFX_POLICE_RADIO_TANK, + SFX_HELI_1, + SFX_HELI_2, + SFX_HELI_3, + SFX_PHONE_RING, + SFX_CAR_REV_1, + SFX_CAR_REV_2, + SFX_CAR_REV_3, + SFX_CAR_REV_4, + SFX_CAR_REV_5, + SFX_CAR_REV_6, + SFX_CAR_REV_7, + SFX_CAR_REV_8, + SFX_CAR_REV_9, + SFX_CAR_REV_10, + SFX_CAR_IDLE_1, + SFX_CAR_IDLE_2, + SFX_CAR_IDLE_3, + SFX_CAR_IDLE_4, + SFX_CAR_IDLE_5, + SFX_CAR_IDLE_6, + SFX_CAR_IDLE_7, + SFX_CAR_IDLE_8, + SFX_CAR_IDLE_9, + SFX_CAR_IDLE_10, + SFX_JUMBO_DIST_FLY, + SFX_JUMBO_TAXI, + SFX_JUMBO_WHINE, + SFX_JUMBO_ENGINE, + SFX_JUMBO_RUMBLE, + SFX_JUMBO_LAND_WHEELS, + SFX_POLICE_BOAT_IDLE, + SFX_POLICE_BOAT_ACCEL, + SFX_POLICE_BOAT_THUMB_OFF, + SFX_BOAT_WATER_LOOP, + SFX_BOAT_SPLASH_1, + SFX_BOAT_SPLASH_2, + SFX_FISHING_BOAT_IDLE, + SFX_CESNA_IDLE, + SFX_CESNA_REV, + SFX_CAR_RAIN_1, + SFX_CAR_RAIN_2, + SFX_CAR_RAIN_3, + SFX_CAR_RAIN_4, + SFX_SPLASH_1, + SFX_PED_CRUNCH_1, + SFX_PED_CRUNCH_2, + SFX_HEADPHONES, + SFX_WOODEN_BOX_SMASH, + SFX_CARDBOARD_BOX_SMASH, + SFX_ERROR_FIRE_ROCKET_LAUNCHER, + SFX_ERROR_FIRE_RIFLE, + SFX_TANK_TURRET, + SFX_CRANE_MAGNET, + SFX_BODY_LAND_AND_FALL, + SFX_BODY_LAND, + SFX_BOMB_BEEP, + SFX_TIMER_BEEP, + SFX_PART_MISSION_COMPLETE, + SFX_START_BUTTON_LEFT, + SFX_START_BUTTON_RIGHT, + SFX_SUSPENSION_FAST_MOVE, + SFX_SUSPENSION_SLOW_MOVE_LOOP, + SFX_SHAG_SUSPENSION, + SFX_RADIO_CLICK, + SFX_INFO, + + // bank 1 + SFX_CAR_ACCEL_1, + SFX_CAR_AFTER_ACCEL_1, + SFX_CAR_FINGER_OFF_ACCEL_1, + + // bank 2 + SFX_CAR_ACCEL_2, + SFX_CAR_AFTER_ACCEL_2, + SFX_CAR_FINGER_OFF_ACCEL_2, + + // bank 3 + SFX_CAR_ACCEL_3, + SFX_CAR_AFTER_ACCEL_3, + SFX_CAR_FINGER_OFF_ACCEL_3, + + // bank 4 + SFX_CAR_ACCEL_4, + SFX_CAR_AFTER_ACCEL_4, + SFX_CAR_FINGER_OFF_ACCEL_4, + + // bank 5 + SFX_CAR_ACCEL_5, + SFX_CAR_AFTER_ACCEL_5, + SFX_CAR_FINGER_OFF_ACCEL_5, + + // bank 6 + SFX_CAR_ACCEL_6, + SFX_CAR_AFTER_ACCEL_6, + SFX_CAR_FINGER_OFF_ACCEL_6, + + // bank 7 + SFX_CAR_ACCEL_7, + SFX_CAR_AFTER_ACCEL_7, + SFX_CAR_FINGER_OFF_ACCEL_7, + + // bank 8 + SFX_CAR_ACCEL_8, + SFX_CAR_AFTER_ACCEL_8, + SFX_CAR_FINGER_OFF_ACCEL_8, + + // bank 9 + SFX_CAR_ACCEL_9, + SFX_CAR_AFTER_ACCEL_9, + SFX_CAR_FINGER_OFF_ACCEL_9, + + // bank 10 + SFX_PAGE_CHANGE_AND_BACK_LEFT, + SFX_PAGE_CHANGE_AND_BACK_RIGHT, + SFX_HIGHLIGHT_LEFT, + SFX_HIGHLIGHT_RIGHT, + SFX_SELECT_LEFT, + SFX_SELECT_RIGHT, + SFX_SUB_MENU_BACK_LEFT, + SFX_SUB_MENU_BACK_RIGHT, + SFX_STEREO_LEFT, + SFX_STEREO_RIGHT, + SFX_MONO, + SFX_NOISE_BURST_1, + SFX_NOISE_BURST_2, + SFX_NOISE_BURST_3, + SFX_ERROR_LEFT, + SFX_ERROR_RIGHT, + + // bank 11 + SFX_TRAIN_STATION_AMBIENCE_LOOP, + SFX_TRAIN_STATION_ANNOUNCE, + + // bank 12 + SFX_CLUB_1, + + // bank 13 + SFX_CLUB_2, + + // bank 14 + SFX_CLUB_3, + + // bank 15 + SFX_CLUB_4, + + // bank 16 + SFX_CLUB_5, + + // bank 17 + SFX_CLUB_6, + + // bank 18 + SFX_CLUB_7, + + // bank 19 + SFX_CLUB_8, + + // bank 20 + SFX_CLUB_9, + + // bank 21 + SFX_CLUB_10, + + // bank 22 + SFX_CLUB_11, + + // bank 23 + SFX_CLUB_12, + + // bank 24 + SFX_CLUB_RAGGA, + + // bank 25 + SFX_STRIP_CLUB_1, + + // bank 26 + SFX_STRIP_CLUB_2, + + // bank 27 + SFX_WORKSHOP_1, + + // bank 28 + SFX_PIANO_BAR_1, + + // bank 29 + SFX_SAWMILL_LOOP, + SFX_SAWMILL_CUT_WOOD, + + // bank 30 + SFX_DOG_FOOD_FACTORY, + + // bank 31 + SFX_LAUNDERETTE_LOOP, + SFX_LAUNDERETTE_SONG_LOOP, + + // bank 32 + SFX_RESTAURANT_CHINATOWN, + + // bank 33 + SFX_RESTAURANT_ITALY, + + // bank 34 + SFX_RESTAURANT_GENERIC_1, + + // bank 35 + SFX_RESTAURANT_GENERIC_2, + + // bank 36 + SFX_AIRPORT_ANNOUNCEMENT_1, + SFX_AIRPORT_ANNOUNCEMENT_2, + SFX_AIRPORT_ANNOUNCEMENT_3, + SFX_AIRPORT_ANNOUNCEMENT_4, + + // bank 37 + SFX_SHOP_LOOP, + SFX_SHOP_TILL_1, + SFX_SHOP_TILL_2, + + // bank 38 + SFX_CINEMA_BASS_1, + SFX_CINEMA_BASS_2, + SFX_CINEMA_BASS_3, + + // bank 39 + SFX_DOCKS_FOGHORN, + + // bank 40 + SFX_HOME_1, + SFX_HOME_2, + SFX_HOME_3, + SFX_HOME_4, + SFX_HOME_5, + + // bank 41 + SFX_PORN_1_LOOP, + SFX_PORN_1_GROAN_1, + SFX_PORN_1_GROAN_2, + + // bank 42 + SFX_PORN_2_LOOP, + SFX_PORN_2_GROAN_1, + SFX_PORN_2_GROAN_2, + + // bank 43 + SFX_PORN_3_LOOP, + SFX_PORN_3_GROAN_1, + SFX_PORN_3_GROAN_2, + + // bank 44 + SFX_POLICE_BALL_1, + + // bank 45 + SFX_BANK_ALARM_1, + + // bank 46 + SFX_RAVE_INDUSTRIAL, + + // bank 47 + SFX_RAVE_COMMERCIAL, + + // bank 48 + SFX_RAVE_SUBURBAN, + + // bank 49 + SFX_RAVE_COMMERCIAL_2, + + // unused banks 50-58 + SFX_CLUB_1_1, + SFX_CLUB_1_2, + SFX_CLUB_1_3, + SFX_CLUB_1_4, + SFX_CLUB_1_5, + SFX_CLUB_1_6, + SFX_CLUB_1_7, + SFX_CLUB_1_8, + SFX_CLUB_1_9, + + // bank 59 + SFX_EXPLOSION_1, + SFX_BRIDGE_OPEN_WARNING, +#ifndef GTA_PS2 + SFX_PAGER, // used to be ped comment on PS2 +#endif + + SFX_COP_VOICE_1_ARREST_1, + SFX_COP_VOICE_1_ARREST_2, + SFX_COP_VOICE_1_ARREST_3, + SFX_COP_VOICE_1_ARREST_4, + SFX_COP_VOICE_1_ARREST_5, + SFX_COP_VOICE_1_ARREST_6, + SFX_COP_VOICE_1_CHASE_1, + SFX_COP_VOICE_1_CHASE_2, + SFX_COP_VOICE_1_CHASE_3, + SFX_COP_VOICE_1_CHASE_4, + SFX_COP_VOICE_1_CHASE_5, + SFX_COP_VOICE_1_CHASE_6, + SFX_COP_VOICE_1_CHASE_7, + SFX_COP_VOICE_2_ARREST_1, + SFX_COP_VOICE_2_ARREST_2, + SFX_COP_VOICE_2_ARREST_3, + SFX_COP_VOICE_2_ARREST_4, + SFX_COP_VOICE_2_ARREST_5, + SFX_COP_VOICE_2_ARREST_6, + SFX_COP_VOICE_2_CHASE_1, + SFX_COP_VOICE_2_CHASE_2, + SFX_COP_VOICE_2_CHASE_3, + SFX_COP_VOICE_2_CHASE_4, + SFX_COP_VOICE_2_CHASE_5, + SFX_COP_VOICE_2_CHASE_6, + SFX_COP_VOICE_2_CHASE_7, + SFX_COP_VOICE_3_ARREST_1, + SFX_COP_VOICE_3_ARREST_2, + SFX_COP_VOICE_3_ARREST_3, + SFX_COP_VOICE_3_ARREST_4, + SFX_COP_VOICE_3_ARREST_5, + SFX_COP_VOICE_3_ARREST_6, + SFX_COP_VOICE_3_CHASE_1, + SFX_COP_VOICE_3_CHASE_2, + SFX_COP_VOICE_3_CHASE_3, + SFX_COP_VOICE_3_CHASE_4, + SFX_COP_VOICE_3_CHASE_5, + SFX_COP_VOICE_3_CHASE_6, + SFX_COP_VOICE_3_CHASE_7, + SFX_COP_VOICE_4_ARREST_1, + SFX_COP_VOICE_4_ARREST_2, + SFX_COP_VOICE_4_ARREST_3, + SFX_COP_VOICE_4_ARREST_4, + SFX_COP_VOICE_4_ARREST_5, + SFX_COP_VOICE_4_ARREST_6, + SFX_COP_VOICE_4_CHASE_1, + SFX_COP_VOICE_4_CHASE_2, + SFX_COP_VOICE_4_CHASE_3, + SFX_COP_VOICE_4_CHASE_4, + SFX_COP_VOICE_4_CHASE_5, + SFX_COP_VOICE_4_CHASE_6, + SFX_COP_VOICE_4_CHASE_7, + SFX_COP_VOICE_5_ARREST_1, + SFX_COP_VOICE_5_ARREST_2, + SFX_COP_VOICE_5_ARREST_3, + SFX_COP_VOICE_5_ARREST_4, + SFX_COP_VOICE_5_ARREST_5, + SFX_COP_VOICE_5_ARREST_6, + SFX_COP_VOICE_5_CHASE_1, + SFX_COP_VOICE_5_CHASE_2, + SFX_COP_VOICE_5_CHASE_3, + SFX_COP_VOICE_5_CHASE_4, + SFX_COP_VOICE_5_CHASE_5, + SFX_COP_VOICE_5_CHASE_6, + SFX_COP_VOICE_5_CHASE_7, + SFX_SWAT_VOICE_1_CHASE_1, + SFX_SWAT_VOICE_1_CHASE_2, + SFX_SWAT_VOICE_1_CHASE_3, + SFX_SWAT_VOICE_1_CHASE_4, + SFX_SWAT_VOICE_1_CHASE_5, + SFX_SWAT_VOICE_1_CHASE_6, + SFX_SWAT_VOICE_2_CHASE_1, + SFX_SWAT_VOICE_2_CHASE_2, + SFX_SWAT_VOICE_2_CHASE_3, + SFX_SWAT_VOICE_2_CHASE_4, + SFX_SWAT_VOICE_2_CHASE_5, + SFX_SWAT_VOICE_2_CHASE_6, + SFX_SWAT_VOICE_3_CHASE_1, + SFX_SWAT_VOICE_3_CHASE_2, + SFX_SWAT_VOICE_3_CHASE_3, + SFX_SWAT_VOICE_3_CHASE_4, + SFX_SWAT_VOICE_3_CHASE_5, + SFX_SWAT_VOICE_3_CHASE_6, + SFX_SWAT_VOICE_4_CHASE_1, + SFX_SWAT_VOICE_4_CHASE_2, + SFX_SWAT_VOICE_4_CHASE_3, + SFX_SWAT_VOICE_4_CHASE_4, + SFX_SWAT_VOICE_4_CHASE_5, + SFX_SWAT_VOICE_4_CHASE_6, + SFX_FBI_VOICE_1_CHASE_1, + SFX_FBI_VOICE_1_CHASE_2, + SFX_FBI_VOICE_1_CHASE_3, + SFX_FBI_VOICE_1_CHASE_4, + SFX_FBI_VOICE_1_CHASE_5, + SFX_FBI_VOICE_1_CHASE_6, + SFX_FBI_VOICE_2_CHASE_1, + SFX_FBI_VOICE_2_CHASE_2, + SFX_FBI_VOICE_2_CHASE_3, + SFX_FBI_VOICE_2_CHASE_4, + SFX_FBI_VOICE_2_CHASE_5, + SFX_FBI_VOICE_2_CHASE_6, + SFX_FBI_VOICE_3_CHASE_1, + SFX_FBI_VOICE_3_CHASE_2, + SFX_FBI_VOICE_3_CHASE_3, + SFX_FBI_VOICE_3_CHASE_4, + SFX_FBI_VOICE_3_CHASE_5, + SFX_FBI_VOICE_3_CHASE_6, + SFX_POLICE_HELI_1, + SFX_POLICE_HELI_2, + SFX_POLICE_HELI_3, + SFX_POLICE_HELI_4, + SFX_POLICE_HELI_5, + SFX_POLICE_HELI_6, + SFX_POLICE_HELI_7, + SFX_POLICE_HELI_8, + SFX_POLICE_HELI_9, + SFX_POLICE_HELI_10, + SFX_POLICE_HELI_11, + SFX_POLICE_HELI_12, + SFX_POLICE_HELI_13, + SFX_POLICE_HELI_14, + SFX_POLICE_HELI_15, + SFX_POLICE_HELI_16, + SFX_POLICE_HELI_17, + SFX_POLICE_HELI_18, + SFX_POLICE_HELI_19, + SFX_POLICE_HELI_20, + SFX_POLICE_HELI_21, + SFX_POLICE_HELI_22, + SFX_POLICE_HELI_23, + SFX_POLICE_HELI_24, + SFX_POLICE_HELI_25, + SFX_POLICE_HELI_26, + SFX_POLICE_HELI_27, + SFX_POLICE_HELI_28, + SFX_POLICE_HELI_29, +#ifdef GTA_PS2 + SFX_MISSION_CAT1, +#endif + SFX_CHUNKY_DEATH, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_DOCKER_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_DOCKER_VOICE_1_CHAT_1, + SFX_BLACK_DOCKER_VOICE_1_CHAT_2, + SFX_BLACK_DOCKER_VOICE_1_CHAT_3, + SFX_BLACK_DOCKER_VOICE_1_CHAT_4, + SFX_BLACK_DOCKER_VOICE_1_CHAT_5, + SFX_BLACK_DOCKER_VOICE_1_DODGE_1, + SFX_BLACK_DOCKER_VOICE_1_DODGE_2, + SFX_BLACK_DOCKER_VOICE_1_DODGE_3, + SFX_BLACK_DOCKER_VOICE_1_DODGE_4, + SFX_BLACK_DOCKER_VOICE_1_DODGE_5, + SFX_BLACK_DOCKER_VOICE_1_EYING_1, + SFX_BLACK_DOCKER_VOICE_1_EYING_2, + SFX_BLACK_DOCKER_VOICE_1_EYING_3, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_1, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_2, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_3, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_4, + SFX_BLACK_DOCKER_VOICE_1_FIGHT_5, + SFX_BLACK_DOCKER_VOICE_1_GUN_PANIC_1, + SFX_BLACK_DOCKER_VOICE_1_GUN_PANIC_2, + SFX_BLACK_DOCKER_VOICE_1_GUN_PANIC_3, + SFX_ARMY_VOICE_1_CHASE_1, + SFX_ARMY_VOICE_1_CHASE_2, + SFX_ARMY_VOICE_1_CHASE_3, + SFX_ARMY_VOICE_1_CHASE_4, + SFX_ARMY_VOICE_1_CHASE_5, + SFX_ARMY_VOICE_1_CHASE_6, + SFX_ARMY_VOICE_1_CHASE_7, + SFX_ARMY_VOICE_1_CHASE_8, + SFX_ARMY_VOICE_1_CHASE_9, + SFX_ARMY_VOICE_1_CHASE_10, + SFX_ARMY_VOICE_1_CHASE_11, + SFX_ARMY_VOICE_1_CHASE_12, + SFX_ARMY_VOICE_1_CHASE_13, + SFX_ARMY_VOICE_1_CHASE_14, + SFX_ARMY_VOICE_1_CHASE_15, + SFX_ARMY_VOICE_2_CHASE_1, + SFX_ARMY_VOICE_2_CHASE_2, + SFX_ARMY_VOICE_2_CHASE_3, + SFX_ARMY_VOICE_2_CHASE_4, + SFX_ARMY_VOICE_2_CHASE_5, + SFX_ARMY_VOICE_2_CHASE_6, + SFX_ARMY_VOICE_2_CHASE_7, + SFX_ARMY_VOICE_2_CHASE_8, + SFX_ARMY_VOICE_2_CHASE_9, + SFX_ARMY_VOICE_2_CHASE_10, + SFX_ARMY_VOICE_2_CHASE_11, + SFX_ARMY_VOICE_2_CHASE_12, + SFX_ARMY_VOICE_2_CHASE_13, + SFX_ARMY_VOICE_2_CHASE_14, + SFX_ARMY_VOICE_2_CHASE_15, +#ifdef GTA_PS2 + SFX_PAGER, +#endif + SFX_CLAUDE_LOW_DAMAGE_GRUNT_1, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_2, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_3, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_4, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_5, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_6, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_7, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_8, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_9, + SFX_CLAUDE_LOW_DAMAGE_GRUNT_10, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_1, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_2, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_3, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_4, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_5, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_6, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_7, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_8, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_9, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_10, + SFX_CLAUDE_HIGH_DAMAGE_GRUNT_11, + SFX_CLAUDE_HIT_GROUND_GRUNT_1, + SFX_CLAUDE_HIT_GROUND_GRUNT_2, + SFX_CLAUDE_HIT_GROUND_GRUNT_3, + SFX_CLAUDE_HIT_GROUND_GRUNT_4, + SFX_CLAUDE_HIT_GROUND_GRUNT_5, + SFX_CLAUDE_HIT_GROUND_GRUNT_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DRIVER_ABUSE_7, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_7, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_8, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_9, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CHAT_10, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_7, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_8, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_9, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_DODGE_10, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_CARJACKED_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_MUGGED_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_MUGGED_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_2, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_3, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_4, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_5, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_RUN_FROM_FIGHT_6, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_SHOCKED_1, + SFX_BLACK_PROJECT_FEMALE_OLD_VOICE_1_SHOCKED_2, + SFX_CHUNKY_RUN_1, + SFX_CHUNKY_RUN_2, + SFX_CHUNKY_RUN_3, + SFX_CHUNKY_RUN_4, + SFX_CHUNKY_RUN_5, + SFX_PIMP_DRIVER_ABUSE_1, + SFX_PIMP_DRIVER_ABUSE_2, + SFX_PIMP_DRIVER_ABUSE_3, + SFX_PIMP_DRIVER_ABUSE_4, + SFX_PIMP_DRIVER_ABUSE_5, + SFX_PIMP_CHAT_1, + SFX_PIMP_CHAT_2, + SFX_PIMP_CHAT_3, + SFX_PIMP_CHAT_4, + SFX_PIMP_CHAT_5, + SFX_PIMP_CHAT_6, + SFX_PIMP_CHAT_7, + SFX_PIMP_CHAT_8, + SFX_PIMP_CHAT_9, + SFX_PIMP_CHAT_10, + SFX_PIMP_CHAT_11, + SFX_PIMP_CHAT_12, + SFX_PIMP_CHAT_13, + SFX_PIMP_CHAT_14, + SFX_PIMP_CHAT_15, + SFX_PIMP_CHAT_16, + SFX_PIMP_CHAT_17, + SFX_PIMP_DODGE_1, + SFX_PIMP_DODGE_2, + SFX_PIMP_DODGE_3, + SFX_PIMP_DODGE_4, + SFX_PIMP_DODGE_5, + SFX_PIMP_DODGE_6, + SFX_PIMP_FIGHT_1, + SFX_PIMP_FIGHT_2, + SFX_PIMP_FIGHT_3, + SFX_PIMP_FIGHT_4, + SFX_PIMP_FIGHT_5, + SFX_PIMP_FIGHT_6, + SFX_PIMP_FIGHT_7, + SFX_PIMP_FIGHT_8, + SFX_PIMP_FIGHT_9, + SFX_PIMP_GUN_COOL_1, + SFX_PIMP_GUN_COOL_2, + SFX_PIMP_GUN_COOL_3, + SFX_PIMP_GUN_COOL_4, + SFX_PIMP_GUN_COOL_5, + SFX_PIMP_GUN_COOL_6, + SFX_PIMP_GUN_COOL_7, + SFX_PIMP_CARJACKED_1, + SFX_PIMP_CARJACKED_2, + SFX_PIMP_CARJACKED_3, + SFX_PIMP_CARJACKED_4, + SFX_PIMP_SHOCKED_1, + SFX_PIMP_SHOCKED_2, + SFX_NORMAL_MALE_DRIVER_ABUSE_1, + SFX_NORMAL_MALE_DRIVER_ABUSE_2, + SFX_NORMAL_MALE_DRIVER_ABUSE_3, + SFX_NORMAL_MALE_DRIVER_ABUSE_4, + SFX_NORMAL_MALE_DRIVER_ABUSE_5, + SFX_NORMAL_MALE_DRIVER_ABUSE_6, + SFX_NORMAL_MALE_DRIVER_ABUSE_7, + SFX_NORMAL_MALE_DRIVER_ABUSE_8, + SFX_NORMAL_MALE_DRIVER_ABUSE_9, + SFX_NORMAL_MALE_DRIVER_ABUSE_10, + SFX_NORMAL_MALE_DRIVER_ABUSE_11, + SFX_NORMAL_MALE_DRIVER_ABUSE_12, + SFX_NORMAL_MALE_CHAT_1, + SFX_NORMAL_MALE_CHAT_2, + SFX_NORMAL_MALE_CHAT_3, + SFX_NORMAL_MALE_CHAT_4, + SFX_NORMAL_MALE_CHAT_5, + SFX_NORMAL_MALE_CHAT_6, + SFX_NORMAL_MALE_CHAT_7, + SFX_NORMAL_MALE_CHAT_8, + SFX_NORMAL_MALE_CHAT_9, + SFX_NORMAL_MALE_CHAT_10, + SFX_NORMAL_MALE_CHAT_11, + SFX_NORMAL_MALE_CHAT_12, + SFX_NORMAL_MALE_CHAT_13, + SFX_NORMAL_MALE_CHAT_14, + SFX_NORMAL_MALE_CHAT_15, + SFX_NORMAL_MALE_CHAT_16, + SFX_NORMAL_MALE_CHAT_17, + SFX_NORMAL_MALE_CHAT_18, + SFX_NORMAL_MALE_CHAT_19, + SFX_NORMAL_MALE_CHAT_20, + SFX_NORMAL_MALE_CHAT_21, + SFX_NORMAL_MALE_CHAT_22, + SFX_NORMAL_MALE_CHAT_23, + SFX_NORMAL_MALE_CHAT_24, + SFX_NORMAL_MALE_CHAT_25, + SFX_NORMAL_MALE_DODGE_1, + SFX_NORMAL_MALE_DODGE_2, + SFX_NORMAL_MALE_DODGE_3, + SFX_NORMAL_MALE_DODGE_4, + SFX_NORMAL_MALE_DODGE_5, + SFX_NORMAL_MALE_DODGE_6, + SFX_NORMAL_MALE_DODGE_7, + SFX_NORMAL_MALE_DODGE_8, + SFX_NORMAL_MALE_DODGE_9, + SFX_NORMAL_MALE_EYING_1, + SFX_NORMAL_MALE_EYING_2, + SFX_NORMAL_MALE_EYING_3, + SFX_NORMAL_MALE_EYING_4, + SFX_NORMAL_MALE_EYING_5, + SFX_NORMAL_MALE_EYING_6, + SFX_NORMAL_MALE_EYING_7, + SFX_NORMAL_MALE_EYING_8, + SFX_NORMAL_MALE_GUN_PANIC_1, + SFX_NORMAL_MALE_GUN_PANIC_2, + SFX_NORMAL_MALE_GUN_PANIC_3, + SFX_NORMAL_MALE_GUN_PANIC_4, + SFX_NORMAL_MALE_GUN_PANIC_5, + SFX_NORMAL_MALE_GUN_PANIC_6, + SFX_NORMAL_MALE_GUN_PANIC_7, + SFX_NORMAL_MALE_CARJACKED_1, + SFX_NORMAL_MALE_CARJACKED_2, + SFX_NORMAL_MALE_CARJACKED_3, + SFX_NORMAL_MALE_CARJACKED_4, + SFX_NORMAL_MALE_CARJACKED_5, + SFX_NORMAL_MALE_CARJACKED_6, + SFX_NORMAL_MALE_CARJACKED_7, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_1, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_2, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_3, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_4, + SFX_NORMAL_MALE_RUN_FROM_FIGHT_5, + SFX_NORMAL_MALE_SHOCKED_1, + SFX_NORMAL_MALE_SHOCKED_2, + SFX_NORMAL_MALE_SHOCKED_3, + SFX_NORMAL_MALE_SHOCKED_4, + SFX_NORMAL_MALE_SHOCKED_5, + SFX_NORMAL_MALE_SHOCKED_6, + SFX_NORMAL_MALE_SHOCKED_7, + SFX_NORMAL_MALE_SHOCKED_8, + SFX_NORMAL_MALE_SHOCKED_9, + SFX_NORMAL_MALE_SHOCKED_10, + SFX_BOMBERMAN_1, + SFX_BOMBERMAN_2, + SFX_BOMBERMAN_3, + SFX_BOMBERMAN_4, + SFX_BOMBERMAN_5, + SFX_BOMBERMAN_6, + SFX_BOMBERMAN_7, + SFX_8BALL_DODGE_1, + SFX_8BALL_DODGE_2, + SFX_8BALL_DODGE_3, + SFX_8BALL_DODGE_4, + SFX_8BALL_DODGE_5, + SFX_8BALL_DODGE_6, + SFX_8BALL_DODGE_7, + SFX_8BALL_FIGHT_1, + SFX_8BALL_FIGHT_2, + SFX_8BALL_FIGHT_3, + SFX_8BALL_FIGHT_4, + SFX_8BALL_FIGHT_5, + SFX_8BALL_FIGHT_6, + SFX_8BALL_GUN_COOL_1, + SFX_8BALL_GUN_COOL_2, + SFX_8BALL_MUGGED_1, + SFX_8BALL_MUGGED_2, + SFX_SALVATORE_DODGE_1, + SFX_SALVATORE_DODGE_2, + SFX_SALVATORE_DODGE_3, + SFX_SALVATORE_FIGHT_1, + SFX_SALVATORE_FIGHT_2, + SFX_SALVATORE_FIGHT_3, + SFX_SALVATORE_FIGHT_4, + SFX_SALVATORE_FIGHT_5, + SFX_SALVATORE_FIGHT_6, + SFX_SALVATORE_GUN_COOL_1, + SFX_SALVATORE_GUN_COOL_2, + SFX_SALVATORE_GUN_COOL_3, + SFX_SALVATORE_GUN_COOL_4, + SFX_SALVATORE_MUGGED_1, + SFX_SALVATORE_MUGGED_2, + SFX_MISTY_DODGE_1, + SFX_MISTY_DODGE_2, + SFX_MISTY_DODGE_3, + SFX_MISTY_DODGE_4, + SFX_MISTY_DODGE_5, + SFX_MISTY_FIGHT_1, + SFX_MISTY_FIGHT_2, + SFX_MISTY_FIGHT_3, + SFX_MISTY_FIGHT_4, + SFX_MISTY_GUN_COOL_1, + SFX_MISTY_GUN_COOL_2, + SFX_MISTY_GUN_COOL_3, + SFX_MISTY_GUN_COOL_4, + SFX_MISTY_GUN_COOL_5, + SFX_MISTY_HERE_1, + SFX_MISTY_HERE_2, + SFX_MISTY_HERE_3, + SFX_MISTY_HERE_4, + SFX_MISTY_MUGGED_1, + SFX_MISTY_MUGGED_2, + SFX_MEDIC_VOICE_1_GUN_PANIC_1, + SFX_MEDIC_VOICE_1_GUN_PANIC_2, + SFX_MEDIC_VOICE_1_GUN_PANIC_3, + SFX_MEDIC_VOICE_1_GUN_PANIC_4, + SFX_MEDIC_VOICE_1_GUN_PANIC_5, + SFX_MEDIC_VOICE_1_CARJACKED_1, + SFX_MEDIC_VOICE_1_CARJACKED_2, + SFX_MEDIC_VOICE_1_CARJACKED_3, + SFX_MEDIC_VOICE_1_CARJACKED_4, + SFX_MEDIC_VOICE_1_CARJACKED_5, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_1, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_2, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_3, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_4, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_5, + SFX_MEDIC_VOICE_1_RUN_FROM_FIGHT_6, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_1, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_2, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_3, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_4, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_5, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_6, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_7, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_8, + SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_9, + SFX_MEDIC_VOICE_1_AT_VICTIM_1, + SFX_MEDIC_VOICE_1_AT_VICTIM_2, + SFX_MEDIC_VOICE_1_AT_VICTIM_3, + SFX_MEDIC_VOICE_1_AT_VICTIM_4, + SFX_MEDIC_VOICE_1_AT_VICTIM_5, + SFX_MEDIC_VOICE_1_AT_VICTIM_6, + SFX_MEDIC_VOICE_1_AT_VICTIM_7, + SFX_MEDIC_VOICE_1_AT_VICTIM_8, + SFX_MEDIC_VOICE_1_AT_VICTIM_9, + SFX_MEDIC_VOICE_1_AT_VICTIM_10, + SFX_MEDIC_VOICE_1_AT_VICTIM_11, + SFX_MEDIC_VOICE_1_AT_VICTIM_12, + SFX_MEDIC_VOICE_2_GUN_PANIC_1, + SFX_MEDIC_VOICE_2_GUN_PANIC_2, + SFX_MEDIC_VOICE_2_GUN_PANIC_3, + SFX_MEDIC_VOICE_2_GUN_PANIC_4, + SFX_MEDIC_VOICE_2_GUN_PANIC_5, + SFX_MEDIC_VOICE_2_CARJACKED_1, + SFX_MEDIC_VOICE_2_CARJACKED_2, + SFX_MEDIC_VOICE_2_CARJACKED_3, + SFX_MEDIC_VOICE_2_CARJACKED_4, + SFX_MEDIC_VOICE_2_CARJACKED_5, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_1, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_2, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_3, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_4, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_5, + SFX_MEDIC_VOICE_2_RUN_FROM_FIGHT_6, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_1, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_2, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_3, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_4, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_5, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_6, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_7, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_8, + SFX_MEDIC_VOICE_2_GET_OUT_VAN_CHAT_9, + SFX_MEDIC_VOICE_2_AT_VICTIM_1, + SFX_MEDIC_VOICE_2_AT_VICTIM_2, + SFX_MEDIC_VOICE_2_AT_VICTIM_3, + SFX_MEDIC_VOICE_2_AT_VICTIM_4, + SFX_MEDIC_VOICE_2_AT_VICTIM_5, + SFX_MEDIC_VOICE_2_AT_VICTIM_6, + SFX_MEDIC_VOICE_2_AT_VICTIM_7, + SFX_MEDIC_VOICE_2_AT_VICTIM_8, + SFX_MEDIC_VOICE_2_AT_VICTIM_9, + SFX_MEDIC_VOICE_2_AT_VICTIM_10, + SFX_MEDIC_VOICE_2_AT_VICTIM_11, + SFX_MEDIC_VOICE_2_AT_VICTIM_12, + SFX_PLASTER_BLOKE_1, + SFX_PLASTER_BLOKE_2, + SFX_PLASTER_BLOKE_3, + SFX_PLASTER_BLOKE_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CHAT_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_DODGE_5, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_EYING_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_4, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_FIGHT_5, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_GUN_PANIC_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_GUN_PANIC_2, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_GUN_PANIC_3, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CARJACKED_1, + SFX_BLACK_CONSTRUCTION_MALE_VOICE_1_CARJACKED_2, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_FOOTBALL_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_1, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_2, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_3, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_4, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_5, + SFX_FOOTBALL_FEMALE_VOICE_1_CHAT_6, + SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_1, + SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_2, + SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_3, + SFX_FOOTBALL_FEMALE_VOICE_1_DODGE_4, + SFX_FOOTBALL_FEMALE_VOICE_1_MUGGED_1, + SFX_FOOTBALL_FEMALE_VOICE_1_SHOCKED_1, + SFX_FOOTBALL_FEMALE_VOICE_1_SHOCKED_2, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_1, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_2, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_3, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_4, + SFX_FOOTBALL_FEMALE_VOICE_2_DRIVER_ABUSE_5, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_1, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_2, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_3, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_4, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_5, + SFX_FOOTBALL_FEMALE_VOICE_2_CHAT_6, + SFX_FOOTBALL_FEMALE_VOICE_2_DODGE_1, + SFX_FOOTBALL_FEMALE_VOICE_2_DODGE_2, + SFX_FOOTBALL_FEMALE_VOICE_2_DODGE_3, + SFX_FOOTBALL_FEMALE_VOICE_2_DODGE_4, + SFX_FOOTBALL_FEMALE_VOICE_2_MUGGED_1, + SFX_FOOTBALL_FEMALE_VOICE_2_SHOCKED_1, + SFX_FOOTBALL_FEMALE_VOICE_2_SHOCKED_2, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_FOOTBALL_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_1, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_2, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_3, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_4, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_5, + SFX_FOOTBALL_MALE_VOICE_1_CHAT_6, + SFX_FOOTBALL_MALE_VOICE_1_DODGE_1, + SFX_FOOTBALL_MALE_VOICE_1_DODGE_2, + SFX_FOOTBALL_MALE_VOICE_1_DODGE_3, + SFX_FOOTBALL_MALE_VOICE_1_DODGE_4, + SFX_FOOTBALL_MALE_VOICE_1_FIGHT_1, + SFX_FOOTBALL_MALE_VOICE_1_FIGHT_2, + SFX_FOOTBALL_MALE_VOICE_1_FIGHT_3, + SFX_FOOTBALL_MALE_VOICE_1_SHOCKED_1, + SFX_FOOTBALL_MALE_VOICE_1_SHOCKED_2, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_FOOTBALL_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_1, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_2, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_3, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_4, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_5, + SFX_FOOTBALL_MALE_VOICE_2_CHAT_6, + SFX_FOOTBALL_MALE_VOICE_2_DODGE_1, + SFX_FOOTBALL_MALE_VOICE_2_DODGE_2, + SFX_FOOTBALL_MALE_VOICE_2_DODGE_3, + SFX_FOOTBALL_MALE_VOICE_2_DODGE_4, + SFX_FOOTBALL_MALE_VOICE_2_FIGHT_1, + SFX_FOOTBALL_MALE_VOICE_2_FIGHT_2, + SFX_FOOTBALL_MALE_VOICE_2_FIGHT_3, + SFX_FOOTBALL_MALE_VOICE_2_SHOCKED_1, + SFX_FOOTBALL_MALE_VOICE_2_SHOCKED_2, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_MODEL_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_MODEL_FEMALE_VOICE_1_CHAT_1, + SFX_MODEL_FEMALE_VOICE_1_CHAT_2, + SFX_MODEL_FEMALE_VOICE_1_CHAT_3, + SFX_MODEL_FEMALE_VOICE_1_CHAT_4, + SFX_MODEL_FEMALE_VOICE_1_CHAT_5, + SFX_MODEL_FEMALE_VOICE_1_CHAT_6, + SFX_MODEL_FEMALE_VOICE_1_CHAT_7, + SFX_MODEL_FEMALE_VOICE_1_CHAT_8, + SFX_MODEL_FEMALE_VOICE_1_DODGE_1, + SFX_MODEL_FEMALE_VOICE_1_DODGE_2, + SFX_MODEL_FEMALE_VOICE_1_DODGE_3, + SFX_MODEL_FEMALE_VOICE_1_DODGE_4, + SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_MODEL_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_MODEL_FEMALE_VOICE_1_MUGGED_1, + SFX_MODEL_FEMALE_VOICE_1_MUGGED_2, + SFX_MODEL_FEMALE_VOICE_1_MUGGED_3, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_1, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_2, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_3, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_4, + SFX_MODEL_FEMALE_VOICE_1_SHOCKED_5, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_MODEL_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_MODEL_MALE_VOICE_1_CHAT_1, + SFX_MODEL_MALE_VOICE_1_CHAT_2, + SFX_MODEL_MALE_VOICE_1_CHAT_3, + SFX_MODEL_MALE_VOICE_1_CHAT_4, + SFX_MODEL_MALE_VOICE_1_CHAT_5, + SFX_MODEL_MALE_VOICE_1_CHAT_6, + SFX_MODEL_MALE_VOICE_1_DODGE_1, + SFX_MODEL_MALE_VOICE_1_DODGE_2, + SFX_MODEL_MALE_VOICE_1_DODGE_3, + SFX_MODEL_MALE_VOICE_1_DODGE_4, + SFX_MODEL_MALE_VOICE_1_DODGE_5, + SFX_MODEL_MALE_VOICE_1_DODGE_6, + SFX_MODEL_MALE_VOICE_1_EYING_1, + SFX_MODEL_MALE_VOICE_1_EYING_2, + SFX_MODEL_MALE_VOICE_1_EYING_3, + SFX_MODEL_MALE_VOICE_1_FIGHT_1, + SFX_MODEL_MALE_VOICE_1_FIGHT_2, + SFX_MODEL_MALE_VOICE_1_FIGHT_3, + SFX_MODEL_MALE_VOICE_1_FIGHT_4, + SFX_MODEL_MALE_VOICE_1_FIGHT_5, + SFX_MODEL_MALE_VOICE_1_CARJACKED_1, + SFX_MODEL_MALE_VOICE_1_CARJACKED_2, + SFX_MODEL_MALE_VOICE_1_MUGGED_1, + SFX_MODEL_MALE_VOICE_1_MUGGED_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_4, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_5, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_6, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_4, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_5, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CHAT_6, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_4, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_DODGE_5, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_EYING_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_EYING_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_EYING_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_3, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_4, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_5, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_FIGHT_6, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_GUN_PANIC_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_GUN_PANIC_2, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CARJACKED_1, + SFX_CHINATOWN_MALE_YOUNG_VOICE_1_CARJACKED_2, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_SCUM_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_SCUM_MALE_VOICE_1_CHAT_1, + SFX_SCUM_MALE_VOICE_1_CHAT_2, + SFX_SCUM_MALE_VOICE_1_CHAT_3, + SFX_SCUM_MALE_VOICE_1_CHAT_4, + SFX_SCUM_MALE_VOICE_1_CHAT_5, + SFX_SCUM_MALE_VOICE_1_CHAT_6, + SFX_SCUM_MALE_VOICE_1_CHAT_7, + SFX_SCUM_MALE_VOICE_1_CHAT_8, + SFX_SCUM_MALE_VOICE_1_CHAT_9, + SFX_SCUM_MALE_VOICE_1_DODGE_1, + SFX_SCUM_MALE_VOICE_1_DODGE_2, + SFX_SCUM_MALE_VOICE_1_DODGE_3, + SFX_SCUM_MALE_VOICE_1_DODGE_4, + SFX_SCUM_MALE_VOICE_1_DODGE_5, + SFX_SCUM_MALE_VOICE_1_EYING_1, + SFX_SCUM_MALE_VOICE_1_EYING_2, + SFX_SCUM_MALE_VOICE_1_EYING_3, + SFX_SCUM_MALE_VOICE_1_EYING_4, + SFX_SCUM_MALE_VOICE_1_EYING_5, + SFX_SCUM_MALE_VOICE_1_FIGHT_1, + SFX_SCUM_MALE_VOICE_1_FIGHT_2, + SFX_SCUM_MALE_VOICE_1_FIGHT_3, + SFX_SCUM_MALE_VOICE_1_FIGHT_4, + SFX_SCUM_MALE_VOICE_1_FIGHT_5, + SFX_SCUM_MALE_VOICE_1_FIGHT_6, + SFX_SCUM_MALE_VOICE_1_FIGHT_7, + SFX_SCUM_MALE_VOICE_1_FIGHT_8, + SFX_SCUM_MALE_VOICE_1_FIGHT_9, + SFX_SCUM_MALE_VOICE_1_FIGHT_10, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_1, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_2, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_3, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_4, + SFX_SCUM_MALE_VOICE_1_GUN_PANIC_5, + SFX_SCUM_MALE_VOICE_1_LOST_1, + SFX_SCUM_MALE_VOICE_1_LOST_2, + SFX_SCUM_MALE_VOICE_1_LOST_3, + SFX_SCUM_MALE_VOICE_1_MUGGED_1, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_SCUM_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_SCUM_FEMALE_VOICE_1_CHAT_1, + SFX_SCUM_FEMALE_VOICE_1_CHAT_2, + SFX_SCUM_FEMALE_VOICE_1_CHAT_3, + SFX_SCUM_FEMALE_VOICE_1_CHAT_4, + SFX_SCUM_FEMALE_VOICE_1_CHAT_5, + SFX_SCUM_FEMALE_VOICE_1_CHAT_6, + SFX_SCUM_FEMALE_VOICE_1_CHAT_7, + SFX_SCUM_FEMALE_VOICE_1_CHAT_8, + SFX_SCUM_FEMALE_VOICE_1_CHAT_9, + SFX_SCUM_FEMALE_VOICE_1_CHAT_10, + SFX_SCUM_FEMALE_VOICE_1_CHAT_11, + SFX_SCUM_FEMALE_VOICE_1_CHAT_12, + SFX_SCUM_FEMALE_VOICE_1_CHAT_13, + SFX_SCUM_FEMALE_VOICE_1_DODGE_1, + SFX_SCUM_FEMALE_VOICE_1_DODGE_2, + SFX_SCUM_FEMALE_VOICE_1_DODGE_3, + SFX_SCUM_FEMALE_VOICE_1_DODGE_4, + SFX_SCUM_FEMALE_VOICE_1_DODGE_5, + SFX_SCUM_FEMALE_VOICE_1_DODGE_6, + SFX_SCUM_FEMALE_VOICE_1_DODGE_7, + SFX_SCUM_FEMALE_VOICE_1_DODGE_8, + SFX_SCUM_FEMALE_VOICE_1_FIGHT_1, + SFX_SCUM_FEMALE_VOICE_1_FIGHT_2, + SFX_SCUM_FEMALE_VOICE_1_FIGHT_3, + SFX_SCUM_FEMALE_VOICE_1_FIGHT_4, + SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_SCUM_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_SCUM_FEMALE_VOICE_1_MUGGED_1, + SFX_SCUM_FEMALE_VOICE_1_MUGGED_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_5, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_6, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CHAT_7, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_DODGE_5, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_GUN_PANIC_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_CARJACKED_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_MUGGED_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_MUGGED_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_1, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_2, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_3, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_4, + SFX_BLACK_PROJECT_FEMALE_YOUNG_VOICE_1_SHOCKED_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DRIVER_ABUSE_6, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CHAT_6, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_DODGE_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_FIGHT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_GUN_PANIC_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_GUN_PANIC_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_GUN_PANIC_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CARJACKED_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_CARJACKED_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_MUGGED_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_MUGGED_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_1_RUN_FROM_FIGHT_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DRIVER_ABUSE_6, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_5, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CHAT_6, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DODGE_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DODGE_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DODGE_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_DODGE_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_FIGHT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_FIGHT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_FIGHT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_FIGHT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_GUN_PANIC_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_GUN_PANIC_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_GUN_PANIC_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CARJACKED_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_CARJACKED_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_MUGGED_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_MUGGED_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_1, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_2, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_3, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_4, + SFX_BUSINESS_MALE_YOUNG_VOICE_2_RUN_FROM_FIGHT_5, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_5, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_6, + SFX_BLACK_FAT_FEMALE_VOICE_1_CHAT_7, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_DODGE_5, + SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_CARJACKED_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_CARJACKED_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_MUGGED_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_MUGGED_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_1, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_2, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_3, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_4, + SFX_BLACK_FAT_FEMALE_VOICE_1_SHOCKED_5, + SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_4, + SFX_WHITE_DOCKER_MALE_VOICE_1_CHAT_5, + SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_DODGE_4, + SFX_WHITE_DOCKER_MALE_VOICE_1_EYING_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_EYING_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_EYING_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_FIGHT_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_FIGHT_2, + SFX_WHITE_DOCKER_MALE_VOICE_1_FIGHT_3, + SFX_WHITE_DOCKER_MALE_VOICE_1_GUN_PANIC_1, + SFX_WHITE_DOCKER_MALE_VOICE_1_GUN_PANIC_2, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_HOSPITAL_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_1, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_2, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_3, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_4, + SFX_HOSPITAL_MALE_VOICE_1_CHAT_5, + SFX_HOSPITAL_MALE_VOICE_1_DODGE_1, + SFX_HOSPITAL_MALE_VOICE_1_DODGE_2, + SFX_HOSPITAL_MALE_VOICE_1_DODGE_3, + SFX_HOSPITAL_MALE_VOICE_1_DODGE_4, + SFX_HOSPITAL_MALE_VOICE_1_FIGHT_1, + SFX_HOSPITAL_MALE_VOICE_1_FIGHT_2, + SFX_HOSPITAL_MALE_VOICE_1_FIGHT_3, + SFX_HOSPITAL_MALE_VOICE_1_FIGHT_4, + SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_1, + SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_2, + SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_3, + SFX_HOSPITAL_MALE_VOICE_1_GUN_PANIC_4, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_HOSPITAL_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_1, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_2, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_3, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_4, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_5, + SFX_HOSPITAL_FEMALE_VOICE_1_CHAT_6, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_1, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_2, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_3, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_4, + SFX_HOSPITAL_FEMALE_VOICE_1_DODGE_5, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_1, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_2, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_3, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_4, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_5, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_6, + SFX_FEMALE_1_VOICE_1_DRIVER_ABUSE_7, + SFX_FEMALE_1_VOICE_1_CHAT_1, + SFX_FEMALE_1_VOICE_1_CHAT_2, + SFX_FEMALE_1_VOICE_1_CHAT_3, + SFX_FEMALE_1_VOICE_1_CHAT_4, + SFX_FEMALE_1_VOICE_1_CHAT_5, + SFX_FEMALE_1_VOICE_1_CHAT_6, + SFX_FEMALE_1_VOICE_1_CHAT_7, + SFX_FEMALE_1_VOICE_1_CHAT_8, + SFX_FEMALE_1_VOICE_1_DODGE_1, + SFX_FEMALE_1_VOICE_1_DODGE_2, + SFX_FEMALE_1_VOICE_1_DODGE_3, + SFX_FEMALE_1_VOICE_1_DODGE_4, + SFX_FEMALE_1_VOICE_1_DODGE_5, + SFX_FEMALE_1_VOICE_1_DODGE_6, + SFX_FEMALE_1_VOICE_1_GUN_PANIC_1, + SFX_FEMALE_1_VOICE_1_GUN_PANIC_2, + SFX_FEMALE_1_VOICE_1_CARJACKED_1, + SFX_FEMALE_1_VOICE_1_CARJACKED_2, + SFX_FEMALE_1_VOICE_1_MUGGED_1, + SFX_FEMALE_1_VOICE_1_MUGGED_2, + SFX_FEMALE_1_VOICE_1_MUGGED_3, + SFX_FEMALE_1_VOICE_1_RUN_FROM_FIGHT_1, + SFX_FEMALE_1_VOICE_1_RUN_FROM_FIGHT_2, + SFX_FEMALE_1_VOICE_1_SHOCKED_1, + SFX_FEMALE_1_VOICE_1_SHOCKED_2, + SFX_FEMALE_1_VOICE_1_SHOCKED_3, + SFX_FEMALE_1_VOICE_1_SHOCKED_4, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_1, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_2, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_3, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_4, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_5, + SFX_FEMALE_3_VOICE_1_DRIVER_ABUSE_6, + SFX_FEMALE_3_VOICE_1_CHAT_1, + SFX_FEMALE_3_VOICE_1_CHAT_2, + SFX_FEMALE_3_VOICE_1_CHAT_3, + SFX_FEMALE_3_VOICE_1_CHAT_4, + SFX_FEMALE_3_VOICE_1_CHAT_5, + SFX_FEMALE_3_VOICE_1_DODGE_1, + SFX_FEMALE_3_VOICE_1_DODGE_2, + SFX_FEMALE_3_VOICE_1_DODGE_3, + SFX_FEMALE_3_VOICE_1_DODGE_4, + SFX_FEMALE_3_VOICE_1_DODGE_5, + SFX_FEMALE_3_VOICE_1_DODGE_6, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_1, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_2, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_3, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_4, + SFX_FEMALE_3_VOICE_1_GUN_PANIC_5, + SFX_FEMALE_3_VOICE_1_CARJACKED_1, + SFX_FEMALE_3_VOICE_1_CARJACKED_2, + SFX_FEMALE_3_VOICE_1_CARJACKED_3, + SFX_FEMALE_3_VOICE_1_MUGGED_1, + SFX_FEMALE_3_VOICE_1_MUGGED_2, + SFX_FEMALE_3_VOICE_1_MUGGED_3, + SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_1, + SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_2, + SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_3, + SFX_FEMALE_3_VOICE_1_RUN_FROM_FIGHT_4, + SFX_FEMALE_3_VOICE_1_SHOCKED_1, + SFX_FEMALE_3_VOICE_1_SHOCKED_2, + SFX_FEMALE_3_VOICE_1_SHOCKED_3, + SFX_FEMALE_3_VOICE_1_SHOCKED_4, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_2, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_3, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_4, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_5, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_6, + SFX_CASUAL_MALE_OLD_VOICE_1_DRIVER_ABUSE_7, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_1, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_2, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_3, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_4, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_5, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_6, + SFX_CASUAL_MALE_OLD_VOICE_1_CHAT_7, + SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_1, + SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_2, + SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_3, + SFX_CASUAL_MALE_OLD_VOICE_1_DODGE_4, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_1, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_2, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_3, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_4, + SFX_CASUAL_MALE_OLD_VOICE_1_EYING_5, + SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_1, + SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_2, + SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_3, + SFX_CASUAL_MALE_OLD_VOICE_1_FIGHT_4, + SFX_CASUAL_MALE_OLD_VOICE_1_CARJACKED_1, + SFX_CASUAL_MALE_OLD_VOICE_1_CARJACKED_2, + SFX_CASUAL_MALE_OLD_VOICE_1_CARJACKED_3, + SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_1, + SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_2, + SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_3, + SFX_CASUAL_MALE_OLD_VOICE_1_MUGGED_4, + SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_STUDENT_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_STUDENT_MALE_VOICE_1_CHAT_1, + SFX_STUDENT_MALE_VOICE_1_CHAT_2, + SFX_STUDENT_MALE_VOICE_1_CHAT_3, + SFX_STUDENT_MALE_VOICE_1_CHAT_4, + SFX_STUDENT_MALE_VOICE_1_CHAT_5, + SFX_STUDENT_MALE_VOICE_1_DODGE_1, + SFX_STUDENT_MALE_VOICE_1_DODGE_2, + SFX_STUDENT_MALE_VOICE_1_DODGE_3, + SFX_STUDENT_MALE_VOICE_1_DODGE_4, + SFX_STUDENT_MALE_VOICE_1_FIGHT_1, + SFX_STUDENT_MALE_VOICE_1_FIGHT_2, + SFX_STUDENT_MALE_VOICE_1_FIGHT_3, + SFX_STUDENT_MALE_VOICE_1_FIGHT_4, + SFX_STUDENT_MALE_VOICE_1_GUN_PANIC_1, + SFX_STUDENT_MALE_VOICE_1_GUN_PANIC_2, + SFX_STUDENT_MALE_VOICE_1_MUGGED_1, + SFX_STUDENT_MALE_VOICE_1_MUGGED_2, + SFX_STUDENT_MALE_VOICE_1_SHOCKED_1, + SFX_STUDENT_MALE_VOICE_1_SHOCKED_2, + SFX_STUDENT_MALE_VOICE_1_SHOCKED_3, + SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_STUDENT_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_STUDENT_FEMALE_VOICE_1_CHAT_1, + SFX_STUDENT_FEMALE_VOICE_1_CHAT_2, + SFX_STUDENT_FEMALE_VOICE_1_CHAT_3, + SFX_STUDENT_FEMALE_VOICE_1_CHAT_4, + SFX_STUDENT_FEMALE_VOICE_1_DODGE_1, + SFX_STUDENT_FEMALE_VOICE_1_DODGE_2, + SFX_STUDENT_FEMALE_VOICE_1_DODGE_3, + SFX_STUDENT_FEMALE_VOICE_1_DODGE_4, + SFX_STUDENT_FEMALE_VOICE_1_FIGHT_1, + SFX_STUDENT_FEMALE_VOICE_1_FIGHT_2, + SFX_STUDENT_FEMALE_VOICE_1_FIGHT_3, + SFX_STUDENT_FEMALE_VOICE_1_FIGHT_4, + SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_STUDENT_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_STUDENT_FEMALE_VOICE_1_MUGGED_1, + SFX_STUDENT_FEMALE_VOICE_1_MUGGED_2, + SFX_STUDENT_FEMALE_VOICE_1_SHOCKED_1, + SFX_STUDENT_FEMALE_VOICE_1_SHOCKED_2, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_HOOD_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_HOOD_MALE_VOICE_1_CHAT_1, + SFX_HOOD_MALE_VOICE_1_CHAT_2, + SFX_HOOD_MALE_VOICE_1_CHAT_3, + SFX_HOOD_MALE_VOICE_1_CHAT_4, + SFX_HOOD_MALE_VOICE_1_CHAT_5, + SFX_HOOD_MALE_VOICE_1_CHAT_6, + SFX_HOOD_MALE_VOICE_1_DODGE_1, + SFX_HOOD_MALE_VOICE_1_DODGE_2, + SFX_HOOD_MALE_VOICE_1_DODGE_3, + SFX_HOOD_MALE_VOICE_1_DODGE_4, + SFX_HOOD_MALE_VOICE_1_DODGE_5, + SFX_HOOD_MALE_VOICE_1_EYING_1, + SFX_HOOD_MALE_VOICE_1_EYING_2, + SFX_HOOD_MALE_VOICE_1_FIGHT_1, + SFX_HOOD_MALE_VOICE_1_FIGHT_2, + SFX_HOOD_MALE_VOICE_1_FIGHT_3, + SFX_HOOD_MALE_VOICE_1_FIGHT_4, + SFX_HOOD_MALE_VOICE_1_FIGHT_5, + SFX_HOOD_MALE_VOICE_1_FIGHT_6, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_1, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_2, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_3, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_4, + SFX_HOOD_MALE_VOICE_1_GUN_COOL_5, + SFX_HOOD_MALE_VOICE_1_CARJACKED_1, + SFX_HOOD_MALE_VOICE_1_CARJACKED_2, + SFX_HOOD_MALE_VOICE_1_CARJACKING_1, + SFX_HOOD_MALE_VOICE_1_CARJACKING_2, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_HOOD_MALE_VOICE_2_DRIVER_ABUSE_7, + SFX_HOOD_MALE_VOICE_2_CHAT_1, + SFX_HOOD_MALE_VOICE_2_CHAT_2, + SFX_HOOD_MALE_VOICE_2_CHAT_3, + SFX_HOOD_MALE_VOICE_2_CHAT_4, + SFX_HOOD_MALE_VOICE_2_CHAT_5, + SFX_HOOD_MALE_VOICE_2_CHAT_6, + SFX_HOOD_MALE_VOICE_2_DODGE_1, + SFX_HOOD_MALE_VOICE_2_DODGE_2, + SFX_HOOD_MALE_VOICE_2_DODGE_3, + SFX_HOOD_MALE_VOICE_2_DODGE_4, + SFX_HOOD_MALE_VOICE_2_DODGE_5, + SFX_HOOD_MALE_VOICE_2_EYING_1, + SFX_HOOD_MALE_VOICE_2_EYING_2, + SFX_HOOD_MALE_VOICE_2_FIGHT_1, + SFX_HOOD_MALE_VOICE_2_FIGHT_2, + SFX_HOOD_MALE_VOICE_2_FIGHT_3, + SFX_HOOD_MALE_VOICE_2_FIGHT_4, + SFX_HOOD_MALE_VOICE_2_FIGHT_5, + SFX_HOOD_MALE_VOICE_2_FIGHT_6, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_1, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_2, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_3, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_4, + SFX_HOOD_MALE_VOICE_2_GUN_COOL_5, + SFX_HOOD_MALE_VOICE_2_CARJACKED_1, + SFX_HOOD_MALE_VOICE_2_CARJACKED_2, + SFX_HOOD_MALE_VOICE_2_CARJACKING_1, + SFX_HOOD_MALE_VOICE_2_CARJACKING_2, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_YARDIE_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_YARDIE_MALE_VOICE_1_CHAT_1, + SFX_YARDIE_MALE_VOICE_1_CHAT_2, + SFX_YARDIE_MALE_VOICE_1_CHAT_3, + SFX_YARDIE_MALE_VOICE_1_CHAT_4, + SFX_YARDIE_MALE_VOICE_1_CHAT_5, + SFX_YARDIE_MALE_VOICE_1_CHAT_6, + SFX_YARDIE_MALE_VOICE_1_CHAT_7, + SFX_YARDIE_MALE_VOICE_1_CHAT_8, + SFX_YARDIE_MALE_VOICE_1_DODGE_1, + SFX_YARDIE_MALE_VOICE_1_DODGE_2, + SFX_YARDIE_MALE_VOICE_1_DODGE_3, + SFX_YARDIE_MALE_VOICE_1_DODGE_4, + SFX_YARDIE_MALE_VOICE_1_DODGE_5, + SFX_YARDIE_MALE_VOICE_1_EYING_1, + SFX_YARDIE_MALE_VOICE_1_EYING_2, + SFX_YARDIE_MALE_VOICE_1_FIGHT_1, + SFX_YARDIE_MALE_VOICE_1_FIGHT_2, + SFX_YARDIE_MALE_VOICE_1_FIGHT_3, + SFX_YARDIE_MALE_VOICE_1_FIGHT_4, + SFX_YARDIE_MALE_VOICE_1_FIGHT_5, + SFX_YARDIE_MALE_VOICE_1_FIGHT_6, + SFX_YARDIE_MALE_VOICE_1_GUN_COOL_1, + SFX_YARDIE_MALE_VOICE_1_CARJACKED_1, + SFX_YARDIE_MALE_VOICE_1_CARJACKING_1, + SFX_YARDIE_MALE_VOICE_1_CARJACKING_2, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_YARDIE_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_YARDIE_MALE_VOICE_2_CHAT_1, + SFX_YARDIE_MALE_VOICE_2_CHAT_2, + SFX_YARDIE_MALE_VOICE_2_CHAT_3, + SFX_YARDIE_MALE_VOICE_2_CHAT_4, + SFX_YARDIE_MALE_VOICE_2_CHAT_5, + SFX_YARDIE_MALE_VOICE_2_CHAT_6, + SFX_YARDIE_MALE_VOICE_2_CHAT_7, + SFX_YARDIE_MALE_VOICE_2_CHAT_8, + SFX_YARDIE_MALE_VOICE_2_DODGE_1, + SFX_YARDIE_MALE_VOICE_2_DODGE_2, + SFX_YARDIE_MALE_VOICE_2_DODGE_3, + SFX_YARDIE_MALE_VOICE_2_DODGE_4, + SFX_YARDIE_MALE_VOICE_2_DODGE_5, + SFX_YARDIE_MALE_VOICE_2_EYING_1, + SFX_YARDIE_MALE_VOICE_2_EYING_2, + SFX_YARDIE_MALE_VOICE_2_FIGHT_1, + SFX_YARDIE_MALE_VOICE_2_FIGHT_2, + SFX_YARDIE_MALE_VOICE_2_FIGHT_3, + SFX_YARDIE_MALE_VOICE_2_FIGHT_4, + SFX_YARDIE_MALE_VOICE_2_FIGHT_5, + SFX_YARDIE_MALE_VOICE_2_FIGHT_6, + SFX_YARDIE_MALE_VOICE_2_GUN_COOL_1, + SFX_YARDIE_MALE_VOICE_2_CARJACKED_1, + SFX_YARDIE_MALE_VOICE_2_CARJACKING_1, + SFX_YARDIE_MALE_VOICE_2_CARJACKING_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_6, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CHAT_7, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_DODGE_6, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_CARAJACKED_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_MUGGED_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_MUGGED_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_MUGGED_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_4, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_5, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_6, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_1, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_2, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_3, + SFX_BLACK_BUSINESS_FEMALE_VOICE_1_SHOCKED_4, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_WORKER_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_1, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_2, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_3, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_4, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_5, + SFX_WHITE_WORKER_MALE_VOICE_1_CHAT_6, + SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_1, + SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_2, + SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_3, + SFX_WHITE_WORKER_MALE_VOICE_1_DODGE_4, + SFX_WHITE_WORKER_MALE_VOICE_1_EYING_1, + SFX_WHITE_WORKER_MALE_VOICE_1_EYING_2, + SFX_WHITE_WORKER_MALE_VOICE_1_FIGHT_1, + SFX_WHITE_WORKER_MALE_VOICE_1_FIGHT_2, + SFX_WHITE_WORKER_MALE_VOICE_1_FIGHT_3, + SFX_WHITE_WORKER_MALE_VOICE_1_GUN_PANIC_1, + SFX_WHITE_WORKER_MALE_VOICE_1_GUN_PANIC_2, + SFX_WHITE_WORKER_MALE_VOICE_1_GUN_PANIC_3, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_STEWARD_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_STEWARD_MALE_VOICE_1_CHAT_1, + SFX_STEWARD_MALE_VOICE_1_CHAT_2, + SFX_STEWARD_MALE_VOICE_1_CHAT_3, + SFX_STEWARD_MALE_VOICE_1_CHAT_4, + SFX_STEWARD_MALE_VOICE_1_DODGE_1, + SFX_STEWARD_MALE_VOICE_1_DODGE_2, + SFX_STEWARD_MALE_VOICE_1_DODGE_3, + SFX_STEWARD_MALE_VOICE_1_FIGHT_1, + SFX_STEWARD_MALE_VOICE_1_FIGHT_2, + SFX_STEWARD_MALE_VOICE_1_FIGHT_3, + SFX_STEWARD_MALE_VOICE_1_FIGHT_4, + SFX_STEWARD_MALE_VOICE_1_GUN_PANIC_1, + SFX_STEWARD_MALE_VOICE_1_GUN_PANIC_2, + SFX_STEWARD_MALE_VOICE_1_GUN_PANIC_3, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_STEWARD_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_1, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_2, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_3, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_4, + SFX_STEWARD_FEMALE_VOICE_1_CHAT_5, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_1, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_2, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_3, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_4, + SFX_STEWARD_FEMALE_VOICE_1_DODGE_5, + SFX_STEWARD_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_STEWARD_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_STEWARD_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_1, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_2, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_3, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_4, + SFX_STEWARD_FEMALE_VOICE_2_DRIVER_ABUSE_5, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_1, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_2, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_3, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_4, + SFX_STEWARD_FEMALE_VOICE_2_CHAT_5, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_1, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_2, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_3, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_4, + SFX_STEWARD_FEMALE_VOICE_2_DODGE_5, + SFX_STEWARD_FEMALE_VOICE_2_GUN_PANIC_1, + SFX_STEWARD_FEMALE_VOICE_2_GUN_PANIC_2, + SFX_STEWARD_FEMALE_VOICE_2_GUN_PANIC_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_4, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_5, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DRIVER_ABUSE_6, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_4, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_5, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_6, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CHAT_7, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_4, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_5, + SFX_CHINATOWN_MALE_OLD_VOICE_1_DODGE_6, + SFX_CHINATOWN_MALE_OLD_VOICE_1_EYING_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_EYING_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_EYING_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_4, + SFX_CHINATOWN_MALE_OLD_VOICE_1_FIGHT_5, + SFX_CHINATOWN_MALE_OLD_VOICE_1_GUN_PANIC_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_GUN_PANIC_2, + SFX_CHINATOWN_MALE_OLD_VOICE_1_GUN_PANIC_3, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CARJACKED_1, + SFX_CHINATOWN_MALE_OLD_VOICE_1_CARJACKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_6, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CHAT_7, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_DODGE_6, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_GUN_PANIC_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CARJACKED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_CARJACKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_MUGGED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_MUGGED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_RUN_FROM_FIGHT_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_1_SHOCKED_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DRIVER_ABUSE_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_6, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CHAT_7, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_5, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_DODGE_6, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_GUN_PANIC_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_GUN_PANIC_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_GUN_PANIC_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_GUN_PANIC_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CARJACKED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_CARJACKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_MUGGED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_MUGGED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_RUN_FROM_FIGHT_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_RUN_FROM_FIGHT_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_RUN_FROM_FIGHT_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_RUN_FROM_FIGHT_4, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_SHOCKED_1, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_SHOCKED_2, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_SHOCKED_3, + SFX_WHITE_BUSINESS_FEMALE_VOICE_2_SHOCKED_4, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_FAT_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_1, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_2, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_3, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_4, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_5, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_6, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_7, + SFX_BLACK_FAT_MALE_VOICE_1_CHAT_8, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_1, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_2, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_3, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_4, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_5, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_6, + SFX_BLACK_FAT_MALE_VOICE_1_DODGE_7, + SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_1, + SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_2, + SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_3, + SFX_BLACK_FAT_MALE_VOICE_1_CARJACKED_4, + SFX_BLACK_FAT_MALE_VOICE_1_LOST_1, + SFX_BLACK_FAT_MALE_VOICE_1_LOST_2, + SFX_BLACK_FAT_MALE_VOICE_1_LOST_3, + SFX_BLACK_FAT_MALE_VOICE_1_MUGGED_1, + SFX_BLACK_FAT_MALE_VOICE_1_MUGGED_2, + SFX_BLACK_FAT_MALE_VOICE_1_MUGGED_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_BLACK_PROJECT_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_4, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_5, + SFX_BLACK_PROJECT_MALE_VOICE_1_CHAT_6, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_4, + SFX_BLACK_PROJECT_MALE_VOICE_1_DODGE_5, + SFX_BLACK_PROJECT_MALE_VOICE_1_EYING_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_EYING_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_EYING_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_4, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_5, + SFX_BLACK_PROJECT_MALE_VOICE_1_FIGHT_6, + SFX_BLACK_PROJECT_MALE_VOICE_1_GUN_COOL_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_GUN_COOL_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_GUN_COOL_3, + SFX_BLACK_PROJECT_MALE_VOICE_1_CARJACKED_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_CARJACKED_2, + SFX_BLACK_PROJECT_MALE_VOICE_1_MUGGED_1, + SFX_BLACK_PROJECT_MALE_VOICE_1_MUGGED_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_BLACK_PROJECT_MALE_VOICE_2_DRIVER_ABUSE_7, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_4, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_5, + SFX_BLACK_PROJECT_MALE_VOICE_2_CHAT_6, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_4, + SFX_BLACK_PROJECT_MALE_VOICE_2_DODGE_5, + SFX_BLACK_PROJECT_MALE_VOICE_2_EYING_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_EYING_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_EYING_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_4, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_5, + SFX_BLACK_PROJECT_MALE_VOICE_2_FIGHT_6, + SFX_BLACK_PROJECT_MALE_VOICE_2_GUN_COOL_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_GUN_COOL_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_GUN_COOL_3, + SFX_BLACK_PROJECT_MALE_VOICE_2_CARJACKED_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_CARJACKED_2, + SFX_BLACK_PROJECT_MALE_VOICE_2_MUGGED_1, + SFX_BLACK_PROJECT_MALE_VOICE_2_MUGGED_2, + SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_WORKER_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_1, + SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_2, + SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_3, + SFX_BLACK_WORKER_MALE_VOICE_1_CHAT_4, + SFX_BLACK_WORKER_MALE_VOICE_1_DODGE_1, + SFX_BLACK_WORKER_MALE_VOICE_1_DODGE_2, + SFX_BLACK_WORKER_MALE_VOICE_1_DODGE_3, + SFX_BLACK_WORKER_MALE_VOICE_1_EYING_1, + SFX_BLACK_WORKER_MALE_VOICE_1_EYING_2, + SFX_BLACK_WORKER_MALE_VOICE_1_EYING_3, + SFX_BLACK_WORKER_MALE_VOICE_1_FIGHT_1, + SFX_BLACK_WORKER_MALE_VOICE_1_FIGHT_2, + SFX_BLACK_WORKER_MALE_VOICE_1_FIGHT_3, + SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_1, + SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_2, + SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_3, + SFX_BLACK_WORKER_MALE_VOICE_1_GUN_PANIC_4, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_1, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_2, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_3, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_4, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_5, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_6, + SFX_SHOPPER_VOICE_1_DRIVER_ABUSE_7, + SFX_SHOPPER_VOICE_1_CHAT_1, + SFX_SHOPPER_VOICE_1_CHAT_2, + SFX_SHOPPER_VOICE_1_CHAT_3, + SFX_SHOPPER_VOICE_1_CHAT_4, + SFX_SHOPPER_VOICE_1_CHAT_5, + SFX_SHOPPER_VOICE_1_CHAT_6, + SFX_SHOPPER_VOICE_1_CHAT_7, + SFX_SHOPPER_VOICE_1_DODGE_1, + SFX_SHOPPER_VOICE_1_DODGE_2, + SFX_SHOPPER_VOICE_1_DODGE_3, + SFX_SHOPPER_VOICE_1_DODGE_4, + SFX_SHOPPER_VOICE_1_DODGE_5, + SFX_SHOPPER_VOICE_1_DODGE_6, + SFX_SHOPPER_VOICE_1_CARJACKED_1, + SFX_SHOPPER_VOICE_1_CARJACKED_2, + SFX_SHOPPER_VOICE_1_MUGGED_1, + SFX_SHOPPER_VOICE_1_MUGGED_2, + SFX_SHOPPER_VOICE_1_SHOCKED_1, + SFX_SHOPPER_VOICE_1_SHOCKED_2, + SFX_SHOPPER_VOICE_1_SHOCKED_3, + SFX_SHOPPER_VOICE_1_SHOCKED_4, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_1, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_2, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_3, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_4, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_5, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_6, + SFX_SHOPPER_VOICE_2_DRIVER_ABUSE_7, + SFX_SHOPPER_VOICE_2_CHAT_1, + SFX_SHOPPER_VOICE_2_CHAT_2, + SFX_SHOPPER_VOICE_2_CHAT_3, + SFX_SHOPPER_VOICE_2_CHAT_4, + SFX_SHOPPER_VOICE_2_CHAT_5, + SFX_SHOPPER_VOICE_2_CHAT_6, + SFX_SHOPPER_VOICE_2_CHAT_7, + SFX_SHOPPER_VOICE_2_DODGE_1, + SFX_SHOPPER_VOICE_2_DODGE_2, + SFX_SHOPPER_VOICE_2_DODGE_3, + SFX_SHOPPER_VOICE_2_DODGE_4, + SFX_SHOPPER_VOICE_2_DODGE_5, + SFX_SHOPPER_VOICE_2_DODGE_6, + SFX_SHOPPER_VOICE_2_CARJACKED_1, + SFX_SHOPPER_VOICE_2_CARJACKED_2, + SFX_SHOPPER_VOICE_2_MUGGED_1, + SFX_SHOPPER_VOICE_2_MUGGED_2, + SFX_SHOPPER_VOICE_2_SHOCKED_1, + SFX_SHOPPER_VOICE_2_SHOCKED_2, + SFX_SHOPPER_VOICE_2_SHOCKED_3, + SFX_SHOPPER_VOICE_2_SHOCKED_4, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_1, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_2, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_3, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_4, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_5, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_6, + SFX_SHOPPER_VOICE_3_DRIVER_ABUSE_7, + SFX_SHOPPER_VOICE_3_CHAT_1, + SFX_SHOPPER_VOICE_3_CHAT_2, + SFX_SHOPPER_VOICE_3_CHAT_3, + SFX_SHOPPER_VOICE_3_CHAT_4, + SFX_SHOPPER_VOICE_3_CHAT_5, + SFX_SHOPPER_VOICE_3_CHAT_6, + SFX_SHOPPER_VOICE_3_CHAT_7, + SFX_SHOPPER_VOICE_3_DODGE_1, + SFX_SHOPPER_VOICE_3_DODGE_2, + SFX_SHOPPER_VOICE_3_DODGE_3, + SFX_SHOPPER_VOICE_3_DODGE_4, + SFX_SHOPPER_VOICE_3_DODGE_5, + SFX_SHOPPER_VOICE_3_DODGE_6, + SFX_SHOPPER_VOICE_3_CARJACKED_1, + SFX_SHOPPER_VOICE_3_CARJACKED_2, + SFX_SHOPPER_VOICE_3_MUGGED_1, + SFX_SHOPPER_VOICE_3_MUGGED_2, + SFX_SHOPPER_VOICE_3_SHOCKED_1, + SFX_SHOPPER_VOICE_3_SHOCKED_2, + SFX_SHOPPER_VOICE_3_SHOCKED_3, + SFX_SHOPPER_VOICE_3_SHOCKED_4, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_COLUMBIAN_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_1, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_2, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_3, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_4, + SFX_COLUMBIAN_MALE_VOICE_1_CHAT_5, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_1, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_2, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_3, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_4, + SFX_COLUMBIAN_MALE_VOICE_1_DODGE_5, + SFX_COLUMBIAN_MALE_VOICE_1_EYING_1, + SFX_COLUMBIAN_MALE_VOICE_1_EYING_2, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_1, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_2, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_3, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_4, + SFX_COLUMBIAN_MALE_VOICE_1_FIGHT_5, + SFX_COLUMBIAN_MALE_VOICE_1_CARJACKED_1, + SFX_COLUMBIAN_MALE_VOICE_1_CARJACKED_2, + SFX_COLUMBIAN_MALE_VOICE_1_CARJACKING_1, + SFX_COLUMBIAN_MALE_VOICE_1_CARJACKING_2, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_COLUMBIAN_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_1, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_2, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_3, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_4, + SFX_COLUMBIAN_MALE_VOICE_2_CHAT_5, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_1, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_2, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_3, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_4, + SFX_COLUMBIAN_MALE_VOICE_2_DODGE_5, + SFX_COLUMBIAN_MALE_VOICE_2_EYING_1, + SFX_COLUMBIAN_MALE_VOICE_2_EYING_2, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_1, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_2, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_3, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_4, + SFX_COLUMBIAN_MALE_VOICE_2_FIGHT_5, + SFX_COLUMBIAN_MALE_VOICE_2_CARJACKED_1, + SFX_COLUMBIAN_MALE_VOICE_2_CARJACKED_2, + SFX_COLUMBIAN_MALE_VOICE_2_CARJACKING_1, + SFX_COLUMBIAN_MALE_VOICE_2_CARJACKING_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_3, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_4, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_5, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_6, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CHAT_7, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_3, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_4, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_5, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_DODGE_6, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CARJACKED_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_CARJACKED_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_MUGGED_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_MUGGED_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_1, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_2, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_3, + SFX_CHINATOWN_YOUNG_FEMALE_VOICE_1_SHOCKED_4, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_3, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_4, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_5, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_CHAT_6, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_3, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_4, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_DODGE_5, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_GUN_PANIC_3, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_MUGGED_1, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_MUGGED_2, + SFX_CHINATOWN_OLD_FEMALE_VOICE_1_SHOCKED_1, + SFX_GENERIC_FEMALE_DEATH_1, + SFX_GENERIC_FEMALE_DEATH_2, + SFX_GENERIC_FEMALE_DEATH_3, + SFX_GENERIC_FEMALE_DEATH_4, + SFX_GENERIC_FEMALE_DEATH_5, + SFX_GENERIC_FEMALE_DEATH_6, + SFX_GENERIC_FEMALE_DEATH_7, + SFX_GENERIC_FEMALE_DEATH_8, + SFX_GENERIC_FEMALE_DEATH_9, + SFX_GENERIC_FEMALE_DEATH_10, + SFX_GENERIC_FEMALE_FIRE_1, + SFX_GENERIC_FEMALE_FIRE_2, + SFX_GENERIC_FEMALE_FIRE_3, + SFX_GENERIC_FEMALE_FIRE_4, + SFX_GENERIC_FEMALE_FIRE_5, + SFX_GENERIC_FEMALE_FIRE_6, + SFX_GENERIC_FEMALE_FIRE_7, + SFX_GENERIC_FEMALE_FIRE_8, + SFX_GENERIC_FEMALE_FIRE_9, + SFX_GENERIC_FEMALE_GRUNT_1, + SFX_GENERIC_FEMALE_GRUNT_2, + SFX_GENERIC_FEMALE_GRUNT_3, + SFX_GENERIC_FEMALE_GRUNT_4, + SFX_GENERIC_FEMALE_GRUNT_5, + SFX_GENERIC_FEMALE_GRUNT_6, + SFX_GENERIC_FEMALE_GRUNT_7, + SFX_GENERIC_FEMALE_GRUNT_8, + SFX_GENERIC_FEMALE_GRUNT_9, + SFX_GENERIC_FEMALE_GRUNT_10, + SFX_GENERIC_FEMALE_GRUNT_11, + SFX_GENERIC_FEMALE_PANIC_1, + SFX_GENERIC_FEMALE_PANIC_2, + SFX_GENERIC_FEMALE_PANIC_3, + SFX_GENERIC_FEMALE_PANIC_4, + SFX_GENERIC_FEMALE_PANIC_5, + SFX_GENERIC_FEMALE_PANIC_6, + SFX_GENERIC_FEMALE_PANIC_7, + SFX_GENERIC_FEMALE_PANIC_8, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_CRIMINAL_VOICE_1_DRIVER_ABUSE_5, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_1, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_2, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_3, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_4, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_5, + SFX_BLACK_CRIMINAL_VOICE_1_DODGE_6, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_1, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_2, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_3, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_4, + SFX_BLACK_CRIMINAL_VOICE_1_FIGHT_5, + SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_1, + SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_2, + SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_3, + SFX_BLACK_CRIMINAL_VOICE_1_GUN_COOL_4, + SFX_BLACK_CRIMINAL_VOICE_1_CARJACKING_1, + SFX_BLACK_CRIMINAL_VOICE_1_MUGGING_1, + SFX_BLACK_CRIMINAL_VOICE_1_MUGGING_2, + SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_CRIMINAL_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_1, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_2, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_3, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_4, + SFX_WHITE_CRIMINAL_VOICE_1_DODGE_5, + SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_1, + SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_2, + SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_3, + SFX_WHITE_CRIMINAL_VOICE_1_FIGHT_4, + SFX_WHITE_CRIMINAL_VOICE_1_GUN_COOL_1, + SFX_WHITE_CRIMINAL_VOICE_1_GUN_COOL_2, + SFX_WHITE_CRIMINAL_VOICE_1_GUN_COOL_3, + SFX_WHITE_CRIMINAL_VOICE_1_CARJACKING_1, + SFX_WHITE_CRIMINAL_VOICE_1_MUGGING_1, + SFX_WHITE_CRIMINAL_VOICE_1_MUGGING_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_DRIVER_ABUSE_5, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_CHAT_5, + SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_DODGE_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_FIGHT_5, + SFX_BUSINESS_MALE_OLD_VOICE_1_GUN_PANIC_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_GUN_PANIC_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_GUN_PANIC_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_CARJACKED_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_CARJACKED_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_MUGGED_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_MUGGED_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_1, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_2, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_3, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_4, + SFX_BUSINESS_MALE_OLD_VOICE_1_MRUN_FROM_FIGHT_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_LITTLE_ITALY_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_4, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_CHAT_6, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_4, + SFX_LITTLE_ITALY_MALE_VOICE_1_DODGE_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_4, + SFX_LITTLE_ITALY_MALE_VOICE_1_FIGHT_5, + SFX_LITTLE_ITALY_MALE_VOICE_1_GUN_PANIC_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_GUN_PANIC_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_GUN_PANIC_3, + SFX_LITTLE_ITALY_MALE_VOICE_1_CARJACKED_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_CARJACKED_2, + SFX_LITTLE_ITALY_MALE_VOICE_1_MUGGED_1, + SFX_LITTLE_ITALY_MALE_VOICE_1_MUGGED_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_LITTLE_ITALY_MALE_VOICE_2_DRIVER_ABUSE_7, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_4, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_5, + SFX_LITTLE_ITALY_MALE_VOICE_2_CHAT_6, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_4, + SFX_LITTLE_ITALY_MALE_VOICE_2_DODGE_5, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_4, + SFX_LITTLE_ITALY_MALE_VOICE_2_FIGHT_5, + SFX_LITTLE_ITALY_MALE_VOICE_2_GUN_PANIC_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_GUN_PANIC_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_GUN_PANIC_3, + SFX_LITTLE_ITALY_MALE_VOICE_2_CARJACKED_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_CARJACKED_2, + SFX_LITTLE_ITALY_MALE_VOICE_2_MUGGED_1, + SFX_LITTLE_ITALY_MALE_VOICE_2_MUGGED_2, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_TRIAD_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_TRIAD_MALE_VOICE_1_CHAT_1, + SFX_TRIAD_MALE_VOICE_1_CHAT_2, + SFX_TRIAD_MALE_VOICE_1_CHAT_3, + SFX_TRIAD_MALE_VOICE_1_CHAT_4, + SFX_TRIAD_MALE_VOICE_1_CHAT_5, + SFX_TRIAD_MALE_VOICE_1_CHAT_6, + SFX_TRIAD_MALE_VOICE_1_CHAT_7, + SFX_TRIAD_MALE_VOICE_1_CHAT_8, + SFX_TRIAD_MALE_VOICE_1_DODGE_1, + SFX_TRIAD_MALE_VOICE_1_DODGE_2, + SFX_TRIAD_MALE_VOICE_1_DODGE_3, + SFX_TRIAD_MALE_VOICE_1_DODGE_4, + SFX_TRIAD_MALE_VOICE_1_EYING_1, + SFX_TRIAD_MALE_VOICE_1_EYING_2, + SFX_TRIAD_MALE_VOICE_1_EYING_3, + SFX_TRIAD_MALE_VOICE_1_FIGHT_1, + SFX_TRIAD_MALE_VOICE_1_FIGHT_2, + SFX_TRIAD_MALE_VOICE_1_FIGHT_3, + SFX_TRIAD_MALE_VOICE_1_FIGHT_4, + SFX_TRIAD_MALE_VOICE_1_FIGHT_5, + SFX_TRIAD_MALE_VOICE_1_GUN_COOL_1, + SFX_TRIAD_MALE_VOICE_1_GUN_COOL_2, + SFX_TRIAD_MALE_VOICE_1_GUN_COOL_3, + SFX_TRIAD_MALE_VOICE_1_CARJACKED_1, + SFX_TRIAD_MALE_VOICE_1_CARJACKED_2, + SFX_TRIAD_MALE_VOICE_1_CARJACKING_1, + SFX_TRIAD_MALE_VOICE_1_CARJACKING_2, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_MAFIA_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_MAFIA_MALE_VOICE_1_CHAT_1, + SFX_MAFIA_MALE_VOICE_1_CHAT_2, + SFX_MAFIA_MALE_VOICE_1_CHAT_3, + SFX_MAFIA_MALE_VOICE_1_CHAT_4, + SFX_MAFIA_MALE_VOICE_1_CHAT_5, + SFX_MAFIA_MALE_VOICE_1_CHAT_6, + SFX_MAFIA_MALE_VOICE_1_CHAT_7, + SFX_MAFIA_MALE_VOICE_1_DODGE_1, + SFX_MAFIA_MALE_VOICE_1_DODGE_2, + SFX_MAFIA_MALE_VOICE_1_DODGE_3, + SFX_MAFIA_MALE_VOICE_1_DODGE_4, + SFX_MAFIA_MALE_VOICE_1_DODGE_5, + SFX_MAFIA_MALE_VOICE_1_EYING_1, + SFX_MAFIA_MALE_VOICE_1_EYING_2, + SFX_MAFIA_MALE_VOICE_1_EYING_3, + SFX_MAFIA_MALE_VOICE_1_FIGHT_1, + SFX_MAFIA_MALE_VOICE_1_FIGHT_2, + SFX_MAFIA_MALE_VOICE_1_FIGHT_3, + SFX_MAFIA_MALE_VOICE_1_FIGHT_4, + SFX_MAFIA_MALE_VOICE_1_FIGHT_5, + SFX_MAFIA_MALE_VOICE_1_CARJACKED_1, + SFX_MAFIA_MALE_VOICE_1_CARJACKED_2, + SFX_MAFIA_MALE_VOICE_1_CARJACKING_1, + SFX_MAFIA_MALE_VOICE_1_CARJACKING_2, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_MAFIA_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_MAFIA_MALE_VOICE_2_CHAT_1, + SFX_MAFIA_MALE_VOICE_2_CHAT_2, + SFX_MAFIA_MALE_VOICE_2_CHAT_3, + SFX_MAFIA_MALE_VOICE_2_CHAT_4, + SFX_MAFIA_MALE_VOICE_2_CHAT_5, + SFX_MAFIA_MALE_VOICE_2_CHAT_6, + SFX_MAFIA_MALE_VOICE_2_CHAT_7, + SFX_MAFIA_MALE_VOICE_2_DODGE_1, + SFX_MAFIA_MALE_VOICE_2_DODGE_2, + SFX_MAFIA_MALE_VOICE_2_DODGE_3, + SFX_MAFIA_MALE_VOICE_2_DODGE_4, + SFX_MAFIA_MALE_VOICE_2_DODGE_5, + SFX_MAFIA_MALE_VOICE_2_EYING_1, + SFX_MAFIA_MALE_VOICE_2_EYING_2, + SFX_MAFIA_MALE_VOICE_2_EYING_3, + SFX_MAFIA_MALE_VOICE_2_FIGHT_1, + SFX_MAFIA_MALE_VOICE_2_FIGHT_2, + SFX_MAFIA_MALE_VOICE_2_FIGHT_3, + SFX_MAFIA_MALE_VOICE_2_FIGHT_4, + SFX_MAFIA_MALE_VOICE_2_FIGHT_5, + SFX_MAFIA_MALE_VOICE_2_CARJACKED_1, + SFX_MAFIA_MALE_VOICE_2_CARJACKED_2, + SFX_MAFIA_MALE_VOICE_2_CARJACKING_1, + SFX_MAFIA_MALE_VOICE_2_CARJACKING_2, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_1, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_2, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_3, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_4, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_5, + SFX_MAFIA_MALE_VOICE_3_DRIVER_ABUSE_6, + SFX_MAFIA_MALE_VOICE_3_CHAT_1, + SFX_MAFIA_MALE_VOICE_3_CHAT_2, + SFX_MAFIA_MALE_VOICE_3_CHAT_3, + SFX_MAFIA_MALE_VOICE_3_CHAT_4, + SFX_MAFIA_MALE_VOICE_3_CHAT_5, + SFX_MAFIA_MALE_VOICE_3_CHAT_6, + SFX_MAFIA_MALE_VOICE_3_CHAT_7, + SFX_MAFIA_MALE_VOICE_3_DODGE_1, + SFX_MAFIA_MALE_VOICE_3_DODGE_2, + SFX_MAFIA_MALE_VOICE_3_DODGE_3, + SFX_MAFIA_MALE_VOICE_3_DODGE_4, + SFX_MAFIA_MALE_VOICE_3_DODGE_5, + SFX_MAFIA_MALE_VOICE_3_EYING_1, + SFX_MAFIA_MALE_VOICE_3_EYING_2, + SFX_MAFIA_MALE_VOICE_3_EYING_3, + SFX_MAFIA_MALE_VOICE_3_FIGHT_1, + SFX_MAFIA_MALE_VOICE_3_FIGHT_2, + SFX_MAFIA_MALE_VOICE_3_FIGHT_3, + SFX_MAFIA_MALE_VOICE_3_FIGHT_4, + SFX_MAFIA_MALE_VOICE_3_FIGHT_5, + SFX_MAFIA_MALE_VOICE_3_CARJACKED_1, + SFX_MAFIA_MALE_VOICE_3_CARJACKED_2, + SFX_MAFIA_MALE_VOICE_3_CARJACKING_1, + SFX_MAFIA_MALE_VOICE_3_CARJACKING_2, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_YAKUZA_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_YAKUZA_MALE_VOICE_1_CHAT_1, + SFX_YAKUZA_MALE_VOICE_1_CHAT_2, + SFX_YAKUZA_MALE_VOICE_1_CHAT_3, + SFX_YAKUZA_MALE_VOICE_1_CHAT_4, + SFX_YAKUZA_MALE_VOICE_1_CHAT_5, + SFX_YAKUZA_MALE_VOICE_1_DODGE_1, + SFX_YAKUZA_MALE_VOICE_1_DODGE_2, + SFX_YAKUZA_MALE_VOICE_1_DODGE_3, + SFX_YAKUZA_MALE_VOICE_1_DODGE_4, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_1, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_2, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_3, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_4, + SFX_YAKUZA_MALE_VOICE_1_FIGHT_5, + SFX_YAKUZA_MALE_VOICE_1_CARJACKED_1, + SFX_YAKUZA_MALE_VOICE_1_CARJACKED_2, + SFX_YAKUZA_MALE_VOICE_1_CARJACKING_1, + SFX_YAKUZA_MALE_VOICE_1_CARJACKING_2, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_YAKUZA_MALE_VOICE_2_DRIVER_ABUSE_6, + SFX_YAKUZA_MALE_VOICE_2_CHAT_1, + SFX_YAKUZA_MALE_VOICE_2_CHAT_2, + SFX_YAKUZA_MALE_VOICE_2_CHAT_3, + SFX_YAKUZA_MALE_VOICE_2_CHAT_4, + SFX_YAKUZA_MALE_VOICE_2_CHAT_5, + SFX_YAKUZA_MALE_VOICE_2_DODGE_1, + SFX_YAKUZA_MALE_VOICE_2_DODGE_2, + SFX_YAKUZA_MALE_VOICE_2_DODGE_3, + SFX_YAKUZA_MALE_VOICE_2_DODGE_4, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_1, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_2, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_3, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_4, + SFX_YAKUZA_MALE_VOICE_2_FIGHT_5, + SFX_YAKUZA_MALE_VOICE_2_CARJACKED_1, + SFX_YAKUZA_MALE_VOICE_2_CARJACKED_2, + SFX_YAKUZA_MALE_VOICE_2_CARJACKING_1, + SFX_YAKUZA_MALE_VOICE_2_CARJACKING_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_4, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_5, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_6, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CHAT_7, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_4, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_DODGE_5, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_EYING_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_EYING_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_EYING_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_4, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_FIGHT_5, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_GUN_PANIC_1, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_GUN_PANIC_2, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_GUN_PANIC_3, + SFX_WHITE_MALE_CONSTRUCTION_VOICE_1_CARJACKED_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_2, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_3, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_4, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_5, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_DRIVER_ABUSE_6, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_2, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_3, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_4, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_5, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_6, + SFX_ASIAN_TAXI_DRIVER_VOICE_1_CARJACKED_7, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_2, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_3, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_4, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_5, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_DRIVER_ABUSE_6, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_1, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_2, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_3, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_4, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_5, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_6, + SFX_ASIAN_TAXI_DRIVER_VOICE_2_CARJACKED_7, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_1, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_2, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_3, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_4, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_5, + SFX_SECURITY_GUARD_VOICE_1_DRIVER_ABUSE_6, + SFX_SECURITY_GUARD_VOICE_1_FIGHT_1, + SFX_SECURITY_GUARD_VOICE_1_FIGHT_2, + SFX_SECURITY_GUARD_VOICE_1_GUN_COOL_1, + SFX_SECURITY_GUARD_VOICE_1_GUN_COOL_2, + SFX_SECURITY_GUARD_VOICE_1_GUN_PANIC_1, + SFX_SECURITY_GUARD_VOICE_1_RUN_FROM_FIGHT_1, + SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_1, + SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_2, + SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_3, + SFX_BLACK_PROSTITUTE_VOICE_1_CHAT_4, + SFX_BLACK_PROSTITUTE_VOICE_1_DODGE_1, + SFX_BLACK_PROSTITUTE_VOICE_1_DODGE_2, + SFX_BLACK_PROSTITUTE_VOICE_1_DODGE_3, + SFX_BLACK_PROSTITUTE_VOICE_1_MUGGED_1, + SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_1, + SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_2, + SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_3, + SFX_BLACK_PROSTITUTE_VOICE_1_DRIVER_ABUSE_4, + SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_1, + SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_2, + SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_3, + SFX_BLACK_PROSTITUTE_VOICE_1_FIGHT_4, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_1, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_2, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_3, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_4, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_5, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_6, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_7, + SFX_BLACK_PROSTITUTE_VOICE_1_SOLICIT_8, + SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_1, + SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_2, + SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_3, + SFX_BLACK_PROSTITUTE_VOICE_1_GUN_COOL_4, + SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_1, + SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_2, + SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_3, + SFX_BLACK_PROSTITUTE_VOICE_2_CHAT_4, + SFX_BLACK_PROSTITUTE_VOICE_2_DODGE_1, + SFX_BLACK_PROSTITUTE_VOICE_2_DODGE_2, + SFX_BLACK_PROSTITUTE_VOICE_2_DODGE_3, + SFX_BLACK_PROSTITUTE_VOICE_2_MUGGED_1, + SFX_BLACK_PROSTITUTE_VOICE_2_DRIVER_ABUSE_1, + SFX_BLACK_PROSTITUTE_VOICE_2_DRIVER_ABUSE_2, + SFX_BLACK_PROSTITUTE_VOICE_2_DRIVER_ABUSE_3, + SFX_BLACK_PROSTITUTE_VOICE_2_DRIVER_ABUSE_4, + SFX_BLACK_PROSTITUTE_VOICE_2_FIGHT_1, + SFX_BLACK_PROSTITUTE_VOICE_2_FIGHT_2, + SFX_BLACK_PROSTITUTE_VOICE_2_FIGHT_3, + SFX_BLACK_PROSTITUTE_VOICE_2_FIGHT_4, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_1, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_2, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_3, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_4, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_5, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_6, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_7, + SFX_BLACK_PROSTITUTE_VOICE_2_SOLICIT_8, + SFX_BLACK_PROSTITUTE_VOICE_2_GUN_COOL_1, + SFX_BLACK_PROSTITUTE_VOICE_2_GUN_COOL_2, + SFX_BLACK_PROSTITUTE_VOICE_2_GUN_COOL_3, + SFX_BLACK_PROSTITUTE_VOICE_2_GUN_COOL_4, + SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_1, + SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_2, + SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_3, + SFX_WHITE_PROSTITUTE_VOICE_1_CHAT_4, + SFX_WHITE_PROSTITUTE_VOICE_1_DODGE_1, + SFX_WHITE_PROSTITUTE_VOICE_1_DODGE_2, + SFX_WHITE_PROSTITUTE_VOICE_1_DODGE_3, + SFX_WHITE_PROSTITUTE_VOICE_1_MUGGED_1, + SFX_WHITE_PROSTITUTE_VOICE_1_MUGGED_2, + SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_PROSTITUTE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_1, + SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_2, + SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_3, + SFX_WHITE_PROSTITUTE_VOICE_1_FIGHT_4, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_1, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_2, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_3, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_4, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_5, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_6, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_7, + SFX_WHITE_PROSTITUTE_VOICE_1_SOLICIT_8, + SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_1, + SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_2, + SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_3, + SFX_WHITE_PROSTITUTE_VOICE_2_CHAT_4, + SFX_WHITE_PROSTITUTE_VOICE_2_DODGE_1, + SFX_WHITE_PROSTITUTE_VOICE_2_DODGE_2, + SFX_WHITE_PROSTITUTE_VOICE_2_DODGE_3, + SFX_WHITE_PROSTITUTE_VOICE_2_MUGGED_1, + SFX_WHITE_PROSTITUTE_VOICE_2_MUGGED_2, + SFX_WHITE_PROSTITUTE_VOICE_2_DRIVER_ABUSE_1, + SFX_WHITE_PROSTITUTE_VOICE_2_DRIVER_ABUSE_2, + SFX_WHITE_PROSTITUTE_VOICE_2_DRIVER_ABUSE_3, + SFX_WHITE_PROSTITUTE_VOICE_2_DRIVER_ABUSE_4, + SFX_WHITE_PROSTITUTE_VOICE_2_FIGHT_1, + SFX_WHITE_PROSTITUTE_VOICE_2_FIGHT_2, + SFX_WHITE_PROSTITUTE_VOICE_2_FIGHT_3, + SFX_WHITE_PROSTITUTE_VOICE_2_FIGHT_4, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_1, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_2, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_3, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_4, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_5, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_6, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_7, + SFX_WHITE_PROSTITUTE_VOICE_2_SOLICIT_8, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_3, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_4, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_5, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CHAT_6, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_3, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_4, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_5, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_6, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DODGE_7, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CARJACKED_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_CARJACKED_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_MUGGED_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_MUGGED_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_1, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_2, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_3, + SFX_LITTLE_ITALY_YOUNG_FEMALE_VOICE_1_SHOCKED_4, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_3, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_4, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_5, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_6, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CHAT_7, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_3, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_4, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_5, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DODGE_6, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CARJACKED_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_CARJACKED_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_MUGGED_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_MUGGED_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_1, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_2, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_3, + SFX_LITTLE_ITALY_OLD_FEMALE_VOICE_1_SHOCKED_4, + SFX_GENERIC_MALE_DEATH_1, + SFX_GENERIC_MALE_DEATH_2, + SFX_GENERIC_MALE_DEATH_3, + SFX_GENERIC_MALE_DEATH_4, + SFX_GENERIC_MALE_DEATH_5, + SFX_GENERIC_MALE_DEATH_6, + SFX_GENERIC_MALE_DEATH_7, + SFX_GENERIC_MALE_DEATH_8, + SFX_GENERIC_MALE_FIRE_1, + SFX_GENERIC_MALE_FIRE_2, + SFX_GENERIC_MALE_FIRE_3, + SFX_GENERIC_MALE_FIRE_4, + SFX_GENERIC_MALE_FIRE_5, + SFX_GENERIC_MALE_FIRE_6, + SFX_GENERIC_MALE_FIRE_7, + SFX_GENERIC_MALE_FIRE_8, + SFX_GENERIC_MALE_GRUNT_1, + SFX_GENERIC_MALE_GRUNT_2, + SFX_GENERIC_MALE_GRUNT_3, + SFX_GENERIC_MALE_GRUNT_4, + SFX_GENERIC_MALE_GRUNT_5, + SFX_GENERIC_MALE_GRUNT_6, + SFX_GENERIC_MALE_GRUNT_7, + SFX_GENERIC_MALE_GRUNT_8, + SFX_GENERIC_MALE_GRUNT_9, + SFX_GENERIC_MALE_GRUNT_10, + SFX_GENERIC_MALE_GRUNT_11, + SFX_GENERIC_MALE_GRUNT_12, + SFX_GENERIC_MALE_GRUNT_13, + SFX_GENERIC_MALE_GRUNT_14, + SFX_GENERIC_MALE_GRUNT_15, + SFX_GENERIC_MALE_PANIC_1, + SFX_GENERIC_MALE_PANIC_2, + SFX_GENERIC_MALE_PANIC_3, + SFX_GENERIC_MALE_PANIC_4, + SFX_GENERIC_MALE_PANIC_5, + SFX_GENERIC_MALE_PANIC_6, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_1, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_2, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_3, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_4, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_5, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_6, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_7, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_8, + SFX_WHITE_FAT_MALE_VOICE_1_CHAT_9, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_1, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_2, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_3, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_4, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_5, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_6, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_7, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_8, + SFX_WHITE_FAT_MALE_VOICE_1_DODGE_9, + SFX_WHITE_FAT_MALE_VOICE_1_CARJACKED_1, + SFX_WHITE_FAT_MALE_VOICE_1_CARJACKED_2, + SFX_WHITE_FAT_MALE_VOICE_1_CARJACKED_3, + SFX_WHITE_FAT_MALE_VOICE_1_MUGGED_1, + SFX_WHITE_FAT_MALE_VOICE_1_MUGGED_2, + SFX_WHITE_FAT_MALE_VOICE_1_MUGGED_3, + SFX_WHITE_FAT_MALE_VOICE_1_LOST_1, + SFX_WHITE_FAT_MALE_VOICE_1_LOST_2, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_6, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_7, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_8, + SFX_WHITE_FAT_MALE_VOICE_1_DRIVER_ABUSE_9, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_3, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_4, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_5, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_6, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_7, + SFX_WHITE_FAT_FEMALE_VOICE_1_CHAT_8, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_3, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_4, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_5, + SFX_WHITE_FAT_FEMALE_VOICE_1_DODGE_6, + SFX_WHITE_FAT_FEMALE_VOICE_1_CARJACKED_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_CARJACKED_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_MUGGED_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_MUGGED_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_LOST_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_LOST_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_WHITE_FAT_FEMALE_VOICE_1_DRIVER_ABUSE_8, + SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_1, + SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_2, + SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_3, + SFX_WHITE_FAT_FEMALE_VOICE_1_SHOCKED_4, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_3, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CHAT_4, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DODGE_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DODGE_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DODGE_3, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CARJACKED_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_CARJACKED_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_MUGGED_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_3, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_4, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_5, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_6, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_7, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_DRIVER_ABUSE_8, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_SHOCKED_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_SHOCKED_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_GUN_PANIC_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_GUN_PANIC_2, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_RUN_FROM_FIGHT_1, + SFX_WHITE_CASUAL_FEMALE_VOICE_1_RUN_FROM_FIGHT_2, + SFX_DIABLO_MALE_VOICE_1_CHAT_1, + SFX_DIABLO_MALE_VOICE_1_CHAT_2, + SFX_DIABLO_MALE_VOICE_1_CHAT_3, + SFX_DIABLO_MALE_VOICE_1_CHAT_4, + SFX_DIABLO_MALE_VOICE_1_CHAT_5, + SFX_DIABLO_MALE_VOICE_1_DODGE_1, + SFX_DIABLO_MALE_VOICE_1_DODGE_2, + SFX_DIABLO_MALE_VOICE_1_DODGE_3, + SFX_DIABLO_MALE_VOICE_1_DODGE_4, + SFX_DIABLO_MALE_VOICE_1_CARJACKED_1, + SFX_DIABLO_MALE_VOICE_1_CARJACKED_2, + SFX_DIABLO_MALE_VOICE_1_CARJACKING_1, + SFX_DIABLO_MALE_VOICE_1_CARJACKING_2, + SFX_DIABLO_MALE_VOICE_1_FIGHT_1, + SFX_DIABLO_MALE_VOICE_1_FIGHT_2, + SFX_DIABLO_MALE_VOICE_1_FIGHT_3, + SFX_DIABLO_MALE_VOICE_1_FIGHT_4, + SFX_DIABLO_MALE_VOICE_1_EYING_1, + SFX_DIABLO_MALE_VOICE_1_EYING_2, + SFX_DIABLO_MALE_VOICE_1_EYING_3, + SFX_DIABLO_MALE_VOICE_1_EYING_4, + SFX_DIABLO_MALE_VOICE_1_GUN_COOL_1, + SFX_DIABLO_MALE_VOICE_1_GUN_COOL_2, + SFX_DIABLO_MALE_VOICE_1_GUN_COOL_3, + SFX_DIABLO_MALE_VOICE_1_GUN_COOL_4, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_1, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_2, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_3, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_4, + SFX_DIABLO_MALE_VOICE_1_DRIVER_ABUSE_5, + SFX_DIABLO_MALE_VOICE_2_CHAT_1, + SFX_DIABLO_MALE_VOICE_2_CHAT_2, + SFX_DIABLO_MALE_VOICE_2_CHAT_3, + SFX_DIABLO_MALE_VOICE_2_CHAT_4, + SFX_DIABLO_MALE_VOICE_2_CHAT_5, + SFX_DIABLO_MALE_VOICE_2_DODGE_1, + SFX_DIABLO_MALE_VOICE_2_DODGE_2, + SFX_DIABLO_MALE_VOICE_2_DODGE_3, + SFX_DIABLO_MALE_VOICE_2_DODGE_4, + SFX_DIABLO_MALE_VOICE_2_CARJACKED_1, + SFX_DIABLO_MALE_VOICE_2_CARJACKED_2, + SFX_DIABLO_MALE_VOICE_2_CARJACKING_1, + SFX_DIABLO_MALE_VOICE_2_CARJACKING_2, + SFX_DIABLO_MALE_VOICE_2_FIGHT_1, + SFX_DIABLO_MALE_VOICE_2_FIGHT_2, + SFX_DIABLO_MALE_VOICE_2_FIGHT_3, + SFX_DIABLO_MALE_VOICE_2_FIGHT_4, + SFX_DIABLO_MALE_VOICE_2_EYING_1, + SFX_DIABLO_MALE_VOICE_2_EYING_2, + SFX_DIABLO_MALE_VOICE_2_EYING_3, + SFX_DIABLO_MALE_VOICE_2_EYING_4, + SFX_DIABLO_MALE_VOICE_2_GUN_COOL_1, + SFX_DIABLO_MALE_VOICE_2_GUN_COOL_2, + SFX_DIABLO_MALE_VOICE_2_GUN_COOL_3, + SFX_DIABLO_MALE_VOICE_2_GUN_COOL_4, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_1, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_2, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_3, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_4, + SFX_DIABLO_MALE_VOICE_2_DRIVER_ABUSE_5, + SFX_AMMU_D, + SFX_AMMU_E, + SFX_AMMU_F, + +#ifdef GTA_PS2 + SFX_MISSION_LIB_A1, + SFX_MISSION_LIB_A2, + SFX_MISSION_LIB_A, + SFX_MISSION_LIB_B, + SFX_MISSION_LIB_C, + SFX_MISSION_LIB_D, + SFX_MISSION_L2_A, + SFX_MISSION_J4T_1, + SFX_MISSION_J4T_2, + SFX_MISSION_J4T_3, + SFX_MISSION_J4T_4, + SFX_MISSION_J4_A, + SFX_MISSION_J4_B, + SFX_MISSION_J4_C, + SFX_MISSION_J4_D, + SFX_MISSION_J4_E, + SFX_MISSION_J4_F, + SFX_MISSION_J6_1, + SFX_MISSION_J6_A, + SFX_MISSION_J6_B, + SFX_MISSION_J6_C, + SFX_MISSION_J6_D, + SFX_MISSION_T4_A, + SFX_MISSION_S1_A, + SFX_MISSION_S1_A1, + SFX_MISSION_S1_B, + SFX_MISSION_S1_C, + SFX_MISSION_S1_C1, + SFX_MISSION_S1_D, + SFX_MISSION_S1_E, + SFX_MISSION_S1_F, + SFX_MISSION_S1_G, + SFX_MISSION_S1_H, + SFX_MISSION_S1_I, + SFX_MISSION_S1_J, + SFX_MISSION_S1_K, + SFX_MISSION_S1_L, + SFX_MISSION_S3_A, + SFX_MISSION_S3_B, + SFX_MISSION_EL3_A, + SFX_MISSION_MF1_A, + SFX_MISSION_MF2_A, + SFX_MISSION_MF3_A, + SFX_MISSION_MF3_B, + SFX_MISSION_MF3_B1, + SFX_MISSION_MF3_C, + SFX_MISSION_MF4_A, + SFX_MISSION_MF4_B, + SFX_MISSION_MF4_C, + SFX_MISSION_A1_A, + SFX_MISSION_A3_A, + SFX_MISSION_A5_A, + SFX_MISSION_A4_A, + SFX_MISSION_A4_B, + SFX_MISSION_A4_C, + SFX_MISSION_A4_D, + SFX_MISSION_K1_A, + SFX_MISSION_K3_A, + SFX_MISSION_R1_A, + SFX_MISSION_R2_A, + SFX_MISSION_R2_B, + SFX_MISSION_R2_C, + SFX_MISSION_R2_D, + SFX_MISSION_R2_E, + SFX_MISSION_R2_F, + SFX_MISSION_R2_G, + SFX_MISSION_R2_H, + SFX_MISSION_R5_A, + SFX_MISSION_R6_A, + SFX_MISSION_R6_A1, + SFX_MISSION_R6_B, + SFX_MISSION_LO2_A, + SFX_MISSION_LO6_A, + SFX_MISSION_YD2_A, + SFX_MISSION_YD2_B, + SFX_MISSION_YD2_C, + SFX_MISSION_YD2_C1, + SFX_MISSION_YD2_D, + SFX_MISSION_YD2_E, + SFX_MISSION_YD2_F, + SFX_MISSION_YD2_G, + SFX_MISSION_YD2_H, + SFX_MISSION_YD2_ASS, + SFX_MISSION_YD2_OK, + SFX_MISSION_H5_A, + SFX_MISSION_H5_B, + SFX_MISSION_H5_C, + SFX_MISSION_DOOR_1, + SFX_MISSION_DOOR_2, + SFX_MISSION_DOOR_3, + SFX_MISSION_DOOR_4, + SFX_MISSION_DOOR_5, + SFX_MISSION_DOOR_6, + SFX_MISSION_T3_A, + SFX_MISSION_T3_B, + SFX_MISSION_T3_C, + SFX_MISSION_K1_B, + SFX_MISSION_AMMU_A, + SFX_MISSION_AMMU_B, + SFX_MISSION_AMMU_C, +#endif + TOTAL_AUDIO_SAMPLES, + NO_SAMPLE, + + // shorthands + SAMPLEBANK_START = SFX_CAR_HORN_JEEP, +#ifdef GTA_PS2 + SAMPLEBANK_END = SFX_INFO, + SAMPLEBANK_MAX = SFX_INFO + 1, +#else + SAMPLEBANK_END = SFX_PAGER, + SAMPLEBANK_MAX = SFX_PAGER + 1, +#endif + SAMPLEBANK_PED_START = SFX_COP_VOICE_1_ARREST_1, +#ifdef GTA_PS2 + SAMPLEBANK_PED_END = SFX_MISSION_AMMU_C, + SAMPLEBANK_PED_MAX = SFX_MISSION_AMMU_C + 1, +#else + SAMPLEBANK_PED_END = SFX_AMMU_F, + SAMPLEBANK_PED_MAX = SFX_AMMU_F + 1, +#endif +}; diff --git a/src/audio/AudioScriptObject.cpp b/src/audio/AudioScriptObject.cpp new file mode 100644 index 0000000..03efdea --- /dev/null +++ b/src/audio/AudioScriptObject.cpp @@ -0,0 +1,101 @@ +#include "common.h" + +#include "AudioScriptObject.h" +#include "Pools.h" +#include "DMAudio.h" +#include "SaveBuf.h" + +cAudioScriptObject::cAudioScriptObject() +{ + Reset(); +}; + +cAudioScriptObject::~cAudioScriptObject() +{ + Reset(); +}; + +void +cAudioScriptObject::Reset() +{ + AudioId = SCRIPT_SOUND_INVALID; + Posn = CVector(0.0f, 0.0f, 0.0f); + AudioEntity = AEHANDLE_NONE; +} + +void * +cAudioScriptObject::operator new(size_t sz) throw() +{ + return CPools::GetAudioScriptObjectPool()->New(); +} + +void * +cAudioScriptObject::operator new(size_t sz, int handle) throw() +{ + return CPools::GetAudioScriptObjectPool()->New(handle); +} + +void +cAudioScriptObject::operator delete(void *p, size_t sz) throw() +{ + CPools::GetAudioScriptObjectPool()->Delete((cAudioScriptObject *)p); +} + +void +cAudioScriptObject::operator delete(void *p, int handle) throw() +{ + CPools::GetAudioScriptObjectPool()->Delete((cAudioScriptObject *)p); +} + +void +cAudioScriptObject::LoadAllAudioScriptObjects(uint8 *buf, uint32 size) +{ + INITSAVEBUF + + CheckSaveHeader(buf, 'A', 'U', 'D', '\0', size - SAVE_HEADER_SIZE); + + int32 pool_size; + ReadSaveBuf(&pool_size, buf); + for (int32 i = 0; i < pool_size; i++) { + int32 handle; + ReadSaveBuf(&handle, buf); + cAudioScriptObject *p = new(handle) cAudioScriptObject; + assert(p != nil); + ReadSaveBuf(p, buf); + p->AudioEntity = DMAudio.CreateLoopingScriptObject(p); + } + + VALIDATESAVEBUF(size); +} + +void +cAudioScriptObject::SaveAllAudioScriptObjects(uint8 *buf, uint32 *size) +{ + INITSAVEBUF + + int32 pool_size = CPools::GetAudioScriptObjectPool()->GetNoOfUsedSpaces(); + *size = SAVE_HEADER_SIZE + sizeof(int32) + pool_size * (sizeof(cAudioScriptObject) + sizeof(int32)); + WriteSaveHeader(buf, 'A', 'U', 'D', '\0', *size - SAVE_HEADER_SIZE); + WriteSaveBuf(buf, pool_size); + + int32 i = CPools::GetAudioScriptObjectPool()->GetSize(); + while (i--) { + cAudioScriptObject *p = CPools::GetAudioScriptObjectPool()->GetSlot(i); + if (p != nil) { + WriteSaveBuf(buf, CPools::GetAudioScriptObjectPool()->GetIndex(p)); + WriteSaveBuf(buf, *p); + } + } + + VALIDATESAVEBUF(*size); +} + +void +PlayOneShotScriptObject(uint8 id, CVector const &pos) +{ + cAudioScriptObject *audioScriptObject = new cAudioScriptObject(); + audioScriptObject->Posn = pos; + audioScriptObject->AudioId = id; + audioScriptObject->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(audioScriptObject); +} diff --git a/src/audio/AudioScriptObject.h b/src/audio/AudioScriptObject.h new file mode 100644 index 0000000..b9a7e61 --- /dev/null +++ b/src/audio/AudioScriptObject.h @@ -0,0 +1,26 @@ +#pragma once + +class cAudioScriptObject +{ +public: + int16 AudioId; + CVector Posn; + int32 AudioEntity; + + cAudioScriptObject(); + ~cAudioScriptObject(); + + void Reset(); /// ok + + static void* operator new(size_t) throw(); + static void* operator new(size_t, int) throw(); + static void operator delete(void*, size_t) throw(); + static void operator delete(void*, int) throw(); + + static void LoadAllAudioScriptObjects(uint8 *buf, uint32 size); + static void SaveAllAudioScriptObjects(uint8 *buf, uint32 *size); +}; + +VALIDATE_SIZE(cAudioScriptObject, 20); + +extern void PlayOneShotScriptObject(uint8 id, CVector const &pos); \ No newline at end of file diff --git a/src/audio/DMAudio.cpp b/src/audio/DMAudio.cpp new file mode 100644 index 0000000..d88bfdd --- /dev/null +++ b/src/audio/DMAudio.cpp @@ -0,0 +1,340 @@ +#include "common.h" + +#include "DMAudio.h" +#include "MusicManager.h" +#include "AudioManager.h" +#include "AudioScriptObject.h" +#include "sampman.h" + +cDMAudio DMAudio; + +void +cDMAudio::Initialise(void) +{ + AudioManager.Initialise(); +} + +void +cDMAudio::Terminate(void) +{ + AudioManager.Terminate(); +} + +void +cDMAudio::Service(void) +{ + AudioManager.Service(); +} + +int32 +cDMAudio::CreateEntity(eAudioType type, void *UID) +{ + return AudioManager.CreateEntity(type, (CPhysical *)UID); +} + +void +cDMAudio::DestroyEntity(int32 audioEntity) +{ + AudioManager.DestroyEntity(audioEntity); +} + +bool8 +cDMAudio::GetEntityStatus(int32 audioEntity) +{ + return AudioManager.GetEntityStatus(audioEntity); +} + +void +cDMAudio::SetEntityStatus(int32 audioEntity, bool8 status) +{ + AudioManager.SetEntityStatus(audioEntity, status); +} + +void +cDMAudio::PlayOneShot(int32 audioEntity, uint16 oneShot, float volume) +{ + AudioManager.PlayOneShot(audioEntity, oneShot, volume); +} + +void +cDMAudio::DestroyAllGameCreatedEntities(void) +{ + AudioManager.DestroyAllGameCreatedEntities(); +} + +void +cDMAudio::SetMonoMode(bool8 mono) +{ + AudioManager.SetMonoMode(mono); +} + +void +cDMAudio::SetEffectsMasterVolume(uint8 volume) +{ + uint8 vol = volume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + AudioManager.SetEffectsMasterVolume(vol); +} + +void +cDMAudio::SetMusicMasterVolume(uint8 volume) +{ + uint8 vol = volume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + AudioManager.SetMusicMasterVolume(vol); +} + +void +cDMAudio::SetEffectsFadeVol(uint8 volume) +{ + uint8 vol = volume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + AudioManager.SetEffectsFadeVol(vol); +} + +void +cDMAudio::SetMusicFadeVol(uint8 volume) +{ + uint8 vol = volume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + AudioManager.SetMusicFadeVol(vol); +} + +uint8 +cDMAudio::GetNum3DProvidersAvailable(void) +{ + return AudioManager.GetNum3DProvidersAvailable(); +} + +char * +cDMAudio::Get3DProviderName(uint8 id) +{ + return AudioManager.Get3DProviderName(id); +} + +int8 +cDMAudio::GetCurrent3DProviderIndex(void) +{ + return AudioManager.GetCurrent3DProviderIndex(); +} + +int8 +cDMAudio::SetCurrent3DProvider(uint8 which) +{ + return AudioManager.SetCurrent3DProvider(which); +} + +void +cDMAudio::SetSpeakerConfig(int32 config) +{ + AudioManager.SetSpeakerConfig(config); +} + +bool8 +cDMAudio::IsMP3RadioChannelAvailable(void) +{ + return AudioManager.IsMP3RadioChannelAvailable(); +} + +void +cDMAudio::ReleaseDigitalHandle(void) +{ + AudioManager.ReleaseDigitalHandle(); +} + +void +cDMAudio::ReacquireDigitalHandle(void) +{ + AudioManager.ReacquireDigitalHandle(); +} + +void +cDMAudio::SetDynamicAcousticModelingStatus(bool8 status) +{ +#ifdef AUDIO_REFLECTIONS + AudioManager.SetDynamicAcousticModelingStatus(status); +#endif +} + +bool8 +cDMAudio::CheckForAnAudioFileOnCD(void) +{ + return AudioManager.CheckForAnAudioFileOnCD(); +} + +char +cDMAudio::GetCDAudioDriveLetter(void) +{ + return AudioManager.GetCDAudioDriveLetter(); +} + +bool8 +cDMAudio::IsAudioInitialised(void) +{ + return AudioManager.IsAudioInitialised(); +} + +void +cDMAudio::ResetPoliceRadio() +{ + AudioManager.ResetPoliceRadio(); +} + +void +cDMAudio::ReportCrime(eCrimeType crime, const CVector &pos) +{ + AudioManager.ReportCrime(crime, pos); +} + +int32 +cDMAudio::CreateLoopingScriptObject(cAudioScriptObject *scriptObject) +{ + int32 audioEntity = AudioManager.CreateEntity(AUDIOTYPE_SCRIPTOBJECT, scriptObject); + + if ( AEHANDLE_IS_OK(audioEntity) ) + AudioManager.SetEntityStatus(audioEntity, TRUE); + + return audioEntity; +} + +void +cDMAudio::DestroyLoopingScriptObject(int32 audioEntity) +{ + AudioManager.DestroyEntity(audioEntity); +} + +void +cDMAudio::CreateOneShotScriptObject(cAudioScriptObject *scriptObject) +{ + int32 audioEntity = AudioManager.CreateEntity(AUDIOTYPE_SCRIPTOBJECT, scriptObject); + + if ( AEHANDLE_IS_OK(audioEntity) ) + { + AudioManager.SetEntityStatus(audioEntity, TRUE); + AudioManager.PlayOneShot(audioEntity, scriptObject->AudioId, 0.0f); + } +} + +void +cDMAudio::PlaySuspectLastSeen(float x, float y, float z) +{ + AudioManager.PlaySuspectLastSeen(x, y, z); +} + +void +cDMAudio::ReportCollision(CEntity *entityA, CEntity *entityB, uint8 surfaceTypeA, uint8 surfaceTypeB, float collisionPower, float velocity) +{ + AudioManager.ReportCollision(entityA, entityB, surfaceTypeA, surfaceTypeB, collisionPower, velocity); +} + +void +cDMAudio::PlayFrontEndSound(uint16 frontend, uint32 volume) +{ + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, frontend, (float)volume); +} + +void +cDMAudio::PlayRadioAnnouncement(uint8 announcement) +{ + MusicManager.PlayAnnouncement(announcement); +} + +void +cDMAudio::PlayFrontEndTrack(uint8 track, bool8 frontendFlag) +{ + MusicManager.PlayFrontEndTrack(track, frontendFlag); +} + +void +cDMAudio::StopFrontEndTrack(void) +{ + MusicManager.StopFrontEndTrack(); +} + +void +cDMAudio::ResetTimers(uint32 time) +{ + AudioManager.ResetTimers(time); +} + +void +cDMAudio::ChangeMusicMode(uint8 mode) +{ + MusicManager.ChangeMusicMode(mode); +} + +void +cDMAudio::PreloadCutSceneMusic(uint8 track) +{ + MusicManager.PreloadCutSceneMusic(track); +} + +void +cDMAudio::PlayPreloadedCutSceneMusic(void) +{ + MusicManager.PlayPreloadedCutSceneMusic(); +} + +void +cDMAudio::StopCutSceneMusic(void) +{ + MusicManager.StopCutSceneMusic(); +} + +void +cDMAudio::PreloadMissionAudio(Const char *missionAudio) +{ + AudioManager.PreloadMissionAudio(missionAudio); +} + +uint8 +cDMAudio::GetMissionAudioLoadingStatus(void) +{ + return AudioManager.GetMissionAudioLoadingStatus(); +} + +void +cDMAudio::SetMissionAudioLocation(float x, float y, float z) +{ + AudioManager.SetMissionAudioLocation(x, y, z); +} + +void +cDMAudio::PlayLoadedMissionAudio(void) +{ + AudioManager.PlayLoadedMissionAudio(); +} + +bool8 +cDMAudio::IsMissionAudioSampleFinished(void) +{ + return AudioManager.IsMissionAudioSampleFinished(); +} + +void +cDMAudio::ClearMissionAudio(void) +{ + AudioManager.ClearMissionAudio(); +} + +uint8 +cDMAudio::GetRadioInCar(void) +{ + return MusicManager.GetRadioInCar(); +} + +void +cDMAudio::SetRadioInCar(uint32 radio) +{ + MusicManager.SetRadioInCar(radio); +} + +void +cDMAudio::SetRadioChannel(uint8 radio, int32 pos) +{ + MusicManager.SetRadioChannelByScript(radio, pos); +} diff --git a/src/audio/DMAudio.h b/src/audio/DMAudio.h new file mode 100644 index 0000000..9f42727 --- /dev/null +++ b/src/audio/DMAudio.h @@ -0,0 +1,91 @@ +#pragma once + +#include "audio_enums.h" +#include "soundlist.h" +#include "Crime.h" + +#define AEHANDLE_IS_FAILED(h) ((h)<0) +#define AEHANDLE_IS_OK(h) ((h)>=0) + +class cAudioScriptObject; +class CEntity; + +class cDMAudio +{ +public: + ~cDMAudio() + { } + + void Initialise(void); + void Terminate(void); + void Service(void); + + int32 CreateEntity(eAudioType type, void *UID); + void DestroyEntity(int32 audioEntity); + bool8 GetEntityStatus(int32 audioEntity); + void SetEntityStatus(int32 audioEntity, bool8 status); + void PlayOneShot(int32 audioEntity, uint16 oneShot, float volume); + void DestroyAllGameCreatedEntities(void); + + void SetMonoMode(bool8 mono); + void SetEffectsMasterVolume(uint8 volume); + void SetMusicMasterVolume(uint8 volume); + void SetEffectsFadeVol(uint8 volume); + void SetMusicFadeVol(uint8 volume); + + uint8 GetNum3DProvidersAvailable(void); + char *Get3DProviderName(uint8 id); + + int8 GetCurrent3DProviderIndex(void); + int8 SetCurrent3DProvider(uint8 which); + + void SetSpeakerConfig(int32 config); + + bool8 IsMP3RadioChannelAvailable(void); + + void ReleaseDigitalHandle(void); + void ReacquireDigitalHandle(void); + + void SetDynamicAcousticModelingStatus(bool8 status); + + bool8 CheckForAnAudioFileOnCD(void); + + char GetCDAudioDriveLetter(void); + bool8 IsAudioInitialised(void); + + void ResetPoliceRadio(); + void ReportCrime(eCrimeType crime, CVector const &pos); + + int32 CreateLoopingScriptObject(cAudioScriptObject *scriptObject); + void DestroyLoopingScriptObject(int32 audioEntity); + void CreateOneShotScriptObject(cAudioScriptObject *scriptObject); + + void PlaySuspectLastSeen(float x, float y, float z); + + void ReportCollision(CEntity *entityA, CEntity *entityB, uint8 surfaceTypeA, uint8 surfaceTypeB, float collisionPower, float velocity); + + void PlayFrontEndSound(uint16 frontend, uint32 volume); + void PlayRadioAnnouncement(uint8 announcement); + void PlayFrontEndTrack(uint8 track, bool8 frontendFlag); + void StopFrontEndTrack(void); + + void ResetTimers(uint32 time); + + void ChangeMusicMode(uint8 mode); + + void PreloadCutSceneMusic(uint8 track); + void PlayPreloadedCutSceneMusic(void); + void StopCutSceneMusic(void); + + void PreloadMissionAudio(Const char *missionAudio); + uint8 GetMissionAudioLoadingStatus(void); + void SetMissionAudioLocation(float x, float y, float z); + void PlayLoadedMissionAudio(void); + bool8 IsMissionAudioSampleFinished(void); + void ClearMissionAudio(void); + + uint8 GetRadioInCar(void); + void SetRadioInCar(uint32 radio); + void SetRadioChannel(uint8 radio, int32 pos); +}; +extern cDMAudio DMAudio; diff --git a/src/audio/MusicManager.cpp b/src/audio/MusicManager.cpp new file mode 100644 index 0000000..3f4ae48 --- /dev/null +++ b/src/audio/MusicManager.cpp @@ -0,0 +1,1026 @@ +#include "common.h" +#include +#include "soundlist.h" +#include "MusicManager.h" +#include "AudioManager.h" +#include "ControllerConfig.h" +#include "Camera.h" +#include "Font.h" +#include "Hud.h" +#include "ModelIndices.h" +#include "Replay.h" +#include "Pad.h" +#include "Text.h" +#include "Timer.h" +#include "World.h" +#include "sampman.h" + +#if !defined FIX_BUGS && (defined RADIO_SCROLL_TO_PREV_STATION || defined RADIO_OFF_TEXT) +static_assert(false, "RADIO_SCROLL_TO_PREV_STATION and RADIO_OFF_TEXT won't work correctly without FIX_BUGS"); +#endif + +cMusicManager MusicManager; +int32 gNumRetunePresses; +int32 gRetuneCounter; +bool8 bHasStarted; + +cMusicManager::cMusicManager() +{ + m_bIsInitialised = FALSE; + m_bDisabled = FALSE; + m_nMusicMode = MUSICMODE_DISABLED; + m_nNextTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + m_bFrontendTrackFinished = FALSE; + m_bPlayInFrontend = FALSE; + m_bSetNextStation = FALSE; + m_nAnnouncement = NO_TRACK; + m_bPreviousPlayerInCar = FALSE; + m_bPlayerInCar = FALSE; + m_bAnnouncementInProgress = FALSE; + m_bVerifyAmbienceTrackStartedToPlay = FALSE; + bHasStarted = FALSE; +} + +bool8 +cMusicManager::PlayerInCar() +{ + if(!FindPlayerVehicle()) + return FALSE; + + int32 State = FindPlayerPed()->m_nPedState; + + if(State == PED_DRAG_FROM_CAR || State == PED_EXIT_CAR || State == PED_ARRESTED) + return FALSE; + + if (!FindPlayerVehicle()) + return TRUE; + + if (FindPlayerVehicle()->GetStatus() == STATUS_WRECKED) + return FALSE; + + switch (FindPlayerVehicle()->GetModelIndex()) { + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_MRWHOOP: + case MI_PREDATOR: + case MI_TRAIN: + case MI_SPEEDER: + case MI_REEFER: + case MI_GHOST: return FALSE; + default: return TRUE; + } +} + +void +cMusicManager::DisplayRadioStationName() +{ + int8 pRetune; + int8 gStreamedSound; + int8 gRetuneCounter; + static wchar *pCurrentStation = nil; + static uint8 cDisplay = 0; + + if(!CTimer::GetIsPaused() && !TheCamera.m_WideScreenOn && PlayerInCar() && + !CReplay::IsPlayingBack()) { + if(m_bPlayerInCar && !m_bPreviousPlayerInCar) + pCurrentStation = nil; + +#ifdef FIX_BUGS + const int curRadio = GetCarTuning(); +#else + const int curRadio = m_nNextTrack; +#endif + +#ifdef RADIO_SCROLL_TO_PREV_STATION + if(gNumRetunePresses < 0) { + gStreamedSound = curRadio; + + gRetuneCounter = gNumRetunePresses; + pRetune = gStreamedSound; + + while(gRetuneCounter < 0) { + if(pRetune == HEAD_RADIO) { + pRetune = RADIO_OFF; + } else if(pRetune == RADIO_OFF || pRetune == NUM_RADIOS) { + pRetune = SampleManager.IsMP3RadioChannelAvailable() ? USERTRACK : USERTRACK - 1; + } else + pRetune--; + + ++gRetuneCounter; + } + } else +#endif + if(SampleManager.IsMP3RadioChannelAvailable()) { + gStreamedSound = curRadio; + + if(gStreamedSound == STREAMED_SOUND_CITY_AMBIENT || + gStreamedSound == STREAMED_SOUND_WATER_AMBIENT) { // which means OFF + gStreamedSound = NUM_RADIOS; + } else if(gStreamedSound > STREAMED_SOUND_RADIO_MP3_PLAYER) + return; + + pRetune = gNumRetunePresses + gStreamedSound; + +#ifdef FIX_BUGS + while(pRetune > NUM_RADIOS) + pRetune -= (NUM_RADIOS + 1); +#endif + if(pRetune == NUM_RADIOS) { + pRetune = RADIO_OFF; + } +#ifndef FIX_BUGS + else if(pRetune > NUM_RADIOS) { + pRetune = pRetune - (NUM_RADIOS + 1); + } +#endif + } else { + gStreamedSound = curRadio; + pRetune = gNumRetunePresses + gStreamedSound; + + if(pRetune >= USERTRACK) { + gRetuneCounter = gNumRetunePresses; + pRetune = curRadio; + + if(gStreamedSound == STREAMED_SOUND_WATER_AMBIENT) + pRetune = STREAMED_SOUND_CITY_AMBIENT; // which is RADIO_OFF + + while(gRetuneCounter) { + if(pRetune == RADIO_OFF) { + pRetune = HEAD_RADIO; + } else if(pRetune < USERTRACK) { + pRetune = pRetune + 1; + } + if(pRetune == USERTRACK) pRetune = RADIO_OFF; + + --gRetuneCounter; + } + } + } + + wchar *string; + + switch(pRetune) { + case HEAD_RADIO: string = TheText.Get("FEA_FM0"); break; + case DOUBLE_CLEF: string = TheText.Get("FEA_FM1"); break; + case JAH_RADIO: string = TheText.Get("FEA_FM2"); break; + case RISE_FM: string = TheText.Get("FEA_FM3"); break; + case LIPS_106: string = TheText.Get("FEA_FM4"); break; + case GAME_FM: string = TheText.Get("FEA_FM5"); break; + case MSX_FM: string = TheText.Get("FEA_FM6"); break; + case FLASHBACK: string = TheText.Get("FEA_FM7"); break; + case CHATTERBOX: string = TheText.Get("FEA_FM8"); break; + case USERTRACK: + if (!SampleManager.IsMP3RadioChannelAvailable()) + return; + string = TheText.Get("FEA_FM9"); break; +#ifdef RADIO_OFF_TEXT + case RADIO_OFF: { + extern wchar WideErrorString[]; + + string = TheText.Get("FEA_FMN"); + if(string == WideErrorString) { + pCurrentStation = nil; + return; + } + break; + } +#endif + default: return; + }; + + if(pCurrentStation != string || + m_nNextTrack == STREAMED_SOUND_RADIO_MP3_PLAYER && m_nPlayingTrack != STREAMED_SOUND_RADIO_MP3_PLAYER) { + pCurrentStation = string; + cDisplay = 60; + } else { + if(cDisplay == 0) return; +#ifdef FIX_BUGS + cDisplay -= CTimer::GetLogicalFramesPassed(); +#else + cDisplay--; +#endif + } + + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetCentreOn(); + // Reminder: Game doesn't have "scaling" at all, it just stretches, and it's team's decision here to not let centered text occupy all the screen. + // Disable ASPECT_RATIO_SCALE and it'll go back to default behaviour; stretching. + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(22.0f) + SCREEN_SCALE_Y(2.0f), pCurrentStation); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + 2.0f, SCREEN_SCALE_Y(22.0f) + 2.0f, pCurrentStation); +#endif + + if(gNumRetunePresses) + CFont::SetColor(CRGBA(102, 133, 143, 255)); + else + CFont::SetColor(CRGBA(147, 196, 211, 255)); + + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_SCALE_Y(22.0f), pCurrentStation); + CFont::DrawFonts(); + } +} + +bool8 +cMusicManager::Initialise() +{ + int pos; + + if (!IsInitialised()) { + time_t timevalue = time(0); + if (timevalue == -1) { + pos = AudioManager.m_anRandomTable[0]; + } else { + tm *pTm = localtime(&timevalue); + if (pTm->tm_sec == 0) + pTm->tm_sec = AudioManager.m_anRandomTable[0]; + if (pTm->tm_min == 0) + pTm->tm_min = AudioManager.m_anRandomTable[1]; + if (pTm->tm_hour == 0) + pTm->tm_hour = AudioManager.m_anRandomTable[2]; + if (pTm->tm_mday == 0) + pTm->tm_mday = AudioManager.m_anRandomTable[3]; + if (pTm->tm_mon == 0) + pTm->tm_mon = AudioManager.m_anRandomTable[4]; + if (pTm->tm_year == 0) + pTm->tm_year = AudioManager.m_anRandomTable[3]; + if (pTm->tm_wday == 0) + pTm->tm_wday = AudioManager.m_anRandomTable[2]; + pos = pTm->tm_yday + * pTm->tm_wday + * pTm->tm_year + * pTm->tm_mon + * pTm->tm_mday + * pTm->tm_hour * pTm->tm_hour + * pTm->tm_min * pTm->tm_min + * pTm->tm_sec * pTm->tm_sec * pTm->tm_sec * pTm->tm_sec; + } + + for (int i = 0; i < TOTAL_STREAMED_SOUNDS; i++) { + m_aTracks[i].m_nLength = SampleManager.GetStreamedFileLength(i); + m_aTracks[i].m_nPosition = pos * AudioManager.m_anRandomTable[i % 5] % m_aTracks[i].m_nLength; + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + + m_bResetTimers = FALSE; + m_nResetTime = 0; + m_nTimer = m_nLastTrackServiceTime = CTimer::GetTimeInMillisecondsPauseMode(); + m_bDoTrackService = FALSE; + m_bIgnoreTimeDelay = FALSE; + m_bRadioSetByScript = FALSE; + m_nRadioStationScript = HEAD_RADIO; + m_nRadioPosition = -1; + m_nRadioInCar = NO_TRACK; + gNumRetunePresses = 0; + gRetuneCounter = 0; + m_bIsInitialised = TRUE; + } + return m_bIsInitialised; +} + +void +cMusicManager::Terminate() +{ + if (!IsInitialised()) return; + + if (SampleManager.IsStreamPlaying()) { + SampleManager.StopStreamedFile(); + m_nNextTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + } + m_bIsInitialised = FALSE; +} + +void +cMusicManager::ChangeMusicMode(uint8 mode) +{ + if (!IsInitialised()) return; + + uint8 mode2; + switch (mode) + { + case MUSICMODE_FRONTEND: + mode2 = MUSICMODE_FRONTEND; +#ifdef PAUSE_RADIO_IN_FRONTEND + // rewind those streams we weren't listening right now + for (uint32 i = STREAMED_SOUND_RADIO_HEAD; i < STREAMED_SOUND_CUTSCENE_LUIGI1_LG; i++) { + m_aTracks[i].m_nPosition = GetTrackStartPos(i); + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } +#endif + break; + case MUSICMODE_GAME: mode2 = MUSICMODE_GAME; break; + case MUSICMODE_CUTSCENE: mode2 = MUSICMODE_CUTSCENE; break; + case MUSICMODE_DISABLE: mode2 = MUSICMODE_DISABLED; break; + default: return; + } + + if (mode2 != m_nMusicMode || mode == MUSICMODE_FRONTEND && mode2 == MUSICMODE_FRONTEND) { + switch (mode) + { + case MUSICMODE_FRONTEND: + case MUSICMODE_GAME: + case MUSICMODE_CUTSCENE: + case MUSICMODE_DISABLED: + if (SampleManager.IsStreamPlaying()) { + if (m_nNextTrack < TOTAL_STREAMED_SOUNDS) { + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + SampleManager.StopStreamedFile(); + } + m_nNextTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + m_bFrontendTrackFinished = FALSE; + m_bPlayInFrontend = FALSE; + m_bSetNextStation = FALSE; + m_bPreviousPlayerInCar = FALSE; + m_bPlayerInCar = FALSE; + m_bAnnouncementInProgress = FALSE; + m_nTimer = m_nLastTrackServiceTime = CTimer::GetTimeInMillisecondsPauseMode(); + m_bDoTrackService = FALSE; + m_bIgnoreTimeDelay = TRUE; + m_bVerifyAmbienceTrackStartedToPlay = FALSE; + m_nMusicMode = mode2; + break; + default: return; + } + } +} + +uint8 +cMusicManager::GetRadioInCar(void) +{ + if (!m_bIsInitialised) return HEAD_RADIO; + if (PlayerInCar()) { + CVehicle *veh = FindPlayerVehicle(); + if (veh != nil){ + if (UsesPoliceRadio(veh)) { + if (m_nRadioInCar == NO_TRACK || (CReplay::IsPlayingBack() && !AudioManager.m_bIsPaused)) + return POLICE_RADIO; + return m_nRadioInCar; + } else return veh->m_nRadioStation; + } + } + + if (m_nRadioInCar == NO_TRACK || (CReplay::IsPlayingBack() && !AudioManager.m_bIsPaused)) + return RADIO_OFF; + return m_nRadioInCar; +} + +void +cMusicManager::SetRadioInCar(uint32 station) +{ + if (m_bIsInitialised) { + if (!PlayerInCar()) { + m_nRadioInCar = station; + return; + } + CVehicle *veh = FindPlayerVehicle(); + if (veh == nil) return; + if (UsesPoliceRadio(veh)) + m_nRadioInCar = station; + else + veh->m_nRadioStation = station; + } +} + +void +cMusicManager::SetRadioChannelByScript(uint8 station, int32 pos) +{ + if (m_bIsInitialised && station < RADIO_OFF) { + m_bRadioSetByScript = TRUE; + m_nRadioStationScript = station; + m_nRadioPosition = pos == -1 ? -1 : pos % m_aTracks[station].m_nLength; + } +} + + +void +cMusicManager::ResetMusicAfterReload() +{ + m_bRadioSetByScript = FALSE; + m_nRadioStationScript = 0; + m_nRadioPosition = -1; + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + m_bSetNextStation = FALSE; + gRetuneCounter = 0; + gNumRetunePresses = 0; +} + + +void +cMusicManager::ResetTimers(uint32 time) +{ + m_bResetTimers = TRUE; + m_nResetTime = time; +} + +void +cMusicManager::Service() +{ + if (m_bResetTimers) { + m_bResetTimers = FALSE; + m_nLastTrackServiceTime = m_nResetTime; + } + + if (!m_bIsInitialised || m_bDisabled) return; + + if (m_nMusicMode == MUSICMODE_CUTSCENE) { + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, TRUE); + return; + } + + m_nTimer = CTimer::GetTimeInMillisecondsPauseMode(); + if (m_nTimer > (m_nLastTrackServiceTime + 2000) || m_bIgnoreTimeDelay) { + m_bIgnoreTimeDelay = FALSE; + m_bDoTrackService = TRUE; + m_nLastTrackServiceTime = m_nTimer; + } else m_bDoTrackService = FALSE; + + if (m_nNextTrack == NO_TRACK && SampleManager.IsStreamPlaying()) + SampleManager.StopStreamedFile(); + else switch (m_nMusicMode) { + case MUSICMODE_FRONTEND: ServiceFrontEndMode(); break; + case MUSICMODE_GAME: ServiceGameMode(); break; + } +} + +void +cMusicManager::ServiceFrontEndMode() +{ +#ifdef PAUSE_RADIO_IN_FRONTEND + // pause radio + for (uint32 i = STREAMED_SOUND_RADIO_HEAD; i < STREAMED_SOUND_CUTSCENE_LUIGI1_LG; i++) + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); +#endif + + if (m_nNextTrack < TOTAL_STREAMED_SOUNDS) { + if (m_bFrontendTrackFinished) { + if (!SampleManager.IsStreamPlaying()) { + switch (m_nNextTrack) + { + case STREAMED_SOUND_MISSION_COMPLETED: + if (!AudioManager.m_bIsPaused) + ChangeMusicMode(MUSICMODE_GAME); + break; + case STREAMED_SOUND_GAME_COMPLETED: + ChangeMusicMode(MUSICMODE_GAME); + break; + default: + break; + } + m_nNextTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + } + } else if (bHasStarted) { + if (!SampleManager.IsStreamPlaying()) + SampleManager.StartStreamedFile(m_nNextTrack, 0); + } else { + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + if (!SampleManager.StartStreamedFile(m_nNextTrack, m_nNextTrack < NUM_RADIOS ? GetTrackStartPos(m_nNextTrack) : 0)) + return; + SampleManager.SetStreamedVolumeAndPan(100, 63, FALSE); + if (m_bPlayInFrontend) bHasStarted = TRUE; + else m_bFrontendTrackFinished = TRUE; + } + } + if (SampleManager.IsStreamPlaying()) + SampleManager.SetStreamedVolumeAndPan((CPad::GetPad(0)->bDisplayNoControllerMessage || CPad::GetPad(0)->bObsoleteControllerMessage) ? 0 : 100, 63, FALSE); +} + +void +cMusicManager::ServiceGameMode() +{ + bool8 bRadioOff = FALSE; + static int8 nFramesSinceCutsceneEnded = -1; + uint8 volume; + + m_bPreviousPlayerInCar = m_bPlayerInCar; + m_bPlayerInCar = PlayerInCar(); + m_nPlayingTrack = m_nNextTrack; + if (m_bPlayerInCar) { + if (FindPlayerPed() != nil + && !FindPlayerPed()->DyingOrDead() + && !CReplay::IsPlayingBack() + && FindPlayerVehicle() != nil + && !UsesPoliceRadio(FindPlayerVehicle())) { + + if (CPad::GetPad(0)->ChangeStationJustDown()) { + gRetuneCounter = 30; + gNumRetunePresses++; + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_CHANGE, 1.0f); + // This needs loop, and this is not the right place. Now done elsewhere. +#ifndef FIX_BUGS + if (SampleManager.IsMP3RadioChannelAvailable()) { + if (gNumRetunePresses > RADIO_OFF) + gNumRetunePresses -= RADIO_OFF; + } +#endif + } +#ifdef RADIO_SCROLL_TO_PREV_STATION + else if(!CPad::GetPad(0)->ArePlayerControlsDisabled() && (CPad::GetPad(0)->GetMouseWheelDownJustDown() || CPad::GetPad(0)->GetMouseWheelUpJustDown())) { + int scrollNext = ControlsManager.GetControllerKeyAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, MOUSE); + int scrollPrev = scrollNext == rsMOUSEWHEELUPBUTTON ? rsMOUSEWHEELDOWNBUTTON : scrollNext == rsMOUSEWHEELDOWNBUTTON ? rsMOUSEWHEELUPBUTTON : -1; + + if (scrollPrev != -1 && !ControlsManager.IsAnyVehicleActionAssignedToMouseKey(scrollPrev)) { + gRetuneCounter = 30; + gNumRetunePresses--; + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_CHANGE, 1.0f); + } + } +#endif + } + } else { + nFramesSinceCutsceneEnded = -1; + } + + if (AudioManager.m_bWasPaused) + m_bPreviousPlayerInCar = FALSE; + if (!m_bPlayerInCar) { + if (m_bPreviousPlayerInCar) { + if (m_nNextTrack != STREAMED_SOUND_RADIO_POLICE) + m_nRadioInCar = m_nNextTrack; + } + ServiceAmbience(); + return; + } + + if (m_bPreviousPlayerInCar) { + if (m_nAnnouncement < TOTAL_STREAMED_SOUNDS + && (m_nNextTrack < RADIO_OFF || m_bAnnouncementInProgress) + && ServiceAnnouncement()) + { + if (m_bAnnouncementInProgress) { + m_bSetNextStation = FALSE; + return; + } + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = GetCarTuning(); + } + if (SampleManager.IsMP3RadioChannelAvailable() + && m_nNextTrack != STREAMED_SOUND_RADIO_MP3_PLAYER + && ControlsManager.GetIsKeyboardKeyJustDown(rsF9)) + { + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = STREAMED_SOUND_RADIO_MP3_PLAYER; + if (FindPlayerVehicle() != nil) + FindPlayerVehicle()->m_nRadioStation = STREAMED_SOUND_RADIO_MP3_PLAYER; + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_CHANGE, 1.0f); + gRetuneCounter = 0; + gNumRetunePresses = 0; + m_bSetNextStation = FALSE; + } + // Because when you switch radio back and forth, gNumRetunePresses will be 0 but gRetuneCounter won't. +#ifdef RADIO_SCROLL_TO_PREV_STATION + if (gRetuneCounter != 0) { + if (gRetuneCounter > 1) gRetuneCounter--; + else if (gRetuneCounter == 1) gRetuneCounter = -1; + else if (gRetuneCounter == -1) { + m_bSetNextStation = TRUE; + gRetuneCounter = 0; + } + } +#else + if (gNumRetunePresses) { + if (gRetuneCounter != 0) gRetuneCounter--; + else m_bSetNextStation = TRUE; + } +#endif + if (gRetuneCounter) + AudioManager.DoPoliceRadioCrackle(); + if (m_bSetNextStation) { + m_bSetNextStation = FALSE; + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = GetNextCarTuning(); + if (m_nNextTrack == STREAMED_SOUND_CITY_AMBIENT || m_nNextTrack == STREAMED_SOUND_WATER_AMBIENT) + bRadioOff = TRUE; + + if (m_nPlayingTrack == STREAMED_SOUND_CITY_AMBIENT || m_nPlayingTrack == STREAMED_SOUND_WATER_AMBIENT) + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_CHANGE, 0.0f); + } + if (m_nNextTrack < RADIO_OFF) { + if (ChangeRadioChannel()) { + ServiceTrack(); + } else { + m_bPlayerInCar = FALSE; + if (FindPlayerVehicle()) + FindPlayerVehicle()->m_nRadioStation = m_nNextTrack; + m_nNextTrack = NO_TRACK; + } + if (CTimer::GetIsSlowMotionActive()) { + if (TheCamera.pTargetEntity != nil) { + float DistToTargetSq = (TheCamera.pTargetEntity->GetPosition() - TheCamera.GetPosition()).MagnitudeSqr(); + if (DistToTargetSq >= SQR(55.0f)) { + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + } else if (DistToTargetSq >= SQR(10.0f)) { + volume = ((45.0f - (Sqrt(DistToTargetSq) - 10.0f)) / 45.0f * 100.0f); + uint8 pan; + if (AudioManager.ShouldDuckMissionAudio()) + volume /= 4; + if (volume > 0) { + CVector panVec; + AudioManager.TranslateEntity(&TheCamera.pTargetEntity->GetPosition(), &panVec); + pan = AudioManager.ComputePan(55.0f, &panVec); + } else { + pan = 0; + } + if (gRetuneCounter) + volume /= 4; + SampleManager.SetStreamedVolumeAndPan(volume, pan, FALSE); + } else if (AudioManager.ShouldDuckMissionAudio()) { + SampleManager.SetStreamedVolumeAndPan(25, 63, FALSE); + } else if (gRetuneCounter) { + SampleManager.SetStreamedVolumeAndPan(25, 63, FALSE); + } else { + SampleManager.SetStreamedVolumeAndPan(100, 63, FALSE); + } + } + } else if (AudioManager.ShouldDuckMissionAudio()) { + SampleManager.SetStreamedVolumeAndPan(25, 63, FALSE); + nFramesSinceCutsceneEnded = 0; + } else { + if (nFramesSinceCutsceneEnded == -1) { + volume = 100; + } else if (nFramesSinceCutsceneEnded < 20) { + nFramesSinceCutsceneEnded++; + volume = 25; + } else if (nFramesSinceCutsceneEnded < 40) { + volume = 3 * (nFramesSinceCutsceneEnded - 20) + 25; + nFramesSinceCutsceneEnded++; + } else { + nFramesSinceCutsceneEnded = -1; + volume = 100; + } + if (gRetuneCounter != 0) + volume /= 4; + SampleManager.SetStreamedVolumeAndPan(volume, 63, FALSE); + } + return; + } + if (bRadioOff) { + m_nNextTrack = m_nPlayingTrack; + if (FindPlayerVehicle() != nil) + FindPlayerVehicle()->m_nRadioStation = RADIO_OFF; + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_TURN_OFF, 0.0f); + } + ServiceAmbience(); + return; + } + if (m_bRadioSetByScript) { + if (UsesPoliceRadio(FindPlayerVehicle())) { + m_nNextTrack = STREAMED_SOUND_RADIO_POLICE; + } else { + m_nNextTrack = m_nRadioStationScript; + if (FindPlayerVehicle()->m_nRadioStation == m_nNextTrack) { + m_nPlayingTrack = NO_TRACK; + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + SampleManager.StopStreamedFile(); + } + if (m_nRadioPosition != -1) { + m_aTracks[m_nNextTrack].m_nPosition = m_nRadioPosition; + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + } + } else { + m_nNextTrack = GetCarTuning(); + } + if (m_nNextTrack >= RADIO_OFF) { + ServiceAmbience(); + return; + } + if (ChangeRadioChannel()) { + if (m_bRadioSetByScript) { + m_bRadioSetByScript = FALSE; + FindPlayerVehicle()->m_nRadioStation = m_nNextTrack; + } + } else { + m_bPlayerInCar = FALSE; + m_nNextTrack = NO_TRACK; + } +} + +void +cMusicManager::StopFrontEndTrack() +{ + if (IsInitialised() && !m_bDisabled && m_nMusicMode == MUSICMODE_FRONTEND && m_nNextTrack != NO_TRACK) { + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.StopStreamedFile(); + m_nPlayingTrack = NO_TRACK; + m_nNextTrack = NO_TRACK; + } +} + +void +cMusicManager::PlayAnnouncement(uint8 announcement) +{ + if (IsInitialised() && !m_bDisabled && !m_bAnnouncementInProgress) + m_nAnnouncement = announcement; +} + +void +cMusicManager::PlayFrontEndTrack(uint8 track, bool8 bPlayInFrontend) +{ + if (IsInitialised() && !m_bDisabled && track < TOTAL_STREAMED_SOUNDS) { + if (m_nMusicMode == MUSICMODE_GAME) { + if (m_nNextTrack != NO_TRACK) { + if (m_bAnnouncementInProgress) { + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + } + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + SampleManager.StopStreamedFile(); + } else if (m_nMusicMode == MUSICMODE_FRONTEND) { + if (m_nNextTrack != NO_TRACK) { + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + SampleManager.StopStreamedFile(); + } + + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = track; + m_bPlayInFrontend = bPlayInFrontend; + m_bFrontendTrackFinished = FALSE; + m_bDoTrackService = TRUE; + bHasStarted = FALSE; + if (m_nNextTrack < NUM_RADIOS) { + gRetuneCounter = 0; + gNumRetunePresses = 0; + } + } +} + +void +cMusicManager::PreloadCutSceneMusic(uint8 track) +{ + if (IsInitialised() && !m_bDisabled && track < TOTAL_STREAMED_SOUNDS && m_nMusicMode == MUSICMODE_CUTSCENE) { + AudioManager.ResetPoliceRadio(); + while (SampleManager.IsStreamPlaying()) + SampleManager.StopStreamedFile(); + SampleManager.PreloadStreamedFile(track); + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, TRUE); + m_nNextTrack = track; + } +} + +void +cMusicManager::PlayPreloadedCutSceneMusic(void) +{ + if (IsInitialised() && !m_bDisabled && m_nMusicMode == MUSICMODE_CUTSCENE) + SampleManager.StartPreloadedStreamedFile(); +} + +void +cMusicManager::StopCutSceneMusic(void) +{ + if (IsInitialised() && !m_bDisabled && m_nMusicMode == MUSICMODE_CUTSCENE) { + SampleManager.StopStreamedFile(); + m_nNextTrack = NO_TRACK; + } +} + +uint32 +cMusicManager::GetTrackStartPos(uint8 track) +{ + uint32 pos = m_aTracks[track].m_nPosition; + if (CTimer::GetTimeInMillisecondsPauseMode() > m_aTracks[track].m_nLastPosCheckTimer) + pos += Min(CTimer::GetTimeInMillisecondsPauseMode() - m_aTracks[track].m_nLastPosCheckTimer, 90000); + else + m_aTracks[track].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + + if (pos > m_aTracks[track].m_nLength) + pos %= m_aTracks[track].m_nLength; + return pos; +} + + +bool8 +cMusicManager::UsesPoliceRadio(CVehicle *veh) +{ + switch (veh->GetModelIndex()) + { + case MI_FBICAR: + case MI_POLICE: + case MI_ENFORCER: + case MI_PREDATOR: + case MI_RHINO: + case MI_BARRACKS: + return TRUE; + } + return FALSE; +} + +void +cMusicManager::ServiceAmbience() +{ + uint8 volume; + + if (m_bAnnouncementInProgress) { + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + } + if (m_nNextTrack < RADIO_OFF) { + if (SampleManager.IsStreamPlaying()) { + m_aTracks[m_nNextTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.StopStreamedFile(); + m_nNextTrack = NO_TRACK; + return; + } + m_nNextTrack = RADIO_OFF; + } + if (CWorld::Players[CWorld::PlayerInFocus].m_WBState != WBSTATE_PLAYING && !SampleManager.IsStreamPlaying()) { + m_nNextTrack = NO_TRACK; + return; + } + + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = TheCamera.DistanceToWater <= 45.0f ? STREAMED_SOUND_WATER_AMBIENT : STREAMED_SOUND_CITY_AMBIENT; + + if (m_nNextTrack == m_nPlayingTrack) { + ComputeAmbienceVol(FALSE, volume); + SampleManager.SetStreamedVolumeAndPan(volume, 63, TRUE); + if (m_bVerifyAmbienceTrackStartedToPlay) { + if (SampleManager.IsStreamPlaying()) + m_bVerifyAmbienceTrackStartedToPlay = FALSE; + } else ServiceTrack(); + } else { + if (m_nPlayingTrack < TOTAL_STREAMED_SOUNDS) { + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.StopStreamedFile(); + } + uint32 pos = GetTrackStartPos(m_nNextTrack); + SampleManager.SetStreamedVolumeAndPan(0, 63, TRUE); + if (SampleManager.StartStreamedFile(m_nNextTrack, pos)) { + ComputeAmbienceVol(TRUE, volume); + SampleManager.SetStreamedVolumeAndPan(volume, 63, TRUE); + m_bVerifyAmbienceTrackStartedToPlay = TRUE; + } else + m_nNextTrack = NO_TRACK; + } +} + +void +cMusicManager::ComputeAmbienceVol(bool8 reset, uint8 &outVolume) +{ + static float fVol = 0.0f; + + if (reset) + fVol = 0.0f; + else if (fVol < 60.0f) + fVol += 1.0f; + + if (TheCamera.DistanceToWater > 70.0f) + outVolume = fVol; + else if (TheCamera.DistanceToWater > 45.0f) + outVolume = (TheCamera.DistanceToWater - 45.0f) / 25.0f * fVol; + else if (TheCamera.DistanceToWater > 20.0f) + outVolume = (45.0f - TheCamera.DistanceToWater) / 25.0f * fVol; + else + outVolume = fVol; +} + +void +cMusicManager::ServiceTrack() +{ + if (m_bDoTrackService) { + if (!SampleManager.IsStreamPlaying()) + SampleManager.StartStreamedFile(m_nNextTrack, 0); + } +} + +bool8 +cMusicManager::ServiceAnnouncement() +{ + static int8 cCheck = 0; + if (m_bAnnouncementInProgress) { + if (!SampleManager.IsStreamPlaying()) { + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + } + return TRUE; + } + + if (++cCheck >= 30) { + cCheck = 0; + int pos = SampleManager.GetStreamedFilePosition(); + if (SampleManager.IsStreamPlaying()) { + if (m_nNextTrack != NO_TRACK) { + m_aTracks[m_nNextTrack].m_nPosition = pos; + m_aTracks[m_nNextTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.StopStreamedFile(); + } + } + + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + if (SampleManager.StartStreamedFile(m_nAnnouncement, 0)) { + SampleManager.SetStreamedVolumeAndPan(AudioManager.ShouldDuckMissionAudio() ? 25 : 100, 63, FALSE); + m_bAnnouncementInProgress = TRUE; + m_nPlayingTrack = m_nNextTrack; + m_nNextTrack = m_nAnnouncement; + return TRUE; + } + + if (cCheck != 0) cCheck--; + else cCheck = 30; + return FALSE; + } + + return FALSE; +} + +uint8 +cMusicManager::GetCarTuning() +{ + CVehicle *veh = FindPlayerVehicle(); + if (veh == nil) return RADIO_OFF; + if (UsesPoliceRadio(veh)) return POLICE_RADIO; + if (veh->m_nRadioStation == USERTRACK && !SampleManager.IsMP3RadioChannelAvailable()) + veh->m_nRadioStation = AudioManager.m_anRandomTable[2] % USERTRACK; + return veh->m_nRadioStation; +} + +uint8 +cMusicManager::GetNextCarTuning() +{ + CVehicle *veh = FindPlayerVehicle(); + if (veh == nil) return RADIO_OFF; + if (UsesPoliceRadio(veh)) return POLICE_RADIO; + if (gNumRetunePresses != 0) { +#ifdef RADIO_SCROLL_TO_PREV_STATION + if (gNumRetunePresses < 0) { + while (gNumRetunePresses < 0) { + if(veh->m_nRadioStation == HEAD_RADIO) { + veh->m_nRadioStation = RADIO_OFF; + } else if(veh->m_nRadioStation == RADIO_OFF || veh->m_nRadioStation == NUM_RADIOS) { + veh->m_nRadioStation = SampleManager.IsMP3RadioChannelAvailable() ? USERTRACK : USERTRACK - 1; + } else + veh->m_nRadioStation--; + + ++gNumRetunePresses; + } + } else +#endif + if (SampleManager.IsMP3RadioChannelAvailable()) { + if (veh->m_nRadioStation == RADIO_OFF) + veh->m_nRadioStation = NUM_RADIOS; + veh->m_nRadioStation += gNumRetunePresses; +#ifdef FIX_BUGS + while (veh->m_nRadioStation > NUM_RADIOS) + veh->m_nRadioStation -= (NUM_RADIOS + 1); +#endif + if (veh->m_nRadioStation == NUM_RADIOS) + veh->m_nRadioStation = RADIO_OFF; +#ifndef FIX_BUGS + else if (veh->m_nRadioStation > NUM_RADIOS) + veh->m_nRadioStation -= (NUM_RADIOS + 1); +#endif + } else if (gNumRetunePresses + veh->m_nRadioStation >= USERTRACK) { + while (gNumRetunePresses) { + if (veh->m_nRadioStation == RADIO_OFF) + veh->m_nRadioStation = HEAD_RADIO; + else if (veh->m_nRadioStation < USERTRACK) + ++veh->m_nRadioStation; + + if (veh->m_nRadioStation == USERTRACK) + veh->m_nRadioStation = RADIO_OFF; + --gNumRetunePresses; + } + } else + veh->m_nRadioStation += gNumRetunePresses; + gNumRetunePresses = 0; + } + return veh->m_nRadioStation; +} + +bool8 +cMusicManager::ChangeRadioChannel() +{ + if (m_nNextTrack != m_nPlayingTrack) { + if (m_nPlayingTrack < TOTAL_STREAMED_SOUNDS) { + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + SampleManager.StopStreamedFile(); + } + if (SampleManager.IsStreamPlaying()) + return FALSE; + if (!SampleManager.StartStreamedFile(m_nNextTrack, GetTrackStartPos(m_nNextTrack))) + return FALSE; + SampleManager.SetStreamedVolumeAndPan(AudioManager.ShouldDuckMissionAudio() ? 25 : 100, 63, FALSE); + } + return TRUE; +} diff --git a/src/audio/MusicManager.h b/src/audio/MusicManager.h new file mode 100644 index 0000000..49c91cf --- /dev/null +++ b/src/audio/MusicManager.h @@ -0,0 +1,89 @@ +#pragma once + +#include "audio_enums.h" + +class tStreamedSample +{ +public: + uint32 m_nLength; + uint32 m_nPosition; + uint32 m_nLastPosCheckTimer; +}; + +class CVehicle; + +class cMusicManager +{ +public: + bool8 m_bIsInitialised; + bool8 m_bDisabled; + uint8 m_nMusicMode; + uint8 m_nNextTrack; + uint8 m_nPlayingTrack; + bool8 m_bFrontendTrackFinished; + bool8 m_bPlayInFrontend; + bool8 m_bSetNextStation; + uint8 m_nAnnouncement; + bool8 m_bPreviousPlayerInCar; + bool8 m_bPlayerInCar; + bool8 m_bAnnouncementInProgress; + tStreamedSample m_aTracks[TOTAL_STREAMED_SOUNDS]; + bool8 m_bResetTimers; + uint32 m_nResetTime; + uint32 m_nLastTrackServiceTime; + uint32 m_nTimer; + bool8 m_bDoTrackService; + bool8 m_bIgnoreTimeDelay; + bool8 m_bVerifyAmbienceTrackStartedToPlay; + bool8 m_bRadioSetByScript; + uint8 m_nRadioStationScript; + int32 m_nRadioPosition; + uint8 m_nRadioInCar; + +public: + cMusicManager(); + bool8 IsInitialised() { return m_bIsInitialised; } + uint32 GetMusicMode() { return m_nMusicMode; } + uint8 GetNextTrack() { return m_nNextTrack; } + + bool8 Initialise(); + void Terminate(); + + void ChangeMusicMode(uint8 mode); + void StopFrontEndTrack(); + + bool8 PlayerInCar(); + void DisplayRadioStationName(); + + void PlayAnnouncement(uint8); + void PlayFrontEndTrack(uint8, bool8); + void PreloadCutSceneMusic(uint8); + void PlayPreloadedCutSceneMusic(void); + void StopCutSceneMusic(void); + uint8 GetRadioInCar(void); + void SetRadioInCar(uint32); + void SetRadioChannelByScript(uint8, int32); + + void ResetMusicAfterReload(); + + void ResetTimers(uint32); + void Service(); + void ServiceFrontEndMode(); + void ServiceGameMode(); + void ServiceAmbience(); + void ServiceTrack(); + + bool8 UsesPoliceRadio(CVehicle *veh); + uint32 GetTrackStartPos(uint8); + + void ComputeAmbienceVol(bool8 reset, uint8& outVolume); + bool8 ServiceAnnouncement(); + + uint8 GetCarTuning(); + uint8 GetNextCarTuning(); + bool8 ChangeRadioChannel(); +}; + +VALIDATE_SIZE(cMusicManager, 0x95C); + +extern cMusicManager MusicManager; diff --git a/src/audio/PolRadio.cpp b/src/audio/PolRadio.cpp new file mode 100644 index 0000000..f2c14ec --- /dev/null +++ b/src/audio/PolRadio.cpp @@ -0,0 +1,805 @@ +#include "common.h" + +#include "DMAudio.h" + +#include "AudioManager.h" + +#include "AudioSamples.h" +#include "MusicManager.h" +#include "PlayerPed.h" +#include "PolRadio.h" +#include "Replay.h" +#include "Vehicle.h" +#include "World.h" +#include "Zones.h" +#include "sampman.h" +#include "Wanted.h" + +struct tPoliceRadioZone { + char m_aName[8]; + uint32 m_nSampleIndex; + int32 field_12; +}; + +tPoliceRadioZone ZoneSfx[NUMAUDIOZONES]; +char SubZo2Label[8]; +char SubZo3Label[8]; + +uint32 g_nMissionAudioSfx = TOTAL_AUDIO_SAMPLES; +int8 g_nMissionAudioPlayingStatus = PLAY_STATUS_FINISHED; +bool8 gSpecialSuspectLastSeenReport; +uint32 gMinTimeToNextReport[NUM_CRIME_TYPES]; + +void +cAudioManager::InitialisePoliceRadioZones() +{ + for (int32 i = 0; i < NUMAUDIOZONES; i++) + memset(ZoneSfx[i].m_aName, 0, 8); + +#define SETZONESFX(i, name, sample) \ + strcpy(ZoneSfx[i].m_aName, name); \ + ZoneSfx[i].m_nSampleIndex = sample; + + SETZONESFX(0, "HOSPI_2", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(1, "CONSTRU", SFX_POLICE_RADIO_FORT_STAUNTON); + SETZONESFX(2, "STADIUM", SFX_POLICE_RADIO_ASPATRIA); + SETZONESFX(3, "YAKUSA", SFX_POLICE_RADIO_TORRINGTON); + SETZONESFX(4, "SHOPING", SFX_POLICE_RADIO_BEDFORD_POINT); + SETZONESFX(5, "COM_EAS", SFX_POLICE_RADIO_NEWPORT); + SETZONESFX(6, "PARK", SFX_POLICE_RADIO_BELLEVILLE_PARK); + SETZONESFX(7, "UNIVERS", SFX_POLICE_RADIO_LIBERTY_CAMPUS); + SETZONESFX(8, "BIG_DAM", SFX_POLICE_RADIO_COCHRANE_DAM); + SETZONESFX(9, "SUB_IND", SFX_POLICE_RADIO_PIKE_CREEK); + SETZONESFX(10, "SWANKS", SFX_POLICE_RADIO_CEDAR_GROVE); + SETZONESFX(11, "PROJECT", SFX_POLICE_RADIO_WICHITA_GARDENS); + SETZONESFX(12, "AIRPORT", SFX_POLICE_RADIO_FRANCIS_INTERNATIONAL_AIRPORT); + SETZONESFX(13, "PORT_W", SFX_POLICE_RADIO_CALLAHAN_POINT); + SETZONESFX(14, "PORT_S", SFX_POLICE_RADIO_ATLANTIC_QUAYS); + SETZONESFX(15, "PORT_E", SFX_POLICE_RADIO_PORTLAND_HARBOUR); + SETZONESFX(16, "PORT_I", SFX_POLICE_RADIO_TRENTON); + SETZONESFX(17, "CHINA", SFX_POLICE_RADIO_CHINATOWN); + SETZONESFX(18, "REDLIGH", SFX_POLICE_RADIO_RED_LIGHT_DISTRICT); + SETZONESFX(19, "TOWERS", SFX_POLICE_RADIO_HEPBURN_HEIGHTS); + SETZONESFX(20, "LITTLEI", SFX_POLICE_RADIO_SAINT_MARKS); + SETZONESFX(21, "HARWOOD", SFX_POLICE_RADIO_HARWOOD); + SETZONESFX(22, "EASTBAY", SFX_POLICE_RADIO_PORTLAND_BEACH); + SETZONESFX(23, "S_VIEW", SFX_POLICE_RADIO_PORTLAND_STRAIGHTS); + SETZONESFX(24, "CITYZON", SFX_POLICE_RADIO_LIBERTY_CITY); + SETZONESFX(25, "IND_ZON", SFX_POLICE_RADIO_PORTLAND); + SETZONESFX(26, "COM_ZON", SFX_POLICE_RADIO_STAUNTON_ISLAND); + SETZONESFX(27, "SUB_ZON", SFX_POLICE_RADIO_SHORESIDE_VALE); + SETZONESFX(28, "SUB_ZO2", SFX_POLICE_RADIO_SHORESIDE_VALE); + SETZONESFX(29, "SUB_ZO3", SFX_POLICE_RADIO_SHORESIDE_VALE); + SETZONESFX(30, "A", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(31, "A", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(32, "A", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(33, "A", SFX_POLICE_RADIO_ROCKFORD); + SETZONESFX(34, "A", SFX_POLICE_RADIO_ROCKFORD); + +#undef SETZONESFX + + strcpy(SubZo2Label, "SUB_ZO2"); + strcpy(SubZo3Label, "SUB_ZO3"); +} + +void +cAudioManager::InitialisePoliceRadio() +{ + m_sPoliceRadioQueue.Reset(); + for (int32 i = 0; i < ARRAY_SIZE(m_aCrimes); i++) + m_aCrimes[i].type = CRIME_NONE; + + SampleManager.SetChannelReverbFlag(CHANNEL_POLICE_RADIO, FALSE); + gSpecialSuspectLastSeenReport = FALSE; + for (int32 i = 0; i < ARRAY_SIZE(gMinTimeToNextReport); i++) + gMinTimeToNextReport[i] = m_FrameCounter; +} + +void +cAudioManager::ResetPoliceRadio() +{ + if (!m_bIsInitialised) return; + if (SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) SampleManager.StopChannel(CHANNEL_POLICE_RADIO); + InitialisePoliceRadio(); +} + +void +cAudioManager::SetMissionScriptPoliceAudio(uint32 sfx) +{ + if (!m_bIsInitialised) return; + if (g_nMissionAudioPlayingStatus != PLAY_STATUS_PLAYING) { + g_nMissionAudioPlayingStatus = PLAY_STATUS_STOPPED; + g_nMissionAudioSfx = sfx; + } +} + +int8 +cAudioManager::GetMissionScriptPoliceAudioPlayingStatus() +{ + return g_nMissionAudioPlayingStatus; +} + +void +cAudioManager::DoPoliceRadioCrackle() +{ + m_sQueueSample.m_nEntityIndex = m_nPoliceChannelEntity; + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_POLICE_RADIO_CRACKLE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nPriority = 10; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_POLICE_RADIO_CRACKLE); + m_sQueueSample.m_nVolume = m_anRandomTable[2] % 20 + 15; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + SET_LOOP_OFFSETS(SFX_POLICE_RADIO_CRACKLE) + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_bReverb = FALSE; + m_sQueueSample.m_nPan = 63; + m_sQueueSample.m_nFramesToPlay = 3; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); +} + +void +cAudioManager::ServicePoliceRadio() +{ + int32 wantedLevel = 0; // uninitialized variable + static uint32 nLastSeen = 300; + + if(!m_bIsInitialised) return; + + if(!m_bIsPaused) { + bool8 crimeReport = SetupCrimeReport(); +#ifdef FIX_BUGS // Crash at 0x5fe6ef + if(CReplay::IsPlayingBack() || !FindPlayerPed() || !FindPlayerPed()->m_pWanted) + return; +#endif + wantedLevel = FindPlayerPed()->m_pWanted->GetWantedLevel(); + if (!crimeReport) { + if (wantedLevel != 0) { + if (nLastSeen != 0) { +#ifdef FIX_BUGS + nLastSeen -= CTimer::GetLogicalFramesPassed(); +#else + nLastSeen--; +#endif + } else { + nLastSeen = m_anRandomTable[1] % 1000 + 2000; + SetupSuspectLastSeenReport(); + } + } + } + } + ServicePoliceRadioChannel(wantedLevel); +} + +void +cAudioManager::ServicePoliceRadioChannel(uint8 wantedLevel) +{ + bool8 processed = FALSE; + uint32 sample; + uint32 freq; + + static int cWait = 0; + static bool8 bChannelOpen = FALSE; + static uint8 bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_STOPPED; + static uint32 PoliceChannelFreq = 5500; + + if (!m_bIsInitialised) return; + + if (m_bIsPaused) { +#ifdef GTA_PS2 + if (SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, 0); +#else + if (SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) SampleManager.StopChannel(CHANNEL_POLICE_RADIO); + if (g_nMissionAudioSfx != TOTAL_AUDIO_SAMPLES && bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_PLAYING && + SampleManager.IsStreamPlaying(1)) + SampleManager.PauseStream(TRUE, 1); +#endif + } else { +#ifdef GTA_PS2 + if (m_bWasPaused) + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, PoliceChannelFreq); +#else + if (m_bWasPaused && g_nMissionAudioSfx != TOTAL_AUDIO_SAMPLES && + bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_PLAYING) + SampleManager.PauseStream(FALSE, 1); +#endif + if (m_sPoliceRadioQueue.m_nSamplesInQueue == 0) bChannelOpen = FALSE; + if (cWait) { +#ifdef FIX_BUGS + cWait -= CTimer::GetLogicalFramesPassed(); +#else + --cWait; +#endif + return; + } + if (g_nMissionAudioSfx != TOTAL_AUDIO_SAMPLES && !bChannelOpen) { + if (g_nMissionAudioPlayingStatus != PLAY_STATUS_STOPPED) { + if (g_nMissionAudioPlayingStatus == PLAY_STATUS_PLAYING && bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_STOPPED && +#ifdef GTA_PS2 + SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) { +#else + SampleManager.IsStreamPlaying(1)) { +#endif + bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_PLAYING; + } + if (bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_PLAYING) { +#ifdef GTA_PS2 + if (SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) { +#else + if (SampleManager.IsStreamPlaying(1)) { +#endif + DoPoliceRadioCrackle(); + } else { + bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_FINISHED; + g_nMissionAudioPlayingStatus = PLAY_STATUS_FINISHED; + g_nMissionAudioSfx = TOTAL_AUDIO_SAMPLES; + cWait = 30; + } + return; + } + } else if (!SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) { +#ifdef GTA_PS2 + SampleManager.InitialiseChannel(CHANNEL_POLICE_RADIO, g_nMissionAudioSfx, SFX_BANK_PED_COMMENTS); + PoliceChannelFreq = SampleManager.GetSampleBaseFrequency(g_nMissionAudioSfx); + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, PoliceChannelFreq); + SampleManager.SetChannelVolume(CHANNEL_POLICE_RADIO, MAX_VOLUME); + SampleManager.SetChannelPan(CHANNEL_POLICE_RADIO, 63); + SampleManager.StartChannel(CHANNEL_POLICE_RADIO); +#else + SampleManager.PreloadStreamedFile(g_nMissionAudioSfx, 1); + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, TRUE, 1); + SampleManager.StartPreloadedStreamedFile(1); +#endif + g_nMissionAudioPlayingStatus = PLAY_STATUS_PLAYING; + bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_STOPPED; + return; + } + } + if (bChannelOpen) DoPoliceRadioCrackle(); + if ((g_nMissionAudioSfx == TOTAL_AUDIO_SAMPLES || g_nMissionAudioPlayingStatus != PLAY_STATUS_PLAYING) && + !SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO) && m_sPoliceRadioQueue.m_nSamplesInQueue != 0) { + sample = m_sPoliceRadioQueue.Remove(); + if (wantedLevel == 0) { + if (gSpecialSuspectLastSeenReport) { + gSpecialSuspectLastSeenReport = FALSE; + } else if (((sample >= SFX_POLICE_RADIO_MESSAGE_NOISE_1) && (sample <= SFX_POLICE_RADIO_MESSAGE_NOISE_3)) || sample == TOTAL_AUDIO_SAMPLES) { + bChannelOpen = FALSE; + processed = TRUE; + } + } + if (sample == TOTAL_AUDIO_SAMPLES) { + if (!processed) cWait = 30; + } else { + SampleManager.InitialiseChannel(CHANNEL_POLICE_RADIO, sample, SFX_BANK_0); + switch (sample) { + case SFX_POLICE_RADIO_MESSAGE_NOISE_1: + case SFX_POLICE_RADIO_MESSAGE_NOISE_2: + case SFX_POLICE_RADIO_MESSAGE_NOISE_3: + freq = m_anRandomTable[4] % 2000 + 10025; + bChannelOpen = bChannelOpen == FALSE; + break; + default: freq = SampleManager.GetSampleBaseFrequency(sample); break; + } + PoliceChannelFreq = freq; +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, freq * CTimer::GetTimeScale()); +#else + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, freq); +#endif + SampleManager.SetChannelVolume(CHANNEL_POLICE_RADIO, 100); + SampleManager.SetChannelPan(CHANNEL_POLICE_RADIO, 63); + SampleManager.SetChannelLoopCount(CHANNEL_POLICE_RADIO, 1); +#ifndef GTA_PS2 + SampleManager.SetChannelLoopPoints(CHANNEL_POLICE_RADIO, 0, -1); +#endif + SampleManager.StartChannel(CHANNEL_POLICE_RADIO); + } + if (processed) ResetPoliceRadio(); + } + } +} + +bool8 +cAudioManager::SetupCrimeReport() +{ + int16 audioZoneId; + CZone *zone; + float rangeX; + float rangeY; + float halfX; + float halfY; + float quarterX; + float quarterY; + int i; + uint32 sampleIndex; + bool8 processed = FALSE; + + if (MusicManager.m_nMusicMode == MUSICMODE_CUTSCENE) return FALSE; + + if (POLICE_RADIO_QUEUE_MAX_SAMPLES - m_sPoliceRadioQueue.m_nSamplesInQueue <= 9) { + AgeCrimes(); + return TRUE; + } + + for (i = 0; i < ARRAY_SIZE(m_aCrimes); i++) { + if (m_aCrimes[i].type != CRIME_NONE) + break; + } + + if (i == ARRAY_SIZE(m_aCrimes)) return FALSE; + audioZoneId = CTheZones::FindAudioZone(&m_aCrimes[i].position); + if (audioZoneId >= 0 && audioZoneId < NUMAUDIOZONES) { + zone = CTheZones::GetAudioZone(audioZoneId); + for (int j = 0; j < NUMAUDIOZONES; j++) { + if (strcmp(zone->name, ZoneSfx[j].m_aName) == 0) { + sampleIndex = ZoneSfx[j].m_nSampleIndex; + m_sPoliceRadioQueue.Add(m_anRandomTable[4] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(m_anRandomTable[0] % 3 + SFX_WEVE_GOT); + m_sPoliceRadioQueue.Add(m_anRandomTable[1] % 2 + SFX_A_10_1); + switch (m_aCrimes[i].type) { + case CRIME_PED_BURNED: m_aCrimes[i].type = CRIME_HIT_PED; break; + case CRIME_COP_BURNED: m_aCrimes[i].type = CRIME_HIT_COP; break; + case CRIME_VEHICLE_BURNED: m_aCrimes[i].type = CRIME_STEAL_CAR; break; + case CRIME_DESTROYED_CESSNA: m_aCrimes[i].type = CRIME_SHOOT_HELI; break; + default: break; + } + m_sPoliceRadioQueue.Add(m_aCrimes[i].type + SFX_CRIME_1 - 1); + m_sPoliceRadioQueue.Add(SFX_IN); + if (sampleIndex == SFX_POLICE_RADIO_SHORESIDE_VALE && + (strcmp(zone->name, SubZo2Label) == 0 || strcmp(zone->name, SubZo3Label) == 0)) { + m_sPoliceRadioQueue.Add(SFX_NORTH); + m_sPoliceRadioQueue.Add(SFX_EAST); + } else { + rangeX = zone->maxx - zone->minx; + rangeY = zone->maxy - zone->miny; + halfX = 0.5f * rangeX + zone->minx; + halfY = 0.5f * rangeY + zone->miny; + quarterX = 0.25f * rangeX; + quarterY = 0.25f * rangeY; + + if (m_aCrimes[i].position.y > halfY + quarterY) { + m_sPoliceRadioQueue.Add(SFX_NORTH); + processed = TRUE; + } else if (m_aCrimes[i].position.y < halfY - quarterY) { + m_sPoliceRadioQueue.Add(SFX_SOUTH); + processed = TRUE; + } + + if (m_aCrimes[i].position.x > halfX + quarterX) + m_sPoliceRadioQueue.Add(SFX_EAST); + else if (m_aCrimes[i].position.x < halfX - quarterX) + m_sPoliceRadioQueue.Add(SFX_WEST); + else if (!processed) + m_sPoliceRadioQueue.Add(SFX_CENTRAL); + + m_sPoliceRadioQueue.Add(sampleIndex); + m_sPoliceRadioQueue.Add(m_anRandomTable[2] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(TOTAL_AUDIO_SAMPLES); + } + break; + } + } + } + m_aCrimes[i].type = CRIME_NONE; + AgeCrimes(); + return TRUE; +} + +Const uint32 gCarColourTable[][3] = { + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLACK, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_WHITE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_BRIGHT, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_BLUE, SFX_POLICE_RADIO_GREY}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_RED, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_ORANGE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_YELLOW, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_GREEN, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_BLUE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_PURPLE, SFX_POLICE_RADIO_BLUE}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, SFX_POLICE_RADIO_GREY}, +#else + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_PURPLE, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, +#else + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, +#endif + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, +#ifdef FIX_BUGS + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, +#else + {TOTAL_AUDIO_SAMPLES, SFX_POLICE_RADIO_SILVER, TOTAL_AUDIO_SAMPLES}, +#endif + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_LIGHT, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES}, + {SFX_POLICE_RADIO_DARK, TOTAL_AUDIO_SAMPLES, TOTAL_AUDIO_SAMPLES} +}; + +void +cAudioManager::SetupSuspectLastSeenReport() +{ + CVehicle *veh; + uint8 color1; + uint32 main_color; + uint32 sample; + + uint32 color_pre_modifier; + uint32 color_post_modifier; + + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) { + veh = FindPlayerVehicle(); + if (veh != nil) { + if (POLICE_RADIO_QUEUE_MAX_SAMPLES - m_sPoliceRadioQueue.m_nSamplesInQueue > 9) { + color1 = veh->m_currentColour1; + if (color1 >= ARRAY_SIZE(gCarColourTable)) { + debug("\n *** UNKNOWN CAR COLOUR %d *** ", color1); + } else { + main_color = gCarColourTable[color1][1]; + color_pre_modifier = gCarColourTable[color1][0]; + color_post_modifier = gCarColourTable[color1][2]; + switch (veh->GetModelIndex()) { +#ifdef FIX_BUGS + case MI_COLUMB: + main_color = SFX_POLICE_RADIO_BLUE; + color_pre_modifier = color_post_modifier = TOTAL_AUDIO_SAMPLES; +#endif + case MI_LANDSTAL: + case MI_BLISTA: sample = SFX_POLICE_RADIO_CRUISER; break; +#ifdef FIX_BUGS + case MI_YARDIE: + color_pre_modifier = TOTAL_AUDIO_SAMPLES; + main_color = SFX_POLICE_RADIO_RED; + color_post_modifier = SFX_POLICE_RADIO_YELLOW; + sample = SFX_POLICE_RADIO_CONVERTIBLE; break; + case MI_DIABLOS: + main_color = SFX_POLICE_RADIO_BLACK; +#endif + case MI_IDAHO: + case MI_STALLION: sample = SFX_POLICE_RADIO_CONVERTIBLE; break; +#ifdef FIX_BUGS + case MI_YAKUZA: + color_pre_modifier = TOTAL_AUDIO_SAMPLES; + main_color = SFX_POLICE_RADIO_SILVER; + color_post_modifier = SFX_POLICE_RADIO_RED; +#endif + case MI_STINGER: + case MI_INFERNUS: + case MI_CHEETAH: + case MI_BANSHEE: sample = SFX_POLICE_RADIO_SPORTS_CAR; break; +#ifdef FIX_BUGS + case MI_MAFIA: + color_pre_modifier = color_post_modifier = TOTAL_AUDIO_SAMPLES; + main_color = SFX_POLICE_RADIO_GREY; + case MI_KURUMA: +#endif + case MI_PEREN: + case MI_SENTINEL: + case MI_FBICAR: sample = SFX_POLICE_RADIO_SALOON; break; + case MI_PATRIOT: + case MI_BOBCAT: sample = SFX_POLICE_RADIO_PICKUP; break; + case MI_FIRETRUCK: sample = SFX_POLICE_RADIO_FIRE_TRUCK; break; +#ifdef FIX_BUGS + case MI_LINERUN: + case MI_FLATBED: +#endif + case MI_TRASH: + case MI_BARRACKS: sample = SFX_POLICE_RADIO_TRUCK; break; + case MI_STRETCH: sample = SFX_POLICE_RADIO_LIMO; break; +#ifdef FIX_BUGS + case MI_CORPSE: +#endif + case MI_MANANA: + case MI_ESPERANT: sample = SFX_POLICE_RADIO_2_DOOR; break; +#ifdef FIX_BUGS + case MI_HOODS: + color_pre_modifier = TOTAL_AUDIO_SAMPLES; + main_color = SFX_POLICE_RADIO_BLUE; + color_post_modifier = SFX_POLICE_RADIO_GREEN; + case MI_BELLYUP: + case MI_YANKEE: + case MI_TOYZ: + case MI_MRWONGS: + case MI_PANLANT: +#endif + case MI_PONY: + case MI_MULE: + case MI_MOONBEAM: + case MI_ENFORCER: + case MI_SECURICA: + case MI_RUMPO: sample = SFX_POLICE_RADIO_VAN; break; + case MI_AMBULAN: sample = SFX_POLICE_RADIO_AMBULANCE; break; + case MI_TAXI: + case MI_CABBIE: + case MI_BORGNINE: sample = SFX_POLICE_RADIO_TAXI; break; + case MI_MRWHOOP: + sample = SFX_POLICE_RADIO_ICE_CREAM_VAN; + break; + case MI_BFINJECT: sample = SFX_POLICE_RADIO_BUGGY; break; + case MI_POLICE: sample = SFX_POLICE_RADIO_POLICE_CAR; break; +#ifdef FIX_BUGS + case MI_SPEEDER: + case MI_REEFER: + case MI_GHOST: +#endif + case MI_PREDATOR: sample = SFX_POLICE_RADIO_BOAT; break; + case MI_BUS: + case MI_COACH: sample = SFX_POLICE_RADIO_BUS; break; + case MI_RHINO: + sample = SFX_POLICE_RADIO_TANK; + main_color = TOTAL_AUDIO_SAMPLES; + color_post_modifier = TOTAL_AUDIO_SAMPLES; + break; + case MI_TRAIN: + sample = SFX_POLICE_RADIO_SUBWAY_CAR; + main_color = TOTAL_AUDIO_SAMPLES; + color_post_modifier = TOTAL_AUDIO_SAMPLES; + + break; + default: + debug("\n *** UNKNOWN CAR MODEL INDEX %d *** ", veh->GetModelIndex()); + return; + } + m_sPoliceRadioQueue.Add(m_anRandomTable[4] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_SUSPECT); + if (m_anRandomTable[3] % 2) + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_LAST_SEEN); +#ifdef FIX_BUGS + if (main_color == SFX_POLICE_RADIO_ORANGE && color_pre_modifier == TOTAL_AUDIO_SAMPLES) +#else + if (main_color == SFX_POLICE_RADIO_ORANGE) +#endif + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_IN_AN); + else + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_IN_A); + if (color_pre_modifier != TOTAL_AUDIO_SAMPLES) + m_sPoliceRadioQueue.Add(color_pre_modifier); + if (main_color != TOTAL_AUDIO_SAMPLES) + m_sPoliceRadioQueue.Add(main_color); + if (color_post_modifier != TOTAL_AUDIO_SAMPLES) + m_sPoliceRadioQueue.Add(color_post_modifier); + m_sPoliceRadioQueue.Add(sample); + m_sPoliceRadioQueue.Add(m_anRandomTable[0] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(TOTAL_AUDIO_SAMPLES); + } + } + } else if (POLICE_RADIO_QUEUE_MAX_SAMPLES - m_sPoliceRadioQueue.m_nSamplesInQueue > 4) { + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_SUSPECT); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_ON_FOOT); + m_sPoliceRadioQueue.Add(m_anRandomTable[0] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(TOTAL_AUDIO_SAMPLES); + } + } +} + +void +cAudioManager::ReportCrime(eCrimeType type, const CVector &pos) +{ + int32 lastCrime = ARRAY_SIZE(m_aCrimes); + if (m_bIsInitialised && MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE && FindPlayerPed()->m_pWanted->GetWantedLevel() > 0 && + (type > CRIME_NONE || type < NUM_CRIME_TYPES) && m_FrameCounter >= gMinTimeToNextReport[type]) { + for (int32 i = 0; i < ARRAY_SIZE(m_aCrimes); i++) { + if (m_aCrimes[i].type != CRIME_NONE) { + if (m_aCrimes[i].type == type) { + m_aCrimes[i].position = pos; + m_aCrimes[i].timer = 0; + return; + } + } else { + lastCrime = i; + } + } + + if (lastCrime < ARRAY_SIZE(m_aCrimes)) { + m_aCrimes[lastCrime].type = type; + m_aCrimes[lastCrime].position = pos; + m_aCrimes[lastCrime].timer = 0; + gMinTimeToNextReport[type] = m_FrameCounter + 500; + } + } +} + +void +cAudioManager::PlaySuspectLastSeen(float x, float y, float z) +{ + int16 audioZone; + CZone *zone; + float rangeX; + float rangeY; + float halfX; + float halfY; + float quarterX; + float quarterY; + uint32 sample; + bool8 processed = FALSE; + CVector vec = CVector(x, y, z); + + if (!m_bIsInitialised) return; + + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE && POLICE_RADIO_QUEUE_MAX_SAMPLES - m_sPoliceRadioQueue.m_nSamplesInQueue > 9) { + audioZone = CTheZones::FindAudioZone(&vec); + if (audioZone >= 0 && audioZone < NUMAUDIOZONES) { + zone = CTheZones::GetAudioZone(audioZone); + for (int i = 0; i < NUMAUDIOZONES; i++) { + if (strcmp(zone->name, ZoneSfx[i].m_aName) == 0) { + sample = ZoneSfx[i].m_nSampleIndex; + m_sPoliceRadioQueue.Add(m_anRandomTable[4] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_SUSPECT); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_LAST_SEEN); + m_sPoliceRadioQueue.Add(SFX_IN); + if (sample == SFX_POLICE_RADIO_SHORESIDE_VALE && + (strcmp(zone->name, SubZo2Label) == 0 || + strcmp(zone->name, SubZo3Label) == 0)) { + m_sPoliceRadioQueue.Add(SFX_NORTH); + m_sPoliceRadioQueue.Add(SFX_EAST); + } else { + rangeX = zone->maxx - zone->minx; + rangeY = zone->maxy - zone->miny; + halfX = 0.5f * rangeX + zone->minx; + halfY = 0.5f * rangeY + zone->miny; + quarterX = 0.25f * rangeX; + quarterY = 0.25f * rangeY; + + if (vec.y > halfY + quarterY) { + m_sPoliceRadioQueue.Add(SFX_NORTH); + processed = TRUE; + } else if (vec.y < halfY - quarterY) { + m_sPoliceRadioQueue.Add(SFX_SOUTH); + processed = TRUE; + } + + if (vec.x > halfX + quarterX) + m_sPoliceRadioQueue.Add(SFX_EAST); + else if (vec.x < halfX - quarterX) + m_sPoliceRadioQueue.Add(SFX_WEST); + else if (!processed) + m_sPoliceRadioQueue.Add(SFX_CENTRAL); + } + m_sPoliceRadioQueue.Add(sample); + m_sPoliceRadioQueue.Add(m_anRandomTable[2] % 3 + SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(TOTAL_AUDIO_SAMPLES); + gSpecialSuspectLastSeenReport = TRUE; + break; + } + } + } + } +} + +void +cAudioManager::AgeCrimes() +{ + for (uint8 i = 0; i < ARRAY_SIZE(m_aCrimes); i++) { + if (m_aCrimes[i].type != CRIME_NONE) { + if (++m_aCrimes[i].timer > 1500) m_aCrimes[i].type = CRIME_NONE; + } + } +} diff --git a/src/audio/PolRadio.h b/src/audio/PolRadio.h new file mode 100644 index 0000000..c7b0bcc --- /dev/null +++ b/src/audio/PolRadio.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Crime.h" +#include "AudioSamples.h" + +struct cAMCrime { + int32 type; + CVector position; + uint16 timer; + + cAMCrime() + { + type = CRIME_NONE; + position = CVector(0.0f, 0.0f, 0.0f); + timer = 0; + } +}; + +VALIDATE_SIZE(cAMCrime, 20); + +#define POLICE_RADIO_QUEUE_MAX_SAMPLES 60 + +class cPoliceRadioQueue +{ +public: + uint32 m_aSamples[POLICE_RADIO_QUEUE_MAX_SAMPLES]; + uint8 m_nSamplesInQueue; + uint8 m_nAddOffset; + uint8 m_nRemoveOffset; + + cPoliceRadioQueue() + { + Reset(); + } + + void Reset() + { + m_nAddOffset = 0; + m_nRemoveOffset = 0; + m_nSamplesInQueue = 0; + } + + bool8 Add(uint32 sample) + { + if (m_nSamplesInQueue != POLICE_RADIO_QUEUE_MAX_SAMPLES) { + m_aSamples[m_nAddOffset] = sample; + m_nSamplesInQueue++; + m_nAddOffset = (m_nAddOffset + 1) % POLICE_RADIO_QUEUE_MAX_SAMPLES; + return TRUE; + } + return FALSE; + } + + uint32 Remove() + { + if (m_nSamplesInQueue != 0) { + uint32 sample = m_aSamples[m_nRemoveOffset]; + m_nSamplesInQueue--; + m_nRemoveOffset = (m_nRemoveOffset + 1) % POLICE_RADIO_QUEUE_MAX_SAMPLES; + return sample; + } + return TOTAL_AUDIO_SAMPLES; + } +}; + +VALIDATE_SIZE(cPoliceRadioQueue, 244); diff --git a/src/audio/audio_enums.h b/src/audio/audio_enums.h new file mode 100644 index 0000000..b8fc611 --- /dev/null +++ b/src/audio/audio_enums.h @@ -0,0 +1,280 @@ +#pragma once + +enum eRadioStation +{ + HEAD_RADIO, + DOUBLE_CLEF, + JAH_RADIO, + RISE_FM, + LIPS_106, + GAME_FM, + MSX_FM, + FLASHBACK, + CHATTERBOX, + USERTRACK, + POLICE_RADIO = 10, + NUM_RADIOS = 10, + RADIO_OFF = 11, +}; + +enum eMusicMode +{ + MUSICMODE_FRONTEND = 0, + MUSICMODE_GAME, + MUSICMODE_CUTSCENE, + MUSICMODE_DISABLE, + MUSICMODE_DISABLED, +}; + +enum eStreamedSounds +{ + STREAMED_SOUND_RADIO_HEAD, + STREAMED_SOUND_RADIO_CLASSIC, + STREAMED_SOUND_RADIO_KJAH, + STREAMED_SOUND_RADIO_RISE, + STREAMED_SOUND_RADIO_LIPS, + STREAMED_SOUND_RADIO_GAME, + STREAMED_SOUND_RADIO_MSX, + STREAMED_SOUND_RADIO_FLASH, + STREAMED_SOUND_RADIO_CHAT, + STREAMED_SOUND_RADIO_MP3_PLAYER, + STREAMED_SOUND_RADIO_POLICE, + STREAMED_SOUND_CITY_AMBIENT, + STREAMED_SOUND_WATER_AMBIENT, + STREAMED_SOUND_ANNOUNCE_COMMERCIAL_OPEN, + STREAMED_SOUND_ANNOUNCE_SUBURBAN_OPEN, + STREAMED_SOUND_NEWS_INTRO, + STREAMED_SOUND_BANK_INTRO, + STREAMED_SOUND_CUTSCENE_LUIGI1_LG, + STREAMED_SOUND_CUTSCENE_LUIGI2_DSB, + STREAMED_SOUND_CUTSCENE_LUIGI3_DM, + STREAMED_SOUND_CUTSCENE_LUIGI4_PAP, + STREAMED_SOUND_CUTSCENE_LUIGI5_TFB, + STREAMED_SOUND_CUTSCENE_JOEY0_DM2, + STREAMED_SOUND_CUTSCENE_JOEY1_LFL, + STREAMED_SOUND_CUTSCENE_JOEY2_KCL, + STREAMED_SOUND_CUTSCENE_JOEY3_VH, + STREAMED_SOUND_CUTSCENE_JOEY4_ETH, + STREAMED_SOUND_CUTSCENE_JOEY5_DST, + STREAMED_SOUND_CUTSCENE_JOEY6_TBJ, + STREAMED_SOUND_CUTSCENE_TONI1_TOL, + STREAMED_SOUND_CUTSCENE_TONI2_TPU, + STREAMED_SOUND_CUTSCENE_TONI3_MAS, + STREAMED_SOUND_CUTSCENE_TONI4_TAT, + STREAMED_SOUND_CUTSCENE_TONI5_BF, + STREAMED_SOUND_CUTSCENE_SAL0_MAS, + STREAMED_SOUND_CUTSCENE_SAL1_PF, + STREAMED_SOUND_CUTSCENE_SAL2_CTG, + STREAMED_SOUND_CUTSCENE_SAL3_RTC, + STREAMED_SOUND_CUTSCENE_SAL5_LRQ, + STREAMED_SOUND_CUTSCENE_SAL4_BDBA, + STREAMED_SOUND_CUTSCENE_SAL4_BDBB, + STREAMED_SOUND_CUTSCENE_SAL2_CTG2, + STREAMED_SOUND_CUTSCENE_SAL4_BDBD, + STREAMED_SOUND_CUTSCENE_SAL5_LRQB, + STREAMED_SOUND_CUTSCENE_SAL5_LRQC, + STREAMED_SOUND_CUTSCENE_ASUKA_1_SSO, + STREAMED_SOUND_CUTSCENE_ASUKA_2_PP, + STREAMED_SOUND_CUTSCENE_ASUKA_3_SS, + STREAMED_SOUND_CUTSCENE_ASUKA_4_PDR, + STREAMED_SOUND_CUTSCENE_ASUKA_5_K2FT, + STREAMED_SOUND_CUTSCENE_KENJI1_KBO, + STREAMED_SOUND_CUTSCENE_KENJI2_GIS, + STREAMED_SOUND_CUTSCENE_KENJI3_DS, + STREAMED_SOUND_CUTSCENE_KENJI4_SHI, + STREAMED_SOUND_CUTSCENE_KENJI5_SD, + STREAMED_SOUND_CUTSCENE_RAY0_PDR2, + STREAMED_SOUND_CUTSCENE_RAY1_SW, + STREAMED_SOUND_CUTSCENE_RAY2_AP, + STREAMED_SOUND_CUTSCENE_RAY3_ED, + STREAMED_SOUND_CUTSCENE_RAY4_GF, + STREAMED_SOUND_CUTSCENE_RAY5_PB, + STREAMED_SOUND_CUTSCENE_RAY6_MM, + STREAMED_SOUND_CUTSCENE_DONALD1_STOG, + STREAMED_SOUND_CUTSCENE_DONALD2_KK, + STREAMED_SOUND_CUTSCENE_DONALD3_ADO, + STREAMED_SOUND_CUTSCENE_DONALD5_ES, + STREAMED_SOUND_CUTSCENE_DONALD7_MLD, + STREAMED_SOUND_CUTSCENE_DONALD4_GTA, + STREAMED_SOUND_CUTSCENE_DONALD4_GTA2, + STREAMED_SOUND_CUTSCENE_DONALD6_STS, + STREAMED_SOUND_CUTSCENE_ASUKA6_BAIT, + STREAMED_SOUND_CUTSCENE_ASUKA7_ETG, + STREAMED_SOUND_CUTSCENE_ASUKA8_PS, + STREAMED_SOUND_CUTSCENE_ASUKA9_ASD, + STREAMED_SOUND_CUTSCENE_KENJI4_SHI2, + STREAMED_SOUND_CUTSCENE_CATALINA1_TEX, + STREAMED_SOUND_CUTSCENE_ELBURRO1_PH1, + STREAMED_SOUND_CUTSCENE_ELBURRO2_PH2, + STREAMED_SOUND_CUTSCENE_ELBURRO3_PH3, + STREAMED_SOUND_CUTSCENE_ELBURRO4_PH4, + STREAMED_SOUND_CUTSCENE_YARDIE_PH1, + STREAMED_SOUND_CUTSCENE_YARDIE_PH2, + STREAMED_SOUND_CUTSCENE_YARDIE_PH3, + STREAMED_SOUND_CUTSCENE_YARDIE_PH4, + STREAMED_SOUND_CUTSCENE_HOODS_PH1, + STREAMED_SOUND_CUTSCENE_HOODS_PH2, + STREAMED_SOUND_CUTSCENE_HOODS_PH3, + STREAMED_SOUND_CUTSCENE_HOODS_PH4, + STREAMED_SOUND_CUTSCENE_HOODS_PH5, + STREAMED_SOUND_CUTSCENE_MARTY_PH1, + STREAMED_SOUND_CUTSCENE_MARTY_PH2, + STREAMED_SOUND_CUTSCENE_MARTY_PH3, + STREAMED_SOUND_CUTSCENE_MARTY_PH4, + STREAMED_SOUND_MISSION_COMPLETED, + STREAMED_SOUND_GAME_COMPLETED, +#ifndef GTA_PS2 + SFX_MISSION_LIB_A1, + SFX_MISSION_LIB_A2, + SFX_MISSION_LIB_A, + SFX_MISSION_LIB_B, + SFX_MISSION_LIB_C, + SFX_MISSION_LIB_D, + SFX_MISSION_L2_A, + SFX_MISSION_J4T_1, + SFX_MISSION_J4T_2, + SFX_MISSION_J4T_3, + SFX_MISSION_J4T_4, + SFX_MISSION_J4_A, + SFX_MISSION_J4_B, + SFX_MISSION_J4_C, + SFX_MISSION_J4_D, + SFX_MISSION_J4_E, + SFX_MISSION_J4_F, + SFX_MISSION_J6_1, + SFX_MISSION_J6_A, + SFX_MISSION_J6_B, + SFX_MISSION_J6_C, + SFX_MISSION_J6_D, + SFX_MISSION_T4_A, + SFX_MISSION_S1_A, + SFX_MISSION_S1_A1, + SFX_MISSION_S1_B, + SFX_MISSION_S1_C, + SFX_MISSION_S1_C1, + SFX_MISSION_S1_D, + SFX_MISSION_S1_E, + SFX_MISSION_S1_F, + SFX_MISSION_S1_G, + SFX_MISSION_S1_H, + SFX_MISSION_S1_I, + SFX_MISSION_S1_J, + SFX_MISSION_S1_K, + SFX_MISSION_S1_L, + SFX_MISSION_S3_A, + SFX_MISSION_S3_B, + SFX_MISSION_EL3_A, + SFX_MISSION_MF1_A, + SFX_MISSION_MF2_A, + SFX_MISSION_MF3_A, + SFX_MISSION_MF3_B, + SFX_MISSION_MF3_B1, + SFX_MISSION_MF3_C, + SFX_MISSION_MF4_A, + SFX_MISSION_MF4_B, + SFX_MISSION_MF4_C, + SFX_MISSION_A1_A, + SFX_MISSION_A3_A, + SFX_MISSION_A5_A, + SFX_MISSION_A4_A, + SFX_MISSION_A4_B, + SFX_MISSION_A4_C, + SFX_MISSION_A4_D, + SFX_MISSION_K1_A, + SFX_MISSION_K3_A, + SFX_MISSION_R1_A, + SFX_MISSION_R2_A, + SFX_MISSION_R2_B, + SFX_MISSION_R2_C, + SFX_MISSION_R2_D, + SFX_MISSION_R2_E, + SFX_MISSION_R2_F, + SFX_MISSION_R2_G, + SFX_MISSION_R2_H, + SFX_MISSION_R5_A, + SFX_MISSION_R6_A, + SFX_MISSION_R6_A1, + SFX_MISSION_R6_B, + SFX_MISSION_LO2_A, + SFX_MISSION_LO6_A, + SFX_MISSION_YD2_A, + SFX_MISSION_YD2_B, + SFX_MISSION_YD2_C, + SFX_MISSION_YD2_C1, + SFX_MISSION_YD2_D, + SFX_MISSION_YD2_E, + SFX_MISSION_YD2_F, + SFX_MISSION_YD2_G, + SFX_MISSION_YD2_H, + SFX_MISSION_YD2_ASS, + SFX_MISSION_YD2_OK, + SFX_MISSION_H5_A, + SFX_MISSION_H5_B, + SFX_MISSION_H5_C, + SFX_MISSION_AMMU_A, + SFX_MISSION_AMMU_B, + SFX_MISSION_AMMU_C, + SFX_MISSION_DOOR_1, + SFX_MISSION_DOOR_2, + SFX_MISSION_DOOR_3, + SFX_MISSION_DOOR_4, + SFX_MISSION_DOOR_5, + SFX_MISSION_DOOR_6, + SFX_MISSION_T3_A, + SFX_MISSION_T3_B, + SFX_MISSION_T3_C, + SFX_MISSION_K1_B, + SFX_MISSION_CAT1, +#endif + TOTAL_STREAMED_SOUNDS, + NO_TRACK, +}; + +enum AudioEntityHandle { + AEHANDLE_NONE = -5, + AEHANDLE_ERROR_NOAUDIOSYS = -4, + AEHANDLE_ERROR_NOFREESLOT = -3, + AEHANDLE_ERROR_NOENTITY = -2, + AEHANDLE_ERROR_BADAUDIOTYPE = -1, +}; + +enum eAudioType +{ + AUDIOTYPE_PHYSICAL = 0, + AUDIOTYPE_EXPLOSION, + AUDIOTYPE_FIRE, + AUDIOTYPE_WEATHER, + AUDIOTYPE_CRANE, + AUDIOTYPE_SCRIPTOBJECT, + AUDIOTYPE_BRIDGE, + AUDIOTYPE_COLLISION, + AUDIOTYPE_FRONTEND, + AUDIOTYPE_PROJECTILE, + AUDIOTYPE_GARAGE, + AUDIOTYPE_FIREHYDRANT, + AUDIOTYPE_WATERCANNON, + AUDIOTYPE_POLICERADIO, + TOTAL_AUDIO_TYPES, +}; + +#ifdef GTA_PS2 +enum +{ + NUM_CHANNELS_GENERIC = 43, + CHANNEL_POLICE_RADIO = NUM_CHANNELS_GENERIC, + CHANNEL_MISSION_AUDIO, + CHANNEL_PLAYER_VEHICLE_ENGINE, + NUM_CHANNELS +}; +#else +enum +{ +#ifdef PS2_AUDIO_CHANNELS + NUM_CHANNELS_GENERIC = 43, +#else + NUM_CHANNELS_GENERIC = 27, +#endif + CHANNEL_POLICE_RADIO, + NUM_CHANNELS +}; +#endif diff --git a/src/audio/eax/eax-util.cpp b/src/audio/eax/eax-util.cpp new file mode 100644 index 0000000..42eef73 --- /dev/null +++ b/src/audio/eax/eax-util.cpp @@ -0,0 +1,706 @@ +/***********************************************************************************************\ +* * +* EAX-UTIL.CPP - utilities for EAX 3.0 * +* Function declaration for EAX Morphing * +* String names of the all the presets defined in eax-util.h * +* Arrays grouping together all the EAX presets in a scenario * +* * +************************************************************************************************/ + +#include "eax-util.h" +#include + +// Function prototypes used by EAX3ListenerInterpolate +void Clamp(EAXVECTOR *eaxVector); +bool CheckEAX3LP(LPEAXLISTENERPROPERTIES lpEAX3LP); + + +/***********************************************************************************************\ +* +* Definition of the EAXMorph function - EAX3ListenerInterpolate +* +\***********************************************************************************************/ + +/* + EAX3ListenerInterpolate + lpStart - Initial EAX 3 Listener parameters + lpFinish - Final EAX 3 Listener parameters + flRatio - Ratio Destination : Source (0.0 == Source, 1.0 == Destination) + lpResult - Interpolated EAX 3 Listener parameters + bCheckValues - Check EAX 3.0 parameters are in range, default = false (no checking) +*/ +bool EAX3ListenerInterpolate(LPEAXLISTENERPROPERTIES lpStart, LPEAXLISTENERPROPERTIES lpFinish, + float flRatio, LPEAXLISTENERPROPERTIES lpResult, bool bCheckValues) +{ + EAXVECTOR StartVector, FinalVector; + + float flInvRatio; + + if (bCheckValues) + { + if (!CheckEAX3LP(lpStart)) + return false; + + if (!CheckEAX3LP(lpFinish)) + return false; + } + + if (flRatio >= 1.0f) + { + memcpy(lpResult, lpFinish, sizeof(EAXLISTENERPROPERTIES)); + return true; + } + else if (flRatio <= 0.0f) + { + memcpy(lpResult, lpStart, sizeof(EAXLISTENERPROPERTIES)); + return true; + } + + flInvRatio = (1.0f - flRatio); + + // Environment + lpResult->ulEnvironment = 26; // (UNDEFINED environment) + + // Environment Size + if (lpStart->flEnvironmentSize == lpFinish->flEnvironmentSize) + lpResult->flEnvironmentSize = lpStart->flEnvironmentSize; + else + lpResult->flEnvironmentSize = (float)exp( (log(lpStart->flEnvironmentSize) * flInvRatio) + (log(lpFinish->flEnvironmentSize) * flRatio) ); + + // Environment Diffusion + if (lpStart->flEnvironmentDiffusion == lpFinish->flEnvironmentDiffusion) + lpResult->flEnvironmentDiffusion = lpStart->flEnvironmentDiffusion; + else + lpResult->flEnvironmentDiffusion = (lpStart->flEnvironmentDiffusion * flInvRatio) + (lpFinish->flEnvironmentDiffusion * flRatio); + + // Room + if (lpStart->lRoom == lpFinish->lRoom) + lpResult->lRoom = lpStart->lRoom; + else + lpResult->lRoom = (int)( ((float)lpStart->lRoom * flInvRatio) + ((float)lpFinish->lRoom * flRatio) ); + + // Room HF + if (lpStart->lRoomHF == lpFinish->lRoomHF) + lpResult->lRoomHF = lpStart->lRoomHF; + else + lpResult->lRoomHF = (int)( ((float)lpStart->lRoomHF * flInvRatio) + ((float)lpFinish->lRoomHF * flRatio) ); + + // Room LF + if (lpStart->lRoomLF == lpFinish->lRoomLF) + lpResult->lRoomLF = lpStart->lRoomLF; + else + lpResult->lRoomLF = (int)( ((float)lpStart->lRoomLF * flInvRatio) + ((float)lpFinish->lRoomLF * flRatio) ); + + // Decay Time + if (lpStart->flDecayTime == lpFinish->flDecayTime) + lpResult->flDecayTime = lpStart->flDecayTime; + else + lpResult->flDecayTime = (float)exp( (log(lpStart->flDecayTime) * flInvRatio) + (log(lpFinish->flDecayTime) * flRatio) ); + + // Decay HF Ratio + if (lpStart->flDecayHFRatio == lpFinish->flDecayHFRatio) + lpResult->flDecayHFRatio = lpStart->flDecayHFRatio; + else + lpResult->flDecayHFRatio = (float)exp( (log(lpStart->flDecayHFRatio) * flInvRatio) + (log(lpFinish->flDecayHFRatio) * flRatio) ); + + // Decay LF Ratio + if (lpStart->flDecayLFRatio == lpFinish->flDecayLFRatio) + lpResult->flDecayLFRatio = lpStart->flDecayLFRatio; + else + lpResult->flDecayLFRatio = (float)exp( (log(lpStart->flDecayLFRatio) * flInvRatio) + (log(lpFinish->flDecayLFRatio) * flRatio) ); + + // Reflections + if (lpStart->lReflections == lpFinish->lReflections) + lpResult->lReflections = lpStart->lReflections; + else + lpResult->lReflections = (int)( ((float)lpStart->lReflections * flInvRatio) + ((float)lpFinish->lReflections * flRatio) ); + + // Reflections Delay + if (lpStart->flReflectionsDelay == lpFinish->flReflectionsDelay) + lpResult->flReflectionsDelay = lpStart->flReflectionsDelay; + else + lpResult->flReflectionsDelay = (float)exp( (log(lpStart->flReflectionsDelay+0.0001f) * flInvRatio) + (log(lpFinish->flReflectionsDelay+0.0001f) * flRatio) ); + + // Reflections Pan + + // To interpolate the vector correctly we need to ensure that both the initial and final vectors vectors are clamped to a length of 1.0f + StartVector = lpStart->vReflectionsPan; + FinalVector = lpFinish->vReflectionsPan; + + Clamp(&StartVector); + Clamp(&FinalVector); + + if (lpStart->vReflectionsPan.x == lpFinish->vReflectionsPan.x) + lpResult->vReflectionsPan.x = lpStart->vReflectionsPan.x; + else + lpResult->vReflectionsPan.x = FinalVector.x + (flInvRatio * (StartVector.x - FinalVector.x)); + + if (lpStart->vReflectionsPan.y == lpFinish->vReflectionsPan.y) + lpResult->vReflectionsPan.y = lpStart->vReflectionsPan.y; + else + lpResult->vReflectionsPan.y = FinalVector.y + (flInvRatio * (StartVector.y - FinalVector.y)); + + if (lpStart->vReflectionsPan.z == lpFinish->vReflectionsPan.z) + lpResult->vReflectionsPan.z = lpStart->vReflectionsPan.z; + else + lpResult->vReflectionsPan.z = FinalVector.z + (flInvRatio * (StartVector.z - FinalVector.z)); + + // Reverb + if (lpStart->lReverb == lpFinish->lReverb) + lpResult->lReverb = lpStart->lReverb; + else + lpResult->lReverb = (int)( ((float)lpStart->lReverb * flInvRatio) + ((float)lpFinish->lReverb * flRatio) ); + + // Reverb Delay + if (lpStart->flReverbDelay == lpFinish->flReverbDelay) + lpResult->flReverbDelay = lpStart->flReverbDelay; + else + lpResult->flReverbDelay = (float)exp( (log(lpStart->flReverbDelay+0.0001f) * flInvRatio) + (log(lpFinish->flReverbDelay+0.0001f) * flRatio) ); + + // Reverb Pan + + // To interpolate the vector correctly we need to ensure that both the initial and final vectors are clamped to a length of 1.0f + StartVector = lpStart->vReverbPan; + FinalVector = lpFinish->vReverbPan; + + Clamp(&StartVector); + Clamp(&FinalVector); + + if (lpStart->vReverbPan.x == lpFinish->vReverbPan.x) + lpResult->vReverbPan.x = lpStart->vReverbPan.x; + else + lpResult->vReverbPan.x = FinalVector.x + (flInvRatio * (StartVector.x - FinalVector.x)); + + if (lpStart->vReverbPan.y == lpFinish->vReverbPan.y) + lpResult->vReverbPan.y = lpStart->vReverbPan.y; + else + lpResult->vReverbPan.y = FinalVector.y + (flInvRatio * (StartVector.y - FinalVector.y)); + + if (lpStart->vReverbPan.z == lpFinish->vReverbPan.z) + lpResult->vReverbPan.z = lpStart->vReverbPan.z; + else + lpResult->vReverbPan.z = FinalVector.z + (flInvRatio * (StartVector.z - FinalVector.z)); + + // Echo Time + if (lpStart->flEchoTime == lpFinish->flEchoTime) + lpResult->flEchoTime = lpStart->flEchoTime; + else + lpResult->flEchoTime = (float)exp( (log(lpStart->flEchoTime) * flInvRatio) + (log(lpFinish->flEchoTime) * flRatio) ); + + // Echo Depth + if (lpStart->flEchoDepth == lpFinish->flEchoDepth) + lpResult->flEchoDepth = lpStart->flEchoDepth; + else + lpResult->flEchoDepth = (lpStart->flEchoDepth * flInvRatio) + (lpFinish->flEchoDepth * flRatio); + + // Modulation Time + if (lpStart->flModulationTime == lpFinish->flModulationTime) + lpResult->flModulationTime = lpStart->flModulationTime; + else + lpResult->flModulationTime = (float)exp( (log(lpStart->flModulationTime) * flInvRatio) + (log(lpFinish->flModulationTime) * flRatio) ); + + // Modulation Depth + if (lpStart->flModulationDepth == lpFinish->flModulationDepth) + lpResult->flModulationDepth = lpStart->flModulationDepth; + else + lpResult->flModulationDepth = (lpStart->flModulationDepth * flInvRatio) + (lpFinish->flModulationDepth * flRatio); + + // Air Absorption HF + if (lpStart->flAirAbsorptionHF == lpFinish->flAirAbsorptionHF) + lpResult->flAirAbsorptionHF = lpStart->flAirAbsorptionHF; + else + lpResult->flAirAbsorptionHF = (lpStart->flAirAbsorptionHF * flInvRatio) + (lpFinish->flAirAbsorptionHF * flRatio); + + // HF Reference + if (lpStart->flHFReference == lpFinish->flHFReference) + lpResult->flHFReference = lpStart->flHFReference; + else + lpResult->flHFReference = (float)exp( (log(lpStart->flHFReference) * flInvRatio) + (log(lpFinish->flHFReference) * flRatio) ); + + // LF Reference + if (lpStart->flLFReference == lpFinish->flLFReference) + lpResult->flLFReference = lpStart->flLFReference; + else + lpResult->flLFReference = (float)exp( (log(lpStart->flLFReference) * flInvRatio) + (log(lpFinish->flLFReference) * flRatio) ); + + // Room Rolloff Factor + if (lpStart->flRoomRolloffFactor == lpFinish->flRoomRolloffFactor) + lpResult->flRoomRolloffFactor = lpStart->flRoomRolloffFactor; + else + lpResult->flRoomRolloffFactor = (lpStart->flRoomRolloffFactor * flInvRatio) + (lpFinish->flRoomRolloffFactor * flRatio); + + // Flags + lpResult->ulFlags = (lpStart->ulFlags & lpFinish->ulFlags); + + // Clamp Delays + if (lpResult->flReflectionsDelay > EAXLISTENER_MAXREFLECTIONSDELAY) + lpResult->flReflectionsDelay = EAXLISTENER_MAXREFLECTIONSDELAY; + + if (lpResult->flReverbDelay > EAXLISTENER_MAXREVERBDELAY) + lpResult->flReverbDelay = EAXLISTENER_MAXREVERBDELAY; + + return true; +} + + +/* + CheckEAX3LP + Checks that the parameters in the EAX 3 Listener Properties structure are in-range +*/ +bool CheckEAX3LP(LPEAXLISTENERPROPERTIES lpEAX3LP) +{ + if ( (lpEAX3LP->lRoom < EAXLISTENER_MINROOM) || (lpEAX3LP->lRoom > EAXLISTENER_MAXROOM) ) + return false; + + if ( (lpEAX3LP->lRoomHF < EAXLISTENER_MINROOMHF) || (lpEAX3LP->lRoomHF > EAXLISTENER_MAXROOMHF) ) + return false; + + if ( (lpEAX3LP->lRoomLF < EAXLISTENER_MINROOMLF) || (lpEAX3LP->lRoomLF > EAXLISTENER_MAXROOMLF) ) + return false; + + if ( (lpEAX3LP->ulEnvironment < EAXLISTENER_MINENVIRONMENT) || (lpEAX3LP->ulEnvironment > EAXLISTENER_MAXENVIRONMENT) ) + return false; + + if ( (lpEAX3LP->flEnvironmentSize < EAXLISTENER_MINENVIRONMENTSIZE) || (lpEAX3LP->flEnvironmentSize > EAXLISTENER_MAXENVIRONMENTSIZE) ) + return false; + + if ( (lpEAX3LP->flEnvironmentDiffusion < EAXLISTENER_MINENVIRONMENTDIFFUSION) || (lpEAX3LP->flEnvironmentDiffusion > EAXLISTENER_MAXENVIRONMENTDIFFUSION) ) + return false; + + if ( (lpEAX3LP->flDecayTime < EAXLISTENER_MINDECAYTIME) || (lpEAX3LP->flDecayTime > EAXLISTENER_MAXDECAYTIME) ) + return false; + + if ( (lpEAX3LP->flDecayHFRatio < EAXLISTENER_MINDECAYHFRATIO) || (lpEAX3LP->flDecayHFRatio > EAXLISTENER_MAXDECAYHFRATIO) ) + return false; + + if ( (lpEAX3LP->flDecayLFRatio < EAXLISTENER_MINDECAYLFRATIO) || (lpEAX3LP->flDecayLFRatio > EAXLISTENER_MAXDECAYLFRATIO) ) + return false; + + if ( (lpEAX3LP->lReflections < EAXLISTENER_MINREFLECTIONS) || (lpEAX3LP->lReflections > EAXLISTENER_MAXREFLECTIONS) ) + return false; + + if ( (lpEAX3LP->flReflectionsDelay < EAXLISTENER_MINREFLECTIONSDELAY) || (lpEAX3LP->flReflectionsDelay > EAXLISTENER_MAXREFLECTIONSDELAY) ) + return false; + + if ( (lpEAX3LP->lReverb < EAXLISTENER_MINREVERB) || (lpEAX3LP->lReverb > EAXLISTENER_MAXREVERB) ) + return false; + + if ( (lpEAX3LP->flReverbDelay < EAXLISTENER_MINREVERBDELAY) || (lpEAX3LP->flReverbDelay > EAXLISTENER_MAXREVERBDELAY) ) + return false; + + if ( (lpEAX3LP->flEchoTime < EAXLISTENER_MINECHOTIME) || (lpEAX3LP->flEchoTime > EAXLISTENER_MAXECHOTIME) ) + return false; + + if ( (lpEAX3LP->flEchoDepth < EAXLISTENER_MINECHODEPTH) || (lpEAX3LP->flEchoDepth > EAXLISTENER_MAXECHODEPTH) ) + return false; + + if ( (lpEAX3LP->flModulationTime < EAXLISTENER_MINMODULATIONTIME) || (lpEAX3LP->flModulationTime > EAXLISTENER_MAXMODULATIONTIME) ) + return false; + + if ( (lpEAX3LP->flModulationDepth < EAXLISTENER_MINMODULATIONDEPTH) || (lpEAX3LP->flModulationDepth > EAXLISTENER_MAXMODULATIONDEPTH) ) + return false; + + if ( (lpEAX3LP->flAirAbsorptionHF < EAXLISTENER_MINAIRABSORPTIONHF) || (lpEAX3LP->flAirAbsorptionHF > EAXLISTENER_MAXAIRABSORPTIONHF) ) + return false; + + if ( (lpEAX3LP->flHFReference < EAXLISTENER_MINHFREFERENCE) || (lpEAX3LP->flHFReference > EAXLISTENER_MAXHFREFERENCE) ) + return false; + + if ( (lpEAX3LP->flLFReference < EAXLISTENER_MINLFREFERENCE) || (lpEAX3LP->flLFReference > EAXLISTENER_MAXLFREFERENCE) ) + return false; + + if ( (lpEAX3LP->flRoomRolloffFactor < EAXLISTENER_MINROOMROLLOFFFACTOR) || (lpEAX3LP->flRoomRolloffFactor > EAXLISTENER_MAXROOMROLLOFFFACTOR) ) + return false; + + if (lpEAX3LP->ulFlags & EAXLISTENERFLAGS_RESERVED) + return false; + + return true; +} + +/* + Clamp + Clamps the length of the vector to 1.0f +*/ +void Clamp(EAXVECTOR *eaxVector) +{ + float flMagnitude; + float flInvMagnitude; + + flMagnitude = (float)sqrt((eaxVector->x*eaxVector->x) + (eaxVector->y*eaxVector->y) + (eaxVector->z*eaxVector->z)); + + if (flMagnitude <= 1.0f) + return; + + flInvMagnitude = 1.0f / flMagnitude; + + eaxVector->x *= flInvMagnitude; + eaxVector->y *= flInvMagnitude; + eaxVector->z *= flInvMagnitude; +} + + +/***********************************************************************************************\ +* +* To assist those developers wishing to add EAX effects to their level editors, each of the + +* List of string names of the various EAX 3.0 presets defined in eax-util.h +* Arrays to group together presets of the same scenario +* +\***********************************************************************************************/ + + +////////////////////////////////////////////////////// +// Array of scenario names // +////////////////////////////////////////////////////// + +const char* EAX30_SCENARIO_NAMES[] = +{ + "Castle", + "Factory", + "IcePalace", + "SpaceStation", + "WoodenShip", + "Sports", + "Prefab", + "Domes and Pipes", + "Outdoors", + "Mood", + "Driving", + "City", + "Miscellaneous", + "Original" +}; + +////////////////////////////////////////////////////// +// Array of standardised location names // +////////////////////////////////////////////////////// + +const char* EAX30_LOCATION_NAMES[] = +{ + "Hall", + "Large Room", + "Medium Room", + "Small Room", + "Cupboard", + "Alcove", + "Long Passage", + "Short Passage", + "Courtyard" +}; + +////////////////////////////////////////////////////// +// Standardised Location effects can be accessed // +// from a matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_STANDARD_PRESETS[EAX30_NUM_STANDARD_SCENARIOS][EAX30_NUM_LOCATIONS]= +{ + {EAX30_PRESET_CASTLE_HALL, EAX30_PRESET_CASTLE_LARGEROOM, EAX30_PRESET_CASTLE_MEDIUMROOM, EAX30_PRESET_CASTLE_SMALLROOM, EAX30_PRESET_CASTLE_CUPBOARD, EAX30_PRESET_CASTLE_ALCOVE, EAX30_PRESET_CASTLE_LONGPASSAGE, EAX30_PRESET_CASTLE_SHORTPASSAGE, EAX30_PRESET_CASTLE_COURTYARD}, + {EAX30_PRESET_FACTORY_HALL, EAX30_PRESET_FACTORY_LARGEROOM, EAX30_PRESET_FACTORY_MEDIUMROOM, EAX30_PRESET_FACTORY_SMALLROOM, EAX30_PRESET_FACTORY_CUPBOARD, EAX30_PRESET_FACTORY_ALCOVE, EAX30_PRESET_FACTORY_LONGPASSAGE, EAX30_PRESET_FACTORY_SHORTPASSAGE, EAX30_PRESET_FACTORY_COURTYARD}, + {EAX30_PRESET_ICEPALACE_HALL, EAX30_PRESET_ICEPALACE_LARGEROOM, EAX30_PRESET_ICEPALACE_MEDIUMROOM, EAX30_PRESET_ICEPALACE_SMALLROOM, EAX30_PRESET_ICEPALACE_CUPBOARD, EAX30_PRESET_ICEPALACE_ALCOVE, EAX30_PRESET_ICEPALACE_LONGPASSAGE, EAX30_PRESET_ICEPALACE_SHORTPASSAGE, EAX30_PRESET_ICEPALACE_COURTYARD}, + {EAX30_PRESET_SPACESTATION_HALL,EAX30_PRESET_SPACESTATION_LARGEROOM,EAX30_PRESET_SPACESTATION_MEDIUMROOM, EAX30_PRESET_SPACESTATION_SMALLROOM,EAX30_PRESET_SPACESTATION_CUPBOARD, EAX30_PRESET_SPACESTATION_ALCOVE, EAX30_PRESET_SPACESTATION_LONGPASSAGE, EAX30_PRESET_SPACESTATION_SHORTPASSAGE, EAX30_PRESET_SPACESTATION_HALL}, + {EAX30_PRESET_WOODEN_HALL, EAX30_PRESET_WOODEN_LARGEROOM, EAX30_PRESET_WOODEN_MEDIUMROOM, EAX30_PRESET_WOODEN_SMALLROOM, EAX30_PRESET_WOODEN_CUPBOARD, EAX30_PRESET_WOODEN_ALCOVE, EAX30_PRESET_WOODEN_LONGPASSAGE, EAX30_PRESET_WOODEN_SHORTPASSAGE, EAX30_PRESET_WOODEN_COURTYARD}, +}; + + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of original environment names // +////////////////////////////////////////////////////// + +const char* EAX30_ORIGINAL_PRESET_NAMES[] = +{ + "Generic", + "Padded Cell", + "Room", + "Bathroom", + "Living Room", + "Stone Room", + "Auditorium", + "Concert Hall", + "Cave", + "Arena", + "Hangar", + "Carpetted Hallway", + "Hallway", + "Stone Corridor", + "Alley", + "Forest", + "City", + "Mountains", + "Quarry", + "Plain", + "Parking Lot", + "Sewer Pipe", + "Underwater", + "Drugged", + "Dizzy", + "Psychotic" +}; + +////////////////////////////////////////////////////// +// Sports effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_ORIGINAL_PRESETS[] = +{ + EAX30_PRESET_GENERIC, + EAX30_PRESET_PADDEDCELL, + EAX30_PRESET_ROOM, + EAX30_PRESET_BATHROOM, + EAX30_PRESET_LIVINGROOM, + EAX30_PRESET_STONEROOM, + EAX30_PRESET_AUDITORIUM, + EAX30_PRESET_CONCERTHALL, + EAX30_PRESET_CAVE, + EAX30_PRESET_ARENA, + EAX30_PRESET_HANGAR, + EAX30_PRESET_CARPETTEDHALLWAY, + EAX30_PRESET_HALLWAY, + EAX30_PRESET_STONECORRIDOR, + EAX30_PRESET_ALLEY, + EAX30_PRESET_FOREST, + EAX30_PRESET_CITY, + EAX30_PRESET_MOUNTAINS, + EAX30_PRESET_QUARRY, + EAX30_PRESET_PLAIN, + EAX30_PRESET_PARKINGLOT, + EAX30_PRESET_SEWERPIPE, + EAX30_PRESET_UNDERWATER, + EAX30_PRESET_DRUGGED, + EAX30_PRESET_DIZZY, + EAX30_PRESET_PSYCHOTIC +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of sport environment names // +////////////////////////////////////////////////////// + +const char* EAX30_SPORTS_PRESET_NAMES[] = +{ + "Empty Stadium", + "Full Stadium", + "Stadium Tannoy", + "Squash Court", + "Small Swimming Pool", + "Large Swimming Pool", + "Gymnasium" +}; + +////////////////////////////////////////////////////// +// Sports effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_SPORTS_PRESETS[] = +{ + EAX30_PRESET_SPORT_EMPTYSTADIUM, + EAX30_PRESET_SPORT_FULLSTADIUM, + EAX30_PRESET_SPORT_STADIUMTANNOY, + EAX30_PRESET_SPORT_SQUASHCOURT, + EAX30_PRESET_SPORT_SMALLSWIMMINGPOOL, + EAX30_PRESET_SPORT_LARGESWIMMINGPOOL, + EAX30_PRESET_SPORT_GYMNASIUM +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of prefab environment names // +////////////////////////////////////////////////////// + +const char* EAX30_PREFAB_PRESET_NAMES[] = +{ + "Workshop", + "School Room", + "Practise Room", + "Outhouse", + "Caravan" +}; + +////////////////////////////////////////////////////// +// Prefab effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_PREFAB_PRESETS[] = +{ + EAX30_PRESET_PREFAB_WORKSHOP, + EAX30_PRESET_PREFAB_SCHOOLROOM, + EAX30_PRESET_PREFAB_PRACTISEROOM, + EAX30_PRESET_PREFAB_OUTHOUSE, + EAX30_PRESET_PREFAB_CARAVAN +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of Domes & Pipes environment names // +////////////////////////////////////////////////////// + +const char* EAX30_DOMESNPIPES_PRESET_NAMES[] = +{ + "Domed Tomb", + "Saint Paul's Dome", + "Small Pipe", + "Long Thin Pipe", + "Large Pipe", + "Resonant Pipe" +}; + +////////////////////////////////////////////////////// +// Domes & Pipes effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_DOMESNPIPES_PRESETS[] = +{ + EAX30_PRESET_DOME_TOMB, + EAX30_PRESET_DOME_SAINTPAULS, + EAX30_PRESET_PIPE_SMALL, + EAX30_PRESET_PIPE_LONGTHIN, + EAX30_PRESET_PIPE_LARGE, + EAX30_PRESET_PIPE_RESONANT +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of Outdoors environment names // +////////////////////////////////////////////////////// + +const char* EAX30_OUTDOORS_PRESET_NAMES[] = +{ + "Backyard", + "Rolling Plains", + "Deep Canyon", + "Creek", + "Valley" +}; + +////////////////////////////////////////////////////// +// Outdoors effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_OUTDOORS_PRESETS[] = +{ + EAX30_PRESET_OUTDOORS_BACKYARD, + EAX30_PRESET_OUTDOORS_ROLLINGPLAINS, + EAX30_PRESET_OUTDOORS_DEEPCANYON, + EAX30_PRESET_OUTDOORS_CREEK, + EAX30_PRESET_OUTDOORS_VALLEY +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of Mood environment names // +////////////////////////////////////////////////////// + +const char* EAX30_MOOD_PRESET_NAMES[] = +{ + "Heaven", + "Hell", + "Memory" +}; + +////////////////////////////////////////////////////// +// Mood effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_MOOD_PRESETS[] = +{ + EAX30_PRESET_MOOD_HEAVEN, + EAX30_PRESET_MOOD_HELL, + EAX30_PRESET_MOOD_MEMORY +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of driving environment names // +////////////////////////////////////////////////////// + +const char* EAX30_DRIVING_PRESET_NAMES[] = +{ + "Race Commentator", + "Pit Garage", + "In-car (Stripped out racer)", + "In-car (Sportscar)", + "In-car (Luxury)", + "Full Grandstand", + "Empty Grandstand", + "Tunnel" +}; + +////////////////////////////////////////////////////// +// Driving effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_DRIVING_PRESETS[] = +{ + EAX30_PRESET_DRIVING_COMMENTATOR, + EAX30_PRESET_DRIVING_PITGARAGE, + EAX30_PRESET_DRIVING_INCAR_RACER, + EAX30_PRESET_DRIVING_INCAR_SPORTS, + EAX30_PRESET_DRIVING_INCAR_LUXURY, + EAX30_PRESET_DRIVING_FULLGRANDSTAND, + EAX30_PRESET_DRIVING_EMPTYGRANDSTAND, + EAX30_PRESET_DRIVING_TUNNEL +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of City environment names // +////////////////////////////////////////////////////// + +const char* EAX30_CITY_PRESET_NAMES[] = +{ + "City Streets", + "Subway", + "Museum", + "Library", + "Underpass", + "Abandoned City" +}; + +////////////////////////////////////////////////////// +// City effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_CITY_PRESETS[] = +{ + EAX30_PRESET_CITY_STREETS, + EAX30_PRESET_CITY_SUBWAY, + EAX30_PRESET_CITY_MUSEUM, + EAX30_PRESET_CITY_LIBRARY, + EAX30_PRESET_CITY_UNDERPASS, + EAX30_PRESET_CITY_ABANDONED +}; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Array of Misc environment names // +////////////////////////////////////////////////////// + +const char* EAX30_MISC_PRESET_NAMES[] = +{ + "Dusty Box Room", + "Chapel", + "Small Water Room" +}; + +////////////////////////////////////////////////////// +// Misc effects matrix // +////////////////////////////////////////////////////// + +EAXLISTENERPROPERTIES EAX30_MISC_PRESETS[] = +{ + EAX30_PRESET_DUSTYROOM, + EAX30_PRESET_CHAPEL, + EAX30_PRESET_SMALLWATERROOM +}; + diff --git a/src/audio/eax/eax-util.h b/src/audio/eax/eax-util.h new file mode 100644 index 0000000..441f011 --- /dev/null +++ b/src/audio/eax/eax-util.h @@ -0,0 +1,765 @@ +/*******************************************************************\ +* * +* EAX-UTIL.H - utilities for Environmental Audio Extensions v. 3.0 * +* Definitions of the Original 26 EAX Presets * +* Definitions for some new EAX Presets * +* Definitions of some Material Presets * +* Function declaration for EAX Morphing * +* * +\*******************************************************************/ + +#ifndef EAXUTIL_INCLUDED +#define EAXUTIL_INCLUDED + +#include + +/*********************************************************************************************** +* Function : EAX3ListenerInterpolate +* Params : lpStart - Initial EAX 3 Listener parameters +* : lpFinish - Final EAX 3 Listener parameters +* : flRatio - Ratio Destination : Source (0.0 == Source, 1.0 == Destination) +* : lpResult - Interpolated EAX 3 Listener parameters +* : bCheckValues - Check EAX 3.0 parameters are in range, + - default == false (no checking) +************************************************************************************************/ +bool EAX3ListenerInterpolate(EAXLISTENERPROPERTIES *lpStartEAX3LP, EAXLISTENERPROPERTIES *lpFinishEAX3LP, + float flRatio, EAXLISTENERPROPERTIES *lpResultEAX3LP, bool bCheckValues = false); + + +/***********************************************************************************************\ +* +* Legacy environment presets for use with DSPROPERTY_EAXLISTENER_ALLPARAMETERS. +* Each array conforms to the DSPROPSETID_EAX30_ListenerProperties structure defined in EAX.H. +* +************************************************************************************************/ + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_GENERIC \ + {0, 7.5f, 1.000f, -1000, -100, 0, 1.49f, 0.83f, 1.00f, -2602, 0.007f, 0.00f,0.00f,0.00f, 200, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PADDEDCELL \ + {1, 1.4f, 1.000f, -1000, -6000, 0, 0.17f, 0.10f, 1.00f, -1204, 0.001f, 0.00f,0.00f,0.00f, 207, 0.002f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_ROOM \ + {2, 1.9f, 1.000f, -1000, -454, 0, 0.40f, 0.83f, 1.00f, -1646, 0.002f, 0.00f,0.00f,0.00f, 53, 0.003f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_BATHROOM \ + {3, 1.4f, 1.000f, -1000, -1200, 0, 1.49f, 0.54f, 1.00f, -370, 0.007f, 0.00f,0.00f,0.00f, 1030, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_LIVINGROOM \ + {4, 2.5f, 1.000f, -1000, -6000, 0, 0.50f, 0.10f, 1.00f, -1376, 0.003f, 0.00f,0.00f,0.00f, -1104, 0.004f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_STONEROOM \ + {5, 11.6f, 1.000f, -1000, -300, 0, 2.31f, 0.64f, 1.00f, -711, 0.012f, 0.00f,0.00f,0.00f, 83, 0.017f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_AUDITORIUM \ + {6, 21.6f, 1.000f, -1000, -476, 0, 4.32f, 0.59f, 1.00f, -789, 0.020f, 0.00f,0.00f,0.00f, -289, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_CONCERTHALL \ + {7, 19.6f, 1.000f, -1000, -500, 0, 3.92f, 0.70f, 1.00f, -1230, 0.020f, 0.00f,0.00f,0.00f, -02, 0.029f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_CAVE \ + {8, 14.6f, 1.000f, -1000, 0, 0, 2.91f, 1.30f, 1.00f, -602, 0.015f, 0.00f,0.00f,0.00f, -302, 0.022f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_ARENA \ + {9, 36.2f, 1.000f, -1000, -698, 0, 7.24f, 0.33f, 1.00f, -1166, 0.020f, 0.00f,0.00f,0.00f, 16, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_HANGAR \ + {10, 50.3f, 1.000f, -1000, -1000, 0, 10.05f, 0.23f, 1.00f, -602, 0.020f, 0.00f,0.00f,0.00f, 198, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_CARPETTEDHALLWAY \ + {11, 1.9f, 1.000f, -1000, -4000, 0, 0.30f, 0.10f, 1.00f, -1831, 0.002f, 0.00f,0.00f,0.00f, -1630, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_HALLWAY \ + {12, 1.8f, 1.000f, -1000, -300, 0, 1.49f, 0.59f, 1.00f, -1219, 0.007f, 0.00f,0.00f,0.00f, 441, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_STONECORRIDOR \ + {13, 13.5f, 1.000f, -1000, -237, 0, 2.70f, 0.79f, 1.00f, -1214, 0.013f, 0.00f,0.00f,0.00f, 395, 0.020f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_ALLEY \ + {14, 7.5f, 0.300f, -1000, -270, 0, 1.49f, 0.86f, 1.00f, -1204, 0.007f, 0.00f,0.00f,0.00f, -4, 0.011f, 0.00f,0.00f,0.00f, 0.125f, 0.950f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_FOREST \ + {15, 38.0f, 0.300f, -1000, -3300, 0, 1.49f, 0.54f, 1.00f, -2560, 0.162f, 0.00f,0.00f,0.00f, -229, 0.088f, 0.00f,0.00f,0.00f, 0.125f, 1.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_CITY \ + {16, 7.5f, 0.500f, -1000, -800, 0, 1.49f, 0.67f, 1.00f, -2273, 0.007f, 0.00f,0.00f,0.00f, -1691, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_MOUNTAINS \ + {17, 100.0f, 0.270f, -1000, -2500, 0, 1.49f, 0.21f, 1.00f, -2780, 0.300f, 0.00f,0.00f,0.00f, -1434, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_QUARRY \ + {18, 17.5f, 1.000f, -1000, -1000, 0, 1.49f, 0.83f, 1.00f, -10000, 0.061f, 0.00f,0.00f,0.00f, 500, 0.025f, 0.00f,0.00f,0.00f, 0.125f, 0.700f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PLAIN \ + {19, 42.5f, 0.210f, -1000, -2000, 0, 1.49f, 0.50f, 1.00f, -2466, 0.179f, 0.00f,0.00f,0.00f, -1926, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PARKINGLOT \ + {20, 8.3f, 1.000f, -1000, 0, 0, 1.65f, 1.50f, 1.00f, -1363, 0.008f, 0.00f,0.00f,0.00f, -1153, 0.012f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_SEWERPIPE \ + {21, 1.7f, 0.800f, -1000, -1000, 0, 2.81f, 0.14f, 1.00f, 429, 0.014f, 0.00f,0.00f,0.00f, 1023, 0.021f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_UNDERWATER \ + {22, 1.8f, 1.000f, -1000, -4000, 0, 1.49f, 0.10f, 1.00f, -449, 0.007f, 0.00f,0.00f,0.00f, 1700, 0.011f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 1.180f, 0.348f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_DRUGGED \ + {23, 1.9f, 0.500f, -1000, 0, 0, 8.39f, 1.39f, 1.00f, -115, 0.002f, 0.00f,0.00f,0.00f, 985, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 1.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_DIZZY \ + {24, 1.8f, 0.600f, -1000, -400, 0, 17.23f, 0.56f, 1.00f, -1713, 0.020f, 0.00f,0.00f,0.00f, -613, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.810f, 0.310f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_PSYCHOTIC \ + {25, 1.0f, 0.500f, -1000, -151, 0, 7.56f, 0.91f, 1.00f, -626, 0.020f, 0.00f,0.00f,0.00f, 774, 0.030f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 4.000f, 1.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } + + +/***********************************************************************************************\ +* +* New environment presets for use with DSPROPERTY_EAXLISTENER_ALLPARAMETERS. +* Each array conforms to the DSPROPSETID_EAX30_ListenerProperties structure defined in EAX.H. +* +************************************************************************************************/ + +// STANDARDISED-LOCATION SCENARIOS + +// CASTLE PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_CASTLE_SMALLROOM \ + { 26, 8.3f, 0.890f, -1100, -800, -2000, 1.22f, 0.83f, 0.31f, -100, 0.022f, 0.00f,0.00f,0.00f, 0, 0.011f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_SHORTPASSAGE \ + { 26, 8.3f, 0.890f, -1000, -1000, -2000, 2.32f, 0.83f, 0.31f, -100, 0.007f, 0.00f,0.00f,0.00f, -500, 0.023f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_MEDIUMROOM \ + { 26, 8.3f, 0.930f, -1000, -1100, -2000, 2.04f, 0.83f, 0.46f, -300, 0.022f, 0.00f,0.00f,0.00f, -200, 0.011f, 0.00f,0.00f,0.00f, 0.155f, 0.030f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_LONGPASSAGE \ + { 26, 8.3f, 0.890f, -1000, -800, -2000, 3.42f, 0.83f, 0.31f, -200, 0.007f, 0.00f,0.00f,0.00f, -600, 0.023f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_LARGEROOM \ + { 26, 8.3f, 0.820f, -1000, -1100, -1800, 2.53f, 0.83f, 0.50f, -900, 0.034f, 0.00f,0.00f,0.00f, -400, 0.016f, 0.00f,0.00f,0.00f, 0.185f, 0.070f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_HALL \ + { 26, 8.3f, 0.810f, -1000, -1100, -1500, 3.14f, 0.79f, 0.62f, -1300, 0.056f, 0.00f,0.00f,0.00f, -500, 0.024f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_CUPBOARD \ + { 26, 8.3f, 0.890f, -1000, -1100, -2000, 0.67f, 0.87f, 0.31f, 300, 0.010f, 0.00f,0.00f,0.00f, 300, 0.007f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } +#define EAX30_PRESET_CASTLE_COURTYARD \ + { 26, 8.3f, 0.420f, -1100, -700, -900, 2.13f, 0.61f, 0.23f, -2300, 0.112f, 0.00f,0.00f,0.00f, -1500, 0.036f, 0.00f,0.00f,0.00f, 0.250f, 0.370f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_CASTLE_ALCOVE \ + { 26, 8.3f, 0.890f, -1000, -600, -2000, 1.64f, 0.87f, 0.31f, -100, 0.007f, 0.00f,0.00f,0.00f, -500, 0.034f, 0.00f,0.00f,0.00f, 0.138f, 0.080f, 0.250f, 0.000f, -5.0f, 5168.6f, 139.5f, 0.00f, 0x20 } + + +// FACTORY PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_FACTORY_ALCOVE \ + { 26, 1.8f, 0.590f, -1200, -200, -600, 3.14f, 0.65f, 1.31f, 300, 0.010f, 0.00f,0.00f,0.00f, -1200, 0.038f, 0.00f,0.00f,0.00f, 0.114f, 0.100f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_SHORTPASSAGE \ + { 26, 1.8f, 0.640f, -1200, -200, -600, 2.53f, 0.65f, 1.31f, 0, 0.010f, 0.00f,0.00f,0.00f, -600, 0.038f, 0.00f,0.00f,0.00f, 0.135f, 0.230f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_MEDIUMROOM \ + { 26, 1.9f, 0.820f, -1200, -200, -600, 2.76f, 0.65f, 1.31f, -1100, 0.022f, 0.00f,0.00f,0.00f, -400, 0.023f, 0.00f,0.00f,0.00f, 0.174f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_LONGPASSAGE \ + { 26, 1.8f, 0.640f, -1200, -200, -600, 4.06f, 0.65f, 1.31f, 0, 0.020f, 0.00f,0.00f,0.00f, -900, 0.037f, 0.00f,0.00f,0.00f, 0.135f, 0.230f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_LARGEROOM \ + { 26, 1.9f, 0.750f, -1200, -300, -400, 4.24f, 0.51f, 1.31f, -1500, 0.039f, 0.00f,0.00f,0.00f, -600, 0.023f, 0.00f,0.00f,0.00f, 0.231f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_HALL \ + { 26, 1.9f, 0.750f, -1000, -300, -400, 7.43f, 0.51f, 1.31f, -2400, 0.073f, 0.00f,0.00f,0.00f, -500, 0.027f, 0.00f,0.00f,0.00f, 0.250f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_CUPBOARD \ + { 26, 1.7f, 0.630f, -1200, -200, -600, 0.49f, 0.65f, 1.31f, 200, 0.010f, 0.00f,0.00f,0.00f, 200, 0.032f, 0.00f,0.00f,0.00f, 0.107f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_COURTYARD \ + { 26, 1.7f, 0.570f, -1000, -1000, -400, 2.32f, 0.29f, 0.56f, -2400, 0.090f, 0.00f,0.00f,0.00f, -2000, 0.039f, 0.00f,0.00f,0.00f, 0.250f, 0.290f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } +#define EAX30_PRESET_FACTORY_SMALLROOM \ + { 26, 1.8f, 0.820f, -1200, -200, -600, 1.72f, 0.65f, 1.31f, -300, 0.010f, 0.00f,0.00f,0.00f, -200, 0.024f, 0.00f,0.00f,0.00f, 0.119f, 0.070f, 0.250f, 0.000f, -0.0f, 3762.6f, 362.5f, 0.00f, 0x20 } + +// ICE PALACE PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_ICEPALACE_ALCOVE \ + { 26, 2.7f, 0.840f, -1000, -500, -1100, 2.76f, 1.46f, 0.28f, 100, 0.010f, 0.00f,0.00f,0.00f, -1200, 0.030f, 0.00f,0.00f,0.00f, 0.161f, 0.090f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_SHORTPASSAGE \ + { 26, 2.7f, 0.750f, -1000, -500, -1100, 1.79f, 1.46f, 0.28f, -600, 0.010f, 0.00f,0.00f,0.00f, -700, 0.019f, 0.00f,0.00f,0.00f, 0.177f, 0.090f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_MEDIUMROOM \ + { 26, 2.7f, 0.870f, -1000, -500, -700, 2.22f, 1.53f, 0.32f, -800, 0.039f, 0.00f,0.00f,0.00f, -1200, 0.027f, 0.00f,0.00f,0.00f, 0.186f, 0.120f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_LONGPASSAGE \ + { 26, 2.7f, 0.770f, -1000, -500, -800, 3.01f, 1.46f, 0.28f, -200, 0.012f, 0.00f,0.00f,0.00f, -800, 0.025f, 0.00f,0.00f,0.00f, 0.186f, 0.040f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_LARGEROOM \ + { 26, 2.9f, 0.810f, -1000, -500, -700, 3.14f, 1.53f, 0.32f, -1200, 0.039f, 0.00f,0.00f,0.00f, -1300, 0.027f, 0.00f,0.00f,0.00f, 0.214f, 0.110f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_HALL \ + { 26, 2.9f, 0.760f, -1000, -700, -500, 5.49f, 1.53f, 0.38f, -1900, 0.054f, 0.00f,0.00f,0.00f, -1400, 0.052f, 0.00f,0.00f,0.00f, 0.226f, 0.110f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_CUPBOARD \ + { 26, 2.7f, 0.830f, -1000, -600, -1300, 0.76f, 1.53f, 0.26f, 100, 0.012f, 0.00f,0.00f,0.00f, 100, 0.016f, 0.00f,0.00f,0.00f, 0.143f, 0.080f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_COURTYARD \ + { 26, 2.9f, 0.590f, -1000, -1100, -1000, 2.04f, 1.20f, 0.38f, -2000, 0.073f, 0.00f,0.00f,0.00f, -2200, 0.043f, 0.00f,0.00f,0.00f, 0.235f, 0.480f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } +#define EAX30_PRESET_ICEPALACE_SMALLROOM \ + { 26, 2.7f, 0.840f, -1000, -500, -1100, 1.51f, 1.53f, 0.27f, -100, 0.010f, 0.00f,0.00f,0.00f, -900, 0.011f, 0.00f,0.00f,0.00f, 0.164f, 0.140f, 0.250f, 0.000f, -0.0f, 12428.5f, 99.6f, 0.00f, 0x20 } + +// SPACE STATION PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_SPACESTATION_ALCOVE \ + { 26, 1.5f, 0.780f, -1100, -300, -100, 1.16f, 0.81f, 0.55f, 300, 0.007f, 0.00f,0.00f,0.00f, -500, 0.018f, 0.00f,0.00f,0.00f, 0.192f, 0.210f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_MEDIUMROOM \ + { 26, 1.5f, 0.750f, -1000, -400, -100, 3.01f, 0.50f, 0.55f, -1000, 0.034f, 0.00f,0.00f,0.00f, -700, 0.035f, 0.00f,0.00f,0.00f, 0.209f, 0.310f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_SHORTPASSAGE \ + { 26, 1.5f, 0.870f, -1000, -400, -100, 3.57f, 0.50f, 0.55f, 0, 0.012f, 0.00f,0.00f,0.00f, -600, 0.016f, 0.00f,0.00f,0.00f, 0.172f, 0.200f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_LONGPASSAGE \ + { 26, 1.9f, 0.820f, -1000, -400, -100, 4.62f, 0.62f, 0.55f, 0, 0.012f, 0.00f,0.00f,0.00f, -800, 0.031f, 0.00f,0.00f,0.00f, 0.250f, 0.230f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_LARGEROOM \ + { 26, 1.8f, 0.810f, -1000, -400, -100, 3.89f, 0.38f, 0.61f, -1200, 0.056f, 0.00f,0.00f,0.00f, -800, 0.035f, 0.00f,0.00f,0.00f, 0.233f, 0.280f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_HALL \ + { 26, 1.9f, 0.870f, -1000, -400, -100, 7.11f, 0.38f, 0.61f, -1500, 0.100f, 0.00f,0.00f,0.00f, -1000, 0.047f, 0.00f,0.00f,0.00f, 0.250f, 0.250f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_CUPBOARD \ + { 26, 1.4f, 0.560f, -1000, -300, -100, 0.79f, 0.81f, 0.55f, 200, 0.007f, 0.00f,0.00f,0.00f, 400, 0.018f, 0.00f,0.00f,0.00f, 0.181f, 0.310f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPACESTATION_SMALLROOM \ + { 26, 1.5f, 0.700f, -1000, -300, -100, 1.72f, 0.82f, 0.55f, -400, 0.007f, 0.00f,0.00f,0.00f, -500, 0.013f, 0.00f,0.00f,0.00f, 0.188f, 0.260f, 0.250f, 0.000f, -5.0f, 3316.1f, 458.2f, 0.00f, 0x20 } + +// WOODEN GALLEON PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_WOODEN_ALCOVE \ + { 26, 7.5f, 1.000f, -1100, -1800, -1000, 1.22f, 0.62f, 0.91f, -100, 0.012f, 0.00f,0.00f,0.00f, -600, 0.024f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_SHORTPASSAGE \ + { 26, 7.5f, 1.000f, -1100, -1800, -1000, 1.45f, 0.50f, 0.87f, -300, 0.012f, 0.00f,0.00f,0.00f, -700, 0.024f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_MEDIUMROOM \ + { 26, 7.5f, 1.000f, -1200, -2000, -1100, 1.07f, 0.42f, 0.82f, -300, 0.039f, 0.00f,0.00f,0.00f, -400, 0.029f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_LONGPASSAGE \ + { 26, 7.5f, 1.000f, -1100, -2000, -1000, 1.79f, 0.40f, 0.79f, -200, 0.020f, 0.00f,0.00f,0.00f, -1000, 0.036f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_LARGEROOM \ + { 26, 7.5f, 1.000f, -1200, -2100, -1100, 1.45f, 0.33f, 0.82f, -300, 0.056f, 0.00f,0.00f,0.00f, -500, 0.049f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_HALL \ + { 26, 7.5f, 1.000f, -1200, -2200, -1100, 1.95f, 0.30f, 0.82f, -300, 0.068f, 0.00f,0.00f,0.00f, -500, 0.063f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_CUPBOARD \ + { 26, 7.5f, 1.000f, -1000, -1700, -1000, 0.56f, 0.46f, 0.91f, -100, 0.012f, 0.00f,0.00f,0.00f, -100, 0.028f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_SMALLROOM \ + { 26, 7.5f, 1.000f, -1200, -1900, -1000, 0.79f, 0.32f, 0.87f, -200, 0.032f, 0.00f,0.00f,0.00f, -300, 0.029f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } +#define EAX30_PRESET_WOODEN_COURTYARD \ + { 26, 7.5f, 0.650f, -1700, -2200, -1000, 1.79f, 0.35f, 0.79f, -700, 0.063f, 0.00f,0.00f,0.00f, -2300, 0.032f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 4705.0f, 99.6f, 0.00f, 0x3f } + + +// OTHER SCENARIOS + +// SPORTS PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_SPORT_EMPTYSTADIUM \ + { 26, 7.2f, 1.000f, -1300, -700, -200, 6.26f, 0.51f, 1.10f, -2400, 0.183f, 0.00f,0.00f,0.00f, -1100, 0.038f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_SPORT_SQUASHCOURT \ + { 26, 7.5f, 0.750f, -1100, -1000, -200, 2.22f, 0.91f, 1.16f, -700, 0.007f, 0.00f,0.00f,0.00f, -300, 0.011f, 0.00f,0.00f,0.00f, 0.126f, 0.190f, 0.250f, 0.000f, -0.0f, 7176.9f, 211.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPORT_SMALLSWIMMINGPOOL \ + { 26, 36.2f, 0.700f, -1400, -200, -100, 2.76f, 1.25f, 1.14f, -400, 0.020f, 0.00f,0.00f,0.00f, -300, 0.030f, 0.00f,0.00f,0.00f, 0.179f, 0.150f, 0.895f, 0.190f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } +#define EAX30_PRESET_SPORT_LARGESWIMMINGPOOL\ + { 26, 36.2f, 0.820f, -1200, -200, 0, 5.49f, 1.31f, 1.14f, -700, 0.039f, 0.00f,0.00f,0.00f, -800, 0.049f, 0.00f,0.00f,0.00f, 0.222f, 0.550f, 1.159f, 0.210f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } +#define EAX30_PRESET_SPORT_GYMNASIUM \ + { 26, 7.5f, 0.810f, -1200, -700, -100, 3.14f, 1.06f, 1.35f, -800, 0.029f, 0.00f,0.00f,0.00f, -700, 0.045f, 0.00f,0.00f,0.00f, 0.146f, 0.140f, 0.250f, 0.000f, -0.0f, 7176.9f, 211.2f, 0.00f, 0x20 } +#define EAX30_PRESET_SPORT_FULLSTADIUM \ + { 26, 7.2f, 1.000f, -1300, -2300, -200, 5.25f, 0.17f, 0.80f, -2000, 0.188f, 0.00f,0.00f,0.00f, -1300, 0.038f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_SPORT_STADIUMTANNOY \ + { 26, 3.0f, 0.780f, -900, -500, -600, 2.53f, 0.88f, 0.68f, -1100, 0.230f, 0.00f,0.00f,0.00f, -600, 0.063f, 0.00f,0.00f,0.00f, 0.250f, 0.200f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } + +// PREFAB PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_PREFAB_WORKSHOP \ + { 26, 1.9f, 1.000f, -1000, -1700, -800, 0.76f, 1.00f, 1.00f, 0, 0.012f, 0.00f,0.00f,0.00f, -200, 0.012f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } +#define EAX30_PRESET_PREFAB_SCHOOLROOM \ + { 26, 1.86f, 0.690f, -1100, -400, -600, 0.98f, 0.45f, 0.18f, 300, 0.017f, 0.00f,0.00f,0.00f, 0, 0.015f, 0.00f,0.00f,0.00f, 0.095f, 0.140f, 0.250f, 0.000f, -0.0f, 7176.9f, 211.2f, 0.00f, 0x20 } +#define EAX30_PRESET_PREFAB_PRACTISEROOM \ + { 26, 1.86f, 0.870f, -1000, -800, -600, 1.12f, 0.56f, 0.18f, 200, 0.010f, 0.00f,0.00f,0.00f, -200, 0.011f, 0.00f,0.00f,0.00f, 0.095f, 0.140f, 0.250f, 0.000f, -0.0f, 7176.9f, 211.2f, 0.00f, 0x20 } +#define EAX30_PRESET_PREFAB_OUTHOUSE \ + { 26, 80.3f, 0.820f, -1100, -1900, -1600, 1.38f, 0.38f, 0.35f, -100, 0.024f, 0.00f,0.00f,-0.00f, -800, 0.044f, 0.00f,0.00f,0.00f, 0.121f, 0.170f, 0.250f, 0.000f, -0.0f, 2854.4f, 107.5f, 0.00f, 0x0 } +#define EAX30_PRESET_PREFAB_CARAVAN \ + { 26, 8.3f, 1.000f, -1000, -2100, -1800, 0.43f, 1.50f, 1.00f, 0, 0.012f, 0.00f,0.00f,0.00f, 400, 0.012f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x1f } + // for US developers, a caravan is the same as a trailer =o) + + +// DOME AND PIPE PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_DOME_TOMB \ + { 26, 51.8f, 0.790f, -1000, -900, -1300, 4.18f, 0.21f, 0.10f, -825, 0.030f, 0.00f,0.00f,0.00f, -125, 0.022f, 0.00f,0.00f,0.00f, 0.177f, 0.190f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x0 } +#define EAX30_PRESET_PIPE_SMALL \ + { 26, 50.3f, 1.000f, -1000, -900, -1300, 5.04f, 0.10f, 0.10f, -600, 0.032f, 0.00f,0.00f,0.00f, 400, 0.015f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x3f } +#define EAX30_PRESET_DOME_SAINTPAULS \ + { 26, 50.3f, 0.870f, -1000, -900, -1300, 10.48f, 0.19f, 0.10f, -1500, 0.090f, 0.00f,0.00f,0.00f, -500, 0.042f, 0.00f,0.00f,0.00f, 0.250f, 0.120f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PIPE_LONGTHIN \ + { 26, 1.6f, 0.910f, -1200, -700, -1100, 9.21f, 0.18f, 0.10f, -300, 0.010f, 0.00f,0.00f,0.00f, -1000, 0.022f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x0 } +#define EAX30_PRESET_PIPE_LARGE \ + { 26, 50.3f, 1.000f, -1000, -900, -1300, 8.45f, 0.10f, 0.10f, -800, 0.046f, 0.00f,0.00f,0.00f, 0, 0.032f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x3f } +#define EAX30_PRESET_PIPE_RESONANT \ + { 26, 1.3f, 0.910f, -1200, -700, -1100, 6.81f, 0.18f, 0.10f, -300, 0.010f, 0.00f,0.00f,0.00f, -700, 0.022f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2854.4f, 20.0f, 0.00f, 0x0 } + +// OUTDOORS PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_OUTDOORS_BACKYARD \ + { 26, 80.3f, 0.450f, -1100, -1200, -600, 1.12f, 0.34f, 0.46f, -1100, 0.049f, 0.00f,0.00f,-0.00f, -1300, 0.023f, 0.00f,0.00f,0.00f, 0.218f, 0.340f, 0.250f, 0.000f, -5.0f, 4399.1f, 242.9f, 0.00f, 0x0 } +#define EAX30_PRESET_OUTDOORS_ROLLINGPLAINS \ + { 26, 80.3f, 0.000f, -1100, -3900, -400, 2.13f, 0.21f, 0.46f, -2000, 0.300f, 0.00f,0.00f,-0.00f, -1500, 0.019f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -5.0f, 4399.1f, 242.9f, 0.00f, 0x0 } +#define EAX30_PRESET_OUTDOORS_DEEPCANYON \ + { 26, 80.3f, 0.740f, -1100, -1500, -400, 3.89f, 0.21f, 0.46f, -2000, 0.193f, 0.00f,0.00f,-0.00f, -1100, 0.019f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -5.0f, 4399.1f, 242.9f, 0.00f, 0x0 } +#define EAX30_PRESET_OUTDOORS_CREEK \ + { 26, 80.3f, 0.350f, -1100, -1500, -600, 2.13f, 0.21f, 0.46f, -1700, 0.115f, 0.00f,0.00f,-0.00f, -1100, 0.031f, 0.00f,0.00f,0.00f, 0.218f, 0.340f, 0.250f, 0.000f, -5.0f, 4399.1f, 242.9f, 0.00f, 0x0 } +#define EAX30_PRESET_OUTDOORS_VALLEY \ + { 26, 80.3f, 0.280f, -1100, -3100, -1600, 2.88f, 0.26f, 0.35f, -3200, 0.163f, 0.00f,0.00f,-0.00f, -1000, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 0.340f, 0.250f, 0.000f, -0.0f, 2854.4f, 107.5f, 0.00f, 0x0 } + + +// MOOD PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_MOOD_HEAVEN \ + { 26, 19.6f, 0.940f, -1000, -200, -700, 5.04f, 1.12f, 0.56f, -1230, 0.020f, 0.00f,0.00f,0.00f, -200, 0.029f, 0.00f,0.00f,0.00f, 0.250f, 0.080f, 2.742f, 0.050f, -2.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_MOOD_HELL \ + { 26, 100.0f, 0.570f, -1000, -900, -700, 3.57f, 0.49f, 2.00f, -10000, 0.020f, 0.00f,0.00f,0.00f, 100, 0.030f, 0.00f,0.00f,0.00f, 0.110f, 0.040f, 2.109f, 0.520f, -5.0f, 5000.0f, 139.5f, 0.00f, 0x40 } +#define EAX30_PRESET_MOOD_MEMORY \ + { 26, 8.0f, 0.850f, -1000, -400, -900, 4.06f, 0.82f, 0.56f, -2800, 0.000f, 0.00f,0.00f,0.00f, -500, 0.000f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.474f, 0.450f, -2.0f, 5000.0f, 250.0f, 0.00f, 0x0 } + +// DRIVING SIMULATION PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_DRIVING_COMMENTATOR \ + { 26, 3.0f, 0.000f, -900, -500, -600, 2.42f, 0.88f, 0.68f, -1400, 0.093f, 0.00f,0.00f,0.00f, -1200, 0.017f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_DRIVING_PITGARAGE \ + { 26, 1.9f, 0.590f, -1400, -300, -500, 1.72f, 0.93f, 0.87f, -500, 0.000f, 0.00f,0.00f,0.00f, 0, 0.016f, 0.00f,0.00f,0.00f, 0.250f, 0.110f, 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } +#define EAX30_PRESET_DRIVING_INCAR_RACER \ + { 26, 1.1f, 0.800f, -700, 0, -200, 0.17f, 2.00f, 0.41f, 500, 0.007f, 0.00f,0.00f,0.00f, -500, 0.015f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -0.0f, 10268.2f, 251.0f, 0.00f, 0x20 } +#define EAX30_PRESET_DRIVING_INCAR_SPORTS \ + { 26, 1.1f, 0.800f, -900, -400, 0, 0.17f, 0.75f, 0.41f, 0, 0.010f, 0.00f,0.00f,0.00f, -600, 0.000f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -0.0f, 10268.2f, 251.0f, 0.00f, 0x20 } +#define EAX30_PRESET_DRIVING_INCAR_LUXURY \ + { 26, 1.6f, 1.000f, -800, -2000, -600, 0.13f, 0.41f, 0.46f, -200, 0.010f, 0.00f,0.00f,0.00f, 300, 0.010f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -0.0f, 10268.2f, 251.0f, 0.00f, 0x20 } +#define EAX30_PRESET_DRIVING_FULLGRANDSTAND \ + { 26, 8.3f, 1.000f, -1100, -1100, -400, 3.01f, 1.37f, 1.28f, -900, 0.090f, 0.00f,0.00f,0.00f, -1700, 0.049f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 10420.2f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_DRIVING_EMPTYGRANDSTAND \ + { 26, 8.3f, 1.000f, -700, 0, -200, 4.62f, 1.75f, 1.40f, -1363, 0.090f, 0.00f,0.00f,0.00f, -1900, 0.049f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 10420.2f, 250.0f, 0.00f, 0x1f } +#define EAX30_PRESET_DRIVING_TUNNEL \ + { 26, 3.1f, 0.810f, -900, -800, -100, 3.42f, 0.94f, 1.31f, -300, 0.051f, 0.00f,0.00f,0.00f, -500, 0.047f, 0.00f,0.00f,0.00f, 0.214f, 0.050f, 0.250f, 0.000f, -0.0f, 5000.0f, 155.3f, 0.00f, 0x20 } + +// CITY PRESETS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_CITY_STREETS \ + { 26, 3.0f, 0.780f, -1100, -300, -100, 1.79f, 1.12f, 0.91f, -1700, 0.046f, 0.00f,0.00f,0.00f, -2800, 0.028f, 0.00f,0.00f,0.00f, 0.250f, 0.200f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_CITY_SUBWAY \ + { 26, 3.0f, 0.740f, -1100, -300, -100, 3.01f, 1.23f, 0.91f, -700, 0.046f, 0.00f,0.00f,0.00f, -1000, 0.028f, 0.00f,0.00f,0.00f, 0.125f, 0.210f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_CITY_MUSEUM \ + { 26, 80.3f, 0.820f, -1100, -1500, -1500, 3.28f, 1.40f, 0.57f, -1600, 0.039f, 0.00f,0.00f,-0.00f, -600, 0.034f, 0.00f,0.00f,0.00f, 0.130f, 0.170f, 0.250f, 0.000f, -0.0f, 2854.4f, 107.5f, 0.00f, 0x0 } +#define EAX30_PRESET_CITY_LIBRARY \ + { 26, 80.3f, 0.820f, -1100, -1100, -2100, 2.76f, 0.89f, 0.41f, -1100, 0.029f, 0.00f,0.00f,-0.00f, -500, 0.020f, 0.00f,0.00f,0.00f, 0.130f, 0.170f, 0.250f, 0.000f, -0.0f, 2854.4f, 107.5f, 0.00f, 0x0 } +#define EAX30_PRESET_CITY_UNDERPASS \ + { 26, 3.0f, 0.820f, -1500, -700, -100, 3.57f, 1.12f, 0.91f, -1500, 0.059f, 0.00f,0.00f,0.00f, -1100, 0.037f, 0.00f,0.00f,0.00f, 0.250f, 0.140f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } +#define EAX30_PRESET_CITY_ABANDONED \ + { 26, 3.0f, 0.690f, -1100, -200, -100, 3.28f, 1.17f, 0.91f, -1400, 0.044f, 0.00f,0.00f,0.00f, -2400, 0.024f, 0.00f,0.00f,0.00f, 0.250f, 0.200f, 0.250f, 0.000f, -0.0f, 5000.0f, 250.0f, 0.00f, 0x20 } + +// MISC ROOMS + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +#define EAX30_PRESET_DUSTYROOM \ + { 26, 1.8f, 0.560f, -1100, -200, -300, 1.79f, 0.38f, 0.21f, -600, 0.002f, 0.00f,0.00f,0.00f, 200, 0.006f, 0.00f,0.00f,0.00f, 0.202f, 0.050f, 0.250f, 0.000f, -3.0f, 13046.0f, 163.3f, 0.00f, 0x20 } +#define EAX30_PRESET_CHAPEL \ + { 26, 19.6f, 0.840f, -1000, -500, 0, 4.62f, 0.64f, 1.23f, -700, 0.032f, 0.00f,0.00f,0.00f, -800, 0.049f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.110f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f } +#define EAX30_PRESET_SMALLWATERROOM \ + { 26, 36.2f, 0.700f, -1200, -698, 0, 1.51f, 1.25f, 1.14f, -100, 0.020f, 0.00f,0.00f,0.00f, 200, 0.030f, 0.00f,0.00f,0.00f, 0.179f, 0.150f, 0.895f, 0.190f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x0 } + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Effect Scenarios enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + EAX30_SCENARIO_CASTLE = 0, + EAX30_SCENARIO_FACTORY, + EAX30_SCENARIO_ICEPALACE, + EAX30_SCENARIO_SPACESTATION, + EAX30_SCENARIO_WOODGALLEON, + EAX30_SCENARIO_SPORTS, + EAX30_SCENARIO_PREFAB, + EAX30_SCENARIO_DOMESNPIPES, + EAX30_SCENARIO_OUTDOORS, + EAX30_SCENARIO_MOOD, + EAX30_SCENARIO_DRIVING, + EAX30_SCENARIO_CITY, + EAX30_SCENARIO_MISC, + EAX30_SCENARIO_ORIGINAL +} +EAX30_SCENARIO; + +////////////////////////////////////////////////////// +// Number of Effect Scenarios // +////////////////////////////////////////////////////// + +#define EAX30_NUM_SCENARIOS 14 + +////////////////////////////////////////////////////// +// Number of Effect Scenarios with standardised // +// locations // +////////////////////////////////////////////////////// + +#define EAX30_NUM_STANDARD_SCENARIOS 5 + +////////////////////////////////////////////////////// +// Array of scenario names // +////////////////////////////////////////////////////// + +extern const char* EAX30_SCENARIO_NAMES[]; + +////////////////////////////////////////////////////// +// Standardised Locations enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + EAX30_LOCATION_HALL = 0, + EAX30_LOCATION_LARGEROOM, + EAX30_LOCATION_MEDIUMROOM, + EAX30_LOCATION_SMALLROOM, + EAX30_LOCATION_CUPBOARD, + EAX30_LOCATION_ALCOVE, + EAX30_LOCATION_LONGPASSAGE, + EAX30_LOCATION_SHORTPASSAGE, + EAX30_LOCATION_COURTYARD +} +EAX30_LOCATION; + +////////////////////////////////////////////////////// +// Number of Standardised Locations // +////////////////////////////////////////////////////// + +#define EAX30_NUM_LOCATIONS 9 + +////////////////////////////////////////////////////// +// Array of standardised location names // +////////////////////////////////////////////////////// + +extern const char* EAX30_LOCATION_NAMES[]; + +////////////////////////////////////////////////////// +// Number of effects in each scenario // +////////////////////////////////////////////////////// + +#define EAX30_NUM_ORIGINAL_PRESETS 26 +#define EAX30_NUM_CASTLE_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_FACTORY_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_ICEPALACE_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_SPACESTATION_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_WOODGALLEON_PRESETS EAX30_NUM_LOCATIONS +#define EAX30_NUM_SPORTS_PRESETS 7 +#define EAX30_NUM_PREFAB_PRESETS 5 +#define EAX30_NUM_DOMESNPIPES_PRESETS 6 +#define EAX30_NUM_OUTDOORS_PRESETS 5 +#define EAX30_NUM_MOOD_PRESETS 3 +#define EAX30_NUM_DRIVING_PRESETS 8 +#define EAX30_NUM_CITY_PRESETS 6 +#define EAX30_NUM_MISC_PRESETS 3 + +////////////////////////////////////////////////////// +// Standardised Location effects can be accessed // +// from a matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_STANDARD_PRESETS[EAX30_NUM_STANDARD_SCENARIOS][EAX30_NUM_LOCATIONS]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Original Preset effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + ORIGINAL_GENERIC = 0, + ORIGINAL_PADDEDCELL, + ORIGINAL_ROOM, + ORIGINAL_BATHROOM, + ORIGINAL_LIVINGROOM, + ORIGINAL_STONEROOM, + ORIGINAL_AUDITORIUM, + ORIGINAL_CONCERTHALL, + ORIGINAL_CAVE, + ORIGINAL_ARENA, + ORIGINAL_HANGAR, + ORIGINAL_CARPETTEDHALLWAY, + ORIGINAL_HALLWAY, + ORIGINAL_STONECORRIDOR, + ORIGINAL_ALLEY, + ORIGINAL_FOREST, + ORIGINAL_CITY, + ORIGINAL_MOUNTAINS, + ORIGINAL_QUARRY, + ORIGINAL_PLAIN, + ORIGINAL_PARKINGLOT, + ORIGINAL_SEWERPIPE, + ORIGINAL_UNDERWATER, + ORIGINAL_DRUGGED, + ORIGINAL_DIZZY, + ORIGINAL_PSYCHOTIC +} +EAX30_ORIGINAL_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of original environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_ORIGINAL_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Original effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_ORIGINAL_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Sports scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + SPORT_EMPTYSTADIUM=0, + SPORT_FULLSTADIUM, + SPORT_STADIUMTANNOY, + SPORT_SQUASHCOURT, + SPORT_SMALLSWIMMINGPOOL, + SPORT_LARGESWIMMINGPOOL, + SPORT_GYMNASIUM +} +EAX30_SPORTS_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of sport environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_SPORTS_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Sports effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_SPORTS_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Prefab scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + PREFAB_WORKSHOP, + PREFAB_SCHOOLROOM, + PREFAB_PRACTISEROOM, + PREFAB_OUTHOUSE, + PREFAB_CARAVAN +} +EAX30_PREFAB_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of prefab environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_PREFAB_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Prefab effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_PREFAB_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Domes & Pipes effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + DOME_TOMB, + DOME_SAINTPAULS, + PIPE_SMALL, + PIPE_LONGTHIN, + PIPE_LARGE, + PIPE_RESONANT +} +EAX30_DOMESNPIPES_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of Domes & Pipes environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_DOMESNPIPES_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Domes & Pipes effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_DOMESNPIPES_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Outdoors scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + OUTDOORS_BACKYARD, + OUTDOORS_ROLLINGPLAINS, + OUTDOORS_DEEPCANYON, + OUTDOORS_CREEK, + OUTDOORS_VALLEY +} +EAX30_OUTDOORS_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of Outdoors environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_OUTDOORS_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Outdoors effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_OUTDOORS_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Mood scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + MOOD_HEAVEN, + MOOD_HELL, + MOOD_MEMORY +} +EAX30_MOOD_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of Mood environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_MOOD_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Mood effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_MOOD_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Driving scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + DRIVING_COMMENTATOR, + DRIVING_PITGARAGE, + DRIVING_INCAR_RACER, + DRIVING_INCAR_SPORTS, + DRIVING_INCAR_LUXURY, + DRIVING_FULLGRANDSTAND, + DRIVING_EMPTYGRANDSTAND, + DRIVING_TUNNEL +} +EAX30_DRIVING_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of driving environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_DRIVING_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Driving effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_DRIVING_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// City scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + CITY_STREETS, + CITY_SUBWAY, + CITY_MUSEUM, + CITY_LIBRARY, + CITY_UNDERPASS, + CITY_ABANDONED +} +EAX30_CITY_PRESET_ENUMS; + +////////////////////////////////////////////////////// +// Array of City environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_CITY_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// City effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_CITY_PRESETS[]; + +/********************************************************************************************************/ + +////////////////////////////////////////////////////// +// Misc scenario effects enumerated // +////////////////////////////////////////////////////// + +typedef enum +{ + DUSTYROOM, + CHAPEL, + SMALLWATERROOM +} +EAX30_MISC_PRESET_ENUMS; + + +////////////////////////////////////////////////////// +// Array of Misc environment names // +////////////////////////////////////////////////////// + +extern const char* EAX30_MISC_PRESET_NAMES[]; + +////////////////////////////////////////////////////// +// Misc effects matrix // +////////////////////////////////////////////////////// + +extern EAXLISTENERPROPERTIES EAX30_MISC_PRESETS[]; + + +/***********************************************************************************************\ +* +* Material transmission presets +* +* Three values in this order :- +* +* 1. Occlusion (or Obstruction) +* 2. Occlusion LF Ratio (or Obstruction LF Ratio) +* 3. Occlusion Room Ratio +* +************************************************************************************************/ + + +// Single window material preset +#define EAX_MATERIAL_SINGLEWINDOW (-2800) +#define EAX_MATERIAL_SINGLEWINDOWLF 0.71f +#define EAX_MATERIAL_SINGLEWINDOWROOMRATIO 0.43f + +// Double window material preset +#define EAX_MATERIAL_DOUBLEWINDOW (-5000) +#define EAX_MATERIAL_DOUBLEWINDOWLF 0.40f +#define EAX_MATERIAL_DOUBLEWINDOWROOMRATIO 0.24f + +// Thin door material preset +#define EAX_MATERIAL_THINDOOR (-1800) +#define EAX_MATERIAL_THINDOORLF 0.66f +#define EAX_MATERIAL_THINDOORROOMRATIO 0.66f + +// Thick door material preset +#define EAX_MATERIAL_THICKDOOR (-4400) +#define EAX_MATERIAL_THICKDOORLF 0.64f +#define EAX_MATERIAL_THICKDOORROOMRATIO 0.27f + +// Wood wall material preset +#define EAX_MATERIAL_WOODWALL (-4000) +#define EAX_MATERIAL_WOODWALLLF 0.50f +#define EAX_MATERIAL_WOODWALLROOMRATIO 0.30f + +// Brick wall material preset +#define EAX_MATERIAL_BRICKWALL (-5000) +#define EAX_MATERIAL_BRICKWALLLF 0.60f +#define EAX_MATERIAL_BRICKWALLROOMRATIO 0.24f + +// Stone wall material preset +#define EAX_MATERIAL_STONEWALL (-6000) +#define EAX_MATERIAL_STONEWALLLF 0.68f +#define EAX_MATERIAL_STONEWALLROOMRATIO 0.20f + +// Curtain material preset +#define EAX_MATERIAL_CURTAIN (-1200) +#define EAX_MATERIAL_CURTAINLF 0.15f +#define EAX_MATERIAL_CURTAINROOMRATIO 1.00f + + +#endif // EAXUTIL_INCLUDED diff --git a/src/audio/eax/eax.h b/src/audio/eax/eax.h new file mode 100644 index 0000000..b221093 --- /dev/null +++ b/src/audio/eax/eax.h @@ -0,0 +1,536 @@ +/*******************************************************************\ +* * +* EAX.H - Environmental Audio Extensions version 3.0 * +* for OpenAL and DirectSound3D * +* * +********************************************************************/ + +#ifndef EAX_H_INCLUDED +#define EAX_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifndef AUDIO_OAL + #include + + /* + * EAX Wrapper Interface (using Direct X 7) {4FF53B81-1CE0-11d3-AAB8-00A0C95949D5} + */ + DEFINE_GUID(CLSID_EAXDirectSound, + 0x4ff53b81, + 0x1ce0, + 0x11d3, + 0xaa, 0xb8, 0x0, 0xa0, 0xc9, 0x59, 0x49, 0xd5); + + /* + * EAX Wrapper Interface (using Direct X 8) {CA503B60-B176-11d4-A094-D0C0BF3A560C} + */ + DEFINE_GUID(CLSID_EAXDirectSound8, + 0xca503b60, + 0xb176, + 0x11d4, + 0xa0, 0x94, 0xd0, 0xc0, 0xbf, 0x3a, 0x56, 0xc); + + + +#ifdef DIRECTSOUND_VERSION +#if DIRECTSOUND_VERSION >= 0x0800 + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate8(GUID*, LPDIRECTSOUND8*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE8)(GUID*, LPDIRECTSOUND8*, IUnknown FAR*); +#endif +#endif + + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate(GUID*, LPDIRECTSOUND*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE)(GUID*, LPDIRECTSOUND*, IUnknown FAR*); + + __declspec(dllimport) void CDECL GetCurrentVersion(LPDWORD major, LPDWORD minor); + typedef void (CDECL *LPGETCURRENTVERSION)(LPDWORD major, LPDWORD minor); + + +#else // AUDIO_OAL + #include + #include + + #ifndef GUID_DEFINED + #define GUID_DEFINED + typedef struct _GUID + { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; + } GUID; + #endif // !GUID_DEFINED + + #ifndef DEFINE_GUID + #ifndef INITGUID + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID /*FAR*/ name + #else + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + #endif // INITGUID + #endif // DEFINE_GUID + + + /* + * EAX OpenAL Extension + */ + typedef ALenum (*EAXSet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALenum (*EAXGet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); +#endif + +#pragma pack(push, 4) + +/* + * EAX 3.0 listener property set {A8FA6880-B476-11d3-BDB9-00C0F02DDF87} + */ +DEFINE_GUID(DSPROPSETID_EAX30_ListenerProperties, + 0xa8fa6882, + 0xb476, + 0x11d3, + 0xbd, 0xb9, 0x00, 0xc0, 0xf0, 0x2d, 0xdf, 0x87); + +// For compatibility with future EAX versions: +#define DSPROPSETID_EAX_ListenerProperties DSPROPSETID_EAX30_ListenerProperties + +typedef enum +{ + DSPROPERTY_EAXLISTENER_NONE, + DSPROPERTY_EAXLISTENER_ALLPARAMETERS, + DSPROPERTY_EAXLISTENER_ENVIRONMENT, + DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, + DSPROPERTY_EAXLISTENER_ENVIRONMENTDIFFUSION, + DSPROPERTY_EAXLISTENER_ROOM, + DSPROPERTY_EAXLISTENER_ROOMHF, + DSPROPERTY_EAXLISTENER_ROOMLF, + DSPROPERTY_EAXLISTENER_DECAYTIME, + DSPROPERTY_EAXLISTENER_DECAYHFRATIO, + DSPROPERTY_EAXLISTENER_DECAYLFRATIO, + DSPROPERTY_EAXLISTENER_REFLECTIONS, + DSPROPERTY_EAXLISTENER_REFLECTIONSDELAY, + DSPROPERTY_EAXLISTENER_REFLECTIONSPAN, + DSPROPERTY_EAXLISTENER_REVERB, + DSPROPERTY_EAXLISTENER_REVERBDELAY, + DSPROPERTY_EAXLISTENER_REVERBPAN, + DSPROPERTY_EAXLISTENER_ECHOTIME, + DSPROPERTY_EAXLISTENER_ECHODEPTH, + DSPROPERTY_EAXLISTENER_MODULATIONTIME, + DSPROPERTY_EAXLISTENER_MODULATIONDEPTH, + DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF, + DSPROPERTY_EAXLISTENER_HFREFERENCE, + DSPROPERTY_EAXLISTENER_LFREFERENCE, + DSPROPERTY_EAXLISTENER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAXLISTENER_FLAGS +} DSPROPERTY_EAX_LISTENERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAXLISTENER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAXLISTENER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAXLISTENER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAXLISTENER_NONE | \ + DSPROPERTY_EAXLISTENER_IMMEDIATE) + +typedef struct _EAXVECTOR { + float x; + float y; + float z; +} EAXVECTOR; + +// Use this structure for DSPROPERTY_EAXLISTENER_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all times and delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myListener.lRoom = -1000; +// myListener.lRoomHF = -100; +// ... +// myListener.dwFlags = myFlags /* see EAXLISTENERFLAGS below */ ; +// instead of: +// myListener = { -1000, -100, ... , 0x00000009 }; +// If you want to save and load presets in binary form, you +// should define your own structure to insure future compatibility. +// +typedef struct _EAXLISTENERPROPERTIES +{ + unsigned long ulEnvironment; // sets all listener properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + long lRoom; // room effect level (at mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lRoomLF; // relative room effect level at low frequencies + float flDecayTime; // reverberation decay time at mid frequencies + float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio + float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + EAXVECTOR vReflectionsPan; // early reflections panning vector + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + EAXVECTOR vReverbPan; // late reverberation panning vector + float flEchoTime; // echo time + float flEchoDepth; // echo depth + float flModulationTime; // modulation time + float flModulationDepth; // modulation depth + float flAirAbsorptionHF; // change in level per meter at high frequencies + float flHFReference; // reference high frequency + float flLFReference; // reference low frequency + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + unsigned long ulFlags; // modifies the behavior of properties +} EAXLISTENERPROPERTIES, *LPEAXLISTENERPROPERTIES; + +// used by DSPROPERTY_EAXLISTENER_ENVIRONMENT +enum +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_UNDEFINED, + + EAX_ENVIRONMENT_COUNT +}; + +// Used by DSPROPERTY_EAXLISTENER_FLAGS +// +// Note: The number and order of flags may change in future EAX versions. +// It is recommended to use the flag defines as follows: +// myFlags = EAXLISTENERFLAGS_DECAYTIMESCALE | EAXLISTENERFLAGS_REVERBSCALE; +// instead of: +// myFlags = 0x00000009; +// +// These flags determine what properties are affected by environment size. +#define EAXLISTENERFLAGS_DECAYTIMESCALE 0x00000001 // reverberation decay time +#define EAXLISTENERFLAGS_REFLECTIONSSCALE 0x00000002 // reflection level +#define EAXLISTENERFLAGS_REFLECTIONSDELAYSCALE 0x00000004 // initial reflection delay time +#define EAXLISTENERFLAGS_REVERBSCALE 0x00000008 // reflections level +#define EAXLISTENERFLAGS_REVERBDELAYSCALE 0x00000010 // late reverberation delay time +#define EAXLISTENERFLAGS_ECHOTIMESCALE 0x00000040 // echo time +#define EAXLISTENERFLAGS_MODULATIONTIMESCALE 0x00000080 // modulation time + +// This flag limits high-frequency decay time according to air absorption. +#define EAXLISTENERFLAGS_DECAYHFLIMIT 0x00000020 + +#define EAXLISTENERFLAGS_RESERVED 0xFFFFFF00 // reserved future use + +// Property ranges and defaults: + +#define EAXLISTENER_MINENVIRONMENT 0 +#define EAXLISTENER_MAXENVIRONMENT (EAX_ENVIRONMENT_COUNT-1) +#define EAXLISTENER_DEFAULTENVIRONMENT EAX_ENVIRONMENT_GENERIC + +#define EAXLISTENER_MINENVIRONMENTSIZE 1.0f +#define EAXLISTENER_MAXENVIRONMENTSIZE 100.0f +#define EAXLISTENER_DEFAULTENVIRONMENTSIZE 7.5f + +#define EAXLISTENER_MINENVIRONMENTDIFFUSION 0.0f +#define EAXLISTENER_MAXENVIRONMENTDIFFUSION 1.0f +#define EAXLISTENER_DEFAULTENVIRONMENTDIFFUSION 1.0f + +#define EAXLISTENER_MINROOM (-10000) +#define EAXLISTENER_MAXROOM 0 +#define EAXLISTENER_DEFAULTROOM (-1000) + +#define EAXLISTENER_MINROOMHF (-10000) +#define EAXLISTENER_MAXROOMHF 0 +#define EAXLISTENER_DEFAULTROOMHF (-100) + +#define EAXLISTENER_MINROOMLF (-10000) +#define EAXLISTENER_MAXROOMLF 0 +#define EAXLISTENER_DEFAULTROOMLF 0 + +#define EAXLISTENER_MINDECAYTIME 0.1f +#define EAXLISTENER_MAXDECAYTIME 20.0f +#define EAXLISTENER_DEFAULTDECAYTIME 1.49f + +#define EAXLISTENER_MINDECAYHFRATIO 0.1f +#define EAXLISTENER_MAXDECAYHFRATIO 2.0f +#define EAXLISTENER_DEFAULTDECAYHFRATIO 0.83f + +#define EAXLISTENER_MINDECAYLFRATIO 0.1f +#define EAXLISTENER_MAXDECAYLFRATIO 2.0f +#define EAXLISTENER_DEFAULTDECAYLFRATIO 1.00f + +#define EAXLISTENER_MINREFLECTIONS (-10000) +#define EAXLISTENER_MAXREFLECTIONS 1000 +#define EAXLISTENER_DEFAULTREFLECTIONS (-2602) + +#define EAXLISTENER_MINREFLECTIONSDELAY 0.0f +#define EAXLISTENER_MAXREFLECTIONSDELAY 0.3f +#define EAXLISTENER_DEFAULTREFLECTIONSDELAY 0.007f + +#define EAXLISTENER_MINREVERB (-10000) +#define EAXLISTENER_MAXREVERB 2000 +#define EAXLISTENER_DEFAULTREVERB 200 + +#define EAXLISTENER_MINREVERBDELAY 0.0f +#define EAXLISTENER_MAXREVERBDELAY 0.1f +#define EAXLISTENER_DEFAULTREVERBDELAY 0.011f + +#define EAXLISTENER_MINECHOTIME 0.075f +#define EAXLISTENER_MAXECHOTIME 0.25f +#define EAXLISTENER_DEFAULTECHOTIME 0.25f + +#define EAXLISTENER_MINECHODEPTH 0.0f +#define EAXLISTENER_MAXECHODEPTH 1.0f +#define EAXLISTENER_DEFAULTECHODEPTH 0.0f + +#define EAXLISTENER_MINMODULATIONTIME 0.04f +#define EAXLISTENER_MAXMODULATIONTIME 4.0f +#define EAXLISTENER_DEFAULTMODULATIONTIME 0.25f + +#define EAXLISTENER_MINMODULATIONDEPTH 0.0f +#define EAXLISTENER_MAXMODULATIONDEPTH 1.0f +#define EAXLISTENER_DEFAULTMODULATIONDEPTH 0.0f + +#define EAXLISTENER_MINAIRABSORPTIONHF (-100.0f) +#define EAXLISTENER_MAXAIRABSORPTIONHF 0.0f +#define EAXLISTENER_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXLISTENER_MINHFREFERENCE 1000.0f +#define EAXLISTENER_MAXHFREFERENCE 20000.0f +#define EAXLISTENER_DEFAULTHFREFERENCE 5000.0f + +#define EAXLISTENER_MINLFREFERENCE 20.0f +#define EAXLISTENER_MAXLFREFERENCE 1000.0f +#define EAXLISTENER_DEFAULTLFREFERENCE 250.0f + +#define EAXLISTENER_MINROOMROLLOFFFACTOR 0.0f +#define EAXLISTENER_MAXROOMROLLOFFFACTOR 10.0f +#define EAXLISTENER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXLISTENER_DEFAULTFLAGS (EAXLISTENERFLAGS_DECAYTIMESCALE | \ + EAXLISTENERFLAGS_REFLECTIONSSCALE | \ + EAXLISTENERFLAGS_REFLECTIONSDELAYSCALE | \ + EAXLISTENERFLAGS_REVERBSCALE | \ + EAXLISTENERFLAGS_REVERBDELAYSCALE | \ + EAXLISTENERFLAGS_DECAYHFLIMIT) + + + +/* +* EAX 3.0 buffer property set {A8FA6881-B476-11d3-BDB9-00C0F02DDF87} +*/ +DEFINE_GUID(DSPROPSETID_EAX30_BufferProperties, + 0xa8fa6881, + 0xb476, + 0x11d3, + 0xbd, 0xb9, 0x0, 0xc0, 0xf0, 0x2d, 0xdf, 0x87); + +// For compatibility with future EAX versions: +#define DSPROPSETID_EAX_BufferProperties DSPROPSETID_EAX30_BufferProperties +#define DSPROPSETID_EAX_SourceProperties DSPROPSETID_EAX30_BufferProperties + +typedef enum +{ + DSPROPERTY_EAXBUFFER_NONE, + DSPROPERTY_EAXBUFFER_ALLPARAMETERS, + DSPROPERTY_EAXBUFFER_OBSTRUCTIONPARAMETERS, + DSPROPERTY_EAXBUFFER_OCCLUSIONPARAMETERS, + DSPROPERTY_EAXBUFFER_EXCLUSIONPARAMETERS, + DSPROPERTY_EAXBUFFER_DIRECT, + DSPROPERTY_EAXBUFFER_DIRECTHF, + DSPROPERTY_EAXBUFFER_ROOM, + DSPROPERTY_EAXBUFFER_ROOMHF, + DSPROPERTY_EAXBUFFER_OBSTRUCTION, + DSPROPERTY_EAXBUFFER_OBSTRUCTIONLFRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSION, + DSPROPERTY_EAXBUFFER_OCCLUSIONLFRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSIONROOMRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSIONDIRECTRATIO, + DSPROPERTY_EAXBUFFER_EXCLUSION, + DSPROPERTY_EAXBUFFER_EXCLUSIONLFRATIO, + DSPROPERTY_EAXBUFFER_OUTSIDEVOLUMEHF, + DSPROPERTY_EAXBUFFER_DOPPLERFACTOR, + DSPROPERTY_EAXBUFFER_ROLLOFFFACTOR, + DSPROPERTY_EAXBUFFER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAXBUFFER_AIRABSORPTIONFACTOR, + DSPROPERTY_EAXBUFFER_FLAGS +} DSPROPERTY_EAX_BUFFERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAXBUFFER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAXBUFFER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAXBUFFER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAXBUFFER_NONE | \ + DSPROPERTY_EAXBUFFER_IMMEDIATE) + +// Use this structure for DSPROPERTY_EAXBUFFER_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myBuffer.lDirect = 0; +// myBuffer.lDirectHF = -200; +// ... +// myBuffer.dwFlags = myFlags /* see EAXBUFFERFLAGS below */ ; +// instead of: +// myBuffer = { 0, -200, ... , 0x00000003 }; +// +typedef struct _EAXBUFFERPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // relative occlusion control for room effect + float flOcclusionDirectRatio; // relative occlusion control for direct path + long lExclusion; // main exlusion control (attenuation at high frequencies) + float flExclusionLFRatio; // exclusion low-frequency level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flDopplerFactor; // like DS3D flDopplerFactor but per source + float flRolloffFactor; // like DS3D flRolloffFactor but per source + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF + unsigned long ulFlags; // modifies the behavior of properties +} EAXBUFFERPROPERTIES, *LPEAXBUFFERPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_OBSTRUCTION, +typedef struct _EAXOBSTRUCTIONPROPERTIES +{ + long lObstruction; + float flObstructionLFRatio; +} EAXOBSTRUCTIONPROPERTIES, *LPEAXOBSTRUCTIONPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_OCCLUSION +typedef struct _EAXOCCLUSIONPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXOCCLUSIONPROPERTIES, *LPEAXOCCLUSIONPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_EXCLUSION +typedef struct _EAXEXCLUSIONPROPERTIES +{ + long lExclusion; + float flExclusionLFRatio; +} EAXEXCLUSIONPROPERTIES, *LPEAXEXCLUSIONPROPERTIES; + +// Used by DSPROPERTY_EAXBUFFER_FLAGS +// TRUE: value is computed automatically - property is an offset +// FALSE: value is used directly +// +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXBUFFERFLAGS_DIRECTHFAUTO | EAXBUFFERFLAGS_ROOMAUTO; +// instead of: +// myFlags = 0x00000003; +// +#define EAXBUFFERFLAGS_DIRECTHFAUTO 0x00000001 // affects DSPROPERTY_EAXBUFFER_DIRECTHF +#define EAXBUFFERFLAGS_ROOMAUTO 0x00000002 // affects DSPROPERTY_EAXBUFFER_ROOM +#define EAXBUFFERFLAGS_ROOMHFAUTO 0x00000004 // affects DSPROPERTY_EAXBUFFER_ROOMHF + +#define EAXBUFFERFLAGS_RESERVED 0xFFFFFFF8 // reserved future use + +// Property ranges and defaults: + +#define EAXBUFFER_MINDIRECT (-10000) +#define EAXBUFFER_MAXDIRECT 1000 +#define EAXBUFFER_DEFAULTDIRECT 0 + +#define EAXBUFFER_MINDIRECTHF (-10000) +#define EAXBUFFER_MAXDIRECTHF 0 +#define EAXBUFFER_DEFAULTDIRECTHF 0 + +#define EAXBUFFER_MINROOM (-10000) +#define EAXBUFFER_MAXROOM 1000 +#define EAXBUFFER_DEFAULTROOM 0 + +#define EAXBUFFER_MINROOMHF (-10000) +#define EAXBUFFER_MAXROOMHF 0 +#define EAXBUFFER_DEFAULTROOMHF 0 + +#define EAXBUFFER_MINOBSTRUCTION (-10000) +#define EAXBUFFER_MAXOBSTRUCTION 0 +#define EAXBUFFER_DEFAULTOBSTRUCTION 0 + +#define EAXBUFFER_MINOBSTRUCTIONLFRATIO 0.0f +#define EAXBUFFER_MAXOBSTRUCTIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTOBSTRUCTIONLFRATIO 0.0f + +#define EAXBUFFER_MINOCCLUSION (-10000) +#define EAXBUFFER_MAXOCCLUSION 0 +#define EAXBUFFER_DEFAULTOCCLUSION 0 + +#define EAXBUFFER_MINOCCLUSIONLFRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTOCCLUSIONLFRATIO 0.25f + +#define EAXBUFFER_MINOCCLUSIONROOMRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONROOMRATIO 10.0f +#define EAXBUFFER_DEFAULTOCCLUSIONROOMRATIO 1.5f + +#define EAXBUFFER_MINOCCLUSIONDIRECTRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONDIRECTRATIO 10.0f +#define EAXBUFFER_DEFAULTOCCLUSIONDIRECTRATIO 1.0f + +#define EAXBUFFER_MINEXCLUSION (-10000) +#define EAXBUFFER_MAXEXCLUSION 0 +#define EAXBUFFER_DEFAULTEXCLUSION 0 + +#define EAXBUFFER_MINEXCLUSIONLFRATIO 0.0f +#define EAXBUFFER_MAXEXCLUSIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTEXCLUSIONLFRATIO 1.0f + +#define EAXBUFFER_MINOUTSIDEVOLUMEHF (-10000) +#define EAXBUFFER_MAXOUTSIDEVOLUMEHF 0 +#define EAXBUFFER_DEFAULTOUTSIDEVOLUMEHF 0 + +#define EAXBUFFER_MINDOPPLERFACTOR 0.0f +#define EAXBUFFER_MAXDOPPLERFACTOR 10.f +#define EAXBUFFER_DEFAULTDOPPLERFACTOR 0.0f + +#define EAXBUFFER_MINROLLOFFFACTOR 0.0f +#define EAXBUFFER_MAXROLLOFFFACTOR 10.f +#define EAXBUFFER_DEFAULTROLLOFFFACTOR 0.0f + +#define EAXBUFFER_MINROOMROLLOFFFACTOR 0.0f +#define EAXBUFFER_MAXROOMROLLOFFFACTOR 10.f +#define EAXBUFFER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXBUFFER_MINAIRABSORPTIONFACTOR 0.0f +#define EAXBUFFER_MAXAIRABSORPTIONFACTOR 10.0f +#define EAXBUFFER_DEFAULTAIRABSORPTIONFACTOR 1.0f + +#define EAXBUFFER_DEFAULTFLAGS (EAXBUFFERFLAGS_DIRECTHFAUTO | \ + EAXBUFFERFLAGS_ROOMAUTO | \ + EAXBUFFERFLAGS_ROOMHFAUTO ) + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif diff --git a/src/audio/oal/aldlist.cpp b/src/audio/oal/aldlist.cpp new file mode 100644 index 0000000..6024adf --- /dev/null +++ b/src/audio/oal/aldlist.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2006, Creative Labs Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions and + * the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the distribution. + * * Neither the name of Creative Labs Inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "aldlist.h" + +#ifdef AUDIO_OAL +/* + * Init call + */ +ALDeviceList::ALDeviceList() +{ + char *devices; + int index; + const char *defaultDeviceName; + const char *actualDeviceName; + + // DeviceInfo vector stores, for each enumerated device, it's device name, selection status, spec version #, and extension support + nNumOfDevices = 0; + + defaultDeviceIndex = 0; + + if (alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT")) { + devices = (char *)alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); + defaultDeviceName = (char *)alcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); + + index = 0; + // go through device list (each device terminated with a single NULL, list terminated with double NULL) + while (*devices != '\0') { + if (strcmp(defaultDeviceName, devices) == 0) { + defaultDeviceIndex = index; + } + ALCdevice *device = alcOpenDevice(devices); + if (device) { + ALCcontext *context = alcCreateContext(device, NULL); + if (context) { + alcMakeContextCurrent(context); + // if new actual device name isn't already in the list, then add it... + actualDeviceName = alcGetString(device, ALC_ALL_DEVICES_SPECIFIER); + if ((actualDeviceName != NULL) && (strlen(actualDeviceName) > 0)) { + ALDEVICEINFO &ALDeviceInfo = aDeviceInfo[nNumOfDevices++]; + ALDeviceInfo.bSelected = true; + ALDeviceInfo.SetName(actualDeviceName); + alcGetIntegerv(device, ALC_MAJOR_VERSION, sizeof(int), &ALDeviceInfo.iMajorVersion); + alcGetIntegerv(device, ALC_MINOR_VERSION, sizeof(int), &ALDeviceInfo.iMinorVersion); + + // Check for ALC Extensions + if (alcIsExtensionPresent(device, "ALC_EXT_CAPTURE") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_CAPTURE; + if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_EFX; + + // Check for AL Extensions + if (alIsExtensionPresent("AL_EXT_OFFSET") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_OFFSET; + + if (alIsExtensionPresent("AL_EXT_LINEAR_DISTANCE") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_LINEAR_DISTANCE; + if (alIsExtensionPresent("AL_EXT_EXPONENT_DISTANCE") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EXT_EXPONENT_DISTANCE; + + if (alIsExtensionPresent("EAX2.0") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX2; + if (alIsExtensionPresent("EAX3.0") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX3; + if (alIsExtensionPresent("EAX4.0") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX4; + if (alIsExtensionPresent("EAX5.0") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX5; + + if (alIsExtensionPresent("EAX-RAM") == AL_TRUE) + ALDeviceInfo.Extensions |= ADEXT_EAX_RAM; + + // Get Source Count + ALDeviceInfo.uiSourceCount = GetMaxNumSources(); + } + alcMakeContextCurrent(NULL); + alcDestroyContext(context); + } + alcCloseDevice(device); + } + devices += strlen(devices) + 1; + index += 1; + } + } + + ResetFilters(); +} + +/* + * Exit call + */ +ALDeviceList::~ALDeviceList() +{ +} + +/* + * Returns the number of devices in the complete device list + */ +unsigned int ALDeviceList::GetNumDevices() +{ + return nNumOfDevices; +} + +/* + * Returns the device name at an index in the complete device list + */ +const char * ALDeviceList::GetDeviceName(unsigned int index) +{ + if (index < GetNumDevices()) + return aDeviceInfo[index].strDeviceName; + else + return NULL; +} + +/* + * Returns the major and minor version numbers for a device at a specified index in the complete list + */ +void ALDeviceList::GetDeviceVersion(unsigned int index, int *major, int *minor) +{ + if (index < GetNumDevices()) { + if (major) + *major = aDeviceInfo[index].iMajorVersion; + if (minor) + *minor = aDeviceInfo[index].iMinorVersion; + } + return; +} + +/* + * Returns the maximum number of Sources that can be generate on the given device + */ +unsigned int ALDeviceList::GetMaxNumSources(unsigned int index) +{ + if (index < GetNumDevices()) + return aDeviceInfo[index].uiSourceCount; + else + return 0; +} + +/* + * Checks if the extension is supported on the given device + */ +bool ALDeviceList::IsExtensionSupported(int index, unsigned short ext) +{ + return !!(aDeviceInfo[index].Extensions & ext); +} + +/* + * returns the index of the default device in the complete device list + */ +int ALDeviceList::GetDefaultDevice() +{ + return defaultDeviceIndex; +} + +/* + * Deselects devices which don't have the specified minimum version + */ +void ALDeviceList::FilterDevicesMinVer(int major, int minor) +{ + int dMajor, dMinor; + for (unsigned int i = 0; i < nNumOfDevices; i++) { + GetDeviceVersion(i, &dMajor, &dMinor); + if ((dMajor < major) || ((dMajor == major) && (dMinor < minor))) { + aDeviceInfo[i].bSelected = false; + } + } +} + +/* + * Deselects devices which don't have the specified maximum version + */ +void ALDeviceList::FilterDevicesMaxVer(int major, int minor) +{ + int dMajor, dMinor; + for (unsigned int i = 0; i < nNumOfDevices; i++) { + GetDeviceVersion(i, &dMajor, &dMinor); + if ((dMajor > major) || ((dMajor == major) && (dMinor > minor))) { + aDeviceInfo[i].bSelected = false; + } + } +} + +/* + * Deselects device which don't support the given extension name + */ +void +ALDeviceList::FilterDevicesExtension(unsigned short ext) +{ + for (unsigned int i = 0; i < nNumOfDevices; i++) { + if (!IsExtensionSupported(i, ext)) + aDeviceInfo[i].bSelected = false; + } +} + +/* + * Resets all filtering, such that all devices are in the list + */ +void ALDeviceList::ResetFilters() +{ + for (unsigned int i = 0; i < GetNumDevices(); i++) { + aDeviceInfo[i].bSelected = true; + } + filterIndex = 0; +} + +/* + * Gets index of first filtered device + */ +int ALDeviceList::GetFirstFilteredDevice() +{ + unsigned int i; + + for (i = 0; i < GetNumDevices(); i++) { + if (aDeviceInfo[i].bSelected == true) { + break; + } + } + filterIndex = i + 1; + return i; +} + +/* + * Gets index of next filtered device + */ +int ALDeviceList::GetNextFilteredDevice() +{ + unsigned int i; + + for (i = filterIndex; i < GetNumDevices(); i++) { + if (aDeviceInfo[i].bSelected == true) { + break; + } + } + filterIndex = i + 1; + return i; +} + +/* + * Internal function to detemine max number of Sources that can be generated + */ +unsigned int ALDeviceList::GetMaxNumSources() +{ + ALuint uiSources[256]; + unsigned int iSourceCount = 0; + + // Clear AL Error Code + alGetError(); + + // Generate up to 256 Sources, checking for any errors + for (iSourceCount = 0; iSourceCount < 256; iSourceCount++) + { + alGenSources(1, &uiSources[iSourceCount]); + if (alGetError() != AL_NO_ERROR) + break; + } + + // Release the Sources + alDeleteSources(iSourceCount, uiSources); + if (alGetError() != AL_NO_ERROR) + { + for (unsigned int i = 0; i < 256; i++) + { + alDeleteSources(1, &uiSources[i]); + } + } + + return iSourceCount; +} +#endif diff --git a/src/audio/oal/aldlist.h b/src/audio/oal/aldlist.h new file mode 100644 index 0000000..3ed12d8 --- /dev/null +++ b/src/audio/oal/aldlist.h @@ -0,0 +1,82 @@ +#ifndef ALDEVICELIST_H +#define ALDEVICELIST_H + +#include "oal_utils.h" + +#ifdef AUDIO_OAL +#pragma warning(disable: 4786) //disable warning "identifier was truncated to '255' characters in the browser information" + +enum +{ + ADEXT_EXT_CAPTURE = (1 << 0), + ADEXT_EXT_EFX = (1 << 1), + ADEXT_EXT_OFFSET = (1 << 2), + ADEXT_EXT_LINEAR_DISTANCE = (1 << 3), + ADEXT_EXT_EXPONENT_DISTANCE = (1 << 4), + ADEXT_EAX2 = (1 << 5), + ADEXT_EAX3 = (1 << 6), + ADEXT_EAX4 = (1 << 7), + ADEXT_EAX5 = (1 << 8), + ADEXT_EAX_RAM = (1 << 9), +}; + +struct ALDEVICEINFO { + char *strDeviceName; + int iMajorVersion; + int iMinorVersion; + unsigned int uiSourceCount; + unsigned short Extensions; + bool bSelected; + + ALDEVICEINFO() : iMajorVersion(0), iMinorVersion(0), uiSourceCount(0), bSelected(false) + { + strDeviceName = NULL; + Extensions = 0; + } + + ~ALDEVICEINFO() + { + delete[] strDeviceName; + strDeviceName = NULL; + } + + void SetName(const char *name) + { + if(strDeviceName) delete[] strDeviceName; + strDeviceName = new char[strlen(name) + 1]; + strcpy(strDeviceName, name); + } +}; + +typedef ALDEVICEINFO *LPALDEVICEINFO; + +class ALDeviceList +{ +private: + ALDEVICEINFO aDeviceInfo[64]; + unsigned int nNumOfDevices; + int defaultDeviceIndex; + int filterIndex; + +public: + ALDeviceList (); + ~ALDeviceList (); + unsigned int GetNumDevices(); + const char *GetDeviceName(unsigned int index); + void GetDeviceVersion(unsigned int index, int *major, int *minor); + unsigned int GetMaxNumSources(unsigned int index); + bool IsExtensionSupported(int index, unsigned short ext); + int GetDefaultDevice(); + void FilterDevicesMinVer(int major, int minor); + void FilterDevicesMaxVer(int major, int minor); + void FilterDevicesExtension(unsigned short ext); + void ResetFilters(); + int GetFirstFilteredDevice(); + int GetNextFilteredDevice(); + +private: + unsigned int GetMaxNumSources(); +}; +#endif + +#endif // ALDEVICELIST_H diff --git a/src/audio/oal/channel.cpp b/src/audio/oal/channel.cpp new file mode 100644 index 0000000..04e7e52 --- /dev/null +++ b/src/audio/oal/channel.cpp @@ -0,0 +1,295 @@ +#include "common.h" + +#ifdef AUDIO_OAL +#include "channel.h" +#include "sampman.h" + +#ifndef _WIN32 +#include +#endif + +extern bool IsFXSupported(); + +ALuint alSources[NUM_CHANNELS]; +ALuint alFilters[NUM_CHANNELS]; +ALuint alBuffers[NUM_CHANNELS]; +bool bChannelsCreated = false; + +int32 CChannel::channelsThatNeedService = 0; + +uint8 tempStereoBuffer[PED_BLOCKSIZE * 2]; + +void +CChannel::InitChannels() +{ + alGenSources(NUM_CHANNELS, alSources); + alGenBuffers(NUM_CHANNELS, alBuffers); + if (IsFXSupported()) + alGenFilters(NUM_CHANNELS, alFilters); + bChannelsCreated = true; +} + +void +CChannel::DestroyChannels() +{ + if (bChannelsCreated) + { + alDeleteSources(NUM_CHANNELS, alSources); + memset(alSources, 0, sizeof(alSources)); + alDeleteBuffers(NUM_CHANNELS, alBuffers); + memset(alBuffers, 0, sizeof(alBuffers)); + if (IsFXSupported()) + { + alDeleteFilters(NUM_CHANNELS, alFilters); + memset(alFilters, 0, sizeof(alFilters)); + } + bChannelsCreated = false; + } +} + + +CChannel::CChannel() +{ + Data = nil; + DataSize = 0; + bIs2D = false; + SetDefault(); +} + +void CChannel::SetDefault() +{ + Pitch = 1.0f; + Gain = 1.0f; + Mix = 0.0f; + + Position[0] = 0.0f; Position[1] = 0.0f; Position[2] = 0.0f; + Distances[0] = 0.0f; Distances[1] = FLT_MAX; + + LoopCount = 1; + LastProcessedOffset = UINT32_MAX; + LoopPoints[0] = 0; LoopPoints[1] = -1; + + Frequency = MAX_FREQ; +} + +void CChannel::Reset() +{ + // Here is safe because ctor don't call this + if (LoopCount > 1) + channelsThatNeedService--; + + ClearBuffer(); + SetDefault(); +} + +void CChannel::Init(uint32 _id, bool Is2D) +{ + id = _id; + if ( HasSource() ) + { + alSourcei(alSources[id], AL_SOURCE_RELATIVE, AL_TRUE); + if ( IsFXSupported() ) + alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); + + if ( Is2D ) + { + bIs2D = true; + alSource3f(alSources[id], AL_POSITION, 0.0f, 0.0f, 0.0f); + alSourcef(alSources[id], AL_GAIN, 1.0f); + } + } +} + +void CChannel::Term() +{ + Stop(); + if ( HasSource() ) + { + if ( IsFXSupported() ) + { + alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL); + } + } +} + +void CChannel::Start() +{ + if ( !HasSource() ) return; + if ( !Data ) return; + + if ( bIs2D ) + { + // convert mono data to stereo + int16 *monoData = (int16*)Data; + int16 *stereoData = (int16*)tempStereoBuffer; + for (size_t i = 0; i < DataSize / 2; i++) + { + *(stereoData++) = *monoData; + *(stereoData++) = *(monoData++); + } + alBufferData(alBuffers[id], AL_FORMAT_STEREO16, tempStereoBuffer, DataSize * 2, Frequency); + } + else + alBufferData(alBuffers[id], AL_FORMAT_MONO16, Data, DataSize, Frequency); + if ( LoopPoints[0] != 0 && LoopPoints[0] != -1 ) + alBufferiv(alBuffers[id], AL_LOOP_POINTS_SOFT, LoopPoints); + alSourcei(alSources[id], AL_BUFFER, alBuffers[id]); + alSourcePlay(alSources[id]); +} + +void CChannel::Stop() +{ + if ( HasSource() ) + alSourceStop(alSources[id]); + + Reset(); +} + +bool CChannel::HasSource() +{ + return alSources[id] != AL_NONE; +} + +bool CChannel::IsUsed() +{ + if ( HasSource() ) + { + ALint sourceState; + alGetSourcei(alSources[id], AL_SOURCE_STATE, &sourceState); + return sourceState == AL_PLAYING; + } + return false; +} + +void CChannel::SetPitch(float pitch) +{ + if ( !HasSource() ) return; + alSourcef(alSources[id], AL_PITCH, pitch); +} + +void CChannel::SetGain(float gain) +{ + if ( !HasSource() ) return; + alSourcef(alSources[id], AL_GAIN, gain); +} + +void CChannel::SetVolume(int32 vol) +{ + SetGain(ALfloat(vol) / MAX_VOLUME); +} + +void CChannel::SetSampleData(void *_data, size_t _DataSize, int32 freq) +{ + Data = _data; + DataSize = _DataSize; + Frequency = freq; +} + +void CChannel::SetCurrentFreq(uint32 freq) +{ + SetPitch(ALfloat(freq) / Frequency); +} + +void CChannel::SetLoopCount(int32 count) +{ + if ( !HasSource() ) return; + + // 0: loop indefinitely, 1: play one time, 2: play two times etc... + // only > 1 needs manual processing + + if (LoopCount > 1 && count < 2) + channelsThatNeedService--; + else if (LoopCount < 2 && count > 1) + channelsThatNeedService++; + + alSourcei(alSources[id], AL_LOOPING, count == 1 ? AL_FALSE : AL_TRUE); + LoopCount = count; +} + +bool CChannel::Update() +{ + if (!HasSource()) return false; + if (LoopCount < 2) return false; + + ALint state; + alGetSourcei(alSources[id], AL_SOURCE_STATE, &state); + if (state == AL_STOPPED) { + debug("Looping channels(%d in this case) shouldn't report AL_STOPPED, but nvm\n", id); + SetLoopCount(1); + return true; + } + + assert(channelsThatNeedService > 0 && "Ref counting is broken"); + + ALint offset; + alGetSourcei(alSources[id], AL_SAMPLE_OFFSET, &offset); + + // Rewound + if (offset < LastProcessedOffset) { + LoopCount--; + if (LoopCount == 1) { + // Playing last tune... + channelsThatNeedService--; + alSourcei(alSources[id], AL_LOOPING, AL_FALSE); + } + } + LastProcessedOffset = offset; + return true; +} + +void CChannel::SetLoopPoints(ALint start, ALint end) +{ + LoopPoints[0] = start; + LoopPoints[1] = end; +} + +void CChannel::SetPosition(float x, float y, float z) +{ + if ( !HasSource() ) return; + alSource3f(alSources[id], AL_POSITION, x, y, z); +} + +void CChannel::SetDistances(float max, float min) +{ + if ( !HasSource() ) return; + alSourcef (alSources[id], AL_MAX_DISTANCE, max); + alSourcef (alSources[id], AL_REFERENCE_DISTANCE, min); + alSourcef (alSources[id], AL_MAX_GAIN, 1.0f); + alSourcef (alSources[id], AL_ROLLOFF_FACTOR, 1.0f); +} + +void CChannel::SetPan(int32 pan) +{ + SetPosition((pan-63)/64.0f, 0.0f, Sqrt(1.0f-SQR((pan-63)/64.0f))); +} + +void CChannel::ClearBuffer() +{ + if ( !HasSource() ) return; + alSourcei(alSources[id], AL_LOOPING, AL_FALSE); + alSourcei(alSources[id], AL_BUFFER, AL_NONE); + Data = nil; + DataSize = 0; +} + +void CChannel::SetReverbMix(ALuint slot, float mix) +{ + if ( !IsFXSupported() ) return; + if ( !HasSource() ) return; + if ( alFilters[id] == AL_FILTER_NULL ) return; + + Mix = mix; + EAX3_SetReverbMix(alFilters[id], mix); + alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, slot, 0, alFilters[id]); +} + +void CChannel::UpdateReverb(ALuint slot) +{ + if ( !IsFXSupported() ) return; + if ( !HasSource() ) return; + if ( alFilters[id] == AL_FILTER_NULL ) return; + EAX3_SetReverbMix(alFilters[id], Mix); + alSource3i(alSources[id], AL_AUXILIARY_SEND_FILTER, slot, 0, alFilters[id]); +} + +#endif diff --git a/src/audio/oal/channel.h b/src/audio/oal/channel.h new file mode 100644 index 0000000..872646c --- /dev/null +++ b/src/audio/oal/channel.h @@ -0,0 +1,55 @@ +#pragma once + +#ifdef AUDIO_OAL +#include "oal/oal_utils.h" +#include +#include +#include + + +class CChannel +{ + uint32 id; + float Pitch, Gain; + float Mix; + void *Data; + size_t DataSize; + int32 Frequency; + float Position[3]; + float Distances[2]; + int32 LoopCount; + ALint LoopPoints[2]; + ALint LastProcessedOffset; + bool bIs2D; +public: + static int32 channelsThatNeedService; + + static void InitChannels(); + static void DestroyChannels(); + + CChannel(); + void SetDefault(); + void Reset(); + void Init(uint32 _id, bool Is2D = false); + void Term(); + void Start(); + void Stop(); + bool HasSource(); + bool IsUsed(); + void SetPitch(float pitch); + void SetGain(float gain); + void SetVolume(int32 vol); + void SetSampleData(void *_data, size_t _DataSize, int32 freq); + void SetCurrentFreq(uint32 freq); + void SetLoopCount(int32 count); + void SetLoopPoints(ALint start, ALint end); + void SetPosition(float x, float y, float z); + void SetDistances(float max, float min); + void SetPan(int32 pan); + void ClearBuffer(); + void SetReverbMix(ALuint slot, float mix); + void UpdateReverb(ALuint slot); + bool Update(); +}; + +#endif \ No newline at end of file diff --git a/src/audio/oal/oal_utils.cpp b/src/audio/oal/oal_utils.cpp new file mode 100644 index 0000000..e4cb0b7 --- /dev/null +++ b/src/audio/oal/oal_utils.cpp @@ -0,0 +1,181 @@ +#include "common.h" +#include "oal_utils.h" + +#ifdef AUDIO_OAL + +/* + * When linking to a static openal-soft library, + * the extension function inside the openal library conflict with the variables here. + * Therefore declare these re3 owned symbols in a private namespace. + */ + +namespace re3_openal { + +LPALGENEFFECTS alGenEffects; +LPALDELETEEFFECTS alDeleteEffects; +LPALISEFFECT alIsEffect; +LPALEFFECTI alEffecti; +LPALEFFECTIV alEffectiv; +LPALEFFECTF alEffectf; +LPALEFFECTFV alEffectfv; +LPALGETEFFECTI alGetEffecti; +LPALGETEFFECTIV alGetEffectiv; +LPALGETEFFECTF alGetEffectf; +LPALGETEFFECTFV alGetEffectfv; +LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; +LPALGENFILTERS alGenFilters; +LPALDELETEFILTERS alDeleteFilters; +LPALISFILTER alIsFilter; +LPALFILTERI alFilteri; +LPALFILTERIV alFilteriv; +LPALFILTERF alFilterf; +LPALFILTERFV alFilterfv; +LPALGETFILTERI alGetFilteri; +LPALGETFILTERIV alGetFilteriv; +LPALGETFILTERF alGetFilterf; +LPALGETFILTERFV alGetFilterfv; + +} + +using namespace re3_openal; + +void EFXInit() +{ + /* Define a macro to help load the function pointers. */ +#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x)) + LOAD_PROC(LPALGENEFFECTS, alGenEffects); + LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); + LOAD_PROC(LPALISEFFECT, alIsEffect); + LOAD_PROC(LPALEFFECTI, alEffecti); + LOAD_PROC(LPALEFFECTIV, alEffectiv); + LOAD_PROC(LPALEFFECTF, alEffectf); + LOAD_PROC(LPALEFFECTFV, alEffectfv); + LOAD_PROC(LPALGETEFFECTI, alGetEffecti); + LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); + LOAD_PROC(LPALGETEFFECTF, alGetEffectf); + LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); + + LOAD_PROC(LPALGENFILTERS, alGenFilters); + LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters); + LOAD_PROC(LPALISFILTER, alIsFilter); + LOAD_PROC(LPALFILTERI, alFilteri); + LOAD_PROC(LPALFILTERIV, alFilteriv); + LOAD_PROC(LPALFILTERF, alFilterf); + LOAD_PROC(LPALFILTERFV, alFilterfv); + LOAD_PROC(LPALGETFILTERI, alGetFilteri); + LOAD_PROC(LPALGETFILTERIV, alGetFilteriv); + LOAD_PROC(LPALGETFILTERF, alGetFilterf); + LOAD_PROC(LPALGETFILTERFV, alGetFilterfv); + + LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); + LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); + LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); +#undef LOAD_PROC +} + +void SetEffectsLevel(ALuint uiFilter, float level) +{ + alFilteri(uiFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + alFilterf(uiFilter, AL_LOWPASS_GAIN, 1.0f); + alFilterf(uiFilter, AL_LOWPASS_GAINHF, level); +} + +static inline float gain_to_mB(float gain) +{ + return (gain > 1e-5f) ? (float)(log10f(gain) * 2000.0f) : -10000l; +} + +static inline float mB_to_gain(float millibels) +{ + return (millibels > -10000.0f) ? powf(10.0f, millibels/2000.0f) : 0.0f; +} + +static inline float clampF(float val, float minval, float maxval) +{ + if(val >= maxval) return maxval; + if(val <= minval) return minval; + return val; +} + +void EAX3_Set(ALuint effect, const EAXLISTENERPROPERTIES *props) +{ + alEffecti (effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + alEffectf (effect, AL_EAXREVERB_DENSITY, clampF(powf(props->flEnvironmentSize, 3.0f) / 16.0f, 0.0f, 1.0f)); + alEffectf (effect, AL_EAXREVERB_DIFFUSION, props->flEnvironmentDiffusion); + alEffectf (effect, AL_EAXREVERB_GAIN, mB_to_gain((float)props->lRoom)); + alEffectf (effect, AL_EAXREVERB_GAINHF, mB_to_gain((float)props->lRoomHF)); + alEffectf (effect, AL_EAXREVERB_GAINLF, mB_to_gain((float)props->lRoomLF)); + alEffectf (effect, AL_EAXREVERB_DECAY_TIME, props->flDecayTime); + alEffectf (effect, AL_EAXREVERB_DECAY_HFRATIO, props->flDecayHFRatio); + alEffectf (effect, AL_EAXREVERB_DECAY_LFRATIO, props->flDecayLFRatio); + alEffectf (effect, AL_EAXREVERB_REFLECTIONS_GAIN, clampF(mB_to_gain((float)props->lReflections), AL_EAXREVERB_MIN_REFLECTIONS_GAIN, AL_EAXREVERB_MAX_REFLECTIONS_GAIN)); + alEffectf (effect, AL_EAXREVERB_REFLECTIONS_DELAY, props->flReflectionsDelay); + alEffectfv(effect, AL_EAXREVERB_REFLECTIONS_PAN, &props->vReflectionsPan.x); + alEffectf (effect, AL_EAXREVERB_LATE_REVERB_GAIN, clampF(mB_to_gain((float)props->lReverb), AL_EAXREVERB_MIN_LATE_REVERB_GAIN, AL_EAXREVERB_MAX_LATE_REVERB_GAIN)); + alEffectf (effect, AL_EAXREVERB_LATE_REVERB_DELAY, props->flReverbDelay); + alEffectfv(effect, AL_EAXREVERB_LATE_REVERB_PAN, &props->vReverbPan.x); + alEffectf (effect, AL_EAXREVERB_ECHO_TIME, props->flEchoTime); + alEffectf (effect, AL_EAXREVERB_ECHO_DEPTH, props->flEchoDepth); + alEffectf (effect, AL_EAXREVERB_MODULATION_TIME, props->flModulationTime); + alEffectf (effect, AL_EAXREVERB_MODULATION_DEPTH, props->flModulationDepth); + alEffectf (effect, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, clampF(mB_to_gain(props->flAirAbsorptionHF), AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)); + alEffectf (effect, AL_EAXREVERB_HFREFERENCE, props->flHFReference); + alEffectf (effect, AL_EAXREVERB_LFREFERENCE, props->flLFReference); + alEffectf (effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, props->flRoomRolloffFactor); + alEffecti (effect, AL_EAXREVERB_DECAY_HFLIMIT, (props->ulFlags&EAXLISTENERFLAGS_DECAYHFLIMIT) ? AL_TRUE : AL_FALSE); +} + +void EFX_Set(ALuint effect, const EAXLISTENERPROPERTIES *props) +{ + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); + + alEffectf(effect, AL_REVERB_DENSITY, clampF(powf(props->flEnvironmentSize, 3.0f) / 16.0f, 0.0f, 1.0f)); + alEffectf(effect, AL_REVERB_DIFFUSION, props->flEnvironmentDiffusion); + alEffectf(effect, AL_REVERB_GAIN, mB_to_gain((float)props->lRoom)); + alEffectf(effect, AL_REVERB_GAINHF, mB_to_gain((float)props->lRoomHF)); + alEffectf(effect, AL_REVERB_DECAY_TIME, props->flDecayTime); + alEffectf(effect, AL_REVERB_DECAY_HFRATIO, props->flDecayHFRatio); + alEffectf(effect, AL_REVERB_REFLECTIONS_GAIN, clampF(mB_to_gain((float)props->lReflections), AL_EAXREVERB_MIN_REFLECTIONS_GAIN, AL_EAXREVERB_MAX_REFLECTIONS_GAIN)); + alEffectf(effect, AL_REVERB_REFLECTIONS_DELAY, props->flReflectionsDelay); + alEffectf(effect, AL_REVERB_LATE_REVERB_GAIN, clampF(mB_to_gain((float)props->lReverb), AL_EAXREVERB_MIN_LATE_REVERB_GAIN, AL_EAXREVERB_MAX_LATE_REVERB_GAIN)); + alEffectf(effect, AL_REVERB_LATE_REVERB_DELAY, props->flReverbDelay); + alEffectf(effect, AL_REVERB_AIR_ABSORPTION_GAINHF, clampF(mB_to_gain(props->flAirAbsorptionHF), AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)); + alEffectf(effect, AL_REVERB_ROOM_ROLLOFF_FACTOR, props->flRoomRolloffFactor); + alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, (props->ulFlags&EAXLISTENERFLAGS_DECAYHFLIMIT) ? AL_TRUE : AL_FALSE); +} + +void EAX3_SetReverbMix(ALuint filter, float mix) +{ + //long vol=(long)linear_to_dB(mix); + //DSPROPERTY_EAXBUFFER_ROOMHF, + //DSPROPERTY_EAXBUFFER_ROOM, + //DSPROPERTY_EAXBUFFER_REVERBMIX, + + long mbvol = gain_to_mB(mix); + float mb = mbvol; + float mbhf = mbvol; + + alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS); + alFilterf(filter, AL_LOWPASS_GAIN, mB_to_gain(Min(mb, 0.0f))); + alFilterf(filter, AL_LOWPASS_GAINHF, mB_to_gain(mbhf)); +} + +#endif \ No newline at end of file diff --git a/src/audio/oal/oal_utils.h b/src/audio/oal/oal_utils.h new file mode 100644 index 0000000..f0fa090 --- /dev/null +++ b/src/audio/oal/oal_utils.h @@ -0,0 +1,54 @@ +#pragma once + +#ifdef AUDIO_OAL +#include "eax.h" +#include "AL/efx.h" + + +void EFXInit(); +void EAX3_Set(ALuint effect, const EAXLISTENERPROPERTIES *props); +void EFX_Set(ALuint effect, const EAXLISTENERPROPERTIES *props); +void EAX3_SetReverbMix(ALuint filter, float mix); +void SetEffectsLevel(ALuint uiFilter, float level); + +namespace re3_openal { + +extern LPALGENEFFECTS alGenEffects; +extern LPALDELETEEFFECTS alDeleteEffects; +extern LPALISEFFECT alIsEffect; +extern LPALEFFECTI alEffecti; +extern LPALEFFECTIV alEffectiv; +extern LPALEFFECTF alEffectf; +extern LPALEFFECTFV alEffectfv; +extern LPALGETEFFECTI alGetEffecti; +extern LPALGETEFFECTIV alGetEffectiv; +extern LPALGETEFFECTF alGetEffectf; +extern LPALGETEFFECTFV alGetEffectfv; +extern LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +extern LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +extern LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +extern LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +extern LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +extern LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +extern LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +extern LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +extern LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +extern LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +extern LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; +extern LPALGENFILTERS alGenFilters; +extern LPALDELETEFILTERS alDeleteFilters; +extern LPALISFILTER alIsFilter; +extern LPALFILTERI alFilteri; +extern LPALFILTERIV alFilteriv; +extern LPALFILTERF alFilterf; +extern LPALFILTERFV alFilterfv; +extern LPALGETFILTERI alGetFilteri; +extern LPALGETFILTERIV alGetFilteriv; +extern LPALGETFILTERF alGetFilterf; +extern LPALGETFILTERFV alGetFilterfv; + +} + +using namespace re3_openal; + +#endif diff --git a/src/audio/oal/stream.cpp b/src/audio/oal/stream.cpp new file mode 100644 index 0000000..6afe8e3 --- /dev/null +++ b/src/audio/oal/stream.cpp @@ -0,0 +1,1759 @@ +#include "common.h" + +#ifdef AUDIO_OAL + +#if defined _MSC_VER && !defined CMAKE_NO_AUTOLINK +#ifdef AUDIO_OAL_USE_SNDFILE +#pragma comment( lib, "libsndfile-1.lib" ) +#endif +#ifdef AUDIO_OAL_USE_MPG123 +#pragma comment( lib, "libmpg123-0.lib" ) +#endif +#endif +#ifdef AUDIO_OAL_USE_SNDFILE +#include +#endif +#ifdef AUDIO_OAL_USE_MPG123 +#include +#endif +#ifdef AUDIO_OAL_USE_OPUS +#include +#endif + +#include +#include + +#ifdef MULTITHREADED_AUDIO +#include +#include +#include +#include +#include "MusicManager.h" +#include "stream.h" + +std::thread gAudioThread; +std::mutex gAudioThreadQueueMutex; +std::condition_variable gAudioThreadCv; +bool gAudioThreadTerm = false; +std::queue gStreamsToProcess; // values are not unique, we will handle that ourself +std::queue> gStreamsToClose; +#else +#include "stream.h" +#endif + +#include "sampman.h" + +#ifndef _WIN32 +#include "crossplatform.h" +#endif + +/* +As we ran onto an issue of having different volume levels for mono streams +and stereo streams we are now handling all the stereo panning ourselves. +Each stream now has two sources - one panned to the left and one to the right, +and uses two separate buffers to store data for each individual channel. +For that we also have to reshuffle all decoded PCM stereo data from LRLRLRLR to +LLLLRRRR (handled by CSortStereoBuffer). +*/ + +class CSortStereoBuffer +{ + uint16* PcmBuf; + size_t BufSize; +//#ifdef MULTITHREADED_AUDIO +// std::mutex Mutex; +//#endif + +public: + CSortStereoBuffer() : PcmBuf(nil), BufSize(0) {} + ~CSortStereoBuffer() + { + if (PcmBuf) + free(PcmBuf); + } + + uint16* GetBuffer(size_t size) + { + if (size == 0) return nil; + if (!PcmBuf) + { + BufSize = size; + PcmBuf = (uint16*)malloc(BufSize); + } + else if (BufSize < size) + { + BufSize = size; + PcmBuf = (uint16*)realloc(PcmBuf, size); + } + return PcmBuf; + } + + void SortStereo(void* buf, size_t size) + { +//#ifdef MULTITHREADED_AUDIO +// std::lock_guard lock(Mutex); +//#endif + uint16* InBuf = (uint16*)buf; + uint16* OutBuf = GetBuffer(size); + + if (!OutBuf) return; + + size_t rightStart = size / 4; + for (size_t i = 0; i < size / 4; i++) + { + OutBuf[i] = InBuf[i*2]; + OutBuf[i+rightStart] = InBuf[i*2+1]; + } + + memcpy(InBuf, OutBuf, size); + } + +}; + +CSortStereoBuffer SortStereoBuffer; + +class CImaADPCMDecoder +{ + const uint16 StepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767 + }; + + int16 Sample, StepIndex; + +public: + CImaADPCMDecoder() + { + Init(0, 0); + } + + void Init(int16 _Sample, int16 _StepIndex) + { + Sample = _Sample; + StepIndex = _StepIndex; + } + + void Decode(uint8 *inbuf, int16 *_outbuf, size_t size) + { + int16* outbuf = _outbuf; + for (size_t i = 0; i < size; i++) + { + *(outbuf++) = DecodeSample(inbuf[i] & 0xF); + *(outbuf++) = DecodeSample(inbuf[i] >> 4); + } + } + + int16 DecodeSample(uint8 adpcm) + { + uint16 step = StepTable[StepIndex]; + + if (adpcm & 4) + StepIndex += ((adpcm & 3) + 1) * 2; + else + StepIndex--; + + StepIndex = Clamp(StepIndex, 0, 88); + + int delta = step >> 3; + if (adpcm & 1) delta += step >> 2; + if (adpcm & 2) delta += step >> 1; + if (adpcm & 4) delta += step; + if (adpcm & 8) delta = -delta; + + int newSample = Sample + delta; + Sample = Clamp(newSample, -32768, 32767); + return Sample; + } +}; + +class CWavFile : public IDecoder +{ + enum + { + WAVEFMT_PCM = 1, + WAVEFMT_IMA_ADPCM = 0x11, + WAVEFMT_XBOX_ADPCM = 0x69, + }; + + struct tDataHeader + { + uint32 ID; + uint32 Size; + }; + + struct tFormatHeader + { + uint16 AudioFormat; + uint16 NumChannels; + uint32 SampleRate; + uint32 ByteRate; + uint16 BlockAlign; + uint16 BitsPerSample; + uint16 extra[2]; // adpcm only + + tFormatHeader() { memset(this, 0, sizeof(*this)); } + }; + + FILE *m_pFile; + bool m_bIsOpen; + + tFormatHeader m_FormatHeader; + + uint32 m_DataStartOffset; // TODO: 64 bit? + uint32 m_nSampleCount; + uint32 m_nSamplesPerBlock; + + // ADPCM things + uint8 *m_pAdpcmBuffer; + int16 **m_ppPcmBuffers; + CImaADPCMDecoder *m_pAdpcmDecoders; + + void Close() + { + if (m_pFile) { + fclose(m_pFile); + m_pFile = nil; + } + delete[] m_pAdpcmBuffer; + delete[] m_ppPcmBuffers; + delete[] m_pAdpcmDecoders; + } + + uint32 GetCurrentSample() const + { + // TODO: 64 bit? + uint32 FilePos = ftell(m_pFile); + if (FilePos <= m_DataStartOffset) + return 0; + return (FilePos - m_DataStartOffset) / m_FormatHeader.BlockAlign * m_nSamplesPerBlock; + } + +public: + CWavFile(const char* path) : m_bIsOpen(false), m_DataStartOffset(0), m_nSampleCount(0), m_nSamplesPerBlock(0), m_pAdpcmBuffer(nil), m_ppPcmBuffers(nil), m_pAdpcmDecoders(nil) + { + m_pFile = fopen(path, "rb"); + if (!m_pFile) return; + +#define CLOSE_ON_ERROR(op)\ + if (op) { \ + Close(); \ + return; \ + } + + tDataHeader DataHeader; + + CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0); + CLOSE_ON_ERROR(DataHeader.ID != 'FFIR'); + + // TODO? validate filesizes + + int WAVE; + CLOSE_ON_ERROR(fread(&WAVE, 4, 1, m_pFile) == 0); + CLOSE_ON_ERROR(WAVE != 'EVAW') + CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0); + CLOSE_ON_ERROR(DataHeader.ID != ' tmf'); + + CLOSE_ON_ERROR(fread(&m_FormatHeader, Min(DataHeader.Size, sizeof(tFormatHeader)), 1, m_pFile) == 0); + CLOSE_ON_ERROR(DataHeader.Size > sizeof(tFormatHeader)); + + switch (m_FormatHeader.AudioFormat) + { + case WAVEFMT_XBOX_ADPCM: + m_FormatHeader.AudioFormat = WAVEFMT_IMA_ADPCM; + case WAVEFMT_IMA_ADPCM: + m_nSamplesPerBlock = (m_FormatHeader.BlockAlign / m_FormatHeader.NumChannels - 4) * 2 + 1; + m_pAdpcmBuffer = new uint8[m_FormatHeader.BlockAlign]; + m_ppPcmBuffers = new int16*[m_FormatHeader.NumChannels]; + m_pAdpcmDecoders = new CImaADPCMDecoder[m_FormatHeader.NumChannels]; + break; + case WAVEFMT_PCM: + m_nSamplesPerBlock = 1; + if (m_FormatHeader.BitsPerSample != 16) + { + debug("Unsupported PCM (%d bits), only signed 16-bit is supported (%s)\n", m_FormatHeader.BitsPerSample, path); + Close(); + return; + } + break; + default: + debug("Unsupported wav format 0x%x (%s)\n", m_FormatHeader.AudioFormat, path); + Close(); + return; + } + + while (true) { + CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0); + if (DataHeader.ID == 'atad') + break; + fseek(m_pFile, DataHeader.Size, SEEK_CUR); + // TODO? validate data size + // maybe check if there no extreme custom headers that might break this + } + + m_DataStartOffset = ftell(m_pFile); + m_nSampleCount = DataHeader.Size / m_FormatHeader.BlockAlign * m_nSamplesPerBlock; + + m_bIsOpen = true; +#undef CLOSE_ON_ERROR + } + + void FileOpen() + { + } + + ~CWavFile() + { + Close(); + } + + bool IsOpened() + { + return m_bIsOpen; + } + + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + return m_nSampleCount; + } + + uint32 GetSampleRate() + { + return m_FormatHeader.SampleRate; + } + + uint32 GetChannels() + { + return m_FormatHeader.NumChannels; + } + + void Seek(uint32 milliseconds) + { + if (!IsOpened()) return; + fseek(m_pFile, m_DataStartOffset + ms2samples(milliseconds) / m_nSamplesPerBlock * m_FormatHeader.BlockAlign, SEEK_SET); + } + + uint32 Tell() + { + if (!IsOpened()) return 0; + return samples2ms(GetCurrentSample()); + } + +#define SAMPLES_IN_LINE (8) + + uint32 Decode(void* buffer) + { + if (!IsOpened()) return 0; + + if (m_FormatHeader.AudioFormat == WAVEFMT_PCM) + { + // just read the file and sort the samples + uint32 size = fread(buffer, 1, GetBufferSize(), m_pFile); + if (m_FormatHeader.NumChannels == 2) + SortStereoBuffer.SortStereo(buffer, size); + return size; + } + else if (m_FormatHeader.AudioFormat == WAVEFMT_IMA_ADPCM) + { + // trim the buffer size if we're at the end of our file + uint32 nMaxSamples = GetBufferSamples() / m_FormatHeader.NumChannels; + uint32 nSamplesLeft = m_nSampleCount - GetCurrentSample(); + nMaxSamples = Min(nMaxSamples, nSamplesLeft); + + // align sample count to our block + nMaxSamples = nMaxSamples / m_nSamplesPerBlock * m_nSamplesPerBlock; + + // count the size of output buffer + uint32 OutBufSizePerChannel = nMaxSamples * GetSampleSize(); + uint32 OutBufSize = OutBufSizePerChannel * m_FormatHeader.NumChannels; + + // calculate the pointers to individual channel buffers + for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++) + m_ppPcmBuffers[i] = (int16*)((int8*)buffer + OutBufSizePerChannel * i); + + uint32 samplesRead = 0; + while (samplesRead < nMaxSamples) + { + // read the file + uint8 *pAdpcmBuf = m_pAdpcmBuffer; + if (fread(m_pAdpcmBuffer, 1, m_FormatHeader.BlockAlign, m_pFile) == 0) + return 0; + + // get the first sample in adpcm block and initialise the decoder(s) + for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++) + { + int16 Sample = *(int16*)pAdpcmBuf; + pAdpcmBuf += sizeof(int16); + int16 Step = *(int16*)pAdpcmBuf; + pAdpcmBuf += sizeof(int16); + m_pAdpcmDecoders[i].Init(Sample, Step); + *(m_ppPcmBuffers[i]) = Sample; + m_ppPcmBuffers[i]++; + } + samplesRead++; + + // decode the rest of the block + for (uint32 s = 1; s < m_nSamplesPerBlock; s += SAMPLES_IN_LINE) + { + for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++) + { + m_pAdpcmDecoders[i].Decode(pAdpcmBuf, m_ppPcmBuffers[i], SAMPLES_IN_LINE / 2); + pAdpcmBuf += SAMPLES_IN_LINE / 2; + m_ppPcmBuffers[i] += SAMPLES_IN_LINE; + } + samplesRead += SAMPLES_IN_LINE; + } + } + return OutBufSize; + } + return 0; + } +}; + +#ifdef AUDIO_OAL_USE_SNDFILE +class CSndFile : public IDecoder +{ + SNDFILE *m_pfSound; + SF_INFO m_soundInfo; +public: + CSndFile(const char *path) : + m_pfSound(nil) + { + memset(&m_soundInfo, 0, sizeof(m_soundInfo)); + m_pfSound = sf_open(path, SFM_READ, &m_soundInfo); + } + + void FileOpen() + { + } + + ~CSndFile() + { + if ( m_pfSound ) + { + sf_close(m_pfSound); + m_pfSound = nil; + } + } + + bool IsOpened() + { + return m_pfSound != nil; + } + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + return m_soundInfo.frames; + } + + uint32 GetSampleRate() + { + return m_soundInfo.samplerate; + } + + uint32 GetChannels() + { + return m_soundInfo.channels; + } + + void Seek(uint32 milliseconds) + { + if ( !IsOpened() ) return; + sf_seek(m_pfSound, ms2samples(milliseconds), SF_SEEK_SET); + } + + uint32 Tell() + { + if ( !IsOpened() ) return 0; + return samples2ms(sf_seek(m_pfSound, 0, SF_SEEK_CUR)); + } + + uint32 Decode(void *buffer) + { + if ( !IsOpened() ) return 0; + + size_t size = sf_read_short(m_pfSound, (short*)buffer, GetBufferSamples()) * GetSampleSize(); + if (GetChannels()==2) + SortStereoBuffer.SortStereo(buffer, size); + return size; + } +}; +#endif + +#ifdef AUDIO_OAL_USE_MPG123 + +class CMP3File : public IDecoder +{ +protected: + mpg123_handle *m_pMH; + bool m_bOpened; + uint32 m_nRate; + uint32 m_nChannels; + const char* m_pPath; + bool m_bFileNotOpenedYet; +public: + CMP3File(const char *path) : + m_pMH(nil), + m_bOpened(false), + m_nRate(0), + m_nChannels(0), + m_pPath(path), + m_bFileNotOpenedYet(false) + { + m_pMH = mpg123_new(nil, nil); + if ( m_pMH ) + { + mpg123_param(m_pMH, MPG123_FLAGS, MPG123_SEEKBUFFER | MPG123_GAPLESS, 0.0); + + m_bOpened = true; + m_bFileNotOpenedYet = true; + // It's possible to move this to audioFileOpsThread(), but effect isn't noticable + probably not compatible with our current cutscene audio handling +#if 1 + FileOpen(); +#endif + } + } + + void FileOpen() + { + if(!m_bFileNotOpenedYet) return; + + long rate = 0; + int channels = 0; + int encoding = 0; + m_bOpened = mpg123_open(m_pMH, m_pPath) == MPG123_OK + && mpg123_getformat(m_pMH, &rate, &channels, &encoding) == MPG123_OK; + + m_nRate = rate; + m_nChannels = channels; + + if(IsOpened()) { + mpg123_format_none(m_pMH); + mpg123_format(m_pMH, rate, channels, encoding); + } + m_bFileNotOpenedYet = false; + } + + ~CMP3File() + { + if ( m_pMH ) + { + mpg123_close(m_pMH); + mpg123_delete(m_pMH); + m_pMH = nil; + } + } + + bool IsOpened() + { + return m_bOpened; + } + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + if ( !IsOpened() || m_bFileNotOpenedYet ) return 0; + return mpg123_length(m_pMH); + } + + uint32 GetSampleRate() + { + return m_nRate; + } + + uint32 GetChannels() + { + return m_nChannels; + } + + void Seek(uint32 milliseconds) + { + if ( !IsOpened() || m_bFileNotOpenedYet ) return; + mpg123_seek(m_pMH, ms2samples(milliseconds), SEEK_SET); + } + + uint32 Tell() + { + if ( !IsOpened() || m_bFileNotOpenedYet ) return 0; + return samples2ms(mpg123_tell(m_pMH)); + } + + uint32 Decode(void *buffer) + { + if ( !IsOpened() || m_bFileNotOpenedYet ) return 0; + + size_t size; + int err = mpg123_read(m_pMH, (unsigned char *)buffer, GetBufferSize(), &size); +#if defined(__LP64__) || defined(_WIN64) + assert("We can't handle audio files more then 2 GB yet :shrug:" && (size < UINT32_MAX)); +#endif + if (err != MPG123_OK && err != MPG123_DONE) return 0; + if (GetChannels() == 2) + SortStereoBuffer.SortStereo(buffer, size); + return (uint32)size; + } +}; + +#endif +#define VAG_LINE_SIZE (0x10) +#define VAG_SAMPLES_IN_LINE (28) + +class CVagDecoder +{ + const double f[5][2] = { { 0.0, 0.0 }, + { 60.0 / 64.0, 0.0 }, + { 115.0 / 64.0, -52.0 / 64.0 }, + { 98.0 / 64.0, -55.0 / 64.0 }, + { 122.0 / 64.0, -60.0 / 64.0 } }; + + double s_1; + double s_2; +public: + CVagDecoder() + { + ResetState(); + } + + void ResetState() + { + s_1 = s_2 = 0.0; + } + + static short quantize(double sample) + { + int a = int(sample + 0.5); + return short(Clamp(a, -32768, 32767)); + } + + void Decode(void* _inbuf, int16* _outbuf, size_t size) + { + uint8* inbuf = (uint8*)_inbuf; + int16* outbuf = _outbuf; + size &= ~(VAG_LINE_SIZE - 1); + + while (size > 0) { + double samples[VAG_SAMPLES_IN_LINE]; + + int predict_nr, shift_factor, flags; + predict_nr = *(inbuf++); + shift_factor = predict_nr & 0xf; + predict_nr >>= 4; + flags = *(inbuf++); + if (flags == 7) // TODO: ignore? + break; + for (int i = 0; i < VAG_SAMPLES_IN_LINE; i += 2) { + int d = *(inbuf++); + int16 s = int16((d & 0xf) << 12); + samples[i] = (double)(s >> shift_factor); + s = int16((d & 0xf0) << 8); + samples[i + 1] = (double)(s >> shift_factor); + } + + for (int i = 0; i < VAG_SAMPLES_IN_LINE; i++) { + samples[i] = samples[i] + s_1 * f[predict_nr][0] + s_2 * f[predict_nr][1]; + s_2 = s_1; + s_1 = samples[i]; + *(outbuf++) = quantize(samples[i] + 0.5); + } + size -= VAG_LINE_SIZE; + } + } +}; + +#define VB_BLOCK_SIZE (0x2000) +#define NUM_VAG_LINES_IN_BLOCK (VB_BLOCK_SIZE / VAG_LINE_SIZE) +#define NUM_VAG_SAMPLES_IN_BLOCK (NUM_VAG_LINES_IN_BLOCK * VAG_SAMPLES_IN_LINE) + +class CVbFile : public IDecoder +{ + FILE *m_pFile; + CVagDecoder *m_pVagDecoders; + + size_t m_FileSize; + size_t m_nNumberOfBlocks; + + uint32 m_nSampleRate; + uint8 m_nChannels; + bool m_bBlockRead; + uint16 m_LineInBlock; + size_t m_CurrentBlock; + + uint8 **m_ppVagBuffers; // buffers that cache actual ADPCM file data + int16 **m_ppPcmBuffers; + + void ReadBlock(int32 block = -1) + { + // just read next block if -1 + if (block != -1) + fseek(m_pFile, block * m_nChannels * VB_BLOCK_SIZE, SEEK_SET); + + for (int i = 0; i < m_nChannels; i++) + fread(m_ppVagBuffers[i], VB_BLOCK_SIZE, 1, m_pFile); + m_bBlockRead = true; + } + +public: + CVbFile(const char* path, uint32 nSampleRate = 32000, uint8 nChannels = 2) : m_nSampleRate(nSampleRate), m_nChannels(nChannels), m_pVagDecoders(nil), m_ppVagBuffers(nil), m_ppPcmBuffers(nil), + m_FileSize(0), m_nNumberOfBlocks(0), m_bBlockRead(false), m_LineInBlock(0), m_CurrentBlock(0) + { + m_pFile = fopen(path, "rb"); + if (!m_pFile) return; + + fseek(m_pFile, 0, SEEK_END); + m_FileSize = ftell(m_pFile); + fseek(m_pFile, 0, SEEK_SET); + + m_nNumberOfBlocks = m_FileSize / (nChannels * VB_BLOCK_SIZE); + m_pVagDecoders = new CVagDecoder[nChannels]; + m_ppVagBuffers = new uint8*[nChannels]; + m_ppPcmBuffers = new int16*[nChannels]; + for (uint8 i = 0; i < nChannels; i++) + m_ppVagBuffers[i] = new uint8[VB_BLOCK_SIZE]; + } + + void FileOpen() + { + } + + ~CVbFile() + { + if (m_pFile) + { + fclose(m_pFile); + + delete[] m_pVagDecoders; + for (int i = 0; i < m_nChannels; i++) + delete[] m_ppVagBuffers[i]; + delete[] m_ppVagBuffers; + delete[] m_ppPcmBuffers; + } + } + + bool IsOpened() + { + return m_pFile != nil; + } + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + if (!IsOpened()) return 0; + return m_nNumberOfBlocks * NUM_VAG_LINES_IN_BLOCK * VAG_SAMPLES_IN_LINE; + } + + uint32 GetSampleRate() + { + return m_nSampleRate; + } + + uint32 GetChannels() + { + return m_nChannels; + } + + void Seek(uint32 milliseconds) + { + if (!IsOpened()) return; + uint32 samples = ms2samples(milliseconds); + + // find the block of our sample + uint32 block = samples / NUM_VAG_SAMPLES_IN_BLOCK; + if (block > m_nNumberOfBlocks) + { + samples = 0; + block = 0; + } + if (block != m_CurrentBlock) + m_bBlockRead = false; + + // find a line of our sample within our block + uint32 remainingSamples = samples - block * NUM_VAG_SAMPLES_IN_BLOCK; + uint32 newLine = remainingSamples / VAG_SAMPLES_IN_LINE / VAG_LINE_SIZE; + + if (m_CurrentBlock != block || m_LineInBlock != newLine) + { + m_CurrentBlock = block; + m_LineInBlock = newLine; + for (uint32 i = 0; i < GetChannels(); i++) + m_pVagDecoders[i].ResetState(); + } + + } + + uint32 Tell() + { + if (!IsOpened()) return 0; + uint32 pos = (m_CurrentBlock * NUM_VAG_LINES_IN_BLOCK + m_LineInBlock) * VAG_SAMPLES_IN_LINE; + return samples2ms(pos); + } + + uint32 Decode(void* buffer) + { + if (!IsOpened()) return 0; + + if (m_CurrentBlock >= m_nNumberOfBlocks) return 0; + + // cache current ADPCM block + if (!m_bBlockRead) + ReadBlock(m_CurrentBlock); + + // trim the buffer size if we're at the end of our file + int numberOfRequiredLines = GetBufferSamples() / m_nChannels / VAG_SAMPLES_IN_LINE; + int numberOfRemainingLines = (m_nNumberOfBlocks - m_CurrentBlock) * NUM_VAG_LINES_IN_BLOCK - m_LineInBlock; + int bufSizePerChannel = Min(numberOfRequiredLines, numberOfRemainingLines) * VAG_SAMPLES_IN_LINE * GetSampleSize(); + + // calculate the pointers to individual channel buffers + for (uint32 i = 0; i < m_nChannels; i++) + m_ppPcmBuffers[i] = (int16*)((int8*)buffer + bufSizePerChannel * i); + + int size = 0; + while (size < bufSizePerChannel) + { + // decode the VAG lines + for (uint32 i = 0; i < m_nChannels; i++) + { + m_pVagDecoders[i].Decode(m_ppVagBuffers[i] + m_LineInBlock * VAG_LINE_SIZE, m_ppPcmBuffers[i], VAG_LINE_SIZE); + m_ppPcmBuffers[i] += VAG_SAMPLES_IN_LINE; + } + size += VAG_SAMPLES_IN_LINE * GetSampleSize(); + m_LineInBlock++; + + // block is over, read the next block + if (m_LineInBlock >= NUM_VAG_LINES_IN_BLOCK) + { + m_CurrentBlock++; + if (m_CurrentBlock >= m_nNumberOfBlocks) // end of file + break; + m_LineInBlock = 0; + ReadBlock(); + } + } + + return bufSizePerChannel * m_nChannels; + } +}; +#ifdef AUDIO_OAL_USE_OPUS +class COpusFile : public IDecoder +{ + OggOpusFile *m_FileH; + bool m_bOpened; + uint32 m_nRate; + uint32 m_nChannels; +public: + COpusFile(const char *path) : m_FileH(nil), + m_bOpened(false), + m_nRate(0), + m_nChannels(0) + { + int ret; + m_FileH = op_open_file(path, &ret); + + if (m_FileH) { + m_nChannels = op_head(m_FileH, 0)->channel_count; + m_nRate = 48000; + const OpusTags *tags = op_tags(m_FileH, 0); + for (int i = 0; i < tags->comments; i++) { + if (strncmp(tags->user_comments[i], "SAMPLERATE", sizeof("SAMPLERATE")-1) == 0) + { + sscanf(tags->user_comments[i], "SAMPLERATE=%i", &m_nRate); + break; + } + } + + m_bOpened = true; + } + } + + void FileOpen() + { + } + + ~COpusFile() + { + if (m_FileH) + { + op_free(m_FileH); + m_FileH = nil; + } + } + + bool IsOpened() + { + return m_bOpened; + } + + uint32 GetSampleSize() + { + return sizeof(uint16); + } + + uint32 GetSampleCount() + { + if ( !IsOpened() ) return 0; + return op_pcm_total(m_FileH, 0); + } + + uint32 GetSampleRate() + { + return m_nRate; + } + + uint32 GetChannels() + { + return m_nChannels; + } + + void Seek(uint32 milliseconds) + { + if ( !IsOpened() ) return; + op_pcm_seek(m_FileH, ms2samples(milliseconds) / GetChannels()); + } + + uint32 Tell() + { + if ( !IsOpened() ) return 0; + return samples2ms(op_pcm_tell(m_FileH) * GetChannels()); + } + + uint32 Decode(void *buffer) + { + if ( !IsOpened() ) return 0; + + int size = op_read(m_FileH, (opus_int16 *)buffer, GetBufferSamples(), NULL); + + if (size < 0) + return 0; + + if (GetChannels() == 2) + SortStereoBuffer.SortStereo(buffer, size * m_nChannels * GetSampleSize()); + + return size * m_nChannels * GetSampleSize(); + } +}; +#endif + + +// For multi-thread: Someone always acquire stream's mutex before entering here +void +CStream::BuffersShouldBeFilled() +{ +#ifdef MULTITHREADED_AUDIO + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) { + std::queue> tempQueue; + for(int i = 0; i < NUM_STREAMBUFFERS / 2; i++) { + tempQueue.push(std::pair(m_alBuffers[i * 2], m_alBuffers[i * 2 + 1])); + } + m_fillBuffers.swap(tempQueue); + + FlagAsToBeProcessed(); + + m_bActive = true; // to allow Update() to queue the filled buffers & play + return; + } + std::queue>().swap(m_fillBuffers); +#endif + if ( FillBuffers() != 0 ) + { + SetPlay(true); + } +} + +// returns whether it's queued (not on multi-thread) +bool +CStream::BufferShouldBeFilledAndQueued(std::pair* bufs) +{ +#ifdef MULTITHREADED_AUDIO + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) + m_fillBuffers.push(*bufs); + else +#endif + { + ALuint alBuffers[2] = {(*bufs).first, (*bufs).second}; // left - right + if (FillBuffer(alBuffers)) { + alSourceQueueBuffers(m_pAlSources[0], 1, &alBuffers[0]); + alSourceQueueBuffers(m_pAlSources[1], 1, &alBuffers[1]); + return true; + } + } + return false; +} + +#ifdef MULTITHREADED_AUDIO +void +CStream::FlagAsToBeProcessed(bool close) +{ + if (!close && MusicManager.m_nMusicMode == MUSICMODE_CUTSCENE) + return; + + gAudioThreadQueueMutex.lock(); + if (close) + gStreamsToClose.push(std::pair(m_pSoundFile ? m_pSoundFile : nil, m_pBuffer ? m_pBuffer : nil)); + else + gStreamsToProcess.push(this); + + gAudioThreadQueueMutex.unlock(); + + gAudioThreadCv.notify_one(); +} + +void audioFileOpsThread() +{ + do + { + CStream *stream; + { + // Just a semaphore + std::unique_lock queueMutex(gAudioThreadQueueMutex); + gAudioThreadCv.wait(queueMutex, [] { return gStreamsToProcess.size() > 0 || gStreamsToClose.size() > 0 || gAudioThreadTerm; }); + if (gAudioThreadTerm) + return; + + if (!gStreamsToClose.empty()) { + auto streamToClose = gStreamsToClose.front(); + gStreamsToClose.pop(); + if (streamToClose.first) { // pSoundFile + delete streamToClose.first; + } + + if (streamToClose.second) { // pBuffer + free(streamToClose.second); + } + } + + if (!gStreamsToProcess.empty()) { + stream = gStreamsToProcess.front(); + gStreamsToProcess.pop(); + } else + continue; + } + + std::unique_lock lock(stream->m_mutex); + + std::pair buffers, *lastBufAddr; + bool insertBufsAfterCheck = false; + + do { + if (!stream->IsOpened()) { + break; + } + + if (stream->m_bReset) + break; + + // We gave up this idea for now + /* + stream->m_pSoundFile->FileOpen(); + + // Deffered allocation, do it now + if (stream->m_pBuffer == nil) { + stream->m_pBuffer = malloc(stream->m_pSoundFile->GetBufferSize()); + ASSERT(stream->m_pBuffer != nil); + } + */ + + if (stream->m_bDoSeek) { + stream->m_bDoSeek = false; + int pos = stream->m_SeekPos; + lock.unlock(); + stream->m_pSoundFile->Seek(pos); + lock.lock(); + + continue; // let's do the checks again, make sure we didn't miss anything while Seeking + } + + if (insertBufsAfterCheck) { + stream->m_queueBuffers.push(buffers); + insertBufsAfterCheck = false; + } + + if (!stream->m_fillBuffers.empty()) { + lastBufAddr = &stream->m_fillBuffers.front(); + buffers = *lastBufAddr; + lock.unlock(); + + ALuint alBuffers[2] = {buffers.first, buffers.second}; // left - right + bool filled = stream->FillBuffer(alBuffers); + + lock.lock(); + + // Make sure queue isn't touched after we released mutex + if (!stream->m_fillBuffers.empty() && lastBufAddr == &stream->m_fillBuffers.front()) { + stream->m_fillBuffers.pop(); + if (filled) + insertBufsAfterCheck = true; // Also make sure stream's properties aren't changed. So make one more pass, and push it to m_queueBuffers only if it pass checks again. + } + } else + break; + + } while (true); + + } while(true); +} +#endif + +void CStream::Initialise() +{ +#ifdef AUDIO_OAL_USE_MPG123 + mpg123_init(); +#endif +#ifdef MULTITHREADED_AUDIO + gAudioThread = std::thread(audioFileOpsThread); +#endif +} + +void CStream::Terminate() +{ +#ifdef AUDIO_OAL_USE_MPG123 + mpg123_exit(); +#endif +#ifdef MULTITHREADED_AUDIO + gAudioThreadQueueMutex.lock(); + gAudioThreadTerm = true; + gAudioThreadQueueMutex.unlock(); + + gAudioThreadCv.notify_one(); + gAudioThread.join(); +#endif +} + +CStream::CStream(ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS]) : + m_pAlSources(sources), + m_alBuffers(buffers), + m_pBuffer(nil), + m_bPaused(false), + m_bActive(false), +#ifdef MULTITHREADED_AUDIO + m_bIExist(false), + m_bDoSeek(false), + m_SeekPos(0), +#endif + m_pSoundFile(nil), + m_bReset(false), + m_nVolume(0), + m_nPan(0), + m_nPosBeforeReset(0), + m_nLoopCount(1) + +{ +} + +bool CStream::Open(const char* filename, uint32 overrideSampleRate) +{ + if (IsOpened()) return false; + +#ifdef MULTITHREADED_AUDIO + std::unique_lock lock(m_mutex); + + m_bDoSeek = false; + m_SeekPos = 0; +#endif + + m_bPaused = false; + m_bActive = false; + m_bReset = false; + m_nVolume = 0; + m_nPan = 0; + m_nPosBeforeReset = 0; + m_nLoopCount = 1; + +// Be case-insensitive on linux (from https://github.com/OneSadCookie/fcaseopen/) +#if !defined(_WIN32) + char *real = casepath(filename); + if (real) { + strcpy(m_aFilename, real); + free(real); + } else { +#else + { +#endif + strcpy(m_aFilename, filename); + } + + DEV("Stream %s\n", m_aFilename); + + if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".wav")], ".wav")) +#ifdef AUDIO_OAL_USE_SNDFILE + m_pSoundFile = new CSndFile(m_aFilename); +#else + m_pSoundFile = new CWavFile(m_aFilename); +#endif +#ifdef AUDIO_OAL_USE_MPG123 + else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".mp3")], ".mp3")) + m_pSoundFile = new CMP3File(m_aFilename); +#endif + else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".vb")], ".VB")) + m_pSoundFile = new CVbFile(m_aFilename, overrideSampleRate); +#ifdef AUDIO_OAL_USE_OPUS + else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".opus")], ".opus")) + m_pSoundFile = new COpusFile(m_aFilename); +#endif + else + m_pSoundFile = nil; + + if ( m_pSoundFile && m_pSoundFile->IsOpened() ) + { + uint32 bufSize = m_pSoundFile->GetBufferSize(); + if(bufSize != 0) { // Otherwise it's deferred + m_pBuffer = malloc(bufSize); + ASSERT(m_pBuffer != nil); + + DEV("AvgSamplesPerSec: %d\n", m_pSoundFile->GetAvgSamplesPerSec()); + DEV("SampleCount: %d\n", m_pSoundFile->GetSampleCount()); + DEV("SampleRate: %d\n", m_pSoundFile->GetSampleRate()); + DEV("Channels: %d\n", m_pSoundFile->GetChannels()); + DEV("Buffer Samples: %d\n", m_pSoundFile->GetBufferSamples()); + DEV("Buffer sec: %f\n", (float(m_pSoundFile->GetBufferSamples()) / float(m_pSoundFile->GetChannels())/ float(m_pSoundFile->GetSampleRate()))); + DEV("Length MS: %02d:%02d\n", (m_pSoundFile->GetLength() / 1000) / 60, (m_pSoundFile->GetLength() / 1000) % 60); + } +#ifdef MULTITHREADED_AUDIO + m_bIExist = true; +#endif + return true; + } + return false; +} + +CStream::~CStream() +{ + assert(!IsOpened()); +} + +void CStream::Close() +{ + if(!IsOpened()) return; + +#ifdef MULTITHREADED_AUDIO + { + std::lock_guard lock(m_mutex); + + Stop(); + ClearBuffers(); + m_bIExist = false; + std::queue>().swap(m_fillBuffers); + tsQueue>().swapNts(m_queueBuffers); // TSness not required, mutex is acquired + } + + FlagAsToBeProcessed(true); +#else + + Stop(); + ClearBuffers(); + + if ( m_pSoundFile ) + { + delete m_pSoundFile; + m_pSoundFile = nil; + } + + if ( m_pBuffer ) + { + free(m_pBuffer); + m_pBuffer = nil; + } +#endif +} + +bool CStream::HasSource() +{ + return (m_pAlSources[0] != AL_NONE) && (m_pAlSources[1] != AL_NONE); +} + +// m_bIExist only written in main thread, thus mutex is not needed on main thread +bool CStream::IsOpened() +{ +#ifdef MULTITHREADED_AUDIO + return m_bIExist; +#else + return m_pSoundFile && m_pSoundFile->IsOpened(); +#endif +} + +bool CStream::IsPlaying() +{ + if ( !HasSource() || !IsOpened() ) return false; + + if ( !m_bPaused ) + { + ALint sourceState[2]; + alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState[0]); + alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState[1]); + if (sourceState[0] == AL_PLAYING || sourceState[1] == AL_PLAYING) + return true; + +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + // Streams are designed in such a way that m_fillBuffers and m_queueBuffers will be *always* filled if audio is playing, and mutex is acquired + if (!m_fillBuffers.empty() || !m_queueBuffers.emptyNts()) + return true; +#endif + } + + return false; +} + +void CStream::Pause() +{ + if ( !HasSource() ) return; + ALint sourceState = AL_PAUSED; + alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PAUSED) + alSourcePause(m_pAlSources[0]); + alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PAUSED) + alSourcePause(m_pAlSources[1]); +} + +void CStream::SetPause(bool bPause) +{ + if ( !HasSource() ) return; + if ( bPause ) + { + Pause(); + m_bPaused = true; + } + else + { + if (m_bPaused) + SetPlay(true); + m_bPaused = false; + } +} + +void CStream::SetPitch(float pitch) +{ + if ( !HasSource() ) return; + alSourcef(m_pAlSources[0], AL_PITCH, pitch); + alSourcef(m_pAlSources[1], AL_PITCH, pitch); +} + +void CStream::SetGain(float gain) +{ + if ( !HasSource() ) return; + alSourcef(m_pAlSources[0], AL_GAIN, gain); + alSourcef(m_pAlSources[1], AL_GAIN, gain); +} + +void CStream::SetPosition(int i, float x, float y, float z) +{ + if ( !HasSource() ) return; + alSource3f(m_pAlSources[i], AL_POSITION, x, y, z); +} + +void CStream::SetVolume(uint32 nVol) +{ + m_nVolume = nVol; + SetGain(ALfloat(nVol) / MAX_VOLUME); +} + +void CStream::SetPan(uint8 nPan) +{ + m_nPan = Clamp((int8)nPan - 63, 0, 63); + SetPosition(0, (m_nPan - 63) / 64.0f, 0.0f, Sqrt(1.0f - SQR((m_nPan - 63) / 64.0f))); + + m_nPan = Clamp((int8)nPan + 64, 64, 127); + SetPosition(1, (m_nPan - 63) / 64.0f, 0.0f, Sqrt(1.0f - SQR((m_nPan - 63) / 64.0f))); + + m_nPan = nPan; +} + +// Should only be called if source is stopped +void CStream::SetPosMS(uint32 nPos) +{ + if ( !IsOpened() ) return; + +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + std::queue>().swap(m_fillBuffers); + tsQueue>().swapNts(m_queueBuffers); // TSness not required, second thread always access it when stream mutex acquired + + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) { + m_bDoSeek = true; + m_SeekPos = nPos; + } else +#endif + { + m_pSoundFile->Seek(nPos); + } + ClearBuffers(); + + // adding to gStreamsToProcess not needed, someone always calls Start() / BuffersShouldBeFilled() after SetPosMS +} + +uint32 CStream::GetPosMS() +{ + if ( !HasSource() ) return 0; + if ( !IsOpened() ) return 0; + + // Deferred init causes division by zero + if (m_pSoundFile->GetChannels() == 0) + return 0; + + ALint offset; + //alGetSourcei(m_alSource, AL_SAMPLE_OFFSET, &offset); + alGetSourcei(m_pAlSources[0], AL_BYTE_OFFSET, &offset); + + //std::lock_guard lock(m_mutex); + + return m_pSoundFile->Tell() + - m_pSoundFile->samples2ms(m_pSoundFile->GetBufferSamples() * (NUM_STREAMBUFFERS/2-1)) / m_pSoundFile->GetChannels() + + m_pSoundFile->samples2ms(offset/m_pSoundFile->GetSampleSize()) / m_pSoundFile->GetChannels(); +} + +uint32 CStream::GetLengthMS() +{ + if ( !IsOpened() ) return 0; + return m_pSoundFile->GetLength(); +} + +bool CStream::FillBuffer(ALuint *alBuffer) +{ +#ifndef MULTITHREADED_AUDIO + if ( !HasSource() ) + return false; + if ( !IsOpened() ) + return false; + if ( !(alBuffer[0] != AL_NONE && alIsBuffer(alBuffer[0])) ) + return false; + if ( !(alBuffer[1] != AL_NONE && alIsBuffer(alBuffer[1])) ) + return false; +#endif + + uint32 size = m_pSoundFile->Decode(m_pBuffer); + if( size == 0 ) + return false; + + uint32 channelSize = size / m_pSoundFile->GetChannels(); + + alBufferData(alBuffer[0], AL_FORMAT_MONO16, m_pBuffer, channelSize, m_pSoundFile->GetSampleRate()); + // TODO: use just one buffer if we play mono + if (m_pSoundFile->GetChannels() == 1) + alBufferData(alBuffer[1], AL_FORMAT_MONO16, m_pBuffer, channelSize, m_pSoundFile->GetSampleRate()); + else + alBufferData(alBuffer[1], AL_FORMAT_MONO16, (uint8*)m_pBuffer + channelSize, channelSize, m_pSoundFile->GetSampleRate()); + return true; +} + +#ifdef MULTITHREADED_AUDIO +bool CStream::QueueBuffers() +{ + bool buffersQueued = false; + std::pair buffers; + while (m_queueBuffers.peekPop(&buffers)) // beware: m_queueBuffers is tsQueue + { + ALuint leftBuf = buffers.first; + ALuint rightBuf = buffers.second; + + alSourceQueueBuffers(m_pAlSources[0], 1, &leftBuf); + alSourceQueueBuffers(m_pAlSources[1], 1, &rightBuf); + + buffersQueued = true; + } + return buffersQueued; +} +#endif + +// Only used in single-threaded audio or cutscene audio +int32 CStream::FillBuffers() +{ + int32 i = 0; + for ( i = 0; i < NUM_STREAMBUFFERS/2; i++ ) + { + if ( !FillBuffer(&m_alBuffers[i*2]) ) + break; + alSourceQueueBuffers(m_pAlSources[0], 1, &m_alBuffers[i*2]); + alSourceQueueBuffers(m_pAlSources[1], 1, &m_alBuffers[i*2+1]); + } + + return i; +} + +void CStream::ClearBuffers() +{ + if ( !HasSource() ) return; + + ALint buffersQueued[2]; + alGetSourcei(m_pAlSources[0], AL_BUFFERS_QUEUED, &buffersQueued[0]); + alGetSourcei(m_pAlSources[1], AL_BUFFERS_QUEUED, &buffersQueued[1]); + + ALuint value; + while (buffersQueued[0]--) + alSourceUnqueueBuffers(m_pAlSources[0], 1, &value); + while (buffersQueued[1]--) + alSourceUnqueueBuffers(m_pAlSources[1], 1, &value); +} + +bool CStream::Setup(bool imSureQueueIsEmpty, bool lock) +{ + if ( IsOpened() ) + { +#ifdef MULTITHREADED_AUDIO + if (lock) + m_mutex.lock(); +#endif + + if (!imSureQueueIsEmpty) { + Stop(); + ClearBuffers(); + } +#ifdef MULTITHREADED_AUDIO + if (MusicManager.m_nMusicMode == MUSICMODE_CUTSCENE) { + m_pSoundFile->Seek(0); + } else { + m_bDoSeek = true; + m_SeekPos = 0; + } + + if (lock) + m_mutex.unlock(); +#else + m_pSoundFile->Seek(0); +#endif + + //SetPosition(0.0f, 0.0f, 0.0f); + SetPitch(1.0f); + //SetPan(m_nPan); + //SetVolume(100); + } + + return IsOpened(); +} + +void CStream::SetLoopCount(int32 count) +{ + if ( !HasSource() ) return; + + m_nLoopCount = count; +} + +void CStream::SetPlay(bool state) +{ + if ( !HasSource() ) return; + if ( state ) + { + ALint sourceState = AL_PLAYING; + alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PLAYING ) + alSourcePlay(m_pAlSources[0]); + + sourceState = AL_PLAYING; + alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PLAYING) + alSourcePlay(m_pAlSources[1]); + + m_bActive = true; + } + else + { + ALint sourceState = AL_STOPPED; + alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_STOPPED) + alSourceStop(m_pAlSources[0]); + + sourceState = AL_STOPPED; + alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_STOPPED) + alSourceStop(m_pAlSources[1]); + + m_bActive = false; + } +} + +void CStream::Start() +{ + if ( !HasSource() ) return; + +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + tsQueue>().swapNts(m_queueBuffers); // TSness not required, second thread always access it when stream mutex acquired +#endif + BuffersShouldBeFilled(); +} + +void CStream::Stop() +{ + if ( !HasSource() ) return; + SetPlay(false); +} + +void CStream::Update() +{ + if ( !IsOpened() ) + return; + + if ( !HasSource() ) + return; + + if ( m_bReset ) + return; + + if ( !m_bPaused ) + { + + bool buffersQueuedAndStarted = false; + bool buffersQueuedButNotStarted = false; +#ifdef MULTITHREADED_AUDIO + // Put it in here because we need totalBuffers after queueing to decide when to loop audio + if (m_bActive) + { + buffersQueuedAndStarted = QueueBuffers(); + if(buffersQueuedAndStarted) { + SetPlay(true); + } + } +#endif + + ALint totalBuffers[2] = {0, 0}; + ALint buffersProcessed[2] = {0, 0}; + + // Relying a lot on left buffer states in here + + do + { + //alSourcef(m_pAlSources[0], AL_ROLLOFF_FACTOR, 0.0f); + alGetSourcei(m_pAlSources[0], AL_BUFFERS_QUEUED, &totalBuffers[0]); + alGetSourcei(m_pAlSources[0], AL_BUFFERS_PROCESSED, &buffersProcessed[0]); + //alSourcef(m_pAlSources[1], AL_ROLLOFF_FACTOR, 0.0f); + alGetSourcei(m_pAlSources[1], AL_BUFFERS_QUEUED, &totalBuffers[1]); + alGetSourcei(m_pAlSources[1], AL_BUFFERS_PROCESSED, &buffersProcessed[1]); + } while (buffersProcessed[0] != buffersProcessed[1]); + + assert(buffersProcessed[0] == buffersProcessed[1]); + + // Correcting OpenAL concepts here: + // AL_BUFFERS_QUEUED = Number of *all* buffers in queue, including processed, processing and pending + // AL_BUFFERS_PROCESSED = Index of the buffer being processing right now. Buffers coming after that(have greater index) are pending buffers. + // which means: totalBuffers[0] - buffersProcessed[0] = pending buffers + + // We should wait queue to be cleared to loop track, because position calculation relies on queue. + if (m_nLoopCount != 1 && m_bActive && totalBuffers[0] == 0) + { +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + if (m_fillBuffers.empty() && m_queueBuffers.emptyNts()) // we already acquired stream mutex, which is enough for second thread. thus Nts variant +#endif + { + Setup(true, false); + BuffersShouldBeFilled(); // will also call SetPlay(true) + if (m_nLoopCount != 0) + m_nLoopCount--; + } + } + else + { + static std::queue> tempFillBuffer; + + while ( buffersProcessed[0]-- ) + { + ALuint buffer[2]; + + alSourceUnqueueBuffers(m_pAlSources[0], 1, &buffer[0]); + alSourceUnqueueBuffers(m_pAlSources[1], 1, &buffer[1]); + + if (m_bActive) + { + tempFillBuffer.push(std::pair(buffer[0], buffer[1])); + } + } + + if (m_bActive && buffersProcessed[1]) + { +#ifdef MULTITHREADED_AUDIO + m_mutex.lock(); +#endif + while (!tempFillBuffer.empty()) { + auto elem = tempFillBuffer.front(); + tempFillBuffer.pop(); + buffersQueuedButNotStarted = BufferShouldBeFilledAndQueued(&elem); + } +#ifdef MULTITHREADED_AUDIO + m_mutex.unlock(); + FlagAsToBeProcessed(); +#endif + + } + } + + // Source may be starved to audio and stopped itself + if (m_bActive && !buffersQueuedAndStarted && (buffersQueuedButNotStarted || (totalBuffers[1] - buffersProcessed[1] != 0))) + SetPlay(true); + } +} + +void CStream::ProviderInit() +{ + if ( m_bReset ) + { + if ( Setup(true, false) ) // lock not needed, thread can't process streams with m_bReset set + { + SetPan(m_nPan); + SetVolume(m_nVolume); + SetLoopCount(m_nLoopCount); + SetPosMS(m_nPosBeforeReset); +#ifdef MULTITHREADED_AUDIO + std::unique_lock lock(m_mutex); +#endif + if(m_bActive) + BuffersShouldBeFilled(); + + if (m_bPaused) + Pause(); + + m_bReset = false; + + } else { +#ifdef MULTITHREADED_AUDIO + std::unique_lock lock(m_mutex); +#endif + m_bReset = false; + } + } +} + +void CStream::ProviderTerm() +{ +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + // unlike Close() we will reuse this stream, so clearing queues are important. + std::queue>().swap(m_fillBuffers); + tsQueue>().swapNts(m_queueBuffers); // stream mutex is already acquired, thus Nts variant +#endif + m_bReset = true; + m_nPosBeforeReset = GetPosMS(); + + Stop(); + ClearBuffers(); +} + +#endif diff --git a/src/audio/oal/stream.h b/src/audio/oal/stream.h new file mode 100644 index 0000000..f045692 --- /dev/null +++ b/src/audio/oal/stream.h @@ -0,0 +1,192 @@ +#pragma once + +#ifdef AUDIO_OAL +#include + +#define NUM_STREAMBUFFERS 8 + +class IDecoder +{ +public: + virtual ~IDecoder() { } + + virtual bool IsOpened() = 0; + virtual void FileOpen() = 0; + + virtual uint32 GetSampleSize() = 0; + virtual uint32 GetSampleCount() = 0; + virtual uint32 GetSampleRate() = 0; + virtual uint32 GetChannels() = 0; + + uint32 GetAvgSamplesPerSec() + { + return GetChannels() * GetSampleRate(); + } + + uint32 ms2samples(uint32 ms) + { + return float(ms) / 1000.0f * float(GetSampleRate()); + } + + uint32 samples2ms(uint32 sm) + { + return float(sm) * 1000.0f / float(GetSampleRate()); + } + + uint32 GetBufferSamples() + { + //return (GetAvgSamplesPerSec() >> 2) - (GetSampleCount() % GetChannels()); + return (GetAvgSamplesPerSec() / 4); // 250ms + } + + uint32 GetBufferSize() + { + return GetBufferSamples() * GetSampleSize(); + } + + virtual void Seek(uint32 milliseconds) = 0; + virtual uint32 Tell() = 0; + + uint32 GetLength() + { + FileOpen(); // abort deferred init, we need length now - game has to cache audio file sizes + return float(GetSampleCount()) * 1000.0f / float(GetSampleRate()); + } + + virtual uint32 Decode(void *buffer) = 0; +}; +#ifdef MULTITHREADED_AUDIO +template class tsQueue +{ +public: + tsQueue() : count(0) { } + + void push(const T &value) + { + std::lock_guard lock(m_mutex); + m_queue.push(value); + count++; + } + + bool peekPop(T *retVal) + { + std::lock_guard lock(m_mutex); + if (count == 0) + return false; + + *retVal = m_queue.front(); + m_queue.pop(); + count--; + return true; + } + + void swapNts(tsQueue &replaceWith) + { + m_queue.swap(replaceWith.m_queue); + replaceWith.count = count; + } + + /* + void swapTs(tsQueue &replaceWith) + { + std::lock_guard lock(m_mutex); + std::lock_guard lock2(replaceWith.m_mutex); + swapNts(replaceWith); + } + */ + + bool emptyNts() + { + return count == 0; + } + + /* + bool emptyTs() + { + std::lock_guard lock(m_mutex); + return emptyNts(); + } + */ + + std::queue m_queue; + int count; + mutable std::mutex m_mutex; +}; +#endif +class CStream +{ + char m_aFilename[128]; + ALuint *m_pAlSources; + ALuint (&m_alBuffers)[NUM_STREAMBUFFERS]; + + bool m_bPaused; + bool m_bActive; + +public: +#ifdef MULTITHREADED_AUDIO + std::mutex m_mutex; + std::queue> m_fillBuffers; // left and right buffer + tsQueue> m_queueBuffers; +// std::condition_variable m_closeCv; + bool m_bDoSeek; + uint32 m_SeekPos; + bool m_bIExist; +#endif + + void *m_pBuffer; + + bool m_bReset; + uint32 m_nVolume; + uint8 m_nPan; + uint32 m_nPosBeforeReset; + int32 m_nLoopCount; + + IDecoder *m_pSoundFile; + + void BuffersShouldBeFilled(); // all + bool BufferShouldBeFilledAndQueued(std::pair*); // two (left-right) +#ifdef MULTITHREADED_AUDIO + void FlagAsToBeProcessed(bool close = false); + bool QueueBuffers(); +#endif + + bool HasSource(); + void SetPosition(int i, float x, float y, float z); + void SetPitch(float pitch); + void SetGain(float gain); + void Pause(); + void SetPlay(bool state); + + bool FillBuffer(ALuint *alBuffer); + int32 FillBuffers(); + void ClearBuffers(); +//public: + static void Initialise(); + static void Terminate(); + + CStream(ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS]); + ~CStream(); + void Delete(); + bool Open(const char *filename, uint32 overrideSampleRate = 32000); + void Close(); + + bool IsOpened(); + bool IsPlaying(); + void SetPause (bool bPause); + void SetVolume(uint32 nVol); + void SetPan (uint8 nPan); + void SetPosMS (uint32 nPos); + uint32 GetPosMS(); + uint32 GetLengthMS(); + + bool Setup(bool imSureQueueIsEmpty = false, bool lock = true); + void Start(); + void Stop(); + void Update(void); + void SetLoopCount(int32); + + void ProviderInit(); + void ProviderTerm(); +}; + +#endif diff --git a/src/audio/sampman.h b/src/audio/sampman.h new file mode 100644 index 0000000..ad14e2b --- /dev/null +++ b/src/audio/sampman.h @@ -0,0 +1,673 @@ +#pragma once +#include "AudioSamples.h" +#include "audio_enums.h" + +#define MAX_VOLUME 127 +#define MAX_FREQ DIGITALRATE + +struct tSample { + uint32 nOffset; + uint32 nSize; + uint32 nFrequency; + uint32 nLoopStart; + int32 nLoopEnd; +}; + +#ifdef GTA_PS2 +#define PS2BANK(e) e +#else +#define PS2BANK(e) e = SFX_BANK_0 +#endif // GTA_PS2 + + +enum +{ + SFX_BANK_0, + + CAR_SFX_BANKS_OFFSET, + SFX_BANK_PACARD = CAR_SFX_BANKS_OFFSET, + SFX_BANK_PATHFINDER, + SFX_BANK_PORSCHE, + SFX_BANK_SPIDER, + SFX_BANK_MERC, + SFX_BANK_TRUCK, + SFX_BANK_HOTROD, + SFX_BANK_COBRA, + SFX_BANK_NONE, + + PS2BANK(SFX_BANK_FRONT_END_MENU), + + PS2BANK(SFX_BANK_TRAIN), + + PS2BANK(SFX_BANK_BUILDING_CLUB_1), + PS2BANK(SFX_BANK_BUILDING_CLUB_2), + PS2BANK(SFX_BANK_BUILDING_CLUB_3), + PS2BANK(SFX_BANK_BUILDING_CLUB_4), + PS2BANK(SFX_BANK_BUILDING_CLUB_5), + PS2BANK(SFX_BANK_BUILDING_CLUB_6), + PS2BANK(SFX_BANK_BUILDING_CLUB_7), + PS2BANK(SFX_BANK_BUILDING_CLUB_8), + PS2BANK(SFX_BANK_BUILDING_CLUB_9), + PS2BANK(SFX_BANK_BUILDING_CLUB_10), + PS2BANK(SFX_BANK_BUILDING_CLUB_11), + PS2BANK(SFX_BANK_BUILDING_CLUB_12), + PS2BANK(SFX_BANK_BUILDING_CLUB_RAGGA), + PS2BANK(SFX_BANK_BUILDING_STRIP_CLUB_1), + PS2BANK(SFX_BANK_BUILDING_STRIP_CLUB_2), + PS2BANK(SFX_BANK_BUILDING_WORKSHOP), + PS2BANK(SFX_BANK_BUILDING_PIANO_BAR), + PS2BANK(SFX_BANK_BUILDING_SAWMILL), + PS2BANK(SFX_BANK_BUILDING_DOG_FOOD_FACTORY), + PS2BANK(SFX_BANK_BUILDING_LAUNDERETTE), + PS2BANK(SFX_BANK_BUILDING_RESTAURANT_CHINATOWN), + PS2BANK(SFX_BANK_BUILDING_RESTAURANT_ITALY), + PS2BANK(SFX_BANK_BUILDING_RESTAURANT_GENERIC_1), + PS2BANK(SFX_BANK_BUILDING_RESTAURANT_GENERIC_2), + PS2BANK(SFX_BANK_BUILDING_AIRPORT), + PS2BANK(SFX_BANK_BUILDING_SHOP), + PS2BANK(SFX_BANK_BUILDING_CINEMA), + PS2BANK(SFX_BANK_BUILDING_DOCKS), + PS2BANK(SFX_BANK_BUILDING_HOME), + PS2BANK(SFX_BANK_BUILDING_PORN_1), + PS2BANK(SFX_BANK_BUILDING_PORN_2), + PS2BANK(SFX_BANK_BUILDING_PORN_3), + PS2BANK(SFX_BANK_BUILDING_POLICE_BALL), + PS2BANK(SFX_BANK_BUILDING_BANK_ALARM), + PS2BANK(SFX_BANK_BUILDING_RAVE_INDUSTRIAL), + PS2BANK(SFX_BANK_BUILDING_RAVE_COMMERCIAL), + PS2BANK(SFX_BANK_BUILDING_RAVE_SUBURBAN), + PS2BANK(SFX_BANK_BUILDING_RAVE_COMMERCIAL_2), + + PS2BANK(SFX_BANK_BUILDING_39), + PS2BANK(SFX_BANK_BUILDING_40), + PS2BANK(SFX_BANK_BUILDING_41), + PS2BANK(SFX_BANK_BUILDING_42), + PS2BANK(SFX_BANK_BUILDING_43), + PS2BANK(SFX_BANK_BUILDING_44), + PS2BANK(SFX_BANK_BUILDING_45), + PS2BANK(SFX_BANK_BUILDING_46), + PS2BANK(SFX_BANK_BUILDING_47), + + PS2BANK(SFX_BANK_GENERIC_EXTRA), + + SFX_BANK_PED_COMMENTS, + MAX_SFX_BANKS, + INVALID_SFX_BANK +}; + +#define MAX_PEDSFX 7 +#define PED_BLOCKSIZE 79000 + +#define MAXPROVIDERS 64 + +#ifdef EXTERNAL_3D_SOUND +#define MAXCHANNELS (NUM_CHANNELS_GENERIC+1) +#define MAXCHANNELS_SURROUND (MAXCHANNELS-4) +#define MAX2DCHANNELS 1 +#else +#define MAXCHANNELS 0 +#define MAXCHANNELS_SURROUND 0 +#define MAX2DCHANNELS NUM_CHANNELS +#endif + +#define MAX_STREAMS 2 + +#define DIGITALRATE 32000 +#define DIGITALBITS 16 +#define DIGITALCHANNELS 2 + +#ifdef FIX_BUGS +#define MAX_DIGITAL_MIXER_CHANNELS (MAXCHANNELS+MAX_STREAMS*2+MAX2DCHANNELS) +#else +#define MAX_DIGITAL_MIXER_CHANNELS (MAXCHANNELS+MAX_STREAMS*2) +#endif + +static_assert( NUM_CHANNELS == MAXCHANNELS + MAX2DCHANNELS, "The number of channels doesn't match with an enum" ); + +class cSampleManager +{ + uint8 m_nEffectsVolume; + uint8 m_nMusicVolume; + uint8 m_nEffectsFadeVolume; + uint8 m_nMusicFadeVolume; + bool8 m_nMonoMode; + char unk; + char m_szCDRomRootPath[80]; + bool8 m_bInitialised; + uint8 m_nNumberOfProviders; + char *m_aAudioProviders[MAXPROVIDERS]; + tSample m_aSamples[TOTAL_AUDIO_SAMPLES]; + +public: + + + + cSampleManager(void); + ~cSampleManager(void); + +#ifdef EXTERNAL_3D_SOUND + void SetSpeakerConfig(int32 nConfig); + uint32 GetMaximumSupportedChannels(void); + + uint32 GetNum3DProvidersAvailable(void); + void SetNum3DProvidersAvailable(uint32 num); + + char *Get3DProviderName(uint8 id); + void Set3DProviderName(uint8 id, char *name); + + int8 GetCurrent3DProviderIndex(void); + int8 SetCurrent3DProvider(uint8 which); +#endif + + bool8 IsMP3RadioChannelAvailable(void); + + void ReleaseDigitalHandle (void); + void ReacquireDigitalHandle(void); + + bool8 Initialise(void); + void Terminate (void); + + bool8 CheckForAnAudioFileOnCD(void); + char GetCDAudioDriveLetter (void); + + void UpdateEffectsVolume(void); + + void SetEffectsMasterVolume(uint8 nVolume); + void SetMusicMasterVolume (uint8 nVolume); + void SetEffectsFadeVolume (uint8 nVolume); + void SetMusicFadeVolume (uint8 nVolume); + void SetMonoMode (bool8 nMode); + + bool8 LoadSampleBank (uint8 nBank); + void UnloadSampleBank (uint8 nBank); + int8 IsSampleBankLoaded(uint8 nBank); + + uint8 IsPedCommentLoaded(uint32 nComment); + bool8 LoadPedComment (uint32 nComment); + int32 GetBankContainingSound(uint32 offset); + + int32 _GetPedCommentSlot(uint32 nComment); + + uint32 GetSampleBaseFrequency (uint32 nSample); + uint32 GetSampleLoopStartOffset(uint32 nSample); + int32 GetSampleLoopEndOffset (uint32 nSample); + uint32 GetSampleLength (uint32 nSample); + + bool8 UpdateReverb(void); + + void SetChannelReverbFlag (uint32 nChannel, bool8 nReverbFlag); + bool8 InitialiseChannel (uint32 nChannel, uint32 nSfx, uint8 nBank); +#ifdef EXTERNAL_3D_SOUND + void SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume); + void SetChannel3DPosition (uint32 nChannel, float fX, float fY, float fZ); + void SetChannel3DDistances (uint32 nChannel, float fMax, float fMin); +#endif + void SetChannelVolume (uint32 nChannel, uint32 nVolume); + void SetChannelPan (uint32 nChannel, uint32 nPan); + void SetChannelFrequency (uint32 nChannel, uint32 nFreq); + void SetChannelLoopPoints (uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd); + void SetChannelLoopCount (uint32 nChannel, uint32 nLoopCount); + bool8 GetChannelUsedFlag (uint32 nChannel); + void StartChannel (uint32 nChannel); + void StopChannel (uint32 nChannel); + + void PreloadStreamedFile (uint8 nFile, uint8 nStream = 0); + void PauseStream (bool8 nPauseFlag, uint8 nStream = 0); + void StartPreloadedStreamedFile (uint8 nStream = 0); + bool8 StartStreamedFile (uint8 nFile, uint32 nPos, uint8 nStream = 0); + void StopStreamedFile (uint8 nStream = 0); + int32 GetStreamedFilePosition (uint8 nStream = 0); + void SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, bool8 nEffectFlag, uint8 nStream = 0); + int32 GetStreamedFileLength (uint8 nStream = 0); + bool8 IsStreamPlaying (uint8 nStream = 0); +#ifdef AUDIO_OAL + void Service(void); +#endif + bool8 InitialiseSampleBanks(void); +}; + +extern cSampleManager SampleManager; +extern uint32 BankStartOffset[MAX_SFX_BANKS]; + +#ifdef AUDIO_OAL +extern int defaultProvider; +#endif + +#if defined(OPUS_AUDIO_PATHS) +static char StreamedNameTable[][25] = { + "AUDIO\\HEAD.OPUS", "AUDIO\\CLASS.OPUS", "AUDIO\\KJAH.OPUS", "AUDIO\\RISE.OPUS", "AUDIO\\LIPS.OPUS", "AUDIO\\GAME.OPUS", + "AUDIO\\MSX.OPUS", "AUDIO\\FLASH.OPUS", "AUDIO\\CHAT.OPUS", "AUDIO\\HEAD.OPUS", "AUDIO\\POLICE.OPUS", "AUDIO\\CITY.OPUS", + "AUDIO\\WATER.OPUS", "AUDIO\\COMOPEN.OPUS", "AUDIO\\SUBOPEN.OPUS", "AUDIO\\JB.OPUS", "AUDIO\\BET.OPUS", "AUDIO\\L1_LG.OPUS", + "AUDIO\\L2_DSB.OPUS", "AUDIO\\L3_DM.OPUS", "AUDIO\\L4_PAP.OPUS", "AUDIO\\L5_TFB.OPUS", "AUDIO\\J0_DM2.OPUS", "AUDIO\\J1_LFL.OPUS", + "AUDIO\\J2_KCL.OPUS", "AUDIO\\J3_VH.OPUS", "AUDIO\\J4_ETH.OPUS", "AUDIO\\J5_DST.OPUS", "AUDIO\\J6_TBJ.OPUS", "AUDIO\\T1_TOL.OPUS", + "AUDIO\\T2_TPU.OPUS", "AUDIO\\T3_MAS.OPUS", "AUDIO\\T4_TAT.OPUS", "AUDIO\\T5_BF.OPUS", "AUDIO\\S0_MAS.OPUS", "AUDIO\\S1_PF.OPUS", + "AUDIO\\S2_CTG.OPUS", "AUDIO\\S3_RTC.OPUS", "AUDIO\\S5_LRQ.OPUS", "AUDIO\\S4_BDBA.OPUS", "AUDIO\\S4_BDBB.OPUS", "AUDIO\\S2_CTG2.OPUS", + "AUDIO\\S4_BDBD.OPUS", "AUDIO\\S5_LRQB.OPUS", "AUDIO\\S5_LRQC.OPUS", "AUDIO\\A1_SSO.OPUS", "AUDIO\\A2_PP.OPUS", "AUDIO\\A3_SS.OPUS", + "AUDIO\\A4_PDR.OPUS", "AUDIO\\A5_K2FT.OPUS", "AUDIO\\K1_KBO.OPUS", "AUDIO\\K2_GIS.OPUS", "AUDIO\\K3_DS.OPUS", "AUDIO\\K4_SHI.OPUS", + "AUDIO\\K5_SD.OPUS", "AUDIO\\R0_PDR2.OPUS", "AUDIO\\R1_SW.OPUS", "AUDIO\\R2_AP.OPUS", "AUDIO\\R3_ED.OPUS", "AUDIO\\R4_GF.OPUS", + "AUDIO\\R5_PB.OPUS", "AUDIO\\R6_MM.OPUS", "AUDIO\\D1_STOG.OPUS", "AUDIO\\D2_KK.OPUS", "AUDIO\\D3_ADO.OPUS", "AUDIO\\D5_ES.OPUS", + "AUDIO\\D7_MLD.OPUS", "AUDIO\\D4_GTA.OPUS", "AUDIO\\D4_GTA2.OPUS", "AUDIO\\D6_STS.OPUS", "AUDIO\\A6_BAIT.OPUS", "AUDIO\\A7_ETG.OPUS", + "AUDIO\\A8_PS.OPUS", "AUDIO\\A9_ASD.OPUS", "AUDIO\\K4_SHI2.OPUS", "AUDIO\\C1_TEX.OPUS", "AUDIO\\EL_PH1.OPUS", "AUDIO\\EL_PH2.OPUS", + "AUDIO\\EL_PH3.OPUS", "AUDIO\\EL_PH4.OPUS", "AUDIO\\YD_PH1.OPUS", "AUDIO\\YD_PH2.OPUS", "AUDIO\\YD_PH3.OPUS", "AUDIO\\YD_PH4.OPUS", + "AUDIO\\HD_PH1.OPUS", "AUDIO\\HD_PH2.OPUS", "AUDIO\\HD_PH3.OPUS", "AUDIO\\HD_PH4.OPUS", "AUDIO\\HD_PH5.OPUS", "AUDIO\\MT_PH1.OPUS", + "AUDIO\\MT_PH2.OPUS", "AUDIO\\MT_PH3.OPUS", "AUDIO\\MT_PH4.OPUS", "AUDIO\\MISCOM.OPUS", "AUDIO\\END.OPUS", "AUDIO\\lib_a1.OPUS", + "AUDIO\\lib_a2.OPUS", "AUDIO\\lib_a.OPUS", "AUDIO\\lib_b.OPUS", "AUDIO\\lib_c.OPUS", "AUDIO\\lib_d.OPUS", "AUDIO\\l2_a.OPUS", + "AUDIO\\j4t_1.OPUS", "AUDIO\\j4t_2.OPUS", "AUDIO\\j4t_3.OPUS", "AUDIO\\j4t_4.OPUS", "AUDIO\\j4_a.OPUS", "AUDIO\\j4_b.OPUS", + "AUDIO\\j4_c.OPUS", "AUDIO\\j4_d.OPUS", "AUDIO\\j4_e.OPUS", "AUDIO\\j4_f.OPUS", "AUDIO\\j6_1.OPUS", "AUDIO\\j6_a.OPUS", + "AUDIO\\j6_b.OPUS", "AUDIO\\j6_c.OPUS", "AUDIO\\j6_d.OPUS", "AUDIO\\t4_a.OPUS", "AUDIO\\s1_a.OPUS", "AUDIO\\s1_a1.OPUS", + "AUDIO\\s1_b.OPUS", "AUDIO\\s1_c.OPUS", "AUDIO\\s1_c1.OPUS", "AUDIO\\s1_d.OPUS", "AUDIO\\s1_e.OPUS", "AUDIO\\s1_f.OPUS", + "AUDIO\\s1_g.OPUS", "AUDIO\\s1_h.OPUS", "AUDIO\\s1_i.OPUS", "AUDIO\\s1_j.OPUS", "AUDIO\\s1_k.OPUS", "AUDIO\\s1_l.OPUS", + "AUDIO\\s3_a.OPUS", "AUDIO\\s3_b.OPUS", "AUDIO\\el3_a.OPUS", "AUDIO\\mf1_a.OPUS", "AUDIO\\mf2_a.OPUS", "AUDIO\\mf3_a.OPUS", + "AUDIO\\mf3_b.OPUS", "AUDIO\\mf3_b1.OPUS", "AUDIO\\mf3_c.OPUS", "AUDIO\\mf4_a.OPUS", "AUDIO\\mf4_b.OPUS", "AUDIO\\mf4_c.OPUS", + "AUDIO\\a1_a.OPUS", "AUDIO\\a3_a.OPUS", "AUDIO\\a5_a.OPUS", "AUDIO\\a4_a.OPUS", "AUDIO\\a4_b.OPUS", "AUDIO\\a4_c.OPUS", + "AUDIO\\a4_d.OPUS", "AUDIO\\k1_a.OPUS", "AUDIO\\k3_a.OPUS", "AUDIO\\r1_a.OPUS", "AUDIO\\r2_a.OPUS", "AUDIO\\r2_b.OPUS", + "AUDIO\\r2_c.OPUS", "AUDIO\\r2_d.OPUS", "AUDIO\\r2_e.OPUS", "AUDIO\\r2_f.OPUS", "AUDIO\\r2_g.OPUS", "AUDIO\\r2_h.OPUS", + "AUDIO\\r5_a.OPUS", "AUDIO\\r6_a.OPUS", "AUDIO\\r6_a1.OPUS", "AUDIO\\r6_b.OPUS", "AUDIO\\lo2_a.OPUS", "AUDIO\\lo6_a.OPUS", + "AUDIO\\yd2_a.OPUS", "AUDIO\\yd2_b.OPUS", "AUDIO\\yd2_c.OPUS", "AUDIO\\yd2_c1.OPUS", "AUDIO\\yd2_d.OPUS", "AUDIO\\yd2_e.OPUS", + "AUDIO\\yd2_f.OPUS", "AUDIO\\yd2_g.OPUS", "AUDIO\\yd2_h.OPUS", "AUDIO\\yd2_ass.OPUS", "AUDIO\\yd2_ok.OPUS", "AUDIO\\h5_a.OPUS", + "AUDIO\\h5_b.OPUS", "AUDIO\\h5_c.OPUS", "AUDIO\\ammu_a.OPUS", "AUDIO\\ammu_b.OPUS", "AUDIO\\ammu_c.OPUS", "AUDIO\\door_1.OPUS", + "AUDIO\\door_2.OPUS", "AUDIO\\door_3.OPUS", "AUDIO\\door_4.OPUS", "AUDIO\\door_5.OPUS", "AUDIO\\door_6.OPUS", "AUDIO\\t3_a.OPUS", + "AUDIO\\t3_b.OPUS", "AUDIO\\t3_c.OPUS", "AUDIO\\k1_b.OPUS", "AUDIO\\cat1.OPUS"}; +#else +#ifdef PS2_AUDIO_PATHS +static char PS2StreamedNameTable[][25]= +{ + "AUDIO\\MUSIC\\HEAD.VB", + "AUDIO\\MUSIC\\CLASS.VB", + "AUDIO\\MUSIC\\KJAH.VB", + "AUDIO\\MUSIC\\RISE.VB", + "AUDIO\\MUSIC\\LIPS.VB", + "AUDIO\\MUSIC\\GAME.VB", + "AUDIO\\MUSIC\\MSX.VB", + "AUDIO\\MUSIC\\FLASH.VB", + "AUDIO\\MUSIC\\CHAT.VB", + "AUDIO\\MUSIC\\HEAD.VB", + "AUDIO\\MUSIC\\POLICE.VB", + "AUDIO\\MUSIC\\CITY.VB", + "AUDIO\\MUSIC\\WATER.VB", + "AUDIO\\MUSIC\\COMOPEN.VB", + "AUDIO\\MUSIC\\SUBOPEN.VB", + "AUDIO\\OTHER\\JB.VB", + "AUDIO\\OTHER\\BET.VB", + "AUDIO\\LUIGI\\L1_LG.VB", + "AUDIO\\LUIGI\\L2_DSB.VB", + "AUDIO\\LUIGI\\L3_DM.VB", + "AUDIO\\LUIGI\\L4_PAP.VB", + "AUDIO\\LUIGI\\L5_TFB.VB", + "AUDIO\\JOEY\\J0_DM2.VB", + "AUDIO\\JOEY\\J1_LFL.VB", + "AUDIO\\JOEY\\J2_KCL.VB", + "AUDIO\\JOEY\\J3_VH.VB", + "AUDIO\\JOEY\\J4_ETH.VB", + "AUDIO\\JOEY\\J5_DST.VB", + "AUDIO\\JOEY\\J6_TBJ.VB", + "AUDIO\\TONI\\T1_TOL.VB", + "AUDIO\\TONI\\T2_TPU.VB", + "AUDIO\\TONI\\T3_MAS.VB", + "AUDIO\\TONI\\T4_TAT.VB", + "AUDIO\\TONI\\T5_BF.VB", + "AUDIO\\SAL\\S0_MAS.VB", + "AUDIO\\SAL\\S1_PF.VB", + "AUDIO\\SAL\\S2_CTG.VB", + "AUDIO\\SAL\\S3_RTC.VB", + "AUDIO\\SAL\\S5_LRQ.VB", + "AUDIO\\EBALL\\S4_BDBA.VB", + "AUDIO\\EBALL\\S4_BDBB.VB", + "AUDIO\\SAL\\S2_CTG2.VB", + "AUDIO\\SAL\\S4_BDBD.VB", + "AUDIO\\SAL\\S5_LRQB.VB", + "AUDIO\\SAL\\S5_LRQC.VB", + "AUDIO\\ASUKA\\A1_SSO.VB", + "AUDIO\\ASUKA\\A2_PP.VB", + "AUDIO\\ASUKA\\A3_SS.VB", + "AUDIO\\ASUKA\\A4_PDR.VB", + "AUDIO\\ASUKA\\A5_K2FT.VB", + "AUDIO\\KENJI\\K1_KBO.VB", + "AUDIO\\KENJI\\K2_GIS.VB", + "AUDIO\\KENJI\\K3_DS.VB", + "AUDIO\\KENJI\\K4_SHI.VB", + "AUDIO\\KENJI\\K5_SD.VB", + "AUDIO\\RAY\\R0_PDR2.VB", + "AUDIO\\RAY\\R1_SW.VB", + "AUDIO\\RAY\\R2_AP.VB", + "AUDIO\\RAY\\R3_ED.VB", + "AUDIO\\RAY\\R4_GF.VB", + "AUDIO\\RAY\\R5_PB.VB", + "AUDIO\\RAY\\R6_MM.VB", + "AUDIO\\LOVE\\D1_STOG.VB", + "AUDIO\\LOVE\\D2_KK.VB", + "AUDIO\\LOVE\\D3_ADO.VB", + "AUDIO\\LOVE\\D5_ES.VB", + "AUDIO\\LOVE\\D7_MLD.VB", + "AUDIO\\LOVE\\D4_GTA.VB", + "AUDIO\\LOVE\\D4_GTA2.VB", + "AUDIO\\LOVE\\D6_STS.VB", + "AUDIO\\ASUKA\\A6_BAIT.VB", + "AUDIO\\ASUKA\\A7_ETG.VB", + "AUDIO\\ASUKA\\A8_PS.VB", + "AUDIO\\ASUKA\\A9_ASD.VB", + "AUDIO\\SHOP\\K4_SHI2.VB", + "AUDIO\\OTHER\\C1_TEX.VB", + "AUDIO\\PHONE\\EL_PH1.VB", + "AUDIO\\PHONE\\EL_PH2.VB", + "AUDIO\\PHONE\\EL_PH3.VB", + "AUDIO\\PHONE\\EL_PH4.VB", + "AUDIO\\PHONE\\YD_PH1.VB", + "AUDIO\\PHONE\\YD_PH2.VB", + "AUDIO\\PHONE\\YD_PH3.VB", + "AUDIO\\PHONE\\YD_PH4.VB", + "AUDIO\\PHONE\\HD_PH1.VB", + "AUDIO\\PHONE\\HD_PH2.VB", + "AUDIO\\PHONE\\HD_PH3.VB", + "AUDIO\\PHONE\\HD_PH4.VB", + "AUDIO\\PHONE\\HD_PH5.VB", + "AUDIO\\PHONE\\MT_PH1.VB", + "AUDIO\\PHONE\\MT_PH2.VB", + "AUDIO\\PHONE\\MT_PH3.VB", + "AUDIO\\PHONE\\MT_PH4.VB", + "AUDIO\\MUSIC\\MISCOM.VB", + "AUDIO\\MUSIC\\END.VB", + "AUDIO\\lib_a1.WAV", + "AUDIO\\lib_a2.WAV", + "AUDIO\\lib_a.WAV", + "AUDIO\\lib_b.WAV", + "AUDIO\\lib_c.WAV", + "AUDIO\\lib_d.WAV", + "AUDIO\\l2_a.WAV", + "AUDIO\\j4t_1.WAV", + "AUDIO\\j4t_2.WAV", + "AUDIO\\j4t_3.WAV", + "AUDIO\\j4t_4.WAV", + "AUDIO\\j4_a.WAV", + "AUDIO\\j4_b.WAV", + "AUDIO\\j4_c.WAV", + "AUDIO\\j4_d.WAV", + "AUDIO\\j4_e.WAV", + "AUDIO\\j4_f.WAV", + "AUDIO\\j6_1.WAV", + "AUDIO\\j6_a.WAV", + "AUDIO\\j6_b.WAV", + "AUDIO\\j6_c.WAV", + "AUDIO\\j6_d.WAV", + "AUDIO\\t4_a.WAV", + "AUDIO\\s1_a.WAV", + "AUDIO\\s1_a1.WAV", + "AUDIO\\s1_b.WAV", + "AUDIO\\s1_c.WAV", + "AUDIO\\s1_c1.WAV", + "AUDIO\\s1_d.WAV", + "AUDIO\\s1_e.WAV", + "AUDIO\\s1_f.WAV", + "AUDIO\\s1_g.WAV", + "AUDIO\\s1_h.WAV", + "AUDIO\\s1_i.WAV", + "AUDIO\\s1_j.WAV", + "AUDIO\\s1_k.WAV", + "AUDIO\\s1_l.WAV", + "AUDIO\\s3_a.WAV", + "AUDIO\\s3_b.WAV", + "AUDIO\\el3_a.WAV", + "AUDIO\\mf1_a.WAV", + "AUDIO\\mf2_a.WAV", + "AUDIO\\mf3_a.WAV", + "AUDIO\\mf3_b.WAV", + "AUDIO\\mf3_b1.WAV", + "AUDIO\\mf3_c.WAV", + "AUDIO\\mf4_a.WAV", + "AUDIO\\mf4_b.WAV", + "AUDIO\\mf4_c.WAV", + "AUDIO\\a1_a.WAV", + "AUDIO\\a3_a.WAV", + "AUDIO\\a5_a.WAV", + "AUDIO\\a4_a.WAV", + "AUDIO\\a4_b.WAV", + "AUDIO\\a4_c.WAV", + "AUDIO\\a4_d.WAV", + "AUDIO\\k1_a.WAV", + "AUDIO\\k3_a.WAV", + "AUDIO\\r1_a.WAV", + "AUDIO\\r2_a.WAV", + "AUDIO\\r2_b.WAV", + "AUDIO\\r2_c.WAV", + "AUDIO\\r2_d.WAV", + "AUDIO\\r2_e.WAV", + "AUDIO\\r2_f.WAV", + "AUDIO\\r2_g.WAV", + "AUDIO\\r2_h.WAV", + "AUDIO\\r5_a.WAV", + "AUDIO\\r6_a.WAV", + "AUDIO\\r6_a1.WAV", + "AUDIO\\r6_b.WAV", + "AUDIO\\lo2_a.WAV", + "AUDIO\\lo6_a.WAV", + "AUDIO\\yd2_a.WAV", + "AUDIO\\yd2_b.WAV", + "AUDIO\\yd2_c.WAV", + "AUDIO\\yd2_c1.WAV", + "AUDIO\\yd2_d.WAV", + "AUDIO\\yd2_e.WAV", + "AUDIO\\yd2_f.WAV", + "AUDIO\\yd2_g.WAV", + "AUDIO\\yd2_h.WAV", + "AUDIO\\yd2_ass.WAV", + "AUDIO\\yd2_ok.WAV", + "AUDIO\\h5_a.WAV", + "AUDIO\\h5_b.WAV", + "AUDIO\\h5_c.WAV", + "AUDIO\\ammu_a.WAV", + "AUDIO\\ammu_b.WAV", + "AUDIO\\ammu_c.WAV", + "AUDIO\\door_1.WAV", + "AUDIO\\door_2.WAV", + "AUDIO\\door_3.WAV", + "AUDIO\\door_4.WAV", + "AUDIO\\door_5.WAV", + "AUDIO\\door_6.WAV", + "AUDIO\\t3_a.WAV", + "AUDIO\\t3_b.WAV", + "AUDIO\\t3_c.WAV", + "AUDIO\\k1_b.WAV", + "AUDIO\\cat1.WAV" +}; +#endif + +static char StreamedNameTable[][25] = +{ + "AUDIO\\HEAD.WAV", + "AUDIO\\CLASS.WAV", + "AUDIO\\KJAH.WAV", + "AUDIO\\RISE.WAV", + "AUDIO\\LIPS.WAV", + "AUDIO\\GAME.WAV", + "AUDIO\\MSX.WAV", + "AUDIO\\FLASH.WAV", + "AUDIO\\CHAT.WAV", + "AUDIO\\HEAD.WAV", + "AUDIO\\POLICE.WAV", + "AUDIO\\CITY.WAV", + "AUDIO\\WATER.WAV", + "AUDIO\\COMOPEN.WAV", + "AUDIO\\SUBOPEN.WAV", + "AUDIO\\JB.MP3", + "AUDIO\\BET.MP3", + "AUDIO\\L1_LG.MP3", + "AUDIO\\L2_DSB.MP3", + "AUDIO\\L3_DM.MP3", + "AUDIO\\L4_PAP.MP3", + "AUDIO\\L5_TFB.MP3", + "AUDIO\\J0_DM2.MP3", + "AUDIO\\J1_LFL.MP3", + "AUDIO\\J2_KCL.MP3", + "AUDIO\\J3_VH.MP3", + "AUDIO\\J4_ETH.MP3", + "AUDIO\\J5_DST.MP3", + "AUDIO\\J6_TBJ.MP3", + "AUDIO\\T1_TOL.MP3", + "AUDIO\\T2_TPU.MP3", + "AUDIO\\T3_MAS.MP3", + "AUDIO\\T4_TAT.MP3", + "AUDIO\\T5_BF.MP3", + "AUDIO\\S0_MAS.MP3", + "AUDIO\\S1_PF.MP3", + "AUDIO\\S2_CTG.MP3", + "AUDIO\\S3_RTC.MP3", + "AUDIO\\S5_LRQ.MP3", + "AUDIO\\S4_BDBA.MP3", + "AUDIO\\S4_BDBB.MP3", + "AUDIO\\S2_CTG2.MP3", + "AUDIO\\S4_BDBD.MP3", + "AUDIO\\S5_LRQB.MP3", + "AUDIO\\S5_LRQC.MP3", + "AUDIO\\A1_SSO.WAV", + "AUDIO\\A2_PP.WAV", + "AUDIO\\A3_SS.WAV", + "AUDIO\\A4_PDR.WAV", + "AUDIO\\A5_K2FT.WAV", + "AUDIO\\K1_KBO.MP3", + "AUDIO\\K2_GIS.MP3", + "AUDIO\\K3_DS.MP3", + "AUDIO\\K4_SHI.MP3", + "AUDIO\\K5_SD.MP3", + "AUDIO\\R0_PDR2.MP3", + "AUDIO\\R1_SW.MP3", + "AUDIO\\R2_AP.MP3", + "AUDIO\\R3_ED.MP3", + "AUDIO\\R4_GF.MP3", + "AUDIO\\R5_PB.MP3", + "AUDIO\\R6_MM.MP3", + "AUDIO\\D1_STOG.MP3", + "AUDIO\\D2_KK.MP3", + "AUDIO\\D3_ADO.MP3", + "AUDIO\\D5_ES.MP3", + "AUDIO\\D7_MLD.MP3", + "AUDIO\\D4_GTA.MP3", + "AUDIO\\D4_GTA2.MP3", + "AUDIO\\D6_STS.MP3", + "AUDIO\\A6_BAIT.WAV", + "AUDIO\\A7_ETG.WAV", + "AUDIO\\A8_PS.WAV", + "AUDIO\\A9_ASD.WAV", + "AUDIO\\K4_SHI2.MP3", + "AUDIO\\C1_TEX.MP3", + "AUDIO\\EL_PH1.MP3", + "AUDIO\\EL_PH2.MP3", + "AUDIO\\EL_PH3.MP3", + "AUDIO\\EL_PH4.MP3", + "AUDIO\\YD_PH1.MP3", + "AUDIO\\YD_PH2.MP3", + "AUDIO\\YD_PH3.MP3", + "AUDIO\\YD_PH4.MP3", + "AUDIO\\HD_PH1.MP3", + "AUDIO\\HD_PH2.MP3", + "AUDIO\\HD_PH3.MP3", + "AUDIO\\HD_PH4.MP3", + "AUDIO\\HD_PH5.MP3", + "AUDIO\\MT_PH1.MP3", + "AUDIO\\MT_PH2.MP3", + "AUDIO\\MT_PH3.MP3", + "AUDIO\\MT_PH4.MP3", + "AUDIO\\MISCOM.WAV", + "AUDIO\\END.MP3", + "AUDIO\\lib_a1.WAV", + "AUDIO\\lib_a2.WAV", + "AUDIO\\lib_a.WAV", + "AUDIO\\lib_b.WAV", + "AUDIO\\lib_c.WAV", + "AUDIO\\lib_d.WAV", + "AUDIO\\l2_a.WAV", + "AUDIO\\j4t_1.WAV", + "AUDIO\\j4t_2.WAV", + "AUDIO\\j4t_3.WAV", + "AUDIO\\j4t_4.WAV", + "AUDIO\\j4_a.WAV", + "AUDIO\\j4_b.WAV", + "AUDIO\\j4_c.WAV", + "AUDIO\\j4_d.WAV", + "AUDIO\\j4_e.WAV", + "AUDIO\\j4_f.WAV", + "AUDIO\\j6_1.WAV", + "AUDIO\\j6_a.WAV", + "AUDIO\\j6_b.WAV", + "AUDIO\\j6_c.WAV", + "AUDIO\\j6_d.WAV", + "AUDIO\\t4_a.WAV", + "AUDIO\\s1_a.WAV", + "AUDIO\\s1_a1.WAV", + "AUDIO\\s1_b.WAV", + "AUDIO\\s1_c.WAV", + "AUDIO\\s1_c1.WAV", + "AUDIO\\s1_d.WAV", + "AUDIO\\s1_e.WAV", + "AUDIO\\s1_f.WAV", + "AUDIO\\s1_g.WAV", + "AUDIO\\s1_h.WAV", + "AUDIO\\s1_i.WAV", + "AUDIO\\s1_j.WAV", + "AUDIO\\s1_k.WAV", + "AUDIO\\s1_l.WAV", + "AUDIO\\s3_a.WAV", + "AUDIO\\s3_b.WAV", + "AUDIO\\el3_a.WAV", + "AUDIO\\mf1_a.WAV", + "AUDIO\\mf2_a.WAV", + "AUDIO\\mf3_a.WAV", + "AUDIO\\mf3_b.WAV", + "AUDIO\\mf3_b1.WAV", + "AUDIO\\mf3_c.WAV", + "AUDIO\\mf4_a.WAV", + "AUDIO\\mf4_b.WAV", + "AUDIO\\mf4_c.WAV", + "AUDIO\\a1_a.WAV", + "AUDIO\\a3_a.WAV", + "AUDIO\\a5_a.WAV", + "AUDIO\\a4_a.WAV", + "AUDIO\\a4_b.WAV", + "AUDIO\\a4_c.WAV", + "AUDIO\\a4_d.WAV", + "AUDIO\\k1_a.WAV", + "AUDIO\\k3_a.WAV", + "AUDIO\\r1_a.WAV", + "AUDIO\\r2_a.WAV", + "AUDIO\\r2_b.WAV", + "AUDIO\\r2_c.WAV", + "AUDIO\\r2_d.WAV", + "AUDIO\\r2_e.WAV", + "AUDIO\\r2_f.WAV", + "AUDIO\\r2_g.WAV", + "AUDIO\\r2_h.WAV", + "AUDIO\\r5_a.WAV", + "AUDIO\\r6_a.WAV", + "AUDIO\\r6_a1.WAV", + "AUDIO\\r6_b.WAV", + "AUDIO\\lo2_a.WAV", + "AUDIO\\lo6_a.WAV", + "AUDIO\\yd2_a.WAV", + "AUDIO\\yd2_b.WAV", + "AUDIO\\yd2_c.WAV", + "AUDIO\\yd2_c1.WAV", + "AUDIO\\yd2_d.WAV", + "AUDIO\\yd2_e.WAV", + "AUDIO\\yd2_f.WAV", + "AUDIO\\yd2_g.WAV", + "AUDIO\\yd2_h.WAV", + "AUDIO\\yd2_ass.WAV", + "AUDIO\\yd2_ok.WAV", + "AUDIO\\h5_a.WAV", + "AUDIO\\h5_b.WAV", + "AUDIO\\h5_c.WAV", + "AUDIO\\ammu_a.WAV", + "AUDIO\\ammu_b.WAV", + "AUDIO\\ammu_c.WAV", + "AUDIO\\door_1.WAV", + "AUDIO\\door_2.WAV", + "AUDIO\\door_3.WAV", + "AUDIO\\door_4.WAV", + "AUDIO\\door_5.WAV", + "AUDIO\\door_6.WAV", + "AUDIO\\t3_a.WAV", + "AUDIO\\t3_b.WAV", + "AUDIO\\t3_c.WAV", + "AUDIO\\k1_b.WAV", + "AUDIO\\cat1.WAV" +}; +#endif \ No newline at end of file diff --git a/src/audio/sampman_miles.cpp b/src/audio/sampman_miles.cpp new file mode 100644 index 0000000..a4309b6 --- /dev/null +++ b/src/audio/sampman_miles.cpp @@ -0,0 +1,2490 @@ +#define WITHWINDOWS +#include "common.h" + +#ifdef AUDIO_MSS +#include +#include + +#include + +#include "eax.h" +#include "eax-util.h" +#include "mss.h" + +#include "sampman.h" +#include "AudioManager.h" +#include "MusicManager.h" +#include "Frontend.h" +#include "Timer.h" + + +#pragma comment( lib, "mss32.lib" ) + +cSampleManager SampleManager; +uint32 BankStartOffset[MAX_SFX_BANKS]; +/////////////////////////////////////////////////////////////// + +char SampleBankDescFilename[] = "AUDIO\\SFX.SDT"; +char SampleBankDataFilename[] = "AUDIO\\SFX.RAW"; + +FILE *fpSampleDescHandle; +FILE *fpSampleDataHandle; +int8 gBankLoaded [MAX_SFX_BANKS]; +int32 nSampleBankDiscStartOffset [MAX_SFX_BANKS]; +int32 nSampleBankSize [MAX_SFX_BANKS]; +int32 nSampleBankMemoryStartAddress[MAX_SFX_BANKS]; +int32 _nSampleDataEndOffset; + +int32 nPedSlotSfx [MAX_PEDSFX]; +int32 nPedSlotSfxAddr[MAX_PEDSFX]; +uint8 nCurrentPedSlot; + +uint8 nChannelVolume[MAXCHANNELS+MAX2DCHANNELS]; + +uint32 nStreamLength[TOTAL_STREAMED_SOUNDS]; + +/////////////////////////////////////////////////////////////// +struct tMP3Entry +{ + char aFilename[MAX_PATH]; + + uint32 nTrackLength; + uint32 nTrackStreamPos; + + tMP3Entry *pNext; + char *pLinkPath; +}; + +uint32 nNumMP3s; +tMP3Entry *_pMP3List; +char _mp3DirectoryPath[MAX_PATH]; +HSTREAM mp3Stream [MAX_STREAMS]; +int8 nStreamPan [MAX_STREAMS]; +int8 nStreamVolume[MAX_STREAMS]; +uint32 _CurMP3Index; +int32 _CurMP3Pos; +bool8 _bIsMp3Active; + +#if GTA_VERSION >= GTA3_PC_11 || defined(NO_CDCHECK) +bool8 _bUseHDDAudio; +char _aHDDPath[MAX_PATH]; +#endif +/////////////////////////////////////////////////////////////// + + +bool8 _bSampmanInitialised = FALSE; +#ifdef EXTERNAL_3D_SOUND +// +// Miscellaneous globals / defines + +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS + +EAXLISTENERPROPERTIES StartEAX3 = + {26, 1.7f, 0.8f, -1000, -1000, -100, 4.42f, 0.14f, 1.00f, 429, 0.014f, 0.00f,0.00f,0.00f, 1023, 0.021f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2727.1f, 250.0f, 0.00f, 0x3f }; + +EAXLISTENERPROPERTIES FinishEAX3 = + {26, 100.0f, 1.0f, 0, -1000, -2200, 20.0f, 1.39f, 1.00f, 1000, 0.069f, 0.00f,0.00f,0.00f, 400, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 3.982f, 0.000f, -18.0f, 3530.8f, 417.9f, 6.70f, 0x3f }; + +EAXLISTENERPROPERTIES EAX3Params; + +S32 prevprovider=-1; +S32 curprovider=-1; +S32 usingEAX=0; +S32 usingEAX3=0; +HPROVIDER opened_provider=0; +H3DSAMPLE opened_samples[MAXCHANNELS] = {0}; +#endif +HSAMPLE opened_2dsamples[MAX2DCHANNELS] = {0}; +HDIGDRIVER DIG; +#ifdef EXTERNAL_3D_SOUND +S32 speaker_type=0; +U32 _maxSamples; +float _fPrevEaxRatioDestination; +bool8 _usingMilesFast2D; +float _fEffectsLevel; + + +struct +{ + HPROVIDER id; + char name[80]; +}providers[MAXPROVIDERS]; + +typedef struct provider_stuff +{ + char* name; + HPROVIDER id; +} provider_stuff; + + +static int __cdecl comp(const provider_stuff*s1,const provider_stuff*s2) +{ + return( _stricmp(s1->name,s2->name) ); +} + +static void +add_providers() +{ + provider_stuff pi[MAXPROVIDERS]; + U32 n,i,j; + + SampleManager.SetNum3DProvidersAvailable(0); + + HPROENUM next = HPROENUM_FIRST; + + n=0; + while (AIL_enumerate_3D_providers(&next, &pi[n].id, &pi[n].name) && (n MAXCHANNELS ) + _maxSamples = MAXCHANNELS; + + SampleManager.SetSpeakerConfig(speaker_type); + + //obtain a 3D sample handles + for ( U32 i = 0; i < _maxSamples; ++i ) + { + opened_samples[i] = AIL_allocate_3D_sample_handle(opened_provider); + if ( opened_samples[i] != NULL ) + AIL_set_3D_sample_effects_level(opened_samples[i], 0.0f); + } + + return TRUE; + } + } + + return FALSE; +} +#endif + +cSampleManager::cSampleManager(void) : + m_nNumberOfProviders(0) +{ + ; +} + +cSampleManager::~cSampleManager(void) +{ + +} + +#ifdef EXTERNAL_3D_SOUND +void +cSampleManager::SetSpeakerConfig(int32 which) +{ + switch ( which ) + { + case 1: + speaker_type=AIL_3D_2_SPEAKER; + break; + + case 2: + speaker_type=AIL_3D_HEADPHONE; + break; + + case 3: + speaker_type=AIL_3D_4_SPEAKER; + break; + + default: + return; + break; + } + + if (opened_provider) + AIL_set_3D_speaker_type(opened_provider, speaker_type); +} + +uint32 +cSampleManager::GetMaximumSupportedChannels(void) +{ + if ( _maxSamples > MAXCHANNELS ) + return MAXCHANNELS; + + return _maxSamples; +} + +uint32 cSampleManager::GetNum3DProvidersAvailable() +{ + return m_nNumberOfProviders; +} + +void cSampleManager::SetNum3DProvidersAvailable(uint32 num) +{ + m_nNumberOfProviders = num; +} + +char *cSampleManager::Get3DProviderName(uint8 id) +{ + return m_aAudioProviders[id]; +} + +void cSampleManager::Set3DProviderName(uint8 id, char *name) +{ + m_aAudioProviders[id] = name; +} + +int8 +cSampleManager::GetCurrent3DProviderIndex(void) +{ + return curprovider; +} + +int8 +cSampleManager::SetCurrent3DProvider(uint8 nProvider) +{ + S32 savedprovider = curprovider; + + if ( nProvider < m_nNumberOfProviders ) + { + if ( set_new_provider(nProvider) ) + return curprovider; + else if ( savedprovider != -1 && savedprovider < m_nNumberOfProviders && set_new_provider(savedprovider) ) + return curprovider; + else + return -1; + } + else + return curprovider; +} +#endif + +static bool8 +_ResolveLink(char const *path, char *out) +{ + IShellLink* psl; + WIN32_FIND_DATA fd; + char filepath[MAX_PATH]; + + CoInitialize(NULL); + + if (SUCCEEDED( CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl ) )) + { + IPersistFile *ppf; + + if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf))) + { + WCHAR wpath[MAX_PATH]; + + MultiByteToWideChar(CP_ACP, 0, path, -1, wpath, MAX_PATH); + + if (SUCCEEDED(ppf->Load(wpath, STGM_READ))) + { + /* Resolve the link */ + if (SUCCEEDED(psl->Resolve(NULL, SLR_ANY_MATCH|SLR_NO_UI|SLR_NOSEARCH))) + { + strcpy(filepath, path); + + if (SUCCEEDED(psl->GetPath(filepath, MAX_PATH, &fd, SLGP_UNCPRIORITY))) + { + OutputDebugString(fd.cFileName); + + strcpy(out, filepath); + // FIX: Release the objects. Taken from SA. +#ifdef FIX_BUGS + ppf->Release(); + psl->Release(); +#endif + return TRUE; + } + } + } + + ppf->Release(); + } + psl->Release(); + } + + return FALSE; +} + +static void +_FindMP3s(void) +{ + tMP3Entry *pList; + bool8 bShortcut; + bool8 bInitFirstEntry; + HANDLE hFind; + char path[MAX_PATH]; + char filepath[MAX_PATH*2]; + S32 total_ms; + WIN32_FIND_DATA fd; + + + if ( GetCurrentDirectory(MAX_PATH, _mp3DirectoryPath) == 0 ) + { + GetLastError(); + return; + } + + OutputDebugString("Finding MP3s..."); + strcpy(path, _mp3DirectoryPath); + strcat(path, "\\MP3\\"); + + strcpy(_mp3DirectoryPath, path); + OutputDebugString(_mp3DirectoryPath); + + strcat(path, "*"); + + hFind = FindFirstFile(path, &fd); + + if ( hFind == INVALID_HANDLE_VALUE ) + { + GetLastError(); + return; + } + + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + int32 filepathlen = strlen(filepath); + + if ( filepathlen <= 0) + { + FindClose(hFind); + return; + } + + FILE *f = fopen("MP3\\MP3Report.txt", "w"); + + if ( f ) + { + fprintf(f, "MP3 Report File\n\n"); + fprintf(f, "\"%s\"", fd.cFileName); + } + + + if ( filepathlen > 4 ) + { + if ( !strcmp(&filepath[filepathlen - 4], ".lnk") ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - shortcut to \"%s\"", filepath); + } + else + { + if ( f ) fprintf(f, " - couldn't resolve shortcut"); + } + + bShortcut = TRUE; + } + else + bShortcut = FALSE; + } + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &total_ms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + OutputDebugString(fd.cFileName); + + _pMP3List = new tMP3Entry; + + if ( _pMP3List == NULL ) + { + FindClose(hFind); + + if ( f ) + fclose(f); + + return; + } + + nNumMP3s = 1; + + strcpy(_pMP3List->aFilename, fd.cFileName); + + _pMP3List->nTrackLength = total_ms; + + _pMP3List->pNext = NULL; + + pList = _pMP3List; + + if ( bShortcut ) + { + _pMP3List->pLinkPath = new char[MAX_PATH*2]; + strcpy(_pMP3List->pLinkPath, filepath); + } + else + { + _pMP3List->pLinkPath = NULL; + } + + if ( f ) fprintf(f, " - OK\n"); + + bInitFirstEntry = FALSE; + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - not an MP3 or supported MP3 type\n"); + + bInitFirstEntry = TRUE; + } + + while ( TRUE ) + { + if ( !FindNextFile(hFind, &fd) ) + break; + + if ( bInitFirstEntry ) + { + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + int32 filepathlen = strlen(filepath); + + if ( f ) fprintf(f, "\"%s\"", fd.cFileName); + + if ( filepathlen > 0 ) + { + if ( filepathlen > 4 ) + { + if ( !strcmp(&filepath[filepathlen - 4], ".lnk") ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - shortcut to \"%s\"", filepath); + } + else + { + if ( f ) fprintf(f, " - couldn't resolve shortcut"); + } + + bShortcut = TRUE; + } + else + { + bShortcut = FALSE; + + if ( filepathlen > MAX_PATH ) + { + if ( f ) fprintf(f, " - Filename and path too long - %s - IGNORED)\n", filepath); + + continue; + } + } + } + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &total_ms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + OutputDebugString(fd.cFileName); + + _pMP3List = new tMP3Entry; + + if ( _pMP3List == NULL) + break; + + nNumMP3s = 1; + + strcpy(_pMP3List->aFilename, fd.cFileName); + + _pMP3List->nTrackLength = total_ms; + _pMP3List->pNext = NULL; + + if ( bShortcut ) + { + _pMP3List->pLinkPath = new char [MAX_PATH*2]; + strcpy(_pMP3List->pLinkPath, filepath); + } + else + { + _pMP3List->pLinkPath = NULL; + } + + pList = _pMP3List; + + if ( f ) fprintf(f, " - OK\n"); + + bInitFirstEntry = FALSE; + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - not an MP3 or supported MP3 type\n"); + } + } + } + else + { + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + int32 filepathlen = strlen(filepath); + + if ( filepathlen > 0 ) + { + if ( f ) fprintf(f, "\"%s\"", fd.cFileName); + + if ( filepathlen > 4 ) + { + if ( !strcmp(&filepath[filepathlen - 4], ".lnk") ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - shortcut to \"%s\"", filepath); + } + else + { + if ( f ) fprintf(f, " - couldn't resolve shortcut"); + } + + bShortcut = TRUE; + } + else + { + bShortcut = FALSE; + } + } + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &total_ms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + pList->pNext = new tMP3Entry; + + tMP3Entry *e = pList->pNext; + + if ( e == NULL ) + break; + + pList = pList->pNext; + + strcpy(e->aFilename, fd.cFileName); + e->nTrackLength = total_ms; + e->pNext = NULL; + + if ( bShortcut ) + { + e->pLinkPath = new char [MAX_PATH*2]; + strcpy(e->pLinkPath, filepath); + } + else + { + e->pLinkPath = NULL; + } + + nNumMP3s++; + + OutputDebugString(fd.cFileName); + + if ( f ) fprintf(f, " - OK\n"); + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + + if ( f ) fprintf(f, " - not an MP3 or supported MP3 type\n"); + } + } + } + } + + if ( f ) + { + fprintf(f, "\nTOTAL SUPPORTED MP3s: %d\n", nNumMP3s); + fclose(f); + } + + FindClose(hFind); +} + +static void +_DeleteMP3Entries(void) +{ + tMP3Entry *e = _pMP3List; + + while ( e != NULL ) + { + tMP3Entry *next = e->pNext; + + if ( next == NULL ) + next = NULL; + + if ( e->pLinkPath != NULL ) + { +#ifndef FIX_BUGS + delete e->pLinkPath; // BUG: should be delete [] +#else + delete[] e->pLinkPath; +#endif + e->pLinkPath = NULL; + } + + delete e; + + if ( next ) + e = next; + else + e = NULL; + + nNumMP3s--; + } + + + if ( nNumMP3s != 0 ) + { + OutputDebugString("Not all MP3 entries were deleted"); + nNumMP3s = 0; + } + + _pMP3List = NULL; +} + +static tMP3Entry * +_GetMP3EntryByIndex(uint32 idx) +{ + uint32 n = ( idx < nNumMP3s ) ? idx : 0; + + if ( _pMP3List != NULL ) + { + tMP3Entry *e = _pMP3List; + + for ( uint32 i = 0; i < n; i++ ) + e = e->pNext; + + return e; + + } + + return NULL; +} + +static inline bool8 +_GetMP3PosFromStreamPos(uint32 *pPosition, tMP3Entry **pEntry) +{ + _CurMP3Index = 0; + + for ( *pEntry = _pMP3List; *pEntry != NULL; *pEntry = (*pEntry)->pNext ) + { + if ( *pPosition >= (*pEntry)->nTrackStreamPos + && *pPosition < (*pEntry)->nTrackLength + (*pEntry)->nTrackStreamPos ) + { + *pPosition -= (*pEntry)->nTrackStreamPos; + _CurMP3Pos = *pPosition; + + return TRUE; + } + + _CurMP3Index++; + } + + *pPosition = 0; + *pEntry = _pMP3List; + _CurMP3Pos = 0; + _CurMP3Index = 0; + + return FALSE; +} + +bool8 +cSampleManager::IsMP3RadioChannelAvailable(void) +{ + return nNumMP3s != 0; +} + +void +cSampleManager::ReleaseDigitalHandle(void) +{ + if ( DIG ) + { +#ifdef EXTERNAL_3D_SOUND + prevprovider = curprovider; + release_existing(); + curprovider = -1; +#endif + AIL_digital_handle_release(DIG); + } +} + +void +cSampleManager::ReacquireDigitalHandle(void) +{ + if ( DIG ) + { + AIL_digital_handle_reacquire(DIG); +#ifdef EXTERNAL_3D_SOUND + if ( prevprovider != -1 ) + set_new_provider(prevprovider); +#endif + } +} + +bool8 +cSampleManager::Initialise(void) +{ + TRACE("start"); + + if ( _bSampmanInitialised ) + return TRUE; + + { + for ( int32 i = 0; i < TOTAL_AUDIO_SAMPLES; i++ ) + { + m_aSamples[i].nOffset = 0; + m_aSamples[i].nSize = 0; + m_aSamples[i].nFrequency = 22050; + m_aSamples[i].nLoopStart = 0; + m_aSamples[i].nLoopEnd = -1; + } + + m_nEffectsVolume = MAX_VOLUME; + m_nMusicVolume = MAX_VOLUME; + m_nEffectsFadeVolume = MAX_VOLUME; + m_nMusicFadeVolume = MAX_VOLUME; + + m_nMonoMode = 0; + } + +#ifdef EXTERNAL_3D_SOUND + // miles + TRACE("MILES"); + { + curprovider = -1; + prevprovider = -1; + + _usingMilesFast2D = FALSE; + usingEAX=0; + usingEAX3=0; + + _fEffectsLevel = 0.0f; + + _maxSamples = 0; + + opened_provider = NULL; + DIG = NULL; + + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + opened_samples[i] = NULL; + } +#endif + + // banks + TRACE("banks"); + { + fpSampleDescHandle = NULL; + fpSampleDataHandle = NULL; + + _nSampleDataEndOffset = 0; + + for ( int32 i = 0; i < MAX_SFX_BANKS; i++ ) + { + gBankLoaded[i] = LOADING_STATUS_NOT_LOADED; + nSampleBankDiscStartOffset[i] = 0; + nSampleBankSize[i] = 0; + nSampleBankMemoryStartAddress[i] = 0; + } + } + + // pedsfx + TRACE("pedsfx"); + { + for ( int32 i = 0; i < MAX_PEDSFX; i++ ) + { + nPedSlotSfx[i] = NO_SAMPLE; + nPedSlotSfxAddr[i] = 0; + } + + nCurrentPedSlot = 0; + } + + // channel volume + TRACE("vol"); + { + for ( int32 i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++ ) + nChannelVolume[i] = 0; + } + + TRACE("mss"); + { + AIL_set_redist_directory( "mss" ); + + AIL_startup(); + + AIL_set_preference(DIG_MIXER_CHANNELS, MAX_DIGITAL_MIXER_CHANNELS); + + DIG = AIL_open_digital_driver(DIGITALRATE, DIGITALBITS, DIGITALCHANNELS, 0); + if ( DIG == NULL ) + { + OutputDebugString(AIL_last_error()); + Terminate(); + return FALSE; + } + +#ifdef EXTERNAL_3D_SOUND + add_providers(); +#endif + + if ( !InitialiseSampleBanks() ) + { + Terminate(); + return FALSE; + } + + nSampleBankMemoryStartAddress[SFX_BANK_0] = (int32)AIL_mem_alloc_lock(nSampleBankSize[SFX_BANK_0]); + if ( !nSampleBankMemoryStartAddress[SFX_BANK_0] ) + { + Terminate(); + return FALSE; + } + + nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] = (int32)AIL_mem_alloc_lock(PED_BLOCKSIZE*MAX_PEDSFX); + + } + +#ifdef AUDIO_CACHE + TRACE("cache"); + FILE *cacheFile = fopen("audio\\sound.cache", "rb"); + if (cacheFile) { + fread(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + m_bInitialised = TRUE; + }else { +#endif + TRACE("cdrom"); + + S32 tatalms; + char filepath[MAX_PATH]; + + { + m_bInitialised = FALSE; + + while (TRUE) + { + int32 drive = 'C'; + + do + { + char latter[2]; + + latter[0] = drive; + latter[1] = '\0'; + + strcpy(m_szCDRomRootPath, latter); + strcat(m_szCDRomRootPath, ":\\"); + + if ( GetDriveType(m_szCDRomRootPath) == DRIVE_CDROM ) + { + FILE *f; +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, PS2StreamedNameTable[0]); + f = fopen(filepath, "rb"); + + if ( !f ) +#endif + { + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, StreamedNameTable[0]); + + f = fopen(filepath, "rb"); + } + if ( f ) + { + fclose(f); + + bool8 bFileNotFound = FALSE; + + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + { +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, PS2StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( !mp3Stream[0] ) +#endif + { + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + } + + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &tatalms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + nStreamLength[i] = tatalms; + } + else + { + bFileNotFound = TRUE; + break; + } + } + + if ( !bFileNotFound ) + { + m_bInitialised = TRUE; + break; + } + else + { + m_bInitialised = FALSE; + continue; + } + } + } + + } while ( ++drive <= 'Z' ); + + if ( !m_bInitialised ) + { +#if GTA_VERSION < GTA3_PC_STEAM && !defined(NO_CDCHECK) + FrontEndMenuManager.WaitForUserCD(); + if ( FrontEndMenuManager.m_bQuitGameNoCD ) + { + Terminate(); + return FALSE; + } + continue; +#else + m_bInitialised = TRUE; +#endif + } + + break; + } + } + +#if GTA_VERSION >= GTA3_PC_11 || defined(NO_CDCHECK) + // hddaudio + /** + Option for user to play audio files directly from hard disk. + Copy the contents of the PLAY discs Audio directory into your installed Grand Theft Auto III Audio directory. + Grand Theft Auto III still requires the presence of the PLAY disc when started. + This may give better performance on some machines (though worse on others). + **/ + TRACE("hddaudio 1.1 patch"); + { + int32 streamLength[TOTAL_STREAMED_SOUNDS]; + + bool8 bFileNotFound = FALSE; + char rootpath[MAX_PATH]; + + strcpy(_aHDDPath, m_szCDRomRootPath); + rootpath[0] = '\0'; + + FILE *f; + +#ifdef PS2_AUDIO_PATHS + f = fopen(PS2StreamedNameTable[0], "rb"); + if (!f) +#endif + + f = fopen(StreamedNameTable[0], "rb"); + + if ( f ) + { + fclose(f); + + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + { +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, rootpath); + strcat(filepath, PS2StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + if ( !mp3Stream[0] ) +#endif + { + strcpy(filepath, rootpath); + strcat(filepath, StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + } + + if ( mp3Stream[0] ) + { + AIL_stream_ms_position(mp3Stream[0], &tatalms, NULL); + + AIL_close_stream(mp3Stream[0]); + mp3Stream[0] = NULL; + + streamLength[i] = tatalms; + } + else + { + bFileNotFound = TRUE; + break; + } + } + + } + else + bFileNotFound = TRUE; + + if ( !bFileNotFound ) + { + strcpy(m_szCDRomRootPath, rootpath); + + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + nStreamLength[i] = streamLength[i]; + + _bUseHDDAudio = TRUE; + } + else + _bUseHDDAudio = FALSE; + } +#endif +#ifdef AUDIO_CACHE + cacheFile = fopen("audio\\sound.cache", "wb"); + fwrite(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + } +#endif + + TRACE("stream"); + { + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + mp3Stream [i] = NULL; + nStreamPan [i] = 63; + nStreamVolume[i] = 100; + } + } + + for ( int32 i = 0; i < MAX2DCHANNELS; i++ ) + { + opened_2dsamples[i] = AIL_allocate_sample_handle(DIG); + if ( opened_2dsamples[i] ) + { + AIL_init_sample(opened_2dsamples[i]); + AIL_set_sample_type(opened_2dsamples[i], DIG_F_MONO_16, DIG_PCM_SIGN); + } + } + + TRACE("providerset"); + { + _bSampmanInitialised = TRUE; + +#ifdef EXTERNAL_3D_SOUND + U32 n = 0; + + while ( n < m_nNumberOfProviders ) + { + if ( !strcmp(providers[n].name, "Miles Fast 2D Positional Audio") ) + { + set_new_provider(n); + break; + } + n++; + } + + if ( n == m_nNumberOfProviders ) + { + Terminate(); + return FALSE; + } +#endif + } + + TRACE("bank"); + + LoadSampleBank(SFX_BANK_0); + + // mp3 + TRACE("mp3"); + { + nNumMP3s = 0; + + _pMP3List = NULL; + + _FindMP3s(); + + if ( nNumMP3s != 0 ) + { + nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] = 0; + + for ( tMP3Entry *e = _pMP3List; e != NULL; e = e->pNext ) + { + e->nTrackStreamPos = nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER]; + nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] += e->nTrackLength; + } + + time_t t = time(NULL); + tm *localtm; + bool8 bUseRandomTable; + + if ( t == -1 ) + bUseRandomTable = TRUE; + else + { + bUseRandomTable = FALSE; + localtm = localtime(&t); + } + + int32 randval; + if ( bUseRandomTable ) + randval = AudioManager.m_anRandomTable[1]; + else + randval = localtm->tm_sec * localtm->tm_min; + + _CurMP3Index = randval % nNumMP3s; + + tMP3Entry *randmp3 = _pMP3List; + for ( int32 i = randval % nNumMP3s; i > 0; --i) + randmp3 = randmp3->pNext; + + if ( bUseRandomTable ) + _CurMP3Pos = AudioManager.m_anRandomTable[0] % randmp3->nTrackLength; + else + { + if ( localtm->tm_sec > 0 ) + { + int32 s = localtm->tm_sec; + _CurMP3Pos = s*s*s*s*s*s*s*s % randmp3->nTrackLength; + } + else + _CurMP3Pos = AudioManager.m_anRandomTable[0] % randmp3->nTrackLength; + } + } + else + _CurMP3Pos = 0; + + _bIsMp3Active = FALSE; + } + + TRACE("end"); + + return TRUE; +} + +void +cSampleManager::Terminate(void) +{ + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + if ( mp3Stream[i] ) + { + AIL_pause_stream(mp3Stream[i], 1); + AIL_close_stream(mp3Stream[i]); + mp3Stream[i] = NULL; + } + } + + for ( int32 i = 0; i < MAX2DCHANNELS; i++ ) + { + if ( opened_2dsamples[i] ) + { + AIL_release_sample_handle(opened_2dsamples[i]); + opened_2dsamples[i] = NULL; + } + } + +#ifdef EXTERNAL_3D_SOUND + release_existing(); +#endif + + _DeleteMP3Entries(); + + if ( nSampleBankMemoryStartAddress[SFX_BANK_0] != 0 ) + { + AIL_mem_free_lock((void *)nSampleBankMemoryStartAddress[SFX_BANK_0]); + nSampleBankMemoryStartAddress[SFX_BANK_0] = 0; + } + + if ( nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] != 0 ) + { + AIL_mem_free_lock((void *)nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS]); + nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] = 0; + } + + if ( DIG ) + { + AIL_close_digital_driver(DIG); + DIG = NULL; + } + + AIL_shutdown(); + + _bSampmanInitialised = FALSE; +} + +bool8 +cSampleManager::CheckForAnAudioFileOnCD(void) +{ +#if GTA_VERSION < GTA3_PC_STEAM && !defined(NO_CDCHECK) + char filepath[MAX_PATH]; + FILE *f; + +#ifdef PS2_AUDIO_PATHS +#if GTA_VERSION >= GTA3_PC_11 + if(_bUseHDDAudio) + strcpy(filepath, _aHDDPath); + else + strcpy(filepath, m_szCDRomRootPath); +#else + strcpy(filepath, m_szCDRomRootPath); +#endif // #if GTA_VERSION >= GTA3_PC_11 + + strcat(filepath, PS2StreamedNameTable[AudioManager.m_anRandomTable[1] % TOTAL_STREAMED_SOUNDS]); + + f = fopen(filepath, "rb"); + if ( !f ) +#endif // PS2_AUDIO_PATHS + { +#if GTA_VERSION >= GTA3_PC_11 + if (_bUseHDDAudio) + strcpy(filepath, _aHDDPath); + else + strcpy(filepath, m_szCDRomRootPath); +#else + strcpy(filepath, m_szCDRomRootPath); +#endif // #if GTA_VERSION >= GTA3_PC_11 + + strcat(filepath, StreamedNameTable[AudioManager.m_anRandomTable[1] % TOTAL_STREAMED_SOUNDS]); + + f = fopen(filepath, "rb"); + } + if ( f ) + { + fclose(f); + + return TRUE; + } + + return FALSE; + +#else + return TRUE; +#endif // #if GTA_VERSION < GTA3_PC_STEAM && !defined(NO_CDCHECK) +} + +char +cSampleManager::GetCDAudioDriveLetter(void) +{ +#if GTA_VERSION >= GTA3_PC_11 || defined(NO_CDCHECK) + if (_bUseHDDAudio) + { + if ( strlen(_aHDDPath) != 0 ) + return _aHDDPath[0]; + else + return '\0'; + } + else + { + if ( strlen(m_szCDRomRootPath) != 0 ) + return m_szCDRomRootPath[0]; + else + return '\0'; + } +#else + if ( strlen(m_szCDRomRootPath) != 0 ) + return m_szCDRomRootPath[0]; + else + return '\0'; +#endif +} + +void +cSampleManager::UpdateEffectsVolume(void) //[Y], cSampleManager::UpdateSoundBuffers ? +{ + if ( _bSampmanInitialised ) + { + for ( int32 i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++ ) + { +#ifdef EXTERNAL_3D_SOUND + if ( i < MAXCHANNELS ) + { + if ( opened_samples[i] && GetChannelUsedFlag(i) ) + { + if ( nChannelVolume[i] ) + { + AIL_set_3D_sample_volume(opened_samples[i], + m_nEffectsFadeVolume * nChannelVolume[i] * m_nEffectsVolume >> 14); + } + } + } + else +#endif + { + if ( opened_2dsamples[i - MAXCHANNELS] ) + { + if ( GetChannelUsedFlag(i - MAXCHANNELS) ) + { + if ( nChannelVolume[i - MAXCHANNELS] ) + { + AIL_set_sample_volume(opened_2dsamples[i - MAXCHANNELS], + m_nEffectsFadeVolume * nChannelVolume[i - MAXCHANNELS] * m_nEffectsVolume >> 14); + } + } + } + } + } + } +} + +void +cSampleManager::SetEffectsMasterVolume(uint8 nVolume) +{ + m_nEffectsVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicMasterVolume(uint8 nVolume) +{ + m_nMusicVolume = nVolume; +} + +void +cSampleManager::SetEffectsFadeVolume(uint8 nVolume) +{ + m_nEffectsFadeVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicFadeVolume(uint8 nVolume) +{ + m_nMusicFadeVolume = nVolume; +} + +void +cSampleManager::SetMonoMode(bool8 nMode) +{ + m_nMonoMode = nMode; +} + +bool8 +cSampleManager::LoadSampleBank(uint8 nBank) +{ + if ( CTimer::GetIsCodePaused() ) + return FALSE; + + if ( MusicManager.IsInitialised() + && MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && nBank != SFX_BANK_0 ) + { + return FALSE; + } + + if ( fseek(fpSampleDataHandle, nSampleBankDiscStartOffset[nBank], SEEK_SET) != 0 ) + return FALSE; + + if ( fread((void *)nSampleBankMemoryStartAddress[nBank], 1, nSampleBankSize[nBank],fpSampleDataHandle) != nSampleBankSize[nBank] ) + return FALSE; + + gBankLoaded[nBank] = LOADING_STATUS_LOADED; + + return TRUE; +} + +void +cSampleManager::UnloadSampleBank(uint8 nBank) +{ + gBankLoaded[nBank] = LOADING_STATUS_NOT_LOADED; +} + +int8 +cSampleManager::IsSampleBankLoaded(uint8 nBank) +{ + return gBankLoaded[nBank]; +} + +uint8 +cSampleManager::IsPedCommentLoaded(uint32 nComment) +{ + int8 slot; + + for ( int32 i = 0; i < _TODOCONST(3); i++ ) + { + slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nComment == nPedSlotSfx[slot] ) + return LOADING_STATUS_LOADED; + } + + return LOADING_STATUS_NOT_LOADED; +} + +int32 +cSampleManager::_GetPedCommentSlot(uint32 nComment) +{ + int8 slot; + + for ( int32 i = 0; i < _TODOCONST(3); i++ ) + { + slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nComment == nPedSlotSfx[slot] ) + return slot; + } + + return -1; +} + +bool8 +cSampleManager::LoadPedComment(uint32 nComment) +{ + if ( CTimer::GetIsCodePaused() ) + return FALSE; + + // no talking peds during cutsenes or the game end + if ( MusicManager.IsInitialised() ) + { + switch ( MusicManager.GetMusicMode() ) + { + case MUSICMODE_CUTSCENE: + { + return FALSE; + + break; + } + + case MUSICMODE_FRONTEND: + { + if ( MusicManager.GetNextTrack() == STREAMED_SOUND_GAME_COMPLETED ) + return FALSE; + + break; + } + } + } + + if ( fseek(fpSampleDataHandle, m_aSamples[nComment].nOffset, SEEK_SET) != 0 ) + return FALSE; + + if ( fread((void *)(nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE*nCurrentPedSlot), 1, m_aSamples[nComment].nSize, fpSampleDataHandle) != m_aSamples[nComment].nSize ) + return FALSE; + + nPedSlotSfxAddr[nCurrentPedSlot] = nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE*nCurrentPedSlot; + nPedSlotSfx [nCurrentPedSlot] = nComment; + + if ( ++nCurrentPedSlot >= MAX_PEDSFX ) + nCurrentPedSlot = 0; + + return TRUE; +} + +int32 +cSampleManager::GetBankContainingSound(uint32 offset) +{ + if ( offset >= BankStartOffset[SFX_BANK_PED_COMMENTS] ) + return SFX_BANK_PED_COMMENTS; + + if ( offset >= BankStartOffset[SFX_BANK_0] ) + return SFX_BANK_0; + + return INVALID_SFX_BANK; +} + +uint32 +cSampleManager::GetSampleBaseFrequency(uint32 nSample) +{ + return m_aSamples[nSample].nFrequency; +} + +uint32 +cSampleManager::GetSampleLoopStartOffset(uint32 nSample) +{ + return m_aSamples[nSample].nLoopStart; +} + +int32 +cSampleManager::GetSampleLoopEndOffset(uint32 nSample) +{ + return m_aSamples[nSample].nLoopEnd; +} + +uint32 +cSampleManager::GetSampleLength(uint32 nSample) +{ + return m_aSamples[nSample].nSize >> 1; +} + +bool8 +cSampleManager::UpdateReverb(void) +{ +#ifdef EXTERNAL_3D_SOUND + if ( !usingEAX ) + return FALSE; + + if ( AudioManager.m_FrameCounter & 15 ) + return FALSE; + +#ifdef AUDIO_REFLECTIONS + float y = AudioManager.m_afReflectionsDistances[REFLECTION_TOP] + AudioManager.m_afReflectionsDistances[REFLECTION_BOTTOM]; + float x = AudioManager.m_afReflectionsDistances[REFLECTION_LEFT] + AudioManager.m_afReflectionsDistances[REFLECTION_RIGHT]; + float z = AudioManager.m_afReflectionsDistances[REFLECTION_UP]; +#else + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; +#endif + + float normy = norm(y, 5.0f, 40.0f); + float normx = norm(x, 5.0f, 40.0f); + float normz = norm(z, 5.0f, 40.0f); + + float fRatio; + + if ( normy == 0.0f ) + { + if ( normx == 0.0f ) + { + if ( normz == 0.0f ) + fRatio = 0.3f; + else + fRatio = 0.5f; + } + else + { + fRatio = 0.3f; + } + } + else + { + if ( normx == 0.0f ) + { + if ( normz == 0.0f ) + fRatio = 0.3f; + else + fRatio = 0.5f; + } + else + { + if ( normz == 0.0f ) + fRatio = 0.3f; + else + fRatio = (normy+normx+normz) / 3.0f; + } + } + + fRatio = Clamp(fRatio, usingEAX3==1 ? 0.0f : 0.30f, 1.0f); + + if ( fRatio == _fPrevEaxRatioDestination ) + return FALSE; + + if ( usingEAX3 ) + { + if ( EAX3ListenerInterpolate(&StartEAX3, &FinishEAX3, fRatio, &EAX3Params, false) ) + { + AIL_set_3D_provider_preference(opened_provider, "EAX all parameters", &EAX3Params); + _fEffectsLevel = 1.0f - fRatio * 0.5f; + } + } + else + { + if ( _usingMilesFast2D ) + _fEffectsLevel = (1.0f - fRatio) * 0.4f; + else + _fEffectsLevel = (1.0f - fRatio) * 0.7f; + } + + _fPrevEaxRatioDestination = fRatio; + + return TRUE; +#endif + return FALSE; +} + +void +cSampleManager::SetChannelReverbFlag(uint32 nChannel, bool8 nReverbFlag) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( usingEAX ) + { + if ( nReverbFlag != FALSE ) + { + if ( !b2d ) + AIL_set_3D_sample_effects_level(opened_samples[nChannel], _fEffectsLevel); + } + else + { + if ( !b2d ) + AIL_set_3D_sample_effects_level(opened_samples[nChannel], 0.0f); + } + } +#endif +} + +bool8 +cSampleManager::InitialiseChannel(uint32 nChannel, uint32 nSfx, uint8 nBank) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } +#endif + + int32 addr; + + if ( nSfx < SAMPLEBANK_MAX ) + { + if ( !IsSampleBankLoaded(nBank) ) + return FALSE; + + addr = nSampleBankMemoryStartAddress[nBank] + m_aSamples[nSfx].nOffset - m_aSamples[BankStartOffset[nBank]].nOffset; + } + else + { + int32 i; + for ( i = 0; i < _TODOCONST(3); i++ ) + { + int32 slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nSfx == nPedSlotSfx[slot] ) + { + addr = nPedSlotSfxAddr[slot]; + break; + } + } + + if (i == _TODOCONST(3)) + return FALSE; + } + +#ifdef EXTERNAL_3D_SOUND + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + { + AIL_set_sample_address(opened_2dsamples[nChannel - MAXCHANNELS], (void *)addr, m_aSamples[nSfx].nSize); + return TRUE; + } + else + return FALSE; +#ifdef EXTERNAL_3D_SOUND + } + else + { + AILSOUNDINFO info; + + info.format = WAVE_FORMAT_PCM; + info.data_ptr = (void *)addr; + info.channels = 1; + info.data_len = m_aSamples[nSfx].nSize; + info.rate = m_aSamples[nSfx].nFrequency; + info.bits = 16; + + if ( AIL_set_3D_sample_info(opened_samples[nChannel], &info) == 0 ) + { + OutputDebugString(AIL_last_error()); + return FALSE; + } + + return TRUE; + } +#endif +} + +#ifdef EXTERNAL_3D_SOUND +void +cSampleManager::SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume) +{ + uint32 vol = nVolume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + nChannelVolume[nChannel] = vol; + + // increase the volume for JB.MP3 and S4_BDBD.MP3 + if ( MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && MusicManager.GetNextTrack() != STREAMED_SOUND_NEWS_INTRO + && MusicManager.GetNextTrack() != STREAMED_SOUND_CUTSCENE_SAL4_BDBD ) + { + nChannelVolume[nChannel] >>= 2; + } + + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_volume(opened_samples[nChannel], m_nEffectsFadeVolume*nChannelVolume[nChannel]*m_nEffectsVolume >> 14); + +} + +void +cSampleManager::SetChannel3DPosition(uint32 nChannel, float fX, float fY, float fZ) +{ + if ( opened_samples[nChannel] ) + AIL_set_3D_position(opened_samples[nChannel], -fX, fY, fZ); +} + +void +cSampleManager::SetChannel3DDistances(uint32 nChannel, float fMax, float fMin) +{ + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_distances(opened_samples[nChannel], fMax, fMin); +} +#endif + +void +cSampleManager::SetChannelVolume(uint32 nChannel, uint32 nVolume) +{ + uint32 vol = nVolume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + +#ifdef EXTERNAL_3D_SOUND + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { +#endif + nChannelVolume[nChannel] = vol; + + // increase the volume for JB.MP3 and S4_BDBD.MP3 + if ( MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && MusicManager.GetNextTrack() != STREAMED_SOUND_NEWS_INTRO + && MusicManager.GetNextTrack() != STREAMED_SOUND_CUTSCENE_SAL4_BDBD ) + { + nChannelVolume[nChannel] >>= 2; + } + + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + { + AIL_set_sample_volume(opened_2dsamples[nChannel - MAXCHANNELS], + m_nEffectsFadeVolume*vol*m_nEffectsVolume >> 14); + } + +#ifdef EXTERNAL_3D_SOUND + break; + } + } +#endif +} + +void +cSampleManager::SetChannelPan(uint32 nChannel, uint32 nPan) +{ +#ifdef EXTERNAL_3D_SOUND + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { +#endif +#if !defined(FIX_BUGS) && defined(EXTERNAL_3D_SOUND) + if ( opened_samples[nChannel - MAXCHANNELS] ) // BUG +#else + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) +#endif + AIL_set_sample_pan(opened_2dsamples[nChannel - MAXCHANNELS], nPan); + +#ifdef EXTERNAL_3D_SOUND + break; + } + } +#endif +} + +void +cSampleManager::SetChannelFrequency(uint32 nChannel, uint32 nFreq) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_set_sample_playback_rate(opened_2dsamples[nChannel - MAXCHANNELS], nFreq); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_playback_rate(opened_samples[nChannel], nFreq); + } +#endif +} + +void +cSampleManager::SetChannelLoopPoints(uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_set_sample_loop_block(opened_2dsamples[nChannel - MAXCHANNELS], nLoopStart, nLoopEnd); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_loop_block(opened_samples[nChannel], nLoopStart, nLoopEnd); + } +#endif +} + +void +cSampleManager::SetChannelLoopCount(uint32 nChannel, uint32 nLoopCount) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_set_sample_loop_count(opened_2dsamples[nChannel - MAXCHANNELS], nLoopCount); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + AIL_set_3D_sample_loop_count(opened_samples[nChannel], nLoopCount); + } +#endif +} + +bool8 +cSampleManager::GetChannelUsedFlag(uint32 nChannel) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + return AIL_sample_status(opened_2dsamples[nChannel - MAXCHANNELS]) == SMP_PLAYING; + else + return FALSE; +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + return AIL_3D_sample_status(opened_samples[nChannel]) == SMP_PLAYING; + else + return FALSE; + } +#endif + +} + +void +cSampleManager::StartChannel(uint32 nChannel) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_start_sample(opened_2dsamples[nChannel - MAXCHANNELS]); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + AIL_start_3D_sample(opened_samples[nChannel]); + } +#endif +} + +void +cSampleManager::StopChannel(uint32 nChannel) +{ +#ifdef EXTERNAL_3D_SOUND + bool8 b2d = FALSE; + + switch ( nChannel ) + { + case CHANNEL_POLICE_RADIO: + { + b2d = TRUE; + break; + } + } + + if ( b2d ) + { +#endif + if ( opened_2dsamples[nChannel - MAXCHANNELS] ) + AIL_end_sample(opened_2dsamples[nChannel - MAXCHANNELS]); +#ifdef EXTERNAL_3D_SOUND + } + else + { + if ( opened_samples[nChannel] ) + { + if ( AIL_3D_sample_status(opened_samples[nChannel]) == SMP_PLAYING ) + AIL_end_3D_sample(opened_samples[nChannel]); + } + } +#endif +} + +void +cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( nFile < TOTAL_STREAMED_SOUNDS ) + { + if ( mp3Stream[nStream] ) + { + AIL_pause_stream(mp3Stream[nStream], 1); + AIL_close_stream(mp3Stream[nStream]); + } + + char filepath[MAX_PATH]; +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filepath, 0); + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filepath, m_szCDRomRootPath); + strcat(filepath, StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filepath, 0); + } + + if ( mp3Stream[nStream] ) + { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_service_stream(mp3Stream[nStream], 1); + } + else + OutputDebugString(AIL_last_error()); + } + } +} + +void +cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + AIL_pause_stream(mp3Stream[nStream], nPauseFlag != FALSE); + } +} + +void +cSampleManager::StartPreloadedStreamedFile(uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + AIL_start_stream(mp3Stream[nStream]); + } +} + +bool8 +cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) +{ + uint32 i = 0; + uint32 position = nPos; + char filename[MAX_PATH]; + + if ( !m_bInitialised || nFile >= TOTAL_STREAMED_SOUNDS ) + return FALSE; + + if ( mp3Stream[nStream] ) + { + AIL_pause_stream(mp3Stream[nStream], 1); + AIL_close_stream(mp3Stream[nStream]); + } + if ( nFile == STREAMED_SOUND_RADIO_MP3_PLAYER ) + { + do + { + // Just switched to MP3 player + if ( !_bIsMp3Active && i == 0 ) + { + if ( nPos > nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] ) + position = 0; + tMP3Entry *e = _pMP3List; + + // Try to continue from previous song, if already started + if(!_GetMP3PosFromStreamPos(&position, &e) && !e) { + nFile = 0; +#ifdef PS2_AUDIO_PATHS + strcpy(filename, m_szCDRomRootPath); + strcat(filename, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filename, m_szCDRomRootPath); + strcat(filename, StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + } + if ( mp3Stream[nStream] ) + { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], position); + AIL_pause_stream(mp3Stream[nStream], 0); + return TRUE; + } + return FALSE; + + } else { + if ( e->pLinkPath != NULL ) + mp3Stream[nStream] = AIL_open_stream(DIG, e->pLinkPath, 0); + else { + strcpy(filename, _mp3DirectoryPath); + strcat(filename, e->aFilename); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + } + + if ( mp3Stream[nStream] ) { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], position); + AIL_pause_stream(mp3Stream[nStream], 0); + + _bIsMp3Active = TRUE; + + return TRUE; + } + // fall through, start playing from another song + } + } else { + if(++_CurMP3Index >= nNumMP3s) _CurMP3Index = 0; + + _CurMP3Pos = 0; + + tMP3Entry *mp3 = _GetMP3EntryByIndex(_CurMP3Index); + if ( !mp3 ) + { + mp3 = _pMP3List; + if ( !_pMP3List ) + { + nFile = 0; + _bIsMp3Active = FALSE; +#ifdef PS2_AUDIO_PATHS + strcpy(filename, m_szCDRomRootPath); + strcat(filename, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filename, m_szCDRomRootPath); + strcat(filename, StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + } + if ( mp3Stream[nStream] ) + { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], position); + AIL_pause_stream(mp3Stream[nStream], 0); + return TRUE; + } + return FALSE; + } + } + if(mp3->pLinkPath != NULL) + mp3Stream[nStream] = AIL_open_stream(DIG, mp3->pLinkPath, 0); + else { + strcpy(filename, _mp3DirectoryPath); + strcat(filename, mp3->aFilename); + + mp3Stream[nStream] = + AIL_open_stream(DIG, filename, 0); + } + + if(mp3Stream[nStream]) { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], 0); + AIL_pause_stream(mp3Stream[nStream], 0); +#ifdef FIX_BUGS + _bIsMp3Active = TRUE; +#endif + return TRUE; + } + + } + _bIsMp3Active = FALSE; + } + while ( ++i < nNumMP3s ); + position = 0; + nFile = 0; + } +#ifdef PS2_AUDIO_PATHS + strcpy(filename, m_szCDRomRootPath); + strcat(filename, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filename, m_szCDRomRootPath); + strcat(filename, StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + } + if ( mp3Stream[nStream] ) + { + AIL_set_stream_loop_count(mp3Stream[nStream], 1); + AIL_set_stream_ms_position(mp3Stream[nStream], position); + AIL_pause_stream(mp3Stream[nStream], 0); + return TRUE; + } + return FALSE; +} + +void +cSampleManager::StopStreamedFile(uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + { + AIL_pause_stream(mp3Stream[nStream], 1); + + AIL_close_stream(mp3Stream[nStream]); + mp3Stream[nStream] = NULL; + + if ( nStream == 0 ) + _bIsMp3Active = FALSE; + } + } +} + +int32 +cSampleManager::GetStreamedFilePosition(uint8 nStream) +{ + S32 currentms; + + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + { + if ( _bIsMp3Active ) + { + tMP3Entry *mp3 = _GetMP3EntryByIndex(_CurMP3Index); + + if ( mp3 != NULL ) + { + AIL_stream_ms_position(mp3Stream[nStream], NULL, ¤tms); + return currentms + mp3->nTrackStreamPos; + } + else + return 0; + } + else + { + AIL_stream_ms_position(mp3Stream[nStream], NULL, ¤tms); + return currentms; + } + } + } + + return 0; +} + +void +cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, bool8 nEffectFlag, uint8 nStream) +{ + uint8 vol = nVolume; + + if ( m_bInitialised ) + { + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + nStreamVolume[nStream] = vol; + nStreamPan[nStream] = nPan; + + if ( mp3Stream[nStream] ) + { + if ( nEffectFlag ) + AIL_set_stream_volume(mp3Stream[nStream], m_nEffectsFadeVolume*vol*m_nEffectsVolume >> 14); + else + AIL_set_stream_volume(mp3Stream[nStream], m_nMusicFadeVolume*vol*m_nMusicVolume >> 14); + + AIL_set_stream_pan(mp3Stream[nStream], nPan); + } + } +} + +int32 +cSampleManager::GetStreamedFileLength(uint8 nStream) +{ + if ( m_bInitialised ) + return nStreamLength[nStream]; + + return 0; +} + +bool8 +cSampleManager::IsStreamPlaying(uint8 nStream) +{ + if ( m_bInitialised ) + { + if ( mp3Stream[nStream] ) + { + if ( AIL_stream_status(mp3Stream[nStream]) == SMP_PLAYING ) + return TRUE; + else + return FALSE; + } + } + + return FALSE; +} + +bool8 +cSampleManager::InitialiseSampleBanks(void) +{ + int32 nBank = SFX_BANK_0; + + fpSampleDescHandle = fopen(SampleBankDescFilename, "rb"); + if ( fpSampleDescHandle == NULL ) + return FALSE; + + fpSampleDataHandle = fopen(SampleBankDataFilename, "rb"); + if ( fpSampleDataHandle == NULL ) + { + fclose(fpSampleDescHandle); + fpSampleDescHandle = NULL; + + return FALSE; + } + + fseek(fpSampleDataHandle, 0, SEEK_END); + _nSampleDataEndOffset = ftell(fpSampleDataHandle); + rewind(fpSampleDataHandle); + + fread(m_aSamples, sizeof(tSample), TOTAL_AUDIO_SAMPLES, fpSampleDescHandle); + + fclose(fpSampleDescHandle); + fpSampleDescHandle = NULL; + + for ( int32 i = 0; i < TOTAL_AUDIO_SAMPLES; i++ ) + { +#ifdef FIX_BUGS + if (nBank >= MAX_SFX_BANKS) break; +#endif + if ( BankStartOffset[nBank] == BankStartOffset[SFX_BANK_0] + i ) + { + nSampleBankDiscStartOffset[nBank] = m_aSamples[i].nOffset; + nBank++; + } + } + + nSampleBankSize[SFX_BANK_0] = nSampleBankDiscStartOffset[SFX_BANK_PED_COMMENTS] - nSampleBankDiscStartOffset[SFX_BANK_0]; + nSampleBankSize[SFX_BANK_PED_COMMENTS] = _nSampleDataEndOffset - nSampleBankDiscStartOffset[SFX_BANK_PED_COMMENTS]; + + return TRUE; +} + +#endif diff --git a/src/audio/sampman_null.cpp b/src/audio/sampman_null.cpp new file mode 100644 index 0000000..d607836 --- /dev/null +++ b/src/audio/sampman_null.cpp @@ -0,0 +1,372 @@ +#include "common.h" +#if !defined(AUDIO_OAL) && !defined(AUDIO_MSS) +#include "sampman.h" +#include "AudioManager.h" + +cSampleManager SampleManager; +bool8 _bSampmanInitialised = FALSE; + +uint32 BankStartOffset[MAX_SFX_BANKS]; +uint32 nNumMP3s; + +cSampleManager::cSampleManager(void) +{ + ; +} + +cSampleManager::~cSampleManager(void) +{ + +} + +#ifdef EXTERNAL_3D_SOUND +void cSampleManager::SetSpeakerConfig(int32 nConfig) +{ + +} + +uint32 cSampleManager::GetMaximumSupportedChannels(void) +{ + return MAXCHANNELS; +} + +uint32 cSampleManager::GetNum3DProvidersAvailable() +{ + return 1; +} + +void cSampleManager::SetNum3DProvidersAvailable(uint32 num) +{ + +} + +char *cSampleManager::Get3DProviderName(uint8 id) +{ + static char name[64] = "NULL"; + return name; +} + +void cSampleManager::Set3DProviderName(uint8 id, char *name) +{ + +} + +int8 cSampleManager::GetCurrent3DProviderIndex(void) +{ + return 0; +} + +int8 cSampleManager::SetCurrent3DProvider(uint8 nProvider) +{ + return 0; +} +#endif + +bool8 +cSampleManager::IsMP3RadioChannelAvailable(void) +{ + return nNumMP3s != 0; +} + + +void cSampleManager::ReleaseDigitalHandle(void) +{ +} + +void cSampleManager::ReacquireDigitalHandle(void) +{ +} + +bool8 +cSampleManager::Initialise(void) +{ + return TRUE; +} + +void +cSampleManager::Terminate(void) +{ + +} + +bool8 cSampleManager::CheckForAnAudioFileOnCD(void) +{ + return TRUE; +} + +char cSampleManager::GetCDAudioDriveLetter(void) +{ + return '\0'; +} + +void +cSampleManager::UpdateEffectsVolume(void) +{ + +} + +void +cSampleManager::SetEffectsMasterVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetMusicMasterVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetEffectsFadeVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetMusicFadeVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetMonoMode(uint8 nMode) +{ +} + +bool8 +cSampleManager::LoadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + return FALSE; +} + +void +cSampleManager::UnloadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); +} + +int8 +cSampleManager::IsSampleBankLoaded(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + + return LOADING_STATUS_NOT_LOADED; +} + +uint8 +cSampleManager::IsPedCommentLoaded(uint32 nComment) +{ + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + + return LOADING_STATUS_NOT_LOADED; +} + + +int32 +cSampleManager::_GetPedCommentSlot(uint32 nComment) +{ + return -1; +} + +bool8 +cSampleManager::LoadPedComment(uint32 nComment) +{ + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + return FALSE; +} + +int32 +cSampleManager::GetBankContainingSound(uint32 offset) +{ + return INVALID_SFX_BANK; +} + +uint32 +cSampleManager::GetSampleBaseFrequency(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return 0; +} + +uint32 +cSampleManager::GetSampleLoopStartOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return 0; +} + +int32 +cSampleManager::GetSampleLoopEndOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return 0; +} + +uint32 +cSampleManager::GetSampleLength(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return 0; +} + +bool8 cSampleManager::UpdateReverb(void) +{ + return FALSE; +} + +void +cSampleManager::SetChannelReverbFlag(uint32 nChannel, bool8 nReverbFlag) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +bool8 +cSampleManager::InitialiseChannel(uint32 nChannel, uint32 nSfx, uint8 nBank) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + return FALSE; +} + +#ifdef EXTERNAL_3D_SOUND +void +cSampleManager::SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume) +{ + ASSERT( nChannel < MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannel3DPosition(uint32 nChannel, float fX, float fY, float fZ) +{ + ASSERT( nChannel < MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannel3DDistances(uint32 nChannel, float fMax, float fMin) +{ + ASSERT( nChannel < MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} +#endif + +void +cSampleManager::SetChannelVolume(uint32 nChannel, uint32 nVolume) +{ + ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannelPan(uint32 nChannel, uint32 nPan) +{ + ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannelFrequency(uint32 nChannel, uint32 nFreq) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannelLoopPoints(uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::SetChannelLoopCount(uint32 nChannel, uint32 nLoopCount) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +bool8 +cSampleManager::GetChannelUsedFlag(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + + return FALSE; +} + +void +cSampleManager::StartChannel(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::StopChannel(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +void +cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +void +cSampleManager::StartPreloadedStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +bool8 +cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + return FALSE; +} + +void +cSampleManager::StopStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +int32 +cSampleManager::GetStreamedFilePosition(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + return 0; +} + +void +cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, uint8 nEffectFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); +} + +int32 +cSampleManager::GetStreamedFileLength(uint8 nStream) +{ + ASSERT( nStream < TOTAL_STREAMED_SOUNDS ); + + return 1; +} + +bool8 +cSampleManager::IsStreamPlaying(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + return FALSE; +} + +bool8 +cSampleManager::InitialiseSampleBanks(void) +{ + + return TRUE; +} + +#endif diff --git a/src/audio/sampman_oal.cpp b/src/audio/sampman_oal.cpp new file mode 100644 index 0000000..d59b86e --- /dev/null +++ b/src/audio/sampman_oal.cpp @@ -0,0 +1,1973 @@ +//#define JUICY_OAL + +#ifdef AUDIO_OAL +#include + +#include "eax.h" +#include "eax-util.h" + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include + +// for user MP3s +#include +#include +#include +#else +#define _getcwd getcwd +#endif + +#if defined _MSC_VER && !defined CMAKE_NO_AUTOLINK +#pragma comment( lib, "OpenAL32.lib" ) +#endif + +#include "common.h" +#include "crossplatform.h" + +#include "sampman.h" + +#include "oal/oal_utils.h" +#include "oal/aldlist.h" +#include "oal/channel.h" + +#include +#ifdef MULTITHREADED_AUDIO +#include +#include +#include +#endif +#include "oal/stream.h" + +#include "AudioManager.h" +#include "MusicManager.h" +#include "Frontend.h" +#include "Timer.h" +#ifdef AUDIO_OAL_USE_OPUS +#include +#endif + +//TODO: fix eax3 reverb + +cSampleManager SampleManager; +bool8 _bSampmanInitialised = FALSE; + +uint32 BankStartOffset[MAX_SFX_BANKS]; + +int prevprovider=-1; +int curprovider=-1; +int usingEAX=0; +int usingEAX3=0; +//int speaker_type=0; +ALCdevice *ALDevice = NULL; +ALCcontext *ALContext = NULL; +unsigned int _maxSamples; +float _fPrevEaxRatioDestination; +bool _effectsSupported = false; +bool _usingEFX; +float _fEffectsLevel; +ALuint ALEffect = AL_EFFECT_NULL; +ALuint ALEffectSlot = AL_EFFECTSLOT_NULL; +struct +{ + const char *id; + char name[256]; + int sources; + bool bSupportsFx; +}providers[MAXPROVIDERS]; + +int defaultProvider; + + +char SampleBankDescFilename[] = "audio/sfx.SDT"; +char SampleBankDataFilename[] = "audio/sfx.RAW"; + +FILE *fpSampleDescHandle; +#ifdef OPUS_SFX +OggOpusFile *fpSampleDataHandle; +#else +FILE *fpSampleDataHandle; +#endif +int8 gBankLoaded [MAX_SFX_BANKS]; +int32 nSampleBankDiscStartOffset [MAX_SFX_BANKS]; +int32 nSampleBankSize [MAX_SFX_BANKS]; +uintptr nSampleBankMemoryStartAddress[MAX_SFX_BANKS]; +int32 _nSampleDataEndOffset; + +int32 nPedSlotSfx [MAX_PEDSFX]; +int32 nPedSlotSfxAddr[MAX_PEDSFX]; +uint8 nCurrentPedSlot; + +CChannel aChannel[NUM_CHANNELS]; +uint8 nChannelVolume[NUM_CHANNELS]; + +uint32 nStreamLength[TOTAL_STREAMED_SOUNDS]; +ALuint ALStreamSources[MAX_STREAMS][2]; +ALuint ALStreamBuffers[MAX_STREAMS][NUM_STREAMBUFFERS]; + +struct tMP3Entry +{ + char aFilename[MAX_PATH]; + + uint32 nTrackLength; + uint32 nTrackStreamPos; + + tMP3Entry* pNext; + char* pLinkPath; +}; + +uint32 nNumMP3s; +tMP3Entry* _pMP3List; +char _mp3DirectoryPath[MAX_PATH]; +CStream *aStream[MAX_STREAMS]; +uint8 nStreamPan [MAX_STREAMS]; +uint8 nStreamVolume[MAX_STREAMS]; +uint32 _CurMP3Index; +int32 _CurMP3Pos; +bool8 _bIsMp3Active; +/////////////////////////////////////////////////////////////// +// Env Size Diffus Room RoomHF RoomLF DecTm DcHF DcLF Refl RefDel Ref Pan Revb RevDel Rev Pan EchTm EchDp ModTm ModDp AirAbs HFRef LFRef RRlOff FLAGS +EAXLISTENERPROPERTIES StartEAX3 = + {26, 1.7f, 0.8f, -1000, -1000, -100, 4.42f, 0.14f, 1.00f, 429, 0.014f, 0.00f,0.00f,0.00f, 1023, 0.021f, 0.00f,0.00f,0.00f, 0.250f, 0.000f, 0.250f, 0.000f, -5.0f, 2727.1f, 250.0f, 0.00f, 0x3f }; + +EAXLISTENERPROPERTIES FinishEAX3 = + {26, 100.0f, 1.0f, 0, -1000, -2200, 20.0f, 1.39f, 1.00f, 1000, 0.069f, 0.00f,0.00f,0.00f, 400, 0.100f, 0.00f,0.00f,0.00f, 0.250f, 1.000f, 3.982f, 0.000f, -18.0f, 3530.8f, 417.9f, 6.70f, 0x3f }; + +EAXLISTENERPROPERTIES EAX3Params; + + +bool IsFXSupported() +{ + return _effectsSupported; // usingEAX || usingEAX3 || _usingEFX; +} + +void EAX_SetAll(const EAXLISTENERPROPERTIES *allparameters) +{ + if ( usingEAX || usingEAX3 ) + EAX3_Set(ALEffect, allparameters); + else + EFX_Set(ALEffect, allparameters); +} + +static void +add_providers() +{ + SampleManager.SetNum3DProvidersAvailable(0); + + static ALDeviceList DeviceList; + ALDeviceList *pDeviceList = &DeviceList; + + if ((pDeviceList) && (pDeviceList->GetNumDevices())) + { + const int devNumber = Min(pDeviceList->GetNumDevices(), MAXPROVIDERS); + int n = 0; + + //for (int i = 0; i < devNumber; i++) + int i = pDeviceList->GetDefaultDevice(); + { + if ( n < MAXPROVIDERS ) + { + providers[n].id = pDeviceList->GetDeviceName(i); + strcpy(providers[n].name, "OPENAL SOFT"); + providers[n].sources = pDeviceList->GetMaxNumSources(i); + SampleManager.Set3DProviderName(n, providers[n].name); + n++; + } + + if ( alGetEnumValue("AL_EFFECT_EAXREVERB") != 0 + || pDeviceList->IsExtensionSupported(i, ADEXT_EAX2) + || pDeviceList->IsExtensionSupported(i, ADEXT_EAX3) + || pDeviceList->IsExtensionSupported(i, ADEXT_EAX4) + || pDeviceList->IsExtensionSupported(i, ADEXT_EAX5) ) + { + providers[n - 1].bSupportsFx = true; + if ( n < MAXPROVIDERS ) + { + providers[n].id = pDeviceList->GetDeviceName(i); + strcpy(providers[n].name, "OPENAL SOFT EAX"); + providers[n].sources = pDeviceList->GetMaxNumSources(i); + providers[n].bSupportsFx = true; + SampleManager.Set3DProviderName(n, providers[n].name); + n++; + } + + if ( n < MAXPROVIDERS ) + { + providers[n].id = pDeviceList->GetDeviceName(i); + strcpy(providers[n].name, "OPENAL SOFT EAX3"); + providers[n].sources = pDeviceList->GetMaxNumSources(i); + providers[n].bSupportsFx = true; + SampleManager.Set3DProviderName(n, providers[n].name); + n++; + } + } + } + SampleManager.SetNum3DProvidersAvailable(n); + + for(int j=n;jGetDefaultDevice(); + //if ( defaultProvider > MAXPROVIDERS ) + defaultProvider = 0; + } +} + +static void +release_existing() +{ + if ( IsFXSupported() ) + { + if ( alIsEffect(ALEffect) ) + { + alEffecti(ALEffect, AL_EFFECT_TYPE, AL_EFFECT_NULL); + } + + if (alIsAuxiliaryEffectSlot(ALEffectSlot)) + { + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, AL_EFFECT_NULL); + } + } + + DEV("release_existing()\n"); +} + +static bool8 +set_new_provider(int index) +{ + if ( curprovider == index ) + return TRUE; + + curprovider = index; + + release_existing(); + + if ( curprovider != -1 ) + { + DEV("set_new_provider()\n"); + + usingEAX = 0; + usingEAX3 = 0; + _usingEFX = false; + + if ( !strcmp(&providers[index].name[strlen(providers[index].name) - strlen(" EAX3")], " EAX3") + && alcIsExtensionPresent(ALDevice, (ALCchar*)ALC_EXT_EFX_NAME) ) + { + + usingEAX = 1; + usingEAX3 = 1; + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + EAX_SetAll(&FinishEAX3); + + DEV("EAX3\n"); + } + else if ( alcIsExtensionPresent(ALDevice, (ALCchar*)ALC_EXT_EFX_NAME) ) + { + + if ( !strcmp(&providers[index].name[strlen(providers[index].name) - strlen(" EAX")], " EAX")) + { + usingEAX = 1; + DEV("EAX1\n"); + } + else + { + _usingEFX = true; + DEV("EFX\n"); + } + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + EAX_SetAll(&EAX30_ORIGINAL_PRESETS[EAX_ENVIRONMENT_CAVE]); + } + + //SampleManager.SetSpeakerConfig(speaker_type); + + if ( IsFXSupported() ) + { + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + aChannel[i].SetReverbMix(ALEffectSlot, 0.0f); + } + + return TRUE; + } + + return FALSE; +} + +static bool8 +IsThisTrackAt16KHz(uint32 track) +{ + return track == STREAMED_SOUND_RADIO_CHAT; +} + +cSampleManager::cSampleManager(void) +{ + ; +} + +cSampleManager::~cSampleManager(void) +{ + +} + +void cSampleManager::SetSpeakerConfig(int32 nConfig) +{ + +} + +uint32 cSampleManager::GetMaximumSupportedChannels(void) +{ + if ( _maxSamples > MAXCHANNELS ) + return MAXCHANNELS; + + return _maxSamples; +} + +uint32 cSampleManager::GetNum3DProvidersAvailable() +{ + return m_nNumberOfProviders; +} + +void cSampleManager::SetNum3DProvidersAvailable(uint32 num) +{ + m_nNumberOfProviders = num; +} + +char *cSampleManager::Get3DProviderName(uint8 id) +{ + return m_aAudioProviders[id]; +} + +void cSampleManager::Set3DProviderName(uint8 id, char *name) +{ + m_aAudioProviders[id] = name; +} + +int8 cSampleManager::GetCurrent3DProviderIndex(void) +{ + return curprovider; +} + +int8 cSampleManager::SetCurrent3DProvider(uint8 nProvider) +{ + int savedprovider = curprovider; + + nProvider = Clamp(nProvider, 0, m_nNumberOfProviders - 1); + + if ( set_new_provider(nProvider) ) + return curprovider; + else if ( savedprovider != -1 && savedprovider < m_nNumberOfProviders && set_new_provider(savedprovider) ) + return curprovider; + else + return curprovider; +} + +static bool8 +_ResolveLink(char const *path, char *out) +{ +#ifdef _WIN32 + size_t len = strlen(path); + if (len < 4 || strcmp(&path[len - 4], ".lnk") != 0) + return FALSE; + + IShellLink* psl; + WIN32_FIND_DATA fd; + char filepath[MAX_PATH]; + + CoInitialize(NULL); + + if (SUCCEEDED( CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl ) )) + { + IPersistFile *ppf; + + if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf))) + { + WCHAR wpath[MAX_PATH]; + + MultiByteToWideChar(CP_ACP, 0, path, -1, wpath, MAX_PATH); + + if (SUCCEEDED(ppf->Load(wpath, STGM_READ))) + { + /* Resolve the link */ + if (SUCCEEDED(psl->Resolve(NULL, SLR_ANY_MATCH|SLR_NO_UI|SLR_NOSEARCH))) + { + strcpy(filepath, path); + + if (SUCCEEDED(psl->GetPath(filepath, MAX_PATH, &fd, SLGP_UNCPRIORITY))) + { + OutputDebugString(fd.cFileName); + + strcpy(out, filepath); + // FIX: Release the objects. Taken from SA. +#ifdef FIX_BUGS + ppf->Release(); + psl->Release(); +#endif + return TRUE; + } + } + } + + ppf->Release(); + } + psl->Release(); + } + + return FALSE; +#else + struct stat sb; + + if (lstat(path, &sb) == -1) { + perror("lstat: "); + return FALSE; + } + + if (S_ISLNK(sb.st_mode)) { + char* linkname = (char*)alloca(sb.st_size + 1); + if (linkname == NULL) { + fprintf(stderr, "insufficient memory\n"); + return FALSE; + } + + if (readlink(path, linkname, sb.st_size + 1) < 0) { + perror("readlink: "); + return FALSE; + } + linkname[sb.st_size] = '\0'; + strcpy(out, linkname); + return TRUE; + } else { + return FALSE; + } +#endif +} + +static void +_FindMP3s(void) +{ + tMP3Entry *pList; + bool8 bShortcut; + bool8 bInitFirstEntry; + HANDLE hFind; + char path[MAX_PATH]; + int total_ms; + WIN32_FIND_DATA fd; + char filepath[MAX_PATH + sizeof(fd.cFileName)]; + + if (getcwd(_mp3DirectoryPath, MAX_PATH) == NULL) { + perror("getcwd: "); + return; + } + + if (strlen(_mp3DirectoryPath) + 1 > MAX_PATH - 10) { + // This is not gonna end well + printf("MP3 folder path is too long, no place left for file names. MP3 finding aborted.\n"); + return; + } + + OutputDebugString("Finding MP3s..."); + strcpy(path, _mp3DirectoryPath); + strcat(path, "\\MP3\\"); + +#if !defined(_WIN32) + char *actualPath = casepath(path); + if (actualPath) { + strcpy(path, actualPath); + free(actualPath); + } +#endif + + strcpy(_mp3DirectoryPath, path); + OutputDebugString(_mp3DirectoryPath); + + strcat(path, "*"); + + hFind = FindFirstFile(path, &fd); + + if ( hFind == INVALID_HANDLE_VALUE ) + { + return; + } + + bShortcut = FALSE; + bInitFirstEntry = TRUE; + + do + { + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + if (!strcmp(fd.cFileName, ".") || !strcmp(fd.cFileName, "..")) + continue; + + size_t filepathlen = strlen(filepath); + + if ( bInitFirstEntry ) + { + if (filepathlen > 0) + { + if (_ResolveLink(filepath, filepath)) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + bShortcut = TRUE; + } + else + { + bShortcut = FALSE; + if (filepathlen > MAX_PATH) { + continue; + } + } + if (aStream[0] && aStream[0]->Open(filepath)) + { + total_ms = aStream[0]->GetLengthMS(); + aStream[0]->Close(); + + OutputDebugString(fd.cFileName); + + _pMP3List = new tMP3Entry; + + if (_pMP3List == NULL) + break; + + nNumMP3s = 1; + + strcpy(_pMP3List->aFilename, fd.cFileName); + + _pMP3List->nTrackLength = total_ms; + _pMP3List->pNext = NULL; + + if (bShortcut) + { + _pMP3List->pLinkPath = new char[MAX_PATH + sizeof(fd.cFileName)]; + strcpy(_pMP3List->pLinkPath, filepath); + } + else + { + _pMP3List->pLinkPath = NULL; + } + + pList = _pMP3List; + + bInitFirstEntry = FALSE; + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + } + } + else + break; + } + else + { + if ( filepathlen > 0 ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + bShortcut = TRUE; + } + else + bShortcut = FALSE; + + if (aStream[0] && aStream[0]->Open(filepath)) + { + total_ms = aStream[0]->GetLengthMS(); + aStream[0]->Close(); + + OutputDebugString(fd.cFileName); + + pList->pNext = new tMP3Entry; + + tMP3Entry *e = pList->pNext; + + if ( e == NULL ) + break; + + pList = pList->pNext; + + strcpy(e->aFilename, fd.cFileName); + e->nTrackLength = total_ms; + e->pNext = NULL; + + if ( bShortcut ) + { + e->pLinkPath = new char [MAX_PATH + sizeof(fd.cFileName)]; + strcpy(e->pLinkPath, filepath); + } + else + { + e->pLinkPath = NULL; + } + + nNumMP3s++; + + OutputDebugString(fd.cFileName); + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + } + } + } + } while (FindNextFile(hFind, &fd)); + + FindClose(hFind); +} + +static void +_DeleteMP3Entries(void) +{ + tMP3Entry *e = _pMP3List; + + while ( e != NULL ) + { + tMP3Entry *next = e->pNext; + + if ( next == NULL ) + next = NULL; + + if ( e->pLinkPath != NULL ) + { +#ifndef FIX_BUGS + delete e->pLinkPath; // BUG: should be delete [] +#else + delete[] e->pLinkPath; +#endif + e->pLinkPath = NULL; + } + + delete e; + + if ( next ) + e = next; + else + e = NULL; + + nNumMP3s--; + } + + + if ( nNumMP3s != 0 ) + { + OutputDebugString("Not all MP3 entries were deleted"); + nNumMP3s = 0; + } + + _pMP3List = NULL; +} + +static tMP3Entry * +_GetMP3EntryByIndex(uint32 idx) +{ + uint32 n = ( idx < nNumMP3s ) ? idx : 0; + + if ( _pMP3List != NULL ) + { + tMP3Entry *e = _pMP3List; + + for ( uint32 i = 0; i < n; i++ ) + e = e->pNext; + + return e; + + } + + return NULL; +} + +static inline bool8 +_GetMP3PosFromStreamPos(uint32 *pPosition, tMP3Entry **pEntry) +{ + _CurMP3Index = 0; + + for ( *pEntry = _pMP3List; *pEntry != NULL; *pEntry = (*pEntry)->pNext ) + { + if ( *pPosition >= (*pEntry)->nTrackStreamPos + && *pPosition < (*pEntry)->nTrackLength + (*pEntry)->nTrackStreamPos ) + { + *pPosition -= (*pEntry)->nTrackStreamPos; + _CurMP3Pos = *pPosition; + + return TRUE; + } + + _CurMP3Index++; + } + + *pPosition = 0; + *pEntry = _pMP3List; + _CurMP3Pos = 0; + _CurMP3Index = 0; + + return FALSE; +} + +bool8 +cSampleManager::IsMP3RadioChannelAvailable(void) +{ + return nNumMP3s != 0; +} + + +void cSampleManager::ReleaseDigitalHandle(void) +{ + // TODO? alcSuspendContext +} + +void cSampleManager::ReacquireDigitalHandle(void) +{ + // TODO? alcProcessContext +} + +bool8 +cSampleManager::Initialise(void) +{ + if ( _bSampmanInitialised ) + return TRUE; + + EFXInit(); + + for(int i = 0; i < MAX_STREAMS; i++) + aStream[i] = new CStream(ALStreamSources[i], ALStreamBuffers[i]); + + CStream::Initialise(); + + { + for ( int32 i = 0; i < TOTAL_AUDIO_SAMPLES; i++ ) + { + m_aSamples[i].nOffset = 0; + m_aSamples[i].nSize = 0; + m_aSamples[i].nFrequency = 22050; + m_aSamples[i].nLoopStart = 0; + m_aSamples[i].nLoopEnd = -1; + } + + m_nEffectsVolume = MAX_VOLUME; + m_nMusicVolume = MAX_VOLUME; + m_nEffectsFadeVolume = MAX_VOLUME; + m_nMusicFadeVolume = MAX_VOLUME; + + m_nMonoMode = 0; + } + + { + curprovider = -1; + prevprovider = -1; + + _usingEFX = false; + usingEAX =0; + usingEAX3=0; + + _fEffectsLevel = 0.0f; + + _maxSamples = 0; + + ALDevice = NULL; + ALContext = NULL; + } + + { + fpSampleDescHandle = NULL; + fpSampleDataHandle = NULL; + + for ( int32 i = 0; i < MAX_SFX_BANKS; i++ ) + { + gBankLoaded[i] = LOADING_STATUS_NOT_LOADED; + nSampleBankDiscStartOffset[i] = 0; + nSampleBankSize[i] = 0; + nSampleBankMemoryStartAddress[i] = 0; + } + } + + { + for ( int32 i = 0; i < MAX_PEDSFX; i++ ) + { + nPedSlotSfx[i] = NO_SAMPLE; + nPedSlotSfxAddr[i] = 0; + } + + nCurrentPedSlot = 0; + } + + { + for ( int32 i = 0; i < NUM_CHANNELS; i++ ) + nChannelVolume[i] = 0; + } + + add_providers(); + + { + int index = 0; + _maxSamples = Min(MAXCHANNELS, providers[index].sources); + + ALCint attr[] = {ALC_FREQUENCY,MAX_FREQ, + ALC_MONO_SOURCES, MAX_DIGITAL_MIXER_CHANNELS - MAX2DCHANNELS, + ALC_STEREO_SOURCES, MAX2DCHANNELS, + 0, + }; + + ALDevice = alcOpenDevice(providers[index].id); + ASSERT(ALDevice != NULL); + + ALContext = alcCreateContext(ALDevice, attr); + ASSERT(ALContext != NULL); + + alcMakeContextCurrent(ALContext); + + const char* ext=(const char*)alGetString(AL_EXTENSIONS); + ASSERT(strstr(ext,"AL_SOFT_loop_points")!=NULL); + if ( strstr(ext,"AL_SOFT_loop_points")==NULL ) + { + Terminate(); + return FALSE; + } + + alListenerf (AL_GAIN, 1.0f); + alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f); + alListener3f(AL_VELOCITY, 0.0f, 0.0f, 0.0f); + ALfloat orientation[6] = { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; + alListenerfv(AL_ORIENTATION, orientation); + + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + + if ( alcIsExtensionPresent(ALDevice, (ALCchar*)ALC_EXT_EFX_NAME) ) + { + _effectsSupported = providers[index].bSupportsFx; + alGenAuxiliaryEffectSlots(1, &ALEffectSlot); + alGenEffects(1, &ALEffect); + } + + alGenSources(MAX_STREAMS*2, ALStreamSources[0]); + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + alGenBuffers(NUM_STREAMBUFFERS, ALStreamBuffers[i]); + alSourcei(ALStreamSources[i][0], AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(ALStreamSources[i][0], AL_POSITION, 0.0f, 0.0f, 0.0f); + alSourcef(ALStreamSources[i][0], AL_GAIN, 1.0f); + alSourcei(ALStreamSources[i][1], AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(ALStreamSources[i][1], AL_POSITION, 0.0f, 0.0f, 0.0f); + alSourcef(ALStreamSources[i][1], AL_GAIN, 1.0f); + } + + CChannel::InitChannels(); + + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + aChannel[i].Init(i); + for ( int32 i = 0; i < MAX2DCHANNELS; i++ ) + aChannel[MAXCHANNELS+i].Init(MAXCHANNELS+i, true); + + if ( IsFXSupported() ) + { + /**/ + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + /**/ + + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + aChannel[i].SetReverbMix(ALEffectSlot, 0.0f); + } + } + + { + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + nStreamLength[i] = 0; + } + +#ifdef AUDIO_CACHE + FILE *cacheFile = fcaseopen("audio\\sound.cache", "rb"); + if (cacheFile) { + debug("Loadind audio cache (If game crashes around here, then your cache is corrupted, remove audio/sound.cache)\n"); + fread(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + } else + { + debug("Cannot load audio cache\n"); +#endif + + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + { + if(aStream[0] && ( +#ifdef PS2_AUDIO_PATHS + aStream[0]->Open(PS2StreamedNameTable[i], IsThisTrackAt16KHz(i) ? 16000 : 32000) || +#endif + aStream[0]->Open(StreamedNameTable[i], IsThisTrackAt16KHz(i) ? 16000 : 32000))) + { + uint32 tatalms = aStream[0]->GetLengthMS(); + aStream[0]->Close(); + nStreamLength[i] = tatalms; + } else + USERERROR("Can't open '%s'\n", StreamedNameTable[i]); + } +#ifdef AUDIO_CACHE + cacheFile = fcaseopen("audio\\sound.cache", "wb"); + if(cacheFile) { + debug("Saving audio cache\n"); + fwrite(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + } else { + debug("Cannot save audio cache\n"); + } + } +#endif + + { + if ( !InitialiseSampleBanks() ) + { + Terminate(); + return FALSE; + } + + nSampleBankMemoryStartAddress[SFX_BANK_0] = (uintptr)malloc(nSampleBankSize[SFX_BANK_0]); + ASSERT(nSampleBankMemoryStartAddress[SFX_BANK_0] != 0); + + if ( nSampleBankMemoryStartAddress[SFX_BANK_0] == 0 ) + { + Terminate(); + return FALSE; + } + + nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] = (uintptr)malloc(PED_BLOCKSIZE*MAX_PEDSFX); + ASSERT(nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] != 0); + + LoadSampleBank(SFX_BANK_0); + } + + { + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + aStream[i]->Close(); + + nStreamVolume[i] = 100; + nStreamPan[i] = 63; + } + } + + { + _bSampmanInitialised = TRUE; + + if ( defaultProvider >= 0 && defaultProvider < m_nNumberOfProviders ) + { + set_new_provider(defaultProvider); + } + else + { + Terminate(); + return FALSE; + } + } + + { + nNumMP3s = 0; + + _pMP3List = NULL; + + _FindMP3s(); + + if ( nNumMP3s != 0 ) + { + nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] = 0; + + for ( tMP3Entry *e = _pMP3List; e != NULL; e = e->pNext ) + { + e->nTrackStreamPos = nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER]; + nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] += e->nTrackLength; + } + + time_t t = time(NULL); + tm *localtm; + bool8 bUseRandomTable; + + if ( t == -1 ) + bUseRandomTable = TRUE; + else + { + bUseRandomTable = FALSE; + localtm = localtime(&t); + } + + int32 randval; + if ( bUseRandomTable ) + randval = AudioManager.m_anRandomTable[1]; + else + randval = localtm->tm_sec * localtm->tm_min; + + _CurMP3Index = randval % nNumMP3s; + + tMP3Entry *randmp3 = _pMP3List; + for ( int32 i = randval % nNumMP3s; i > 0; --i) + randmp3 = randmp3->pNext; + + if ( bUseRandomTable ) + _CurMP3Pos = AudioManager.m_anRandomTable[0] % randmp3->nTrackLength; + else + { + if ( localtm->tm_sec > 0 ) + { + int32 s = localtm->tm_sec; + _CurMP3Pos = s*s*s*s*s*s*s*s % randmp3->nTrackLength; + } + else + _CurMP3Pos = AudioManager.m_anRandomTable[0] % randmp3->nTrackLength; + } + } + else + _CurMP3Pos = 0; + + _bIsMp3Active = FALSE; + } + + return TRUE; +} + +void +cSampleManager::Terminate(void) +{ + for (int32 i = 0; i < MAX_STREAMS; i++) + aStream[i]->Close(); + + for ( int32 i = 0; i < NUM_CHANNELS; i++ ) + aChannel[i].Term(); + + if ( IsFXSupported() ) + { + if ( alIsEffect(ALEffect) ) + { + alEffecti(ALEffect, AL_EFFECT_TYPE, AL_EFFECT_NULL); + alDeleteEffects(1, &ALEffect); + ALEffect = AL_EFFECT_NULL; + } + + if (alIsAuxiliaryEffectSlot(ALEffectSlot)) + { + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, AL_EFFECT_NULL); + + alDeleteAuxiliaryEffectSlots(1, &ALEffectSlot); + ALEffectSlot = AL_EFFECTSLOT_NULL; + } + } + + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + alDeleteBuffers(NUM_STREAMBUFFERS, ALStreamBuffers[i]); + } + alDeleteSources(MAX_STREAMS*2, ALStreamSources[0]); + + CChannel::DestroyChannels(); + + if ( ALContext ) + { + alcMakeContextCurrent(NULL); + alcSuspendContext(ALContext); + alcDestroyContext(ALContext); + } + if ( ALDevice ) + alcCloseDevice(ALDevice); + + ALDevice = NULL; + ALContext = NULL; + + _fPrevEaxRatioDestination = 0.0f; + _usingEFX = false; + _fEffectsLevel = 0.0f; + + _DeleteMP3Entries(); + + CStream::Terminate(); + + for(int32 i = 0; i < MAX_STREAMS; i++) + delete aStream[i]; + + if ( nSampleBankMemoryStartAddress[SFX_BANK_0] != 0 ) + { + free((void *)nSampleBankMemoryStartAddress[SFX_BANK_0]); + nSampleBankMemoryStartAddress[SFX_BANK_0] = 0; + } + + if ( nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] != 0 ) + { + free((void *)nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS]); + nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] = 0; + } + + _bSampmanInitialised = FALSE; +} + +bool8 cSampleManager::CheckForAnAudioFileOnCD(void) +{ + return TRUE; +} + +char cSampleManager::GetCDAudioDriveLetter(void) +{ + return '\0'; +} + +void +cSampleManager::UpdateEffectsVolume(void) +{ + if ( _bSampmanInitialised ) + { + for ( int32 i = 0; i < NUM_CHANNELS; i++ ) + { + if ( GetChannelUsedFlag(i) ) + { + if ( nChannelVolume[i] != 0 ) + aChannel[i].SetVolume(m_nEffectsFadeVolume*nChannelVolume[i]*m_nEffectsVolume >> 14); + } + } + } +} + +void +cSampleManager::SetEffectsMasterVolume(uint8 nVolume) +{ + m_nEffectsVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicMasterVolume(uint8 nVolume) +{ + m_nMusicVolume = nVolume; +} + +void +cSampleManager::SetEffectsFadeVolume(uint8 nVolume) +{ + m_nEffectsFadeVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicFadeVolume(uint8 nVolume) +{ + m_nMusicFadeVolume = nVolume; +} + +void +cSampleManager::SetMonoMode(uint8 nMode) +{ + m_nMonoMode = nMode; +} + +bool8 +cSampleManager::LoadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS); + + if ( CTimer::GetIsCodePaused() ) + return FALSE; + + if ( MusicManager.IsInitialised() + && MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && nBank != SFX_BANK_0 ) + { + return FALSE; + } + +#ifdef OPUS_SFX + int samplesRead = 0; + int samplesSize = nSampleBankSize[nBank] / 2; + op_pcm_seek(fpSampleDataHandle, 0); + while (samplesSize > 0) { + int size = op_read(fpSampleDataHandle, (opus_int16 *)(nSampleBankMemoryStartAddress[nBank] + samplesRead), samplesSize, NULL); + if (size <= 0) { + // huh? + //assert(0); + break; + } + samplesRead += size*2; + samplesSize -= size; + } +#else + if ( fseek(fpSampleDataHandle, nSampleBankDiscStartOffset[nBank], SEEK_SET) != 0 ) + return FALSE; + + if ( fread((void *)nSampleBankMemoryStartAddress[nBank], 1, nSampleBankSize[nBank], fpSampleDataHandle) != nSampleBankSize[nBank] ) + return FALSE; +#endif + gBankLoaded[nBank] = LOADING_STATUS_LOADED; + + return TRUE; +} + +void +cSampleManager::UnloadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS); + + gBankLoaded[nBank] = LOADING_STATUS_NOT_LOADED; +} + +int8 +cSampleManager::IsSampleBankLoaded(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS); + + return gBankLoaded[nBank]; +} + +uint8 +cSampleManager::IsPedCommentLoaded(uint32 nComment) +{ + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + + for ( int32 i = 0; i < _TODOCONST(3); i++ ) + { +#ifdef FIX_BUGS + int8 slot = (int8)nCurrentPedSlot - i - 1; + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#else + uint8 slot = nCurrentPedSlot - i - 1; +#endif + if ( nComment == nPedSlotSfx[slot] ) + return LOADING_STATUS_LOADED; + } + + return LOADING_STATUS_NOT_LOADED; +} + + +int32 +cSampleManager::_GetPedCommentSlot(uint32 nComment) +{ + for (int32 i = 0; i < _TODOCONST(3); i++) + { +#ifdef FIX_BUGS + int8 slot = (int8)nCurrentPedSlot - i - 1; + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#else + uint8 slot = nCurrentPedSlot - i - 1; +#endif + if (nComment == nPedSlotSfx[slot]) + return slot; + } + + return -1; +} + +bool8 +cSampleManager::LoadPedComment(uint32 nComment) +{ + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + + if ( CTimer::GetIsCodePaused() ) + return FALSE; + + // no talking peds during cutsenes or the game end + if ( MusicManager.IsInitialised() ) + { + switch ( MusicManager.GetMusicMode() ) + { + case MUSICMODE_CUTSCENE: + { + return FALSE; + + break; + } + + case MUSICMODE_FRONTEND: + { + if ( MusicManager.GetNextTrack() == STREAMED_SOUND_GAME_COMPLETED ) + return FALSE; + + break; + } + } + } + +#ifdef OPUS_SFX + int samplesRead = 0; + int samplesSize = m_aSamples[nComment].nSize / 2; + op_pcm_seek(fpSampleDataHandle, m_aSamples[nComment].nOffset / 2); + while (samplesSize > 0) { + int size = op_read(fpSampleDataHandle, (opus_int16 *)(nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE * nCurrentPedSlot + samplesRead), + samplesSize, NULL); + if (size <= 0) { + return FALSE; + } + samplesRead += size * 2; + samplesSize -= size; + } +#else + if ( fseek(fpSampleDataHandle, m_aSamples[nComment].nOffset, SEEK_SET) != 0 ) + return FALSE; + + if ( fread((void *)(nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE*nCurrentPedSlot), 1, m_aSamples[nComment].nSize, fpSampleDataHandle) != m_aSamples[nComment].nSize ) + return FALSE; + +#endif + nPedSlotSfx[nCurrentPedSlot] = nComment; + + if ( ++nCurrentPedSlot >= MAX_PEDSFX ) + nCurrentPedSlot = 0; + + return TRUE; +} + +int32 +cSampleManager::GetBankContainingSound(uint32 offset) +{ + if ( offset >= BankStartOffset[SFX_BANK_PED_COMMENTS] ) + return SFX_BANK_PED_COMMENTS; + + if ( offset >= BankStartOffset[SFX_BANK_0] ) + return SFX_BANK_0; + + return INVALID_SFX_BANK; +} + +uint32 +cSampleManager::GetSampleBaseFrequency(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nFrequency; +} + +uint32 +cSampleManager::GetSampleLoopStartOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nLoopStart; +} + +int32 +cSampleManager::GetSampleLoopEndOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nLoopEnd; +} + +uint32 +cSampleManager::GetSampleLength(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nSize / sizeof(uint16); +} + +bool8 cSampleManager::UpdateReverb(void) +{ + if ( !usingEAX && !_usingEFX ) + return FALSE; + + if ( AudioManager.m_FrameCounter & 15 ) + return FALSE; + +#ifdef AUDIO_REFLECTIONS + float y = AudioManager.m_afReflectionsDistances[REFLECTION_TOP] + AudioManager.m_afReflectionsDistances[REFLECTION_BOTTOM]; + float x = AudioManager.m_afReflectionsDistances[REFLECTION_LEFT] + AudioManager.m_afReflectionsDistances[REFLECTION_RIGHT]; + float z = AudioManager.m_afReflectionsDistances[REFLECTION_UP]; +#else + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; +#endif + + float normy = norm(y, 5.0f, 40.0f); + float normx = norm(x, 5.0f, 40.0f); + float normz = norm(z, 5.0f, 40.0f); + + #define ZR(v, a, b) (((v)==0)?(a):(b)) + #define CALCRATIO(x,y,z,min,max,val) (ZR(y, ZR(x, ZR(z, min, max), min), ZR(x, ZR(z, min, max), ZR(z, min, val)))) + + float fRatio = CALCRATIO(normx, normy, normz, 0.3f, 0.5f, (normy+normx+normz)/3.0f); + + #undef CALCRATIO + #undef ZR + + fRatio = Clamp(fRatio, usingEAX3==1 ? 0.0f : 0.30f, 1.0f); + + if ( fRatio == _fPrevEaxRatioDestination ) + return FALSE; + +#ifdef JUICY_OAL + if ( usingEAX3 || _usingEFX ) +#else + if ( usingEAX3 ) +#endif + { + if ( EAX3ListenerInterpolate(&StartEAX3, &FinishEAX3, fRatio, &EAX3Params, false) ) + { + EAX_SetAll(&EAX3Params); + + /* + if ( IsFXSupported() ) + { + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + + for ( int32 i = 0; i < MAXCHANNELS; i++ ) + aChannel[i].UpdateReverb(ALEffectSlot); + } + */ + + _fEffectsLevel = 1.0f - fRatio * 0.5f; + } + } + else + { + if ( _usingEFX ) + _fEffectsLevel = (1.0f - fRatio) * 0.4f; + else + _fEffectsLevel = (1.0f - fRatio) * 0.7f; + } + + _fPrevEaxRatioDestination = fRatio; + + return TRUE; +} + +void +cSampleManager::SetChannelReverbFlag(uint32 nChannel, bool8 nReverbFlag) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + if ( usingEAX || _usingEFX ) + { + if ( IsFXSupported() ) + { + alAuxiliaryEffectSloti(ALEffectSlot, AL_EFFECTSLOT_EFFECT, ALEffect); + + if ( nReverbFlag != FALSE ) + aChannel[nChannel].SetReverbMix(ALEffectSlot, _fEffectsLevel); + else + aChannel[nChannel].SetReverbMix(ALEffectSlot, 0.0f); + } + } +} + +bool8 +cSampleManager::InitialiseChannel(uint32 nChannel, uint32 nSfx, uint8 nBank) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + uintptr addr; + + if ( nSfx < SAMPLEBANK_MAX ) + { + if ( !IsSampleBankLoaded(nBank) ) + return FALSE; + + addr = nSampleBankMemoryStartAddress[nBank] + m_aSamples[nSfx].nOffset - m_aSamples[BankStartOffset[nBank]].nOffset; + } + else + { + int32 i; + for ( i = 0; i < _TODOCONST(3); i++ ) + { + int32 slot = nCurrentPedSlot - i - 1; +#ifdef FIX_BUGS + if (slot < 0) + slot += ARRAY_SIZE(nPedSlotSfx); +#endif + if ( nSfx == nPedSlotSfx[slot] ) + { + addr = (nSampleBankMemoryStartAddress[SFX_BANK_PED_COMMENTS] + PED_BLOCKSIZE * slot); + break; + } + } + + if (i == _TODOCONST(3)) + return FALSE; + } + + if ( GetChannelUsedFlag(nChannel) ) + { + TRACE("Stopping channel %d - really!!!", nChannel); + StopChannel(nChannel); + } + + aChannel[nChannel].Reset(); + if ( aChannel[nChannel].HasSource() ) + { + aChannel[nChannel].SetSampleData ((void*)addr, m_aSamples[nSfx].nSize, m_aSamples[nSfx].nFrequency); + aChannel[nChannel].SetLoopPoints (0, -1); + aChannel[nChannel].SetPitch (1.0f); + return TRUE; + } + + return FALSE; +} + +void +cSampleManager::SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume) +{ + ASSERT( nChannel < MAXCHANNELS ); + + uint32 vol = nVolume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + nChannelVolume[nChannel] = vol; + + // reduce channel volume when JB.MP3 or S4_BDBD.MP3 playing + if ( MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && MusicManager.GetNextTrack() != STREAMED_SOUND_NEWS_INTRO + && MusicManager.GetNextTrack() != STREAMED_SOUND_CUTSCENE_SAL4_BDBD ) + { + nChannelVolume[nChannel] = vol / 4; + } + + // no idea, does this one looks like a bug or it's SetChannelVolume ? + aChannel[nChannel].SetVolume(m_nEffectsFadeVolume*nChannelVolume[nChannel]*m_nEffectsVolume >> 14); +} + +void +cSampleManager::SetChannel3DPosition(uint32 nChannel, float fX, float fY, float fZ) +{ + ASSERT( nChannel < MAXCHANNELS ); + + aChannel[nChannel].SetPosition(-fX, fY, fZ); +} + +void +cSampleManager::SetChannel3DDistances(uint32 nChannel, float fMax, float fMin) +{ + ASSERT( nChannel < MAXCHANNELS ); + aChannel[nChannel].SetDistances(fMax, fMin); +} + +void +cSampleManager::SetChannelVolume(uint32 nChannel, uint32 nVolume) +{ + ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < NUM_CHANNELS ); + + if ( nChannel == CHANNEL_POLICE_RADIO ) + { + uint32 vol = nVolume; + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + nChannelVolume[nChannel] = vol; + + // reduce the volume for JB.MP3 and S4_BDBD.MP3 + if ( MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE + && MusicManager.GetNextTrack() != STREAMED_SOUND_NEWS_INTRO + && MusicManager.GetNextTrack() != STREAMED_SOUND_CUTSCENE_SAL4_BDBD ) + { + nChannelVolume[nChannel] = vol / 4; + } + + aChannel[nChannel].SetVolume(m_nEffectsFadeVolume*vol*m_nEffectsVolume >> 14); + } +} + +void +cSampleManager::SetChannelPan(uint32 nChannel, uint32 nPan) +{ + ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < NUM_CHANNELS ); + + if ( nChannel == CHANNEL_POLICE_RADIO ) + { + aChannel[nChannel].SetPan(nPan); + } +} + +void +cSampleManager::SetChannelFrequency(uint32 nChannel, uint32 nFreq) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].SetCurrentFreq(nFreq); +} + +void +cSampleManager::SetChannelLoopPoints(uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].SetLoopPoints(nLoopStart / (DIGITALBITS / 8), nLoopEnd / (DIGITALBITS / 8)); +} + +void +cSampleManager::SetChannelLoopCount(uint32 nChannel, uint32 nLoopCount) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].SetLoopCount(nLoopCount); +} + +bool8 +cSampleManager::GetChannelUsedFlag(uint32 nChannel) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + return aChannel[nChannel].IsUsed(); +} + +void +cSampleManager::StartChannel(uint32 nChannel) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].Start(); +} + +void +cSampleManager::StopChannel(uint32 nChannel) +{ + ASSERT( nChannel < NUM_CHANNELS ); + + aChannel[nChannel].Stop(); +} + +void +cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + if ( nFile < TOTAL_STREAMED_SOUNDS ) + { + CStream *stream = aStream[nStream]; + + stream->Close(); + +#ifdef PS2_AUDIO_PATHS + if(!stream->Open(PS2StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000)) +#endif + stream->Open(StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + if ( !stream->Setup() ) + { + stream->Close(); + } + } +} + +void +cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + stream->SetPause(nPauseFlag != FALSE); + } +} + +void +cSampleManager::StartPreloadedStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + stream->Start(); + } +} + +bool8 +cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) +{ + uint32 i = 0; + uint32 position = nPos; + char filename[MAX_PATH]; + + if ( nFile >= TOTAL_STREAMED_SOUNDS ) + return FALSE; + + aStream[nStream]->Close(); + + if ( nFile == STREAMED_SOUND_RADIO_MP3_PLAYER ) + { + do + { + // Switched to MP3 player just now + if ( !_bIsMp3Active && i == 0 ) + { + if ( nPos > nStreamLength[STREAMED_SOUND_RADIO_MP3_PLAYER] ) + position = 0; + tMP3Entry *e = _pMP3List; + + // Try to continue from previous song, if already started + if(!_GetMP3PosFromStreamPos(&position, &e) && !e) { + nFile = 0; + CStream *stream = aStream[nStream]; +#ifdef PS2_AUDIO_PATHS + if(!stream->Open(PS2StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000)) +#endif + stream->Open(StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + if ( stream->Setup() ) { + if (position != 0) + stream->SetPosMS(position); + + stream->Start(); + + return TRUE; + } else { + stream->Close(); + } + return FALSE; + + } else { + if (e->pLinkPath != NULL) + aStream[nStream]->Open(e->pLinkPath, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + else { + strcpy(filename, _mp3DirectoryPath); + strcat(filename, e->aFilename); + + aStream[nStream]->Open(filename); + } + + if (aStream[nStream]->Setup()) { + if (position != 0) + aStream[nStream]->SetPosMS(position); + + aStream[nStream]->Start(); + + _bIsMp3Active = TRUE; + return TRUE; + } else { + aStream[nStream]->Close(); + } + // fall through, start playing from another song + } + } else { + if(++_CurMP3Index >= nNumMP3s) _CurMP3Index = 0; + + _CurMP3Pos = 0; + + tMP3Entry *mp3 = _GetMP3EntryByIndex(_CurMP3Index); + if ( !mp3 ) + { + mp3 = _pMP3List; + if ( !_pMP3List ) + { + nFile = 0; + _bIsMp3Active = FALSE; + CStream *stream = aStream[nStream]; +#ifdef PS2_AUDIO_PATHS + if(!stream->Open(PS2StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000)) +#endif + stream->Open(StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + + if (stream->Setup()) { + if (position != 0) + stream->SetPosMS(position); + + stream->Start(); + + return TRUE; + } else { + stream->Close(); + } + return FALSE; + } + } + if (mp3->pLinkPath != NULL) + aStream[nStream]->Open(mp3->pLinkPath, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + else { + strcpy(filename, _mp3DirectoryPath); + strcat(filename, mp3->aFilename); + aStream[nStream]->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + } + + if (aStream[nStream]->Setup()) { + aStream[nStream]->Start(); +#ifdef FIX_BUGS + _bIsMp3Active = TRUE; +#endif + return TRUE; + } else { + aStream[nStream]->Close(); + } + + } + _bIsMp3Active = FALSE; + } + while ( ++i < nNumMP3s ); + position = 0; + nFile = 0; + } + CStream *stream = aStream[nStream]; +#ifdef PS2_AUDIO_PATHS + if(!stream->Open(PS2StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000)) +#endif + stream->Open(StreamedNameTable[nFile], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + + if ( stream->Setup() ) { + if (position != 0) + stream->SetPosMS(position); + + stream->Start(); + + return TRUE; + } else { + stream->Close(); + } + return FALSE; +} + +void +cSampleManager::StopStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + stream->Close(); + + if ( nStream == 0 ) + _bIsMp3Active = FALSE; +} + +int32 +cSampleManager::GetStreamedFilePosition(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + if ( _bIsMp3Active ) + { + tMP3Entry *mp3 = _GetMP3EntryByIndex(_CurMP3Index); + + if ( mp3 != NULL ) + { + return stream->GetPosMS() + mp3->nTrackStreamPos; + } + else + return 0; + } + else + { + return stream->GetPosMS(); + } + } + + return 0; +} + +void +cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, uint8 nEffectFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + if ( nVolume > MAX_VOLUME ) + nVolume = MAX_VOLUME; + + if ( nPan > MAX_VOLUME ) + nPan = MAX_VOLUME; + + nStreamVolume[nStream] = nVolume; + nStreamPan [nStream] = nPan; + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + if ( nEffectFlag ) + stream->SetVolume(m_nEffectsFadeVolume*nVolume*m_nEffectsVolume >> 14); + else + stream->SetVolume(m_nMusicFadeVolume*nVolume*m_nMusicVolume >> 14); + + stream->SetPan(nPan); + } +} + +int32 +cSampleManager::GetStreamedFileLength(uint8 nStream) +{ + ASSERT( nStream < TOTAL_STREAMED_SOUNDS ); + + return nStreamLength[nStream]; +} + +bool8 +cSampleManager::IsStreamPlaying(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + if ( stream->IsPlaying() ) + return TRUE; + } + + return FALSE; +} + +void +cSampleManager::Service(void) +{ + for ( int32 i = 0; i < MAX_STREAMS; i++ ) + { + CStream *stream = aStream[i]; + + if ( stream->IsOpened() ) + stream->Update(); + } + int refCount = CChannel::channelsThatNeedService; + for ( int32 i = 0; refCount && i < NUM_CHANNELS; i++ ) + { + if ( aChannel[i].Update() ) + refCount--; + } +} + +bool8 +cSampleManager::InitialiseSampleBanks(void) +{ + int32 nBank = SFX_BANK_0; + + fpSampleDescHandle = fcaseopen(SampleBankDescFilename, "rb"); + if ( fpSampleDescHandle == NULL ) + return FALSE; +#ifndef OPUS_SFX + fpSampleDataHandle = fcaseopen(SampleBankDataFilename, "rb"); + if ( fpSampleDataHandle == NULL ) + { + fclose(fpSampleDescHandle); + fpSampleDescHandle = NULL; + + return FALSE; + } + + fseek(fpSampleDataHandle, 0, SEEK_END); + int32 _nSampleDataEndOffset = ftell(fpSampleDataHandle); + rewind(fpSampleDataHandle); +#else + int e; + fpSampleDataHandle = op_open_file(SampleBankDataFilename, &e); +#endif + fread(m_aSamples, sizeof(tSample), TOTAL_AUDIO_SAMPLES, fpSampleDescHandle); +#ifdef OPUS_SFX + int32 _nSampleDataEndOffset = m_aSamples[TOTAL_AUDIO_SAMPLES - 1].nOffset + m_aSamples[TOTAL_AUDIO_SAMPLES - 1].nSize; +#endif + fclose(fpSampleDescHandle); + fpSampleDescHandle = NULL; + + for ( int32 i = 0; i < TOTAL_AUDIO_SAMPLES; i++ ) + { +#ifdef FIX_BUGS + if (nBank >= MAX_SFX_BANKS) break; +#endif + if ( BankStartOffset[nBank] == BankStartOffset[SFX_BANK_0] + i ) + { + nSampleBankDiscStartOffset[nBank] = m_aSamples[i].nOffset; + nBank++; + } + } + + nSampleBankSize[SFX_BANK_0] = nSampleBankDiscStartOffset[SFX_BANK_PED_COMMENTS] - nSampleBankDiscStartOffset[SFX_BANK_0]; + nSampleBankSize[SFX_BANK_PED_COMMENTS] = _nSampleDataEndOffset - nSampleBankDiscStartOffset[SFX_BANK_PED_COMMENTS]; + + return TRUE; +} +#endif diff --git a/src/audio/soundlist.h b/src/audio/soundlist.h new file mode 100644 index 0000000..5f3f752 --- /dev/null +++ b/src/audio/soundlist.h @@ -0,0 +1,303 @@ +#pragma once + +enum eSound +{ + SOUND_CAR_DOOR_CLOSE_BONNET = 0, + SOUND_CAR_DOOR_CLOSE_BUMPER, + SOUND_CAR_DOOR_CLOSE_FRONT_LEFT, + SOUND_CAR_DOOR_CLOSE_FRONT_RIGHT, + SOUND_CAR_DOOR_CLOSE_BACK_LEFT, + SOUND_CAR_DOOR_CLOSE_BACK_RIGHT, + SOUND_CAR_DOOR_OPEN_BONNET, + SOUND_CAR_DOOR_OPEN_BUMPER, + SOUND_CAR_DOOR_OPEN_FRONT_LEFT, + SOUND_CAR_DOOR_OPEN_FRONT_RIGHT, + SOUND_CAR_DOOR_OPEN_BACK_LEFT, + SOUND_CAR_DOOR_OPEN_BACK_RIGHT, + SOUND_CAR_WINDSHIELD_CRACK, + SOUND_CAR_JUMP, + SOUND_E, + SOUND_F, + SOUND_CAR_ENGINE_START, + SOUND_CAR_LIGHT_BREAK, + SOUND_CAR_HYDRAULIC_1, + SOUND_CAR_HYDRAULIC_2, + SOUND_CAR_HYDRAULIC_3, + SOUND_CAR_JERK, + SOUND_CAR_SPLASH, + SOUND_BOAT_SLOWDOWN, + SOUND_TRAIN_DOOR_CLOSE, + SOUND_TRAIN_DOOR_OPEN, + SOUND_CAR_TANK_TURRET_ROTATE, + SOUND_CAR_BOMB_TICK, + SOUND_PLANE_ON_GROUND, + SOUND_STEP_START, + SOUND_STEP_END, + SOUND_FALL_LAND, + SOUND_FALL_COLLAPSE, + SOUND_FIGHT_PUNCH_33, + SOUND_FIGHT_KICK_34, + SOUND_FIGHT_HEADBUTT_35, + SOUND_FIGHT_PUNCH_36, + SOUND_FIGHT_PUNCH_37, + SOUND_FIGHT_CLOSE_PUNCH_38, + SOUND_FIGHT_PUNCH_39, + SOUND_FIGHT_PUNCH_OR_KICK_BELOW_40, + SOUND_FIGHT_PUNCH_41, + SOUND_FIGHT_PUNCH_FROM_BEHIND_42, + SOUND_FIGHT_KNEE_OR_KICK_43, + SOUND_FIGHT_KICK_44, + SOUND_2D, + SOUND_WEAPON_BAT_ATTACK, + SOUND_WEAPON_SHOT_FIRED, + SOUND_WEAPON_RELOAD, + SOUND_WEAPON_AK47_BULLET_ECHO, + SOUND_WEAPON_UZI_BULLET_ECHO, + SOUND_WEAPON_M16_BULLET_ECHO, + SOUND_WEAPON_FLAMETHROWER_FIRE, + SOUND_WEAPON_SNIPER_SHOT_NO_ZOOM, + SOUND_WEAPON_ROCKET_SHOT_NO_ZOOM, + SOUND_WEAPON_HIT_PED, + SOUND_WEAPON_HIT_VEHICLE, + SOUND_GARAGE_NO_MONEY, + SOUND_GARAGE_BAD_VEHICLE, + SOUND_GARAGE_OPENING, + SOUND_GARAGE_BOMB_ALREADY_SET, + SOUND_GARAGE_BOMB1_SET, + SOUND_GARAGE_BOMB2_SET, + SOUND_GARAGE_BOMB3_SET, + SOUND_40, + SOUND_41, + SOUND_GARAGE_VEHICLE_DECLINED, + SOUND_GARAGE_VEHICLE_ACCEPTED, + SOUND_GARAGE_DOOR_CLOSED, + SOUND_GARAGE_DOOR_OPENED, + SOUND_CRANE_PICKUP, + SOUND_PICKUP_WEAPON_BOUGHT, + SOUND_PICKUP_WEAPON, + SOUND_PICKUP_HEALTH, + SOUND_PICKUP_ERROR, + SOUND_4B, + SOUND_PICKUP_ADRENALINE, + SOUND_PICKUP_ARMOUR, + SOUND_PICKUP_BONUS, + SOUND_PICKUP_MONEY, + SOUND_PICKUP_HIDDEN_PACKAGE, + SOUND_PICKUP_PACMAN_PILL, + SOUND_PICKUP_PACMAN_PACKAGE, + SOUND_PICKUP_FLOAT_PACKAGE, + SOUND_BOMB_TIMED_ACTIVATED, + SOUND_55, + SOUND_BOMB_ONIGNITION_ACTIVATED, + SOUND_BOMB_TICK, + SOUND_RAMPAGE_START, + SOUND_RAMPAGE_ONGOING, + SOUND_RAMPAGE_PASSED, + SOUND_RAMPAGE_FAILED, + SOUND_RAMPAGE_KILL, + SOUND_RAMPAGE_CAR_BLOWN, + SOUND_EVIDENCE_PICKUP, + SOUND_UNLOAD_GOLD, + SOUND_PAGER, + SOUND_PED_DEATH, + SOUND_PED_DAMAGE, + SOUND_PED_HIT, + SOUND_PED_LAND, + SOUND_PED_BULLET_HIT, + SOUND_PED_BOMBER, + SOUND_PED_BURNING, + SOUND_PED_ARREST_FBI, + SOUND_PED_ARREST_SWAT, + SOUND_PED_ARREST_COP, + SOUND_PED_HELI_PLAYER_FOUND, + SOUND_PED_HANDS_UP, + SOUND_PED_HANDS_COWER, + SOUND_PED_FLEE_SPRINT, + SOUND_PED_CAR_JACKING, + SOUND_PED_MUGGING, + SOUND_PED_CAR_JACKED, + SOUND_PED_ROBBED, + SOUND_PED_TAXI_WAIT, + SOUND_PED_ATTACK, + SOUND_PED_DEFEND, + SOUND_PED_PURSUIT_ARMY, + SOUND_PED_PURSUIT_FBI, + SOUND_PED_PURSUIT_SWAT, + SOUND_PED_PURSUIT_COP, + SOUND_PED_HEALING, + SOUND_PED_7B, + SOUND_PED_LEAVE_VEHICLE, + SOUND_PED_EVADE, + SOUND_PED_FLEE_RUN, + SOUND_PED_ANNOYED_DRIVER, + SOUND_PED_SOLICIT, + SOUND_PED_EXTINGUISHING_FIRE, + SOUND_PED_WAIT_DOUBLEBACK, + SOUND_PED_CHAT_SEXY, + SOUND_PED_CHAT_EVENT, + SOUND_PED_CHAT, + SOUND_PED_BODYCAST_HIT, + SOUND_PED_TAXI_CALL, + SOUND_INJURED_PED_MALE_OUCH, + SOUND_INJURED_PED_FEMALE, + SOUND_INJURED_PED_MALE_PRISON, + SOUND_RACE_START_3, + SOUND_RACE_START_2, + SOUND_RACE_START_1, + SOUND_RACE_START_GO, + SOUND_SPLASH, + SOUND_WATER_FALL, + SOUND_SPLATTER, + SOUND_CAR_PED_COLLISION, + SOUND_CLOCK_TICK, + SOUND_PART_MISSION_COMPLETE, + SOUND_FRONTEND_MENU_STARTING, + SOUND_FRONTEND_MENU_NEW_PAGE, + SOUND_FRONTEND_MENU_NAVIGATION, + SOUND_FRONTEND_MENU_SETTING_CHANGE, + SOUND_FRONTEND_MENU_BACK, + SOUND_FRONTEND_STEREO, + SOUND_FRONTEND_MONO, + SOUND_FRONTEND_AUDIO_TEST, + SOUND_FRONTEND_FAIL, + SOUND_FRONTEND_RADIO_TURN_OFF, + SOUND_FRONTEND_RADIO_CHANGE, + SOUND_HUD, + SOUND_AMMUNATION_WELCOME_1, + SOUND_AMMUNATION_WELCOME_2, + SOUND_AMMUNATION_WELCOME_3, + SOUND_LIGHTNING, + SOUND_A5, + SOUND_TOTAL_SOUNDS, + SOUND_NO_SOUND, +}; + + +enum eScriptSounds { + SCRIPT_SOUND_0 = 0, + SCRIPT_SOUND_1, + SCRIPT_SOUND_2, + SCRIPT_SOUND_3, + SCRIPT_SOUND_PARTY_1_LOOP_S, + SCRIPT_SOUND_PARTY_1_LOOP_L, + SCRIPT_SOUND_PARTY_2_LOOP_S, + SCRIPT_SOUND_PARTY_2_LOOP_L, + SCRIPT_SOUND_PARTY_3_LOOP_S, + SCRIPT_SOUND_PARTY_3_LOOP_L, + SCRIPT_SOUND_PARTY_4_LOOP_S, + SCRIPT_SOUND_PARTY_4_LOOP_L, + SCRIPT_SOUND_PARTY_5_LOOP_S, + SCRIPT_SOUND_PARTY_5_LOOP_L, + SCRIPT_SOUND_PARTY_6_LOOP_S, + SCRIPT_SOUND_PARTY_6_LOOP_L, + SCRIPT_SOUND_PARTY_7_LOOP_S, + SCRIPT_SOUND_PARTY_7_LOOP_L, + SCRIPT_SOUND_PARTY_8_LOOP_S, + SCRIPT_SOUND_PARTY_8_LOOP_L, + SCRIPT_SOUND_PARTY_9_LOOP_S, + SCRIPT_SOUND_PARTY_9_LOOP_L, + SCRIPT_SOUND_PARTY_10_LOOP_S, + SCRIPT_SOUND_PARTY_10_LOOP_L, + SCRIPT_SOUND_PARTY_11_LOOP_S, + SCRIPT_SOUND_PARTY_11_LOOP_L, + SCRIPT_SOUND_PARTY_12_LOOP_S, + SCRIPT_SOUND_PARTY_12_LOOP_L, + SCRIPT_SOUND_PARTY_13_LOOP_S, + SCRIPT_SOUND_PARTY_13_LOOP_L, + SCRIPT_SOUND_STRIP_CLUB_LOOP_1_S, + SCRIPT_SOUND_STRIP_CLUB_LOOP_1_L, + SCRIPT_SOUND_STRIP_CLUB_LOOP_2_S, + SCRIPT_SOUND_STRIP_CLUB_LOOP_2_L, + SCRIPT_SOUND_WORK_SHOP_LOOP_S, + SCRIPT_SOUND_WORK_SHOP_LOOP_L, + SCRIPT_SOUND_SAWMILL_LOOP_S, + SCRIPT_SOUND_SAWMILL_LOOP_L, + SCRIPT_SOUND_DOG_FOOD_FACTORY_S, + SCRIPT_SOUND_DOG_FOOD_FACTORY_L, + SCRIPT_SOUND_LAUNDERETTE_LOOP_S, + SCRIPT_SOUND_LAUNDERETTE_LOOP_L, + SCRIPT_SOUND_CHINATOWN_RESTAURANT_S, + SCRIPT_SOUND_CHINATOWN_RESTAURANT_L, + SCRIPT_SOUND_CIPRIANI_RESAURANT_S, + SCRIPT_SOUND_CIPRIANI_RESAURANT_L, + SCRIPT_SOUND_46_S, + SCRIPT_SOUND_47_L, + SCRIPT_SOUND_MARCO_BISTRO_S, + SCRIPT_SOUND_MARCO_BISTRO_L, + SCRIPT_SOUND_AIRPORT_LOOP_S, + SCRIPT_SOUND_AIRPORT_LOOP_L, + SCRIPT_SOUND_SHOP_LOOP_S, + SCRIPT_SOUND_SHOP_LOOP_L, + SCRIPT_SOUND_CINEMA_LOOP_S, + SCRIPT_SOUND_CINEMA_LOOP_L, + SCRIPT_SOUND_DOCKS_LOOP_S, + SCRIPT_SOUND_DOCKS_LOOP_L, + SCRIPT_SOUND_HOME_LOOP_S, + SCRIPT_SOUND_HOME_LOOP_L, + SCRIPT_SOUND_FRANKIE_PIANO, + SCRIPT_SOUND_PARTY_1_LOOP, + SCRIPT_SOUND_PORN_CINEMA_1_S, + SCRIPT_SOUND_PORN_CINEMA_1_L, + SCRIPT_SOUND_PORN_CINEMA_2_S, + SCRIPT_SOUND_PORN_CINEMA_2_L, + SCRIPT_SOUND_PORN_CINEMA_3_S, + SCRIPT_SOUND_PORN_CINEMA_3_L, + SCRIPT_SOUND_BANK_ALARM_LOOP_S, + SCRIPT_SOUND_BANK_ALARM_LOOP_L, + SCRIPT_SOUND_POLICE_BALL_LOOP_S, + SCRIPT_SOUND_POLICE_BALL_LOOP_L, + SCRIPT_SOUND_RAVE_LOOP_INDUSTRIAL_S, + SCRIPT_SOUND_RAVE_LOOP_INDUSTRIAL_L, + SCRIPT_SOUND_74, + SCRIPT_SOUND_75, + SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_S, + SCRIPT_SOUND_POLICE_CELL_BEATING_LOOP_L, + SCRIPT_SOUND_INJURED_PED_MALE_OUCH_S, + SCRIPT_SOUND_INJURED_PED_MALE_OUCH_L, + SCRIPT_SOUND_INJURED_PED_FEMALE_OUCH_S, + SCRIPT_SOUND_INJURED_PED_FEMALE_OUCH_L, + SCRIPT_SOUND_EVIDENCE_PICKUP, + SCRIPT_SOUND_UNLOAD_GOLD, + SCRIPT_SOUND_RAVE_1_LOOP_S, + SCRIPT_SOUND_RAVE_1_LOOP_L, + SCRIPT_SOUND_RAVE_2_LOOP_S, + SCRIPT_SOUND_RAVE_2_LOOP_L, + SCRIPT_SOUND_RAVE_3_LOOP_S, + SCRIPT_SOUND_RAVE_3_LOOP_L, + SCRIPT_SOUND_MISTY_SEX_S, + SCRIPT_SOUND_MISTY_SEX_L, + SCRIPT_SOUND_GATE_START_CLUNK, + SCRIPT_SOUND_GATE_STOP_CLUNK, + SCRIPT_SOUND_PART_MISSION_COMPLETE, + SCRIPT_SOUND_CHUNKY_RUN_SHOUT, + SCRIPT_SOUND_SECURITY_GUARD_AWAY_SHOUT, + SCRIPT_SOUND_RACE_START_3, + SCRIPT_SOUND_RACE_START_2, + SCRIPT_SOUND_RACE_START_1, + SCRIPT_SOUND_RACE_START_GO, + SCRIPT_SOUND_SWAT_PED_SHOUT, + SCRIPT_SOUND_PRETEND_FIRE_LOOP, + SCRIPT_SOUND_AMMUNATION_CHAT_1, + SCRIPT_SOUND_AMMUNATION_CHAT_2, + SCRIPT_SOUND_AMMUNATION_CHAT_3, + SCRIPT_SOUND_BULLET_HIT_GROUND_1, + SCRIPT_SOUND_BULLET_HIT_GROUND_2, + SCRIPT_SOUND_BULLET_HIT_GROUND_3, + SCRIPT_SOUND_BULLET_HIT_WATER, // no sound + SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_1, + SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_2, + SCRIPT_SOUND_PAYPHONE_RINGING, + SCRIPT_SOUND_113, + SCRIPT_SOUND_GLASS_BREAK_L, + SCRIPT_SOUND_GLASS_BREAK_S, + SCRIPT_SOUND_GLASS_CRACK, + SCRIPT_SOUND_GLASS_LIGHT_BREAK, + SCRIPT_SOUND_BOX_DESTROYED_1, + SCRIPT_SOUND_BOX_DESTROYED_2, + SCRIPT_SOUND_METAL_COLLISION, + SCRIPT_SOUND_TIRE_COLLISION, + SCRIPT_SOUND_GUNSHELL_DROP, + SCRIPT_SOUND_GUNSHELL_DROP_SOFT, + SCRIPT_SOUND_TOTAL, + SCRIPT_SOUND_INVALID, +}; diff --git a/src/buildings/Building.cpp b/src/buildings/Building.cpp new file mode 100644 index 0000000..e4475ae --- /dev/null +++ b/src/buildings/Building.cpp @@ -0,0 +1,22 @@ +#include "common.h" + +#include "Building.h" +#include "Streaming.h" +#include "Pools.h" + +void *CBuilding::operator new(size_t sz) throw() { return CPools::GetBuildingPool()->New(); } +void CBuilding::operator delete(void *p, size_t sz) throw() { CPools::GetBuildingPool()->Delete((CBuilding*)p); } + +void +CBuilding::ReplaceWithNewModel(int32 id) +{ + DeleteRwObject(); + + if (CModelInfo::GetModelInfo(m_modelIndex)->GetNumRefs() == 0) + CStreaming::RemoveModel(m_modelIndex); + m_modelIndex = id; + + if(bIsBIGBuilding) + if(m_level == LEVEL_GENERIC || m_level == CGame::currLevel) + CStreaming::RequestModel(id, STREAMFLAGS_DONT_REMOVE); +} diff --git a/src/buildings/Building.h b/src/buildings/Building.h new file mode 100644 index 0000000..94e66c8 --- /dev/null +++ b/src/buildings/Building.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Entity.h" + +class CBuilding : public CEntity +{ +public: + CBuilding(void) { + m_type = ENTITY_TYPE_BUILDING; + bUsesCollision = true; + } + static void *operator new(size_t) throw(); + static void operator delete(void*, size_t) throw(); + + void ReplaceWithNewModel(int32 id); + + virtual bool GetIsATreadable(void) { return false; } +}; + +VALIDATE_SIZE(CBuilding, 0x64); + diff --git a/src/buildings/Solid.h b/src/buildings/Solid.h new file mode 100644 index 0000000..4ca800c --- /dev/null +++ b/src/buildings/Solid.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Entity.h" + +class CSolid : public CEntity +{ +public: + CSolid(void) { + m_type = ENTITY_TYPE_BUILDING; + bUsesCollision = true; + } +}; \ No newline at end of file diff --git a/src/buildings/Treadable.cpp b/src/buildings/Treadable.cpp new file mode 100644 index 0000000..d84603a --- /dev/null +++ b/src/buildings/Treadable.cpp @@ -0,0 +1,8 @@ +#include "common.h" + +#include "rpworld.h" +#include "Treadable.h" +#include "Pools.h" + +void *CTreadable::operator new(size_t sz) throw() { return CPools::GetTreadablePool()->New(); } +void CTreadable::operator delete(void *p, size_t sz) throw() { CPools::GetTreadablePool()->Delete((CTreadable*)p); } diff --git a/src/buildings/Treadable.h b/src/buildings/Treadable.h new file mode 100644 index 0000000..9e89596 --- /dev/null +++ b/src/buildings/Treadable.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Building.h" + +class CTreadable : public CBuilding +{ +public: + static void *operator new(size_t) throw(); + static void operator delete(void*, size_t) throw(); + + int16 m_nodeIndices[2][12]; // first car, then ped + + bool GetIsATreadable(void) { return true; } +}; + +VALIDATE_SIZE(CTreadable, 0x94); + diff --git a/src/collision/ColBox.cpp b/src/collision/ColBox.cpp new file mode 100644 index 0000000..53cba88 --- /dev/null +++ b/src/collision/ColBox.cpp @@ -0,0 +1,21 @@ +#include "common.h" +#include "ColBox.h" + +void +CColBox::Set(const CVector &min, const CVector &max, uint8 surf, uint8 piece) +{ + this->min = min; + this->max = max; + this->surface = surf; + this->piece = piece; +} + +CColBox& +CColBox::operator=(const CColBox& other) +{ + min = other.min; + max = other.max; + surface = other.surface; + piece = other.piece; + return *this; +} \ No newline at end of file diff --git a/src/collision/ColBox.h b/src/collision/ColBox.h new file mode 100644 index 0000000..ac2cd67 --- /dev/null +++ b/src/collision/ColBox.h @@ -0,0 +1,16 @@ +#pragma once + +#include "SurfaceTable.h" + +struct CColBox +{ + CVector min; + CVector max; + uint8 surface; + uint8 piece; + + void Set(const CVector &min, const CVector &max, uint8 surf = SURFACE_DEFAULT, uint8 piece = 0); + CVector GetSize(void) { return max - min; } + + CColBox& operator=(const CColBox &other); +}; \ No newline at end of file diff --git a/src/collision/ColLine.cpp b/src/collision/ColLine.cpp new file mode 100644 index 0000000..c624744 --- /dev/null +++ b/src/collision/ColLine.cpp @@ -0,0 +1,9 @@ +#include "common.h" +#include "ColLine.h" + +void +CColLine::Set(const CVector &p0, const CVector &p1) +{ + this->p0 = p0; + this->p1 = p1; +} \ No newline at end of file diff --git a/src/collision/ColLine.h b/src/collision/ColLine.h new file mode 100644 index 0000000..21587a0 --- /dev/null +++ b/src/collision/ColLine.h @@ -0,0 +1,14 @@ +#pragma once + +struct CColLine +{ + // NB: this has to be compatible with two CVuVectors + CVector p0; + int pad0; + CVector p1; + int pad1; + + CColLine(void) { }; + CColLine(const CVector &p0, const CVector &p1) { this->p0 = p0; this->p1 = p1; }; + void Set(const CVector &p0, const CVector &p1); +}; \ No newline at end of file diff --git a/src/collision/ColModel.cpp b/src/collision/ColModel.cpp new file mode 100644 index 0000000..fb90e7d --- /dev/null +++ b/src/collision/ColModel.cpp @@ -0,0 +1,190 @@ +#include "common.h" +#include "ColModel.h" +#include "Game.h" +#include "MemoryHeap.h" + +CColModel::CColModel(void) +{ + numSpheres = 0; + spheres = nil; + numLines = 0; + lines = nil; + numBoxes = 0; + boxes = nil; + numTriangles = 0; + vertices = nil; + triangles = nil; + trianglePlanes = nil; + level = CGame::currLevel; + ownsCollisionVolumes = true; +} + +CColModel::~CColModel(void) +{ + RemoveCollisionVolumes(); + RemoveTrianglePlanes(); +} + +void +CColModel::RemoveCollisionVolumes(void) +{ + if(ownsCollisionVolumes){ + RwFree(spheres); + RwFree(lines); + RwFree(boxes); + RwFree(vertices); + RwFree(triangles); + } + numSpheres = 0; + numLines = 0; + numBoxes = 0; + numTriangles = 0; + spheres = nil; + lines = nil; + boxes = nil; + vertices = nil; + triangles = nil; +} + +void +CColModel::CalculateTrianglePlanes(void) +{ + PUSH_MEMID(MEMID_COLLISION); + + // HACK: allocate space for one more element to stuff the link pointer into + trianglePlanes = (CColTrianglePlane*)RwMalloc(sizeof(CColTrianglePlane) * (numTriangles+1)); + REGISTER_MEMPTR(&trianglePlanes); + for(int i = 0; i < numTriangles; i++) + trianglePlanes[i].Set(vertices, triangles[i]); + + POP_MEMID(); +} + +void +CColModel::RemoveTrianglePlanes(void) +{ + RwFree(trianglePlanes); + trianglePlanes = nil; +} + +void +CColModel::SetLinkPtr(CLink *lptr) +{ + assert(trianglePlanes); + *(CLink**)ALIGNPTR(&trianglePlanes[numTriangles]) = lptr; +} + +CLink* +CColModel::GetLinkPtr(void) +{ + assert(trianglePlanes); + return *(CLink**)ALIGNPTR(&trianglePlanes[numTriangles]); +} + +void +CColModel::GetTrianglePoint(CVector &v, int i) const +{ + v = vertices[i].Get(); +} + +CColModel& +CColModel::operator=(const CColModel &other) +{ + int i; + int numVerts; + + boundingSphere = other.boundingSphere; + boundingBox = other.boundingBox; + + // copy spheres + if(other.numSpheres){ + if(numSpheres != other.numSpheres){ + numSpheres = other.numSpheres; + if(spheres) + RwFree(spheres); + spheres = (CColSphere*)RwMalloc(numSpheres*sizeof(CColSphere)); + } + for(i = 0; i < numSpheres; i++) + spheres[i] = other.spheres[i]; + }else{ + numSpheres = 0; + if(spheres) + RwFree(spheres); + spheres = nil; + } + + // copy lines + if(other.numLines){ + if(numLines != other.numLines){ + numLines = other.numLines; + if(lines) + RwFree(lines); + lines = (CColLine*)RwMalloc(numLines*sizeof(CColLine)); + } + for(i = 0; i < numLines; i++) + lines[i] = other.lines[i]; + }else{ + numLines = 0; + if(lines) + RwFree(lines); + lines = nil; + } + + // copy boxes + if(other.numBoxes){ + if(numBoxes != other.numBoxes){ + numBoxes = other.numBoxes; + if(boxes) + RwFree(boxes); + boxes = (CColBox*)RwMalloc(numBoxes*sizeof(CColBox)); + } + for(i = 0; i < numBoxes; i++) + boxes[i] = other.boxes[i]; + }else{ + numBoxes = 0; + if(boxes) + RwFree(boxes); + boxes = nil; + } + + // copy mesh + if(other.numTriangles){ + // copy vertices + numVerts = 0; + for(i = 0; i < other.numTriangles; i++){ + if(other.triangles[i].a > numVerts) + numVerts = other.triangles[i].a; + if(other.triangles[i].b > numVerts) + numVerts = other.triangles[i].b; + if(other.triangles[i].c > numVerts) + numVerts = other.triangles[i].c; + } + numVerts++; + if(vertices) + RwFree(vertices); + if(numVerts){ + vertices = (CompressedVector*)RwMalloc(numVerts*sizeof(CompressedVector)); + for(i = 0; i < numVerts; i++) + vertices[i] = other.vertices[i]; + } + + // copy triangles + if(numTriangles != other.numTriangles){ + numTriangles = other.numTriangles; + if(triangles) + RwFree(triangles); + triangles = (CColTriangle*)RwMalloc(numTriangles*sizeof(CColTriangle)); + } + for(i = 0; i < numTriangles; i++) + triangles[i] = other.triangles[i]; + }else{ + numTriangles = 0; + if(triangles) + RwFree(triangles); + triangles = nil; + if(vertices) + RwFree(vertices); + vertices = nil; + } + return *this; +} diff --git a/src/collision/ColModel.h b/src/collision/ColModel.h new file mode 100644 index 0000000..7dcdfa4 --- /dev/null +++ b/src/collision/ColModel.h @@ -0,0 +1,37 @@ +#pragma once + +#include "templates.h" +#include "ColBox.h" +#include "ColSphere.h" +#include "ColLine.h" +#include "ColPoint.h" +#include "ColTriangle.h" + +struct CColModel +{ + CColSphere boundingSphere; + CColBox boundingBox; + int16 numSpheres; + int16 numLines; + int16 numBoxes; + int16 numTriangles; + int32 level; + bool ownsCollisionVolumes; // missing on PS2 + CColSphere *spheres; + CColLine *lines; + CColBox *boxes; + CompressedVector *vertices; + CColTriangle *triangles; + CColTrianglePlane *trianglePlanes; + + CColModel(void); + ~CColModel(void); + void RemoveCollisionVolumes(void); + void CalculateTrianglePlanes(void); + void RemoveTrianglePlanes(void); + CLink *GetLinkPtr(void); + void SetLinkPtr(CLink*); + void GetTrianglePoint(CVector &v, int i) const; + + CColModel& operator=(const CColModel& other); +}; \ No newline at end of file diff --git a/src/collision/ColPoint.cpp b/src/collision/ColPoint.cpp new file mode 100644 index 0000000..fbf9e8c --- /dev/null +++ b/src/collision/ColPoint.cpp @@ -0,0 +1,16 @@ +#include "common.h" +#include "ColPoint.h" + +CColPoint& +CColPoint::operator=(const CColPoint &other) +{ + point = other.point; + normal = other.normal; + surfaceA = other.surfaceA; + pieceA = other.pieceA; + surfaceB = other.surfaceB; + pieceB = other.pieceB; + + // no depth? + return *this; +} diff --git a/src/collision/ColPoint.h b/src/collision/ColPoint.h new file mode 100644 index 0000000..a15b234 --- /dev/null +++ b/src/collision/ColPoint.h @@ -0,0 +1,34 @@ +#pragma once + +struct CColPoint +{ + CVector point; + int pad1; + // the surface normal on the surface of point + CVector normal; + int pad2; + uint8 surfaceA; + uint8 pieceA; + uint8 surfaceB; + uint8 pieceB; + float depth; + + const CVector &GetNormal() { return normal; } + float GetDepth() { return depth; } + void Set(float depth, uint8 surfA, uint8 pieceA, uint8 surfB, uint8 pieceB) { + this->depth = depth; + this->surfaceA = surfA; + this->pieceA = pieceA; + this->surfaceB = surfB; + this->pieceB = pieceB; + } + void Set(uint8 surfA, uint8 pieceA, uint8 surfB, uint8 pieceB) { + this->surfaceA = surfA; + this->pieceA = pieceA; + this->surfaceB = surfB; + this->pieceB = pieceB; + } + + CColPoint &operator=(const CColPoint &other); +}; + diff --git a/src/collision/ColSphere.cpp b/src/collision/ColSphere.cpp new file mode 100644 index 0000000..9aac01e --- /dev/null +++ b/src/collision/ColSphere.cpp @@ -0,0 +1,11 @@ +#include "common.h" +#include "ColSphere.h" + +void +CColSphere::Set(float radius, const CVector ¢er, uint8 surf, uint8 piece) +{ + this->radius = radius; + this->center = center; + this->surface = surf; + this->piece = piece; +} \ No newline at end of file diff --git a/src/collision/ColSphere.h b/src/collision/ColSphere.h new file mode 100644 index 0000000..70e2976 --- /dev/null +++ b/src/collision/ColSphere.h @@ -0,0 +1,13 @@ +#pragma once + +#include "SurfaceTable.h" + +struct CColSphere +{ + // NB: this has to be compatible with a CVuVector + CVector center; + float radius; + uint8 surface; + uint8 piece; + void Set(float radius, const CVector ¢er, uint8 surf = SURFACE_DEFAULT, uint8 piece = 0); +}; \ No newline at end of file diff --git a/src/collision/ColTriangle.cpp b/src/collision/ColTriangle.cpp new file mode 100644 index 0000000..9120fcf --- /dev/null +++ b/src/collision/ColTriangle.cpp @@ -0,0 +1,41 @@ +#include "common.h" +#include "ColTriangle.h" + +void +CColTriangle::Set(const CompressedVector *, int a, int b, int c, uint8 surf, uint8 piece) +{ + this->a = a; + this->b = b; + this->c = c; + this->surface = surf; +} + +#ifdef VU_COLLISION +void +CColTrianglePlane::Set(const CVector &va, const CVector &vb, const CVector &vc) +{ + CVector norm = CrossProduct(vc-va, vb-va); + norm.Normalise(); + float d = DotProduct(norm, va); + normal.x = norm.x*4096.0f; + normal.y = norm.y*4096.0f; + normal.z = norm.z*4096.0f; + dist = d*128.0f; +} +#else +void +CColTrianglePlane::Set(const CVector &va, const CVector &vb, const CVector &vc) +{ + normal = CrossProduct(vc-va, vb-va); + normal.Normalise(); + dist = DotProduct(normal, va); + CVector an(Abs(normal.x), Abs(normal.y), Abs(normal.z)); + // find out largest component and its direction + if(an.x > an.y && an.x > an.z) + dir = normal.x < 0.0f ? DIR_X_NEG : DIR_X_POS; + else if(an.y > an.z) + dir = normal.y < 0.0f ? DIR_Y_NEG : DIR_Y_POS; + else + dir = normal.z < 0.0f ? DIR_Z_NEG : DIR_Z_POS; +} +#endif \ No newline at end of file diff --git a/src/collision/ColTriangle.h b/src/collision/ColTriangle.h new file mode 100644 index 0000000..9e918e3 --- /dev/null +++ b/src/collision/ColTriangle.h @@ -0,0 +1,68 @@ +#pragma once + +#include "CompressedVector.h" + +enum Direction { + DIR_X_POS, + DIR_X_NEG, + DIR_Y_POS, + DIR_Y_NEG, + DIR_Z_POS, + DIR_Z_NEG, +}; + +struct CColTriangle +{ + uint16 a; + uint16 b; + uint16 c; + uint8 surface; + + void Set(const CompressedVector *v, int a, int b, int c, uint8 surf, uint8 piece); +}; + +struct CColTrianglePlane +{ +#ifdef VU_COLLISION + CompressedVector normal; + int16 dist; + + void Set(const CVector &va, const CVector &vb, const CVector &vc); + void Set(const CompressedVector *v, CColTriangle &tri) { Set(v[tri.a].Get(), v[tri.b].Get(), v[tri.c].Get()); } + void GetNormal(CVector &n) const { n.x = normal.x/4096.0f; n.y = normal.y/4096.0f; n.z = normal.z/4096.0f; } + float CalcPoint(const CVector &v) const { CVector n; GetNormal(n); return DotProduct(n, v) - dist/128.0f; }; +#ifdef GTA_PS2 + void Unpack(uint128 &qword) const { + __asm__ volatile ( + "lh $8, 0(%1)\n" + "lh $9, 2(%1)\n" + "lh $10, 4(%1)\n" + "lh $11, 6(%1)\n" + "pextlw $10, $8\n" + "pextlw $11, $9\n" + "pextlw $2, $11, $10\n" + "sq $2, %0\n" + : "=m" (qword) + : "r" (this) + : "$8", "$9", "$10", "$11", "$2" + ); + } +#else + void Unpack(int32 *qword) const { + qword[0] = normal.x; + qword[1] = normal.y; + qword[2] = normal.z; + qword[3] = dist; + } +#endif +#else + CVector normal; + float dist; + uint8 dir; + + void Set(const CVector &va, const CVector &vb, const CVector &vc); + void Set(const CompressedVector *v, CColTriangle &tri) { Set(v[tri.a].Get(), v[tri.b].Get(), v[tri.c].Get()); } + void GetNormal(CVector &n) const { n = normal; } + float CalcPoint(const CVector &v) const { return DotProduct(normal, v) - dist; }; +#endif +}; \ No newline at end of file diff --git a/src/collision/Collision.cpp b/src/collision/Collision.cpp new file mode 100644 index 0000000..832d773 --- /dev/null +++ b/src/collision/Collision.cpp @@ -0,0 +1,2753 @@ +#include "common.h" + +#include "VuVector.h" +#include "main.h" +#include "Lists.h" +#include "Game.h" +#include "Zones.h" +#include "General.h" +#include "ZoneCull.h" +#include "World.h" +#include "Entity.h" +#include "Train.h" +#include "Streaming.h" +#include "Pad.h" +#include "DMAudio.h" +#include "Population.h" +#include "FileLoader.h" +#include "Replay.h" +#include "CutsceneMgr.h" +#include "RenderBuffer.h" +#include "SurfaceTable.h" +#include "Lines.h" +#include "Collision.h" +#include "Frontend.h" + +#ifdef VU_COLLISION +#include "VuCollision.h" + +inline int +GetVUresult(void) +{ +#ifdef GTA_PS2 + int ret; + __asm__ volatile ( + "cfc2.i %0,vi01\n" // .i important! wait for VU0 to finish + : "=r" (ret) + ); + return ret; +#else + return vi01; +#endif +} + +inline int +GetVUresult(CVuVector &point, CVuVector &normal, float &dist) +{ +#ifdef GTA_PS2 + int ret; + __asm__ volatile ( + "cfc2.i %0,vi01\n" // .i important! wait for VU0 to finish + "sqc2 vf01,(%1)\n" + "sqc2 vf02,(%2)\n" + "qmfc2 $12,vf03\n" + "sw $12,(%3)\n" + : "=r" (ret) + : "r" (&point), "r" (&normal), "r" (&dist) + : "$12" + ); + return ret; +#else + point = vf01; + normal = vf02; + dist = vf03.x; + return vi01; +#endif +} + +#endif + +eLevelName CCollision::ms_collisionInMemory; +CLinkList CCollision::ms_colModelCache; + +void +CCollision::Init(void) +{ + ms_colModelCache.Init(NUMCOLCACHELINKS); + ms_collisionInMemory = LEVEL_GENERIC; +} + +void +CCollision::Shutdown(void) +{ + ms_colModelCache.Shutdown(); +} + +void +CCollision::Update(void) +{ + CVector playerCoors; + playerCoors = FindPlayerCoors(); + eLevelName level = CTheZones::m_CurrLevel; + bool forceLevelChange = false; + + if(CTimer::GetTimeInMilliseconds() < 2000 || CCutsceneMgr::IsCutsceneProcessing()) + return; + + // hardcode a level if there are no zones + if(level == LEVEL_GENERIC){ + if(CGame::currLevel == LEVEL_INDUSTRIAL && + playerCoors.x < 400.0f){ + level = LEVEL_COMMERCIAL; + forceLevelChange = true; + }else if(CGame::currLevel == LEVEL_SUBURBAN && + playerCoors.x > -450.0f && playerCoors.y < -1400.0f){ + level = LEVEL_COMMERCIAL; + forceLevelChange = true; + }else{ + if(playerCoors.x > 800.0f){ + level = LEVEL_INDUSTRIAL; + forceLevelChange = true; + }else if(playerCoors.x < -800.0f){ + level = LEVEL_SUBURBAN; + forceLevelChange = true; + } + } + } + if(level != LEVEL_GENERIC && level != CGame::currLevel) + CGame::currLevel = level; + if(ms_collisionInMemory != CGame::currLevel) + LoadCollisionWhenINeedIt(forceLevelChange); + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); +} + +eLevelName +GetCollisionInSectorList(CPtrList &list) +{ + CPtrNode *node; + CEntity *e; + int level; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + level = CModelInfo::GetColModel(e->GetModelIndex())->level; + if(level != LEVEL_GENERIC) + return (eLevelName)level; + } + return LEVEL_GENERIC; +} + +// Get a level this sector is in based on collision models +eLevelName +GetCollisionInSector(CSector §) +{ + int level; + + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_BUILDINGS]); + if(level == LEVEL_GENERIC) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + if(level == LEVEL_GENERIC) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_OBJECTS]); + if(level == LEVEL_GENERIC) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_OBJECTS_OVERLAP]); + if(level == LEVEL_GENERIC) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_DUMMIES]); + if(level == LEVEL_GENERIC) + level = GetCollisionInSectorList(sect.m_lists[ENTITYLIST_DUMMIES_OVERLAP]); + return (eLevelName)level; +} + +void +CCollision::LoadCollisionWhenINeedIt(bool forceChange) +{ + eLevelName level, l; + bool multipleLevels; + CVector playerCoors; + CVehicle *veh; + CEntryInfoNode *ei; + int sx, sy; + int xmin, xmax, ymin, ymax; + int x, y; + + level = LEVEL_GENERIC; + + playerCoors = FindPlayerCoors(); + sx = CWorld::GetSectorIndexX(playerCoors.x); + sy = CWorld::GetSectorIndexY(playerCoors.y); + multipleLevels = false; + + veh = FindPlayerVehicle(); + if(veh && veh->IsTrain()){ + if(((CTrain*)veh)->m_nDoorState != TRAIN_DOOR_OPEN) + return; + }else if(playerCoors.z < -4.0f && !CCullZones::DoINeedToLoadCollision()) + return; + + // Figure out whose level's collisions we're most likely to be interested in + if(!forceChange){ + if(veh && veh->IsBoat()){ + // on water we expect to be between levels + multipleLevels = true; + }else{ + xmin = Max(sx - 1, 0); + xmax = Min(sx + 1, NUMSECTORS_X-1); + ymin = Max(sy - 1, 0); + ymax = Min(sy + 1, NUMSECTORS_Y-1); + + for(x = xmin; x <= xmax; x++) + for(y = ymin; y <= ymax; y++){ + l = GetCollisionInSector(*CWorld::GetSector(x, y)); + if(l != LEVEL_GENERIC){ + if(level == LEVEL_GENERIC) + level = l; + if(level != l) + multipleLevels = true; + } + } + } + + if(multipleLevels && veh && veh->IsBoat()) + for(ei = veh->m_entryInfoList.first; ei; ei = ei->next){ + level = GetCollisionInSector(*ei->sector); + if(level != LEVEL_GENERIC) + break; + } + } + + if (level == CGame::currLevel || forceChange) { +#ifdef FIX_BUGS + CTimer::Suspend(); +#else + CTimer::Stop(); +#endif + ISLAND_LOADING_IS(LOW) + { + DMAudio.SetEffectsFadeVol(0); + CPad::StopPadsShaking(); + LoadCollisionScreen(CGame::currLevel); + DMAudio.Service(); + } + + CPopulation::DealWithZoneChange(ms_collisionInMemory, CGame::currLevel, false); + + ISLAND_LOADING_ISNT(HIGH) + { + CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + } + ISLAND_LOADING_IS(LOW) + { + CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL); + CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL); + CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN); + CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel); + CStreaming::RemoveUnusedModelsInLoadedList(); + CGame::TidyUpMemory(true, true); + CFileLoader::LoadCollisionFromDatFile(CGame::currLevel); + } + + ms_collisionInMemory = CGame::currLevel; + CReplay::EmptyReplayBuffer(); + ISLAND_LOADING_IS(LOW) + { + if (CGame::currLevel != LEVEL_GENERIC) + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + CStreaming::RequestBigBuildings(CGame::currLevel); + } +#ifdef NO_ISLAND_LOADING + else if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_MEDIUM) + CStreaming::RequestIslands(CGame::currLevel); +#endif + CStreaming::LoadAllRequestedModels(true); + + ISLAND_LOADING_IS(LOW) + { + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); + + CGame::TidyUpMemory(true, true); + } +#ifdef FIX_BUGS + CTimer::Resume(); +#else + CTimer::Update(); +#endif + ISLAND_LOADING_IS(LOW) + DMAudio.SetEffectsFadeVol(127); + } +} + +#ifdef NO_ISLAND_LOADING +bool CCollision::bAlreadyLoaded = false; +#endif +void +CCollision::SortOutCollisionAfterLoad(void) +{ + if(ms_collisionInMemory == CGame::currLevel) + return; + ISLAND_LOADING_IS(LOW) + CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel); + + if (CGame::currLevel != LEVEL_GENERIC) { +#ifdef NO_ISLAND_LOADING + if (CMenuManager::m_PrefsIslandLoading != CMenuManager::ISLAND_LOADING_LOW) { + if (bAlreadyLoaded) { + ms_collisionInMemory = CGame::currLevel; + return; + } + bAlreadyLoaded = true; + CFileLoader::LoadCollisionFromDatFile(LEVEL_INDUSTRIAL); + CFileLoader::LoadCollisionFromDatFile(LEVEL_COMMERCIAL); + CFileLoader::LoadCollisionFromDatFile(LEVEL_SUBURBAN); + } else +#endif + CFileLoader::LoadCollisionFromDatFile(CGame::currLevel); + if(!CGame::playingIntro) + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + } + ms_collisionInMemory = CGame::currLevel; + CGame::TidyUpMemory(true, false); +} + +void +CCollision::LoadCollisionScreen(eLevelName level) +{ + static Const char *levelNames[4] = { + "", + "IND_ZON", + "COM_ZON", + "SUB_ZON" + }; + + // Why twice? + LoadingIslandScreen(levelNames[level]); + LoadingIslandScreen(levelNames[level]); +} + +// +// Test +// + + +bool +CCollision::TestSphereSphere(const CColSphere &s1, const CColSphere &s2) +{ + float d = s1.radius + s2.radius; + return (s1.center - s2.center).MagnitudeSqr() < d*d; +} + +bool +CCollision::TestSphereBox(const CColSphere &sph, const CColBox &box) +{ + if(sph.center.x + sph.radius < box.min.x) return false; + if(sph.center.x - sph.radius > box.max.x) return false; + if(sph.center.y + sph.radius < box.min.y) return false; + if(sph.center.y - sph.radius > box.max.y) return false; + if(sph.center.z + sph.radius < box.min.z) return false; + if(sph.center.z - sph.radius > box.max.z) return false; + return true; +} + +bool +CCollision::TestLineBox(const CColLine &line, const CColBox &box) +{ + float t, x, y, z; + // If either line point is in the box, we have a collision + if(line.p0.x > box.min.x && line.p0.x < box.max.x && + line.p0.y > box.min.y && line.p0.y < box.max.y && + line.p0.z > box.min.z && line.p0.z < box.max.z) + return true; + if(line.p1.x > box.min.x && line.p1.x < box.max.x && + line.p1.y > box.min.y && line.p1.y < box.max.y && + line.p1.z > box.min.z && line.p1.z < box.max.z) + return true; + + // check if points are on opposite sides of min x plane + if((box.min.x - line.p1.x) * (box.min.x - line.p0.x) < 0.0f){ + // parameter along line where we intersect + t = (box.min.x - line.p0.x) / (line.p1.x - line.p0.x); + // y of intersection + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + // z of intersection + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // same test with max x plane + if((line.p1.x - box.max.x) * (line.p0.x - box.max.x) < 0.0f){ + t = (line.p0.x - box.max.x) / (line.p0.x - line.p1.x); + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // min y plne + if((box.min.y - line.p0.y) * (box.min.y - line.p1.y) < 0.0f){ + t = (box.min.y - line.p0.y) / (line.p1.y - line.p0.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // max y plane + if((line.p0.y - box.max.y) * (line.p1.y - box.max.y) < 0.0f){ + t = (line.p0.y - box.max.y) / (line.p0.y - line.p1.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + return true; + } + } + + // min z plne + if((box.min.z - line.p0.z) * (box.min.z - line.p1.z) < 0.0f){ + t = (box.min.z - line.p0.z) / (line.p1.z - line.p0.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + return true; + } + } + + // max z plane + if((line.p0.z - box.max.z) * (line.p1.z - box.max.z) < 0.0f){ + t = (line.p0.z - box.max.z) / (line.p0.z - line.p1.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + return true; + } + } + return false; +} + +bool +CCollision::TestVerticalLineBox(const CColLine &line, const CColBox &box) +{ + if(line.p0.x <= box.min.x) return false; + if(line.p0.y <= box.min.y) return false; + if(line.p0.x >= box.max.x) return false; + if(line.p0.y >= box.max.y) return false; + if(line.p0.z < line.p1.z){ + if(line.p0.z > box.max.z) return false; + if(line.p1.z < box.min.z) return false; + }else{ + if(line.p1.z > box.max.z) return false; + if(line.p0.z < box.min.z) return false; + } + return true; +} + +bool +CCollision::TestLineTriangle(const CColLine &line, const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + VuTriangle vutri; + verts[tri.a].Unpack(vutri.v0); + verts[tri.b].Unpack(vutri.v1); + verts[tri.c].Unpack(vutri.v2); + plane.Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(*(CVuVector*)&line.p0, *(CVuVector*)&line.p1, vutri); + + if(GetVUresult()) + return true; + return false; +#else + float t; + CVector normal; + plane.GetNormal(normal); + + // if points are on the same side, no collision + if(plane.CalcPoint(line.p0) * plane.CalcPoint(line.p1) > 0.0f) + return false; + + float p0dist = DotProduct(line.p1 - line.p0, normal); + +#ifdef FIX_BUGS + // line lines in the plane, assume no collision + if (p0dist == 0.0f) + return false; +#endif + + // intersection parameter on line + t = -plane.CalcPoint(line.p0) / p0dist; + // find point of intersection + CVector p = line.p0 + (line.p1-line.p0)*t; + + const CVector &va = verts[tri.a].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + CVector2D vec1, vec2, vec3, vect; + + // We do the test in 2D. With the plane direction we + // can figure out how to project the vectors. + // normal = (c-a) x (b-a) + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + // This is our triangle: + // 3-------2 + // \ P / + // \ / + // \ / + // 1 + // We can use the "2d cross product" to check on which side + // a vector is of another. Test is true if point is inside of all edges. + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false; + return true; +#endif +} + +// Test if line segment intersects with sphere. +// If the first point is inside the sphere this test does not register a collision! +// The code is reversed from the original code and rather ugly, see Process for a clear version. +// TODO: actually rewrite this mess +bool +CCollision::TestLineSphere(const CColLine &line, const CColSphere &sph) +{ + CVector v01 = line.p1 - line.p0; // vector from p0 to p1 + CVector v0c = sph.center - line.p0; // vector from p0 to center + float linesq = v01.MagnitudeSqr(); + // I leave in the strange -2 factors even though they serve no real purpose + float projline = -2.0f * DotProduct(v01, v0c); // project v0c onto line + // Square of tangent from p0 multiplied by line length so we can compare with projline. + // The length of the tangent would be this: Sqrt((c-p0)^2 - r^2). + // Negative if p0 is inside the sphere! This breaks the test! + float tansq = 4.0f * linesq * + (sph.center.MagnitudeSqr() - 2.0f*DotProduct(sph.center, line.p0) + line.p0.MagnitudeSqr() - sph.radius*sph.radius); + float diffsq = projline*projline - tansq; + // if diffsq < 0 that means the line is a passant, so no intersection + if(diffsq < 0.0f) + return false; + // projline (negative in GTA for some reason) is the point on the line + // in the middle of the two intersection points (startin from p0). + // Sqrt(diffsq) somehow works out to be the distance from that + // midpoint to the intersection points. + // So subtract that and get rid of the awkward scaling: + float f = (-projline - Sqrt(diffsq)) / (2.0f*linesq); + // f should now be in range [0, 1] for [p0, p1] + return f >= 0.0f && f <= 1.0f; +} + +bool +CCollision::TestSphereTriangle(const CColSphere &sphere, + const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + VuTriangle vutri; + verts[tri.a].Unpack(vutri.v0); + verts[tri.b].Unpack(vutri.v1); + verts[tri.c].Unpack(vutri.v2); + plane.Unpack(vutri.plane); + + SphereToTriangleCollisionCompressed(*(CVuVector*)&sphere, vutri); + + if(GetVUresult()) + return true; + return false; +#else + // If sphere and plane don't intersect, no collision + float planedist = plane.CalcPoint(sphere.center); + if(Abs(planedist) > sphere.radius) + return false; + + const CVector &va = verts[tri.a].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + + // calculate two orthogonal basis vectors for the triangle + CVector vec2 = vb - va; + float len = vec2.Magnitude(); + vec2 = vec2 * (1.0f/len); + CVector normal; + plane.GetNormal(normal); + CVector vec1 = CrossProduct(vec2, normal); + + // We know A has local coordinate [0,0] and B has [0,len]. + // Now calculate coordinates on triangle for these two vectors: + CVector vac = vc - va; + CVector vas = sphere.center - va; + CVector2D b(0.0f, len); + CVector2D c(DotProduct(vec1, vac), DotProduct(vec2, vac)); + CVector2D s(DotProduct(vec1, vas), DotProduct(vec2, vas)); + + // The three triangle lines partition the space into 6 sectors, + // find out in which the center lies. + int insideAB = CrossProduct2D(s, b) >= 0.0f; + int insideAC = CrossProduct2D(c, s) >= 0.0f; + int insideBC = CrossProduct2D(s-b, c-b) >= 0.0f; + + int testcase = insideAB + insideAC + insideBC; + float dist = 0.0f; + switch(testcase){ + case 1: + // closest to a vertex + if(insideAB) dist = (sphere.center - vc).Magnitude(); + else if(insideAC) dist = (sphere.center - vb).Magnitude(); + else if(insideBC) dist = (sphere.center - va).Magnitude(); + else assert(0); + break; + case 2: + // closest to an edge + // looks like original game as DistToLine manually inlined + if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center); + else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center); + else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center); + else assert(0); + break; + case 3: + // center is in triangle + dist = Abs(planedist); + break; + default: + assert(0); + } + + return dist < sphere.radius; +#endif +} + +bool +CCollision::TestLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSeeThrough) +{ +#ifdef VU_COLLISION + CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CVuVector newline[2]; + TransformPoints(newline, 2, matTransform, &line.p0, sizeof(CColLine)/2); + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(*(CColLine*)newline, model.boundingBox)) + return false; + + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.spheres[i].surface)) continue; + if(TestLineSphere(*(CColLine*)newline, model.spheres[i])) + return true; + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + if(TestLineBox(*(CColLine*)newline, model.boxes[i])) + return true; + } + + CalculateTrianglePlanes(&model); + int lastTest = -1; + VuTriangle vutri; + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lastTest = i; + break; + } +#ifdef FIX_BUGS + // no need to check first again + i++; +#endif + for(; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + if(GetVUresult()) + return true; + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lastTest = i; + + } + if(lastTest != -1 && GetVUresult()) + return true; + + return false; +#else + static CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CColLine newline(matTransform * line.p0, matTransform * line.p1); + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(newline, model.boundingBox)) + return false; + + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.spheres[i].surface)) continue; + if(TestLineSphere(newline, model.spheres[i])) + return true; + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + if(TestLineBox(newline, model.boxes[i])) + return true; + } + + CalculateTrianglePlanes(&model); + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + if(TestLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i])) + return true; + } + + return false; +#endif +} + + +// +// Process +// + +// For Spheres mindist is the squared distance to its center +// For Lines mindist is between [0,1] + +bool +CCollision::ProcessSphereSphere(const CColSphere &s1, const CColSphere &s2, CColPoint &point, float &mindistsq) +{ + CVector dist = s1.center - s2.center; + float d = dist.Magnitude() - s2.radius; // distance from s1's center to s2 + float depth = s1.radius - d; // sphere overlap + if(d < 0.0f) d = 0.0f; // clamp to zero, i.e. if s1's center is inside s2 + // no collision if sphere is not close enough + if(d*d < mindistsq && d < s1.radius){ + dist.Normalise(); + point.point = s1.center - dist*d; + point.normal = dist; +#ifndef VU_COLLISION + point.surfaceA = s1.surface; + point.pieceA = s1.piece; + point.surfaceB = s2.surface; + point.pieceB = s2.piece; +#endif + point.depth = depth; + mindistsq = d*d; // collision radius + return true; + } + return false; +} + +bool +CCollision::ProcessSphereBox(const CColSphere &sph, const CColBox &box, CColPoint &point, float &mindistsq) +{ + CVector p; + CVector dist; + + // GTA's code is too complicated, uses a huge 3x3x3 if statement + // we can simplify the structure a lot + + // first make sure we have a collision at all + if(sph.center.x + sph.radius < box.min.x) return false; + if(sph.center.x - sph.radius > box.max.x) return false; + if(sph.center.y + sph.radius < box.min.y) return false; + if(sph.center.y - sph.radius > box.max.y) return false; + if(sph.center.z + sph.radius < box.min.z) return false; + if(sph.center.z - sph.radius > box.max.z) return false; + + // Now find out where the sphere center lies in relation to all the sides + int xpos = sph.center.x < box.min.x ? 1 : + sph.center.x > box.max.x ? 2 : + 0; + int ypos = sph.center.y < box.min.y ? 1 : + sph.center.y > box.max.y ? 2 : + 0; + int zpos = sph.center.z < box.min.z ? 1 : + sph.center.z > box.max.z ? 2 : + 0; + + if(xpos == 0 && ypos == 0 && zpos == 0){ + // sphere is inside the box + p = (box.min + box.max)*0.5f; + + dist = sph.center - p; + float lensq = dist.MagnitudeSqr(); + if(lensq < mindistsq){ + point.normal = dist * (1.0f/Sqrt(lensq)); + point.point = sph.center - point.normal; +#ifndef VU_COLLISION + point.surfaceA = sph.surface; + point.pieceA = sph.piece; + point.surfaceB = box.surface; + point.pieceB = box.piece; +#endif + + // find absolute distance to the closer side in each dimension + float dx = dist.x > 0.0f ? + box.max.x - sph.center.x : + sph.center.x - box.min.x; + float dy = dist.y > 0.0f ? + box.max.y - sph.center.y : + sph.center.y - box.min.y; + float dz = dist.z > 0.0f ? + box.max.z - sph.center.z : + sph.center.z - box.min.z; + // collision depth is maximum of that: + if(dx > dy && dx > dz) + point.depth = dx; + else if(dy > dz) + point.depth = dy; + else + point.depth = dz; + return true; + } + }else{ + // sphere is outside. + // closest point on box: + p.x = xpos == 1 ? box.min.x : + xpos == 2 ? box.max.x : + sph.center.x; + p.y = ypos == 1 ? box.min.y : + ypos == 2 ? box.max.y : + sph.center.y; + p.z = zpos == 1 ? box.min.z : + zpos == 2 ? box.max.z : + sph.center.z; + + dist = sph.center - p; + float lensq = dist.MagnitudeSqr(); + if(lensq < mindistsq){ + float len = Sqrt(lensq); + point.point = p; + point.normal = dist * (1.0f/len); +#ifndef VU_COLLISION + point.surfaceA = sph.surface; + point.pieceA = sph.piece; + point.surfaceB = box.surface; + point.pieceB = box.piece; +#endif + point.depth = sph.radius - len; + mindistsq = lensq; + return true; + } + } + return false; +} + +bool +CCollision::ProcessLineBox(const CColLine &line, const CColBox &box, CColPoint &point, float &mindist) +{ + float mint, t, x, y, z; + CVector normal; + CVector p; + + mint = 1.0f; + // check if points are on opposite sides of min x plane + if((box.min.x - line.p1.x) * (box.min.x - line.p0.x) < 0.0f){ + // parameter along line where we intersect + t = (box.min.x - line.p0.x) / (line.p1.x - line.p0.x); + // y of intersection + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + // z of intersection + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(box.min.x, y, z); + normal = CVector(-1.0f, 0.0f, 0.0f); + } + } + } + + // max x plane + if((line.p1.x - box.max.x) * (line.p0.x - box.max.x) < 0.0f){ + t = (line.p0.x - box.max.x) / (line.p0.x - line.p1.x); + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(box.max.x, y, z); + normal = CVector(1.0f, 0.0f, 0.0f); + } + } + } + + // min y plne + if((box.min.y - line.p0.y) * (box.min.y - line.p1.y) < 0.0f){ + t = (box.min.y - line.p0.y) / (line.p1.y - line.p0.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(x, box.min.y, z); + normal = CVector(0.0f, -1.0f, 0.0f); + } + } + } + + // max y plane + if((line.p0.y - box.max.y) * (line.p1.y - box.max.y) < 0.0f){ + t = (line.p0.y - box.max.y) / (line.p0.y - line.p1.y); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + z = line.p0.z + (line.p1.z - line.p0.z)*t; + if(z > box.min.z && z < box.max.z) + if(t < mint){ + mint = t; + p = CVector(x, box.max.y, z); + normal = CVector(0.0f, 1.0f, 0.0f); + } + } + } + + // min z plne + if((box.min.z - line.p0.z) * (box.min.z - line.p1.z) < 0.0f){ + t = (box.min.z - line.p0.z) / (line.p1.z - line.p0.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + if(t < mint){ + mint = t; + p = CVector(x, y, box.min.z); + normal = CVector(0.0f, 0.0f, -1.0f); + } + } + } + + // max z plane + if((line.p0.z - box.max.z) * (line.p1.z - box.max.z) < 0.0f){ + t = (line.p0.z - box.max.z) / (line.p0.z - line.p1.z); + x = line.p0.x + (line.p1.x - line.p0.x)*t; + if(x > box.min.x && x < box.max.x){ + y = line.p0.y + (line.p1.y - line.p0.y)*t; + if(y > box.min.y && y < box.max.y) + if(t < mint){ + mint = t; + p = CVector(x, y, box.max.z); + normal = CVector(0.0f, 0.0f, 1.0f); + } + } + } + + if(mint >= mindist) + return false; + + point.point = p; + point.normal = normal; +#ifndef VU_COLLISION + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = box.surface; + point.pieceB = box.piece; +#endif + mindist = mint; + + return true; +} + +// If line.p0 lies inside sphere, no collision is registered. +bool +CCollision::ProcessLineSphere(const CColLine &line, const CColSphere &sphere, CColPoint &point, float &mindist) +{ + CVector v01 = line.p1 - line.p0; + CVector v0c = sphere.center - line.p0; + float linesq = v01.MagnitudeSqr(); + // project v0c onto v01, scaled by |v01| this is the midpoint of the two intersections + float projline = DotProduct(v01, v0c); + // tangent of p0 to sphere, scaled by linesq just like projline^2 + float tansq = (v0c.MagnitudeSqr() - sphere.radius*sphere.radius) * linesq; + // this works out to be the square of the distance between the midpoint and the intersections + float diffsq = projline*projline - tansq; + // no intersection + if(diffsq < 0.0f) + return false; + // point of first intersection, in range [0,1] between p0 and p1 + float t = (projline - Sqrt(diffsq)) / linesq; + // if not on line or beyond mindist, no intersection + if(t < 0.0f || t > 1.0f || t >= mindist) + return false; + point.point = line.p0 + v01*t; + point.normal = point.point - sphere.center; + point.normal.Normalise(); +#ifndef VU_COLLISION + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = sphere.surface; + point.pieceB = sphere.piece; +#endif + mindist = t; + return true; +} + +bool +CCollision::ProcessVerticalLineTriangle(const CColLine &line, + const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindist, CStoredCollPoly *poly) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + bool res = ProcessLineTriangle(line, verts, tri, plane, point, mindist); + if(res && poly){ + poly->verts[0] = verts[tri.a].Get(); + poly->verts[1] = verts[tri.b].Get(); + poly->verts[2] = verts[tri.c].Get(); + poly->valid = true; + } + return res; +#else + float t; + CVector normal; + + const CVector &p0 = line.p0; + const CVector &va = verts[tri.a].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + + // early out bound rect test + if(p0.x < va.x && p0.x < vb.x && p0.x < vc.x) return false; + if(p0.x > va.x && p0.x > vb.x && p0.x > vc.x) return false; + if(p0.y < va.y && p0.y < vb.y && p0.y < vc.y) return false; + if(p0.y > va.y && p0.y > vb.y && p0.y > vc.y) return false; + + plane.GetNormal(normal); + // if points are on the same side, no collision + if(plane.CalcPoint(p0) * plane.CalcPoint(line.p1) > 0.0f) + return false; + + // intersection parameter on line + float h = (line.p1 - p0).z; + t = -plane.CalcPoint(p0) / (h * normal.z); + // early out if we're beyond the mindist + if(t >= mindist) + return false; + CVector p(p0.x, p0.y, p0.z + h*t); + + CVector2D vec1, vec2, vec3, vect; + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false; + if(t >= mindist) return false; + point.point = p; + point.normal = normal; + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = tri.surface; + point.pieceB = 0; + if(poly){ + poly->verts[0] = va; + poly->verts[1] = vb; + poly->verts[2] = vc; + poly->valid = true; + } + mindist = t; + return true; +#endif +} + +bool +CCollision::IsStoredPolyStillValidVerticalLine(const CVector &pos, float z, CColPoint &point, CStoredCollPoly *poly) +{ +#ifdef VU_COLLISION + if(!poly->valid) + return false; + + CVuVector p0 = pos; + CVuVector p1 = pos; + p1.z = z; + + CVector v01 = poly->verts[1] - poly->verts[0]; + CVector v02 = poly->verts[2] - poly->verts[0]; + CVuVector plane = CrossProduct(v02, v01); + plane.Normalise(); + plane.w = DotProduct(plane, poly->verts[0]); + + LineToTriangleCollision(p0, p1, poly->verts[0], poly->verts[1], poly->verts[2], plane); + + CVuVector pnt; + float dist; + if(!GetVUresult(pnt, plane, dist)) +#ifdef FIX_BUGS + // perhaps not needed but be safe + return poly->valid = false; +#else + return false; +#endif + point.point = pnt; + return true; +#else + float t; + + if(!poly->valid) + return false; + + // maybe inlined? + CColTrianglePlane plane; + plane.Set(poly->verts[0], poly->verts[1], poly->verts[2]); + + const CVector &va = poly->verts[0]; + const CVector &vb = poly->verts[1]; + const CVector &vc = poly->verts[2]; + CVector p0 = pos; + CVector p1(pos.x, pos.y, z); + + // The rest is pretty much CCollision::ProcessLineTriangle + + // if points are on the same side, no collision + if(plane.CalcPoint(p0) * plane.CalcPoint(p1) > 0.0f) + return poly->valid = false; + + // intersection parameter on line + CVector normal; + plane.GetNormal(normal); + t = -plane.CalcPoint(p0) / DotProduct(p1 - p0, normal); + // find point of intersection + CVector p = p0 + (p1-p0)*t; + + CVector2D vec1, vec2, vec3, vect; + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return poly->valid = false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return poly->valid = false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return poly->valid = false; + point.point = p; + return poly->valid = true; +#endif +} + +bool +CCollision::ProcessLineTriangle(const CColLine &line, + const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindist) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + VuTriangle vutri; + verts[tri.a].Unpack(vutri.v0); + verts[tri.b].Unpack(vutri.v1); + verts[tri.c].Unpack(vutri.v2); + plane.Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(*(CVuVector*)&line.p0, *(CVuVector*)&line.p1, vutri); + + CVuVector pnt, normal; + float dist; + if(GetVUresult(pnt, normal, dist)){ + if(dist < mindist){ + point.point = pnt; + point.normal = normal; + mindist = dist; + return true; + } + } + return false; +#else + float t; + CVector normal; + plane.GetNormal(normal); + + // if points are on the same side, no collision + if(plane.CalcPoint(line.p0) * plane.CalcPoint(line.p1) > 0.0f) + return false; + + float p0dist = DotProduct(line.p1 - line.p0, normal); + +#ifdef FIX_BUGS + // line lines in the plane, assume no collision + if (p0dist == 0.0f) + return false; +#endif + + // intersection parameter on line + t = -plane.CalcPoint(line.p0) / p0dist; + + // early out if we're beyond the mindist + if(t >= mindist) + return false; + // find point of intersection + CVector p = line.p0 + (line.p1-line.p0)*t; + + const CVector &va = verts[tri.a].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + CVector2D vec1, vec2, vec3, vect; + + switch(plane.dir){ + case DIR_X_POS: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vc.y; vec2.y = vc.z; + vec3.x = vb.y; vec3.y = vb.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_X_NEG: + vec1.x = va.y; vec1.y = va.z; + vec2.x = vb.y; vec2.y = vb.z; + vec3.x = vc.y; vec3.y = vc.z; + vect.x = p.y; vect.y = p.z; + break; + case DIR_Y_POS: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vc.z; vec2.y = vc.x; + vec3.x = vb.z; vec3.y = vb.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Y_NEG: + vec1.x = va.z; vec1.y = va.x; + vec2.x = vb.z; vec2.y = vb.x; + vec3.x = vc.z; vec3.y = vc.x; + vect.x = p.z; vect.y = p.x; + break; + case DIR_Z_POS: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vc.x; vec2.y = vc.y; + vec3.x = vb.x; vec3.y = vb.y; + vect.x = p.x; vect.y = p.y; + break; + case DIR_Z_NEG: + vec1.x = va.x; vec1.y = va.y; + vec2.x = vb.x; vec2.y = vb.y; + vec3.x = vc.x; vec3.y = vc.y; + vect.x = p.x; vect.y = p.y; + break; + default: + assert(0); + } + if(CrossProduct2D(vec2-vec1, vect-vec1) < 0.0f) return false; + if(CrossProduct2D(vec3-vec1, vect-vec1) > 0.0f) return false; + if(CrossProduct2D(vec3-vec2, vect-vec2) < 0.0f) return false; + if(t >= mindist) return false; + point.point = p; + point.normal = normal; + point.surfaceA = 0; + point.pieceA = 0; + point.surfaceB = tri.surface; + point.pieceB = 0; + mindist = t; + return true; +#endif +} + +bool +CCollision::ProcessSphereTriangle(const CColSphere &sphere, + const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, + CColPoint &point, float &mindistsq) +{ +#ifdef VU_COLLISION + // not used in favour of optimized loops + VuTriangle vutri; + verts[tri.a].Unpack(vutri.v0); + verts[tri.b].Unpack(vutri.v1); + verts[tri.c].Unpack(vutri.v2); + plane.Unpack(vutri.plane); + + SphereToTriangleCollisionCompressed(*(CVuVector*)&sphere, vutri); + + CVuVector pnt, normal; + float dist; + if(GetVUresult(pnt, normal, dist) && dist*dist < mindistsq){ + float depth = sphere.radius - dist; + if(depth > point.depth){ + point.point = pnt; + point.normal = normal; + point.depth = depth; + mindistsq = dist*dist; + return true; + } + } + return false; +#else + // If sphere and plane don't intersect, no collision + float planedist = plane.CalcPoint(sphere.center); + float distsq = planedist*planedist; + if(Abs(planedist) > sphere.radius || distsq > mindistsq) + return false; + + const CVector &va = verts[tri.a].Get(); + const CVector &vb = verts[tri.b].Get(); + const CVector &vc = verts[tri.c].Get(); + + // calculate two orthogonal basis vectors for the triangle + CVector normal; + plane.GetNormal(normal); + CVector vec2 = vb - va; + float len = vec2.Magnitude(); + vec2 = vec2 * (1.0f/len); + CVector vec1 = CrossProduct(vec2, normal); + + // We know A has local coordinate [0,0] and B has [0,len]. + // Now calculate coordinates on triangle for these two vectors: + CVector vac = vc - va; + CVector vas = sphere.center - va; + CVector2D b(0.0f, len); + CVector2D c(DotProduct(vec1, vac), DotProduct(vec2, vac)); + CVector2D s(DotProduct(vec1, vas), DotProduct(vec2, vas)); + + // The three triangle lines partition the space into 6 sectors, + // find out in which the center lies. + int insideAB = CrossProduct2D(s, b) >= 0.0f; + int insideAC = CrossProduct2D(c, s) >= 0.0f; + int insideBC = CrossProduct2D(s-b, c-b) >= 0.0f; + + int testcase = insideAB + insideAC + insideBC; + float dist = 0.0f; + CVector p; + switch(testcase){ + case 1: + // closest to a vertex + if(insideAB) p = vc; + else if(insideAC) p = vb; + else if(insideBC) p = va; + else assert(0); + dist = (sphere.center - p).Magnitude(); + break; + case 2: + // closest to an edge + // looks like original game as DistToLine manually inlined + if(!insideAB) dist = DistToLine(&va, &vb, &sphere.center, p); + else if(!insideAC) dist = DistToLine(&va, &vc, &sphere.center, p); + else if(!insideBC) dist = DistToLine(&vb, &vc, &sphere.center, p); + else assert(0); + break; + case 3: + // center is in triangle + dist = Abs(planedist); + p = sphere.center - normal*planedist; + break; + default: + assert(0); + } + + if(dist >= sphere.radius || dist*dist >= mindistsq) + return false; + + point.point = p; + point.normal = sphere.center - p; + point.normal.Normalise(); +#ifndef VU_COLLISION + point.surfaceA = sphere.surface; + point.pieceA = sphere.piece; + point.surfaceB = tri.surface; + point.pieceB = 0; +#endif + point.depth = sphere.radius - dist; + mindistsq = dist*dist; + return true; +#endif +} + +bool +CCollision::ProcessLineOfSight(const CColLine &line, + const CMatrix &matrix, CColModel &model, + CColPoint &point, float &mindist, bool ignoreSeeThrough) +{ +#ifdef VU_COLLISION + CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CVuVector newline[2]; + TransformPoints(newline, 2, matTransform, &line.p0, sizeof(CColLine)/2); + + if(mindist < 1.0f) + newline[1] = newline[0] + (newline[1] - newline[0])*mindist; + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(*(CColLine*)newline, model.boundingBox)) + return false; + + float coldist = 1.0f; + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.spheres[i].surface)) continue; + if(ProcessLineSphere(*(CColLine*)newline, model.spheres[i], point, coldist)) + point.Set(0, 0, model.spheres[i].surface, model.spheres[i].piece); + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + if(ProcessLineBox(*(CColLine*)newline, model.boxes[i], point, coldist)) + point.Set(0, 0, model.boxes[i].surface, model.boxes[i].piece); + } + + CalculateTrianglePlanes(&model); + VuTriangle vutri; + CColTriangle *lasttri = nil; + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lasttri = tri; + break; + } +#ifdef FIX_BUGS + // no need to check first again + i++; +#endif + CVuVector pnt, normal; + float dist; + for(; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + if(GetVUresult(pnt, normal, dist)) + if(dist < coldist){ + point.point = pnt; + point.normal = normal; + point.Set(0, 0, lasttri->surface, 0); + coldist = dist; + } + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lasttri = tri; + } + if(lasttri && GetVUresult(pnt, normal, dist)) + if(dist < coldist){ + point.point = pnt; + point.normal = normal; + point.Set(0, 0, lasttri->surface, 0); + coldist = dist; + } + + + if(coldist < 1.0f){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + mindist *= coldist; + return true; + } + return false; +#else + static CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CColLine newline(matTransform * line.p0, matTransform * line.p1); + + // If we don't intersect with the bounding box, no chance on the rest + if(!TestLineBox(newline, model.boundingBox)) + return false; + + float coldist = mindist; + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.spheres[i].surface)) continue; + ProcessLineSphere(newline, model.spheres[i], point, coldist); + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + ProcessLineBox(newline, model.boxes[i], point, coldist); + } + + CalculateTrianglePlanes(&model); + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + ProcessLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i], point, coldist); + } + + if(coldist < mindist){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + mindist = coldist; + return true; + } + return false; +#endif +} + +bool +CCollision::ProcessVerticalLine(const CColLine &line, + const CMatrix &matrix, CColModel &model, + CColPoint &point, float &mindist, bool ignoreSeeThrough, CStoredCollPoly *poly) +{ +#ifdef VU_COLLISION + static CStoredCollPoly TempStoredPoly; + CMatrix matTransform; + int i; + + // transform line to model space + Invert(matrix, matTransform); + CVuVector newline[2]; + TransformPoints(newline, 2, matTransform, &line.p0, sizeof(CColLine)/2); + + if(mindist < 1.0f) + newline[1] = newline[0] + (newline[1] - newline[0])*mindist; + + if(!TestLineBox(*(CColLine*)newline, model.boundingBox)) + return false; + + float coldist = 1.0f; + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.spheres[i].surface)) continue; + if(ProcessLineSphere(*(CColLine*)newline, model.spheres[i], point, coldist)) + point.Set(0, 0, model.spheres[i].surface, model.spheres[i].piece); + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + if(ProcessLineBox(*(CColLine*)newline, model.boxes[i], point, coldist)) + point.Set(0, 0, model.boxes[i].surface, model.boxes[i].piece); + } + + CalculateTrianglePlanes(&model); + TempStoredPoly.valid = false; + if(model.numTriangles){ + bool registeredCol; + CColTriangle *lasttri = nil; + VuTriangle vutri; + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lasttri = tri; + break; + } +#ifdef FIX_BUGS + // no need to check first again + i++; +#endif + CVuVector pnt, normal; + float dist; + for(; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + + CColTriangle *tri = &model.triangles[i]; + model.vertices[tri->a].Unpack(vutri.v0); + model.vertices[tri->b].Unpack(vutri.v1); + model.vertices[tri->c].Unpack(vutri.v2); + model.trianglePlanes[i].Unpack(vutri.plane); + + if(GetVUresult(pnt, normal, dist)){ + if(dist < coldist){ + point.point = pnt; + point.normal = normal; + point.Set(0, 0, lasttri->surface, 0); + coldist = dist; + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol){ + TempStoredPoly.verts[0] = model.vertices[lasttri->a].Get(); + TempStoredPoly.verts[1] = model.vertices[lasttri->b].Get(); + TempStoredPoly.verts[2] = model.vertices[lasttri->c].Get(); + TempStoredPoly.valid = true; + } + + LineToTriangleCollisionCompressed(newline[0], newline[1], vutri); + lasttri = tri; + } + if(lasttri && GetVUresult(pnt, normal, dist)){ + if(dist < coldist){ + point.point = pnt; + point.normal = normal; + point.Set(0, 0, lasttri->surface, 0); + coldist = dist; + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol){ + TempStoredPoly.verts[0] = model.vertices[lasttri->a].Get(); + TempStoredPoly.verts[1] = model.vertices[lasttri->b].Get(); + TempStoredPoly.verts[2] = model.vertices[lasttri->c].Get(); + TempStoredPoly.valid = true; + } + } + + if(coldist < 1.0f){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + if(TempStoredPoly.valid && poly){ + *poly = TempStoredPoly; + poly->verts[0] = matrix * CVector(poly->verts[0]); + poly->verts[1] = matrix * CVector(poly->verts[1]); + poly->verts[2] = matrix * CVector(poly->verts[2]); + } + mindist *= coldist; + return true; + } + return false; +#else + static CStoredCollPoly TempStoredPoly; + int i; + + // transform line to model space + // Why does the game seem to do this differently than above? + CColLine newline(MultiplyInverse(matrix, line.p0), MultiplyInverse(matrix, line.p1)); + newline.p1.x = newline.p0.x; + newline.p1.y = newline.p0.y; + + if(!TestVerticalLineBox(newline, model.boundingBox)) + return false; + + float coldist = mindist; + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.spheres[i].surface)) continue; + ProcessLineSphere(newline, model.spheres[i], point, coldist); + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.boxes[i].surface)) continue; + ProcessLineBox(newline, model.boxes[i], point, coldist); + } + + CalculateTrianglePlanes(&model); + TempStoredPoly.valid = false; + for(i = 0; i < model.numTriangles; i++){ + if(ignoreSeeThrough && IsSeeThrough(model.triangles[i].surface)) continue; + ProcessVerticalLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i], point, coldist, &TempStoredPoly); + } + + if(coldist < mindist){ + point.point = matrix * point.point; + point.normal = Multiply3x3(matrix, point.normal); + if(TempStoredPoly.valid && poly){ + *poly = TempStoredPoly; + poly->verts[0] = matrix * poly->verts[0]; + poly->verts[1] = matrix * poly->verts[1]; + poly->verts[2] = matrix * poly->verts[2]; + } + mindist = coldist; + return true; + } + return false; +#endif +} + +enum { + MAXNUMSPHERES = 128, + MAXNUMBOXES = 32, + MAXNUMLINES = 16, + MAXNUMTRIS = 600 +}; + +#ifdef VU_COLLISION +#ifdef GTA_PS2 +#define SPR(off) ((uint8*)(0x70000000 + (off))) +#else +static uint8 fakeSPR[16*1024]; +#define SPR(off) ((uint8*)(fakeSPR + (off))) +#endif +#endif + +// This checks model A's spheres and lines against model B's spheres, boxes and triangles. +// Returns the number of A's spheres that collide. +// Returned ColPoints are in world space. +// NB: only vehicles can have col models with lines, exactly 4, one for each wheel +int32 +CCollision::ProcessColModels(const CMatrix &matrixA, CColModel &modelA, + const CMatrix &matrixB, CColModel &modelB, + CColPoint *spherepoints, CColPoint *linepoints, float *linedists) +{ +#ifdef VU_COLLISION + CVuVector *aSpheresA = (CVuVector*)SPR(0x0000); + CVuVector *aSpheresB = (CVuVector*)SPR(0x0800); + CVuVector *aLinesA = (CVuVector*)SPR(0x1000); + int32 *aSphereIndicesA = (int32*)SPR(0x1200); + int32 *aSphereIndicesB = (int32*)SPR(0x1400); + int32 *aBoxIndicesB = (int32*)SPR(0x1600); + int32 *aTriangleIndicesB = (int32*)SPR(0x1680); + bool *aCollided = (bool*)SPR(0x1FE0); + CMatrix &matAB = *(CMatrix*)SPR(0x1FF0); + CMatrix &matBA = *(CMatrix*)SPR(0x2040); + int i, j, k; + + // From model A space to model B space + Invert(matrixB, matAB); + matAB *= matrixA; + + CVuVector bsphereAB; // bounding sphere of A in B space + TransformPoint(bsphereAB, matAB, modelA.boundingSphere.center); // inlined + bsphereAB.w = modelA.boundingSphere.radius; + if(!TestSphereBox(*(CColSphere*)&bsphereAB, modelB.boundingBox)) + return 0; + + // transform modelA's spheres and lines to B space + TransformPoints(aSpheresA, modelA.numSpheres, matAB, &modelA.spheres->center, sizeof(CColSphere)); + for(i = 0; i < modelA.numSpheres; i++) + aSpheresA[i].w = modelA.spheres[i].radius; + TransformPoints(aLinesA, modelA.numLines*2, matAB, &modelA.lines->p0, sizeof(CColLine)/2); + + // Test them against model B's bounding volumes + int numSpheresA = 0; + for(i = 0; i < modelA.numSpheres; i++) + if(TestSphereBox(*(CColSphere*)&aSpheresA[i], modelB.boundingBox)) + aSphereIndicesA[numSpheresA++] = i; + // No collision + if(numSpheresA == 0 && modelA.numLines == 0) + return 0; + + + // B to A space + Invert(matrixA, matBA); + matBA *= matrixB; + + // transform modelB's spheres to A space + TransformPoints(aSpheresB, modelB.numSpheres, matBA, &modelB.spheres->center, sizeof(CColSphere)); + for(i = 0; i < modelB.numSpheres; i++) + aSpheresB[i].w = modelB.spheres[i].radius; + + // Check model B against A's bounding volumes + int numSpheresB = 0; + int numBoxesB = 0; + int numTrianglesB = 0; + for(i = 0; i < modelB.numSpheres; i++) + if(TestSphereBox(*(CColSphere*)&aSpheresB[i], modelA.boundingBox)) + aSphereIndicesB[numSpheresB++] = i; + for(i = 0; i < modelB.numBoxes; i++) + if(TestSphereBox(*(CColSphere*)&bsphereAB, modelB.boxes[i])) + aBoxIndicesB[numBoxesB++] = i; + CalculateTrianglePlanes(&modelB); + if(modelB.numTriangles){ + VuTriangle vutri; + // process the first triangle + CColTriangle *tri = &modelB.triangles[0]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[0].Unpack(vutri.plane); + + SphereToTriangleCollisionCompressed(bsphereAB, vutri); + + for(i = 1; i < modelB.numTriangles; i++){ + // set up the next triangle while VU0 is running + tri = &modelB.triangles[i]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[i].Unpack(vutri.plane); + + // check previous result + if(GetVUresult()) + aTriangleIndicesB[numTrianglesB++] = i-1; + + // kick off this one + SphereToTriangleCollisionCompressed(bsphereAB, vutri); + } + + // check last result + if(GetVUresult()) + aTriangleIndicesB[numTrianglesB++] = i-1; + } + // No collision + if(numSpheresB == 0 && numBoxesB == 0 && numTrianglesB == 0) + return 0; + + // We now have the collision volumes in A and B that are worth processing. + + // Process A's spheres against B's collision volumes + int numCollisions = 0; + spherepoints[numCollisions].depth = -1.0f; + for(i = 0; i < numSpheresA; i++){ + float coldist = 1.0e24f; + bool hasCollided = false; + CColSphere *sphA = &modelA.spheres[aSphereIndicesA[i]]; + CVuVector *vusphA = &aSpheresA[aSphereIndicesA[i]]; + + for(j = 0; j < numSpheresB; j++) + // This actually looks like something was inlined here + if(ProcessSphereSphere(*(CColSphere*)vusphA, modelB.spheres[aSphereIndicesB[j]], + spherepoints[numCollisions], coldist)){ + spherepoints[numCollisions].Set( + sphA->surface, sphA->piece, + modelB.spheres[aSphereIndicesB[j]].surface, modelB.spheres[aSphereIndicesB[j]].piece); + hasCollided = true; + } + for(j = 0; j < numBoxesB; j++) + if(ProcessSphereBox(*(CColSphere*)vusphA, modelB.boxes[aBoxIndicesB[j]], + spherepoints[numCollisions], coldist)){ + spherepoints[numCollisions].Set( + sphA->surface, sphA->piece, + modelB.boxes[aBoxIndicesB[j]].surface, modelB.boxes[aBoxIndicesB[j]].piece); + hasCollided = true; + } + if(numTrianglesB){ + CVuVector point, normal; + float depth; + bool registeredCol; + CColTriangle *lasttri; + + VuTriangle vutri; + // process the first triangle + k = aTriangleIndicesB[0]; + CColTriangle *tri = &modelB.triangles[k]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[k].Unpack(vutri.plane); + + SphereToTriangleCollisionCompressed(*vusphA, vutri); + lasttri = tri; + + for(j = 1; j < numTrianglesB; j++){ + k = aTriangleIndicesB[j]; + // set up the next triangle while VU0 is running + tri = &modelB.triangles[k]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[k].Unpack(vutri.plane); + + // check previous result + // TODO: this looks inlined but spherepoints[numCollisions] does not... + if(GetVUresult(point, normal, depth)){ + depth = sphA->radius - depth; + if(depth > spherepoints[numCollisions].depth){ + spherepoints[numCollisions].point = point; + spherepoints[numCollisions].normal = normal; + spherepoints[numCollisions].Set(depth, + sphA->surface, sphA->piece, lasttri->surface, 0); + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol) + hasCollided = true; + + // kick off this one + SphereToTriangleCollisionCompressed(*vusphA, vutri); + lasttri = tri; + } + + // check last result + // TODO: this looks inlined but spherepoints[numCollisions] does not... + if(GetVUresult(point, normal, depth)){ + depth = sphA->radius - depth; + if(depth > spherepoints[numCollisions].depth){ + spherepoints[numCollisions].point = point; + spherepoints[numCollisions].normal = normal; + spherepoints[numCollisions].Set(depth, + sphA->surface, sphA->piece, lasttri->surface, 0); + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol) + hasCollided = true; + } + + if(hasCollided){ + numCollisions++; + if(numCollisions == MAX_COLLISION_POINTS) + break; + spherepoints[numCollisions].depth = -1.0f; + } + } + for(i = 0; i < numCollisions; i++){ + // TODO: both VU0 macros + spherepoints[i].point = matrixB * spherepoints[i].point; + spherepoints[i].normal = Multiply3x3(matrixB, spherepoints[i].normal); + } + + // And the same thing for the lines in A + for(i = 0; i < modelA.numLines; i++){ + aCollided[i] = false; + CVuVector *lineA = &aLinesA[i*2]; + + for(j = 0; j < numSpheresB; j++) + if(ProcessLineSphere(*(CColLine*)lineA, modelB.spheres[aSphereIndicesB[j]], + linepoints[i], linedists[i])){ + linepoints[i].Set(0, 0, +#ifdef FIX_BUGS + modelB.spheres[aSphereIndicesB[j]].surface, modelB.spheres[aSphereIndicesB[j]].piece); +#else + modelB.spheres[j].surface, modelB.spheres[j].piece); +#endif + aCollided[i] = true; + } + for(j = 0; j < numBoxesB; j++) + if(ProcessLineBox(*(CColLine*)lineA, modelB.boxes[aBoxIndicesB[j]], + linepoints[i], linedists[i])){ + linepoints[i].Set(0, 0, + modelB.boxes[aBoxIndicesB[j]].surface, modelB.boxes[aBoxIndicesB[j]].piece); + aCollided[i] = true; + } + if(numTrianglesB){ + CVuVector point, normal; + float dist; + bool registeredCol; + CColTriangle *lasttri; + + VuTriangle vutri; + // process the first triangle + k = aTriangleIndicesB[0]; + CColTriangle *tri = &modelB.triangles[k]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[k].Unpack(vutri.plane); + + LineToTriangleCollisionCompressed(lineA[0], lineA[1], vutri); + lasttri = tri; + + for(j = 1; j < numTrianglesB; j++){ + k = aTriangleIndicesB[j]; + // set up the next triangle while VU0 is running + CColTriangle *tri = &modelB.triangles[k]; + modelB.vertices[tri->a].Unpack(vutri.v0); + modelB.vertices[tri->b].Unpack(vutri.v1); + modelB.vertices[tri->c].Unpack(vutri.v2); + modelB.trianglePlanes[k].Unpack(vutri.plane); + + // check previous result + // TODO: this again somewhat looks inlined + if(GetVUresult(point, normal, dist)){ + if(dist < linedists[i]){ + linepoints[i].point = point; + linepoints[i].normal = normal; + linedists[i] = dist; + linepoints[i].Set(0, 0, lasttri->surface, 0); + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol) + aCollided[i] = true; + + // kick of this one + LineToTriangleCollisionCompressed(lineA[0], lineA[1], vutri); + lasttri = tri; + } + + // check last result + if(GetVUresult(point, normal, dist)){ + if(dist < linedists[i]){ + linepoints[i].point = point; + linepoints[i].normal = normal; + linedists[i] = dist; + linepoints[i].Set(0, 0, lasttri->surface, 0); + registeredCol = true; + }else + registeredCol = false; + }else + registeredCol = false; + + if(registeredCol) + aCollided[i] = true; + } + + if(aCollided[i]){ + // TODO: both VU0 macros + linepoints[i].point = matrixB * linepoints[i].point; + linepoints[i].normal = Multiply3x3(matrixB, linepoints[i].normal); + } + } + + return numCollisions; // sphere collisions +#else + static int aSphereIndicesA[MAXNUMSPHERES]; + static int aLineIndicesA[MAXNUMLINES]; + static int aSphereIndicesB[MAXNUMSPHERES]; + static int aBoxIndicesB[MAXNUMBOXES]; + static int aTriangleIndicesB[MAXNUMTRIS]; + static bool aCollided[MAXNUMLINES]; + static CColSphere aSpheresA[MAXNUMSPHERES]; + static CColLine aLinesA[MAXNUMLINES]; + static CMatrix matAB, matBA; + CColSphere s; + int i, j; + + assert(modelA.numSpheres <= MAXNUMSPHERES); + assert(modelA.numLines <= MAXNUMLINES); + + // From model A space to model B space + matAB = Invert(matrixB, matAB); + matAB *= matrixA; + + CColSphere bsphereAB; // bounding sphere of A in B space + bsphereAB.radius = modelA.boundingSphere.radius; + bsphereAB.center = matAB * modelA.boundingSphere.center; + if(!TestSphereBox(bsphereAB, modelB.boundingBox)) + return 0; + // B to A space + matBA = Invert(matrixA, matBA); + matBA *= matrixB; + + // transform modelA's spheres and lines to B space + for(i = 0; i < modelA.numSpheres; i++){ + CColSphere &s = modelA.spheres[i]; + aSpheresA[i].Set(s.radius, matAB * s.center, s.surface, s.piece); + } + for(i = 0; i < modelA.numLines; i++) + aLinesA[i].Set(matAB * modelA.lines[i].p0, matAB * modelA.lines[i].p1); + + // Test them against model B's bounding volumes + int numSpheresA = 0; + int numLinesA = 0; + for(i = 0; i < modelA.numSpheres; i++) + if(TestSphereBox(aSpheresA[i], modelB.boundingBox)) + aSphereIndicesA[numSpheresA++] = i; + // no actual check??? + for(i = 0; i < modelA.numLines; i++) + aLineIndicesA[numLinesA++] = i; + // No collision + if(numSpheresA == 0 && numLinesA == 0) + return 0; + + // Check model B against A's bounding volumes + int numSpheresB = 0; + int numBoxesB = 0; + int numTrianglesB = 0; + for(i = 0; i < modelB.numSpheres; i++){ + s.radius = modelB.spheres[i].radius; + s.center = matBA * modelB.spheres[i].center; + if(TestSphereBox(s, modelA.boundingBox)) + aSphereIndicesB[numSpheresB++] = i; + } + for(i = 0; i < modelB.numBoxes; i++) + if(TestSphereBox(bsphereAB, modelB.boxes[i])) + aBoxIndicesB[numBoxesB++] = i; + CalculateTrianglePlanes(&modelB); + for(i = 0; i < modelB.numTriangles; i++) + if(TestSphereTriangle(bsphereAB, modelB.vertices, modelB.triangles[i], modelB.trianglePlanes[i])) + aTriangleIndicesB[numTrianglesB++] = i; + assert(numSpheresB <= MAXNUMSPHERES); + assert(numBoxesB <= MAXNUMBOXES); + assert(numTrianglesB <= MAXNUMTRIS); + // No collision + if(numSpheresB == 0 && numBoxesB == 0 && numTrianglesB == 0) + return 0; + + // We now have the collision volumes in A and B that are worth processing. + + // Process A's spheres against B's collision volumes + int numCollisions = 0; + for(i = 0; i < numSpheresA; i++){ + float coldist = 1.0e24f; + bool hasCollided = false; + + for(j = 0; j < numSpheresB; j++) + hasCollided |= ProcessSphereSphere( + aSpheresA[aSphereIndicesA[i]], + modelB.spheres[aSphereIndicesB[j]], + spherepoints[numCollisions], coldist); + for(j = 0; j < numBoxesB; j++) + hasCollided |= ProcessSphereBox( + aSpheresA[aSphereIndicesA[i]], + modelB.boxes[aBoxIndicesB[j]], + spherepoints[numCollisions], coldist); + for(j = 0; j < numTrianglesB; j++) + hasCollided |= ProcessSphereTriangle( + aSpheresA[aSphereIndicesA[i]], + modelB.vertices, + modelB.triangles[aTriangleIndicesB[j]], + modelB.trianglePlanes[aTriangleIndicesB[j]], + spherepoints[numCollisions], coldist); + + if(hasCollided) + numCollisions++; + } + for(i = 0; i < numCollisions; i++){ + spherepoints[i].point = matrixB * spherepoints[i].point; + spherepoints[i].normal = Multiply3x3(matrixB, spherepoints[i].normal); + } + + // And the same thing for the lines in A + for(i = 0; i < numLinesA; i++){ + aCollided[i] = false; + + for(j = 0; j < numSpheresB; j++) + aCollided[i] |= ProcessLineSphere( + aLinesA[aLineIndicesA[i]], + modelB.spheres[aSphereIndicesB[j]], + linepoints[aLineIndicesA[i]], + linedists[aLineIndicesA[i]]); + for(j = 0; j < numBoxesB; j++) + aCollided[i] |= ProcessLineBox( + aLinesA[aLineIndicesA[i]], + modelB.boxes[aBoxIndicesB[j]], + linepoints[aLineIndicesA[i]], + linedists[aLineIndicesA[i]]); + for(j = 0; j < numTrianglesB; j++) + aCollided[i] |= ProcessLineTriangle( + aLinesA[aLineIndicesA[i]], + modelB.vertices, + modelB.triangles[aTriangleIndicesB[j]], + modelB.trianglePlanes[aTriangleIndicesB[j]], + linepoints[aLineIndicesA[i]], + linedists[aLineIndicesA[i]]); + } + for(i = 0; i < numLinesA; i++) + if(aCollided[i]){ + j = aLineIndicesA[i]; + linepoints[j].point = matrixB * linepoints[j].point; + linepoints[j].normal = Multiply3x3(matrixB, linepoints[j].normal); + } + + return numCollisions; // sphere collisions +#endif +} + + +// +// Misc +// + +float +CCollision::DistToLine(const CVector *l0, const CVector *l1, const CVector *point) +{ + float lensq = (*l1 - *l0).MagnitudeSqr(); + float dot = DotProduct(*point - *l0, *l1 - *l0); + // Between 0 and len we're above the line. + // if not, calculate distance to endpoint + if(dot <= 0.0f) return (*point - *l0).Magnitude(); + if(dot >= lensq) return (*point - *l1).Magnitude(); + // distance to line + float distSqr = (*point - *l0).MagnitudeSqr() - dot * dot / lensq; + if(distSqr <= 0.f) return 0.f; + return Sqrt(distSqr); +} + +// same as above but also return the point on the line +float +CCollision::DistToLine(const CVector *l0, const CVector *l1, const CVector *point, CVector &closest) +{ + float lensq = (*l1 - *l0).MagnitudeSqr(); + float dot = DotProduct(*point - *l0, *l1 - *l0); + // find out which point we're closest to + if(dot <= 0.0f) + closest = *l0; + else if(dot >= lensq) + closest = *l1; + else + closest = *l0 + (*l1 - *l0)*(dot/lensq); + // this is the distance + return (*point - closest).Magnitude(); +} + +void +CCollision::CalculateTrianglePlanes(CColModel *model) +{ + assert(model); + if(model->numTriangles == 0) + return; + + CLink *lptr; + if(model->trianglePlanes){ + // re-insert at front so it's not removed again soon + lptr = model->GetLinkPtr(); + lptr->Remove(); + ms_colModelCache.head.Insert(lptr); + }else{ + lptr = ms_colModelCache.Insert(model); + if(lptr == nil){ + // make room if we have to, remove last in list + lptr = ms_colModelCache.tail.prev; + assert(lptr); + assert(lptr->item); + lptr->item->RemoveTrianglePlanes(); + ms_colModelCache.Remove(lptr); + // now this cannot fail + lptr = ms_colModelCache.Insert(model); + assert(lptr); + } + model->CalculateTrianglePlanes(); + model->SetLinkPtr(lptr); + } +} + +void +CCollision::DrawColModel(const CMatrix &mat, const CColModel &colModel) +{ + int i; + CVector min, max; + CVector verts[8]; + CVector c; + float r; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + min = colModel.boundingBox.min; + max = colModel.boundingBox.max; + + verts[0] = mat * CVector(min.x, min.y, min.z); + verts[1] = mat * CVector(min.x, min.y, max.z); + verts[2] = mat * CVector(min.x, max.y, min.z); + verts[3] = mat * CVector(min.x, max.y, max.z); + verts[4] = mat * CVector(max.x, min.y, min.z); + verts[5] = mat * CVector(max.x, min.y, max.z); + verts[6] = mat * CVector(max.x, max.y, min.z); + verts[7] = mat * CVector(max.x, max.y, max.z); + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[1].x, verts[1].y, verts[1].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[3].x, verts[3].y, verts[3].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[3].x, verts[3].y, verts[3].z, + verts[2].x, verts[2].y, verts[2].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[2].x, verts[2].y, verts[2].z, + verts[0].x, verts[0].y, verts[0].z, + 0xFF0000FF, 0xFF0000FF); + + CLines::RenderLineWithClipping( + verts[4].x, verts[4].y, verts[4].z, + verts[5].x, verts[5].y, verts[5].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[5].x, verts[5].y, verts[5].z, + verts[7].x, verts[7].y, verts[7].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[7].x, verts[7].y, verts[7].z, + verts[6].x, verts[6].y, verts[6].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[6].x, verts[6].y, verts[6].z, + verts[4].x, verts[4].y, verts[4].z, + 0xFF0000FF, 0xFF0000FF); + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[4].x, verts[4].y, verts[4].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[5].x, verts[5].y, verts[5].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[2].x, verts[2].y, verts[2].z, + verts[6].x, verts[6].y, verts[6].z, + 0xFF0000FF, 0xFF0000FF); + CLines::RenderLineWithClipping( + verts[3].x, verts[3].y, verts[3].z, + verts[7].x, verts[7].y, verts[7].z, + 0xFF0000FF, 0xFF0000FF); + + for(i = 0; i < colModel.numSpheres; i++){ + c = mat * colModel.spheres[i].center; + r = colModel.spheres[i].radius; + + CLines::RenderLineWithClipping( + c.x, c.y, c.z-r, + c.x-r, c.y-r, c.z, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x, c.y, c.z-r, + c.x-r, c.y+r, c.z, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x, c.y, c.z-r, + c.x+r, c.y-r, c.z, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x, c.y, c.z-r, + c.x+r, c.y+r, c.z, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x-r, c.y-r, c.z, + c.x, c.y, c.z+r, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x-r, c.y+r, c.z, + c.x, c.y, c.z+r, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x+r, c.y-r, c.z, + c.x, c.y, c.z+r, + 0xFF00FFFF, 0xFF00FFFF); + CLines::RenderLineWithClipping( + c.x+r, c.y+r, c.z, + c.x, c.y, c.z+r, + 0xFF00FFFF, 0xFF00FFFF); + } + + for(i = 0; i < colModel.numLines; i++){ + verts[0] = colModel.lines[i].p0; + verts[1] = colModel.lines[i].p1; + + verts[0] = mat * verts[0]; + verts[1] = mat * verts[1]; + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[1].x, verts[1].y, verts[1].z, + 0x00FFFFFF, 0x00FFFFFF); + } + + for(i = 0; i < colModel.numBoxes; i++){ + min = colModel.boxes[i].min; + max = colModel.boxes[i].max; + + verts[0] = mat * CVector(min.x, min.y, min.z); + verts[1] = mat * CVector(min.x, min.y, max.z); + verts[2] = mat * CVector(min.x, max.y, min.z); + verts[3] = mat * CVector(min.x, max.y, max.z); + verts[4] = mat * CVector(max.x, min.y, min.z); + verts[5] = mat * CVector(max.x, min.y, max.z); + verts[6] = mat * CVector(max.x, max.y, min.z); + verts[7] = mat * CVector(max.x, max.y, max.z); + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[1].x, verts[1].y, verts[1].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[3].x, verts[3].y, verts[3].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[3].x, verts[3].y, verts[3].z, + verts[2].x, verts[2].y, verts[2].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[2].x, verts[2].y, verts[2].z, + verts[0].x, verts[0].y, verts[0].z, + 0xFFFFFFFF, 0xFFFFFFFF); + + CLines::RenderLineWithClipping( + verts[4].x, verts[4].y, verts[4].z, + verts[5].x, verts[5].y, verts[5].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[5].x, verts[5].y, verts[5].z, + verts[7].x, verts[7].y, verts[7].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[7].x, verts[7].y, verts[7].z, + verts[6].x, verts[6].y, verts[6].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[6].x, verts[6].y, verts[6].z, + verts[4].x, verts[4].y, verts[4].z, + 0xFFFFFFFF, 0xFFFFFFFF); + + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[4].x, verts[4].y, verts[4].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[5].x, verts[5].y, verts[5].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[2].x, verts[2].y, verts[2].z, + verts[6].x, verts[6].y, verts[6].z, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping( + verts[3].x, verts[3].y, verts[3].z, + verts[7].x, verts[7].y, verts[7].z, + 0xFFFFFFFF, 0xFFFFFFFF); + } + + for(i = 0; i < colModel.numTriangles; i++){ + colModel.GetTrianglePoint(verts[0], colModel.triangles[i].a); + colModel.GetTrianglePoint(verts[1], colModel.triangles[i].b); + colModel.GetTrianglePoint(verts[2], colModel.triangles[i].c); + verts[0] = mat * verts[0]; + verts[1] = mat * verts[1]; + verts[2] = mat * verts[2]; + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[1].x, verts[1].y, verts[1].z, + 0x00FF00FF, 0x00FF00FF); + CLines::RenderLineWithClipping( + verts[0].x, verts[0].y, verts[0].z, + verts[2].x, verts[2].y, verts[2].z, + 0x00FF00FF, 0x00FF00FF); + CLines::RenderLineWithClipping( + verts[1].x, verts[1].y, verts[1].z, + verts[2].x, verts[2].y, verts[2].z, + 0x00FF00FF, 0x00FF00FF); + } + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); +} + +void +CCollision::DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id) +{ + int i; + int s; + float f; + CVector verts[8]; + CVector min, max; + int r, g, b; + RwImVertexIndex *iptr; + RwIm3DVertex *vptr; + + RenderBuffer::ClearRenderBuffer(); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + for(i = 0; i < colModel.numTriangles; i++){ + colModel.GetTrianglePoint(verts[0], colModel.triangles[i].a); + colModel.GetTrianglePoint(verts[1], colModel.triangles[i].b); + colModel.GetTrianglePoint(verts[2], colModel.triangles[i].c); + verts[0] = mat * verts[0]; + verts[1] = mat * verts[1]; + verts[2] = mat * verts[2]; + + // game doesn't do this + r = 255; + g = 128; + b = 0; + + s = colModel.triangles[i].surface; + f = (s & 0xF)/32.0f + 0.5f; + switch(CSurfaceTable::GetAdhesionGroup(s)){ + case ADHESIVE_RUBBER: + r = f * 255.0f; + g = 0; + b = 0; + break; + case ADHESIVE_HARD: + r = f*255.0f; + g = f*255.0f; + b = f*128.0f; + break; + case ADHESIVE_ROAD: + r = f*128.0f; + g = f*128.0f; + b = f*128.0f; + break; + case ADHESIVE_LOOSE: + r = 0; + g = f * 255.0f; + b = 0; + break; + case ADHESIVE_WET: + r = 0; + g = 0; + b = f * 255.0f; + break; + default: + // this doesn't make much sense + r *= f; + g *= f; + b *= f; + } + + if(s == SURFACE_TRANSPARENT_CLOTH || s == SURFACE_METAL_CHAIN_FENCE || + s == SURFACE_TRANSPARENT_STONE || s == SURFACE_SCAFFOLD_POLE) + if(CTimer::GetFrameCounter() & 1){ + r = 0; + g = 0; + b = 0; + } + + if(s > SURFACE_METAL_GATE){ + r = CGeneral::GetRandomNumber(); + g = CGeneral::GetRandomNumber(); + b = CGeneral::GetRandomNumber(); + printf("Illegal surfacetype:%d on MI:%d\n", s, id); + } + + RenderBuffer::StartStoring(6, 3, &iptr, &vptr); + RwIm3DVertexSetRGBA(&vptr[0], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[1], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[2], r, g, b, 255); + RwIm3DVertexSetU(&vptr[0], 0.0f); + RwIm3DVertexSetV(&vptr[0], 0.0f); + RwIm3DVertexSetU(&vptr[1], 0.0f); + RwIm3DVertexSetV(&vptr[1], 1.0f); + RwIm3DVertexSetU(&vptr[2], 1.0f); + RwIm3DVertexSetV(&vptr[2], 1.0f); + RwIm3DVertexSetPos(&vptr[0], verts[0].x, verts[0].y, verts[0].z); + RwIm3DVertexSetPos(&vptr[1], verts[1].x, verts[1].y, verts[1].z); + RwIm3DVertexSetPos(&vptr[2], verts[2].x, verts[2].y, verts[2].z); + iptr[0] = 0; iptr[1] = 1; iptr[2] = 2; + iptr[3] = 0; iptr[4] = 2; iptr[5] = 1; + RenderBuffer::StopStoring(); + } + + for(i = 0; i < colModel.numBoxes; i++){ + min = colModel.boxes[i].min; + max = colModel.boxes[i].max; + + verts[0] = mat * CVector(min.x, min.y, min.z); + verts[1] = mat * CVector(min.x, min.y, max.z); + verts[2] = mat * CVector(min.x, max.y, min.z); + verts[3] = mat * CVector(min.x, max.y, max.z); + verts[4] = mat * CVector(max.x, min.y, min.z); + verts[5] = mat * CVector(max.x, min.y, max.z); + verts[6] = mat * CVector(max.x, max.y, min.z); + verts[7] = mat * CVector(max.x, max.y, max.z); + + s = colModel.boxes[i].surface; + f = (s & 0xF)/32.0f + 0.5f; + switch(CSurfaceTable::GetAdhesionGroup(s)){ + case ADHESIVE_RUBBER: + r = f * 255.0f; + g = 0; + b = 0; + break; + case ADHESIVE_HARD: + r = f*255.0f; + g = f*255.0f; + b = f*128.0f; + break; + case ADHESIVE_ROAD: + r = f*128.0f; + g = f*128.0f; + b = f*128.0f; + break; + case ADHESIVE_LOOSE: + r = 0; + g = f * 255.0f; + b = 0; + break; + case ADHESIVE_WET: + r = 0; + g = 0; + b = f * 255.0f; + break; + default: + // this doesn't make much sense + r *= f; + g *= f; + b *= f; + } + + if(s == SURFACE_TRANSPARENT_CLOTH || s == SURFACE_METAL_CHAIN_FENCE || + s == SURFACE_TRANSPARENT_STONE || s == SURFACE_SCAFFOLD_POLE) + if(CTimer::GetFrameCounter() & 1){ + r = 0; + g = 0; + b = 0; + } + + RenderBuffer::StartStoring(36, 8, &iptr, &vptr); + RwIm3DVertexSetRGBA(&vptr[0], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[1], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[2], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[3], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[4], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[5], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[6], r, g, b, 255); + RwIm3DVertexSetRGBA(&vptr[7], r, g, b, 255); + RwIm3DVertexSetU(&vptr[0], 0.0f); + RwIm3DVertexSetV(&vptr[0], 0.0f); + RwIm3DVertexSetU(&vptr[1], 0.0f); + RwIm3DVertexSetV(&vptr[1], 1.0f); + RwIm3DVertexSetU(&vptr[2], 1.0f); + RwIm3DVertexSetV(&vptr[2], 1.0f); + RwIm3DVertexSetU(&vptr[3], 0.0f); + RwIm3DVertexSetV(&vptr[3], 0.0f); + RwIm3DVertexSetU(&vptr[4], 0.0f); + RwIm3DVertexSetV(&vptr[4], 1.0f); + RwIm3DVertexSetU(&vptr[5], 1.0f); + RwIm3DVertexSetV(&vptr[5], 1.0f); + RwIm3DVertexSetU(&vptr[6], 0.0f); + RwIm3DVertexSetV(&vptr[6], 1.0f); + RwIm3DVertexSetU(&vptr[7], 1.0f); + RwIm3DVertexSetV(&vptr[7], 1.0f); + RwIm3DVertexSetPos(&vptr[0], verts[0].x, verts[0].y, verts[0].z); + RwIm3DVertexSetPos(&vptr[1], verts[1].x, verts[1].y, verts[1].z); + RwIm3DVertexSetPos(&vptr[2], verts[2].x, verts[2].y, verts[2].z); + RwIm3DVertexSetPos(&vptr[3], verts[3].x, verts[3].y, verts[3].z); + RwIm3DVertexSetPos(&vptr[4], verts[4].x, verts[4].y, verts[4].z); + RwIm3DVertexSetPos(&vptr[5], verts[5].x, verts[5].y, verts[5].z); + RwIm3DVertexSetPos(&vptr[6], verts[6].x, verts[6].y, verts[6].z); + RwIm3DVertexSetPos(&vptr[7], verts[7].x, verts[7].y, verts[7].z); + iptr[0] = 0; iptr[1] = 1; iptr[2] = 2; + iptr[3] = 1; iptr[4] = 3; iptr[5] = 2; + iptr[6] = 1; iptr[7] = 5; iptr[8] = 7; + iptr[9] = 1; iptr[10] = 7; iptr[11] = 3; + iptr[12] = 2; iptr[13] = 3; iptr[14] = 7; + iptr[15] = 2; iptr[16] = 7; iptr[17] = 6; + iptr[18] = 0; iptr[19] = 5; iptr[20] = 1; + iptr[21] = 0; iptr[22] = 4; iptr[23] = 5; + iptr[24] = 0; iptr[25] = 2; iptr[26] = 4; + iptr[27] = 2; iptr[28] = 6; iptr[29] = 4; + iptr[30] = 4; iptr[31] = 6; iptr[32] = 7; + iptr[33] = 4; iptr[34] = 7; iptr[35] = 5; + RenderBuffer::StopStoring(); + } + + RenderBuffer::RenderStuffInBuffer(); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); +} diff --git a/src/collision/Collision.h b/src/collision/Collision.h new file mode 100644 index 0000000..f4270bc --- /dev/null +++ b/src/collision/Collision.h @@ -0,0 +1,70 @@ +#pragma once + +#include "ColModel.h" +#include "Game.h" // for eLevelName +#ifdef VU_COLLISION +#include "VuVector.h" +#endif + +struct CStoredCollPoly +{ +#ifdef VU_COLLISION + CVuVector verts[3]; +#else + CVector verts[3]; +#endif + bool valid; +}; + +// If you spawn many tanks at once, you will see that collisions of two entity exceeds 32. +#if defined(FIX_BUGS) && !defined(SQUEEZE_PERFORMANCE) +#define MAX_COLLISION_POINTS 64 +#else +#define MAX_COLLISION_POINTS 32 +#endif + +class CCollision +{ +public: + static eLevelName ms_collisionInMemory; + static CLinkList ms_colModelCache; +#ifdef NO_ISLAND_LOADING + static bool bAlreadyLoaded; +#endif + + static void Init(void); + static void Shutdown(void); + static void Update(void); + static void LoadCollisionWhenINeedIt(bool changeLevel); + static void SortOutCollisionAfterLoad(void); + static void LoadCollisionScreen(eLevelName level); + static void DrawColModel(const CMatrix &mat, const CColModel &colModel); + static void DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id); + + static void CalculateTrianglePlanes(CColModel *model); + + // all these return true if there's a collision + static bool TestSphereSphere(const CColSphere &s1, const CColSphere &s2); + static bool TestSphereBox(const CColSphere &sph, const CColBox &box); + static bool TestLineBox(const CColLine &line, const CColBox &box); + static bool TestVerticalLineBox(const CColLine &line, const CColBox &box); + static bool TestLineTriangle(const CColLine &line, const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane); + static bool TestLineSphere(const CColLine &line, const CColSphere &sph); + static bool TestSphereTriangle(const CColSphere &sphere, const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane); + static bool TestLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, bool ignoreSeeThrough); + + static bool ProcessSphereSphere(const CColSphere &s1, const CColSphere &s2, CColPoint &point, float &mindistsq); + static bool ProcessSphereBox(const CColSphere &sph, const CColBox &box, CColPoint &point, float &mindistsq); + static bool ProcessLineBox(const CColLine &line, const CColBox &box, CColPoint &point, float &mindist); + static bool ProcessVerticalLineTriangle(const CColLine &line, const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindist, CStoredCollPoly *poly); + static bool ProcessLineTriangle(const CColLine &line , const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindist); + static bool ProcessLineSphere(const CColLine &line, const CColSphere &sphere, CColPoint &point, float &mindist); + static bool ProcessSphereTriangle(const CColSphere &sph, const CompressedVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindistsq); + static bool ProcessLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSeeThrough); + static bool ProcessVerticalLine(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSeeThrough, CStoredCollPoly *poly); + static int32 ProcessColModels(const CMatrix &matrixA, CColModel &modelA, const CMatrix &matrixB, CColModel &modelB, CColPoint *spherepoints, CColPoint *linepoints, float *linedists); + static bool IsStoredPolyStillValidVerticalLine(const CVector &pos, float z, CColPoint &point, CStoredCollPoly *poly); + + static float DistToLine(const CVector *l0, const CVector *l1, const CVector *point); + static float DistToLine(const CVector *l0, const CVector *l1, const CVector *point, CVector &closest); +}; diff --git a/src/collision/CompressedVector.h b/src/collision/CompressedVector.h new file mode 100644 index 0000000..d54e49b --- /dev/null +++ b/src/collision/CompressedVector.h @@ -0,0 +1,36 @@ +#pragma once + +struct CompressedVector +{ +#ifdef COMPRESSED_COL_VECTORS + int16 x, y, z; + CVector Get(void) const { return CVector(x, y, z)/128.0f; }; + void Set(float x, float y, float z) { this->x = x*128.0f; this->y = y*128.0f; this->z = z*128.0f; }; +#ifdef GTA_PS2 + void Unpack(uint128 &qword) const { + __asm__ volatile ( + "lh $8, 0(%1)\n" + "lh $9, 2(%1)\n" + "lh $10, 4(%1)\n" + "pextlw $10, $8\n" + "pextlw $2, $9, $10\n" + "sq $2, %0\n" + : "=m" (qword) + : "r" (this) + : "$8", "$9", "$10", "$2" + ); + } +#else + void Unpack(int32 *qword) const { + qword[0] = x; + qword[1] = y; + qword[2] = z; + qword[3] = 0; // junk + } +#endif +#else + float x, y, z; + CVector Get(void) const { return CVector(x, y, z); }; + void Set(float x, float y, float z) { this->x = x; this->y = y; this->z = z; }; +#endif +}; \ No newline at end of file diff --git a/src/collision/TempColModels.cpp b/src/collision/TempColModels.cpp new file mode 100644 index 0000000..494c148 --- /dev/null +++ b/src/collision/TempColModels.cpp @@ -0,0 +1,297 @@ +#include "common.h" + +#include "TempColModels.h" +#include "Game.h" + +CColModel CTempColModels::ms_colModelPed1; +CColModel CTempColModels::ms_colModelPed2; +CColModel CTempColModels::ms_colModelBBox; +CColModel CTempColModels::ms_colModelBumper1; +CColModel CTempColModels::ms_colModelWheel1; +CColModel CTempColModels::ms_colModelPanel1; +CColModel CTempColModels::ms_colModelBodyPart2; +CColModel CTempColModels::ms_colModelBodyPart1; +CColModel CTempColModels::ms_colModelCutObj[5]; +CColModel CTempColModels::ms_colModelPedGroundHit; +CColModel CTempColModels::ms_colModelBoot1; +CColModel CTempColModels::ms_colModelDoor1; +CColModel CTempColModels::ms_colModelBonnet1; + + +CColSphere s_aPedSpheres[3]; +CColSphere s_aPed2Spheres[3]; +CColSphere s_aPedGSpheres[4]; +#ifdef FIX_BUGS +CColSphere s_aDoorSpheres[3]; +#else +CColSphere s_aDoorSpheres[4]; +#endif +CColSphere s_aBumperSpheres[4]; +CColSphere s_aPanelSpheres[4]; +CColSphere s_aBonnetSpheres[4]; +CColSphere s_aBootSpheres[4]; +CColSphere s_aWheelSpheres[2]; +CColSphere s_aBodyPartSpheres1[2]; +CColSphere s_aBodyPartSpheres2[2]; + +void +CTempColModels::Initialise(void) +{ +#define SET_COLMODEL_SPHERES(colmodel, sphrs)\ + colmodel.numSpheres = ARRAY_SIZE(sphrs);\ + colmodel.spheres = sphrs;\ + colmodel.level = LEVEL_GENERIC;\ + colmodel.ownsCollisionVolumes = false; + + int i; + + ms_colModelBBox.boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelBBox.boundingBox.Set(CVector(-2.0f, -2.0f, -2.0f), CVector(2.0f, 2.0f, 2.0f)); + ms_colModelBBox.level = LEVEL_GENERIC; + + for (i = 0; i < ARRAY_SIZE(ms_colModelCutObj); i++) { + ms_colModelCutObj[i].boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelCutObj[i].boundingBox.Set(CVector(-2.0f, -2.0f, -2.0f), CVector(2.0f, 2.0f, 2.0f)); + ms_colModelCutObj[i].level = LEVEL_GENERIC; + } + + // Ped Spheres + + for (i = 0; i < ARRAY_SIZE(s_aPedSpheres); i++) + s_aPedSpheres[i].radius = 0.35f; + + s_aPedSpheres[0].center = CVector(0.0f, 0.0f, -0.25f); + s_aPedSpheres[1].center = CVector(0.0f, 0.0f, 0.15f); + s_aPedSpheres[2].center = CVector(0.0f, 0.0f, 0.55f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aPedSpheres); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aPedGSpheres); i++) { +#endif + s_aPedSpheres[i].surface = SURFACE_PED; + s_aPedSpheres[i].piece = 0; + } + + ms_colModelPed1.boundingSphere.Set(1.25f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelPed1.boundingBox.Set(CVector(-0.35f, -0.35f, -1.0f), CVector(0.35f, 0.35f, 0.9f)); + SET_COLMODEL_SPHERES(ms_colModelPed1, s_aPedSpheres); + + // Ped 2 Spheres + + s_aPed2Spheres[0].radius = 0.3f; + s_aPed2Spheres[1].radius = 0.4f; + s_aPed2Spheres[2].radius = 0.3f; + + s_aPed2Spheres[0].center = CVector(0.0f, 0.35f, -0.9f); + s_aPed2Spheres[1].center = CVector(0.0f, 0.0f, -0.9f); + s_aPed2Spheres[2].center = CVector(0.0f, -0.35f, -0.9f); + + for (i = 0; i < ARRAY_SIZE(s_aPed2Spheres); i++) { + s_aPed2Spheres[i].surface = SURFACE_PED; + s_aPed2Spheres[i].piece = 0; + } + + ms_colModelPed2.boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelPed2.boundingBox.Set(CVector(-0.7f, -0.7f, -1.2f), CVector(0.7f, 0.7f, 0.0f)); + + SET_COLMODEL_SPHERES(ms_colModelPed2, s_aPed2Spheres); + + // Ped ground collision + + s_aPedGSpheres[0].radius = 0.35f; + s_aPedGSpheres[1].radius = 0.35f; + s_aPedGSpheres[2].radius = 0.35f; + s_aPedGSpheres[3].radius = 0.3f; + + s_aPedGSpheres[0].center = CVector(0.0f, -0.4f, -0.9f); + s_aPedGSpheres[1].center = CVector(0.0f, -0.1f, -0.9f); + s_aPedGSpheres[2].center = CVector(0.0f, 0.25f, -0.9f); + s_aPedGSpheres[3].center = CVector(0.0f, 0.65f, -0.9f); + + s_aPedGSpheres[0].surface = SURFACE_PED; + s_aPedGSpheres[1].surface = SURFACE_PED; + s_aPedGSpheres[2].surface = SURFACE_PED; + s_aPedGSpheres[3].surface = SURFACE_PED; + s_aPedGSpheres[0].piece = 4; + s_aPedGSpheres[1].piece = 1; + s_aPedGSpheres[2].piece = 0; + s_aPedGSpheres[3].piece = 6; + + ms_colModelPedGroundHit.boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelPedGroundHit.boundingBox.Set(CVector(-0.4f, -1.0f, -1.25f), CVector(0.4f, 1.2f, -0.5f)); + + SET_COLMODEL_SPHERES(ms_colModelPedGroundHit, s_aPedGSpheres); + + // Door Spheres + + s_aDoorSpheres[0].radius = 0.15f; + s_aDoorSpheres[1].radius = 0.15f; + s_aDoorSpheres[2].radius = 0.25f; + + s_aDoorSpheres[0].center = CVector(0.0f, -0.25f, -0.35f); + s_aDoorSpheres[1].center = CVector(0.0f, -0.95f, -0.35f); + s_aDoorSpheres[2].center = CVector(0.0f, -0.6f, 0.25f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aDoorSpheres); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aPed2Spheres); i++) { +#endif + s_aDoorSpheres[i].surface = SURFACE_CAR_PANEL; + s_aDoorSpheres[i].piece = 0; + } + + ms_colModelDoor1.boundingSphere.Set(1.5f, CVector(0.0f, -0.6f, 0.0f)); + ms_colModelDoor1.boundingBox.Set(CVector(-0.3f, 0.0f, -0.6f), CVector(0.3f, -1.2f, 0.6f)); + + SET_COLMODEL_SPHERES(ms_colModelDoor1, s_aDoorSpheres); + + // Bumper Spheres + + for (i = 0; i < ARRAY_SIZE(s_aBumperSpheres); i++) + s_aBumperSpheres[i].radius = 0.15f; + + s_aBumperSpheres[0].center = CVector(0.85f, -0.05f, 0.0f); + s_aBumperSpheres[1].center = CVector(0.4f, 0.05f, 0.0f); + s_aBumperSpheres[2].center = CVector(-0.4f, 0.05f, 0.0f); + s_aBumperSpheres[3].center = CVector(-0.85f, -0.05f, 0.0f); + + for (i = 0; i < ARRAY_SIZE(s_aBumperSpheres); i++) { + s_aBumperSpheres[i].surface = SURFACE_CAR_PANEL; + s_aBumperSpheres[i].piece = 0; + } + + ms_colModelBumper1.boundingSphere.Set(2.2f, CVector(0.0f, -0.6f, 0.0f)); + ms_colModelBumper1.boundingBox.Set(CVector(-1.2f, -0.3f, -0.2f), CVector(1.2f, 0.3f, 0.2f)); + + SET_COLMODEL_SPHERES(ms_colModelBumper1, s_aBumperSpheres); + + // Panel Spheres + + for (i = 0; i < ARRAY_SIZE(s_aPanelSpheres); i++) + s_aPanelSpheres[i].radius = 0.15f; + + s_aPanelSpheres[0].center = CVector(0.15f, 0.45f, 0.0f); + s_aPanelSpheres[1].center = CVector(0.15f, -0.45f, 0.0f); + s_aPanelSpheres[2].center = CVector(-0.15f, -0.45f, 0.0f); + s_aPanelSpheres[3].center = CVector(-0.15f, 0.45f, 0.0f); + + for (i = 0; i < ARRAY_SIZE(s_aPanelSpheres); i++) { + s_aPanelSpheres[i].surface = SURFACE_CAR_PANEL; + s_aPanelSpheres[i].piece = 0; + } + + ms_colModelPanel1.boundingSphere.Set(1.4f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelPanel1.boundingBox.Set(CVector(-0.3f, -0.6f, -0.15f), CVector(0.3f, 0.6f, 0.15f)); + + SET_COLMODEL_SPHERES(ms_colModelPanel1, s_aPanelSpheres); + + // Bonnet Spheres + + for (i = 0; i < ARRAY_SIZE(s_aBonnetSpheres); i++) + s_aBonnetSpheres[i].radius = 0.2f; + + s_aBonnetSpheres[0].center = CVector(-0.4f, 0.1f, 0.0f); + s_aBonnetSpheres[1].center = CVector(-0.4f, 0.9f, 0.0f); + s_aBonnetSpheres[2].center = CVector(0.4f, 0.1f, 0.0f); + s_aBonnetSpheres[3].center = CVector(0.4f, 0.9f, 0.0f); + + for (i = 0; i < ARRAY_SIZE(s_aBonnetSpheres); i++) { + s_aBonnetSpheres[i].surface = SURFACE_CAR_PANEL; + s_aBonnetSpheres[i].piece = 0; + } + + ms_colModelBonnet1.boundingSphere.Set(1.7f, CVector(0.0f, 0.5f, 0.0f)); + ms_colModelBonnet1.boundingBox.Set(CVector(-0.7f, -0.2f, -0.3f), CVector(0.7f, 1.2f, 0.3f)); + + SET_COLMODEL_SPHERES(ms_colModelBonnet1, s_aBonnetSpheres); + + // Boot Spheres + + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) + s_aBootSpheres[i].radius = 0.2f; + + s_aBootSpheres[0].center = CVector(-0.4f, -0.1f, 0.0f); + s_aBootSpheres[1].center = CVector(-0.4f, -0.6f, 0.0f); + s_aBootSpheres[2].center = CVector(0.4f, -0.1f, 0.0f); + s_aBootSpheres[3].center = CVector(0.4f, -0.6f, 0.0f); + + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) { + s_aBootSpheres[i].surface = SURFACE_CAR_PANEL; + s_aBootSpheres[i].piece = 0; + } + + ms_colModelBoot1.boundingSphere.Set(1.4f, CVector(0.0f, -0.4f, 0.0f)); + ms_colModelBoot1.boundingBox.Set(CVector(-0.7f, -0.9f, -0.3f), CVector(0.7f, 0.2f, 0.3f)); + + SET_COLMODEL_SPHERES(ms_colModelBoot1, s_aBootSpheres); + + // Wheel Spheres + + s_aWheelSpheres[0].radius = 0.35f; + s_aWheelSpheres[1].radius = 0.35f; + + s_aWheelSpheres[0].center = CVector(-0.3f, 0.0f, 0.0f); + s_aWheelSpheres[1].center = CVector(0.3f, 0.0f, 0.0f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aWheelSpheres); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) { +#endif + s_aWheelSpheres[i].surface = SURFACE_WHEELBASE; + s_aWheelSpheres[i].piece = 0; + } + + ms_colModelWheel1.boundingSphere.Set(1.4f, CVector(0.0f, 0.0f, 0.0f)); + ms_colModelWheel1.boundingBox.Set(CVector(-0.7f, -0.4f, -0.4f), CVector(0.7f, 0.4f, 0.4f)); + + SET_COLMODEL_SPHERES(ms_colModelWheel1, s_aWheelSpheres); + + // Body Part Spheres 1 + + s_aBodyPartSpheres1[0].radius = 0.2f; + s_aBodyPartSpheres1[1].radius = 0.2f; + + s_aBodyPartSpheres1[0].center = CVector(0.0f, 0.0f, 0.0f); + s_aBodyPartSpheres1[1].center = CVector(0.8f, 0.0f, 0.0f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aBodyPartSpheres1); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) { +#endif + s_aBodyPartSpheres1[i].surface = SURFACE_PED; + s_aBodyPartSpheres1[i].piece = 0; + } + + ms_colModelBodyPart1.boundingSphere.Set(0.7f, CVector(0.4f, 0.0f, 0.0f)); + ms_colModelBodyPart1.boundingBox.Set(CVector(-0.3f, -0.3f, -0.3f), CVector(1.1f, 0.3f, 0.3f)); + + SET_COLMODEL_SPHERES(ms_colModelBodyPart1, s_aBodyPartSpheres1); + + // Body Part Spheres 2 + + s_aBodyPartSpheres2[0].radius = 0.15f; + s_aBodyPartSpheres2[1].radius = 0.15f; + + s_aBodyPartSpheres2[0].center = CVector(0.0f, 0.0f, 0.0f); + s_aBodyPartSpheres2[1].center = CVector(0.5f, 0.0f, 0.0f); + +#ifdef FIX_BUGS + for (i = 0; i < ARRAY_SIZE(s_aBodyPartSpheres2); i++) { +#else + for (i = 0; i < ARRAY_SIZE(s_aBootSpheres); i++) { +#endif + s_aBodyPartSpheres2[i].surface = SURFACE_PED; + s_aBodyPartSpheres2[i].piece = 0; + } + + ms_colModelBodyPart2.boundingSphere.Set(0.5f, CVector(0.25f, 0.0f, 0.0f)); + ms_colModelBodyPart2.boundingBox.Set(CVector(-0.2f, -0.2f, -0.2f), CVector(0.7f, 0.2f, 0.2f)); + + SET_COLMODEL_SPHERES(ms_colModelBodyPart2, s_aBodyPartSpheres2); + +#undef SET_COLMODEL_SPHERES +} diff --git a/src/collision/TempColModels.h b/src/collision/TempColModels.h new file mode 100644 index 0000000..057728a --- /dev/null +++ b/src/collision/TempColModels.h @@ -0,0 +1,23 @@ +#pragma once + +#include "ColModel.h" + +class CTempColModels +{ +public: + static CColModel ms_colModelPed1; + static CColModel ms_colModelPed2; + static CColModel ms_colModelBBox; + static CColModel ms_colModelBumper1; + static CColModel ms_colModelWheel1; + static CColModel ms_colModelPanel1; + static CColModel ms_colModelBodyPart2; + static CColModel ms_colModelBodyPart1; + static CColModel ms_colModelCutObj[5]; + static CColModel ms_colModelPedGroundHit; + static CColModel ms_colModelBoot1; + static CColModel ms_colModelDoor1; + static CColModel ms_colModelBonnet1; + + static void Initialise(void); +}; diff --git a/src/collision/VuCollision.cpp b/src/collision/VuCollision.cpp new file mode 100644 index 0000000..8828d2e --- /dev/null +++ b/src/collision/VuCollision.cpp @@ -0,0 +1,282 @@ +#include "common.h" +#ifdef VU_COLLISION +#include "VuVector.h" +#include "VuCollision.h" + +#ifndef GTA_PS2 +int16 vi01; +CVuVector vf01; +CVuVector vf02; +CVuVector vf03; + +CVuVector +DistanceBetweenSphereAndLine(const CVuVector ¢er, const CVuVector &p0, const CVuVector &line) +{ + // center VF12 + // p0 VF14 + // line VF15 + CVuVector ret; // VF16 + CVuVector p1 = p0+line; + CVuVector dist0 = center - p0; // VF20 + CVuVector dist1 = center - p1; // VF25 + float lenSq = line.MagnitudeSqr(); // VF21 + float distSq0 = dist0.MagnitudeSqr(); // VF22 + float distSq1 = dist1.MagnitudeSqr(); + float dot = DotProduct(dist0, line); // VF23 + if(dot < 0.0f){ + // not above line, closest to p0 + ret = p0; + ret.w = distSq0; + return ret; + } + float t = dot/lenSq; // param of nearest point on infinite line + if(t > 1.0f){ + // not above line, closest to p1 + ret = p1; + ret.w = distSq1; + return ret; + } + // closest to line + ret = p0 + line*t; + ret.w = (ret - center).MagnitudeSqr(); + return ret; +} +inline int SignFlags(const CVector &v) +{ + int f = 0; + if(v.x < 0.0f) f |= 1; + if(v.y < 0.0f) f |= 2; + if(v.z < 0.0f) f |= 4; + return f; +} +#endif + +extern "C" void +LineToTriangleCollision(const CVuVector &p0, const CVuVector &p1, + const CVuVector &v0, const CVuVector &v1, const CVuVector &v2, + const CVuVector &plane) +{ +#ifdef GTA_PS2 + __asm__ volatile ( + ".set noreorder\n" + "lqc2 vf12, 0x0(%0)\n" + "lqc2 vf13, 0x0(%1)\n" + "lqc2 vf14, 0x0(%2)\n" + "lqc2 vf15, 0x0(%3)\n" + "lqc2 vf16, 0x0(%4)\n" + "lqc2 vf17, 0x0(%5)\n" + "vcallms Vu0LineToTriangleCollisionStart\n" + ".set reorder\n" + : + : "r" (&p0), "r" (&p1), "r" (&v0), "r" (&v1), "r" (&v2), "r" (&plane) + ); +#else + float dot0 = DotProduct(plane, p0); + float dot1 = DotProduct(plane, p1); + float dist0 = plane.w - dot0; + float dist1 = plane.w - dot1; + + // if points are on the same side, no collision + if(dist0 * dist1 > 0.0f){ + vi01 = 0; + return; + } + + CVuVector diff = p1 - p0; + float t = dist0/(dot1 - dot0); + CVuVector p = p0 + diff*t; + p.w = 0.0f; + vf01 = p; + vf03.x = t; + + // Check if point is inside + CVector cross1 = CrossProduct(p-v0, v1-v0); + CVector cross2 = CrossProduct(p-v1, v2-v1); + CVector cross3 = CrossProduct(p-v2, v0-v2); + // Only check relevant directions + int flagmask = 0; + if(Abs(plane.x) > 0.5f) flagmask |= 1; + if(Abs(plane.y) > 0.5f) flagmask |= 2; + if(Abs(plane.z) > 0.5f) flagmask |= 4; + int flags1 = SignFlags(cross1) & flagmask; + int flags2 = SignFlags(cross2) & flagmask; + int flags3 = SignFlags(cross3) & flagmask; + // inside if on the same side of all edges + if(flags1 != flags2 || flags1 != flags3){ + vi01 = 0; + return; + } + vi01 = 1; + vf02 = plane; + return; +#endif +} + +extern "C" void +LineToTriangleCollisionCompressed(const CVuVector &p0, const CVuVector &p1, VuTriangle &tri) +{ +#ifdef GTA_PS2 + __asm__ volatile ( + ".set noreorder\n" + "lqc2 vf12, 0x0(%0)\n" + "lqc2 vf13, 0x0(%1)\n" + "lqc2 vf14, 0x0(%2)\n" + "lqc2 vf15, 0x10(%2)\n" + "lqc2 vf16, 0x20(%2)\n" + "lqc2 vf17, 0x30(%2)\n" + "vcallms Vu0LineToTriangleCollisionCompressedStart\n" + ".set reorder\n" + : + : "r" (&p0), "r" (&p1), "r" (&tri) + ); +#else + CVuVector v0, v1, v2, plane; + v0.x = tri.v0[0]/128.0f; + v0.y = tri.v0[1]/128.0f; + v0.z = tri.v0[2]/128.0f; + v0.w = tri.v0[3]/128.0f; + v1.x = tri.v1[0]/128.0f; + v1.y = tri.v1[1]/128.0f; + v1.z = tri.v1[2]/128.0f; + v1.w = tri.v1[3]/128.0f; + v2.x = tri.v2[0]/128.0f; + v2.y = tri.v2[1]/128.0f; + v2.z = tri.v2[2]/128.0f; + v2.w = tri.v2[3]/128.0f; + plane.x = tri.plane[0]/4096.0f; + plane.y = tri.plane[1]/4096.0f; + plane.z = tri.plane[2]/4096.0f; + plane.w = tri.plane[3]/128.0f; + LineToTriangleCollision(p0, p1, v0, v1, v2, plane); +#endif +} + +extern "C" void +SphereToTriangleCollision(const CVuVector &sph, + const CVuVector &v0, const CVuVector &v1, const CVuVector &v2, + const CVuVector &plane) +{ +#ifdef GTA_PS2 + __asm__ volatile ( + ".set noreorder\n" + "lqc2 vf12, 0x0(%0)\n" + "lqc2 vf14, 0x0(%1)\n" + "lqc2 vf15, 0x0(%2)\n" + "lqc2 vf16, 0x0(%3)\n" + "lqc2 vf17, 0x0(%4)\n" + "vcallms Vu0SphereToTriangleCollisionStart\n" + ".set reorder\n" + : + : "r" (&sph), "r" (&v0), "r" (&v1), "r" (&v2), "r" (&plane) + ); +#else + float planedist = DotProduct(plane, sph) - plane.w; // VF02 + if(Abs(planedist) > sph.w){ + vi01 = 0; + return; + } + // point on plane + CVuVector p = sph - planedist*plane; + p.w = 0.0f; + vf01 = p; + planedist = Abs(planedist); + // edges + CVuVector v01 = v1 - v0; + CVuVector v12 = v2 - v1; + CVuVector v20 = v0 - v2; + // VU code calculates normal again for some weird reason... + // Check sides of point + CVector cross1 = CrossProduct(p-v0, v01); + CVector cross2 = CrossProduct(p-v1, v12); + CVector cross3 = CrossProduct(p-v2, v20); + // Only check relevant directions + int flagmask = 0; + if(Abs(plane.x) > 0.1f) flagmask |= 1; + if(Abs(plane.y) > 0.1f) flagmask |= 2; + if(Abs(plane.z) > 0.1f) flagmask |= 4; + int nflags = SignFlags(plane) & flagmask; + int flags1 = SignFlags(cross1) & flagmask; + int flags2 = SignFlags(cross2) & flagmask; + int flags3 = SignFlags(cross3) & flagmask; + int testcase = 0; + CVuVector closest(0.0f, 0.0f, 0.0f); // VF04 + if(flags1 == nflags){ + closest += v2; + testcase++; + } + if(flags2 == nflags){ + closest += v0; + testcase++; + } + if(flags3 == nflags){ + closest += v1; + testcase++; + } + if(testcase == 3){ + // inside triangle - dist to plane already checked + vf02 = plane; + vf02.w = vf03.x = planedist; + vi01 = 1; + }else if(testcase == 1){ + // outside two sides - closest to point opposide inside edge + vf01 = closest; + vf02 = sph - closest; + float distSq = vf02.MagnitudeSqr(); + vi01 = sph.w*sph.w > distSq; + vf03.x = Sqrt(distSq); + vf02 *= 1.0f/vf03.x; + }else{ + // inside two sides - closest to third edge + if(flags1 != nflags) + closest = DistanceBetweenSphereAndLine(sph, v0, v01); + else if(flags2 != nflags) + closest = DistanceBetweenSphereAndLine(sph, v1, v12); + else + closest = DistanceBetweenSphereAndLine(sph, v2, v20); + vi01 = sph.w*sph.w > closest.w; + vf01 = closest; + vf02 = sph - closest; + vf03.x = Sqrt(closest.w); + vf02 *= 1.0f/vf03.x; + } +#endif +} + +extern "C" void +SphereToTriangleCollisionCompressed(const CVuVector &sph, VuTriangle &tri) +{ +#ifdef GTA_PS2 + __asm__ volatile ( + ".set noreorder\n" + "lqc2 vf12, 0x0(%0)\n" + "lqc2 vf14, 0x0(%1)\n" + "lqc2 vf15, 0x10(%1)\n" + "lqc2 vf16, 0x20(%1)\n" + "lqc2 vf17, 0x30(%1)\n" + "vcallms Vu0SphereToTriangleCollisionCompressedStart\n" + ".set reorder\n" + : + : "r" (&sph), "r" (&tri) + ); +#else + CVuVector v0, v1, v2, plane; + v0.x = tri.v0[0]/128.0f; + v0.y = tri.v0[1]/128.0f; + v0.z = tri.v0[2]/128.0f; + v0.w = tri.v0[3]/128.0f; + v1.x = tri.v1[0]/128.0f; + v1.y = tri.v1[1]/128.0f; + v1.z = tri.v1[2]/128.0f; + v1.w = tri.v1[3]/128.0f; + v2.x = tri.v2[0]/128.0f; + v2.y = tri.v2[1]/128.0f; + v2.z = tri.v2[2]/128.0f; + v2.w = tri.v2[3]/128.0f; + plane.x = tri.plane[0]/4096.0f; + plane.y = tri.plane[1]/4096.0f; + plane.z = tri.plane[2]/4096.0f; + plane.w = tri.plane[3]/128.0f; + SphereToTriangleCollision(sph, v0, v1, v2, plane); +#endif +} +#endif \ No newline at end of file diff --git a/src/collision/VuCollision.h b/src/collision/VuCollision.h new file mode 100644 index 0000000..29ca4cb --- /dev/null +++ b/src/collision/VuCollision.h @@ -0,0 +1,32 @@ +#pragma once + + +struct VuTriangle +{ + // Compressed int16 but unpacked +#ifdef GTA_PS2 + uint128 v0; + uint128 v1; + uint128 v2; + uint128 plane; +#else + int32 v0[4]; + int32 v1[4]; + int32 v2[4]; + int32 plane[4]; +#endif +}; + +#ifndef GTA_PS2 +extern int16 vi01; +extern CVuVector vf01; +extern CVuVector vf02; +extern CVuVector vf03; +#endif + +extern "C" { +void LineToTriangleCollision(const CVuVector &p0, const CVuVector &p1, const CVuVector &v0, const CVuVector &v1, const CVuVector &v2, const CVuVector &plane); +void LineToTriangleCollisionCompressed(const CVuVector &p0, const CVuVector &p1, VuTriangle &tri); +void SphereToTriangleCollision(const CVuVector &sph, const CVuVector &v0, const CVuVector &v1, const CVuVector &v2, const CVuVector &plane); +void SphereToTriangleCollisionCompressed(const CVuVector &sph, VuTriangle &tri); +} diff --git a/src/collision/vu0Collision.dsm b/src/collision/vu0Collision.dsm new file mode 100644 index 0000000..657c8b8 --- /dev/null +++ b/src/collision/vu0Collision.dsm @@ -0,0 +1,21 @@ +.align 4 +.global Vu0CollisionDmaTag +Vu0CollisionDmaTag: +DMAcnt * +MPG 0, * +.vu +.include "vu0Collision_1.s" +.EndMPG +.EndDmaData +DMAend + +.global Vu0Collision2DmaTag +Vu0Collision2DmaTag: +DMAcnt * +MPG 0, * +.vu +.include "vu0Collision_2.s" +.EndMPG +.EndDmaData +DMAend +.end diff --git a/src/collision/vu0Collision_1.s b/src/collision/vu0Collision_1.s new file mode 100644 index 0000000..055c864 --- /dev/null +++ b/src/collision/vu0Collision_1.s @@ -0,0 +1,610 @@ +QuitAndFail: + NOP[E] IADDIU VI01, VI00, 0 + NOP NOP + + +QuitAndSucceed: + NOP[E] IADDIU VI01, VI00, 1 + NOP NOP + + +; 20 -- unused +; VF12, VF13 xyz: sphere centers +; VF14, VF15 x: sphere radii +; out: +; VI01: set when collision +; VF01: supposed to be intersection point? +; VF02: normal (pointing towards s1, not normalized) +.globl Vu0SphereToSphereCollision +Vu0SphereToSphereCollision: + SUB.xyz VF02, VF13, VF12 NOP ; dist of centers + ADD.x VF04, VF14, VF15 NOP ; s = sum of radii + MUL.xyzw VF03, VF02, VF02 NOP ; + MUL.x VF04, VF04, VF04 DIV Q, VF14x, VF04x ; square s + NOP NOP ; + NOP NOP ; + MULAx.w ACC, VF00, VF03 NOP ; + MADDAy.w ACC, VF00, VF03 NOP ; + MADDz.w VF03, VF00, VF03 NOP ; d = DistSq of centers + NOP NOP ; + MULAw.xyz ACC, VF12, VF00 NOP ; + MADDq.xyz VF01, VF02, Q NOP ; intersection, but wrong + CLIPw.xyz VF04, VF03 NOP ; compare s and d + SUB.xyz VF02, VF00, VF02 NOP ; compute normal + NOP NOP ; + NOP NOP ; + NOP FCAND VI01, 0x3 ; 0x2 cannot be set here + NOP[E] NOP ; + NOP NOP ; + + +; B8 -- unused +; VF12: +; VF13: radius +; VF14: +; VF15: box dimensions (?) +.globl Vu0SphereToAABBCollision +Vu0SphereToAABBCollision: + SUB.xyz VF03, VF12, VF14 LOI 0.5 + MULi.xyz VF15, VF15, I NOP + MUL.x VF13, VF13, VF13 NOP + SUB.xyz VF04, VF03, VF15 NOP + ADD.xyz VF05, VF03, VF15 MR32.xyzw VF16, VF15 + CLIPw.xyz VF03, VF16 MR32.xyzw VF17, VF16 + MUL.xyz VF04, VF04, VF04 NOP + MUL.xyz VF05, VF05, VF05 NOP + CLIPw.xyz VF03, VF17 MR32.xyzw VF16, VF17 + NOP FCAND VI01, 0x1 + MINI.xyz VF04, VF04, VF05 MFIR.x VF09, VI01 + NOP NOP + CLIPw.xyz VF03, VF16 FCAND VI01, 0x4 + NOP MFIR.y VF09, VI01 + NOP NOP + MULAx.w ACC, VF00, VF00 NOP + ADD.xyz VF01, VF00, VF03 FCAND VI01, 0x10 + NOP MFIR.z VF09, VI01 + NOP LOI 2 + NOP FCAND VI01, 0x30 + SUBAw.xyz ACC, VF00, VF00 IADD VI04, VI00, VI01 + ITOF0.xyz VF09, VF09 FCAND VI01, 0x300 + NOP IADD VI03, VI00, VI01 + NOP FCAND VI01, 0x3000 + NOP IADD VI02, VI00, VI01 + MADDi.xyzw VF09, VF09, I NOP + NOP IBEQ VI04, VI00, IgnoreZValue + NOP NOP + MADDAz.w ACC, VF00, VF04 NOP + MUL.z VF01, VF09, VF15 NOP +IgnoreZValue: + NOP IBEQ VI03, VI00, IgnoreYValue + NOP NOP + MADDAy.w ACC, VF00, VF04 NOP + MUL.y VF01, VF09, VF15 NOP +IgnoreYValue: + NOP IBEQ VI02, VI00, IgnoreXValue + NOP NOP + MADDAx.w ACC, VF00, VF04 NOP + MUL.x VF01, VF09, VF15 NOP +IgnoreXValue: + MADDx.w VF06, VF00, VF00 NOP + SUB.xyz VF02, VF03, VF01 NOP + ADD.xyz VF01, VF01, VF14 NOP + MULx.w VF01, VF00, VF00 NOP + CLIPw.xyz VF13, VF06 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x1 +QuitMicrocode: + NOP[E] NOP + NOP NOP + + +; 240 +.globl Vu0LineToSphereCollision +Vu0LineToSphereCollision: + SUB.xyzw VF01, VF13, VF12 NOP + SUB.xyzw VF02, VF14, VF12 NOP + MUL.xyz VF03, VF01, VF02 NOP + MUL.xyz VF04, VF01, VF01 NOP + MUL.x VF15, VF15, VF15 NOP + MUL.xyz VF02, VF02, VF02 NOP + MULAx.w ACC, VF00, VF03 NOP + MADDAy.w ACC, VF00, VF03 NOP + MADDz.w VF03, VF00, VF03 NOP + MULAx.w ACC, VF00, VF04 NOP + MADDAy.w ACC, VF00, VF04 NOP + MADDz.w VF01, VF00, VF04 NOP + MULAx.w ACC, VF00, VF02 NOP + MADDAy.w ACC, VF00, VF02 NOP + MADDz.w VF02, VF00, VF02 NOP + MULA.w ACC, VF03, VF03 NOP + MADDAx.w ACC, VF01, VF15 NOP + MSUB.w VF05, VF01, VF02 NOP + NOP NOP + NOP NOP + NOP IADDIU VI02, VI00, 0x10 + NOP FMAND VI01, VI02 + NOP IBNE VI01, VI00, QuitAndFail + NOP NOP + CLIPw.xyz VF15, VF02 SQRT Q, VF05w + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x1 + NOP IBNE VI00, VI01, LineStartInsideSphere + NOP NOP + SUBq.w VF05, VF03, Q NOP + SUB.w VF05, VF05, VF01 DIV Q, VF05w, VF01w + NOP FMAND VI01, VI02 + NOP IBNE VI01, VI00, QuitAndFail + NOP NOP + NOP FMAND VI01, VI02 + NOP IBEQ VI01, VI00, QuitAndFail + NOP NOP + ADDA.xyz ACC, VF12, VF00 NOP + MADDq.xyz VF01, VF01, Q NOP + MULx.w VF01, VF00, VF00 NOP + SUB.xyz VF02, VF01, VF14 NOP + NOP[E] NOP + NOP NOP +LineStartInsideSphere: + NOP MOVE.xyzw VF01, VF12 + NOP[E] IADDIU VI01, VI00, 0x1 + NOP NOP + + +; 3C0 +.globl Vu0LineToAABBCollision +Vu0LineToAABBCollision: + SUB.xyzw VF08, VF13, VF12 LOI 0.5 + MULi.xyz VF15, VF15, I IADDIU VI08, VI00, 0x0 + SUB.xyzw VF12, VF12, VF14 NOP + SUB.xyzw VF13, VF13, VF14 NOP + NOP DIV Q, VF00w, VF08x + NOP MR32.xyzw VF03, VF15 + SUB.xyz VF06, VF15, VF12 NOP + ADD.xyz VF07, VF15, VF12 NOP + NOP NOP + CLIPw.xyz VF12, VF03 MR32.xyzw VF04, VF03 + NOP NOP + ADDq.x VF09, VF00, Q DIV Q, VF00w, VF08y + NOP NOP + CLIPw.xyz VF12, VF04 MR32.xyzw VF05, VF04 + SUB.xyz VF07, VF00, VF07 IADDIU VI06, VI00, 0xCC + NOP IADDIU VI07, VI00, 0x30 + NOP NOP + CLIPw.xyz VF12, VF05 FCGET VI02 + NOP IAND VI02, VI02, VI06 + ADDq.y VF09, VF00, Q DIV Q, VF00w, VF08z + SUB.xyz VF10, VF00, VF10 NOP + CLIPw.xyz VF13, VF03 FCGET VI03 + CLIPw.xyz VF13, VF04 IAND VI03, VI03, VI07 + CLIPw.xyz VF13, VF05 FCAND VI01, 0x3330 + NOP IBEQ VI01, VI00, StartPointInsideAABB + NOP NOP + ADDq.z VF09, VF00, Q FCGET VI04 + NOP FCGET VI05 + NOP IAND VI04, VI04, VI06 + NOP IAND VI05, VI05, VI07 + MULx.xyz VF17, VF08, VF09 NOP + MULy.xyz VF18, VF08, VF09 IADDIU VI07, VI00, 0x80 + MULz.xyz VF19, VF08, VF09 IAND VI06, VI02, VI07 + MUL.w VF10, VF00, VF00 IAND VI07, VI04, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMaxXSide + NOP NOP + MULAx.xyz ACC, VF17, VF07 NOP + MADDw.xyz VF16, VF12, VF00 NOP + MUL.x VF10, VF07, VF09 NOP + CLIPw.xyz VF16, VF04 NOP + CLIPw.xyz VF16, VF05 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x330 + NOP IBNE VI01, VI00, CheckMaxXSide + NOP NOP + MULx.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.yz VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + SUBw.x VF02, VF00, VF00 NOP +CheckMaxXSide: + MULAx.xyz ACC, VF17, VF06 IADDIU VI07, VI00, 0x40 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI02, VI07 + MUL.x VF10, VF06, VF09 IAND VI07, VI04, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMinYSide + NOP NOP + CLIPw.xyz VF16, VF04 NOP + CLIPw.xyz VF16, VF05 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0xCC03 + NOP IBNE VI01, VI00, CheckMinYSide + NOP NOP + MULx.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.yz VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + ADDw.x VF02, VF00, VF00 NOP +CheckMinYSide: + MULAy.xyz ACC, VF18, VF07 IADDIU VI07, VI00, 0x8 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI02, VI07 + MUL.y VF10, VF07, VF09 IAND VI07, VI04, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMaxYSide + NOP NOP + CLIPw.xyz VF16, VF03 NOP + CLIPw.xyz VF16, VF05 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3C0C + NOP IBNE VI01, VI00, CheckMaxYSide + NOP NOP + MULy.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.xz VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + SUBw.y VF02, VF00, VF00 NOP +CheckMaxYSide: + MULAy.xyz ACC, VF18, VF06 IADDIU VI07, VI00, 0x4 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI02, VI07 + MUL.y VF10, VF06, VF09 IAND VI07, VI04, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMinZSide + NOP NOP + CLIPw.xyz VF16, VF03 NOP + CLIPw.xyz VF16, VF05 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3C0C + NOP IBNE VI01, VI00, CheckMinZSide + NOP NOP + MULy.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.xz VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + ADDw.y VF02, VF00, VF00 NOP +CheckMinZSide: + MULAz.xyz ACC, VF19, VF07 IADDIU VI07, VI00, 0x20 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI03, VI07 + MUL.z VF10, VF07, VF09 IAND VI07, VI05, VI07 + NOP NOP + NOP IBEQ VI06, VI07, CheckMaxZSide + NOP NOP + CLIPw.xyz VF16, VF03 NOP + CLIPw.xyz VF16, VF04 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3330 + NOP IBNE VI01, VI00, CheckMaxZSide + NOP NOP + MULz.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.xy VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + SUBw.z VF02, VF00, VF00 NOP +CheckMaxZSide: + MULAz.xyz ACC, VF19, VF06 IADDIU VI07, VI00, 0x10 + MADDw.xyz VF16, VF12, VF00 IAND VI06, VI03, VI07 + MUL.z VF10, VF06, VF09 IAND VI07, VI05, VI07 + NOP NOP + NOP IBEQ VI06, VI07, DoneAllChecks + NOP NOP + CLIPw.xyz VF16, VF03 NOP + CLIPw.xyz VF16, VF04 NOP + CLIPw.xyz VF10, VF10 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3330 + NOP IBNE VI01, VI00, DoneAllChecks + NOP NOP + MULz.w VF10, VF00, VF10 IADDIU VI08, VI00, 0x1 + ADD.xy VF02, VF00, VF00 MOVE.xyzw VF01, VF16 + ADDw.z VF02, VF00, VF00 NOP +DoneAllChecks: + ADD.xyz VF01, VF01, VF14 IADD VI01, VI00, VI08 + NOP[E] NOP + NOP NOP +StartPointInsideAABB: + ADD.xyz VF01, VF12, VF14 WAITQ + NOP IADDIU VI01, VI00, 0x1 + NOP[E] NOP + NOP NOP + + +; 860 +.globl Vu0LineToTriangleCollisionCompressedStart +Vu0LineToTriangleCollisionCompressedStart: + ITOF0.xyzw VF17, VF17 LOI 0.000244140625 ; 1.0/4096.0 + ITOF0.xyzw VF14, VF14 NOP + ITOF0.xyzw VF15, VF15 NOP + ITOF0.xyzw VF16, VF16 NOP + MULi.xyz VF17, VF17, I LOI 0.0078125 ; 1.0/128.0 + MULi.w VF17, VF17, I NOP + MULi.xyzw VF14, VF14, I NOP + MULi.xyzw VF15, VF15, I NOP + MULi.xyzw VF16, VF16, I NOP +; fall through + +; 8A8 +; VF12: point0 +; VF13: point1 +; VF14-16: verts +; VF17: plane +; out: +; VF01: intersection point +; VF02: triangle normal +; VF03 x: intersection parameter +.globl Vu0LineToTriangleCollisionStart +Vu0LineToTriangleCollisionStart: + MUL.xyz VF10, VF17, VF12 LOI 0.5 + MUL.xyz VF11, VF17, VF13 NOP + SUB.xyz VF02, VF13, VF12 NOP ; line dist + ADD.xyz VF17, VF17, VF00 NOP + MULi.w VF03, VF00, I NOP + MULAx.w ACC, VF00, VF10 NOP + MADDAy.w ACC, VF00, VF10 IADDIU VI06, VI00, 0xE0 + MADDz.w VF10, VF00, VF10 FMAND VI05, VI06 ; -- normal sign flags, unused + MULAx.w ACC, VF00, VF11 NOP + MADDAy.w ACC, VF00, VF11 NOP + MADDz.w VF11, VF00, VF11 NOP + SUB.w VF09, VF17, VF10 NOP ; plane-pos 0 + CLIPw.xyz VF17, VF03 NOP ; compare normal against 0.5 to figure out which in which dimension to compare + NOP IADDIU VI02, VI00, 0x10 ; Sw flag + SUBA.w ACC, VF17, VF11 NOP ; plane-pos 1 + SUB.w VF08, VF11, VF10 FMAND VI01, VI02 + NOP NOP + NOP NOP + NOP FMAND VI02, VI02 + NOP IBEQ VI01, VI02, QuitAndFail ; if on same side, no collision + NOP NOP + NOP DIV Q, VF09w, VF08w ; parameter of intersection + NOP FCAND VI01, 0x3 ; check x direction + NOP IADDIU VI02, VI01, 0x7F + NOP IADDIU VI06, VI00, 0x80 + NOP IAND VI02, VI02, VI06 ; Sx flag + NOP FCAND VI01, 0xC ; check y direction + NOP IADDIU VI03, VI01, 0x3F + MULAw.xyz ACC, VF12, VF00 IADDIU VI06, VI00, 0x40 + MADDq.xyz VF01, VF02, Q IAND VI03, VI03, VI06 ; point of intersection -- Sy flag + MULx.w VF01, VF00, VF00 FCAND VI01, 0x30 ; -- check z direction + ADDq.x VF03, VF00, Q IADDIU VI04, VI01, 0x1F ; output parameter + SUB.xyz VF05, VF15, VF14 IADDIU VI06, VI00, 0x20 ; edge vectors + SUB.xyz VF08, VF01, VF14 IAND VI04, VI04, VI06 ; edge vectors -- Sz flag + SUB.xyz VF06, VF16, VF15 IADD VI06, VI02, VI03 ; edge vectors + SUB.xyz VF09, VF01, VF15 IADD VI06, VI06, VI04 ; edge vectors -- combine flags + SUB.xyz VF07, VF14, VF16 NOP ; edge vectors + SUB.xyz VF10, VF01, VF16 NOP ; edge vectors + OPMULA.xyz ACC, VF08, VF05 NOP + OPMSUB.xyz VF18, VF05, VF08 NOP ; cross1 + OPMULA.xyz ACC, VF09, VF06 NOP + OPMSUB.xyz VF19, VF06, VF09 NOP ; cross2 + OPMULA.xyz ACC, VF10, VF07 NOP + OPMSUB.xyz VF20, VF07, VF10 FMAND VI02, VI06 ; cross3 + NOP NOP + NOP FMAND VI03, VI06 + NOP NOP + NOP FMAND VI04, VI06 + NOP NOP + NOP IBNE VI03, VI02, QuitAndFail ; point has to lie on the same side of all edges (i.e. inside) + NOP NOP + NOP IBNE VI04, VI02, QuitAndFail + NOP NOP + MULw.xyz VF02, VF17, VF00 IADDIU VI01, VI00, 0x1 ; success + NOP[E] NOP + NOP NOP + + +; A68 +; VF12: center +; VF14: line origin +; VF15: line vector to other point +; out: VF16 xyz: nearest point on line; w: distance to that point +DistanceBetweenSphereAndLine: + SUB.xyz VF20, VF12, VF14 NOP + MUL.xyz VF21, VF15, VF15 NOP + ADDA.xyz ACC, VF14, VF15 NOP + MSUBw.xyz VF25, VF12, VF00 NOP ; VF25 = VF12 - (VF14+VF15) + MUL.xyz VF22, VF20, VF20 NOP + MUL.xyz VF23, VF20, VF15 NOP + MULAx.w ACC, VF00, VF21 NOP + MADDAy.w ACC, VF00, VF21 NOP + MADDz.w VF21, VF00, VF21 NOP ; MagSq VF15 (line length) + MULAx.w ACC, VF00, VF23 NOP + MADDAy.w ACC, VF00, VF23 NOP + MADDz.w VF23, VF00, VF23 NOP ; dot(VF12-VF14, VF15) + MULAx.w ACC, VF00, VF22 NOP + MADDAy.w ACC, VF00, VF22 NOP + MADDz.w VF22, VF00, VF22 IADDIU VI08, VI00, 0x10 ; MagSq VF12-VF14 -- Sw bit + MUL.xyz VF25, VF25, VF25 FMAND VI08, VI08 + NOP DIV Q, VF23w, VF21w + NOP IBNE VI00, VI08, NegativeRatio + NOP NOP + ADDA.xyz ACC, VF00, VF14 NOP + MADDq.xyz VF16, VF15, Q WAITQ ; nearest point on infinte line + ADDq.x VF24, VF00, Q NOP ; ratio + NOP NOP + NOP NOP + SUB.xyz VF26, VF16, VF12 NOP + CLIPw.xyz VF24, VF00 NOP ; compare ratio to 1.0 + NOP NOP + NOP NOP + MUL.xyz VF26, VF26, VF26 NOP + NOP FCAND VI01, 0x1 + NOP IBNE VI00, VI01, RatioGreaterThanOne + NOP NOP + MULAx.w ACC, VF00, VF26 NOP + MADDAy.w ACC, VF00, VF26 NOP + MADDz.w VF16, VF00, VF26 NOP ; distance + NOP JR VI15 + NOP NOP +NegativeRatio: + ADD.xyz VF16, VF00, VF14 NOP ; return line origin + MUL.w VF16, VF00, VF22 NOP ; and DistSq to it + NOP JR VI15 + NOP NOP +RatioGreaterThanOne: + MULAx.w ACC, VF00, VF25 NOP + MADDAy.w ACC, VF00, VF25 NOP + MADDz.w VF16, VF00, VF25 NOP + ADD.xyz VF16, VF14, VF15 NOP ; return toerh line point + NOP JR VI15 + NOP NOP + + +; BE0 +.globl Vu0SphereToTriangleCollisionCompressedStart +Vu0SphereToTriangleCollisionCompressedStart: + ITOF0.xyzw VF17, VF17 LOI 0.000244140625 ; 1.0/4096.0 + ITOF0.xyzw VF14, VF14 NOP + ITOF0.xyzw VF15, VF15 NOP + ITOF0.xyzw VF16, VF16 NOP + MULi.xyz VF17, VF17, I LOI 0.0078125 ; 1.0/128.0 + MULi.w VF17, VF17, I NOP + MULi.xyzw VF14, VF14, I NOP + MULi.xyzw VF15, VF15, I NOP + MULi.xyzw VF16, VF16, I NOP +; fall through + +; C28 +; VF12: sphere +; VF14-16: verts +; VF17: plane +; out: +; VF01: intersection point +; VF02: triangle normal +; VF03 x: intersection parameter +.globl Vu0SphereToTriangleCollisionStart +Vu0SphereToTriangleCollisionStart: + MUL.xyz VF02, VF12, VF17 LOI 0.1 + ADD.xyz VF17, VF17, VF00 NOP + ADDw.x VF13, VF00, VF12 NOP + NOP NOP + MULAx.w ACC, VF00, VF02 IADDIU VI06, VI00, 0xE0 + MADDAy.w ACC, VF00, VF02 FMAND VI05, VI06 ; normal sign flags + MADDAz.w ACC, VF00, VF02 NOP + MSUB.w VF02, VF00, VF17 NOP ; center plane pos + MULi.w VF03, VF00, I MOVE.xyzw VF04, VF03 + NOP NOP + NOP NOP + CLIPw.xyz VF13, VF02 NOP ; compare dist and radius + CLIPw.xyz VF17, VF03 NOP + MULAw.xyz ACC, VF12, VF00 IADDIU VI07, VI00, 0x0 ; -- clear test case + MSUBw.xyz VF01, VF17, VF02 NOP + MULx.w VF01, VF00, VF00 FCAND VI01, 0x3 ; projected center on plane + ABS.w VF02, VF02 IBEQ VI00, VI01, QuitAndFail ; no intersection + NOP NOP + NOP FCAND VI01, 0x3 ; -- check x direction + SUB.xyz VF02, VF12, VF01 IADDIU VI02, VI01, 0x7F + NOP IADDIU VI06, VI00, 0x80 + SUB.xyz VF05, VF15, VF14 IAND VI02, VI02, VI06 + SUB.xyz VF08, VF01, VF14 FCAND VI01, 0xC ; -- check y direction + SUB.xyz VF06, VF16, VF15 IADDIU VI03, VI01, 0x3F + SUB.xyz VF09, VF01, VF15 IADDIU VI06, VI00, 0x40 + SUB.xyz VF07, VF14, VF16 IAND VI03, VI03, VI06 + SUB.xyz VF10, VF01, VF16 FCAND VI01, 0x30 ; -- check z direction + MUL.xyz VF03, VF02, VF02 IADDIU VI04, VI01, 0x1F + OPMULA.xyz ACC, VF08, VF05 IADDIU VI06, VI00, 0x20 + OPMSUB.xyz VF18, VF05, VF08 IAND VI04, VI04, VI06 + OPMULA.xyz ACC, VF09, VF06 NOP + OPMSUB.xyz VF19, VF06, VF09 IADD VI06, VI02, VI03 + OPMULA.xyz ACC, VF10, VF07 IADD VI06, VI06, VI04 ; -- combine flags + OPMSUB.xyz VF20, VF07, VF10 FMAND VI02, VI06 ; -- cross 1 flags + MULAx.w ACC, VF00, VF03 IAND VI05, VI05, VI06 + MADDAy.w ACC, VF00, VF03 FMAND VI03, VI06 ; -- cross 2 flags + MADDz.w VF03, VF00, VF03 IADDIU VI08, VI00, 0x3 + NOP FMAND VI04, VI06 ; -- cross 3 flags + NOP NOP + NOP IBNE VI02, VI05, CheckSide2 + NOP RSQRT Q, VF00w, VF03w + ADD.xyz VF04, VF00, VF16 IADDIU VI07, VI07, 0x1 ; inside side 1 +CheckSide2: + NOP IBNE VI03, VI05, CheckSide3 + NOP NOP + ADD.xyz VF04, VF00, VF14 IADDIU VI07, VI07, 0x1 ; inside side 2 +CheckSide3: + NOP IBNE VI04, VI05, FinishCheckingSides + NOP NOP + ADD.xyz VF04, VF00, VF15 IADDIU VI07, VI07, 0x1 ; inside side 3 + NOP NOP + NOP IBEQ VI07, VI08, TotallyInsideTriangle + NOP NOP +FinishCheckingSides: + MUL.x VF13, VF13, VF13 IADDIU VI08, VI00, 0x2 + MULq.xyz VF02, VF02, Q WAITQ + NOP IBNE VI07, VI08, IntersectionOutsideTwoSides + NOP NOP + NOP IBEQ VI02, VI05, CheckDistanceSide2 + NOP NOP + NOP MOVE.xyzw VF15, VF05 + NOP BAL VI15, DistanceBetweenSphereAndLine + NOP NOP + NOP B ProcessLineResult + NOP NOP +CheckDistanceSide2: + NOP IBEQ VI03, VI05, CheckDistanceSide3 + NOP NOP + NOP MOVE.xyzw VF14, VF15 + NOP MOVE.xyzw VF15, VF06 + NOP BAL VI15, DistanceBetweenSphereAndLine + NOP NOP + NOP B ProcessLineResult + NOP NOP +CheckDistanceSide3: + NOP MOVE.xyzw VF14, VF16 + NOP MOVE.xyzw VF15, VF07 + NOP BAL VI15, DistanceBetweenSphereAndLine + NOP NOP + NOP B ProcessLineResult + NOP NOP +IntersectionOutsideTwoSides: + SUB.xyz VF05, VF04, VF12 NOP + ADD.xyz VF01, VF00, VF04 NOP ; col point + SUB.xyz VF02, VF12, VF04 NOP + NOP NOP + MUL.xyz VF05, VF05, VF05 NOP + NOP NOP + NOP NOP + NOP NOP + MULAx.w ACC, VF00, VF05 NOP + MADDAy.w ACC, VF00, VF05 NOP + MADDz.w VF05, VF00, VF05 NOP ; distSq to vertex + NOP NOP + NOP NOP + NOP NOP + CLIPw.xyz VF13, VF05 SQRT Q, VF05w ; compare radiusSq and distSq + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x1 + ADDq.x VF03, VF00, Q WAITQ ; dist to vertex + NOP IBEQ VI00, VI01, QuitAndFail ; too far + NOP NOP + NOP NOP + NOP DIV Q, VF00w, VF03x + MULq.xyz VF02, VF02, Q WAITQ ; col normal + NOP[E] NOP + NOP NOP +TotallyInsideTriangle: + ADDw.x VF03, VF00, VF02 WAITQ + MULq.xyz VF02, VF02, Q NOP + NOP[E] IADDIU VI01, VI00, 0x1 + NOP NOP +ProcessLineResult: + CLIPw.xyz VF13, VF16 SQRT Q, VF16w + ADD.xyz VF01, VF00, VF16 NOP + SUB.xyz VF02, VF12, VF16 NOP + NOP NOP + NOP FCAND VI01, 0x1 + ADDq.x VF03, VF00, Q WAITQ + NOP IBEQ VI00, VI01, QuitAndFail + NOP NOP + NOP NOP + NOP DIV Q, VF00w, VF03x + MULq.xyz VF02, VF02, Q WAITQ + NOP[E] NOP + NOP NOP + +EndOfMicrocode: diff --git a/src/collision/vu0Collision_2.s b/src/collision/vu0Collision_2.s new file mode 100644 index 0000000..716c29a --- /dev/null +++ b/src/collision/vu0Collision_2.s @@ -0,0 +1,191 @@ +QuitAndFail2: + NOP[E] IADDIU VI01, VI00, 0x0 + NOP NOP + + +QuitAndSucceed2: + NOP[E] IADDIU VI01, VI00, 0x1 + NOP NOP + + +; 20 +GetBBVertices: + MULw.xy VF02, VF01, VF00 NOP + MUL.z VF02, VF01, VF11 NOP + MULw.xz VF03, VF01, VF00 NOP + MUL.y VF03, VF01, VF11 NOP + MULw.x VF04, VF01, VF00 NOP + MUL.yz VF04, VF01, VF11 NOP + NOP JR VI15 + NOP NOP + + +; 60 +Vu0OBBToOBBCollision: + SUBw.xyz VF11, VF00, VF00 LOI 0.5 + MULi.xyz VF12, VF12, I NOP + MULi.xyz VF13, VF13, I NOP + NOP NOP + NOP NOP + NOP MOVE.xyz VF01, VF12 + NOP BAL VI15, GetBBVertices + NOP NOP + MULAx.xyz ACC, VF14, VF01 NOP + MADDAy.xyz ACC, VF15, VF01 NOP + MADDz.xyz VF01, VF16, VF01 NOP + MULAx.xyz ACC, VF14, VF02 NOP + MADDAy.xyz ACC, VF15, VF02 NOP + MADDz.xyz VF02, VF16, VF02 NOP + MULAx.xyz ACC, VF14, VF03 NOP + MADDAy.xyz ACC, VF15, VF03 NOP + MADDz.xyz VF03, VF16, VF03 NOP + MULAx.xyz ACC, VF14, VF04 NOP + MADDAy.xyz ACC, VF15, VF04 NOP + MADDz.xyz VF04, VF16, VF04 NOP + ABS.xyz VF05, VF01 NOP + ABS.xyz VF06, VF02 NOP + ABS.xyz VF07, VF03 NOP + ABS.xyz VF08, VF04 NOP + NOP NOP + MAX.xyz VF05, VF05, VF06 NOP + NOP NOP + MAX.xyz VF07, VF07, VF08 NOP + NOP NOP + NOP NOP + NOP NOP + MAX.xyz VF05, VF05, VF07 NOP + NOP NOP + NOP NOP + NOP NOP + ADD.xyz VF09, VF05, VF13 NOP + NOP NOP + NOP NOP + NOP NOP + MULx.w VF05, VF00, VF09 NOP + MULy.w VF06, VF00, VF09 NOP + MULz.w VF07, VF00, VF09 NOP + CLIPw.xyz VF17, VF05 NOP + CLIPw.xyz VF17, VF06 NOP + CLIPw.xyz VF17, VF07 MOVE.xyz VF01, VF13 + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3330 + NOP IBNE VI01, VI00, QuitAndFail2 + NOP NOP + NOP BAL VI15, GetBBVertices + NOP NOP + MULAx.xyz ACC, VF18, VF01 NOP + MADDAy.xyz ACC, VF19, VF01 NOP + MADDz.xyz VF01, VF20, VF01 NOP + MULAx.xyz ACC, VF18, VF02 NOP + MADDAy.xyz ACC, VF19, VF02 NOP + MADDz.xyz VF02, VF20, VF02 NOP + MULAx.xyz ACC, VF18, VF03 NOP + MADDAy.xyz ACC, VF19, VF03 NOP + MADDz.xyz VF03, VF20, VF03 NOP + MULAx.xyz ACC, VF18, VF04 NOP + MADDAy.xyz ACC, VF19, VF04 NOP + MADDz.xyz VF04, VF20, VF04 NOP + ABS.xyz VF05, VF01 NOP + ABS.xyz VF06, VF02 NOP + ABS.xyz VF07, VF03 NOP + ABS.xyz VF08, VF04 NOP + NOP NOP + MAX.xyz VF05, VF05, VF06 NOP + NOP NOP + MAX.xyz VF07, VF07, VF08 NOP + NOP NOP + NOP NOP + NOP NOP + MAX.xyz VF05, VF05, VF07 NOP + NOP NOP + NOP NOP + NOP NOP + ADD.xyz VF09, VF05, VF12 NOP + NOP NOP + NOP NOP + NOP NOP + MULx.w VF05, VF00, VF09 NOP + MULy.w VF06, VF00, VF09 NOP + MULz.w VF07, VF00, VF09 NOP + CLIPw.xyz VF21, VF05 NOP + CLIPw.xyz VF21, VF06 NOP + CLIPw.xyz VF21, VF07 NOP + NOP NOP + NOP NOP + NOP NOP + NOP FCAND VI01, 0x3330 + NOP IBNE VI01, VI00, QuitAndFail2 + NOP NOP + SUB.xyz VF06, VF02, VF01 NOP + SUB.xyz VF07, VF03, VF01 NOP + ADD.xyz VF08, VF04, VF01 NOP + ADD.x VF09, VF00, VF12 NOP + ADD.yz VF09, VF00, VF00 NOP + ADD.y VF10, VF00, VF12 NOP + ADD.xz VF10, VF00, VF00 NOP + ADD.z VF11, VF00, VF12 IADDI VI04, VI00, 0x0 + ADD.xy VF11, VF00, VF00 IADD VI02, VI00, VI00 + OPMULA.xyz ACC, VF06, VF09 NOP + OPMSUB.xyz VF01, VF09, VF06 NOP + OPMULA.xyz ACC, VF06, VF10 NOP + OPMSUB.xyz VF02, VF10, VF06 NOP + OPMULA.xyz ACC, VF06, VF11 NOP + OPMSUB.xyz VF03, VF11, VF06 SQI.xyzw VF01, (VI02++) + OPMULA.xyz ACC, VF07, VF09 NOP + OPMSUB.xyz VF01, VF09, VF07 SQI.xyzw VF02, (VI02++) + OPMULA.xyz ACC, VF07, VF10 NOP + OPMSUB.xyz VF02, VF10, VF07 SQI.xyzw VF03, (VI02++) + OPMULA.xyz ACC, VF07, VF11 NOP + OPMSUB.xyz VF03, VF11, VF07 SQI.xyzw VF01, (VI02++) + OPMULA.xyz ACC, VF08, VF09 NOP + OPMSUB.xyz VF01, VF09, VF08 SQI.xyzw VF02, (VI02++) + OPMULA.xyz ACC, VF08, VF10 NOP + OPMSUB.xyz VF02, VF10, VF08 SQI.xyzw VF03, (VI02++) + OPMULA.xyz ACC, VF08, VF11 LOI 0.5 + OPMSUB.xyz VF01, VF11, VF08 SQI.xyzw VF01, (VI02++) + MULi.xyz VF06, VF06, I NOP + MULi.xyz VF07, VF07, I SQI.xyzw VF02, (VI02++) + MULi.xyz VF08, VF08, I NOP + MUL.xyz VF02, VF21, VF01 NOP + MUL.xyz VF03, VF12, VF01 NOP + MUL.xyz VF09, VF06, VF01 NOP + MUL.xyz VF10, VF07, VF01 NOP + MUL.xyz VF11, VF08, VF01 NOP + ABS.xyz VF03, VF03 NOP + ADDy.x VF05, VF09, VF09 NOP + ADDx.y VF05, VF10, VF10 NOP + ADDx.z VF05, VF11, VF11 NOP + NOP NOP +EdgePairLoop: + ADDz.x VF05, VF05, VF09 NOP + ADDz.y VF05, VF05, VF10 NOP + ADDy.z VF05, VF05, VF11 NOP + MULAx.w ACC, VF00, VF02 IADD VI03, VI02, VI00 + MADDAy.w ACC, VF00, VF02 LQD.xyzw VF01, (--VI02) + MADDz.w VF02, VF00, VF02 NOP + ABS.xyz VF05, VF05 NOP + MULAx.w ACC, VF00, VF03 NOP + MADDAy.w ACC, VF00, VF03 NOP + MADDAz.w ACC, VF00, VF03 NOP + MADDAx.w ACC, VF00, VF05 NOP + MADDAy.w ACC, VF00, VF05 NOP + MADDz.w VF03, VF00, VF05 NOP + ADDw.x VF04, VF00, VF02 NOP + MUL.xyz VF02, VF21, VF01 NOP + MUL.xyz VF03, VF12, VF01 NOP + MUL.xyz VF09, VF06, VF01 NOP + CLIPw.xyz VF04, VF03 NOP + MUL.xyz VF10, VF07, VF01 NOP + MUL.xyz VF11, VF08, VF01 NOP + ABS.xyz VF03, VF03 NOP + ADDy.x VF05, VF09, VF09 FCAND VI01, 0x3 + ADDx.y VF05, VF10, VF10 IBNE VI01, VI00, QuitAndFail2 + ADDx.z VF05, VF11, VF11 NOP + NOP IBNE VI03, VI00, EdgePairLoop + NOP NOP + NOP[E] IADDIU VI01, VI00, 0x1 + NOP NOP + +EndOfMicrocode2: diff --git a/src/control/AutoPilot.cpp b/src/control/AutoPilot.cpp new file mode 100644 index 0000000..5af4071 --- /dev/null +++ b/src/control/AutoPilot.cpp @@ -0,0 +1,128 @@ +#include "common.h" + +#include "AutoPilot.h" + +#include "CarCtrl.h" +#include "Curves.h" +#include "PathFind.h" +#include "SaveBuf.h" + +void CAutoPilot::ModifySpeed(float speed) +{ + m_fMaxTrafficSpeed = Max(0.01f, speed); + float positionBetweenNodes = (float)(CTimer::GetTimeInMilliseconds() - m_nTimeEnteredCurve) / m_nTimeToSpendOnCurrentCurve; + CCarPathLink* pCurrentLink = &ThePaths.m_carPathLinks[m_nCurrentPathNodeInfo]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[m_nNextPathNodeInfo]; + float currentPathLinkForwardX = m_nCurrentDirection * ThePaths.m_carPathLinks[m_nCurrentPathNodeInfo].GetDirX(); + float currentPathLinkForwardY = m_nCurrentDirection * ThePaths.m_carPathLinks[m_nCurrentPathNodeInfo].GetDirY(); + float nextPathLinkForwardX = m_nNextDirection * ThePaths.m_carPathLinks[m_nNextPathNodeInfo].GetDirX(); + float nextPathLinkForwardY = m_nNextDirection * ThePaths.m_carPathLinks[m_nNextPathNodeInfo].GetDirY(); + CVector positionOnCurrentLinkIncludingLane( + pCurrentLink->GetX() + ((m_nCurrentLane + 0.5f) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((m_nCurrentLane + 0.5f) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((m_nNextLane + 0.5f) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((m_nNextLane + 0.5f) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + currentPathLinkForwardX, currentPathLinkForwardY, + nextPathLinkForwardX, nextPathLinkForwardY + ) * (1000.0f / m_fMaxTrafficSpeed); +#ifdef FIX_BUGS + /* Casting timer to float is very unwanted, and in this case even causes crashes. */ + m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - + (uint32)(positionBetweenNodes * m_nTimeToSpendOnCurrentCurve); +#else + m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - positionBetweenNodes * m_nTimeToSpendOnCurrentCurve; +#endif +} + +void CAutoPilot::RemoveOnePathNode() +{ + --m_nPathFindNodesCount; + for (int i = 0; i < m_nPathFindNodesCount; i++) + m_aPathFindNodesInfo[i] = m_aPathFindNodesInfo[i + 1]; +} + +#ifdef COMPATIBLE_SAVES +void CAutoPilot::Save(uint8*& buf) +{ + WriteSaveBuf(buf, m_nCurrentRouteNode); + WriteSaveBuf(buf, m_nNextRouteNode); + WriteSaveBuf(buf, m_nPrevRouteNode); + WriteSaveBuf(buf, m_nTimeEnteredCurve); + WriteSaveBuf(buf, m_nTimeToSpendOnCurrentCurve); + WriteSaveBuf(buf, m_nCurrentPathNodeInfo); + WriteSaveBuf(buf, m_nNextPathNodeInfo); + WriteSaveBuf(buf, m_nPreviousPathNodeInfo); + WriteSaveBuf(buf, m_nAntiReverseTimer); + WriteSaveBuf(buf, m_nTimeToStartMission); + WriteSaveBuf(buf, m_nPreviousDirection); + WriteSaveBuf(buf, m_nCurrentDirection); + WriteSaveBuf(buf, m_nNextDirection); + WriteSaveBuf(buf, m_nCurrentLane); + WriteSaveBuf(buf, m_nNextLane); + WriteSaveBuf(buf, m_nDrivingStyle); + WriteSaveBuf(buf, m_nCarMission); + WriteSaveBuf(buf, m_nTempAction); + WriteSaveBuf(buf, m_nTimeTempAction); + WriteSaveBuf(buf, m_fMaxTrafficSpeed); + WriteSaveBuf(buf, m_nCruiseSpeed); + uint8 flags = 0; + if (m_bSlowedDownBecauseOfCars) flags |= BIT(0); + if (m_bSlowedDownBecauseOfPeds) flags |= BIT(1); + if (m_bStayInCurrentLevel) flags |= BIT(2); + if (m_bStayInFastLane) flags |= BIT(3); + if (m_bIgnorePathfinding) flags |= BIT(4); + WriteSaveBuf(buf, flags); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, m_vecDestinationCoors.x); + WriteSaveBuf(buf, m_vecDestinationCoors.y); + WriteSaveBuf(buf, m_vecDestinationCoors.z); + ZeroSaveBuf(buf, 32); + WriteSaveBuf(buf, m_nPathFindNodesCount); + ZeroSaveBuf(buf, 6); +} + +void CAutoPilot::Load(uint8*& buf) +{ + ReadSaveBuf(&m_nCurrentRouteNode, buf); + ReadSaveBuf(&m_nNextRouteNode, buf); + ReadSaveBuf(&m_nPrevRouteNode, buf); + ReadSaveBuf(&m_nTimeEnteredCurve, buf); + ReadSaveBuf(&m_nTimeToSpendOnCurrentCurve, buf); + ReadSaveBuf(&m_nCurrentPathNodeInfo, buf); + ReadSaveBuf(&m_nNextPathNodeInfo, buf); + ReadSaveBuf(&m_nPreviousPathNodeInfo, buf); + ReadSaveBuf(&m_nAntiReverseTimer, buf); + ReadSaveBuf(&m_nTimeToStartMission, buf); + ReadSaveBuf(&m_nPreviousDirection, buf); + ReadSaveBuf(&m_nCurrentDirection, buf); + ReadSaveBuf(&m_nNextDirection, buf); + ReadSaveBuf(&m_nCurrentLane, buf); + ReadSaveBuf(&m_nNextLane, buf); + ReadSaveBuf(&m_nDrivingStyle, buf); + ReadSaveBuf(&m_nCarMission, buf); + ReadSaveBuf(&m_nTempAction, buf); + ReadSaveBuf(&m_nTimeTempAction, buf); + ReadSaveBuf(&m_fMaxTrafficSpeed, buf); + ReadSaveBuf(&m_nCruiseSpeed, buf); + uint8 flags; + ReadSaveBuf(&flags, buf); + m_bSlowedDownBecauseOfCars = !!(flags & BIT(0)); + m_bSlowedDownBecauseOfPeds = !!(flags & BIT(1)); + m_bStayInCurrentLevel = !!(flags & BIT(2)); + m_bStayInFastLane = !!(flags & BIT(3)); + m_bIgnorePathfinding = !!(flags & BIT(4)); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&m_vecDestinationCoors.x, buf); + ReadSaveBuf(&m_vecDestinationCoors.y, buf); + ReadSaveBuf(&m_vecDestinationCoors.z, buf); + SkipSaveBuf(buf, 32); + ReadSaveBuf(&m_nPathFindNodesCount, buf); + SkipSaveBuf(buf, 6); +} +#endif \ No newline at end of file diff --git a/src/control/AutoPilot.h b/src/control/AutoPilot.h new file mode 100644 index 0000000..c7707ed --- /dev/null +++ b/src/control/AutoPilot.h @@ -0,0 +1,123 @@ +#pragma once +#include "Timer.h" + +class CVehicle; +struct CPathNode; + +enum eCarMission +{ + MISSION_NONE, + MISSION_CRUISE, + MISSION_RAMPLAYER_FARAWAY, + MISSION_RAMPLAYER_CLOSE, + MISSION_BLOCKPLAYER_FARAWAY, + MISSION_BLOCKPLAYER_CLOSE, + MISSION_BLOCKPLAYER_HANDBRAKESTOP, + MISSION_WAITFORDELETION, + MISSION_GOTOCOORDS, + MISSION_GOTOCOORDS_STRAIGHT, + MISSION_EMERGENCYVEHICLE_STOP, + MISSION_STOP_FOREVER, + MISSION_GOTOCOORDS_ACCURATE, + MISSION_GOTO_COORDS_STRAIGHT_ACCURATE, + MISSION_GOTOCOORDS_ASTHECROWSWIMS, + MISSION_RAMCAR_FARAWAY, + MISSION_RAMCAR_CLOSE, + MISSION_BLOCKCAR_FARAWAY, + MISSION_BLOCKCAR_CLOSE, + MISSION_BLOCKCAR_HANDBRAKESTOP, +}; + +enum eCarTempAction +{ + TEMPACT_NONE, + TEMPACT_WAIT, + TEMPACT_REVERSE, + TEMPACT_HANDBRAKETURNLEFT, + TEMPACT_HANDBRAKETURNRIGHT, + TEMPACT_HANDBRAKESTRAIGHT, + TEMPACT_TURNLEFT, + TEMPACT_TURNRIGHT, + TEMPACT_GOFORWARD, + TEMPACT_SWERVELEFT, + TEMPACT_SWERVERIGHT +}; + +enum eCarDrivingStyle +{ + DRIVINGSTYLE_STOP_FOR_CARS, + DRIVINGSTYLE_SLOW_DOWN_FOR_CARS, + DRIVINGSTYLE_AVOID_CARS, + DRIVINGSTYLE_PLOUGH_THROUGH, + DRIVINGSTYLE_STOP_FOR_CARS_IGNORE_LIGHTS +}; + +class CAutoPilot { +public: + int32 m_nCurrentRouteNode; + int32 m_nNextRouteNode; + int32 m_nPrevRouteNode; + int32 m_nTimeEnteredCurve; + int32 m_nTimeToSpendOnCurrentCurve; + uint32 m_nCurrentPathNodeInfo; + uint32 m_nNextPathNodeInfo; + uint32 m_nPreviousPathNodeInfo; + uint32 m_nAntiReverseTimer; + uint32 m_nTimeToStartMission; + int8 m_nPreviousDirection; + int8 m_nCurrentDirection; + int8 m_nNextDirection; + int8 m_nCurrentLane; + int8 m_nNextLane; + uint8 m_nDrivingStyle; + uint8 m_nCarMission; + uint8 m_nTempAction; + uint32 m_nTimeTempAction; + float m_fMaxTrafficSpeed; + uint8 m_nCruiseSpeed; + uint8 m_bSlowedDownBecauseOfCars : 1; + uint8 m_bSlowedDownBecauseOfPeds : 1; + uint8 m_bStayInCurrentLevel : 1; + uint8 m_bStayInFastLane : 1; + uint8 m_bIgnorePathfinding : 1; + CVector m_vecDestinationCoors; + CPathNode *m_aPathFindNodesInfo[NUM_PATH_NODES_IN_AUTOPILOT]; + int16 m_nPathFindNodesCount; + CVehicle *m_pTargetCar; + + CAutoPilot(void) { + m_nPrevRouteNode = 0; + m_nNextRouteNode = m_nPrevRouteNode; + m_nCurrentRouteNode = m_nNextRouteNode; + m_nTimeEnteredCurve = 0; + m_nTimeToSpendOnCurrentCurve = 1000; + m_nPreviousPathNodeInfo = 0; + m_nNextPathNodeInfo = m_nPreviousPathNodeInfo; + m_nCurrentPathNodeInfo = m_nNextPathNodeInfo; + m_nNextDirection = 1; + m_nCurrentDirection = m_nNextDirection; + m_nCurrentLane = m_nNextLane = 0; + m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + m_nCarMission = MISSION_NONE; + m_nTempAction = TEMPACT_NONE; + m_nCruiseSpeed = 10; + m_fMaxTrafficSpeed = 10.0f; + m_bSlowedDownBecauseOfPeds = false; + m_bSlowedDownBecauseOfCars = false; + m_nPathFindNodesCount = 0; + m_pTargetCar = 0; + m_nTimeToStartMission = CTimer::GetTimeInMilliseconds(); + m_nAntiReverseTimer = m_nTimeToStartMission; + m_bStayInFastLane = false; + } + + void ModifySpeed(float); + void RemoveOnePathNode(); +#ifdef COMPATIBLE_SAVES + void Save(uint8*& buf); + void Load(uint8*& buf); +#endif + +}; + +VALIDATE_SIZE(CAutoPilot, 0x70); diff --git a/src/control/Bridge.cpp b/src/control/Bridge.cpp new file mode 100644 index 0000000..e873062 --- /dev/null +++ b/src/control/Bridge.cpp @@ -0,0 +1,149 @@ +#include "common.h" + +#include "Bridge.h" +#include "Pools.h" +#include "ModelIndices.h" +#include "PathFind.h" +#include "Stats.h" + +CEntity *CBridge::pLiftRoad; +CEntity *CBridge::pLiftPart; +CEntity *CBridge::pWeight; + +int CBridge::State; +int CBridge::OldState; + +float CBridge::DefaultZLiftPart; +float CBridge::DefaultZLiftRoad; +float CBridge::DefaultZLiftWeight; + +float CBridge::OldLift; + +uint32 CBridge::TimeOfBridgeBecomingOperational; + +void CBridge::Init() +{ + FindBridgeEntities(); + OldLift = -1.0f; + if (pLiftPart && pWeight) + { + DefaultZLiftPart = pLiftPart->GetPosition().z; + DefaultZLiftWeight = pWeight->GetPosition().z; + + if (pLiftRoad) + DefaultZLiftRoad = pLiftRoad->GetPosition().z; + + ThePaths.SetLinksBridgeLights(-330.0, -230.0, -700.0, -588.0, true); + } +} + +void CBridge::Update() +{ + if (!pLiftPart || !pWeight) + return; + + OldState = State; + + float liftHeight; + + // Set bridge height and state + if (CStats::CommercialPassed) + { + if (TimeOfBridgeBecomingOperational == 0) + TimeOfBridgeBecomingOperational = CTimer::GetTimeInMilliseconds(); + + // Time remaining for bridge to become operational + // uint16, so after about a minute it overflows to 0 and the cycle repeats + uint16 timeElapsed = CTimer::GetTimeInMilliseconds() - TimeOfBridgeBecomingOperational; + + // Calculate lift part height and bridge state + if (timeElapsed < 10000) + { + State = STATE_LIFT_PART_MOVING_DOWN; + liftHeight = 25.0f - timeElapsed / 10000.0f * 25.0f; + } + else if (timeElapsed < 40000) + { + liftHeight = 0.0f; + State = STATE_LIFT_PART_IS_DOWN; + } + else if (timeElapsed < 50000) + { + liftHeight = 0.0f; + State = STATE_LIFT_PART_ABOUT_TO_MOVE_UP; + } + else if (timeElapsed < 60000) + { + State = STATE_LIFT_PART_MOVING_UP; + liftHeight = (timeElapsed - 50000) / 10000.0f * 25.0f; + } + else + { + liftHeight = 25.0f; + State = STATE_LIFT_PART_IS_UP; + } + } + else + { + liftHeight = 25.0f; + TimeOfBridgeBecomingOperational = 0; + State = STATE_BRIDGE_LOCKED; + } + + // Move bridge part + if (liftHeight != OldLift) + { + pLiftPart->GetMatrix().GetPosition().z = DefaultZLiftPart + liftHeight; + pLiftPart->GetMatrix().UpdateRW(); + pLiftPart->UpdateRwFrame(); + if (pLiftRoad) + { + pLiftRoad->GetMatrix().GetPosition().z = DefaultZLiftRoad + liftHeight; + pLiftRoad->GetMatrix().UpdateRW(); + pLiftRoad->UpdateRwFrame(); + } + pWeight->GetMatrix().GetPosition().z = DefaultZLiftWeight - liftHeight; + pWeight->GetMatrix().UpdateRW(); + pWeight->UpdateRwFrame(); + + OldLift = liftHeight; + } + + if (State == STATE_LIFT_PART_ABOUT_TO_MOVE_UP && OldState == STATE_LIFT_PART_IS_DOWN) + ThePaths.SetLinksBridgeLights(-330.0, -230.0, -700.0, -588.0, true); + else if (State == STATE_LIFT_PART_IS_DOWN && OldState == STATE_LIFT_PART_MOVING_DOWN) + ThePaths.SetLinksBridgeLights(-330.0, -230.0, -700.0, -588.0, false); +} + +bool CBridge::ShouldLightsBeFlashing() +{ + return State != STATE_LIFT_PART_IS_DOWN; +} + +void CBridge::FindBridgeEntities() +{ + pWeight = nil; + pLiftRoad = nil; + pLiftPart = nil; + + for (int i = CPools::GetBuildingPool()->GetSize()-1; i >= 0; i--) { + CBuilding* entry = CPools::GetBuildingPool()->GetSlot(i); + if (entry) + { + if (entry->GetModelIndex() == MI_BRIDGELIFT) + pLiftPart = entry; + else if (entry->GetModelIndex() == MI_BRIDGEROADSEGMENT) + pLiftRoad = entry; + else if (entry->GetModelIndex() == MI_BRIDGEWEIGHT) + pWeight = entry; + } + } +} + +bool CBridge::ThisIsABridgeObjectMovingUp(int index) +{ + if (index != MI_BRIDGEROADSEGMENT && index != MI_BRIDGELIFT) + return false; + + return State == STATE_LIFT_PART_ABOUT_TO_MOVE_UP || State == STATE_LIFT_PART_MOVING_UP; +} diff --git a/src/control/Bridge.h b/src/control/Bridge.h new file mode 100644 index 0000000..c570262 --- /dev/null +++ b/src/control/Bridge.h @@ -0,0 +1,28 @@ +#pragma once + +class CEntity; + +enum bridgeStates { + STATE_BRIDGE_LOCKED, + STATE_LIFT_PART_IS_UP, + STATE_LIFT_PART_MOVING_DOWN, + STATE_LIFT_PART_IS_DOWN, + STATE_LIFT_PART_ABOUT_TO_MOVE_UP, + STATE_LIFT_PART_MOVING_UP +}; + +class CBridge +{ +public: + static CEntity *pLiftRoad, *pLiftPart, *pWeight; + static int State, OldState; + static float DefaultZLiftPart, DefaultZLiftRoad, DefaultZLiftWeight; + static float OldLift; + static uint32 TimeOfBridgeBecomingOperational; + + static void Init(); + static void Update(); + static bool ShouldLightsBeFlashing(); + static void FindBridgeEntities(); + static bool ThisIsABridgeObjectMovingUp(int); +}; diff --git a/src/control/CarAI.cpp b/src/control/CarAI.cpp new file mode 100644 index 0000000..ffde7ab --- /dev/null +++ b/src/control/CarAI.cpp @@ -0,0 +1,666 @@ +#include "common.h" + +#include "CarAI.h" + +#include "Accident.h" +#include "AutoPilot.h" +#include "CarCtrl.h" +#include "General.h" +#include "HandlingMgr.h" +#include "ModelIndices.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "Fire.h" +#include "Pools.h" +#include "Timer.h" +#include "TrafficLights.h" +#include "Vehicle.h" +#include "World.h" +#include "ZoneCull.h" + +#define DISTANCE_TO_SWITCH_DISTANCE_GOTO 20.0f + +float CCarAI::FindSwitchDistanceClose(CVehicle* pVehicle) +{ + return 30.0f; +} + +float CCarAI::FindSwitchDistanceFarNormalVehicle(CVehicle* pVehicle) +{ + return FindSwitchDistanceClose(pVehicle) + 5.0f; +} + +float CCarAI::FindSwitchDistanceFar(CVehicle* pVehicle) +{ + if (pVehicle->bIsLawEnforcer) + return 50.0f; + return FindSwitchDistanceFarNormalVehicle(pVehicle); +} + +void CCarAI::UpdateCarAI(CVehicle* pVehicle) +{ + if (pVehicle->bIsLawEnforcer){ + if (pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKCAR_FARAWAY || + pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_FARAWAY || + pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_CLOSE || + pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE) + pVehicle->AutoPilot.m_nCruiseSpeed = FindPoliceCarSpeedForWantedLevel(pVehicle); + } + switch (pVehicle->GetStatus()){ + case STATUS_PLAYER: + case STATUS_PLAYER_PLAYBACKFROMBUFFER: + case STATUS_TRAIN_MOVING: + case STATUS_TRAIN_NOT_MOVING: + case STATUS_HELI: + case STATUS_PLANE: + case STATUS_PLAYER_REMOTE: + case STATUS_PLAYER_DISABLED: + break; + case STATUS_SIMPLE: + case STATUS_PHYSICS: + switch (pVehicle->AutoPilot.m_nCarMission) { + case MISSION_RAMPLAYER_FARAWAY: + if (FindSwitchDistanceClose(pVehicle) > (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() || + pVehicle->AutoPilot.m_bIgnorePathfinding) { + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMPLAYER_CLOSE; + if (pVehicle->UsesSiren(pVehicle->GetModelIndex())) + pVehicle->m_bSirenOrAlarm = true; + } + if (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())) { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_RAMPLAYER_CLOSE: + if (FindSwitchDistanceFar(pVehicle) >= (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() || + pVehicle->AutoPilot.m_bIgnorePathfinding) { + if (FindPlayerVehicle()) { + if (pVehicle->GetHasCollidedWith(FindPlayerVehicle())) { + if (pVehicle->AutoPilot.m_nTempAction != TEMPACT_TURNLEFT && pVehicle->AutoPilot.m_nTempAction != TEMPACT_TURNRIGHT) { + if (FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f) { + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 800; + } + else { + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 50; + } + } + } + } + if (FindPlayerVehicle() && FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f) +#ifdef FIX_BUGS + pVehicle->m_nTimeBlocked += CTimer::GetTimeStepInMilliseconds(); +#else + pVehicle->m_nTimeBlocked += 1000.0f / 60.0f * CTimer::GetTimeStep(); +#endif + else + pVehicle->m_nTimeBlocked = 0; + if (!FindPlayerVehicle() || FindPlayerVehicle()->IsUpsideDown() || + FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f && pVehicle->m_nTimeBlocked > TIME_COPS_WAIT_TO_EXIT_AFTER_STOPPING) { + if (pVehicle->bIsLawEnforcer && + (pVehicle->GetModelIndex() != MI_RHINO || pVehicle->m_randomSeed > 10000) && + (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() < 10.0f) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + if (FindPlayerPed()->m_pWanted->GetWantedLevel() <= 1) + pVehicle->m_bSirenOrAlarm = false; + } + } + } + else if (!CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, FindPlayerCoors(), true)){ + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMPLAYER_FARAWAY; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->m_nCarHornTimer = 0; + } + if (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())){ + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + + else if (pVehicle->bIsLawEnforcer) + MellowOutChaseSpeed(pVehicle); + break; + case MISSION_BLOCKPLAYER_FARAWAY: + if (FindSwitchDistanceClose(pVehicle) > (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() || + pVehicle->AutoPilot.m_bIgnorePathfinding) { + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKPLAYER_CLOSE; + if (pVehicle->UsesSiren(pVehicle->GetModelIndex())) + pVehicle->m_bSirenOrAlarm = true; + } + if (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())) { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_BLOCKPLAYER_CLOSE: + if (FindSwitchDistanceFar(pVehicle) >= (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() || + pVehicle->AutoPilot.m_bIgnorePathfinding) { + if (FindPlayerVehicle() && FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f) +#ifdef FIX_BUGS + pVehicle->m_nTimeBlocked += CTimer::GetTimeStepInMilliseconds(); +#else + pVehicle->m_nTimeBlocked += 1000.0f / 60.0f * CTimer::GetTimeStep(); +#endif + else + pVehicle->m_nTimeBlocked = 0; + if (!FindPlayerVehicle() || FindPlayerVehicle()->IsUpsideDown() || + FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f && pVehicle->m_nTimeBlocked > TIME_COPS_WAIT_TO_EXIT_AFTER_STOPPING) { + if (pVehicle->bIsLawEnforcer && + (pVehicle->GetModelIndex() != MI_RHINO || pVehicle->m_randomSeed > 10000) && + (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() < 10.0f) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + if (FindPlayerPed()->m_pWanted->GetWantedLevel() <= 1) + pVehicle->m_bSirenOrAlarm = false; + } + } + }else if (!CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, FindPlayerCoors(), true)) { + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKPLAYER_FARAWAY; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->m_nCarHornTimer = 0; + } + if (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())) { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + if (pVehicle->bIsLawEnforcer) + MellowOutChaseSpeed(pVehicle); + break; + case MISSION_GOTOCOORDS: + if ((pVehicle->AutoPilot.m_vecDestinationCoors - pVehicle->GetPosition()).Magnitude2D() < DISTANCE_TO_SWITCH_DISTANCE_GOTO || + pVehicle->AutoPilot.m_bIgnorePathfinding) + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + break; + case MISSION_GOTOCOORDS_STRAIGHT: + { + float distance = (pVehicle->AutoPilot.m_vecDestinationCoors - pVehicle->GetPosition()).Magnitude2D(); + if ((pVehicle->bIsAmbulanceOnDuty || pVehicle->bIsFireTruckOnDuty) && distance < 20.0f) + pVehicle->AutoPilot.m_nCarMission = MISSION_EMERGENCYVEHICLE_STOP; + if (distance < 5.0f){ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + } + else if (distance > FindSwitchDistanceFarNormalVehicle(pVehicle) && !pVehicle->AutoPilot.m_bIgnorePathfinding && (CTimer::GetFrameCounter() & 7) == 0){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nCarMission = (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, pVehicle->AutoPilot.m_vecDestinationCoors, true)) ? + MISSION_GOTOCOORDS_STRAIGHT : MISSION_GOTOCOORDS; + } + break; + } + case MISSION_EMERGENCYVEHICLE_STOP: + if (pVehicle->GetMoveSpeed().Magnitude2D() < 0.01f){ + if (pVehicle->bIsAmbulanceOnDuty){ + float distance = 30.0f; + if (gAccidentManager.FindNearestAccident(pVehicle->AutoPilot.m_vecDestinationCoors, &distance)){ + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_STOP_FOREVER; + }else{ + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->AutoPilot.m_nCruiseSpeed = 17; + if (pVehicle->bIsAmbulanceOnDuty){ + pVehicle->bIsAmbulanceOnDuty = false; + --CCarCtrl::NumAmbulancesOnDuty; + } + } + } + if (pVehicle->bIsFireTruckOnDuty) { + float distance = 30.0f; + if (gFireManager.FindNearestFire(pVehicle->AutoPilot.m_vecDestinationCoors, &distance)) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_STOP_FOREVER; + } + else { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->AutoPilot.m_nCruiseSpeed = 17; + if (pVehicle->bIsFireTruckOnDuty) { + pVehicle->bIsFireTruckOnDuty = false; + --CCarCtrl::NumFiretrucksOnDuty; + } + } + } + } + break; + case MISSION_GOTOCOORDS_ACCURATE: + if ((pVehicle->AutoPilot.m_vecDestinationCoors - pVehicle->GetPosition()).Magnitude2D() < 20.0f || + pVehicle->AutoPilot.m_bIgnorePathfinding) + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTO_COORDS_STRAIGHT_ACCURATE; + break; + case MISSION_GOTO_COORDS_STRAIGHT_ACCURATE: + { + float distance = (pVehicle->AutoPilot.m_vecDestinationCoors - pVehicle->GetPosition()).Magnitude2D(); + if (distance < 1.0f) { + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + } + else if (distance > FindSwitchDistanceFarNormalVehicle(pVehicle) && !pVehicle->AutoPilot.m_bIgnorePathfinding && (CTimer::GetFrameCounter() & 7) == 0) { + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nCarMission = (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, pVehicle->AutoPilot.m_vecDestinationCoors, true)) ? + MISSION_GOTO_COORDS_STRAIGHT_ACCURATE : MISSION_GOTOCOORDS_ACCURATE; + } + break; + } + case MISSION_RAMCAR_FARAWAY: + if (pVehicle->AutoPilot.m_pTargetCar){ + if ((pVehicle->GetPosition() - pVehicle->AutoPilot.m_pTargetCar->GetPosition()).Magnitude2D() < FindSwitchDistanceClose(pVehicle) || + pVehicle->AutoPilot.m_bIgnorePathfinding) + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMCAR_CLOSE; + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_RAMCAR_CLOSE: + if (pVehicle->AutoPilot.m_pTargetCar){ + if +#ifdef FIX_BUGS + (FindPlayerVehicle() == pVehicle->AutoPilot.m_pTargetCar && +#endif + (FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || pVehicle->bIsLawEnforcer && + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())) +#ifdef FIX_BUGS + ) +#endif + { + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->m_bSirenOrAlarm = false; + if (CCullZones::NoPolice()) + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + if ((pVehicle->AutoPilot.m_pTargetCar->GetPosition() - pVehicle->GetPosition()).Magnitude2D() <= FindSwitchDistanceFar(pVehicle) || + pVehicle->AutoPilot.m_bIgnorePathfinding){ + if (pVehicle->GetHasCollidedWith(pVehicle->AutoPilot.m_pTargetCar)){ + if (pVehicle->GetMoveSpeed().Magnitude() < 0.04f){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 800; + } + } + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMCAR_FARAWAY; + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + } + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_BLOCKCAR_FARAWAY: + if (pVehicle->AutoPilot.m_pTargetCar){ + if ((pVehicle->AutoPilot.m_pTargetCar->GetPosition() - pVehicle->GetPosition()).Magnitude2D() < FindSwitchDistanceClose(pVehicle) || + pVehicle->AutoPilot.m_bIgnorePathfinding){ + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKCAR_CLOSE; + if (pVehicle->UsesSiren(pVehicle->GetModelIndex())) + pVehicle->m_bSirenOrAlarm = true; + } + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + case MISSION_BLOCKCAR_CLOSE: + if (pVehicle->AutoPilot.m_pTargetCar){ + if ((pVehicle->AutoPilot.m_pTargetCar->GetPosition() - pVehicle->GetPosition()).Magnitude2D() > FindSwitchDistanceFar(pVehicle) && + !pVehicle->AutoPilot.m_bIgnorePathfinding){ + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKCAR_FARAWAY; + pVehicle->m_bSirenOrAlarm = false; + pVehicle->m_nCarHornTimer = 0; + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + } + }else{ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + break; + default: + if (pVehicle->bIsLawEnforcer && FindPlayerPed()->m_pWanted->GetWantedLevel() > 0 && !CCullZones::NoPolice()){ + if (ABS(FindPlayerCoors().x - pVehicle->GetPosition().x) > 10.0f || + ABS(FindPlayerCoors().y - pVehicle->GetPosition().y) > 10.0f){ + pVehicle->AutoPilot.m_nCruiseSpeed = FindPoliceCarSpeedForWantedLevel(pVehicle); + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->AutoPilot.m_nCarMission = + FindPoliceCarMissionForWantedLevel(); + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + }else if (pVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE){ + pVehicle->SetStatus(STATUS_PHYSICS); + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + if (FindPlayerPed()->m_pWanted->GetWantedLevel() <= 1) + pVehicle->m_bSirenOrAlarm = false; + } + } + break; + } + break; + case STATUS_ABANDONED: + case STATUS_WRECKED: + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + break; + } + float flatSpeed = pVehicle->GetMoveSpeed().MagnitudeSqr2D(); + if (flatSpeed > SQR(0.018f)){ + pVehicle->AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds(); + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + } + if (pVehicle->GetStatus() == STATUS_PHYSICS && pVehicle->AutoPilot.m_nTempAction == TEMPACT_NONE){ + if (pVehicle->AutoPilot.m_nCarMission != MISSION_NONE){ + if (pVehicle->AutoPilot.m_nCarMission != MISSION_STOP_FOREVER && + pVehicle->AutoPilot.m_nCruiseSpeed != 0 && + (pVehicle->VehicleCreatedBy != RANDOM_VEHICLE || pVehicle->AutoPilot.m_nCarMission != MISSION_CRUISE)){ + if (pVehicle->AutoPilot.m_nDrivingStyle != DRIVINGSTYLE_STOP_FOR_CARS + ) { + if (CTimer::GetTimeInMilliseconds() - pVehicle->m_nLastTimeCollided > 500) + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + if (flatSpeed < SQR(0.018f) && CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nAntiReverseTimer > 2000){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + if (pVehicle->AutoPilot.m_nCarMission != MISSION_NONE && + pVehicle->AutoPilot.m_nCarMission != MISSION_CRUISE || pVehicle->VehicleCreatedBy == MISSION_VEHICLE) + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 1500; + else + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 750; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + if (pVehicle->VehicleCreatedBy == RANDOM_VEHICLE) + pVehicle->AutoPilot.m_nDrivingStyle = Max(DRIVINGSTYLE_AVOID_CARS, pVehicle->AutoPilot.m_nDrivingStyle); + pVehicle->PlayCarHorn(); + } + } + } + } + } + if ((pVehicle->m_randomSeed & 7) == 0){ + if (CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeToStartMission > 30000 && + CTimer::GetPreviousTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeToStartMission <= 30000 && + pVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE && + !CTrafficLights::ShouldCarStopForBridge(pVehicle)){ + pVehicle->SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(pVehicle); + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 400; + } + } + if (pVehicle->GetUp().z < -0.7f){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 1000; + } + if (pVehicle->AutoPilot.m_nTempAction == TEMPACT_NONE){ + switch (pVehicle->AutoPilot.m_nCarMission){ + case MISSION_RAMPLAYER_FARAWAY: + case MISSION_RAMPLAYER_CLOSE: + case MISSION_BLOCKPLAYER_FARAWAY: + case MISSION_BLOCKPLAYER_CLOSE: + if (FindPlayerVehicle() && FindPlayerSpeed().Magnitude() > pVehicle->GetMoveSpeed().Magnitude()){ + if (FindPlayerSpeed().Magnitude() > 0.1f){ + if (DotProduct2D(FindPlayerVehicle()->GetForward(), pVehicle->GetForward()) > 0.0f){ + CVector2D dist = pVehicle->GetPosition() - FindPlayerCoors(); + CVector2D speed = FindPlayerSpeed(); + if (0.5f * dist.Magnitude() * speed.Magnitude() < DotProduct2D(dist, speed)){ + if ((FindPlayerCoors() - pVehicle->GetPosition()).Magnitude() > 12.0f){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 500; + } + } + } + } + } + break; + default: break; + } + } + if (pVehicle->pDriver && pVehicle->pDriver->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS){ + if ((pVehicle->GetPosition() - FindPlayerCoors()).Magnitude() < 15.0f){ + if (!FindPlayerVehicle() || pVehicle->GetHasCollidedWith(FindPlayerVehicle())){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 3000; + } + } + } + if (pVehicle->m_bSirenOrAlarm){ + if ((uint8)(pVehicle->m_randomSeed ^ CGeneral::GetRandomNumber()) == 0xAD) + pVehicle->m_nCarHornTimer = 45; + } +} + +void CCarAI::CarHasReasonToStop(CVehicle* pVehicle) +{ + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); +} + +float CCarAI::GetCarToGoToCoors(CVehicle* pVehicle, CVector* pTarget) +{ + if (pVehicle->AutoPilot.m_nCarMission != MISSION_GOTOCOORDS && pVehicle->AutoPilot.m_nCarMission != MISSION_GOTOCOORDS_STRAIGHT){ + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nCruiseSpeed = 20; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->AutoPilot.m_nCarMission = (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, *pTarget, false)) ? + MISSION_GOTOCOORDS_STRAIGHT : MISSION_GOTOCOORDS; + }else if (Abs(pTarget->x - pVehicle->AutoPilot.m_vecDestinationCoors.x) > 2.0f || + Abs(pTarget->y - pVehicle->AutoPilot.m_vecDestinationCoors.y) > 2.0f){ + pVehicle->AutoPilot.m_vecDestinationCoors = *pTarget; + } + return (pVehicle->GetPosition() - *pTarget).Magnitude2D(); +} + +void CCarAI::AddPoliceCarOccupants(CVehicle* pVehicle) +{ + if (pVehicle->bOccupantsHaveBeenGenerated) + return; + pVehicle->bOccupantsHaveBeenGenerated = true; + switch (pVehicle->GetModelIndex()){ + case MI_FBICAR: + case MI_ENFORCER: + pVehicle->SetUpDriver(); + for (int i = 0; i < 3; i++) + pVehicle->SetupPassenger(i); + return; + case MI_POLICE: + case MI_RHINO: + case MI_BARRACKS: + pVehicle->SetUpDriver(); + if (FindPlayerPed()->m_pWanted->GetWantedLevel() > 1) + pVehicle->SetupPassenger(0); + return; + default: + return; + } +} + +void CCarAI::AddAmbulanceOccupants(CVehicle* pVehicle) +{ + pVehicle->SetUpDriver(); + pVehicle->SetupPassenger(1); +} + +void CCarAI::AddFiretruckOccupants(CVehicle* pVehicle) +{ + pVehicle->SetUpDriver(); + pVehicle->SetupPassenger(0); +} + +void CCarAI::TellOccupantsToLeaveCar(CVehicle* pVehicle) +{ + if (pVehicle->pDriver){ + pVehicle->pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + switch (pVehicle->GetModelIndex()) { + case MI_FIRETRUCK: + case MI_FBICAR: + case MI_ENFORCER: + case MI_BARRACKS: + case MI_RHINO: + case MI_POLICE: + break; + case MI_AMBULAN: + pVehicle->pDriver->Say(SOUND_PED_LEAVE_VEHICLE); + break; + } + } + int timer = 100; + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++){ + if (pVehicle->pPassengers[i]) { + pVehicle->pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + } + } +} + +void CCarAI::TellCarToRamOtherCar(CVehicle* pVehicle, CVehicle* pTarget) +{ + pVehicle->AutoPilot.m_pTargetCar = pTarget; + pTarget->RegisterReference((CEntity**)&pVehicle->AutoPilot.m_pTargetCar); + pVehicle->AutoPilot.m_nCarMission = MISSION_RAMCAR_FARAWAY; + pVehicle->bEngineOn = true; + pVehicle->AutoPilot.m_nCruiseSpeed = Max(6, pVehicle->AutoPilot.m_nCruiseSpeed); +} + +void CCarAI::TellCarToBlockOtherCar(CVehicle* pVehicle, CVehicle* pTarget) +{ + pVehicle->AutoPilot.m_pTargetCar = pTarget; + pTarget->RegisterReference((CEntity**)&pVehicle->AutoPilot.m_pTargetCar); + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKCAR_FARAWAY; + pVehicle->bEngineOn = true; + pVehicle->AutoPilot.m_nCruiseSpeed = Max(6, pVehicle->AutoPilot.m_nCruiseSpeed); +} + +uint8 CCarAI::FindPoliceCarMissionForWantedLevel() +{ + switch (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel()){ + case 0: + case 1: return MISSION_BLOCKPLAYER_FARAWAY; + case 2: return (CGeneral::GetRandomNumber() & 3) >= 3 ? MISSION_RAMPLAYER_FARAWAY : MISSION_BLOCKPLAYER_FARAWAY; + case 3: return (CGeneral::GetRandomNumber() & 3) >= 2 ? MISSION_RAMPLAYER_FARAWAY : MISSION_BLOCKPLAYER_FARAWAY; + case 4: + case 5: + case 6: return (CGeneral::GetRandomNumber() & 3) >= 1 ? MISSION_RAMPLAYER_FARAWAY : MISSION_BLOCKPLAYER_FARAWAY; + default: return MISSION_BLOCKPLAYER_FARAWAY; + } +} + +int32 CCarAI::FindPoliceCarSpeedForWantedLevel(CVehicle* pVehicle) +{ + switch (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel()) { + case 0: return CGeneral::GetRandomNumberInRange(12, 16); + case 1: return 25; + case 2: return 34; + case 3: return GAME_SPEED_TO_CARAI_SPEED * pVehicle->pHandling->Transmission.fMaxVelocity * 0.9f; + case 4: return GAME_SPEED_TO_CARAI_SPEED * pVehicle->pHandling->Transmission.fMaxVelocity * 1.2f; + case 5: return GAME_SPEED_TO_CARAI_SPEED * pVehicle->pHandling->Transmission.fMaxVelocity * 1.25f; + case 6: return GAME_SPEED_TO_CARAI_SPEED * pVehicle->pHandling->Transmission.fMaxVelocity * 1.3f; + default: return 0; + } +} + +void CCarAI::MellowOutChaseSpeed(CVehicle* pVehicle) +{ + if (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel() == 1){ + float distanceToPlayer = (pVehicle->GetPosition() - FindPlayerCoors()).Magnitude(); + if (FindPlayerVehicle()){ + if (distanceToPlayer < 10.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 15; + else if (distanceToPlayer < 20.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 22; + else + pVehicle->AutoPilot.m_nCruiseSpeed = 25; + }else{ + if (distanceToPlayer < 20.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 5; + else if (distanceToPlayer < 40.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 13; + else + pVehicle->AutoPilot.m_nCruiseSpeed = 25; + } + }else if (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel() == 2){ + float distanceToPlayer = (pVehicle->GetPosition() - FindPlayerCoors()).Magnitude(); + if (FindPlayerVehicle()) { + if (distanceToPlayer < 10.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 27; + else if (distanceToPlayer < 20.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 30; + else + pVehicle->AutoPilot.m_nCruiseSpeed = 34; + } + else { + if (distanceToPlayer < 20.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 5; + else if (distanceToPlayer < 40.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = 18; + else + pVehicle->AutoPilot.m_nCruiseSpeed = 34; + } + } +} + +void CCarAI::MakeWayForCarWithSiren(CVehicle *pVehicle) +{ + float flatSpeed = pVehicle->GetMoveSpeed().Magnitude2D(); + if (flatSpeed < 0.1f) + return; + CVector2D forward = pVehicle->GetMoveSpeed() / flatSpeed; + float projection = flatSpeed * 45 + 20; + int i = CPools::GetVehiclePool()->GetSize(); + while (--i >= 0) { + CVehicle* vehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!vehicle) + continue; + if (!vehicle->IsCar() && !vehicle->IsBike()) + continue; + if (vehicle->GetStatus() != STATUS_SIMPLE && vehicle->GetStatus() != STATUS_PHYSICS) + continue; + if (vehicle->VehicleCreatedBy != RANDOM_VEHICLE) + continue; + if (vehicle->bIsLawEnforcer || vehicle->bIsAmbulanceOnDuty || vehicle->bIsFireTruckOnDuty) + continue; + if (vehicle == pVehicle) + continue; + if (Abs(pVehicle->GetPosition().z - vehicle->GetPosition().z) >= 5.0f) + continue; + CVector2D distance = vehicle->GetPosition() - pVehicle->GetPosition(); + if (distance.Magnitude() >= projection) + continue; + if (vehicle->GetMoveSpeed().Magnitude2D() <= 0.05f) + continue; + float correlation = DotProduct2D(forward, distance) / distance.Magnitude(); + if (correlation <= 0.0f) + continue; + if (correlation > 0.8f && DotProduct2D(forward, vehicle->GetForward()) > 0.7f){ + if (vehicle->AutoPilot.m_nTempAction != TEMPACT_SWERVELEFT && vehicle->AutoPilot.m_nTempAction != TEMPACT_SWERVERIGHT){ + vehicle->AutoPilot.m_nTempAction = (distance.x * forward.y - distance.y * forward.x > 0.0f) ? + TEMPACT_SWERVELEFT : TEMPACT_SWERVERIGHT; + vehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + } + vehicle->SetStatus(STATUS_PHYSICS); + }else{ + if (DotProduct2D(vehicle->GetMoveSpeed(), distance) < 0.0f && vehicle->AutoPilot.m_nTempAction != TEMPACT_WAIT){ + vehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + vehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + } + } + } +} diff --git a/src/control/CarAI.h b/src/control/CarAI.h new file mode 100644 index 0000000..9b731ad --- /dev/null +++ b/src/control/CarAI.h @@ -0,0 +1,26 @@ +#pragma once + +#include "AutoPilot.h" + +class CVehicle; + +class CCarAI +{ +public: + static float FindSwitchDistanceClose(CVehicle*); + static float FindSwitchDistanceFarNormalVehicle(CVehicle*); + static float FindSwitchDistanceFar(CVehicle*); + static void UpdateCarAI(CVehicle*); + static void CarHasReasonToStop(CVehicle*); + static float GetCarToGoToCoors(CVehicle*, CVector*); + static void AddPoliceCarOccupants(CVehicle*); + static void AddAmbulanceOccupants(CVehicle*); + static void AddFiretruckOccupants(CVehicle*); + static void TellOccupantsToLeaveCar(CVehicle*); + static void TellCarToRamOtherCar(CVehicle*, CVehicle*); + static void TellCarToBlockOtherCar(CVehicle*, CVehicle*); + static uint8 FindPoliceCarMissionForWantedLevel(); + static int32 FindPoliceCarSpeedForWantedLevel(CVehicle*); + static void MellowOutChaseSpeed(CVehicle*); + static void MakeWayForCarWithSiren(CVehicle *veh); +}; diff --git a/src/control/CarCtrl.cpp b/src/control/CarCtrl.cpp new file mode 100644 index 0000000..2085681 --- /dev/null +++ b/src/control/CarCtrl.cpp @@ -0,0 +1,2825 @@ +#include "common.h" + +#include "CarCtrl.h" + +#include "Accident.h" +#include "Automobile.h" +#include "Camera.h" +#include "CarAI.h" +#include "CarGen.h" +#include "Cranes.h" +#include "Curves.h" +#include "CutsceneMgr.h" +#include "Gangs.h" +#include "Garages.h" +#include "General.h" +#include "IniFile.h" +#include "ModelIndices.h" +#include "PathFind.h" +#include "Ped.h" +#include "PlayerInfo.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Pools.h" +#include "Renderer.h" +#include "RoadBlocks.h" +#include "Timer.h" +#include "TrafficLights.h" +#include "Streaming.h" +#include "VisibilityPlugins.h" +#include "Vehicle.h" +#include "Fire.h" +#include "World.h" +#include "Zones.h" + +#define DISTANCE_TO_SPAWN_ROADBLOCK_PEDS 51.0f +#define DISTANCE_TO_SCAN_FOR_DANGER 11.0f +#define SAFE_DISTANCE_TO_PED 3.0f +#define INFINITE_Z 1000000000.0f + +#define VEHICLE_HEIGHT_DIFF_TO_CONSIDER_WEAVING 4.0f +#define PED_HEIGHT_DIFF_TO_CONSIDER_WEAVING 4.0f +#define OBJECT_HEIGHT_DIFF_TO_CONSIDER_WEAVING 8.0f +#define WIDTH_COEF_TO_WEAVE_SAFELY 1.2f +#define OBJECT_WIDTH_TO_WEAVE 0.3f +#define PED_WIDTH_TO_WEAVE 0.8f + +#define PATH_DIRECTION_NONE 0 +#define PATH_DIRECTION_STRAIGHT 1 +#define PATH_DIRECTION_RIGHT 2 +#define PATH_DIRECTION_LEFT 4 + +#define ATTEMPTS_TO_FIND_NEXT_NODE 15 + +#define DISTANCE_TO_SWITCH_FROM_BLOCK_TO_STOP 5.0f +#define DISTANCE_TO_SWITCH_FROM_STOP_TO_BLOCK 10.0f +#define MAX_SPEED_TO_ACCOUNT_IN_INTERCEPTING 0.13f +#define DISTANCE_TO_NEXT_NODE_TO_CONSIDER_SLOWING_DOWN 40.0f +#define MAX_ANGLE_TO_STEER_AT_HIGH_SPEED 0.2f +#define MIN_SPEED_TO_START_LIMITING_STEER 0.45f +#define DISTANCE_TO_NEXT_NODE_TO_SELECT_NEW 5.0f +#define DISTANCE_TO_FACING_NEXT_NODE_TO_SELECT_NEW 8.0f +#define DEFAULT_MAX_STEER_ANGLE 0.5f +#define MIN_LOWERING_SPEED_COEFFICIENT 0.4f +#define MAX_ANGLE_FOR_SPEED_LIMITING 1.2f +#define MIN_ANGLE_FOR_SPEED_LIMITING 0.4f +#define MIN_ANGLE_FOR_SPEED_LIMITING_BETWEEN_NODES 0.1f +#define MIN_ANGLE_TO_APPLY_HANDBRAKE 0.7f +#define MIN_SPEED_TO_APPLY_HANDBRAKE 0.3f + +int CCarCtrl::NumLawEnforcerCars; +int CCarCtrl::NumAmbulancesOnDuty; +int CCarCtrl::NumFiretrucksOnDuty; +bool CCarCtrl::bCarsGeneratedAroundCamera; +float CCarCtrl::CarDensityMultiplier = 1.0f; +int32 CCarCtrl::NumMissionCars; +int32 CCarCtrl::NumRandomCars; +int32 CCarCtrl::NumParkedCars; +int32 CCarCtrl::NumPermanentCars; +int8 CCarCtrl::CountDownToCarsAtStart; +int32 CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS; +uint32 CCarCtrl::LastTimeLawEnforcerCreated; +uint32 CCarCtrl::LastTimeFireTruckCreated; +uint32 CCarCtrl::LastTimeAmbulanceCreated; +int32 CCarCtrl::TotalNumOfCarsOfRating[TOTAL_CUSTOM_CLASSES]; +int32 CCarCtrl::NextCarOfRating[TOTAL_CUSTOM_CLASSES]; +int32 CCarCtrl::CarArrays[TOTAL_CUSTOM_CLASSES][MAX_CAR_MODELS_IN_ARRAY]; +CVehicle* apCarsToKeep[MAX_CARS_TO_KEEP]; +uint32 aCarsToKeepTime[MAX_CARS_TO_KEEP]; + +void +CCarCtrl::GenerateRandomCars() +{ + if (CCutsceneMgr::IsRunning()) + return; + if (NumRandomCars < 30){ + if (CountDownToCarsAtStart == 0){ + GenerateOneRandomCar(); + } + else if (--CountDownToCarsAtStart == 0) { + for (int i = 0; i < 50; i++) + GenerateOneRandomCar(); + CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter = 20; + } + } + /* Approximately once per 4 seconds. */ + if ((CTimer::GetTimeInMilliseconds() & 0xFFFFF000) != (CTimer::GetPreviousTimeInMilliseconds() & 0xFFFFF000)) + GenerateEmergencyServicesCar(); +} + +void +CCarCtrl::GenerateOneRandomCar() +{ + static int32 unk = 0; + CPlayerInfo* pPlayer = &CWorld::Players[CWorld::PlayerInFocus]; + CVector vecTargetPos = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + CVector2D vecPlayerSpeed = FindPlayerSpeed(); + CZoneInfo zone; + CTheZones::GetZoneInfoForTimeOfDay(&vecTargetPos, &zone); + pPlayer->m_nTrafficMultiplier = pPlayer->m_fRoadDensity * zone.carDensity; + if (NumRandomCars >= pPlayer->m_nTrafficMultiplier * CarDensityMultiplier * CIniFile::CarNumberMultiplier) + return; + if (NumFiretrucksOnDuty + NumAmbulancesOnDuty + NumParkedCars + NumMissionCars + NumLawEnforcerCars + NumRandomCars >= MaxNumberOfCarsInUse) + return; + CWanted* pWanted = pPlayer->m_pPed->m_pWanted; + int carClass; + int carModel; + if (pWanted->GetWantedLevel() > 1 && NumLawEnforcerCars < pWanted->m_MaximumLawEnforcerVehicles && + pWanted->m_CurrentCops < pWanted->m_MaxCops && ( + pWanted->GetWantedLevel() > 3 || + pWanted->GetWantedLevel() > 2 && CTimer::GetTimeInMilliseconds() > LastTimeLawEnforcerCreated + 5000 || + pWanted->GetWantedLevel() > 1 && CTimer::GetTimeInMilliseconds() > LastTimeLawEnforcerCreated + 8000)) { + /* Last pWanted->GetWantedLevel() > 1 is unnecessary but I added it for better readability. */ + /* Wouldn't be surprised it was there originally but was optimized out. */ + carClass = COPS; + carModel = ChoosePoliceCarModel(); + }else{ + carModel = ChooseModel(&zone, &vecTargetPos, &carClass); + if (carClass == COPS && pWanted->GetWantedLevel() >= 1) + /* All cop spawns with wanted level are handled by condition above. */ + /* In particular it means that cop cars never spawn if player has wanted level of 1. */ + return; + } + float frontX, frontY; + float preferredDistance, angleLimit; + bool invertAngleLimitTest; + CVector spawnPosition; + int32 curNodeId, nextNodeId; + float positionBetweenNodes; + bool testForCollision; + CVehicle* pPlayerVehicle = FindPlayerVehicle(); + CVector2D vecPlayerVehicleSpeed; + float fPlayerVehicleSpeed; + if (pPlayerVehicle) { + vecPlayerVehicleSpeed = FindPlayerVehicle()->GetMoveSpeed(); + fPlayerVehicleSpeed = vecPlayerVehicleSpeed.Magnitude(); + } + if (TheCamera.GetForward().z < -0.9f){ + /* Player uses topdown camera. */ + /* Spawn essentially anywhere. */ + frontX = frontY = 0.707f; /* 45 degrees */ + angleLimit = -1.0f; + invertAngleLimitTest = true; + preferredDistance = 40.0f; + /* BUG: testForCollision not initialized in original game. */ + testForCollision = false; + }else if (!pPlayerVehicle){ + /* Player is not in vehicle. */ + testForCollision = true; + frontX = TheCamera.CamFrontXNorm; + frontY = TheCamera.CamFrontYNorm; + switch (CTimer::GetFrameCounter() & 1) { + case 0: + /* Spawn a vehicle relatively far away from player. */ + /* Forward to his current direction (camera direction). */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 1: + /* Spawn a vehicle close to player to his side. */ + /* Kinda not within camera angle. */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = false; + preferredDistance = 40.0f; + break; + } + }else if (fPlayerVehicleSpeed > 0.4f){ /* 72 km/h */ + /* Player is moving fast in vehicle */ + /* Prefer spawning vehicles very far away from him. */ + frontX = vecPlayerVehicleSpeed.x / fPlayerVehicleSpeed; + frontY = vecPlayerVehicleSpeed.y / fPlayerVehicleSpeed; + testForCollision = false; + switch (CTimer::GetFrameCounter() & 3) { + case 0: + case 1: + /* Spawn a vehicle in a very narrow gap in front of a player */ + angleLimit = 0.85f; /* approx 30 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 2: + /* Spawn a vehicle relatively far away from player. */ + /* Forward to his current direction (camera direction). */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 3: + /* Spawn a vehicle close to player to his side. */ + /* Kinda not within camera angle. */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = false; + preferredDistance = 40.0f; + break; + } + }else if (fPlayerVehicleSpeed > 0.1f){ /* 18 km/h */ + /* Player is moving moderately fast in vehicle */ + /* Spawn more vehicles to player's side. */ + frontX = vecPlayerVehicleSpeed.x / fPlayerVehicleSpeed; + frontY = vecPlayerVehicleSpeed.y / fPlayerVehicleSpeed; + testForCollision = false; + switch (CTimer::GetFrameCounter() & 3) { + case 0: + /* Spawn a vehicle in a very narrow gap in front of a player */ + angleLimit = 0.85f; /* approx 30 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 1: + /* Spawn a vehicle relatively far away from player. */ + /* Forward to his current direction (camera direction). */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 2: + case 3: + /* Spawn a vehicle close to player to his side. */ + /* Kinda not within camera angle. */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = false; + preferredDistance = 40.0f; + break; + } + }else{ + /* Player is in vehicle but moving very slow. */ + /* Then use camera direction instead of vehicle direction. */ + testForCollision = true; + frontX = TheCamera.CamFrontXNorm; + frontY = TheCamera.CamFrontYNorm; + switch (CTimer::GetFrameCounter() & 1) { + case 0: + /* Spawn a vehicle relatively far away from player. */ + /* Forward to his current direction (camera direction). */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = true; + preferredDistance = 120.0f * TheCamera.GenerationDistMultiplier; + break; + case 1: + /* Spawn a vehicle close to player to his side. */ + /* Kinda not within camera angle. */ + angleLimit = 0.707f; /* 45 degrees */ + invertAngleLimitTest = false; + preferredDistance = 40.0f; + break; + } + } + if (!ThePaths.NewGenerateCarCreationCoors(vecTargetPos.x, vecTargetPos.y, frontX, frontY, + preferredDistance, angleLimit, invertAngleLimitTest, &spawnPosition, &curNodeId, &nextNodeId, + &positionBetweenNodes, carClass == COPS && pWanted->GetWantedLevel() >= 1)) + return; + int16 colliding; + CWorld::FindObjectsKindaColliding(spawnPosition, 10.0f, true, &colliding, 2, nil, false, true, true, false, false); + if (colliding) + /* If something is already present in spawn position, do not create vehicle*/ + return; + if (!ThePaths.TestCoorsCloseness(vecTargetPos, false, spawnPosition)) + /* Testing if spawn position can reach target position via valid path. */ + return; + int16 idInNode = 0; + CPathNode* pCurNode = &ThePaths.m_pathNodes[curNodeId]; + CPathNode* pNextNode = &ThePaths.m_pathNodes[nextNodeId]; + while (idInNode < pCurNode->numLinks && + ThePaths.ConnectedNode(idInNode + pCurNode->firstLink) != nextNodeId) + idInNode++; + int16 connectionId = ThePaths.m_carPathConnections[idInNode + pCurNode->firstLink]; + CCarPathLink* pPathLink = &ThePaths.m_carPathLinks[connectionId]; + int16 lanesOnCurrentRoad = pPathLink->pathNodeIndex == nextNodeId ? pPathLink->numLeftLanes : pPathLink->numRightLanes; + CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(carModel); + if (lanesOnCurrentRoad == 0 || pModelInfo->m_vehicleType == VEHICLE_TYPE_BIKE) + /* Not spawning vehicle if road is one way and intended direction is opposide to that way. */ + /* Also not spawning bikes but they don't exist in final game. */ + return; + CAutomobile* pVehicle = new CAutomobile(carModel, RANDOM_VEHICLE); + pVehicle->AutoPilot.m_nPrevRouteNode = 0; + pVehicle->AutoPilot.m_nCurrentRouteNode = curNodeId; + pVehicle->AutoPilot.m_nNextRouteNode = nextNodeId; + switch (carClass) { + case POOR: + case RICH: + case EXEC: + case WORKER: + case SPECIAL: + case BIG: + case TAXI: + case MAFIA: + case TRIAD: + case DIABLO: + case YAKUZA: + case YARDIE: + case COLOMB: + case NINES: + case GANG8: + case GANG9: + { + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(9, 14); + if (carClass == EXEC) + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(12, 18); + else if (carClass == POOR || carClass == SPECIAL) + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(7, 10); + CVehicleModelInfo* pVehicleInfo = pVehicle->GetModelInfo(); + if (pVehicleInfo->GetColModel()->boundingBox.max.y - pVehicle->GetModelInfo()->GetColModel()->boundingBox.min.y > 10.0f || carClass == BIG) { + pVehicle->AutoPilot.m_nCruiseSpeed *= 3; + pVehicle->AutoPilot.m_nCruiseSpeed /= 4; + } + pVehicle->AutoPilot.m_fMaxTrafficSpeed = pVehicle->AutoPilot.m_nCruiseSpeed; + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + break; + } + case COPS: + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + if (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel() != 0){ + pVehicle->AutoPilot.m_nCruiseSpeed = CCarAI::FindPoliceCarSpeedForWantedLevel(pVehicle); + pVehicle->AutoPilot.m_fMaxTrafficSpeed = pVehicle->AutoPilot.m_nCruiseSpeed / 2; + pVehicle->AutoPilot.m_nCarMission = CCarAI::FindPoliceCarMissionForWantedLevel(); + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + }else{ + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(12, 16); + pVehicle->AutoPilot.m_fMaxTrafficSpeed = pVehicle->AutoPilot.m_nCruiseSpeed; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + } + if (carModel == MI_FBICAR){ + pVehicle->m_currentColour1 = 0; + pVehicle->m_currentColour2 = 0; + /* FBI cars are gray in carcols, but we want them black if they going after player. */ + } + default: + break; + } + if (pVehicle && pVehicle->GetModelIndex() == MI_MRWHOOP) + pVehicle->m_bSirenOrAlarm = true; + pVehicle->AutoPilot.m_nNextPathNodeInfo = connectionId; + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane = CGeneral::GetRandomNumber() % lanesOnCurrentRoad; + CColBox* boundingBox = &CModelInfo::GetColModel(pVehicle->GetModelIndex())->boundingBox; + float carLength = 1.0f + (boundingBox->max.y - boundingBox->min.y) / 2; + float distanceBetweenNodes = (pCurNode->GetPosition() - pNextNode->GetPosition()).Magnitude2D(); + /* If car is so long that it doesn't fit between two car nodes, place it directly in the middle. */ + /* Otherwise put it at least in a way that full vehicle length fits between two nodes. */ + if (distanceBetweenNodes / 2 < carLength) + positionBetweenNodes = 0.5f; + else + positionBetweenNodes = Min(1.0f - carLength / distanceBetweenNodes, Max(carLength / distanceBetweenNodes, positionBetweenNodes)); + pVehicle->AutoPilot.m_nNextDirection = (curNodeId >= nextNodeId) ? 1 : -1; + if (pCurNode->numLinks == 1){ + /* Do not create vehicle if there is nowhere to go. */ + delete pVehicle; + return; + } + int16 nextConnection = pVehicle->AutoPilot.m_nNextPathNodeInfo; + int16 newLink; + while (nextConnection == pVehicle->AutoPilot.m_nNextPathNodeInfo){ + newLink = CGeneral::GetRandomNumber() % pCurNode->numLinks; + nextConnection = ThePaths.m_carPathConnections[newLink + pCurNode->firstLink]; + } + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = nextConnection; + pVehicle->AutoPilot.m_nCurrentDirection = (ThePaths.ConnectedNode(newLink + pCurNode->firstLink) >= curNodeId) ? 1 : -1; + CVector2D vecBetweenNodes = pNextNode->GetPosition() - pCurNode->GetPosition(); + float forwardX, forwardY; + float distBetweenNodes = vecBetweenNodes.Magnitude(); + if (distanceBetweenNodes == 0.0f){ + forwardX = 1.0f; + forwardY = 0.0f; + }else{ + forwardX = vecBetweenNodes.x / distBetweenNodes; + forwardY = vecBetweenNodes.y / distBetweenNodes; + } + /* I think the following might be some form of SetRotateZOnly. */ + /* Setting up direction between two car nodes. */ + pVehicle->GetForward() = CVector(forwardX, forwardY, 0.0f); + pVehicle->GetRight() = CVector(forwardY, -forwardX, 0.0f); + pVehicle->GetUp() = CVector(0.0f, 0.0f, 1.0f); + + float currentPathLinkForwardX = pVehicle->AutoPilot.m_nCurrentDirection * ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo].GetDirX(); + float currentPathLinkForwardY = pVehicle->AutoPilot.m_nCurrentDirection * ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo].GetDirY(); + float nextPathLinkForwardX = pVehicle->AutoPilot.m_nNextDirection * ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo].GetDirX(); + float nextPathLinkForwardY = pVehicle->AutoPilot.m_nNextDirection * ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo].GetDirY(); + +#ifdef FIX_BUGS + CCarPathLink* pCurrentLink; + CCarPathLink* pNextLink; + CVector positionOnCurrentLinkIncludingLane; + CVector positionOnNextLinkIncludingLane; + float directionCurrentLinkX; + float directionCurrentLinkY; + float directionNextLinkX; + float directionNextLinkY; + if (positionBetweenNodes < 0.5f) { + pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + positionOnCurrentLinkIncludingLane = CVector( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + positionOnNextLinkIncludingLane = CVector( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + directionCurrentLinkX = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + directionCurrentLinkY = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + pVehicle->AutoPilot.m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - + (uint32)((0.5f + positionBetweenNodes) * pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); + } + else { + PickNextNodeRandomly(pVehicle); + pVehicle->AutoPilot.m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - + (uint32)((positionBetweenNodes - 0.5f) * pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); + + pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + positionOnCurrentLinkIncludingLane = CVector( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + positionOnNextLinkIncludingLane = CVector( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + directionCurrentLinkX = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + directionCurrentLinkY = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + } +#else + + CCarPathLink* pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + CVector positionOnCurrentLinkIncludingLane( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + float directionCurrentLinkX = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionCurrentLinkY = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + pVehicle->AutoPilot.m_nTimeEnteredCurve = CTimer::GetTimeInMilliseconds() - + (0.5f + positionBetweenNodes) * pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; +#endif + CVector directionCurrentLink(directionCurrentLinkX, directionCurrentLinkY, 0.0f); + CVector directionNextLink(directionNextLinkX, directionNextLinkY, 0.0f); + CVector positionIncludingCurve; + CVector directionIncludingCurve; + CCurves::CalcCurvePoint( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + &directionCurrentLink, + &directionNextLink, + GetPositionAlongCurrentCurve(pVehicle), + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve, + &positionIncludingCurve, + &directionIncludingCurve + ); + CVector vectorBetweenNodes = pCurNode->GetPosition() - pNextNode->GetPosition(); + CVector finalPosition = positionIncludingCurve + vectorBetweenNodes * 2.0f / vectorBetweenNodes.Magnitude(); + finalPosition.z = positionBetweenNodes * pNextNode->GetZ() + + (1.0f - positionBetweenNodes) * pCurNode->GetZ(); + float groundZ = INFINITE_Z; + CColPoint colPoint; + CEntity* pEntity; + if (CWorld::ProcessVerticalLine(finalPosition, 1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil)) + groundZ = colPoint.point.z; + if (CWorld::ProcessVerticalLine(finalPosition, -1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil)){ + if (ABS(colPoint.point.z - finalPosition.z) < ABS(groundZ - finalPosition.z)) + groundZ = colPoint.point.z; + } + if (groundZ == INFINITE_Z || ABS(groundZ - finalPosition.z) > 7.0f) { + /* Failed to find ground or too far from expected position. */ + delete pVehicle; + return; + } + finalPosition.z = groundZ + pVehicle->GetHeightAboveRoad(); + pVehicle->SetPosition(finalPosition); + pVehicle->SetMoveSpeed(directionIncludingCurve / GAME_SPEED_TO_CARAI_SPEED); + CVector2D speedDifferenceWithTarget = (CVector2D)pVehicle->GetMoveSpeed() - vecPlayerSpeed; + CVector2D distanceToTarget = positionIncludingCurve - vecTargetPos; + switch (carClass) { + case POOR: + case RICH: + case EXEC: + case WORKER: + case SPECIAL: + case BIG: + case TAXI: + case MAFIA: + case TRIAD: + case DIABLO: + case YAKUZA: + case YARDIE: + case COLOMB: + case NINES: + case GANG8: + case GANG9: + pVehicle->SetStatus(STATUS_SIMPLE); + break; + case COPS: + pVehicle->SetStatus((pVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE) ? STATUS_SIMPLE : STATUS_PHYSICS); + pVehicle->ChangeLawEnforcerState(1); + break; + default: + break; + } + CVisibilityPlugins::SetClumpAlpha(pVehicle->GetClump(), 0); + if (!pVehicle->GetIsOnScreen()){ + if ((vecTargetPos - pVehicle->GetPosition()).Magnitude2D() > 50.0f) { + /* Too far away cars that are not visible aren't needed. */ + delete pVehicle; + return; + } + }else if((vecTargetPos - pVehicle->GetPosition()).Magnitude2D() > TheCamera.GenerationDistMultiplier * 130.0f || + (vecTargetPos - pVehicle->GetPosition()).Magnitude2D() < TheCamera.GenerationDistMultiplier * 110.0f){ + delete pVehicle; + return; + }else if((TheCamera.GetPosition() - pVehicle->GetPosition()).Magnitude2D() < 90.0f * TheCamera.GenerationDistMultiplier){ + delete pVehicle; + return; + } + CVehicleModelInfo* pVehicleModel = pVehicle->GetModelInfo(); + float radiusToTest = pVehicleModel->GetColModel()->boundingSphere.radius; + if (testForCollision){ + CWorld::FindObjectsKindaColliding(pVehicle->GetPosition(), radiusToTest + 20.0f, true, &colliding, 2, nil, false, true, false, false, false); + if (colliding){ + delete pVehicle; + return; + } + } + CWorld::FindObjectsKindaColliding(pVehicle->GetPosition(), radiusToTest, true, &colliding, 2, nil, false, true, false, false, false); + if (colliding){ + delete pVehicle; + return; + } + if (speedDifferenceWithTarget.x * distanceToTarget.x + + speedDifferenceWithTarget.y * distanceToTarget.y >= 0.0f){ + delete pVehicle; + return; + } + pVehicleModel->AvoidSameVehicleColour(&pVehicle->m_currentColour1, &pVehicle->m_currentColour2); + CWorld::Add(pVehicle); + if (carClass == COPS) + CCarAI::AddPoliceCarOccupants(pVehicle); + else + pVehicle->SetUpDriver(); + if ((CGeneral::GetRandomNumber() & 0x3F) == 0){ /* 1/64 probability */ + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle->AutoPilot.m_nCruiseSpeed += 10; + } + if (carClass == COPS) + LastTimeLawEnforcerCreated = CTimer::GetTimeInMilliseconds(); +} + +int32 +CCarCtrl::ChooseModel(CZoneInfo* pZone, CVector* pPos, int* pClass) { + int32 model = -1; + while (model == -1 || !CStreaming::HasModelLoaded(model)){ + int rnd = CGeneral::GetRandomNumberInRange(0, 1000); + if (rnd < pZone->carThreshold[0]) + model = CCarCtrl::ChooseCarModel((*pClass = POOR)); + else if (rnd < pZone->carThreshold[1]) + model = CCarCtrl::ChooseCarModel((*pClass = RICH)); + else if (rnd < pZone->carThreshold[2]) + model = CCarCtrl::ChooseCarModel((*pClass = EXEC)); + else if (rnd < pZone->carThreshold[3]) + model = CCarCtrl::ChooseCarModel((*pClass = WORKER)); + else if (rnd < pZone->carThreshold[4]) + model = CCarCtrl::ChooseCarModel((*pClass = SPECIAL)); + else if (rnd < pZone->carThreshold[5]) + model = CCarCtrl::ChooseCarModel((*pClass = BIG)); + else if (rnd < pZone->copThreshold) + *pClass = COPS, model = CCarCtrl::ChoosePoliceCarModel(); + else if (rnd < pZone->gangThreshold[0]) + model = CCarCtrl::ChooseGangCarModel((*pClass = MAFIA) - MAFIA); + else if (rnd < pZone->gangThreshold[1]) + model = CCarCtrl::ChooseGangCarModel((*pClass = TRIAD) - MAFIA); + else if (rnd < pZone->gangThreshold[2]) + model = CCarCtrl::ChooseGangCarModel((*pClass = DIABLO) - MAFIA); + else if (rnd < pZone->gangThreshold[3]) + model = CCarCtrl::ChooseGangCarModel((*pClass = YAKUZA) - MAFIA); + else if (rnd < pZone->gangThreshold[4]) + model = CCarCtrl::ChooseGangCarModel((*pClass = YARDIE) - MAFIA); + else if (rnd < pZone->gangThreshold[5]) + model = CCarCtrl::ChooseGangCarModel((*pClass = COLOMB) - MAFIA); + else if (rnd < pZone->gangThreshold[6]) + model = CCarCtrl::ChooseGangCarModel((*pClass = NINES) - MAFIA); + else if (rnd < pZone->gangThreshold[7]) + model = CCarCtrl::ChooseGangCarModel((*pClass = GANG8) - MAFIA); + else if (rnd < pZone->gangThreshold[8]) + model = CCarCtrl::ChooseGangCarModel((*pClass = GANG9) - MAFIA); + else + model = CCarCtrl::ChooseCarModel((*pClass = TAXI)); + } + return model; +} + +int32 +CCarCtrl::ChooseCarModel(int32 vehclass) +{ + int32 model = -1; + switch (vehclass) { + case POOR: + case RICH: + case EXEC: + case WORKER: + case SPECIAL: + case BIG: + case TAXI: + { + if (TotalNumOfCarsOfRating[vehclass] == 0) + debug("ChooseCarModel : No cars of type %d have been declared\n", vehclass); + model = CarArrays[vehclass][NextCarOfRating[vehclass]]; + int32 total = TotalNumOfCarsOfRating[vehclass]; + NextCarOfRating[vehclass] += CGeneral::GetRandomNumberInRange(1, total); + while (NextCarOfRating[vehclass] >= total) + NextCarOfRating[vehclass] -= total; + //NextCarOfRating[vehclass] %= total; + TotalNumOfCarsOfRating[vehclass] = total; /* why... */ + } + default: + break; + } + return model; +} + +int32 +CCarCtrl::ChoosePoliceCarModel(void) +{ + if (FindPlayerPed()->m_pWanted->AreSwatRequired() && + CStreaming::HasModelLoaded(MI_ENFORCER) && + CStreaming::HasModelLoaded(MI_POLICE)) + return ((CGeneral::GetRandomNumber() & 0xF) == 0) ? MI_ENFORCER : MI_POLICE; + if (FindPlayerPed()->m_pWanted->AreFbiRequired() && + CStreaming::HasModelLoaded(MI_FBICAR) && + CStreaming::HasModelLoaded(MI_FBI)) + return MI_FBICAR; + if (FindPlayerPed()->m_pWanted->AreArmyRequired() && + CStreaming::HasModelLoaded(MI_RHINO) && + CStreaming::HasModelLoaded(MI_BARRACKS) && + CStreaming::HasModelLoaded(MI_ARMY)) + return CGeneral::GetRandomTrueFalse() ? MI_BARRACKS : MI_RHINO; + return MI_POLICE; +} + +int32 +CCarCtrl::ChooseGangCarModel(int32 gang) +{ + if (CStreaming::HasModelLoaded(MI_GANG01 + 2 * gang) && + CStreaming::HasModelLoaded(MI_GANG02 + 2 * gang)) + return CGangs::GetGangVehicleModel(gang); + return -1; +} + +void +CCarCtrl::AddToCarArray(int32 id, int32 vehclass) +{ + CarArrays[vehclass][TotalNumOfCarsOfRating[vehclass]++] = id; +} + +void +CCarCtrl::RemoveDistantCars() +{ + for (int i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + PossiblyRemoveVehicle(pVehicle); + if (pVehicle->bCreateRoadBlockPeds){ + if ((pVehicle->GetPosition() - FindPlayerCentreOfWorld(CWorld::PlayerInFocus)).Magnitude2D() < DISTANCE_TO_SPAWN_ROADBLOCK_PEDS) { + CRoadBlocks::GenerateRoadBlockCopsForCar(pVehicle, pVehicle->m_nRoadblockType, pVehicle->m_nRoadblockNode); + pVehicle->bCreateRoadBlockPeds = false; + } + } + } +} + +void +CCarCtrl::PossiblyRemoveVehicle(CVehicle* pVehicle) +{ +#ifdef FIX_BUGS + if (pVehicle->bIsLocked) + return; +#endif + CVector vecPlayerPos = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + /* BUG: this variable is initialized only in if-block below but can be used outside of it. */ + if (!IsThisVehicleInteresting(pVehicle) && !pVehicle->bIsLocked && + pVehicle->CanBeDeleted() && !CCranes::IsThisCarBeingTargettedByAnyCrane(pVehicle)){ + if (pVehicle->bFadeOut && CVisibilityPlugins::GetClumpAlpha(pVehicle->GetClump()) == 0){ + CWorld::Remove(pVehicle); + delete pVehicle; + return; + } + float distanceToPlayer = (pVehicle->GetPosition() - vecPlayerPos).Magnitude2D(); + float threshold = 50.0f; +#ifndef EXTENDED_OFFSCREEN_DESPAWN_RANGE + if (pVehicle->GetIsOnScreen() || + TheCamera.Cams[TheCamera.ActiveCam].LookingLeft || + TheCamera.Cams[TheCamera.ActiveCam].LookingRight || + TheCamera.Cams[TheCamera.ActiveCam].LookingBehind || + TheCamera.GetLookDirection() == 0 || + pVehicle->VehicleCreatedBy == PARKED_VEHICLE || + pVehicle->GetModelIndex() == MI_AMBULAN || + pVehicle->GetModelIndex() == MI_FIRETRUCK || + pVehicle->bIsLawEnforcer || + pVehicle->bIsCarParkVehicle + ) +#endif + { + threshold = 130.0f * TheCamera.GenerationDistMultiplier; + } + if (pVehicle->bExtendedRange) + threshold *= 1.5f; + if (distanceToPlayer > threshold && !CGarages::IsPointWithinHideOutGarage(pVehicle->GetPosition())){ + if (pVehicle->GetIsOnScreen() && CRenderer::IsEntityCullZoneVisible(pVehicle)) { + pVehicle->bFadeOut = true; + }else{ + CWorld::Remove(pVehicle); + delete pVehicle; + } + return; + } + } + if ((pVehicle->GetStatus() == STATUS_SIMPLE || pVehicle->GetStatus() == STATUS_PHYSICS && pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_STOP_FOR_CARS) && + CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeToStartMission > 5000 && + !pVehicle->GetIsOnScreen() && + (pVehicle->GetPosition() - vecPlayerPos).Magnitude2D() > 25.0f && + !IsThisVehicleInteresting(pVehicle) && + !pVehicle->bIsLocked && + pVehicle->CanBeDeleted() && + !CTrafficLights::ShouldCarStopForLight(pVehicle, true) && + !CTrafficLights::ShouldCarStopForBridge(pVehicle) && + !CGarages::IsPointWithinHideOutGarage(pVehicle->GetPosition())){ + CWorld::Remove(pVehicle); + delete pVehicle; + return; + } + if (pVehicle->GetStatus() == STATUS_WRECKED) { + if (pVehicle->m_nTimeOfDeath != 0) { + if (CTimer::GetTimeInMilliseconds() > pVehicle->m_nTimeOfDeath + 60000 && + !(pVehicle->GetIsOnScreen() && CRenderer::IsEntityCullZoneVisible(pVehicle))) { + if ((pVehicle->GetPosition() - vecPlayerPos).MagnitudeSqr() > SQR(7.5f)) { + if (!CGarages::IsPointWithinHideOutGarage(pVehicle->GetPosition())) { + CWorld::Remove(pVehicle); + delete pVehicle; + } + } + } + } + } +} + +int32 +CCarCtrl::CountCarsOfType(int32 mi) +{ + int32 total = 0; + for (int i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (pVehicle->GetModelIndex() == mi) + total++; + } + return total; +} + +void +CCarCtrl::UpdateCarOnRails(CVehicle* pVehicle) +{ + if (pVehicle->AutoPilot.m_nTempAction == TEMPACT_WAIT){ + pVehicle->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pVehicle->AutoPilot.ModifySpeed(0.0f); + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTempAction){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + pVehicle->AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds(); + } + return; + } + SlowCarOnRailsDownForTrafficAndLights(pVehicle); + if (pVehicle->AutoPilot.m_nTimeEnteredCurve + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve <= CTimer::GetTimeInMilliseconds()) + PickNextNodeAccordingStrategy(pVehicle); + if (pVehicle->GetStatus() == STATUS_PHYSICS) + return; + CCarPathLink* pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + float currentPathLinkForwardX = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float currentPathLinkForwardY = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float nextPathLinkForwardX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float nextPathLinkForwardY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + CVector positionOnCurrentLinkIncludingLane( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + CVector directionCurrentLink(currentPathLinkForwardX, currentPathLinkForwardY, 0.0f); + CVector directionNextLink(nextPathLinkForwardX, nextPathLinkForwardY, 0.0f); + CVector positionIncludingCurve; + CVector directionIncludingCurve; + CCurves::CalcCurvePoint( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + &directionCurrentLink, + &directionNextLink, + GetPositionAlongCurrentCurve(pVehicle), + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve, + &positionIncludingCurve, + &directionIncludingCurve + ); + positionIncludingCurve.z = 15.0f; + DragCarToPoint(pVehicle, &positionIncludingCurve); + pVehicle->SetMoveSpeed(directionIncludingCurve / GAME_SPEED_TO_CARAI_SPEED); +} + +float +CCarCtrl::FindMaximumSpeedForThisCarInTraffic(CVehicle* pVehicle) +{ + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_AVOID_CARS || + pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_PLOUGH_THROUGH) + return pVehicle->AutoPilot.m_nCruiseSpeed; + float left = pVehicle->GetPosition().x - DISTANCE_TO_SCAN_FOR_DANGER; + float right = pVehicle->GetPosition().x + DISTANCE_TO_SCAN_FOR_DANGER; + float top = pVehicle->GetPosition().y - DISTANCE_TO_SCAN_FOR_DANGER; + float bottom = pVehicle->GetPosition().y + DISTANCE_TO_SCAN_FOR_DANGER; + int xstart = Max(0, CWorld::GetSectorIndexX(left)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(right)); + int ystart = Max(0, CWorld::GetSectorIndexY(top)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(bottom)); + assert(xstart <= xend); + assert(ystart <= yend); + + float maxSpeed = pVehicle->AutoPilot.m_nCruiseSpeed; + + CWorld::AdvanceCurrentScanCode(); + + for (int y = ystart; y <= yend; y++){ + for (int x = xstart; x <= xend; x++){ + CSector* s = CWorld::GetSector(x, y); + SlowCarDownForCarsSectorList(s->m_lists[ENTITYLIST_VEHICLES], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + SlowCarDownForCarsSectorList(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS_OVERLAP], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + } + } + pVehicle->bWarnedPeds = true; + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_STOP_FOR_CARS) + return maxSpeed; + return (maxSpeed + pVehicle->AutoPilot.m_nCruiseSpeed) / 2; +} + +void +CCarCtrl::ScanForPedDanger(CVehicle* pVehicle) +{ + bool storedSlowDownFlag = pVehicle->AutoPilot.m_bSlowedDownBecauseOfPeds; + float left = pVehicle->GetPosition().x - DISTANCE_TO_SCAN_FOR_DANGER; + float right = pVehicle->GetPosition().x + DISTANCE_TO_SCAN_FOR_DANGER; + float top = pVehicle->GetPosition().y - DISTANCE_TO_SCAN_FOR_DANGER; + float bottom = pVehicle->GetPosition().y + DISTANCE_TO_SCAN_FOR_DANGER; + int xstart = Max(0, CWorld::GetSectorIndexX(left)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(right)); + int ystart = Max(0, CWorld::GetSectorIndexY(top)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(bottom)); + assert(xstart <= xend); + assert(ystart <= yend); + + float maxSpeed = pVehicle->AutoPilot.m_nCruiseSpeed; + + CWorld::AdvanceCurrentScanCode(); + + for (int y = ystart; y <= yend; y++) { + for (int x = xstart; x <= xend; x++) { + CSector* s = CWorld::GetSector(x, y); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS_OVERLAP], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.m_nCruiseSpeed); + } + } + pVehicle->bWarnedPeds = true; + pVehicle->AutoPilot.m_bSlowedDownBecauseOfPeds = storedSlowDownFlag; +} + +void +CCarCtrl::SlowCarOnRailsDownForTrafficAndLights(CVehicle* pVehicle) +{ + float maxSpeed; + if (CTrafficLights::ShouldCarStopForLight(pVehicle, false) || CTrafficLights::ShouldCarStopForBridge(pVehicle)){ + CCarAI::CarHasReasonToStop(pVehicle); + maxSpeed = 0.0f; + }else{ + maxSpeed = FindMaximumSpeedForThisCarInTraffic(pVehicle); + } + float curSpeed = pVehicle->AutoPilot.m_fMaxTrafficSpeed; + if (maxSpeed >= curSpeed){ + if (maxSpeed > curSpeed) + pVehicle->AutoPilot.ModifySpeed(Min(maxSpeed, curSpeed + 0.05f * CTimer::GetTimeStep())); + }else if (curSpeed != 0.0f) { + if (curSpeed < 0.1f) + pVehicle->AutoPilot.ModifySpeed(0.0f); + else + pVehicle->AutoPilot.ModifySpeed(Max(maxSpeed, curSpeed - 0.5f * CTimer::GetTimeStep())); + } +} + +void CCarCtrl::SlowCarDownForPedsSectorList(CPtrList& lst, CVehicle* pVehicle, float x_inf, float y_inf, float x_sup, float y_sup, float* pSpeed, float curSpeed) +{ + float frontOffset = pVehicle->GetModelInfo()->GetColModel()->boundingBox.max.y; + float frontSafe = frontOffset + SAFE_DISTANCE_TO_PED; + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next){ + CPed* pPed = (CPed*)pNode->item; + if (pPed->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pPed->bUsesCollision) + continue; + pPed->m_scanCode = CWorld::GetCurrentScanCode(); + CVector vecPedPos = pPed->GetPosition(); + if (vecPedPos.x < x_inf || vecPedPos.x > x_sup) + continue; + if (vecPedPos.y < y_inf || vecPedPos.y > y_sup) + continue; + if (ABS(vecPedPos.z - pVehicle->GetPosition().z) >= 4.0f) + continue; + CVector vecToPed = vecPedPos - pVehicle->GetPosition(); + float dotDirection = DotProduct(pVehicle->GetForward(), vecToPed); + float dotVelocity = DotProduct(pVehicle->GetForward(), pVehicle->GetMoveSpeed()); + if (dotDirection <= frontOffset) /* If already run him over, don't care */ + continue; + float distanceUntilHit = dotDirection - frontOffset; + float movementTowardsPedPerSecond = GAME_SPEED_TO_METERS_PER_SECOND * dotVelocity; + if (4 * movementTowardsPedPerSecond <= distanceUntilHit) + /* If car isn't projected to hit a ped in 4 seconds, don't care */ + continue; + float sidewaysDistance = ABS(DotProduct(pVehicle->GetRight(), vecToPed)); + float sideLength = pVehicle->GetModelInfo()->GetColModel()->boundingBox.max.x; + if (pVehicle->m_vehType == VEHICLE_TYPE_BIKE) + sideLength *= 1.6f; + if (sideLength + 0.5f < sidewaysDistance) + /* If car is far enough taking side into account, don't care */ + continue; + if (pPed->IsPed()){ /* ...how can it not be? */ + if (pPed->GetPedState() != PED_STEP_AWAY && pPed->GetPedState() != PED_DIVE_AWAY){ + if (distanceUntilHit < movementTowardsPedPerSecond){ + /* Very close. Time to evade. */ + if (pVehicle->GetModelIndex() == MI_RCBANDIT){ + if (dotVelocity * GAME_SPEED_TO_METERS_PER_SECOND / 2 > distanceUntilHit) + pPed->SetEvasiveStep(pVehicle, 0); + } + else if (dotVelocity > 0.3f) { + if (sideLength + 0.1f < sidewaysDistance) + pPed->SetEvasiveStep(pVehicle, 0); + else + pPed->SetEvasiveDive(pVehicle, 0); + } + else if (dotVelocity > 0.1f) { + if (sideLength - 0.5f < sidewaysDistance) + pPed->SetEvasiveStep(pVehicle, 0); + else + pPed->SetEvasiveDive(pVehicle, 0); + } + }else{ + /* Relatively safe but annoying. */ + if (pVehicle->GetStatus() == STATUS_PLAYER && + pPed->GetPedState() != PED_FLEE_ENTITY && + pPed->CharCreatedBy == RANDOM_CHAR){ + float angleCarToPed = CGeneral::GetRadianAngleBetweenPoints( + pVehicle->GetPosition().x, pVehicle->GetPosition().y, + pPed->GetPosition().x, pPed->GetPosition().y + ); + angleCarToPed = CGeneral::LimitRadianAngle(angleCarToPed); + pPed->m_headingRate = CGeneral::LimitRadianAngle(pPed->m_headingRate); + float visibilityAngle = ABS(angleCarToPed - pPed->m_headingRate); + if (visibilityAngle > PI) + visibilityAngle = TWOPI - visibilityAngle; + if (visibilityAngle < HALFPI || pVehicle->m_nCarHornTimer){ + /* if ped sees the danger or if car horn is on */ + pPed->SetFlee(pVehicle, 2000); + pPed->bUsePedNodeSeek = false; + pPed->SetMoveState(PEDMOVE_RUN); + } + }else{ + CPlayerPed* pPlayerPed = (CPlayerPed*)pPed; + if (pPlayerPed->IsPlayer() && dotDirection < frontSafe && + pPlayerPed->IsPedInControl() && + pPlayerPed->m_fMoveSpeed < 1.0f && !pPlayerPed->bIsLooking && + CTimer::GetTimeInMilliseconds() > pPlayerPed->m_lookTimer) { + pPlayerPed->AnnoyPlayerPed(false); + pPlayerPed->SetLookFlag(pVehicle, true); + pPlayerPed->SetLookTimer(1500); + if (pPlayerPed->GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED || + pPlayerPed->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT || + pPlayerPed->GetWeapon()->m_eWeaponType == WEAPONTYPE_COLT45 || + pPlayerPed->GetWeapon()->m_eWeaponType == WEAPONTYPE_UZI) { + pPlayerPed->bShakeFist = true; + } + } + } + } + } + } + /* Ped stuff done. Now vehicle stuff. */ + if (distanceUntilHit < 10.0f){ + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_STOP_FOR_CARS || + pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_SLOW_DOWN_FOR_CARS){ + *pSpeed = Min(*pSpeed, ABS(distanceUntilHit - 1.0f) * 0.1f * curSpeed); + pVehicle->AutoPilot.m_bSlowedDownBecauseOfPeds = true; + if (distanceUntilHit < 2.0f){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 3000; + } + } + } + } +} + +void CCarCtrl::SlowCarDownForCarsSectorList(CPtrList& lst, CVehicle* pVehicle, float x_inf, float y_inf, float x_sup, float y_sup, float* pSpeed, float curSpeed) +{ + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next){ + CVehicle* pTestVehicle = (CVehicle*)pNode->item; + if (pVehicle == pTestVehicle) + continue; + if (pTestVehicle->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pTestVehicle->bUsesCollision) + continue; + pTestVehicle->m_scanCode = CWorld::GetCurrentScanCode(); + CVector boundCenter = pTestVehicle->GetBoundCentre(); + if (boundCenter.x < x_inf || boundCenter.x > x_sup) + continue; + if (boundCenter.y < y_inf || boundCenter.y > y_sup) + continue; + if (Abs(boundCenter.z - pVehicle->GetPosition().z) < 5.0f) + SlowCarDownForOtherCar(pTestVehicle, pVehicle, pSpeed, curSpeed); + } +} + +void CCarCtrl::SlowCarDownForOtherCar(CEntity* pOtherEntity, CVehicle* pVehicle, float* pSpeed, float curSpeed) +{ + CVector forwardA = pVehicle->GetForward(); + ((CVector2D)forwardA).NormaliseSafe(); + if (DotProduct2D(pOtherEntity->GetPosition() - pVehicle->GetPosition(), forwardA) < 0.0f) + return; + CVector forwardB = pOtherEntity->GetForward(); + ((CVector2D)forwardB).NormaliseSafe(); + forwardA.z = forwardB.z = 0.0f; + CVehicle* pOtherVehicle = (CVehicle*)pOtherEntity; + /* why is the argument CEntity if it's always CVehicle anyway and is casted? */ + float speedOtherX = GAME_SPEED_TO_CARAI_SPEED * pOtherVehicle->GetMoveSpeed().x; + float speedOtherY = GAME_SPEED_TO_CARAI_SPEED * pOtherVehicle->GetMoveSpeed().y; + float projectionX = speedOtherX - forwardA.x * curSpeed; + float projectionY = speedOtherY - forwardA.y * curSpeed; + float proximityA = TestCollisionBetween2MovingRects(pOtherVehicle, pVehicle, projectionX, projectionY, &forwardA, &forwardB, 0); + float proximityB = TestCollisionBetween2MovingRects(pVehicle, pOtherVehicle, -projectionX, -projectionY, &forwardB, &forwardA, 1); + float minProximity = Min(proximityA, proximityB); + if (minProximity >= 0.0f && minProximity < 1.0f){ + minProximity = Max(0.0f, (minProximity - 0.2f) * 1.25f); + pVehicle->AutoPilot.m_bSlowedDownBecauseOfCars = true; + *pSpeed = Min(*pSpeed, minProximity * curSpeed); + } + if (minProximity >= 0.0f && minProximity < 0.5f && pOtherEntity->IsVehicle() && + CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeToStartMission > 15000 && + CTimer::GetTimeInMilliseconds() - pOtherVehicle->AutoPilot.m_nTimeToStartMission > 15000){ + /* If cars are standing for 15 seconds, annoy one of them and make avoid cars. */ + if (pOtherEntity != FindPlayerVehicle() && + DotProduct2D(pVehicle->GetForward(), pOtherVehicle->GetForward()) < -0.5f && + pVehicle < pOtherVehicle){ /* that comparasion though... */ + *pSpeed = Max(curSpeed / 5, *pSpeed); + if (pVehicle->GetStatus() == STATUS_SIMPLE){ + pVehicle->SetStatus(STATUS_PHYSICS); + SwitchVehicleToRealPhysics(pVehicle); + } + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 1000; + } + } +} + +float CCarCtrl::TestCollisionBetween2MovingRects(CVehicle* pVehicleA, CVehicle* pVehicleB, float projectionX, float projectionY, CVector* pForwardA, CVector* pForwardB, uint8 id) +{ + CVector2D vecBToA = pVehicleA->GetPosition() - pVehicleB->GetPosition(); + float lenB = pVehicleB->GetModelInfo()->GetColModel()->boundingBox.max.y; + float widthB = pVehicleB->GetModelInfo()->GetColModel()->boundingBox.max.x; + float backLenB = -pVehicleB->GetModelInfo()->GetColModel()->boundingBox.min.y; + float lenA = pVehicleA->GetModelInfo()->GetColModel()->boundingBox.max.y; + float widthA = pVehicleA->GetModelInfo()->GetColModel()->boundingBox.max.x; + float backLenA = -pVehicleA->GetModelInfo()->GetColModel()->boundingBox.min.y; + float proximity = 1.0f; + float fullWidthB = 2.0f * widthB; + float fullLenB = lenB + backLenB; + for (int i = 0; i < 4; i++){ + float testedOffsetX; + float testedOffsetY; + switch (i) { + case 0: /* Front right corner */ + testedOffsetX = vecBToA.x + widthA * pForwardB->y + lenA * pForwardB->x; + testedOffsetY = vecBToA.y + lenA * pForwardB->y - widthA * pForwardB->x; + break; + case 1: /* Front left corner */ + testedOffsetX = vecBToA.x + -widthA * pForwardB->x + lenA * pForwardB->x; + testedOffsetY = vecBToA.y + lenA * pForwardB->y + widthA * pForwardB->x; + break; + case 2: /* Rear right corner */ + testedOffsetX = vecBToA.x + widthA * pForwardB->y - backLenA * pForwardB->x; + testedOffsetY = vecBToA.y - backLenA * pForwardB->y - widthA * pForwardB->x; + break; + case 3: /* Rear left corner */ + testedOffsetX = vecBToA.x - widthA * pForwardB->y - backLenA * pForwardB->x; + testedOffsetY = vecBToA.y - backLenA * pForwardB->y + widthA * pForwardB->x; + break; + default: + break; + } + /* Testing width collision */ + float baseWidthProximity = 0.0f; + float fullWidthProximity = 1.0f; + float widthDistance = testedOffsetX * pForwardA->y - testedOffsetY * pForwardA->x; + float widthProjection = projectionX * pForwardA->y - projectionY * pForwardA->x; + if (widthDistance > widthB){ + if (widthProjection < 0.0f){ + float proximityWidth = -(widthDistance - widthB) / widthProjection; + if (proximityWidth < 1.0f){ + baseWidthProximity = proximityWidth; + fullWidthProximity = Min(1.0f, proximityWidth - fullWidthB / widthProjection); + }else{ + baseWidthProximity = 1.0f; + } + }else{ + baseWidthProximity = 1.0f; + fullWidthProximity = 1.0f; + } + }else if (widthDistance < -widthB){ + if (widthProjection > 0.0f) { + float proximityWidth = -(widthDistance + widthB) / widthProjection; + if (proximityWidth < 1.0f) { + baseWidthProximity = proximityWidth; + fullWidthProximity = Min(1.0f, proximityWidth + fullWidthB / widthProjection); + } + else { + baseWidthProximity = 1.0f; + } + } + else { + baseWidthProximity = 1.0f; + fullWidthProximity = 1.0f; + } + }else if (widthProjection > 0.0f){ + fullWidthProximity = (widthB - widthDistance) / widthProjection; + }else if (widthProjection < 0.0f){ + fullWidthProximity = -(widthB + widthDistance) / widthProjection; + } + /* Testing length collision */ + float baseLengthProximity = 0.0f; + float fullLengthProximity = 1.0f; + float lenDistance = testedOffsetX * pForwardA->x + testedOffsetY * pForwardA->y; + float lenProjection = projectionX * pForwardA->x + projectionY * pForwardA->y; + if (lenDistance > lenB) { + if (lenProjection < 0.0f) { + float proximityLength = -(lenDistance - lenB) / lenProjection; + if (proximityLength < 1.0f) { + baseLengthProximity = proximityLength; + fullLengthProximity = Min(1.0f, proximityLength - fullLenB / lenProjection); + } + else { + baseLengthProximity = 1.0f; + } + } + else { + baseLengthProximity = 1.0f; + fullLengthProximity = 1.0f; + } + } + else if (lenDistance < -backLenB) { + if (lenProjection > 0.0f) { + float proximityLength = -(lenDistance + backLenB) / lenProjection; + if (proximityLength < 1.0f) { + baseLengthProximity = proximityLength; + fullLengthProximity = Min(1.0f, proximityLength + fullLenB / lenProjection); + } + else { + baseLengthProximity = 1.0f; + } + } + else { + baseLengthProximity = 1.0f; + fullLengthProximity = 1.0f; + } + } + else if (lenProjection > 0.0f) { + fullLengthProximity = (lenB - lenDistance) / lenProjection; + } + else if (lenProjection < 0.0f) { + fullLengthProximity = -(backLenB + lenDistance) / lenProjection; + } + float baseProximity = Max(baseWidthProximity, baseLengthProximity); + if (baseProximity < fullWidthProximity && baseProximity < fullLengthProximity) + proximity = Min(proximity, baseProximity); + } + return proximity; +} + +float CCarCtrl::FindAngleToWeaveThroughTraffic(CVehicle* pVehicle, CPhysical* pTarget, float angleToTarget, float angleForward) +{ + float distanceToTest = Min(2.0f, pVehicle->GetMoveSpeed().Magnitude2D() * 2.5f + 1.0f) * 12.0f; + float left = pVehicle->GetPosition().x - distanceToTest; + float right = pVehicle->GetPosition().x + distanceToTest; + float top = pVehicle->GetPosition().y - distanceToTest; + float bottom = pVehicle->GetPosition().y + distanceToTest; + int xstart = Max(0, CWorld::GetSectorIndexX(left)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(right)); + int ystart = Max(0, CWorld::GetSectorIndexY(top)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(bottom)); + assert(xstart <= xend); + assert(ystart <= yend); + + float angleToWeaveLeft = angleToTarget; + float angleToWeaveRight = angleToTarget; + + CWorld::AdvanceCurrentScanCode(); + + float angleToWeaveLeftLastIteration = -9999.9f; + float angleToWeaveRightLastIteration = -9999.9f; + + while (angleToWeaveLeft != angleToWeaveLeftLastIteration || + angleToWeaveRight != angleToWeaveRightLastIteration){ + angleToWeaveLeftLastIteration = angleToWeaveLeft; + angleToWeaveRightLastIteration = angleToWeaveRight; + for (int y = ystart; y <= yend; y++) { + for (int x = xstart; x <= xend; x++) { + CSector* s = CWorld::GetSector(x, y); + WeaveThroughCarsSectorList(s->m_lists[ENTITYLIST_VEHICLES], pVehicle, pTarget, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughCarsSectorList(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP], pVehicle, pTarget, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughPedsSectorList(s->m_lists[ENTITYLIST_PEDS], pVehicle, pTarget, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughPedsSectorList(s->m_lists[ENTITYLIST_PEDS_OVERLAP], pVehicle, pTarget, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughObjectsSectorList(s->m_lists[ENTITYLIST_OBJECTS], pVehicle, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + WeaveThroughObjectsSectorList(s->m_lists[ENTITYLIST_OBJECTS_OVERLAP], pVehicle, + left, top, right, bottom, &angleToWeaveLeft, &angleToWeaveRight); + } + } + } + float angleDiffFromActualToTarget = LimitRadianAngle(angleForward - angleToTarget); + float angleToBisectActualToTarget = LimitRadianAngle(angleToTarget + angleDiffFromActualToTarget / 2); + float angleDiffLeft = LimitRadianAngle(angleToWeaveLeft - angleToBisectActualToTarget); + angleDiffLeft = ABS(angleDiffLeft); + float angleDiffRight = LimitRadianAngle(angleToWeaveRight - angleToBisectActualToTarget); + angleDiffRight = ABS(angleDiffRight); + if (angleDiffLeft > HALFPI && angleDiffRight > HALFPI) + return angleToBisectActualToTarget; + if (ABS(angleDiffLeft - angleDiffRight) < 0.08f) + return angleToWeaveRight; + return angleDiffLeft < angleDiffRight ? angleToWeaveLeft : angleToWeaveRight; +} + +void CCarCtrl::WeaveThroughCarsSectorList(CPtrList& lst, CVehicle* pVehicle, CPhysical* pTarget, float x_inf, float y_inf, float x_sup, float y_sup, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next) { + CVehicle* pTestVehicle = (CVehicle*)pNode->item; + if (pTestVehicle->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pTestVehicle->bUsesCollision) + continue; + if (pTestVehicle == pTarget) + continue; + pTestVehicle->m_scanCode = CWorld::GetCurrentScanCode(); + if (pTestVehicle->GetBoundCentre().x < x_inf || pTestVehicle->GetBoundCentre().x > x_sup) + continue; + if (pTestVehicle->GetBoundCentre().y < y_inf || pTestVehicle->GetBoundCentre().y > y_sup) + continue; + if (Abs(pTestVehicle->GetPosition().z - pVehicle->GetPosition().z) >= VEHICLE_HEIGHT_DIFF_TO_CONSIDER_WEAVING) + continue; + if (pTestVehicle != pVehicle) + WeaveForOtherCar(pTestVehicle, pVehicle, pAngleToWeaveLeft, pAngleToWeaveRight); + } +} + +void CCarCtrl::WeaveForOtherCar(CEntity* pOtherEntity, CVehicle* pVehicle, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + if (pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE && pOtherEntity == FindPlayerVehicle()) + return; + if (pVehicle->AutoPilot.m_nCarMission == MISSION_RAMCAR_CLOSE && pOtherEntity == pVehicle->AutoPilot.m_pTargetCar) + return; + CVehicle* pOtherCar = (CVehicle*)pOtherEntity; + CVector2D vecDiff = pOtherCar->GetPosition() - pVehicle->GetPosition(); + float angleBetweenVehicles = CGeneral::GetATanOfXY(vecDiff.x, vecDiff.y); + float distance = vecDiff.Magnitude(); + if (distance < 1.0f) + return; + if (DotProduct2D(pVehicle->GetMoveSpeed() - pOtherCar->GetMoveSpeed(), vecDiff) * 110.0f - + pOtherCar->GetModelInfo()->GetColModel()->boundingSphere.radius - + pVehicle->GetModelInfo()->GetColModel()->boundingSphere.radius < distance) + return; + CVector2D forward = pVehicle->GetForward(); + forward.NormaliseSafe(); + float forwardAngle = CGeneral::GetATanOfXY(forward.x, forward.y); + float angleDiff = angleBetweenVehicles - forwardAngle; + float lenProjection = ABS(pOtherCar->GetColModel()->boundingBox.max.y * Sin(angleDiff)); + float widthProjection = ABS(pOtherCar->GetColModel()->boundingBox.max.x * Cos(angleDiff)); + float lengthToEvade = (2 * (lenProjection + widthProjection) + WIDTH_COEF_TO_WEAVE_SAFELY * 2 * pVehicle->GetColModel()->boundingBox.max.x) / distance; + float diffToLeftAngle = LimitRadianAngle(angleBetweenVehicles - *pAngleToWeaveLeft); + diffToLeftAngle = ABS(diffToLeftAngle); + float angleToWeave = lengthToEvade / 2; + if (diffToLeftAngle < angleToWeave){ + *pAngleToWeaveLeft = angleBetweenVehicles - angleToWeave; + while (*pAngleToWeaveLeft < -PI) + *pAngleToWeaveLeft += TWOPI; + } + float diffToRightAngle = LimitRadianAngle(angleBetweenVehicles - *pAngleToWeaveRight); + diffToRightAngle = ABS(diffToRightAngle); + if (diffToRightAngle < angleToWeave){ + *pAngleToWeaveRight = angleBetweenVehicles + angleToWeave; + while (*pAngleToWeaveRight > PI) + *pAngleToWeaveRight -= TWOPI; + } +} + +void CCarCtrl::WeaveThroughPedsSectorList(CPtrList& lst, CVehicle* pVehicle, CPhysical* pTarget, float x_inf, float y_inf, float x_sup, float y_sup, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next) { + CPed* pPed = (CPed*)pNode->item; + if (pPed->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pPed->bUsesCollision) + continue; + if (pPed == pTarget) + continue; + pPed->m_scanCode = CWorld::GetCurrentScanCode(); + if (pPed->GetPosition().x < x_inf || pPed->GetPosition().x > x_sup) + continue; + if (pPed->GetPosition().y < y_inf || pPed->GetPosition().y > y_sup) + continue; + if (Abs(pPed->GetPosition().z - pVehicle->GetPosition().z) >= PED_HEIGHT_DIFF_TO_CONSIDER_WEAVING) + continue; + if (pPed->m_pCurSurface != pVehicle) + WeaveForPed(pPed, pVehicle, pAngleToWeaveLeft, pAngleToWeaveRight); + } + +} +void CCarCtrl::WeaveForPed(CEntity* pOtherEntity, CVehicle* pVehicle, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + if (pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE && pOtherEntity == FindPlayerPed()) + return; + CPed* pPed = (CPed*)pOtherEntity; + CVector2D vecDiff = pPed->GetPosition() - pVehicle->GetPosition(); + float angleBetweenVehicleAndPed = CGeneral::GetATanOfXY(vecDiff.x, vecDiff.y); + float distance = vecDiff.Magnitude(); + float lengthToEvade = (WIDTH_COEF_TO_WEAVE_SAFELY * 2 * pVehicle->GetColModel()->boundingBox.max.x + PED_WIDTH_TO_WEAVE) / distance; + float diffToLeftAngle = LimitRadianAngle(angleBetweenVehicleAndPed - *pAngleToWeaveLeft); + diffToLeftAngle = ABS(diffToLeftAngle); + float angleToWeave = lengthToEvade / 2; + if (diffToLeftAngle < angleToWeave) { + *pAngleToWeaveLeft = angleBetweenVehicleAndPed - angleToWeave; + while (*pAngleToWeaveLeft < -PI) + *pAngleToWeaveLeft += TWOPI; + } + float diffToRightAngle = LimitRadianAngle(angleBetweenVehicleAndPed - *pAngleToWeaveRight); + diffToRightAngle = ABS(diffToRightAngle); + if (diffToRightAngle < angleToWeave) { + *pAngleToWeaveRight = angleBetweenVehicleAndPed + angleToWeave; + while (*pAngleToWeaveRight > PI) + *pAngleToWeaveRight -= TWOPI; + } +} + +void CCarCtrl::WeaveThroughObjectsSectorList(CPtrList& lst, CVehicle* pVehicle, float x_inf, float y_inf, float x_sup, float y_sup, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + for (CPtrNode* pNode = lst.first; pNode != nil; pNode = pNode->next) { + CObject* pObject = (CObject*)pNode->item; + if (pObject->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + if (!pObject->bUsesCollision) + continue; + pObject->m_scanCode = CWorld::GetCurrentScanCode(); + if (pObject->GetPosition().x < x_inf || pObject->GetPosition().x > x_sup) + continue; + if (pObject->GetPosition().y < y_inf || pObject->GetPosition().y > y_sup) + continue; + if (Abs(pObject->GetPosition().z - pVehicle->GetPosition().z) >= OBJECT_HEIGHT_DIFF_TO_CONSIDER_WEAVING) + continue; + if (pObject->GetUp().z > 0.9f) + WeaveForObject(pObject, pVehicle, pAngleToWeaveLeft, pAngleToWeaveRight); + } +} + +void CCarCtrl::WeaveForObject(CEntity* pOtherEntity, CVehicle* pVehicle, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + float rightCoef; + float forwardCoef; + if (pOtherEntity->GetModelIndex() == MI_TRAFFICLIGHTS){ + rightCoef = 2.957f; + forwardCoef = 0.147f; + }else if (pOtherEntity->GetModelIndex() == MI_SINGLESTREETLIGHTS1){ + rightCoef = 0.744f; + forwardCoef = 0.0f; + }else if (pOtherEntity->GetModelIndex() == MI_SINGLESTREETLIGHTS2){ + rightCoef = 0.043f; + forwardCoef = 0.0f; + }else if (pOtherEntity->GetModelIndex() == MI_SINGLESTREETLIGHTS3){ + rightCoef = 1.143f; + forwardCoef = 0.145f; + }else if (pOtherEntity->GetModelIndex() == MI_DOUBLESTREETLIGHTS){ + rightCoef = 0.0f; + forwardCoef = -0.048f; + }else if (IsTreeModel(pOtherEntity->GetModelIndex())){ + rightCoef = 0.0f; + forwardCoef = 0.0f; + }else if (pOtherEntity->GetModelIndex() == MI_STREETLAMP1 || pOtherEntity->GetModelIndex() == MI_STREETLAMP2){ + rightCoef = 0.0f; + forwardCoef = 0.0f; + }else + return; + CObject* pObject = (CObject*)pOtherEntity; + CVector2D vecDiff = pObject->GetPosition() + + rightCoef * pObject->GetRight() + + forwardCoef * pObject->GetForward() - + pVehicle->GetPosition(); + float angleBetweenVehicleAndObject = CGeneral::GetATanOfXY(vecDiff.x, vecDiff.y); + float distance = vecDiff.Magnitude(); + float lengthToEvade = (WIDTH_COEF_TO_WEAVE_SAFELY * 2 * pVehicle->GetColModel()->boundingBox.max.x + OBJECT_WIDTH_TO_WEAVE) / distance; + float diffToLeftAngle = LimitRadianAngle(angleBetweenVehicleAndObject - *pAngleToWeaveLeft); + diffToLeftAngle = ABS(diffToLeftAngle); + float angleToWeave = lengthToEvade / 2; + if (diffToLeftAngle < angleToWeave) { + *pAngleToWeaveLeft = angleBetweenVehicleAndObject - angleToWeave; + while (*pAngleToWeaveLeft < -PI) + *pAngleToWeaveLeft += TWOPI; + } + float diffToRightAngle = LimitRadianAngle(angleBetweenVehicleAndObject - *pAngleToWeaveRight); + diffToRightAngle = ABS(diffToRightAngle); + if (diffToRightAngle < angleToWeave) { + *pAngleToWeaveRight = angleBetweenVehicleAndObject + angleToWeave; + while (*pAngleToWeaveRight > PI) + *pAngleToWeaveRight -= TWOPI; + } +} + +bool CCarCtrl::PickNextNodeAccordingStrategy(CVehicle* pVehicle) +{ + switch (pVehicle->AutoPilot.m_nCarMission){ + case MISSION_RAMPLAYER_FARAWAY: + case MISSION_BLOCKPLAYER_FARAWAY: + PickNextNodeToChaseCar(pVehicle, + FindPlayerCoors().x, + FindPlayerCoors().y, +#ifdef FIX_PATHFIND_BUG + FindPlayerCoors().z, +#endif + FindPlayerVehicle()); + return false; + case MISSION_GOTOCOORDS: + case MISSION_GOTOCOORDS_ACCURATE: + return PickNextNodeToFollowPath(pVehicle); + case MISSION_RAMCAR_FARAWAY: + case MISSION_BLOCKCAR_FARAWAY: + PickNextNodeToChaseCar(pVehicle, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().x, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().y, +#ifdef FIX_PATHFIND_BUG + pVehicle->AutoPilot.m_pTargetCar->GetPosition().z, +#endif + pVehicle->AutoPilot.m_pTargetCar); + return false; + default: + PickNextNodeRandomly(pVehicle); + return false; + } +} + +void CCarCtrl::PickNextNodeRandomly(CVehicle* pVehicle) +{ + int32 prevNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + int32 curNode = pVehicle->AutoPilot.m_nNextRouteNode; + uint8 totalLinks = ThePaths.m_pathNodes[curNode].numLinks; + CCarPathLink* pCurLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; +#ifdef FIX_BUGS + uint8 lanesOnCurrentPath = pCurLink->pathNodeIndex == curNode ? + pCurLink->numLeftLanes : pCurLink->numRightLanes; +#else + uint8 lanesOnCurrentPath = pCurLink->pathNodeIndex == curNode ? + pCurLink->numRightLanes : pCurLink->numLeftLanes; +#endif + uint8 allowedDirections = PATH_DIRECTION_NONE; + uint8 nextLane = pVehicle->AutoPilot.m_nNextLane; + if (nextLane == 0) + /* We are always allowed to turn left from leftmost lane */ + allowedDirections |= PATH_DIRECTION_LEFT; + if (nextLane == lanesOnCurrentPath - 1) + /* We are always allowed to turn right from rightmost lane */ + allowedDirections |= PATH_DIRECTION_RIGHT; + if (lanesOnCurrentPath < 3 || allowedDirections == PATH_DIRECTION_NONE) + /* We are always allowed to go straight on one/two-laned road */ + /* or if we are in one of middle lanes of the road */ + allowedDirections |= PATH_DIRECTION_STRAIGHT; + int attempt; + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode; + CPathNode* pPrevPathNode = &ThePaths.m_pathNodes[prevNode]; + CPathNode* pCurPathNode = &ThePaths.m_pathNodes[curNode]; + int16 nextLink; + CCarPathLink* pNextLink; + CPathNode* pNextPathNode; + bool goingAgainstOneWayRoad; + uint8 direction; + for(attempt = 0; attempt < ATTEMPTS_TO_FIND_NEXT_NODE; attempt++){ + if (attempt != 0){ + if (pVehicle->AutoPilot.m_nNextRouteNode != prevNode){ + if (direction & allowedDirections){ + pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + if ((!pNextPathNode->bDeadEnd || pPrevPathNode->bDeadEnd) && + (!pNextPathNode->bDisabled || pPrevPathNode->bDisabled) && + (!pNextPathNode->bBetweenLevels || pPrevPathNode->bBetweenLevels || !pVehicle->AutoPilot.m_bStayInCurrentLevel) && + !goingAgainstOneWayRoad) + break; + } + } + } + nextLink = CGeneral::GetRandomNumber() % totalLinks; + pVehicle->AutoPilot.m_nNextRouteNode = ThePaths.ConnectedNode(nextLink + pCurPathNode->firstLink); + direction = FindPathDirection(prevNode, curNode, pVehicle->AutoPilot.m_nNextRouteNode); + pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]]; + goingAgainstOneWayRoad = pNextLink->pathNodeIndex == curNode ? pNextLink->numRightLanes == 0 : pNextLink->numLeftLanes == 0; + } + if (attempt >= ATTEMPTS_TO_FIND_NEXT_NODE) { + /* If we failed 15 times, then remove dead end and current lane limitations */ + for (attempt = 0; attempt < ATTEMPTS_TO_FIND_NEXT_NODE; attempt++) { + if (attempt != 0) { + if (pVehicle->AutoPilot.m_nNextRouteNode != prevNode) { + pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + if ((!pNextPathNode->bDisabled || pPrevPathNode->bDisabled) && + (!pNextPathNode->bBetweenLevels || pPrevPathNode->bBetweenLevels || !pVehicle->AutoPilot.m_bStayInCurrentLevel) && + !goingAgainstOneWayRoad) + break; + } + } + nextLink = CGeneral::GetRandomNumber() % totalLinks; + pVehicle->AutoPilot.m_nNextRouteNode = ThePaths.ConnectedNode(nextLink + pCurPathNode->firstLink); + pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]]; + goingAgainstOneWayRoad = pNextLink->pathNodeIndex == curNode ? pNextLink->numRightLanes == 0 : pNextLink->numLeftLanes == 0; + } + } + if (attempt >= ATTEMPTS_TO_FIND_NEXT_NODE) { + /* If we failed again, remove no U-turn limitation and remove randomness */ + for (nextLink = 0; nextLink < totalLinks; nextLink++) { + pVehicle->AutoPilot.m_nNextRouteNode = ThePaths.ConnectedNode(nextLink + pCurPathNode->firstLink); + pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]]; + goingAgainstOneWayRoad = pNextLink->pathNodeIndex == curNode ? pNextLink->numRightLanes == 0 : pNextLink->numLeftLanes == 0; + if (!goingAgainstOneWayRoad) { + pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + if ((!pNextPathNode->bDisabled || pPrevPathNode->bDisabled) && + (!pNextPathNode->bBetweenLevels || pPrevPathNode->bBetweenLevels || !pVehicle->AutoPilot.m_bStayInCurrentLevel)) + /* Nice way to exit loop but this will fail because this is used for indexing! */ + nextLink = 1000; + } + } + if (nextLink < 999) + /* If everything else failed, turn vehicle around */ + pVehicle->AutoPilot.m_nNextRouteNode = prevNode; + } + pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]]; + if (prevNode == pVehicle->AutoPilot.m_nNextRouteNode){ + /* We can no longer shift vehicle without physics if we have to turn it around. */ + pVehicle->SetStatus(STATUS_PHYSICS); + SwitchVehicleToRealPhysics(pVehicle); + } + pVehicle->AutoPilot.m_nTimeEnteredCurve += pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; + pVehicle->AutoPilot.m_nPreviousPathNodeInfo = pVehicle->AutoPilot.m_nCurrentPathNodeInfo; + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = pVehicle->AutoPilot.m_nNextPathNodeInfo; + pVehicle->AutoPilot.m_nPreviousDirection = pVehicle->AutoPilot.m_nCurrentDirection; + pVehicle->AutoPilot.m_nCurrentDirection = pVehicle->AutoPilot.m_nNextDirection; + pVehicle->AutoPilot.m_nCurrentLane = pVehicle->AutoPilot.m_nNextLane; + pVehicle->AutoPilot.m_nNextPathNodeInfo = ThePaths.m_carPathConnections[nextLink + pCurPathNode->firstLink]; + int8 lanesOnNextNode; + if (curNode >= pVehicle->AutoPilot.m_nNextRouteNode){ + pVehicle->AutoPilot.m_nNextDirection = 1; + lanesOnNextNode = pNextLink->numLeftLanes; + }else{ + pVehicle->AutoPilot.m_nNextDirection = -1; + lanesOnNextNode = pNextLink->numRightLanes; + } + float currentPathLinkForwardX = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirX(); + float nextPathLinkForwardX = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirX(); +#ifdef FIX_BUGS + float currentPathLinkForwardY = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirY(); + float nextPathLinkForwardY = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirY(); +#endif + if (lanesOnNextNode >= 0){ + if ((CGeneral::GetRandomNumber() & 0x600) == 0){ + /* 25% chance vehicle will try to switch lane */ + CVector2D dist = pNextPathNode->GetPosition() - pCurPathNode->GetPosition(); + if (dist.MagnitudeSqr() >= SQR(14.0f)){ + if (CGeneral::GetRandomTrueFalse()) + pVehicle->AutoPilot.m_nNextLane += 1; + else + pVehicle->AutoPilot.m_nNextLane -= 1; + } + } + pVehicle->AutoPilot.m_nNextLane = Min(lanesOnNextNode - 1, pVehicle->AutoPilot.m_nNextLane); + pVehicle->AutoPilot.m_nNextLane = Max(0, pVehicle->AutoPilot.m_nNextLane); + }else{ + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane; + } + if (pVehicle->AutoPilot.m_bStayInFastLane) + pVehicle->AutoPilot.m_nNextLane = 0; + CVector positionOnCurrentLinkIncludingLane( + pCurLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) +#ifdef FIX_BUGS + * currentPathLinkForwardY +#endif + ,pCurLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) +#ifdef FIX_BUGS + * nextPathLinkForwardY +#endif + ,pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + float directionCurrentLinkX = pCurLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionCurrentLinkY = pCurLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + if (pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve < 10) + /* Oh hey there Obbe */ + printf("fout\n"); + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = Max(10, pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); +} + +uint8 CCarCtrl::FindPathDirection(int32 prevNode, int32 curNode, int32 nextNode) +{ + CVector2D prevToCur = ThePaths.m_pathNodes[curNode].GetPosition() - ThePaths.m_pathNodes[prevNode].GetPosition(); + CVector2D curToNext = ThePaths.m_pathNodes[nextNode].GetPosition() - ThePaths.m_pathNodes[curNode].GetPosition(); + float distPrevToCur = prevToCur.Magnitude(); + if (distPrevToCur == 0.0f) + return PATH_DIRECTION_NONE; + /* We are trying to determine angle between prevToCur and curToNext. */ + /* To find it, we consider a to be an angle between y axis and prevToCur */ + /* and b to be an angle between x axis and curToNext */ + /* Then the angle we are looking for is (pi/2 + a + b). */ + float sin_a = prevToCur.x / distPrevToCur; + float cos_a = prevToCur.y / distPrevToCur; + float distCurToNext = curToNext.Magnitude(); + if (distCurToNext == 0.0f) + return PATH_DIRECTION_NONE; + float sin_b = curToNext.y / distCurToNext; + float cos_b = curToNext.x / distCurToNext; + /* sin(a) * sin(b) - cos(a) * cos(b) = -cos(a+b) = sin(pi/2+a+b) */ + float sin_direction = sin_a * sin_b - cos_a * cos_b; + if (sin_direction > 0.77f) /* Roughly between -50 and -130 degrees */ + return PATH_DIRECTION_LEFT; + if (sin_direction < -0.77f) /* Roughly between 50 and 130 degrees */ + return PATH_DIRECTION_RIGHT; + return PATH_DIRECTION_STRAIGHT; +} + +#ifdef FIX_PATHFIND_BUG +void CCarCtrl::PickNextNodeToChaseCar(CVehicle* pVehicle, float targetX, float targetY, float targetZ, CVehicle* pTarget) +#else +void CCarCtrl::PickNextNodeToChaseCar(CVehicle* pVehicle, float targetX, float targetY, CVehicle* pTarget) +#endif +{ + int prevNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + int curNode = pVehicle->AutoPilot.m_nNextRouteNode; + CPathNode* pPrevNode = &ThePaths.m_pathNodes[prevNode]; + CPathNode* pCurNode = &ThePaths.m_pathNodes[curNode]; + CPathNode* pTargetNode; + int16 numNodes; + float distanceToTargetNode; + if (pTarget && pTarget->m_pCurGroundEntity && + pTarget->m_pCurGroundEntity->IsBuilding() && + ((CBuilding*)pTarget->m_pCurGroundEntity)->GetIsATreadable() && + ((CTreadable*)pTarget->m_pCurGroundEntity)->m_nodeIndices[0][0] >= 0){ + CTreadable* pCurrentMapObject = (CTreadable*)pTarget->m_pCurGroundEntity; + int closestNode = -1; + float minDist = 100000.0f; + for (int i = 0; i < 12; i++){ + int node = pCurrentMapObject->m_nodeIndices[0][i]; + if (node < 0) + break; + float dist = (ThePaths.m_pathNodes[node].GetPosition() - pTarget->GetPosition()).Magnitude(); + if (dist < minDist){ + minDist = dist; + closestNode = node; + } + } + ThePaths.DoPathSearch(0, pCurNode->GetPosition(), curNode, +#ifdef FIX_PATHFIND_BUG + CVector(targetX, targetY, targetZ), +#else + CVector(targetX, targetY, 0.0f), +#endif + &pTargetNode, &numNodes, 1, pVehicle, &distanceToTargetNode, 999999.9f, closestNode); + }else + { + + ThePaths.DoPathSearch(0, pCurNode->GetPosition(), curNode, +#ifdef FIX_PATHFIND_BUG + CVector(targetX, targetY, targetZ), +#else + CVector(targetX, targetY, 0.0f), +#endif + &pTargetNode, &numNodes, 1, pVehicle, &distanceToTargetNode, 999999.9f, -1); + } + + int newNextNode; + int nextLink; + if (numNodes != 1 || pTargetNode == pCurNode){ + float currentAngle = CGeneral::GetATanOfXY(targetX - pVehicle->GetPosition().x, targetY - pVehicle->GetPosition().y); + nextLink = 0; + float lowestAngleChange = 10.0f; + int numLinks = pCurNode->numLinks; + newNextNode = 0; + for (int i = 0; i < numLinks; i++){ + int conNode = ThePaths.ConnectedNode(i + pCurNode->firstLink); + if (conNode == prevNode && i > 1) + continue; + CPathNode* pTestNode = &ThePaths.m_pathNodes[conNode]; + float angle = CGeneral::GetATanOfXY(pTestNode->GetX() - pCurNode->GetX(), pTestNode->GetY() - pCurNode->GetY()); + angle = LimitRadianAngle(angle - currentAngle); + angle = ABS(angle); + if (angle < lowestAngleChange){ + lowestAngleChange = angle; + newNextNode = conNode; + nextLink = i; + } + } + }else{ + nextLink = 0; + newNextNode = pTargetNode - ThePaths.m_pathNodes; + for (int i = pCurNode->firstLink; ThePaths.ConnectedNode(i) != newNextNode; i++, nextLink++) + ; + } + CPathNode* pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]]; + CCarPathLink* pCurLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode; + pVehicle->AutoPilot.m_nNextRouteNode = newNextNode; + pVehicle->AutoPilot.m_nTimeEnteredCurve += pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; + pVehicle->AutoPilot.m_nPreviousPathNodeInfo = pVehicle->AutoPilot.m_nCurrentPathNodeInfo; + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = pVehicle->AutoPilot.m_nNextPathNodeInfo; + pVehicle->AutoPilot.m_nPreviousDirection = pVehicle->AutoPilot.m_nCurrentDirection; + pVehicle->AutoPilot.m_nCurrentDirection = pVehicle->AutoPilot.m_nNextDirection; + pVehicle->AutoPilot.m_nCurrentLane = pVehicle->AutoPilot.m_nNextLane; + pVehicle->AutoPilot.m_nNextPathNodeInfo = ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]; + int8 lanesOnNextNode; + if (curNode >= pVehicle->AutoPilot.m_nNextRouteNode) { + pVehicle->AutoPilot.m_nNextDirection = 1; + lanesOnNextNode = pNextLink->numLeftLanes; + } + else { + pVehicle->AutoPilot.m_nNextDirection = -1; + lanesOnNextNode = pNextLink->numRightLanes; + } + float currentPathLinkForwardX = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirX(); + float currentPathLinkForwardY = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirY(); + float nextPathLinkForwardX = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirX(); + float nextPathLinkForwardY = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirY(); + if (lanesOnNextNode >= 0) { + CVector2D dist = pNextPathNode->GetPosition() - pCurNode->GetPosition(); + if (dist.MagnitudeSqr() >= SQR(7.0f)){ + /* 25% chance vehicle will try to switch lane */ + /* No lane switching if following car from far away */ + /* ...although it's always one of those. */ + if ((CGeneral::GetRandomNumber() & 0x600) == 0 && + pVehicle->AutoPilot.m_nCarMission != MISSION_RAMPLAYER_FARAWAY && + pVehicle->AutoPilot.m_nCarMission != MISSION_BLOCKPLAYER_FARAWAY && + pVehicle->AutoPilot.m_nCarMission != MISSION_RAMCAR_FARAWAY && + pVehicle->AutoPilot.m_nCarMission != MISSION_BLOCKCAR_FARAWAY){ + if (CGeneral::GetRandomTrueFalse()) + pVehicle->AutoPilot.m_nNextLane += 1; + else + pVehicle->AutoPilot.m_nNextLane -= 1; + } + } + pVehicle->AutoPilot.m_nNextLane = Min(lanesOnNextNode - 1, pVehicle->AutoPilot.m_nNextLane); + pVehicle->AutoPilot.m_nNextLane = Max(0, pVehicle->AutoPilot.m_nNextLane); + } + else { + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane; + } + if (pVehicle->AutoPilot.m_bStayInFastLane) + pVehicle->AutoPilot.m_nNextLane = 0; + CVector positionOnCurrentLinkIncludingLane( + pCurLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + float directionCurrentLinkX = pCurLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionCurrentLinkY = pCurLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = Max(10, pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); +} + +bool CCarCtrl::PickNextNodeToFollowPath(CVehicle* pVehicle) +{ + int curNode = pVehicle->AutoPilot.m_nNextRouteNode; + CPathNode* pCurNode = &ThePaths.m_pathNodes[curNode]; + if (pVehicle->AutoPilot.m_nPathFindNodesCount == 0){ + ThePaths.DoPathSearch(0, pVehicle->GetPosition(), curNode, + pVehicle->AutoPilot.m_vecDestinationCoors, pVehicle->AutoPilot.m_aPathFindNodesInfo, + &pVehicle->AutoPilot.m_nPathFindNodesCount, NUM_PATH_NODES_IN_AUTOPILOT, + pVehicle, nil, 999999.9f, -1); + if (pVehicle->AutoPilot.m_nPathFindNodesCount < 1) + return true; + } + CPathNode* pNextPathNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode]; + CCarPathLink* pCurLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode; + pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode; + pVehicle->AutoPilot.m_nNextRouteNode = pVehicle->AutoPilot.m_aPathFindNodesInfo[0] - ThePaths.m_pathNodes; + pVehicle->AutoPilot.RemoveOnePathNode(); + pVehicle->AutoPilot.m_nTimeEnteredCurve += pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; + pVehicle->AutoPilot.m_nPreviousPathNodeInfo = pVehicle->AutoPilot.m_nCurrentPathNodeInfo; + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = pVehicle->AutoPilot.m_nNextPathNodeInfo; + pVehicle->AutoPilot.m_nPreviousDirection = pVehicle->AutoPilot.m_nCurrentDirection; + pVehicle->AutoPilot.m_nCurrentDirection = pVehicle->AutoPilot.m_nNextDirection; + pVehicle->AutoPilot.m_nCurrentLane = pVehicle->AutoPilot.m_nNextLane; + int nextLink = 0; + for (int i = pCurNode->firstLink; ThePaths.ConnectedNode(i) != pVehicle->AutoPilot.m_nNextRouteNode; i++, nextLink++) + ; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]]; + pVehicle->AutoPilot.m_nNextPathNodeInfo = ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]; + int8 lanesOnNextNode; + if (curNode >= pVehicle->AutoPilot.m_nNextRouteNode) { + pVehicle->AutoPilot.m_nNextDirection = 1; + lanesOnNextNode = pNextLink->numLeftLanes; + } + else { + pVehicle->AutoPilot.m_nNextDirection = -1; + lanesOnNextNode = pNextLink->numRightLanes; + } + float currentPathLinkForwardX = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirX(); + float currentPathLinkForwardY = pVehicle->AutoPilot.m_nCurrentDirection * pCurLink->GetDirY(); + float nextPathLinkForwardX = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirX(); + float nextPathLinkForwardY = pVehicle->AutoPilot.m_nNextDirection * pNextLink->GetDirY(); + if (lanesOnNextNode >= 0) { + CVector2D dist = pNextPathNode->GetPosition() - pCurNode->GetPosition(); + if (dist.MagnitudeSqr() >= SQR(7.0f) && (CGeneral::GetRandomNumber() & 0x600) == 0) { + if (CGeneral::GetRandomTrueFalse()) + pVehicle->AutoPilot.m_nNextLane += 1; + else + pVehicle->AutoPilot.m_nNextLane -= 1; + } + pVehicle->AutoPilot.m_nNextLane = Min(lanesOnNextNode - 1, pVehicle->AutoPilot.m_nNextLane); + pVehicle->AutoPilot.m_nNextLane = Max(0, pVehicle->AutoPilot.m_nNextLane); + } + else { + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane; + } + if (pVehicle->AutoPilot.m_bStayInFastLane) + pVehicle->AutoPilot.m_nNextLane = 0; + CVector positionOnCurrentLinkIncludingLane( + pCurLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardY, + pCurLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForwardX, + 0.0f); + CVector positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX, + 0.0f); + float directionCurrentLinkX = pCurLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionCurrentLinkY = pCurLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + float directionNextLinkX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float directionNextLinkY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + /* We want to make a path between two links that may not have the same forward directions a curve. */ + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = CCurves::CalcSpeedScaleFactor( + &positionOnCurrentLinkIncludingLane, + &positionOnNextLinkIncludingLane, + directionCurrentLinkX, directionCurrentLinkY, + directionNextLinkX, directionNextLinkY + ) * (1000.0f / pVehicle->AutoPilot.m_fMaxTrafficSpeed); + pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve = Max(10, pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve); + return false; +} + +void CCarCtrl::Init(void) +{ + NumRandomCars = 0; + NumLawEnforcerCars = 0; + NumMissionCars = 0; + NumParkedCars = 0; + NumPermanentCars = 0; + NumAmbulancesOnDuty = 0; + NumFiretrucksOnDuty = 0; + LastTimeFireTruckCreated = 0; + LastTimeAmbulanceCreated = 0; +#ifdef FIX_BUGS + LastTimeLawEnforcerCreated = 0; +#endif + bCarsGeneratedAroundCamera = false; + CountDownToCarsAtStart = 2; + CarDensityMultiplier = 1.0f; + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) + apCarsToKeep[i] = nil; + for (int i = 0; i < TOTAL_CUSTOM_CLASSES; i++){ + for (int j = 0; j < MAX_CAR_MODELS_IN_ARRAY; j++) + CarArrays[i][j] = 0; + NextCarOfRating[i] = 0; + TotalNumOfCarsOfRating[i] = 0; + } +} + +void CCarCtrl::ReInit(void) +{ + NumRandomCars = 0; + NumLawEnforcerCars = 0; + NumMissionCars = 0; + NumParkedCars = 0; + NumPermanentCars = 0; + NumAmbulancesOnDuty = 0; + NumFiretrucksOnDuty = 0; +#ifdef FIX_BUGS + LastTimeFireTruckCreated = 0; + LastTimeAmbulanceCreated = 0; + LastTimeLawEnforcerCreated = 0; +#endif + CountDownToCarsAtStart = 2; + CarDensityMultiplier = 1.0f; + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) + apCarsToKeep[i] = nil; + for (int i = 0; i < TOTAL_CUSTOM_CLASSES; i++) + NextCarOfRating[i] = 0; +} + +void CCarCtrl::DragCarToPoint(CVehicle* pVehicle, CVector* pPoint) +{ + CVector2D posBehind = (CVector2D)pVehicle->GetPosition() - 3 * pVehicle->GetForward() / 2; + CVector2D posTarget = *pPoint; + CVector2D direction = posBehind - posTarget; + CVector2D midPos = posTarget + direction * 3 / direction.Magnitude(); + float actualAheadZ; + float actualBehindZ; + CColPoint point; + CEntity* pRoadObject; + if (CCollision::IsStoredPolyStillValidVerticalLine(CVector(posTarget.x, posTarget.y, pVehicle->GetPosition().z - 3.0f), + pVehicle->GetPosition().z - 3.0f, point, &pVehicle->m_aCollPolys[0])){ + actualAheadZ = point.point.z; + }else if (CWorld::ProcessVerticalLine(CVector(posTarget.x, posTarget.y, pVehicle->GetPosition().z + 1.5f), + pVehicle->GetPosition().z - 2.0f, point, + pRoadObject, true, false, false, false, false, false, &pVehicle->m_aCollPolys[0])){ + actualAheadZ = point.point.z; + pVehicle->m_pCurGroundEntity = pRoadObject; + if (ThisRoadObjectCouldMove(pRoadObject->GetModelIndex())) + pVehicle->m_aCollPolys[0].valid = false; + }else if (CWorld::ProcessVerticalLine(CVector(posTarget.x, posTarget.y, pVehicle->GetPosition().z + 3.0f), + pVehicle->GetPosition().z - 3.0f, point, + pRoadObject, true, false, false, false, false, false, &pVehicle->m_aCollPolys[0])) { + actualAheadZ = point.point.z; + pVehicle->m_pCurGroundEntity = pRoadObject; + if (ThisRoadObjectCouldMove(pRoadObject->GetModelIndex())) + pVehicle->m_aCollPolys[0].valid = false; + }else{ + actualAheadZ = pVehicle->m_fMapObjectHeightAhead; + } + pVehicle->m_fMapObjectHeightAhead = actualAheadZ; + if (CCollision::IsStoredPolyStillValidVerticalLine(CVector(midPos.x, midPos.y, pVehicle->GetPosition().z - 3.0f), + pVehicle->GetPosition().z - 3.0f, point, &pVehicle->m_aCollPolys[1])){ + actualBehindZ = point.point.z; + }else if (CWorld::ProcessVerticalLine(CVector(midPos.x, midPos.y, pVehicle->GetPosition().z + 1.5f), + pVehicle->GetPosition().z - 2.0f, point, + pRoadObject, true, false, false, false, false, false, &pVehicle->m_aCollPolys[1])){ + actualBehindZ = point.point.z; + pVehicle->m_pCurGroundEntity = pRoadObject; + if (ThisRoadObjectCouldMove(pRoadObject->GetModelIndex())) + pVehicle->m_aCollPolys[1].valid = false; + }else if (CWorld::ProcessVerticalLine(CVector(midPos.x, midPos.y, pVehicle->GetPosition().z + 3.0f), + pVehicle->GetPosition().z - 3.0f, point, + pRoadObject, true, false, false, false, false, false, &pVehicle->m_aCollPolys[1])){ + actualBehindZ = point.point.z; + pVehicle->m_pCurGroundEntity = pRoadObject; + if (ThisRoadObjectCouldMove(pRoadObject->GetModelIndex())) + pVehicle->m_aCollPolys[1].valid = false; + }else{ + actualBehindZ = pVehicle->m_fMapObjectHeightBehind; + } + pVehicle->m_fMapObjectHeightBehind = actualBehindZ; + float angleZ = Atan2((actualAheadZ - actualBehindZ) / 3, 1.0f); + float cosZ = Cos(angleZ); + float sinZ = Sin(angleZ); + pVehicle->GetRight() = CVector(posTarget.y - midPos.y, -(posTarget.x - midPos.x), 0.0f) / 3; + pVehicle->GetForward() = CVector(-cosZ * pVehicle->GetRight().y, cosZ * pVehicle->GetRight().x, sinZ); + pVehicle->GetUp() = CrossProduct(pVehicle->GetRight(), pVehicle->GetForward()); + pVehicle->SetPosition((CVector(midPos.x, midPos.y, actualBehindZ) + CVector(posTarget.x, posTarget.y, actualAheadZ)) / 2); + pVehicle->GetMatrix().GetPosition().z += pVehicle->GetHeightAboveRoad(); +} + +float CCarCtrl::FindSpeedMultiplier(float angleChange, float minAngle, float maxAngle, float coef) +{ + float angle = Abs(LimitRadianAngle(angleChange)); + float n = angle - minAngle; + n = Max(0.0f, n); + float d = maxAngle - minAngle; + float mult = 1.0f - n / d * (1.0f - coef); + if (n > d) + return coef; + return mult; +} + +void CCarCtrl::SteerAICarWithPhysics(CVehicle* pVehicle) +{ + float swerve; + float accel; + float brake; + bool handbrake; + switch (pVehicle->AutoPilot.m_nTempAction){ + case TEMPACT_WAIT: + swerve = 0.0f; + accel = 0.0f; + brake = 0.2f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction){ + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds(); + } + break; + case TEMPACT_REVERSE: + SteerAICarWithPhysics_OnlyMission(pVehicle, &swerve, &accel, &brake, &handbrake); + handbrake = false; + swerve = -swerve; + if (DotProduct(pVehicle->GetMoveSpeed(), pVehicle->GetForward()) > 0.04f){ + accel = 0.0f; + brake = 0.5f; + }else{ + accel = -0.5f; + brake = 0.0f; + } + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_HANDBRAKETURNLEFT: + swerve = -1.0f; // It seems like this should be swerve = 1.0f (fixed in VC) + accel = 0.0f; + brake = 0.0f; + handbrake = true; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_HANDBRAKETURNRIGHT: + swerve = 1.0f; // It seems like this should be swerve = -1.0f (fixed in VC) + accel = 0.0f; + brake = 0.0f; + handbrake = true; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_HANDBRAKESTRAIGHT: + swerve = 0.0f; + accel = 0.0f; + brake = 0.0f; + handbrake = true; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_TURNLEFT: + swerve = 1.0f; + accel = 1.0f; + brake = 0.0f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_TURNRIGHT: + swerve = -1.0f; + accel = 1.0f; + brake = 0.0f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_GOFORWARD: + swerve = 0.0f; + accel = 0.5f; + brake = 0.0f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + case TEMPACT_SWERVELEFT: + case TEMPACT_SWERVERIGHT: + swerve = (pVehicle->AutoPilot.m_nTempAction == TEMPACT_SWERVERIGHT) ? 0.15f : -0.15f; + accel = 0.0f; + brake = 0.001f; + handbrake = false; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction - 1000) + swerve = -swerve; + if (CTimer::GetTimeInMilliseconds() > pVehicle->AutoPilot.m_nTimeTempAction) + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + break; + default: + SteerAICarWithPhysics_OnlyMission(pVehicle, &swerve, &accel, &brake, &handbrake); + break; + } + pVehicle->m_fSteerAngle = swerve; + pVehicle->bIsHandbrakeOn = handbrake; + pVehicle->m_fGasPedal = accel; + pVehicle->m_fBrakePedal = brake; +} + +void CCarCtrl::SteerAICarWithPhysics_OnlyMission(CVehicle* pVehicle, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + switch (pVehicle->AutoPilot.m_nCarMission) { + case MISSION_NONE: + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pBrake = 0.5f; + *pHandbrake = true; + return; + case MISSION_CRUISE: + case MISSION_RAMPLAYER_FARAWAY: + case MISSION_BLOCKPLAYER_FARAWAY: + case MISSION_GOTOCOORDS: + case MISSION_GOTOCOORDS_ACCURATE: + case MISSION_RAMCAR_FARAWAY: + case MISSION_BLOCKCAR_FARAWAY: + SteerAICarWithPhysicsFollowPath(pVehicle, pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_RAMPLAYER_CLOSE: + { + CVector2D targetPos = FindPlayerCoors(); + if (FindPlayerVehicle()){ + if (pVehicle->m_randomSeed & 1 && DotProduct(FindPlayerVehicle()->GetForward(), pVehicle->GetForward()) > 0.5f){ + float targetWidth = FindPlayerVehicle()->GetColModel()->boundingBox.max.x; + float ownWidth = pVehicle->GetColModel()->boundingBox.max.x; + if (pVehicle->m_randomSeed & 2){ + targetPos += (targetWidth + ownWidth - 0.2f) * FindPlayerVehicle()->GetRight(); + }else{ + targetPos -= (targetWidth + ownWidth - 0.2f) * FindPlayerVehicle()->GetRight(); + } + float targetSpeed = FindPlayerVehicle()->GetMoveSpeed().Magnitude(); + float distanceToTarget = ((CVector2D)pVehicle->GetPosition() - targetPos).Magnitude(); + if (12.0f * targetSpeed + 2.0f > distanceToTarget && pVehicle->AutoPilot.m_nTempAction == TEMPACT_NONE){ + pVehicle->AutoPilot.m_nTempAction = (pVehicle->m_randomSeed & 2) ? TEMPACT_TURNLEFT : TEMPACT_TURNRIGHT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 250; + } + }else{ + targetPos += FindPlayerVehicle()->GetRight() / 160 * ((pVehicle->m_randomSeed & 0xFF) - 128); + } + } + SteerAICarWithPhysicsHeadingForTarget(pVehicle, FindPlayerVehicle(), targetPos.x, targetPos.y, pSwerve, pAccel, pBrake, pHandbrake); + return; + } + case MISSION_BLOCKPLAYER_CLOSE: + SteerAICarWithPhysicsTryingToBlockTarget(pVehicle, FindPlayerCoors().x, FindPlayerCoors().y, + FindPlayerSpeed().x, FindPlayerSpeed().y, pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_BLOCKPLAYER_HANDBRAKESTOP: + SteerAICarWithPhysicsTryingToBlockTarget_Stop(pVehicle, FindPlayerCoors().x, FindPlayerCoors().y, + FindPlayerSpeed().x, FindPlayerSpeed().y, pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_GOTOCOORDS_STRAIGHT: + case MISSION_GOTO_COORDS_STRAIGHT_ACCURATE: + SteerAICarWithPhysicsHeadingForTarget(pVehicle, nil, + pVehicle->AutoPilot.m_vecDestinationCoors.x, pVehicle->AutoPilot.m_vecDestinationCoors.y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_EMERGENCYVEHICLE_STOP: + case MISSION_STOP_FOREVER: + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pHandbrake = true; + *pBrake = 0.5f; + return; + case MISSION_RAMCAR_CLOSE: + SteerAICarWithPhysicsHeadingForTarget(pVehicle, pVehicle->AutoPilot.m_pTargetCar, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().x, pVehicle->AutoPilot.m_pTargetCar->GetPosition().y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_BLOCKCAR_CLOSE: + SteerAICarWithPhysicsTryingToBlockTarget(pVehicle, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().x, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().y, + pVehicle->AutoPilot.m_pTargetCar->GetMoveSpeed().x, + pVehicle->AutoPilot.m_pTargetCar->GetMoveSpeed().y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_BLOCKCAR_HANDBRAKESTOP: + SteerAICarWithPhysicsTryingToBlockTarget_Stop(pVehicle, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().x, + pVehicle->AutoPilot.m_pTargetCar->GetPosition().y, + pVehicle->AutoPilot.m_pTargetCar->GetMoveSpeed().x, + pVehicle->AutoPilot.m_pTargetCar->GetMoveSpeed().y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + default: + return; + } +} + +void CCarCtrl::SteerAIBoatWithPhysics(CBoat* pBoat) +{ + if (pBoat->AutoPilot.m_nCarMission == MISSION_GOTOCOORDS_ASTHECROWSWIMS){ + SteerAIBoatWithPhysicsHeadingForTarget(pBoat, + pBoat->AutoPilot.m_vecDestinationCoors.x, pBoat->AutoPilot.m_vecDestinationCoors.y, + &pBoat->m_fSteeringLeftRight, &pBoat->m_fAccelerate, &pBoat->m_fBrake); + }else if (pBoat->AutoPilot.m_nCarMission == MISSION_NONE){ + pBoat->m_fSteeringLeftRight = 0.0f; + pBoat->m_fAccelerate = 0.0f; + pBoat->m_fBrake = 0.0f; + } + pBoat->m_fSteerAngle = pBoat->m_fSteeringLeftRight; + pBoat->m_fGasPedal = pBoat->m_fAccelerate; + pBoat->m_fBrakePedal = pBoat->m_fBrake; + pBoat->bIsHandbrakeOn = false; +} + +float CCarCtrl::FindMaxSteerAngle(CVehicle* pVehicle) +{ + return pVehicle->GetModelIndex() == MI_ENFORCER ? 0.7f : DEFAULT_MAX_STEER_ANGLE; +} + +void CCarCtrl::SteerAICarWithPhysicsFollowPath(CVehicle* pVehicle, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + CVector2D forward = pVehicle->GetForward(); + forward.NormaliseSafe(); + CCarPathLink* pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + CCarPathLink* pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + CVector2D currentPathLinkForward(pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection, + pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection); + float nextPathLinkForwardX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + float nextPathLinkForwardY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + CVector2D positionOnCurrentLinkIncludingLane( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.y, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.x); + CVector2D positionOnNextLinkIncludingLane( + pNextLink->GetX() + ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardY, + pNextLink->GetY() - ((pVehicle->AutoPilot.m_nNextLane + pNextLink->OneWayLaneOffset()) * LANE_WIDTH) * nextPathLinkForwardX); + CVector2D distanceToNextNode = (CVector2D)pVehicle->GetPosition() - positionOnCurrentLinkIncludingLane; + float scalarDistanceToNextNode = distanceToNextNode.Magnitude(); + CVector2D distanceBetweenNodes = positionOnNextLinkIncludingLane - positionOnCurrentLinkIncludingLane; + float dp = DotProduct2D(distanceBetweenNodes, distanceToNextNode); + if (scalarDistanceToNextNode < DISTANCE_TO_NEXT_NODE_TO_SELECT_NEW || + dp > 0.0f && scalarDistanceToNextNode < DISTANCE_TO_FACING_NEXT_NODE_TO_SELECT_NEW || + dp / (scalarDistanceToNextNode * distanceBetweenNodes.Magnitude()) > 0.7f || + pVehicle->AutoPilot.m_nNextPathNodeInfo == pVehicle->AutoPilot.m_nCurrentPathNodeInfo){ + if (PickNextNodeAccordingStrategy(pVehicle)) { + switch (pVehicle->AutoPilot.m_nCarMission){ + case MISSION_GOTOCOORDS: + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pBrake = 0.0f; + *pHandbrake = false; + return; + case MISSION_GOTOCOORDS_ACCURATE: + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTO_COORDS_STRAIGHT_ACCURATE; + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pBrake = 0.0f; + *pHandbrake = false; + return; + default: break; + } + } + pCurrentLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nCurrentPathNodeInfo]; + scalarDistanceToNextNode = CVector2D( + pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.y - pVehicle->GetPosition().x, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.x - pVehicle->GetPosition().y).Magnitude(); + pNextLink = &ThePaths.m_carPathLinks[pVehicle->AutoPilot.m_nNextPathNodeInfo]; + currentPathLinkForward.x = pCurrentLink->GetDirX() * pVehicle->AutoPilot.m_nCurrentDirection; + currentPathLinkForward.y = pCurrentLink->GetDirY() * pVehicle->AutoPilot.m_nCurrentDirection; + nextPathLinkForwardX = pNextLink->GetDirX() * pVehicle->AutoPilot.m_nNextDirection; + nextPathLinkForwardY = pNextLink->GetDirY() * pVehicle->AutoPilot.m_nNextDirection; + } + positionOnCurrentLinkIncludingLane.x = pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.y; + positionOnCurrentLinkIncludingLane.y = pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.x; + CVector2D projectedPosition = positionOnCurrentLinkIncludingLane - currentPathLinkForward * scalarDistanceToNextNode * 0.4f; + if (scalarDistanceToNextNode > DISTANCE_TO_NEXT_NODE_TO_CONSIDER_SLOWING_DOWN){ + projectedPosition.x = positionOnCurrentLinkIncludingLane.x; + projectedPosition.y = positionOnCurrentLinkIncludingLane.y; + } + CVector2D distanceToProjectedPosition = projectedPosition - pVehicle->GetPosition(); + float angleCurrentLink = CGeneral::GetATanOfXY(distanceToProjectedPosition.x, distanceToProjectedPosition.y); + float angleForward = CGeneral::GetATanOfXY(forward.x, forward.y); + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_AVOID_CARS) + angleCurrentLink = FindAngleToWeaveThroughTraffic(pVehicle, nil, angleCurrentLink, angleForward); + float steerAngle = LimitRadianAngle(angleCurrentLink - angleForward); + float maxAngle = FindMaxSteerAngle(pVehicle); + steerAngle = Min(maxAngle, Max(-maxAngle, steerAngle)); + if (pVehicle->GetMoveSpeed().Magnitude() > MIN_SPEED_TO_START_LIMITING_STEER) + steerAngle = Min(MAX_ANGLE_TO_STEER_AT_HIGH_SPEED, Max(-MAX_ANGLE_TO_STEER_AT_HIGH_SPEED, steerAngle)); + float currentForwardSpeed = DotProduct(pVehicle->GetMoveSpeed(), pVehicle->GetForward()) * GAME_SPEED_TO_CARAI_SPEED; + float speedStyleMultiplier; + switch (pVehicle->AutoPilot.m_nDrivingStyle) { + case DRIVINGSTYLE_STOP_FOR_CARS: + case DRIVINGSTYLE_SLOW_DOWN_FOR_CARS: + speedStyleMultiplier = FindMaximumSpeedForThisCarInTraffic(pVehicle); +#ifdef FIX_BUGS + if (pVehicle->AutoPilot.m_nCruiseSpeed != 0) +#endif + speedStyleMultiplier /= pVehicle->AutoPilot.m_nCruiseSpeed; + break; + default: + speedStyleMultiplier = 1.0f; + break; + } + switch (pVehicle->AutoPilot.m_nDrivingStyle) { + case DRIVINGSTYLE_STOP_FOR_CARS: + case DRIVINGSTYLE_SLOW_DOWN_FOR_CARS: + if (CTrafficLights::ShouldCarStopForLight(pVehicle, false)){ + CCarAI::CarHasReasonToStop(pVehicle); + speedStyleMultiplier = 0.0f; + } + break; + default: + break; + } + if (CTrafficLights::ShouldCarStopForBridge(pVehicle)){ + CCarAI::CarHasReasonToStop(pVehicle); + speedStyleMultiplier = 0.0f; + } + CVector2D trajectory(pCurrentLink->GetX() + ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.y, + pCurrentLink->GetY() - ((pVehicle->AutoPilot.m_nCurrentLane + pCurrentLink->OneWayLaneOffset()) * LANE_WIDTH) * currentPathLinkForward.x); + trajectory -= pVehicle->GetPosition(); + float speedAngleMultiplier = FindSpeedMultiplier( + CGeneral::GetATanOfXY(trajectory.x, trajectory.y) - angleForward, + MIN_ANGLE_FOR_SPEED_LIMITING, MAX_ANGLE_FOR_SPEED_LIMITING, MIN_LOWERING_SPEED_COEFFICIENT); + float tmpWideMultiplier = FindSpeedMultiplier( + CGeneral::GetATanOfXY(currentPathLinkForward.x, currentPathLinkForward.y) - + CGeneral::GetATanOfXY(nextPathLinkForwardX, nextPathLinkForwardY), + MIN_ANGLE_FOR_SPEED_LIMITING_BETWEEN_NODES, MAX_ANGLE_FOR_SPEED_LIMITING, MIN_LOWERING_SPEED_COEFFICIENT); + float speedNodesMultiplier; + if (scalarDistanceToNextNode > DISTANCE_TO_NEXT_NODE_TO_CONSIDER_SLOWING_DOWN || pVehicle->AutoPilot.m_nCruiseSpeed < 12) + speedNodesMultiplier = 1.0f; + else + speedNodesMultiplier = 1.0f - + (1.0f - scalarDistanceToNextNode / DISTANCE_TO_NEXT_NODE_TO_CONSIDER_SLOWING_DOWN) * + (1.0f - tmpWideMultiplier); + float speedMultiplier = Min(speedStyleMultiplier, Min(speedAngleMultiplier, speedNodesMultiplier)); + float speed = pVehicle->AutoPilot.m_nCruiseSpeed * speedMultiplier; + float speedDifference = speed - currentForwardSpeed; + if (speed < 0.05f && speedDifference < 0.03f){ + *pBrake = 1.0f; + *pAccel = 0.0f; + }else if (speedDifference <= 0.0f){ + *pBrake = Min(0.5f, -speedDifference * 0.05f); + *pAccel = 0.0f; + }else if (currentForwardSpeed < 2.0f){ + *pBrake = 0.0f; + *pAccel = Min(1.0f, speedDifference * 0.25f); + }else{ + *pBrake = 0.0f; + *pAccel = Min(1.0f, speedDifference * 0.125f); + } + *pSwerve = steerAngle; + *pHandbrake = false; +} + +void CCarCtrl::SteerAICarWithPhysicsHeadingForTarget(CVehicle* pVehicle, CPhysical* pTarget, float targetX, float targetY, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + *pHandbrake = false; + CVector2D forward = pVehicle->GetForward(); + forward.NormaliseSafe(); + float angleToTarget = CGeneral::GetATanOfXY(targetX - pVehicle->GetPosition().x, targetY - pVehicle->GetPosition().y); + float angleForward = CGeneral::GetATanOfXY(forward.x, forward.y); + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_AVOID_CARS) + angleToTarget = FindAngleToWeaveThroughTraffic(pVehicle, pTarget, angleToTarget, angleForward); + float steerAngle = LimitRadianAngle(angleToTarget - angleForward); + if (pVehicle->GetMoveSpeed().Magnitude() > MIN_SPEED_TO_APPLY_HANDBRAKE) + if (ABS(steerAngle) > MIN_ANGLE_TO_APPLY_HANDBRAKE) + *pHandbrake = true; + float maxAngle = FindMaxSteerAngle(pVehicle); + steerAngle = Min(maxAngle, Max(-maxAngle, steerAngle)); + float speedMultiplier = FindSpeedMultiplier(CGeneral::GetATanOfXY(targetX - pVehicle->GetPosition().x, targetY - pVehicle->GetPosition().y) - angleForward, + MIN_ANGLE_FOR_SPEED_LIMITING, MAX_ANGLE_FOR_SPEED_LIMITING, MIN_LOWERING_SPEED_COEFFICIENT); + float speedTarget = pVehicle->AutoPilot.m_nCruiseSpeed * speedMultiplier; + float currentSpeed = pVehicle->GetMoveSpeed().Magnitude() * GAME_SPEED_TO_CARAI_SPEED; + float speedDiff = speedTarget - currentSpeed; + if (speedDiff <= 0.0f){ + *pAccel = 0.0f; + *pBrake = Min(0.5f, -speedDiff * 0.05f); + }else if (currentSpeed < 25.0f){ + *pAccel = Min(1.0f, speedDiff * 0.1f); + *pBrake = 0.0f; + }else{ + *pAccel = 1.0f; + *pBrake = 0.0f; + } + *pSwerve = steerAngle; +} + +void CCarCtrl::SteerAICarWithPhysicsTryingToBlockTarget(CVehicle* pVehicle, float targetX, float targetY, float targetSpeedX, float targetSpeedY, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + CVector2D targetPos(targetX, targetY); + CVector2D offset(targetSpeedX, targetSpeedY); + float trajectoryLen = offset.Magnitude(); + if (trajectoryLen > MAX_SPEED_TO_ACCOUNT_IN_INTERCEPTING) + offset *= MAX_SPEED_TO_ACCOUNT_IN_INTERCEPTING / trajectoryLen; + targetPos += offset * GAME_SPEED_TO_CARAI_SPEED; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + SteerAICarWithPhysicsHeadingForTarget(pVehicle, nil, targetPos.x, targetPos.y, pSwerve, pAccel, pBrake, pHandbrake); + if ((targetPos - pVehicle->GetPosition()).MagnitudeSqr() < SQR(DISTANCE_TO_SWITCH_FROM_BLOCK_TO_STOP)) + pVehicle->AutoPilot.m_nCarMission = (pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKCAR_CLOSE) ? + MISSION_BLOCKCAR_HANDBRAKESTOP : MISSION_BLOCKPLAYER_HANDBRAKESTOP; +} +void CCarCtrl::SteerAICarWithPhysicsTryingToBlockTarget_Stop(CVehicle* pVehicle, float targetX, float targetY, float targetSpeedX, float targetSpeedY, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + *pSwerve = 0.0f; + *pAccel = 0.0f; + *pBrake = 1.0f; + *pHandbrake = true; + float distanceToTargetSqr = (CVector2D(targetX, targetY) - pVehicle->GetPosition()).MagnitudeSqr(); + if (distanceToTargetSqr > SQR(DISTANCE_TO_SWITCH_FROM_STOP_TO_BLOCK)){ + pVehicle->AutoPilot.m_nCarMission = (pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKCAR_HANDBRAKESTOP) ? + MISSION_BLOCKCAR_CLOSE : MISSION_BLOCKPLAYER_CLOSE; + return; + } + if (pVehicle->AutoPilot.m_nCarMission == MISSION_BLOCKCAR_HANDBRAKESTOP){ + if (((CVector2D)pVehicle->GetMoveSpeed()).MagnitudeSqr() < SQR(0.01f) && + CVector2D(targetSpeedX, targetSpeedY).MagnitudeSqr() < SQR(0.02f) && + pVehicle->bIsLawEnforcer){ + CCarAI::TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + }else{ + if (FindPlayerVehicle() && FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f) +#ifdef FIX_BUGS + pVehicle->m_nTimeBlocked += CTimer::GetTimeStepInMilliseconds(); +#else + pVehicle->m_nTimeBlocked += 1000.0f / 60.0f * CTimer::GetTimeStep(); // very doubtful constant +#endif + else + pVehicle->m_nTimeBlocked = 0; + if (FindPlayerVehicle() == nil || FindPlayerVehicle()->IsUpsideDown() || + FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.05f && + pVehicle->m_nTimeBlocked > TIME_COPS_WAIT_TO_EXIT_AFTER_STOPPING){ + if (pVehicle->bIsLawEnforcer && distanceToTargetSqr < SQR(DISTANCE_TO_SWITCH_FROM_STOP_TO_BLOCK)){ + CCarAI::TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + } + } +} + +void CCarCtrl::SteerAIBoatWithPhysicsHeadingForTarget(CBoat* pBoat, float targetX, float targetY, float* pSwerve, float* pAccel, float* pBrake) +{ + CVector2D forward(pBoat->GetForward()); + forward.NormaliseSafe(); + CVector2D distanceToTarget = CVector2D(targetX, targetY) - pBoat->GetPosition(); + float angleToTarget = CGeneral::GetATanOfXY(distanceToTarget.x, distanceToTarget.y); + float angleForward = CGeneral::GetATanOfXY(forward.x, forward.y); + float angleDiff = LimitRadianAngle(angleToTarget - angleForward); + angleDiff = Min(DEFAULT_MAX_STEER_ANGLE, Max(-DEFAULT_MAX_STEER_ANGLE, angleDiff)); + float currentSpeed = pBoat->GetMoveSpeed().Magnitude2D(); // +0.0f for some reason + float speedDiff = pBoat->AutoPilot.m_nCruiseSpeed - currentSpeed * 60.0f; + if (speedDiff > 0.0f){ + float accRemaining = speedDiff / pBoat->AutoPilot.m_nCruiseSpeed; + *pAccel = (accRemaining > 0.25f) ? 1.0f : 1.0f - (0.25f - accRemaining) * 4.0f; + }else + *pAccel = (speedDiff < -5.0f) ? -0.2f : -0.1f; + *pBrake = 0.0f; + *pSwerve = angleDiff; +} + +void +CCarCtrl::RegisterVehicleOfInterest(CVehicle* pVehicle) +{ + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (apCarsToKeep[i] == pVehicle) { + aCarsToKeepTime[i] = CTimer::GetTimeInMilliseconds(); + return; + } + } + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (!apCarsToKeep[i]) { + apCarsToKeep[i] = pVehicle; + aCarsToKeepTime[i] = CTimer::GetTimeInMilliseconds(); + return; + } + } + uint32 oldestCarWeKeepTime = UINT32_MAX; + int oldestCarWeKeepIndex = 0; + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (apCarsToKeep[i] && aCarsToKeepTime[i] < oldestCarWeKeepTime) { + oldestCarWeKeepTime = aCarsToKeepTime[i]; + oldestCarWeKeepIndex = i; + } + } + apCarsToKeep[oldestCarWeKeepIndex] = pVehicle; + aCarsToKeepTime[oldestCarWeKeepIndex] = CTimer::GetTimeInMilliseconds(); +} + +bool +CCarCtrl::IsThisVehicleInteresting(CVehicle* pVehicle) +{ + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (apCarsToKeep[i] == pVehicle) + return true; + } + return false; +} + +void CCarCtrl::RemoveFromInterestingVehicleList(CVehicle* pVehicle) +{ + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + if (apCarsToKeep[i] == pVehicle) + apCarsToKeep[i] = nil; + } +} + +void CCarCtrl::ClearInterestingVehicleList() +{ + for (int i = 0; i < MAX_CARS_TO_KEEP; i++) { + apCarsToKeep[i] = nil; + } +} + +void CCarCtrl::SwitchVehicleToRealPhysics(CVehicle* pVehicle) +{ + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds(); +} + +void CCarCtrl::JoinCarWithRoadSystem(CVehicle* pVehicle) +{ + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode = 0; + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = pVehicle->AutoPilot.m_nPreviousPathNodeInfo = pVehicle->AutoPilot.m_nNextPathNodeInfo = 0; + int nodeId = ThePaths.FindNodeClosestToCoorsFavourDirection(pVehicle->GetPosition(), 0, pVehicle->GetForward().x, pVehicle->GetForward().y); + CPathNode* pNode = &ThePaths.m_pathNodes[nodeId]; + int prevNodeId = -1; + float minDistance = 999999.9f; + for (int i = 0; i < pNode->numLinks; i++){ + int candidateId = ThePaths.ConnectedNode(i + pNode->firstLink); + CPathNode* pCandidateNode = &ThePaths.m_pathNodes[candidateId]; + float distance = (pCandidateNode->GetPosition() - pNode->GetPosition()).Magnitude2D(); + if (distance < minDistance){ + minDistance = distance; + prevNodeId = candidateId; + } + } + if (prevNodeId < 0) + return; + CVector2D forward = pVehicle->GetForward(); + CPathNode* pPrevNode = &ThePaths.m_pathNodes[prevNodeId]; + if (forward.x == 0.0f && forward.y == 0.0f) + forward.x = 1.0f; + if (DotProduct2D(pNode->GetPosition() - pPrevNode->GetPosition(), forward) < 0.0f){ + int tmp; + tmp = prevNodeId; + prevNodeId = nodeId; + nodeId = tmp; + } + pVehicle->AutoPilot.m_nPrevRouteNode = 0; + pVehicle->AutoPilot.m_nCurrentRouteNode = prevNodeId; + pVehicle->AutoPilot.m_nNextRouteNode = nodeId; + pVehicle->AutoPilot.m_nPathFindNodesCount = 0; + FindLinksToGoWithTheseNodes(pVehicle); + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane = 0; +} + +bool CCarCtrl::JoinCarWithRoadSystemGotoCoors(CVehicle* pVehicle, CVector vecTarget, bool isProperNow) +{ + pVehicle->AutoPilot.m_vecDestinationCoors = vecTarget; + ThePaths.DoPathSearch(0, pVehicle->GetPosition(), -1, vecTarget, pVehicle->AutoPilot.m_aPathFindNodesInfo, + &pVehicle->AutoPilot.m_nPathFindNodesCount, NUM_PATH_NODES_IN_AUTOPILOT, pVehicle, nil, 999999.9f, -1); + ThePaths.RemoveBadStartNode(pVehicle->GetPosition(), + pVehicle->AutoPilot.m_aPathFindNodesInfo, &pVehicle->AutoPilot.m_nPathFindNodesCount); + if (pVehicle->AutoPilot.m_nPathFindNodesCount < 2){ + pVehicle->AutoPilot.m_nPrevRouteNode = pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_nNextRouteNode = 0; + return true; + } + pVehicle->AutoPilot.m_nPrevRouteNode = 0; + pVehicle->AutoPilot.m_nCurrentRouteNode = pVehicle->AutoPilot.m_aPathFindNodesInfo[0] - ThePaths.m_pathNodes; + pVehicle->AutoPilot.RemoveOnePathNode(); + pVehicle->AutoPilot.m_nNextRouteNode = pVehicle->AutoPilot.m_aPathFindNodesInfo[0] - ThePaths.m_pathNodes; + pVehicle->AutoPilot.RemoveOnePathNode(); + FindLinksToGoWithTheseNodes(pVehicle); + pVehicle->AutoPilot.m_nNextLane = pVehicle->AutoPilot.m_nCurrentLane = 0; + return false; +} + +void CCarCtrl::FindLinksToGoWithTheseNodes(CVehicle* pVehicle) +{ + int nextLink; + CPathNode* pCurNode = &ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nCurrentRouteNode]; + for (nextLink = 0; nextLink < 12; nextLink++) + if (ThePaths.ConnectedNode(nextLink + pCurNode->firstLink) == pVehicle->AutoPilot.m_nNextRouteNode) + break; + pVehicle->AutoPilot.m_nNextPathNodeInfo = ThePaths.m_carPathConnections[nextLink + pCurNode->firstLink]; + pVehicle->AutoPilot.m_nNextDirection = (pVehicle->AutoPilot.m_nCurrentRouteNode >= pVehicle->AutoPilot.m_nNextRouteNode) ? 1 : -1; + int curLink; + int curConnection; + if (pCurNode->numLinks == 1) { + curLink = 0; + curConnection = ThePaths.m_carPathConnections[pCurNode->firstLink]; + }else{ + curConnection = pVehicle->AutoPilot.m_nNextPathNodeInfo; + while (curConnection == pVehicle->AutoPilot.m_nNextPathNodeInfo){ + curLink = CGeneral::GetRandomNumber() % pCurNode->numLinks; + curConnection = ThePaths.m_carPathConnections[curLink + pCurNode->firstLink]; + } + } + pVehicle->AutoPilot.m_nCurrentPathNodeInfo = curConnection; + pVehicle->AutoPilot.m_nCurrentDirection = (ThePaths.ConnectedNode(curLink + pCurNode->firstLink) >= pVehicle->AutoPilot.m_nCurrentRouteNode) ? 1 : -1; +} + +void CCarCtrl::GenerateEmergencyServicesCar(void) +{ + if (FindPlayerPed()->m_pWanted->GetWantedLevel() > 3) + return; + if (NumFiretrucksOnDuty + NumAmbulancesOnDuty + NumParkedCars + NumMissionCars + + NumLawEnforcerCars + NumRandomCars > MaxNumberOfCarsInUse) + return; + if (NumAmbulancesOnDuty == 0){ + if (gAccidentManager.CountActiveAccidents() < 2){ + if (CStreaming::HasModelLoaded(MI_AMBULAN)) + CStreaming::SetModelIsDeletable(MI_MEDIC); + }else{ + float distance = 30.0f; + CAccident* pNearestAccident = gAccidentManager.FindNearestAccident(FindPlayerCoors(), &distance); + if (pNearestAccident){ + if (CountCarsOfType(MI_AMBULAN) < 2 && CTimer::GetTimeInMilliseconds() > LastTimeAmbulanceCreated + 30000){ + CStreaming::RequestModel(MI_AMBULAN, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestModel(MI_MEDIC, STREAMFLAGS_DONT_REMOVE); + if (CStreaming::HasModelLoaded(MI_AMBULAN) && CStreaming::HasModelLoaded(MI_MEDIC)){ + if (GenerateOneEmergencyServicesCar(MI_AMBULAN, pNearestAccident->m_pVictim->GetPosition())) + LastTimeAmbulanceCreated = CTimer::GetTimeInMilliseconds(); + } + } + } + } + } + if (NumFiretrucksOnDuty == 0){ + if (gFireManager.GetTotalActiveFires() < 3){ + if (CStreaming::HasModelLoaded(MI_FIRETRUCK)) + CStreaming::SetModelIsDeletable(MI_FIREMAN); + }else{ + float distance = 30.0f; + CFire* pNearestFire = gFireManager.FindNearestFire(FindPlayerCoors(), &distance); + if (pNearestFire) { + if (CountCarsOfType(MI_FIRETRUCK) < 2 && CTimer::GetTimeInMilliseconds() > LastTimeFireTruckCreated + 35000){ + CStreaming::RequestModel(MI_FIRETRUCK, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestModel(MI_FIREMAN, STREAMFLAGS_DONT_REMOVE); + if (CStreaming::HasModelLoaded(MI_FIRETRUCK) && CStreaming::HasModelLoaded(MI_FIREMAN)){ + if (GenerateOneEmergencyServicesCar(MI_FIRETRUCK, pNearestFire->m_vecPos)) + LastTimeFireTruckCreated = CTimer::GetTimeInMilliseconds(); + } + } + } + } + } +} + +bool CCarCtrl::GenerateOneEmergencyServicesCar(uint32 mi, CVector vecPos) +{ + CVector pPlayerPos = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + bool created = false; + int attempts = 0; + CVector spawnPos; + int curNode, nextNode; + float posBetweenNodes; + while (!created && attempts < 5){ + if (ThePaths.NewGenerateCarCreationCoors(pPlayerPos.x, pPlayerPos.y, 0.707f, 0.707f, + 120.0f, -1.0f, true, &spawnPos, &curNode, &nextNode, &posBetweenNodes, false)){ + int16 colliding[2]; + CWorld::FindObjectsKindaColliding(spawnPos, 10.0f, true, colliding, 2, nil, false, true, true, false, false); + if (colliding[0] == 0) + created = true; + } + attempts += 1; + } + if (attempts >= 5) + return false; + CAutomobile* pVehicle = new CAutomobile(mi, RANDOM_VEHICLE); + pVehicle->AutoPilot.m_vecDestinationCoors = vecPos; + pVehicle->SetPosition(spawnPos); + pVehicle->AutoPilot.m_nCarMission = (JoinCarWithRoadSystemGotoCoors(pVehicle, vecPos, false)) ? MISSION_GOTOCOORDS_STRAIGHT : MISSION_GOTOCOORDS; + pVehicle->AutoPilot.m_fMaxTrafficSpeed = pVehicle->AutoPilot.m_nCruiseSpeed = 25; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + CVector2D direction = vecPos - spawnPos; + direction.NormaliseSafe(); + pVehicle->GetForward() = CVector(direction.x, direction.y, 0.0f); + pVehicle->GetRight() = CVector(direction.y, -direction.x, 0.0f); + pVehicle->GetUp() = CVector(0.0f, 0.0f, 1.0f); + spawnPos.z = posBetweenNodes * ThePaths.m_pathNodes[curNode].GetZ() + (1.0f - posBetweenNodes) * ThePaths.m_pathNodes[nextNode].GetZ(); + float groundZ = INFINITE_Z; + CColPoint colPoint; + CEntity* pEntity; + if (CWorld::ProcessVerticalLine(spawnPos, 1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil)) + groundZ = colPoint.point.z; + if (CWorld::ProcessVerticalLine(spawnPos, -1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil)) { + if (ABS(colPoint.point.z - spawnPos.z) < ABS(groundZ - spawnPos.z)) + groundZ = colPoint.point.z; + } + if (groundZ == INFINITE_Z) { + delete pVehicle; + return false; + } + spawnPos.z = groundZ + pVehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + pVehicle->SetPosition(spawnPos); + pVehicle->SetMoveSpeed(CVector(0.0f, 0.0f, 0.0f)); + pVehicle->SetStatus(STATUS_PHYSICS); + switch (mi){ + case MI_FIRETRUCK: + pVehicle->bIsFireTruckOnDuty = true; + ++NumFiretrucksOnDuty; + CCarAI::AddFiretruckOccupants(pVehicle); + break; + case MI_AMBULAN: + pVehicle->bIsAmbulanceOnDuty = true; + ++NumAmbulancesOnDuty; + CCarAI::AddAmbulanceOccupants(pVehicle); + break; + } + pVehicle->m_bSirenOrAlarm = true; + CWorld::Add(pVehicle); + printf("CREATED EMERGENCY VEHICLE\n"); + return true; +} + +void CCarCtrl::UpdateCarCount(CVehicle* pVehicle, bool remove) +{ + if (remove){ + switch (pVehicle->VehicleCreatedBy){ + case RANDOM_VEHICLE: + if (pVehicle->bIsLawEnforcer) + --NumLawEnforcerCars; + --NumRandomCars; + return; + case MISSION_VEHICLE: + --NumMissionCars; + return; + case PARKED_VEHICLE: + --NumParkedCars; + return; + case PERMANENT_VEHICLE: + --NumPermanentCars;; + return; + } + } + else{ + switch (pVehicle->VehicleCreatedBy){ + case RANDOM_VEHICLE: + if (pVehicle->bIsLawEnforcer) + ++NumLawEnforcerCars; + ++NumRandomCars; + return; + case MISSION_VEHICLE: + ++NumMissionCars; + return; + case PARKED_VEHICLE: + ++NumParkedCars; + return; + case PERMANENT_VEHICLE: + ++NumPermanentCars;; + return; + } + } +} + +bool CCarCtrl::ThisRoadObjectCouldMove(int16 mi) +{ + return mi == MI_BRIDGELIFT || mi == MI_BRIDGEROADSEGMENT; +} + +bool CCarCtrl::MapCouldMoveInThisArea(float x, float y) +{ + // bridge moves up and down + return x > -342.0f && x < -219.0f && + y > -677.0f && y < -580.0f; +} diff --git a/src/control/CarCtrl.h b/src/control/CarCtrl.h new file mode 100644 index 0000000..457224f --- /dev/null +++ b/src/control/CarCtrl.h @@ -0,0 +1,143 @@ +#pragma once +#include "PathFind.h" +#include "Boat.h" +#include "Vehicle.h" + +#define GAME_SPEED_TO_METERS_PER_SECOND 50.0f +#define METERS_PER_SECOND_TO_GAME_SPEED (1.0f / GAME_SPEED_TO_METERS_PER_SECOND) +#define GAME_SPEED_TO_CARAI_SPEED 60.0f +#define TIME_COPS_WAIT_TO_EXIT_AFTER_STOPPING 2500 + +class CZoneInfo; + +enum{ + MAX_CARS_TO_KEEP = 2, + MAX_CAR_MODELS_IN_ARRAY = 256, +}; + +#define LANE_WIDTH 5.0f + +#ifdef FIX_BUGS +#define FIX_PATHFIND_BUG +#endif + +class CCarCtrl +{ +public: + enum eCarClass { + POOR = 0, + RICH, + EXEC, + WORKER, + SPECIAL, + BIG, + TAXI, + TOTAL_CUSTOM_CLASSES, + MAFIA, + TRIAD, + DIABLO, + YAKUZA, + YARDIE, + COLOMB, + NINES, + GANG8, + GANG9, + COPS + }; + + static void SwitchVehicleToRealPhysics(CVehicle*); + static void AddToCarArray(int32 id, int32 vehclass); + static void UpdateCarCount(CVehicle*, bool); + static int32 ChooseCarModel(int32 vehclass); + static bool JoinCarWithRoadSystemGotoCoors(CVehicle*, CVector, bool); + static void JoinCarWithRoadSystem(CVehicle*); + static void UpdateCarOnRails(CVehicle*); + static bool MapCouldMoveInThisArea(float x, float y); + static void ScanForPedDanger(CVehicle *veh); + static void RemoveFromInterestingVehicleList(CVehicle*); + static void GenerateRandomCars(void); + static void GenerateOneRandomCar(void); + static void GenerateEmergencyServicesCar(void); + static int32 ChooseModel(CZoneInfo*, CVector*, int*); + static int32 ChoosePoliceCarModel(void); + static int32 ChooseGangCarModel(int32 gang); + static void RemoveDistantCars(void); + static void PossiblyRemoveVehicle(CVehicle*); + static bool IsThisVehicleInteresting(CVehicle*); + static void RegisterVehicleOfInterest(CVehicle*); + static int32 CountCarsOfType(int32 mi); + static void SlowCarOnRailsDownForTrafficAndLights(CVehicle*); + static bool PickNextNodeAccordingStrategy(CVehicle*); + static void DragCarToPoint(CVehicle*, CVector*); + static float FindMaximumSpeedForThisCarInTraffic(CVehicle*); + static void SlowCarDownForCarsSectorList(CPtrList&, CVehicle*, float, float, float, float, float*, float); + static void SlowCarDownForPedsSectorList(CPtrList&, CVehicle*, float, float, float, float, float*, float); + static void SlowCarDownForOtherCar(CEntity*, CVehicle*, float*, float); + static float TestCollisionBetween2MovingRects(CVehicle*, CVehicle*, float, float, CVector*, CVector*, uint8); + static float FindAngleToWeaveThroughTraffic(CVehicle*, CPhysical*, float, float); + static void WeaveThroughCarsSectorList(CPtrList&, CVehicle*, CPhysical*, float, float, float, float, float*, float*); + static void WeaveForOtherCar(CEntity*, CVehicle*, float*, float*); + static void WeaveThroughPedsSectorList(CPtrList&, CVehicle*, CPhysical*, float, float, float, float, float*, float*); + static void WeaveForPed(CEntity*, CVehicle*, float*, float*); + static void WeaveThroughObjectsSectorList(CPtrList&, CVehicle*, float, float, float, float, float*, float*); + static void WeaveForObject(CEntity*, CVehicle*, float*, float*); +#ifdef FIX_PATHFIND_BUG + static void PickNextNodeToChaseCar(CVehicle*, float, float, float, CVehicle*); +#else + static void PickNextNodeToChaseCar(CVehicle*, float, float, CVehicle*); +#endif + static bool PickNextNodeToFollowPath(CVehicle*); + static void PickNextNodeRandomly(CVehicle*); + static uint8 FindPathDirection(int32, int32, int32); + static void Init(void); + static void ReInit(void); + static float FindSpeedMultiplier(float, float, float, float); + static void SteerAICarWithPhysics(CVehicle*); + static void SteerAICarWithPhysics_OnlyMission(CVehicle*, float*, float*, float*, bool*); + static void SteerAIBoatWithPhysics(CBoat*); + static float FindMaxSteerAngle(CVehicle*); + static void SteerAICarWithPhysicsFollowPath(CVehicle*, float*, float*, float*, bool*); + static void SteerAICarWithPhysicsHeadingForTarget(CVehicle*, CPhysical*, float, float, float*, float*, float*, bool*); + static void SteerAICarWithPhysicsTryingToBlockTarget(CVehicle*, float, float, float, float, float*, float*, float*, bool*); + static void SteerAICarWithPhysicsTryingToBlockTarget_Stop(CVehicle*, float, float, float, float, float*, float*, float*, bool*); + static void SteerAIBoatWithPhysicsHeadingForTarget(CBoat*, float, float, float*, float*, float*); + static bool ThisRoadObjectCouldMove(int16); + static void ClearInterestingVehicleList(); + static void FindLinksToGoWithTheseNodes(CVehicle*); + static bool GenerateOneEmergencyServicesCar(uint32, CVector); + + static float GetPositionAlongCurrentCurve(CVehicle* pVehicle) + { + uint32 timeInCurve = CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeEnteredCurve; + return (float)timeInCurve / pVehicle->AutoPilot.m_nTimeToSpendOnCurrentCurve; + } + + static float LimitRadianAngle(float angle) + { + while (angle < -PI) + angle += TWOPI; + while (angle > PI) + angle -= TWOPI; + return angle; + } + + static int32 NumLawEnforcerCars; + static int32 NumAmbulancesOnDuty; + static int32 NumFiretrucksOnDuty; + static int32 NumRandomCars; + static int32 NumMissionCars; + static int32 NumParkedCars; + static int32 NumPermanentCars; + static bool bCarsGeneratedAroundCamera; + static float CarDensityMultiplier; + static int8 CountDownToCarsAtStart; + static int32 MaxNumberOfCarsInUse; + static uint32 LastTimeLawEnforcerCreated; + static uint32 LastTimeFireTruckCreated; + static uint32 LastTimeAmbulanceCreated; + static int32 TotalNumOfCarsOfRating[TOTAL_CUSTOM_CLASSES]; + static int32 NextCarOfRating[TOTAL_CUSTOM_CLASSES]; + static int32 CarArrays[TOTAL_CUSTOM_CLASSES][MAX_CAR_MODELS_IN_ARRAY]; +}; + +extern CVehicle* apCarsToKeep[MAX_CARS_TO_KEEP]; \ No newline at end of file diff --git a/src/control/Curves.cpp b/src/control/Curves.cpp new file mode 100644 index 0000000..0a01a7a --- /dev/null +++ b/src/control/Curves.cpp @@ -0,0 +1,31 @@ +#include "common.h" + +#include "Curves.h" + +float CCurves::CalcSpeedScaleFactor(CVector* pPoint1, CVector* pPoint2, float dir1X, float dir1Y, float dir2X, float dir2Y) +{ + CVector2D dir1(dir1X, dir1Y); + CVector2D dir2(dir2X, dir2Y); + float distance = (*pPoint1 - *pPoint2).Magnitude2D(); + float dp = DotProduct2D(dir1, dir2); + if (dp > 0.9f) + return distance + Abs((pPoint1->x * dir1Y - pPoint1->y * dir1X) - (pPoint2->x * dir1Y - pPoint2->y * dir1X)); + else + return ((1.0f - dp) * 0.2f + 1.0f) * distance; +} + +void CCurves::CalcCurvePoint(CVector* pPos1, CVector* pPos2, CVector* pDir1, CVector* pDir2, float between, int32 timeOnCurve, CVector* pOutPos, CVector* pOutDir) +{ + float actualFactor = CalcSpeedScaleFactor(pPos1, pPos2, pDir1->x, pDir1->y, pDir2->x, pDir2->y); + CVector2D dir1 = *pDir1 * actualFactor; + CVector2D dir2 = *pDir2 * actualFactor; + float curveCoef = 0.5f - 0.5f * Cos(3.1415f * between); + *pOutPos = CVector( + (pPos1->x + between * dir1.x) * (1.0f - curveCoef) + (pPos2->x - (1 - between) * dir2.x) * curveCoef, + (pPos1->y + between * dir1.y) * (1.0f - curveCoef) + (pPos2->y - (1 - between) * dir2.y) * curveCoef, + 0.0f); + *pOutDir = CVector( + (dir1.x * (1.0f - curveCoef) + dir2.x * curveCoef) / (timeOnCurve * 0.001f), + (dir1.y * (1.0f - curveCoef) + dir2.y * curveCoef) / (timeOnCurve * 0.001f), + 0.0f); +} \ No newline at end of file diff --git a/src/control/Curves.h b/src/control/Curves.h new file mode 100644 index 0000000..5d4e05a --- /dev/null +++ b/src/control/Curves.h @@ -0,0 +1,9 @@ +#pragma once +class CVector; + +class CCurves +{ +public: + static float CalcSpeedScaleFactor(CVector*, CVector*, float, float, float, float); + static void CalcCurvePoint(CVector*, CVector*, CVector*, CVector*, float, int32, CVector*, CVector*); +}; diff --git a/src/control/Darkel.cpp b/src/control/Darkel.cpp new file mode 100644 index 0000000..9f6809d --- /dev/null +++ b/src/control/Darkel.cpp @@ -0,0 +1,448 @@ +#include "common.h" + +#include "main.h" +#include "Darkel.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Timer.h" +#include "DMAudio.h" +#include "Population.h" +#include "Weapon.h" +#include "World.h" +#include "Stats.h" +#include "Font.h" +#include "Text.h" +#include "Vehicle.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif + +#define FRENZY_ANY_PED -1 +#define FRENZY_ANY_CAR -2 + +int32 CDarkel::TimeLimit; +int32 CDarkel::PreviousTime; +int32 CDarkel::TimeOfFrenzyStart; +int32 CDarkel::WeaponType; +int32 CDarkel::AmmoInterruptedWeapon; +int32 CDarkel::KillsNeeded; +int8 CDarkel::InterruptedWeapon; + +/* + * bStandardSoundAndMessages is a completely beta thing, + * makes game handle sounds & messages instead of SCM (just like in GTA2) + * but it's never been used in the game. Has unused sliding text when frenzy completed etc. + */ +bool CDarkel::bStandardSoundAndMessages; +bool CDarkel::bNeedHeadShot; +bool CDarkel::bProperKillFrenzy; +uint16 CDarkel::Status; +uint16 CDarkel::RegisteredKills[NUM_DEFAULT_MODELS]; +int32 CDarkel::ModelToKill; +int32 CDarkel::ModelToKill2; +int32 CDarkel::ModelToKill3; +int32 CDarkel::ModelToKill4; +wchar *CDarkel::pStartMessage; + +uint8 +CDarkel::CalcFade(uint32 time, uint32 start, uint32 end) +{ + if (time >= start && time <= end) { + if (time >= start + 500) { + if (time <= end - 500) + return 255; + else + return 255 * (end - time) / 500; + } else + return 255 * (time - start) / 500; + } else + return 0; +} + +// Screen positions taken from VC +void +CDarkel::DrawMessages() +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + switch (Status) { + case KILLFRENZY_ONGOING: + { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 30)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 30); +#endif + CFont::SetCentreOn(); + CFont::SetPropOn(); + uint32 timePassedSinceStart = CTimer::GetTimeInMilliseconds() - CDarkel::TimeOfFrenzyStart; + if (CDarkel::bStandardSoundAndMessages) { + if (timePassedSinceStart >= 3000 && timePassedSinceStart < 11000) { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.3f), SCREEN_SCALE_Y(1.3f)); +#else + CFont::SetScale(1.3f, 1.3f); +#endif + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 128, CalcFade(timePassedSinceStart, 3000, 11000))); + CFont::SetFontStyle(FONT_BANK); + if (pStartMessage) { + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, pStartMessage); + } + } + } else { + if (timePassedSinceStart < 8000) { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.3f), SCREEN_SCALE_Y(1.3f)); +#else + CFont::SetScale(1.3f, 1.3f); +#endif + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 128, CalcFade(timePassedSinceStart, 0, 8000))); + CFont::SetFontStyle(FONT_BANK); + if (pStartMessage) { + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, pStartMessage); + } + } + } +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.75f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(0.75f, 1.5f); +#endif + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + if (CDarkel::TimeLimit >= 0) { + uint32 timeLeft = CDarkel::TimeLimit - (CTimer::GetTimeInMilliseconds() - CDarkel::TimeOfFrenzyStart); + sprintf(gString, "%d:%02d", timeLeft / 60000, timeLeft % 60000 / 1000); + AsciiToUnicode(gString, gUString); + if (timeLeft > 4000 || CTimer::GetFrameCounter() & 1) { + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#if defined(PS2_HUD) || defined(FIX_BUGS) + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f - 1.0f), SCREEN_SCALE_Y(108.0f + 1.0f), gUString); + #else + CFont::PrintString(SCREEN_WIDTH-(34.0f - 1.0f), 108.0f + 1.0f, gUString); + #endif +#else + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f + 1.0f), SCREEN_SCALE_Y(108.0f + 1.0f), gUString); +#endif + CFont::SetColor(CRGBA(150, 100, 255, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f), SCREEN_SCALE_Y(108.0f), gUString); + } + } + sprintf(gString, "%d", (CDarkel::KillsNeeded >= 0 ? CDarkel::KillsNeeded : 0)); + AsciiToUnicode(gString, gUString); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS +#define DARKEL_COUNTER_HEIGHT 143.0f +#else +#define DARKEL_COUNTER_HEIGHT 128.0f +#endif + +#if defined(PS2_HUD) || defined(FIX_BUGS) + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f - 1.0f), SCREEN_SCALE_Y(DARKEL_COUNTER_HEIGHT + 1.0f), gUString); + #else + CFont::PrintString(SCREEN_WIDTH-(34.0f - 1.0f), DARKEL_COUNTER_HEIGHT + 1.0f, gUString); + #endif +#else + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f + 1.0f), SCREEN_SCALE_Y(DARKEL_COUNTER_HEIGHT + 1.0f), gUString); +#endif + CFont::SetColor(CRGBA(255, 128, 128, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f), SCREEN_SCALE_Y(DARKEL_COUNTER_HEIGHT), gUString); +#undef DARKEL_COUNTER_HEIGHT + break; + } + case KILLFRENZY_PASSED: + { + if (CDarkel::bStandardSoundAndMessages) { + uint32 timePassedSinceStart = CTimer::GetTimeInMilliseconds() - CDarkel::TimeOfFrenzyStart; + if (CTimer::GetTimeInMilliseconds() - CDarkel::TimeOfFrenzyStart < 5000) { + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 20); +#endif + CFont::SetCentreOn(); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(1.5f, 1.5f); +#endif + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(128, 255, 128, CalcFade(timePassedSinceStart, 0, 5000))); + CFont::SetFontStyle(FONT_BANK); +#ifdef FIX_BUGS + int y = SCREEN_HEIGHT / 2 + SCREEN_SCALE_Y(25.0f - timePassedSinceStart * 0.01f); +#else + int y = (SCREEN_HEIGHT / 2 + 25) - (timePassedSinceStart * 0.01f); +#endif + CFont::PrintString(SCREEN_WIDTH / 2, y, TheText.Get("KF_3")); + } + } + break; + } + default: + break; + } +} + +void +CDarkel::Init() +{ + Status = KILLFRENZY_NONE; +} + +uint16 +CDarkel::QueryModelsKilledByPlayer(int32 modelId) +{ + return RegisteredKills[modelId]; +} + + +bool +CDarkel::FrenzyOnGoing() +{ + return Status == KILLFRENZY_ONGOING; +} + + +uint16 +CDarkel::ReadStatus() +{ + return Status; +} + +void +CDarkel::RegisterCarBlownUpByPlayer(CVehicle *vehicle) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + if (FrenzyOnGoing()) { + int32 model = vehicle->GetModelIndex(); + if (ModelToKill == FRENZY_ANY_CAR || ModelToKill == model || ModelToKill2 == model || ModelToKill3 == model || ModelToKill4 == model) { + KillsNeeded--; + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_CAR_BLOWN, 0); + } + } + RegisteredKills[vehicle->GetModelIndex()]++; + CStats::CarsExploded++; +} + +void +CDarkel::RegisterKillByPlayer(CPed *victim, eWeaponType weapon, bool headshot) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + if (FrenzyOnGoing() && (weapon == WeaponType + || weapon == WEAPONTYPE_EXPLOSION + || weapon == WEAPONTYPE_UZI_DRIVEBY && WeaponType == WEAPONTYPE_UZI + || weapon == WEAPONTYPE_RAMMEDBYCAR && WeaponType == WEAPONTYPE_RUNOVERBYCAR + || weapon == WEAPONTYPE_RUNOVERBYCAR && WeaponType == WEAPONTYPE_RAMMEDBYCAR + || weapon == WEAPONTYPE_FLAMETHROWER && WeaponType == WEAPONTYPE_MOLOTOV)) { + int32 model = victim->GetModelIndex(); + if (ModelToKill == FRENZY_ANY_PED || ModelToKill == model || ModelToKill2 == model || ModelToKill3 == model || ModelToKill4 == model) { + if (!bNeedHeadShot || headshot) { + KillsNeeded--; + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_KILL, 0); + } + } + } + CStats::PeopleKilledByPlayer++; + RegisteredKills[victim->GetModelIndex()]++; + CStats::PedsKilledOfThisType[victim->bChrisCriminal ? PEDTYPE_CRIMINAL : victim->m_nPedType]++; + if (headshot) + CStats::HeadsPopped++; + CStats::KillsSinceLastCheckpoint++; +} + +void +CDarkel::RegisterKillNotByPlayer(CPed* victim, eWeaponType weapontype) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + CStats::PeopleKilledByOthers++; +} + +void +CDarkel::ResetModelsKilledByPlayer() +{ + for (int i = 0; i < NUM_DEFAULT_MODELS; i++) + RegisteredKills[i] = 0; +} + +void +CDarkel::ResetOnPlayerDeath() +{ + if (Status != KILLFRENZY_ONGOING) + return; + + CPopulation::m_AllRandomPedsThisType = -1; + Status = KILLFRENZY_FAILED; + TimeOfFrenzyStart = CTimer::GetTimeInMilliseconds(); + + eWeaponType fixedWeapon; + if (WeaponType == WEAPONTYPE_UZI_DRIVEBY) + fixedWeapon = WEAPONTYPE_UZI; + else + fixedWeapon = (eWeaponType)WeaponType; + + CPlayerPed *player = FindPlayerPed(); + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + player->m_nSelectedWepSlot = InterruptedWeapon; + player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_nAmmoTotal = CDarkel::AmmoInterruptedWeapon; + } + + if (FindPlayerVehicle()) { + player->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(player->GetWeapon()->m_eWeaponType)->m_nModelId); + player->m_currentWeapon = player->m_nSelectedWepSlot; + player->MakeChangesForNewWeapon(player->m_currentWeapon); + } +} + +void +CDarkel::StartFrenzy(eWeaponType weaponType, int32 time, uint16 kill, int32 modelId0, wchar *text, int32 modelId2, int32 modelId3, int32 modelId4, bool standardSound, bool needHeadShot) +{ + eWeaponType fixedWeapon; + if (weaponType == WEAPONTYPE_UZI_DRIVEBY) + fixedWeapon = WEAPONTYPE_UZI; + else + fixedWeapon = weaponType; + + WeaponType = weaponType; + Status = KILLFRENZY_ONGOING; + KillsNeeded = kill; + ModelToKill = modelId0; + ModelToKill2 = modelId2; + ModelToKill3 = modelId3; + ModelToKill4 = modelId4; + pStartMessage = text; + + if (text == TheText.Get("PAGE_00")) { + CDarkel::bProperKillFrenzy = true; + CDarkel::pStartMessage = nil; + } else + bProperKillFrenzy = false; + + bStandardSoundAndMessages = standardSound; + bNeedHeadShot = needHeadShot; + TimeOfFrenzyStart = CTimer::GetTimeInMilliseconds(); + TimeLimit = time; + PreviousTime = time / 1000; + + CPlayerPed *player = FindPlayerPed(); + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + InterruptedWeapon = player->m_currentWeapon; + player->GiveWeapon(fixedWeapon, 0); + AmmoInterruptedWeapon = player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_nAmmoTotal; + player->GiveWeapon(fixedWeapon, 30000); + player->m_nSelectedWepSlot = player->GetWeaponSlot(fixedWeapon); + player->MakeChangesForNewWeapon(player->m_nSelectedWepSlot); + + if (FindPlayerVehicle()) { + player->m_currentWeapon = player->m_nSelectedWepSlot; + player->GetWeapon()->m_nAmmoInClip = Min(player->GetWeapon()->m_nAmmoTotal, CWeaponInfo::GetWeaponInfo(player->GetWeapon()->m_eWeaponType)->m_nAmountofAmmunition); + player->ClearWeaponTarget(); + } + } + if (CDarkel::bStandardSoundAndMessages) + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_START, 0); +} + +void +CDarkel::Update() +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + + if (Status != KILLFRENZY_ONGOING) + return; + + int32 FrameTime = TimeLimit - (CTimer::GetTimeInMilliseconds() - TimeOfFrenzyStart); + if (FrameTime > 0 || TimeLimit < 0) { + + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_ONGOING, FrameTime); + + int32 PrevTime = FrameTime / 1000; + + if (PrevTime != PreviousTime) { + if (PreviousTime < 12) + DMAudio.PlayFrontEndSound(SOUND_CLOCK_TICK, PrevTime); + PreviousTime = PrevTime; + } + + } else { + CPopulation::m_AllRandomPedsThisType = -1; + Status = KILLFRENZY_FAILED; + TimeOfFrenzyStart = CTimer::GetTimeInMilliseconds(); + + eWeaponType fixedWeapon; + if (WeaponType == WEAPONTYPE_UZI_DRIVEBY) + fixedWeapon = WEAPONTYPE_UZI; + else + fixedWeapon = (eWeaponType)WeaponType; + + CPlayerPed *player = FindPlayerPed(); + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + player->m_nSelectedWepSlot = InterruptedWeapon; + player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_nAmmoTotal = CDarkel::AmmoInterruptedWeapon; + } + + if (FindPlayerVehicle()) { + player->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(player->GetWeapon()->m_eWeaponType)->m_nModelId); + player->m_currentWeapon = player->m_nSelectedWepSlot; + player->MakeChangesForNewWeapon(player->m_currentWeapon); + } + + if (bStandardSoundAndMessages) + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_FAILED, 0); + } + + if (KillsNeeded <= 0) { + CPopulation::m_AllRandomPedsThisType = -1; + Status = KILLFRENZY_PASSED; + + if (bProperKillFrenzy) + CStats::AnotherKillFrenzyPassed(); + + TimeOfFrenzyStart = CTimer::GetTimeInMilliseconds(); + + FindPlayerPed()->m_pWanted->SetWantedLevel(0); + + eWeaponType fixedWeapon; + if (WeaponType == WEAPONTYPE_UZI_DRIVEBY) + fixedWeapon = WEAPONTYPE_UZI; + else + fixedWeapon = (eWeaponType)WeaponType; + + CPlayerPed* player = FindPlayerPed(); + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + player->m_nSelectedWepSlot = InterruptedWeapon; + player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_nAmmoTotal = CDarkel::AmmoInterruptedWeapon; + } + + if (FindPlayerVehicle()) { + player->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(player->GetWeapon()->m_eWeaponType)->m_nModelId); + player->m_currentWeapon = player->m_nSelectedWepSlot; + player->MakeChangesForNewWeapon(player->m_currentWeapon); + } + + if (bStandardSoundAndMessages) + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_PASSED, 0); + } +} diff --git a/src/control/Darkel.h b/src/control/Darkel.h new file mode 100644 index 0000000..0f5c232 --- /dev/null +++ b/src/control/Darkel.h @@ -0,0 +1,53 @@ +#pragma once + +#include "ModelIndices.h" +#include "WeaponType.h" + +class CVehicle; +class CPed; + +enum +{ + KILLFRENZY_NONE, + KILLFRENZY_ONGOING, + KILLFRENZY_PASSED, + KILLFRENZY_FAILED, +}; + +class CDarkel +{ +private: + static int32 TimeLimit; + static int32 PreviousTime; + static int32 TimeOfFrenzyStart; + static int32 WeaponType; + static int32 AmmoInterruptedWeapon; + static int32 KillsNeeded; + static int8 InterruptedWeapon; + static bool bStandardSoundAndMessages; + static bool bNeedHeadShot; + static bool bProperKillFrenzy; + static uint16 Status; + static uint16 RegisteredKills[NUM_DEFAULT_MODELS]; + static int32 ModelToKill; + static int32 ModelToKill2; + static int32 ModelToKill3; + static int32 ModelToKill4; + static wchar *pStartMessage; + +public: + static uint8 CalcFade(uint32 time, uint32 min, uint32 max); + static void DrawMessages(void); + static bool FrenzyOnGoing(); + static void Init(); + static uint16 QueryModelsKilledByPlayer(int32 modelId); + static uint16 ReadStatus(); + static void RegisterCarBlownUpByPlayer(CVehicle *vehicle); + static void RegisterKillByPlayer(CPed *victim, eWeaponType weapontype, bool headshot = false); + static void RegisterKillNotByPlayer(CPed* victim, eWeaponType weapontype); + static void ResetModelsKilledByPlayer(); + static void ResetOnPlayerDeath(); + static void StartFrenzy(eWeaponType weaponType, int32 time, uint16 kill, int32 modelId0, wchar *text, int32 modelId2, int32 modelId3, int32 modelId4, bool standardSound, bool needHeadShot); + static void Update(); + +}; diff --git a/src/control/GameLogic.cpp b/src/control/GameLogic.cpp new file mode 100644 index 0000000..19e0f83 --- /dev/null +++ b/src/control/GameLogic.cpp @@ -0,0 +1,320 @@ +#include "common.h" + +#include "GameLogic.h" +#include "Clock.h" +#include "Stats.h" +#include "Pickups.h" +#include "Timer.h" +#include "Streaming.h" +#include "CutsceneMgr.h" +#include "World.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Camera.h" +#include "Messages.h" +#include "CarCtrl.h" +#include "Restart.h" +#include "Pad.h" +#include "References.h" +#include "Fire.h" +#include "Script.h" +#include "Garages.h" +#include "screendroplets.h" + +uint8 CGameLogic::ActivePlayers; + +void +CGameLogic::InitAtStartOfGame() +{ + ActivePlayers = 1; +} + +void +CGameLogic::PassTime(uint32 time) +{ + int32 minutes, hours, days; + + minutes = time + CClock::GetMinutes(); + hours = CClock::GetHours(); + + for (; minutes >= 60; minutes -= 60) + hours++; + + if (hours > 23) { + days = CStats::DaysPassed; + for (; hours >= 24; hours -= 24) + days++; + CStats::DaysPassed = days; + } + + CClock::SetGameClock(hours, minutes); + CPickups::PassTime(time * 1000); +} + +void +CGameLogic::SortOutStreamingAndMemory(const CVector &pos) +{ + CTimer::Stop(); + CStreaming::FlushRequestList(); + CStreaming::DeleteRwObjectsAfterDeath(pos); + CStreaming::RemoveUnusedModelsInLoadedList(); + CGame::DrasticTidyUpMemory(true); + CStreaming::LoadScene(pos); + CTimer::Update(); +} + +void +CGameLogic::Update() +{ + CVector vecRestartPos; + float fRestartFloat; + + if (CCutsceneMgr::IsCutsceneProcessing()) return; + + CPlayerInfo &pPlayerInfo = CWorld::Players[CWorld::PlayerInFocus]; + switch (pPlayerInfo.m_WBState) { + case WBSTATE_PLAYING: + if (pPlayerInfo.m_pPed->m_nPedState == PED_DEAD) { + pPlayerInfo.m_pPed->ClearAdrenaline(); + pPlayerInfo.KillPlayer(); + } + if (pPlayerInfo.m_pPed->m_nPedState == PED_ARRESTED) { + pPlayerInfo.m_pPed->ClearAdrenaline(); + pPlayerInfo.ArrestPlayer(); + } + break; + case WBSTATE_WASTED: +#ifdef MISSION_REPLAY + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > AddExtraDeathDelay() + 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= AddExtraDeathDelay() + 0x800)) { +#else + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= 0x800)) { +#endif + TheCamera.SetFadeColour(200, 200, 200); + TheCamera.Fade(2.0f, FADE_OUT); + } + +#ifdef MISSION_REPLAY + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) { +#else + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) { +#endif + pPlayerInfo.m_WBState = WBSTATE_PLAYING; + if (pPlayerInfo.m_bGetOutOfHospitalFree) { + pPlayerInfo.m_bGetOutOfHospitalFree = false; + } else { + pPlayerInfo.m_nMoney = Max(0, pPlayerInfo.m_nMoney - 1000); + pPlayerInfo.m_pPed->ClearWeapons(); + } + + if (pPlayerInfo.m_pPed->bInVehicle) { + CVehicle *pVehicle = pPlayerInfo.m_pPed->m_pMyVehicle; + if (pVehicle != nil) { + if (pVehicle->pDriver == pPlayerInfo.m_pPed) { + pVehicle->pDriver = nil; + if (pVehicle->GetStatus() != STATUS_WRECKED) + pVehicle->SetStatus(STATUS_ABANDONED); + } else + pVehicle->RemovePassenger(pPlayerInfo.m_pPed); + } + } + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + CMessages::ClearMessages(); + CCarCtrl::ClearInterestingVehicleList(); + CWorld::ClearExcitingStuffFromArea(pPlayerInfo.GetPos(), 4000.0f, 1); + CRestart::FindClosestHospitalRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat); + CRestart::OverrideHospitalLevel = LEVEL_GENERIC; + CRestart::OverridePoliceStationLevel = LEVEL_GENERIC; + PassTime(720); + RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat); + SortOutStreamingAndMemory(pPlayerInfo.GetPos()); + TheCamera.m_fCamShakeForce = 0.0f; + TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE); + CPad::GetPad(0)->StopShaking(0); + CReferences::RemoveReferencesToPlayer(); + CCarCtrl::CountDownToCarsAtStart = 2; + CPad::GetPad(CWorld::PlayerInFocus)->DisablePlayerControls = PLAYERCONTROL_ENABLED; + if (CRestart::bFadeInAfterNextDeath) { + TheCamera.SetFadeColour(200, 200, 200); + TheCamera.Fade(4.0f, FADE_IN); + } else CRestart::bFadeInAfterNextDeath = true; + } + break; + case WBSTATE_BUSTED: +#ifdef MISSION_REPLAY + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > AddExtraDeathDelay() + 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= AddExtraDeathDelay() + 0x800)) { +#else + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= 0x800)) { +#endif + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(2.0f, FADE_OUT); + } +#ifdef MISSION_REPLAY + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) { +#else + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) { +#endif + pPlayerInfo.m_WBState = WBSTATE_PLAYING; + int takeMoney; + + switch (pPlayerInfo.m_pPed->m_pWanted->GetWantedLevel()) { + case 0: + case 1: + takeMoney = 100; + break; + case 2: + takeMoney = 200; + break; + case 3: + takeMoney = 400; + break; + case 4: + takeMoney = 600; + break; + case 5: + takeMoney = 900; + break; + case 6: + takeMoney = 1500; + break; + } + if (pPlayerInfo.m_bGetOutOfJailFree) { + pPlayerInfo.m_bGetOutOfJailFree = false; + } else { + pPlayerInfo.m_nMoney = Max(0, pPlayerInfo.m_nMoney - takeMoney); + pPlayerInfo.m_pPed->ClearWeapons(); + } + + if (pPlayerInfo.m_pPed->bInVehicle) { + CVehicle *pVehicle = pPlayerInfo.m_pPed->m_pMyVehicle; + if (pVehicle != nil) { + if (pVehicle->pDriver == pPlayerInfo.m_pPed) { + pVehicle->pDriver = nil; + if (pVehicle->GetStatus() != STATUS_WRECKED) + pVehicle->SetStatus(STATUS_ABANDONED); + } + else + pVehicle->RemovePassenger(pPlayerInfo.m_pPed); + } + } + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + CMessages::ClearMessages(); + CCarCtrl::ClearInterestingVehicleList(); + CWorld::ClearExcitingStuffFromArea(pPlayerInfo.GetPos(), 4000.0f, 1); + CRestart::FindClosestPoliceRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat); + CRestart::OverrideHospitalLevel = LEVEL_GENERIC; + CRestart::OverridePoliceStationLevel = LEVEL_GENERIC; + PassTime(720); + RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat); + pPlayerInfo.m_pPed->ClearWeapons(); + SortOutStreamingAndMemory(pPlayerInfo.GetPos()); + TheCamera.m_fCamShakeForce = 0.0f; + TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE); + CPad::GetPad(0)->StopShaking(0); + CReferences::RemoveReferencesToPlayer(); + CCarCtrl::CountDownToCarsAtStart = 2; + CPad::GetPad(CWorld::PlayerInFocus)->DisablePlayerControls = PLAYERCONTROL_ENABLED; + if (CRestart::bFadeInAfterNextArrest) { + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(4.0f, FADE_IN); + } else CRestart::bFadeInAfterNextArrest = true; + } + break; + case WBSTATE_FAILED_CRITICAL_MISSION: +#ifdef MISSION_REPLAY + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > AddExtraDeathDelay() + 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= AddExtraDeathDelay() + 0x800)) { +#else + if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= 0x800)) { +#endif + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(2.0f, FADE_OUT); + } +#ifdef MISSION_REPLAY + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) { +#else + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) { +#endif + pPlayerInfo.m_WBState = WBSTATE_PLAYING; + if (pPlayerInfo.m_pPed->bInVehicle) { + CVehicle *pVehicle = pPlayerInfo.m_pPed->m_pMyVehicle; + if (pVehicle != nil) { + if (pVehicle->pDriver == pPlayerInfo.m_pPed) { + pVehicle->pDriver = nil; + if (pVehicle->GetStatus() != STATUS_WRECKED) + pVehicle->SetStatus(STATUS_ABANDONED); + } else + pVehicle->RemovePassenger(pPlayerInfo.m_pPed); + } + } + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + CMessages::ClearMessages(); + CCarCtrl::ClearInterestingVehicleList(); + CWorld::ClearExcitingStuffFromArea(pPlayerInfo.GetPos(), 4000.0f, 1); + CRestart::FindClosestPoliceRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat); + CRestart::OverridePoliceStationLevel = LEVEL_GENERIC; + CRestart::OverrideHospitalLevel = LEVEL_GENERIC; + RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat); + SortOutStreamingAndMemory(pPlayerInfo.GetPos()); + TheCamera.m_fCamShakeForce = 0.0f; + TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE); + CPad::GetPad(0)->StopShaking(0); + CReferences::RemoveReferencesToPlayer(); + CCarCtrl::CountDownToCarsAtStart = 2; + CPad::GetPad(CWorld::PlayerInFocus)->DisablePlayerControls = PLAYERCONTROL_ENABLED; + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(4.0f, FADE_IN); + } + break; + case 4: + return; + } +} + +void +CGameLogic::RestorePlayerStuffDuringResurrection(CPlayerPed *pPlayerPed, CVector pos, float angle) +{ + pPlayerPed->m_fHealth = 100.0f; + pPlayerPed->m_fArmour = 0.0f; + pPlayerPed->bIsVisible = true; + pPlayerPed->m_bloodyFootprintCountOrDeathTime = 0; + pPlayerPed->bDoBloodyFootprints = false; + pPlayerPed->ClearAdrenaline(); + pPlayerPed->m_fCurrentStamina = pPlayerPed->m_fMaxStamina; + if (pPlayerPed->m_pFire) + pPlayerPed->m_pFire->Extinguish(); + pPlayerPed->bInVehicle = false; + pPlayerPed->m_pMyVehicle = nil; + pPlayerPed->m_pVehicleAnim = nil; + pPlayerPed->m_pWanted->Reset(); + pPlayerPed->RestartNonPartialAnims(); + pPlayerPed->GetPlayerInfoForThisPlayerPed()->MakePlayerSafe(false); + pPlayerPed->bRemoveFromWorld = false; + pPlayerPed->ClearWeaponTarget(); + pPlayerPed->SetInitialState(); + CCarCtrl::ClearInterestingVehicleList(); + + pos.z += 1.0f; + pPlayerPed->Teleport(pos); + pPlayerPed->SetMoveSpeed(CVector(0.0f, 0.0f, 0.0f)); + + pPlayerPed->m_fRotationCur = DEGTORAD(angle); + pPlayerPed->m_fRotationDest = pPlayerPed->m_fRotationCur; + pPlayerPed->SetHeading(pPlayerPed->m_fRotationCur); + CTheScripts::ClearSpaceForMissionEntity(pos, pPlayerPed); + CWorld::ClearExcitingStuffFromArea(pos, 4000.0, 1); + pPlayerPed->RestoreHeadingRate(); + TheCamera.SetCameraDirectlyInFrontForFollowPed_CamOnAString(); + CReferences::RemoveReferencesToPlayer(); + CGarages::PlayerArrestedOrDied(); + CStats::CheckPointReachedUnsuccessfully(); + CWorld::Remove(pPlayerPed); + CWorld::Add(pPlayerPed); +} diff --git a/src/control/GameLogic.h b/src/control/GameLogic.h new file mode 100644 index 0000000..43e244a --- /dev/null +++ b/src/control/GameLogic.h @@ -0,0 +1,13 @@ +#pragma once + +class CGameLogic +{ +public: + static void InitAtStartOfGame(); + static void PassTime(uint32 time); + static void SortOutStreamingAndMemory(const CVector &pos); + static void Update(); + static void RestorePlayerStuffDuringResurrection(class CPlayerPed *pPlayerPed, CVector pos, float angle); + + static uint8 ActivePlayers; +}; \ No newline at end of file diff --git a/src/control/Garages.cpp b/src/control/Garages.cpp new file mode 100644 index 0000000..e434bdb --- /dev/null +++ b/src/control/Garages.cpp @@ -0,0 +1,2548 @@ +#include "common.h" + +#include "Garages.h" +#include "main.h" + +#ifdef FIX_BUGS +#include "Boat.h" +#endif +#include "DMAudio.h" +#include "General.h" +#include "Font.h" +#include "HandlingMgr.h" +#include "Hud.h" +#include "Messages.h" +#include "ModelIndices.h" +#include "Pad.h" +#include "Particle.h" +#include "PlayerPed.h" +#include "Replay.h" +#include "Stats.h" +#include "Streaming.h" +#include "Text.h" +#include "Timer.h" +#include "Vehicle.h" +#include "Wanted.h" +#include "World.h" +#include "SaveBuf.h" + +#define ROTATED_DOOR_OPEN_SPEED (0.015f) +#define ROTATED_DOOR_CLOSE_SPEED (0.02f) +#define DEFAULT_DOOR_OPEN_SPEED (0.035f) +#define DEFAULT_DOOR_CLOSE_SPEED (0.04f) +#define CRUSHER_CRANE_SPEED (0.005f) + +// Prices +#define BOMB_PRICE (1000) +#define RESPRAY_PRICE (1000) + +// Distances +#define DISTANCE_TO_CALL_OFF_CHASE (10.0f) +#define DISTANCE_FOR_MRWHOOP_HACK (4.0f) +#define DISTANCE_TO_ACTIVATE_GARAGE (8.0f) +#define DISTANCE_TO_ACTIVATE_KEEPCAR_GARAGE (17.0f) +#define DISTANCE_TO_CLOSE_MISSION_GARAGE (30.0f) +#define DISTANCE_TO_CLOSE_COLLECTSPECIFICCARS_GARAGE (25.0f) +#define DISTANCE_TO_CLOSE_COLLECTCARS_GARAGE (40.0f) +#define DISTANCE_TO_CLOSE_HIDEOUT_GARAGE_ON_FOOT (2.2f) +#define DISTANCE_TO_CLOSE_HIDEOUT_GARAGE_IN_CAR (15.0f) +#define DISTANCE_TO_FORCE_CLOSE_HIDEOUT_GARAGE (70.0f) +#define DISTANCE_TO_OPEN_HIDEOUT_GARAGE_ON_FOOT (1.7f) +#define DISTANCE_TO_OPEN_HIDEOUT_GARAGE_IN_CAR (10.0f) +#define DISTANCE_TO_SHOW_HIDEOUT_MESSAGE (5.0f) + +#define DISTANCE_TO_CONSIDER_DOOR_FOR_GARAGE (20.0f) + +// Time +#define TIME_TO_RESPRAY (2000) +#define TIME_TO_SETUP_BOMB (2000) +#define TIME_TO_CRUSH_CAR (3000) +#define TIME_TO_PROCESS_KEEPCAR_GARAGE (2000) + +// Respray stuff +#define FREE_RESPRAY_HEALTH_THRESHOLD (970.0f) +#define NUM_PARTICLES_IN_RESPRAY (200) +#define RESPRAY_CENTERING_COEFFICIENT (0.75f) + +// Bomb stuff +#define KGS_OF_EXPLOSIVES_IN_BOMB (10) + +// Collect specific cars stuff +#define REWARD_FOR_FIRST_POLICE_CAR (5000) +#define REWARD_FOR_FIRST_BANK_VAN (5000) +#define MAX_POLICE_CARS_TO_COLLECT (10) +#define MAX_BANK_VANS_TO_COLLECT (10) + +// Collect cars stuff +#define MAX_SPEED_TO_SHOW_COLLECTED_MESSAGE (0.03f) +#define IMPORT_REWARD (1000) +#define IMPORT_ALLCARS_REWARD (200000) + +// Crusher stuff +#define CRUSHER_VEHICLE_TEST_SPAN (8) +#define CRUSHER_MIN_REWARD (25) +#define CRUSHER_MAX_REWARD (125) +#define CRUSHER_REWARD_COEFFICIENT (1.0f/500000) + +// Hideout stuff +#define MAX_STORED_CARS_IN_INDUSTRIAL (1) +#define MAX_STORED_CARS_IN_COMMERCIAL (NUM_GARAGE_STORED_CARS) +#define MAX_STORED_CARS_IN_SUBURBAN (NUM_GARAGE_STORED_CARS) +#define LIMIT_CARS_IN_INDUSTRIAL (1) +#define LIMIT_CARS_IN_COMMERCIAL (2) +#define LIMIT_CARS_IN_SUBURBAN (3) +#define HIDEOUT_DOOR_SPEED_COEFFICIENT (1.7f) +#define TIME_BETWEEN_HIDEOUT_MESSAGES (18000) + +// Camera stuff +#define MARGIN_FOR_CAMERA_COLLECTCARS (1.3f) +#define MARGIN_FOR_CAMERA_DEFAULT (4.0f) + +const int32 gaCarsToCollectInCraigsGarages[TOTAL_COLLECTCARS_GARAGES][TOTAL_COLLECTCARS_CARS] = +{ + { MI_SECURICA, MI_MOONBEAM, MI_COACH, MI_FLATBED, MI_LINERUN, MI_TRASH, MI_PATRIOT, MI_MRWHOOP, MI_BLISTA, MI_MULE, MI_YANKEE, MI_BOBCAT, MI_DODO, MI_BUS, MI_RUMPO, MI_PONY }, + { MI_SENTINEL, MI_CHEETAH, MI_BANSHEE, MI_IDAHO, MI_INFERNUS, MI_TAXI, MI_KURUMA, MI_STRETCH, MI_PEREN, MI_STINGER, MI_MANANA, MI_LANDSTAL, MI_STALLION, MI_BFINJECT, MI_CABBIE, MI_ESPERANT }, + { MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_LANDSTAL, MI_CHEETAH, MI_TAXI, MI_ESPERANT, MI_SENTINEL, MI_IDAHO } +}; + +const int32 gaCarsToCollectIn60Seconds[] = { MI_CHEETAH, MI_TAXI, MI_ESPERANT, MI_SENTINEL, MI_IDAHO }; + +int32 CGarages::BankVansCollected; +bool CGarages::BombsAreFree; +bool CGarages::RespraysAreFree; +int32 CGarages::CarsCollected; +int32 CGarages::CarTypesCollected[TOTAL_COLLECTCARS_GARAGES]; +int32 CGarages::CrushedCarId; +uint32 CGarages::LastTimeHelpMessage; +int32 CGarages::MessageNumberInString; +char CGarages::MessageIDString[MESSAGE_LENGTH]; +int32 CGarages::MessageNumberInString2; +uint32 CGarages::MessageStartTime; +uint32 CGarages::MessageEndTime; +uint32 CGarages::NumGarages; +bool CGarages::PlayerInGarage; +int32 CGarages::PoliceCarsCollected; +CStoredCar CGarages::aCarsInSafeHouse1[NUM_GARAGE_STORED_CARS]; +CStoredCar CGarages::aCarsInSafeHouse2[NUM_GARAGE_STORED_CARS]; +CStoredCar CGarages::aCarsInSafeHouse3[NUM_GARAGE_STORED_CARS]; +int32 hGarages = AEHANDLE_NONE; +CGarage CGarages::aGarages[NUM_GARAGES]; +bool CGarages::bCamShouldBeOutisde; + +void CGarages::Init(void) +{ + CrushedCarId = -1; + NumGarages = 0; + MessageEndTime = 0; + MessageStartTime = 0; + PlayerInGarage = false; + BombsAreFree = false; +#ifdef FIX_BUGS + RespraysAreFree = false; +#endif + CarsCollected = 0; + BankVansCollected = 0; + PoliceCarsCollected = 0; + for (int i = 0; i < TOTAL_COLLECTCARS_GARAGES; i++) + CarTypesCollected[i] = 0; + LastTimeHelpMessage = 0; + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) + aCarsInSafeHouse1[i].Init(); + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) + aCarsInSafeHouse2[i].Init(); + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) + aCarsInSafeHouse3[i].Init(); + hGarages = DMAudio.CreateEntity(AUDIOTYPE_GARAGE, (void*)1); + if (hGarages >= 0) + DMAudio.SetEntityStatus(hGarages, TRUE); + AddOne( + CVector(CRUSHER_GARAGE_X1, CRUSHER_GARAGE_Y1, CRUSHER_GARAGE_Z1), + CVector(CRUSHER_GARAGE_X2, CRUSHER_GARAGE_Y2, CRUSHER_GARAGE_Z2), + GARAGE_CRUSHER, 0); +} + +#ifndef PS2 +void CGarages::Shutdown(void) +{ + NumGarages = 0; + if (hGarages < 0) + return; + DMAudio.DestroyEntity(hGarages); + hGarages = AEHANDLE_NONE; +} +#endif + +void CGarages::Update(void) +{ + static int GarageToBeTidied = 0; + if (CReplay::IsPlayingBack()) + return; + bCamShouldBeOutisde = false; + TheCamera.pToGarageWeAreIn = nil; + TheCamera.pToGarageWeAreInForHackAvoidFirstPerson = nil; + for (int i = 0; i < NUM_GARAGES; i++) { + if (aGarages[i].IsUsed()) + aGarages[i].Update(); + } + if ((CTimer::GetFrameCounter() & 0xF) != 0xC) + return; + if (++GarageToBeTidied >= NUM_GARAGES) + GarageToBeTidied = 0; + if (!aGarages[GarageToBeTidied].IsUsed()) + return; + if (!aGarages[GarageToBeTidied].IsFar()) + aGarages[GarageToBeTidied].TidyUpGarageClose(); + else + aGarages[GarageToBeTidied].TidyUpGarage(); +} + +int16 CGarages::AddOne(CVector p1, CVector p2, uint8 type, int32 targetId) +{ + if (NumGarages >= NUM_GARAGES) { + assert(0); + return NumGarages++; + } + CGarage* pGarage = &aGarages[NumGarages]; + pGarage->m_fX1 = Min(p1.x, p2.x); + pGarage->m_fX2 = Max(p1.x, p2.x); + pGarage->m_fY1 = Min(p1.y, p2.y); + pGarage->m_fY2 = Max(p1.y, p2.y); + pGarage->m_fZ1 = Min(p1.z, p2.z); + pGarage->m_fZ2 = Max(p1.z, p2.z); + pGarage->m_pDoor1 = nil; + pGarage->m_pDoor2 = nil; + pGarage->m_fDoor1Z = p1.z; + pGarage->m_fDoor2Z = p1.z; + pGarage->m_eGarageType = type; + pGarage->m_bRecreateDoorOnNextRefresh = false; + pGarage->m_bRotatedDoor = false; + pGarage->m_bCameraFollowsPlayer = false; + pGarage->RefreshDoorPointers(true); + if (pGarage->m_pDoor1) { + pGarage->m_fDoor1Z = pGarage->m_pDoor1->GetPosition().z; + pGarage->m_fDoor1X = pGarage->m_pDoor1->GetPosition().x; + pGarage->m_fDoor1Y = pGarage->m_pDoor1->GetPosition().y; + } + if (pGarage->m_pDoor2) { + pGarage->m_fDoor2Z = pGarage->m_pDoor2->GetPosition().z; + pGarage->m_fDoor2X = pGarage->m_pDoor2->GetPosition().x; + pGarage->m_fDoor2Y = pGarage->m_pDoor2->GetPosition().y; + } + pGarage->m_fDoorHeight = pGarage->m_pDoor1 ? FindDoorHeightForMI(pGarage->m_pDoor1->GetModelIndex()) : 4.0f; + pGarage->m_fDoorPos = 0.0f; + pGarage->m_eGarageState = GS_FULLYCLOSED; + pGarage->m_nTimeToStartAction = 0; + pGarage->field_2 = false; + pGarage->m_nTargetModelIndex = targetId; + pGarage->field_96 = nil; + pGarage->m_bCollectedCarsState = 0; + pGarage->m_bDeactivated = false; + pGarage->m_bResprayHappened = false; + switch (type) { + case GARAGE_MISSION: + case GARAGE_COLLECTORSITEMS: + case GARAGE_COLLECTSPECIFICCARS: + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + case GARAGE_FORCARTOCOMEOUTOF: + case GARAGE_60SECONDS: + case GARAGE_MISSION_KEEPCAR: + case GARAGE_FOR_SCRIPT_TO_OPEN: + case GARAGE_HIDEOUT_ONE: + case GARAGE_HIDEOUT_TWO: + case GARAGE_HIDEOUT_THREE: + case GARAGE_FOR_SCRIPT_TO_OPEN_AND_CLOSE: + case GARAGE_KEEPS_OPENING_FOR_SPECIFIC_CAR: + case GARAGE_MISSION_KEEPCAR_REMAINCLOSED: + pGarage->m_eGarageState = GS_FULLYCLOSED; + pGarage->m_fDoorPos = 0.0f; + break; + case GARAGE_BOMBSHOP1: + case GARAGE_BOMBSHOP2: + case GARAGE_BOMBSHOP3: + case GARAGE_RESPRAY: + pGarage->m_eGarageState = GS_OPENED; + pGarage->m_fDoorPos = pGarage->m_fDoorHeight; + break; + case GARAGE_CRUSHER: + pGarage->m_eGarageState = GS_OPENED; + pGarage->m_fDoorPos = HALFPI; + break; + default: + assert(false); + } + if (type == GARAGE_CRUSHER) + pGarage->UpdateCrusherAngle(); + else + pGarage->UpdateDoorsHeight(); + return NumGarages++; +} + +void CGarages::ChangeGarageType(int16 garage, uint8 type, int32 mi) +{ + CGarage* pGarage = &aGarages[garage]; + pGarage->m_eGarageType = type; + pGarage->m_nTargetModelIndex = mi; + pGarage->m_eGarageState = GS_FULLYCLOSED; +} + +void CGarage::Update() +{ + if (m_eGarageType != GARAGE_CRUSHER) { + switch (m_eGarageState) { + case GS_FULLYCLOSED: + case GS_OPENED: + case GS_CLOSING: + case GS_OPENING: + case GS_OPENEDCONTAINSCAR: + case GS_CLOSEDCONTAINSCAR: + if (FindPlayerPed() && !m_bCameraFollowsPlayer) { + CVehicle* pVehicle = FindPlayerVehicle(); + if (IsEntityEntirelyInside3D(FindPlayerPed(), 0.25f)) { + TheCamera.pToGarageWeAreIn = this; + CGarages::bCamShouldBeOutisde = true; + } + if (pVehicle) { + if (!IsEntityEntirelyOutside(pVehicle, 0.0f)) + TheCamera.pToGarageWeAreInForHackAvoidFirstPerson = this; + if (pVehicle->GetModelIndex() == MI_MRWHOOP) { + if (pVehicle->IsWithinArea( + m_fX1 - DISTANCE_FOR_MRWHOOP_HACK, + m_fY1 + DISTANCE_FOR_MRWHOOP_HACK, + m_fX2 - DISTANCE_FOR_MRWHOOP_HACK, + m_fY2 + DISTANCE_FOR_MRWHOOP_HACK)) { + TheCamera.pToGarageWeAreIn = this; + CGarages::bCamShouldBeOutisde = true; + } + } + } + } + break; + default: + break; + } + } + if (m_bDeactivated && m_eGarageState == GS_FULLYCLOSED) + return; + switch (m_eGarageType) { + case GARAGE_RESPRAY: + switch (m_eGarageState) { + case GS_OPENED: + if (IsStaticPlayerCarEntirelyInside() && !IsAnyOtherCarTouchingGarage(FindPlayerVehicle())) { + if (CGarages::IsCarSprayable(FindPlayerVehicle())) { + if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney >= RESPRAY_PRICE || CGarages::RespraysAreFree) { + m_eGarageState = GS_CLOSING; + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + } + else { + CGarages::TriggerMessage("GA_3", -1, 4000, -1); // No more freebies. $1000 to respray! + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_NO_MONEY, 1); + } + } + else { + CGarages::TriggerMessage("GA_1", -1, 4000, -1); // Whoa! I don't touch nothing THAT hot! + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_BAD_VEHICLE, 1); + } + } + if (FindPlayerVehicle()) { + if (CalcDistToGarageRectangleSquared(FindPlayerVehicle()->GetPosition().x, FindPlayerVehicle()->GetPosition().y) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) + CWorld::CallOffChaseForArea( + m_fX1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fY1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fX2 + DISTANCE_TO_CALL_OFF_CHASE, + m_fY2 + DISTANCE_TO_CALL_OFF_CHASE); + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + m_nTimeToStartAction = CTimer::GetTimeInMilliseconds() + TIME_TO_RESPRAY; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + CStats::CheckPointReachedSuccessfully(); + } + UpdateDoorsHeight(); +#ifdef FIX_BUGS + if (FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) +#else + if (FindPlayerVehicle()) +#endif + ((CAutomobile*)(FindPlayerVehicle()))->m_fFireBlowUpTimer = 0.0f; + CWorld::CallOffChaseForArea( + m_fX1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fY1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fX2 + DISTANCE_TO_CALL_OFF_CHASE, + m_fY2 + DISTANCE_TO_CALL_OFF_CHASE); + break; + case GS_FULLYCLOSED: + if (CTimer::GetTimeInMilliseconds() > m_nTimeToStartAction) { + m_eGarageState = GS_OPENING; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_OPENING, 1); + bool bTakeMoney = false; + if (FindPlayerPed()->m_pWanted->GetWantedLevel() != 0) + bTakeMoney = true; + FindPlayerPed()->m_pWanted->Reset(); + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; +#ifdef FIX_BUGS + bool bChangedColour = false; +#else + bool bChangedColour; +#endif + if (FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) { + if (FindPlayerVehicle()->m_fHealth < FREE_RESPRAY_HEALTH_THRESHOLD) + bTakeMoney = true; + FindPlayerVehicle()->m_fHealth = 1000.0f; + ((CAutomobile*)(FindPlayerVehicle()))->m_fFireBlowUpTimer = 0.0f; + ((CAutomobile*)(FindPlayerVehicle()))->Fix(); + if (FindPlayerVehicle()->GetUp().z < 0.0f) { + FindPlayerVehicle()->GetUp() = -FindPlayerVehicle()->GetUp(); + FindPlayerVehicle()->GetRight() = -FindPlayerVehicle()->GetRight(); + } + bChangedColour = false; + if (!((CAutomobile*)(FindPlayerVehicle()))->bFixedColour) { + uint8 colour1, colour2; + uint16 attempt; + FindPlayerVehicle()->GetModelInfo()->ChooseVehicleColour(colour1, colour2); + for (attempt = 0; attempt < 10; attempt++) { + if (colour1 != FindPlayerVehicle()->m_currentColour1 || colour2 != FindPlayerVehicle()->m_currentColour2) + break; + FindPlayerVehicle()->GetModelInfo()->ChooseVehicleColour(colour1, colour2); + } + bChangedColour = (attempt < 10); + FindPlayerVehicle()->m_currentColour1 = colour1; + FindPlayerVehicle()->m_currentColour2 = colour2; + if (bChangedColour) { + for (int i = 0; i < NUM_PARTICLES_IN_RESPRAY; i++) { + CVector pos; +#ifdef FIX_BUGS + pos.x = CGeneral::GetRandomNumberInRange(m_fX1 + 0.5f, m_fX2 - 0.5f); + pos.y = CGeneral::GetRandomNumberInRange(m_fY1 + 0.5f, m_fY2 - 0.5f); + pos.z = CGeneral::GetRandomNumberInRange(m_fDoor1Z - 3.0f, m_fDoor1Z + 1.0f); +#else + // wtf is this + pos.x = m_fX1 + 0.5f + (uint8)(CGeneral::GetRandomNumber()) / 256.0f * (m_fX2 - m_fX1 - 1.0f); + pos.y = m_fY1 + 0.5f + (uint8)(CGeneral::GetRandomNumber()) / 256.0f * (m_fY2 - m_fY1 - 1.0f); + pos.z = m_fDoor1Z - 3.0f + (uint8)(CGeneral::GetRandomNumber()) / 256.0f * 4.0f; +#endif + CParticle::AddParticle(PARTICLE_GARAGEPAINT_SPRAY, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, CVehicleModelInfo::ms_vehicleColourTable[colour1]); + } + } + } + CenterCarInGarage(FindPlayerVehicle()); + } + if (bTakeMoney) { + if (!CGarages::RespraysAreFree) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney = Max(0, CWorld::Players[CWorld::PlayerInFocus].m_nMoney - RESPRAY_PRICE); + CGarages::TriggerMessage("GA_2", -1, 4000, -1); // New engine and paint job. The cops won't recognize you! + } + else if (bChangedColour) { + if (CGeneral::GetRandomTrueFalse()) + CGarages::TriggerMessage("GA_15", -1, 4000, -1); // Hope you like the new color. + else + CGarages::TriggerMessage("GA_16", -1, 4000, -1); // Respray is complementary. + } + m_bResprayHappened = true; + } + CWorld::CallOffChaseForArea( + m_fX1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fY1 - DISTANCE_TO_CALL_OFF_CHASE, + m_fX2 + DISTANCE_TO_CALL_OFF_CHASE, + m_fY2 + DISTANCE_TO_CALL_OFF_CHASE); + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_OPENEDCONTAINSCAR: + if (IsPlayerOutsideGarage()) + m_eGarageState = GS_OPENED; + break; + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_BOMBSHOP1: + case GARAGE_BOMBSHOP2: + case GARAGE_BOMBSHOP3: + switch (m_eGarageState) { + case GS_OPENED: + if (IsStaticPlayerCarEntirelyInside() && !IsAnyOtherCarTouchingGarage(FindPlayerVehicle())) { +#ifdef FIX_BUGS // FindPlayerVehicle() can never be NULL here because IsStaticPlayerCarEntirelyInside() is true, and there is no IsCar() check + if (FindPlayerVehicle()->IsCar() && ((CAutomobile*)FindPlayerVehicle())->m_bombType) { +#else + if (!FindPlayerVehicle() || ((CAutomobile*)FindPlayerVehicle())->m_bombType) { +#endif + CGarages::TriggerMessage("GA_5", -1, 4000, -1); //"Your car is already fitted with a bomb" + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_BOMB_ALREADY_SET, 1); + break; + } + if (!CGarages::BombsAreFree && CWorld::Players[CWorld::PlayerInFocus].m_nMoney < BOMB_PRICE) { + CGarages::TriggerMessage("GA_4", -1, 4000, -1); // "Car bombs are $1000 each" - weird that the price is hardcoded in message + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayFrontEndSound(SOUND_GARAGE_NO_MONEY, 1); + break; + } + m_eGarageState = GS_CLOSING; + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + m_nTimeToStartAction = CTimer::GetTimeInMilliseconds() + TIME_TO_SETUP_BOMB; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (CTimer::GetTimeInMilliseconds() > m_nTimeToStartAction) { + switch (m_eGarageType) { + case GARAGE_BOMBSHOP1: DMAudio.PlayFrontEndSound(SOUND_GARAGE_BOMB1_SET, 1); break; + case GARAGE_BOMBSHOP2: DMAudio.PlayFrontEndSound(SOUND_GARAGE_BOMB2_SET, 1); break; + case GARAGE_BOMBSHOP3: DMAudio.PlayFrontEndSound(SOUND_GARAGE_BOMB3_SET, 1); break; + default: break; + } + m_eGarageState = GS_OPENING; + if (!CGarages::BombsAreFree) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney = Max(0, CWorld::Players[CWorld::PlayerInFocus].m_nMoney - BOMB_PRICE); + if (FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) { + ((CAutomobile*)(FindPlayerVehicle()))->m_bombType = CGarages::GetBombTypeForGarageType(m_eGarageType); + ((CAutomobile*)(FindPlayerVehicle()))->m_pBombRigger = FindPlayerPed(); + if (m_eGarageType == GARAGE_BOMBSHOP3) + CGarages::GivePlayerDetonator(); + CStats::KgsOfExplosivesUsed += KGS_OF_EXPLOSIVES_IN_BOMB; + } +#ifdef DETECT_PAD_INPUT_SWITCH + int16 Mode = CPad::IsAffectedByController ? CPad::GetPad(0)->Mode : 0; +#else + int16 Mode = CPad::GetPad(0)->Mode; +#endif + switch (m_eGarageType) { + case GARAGE_BOMBSHOP1: + switch (Mode) { + case 0: + case 1: + case 2: + CHud::SetHelpMessage(TheText.Get("GA_6"), false); // Arm with ~h~~k~~PED_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + break; + case 3: + CHud::SetHelpMessage(TheText.Get("GA_6B"), false); // Arm with ~h~~k~~PED_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + break; + } + break; + case GARAGE_BOMBSHOP2: + switch (Mode) { + case 0: + case 1: + case 2: + CHud::SetHelpMessage(TheText.Get("GA_7"), false); // Park it, prime it by pressing the ~h~~k~~PED_FIREWEAPON~ button~w~ and LEG IT! + break; + case 3: + CHud::SetHelpMessage(TheText.Get("GA_7B"), false); // Park it, prime it by pressing the ~h~~k~~PED_FIREWEAPON~ button~w~ and LEG IT! + break; + } + break; + case GARAGE_BOMBSHOP3: + CHud::SetHelpMessage(TheText.Get("GA_8"), false); // Use the detonator to activate the bomb. + break; + default: break; + } + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + } + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENEDCONTAINSCAR; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_OPENEDCONTAINSCAR: + if (IsPlayerOutsideGarage()) + m_eGarageState = GS_OPENED; + break; + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_MISSION: + switch (m_eGarageState) { + case GS_OPENED: + if (((CVector2D)FindPlayerCoors() - CVector2D(GetGarageCenterX(), GetGarageCenterY())).MagnitudeSqr() > SQR(DISTANCE_TO_CLOSE_MISSION_GARAGE)) { + if ((CTimer::GetFrameCounter() & 0x1F) == 0 && !IsAnyOtherCarTouchingGarage(nil)) { + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = true; + } + } + else if (!FindPlayerVehicle() && m_pTarget && IsEntityEntirelyInside3D(m_pTarget, 0.0f) && + !IsAnyOtherCarTouchingGarage(m_pTarget) && IsEntityEntirelyOutside(FindPlayerPed(), 2.0f) && + !IsAnyOtherCarTouchingGarage(m_pTarget)) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = false; + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + if (m_bClosingWithoutTargetCar) + m_eGarageState = GS_FULLYCLOSED; + else { + if (m_pTarget) { + m_eGarageState = GS_CLOSEDCONTAINSCAR; + DestroyVehicleAndDriverAndPassengers(m_pTarget); + m_pTarget = nil; + } + else { + m_eGarageState = GS_FULLYCLOSED; + } + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() == m_pTarget && m_pTarget) { + if (CalcDistToGarageRectangleSquared( + FindPlayerVehicle()->GetPosition().x, + FindPlayerVehicle()->GetPosition().y) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) + m_eGarageState = GS_OPENING; + } + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_COLLECTSPECIFICCARS: + switch (m_eGarageState) { + case GS_OPENED: + if (FindPlayerVehicle() && m_nTargetModelIndex == FindPlayerVehicle()->GetModelIndex()) { + m_pTarget = FindPlayerVehicle(); + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + if (!FindPlayerVehicle()) { + if (m_pTarget && IsEntityEntirelyInside3D(m_pTarget, 0.0f) && !IsAnyOtherCarTouchingGarage(m_pTarget)) { + if (IsEntityEntirelyOutside(FindPlayerPed(), 2.0f)) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + m_eGarageState = GS_CLOSING; + } + } + else if (Abs(FindPlayerCoors().x - GetGarageCenterX()) > DISTANCE_TO_CLOSE_COLLECTSPECIFICCARS_GARAGE || + Abs(FindPlayerCoors().y - GetGarageCenterY()) > DISTANCE_TO_CLOSE_COLLECTSPECIFICCARS_GARAGE) { + m_eGarageState = GS_CLOSING; + m_pTarget = nil; + } + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + if (m_pTarget) { + DestroyVehicleAndDriverAndPassengers(m_pTarget); + m_pTarget = nil; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + int16 reward; + switch (m_nTargetModelIndex) { + case MI_POLICE: + reward = REWARD_FOR_FIRST_POLICE_CAR * (MAX_POLICE_CARS_TO_COLLECT - CGarages::PoliceCarsCollected++) / MAX_POLICE_CARS_TO_COLLECT; + break; + case MI_SECURICA: + reward = REWARD_FOR_FIRST_BANK_VAN * (MAX_BANK_VANS_TO_COLLECT - CGarages::BankVansCollected++) / MAX_BANK_VANS_TO_COLLECT; + break; +#ifdef FIX_BUGS // not possible though + default: + reward = 0; + break; +#endif + } + if (reward > 0) { + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += reward; + CGarages::TriggerMessage("GA_10", reward, 4000, -1); // Nice one. Here's your $~1~ + DMAudio.PlayFrontEndSound(SOUND_GARAGE_VEHICLE_ACCEPTED, 1); + } + else { + CGarages::TriggerMessage("GA_11", -1, 4000, -1); // We got these wheels already. It's worthless to us! + DMAudio.PlayFrontEndSound(SOUND_GARAGE_VEHICLE_DECLINED, 1); + } + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() && m_nTargetModelIndex == FindPlayerVehicle()->GetModelIndex()) { + if (CalcDistToGarageRectangleSquared(FindPlayerVehicle()->GetPosition().x, FindPlayerVehicle()->GetPosition().y) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) + m_eGarageState = GS_OPENING; + } + break; + case GS_OPENING: + if (FindPlayerVehicle() && m_nTargetModelIndex == FindPlayerVehicle()->GetModelIndex()) { + m_pTarget = FindPlayerVehicle(); + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + switch (m_eGarageState) { + case GS_OPENED: + if (FindPlayerVehicle() && DoesCraigNeedThisCar(FindPlayerVehicle()->GetModelIndex())) { + m_pTarget = FindPlayerVehicle(); + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + if (Abs(FindPlayerCoors().x - GetGarageCenterX()) > DISTANCE_TO_CLOSE_COLLECTCARS_GARAGE || + Abs(FindPlayerCoors().y - GetGarageCenterY()) > DISTANCE_TO_CLOSE_COLLECTCARS_GARAGE) { + m_eGarageState = GS_CLOSING; + m_pTarget = nil; + break; + } + if (m_pTarget && !FindPlayerVehicle() && IsEntityEntirelyInside3D(m_pTarget, 0.0f) && + !IsAnyOtherCarTouchingGarage(m_pTarget) && IsEntityEntirelyOutside(FindPlayerPed(), 2.0f)) { +#ifdef FIX_BUGS + if (!m_pTarget->IsCar() || + ((CAutomobile*)(m_pTarget))->Damage.GetEngineStatus() <= ENGINE_STATUS_ON_FIRE && + ((CAutomobile*)(m_pTarget))->m_fFireBlowUpTimer == 0.0f) { +#else + if (((CAutomobile*)(m_pTarget))->Damage.GetEngineStatus() <= ENGINE_STATUS_ON_FIRE && + ((CAutomobile*)(m_pTarget))->m_fFireBlowUpTimer == 0.0f) { +#endif + if (m_pTarget->GetStatus() != STATUS_WRECKED) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + m_eGarageState = GS_CLOSING; + TheCamera.SetCameraDirectlyBehindForFollowPed_CamOnAString(); + } + } + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + if (m_pTarget) { + MarkThisCarAsCollectedForCraig(m_pTarget->GetModelIndex()); + DestroyVehicleAndDriverAndPassengers(m_pTarget); + m_pTarget = nil; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() && + CalcSmallestDistToGarageDoorSquared( + FindPlayerVehicle()->GetPosition().x, + FindPlayerVehicle()->GetPosition().y + ) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) { + if (DoesCraigNeedThisCar(FindPlayerVehicle()->GetModelIndex())) { + if (FindPlayerVehicle()->VehicleCreatedBy == MISSION_VEHICLE) + CGarages::TriggerMessage("GA_1A", -1, 5000, -1); // Come back when you're not so busy... + else + m_eGarageState = GS_OPENING; + } + else { + if (HasCraigCollectedThisCar(FindPlayerVehicle()->GetModelIndex())) + CGarages::TriggerMessage("GA_20", -1, 5000, -1); // We got more of these than we can shift. Sorry man, no deal. + else if (FindPlayerSpeed().Magnitude() < MAX_SPEED_TO_SHOW_COLLECTED_MESSAGE) + CGarages::TriggerMessage("GA_19", -1, 5000, -1); // We're not interested in that model. + } + } + m_pTarget = nil; + break; + case GS_OPENING: + if (FindPlayerVehicle() && DoesCraigNeedThisCar(FindPlayerVehicle()->GetModelIndex())) { + m_pTarget = FindPlayerVehicle(); + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_FORCARTOCOMEOUTOF: + switch (m_eGarageState) { + case GS_OPENED: + if (IsGarageEmpty()) + m_eGarageState = GS_CLOSING; + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + if (!IsGarageEmpty()) + m_eGarageState = GS_OPENING; + break; + case GS_FULLYCLOSED: + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_CRUSHER: + switch (m_eGarageState) { + case GS_OPENED: + { + int i = CPools::GetVehiclePool()->GetSize() * (CTimer::GetFrameCounter() % CRUSHER_VEHICLE_TEST_SPAN) / CRUSHER_VEHICLE_TEST_SPAN; + int end = CPools::GetVehiclePool()->GetSize() * (CTimer::GetFrameCounter() % CRUSHER_VEHICLE_TEST_SPAN + 1) / CRUSHER_VEHICLE_TEST_SPAN; + for (; i < end; i++) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (pVehicle->IsCar() && IsEntityEntirelyInside3D(pVehicle, 0.0f)) { + m_eGarageState = GS_CLOSING; + m_pTarget = pVehicle; + m_pTarget->RegisterReference((CEntity**)&m_pTarget); + } + } + break; + } + case GS_CLOSING: + if (m_pTarget) { + m_fDoorPos = Max(0.0f, m_fDoorPos - CRUSHER_CRANE_SPEED * CTimer::GetTimeStep()); + if (m_fDoorPos < TWOPI / 5) { + m_pTarget->bUsesCollision = false; + m_pTarget->bAffectedByGravity = false; + m_pTarget->SetMoveSpeed(0.0f, 0.0f, 0.0f); + } + else { + m_pTarget->SetMoveSpeed(m_pTarget->GetMoveSpeed() * Pow(0.8f, CTimer::GetTimeStep())); + } + if (m_fDoorPos == 0.0f) { + CGarages::CrushedCarId = CPools::GetVehiclePool()->GetIndex(m_pTarget); + float reward = Min(CRUSHER_MAX_REWARD, CRUSHER_MIN_REWARD + m_pTarget->pHandling->nMonetaryValue * m_pTarget->m_fHealth * CRUSHER_REWARD_COEFFICIENT); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += reward; + DestroyVehicleAndDriverAndPassengers(m_pTarget); + ++CStats::CarsCrushed; + m_pTarget = nil; + m_eGarageState = GS_AFTERDROPOFF; + m_nTimeToStartAction = CTimer::GetTimeInMilliseconds() + TIME_TO_CRUSH_CAR; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + } + else + m_eGarageState = GS_OPENING; + UpdateCrusherAngle(); + break; + case GS_AFTERDROPOFF: + if (CTimer::GetTimeInMilliseconds() <= m_nTimeToStartAction) { + UpdateCrusherShake((myrand() & 0xFF - 128) * 0.0002f, (myrand() & 0xFF - 128) * 0.0002f); + } + else { + UpdateCrusherShake(0.0f, 0.0f); + m_eGarageState = GS_OPENING; + } + break; + case GS_OPENING: + m_fDoorPos = Min(HALFPI, m_fDoorPos + CTimer::GetTimeStep() * CRUSHER_CRANE_SPEED); + if (m_fDoorPos == HALFPI) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateCrusherAngle(); + break; + //case GS_FULLYCLOSED: + //case GS_CLOSEDCONTAINSCAR: + //case GS_OPENEDCONTAINSCAR: + default: + break; + } + if (!FindPlayerVehicle() && (CTimer::GetFrameCounter() & 0x1F) == 0x17 && IsEntityEntirelyInside(FindPlayerPed())) + FindPlayerPed()->InflictDamage(nil, WEAPONTYPE_RAMMEDBYCAR, 300.0f, PEDPIECE_TORSO, 0); + break; + case GARAGE_MISSION_KEEPCAR: + case GARAGE_MISSION_KEEPCAR_REMAINCLOSED: + switch (m_eGarageState) { + case GS_OPENED: + if (((CVector2D)FindPlayerCoors() - CVector2D(GetGarageCenterX(), GetGarageCenterY())).MagnitudeSqr() > SQR(DISTANCE_TO_CLOSE_MISSION_GARAGE) && + !IsAnyOtherCarTouchingGarage(nil)) { + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = true; + } + else if (m_pTarget && m_pTarget == FindPlayerVehicle() && IsStaticPlayerCarEntirelyInside() && !IsAnyCarBlockingDoor()) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = false; + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + if (m_bClosingWithoutTargetCar) + m_eGarageState = GS_FULLYCLOSED; + else { + if (m_pTarget) { + m_eGarageState = GS_CLOSEDCONTAINSCAR; + m_nTimeToStartAction = CTimer::GetTimeInMilliseconds() + TIME_TO_PROCESS_KEEPCAR_GARAGE; + m_pTarget = nil; + } + else + m_eGarageState = GS_FULLYCLOSED; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() == m_pTarget && m_pTarget && + CalcDistToGarageRectangleSquared( + FindPlayerVehicle()->GetPosition().x, + FindPlayerVehicle()->GetPosition().y + ) < SQR(DISTANCE_TO_ACTIVATE_KEEPCAR_GARAGE)) + m_eGarageState = GS_OPENING; + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_CLOSEDCONTAINSCAR: + if (m_eGarageType == GARAGE_MISSION_KEEPCAR && CTimer::GetTimeInMilliseconds() > m_nTimeToStartAction) + m_eGarageState = GS_OPENING; + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_FOR_SCRIPT_TO_OPEN: + switch (m_eGarageState) { + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENED: + //case GS_CLOSING: + //case GS_FULLYCLOSED: + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_FOR_SCRIPT_TO_OPEN_AND_CLOSE: + switch (m_eGarageState) { + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENED: + //case GS_FULLYCLOSED: + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_HIDEOUT_ONE: + case GARAGE_HIDEOUT_TWO: + case GARAGE_HIDEOUT_THREE: + switch (m_eGarageState) { + case GS_OPENED: + { + float distance = CalcDistToGarageRectangleSquared(FindPlayerCoors().x, FindPlayerCoors().y); + // Close car doors either if player is far, or if he is in vehicle and garage is full, + // or if player is very very far so that we can remove whatever is blocking garage door without him noticing + if ((distance > SQR(DISTANCE_TO_CLOSE_HIDEOUT_GARAGE_IN_CAR) || + !FindPlayerVehicle() && distance > SQR(DISTANCE_TO_CLOSE_HIDEOUT_GARAGE_ON_FOOT)) && + !IsAnyCarBlockingDoor()) + m_eGarageState = GS_CLOSING; + else if (FindPlayerVehicle() && + CountCarsWithCenterPointWithinGarage(FindPlayerVehicle()) >= + CGarages::FindMaxNumStoredCarsForGarage(m_eGarageType)) { + m_eGarageState = GS_CLOSING; + } + else if (distance > SQR(DISTANCE_TO_FORCE_CLOSE_HIDEOUT_GARAGE)) { + m_eGarageState = GS_CLOSING; + RemoveCarsBlockingDoorNotInside(); + } + break; + } + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - HIDEOUT_DOOR_SPEED_COEFFICIENT * (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (!IsPlayerOutsideGarage()) + m_eGarageState = GS_OPENING; + else if (m_fDoorPos == 0.0f) { + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + m_eGarageState = GS_FULLYCLOSED; + switch (m_eGarageType) { + case GARAGE_HIDEOUT_ONE: StoreAndRemoveCarsForThisHideout(CGarages::aCarsInSafeHouse1, MAX_STORED_CARS_IN_INDUSTRIAL); break; + case GARAGE_HIDEOUT_TWO: StoreAndRemoveCarsForThisHideout(CGarages::aCarsInSafeHouse2, MAX_STORED_CARS_IN_COMMERCIAL); break; + case GARAGE_HIDEOUT_THREE: StoreAndRemoveCarsForThisHideout(CGarages::aCarsInSafeHouse3, MAX_STORED_CARS_IN_SUBURBAN); break; + default: break; + } + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + { + float distance = CalcDistToGarageRectangleSquared(FindPlayerCoors().x, FindPlayerCoors().y); + if (distance < SQR(DISTANCE_TO_OPEN_HIDEOUT_GARAGE_ON_FOOT) || + distance < SQR(DISTANCE_TO_OPEN_HIDEOUT_GARAGE_IN_CAR) && FindPlayerVehicle()) { + if (FindPlayerVehicle() && CGarages::CountCarsInHideoutGarage(m_eGarageType) >= CGarages::FindMaxNumStoredCarsForGarage(m_eGarageType)) { + if (m_pDoor1) { + if (((CVector2D)FindPlayerVehicle()->GetPosition() - (CVector2D)m_pDoor1->GetPosition()).MagnitudeSqr() < SQR(DISTANCE_TO_SHOW_HIDEOUT_MESSAGE) && + CTimer::GetTimeInMilliseconds() - CGarages::LastTimeHelpMessage > TIME_BETWEEN_HIDEOUT_MESSAGES) { + CHud::SetHelpMessage(TheText.Get("GA_21"), false); // You cannot store any more cars in this garage. + CGarages::LastTimeHelpMessage = CTimer::GetTimeInMilliseconds(); + } + } + } + else { +#ifdef FIX_BUGS + bool bCreatedAllCars = false; +#else + bool bCreatedAllCars; +#endif + switch (m_eGarageType) { + case GARAGE_HIDEOUT_ONE: bCreatedAllCars = RestoreCarsForThisHideout(CGarages::aCarsInSafeHouse1); break; + case GARAGE_HIDEOUT_TWO: bCreatedAllCars = RestoreCarsForThisHideout(CGarages::aCarsInSafeHouse2); break; + case GARAGE_HIDEOUT_THREE: bCreatedAllCars = RestoreCarsForThisHideout(CGarages::aCarsInSafeHouse3); break; + default: break; + } + if (bCreatedAllCars) + m_eGarageState = GS_OPENING; + } + } + break; + } + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + HIDEOUT_DOOR_SPEED_COEFFICIENT * (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + case GARAGE_KEEPS_OPENING_FOR_SPECIFIC_CAR: + switch (m_eGarageState) { + case GS_OPENED: + if (((CVector2D)FindPlayerCoors() - CVector2D(GetGarageCenterX(), GetGarageCenterY())).MagnitudeSqr() > SQR(DISTANCE_TO_CLOSE_MISSION_GARAGE)) { + if (m_pTarget && IsEntityEntirelyOutside(m_pTarget, 0.0f) && !IsAnyOtherCarTouchingGarage(nil)) { + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = true; + } + } + break; + case GS_CLOSING: + m_fDoorPos = Max(0.0f, m_fDoorPos - (m_bRotatedDoor ? ROTATED_DOOR_CLOSE_SPEED : DEFAULT_DOOR_CLOSE_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == 0.0f) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + } + UpdateDoorsHeight(); + break; + case GS_FULLYCLOSED: + if (FindPlayerVehicle() == m_pTarget && m_pTarget && + CalcDistToGarageRectangleSquared( + FindPlayerVehicle()->GetPosition().x, + FindPlayerVehicle()->GetPosition().y + ) < SQR(DISTANCE_TO_ACTIVATE_GARAGE)) + m_eGarageState = GS_OPENING; + break; + case GS_OPENING: + m_fDoorPos = Min(m_fDoorHeight, m_fDoorPos + (m_bRotatedDoor ? ROTATED_DOOR_OPEN_SPEED : DEFAULT_DOOR_OPEN_SPEED) * CTimer::GetTimeStep()); + if (m_fDoorPos == m_fDoorHeight) { + m_eGarageState = GS_OPENED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_OPENED, 1.0f); + } + UpdateDoorsHeight(); + break; + //case GS_OPENEDCONTAINSCAR: + //case GS_CLOSEDCONTAINSCAR: + //case GS_AFTERDROPOFF: + default: + break; + } + break; + //case GARAGE_COLLECTORSITEMS: + //case GARAGE_60SECONDS: + default: + break; + } +} + +bool CGarage::IsStaticPlayerCarEntirelyInside() +{ + if (!FindPlayerVehicle()) + return false; + if (!FindPlayerVehicle()->IsCar()) + return false; + if (FindPlayerPed()->GetPedState() != PED_DRIVING) + return false; + if (FindPlayerPed()->m_objective == OBJECTIVE_LEAVE_CAR) + return false; + CVehicle* pVehicle = FindPlayerVehicle(); + if (pVehicle->GetPosition().x < m_fX1 || pVehicle->GetPosition().x > m_fX2 || + pVehicle->GetPosition().y < m_fY1 || pVehicle->GetPosition().y > m_fY2) + return false; + if (Abs(pVehicle->GetSpeed().x) > 0.01f || + Abs(pVehicle->GetSpeed().y) > 0.01f || + Abs(pVehicle->GetSpeed().z) > 0.01f) + return false; + if (pVehicle->GetSpeed().MagnitudeSqr() > SQR(0.01f)) + return false; + return IsEntityEntirelyInside3D(pVehicle, 0.0f); +} + +bool CGarage::IsEntityEntirelyInside(CEntity * pEntity) +{ + if (pEntity->GetPosition().x < m_fX1 || pEntity->GetPosition().x > m_fX2 || + pEntity->GetPosition().y < m_fY1 || pEntity->GetPosition().y > m_fY2) + return false; + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x - radius < m_fX1 || pos.x + radius > m_fX2 || + pos.y - radius < m_fY1 || pos.y + radius > m_fY2) + return false; + } + return true; +} + +bool CGarage::IsEntityEntirelyInside3D(CEntity * pEntity, float fMargin) +{ + if (pEntity->GetPosition().x < m_fX1 - fMargin || pEntity->GetPosition().x > m_fX2 + fMargin || + pEntity->GetPosition().y < m_fY1 - fMargin || pEntity->GetPosition().y > m_fY2 + fMargin || + pEntity->GetPosition().z < m_fZ1 - fMargin || pEntity->GetPosition().z > m_fZ2 + fMargin) + return false; + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius < m_fX1 - fMargin || pos.x - radius > m_fX2 + fMargin || + pos.y + radius < m_fY1 - fMargin || pos.y - radius > m_fY2 + fMargin || + pos.z + radius < m_fZ1 - fMargin || pos.z - radius > m_fZ2 + fMargin) + return false; + } + return true; +} + +bool CGarage::IsEntityEntirelyOutside(CEntity * pEntity, float fMargin) +{ + if (pEntity->GetPosition().x > m_fX1 - fMargin && pEntity->GetPosition().x < m_fX2 + fMargin && + pEntity->GetPosition().y > m_fY1 - fMargin && pEntity->GetPosition().y < m_fY2 + fMargin) + return false; + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius > m_fX1 - fMargin && pos.x - radius < m_fX2 + fMargin && + pos.y + radius > m_fY1 - fMargin && pos.y - radius < m_fY2 + fMargin) + return false; + } + return true; +} + +bool CGarage::IsGarageEmpty() +{ + int16 num; + CWorld::FindObjectsIntersectingCube(CVector(m_fX1, m_fY1, m_fZ1), CVector(m_fX2, m_fY2, m_fZ2), &num, 2, nil, false, true, true, false, false); + return num == 0; +} + +bool CGarage::IsPlayerOutsideGarage() +{ + if (FindPlayerVehicle()) + return IsEntityEntirelyOutside(FindPlayerVehicle(), 0.0f); + return IsEntityEntirelyOutside(FindPlayerPed(), 0.0f); +} + +bool CGarage::IsEntityTouching3D(CEntity * pEntity) +{ + float radius = pEntity->GetBoundRadius(); + if (m_fX1 - radius > pEntity->GetPosition().x || m_fX2 + radius < pEntity->GetPosition().x || + m_fY1 - radius > pEntity->GetPosition().y || m_fY2 + radius < pEntity->GetPosition().y || + m_fZ1 - radius > pEntity->GetPosition().z || m_fZ2 + radius < pEntity->GetPosition().z) + return false; + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + radius = pColModel->spheres[i].radius; + if (pos.x + radius > m_fX1 && pos.x - radius < m_fX2 && + pos.y + radius > m_fY1 && pos.y - radius < m_fY2 && + pos.z + radius > m_fZ1 && pos.z - radius < m_fZ2) + return true; + } + return false; +} + +bool CGarage::EntityHasASphereWayOutsideGarage(CEntity * pEntity, float fMargin) +{ + CColModel* pColModel = pEntity->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pEntity->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius + fMargin < m_fX1 || pos.x - radius - fMargin > m_fX2 || + pos.y + radius + fMargin < m_fY1 || pos.y - radius - fMargin > m_fY2 || + pos.z + radius + fMargin < m_fZ1 || pos.z - radius - fMargin > m_fZ2) + return true; + } + return false; +} + +bool CGarage::IsAnyOtherCarTouchingGarage(CVehicle * pException) +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle || pVehicle == pException) + continue; + if (!IsEntityTouching3D(pVehicle)) + continue; + CColModel* pColModel = pVehicle->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pVehicle->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius > m_fX1 && pos.x - radius < m_fX2 && + pos.y + radius > m_fY1 && pos.y - radius < m_fY2 && + pos.z + radius > m_fZ1 && pos.z - radius < m_fZ2) + return true; + } + } + return false; +} + +bool CGarage::IsAnyOtherPedTouchingGarage(CPed * pException) +{ + uint32 i = CPools::GetPedPool()->GetSize(); + while (i--) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed || pPed == pException) + continue; + if (!IsEntityTouching3D(pPed)) + continue; + CColModel* pColModel = pException->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pPed->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius > m_fX1 && pos.x - radius < m_fX2 && + pos.y + radius > m_fY1 && pos.y - radius < m_fY2 && + pos.z + radius > m_fZ1 && pos.z - radius < m_fZ2) + return true; + } + } + return false; +} + +bool CGarage::IsAnyCarBlockingDoor() +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (!IsEntityTouching3D(pVehicle)) + continue; + CColModel* pColModel = pVehicle->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pVehicle->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius < m_fX1 || pos.x - radius > m_fX2 || + pos.y + radius < m_fY1 || pos.y - radius > m_fY2 || + pos.z + radius < m_fZ1 || pos.z - radius > m_fZ2) + return true; + } + } + return false; +} + +int32 CGarage::CountCarsWithCenterPointWithinGarage(CEntity * pException) +{ + int32 total = 0; + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle || pVehicle == pException) + continue; + if (pVehicle->GetPosition().x > m_fX1 && pVehicle->GetPosition().x < m_fX2 && + pVehicle->GetPosition().y > m_fY1 && pVehicle->GetPosition().y < m_fY2 && + pVehicle->GetPosition().z > m_fZ1 && pVehicle->GetPosition().z < m_fZ2) + total++; + } + return total; +} + +void CGarage::RemoveCarsBlockingDoorNotInside() +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (!IsEntityTouching3D(pVehicle)) + continue; + if (pVehicle->GetPosition().x < m_fX1 || pVehicle->GetPosition().x > m_fX2 || + pVehicle->GetPosition().y < m_fY1 || pVehicle->GetPosition().y > m_fY2 || + pVehicle->GetPosition().z < m_fZ1 || pVehicle->GetPosition().z > m_fZ2) { + if (!pVehicle->bIsLocked && pVehicle->CanBeDeleted()) { + CWorld::Remove(pVehicle); + delete pVehicle; +#ifndef FIX_BUGS + return; // makes no sense +#endif + } + } + } +} + +void CGarages::PrintMessages() +{ + if (CTimer::GetTimeInMilliseconds() > MessageStartTime && CTimer::GetTimeInMilliseconds() < MessageEndTime) { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(1.2f, 1.5f); +#endif + CFont::SetPropOn(); + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 50)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 50); +#endif + CFont::SetCentreOn(); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + +#if defined(PS2_HUD) || defined (FIX_BUGS) + float y_offset = SCREEN_HEIGHT / 3; // THIS is PS2 calculation +#else + float y_offset = SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(84.0f); // This is PC and results in text being written over some HUD elements +#endif + + if (MessageNumberInString2 >= 0) { + CMessages::InsertNumberInString(TheText.Get(MessageIDString), MessageNumberInString, MessageNumberInString2, -1, -1, -1, -1, gUString); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(2.0f), y_offset - SCREEN_SCALE_Y(40.0f) + SCREEN_SCALE_Y(2.0f), gUString); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + 2.0f, y_offset - 40.0f + 2.0f, gUString); +#endif + CFont::SetColor(CRGBA(89, 115, 150, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - SCREEN_SCALE_Y(40.0f), gUString); +#else + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - 40.0f, gUString); +#endif + } + else if (MessageNumberInString >= 0) { + CMessages::InsertNumberInString(TheText.Get(MessageIDString), MessageNumberInString, -1, -1, -1, -1, -1, gUString); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(2.0f), y_offset - SCREEN_SCALE_Y(40.0f) + SCREEN_SCALE_Y(2.0f), gUString); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + 2.0f, y_offset - 40.0f + 2.0f, gUString); +#endif + + CFont::SetColor(CRGBA(89, 115, 150, 255)); + +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - SCREEN_SCALE_Y(40.0f), gUString); +#else + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - 40.0f, gUString); +#endif + } + else { +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH / 2 - SCREEN_SCALE_X(2.0f), y_offset - SCREEN_SCALE_Y(2.0f), TheText.Get(MessageIDString)); +#else + CFont::PrintString(SCREEN_WIDTH / 2 - 2.0f, y_offset - 2.0f, TheText.Get(MessageIDString)); +#endif + CFont::SetColor(CRGBA(89, 115, 150, 255)); + CFont::PrintString(SCREEN_WIDTH / 2, y_offset, TheText.Get(MessageIDString)); + } + } +} + +bool CGarages::IsCarSprayable(CVehicle * pVehicle) +{ + switch (pVehicle->GetModelIndex()) { + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_POLICE: + case MI_ENFORCER: + case MI_BUS: + case MI_RHINO: + case MI_BARRACKS: + case MI_DODO: + case MI_COACH: + return false; + default: + break; + } + return true; +} + +void CGarage::UpdateDoorsHeight() +{ + RefreshDoorPointers(false); + if (m_pDoor1) { + m_pDoor1->GetMatrix().GetPosition().z = m_fDoorPos + m_fDoor1Z; + if (m_bRotatedDoor) + BuildRotatedDoorMatrix(m_pDoor1, m_fDoorPos / m_fDoorHeight); + m_pDoor1->GetMatrix().UpdateRW(); + m_pDoor1->UpdateRwFrame(); + } + if (m_pDoor2) { + m_pDoor2->GetMatrix().GetPosition().z = m_fDoorPos + m_fDoor2Z; + if (m_bRotatedDoor) + BuildRotatedDoorMatrix(m_pDoor2, m_fDoorPos / m_fDoorHeight); + m_pDoor2->GetMatrix().UpdateRW(); + m_pDoor2->UpdateRwFrame(); + } +} + +void CGarage::BuildRotatedDoorMatrix(CEntity * pDoor, float fPosition) +{ + float fAngle = -fPosition * HALFPI; + CVector up(-Sin(fAngle) * pDoor->GetForward().y, Sin(fAngle) * pDoor->GetForward().x, Cos(fAngle)); + pDoor->GetRight() = CrossProduct(up, pDoor->GetForward()); + pDoor->GetUp() = up; +} + +void CGarage::UpdateCrusherAngle() +{ + RefreshDoorPointers(false); + m_pDoor2->GetMatrix().SetRotateXOnly(TWOPI - m_fDoorPos); + m_pDoor2->GetMatrix().UpdateRW(); + m_pDoor2->UpdateRwFrame(); +} + +void CGarage::UpdateCrusherShake(float X, float Y) +{ + RefreshDoorPointers(false); + m_pDoor1->GetMatrix().GetPosition().x += X; + m_pDoor1->GetMatrix().GetPosition().y += Y; + m_pDoor1->GetMatrix().UpdateRW(); + m_pDoor1->UpdateRwFrame(); + m_pDoor1->GetMatrix().GetPosition().x -= X; + m_pDoor1->GetMatrix().GetPosition().y -= Y; + m_pDoor2->GetMatrix().GetPosition().x += X; + m_pDoor2->GetMatrix().GetPosition().y += Y; + m_pDoor2->GetMatrix().UpdateRW(); + m_pDoor2->UpdateRwFrame(); + m_pDoor2->GetMatrix().GetPosition().x -= X; + m_pDoor2->GetMatrix().GetPosition().y -= Y; +} + +void CGarage::RefreshDoorPointers(bool bCreate) +{ + bool bNeedToFindDoorEntities = bCreate || m_bRecreateDoorOnNextRefresh; + m_bRecreateDoorOnNextRefresh = false; + if (m_pDoor1) { + if (m_bDoor1IsDummy) { + if (CPools::GetDummyPool()->GetIsFree(CPools::GetDummyPool()->GetJustIndex_NoFreeAssert((CDummy*)m_pDoor1))) + bNeedToFindDoorEntities = true; + else { + if (m_bDoor1PoolIndex != (CPools::GetDummyPool()->GetIndex((CDummy*)m_pDoor1) & 0x7F)) + bNeedToFindDoorEntities = true; + if (!CGarages::IsModelIndexADoor(m_pDoor1->GetModelIndex())) + bNeedToFindDoorEntities = true; + } + } + else { + if (CPools::GetObjectPool()->GetIsFree(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert((CObject*)m_pDoor1))) + bNeedToFindDoorEntities = true; + else { + if (m_bDoor1PoolIndex != (CPools::GetObjectPool()->GetIndex((CObject*)m_pDoor1) & 0x7F)) + bNeedToFindDoorEntities = true; + if (!CGarages::IsModelIndexADoor(m_pDoor1->GetModelIndex())) + bNeedToFindDoorEntities = true; + } + } + } + if (m_pDoor2) { + if (m_bDoor2IsDummy) { + if (CPools::GetDummyPool()->GetIsFree(CPools::GetDummyPool()->GetJustIndex_NoFreeAssert((CDummy*)m_pDoor2))) + bNeedToFindDoorEntities = true; + else { + if (m_bDoor2PoolIndex != (CPools::GetDummyPool()->GetIndex((CDummy*)m_pDoor2) & 0x7F)) + bNeedToFindDoorEntities = true; + if (!CGarages::IsModelIndexADoor(m_pDoor2->GetModelIndex())) + bNeedToFindDoorEntities = true; + } + } + else { + if (CPools::GetObjectPool()->GetIsFree(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert((CObject*)m_pDoor2))) + bNeedToFindDoorEntities = true; + else { + if (m_bDoor2PoolIndex != (CPools::GetObjectPool()->GetIndex((CObject*)m_pDoor2) & 0x7F)) + bNeedToFindDoorEntities = true; + if (!CGarages::IsModelIndexADoor(m_pDoor2->GetModelIndex())) + bNeedToFindDoorEntities = true; + } + } + } + if (bNeedToFindDoorEntities) + FindDoorsEntities(); +} + +void CGarages::TriggerMessage(const char* text, int16 num1, uint16 time, int16 num2) +{ + if (strcmp(text, MessageIDString) == 0 && + CTimer::GetTimeInMilliseconds() >= MessageStartTime && + CTimer::GetTimeInMilliseconds() <= MessageEndTime) { + if (CTimer::GetTimeInMilliseconds() - MessageStartTime <= 500) + return; + MessageStartTime = CTimer::GetTimeInMilliseconds() - 500; + MessageEndTime = CTimer::GetTimeInMilliseconds() - 500 + time; + } + else { + strcpy(MessageIDString, text); + MessageStartTime = CTimer::GetTimeInMilliseconds(); + MessageEndTime = CTimer::GetTimeInMilliseconds() + time; + } + MessageNumberInString = num1; + MessageNumberInString2 = num2; +} + +void CGarages::SetTargetCarForMissonGarage(int16 garage, CVehicle * pVehicle) +{ + assert(garage >= 0 && garage < NUM_GARAGES); + if (pVehicle) { + aGarages[garage].m_pTarget = pVehicle; + if (aGarages[garage].m_eGarageState == GS_CLOSEDCONTAINSCAR) + aGarages[garage].m_eGarageState = GS_FULLYCLOSED; + } + else + aGarages[garage].m_pTarget = nil; +} + +bool CGarages::HasCarBeenDroppedOffYet(int16 garage) +{ + return aGarages[garage].m_eGarageState == GS_CLOSEDCONTAINSCAR; +} + +void CGarages::DeActivateGarage(int16 garage) +{ + aGarages[garage].m_bDeactivated = true; +} + +void CGarages::ActivateGarage(int16 garage) +{ + aGarages[garage].m_bDeactivated = false; + if (aGarages[garage].m_eGarageType == GARAGE_FORCARTOCOMEOUTOF && aGarages[garage].m_eGarageState == GS_FULLYCLOSED) + aGarages[garage].m_eGarageState = GS_OPENING; +} + +int32 CGarages::QueryCarsCollected(int16 garage) +{ + return 0; +} + +bool CGarages::HasImportExportGarageCollectedThisCar(int16 garage, int8 car) +{ + return CarTypesCollected[GetCarsCollectedIndexForGarageType(aGarages[garage].m_eGarageType)] & (BIT(car)); +} + +bool CGarages::IsGarageOpen(int16 garage) +{ + return aGarages[garage].IsOpen(); +} + +bool CGarages::IsGarageClosed(int16 garage) +{ + return aGarages[garage].IsClosed(); +} + +bool CGarages::HasThisCarBeenCollected(int16 garage, uint8 id) +{ + return aGarages[garage].m_bCollectedCarsState & BIT(id); +} + +bool CGarage::DoesCraigNeedThisCar(int32 mi) +{ + if (mi == MI_CORPSE) + mi = MI_MANANA; + int ct = CGarages::GetCarsCollectedIndexForGarageType(m_eGarageType); + for (int i = 0; i < TOTAL_COLLECTCARS_CARS; i++) { + if (mi == gaCarsToCollectInCraigsGarages[ct][i]) + return (CGarages::CarTypesCollected[ct] & BIT(i)) == 0; + } + return false; +} + +bool CGarage::HasCraigCollectedThisCar(int32 mi) +{ + if (mi == MI_CORPSE) + mi = MI_MANANA; + int ct = CGarages::GetCarsCollectedIndexForGarageType(m_eGarageType); + for (int i = 0; i < TOTAL_COLLECTCARS_CARS; i++) { + if (mi == gaCarsToCollectInCraigsGarages[ct][i]) + return CGarages::CarTypesCollected[ct] & BIT(i); + } + return false; +} + +bool CGarage::MarkThisCarAsCollectedForCraig(int32 mi) +{ + if (mi == MI_CORPSE) + mi = MI_MANANA; + int ct = CGarages::GetCarsCollectedIndexForGarageType(m_eGarageType); + int index; + for (index = 0; index < TOTAL_COLLECTCARS_CARS; index++) { + if (mi == gaCarsToCollectInCraigsGarages[ct][index]) + break; + } + if (index >= TOTAL_COLLECTCARS_CARS) + return false; + CGarages::CarTypesCollected[ct] |= BIT(index); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += IMPORT_REWARD; + for (int i = 0; i < TOTAL_COLLECTCARS_CARS; i++) { + if ((CGarages::CarTypesCollected[ct] & BIT(i)) == 0) { + CGarages::TriggerMessage("GA_13", -1, 5000, -1); // Delivered like a pro. Complete the list and there'll be a bonus for you. + return false; + } + } + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += IMPORT_ALLCARS_REWARD; + CGarages::TriggerMessage("GA_14", -1, 5000, -1); // All the cars. NICE! Here's a little something. + return true; +} + +void CGarage::OpenThisGarage() +{ + if (m_eGarageState == GS_FULLYCLOSED || m_eGarageState == GS_CLOSING || m_eGarageState == GS_CLOSEDCONTAINSCAR) + m_eGarageState = GS_OPENING; +} + +void CGarage::CloseThisGarage() +{ + if (m_eGarageState == GS_OPENED || m_eGarageState == GS_OPENING) + m_eGarageState = GS_CLOSING; +} + +float CGarage::CalcDistToGarageRectangleSquared(float X, float Y) +{ + float distX, distY; + if (X < m_fX1) + distX = m_fX1 - X; + else if (X > m_fX2) + distX = X - m_fX2; + else + distX = 0.0f; + if (Y < m_fY1) + distY = m_fY1 - Y; + else if (Y > m_fY2) + distY = Y - m_fY2; + else + distY = 0.0f; + return SQR(distX) + SQR(distY); +} + +float CGarage::CalcSmallestDistToGarageDoorSquared(float X, float Y) +{ + float dist1 = 10000000.0f; + float dist2 = 10000000.0f; + if (m_pDoor1) + dist1 = SQR(m_fDoor1X - X) + SQR(m_fDoor1Y - Y); + if (m_pDoor2) + dist2 = SQR(m_fDoor2X - X) + SQR(m_fDoor2Y - Y); + return Min(dist1, dist2); +} + +void CGarage::FindDoorsEntities() +{ + m_pDoor1 = nil; + m_pDoor2 = nil; + int xstart = Max(0, CWorld::GetSectorIndexX(m_fX1)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(m_fX2)); + int ystart = Max(0, CWorld::GetSectorIndexY(m_fY1)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(m_fY2)); + assert(xstart <= xend); + assert(ystart <= yend); + + CWorld::AdvanceCurrentScanCode(); + + for (int y = ystart; y <= yend; y++) { + for (int x = xstart; x <= xend; x++) { + CSector* s = CWorld::GetSector(x, y); + FindDoorsEntitiesSectorList(s->m_lists[ENTITYLIST_OBJECTS], false); + FindDoorsEntitiesSectorList(s->m_lists[ENTITYLIST_OBJECTS_OVERLAP], false); + FindDoorsEntitiesSectorList(s->m_lists[ENTITYLIST_DUMMIES], true); + FindDoorsEntitiesSectorList(s->m_lists[ENTITYLIST_DUMMIES_OVERLAP], true); + } + } + if (!m_pDoor1 || !m_pDoor2) + return; + if (m_pDoor1->GetModelIndex() == MI_CRUSHERBODY || m_pDoor1->GetModelIndex() == MI_CRUSHERLID) + return; + CVector2D vecDoor1ToGarage(m_pDoor1->GetPosition().x - GetGarageCenterX(), m_pDoor1->GetPosition().y - GetGarageCenterY()); + CVector2D vecDoor2ToGarage(m_pDoor2->GetPosition().x - GetGarageCenterX(), m_pDoor2->GetPosition().y - GetGarageCenterY()); + if (DotProduct2D(vecDoor1ToGarage, vecDoor2ToGarage) > 0.0f) { + if (vecDoor1ToGarage.MagnitudeSqr() >= vecDoor2ToGarage.MagnitudeSqr()) { + m_pDoor1 = m_pDoor2; + m_bDoor1IsDummy = m_bDoor2IsDummy; + } + m_pDoor2 = nil; + m_bDoor2IsDummy = false; + } +} + +void CGarage::FindDoorsEntitiesSectorList(CPtrList& list, bool dummy) +{ + CPtrNode* node; + for (node = list.first; node; node = node->next) { + CEntity* pEntity = (CEntity*)node->item; + if (pEntity->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + pEntity->m_scanCode = CWorld::GetCurrentScanCode(); + if (!pEntity || !CGarages::IsModelIndexADoor(pEntity->GetModelIndex())) + continue; + if (Abs(pEntity->GetPosition().x - GetGarageCenterX()) >= DISTANCE_TO_CONSIDER_DOOR_FOR_GARAGE) + continue; + if (Abs(pEntity->GetPosition().y - GetGarageCenterY()) >= DISTANCE_TO_CONSIDER_DOOR_FOR_GARAGE) + continue; + if (pEntity->GetModelIndex() == MI_CRUSHERBODY) { + m_pDoor1 = pEntity; + m_bDoor1IsDummy = dummy; + // very odd pool operations, they could have used GetJustIndex + if (dummy) + m_bDoor1PoolIndex = (CPools::GetDummyPool()->GetIndex((CDummy*)pEntity)) & 0x7F; + else + m_bDoor1PoolIndex = (CPools::GetObjectPool()->GetIndex((CObject*)pEntity)) & 0x7F; + continue; + } + if (pEntity->GetModelIndex() == MI_CRUSHERLID) { + m_pDoor2 = pEntity; + m_bDoor2IsDummy = dummy; + if (dummy) + m_bDoor2PoolIndex = (CPools::GetDummyPool()->GetIndex((CDummy*)pEntity)) & 0x7F; + else + m_bDoor2PoolIndex = (CPools::GetObjectPool()->GetIndex((CObject*)pEntity)) & 0x7F; + continue; + } + if (!m_pDoor1) { + m_pDoor1 = pEntity; + m_bDoor1IsDummy = dummy; + if (dummy) + m_bDoor1PoolIndex = (CPools::GetDummyPool()->GetIndex((CDummy*)pEntity)) & 0x7F; + else + m_bDoor1PoolIndex = (CPools::GetObjectPool()->GetIndex((CObject*)pEntity)) & 0x7F; + continue; + } + else { + m_pDoor2 = pEntity; + m_bDoor2IsDummy = dummy; + if (dummy) + m_bDoor2PoolIndex = (CPools::GetDummyPool()->GetIndex((CDummy*)pEntity)) & 0x7F; + else + m_bDoor2PoolIndex = (CPools::GetObjectPool()->GetIndex((CObject*)pEntity)) & 0x7F; + } + } +} + +bool CGarages::HasResprayHappened(int16 garage) +{ + bool result = aGarages[garage].m_bResprayHappened; + aGarages[garage].m_bResprayHappened = false; + return result; +} + +void CGarages::SetGarageDoorToRotate(int16 garage) +{ + if (aGarages[garage].m_bRotatedDoor) + return; + aGarages[garage].m_bRotatedDoor = true; + aGarages[garage].m_fDoorHeight /= 2.0f; + aGarages[garage].m_fDoorHeight -= 0.1f; +} + +void CGarages::SetLeaveCameraForThisGarage(int16 garage) +{ + aGarages[garage].m_bCameraFollowsPlayer = true; +} + +bool CGarages::IsThisCarWithinGarageArea(int16 garage, CEntity * pCar) +{ + return aGarages[garage].IsEntityEntirelyInside3D(pCar, 0.0f); +} + +bool CGarages::HasCarBeenCrushed(int32 handle) +{ + return CrushedCarId == handle; +} + +void CStoredCar::StoreCar(CVehicle* pVehicle) +{ + m_nModelIndex = pVehicle->GetModelIndex(); + m_vecPos = pVehicle->GetPosition(); + m_vecAngle = pVehicle->GetForward(); + m_nPrimaryColor = pVehicle->m_currentColour1; + m_nSecondaryColor = pVehicle->m_currentColour2; + m_nRadioStation = pVehicle->m_nRadioStation; + m_nVariationA = pVehicle->m_aExtras[0]; + m_nVariationB = pVehicle->m_aExtras[1]; + m_nFlags = 0; + if (pVehicle->bBulletProof) m_nFlags |= FLAG_BULLETPROOF; + if (pVehicle->bFireProof) m_nFlags |= FLAG_FIREPROOF; + if (pVehicle->bExplosionProof) m_nFlags |= FLAG_EXPLOSIONPROOF; + if (pVehicle->bCollisionProof) m_nFlags |= FLAG_COLLISIONPROOF; + if (pVehicle->bMeleeProof) m_nFlags |= FLAG_MELEEPROOF; + if (pVehicle->IsCar()) + m_nCarBombType = ((CAutomobile*)pVehicle)->m_bombType; +} + +CVehicle* CStoredCar::RestoreCar() +{ + CStreaming::RequestModel(m_nModelIndex, STREAMFLAGS_DEPENDENCY); + if (!CStreaming::HasModelLoaded(m_nModelIndex)) + return nil; +#ifdef FIX_BUGS + CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(m_nModelIndex); + assert(pModelInfo); + if (pModelInfo->m_numComps != 0) +#endif + { + CVehicleModelInfo::SetComponentsToUse(m_nVariationA, m_nVariationB); + } +#ifdef FIX_BUGS + CVehicle* pVehicle; + if (CModelInfo::IsBoatModel(m_nModelIndex)) + pVehicle = new CBoat(m_nModelIndex, RANDOM_VEHICLE); + else + pVehicle = new CAutomobile(m_nModelIndex, RANDOM_VEHICLE); +#else + CVehicle* pVehicle = new CAutomobile(m_nModelIndex, RANDOM_VEHICLE); +#endif + pVehicle->SetPosition(m_vecPos); + pVehicle->SetStatus(STATUS_ABANDONED); + pVehicle->GetForward() = m_vecAngle; + pVehicle->GetRight() = CVector(m_vecAngle.y, -m_vecAngle.x, 0.0f); + pVehicle->GetUp() = CVector(0.0f, 0.0f, 1.0f); + pVehicle->pDriver = nil; + pVehicle->m_currentColour1 = m_nPrimaryColor; + pVehicle->m_currentColour2 = m_nSecondaryColor; + pVehicle->m_nRadioStation = m_nRadioStation; + pVehicle->bFreebies = false; +#ifdef FIX_BUGS + if (pVehicle->IsCar()) +#endif + { + ((CAutomobile*)pVehicle)->m_bombType = m_nCarBombType; +#ifdef FIX_BUGS + if (m_nCarBombType != CARBOMB_NONE) + ((CAutomobile*)pVehicle)->m_pBombRigger = FindPlayerPed(); +#endif + } + pVehicle->bHasBeenOwnedByPlayer = true; + pVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + if (m_nFlags & FLAG_BULLETPROOF) pVehicle->bBulletProof = true; + if (m_nFlags & FLAG_FIREPROOF) pVehicle->bFireProof = true; + if (m_nFlags & FLAG_EXPLOSIONPROOF) pVehicle->bExplosionProof = true; + if (m_nFlags & FLAG_COLLISIONPROOF) pVehicle->bCollisionProof = true; + if (m_nFlags & FLAG_MELEEPROOF) pVehicle->bMeleeProof = true; + return pVehicle; +} + +void CGarage::StoreAndRemoveCarsForThisHideout(CStoredCar* aCars, int32 nMax) +{ + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) + aCars[i].Clear(); + int i = CPools::GetVehiclePool()->GetSize(); + int index = 0; + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (pVehicle->GetPosition().x > m_fX1 && pVehicle->GetPosition().x < m_fX2 && + pVehicle->GetPosition().y > m_fY1 && pVehicle->GetPosition().y < m_fY2 && + pVehicle->GetPosition().z > m_fZ1 && pVehicle->GetPosition().z < m_fZ2) { + if (pVehicle->VehicleCreatedBy != MISSION_VEHICLE) { + if (index < Max(NUM_GARAGE_STORED_CARS, nMax) && !EntityHasASphereWayOutsideGarage(pVehicle, 1.0f)) + aCars[index++].StoreCar(pVehicle); + CWorld::Players[CWorld::PlayerInFocus].CancelPlayerEnteringCars(pVehicle); + CWorld::Remove(pVehicle); + delete pVehicle; + } + } + } + // why? + for (i = index; i < NUM_GARAGE_STORED_CARS; i++) + aCars[i].Clear(); +} + +bool CGarage::RestoreCarsForThisHideout(CStoredCar* aCars) +{ + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + if (aCars[i].HasCar()) { + CVehicle* pVehicle = aCars[i].RestoreCar(); + if (pVehicle) { + CWorld::Add(pVehicle); + aCars[i].Clear(); + } + } + } + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + if (aCars[i].HasCar()) + return false; + } + return true; +} + +bool CGarages::IsPointInAGarageCameraZone(CVector point) +{ + for (int i = 0; i < NUM_GARAGES; i++) { + switch (aGarages[i].m_eGarageType) { + case GARAGE_NONE: + break; + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + if (aGarages[i].m_fX1 - MARGIN_FOR_CAMERA_COLLECTCARS <= point.x && + aGarages[i].m_fX2 + MARGIN_FOR_CAMERA_COLLECTCARS >= point.x && + aGarages[i].m_fY1 - MARGIN_FOR_CAMERA_COLLECTCARS <= point.y && + aGarages[i].m_fY2 + MARGIN_FOR_CAMERA_COLLECTCARS >= point.y) + return true; + break; + default: + if (aGarages[i].m_fX1 - MARGIN_FOR_CAMERA_DEFAULT <= point.x && + aGarages[i].m_fX2 + MARGIN_FOR_CAMERA_DEFAULT >= point.x && + aGarages[i].m_fY1 - MARGIN_FOR_CAMERA_DEFAULT <= point.y && + aGarages[i].m_fY2 + MARGIN_FOR_CAMERA_DEFAULT >= point.y) + return true; + break; + } + } + return false; +} + +bool CGarages::CameraShouldBeOutside() +{ + return bCamShouldBeOutisde; +} + +void CGarages::GivePlayerDetonator() +{ + FindPlayerPed()->GiveWeapon(WEAPONTYPE_DETONATOR, 1); + FindPlayerPed()->GetWeapon(FindPlayerPed()->GetWeaponSlot(WEAPONTYPE_DETONATOR)).m_eWeaponState = WEAPONSTATE_READY; +} + +float CGarages::FindDoorHeightForMI(int32 mi) +{ + return CModelInfo::GetColModel(mi)->boundingBox.max.z - CModelInfo::GetColModel(mi)->boundingBox.min.z - 0.1f; +} + +void CGarage::TidyUpGarage() +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); +#ifdef FIX_BUGS + while (i--) { +#else + while (--i) { +#endif + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle || !pVehicle->IsCar()) + continue; + if (pVehicle->GetPosition().x > m_fX1 && pVehicle->GetPosition().x < m_fX2 && + pVehicle->GetPosition().y > m_fY1 && pVehicle->GetPosition().y < m_fY2 && + pVehicle->GetPosition().z > m_fZ1 && pVehicle->GetPosition().z < m_fZ2) { + if (pVehicle->GetStatus() == STATUS_WRECKED || pVehicle->GetUp().z < 0.5f) { + CWorld::Remove(pVehicle); + delete pVehicle; + } + } + } +} + +void CGarage::TidyUpGarageClose() +{ + uint32 i = CPools::GetVehiclePool()->GetSize(); +#ifdef FIX_BUGS + while (i--) { +#else + while (--i) { +#endif + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle || !pVehicle->IsCar()) + continue; + if (!pVehicle->IsCar() || pVehicle->GetStatus() != STATUS_WRECKED || !IsEntityTouching3D(pVehicle)) + continue; + bool bRemove = false; + if (m_eGarageState != GS_FULLYCLOSED) { + CColModel* pColModel = pVehicle->GetColModel(); + for (int i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pVehicle->GetMatrix() * pColModel->spheres[i].center; + float radius = pColModel->spheres[i].radius; + if (pos.x + radius < m_fX1 || pos.x - radius > m_fX2 || + pos.y + radius < m_fY1 || pos.y - radius > m_fY2 || + pos.z + radius < m_fZ1 || pos.z - radius > m_fZ2) { + bRemove = true; + } + } + } + else + bRemove = true; + if (bRemove) { + // no MISSION_VEHICLE check??? + CWorld::Remove(pVehicle); + delete pVehicle; + } + } +} + +void CGarages::PlayerArrestedOrDied() +{ + static int GarageToBeTidied = 0; // lol + for (int i = 0; i < NUM_GARAGES; i++) { + if (aGarages[i].m_eGarageType != GARAGE_NONE) + aGarages[i].PlayerArrestedOrDied(); + } + MessageEndTime = 0; + MessageStartTime = 0; +} + +void CGarage::PlayerArrestedOrDied() +{ + switch (m_eGarageType) { + case GARAGE_MISSION: + case GARAGE_COLLECTORSITEMS: + case GARAGE_COLLECTSPECIFICCARS: + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + case GARAGE_FORCARTOCOMEOUTOF: + case GARAGE_60SECONDS: + case GARAGE_MISSION_KEEPCAR: + case GARAGE_FOR_SCRIPT_TO_OPEN: + case GARAGE_HIDEOUT_ONE: + case GARAGE_HIDEOUT_TWO: + case GARAGE_HIDEOUT_THREE: + case GARAGE_FOR_SCRIPT_TO_OPEN_AND_CLOSE: + case GARAGE_KEEPS_OPENING_FOR_SPECIFIC_CAR: + case GARAGE_MISSION_KEEPCAR_REMAINCLOSED: + switch (m_eGarageState) { + case GS_OPENED: + case GS_CLOSING: + case GS_OPENING: + m_eGarageState = GS_CLOSING; + break; + default: + break; + } + break; + case GARAGE_BOMBSHOP1: + case GARAGE_BOMBSHOP2: + case GARAGE_BOMBSHOP3: + case GARAGE_RESPRAY: + case GARAGE_CRUSHER: + switch (m_eGarageState) { + case GS_FULLYCLOSED: + case GS_CLOSING: + case GS_OPENING: + m_eGarageState = GS_OPENING; + break; + default: + break; + } + break; + default: + break; + } +} + +void CGarage::CenterCarInGarage(CVehicle* pVehicle) +{ + if (IsAnyOtherCarTouchingGarage(FindPlayerVehicle())) + return; + if (IsAnyOtherPedTouchingGarage(FindPlayerPed())) + return; + CVector pos = pVehicle->GetPosition(); + float garageX = GetGarageCenterX(); + float garageY = GetGarageCenterY(); + float offsetX = garageX - pos.x; + float offsetY = garageY - pos.y; + float offsetZ = pos.z - pos.z; + float distance = CVector(offsetX, offsetY, offsetZ).Magnitude(); + if (distance < RESPRAY_CENTERING_COEFFICIENT) { + pVehicle->GetMatrix().GetPosition().x = GetGarageCenterX(); + pVehicle->GetMatrix().GetPosition().y = GetGarageCenterY(); + } + else { + pVehicle->GetMatrix().GetPosition().x += offsetX * RESPRAY_CENTERING_COEFFICIENT / distance; + pVehicle->GetMatrix().GetPosition().y += offsetY * RESPRAY_CENTERING_COEFFICIENT / distance; + } + if (!IsEntityEntirelyInside3D(pVehicle, 0.1f)) + pVehicle->SetPosition(pos); +} + +void CGarages::CloseHideOutGaragesBeforeSave() +{ + for (int i = 0; i < NUM_GARAGES; i++) { + if (aGarages[i].m_eGarageType != GARAGE_HIDEOUT_ONE && + aGarages[i].m_eGarageType != GARAGE_HIDEOUT_TWO && + aGarages[i].m_eGarageType != GARAGE_HIDEOUT_THREE) + continue; + if (aGarages[i].m_eGarageState != GS_FULLYCLOSED && + (aGarages[i].m_eGarageType != GARAGE_HIDEOUT_ONE || !aGarages[i].IsAnyCarBlockingDoor())) { + aGarages[i].m_eGarageState = GS_FULLYCLOSED; + switch (aGarages[i].m_eGarageType) { + case GARAGE_HIDEOUT_ONE: + aGarages[i].StoreAndRemoveCarsForThisHideout(aCarsInSafeHouse1, NUM_GARAGE_STORED_CARS); + aGarages[i].RemoveCarsBlockingDoorNotInside(); + break; + case GARAGE_HIDEOUT_TWO: + aGarages[i].StoreAndRemoveCarsForThisHideout(aCarsInSafeHouse2, NUM_GARAGE_STORED_CARS); + aGarages[i].RemoveCarsBlockingDoorNotInside(); + break; + case GARAGE_HIDEOUT_THREE: + aGarages[i].StoreAndRemoveCarsForThisHideout(aCarsInSafeHouse3, NUM_GARAGE_STORED_CARS); + aGarages[i].RemoveCarsBlockingDoorNotInside(); + break; + default: + break; + } + } + aGarages[i].m_fDoorPos = 0.0f; + aGarages[i].UpdateDoorsHeight(); + } +} + +int32 CGarages::CountCarsInHideoutGarage(uint8 type) +{ + int32 total = 0; + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + switch (type) { + case GARAGE_HIDEOUT_ONE: + total += (aCarsInSafeHouse1[i].HasCar()); + break; + case GARAGE_HIDEOUT_TWO: + total += (aCarsInSafeHouse2[i].HasCar()); + break; + case GARAGE_HIDEOUT_THREE: + total += (aCarsInSafeHouse3[i].HasCar()); + break; + default: break; + } + } + return total; +} + +int32 CGarages::FindMaxNumStoredCarsForGarage(uint8 type) +{ + switch (type) { + case GARAGE_HIDEOUT_ONE: + return LIMIT_CARS_IN_INDUSTRIAL; + case GARAGE_HIDEOUT_TWO: + return LIMIT_CARS_IN_COMMERCIAL; + case GARAGE_HIDEOUT_THREE: + return LIMIT_CARS_IN_SUBURBAN; + default: break; + } + return 0; +} + +bool CGarages::IsPointWithinHideOutGarage(Const CVector& point) +{ + for (int i = 0; i < NUM_GARAGES; i++) { + switch (aGarages[i].m_eGarageType) { + case GARAGE_HIDEOUT_ONE: + case GARAGE_HIDEOUT_TWO: + case GARAGE_HIDEOUT_THREE: + if (point.x > aGarages[i].m_fX1 && point.x < aGarages[i].m_fX2 && + point.y > aGarages[i].m_fY1 && point.y < aGarages[i].m_fY2 && + point.z > aGarages[i].m_fZ1 && point.z < aGarages[i].m_fZ2) + return true; + default: break; + } + } + return false; +} + +bool CGarages::IsPointWithinAnyGarage(Const CVector& point) +{ + for (int i = 0; i < NUM_GARAGES; i++) { + switch (aGarages[i].m_eGarageType) { + case GARAGE_NONE: + continue; + default: + if (point.x > aGarages[i].m_fX1 && point.x < aGarages[i].m_fX2 && + point.y > aGarages[i].m_fY1 && point.y < aGarages[i].m_fY2 && + point.z > aGarages[i].m_fZ1 && point.z < aGarages[i].m_fZ2) + return true; + } + } + return false; +} + +void CGarages::SetAllDoorsBackToOriginalHeight() +{ + for (int i = 0; i < NUM_GARAGES; i++) { + switch (aGarages[i].m_eGarageType) { + case GARAGE_NONE: + continue; + default: + aGarages[i].RefreshDoorPointers(true); + if (aGarages[i].m_pDoor1) { + aGarages[i].m_pDoor1->GetMatrix().GetPosition().z = aGarages[i].m_fDoor1Z; + if (aGarages[i].m_pDoor1->IsObject()) + ((CObject*)aGarages[i].m_pDoor1)->m_objectMatrix.GetPosition().z = aGarages[i].m_fDoor1Z; + if (aGarages[i].m_bRotatedDoor) + aGarages[i].BuildRotatedDoorMatrix(aGarages[i].m_pDoor1, 0.0f); + aGarages[i].m_pDoor1->GetMatrix().UpdateRW(); + aGarages[i].m_pDoor1->UpdateRwFrame(); + } + if (aGarages[i].m_pDoor2) { + aGarages[i].m_pDoor2->GetMatrix().GetPosition().z = aGarages[i].m_fDoor2Z; + if (aGarages[i].m_pDoor2->IsObject()) + ((CObject*)aGarages[i].m_pDoor2)->m_objectMatrix.GetPosition().z = aGarages[i].m_fDoor2Z; + if (aGarages[i].m_bRotatedDoor) + aGarages[i].BuildRotatedDoorMatrix(aGarages[i].m_pDoor2, 0.0f); + aGarages[i].m_pDoor2->GetMatrix().UpdateRW(); + aGarages[i].m_pDoor2->UpdateRwFrame(); + } + } + } +} + +void CGarages::Save(uint8 * buf, uint32 * size) +{ +#ifdef FIX_GARAGE_SIZE + INITSAVEBUF + *size = (6 * sizeof(uint32) + TOTAL_COLLECTCARS_GARAGES * sizeof(*CarTypesCollected) + sizeof(uint32) + 3 * NUM_GARAGE_STORED_CARS * sizeof(CStoredCar) + NUM_GARAGES * sizeof(CGarage)); +#else + * size = 5484; +#endif +#if !defined THIS_IS_STUPID && !defined FIX_GARAGE_SIZE && defined COMPATIBLE_SAVES + memset(buf + 5240, 0, *size - 5240); // garbage data is written otherwise +#endif + CloseHideOutGaragesBeforeSave(); + WriteSaveBuf(buf, NumGarages); + WriteSaveBuf(buf, (uint32)BombsAreFree); + WriteSaveBuf(buf, (uint32)RespraysAreFree); + WriteSaveBuf(buf, CarsCollected); + WriteSaveBuf(buf, BankVansCollected); + WriteSaveBuf(buf, PoliceCarsCollected); + for (int i = 0; i < TOTAL_COLLECTCARS_GARAGES; i++) + WriteSaveBuf(buf, CarTypesCollected[i]); + WriteSaveBuf(buf, LastTimeHelpMessage); + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + WriteSaveBuf(buf, aCarsInSafeHouse1[i]); + WriteSaveBuf(buf, aCarsInSafeHouse2[i]); + WriteSaveBuf(buf, aCarsInSafeHouse3[i]); + } + for (int i = 0; i < NUM_GARAGES; i++) { +#ifdef COMPATIBLE_SAVES + WriteSaveBuf(buf, aGarages[i].m_eGarageType); + WriteSaveBuf(buf, aGarages[i].m_eGarageState); + WriteSaveBuf(buf, aGarages[i].field_2); + WriteSaveBuf(buf, aGarages[i].m_bClosingWithoutTargetCar); + WriteSaveBuf(buf, aGarages[i].m_bDeactivated); + WriteSaveBuf(buf, aGarages[i].m_bResprayHappened); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, aGarages[i].m_nTargetModelIndex); + ZeroSaveBuf(buf, 4 + 4); + WriteSaveBuf(buf, aGarages[i].m_bDoor1PoolIndex); + WriteSaveBuf(buf, aGarages[i].m_bDoor2PoolIndex); + WriteSaveBuf(buf, aGarages[i].m_bDoor1IsDummy); + WriteSaveBuf(buf, aGarages[i].m_bDoor2IsDummy); + WriteSaveBuf(buf, aGarages[i].m_bRecreateDoorOnNextRefresh); + WriteSaveBuf(buf, aGarages[i].m_bRotatedDoor); + WriteSaveBuf(buf, aGarages[i].m_bCameraFollowsPlayer); + ZeroSaveBuf(buf, 1); + WriteSaveBuf(buf, aGarages[i].m_fX1); + WriteSaveBuf(buf, aGarages[i].m_fX2); + WriteSaveBuf(buf, aGarages[i].m_fY1); + WriteSaveBuf(buf, aGarages[i].m_fY2); + WriteSaveBuf(buf, aGarages[i].m_fZ1); + WriteSaveBuf(buf, aGarages[i].m_fZ2); + WriteSaveBuf(buf, aGarages[i].m_fDoorPos); + WriteSaveBuf(buf, aGarages[i].m_fDoorHeight); + WriteSaveBuf(buf, aGarages[i].m_fDoor1X); + WriteSaveBuf(buf, aGarages[i].m_fDoor1Y); + WriteSaveBuf(buf, aGarages[i].m_fDoor2X); + WriteSaveBuf(buf, aGarages[i].m_fDoor2Y); + WriteSaveBuf(buf, aGarages[i].m_fDoor1Z); + WriteSaveBuf(buf, aGarages[i].m_fDoor2Z); + WriteSaveBuf(buf, aGarages[i].m_nTimeToStartAction); + WriteSaveBuf(buf, aGarages[i].m_bCollectedCarsState); + ZeroSaveBuf(buf, 3 + 4 + 4); + ZeroSaveBuf(buf, sizeof(aGarages[i].m_sStoredCar)); +#else + WriteSaveBuf(buf, aGarages[i]); +#endif + } +#ifdef FIX_GARAGE_SIZE + VALIDATESAVEBUF(*size); +#endif +} + +const CStoredCar &CStoredCar::operator=(const CStoredCar & other) +{ + m_nModelIndex = other.m_nModelIndex; + m_vecPos = other.m_vecPos; + m_vecAngle = other.m_vecAngle; + m_nFlags = other.m_nFlags; + m_nPrimaryColor = other.m_nPrimaryColor; + m_nSecondaryColor = other.m_nSecondaryColor; + m_nRadioStation = other.m_nRadioStation; + m_nVariationA = other.m_nVariationA; + m_nVariationB = other.m_nVariationB; + m_nCarBombType = other.m_nCarBombType; + return *this; +} + +void CGarages::Load(uint8* buf, uint32 size) +{ +#ifdef FIX_GARAGE_SIZE + INITSAVEBUF + assert(size == (6 * sizeof(uint32) + TOTAL_COLLECTCARS_GARAGES * sizeof(*CarTypesCollected) + sizeof(uint32) + 3 * NUM_GARAGE_STORED_CARS * sizeof(CStoredCar) + NUM_GARAGES * sizeof(CGarage))); +#else + assert(size == 5484); +#endif + CloseHideOutGaragesBeforeSave(); + ReadSaveBuf(&NumGarages, buf); + int32 tempInt; + ReadSaveBuf(&tempInt, buf); + BombsAreFree = tempInt ? true : false; + ReadSaveBuf(&tempInt, buf); + RespraysAreFree = tempInt ? true : false; + ReadSaveBuf(&CarsCollected, buf); + ReadSaveBuf(&BankVansCollected, buf); + ReadSaveBuf(&PoliceCarsCollected, buf); + for (int i = 0; i < TOTAL_COLLECTCARS_GARAGES; i++) + ReadSaveBuf(&CarTypesCollected[i], buf); + ReadSaveBuf(&LastTimeHelpMessage, buf); + for (int i = 0; i < NUM_GARAGE_STORED_CARS; i++) { + ReadSaveBuf(&aCarsInSafeHouse1[i], buf); + ReadSaveBuf(&aCarsInSafeHouse2[i], buf); + ReadSaveBuf(&aCarsInSafeHouse3[i], buf); + } + for (int i = 0; i < NUM_GARAGES; i++) { +#ifdef COMPATIBLE_SAVES + ReadSaveBuf(&aGarages[i].m_eGarageType, buf); + ReadSaveBuf(&aGarages[i].m_eGarageState, buf); + ReadSaveBuf(&aGarages[i].field_2, buf); + ReadSaveBuf(&aGarages[i].m_bClosingWithoutTargetCar, buf); + ReadSaveBuf(&aGarages[i].m_bDeactivated, buf); + ReadSaveBuf(&aGarages[i].m_bResprayHappened, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&aGarages[i].m_nTargetModelIndex, buf); + SkipSaveBuf(buf, 4 + 4); + ReadSaveBuf(&aGarages[i].m_bDoor1PoolIndex, buf); + ReadSaveBuf(&aGarages[i].m_bDoor2PoolIndex, buf); + ReadSaveBuf(&aGarages[i].m_bDoor1IsDummy, buf); + ReadSaveBuf(&aGarages[i].m_bDoor2IsDummy, buf); + ReadSaveBuf(&aGarages[i].m_bRecreateDoorOnNextRefresh, buf); + ReadSaveBuf(&aGarages[i].m_bRotatedDoor, buf); + ReadSaveBuf(&aGarages[i].m_bCameraFollowsPlayer, buf); + SkipSaveBuf(buf, 1); + ReadSaveBuf(&aGarages[i].m_fX1, buf); + ReadSaveBuf(&aGarages[i].m_fX2, buf); + ReadSaveBuf(&aGarages[i].m_fY1, buf); + ReadSaveBuf(&aGarages[i].m_fY2, buf); + ReadSaveBuf(&aGarages[i].m_fZ1, buf); + ReadSaveBuf(&aGarages[i].m_fZ2, buf); + ReadSaveBuf(&aGarages[i].m_fDoorPos, buf); + ReadSaveBuf(&aGarages[i].m_fDoorHeight, buf); + ReadSaveBuf(&aGarages[i].m_fDoor1X, buf); + ReadSaveBuf(&aGarages[i].m_fDoor1Y, buf); + ReadSaveBuf(&aGarages[i].m_fDoor2X, buf); + ReadSaveBuf(&aGarages[i].m_fDoor2Y, buf); + ReadSaveBuf(&aGarages[i].m_fDoor1Z, buf); + ReadSaveBuf(&aGarages[i].m_fDoor2Z, buf); + ReadSaveBuf(&aGarages[i].m_nTimeToStartAction, buf); + ReadSaveBuf(&aGarages[i].m_bCollectedCarsState, buf); + SkipSaveBuf(buf, 3 + 4 + 4); + SkipSaveBuf(buf, sizeof(aGarages[i].m_sStoredCar)); +#else + ReadSaveBuf(&aGarages[i], buf); +#endif + aGarages[i].m_pDoor1 = nil; + aGarages[i].m_pDoor2 = nil; + aGarages[i].m_pTarget = nil; + aGarages[i].field_96 = nil; + aGarages[i].m_bRecreateDoorOnNextRefresh = true; + aGarages[i].RefreshDoorPointers(true); + if (aGarages[i].m_eGarageType == GARAGE_CRUSHER) + aGarages[i].UpdateCrusherAngle(); + else + aGarages[i].UpdateDoorsHeight(); + } +#ifdef FIX_GARAGE_SIZE + VALIDATESAVEBUF(size); +#endif + + MessageEndTime = 0; + bCamShouldBeOutisde = false; + MessageStartTime = 0; +} + +bool +CGarages::IsModelIndexADoor(uint32 id) +{ + return id == MI_GARAGEDOOR1 || + id == MI_GARAGEDOOR2 || + id == MI_GARAGEDOOR3 || + id == MI_GARAGEDOOR4 || + id == MI_GARAGEDOOR5 || + id == MI_GARAGEDOOR6 || + id == MI_GARAGEDOOR7 || + id == MI_GARAGEDOOR9 || + id == MI_GARAGEDOOR10 || + id == MI_GARAGEDOOR11 || + id == MI_GARAGEDOOR12 || + id == MI_GARAGEDOOR13 || + id == MI_GARAGEDOOR14 || + id == MI_GARAGEDOOR15 || + id == MI_GARAGEDOOR16 || + id == MI_GARAGEDOOR17 || + id == MI_GARAGEDOOR18 || + id == MI_GARAGEDOOR19 || + id == MI_GARAGEDOOR20 || + id == MI_GARAGEDOOR21 || + id == MI_GARAGEDOOR22 || + id == MI_GARAGEDOOR23 || + id == MI_GARAGEDOOR24 || + id == MI_GARAGEDOOR25 || + id == MI_GARAGEDOOR26 || + id == MI_GARAGEDOOR27 || + id == MI_GARAGEDOOR28 || + id == MI_GARAGEDOOR29 || + id == MI_GARAGEDOOR30 || + id == MI_GARAGEDOOR31 || + id == MI_GARAGEDOOR32 || + id == MI_CRUSHERBODY || + id == MI_CRUSHERLID; +} + +void CGarages::StopCarFromBlowingUp(CAutomobile* pCar) +{ + pCar->m_fFireBlowUpTimer = 0.0f; + pCar->m_fHealth = Max(pCar->m_fHealth, 300.0f); + pCar->Damage.SetEngineStatus(Max(pCar->Damage.GetEngineStatus(), 275)); +} + +bool CGarage::Does60SecondsNeedThisCarAtAll(int mi) +{ + for (int i = 0; i < ARRAY_SIZE(gaCarsToCollectIn60Seconds); i++) { + if (gaCarsToCollectIn60Seconds[i] == mi) + return true; + } + return false; +} + +bool CGarage::Does60SecondsNeedThisCar(int mi) +{ + for (int i = 0; i < ARRAY_SIZE(gaCarsToCollectIn60Seconds); i++) { + if (gaCarsToCollectIn60Seconds[i] == mi) + return m_bCollectedCarsState & BIT(i); + } + return false; +} + +void CGarage::MarkThisCarAsCollectedFor60Seconds(int mi) +{ + for (int i = 0; i < ARRAY_SIZE(gaCarsToCollectIn60Seconds); i++) { + if (gaCarsToCollectIn60Seconds[i] == mi) + m_bCollectedCarsState |= BIT(i); + } +} + +bool CGarage::IsPlayerEntirelyInsideGarage() +{ + return IsEntityEntirelyInside3D(FindPlayerVehicle() ? (CEntity*)FindPlayerVehicle() : (CEntity*)FindPlayerPed(), 0.0f); +} diff --git a/src/control/Garages.h b/src/control/Garages.h new file mode 100644 index 0000000..8a9fd1b --- /dev/null +++ b/src/control/Garages.h @@ -0,0 +1,263 @@ +#pragma once +#include "audio_enums.h" +#include "Camera.h" +#include "config.h" +#include "Lists.h" + +class CVehicle; + +enum eGarageState +{ + GS_FULLYCLOSED, + GS_OPENED, + GS_CLOSING, + GS_OPENING, + GS_OPENEDCONTAINSCAR, + GS_CLOSEDCONTAINSCAR, + GS_AFTERDROPOFF, +}; + +enum eGarageType +{ + GARAGE_NONE, + GARAGE_MISSION, + GARAGE_BOMBSHOP1, + GARAGE_BOMBSHOP2, + GARAGE_BOMBSHOP3, + GARAGE_RESPRAY, + GARAGE_COLLECTORSITEMS, + GARAGE_COLLECTSPECIFICCARS, + GARAGE_COLLECTCARS_1, + GARAGE_COLLECTCARS_2, + GARAGE_COLLECTCARS_3, + GARAGE_FORCARTOCOMEOUTOF, + GARAGE_60SECONDS, + GARAGE_CRUSHER, + GARAGE_MISSION_KEEPCAR, + GARAGE_FOR_SCRIPT_TO_OPEN, + GARAGE_HIDEOUT_ONE, + GARAGE_HIDEOUT_TWO, + GARAGE_HIDEOUT_THREE, + GARAGE_FOR_SCRIPT_TO_OPEN_AND_CLOSE, + GARAGE_KEEPS_OPENING_FOR_SPECIFIC_CAR, + GARAGE_MISSION_KEEPCAR_REMAINCLOSED, +}; + +enum +{ + TOTAL_COLLECTCARS_GARAGES = GARAGE_COLLECTCARS_3 - GARAGE_COLLECTCARS_1 + 1, + TOTAL_COLLECTCARS_CARS = 16 +}; + +class CStoredCar +{ + enum { + FLAG_BULLETPROOF = 0x1, + FLAG_FIREPROOF = 0x2, + FLAG_EXPLOSIONPROOF = 0x4, + FLAG_COLLISIONPROOF = 0x8, + FLAG_MELEEPROOF = 0x10, + }; + int32 m_nModelIndex; + CVector m_vecPos; + CVector m_vecAngle; + int32 m_nFlags; + int8 m_nPrimaryColor; + int8 m_nSecondaryColor; + int8 m_nRadioStation; + int8 m_nVariationA; + int8 m_nVariationB; + int8 m_nCarBombType; +public: + void Init() { m_nModelIndex = 0; } + void Clear() { m_nModelIndex = 0; } + bool HasCar() { return m_nModelIndex != 0; } + const CStoredCar &operator=(const CStoredCar& other); + void StoreCar(CVehicle*); + CVehicle* RestoreCar(); +}; + +VALIDATE_SIZE(CStoredCar, 0x28); + +#define SWITCH_GARAGE_DISTANCE_CLOSE 40.0f + +#define CRUSHER_GARAGE_X1 (1135.5f) +#define CRUSHER_GARAGE_Y1 (57.0f) +#define CRUSHER_GARAGE_Z1 (-1.0f) +#define CRUSHER_GARAGE_X2 (1149.5f) +#define CRUSHER_GARAGE_Y2 (63.7f) +#define CRUSHER_GARAGE_Z2 (3.5f) + +class CGarage +{ +public: + uint8 m_eGarageType; + uint8 m_eGarageState; + bool field_2; // unused + bool m_bClosingWithoutTargetCar; + bool m_bDeactivated; + bool m_bResprayHappened; + int32 m_nTargetModelIndex; + CEntity *m_pDoor1; + CEntity *m_pDoor2; + uint8 m_bDoor1PoolIndex; + uint8 m_bDoor2PoolIndex; + bool m_bDoor1IsDummy; + bool m_bDoor2IsDummy; + bool m_bRecreateDoorOnNextRefresh; + bool m_bRotatedDoor; + bool m_bCameraFollowsPlayer; + float m_fX1; + float m_fX2; + float m_fY1; + float m_fY2; + float m_fZ1; + float m_fZ2; + float m_fDoorPos; + float m_fDoorHeight; + float m_fDoor1X; + float m_fDoor1Y; + float m_fDoor2X; + float m_fDoor2Y; + float m_fDoor1Z; + float m_fDoor2Z; + uint32 m_nTimeToStartAction; + uint8 m_bCollectedCarsState; + CVehicle *m_pTarget; + void* field_96; // unused + CStoredCar m_sStoredCar; // not needed + + void OpenThisGarage(); + void CloseThisGarage(); + bool IsOpen() { return m_eGarageState == GS_OPENED || m_eGarageState == GS_OPENEDCONTAINSCAR; } + bool IsClosed() { return m_eGarageState == GS_FULLYCLOSED; } + bool IsUsed() { return m_eGarageType != GARAGE_NONE; } + void Update(); + float GetGarageCenterX() { return (m_fX1 + m_fX2) / 2; } + float GetGarageCenterY() { return (m_fY1 + m_fY2) / 2; } + bool IsFar() + { +#ifdef FIX_BUGS + return Abs(TheCamera.GetPosition().x - GetGarageCenterX()) > SWITCH_GARAGE_DISTANCE_CLOSE || + Abs(TheCamera.GetPosition().y - GetGarageCenterY()) > SWITCH_GARAGE_DISTANCE_CLOSE; +#else + return Abs(TheCamera.GetPosition().x - m_fX1) > SWITCH_GARAGE_DISTANCE_CLOSE || + Abs(TheCamera.GetPosition().y - m_fY1) > SWITCH_GARAGE_DISTANCE_CLOSE; +#endif + } + void TidyUpGarageClose(); + void TidyUpGarage(); + void RefreshDoorPointers(bool); + void UpdateCrusherAngle(); + void UpdateDoorsHeight(); + bool IsEntityEntirelyInside3D(CEntity*, float); + bool IsEntityEntirelyOutside(CEntity*, float); + bool IsEntityEntirelyInside(CEntity*); + float CalcDistToGarageRectangleSquared(float, float); + float CalcSmallestDistToGarageDoorSquared(float, float); + bool IsAnyOtherCarTouchingGarage(CVehicle* pException); + bool IsStaticPlayerCarEntirelyInside(); + bool IsPlayerOutsideGarage(); + bool IsAnyCarBlockingDoor(); + void CenterCarInGarage(CVehicle*); + bool DoesCraigNeedThisCar(int32); + bool MarkThisCarAsCollectedForCraig(int32); + bool HasCraigCollectedThisCar(int32); + bool IsGarageEmpty(); + void UpdateCrusherShake(float, float); + int32 CountCarsWithCenterPointWithinGarage(CEntity* pException); + void RemoveCarsBlockingDoorNotInside(); + void StoreAndRemoveCarsForThisHideout(CStoredCar*, int32); + bool RestoreCarsForThisHideout(CStoredCar*); + bool IsEntityTouching3D(CEntity*); + bool EntityHasASphereWayOutsideGarage(CEntity*, float); + bool IsAnyOtherPedTouchingGarage(CPed* pException); + void BuildRotatedDoorMatrix(CEntity*, float); + void FindDoorsEntities(); + void FindDoorsEntitiesSectorList(CPtrList&, bool); + void PlayerArrestedOrDied(); + bool Does60SecondsNeedThisCarAtAll(int mi); + bool Does60SecondsNeedThisCar(int mi); + void MarkThisCarAsCollectedFor60Seconds(int mi); + bool IsPlayerEntirelyInsideGarage(); + +}; + +VALIDATE_SIZE(CGarage, 140); + +class CGarages +{ + enum { + MESSAGE_LENGTH = 8 + }; +public: + static int32 BankVansCollected; + static bool BombsAreFree; + static bool RespraysAreFree; + static int32 CarsCollected; + static int32 CarTypesCollected[TOTAL_COLLECTCARS_GARAGES]; + static int32 CrushedCarId; + static uint32 LastTimeHelpMessage; + static int32 MessageNumberInString; + static char MessageIDString[MESSAGE_LENGTH]; + static int32 MessageNumberInString2; + static uint32 MessageStartTime; + static uint32 MessageEndTime; + static uint32 NumGarages; + static bool PlayerInGarage; + static int32 PoliceCarsCollected; + static CGarage aGarages[NUM_GARAGES]; + static CStoredCar aCarsInSafeHouse1[NUM_GARAGE_STORED_CARS]; + static CStoredCar aCarsInSafeHouse2[NUM_GARAGE_STORED_CARS]; + static CStoredCar aCarsInSafeHouse3[NUM_GARAGE_STORED_CARS]; + static bool bCamShouldBeOutisde; + + static void Init(void); +#ifndef PS2 + static void Shutdown(void); +#endif + static void Update(void); + + static int16 AddOne(CVector pos1, CVector pos2, uint8 type, int32 targetId); + static void ChangeGarageType(int16, uint8, int32); + static void PrintMessages(void); + static void TriggerMessage(const char* text, int16, uint16 time, int16); + static void SetTargetCarForMissonGarage(int16, CVehicle*); + static bool HasCarBeenDroppedOffYet(int16); + static void DeActivateGarage(int16); + static void ActivateGarage(int16); + static int32 QueryCarsCollected(int16); + static bool HasImportExportGarageCollectedThisCar(int16, int8); + static bool IsGarageOpen(int16); + static bool IsGarageClosed(int16); + static bool HasThisCarBeenCollected(int16, uint8); + static void OpenGarage(int16 garage) { aGarages[garage].OpenThisGarage(); } + static void CloseGarage(int16 garage) { aGarages[garage].CloseThisGarage(); } + static bool HasResprayHappened(int16); + static void SetGarageDoorToRotate(int16); + static void SetLeaveCameraForThisGarage(int16); + static bool IsThisCarWithinGarageArea(int16, CEntity*); + static bool HasCarBeenCrushed(int32); + static bool IsPointInAGarageCameraZone(CVector); + static bool CameraShouldBeOutside(void); + static void GivePlayerDetonator(void); + static void PlayerArrestedOrDied(void); + static bool IsPointWithinHideOutGarage(Const CVector&); + static bool IsPointWithinAnyGarage(Const CVector&); + static void SetAllDoorsBackToOriginalHeight(void); + static void Save(uint8* buf, uint32* size); + static void Load(uint8* buf, uint32 size); + static bool IsModelIndexADoor(uint32 id); + static void SetFreeBombs(bool bValue) { BombsAreFree = bValue; } + static void SetFreeResprays(bool bValue) { RespraysAreFree = bValue; } + static void StopCarFromBlowingUp(CAutomobile*); + + static bool IsCarSprayable(CVehicle*); + static float FindDoorHeightForMI(int32); + static void CloseHideOutGaragesBeforeSave(void); + static int32 CountCarsInHideoutGarage(uint8); + static int32 FindMaxNumStoredCarsForGarage(uint8); + static int32 GetBombTypeForGarageType(uint8 type) { return type - GARAGE_BOMBSHOP1 + 1; } + static int32 GetCarsCollectedIndexForGarageType(uint8 type) { return type - GARAGE_COLLECTCARS_1; } + +}; diff --git a/src/control/NameGrid.cpp b/src/control/NameGrid.cpp new file mode 100644 index 0000000..204e8b9 --- /dev/null +++ b/src/control/NameGrid.cpp @@ -0,0 +1,87 @@ +#include "common.h" +#include "NameGrid.h" + +// TODO: reverse mobile code + +CPlayerName::CPlayerName() +{ + // TODO +} + +void +CPlayerName::DisplayName(int) +{ + // TODO +} + +CRow::CRow() +{ + // TODO +} + +void +CRow::SetLetter(int, wchar *) +{ + // TODO +} + +CGrid::CGrid() +{ + // TODO +} + +void +CGrid::ProcessAnyLeftJustDown() +{ + unk_int2--; +} + +void +CGrid::ProcessAnyRightJustDown() +{ + unk_int2++; +} + +void +CGrid::ProcessAnyUpJustDown() +{ + unk_int1--; +} + +void +CGrid::ProcessAnyDownJustDown() +{ + unk_int1++; +} + +void +CGrid::AllDoneMakePlayerName() +{ + // TODO +} + +void +CGrid::ProcessDPadCrossJustDown() +{ + // TODO +} + +void +CGrid::DisplayGrid() +{ + // TODO +} + +void +CGrid::ProcessControllerInput() +{ + // TODO +} + +void +CGrid::Process() +{ + ProcessControllerInput(); + DisplayGrid(); + playerName.DisplayName(2 * playerName.unk_4c); +} \ No newline at end of file diff --git a/src/control/NameGrid.h b/src/control/NameGrid.h new file mode 100644 index 0000000..d52cec7 --- /dev/null +++ b/src/control/NameGrid.h @@ -0,0 +1,53 @@ +#pragma once + +// TODO: reverse mobile code + +class CPlayerName +{ + friend class CGrid; + + float x; + float y; + wchar unk_8[34]; + int unk_4c; +public: + CPlayerName(); + void DisplayName(int); +}; + +class CRow +{ + friend class CGrid; + + int unk_0; + int unk_4; + wchar unk_8[20]; + int unk_30; +public: + CRow(); + void SetLetter(int, wchar *); +}; + +class CGrid +{ + CRow rows[5]; + int unk_int1; + int unk_int2; + int unk_int3; + float unk_float1; + float unk_float2; + CPlayerName playerName; + char unk2[4]; + char unk3[4]; +public: + CGrid(); + void ProcessAnyLeftJustDown(); + void ProcessAnyRightJustDown(); + void ProcessAnyUpJustDown(); + void ProcessAnyDownJustDown(); + void AllDoneMakePlayerName(); + void ProcessDPadCrossJustDown(); + void DisplayGrid(); + void ProcessControllerInput(); + void Process(); +}; \ No newline at end of file diff --git a/src/control/OnscreenTimer.cpp b/src/control/OnscreenTimer.cpp new file mode 100644 index 0000000..08c68cb --- /dev/null +++ b/src/control/OnscreenTimer.cpp @@ -0,0 +1,159 @@ +#include "common.h" + + +#include "DMAudio.h" +#include "Hud.h" +#include "Replay.h" +#include "Timer.h" +#include "Script.h" +#include "OnscreenTimer.h" + +void +COnscreenTimer::Init() +{ + m_bDisabled = false; + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) { + m_sEntries[i].m_nTimerOffset = 0; + m_sEntries[i].m_nCounterOffset = 0; + + for(uint32 j = 0; j < 10; j++) { + m_sEntries[i].m_aTimerText[j] = '\0'; + m_sEntries[i].m_aCounterText[j] = '\0'; + } + + m_sEntries[i].m_nType = COUNTER_DISPLAY_NUMBER; + m_sEntries[i].m_bTimerProcessed = false; + m_sEntries[i].m_bCounterProcessed = false; + } +} + +void +COnscreenTimer::Process() +{ + if(!CReplay::IsPlayingBack() && !m_bDisabled) + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) + m_sEntries[i].Process(); +} + +void +COnscreenTimer::ProcessForDisplay() +{ + if(CHud::m_Wants_To_Draw_Hud) { + m_bProcessed = false; + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) + if(m_sEntries[i].ProcessForDisplay()) + m_bProcessed = true; + } +} + +void +COnscreenTimer::ClearCounter(uint32 offset) +{ + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) { + if(offset == m_sEntries[i].m_nCounterOffset) { + m_sEntries[i].m_nCounterOffset = 0; + m_sEntries[i].m_aCounterText[0] = '\0'; + m_sEntries[i].m_nType = COUNTER_DISPLAY_NUMBER; + m_sEntries[i].m_bCounterProcessed = false; + } + } +} + +void +COnscreenTimer::ClearClock(uint32 offset) +{ + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) + if(offset == m_sEntries[i].m_nTimerOffset) { + m_sEntries[i].m_nTimerOffset = 0; + m_sEntries[i].m_aTimerText[0] = '\0'; + m_sEntries[i].m_bTimerProcessed = false; + } +} + +void +COnscreenTimer::AddCounter(uint32 offset, uint16 type, char* text) +{ + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) + if(m_sEntries[i].m_nCounterOffset == 0) { + m_sEntries[i].m_nCounterOffset = offset; + if (text) + strncpy(m_sEntries[i].m_aCounterText, text, 10); + else + m_sEntries[i].m_aCounterText[0] = '\0'; + m_sEntries[i].m_nType = type; + break; + } +} + +void +COnscreenTimer::AddClock(uint32 offset, char* text) +{ + for(uint32 i = 0; i < NUMONSCREENTIMERENTRIES; i++) + if(m_sEntries[i].m_nTimerOffset == 0) { + m_sEntries[i].m_nTimerOffset = offset; + if (text) + strncpy(m_sEntries[i].m_aTimerText, text, 10); + else + m_sEntries[i].m_aTimerText[0] = '\0'; + break; + } +} + +void +COnscreenTimerEntry::Process() +{ + if(m_nTimerOffset == 0) + return; + + int32* timerPtr = CTheScripts::GetPointerToScriptVariable(m_nTimerOffset); + int32 oldTime = *timerPtr; + int32 newTime = oldTime - int32(CTimer::GetTimeStepInMilliseconds()); + if(newTime < 0) { + *timerPtr = 0; + m_bTimerProcessed = false; + m_nTimerOffset = 0; + m_aTimerText[0] = '\0'; + } else { + *timerPtr = newTime; + int32 oldTimeSeconds = oldTime / 1000; + if(oldTimeSeconds < 12 && newTime / 1000 != oldTimeSeconds) { + DMAudio.PlayFrontEndSound(SOUND_CLOCK_TICK, newTime / 1000); + } + } +} + +bool +COnscreenTimerEntry::ProcessForDisplay() +{ + m_bTimerProcessed = false; + m_bCounterProcessed = false; + + if(m_nTimerOffset == 0 && m_nCounterOffset == 0) + return false; + + if(m_nTimerOffset != 0) { + m_bTimerProcessed = true; + ProcessForDisplayClock(); + } + + if(m_nCounterOffset != 0) { + m_bCounterProcessed = true; + ProcessForDisplayCounter(); + } + return true; +} + +void +COnscreenTimerEntry::ProcessForDisplayClock() +{ + uint32 time = *CTheScripts::GetPointerToScriptVariable(m_nTimerOffset); + sprintf(m_bTimerBuffer, "%02d:%02d", time / 1000 / 60, + time / 1000 % 60); +} + +void +COnscreenTimerEntry::ProcessForDisplayCounter() +{ + uint32 counter = *CTheScripts::GetPointerToScriptVariable(m_nCounterOffset); + sprintf(m_bCounterBuffer, "%d", counter); +} diff --git a/src/control/OnscreenTimer.h b/src/control/OnscreenTimer.h new file mode 100644 index 0000000..3ef7764 --- /dev/null +++ b/src/control/OnscreenTimer.h @@ -0,0 +1,49 @@ +#pragma once + +enum +{ + COUNTER_DISPLAY_NUMBER, + COUNTER_DISPLAY_BAR, +}; + +class COnscreenTimerEntry +{ +public: + uint32 m_nTimerOffset; + uint32 m_nCounterOffset; + char m_aTimerText[10]; + char m_aCounterText[10]; + uint16 m_nType; + char m_bCounterBuffer[42]; + char m_bTimerBuffer[42]; + bool m_bTimerProcessed; + bool m_bCounterProcessed; + + void Process(); + bool ProcessForDisplay(); + + void ProcessForDisplayClock(); + void ProcessForDisplayCounter(); +}; + +VALIDATE_SIZE(COnscreenTimerEntry, 0x74); + +class COnscreenTimer +{ +public: + COnscreenTimerEntry m_sEntries[NUMONSCREENTIMERENTRIES]; + bool m_bProcessed; + bool m_bDisabled; + + void Init(); + void Process(); + void ProcessForDisplay(); + + void ClearCounter(uint32 offset); + void ClearClock(uint32 offset); + + void AddCounter(uint32 offset, uint16 type, char* text); + void AddClock(uint32 offset, char* text); +}; + +VALIDATE_SIZE(COnscreenTimer, 0x78); diff --git a/src/control/PathFind.cpp b/src/control/PathFind.cpp new file mode 100644 index 0000000..c640782 --- /dev/null +++ b/src/control/PathFind.cpp @@ -0,0 +1,1830 @@ +#include "common.h" + +#include "General.h" +#include "FileMgr.h" // only needed for empty function +#include "Camera.h" +#include "Vehicle.h" +#include "World.h" +#include "Lines.h" // for debug +#include "PathFind.h" + +bool gbShowPedPaths; +bool gbShowCarPaths; +bool gbShowCarPathsLinks; + +CPathFind ThePaths; + +#define MAX_DIST INT16_MAX-1 +#define MIN_PED_ROUTE_DISTANCE 23.8f + + +#define NUMTEMPNODES 4000 +#define NUMDETACHED_CARS 100 +#define NUMDETACHED_PEDS 50 + + +// object flags: +// 1 UseInRoadBlock +// 2 east/west road(?) + +CPathInfoForObject *InfoForTileCars; +CPathInfoForObject *InfoForTilePeds; + +// unused +CTempDetachedNode *DetachedNodesCars; +CTempDetachedNode *DetachedNodesPeds; + +bool +CPedPath::CalcPedRoute(int8 pathType, CVector position, CVector destination, CVector *pointPoses, int16 *pointsFound, int16 maxPoints) +{ + *pointsFound = 0; + CVector vecDistance = destination - position; + if (Abs(vecDistance.x) > MIN_PED_ROUTE_DISTANCE || Abs(vecDistance.y) > MIN_PED_ROUTE_DISTANCE || Abs(vecDistance.z) > MIN_PED_ROUTE_DISTANCE) + return false; + CVector vecPos = (position + destination) * 0.5f; + CVector vecSectorStartPos (vecPos.x - 14.0f, vecPos.y - 14.0f, vecPos.z); + CVector2D vecSectorEndPos (vecPos.x + 28.0f, vecPos.x + 28.0f); + const int16 nodeStartX = (position.x - vecSectorStartPos.x) / 0.7f; + const int16 nodeStartY = (position.y - vecSectorStartPos.y) / 0.7f; + const int16 nodeEndX = (destination.x - vecSectorStartPos.x) / 0.7f; + const int16 nodeEndY = (destination.y - vecSectorStartPos.y) / 0.7f; + if (nodeStartX == nodeEndX && nodeStartY == nodeEndY) + return false; + CPedPathNode pathNodes[40][40]; + CPedPathNode pathNodesList[416]; + for (int32 x = 0; x < 40; x++) { + for (int32 y = 0; y < 40; y++) { + pathNodes[x][y].bBlockade = false; + pathNodes[x][y].id = INT16_MAX; + pathNodes[x][y].nodeIdX = x; + pathNodes[x][y].nodeIdY = y; + } + } + CWorld::AdvanceCurrentScanCode(); + if (pathType != ROUTE_NO_BLOCKADE) { + const int32 nStartX = Max(CWorld::GetSectorIndexX(vecSectorStartPos.x), 0); + const int32 nStartY = Max(CWorld::GetSectorIndexY(vecSectorStartPos.y), 0); + const int32 nEndX = Min(CWorld::GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(CWorld::GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y - 1); + for (int32 y = nStartY; y <= nEndY; y++) { + for (int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = CWorld::GetSector(x, y); + AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_VEHICLES], pathNodes, &vecSectorStartPos); + AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], pathNodes, &vecSectorStartPos); + AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_OBJECTS], pathNodes, &vecSectorStartPos); + AddBlockadeSectorList(pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], pathNodes, &vecSectorStartPos); + } + } + } + for (int32 i = 0; i < 416; i++) { + pathNodesList[i].prev = nil; + pathNodesList[i].next = nil; + } + CPedPathNode *pStartPathNode = &pathNodes[nodeStartX][nodeStartY]; + CPedPathNode *pEndPathNode = &pathNodes[nodeEndX][nodeEndY]; + pEndPathNode->bBlockade = false; + pEndPathNode->id = 0; + pEndPathNode->prev = nil; + pEndPathNode->next = pathNodesList; + pathNodesList[0].prev = pEndPathNode; + int32 pathNodeIndex = 0; + CPedPathNode *pPreviousNode = nil; + for (; pathNodeIndex < 414; pathNodeIndex++) + { + pPreviousNode = pathNodesList[pathNodeIndex].prev; + while (pPreviousNode && pPreviousNode != pStartPathNode) { + const uint8 nodeIdX = pPreviousNode->nodeIdX; + const uint8 nodeIdY = pPreviousNode->nodeIdY; + if (nodeIdX > 0) { + AddNodeToPathList(&pathNodes[nodeIdX - 1][nodeIdY], pathNodeIndex + 5, pathNodesList); + if (nodeIdY > 0) + AddNodeToPathList(&pathNodes[nodeIdX - 1][nodeIdY - 1], pathNodeIndex + 7, pathNodesList); + if (nodeIdY < 39) + AddNodeToPathList(&pathNodes[nodeIdX - 1][nodeIdY + 1], pathNodeIndex + 7, pathNodesList); + } + if (nodeIdX < 39) { + AddNodeToPathList(&pathNodes[nodeIdX + 1][nodeIdY], pathNodeIndex + 5, pathNodesList); + if (nodeIdY > 0) + AddNodeToPathList(&pathNodes[nodeIdX + 1][nodeIdY - 1], pathNodeIndex + 7, pathNodesList); + if (nodeIdY < 39) + AddNodeToPathList(&pathNodes[nodeIdX + 1][nodeIdY + 1], pathNodeIndex + 7, pathNodesList); + } + if (nodeIdY > 0) + AddNodeToPathList(&pathNodes[nodeIdX][nodeIdY - 1], pathNodeIndex + 5, pathNodesList); + if (nodeIdY < 39) + AddNodeToPathList(&pathNodes[nodeIdX][nodeIdY + 1], pathNodeIndex + 5, pathNodesList); + pPreviousNode = pPreviousNode->prev; + if (!pPreviousNode) + break; + } + + if (pPreviousNode && pPreviousNode == pStartPathNode) + break; + } + if (pathNodeIndex == 414) + return false; + CPedPathNode *pPathNode = pStartPathNode; + for (*pointsFound = 0; pPathNode != pEndPathNode && *pointsFound < maxPoints; ++ *pointsFound) { + const uint8 nodeIdX = pPathNode->nodeIdX; + const uint8 nodeIdY = pPathNode->nodeIdY; + if (nodeIdX > 0 && pathNodes[nodeIdX - 1][nodeIdY].id + 5 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX - 1][nodeIdY]; + else if (nodeIdX > 39 && pathNodes[nodeIdX + 1][nodeIdY].id + 5 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX + 1][nodeIdY]; + else if (nodeIdY > 0 && pathNodes[nodeIdX][nodeIdY - 1].id + 5 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX][nodeIdY - 1]; + else if (nodeIdY > 39 && pathNodes[nodeIdX][nodeIdY + 1].id + 5 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX][nodeIdY + 1]; + else if (nodeIdX > 0 && nodeIdY > 0 && pathNodes[nodeIdX - 1][nodeIdY - 1].id + 7 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX - 1][nodeIdY - 1]; + else if (nodeIdX > 0 && nodeIdY < 39 && pathNodes[nodeIdX - 1][nodeIdY + 1].id + 7 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX - 1][nodeIdY + 1]; + else if (nodeIdX < 39 && nodeIdY > 0 && pathNodes[nodeIdX + 1][nodeIdY - 1].id + 7 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX + 1][nodeIdY - 1]; + else if (nodeIdX < 39 && nodeIdY < 39 && pathNodes[nodeIdX + 1][nodeIdY + 1].id + 7 == pPathNode->id) + pPathNode = &pathNodes[nodeIdX + 1][nodeIdY + 1]; + pointPoses[*pointsFound] = vecSectorStartPos; + pointPoses[*pointsFound].x += pPathNode->nodeIdX * 0.7f; + pointPoses[*pointsFound].y += pPathNode->nodeIdY * 0.7f; + } + return true; +} + + +void +CPedPath::AddNodeToPathList(CPedPathNode *pNodeToAdd, int16 id, CPedPathNode *pNodeList) +{ + if (!pNodeToAdd->bBlockade && id < pNodeToAdd->id) { + if (pNodeToAdd->id != INT16_MAX) + RemoveNodeFromList(pNodeToAdd); + AddNodeToList(pNodeToAdd, id, pNodeList); + } +} + +void +CPedPath::RemoveNodeFromList(CPedPathNode *pNode) +{ + pNode->next->prev = pNode->prev; + if (pNode->prev) + pNode->prev->next = pNode->next; +} + +void +CPedPath::AddNodeToList(CPedPathNode *pNode, int16 index, CPedPathNode *pList) +{ + pNode->prev = pList[index].prev; + pNode->next = &pList[index]; + if (pList[index].prev) + pList[index].prev->next = pNode; + pList[index].prev = pNode; + pNode->id = index; +} + +void +CPedPath::AddBlockadeSectorList(CPtrList& list, CPedPathNode(*pathNodes)[40], CVector *pPosition) +{ + CPtrNode* listNode = list.first; + while (listNode) { + CEntity* pEntity = (CEntity*)listNode->item; + if (pEntity->m_scanCode != CWorld::GetCurrentScanCode() && pEntity->bUsesCollision) { + pEntity->m_scanCode = CWorld::GetCurrentScanCode(); + AddBlockade(pEntity, pathNodes, pPosition); + } + listNode = listNode->next; + } +} + +void +CPedPath::AddBlockade(CEntity *pEntity, CPedPathNode(*pathNodes)[40], CVector *pPosition) +{ + const CColBox& boundingBox = pEntity->GetColModel()->boundingBox; + const float fBoundMaxY = boundingBox.max.y + 0.3f; + const float fBoundMinY = boundingBox.min.y - 0.3f; + const float fBoundMaxX = boundingBox.max.x + 0.3f; + const float fDistanceX = pPosition->x - pEntity->GetMatrix().GetPosition().x; + const float fDistanceY = pPosition->y - pEntity->GetMatrix().GetPosition().y; + const float fBoundRadius = pEntity->GetBoundRadius(); + CVector vecBoundCentre; + pEntity->GetBoundCentre(vecBoundCentre); + if (vecBoundCentre.x + fBoundRadius >= pPosition->x && + vecBoundCentre.y + fBoundRadius >= pPosition->y && + vecBoundCentre.x - fBoundRadius <= pPosition->x + 28.0f && + vecBoundCentre.y - fBoundRadius <= pPosition->y + 28.0f) { + for (int16 x = 0; x < 40; x++) { + const float pointX = x * 0.7f + fDistanceX; + for (int16 y = 0; y < 40; y++) { + if (!pathNodes[x][y].bBlockade) { + const float pointY = y * 0.7f + fDistanceY; + CVector2D point(pointX, pointY); + if (fBoundMaxX > Abs(DotProduct2D(point, pEntity->GetMatrix().GetRight()))) { + float fDotProduct = DotProduct2D(point, pEntity->GetMatrix().GetForward()); + if (fBoundMaxY > fDotProduct && fBoundMinY < fDotProduct) + pathNodes[x][y].bBlockade = true; + } + } + } + } + } +} + +void +CPathFind::Init(void) +{ + int i; + + m_numPathNodes = 0; + m_numMapObjects = 0; + m_numConnections = 0; + m_numCarPathLinks = 0; + unk = 0; + + for(i = 0; i < NUM_PATHNODES; i++) + m_pathNodes[i].distance = MAX_DIST; +} + +void +CPathFind::AllocatePathFindInfoMem(int16 numPathGroups) +{ + delete[] InfoForTileCars; + InfoForTileCars = nil; + delete[] InfoForTilePeds; + InfoForTilePeds = nil; + + // NB: MIAMI doesn't use numPathGroups here but hardcodes 4500 + InfoForTileCars = new CPathInfoForObject[12*numPathGroups]; + memset(InfoForTileCars, 0, 12*numPathGroups*sizeof(CPathInfoForObject)); + InfoForTilePeds = new CPathInfoForObject[12*numPathGroups]; + memset(InfoForTilePeds, 0, 12*numPathGroups*sizeof(CPathInfoForObject)); + + // unused + delete[] DetachedNodesCars; + DetachedNodesCars = nil; + delete[] DetachedNodesPeds; + DetachedNodesPeds = nil; + DetachedNodesCars = new CTempDetachedNode[NUMDETACHED_CARS]; + memset(DetachedNodesCars, 0, NUMDETACHED_CARS*sizeof(CTempDetachedNode)); + DetachedNodesPeds = new CTempDetachedNode[NUMDETACHED_PEDS]; + memset(DetachedNodesPeds, 0, NUMDETACHED_PEDS*sizeof(CTempDetachedNode)); +} + +void +CPathFind::RegisterMapObject(CTreadable *mapObject) +{ + m_mapObjects[m_numMapObjects++] = mapObject; +} + +void +CPathFind::StoreNodeInfoPed(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, bool crossing) +{ + int i, j; + + i = id*12 + node; + InfoForTilePeds[i].type = type; + InfoForTilePeds[i].next = next; + InfoForTilePeds[i].x = x; + InfoForTilePeds[i].y = y; + InfoForTilePeds[i].z = z; + InfoForTilePeds[i].numLeftLanes = 0; + InfoForTilePeds[i].numRightLanes = 0; + InfoForTilePeds[i].crossing = crossing; + + if(type) + for(i = 0; i < node; i++){ + j = id*12 + i; + if(x == InfoForTilePeds[j].x && y == InfoForTilePeds[j].y){ + printf("^^^^^^^^^^^^^ AARON IS TOO CHICKEN TO EAT MEAT!\n"); + printf("Several ped nodes on one road segment have identical coordinates (%d==%d && %d==%d)\n", + x, InfoForTilePeds[j].x, y, InfoForTilePeds[j].y); + printf("Modelindex of cullprit: %d\n\n", id); + } + } +} + +void +CPathFind::StoreNodeInfoCar(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, int8 numLeft, int8 numRight) +{ + int i, j; + + i = id*12 + node; + InfoForTileCars[i].type = type; + InfoForTileCars[i].next = next; + InfoForTileCars[i].x = x; + InfoForTileCars[i].y = y; + InfoForTileCars[i].z = z; + InfoForTileCars[i].numLeftLanes = numLeft; + InfoForTileCars[i].numRightLanes = numRight; + + + if(type) + for(i = 0; i < node; i++){ + j = id*12 + i; + if(x == InfoForTileCars[j].x && y == InfoForTileCars[j].y){ + printf("^^^^^^^^^^^^^ AARON IS TOO CHICKEN TO EAT MEAT!\n"); + printf("Several car nodes on one road segment have identical coordinates (%d==%d && %d==%d)\n", + x, InfoForTileCars[j].x, y, InfoForTileCars[j].y); + printf("Modelindex of cullprit: %d\n\n", id); + } + } +} + +void +CPathFind::CalcNodeCoors(int16 x, int16 y, int16 z, int id, CVector *out) +{ + CVector pos; + pos.x = x / 16.0f; + pos.y = y / 16.0f; + pos.z = z / 16.0f; + *out = m_mapObjects[id]->GetMatrix() * pos; +} + +bool +CPathFind::LoadPathFindData(void) +{ + CFileMgr::SetDir(""); + return false; +} + +void +CPathFind::PreparePathData(void) +{ + int i, j, k; + int numExtern, numIntern, numLanes; + float maxX, maxY; + CTempNode *tempNodes; + + printf("PreparePathData\n"); + if(!CPathFind::LoadPathFindData() && // empty + InfoForTileCars && InfoForTilePeds && + DetachedNodesCars && DetachedNodesPeds + ){ + tempNodes = new CTempNode[NUMTEMPNODES]; + + m_numConnections = 0; + + for(i = 0; i < PATHNODESIZE; i++) + m_pathNodes[i].unkBits = 0; + + for(i = 0; i < PATHNODESIZE; i++){ + numExtern = 0; + numIntern = 0; + for(j = 0; j < 12; j++){ + if(InfoForTileCars[i*12 + j].type == NodeTypeExtern) + numExtern++; + if(InfoForTileCars[i*12 + j].type == NodeTypeIntern) + numIntern++; + } + if(numIntern > 1 && numExtern != 2) + printf("ILLEGAL BLOCK. MORE THAN 1 INTERNALS AND NOT 2 EXTERNALS (Modelindex:%d)\n", i); + } + + for(i = 0; i < PATHNODESIZE; i++) + for(j = 0; j < 12; j++) + if(InfoForTileCars[i*12 + j].type == NodeTypeExtern){ + // MIAMI has MI:%d here but no argument for it + if(InfoForTileCars[i*12 + j].numLeftLanes < 0) + printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); + if(InfoForTileCars[i*12 + j].numRightLanes < 0) + printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); + if(InfoForTileCars[i*12 + j].numLeftLanes + InfoForTileCars[i*12 + j].numRightLanes <= 0) + printf("ILLEGAL BLOCK. NO LANES IN NODE (Obj:%d)\n", i); + } + + m_numPathNodes = 0; + PreparePathDataForType(PATH_CAR, tempNodes, InfoForTileCars, 1.0f, DetachedNodesCars, NUMDETACHED_CARS); + m_numCarPathNodes = m_numPathNodes; + PreparePathDataForType(PATH_PED, tempNodes, InfoForTilePeds, 1.0f, DetachedNodesPeds, NUMDETACHED_PEDS); + m_numPedPathNodes = m_numPathNodes - m_numCarPathNodes; + + // TODO: figure out what exactly is going on here + // Some roads seem to get a west/east flag + for(i = 0; i < m_numMapObjects; i++){ + numExtern = 0; + numIntern = 0; + numLanes = 0; + maxX = 0.0f; + maxY = 0.0f; + for(j = 0; j < 12; j++){ + k = m_mapObjects[i]->GetModelIndex()*12 + j; + if(InfoForTileCars[k].type == NodeTypeExtern){ + numExtern++; + numLanes = Max(numLanes, InfoForTileCars[k].numLeftLanes + InfoForTileCars[k].numRightLanes); + maxX = Max(maxX, Abs(InfoForTileCars[k].x)); + maxY = Max(maxY, Abs(InfoForTileCars[k].y)); + }else if(InfoForTileCars[k].type == NodeTypeIntern) + numIntern++; + } + + if(numIntern == 1 && numExtern == 2){ + if(numLanes < 4){ + if((i & 7) == 4){ // 1/8 probability + m_objectFlags[i] |= UseInRoadBlock; + if(maxX > maxY) + m_objectFlags[i] |= ObjectEastWest; + else + m_objectFlags[i] &= ~ObjectEastWest; + } + }else{ + m_objectFlags[i] |= UseInRoadBlock; + if(maxX > maxY) + m_objectFlags[i] |= ObjectEastWest; + else + m_objectFlags[i] &= ~ObjectEastWest; + } + } + } + + delete[] tempNodes; + + CountFloodFillGroups(PATH_CAR); + CountFloodFillGroups(PATH_PED); + + delete[] InfoForTileCars; + InfoForTileCars = nil; + delete[] InfoForTilePeds; + InfoForTilePeds = nil; + + delete[] DetachedNodesCars; + DetachedNodesCars = nil; + delete[] DetachedNodesPeds; + DetachedNodesPeds = nil; + } + printf("Done with PreparePathData\n"); +} + +/* String together connected nodes in a list by a flood fill algorithm */ +void +CPathFind::CountFloodFillGroups(uint8 type) +{ + int start, end; + int i, l; + uint16 n; + CPathNode *node, *prev; + + switch(type){ + case PATH_CAR: + start = 0; + end = m_numCarPathNodes; + break; + case PATH_PED: + start = m_numCarPathNodes; + end = start + m_numPedPathNodes; + break; + } + + for(i = start; i < end; i++) + m_pathNodes[i].group = 0; + + n = 0; + for(;;){ + n++; + if(n > 1500){ + for(i = start; m_pathNodes[i].group && i < end; i++); + printf("NumNodes:%d Accounted for:%d\n", end - start, i - start); + } + + // Look for unvisited node + for(i = start; m_pathNodes[i].group && i < end; i++); + if(i == end) + break; + + node = &m_pathNodes[i]; + node->SetNext(nil); + node->group = n; + + if(node->numLinks == 0){ + if(type == PATH_CAR) + printf("Single car node: %f %f %f (%d)\n", + node->GetX(), node->GetY(), node->GetZ(), m_mapObjects[node->objectIndex]->GetModelIndex()); + else + printf("Single ped node: %f %f %f\n", + node->GetX(), node->GetY(), node->GetZ()); + } + + while(node){ + prev = node; + node = node->GetNext(); + for(i = 0; i < prev->numLinks; i++){ + l = ConnectedNode(prev->firstLink + i); + if(m_pathNodes[l].group == 0){ + m_pathNodes[l].group = n; + if(m_pathNodes[l].group == 0) + m_pathNodes[l].group = INT8_MIN; + m_pathNodes[l].SetNext(node); + node = &m_pathNodes[l]; + } + } + } + } + + m_numGroups[type] = n-1; + printf("GraphType:%d. FloodFill groups:%d\n", type, n); +} + +int32 TempListLength; + +void +CPathFind::PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo, + float maxdist, CTempDetachedNode *detachednodes, int numDetached) +{ + static CVector CoorsXFormed; + int i, j, k, l; + int l1, l2; + int start; + float posx, posy; + float dx, dy, mag; + float nearestDist; + int nearestId; + int next; + int oldNumPathNodes, oldNumLinks; + float dist; + int iseg, jseg; + int istart, jstart; + int done, cont; + int tileStart; + +#ifndef MASTER + for (i = 0; i < m_numMapObjects-1; i++) + for (j = i+1; j < m_numMapObjects; j++) { + CTreadable *obj1 = m_mapObjects[i]; + CTreadable *obj2 = m_mapObjects[j]; + if (obj1->GetModelIndex() == obj2->GetModelIndex() && + obj1->GetPosition().x == obj2->GetPosition().x && obj1->GetPosition().y == obj2->GetPosition().y && obj1->GetPosition().z == obj2->GetPosition().z && + obj1->GetRight().x == obj2->GetRight().x && obj1->GetForward().x == obj2->GetForward().x && obj1->GetUp().x == obj2->GetUp().x && + obj1->GetRight().y == obj2->GetRight().y && obj1->GetForward().y == obj2->GetForward().y && obj1->GetUp().y == obj2->GetUp().y && + obj1->GetRight().z == obj2->GetRight().z && obj1->GetForward().z == obj2->GetForward().z && obj1->GetUp().z == obj2->GetUp().z) { + printf("THIS IS VERY BAD INDEED. FIX IMMEDIATELY!!!\n"); + printf("Double road objects at the following coors: %f %f %f\n", obj1->GetPosition().x, obj1->GetPosition().y, obj1->GetPosition().z); + } + } +#endif // !MASTER + + oldNumPathNodes = m_numPathNodes; + oldNumLinks = m_numConnections; + +#define OBJECTINDEX(n) (m_pathNodes[(n)].objectIndex) + // Initialize map objects + for(i = 0; i < m_numMapObjects; i++) + for(j = 0; j < 12; j++) + m_mapObjects[i]->m_nodeIndices[type][j] = -1; + + // Calculate internal nodes, store them and connect them to defining object + for(i = 0; i < m_numMapObjects; i++){ + tileStart = m_numPathNodes; + start = 12 * m_mapObjects[i]->GetModelIndex(); + for(j = 0; j < 12; j++){ + if(objectpathinfo[start + j].type == NodeTypeIntern){ + CalcNodeCoors( + objectpathinfo[start + j].x, + objectpathinfo[start + j].y, + objectpathinfo[start + j].z, + i, + &CoorsXFormed); + m_pathNodes[m_numPathNodes].SetPosition(CoorsXFormed); + OBJECTINDEX(m_numPathNodes) = i; + m_pathNodes[m_numPathNodes].unkBits = 1; + m_mapObjects[i]->m_nodeIndices[type][j] = m_numPathNodes; + m_numPathNodes++; + } + } + } + + + // Insert external nodes into TempList + TempListLength = 0; + for(i = 0; i < m_numMapObjects; i++){ + start = 12 * m_mapObjects[i]->GetModelIndex(); + for(j = 0; j < 12; j++){ + if(objectpathinfo[start + j].type != NodeTypeExtern) + continue; + CalcNodeCoors( + objectpathinfo[start + j].x, + objectpathinfo[start + j].y, + objectpathinfo[start + j].z, + i, + &CoorsXFormed); + + // find closest unconnected node + nearestId = -1; + nearestDist = maxdist; + for(k = 0; k < TempListLength; k++){ + if(tempnodes[k].linkState != 1) + continue; + dx = tempnodes[k].pos.x - CoorsXFormed.x; + if(Abs(dx) < nearestDist){ + dy = tempnodes[k].pos.y - CoorsXFormed.y; + if(Abs(dy) < nearestDist){ + nearestDist = Max(Abs(dx), Abs(dy)); + nearestId = k; + } + } + } + + if(nearestId < 0){ + // None found, add this one to temp list + tempnodes[TempListLength].pos = CoorsXFormed; + next = objectpathinfo[start + j].next; + if(next < 0){ + // no link from this node, find link to this node + next = 0; + for(k = start; j != objectpathinfo[k].next; k++) + next++; + } + // link to connecting internal node + tempnodes[TempListLength].link1 = m_mapObjects[i]->m_nodeIndices[type][next]; + if(type == PATH_CAR){ + tempnodes[TempListLength].numLeftLanes = objectpathinfo[start + j].numLeftLanes; + tempnodes[TempListLength].numRightLanes = objectpathinfo[start + j].numRightLanes; + } + tempnodes[TempListLength++].linkState = 1; + }else{ + // Found nearest, connect it to our neighbour + next = objectpathinfo[start + j].next; + if(next < 0){ + // no link from this node, find link to this node + next = 0; + for(k = start; j != objectpathinfo[k].next; k++) + next++; + } + tempnodes[nearestId].link2 = m_mapObjects[i]->m_nodeIndices[type][next]; + tempnodes[nearestId].linkState = 2; + + // collapse this node with nearest we found + dx = m_pathNodes[tempnodes[nearestId].link1].GetX() - m_pathNodes[tempnodes[nearestId].link2].GetX(); + dy = m_pathNodes[tempnodes[nearestId].link1].GetY() - m_pathNodes[tempnodes[nearestId].link2].GetY(); + tempnodes[nearestId].pos = (tempnodes[nearestId].pos + CoorsXFormed)*0.5f; + mag = Sqrt(dx*dx + dy*dy); + tempnodes[nearestId].dirX = dx/mag; + tempnodes[nearestId].dirY = dy/mag; + // do something when number of lanes doesn't agree + if(type == PATH_CAR) + if(tempnodes[nearestId].numLeftLanes != 0 && tempnodes[nearestId].numRightLanes != 0 && + (objectpathinfo[start + j].numLeftLanes == 0 || objectpathinfo[start + j].numRightLanes == 0)){ + // why switch left and right here? + tempnodes[nearestId].numLeftLanes = objectpathinfo[start + j].numRightLanes; + tempnodes[nearestId].numRightLanes = objectpathinfo[start + j].numLeftLanes; + } + } + } + } + + // Loop through previously added internal nodes and link them + for(i = oldNumPathNodes; i < m_numPathNodes; i++){ + // Init link + m_pathNodes[i].numLinks = 0; + m_pathNodes[i].firstLink = m_numConnections; + + // See if node connects to external nodes + for(j = 0; j < TempListLength; j++){ + if(tempnodes[j].linkState != 2) + continue; + + // Add link to other side of the external + // NB this clears the flags in MIAMI + if(tempnodes[j].link1 == i) + m_connections[m_numConnections] = tempnodes[j].link2; + else if(tempnodes[j].link2 == i) + m_connections[m_numConnections] = tempnodes[j].link1; + else + continue; + + dist = (m_pathNodes[i].GetPosition() - m_pathNodes[ConnectedNode(m_numConnections)].GetPosition()).Magnitude(); + m_distances[m_numConnections] = dist; + m_connectionFlags[m_numConnections].flags = 0; + + if(type == PATH_CAR){ + // IMPROVE: use a goto here + // Find existing car path link + for(k = 0; k < m_numCarPathLinks; k++){ + if(m_carPathLinks[k].dir.x == tempnodes[j].dirX && + m_carPathLinks[k].dir.y == tempnodes[j].dirY && + m_carPathLinks[k].pos.x == tempnodes[j].pos.x && + m_carPathLinks[k].pos.y == tempnodes[j].pos.y){ + m_carPathConnections[m_numConnections] = k; + k = m_numCarPathLinks; + } + } + // k is m_numCarPathLinks+1 if we found one + if(k == m_numCarPathLinks){ + m_carPathLinks[m_numCarPathLinks].dir.x = tempnodes[j].dirX; + m_carPathLinks[m_numCarPathLinks].dir.y = tempnodes[j].dirY; + m_carPathLinks[m_numCarPathLinks].pos.x = tempnodes[j].pos.x; + m_carPathLinks[m_numCarPathLinks].pos.y = tempnodes[j].pos.y; + m_carPathLinks[m_numCarPathLinks].pathNodeIndex = i; + m_carPathLinks[m_numCarPathLinks].numLeftLanes = tempnodes[j].numLeftLanes; + m_carPathLinks[m_numCarPathLinks].numRightLanes = tempnodes[j].numRightLanes; + m_carPathLinks[m_numCarPathLinks].trafficLightType = 0; + assert(m_numCarPathLinks <= NUM_CARPATHLINKS); + m_carPathConnections[m_numConnections] = m_numCarPathLinks++; + } + } + + m_pathNodes[i].numLinks++; + m_numConnections++; + } + + + // Find i inside path segment + iseg = 0; + for(j = Max(oldNumPathNodes, i-12); j < i; j++) + if(OBJECTINDEX(j) == OBJECTINDEX(i)) + iseg++; + + istart = 12 * m_mapObjects[m_pathNodes[i].objectIndex]->GetModelIndex(); + // Add links to other internal nodes + for(j = Max(oldNumPathNodes, i-12); j < Min(m_numPathNodes, i+12); j++){ + if(OBJECTINDEX(i) != OBJECTINDEX(j) || i == j) + continue; + // N.B.: in every path segment, the externals have to be at the end + jseg = j-i + iseg; + + jstart = 12 * m_mapObjects[m_pathNodes[j].objectIndex]->GetModelIndex(); + if(objectpathinfo[istart + iseg].next == jseg || + objectpathinfo[jstart + jseg].next == iseg){ + // Found a link between i and jConnectionSetCrossesRoad + // NB this clears the flags in MIAMI + m_connections[m_numConnections] = j; + dist = (m_pathNodes[i].GetPosition() - m_pathNodes[j].GetPosition()).Magnitude(); + m_distances[m_numConnections] = dist; + + if(type == PATH_CAR){ + posx = (m_pathNodes[i].GetX() + m_pathNodes[j].GetX())*0.5f; + posy = (m_pathNodes[i].GetY() + m_pathNodes[j].GetY())*0.5f; + dx = m_pathNodes[j].GetX() - m_pathNodes[i].GetX(); + dy = m_pathNodes[j].GetY() - m_pathNodes[i].GetY(); + mag = Sqrt(dx*dx + dy*dy); + dx /= mag; + dy /= mag; + if(i < j){ + dx = -dx; + dy = -dy; + } + // IMPROVE: use a goto here + // Find existing car path link + for(k = 0; k < m_numCarPathLinks; k++){ + if(m_carPathLinks[k].dir.x == dx && + m_carPathLinks[k].dir.y == dy && + m_carPathLinks[k].pos.x == posx && + m_carPathLinks[k].pos.y == posy){ + m_carPathConnections[m_numConnections] = k; + k = m_numCarPathLinks; + } + } + // k is m_numCarPathLinks+1 if we found one + if(k == m_numCarPathLinks){ + m_carPathLinks[m_numCarPathLinks].dir.x = dx; + m_carPathLinks[m_numCarPathLinks].dir.y = dy; + m_carPathLinks[m_numCarPathLinks].pos.x = posx; + m_carPathLinks[m_numCarPathLinks].pos.y = posy; + m_carPathLinks[m_numCarPathLinks].pathNodeIndex = i; + m_carPathLinks[m_numCarPathLinks].numLeftLanes = -1; + m_carPathLinks[m_numCarPathLinks].numRightLanes = -1; + m_carPathLinks[m_numCarPathLinks].trafficLightType = 0; + assert(m_numCarPathLinks <= NUM_CARPATHLINKS); + m_carPathConnections[m_numConnections] = m_numCarPathLinks++; + } + }else{ + // Crosses road + if(objectpathinfo[istart + iseg].next == jseg && objectpathinfo[istart + iseg].crossing || + objectpathinfo[jstart + jseg].next == iseg && objectpathinfo[jstart + jseg].crossing) + m_connectionFlags[m_numConnections].bCrossesRoad = true; + else + m_connectionFlags[m_numConnections].bCrossesRoad = false; + } + + m_pathNodes[i].numLinks++; + m_numConnections++; + } + } + } + + if(type == PATH_CAR){ + done = 0; + // Set number of lanes for all nodes somehow + // very strange code + for(k = 0; !done && k < 10; k++){ + done = 1; + for(i = 0; i < m_numPathNodes; i++){ + if(m_pathNodes[i].numLinks != 2) + continue; + l1 = m_carPathConnections[m_pathNodes[i].firstLink]; + l2 = m_carPathConnections[m_pathNodes[i].firstLink+1]; + + if(m_carPathLinks[l1].numLeftLanes == -1 && + m_carPathLinks[l2].numLeftLanes != -1){ + done = 0; + if(m_carPathLinks[l2].pathNodeIndex == i){ + // why switch left and right here? + m_carPathLinks[l1].numLeftLanes = m_carPathLinks[l2].numRightLanes; + m_carPathLinks[l1].numRightLanes = m_carPathLinks[l2].numLeftLanes; + }else{ + m_carPathLinks[l1].numLeftLanes = m_carPathLinks[l2].numLeftLanes; + m_carPathLinks[l1].numRightLanes = m_carPathLinks[l2].numRightLanes; + } + m_carPathLinks[l1].pathNodeIndex = i; + }else if(m_carPathLinks[l1].numLeftLanes != -1 && + m_carPathLinks[l2].numLeftLanes == -1){ + done = 0; + if(m_carPathLinks[l1].pathNodeIndex == i){ + // why switch left and right here? + m_carPathLinks[l2].numLeftLanes = m_carPathLinks[l1].numRightLanes; + m_carPathLinks[l2].numRightLanes = m_carPathLinks[l1].numLeftLanes; + }else{ + m_carPathLinks[l2].numLeftLanes = m_carPathLinks[l1].numLeftLanes; + m_carPathLinks[l2].numRightLanes = m_carPathLinks[l1].numRightLanes; + } + m_carPathLinks[l2].pathNodeIndex = i; + }else if(m_carPathLinks[l1].numLeftLanes == -1 && + m_carPathLinks[l2].numLeftLanes == -1) + done = 0; + } + } + + // Fall back to default values for number of lanes + for(i = 0; i < m_numPathNodes; i++) + for(j = 0; j < m_pathNodes[i].numLinks; j++){ + k = m_carPathConnections[m_pathNodes[i].firstLink + j]; + if(m_carPathLinks[k].numLeftLanes < 0) + m_carPathLinks[k].numLeftLanes = 1; + if(m_carPathLinks[k].numRightLanes < 0) + m_carPathLinks[k].numRightLanes = 1; + } + } + + // Set flags for car nodes + if(type == PATH_CAR){ + do{ + cont = 0; + for(i = 0; i < m_numPathNodes; i++){ + m_pathNodes[i].bDisabled = false; + m_pathNodes[i].bBetweenLevels = false; + // See if node is a dead end, if so, we're not done yet + if(!m_pathNodes[i].bDeadEnd){ + k = 0; + for(j = 0; j < m_pathNodes[i].numLinks; j++) + if(!m_pathNodes[ConnectedNode(m_pathNodes[i].firstLink + j)].bDeadEnd) + k++; + if(k < 2){ + m_pathNodes[i].bDeadEnd = true; + cont = 1; + } + } + } + }while(cont); + } + + // Remove isolated ped nodes + if(type == PATH_PED) + for(i = oldNumPathNodes; i < m_numPathNodes; i++){ + if(m_pathNodes[i].numLinks != 0) + continue; + + // Remove node + for(j = i; j < m_numPathNodes-1; j++) + m_pathNodes[j] = m_pathNodes[j+1]; + + // Fix links + for(j = oldNumLinks; j < m_numConnections; j++){ + int node = ConnectedNode(j); + if(node >= i) + m_connections[j] = node-1; + } + + // Also in treadables + for(j = 0; j < m_numMapObjects; j++) + for(k = 0; k < 12; k++){ + if(m_mapObjects[j]->m_nodeIndices[PATH_PED][k] == i){ + // remove this one + for(l = k; l < 12-1; l++) + m_mapObjects[j]->m_nodeIndices[PATH_PED][l] = m_mapObjects[j]->m_nodeIndices[PATH_PED][l+1]; + m_mapObjects[j]->m_nodeIndices[PATH_PED][11] = -1; + }else if(m_mapObjects[j]->m_nodeIndices[PATH_PED][k] > i) + m_mapObjects[j]->m_nodeIndices[PATH_PED][k]--; + } + + i--; + m_numPathNodes--; + } +} + +float +CPathFind::CalcRoadDensity(float x, float y) +{ + int i, j; + float density = 0.0f; + + for(i = 0; i < m_numCarPathNodes; i++){ + if(Abs(m_pathNodes[i].GetX() - x) < 80.0f && + Abs(m_pathNodes[i].GetY() - y) < 80.0f && + m_pathNodes[i].numLinks > 0){ + for(j = 0; j < m_pathNodes[i].numLinks; j++){ + int next = ConnectedNode(m_pathNodes[i].firstLink + j); + float dist = (m_pathNodes[i].GetPosition() - m_pathNodes[next].GetPosition()).Magnitude2D(); + next = m_carPathConnections[m_pathNodes[i].firstLink + j]; + density += m_carPathLinks[next].numLeftLanes * dist; + density += m_carPathLinks[next].numRightLanes * dist; + + if(m_carPathLinks[next].numLeftLanes < 0) + printf("Link from object %d to %d (MIs)\n", + m_mapObjects[m_pathNodes[i].objectIndex]->GetModelIndex(), + m_mapObjects[m_pathNodes[ConnectedNode(m_pathNodes[i].firstLink + j)].objectIndex]->GetModelIndex()); + if(m_carPathLinks[next].numRightLanes < 0) + printf("Link from object %d to %d (MIs)\n", + m_mapObjects[m_pathNodes[i].objectIndex]->GetModelIndex(), + m_mapObjects[m_pathNodes[ConnectedNode(m_pathNodes[i].firstLink + j)].objectIndex]->GetModelIndex()); + } + } + } + return density/2500.0f; +} + +bool +CPathFind::TestForPedTrafficLight(CPathNode *n1, CPathNode *n2) +{ + int i; + for(i = 0; i < n1->numLinks; i++) + if(&m_pathNodes[ConnectedNode(n1->firstLink + i)] == n2) + return ConnectionHasTrafficLight(n1->firstLink + i); + return false; +} + +bool +CPathFind::TestCrossesRoad(CPathNode *n1, CPathNode *n2) +{ + int i; + for(i = 0; i < n1->numLinks; i++) + if(&m_pathNodes[ConnectedNode(n1->firstLink + i)] == n2) + return ConnectionCrossesRoad(n1->firstLink + i); + return false; +} + +void +CPathFind::AddNodeToList(CPathNode *node, int32 listId) +{ + int i = listId & 0x1FF; + node->SetNext(m_searchNodes[i].GetNext()); + node->SetPrev(&m_searchNodes[i]); + if(m_searchNodes[i].GetNext()) + m_searchNodes[i].GetNext()->SetPrev(node); + m_searchNodes[i].SetNext(node); + node->distance = listId; +} + +void +CPathFind::RemoveNodeFromList(CPathNode *node) +{ + node->GetPrev()->SetNext(node->GetNext()); + if(node->GetNext()) + node->GetNext()->SetPrev(node->GetPrev()); +} + +void +CPathFind::RemoveBadStartNode(CVector pos, CPathNode **nodes, int16 *n) +{ + int i; + if(*n < 2) + return; + if(DotProduct2D(nodes[1]->GetPosition() - pos, nodes[0]->GetPosition() - pos) < 0.0f){ + (*n)--; + for(i = 0; i < *n; i++) + nodes[i] = nodes[i+1]; + } +} + +void +CPathFind::SetLinksBridgeLights(float x1, float x2, float y1, float y2, bool enable) +{ + int i; + for(i = 0; i < m_numCarPathLinks; i++){ + CVector2D pos = m_carPathLinks[i].GetPosition(); + if(x1 < pos.x && pos.x < x2 && + y1 < pos.y && pos.y < y2) + m_carPathLinks[i].bBridgeLights = enable; + } +} + +void +CPathFind::SwitchOffNodeAndNeighbours(int32 nodeId, bool disable) +{ + int i, next; + + m_pathNodes[nodeId].bDisabled = disable; + if(m_pathNodes[nodeId].numLinks < 3) + for(i = 0; i < m_pathNodes[nodeId].numLinks; i++){ + next = ConnectedNode(m_pathNodes[nodeId].firstLink + i); + if(m_pathNodes[next].bDisabled != disable && + m_pathNodes[next].numLinks < 3) + SwitchOffNodeAndNeighbours(next, disable); + } +} + +void +CPathFind::SwitchRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable) +{ + int i; + + for(i = 0; i < m_numCarPathNodes; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(x1 <= pos.x && pos.x <= x2 && + y1 <= pos.y && pos.y <= y2 && + z1 <= pos.z && pos.z <= z2 && + disable != m_pathNodes[i].bDisabled) + SwitchOffNodeAndNeighbours(i, disable); + } +} + +void +CPathFind::SwitchPedRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable) +{ + int i; + + for(i = m_numCarPathNodes; i < m_numPathNodes; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(x1 <= pos.x && pos.x <= x2 && + y1 <= pos.y && pos.y <= y2 && + z1 <= pos.z && pos.z <= z2 && + disable != m_pathNodes[i].bDisabled) + SwitchOffNodeAndNeighbours(i, disable); + } +} + +void +CPathFind::SwitchRoadsInAngledArea(float x1, float y1, float z1, float x2, float y2, float z2, float length, uint8 type, uint8 mode) +{ + int i; + int firstNode, lastNode; + + // this is NOT PATH_CAR + if(type != 0){ + firstNode = 0; + lastNode = m_numCarPathNodes; + }else{ + firstNode = m_numCarPathNodes; + lastNode = m_numPathNodes; + } + + if(z1 > z2){ + float tmp = z2; + z2 = z1; + z1 = tmp; + } + + // angle of vector from p2 to p1 + float angle = CGeneral::GetRadianAngleBetweenPoints(x1, y1, x2, y2) + HALFPI; + while(angle < 0.0f) angle += TWOPI; + while(angle > TWOPI) angle -= TWOPI; + // vector from p1 to p2 + CVector2D v12(x2 - x1, y2 - y1); + float len12 = v12.Magnitude(); + v12 /= len12; + + // vector from p2 to new point p3 + CVector2D v23(Sin(angle)*length, -(Cos(angle)*length)); + v23 /= v23.Magnitude(); // obivously just 'length' but whatever + + bool disable = mode == SWITCH_OFF; + for(i = firstNode; i < lastNode; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(pos.z < z1 || pos.z > z2) + continue; + CVector2D d(pos.x - x1, pos.y - y1); + float dot = DotProduct2D(d, v12); + if(dot < 0.0f || dot > len12) + continue; + dot = DotProduct2D(d, v23); + if(dot < 0.0f || dot > length) + continue; + if(m_pathNodes[i].bDisabled != disable) + SwitchOffNodeAndNeighbours(i, disable); + } +} + +void +CPathFind::MarkRoadsBetweenLevelsNodeAndNeighbours(int32 nodeId) +{ + int i, next; + + m_pathNodes[nodeId].bBetweenLevels = true; + if(m_pathNodes[nodeId].numLinks < 3) + for(i = 0; i < m_pathNodes[nodeId].numLinks; i++){ + next = ConnectedNode(m_pathNodes[nodeId].firstLink + i); + if(!m_pathNodes[next].bBetweenLevels && + m_pathNodes[next].numLinks < 3) + MarkRoadsBetweenLevelsNodeAndNeighbours(next); + } +} + +void +CPathFind::MarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2) +{ + int i; + + for(i = 0; i < m_numPathNodes; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(x1 < pos.x && pos.x < x2 && + y1 < pos.y && pos.y < y2 && + z1 < pos.z && pos.z < z2) + MarkRoadsBetweenLevelsNodeAndNeighbours(i); + } +} + +void +CPathFind::PedMarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2) +{ + int i; + + for(i = m_numCarPathNodes; i < m_numPathNodes; i++){ + CVector pos = m_pathNodes[i].GetPosition(); + if(x1 < pos.x && pos.x < x2 && + y1 < pos.y && pos.y < y2 && + z1 < pos.z && pos.z < z2) + MarkRoadsBetweenLevelsNodeAndNeighbours(i); + } +} + +int32 +CPathFind::FindNodeClosestToCoors(CVector coors, uint8 type, float distLimit, bool ignoreDisabled, bool ignoreBetweenLevels) +{ + int i; + int firstNode, lastNode; + float dist; + float closestDist = 10000.0f; + int closestNode = 0; + + switch(type){ + case PATH_CAR: + firstNode = 0; + lastNode = m_numCarPathNodes; + break; + case PATH_PED: + firstNode = m_numCarPathNodes; + lastNode = m_numPathNodes; + break; + } + + for(i = firstNode; i < lastNode; i++){ + if(ignoreDisabled && m_pathNodes[i].bDisabled) continue; + if(ignoreBetweenLevels && m_pathNodes[i].bBetweenLevels) continue; + switch(m_pathNodes[i].unkBits){ + case 1: + case 2: + dist = Abs(m_pathNodes[i].GetX() - coors.x) + + Abs(m_pathNodes[i].GetY() - coors.y) + + 3.0f*Abs(m_pathNodes[i].GetZ() - coors.z); + if(dist < closestDist){ + closestDist = dist; + closestNode = i; + } + break; + } + } + return closestDist < distLimit ? closestNode : -1; +} + +int32 +CPathFind::FindNodeClosestToCoorsFavourDirection(CVector coors, uint8 type, float dirX, float dirY) +{ + int i; + int firstNode, lastNode; + float dist, dX, dY; + NormalizeXY(dirX, dirY); + float closestDist = 10000.0f; + int closestNode = 0; + + switch(type){ + case PATH_CAR: + firstNode = 0; + lastNode = m_numCarPathNodes; + break; + case PATH_PED: + firstNode = m_numCarPathNodes; + lastNode = m_numPathNodes; + break; + } + + for(i = firstNode; i < lastNode; i++){ + switch(m_pathNodes[i].unkBits){ + case 1: + case 2: + dX = m_pathNodes[i].GetX() - coors.x; + dY = m_pathNodes[i].GetY() - coors.y; + dist = Abs(dX) + Abs(dY) + + 3.0f*Abs(m_pathNodes[i].GetZ() - coors.z); + if(dist < closestDist){ + NormalizeXY(dX, dY); + dist -= (dX*dirX + dY*dirY - 1.0f)*20.0f; + if(dist < closestDist){ + closestDist = dist; + closestNode = i; + } + } + break; + } + } + return closestNode; +} + +float +CPathFind::FindNodeOrientationForCarPlacement(int32 nodeId) +{ + if(m_pathNodes[nodeId].numLinks == 0) + return 0.0f; + CVector dir = m_pathNodes[ConnectedNode(m_pathNodes[nodeId].firstLink)].GetPosition() - m_pathNodes[nodeId].GetPosition(); + dir.z = 0.0f; + dir.Normalise(); + return RADTODEG(dir.Heading()); +} + +float +CPathFind::FindNodeOrientationForCarPlacementFacingDestination(int32 nodeId, float x, float y, bool towards) +{ + int i; + + CVector targetDir(x - m_pathNodes[nodeId].GetX(), y - m_pathNodes[nodeId].GetY(), 0.0f); + targetDir.Normalise(); + CVector dir; + + if(m_pathNodes[nodeId].numLinks == 0) + return 0.0f; + + int bestNode = ConnectedNode(m_pathNodes[nodeId].firstLink); +#ifdef FIX_BUGS + float bestDot = towards ? -2.0f : 2.0f; +#else + int bestDot = towards ? -2 : 2; // why int? +#endif + + for(i = 0; i < m_pathNodes[nodeId].numLinks; i++){ + dir = m_pathNodes[ConnectedNode(m_pathNodes[nodeId].firstLink + i)].GetPosition() - m_pathNodes[nodeId].GetPosition(); + dir.z = 0.0f; + dir.Normalise(); + float angle = DotProduct2D(dir, targetDir); + if(towards){ + if(angle > bestDot){ + bestDot = angle; + bestNode = ConnectedNode(m_pathNodes[nodeId].firstLink + i); + } + }else{ + if(angle < bestDot){ + bestDot = angle; + bestNode = ConnectedNode(m_pathNodes[nodeId].firstLink + i); + } + } + } + + dir = m_pathNodes[bestNode].GetPosition() - m_pathNodes[nodeId].GetPosition(); + dir.z = 0.0f; + dir.Normalise(); + return RADTODEG(dir.Heading()); +} + +bool +CPathFind::NewGenerateCarCreationCoors(float x, float y, float dirX, float dirY, float spawnDist, float angleLimit, bool forward, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, bool ignoreDisabled) +{ + int i, j; + int node1, node2; + float dist1, dist2, d1, d2; + + if(m_numCarPathNodes == 0) + return false; + + for(i = 0; i < 500; i++){ + node1 = (CGeneral::GetRandomNumber()>>3) % m_numCarPathNodes; + if(m_pathNodes[node1].bDisabled && !ignoreDisabled) + continue; + dist1 = Distance2D(m_pathNodes[node1].GetPosition(), x, y); + if(dist1 < spawnDist + 60.0f){ + d1 = dist1 - spawnDist; + for(j = 0; j < m_pathNodes[node1].numLinks; j++){ + node2 = ConnectedNode(m_pathNodes[node1].firstLink + j); + if(m_pathNodes[node2].bDisabled && !ignoreDisabled) + continue; + dist2 = Distance2D(m_pathNodes[node2].GetPosition(), x, y); + d2 = dist2 - spawnDist; + if(d1*d2 < 0.0f){ + // nodes are on different sides of spawn distance + float f2 = Abs(d1)/(Abs(d1) + Abs(d2)); + float f1 = 1.0f - f2; + *pPositionBetweenNodes = f2; + CVector pos = m_pathNodes[node1].GetPosition()*f1 + m_pathNodes[node2].GetPosition()*f2; + CVector2D dist2d(pos.x - x, pos.y - y); + dist2d.Normalise(); // done manually in the game + float dot = DotProduct2D(dist2d, CVector2D(dirX, dirY)); + if(forward){ + if(dot > angleLimit){ + *pNode1 = node1; + *pNode2 = node2; + *pPosition = pos; + return true; + } + }else{ + if(dot <= angleLimit){ + *pNode1 = node1; + *pNode2 = node2; + *pPosition = pos; + return true; + } + } + } + } + } + } + return false; +} + +bool +CPathFind::GeneratePedCreationCoors(float x, float y, float minDist, float maxDist, float minDistOffScreen, float maxDistOffScreen, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, CMatrix *camMatrix) +{ + int i; + int node1, node2; + + if(m_numPedPathNodes == 0) + return false; + + for(i = 0; i < 400; i++){ + node1 = m_numCarPathNodes + CGeneral::GetRandomNumber() % m_numPedPathNodes; + if(DistanceSqr2D(m_pathNodes[node1].GetPosition(), x, y) < sq(maxDist+30.0f)){ + if(m_pathNodes[node1].numLinks == 0) + continue; + int link = m_pathNodes[node1].firstLink + CGeneral::GetRandomNumber() % m_pathNodes[node1].numLinks; + if(ConnectionCrossesRoad(link)) + continue; + node2 = ConnectedNode(link); + if(m_pathNodes[node1].bDisabled || m_pathNodes[node2].bDisabled) + continue; + + float f2 = (CGeneral::GetRandomNumber()&0xFF)/256.0f; + float f1 = 1.0f - f2; + *pPositionBetweenNodes = f2; + CVector pos = m_pathNodes[node1].GetPosition()*f1 + m_pathNodes[node2].GetPosition()*f2; + if(Distance2D(pos, x, y) < maxDist+20.0f){ + pos.x += ((CGeneral::GetRandomNumber()&0xFF)-128)*0.01f; + pos.y += ((CGeneral::GetRandomNumber()&0xFF)-128)*0.01f; + float dist = Distance2D(pos, x, y); + + bool visible; + if(camMatrix) + visible = TheCamera.IsSphereVisible(pos, 2.0f, camMatrix); + else + visible = TheCamera.IsSphereVisible(pos, 2.0f); + if(!visible){ + minDist = minDistOffScreen; + maxDist = maxDistOffScreen; + } + if(minDist < dist && dist < maxDist){ + *pNode1 = node1; + *pNode2 = node2; + *pPosition = pos; + + bool found; + float groundZ = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z+2.0f, &found); + if(!found) + return false; + if(Abs(groundZ - pos.z) > 3.0f) + return false; + pPosition->z = groundZ; + return true; + } + } + } + } + return false; +} + +CTreadable* +CPathFind::FindRoadObjectClosestToCoors(CVector coors, uint8 type) +{ + int i, j, k; + int node1, node2; + CTreadable *closestMapObj = nil; + float closestDist = 10000.0f; + + for(i = 0; i < m_numMapObjects; i++){ + CTreadable *mapObj = m_mapObjects[i]; + if(mapObj->m_nodeIndices[type][0] < 0) + continue; + CVector vDist = mapObj->GetPosition() - coors; + float fDist = Abs(vDist.x) + Abs(vDist.y) + Abs(vDist.z); + if(fDist < 200.0f || fDist < closestDist) + for(j = 0; j < 12; j++){ + node1 = mapObj->m_nodeIndices[type][j]; + if(node1 < 0) + break; + // FIX: game uses ThePaths here explicitly + for(k = 0; k < m_pathNodes[node1].numLinks; k++){ + node2 = ConnectedNode(m_pathNodes[node1].firstLink + k); + float lineDist = CCollision::DistToLine(&m_pathNodes[node1].GetPosition(), &m_pathNodes[node2].GetPosition(), &coors); + if(lineDist < closestDist){ + closestDist = lineDist; + if((coors - m_pathNodes[node1].GetPosition()).MagnitudeSqr() < (coors - m_pathNodes[node2].GetPosition()).MagnitudeSqr()) + closestMapObj = m_mapObjects[m_pathNodes[node1].objectIndex]; + else + closestMapObj = m_mapObjects[m_pathNodes[node2].objectIndex]; + } + } + } + } + return closestMapObj; +} + +void +CPathFind::FindNextNodeWandering(uint8 type, CVector coors, CPathNode **lastNode, CPathNode **nextNode, uint8 curDir, uint8 *nextDir) +{ + int i; + CPathNode *node; + + if(lastNode == nil || (node = *lastNode) == nil || (coors - (*lastNode)->GetPosition()).MagnitudeSqr() > 7.0f){ + // need to find the node we're coming from + node = nil; + CTreadable *obj = FindRoadObjectClosestToCoors(coors, type); + float nodeDist = 1000000000.0f; + for(i = 0; i < 12; i++){ + if(obj->m_nodeIndices[type][i] < 0) + break; + float dist = (coors - m_pathNodes[obj->m_nodeIndices[type][i]].GetPosition()).MagnitudeSqr(); + if(dist < nodeDist){ + nodeDist = dist; + node = &m_pathNodes[obj->m_nodeIndices[type][i]]; + } + } + } + + CVector2D vCurDir(Sin(curDir*PI/4.0f), Cos(curDir * PI / 4.0f)); + *nextNode = 0; + float bestDot = -999999.0f; + for(i = 0; i < node->numLinks; i++){ + int next = ConnectedNode(node->firstLink+i); + if(!node->bDisabled && m_pathNodes[next].bDisabled) + continue; + CVector pedCoors = coors; + pedCoors.z += 1.0f; + CVector nodeCoors = m_pathNodes[next].GetPosition(); + nodeCoors.z += 1.0f; + if(!CWorld::GetIsLineOfSightClear(pedCoors, nodeCoors, true, false, false, false, false, false)) + continue; + CVector2D nodeDir = m_pathNodes[next].GetPosition() - node->GetPosition(); + nodeDir.Normalise(); + float dot = DotProduct2D(nodeDir, vCurDir); + if(dot >= bestDot){ + *nextNode = &m_pathNodes[next]; + bestDot = dot; + + // direction is 0, 2, 4, 6 for north, east, south, west + // this could be done simpler... + if(nodeDir.x < 0.0f){ + if(2.0f*Abs(nodeDir.y) < -nodeDir.x) + *nextDir = 6; // west + else if(-2.0f*nodeDir.x < nodeDir.y) + *nextDir = 0; // north + else if(2.0f*nodeDir.x > nodeDir.y) + *nextDir = 4; // south + else if(nodeDir.y > 0.0f) + *nextDir = 7; // north west + else + *nextDir = 5; // south west` + }else{ + if(2.0f*Abs(nodeDir.y) < nodeDir.x) + *nextDir = 2; // east + else if(2.0f*nodeDir.x < nodeDir.y) + *nextDir = 0; // north + else if(-2.0f*nodeDir.x > nodeDir.y) + *nextDir = 4; // south + else if(nodeDir.y > 0.0f) + *nextDir = 1; // north east + else + *nextDir = 3; // south east` + } + } + } + if(*nextNode == nil){ + *nextDir = 0; + *nextNode = node; + } +} + +static CPathNode *apNodesToBeCleared[4995]; + +void +CPathFind::DoPathSearch(uint8 type, CVector start, int32 startNodeId, CVector target, CPathNode **nodes, int16 *pNumNodes, int16 maxNumNodes, CVehicle *vehicle, float *pDist, float distLimit, int32 targetNodeId) +{ + int i, j; + + // Find target + if(targetNodeId < 0) + targetNodeId = FindNodeClosestToCoors(target, type, distLimit); + if(targetNodeId < 0) { + *pNumNodes = 0; + if(pDist) *pDist = 100000.0f; + return; + } + + // Find start + int numPathsToTry; + CTreadable *startObj; + if(startNodeId < 0){ + if(vehicle == nil || (startObj = vehicle->m_treadable[type]) == nil) + startObj = FindRoadObjectClosestToCoors(start, type); + numPathsToTry = 0; + for(i = 0; i < 12; i++){ + if(startObj->m_nodeIndices[type][i] < 0) + break; + if(m_pathNodes[startObj->m_nodeIndices[type][i]].group == m_pathNodes[targetNodeId].group) + numPathsToTry++; + } + }else{ + numPathsToTry = 1; + startObj = m_mapObjects[m_pathNodes[startNodeId].objectIndex]; + } + if(numPathsToTry == 0) { + *pNumNodes = 0; + if(pDist) *pDist = 100000.0f; + return; + } + + if(startNodeId < 0){ + // why only check node 0? + if(m_pathNodes[startObj->m_nodeIndices[type][0]].group != + m_pathNodes[targetNodeId].group) { + *pNumNodes = 0; + if(pDist) *pDist = 100000.0f; + return; + } + }else{ + if(m_pathNodes[startNodeId].group != m_pathNodes[targetNodeId].group) { + *pNumNodes = 0; + if(pDist) *pDist = 100000.0f; + return; + } + } + + for(i = 0; i < ARRAY_SIZE(m_searchNodes); i++) + m_searchNodes[i].SetNext(nil); + AddNodeToList(&m_pathNodes[targetNodeId], 0); + int numNodesToBeCleared = 0; + apNodesToBeCleared[numNodesToBeCleared++] = &m_pathNodes[targetNodeId]; + + // Dijkstra's algorithm + // Find distances + int numPathsFound = 0; + if(startNodeId < 0 && m_mapObjects[m_pathNodes[targetNodeId].objectIndex] == startObj) + numPathsFound++; + for(i = 0; numPathsFound < numPathsToTry; i = (i+1) & 0x1FF){ + CPathNode *node; + for(node = m_searchNodes[i].GetNext(); node; node = node->GetNext()){ + if(m_mapObjects[node->objectIndex] == startObj && + (startNodeId < 0 || node == &m_pathNodes[startNodeId])) + numPathsFound++; + + for(j = 0; j < node->numLinks; j++){ + int next = ConnectedNode(node->firstLink + j); + int dist = node->distance + m_distances[node->firstLink + j]; + if(dist < m_pathNodes[next].distance){ + if(m_pathNodes[next].distance != MAX_DIST) + RemoveNodeFromList(&m_pathNodes[next]); + if(m_pathNodes[next].distance == MAX_DIST) + apNodesToBeCleared[numNodesToBeCleared++] = &m_pathNodes[next]; + AddNodeToList(&m_pathNodes[next], dist); + } + } + + RemoveNodeFromList(node); + } + } + + // Find out whence to start tracing back + CPathNode *curNode; + if(startNodeId < 0){ + int minDist = MAX_DIST; + *pNumNodes = 1; + for(i = 0; i < 12; i++){ + if(startObj->m_nodeIndices[type][i] < 0) + break; + int dist = (m_pathNodes[startObj->m_nodeIndices[type][i]].GetPosition() - start).Magnitude(); + if(m_pathNodes[startObj->m_nodeIndices[type][i]].distance + dist < minDist){ + minDist = m_pathNodes[startObj->m_nodeIndices[type][i]].distance + dist; + curNode = &m_pathNodes[startObj->m_nodeIndices[type][i]]; + } + } + if(maxNumNodes == 0){ + *pNumNodes = 0; + }else{ + nodes[0] = curNode; + *pNumNodes = 1; + } + if(pDist) + *pDist = minDist; + }else + { + curNode = &m_pathNodes[startNodeId]; + *pNumNodes = 0; + if(pDist) + *pDist = m_pathNodes[startNodeId].distance; + } + + // Trace back to target and update list of nodes + while(*pNumNodes < maxNumNodes && curNode != &m_pathNodes[targetNodeId]) + for(i = 0; i < curNode->numLinks; i++){ + int next = ConnectedNode(curNode->firstLink + i); + if(curNode->distance - m_distances[curNode->firstLink + i] == m_pathNodes[next].distance){ + curNode = &m_pathNodes[next]; + nodes[(*pNumNodes)++] = curNode; + i = 29030; // could have used a break... + } + } + + for(i = 0; i < numNodesToBeCleared; i++) + apNodesToBeCleared[i]->distance = MAX_DIST; + return; +} + +static CPathNode *pNodeList[32]; +static int16 DummyResult; +static int16 DummyResult2; + +bool +CPathFind::TestCoorsCloseness(CVector target, uint8 type, CVector start) +{ + float dist; + + if(type == PATH_CAR) + DoPathSearch(type, start, -1, target, pNodeList, &DummyResult, 32, nil, &dist, 999999.88f, -1); + else + DoPathSearch(type, start, -1, target, nil, &DummyResult2, 0, nil, &dist, 50.0f, -1); +#ifdef FIX_BUGS + // dist has GenerationDistMultiplier as a factor, so our reference dist should have it too + if(type == PATH_CAR) + return dist < 160.0f*TheCamera.GenerationDistMultiplier; + else + return dist < 100.0f*TheCamera.GenerationDistMultiplier; +#else + if(type == PATH_CAR) + return dist < 160.0f; + else + return dist < 100.0f; +#endif +} + +void +CPathFind::Save(uint8 *buf, uint32 *size) +{ + int i; + int n = m_numPathNodes/8 + 1; + + *size = 2*n; + + for(i = 0; i < m_numPathNodes; i++) + if(m_pathNodes[i].bDisabled) + buf[i/8] |= 1 << i%8; + else + buf[i/8] &= ~(1 << i%8); + + for(i = 0; i < m_numPathNodes; i++) + if(m_pathNodes[i].bBetweenLevels) + buf[i/8 + n] |= 1 << i%8; + else + buf[i/8 + n] &= ~(1 << i%8); +} + +void +CPathFind::Load(uint8 *buf, uint32 size) +{ + int i; + int n = m_numPathNodes/8 + 1; + + for(i = 0; i < m_numPathNodes; i++) + if(buf[i/8] & (1 << i%8)) + m_pathNodes[i].bDisabled = true; + else + m_pathNodes[i].bDisabled = false; + + for(i = 0; i < m_numPathNodes; i++) + if(buf[i/8 + n] & (1 << i%8)) + m_pathNodes[i].bBetweenLevels = true; + else + m_pathNodes[i].bBetweenLevels = false; +} + +void +CPathFind::DisplayPathData(void) +{ + // Not the function from mobile but my own! + + int i, j, k; + // Draw 50 units around camera + CVector pos = TheCamera.GetPosition(); + const float maxDist = 50.0f; + + // Render car path nodes + if(gbShowCarPaths) + for(i = 0; i < m_numCarPathNodes; i++){ + if((m_pathNodes[i].GetPosition() - pos).MagnitudeSqr() > SQR(maxDist)) + continue; + + CVector n1 = m_pathNodes[i].GetPosition(); + n1.z += 0.3f; + + // Draw node itself + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n1.x, n1.y, n1.z + 1.0f, + 0xFFFFFFFF, 0xFFFFFFFF); + + for(j = 0; j < m_pathNodes[i].numLinks; j++){ + k = ConnectedNode(m_pathNodes[i].firstLink + j); + CVector n2 = m_pathNodes[k].GetPosition(); + n2.z += 0.3f; + // Draw links to neighbours + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n2.x, n2.y, n2.z, + 0xFFFFFFFF, 0xFFFFFFFF); + } + } + + // Render car path nodes + if(gbShowCarPathsLinks) + for(i = 0; i < m_numCarPathLinks; i++){ + CVector2D n1_2d = m_carPathLinks[i].GetPosition(); + if((n1_2d - pos).MagnitudeSqr() > SQR(maxDist)) + continue; + + int ni = m_carPathLinks[i].pathNodeIndex; + CVector pn1 = m_pathNodes[ni].GetPosition(); + pn1.z += 0.3f; + CVector n1(n1_2d.x, n1_2d.y, pn1.z); + n1.z += 0.3f; + + // Draw car node itself + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n1.x, n1.y, n1.z + 1.0f, + 0xFFFFFFFF, 0xFFFFFFFF); + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z + 0.5f, + n1.x+m_carPathLinks[i].GetDirX(), n1.y+m_carPathLinks[i].GetDirY(), n1.z + 0.5f, + 0xFFFFFFFF, 0xFFFFFFFF); + + // Draw connection to car path node + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + pn1.x, pn1.y, pn1.z, + 0xFF0000FF, 0xFFFFFFFF); + + // traffic light type + uint32 col = 0xFF; + if((m_carPathLinks[i].trafficLightType&0x7F) == 1) + col += 0xFF000000; + if((m_carPathLinks[i].trafficLightType&0x7F) == 2) + col += 0x00FF0000; + if(m_carPathLinks[i].trafficLightType & 0x80) + col += 0x0000FF00; + CLines::RenderLineWithClipping(n1.x+0.2f, n1.y, n1.z, + n1.x+0.2f, n1.y, n1.z + 1.0f, + col, col); + + for(j = 0; j < m_pathNodes[ni].numLinks; j++){ + k = m_carPathConnections[m_pathNodes[ni].firstLink + j]; + CVector2D n2_2d = m_carPathLinks[k].GetPosition(); + int nk = m_carPathLinks[k].pathNodeIndex; + CVector pn2 = m_pathNodes[nk].GetPosition(); + pn2.z += 0.3f; + CVector n2(n2_2d.x, n2_2d.y, pn2.z); + n2.z += 0.3f; + + // Draw links to neighbours + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n2.x, n2.y, n2.z, + 0xFF00FFFF, 0xFF00FFFF); + } + } + + // Render ped path nodes + if(gbShowPedPaths) + for(i = m_numCarPathNodes; i < m_numPathNodes; i++){ + if((m_pathNodes[i].GetPosition() - pos).MagnitudeSqr() > SQR(maxDist)) + continue; + + CVector n1 = m_pathNodes[i].GetPosition(); + n1.z += 0.3f; + + // Draw node itself + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n1.x, n1.y, n1.z + 1.0f, + 0xFFFFFFFF, 0xFFFFFFFF); + + for(j = 0; j < m_pathNodes[i].numLinks; j++){ + k = ConnectedNode(m_pathNodes[i].firstLink + j); + CVector n2 = m_pathNodes[k].GetPosition(); + n2.z += 0.3f; + // Draw links to neighbours + CLines::RenderLineWithClipping(n1.x, n1.y, n1.z, + n2.x, n2.y, n2.z, + 0xFFFFFFFF, 0xFFFFFFFF); + + // Draw connection flags + CVector mid = (n1+n2)/2.0f; + uint32 col = 0xFF; + if(ConnectionCrossesRoad(m_pathNodes[i].firstLink + j)) + col += 0x00FF0000; + if(ConnectionHasTrafficLight(m_pathNodes[i].firstLink + j)) + col += 0xFF000000; + CLines::RenderLineWithClipping(mid.x, mid.y, mid.z, + mid.x, mid.y, mid.z + 1.0f, + col, col); + } + } +} diff --git a/src/control/PathFind.h b/src/control/PathFind.h new file mode 100644 index 0000000..bbfdf7b --- /dev/null +++ b/src/control/PathFind.h @@ -0,0 +1,232 @@ +#pragma once + +#include "Treadable.h" + +class CVehicle; +class CPtrList; + +enum +{ + NodeTypeExtern = 1, + NodeTypeIntern = 2, + + UseInRoadBlock = 1, + ObjectEastWest = 2, +}; + +enum +{ + PATH_CAR = 0, + PATH_PED = 1, +}; + +enum +{ + SWITCH_OFF = 0, + SWITCH_ON = 1, +}; + +enum +{ + ROUTE_ADD_BLOCKADE = 0, + ROUTE_NO_BLOCKADE = 1 +}; + +struct CPedPathNode +{ + bool bBlockade; + uint8 nodeIdX; + uint8 nodeIdY; + int16 id; + CPedPathNode* prev; + CPedPathNode* next; +}; + +VALIDATE_SIZE(CPedPathNode, 0x10); + +class CPedPath { +public: + static bool CalcPedRoute(int8 pathType, CVector position, CVector destination, CVector *pointPoses, int16 *pointsFound, int16 maxPoints); + static void AddNodeToPathList(CPedPathNode *pNodeToAdd, int16 id, CPedPathNode *pNodeList); + static void RemoveNodeFromList(CPedPathNode *pNode); + static void AddNodeToList(CPedPathNode *pNode, int16 index, CPedPathNode *pList); + static void AddBlockade(CEntity *pEntity, CPedPathNode(*pathNodes)[40], CVector *pPosition); + static void AddBlockadeSectorList(CPtrList& list, CPedPathNode(*pathNodes)[40], CVector *pPosition); +}; + +struct CPathNode +{ + CVector pos; + CPathNode *prev; + CPathNode *next; + int16 distance; // in path search + int16 objectIndex; + int16 firstLink; + uint8 numLinks; + + uint8 unkBits : 2; + uint8 bDeadEnd : 1; + uint8 bDisabled : 1; + uint8 bBetweenLevels : 1; + + int8 group; + + CVector &GetPosition(void) { return pos; } + void SetPosition(const CVector &p) { pos = p; } + float GetX(void) { return pos.x; } + float GetY(void) { return pos.y; } + float GetZ(void) { return pos.z; } + + CPathNode *GetPrev(void) { return prev; } + CPathNode *GetNext(void) { return next; } + void SetPrev(CPathNode *node) { prev = node; } + void SetNext(CPathNode *node) { next = node; } +}; + +union CConnectionFlags +{ + uint8 flags; + struct { + uint8 bCrossesRoad : 1; + uint8 bTrafficLight : 1; + }; +}; + +struct CCarPathLink +{ + CVector2D pos; + CVector2D dir; + int16 pathNodeIndex; + int8 numLeftLanes; + int8 numRightLanes; + uint8 trafficLightType; + + uint8 bBridgeLights : 1; + // more? + + CVector2D &GetPosition(void) { return pos; } + CVector2D &GetDirection(void) { return dir; } + float GetX(void) { return pos.x; } + float GetY(void) { return pos.y; } + float GetDirX(void) { return dir.x; } + float GetDirY(void) { return dir.y; } + + float OneWayLaneOffset() + { + if (numLeftLanes == 0) + return 0.5f - 0.5f * numRightLanes; + if (numRightLanes == 0) + return 0.5f - 0.5f * numLeftLanes; + return 0.5f; + } +}; + +// This is what we're reading from the files, only temporary +struct CPathInfoForObject +{ + int16 x; + int16 y; + int16 z; + int8 type; + int8 next; + int8 numLeftLanes; + int8 numRightLanes; + uint8 crossing : 1; +}; +extern CPathInfoForObject *InfoForTileCars; +extern CPathInfoForObject *InfoForTilePeds; + +struct CTempNode +{ + CVector pos; + float dirX; + float dirY; + int16 link1; + int16 link2; + int8 numLeftLanes; + int8 numRightLanes; + int8 linkState; +}; + +struct CTempDetachedNode // unused +{ + uint8 foo[20]; +}; + +class CPathFind +{ +public: + CPathNode m_pathNodes[NUM_PATHNODES]; + CCarPathLink m_carPathLinks[NUM_CARPATHLINKS]; + CTreadable *m_mapObjects[NUM_MAPOBJECTS]; + uint8 m_objectFlags[NUM_MAPOBJECTS]; + int16 m_connections[NUM_PATHCONNECTIONS]; + int16 m_distances[NUM_PATHCONNECTIONS]; + CConnectionFlags m_connectionFlags[NUM_PATHCONNECTIONS]; + int16 m_carPathConnections[NUM_PATHCONNECTIONS]; + + int32 m_numPathNodes; + int32 m_numCarPathNodes; + int32 m_numPedPathNodes; + int16 m_numMapObjects; + int16 m_numConnections; + int32 m_numCarPathLinks; + int32 unk; + uint8 m_numGroups[2]; + CPathNode m_searchNodes[512]; + + void Init(void); + void AllocatePathFindInfoMem(int16 numPathGroups); + void RegisterMapObject(CTreadable *mapObject); + void StoreNodeInfoPed(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, bool crossing); + void StoreNodeInfoCar(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, int16 width, int8 numLeft, int8 numRight); + void CalcNodeCoors(int16 x, int16 y, int16 z, int32 id, CVector *out); + bool LoadPathFindData(void); + void PreparePathData(void); + void CountFloodFillGroups(uint8 type); + void PreparePathDataForType(uint8 type, CTempNode *tempnodes, CPathInfoForObject *objectpathinfo, + float maxdist, CTempDetachedNode *detachednodes, int32 numDetached); + + bool IsPathObject(int id) { return id < PATHNODESIZE && (InfoForTileCars[id*12].type != 0 || InfoForTilePeds[id*12].type != 0); } + + float CalcRoadDensity(float x, float y); + bool TestForPedTrafficLight(CPathNode *n1, CPathNode *n2); + bool TestCrossesRoad(CPathNode *n1, CPathNode *n2); + void AddNodeToList(CPathNode *node, int32 listId); + void RemoveNodeFromList(CPathNode *node); + void RemoveBadStartNode(CVector pos, CPathNode **nodes, int16 *n); + void SetLinksBridgeLights(float, float, float, float, bool); + void SwitchOffNodeAndNeighbours(int32 nodeId, bool disable); + void SwitchRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable); + void SwitchPedRoadsOffInArea(float x1, float x2, float y1, float y2, float z1, float z2, bool disable); + void SwitchRoadsInAngledArea(float x1, float y1, float z1, float x2, float y2, float z2, float length, uint8 type, uint8 enable); + void MarkRoadsBetweenLevelsNodeAndNeighbours(int32 nodeId); + void MarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2); + void PedMarkRoadsBetweenLevelsInArea(float x1, float x2, float y1, float y2, float z1, float z2); + int32 FindNodeClosestToCoors(CVector coors, uint8 type, float distLimit, bool ignoreDisabled = false, bool ignoreBetweenLevels = false); + int32 FindNodeClosestToCoorsFavourDirection(CVector coors, uint8 type, float dirX, float dirY); + float FindNodeOrientationForCarPlacement(int32 nodeId); + float FindNodeOrientationForCarPlacementFacingDestination(int32 nodeId, float x, float y, bool towards); + bool NewGenerateCarCreationCoors(float x, float y, float dirX, float dirY, float spawnDist, float angleLimit, bool forward, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, bool ignoreDisabled = false); + bool GeneratePedCreationCoors(float x, float y, float minDist, float maxDist, float minDistOffScreen, float maxDistOffScreen, CVector *pPosition, int32 *pNode1, int32 *pNode2, float *pPositionBetweenNodes, CMatrix *camMatrix); + CTreadable *FindRoadObjectClosestToCoors(CVector coors, uint8 type); + void FindNextNodeWandering(uint8, CVector, CPathNode**, CPathNode**, uint8, uint8*); + void DoPathSearch(uint8 type, CVector start, int32 startNodeId, CVector target, CPathNode **nodes, int16 *numNodes, int16 maxNumNodes, CVehicle *vehicle, float *dist, float distLimit, int32 forcedTargetNode); + bool TestCoorsCloseness(CVector target, uint8 type, CVector start); + void Save(uint8 *buf, uint32 *size); + void Load(uint8 *buf, uint32 size); + uint16 ConnectedNode(int id) { return m_connections[id]; } + bool ConnectionCrossesRoad(int id) { return m_connectionFlags[id].bCrossesRoad; } + bool ConnectionHasTrafficLight(int id) { return m_connectionFlags[id].bTrafficLight; } + void ConnectionSetTrafficLight(int id) { m_connectionFlags[id].bTrafficLight = true; } + + void DisplayPathData(void); +}; + +VALIDATE_SIZE(CPathFind, 0x49bf4); + +extern CPathFind ThePaths; + +extern bool gbShowPedPaths; +extern bool gbShowCarPaths; +extern bool gbShowCarPathsLinks; diff --git a/src/control/Phones.cpp b/src/control/Phones.cpp new file mode 100644 index 0000000..7632cfa --- /dev/null +++ b/src/control/Phones.cpp @@ -0,0 +1,497 @@ +#include "common.h" + +#include "Phones.h" +#include "Pools.h" +#include "ModelIndices.h" +#include "Ped.h" +#include "Pad.h" +#include "Messages.h" +#include "Camera.h" +#include "World.h" +#include "General.h" +#include "AudioScriptObject.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "soundlist.h" +#include "SaveBuf.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif + +#ifdef COMPATIBLE_SAVES +#define PHONEINFO_SAVE_SIZE 0xA30 +#else +#define PHONEINFO_SAVE_SIZE sizeof(CPhoneInfo) +#endif + +CPhoneInfo gPhoneInfo; + +bool CPhoneInfo::bDisplayingPhoneMessage; // is phone picked up +uint32 CPhoneInfo::PhoneEnableControlsTimer; +CPhone *CPhoneInfo::pPhoneDisplayingMessages; +bool CPhoneInfo::bPickingUpPhone; +CPed *CPhoneInfo::pCallBackPed; // ped who picking up the phone (reset after pickup cb) + +/* + Entering phonebooth cutscene, showing messages and triggering these things + by checking coordinates happens in here - blue mission marker is cosmetic. + + Repeated message means after the script set the messages for a particular phone, + player can pick the phone again with the same messages appearing, + after 60 seconds of last phone pick-up. +*/ + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +CPed* crimeReporters[NUMPHONES] = {}; +bool +isPhoneAvailable(int m_phoneId) +{ + return crimeReporters[m_phoneId] == nil || !crimeReporters[m_phoneId]->IsPointerValid() || crimeReporters[m_phoneId]->m_objective > OBJECTIVE_WAIT_ON_FOOT || + (crimeReporters[m_phoneId]->m_nPedState != PED_MAKE_CALL && crimeReporters[m_phoneId]->m_nPedState != PED_FACE_PHONE && crimeReporters[m_phoneId]->m_nPedState != PED_SEEK_POS); +} +#endif + +void +CPhoneInfo::Update(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + CPlayerPed *player = FindPlayerPed(); + CPlayerInfo *playerInfo = &CWorld::Players[CWorld::PlayerInFocus]; + if (bDisplayingPhoneMessage && CTimer::GetTimeInMilliseconds() > PhoneEnableControlsTimer) { + playerInfo->MakePlayerSafe(false); + TheCamera.SetWideScreenOff(); + pPhoneDisplayingMessages = nil; + bDisplayingPhoneMessage = false; + CAnimBlendAssociation *talkAssoc = RpAnimBlendClumpGetAssociation(player->GetClump(), ANIM_STD_PHONE_TALK); + if (talkAssoc && talkAssoc->blendAmount > 0.5f) { + CAnimBlendAssociation *endAssoc = CAnimManager::BlendAnimation(player->GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_OUT, 8.0f); + endAssoc->flags &= ~ASSOC_DELETEFADEDOUT; + endAssoc->SetFinishCallback(PhonePutDownCB, player); + } else { + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_PHONE); + if (player->m_nPedState == PED_MAKE_CALL) + player->SetPedState(PED_IDLE); + } + } + bool notInCar; + CVector playerPos; + if (FindPlayerVehicle()) { + notInCar = false; + playerPos = FindPlayerVehicle()->GetPosition(); + } else { + notInCar = true; + playerPos = player->GetPosition(); + } + bool phoneRings = false; + bool scratchTheCabinet; + for(int phoneId = 0; phoneId < m_nScriptPhonesMax; phoneId++) { + if (m_aPhones[phoneId].m_visibleToCam) { + switch (m_aPhones[phoneId].m_nState) { + case PHONE_STATE_ONETIME_MESSAGE_SET: + case PHONE_STATE_REPEATED_MESSAGE_SET: + case PHONE_STATE_REPEATED_MESSAGE_SHOWN_ONCE: + if (bPickingUpPhone) { + scratchTheCabinet = false; + phoneRings = false; + } else { + scratchTheCabinet = (CTimer::GetTimeInMilliseconds() / 1880) % 2 == 1; + phoneRings = (CTimer::GetPreviousTimeInMilliseconds() / 1880) % 2 == 1; + } + if (scratchTheCabinet) { + m_aPhones[phoneId].m_pEntity->GetUp().z = (CGeneral::GetRandomNumber() % 1024) / 16000.0f + 1.0f; + if (!phoneRings) + PlayOneShotScriptObject(SCRIPT_SOUND_PAYPHONE_RINGING, m_aPhones[phoneId].m_pEntity->GetPosition()); + } else { + m_aPhones[phoneId].m_pEntity->GetUp().z = 1.0f; + } + m_aPhones[phoneId].m_pEntity->GetMatrix().UpdateRW(); + m_aPhones[phoneId].m_pEntity->UpdateRwFrame(); + if (notInCar && !bPickingUpPhone && player->IsPedInControl()) { + CVector2D distToPhone = playerPos - m_aPhones[phoneId].m_vecPos; + if (Abs(distToPhone.x) < 1.0f && Abs(distToPhone.y) < 1.0f) { + if (DotProduct2D(distToPhone, m_aPhones[phoneId].m_pEntity->GetForward()) / distToPhone.Magnitude() < -0.85f) { + CVector2D distToPhoneObj = playerPos - m_aPhones[phoneId].m_pEntity->GetPosition(); + float angleToFace = CGeneral::GetATanOfXY(distToPhoneObj.x, distToPhoneObj.y) + HALFPI; + if (angleToFace > TWOPI) + angleToFace = angleToFace - TWOPI; + player->m_fRotationCur = angleToFace; + player->m_fRotationDest = angleToFace; + player->SetHeading(angleToFace); + player->SetPedState(PED_MAKE_CALL); + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_PHONE); + TheCamera.SetWideScreenOn(); + playerInfo->MakePlayerSafe(true); + CAnimBlendAssociation *phonePickAssoc = CAnimManager::BlendAnimation(player->GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_IN, 4.0f); + phonePickAssoc->SetFinishCallback(PhonePickUpCB, &m_aPhones[phoneId]); + bPickingUpPhone = true; + pCallBackPed = player; + } + } + } + break; + case PHONE_STATE_REPEATED_MESSAGE_STARTED: + if (CTimer::GetTimeInMilliseconds() - m_aPhones[phoneId].m_repeatedMessagePickupStart > 60000) + m_aPhones[phoneId].m_nState = PHONE_STATE_REPEATED_MESSAGE_SHOWN_ONCE; + break; + case PHONE_STATE_9: + scratchTheCabinet = (CTimer::GetTimeInMilliseconds() / 1880) % 2 == 1; + phoneRings = (CTimer::GetPreviousTimeInMilliseconds() / 1880) % 2 == 1; + if (scratchTheCabinet) { + m_aPhones[phoneId].m_pEntity->GetUp().z = (CGeneral::GetRandomNumber() % 1024) / 16000.0f + 1.0f; + if (!phoneRings) + PlayOneShotScriptObject(SCRIPT_SOUND_PAYPHONE_RINGING, m_aPhones[phoneId].m_pEntity->GetPosition()); + } else { + m_aPhones[phoneId].m_pEntity->GetUp().z = 1.0f; + } + m_aPhones[phoneId].m_pEntity->GetMatrix().UpdateRW(); + m_aPhones[phoneId].m_pEntity->UpdateRwFrame(); + break; + default: + break; + } + if (CVector2D(TheCamera.GetPosition() - m_aPhones[phoneId].m_vecPos).MagnitudeSqr() > sq(100.0f)) + m_aPhones[phoneId].m_visibleToCam = false; + } else if (!((CTimer::GetFrameCounter() + m_aPhones[phoneId].m_pEntity->m_randomSeed) % 16)) { + if (CVector2D(TheCamera.GetPosition() - m_aPhones[phoneId].m_vecPos).MagnitudeSqr() < sq(60.0f)) + m_aPhones[phoneId].m_visibleToCam = true; + } + } +} + +int +CPhoneInfo::FindNearestFreePhone(CVector *pos) +{ + int nearestPhoneId = -1; + float nearestPhoneDist = 60.0f; + + for (int phoneId = 0; phoneId < m_nMax; phoneId++) { + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (isPhoneAvailable(phoneId)) +#else + if (gPhoneInfo.m_aPhones[phoneId].m_nState == PHONE_STATE_FREE) +#endif + { + float phoneDist = (m_aPhones[phoneId].m_vecPos - *pos).Magnitude2D(); + + if (phoneDist < nearestPhoneDist) { + nearestPhoneDist = phoneDist; + nearestPhoneId = phoneId; + } + } + } + return nearestPhoneId; +} + +bool +CPhoneInfo::PhoneAtThisPosition(CVector pos) +{ + for (int phoneId = 0; phoneId < m_nMax; phoneId++) { + if (pos.x == m_aPhones[phoneId].m_vecPos.x && pos.y == m_aPhones[phoneId].m_vecPos.y) + return true; + } + return false; +} + +bool +CPhoneInfo::HasMessageBeenDisplayed(int phoneId) +{ + if (bDisplayingPhoneMessage) + return false; + + int state = m_aPhones[phoneId].m_nState; + + return state == PHONE_STATE_REPEATED_MESSAGE_SHOWN_ONCE || + state == PHONE_STATE_ONETIME_MESSAGE_STARTED || + state == PHONE_STATE_REPEATED_MESSAGE_STARTED; +} + +bool +CPhoneInfo::IsMessageBeingDisplayed(int phoneId) +{ + return pPhoneDisplayingMessages == &m_aPhones[phoneId]; +} + +#ifdef COMPATIBLE_SAVES +static inline void +LoadPhone(CPhone &phone, uint8 *&buf) +{ + ReadSaveBuf(&phone.m_vecPos, buf); + SkipSaveBuf(buf, 6 * 4); + ReadSaveBuf(&phone.m_repeatedMessagePickupStart, buf); + uint32 tmp; + ReadSaveBuf(&tmp, buf); + phone.m_pEntity = (CEntity*)(uintptr)tmp; + ReadSaveBuf(&phone.m_nState, buf); + ReadSaveBuf(&phone.m_visibleToCam, buf); + SkipSaveBuf(buf, 3); +} +#endif + +void +CPhoneInfo::Load(uint8 *buf, uint32 size) +{ +INITSAVEBUF + int32 max, scriptPhonesMax; + ReadSaveBuf(&max, buf); + ReadSaveBuf(&scriptPhonesMax, buf); + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + m_nMax = Min(NUMPHONES, max); + m_nScriptPhonesMax = 0; + + bool ignoreOtherPhones = false; + + // We can do it without touching saves. We'll only load script phones, others are already loaded in Initialise + for (int i = 0; i < 50; i++) { + CPhone phoneToLoad; +#ifdef COMPATIBLE_SAVES + phoneToLoad.m_apMessages[0]=phoneToLoad.m_apMessages[1]=phoneToLoad.m_apMessages[2]=phoneToLoad.m_apMessages[3]=phoneToLoad.m_apMessages[4]=phoneToLoad.m_apMessages[5] = nil; + LoadPhone(phoneToLoad, buf); +#else + ReadSaveBuf(&phoneToLoad, buf); +#endif + + if (ignoreOtherPhones) + continue; + + if (i < scriptPhonesMax) { + if (i >= m_nMax) { + assert(0 && "Number of phones used by script exceeds the NUMPHONES or the stored phones in save file. Ignoring some phones"); + ignoreOtherPhones = true; + continue; + } + SwapPhone(phoneToLoad.m_vecPos.x, phoneToLoad.m_vecPos.y, i); + + m_aPhones[i] = phoneToLoad; + // It's saved as building pool index in save file, convert it to true entity + if (m_aPhones[i].m_pEntity) { + m_aPhones[i].m_pEntity = CPools::GetBuildingPool()->GetSlot((uintptr)m_aPhones[i].m_pEntity - 1); + } + } else + ignoreOtherPhones = true; + } +#else + m_nMax = max; + m_nScriptPhonesMax = scriptPhonesMax; + + for (int i = 0; i < NUMPHONES; i++) { +#ifdef COMPATIBLE_SAVES + LoadPhone(m_aPhones[i], buf); +#else + ReadSaveBuf(&m_aPhones[i], buf); +#endif + // It's saved as building pool index in save file, convert it to true entity + if (m_aPhones[i].m_pEntity) { + m_aPhones[i].m_pEntity = CPools::GetBuildingPool()->GetSlot((uintptr)m_aPhones[i].m_pEntity - 1); + } + } +#endif +VALIDATESAVEBUF(size) +} + +void +CPhoneInfo::SetPhoneMessage_JustOnce(int phoneId, wchar *msg1, wchar *msg2, wchar *msg3, wchar *msg4, wchar *msg5, wchar *msg6) +{ + // If there is at least one message, it should be msg1. + if (msg1) { + m_aPhones[phoneId].m_apMessages[0] = msg1; + m_aPhones[phoneId].m_apMessages[1] = msg2; + m_aPhones[phoneId].m_apMessages[2] = msg3; + m_aPhones[phoneId].m_apMessages[3] = msg4; + m_aPhones[phoneId].m_apMessages[4] = msg5; + m_aPhones[phoneId].m_apMessages[5] = msg6; + m_aPhones[phoneId].m_nState = PHONE_STATE_ONETIME_MESSAGE_SET; + } else { + m_aPhones[phoneId].m_nState = PHONE_STATE_MESSAGE_REMOVED; + } +} + +void +CPhoneInfo::SetPhoneMessage_Repeatedly(int phoneId, wchar *msg1, wchar *msg2, wchar *msg3, wchar *msg4, wchar *msg5, wchar *msg6) +{ + // If there is at least one message, it should be msg1. + if (msg1) { + m_aPhones[phoneId].m_apMessages[0] = msg1; + m_aPhones[phoneId].m_apMessages[1] = msg2; + m_aPhones[phoneId].m_apMessages[2] = msg3; + m_aPhones[phoneId].m_apMessages[3] = msg4; + m_aPhones[phoneId].m_apMessages[4] = msg5; + m_aPhones[phoneId].m_apMessages[5] = msg6; + m_aPhones[phoneId].m_nState = PHONE_STATE_REPEATED_MESSAGE_SET; + } else { + m_aPhones[phoneId].m_nState = PHONE_STATE_MESSAGE_REMOVED; + } +} + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +void +CPhoneInfo::SwapPhone(float xPos, float yPos, int into) +{ + // "into" should be in 0 - m_nScriptPhonesMax range + int nearestPhoneId = -1; + CVector pos(xPos, yPos, 0.0f); + float nearestPhoneDist = 1.0f; + + for (int phoneId = m_nScriptPhonesMax; phoneId < m_nMax; phoneId++) { + float phoneDistance = (m_aPhones[phoneId].m_vecPos - pos).Magnitude2D(); + if (phoneDistance < nearestPhoneDist) { + nearestPhoneDist = phoneDistance; + nearestPhoneId = phoneId; + } + } + m_aPhones[nearestPhoneId].m_nState = PHONE_STATE_MESSAGE_REMOVED; + + CPhone oldPhone = m_aPhones[into]; + m_aPhones[into] = m_aPhones[nearestPhoneId]; + m_aPhones[nearestPhoneId] = oldPhone; + m_nScriptPhonesMax++; +} +#endif + +int +CPhoneInfo::GrabPhone(float xPos, float yPos) +{ + // "Grab" doesn't mean picking up the phone, it means allocating some particular phone to + // whoever called the 024A opcode first with the position parameters closest to phone. + // Same phone won't be available on next run of this function. + + int nearestPhoneId = -1; + CVector pos(xPos, yPos, 0.0f); + float nearestPhoneDist = 100.0f; + + for (int phoneId = m_nScriptPhonesMax; phoneId < m_nMax; phoneId++) { + float phoneDistance = (m_aPhones[phoneId].m_vecPos - pos).Magnitude2D(); + if (phoneDistance < nearestPhoneDist) { + nearestPhoneDist = phoneDistance; + nearestPhoneId = phoneId; + } + } + m_aPhones[nearestPhoneId].m_nState = PHONE_STATE_MESSAGE_REMOVED; + + CPhone oldFirstPhone = m_aPhones[m_nScriptPhonesMax]; + m_aPhones[m_nScriptPhonesMax] = m_aPhones[nearestPhoneId]; + m_aPhones[nearestPhoneId] = oldFirstPhone; + m_nScriptPhonesMax++; + return m_nScriptPhonesMax - 1; +} + +void +CPhoneInfo::Initialise(void) +{ + CBuildingPool *pool = CPools::GetBuildingPool(); + pCallBackPed = nil; + bDisplayingPhoneMessage = false; + bPickingUpPhone = false; + pPhoneDisplayingMessages = nil; + m_nMax = 0; + m_nScriptPhonesMax = 0; + for (int i = pool->GetSize() - 1; i >= 0; i--) { + CBuilding *building = pool->GetSlot(i); + if (building) { + if (building->GetModelIndex() == MI_PHONEBOOTH1) { + assert(m_nMax < ARRAY_SIZE(m_aPhones) && "NUMPHONES should be increased"); + CPhone *maxPhone = &m_aPhones[m_nMax]; + maxPhone->m_nState = PHONE_STATE_FREE; + maxPhone->m_vecPos = building->GetPosition(); + maxPhone->m_pEntity = building; + m_nMax++; + } + } + } +} + +void +CPhoneInfo::Save(uint8 *buf, uint32 *size) +{ + *size = PHONEINFO_SAVE_SIZE; +INITSAVEBUF + WriteSaveBuf(buf, m_nMax); + WriteSaveBuf(buf, m_nScriptPhonesMax); +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + for (int phoneId = 0; phoneId < 50; phoneId++) { // We can do it without touching saves +#else + for (int phoneId = 0; phoneId < NUMPHONES; phoneId++) { +#endif +#ifdef COMPATIBLE_SAVES + WriteSaveBuf(buf, m_aPhones[phoneId].m_vecPos); + ZeroSaveBuf(buf, 6 * 4); + WriteSaveBuf(buf, m_aPhones[phoneId].m_repeatedMessagePickupStart); + // Convert entity pointer to building pool index while saving + int32 tmp = m_aPhones[phoneId].m_pEntity ? CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert((CBuilding*)m_aPhones[phoneId].m_pEntity) + 1 : 0; + WriteSaveBuf(buf, tmp); + WriteSaveBuf(buf, m_aPhones[phoneId].m_nState); + WriteSaveBuf(buf, m_aPhones[phoneId].m_visibleToCam); + ZeroSaveBuf(buf, 3); +#else + CPhone* phone = WriteSaveBuf(buf, m_aPhones[phoneId]); + + // Convert entity pointer to building pool index while saving + if (phone->m_pEntity) { + phone->m_pEntity = (CEntity*) (CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert((CBuilding*)phone->m_pEntity) + 1); + } +#endif + } +VALIDATESAVEBUF(*size) +} + +void +CPhoneInfo::Shutdown(void) +{ + m_nMax = 0; + m_nScriptPhonesMax = 0; +} + +void +PhonePutDownCB(CAnimBlendAssociation *assoc, void *arg) +{ + assoc->flags |= ASSOC_DELETEFADEDOUT; + assoc->blendDelta = -1000.0f; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_PHONE); + CPed *ped = (CPed*)arg; + + if (assoc->blendAmount > 0.5f) + ped->bUpdateAnimHeading = true; + + if (ped->m_nPedState == PED_MAKE_CALL) + ped->SetPedState(PED_IDLE); +} + +void +PhonePickUpCB(CAnimBlendAssociation *assoc, void *arg) +{ + CPhone *phone = (CPhone*)arg; + int messagesDisplayTime = 0; + + for(int i=0; i < 6; i++) { + wchar *msg = phone->m_apMessages[i]; + if (msg) { + CMessages::AddMessage(msg, 3000, 0); + messagesDisplayTime += 3000; + } + } + + CPhoneInfo::bPickingUpPhone = false; + CPhoneInfo::bDisplayingPhoneMessage = true; + CPhoneInfo::pPhoneDisplayingMessages = phone; + CPhoneInfo::PhoneEnableControlsTimer = CTimer::GetTimeInMilliseconds() + messagesDisplayTime; + + if (phone->m_nState == PHONE_STATE_ONETIME_MESSAGE_SET) { + phone->m_nState = PHONE_STATE_ONETIME_MESSAGE_STARTED; + } else { + phone->m_nState = PHONE_STATE_REPEATED_MESSAGE_STARTED; + phone->m_repeatedMessagePickupStart = CTimer::GetTimeInMilliseconds(); + } + + CPed *ped = CPhoneInfo::pCallBackPed; + ped->m_nMoveState = PEDMOVE_STILL; + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE, 8.0f); + + if (assoc->blendAmount > 0.5f && ped) + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_TALK, 8.0f); + + CPhoneInfo::pCallBackPed = nil; +} diff --git a/src/control/Phones.h b/src/control/Phones.h new file mode 100644 index 0000000..02c9a92 --- /dev/null +++ b/src/control/Phones.h @@ -0,0 +1,77 @@ +#pragma once + +#include "Physical.h" + +class CPed; +class CAnimBlendAssociation; + +enum PhoneState { + PHONE_STATE_FREE, + PHONE_STATE_REPORTING_CRIME, // CCivilianPed::ProcessControl sets it but unused + PHONE_STATE_2, + PHONE_STATE_MESSAGE_REMOVED, + PHONE_STATE_ONETIME_MESSAGE_SET, + PHONE_STATE_REPEATED_MESSAGE_SET, + PHONE_STATE_REPEATED_MESSAGE_SHOWN_ONCE, + PHONE_STATE_ONETIME_MESSAGE_STARTED, + PHONE_STATE_REPEATED_MESSAGE_STARTED, + PHONE_STATE_9 // just rings, picking being handled via script. most of the time game uses this +}; + +class CPhone +{ +public: + CVector m_vecPos; + wchar *m_apMessages[6]; + uint32 m_repeatedMessagePickupStart; + CEntity *m_pEntity; // stored as building pool index in save files + PhoneState m_nState; + bool m_visibleToCam; + + CPhone() { } + ~CPhone() { } +}; + +VALIDATE_SIZE(CPhone, 0x34); + +class CPhoneInfo { +public: + static bool bDisplayingPhoneMessage; + static uint32 PhoneEnableControlsTimer; + static CPhone *pPhoneDisplayingMessages; + static bool bPickingUpPhone; + static CPed *pCallBackPed; + + int32 m_nMax; + int32 m_nScriptPhonesMax; + CPhone m_aPhones[NUMPHONES]; + + CPhoneInfo() { } + ~CPhoneInfo() { } + + int FindNearestFreePhone(CVector*); + bool PhoneAtThisPosition(CVector); + bool HasMessageBeenDisplayed(int); + bool IsMessageBeingDisplayed(int); + void Load(uint8 *buf, uint32 size); + void Save(uint8 *buf, uint32 *size); + void SetPhoneMessage_JustOnce(int phoneId, wchar *msg1, wchar *msg2, wchar *msg3, wchar *msg4, wchar *msg5, wchar *msg6); + void SetPhoneMessage_Repeatedly(int phoneId, wchar *msg1, wchar *msg2, wchar *msg3, wchar *msg4, wchar *msg5, wchar *msg6); + int GrabPhone(float, float); + void Initialise(void); + void Shutdown(void); + void Update(void); +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + void SwapPhone(float xPos, float yPos, int into); +#endif +}; + +extern CPhoneInfo gPhoneInfo; + +void PhonePutDownCB(CAnimBlendAssociation *assoc, void *arg); +void PhonePickUpCB(CAnimBlendAssociation *assoc, void *arg); + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +extern CPed *crimeReporters[NUMPHONES]; +bool isPhoneAvailable(int); +#endif diff --git a/src/control/Pickups.cpp b/src/control/Pickups.cpp new file mode 100644 index 0000000..8d3472e --- /dev/null +++ b/src/control/Pickups.cpp @@ -0,0 +1,1568 @@ +#include "common.h" + +#include "main.h" + +#include "Camera.h" +#include "Coronas.h" +#include "Darkel.h" +#include "Entity.h" +#include "Explosion.h" +#include "Font.h" +#include "Garages.h" +#include "General.h" +#include "ModelIndices.h" +#include "Object.h" +#include "Pad.h" +#include "Pickups.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "Fire.h" +#include "PointLights.h" +#include "Pools.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif +#include "SaveBuf.h" +#include "Script.h" +#include "Shadows.h" +#include "SpecialFX.h" +#include "Sprite.h" +#include "Timer.h" +#include "WaterLevel.h" +#include "World.h" + +#ifdef COMPATIBLE_SAVES +#define PICKUPS_SAVE_SIZE 0x24C0 +#else +#define PICKUPS_SAVE_SIZE sizeof(aPickUps) +#endif + +CPickup CPickups::aPickUps[NUMPICKUPS]; +int16 CPickups::NumMessages; +int32 CPickups::aPickUpsCollected[NUMCOLLECTEDPICKUPS]; +int16 CPickups::CollectedPickUpIndex; + +// unused +bool CPickups::bPickUpcamActivated; +CVehicle *CPickups::pPlayerVehicle; +CVector CPickups::StaticCamCoors; +uint32 CPickups::StaticCamStartTime; + +tPickupMessage CPickups::aMessages[NUMPICKUPMESSAGES]; + +// 20 ?! Some Miami leftover? (Originally at 0x5ED8D4) +uint16 AmmoForWeapon[20] = { 0, 1, 45, 125, 25, 150, 300, 25, 5, 250, 5, 5, 0, 500, 0, 100, 0, 0, 0, 0 }; +uint16 AmmoForWeapon_OnStreet[20] = { 0, 1, 9, 25, 5, 30, 60, 5, 1, 50, 1, 1, 0, 200, 0, 100, 0, 0, 0, 0 }; +uint16 CostOfWeapon[20] = { 0, 10, 250, 800, 1500, 3000, 5000, 10000, 25000, 25000, 2000, 2000, 0, 50000, 0, 3000, 0, 0, 0, 0 }; + +uint8 aWeaponReds[] = { 255, 0, 128, 255, 255, 0, 255, 0, 128, 128, 255, 255, 128, 0, 255, 0 }; +uint8 aWeaponGreens[] = { 0, 255, 128, 255, 0, 255, 128, 255, 0, 255, 255, 0, 255, 0, 255, 0 }; +uint8 aWeaponBlues[] = { 0, 0, 255, 0, 255, 255, 0, 128, 255, 0, 255, 0, 128, 255, 0, 0 }; +float aWeaponScale[] = { 1.0f, 2.0f, 1.5f, 1.0f, 1.0f, 1.5f, 1.0f, 2.0f, 1.0f, 2.0f, 2.5f, 1.0f, 1.0f, 1.0f, 1.0f }; + + +inline void +CPickup::Remove() +{ + CWorld::Remove(m_pObject); + delete m_pObject; + + m_bRemoved = true; + m_pObject = nil; + m_eType = PICKUP_NONE; +} + +CObject * +CPickup::GiveUsAPickUpObject(int32 handle) +{ + CObject *object; + + if (handle >= 0) { + CPools::MakeSureSlotInObjectPoolIsEmpty(handle); + object = new (handle) CObject(m_eModelIndex, false); + } else + object = new CObject(m_eModelIndex, false); + + if (object == nil) return nil; + object->ObjectCreatedBy = MISSION_OBJECT; + object->SetPosition(m_vecPos); + object->SetOrientation(0.0f, 0.0f, -HALFPI); + object->GetMatrix().UpdateRW(); + object->UpdateRwFrame(); + + object->bAffectedByGravity = false; + object->bExplosionProof = true; + object->bUsesCollision = false; + object->bIsPickup = true; + + object->m_nBonusValue = m_eModelIndex == MI_PICKUP_BONUS ? m_nQuantity : 0; + + switch (m_eType) + { + case PICKUP_IN_SHOP: + object->bPickupObjWithMessage = true; + object->bOutOfStock = false; + break; + case PICKUP_ON_STREET: + case PICKUP_ONCE: + case PICKUP_ONCE_TIMEOUT: + case PICKUP_COLLECTABLE1: + case PICKUP_MONEY: + case PICKUP_MINE_INACTIVE: + case PICKUP_MINE_ARMED: + case PICKUP_NAUTICAL_MINE_INACTIVE: + case PICKUP_NAUTICAL_MINE_ARMED: + case PICKUP_FLOATINGPACKAGE: + case PICKUP_ON_STREET_SLOW: + object->bPickupObjWithMessage = false; + object->bOutOfStock = false; + break; + case PICKUP_IN_SHOP_OUT_OF_STOCK: + object->bPickupObjWithMessage = false; + object->bOutOfStock = true; + object->bRenderScorched = true; + break; + case PICKUP_FLOATINGPACKAGE_FLOATING: + default: + break; + } + return object; +} + +bool +CPickup::CanBePickedUp(CPlayerPed *player) +{ + bool cannotBePickedUp = + (m_pObject->GetModelIndex() == MI_PICKUP_BODYARMOUR && player->m_fArmour > 99.5f) + || (m_pObject->GetModelIndex() == MI_PICKUP_HEALTH && player->m_fHealth > 99.5f) + || (m_pObject->GetModelIndex() == MI_PICKUP_BRIBE && player->m_pWanted->GetWantedLevel() == 0) + || (m_pObject->GetModelIndex() == MI_PICKUP_KILLFRENZY && (CTheScripts::IsPlayerOnAMission() || CDarkel::FrenzyOnGoing() || !CGame::nastyGame)); + return !cannotBePickedUp; +} + +bool +CPickup::Update(CPlayerPed *player, CVehicle *vehicle, int playerId) +{ + float waterLevel; + bool result = false; + + if (m_bRemoved) { + if (CTimer::GetTimeInMilliseconds() > m_nTimer) { + // respawn pickup if we're far enough + float dist = (FindPlayerCoors().x - m_vecPos.x) * (FindPlayerCoors().x - m_vecPos.x) + (FindPlayerCoors().y - m_vecPos.y) * (FindPlayerCoors().y - m_vecPos.y); + if (dist > 100.0f || m_eType == PICKUP_IN_SHOP && dist > 2.4f) { + m_pObject = GiveUsAPickUpObject(-1); + if (m_pObject) { + CWorld::Add(m_pObject); + m_bRemoved = false; + } + } + } + return false; + } + + if (!m_pObject) return false; + + if (!IsMine()) { + // let's check if we touched the pickup + bool isPickupTouched = false; + if (m_pObject->GetModelIndex() == MI_PICKUP_BRIBE) { + if (vehicle != nil) { + if (vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) + isPickupTouched = true; + } + else { + if (Abs(player->GetPosition().z - m_pObject->GetPosition().z) < 2.0f) { + if ((player->GetPosition().x - m_pObject->GetPosition().x) * (player->GetPosition().x - m_pObject->GetPosition().x) + + (player->GetPosition().y - m_pObject->GetPosition().y) * (player->GetPosition().y - m_pObject->GetPosition().y) < 1.8f) + isPickupTouched = true; + } + } + } else if (m_pObject->GetModelIndex() == MI_PICKUP_CAMERA) { + if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) { + isPickupTouched = true; + } + } else if (vehicle == nil) { + if (Abs(player->GetPosition().z - m_pObject->GetPosition().z) < 2.0f) { + if ((player->GetPosition().x - m_pObject->GetPosition().x) * (player->GetPosition().x - m_pObject->GetPosition().x) + + (player->GetPosition().y - m_pObject->GetPosition().y) * (player->GetPosition().y - m_pObject->GetPosition().y) < 1.8f) + isPickupTouched = true; + } + } + + // if we didn't then we've got nothing to do + if (isPickupTouched && CanBePickedUp(player)) { + CPad::GetPad(0)->StartShake(120, 100); + switch (m_eType) + { + case PICKUP_IN_SHOP: + if (CWorld::Players[playerId].m_nMoney < CostOfWeapon[CPickups::WeaponForModel(m_pObject->GetModelIndex())]) { + CGarages::TriggerMessage("PU_MONY", -1, 6000, -1); + } else { + CWorld::Players[playerId].m_nMoney -= CostOfWeapon[CPickups::WeaponForModel(m_pObject->GetModelIndex())]; + if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) { + player->GiveWeapon(CPickups::WeaponForModel(m_pObject->GetModelIndex()), AmmoForWeapon[CPickups::WeaponForModel(m_pObject->GetModelIndex())]); + player->m_nSelectedWepSlot = player->GetWeaponSlot(CPickups::WeaponForModel(m_pObject->GetModelIndex())); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON_BOUGHT, m_pObject->GetModelIndex() - MI_GRENADE); + } + result = true; + CWorld::Remove(m_pObject); + delete m_pObject; + m_pObject = nil; + m_nTimer = CTimer::GetTimeInMilliseconds() + 5000; + m_bRemoved = true; + } + break; + case PICKUP_ON_STREET: + case PICKUP_ON_STREET_SLOW: + if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) { + if (CPickups::WeaponForModel(m_pObject->GetModelIndex())) { + player->GiveWeapon(CPickups::WeaponForModel(m_pObject->GetModelIndex()), m_nQuantity != 0 ? m_nQuantity : AmmoForWeapon_OnStreet[CPickups::WeaponForModel(m_pObject->GetModelIndex())]); + if (player->m_nSelectedWepSlot == player->GetWeaponSlot(WEAPONTYPE_UNARMED)) { + player->m_nSelectedWepSlot = player->GetWeaponSlot(CPickups::WeaponForModel(m_pObject->GetModelIndex())); + } + DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON, m_pObject->GetModelIndex() - MI_GRENADE); + } else if (m_pObject->GetModelIndex() == MI_PICKUP_CAMERA && vehicle != nil) { + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + CPickups::bPickUpcamActivated = true; + CPickups::pPlayerVehicle = FindPlayerVehicle(); + CPickups::StaticCamCoors = m_pObject->GetPosition(); + CPickups::StaticCamStartTime = CTimer::GetTimeInMilliseconds(); + } + } + if (m_eType == PICKUP_ON_STREET) { + m_nTimer = CTimer::GetTimeInMilliseconds() + 30000; + } else if (m_eType == PICKUP_ON_STREET_SLOW) { + if (MI_PICKUP_BRIBE == m_pObject->GetModelIndex()) + m_nTimer = CTimer::GetTimeInMilliseconds() + 300000; + else + m_nTimer = CTimer::GetTimeInMilliseconds() + 720000; + } + + result = true; + CWorld::Remove(m_pObject); + delete m_pObject; + m_pObject = nil; + m_bRemoved = true; + break; + case PICKUP_ONCE: + case PICKUP_ONCE_TIMEOUT: + if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) { + if (CPickups::WeaponForModel(m_pObject->GetModelIndex())) { + player->GiveWeapon(CPickups::WeaponForModel(m_pObject->GetModelIndex()), m_nQuantity != 0 ? m_nQuantity : AmmoForWeapon[CPickups::WeaponForModel(m_pObject->GetModelIndex())]); + if (player->m_nSelectedWepSlot == player->GetWeaponSlot(WEAPONTYPE_UNARMED)) + player->m_nSelectedWepSlot = player->GetWeaponSlot(CPickups::WeaponForModel(m_pObject->GetModelIndex())); + } + DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON, m_pObject->GetModelIndex() - MI_GRENADE); + } + result = true; + Remove(); + break; + case PICKUP_COLLECTABLE1: + CWorld::Players[playerId].m_nCollectedPackages++; + CWorld::Players[playerId].m_nMoney += 1000; + + if (CWorld::Players[playerId].m_nCollectedPackages == CWorld::Players[playerId].m_nTotalPackages) { + printf("All collectables have been picked up\n"); + CGarages::TriggerMessage("CO_ALL", -1, 5000, -1); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 1000000; + } else + CGarages::TriggerMessage("CO_ONE", CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages, 5000, CWorld::Players[CWorld::PlayerInFocus].m_nTotalPackages); + + result = true; + Remove(); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_HIDDEN_PACKAGE, 0); + break; + case PICKUP_MONEY: + CWorld::Players[playerId].m_nMoney += m_nQuantity; + sprintf(gString, "$%d", m_nQuantity); +#ifdef MONEY_MESSAGES + CMoneyMessages::RegisterOne(m_vecPos + CVector(0.0f, 0.0f, 1.0f), gString, 0, 255, 0, 0.5f, 0.5f); +#endif + result = true; + Remove(); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_MONEY, 0); + break; + default: + break; + } + } + } else { + switch (m_eType) + { + case PICKUP_MINE_INACTIVE: + if (vehicle != nil && !vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) { + m_eType = PICKUP_MINE_ARMED; + m_nTimer = CTimer::GetTimeInMilliseconds() + 10000; + } + break; + case PICKUP_NAUTICAL_MINE_INACTIVE: + { + if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false)) + m_pObject->GetMatrix().GetPosition().z = waterLevel + 0.6f; + + m_pObject->GetMatrix().UpdateRW(); + m_pObject->UpdateRwFrame(); + + bool touched = false; + for (int32 i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) { + CVehicle *vehicle = CPools::GetVehiclePool()->GetSlot(i); + if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 1.5f)) { + touched = true; +#ifdef FIX_BUGS + break; +#endif + } + } + + if (!touched) { + m_eType = PICKUP_NAUTICAL_MINE_ARMED; + m_nTimer = CTimer::GetTimeInMilliseconds() + 10000; + } + break; + } + case PICKUP_NAUTICAL_MINE_ARMED: + if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false)) + m_pObject->GetMatrix().GetPosition().z = waterLevel + 0.6f; + + m_pObject->GetMatrix().UpdateRW(); + m_pObject->UpdateRwFrame(); + // no break here + case PICKUP_MINE_ARMED: + { + bool explode = false; + if (CTimer::GetTimeInMilliseconds() > m_nTimer) + explode = true; +#ifdef FIX_BUGS + else// added else here since vehicle lookup is useless +#endif + { + for (int32 i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) { + CVehicle *vehicle = CPools::GetVehiclePool()->GetSlot(i); + if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 1.5f)) { + explode = true; +#ifdef FIX_BUGS + break; +#endif + } + } + } + if (explode) { + CExplosion::AddExplosion(nil, nil, EXPLOSION_MINE, m_pObject->GetPosition(), 0); + Remove(); + } + break; + } + case PICKUP_FLOATINGPACKAGE: + m_pObject->m_vecMoveSpeed.z -= 0.01f * CTimer::GetTimeStep(); + m_pObject->GetMatrix().GetPosition() += m_pObject->GetMoveSpeed() * CTimer::GetTimeStep(); + + m_pObject->GetMatrix().UpdateRW(); + m_pObject->UpdateRwFrame(); + if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false) && waterLevel >= m_pObject->GetPosition().z) + m_eType = PICKUP_FLOATINGPACKAGE_FLOATING; + break; + case PICKUP_FLOATINGPACKAGE_FLOATING: + if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false)) + m_pObject->GetMatrix().GetPosition().z = waterLevel; + + m_pObject->GetMatrix().UpdateRW(); + m_pObject->UpdateRwFrame(); + if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) { + Remove(); + result = true; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_FLOAT_PACKAGE, 0); + } + break; + default: break; + } + } + if (!m_bRemoved && (m_eType == PICKUP_ONCE_TIMEOUT || m_eType == PICKUP_MONEY) && CTimer::GetTimeInMilliseconds() > m_nTimer) + Remove(); + return result; +} + +void +CPickups::Init(void) +{ + NumMessages = 0; + for (int i = 0; i < NUMPICKUPS; i++) { + aPickUps[i].m_eType = PICKUP_NONE; + aPickUps[i].m_nIndex = 1; + aPickUps[i].m_pObject = nil; + } + + for (int i = 0; i < NUMCOLLECTEDPICKUPS; i++) + aPickUpsCollected[i] = 0; + + CollectedPickUpIndex = 0; +} + +bool +CPickups::IsPickUpPickedUp(int32 pickupId) +{ + for (int i = 0; i < NUMCOLLECTEDPICKUPS; i++) { + if (pickupId == aPickUpsCollected[i]) { + aPickUpsCollected[i] = 0; + return true; + } + } + return false; +} + +void +CPickups::PassTime(uint32 time) +{ + for (int i = 0; i < NUMPICKUPS; i++) { + if (aPickUps[i].m_eType != PICKUP_NONE) { + if (aPickUps[i].m_nTimer <= time) + aPickUps[i].m_nTimer = 0; + else + aPickUps[i].m_nTimer -= time; + } + } +} + +int32 +CPickups::GetActualPickupIndex(int32 index) +{ + if (index == -1) return -1; + + // doesn't look nice + if ((uint16)((index & 0xFFFF0000) >> 16) != aPickUps[(uint16)index].m_nIndex) return -1; + return (uint16)index; +} + +bool +CPickups::GivePlayerGoodiesWithPickUpMI(int16 modelIndex, int playerIndex) +{ + CPlayerPed *player; + + if (playerIndex <= 0) player = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + else player = CWorld::Players[playerIndex].m_pPed; + + if (modelIndex == MI_PICKUP_ADRENALINE) { + player->m_bAdrenalineActive = true; + player->m_nAdrenalineTime = CTimer::GetTimeInMilliseconds() + 20000; + player->m_fCurrentStamina = player->m_fMaxStamina; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_ADRENALINE, 0); + return true; + } else if (modelIndex == MI_PICKUP_BODYARMOUR) { + player->m_fArmour = 100.0f; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_ARMOUR, 0); + return true; + } else if (modelIndex == MI_PICKUP_INFO) { + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + return true; + } else if (modelIndex == MI_PICKUP_HEALTH) { + player->m_fHealth = 100.0f; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_HEALTH, 0); + return true; + } else if (modelIndex == MI_PICKUP_BONUS) { + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + return true; + } else if (modelIndex == MI_PICKUP_BRIBE) { + int32 level = FindPlayerPed()->m_pWanted->GetWantedLevel() - 1; + if (level < 0) level = 0; + player->SetWantedLevel(level); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + return true; + } else if (modelIndex == MI_PICKUP_KILLFRENZY) { + DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0); + return true; + } + return false; +} + +void +CPickups::RemoveAllFloatingPickups() +{ + for (int i = 0; i < NUMPICKUPS; i++) { + if (aPickUps[i].m_eType == PICKUP_FLOATINGPACKAGE || aPickUps[i].m_eType == PICKUP_FLOATINGPACKAGE_FLOATING) { + if (aPickUps[i].m_pObject) { + CWorld::Remove(aPickUps[i].m_pObject); + delete aPickUps[i].m_pObject; + aPickUps[i].m_pObject = nil; + } + } + } +} + +void +CPickups::RemovePickUp(int32 pickupIndex) +{ + int32 index = CPickups::GetActualPickupIndex(pickupIndex); + if (index == -1) return; + + if (aPickUps[index].m_pObject) { + CWorld::Remove(aPickUps[index].m_pObject); + delete aPickUps[index].m_pObject; + aPickUps[index].m_pObject = nil; + } + aPickUps[index].m_eType = PICKUP_NONE; + aPickUps[index].m_bRemoved = true; +} + +int32 +CPickups::GenerateNewOne(CVector pos, uint32 modelIndex, uint8 type, uint32 quantity) +{ + bool bFreeFound = false; + int32 slot = 0; + + if (type == PICKUP_FLOATINGPACKAGE || type == PICKUP_NAUTICAL_MINE_INACTIVE) { + for (slot = NUMPICKUPS-1; slot >= 0; slot--) { + if (aPickUps[slot].m_eType == PICKUP_NONE) { + bFreeFound = true; + break; + } + } + } else { + for (slot = 0; slot < NUMGENERALPICKUPS; slot++) { + if (aPickUps[slot].m_eType == PICKUP_NONE) { + bFreeFound = true; + break; + } + } + } + + if (!bFreeFound) { + for (slot = 0; slot < NUMGENERALPICKUPS; slot++) { + if (aPickUps[slot].m_eType == PICKUP_MONEY) break; + } + + if (slot >= NUMGENERALPICKUPS) { + for (slot = 0; slot < NUMGENERALPICKUPS; slot++) { + if (aPickUps[slot].m_eType == PICKUP_ONCE_TIMEOUT) break; + } + + if (slot >= NUMGENERALPICKUPS) return -1; + } + } + + if (slot >= NUMPICKUPS) return -1; + + aPickUps[slot].m_eType = type; + aPickUps[slot].m_bRemoved = false; + aPickUps[slot].m_nQuantity = quantity; + if (type == PICKUP_ONCE_TIMEOUT) + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 20000; + else if (type == PICKUP_MONEY) + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 30000; + else if (type == PICKUP_MINE_INACTIVE || type == PICKUP_MINE_ARMED) { + aPickUps[slot].m_eType = PICKUP_MINE_INACTIVE; + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 1500; + } else if (type == PICKUP_NAUTICAL_MINE_INACTIVE || type == PICKUP_NAUTICAL_MINE_ARMED) { + aPickUps[slot].m_eType = PICKUP_NAUTICAL_MINE_INACTIVE; + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 1500; + } + aPickUps[slot].m_eModelIndex = modelIndex; + aPickUps[slot].m_vecPos = pos; + aPickUps[slot].m_pObject = aPickUps[slot].GiveUsAPickUpObject(-1); + if (aPickUps[slot].m_pObject) + CWorld::Add(aPickUps[slot].m_pObject); + return GetNewUniquePickupIndex(slot); +} + +int32 +CPickups::GenerateNewOne_WeaponType(CVector pos, eWeaponType weaponType, uint8 type, uint32 quantity) +{ + return GenerateNewOne(pos, ModelForWeapon(weaponType), type, quantity); +} + +int32 +CPickups::GetNewUniquePickupIndex(int32 slot) +{ + if (aPickUps[slot].m_nIndex >= 0xFFFE) + aPickUps[slot].m_nIndex = 1; + else + aPickUps[slot].m_nIndex++; + return slot | (aPickUps[slot].m_nIndex << 16); +} + +int32 +CPickups::ModelForWeapon(eWeaponType weaponType) +{ + switch (weaponType) + { + case WEAPONTYPE_BASEBALLBAT: return MI_BASEBALL_BAT; + case WEAPONTYPE_COLT45: return MI_COLT; + case WEAPONTYPE_UZI: return MI_UZI; + case WEAPONTYPE_SHOTGUN: return MI_SHOTGUN; + case WEAPONTYPE_AK47: return MI_AK47; + case WEAPONTYPE_M16: return MI_M16; + case WEAPONTYPE_SNIPERRIFLE: return MI_SNIPER; + case WEAPONTYPE_ROCKETLAUNCHER: return MI_ROCKETLAUNCHER; + case WEAPONTYPE_FLAMETHROWER: return MI_FLAMETHROWER; + case WEAPONTYPE_MOLOTOV: return MI_MOLOTOV; + case WEAPONTYPE_GRENADE: return MI_GRENADE; + default: break; + } + return 0; +} + +eWeaponType +CPickups::WeaponForModel(int32 model) +{ + if (model == MI_PICKUP_BODYARMOUR) return WEAPONTYPE_ARMOUR; + switch (model) + { + case MI_GRENADE: return WEAPONTYPE_GRENADE; + case MI_AK47: return WEAPONTYPE_AK47; + case MI_BASEBALL_BAT: return WEAPONTYPE_BASEBALLBAT; + case MI_COLT: return WEAPONTYPE_COLT45; + case MI_MOLOTOV: return WEAPONTYPE_MOLOTOV; + case MI_ROCKETLAUNCHER: return WEAPONTYPE_ROCKETLAUNCHER; + case MI_SHOTGUN: return WEAPONTYPE_SHOTGUN; + case MI_SNIPER: return WEAPONTYPE_SNIPERRIFLE; + case MI_UZI: return WEAPONTYPE_UZI; + case MI_MISSILE: return WEAPONTYPE_UNARMED; + case MI_M16: return WEAPONTYPE_M16; + case MI_FLAMETHROWER: return WEAPONTYPE_FLAMETHROWER; + } + return WEAPONTYPE_UNARMED; +} + +int32 +CPickups::FindColourIndexForWeaponMI(int32 model) +{ + return WeaponForModel(model) - 1; +} + +void +CPickups::AddToCollectedPickupsArray(int32 index) +{ + aPickUpsCollected[CollectedPickUpIndex++] = index | (aPickUps[index].m_nIndex << 16); + if (CollectedPickUpIndex >= NUMCOLLECTEDPICKUPS) + CollectedPickUpIndex = 0; +} + +void +CPickups::Update() +{ +#ifdef FIX_BUGS // RIP speedrunning (solution from SA) + if (CReplay::IsPlayingBack()) + return; +#endif +#ifdef CAMERA_PICKUP + if ( bPickUpcamActivated ) // taken from PS2 + { + float dist = Distance2D(StaticCamCoors, FindPlayerCoors()); + float mult; + if ( dist < 10.0f ) + mult = 1.0f - (dist / 10.0f ); + else + mult = 0.0f; + + CVector pos = StaticCamCoors; + pos.z += (pPlayerVehicle->GetColModel()->boundingBox.GetSize().z + 2.0f) * mult; + + if ( (CTimer::GetTimeInMilliseconds() - StaticCamStartTime) > 750 ) + { + TheCamera.SetCamPositionForFixedMode(pos, CVector(0.0f, 0.0f, 0.0f)); + TheCamera.TakeControl(FindPlayerVehicle(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_SCRIPT); + } + + if ( FindPlayerVehicle() != pPlayerVehicle || Distance(StaticCamCoors, FindPlayerCoors()) > 40.0f + || ((CTimer::GetTimeInMilliseconds() - StaticCamStartTime) > 60000) ) + { + TheCamera.RestoreWithJumpCut(); + bPickUpcamActivated = false; + } + } +#endif +#define PICKUPS_FRAME_SPAN (6) +#ifdef FIX_BUGS + for (uint32 i = NUMGENERALPICKUPS * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN) / PICKUPS_FRAME_SPAN; i < NUMGENERALPICKUPS * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN + 1) / PICKUPS_FRAME_SPAN; i++) { +#else // BUG: this code can only reach 318 out of 320 pickups + for (uint32 i = NUMGENERALPICKUPS / PICKUPS_FRAME_SPAN * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN); i < NUMGENERALPICKUPS / PICKUPS_FRAME_SPAN * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN + 1); i++) { +#endif + if (aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].Update(FindPlayerPed(), FindPlayerVehicle(), CWorld::PlayerInFocus)) { + AddToCollectedPickupsArray(i); + } + } +#undef PICKUPS_FRAME_SPAN + for (uint32 i = NUMGENERALPICKUPS; i < NUMPICKUPS; i++) { + if (aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].Update(FindPlayerPed(), FindPlayerVehicle(), CWorld::PlayerInFocus)) { + AddToCollectedPickupsArray(i); + } + } +} + +void +CPickups::DoPickUpEffects(CEntity *entity) +{ + if (entity->GetModelIndex() == MI_PICKUP_KILLFRENZY) + entity->bDoNotRender = CTheScripts::IsPlayerOnAMission() || CDarkel::FrenzyOnGoing() || !CGame::nastyGame; + + if (!entity->bDoNotRender) { + float modifiedSin = 0.3f * (Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x7FF) * DEGTORAD(360.0f / 0x800)) + 1.0f); + + + int16 colorId; + + if (entity->GetModelIndex() == MI_PICKUP_ADRENALINE || entity->GetModelIndex() == MI_PICKUP_CAMERA) + colorId = 11; + else if (entity->GetModelIndex() == MI_PICKUP_BODYARMOUR || entity->GetModelIndex() == MI_PICKUP_BRIBE) + colorId = 12; + else if (entity->GetModelIndex() == MI_PICKUP_INFO || entity->GetModelIndex() == MI_PICKUP_KILLFRENZY) + colorId = 13; + else if (entity->GetModelIndex() == MI_PICKUP_HEALTH || entity->GetModelIndex() == MI_PICKUP_BONUS) + colorId = 14; + else + colorId = FindColourIndexForWeaponMI(entity->GetModelIndex()); + + assert(colorId >= 0); + + const CVector &pos = entity->GetPosition(); + + float colorModifier = ((CGeneral::GetRandomNumber() & 0x1F) * 0.015f + 1.0f) * modifiedSin * 0.15f; + CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, + aWeaponReds[colorId] * colorModifier, aWeaponGreens[colorId] * colorModifier, aWeaponBlues[colorId] * colorModifier, 4.0f, + 1.0f, 40.0f, false, 0.0f); + + float radius = (CGeneral::GetRandomNumber() & 0xF) * 0.1f + 3.0f; + CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f), radius, aWeaponReds[colorId] * modifiedSin / 256.0f, aWeaponGreens[colorId] * modifiedSin / 256.0f, aWeaponBlues[colorId] * modifiedSin / 256.0f, CPointLights::FOG_NONE, true); + float size = (CGeneral::GetRandomNumber() & 0xF) * 0.0005f + 0.6f; + CCoronas::RegisterCorona( (uintptr)entity, + aWeaponReds[colorId] * modifiedSin / 2.0f, aWeaponGreens[colorId] * modifiedSin / 2.0f, aWeaponBlues[colorId] * modifiedSin / 2.0f, + 255, + pos, + size, 65.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + + CObject *object = (CObject*)entity; + if (object->bPickupObjWithMessage || object->bOutOfStock || object->m_nBonusValue) { + float dist = Distance2D(pos, TheCamera.GetPosition()); + const float MAXDIST = 12.0f; + + if (dist < MAXDIST && NumMessages < NUMPICKUPMESSAGES) { + RwV3d vecOut; + float fDistX, fDistY; + if (CSprite::CalcScreenCoors(entity->GetPosition() + CVector(0.0f, 0.0f, 0.7f), &vecOut, &fDistX, &fDistY, true)) { + aMessages[NumMessages].m_pos.x = vecOut.x; + aMessages[NumMessages].m_pos.y = vecOut.y; + aMessages[NumMessages].m_dist.x = fDistX; + aMessages[NumMessages].m_dist.y = fDistY; + aMessages[NumMessages].m_weaponType = WeaponForModel(entity->GetModelIndex()); + aMessages[NumMessages].m_color.red = aWeaponReds[colorId]; + aMessages[NumMessages].m_color.green = aWeaponGreens[colorId]; + aMessages[NumMessages].m_color.blue = aWeaponBlues[colorId]; + aMessages[NumMessages].m_color.alpha = (1.0f - dist / MAXDIST) * 128.0f; + aMessages[NumMessages].m_bOutOfStock = object->bOutOfStock; + aMessages[NumMessages].m_quantity = object->m_nBonusValue; + NumMessages++; + } + } + } + + float angle = (float)(CTimer::GetTimeInMilliseconds() & 0x7FF) * DEGTORAD(360.0f / 0x800); + float c = Cos(angle) * aWeaponScale[colorId]; + float s = Sin(angle) * aWeaponScale[colorId]; + + // we know from SA they were setting each field manually like this + entity->GetMatrix().rx = c; + entity->GetMatrix().ry = s; + entity->GetMatrix().rz = 0.0f; + entity->GetMatrix().fx = -s; + entity->GetMatrix().fy = c; + entity->GetMatrix().fz = 0.0f; + entity->GetMatrix().ux = 0.0f; + entity->GetMatrix().uy = 0.0f; + entity->GetMatrix().uz = aWeaponScale[colorId]; + } +} + +void +CPickups::DoMineEffects(CEntity *entity) +{ + const CVector &pos = entity->GetPosition(); + float dist = Distance(pos, TheCamera.GetPosition()); + const float MAXDIST = 20.0f; + + if (dist < MAXDIST) { + float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x1FF) * DEGTORAD(360.0f / 0x200)); + + int32 red = (MAXDIST - dist) * (0.5f * s + 0.5f) / MAXDIST * 64.0f; + CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, red, 0, 0, 4.0f, 1.0f, 40.0f, + false, 0.0f); + CCoronas::RegisterCorona((uintptr)entity, red, 0, 0, 255, pos, 0.6f, 60.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } + + entity->GetMatrix().SetRotateZOnly((float)(CTimer::GetTimeInMilliseconds() & 0x3FF) * DEGTORAD(360.0f / 0x400)); +} + +void +CPickups::DoMoneyEffects(CEntity *entity) +{ + const CVector &pos = entity->GetPosition(); + float dist = Distance(pos, TheCamera.GetPosition()); + const float MAXDIST = 20.0f; + + if (dist < MAXDIST) { + float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x3FF) * DEGTORAD(360.0f / 0x400)); + + int32 green = (MAXDIST - dist) * (0.2f * s + 0.3f) / MAXDIST * 64.0f; + CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, 0, green, 0, 4.0f, 1.0f, + 40.0f, false, 0.0f); + CCoronas::RegisterCorona((uintptr)entity, 0, green, 0, 255, pos, 0.4f, 40.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } + + entity->GetMatrix().SetRotateZOnly((float)(CTimer::GetTimeInMilliseconds() & 0x7FF) * DEGTORAD(360.0f / 0x800)); +} + +void +CPickups::DoCollectableEffects(CEntity *entity) +{ + const CVector &pos = entity->GetPosition(); + float dist = Distance(pos, TheCamera.GetPosition()); + const float MAXDIST = 14.0f; + + if (dist < MAXDIST) { + float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x7FF) * DEGTORAD(360.0f / 0x800)); + + int32 color = (MAXDIST - dist) * (0.5f * s + 0.5f) / MAXDIST * 255.0f; + CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, color, color, color, 4.0f, + 1.0f, 40.0f, false, 0.0f); + CCoronas::RegisterCorona((uintptr)entity, color, color, color, 255, pos, 0.6f, 40.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } + + entity->GetMatrix().SetRotateZOnly((float)(CTimer::GetTimeInMilliseconds() & 0xFFF) * DEGTORAD(360.0f / 0x1000)); +} + +void +CPickups::RenderPickUpText() +{ + wchar *strToPrint; + for (int32 i = 0; i < NumMessages; i++) { + if (aMessages[i].m_quantity <= 39) { + switch (aMessages[i].m_quantity) // could use some enum maybe + { + case 0: + if (aMessages[i].m_weaponType == WEAPONTYPE_TOTALWEAPONS) { // unreachable code? + // what is this?? + sprintf(gString, "%d/%d", CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages, 2903); + } else { + if (aMessages[i].m_bOutOfStock) + strToPrint = TheText.Get("STOCK"); + else { + sprintf(gString, "$%d", CostOfWeapon[aMessages[i].m_weaponType]); + AsciiToUnicode(gString, gUString); + strToPrint = gUString; + } + } + break; + case 1: + strToPrint = TheText.Get("SECURI"); + break; + case 2: + strToPrint = TheText.Get("MOONBM"); + break; + case 3: + strToPrint = TheText.Get("COACH"); + break; + case 4: + strToPrint = TheText.Get("FLATBED"); + break; + case 5: + strToPrint = TheText.Get("LINERUN"); + break; + case 6: + strToPrint = TheText.Get("TRASHM"); + break; + case 7: + strToPrint = TheText.Get("PATRIOT"); + break; + case 8: + strToPrint = TheText.Get("WHOOPEE"); + break; + case 9: + strToPrint = TheText.Get("BLISTA"); + break; + case 10: + strToPrint = TheText.Get("MULE"); + break; + case 11: + strToPrint = TheText.Get("YANKEE"); + break; + case 12: + strToPrint = TheText.Get("BOBCAT"); + break; + case 13: + strToPrint = TheText.Get("DODO"); + break; + case 14: + strToPrint = TheText.Get("BUS"); + break; + case 15: + strToPrint = TheText.Get("RUMPO"); + break; + case 16: + strToPrint = TheText.Get("PONY"); + break; + case 17: + strToPrint = TheText.Get("SENTINL"); + break; + case 18: + strToPrint = TheText.Get("CHEETAH"); + break; + case 19: + strToPrint = TheText.Get("BANSHEE"); + break; + case 20: + strToPrint = TheText.Get("IDAHO"); + break; + case 21: + strToPrint = TheText.Get("INFERNS"); + break; + case 22: + strToPrint = TheText.Get("TAXI"); + break; + case 23: + strToPrint = TheText.Get("KURUMA"); + break; + case 24: + strToPrint = TheText.Get("STRETCH"); + break; + case 25: + strToPrint = TheText.Get("PEREN"); + break; + case 26: + strToPrint = TheText.Get("STINGER"); + break; + case 27: + strToPrint = TheText.Get("MANANA"); + break; + case 28: + strToPrint = TheText.Get("LANDSTK"); + break; + case 29: + strToPrint = TheText.Get("STALION"); + break; + case 30: + strToPrint = TheText.Get("BFINJC"); + break; + case 31: + strToPrint = TheText.Get("CABBIE"); + break; + case 32: + strToPrint = TheText.Get("ESPERAN"); + break; + case 33: + strToPrint = TheText.Get("FIRETRK"); + break; + case 34: + strToPrint = TheText.Get("AMBULAN"); + break; + case 35: + strToPrint = TheText.Get("ENFORCR"); + break; + case 36: + strToPrint = TheText.Get("FBICAR"); + break; + case 37: + strToPrint = TheText.Get("RHINO"); + break; + case 38: + strToPrint = TheText.Get("BARRCKS"); + break; + case 39: + strToPrint = TheText.Get("POLICAR"); + break; + default: + break; + } + } + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + + const float MAX_SCALE = 1.0f; + + float fScaleY = aMessages[i].m_dist.y / 100.0f; + if (fScaleY > MAX_SCALE) fScaleY = MAX_SCALE; + + float fScaleX = aMessages[i].m_dist.x / 100.0f; + if (fScaleX > MAX_SCALE) fScaleX = MAX_SCALE; + +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(fScaleX), SCREEN_SCALE_Y(fScaleY)); +#else + CFont::SetScale(fScaleX, fScaleY); +#endif + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_WIDTH); + CFont::SetJustifyOff(); + + CFont::SetColor(CRGBA(aMessages[i].m_color.red, aMessages[i].m_color.green, aMessages[i].m_color.blue, aMessages[i].m_color.alpha)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString(aMessages[i].m_pos.x, aMessages[i].m_pos.y, strToPrint); + } + NumMessages = 0; +} + +void +CPickups::Load(uint8 *buf, uint32 size) +{ +INITSAVEBUF + + for (int32 i = 0; i < NUMPICKUPS; i++) { +#ifdef COMPATIBLE_SAVES + ReadSaveBuf(&aPickUps[i].m_eType, buf); + ReadSaveBuf(&aPickUps[i].m_bRemoved, buf); + ReadSaveBuf(&aPickUps[i].m_nQuantity, buf); + int32 tmp; + ReadSaveBuf(&tmp, buf); + aPickUps[i].m_pObject = aPickUps[i].m_eType != PICKUP_NONE && tmp != 0 ? CPools::GetObjectPool()->GetSlot(tmp - 1) : nil; + ReadSaveBuf(&aPickUps[i].m_nTimer, buf); + ReadSaveBuf(&aPickUps[i].m_eModelIndex, buf); + ReadSaveBuf(&aPickUps[i].m_nIndex, buf); + ReadSaveBuf(&aPickUps[i].m_vecPos, buf); +#else + ReadSaveBuf(&aPickUps[i], buf); + + if (aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].m_pObject != nil) + aPickUps[i].m_pObject = CPools::GetObjectPool()->GetSlot((uintptr)aPickUps[i].m_pObject - 1); +#endif + } + + ReadSaveBuf(&CollectedPickUpIndex, buf); + SkipSaveBuf(buf, 2); + NumMessages = 0; + + for (uint16 i = 0; i < NUMCOLLECTEDPICKUPS; i++) + ReadSaveBuf(&aPickUpsCollected[i], buf); + +VALIDATESAVEBUF(size) +} + +void +CPickups::Save(uint8 *buf, uint32 *size) +{ + *size = PICKUPS_SAVE_SIZE + sizeof(uint16) + sizeof(uint16) + sizeof(aPickUpsCollected); + +INITSAVEBUF + + for (int32 i = 0; i < NUMPICKUPS; i++) { +#ifdef COMPATIBLE_SAVES + WriteSaveBuf(buf, aPickUps[i].m_eType); + WriteSaveBuf(buf, aPickUps[i].m_bRemoved); + WriteSaveBuf(buf, aPickUps[i].m_nQuantity); + int32 tmp = aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].m_pObject != nil ? CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(aPickUps[i].m_pObject) + 1 : 0; + WriteSaveBuf(buf, tmp); + WriteSaveBuf(buf, aPickUps[i].m_nTimer); + WriteSaveBuf(buf, aPickUps[i].m_eModelIndex); + WriteSaveBuf(buf, aPickUps[i].m_nIndex); + WriteSaveBuf(buf, aPickUps[i].m_vecPos); +#else + CPickup *buf_pickup = WriteSaveBuf(buf, aPickUps[i]); + if (buf_pickup->m_eType != PICKUP_NONE && buf_pickup->m_pObject != nil) + buf_pickup->m_pObject = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(buf_pickup->m_pObject) + 1); +#endif + } + + WriteSaveBuf(buf, CollectedPickUpIndex); + WriteSaveBuf(buf, (uint16)0); // possibly was NumMessages + + for (uint16 i = 0; i < NUMCOLLECTEDPICKUPS; i++) + WriteSaveBuf(buf, aPickUpsCollected[i]); + +VALIDATESAVEBUF(*size) +} + +void +CPacManPickup::Update() +{ + if (FindPlayerVehicle() == nil) return; + + CVehicle *veh = FindPlayerVehicle(); + + if (DistanceSqr2D(FindPlayerVehicle()->GetPosition(), m_vecPosn.x, m_vecPosn.y) < 100.0f && veh->IsSphereTouchingVehicle(m_vecPosn.x, m_vecPosn.y, m_vecPosn.z, 1.5f)) { + switch (m_eType) + { + case PACMAN_SCRAMBLE: + { + veh->m_nPacManPickupsCarried++; + veh->m_vecMoveSpeed *= 0.65f; + float massMult = (veh->m_fMass + 250.0f) / veh->m_fMass; + veh->m_fMass *= massMult; + veh->m_fTurnMass *= massMult; + veh->m_fForceMultiplier *= massMult; + FindPlayerPed()->m_pWanted->m_nChaos += 10; + FindPlayerPed()->m_pWanted->UpdateWantedLevel(); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_PACMAN_PACKAGE, 0); + break; + } + case PACMAN_RACE: + CPacManPickups::PillsEatenInRace++; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_PACMAN_PILL, 0); + break; + default: + break; + } + m_eType = PACMAN_NONE; + if (m_pObject != nil) { + CWorld::Remove(m_pObject); + delete m_pObject; + m_pObject = nil; + } + } +} + +int32 CollectGameState; +int16 ThingsToCollect; + +CPacManPickup CPacManPickups::aPMPickUps[NUMPACMANPICKUPS]; +CVector CPacManPickups::LastPickUpCoors; +int32 CPacManPickups::PillsEatenInRace; +bool CPacManPickups::bPMActive; + +void +CPacManPickups::Init() +{ + for (int i = 0; i < NUMPACMANPICKUPS; i++) + aPMPickUps[i].m_eType = PACMAN_NONE; + bPMActive = false; +} + +void +CPacManPickups::Update() +{ + if (FindPlayerVehicle()) { + float dist = Distance(FindPlayerCoors(), CVector(1072.0f, -948.0f, 14.5f)); + switch (CollectGameState) { + case 1: + if (dist < 10.0f) { + ThingsToCollect -= FindPlayerVehicle()->m_nPacManPickupsCarried; + FindPlayerVehicle()->m_nPacManPickupsCarried = 0; + FindPlayerVehicle()->m_fMass /= FindPlayerVehicle()->m_fForceMultiplier; + FindPlayerVehicle()->m_fTurnMass /= FindPlayerVehicle()->m_fForceMultiplier; + FindPlayerVehicle()->m_fForceMultiplier = 1.0f; + } + if (ThingsToCollect <= 0) { + CollectGameState = 2; + ClearPMPickUps(); + } + break; + case 2: + if (dist > 11.0f) + CollectGameState = 0; + break; + case 20: + if (Distance(FindPlayerCoors(), LastPickUpCoors) > 30.0f) { + LastPickUpCoors = FindPlayerCoors(); + printf("%f, %f, %f,\n", LastPickUpCoors.x, LastPickUpCoors.y, LastPickUpCoors.z); + } + break; + default: + break; + } + } + if (bPMActive) { +#define PACMANPICKUPS_FRAME_SPAN (4) + for (uint32 i = (CTimer::GetFrameCounter() % PACMANPICKUPS_FRAME_SPAN) * (NUMPACMANPICKUPS / PACMANPICKUPS_FRAME_SPAN); i < ((CTimer::GetFrameCounter() % PACMANPICKUPS_FRAME_SPAN) + 1) * (NUMPACMANPICKUPS / PACMANPICKUPS_FRAME_SPAN); i++) { + if (aPMPickUps[i].m_eType != PACMAN_NONE) + aPMPickUps[i].Update(); + } +#undef PACMANPICKUPS_FRAME_SPAN + } +} + +void +CPacManPickups::GeneratePMPickUps(CVector pos, float scrambleMult, int16 count, uint8 type) +{ + int i = 0; + while (count > 0) { + while (aPMPickUps[i].m_eType != PACMAN_NONE) + i++; + + bool bPickupCreated = false; + while (!bPickupCreated) { + CVector newPos = pos; + CColPoint colPoint; + CEntity *pRoad; + uint16 nRand = CGeneral::GetRandomNumber(); + newPos.x += ((nRand & 0xFF) - 128) * scrambleMult / 128.0f; + newPos.y += (((nRand >> 8) & 0xFF) - 128) * scrambleMult / 128.0f; + newPos.z = 1000.0f; + if (CWorld::ProcessVerticalLine(newPos, -1000.0f, colPoint, pRoad, true, false, false, false, true, false, nil) && pRoad->IsBuilding() && ((CBuilding*)pRoad)->GetIsATreadable()) { + newPos.z = 0.7f + colPoint.point.z; + aPMPickUps[i].m_eType = type; + aPMPickUps[i].m_vecPosn = newPos; + CObject *obj = new CObject(MI_BULLION, true); + if (obj != nil) { + obj->ObjectCreatedBy = MISSION_OBJECT; + obj->SetPosition(aPMPickUps[i].m_vecPosn); + obj->SetOrientation(0.0f, 0.0f, -HALFPI); + obj->GetMatrix().UpdateRW(); + obj->UpdateRwFrame(); + + obj->bAffectedByGravity = false; + obj->bExplosionProof = true; + obj->bUsesCollision = false; + obj->bIsPickup = false; + CWorld::Add(obj); + } + aPMPickUps[i].m_pObject = obj; + bPickupCreated = true; + } + } + count--; + } + bPMActive = true; +} + +// diablo porn mission pickups +static const CVector aRacePoints1[] = { + CVector(913.62219f, -155.13692f, 4.9699469f), + CVector(913.92401f, -124.12943f, 4.9692569f), + CVector(913.27899f, -93.524231f, 7.4325991f), + CVector(912.60852f, -63.15905f, 7.4533591f), + CVector(934.22144f, -42.049122f, 7.4511471f), + CVector(958.88092f, -23.863735f, 7.4652338f), + CVector(978.50812f, -0.78458798f, 5.13515f), + CVector(1009.4175f, -2.1041219f, 2.4461579f), + CVector(1040.6313f, -2.0793829f, 2.293175f), + CVector(1070.7863f, -2.084095f, 2.2789791f), + CVector(1100.5773f, -8.468729f, 5.3248072f), + CVector(1119.9341f, -31.738031f, 7.1913071f), + CVector(1122.1664f, -62.762737f, 7.4703908f), + CVector(1122.814f, -93.650566f, 8.5577497f), + CVector(1125.8253f, -124.26616f, 9.9803305f), + CVector(1153.8727f, -135.47169f, 14.150617f), + CVector(1184.0831f, -135.82845f, 14.973998f), + CVector(1192.0432f, -164.57816f, 19.18627f), + CVector(1192.7761f, -194.28871f, 24.799675f), + CVector(1215.1527f, -215.0714f, 25.74975f), + CVector(1245.79f, -215.39304f, 28.70726f), + CVector(1276.2477f, -216.39485f, 33.71236f), + CVector(1306.5535f, -216.71007f, 39.711472f), + CVector(1335.0244f, -224.59329f, 46.474979f), + CVector(1355.4879f, -246.27664f, 49.934841f), + CVector(1362.6003f, -276.47064f, 49.96265f), + CVector(1363.027f, -307.30847f, 49.969173f), + CVector(1365.343f, -338.08609f, 49.967789f), + CVector(1367.5957f, -368.01105f, 50.092304f), + CVector(1368.2749f, -398.38049f, 50.061268f), + CVector(1366.9034f, -429.98483f, 50.057545f), + CVector(1356.8534f, -459.09259f, 50.035545f), + CVector(1335.5819f, -481.13544f, 47.217903f), + CVector(1306.7552f, -491.07443f, 40.202629f), + CVector(1275.5978f, -491.33194f, 33.969223f), + CVector(1244.702f, -491.46451f, 29.111021f), + CVector(1213.2222f, -491.8754f, 25.771168f), + CVector(1182.7729f, -492.19995f, 24.749964f), + CVector(1152.6874f, -491.42221f, 21.70038f), + CVector(1121.5352f, -491.94604f, 20.075182f), + CVector(1090.7056f, -492.63751f, 17.585758f), + CVector(1059.6008f, -491.65762f, 14.848632f), + CVector(1029.113f, -489.66031f, 14.918498f), + CVector(998.20679f, -486.78107f, 14.945688f), + CVector(968.00555f, -484.91266f, 15.001229f), + CVector(937.74939f, -492.09015f, 14.958629f), + CVector(927.17352f, -520.97736f, 14.972308f), + CVector(929.29749f, -552.08643f, 14.978855f), + CVector(950.69525f, -574.47778f, 14.972788f), + CVector(974.02826f, -593.56024f, 14.966445f), + CVector(989.04779f, -620.12854f, 14.951016f), + CVector(1014.1639f, -637.3905f, 14.966736f), + CVector(1017.5961f, -667.3736f, 14.956415f), + CVector(1041.9735f, -685.94391f, 15.003841f), + CVector(1043.3064f, -716.11298f, 14.974236f), + CVector(1043.5337f, -746.63855f, 14.96919f), + CVector(1044.142f, -776.93823f, 14.965424f), + CVector(1044.2657f, -807.29395f, 14.97171f), + CVector(1017.0797f, -820.1076f, 14.975431f), + CVector(986.23865f, -820.37103f, 14.972883f), + CVector(956.10065f, -820.23291f, 14.981133f), + CVector(925.86914f, -820.19049f, 14.976553f), + CVector(897.69702f, -831.08734f, 14.962709f), + CVector(868.06586f, -835.99237f, 14.970685f), + CVector(836.93054f, -836.84387f, 14.965049f), + CVector(811.63586f, -853.7915f, 15.067576f), + CVector(811.46344f, -884.27368f, 12.247812f), + CVector(811.60651f, -914.70959f, 9.2393751f), + CVector(811.10425f, -945.16272f, 5.817255f), + CVector(816.54584f, -975.64587f, 4.998558f), + CVector(828.2951f, -1003.3685f, 5.0471172f), + CVector(852.28839f, -1021.5963f, 4.9371028f), + CVector(882.50067f, -1025.4459f, 5.14077f), + CVector(912.84821f, -1026.7874f, 8.3415451f), + CVector(943.68274f, -1026.6914f, 11.341879f), + CVector(974.4129f, -1027.3682f, 14.410345f), + CVector(1004.1079f, -1036.0778f, 14.92961f), + CVector(1030.1144f, -1051.1224f, 14.850387f), + CVector(1058.7585f, -1060.342f, 14.821624f), + CVector(1087.7797f, -1068.3263f, 14.800561f), + CVector(1099.8807f, -1095.656f, 11.877907f), + CVector(1130.0005f, -1101.994f, 11.853914f), + CVector(1160.3809f, -1101.6355f, 11.854824f), + CVector(1191.8524f, -1102.1577f, 11.853843f), + CVector(1223.3307f, -1102.7448f, 11.852233f), + CVector(1253.564f, -1098.1045f, 11.853944f), + CVector(1262.0203f, -1069.1785f, 14.8147f), + CVector(1290.9998f, -1059.1882f, 14.816016f), + CVector(1316.246f, -1041.0635f, 14.81109f), + CVector(1331.7539f, -1013.835f, 14.81207f), + CVector(1334.0579f, -983.55402f, 14.827253f), + CVector(1323.2429f, -954.23083f, 14.954678f), + CVector(1302.7495f, -932.21216f, 14.962917f), + CVector(1317.418f, -905.89325f, 14.967506f), + CVector(1337.9503f, -883.5025f, 14.969675f), + CVector(1352.6929f, -855.96954f, 14.967854f), + CVector(1357.2388f, -826.26971f, 14.97295f), + CVector(1384.8668f, -812.47693f, 12.907736f), + CVector(1410.8983f, -795.39056f, 12.052228f), + CVector(1433.901f, -775.55811f, 11.96265f), + CVector(1443.8615f, -746.92511f, 11.976114f), + CVector(1457.7015f, -720.00903f, 11.971177f), + CVector(1481.5685f, -701.30237f, 11.977908f), + CVector(1511.4004f, -696.83295f, 11.972709f), + CVector(1542.1796f, -695.61676f, 11.970441f), + CVector(1570.3301f, -684.6239f, 11.969202f), + CVector(0.0f, 0.0f, 0.0f), +}; + +void +CPacManPickups::GeneratePMPickUpsForRace(int32 race) +{ + const CVector *pPos = nil; + int i = 0; + + if (race == 0) pPos = aRacePoints1; // there's only one available + assert(pPos != nil); + + while (!pPos->IsZero()) { + while (aPMPickUps[i].m_eType != PACMAN_NONE) + i++; + + aPMPickUps[i].m_eType = PACMAN_RACE; + aPMPickUps[i].m_vecPosn = *(pPos++); + if (race == 0) { + CObject* obj = new CObject(MI_DONKEYMAG, true); + if (obj != nil) { + obj->ObjectCreatedBy = MISSION_OBJECT; + + obj->SetPosition(aPMPickUps[i].m_vecPosn); + obj->SetOrientation(0.0f, 0.0f, -HALFPI); + obj->GetMatrix().UpdateRW(); + obj->UpdateRwFrame(); + + obj->bAffectedByGravity = false; + obj->bExplosionProof = true; + obj->bUsesCollision = false; + obj->bIsPickup = false; + + CWorld::Add(obj); + } + aPMPickUps[i].m_pObject = obj; + } else + aPMPickUps[i].m_pObject = nil; + } + bPMActive = true; +} + +void +CPacManPickups::GenerateOnePMPickUp(CVector pos) +{ + bPMActive = true; + aPMPickUps[0].m_eType = PACMAN_RACE; + aPMPickUps[0].m_vecPosn = pos; +} + +void +CPacManPickups::Render() +{ + if (!bPMActive) return; + + PUSH_RENDERGROUP("CPacManPickups::Render"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[6])); + + RwV3d pos; + float w, h; + + for (int i = 0; i < NUMPACMANPICKUPS; i++) { + switch (aPMPickUps[i].m_eType) + { + case PACMAN_SCRAMBLE: + case PACMAN_RACE: + if (CSprite::CalcScreenCoors(aPMPickUps[i].m_vecPosn, &pos, &w, &h, true) && pos.z < 100.0f) { + if (aPMPickUps[i].m_pObject != nil) { + aPMPickUps[i].m_pObject->GetMatrix().SetRotateZOnly((CTimer::GetTimeInMilliseconds() % 1024) * TWOPI / 1024.0f); + aPMPickUps[i].m_pObject->GetMatrix().UpdateRW(); + aPMPickUps[i].m_pObject->UpdateRwFrame(); + } + float fsin = Sin((CTimer::GetTimeInMilliseconds() % 1024) * 6.28f / 1024.0f); // yes, it is 6.28f when it was TWOPI just now... + CSprite::RenderOneXLUSprite(pos.x, pos.y, pos.z, 0.8f * w * fsin, 0.8f * h, 100, 50, 5, 255, 1.0f / pos.z, 255); + } + break; + default: + break; + } + } + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, FALSE); + + POP_RENDERGROUP(); +} + +void +CPacManPickups::ClearPMPickUps() +{ + bPMActive = false; + + for (int i = 0; i < NUMPACMANPICKUPS; i++) { + if (aPMPickUps[i].m_pObject != nil) { + CWorld::Remove(aPMPickUps[i].m_pObject); + delete aPMPickUps[i].m_pObject; + aPMPickUps[i].m_pObject = nil; + } + aPMPickUps[i].m_eType = PACMAN_NONE; + } +} + +void +CPacManPickups::StartPacManRace(int32 race) +{ + GeneratePMPickUpsForRace(race); + PillsEatenInRace = 0; +} + +void +CPacManPickups::StartPacManRecord() +{ + CollectGameState = 20; + LastPickUpCoors = FindPlayerCoors(); +} + +uint32 +CPacManPickups::QueryPowerPillsEatenInRace() +{ + return PillsEatenInRace; +} + +void +CPacManPickups::ResetPowerPillsEatenInRace() +{ + PillsEatenInRace = 0; +} + +void +CPacManPickups::CleanUpPacManStuff() +{ + ClearPMPickUps(); +} + +void +CPacManPickups::StartPacManScramble(CVector pos, float scrambleMult, int16 count) +{ + GeneratePMPickUps(pos, scrambleMult, count, PACMAN_SCRAMBLE); +} + +uint32 +CPacManPickups::QueryPowerPillsCarriedByPlayer() +{ + if (FindPlayerVehicle()) + return FindPlayerVehicle()->m_nPacManPickupsCarried; + return 0; +} + +void +CPacManPickups::ResetPowerPillsCarriedByPlayer() +{ + if (FindPlayerVehicle() != nil) { + FindPlayerVehicle()->m_nPacManPickupsCarried = 0; + FindPlayerVehicle()->m_fMass /= FindPlayerVehicle()->m_fForceMultiplier; + FindPlayerVehicle()->m_fTurnMass /= FindPlayerVehicle()->m_fForceMultiplier; + FindPlayerVehicle()->m_fForceMultiplier = 1.0f; + } +} + +void +CPed::CreateDeadPedMoney(void) +{ + if (!CGame::nastyGame) + return; + + int mi = GetModelIndex(); + + if ((mi >= MI_COP && mi <= MI_FIREMAN) || CharCreatedBy == MISSION_CHAR || bInVehicle) + return; + + int money = CGeneral::GetRandomNumber() % 60; + if (money < 10) + return; + + if (money == 43) + money = 700; + + int pickupCount = money / 40 + 1; + int moneyPerPickup = money / pickupCount; + + for(int i = 0; i < pickupCount; i++) { + // (CGeneral::GetRandomNumber() % 256) * PI / 128 gives a float up to something TWOPI-ish. + float pickupX = 1.5f * Sin((CGeneral::GetRandomNumber() % 256) * PI / 128) + GetPosition().x; + float pickupY = 1.5f * Cos((CGeneral::GetRandomNumber() % 256) * PI / 128) + GetPosition().y; + bool found = false; + float groundZ = CWorld::FindGroundZFor3DCoord(pickupX, pickupY, GetPosition().z, &found) + 0.5f; + if (found) { + CPickups::GenerateNewOne(CVector(pickupX, pickupY, groundZ), MI_MONEY, PICKUP_MONEY, moneyPerPickup + (CGeneral::GetRandomNumber() & 7)); + } + } +} + +void +CPed::CreateDeadPedWeaponPickups(void) +{ + bool found = false; + float angleToPed; + CVector pickupPos; + + if (bInVehicle) + return; + + for(int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) { + + eWeaponType weapon = GetWeapon(i).m_eWeaponType; + int weaponAmmo = GetWeapon(i).m_nAmmoTotal; + if (weapon == WEAPONTYPE_UNARMED || weapon == WEAPONTYPE_DETONATOR || weaponAmmo == 0) + continue; + + angleToPed = i * 1.75f; + pickupPos = GetPosition(); + pickupPos.x += 1.5f * Sin(angleToPed); + pickupPos.y += 1.5f * Cos(angleToPed); + pickupPos.z = CWorld::FindGroundZFor3DCoord(pickupPos.x, pickupPos.y, pickupPos.z, &found) + 0.5f; + + CVector pedPos = GetPosition(); + pedPos.z += 0.3f; + + CVector pedToPickup = pickupPos - pedPos; + float distance = pedToPickup.Magnitude(); + + // outer edge of pickup + distance = (distance + 0.3f) / distance; + CVector pickupPos2 = pedPos; + pickupPos2 += distance * pedToPickup; + + // pickup must be on ground and line to its edge must be clear + if (!found || CWorld::GetIsLineOfSightClear(pickupPos2, pedPos, true, false, false, false, false, false, false)) { + // otherwise try another position (but disregard second check apparently) + angleToPed += 3.14f; + pickupPos = GetPosition(); + pickupPos.x += 1.5f * Sin(angleToPed); + pickupPos.y += 1.5f * Cos(angleToPed); + pickupPos.z = CWorld::FindGroundZFor3DCoord(pickupPos.x, pickupPos.y, pickupPos.z, &found) + 0.5f; + } + if (found) + CPickups::GenerateNewOne_WeaponType(pickupPos, weapon, PICKUP_ONCE_TIMEOUT, Min(weaponAmmo, AmmoForWeapon_OnStreet[weapon])); + } + ClearWeapons(); +} \ No newline at end of file diff --git a/src/control/Pickups.h b/src/control/Pickups.h new file mode 100644 index 0000000..4e1c764 --- /dev/null +++ b/src/control/Pickups.h @@ -0,0 +1,146 @@ +#pragma once +#include "Weapon.h" + +enum ePickupType +{ + PICKUP_NONE = 0, + PICKUP_IN_SHOP, + PICKUP_ON_STREET, + PICKUP_ONCE, + PICKUP_ONCE_TIMEOUT, + PICKUP_COLLECTABLE1, + PICKUP_IN_SHOP_OUT_OF_STOCK, + PICKUP_MONEY, + PICKUP_MINE_INACTIVE, + PICKUP_MINE_ARMED, + PICKUP_NAUTICAL_MINE_INACTIVE, + PICKUP_NAUTICAL_MINE_ARMED, + PICKUP_FLOATINGPACKAGE, + PICKUP_FLOATINGPACKAGE_FLOATING, + PICKUP_ON_STREET_SLOW, + PICKUP_NUMOFTYPES +}; + +class CEntity; +class CObject; +class CVehicle; +class CPlayerPed; + +class CPickup +{ +public: + uint8 m_eType; + bool m_bRemoved; + uint16 m_nQuantity; + CObject *m_pObject; + uint32 m_nTimer; + int16 m_eModelIndex; + uint16 m_nIndex; + CVector m_vecPos; + + CObject *GiveUsAPickUpObject(int32 handle); + bool Update(CPlayerPed *player, CVehicle *vehicle, int playerId); +private: + inline bool IsMine() { return m_eType >= PICKUP_MINE_INACTIVE && m_eType <= PICKUP_FLOATINGPACKAGE_FLOATING; } + inline bool CanBePickedUp(CPlayerPed *player); + inline void Remove(); +}; + +VALIDATE_SIZE(CPickup, 0x1C); + +struct tPickupMessage +{ + CVector2D m_pos; + eWeaponType m_weaponType; + CVector2D m_dist; + CRGBA m_color; + uint8 m_bOutOfStock : 1; + uint8 m_quantity; +}; + +class CPickups +{ + static int32 aPickUpsCollected[NUMCOLLECTEDPICKUPS]; + static int16 CollectedPickUpIndex; + static int16 NumMessages; + static tPickupMessage aMessages[NUMPICKUPMESSAGES]; +public: + static void Init(); + static void Update(); + static void RenderPickUpText(); + static void DoCollectableEffects(CEntity *ent); + static void DoMoneyEffects(CEntity *ent); + static void DoMineEffects(CEntity *ent); + static void DoPickUpEffects(CEntity *ent); + static int32 GenerateNewOne(CVector pos, uint32 modelIndex, uint8 type, uint32 quantity); + static int32 GenerateNewOne_WeaponType(CVector pos, eWeaponType weaponType, uint8 type, uint32 quantity); + static void RemovePickUp(int32 pickupIndex); + static void RemoveAllFloatingPickups(); + static void AddToCollectedPickupsArray(int32 index); + static bool IsPickUpPickedUp(int32 pickupId); + static int32 ModelForWeapon(eWeaponType weaponType); + static enum eWeaponType WeaponForModel(int32 model); + static int32 FindColourIndexForWeaponMI(int32 model); + static int32 GetActualPickupIndex(int32 index); + static int32 GetNewUniquePickupIndex(int32 slot); + static void PassTime(uint32 time); + static bool GivePlayerGoodiesWithPickUpMI(int16 modelIndex, int playerIndex); + static void Load(uint8 *buf, uint32 size); + static void Save(uint8 *buf, uint32 *size); + + static CPickup aPickUps[NUMPICKUPS]; + + // unused + static bool bPickUpcamActivated; + static CVehicle *pPlayerVehicle; + static CVector StaticCamCoors; + static uint32 StaticCamStartTime; +}; + +extern uint16 AmmoForWeapon[20]; +extern uint16 AmmoForWeapon_OnStreet[20]; +extern uint16 CostOfWeapon[20]; + +enum ePacmanPickupType +{ + PACMAN_NONE, + PACMAN_SCRAMBLE, + PACMAN_RACE, +}; + +class CPacManPickup +{ +public: + CVector m_vecPosn; + CObject *m_pObject; + uint8 m_eType; + + void Update(); +}; + +class CPacManPickups +{ + friend class CPacManPickup; + + static CPacManPickup aPMPickUps[NUMPACMANPICKUPS]; + static CVector LastPickUpCoors; + static int PillsEatenInRace; + static bool bPMActive; +public: + static void Init(void); + static void Update(void); + static void GeneratePMPickUps(CVector, float, int16, uint8); + static void GeneratePMPickUpsForRace(int32); + static void GenerateOnePMPickUp(CVector); + static void Render(void); + static void StartPacManRace(int32); + static void StartPacManRecord(void); + static uint32 QueryPowerPillsEatenInRace(void); + static void ResetPowerPillsEatenInRace(void); + static void ClearPMPickUps(void); + static void CleanUpPacManStuff(void); + static void StartPacManScramble(CVector, float, int16); + static uint32 QueryPowerPillsCarriedByPlayer(void); + static void ResetPowerPillsCarriedByPlayer(void); + +}; diff --git a/src/control/PowerPoints.cpp b/src/control/PowerPoints.cpp new file mode 100644 index 0000000..9a74e8d --- /dev/null +++ b/src/control/PowerPoints.cpp @@ -0,0 +1,22 @@ +#include "common.h" +#include "PowerPoints.h" + +// Some cut beta feature + +void CPowerPoint::Update() +{} + +void CPowerPoints::Init() +{} + +void CPowerPoints::Update() +{} + +void CPowerPoints::GenerateNewOne(float, float, float, float, float, float, uint8) +{} + +void CPowerPoints::Save(uint8**, uint32*) +{} + +void CPowerPoints::Load(uint8*, uint32) +{} \ No newline at end of file diff --git a/src/control/PowerPoints.h b/src/control/PowerPoints.h new file mode 100644 index 0000000..ee3750c --- /dev/null +++ b/src/control/PowerPoints.h @@ -0,0 +1,26 @@ +#pragma once + +enum +{ + POWERPOINT_NONE = 0, + POWERPOINT_HEALTH, + POWERPOINT_HIDEOUT_INDUSTRIAL, + POWERPOINT_HIDEOUT_COMMERCIAL, + POWERPOINT_HIDEOUT_SUBURBAN +}; + +class CPowerPoint +{ +public: + void Update(); +}; + +class CPowerPoints +{ +public: + static void Init(); + static void Update(); + static void GenerateNewOne(float, float, float, float, float, float, uint8); + static void Save(uint8**, uint32*); + static void Load(uint8*, uint32); +}; \ No newline at end of file diff --git a/src/control/Record.cpp b/src/control/Record.cpp new file mode 100644 index 0000000..7f636ec --- /dev/null +++ b/src/control/Record.cpp @@ -0,0 +1,529 @@ +#include "common.h" + +#include "Record.h" + +#include "FileMgr.h" +#include "Pad.h" +#include "Pools.h" +#include "Streaming.h" +#include "Timer.h" +#include "VehicleModelInfo.h" +#include "World.h" +#include "Frontend.h" + +uint16 CRecordDataForGame::RecordingState; +uint8* CRecordDataForGame::pDataBuffer; +uint8* CRecordDataForGame::pDataBufferPointer; +int CRecordDataForGame::FId; +tGameBuffer CRecordDataForGame::pDataBufferForFrame; + +#define MEMORY_FOR_GAME_RECORD (150000) + +void CRecordDataForGame::Init(void) +{ + RecordingState = STATE_NONE; + delete[] pDataBuffer; + pDataBufferPointer = nil; + pDataBuffer = nil; +#ifndef GTA_PS2 // this stuff is not present on PS2 + FId = CFileMgr::OpenFile("playback.dat", "r"); + if (FId <= 0) { + if ((FId = CFileMgr::OpenFile("record.dat", "r")) <= 0) + RecordingState = STATE_NONE; + else { + CFileMgr::CloseFile(FId); + FId = CFileMgr::OpenFileForWriting("record.dat"); + RecordingState = STATE_RECORD; + } + } + else { + RecordingState = STATE_PLAYBACK; + } + if (RecordingState == STATE_PLAYBACK) { + pDataBufferPointer = new uint8[MEMORY_FOR_GAME_RECORD]; + pDataBuffer = pDataBufferPointer; + pDataBuffer[CFileMgr::Read(FId, (char*)pDataBufferPointer, MEMORY_FOR_GAME_RECORD) + 8] = (uint8)-1; + CFileMgr::CloseFile(FId); + } +#else + RecordingState = STATE_NONE; // second time to make sure +#endif +} + +void CRecordDataForGame::SaveOrRetrieveDataForThisFrame(void) +{ + switch (RecordingState) { + case STATE_RECORD: + { + pDataBufferForFrame.m_fTimeStep = CTimer::GetTimeStep(); + pDataBufferForFrame.m_nTimeInMilliseconds = CTimer::GetTimeInMilliseconds(); + pDataBufferForFrame.m_nSizeOfPads[0] = 0; + pDataBufferForFrame.m_nSizeOfPads[1] = 0; + pDataBufferForFrame.m_nChecksum = CalcGameChecksum(); + uint8* pController1 = PackCurrentPadValues(pDataBufferForFrame.m_ControllerBuffer, &CPad::GetPad(0)->OldState, &CPad::GetPad(0)->NewState); + pDataBufferForFrame.m_nSizeOfPads[0] = (pController1 - pDataBufferForFrame.m_ControllerBuffer) / 2; + uint8* pController2 = PackCurrentPadValues(pController1, &CPad::GetPad(1)->OldState, &CPad::GetPad(1)->NewState); + pDataBufferForFrame.m_nSizeOfPads[1] = (pController2 - pController1) / 2; + uint8* pEndPtr = pController2; + if ((pDataBufferForFrame.m_nSizeOfPads[0] + pDataBufferForFrame.m_nSizeOfPads[1]) & 1) + pEndPtr += 2; + CFileMgr::Write(FId, (char*)&pDataBufferForFrame, pEndPtr - (uint8*)&pDataBufferForFrame); + break; + } + case STATE_PLAYBACK: + if (pDataBufferPointer[8] == (uint8)-1) + CPad::GetPad(0)->NewState.Clear(); + else { + tGameBuffer* pData = (tGameBuffer*)pDataBufferPointer; + CTimer::SetTimeInMilliseconds(pData->m_nTimeInMilliseconds); + CTimer::SetTimeStep(pData->m_fTimeStep); + uint8 size1 = pData->m_nSizeOfPads[0]; + uint8 size2 = pData->m_nSizeOfPads[1]; + pDataBufferPointer = (uint8*)&pData->m_ControllerBuffer; + pDataBufferPointer = UnPackCurrentPadValues(pDataBufferPointer, size1, &CPad::GetPad(0)->NewState); + pDataBufferPointer = UnPackCurrentPadValues(pDataBufferPointer, size2, &CPad::GetPad(1)->NewState); + if ((size1 + size2) & 1) + pDataBufferPointer += 2; + if (pData->m_nChecksum != CalcGameChecksum()) + printf("Playback out of sync\n"); + } + } +} + +#define PROCESS_BUTTON_STATE_STORE(buf, os, ns, field, id) \ + do { \ + if (os->field != ns->field){ \ + *buf++ = id; \ + *buf++ = ns->field; \ + } \ + } while (0); + +uint8* CRecordDataForGame::PackCurrentPadValues(uint8* buf, CControllerState* os, CControllerState* ns) +{ + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftStickX, 0); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftStickY, 1); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightStickX, 2); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightStickY, 3); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShoulder1, 4); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShoulder2, 5); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightShoulder1, 6); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightShoulder2, 7); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadUp, 8); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadDown, 9); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadLeft, 10); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadRight, 11); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Start, 12); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Select, 13); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Square, 14); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Triangle, 15); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Cross, 16); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, Circle, 17); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShock, 18); + PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightShock, 19); + return buf; +} +#undef PROCESS_BUTTON_STATE_STORE + +#define PROCESS_BUTTON_STATE_RESTORE(buf, state, field, id) case id: state->field = *buf++; break; + +uint8* CRecordDataForGame::UnPackCurrentPadValues(uint8* buf, uint8 total, CControllerState* state) +{ + for (uint8 i = 0; i < total; i++) { + switch (*buf++) { + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftStickX, 0); + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftStickY, 1); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightStickX, 2); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightStickY, 3); + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShoulder1, 4); + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShoulder2, 5); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightShoulder1, 6); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightShoulder2, 7); + PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadUp, 8); + PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadDown, 9); + PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadLeft, 10); + PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadRight, 11); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Start, 12); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Select, 13); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Square, 14); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Triangle, 15); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Cross, 16); + PROCESS_BUTTON_STATE_RESTORE(buf, state, Circle, 17); + PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShock, 18); + PROCESS_BUTTON_STATE_RESTORE(buf, state, RightShock, 19); + } + } + return buf; +} + +#undef PROCESS_BUTTON_STATE_RESTORE + +uint16 CRecordDataForGame::CalcGameChecksum(void) +{ + uint32 checksum = 0; + int i = CPools::GetPedPool()->GetSize(); + while (i--) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + checksum ^= pPed->GetModelIndex() ^ *(uint32*)&pPed->GetPosition().z ^ *(uint32*)&pPed->GetPosition().y ^ *(uint32*)&pPed->GetPosition().x; + } + i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + checksum ^= pVehicle->GetModelIndex() ^ *(uint32*)&pVehicle->GetPosition().z ^ *(uint32*)&pVehicle->GetPosition().y ^ *(uint32*)&pVehicle->GetPosition().x; + } + return checksum ^ checksum >> 16; +} + +uint8 CRecordDataForChase::Status; +int CRecordDataForChase::PositionChanges; +uint8 CRecordDataForChase::CurrentCar; +CAutomobile* CRecordDataForChase::pChaseCars[NUM_CHASE_CARS]; +uint32 CRecordDataForChase::AnimStartTime; +float CRecordDataForChase::AnimTime; +CCarStateEachFrame* CRecordDataForChase::pBaseMemForCar[NUM_CHASE_CARS]; +float CRecordDataForChase::TimeMultiplier; +int CRecordDataForChase::FId2; + +#define CHASE_SCENE_LENGTH_IN_SECONDS (80) +#define CHASE_SCENE_FRAMES_PER_SECOND (15) // skipping every second frame +#define CHASE_SCENE_FRAMES_IN_RECORDING (CHASE_SCENE_LENGTH_IN_SECONDS * CHASE_SCENE_FRAMES_PER_SECOND) +#define CHASE_SCENE_LENGTH_IN_FRAMES (CHASE_SCENE_FRAMES_IN_RECORDING * 2) + +void CRecordDataForChase::Init(void) +{ + Status = STATE_NONE; + PositionChanges = 0; + CurrentCar = 0; + for (int i = 0; i < NUM_CHASE_CARS; i++) + pChaseCars[i] = nil; + AnimStartTime = 0; +} + +void CRecordDataForChase::SaveOrRetrieveDataForThisFrame(void) +{ + switch (Status) { + case STATE_NONE: + return; + case STATE_RECORD: + { + if ((CTimer::GetFrameCounter() & 1) == 0) + StoreInfoForCar(pChaseCars[CurrentCar], &pBaseMemForCar[CurrentCar][CTimer::GetFrameCounter() / 2]); + if (CTimer::GetFrameCounter() < CHASE_SCENE_LENGTH_IN_FRAMES * 2) + return; + CFileMgr::SetDir("data\\paths"); + sprintf(gString, "chase%d.dat", CurrentCar); + int fid = CFileMgr::OpenFileForWriting(gString); + uint32 fs = CHASE_SCENE_LENGTH_IN_FRAMES * sizeof(CCarStateEachFrame); + printf("FileSize:%d\n", fs); + CFileMgr::Write(fid, (char*)pBaseMemForCar[CurrentCar], fs); + CFileMgr::CloseFile(fid); + CFileMgr::SetDir(""); + sprintf(gString, "car%d.max", CurrentCar); + int fid2 = CFileMgr::OpenFileForWriting(gString); + for (int i = 0; i < CHASE_SCENE_FRAMES_IN_RECORDING; i++) { + // WTF? Was it ever used? +#ifdef FIX_BUGS + CCarStateEachFrame* pState = pBaseMemForCar[CurrentCar]; +#else + CCarStateEachFrame* pState = (CCarStateEachFrame*)pChaseCars[CurrentCar]; +#endif + CVector right = CVector(pState->rightX, pState->rightY, pState->rightZ) / INT8_MAX; + CVector forward = CVector(pState->forwardX, pState->forwardY, pState->forwardZ) / INT8_MAX; + CVector up = CrossProduct(right, forward); + sprintf(gString, "%f %f %f\n", pState->pos.x, pState->pos.y, pState->pos.z); + CFileMgr::Write(fid2, gString, strlen(gString) - 1); + sprintf(gString, "%f %f %f\n", right.x, right.y, right.z); + CFileMgr::Write(fid2, gString, strlen(gString) - 1); + sprintf(gString, "%f %f %f\n", forward.x, forward.y, forward.z); + CFileMgr::Write(fid2, gString, strlen(gString) - 1); + sprintf(gString, "%f %f %f\n", up.x, up.y, up.z); + CFileMgr::Write(fid2, gString, strlen(gString) - 1); + } + CFileMgr::CloseFile(fid2); + } + case STATE_PLAYBACK: + case STATE_PLAYBACK_BEFORE_RECORDING: + case STATE_PLAYBACK_INIT: + break; + } +} + +struct tCoors { + CVector pos; + float angle; +}; + +// I guess developer was filling this with actual data before running the game +tCoors NewCoorsForRecordedCars[7]; + +void CRecordDataForChase::SaveOrRetrieveCarPositions(void) +{ + switch (Status) { + case STATE_NONE: + return; + case STATE_RECORD: + case STATE_PLAYBACK_BEFORE_RECORDING: + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (i != CurrentCar && CTimer::GetFrameCounter()) { + RestoreInfoForCar(pChaseCars[i], &pBaseMemForCar[i][CTimer::GetFrameCounter() / 2], false); + pChaseCars[i]->GetMatrix().UpdateRW(); + pChaseCars[i]->UpdateRwFrame(); + } + } + if (Status == STATE_PLAYBACK_BEFORE_RECORDING && CTimer::GetFrameCounter()) { + RestoreInfoForCar(pChaseCars[CurrentCar], &pBaseMemForCar[CurrentCar][CTimer::GetFrameCounter() / 2], false); + pChaseCars[CurrentCar]->GetMatrix().UpdateRW(); + pChaseCars[CurrentCar]->UpdateRwFrame(); + } + if (CPad::GetPad(0)->GetLeftShockJustDown() && CPad::GetPad(0)->GetRightShockJustDown()) { + if (!CPad::GetPad(0)->GetRightShockJustDown()) { + pChaseCars[CurrentCar]->SetPosition(NewCoorsForRecordedCars[PositionChanges].pos); + pChaseCars[CurrentCar]->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pChaseCars[CurrentCar]->GetMatrix().SetRotateZOnly(DEGTORAD(NewCoorsForRecordedCars[PositionChanges].angle)); + ++PositionChanges; + } + if (Status == STATE_PLAYBACK_BEFORE_RECORDING) { + Status = STATE_RECORD; + pChaseCars[CurrentCar]->SetStatus(STATUS_PLAYER); + } + } + break; + case STATE_PLAYBACK_INIT: + Status = STATE_PLAYBACK; + break; + case STATE_PLAYBACK: + { + TimeMultiplier += CTimer::GetTimeStepNonClippedInSeconds(); + float EndOfFrameTime = CHASE_SCENE_FRAMES_PER_SECOND * Min(CHASE_SCENE_LENGTH_IN_SECONDS, TimeMultiplier); + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (!pBaseMemForCar[i]) + continue; + if (!pChaseCars[i]) + continue; + if (EndOfFrameTime < CHASE_SCENE_FRAMES_IN_RECORDING - 1) { + int FlooredEOFTime = EndOfFrameTime; + RestoreInfoForCar(pChaseCars[i], &pBaseMemForCar[i][FlooredEOFTime], false); + CMatrix tmp; + float dp = EndOfFrameTime - FlooredEOFTime; + RestoreInfoForMatrix(tmp, &pBaseMemForCar[i][FlooredEOFTime + 1]); + pChaseCars[i]->GetRight() += (tmp.GetRight() - pChaseCars[i]->GetRight()) * dp; + pChaseCars[i]->GetForward() += (tmp.GetForward() - pChaseCars[i]->GetForward()) * dp; + pChaseCars[i]->GetUp() += (tmp.GetUp() - pChaseCars[i]->GetUp()) * dp; + pChaseCars[i]->GetMatrix().GetPosition() += (tmp.GetPosition() - pChaseCars[i]->GetPosition()) * dp; + } + else{ + RestoreInfoForCar(pChaseCars[i], &pBaseMemForCar[i][CHASE_SCENE_FRAMES_IN_RECORDING - 1], true); + if (i == 0) + pChaseCars[i]->GetMatrix().GetPosition().z += 0.2f; + } + pChaseCars[i]->GetMatrix().UpdateRW(); + pChaseCars[i]->UpdateRwFrame(); + pChaseCars[i]->RemoveAndAdd(); + } + break; + } + } +} + +void CRecordDataForChase::StoreInfoForCar(CAutomobile* pCar, CCarStateEachFrame* pState) +{ + pState->rightX = INT8_MAX * pCar->GetRight().x; + pState->rightY = INT8_MAX * pCar->GetRight().y; + pState->rightZ = INT8_MAX * pCar->GetRight().z; + pState->forwardX = INT8_MAX * pCar->GetForward().x; + pState->forwardY = INT8_MAX * pCar->GetForward().y; + pState->forwardZ = INT8_MAX * pCar->GetForward().z; + pState->pos = pCar->GetPosition(); + pState->velX = 0.5f * INT16_MAX * pCar->GetMoveSpeed().x; + pState->velY = 0.5f * INT16_MAX * pCar->GetMoveSpeed().y; + pState->velZ = 0.5f * INT16_MAX * pCar->GetMoveSpeed().z; + pState->wheel = 20 * pCar->m_fSteerAngle; + pState->gas = 100 * pCar->m_fGasPedal; + pState->brake = 100 * pCar->m_fBrakePedal; + pState->handbrake = pCar->bIsHandbrakeOn; +} + +void CRecordDataForChase::RestoreInfoForMatrix(CMatrix& matrix, CCarStateEachFrame* pState) +{ + matrix.GetRight() = CVector(pState->rightX, pState->rightY, pState->rightZ) / INT8_MAX; + matrix.GetForward() = CVector(pState->forwardX, pState->forwardY, pState->forwardZ) / INT8_MAX; + matrix.GetUp() = CrossProduct(matrix.GetRight(), matrix.GetForward()); + matrix.GetPosition() = pState->pos; +} + +void CRecordDataForChase::RestoreInfoForCar(CAutomobile* pCar, CCarStateEachFrame* pState, bool stop) +{ + CVector oldPos = pCar->GetPosition(); + RestoreInfoForMatrix(pCar->GetMatrix(), pState); + pCar->SetMoveSpeed(CVector(pState->velX, pState->velY, pState->velZ) / INT16_MAX / 0.5f); + pCar->SetTurnSpeed(0.0f, 0.0f, 0.0f); + pCar->m_fSteerAngle = pState->wheel / 20.0f; + pCar->m_fGasPedal = pState->gas / 100.0f; + pCar->m_fBrakePedal = pState->brake / 100.0f; + pCar->bIsHandbrakeOn = pState->handbrake; + if ((oldPos - pCar->GetPosition()).Magnitude() > 15.0f) { + if (pCar == pChaseCars[14]) { + pCar->m_currentColour1 = 58; + pCar->m_currentColour2 = 1; + } + else + pCar->GetModelInfo()->ChooseVehicleColour(pCar->m_currentColour1, pCar->m_currentColour2); + } + pCar->m_fHealth = Min(pCar->m_fHealth, 500.0f); + if (stop) { + pCar->m_fGasPedal = 0.0f; + pCar->m_fBrakePedal = 0.0f; + pCar->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pCar->bIsHandbrakeOn = false; + } +} + +void CRecordDataForChase::ProcessControlCars(void) +{ + if (Status != STATE_PLAYBACK) + return; + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (pChaseCars[i]) + pChaseCars[i]->ProcessControl(); + } +} + +bool CRecordDataForChase::ShouldThisPadBeLeftAlone(uint8 pad) +{ + // may be wrong + if (Status == STATE_PLAYBACK_INIT) // this is useless but ps2 def checks if it's STATE_PLAYBACK_INIT + return false; + + if (Status == STATE_RECORD) + return pad != 0; + + return false; +} + +void CRecordDataForChase::GiveUsACar(int32 mi, CVector pos, float angle, CAutomobile** ppCar, uint8 colour1, uint8 colour2) +{ + CStreaming::RequestModel(mi, STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + if (!CStreaming::HasModelLoaded(mi)) + return; + CAutomobile* pCar = new CAutomobile(mi, MISSION_VEHICLE); + pCar->SetPosition(pos); + pCar->SetStatus(STATUS_PLAYER_PLAYBACKFROMBUFFER); + pCar->GetMatrix().SetRotateZOnly(DEGTORAD(angle)); + pCar->pDriver = nil; + pCar->m_currentColour1 = colour1; + pCar->m_currentColour2 = colour2; + CWorld::Add(pCar); + *ppCar = pCar; +} + +void RemoveUnusedCollision(void) +{ + static const char* dontDeleteArray[] = { + "rd_SrRoad2A50", "rd_SrRoad2A20", "rd_CrossRda1w22", "rd_CrossRda1rw22", + "road_broadway02", "road_broadway01", "com_21way5", "com_21way50", + "cm1waycrosscom", "com_21way20", "com_21way10", "road_broadway04", + "com_rvroads52", "com_roadsrv", "com_roadkb23", "com_roadkb22" + }; + for (int i = 0; i < ARRAY_SIZE(dontDeleteArray); i++) + CModelInfo::GetModelInfo(dontDeleteArray[i], nil)->GetColModel()->level = LEVEL_GENERIC; + CModelInfo::RemoveColModelsFromOtherLevels(LEVEL_GENERIC); + for (int i = 0; i < ARRAY_SIZE(dontDeleteArray); i++) + CModelInfo::GetModelInfo(dontDeleteArray[i], nil)->GetColModel()->level = LEVEL_COMMERCIAL; +} + +void CRecordDataForChase::StartChaseScene(float startTime) +{ + char filename[28]; + SetUpCarsForChaseScene(); + Status = STATE_PLAYBACK; + AnimTime = startTime; + AnimStartTime = CTimer::GetTimeInMilliseconds(); +#ifdef NO_ISLAND_LOADING + if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_LOW) +#endif + RemoveUnusedCollision(); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + CGame::TidyUpMemory(true, true); + CStreaming::ImGonnaUseStreamingMemory(); + CFileMgr::SetDir("data\\paths"); + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (!pChaseCars[i]) { + pBaseMemForCar[i] = nil; + continue; + } + sprintf(filename, "chase%d.dat", i); + FId2 = CFileMgr::OpenFile(filename, "rb"); + if (FId2 <= 0) { + pBaseMemForCar[i] = nil; + continue; + } + pBaseMemForCar[i] = new CCarStateEachFrame[CHASE_SCENE_FRAMES_IN_RECORDING]; + for (int j = 0; j < CHASE_SCENE_FRAMES_IN_RECORDING; j++) { + CFileMgr::Read(FId2, (char*)&pBaseMemForCar[i][j], sizeof(CCarStateEachFrame)); + CFileMgr::Seek(FId2, sizeof(CCarStateEachFrame), 1); + } + CFileMgr::CloseFile(FId2); + } + CFileMgr::SetDir(""); + CStreaming::IHaveUsedStreamingMemory(); + TimeMultiplier = 0.0f; +} + +void CRecordDataForChase::CleanUpChaseScene(void) +{ + if (Status != STATE_PLAYBACK_INIT && Status != STATE_PLAYBACK) + return; + Status = STATE_NONE; + CleanUpCarsForChaseScene(); + for (int i = 0; i < NUM_CHASE_CARS; i++) { + if (pBaseMemForCar[i]) { + delete[] pBaseMemForCar[i]; + pBaseMemForCar[i] = nil; + } + } +} + +void CRecordDataForChase::SetUpCarsForChaseScene(void) +{ + GiveUsACar(MI_POLICE, CVector(273.54221f, -1167.1907f, 24.880601f), 63.0f, &pChaseCars[0], 2, 1); + GiveUsACar(MI_ENFORCER, CVector(231.1783f, -1388.8322f, 25.978201f), 90.0f, &pChaseCars[1], 2, 1); + GiveUsACar(MI_TAXI, CVector(184.3156f, -1473.251f, 25.978201f), 0.0f, &pChaseCars[4], 6, 6); + GiveUsACar(MI_CHEETAH, CVector(173.8868f, -1377.6514f, 25.978201f), 0.0f, &pChaseCars[6], 4, 5); + GiveUsACar(MI_STINGER, CVector(102.5946f, -943.93628f, 25.9781f), 270.0f, &pChaseCars[7], 53, 53); + GiveUsACar(MI_CHEETAH, CVector(-177.7157f, -862.18652f, 25.978201f), 155.0f, &pChaseCars[10], 41, 1); + GiveUsACar(MI_STINGER, CVector(-170.56979f, -889.02362f, 25.978201f), 154.0f, &pChaseCars[11], 10, 10); + GiveUsACar(MI_KURUMA, CVector(402.60809f, -917.49628f, 37.381001f), 90.0f, &pChaseCars[14], 34, 1); + GiveUsACar(MI_TAXI, CVector(-33.496201f, -938.4563f, 25.9781f), 266.0f, &pChaseCars[16], 6, 6); + GiveUsACar(MI_KURUMA, CVector(49.363098f, -987.60498f, 25.9781f), 0.0f, &pChaseCars[18], 51, 1); + GiveUsACar(MI_TAXI, CVector(179.0049f, -1154.6686f, 25.9781f), 0.0f, &pChaseCars[19], 6, 76); + GiveUsACar(MI_RUMPO, CVector(-28.9762f, -1031.3367f, 25.990601f), 242.0f, &pChaseCars[2], 1, 75); + GiveUsACar(MI_PATRIOT, CVector(114.1564f, -796.69379f, 24.978201f), 180.0f, &pChaseCars[3], 0, 0); +} + +void CRecordDataForChase::CleanUpCarsForChaseScene(void) +{ + for (int i = 0; i < NUM_CHASE_CARS; i++) + RemoveCarFromChase(i); +} + +void CRecordDataForChase::RemoveCarFromChase(int32 i) +{ + if (!pChaseCars[i]) + return; + CWorld::Remove(pChaseCars[i]); + delete pChaseCars[i]; + pChaseCars[i] = nil; +} + +CVehicle* CRecordDataForChase::TurnChaseCarIntoScriptCar(int32 i) +{ + CVehicle* pVehicle = pChaseCars[i]; + pChaseCars[i] = nil; + pVehicle->SetStatus(STATUS_PHYSICS); + return pVehicle; +} + diff --git a/src/control/Record.h b/src/control/Record.h new file mode 100644 index 0000000..6a94c40 --- /dev/null +++ b/src/control/Record.h @@ -0,0 +1,104 @@ +#pragma once + +class CAutomobile; +class CVehicle; +class CControllerState; + +class CCarStateEachFrame +{ +public: + int16 velX; + int16 velY; + int16 velZ; + int8 rightX; + int8 rightY; + int8 rightZ; + int8 forwardX; + int8 forwardY; + int8 forwardZ; + int8 wheel; + int8 gas; + int8 brake; + bool handbrake; + CVector pos; +}; + +extern char gString[256]; + +class CRecordDataForChase +{ + enum { + NUM_CHASE_CARS = 20 + }; + enum { + STATE_NONE = 0, + STATE_RECORD = 1, + STATE_PLAYBACK_INIT = 2, + STATE_PLAYBACK = 3, + STATE_PLAYBACK_BEFORE_RECORDING = 4 + }; + static uint8 Status; + static int PositionChanges; + static uint8 CurrentCar; + static CAutomobile*pChaseCars[NUM_CHASE_CARS]; + static float AnimTime; + static uint32 AnimStartTime; + static CCarStateEachFrame* pBaseMemForCar[NUM_CHASE_CARS]; + static float TimeMultiplier; + static int FId2; +public: + + static bool IsRecording(void) { return Status == STATE_RECORD; } + + static void Init(void); + static void SaveOrRetrieveDataForThisFrame(void); + static void SaveOrRetrieveCarPositions(void); + static void StoreInfoForCar(CAutomobile*, CCarStateEachFrame*); + static void RestoreInfoForMatrix(CMatrix&, CCarStateEachFrame*); + static void RestoreInfoForCar(CAutomobile*, CCarStateEachFrame*, bool); + static void ProcessControlCars(void); + static bool ShouldThisPadBeLeftAlone(uint8 pad); + static void GiveUsACar(int32, CVector, float, CAutomobile**, uint8, uint8); + static void StartChaseScene(float); + static void CleanUpChaseScene(void); + static void SetUpCarsForChaseScene(void); + static void CleanUpCarsForChaseScene(void); + static void RemoveCarFromChase(int32); + static CVehicle* TurnChaseCarIntoScriptCar(int32); + +}; + +struct tGameBuffer +{ + float m_fTimeStep; + uint32 m_nTimeInMilliseconds; + uint8 m_nSizeOfPads[2]; + uint16 m_nChecksum; + uint8 m_ControllerBuffer[116]; +}; + +class CRecordDataForGame +{ + enum { + STATE_NONE = 0, + STATE_RECORD = 1, + STATE_PLAYBACK = 2, + }; + static uint16 RecordingState; + static uint8* pDataBuffer; + static uint8* pDataBufferPointer; + static int FId; + static tGameBuffer pDataBufferForFrame; + +public: + static bool IsRecording() { return RecordingState == STATE_RECORD; } + static bool IsPlayingBack() { return RecordingState == STATE_PLAYBACK; } + + static void SaveOrRetrieveDataForThisFrame(void); + static void Init(void); + +private: + static uint16 CalcGameChecksum(void); + static uint8* PackCurrentPadValues(uint8*, CControllerState*, CControllerState*); + static uint8* UnPackCurrentPadValues(uint8*, uint8, CControllerState*); +}; diff --git a/src/control/Remote.cpp b/src/control/Remote.cpp new file mode 100644 index 0000000..904e902 --- /dev/null +++ b/src/control/Remote.cpp @@ -0,0 +1,51 @@ +#include "common.h" + +#include "Automobile.h" +#include "CarCtrl.h" +#include "Camera.h" +#include "Remote.h" +#include "Timer.h" +#include "World.h" +#include "PlayerInfo.h" +#include "Vehicle.h" + +void +CRemote::GivePlayerRemoteControlledCar(float x, float y, float z, float rot, uint16 model) +{ + CAutomobile *car = new CAutomobile(model, MISSION_VEHICLE); + bool found; + + z = car->GetDistanceFromCentreOfMassToBaseOfModel() + CWorld::FindGroundZFor3DCoord(x, y, z + 2.0f, &found); + + car->GetMatrix().SetRotateZOnly(rot); + car->SetPosition(x, y, z); + car->SetStatus(STATUS_PLAYER_REMOTE); + car->bIsLocked = true; + + CCarCtrl::JoinCarWithRoadSystem(car); + car->AutoPilot.m_nCarMission = MISSION_NONE; + car->AutoPilot.m_nTempAction = TEMPACT_NONE; + car->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + car->AutoPilot.m_nCruiseSpeed = car->AutoPilot.m_fMaxTrafficSpeed = 9.0f; + car->AutoPilot.m_nNextLane = car->AutoPilot.m_nCurrentLane = 0; + car->bEngineOn = true; + CWorld::Add(car); + if (FindPlayerVehicle() != nil) + FindPlayerVehicle()->SetStatus(STATUS_PLAYER_DISABLED); + + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle = car; + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->RegisterReference((CEntity**)&CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle); + TheCamera.TakeControl(car, CCam::MODE_BEHINDCAR, INTERPOLATION, CAMCONTROL_SCRIPT); +} + +void +CRemote::TakeRemoteControlledCarFromPlayer(void) +{ + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->VehicleCreatedBy = RANDOM_VEHICLE; + CCarCtrl::NumMissionCars--; + CCarCtrl::NumRandomCars++; + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->bIsLocked = false; + CWorld::Players[CWorld::PlayerInFocus].m_nTimeLostRemoteCar = CTimer::GetTimeInMilliseconds(); + CWorld::Players[CWorld::PlayerInFocus].m_bInRemoteMode = true; + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->bRemoveFromWorld = true; +} diff --git a/src/control/Remote.h b/src/control/Remote.h new file mode 100644 index 0000000..5e47458 --- /dev/null +++ b/src/control/Remote.h @@ -0,0 +1,8 @@ +#pragma once + +class CRemote +{ +public: + static void GivePlayerRemoteControlledCar(float, float, float, float, uint16); + static void TakeRemoteControlledCarFromPlayer(void); +}; diff --git a/src/control/Replay.cpp b/src/control/Replay.cpp new file mode 100644 index 0000000..b9b5530 --- /dev/null +++ b/src/control/Replay.cpp @@ -0,0 +1,1635 @@ +#include "common.h" +#ifdef GTA_REPLAY +#include "AnimBlendAssociation.h" +#include "Boat.h" +#include "SpecialFX.h" +#include "CarCtrl.h" +#include "CivilianPed.h" +#include "Wanted.h" +#include "Clock.h" +#include "DMAudio.h" +#include "Draw.h" +#include "FileMgr.h" +#ifdef FIX_BUGS +#include "Fire.h" +#include "Garages.h" +#endif +#include "Heli.h" +#include "main.h" +#include "Matrix.h" +#include "ModelIndices.h" +#include "ModelInfo.h" +#include "Object.h" +#include "Pad.h" +#include "Phones.h" +#include "Pickups.h" +#include "Plane.h" +#include "Pools.h" +#include "Population.h" +#ifdef FIX_BUGS +#include "Projectile.h" +#include "ProjectileInfo.h" +#endif +#include "Replay.h" +#include "References.h" +#include "Pools.h" +#include "RpAnimBlend.h" +#include "RwHelper.h" +#include "CutsceneMgr.h" +#include "Skidmarks.h" +#include "Streaming.h" +#include "Timer.h" +#include "Train.h" +#include "Weather.h" +#include "Zones.h" +#include "Font.h" +#include "Text.h" +#include "Camera.h" +#include "Radar.h" + +uint8 CReplay::Mode; +CAddressInReplayBuffer CReplay::Record; +CAddressInReplayBuffer CReplay::Playback; +uint8 *CReplay::pBuf0; +CAutomobile *CReplay::pBuf1; +uint8 *CReplay::pBuf2; +CPlayerPed *CReplay::pBuf3; +uint8 *CReplay::pBuf4; +CCutsceneHead *CReplay::pBuf5; +uint8 *CReplay::pBuf6; +CPtrNode *CReplay::pBuf7; +uint8 *CReplay::pBuf8; +CEntryInfoNode *CReplay::pBuf9; +uint8 *CReplay::pBuf10; +CDummyPed *CReplay::pBuf11; +uint8 *CReplay::pRadarBlips; +uint8 *CReplay::pStoredCam; +uint8 *CReplay::pWorld1; +CReference *CReplay::pEmptyReferences; +CStoredDetailedAnimationState *CReplay::pPedAnims; +uint8 *CReplay::pPickups; +uint8 *CReplay::pReferences; +uint8 CReplay::BufferStatus[NUM_REPLAYBUFFERS]; +uint8 CReplay::Buffers[NUM_REPLAYBUFFERS][REPLAYBUFFERSIZE]; +bool CReplay::bPlayingBackFromFile; +bool CReplay::bReplayEnabled = true; +uint32 CReplay::SlowMotion; +uint32 CReplay::FramesActiveLookAroundCam; +bool CReplay::bDoLoadSceneWhenDone; +CPtrNode* CReplay::WorldPtrList; +CPtrNode* CReplay::BigBuildingPtrList; +CWanted CReplay::PlayerWanted; +CPlayerInfo CReplay::PlayerInfo; +uint32 CReplay::Time1; +uint32 CReplay::Time2; +uint32 CReplay::Time3; +uint32 CReplay::Time4; +uint32 CReplay::Frame; +uint8 CReplay::ClockHours; +uint8 CReplay::ClockMinutes; +uint16 CReplay::OldWeatherType; +uint16 CReplay::NewWeatherType; +float CReplay::WeatherInterpolationValue; +float CReplay::TimeStepNonClipped; +float CReplay::TimeStep; +float CReplay::TimeScale; +float CReplay::CameraFixedX; +float CReplay::CameraFixedY; +float CReplay::CameraFixedZ; +int32 CReplay::OldRadioStation; +int8 CReplay::CameraMode; +bool CReplay::bAllowLookAroundCam; +float CReplay::LoadSceneX; +float CReplay::LoadSceneY; +float CReplay::LoadSceneZ; +float CReplay::CameraFocusX; +float CReplay::CameraFocusY; +float CReplay::CameraFocusZ; +bool CReplay::bPlayerInRCBuggy; +float CReplay::fDistanceLookAroundCam; +float CReplay::fBetaAngleLookAroundCam; +float CReplay::fAlphaAngleLookAroundCam; +#ifdef FIX_BUGS +uint8* CReplay::pGarages; +CFire* CReplay::FireArray; +uint32 CReplay::NumOfFires; +uint8* CReplay::paProjectileInfo; +uint8* CReplay::paProjectiles; +int CReplay::nHandleOfPlayerPed[NUMPLAYERS]; +#endif + +static void(*CBArray[])(CAnimBlendAssociation*, void*) = +{ + nil, &CPed::PedGetupCB, &CPed::PedStaggerCB, &CPed::PedEvadeCB, &CPed::FinishDieAnimCB, + &CPed::FinishedWaitCB, &CPed::FinishLaunchCB, &CPed::FinishHitHeadCB, &CPed::PedAnimGetInCB, &CPed::PedAnimDoorOpenCB, + &CPed::PedAnimPullPedOutCB, &CPed::PedAnimDoorCloseCB, &CPed::PedSetInCarCB, &CPed::PedSetOutCarCB, &CPed::PedAnimAlignCB, + &CPed::PedSetDraggedOutCarCB, &CPed::PedAnimStepOutCarCB, &CPed::PedSetInTrainCB, &CPed::PedSetOutTrainCB, &CPed::FinishedAttackCB, + &CPed::FinishFightMoveCB, &PhonePutDownCB, &PhonePickUpCB, &CPed::PedAnimDoorCloseRollingCB, &CPed::FinishJumpCB, + &CPed::PedLandCB, &FinishFuckUCB, &CPed::RestoreHeadingRateCB, &CPed::PedSetQuickDraggedOutCarPositionCB, &CPed::PedSetDraggedOutCarPositionCB +}; + +static uint8 FindCBFunctionID(void(*f)(CAnimBlendAssociation*, void*)) +{ + for (int i = 0; i < sizeof(CBArray) / sizeof(*CBArray); i++){ + if (CBArray[i] == f) + return i; + } + + return 0; +} + +static void(*FindCBFunction(uint8 id))(CAnimBlendAssociation*, void*) +{ + return CBArray[id]; +} + +static void ApplyPanelDamageToCar(uint32 panels, CAutomobile* vehicle, bool flying) +{ + if(vehicle->Damage.GetPanelStatus(VEHPANEL_FRONT_LEFT) != CDamageManager::GetPanelStatus(panels, VEHPANEL_FRONT_LEFT)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_FRONT_LEFT, CDamageManager::GetPanelStatus(panels, VEHPANEL_FRONT_LEFT)); + vehicle->SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHPANEL_FRONT_RIGHT) != CDamageManager::GetPanelStatus(panels, VEHPANEL_FRONT_RIGHT)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_FRONT_RIGHT, CDamageManager::GetPanelStatus(panels, VEHPANEL_FRONT_RIGHT)); + vehicle->SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHPANEL_REAR_LEFT) != CDamageManager::GetPanelStatus(panels, VEHPANEL_REAR_LEFT)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_REAR_LEFT, CDamageManager::GetPanelStatus(panels, VEHPANEL_REAR_LEFT)); + vehicle->SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHPANEL_REAR_RIGHT) != CDamageManager::GetPanelStatus(panels, VEHPANEL_REAR_RIGHT)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_REAR_RIGHT, CDamageManager::GetPanelStatus(panels, VEHPANEL_REAR_RIGHT)); + vehicle->SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHPANEL_WINDSCREEN) != CDamageManager::GetPanelStatus(panels, VEHPANEL_WINDSCREEN)){ + vehicle->Damage.SetPanelStatus(VEHPANEL_WINDSCREEN, CDamageManager::GetPanelStatus(panels, VEHPANEL_WINDSCREEN)); + vehicle->SetPanelDamage(CAR_WINDSCREEN, VEHPANEL_WINDSCREEN, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHBUMPER_FRONT) != CDamageManager::GetPanelStatus(panels, VEHBUMPER_FRONT)){ + vehicle->Damage.SetPanelStatus(VEHBUMPER_FRONT, CDamageManager::GetPanelStatus(panels, VEHBUMPER_FRONT)); + vehicle->SetPanelDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT, flying); + } + if(vehicle->Damage.GetPanelStatus(VEHBUMPER_REAR) != CDamageManager::GetPanelStatus(panels, VEHBUMPER_REAR)){ + vehicle->Damage.SetPanelStatus(VEHBUMPER_REAR, CDamageManager::GetPanelStatus(panels, VEHBUMPER_REAR)); + vehicle->SetPanelDamage(CAR_BUMP_REAR, VEHBUMPER_REAR, flying); + } +} + +void PrintElementsInPtrList(void) +{ + for (CPtrNode* node = CWorld::GetBigBuildingList(LEVEL_GENERIC).first; node; node = node->next) { + /* Most likely debug print was present here */ + } +} + +void CReplay::Init(void) +{ + pBuf0 = nil; + pBuf1 = nil; + pBuf2 = nil; + pBuf3 = nil; + pBuf4 = nil; + pBuf5 = nil; + pBuf6 = nil; + pBuf7 = nil; + pBuf8 = nil; + pBuf9 = nil; + pBuf10 = nil; + pBuf11 = nil; + pRadarBlips = nil; + pStoredCam = nil; + pWorld1 = nil; + pEmptyReferences = nil; + pPedAnims = nil; + pPickups = nil; + pReferences = nil; + Mode = MODE_RECORD; + Playback.m_nOffset = 0; + Playback.m_pBase = nil; + Playback.m_bSlot = 0; + Record.m_nOffset = 0; + Record.m_pBase = nil; + Record.m_bSlot = 0; + for (int i = 0; i < NUM_REPLAYBUFFERS; i++) + BufferStatus[i] = REPLAYBUFFER_UNUSED; + Record.m_bSlot = 0; + Record.m_pBase = Buffers[0]; + BufferStatus[0] = REPLAYBUFFER_RECORD; + Buffers[0][Record.m_nOffset] = REPLAYPACKET_END; + bPlayingBackFromFile = false; + bReplayEnabled = true; + SlowMotion = 1; + FramesActiveLookAroundCam = 0; + bDoLoadSceneWhenDone = false; +} + +void CReplay::DisableReplays(void) +{ + bReplayEnabled = false; +} + +void CReplay::EnableReplays(void) +{ + bReplayEnabled = true; +} + +void PlayReplayFromHD(void); +void CReplay::Update(void) +{ + if (CCutsceneMgr::IsCutsceneProcessing() || CTimer::GetIsPaused()) + return; + switch (Mode){ + case MODE_RECORD: + RecordThisFrame(); + break; + case MODE_PLAYBACK: + PlaybackThisFrame(); + break; + } + if (CDraw::FadeValue || !bReplayEnabled) + return; + if (Mode == MODE_PLAYBACK){ + if (CPad::GetPad(0)->GetFJustDown(0)) + FinishPlayback(); + } + else if (Mode == MODE_RECORD){ + if (CPad::GetPad(0)->GetFJustDown(0)) + TriggerPlayback(REPLAYCAMMODE_ASSTORED, 0.0f, 0.0f, 0.0f, false); + if (CPad::GetPad(0)->GetFJustDown(1)) + SaveReplayToHD(); + if (CPad::GetPad(0)->GetFJustDown(2)) + PlayReplayFromHD(); +#ifdef USE_BETA_REPLAY_MODE + if (CPad::GetPad(0)->GetFJustDown(3)) + TriggerPlaybackLastCoupleOfSeconds(5000, REPLAYCAMMODE_TOPDOWN, 0.0f, 0.0f, 0.0f, 4); +#endif + } +} + +void CReplay::RecordThisFrame(void) +{ +#ifdef FIX_REPLAY_BUGS + uint32 memory_required = sizeof(tGeneralPacket) + sizeof(tClockPacket) + sizeof(tWeatherPacket) + sizeof(tTimerPacket); + CVehiclePool* vehiclesT = CPools::GetVehiclePool(); + for (int i = 0; i < vehiclesT->GetSize(); i++) { + CVehicle* v = vehiclesT->GetSlot(i); + if (v && v->m_rwObject && v->GetModelIndex() != MI_AIRTRAIN && v->GetModelIndex() != MI_TRAIN) + memory_required += sizeof(tVehicleUpdatePacket); + } + CPedPool* pedsT = CPools::GetPedPool(); + for (int i = 0; i < pedsT->GetSize(); i++) { + CPed* p = pedsT->GetSlot(i); + if (!p || !p->m_rwObject) + continue; + if (!p->bHasAlreadyBeenRecorded) { + memory_required += sizeof(tPedHeaderPacket); + } + memory_required += sizeof(tPedUpdatePacket); + } + for (uint8 i = 0; i < NUMBULLETTRACES; i++) { + if (!CBulletTraces::aTraces[i].m_bInUse) + continue; + memory_required += sizeof(tBulletTracePacket); + } + memory_required += sizeof(tEndOfFramePacket) + 1; // 1 for Record.m_pBase[Record.m_nOffset] = REPLAYPACKET_END; + if (Record.m_nOffset + memory_required > REPLAYBUFFERSIZE) { + Record.m_pBase[Record.m_nOffset] = REPLAYPACKET_END; + BufferStatus[Record.m_bSlot] = REPLAYBUFFER_PLAYBACK; + Record.m_bSlot = (Record.m_bSlot + 1) % NUM_REPLAYBUFFERS; + BufferStatus[Record.m_bSlot] = REPLAYBUFFER_RECORD; + Record.m_pBase = Buffers[Record.m_bSlot]; + Record.m_nOffset = 0; + *Record.m_pBase = REPLAYPACKET_END; + MarkEverythingAsNew(); + } +#endif + tGeneralPacket* general = (tGeneralPacket*)&Record.m_pBase[Record.m_nOffset]; + general->type = REPLAYPACKET_GENERAL; + general->camera_pos.CopyOnlyMatrix(TheCamera.GetMatrix()); + general->player_pos = FindPlayerCoors(); + general->in_rcvehicle = CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle ? true : false; + Record.m_nOffset += sizeof(*general); + tClockPacket* clock = (tClockPacket*)&Record.m_pBase[Record.m_nOffset]; + clock->type = REPLAYPACKET_CLOCK; + clock->hours = CClock::GetHours(); + clock->minutes = CClock::GetMinutes(); + Record.m_nOffset += sizeof(*clock); + tWeatherPacket* weather = (tWeatherPacket*)&Record.m_pBase[Record.m_nOffset]; + weather->type = REPLAYPACKET_WEATHER; + weather->old_weather = CWeather::OldWeatherType; + weather->new_weather = CWeather::NewWeatherType; + weather->interpolation = CWeather::InterpolationValue; + Record.m_nOffset += sizeof(*weather); + tTimerPacket* timer = (tTimerPacket*)&Record.m_pBase[Record.m_nOffset]; + timer->type = REPLAYPACKET_TIMER; + timer->timer = CTimer::GetTimeInMilliseconds(); + Record.m_nOffset += sizeof(*timer); + CVehiclePool* vehicles = CPools::GetVehiclePool(); + for (int i = 0; i < vehicles->GetSize(); i++){ + CVehicle* v = vehicles->GetSlot(i); + if (v && v->m_rwObject && v->GetModelIndex() != MI_AIRTRAIN && v->GetModelIndex() != MI_TRAIN) + StoreCarUpdate(v, i); + } + CPedPool* peds = CPools::GetPedPool(); + for (int i = 0; i < peds->GetSize(); i++) { + CPed* p = peds->GetSlot(i); + if (!p || !p->m_rwObject) + continue; + if (!p->bHasAlreadyBeenRecorded){ + tPedHeaderPacket* ph = (tPedHeaderPacket*)&Record.m_pBase[Record.m_nOffset]; + ph->type = REPLAYPACKET_PED_HEADER; + ph->index = i; + ph->mi = p->GetModelIndex(); + ph->pedtype = p->m_nPedType; + Record.m_nOffset += sizeof(*ph); + p->bHasAlreadyBeenRecorded = true; + } + StorePedUpdate(p, i); + } + for (uint8 i = 0; i < NUMBULLETTRACES; i++){ + if (!CBulletTraces::aTraces[i].m_bInUse) + continue; + tBulletTracePacket* bt = (tBulletTracePacket*)&Record.m_pBase[Record.m_nOffset]; + bt->type = REPLAYPACKET_BULLET_TRACES; + bt->index = i; + bt->frames = CBulletTraces::aTraces[i].m_framesInUse; + bt->lifetime = CBulletTraces::aTraces[i].m_lifeTime; + bt->inf = CBulletTraces::aTraces[i].m_vecCurrentPos; + bt->sup = CBulletTraces::aTraces[i].m_vecTargetPos; + Record.m_nOffset += sizeof(*bt); + } + tEndOfFramePacket* eof = (tEndOfFramePacket*)&Record.m_pBase[Record.m_nOffset]; + eof->type = REPLAYPACKET_ENDOFFRAME; + Record.m_nOffset += sizeof(*eof); + Record.m_pBase[Record.m_nOffset] = REPLAYPACKET_END; +#ifndef FIX_REPLAY_BUGS + if (Record.m_nOffset <= REPLAYBUFFERSIZE - 3000){ + /* Unsafe assumption which can cause buffer overflow + * if size of next frame exceeds 3000 bytes. + * Most notably it causes various timecyc errors. */ + return; + } + BufferStatus[Record.m_bSlot] = REPLAYBUFFER_PLAYBACK; + Record.m_bSlot = (Record.m_bSlot + 1) % NUM_REPLAYBUFFERS; + BufferStatus[Record.m_bSlot] = REPLAYBUFFER_RECORD; + Record.m_pBase = Buffers[Record.m_bSlot]; + Record.m_nOffset = 0; + *Record.m_pBase = REPLAYPACKET_END; + MarkEverythingAsNew(); +#endif +} + +void CReplay::StorePedUpdate(CPed *ped, int id) +{ + tPedUpdatePacket* pp = (tPedUpdatePacket*)&Record.m_pBase[Record.m_nOffset]; + pp->type = REPLAYPACKET_PED_UPDATE; + pp->index = id; + pp->heading = 128.0f / PI * ped->m_fRotationCur; + pp->matrix.CompressFromFullMatrix(ped->GetMatrix()); + pp->assoc_group_id = ped->m_animGroup; + /* Would be more sane to use GetJustIndex(ped->m_pMyVehicle) in following assignment */ + if (ped->InVehicle()) + pp->vehicle_index = (CPools::GetVehiclePool()->GetIndex(ped->m_pMyVehicle) >> 8) + 1; + else + pp->vehicle_index = 0; + pp->weapon_model = ped->m_wepModelID; + StorePedAnimation(ped, &pp->anim_state); + Record.m_nOffset += sizeof(tPedUpdatePacket); +} + +void CReplay::StorePedAnimation(CPed *ped, CStoredAnimationState *state) +{ + CAnimBlendAssociation* second; + float blend_amount; + CAnimBlendAssociation* main = RpAnimBlendClumpGetMainAssociation((RpClump*)ped->m_rwObject, &second, &blend_amount); + if (main){ + state->animId = main->animId; + state->time = 255.0f / 4.0f * Clamp(main->currentTime, 0.0f, 4.0f); + state->speed = 255.0f / 3.0f * Clamp(main->speed, 0.0f, 3.0f); + }else{ + state->animId = 3; + state->time = 0; + state->speed = 85; + } + if (second) { + state->secAnimId = second->animId; + state->secTime = 255.0f / 4.0f * Clamp(second->currentTime, 0.0f, 4.0f); + state->secSpeed = 255.0f / 3.0f * Clamp(second->speed, 0.0f, 3.0f); + state->blendAmount = 255.0f / 2.0f * Clamp(blend_amount, 0.0f, 2.0f); + }else{ + state->secAnimId = 0; + state->secTime = 0; + state->secSpeed = 0; + state->blendAmount = 0; + } + CAnimBlendAssociation* partial = RpAnimBlendClumpGetMainPartialAssociation((RpClump*)ped->m_rwObject); + if (partial) { + state->partAnimId = partial->animId; + state->partAnimTime = 255.0f / 4.0f * Clamp(partial->currentTime, 0.0f, 4.0f); + state->partAnimSpeed = 255.0f / 3.0f * Clamp(partial->speed, 0.0f, 3.0f); + state->partBlendAmount = 255.0f / 2.0f * Clamp(partial->blendAmount, 0.0f, 2.0f); + }else{ + state->partAnimId = 0; + state->partAnimTime = 0; + state->partAnimSpeed = 0; + state->partBlendAmount = 0; + } +} + +void CReplay::StoreDetailedPedAnimation(CPed *ped, CStoredDetailedAnimationState *state) +{ + for (int i = 0; i < NUM_MAIN_ANIMS_IN_REPLAY; i++){ + CAnimBlendAssociation* assoc = RpAnimBlendClumpGetMainAssociation_N((RpClump*)ped->m_rwObject, i); + if (assoc){ + state->aAnimId[i] = assoc->animId; + state->aCurTime[i] = 255.0f / 4.0f * Clamp(assoc->currentTime, 0.0f, 4.0f); + state->aSpeed[i] = 255.0f / 3.0f * Clamp(assoc->speed, 0.0f, 3.0f); + state->aBlendAmount[i] = 255.0f / 2.0f * Clamp(assoc->blendAmount, 0.0f, 2.0f); +#ifdef FIX_REPLAY_BUGS + state->aBlendDelta[i] = 127.0f / 32.0f * Clamp(assoc->blendDelta, -16.0f, 16.0f); +#endif + state->aFlags[i] = assoc->flags; + if (assoc->callbackType == CAnimBlendAssociation::CB_FINISH || assoc->callbackType == CAnimBlendAssociation::CB_DELETE) { + state->aFunctionCallbackID[i] = FindCBFunctionID(assoc->callback); + if (assoc->callbackType == CAnimBlendAssociation::CB_FINISH) + state->aFunctionCallbackID[i] |= 0x80; + }else{ + state->aFunctionCallbackID[i] = 0; + } + }else{ + state->aAnimId[i] = ANIM_STD_NUM; + state->aCurTime[i] = 0; + state->aSpeed[i] = 85; + state->aFunctionCallbackID[i] = 0; + state->aFlags[i] = 0; + } + } + for (int i = 0; i < NUM_PARTIAL_ANIMS_IN_REPLAY; i++) { + CAnimBlendAssociation* assoc = RpAnimBlendClumpGetMainPartialAssociation_N((RpClump*)ped->m_rwObject, i); + if (assoc) { + state->aAnimId2[i] = assoc->animId; + state->aCurTime2[i] = 255.0f / 4.0f * Clamp(assoc->currentTime, 0.0f, 4.0f); + state->aSpeed2[i] = 255.0f / 3.0f * Clamp(assoc->speed, 0.0f, 3.0f); + state->aBlendAmount2[i] = 255.0f / 2.0f * Clamp(assoc->blendAmount, 0.0f, 2.0f); +#ifdef FIX_REPLAY_BUGS + state->aBlendDelta2[i] = 127.0f / 16.0f * Clamp(assoc->blendDelta, -16.0f, 16.0f); +#endif + state->aFlags2[i] = assoc->flags; + if (assoc->callbackType == CAnimBlendAssociation::CB_FINISH || assoc->callbackType == CAnimBlendAssociation::CB_DELETE) { + state->aFunctionCallbackID2[i] = FindCBFunctionID(assoc->callback); + if (assoc->callbackType == CAnimBlendAssociation::CB_FINISH) + state->aFunctionCallbackID2[i] |= 0x80; + }else{ + state->aFunctionCallbackID2[i] = 0; + } + } + else { + state->aAnimId2[i] = ANIM_STD_NUM; + state->aCurTime2[i] = 0; + state->aSpeed2[i] = 85; + state->aFunctionCallbackID2[i] = 0; + state->aFlags2[i] = 0; + } + } +} + +void CReplay::ProcessPedUpdate(CPed *ped, float interpolation, CAddressInReplayBuffer *buffer) +{ + tPedUpdatePacket *pp = (tPedUpdatePacket*)&buffer->m_pBase[buffer->m_nOffset]; + if (!ped){ + printf("Replay:Ped wasn't there\n"); + buffer->m_nOffset += sizeof(tPedUpdatePacket); + return; + } + ped->m_fRotationCur = pp->heading * PI / 128.0f; + ped->m_fRotationDest = pp->heading * PI / 128.0f; + CMatrix ped_matrix; + pp->matrix.DecompressIntoFullMatrix(ped_matrix); + ped->GetMatrix() = ped->GetMatrix() * CMatrix(1.0f - interpolation); + ped->GetMatrix().GetPosition() *= (1.0f - interpolation); + ped->GetMatrix() += CMatrix(interpolation) * ped_matrix; + if (pp->vehicle_index) { + ped->m_pMyVehicle = CPools::GetVehiclePool()->GetSlot(pp->vehicle_index - 1); + ped->bInVehicle = pp->vehicle_index; + } + else { + ped->m_pMyVehicle = nil; + ped->bInVehicle = false; + } + if (pp->assoc_group_id != ped->m_animGroup) { + ped->m_animGroup = (AssocGroupId)pp->assoc_group_id; + if (ped == FindPlayerPed()) + ((CPlayerPed*)ped)->ReApplyMoveAnims(); + } + RetrievePedAnimation(ped, &pp->anim_state); + ped->RemoveWeaponModel(-1); + if (pp->weapon_model != (uint8)-1) + ped->AddWeaponModel(pp->weapon_model); + CWorld::Remove(ped); + CWorld::Add(ped); + buffer->m_nOffset += sizeof(tPedUpdatePacket); +} + +void CReplay::RetrievePedAnimation(CPed *ped, CStoredAnimationState *state) +{ + CAnimBlendAssociation* anim1 = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, + (state->animId > 3) ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->animId, 100.0f); + anim1->SetCurrentTime(state->time * 4.0f / 255.0f); + anim1->speed = state->speed * 3.0f / 255.0f; + anim1->SetBlend(1.0f, 1.0f); + anim1->callbackType = CAnimBlendAssociation::CB_NONE; + if (state->blendAmount && state->secAnimId){ + float time = state->secTime * 4.0f / 255.0f; + float speed = state->secSpeed * 3.0f / 255.0f; + float blend = state->blendAmount * 2.0f / 255.0f; + CAnimBlendAssociation* anim2 = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, + (state->secAnimId > 3) ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->secAnimId, 100.0f); + anim2->SetCurrentTime(time); + anim2->speed = speed; + anim2->SetBlend(blend, 1.0f); + anim2->callbackType = CAnimBlendAssociation::CB_NONE; + } + RpAnimBlendClumpRemoveAssociations((RpClump*)ped->m_rwObject, 0x10); + if (state->partAnimId){ + float time = state->partAnimTime * 4.0f / 255.0f; + float speed = state->partAnimSpeed * 3.0f / 255.0f; + float blend = state->partBlendAmount * 2.0f / 255.0f; + if (blend > 0.0f && state->partAnimId != ANIM_STD_IDLE){ + CAnimBlendAssociation* anim3 = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, ASSOCGRP_STD, (AnimationId)state->partAnimId, 1000.0f); + anim3->SetCurrentTime(time); + anim3->speed = speed; + anim3->SetBlend(blend, 0.0f); + } + } +} + +void CReplay::RetrieveDetailedPedAnimation(CPed *ped, CStoredDetailedAnimationState *state) +{ +#ifdef FIX_REPLAY_BUGS + CAnimBlendAssociation* assoc; + for (int i = 0; ((assoc = RpAnimBlendClumpGetMainAssociation_N(ped->GetClump(), i))); i++) + assoc->SetBlend(0.0f, -1.0f); + for (int i = 0; ((assoc = RpAnimBlendClumpGetMainPartialAssociation_N(ped->GetClump(), i))); i++) + assoc->SetBlend(0.0f, -1.0f); +#endif + for (int i = 0; i < NUM_MAIN_ANIMS_IN_REPLAY; i++) { + if (state->aAnimId[i] == ANIM_STD_NUM) + continue; +#ifdef FIX_REPLAY_BUGS + CAnimBlendAssociation* anim = CAnimManager::AddAnimation(ped->GetClump(), + state->aAnimId[i] > 3 ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->aAnimId[i]); +#else + CAnimBlendAssociation* anim = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, + state->aAnimId[i] > 3 ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->aAnimId[i], 100.0f); +#endif + anim->SetCurrentTime(state->aCurTime[i] * 4.0f / 255.0f); + anim->speed = state->aSpeed[i] * 3.0f / 255.0f; +#ifdef FIX_REPLAY_BUGS + anim->SetBlend(state->aBlendAmount[i] * 2.0f / 255.0f, state->aBlendDelta[i] * 16.0f / 127.0f); +#else + anim->SetBlend(state->aBlendAmount[i], 1.0f); +#endif + anim->flags = state->aFlags[i]; + uint8 callback = state->aFunctionCallbackID[i]; + if (!callback) + anim->callbackType = CAnimBlendAssociation::CB_NONE; + else if (callback & 0x80) + anim->SetFinishCallback(FindCBFunction(callback & 0x7F), ped); + else + anim->SetDeleteCallback(FindCBFunction(callback & 0x7F), ped); + } + for (int i = 0; i < NUM_PARTIAL_ANIMS_IN_REPLAY; i++) { + if (state->aAnimId2[i] == ANIM_STD_NUM) + continue; +#ifdef FIX_REPLAY_BUGS + CAnimBlendAssociation* anim = CAnimManager::AddAnimation(ped->GetClump(), + state->aAnimId2[i] > 3 ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->aAnimId2[i]); +#else + CAnimBlendAssociation* anim = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, + state->aAnimId2[i] > 3 ? ASSOCGRP_STD : ped->m_animGroup, + (AnimationId)state->aAnimId2[i], 100.0f); +#endif + anim->SetCurrentTime(state->aCurTime2[i] * 4.0f / 255.0f); + anim->speed = state->aSpeed2[i] * 3.0f / 255.0f; +#ifdef FIX_REPLAY_BUGS + anim->SetBlend(state->aBlendAmount2[i] * 2.0f / 255.0f, state->aBlendDelta2[i] * 16.0f / 127.0f); +#else + anim->SetBlend(state->aBlendAmount2[i], 1.0f); +#endif + anim->flags = state->aFlags2[i]; + uint8 callback = state->aFunctionCallbackID2[i]; + if (!callback) + anim->callbackType = CAnimBlendAssociation::CB_NONE; + else if (callback & 0x80) + anim->SetFinishCallback(FindCBFunction(callback & 0x7F), ped); + else + anim->SetDeleteCallback(FindCBFunction(callback & 0x7F), ped); + } +} + +void CReplay::PlaybackThisFrame(void) +{ + static int FrameSloMo = 0; + CAddressInReplayBuffer buf = Playback; + if (PlayBackThisFrameInterpolation(&buf, 1.0f, nil)){ + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); + return; + } + if (FrameSloMo){ + CAddressInReplayBuffer buf_sm = buf; + if (PlayBackThisFrameInterpolation(&buf_sm, FrameSloMo * 1.0f / SlowMotion, nil)){ + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); + return; + } + } + FrameSloMo = (FrameSloMo + 1) % SlowMotion; + if (FrameSloMo == 0) + Playback = buf; + ProcessLookAroundCam(); + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); +} + +// next two functions are only found in mobile version +// most likely they were optimized out for being unused + +void CReplay::TriggerPlaybackLastCoupleOfSeconds(uint32 start, uint8 cam_mode, float cam_x, float cam_y, float cam_z, uint32 slomo) +{ + if (Mode != MODE_RECORD) + return; + TriggerPlayback(cam_mode, cam_x, cam_y, cam_z, true); + SlowMotion = slomo; + bAllowLookAroundCam = false; + if (!FastForwardToTime(CTimer::GetTimeInMilliseconds() - start)) + Mode = MODE_RECORD; +} + +bool CReplay::FastForwardToTime(uint32 start) +{ + uint32 timer = 0; + while (start > timer) + if (PlayBackThisFrameInterpolation(&Playback, 1.0f, &timer)) + return false; + return true; +} + +void CReplay::StoreCarUpdate(CVehicle *vehicle, int id) +{ + tVehicleUpdatePacket* vp = (tVehicleUpdatePacket*)&Record.m_pBase[Record.m_nOffset]; + vp->type = REPLAYPACKET_VEHICLE; + vp->index = id; + vp->matrix.CompressFromFullMatrix(vehicle->GetMatrix()); + vp->health = vehicle->m_fHealth / 4.0f; /* Not anticipated that health can be > 1000. */ + vp->acceleration = vehicle->m_fGasPedal * 100.0f; + vp->panels = vehicle->IsCar() ? ((CAutomobile*)vehicle)->Damage.m_panelStatus : 0; + vp->velocityX = 8000.0f * Max(-4.0f, Min(4.0f, vehicle->GetMoveSpeed().x)); /* 8000!? */ + vp->velocityY = 8000.0f * Max(-4.0f, Min(4.0f, vehicle->GetMoveSpeed().y)); + vp->velocityZ = 8000.0f * Max(-4.0f, Min(4.0f, vehicle->GetMoveSpeed().z)); + vp->mi = vehicle->GetModelIndex(); + vp->primary_color = vehicle->m_currentColour1; + vp->secondary_color = vehicle->m_currentColour2; + if (vehicle->GetModelIndex() == MI_RHINO) + vp->car_gun = 128.0f / PI * ((CAutomobile*)vehicle)->m_fCarGunLR; + else + vp->wheel_state = 50.0f * vehicle->m_fSteerAngle; + if (vehicle->IsCar()){ + CAutomobile* car = (CAutomobile*)vehicle; + for (int i = 0; i < 4; i++){ + vp->wheel_susp_dist[i] = 50.0f * car->m_aSuspensionSpringRatio[i]; + vp->wheel_rotation[i] = 128.0f / PI * car->m_aWheelRotation[i]; + } + vp->door_angles[0] = 127.0f / PI * car->Doors[2].m_fAngle; + vp->door_angles[1] = 127.0f / PI * car->Doors[3].m_fAngle; + vp->door_status = 0; + for (int i = 0; i < 6; i++){ + if (car->Damage.GetDoorStatus(i) == DOOR_STATUS_MISSING) + vp->door_status |= BIT(i); + } + } + Record.m_nOffset += sizeof(tVehicleUpdatePacket); +} + +void CReplay::ProcessCarUpdate(CVehicle *vehicle, float interpolation, CAddressInReplayBuffer *buffer) +{ + tVehicleUpdatePacket* vp = (tVehicleUpdatePacket*)&buffer->m_pBase[buffer->m_nOffset]; + if (!vehicle){ + printf("Replay:Car wasn't there"); + return; + } + CMatrix vehicle_matrix; + vp->matrix.DecompressIntoFullMatrix(vehicle_matrix); + vehicle->GetMatrix() = vehicle->GetMatrix() * CMatrix(1.0f - interpolation); + vehicle->GetMatrix().GetPosition() *= (1.0f - interpolation); + vehicle->GetMatrix() += CMatrix(interpolation) * vehicle_matrix; + vehicle->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + vehicle->m_fHealth = 4 * vp->health; + vehicle->m_fGasPedal = vp->acceleration / 100.0f; + if (vehicle->IsCar()) + ApplyPanelDamageToCar(vp->panels, (CAutomobile*)vehicle, true); + vehicle->m_vecMoveSpeed = CVector(vp->velocityX / 8000.0f, vp->velocityY / 8000.0f, vp->velocityZ / 8000.0f); + if (vehicle->GetModelIndex() == MI_RHINO) { + ((CAutomobile*)vehicle)->m_fCarGunLR = vp->car_gun * PI / 128.0f; + vehicle->m_fSteerAngle = 0.0f; + }else{ + vehicle->m_fSteerAngle = vp->wheel_state / 50.0f; + } + if (vehicle->IsCar()) { + CAutomobile* car = (CAutomobile*)vehicle; + for (int i = 0; i < 4; i++) { + car->m_aSuspensionSpringRatio[i] = vp->wheel_susp_dist[i] / 50.0f; + car->m_aWheelRotation[i] = vp->wheel_rotation[i] * PI / 128.0f; + } + car->Doors[DOOR_FRONT_LEFT].m_fAngle = car->Doors[DOOR_FRONT_LEFT].m_fPrevAngle = vp->door_angles[0] * PI / 127.0f; + car->Doors[DOOR_FRONT_RIGHT].m_fAngle = car->Doors[DOOR_FRONT_RIGHT].m_fPrevAngle = vp->door_angles[1] * PI / 127.0f; + if (vp->door_angles[0]) + car->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_SWINGING); + if (vp->door_angles[1]) + car->Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_SWINGING); + if (vp->door_status & 1 && car->Damage.GetDoorStatus(DOOR_BONNET) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_BONNET, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_BONNET, DOOR_BONNET, true); + } + if (vp->door_status & 2 && car->Damage.GetDoorStatus(DOOR_BOOT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_BOOT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_BOOT, DOOR_BOOT, true); + } + if (vp->door_status & 4 && car->Damage.GetDoorStatus(DOOR_FRONT_LEFT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT, true); + } + if (vp->door_status & 8 && car->Damage.GetDoorStatus(DOOR_FRONT_RIGHT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT, true); + } + if (vp->door_status & 0x10 && car->Damage.GetDoorStatus(DOOR_REAR_LEFT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_REAR_LEFT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT, true); + } + if (vp->door_status & 0x20 && car->Damage.GetDoorStatus(DOOR_REAR_RIGHT) != DOOR_STATUS_MISSING) { + car->Damage.SetDoorStatus(DOOR_REAR_RIGHT, DOOR_STATUS_MISSING); + car->SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT, true); + } + } + vehicle->bEngineOn = true; + if (vehicle->IsCar()) + ((CAutomobile*)vehicle)->m_nDriveWheelsOnGround = 4; + CWorld::Remove(vehicle); + CWorld::Add(vehicle); + if (vehicle->IsBoat()) + ((CBoat*)vehicle)->m_bIsAnchored = false; +} + +bool CReplay::PlayBackThisFrameInterpolation(CAddressInReplayBuffer *buffer, float interpolation, uint32 *pTimer){ + /* Mistake. Not even sure what this is even doing here... + * PlayerWanted is a backup to restore at the end of replay. + * Setting current wanted pointer to it makes it useless. + * Causes picking up bribes in replays reducing wanted level bug. + * Obviously fact of picking them up is a bug on its own, + * but it doesn't cancel this one. + */ +#ifndef FIX_REPLAY_BUGS + FindPlayerPed()->m_pWanted = &PlayerWanted; +#endif + + CBulletTraces::Init(); + float split = 1.0f - interpolation; + int ped_min_index = 0; /* Optimization due to peds and vehicles placed in buffer sequentially. */ + int vehicle_min_index = 0; /* So next ped can't have pool index less than current. */ + for(;;){ + uint8* ptr = buffer->m_pBase; + uint32 offset = buffer->m_nOffset; + uint8 type = ptr[offset]; + if (type == REPLAYPACKET_ENDOFFRAME) + break; + switch (type) { + case REPLAYPACKET_END: + { + int slot = buffer->m_bSlot; + if (BufferStatus[slot] == REPLAYBUFFER_RECORD) { + FinishPlayback(); + return true; + } + buffer->m_bSlot = (slot + 1) % NUM_REPLAYBUFFERS; + buffer->m_nOffset = 0; + buffer->m_pBase = Buffers[buffer->m_bSlot]; + ped_min_index = 0; + vehicle_min_index = 0; + break; + } + case REPLAYPACKET_VEHICLE: + { + tVehicleUpdatePacket* vp = (tVehicleUpdatePacket*)&ptr[offset]; + for (int i = vehicle_min_index; i < vp->index; i++) { + CVehicle* v = CPools::GetVehiclePool()->GetSlot(i); + if (!v) + continue; + /* Removing vehicles not present in this frame. */ + CWorld::Remove(v); + delete v; + } + vehicle_min_index = vp->index + 1; + CVehicle* v = CPools::GetVehiclePool()->GetSlot(vp->index); + CVehicle* new_v; + if (!v) { + int mi = vp->mi; + if (CStreaming::ms_aInfoForModel[mi].m_loadState != 1) { + CStreaming::RequestModel(mi, 0); + } + else { + if (mi == MI_DEADDODO || mi == MI_AIRTRAIN) { + new_v = new(vp->index << 8) CPlane(mi, 2); + } + else if (mi == MI_TRAIN) { + new_v = new(vp->index << 8) CTrain(mi, 2); + } + else if (mi == MI_CHOPPER || mi == MI_ESCAPE) { + new_v = new(vp->index << 8) CHeli(mi, 2); + } + else if (CModelInfo::IsBoatModel(mi)){ + new_v = new(vp->index << 8) CBoat(mi, 2); + } + else{ + new_v = new(vp->index << 8) CAutomobile(mi, 2); + } + new_v->SetStatus(STATUS_PLAYER_PLAYBACKFROMBUFFER); + vp->matrix.DecompressIntoFullMatrix(new_v->GetMatrix()); + new_v->m_currentColour1 = vp->primary_color; + new_v->m_currentColour2 = vp->secondary_color; + CWorld::Add(new_v); + } + } + ProcessCarUpdate(CPools::GetVehiclePool()->GetSlot(vp->index), interpolation, buffer); + buffer->m_nOffset += sizeof(tVehicleUpdatePacket); + break; + } + case REPLAYPACKET_PED_HEADER: + { + tPedHeaderPacket* ph = (tPedHeaderPacket*)&ptr[offset]; + if (!CPools::GetPedPool()->GetSlot(ph->index)) { + if (CStreaming::ms_aInfoForModel[ph->mi].m_loadState != 1) { + CStreaming::RequestModel(ph->mi, 0); + } + else { + CPed* new_p = new(ph->index << 8) CCivilianPed((ePedType)ph->pedtype, ph->mi); + new_p->SetStatus(STATUS_PLAYER_PLAYBACKFROMBUFFER); + new_p->GetMatrix().SetUnity(); + CWorld::Add(new_p); + } + } + buffer->m_nOffset += sizeof(tPedHeaderPacket); + break; + } + case REPLAYPACKET_PED_UPDATE: + { + tPedUpdatePacket* pu = (tPedUpdatePacket*)&ptr[offset]; + for (int i = ped_min_index; i < pu->index; i++) { + CPed* p = CPools::GetPedPool()->GetSlot(i); + if (!p) + continue; + /* Removing peds not present in this frame. */ + CWorld::Remove(p); + delete p; + } + ped_min_index = pu->index + 1; + ProcessPedUpdate(CPools::GetPedPool()->GetSlot(pu->index), interpolation, buffer); + break; + } + case REPLAYPACKET_GENERAL: + { + tGeneralPacket* pg = (tGeneralPacket*)&ptr[offset]; + TheCamera.GetMatrix() = TheCamera.GetMatrix() * CMatrix(split); + TheCamera.GetMatrix().GetPosition() *= split; + TheCamera.GetMatrix() += CMatrix(interpolation) * pg->camera_pos; + RwMatrix* pm = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + pm->pos = TheCamera.GetPosition(); + pm->at = TheCamera.GetForward(); + pm->up = TheCamera.GetUp(); + pm->right = TheCamera.GetRight(); + CameraFocusX = split * CameraFocusX + interpolation * pg->player_pos.x; + CameraFocusY = split * CameraFocusY + interpolation * pg->player_pos.y; + CameraFocusZ = split * CameraFocusZ + interpolation * pg->player_pos.z; + bPlayerInRCBuggy = pg->in_rcvehicle; + buffer->m_nOffset += sizeof(tGeneralPacket); + break; + } + case REPLAYPACKET_CLOCK: + { + tClockPacket* pc = (tClockPacket*)&ptr[offset]; + CClock::SetGameClock(pc->hours, pc->minutes); + buffer->m_nOffset += sizeof(tClockPacket); + break; + } + case REPLAYPACKET_WEATHER: + { + tWeatherPacket* pw = (tWeatherPacket*)&ptr[offset]; + CWeather::OldWeatherType = pw->old_weather; + CWeather::NewWeatherType = pw->new_weather; + CWeather::InterpolationValue = pw->interpolation; + buffer->m_nOffset += sizeof(tWeatherPacket); + break; + } + case REPLAYPACKET_ENDOFFRAME: + { + /* Not supposed to be here. */ + assert(false); + buffer->m_nOffset++; + break; + } + case REPLAYPACKET_TIMER: + { + tTimerPacket* pt = (tTimerPacket*)&ptr[offset]; + if (pTimer) + *pTimer = pt->timer; + CTimer::SetTimeInMilliseconds(pt->timer); + buffer->m_nOffset += sizeof(tTimerPacket); + break; + } + case REPLAYPACKET_BULLET_TRACES: + { + tBulletTracePacket* pb = (tBulletTracePacket*)&ptr[offset]; + CBulletTraces::aTraces[pb->index].m_bInUse = true; + CBulletTraces::aTraces[pb->index].m_framesInUse = pb->frames; + CBulletTraces::aTraces[pb->index].m_lifeTime = pb->lifetime; + CBulletTraces::aTraces[pb->index].m_vecCurrentPos = pb->inf; + CBulletTraces::aTraces[pb->index].m_vecTargetPos = pb->sup; + buffer->m_nOffset += sizeof(tBulletTracePacket); + } + default: + break; + } + } + buffer->m_nOffset += 4; + for (int i = vehicle_min_index; i < CPools::GetVehiclePool()->GetSize(); i++) { + CVehicle* v = CPools::GetVehiclePool()->GetSlot(i); + if (!v) + continue; + /* Removing vehicles not present in this frame. */ + CWorld::Remove(v); + delete v; + } + for (int i = ped_min_index; i < CPools::GetPedPool()->GetSize(); i++) { + CPed* p = CPools::GetPedPool()->GetSlot(i); + if (!p) + continue; + /* Removing peds not present in this frame. */ + CWorld::Remove(p); + delete p; + } + ProcessReplayCamera(); + return false; +} + +void CReplay::FinishPlayback(void) +{ + if (Mode != MODE_PLAYBACK) + return; + EmptyAllPools(); + RestoreStuffFromMem(); + Mode = MODE_RECORD; + if (bDoLoadSceneWhenDone){ + CVector v_ls(LoadSceneX, LoadSceneY, LoadSceneZ); + CGame::currLevel = CTheZones::GetLevelFromPosition(&v_ls); + CCollision::SortOutCollisionAfterLoad(); + CStreaming::LoadScene(v_ls); + } + bDoLoadSceneWhenDone = false; + if (bPlayingBackFromFile){ + Init(); + MarkEverythingAsNew(); + } + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); +} + +void CReplay::EmptyReplayBuffer(void) +{ + if (Mode == MODE_PLAYBACK) + return; + Record.m_nOffset = 0; + for (int i = 0; i < NUM_REPLAYBUFFERS; i++){ + BufferStatus[i] = REPLAYBUFFER_UNUSED; + } + Record.m_bSlot = 0; + Record.m_pBase = Buffers[0]; + BufferStatus[0] = REPLAYBUFFER_RECORD; + Record.m_pBase[Record.m_nOffset] = 0; + MarkEverythingAsNew(); +} + +void CReplay::ProcessReplayCamera(void) +{ + switch (CameraMode) { + case REPLAYCAMMODE_TOPDOWN: + { + TheCamera.SetPosition(CameraFocusX, CameraFocusY, CameraFocusZ + 15.0f); + TheCamera.GetForward() = CVector(0.0f, 0.0f, -1.0f); + TheCamera.GetUp() = CVector(0.0f, 1.0f, 0.0f); + TheCamera.GetRight() = CVector(1.0f, 0.0f, 0.0f); + RwMatrix* pm = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + pm->pos = TheCamera.GetPosition(); + pm->at = TheCamera.GetForward(); + pm->up = TheCamera.GetUp(); + pm->right = TheCamera.GetRight(); + break; + } + case REPLAYCAMMODE_FIXED: + { + TheCamera.GetMatrix().GetPosition() = CVector(CameraFixedX, CameraFixedY, CameraFixedZ); + CVector forward(CameraFocusX - CameraFixedX, CameraFocusY - CameraFixedY, CameraFocusZ - CameraFixedZ); + forward.Normalise(); + CVector right = CrossProduct(CVector(0.0f, 0.0f, 1.0f), forward); + right.Normalise(); + CVector up = CrossProduct(forward, right); + up.Normalise(); + TheCamera.GetMatrix().GetForward() = forward; + TheCamera.GetMatrix().GetUp() = up; + TheCamera.GetMatrix().GetRight() = right; + RwMatrix* pm = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + pm->pos = TheCamera.GetMatrix().GetPosition(); + pm->at = TheCamera.GetMatrix().GetForward(); + pm->up = TheCamera.GetMatrix().GetUp(); + pm->right = TheCamera.GetMatrix().GetRight(); + break; + } + default: + break; + } + TheCamera.m_vecGameCamPos = TheCamera.GetMatrix().GetPosition(); + TheCamera.CalculateDerivedValues(); + RwMatrixUpdate(RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera))); + RwFrameUpdateObjects(RwCameraGetFrame(TheCamera.m_pRwCamera)); +} + +void CReplay::TriggerPlayback(uint8 cam_mode, float cam_x, float cam_y, float cam_z, bool load_scene) +{ + if (Mode != MODE_RECORD) + return; + CameraFixedX = cam_x; + CameraFixedY = cam_y; + CameraFixedZ = cam_z; + Mode = MODE_PLAYBACK; + FramesActiveLookAroundCam = 0; + CameraMode = cam_mode; + bAllowLookAroundCam = true; + bPlayingBackFromFile = false; + OldRadioStation = DMAudio.GetRadioInCar(); + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + int current; + for (current = 0; current < NUM_REPLAYBUFFERS; current++) + if (BufferStatus[current] == REPLAYBUFFER_RECORD) + break; + int first; + for (first = (current + 1) % NUM_REPLAYBUFFERS; ; first = (first + 1) % NUM_REPLAYBUFFERS) + if (BufferStatus[first] == REPLAYBUFFER_RECORD || BufferStatus[first] == REPLAYBUFFER_PLAYBACK) + break; + Playback.m_bSlot = first; + Playback.m_nOffset = 0; + Playback.m_pBase = Buffers[first]; + CObject::DeleteAllTempObjectsInArea(CVector(0.0f, 0.0f, 0.0f), 1000000.0f); + StoreStuffInMem(); + EmptyPedsAndVehiclePools(); + SlowMotion = 1; + CSkidmarks::Clear(); + StreamAllNecessaryCarsAndPeds(); + if (load_scene) + bDoLoadSceneWhenDone = false; + else{ + bDoLoadSceneWhenDone = true; + LoadSceneX = TheCamera.GetPosition().x; + LoadSceneY = TheCamera.GetPosition().y; + LoadSceneZ = TheCamera.GetPosition().z; + CVector ff_coord; + FindFirstFocusCoordinate(&ff_coord); + CGame::currLevel = CTheZones::GetLevelFromPosition(&ff_coord); + CCollision::SortOutCollisionAfterLoad(); + CStreaming::LoadScene(ff_coord); + } + if (cam_mode == REPLAYCAMMODE_ASSTORED) + TheCamera.CarZoomIndicator = CAM_ZOOM_CINEMATIC; +} + +void CReplay::StoreStuffInMem(void) +{ +#ifdef FIX_BUGS + for (int i = 0; i < NUMPLAYERS; i++) + nHandleOfPlayerPed[i] = CPools::GetPedPool()->GetIndex(CWorld::Players[i].m_pPed); +#endif + CPools::GetVehiclePool()->Store(pBuf0, pBuf1); + CPools::GetPedPool()->Store(pBuf2, pBuf3); + CPools::GetObjectPool()->Store(pBuf4, pBuf5); + CPools::GetPtrNodePool()->Store(pBuf6, pBuf7); + CPools::GetEntryInfoNodePool()->Store(pBuf8, pBuf9); + CPools::GetDummyPool()->Store(pBuf10, pBuf11); + pWorld1 = new uint8[sizeof(CSector) * NUMSECTORS_X * NUMSECTORS_Y]; + memcpy(pWorld1, CWorld::GetSector(0, 0), NUMSECTORS_X * NUMSECTORS_Y * sizeof(CSector)); + WorldPtrList = CWorld::GetMovingEntityList().first; // why + BigBuildingPtrList = CWorld::GetBigBuildingList(LEVEL_GENERIC).first; + pPickups = new uint8[sizeof(CPickup) * NUMPICKUPS]; + memcpy(pPickups, CPickups::aPickUps, NUMPICKUPS * sizeof(CPickup)); + pReferences = new uint8[(sizeof(CReference) * NUMREFERENCES)]; + memcpy(pReferences, CReferences::aRefs, NUMREFERENCES * sizeof(CReference)); + pEmptyReferences = CReferences::pEmptyList; + pStoredCam = new uint8[sizeof(CCamera)]; + memcpy(pStoredCam, &TheCamera, sizeof(CCamera)); + pRadarBlips = new uint8[sizeof(sRadarTrace) * NUMRADARBLIPS]; + memcpy(pRadarBlips, CRadar::ms_RadarTrace, NUMRADARBLIPS * sizeof(sRadarTrace)); + PlayerWanted = *FindPlayerPed()->m_pWanted; + PlayerInfo = CWorld::Players[0]; + Time1 = CTimer::GetTimeInMilliseconds(); + Time2 = CTimer::GetTimeInMillisecondsNonClipped(); + Time3 = CTimer::GetPreviousTimeInMilliseconds(); + Time4 = CTimer::GetTimeInMillisecondsPauseMode(); + Frame = CTimer::GetFrameCounter(); + ClockHours = CClock::GetHours(); + ClockMinutes = CClock::GetMinutes(); + OldWeatherType = CWeather::OldWeatherType; + NewWeatherType = CWeather::NewWeatherType; + WeatherInterpolationValue = CWeather::InterpolationValue; + TimeStepNonClipped = CTimer::GetTimeStepNonClipped(); + TimeStep = CTimer::GetTimeStep(); + TimeScale = CTimer::GetTimeScale(); + int size = CPools::GetPedPool()->GetSize(); + pPedAnims = new CStoredDetailedAnimationState[size]; + for (int i = 0; i < size; i++) { + CPed* ped = CPools::GetPedPool()->GetSlot(i); + if (ped) + StoreDetailedPedAnimation(ped, &pPedAnims[i]); + } +#ifdef FIX_BUGS + pGarages = new uint8[sizeof(CGarages::aGarages)]; + memcpy(pGarages, CGarages::aGarages, sizeof(CGarages::aGarages)); + FireArray = new CFire[NUM_FIRES]; + memcpy(FireArray, gFireManager.m_aFires, sizeof(gFireManager.m_aFires)); + NumOfFires = gFireManager.m_nTotalFires; + paProjectileInfo = new uint8[sizeof(gaProjectileInfo)]; + memcpy(paProjectileInfo, gaProjectileInfo, sizeof(gaProjectileInfo)); + paProjectiles = new uint8[sizeof(CProjectileInfo::ms_apProjectile)]; + memcpy(paProjectiles, CProjectileInfo::ms_apProjectile, sizeof(CProjectileInfo::ms_apProjectile)); +#endif +} + +void CReplay::RestoreStuffFromMem(void) +{ + CPools::GetVehiclePool()->CopyBack(pBuf0, pBuf1); + CPools::GetPedPool()->CopyBack(pBuf2, pBuf3); + CPools::GetObjectPool()->CopyBack(pBuf4, pBuf5); + CPools::GetPtrNodePool()->CopyBack(pBuf6, pBuf7); + CPools::GetEntryInfoNodePool()->CopyBack(pBuf8, pBuf9); + CPools::GetDummyPool()->CopyBack(pBuf10, pBuf11); + memcpy(CWorld::GetSector(0, 0), pWorld1, sizeof(CSector) * NUMSECTORS_X * NUMSECTORS_Y); + delete[] pWorld1; + pWorld1 = nil; + CWorld::GetMovingEntityList().first = WorldPtrList; + CWorld::GetBigBuildingList(LEVEL_GENERIC).first = BigBuildingPtrList; + memcpy(CPickups::aPickUps, pPickups, sizeof(CPickup) * NUMPICKUPS); + delete[] pPickups; + pPickups = nil; + memcpy(CReferences::aRefs, pReferences, sizeof(CReference) * NUMREFERENCES); + delete[] pReferences; + pReferences = nil; + CReferences::pEmptyList = pEmptyReferences; + pEmptyReferences = nil; + memcpy(&TheCamera, pStoredCam, sizeof(CCamera)); + delete[] pStoredCam; + pStoredCam = nil; + memcpy(CRadar::ms_RadarTrace, pRadarBlips, sizeof(sRadarTrace) * NUMRADARBLIPS); + delete[] pRadarBlips; + pRadarBlips = nil; +#ifdef FIX_BUGS + for (int i = 0; i < NUMPLAYERS; i++) { + CPlayerPed* pPlayerPed = (CPlayerPed*)CPools::GetPedPool()->GetAt(nHandleOfPlayerPed[i]); + assert(pPlayerPed); + CWorld::Players[i].m_pPed = pPlayerPed; + pPlayerPed->RegisterReference((CEntity**)&CWorld::Players[i].m_pPed); + } +#endif + FindPlayerPed()->m_pWanted = new CWanted(PlayerWanted); + CWorld::Players[0] = PlayerInfo; + int i = CPools::GetPedPool()->GetSize(); + while (--i >= 0) { + CPed* ped = CPools::GetPedPool()->GetSlot(i); + if (!ped) + continue; + int mi = ped->GetModelIndex(); + CStreaming::RequestModel(mi, 0); + CStreaming::LoadAllRequestedModels(false); + ped->m_rwObject = nil; + ped->m_modelIndex = -1; + ped->SetModelIndex(mi); + ped->m_pVehicleAnim = nil; + ped->m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, ped); + DMAudio.SetEntityStatus(ped->m_audioEntityId, TRUE); + CPopulation::UpdatePedCount((ePedType)ped->m_nPedType, false); + if (ped->m_wepModelID >= 0) + ped->AddWeaponModel(ped->m_wepModelID); + } + i = CPools::GetVehiclePool()->GetSize(); + while (--i >= 0) { + CVehicle* vehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!vehicle) + continue; + int mi = vehicle->GetModelIndex(); + CStreaming::RequestModel(mi, 0); + CStreaming::LoadAllRequestedModels(false); + vehicle->m_rwObject = nil; + vehicle->m_modelIndex = -1; + vehicle->SetModelIndex(mi); + if (mi == MI_DODO){ + CAutomobile* dodo = (CAutomobile*)vehicle; + RpAtomicSetFlags((RpAtomic*)GetFirstObject(dodo->m_aCarNodes[CAR_WHEEL_LF]), 0); + CMatrix tmp1; + tmp1.Attach(RwFrameGetMatrix(dodo->m_aCarNodes[CAR_WHEEL_RF]), false); + CMatrix tmp2(RwFrameGetMatrix(dodo->m_aCarNodes[CAR_WHEEL_LF]), false); + tmp1.GetPosition() += CVector(tmp2.GetPosition().x + 0.1f, 0.0f, tmp2.GetPosition().z); + tmp1.UpdateRW(); + } + if (vehicle->IsCar()){ + CAutomobile* car = (CAutomobile*)vehicle; + int32 panels = car->Damage.m_panelStatus; + car->Damage.m_panelStatus = 0; + ApplyPanelDamageToCar(panels, car, true); + car->SetDoorDamage(CAR_BONNET, DOOR_BONNET, true); + car->SetDoorDamage(CAR_BOOT, DOOR_BOOT, true); + car->SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT, true); + car->SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT, true); + car->SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT, true); + car->SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT, true); + } + vehicle->m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, vehicle); + DMAudio.SetEntityStatus(vehicle->m_audioEntityId, TRUE); + CCarCtrl::UpdateCarCount(vehicle, false); + if ((mi == MI_AIRTRAIN || mi == MI_DEADDODO) && vehicle->m_rwObject){ + CVehicleModelInfo* info = (CVehicleModelInfo*)CModelInfo::GetModelInfo(mi); + if (RwObjectGetType(vehicle->m_rwObject) == rpATOMIC){ + vehicle->GetMatrix().Detach(); + if (vehicle->m_rwObject){ + if (RwObjectGetType(vehicle->m_rwObject) == rpATOMIC){ + RwFrame* frame = RpAtomicGetFrame((RpAtomic*)vehicle->m_rwObject); + RpAtomicDestroy((RpAtomic*)vehicle->m_rwObject); + RwFrameDestroy(frame); + } + vehicle->m_rwObject = nil; + } + }else{ + vehicle->DeleteRwObject(); + int model_id = info->m_wheelId; + if (model_id != -1){ + if ((vehicle->m_rwObject = CModelInfo::GetModelInfo(model_id)->CreateInstance())){ + vehicle->GetMatrix().AttachRW(RwFrameGetMatrix(RpClumpGetFrame((RpClump*)vehicle->m_rwObject)), false); + } + } + } + } + } + PrintElementsInPtrList(); + i = CPools::GetObjectPool()->GetSize(); + while (--i >= 0) { + CObject* object = CPools::GetObjectPool()->GetSlot(i); + if (!object) + continue; + int mi = object->GetModelIndex(); + CStreaming::RequestModel(mi, 0); + CStreaming::LoadAllRequestedModels(false); + object->m_rwObject = nil; + object->m_modelIndex = -1; + object->SetModelIndex(mi); + object->GetMatrix().m_attachment = nil; + if (RwObjectGetType(object->m_rwObject) == rpATOMIC) + object->GetMatrix().AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic*)object->m_rwObject)), false); + } + i = CPools::GetDummyPool()->GetSize(); + while (--i >= 0) { + CDummy* dummy = CPools::GetDummyPool()->GetSlot(i); + if (!dummy) + continue; + int mi = dummy->GetModelIndex(); + CStreaming::RequestModel(mi, 0); + CStreaming::LoadAllRequestedModels(false); + dummy->m_rwObject = nil; + dummy->m_modelIndex = -1; + dummy->SetModelIndex(mi); + dummy->GetMatrix().m_attachment = nil; + if (RwObjectGetType(dummy->m_rwObject) == rpATOMIC) + dummy->GetMatrix().AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic*)dummy->m_rwObject)), false); + } + CTimer::SetTimeInMilliseconds(Time1); + CTimer::SetTimeInMillisecondsNonClipped(Time2); + CTimer::SetPreviousTimeInMilliseconds(Time3); + CTimer::SetTimeInMillisecondsPauseMode(Time4); + CTimer::SetTimeScale(TimeScale); + CTimer::SetFrameCounter(Frame); + CTimer::SetTimeStep(TimeStep); + CTimer::SetTimeStepNonClipped(TimeStepNonClipped); + CClock::SetGameClock(ClockHours, ClockMinutes); + CWeather::OldWeatherType = OldWeatherType; + CWeather::NewWeatherType = NewWeatherType; + CWeather::InterpolationValue = WeatherInterpolationValue; + for (int i = 0; i < CPools::GetPedPool()->GetSize(); i++) { + CPed* ped = CPools::GetPedPool()->GetSlot(i); + if (!ped) + continue; + RetrieveDetailedPedAnimation(ped, &pPedAnims[i]); + } + delete[] pPedAnims; + pPedAnims = nil; +#ifdef FIX_BUGS + memcpy(CGarages::aGarages, pGarages, sizeof(CGarages::aGarages)); + delete[] pGarages; + pGarages = nil; + memcpy(gFireManager.m_aFires, FireArray, sizeof(gFireManager.m_aFires)); + delete[] FireArray; + FireArray = nil; + gFireManager.m_nTotalFires = NumOfFires; + memcpy(gaProjectileInfo, paProjectileInfo, sizeof(gaProjectileInfo)); + delete[] paProjectileInfo; + paProjectileInfo = nil; + memcpy(CProjectileInfo::ms_apProjectile, paProjectiles, sizeof(CProjectileInfo::ms_apProjectile)); + delete[] paProjectiles; + paProjectiles = nil; + //CExplosion::ClearAllExplosions(); not in III +#endif + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.SetRadioInCar(OldRadioStation); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); +} + +void CReplay::EmptyPedsAndVehiclePools(void) +{ + int i = CPools::GetVehiclePool()->GetSize(); + while (--i >= 0) { + CVehicle* v = CPools::GetVehiclePool()->GetSlot(i); + if (!v) + continue; + CWorld::Remove(v); + delete v; + } + i = CPools::GetPedPool()->GetSize(); + while (--i >= 0) { + CPed* p = CPools::GetPedPool()->GetSlot(i); + if (!p) + continue; + CWorld::Remove(p); + delete p; + } +} + +void CReplay::EmptyAllPools(void) +{ + EmptyPedsAndVehiclePools(); + int i = CPools::GetObjectPool()->GetSize(); + while (--i >= 0) { + CObject* o = CPools::GetObjectPool()->GetSlot(i); + if (!o) + continue; + CWorld::Remove(o); + delete o; + } + i = CPools::GetDummyPool()->GetSize(); + while (--i >= 0) { + CDummy* d = CPools::GetDummyPool()->GetSlot(i); + if (!d) + continue; + CWorld::Remove(d); + delete d; + } +} + +void CReplay::MarkEverythingAsNew(void) +{ + int i = CPools::GetVehiclePool()->GetSize(); + while (--i >= 0) { + CVehicle* v = CPools::GetVehiclePool()->GetSlot(i); + if (!v) + continue; + v->bHasAlreadyBeenRecorded = false; + } + i = CPools::GetPedPool()->GetSize(); + while (--i >= 0) { + CPed* p = CPools::GetPedPool()->GetSlot(i); + if (!p) + continue; + p->bHasAlreadyBeenRecorded = false; + } +} + +void CReplay::SaveReplayToHD(void) +{ + CFileMgr::SetDirMyDocuments(); + int fw = CFileMgr::OpenFileForWriting("replay.rep"); +#ifdef FIX_REPLAY_BUGS + if (fw == 0) { +#else + if (fw < 0){ // BUG? +#endif + printf("Couldn't open replay.rep for writing"); + CFileMgr::SetDir(""); + return; + } + CFileMgr::Write(fw, "gta3_7f", sizeof("gta3_7f")); + int current; + for (current = 0; current < NUM_REPLAYBUFFERS; current++) + if (BufferStatus[current] == REPLAYBUFFER_RECORD) + break; + int first; + for (first = (current + 1) % NUM_REPLAYBUFFERS; ; first = (first + 1) % NUM_REPLAYBUFFERS) + if (BufferStatus[first] == REPLAYBUFFER_RECORD || BufferStatus[first] == REPLAYBUFFER_PLAYBACK) + break; + for(int i = first; ; i = (i + 1) % NUM_REPLAYBUFFERS){ + CFileMgr::Write(fw, (char*)Buffers[i], sizeof(Buffers[i])); + if (BufferStatus[i] == REPLAYBUFFER_RECORD) + break; + } + CFileMgr::CloseFile(fw); + CFileMgr::SetDir(""); +} + +void CReplay::PlayReplayFromHD(void) +{ + CFileMgr::SetDirMyDocuments(); + int fr = CFileMgr::OpenFile("replay.rep", "rb"); + if (fr == 0) { + printf("Couldn't open replay.rep for reading"); +#ifdef FIX_REPLAY_BUGS + CFileMgr::SetDir(""); +#endif + return; + } + CFileMgr::Read(fr, gString, 8); + if (strncmp(gString, "gta3_7f", sizeof("gta3_7f"))){ + CFileMgr::CloseFile(fr); + printf("Wrong file type for replay"); + CFileMgr::SetDir(""); + return; + } + int slot; + for (slot = 0; CFileMgr::Read(fr, (char*)Buffers[slot], sizeof(Buffers[slot])); slot++) + BufferStatus[slot] = REPLAYBUFFER_PLAYBACK; + BufferStatus[slot - 1] = REPLAYBUFFER_RECORD; + while (slot < NUM_REPLAYBUFFERS) + BufferStatus[slot++] = REPLAYBUFFER_UNUSED; + CFileMgr::CloseFile(fr); + CFileMgr::SetDir(""); + TriggerPlayback(REPLAYCAMMODE_ASSTORED, 0.0f, 0.0f, 0.0f, false); + bPlayingBackFromFile = true; + bAllowLookAroundCam = true; + StreamAllNecessaryCarsAndPeds(); +} + +void CReplay::StreamAllNecessaryCarsAndPeds(void) +{ + for (int slot = 0; slot < NUM_REPLAYBUFFERS; slot++) { + if (BufferStatus[slot] == REPLAYBUFFER_UNUSED) + continue; + for (size_t offset = 0; Buffers[slot][offset] != REPLAYPACKET_END; offset += FindSizeOfPacket(Buffers[slot][offset])) { + switch (Buffers[slot][offset]) { + case REPLAYPACKET_VEHICLE: + CStreaming::RequestModel(((tVehicleUpdatePacket*)&Buffers[slot][offset])->mi, 0); + break; + case REPLAYPACKET_PED_HEADER: + CStreaming::RequestModel(((tPedHeaderPacket*)&Buffers[slot][offset])->mi, 0); + break; + default: + break; + } + } + } + CStreaming::LoadAllRequestedModels(false); +} + +void CReplay::FindFirstFocusCoordinate(CVector *coord) +{ + *coord = CVector(0.0f, 0.0f, 0.0f); + for (int slot = 0; slot < NUM_REPLAYBUFFERS; slot++) { + if (BufferStatus[slot] == REPLAYBUFFER_UNUSED) + continue; + for (size_t offset = 0; Buffers[slot][offset] != REPLAYPACKET_END; offset += FindSizeOfPacket(Buffers[slot][offset])) { + if (Buffers[slot][offset] == REPLAYPACKET_GENERAL) { + *coord = ((tGeneralPacket*)&Buffers[slot][offset])->player_pos; + return; + } + } + } +} + +bool CReplay::ShouldStandardCameraBeProcessed(void) +{ + if (Mode != MODE_PLAYBACK) + return true; + if (FramesActiveLookAroundCam || bPlayerInRCBuggy) + return false; + return FindPlayerVehicle() != nil; +} + +void CReplay::ProcessLookAroundCam(void) +{ + if (!bAllowLookAroundCam) + return; + float x_moved = CPad::NewMouseControllerState.x / 200.0f; + float y_moved = CPad::NewMouseControllerState.y / 200.0f; + if (x_moved > 0.01f || y_moved > 0.01f) { + if (FramesActiveLookAroundCam == 0) + fDistanceLookAroundCam = 9.0f; + FramesActiveLookAroundCam = 60; + } + if (bPlayerInRCBuggy) + FramesActiveLookAroundCam = 0; + if (!FramesActiveLookAroundCam) + return; + --FramesActiveLookAroundCam; + fBetaAngleLookAroundCam += x_moved; + if (CPad::NewMouseControllerState.LMB && CPad::NewMouseControllerState.RMB) + fDistanceLookAroundCam = Max(3.0f, Min(15.0f, fDistanceLookAroundCam + 2.0f * y_moved)); + else + fAlphaAngleLookAroundCam = Max(0.1f, Min(1.5f, fAlphaAngleLookAroundCam + y_moved)); + CVector camera_pt( + fDistanceLookAroundCam * Sin(fBetaAngleLookAroundCam) * Cos(fAlphaAngleLookAroundCam), + fDistanceLookAroundCam * Cos(fBetaAngleLookAroundCam) * Cos(fAlphaAngleLookAroundCam), + fDistanceLookAroundCam * Sin(fAlphaAngleLookAroundCam) + ); + CVector focus = CVector(CameraFocusX, CameraFocusY, CameraFocusZ); + camera_pt += focus; + CColPoint cp; + CEntity* pe = nil; + if (CWorld::ProcessLineOfSight(focus, camera_pt, cp, pe, true, false, false, false, false, true, true)){ + camera_pt = cp.point; + CVector direction = focus - cp.point; + direction.Normalise(); + camera_pt += direction / 4.0f; + } + CVector forward = focus - camera_pt; + forward.Normalise(); + CVector right = CrossProduct(CVector(0.0f, 0.0f, 1.0f), forward); + right.Normalise(); + CVector up = CrossProduct(forward, right); + up.Normalise(); + TheCamera.GetForward() = forward; + TheCamera.GetUp() = up; + TheCamera.GetRight() = right; + TheCamera.SetPosition(camera_pt); + RwMatrix* pm = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + pm->pos = TheCamera.GetPosition(); + pm->at = TheCamera.GetForward(); + pm->up = TheCamera.GetUp(); + pm->right = TheCamera.GetRight(); + TheCamera.CalculateDerivedValues(); + RwMatrixUpdate(RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera))); + RwFrameUpdateObjects(RwCameraGetFrame(TheCamera.m_pRwCamera)); +} + +size_t CReplay::FindSizeOfPacket(uint8 type) +{ + switch (type) { + case REPLAYPACKET_END: return 4; + case REPLAYPACKET_VEHICLE: return sizeof(tVehicleUpdatePacket); + case REPLAYPACKET_PED_HEADER: return sizeof(tPedHeaderPacket); + case REPLAYPACKET_PED_UPDATE: return sizeof(tPedUpdatePacket); + case REPLAYPACKET_GENERAL: return sizeof(tGeneralPacket); + case REPLAYPACKET_CLOCK: return sizeof(tClockPacket); + case REPLAYPACKET_WEATHER: return sizeof(tWeatherPacket); + case REPLAYPACKET_ENDOFFRAME: return 4; + case REPLAYPACKET_TIMER: return sizeof(tTimerPacket); + case REPLAYPACKET_BULLET_TRACES:return sizeof(tBulletTracePacket); + default: assert(false); break; + } + return 0; +} + +void CReplay::Display() +{ + static int TimeCount = 0; + if (Mode == MODE_RECORD) + return; + TimeCount = (TimeCount + 1) % UINT16_MAX; + if ((TimeCount & 0x20) == 0) + return; + + CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f)); + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH-20); +#endif + CFont::SetCentreOff(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(255, 255, 200, 200)); + CFont::SetFontStyle(FONT_BANK); + if (Mode == MODE_PLAYBACK) + CFont::PrintString(SCREEN_WIDTH/15, SCREEN_HEIGHT/10, TheText.Get("REPLAY")); +} +#endif diff --git a/src/control/Replay.h b/src/control/Replay.h new file mode 100644 index 0000000..68da9cc --- /dev/null +++ b/src/control/Replay.h @@ -0,0 +1,329 @@ +#pragma once + +#include "Pools.h" +#include "World.h" + +#ifdef FIX_BUGS +#ifndef DONT_FIX_REPLAY_BUGS +#define FIX_REPLAY_BUGS +#endif +#endif + +class CVehicle; +struct CReference; + +struct CAddressInReplayBuffer +{ + uint32 m_nOffset; + uint8 *m_pBase; + uint8 m_bSlot; +}; + +struct CStoredAnimationState +{ + uint8 animId; + uint8 time; + uint8 speed; + uint8 secAnimId; + uint8 secTime; + uint8 secSpeed; + uint8 blendAmount; + uint8 partAnimId; + uint8 partAnimTime; + uint8 partAnimSpeed; + uint8 partBlendAmount; +}; + +enum { + NUM_MAIN_ANIMS_IN_REPLAY = 3, + NUM_PARTIAL_ANIMS_IN_REPLAY = 6 +}; + +struct CStoredDetailedAnimationState +{ + uint8 aAnimId[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aCurTime[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aSpeed[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aBlendAmount[NUM_MAIN_ANIMS_IN_REPLAY]; +#ifdef FIX_REPLAY_BUGS + int8 aBlendDelta[NUM_MAIN_ANIMS_IN_REPLAY]; +#endif + uint8 aFunctionCallbackID[NUM_MAIN_ANIMS_IN_REPLAY]; + uint16 aFlags[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aAnimId2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint8 aCurTime2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint8 aSpeed2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint8 aBlendAmount2[NUM_PARTIAL_ANIMS_IN_REPLAY]; +#ifdef FIX_REPLAY_BUGS + int8 aBlendDelta2[NUM_PARTIAL_ANIMS_IN_REPLAY]; +#endif + uint8 aFunctionCallbackID2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint16 aFlags2[NUM_PARTIAL_ANIMS_IN_REPLAY]; +}; + +#ifdef GTA_REPLAY +#define REPLAY_STUB +#else +#define REPLAY_STUB {} +#endif + +class CReplay +{ + enum { + MODE_RECORD = 0, + MODE_PLAYBACK = 1 + }; + + enum { + REPLAYCAMMODE_ASSTORED = 0, + REPLAYCAMMODE_TOPDOWN = 1, + REPLAYCAMMODE_FIXED = 2 + }; + + enum { + REPLAYPACKET_END = 0, + REPLAYPACKET_VEHICLE = 1, + REPLAYPACKET_PED_HEADER = 2, + REPLAYPACKET_PED_UPDATE = 3, + REPLAYPACKET_GENERAL = 4, + REPLAYPACKET_CLOCK = 5, + REPLAYPACKET_WEATHER = 6, + REPLAYPACKET_ENDOFFRAME = 7, + REPLAYPACKET_TIMER = 8, + REPLAYPACKET_BULLET_TRACES = 9 + }; + + enum { + REPLAYBUFFER_UNUSED = 0, + REPLAYBUFFER_PLAYBACK = 1, + REPLAYBUFFER_RECORD = 2 + }; + + enum { + NUM_REPLAYBUFFERS = 8, + REPLAYBUFFERSIZE = 100000 + }; + + + struct tGeneralPacket + { + uint8 type; + bool in_rcvehicle; + CMatrix camera_pos; + CVector player_pos; + }; + + VALIDATE_SIZE(tGeneralPacket, 88); + + struct tClockPacket + { + uint8 type; + uint8 hours; + uint8 minutes; + private: + uint8 __align; + }; + VALIDATE_SIZE(tClockPacket, 4); + + struct tWeatherPacket + { + uint8 type; + uint8 old_weather; + uint8 new_weather; + float interpolation; + }; + VALIDATE_SIZE(tWeatherPacket, 8); + + struct tTimerPacket + { + uint8 type; + uint32 timer; + }; + VALIDATE_SIZE(tTimerPacket, 8); + + struct tPedHeaderPacket + { + uint8 type; + uint8 index; + uint16 mi; + uint8 pedtype; + private: + uint8 __align[3]; + }; + VALIDATE_SIZE(tPedHeaderPacket, 8); + + struct tBulletTracePacket + { + uint8 type; + uint8 frames; + uint8 lifetime; + uint8 index; + CVector inf; + CVector sup; + }; + VALIDATE_SIZE(tBulletTracePacket, 28); + + struct tEndOfFramePacket + { + uint8 type; + private: + uint8 __align[3]; + }; + VALIDATE_SIZE(tEndOfFramePacket, 4); + + struct tPedUpdatePacket + { + uint8 type; + uint8 index; + int8 heading; + int8 vehicle_index; + CStoredAnimationState anim_state; + CCompressedMatrixNotAligned matrix; + int8 assoc_group_id; + uint8 weapon_model; + }; + VALIDATE_SIZE(tPedUpdatePacket, 40); + + struct tVehicleUpdatePacket + { + uint8 type; + uint8 index; + uint8 health; + uint8 acceleration; + CCompressedMatrixNotAligned matrix; + int8 door_angles[2]; + uint16 mi; + uint32 panels; + int8 velocityX; + int8 velocityY; + int8 velocityZ; + union { + int8 car_gun; + int8 wheel_state; + }; + uint8 wheel_susp_dist[4]; + uint8 wheel_rotation[4]; + uint8 door_status; + uint8 primary_color; + uint8 secondary_color; + }; + VALIDATE_SIZE(tVehicleUpdatePacket, 48); + +private: + static uint8 Mode; + static CAddressInReplayBuffer Record; + static CAddressInReplayBuffer Playback; + static uint8* pBuf0; + static CAutomobile* pBuf1; + static uint8* pBuf2; + static CPlayerPed* pBuf3; + static uint8* pBuf4; + static CCutsceneHead* pBuf5; + static uint8* pBuf6; + static CPtrNode* pBuf7; + static uint8* pBuf8; + static CEntryInfoNode* pBuf9; + static uint8* pBuf10; + static CDummyPed* pBuf11; + static uint8* pRadarBlips; + static uint8* pStoredCam; + static uint8* pWorld1; + static CReference* pEmptyReferences; + static CStoredDetailedAnimationState* pPedAnims; + static uint8* pPickups; + static uint8* pReferences; + static uint8 BufferStatus[NUM_REPLAYBUFFERS]; + static uint8 Buffers[NUM_REPLAYBUFFERS][REPLAYBUFFERSIZE]; + static bool bPlayingBackFromFile; + static bool bReplayEnabled; + static uint32 SlowMotion; + static uint32 FramesActiveLookAroundCam; + static bool bDoLoadSceneWhenDone; + static CPtrNode* WorldPtrList; + static CPtrNode* BigBuildingPtrList; + static CWanted PlayerWanted; + static CPlayerInfo PlayerInfo; + static uint32 Time1; + static uint32 Time2; + static uint32 Time3; + static uint32 Time4; + static uint32 Frame; + static uint8 ClockHours; + static uint8 ClockMinutes; + static uint16 OldWeatherType; + static uint16 NewWeatherType; + static float WeatherInterpolationValue; + static float TimeStepNonClipped; + static float TimeStep; + static float TimeScale; + static float CameraFixedX; + static float CameraFixedY; + static float CameraFixedZ; + static int32 OldRadioStation; + static int8 CameraMode; + static bool bAllowLookAroundCam; + static float LoadSceneX; + static float LoadSceneY; + static float LoadSceneZ; + static float CameraFocusX; + static float CameraFocusY; + static float CameraFocusZ; + static bool bPlayerInRCBuggy; + static float fDistanceLookAroundCam; + static float fAlphaAngleLookAroundCam; + static float fBetaAngleLookAroundCam; +#ifdef FIX_BUGS + static uint8* pGarages; + static CFire* FireArray; + static uint32 NumOfFires; + static uint8* paProjectileInfo; + static uint8* paProjectiles; + static int nHandleOfPlayerPed[NUMPLAYERS]; +#endif + +public: + static void Init(void) REPLAY_STUB; + static void DisableReplays(void) REPLAY_STUB; + static void EnableReplays(void) REPLAY_STUB; + static void Update(void) REPLAY_STUB; + static void FinishPlayback(void) REPLAY_STUB; + static void EmptyReplayBuffer(void) REPLAY_STUB; + static void Display(void) REPLAY_STUB; + static void TriggerPlayback(uint8 cam_mode, float cam_x, float cam_y, float cam_z, bool load_scene) REPLAY_STUB; + static void StreamAllNecessaryCarsAndPeds(void) REPLAY_STUB; + +#ifndef GTA_REPLAY + static bool ShouldStandardCameraBeProcessed(void) { return true; } + static bool IsPlayingBack() { return false; } + static bool IsPlayingBackFromFile() { return false; } +#else + static bool ShouldStandardCameraBeProcessed(void); + static bool IsPlayingBack() { return Mode == MODE_PLAYBACK; } + static bool IsPlayingBackFromFile() { return bPlayingBackFromFile; } +private: + static void RecordThisFrame(void); + static void StorePedUpdate(CPed *ped, int id); + static void StorePedAnimation(CPed *ped, CStoredAnimationState *state); + static void StoreDetailedPedAnimation(CPed *ped, CStoredDetailedAnimationState *state); + static void ProcessPedUpdate(CPed *ped, float interpolation, CAddressInReplayBuffer *buffer); + static void RetrievePedAnimation(CPed *ped, CStoredAnimationState *state); + static void RetrieveDetailedPedAnimation(CPed *ped, CStoredDetailedAnimationState *state); + static void PlaybackThisFrame(void); + static void TriggerPlaybackLastCoupleOfSeconds(uint32, uint8, float, float, float, uint32); + static bool FastForwardToTime(uint32); + static void StoreCarUpdate(CVehicle *vehicle, int id); + static void ProcessCarUpdate(CVehicle *vehicle, float interpolation, CAddressInReplayBuffer *buffer); + static bool PlayBackThisFrameInterpolation(CAddressInReplayBuffer *buffer, float interpolation, uint32 *pTimer); + static void ProcessReplayCamera(void); + static void StoreStuffInMem(void); + static void RestoreStuffFromMem(void); + static void EmptyPedsAndVehiclePools(void); + static void EmptyAllPools(void); + static void MarkEverythingAsNew(void); + static void SaveReplayToHD(void); + static void PlayReplayFromHD(void); // out of class in III PC and later because of SecuROM + static void FindFirstFocusCoordinate(CVector *coord); + static void ProcessLookAroundCam(void); + static size_t FindSizeOfPacket(uint8); +#endif +}; diff --git a/src/control/Restart.cpp b/src/control/Restart.cpp new file mode 100644 index 0000000..2f5e3d4 --- /dev/null +++ b/src/control/Restart.cpp @@ -0,0 +1,249 @@ +#include "common.h" + +#include "Restart.h" +#include "SaveBuf.h" +#include "Zones.h" +#include "PathFind.h" + +uint8 CRestart::OverrideHospitalLevel; +uint8 CRestart::OverridePoliceStationLevel; +bool CRestart::bFadeInAfterNextArrest; +bool CRestart::bFadeInAfterNextDeath; + +bool CRestart::bOverrideRestart; +CVector CRestart::OverridePosition; +float CRestart::OverrideHeading; + +CVector CRestart::HospitalRestartPoints[NUM_RESTART_POINTS]; +float CRestart::HospitalRestartHeadings[NUM_RESTART_POINTS]; +uint16 CRestart::NumberOfHospitalRestarts; + +CVector CRestart::PoliceRestartPoints[NUM_RESTART_POINTS]; +float CRestart::PoliceRestartHeadings[NUM_RESTART_POINTS]; +uint16 CRestart::NumberOfPoliceRestarts; + +void +CRestart::Initialise() +{ + OverridePoliceStationLevel = LEVEL_GENERIC; + OverrideHospitalLevel = LEVEL_GENERIC; + bFadeInAfterNextArrest = true; + bFadeInAfterNextDeath = true; + OverrideHeading = 0.0f; + OverridePosition = CVector(0.0f, 0.0f, 0.0f); + bOverrideRestart = false; + NumberOfPoliceRestarts = 0; + NumberOfHospitalRestarts = 0; + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + HospitalRestartPoints[i] = CVector(0.0f, 0.0f, 0.0f); + HospitalRestartHeadings[i] = 0.0f; + PoliceRestartPoints[i] = CVector(0.0f, 0.0f, 0.0f); + PoliceRestartHeadings[i] = 0.0f; + } +} + +void +CRestart::AddHospitalRestartPoint(const CVector &pos, float heading) +{ + HospitalRestartPoints[NumberOfHospitalRestarts] = pos; + HospitalRestartHeadings[NumberOfHospitalRestarts++] = heading; +} + +void +CRestart::AddPoliceRestartPoint(const CVector &pos, float heading) +{ + PoliceRestartPoints[NumberOfPoliceRestarts] = pos; + PoliceRestartHeadings[NumberOfPoliceRestarts++] = heading; +} + +void +CRestart::OverrideNextRestart(const CVector &pos, float heading) +{ + bOverrideRestart = true; + OverridePosition = pos; + OverrideHeading = heading; +} + +void +CRestart::CancelOverrideRestart() +{ + bOverrideRestart = false; +} + +void +CRestart::FindClosestHospitalRestartPoint(const CVector &pos, CVector *outPos, float *outHeading) +{ + if (bOverrideRestart) { + *outPos = OverridePosition; + *outHeading = OverrideHeading; + CancelOverrideRestart(); + return; + } + + eLevelName curlevel = CTheZones::FindZoneForPoint(pos); + float fMinDist = SQR(4000.0f); + int closestPoint = NUM_RESTART_POINTS; + + // find closest point on this level + for (int i = 0; i < NumberOfHospitalRestarts; i++) { + if (CTheZones::FindZoneForPoint(HospitalRestartPoints[i]) == (OverrideHospitalLevel != LEVEL_GENERIC ? OverrideHospitalLevel : curlevel)) { + float dist = (pos - HospitalRestartPoints[i]).MagnitudeSqr(); + if (fMinDist >= dist) { + fMinDist = dist; + closestPoint = i; + } + } + } + + // if we didn't find anything, find closest point on any level + if (closestPoint == NUM_RESTART_POINTS) { + for (int i = 0; i < NumberOfHospitalRestarts; i++) { + float dist = (pos - HospitalRestartPoints[i]).MagnitudeSqr(); + if (fMinDist >= dist) { + fMinDist = dist; + closestPoint = i; + } + } + } + + // if we still didn't find anything, find closest path node + if (closestPoint == NUM_RESTART_POINTS) { + *outPos = ThePaths.m_pathNodes[ThePaths.FindNodeClosestToCoors(pos, PATH_PED, 999999.9f)].GetPosition(); + *outHeading = 0.0f; + printf("Couldn't find a hospital restart zone near the player %f %f %f->%f %f %f\n", pos.x, pos.y, pos.z, outPos->x, outPos->y, outPos->z); + } else { + *outPos = HospitalRestartPoints[closestPoint]; + *outHeading = HospitalRestartHeadings[closestPoint]; + } +} + +void +CRestart::FindClosestPoliceRestartPoint(const CVector &pos, CVector *outPos, float *outHeading) +{ + if (bOverrideRestart) { + *outPos = OverridePosition; + *outHeading = OverrideHeading; + CancelOverrideRestart(); + return; + } + + eLevelName curlevel = CTheZones::FindZoneForPoint(pos); + float fMinDist = SQR(4000.0f); + int closestPoint = NUM_RESTART_POINTS; + + // find closest point on this level + for (int i = 0; i < NumberOfPoliceRestarts; i++) { + if (CTheZones::FindZoneForPoint(PoliceRestartPoints[i]) == (OverridePoliceStationLevel != LEVEL_GENERIC ? OverridePoliceStationLevel : curlevel)) { + float dist = (pos - PoliceRestartPoints[i]).MagnitudeSqr(); + if (fMinDist >= dist) { + fMinDist = dist; + closestPoint = i; + } + } + } + + // if we didn't find anything, find closest point on any level + if (closestPoint == NUM_RESTART_POINTS) { + for (int i = 0; i < NumberOfPoliceRestarts; i++) { + float dist = (pos - PoliceRestartPoints[i]).MagnitudeSqr(); + if (fMinDist >= dist) { + fMinDist = dist; + closestPoint = i; + } + } + } + + // if we still didn't find anything, find closest path node + if (closestPoint == NUM_RESTART_POINTS) { + printf("Couldn't find a police restart zone near the player\n"); + *outPos = ThePaths.m_pathNodes[ThePaths.FindNodeClosestToCoors(pos, PATH_PED, 999999.9f)].GetPosition(); + *outHeading = 0.0f; + } else { + *outPos = PoliceRestartPoints[closestPoint]; + *outHeading = PoliceRestartHeadings[closestPoint]; + } +} + +void +CRestart::LoadAllRestartPoints(uint8 *buf, uint32 size) +{ + Initialise(); + +INITSAVEBUF + CheckSaveHeader(buf, 'R','S','T','\0', size - SAVE_HEADER_SIZE); + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + ReadSaveBuf(&HospitalRestartPoints[i], buf); + ReadSaveBuf(&HospitalRestartHeadings[i], buf); + } + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + ReadSaveBuf(&PoliceRestartPoints[i], buf); + ReadSaveBuf(&PoliceRestartHeadings[i], buf); + } + + ReadSaveBuf(&NumberOfHospitalRestarts, buf); + ReadSaveBuf(&NumberOfPoliceRestarts, buf); + ReadSaveBuf(&bOverrideRestart, buf); + + // skip something unused + SkipSaveBuf(buf, 3); + + ReadSaveBuf(&OverridePosition, buf); + ReadSaveBuf(&OverrideHeading, buf); + ReadSaveBuf(&bFadeInAfterNextDeath, buf); + ReadSaveBuf(&bFadeInAfterNextArrest, buf); + ReadSaveBuf(&OverrideHospitalLevel, buf); + ReadSaveBuf(&OverridePoliceStationLevel, buf); +VALIDATESAVEBUF(size); +} + +void +CRestart::SaveAllRestartPoints(uint8 *buf, uint32 *size) +{ + *size = SAVE_HEADER_SIZE + + sizeof(HospitalRestartPoints) + + sizeof(HospitalRestartHeadings) + + sizeof(PoliceRestartPoints) + + sizeof(PoliceRestartHeadings) + + sizeof(NumberOfHospitalRestarts) + + sizeof(NumberOfPoliceRestarts) + + sizeof(bOverrideRestart) + + sizeof(uint8) + + sizeof(uint16) + + sizeof(OverridePosition) + + sizeof(OverrideHeading) + + sizeof(bFadeInAfterNextDeath) + + sizeof(bFadeInAfterNextArrest) + + sizeof(OverrideHospitalLevel) + + sizeof(OverridePoliceStationLevel); // == 292 + +INITSAVEBUF + WriteSaveHeader(buf, 'R','S','T','\0', *size - SAVE_HEADER_SIZE); + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + WriteSaveBuf(buf, HospitalRestartPoints[i]); + WriteSaveBuf(buf, HospitalRestartHeadings[i]); + } + + for (int i = 0; i < NUM_RESTART_POINTS; i++) { + WriteSaveBuf(buf, PoliceRestartPoints[i]); + WriteSaveBuf(buf, PoliceRestartHeadings[i]); + } + + WriteSaveBuf(buf, NumberOfHospitalRestarts); + WriteSaveBuf(buf, NumberOfPoliceRestarts); + WriteSaveBuf(buf, bOverrideRestart); + + WriteSaveBuf(buf, (uint8)0); + WriteSaveBuf(buf, (uint16)0); + + WriteSaveBuf(buf, OverridePosition); + WriteSaveBuf(buf, OverrideHeading); + WriteSaveBuf(buf, bFadeInAfterNextDeath); + WriteSaveBuf(buf, bFadeInAfterNextArrest); + WriteSaveBuf(buf, OverrideHospitalLevel); + WriteSaveBuf(buf, OverridePoliceStationLevel); +VALIDATESAVEBUF(*size); +} diff --git a/src/control/Restart.h b/src/control/Restart.h new file mode 100644 index 0000000..5d84c72 --- /dev/null +++ b/src/control/Restart.h @@ -0,0 +1,36 @@ +#pragma once + +#define NUM_RESTART_POINTS 8 + +class CRestart +{ +public: + static void AddPoliceRestartPoint(const CVector&, float); + static void AddHospitalRestartPoint(const CVector&, float); + static void OverrideNextRestart(const CVector&, float); + + static void FindClosestHospitalRestartPoint(const CVector &, CVector *, float *); + static void FindClosestPoliceRestartPoint(const CVector &, CVector *, float *); + static void Initialise(); + static void CancelOverrideRestart(); + + static void LoadAllRestartPoints(uint8 *buf, uint32 size); + static void SaveAllRestartPoints(uint8 *buf, uint32 *size); + + static uint8 OverrideHospitalLevel; + static uint8 OverridePoliceStationLevel; + static bool bFadeInAfterNextArrest; + static bool bFadeInAfterNextDeath; + + static bool bOverrideRestart; + static CVector OverridePosition; + static float OverrideHeading; + + static CVector HospitalRestartPoints[NUM_RESTART_POINTS]; + static float HospitalRestartHeadings[NUM_RESTART_POINTS]; + static uint16 NumberOfHospitalRestarts; + + static CVector PoliceRestartPoints[NUM_RESTART_POINTS]; + static float PoliceRestartHeadings[NUM_RESTART_POINTS]; + static uint16 NumberOfPoliceRestarts; +}; diff --git a/src/control/RoadBlocks.cpp b/src/control/RoadBlocks.cpp new file mode 100644 index 0000000..4c3307c --- /dev/null +++ b/src/control/RoadBlocks.cpp @@ -0,0 +1,197 @@ +#include "common.h" + +#include "RoadBlocks.h" +#include "PathFind.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "World.h" +#include "PedPlacement.h" +#include "Automobile.h" +#include "CopPed.h" +#include "VisibilityPlugins.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "General.h" + +#define ROADBLOCKDIST (80.0f) + +int16 CRoadBlocks::NumRoadBlocks; +int16 CRoadBlocks::RoadBlockObjects[NUMROADBLOCKS]; +bool CRoadBlocks::InOrOut[NUMROADBLOCKS]; + +void +CRoadBlocks::Init(void) +{ + int i; + NumRoadBlocks = 0; + for (i = 0; i < ThePaths.m_numMapObjects; i++) { + if (ThePaths.m_objectFlags[i] & UseInRoadBlock) { + if (NumRoadBlocks < NUMROADBLOCKS) { + InOrOut[NumRoadBlocks] = true; + RoadBlockObjects[NumRoadBlocks] = i; + NumRoadBlocks++; + } else { +#ifndef MASTER + printf("Not enough room for the potential roadblocks\n"); +#endif + // FIX: Don't iterate loop after NUMROADBLOCKS + return; + } + } + } +} + +void +CRoadBlocks::GenerateRoadBlockCopsForCar(CVehicle* pVehicle, int32 roadBlockType, int16 roadBlockNode) +{ + static const CVector vecRoadBlockOffets[6] = { CVector(-1.5, 1.8f, 0.0f), CVector(-1.5f, -1.8f, 0.0f), CVector(1.5f, 1.8f, 0.0f), + CVector(1.5f, -1.8f, 0.0f), CVector(-1.5f, 0.0f, 0.0f), CVector(1.5, 0.0, 0.0) }; + CEntity* pEntityToAttack = (CEntity*)FindPlayerVehicle(); + if (!pEntityToAttack) + pEntityToAttack = (CEntity*)FindPlayerPed(); + CColModel* pPoliceColModel = CModelInfo::GetColModel(MI_POLICE); + float fRadius = pVehicle->GetBoundRadius() / pPoliceColModel->boundingSphere.radius; + for (int32 i = 0; i < 2; i++) { + const int32 roadBlockIndex = i + 2 * roadBlockType; + CVector posForZ = pVehicle->GetMatrix() * (fRadius * vecRoadBlockOffets[roadBlockIndex]); + int32 modelInfoId = MI_COP; + eCopType copType = COP_STREET; + switch (pVehicle->GetModelIndex()) + { + case MI_FBICAR: + modelInfoId = MI_FBI; + copType = COP_FBI; + break; + case MI_ENFORCER: + modelInfoId = MI_SWAT; + copType = COP_SWAT; + break; + case MI_BARRACKS: + modelInfoId = MI_ARMY; + copType = COP_ARMY; + break; + } + if (!CStreaming::HasModelLoaded(modelInfoId)) + copType = COP_STREET; + CCopPed* pCopPed = new CCopPed(copType); + if (copType == COP_STREET) + pCopPed->SetCurrentWeapon(WEAPONTYPE_COLT45); + CPedPlacement::FindZCoorForPed(&posForZ); + pCopPed->SetPosition(posForZ); + pCopPed->SetOrientation(0.0f, 0.0f, -HALFPI); + pCopPed->m_bIsDisabledCop = true; + pCopPed->SetIdle(); + pCopPed->bKindaStayInSamePlace = true; + pCopPed->bNotAllowedToDuck = false; + pCopPed->m_nRoadblockNode = roadBlockNode; + pCopPed->bCrouchWhenShooting = roadBlockType != 2; + if (pEntityToAttack) { + pCopPed->SetWeaponLockOnTarget(pEntityToAttack); + pCopPed->SetAttack(pEntityToAttack); + } + pCopPed->m_pMyVehicle = pVehicle; + pVehicle->RegisterReference((CEntity**)&pCopPed->m_pMyVehicle); + pCopPed->bCullExtraFarAway = true; + CVisibilityPlugins::SetClumpAlpha(pCopPed->GetClump(), 0); + CWorld::Add(pCopPed); + } +} + +void +CRoadBlocks::GenerateRoadBlocks(void) +{ +#ifdef SQUEEZE_PERFORMANCE + if (FindPlayerPed()->m_pWanted->m_RoadblockDensity == 0) + return; +#endif + CMatrix offsetMatrix; + uint32 frame = CTimer::GetFrameCounter() & 0xF; + int16 nRoadblockNode = (int16)(NUMROADBLOCKS * frame) / 16; + const int16 maxRoadBlocks = (int16)(NUMROADBLOCKS * (frame + 1)) / 16; + for (; nRoadblockNode < Min(NumRoadBlocks, maxRoadBlocks); nRoadblockNode++) { + CTreadable *mapObject = ThePaths.m_mapObjects[RoadBlockObjects[nRoadblockNode]]; + CVector2D vecDistance = FindPlayerCoors() - mapObject->GetPosition(); + if (vecDistance.x > -ROADBLOCKDIST && vecDistance.x < ROADBLOCKDIST && + vecDistance.y > -ROADBLOCKDIST && vecDistance.y < ROADBLOCKDIST && + vecDistance.Magnitude() < ROADBLOCKDIST) { + if (!InOrOut[nRoadblockNode]) { + InOrOut[nRoadblockNode] = true; + if (FindPlayerVehicle() && (CGeneral::GetRandomNumber() & 0x7F) < FindPlayerPed()->m_pWanted->m_RoadblockDensity) { + CWanted *pPlayerWanted = FindPlayerPed()->m_pWanted; + float fMapObjectRadius = 2.0f * mapObject->GetColModel()->boundingBox.max.x; + int32 vehicleId = MI_POLICE; + if (pPlayerWanted->AreArmyRequired()) + vehicleId = MI_BARRACKS; + else if (pPlayerWanted->AreFbiRequired()) + vehicleId = MI_FBICAR; + else if (pPlayerWanted->AreSwatRequired()) + vehicleId = MI_ENFORCER; + if (!CStreaming::HasModelLoaded(vehicleId)) + vehicleId = MI_POLICE; + CColModel *pVehicleColModel = CModelInfo::GetColModel(vehicleId); + float fModelRadius = 2.0f * pVehicleColModel->boundingSphere.radius + 0.25f; + int16 radius = (int16)(fMapObjectRadius / fModelRadius); + if (radius >= 6) + continue; + CVector2D vecDistanceToCamera = TheCamera.GetPosition() - mapObject->GetPosition(); + float fDotProduct = DotProduct2D(vecDistanceToCamera, mapObject->GetForward()); + float fOffset = 0.5f * fModelRadius * (float)(radius - 1); + for (int16 i = 0; i < radius; i++) { + uint8 nRoadblockType = fDotProduct < 0.0f; + if (CGeneral::GetRandomNumber() & 1) { + offsetMatrix.SetRotateZ(((CGeneral::GetRandomNumber() & 0xFF) - 128.0f) * 0.003f + HALFPI); + } + else { + nRoadblockType = !nRoadblockType; + offsetMatrix.SetRotateZ(((CGeneral::GetRandomNumber() & 0xFF) - 128.0f) * 0.003f - HALFPI); + } + if (ThePaths.m_objectFlags[RoadBlockObjects[nRoadblockNode]] & ObjectEastWest) + offsetMatrix.GetPosition() = CVector(0.0f, i * fModelRadius - fOffset, 0.6f); + else + offsetMatrix.GetPosition() = CVector(i * fModelRadius - fOffset, 0.0f, 0.6f); + CMatrix vehicleMatrix = mapObject->GetMatrix() * offsetMatrix; + float fModelRadius = CModelInfo::GetColModel(vehicleId)->boundingSphere.radius - 0.25f; + int16 colliding = 0; + CWorld::FindObjectsKindaColliding(vehicleMatrix.GetPosition(), fModelRadius, 0, &colliding, 2, nil, false, true, true, false, false); + if (!colliding) { + CAutomobile *pVehicle = new CAutomobile(vehicleId, RANDOM_VEHICLE); + pVehicle->SetStatus(STATUS_ABANDONED); + // pVehicle->GetHeightAboveRoad(); // called but return value is ignored? + vehicleMatrix.GetPosition().z += fModelRadius - 0.6f; + pVehicle->SetMatrix(vehicleMatrix); + pVehicle->PlaceOnRoadProperly(); + pVehicle->SetIsStatic(false); + pVehicle->GetMatrix().UpdateRW(); + pVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + CCarCtrl::JoinCarWithRoadSystem(pVehicle); + pVehicle->bIsLocked = false; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nCurrentLane = 0; + pVehicle->AutoPilot.m_nNextLane = 0; + pVehicle->AutoPilot.m_fMaxTrafficSpeed = 0.0f; + pVehicle->AutoPilot.m_nCruiseSpeed = 0.0f; + pVehicle->bExtendedRange = true; + if (pVehicle->UsesSiren(pVehicle->GetModelIndex()) && CGeneral::GetRandomNumber() & 1) + pVehicle->m_bSirenOrAlarm = true; + if (pVehicle->GetUp().z > 0.94f) { + CVisibilityPlugins::SetClumpAlpha(pVehicle->GetClump(), 0); + CWorld::Add(pVehicle); + pVehicle->bCreateRoadBlockPeds = true; + pVehicle->m_nRoadblockType = nRoadblockType; + pVehicle->m_nRoadblockNode = nRoadblockNode; + } + else { + delete pVehicle; + } + } + } + } + } + } else { + InOrOut[nRoadblockNode] = false; + } + } +} diff --git a/src/control/RoadBlocks.h b/src/control/RoadBlocks.h new file mode 100644 index 0000000..0f0c188 --- /dev/null +++ b/src/control/RoadBlocks.h @@ -0,0 +1,16 @@ +#pragma once +#include "common.h" + +class CVehicle; + +class CRoadBlocks +{ +public: + static int16 NumRoadBlocks; + static int16 RoadBlockObjects[NUMROADBLOCKS]; + static bool InOrOut[NUMROADBLOCKS]; + + static void Init(void); + static void GenerateRoadBlockCopsForCar(CVehicle* pVehicle, int32 roadBlockType, int16 roadBlockNode); + static void GenerateRoadBlocks(void); +}; diff --git a/src/control/SceneEdit.cpp b/src/control/SceneEdit.cpp new file mode 100644 index 0000000..42dadee --- /dev/null +++ b/src/control/SceneEdit.cpp @@ -0,0 +1,1127 @@ +#include "common.h" + +#include "SceneEdit.h" +#ifdef GTA_SCENE_EDIT +#include "Automobile.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "CivilianPed.h" +#include "FileMgr.h" +#include "Font.h" +#include "ModelIndices.h" +#include "ModelInfo.h" +#include "Pad.h" +#include "Ped.h" +#include "Population.h" +#include "Text.h" +#include "Timecycle.h" +#include "Streaming.h" +#include "Vehicle.h" +#include "WeaponInfo.h" +#include "World.h" + +bool CSceneEdit::m_bEditOn; +int32 CSceneEdit::m_bCameraFollowActor; +bool CSceneEdit::m_bRecording; +CVector CSceneEdit::m_vecCurrentPosition; +CVector CSceneEdit::m_vecCamHeading; +CVector CSceneEdit::m_vecGotoPosition; +int32 CSceneEdit::m_nVehicle; +int32 CSceneEdit::m_nVehicle2; +int32 CSceneEdit::m_nActor; +int32 CSceneEdit::m_nActor2; +int32 CSceneEdit::m_nVehiclemodelId; +int32 CSceneEdit::m_nPedmodelId; +int16 CSceneEdit::m_nCurrentMovieCommand; +int16 CSceneEdit::m_nNumActors; +int16 CSceneEdit::m_nNumMovieCommands; +int16 CSceneEdit::m_nCurrentCommand; +int16 CSceneEdit::m_nCurrentVehicle; +int16 CSceneEdit::m_nCurrentActor; +int16 CSceneEdit::m_nWeaponType; +bool CSceneEdit::m_bCommandActive; +bool CSceneEdit::m_bActorSelected; +bool CSceneEdit::m_bActor2Selected; +bool CSceneEdit::m_bVehicleSelected; +int16 CSceneEdit::m_nNumVehicles; +CPed* CSceneEdit::pActors[NUM_ACTORS_IN_MOVIE]; +CVehicle* CSceneEdit::pVehicles[NUM_VEHICLES_IN_MOVIE]; +bool CSceneEdit::m_bDrawGotoArrow; +CMovieCommand CSceneEdit::Movie[NUM_COMMANDS_IN_MOVIE]; + +#define SHADOW_OFFSET (2.0f) +#define ACTION_MESSAGE_X_RIGHT (60.0f) +#define ACTION_MESSAGE_Y (8.0f) +#define SELECTED_MESSAGE_X_RIGHT (60.0f) +#define SELECTED_MESSAGE_Y (248.0f) +#define COMMAND_NAME_X_RIGHT (60.0f) +#define COMMAND_NAME_Y (38.0f) +#define COMMAND_NAME_HEIGHT (16.0f) + +#define NUM_COMMANDS_TO_DRAW (9) + +static const char* pCommandStrings[] = { + "do-nothing", "New Actor", "Move Actor", "Select Actor", "Delete Actor", + "New Vehicle", "Move Vehicle", "Select Vehicle", "Delete Vehicle", "Give Weapon", + "Goto", "Goto (wait)", "Get In Car", "Get Out Car", "Kill", + "Flee", "Wait", "Position Camera", "Set Camera Target", "Select Camera Mode", + "Save Movie", "Load Movie", "Play Movie", "END" +}; + +#ifdef CHECK_STRUCT_SIZES +static_assert(ARRAY_SIZE(pCommandStrings) == CSceneEdit::MOVIE_TOTAL_COMMANDS, "Scene edit: not all commands have names"); +#endif + +static int32 NextValidModelId(int32 mi, int32 step) +{ + int32 result = -1; + int32 i = mi; + while (result == -1) { + i += step; + if (i < 0 || i > MODELINFOSIZE) { + step = -step; + continue; + } + CBaseModelInfo* pInfo = CModelInfo::GetModelInfo(i); + CVehicleModelInfo* pVehicleInfo = (CVehicleModelInfo*)pInfo; + if (!pInfo) + continue; + if (pInfo->GetModelType() == MITYPE_PED +#ifdef FIX_BUGS + && !(i >= MI_SPECIAL01 && i <= MI_SPECIAL04) +#endif + || pInfo->GetModelType() == MITYPE_VEHICLE && +#ifdef FIX_BUGS + (pVehicleInfo->m_vehicleType == VEHICLE_TYPE_CAR || pVehicleInfo->m_vehicleType == VEHICLE_TYPE_BOAT)) +#else // && and || priority failure it seems, also crashes on special models + pVehicleInfo->m_vehicleType == VEHICLE_TYPE_CAR || pVehicleInfo->m_vehicleType == VEHICLE_TYPE_BOAT) +#endif + result = i; + } + return result; +} + +void CSceneEdit::LoadMovie(void) +{ + ReInitialise(); + CFileMgr::SetDir("DATA"); + int fid = CFileMgr::OpenFile("movie.dat", "r"); +#ifdef FIX_BUGS + if (fid >= 0) +#endif + { + CFileMgr::Read(fid, (char*)&Movie, sizeof(Movie)); + CFileMgr::Read(fid, (char*)&m_nNumMovieCommands, sizeof(m_nNumMovieCommands)); + CFileMgr::CloseFile(fid); + } + CFileMgr::SetDir(""); + m_bCommandActive = false; +} + +void CSceneEdit::SaveMovie(void) +{ + CFileMgr::SetDir("DATA"); + int fid = CFileMgr::OpenFileForWriting("movie.dat"); + if (fid >= 0) { + CFileMgr::Write(fid, (char*)&Movie, sizeof(Movie)); + CFileMgr::Write(fid, (char*)&m_nNumMovieCommands, sizeof(m_nNumMovieCommands)); + CFileMgr::CloseFile(fid); + } + CFileMgr::SetDir(""); + m_bCommandActive = false; +} + +void CSceneEdit::Initialise(void) +{ + m_nActor = -1; + m_nActor2 = -1; + m_nVehicle = -1; + m_nVehicle2 = -1; + m_nCurrentCommand = MOVIE_NEW_ACTOR; + m_nVehiclemodelId = MI_INFERNUS; + m_nPedmodelId = MI_MALE01; + m_nNumVehicles = 0; + m_nNumActors = 0; + m_nNumMovieCommands = 0; + m_bCommandActive = false; + m_bRecording = true; + m_bEditOn = false; + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) + pActors[i] = nil; + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) + pVehicles[i] = nil; + m_vecCamHeading = TheCamera.Cams[TheCamera.ActiveCam].Front; + m_vecGotoPosition = CVector(0.0f, 0.0f, 0.0f); + m_bCameraFollowActor = false; + TheCamera.Cams[TheCamera.ActiveCam].ResetStatics = true; + m_bDrawGotoArrow = false; +} + +void CSceneEdit::InitPlayback(void) +{ + m_nVehiclemodelId = MI_INFERNUS; + m_nPedmodelId = MI_MALE01; + m_bCommandActive = false; + m_nNumActors = 0; + m_nNumVehicles = 0; + m_nActor = -1; + m_nActor2 = -1; + m_nVehicle = -1; + m_nVehicle2 = -1; + TheCamera.Cams[TheCamera.ActiveCam].ResetStatics = true; + m_vecCamHeading = TheCamera.Cams[TheCamera.ActiveCam].Front; + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i]) { + CPopulation::RemovePed(pActors[i]); + pActors[i] = nil; + } + } + m_nCurrentActor = 0; + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) { + if (pVehicles[i]) { + CWorld::Remove(pVehicles[i]); + delete pVehicles[i]; + pVehicles[i] = nil; + } + } + m_nCurrentVehicle = 0; + m_vecGotoPosition = CVector(0.0f, 0.0f, 0.0f); + m_nCurrentMovieCommand = MOVIE_DO_NOTHING; + m_bDrawGotoArrow = false; +} + +void CSceneEdit::ReInitialise(void) +{ + m_nVehiclemodelId = MI_INFERNUS; + m_nPedmodelId = MI_MALE01; + m_nCurrentCommand = MOVIE_NEW_ACTOR; + m_bEditOn = true; + m_bRecording = true; + m_bCommandActive = false; +#ifdef FIX_BUGS + m_bCameraFollowActor = false; + TheCamera.Cams[TheCamera.ActiveCam].ResetStatics = true; // not enough... +#endif + m_nActor = -1; + m_nActor2 = -1; + m_nVehicle = -1; + m_nVehicle2 = -1; + m_nNumMovieCommands = 0; + m_nCurrentMovieCommand = MOVIE_DO_NOTHING; + m_nNumActors = 0; + m_nNumVehicles = 0; + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i]) { + CPopulation::RemovePed(pActors[i]); + pActors[i] = nil; + } + } + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) { + if (pVehicles[i]) { + CWorld::Remove(pVehicles[i]); + delete pVehicles[i]; + pVehicles[i] = nil; + } + } + for (int i = 0; i < NUM_COMMANDS_IN_MOVIE; i++) { + Movie[i].m_nCommandId = MOVIE_DO_NOTHING; + Movie[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + Movie[i].m_vecCamera = CVector(0.0f, 0.0f, 0.0f); + Movie[i].m_nActorId = -1; + Movie[i].m_nActor2Id = -1; + Movie[i].m_nVehicleId = -1; + Movie[i].m_nModelIndex = 0; + } + m_vecGotoPosition = CVector(0.0f, 0.0f, 0.0f); + m_bDrawGotoArrow = false; +} + +void CSceneEdit::Update(void) +{ + if (!m_bEditOn) + return; + if (m_bRecording) + ProcessCommand(); + else { + if (m_bCameraFollowActor && m_nActor != -1) { + if (pActors[m_nActor]->bInVehicle) + TheCamera.TakeControl(pActors[m_nActor]->m_pMyVehicle, CCam::MODE_BEHINDCAR, JUMP_CUT, CAMCONTROL_SCRIPT); + else + TheCamera.TakeControl(pActors[m_nActor], CCam::MODE_FOLLOWPED, JUMP_CUT, CAMCONTROL_SCRIPT); + } + PlayBack(); + } +} + +void CSceneEdit::Draw(void) +{ + char str[200]; + wchar wstr[200]; + if (TheCamera.m_WideScreenOn) + return; +#ifndef FIX_BUGS + CFont::SetPropOff(); +#endif + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetCentreOn(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); +#ifdef FIX_BUGS + CFont::SetFontStyle(FONT_BANK); + CFont::SetPropOn(); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetDropShadowPosition(1); +#else + CFont::SetFontStyle(FONT_HEADING); + CFont::SetPropOff(); +#endif + sprintf(str, "Action"); + AsciiToUnicode(str, wstr); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(ACTION_MESSAGE_X_RIGHT - SHADOW_OFFSET), SCREEN_SCALE_Y(ACTION_MESSAGE_Y + SHADOW_OFFSET), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-ACTION_MESSAGE_X_RIGHT) + SHADOW_OFFSET, SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-ACTION_MESSAGE_Y) + SHADOW_OFFSET, wstr); +#endif + CFont::SetColor(CRGBA(193, 164, 120, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(ACTION_MESSAGE_X_RIGHT), SCREEN_SCALE_Y(ACTION_MESSAGE_Y), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-ACTION_MESSAGE_X_RIGHT), SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-ACTION_MESSAGE_Y), wstr); +#endif + sprintf(str, "Selected"); + AsciiToUnicode(str, wstr); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(SELECTED_MESSAGE_X_RIGHT - SHADOW_OFFSET), SCREEN_SCALE_Y(SELECTED_MESSAGE_Y + SHADOW_OFFSET), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-SELECTED_MESSAGE_X_RIGHT) + SHADOW_OFFSET, SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-SELECTED_MESSAGE_Y) + SHADOW_OFFSET, wstr); +#endif + CFont::SetColor(CRGBA(193, 164, 120, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(SELECTED_MESSAGE_X_RIGHT), SCREEN_SCALE_Y(SELECTED_MESSAGE_Y), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-SELECTED_MESSAGE_X_RIGHT), SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-SELECTED_MESSAGE_Y), wstr); +#endif + CFont::SetCentreOff(); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.7f), SCREEN_SCALE_Y(0.7f)); +#else + CFont::SetScale(0.7f, 0.7f); +#endif +#ifdef FIX_BUGS + CFont::SetFontStyle(FONT_BANK); +#else + CFont::SetFontStyle(FONT_HEADING); +#endif + CFont::SetColor(CRGBA(0, 0, 0, 255)); + for (int i = 0; i < NUM_COMMANDS_TO_DRAW; i++) { + int16 nCommandDrawn = m_nCurrentCommand + i - NUM_COMMANDS_TO_DRAW / 2; + if (nCommandDrawn >= MOVIE_TOTAL_COMMANDS) + nCommandDrawn -= (MOVIE_TOTAL_COMMANDS - 1); + if (nCommandDrawn <= MOVIE_DO_NOTHING) + nCommandDrawn += (MOVIE_TOTAL_COMMANDS - 1); + sprintf(str, "%s", pCommandStrings[nCommandDrawn]); + AsciiToUnicode(str, wstr); + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(COMMAND_NAME_X_RIGHT - SHADOW_OFFSET), SCREEN_SCALE_Y(COMMAND_NAME_Y + SHADOW_OFFSET + i * COMMAND_NAME_HEIGHT), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-COMMAND_NAME_X_RIGHT) + SHADOW_OFFSET, SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-COMMAND_NAME_Y) + SHADOW_OFFSET + i * COMMAND_NAME_HEIGHT, wstr); +#endif + if (nCommandDrawn == m_nCurrentCommand) + CFont::SetColor(CRGBA(156, 91, 40, 255)); + else + CFont::SetColor(CRGBA(193, 164, 120, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(COMMAND_NAME_X_RIGHT), SCREEN_SCALE_Y(COMMAND_NAME_Y + i * COMMAND_NAME_HEIGHT), wstr); +#else + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH-COMMAND_NAME_X_RIGHT), SCREEN_SCALE_FROM_BOTTOM(DEFAULT_SCREEN_HEIGHT-COMMAND_NAME_Y) + i * COMMAND_NAME_HEIGHT, wstr); +#endif + } +} + +void CSceneEdit::ProcessCommand(void) +{ + if (!m_bCommandActive) { + ClearForNewCommand(); + if (CPad::GetPad(1)->GetDPadUpJustDown()) { + if (--m_nCurrentCommand == MOVIE_DO_NOTHING) + m_nCurrentCommand = MOVIE_END; + } + if (CPad::GetPad(1)->GetDPadDownJustDown()) { + if (++m_nCurrentCommand == MOVIE_TOTAL_COMMANDS) + m_nCurrentCommand = MOVIE_NEW_ACTOR; + } + if (CPad::GetPad(1)->GetTriangleJustDown()) { + if (m_nCurrentCommand != MOVIE_DO_NOTHING) + m_bCommandActive = true; + } + return; + } + switch (m_nCurrentCommand) { + case MOVIE_DO_NOTHING: + m_bCommandActive = false; + break; + case MOVIE_NEW_ACTOR: + if (m_nActor == -1) { + if (m_nNumActors == NUM_ACTORS_IN_MOVIE) + break; + if (!CStreaming::HasModelLoaded(m_nPedmodelId)) { + CStreaming::RequestModel(m_nPedmodelId, 0); +#ifdef FIX_BUGS + CStreaming::LoadAllRequestedModels(false); // otherwise gets stuck :( +#endif + break; + } + CPed* pPed = new CCivilianPed(PEDTYPE_SPECIAL, m_nPedmodelId); + pPed->CharCreatedBy = MISSION_CHAR; + pPed->SetPosition(m_vecCurrentPosition); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(pPed); + pPed->bUsesCollision = false; + pPed->bAffectedByGravity = false; + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i] == nil) { + m_nActor = i; + pActors[i] = pPed; + break; + } + } + } + else { + pActors[m_nActor]->SetPosition(m_vecCurrentPosition); + pActors[m_nActor]->SetOrientation(0.0f, 0.0f, 0.0f); + int32 mi = m_nPedmodelId; + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) + mi = NextValidModelId(m_nPedmodelId, -1); + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) + mi = NextValidModelId(m_nPedmodelId, 1); + if (mi == m_nPedmodelId) { + if (CPad::GetPad(1)->GetTriangleJustDown()) { + pActors[m_nActor]->bUsesCollision = true; + pActors[m_nActor]->bAffectedByGravity = true; + ++m_nNumActors; + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_NEW_ACTOR; + Movie[m_nNumMovieCommands].m_vecPosition = m_vecCurrentPosition; + Movie[m_nNumMovieCommands].m_nModelIndex = m_nPedmodelId; + Movie[m_nNumMovieCommands++].m_nActorId = m_nActor; + m_nActor = -1; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + CWorld::Remove(pActors[m_nActor]); + delete pActors[m_nActor]; + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + } + else { + m_nPedmodelId = mi; + if (pActors[m_nActor]) { + CWorld::Remove(pActors[m_nActor]); + delete pActors[m_nActor]; + } + pActors[m_nActor] = nil; + m_nActor = -1; + } + } + break; + case MOVIE_MOVE_ACTOR: + SelectActor(); + if (m_bCommandActive) + break; + pActors[m_nActor]->SetPosition(m_vecCurrentPosition); + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bCommandActive = false; +#ifndef FIX_BUGS // why? it crashes, also makes no sense + pActors[m_nActor] = nil; +#endif + SelectActor(); + } + break; + case MOVIE_SELECT_ACTOR: + SelectActor(); + break; + case MOVIE_DELETE_ACTOR: + SelectActor(); + if (m_bActorSelected) { + CPopulation::RemovePed(pActors[m_nActor]); + m_nCurrentActor = 0; + --m_nNumActors; +#ifdef FIX_BUGS + pActors[m_nActor] = nil; + m_nActor = -1; +#else + m_nActor = -1; + pActors[m_nActor] = nil; +#endif + SelectActor(); + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + m_nActor = -1; + m_bCommandActive = false; + } + break; + case MOVIE_NEW_VEHICLE: + if (m_nVehicle == -1) { + if (m_nNumVehicles == NUM_VEHICLES_IN_MOVIE) + break; + if (!CStreaming::HasModelLoaded(m_nVehiclemodelId)) { + CStreaming::RequestModel(m_nVehiclemodelId, 0); +#ifdef FIX_BUGS + CStreaming::LoadAllRequestedModels(false); // otherwise gets stuck :( +#endif + break; + } + CVehicle* pVehicle = new CAutomobile(m_nVehiclemodelId, MISSION_VEHICLE); + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->SetPosition(m_vecCurrentPosition); + pVehicle->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(pVehicle); + pVehicle->bUsesCollision = false; + pVehicle->bAffectedByGravity = false; + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) { + if (pVehicles[i] == nil) { + m_nVehicle = i; + pVehicles[i] = pVehicle; + break; + } + } + } + else { + pVehicles[m_nVehicle]->SetPosition(m_vecCurrentPosition); + pVehicles[m_nVehicle]->SetOrientation(0.0f, 0.0f, 0.0f); + int32 mi = m_nVehiclemodelId; + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) + mi = NextValidModelId(m_nVehiclemodelId, -1); + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) + mi = NextValidModelId(m_nVehiclemodelId, 1); + if (mi == m_nVehiclemodelId) { + if (CPad::GetPad(1)->GetTriangleJustDown()) { + pVehicles[m_nVehicle]->bUsesCollision = true; + pVehicles[m_nVehicle]->bAffectedByGravity = true; + ++m_nNumVehicles; + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_NEW_VEHICLE; + Movie[m_nNumMovieCommands].m_vecPosition = m_vecCurrentPosition; + Movie[m_nNumMovieCommands].m_nModelIndex = m_nVehiclemodelId; + Movie[m_nNumMovieCommands++].m_nVehicleId = m_nVehicle; + m_nVehicle = -1; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + CWorld::Remove(pVehicles[m_nVehicle]); + delete pVehicles[m_nVehicle]; + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + m_bCommandActive = false; + } + } + else { + m_nVehiclemodelId = mi; + if (pVehicles[m_nVehicle]) { + CWorld::Remove(pVehicles[m_nVehicle]); + delete pVehicles[m_nVehicle]; + } + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + } + } + break; + case MOVIE_MOVE_VEHICLE: + SelectVehicle(); + if (m_bCommandActive) + break; + pVehicles[m_nVehicle]->SetPosition(m_vecCurrentPosition); + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bCommandActive = false; +#ifndef FIX_BUGS // again, why? works wrong + pVehicles[m_nVehicle] = nil; +#endif + m_nVehicle = -1; + } + break; + case MOVIE_SELECT_VEHICLE: + SelectVehicle(); + break; + case MOVIE_DELETE_VEHICLE: + SelectVehicle(); + if (m_bVehicleSelected) { + CWorld::Remove(pVehicles[m_nVehicle]); + delete pVehicles[m_nVehicle]; + m_nCurrentVehicle = 0; + --m_nNumVehicles; + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + SelectVehicle(); + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + m_bCommandActive = false; + } + break; + case MOVIE_GIVE_WEAPON: + if (m_bActorSelected) { + if (SelectWeapon()) { + m_bCommandActive = false; + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_GIVE_WEAPON; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_nModelIndex = m_nWeaponType; + } + } + else { + SelectActor(); + m_bCommandActive = true; + } + break; + case MOVIE_GOTO: + case MOVIE_GOTO_WAIT: + if (!m_bActorSelected) { + m_bDrawGotoArrow = true; + SelectActor(); + if (m_nActor == -1) + m_bCommandActive = true; + } + else { + m_vecGotoPosition = m_vecCurrentPosition; + if (CPad::GetPad(1)->GetTriangleJustDown()) { + if (pActors[m_nActor]->bInVehicle) { + if (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pActors[m_nActor]->m_pMyVehicle, m_vecGotoPosition, false)) + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + else + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS; + pActors[m_nActor]->m_pMyVehicle->SetStatus(STATUS_PHYSICS); + pActors[m_nActor]->m_pMyVehicle->bEngineOn = true; + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCruiseSpeed = Max(16, pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCruiseSpeed); + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + TheCamera.TakeControl(pActors[m_nActor]->m_pMyVehicle, CCam::MODE_BEHINDCAR, JUMP_CUT, CAMCONTROL_SCRIPT); + } + else { + pActors[m_nActor]->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, m_vecGotoPosition); + TheCamera.TakeControl(pActors[m_nActor], CCam::MODE_FOLLOWPED, JUMP_CUT, CAMCONTROL_SCRIPT); + } + m_bDrawGotoArrow = false; + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_GOTO; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_vecPosition = m_vecGotoPosition; + } + if (!m_bDrawGotoArrow) { + if (pActors[m_nActor]->bInVehicle && pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE || + !pActors[m_nActor]->bInVehicle && pActors[m_nActor]->m_objective == OBJECTIVE_NONE) { + if (pActors[m_nActor]) // if there is something that requires this check the least, it's this one + m_vecCamHeading = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].Source; + m_bCommandActive = false; + TheCamera.Cams[TheCamera.ActiveCam].Mode = CCam::MODE_FIGHT_CAM_RUNABOUT; + m_vecCurrentPosition = pActors[m_nActor]->GetPosition(); + pActors[m_nActor]->SetObjective(OBJECTIVE_NONE); + if (pActors[m_nActor]->bInVehicle) + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + } + break; + case MOVIE_GET_IN_CAR: + if (m_bActorSelected) + SelectVehicle(); + else { + SelectActor(); + if (m_nActor != -1) + m_bCommandActive = true; + } + if (m_bVehicleSelected) { + pActors[m_nActor]->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicles[m_nVehicle]); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_GET_IN_CAR; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_nVehicleId = m_nVehicle; + m_nVehicle = -1; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + break; + case MOVIE_GET_OUT_CAR: + SelectActor(); + if (m_bActorSelected) { + if (pActors[m_nActor]->bInVehicle) { + pActors[m_nActor]->SetObjective(OBJECTIVE_LEAVE_CAR); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_GET_OUT_CAR; + Movie[m_nNumMovieCommands++].m_nActorId = m_nActor; + } + m_nActor = -1; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pVehicles[m_nVehicle] = nil; + m_nVehicle = -1; + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + break; + case MOVIE_KILL: + if (!m_bActorSelected) { + SelectActor(); + m_bCommandActive = true; + } + else if (!m_bActor2Selected) { + SelectActor2(); + if (m_bActorSelected && m_bActor2Selected && m_nActor != -1 && m_nActor2 != -1 && m_nActor != m_nActor2) { + pActors[m_nActor]->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, pActors[m_nActor2]); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_KILL; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_nActor2Id = m_nActor2; + m_bCommandActive = false; + } + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor] = nil; + m_nActor = -1; + pActors[m_nActor2] = nil; + m_nActor2 = -1; + m_bCommandActive = false; + } + break; + case MOVIE_FLEE: + if (!m_bActorSelected) { + SelectActor(); + m_bCommandActive = true; + } + else if (!m_bActor2Selected) { + SelectActor2(); + if (m_bActorSelected && m_bActor2Selected && m_nActor != -1 && m_nActor2 != -1 && m_nActor != m_nActor2) { + pActors[m_nActor]->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pActors[m_nActor2]); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_FLEE; + Movie[m_nNumMovieCommands].m_nActorId = m_nActor; + Movie[m_nNumMovieCommands++].m_nActor2Id = m_nActor2; + m_bCommandActive = false; + } + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor] = nil; + m_nActor = -1; + pActors[m_nActor2] = nil; + m_nActor2 = -1; + m_bCommandActive = false; + } + break; + case MOVIE_WAIT: + SelectActor(); + if (m_bActorSelected) { + pActors[m_nActor]->SetObjective(OBJECTIVE_WAIT_ON_FOOT); + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_WAIT; + Movie[m_nNumMovieCommands++].m_nActorId = m_nActor; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor] = nil; + m_nActor = -1; + m_bCommandActive = false; + } + break; + case MOVIE_POSITION_CAMERA: + if (CPad::GetPad(1)->GetTriangleJustDown()) { + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_POSITION_CAMERA; + Movie[m_nNumMovieCommands].m_vecPosition = TheCamera.Cams[TheCamera.ActiveCam].Source; + Movie[m_nNumMovieCommands++].m_vecCamera = m_vecCamHeading; + m_bCommandActive = false; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + m_bCommandActive = false; + } + break; + case MOVIE_SET_CAMERA_TARGET: + if (!m_bActorSelected) { + SelectActor(); + m_bCommandActive = true; + } + else { + TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity = pActors[m_nActor]; + if (CPad::GetPad(1)->GetTriangleJustDown()) { + Movie[m_nNumMovieCommands].m_nCommandId = MOVIE_SET_CAMERA_TARGET; + Movie[m_nNumMovieCommands++].m_nActorId = m_nActor; + m_bCommandActive = false; + } + } + break; + case MOVIE_SELECT_CAMERA_MODE: + m_bCommandActive = false; + break; + case MOVIE_SAVE_MOVIE: + SaveMovie(); + break; + case MOVIE_LOAD_MOVIE: + LoadMovie(); + break; + case MOVIE_PLAY_MOVIE: + InitPlayback(); + LoadMovie(); + m_bRecording = false; + break; + case MOVIE_END: + m_bRecording = false; + break; + default: + assert(0); + } +} + +void CSceneEdit::PlayBack(void) +{ + m_nCurrentCommand = Movie[m_nCurrentMovieCommand].m_nCommandId; + if (m_nCurrentMovieCommand >= m_nNumMovieCommands) { + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_nCurrentCommand = MOVIE_DO_NOTHING; + m_bRecording = true; + ReInitialise(); + } + return; + } + switch (m_nCurrentCommand) { + case MOVIE_DO_NOTHING: + case MOVIE_MOVE_ACTOR: + case MOVIE_SELECT_ACTOR: + case MOVIE_DELETE_ACTOR: + case MOVIE_MOVE_VEHICLE: + case MOVIE_SELECT_VEHICLE: + case MOVIE_DELETE_VEHICLE: + break; + case MOVIE_NEW_ACTOR: + { + m_nPedmodelId = Movie[m_nCurrentMovieCommand].m_nModelIndex; + m_vecCurrentPosition = Movie[m_nCurrentMovieCommand].m_vecPosition; + if (!CStreaming::HasModelLoaded(m_nPedmodelId)) { + CStreaming::RequestModel(m_nPedmodelId, 0); +#ifdef FIX_BUGS + CStreaming::LoadAllRequestedModels(false); // otherwise gets stuck :( +#endif + break; + } + CPed* pPed = new CCivilianPed(PEDTYPE_SPECIAL, m_nPedmodelId); + pPed->CharCreatedBy = MISSION_CHAR; + CWorld::Add(pPed); + pPed->SetPosition(m_vecCurrentPosition); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i] == nil) { + m_nActor = i; + pActors[i] = pPed; + break; + } + } + m_nNumActors++; + m_nCurrentMovieCommand++; + break; + } + case MOVIE_NEW_VEHICLE: + { + m_nVehiclemodelId = Movie[m_nCurrentMovieCommand].m_nModelIndex; + m_vecCurrentPosition = Movie[m_nCurrentMovieCommand].m_vecPosition; + if (!CStreaming::HasModelLoaded(m_nVehiclemodelId)) { + CStreaming::RequestModel(m_nVehiclemodelId, 0); +#ifdef FIX_BUGS + CStreaming::LoadAllRequestedModels(false); // otherwise gets stuck :( +#endif + break; + } + CVehicle* pVehicle = new CAutomobile(m_nVehiclemodelId, MISSION_VEHICLE); + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->SetPosition(m_vecCurrentPosition); + pVehicle->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(pVehicle); + for (int i = 0; i < NUM_VEHICLES_IN_MOVIE; i++) { + if (pVehicles[i] == nil) { + m_nVehicle = i; + pVehicles[i] = pVehicle; + break; + } + } + m_nNumVehicles++; + m_nCurrentMovieCommand++; + break; + } + case MOVIE_GIVE_WEAPON: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + m_nWeaponType = Movie[m_nCurrentMovieCommand].m_nModelIndex; + pActors[m_nActor]->GiveWeapon((eWeaponType)m_nWeaponType, 1000); + pActors[m_nActor]->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pActors[m_nActor]->GetWeapon()->m_eWeaponType)->m_nModelId); + pActors[m_nActor]->SetCurrentWeapon(m_nWeaponType); + m_nCurrentMovieCommand++; + break; + case MOVIE_GOTO: + case MOVIE_GOTO_WAIT: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + m_vecGotoPosition = Movie[m_nCurrentMovieCommand].m_vecPosition; + if (pActors[m_nActor]->bInVehicle) { + if (pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission != MISSION_GOTOCOORDS && + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission != MISSION_GOTOCOORDS_STRAIGHT) { + if ((pActors[m_nActor]->m_pMyVehicle->GetPosition() - m_vecGotoPosition).Magnitude() < 5.0f) { + if (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pActors[m_nActor]->m_pMyVehicle, m_vecGotoPosition, false)) + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + else + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS; + pActors[m_nActor]->m_pMyVehicle->SetStatus(STATUS_PHYSICS); + pActors[m_nActor]->m_pMyVehicle->bEngineOn = true; + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCruiseSpeed = Max(16, pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nCruiseSpeed); + pActors[m_nActor]->m_pMyVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + if (m_nCurrentCommand != MOVIE_GOTO_WAIT) + ++m_nCurrentMovieCommand; + } + else + ++m_nCurrentMovieCommand; + } + } + else { + if (pActors[m_nActor]->m_objective != OBJECTIVE_GOTO_AREA_ON_FOOT) { + pActors[m_nActor]->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, m_vecGotoPosition); + ++m_nCurrentMovieCommand; + } + } + break; + case MOVIE_GET_IN_CAR: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + if (!pActors[m_nActor]->bInVehicle){ + m_nVehicle = Movie[m_nCurrentMovieCommand].m_nVehicleId; + pActors[m_nActor]->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicles[m_nVehicle]); + } + else + ++m_nCurrentMovieCommand; + break; + case MOVIE_GET_OUT_CAR: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + if (pActors[m_nActor]->bInVehicle) + pActors[m_nActor]->SetObjective(OBJECTIVE_LEAVE_CAR); + else + ++m_nCurrentMovieCommand; + break; + case MOVIE_KILL: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + m_nActor2 = Movie[m_nCurrentMovieCommand].m_nActor2Id; + pActors[m_nActor]->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, pActors[m_nActor2]); + if (pActors[m_nActor2]->GetPedState() == PED_DEAD) + ++m_nCurrentMovieCommand; + break; + case MOVIE_FLEE: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + m_nActor2 = Movie[m_nCurrentMovieCommand].m_nActor2Id; + pActors[m_nActor]->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pActors[m_nActor2]); + ++m_nCurrentMovieCommand; + break; + case MOVIE_WAIT: + m_nActor = Movie[m_nCurrentMovieCommand].m_nActorId; + pActors[m_nActor]->SetObjective(OBJECTIVE_WAIT_ON_FOOT); + ++m_nCurrentMovieCommand; + break; + case MOVIE_POSITION_CAMERA: + TheCamera.Cams[TheCamera.ActiveCam].Source = Movie[m_nCurrentMovieCommand].m_vecPosition; + m_vecCamHeading = Movie[m_nCurrentMovieCommand].m_vecCamera; + TheCamera.Cams[TheCamera.ActiveCam].Front = m_vecCamHeading; + ++m_nCurrentMovieCommand; + break; + case MOVIE_SET_CAMERA_TARGET: + m_bCameraFollowActor = true; + TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity = pActors[Movie[m_nNumMovieCommands].m_nActorId]; + TheCamera.pTargetEntity = pActors[Movie[m_nNumMovieCommands].m_nActorId]; + TheCamera.m_bLookingAtPlayer = false; + ++m_nCurrentMovieCommand; + break; + case MOVIE_SELECT_CAMERA_MODE: + m_bCommandActive = false; // this is wrong + break; + } +} + +void CSceneEdit::ClearForNewCommand(void) +{ + m_nActor = -1; + m_nActor2 = -1; + m_nVehicle = -1; + m_bActorSelected = false; + m_bActor2Selected = false; + m_bVehicleSelected = false; + m_bDrawGotoArrow = false; +} +void CSceneEdit::SelectActor(void) +{ + m_bActorSelected = false; + if (m_nActor != -1) { + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) { + CPed* pPed; + do { + if (--m_nActor < 0) + m_nActor = NUM_ACTORS_IN_MOVIE - 1; + pPed = pActors[m_nActor]; + } while (pPed == nil); + TheCamera.Cams[TheCamera.ActiveCam].Source = pPed->GetPosition() - m_vecCamHeading; + } + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) { + CPed* pPed; + do { + if (++m_nActor == NUM_ACTORS_IN_MOVIE) + m_nActor = 0; + pPed = pActors[m_nActor]; + } while (pPed == nil); + TheCamera.Cams[TheCamera.ActiveCam].Source = pPed->GetPosition() - m_vecCamHeading; + } + m_vecCurrentPosition = pActors[m_nActor]->GetPosition(); + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bActorSelected = true; + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + m_nActor = -1; + } + } + else if (m_nNumActors != 0) { + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i] != nil) { + m_nActor = i; + break; + } + } + TheCamera.Cams[TheCamera.ActiveCam].Source = pActors[m_nActor]->GetPosition() - m_vecCamHeading; + if (m_nNumActors == 1) { + m_bActorSelected = true; + m_bCommandActive = false; + } + } + else { + m_bCommandActive = false; + } +} + +void CSceneEdit::SelectActor2(void) +{ + m_bActor2Selected = false; + if (m_nNumActors <= 1) { + m_bCommandActive = false; + return; + } + if (m_nActor2 != -1) { + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) { + CPed* pPed; + do { + if (--m_nActor2 < 0) + m_nActor2 = NUM_ACTORS_IN_MOVIE - 1; + pPed = pActors[m_nActor2]; + } while (pPed == nil || pPed == pActors[m_nActor]); + TheCamera.Cams[TheCamera.ActiveCam].Source = pPed->GetPosition() - m_vecCamHeading; + } + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) { + CPed* pPed; + do { + if (++m_nActor2 == NUM_ACTORS_IN_MOVIE) + m_nActor2 = 0; + pPed = pActors[m_nActor2]; + } while (pPed == nil || pPed == pActors[m_nActor]); + TheCamera.Cams[TheCamera.ActiveCam].Source = pPed->GetPosition() - m_vecCamHeading; + } + m_vecCurrentPosition = pActors[m_nActor2]->GetPosition(); + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bActor2Selected = true; + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + m_nActor2 = -1; + } + } + else { + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pActors[i] != nil && pActors[m_nActor] != pActors[i] ) { + m_nActor2 = i; + break; + } + } + TheCamera.Cams[TheCamera.ActiveCam].Source = pActors[m_nActor2]->GetPosition() - m_vecCamHeading; + } +} + +void CSceneEdit::SelectVehicle(void) +{ + m_bVehicleSelected = false; + if (m_nVehicle != -1) { + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) { + CVehicle* pVehicle; + do { + if (--m_nVehicle < 0) + m_nVehicle = NUM_VEHICLES_IN_MOVIE - 1; + pVehicle = pVehicles[m_nVehicle]; + } while (pVehicle == nil); + } + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()) { + CVehicle* pVehicle; + do { + if (++m_nVehicle == NUM_VEHICLES_IN_MOVIE) + m_nVehicle = 0; + pVehicle = pVehicles[m_nVehicle]; + } while (pVehicle == nil); + } + m_vecCurrentPosition = pVehicles[m_nVehicle]->GetPosition(); + TheCamera.Cams[TheCamera.ActiveCam].Source = pVehicles[m_nVehicle]->GetPosition() - m_vecCamHeading; + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bVehicleSelected = true; + m_bCommandActive = false; + } + else if (CPad::GetPad(1)->GetCircleJustDown()) { + m_nVehicle = -1; + } + } + else if (m_nNumVehicles != 0) { + for (int i = 0; i < NUM_ACTORS_IN_MOVIE; i++) { + if (pVehicles[i] != nil) { + m_nVehicle = i; + break; + } + } + } +} + +bool CSceneEdit::SelectWeapon(void) +{ + if (m_nWeaponType == WEAPONTYPE_UNARMED) { + m_nWeaponType = WEAPONTYPE_COLT45; + return false; + } + if (CPad::GetPad(1)->GetLeftShoulder1JustDown()) { + if (++m_nWeaponType >= WEAPONTYPE_DETONATOR) + m_nWeaponType = WEAPONTYPE_BASEBALLBAT; + pActors[m_nActor]->ClearWeapons(); + pActors[m_nActor]->GiveWeapon((eWeaponType)m_nWeaponType, 1000); + pActors[m_nActor]->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pActors[m_nActor]->GetWeapon()->m_eWeaponType)->m_nModelId); + pActors[m_nActor]->SetCurrentWeapon(m_nWeaponType); + } + else if (CPad::GetPad(1)->GetRightShoulder1JustDown()){ + if (--m_nWeaponType <= WEAPONTYPE_UNARMED) + m_nWeaponType = WEAPONTYPE_GRENADE; + pActors[m_nActor]->ClearWeapons(); + pActors[m_nActor]->GiveWeapon((eWeaponType)m_nWeaponType, 1000); + pActors[m_nActor]->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pActors[m_nActor]->GetWeapon()->m_eWeaponType)->m_nModelId); + pActors[m_nActor]->SetCurrentWeapon(m_nWeaponType); + } + if (CPad::GetPad(1)->GetTriangleJustDown()) { + m_bCommandActive = false; + return true; + } + if (CPad::GetPad(1)->GetCircleJustDown()) { + pActors[m_nActor]->ClearWeapons(); + m_nWeaponType = WEAPONTYPE_UNARMED; + m_bCommandActive = false; + return false; + } + return false; +} +#endif diff --git a/src/control/SceneEdit.h b/src/control/SceneEdit.h new file mode 100644 index 0000000..7c8fb98 --- /dev/null +++ b/src/control/SceneEdit.h @@ -0,0 +1,96 @@ +#pragma once +#ifdef GTA_SCENE_EDIT +class CPed; +class CVehicle; + +struct CMovieCommand +{ + int32 m_nCommandId; + CVector m_vecPosition; + CVector m_vecCamera; + int16 m_nActorId; + int16 m_nActor2Id; + int16 m_nVehicleId; + int16 m_nModelIndex; +}; + +class CSceneEdit +{ +public: + enum { + MOVIE_DO_NOTHING = 0, + MOVIE_NEW_ACTOR, + MOVIE_MOVE_ACTOR, + MOVIE_SELECT_ACTOR, + MOVIE_DELETE_ACTOR, + MOVIE_NEW_VEHICLE, + MOVIE_MOVE_VEHICLE, + MOVIE_SELECT_VEHICLE, + MOVIE_DELETE_VEHICLE, + MOVIE_GIVE_WEAPON, + MOVIE_GOTO, + MOVIE_GOTO_WAIT, + MOVIE_GET_IN_CAR, + MOVIE_GET_OUT_CAR, + MOVIE_KILL, + MOVIE_FLEE, + MOVIE_WAIT, + MOVIE_POSITION_CAMERA, + MOVIE_SET_CAMERA_TARGET, + MOVIE_SELECT_CAMERA_MODE, + MOVIE_SAVE_MOVIE, + MOVIE_LOAD_MOVIE, + MOVIE_PLAY_MOVIE, + MOVIE_END, + MOVIE_TOTAL_COMMANDS + }; + enum { + NUM_ACTORS_IN_MOVIE = 5, + NUM_VEHICLES_IN_MOVIE = 5, + NUM_COMMANDS_IN_MOVIE = 20 + }; + static int32 m_bCameraFollowActor; + static CVector m_vecCurrentPosition; + static CVector m_vecCamHeading; + static CVector m_vecGotoPosition; + static int32 m_nVehicle; + static int32 m_nVehicle2; + static int32 m_nActor; + static int32 m_nActor2; + static int32 m_nVehiclemodelId; + static int32 m_nPedmodelId; + static int16 m_nCurrentMovieCommand; + static int16 m_nCurrentCommand; + static int16 m_nCurrentVehicle; + static int16 m_nCurrentActor; + static bool m_bEditOn; + static bool m_bRecording; + static bool m_bCommandActive; + static bool m_bActorSelected; + static bool m_bActor2Selected; + static bool m_bVehicleSelected; + static int16 m_nNumActors; + static int16 m_nNumVehicles; + static int16 m_nNumMovieCommands; + static int16 m_nWeaponType; + static CPed* pActors[NUM_ACTORS_IN_MOVIE]; + static CVehicle* pVehicles[NUM_VEHICLES_IN_MOVIE]; + static bool m_bDrawGotoArrow; + static CMovieCommand Movie[NUM_COMMANDS_IN_MOVIE]; + + static void LoadMovie(void); + static void SaveMovie(void); + static void Initialise(void); + static void InitPlayback(void); + static void ReInitialise(void); + static void Update(void); + static void Draw(void); + static void ProcessCommand(void); + static void PlayBack(void); + static void ClearForNewCommand(void); + static void SelectActor(void); + static void SelectActor2(void); + static void SelectVehicle(void); + static bool SelectWeapon(void); +}; +#endif diff --git a/src/control/Script.cpp b/src/control/Script.cpp new file mode 100644 index 0000000..f1c8b34 --- /dev/null +++ b/src/control/Script.cpp @@ -0,0 +1,3014 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "AnimBlendAssociation.h" +#include "AudioManager.h" +#include "Boat.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "CivilianPed.h" +#include "Clock.h" +#include "CopPed.h" +#include "Debug.h" +#include "DMAudio.h" +#include "EmergencyPed.h" +#include "FileMgr.h" +#include "Frontend.h" +#include "General.h" +#ifdef MISSION_REPLAY +#include "GenericGameStorage.h" +#endif +#include "HandlingMgr.h" +#include "Heli.h" +#include "Hud.h" +#include "Lines.h" +#include "Messages.h" +#include "Pad.h" +#include "Pickups.h" +#include "Pools.h" +#include "Population.h" +#include "Remote.h" +#include "Replay.h" +#include "Stats.h" +#include "Streaming.h" +#include "User.h" +#include "Wanted.h" +#include "Weather.h" +#include "Zones.h" + +uint8 CTheScripts::ScriptSpace[SIZE_SCRIPT_SPACE]; +CRunningScript CTheScripts::ScriptsArray[MAX_NUM_SCRIPTS]; +int32 CTheScripts::BaseBriefIdForContact[MAX_NUM_CONTACTS]; +int32 CTheScripts::OnAMissionForContactFlag[MAX_NUM_CONTACTS]; +intro_text_line CTheScripts::IntroTextLines[MAX_NUM_INTRO_TEXT_LINES]; +intro_script_rectangle CTheScripts::IntroRectangles[MAX_NUM_INTRO_RECTANGLES]; +CSprite2d CTheScripts::ScriptSprites[MAX_NUM_SCRIPT_SRPITES]; +script_sphere_struct CTheScripts::ScriptSphereArray[MAX_NUM_SCRIPT_SPHERES]; +tCollectiveData CTheScripts::CollectiveArray[MAX_NUM_COLLECTIVES]; +tUsedObject CTheScripts::UsedObjectArray[MAX_NUM_USED_OBJECTS]; +int32 CTheScripts::MultiScriptArray[MAX_NUM_MISSION_SCRIPTS]; +tBuildingSwap CTheScripts::BuildingSwapArray[MAX_NUM_BUILDING_SWAPS]; +CEntity* CTheScripts::InvisibilitySettingArray[MAX_NUM_INVISIBILITY_SETTINGS]; +CStoredLine CTheScripts::aStoredLines[MAX_NUM_STORED_LINES]; +bool CTheScripts::DbgFlag; +uint32 CTheScripts::OnAMissionFlag; +int32 CTheScripts::StoreVehicleIndex; +bool CTheScripts::StoreVehicleWasRandom; +CRunningScript *CTheScripts::pIdleScripts; +CRunningScript *CTheScripts::pActiveScripts; +int32 CTheScripts::NextFreeCollectiveIndex; +int32 CTheScripts::LastRandomPedId; +uint16 CTheScripts::NumberOfUsedObjects; +bool CTheScripts::bAlreadyRunningAMissionScript; +bool CTheScripts::bUsingAMultiScriptFile; +uint16 CTheScripts::NumberOfMissionScripts; +uint32 CTheScripts::LargestMissionScriptSize; +uint32 CTheScripts::MainScriptSize; +uint8 CTheScripts::FailCurrentMission; +uint8 CTheScripts::CountdownToMakePlayerUnsafe; +uint8 CTheScripts::DelayMakingPlayerUnsafeThisTime; +uint16 CTheScripts::NumScriptDebugLines; +uint16 CTheScripts::NumberOfIntroRectanglesThisFrame; +uint16 CTheScripts::NumberOfIntroTextLinesThisFrame; +uint8 CTheScripts::UseTextCommands; +CMissionCleanup CTheScripts::MissionCleanUp; +CUpsideDownCarCheck CTheScripts::UpsideDownCars; +CStuckCarCheck CTheScripts::StuckCars; +uint16 CTheScripts::CommandsExecuted; +uint16 CTheScripts::ScriptsUpdated; +int32 ScriptParams[32]; + +#ifdef MISSION_REPLAY + +static const char* nonMissionScripts[] = { + "copcar", + "ambulan", + "taxi", + "firetru", + "rampage", + "t4x4_1", + "t4x4_2", + "t4x4_3", + "rc1", + "rc2", + "rc3", + "rc4", + "hj", + "usj", + "mayhem" +}; + +int AllowMissionReplay; +uint32 NextMissionDelay; +uint32 MissionStartTime; +uint32 WaitForMissionActivate; +uint32 WaitForSave; +float oldTargetX; +float oldTargetY; +int missionRetryScriptIndex; +bool doingMissionRetry; + +#endif + +const uint32 CRunningScript::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 136; +#else + sizeof(CRunningScript); +#endif + +CMissionCleanup::CMissionCleanup() +{ + Init(); +} + +void CMissionCleanup::Init() +{ + m_nCount = 0; + for (int i = 0; i < MAX_CLEANUP; i++){ + m_sEntities[i].type = CLEANUP_UNUSED; + m_sEntities[i].id = 0; + } +} + +cleanup_entity_struct* CMissionCleanup::FindFree() +{ + for (int i = 0; i < MAX_CLEANUP; i++){ + if (m_sEntities[i].type == CLEANUP_UNUSED) + return &m_sEntities[i]; + } + script_assert(0); + return nil; +} + +void CMissionCleanup::AddEntityToList(int32 id, uint8 type) +{ + cleanup_entity_struct* pNew = FindFree(); + if (!pNew) + return; + pNew->id = id; + pNew->type = type; + m_nCount++; +} + +void CMissionCleanup::RemoveEntityFromList(int32 id, uint8 type) +{ + for (int i = 0; i < MAX_CLEANUP; i++){ + if (m_sEntities[i].type == type && m_sEntities[i].id == id){ + m_sEntities[i].id = 0; + m_sEntities[i].type = CLEANUP_UNUSED; + m_nCount--; + } + } +} + +void CMissionCleanup::Process() +{ + CPopulation::m_AllRandomPedsThisType = -1; + CPopulation::PedDensityMultiplier = 1.0f; + CCarCtrl::CarDensityMultiplier = 1.0f; + FindPlayerPed()->m_pWanted->m_fCrimeSensitivity = 1.0f; + TheCamera.Restore(); + TheCamera.SetWideScreenOff(); + DMAudio.ClearMissionAudio(); + CWeather::ReleaseWeather(); + for (int i = 0; i < NUM_OF_SPECIAL_CHARS; i++) + CStreaming::SetMissionDoesntRequireSpecialChar(i); + for (int i = 0; i < NUM_OF_CUTSCENE_OBJECTS; i++) + CStreaming::SetMissionDoesntRequireModel(MI_CUTOBJ01 + i); + CStreaming::ms_disableStreaming = false; + CHud::m_ItemToFlash = -1; + CHud::SetHelpMessage(nil, false); + CUserDisplay::OnscnTimer.m_bDisabled = false; + CWorld::Players[0].m_pPed->m_pWanted->m_bIgnoredByCops = false; + CWorld::Players[0].m_pPed->m_pWanted->m_bIgnoredByEveryone = false; + CWorld::Players[0].MakePlayerSafe(false); + CTheScripts::StoreVehicleIndex = -1; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::UpsideDownCars.Init(); + CTheScripts::StuckCars.Init(); + for (int i = 0; i < MAX_CLEANUP; i++){ + if (m_sEntities[i].type == CLEANUP_UNUSED) + continue; + switch (m_sEntities[i].type) { + case CLEANUP_CAR: + { + CVehicle* v = CPools::GetVehiclePool()->GetAt(m_sEntities[i].id); + if (v) + CTheScripts::CleanUpThisVehicle(v); + break; + } + case CLEANUP_CHAR: + { + CPed* p = CPools::GetPedPool()->GetAt(m_sEntities[i].id); + if (p) + CTheScripts::CleanUpThisPed(p); + break; + } + case CLEANUP_OBJECT: + { + CObject* o = CPools::GetObjectPool()->GetAt(m_sEntities[i].id); + if (o) + CTheScripts::CleanUpThisObject(o); + break; + } + default: + break; + } + m_sEntities[i].id = 0; + m_sEntities[i].type = CLEANUP_UNUSED; + m_nCount--; + } +} + +/* NB: CUpsideDownCarCheck is not used by actual script at all + * It has a weird usage: AreAnyCarsUpsideDown would fail any mission + * just like death or arrest. */ + +void CUpsideDownCarCheck::Init() +{ + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + m_sCars[i].m_nVehicleIndex = -1; + m_sCars[i].m_nUpsideDownTimer = 0; + } +} + +bool CUpsideDownCarCheck::IsCarUpsideDown(int32 id) +{ + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(id); + return IsCarUpsideDown(pVehicle); +} + +bool CUpsideDownCarCheck::IsCarUpsideDown(CVehicle* pVehicle) +{ + assert(pVehicle); + return pVehicle->GetUp().z <= UPSIDEDOWN_UP_THRESHOLD && + pVehicle->GetMoveSpeed().Magnitude() < UPSIDEDOWN_MOVE_SPEED_THRESHOLD && + pVehicle->GetTurnSpeed().Magnitude() < UPSIDEDOWN_TURN_SPEED_THRESHOLD; +} + +void CUpsideDownCarCheck::UpdateTimers() +{ + uint32 timeStep = CTimer::GetTimeStepInMilliseconds(); + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + CVehicle* v = CPools::GetVehiclePool()->GetAt(m_sCars[i].m_nVehicleIndex); + if (v){ + if (IsCarUpsideDown(m_sCars[i].m_nVehicleIndex)) + m_sCars[i].m_nUpsideDownTimer += timeStep; + else + m_sCars[i].m_nUpsideDownTimer = 0; + }else{ + m_sCars[i].m_nVehicleIndex = -1; + m_sCars[i].m_nUpsideDownTimer = 0; + } + } +} + +bool CUpsideDownCarCheck::AreAnyCarsUpsideDown() +{ + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex >= 0 && m_sCars[i].m_nUpsideDownTimer > UPSIDEDOWN_TIMER_THRESHOLD) + return true; + } + return false; +} + +void CUpsideDownCarCheck::AddCarToCheck(int32 id) +{ + uint16 index = 0; + while (index < MAX_UPSIDEDOWN_CAR_CHECKS && m_sCars[index].m_nVehicleIndex >= 0) + index++; +#ifdef FIX_BUGS + if (index >= MAX_UPSIDEDOWN_CAR_CHECKS) + return; +#endif + m_sCars[index].m_nVehicleIndex = id; + m_sCars[index].m_nUpsideDownTimer = 0; +} + +void CUpsideDownCarCheck::RemoveCarFromCheck(int32 id) +{ + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex == id){ + m_sCars[i].m_nVehicleIndex = -1; + m_sCars[i].m_nUpsideDownTimer = 0; + } + } +} + +bool CUpsideDownCarCheck::HasCarBeenUpsideDownForAWhile(int32 id) +{ + for (int i = 0; i < MAX_UPSIDEDOWN_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex == id) + return m_sCars[i].m_nUpsideDownTimer > UPSIDEDOWN_TIMER_THRESHOLD; + } + return false; +} + +void stuck_car_data::Reset() +{ + m_nVehicleIndex = -1; + m_vecPos = CVector(-5000.0f, -5000.0f, -5000.0f); + m_nLastCheck = -1; + m_fRadius = 0.0f; + m_nStuckTime = 0; + m_bStuck = false; +} + +void CStuckCarCheck::Init() +{ + for (int i = 0; i < MAX_STUCK_CAR_CHECKS; i++) { + m_sCars[i].Reset(); + } +} + +void CStuckCarCheck::Process() +{ + uint32 timer = CTimer::GetTimeInMilliseconds(); + for (int i = 0; i < MAX_STUCK_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex < 0) + continue; + if (timer <= m_sCars[i].m_nStuckTime + m_sCars[i].m_nLastCheck) + continue; + CVehicle* pv = CPools::GetVehiclePool()->GetAt(m_sCars[i].m_nVehicleIndex); + if (!pv){ + m_sCars[i].Reset(); + continue; + } + float distance = (pv->GetPosition() - m_sCars[i].m_vecPos).Magnitude(); + m_sCars[i].m_bStuck = distance < m_sCars[i].m_fRadius; + m_sCars[i].m_vecPos = pv->GetPosition(); + m_sCars[i].m_nLastCheck = timer; + } +} + +void CStuckCarCheck::AddCarToCheck(int32 id, float radius, uint32 time) +{ + CVehicle* pv = CPools::GetVehiclePool()->GetAt(id); + if (!pv) + return; + int index = 0; + while (index < MAX_STUCK_CAR_CHECKS && m_sCars[index].m_nVehicleIndex >= 0) + index++; +#ifdef FIX_BUGS + if (index >= MAX_STUCK_CAR_CHECKS) + return; +#endif + m_sCars[index].m_nVehicleIndex = id; + m_sCars[index].m_vecPos = pv->GetPosition(); + m_sCars[index].m_nLastCheck = CTimer::GetTimeInMilliseconds(); + m_sCars[index].m_fRadius = radius; + m_sCars[index].m_nStuckTime = time; + m_sCars[index].m_bStuck = false; +} + +void CStuckCarCheck::RemoveCarFromCheck(int32 id) +{ + for (int i = 0; i < MAX_STUCK_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex == id){ + m_sCars[i].Reset(); + } + } +} + +bool CStuckCarCheck::HasCarBeenStuckForAWhile(int32 id) +{ + for (int i = 0; i < MAX_STUCK_CAR_CHECKS; i++){ + if (m_sCars[i].m_nVehicleIndex == id) + return m_sCars[i].m_bStuck; + } + return false; +} + +void CRunningScript::CollectParameters(uint32* pIp, int16 total) +{ + for (int16 i = 0; i < total; i++){ + float tmp; + uint16 varIndex; + switch (CTheScripts::Read1ByteFromScript(pIp)) + { + case ARGUMENT_INT32: + ScriptParams[i] = CTheScripts::Read4BytesFromScript(pIp); + break; + case ARGUMENT_GLOBALVAR: + varIndex = CTheScripts::Read2BytesFromScript(pIp); + script_assert(varIndex >= 8 && varIndex < CTheScripts::GetSizeOfVariableSpace()); + ScriptParams[i] = *((int32*)&CTheScripts::ScriptSpace[varIndex]); + break; + case ARGUMENT_LOCALVAR: + varIndex = CTheScripts::Read2BytesFromScript(pIp); + script_assert(varIndex >= 0 && varIndex < ARRAY_SIZE(m_anLocalVariables)); + ScriptParams[i] = m_anLocalVariables[varIndex]; + break; + case ARGUMENT_INT8: + ScriptParams[i] = CTheScripts::Read1ByteFromScript(pIp); + break; + case ARGUMENT_INT16: + ScriptParams[i] = CTheScripts::Read2BytesFromScript(pIp); + break; + case ARGUMENT_FLOAT: + tmp = CTheScripts::ReadFloatFromScript(pIp); + ScriptParams[i] = *(int32*)&tmp; + break; + default: + script_assert(0); + break; + } + } +} + +int32 CRunningScript::CollectNextParameterWithoutIncreasingPC(uint32 ip) +{ + uint32* pIp = &ip; + float tmp; + switch (CTheScripts::Read1ByteFromScript(pIp)) + { + case ARGUMENT_INT32: + return CTheScripts::Read4BytesFromScript(pIp); + case ARGUMENT_GLOBALVAR: + return *((int32*)&CTheScripts::ScriptSpace[(uint16)CTheScripts::Read2BytesFromScript(pIp)]); + case ARGUMENT_LOCALVAR: + return m_anLocalVariables[CTheScripts::Read2BytesFromScript(pIp)]; + case ARGUMENT_INT8: + return CTheScripts::Read1ByteFromScript(pIp); + case ARGUMENT_INT16: + return CTheScripts::Read2BytesFromScript(pIp); + case ARGUMENT_FLOAT: + tmp = CTheScripts::ReadFloatFromScript(pIp); + return *(int32*)&tmp; + default: + script_assert(0); + } + return -1; +} + +void CRunningScript::StoreParameters(uint32* pIp, int16 number) +{ + for (int16 i = 0; i < number; i++){ + switch (CTheScripts::Read1ByteFromScript(pIp)) { + case ARGUMENT_GLOBALVAR: + *(int32*)&CTheScripts::ScriptSpace[(uint16)CTheScripts::Read2BytesFromScript(pIp)] = ScriptParams[i]; + break; + case ARGUMENT_LOCALVAR: + m_anLocalVariables[CTheScripts::Read2BytesFromScript(pIp)] = ScriptParams[i]; + break; + default: + script_assert(0); + } + } +} + +int32 *CRunningScript::GetPointerToScriptVariable(uint32* pIp, int16 type) +{ + switch (CTheScripts::Read1ByteFromScript(pIp)) + { + case ARGUMENT_GLOBALVAR: + script_assert(type == VAR_GLOBAL); + return (int32*)&CTheScripts::ScriptSpace[(uint16)CTheScripts::Read2BytesFromScript(pIp)]; + case ARGUMENT_LOCALVAR: + script_assert(type == VAR_LOCAL); + return &m_anLocalVariables[CTheScripts::Read2BytesFromScript(pIp)]; + default: + script_assert(0); + } + return nil; +} + +void CRunningScript::Init() +{ + strcpy(m_abScriptName, "noname"); + next = prev = nil; + SetIP(0); + for (int i = 0; i < MAX_STACK_DEPTH; i++) + m_anStack[i] = 0; + m_nStackPointer = 0; + m_nWakeTime = 0; + m_bCondResult = false; + m_bIsMissionScript = false; + m_bSkipWakeTime = false; + for (int i = 0; i < NUM_LOCAL_VARS + NUM_TIMERS; i++) + m_anLocalVariables[i] = 0; + m_nAndOrState = 0; + m_bNotFlag = false; + m_bDeatharrestEnabled = true; + m_bDeatharrestExecuted = false; + m_bMissionFlag = false; +} + +#ifdef USE_DEBUG_SCRIPT_LOADER +int CTheScripts::ScriptToLoad = 0; + +int CTheScripts::OpenScript() +{ + CFileMgr::ChangeDir("\\"); + switch (ScriptToLoad) { + case 0: return CFileMgr::OpenFile("data\\main.scm", "rb"); + case 1: return CFileMgr::OpenFile("data\\main_freeroam.scm", "rb"); + case 2: return CFileMgr::OpenFile("data\\main_d.scm", "rb"); + } + return CFileMgr::OpenFile("data\\main.scm", "rb"); +} +#endif + +void CTheScripts::Init() +{ + for (int i = 0; i < SIZE_SCRIPT_SPACE; i++) + ScriptSpace[i] = 0; + pActiveScripts = pIdleScripts = nil; + for (int i = 0; i < MAX_NUM_SCRIPTS; i++){ + ScriptsArray[i].Init(); + ScriptsArray[i].AddScriptToList(&pIdleScripts); + } + MissionCleanUp.Init(); + UpsideDownCars.Init(); + StuckCars.Init(); +#ifdef USE_DEBUG_SCRIPT_LOADER + // glfwGetKey doesn't work because of CGame::Initialise is blocking + CPad::UpdatePads(); + if(CPad::GetPad(0)->GetChar('G')) ScriptToLoad = 0; + if(CPad::GetPad(0)->GetChar('R')) ScriptToLoad = 1; + if(CPad::GetPad(0)->GetChar('D')) ScriptToLoad = 2; + + int mainf = OpenScript(); +#else + CFileMgr::SetDir("data"); + int mainf = CFileMgr::OpenFile("main.scm", "rb"); +#endif + CFileMgr::Read(mainf, (char*)ScriptSpace, SIZE_MAIN_SCRIPT); + CFileMgr::CloseFile(mainf); + CFileMgr::SetDir(""); + StoreVehicleIndex = -1; + StoreVehicleWasRandom = true; + OnAMissionFlag = 0; + for (int i = 0; i < MAX_NUM_CONTACTS; i++){ + BaseBriefIdForContact[i] = 0; + OnAMissionForContactFlag[i] = 0; + } + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++){ + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + NextFreeCollectiveIndex = 0; + LastRandomPedId = -1; + for (int i = 0; i < MAX_NUM_USED_OBJECTS; i++){ + memset(&UsedObjectArray[i].name, 0, sizeof(UsedObjectArray[i].name)); + UsedObjectArray[i].index = 0; + } + NumberOfUsedObjects = 0; + ReadObjectNamesFromScript(); + UpdateObjectIndices(); + bAlreadyRunningAMissionScript = false; + bUsingAMultiScriptFile = true; + for (int i = 0; i < MAX_NUM_MISSION_SCRIPTS; i++) + MultiScriptArray[i] = 0; + NumberOfMissionScripts = 0; + LargestMissionScriptSize = 0; + MainScriptSize = 0; + ReadMultiScriptFileOffsetsFromScript(); + FailCurrentMission = 0; + CountdownToMakePlayerUnsafe = 0; + DbgFlag = false; + DelayMakingPlayerUnsafeThisTime = 1; + NumScriptDebugLines = 0; + for (int i = 0; i < MAX_NUM_SCRIPT_SPHERES; i++){ + ScriptSphereArray[i].m_bInUse = false; + ScriptSphereArray[i].m_Index = 1; + ScriptSphereArray[i].m_Id = 0; + ScriptSphereArray[i].m_vecCenter = CVector(0.0f, 0.0f, 0.0f); + ScriptSphereArray[i].m_fRadius = 0.0f; + } + for (int i = 0; i < MAX_NUM_INTRO_TEXT_LINES; i++){ + IntroTextLines[i].Reset(); + } + NumberOfIntroTextLinesThisFrame = 0; + UseTextCommands = 0; + for (int i = 0; i < MAX_NUM_INTRO_RECTANGLES; i++){ + IntroRectangles[i].m_bIsUsed = false; + IntroRectangles[i].m_bBeforeFade = false; + IntroRectangles[i].m_nTextureId = -1; + IntroRectangles[i].m_sRect = CRect(0.0f, 0.0f, 0.0f, 0.0f); + IntroRectangles[i].m_sColor = CRGBA(255, 255, 255, 255); + } + NumberOfIntroRectanglesThisFrame = 0; + for (int i = 0; i < MAX_NUM_BUILDING_SWAPS; i++){ + BuildingSwapArray[i].m_pBuilding = nil; + BuildingSwapArray[i].m_nNewModel = -1; + BuildingSwapArray[i].m_nOldModel = -1; + } + for (int i = 0; i < MAX_NUM_INVISIBILITY_SETTINGS; i++) + InvisibilitySettingArray[i] = nil; + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogAfterScriptInitializing(); +#endif +} + +void CRunningScript::RemoveScriptFromList(CRunningScript** ppScript) +{ + if (prev) + prev->next = next; + else + *ppScript = next; + if (next) + next->prev = prev; +} + +void CRunningScript::AddScriptToList(CRunningScript** ppScript) +{ + next = *ppScript; + prev = nil; + if (*ppScript) + (*ppScript)->prev = this; + *ppScript = this; +} + +CRunningScript* CTheScripts::StartNewScript(uint32 ip) +{ + CRunningScript* pNew = pIdleScripts; + script_assert(pNew); + pNew->RemoveScriptFromList(&pIdleScripts); + pNew->Init(); + pNew->SetIP(ip); + pNew->AddScriptToList(&pActiveScripts); + return pNew; +} + +void CTheScripts::Process() +{ + if (CReplay::IsPlayingBack()) + return; + CommandsExecuted = 0; + ScriptsUpdated = 0; + float timeStep = CTimer::GetTimeStepInMilliseconds(); + UpsideDownCars.UpdateTimers(); + StuckCars.Process(); + DrawScriptSpheres(); + if (FailCurrentMission) + --FailCurrentMission; + if (CountdownToMakePlayerUnsafe){ + if (--CountdownToMakePlayerUnsafe == 0) + CWorld::Players[0].MakePlayerSafe(false); + } + if (UseTextCommands){ + for (int i = 0; i < MAX_NUM_INTRO_TEXT_LINES; i++) + IntroTextLines[i].Reset(); + NumberOfIntroTextLinesThisFrame = 0; + for (int i = 0; i < MAX_NUM_INTRO_RECTANGLES; i++){ + IntroRectangles[i].m_bIsUsed = false; + IntroRectangles[i].m_bBeforeFade = false; + } + NumberOfIntroRectanglesThisFrame = 0; + if (UseTextCommands == 1) + UseTextCommands = 0; + } + +#ifdef MISSION_REPLAY + static uint32 TimeToWaitTill; + switch (AllowMissionReplay) { + case MISSION_RETRY_STAGE_START_PROCESSING: + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_DELAY; + TimeToWaitTill = CTimer::GetTimeInMilliseconds() + (AddExtraDeathDelay() > 1000 ? 4000 : 2500); + break; + case MISSION_RETRY_STAGE_WAIT_FOR_DELAY: + if (TimeToWaitTill < CTimer::GetTimeInMilliseconds()) + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_MENU; + break; + case MISSION_RETRY_STAGE_WAIT_FOR_MENU: + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_USER; + RetryMission(MISSION_RETRY_TYPE_SUGGEST_TO_PLAYER); + break; + case MISSION_RETRY_STAGE_START_RESTARTING: + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART; + TimeToWaitTill = CTimer::GetTimeInMilliseconds() + 500; + break; + case MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART: + if (TimeToWaitTill < CTimer::GetTimeInMilliseconds()) { + AllowMissionReplay = MISSION_RETRY_STAGE_NORMAL; + return; + } + break; + } + if (WaitForMissionActivate) { + if (WaitForMissionActivate > CTimer::GetTimeInMilliseconds()) + return; + WaitForMissionActivate = 0; + WaitForSave = CTimer::GetTimeInMilliseconds() + 3000; + } + if (WaitForSave && WaitForSave > CTimer::GetTimeInMilliseconds()) + WaitForSave = 0; +#endif + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogBeforeScriptProcessing(); +#endif + + CRunningScript* script = pActiveScripts; + while (script != nil){ + CRunningScript* next = script->GetNext(); + ++ScriptsUpdated; + script->UpdateTimers(timeStep); + script->Process(); + script = next; + } + DbgFlag = false; + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogAfterScriptProcessing(); +#endif +} + +CRunningScript* CTheScripts::StartTestScript() +{ + return StartNewScript(0); +} + +bool CTheScripts::IsPlayerOnAMission() +{ + return OnAMissionFlag && *(int32*)&ScriptSpace[OnAMissionFlag] == 1; +} + +void CRunningScript::Process() +{ +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogOnStartProcessing(); +#endif + if (m_bIsMissionScript) + DoDeatharrestCheck(); + if (m_bMissionFlag && CTheScripts::FailCurrentMission == 1 && m_nStackPointer == 1) + SetIP(m_anStack[--m_nStackPointer]); + if (CTimer::GetTimeInMilliseconds() >= m_nWakeTime){ + while (!ProcessOneCommand()) + ; + return; + } + if (!m_bSkipWakeTime) + return; + if (!CPad::GetPad(0)->GetCrossJustDown()) + return; + m_nWakeTime = 0; + for (int i = 0; i < NUMBIGMESSAGES; i++){ + if (CMessages::BIGMessages[i].m_Stack[0].m_pText != nil) + CMessages::BIGMessages[i].m_Stack[0].m_nStartTime = 0; + } + if (CMessages::BriefMessages[0].m_pText != nil) + CMessages::BriefMessages[0].m_nStartTime = 0; +} + +int8 CRunningScript::ProcessOneCommand() +{ + int8 retval = -1; + ++CTheScripts::CommandsExecuted; + int32 command = (uint16)CTheScripts::Read2BytesFromScript(&m_nIp); + m_bNotFlag = (command & 0x8000); + command &= 0x7FFF; +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogBeforeProcessingCommand(command); +#endif + if (command < 100) + retval = ProcessCommands0To99(command); + else if (command < 200) + retval = ProcessCommands100To199(command); + else if (command < 300) + retval = ProcessCommands200To299(command); + else if (command < 400) + retval = ProcessCommands300To399(command); + else if (command < 500) + retval = ProcessCommands400To499(command); + else if (command < 600) + retval = ProcessCommands500To599(command); + else if (command < 700) + retval = ProcessCommands600To699(command); + else if (command < 800) + retval = ProcessCommands700To799(command); + else if (command < 900) + retval = ProcessCommands800To899(command); + else if (command < 1000) + retval = ProcessCommands900To999(command); +#if GTA_VERSION <= GTA3_PS2_160 + else if (command < 1200) + retval = ProcessCommands1000To1099(command); +#else + else if (command < 1100) + retval = ProcessCommands1000To1099(command); + else if (command < 1200) + retval = ProcessCommands1100To1199(command); +#endif +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LogAfterProcessingCommand(command); +#elif defined USE_BASIC_SCRIPT_DEBUG_OUTPUT + if (m_bMissionFlag) { + char tmp[128]; + sprintf(tmp, "Comm %d Cmp %d", command, m_bCondResult); + CDebug::DebugAddText(tmp); + } +#endif + return retval; +} + +int8 CRunningScript::ProcessCommands0To99(int32 command) +{ + float *fScriptVar1; + int *nScriptVar1; + switch (command) { + case COMMAND_NOP: + return 0; + case COMMAND_WAIT: + CollectParameters(&m_nIp, 1); + m_nWakeTime = CTimer::GetTimeInMilliseconds() + ScriptParams[0]; + m_bSkipWakeTime = false; + return 1; + case COMMAND_GOTO: + CollectParameters(&m_nIp, 1); + SetIP(ScriptParams[0] >= 0 ? ScriptParams[0] : SIZE_MAIN_SCRIPT - ScriptParams[0]); + /* Known issue: GOTO to 0. It might have been "better" to use > instead of >= */ + /* simply because it never makes sense to jump to start of the script */ + /* but jumping to start of a custom mission is an issue for simple mission-like scripts */ + /* However, it's not an issue for actual mission scripts, because they follow a structure */ + /* and never start with a loop. */ + return 0; + case COMMAND_SHAKE_CAM: + CollectParameters(&m_nIp, 1); + CamShakeNoPos(&TheCamera, ScriptParams[0] / 1000.0f); + return 0; + case COMMAND_SET_VAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr = ScriptParams[0]; + return 0; + } + case COMMAND_SET_VAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SET_LVAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr = ScriptParams[0]; + return 0; + } + case COMMAND_SET_LVAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_ADD_VAL_TO_INT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr += ScriptParams[0]; + return 0; + } + case COMMAND_ADD_VAL_TO_FLOAT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr += *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_ADD_VAL_TO_INT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr += ScriptParams[0]; + return 0; + } + case COMMAND_ADD_VAL_TO_FLOAT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr += *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SUB_VAL_FROM_INT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr -= ScriptParams[0]; + return 0; + } + case COMMAND_SUB_VAL_FROM_FLOAT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr -= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SUB_VAL_FROM_INT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr -= ScriptParams[0]; + return 0; + } + case COMMAND_SUB_VAL_FROM_FLOAT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr -= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_MULT_INT_VAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr *= ScriptParams[0]; + return 0; + } + case COMMAND_MULT_FLOAT_VAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr *= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_MULT_INT_LVAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr *= ScriptParams[0]; + return 0; + } + case COMMAND_MULT_FLOAT_LVAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr *= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_DIV_INT_VAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr /= ScriptParams[0]; + return 0; + } + case COMMAND_DIV_FLOAT_VAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr /= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_DIV_INT_LVAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr /= ScriptParams[0]; + return 0; + } + case COMMAND_DIV_FLOAT_LVAR_BY_VAL: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr /= *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_THAN_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr > ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_THAN_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr > ScriptParams[0]); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_THAN_INT_VAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(ScriptParams[0] > *ptr); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_THAN_INT_LVAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(ScriptParams[0] > *ptr); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_THAN_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 > *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_THAN_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 > *ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_THAN_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 > *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_THAN_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 > *ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_THAN_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr > *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_THAN_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr > *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_VAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)&ScriptParams[0] > *(float*)ptr); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_LVAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)&ScriptParams[0] > *(float*)ptr); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 > *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 > *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 > *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 > *(float*)ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr >= ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr >= ScriptParams[0]); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_VAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(ScriptParams[0] >= *ptr); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_LVAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(ScriptParams[0] >= *ptr); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 >= *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 >= *ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 >= *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 >= *ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr >= *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr >= *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_VAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)&ScriptParams[0] >= *(float*)ptr); + return 0; + } + case COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_LVAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)&ScriptParams[0] >= *(float*)ptr); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 >= *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 >= *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 >= *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 >= *(float*)ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr == ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_LVAR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr == ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_VAR_EQUAL_TO_INT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*ptr1 == *ptr2); + return 0; + } + case COMMAND_IS_INT_VAR_EQUAL_TO_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 == *ptr2); + return 0; + } + case COMMAND_IS_INT_LVAR_EQUAL_TO_INT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*ptr1 == *ptr2); + return 0; + } + /* Following commands are not implemented, and go to default case + case COMMAND_IS_INT_VAR_NOT_EQUAL_TO_NUMBER: + case COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_NUMBER: + case COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_VAR: + case COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_INT_LVAR: + case COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_LVAR: + */ + case COMMAND_IS_FLOAT_VAR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr == *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_EQUAL_TO_NUMBER: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*(float*)ptr == *(float*)&ScriptParams[0]); + return 0; + } + case COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_VAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(*(float*)ptr1 == *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 == *(float*)ptr2); + return 0; + } + case COMMAND_IS_FLOAT_LVAR_EQUAL_TO_FLOAT_LVAR: + { + int32* ptr1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + int32* ptr2 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + UpdateCompareFlag(*(float*)ptr1 == *(float*)ptr2); + return 0; + } + /* Following commands are not implemented, and go to default case + case COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_NUMBER: + case COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_NUMBER: + case COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_VAR: + case COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_FLOAT_LVAR: + case COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_LVAR: + */ + case COMMAND_GOTO_IF_TRUE: + CollectParameters(&m_nIp, 1); + if (m_bCondResult) + SetIP(ScriptParams[0] >= 0 ? ScriptParams[0] : SIZE_MAIN_SCRIPT - ScriptParams[0]); + /* Check COMMAND_GOTO note. */ + return 0; + case COMMAND_GOTO_IF_FALSE: + CollectParameters(&m_nIp, 1); + if (!m_bCondResult) + SetIP(ScriptParams[0] >= 0 ? ScriptParams[0] : SIZE_MAIN_SCRIPT - ScriptParams[0]); + /* Check COMMAND_GOTO note. */ + return 0; + case COMMAND_TERMINATE_THIS_SCRIPT: + if (m_bMissionFlag) + CTheScripts::bAlreadyRunningAMissionScript = false; + RemoveScriptFromList(&CTheScripts::pActiveScripts); + AddScriptToList(&CTheScripts::pIdleScripts); +#ifdef MISSION_REPLAY + if (m_bMissionFlag) { + CPlayerInfo* pPlayerInfo = &CWorld::Players[CWorld::PlayerInFocus]; +#if 0 // makeing autosave is pointless and is a bit buggy + if (pPlayerInfo->m_pPed->GetPedState() != PED_DEAD && pPlayerInfo->m_WBState == WBSTATE_PLAYING && !m_bDeatharrestExecuted) + SaveGameForPause(SAVE_TYPE_QUICKSAVE); +#endif + oldTargetX = oldTargetY = 0.0f; + if (AllowMissionReplay == MISSION_RETRY_STAGE_WAIT_FOR_SCRIPT_TO_TERMINATE) + AllowMissionReplay = MISSION_RETRY_STAGE_START_PROCESSING; + // I am fairly sure they forgot to set return value here + } +#endif + return 1; + case COMMAND_START_NEW_SCRIPT: + { + CollectParameters(&m_nIp, 1); + script_assert(ScriptParams[0] >= 0); + CRunningScript* pNew = CTheScripts::StartNewScript(ScriptParams[0]); + int8 type = CTheScripts::Read1ByteFromScript(&m_nIp); + float tmp; + for (int i = 0; type != ARGUMENT_END; type = CTheScripts::Read1ByteFromScript(&m_nIp), i++) { + switch (type) { + case ARGUMENT_INT32: + pNew->m_anLocalVariables[i] = CTheScripts::Read4BytesFromScript(&m_nIp); + break; + case ARGUMENT_GLOBALVAR: + pNew->m_anLocalVariables[i] = *(int32*)&CTheScripts::ScriptSpace[(uint16)CTheScripts::Read2BytesFromScript(&m_nIp)]; + break; + case ARGUMENT_LOCALVAR: + pNew->m_anLocalVariables[i] = m_anLocalVariables[CTheScripts::Read2BytesFromScript(&m_nIp)]; + break; + case ARGUMENT_INT8: + pNew->m_anLocalVariables[i] = CTheScripts::Read1ByteFromScript(&m_nIp); + break; + case ARGUMENT_INT16: + pNew->m_anLocalVariables[i] = CTheScripts::Read2BytesFromScript(&m_nIp); + break; + case ARGUMENT_FLOAT: + tmp = CTheScripts::ReadFloatFromScript(&m_nIp); + pNew->m_anLocalVariables[i] = *(int32*)&tmp; + break; + default: + break; + } + } + return 0; + } + case COMMAND_GOSUB: + CollectParameters(&m_nIp, 1); + script_assert(m_nStackPointer < MAX_STACK_DEPTH); + m_anStack[m_nStackPointer++] = m_nIp; + SetIP(ScriptParams[0] >= 0 ? ScriptParams[0] : SIZE_MAIN_SCRIPT - ScriptParams[0]); + return 0; + case COMMAND_RETURN: + script_assert(m_nStackPointer > 0); /* No more SSU */ + SetIP(m_anStack[--m_nStackPointer]); + return 0; + case COMMAND_LINE: + CollectParameters(&m_nIp, 6); + /* Something must have been here */ + return 0; + case COMMAND_CREATE_PLAYER: + { + CollectParameters(&m_nIp, 4); + int32 index = ScriptParams[0]; + script_assert(index < 1); /* Constant? Also no more double player glitch */ + printf("&&&&&&&&&&&&&Creating player: %d\n", index); + if (!CStreaming::HasModelLoaded(MI_PLAYER)) { + CStreaming::RequestSpecialModel(MI_PLAYER, "player", STREAMFLAGS_DONT_REMOVE | STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + } + CPlayerPed::SetupPlayerPed(index); + CWorld::Players[index].m_pPed->CharCreatedBy = MISSION_CHAR; + CPlayerPed::DeactivatePlayerPed(index); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += CWorld::Players[index].m_pPed->GetDistanceFromCentreOfMassToBaseOfModel(); + CWorld::Players[index].m_pPed->SetPosition(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, CWorld::Players[index].m_pPed); + CPlayerPed::ReactivatePlayerPed(index); + ScriptParams[0] = index; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_PLAYER_COORDINATES: + { + CVector pos; + CollectParameters(&m_nIp, 1); + if (CWorld::Players[ScriptParams[0]].m_pPed->bInVehicle) + pos = CWorld::Players[ScriptParams[0]].m_pPed->m_pMyVehicle->GetPosition(); + else + pos = CWorld::Players[ScriptParams[0]].m_pPed->GetPosition(); + *(CVector*)&ScriptParams[0] = pos; + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_SET_PLAYER_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[1]; + int index = ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPlayerPed* ped = CWorld::Players[index].m_pPed; + if (!ped->bInVehicle) { + pos.z += ped->GetDistanceFromCentreOfMassToBaseOfModel(); + ped->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + return 0; + } + pos.z += ped->m_pMyVehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + if (ped->m_pMyVehicle->IsBoat()) + ped->m_pMyVehicle->Teleport(pos); + else + ped->m_pMyVehicle->Teleport(pos); + /* I'll keep this condition here but obviously it is absolutely pointless */ + /* It's clearly present in disassembly so it had to be in original code */ + CTheScripts::ClearSpaceForMissionEntity(pos, ped->m_pMyVehicle); + return 0; + } + case COMMAND_IS_PLAYER_IN_AREA_2D: + { + CollectParameters(&m_nIp, 6); + CPlayerPed* ped = CWorld::Players[ScriptParams[0]].m_pPed; + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + if (!ped->bInVehicle) + UpdateCompareFlag(ped->IsWithinArea(x1, y1, x2, y2)); + else + UpdateCompareFlag(ped->m_pMyVehicle->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_PLAYER_IN_AREA_3D: + { + CollectParameters(&m_nIp, 8); + CPlayerPed* ped = CWorld::Players[ScriptParams[0]].m_pPed; + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float z1 = *(float*)&ScriptParams[3]; + float x2 = *(float*)&ScriptParams[4]; + float y2 = *(float*)&ScriptParams[5]; + float z2 = *(float*)&ScriptParams[6]; + if (ped->bInVehicle) + UpdateCompareFlag(ped->m_pMyVehicle->IsWithinArea(x1, y1, z1, x2, y2, z2)); + else + UpdateCompareFlag(ped->IsWithinArea(x1, y1, z1, x2, y2, z2)); + if (ScriptParams[7]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, (z1 + z2) / 2); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(x1, y1, z1, x2, y2, z2); + return 0; + } + case COMMAND_ADD_INT_VAR_TO_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 += *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_INT_LVAR_TO_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 += *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_ADD_INT_VAR_TO_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 += *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_INT_LVAR_TO_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 += *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_ADD_FLOAT_VAR_TO_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 += *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 += *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_ADD_FLOAT_VAR_TO_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 += *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 += *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_INT_VAR_FROM_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 -= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_SUB_INT_LVAR_FROM_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 -= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 -= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 -= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + default: + script_assert(0); + break; + } + return -1; +} + +int8 CRunningScript::ProcessCommands100To199(int32 command) +{ + float *fScriptVar1; + int *nScriptVar1; + switch (command) { + case COMMAND_SUB_INT_LVAR_FROM_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 -= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_INT_VAR_FROM_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 -= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 -= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 -= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_INT_VAR_BY_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 *= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_INT_VAR_BY_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 *= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_MULT_INT_LVAR_BY_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 *= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_INT_LVAR_BY_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 *= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_MULT_FLOAT_VAR_BY_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 *= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_FLOAT_VAR_BY_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 *= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 *= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 *= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_DIV_INT_VAR_BY_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 /= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_DIV_INT_VAR_BY_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *nScriptVar1 /= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_DIV_INT_LVAR_BY_INT_VAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 /= *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_DIV_INT_LVAR_BY_INT_LVAR: + nScriptVar1 = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *nScriptVar1 /= *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_DIV_FLOAT_VAR_BY_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 /= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_DIV_FLOAT_VAR_BY_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 /= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 /= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 /= *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_ADD_TIMED_VAL_TO_FLOAT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr += CTimer::GetTimeStep() * *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_ADD_TIMED_VAL_TO_FLOAT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr += CTimer::GetTimeStep() * *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 += CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; +#ifdef FIX_BUGS + case COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_LVAR: +#else + case COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_VAR: +#endif + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 += CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; +#ifdef FIX_BUGS + case COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_VAR: +#else + case COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_LVAR: +#endif + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 += CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 += CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SUB_TIMED_VAL_FROM_FLOAT_VAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr -= CTimer::GetTimeStep() * *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SUB_TIMED_VAL_FROM_FLOAT_LVAR: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *(float*)ptr -= CTimer::GetTimeStep() * *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_VAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 -= CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; +#ifdef FIX_BUGS // in SA it was fixed by reversing their order in enum + case COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_LVAR: +#else + case COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_VAR: +#endif + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *fScriptVar1 -= CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; +#ifdef FIX_BUGS + case COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_VAR: +#else + case COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_LVAR: +#endif + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 -= CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + case COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_LVAR: + fScriptVar1 = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *fScriptVar1 -= CTimer::GetTimeStep() * *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + case COMMAND_SET_VAR_INT_TO_VAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_SET_VAR_INT_TO_LVAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_SET_LVAR_INT_TO_VAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_SET_LVAR_INT_TO_LVAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_SET_VAR_FLOAT_TO_VAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_SET_VAR_FLOAT_TO_LVAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_SET_LVAR_FLOAT_TO_VAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_SET_LVAR_FLOAT_TO_LVAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_CSET_VAR_INT_TO_VAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_CSET_VAR_INT_TO_LVAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_CSET_LVAR_INT_TO_VAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_CSET_LVAR_INT_TO_LVAR_FLOAT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *(float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_CSET_VAR_FLOAT_TO_VAR_INT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_CSET_VAR_FLOAT_TO_LVAR_INT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_CSET_LVAR_FLOAT_TO_VAR_INT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + return 0; + } + case COMMAND_CSET_LVAR_FLOAT_TO_LVAR_INT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = *GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + return 0; + } + case COMMAND_ABS_VAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = ABS(*ptr); + return 0; + } + case COMMAND_ABS_LVAR_INT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = ABS(*ptr); + return 0; + } + case COMMAND_ABS_VAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + *ptr = ABS(*ptr); + return 0; + } + case COMMAND_ABS_LVAR_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + *ptr = ABS(*ptr); + return 0; + } + case COMMAND_GENERATE_RANDOM_FLOAT: + { + float* ptr = (float*)GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CGeneral::GetRandomNumber(); + CGeneral::GetRandomNumber(); + CGeneral::GetRandomNumber(); /* To make it EXTRA random! */ +#ifdef FIX_BUGS + *ptr = CGeneral::GetRandomNumberInRange(0.0f, 1.0f); +#else + *ptr = CGeneral::GetRandomNumber() / 65536.0f; + /* Between 0 and 0.5 on PC (oh well...), never used in original script. */ +#endif + + return 0; + } + case COMMAND_GENERATE_RANDOM_INT: + *GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL) = CGeneral::GetRandomNumber(); + return 0; + case COMMAND_CREATE_CHAR: + { + CollectParameters(&m_nIp, 5); + switch (ScriptParams[1]) { + case MI_COP: + if (ScriptParams[0] == PEDTYPE_COP) + ScriptParams[1] = COP_STREET; + break; + case MI_SWAT: + if (ScriptParams[0] == PEDTYPE_COP) + ScriptParams[1] = COP_SWAT; + break; + case MI_FBI: + if (ScriptParams[0] == PEDTYPE_COP) + ScriptParams[1] = COP_FBI; + break; + case MI_ARMY: + if (ScriptParams[0] == PEDTYPE_COP) + ScriptParams[1] = COP_ARMY; + break; + case MI_MEDIC: + if (ScriptParams[0] == PEDTYPE_EMERGENCY) + ScriptParams[1] = PEDTYPE_EMERGENCY; + break; + case MI_FIREMAN: + if (ScriptParams[0] == PEDTYPE_FIREMAN) + ScriptParams[1] = PEDTYPE_FIREMAN; + break; + default: + break; + } + CPed* ped; + if (ScriptParams[0] == PEDTYPE_COP) + ped = new CCopPed((eCopType)ScriptParams[1]); + else if (ScriptParams[0] == PEDTYPE_EMERGENCY || ScriptParams[0] == PEDTYPE_FIREMAN) + ped = new CEmergencyPed(ScriptParams[1]); + else + ped = new CCivilianPed((ePedType)ScriptParams[0], ScriptParams[1]); + ped->CharCreatedBy = MISSION_CHAR; + ped->bRespondsToThreats = false; + ped->bAllowMedicsToReviveMe = false; + CVector pos = *(CVector*)&ScriptParams[2]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += 1.0f; + ped->SetPosition(pos); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + CWorld::Add(ped); + ped->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pos); + CPopulation::ms_nTotalMissionPeds++; + ScriptParams[0] = CPools::GetPedPool()->GetIndex(ped); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_DELETE_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + if (ped) { + if (ped->InVehicle()) { + if (ped->m_pMyVehicle->pDriver == ped) { + ped->m_pMyVehicle->RemoveDriver(); + ped->m_pMyVehicle->SetStatus(STATUS_ABANDONED); + if (ped->m_pMyVehicle->m_nDoorLock == CARLOCK_LOCKED_INITIALLY) + ped->m_pMyVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + if (ped->m_nPedType == PEDTYPE_COP && ped->m_pMyVehicle->IsLawEnforcementVehicle()) + ped->m_pMyVehicle->ChangeLawEnforcerState(0); + } + else { + ped->m_pMyVehicle->RemovePassenger(ped); + } + } + CWorld::RemoveReferencesToDeletedObject(ped); + delete ped; + --CPopulation::ms_nTotalMissionPeds; + } + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_CHAR_WANDER_DIR: + { + CollectParameters(&m_nIp, 2); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + ped->ClearAll(); + int8 path = ScriptParams[1]; + if (ScriptParams[1] < 0 || ScriptParams[1] > 7) + // Max number GetRandomNumberInRange returns is max-1 +#ifdef FIX_BUGS + path = CGeneral::GetRandomNumberInRange(0, 8); +#else + path = CGeneral::GetRandomNumberInRange(0, 7); +#endif + ped->SetWanderPath(path); + return 0; + } + /* Not implemented. + case COMMAND_CHAR_WANDER_RANGE: + */ + case COMMAND_CHAR_FOLLOW_PATH: + { + CollectParameters(&m_nIp, 4); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + ped->ClearAll(); + ped->SetFollowPath(pos); + return 0; + } + case COMMAND_CHAR_SET_IDLE: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + ped->bScriptObjectiveCompleted = false; + ped->SetObjective(OBJECTIVE_WAIT_ON_FOOT); + return 0; + } + case COMMAND_GET_CHAR_COORDINATES: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* vehicle; + CVector pos; + /* Seems a bit clumsy but I'll leave original flow */ + if (ped->bInVehicle) + vehicle = ped->m_pMyVehicle; + else + vehicle = nil; + if (vehicle) + pos = vehicle->GetPosition(); + else + pos = ped->GetPosition(); + *(CVector*)&ScriptParams[0] = pos; + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_SET_CHAR_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* vehicle; + if (ped->bInVehicle) + vehicle = ped->m_pMyVehicle; + else + vehicle = nil; + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + /* The following block was once again written + * by someone not familiar with virtual functions. + * It doesn't require any ifs at all. + * To keep as close to original as possible, I'll keep it. + * Maybe there was more commented out/debug + * stuff, but I doubt it. + */ + if (!vehicle) { + pos.z += ped->GetDistanceFromCentreOfMassToBaseOfModel(); + ped->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + } + else if (vehicle->IsBoat()) { + pos.z += vehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + vehicle->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, vehicle); + } + else { + pos.z += vehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + vehicle->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, vehicle); + } + /* Short version of this command. + * + * CollectParameters(&m_nIp, 4); + * CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + * script_assert(ped); + * CEntity* entityToMove = ped->bInVehicle ? ped->m_pMyVehicle : ped; + * CVector pos = *(CVector*)&ScriptParams[1]; + * if (pos.z <= MAP_Z_LOW_LIMIT) + * pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + * pos.z += entityToMove->GetDistanceFromCentreOfMassToBaseOfModel(); + * entityToMove->Teleport(pos); + * CTheScripts::ClearSpaceForMissionEntity(pos, entityToMove); + * + */ + return 0; + } + case COMMAND_IS_CHAR_STILL_ALIVE: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(ped && ped->GetPedState() != PED_DEAD && ped->GetPedState() != PED_DIE); + return 0; + } + case COMMAND_IS_CHAR_IN_AREA_2D: + { + CollectParameters(&m_nIp, 6); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* vehicle; + if (ped->bInVehicle) + vehicle = ped->m_pMyVehicle; + else + vehicle = nil; + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + if (vehicle) + UpdateCompareFlag(ped->m_pMyVehicle->IsWithinArea(x1, y1, x2, y2)); + else + UpdateCompareFlag(ped->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CHAR_IN_AREA_3D: + { + CollectParameters(&m_nIp, 8); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* vehicle; + if (ped->bInVehicle) + vehicle = ped->m_pMyVehicle; + else + vehicle = nil; + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float z1 = *(float*)&ScriptParams[3]; + float x2 = *(float*)&ScriptParams[4]; + float y2 = *(float*)&ScriptParams[5]; + float z2 = *(float*)&ScriptParams[6]; + if (vehicle) + UpdateCompareFlag(ped->m_pMyVehicle->IsWithinArea(x1, y1, z1, x2, y2, z2)); + else + UpdateCompareFlag(ped->IsWithinArea(x1, y1, z1, x2, y2, z2)); + if (ScriptParams[7]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, (z1 + z2) / 2); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(x1, y1, z1, x2, y2, z2); + return 0; + } + case COMMAND_CREATE_CAR: + { + CollectParameters(&m_nIp, 4); + int32 handle; + if (CModelInfo::IsBoatModel(ScriptParams[0])) { + CBoat* boat = new CBoat(ScriptParams[0], MISSION_VEHICLE); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += boat->GetDistanceFromCentreOfMassToBaseOfModel(); + boat->SetPosition(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, boat); + boat->SetStatus(STATUS_ABANDONED); + boat->bIsLocked = true; + boat->AutoPilot.m_nCarMission = MISSION_NONE; + boat->AutoPilot.m_nTempAction = TEMPACT_NONE; /* Animation ID? */ + boat->AutoPilot.m_nCruiseSpeed = boat->AutoPilot.m_fMaxTrafficSpeed = 20.0f; + CWorld::Add(boat); + handle = CPools::GetVehiclePool()->GetIndex(boat); + } + else { + CVehicle* car; + if (!CModelInfo::IsBikeModel(ScriptParams[0])) + car = new CAutomobile(ScriptParams[0], MISSION_VEHICLE); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += car->GetDistanceFromCentreOfMassToBaseOfModel(); + car->SetPosition(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, car); + car->SetStatus(STATUS_ABANDONED); + car->bIsLocked = true; + CCarCtrl::JoinCarWithRoadSystem(car); + car->AutoPilot.m_nCarMission = MISSION_NONE; + car->AutoPilot.m_nTempAction = TEMPACT_NONE; /* Animation ID? */ + car->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + car->AutoPilot.m_nCruiseSpeed = car->AutoPilot.m_fMaxTrafficSpeed = 9.0f; + car->AutoPilot.m_nCurrentLane = car->AutoPilot.m_nNextLane = 0; + car->bEngineOn = false; + car->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pos); + car->bHasBeenOwnedByPlayer = true; + CWorld::Add(car); + handle = CPools::GetVehiclePool()->GetIndex(car); + } + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(handle, CLEANUP_CAR); + return 0; + } + case COMMAND_DELETE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (car) { + CWorld::Remove(car); + CWorld::RemoveReferencesToDeletedObject(car); + delete car; + } + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CAR); + return 0; + } + case COMMAND_CAR_GOTO_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += car->GetDistanceFromCentreOfMassToBaseOfModel(); + if (CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, pos, false)) + car->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_STRAIGHT; + else + car->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS; + car->SetStatus(STATUS_PHYSICS); + car->bEngineOn = true; + car->AutoPilot.m_nCruiseSpeed = Max(6, car->AutoPilot.m_nCruiseSpeed); + car->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + return 0; + } + case COMMAND_CAR_WANDER_RANDOMLY: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + CCarCtrl::JoinCarWithRoadSystem(car); + car->AutoPilot.m_nCarMission = MISSION_CRUISE; + car->bEngineOn = true; + car->AutoPilot.m_nCruiseSpeed = Max(6, car->AutoPilot.m_nCruiseSpeed); + car->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + return 0; + } + case COMMAND_CAR_SET_IDLE: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + car->AutoPilot.m_nCarMission = MISSION_NONE; + return 0; + } + case COMMAND_GET_CAR_COORDINATES: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + *(CVector*)&ScriptParams[0] = car->GetPosition(); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_SET_CAR_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += car->GetDistanceFromCentreOfMassToBaseOfModel(); + car->SetIsStatic(false); + /* Again weird usage of virtual functions. */ + if (car->IsBoat()) { + car->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, car); + } + else { + car->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, car); + /* May the following be inlined CCarCtrl function? */ + switch (car->AutoPilot.m_nCarMission) { + case MISSION_CRUISE: + CCarCtrl::JoinCarWithRoadSystem(car); + break; + case MISSION_RAMPLAYER_FARAWAY: + case MISSION_RAMPLAYER_CLOSE: + case MISSION_BLOCKPLAYER_FARAWAY: + case MISSION_BLOCKPLAYER_CLOSE: + case MISSION_BLOCKPLAYER_HANDBRAKESTOP: + CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, FindPlayerCoors(), false); + break; + case MISSION_GOTOCOORDS: + case MISSION_GOTOCOORDS_STRAIGHT: + CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, car->AutoPilot.m_vecDestinationCoors, false); + break; + case MISSION_GOTOCOORDS_ACCURATE: + case MISSION_GOTO_COORDS_STRAIGHT_ACCURATE: + CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, car->AutoPilot.m_vecDestinationCoors, false); + break; + case MISSION_RAMCAR_FARAWAY: + case MISSION_RAMCAR_CLOSE: + case MISSION_BLOCKCAR_FARAWAY: + case MISSION_BLOCKCAR_CLOSE: + case MISSION_BLOCKCAR_HANDBRAKESTOP: + CCarCtrl::JoinCarWithRoadSystemGotoCoors(car, car->AutoPilot.m_pTargetCar->GetPosition(), false); + break; + default: + break; + } + } + return 0; + } + case COMMAND_IS_CAR_STILL_ALIVE: + { + CollectParameters(&m_nIp, 1); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(car && car->GetStatus() != STATUS_WRECKED && (car->IsBoat() || !car->bIsInWater)); + return 0; + } + case COMMAND_SET_CAR_CRUISE_SPEED: + { + CollectParameters(&m_nIp, 2); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); +#if defined MISSION_REPLAY && defined SIMPLIER_MISSIONS + car->AutoPilot.m_nCruiseSpeed = *(float*)&ScriptParams[1]; + if (missionRetryScriptIndex == 40 && car->GetModelIndex() == MI_CHEETAH) // Turismo + car->AutoPilot.m_nCruiseSpeed = 8 * car->AutoPilot.m_nCruiseSpeed / 10; + car->AutoPilot.m_nCruiseSpeed = Min(car->AutoPilot.m_nCruiseSpeed, 60.0f * car->pHandling->Transmission.fMaxCruiseVelocity); +#else + car->AutoPilot.m_nCruiseSpeed = Min(*(float*)&ScriptParams[1], 60.0f * car->pHandling->Transmission.fMaxCruiseVelocity); +#endif + return 0; + } + case COMMAND_SET_CAR_DRIVING_STYLE: + { + CollectParameters(&m_nIp, 2); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + car->AutoPilot.m_nDrivingStyle = (uint8)ScriptParams[1]; + return 0; + } + case COMMAND_SET_CAR_MISSION: + { + CollectParameters(&m_nIp, 2); + CVehicle* car = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(car); + car->AutoPilot.m_nCarMission = (uint8)ScriptParams[1]; + car->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + car->bEngineOn = true; + return 0; + } + case COMMAND_IS_CAR_IN_AREA_2D: + { + CollectParameters(&m_nIp, 6); + CVehicle* vehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(vehicle); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + UpdateCompareFlag(vehicle->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CAR_IN_AREA_3D: + { + CollectParameters(&m_nIp, 8); + CVehicle* vehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(vehicle); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float z1 = *(float*)&ScriptParams[3]; + float x2 = *(float*)&ScriptParams[4]; + float y2 = *(float*)&ScriptParams[5]; + float z2 = *(float*)&ScriptParams[6]; + UpdateCompareFlag(vehicle->IsWithinArea(x1, y1, z1, x2, y2, z2)); + if (ScriptParams[7]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, (z1 + z2) / 2); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(x1, y1, z1, x2, y2, z2); + return 0; + } + case COMMAND_SPECIAL_0: + case COMMAND_SPECIAL_1: + case COMMAND_SPECIAL_2: + case COMMAND_SPECIAL_3: + case COMMAND_SPECIAL_4: + case COMMAND_SPECIAL_5: + case COMMAND_SPECIAL_6: + case COMMAND_SPECIAL_7: + script_assert(0); + return 0; + case COMMAND_PRINT_BIG: + { + wchar* key = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); +#ifdef MISSION_REPLAY + if (strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "M_FAIL") == 0 && CanAllowMissionReplay()) + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_SCRIPT_TO_TERMINATE; +#endif + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + CMessages::AddBigMessage(key, ScriptParams[0], ScriptParams[1] - 1); + return 0; + } + case COMMAND_PRINT: + { + wchar* key = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + CMessages::AddMessage(key, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_PRINT_NOW: + { + wchar* key = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + CMessages::AddMessageJumpQ(key, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_PRINT_SOON: + { + wchar* key = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + CMessages::AddMessageSoon(key, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_CLEAR_PRINTS: + CMessages::ClearMessages(); + return 0; + case COMMAND_GET_TIME_OF_DAY: + ScriptParams[0] = CClock::GetHours(); + ScriptParams[1] = CClock::GetMinutes(); + StoreParameters(&m_nIp, 2); + return 0; + case COMMAND_SET_TIME_OF_DAY: + CollectParameters(&m_nIp, 2); + CClock::SetGameClock(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_GET_MINUTES_TO_TIME_OF_DAY: + CollectParameters(&m_nIp, 2); + ScriptParams[0] = CClock::GetGameClockMinutesUntil(ScriptParams[0], ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_IS_POINT_ON_SCREEN: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= -100) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + UpdateCompareFlag(TheCamera.IsSphereVisible(pos, *(float*)&ScriptParams[3])); + return 0; + } + case COMMAND_DEBUG_ON: + CTheScripts::DbgFlag = true; + return 0; + case COMMAND_DEBUG_OFF: + CTheScripts::DbgFlag = false; + return 0; + case COMMAND_RETURN_TRUE: + UpdateCompareFlag(true); + return 0; + case COMMAND_RETURN_FALSE: + UpdateCompareFlag(false); + return 0; + /* Special command only used by compiler. + case COMMAND_VAR_INT: + */ + default: + script_assert(0); + break; + } + return -1; +} + +int8 CRunningScript::ProcessCommands200To299(int32 command) +{ + switch (command) { + /* Special commands. + case COMMAND_VAR_FLOAT: + case COMMAND_LVAR_INT: + case COMMAND_LVAR_FLOAT: + case COMMAND_LBRACKET: + case COMMAND_RBRACKET: + case COMMAND_REPEAT: + case COMMAND_ENDREPEAT: + case COMMAND_IF: + case COMMAND_IFNOT: + case COMMAND_ELSE: + case COMMAND_ENDIF: + case COMMAND_WHILE: + case COMMAND_WHILENOT: + case COMMAND_ENDWHILE: + */ + case COMMAND_ANDOR: + CollectParameters(&m_nIp, 1); + m_nAndOrState = ScriptParams[0]; + if (m_nAndOrState == ANDOR_NONE){ + m_bCondResult = false; // pointless + }else if (m_nAndOrState >= ANDS_1 && m_nAndOrState <= ANDS_8){ + m_bCondResult = true; + m_nAndOrState++; + }else if (m_nAndOrState >= ORS_1 && m_nAndOrState <= ORS_8){ + m_bCondResult = false; + m_nAndOrState++; + }else{ + script_assert(0 && "COMMAND_ANDOR: invalid ANDOR state"); + } + return 0; + case COMMAND_LAUNCH_MISSION: + { + CollectParameters(&m_nIp, 1); + CRunningScript* pNew = CTheScripts::StartNewScript(ScriptParams[0]); + pNew->m_bIsMissionScript = true; + return 0; + } + case COMMAND_MISSION_HAS_FINISHED: + { + if (!m_bIsMissionScript) + return 0; + if (strcmp(m_abScriptName, "love3") == 0) /* A Drop in the Ocean */ + CPickups::RemoveAllFloatingPickups(); + CTheScripts::MissionCleanUp.Process(); + return 0; + } + case COMMAND_STORE_CAR_CHAR_IS_IN: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + CVehicle* pCurrent = nil; + if (ped->bInVehicle) { + pCurrent = ped->m_pMyVehicle; + } + script_assert(pCurrent); // GetIndex(0) doesn't look good + int handle = CPools::GetVehiclePool()->GetIndex(pCurrent); + if (handle != CTheScripts::StoreVehicleIndex && m_bIsMissionScript){ + CVehicle* pOld = CPools::GetVehiclePool()->GetAt(CTheScripts::StoreVehicleIndex); + if (pOld){ + CCarCtrl::RemoveFromInterestingVehicleList(pOld); + if (pOld->VehicleCreatedBy == MISSION_VEHICLE && CTheScripts::StoreVehicleWasRandom){ + pOld->VehicleCreatedBy = RANDOM_VEHICLE; + pOld->bIsLocked = false; + CCarCtrl::NumRandomCars++; + CCarCtrl::NumMissionCars--; + CTheScripts::MissionCleanUp.RemoveEntityFromList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + } + } + + CTheScripts::StoreVehicleIndex = handle; + switch (pCurrent->VehicleCreatedBy){ + case RANDOM_VEHICLE: + pCurrent->VehicleCreatedBy = MISSION_VEHICLE; + CCarCtrl::NumMissionCars++; + CCarCtrl::NumRandomCars--; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::MissionCleanUp.AddEntityToList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + break; + case PARKED_VEHICLE: + pCurrent->VehicleCreatedBy = MISSION_VEHICLE; + CCarCtrl::NumMissionCars++; + CCarCtrl::NumParkedCars--; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::MissionCleanUp.AddEntityToList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + break; + case MISSION_VEHICLE: + case PERMANENT_VEHICLE: + CTheScripts::StoreVehicleWasRandom = false; + break; + default: + break; + } + } + ScriptParams[0] = CTheScripts::StoreVehicleIndex; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_STORE_CAR_PLAYER_IS_IN: + { + CollectParameters(&m_nIp, 1); + CPed* ped = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(ped); + if (!ped->bInVehicle) + return 0; // No value written to output variable + CVehicle* pCurrent = ped->m_pMyVehicle; + script_assert(pCurrent); // Here pCurrent shouldn't be NULL anyway + int handle = CPools::GetVehiclePool()->GetIndex(pCurrent); + if (handle != CTheScripts::StoreVehicleIndex && m_bIsMissionScript) { + CVehicle* pOld = CPools::GetVehiclePool()->GetAt(CTheScripts::StoreVehicleIndex); + if (pOld){ + CCarCtrl::RemoveFromInterestingVehicleList(pOld); + if (pOld->VehicleCreatedBy == MISSION_VEHICLE && CTheScripts::StoreVehicleWasRandom){ + pOld->VehicleCreatedBy = RANDOM_VEHICLE; + pOld->bIsLocked = false; + CCarCtrl::NumRandomCars++; + CCarCtrl::NumMissionCars--; + CTheScripts::MissionCleanUp.RemoveEntityFromList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + } + } + + CTheScripts::StoreVehicleIndex = handle; + switch (pCurrent->VehicleCreatedBy) { + case RANDOM_VEHICLE: + pCurrent->VehicleCreatedBy = MISSION_VEHICLE; + CCarCtrl::NumMissionCars++; + CCarCtrl::NumRandomCars--; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::MissionCleanUp.AddEntityToList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + break; + case PARKED_VEHICLE: + pCurrent->VehicleCreatedBy = MISSION_VEHICLE; + CCarCtrl::NumMissionCars++; + CCarCtrl::NumParkedCars--; + CTheScripts::StoreVehicleWasRandom = true; + CTheScripts::MissionCleanUp.AddEntityToList(CTheScripts::StoreVehicleIndex, CLEANUP_CAR); + break; + case MISSION_VEHICLE: + case PERMANENT_VEHICLE: + CTheScripts::StoreVehicleWasRandom = false; + break; + default: + break; + } + } + ScriptParams[0] = CTheScripts::StoreVehicleIndex; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_CHAR_IN_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CVehicle* pCheckedVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CVehicle* pActualVehicle = pPed->bInVehicle ? pPed->m_pMyVehicle : nil; + UpdateCompareFlag(pActualVehicle && pActualVehicle == pCheckedVehicle); + return 0; + } + case COMMAND_IS_PLAYER_IN_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pCheckedVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle == pCheckedVehicle); + return 0; + } + case COMMAND_IS_CHAR_IN_MODEL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CVehicle* pActualVehicle = pPed->bInVehicle ? pPed->m_pMyVehicle : nil; + UpdateCompareFlag(pActualVehicle && pActualVehicle->GetModelIndex() == ScriptParams[1]); + return 0; + } + case COMMAND_IS_PLAYER_IN_MODEL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->GetModelIndex() == ScriptParams[1]); + return 0; + } + case COMMAND_IS_CHAR_IN_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(pPed->bInVehicle); + return 0; + } + case COMMAND_IS_PLAYER_IN_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + UpdateCompareFlag(pPed->bInVehicle); + return 0; + } + case COMMAND_IS_BUTTON_PRESSED: + { + CollectParameters(&m_nIp, 2); + bool value = GetPadState(ScriptParams[0], ScriptParams[1]) != 0; + if (CGame::playingIntro && ScriptParams[0] == 0 && ScriptParams[1] == 12) { + if (CPad::GetPad(0)->GetLeftMouseJustDown() || + CPad::GetPad(0)->GetEnterJustDown() || + CPad::GetPad(0)->GetCharJustDown(' ')) + value = true; + } + UpdateCompareFlag(value); + return 0; + } + case COMMAND_GET_PAD_STATE: + { + CollectParameters(&m_nIp, 1); + ScriptParams[0] = GetPadState(ScriptParams[0], ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_LOCATE_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D: + LocatePlayerCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_2D: + LocatePlayerCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D: + LocateCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_2D: + LocateCharCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D: + LocatePlayerCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D: + LocatePlayerCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D: + LocateCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D: + LocateCharCharCommand(command, &m_nIp); + return 0; + case COMMAND_CREATE_OBJECT: + { + CollectParameters(&m_nIp, 4); + int mi = ScriptParams[0] >= 0 ? ScriptParams[0] : CTheScripts::UsedObjectArray[-ScriptParams[0]].index; + CObject* pObj = new CObject(mi, false); + pObj->ObjectCreatedBy = MISSION_OBJECT; + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += pObj->GetDistanceFromCentreOfMassToBaseOfModel(); + pObj->SetPosition(pos); + pObj->SetOrientation(0.0f, 0.0f, 0.0f); + pObj->GetMatrix().UpdateRW(); + pObj->UpdateRwFrame(); + CTheScripts::ClearSpaceForMissionEntity(pos, pObj); + CWorld::Add(pObj); + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pObj); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_DELETE_OBJECT: + { + CollectParameters(&m_nIp, 1); + CObject* pObj = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + if (pObj){ + CWorld::Remove(pObj); + CWorld::RemoveReferencesToDeletedObject(pObj); + delete pObj; + } + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_ADD_SCORE: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_nMoney += ScriptParams[1]; + return 0; + case COMMAND_IS_SCORE_GREATER: + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_nMoney > ScriptParams[1]); + return 0; + case COMMAND_STORE_SCORE: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CWorld::Players[ScriptParams[0]].m_nMoney; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_GIVE_REMOTE_CONTROLLED_CAR_TO_PLAYER: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRemote::GivePlayerRemoteControlledCar(pos.x, pos.y, pos.z, DEGTORAD(*(float*)&ScriptParams[4]), MI_RCBANDIT); + return 0; + } + case COMMAND_ALTER_WANTED_LEVEL: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_pPed->SetWantedLevel(ScriptParams[1]); + return 0; + case COMMAND_ALTER_WANTED_LEVEL_NO_DROP: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_pPed->SetWantedLevelNoDrop(ScriptParams[1]); + return 0; + case COMMAND_IS_WANTED_LEVEL_GREATER: + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_pPed->m_pWanted->GetWantedLevel() > ScriptParams[1]); + return 0; + case COMMAND_CLEAR_WANTED_LEVEL: + CollectParameters(&m_nIp, 1); + CWorld::Players[ScriptParams[0]].m_pPed->SetWantedLevel(0); + return 0; + case COMMAND_SET_DEATHARREST_STATE: + CollectParameters(&m_nIp, 1); + m_bDeatharrestEnabled = (ScriptParams[0] == 1); + return 0; + case COMMAND_HAS_DEATHARREST_BEEN_EXECUTED: + UpdateCompareFlag(m_bDeatharrestExecuted); + return 0; + case COMMAND_ADD_AMMO_TO_PLAYER: + { + CollectParameters(&m_nIp, 3); + CWorld::Players[ScriptParams[0]].m_pPed->GrantAmmo((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_ADD_AMMO_TO_CHAR: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->GrantAmmo((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + /* Not implemented + case COMMAND_ADD_AMMO_TO_CAR: + case COMMAND_IS_PLAYER_STILL_ALIVE: + */ + case COMMAND_IS_PLAYER_DEAD: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_WBState == WBSTATE_WASTED); + return 0; + case COMMAND_IS_CHAR_DEAD: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(!pPed || pPed->GetPedState() == PED_DIE || pPed->GetPedState() == PED_DEAD); + return 0; + } + case COMMAND_IS_CAR_DEAD: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(!pVehicle || pVehicle->GetStatus() == STATUS_WRECKED || !pVehicle->IsBoat() && pVehicle->bIsInWater); + return 0; + } + case COMMAND_SET_CHAR_THREAT_SEARCH: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_fearFlags |= ScriptParams[1]; + return 0; + } + /* Not implemented. + case COMMAND_SET_CHAR_THREAT_REACTION: + */ + case COMMAND_SET_CHAR_OBJ_NO_OBJ: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->ClearObjective(); + return 0; + } + /* Not implemented. + case COMMAND_ORDER_DRIVER_OUT_OF_CAR: + case COMMAND_ORDER_CHAR_TO_DRIVE_CAR: + case COMMAND_ADD_PATROL_POINT: + case COMMAND_IS_PLAYER_IN_GANGZONE: + */ + case COMMAND_IS_PLAYER_IN_ZONE: + { + CollectParameters(&m_nIp, 1); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + int zoneToCheck = CTheZones::FindZoneByLabelAndReturnIndex(label); + if (zoneToCheck != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; /* why only if zone != -1? */ + CVector pos = pPlayer->GetPos(); + CZone* pZone = CTheZones::GetZone(zoneToCheck); + UpdateCompareFlag(CTheZones::PointLiesWithinZone(&pos, pZone)); + return 0; + } + case COMMAND_IS_PLAYER_PRESSING_HORN: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_pPed->GetPedState() == PED_DRIVING && + CPad::GetPad(ScriptParams[0])->GetHorn()); + /* Is it correct that same parameter is used both as index of Players */ + /* and as ID of pad? Pratically this parameter is always 0 anyway of course. */ + return 0; + case COMMAND_HAS_CHAR_SPOTTED_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->OurPedCanSeeThisOne(CWorld::Players[ScriptParams[1]].m_pPed)); + return 0; + } + /* Not implemented. + case COMMAND_ORDER_CHAR_TO_BACKDOOR: + case COMMAND_ADD_CHAR_TO_GANG: + */ + case COMMAND_IS_CHAR_OBJECTIVE_PASSED: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bScriptObjectiveCompleted); + return 0; + } + /* Not implemented. + case COMMAND_SET_CHAR_DRIVE_AGGRESSION: + case COMMAND_SET_CHAR_MAX_DRIVESPEED: + */ + case COMMAND_CREATE_CHAR_INSIDE_CAR: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + switch (ScriptParams[2]) { + case MI_COP: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_STREET; + break; + case MI_SWAT: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_SWAT; + break; + case MI_FBI: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_FBI; + break; + case MI_ARMY: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_ARMY; + break; + case MI_MEDIC: + if (ScriptParams[1] == PEDTYPE_EMERGENCY) + ScriptParams[2] = PEDTYPE_EMERGENCY; + break; + case MI_FIREMAN: + if (ScriptParams[1] == PEDTYPE_FIREMAN) + ScriptParams[2] = PEDTYPE_FIREMAN; + break; + default: + break; + } + CPed* pPed; + if (ScriptParams[1] == PEDTYPE_COP) + pPed = new CCopPed((eCopType)ScriptParams[2]); + else if (ScriptParams[1] == PEDTYPE_EMERGENCY || ScriptParams[1] == PEDTYPE_FIREMAN) + pPed = new CEmergencyPed(ScriptParams[2]); + else + pPed = new CCivilianPed((ePedType)ScriptParams[1], ScriptParams[2]); + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + pPed->bAllowMedicsToReviveMe = false; + pPed->SetPosition(pVehicle->GetPosition()); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + pPed->SetPedState(PED_DRIVING); + CPopulation::ms_nTotalMissionPeds++; + script_assert(!pVehicle->pDriver); + pVehicle->pDriver = pPed; + pVehicle->pDriver->RegisterReference((CEntity**)&pVehicle->pDriver); + pPed->m_pMyVehicle = pVehicle; + pPed->m_pMyVehicle->RegisterReference((CEntity**)&pPed->m_pMyVehicle); + pPed->bInVehicle = true; + pVehicle->SetStatus(STATUS_PHYSICS); + if (!pVehicle->IsBoat()) + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->bEngineOn = true; + pPed->bUsesCollision = false; +#ifdef FIX_BUGS + AnimationId anim = pVehicle->GetDriverAnim(); +#else + AnimationId anim = pVehicle->bLowVehicle ? ANIM_STD_CAR_SIT_LO : ANIM_STD_CAR_SIT; +#endif + pPed->m_pVehicleAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, anim, 100.0f); + pPed->StopNonPartialAnims(); + pPed->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pPed->GetPosition()); + CWorld::Add(pPed); + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pPed); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_WARP_PLAYER_FROM_CAR_TO_COORD: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[1]; + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + if (pPlayer->m_pPed->bInVehicle){ + script_assert(pPlayer->m_pPed->m_pMyVehicle); + if (pPlayer->m_pPed->m_pMyVehicle->bIsBus) + pPlayer->m_pPed->bRenderPedInCar = true; + if (pPlayer->m_pPed->m_pMyVehicle->pDriver == pPlayer->m_pPed){ + pPlayer->m_pPed->m_pMyVehicle->RemoveDriver(); + pPlayer->m_pPed->m_pMyVehicle->SetStatus(STATUS_ABANDONED); + pPlayer->m_pPed->m_pMyVehicle->bEngineOn = false; + pPlayer->m_pPed->m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + }else{ + pPlayer->m_pPed->m_pMyVehicle->RemovePassenger(pPlayer->m_pPed); + } + } + pPlayer->m_pPed->bInVehicle = false; + pPlayer->m_pPed->m_pMyVehicle = nil; + pPlayer->m_pPed->SetPedState(PED_IDLE); + pPlayer->m_pPed->bUsesCollision = true; + pPlayer->m_pPed->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pPlayer->m_pPed->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pPlayer->m_pPed->GetWeapon()->m_eWeaponType)->m_nModelId); + pPlayer->m_pPed->RemoveInCarAnims(); + if (pPlayer->m_pPed->m_pVehicleAnim) + pPlayer->m_pPed->m_pVehicleAnim->blendDelta = -1000.0f; + pPlayer->m_pPed->m_pVehicleAnim = nil; + pPlayer->m_pPed->SetMoveState(PEDMOVE_NONE); + CAnimManager::BlendAnimation(pPlayer->m_pPed->GetClump(), pPlayer->m_pPed->m_animGroup, ANIM_STD_IDLE, 100.0f); + pPlayer->m_pPed->RestartNonPartialAnims(); + AudioManager.PlayerJustLeftCar(); + pos.z += pPlayer->m_pPed->GetDistanceFromCentreOfMassToBaseOfModel(); + pPlayer->m_pPed->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, pPlayer->m_pPed); + return 0; + } + /* Not implemented. + case COMMAND_MAKE_CHAR_DO_NOTHING: + */ + default: + script_assert(0); + break; + } + return -1; +} + +#ifdef MISSION_REPLAY + +bool CRunningScript::CanAllowMissionReplay() +{ + if (AllowMissionReplay != MISSION_RETRY_STAGE_NORMAL) + return false; + if (CStats::LastMissionPassedName[0] == '\0') + return false; + for (int i = 0; i < ARRAY_SIZE(nonMissionScripts); i++) { + if (strcmp(m_abScriptName, nonMissionScripts[i]) == 0) + return false; + } + return true; +} + +uint32 AddExtraDeathDelay() +{ + if (missionRetryScriptIndex == 63) + return 7000; + if (missionRetryScriptIndex == 64) + return 4000; + return 1000; +} + +void RetryMission(int type, int unk) +{ + if (type == MISSION_RETRY_TYPE_SUGGEST_TO_PLAYER) { + doingMissionRetry = true; + FrontEndMenuManager.m_nCurrScreen = MENUPAGE_MISSION_RETRY; + FrontEndMenuManager.RequestFrontEndStartUp(); + } + else if (type == MISSION_RETRY_TYPE_BEGIN_RESTARTING) { + doingMissionRetry = false; + AllowMissionReplay = MISSION_RETRY_STAGE_START_RESTARTING; + CTheScripts::MissionCleanUp.Process(); + } +} + +#endif diff --git a/src/control/Script.h b/src/control/Script.h new file mode 100644 index 0000000..d14abe9 --- /dev/null +++ b/src/control/Script.h @@ -0,0 +1,630 @@ +#pragma once +#include "Font.h" +#include "PedType.h" +#include "Text.h" +#include "Sprite2d.h" + +class CEntity; +class CBuilding; +class CVehicle; +class CPed; +class CObject; +class CPlayerInfo; + +class CRunningScript; + +extern int32 ScriptParams[32]; + +void FlushLog(); +#define script_assert(_Expression) FlushLog(); assert(_Expression); + +#define PICKUP_PLACEMENT_OFFSET (0.5f) +#define PED_FIND_Z_OFFSET (5.0f) + +#define UPSIDEDOWN_UP_THRESHOLD (-0.97f) +#define UPSIDEDOWN_MOVE_SPEED_THRESHOLD (0.01f) +#define UPSIDEDOWN_TURN_SPEED_THRESHOLD (0.02f) +#define UPSIDEDOWN_TIMER_THRESHOLD (1000) + +#define SPHERE_MARKER_R (0) +#define SPHERE_MARKER_G (128) +#define SPHERE_MARKER_B (255) +#define SPHERE_MARKER_A (128) +#define SPHERE_MARKER_PULSE_PERIOD (2048) +#define SPHERE_MARKER_PULSE_FRACTION (0.1f) + +#ifdef USE_PRECISE_MEASUREMENT_CONVERTION +#define MILES_IN_METER (0.000621371192f) +#define METERS_IN_FOOT (0.3048f) +#define FEET_IN_METER (3.28084f) +#else +#define MILES_IN_METER (1 / 1670.f) +#define METERS_IN_FOOT (0.3f) +#define FEET_IN_METER (3.33f) +#endif + +#define KEY_LENGTH_IN_SCRIPT (8) + +#if GTA_VERSION <= GTA3_PS2_160 +#define GTA_SCRIPT_COLLECTIVE +#endif + +struct intro_script_rectangle +{ + bool m_bIsUsed; + bool m_bBeforeFade; + int16 m_nTextureId; + CRect m_sRect; + CRGBA m_sColor; + + intro_script_rectangle() { } + ~intro_script_rectangle() { } +}; + +VALIDATE_SIZE(intro_script_rectangle, 0x18); + +enum { + SCRIPT_TEXT_MAX_LENGTH = 500 +}; + +struct intro_text_line +{ + float m_fScaleX; + float m_fScaleY; + CRGBA m_sColor; + bool m_bJustify; + bool m_bCentered; + bool m_bBackground; + bool m_bBackgroundOnly; + float m_fWrapX; + float m_fCenterSize; + CRGBA m_sBackgroundColor; + bool m_bTextProportional; + bool m_bTextBeforeFade; + bool m_bRightJustify; + int32 m_nFont; + float m_fAtX; + float m_fAtY; + wchar m_Text[SCRIPT_TEXT_MAX_LENGTH]; + + intro_text_line() { } + ~intro_text_line() { } + + void Reset() + { + m_fScaleX = 0.48f; + m_fScaleY = 1.12f; + m_sColor = CRGBA(225, 225, 225, 255); + m_bJustify = false; + m_bRightJustify = false; + m_bCentered = false; + m_bBackground = false; + m_bBackgroundOnly = false; + m_fWrapX = 182.0f; + m_fCenterSize = DEFAULT_SCREEN_WIDTH; + m_sBackgroundColor = CRGBA(128, 128, 128, 128); + m_bTextProportional = true; + m_bTextBeforeFade = false; + m_nFont = FONT_HEADING; + m_fAtX = 0.0f; + m_fAtY = 0.0f; + memset(&m_Text, 0, sizeof(m_Text)); + } +}; + +VALIDATE_SIZE(intro_text_line, 0x414); + +struct script_sphere_struct +{ + bool m_bInUse; + uint16 m_Index; + uint32 m_Id; + CVector m_vecCenter; + float m_fRadius; + + script_sphere_struct() { } +}; + +struct CStoredLine +{ + CVector vecInf; + CVector vecSup; + uint32 color1; + uint32 color2; +}; + +enum { + CLEANUP_UNUSED = 0, + CLEANUP_CAR, + CLEANUP_CHAR, + CLEANUP_OBJECT +}; + +struct cleanup_entity_struct +{ + uint8 type; + int32 id; +}; + +enum { + MAX_CLEANUP = 50, + MAX_UPSIDEDOWN_CAR_CHECKS = 6, + MAX_STUCK_CAR_CHECKS = 6 +}; + +class CMissionCleanup +{ +public: + cleanup_entity_struct m_sEntities[MAX_CLEANUP]; + uint8 m_nCount; + + CMissionCleanup(); + + void Init(); + cleanup_entity_struct* FindFree(); + void AddEntityToList(int32, uint8); + void RemoveEntityFromList(int32, uint8); + void Process(); +}; + +struct upsidedown_car_data +{ + int32 m_nVehicleIndex; + uint32 m_nUpsideDownTimer; +}; + +class CUpsideDownCarCheck +{ + upsidedown_car_data m_sCars[MAX_UPSIDEDOWN_CAR_CHECKS]; + +public: + void Init(); + bool IsCarUpsideDown(int32); + bool IsCarUpsideDown(CVehicle*); + void UpdateTimers(); + bool AreAnyCarsUpsideDown(); + void AddCarToCheck(int32); + void RemoveCarFromCheck(int32); + bool HasCarBeenUpsideDownForAWhile(int32); +}; + +struct stuck_car_data +{ + int32 m_nVehicleIndex; + CVector m_vecPos; + int32 m_nLastCheck; + float m_fRadius; + uint32 m_nStuckTime; + bool m_bStuck; + + stuck_car_data() { } + void Reset(); +}; + +class CStuckCarCheck +{ + stuck_car_data m_sCars[MAX_STUCK_CAR_CHECKS]; + +public: + void Init(); + void Process(); + void AddCarToCheck(int32, float, uint32); + void RemoveCarFromCheck(int32); + bool HasCarBeenStuckForAWhile(int32); +}; + +enum { + ARGUMENT_END = 0, + ARGUMENT_INT32, + ARGUMENT_GLOBALVAR, + ARGUMENT_LOCALVAR, + ARGUMENT_INT8, + ARGUMENT_INT16, + ARGUMENT_FLOAT +}; + +struct tCollectiveData +{ + int32 colIndex; + int32 pedIndex; +}; + +enum { + USED_OBJECT_NAME_LENGTH = 24 +}; + +struct tUsedObject +{ + char name[USED_OBJECT_NAME_LENGTH]; + int32 index; +}; + +struct tBuildingSwap +{ + CBuilding* m_pBuilding; + int32 m_nNewModel; + int32 m_nOldModel; +}; + + +enum { +#if GTA_VERSION > GTA3_PS2_160 + MAX_STACK_DEPTH = 6, +#else + MAX_STACK_DEPTH = 4, +#endif + NUM_LOCAL_VARS = 16, + NUM_TIMERS = 2 +}; + +class CRunningScript +{ + enum { + ANDOR_NONE = 0, + ANDS_1 = 1, + ANDS_2, + ANDS_3, + ANDS_4, + ANDS_5, + ANDS_6, + ANDS_7, + ANDS_8, + ORS_1 = 21, + ORS_2, + ORS_3, + ORS_4, + ORS_5, + ORS_6, + ORS_7, + ORS_8 + }; + +public: + CRunningScript* next; + CRunningScript* prev; + char m_abScriptName[8]; + uint32 m_nIp; + uint32 m_anStack[MAX_STACK_DEPTH]; + uint16 m_nStackPointer; + int32 m_anLocalVariables[NUM_LOCAL_VARS + NUM_TIMERS]; + bool m_bCondResult; + bool m_bIsMissionScript; + bool m_bSkipWakeTime; + uint32 m_nWakeTime; + uint16 m_nAndOrState; + bool m_bNotFlag; + bool m_bDeatharrestEnabled; + bool m_bDeatharrestExecuted; + bool m_bMissionFlag; + +public: + void SetIP(uint32 ip) { m_nIp = ip; } + CRunningScript* GetNext() const { return next; } + + void Save(uint8*& buf); + void Load(uint8*& buf); + + void UpdateTimers(float timeStep) { + m_anLocalVariables[NUM_LOCAL_VARS] += timeStep; + m_anLocalVariables[NUM_LOCAL_VARS + 1] += timeStep; + } + + void Init(); + void Process(); + + void RemoveScriptFromList(CRunningScript**); + void AddScriptToList(CRunningScript**); + + static const uint32 nSaveStructSize; + + void CollectParameters(uint32*, int16); + int32 CollectNextParameterWithoutIncreasingPC(uint32); + int32* GetPointerToScriptVariable(uint32*, int16); + void StoreParameters(uint32*, int16); + + int8 ProcessOneCommand(); + void DoDeatharrestCheck(); + void UpdateCompareFlag(bool); + int16 GetPadState(uint16, uint16); + + int8 ProcessCommands0To99(int32); + int8 ProcessCommands100To199(int32); + int8 ProcessCommands200To299(int32); + int8 ProcessCommands300To399(int32); + int8 ProcessCommands400To499(int32); + int8 ProcessCommands500To599(int32); + int8 ProcessCommands600To699(int32); + int8 ProcessCommands700To799(int32); + int8 ProcessCommands800To899(int32); + int8 ProcessCommands900To999(int32); + int8 ProcessCommands1000To1099(int32); +#if GTA_VERSION > GTA3_PS2_160 + int8 ProcessCommands1100To1199(int32); +#endif + void LocatePlayerCommand(int32, uint32*); + void LocatePlayerCharCommand(int32, uint32*); + void LocatePlayerCarCommand(int32, uint32*); + void LocateCharCommand(int32, uint32*); + void LocateCharCharCommand(int32, uint32*); + void LocateCharCarCommand(int32, uint32*); +#if GTA_VERSION > GTA3_PS2_160 + void LocateCharObjectCommand(int32, uint32*); +#endif + void LocateCarCommand(int32, uint32*); +#if GTA_VERSION > GTA3_PS2_160 + void LocateSniperBulletCommand(int32, uint32*); +#endif + void PlayerInAreaCheckCommand(int32, uint32*); + void PlayerInAngledAreaCheckCommand(int32, uint32*); + void CharInAreaCheckCommand(int32, uint32*); + void CarInAreaCheckCommand(int32, uint32*); + +#ifdef GTA_SCRIPT_COLLECTIVE + void LocateCollectiveCommand(int32, uint32*); + void LocateCollectiveCharCommand(int32, uint32*); + void LocateCollectiveCarCommand(int32, uint32*); + void LocateCollectivePlayerCommand(int32, uint32*); + void CollectiveInAreaCheckCommand(int32, uint32*); +#endif + +#ifdef MISSION_REPLAY + bool CanAllowMissionReplay(); +#endif + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + int CollectParameterForDebug(char* buf, bool& var); + void GetStoredParameterForDebug(char* buf); + void LogOnStartProcessing(); + void LogBeforeProcessingCommand(int32 command); + void LogAfterProcessingCommand(int32 command); + + static char commandInfo[]; + static uint32 storedIp; + +#endif + + float LimitAngleOnCircle(float angle) { return angle < 0.0f ? angle + 360.0f : angle; } + + bool ThisIsAValidRandomPed(uint32 pedtype) { + switch (pedtype) { + case PEDTYPE_CIVMALE: + case PEDTYPE_CIVFEMALE: + case PEDTYPE_GANG1: + case PEDTYPE_GANG2: + case PEDTYPE_GANG3: + case PEDTYPE_GANG4: + case PEDTYPE_GANG5: + case PEDTYPE_GANG6: + case PEDTYPE_GANG7: + case PEDTYPE_GANG8: + case PEDTYPE_GANG9: + case PEDTYPE_CRIMINAL: + case PEDTYPE_PROSTITUTE: + return true; + default: + return false; + } + } +}; + + +enum { + VAR_LOCAL = 1, + VAR_GLOBAL = 2, +}; + +enum { + SIZE_MAIN_SCRIPT = 128 * 1024, + SIZE_MISSION_SCRIPT = 32 * 1024, + SIZE_SCRIPT_SPACE = SIZE_MAIN_SCRIPT + SIZE_MISSION_SCRIPT +}; + +enum { + MAX_NUM_SCRIPTS = 128, + MAX_NUM_CONTACTS = 16, + MAX_NUM_INTRO_TEXT_LINES = 2, + MAX_NUM_INTRO_RECTANGLES = 16, + MAX_NUM_SCRIPT_SRPITES = 16, + MAX_NUM_SCRIPT_SPHERES = 16, + MAX_NUM_COLLECTIVES = 32, + MAX_NUM_USED_OBJECTS = 200, + MAX_NUM_MISSION_SCRIPTS = 120, + MAX_NUM_BUILDING_SWAPS = 25, + MAX_NUM_INVISIBILITY_SETTINGS = 20, + MAX_NUM_STORED_LINES = 1024 +}; + +class CTheScripts +{ +public: + static uint8 ScriptSpace[SIZE_SCRIPT_SPACE]; + static CRunningScript ScriptsArray[MAX_NUM_SCRIPTS]; + static int32 BaseBriefIdForContact[MAX_NUM_CONTACTS]; + static int32 OnAMissionForContactFlag[MAX_NUM_CONTACTS]; + static intro_text_line IntroTextLines[MAX_NUM_INTRO_TEXT_LINES]; + static intro_script_rectangle IntroRectangles[MAX_NUM_INTRO_RECTANGLES]; + static CSprite2d ScriptSprites[MAX_NUM_SCRIPT_SRPITES]; + static script_sphere_struct ScriptSphereArray[MAX_NUM_SCRIPT_SPHERES]; + static tCollectiveData CollectiveArray[MAX_NUM_COLLECTIVES]; + static tUsedObject UsedObjectArray[MAX_NUM_USED_OBJECTS]; + static int32 MultiScriptArray[MAX_NUM_MISSION_SCRIPTS]; + static tBuildingSwap BuildingSwapArray[MAX_NUM_BUILDING_SWAPS]; + static CEntity* InvisibilitySettingArray[MAX_NUM_INVISIBILITY_SETTINGS]; + static CStoredLine aStoredLines[MAX_NUM_STORED_LINES]; + static bool DbgFlag; + static uint32 OnAMissionFlag; + static CMissionCleanup MissionCleanUp; + static CStuckCarCheck StuckCars; + static CUpsideDownCarCheck UpsideDownCars; + static int32 StoreVehicleIndex; + static bool StoreVehicleWasRandom; + static CRunningScript *pIdleScripts; + static CRunningScript *pActiveScripts; + static int32 NextFreeCollectiveIndex; + static int32 LastRandomPedId; + static uint16 NumberOfUsedObjects; + static bool bAlreadyRunningAMissionScript; + static bool bUsingAMultiScriptFile; + static uint16 NumberOfMissionScripts; + static uint32 LargestMissionScriptSize; + static uint32 MainScriptSize; + static uint8 FailCurrentMission; + static uint8 CountdownToMakePlayerUnsafe; + static uint8 DelayMakingPlayerUnsafeThisTime; + static uint16 NumScriptDebugLines; + static uint16 NumberOfIntroRectanglesThisFrame; + static uint16 NumberOfIntroTextLinesThisFrame; + static uint8 UseTextCommands; + static uint16 CommandsExecuted; + static uint16 ScriptsUpdated; + + static void Init(); + static void Process(); + + static CRunningScript* StartTestScript(); + static bool IsPlayerOnAMission(); + static void ClearSpaceForMissionEntity(const CVector&, CEntity*); + + static void UndoBuildingSwaps(); + static void UndoEntityInvisibilitySettings(); + + static void ScriptDebugLine3D(float x1, float y1, float z1, float x2, float y2, float z2, uint32 col, uint32 col2); + static void RenderTheScriptDebugLines(); + + static void SaveAllScripts(uint8*, uint32*); + static void LoadAllScripts(uint8*, uint32); + + static bool IsDebugOn() { return DbgFlag; }; + static void InvertDebugFlag() { DbgFlag = !DbgFlag; } + + static int32* GetPointerToScriptVariable(int32 offset) { assert(offset >= 8 && offset < CTheScripts::GetSizeOfVariableSpace()); return (int32*)&ScriptSpace[offset]; } + + static void ResetCountdownToMakePlayerUnsafe() { CountdownToMakePlayerUnsafe = 0; } + static bool IsCountdownToMakePlayerUnsafeOn() { return CountdownToMakePlayerUnsafe != 0; } + + static int32 Read4BytesFromScript(uint32* pIp) { + int32 retval = ScriptSpace[*pIp + 3] << 24 | ScriptSpace[*pIp + 2] << 16 | ScriptSpace[*pIp + 1] << 8 | ScriptSpace[*pIp]; + *pIp += 4; + return retval; + } + static int16 Read2BytesFromScript(uint32* pIp) { + int16 retval = ScriptSpace[*pIp + 1] << 8 | ScriptSpace[*pIp]; + *pIp += 2; + return retval; + } + static int8 Read1ByteFromScript(uint32* pIp) { + int8 retval = ScriptSpace[*pIp]; + *pIp += 1; + return retval; + } + static float ReadFloatFromScript(uint32* pIp) { + return Read2BytesFromScript(pIp) / 16.0f; + } + static void ReadTextLabelFromScript(uint32* pIp, char* buf) { + strncpy(buf, (const char*)&CTheScripts::ScriptSpace[*pIp], KEY_LENGTH_IN_SCRIPT); + } + static wchar* GetTextByKeyFromScript(uint32* pIp) { + wchar* text = TheText.Get((const char*)&CTheScripts::ScriptSpace[*pIp]); + *pIp += KEY_LENGTH_IN_SCRIPT; + return text; + } + static int32 GetSizeOfVariableSpace() + { + uint32 tmp = 3; + return Read4BytesFromScript(&tmp); + } + + static CRunningScript* StartNewScript(uint32); + + static void CleanUpThisVehicle(CVehicle*); + static void CleanUpThisPed(CPed*); + static void CleanUpThisObject(CObject*); + + static bool IsPedStopped(CPed*); + static bool IsPlayerStopped(CPlayerInfo*); + static bool IsVehicleStopped(CVehicle*); + + static void PrintListSizes(); + static void ReadObjectNamesFromScript(); + static void UpdateObjectIndices(); + static void ReadMultiScriptFileOffsetsFromScript(); + static void DrawScriptSpheres(); + static void HighlightImportantArea(uint32, float, float, float, float, float); + static void HighlightImportantAngledArea(uint32, float, float, float, float, float, float, float, float, float); + static void DrawDebugSquare(float, float, float, float); + static void DrawDebugAngledSquare(float, float, float, float, float, float, float, float); + static void DrawDebugCube(float, float, float, float, float, float); + static void DrawDebugAngledCube(float, float, float, float, float, float, float, float, float, float); + + static void AddToInvisibilitySwapArray(CEntity*, bool); + static void AddToBuildingSwapArray(CBuilding*, int32, int32); + + static int32 GetActualScriptSphereIndex(int32 index); + static int32 AddScriptSphere(int32 id, CVector pos, float radius); + static int32 GetNewUniqueScriptSphereIndex(int32 index); + static void RemoveScriptSphere(int32 index); + +#ifdef GTA_SCRIPT_COLLECTIVE + static void AdvanceCollectiveIndex() + { + if (NextFreeCollectiveIndex == INT32_MAX) + NextFreeCollectiveIndex = 0; + else + NextFreeCollectiveIndex++; + } + + static int AddPedsInVehicleToCollective(int); + static int AddPedsInAreaToCollective(float, float, float, float); + static int FindFreeSlotInCollectiveArray(); + static void SetObjectiveForAllPedsInCollective(int, eObjective, int16, int16); + static void SetObjectiveForAllPedsInCollective(int, eObjective, CVector, float); + static void SetObjectiveForAllPedsInCollective(int, eObjective, CVector); + static void SetObjectiveForAllPedsInCollective(int, eObjective, void*); + static void SetObjectiveForAllPedsInCollective(int, eObjective); +#endif + +#ifdef MISSION_SWITCHER +public: + static void SwitchToMission(int32 mission); +#endif + +#ifdef USE_DEBUG_SCRIPT_LOADER + static int ScriptToLoad; + static int OpenScript(); +#endif + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + static void LogAfterScriptInitializing(); + static void LogBeforeScriptProcessing(); + static void LogAfterScriptProcessing(); +#endif +}; + +#ifdef MISSION_REPLAY +extern int AllowMissionReplay; +extern uint32 WaitForMissionActivate; +extern uint32 WaitForSave; +extern uint32 MissionStartTime; +extern int missionRetryScriptIndex; +extern bool doingMissionRetry; + +uint32 AddExtraDeathDelay(); +void RetryMission(int, int unk = 0); + +enum { + MISSION_RETRY_TYPE_SUGGEST_TO_PLAYER = 0, + MISSION_RETRY_TYPE_1, + MISSION_RETRY_TYPE_BEGIN_RESTARTING +}; + +enum { + MISSION_RETRY_STAGE_NORMAL = 0, + MISSION_RETRY_STAGE_WAIT_FOR_SCRIPT_TO_TERMINATE, + MISSION_RETRY_STAGE_START_PROCESSING, + MISSION_RETRY_STAGE_WAIT_FOR_DELAY, + MISSION_RETRY_STAGE_WAIT_FOR_MENU, + MISSION_RETRY_STAGE_WAIT_FOR_USER, + MISSION_RETRY_STAGE_START_RESTARTING, + MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART, +}; +#endif diff --git a/src/control/Script2.cpp b/src/control/Script2.cpp new file mode 100644 index 0000000..d3ab2af --- /dev/null +++ b/src/control/Script2.cpp @@ -0,0 +1,1555 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "Camera.h" +#include "CarCtrl.h" +#include "CarGen.h" +#include "CivilianPed.h" +#include "CopPed.h" +#include "Cranes.h" +#include "DMAudio.h" +#include "EmergencyPed.h" +#include "Garages.h" +#include "General.h" +#include "Messages.h" +#include "Pad.h" +#include "PedRoutes.h" +#include "Pools.h" +#include "Population.h" +#include "Radar.h" +#include "Restart.h" +#include "Shadows.h" +#include "User.h" +#include "Wanted.h" +#include "WaterLevel.h" +#include "Weather.h" +#include "World.h" +#include "Zones.h" + +int8 CRunningScript::ProcessCommands300To399(int32 command) +{ + switch (command) { + /* Not implemented. + case COMMAND_SET_CHAR_INVINCIBLE: + case COMMAND_SET_PLAYER_INVINCIBLE: + case COMMAND_SET_CHAR_GRAPHIC_TYPE: + case COMMAND_SET_PLAYER_GRAPHIC_TYPE: + */ + case COMMAND_HAS_PLAYER_BEEN_ARRESTED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_WBState == WBSTATE_BUSTED); + return 0; + /* Not implemented. + case COMMAND_STOP_CHAR_DRIVING: + case COMMAND_KILL_CHAR: + case COMMAND_SET_FAVOURITE_CAR_MODEL_FOR_CHAR: + case COMMAND_SET_CHAR_OCCUPATION: + */ + case COMMAND_CHANGE_CAR_LOCK: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->m_nDoorLock = (eCarLock)ScriptParams[1]; + return 0; + } + case COMMAND_SHAKE_CAM_WITH_POINT: + CollectParameters(&m_nIp, 4); + TheCamera.CamShake(ScriptParams[0] / 1000.0f, + *(float*)&ScriptParams[1], + *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3]); + return 0; + case COMMAND_IS_CAR_MODEL: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->GetModelIndex() == ScriptParams[1]); + return 0; + } + /* Not implemented. + case COMMAND_IS_CAR_REMAP: + case COMMAND_HAS_CAR_JUST_SUNK: + case COMMAND_SET_CAR_NO_COLLIDE: + */ + case COMMAND_IS_CAR_DEAD_IN_AREA_2D: + { + CollectParameters(&m_nIp, 6); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + UpdateCompareFlag(pVehicle->GetStatus() == STATUS_WRECKED && + pVehicle->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CAR_DEAD_IN_AREA_3D: + { + CollectParameters(&m_nIp, 8); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float z1 = *(float*)&ScriptParams[3]; + float x2 = *(float*)&ScriptParams[4]; + float y2 = *(float*)&ScriptParams[5]; + float z2 = *(float*)&ScriptParams[6]; + UpdateCompareFlag(pVehicle->GetStatus() == STATUS_WRECKED && + pVehicle->IsWithinArea(x1, y1, z1, x2, y2, z2)); + if (ScriptParams[7]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, (z1 + z2) / 2); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(x1, y1, z1, x2, y2, z2); + return 0; + } + /* Not implemented. + case COMMAND_IS_TRAILER_ATTACHED: + case COMMAND_IS_CAR_ON_TRAILER: + case COMMAND_HAS_CAR_GOT_WEAPON: + case COMMAND_PARK: + case COMMAND_HAS_PARK_FINISHED: + case COMMAND_KILL_ALL_PASSENGERS: + case COMMAND_SET_CAR_BULLETPROOF: + case COMMAND_SET_CAR_FLAMEPROOF: + case COMMAND_SET_CAR_ROCKETPROOF: + case COMMAND_IS_CARBOMB_ACTIVE: + case COMMAND_GIVE_CAR_ALARM: + case COMMAND_PUT_CAR_ON_TRAILER: + */ + case COMMAND_IS_CAR_CRUSHED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::HasCarBeenCrushed(ScriptParams[0])); + return 0; + /* Not implemented. + case COMMAND_CREATE_GANG_CAR: + */ + case COMMAND_CREATE_CAR_GENERATOR: + CollectParameters(&m_nIp, 12); + ScriptParams[0] = CTheCarGenerators::CreateCarGenerator( + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], *(float*)&ScriptParams[3], + ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7], + ScriptParams[8], ScriptParams[9], ScriptParams[10], ScriptParams[11]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_SWITCH_CAR_GENERATOR: + { + CollectParameters(&m_nIp, 2); + CCarGenerator* pCarGen = &CTheCarGenerators::CarGeneratorArray[ScriptParams[0]]; + if (ScriptParams[1] == 0){ + pCarGen->SwitchOff(); + }else if (ScriptParams[1] <= 100){ + pCarGen->SwitchOn(); + pCarGen->SetUsesRemaining(ScriptParams[1]); + }else{ + pCarGen->SwitchOn(); + } + return 0; + } + case COMMAND_ADD_PAGER_MESSAGE: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CUserDisplay::Pager.AddMessage(text, ScriptParams[0], ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_DISPLAY_ONSCREEN_TIMER: + { + script_assert(CTheScripts::ScriptSpace[m_nIp] == ARGUMENT_GLOBALVAR); + m_nIp++; + CUserDisplay::OnscnTimer.AddClock((uint16)CTheScripts::Read2BytesFromScript(&m_nIp), nil); + return 0; + } + case COMMAND_CLEAR_ONSCREEN_TIMER: + { + script_assert(CTheScripts::ScriptSpace[m_nIp] == ARGUMENT_GLOBALVAR); + m_nIp++; + CUserDisplay::OnscnTimer.ClearClock((uint16)CTheScripts::Read2BytesFromScript(&m_nIp)); + return 0; + } + case COMMAND_DISPLAY_ONSCREEN_COUNTER: + { + script_assert(CTheScripts::ScriptSpace[m_nIp] == ARGUMENT_GLOBALVAR); + m_nIp++; + uint16 counter = CTheScripts::Read2BytesFromScript(&m_nIp); + CollectParameters(&m_nIp, 1); + CUserDisplay::OnscnTimer.AddCounter(counter, ScriptParams[0], nil); + return 0; + } + case COMMAND_CLEAR_ONSCREEN_COUNTER: + { + script_assert(CTheScripts::ScriptSpace[m_nIp] == ARGUMENT_GLOBALVAR); + m_nIp++; + CUserDisplay::OnscnTimer.ClearCounter((uint16)CTheScripts::Read2BytesFromScript(&m_nIp)); + return 0; + } + case COMMAND_SET_ZONE_CAR_INFO: + { + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 16); + int zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + CTheZones::SetZoneCarInfo(zone, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], + ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7], ScriptParams[8], 0, 0, + ScriptParams[9], ScriptParams[10], ScriptParams[11], ScriptParams[12], + ScriptParams[13], ScriptParams[14], ScriptParams[15]); + return 0; + } + /* Not implemented. + case COMMAND_IS_CHAR_IN_GANG_ZONE: + */ + case COMMAND_IS_CHAR_IN_ZONE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + int zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + if (zone != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + UpdateCompareFlag(CTheZones::PointLiesWithinZone(&pos, CTheZones::GetZone(zone))); + return 0; + } + case COMMAND_SET_CAR_DENSITY: + { + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + int16 zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + m_nIp += 8; + CollectParameters(&m_nIp, 2); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + CTheZones::SetCarDensity(zone, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_SET_PED_DENSITY: + { + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + int16 zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + CTheZones::SetPedDensity(zone, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_POINT_CAMERA_AT_PLAYER: + { + CollectParameters(&m_nIp, 3); + // ScriptParams[0] is unused. + TheCamera.TakeControl(nil, ScriptParams[1], ScriptParams[2], CAMCONTROL_SCRIPT); + return 0; + } + case COMMAND_POINT_CAMERA_AT_CAR: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + TheCamera.TakeControl(pVehicle, ScriptParams[1], ScriptParams[2], CAMCONTROL_SCRIPT); + return 0; + } + case COMMAND_POINT_CAMERA_AT_CHAR: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + TheCamera.TakeControl(pPed, ScriptParams[1], ScriptParams[2], CAMCONTROL_SCRIPT); + return 0; + } + case COMMAND_RESTORE_CAMERA: + TheCamera.Restore(); + return 0; + case COMMAND_SHAKE_PAD: + CPad::GetPad(ScriptParams[0])->StartShake(ScriptParams[1], ScriptParams[2]); + return 0; + case COMMAND_SET_ZONE_PED_INFO: + { + char label[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 10); + int16 zone = CTheZones::FindZoneByLabelAndReturnIndex(label); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + CTheZones::SetZonePedInfo(zone, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], + ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7], ScriptParams[8], 0, 0, ScriptParams[9]); + return 0; + } + case COMMAND_SET_TIME_SCALE: + CollectParameters(&m_nIp, 1); + CTimer::SetTimeScale(*(float*)&ScriptParams[0]); + return 0; + case COMMAND_IS_CAR_IN_AIR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle && pVehicle->IsCar()); + CAutomobile* pCar = (CAutomobile*)pVehicle; + UpdateCompareFlag(pCar->GetAllWheelsOffGround()); + return 0; + } + case COMMAND_SET_FIXED_CAMERA_POSITION: + { + CollectParameters(&m_nIp, 6); + TheCamera.SetCamPositionForFixedMode( + CVector(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2]), + CVector(*(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5])); + return 0; + } + case COMMAND_POINT_CAMERA_AT_POINT: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + TheCamera.TakeControlNoEntity(pos, ScriptParams[3], CAMCONTROL_SCRIPT); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CAR_OLD: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetEntityBlip(BLIP_CAR, ScriptParams[0], ScriptParams[1], (eBlipDisplay)ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CHAR_OLD: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetEntityBlip(BLIP_CHAR, ScriptParams[0], ScriptParams[1], (eBlipDisplay)ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_OBJECT_OLD: + { + CollectParameters(&m_nIp, 3); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetEntityBlip(BLIP_OBJECT, ScriptParams[0], ScriptParams[1], (eBlipDisplay)ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_REMOVE_BLIP: + CollectParameters(&m_nIp, 1); + CRadar::ClearBlip(ScriptParams[0]); + return 0; + case COMMAND_CHANGE_BLIP_COLOUR: + CollectParameters(&m_nIp, 2); + CRadar::ChangeBlipColour(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_DIM_BLIP: + CollectParameters(&m_nIp, 2); + CRadar::ChangeBlipBrightness(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_ADD_BLIP_FOR_COORD_OLD: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + // Useless call + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetCoordBlip(BLIP_COORD, pos, ScriptParams[3], (eBlipDisplay)ScriptParams[4]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CHANGE_BLIP_SCALE: + CollectParameters(&m_nIp, 2); + CRadar::ChangeBlipScale(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_SET_FADING_COLOUR: + CollectParameters(&m_nIp, 3); + TheCamera.SetFadeColour(ScriptParams[0], ScriptParams[1], ScriptParams[2]); + return 0; + case COMMAND_DO_FADE: + CollectParameters(&m_nIp, 2); + TheCamera.Fade(ScriptParams[0] / 1000.0f, ScriptParams[1]); + return 0; + case COMMAND_GET_FADING_STATUS: + UpdateCompareFlag(TheCamera.GetFading()); + return 0; + case COMMAND_ADD_HOSPITAL_RESTART: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + float angle = *(float*)&ScriptParams[3]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRestart::AddHospitalRestartPoint(pos, angle); + return 0; + } + case COMMAND_ADD_POLICE_RESTART: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + float angle = *(float*)&ScriptParams[3]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRestart::AddPoliceRestartPoint(pos, angle); + return 0; + } + case COMMAND_OVERRIDE_NEXT_RESTART: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + float angle = *(float*)&ScriptParams[3]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRestart::OverrideNextRestart(pos, angle); + return 0; + } + case COMMAND_DRAW_SHADOW: + { + CollectParameters(&m_nIp, 10); + CVector pos = *(CVector*)&ScriptParams[1]; + float angle = *(float*)&ScriptParams[4]; + float length = *(float*)&ScriptParams[5]; + float x, y; + if (angle != 0.0f){ + y = Cos(angle) * length; + x = Sin(angle) * length; + }else{ + y = length; + x = 0.0f; + } + float frontX = -x; + float frontY = y; + float sideX = y; + float sideY = x; + /* Not very nicely named intermediate variables. */ + CShadows::StoreShadowToBeRendered(ScriptParams[0], &pos, frontX, frontY, sideX, sideY, + ScriptParams[6], ScriptParams[7], ScriptParams[8], ScriptParams[9]); + return 0; + } + case COMMAND_GET_PLAYER_HEADING: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + float angle = pPed->bInVehicle ? pPed->m_pMyVehicle->GetForward().Heading() : pPed->GetForward().Heading(); + *(float*)&ScriptParams[0] = CGeneral::LimitAngle(RADTODEG(angle)); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_PLAYER_HEADING: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + if (pPed->bInVehicle){ + // Is script_assertion required? + return 0; + } + pPed->m_fRotationDest = pPed->m_fRotationCur = DEGTORAD(*(float*)&ScriptParams[1]); + pPed->SetHeading(DEGTORAD(*(float*)&ScriptParams[1])); + return 0; + } + case COMMAND_GET_CHAR_HEADING: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float angle = pPed->bInVehicle ? pPed->m_pMyVehicle->GetForward().Heading() : pPed->GetForward().Heading(); + *(float*)&ScriptParams[0] = CGeneral::LimitAngle(RADTODEG(angle)); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_HEADING: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (pPed->bInVehicle) { + // Is script_assertion required? + return 0; + } + pPed->m_fRotationDest = pPed->m_fRotationCur = DEGTORAD(*(float*)&ScriptParams[1]); + pPed->SetHeading(DEGTORAD(*(float*)&ScriptParams[1])); + return 0; + } + case COMMAND_GET_CAR_HEADING: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + float angle = pVehicle->GetForward().Heading(); + *(float*)&ScriptParams[0] = CGeneral::LimitAngle(RADTODEG(angle)); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CAR_HEADING: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->SetHeading(DEGTORAD(*(float*)&ScriptParams[1])); + return 0; + } + case COMMAND_GET_OBJECT_HEADING: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + float angle = pObject->GetForward().Heading(); + *(float*)&ScriptParams[0] = CGeneral::LimitAngle(RADTODEG(angle)); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_OBJECT_HEADING: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CWorld::Remove(pObject); + pObject->SetHeading(DEGTORAD(*(float*)&ScriptParams[1])); + pObject->GetMatrix().UpdateRW(); + pObject->UpdateRwFrame(); + CWorld::Add(pObject); + return 0; + } + case COMMAND_IS_PLAYER_TOUCHING_OBJECT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pObject); + CPhysical* pEntityToTest = pPed->bInVehicle ? (CPhysical*)pPed->m_pMyVehicle : pPed; + UpdateCompareFlag(pEntityToTest->GetHasCollidedWith(pObject)); + return 0; + } + case COMMAND_IS_CHAR_TOUCHING_OBJECT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pObject); + CPhysical* pEntityToTest = pPed->bInVehicle ? (CPhysical*)pPed->m_pMyVehicle : pPed; + UpdateCompareFlag(pEntityToTest->GetHasCollidedWith(pObject)); + return 0; + } + case COMMAND_SET_PLAYER_AMMO: + { + CollectParameters(&m_nIp, 3); + CWorld::Players[0].m_pPed->SetAmmo((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_SET_CHAR_AMMO: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + pPed->SetAmmo((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + /* Not implemented. + case COMMAND_SET_CAR_AMMO: + case COMMAND_LOAD_CAMERA_SPLINE: + case COMMAND_MOVE_CAMERA_ALONG_SPLINE: + case COMMAND_GET_CAMERA_POSITION_ALONG_SPLINE: + */ + case COMMAND_DECLARE_MISSION_FLAG: + CTheScripts::OnAMissionFlag = (uint16)CTheScripts::Read2BytesFromScript(&++m_nIp); + return 0; + case COMMAND_DECLARE_MISSION_FLAG_FOR_CONTACT: + CollectParameters(&m_nIp, 1); + CTheScripts::OnAMissionForContactFlag[ScriptParams[0]] = (uint16)CTheScripts::Read2BytesFromScript(&++m_nIp); + return 0; + case COMMAND_DECLARE_BASE_BRIEF_ID_FOR_CONTACT: + CollectParameters(&m_nIp, 2); + CTheScripts::BaseBriefIdForContact[ScriptParams[0]] = ScriptParams[1]; + return 0; + case COMMAND_IS_PLAYER_HEALTH_GREATER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + UpdateCompareFlag(pPed->m_fHealth > ScriptParams[1]); + return 0; + } + case COMMAND_IS_CHAR_HEALTH_GREATER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->m_fHealth > ScriptParams[1]); + return 0; + } + case COMMAND_IS_CAR_HEALTH_GREATER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->m_fHealth > ScriptParams[1]); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_CAR, ScriptParams[0], 0, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_CHAR, ScriptParams[0], 1, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_OBJECT: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + // Useless call. + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_OBJECT, ScriptParams[0], 6, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_CONTACT_POINT: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + // Useless call + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetCoordBlip(BLIP_CONTACT_POINT, pos, 2, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_COORD: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + // Useless call + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetCoordBlip(BLIP_COORD, pos, 5, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CHANGE_BLIP_DISPLAY: + CollectParameters(&m_nIp, 2); + CRadar::ChangeBlipDisplay(ScriptParams[0], (eBlipDisplay)ScriptParams[1]); + return 0; + case COMMAND_ADD_ONE_OFF_SOUND: + { + CollectParameters(&m_nIp, 4); + switch (ScriptParams[3]) { + case SCRIPT_SOUND_EVIDENCE_PICKUP: + DMAudio.PlayFrontEndSound(SOUND_EVIDENCE_PICKUP, 0); + return 0; + case SCRIPT_SOUND_UNLOAD_GOLD: + DMAudio.PlayFrontEndSound(SOUND_UNLOAD_GOLD, 0); + return 0; + case SCRIPT_SOUND_PART_MISSION_COMPLETE: + DMAudio.PlayFrontEndSound(SOUND_PART_MISSION_COMPLETE, 0); + return 0; + case SCRIPT_SOUND_RACE_START_3: + DMAudio.PlayFrontEndSound(SOUND_RACE_START_3, 0); + return 0; + case SCRIPT_SOUND_RACE_START_2: + DMAudio.PlayFrontEndSound(SOUND_RACE_START_2, 0); + return 0; + case SCRIPT_SOUND_RACE_START_1: + DMAudio.PlayFrontEndSound(SOUND_RACE_START_1, 0); + return 0; + case SCRIPT_SOUND_RACE_START_GO: + DMAudio.PlayFrontEndSound(SOUND_RACE_START_GO, 0); + return 0; + default: + break; + } +#ifdef FIX_BUGS + /* BUG: if audio is not initialized, this object will not be freed. */ + if (!DMAudio.IsAudioInitialised()) + return 0; +#endif + cAudioScriptObject* obj = new cAudioScriptObject(); + obj->Posn = *(CVector*)&ScriptParams[0]; + obj->AudioId = ScriptParams[3]; + obj->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(obj); + return 0; + } + case COMMAND_ADD_CONTINUOUS_SOUND: + { + CollectParameters(&m_nIp, 4); + cAudioScriptObject* obj = new cAudioScriptObject(); + obj->Posn = *(CVector*)&ScriptParams[0]; + obj->AudioId = ScriptParams[3]; + obj->AudioEntity = DMAudio.CreateLoopingScriptObject(obj); + ScriptParams[0] = CPools::GetAudioScriptObjectPool()->GetIndex(obj); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_REMOVE_SOUND: + { + CollectParameters(&m_nIp, 1); + cAudioScriptObject* obj = CPools::GetAudioScriptObjectPool()->GetAt(ScriptParams[0]); + if (!obj){ + debug("REMOVE_SOUND - Sound doesn't exist\n"); + return 0; + } + DMAudio.DestroyLoopingScriptObject(obj->AudioEntity); + delete obj; + return 0; + } + case COMMAND_IS_CAR_STUCK_ON_ROOF: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(CTheScripts::UpsideDownCars.HasCarBeenUpsideDownForAWhile(ScriptParams[0])); + return 0; + } + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands400To499(int32 command) +{ + switch (command) { + case COMMAND_ADD_UPSIDEDOWN_CAR_CHECK: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CTheScripts::UpsideDownCars.AddCarToCheck(ScriptParams[0]); + return 0; + } + case COMMAND_REMOVE_UPSIDEDOWN_CAR_CHECK: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CTheScripts::UpsideDownCars.RemoveCarFromCheck(ScriptParams[0]); + return 0; + } + case COMMAND_SET_CHAR_OBJ_WAIT_ON_FOOT: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_WAIT_ON_FOOT); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GUARD_SPOT: + { + CollectParameters(&m_nIp, 4); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GUARD_SPOT, pos); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GUARD_AREA: + { + CollectParameters(&m_nIp, 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + if (infX > supX){ + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GUARD_SPOT, pos, radius); + return 0; + } + case COMMAND_SET_CHAR_OBJ_WAIT_IN_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_WAIT_IN_CAR); + return 0; + } + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D: + PlayerInAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D: + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D: + CharInAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_IS_CAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_3D: + CarInAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CAR_2D: + case COMMAND_LOCATE_STOPPED_CAR_2D: + case COMMAND_LOCATE_CAR_3D: + case COMMAND_LOCATE_STOPPED_CAR_3D: + LocateCarCommand(command, &m_nIp); + return 0; + case COMMAND_GIVE_WEAPON_TO_PLAYER: + { + CollectParameters(&m_nIp, 3); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->m_nSelectedWepSlot = pPed->GiveWeapon((eWeaponType)ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_GIVE_WEAPON_TO_CHAR: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->SetCurrentWeapon(pPed->GiveWeapon((eWeaponType)ScriptParams[1], ScriptParams[2])); + if (pPed->bInVehicle) + pPed->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType)->m_nModelId); + return 0; + } + /* Not implemented */ + //case COMMAND_GIVE_WEAPON_TO_CAR: + case COMMAND_SET_PLAYER_CONTROL: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + if (ScriptParams[1]){ + if (CGame::playingIntro || CTheScripts::DelayMakingPlayerUnsafeThisTime){ + CTheScripts::CountdownToMakePlayerUnsafe = 50; + if (CTheScripts::DelayMakingPlayerUnsafeThisTime) + CTheScripts::DelayMakingPlayerUnsafeThisTime--; + }else{ + pPlayer->MakePlayerSafe(false); + } + }else{ + pPlayer->MakePlayerSafe(true); + if (strcmp(m_abScriptName, "camera") == 0){ + pPlayer->m_pPed->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pPlayer->m_pPed->SetTurnSpeed(0.0f, 0.0f, 0.0f); + CAnimManager::BlendAnimation((RpClump*)pPlayer->m_pPed->m_rwObject, pPlayer->m_pPed->m_animGroup, ANIM_STD_IDLE, 1000.0f); + } + } + return 0; + } + case COMMAND_FORCE_WEATHER: + CollectParameters(&m_nIp, 1); + CWeather::ForceWeather(ScriptParams[0]); + return 0; + case COMMAND_FORCE_WEATHER_NOW: + CollectParameters(&m_nIp, 1); + CWeather::ForceWeatherNow(ScriptParams[0]); + return 0; + case COMMAND_RELEASE_WEATHER: + CWeather::ReleaseWeather(); + return 0; + case COMMAND_SET_CURRENT_PLAYER_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + for (int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++){ + if (pPed->m_weapons[i].m_eWeaponType == ScriptParams[1]) + pPed->m_nSelectedWepSlot = i; + } + return 0; + } + case COMMAND_SET_CURRENT_CHAR_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + for (int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) { + if (pPed->m_weapons[i].m_eWeaponType == ScriptParams[1]) + pPed->SetCurrentWeapon(i); + } + return 0; + } + /* Not implemented */ + //case COMMAND_SET_CURRENT_CAR_WEAPON: + case COMMAND_GET_OBJECT_COORDINATES: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + *(CVector*)&ScriptParams[0] = pObject->GetPosition(); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_SET_OBJECT_COORDINATES: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pObject->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, pObject); + return 0; + } + case COMMAND_GET_GAME_TIMER: + ScriptParams[0] = CTimer::GetTimeInMilliseconds(); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_TURN_CHAR_TO_FACE_COORD: + { + CollectParameters(&m_nIp, 4); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle; + CVector pos; + if (pPed->bInVehicle) + pVehicle = pPed->m_pMyVehicle; + else + pVehicle = nil; + if (pVehicle) + pos = pVehicle->GetPosition(); + else + pos = pPed->GetPosition(); + float heading = CGeneral::GetATanOfXY(pos.x - *(float*)&ScriptParams[1], pos.y - *(float*)&ScriptParams[2]); + heading += HALFPI; + if (heading > TWOPI) + heading -= TWOPI; + if (!pVehicle){ + pPed->m_fRotationCur = heading; + pPed->m_fRotationDest = heading; + pPed->SetHeading(heading); + } + return 0; + } + case COMMAND_TURN_PLAYER_TO_FACE_COORD: + { + CollectParameters(&m_nIp, 4); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CVehicle* pVehicle; + CVector pos; + if (pPed->bInVehicle) + pVehicle = pPed->m_pMyVehicle; + else + pVehicle = nil; + if (pVehicle) + pos = pVehicle->GetPosition(); + else + pos = pPed->GetPosition(); + float heading = CGeneral::GetATanOfXY(pos.x - *(float*)&ScriptParams[1], pos.y - *(float*)&ScriptParams[2]); + heading += HALFPI; + if (heading > TWOPI) + heading -= TWOPI; + if (!pVehicle) { + pPed->m_fRotationCur = heading; + pPed->m_fRotationDest = heading; + pPed->SetHeading(heading); + } + return 0; + } + case COMMAND_STORE_WANTED_LEVEL: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + ScriptParams[0] = pPed->m_pWanted->GetWantedLevel(); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_CAR_STOPPED: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(CTheScripts::IsVehicleStopped(pVehicle)); + return 0; + } + case COMMAND_MARK_CHAR_AS_NO_LONGER_NEEDED: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CTheScripts::CleanUpThisPed(pPed); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_MARK_CAR_AS_NO_LONGER_NEEDED: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + CTheScripts::CleanUpThisVehicle(pVehicle); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CAR); + return 0; + } + case COMMAND_MARK_OBJECT_AS_NO_LONGER_NEEDED: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + CTheScripts::CleanUpThisObject(pObject); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_DONT_REMOVE_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_DONT_REMOVE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CAR); + return 0; + } + case COMMAND_DONT_REMOVE_OBJECT: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_CREATE_CHAR_AS_PASSENGER: + { + CollectParameters(&m_nIp, 4); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + switch (ScriptParams[2]) { + case MI_COP: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_STREET; + break; + case MI_SWAT: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_SWAT; + break; + case MI_FBI: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_FBI; + break; + case MI_ARMY: + if (ScriptParams[1] == PEDTYPE_COP) + ScriptParams[2] = COP_ARMY; + break; + case MI_MEDIC: + if (ScriptParams[1] == PEDTYPE_EMERGENCY) + ScriptParams[2] = PEDTYPE_EMERGENCY; + break; + case MI_FIREMAN: + if (ScriptParams[1] == PEDTYPE_FIREMAN) + ScriptParams[2] = PEDTYPE_FIREMAN; + break; + default: + break; + } + CPed* pPed; + if (ScriptParams[1] == PEDTYPE_COP) + pPed = new CCopPed((eCopType)ScriptParams[2]); + else if (ScriptParams[1] == PEDTYPE_EMERGENCY || ScriptParams[1] == PEDTYPE_FIREMAN) + pPed = new CEmergencyPed(ScriptParams[2]); + else + pPed = new CCivilianPed((ePedType)ScriptParams[1], ScriptParams[2]); + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + pPed->bAllowMedicsToReviveMe = false; + pPed->SetPosition(pVehicle->GetPosition()); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + pPed->SetPedState(PED_DRIVING); + CPopulation::ms_nTotalMissionPeds++; + if (ScriptParams[3] >= 0) + pVehicle->AddPassenger(pPed, ScriptParams[3]); + else + pVehicle->AddPassenger(pPed); + pPed->m_pMyVehicle = pVehicle; + pPed->m_pMyVehicle->RegisterReference((CEntity**)&pPed->m_pMyVehicle); + pPed->bInVehicle = true; + pPed->SetPedState(PED_DRIVING); + pVehicle->SetStatus(STATUS_PHYSICS); + pPed->bUsesCollision = false; +#ifdef FIX_BUGS + AnimationId anim = pVehicle->GetDriverAnim(); +#else + AnimationId anim = pVehicle->bLowVehicle ? ANIM_STD_CAR_SIT_LO : ANIM_STD_CAR_SIT; +#endif + pPed->m_pVehicleAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, anim, 100.0f); + pPed->StopNonPartialAnims(); + pPed->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pPed->GetPosition()); + CWorld::Add(pPed); + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pPed); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_SET_CHAR_OBJ_KILL_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_KILL_CHAR_ANY_MEANS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_KILL_CHAR_ANY_MEANS, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ANY_MEANS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_KILL_CHAR_ANY_MEANS, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_LEAVE_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + return 0; + } + case COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_PASSENGER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, pVehicle); + return 0; + } + case COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_DRIVER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicle); + return 0; + } + /* Not implemented. + case COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_IN_CAR: + case COMMAND_SET_CHAR_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE: + case COMMAND_SET_CHAR_OBJ_DESTROY_OBJECT: + */ + case COMMAND_SET_CHAR_OBJ_DESTROY_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_DESTROY_CAR, pVehicle); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_AREA_ON_FOOT: + { + CollectParameters(&m_nIp, 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, pos, radius); + return 0; + } + /* Not implemented. + case COMMAND_SET_CHAR_OBJ_GOTO_AREA_IN_CAR: + case COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET: + case COMMAND_SET_CHAR_OBJ_GUARD_ATTACK: + */ + case COMMAND_SET_CHAR_AS_LEADER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->SetObjective(OBJECTIVE_SET_LEADER, pTarget); + return 0; + } + case COMMAND_SET_PLAYER_AS_LEADER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CWorld::Players[ScriptParams[1]].m_pPed; + pPed->SetObjective(OBJECTIVE_SET_LEADER, pTarget); + return 0; + } + case COMMAND_LEAVE_GROUP: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->ClearLeader(); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FOLLOW_ROUTE: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FOLLOW_ROUTE, ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_ADD_ROUTE_POINT: + { + CollectParameters(&m_nIp, 4); + CRouteNode::AddRoutePoint(ScriptParams[0], *(CVector*)&ScriptParams[1]); + return 0; + } + case COMMAND_PRINT_WITH_NUMBER_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CMessages::AddBigMessageWithNumber(text, ScriptParams[1], ScriptParams[2] - 1, ScriptParams[0], -1, -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_NUMBER: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CMessages::AddMessageWithNumber(text, ScriptParams[1], ScriptParams[2], ScriptParams[0], -1, -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_NUMBER_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[1], ScriptParams[2], ScriptParams[0], -1, -1, -1, -1, -1); + return 0; + } + /* Not implemented. + case COMMAND_PRINT_WITH_NUMBER_SOON: + */ + case COMMAND_SWITCH_ROADS_ON: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX){ + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY){ + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ){ + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.SwitchRoadsOffInArea(infX, supX, infY, supY, infZ, supZ, false); + return 0; + } + case COMMAND_SWITCH_ROADS_OFF: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.SwitchRoadsOffInArea(infX, supX, infY, supY, infZ, supZ, true); + return 0; + } + case COMMAND_GET_NUMBER_OF_PASSENGERS: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->m_nNumPassengers; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_MAXIMUM_NUMBER_OF_PASSENGERS: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->m_nNumMaxPassengers; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CAR_DENSITY_MULTIPLIER: + { + CollectParameters(&m_nIp, 1); + CCarCtrl::CarDensityMultiplier = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SET_CAR_HEAVY: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bIsHeavy = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_CLEAR_CHAR_THREAT_SEARCH: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_fearFlags = 0; + return 0; + } + case COMMAND_ACTIVATE_CRANE: + { + CollectParameters(&m_nIp, 10); + float infX = *(float*)&ScriptParams[2]; + float infY = *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[4]; + supX = *(float*)&ScriptParams[2]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[5]; + supY = *(float*)&ScriptParams[3]; + } + CCranes::ActivateCrane(infX, supX, infY, supY, + *(float*)&ScriptParams[6], *(float*)&ScriptParams[7], *(float*)&ScriptParams[8], + DEGTORAD(*(float*)&ScriptParams[9]), false, false, + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + return 0; + } + case COMMAND_DEACTIVATE_CRANE: + { + CollectParameters(&m_nIp, 2); + CCranes::DeActivateCrane(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + return 0; + } + case COMMAND_SET_MAX_WANTED_LEVEL: + { + CollectParameters(&m_nIp, 1); + CWanted::SetMaximumWantedLevel(ScriptParams[0]); + return 0; + } + /* Debug commands? + case COMMAND_SAVE_VAR_INT: + case COMMAND_SAVE_VAR_FLOAT: + */ + case COMMAND_IS_CAR_IN_AIR_PROPER: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); +#ifdef FIX_BUGS + // don't wanna get stuck in unique stunt jump cam forever + bool usj_with_dodo = strcmp(m_abScriptName, "usj") == 0 && pVehicle->GetModelIndex() == MI_DODO; + UpdateCompareFlag(pVehicle->m_nCollisionRecords == 0 && !usj_with_dodo); +#else + UpdateCompareFlag(pVehicle->m_nCollisionRecords == 0); +#endif + return 0; + } + default: + script_assert(0); + } + return -1; +} diff --git a/src/control/Script3.cpp b/src/control/Script3.cpp new file mode 100644 index 0000000..ac88347 --- /dev/null +++ b/src/control/Script3.cpp @@ -0,0 +1,2362 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "Boat.h" +#include "CarCtrl.h" +#include "Clock.h" +#include "Coronas.h" +#include "Cranes.h" +#include "CutsceneMgr.h" +#include "Darkel.h" +#include "Explosion.h" +#include "Fire.h" +#include "General.h" +#include "Garages.h" +#include "Heli.h" +#include "Messages.h" +#include "Pad.h" +#include "ParticleObject.h" +#include "Phones.h" +#include "Pickups.h" +#include "PointLights.h" +#include "Population.h" +#include "Pools.h" +#include "ProjectileInfo.h" +#include "Radar.h" +#include "Restart.h" +#include "Stats.h" +#include "Streaming.h" +#include "User.h" +#include "WaterLevel.h" +#include "Weather.h" +#include "Zones.h" +#include "Wanted.h" + +int8 CRunningScript::ProcessCommands500To599(int32 command) +{ + switch (command) { + case COMMAND_IS_CAR_UPSIDEDOWN: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->GetUp().z <= -0.97f); + return 0; + } + case COMMAND_GET_PLAYER_CHAR: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pPed); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CANCEL_OVERRIDE_RESTART: + CRestart::CancelOverrideRestart(); + return 0; + case COMMAND_SET_POLICE_IGNORE_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + if (ScriptParams[1]) { + pPed->m_pWanted->m_bIgnoredByCops = true; + CWorld::StopAllLawEnforcersInTheirTracks(); + } + else { + pPed->m_pWanted->m_bIgnoredByCops = false; + } + return 0; + } + case COMMAND_ADD_PAGER_MESSAGE_WITH_NUMBER: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CUserDisplay::Pager.AddMessageWithNumber(text, ScriptParams[0], -1, -1, -1, -1, -1, + ScriptParams[1], ScriptParams[2], ScriptParams[3]); + return 0; + } + case COMMAND_START_KILL_FRENZY: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CDarkel::StartFrenzy((eWeaponType)ScriptParams[0], ScriptParams[1], ScriptParams[2], + ScriptParams[3], text, ScriptParams[4], ScriptParams[5], + ScriptParams[6], ScriptParams[7] != 0, false); + return 0; + } + case COMMAND_READ_KILL_FRENZY_STATUS: + { + ScriptParams[0] = CDarkel::ReadStatus(); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SQRT: + { + CollectParameters(&m_nIp, 1); + *(float*)&ScriptParams[0] = Sqrt(*(float*)&ScriptParams[0]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_2D: + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D: + LocatePlayerCarCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D: + LocateCharCarCommand(command, &m_nIp); + return 0; + case COMMAND_GENERATE_RANDOM_FLOAT_IN_RANGE: + CollectParameters(&m_nIp, 2); + *(float*)&ScriptParams[0] = CGeneral::GetRandomNumberInRange(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_GENERATE_RANDOM_INT_IN_RANGE: + CollectParameters(&m_nIp, 2); + ScriptParams[0] = CGeneral::GetRandomNumberInRange(ScriptParams[0], ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_LOCK_CAR_DOORS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->m_nDoorLock = (eCarLock)ScriptParams[1]; + return 0; + } + case COMMAND_EXPLODE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->BlowUpCar(nil); + return 0; + } + case COMMAND_ADD_EXPLOSION: + CollectParameters(&m_nIp, 4); + CExplosion::AddExplosion(nil, nil, (eExplosionType)ScriptParams[3], *(CVector*)&ScriptParams[0], 0); + return 0; + + case COMMAND_IS_CAR_UPRIGHT: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->GetUp().z >= 0.0f); + return 0; + } + case COMMAND_TURN_CHAR_TO_FACE_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CVehicle* pVehicle = pSourcePed->bInVehicle ? pSourcePed->m_pMyVehicle : nil; + CVector2D sourcePos = pSourcePed->bInVehicle ? pVehicle->GetPosition() : pSourcePed->GetPosition(); + CVector2D targetPos = pTargetPed->bInVehicle ? pTargetPed->m_pMyVehicle->GetPosition() : pTargetPed->GetPosition(); + float angle = CGeneral::GetATanOfXY(sourcePos.x - targetPos.x, sourcePos.y - targetPos.y) + HALFPI; + if (angle > TWOPI) + angle -= TWOPI; + if (!pVehicle) { + pSourcePed->m_fRotationCur = angle; + pSourcePed->m_fRotationDest = angle; + pSourcePed->SetHeading(angle); + } + return 0; + } + case COMMAND_TURN_CHAR_TO_FACE_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pTargetPed = CWorld::Players[ScriptParams[1]].m_pPed; + CVehicle* pVehicle = pSourcePed->bInVehicle ? pSourcePed->m_pMyVehicle : nil; + CVector2D sourcePos = pSourcePed->bInVehicle ? pVehicle->GetPosition() : pSourcePed->GetPosition(); + CVector2D targetPos = pTargetPed->bInVehicle ? pTargetPed->m_pMyVehicle->GetPosition() : pTargetPed->GetPosition(); + float angle = CGeneral::GetATanOfXY(sourcePos.x - targetPos.x, sourcePos.y - targetPos.y) + HALFPI; + if (angle > TWOPI) + angle -= TWOPI; + if (!pVehicle) { + pSourcePed->m_fRotationCur = angle; + pSourcePed->m_fRotationDest = angle; + pSourcePed->SetHeading(angle); + } + return 0; + } + case COMMAND_TURN_PLAYER_TO_FACE_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CWorld::Players[ScriptParams[0]].m_pPed; + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CVehicle* pVehicle = pSourcePed->bInVehicle ? pSourcePed->m_pMyVehicle : nil; + CVector2D sourcePos = pSourcePed->bInVehicle ? pVehicle->GetPosition() : pSourcePed->GetPosition(); + CVector2D targetPos = pTargetPed->bInVehicle ? pTargetPed->m_pMyVehicle->GetPosition() : pTargetPed->GetPosition(); + float angle = CGeneral::GetATanOfXY(sourcePos.x - targetPos.x, sourcePos.y - targetPos.y) + HALFPI; + if (angle > TWOPI) + angle -= TWOPI; + if (!pVehicle) { + pSourcePed->m_fRotationCur = angle; + pSourcePed->m_fRotationDest = angle; + pSourcePed->SetHeading(angle); + } + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_COORD_ON_FOOT: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector target; + target.x = *(float*)&ScriptParams[1]; + target.y = *(float*)&ScriptParams[2]; + target.z = CWorld::FindGroundZForCoord(target.x, target.y); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, target); + return 0; + } + /* Not implemented*/ + //case COMMAND_SET_CHAR_OBJ_GOTO_COORD_IN_CAR: + case COMMAND_CREATE_PICKUP: + { + CollectParameters(&m_nIp, 5); + int16 model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + CVector pos = *(CVector*)&ScriptParams[2]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GetActualPickupIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CPickups::GenerateNewOne(pos, model, ScriptParams[1], 0); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_HAS_PICKUP_BEEN_COLLECTED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CPickups::IsPickUpPickedUp(ScriptParams[0]) != 0); + return 0; + case COMMAND_REMOVE_PICKUP: + CollectParameters(&m_nIp, 1); + CPickups::RemovePickUp(ScriptParams[0]); + return 0; + case COMMAND_SET_TAXI_LIGHTS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + ((CAutomobile*)pVehicle)->SetTaxiLight(ScriptParams[1] != 0); + return 0; + } + case COMMAND_PRINT_BIG_Q: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 2); + CMessages::AddBigMessageQ(text, ScriptParams[0], ScriptParams[1] - 1); + return 0; + } + case COMMAND_PRINT_WITH_NUMBER_BIG_Q: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 3); + CMessages::AddBigMessageWithNumberQ(text, ScriptParams[1], ScriptParams[2] - 1, + ScriptParams[0], -1, -1, -1, -1, -1); + return 0; + } + case COMMAND_SET_GARAGE: + { + CollectParameters(&m_nIp, 7); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ScriptParams[0] = CGarages::AddOne(CVector(infX, infY, infZ), CVector(supX, supY, supZ), ScriptParams[6], 0); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_GARAGE_WITH_CAR_MODEL: + { + CollectParameters(&m_nIp, 8); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ScriptParams[0] = CGarages::AddOne(CVector(infX, infY, infZ), CVector(supX, supY, supZ), ScriptParams[6], ScriptParams[7]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_TARGET_CAR_FOR_MISSION_GARAGE: + { + CollectParameters(&m_nIp, 2); + CVehicle* pTarget; + if (ScriptParams[1] >= 0) { + pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + } + else { + pTarget = nil; + } + CGarages::SetTargetCarForMissonGarage(ScriptParams[0], pTarget); + return 0; + } + case COMMAND_IS_CAR_IN_MISSION_GARAGE: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::HasCarBeenDroppedOffYet(ScriptParams[0])); + return 0; + case COMMAND_SET_FREE_BOMBS: + CollectParameters(&m_nIp, 1); + CGarages::SetFreeBombs(ScriptParams[0] != 0); + return 0; +#if GTA_VERSION <= GTA3_PS2_160 + case COMMAND_SET_POWERPOINT: + { + CollectParameters(&m_nIp, 7); + float f1 = *(float*)&ScriptParams[0]; + float f2 = *(float*)&ScriptParams[1]; + float f3 = *(float*)&ScriptParams[2]; + float f4 = *(float*)&ScriptParams[3]; + float f5 = *(float*)&ScriptParams[4]; + float f6 = *(float*)&ScriptParams[5]; + float temp; + + if (f1 > f4) { + temp = f1; + f1 = f4; + f4 = temp; + } + + if (f2 > f5) { + temp = f2; + f2 = f5; + f5 = temp; + } + + if (f3 > f6) { + temp = f3; + f3 = f6; + f6 = temp; + } + + CPowerPoints::GenerateNewOne(f1, f2, f3, f4, f5, f6, *(uint8*)&ScriptParams[6]); + + return 0; + } +#endif // GTA_VERSION <= GTA3_PS2_160 + case COMMAND_SET_ALL_TAXI_LIGHTS: + CollectParameters(&m_nIp, 1); + CAutomobile::SetAllTaxiLights(ScriptParams[0] != 0); + return 0; + case COMMAND_IS_CAR_ARMED_WITH_ANY_BOMB: + { + CollectParameters(&m_nIp, 1); + CAutomobile* pCar = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pCar); + script_assert(pCar->m_vehType == VEHICLE_TYPE_CAR); + UpdateCompareFlag(pCar->m_bombType != 0); //TODO: enum + return 0; + } + case COMMAND_APPLY_BRAKES_TO_PLAYERS_CAR: + CollectParameters(&m_nIp, 2); + CPad::GetPad(ScriptParams[0])->bApplyBrakes = (ScriptParams[1] != 0); + return 0; + case COMMAND_SET_PLAYER_HEALTH: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->m_fHealth = ScriptParams[1]; + return 0; + } + case COMMAND_SET_CHAR_HEALTH: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) { + pPed->m_fHealth = ScriptParams[1]; + } + else if (pPed->bInVehicle) { + pPed->SetDead(); + if (!pPed->IsPlayer()) + pPed->FlagToDestroyWhenNextProcessed(); + } + else { + pPed->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + return 0; + } + case COMMAND_SET_CAR_HEALTH: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->m_fHealth = ScriptParams[1]; + return 0; + } + case COMMAND_GET_PLAYER_HEALTH: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + ScriptParams[0] = pPed->m_fHealth; // correct cast float to int + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_CHAR_HEALTH: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + ScriptParams[0] = pPed->m_fHealth; // correct cast float to int + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_CAR_HEALTH: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->m_fHealth; // correct cast float to int + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_CAR_ARMED_WITH_BOMB: + { + CollectParameters(&m_nIp, 2); + CAutomobile* pCar = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pCar); + script_assert(pCar->m_vehType == VEHICLE_TYPE_CAR); + UpdateCompareFlag(pCar->m_bombType == ScriptParams[1]); //TODO: enum + return 0; + } + case COMMAND_CHANGE_CAR_COLOUR: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (ScriptParams[1] >= 256 || ScriptParams[2] >= 256) + debug("CHANGE_CAR_COLOUR - Colours must be less than %d", 256); + pVehicle->m_currentColour1 = ScriptParams[1]; + pVehicle->m_currentColour2 = ScriptParams[2]; + return 0; + } + case COMMAND_SWITCH_PED_ROADS_ON: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.SwitchPedRoadsOffInArea(infX, supX, infY, supY, infZ, supZ, false); + return 0; + } + case COMMAND_SWITCH_PED_ROADS_OFF: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.SwitchPedRoadsOffInArea(infX, supX, infY, supY, infZ, supZ, true); + return 0; + } + case COMMAND_CHAR_LOOK_AT_CHAR_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pSourcePed); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTargetPed); + pSourcePed->SetLookFlag(pTargetPed, true); + pSourcePed->SetLookTimer(60000); + return 0; + } + case COMMAND_CHAR_LOOK_AT_PLAYER_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pSourcePed); + CPed* pTargetPed = CWorld::Players[ScriptParams[1]].m_pPed; + script_assert(pTargetPed); + pSourcePed->SetLookFlag(pTargetPed, true); + pSourcePed->SetLookTimer(60000); + return 0; + } + case COMMAND_PLAYER_LOOK_AT_CHAR_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pSourcePed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pSourcePed); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTargetPed); + pSourcePed->SetLookFlag(pTargetPed, true); + pSourcePed->SetLookTimer(60000); + return 0; + } + case COMMAND_STOP_CHAR_LOOKING: + { + CollectParameters(&m_nIp, 1); + CPed* pSourcePed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pSourcePed); + pSourcePed->ClearLookFlag(); + pSourcePed->bKeepTryingToLook = false; + if (pSourcePed->GetPedState() == PED_LOOK_HEADING || pSourcePed->GetPedState() == PED_LOOK_ENTITY) + pSourcePed->RestorePreviousState(); + return 0; + } + case COMMAND_STOP_PLAYER_LOOKING: + { + CollectParameters(&m_nIp, 1); + CPed* pSourcePed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pSourcePed); + pSourcePed->ClearLookFlag(); + pSourcePed->bKeepTryingToLook = false; + if (pSourcePed->GetPedState() == PED_LOOK_HEADING || pSourcePed->GetPedState() == PED_LOOK_ENTITY) + pSourcePed->RestorePreviousState(); + return 0; + } + case COMMAND_SWITCH_HELICOPTER: + CollectParameters(&m_nIp, 1); + CHeli::ActivateHeli(ScriptParams[0] != 0); + return 0; + + //case COMMAND_SET_GANG_ATTITUDE: + //case COMMAND_SET_GANG_GANG_ATTITUDE: + //case COMMAND_SET_GANG_PLAYER_ATTITUDE: + //case COMMAND_SET_GANG_PED_MODELS: + case COMMAND_SET_GANG_CAR_MODEL: + CollectParameters(&m_nIp, 2); + CGangs::SetGangVehicleModel(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_SET_GANG_WEAPONS: + CollectParameters(&m_nIp, 3); + CGangs::SetGangWeapons(ScriptParams[0], (eWeaponType)ScriptParams[1], (eWeaponType)ScriptParams[2]); + return 0; + case COMMAND_SET_CHAR_OBJ_RUN_TO_AREA: + { + CollectParameters(&m_nIp, 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_RUN_TO_AREA, pos, radius); + return 0; + } + case COMMAND_SET_CHAR_OBJ_RUN_TO_COORD: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos; + pos.x = *(float*)&ScriptParams[1]; + pos.y = *(float*)&ScriptParams[2]; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_RUN_TO_AREA, pos); + return 0; + } + case COMMAND_IS_PLAYER_TOUCHING_OBJECT_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + bool isTouching = false; + if (pPed->bInVehicle) + isTouching = false; + else if (pPed->GetHasCollidedWith(pObject)) + isTouching = true; + UpdateCompareFlag(isTouching); + return 0; + } + case COMMAND_IS_CHAR_TOUCHING_OBJECT_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + bool isTouching = false; + if (pPed->InVehicle()) + isTouching = false; + else if (pPed->GetHasCollidedWith(pObject)) + isTouching = true; + UpdateCompareFlag(isTouching); + return 0; + } + case COMMAND_LOAD_SPECIAL_CHARACTER: + { + CollectParameters(&m_nIp, 1); + char name[16]; + strncpy(name, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + name[i] = tolower(name[i]); + CStreaming::RequestSpecialChar(ScriptParams[0] - 1, name, STREAMFLAGS_DEPENDENCY | STREAMFLAGS_SCRIPTOWNED); + m_nIp += KEY_LENGTH_IN_SCRIPT; + return 0; + } + case COMMAND_HAS_SPECIAL_CHARACTER_LOADED: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CStreaming::HasSpecialCharLoaded(ScriptParams[0] - 1)); + return 0; + } + case COMMAND_FLASH_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bHasBlip = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_FLASH_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bHasBlip = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_FLASH_OBJECT: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->bHasBlip = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_IS_PLAYER_IN_REMOTE_MODE: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].IsPlayerInRemoteMode()); + return 0; + case COMMAND_ARM_CAR_WITH_BOMB: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + ((CAutomobile*)pVehicle)->m_bombType = ScriptParams[1]; + ((CAutomobile*)pVehicle)->m_pBombRigger = FindPlayerPed(); + return 0; + } + case COMMAND_SET_CHAR_PERSONALITY: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->SetPedStats((ePedStats)ScriptParams[1]); + return 0; + } + case COMMAND_SET_CUTSCENE_OFFSET: + CollectParameters(&m_nIp, 3); + CCutsceneMgr::SetCutsceneOffset(*(CVector*)&ScriptParams[0]); + return 0; + case COMMAND_SET_ANIM_GROUP_FOR_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_animGroup = (AssocGroupId)ScriptParams[1]; + return 0; + } + case COMMAND_SET_ANIM_GROUP_FOR_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->m_animGroup = (AssocGroupId)ScriptParams[1]; + return 0; + } + case COMMAND_REQUEST_MODEL: + { + CollectParameters(&m_nIp, 1); + int model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + CStreaming::RequestModel(model, STREAMFLAGS_DEPENDENCY | STREAMFLAGS_NOFADE | STREAMFLAGS_SCRIPTOWNED); + return 0; + } + case COMMAND_HAS_MODEL_LOADED: + { + CollectParameters(&m_nIp, 1); + int model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + UpdateCompareFlag(CStreaming::HasModelLoaded(model)); + return 0; + } + case COMMAND_MARK_MODEL_AS_NO_LONGER_NEEDED: + { + CollectParameters(&m_nIp, 1); + int model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + CStreaming::SetMissionDoesntRequireModel(model); + return 0; + } + case COMMAND_GRAB_PHONE: + { + CollectParameters(&m_nIp, 2); + ScriptParams[0] = gPhoneInfo.GrabPhone(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_REPEATED_PHONE_MESSAGE: + { + CollectParameters(&m_nIp, 1); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text, nil, nil, nil, nil, nil); + return 0; + } + case COMMAND_SET_PHONE_MESSAGE: + { + CollectParameters(&m_nIp, 1); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text, nil, nil, nil, nil, nil); + return 0; + } + case COMMAND_HAS_PHONE_DISPLAYED_MESSAGE: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(gPhoneInfo.HasMessageBeenDisplayed(ScriptParams[0])); + return 0; + } + case COMMAND_TURN_PHONE_OFF: + { + CollectParameters(&m_nIp, 1); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], nil, nil, nil, nil, nil, nil); + return 0; + } + case COMMAND_DRAW_CORONA: + { + CollectParameters(&m_nIp, 9); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CCoronas::RegisterCorona((uintptr)this + m_nIp, ScriptParams[6], ScriptParams[7], ScriptParams[8], + 255, pos, *(float*)&ScriptParams[3], 150.0f, ScriptParams[4], ScriptParams[5], 1, 0, 0, 0.0f); + return 0; + } + case COMMAND_DRAW_LIGHT: + { + CollectParameters(&m_nIp, 6); + CVector pos = *(CVector*)&ScriptParams[0]; + CVector unused(0.0f, 0.0f, 0.0f); + CPointLights::AddLight(0, *(CVector*)&ScriptParams[0], CVector(0.0f, 0.0f, 0.0f), 12.0f, + ScriptParams[3] / 255.0f, ScriptParams[4] / 255.0f, ScriptParams[5] / 255.0f, 0, true); + return 0; + } + case COMMAND_STORE_WEATHER: + CWeather::StoreWeatherState(); + return 0; + case COMMAND_RESTORE_WEATHER: + CWeather::RestoreWeatherState(); + return 0; + case COMMAND_STORE_CLOCK: + CClock::StoreClock(); + return 0; + case COMMAND_RESTORE_CLOCK: + CClock::RestoreClock(); + return 0; + case COMMAND_RESTART_CRITICAL_MISSION: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRestart::OverrideNextRestart(pos, *(float*)&ScriptParams[3]); + if (CWorld::Players[CWorld::PlayerInFocus].m_WBState != WBSTATE_PLAYING) + printf("RESTART_CRITICAL_MISSION - Player state is not PLAYING\n"); + CWorld::Players[CWorld::PlayerInFocus].PlayerFailedCriticalMission(); + return 0; + } + case COMMAND_IS_PLAYER_PLAYING: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CWorld::Players[ScriptParams[0]].m_WBState == WBSTATE_PLAYING); + return 0; + } +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_NO_OBJ: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_NONE); + return 0; +#endif + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands600To699(int32 command) +{ + switch (command){ +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_WAIT_ON_FOOT: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_WAIT_ON_FOOT); + return 0; + case COMMAND_SET_COLL_OBJ_FLEE_ON_FOOT_TILL_SAFE: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + return 0; + case COMMAND_SET_COLL_OBJ_GUARD_SPOT: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GUARD_AREA, pos); + return 0; + } + case COMMAND_SET_COLL_OBJ_GUARD_AREA: + { + CollectParameters(&m_nIp, 5); + float infX = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[3]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + float infY = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[4]; + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GUARD_AREA, pos, radius); + return 0; + } + case COMMAND_SET_COLL_OBJ_WAIT_IN_CAR: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_WAIT_IN_CAR); + return 0; + case COMMAND_SET_COLL_OBJ_KILL_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_KILL_CHAR_ON_FOOT, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_KILL_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_KILL_CHAR_ON_FOOT, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_KILL_CHAR_ANY_MEANS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_KILL_CHAR_ANY_MEANS, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_KILL_PLAYER_ANY_MEANS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_KILL_CHAR_ANY_MEANS, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_GOTO_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_CHAR_ON_FOOT, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_GOTO_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[1]].m_pPed; + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_CHAR_ON_FOOT, pPed); + return 0; + } + case COMMAND_SET_COLL_OBJ_LEAVE_CAR: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_LEAVE_CAR); + return 0; + case COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_PASSENGER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_ENTER_CAR_AS_PASSENGER, pVehicle); + return 0; + } + case COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_DRIVER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicle); + return 0; + } + /* + case COMMAND_SET_COLL_OBJ_FOLLOW_CAR_IN_CAR: + case COMMAND_SET_COLL_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE: + case COMMAND_SET_COLL_OBJ_DESTROY_OBJECT: + */ + case COMMAND_SET_COLL_OBJ_DESTROY_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_DESTROY_CAR, pVehicle); + return 0; + } + case COMMAND_SET_COLL_OBJ_GOTO_AREA_ON_FOOT: + { + CollectParameters(&m_nIp, 5); + float infX = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[3]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + float infY = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[4]; + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_AREA_ON_FOOT, pos, radius); + return 0; + } + /* + case COMMAND_SET_COLL_OBJ_GOTO_AREA_IN_CAR: + case COMMAND_SET_COLL_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET: + case COMMAND_SET_COLL_OBJ_GUARD_ATTACK: + */ + case COMMAND_SET_COLL_OBJ_FOLLOW_ROUTE: + CollectParameters(&m_nIp, 3); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_FOLLOW_ROUTE, ScriptParams[1], ScriptParams[2]); + return 0; + case COMMAND_SET_COLL_OBJ_GOTO_COORD_ON_FOOT: + { + CollectParameters(&m_nIp, 3); + CVector pos(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2], CWorld::FindGroundZForCoord(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2])); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_AREA_ON_FOOT, pos); + return 0; + } + //case COMMAND_SET_COLL_OBJ_GOTO_COORD_IN_CAR: + case COMMAND_SET_COLL_OBJ_RUN_TO_AREA: + { + CollectParameters(&m_nIp, 5); + float infX = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[3]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + float infY = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[4]; + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_RUN_TO_AREA, pos, radius); + return 0; + } + case COMMAND_SET_COLL_OBJ_RUN_TO_COORD: + { + CollectParameters(&m_nIp, 3); + CVector pos(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2], CWorld::FindGroundZForCoord(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2])); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_RUN_TO_AREA, pos); + return 0; + } + case COMMAND_ADD_PEDS_IN_AREA_TO_COLL: + { + CollectParameters(&m_nIp, 3); + float X = *(float*)&ScriptParams[0]; + float Y = *(float*)&ScriptParams[1]; + float Z = CWorld::FindGroundZForCoord(X, Y); + float radius = *(float*)&ScriptParams[2]; + ScriptParams[0] = CTheScripts::AddPedsInAreaToCollective(X, Y, Z, radius); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_PEDS_IN_VEHICLE_TO_COLL: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CTheScripts::AddPedsInVehicleToCollective(ScriptParams[0]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_CLEAR_COLL: + CollectParameters(&m_nIp, 1); + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CTheScripts::CollectiveArray[i].colIndex == ScriptParams[0]) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + } + } + return 0; + case COMMAND_IS_COLL_IN_CARS: + { + CollectParameters(&m_nIp, 1); + bool result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + } + else { + result = false; + break; + } + } + UpdateCompareFlag(result); + return 0; + } + case COMMAND_LOCATE_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_COLL_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D: + LocateCollectiveCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D: + LocateCollectiveCharCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_COLL_IN_CAR_CAR_2D: + LocateCollectiveCarCommand(command, &m_nIp); + return 0; + case COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D: + case COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D: + LocateCollectivePlayerCommand(command, &m_nIp); + return 0; + case COMMAND_IS_COLL_IN_AREA_2D: + case COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_IN_AREA_IN_CAR_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D: + CollectiveInAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_GET_NUMBER_OF_PEDS_IN_COLL: + { + CollectParameters(&m_nIp, 1); + int total = 0; + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + } + else { + total++; + } + } + ScriptParams[0] = total; + StoreParameters(&m_nIp, 1); + return 0; + } +#endif + case COMMAND_SET_CHAR_HEED_THREATS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bRespondsToThreats = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_PLAYER_HEED_THREATS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->bRespondsToThreats = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_GET_CONTROLLER_MODE: +#if defined(GTA_PC) && !defined(DETECT_PAD_INPUT_SWITCH) + ScriptParams[0] = 0; +#else + ScriptParams[0] = CPad::IsAffectedByController ? CPad::GetPad(0)->Mode : 0; +#endif + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_SET_CAN_RESPRAY_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + ((CAutomobile*)pVehicle)->bFixedColour = (ScriptParams[1] == 0); + return 0; + } + case COMMAND_IS_TAXI: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + int mi = pVehicle->GetModelIndex(); + UpdateCompareFlag(mi == MI_TAXI || mi == MI_CABBIE || mi == MI_BORGNINE); + return 0; + } + case COMMAND_UNLOAD_SPECIAL_CHARACTER: + CollectParameters(&m_nIp, 1); + CStreaming::SetMissionDoesntRequireSpecialChar(ScriptParams[0] - 1); + return 0; + case COMMAND_RESET_NUM_OF_MODELS_KILLED_BY_PLAYER: + CDarkel::ResetModelsKilledByPlayer(); + return 0; + case COMMAND_GET_NUM_OF_MODELS_KILLED_BY_PLAYER: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CDarkel::QueryModelsKilledByPlayer(ScriptParams[0]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_ACTIVATE_GARAGE: + CollectParameters(&m_nIp, 1); + CGarages::ActivateGarage(ScriptParams[0]); + return 0; + case COMMAND_SWITCH_TAXI_TIMER: + { + CollectParameters(&m_nIp, 1); + if (ScriptParams[0] != 0){ + CWorld::Players[CWorld::PlayerInFocus].m_nUnusedTaxiTimer = CTimer::GetTimeInMilliseconds(); + CWorld::Players[CWorld::PlayerInFocus].m_bUnusedTaxiThing = true; + }else{ + CWorld::Players[CWorld::PlayerInFocus].m_bUnusedTaxiThing = false; + } + return 0; + } + case COMMAND_CREATE_OBJECT_NO_OFFSET: + { + CollectParameters(&m_nIp, 4); + int mi = ScriptParams[0] >= 0 ? ScriptParams[0] : CTheScripts::UsedObjectArray[-ScriptParams[0]].index; + CObject* pObj = new CObject(mi, false); + pObj->ObjectCreatedBy = MISSION_OBJECT; + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pObj->SetPosition(pos); + pObj->SetOrientation(0.0f, 0.0f, 0.0f); + pObj->GetMatrix().UpdateRW(); + pObj->UpdateRwFrame(); + CTheScripts::ClearSpaceForMissionEntity(pos, pObj); + CWorld::Add(pObj); + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pObj); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_OBJECT); + return 0; + } + case COMMAND_IS_BOAT: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->m_vehType == VEHICLE_TYPE_BOAT); + return 0; + } + case COMMAND_SET_CHAR_OBJ_GOTO_AREA_ANY_MEANS: + { + CollectParameters(&m_nIp, 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_AREA_ANY_MEANS, pos, radius); + return 0; + } +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_GOTO_AREA_ANY_MEANS: + { + CollectParameters(&m_nIp, 5); + float infX = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[3]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[1]; + } + float infY = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[4]; + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[2]; + } + CVector pos; + pos.x = (infX + supX) / 2; + pos.y = (infY + supY) / 2; + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = Max(pos.x - infX, pos.y - infY); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_GOTO_AREA_ANY_MEANS, pos, radius); + return 0; + } +#endif + case COMMAND_IS_PLAYER_STOPPED: + { + CollectParameters(&m_nIp, 1); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + UpdateCompareFlag(CTheScripts::IsPlayerStopped(pPlayer)); + return 0; + } + case COMMAND_IS_CHAR_STOPPED: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(CTheScripts::IsPedStopped(pPed)); + return 0; + } + case COMMAND_MESSAGE_WAIT: + CollectParameters(&m_nIp, 2); + m_nWakeTime = CTimer::GetTimeInMilliseconds() + ScriptParams[0]; + if (ScriptParams[1] != 0) + m_bSkipWakeTime = true; + return 1; + case COMMAND_ADD_PARTICLE_EFFECT: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CParticleObject::AddObject(ScriptParams[0], pos, ScriptParams[4] != 0); + return 0; + } + case COMMAND_SWITCH_WIDESCREEN: + CollectParameters(&m_nIp, 1); + if (ScriptParams[0] != 0) + TheCamera.SetWideScreenOn(); + else + TheCamera.SetWideScreenOff(); + return 0; + case COMMAND_ADD_SPRITE_BLIP_FOR_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetEntityBlip(BLIP_CAR, ScriptParams[0], 0, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[1]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetEntityBlip(BLIP_CHAR, ScriptParams[0], 1, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[1]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_OBJECT: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetEntityBlip(BLIP_OBJECT, ScriptParams[0], 6, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[1]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_CONTACT_POINT: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetCoordBlip(BLIP_CONTACT_POINT, pos, 2, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[3]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_COORD: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int id = CRadar::SetCoordBlip(BLIP_COORD, pos, 5, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[3]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_ONLY_DAMAGED_BY_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bOnlyDamagedByPlayer = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CAR_ONLY_DAMAGED_BY_PLAYER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bOnlyDamagedByPlayer = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CHAR_PROOFS: + { + CollectParameters(&m_nIp, 6); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bBulletProof = (ScriptParams[1] != 0); + pPed->bFireProof = (ScriptParams[2] != 0); + pPed->bExplosionProof = (ScriptParams[3] != 0); + pPed->bCollisionProof = (ScriptParams[4] != 0); + pPed->bMeleeProof = (ScriptParams[5] != 0); + return 0; + } + case COMMAND_SET_CAR_PROOFS: + { + CollectParameters(&m_nIp, 6); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bBulletProof = (ScriptParams[1] != 0); + pVehicle->bFireProof = (ScriptParams[2] != 0); + pVehicle->bExplosionProof = (ScriptParams[3] != 0); + pVehicle->bCollisionProof = (ScriptParams[4] != 0); + pVehicle->bMeleeProof = (ScriptParams[5] != 0); + return 0; + } + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D: + PlayerInAngledAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_DEACTIVATE_GARAGE: + CollectParameters(&m_nIp, 1); + CGarages::DeActivateGarage(ScriptParams[0]); + return 0; + case COMMAND_GET_NUMBER_OF_CARS_COLLECTED_BY_GARAGE: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CGarages::QueryCarsCollected(ScriptParams[0]); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_HAS_CAR_BEEN_TAKEN_TO_GARAGE: + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CGarages::HasThisCarBeenCollected(ScriptParams[0], ScriptParams[1] - 1)); + return 0; + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands700To799(int32 command) +{ + switch (command){ + case COMMAND_SET_SWAT_REQUIRED: + CollectParameters(&m_nIp, 1); + FindPlayerPed()->m_pWanted->m_bSwatRequired = (ScriptParams[0] != 0); + return 0; + case COMMAND_SET_FBI_REQUIRED: + CollectParameters(&m_nIp, 1); + FindPlayerPed()->m_pWanted->m_bFbiRequired = (ScriptParams[0] != 0); + return 0; + case COMMAND_SET_ARMY_REQUIRED: + CollectParameters(&m_nIp, 1); + FindPlayerPed()->m_pWanted->m_bArmyRequired = (ScriptParams[0] != 0); + return 0; + case COMMAND_IS_CAR_IN_WATER: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(pVehicle && pVehicle->bIsInWater); + return 0; + } + case COMMAND_GET_CLOSEST_CHAR_NODE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPathNode* pNode = &ThePaths.m_pathNodes[ThePaths.FindNodeClosestToCoors(pos, 1, 999999.9f)]; + *(CVector*)&ScriptParams[0] = pNode->GetPosition(); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_GET_CLOSEST_CAR_NODE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPathNode* pNode = &ThePaths.m_pathNodes[ThePaths.FindNodeClosestToCoors(pos, 0, 999999.9f)]; + *(CVector*)&ScriptParams[0] = pNode->GetPosition(); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_CAR_GOTO_COORDINATES_ACCURATE: + { + CollectParameters(&m_nIp, 4); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += pVehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + if (CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, pos, false)) + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTO_COORDS_STRAIGHT_ACCURATE; + else + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_ACCURATE; + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->bEngineOn = true; + pVehicle->AutoPilot.m_nCruiseSpeed = Max(6, pVehicle->AutoPilot.m_nCruiseSpeed); + pVehicle->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + return 0; + } + case COMMAND_START_PACMAN_RACE: + CollectParameters(&m_nIp, 1); + CPacManPickups::StartPacManRace(ScriptParams[0]); + return 0; + case COMMAND_START_PACMAN_RECORD: + CPacManPickups::StartPacManRecord(); + return 0; + case COMMAND_GET_NUMBER_OF_POWER_PILLS_EATEN: + ScriptParams[0] = CPacManPickups::QueryPowerPillsEatenInRace(); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_CLEAR_PACMAN: + CPacManPickups::CleanUpPacManStuff(); + return 0; + case COMMAND_START_PACMAN_SCRAMBLE: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPacManPickups::StartPacManScramble(pos, *(float*)&ScriptParams[3], ScriptParams[4]); + return 0; + } + case COMMAND_GET_NUMBER_OF_POWER_PILLS_CARRIED: + ScriptParams[0] = CPacManPickups::QueryPowerPillsCarriedByPlayer(); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_CARRIED: + CPacManPickups::ResetPowerPillsCarriedByPlayer(); + return 0; + case COMMAND_IS_CAR_ON_SCREEN: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(TheCamera.IsSphereVisible(pVehicle->GetBoundCentre(), pVehicle->GetBoundRadius())); + return 0; + } + case COMMAND_IS_CHAR_ON_SCREEN: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(TheCamera.IsSphereVisible(pPed->GetBoundCentre(), pPed->GetBoundRadius())); + return 0; + } + case COMMAND_IS_OBJECT_ON_SCREEN: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + UpdateCompareFlag(TheCamera.IsSphereVisible(pObject->GetBoundCentre(), pObject->GetBoundRadius())); + return 0; + } + case COMMAND_GOSUB_FILE: + { + CollectParameters(&m_nIp, 2); + script_assert(m_nStackPointer < MAX_STACK_DEPTH); + m_anStack[m_nStackPointer++] = m_nIp; + SetIP(ScriptParams[0]); + // ScriptParams[1] == filename + return 0; + } + case COMMAND_GET_GROUND_Z_FOR_3D_COORD: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + bool success; + *(float*)&ScriptParams[0] = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &success); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_START_SCRIPT_FIRE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + ScriptParams[0] = gFireManager.StartScriptFire(pos, nil, 0.8f, 1); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_SCRIPT_FIRE_EXTINGUISHED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(gFireManager.IsScriptFireExtinguish(ScriptParams[0])); + return 0; + case COMMAND_REMOVE_SCRIPT_FIRE: + CollectParameters(&m_nIp, 1); + gFireManager.RemoveScriptFire(ScriptParams[0]); + return 0; + case COMMAND_SET_COMEDY_CONTROLS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bComedyControls = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_BOAT_GOTO_COORDS: + { + CollectParameters(&m_nIp, 4); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_BOAT); + CBoat* pBoat = (CBoat*)pVehicle; + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &pos.z, false); + pBoat->AutoPilot.m_nCarMission = MISSION_GOTOCOORDS_ASTHECROWSWIMS; + pBoat->AutoPilot.m_vecDestinationCoors = pos; + pBoat->SetStatus(STATUS_PHYSICS); + pBoat->bEngineOn = true; + pBoat->AutoPilot.m_nCruiseSpeed = Max(6, pBoat->AutoPilot.m_nCruiseSpeed); + pBoat->AutoPilot.m_nAntiReverseTimer = CTimer::GetTimeInMilliseconds(); + return 0; + } + case COMMAND_BOAT_STOP: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_BOAT); + CBoat* pBoat = (CBoat*)pVehicle; + pBoat->AutoPilot.m_nCarMission = MISSION_NONE; + pBoat->SetStatus(STATUS_PHYSICS); + pBoat->bEngineOn = false; + pBoat->AutoPilot.m_nCruiseSpeed = 0; + return 0; + } + case COMMAND_IS_PLAYER_SHOOTING_IN_AREA: + { + CollectParameters(&m_nIp, 6); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + UpdateCompareFlag(pPed->bIsShooting && pPed->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CHAR_SHOOTING_IN_AREA: + { + CollectParameters(&m_nIp, 6); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + float x1 = *(float*)&ScriptParams[1]; + float y1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + UpdateCompareFlag(pPed->bIsShooting && pPed->IsWithinArea(x1, y1, x2, y2)); + if (ScriptParams[5]) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, x1, y1, x2, y2, MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugSquare(x1, y1, x2, y2); + return 0; + } + case COMMAND_IS_CURRENT_PLAYER_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(ScriptParams[1] == pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType); + return 0; + } + case COMMAND_IS_CURRENT_CHAR_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(ScriptParams[1] == pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType); + return 0; + } + case COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_EATEN: + CPacManPickups::ResetPowerPillsEatenInRace(); + return 0; + case COMMAND_ADD_POWER_PILL: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CPacManPickups::GenerateOnePMPickUp(pos); + return 0; + } + case COMMAND_SET_BOAT_CRUISE_SPEED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_BOAT); + CBoat* pBoat = (CBoat*)pVehicle; + pBoat->AutoPilot.m_nCruiseSpeed = *(float*)&ScriptParams[1]; + return 0; + } + case COMMAND_GET_RANDOM_CHAR_IN_AREA: + { + CollectParameters(&m_nIp, 4); + int ped_handle = -1; + CVector pos = FindPlayerCoors(); + float x1 = *(float*)&ScriptParams[0]; + float y1 = *(float*)&ScriptParams[1]; + float x2 = *(float*)&ScriptParams[2]; + float y2 = *(float*)&ScriptParams[3]; + int i = CPools::GetPedPool()->GetSize(); + while (--i && ped_handle == -1){ + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (CTheScripts::LastRandomPedId == CPools::GetPedPool()->GetIndex(pPed)) + continue; + if (pPed->CharCreatedBy != RANDOM_CHAR) + continue; + if (!pPed->IsPedInControl()) + continue; + if (pPed->bRemoveFromWorld) + continue; + if (pPed->bFadeOut) + continue; + if (pPed->GetModelIndex() == MI_SCUM_WOM || pPed->GetModelIndex() == MI_SCUM_MAN) + continue; + if (!ThisIsAValidRandomPed(pPed->m_nPedType)) + continue; + if (pPed->bIsLeader || pPed->m_leader) + continue; + if (!pPed->IsWithinArea(x1, y1, x2, y2)) + continue; + if (pos.z - PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + PED_FIND_Z_OFFSET < pPed->GetPosition().z) + continue; + ped_handle = CPools::GetPedPool()->GetIndex(pPed); + CTheScripts::LastRandomPedId = ped_handle; + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + ++CPopulation::ms_nTotalMissionPeds; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ped_handle, CLEANUP_CHAR); + } + ScriptParams[0] = ped_handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_RANDOM_CHAR_IN_ZONE: + { + char zone[KEY_LENGTH_IN_SCRIPT]; + strncpy(zone, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + int nZone = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (nZone != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetZone(nZone); + int ped_handle = -1; + CVector pos = FindPlayerCoors(); + int i = CPools::GetPedPool()->GetSize(); + while (--i && ped_handle == -1) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (CTheScripts::LastRandomPedId == CPools::GetPedPool()->GetIndex(pPed)) + continue; + if (pPed->CharCreatedBy != RANDOM_CHAR) + continue; + if (!pPed->IsPedInControl()) + continue; + if (pPed->bRemoveFromWorld) + continue; + if (pPed->bFadeOut) + continue; + if (pPed->GetModelIndex() == MI_SCUM_WOM || pPed->GetModelIndex() == MI_SCUM_MAN) + continue; + if (!ThisIsAValidRandomPed(pPed->m_nPedType)) + continue; + if (pPed->bIsLeader || pPed->m_leader) + continue; + if (!CTheZones::PointLiesWithinZone(&pPed->GetPosition(), pZone)) + continue; + if (pos.z - PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + PED_FIND_Z_OFFSET < pPed->GetPosition().z) + continue; + ped_handle = CPools::GetPedPool()->GetIndex(pPed); + CTheScripts::LastRandomPedId = ped_handle; + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + ++CPopulation::ms_nTotalMissionPeds; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ped_handle, CLEANUP_CHAR); + } + ScriptParams[0] = ped_handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_PLAYER_IN_TAXI: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->IsTaxi()); + return 0; + } + case COMMAND_IS_PLAYER_SHOOTING: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bIsShooting); + return 0; + } + case COMMAND_IS_CHAR_SHOOTING: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bIsShooting); + return 0; + } + case COMMAND_CREATE_MONEY_PICKUP: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GetActualPickupIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CPickups::GenerateNewOne(pos, MI_MONEY, PICKUP_MONEY, ScriptParams[3]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_ACCURACY: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_wepAccuracy = ScriptParams[1]; + return 0; + } + case COMMAND_GET_CAR_SPEED: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + *(float*)&ScriptParams[0] = pVehicle->GetSpeed().Magnitude() * GAME_SPEED_TO_METERS_PER_SECOND; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_LOAD_CUTSCENE: + { + char name[KEY_LENGTH_IN_SCRIPT]; + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CCutsceneMgr::LoadCutsceneData(name); + return 0; + } + case COMMAND_CREATE_CUTSCENE_OBJECT: + { + CollectParameters(&m_nIp, 1); + CCutsceneObject* pCutObj = CCutsceneMgr::CreateCutsceneObject(ScriptParams[0]); + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pCutObj); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CUTSCENE_ANIM: + { + CollectParameters(&m_nIp, 1); + char name[KEY_LENGTH_IN_SCRIPT]; + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CCutsceneMgr::SetCutsceneAnim(name, pObject); + return 0; + } + case COMMAND_START_CUTSCENE: + CCutsceneMgr::ms_cutsceneLoadStatus = 1; + return 0; + case COMMAND_GET_CUTSCENE_TIME: + ScriptParams[0] = CCutsceneMgr::GetCutsceneTimeInMilleseconds(); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_HAS_CUTSCENE_FINISHED: + UpdateCompareFlag(CCutsceneMgr::HasCutsceneFinished()); + return 0; + case COMMAND_CLEAR_CUTSCENE: + CCutsceneMgr::DeleteCutsceneData(); + return 0; + case COMMAND_RESTORE_CAMERA_JUMPCUT: + TheCamera.RestoreWithJumpCut(); + return 0; + case COMMAND_CREATE_COLLECTABLE1: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GenerateNewOne(pos, MI_COLLECTABLE1, PICKUP_COLLECTABLE1, 0); + return 0; + } + case COMMAND_SET_COLLECTABLE1_TOTAL: + CollectParameters(&m_nIp, 1); + CWorld::Players[CWorld::PlayerInFocus].m_nTotalPackages = ScriptParams[0]; + return 0; + case COMMAND_IS_PROJECTILE_IN_AREA: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + UpdateCompareFlag(CProjectileInfo::IsProjectileInRange(infX, supX, infY, supY, infZ, supZ, false)); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + return 0; + } + case COMMAND_DESTROY_PROJECTILES_IN_AREA: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + UpdateCompareFlag(CProjectileInfo::IsProjectileInRange(infX, supX, infY, supY, infZ, supZ, true)); + if (CTheScripts::DbgFlag) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + return 0; + } + case COMMAND_DROP_MINE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GenerateNewOne(pos, MI_CARMINE, PICKUP_MINE_INACTIVE, 0); + return 0; + } + case COMMAND_DROP_NAUTICAL_MINE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GenerateNewOne(pos, MI_NAUTICALMINE, PICKUP_MINE_INACTIVE, 0); + return 0; + } + case COMMAND_IS_CHAR_MODEL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(ScriptParams[1] == pPed->GetModelIndex()); + return 0; + } + case COMMAND_LOAD_SPECIAL_MODEL: + { + CollectParameters(&m_nIp, 1); + char name[KEY_LENGTH_IN_SCRIPT]; + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + name[i] = tolower(name[i]); + CStreaming::RequestSpecialModel(ScriptParams[0], name, STREAMFLAGS_DEPENDENCY | STREAMFLAGS_SCRIPTOWNED); + m_nIp += KEY_LENGTH_IN_SCRIPT; + return 0; + } + case COMMAND_CREATE_CUTSCENE_HEAD: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CCutsceneHead* pCutHead = CCutsceneMgr::AddCutsceneHead(pObject, ScriptParams[1]); + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pCutHead); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CUTSCENE_HEAD_ANIM: + { + CollectParameters(&m_nIp, 1); + CObject* pCutHead = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pCutHead); + char name[KEY_LENGTH_IN_SCRIPT]; + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CTimer::Stop(); + CCutsceneMgr::SetHeadAnim(name, pCutHead); + CTimer::Update(); + return 0; + } + case COMMAND_SIN: + CollectParameters(&m_nIp, 1); + *(float*)&ScriptParams[0] = Sin(DEGTORAD(*(float*)&ScriptParams[0])); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_COS: + CollectParameters(&m_nIp, 1); + *(float*)&ScriptParams[0] = Cos(DEGTORAD(*(float*)&ScriptParams[0])); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_GET_CAR_FORWARD_X: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + float forwardX = pVehicle->GetForward().x / pVehicle->GetForward().Magnitude2D(); + *(float*)&ScriptParams[0] = forwardX; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_CAR_FORWARD_Y: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + float forwardY = pVehicle->GetForward().y / pVehicle->GetForward().Magnitude2D(); + *(float*)&ScriptParams[0] = forwardY; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CHANGE_GARAGE_TYPE: + CollectParameters(&m_nIp, 2); + CGarages::ChangeGarageType(ScriptParams[0], ScriptParams[1], 0); + return 0; + case COMMAND_ACTIVATE_CRUSHER_CRANE: + { + CollectParameters(&m_nIp, 10); + float infX = *(float*)&ScriptParams[2]; + float infY = *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[4]; + supX = *(float*)&ScriptParams[2]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[5]; + supY = *(float*)&ScriptParams[3]; + } + CCranes::ActivateCrane(infX, supX, infY, supY, + *(float*)&ScriptParams[6], *(float*)&ScriptParams[7], *(float*)&ScriptParams[8], + DEGTORAD(*(float*)&ScriptParams[9]), true, false, + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + return 0; + } + case COMMAND_PRINT_WITH_2_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CMessages::AddMessageWithNumber(text, ScriptParams[2], ScriptParams[3], ScriptParams[0], ScriptParams[1], -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_2_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[2], ScriptParams[3], ScriptParams[0], ScriptParams[1], -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_2_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[2], ScriptParams[3], ScriptParams[0], ScriptParams[1], -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_3_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 5); + CMessages::AddMessageWithNumber(text, ScriptParams[3], ScriptParams[4], ScriptParams[0], ScriptParams[1], ScriptParams[2], -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_3_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 5); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[3], ScriptParams[4], ScriptParams[0], ScriptParams[1], ScriptParams[2], -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_3_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 5); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[3], ScriptParams[4], ScriptParams[0], ScriptParams[1], ScriptParams[2], -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_4_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 6); + CMessages::AddMessageWithNumber(text, ScriptParams[4], ScriptParams[5], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_4_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 6); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[4], ScriptParams[5], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_4_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 6); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[4], ScriptParams[5], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_5_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 7); + CMessages::AddMessageWithNumber(text, ScriptParams[5], ScriptParams[6], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], -1); + return 0; + } + case COMMAND_PRINT_WITH_5_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 7); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[5], ScriptParams[6], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], -1); + return 0; + } + case COMMAND_PRINT_WITH_5_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 7); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[5], ScriptParams[6], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], -1); + return 0; + } + case COMMAND_PRINT_WITH_6_NUMBERS: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CMessages::AddMessageWithNumber(text, ScriptParams[6], ScriptParams[7], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], ScriptParams[5]); + return 0; + } + case COMMAND_PRINT_WITH_6_NUMBERS_NOW: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CMessages::AddMessageJumpQWithNumber(text, ScriptParams[6], ScriptParams[7], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], ScriptParams[5]); + return 0; + } + case COMMAND_PRINT_WITH_6_NUMBERS_SOON: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CMessages::AddMessageSoonWithNumber(text, ScriptParams[6], ScriptParams[7], ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], ScriptParams[5]); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FOLLOW_CHAR_IN_FORMATION: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FOLLOW_CHAR_IN_FORMATION, pTargetPed); + pPed->SetFormation((eFormation)ScriptParams[2]); + return 0; + } + case COMMAND_PLAYER_MADE_PROGRESS: + CollectParameters(&m_nIp, 1); + CStats::ProgressMade += ScriptParams[0]; + return 0; + case COMMAND_SET_PROGRESS_TOTAL: + CollectParameters(&m_nIp, 1); + CStats::TotalProgressInGame = ScriptParams[0]; + return 0; + case COMMAND_REGISTER_JUMP_DISTANCE: + CollectParameters(&m_nIp, 1); + CStats::MaximumJumpDistance = Max(CStats::MaximumJumpDistance, *(float*)&ScriptParams[0]); + return 0; + case COMMAND_REGISTER_JUMP_HEIGHT: + CollectParameters(&m_nIp, 1); + CStats::MaximumJumpHeight = Max(CStats::MaximumJumpHeight, *(float*)&ScriptParams[0]); + return 0; + case COMMAND_REGISTER_JUMP_FLIPS: + CollectParameters(&m_nIp, 1); + CStats::MaximumJumpFlips = Max(CStats::MaximumJumpFlips, ScriptParams[0]); + return 0; + case COMMAND_REGISTER_JUMP_SPINS: + CollectParameters(&m_nIp, 1); + CStats::MaximumJumpSpins = Max(CStats::MaximumJumpSpins, ScriptParams[0]); + return 0; + case COMMAND_REGISTER_JUMP_STUNT: + CollectParameters(&m_nIp, 1); + CStats::BestStuntJump = Max(CStats::BestStuntJump, ScriptParams[0]); + return 0; + case COMMAND_REGISTER_UNIQUE_JUMP_FOUND: + ++CStats::NumberOfUniqueJumpsFound; + return 0; + case COMMAND_SET_UNIQUE_JUMPS_TOTAL: + CollectParameters(&m_nIp, 1); + CStats::TotalNumberOfUniqueJumps = ScriptParams[0]; + return 0; + case COMMAND_REGISTER_PASSENGER_DROPPED_OFF_TAXI: + ++CStats::PassengersDroppedOffWithTaxi; + return 0; + case COMMAND_REGISTER_MONEY_MADE_TAXI: + CollectParameters(&m_nIp, 1); + CStats::MoneyMadeWithTaxi += ScriptParams[0]; + return 0; + case COMMAND_REGISTER_MISSION_GIVEN: + ++CStats::MissionsGiven; + return 0; + case COMMAND_REGISTER_MISSION_PASSED: + { + char name[KEY_LENGTH_IN_SCRIPT]; + strncpy(name, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + strncpy(CStats::LastMissionPassedName, name, KEY_LENGTH_IN_SCRIPT); + ++CStats::MissionsPassed; + CStats::CheckPointReachedSuccessfully(); + return 0; + } + case COMMAND_SET_CHAR_RUNNING: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bIsRunning = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_REMOVE_ALL_SCRIPT_FIRES: + gFireManager.RemoveAllScriptFires(); + return 0; + case COMMAND_IS_FIRST_CAR_COLOUR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->m_currentColour1 == ScriptParams[1]); + return 0; + } + case COMMAND_IS_SECOND_CAR_COLOUR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->m_currentColour2 == ScriptParams[1]); + return 0; + } + case COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + if (!pPed) + printf("HAS_CHAR_BEEN_DAMAGED_BY_WEAPON - Character doesn't exist\n"); + UpdateCompareFlag(pPed && pPed->m_lastWepDam == ScriptParams[1]); + return 0; + } + case COMMAND_HAS_CAR_BEEN_DAMAGED_BY_WEAPON: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (!pVehicle) + printf("HAS_CAR_BEEN_DAMAGED_BY_WEAPON - Vehicle doesn't exist\n"); + UpdateCompareFlag(pVehicle && pVehicle->m_nLastWeaponDamage == ScriptParams[1]); + return 0; + } + case COMMAND_IS_CHAR_IN_CHARS_GROUP: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pLeader = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pPed); + script_assert(pLeader); + UpdateCompareFlag(pPed->m_leader == pLeader); + return 0; + } + default: + script_assert(0); + } + return -1; +} diff --git a/src/control/Script4.cpp b/src/control/Script4.cpp new file mode 100644 index 0000000..4e798be --- /dev/null +++ b/src/control/Script4.cpp @@ -0,0 +1,2178 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "AnimBlendAssociation.h" +#include "BulletInfo.h" +#include "CarAI.h" +#include "CarCtrl.h" +#include "CivilianPed.h" +#include "Cranes.h" +#include "DMAudio.h" +#include "Darkel.h" +#include "Explosion.h" +#include "Fire.h" +#include "Frontend.h" +#include "Garages.h" +#include "General.h" +#include "Heli.h" +#include "Hud.h" +#include "Messages.h" +#include "ParticleObject.h" +#include "PedRoutes.h" +#include "Phones.h" +#include "Pickups.h" +#include "Plane.h" +#include "Pools.h" +#include "Population.h" +#include "Radar.h" +#include "Record.h" +#include "RpAnimBlend.h" +#include "Rubbish.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "Streaming.h" +#include "TxdStore.h" +#include "User.h" +#include "WaterLevel.h" +#include "World.h" +#include "Zones.h" +#include "Wanted.h" + +int8 CRunningScript::ProcessCommands800To899(int32 command) +{ + CMatrix tmp_matrix; + switch (command) { + case COMMAND_IS_CHAR_IN_PLAYERS_GROUP: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pLeader = CWorld::Players[ScriptParams[1]].m_pPed; + script_assert(pPed); + script_assert(pLeader); + UpdateCompareFlag(pPed->m_leader == pLeader); + return 0; + } + case COMMAND_EXPLODE_CHAR_HEAD: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (pPed->m_nPedState == PED_DRIVING) { + pPed->SetDead(); + if (!pPed->IsPlayer()) + pPed->FlagToDestroyWhenNextProcessed(); + } + else if (CGame::nastyGame && pPed->IsPedInControl()) { + pPed->ApplyHeadShot(WEAPONTYPE_SNIPERRIFLE, pPed->GetNodePosition(PED_HEAD), true); + } + else { + pPed->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + return 0; + } + case COMMAND_EXPLODE_PLAYER_HEAD: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + if (CGame::nastyGame) { + pPed->ApplyHeadShot(WEAPONTYPE_SNIPERRIFLE, pPed->GetNodePosition(PED_HEAD), true); + } + else { + pPed->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + return 0; + } + case COMMAND_ANCHOR_BOAT: + { + CollectParameters(&m_nIp, 2); + CBoat* pBoat = (CBoat*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pBoat && pBoat->m_vehType == VEHICLE_TYPE_BOAT); + pBoat->m_bIsAnchored = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_ZONE_GROUP: + { + char zone[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, zone); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 2); + int zone_id = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (zone_id < 0) { + printf("Couldn't find zone - %s\n", zone); + return 0; + } + CTheZones::SetPedGroup(zone_id, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_START_CAR_FIRE: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = gFireManager.StartScriptFire(pVehicle->GetPosition(), pVehicle, 0.8f, 1); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_START_CHAR_FIRE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + ScriptParams[0] = gFireManager.StartScriptFire(pPed->GetPosition(), pPed, 0.8f, 1); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_AREA: + { + CollectParameters(&m_nIp, 5); + int handle = -1; + uint32 i = CPools::GetVehiclePool()->GetSize(); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float supX = *(float*)&ScriptParams[2]; + float supY = *(float*)&ScriptParams[3]; + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (ScriptParams[4] != pVehicle->GetModelIndex() && ScriptParams[4] >= 0) + continue; + if (pVehicle->VehicleCreatedBy != RANDOM_VEHICLE) + continue; + if (!pVehicle->IsWithinArea(infX, infY, supX, supY)) + continue; + handle = CPools::GetVehiclePool()->GetIndex(pVehicle); + pVehicle->VehicleCreatedBy = MISSION_VEHICLE; + ++CCarCtrl::NumMissionCars; + --CCarCtrl::NumRandomCars; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(handle, CLEANUP_CAR); + } + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_ZONE: + { + char zone[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, zone); + int zone_id = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (zone_id != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetZone(zone_id); + CollectParameters(&m_nIp, 1); + int handle = -1; + uint32 i = CPools::GetVehiclePool()->GetSize(); + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (ScriptParams[0] != pVehicle->GetModelIndex() && ScriptParams[0] >= 0) + continue; + if (pVehicle->VehicleCreatedBy != RANDOM_VEHICLE) + continue; + if (!CTheZones::PointLiesWithinZone(&pVehicle->GetPosition(), pZone)) + continue; + handle = CPools::GetVehiclePool()->GetIndex(pVehicle); + pVehicle->VehicleCreatedBy = MISSION_VEHICLE; + ++CCarCtrl::NumMissionCars; + --CCarCtrl::NumRandomCars; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(handle, CLEANUP_CAR); + } + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_HAS_RESPRAY_HAPPENED: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::HasResprayHappened(ScriptParams[0])); + return 0; + } + case COMMAND_SET_CAMERA_ZOOM: + { + CollectParameters(&m_nIp, 1); + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FOLLOWPED) + TheCamera.SetZoomValueFollowPedScript(ScriptParams[0]); + else if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_CAM_ON_A_STRING) + TheCamera.SetZoomValueCamStringScript(ScriptParams[0]); + return 0; + } + case COMMAND_CREATE_PICKUP_WITH_AMMO: + { + CollectParameters(&m_nIp, 6); + int16 model = ScriptParams[0]; + if (model < 0) + model = CTheScripts::UsedObjectArray[-model].index; + CVector pos = *(CVector*)&ScriptParams[3]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + CPickups::GetActualPickupIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CPickups::GenerateNewOne(pos, model, ScriptParams[1], ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CAR_RAM_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CCarAI::TellCarToRamOtherCar(pVehicle, pTarget); + return 0; + } + case COMMAND_SET_CAR_BLOCK_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CCarAI::TellCarToBlockOtherCar(pVehicle, pTarget); + return 0; + } + case COMMAND_SET_CHAR_OBJ_CATCH_TRAIN: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_CATCH_TRAIN); + return 0; + } +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_CATCH_TRAIN: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_CATCH_TRAIN); + return 0; +#endif + case COMMAND_SET_PLAYER_NEVER_GETS_TIRED: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + pPlayer->m_bInfiniteSprint = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_PLAYER_FAST_RELOAD: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + pPlayer->m_bFastReload = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CHAR_BLEEDING: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bPedIsBleeding = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CAR_FUNNY_SUSPENSION: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + // no action + return 0; + } + case COMMAND_SET_CAR_BIG_WHEELS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + CAutomobile* pCar = (CAutomobile*)pVehicle; + pCar->bBigWheels = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_FREE_RESPRAYS: + CollectParameters(&m_nIp, 1); + CGarages::SetFreeResprays(ScriptParams[0] != 0); + return 0; + case COMMAND_SET_PLAYER_VISIBLE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->bIsVisible = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CHAR_VISIBLE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bIsVisible = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_SET_CAR_VISIBLE: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bIsVisible = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_IS_AREA_OCCUPIED: + { + CollectParameters(&m_nIp, 11); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + int16 total; + CWorld::FindObjectsIntersectingCube(CVector(infX, infY, infZ), CVector(supX, supY, supZ), &total, 2, nil, + !!ScriptParams[6], !!ScriptParams[7], !!ScriptParams[8], !!ScriptParams[9], !!ScriptParams[10]); + UpdateCompareFlag(total > 0); + return 0; + } + case COMMAND_START_DRUG_RUN: + CPlane::CreateIncomingCesna(); + return 0; + case COMMAND_HAS_DRUG_RUN_BEEN_COMPLETED: + UpdateCompareFlag(CPlane::HasCesnaLanded()); + return 0; + case COMMAND_HAS_DRUG_PLANE_BEEN_SHOT_DOWN: + UpdateCompareFlag(CPlane::HasCesnaBeenDestroyed()); + return 0; + case COMMAND_SAVE_PLAYER_FROM_FIRES: + CollectParameters(&m_nIp, 1); + gFireManager.ExtinguishPoint(CWorld::Players[ScriptParams[0]].GetPos(), 3.0f); + return 0; + case COMMAND_DISPLAY_TEXT: + { + CollectParameters(&m_nIp, 2); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtX = *(float*)&ScriptParams[0]; + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtY = *(float*)&ScriptParams[1]; + uint16 len = CMessages::GetWideStringLength(text); + for (uint16 i = 0; i < len; i++) + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_Text[i] = text[i]; + for (uint16 i = len; i < SCRIPT_TEXT_MAX_LENGTH; i++) + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_Text[i] = 0; + ++CTheScripts::NumberOfIntroTextLinesThisFrame; + return 0; + } + case COMMAND_SET_TEXT_SCALE: + { + CollectParameters(&m_nIp, 2); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fScaleX = *(float*)&ScriptParams[0]; + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fScaleY = *(float*)&ScriptParams[1]; + return 0; + } + case COMMAND_SET_TEXT_COLOUR: + { + CollectParameters(&m_nIp, 4); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_sColor = + CRGBA(ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3]); + return 0; + } + case COMMAND_SET_TEXT_JUSTIFY: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bJustify = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_CENTRE: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bCentered = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_WRAPX: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fWrapX = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SET_TEXT_CENTRE_SIZE: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fCenterSize = *(float*)&ScriptParams[0]; + return 0; + } + case COMMAND_SET_TEXT_BACKGROUND: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bBackground = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_BACKGROUND_COLOUR: + { + CollectParameters(&m_nIp, 4); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_sBackgroundColor = + CRGBA(ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3]); + return 0; + } + case COMMAND_SET_TEXT_BACKGROUND_ONLY_TEXT: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bBackgroundOnly = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_PROPORTIONAL: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bTextProportional = (ScriptParams[0] != 0); + return 0; + } + case COMMAND_SET_TEXT_FONT: + { + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_nFont = ScriptParams[0]; + return 0; + } + case COMMAND_INDUSTRIAL_PASSED: + CStats::IndustrialPassed = true; + DMAudio.PlayRadioAnnouncement(STREAMED_SOUND_ANNOUNCE_COMMERCIAL_OPEN); + return 0; + case COMMAND_COMMERCIAL_PASSED: + CStats::CommercialPassed = true; + DMAudio.PlayRadioAnnouncement(STREAMED_SOUND_ANNOUNCE_SUBURBAN_OPEN); + return 0; + case COMMAND_SUBURBAN_PASSED: + CStats::SuburbanPassed = true; + return 0; + case COMMAND_ROTATE_OBJECT: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + float heading = LimitAngleOnCircle( + RADTODEG(Atan2(-pObject->GetForward().x, pObject->GetForward().y))); + float headingTarget = *(float*)&ScriptParams[1]; +#ifdef FIX_BUGS + float rotateBy = *(float*)&ScriptParams[2] * CTimer::GetTimeStepFix(); +#else + float rotateBy = *(float*)&ScriptParams[2]; +#endif + if (headingTarget == heading) { // using direct comparasion here is fine + UpdateCompareFlag(true); + return 0; + } + float angleClockwise = LimitAngleOnCircle(headingTarget - heading); + float angleCounterclockwise = LimitAngleOnCircle(heading - headingTarget); + float newHeading; + if (angleClockwise < angleCounterclockwise) + newHeading = rotateBy < angleClockwise ? heading + rotateBy : headingTarget; + else + newHeading = rotateBy < angleCounterclockwise ? heading - rotateBy : headingTarget; + bool obstacleInPath = false; + if (ScriptParams[3]) { + CVector pos = pObject->GetPosition(); + tmp_matrix.SetRotateZ(DEGTORAD(newHeading)); + tmp_matrix.GetPosition() += pos; + CColModel* pColModel = pObject->GetColModel(); + CVector cp1 = tmp_matrix * pColModel->boundingBox.min; + CVector cp2 = tmp_matrix * CVector(pColModel->boundingBox.max.x, pColModel->boundingBox.min.y, pColModel->boundingBox.min.z); + CVector cp3 = tmp_matrix * CVector(pColModel->boundingBox.min.x, pColModel->boundingBox.max.y, pColModel->boundingBox.min.z); + CVector cp4 = tmp_matrix * CVector(pColModel->boundingBox.min.x, pColModel->boundingBox.min.y, pColModel->boundingBox.max.z); + int16 collisions; + CWorld::FindObjectsIntersectingAngledCollisionBox(pColModel->boundingBox, tmp_matrix, pos, + Min(cp1.x, Min(cp2.x, Min(cp3.x, cp4.x))), + Min(cp1.y, Min(cp2.y, Min(cp3.y, cp4.y))), + Max(cp1.x, Max(cp2.x, Max(cp3.x, cp4.x))), + Max(cp1.y, Max(cp2.y, Max(cp3.y, cp4.y))), + &collisions, 2, nil, false, true, true, false, false); + if (collisions > 0) + obstacleInPath = true; + } + if (obstacleInPath) { + UpdateCompareFlag(true); + return 0; + } + pObject->SetHeading(DEGTORAD(newHeading)); + pObject->GetMatrix().UpdateRW(); + pObject->UpdateRwFrame(); + UpdateCompareFlag(newHeading == headingTarget); // using direct comparasion here is fine + return 0; + } + case COMMAND_SLIDE_OBJECT: + { + CollectParameters(&m_nIp, 8); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CVector pos = pObject->GetPosition(); + CVector posTarget = *(CVector*)&ScriptParams[1]; +#ifdef FIX_BUGS + CVector slideBy = *(CVector*)&ScriptParams[4] * CTimer::GetTimeStepFix(); +#else + CVector slideBy = *(CVector*)&ScriptParams[4]; +#endif + if (posTarget == pos) { // using direct comparasion here is fine + UpdateCompareFlag(true); + return 0; + } + CVector posDiff = pos - posTarget; + CVector newPosition; + if (posDiff.x < 0) + newPosition.x = -posDiff.x < slideBy.x ? posTarget.x : pos.x + slideBy.x; + else + newPosition.x = posDiff.x < slideBy.x ? posTarget.x : pos.x - slideBy.x; + if (posDiff.y < 0) + newPosition.y = -posDiff.y < slideBy.y ? posTarget.y : pos.y + slideBy.y; + else + newPosition.y = posDiff.y < slideBy.y ? posTarget.y : pos.y - slideBy.y; + if (posDiff.z < 0) + newPosition.z = -posDiff.z < slideBy.z ? posTarget.z : pos.z + slideBy.z; + else + newPosition.z = posDiff.z < slideBy.z ? posTarget.z : pos.z - slideBy.z; + bool obstacleInPath = false; + if (ScriptParams[7]) { + tmp_matrix = pObject->GetMatrix(); + tmp_matrix.GetPosition() = newPosition; + CColModel* pColModel = pObject->GetColModel(); + CVector cp1 = tmp_matrix * pColModel->boundingBox.min; + CVector cp2 = tmp_matrix * CVector(pColModel->boundingBox.max.x, pColModel->boundingBox.min.y, pColModel->boundingBox.min.z); + CVector cp3 = tmp_matrix * CVector(pColModel->boundingBox.min.x, pColModel->boundingBox.max.y, pColModel->boundingBox.min.z); + CVector cp4 = tmp_matrix * CVector(pColModel->boundingBox.min.x, pColModel->boundingBox.min.y, pColModel->boundingBox.max.z); + int16 collisions; + CWorld::FindObjectsIntersectingAngledCollisionBox(pColModel->boundingBox, tmp_matrix, newPosition, + Min(cp1.x, Min(cp2.x, Min(cp3.x, cp4.x))), + Min(cp1.y, Min(cp2.y, Min(cp3.y, cp4.y))), + Max(cp1.x, Max(cp2.x, Max(cp3.x, cp4.x))), + Max(cp1.y, Max(cp2.y, Max(cp3.y, cp4.y))), + &collisions, 2, nil, false, true, true, false, false); + if (collisions > 0) + obstacleInPath = true; + } + if (obstacleInPath) { + UpdateCompareFlag(true); + return 0; + } + pObject->Teleport(newPosition); + UpdateCompareFlag(newPosition == posTarget); // using direct comparasion here is fine + return 0; + } + case COMMAND_REMOVE_CHAR_ELEGANTLY: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + if (pPed && pPed->CharCreatedBy == MISSION_CHAR){ + CWorld::RemoveReferencesToDeletedObject(pPed); + if (pPed->bInVehicle){ + if (pPed->m_pMyVehicle){ + if (pPed == pPed->m_pMyVehicle->pDriver){ + pPed->m_pMyVehicle->RemoveDriver(); + pPed->m_pMyVehicle->SetStatus(STATUS_ABANDONED); + if (pPed->m_pMyVehicle->m_nDoorLock == CARLOCK_LOCKED_INITIALLY) + pPed->m_pMyVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + if (pPed->m_nPedType == PEDTYPE_COP && pPed->m_pMyVehicle->IsLawEnforcementVehicle()) + pPed->m_pMyVehicle->ChangeLawEnforcerState(0); + }else{ + pPed->m_pMyVehicle->RemovePassenger(pPed); + } + } + delete pPed; + --CPopulation::ms_nTotalMissionPeds; + }else{ + pPed->CharCreatedBy = RANDOM_CHAR; + pPed->bRespondsToThreats = true; + pPed->bScriptObjectiveCompleted = false; + pPed->ClearLeader(); + --CPopulation::ms_nTotalMissionPeds; + pPed->bFadeOut = true; + } + } + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.RemoveEntityFromList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_SET_CHAR_STAY_IN_SAME_PLACE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bKindaStayInSamePlace = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_IS_NASTY_GAME: + UpdateCompareFlag(CGame::nastyGame); + return 0; + case COMMAND_UNDRESS_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + char name[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, name); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + name[i] = tolower(name[i]); + int mi = pPed->GetModelIndex(); + pPed->DeleteRwObject(); + if (pPed->IsPlayer()) + mi = 0; + CStreaming::RequestSpecialModel(mi, name, STREAMFLAGS_DEPENDENCY | STREAMFLAGS_SCRIPTOWNED); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CWorld::Remove(pPed); + return 0; + } + case COMMAND_DRESS_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + int mi = pPed->GetModelIndex(); + pPed->m_modelIndex = -1; + pPed->SetModelIndex(mi); + CWorld::Add(pPed); + return 0; + } + case COMMAND_START_CHASE_SCENE: + CollectParameters(&m_nIp, 1); + CTimer::Suspend(); + CStreaming::DeleteAllRwObjects(); + CRecordDataForChase::StartChaseScene(*(float*)&ScriptParams[0]); + CTimer::Resume(); + return 0; + case COMMAND_STOP_CHASE_SCENE: + CRecordDataForChase::CleanUpChaseScene(); + return 0; + case COMMAND_IS_EXPLOSION_IN_AREA: + { + CollectParameters(&m_nIp, 7); + float infX = *(float*)&ScriptParams[1]; + float infY = *(float*)&ScriptParams[2]; + float infZ = *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[5]; + float supZ = *(float*)&ScriptParams[6]; + if (infX > supX) { + infX = *(float*)&ScriptParams[4]; + supX = *(float*)&ScriptParams[1]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[5]; + supY = *(float*)&ScriptParams[2]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + UpdateCompareFlag(CExplosion::TestForExplosionInArea((eExplosionType)ScriptParams[0], + infX, supX, infY, supY, infZ, supZ)); + return 0; + } + case COMMAND_IS_EXPLOSION_IN_ZONE: + { + CollectParameters(&m_nIp, 1); + char zone[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, zone); + int zone_id = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (zone_id != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetZone(zone_id); + UpdateCompareFlag(CExplosion::TestForExplosionInArea((eExplosionType)ScriptParams[0], + pZone->minx, pZone->maxx, pZone->miny, pZone->maxy, pZone->minz, pZone->maxz)); + return 0; + } + case COMMAND_START_DRUG_DROP_OFF: + CPlane::CreateDropOffCesna(); + return 0; + case COMMAND_HAS_DROP_OFF_PLANE_BEEN_SHOT_DOWN: + UpdateCompareFlag(CPlane::HasDropOffCesnaBeenShotDown()); + return 0; + case COMMAND_FIND_DROP_OFF_PLANE_COORDINATES: + { + CVector pos = CPlane::FindDropOffCesnaCoordinates(); + *(CVector*)&ScriptParams[0] = pos; + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_CREATE_FLOATING_PACKAGE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y) + PICKUP_PLACEMENT_OFFSET; + ScriptParams[0] = CPickups::GenerateNewOne(pos, MI_FLOATPACKAGE1, PICKUP_FLOATINGPACKAGE, 0); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_PLACE_OBJECT_RELATIVE_TO_CAR: + { + CollectParameters(&m_nIp, 5); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + CVector offset = *(CVector*)&ScriptParams[2]; + CPhysical::PlacePhysicalRelativeToOtherPhysical(pVehicle, pObject, offset); + return 0; + } + case COMMAND_MAKE_OBJECT_TARGETTABLE: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CPlayerPed* pPlayerPed = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + script_assert(pPlayerPed); + pPlayerPed->MakeObjectTargettable(ScriptParams[0]); + return 0; + } + case COMMAND_ADD_ARMOUR_TO_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPlayerPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPlayerPed); + pPlayerPed->m_fArmour = Clamp(pPlayerPed->m_fArmour + ScriptParams[1], 0.0f, 100.0f); + return 0; + } + case COMMAND_ADD_ARMOUR_TO_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_fArmour = Clamp(pPed->m_fArmour + ScriptParams[1], 0.0f, 100.0f); + return 0; + } + case COMMAND_OPEN_GARAGE: + { + CollectParameters(&m_nIp, 1); + CGarages::OpenGarage(ScriptParams[0]); + return 0; + } + case COMMAND_CLOSE_GARAGE: + { + CollectParameters(&m_nIp, 1); + CGarages::CloseGarage(ScriptParams[0]); + return 0; + } + case COMMAND_WARP_CHAR_FROM_CAR_TO_COORD: + { + CollectParameters(&m_nIp, 4); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + if (pPed->bInVehicle){ + if (pPed->m_pMyVehicle->bIsBus) + pPed->bRenderPedInCar = true; + if (pPed->m_pMyVehicle->pDriver == pPed){ + pPed->m_pMyVehicle->RemoveDriver(); + pPed->m_pMyVehicle->SetStatus(STATUS_ABANDONED); + pPed->m_pMyVehicle->bEngineOn = false; + pPed->m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + }else{ + pPed->m_pMyVehicle->RemovePassenger(pPed); + } + pPed->m_pMyVehicle->SetMoveSpeed(0.0f, 0.0f, -0.00001f); + pPed->m_pMyVehicle->SetTurnSpeed(0.0f, 0.0f, 0.0f); + } + pPed->bInVehicle = false; + pPed->m_pMyVehicle = nil; + pPed->SetPedState(PED_IDLE); + pPed->m_nLastPedState = PED_NONE; + pPed->bUsesCollision = true; + pPed->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pPed->AddWeaponModel(CWeaponInfo::GetWeaponInfo(pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType)->m_nModelId); + pPed->RemoveInCarAnims(); + if (pPed->m_pVehicleAnim) + pPed->m_pVehicleAnim->blendDelta = -1000.0f; + pPed->m_pVehicleAnim = nil; + pPed->RestartNonPartialAnims(); + pPed->SetMoveState(PEDMOVE_NONE); + CAnimManager::BlendAnimation(pPed->GetClump(), pPed->m_animGroup, ANIM_STD_IDLE, 100.0f); + pos.z += pPed->GetDistanceFromCentreOfMassToBaseOfModel(); + pPed->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, pPed); + return 0; + } + case COMMAND_SET_VISIBILITY_OF_CLOSEST_OBJECT_OF_TYPE: + { + CollectParameters(&m_nIp, 6); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float range = *(float*)&ScriptParams[3]; + int mi = ScriptParams[4] < 0 ? CTheScripts::UsedObjectArray[-ScriptParams[4]].index : ScriptParams[4]; + int16 total; + CEntity* apEntities[16]; + CWorld::FindObjectsOfTypeInRange(mi, pos, range, true, &total, 16, apEntities, true, false, false, true, true); + if (total == 0) + CWorld::FindObjectsOfTypeInRangeSectorList(mi, CWorld::GetBigBuildingList(LEVEL_GENERIC), pos, range, true, &total, 16, apEntities); + if (total == 0) + CWorld::FindObjectsOfTypeInRangeSectorList(mi, CWorld::GetBigBuildingList(CTheZones::FindZoneForPoint(pos)), pos, range, true, &total, 16, apEntities); + CEntity* pClosestEntity = nil; + float min_dist = 2.0f * range; + for (int i = 0; i < total; i++) { + float dist = (apEntities[i]->GetPosition() - pos).Magnitude(); + if (dist < min_dist) { + min_dist = dist; + pClosestEntity = apEntities[i]; + } + } + if (pClosestEntity) { + pClosestEntity->bIsVisible = (ScriptParams[5] != 0); + CTheScripts::AddToInvisibilitySwapArray(pClosestEntity, ScriptParams[5] != 0); + } + return 0; + } + case COMMAND_HAS_CHAR_SPOTTED_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + UpdateCompareFlag(pPed->OurPedCanSeeThisOne(pTarget)); + return 0; + } + case COMMAND_SET_CHAR_OBJ_HAIL_TAXI: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_HAIL_TAXI); + return 0; + } + case COMMAND_HAS_OBJECT_BEEN_DAMAGED: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + UpdateCompareFlag(pObject->bRenderDamaged || !pObject->bIsVisible); + return 0; + } + case COMMAND_START_KILL_FRENZY_HEADSHOT: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CDarkel::StartFrenzy((eWeaponType)ScriptParams[0], ScriptParams[1], ScriptParams[2], + ScriptParams[3], text, ScriptParams[4], ScriptParams[5], + ScriptParams[6], ScriptParams[7] != 0, true); + return 0; + } + case COMMAND_ACTIVATE_MILITARY_CRANE: + { + CollectParameters(&m_nIp, 10); + float infX = *(float*)&ScriptParams[2]; + float infY = *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[4]; + supX = *(float*)&ScriptParams[2]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[5]; + supY = *(float*)&ScriptParams[3]; + } + CCranes::ActivateCrane(infX, supX, infY, supY, + *(float*)&ScriptParams[6], *(float*)&ScriptParams[7], *(float*)&ScriptParams[8], + DEGTORAD(*(float*)&ScriptParams[9]), false, true, + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1]); + return 0; + } + case COMMAND_WARP_PLAYER_INTO_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicle); + pPed->WarpPedIntoCar(pVehicle); + return 0; + } + case COMMAND_WARP_CHAR_INTO_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, pVehicle); + pPed->WarpPedIntoCar(pVehicle); + return 0; + } + //case COMMAND_SWITCH_CAR_RADIO: + //case COMMAND_SET_AUDIO_STREAM: + case COMMAND_PRINT_WITH_2_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 4); + CMessages::AddBigMessageWithNumber(text, ScriptParams[2], ScriptParams[3] - 1, ScriptParams[0], ScriptParams[1], -1, -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_3_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 5); + CMessages::AddBigMessageWithNumber(text, ScriptParams[3], ScriptParams[4] - 1, ScriptParams[0], ScriptParams[1], ScriptParams[2], -1, -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_4_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 6); + CMessages::AddBigMessageWithNumber(text, ScriptParams[4], ScriptParams[5] - 1, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], -1, -1); + return 0; + } + case COMMAND_PRINT_WITH_5_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 7); + CMessages::AddBigMessageWithNumber(text, ScriptParams[5], ScriptParams[6] - 1, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], -1); + return 0; + } + case COMMAND_PRINT_WITH_6_NUMBERS_BIG: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 8); + CMessages::AddBigMessageWithNumber(text, ScriptParams[6], ScriptParams[7] - 1, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], ScriptParams[4], ScriptParams[5]); + return 0; + } + case COMMAND_SET_CHAR_WAIT_STATE: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->SetWaitState((eWaitState)ScriptParams[1], ScriptParams[2] >= 0 ? &ScriptParams[2] : nil); + return 0; + } + case COMMAND_SET_CAMERA_BEHIND_PLAYER: + TheCamera.SetCameraDirectlyBehindForFollowPed_CamOnAString(); + return 0; + case COMMAND_SET_MOTION_BLUR: + CollectParameters(&m_nIp, 1); + TheCamera.SetMotionBlur(0, 0, 0, 0, ScriptParams[0]); + return 0; + case COMMAND_PRINT_STRING_IN_STRING: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* string = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 2); + CMessages::AddMessageWithString(text, ScriptParams[0], ScriptParams[1], string); + return 0; + } + case COMMAND_CREATE_RANDOM_CHAR: + { + CollectParameters(&m_nIp, 3); + CZoneInfo zoneinfo; + CTheZones::GetZoneInfoForTimeOfDay(&CWorld::Players[CWorld::PlayerInFocus].GetPos(), &zoneinfo); + int mi; + ePedType pedtype = PEDTYPE_COP; + int attempt = 0; + while (pedtype != PEDTYPE_CIVMALE && pedtype != PEDTYPE_CIVFEMALE && attempt < 5) { + mi = CPopulation::ChooseCivilianOccupation(zoneinfo.pedGroup); + if (CModelInfo::GetModelInfo(mi)->GetRwObject()) + pedtype = ((CPedModelInfo*)(CModelInfo::GetModelInfo(mi)))->m_pedType; + attempt++; + } + if (!CModelInfo::GetModelInfo(mi)->GetRwObject()) { + mi = MI_MALE01; + pedtype = ((CPedModelInfo*)(CModelInfo::GetModelInfo(mi)))->m_pedType; + } + CPed* ped = new CCivilianPed(pedtype, mi); + ped->CharCreatedBy = MISSION_CHAR; + ped->bRespondsToThreats = false; + ped->bAllowMedicsToReviveMe = false; + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += 1.0f; + ped->SetPosition(pos); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + CWorld::Add(ped); + ped->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pos); + CPopulation::ms_nTotalMissionPeds++; + ScriptParams[0] = CPools::GetPedPool()->GetIndex(ped); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_SET_CHAR_OBJ_STEAL_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_STEAL_ANY_CAR); + return 0; + } + case COMMAND_SET_2_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, nil, nil, nil, nil); + return 0; + } + case COMMAND_SET_2_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, nil, nil, nil, nil); + return 0; + } + case COMMAND_SET_3_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, text3, nil, nil, nil); + return 0; + } + case COMMAND_SET_3_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, text3, nil, nil, nil); + return 0; + } + case COMMAND_SET_4_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, text3, text4, nil, nil); + return 0; + } + case COMMAND_SET_4_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, text3, text4, nil, nil); + return 0; + } + case COMMAND_IS_SNIPER_BULLET_IN_AREA: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + UpdateCompareFlag(CBulletInfo::TestForSniperBullet(infX, supX, infY, supY, infZ, supZ)); + return 0; + } + case COMMAND_GIVE_PLAYER_DETONATOR: + CGarages::GivePlayerDetonator(); + return 0; +#ifdef GTA_SCRIPT_COLLECTIVE + case COMMAND_SET_COLL_OBJ_STEAL_ANY_CAR: + CollectParameters(&m_nIp, 1); + CTheScripts::SetObjectiveForAllPedsInCollective(ScriptParams[0], OBJECTIVE_STEAL_ANY_CAR); + return 0; +#endif + case COMMAND_SET_OBJECT_VELOCITY: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->SetMoveSpeed(*(CVector*)&ScriptParams[1] * METERS_PER_SECOND_TO_GAME_SPEED); + return 0; + } + case COMMAND_SET_OBJECT_COLLISION: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->bUsesCollision = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_IS_ICECREAM_JINGLE_ON: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + // Adding this check to correspond to command name. + // All original game scripts always assume that the vehicle is actually Mr. Whoopee, + // but maybe there are mods that use it as "is alarm activated"? + script_assert(pVehicle->GetModelIndex() == MI_MRWHOOP); + UpdateCompareFlag(pVehicle->m_bSirenOrAlarm); + return 0; + } + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands900To999(int32 command) +{ + char str[52]; + char onscreen_str[KEY_LENGTH_IN_SCRIPT]; + switch (command) { + case COMMAND_PRINT_STRING_IN_STRING_NOW: + { + wchar* source = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* pstr = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 2); + CMessages::AddMessageJumpQWithString(source, ScriptParams[0], ScriptParams[1], pstr); + return 0; + } + //case COMMAND_PRINT_STRING_IN_STRING_SOON: + case COMMAND_SET_5_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text5 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, text3, text4, text5, nil); + return 0; + } + case COMMAND_SET_5_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text5 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, text3, text4, text5, nil); + return 0; + } + case COMMAND_SET_6_REPEATED_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text5 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text6 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_Repeatedly(ScriptParams[0], text1, text2, text3, text4, text5, text6); + return 0; + } + case COMMAND_SET_6_PHONE_MESSAGES: + { + CollectParameters(&m_nIp, 1); + wchar* text1 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text2 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text3 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text4 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text5 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + wchar* text6 = CTheScripts::GetTextByKeyFromScript(&m_nIp); + gPhoneInfo.SetPhoneMessage_JustOnce(ScriptParams[0], text1, text2, text3, text4, text5, text6); + return 0; + } + case COMMAND_IS_POINT_OBSCURED_BY_A_MISSION_ENTITY: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0] - *(float*)&ScriptParams[3]; + float supX = *(float*)&ScriptParams[0] + *(float*)&ScriptParams[3]; + float infY = *(float*)&ScriptParams[1] - *(float*)&ScriptParams[4]; + float supY = *(float*)&ScriptParams[1] + *(float*)&ScriptParams[4]; + float infZ = *(float*)&ScriptParams[2] - *(float*)&ScriptParams[5]; + float supZ = *(float*)&ScriptParams[2] + *(float*)&ScriptParams[5]; + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + if (infZ > supZ) { + float tmp = infZ; + infZ = supZ; + supZ = tmp; + } + int16 total; + CWorld::FindMissionEntitiesIntersectingCube(CVector(infX, infY, infZ), CVector(supX, supY, supZ), &total, 2, nil, true, true, true); + UpdateCompareFlag(total > 0); + return 0; + } + case COMMAND_LOAD_ALL_MODELS_NOW: + CTimer::Stop(); + CStreaming::LoadAllRequestedModels(false); + CTimer::Update(); + return 0; + case COMMAND_ADD_TO_OBJECT_VELOCITY: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->SetMoveSpeed(pObject->GetMoveSpeed() + METERS_PER_SECOND_TO_GAME_SPEED * *(CVector*)&ScriptParams[1]); + return 0; + } + case COMMAND_DRAW_SPRITE: + { + CollectParameters(&m_nIp, 9); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_bIsUsed = true; + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_nTextureId = ScriptParams[0] - 1; + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_sRect = CRect( + *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], *(float*)&ScriptParams[1] + *(float*)&ScriptParams[3], *(float*)&ScriptParams[2] + *(float*)&ScriptParams[4]); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_sColor = CRGBA(ScriptParams[5], ScriptParams[6], ScriptParams[7], ScriptParams[8]); + CTheScripts::NumberOfIntroRectanglesThisFrame++; + return 0; + } + case COMMAND_DRAW_RECT: + { + CollectParameters(&m_nIp, 8); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_bIsUsed = true; + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_nTextureId = -1; + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_sRect = CRect( + *(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[0] + *(float*)&ScriptParams[2], *(float*)&ScriptParams[1] + *(float*)&ScriptParams[3]); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_sColor = CRGBA(ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7]); + CTheScripts::NumberOfIntroRectanglesThisFrame++; + return 0; + } + case COMMAND_LOAD_SPRITE: + { + CollectParameters(&m_nIp, 1); + strncpy(str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + str[i] = tolower(str[i]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + int slot = CTxdStore::FindTxdSlot("script"); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(slot); + CTheScripts::ScriptSprites[ScriptParams[0] - 1].SetTexture(str); + CTxdStore::PopCurrentTxd(); + return 0; + } + case COMMAND_LOAD_TEXTURE_DICTIONARY: + { + strcpy(str, "models\\"); + strncpy(str + sizeof("models\\"), (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + strcat(str, ".txd"); + m_nIp += KEY_LENGTH_IN_SCRIPT; + int slot = CTxdStore::FindTxdSlot("script"); + if (slot == -1) + slot = CTxdStore::AddTxdSlot("script"); + CTxdStore::LoadTxd(slot, str); + CTxdStore::AddRef(slot); + return 0; + } + case COMMAND_REMOVE_TEXTURE_DICTIONARY: + { + for (int i = 0; i < ARRAY_SIZE(CTheScripts::ScriptSprites); i++) + CTheScripts::ScriptSprites[i].Delete(); + CTxdStore::RemoveTxd(CTxdStore::FindTxdSlot("script")); + return 0; + } + case COMMAND_SET_OBJECT_DYNAMIC: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + if (ScriptParams[1]) { + if (pObject->bIsStatic) { + pObject->SetIsStatic(false); + pObject->AddToMovingList(); + } + } + else { + if (!pObject->bIsStatic) { + pObject->SetIsStatic(true); + pObject->RemoveFromMovingList(); + } + } + return 0; + } + case COMMAND_SET_CHAR_ANIM_SPEED: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CAnimBlendAssociation* pAssoc = RpAnimBlendClumpGetFirstAssociation(pPed->GetClump()); + if (pAssoc) + pAssoc->speed = *(float*)&ScriptParams[1]; + return 0; + } + case COMMAND_PLAY_MISSION_PASSED_TUNE: + { + CollectParameters(&m_nIp, 1); + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.PlayFrontEndTrack(ScriptParams[0] + STREAMED_SOUND_MISSION_COMPLETED - 1, FALSE); + return 0; + } + case COMMAND_CLEAR_AREA: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CWorld::ClearExcitingStuffFromArea(pos, *(float*)&ScriptParams[3], ScriptParams[4]); + return 0; + } + case COMMAND_FREEZE_ONSCREEN_TIMER: + CollectParameters(&m_nIp, 1); + CUserDisplay::OnscnTimer.m_bDisabled = ScriptParams[0] != 0; + return 0; + case COMMAND_SWITCH_CAR_SIREN: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->m_bSirenOrAlarm = ScriptParams[1] != 0; + return 0; + } + case COMMAND_SWITCH_PED_ROADS_ON_ANGLED: + { + CollectParameters(&m_nIp, 7); + ThePaths.SwitchRoadsInAngledArea(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5], *(float*)&ScriptParams[6], 0, 1); + return 0; + } + case COMMAND_SWITCH_PED_ROADS_OFF_ANGLED: + CollectParameters(&m_nIp, 7); + ThePaths.SwitchRoadsInAngledArea(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5], *(float*)&ScriptParams[6], 0, 0); + return 0; + case COMMAND_SWITCH_ROADS_ON_ANGLED: + CollectParameters(&m_nIp, 7); + ThePaths.SwitchRoadsInAngledArea(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5], *(float*)&ScriptParams[6], 1, 1); + return 0; + case COMMAND_SWITCH_ROADS_OFF_ANGLED: + CollectParameters(&m_nIp, 7); + ThePaths.SwitchRoadsInAngledArea(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2], + *(float*)&ScriptParams[3], *(float*)&ScriptParams[4], *(float*)&ScriptParams[5], *(float*)&ScriptParams[6], 1, 0); + return 0; + case COMMAND_SET_CAR_WATERTIGHT: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + CAutomobile* pCar = (CAutomobile*)pVehicle; + pCar->bWaterTight = ScriptParams[1] != 0; + return 0; + } + case COMMAND_ADD_MOVING_PARTICLE_EFFECT: + { + CollectParameters(&m_nIp, 12); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float size = Max(0.0f, *(float*)&ScriptParams[7]); + eParticleObjectType type = (eParticleObjectType)ScriptParams[0]; + RwRGBA color; + if (type == POBJECT_SMOKE_TRAIL){ + color.alpha = -1; + color.red = ScriptParams[8]; + color.green = ScriptParams[9]; + color.blue = ScriptParams[10]; + }else{ + color.alpha = color.red = color.blue = color.green = 0; + } + CVector target = *(CVector*)&ScriptParams[4]; + CParticleObject::AddObject(type, pos, target, size, ScriptParams[11], color, 1); + return 0; + } + case COMMAND_SET_CHAR_CANT_BE_DRAGGED_OUT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bDontDragMeOutCar = ScriptParams[1] != 0; + return 0; + } + case COMMAND_TURN_CAR_TO_FACE_COORD: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + const CVector& pos = pVehicle->GetPosition(); + float heading = CGeneral::GetATanOfXY(pos.x - *(float*)&ScriptParams[1], pos.y - *(float*)&ScriptParams[2]) + HALFPI; + if (heading > TWOPI) + heading -= TWOPI; + pVehicle->SetHeading(heading); + return 0; + } + case COMMAND_IS_CRANE_LIFTING_CAR: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[2]); + UpdateCompareFlag(CCranes::IsThisCarPickedUp(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], pVehicle)); + return 0; + } + case COMMAND_DRAW_SPHERE: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + C3dMarkers::PlaceMarkerSet((uintptr)this + m_nIp, MARKERTYPE_CYLINDER, pos, *(float*)&ScriptParams[3], + SPHERE_MARKER_R, SPHERE_MARKER_G, SPHERE_MARKER_B, SPHERE_MARKER_A, + SPHERE_MARKER_PULSE_PERIOD, SPHERE_MARKER_PULSE_FRACTION, 0); + return 0; + } + case COMMAND_SET_CAR_STATUS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->SetStatus(ScriptParams[1]); + return 0; + } + case COMMAND_IS_CHAR_MALE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->m_nPedType != PEDTYPE_CIVFEMALE && pPed->m_nPedType != PEDTYPE_PROSTITUTE); + return 0; + } + case COMMAND_SCRIPT_NAME: + { + strncpy(str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + str[i] = tolower(str[i]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + strncpy(m_abScriptName, str, KEY_LENGTH_IN_SCRIPT); + return 0; + } + case COMMAND_CHANGE_GARAGE_TYPE_WITH_CAR_MODEL: + { + CollectParameters(&m_nIp, 3); + CGarages::ChangeGarageType(ScriptParams[0], ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_FIND_DRUG_PLANE_COORDINATES: + *(CVector*)&ScriptParams[0] = CPlane::FindDrugPlaneCoordinates(); + StoreParameters(&m_nIp, 3); + return 0; + case COMMAND_SAVE_INT_TO_DEBUG_FILE: + // TODO: implement something here + CollectParameters(&m_nIp, 1); + return 0; + case COMMAND_SAVE_FLOAT_TO_DEBUG_FILE: + CollectParameters(&m_nIp, 1); + return 0; + case COMMAND_SAVE_NEWLINE_TO_DEBUG_FILE: + return 0; + case COMMAND_POLICE_RADIO_MESSAGE: + CollectParameters(&m_nIp, 3); + DMAudio.PlaySuspectLastSeen(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2]); + return 0; + case COMMAND_SET_CAR_STRONG: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bTakeLessDamage = ScriptParams[1] != 0; + return 0; + } + case COMMAND_REMOVE_ROUTE: + CollectParameters(&m_nIp, 1); + CRouteNode::RemoveRoute(ScriptParams[0]); + return 0; + case COMMAND_SWITCH_RUBBISH: + CollectParameters(&m_nIp, 1); + CRubbish::SetVisibility(ScriptParams[0] != 0);; + return 0; + case COMMAND_REMOVE_PARTICLE_EFFECTS_IN_AREA: + { + CollectParameters(&m_nIp, 6); + float x1 = *(float*)&ScriptParams[0]; + float y1 = *(float*)&ScriptParams[1]; + float z1 = *(float*)&ScriptParams[2]; + float x2 = *(float*)&ScriptParams[3]; + float y2 = *(float*)&ScriptParams[4]; + float z2 = *(float*)&ScriptParams[5]; + CParticleObject* tmp = CParticleObject::pCloseListHead; + while (tmp) { + CParticleObject* next = tmp->m_pNext; + if (tmp->IsWithinArea(x1, y1, z1, x2, y2, z2)) + tmp->RemoveObject(); + tmp = next; + } + tmp = CParticleObject::pFarListHead; + while (tmp) { + CParticleObject* next = tmp->m_pNext; + if (tmp->IsWithinArea(x1, y1, z1, x2, y2, z2)) + tmp->RemoveObject(); + tmp = next; + } + return 0; + } + case COMMAND_SWITCH_STREAMING: + CollectParameters(&m_nIp, 1); + CStreaming::ms_disableStreaming = ScriptParams[0] == 0; + return 0; + case COMMAND_IS_GARAGE_OPEN: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::IsGarageOpen(ScriptParams[0])); + return 0; + case COMMAND_IS_GARAGE_CLOSED: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CGarages::IsGarageClosed(ScriptParams[0])); + return 0; + case COMMAND_START_CATALINA_HELI: + CHeli::StartCatalinaFlyBy(); + return 0; + case COMMAND_CATALINA_HELI_TAKE_OFF: + CHeli::CatalinaTakeOff(); + return 0; + case COMMAND_REMOVE_CATALINA_HELI: + CHeli::RemoveCatalinaHeli(); + return 0; + case COMMAND_HAS_CATALINA_HELI_BEEN_SHOT_DOWN: + UpdateCompareFlag(CHeli::HasCatalinaBeenShotDown()); + return 0; + case COMMAND_SWAP_NEAREST_BUILDING_MODEL: + { + CollectParameters(&m_nIp, 6); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = *(float*)&ScriptParams[3]; + int mi1 = ScriptParams[4] >= 0 ? ScriptParams[4] : CTheScripts::UsedObjectArray[-ScriptParams[4]].index; + int mi2 = ScriptParams[5] >= 0 ? ScriptParams[5] : CTheScripts::UsedObjectArray[-ScriptParams[5]].index; + int16 total; + CEntity* apEntities[16]; + CWorld::FindObjectsOfTypeInRange(mi1, pos, radius, true, &total, 16, apEntities, true, false, false, false, false); + if (total == 0) + CWorld::FindObjectsOfTypeInRangeSectorList(mi1, CWorld::GetBigBuildingList(LEVEL_GENERIC), pos, radius, true, &total, 16, apEntities); + if (total == 0) + CWorld::FindObjectsOfTypeInRangeSectorList(mi1, CWorld::GetBigBuildingList(CTheZones::FindZoneForPoint(pos)), pos, radius, true, &total, 16, apEntities); + CEntity* pClosestEntity = nil; + float min_dist = 2.0f * radius; + for (int i = 0; i < total; i++) { + float dist = (apEntities[i]->GetPosition() - pos).Magnitude(); + if (dist < min_dist) { + min_dist = dist; + pClosestEntity = apEntities[i]; + } + } + if (!pClosestEntity) { + printf("Failed to find building\n"); + return 0; + } + CBuilding* pReplacedBuilding = ((CBuilding*)pClosestEntity); + pReplacedBuilding->ReplaceWithNewModel(mi2); + CTheScripts::AddToBuildingSwapArray(pReplacedBuilding, mi1, mi2); + return 0; + } + case COMMAND_SWITCH_WORLD_PROCESSING: + CollectParameters(&m_nIp, 1); + CWorld::bProcessCutsceneOnly = ScriptParams[0] == 0; + return 0; + case COMMAND_REMOVE_ALL_PLAYER_WEAPONS: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->ClearWeapons(); + return 0; + } + case COMMAND_GRAB_CATALINA_HELI: + { + CHeli* pHeli = CHeli::FindPointerToCatalinasHeli(); + ScriptParams[0] = pHeli ? CPools::GetVehiclePool()->GetIndex(pHeli) : -1; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CLEAR_AREA_OF_CARS: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + CWorld::ClearCarsFromArea(infX, infY, infZ, supX, supY, supZ); + return 0; + } + case COMMAND_SET_ROTATING_GARAGE_DOOR: + CollectParameters(&m_nIp, 1); + CGarages::SetGarageDoorToRotate(ScriptParams[0]); + return 0; + case COMMAND_ADD_SPHERE: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = *(float*)&ScriptParams[3]; + CTheScripts::GetActualScriptSphereIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CTheScripts::AddScriptSphere((uintptr)this + m_nIp, pos, radius); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_REMOVE_SPHERE: + CollectParameters(&m_nIp, 1); + CTheScripts::RemoveScriptSphere(ScriptParams[0]); + return 0; + case COMMAND_CATALINA_HELI_FLY_AWAY: + CHeli::MakeCatalinaHeliFlyAway(); + return 0; + case COMMAND_SET_EVERYONE_IGNORE_PLAYER: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + if (ScriptParams[1]) { + pPed->m_pWanted->m_bIgnoredByEveryone = true; + CWorld::StopAllLawEnforcersInTheirTracks(); + } + else { + pPed->m_pWanted->m_bIgnoredByEveryone = false; + } + return 0; + } + case COMMAND_STORE_CAR_CHAR_IS_IN_NO_SAVE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = pPed->bInVehicle ? pPed->m_pMyVehicle : nil; + ScriptParams[0] = CPools::GetVehiclePool()->GetIndex(pVehicle); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_STORE_CAR_PLAYER_IS_IN_NO_SAVE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CVehicle* pVehicle = pPed->bInVehicle ? pPed->m_pMyVehicle : nil; + ScriptParams[0] = CPools::GetVehiclePool()->GetIndex(pVehicle); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_PHONE_DISPLAYING_MESSAGE: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(gPhoneInfo.IsMessageBeingDisplayed(ScriptParams[0])); + return 0; + case COMMAND_DISPLAY_ONSCREEN_TIMER_WITH_STRING: + { + script_assert(CTheScripts::ScriptSpace[m_nIp++] == ARGUMENT_GLOBALVAR); + uint16 var = CTheScripts::Read2BytesFromScript(&m_nIp); + wchar* text = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); // ??? + strncpy(onscreen_str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CUserDisplay::OnscnTimer.AddClock(var, onscreen_str); + return 0; + } + case COMMAND_DISPLAY_ONSCREEN_COUNTER_WITH_STRING: + { + script_assert(CTheScripts::ScriptSpace[m_nIp++] == ARGUMENT_GLOBALVAR); + uint16 var = CTheScripts::Read2BytesFromScript(&m_nIp); + CollectParameters(&m_nIp, 1); + wchar* text = TheText.Get((char*)&CTheScripts::ScriptSpace[m_nIp]); // ??? + strncpy(onscreen_str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CUserDisplay::OnscnTimer.AddCounter(var, ScriptParams[0], onscreen_str); + return 0; + } + case COMMAND_CREATE_RANDOM_CAR_FOR_CAR_PARK: + { + CollectParameters(&m_nIp, 4); + if (CCarCtrl::NumRandomCars >= 30) + return 0; + int attempts; + int model = -1; + int index = CGeneral::GetRandomNumberInRange(0, 50); + for (attempts = 0; attempts < 50; attempts++) { + if (model != -1) + break; + model = CStreaming::ms_vehiclesLoaded[index]; + if (model == -1) + continue; + // desperatly want to believe this was inlined :| + CBaseModelInfo* pInfo = CModelInfo::GetModelInfo(model); + script_assert(pInfo->GetModelType() == MITYPE_VEHICLE); + CVehicleModelInfo* pVehicleInfo = (CVehicleModelInfo*)pInfo; + if (pVehicleInfo->m_vehicleType == VEHICLE_TYPE_CAR) { + switch (model) { + case MI_LANDSTAL: + case MI_LINERUN: + case MI_FIRETRUCK: + case MI_TRASH: + case MI_STRETCH: + case MI_MULE: + case MI_AMBULAN: + case MI_FBICAR: + case MI_MRWHOOP: + case MI_BFINJECT: + case MI_CORPSE: + case MI_POLICE: + case MI_ENFORCER: + case MI_SECURICA: + case MI_PREDATOR: + case MI_BUS: + case MI_RHINO: + case MI_BARRACKS: + case MI_TRAIN: + case MI_CHOPPER: + case MI_DODO: + case MI_COACH: + case MI_RCBANDIT: + case MI_BELLYUP: + case MI_MRWONGS: + case MI_MAFIA: + case MI_YARDIE: + case MI_YAKUZA: + case MI_DIABLOS: + case MI_COLUMB: + case MI_HOODS: + case MI_AIRTRAIN: + case MI_DEADDODO: + case MI_SPEEDER: + case MI_REEFER: + case MI_PANLANT: + case MI_FLATBED: + case MI_YANKEE: + case MI_ESCAPE: + case MI_BORGNINE: + case MI_TOYZ: + case MI_GHOST: + case MI_MIAMI_RCBARON: + case MI_MIAMI_RCRAIDER: + model = -1; + break; + case MI_IDAHO: + case MI_STINGER: + case MI_PEREN: + case MI_SENTINEL: + case MI_PATRIOT: + case MI_MANANA: + case MI_INFERNUS: + case MI_BLISTA: + case MI_PONY: + case MI_CHEETAH: + case MI_MOONBEAM: + case MI_ESPERANT: + case MI_TAXI: + case MI_KURUMA: + case MI_BOBCAT: + case MI_BANSHEE: + case MI_CABBIE: + case MI_STALLION: + case MI_RUMPO: + case 151: + case 152: + case 153: + break; + default: + printf("CREATE_RANDOM_CAR_FOR_CAR_PARK - Unknown car model %d\n", CStreaming::ms_vehiclesLoaded[index]); + model = -1; + break; + } + } + else + model = -1; + if (++index >= 50) + index = 0; + } + if (model == -1) + return 0; + CVehicle* car; + if (!CModelInfo::IsBikeModel(model)) + car = new CAutomobile(model, RANDOM_VEHICLE); + CVector pos = *(CVector*)&ScriptParams[0]; + pos.z += car->GetDistanceFromCentreOfMassToBaseOfModel(); + car->SetPosition(pos); + car->SetHeading(DEGTORAD(*(float*)&ScriptParams[3])); + CTheScripts::ClearSpaceForMissionEntity(pos, car); + car->SetStatus(STATUS_ABANDONED); + car->bIsLocked = false; + car->bIsCarParkVehicle = true; + CCarCtrl::JoinCarWithRoadSystem(car); + car->AutoPilot.m_nCarMission = MISSION_NONE; + car->AutoPilot.m_nTempAction = TEMPACT_NONE; + car->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + car->AutoPilot.m_nCruiseSpeed = car->AutoPilot.m_fMaxTrafficSpeed = 9.0f; + car->AutoPilot.m_nCurrentLane = car->AutoPilot.m_nNextLane = 0; + car->bEngineOn = false; + car->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pos); + CWorld::Add(car); + return 0; + } + case COMMAND_IS_COLLISION_IN_MEMORY: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CCollision::ms_collisionInMemory == ScriptParams[0]); + return 0; + case COMMAND_SET_WANTED_MULTIPLIER: + CollectParameters(&m_nIp, 1); + FindPlayerPed()->m_pWanted->m_fCrimeSensitivity = *(float*)&ScriptParams[0]; + return 0; + case COMMAND_SET_CAMERA_IN_FRONT_OF_PLAYER: + TheCamera.SetCameraDirectlyInFrontForFollowPed_CamOnAString(); + return 0; + case COMMAND_IS_CAR_VISIBLY_DAMAGED: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->bIsDamaged); + return 0; + } + case COMMAND_DOES_OBJECT_EXIST: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CPools::GetObjectPool()->GetAt(ScriptParams[0])); + return 0; + case COMMAND_LOAD_SCENE: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + CTimer::Stop(); + CStreaming::LoadScene(pos); + CTimer::Update(); + return 0; + } + case COMMAND_ADD_STUCK_CAR_CHECK: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CTheScripts::StuckCars.AddCarToCheck(ScriptParams[0], *(float*)&ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_REMOVE_STUCK_CAR_CHECK: + { + CollectParameters(&m_nIp, 1); + CTheScripts::StuckCars.RemoveCarFromCheck(ScriptParams[0]); + return 0; + } + case COMMAND_IS_CAR_STUCK: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CTheScripts::StuckCars.HasCarBeenStuckForAWhile(ScriptParams[0])); + return 0; + case COMMAND_LOAD_MISSION_AUDIO: + strncpy(str, (char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + str[i] = tolower(str[i]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + DMAudio.PreloadMissionAudio(str); + return 0; + case COMMAND_HAS_MISSION_AUDIO_LOADED: + UpdateCompareFlag(DMAudio.GetMissionAudioLoadingStatus() == 1); + return 0; + case COMMAND_PLAY_MISSION_AUDIO: + DMAudio.PlayLoadedMissionAudio(); + return 0; + case COMMAND_HAS_MISSION_AUDIO_FINISHED: + UpdateCompareFlag(DMAudio.IsMissionAudioSampleFinished()); + return 0; + case COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + int node = ThePaths.FindNodeClosestToCoors(pos, 0, 999999.9f, true, true); + *(CVector*)&ScriptParams[0] = ThePaths.m_pathNodes[node].GetPosition(); + *(float*)&ScriptParams[3] = ThePaths.FindNodeOrientationForCarPlacement(node); + StoreParameters(&m_nIp, 4); + return 0; + } + case COMMAND_HAS_IMPORT_GARAGE_SLOT_BEEN_FILLED: + { + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CGarages::HasImportExportGarageCollectedThisCar(ScriptParams[0], ScriptParams[1] - 1)); + return 0; + } + case COMMAND_CLEAR_THIS_PRINT: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CMessages::ClearThisPrint(text); + return 0; + } + case COMMAND_CLEAR_THIS_BIG_PRINT: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CMessages::ClearThisBigPrint(text); + return 0; + } + case COMMAND_SET_MISSION_AUDIO_POSITION: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + DMAudio.SetMissionAudioLocation(pos.x, pos.y, pos.z); + return 0; + } + case COMMAND_ACTIVATE_SAVE_MENU: + FrontEndMenuManager.m_bSaveMenuActive = true; + return 0; + case COMMAND_HAS_SAVE_GAME_FINISHED: + UpdateCompareFlag(!FrontEndMenuManager.m_bMenuActive); + return 0; + case COMMAND_NO_SPECIAL_CAMERA_FOR_THIS_GARAGE: + CollectParameters(&m_nIp, 1); + CGarages::SetLeaveCameraForThisGarage(ScriptParams[0]); + return 0; + case COMMAND_ADD_BLIP_FOR_PICKUP_OLD: + { + CollectParameters(&m_nIp, 3); + CObject* pObject = CPickups::aPickUps[CPickups::GetActualPickupIndex(ScriptParams[0])].m_pObject; + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CRadar::SetEntityBlip(BLIP_OBJECT, CPools::GetObjectPool()->GetIndex(pObject), ScriptParams[1], (eBlipDisplay)ScriptParams[2]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_BLIP_FOR_PICKUP: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPickups::aPickUps[CPickups::GetActualPickupIndex(ScriptParams[0])].m_pObject; + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_OBJECT, CPools::GetObjectPool()->GetIndex(pObject), 6, BLIP_DISPLAY_BOTH); + CRadar::ChangeBlipScale(handle, 3); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_SPRITE_BLIP_FOR_PICKUP: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPickups::aPickUps[CPickups::GetActualPickupIndex(ScriptParams[0])].m_pObject; + CRadar::GetActualBlipArrayIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + int handle = CRadar::SetEntityBlip(BLIP_OBJECT, CPools::GetObjectPool()->GetIndex(pObject), 6, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(handle, ScriptParams[1]); + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_PED_DENSITY_MULTIPLIER: + CollectParameters(&m_nIp, 1); + CPopulation::PedDensityMultiplier = *(float*)&ScriptParams[0]; + return 0; + case COMMAND_FORCE_RANDOM_PED_TYPE: + CollectParameters(&m_nIp, 1); + CPopulation::m_AllRandomPedsThisType = ScriptParams[0]; + return 0; + case COMMAND_SET_TEXT_DRAW_BEFORE_FADE: + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bTextBeforeFade = ScriptParams[0] != 0; + return 0; + case COMMAND_GET_COLLECTABLE1S_COLLECTED: + ScriptParams[0] = CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_REGISTER_EL_BURRO_TIME: + CollectParameters(&m_nIp, 1); + CStats::RegisterElBurroTime(ScriptParams[0]); + return 0; + case COMMAND_SET_SPRITES_DRAW_BEFORE_FADE: + CollectParameters(&m_nIp, 1); + CTheScripts::IntroRectangles[CTheScripts::NumberOfIntroRectanglesThisFrame].m_bBeforeFade = ScriptParams[0] != 0; + return 0; + case COMMAND_SET_TEXT_RIGHT_JUSTIFY: + CollectParameters(&m_nIp, 1); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_bRightJustify = ScriptParams[0] != 0; + return 0; + case COMMAND_PRINT_HELP: + { + if (CCamera::m_bUseMouse3rdPerson && ( + strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "HELP15") == 0 || + strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "GUN_2A") == 0 || + strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "GUN_3A") == 0 || + strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "GUN_4A") == 0)) { + m_nIp += KEY_LENGTH_IN_SCRIPT; + return 0; + } + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CHud::SetHelpMessage(text, false); + return 0; + } + case COMMAND_CLEAR_HELP: + CHud::SetHelpMessage(nil, false); + return 0; + case COMMAND_FLASH_HUD_OBJECT: + CollectParameters(&m_nIp, 1); + CHud::m_ItemToFlash = ScriptParams[0]; + return 0; + default: + script_assert(0); + } + return -1; +} + +int32 CTheScripts::GetNewUniqueScriptSphereIndex(int32 index) +{ + if (ScriptSphereArray[index].m_Index >= UINT16_MAX - 1) + ScriptSphereArray[index].m_Index = 1; + else + ScriptSphereArray[index].m_Index++; + return (uint16)index | ScriptSphereArray[index].m_Index << 16; +} + +int32 CTheScripts::GetActualScriptSphereIndex(int32 index) +{ + if (index == -1) + return -1; + uint16 check = (uint32)index >> 16; + uint16 array_idx = index & (0xFFFF); + script_assert(array_idx < ARRAY_SIZE(ScriptSphereArray)); + if (check != ScriptSphereArray[array_idx].m_Index) + return -1; + return array_idx; +} + +void CTheScripts::DrawScriptSpheres() +{ + for (int i = 0; i < MAX_NUM_SCRIPT_SPHERES; i++) { + if (ScriptSphereArray[i].m_bInUse) + C3dMarkers::PlaceMarkerSet(ScriptSphereArray[i].m_Id, MARKERTYPE_CYLINDER, ScriptSphereArray[i].m_vecCenter, ScriptSphereArray[i].m_fRadius, + SPHERE_MARKER_R, SPHERE_MARKER_G, SPHERE_MARKER_B, SPHERE_MARKER_A, SPHERE_MARKER_PULSE_PERIOD, SPHERE_MARKER_PULSE_FRACTION, 0); + } +} + +int32 CTheScripts::AddScriptSphere(int32 id, CVector pos, float radius) +{ + int16 i = 0; + for (i = 0; i < MAX_NUM_SCRIPT_SPHERES; i++) { + if (!ScriptSphereArray[i].m_bInUse) + break; + } +#ifdef FIX_BUGS + if (i == MAX_NUM_SCRIPT_SPHERES) + return -1; +#endif + ScriptSphereArray[i].m_bInUse = true; + ScriptSphereArray[i].m_Id = id; + ScriptSphereArray[i].m_vecCenter = pos; + ScriptSphereArray[i].m_fRadius = radius; + return GetNewUniqueScriptSphereIndex(i); +} + +void CTheScripts::RemoveScriptSphere(int32 index) +{ + index = GetActualScriptSphereIndex(index); + if (index == -1) + return; + ScriptSphereArray[index].m_bInUse = false; + ScriptSphereArray[index].m_Id = 0; +} + +void CTheScripts::AddToBuildingSwapArray(CBuilding* pBuilding, int32 old_model, int32 new_model) +{ + int i = 0; + bool found = false; + while (i < MAX_NUM_BUILDING_SWAPS && !found) { + if (BuildingSwapArray[i].m_pBuilding == pBuilding) + found = true; + else + i++; + } + if (found) { + if (BuildingSwapArray[i].m_nOldModel == new_model) { + BuildingSwapArray[i].m_pBuilding = nil; + BuildingSwapArray[i].m_nOldModel = BuildingSwapArray[i].m_nNewModel = -1; + } + else { + BuildingSwapArray[i].m_nNewModel = new_model; + } + } + else { + i = 0; + while (i < MAX_NUM_BUILDING_SWAPS && !found) { + if (BuildingSwapArray[i].m_pBuilding == nil) + found = true; + else + i++; + } + if (found) { + BuildingSwapArray[i].m_pBuilding = pBuilding; + BuildingSwapArray[i].m_nNewModel = new_model; + BuildingSwapArray[i].m_nOldModel = old_model; + } + } +} + +void CTheScripts::AddToInvisibilitySwapArray(CEntity* pEntity, bool remove) +{ + int i = 0; + bool found = false; + while (i < MAX_NUM_INVISIBILITY_SETTINGS && !found) { + if (InvisibilitySettingArray[i] == pEntity) + found = true; + else + i++; + } + if (found) { + if (remove) + InvisibilitySettingArray[i] = nil; + } + else if (!remove) { + i = 0; + while (i < MAX_NUM_INVISIBILITY_SETTINGS && !found) { + if (InvisibilitySettingArray[i] == nil) + found = true; + else + i++; + } + if (found) + InvisibilitySettingArray[i] = pEntity; + } +} + +void CTheScripts::UndoBuildingSwaps() +{ + for (int i = 0; i < MAX_NUM_BUILDING_SWAPS; i++) { + if (BuildingSwapArray[i].m_pBuilding) { + BuildingSwapArray[i].m_pBuilding->ReplaceWithNewModel(BuildingSwapArray[i].m_nOldModel); + BuildingSwapArray[i].m_pBuilding = nil; + BuildingSwapArray[i].m_nOldModel = BuildingSwapArray[i].m_nNewModel = -1; + } + } +} + +void CTheScripts::UndoEntityInvisibilitySettings() +{ + for (int i = 0; i < MAX_NUM_INVISIBILITY_SETTINGS; i++) { + if (InvisibilitySettingArray[i]) { + InvisibilitySettingArray[i]->bIsVisible = true; + InvisibilitySettingArray[i] = nil; + } + } +} diff --git a/src/control/Script5.cpp b/src/control/Script5.cpp new file mode 100644 index 0000000..ce5fa4b --- /dev/null +++ b/src/control/Script5.cpp @@ -0,0 +1,2606 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "CarCtrl.h" +#include "BulletInfo.h" +#include "General.h" +#include "Lines.h" +#include "Messages.h" +#include "Pad.h" +#include "Pools.h" +#include "Population.h" +#include "RpAnimBlend.h" +#include "SaveBuf.h" +#include "Shadows.h" +#include "SpecialFX.h" +#include "World.h" +#include "main.h" + +void CRunningScript::UpdateCompareFlag(bool flag) +{ + if (m_bNotFlag) + flag = !flag; + if (m_nAndOrState == ANDOR_NONE) { + m_bCondResult = flag; + return; + } + if (m_nAndOrState >= ANDS_1 && m_nAndOrState <= ANDS_8) { + m_bCondResult &= flag; + if (m_nAndOrState == ANDS_1) { + m_nAndOrState = ANDOR_NONE; + return; + } + } + else if (m_nAndOrState >= ORS_1 && m_nAndOrState <= ORS_8) { + m_bCondResult |= flag; + if (m_nAndOrState == ORS_1) { + m_nAndOrState = ANDOR_NONE; + return; + } + } + else { + return; + } + m_nAndOrState--; +} + +void CRunningScript::LocatePlayerCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + switch (command) { + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D: + if (!CTheScripts::IsPlayerStopped(pPlayerInfo)) { + result = false; + decided = true; + } + break; + default: + break; + } + X = *(float*)&ScriptParams[1]; + Y = *(float*)&ScriptParams[2]; + if (b3D) { + Z = *(float*)&ScriptParams[3]; + dX = *(float*)&ScriptParams[4]; + dY = *(float*)&ScriptParams[5]; + dZ = *(float*)&ScriptParams[6]; + debug = ScriptParams[7]; + } else { + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (!decided) { + CVector pos = pPlayerInfo->GetPos(); + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_PLAYER_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D: + result = true; + break; + case COMMAND_LOCATE_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_LOCATE_PLAYER_IN_CAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocatePlayerCharCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPlayerInfo->GetPos(); + if (pTarget->bInVehicle) { + X = pTarget->m_pMyVehicle->GetPosition().x; + Y = pTarget->m_pMyVehicle->GetPosition().y; + Z = pTarget->m_pMyVehicle->GetPosition().z; + } else { + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + } + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D: + result = true; + break; + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) +#ifdef FIX_BUGS + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); +#else + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dX, b3D ? Z : MAP_Z_LOW_LIMIT); +#endif + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocatePlayerCarCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPlayerInfo->GetPos(); + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D: + result = true; + break; + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_2D: + case COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCharCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + switch (command) { + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D: + if (!CTheScripts::IsPedStopped(pPed)) { + result = false; + decided = true; + } + break; + default: + break; + } + X = *(float*)&ScriptParams[1]; + Y = *(float*)&ScriptParams[2]; + if (b3D) { + Z = *(float*)&ScriptParams[3]; + dX = *(float*)&ScriptParams[4]; + dY = *(float*)&ScriptParams[5]; + dZ = *(float*)&ScriptParams[6]; + debug = ScriptParams[7]; + } + else { + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (!decided) { + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D: + result = true; + break; + case COMMAND_LOCATE_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_3D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_CHAR_IN_CAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_3D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCharCharCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + if (pTarget->bInVehicle) { + X = pTarget->m_pMyVehicle->GetPosition().x; + Y = pTarget->m_pMyVehicle->GetPosition().y; + Z = pTarget->m_pMyVehicle->GetPosition().z; + } + else { + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + } + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D: + result = true; + break; + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) +#ifdef FIX_BUGS + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); +#else + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dX, b3D ? Z : MAP_Z_LOW_LIMIT); +#endif + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCharCarCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D: + result = true; + break; + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +#if GTA_VERSION > GTA3_PS2_160 +void CRunningScript::LocateCharObjectCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CObject* pTarget = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D: + result = true; + break; + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} +#endif + +void CRunningScript::LocateCarCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_CAR_3D: + case COMMAND_LOCATE_STOPPED_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVector pos = pVehicle->GetPosition(); + switch (command) { + case COMMAND_LOCATE_STOPPED_CAR_2D: + case COMMAND_LOCATE_STOPPED_CAR_3D: + if (!CTheScripts::IsVehicleStopped(pVehicle)) { + result = false; + decided = true; + } + break; + default: + break; + } + X = *(float*)&ScriptParams[1]; + Y = *(float*)&ScriptParams[2]; + if (b3D) { + Z = *(float*)&ScriptParams[3]; + dX = *(float*)&ScriptParams[4]; + dY = *(float*)&ScriptParams[5]; + dZ = *(float*)&ScriptParams[6]; + debug = ScriptParams[7]; + } + else { + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (!decided) { + result = false; + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = in_area; + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +#if GTA_VERSION > GTA3_PS2_160 +void CRunningScript::LocateSniperBulletCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_SNIPER_BULLET_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 7 : 5); + X = *(float*)&ScriptParams[0]; + Y = *(float*)&ScriptParams[1]; + if (b3D) { + Z = *(float*)&ScriptParams[2]; + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + dZ = *(float*)&ScriptParams[5]; + debug = ScriptParams[6]; + } + else { + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + debug = ScriptParams[4]; + } + result = CBulletInfo::TestForSniperBullet(X - dX, X + dX, Y - dY, Y + dY, b3D ? Z - dZ : -1000.0f, b3D ? Z + dZ : 1000.0f); + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} +#endif + +void CRunningScript::PlayerInAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ; + switch (command) { + case COMMAND_IS_PLAYER_IN_AREA_3D: + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + switch (command) { + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D: + if (!CTheScripts::IsPlayerStopped(pPlayerInfo)) { + result = false; + decided = true; + } + break; + default: + break; + } + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + debug = ScriptParams[7]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + if (!decided) { + CVector pos = pPlayerInfo->GetPos(); + result = false; + bool in_area; + if (b3D) { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y && + infZ <= pos.z && + supZ >= pos.z; + } + else { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_IS_PLAYER_IN_AREA_2D: + case COMMAND_IS_PLAYER_IN_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D: + result = true; + break; + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, infX, infY, supX, supY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + else + CTheScripts::DrawDebugSquare(infX, infY, supX, supY); + } +} + +void CRunningScript::PlayerInAngledAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ, side2length; + switch (command) { + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 9 : 7); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + switch (command) { + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D: + if (!CTheScripts::IsPlayerStopped(pPlayerInfo)) { + result = false; + decided = true; + } + break; + default: + break; + } + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + side2length = *(float*)&ScriptParams[7]; + debug = ScriptParams[8]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + side2length = *(float*)&ScriptParams[5]; + debug = ScriptParams[6]; + } + float initAngle = CGeneral::GetRadianAngleBetweenPoints(infX, infY, supX, supY) + HALFPI; + while (initAngle < 0.0f) + initAngle += TWOPI; + while (initAngle > TWOPI) + initAngle -= TWOPI; + // it looks like the idea is to use a rectangle using the diagonal of the rectangle as + // the side of new rectangle, with "length" being the length of second side + float rotatedSupX = supX + side2length * Sin(initAngle); + float rotatedSupY = supY - side2length * Cos(initAngle); + float rotatedInfX = infX + side2length * Sin(initAngle); + float rotatedInfY = infY - side2length * Cos(initAngle); + float side1X = supX - infX; + float side1Y = supY - infY; + float side1Length = CVector2D(side1X, side1Y).Magnitude(); + float side2X = rotatedInfX - infX; + float side2Y = rotatedInfY - infY; + float side2Length = CVector2D(side2X, side2Y).Magnitude(); // == side2length? + if (!decided) { + CVector pos = pPlayerInfo->GetPos(); + result = false; + float X = pos.x - infX; + float Y = pos.y - infY; + float positionAlongSide1 = X * side1X / side1Length + Y * side1Y / side1Length; + bool in_area = false; + if (positionAlongSide1 >= 0.0f && positionAlongSide1 <= side1Length) { + float positionAlongSide2 = X * side2X / side2Length + Y * side2Y / side2Length; + if (positionAlongSide2 >= 0.0f && positionAlongSide2 <= side2Length) { + in_area = !b3D || pos.z >= infZ && pos.z <= supZ; + } + } + + if (in_area) { + switch (command) { + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D: + result = true; + break; + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D: + result = !pPlayerInfo->m_pPed->bInVehicle; + break; + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D: + case COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D: + result = pPlayerInfo->m_pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantAngledArea((uintptr)this + m_nIp, infX, infY, supX, supY, + rotatedSupX, rotatedSupY, rotatedInfX, rotatedInfY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugAngledCube(infX, infY, infZ, supX, supY, supZ, + rotatedSupX, rotatedSupY, rotatedInfX, rotatedInfY); + else + CTheScripts::DrawDebugAngledSquare(infX, infY, supX, supY, + rotatedSupX, rotatedSupY, rotatedInfX, rotatedInfY); + } +} + +void CRunningScript::CharInAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ; + switch (command) { + case COMMAND_IS_CHAR_IN_AREA_3D: + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + switch (command) { + case COMMAND_IS_CHAR_STOPPED_IN_AREA_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D: + if (!CTheScripts::IsPedStopped(pPed)) { + result = false; + decided = true; + } + break; + default: + break; + } + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + debug = ScriptParams[7]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + if (!decided) { + result = false; + bool in_area; + if (b3D) { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y && + infZ <= pos.z && + supZ >= pos.z; + } + else { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_IS_CHAR_IN_AREA_2D: + case COMMAND_IS_CHAR_IN_AREA_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_3D: + result = true; + break; + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D: + result = !pPed->bInVehicle; + break; + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_2D: + case COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D: + case COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, infX, infY, supX, supY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + else + CTheScripts::DrawDebugSquare(infX, infY, supX, supY); + } +} + +void CRunningScript::CarInAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ; + switch (command) { + case COMMAND_IS_CAR_IN_AREA_3D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVector pos = pVehicle->GetPosition(); + switch (command) { + case COMMAND_IS_CAR_STOPPED_IN_AREA_3D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_2D: + if (!CTheScripts::IsVehicleStopped(pVehicle)) { + result = false; + decided = true; + } + break; + default: + break; + } + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + debug = ScriptParams[7]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + if (!decided) { + result = false; + bool in_area; + if (b3D) { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y && + infZ <= pos.z && + supZ >= pos.z; + } + else { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y; + } + if (in_area) { + switch (command) { + case COMMAND_IS_CAR_IN_AREA_2D: + case COMMAND_IS_CAR_IN_AREA_3D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_2D: + case COMMAND_IS_CAR_STOPPED_IN_AREA_3D: + result = true; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, infX, infY, supX, supY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + else + CTheScripts::DrawDebugSquare(infX, infY, supX, supY); + } +} + +void CRunningScript::DoDeatharrestCheck() +{ + if (!m_bDeatharrestEnabled) + return; + if (!CTheScripts::IsPlayerOnAMission()) + return; + CPlayerInfo* pPlayer = &CWorld::Players[CWorld::PlayerInFocus]; + if (!pPlayer->IsRestartingAfterDeath() && !pPlayer->IsRestartingAfterArrest() && !CTheScripts::UpsideDownCars.AreAnyCarsUpsideDown()) + return; +#ifdef MISSION_REPLAY + if (AllowMissionReplay != MISSION_RETRY_STAGE_NORMAL) + return; + if (CanAllowMissionReplay()) + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_SCRIPT_TO_TERMINATE; +#endif + script_assert(m_nStackPointer > 0); + while (m_nStackPointer > 1) + --m_nStackPointer; + m_nIp = m_anStack[--m_nStackPointer]; + int16 messageId; + if (pPlayer->IsRestartingAfterDeath()) + messageId = 0; + else if (pPlayer->IsRestartingAfterArrest()) + messageId = 5; + else + messageId = 10; + messageId += CGeneral::GetRandomNumberInRange(0, 5); + bool found = false; + for (int16 contact = 0; !found && contact < MAX_NUM_CONTACTS; contact++) { + int contactFlagOffset = CTheScripts::OnAMissionForContactFlag[contact]; + if (contactFlagOffset && CTheScripts::ScriptSpace[contactFlagOffset] == 1) { + messageId += CTheScripts::BaseBriefIdForContact[contact]; + found = true; + } + } + if (!found) + messageId = 8001; + char tmp[16]; + sprintf(tmp, "%d", messageId); + CMessages::ClearSmallMessagesOnly(); + wchar* text = TheText.Get(tmp); + // ...and do nothing about it + *(int32*)&CTheScripts::ScriptSpace[CTheScripts::OnAMissionFlag] = 0; + m_bDeatharrestExecuted = true; + m_nWakeTime = 0; +} + +int16 CRunningScript::GetPadState(uint16 pad, uint16 button) +{ + CPad* pPad = CPad::GetPad(pad); + switch (button) { + case 0: return pPad->NewState.LeftStickX; + case 1: return pPad->NewState.LeftStickY; + case 2: return pPad->NewState.RightStickX; + case 3: return pPad->NewState.RightStickY; + case 4: return pPad->NewState.LeftShoulder1; + case 5: return pPad->NewState.LeftShoulder2; + case 6: return pPad->NewState.RightShoulder1; + case 7: return pPad->NewState.RightShoulder2; + case 8: return pPad->NewState.DPadUp; + case 9: return pPad->NewState.DPadDown; + case 10: return pPad->NewState.DPadLeft; + case 11: return pPad->NewState.DPadRight; + case 12: return pPad->NewState.Start; + case 13: return pPad->NewState.Select; + case 14: return pPad->NewState.Square; + case 15: return pPad->NewState.Triangle; + case 16: return pPad->NewState.Cross; + case 17: return pPad->NewState.Circle; + case 18: return pPad->NewState.LeftShock; + case 19: return pPad->NewState.RightShock; + default: break; + } + return 0; +} + +#ifdef GTA_SCRIPT_COLLECTIVE +void CRunningScript::LocateCollectiveCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_COLL_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + X = *(float*)&ScriptParams[1]; + Y = *(float*)&ScriptParams[2]; + if (b3D) { + Z = *(float*)&ScriptParams[3]; + dX = *(float*)&ScriptParams[4]; + dY = *(float*)&ScriptParams[5]; + dZ = *(float*)&ScriptParams[6]; + debug = ScriptParams[7]; + } + else { + dX = *(float*)&ScriptParams[3]; + dY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + switch (command) { + case COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D: + if (!CTheScripts::IsPedStopped(pPed)) { + result = false; + decided = true; + } + break; + default: + break; + } + if (!decided) { + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_2D: + case COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D: + result = true; + break; + case COMMAND_LOCATE_COLL_ON_FOOT_2D: + case COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_COLL_IN_CAR_2D: + case COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCollectiveCharCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D: + case COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + if (pTarget->bInVehicle) { + X = pTarget->m_pMyVehicle->GetPosition().x; + Y = pTarget->m_pMyVehicle->GetPosition().y; + Z = pTarget->m_pMyVehicle->GetPosition().z; + } + else { + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + } + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D: + result = true; + break; + case COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCollectiveCarCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D: + case COMMAND_LOCATE_COLL_IN_CAR_CAR_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CVehicle* pTarget = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + X = pTarget->GetPosition().x; + Y = pTarget->GetPosition().y; + Z = pTarget->GetPosition().z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D: + result = true; + break; + case COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_COLL_IN_CAR_CAR_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::LocateCollectivePlayerCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D: + case COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D: + case COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 6 : 5); + CVector pos = CWorld::Players[ScriptParams[1]].GetPos(); + X = pos.x; + Y = pos.y; + Z = pos.z; + dX = *(float*)&ScriptParams[2]; + dY = *(float*)&ScriptParams[3]; + if (b3D) { + dZ = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + else { + debug = ScriptParams[4]; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + bool in_area; + if (b3D) { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y && + Z - dZ <= pos.z && + Z + dZ >= pos.z; + } + else { + in_area = X - dX <= pos.x && + X + dX >= pos.x && + Y - dY <= pos.y && + Y + dY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D: + result = true; + break; + case COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, X - dX, Y - dY, X + dX, Y + dY, b3D ? Z : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(X - dX, Y - dY, Z - dZ, X + dX, Y + dY, Z + dZ); + else + CTheScripts::DrawDebugSquare(X - dX, Y - dY, X + dX, Y + dY); + } +} + +void CRunningScript::CollectiveInAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug, decided = false; + float infX, infY, infZ, supX, supY, supZ; + switch (command) { + case COMMAND_IS_COLL_IN_AREA_2D: + case COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_IN_AREA_IN_CAR_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D: + b3D = false; + break; + default: + b3D = true; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + infX = *(float*)&ScriptParams[1]; + infY = *(float*)&ScriptParams[2]; + if (b3D) { + infZ = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[6]; + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[6]; + supZ = *(float*)&ScriptParams[3]; + } + debug = ScriptParams[7]; + } + else { + supX = *(float*)&ScriptParams[3]; + supY = *(float*)&ScriptParams[4]; + debug = ScriptParams[5]; + } + if (infX > supX) { + float tmp = infX; + infX = supX; + supX = tmp; + } + if (infY > supY) { + float tmp = infY; + infY = supY; + supY = tmp; + } + result = true; + for (int i = 0; i < MAX_NUM_COLLECTIVES && result; i++) { + if (ScriptParams[0] != CTheScripts::CollectiveArray[i].colIndex) + continue; + CPed* pPed = CPools::GetPedPool()->GetAt(CTheScripts::CollectiveArray[i].pedIndex); + if (!pPed) { + CTheScripts::CollectiveArray[i].colIndex = -1; + CTheScripts::CollectiveArray[i].pedIndex = 0; + continue; + } + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + switch (command) { + case COMMAND_IS_COLL_STOPPED_IN_AREA_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D: + if (!CTheScripts::IsPedStopped(pPed)) { + result = false; + decided = true; + } + break; + default: + break; + } + if (!decided) { + bool in_area; + if (b3D) { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y && + infZ <= pos.z && + supZ >= pos.z; + } + else { + in_area = infX <= pos.x && + supX >= pos.x && + infY <= pos.y && + supY >= pos.y; + } + result = false; + if (in_area) { + switch (command) { + case COMMAND_IS_COLL_IN_AREA_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_2D: + result = true; + break; + case COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D: + result = !pPed->bInVehicle; + break; + case COMMAND_IS_COLL_IN_AREA_IN_CAR_2D: + case COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D: + result = pPed->bInVehicle; + break; + default: + script_assert(false); + break; + } + } + } + } + UpdateCompareFlag(result); + if (debug) + CTheScripts::HighlightImportantArea((uintptr)this + m_nIp, infX, infY, supX, supY, b3D ? (infZ + supZ) / 2 : MAP_Z_LOW_LIMIT); + if (CTheScripts::DbgFlag) { + if (b3D) + CTheScripts::DrawDebugCube(infX, infY, infZ, supX, supY, supZ); + else + CTheScripts::DrawDebugSquare(infX, infY, supX, supY); + } +} +#endif + +void CTheScripts::PrintListSizes() +{ + int active = 0; + int idle = 0; + + for (CRunningScript* pScript = pActiveScripts; pScript; pScript = pScript->GetNext()) + active++; + for (CRunningScript* pScript = pIdleScripts; pScript; pScript = pScript->GetNext()) + idle++; + + debug("active: %d, idle: %d", active, idle); +} + +uint32 DbgLineColour = 0x0000FFFF; // r = 0, g = 0, b = 255, a = 255 + +void CTheScripts::DrawDebugSquare(float infX, float infY, float supX, float supY) +{ + CColPoint tmpCP; + CEntity* tmpEP; + CVector p1, p2, p3, p4; + p1 = CVector(infX, infY, -1000.0f); + CWorld::ProcessVerticalLine(p1, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p1.z = 2.0f + tmpCP.point.z; + p2 = CVector(supX, supY, -1000.0f); + CWorld::ProcessVerticalLine(p2, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p2.z = 2.0f + tmpCP.point.z; + p3 = CVector(infX, supY, -1000.0f); + CWorld::ProcessVerticalLine(p3, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p3.z = 2.0f + tmpCP.point.z; + p4 = CVector(supX, infY, -1000.0f); + CWorld::ProcessVerticalLine(p4, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p4.z = 2.0f + tmpCP.point.z; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p3.x, p3.y, p3.z, p4.x, p4.y, p4.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p4.x, p4.y, p4.z, p1.x, p1.y, p1.z, DbgLineColour, DbgLineColour); +} + +void CTheScripts::DrawDebugAngledSquare(float infX, float infY, float supX, float supY, float rotSupX, float rotSupY, float rotInfX, float rotInfY) +{ + CColPoint tmpCP; + CEntity* tmpEP; + CVector p1, p2, p3, p4; + p1 = CVector(infX, infY, -1000.0f); + CWorld::ProcessVerticalLine(p1, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p1.z = 2.0f + tmpCP.point.z; + p2 = CVector(supX, supY, -1000.0f); + CWorld::ProcessVerticalLine(p2, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p2.z = 2.0f + tmpCP.point.z; + p3 = CVector(rotSupX, rotSupY, -1000.0f); + CWorld::ProcessVerticalLine(p3, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p3.z = 2.0f + tmpCP.point.z; + p4 = CVector(rotInfX, rotInfY, -1000.0f); + CWorld::ProcessVerticalLine(p4, 1000.0f, tmpCP, tmpEP, true, false, false, false, true, false, nil); + p4.z = 2.0f + tmpCP.point.z; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p3.x, p3.y, p3.z, p4.x, p4.y, p4.z, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(p4.x, p4.y, p4.z, p1.x, p1.y, p1.z, DbgLineColour, DbgLineColour); +} + +void CTheScripts::DrawDebugCube(float infX, float infY, float infZ, float supX, float supY, float supZ) +{ + CTheScripts::ScriptDebugLine3D(infX, infY, infZ, supX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, infZ, supX, supY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, supY, infZ, infX, supY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, supY, infZ, infX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, infY, supZ, supX, infY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, supZ, supX, supY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, supY, supZ, infX, supY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, supY, supZ, infX, infY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, infY, supZ, infX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, supZ, supX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, supY, supZ, supX, supY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, supY, supZ, infX, supY, infZ, DbgLineColour, DbgLineColour); +} + +void CTheScripts::DrawDebugAngledCube(float infX, float infY, float infZ, float supX, float supY, float supZ, float rotSupX, float rotSupY, float rotInfX, float rotInfY) +{ + CTheScripts::ScriptDebugLine3D(infX, infY, infZ, supX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, infZ, rotSupX, rotSupY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotSupX, rotSupY, infZ, rotInfX, rotInfY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotInfX, rotInfY, infZ, infX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, infY, supZ, supX, infY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, supZ, rotSupX, rotSupY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotSupX, rotSupY, rotInfX, rotInfY, supY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotInfX, rotInfY, supZ, infX, infY, supZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(infX, infY, supZ, infX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(supX, infY, supZ, supX, infY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotSupX, rotSupY, supZ, rotSupX, rotSupY, infZ, DbgLineColour, DbgLineColour); + CTheScripts::ScriptDebugLine3D(rotInfX, rotInfY, supZ, rotInfX, rotInfY, infZ, DbgLineColour, DbgLineColour); +} + +void CTheScripts::ScriptDebugLine3D(float x1, float y1, float z1, float x2, float y2, float z2, uint32 col, uint32 col2) +{ + if (NumScriptDebugLines >= MAX_NUM_STORED_LINES) + return; + aStoredLines[NumScriptDebugLines].vecInf = CVector(x1, y1, z1); + aStoredLines[NumScriptDebugLines].vecSup = CVector(x2, y2, z2); + aStoredLines[NumScriptDebugLines].color1 = col; + aStoredLines[NumScriptDebugLines++].color2 = col2; +} + +void CTheScripts::RenderTheScriptDebugLines() +{ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)1); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)1); + for (int i = 0; i < NumScriptDebugLines; i++) { + CLines::RenderLineWithClipping( + aStoredLines[i].vecInf.x, + aStoredLines[i].vecInf.y, + aStoredLines[i].vecInf.z, + aStoredLines[i].vecSup.x, + aStoredLines[i].vecSup.y, + aStoredLines[i].vecSup.z, + aStoredLines[i].color1, + aStoredLines[i].color2); + } + NumScriptDebugLines = 0; + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)0); +} + +#define SCRIPT_DATA_SIZE sizeof(CTheScripts::OnAMissionFlag) + sizeof(CTheScripts::BaseBriefIdForContact) + sizeof(CTheScripts::OnAMissionForContactFlag) +\ + sizeof(CTheScripts::CollectiveArray) + 4 * sizeof(uint32) * MAX_NUM_BUILDING_SWAPS + 2 * sizeof(uint32) * MAX_NUM_INVISIBILITY_SETTINGS + 5 * sizeof(uint32) + +void CTheScripts::SaveAllScripts(uint8* buf, uint32* size) +{ +INITSAVEBUF + uint32 varSpace = GetSizeOfVariableSpace(); + uint32 runningScripts = 0; + for (CRunningScript* pScript = pActiveScripts; pScript; pScript = pScript->GetNext()) + runningScripts++; + *size = CRunningScript::nSaveStructSize * runningScripts + varSpace + SCRIPT_DATA_SIZE + SAVE_HEADER_SIZE + 3 * sizeof(uint32); + WriteSaveHeader(buf, 'S', 'C', 'R', '\0', *size - SAVE_HEADER_SIZE); + WriteSaveBuf(buf, varSpace); + for (uint32 i = 0; i < varSpace; i++) + WriteSaveBuf(buf, ScriptSpace[i]); +#ifdef CHECK_STRUCT_SIZES + static_assert(SCRIPT_DATA_SIZE == 968, "CTheScripts::SaveAllScripts"); +#endif + uint32 script_data_size = SCRIPT_DATA_SIZE; + WriteSaveBuf(buf, script_data_size); + WriteSaveBuf(buf, OnAMissionFlag); + for (uint32 i = 0; i < MAX_NUM_CONTACTS; i++) { + WriteSaveBuf(buf, OnAMissionForContactFlag[i]); + WriteSaveBuf(buf, BaseBriefIdForContact[i]); + } + for (uint32 i = 0; i < MAX_NUM_COLLECTIVES; i++) + WriteSaveBuf(buf, CollectiveArray[i]); + WriteSaveBuf(buf, NextFreeCollectiveIndex); + for (uint32 i = 0; i < MAX_NUM_BUILDING_SWAPS; i++) { + CBuilding* pBuilding = BuildingSwapArray[i].m_pBuilding; + uint32 type, handle; + if (!pBuilding) { + type = 0; + handle = 0; + } else if (pBuilding->GetIsATreadable()) { + type = 1; + handle = CPools::GetTreadablePool()->GetJustIndex_NoFreeAssert((CTreadable*)pBuilding) + 1; + } else { + type = 2; + handle = CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(pBuilding) + 1; + } + WriteSaveBuf(buf, type); + WriteSaveBuf(buf, handle); + WriteSaveBuf(buf, BuildingSwapArray[i].m_nNewModel); + WriteSaveBuf(buf, BuildingSwapArray[i].m_nOldModel); + } + for (uint32 i = 0; i < MAX_NUM_INVISIBILITY_SETTINGS; i++) { + CEntity* pEntity = InvisibilitySettingArray[i]; + uint32 type, handle; + if (!pEntity) { + type = 0; + handle = 0; + } else { + switch (pEntity->GetType()) { + case ENTITY_TYPE_BUILDING: + if (((CBuilding*)pEntity)->GetIsATreadable()) { + type = 1; + handle = CPools::GetTreadablePool()->GetJustIndex_NoFreeAssert((CTreadable*)pEntity) + 1; + } else { + type = 2; + handle = CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert((CBuilding*)pEntity) + 1; + } + break; + case ENTITY_TYPE_OBJECT: + type = 3; + handle = CPools::GetObjectPool()->GetJustIndex_NoFreeAssert((CObject*)pEntity) + 1; + break; + case ENTITY_TYPE_DUMMY: + type = 4; + handle = CPools::GetDummyPool()->GetJustIndex_NoFreeAssert((CDummy*)pEntity) + 1; + default: break; + } + } + WriteSaveBuf(buf, type); + WriteSaveBuf(buf, handle); + } + WriteSaveBuf(buf, bUsingAMultiScriptFile); + WriteSaveBuf(buf, (uint8)0); + WriteSaveBuf(buf, (uint16)0); + WriteSaveBuf(buf, MainScriptSize); + WriteSaveBuf(buf, LargestMissionScriptSize); + WriteSaveBuf(buf, NumberOfMissionScripts); + WriteSaveBuf(buf, (uint16)0); + WriteSaveBuf(buf, runningScripts); + for (CRunningScript* pScript = pActiveScripts; pScript; pScript = pScript->GetNext()) + pScript->Save(buf); +VALIDATESAVEBUF(*size) +} + +void CTheScripts::LoadAllScripts(uint8* buf, uint32 size) +{ + Init(); +INITSAVEBUF + CheckSaveHeader(buf, 'S', 'C', 'R', '\0', size - SAVE_HEADER_SIZE); + uint32 varSpace, type, handle; + uint32 tmp; + + ReadSaveBuf(&varSpace, buf); + for (uint32 i = 0; i < varSpace; i++) + ReadSaveBuf(&ScriptSpace[i], buf); + ReadSaveBuf(&tmp, buf); + script_assert(tmp == SCRIPT_DATA_SIZE); + ReadSaveBuf(&OnAMissionFlag, buf); + for (uint32 i = 0; i < MAX_NUM_CONTACTS; i++) { + ReadSaveBuf(&OnAMissionForContactFlag[i], buf); + ReadSaveBuf(&BaseBriefIdForContact[i], buf); + } + for (uint32 i = 0; i < MAX_NUM_COLLECTIVES; i++) + ReadSaveBuf(&CollectiveArray[i], buf); + ReadSaveBuf(&NextFreeCollectiveIndex, buf); + for (uint32 i = 0; i < MAX_NUM_BUILDING_SWAPS; i++) { + ReadSaveBuf(&type, buf); + ReadSaveBuf(&handle, buf); + switch (type) { + case 0: + BuildingSwapArray[i].m_pBuilding = nil; + break; + case 1: + BuildingSwapArray[i].m_pBuilding = CPools::GetTreadablePool()->GetSlot(handle - 1); + break; + case 2: + BuildingSwapArray[i].m_pBuilding = CPools::GetBuildingPool()->GetSlot(handle - 1); + break; + default: + script_assert(false); + } + ReadSaveBuf(&BuildingSwapArray[i].m_nNewModel, buf); + ReadSaveBuf(&BuildingSwapArray[i].m_nOldModel, buf); + if (BuildingSwapArray[i].m_pBuilding) + BuildingSwapArray[i].m_pBuilding->ReplaceWithNewModel(BuildingSwapArray[i].m_nNewModel); + } + for (uint32 i = 0; i < MAX_NUM_INVISIBILITY_SETTINGS; i++) { + ReadSaveBuf(&type, buf); + ReadSaveBuf(&handle, buf); + switch (type) { + case 0: + InvisibilitySettingArray[i] = nil; + break; + case 1: + InvisibilitySettingArray[i] = CPools::GetTreadablePool()->GetSlot(handle - 1); + break; + case 2: + InvisibilitySettingArray[i] = CPools::GetBuildingPool()->GetSlot(handle - 1); + break; + case 3: + InvisibilitySettingArray[i] = CPools::GetObjectPool()->GetSlot(handle - 1); + break; + case 4: + InvisibilitySettingArray[i] = CPools::GetDummyPool()->GetSlot(handle - 1); + break; + default: + script_assert(false); + } + if (InvisibilitySettingArray[i]) + InvisibilitySettingArray[i]->bIsVisible = false; + } + bool tmpBool; + ReadSaveBuf(&tmpBool, buf); + script_assert(tmpBool == bUsingAMultiScriptFile); + SkipSaveBuf(buf, 3); + ReadSaveBuf(&tmp, buf); + script_assert(tmp == MainScriptSize); + ReadSaveBuf(&tmp, buf); + script_assert(tmp == LargestMissionScriptSize); + uint16 tmp16; + ReadSaveBuf(&tmp16, buf); + script_assert(tmp16 == NumberOfMissionScripts); + SkipSaveBuf(buf, 2); + uint32 runningScripts; + ReadSaveBuf(&runningScripts, buf); + for (uint32 i = 0; i < runningScripts; i++) + StartNewScript(0)->Load(buf); +VALIDATESAVEBUF(size) +} + +#undef SCRIPT_DATA_SIZE + +void CRunningScript::Save(uint8*& buf) +{ +#ifdef COMPATIBLE_SAVES + ZeroSaveBuf(buf, 8); + for (int i = 0; i < 8; i++) + WriteSaveBuf(buf, m_abScriptName[i]); + WriteSaveBuf(buf, m_nIp); +#ifdef CHECK_STRUCT_SIZES + static_assert(MAX_STACK_DEPTH == 6, "Compatibility loss: MAX_STACK_DEPTH != 6"); +#endif + for (int i = 0; i < MAX_STACK_DEPTH; i++) + WriteSaveBuf(buf, m_anStack[i]); + WriteSaveBuf(buf, m_nStackPointer); + ZeroSaveBuf(buf, 2); +#ifdef CHECK_STRUCT_SIZES + static_assert(NUM_LOCAL_VARS + NUM_TIMERS == 18, "Compatibility loss: NUM_LOCAL_VARS + NUM_TIMERS != 18"); +#endif + for (int i = 0; i < NUM_LOCAL_VARS + NUM_TIMERS; i++) + WriteSaveBuf(buf, m_anLocalVariables[i]); + WriteSaveBuf(buf, m_bCondResult); + WriteSaveBuf(buf, m_bIsMissionScript); + WriteSaveBuf(buf, m_bSkipWakeTime); + ZeroSaveBuf(buf, 1); + WriteSaveBuf(buf, m_nWakeTime); + WriteSaveBuf(buf, m_nAndOrState); + WriteSaveBuf(buf, m_bNotFlag); + WriteSaveBuf(buf, m_bDeatharrestEnabled); + WriteSaveBuf(buf, m_bDeatharrestExecuted); + WriteSaveBuf(buf, m_bMissionFlag); + ZeroSaveBuf(buf, 2); +#else + WriteSaveBuf(buf, *this); +#endif +} + +void CRunningScript::Load(uint8*& buf) +{ +#ifdef COMPATIBLE_SAVES + SkipSaveBuf(buf, 8); + for (int i = 0; i < 8; i++) + ReadSaveBuf(&m_abScriptName[i], buf); + ReadSaveBuf(&m_nIp, buf); +#ifdef CHECK_STRUCT_SIZES + static_assert(MAX_STACK_DEPTH == 6, "Compatibility loss: MAX_STACK_DEPTH != 6"); +#endif + for (int i = 0; i < MAX_STACK_DEPTH; i++) + ReadSaveBuf(&m_anStack[i], buf); + ReadSaveBuf(&m_nStackPointer, buf); + SkipSaveBuf(buf, 2); +#ifdef CHECK_STRUCT_SIZES + static_assert(NUM_LOCAL_VARS + NUM_TIMERS == 18, "Compatibility loss: NUM_LOCAL_VARS + NUM_TIMERS != 18"); +#endif + for (int i = 0; i < NUM_LOCAL_VARS + NUM_TIMERS; i++) + ReadSaveBuf(&m_anLocalVariables[i], buf); + ReadSaveBuf(&m_bCondResult, buf); + ReadSaveBuf(&m_bIsMissionScript, buf); + ReadSaveBuf(&m_bSkipWakeTime, buf); + SkipSaveBuf(buf, 1); + ReadSaveBuf(&m_nWakeTime, buf); + ReadSaveBuf(&m_nAndOrState, buf); + ReadSaveBuf(&m_bNotFlag, buf); + ReadSaveBuf(&m_bDeatharrestEnabled, buf); + ReadSaveBuf(&m_bDeatharrestExecuted, buf); + ReadSaveBuf(&m_bMissionFlag, buf); + SkipSaveBuf(buf, 2); +#else + CRunningScript* n = next; + CRunningScript* p = prev; + ReadSaveBuf(this, buf); + next = n; + prev = p; +#endif +} + +void CTheScripts::ClearSpaceForMissionEntity(const CVector& pos, CEntity* pEntity) +{ + static CColPoint aTempColPoints[MAX_COLLISION_POINTS]; + int16 entities = 0; + CEntity* aEntities[16]; + CWorld::FindObjectsKindaColliding(pos, pEntity->GetBoundRadius(), false, &entities, 16, aEntities, false, true, true, false, false); + if (entities <= 0) + return; + for (uint16 i = 0; i < entities; i++) { + if (aEntities[i] != pEntity && aEntities[i]->IsPed() && ((CPed*)aEntities[i])->bInVehicle) + aEntities[i] = nil; + } + for (uint16 i = 0; i < entities; i++) { + if (aEntities[i] == pEntity || !aEntities[i]) + continue; + CEntity* pFound = aEntities[i]; + int cols; + if (pEntity->GetColModel()->numLines <= 0) + cols = CCollision::ProcessColModels(pEntity->GetMatrix(), *pEntity->GetColModel(), + pFound->GetMatrix(), *pFound->GetColModel(), aTempColPoints, nil, nil); + else { + float lines[4]; + lines[0] = lines[1] = lines[2] = lines[3] = 1.0f; + CColPoint tmp[4]; + cols = CCollision::ProcessColModels(pEntity->GetMatrix(), *pEntity->GetColModel(), + pFound->GetMatrix(), *pFound->GetColModel(), aTempColPoints,tmp, lines); + } + if (cols <= 0) + continue; + switch (pFound->GetType()) { + case ENTITY_TYPE_VEHICLE: + { + printf("Will try to delete a vehicle where a mission entity should be\n"); + CVehicle* pVehicle = (CVehicle*)pFound; + if (pVehicle->bIsLocked || !pVehicle->CanBeDeleted()) + break; + if (pVehicle->pDriver) { + CPopulation::RemovePed(pVehicle->pDriver); + pVehicle->pDriver = nil; + } + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) { + if (pVehicle->pPassengers[i]) { + CPopulation::RemovePed(pVehicle->pPassengers[i]); + pVehicle->pPassengers[i] = 0; + pVehicle->m_nNumPassengers--; + } + } + CCarCtrl::RemoveFromInterestingVehicleList(pVehicle); + CWorld::Remove(pVehicle); + delete pVehicle; + break; + } + case ENTITY_TYPE_PED: + { + CPed* pPed = (CPed*)pFound; + if (pPed->IsPlayer() || !pPed->CanBeDeleted()) + break; + CPopulation::RemovePed(pPed); + printf("Deleted a ped where a mission entity should be\n"); + break; + } + default: break; + } + } +} + +void CTheScripts::HighlightImportantArea(uint32 id, float x1, float y1, float x2, float y2, float z) +{ + float infX, infY, supX, supY; + if (x1 < x2) { + infX = x1; + supX = x2; + } else { + infX = x2; + supX = x1; + } + if (y1 < y2) { + infY = y1; + supY = y2; + } + else { + infY = y2; + supY = y1; + } + CVector center; + center.x = (infX + supX) / 2; + center.y = (infY + supY) / 2; + center.z = (z <= MAP_Z_LOW_LIMIT) ? CWorld::FindGroundZForCoord(center.x, center.y) : z; + CShadows::RenderIndicatorShadow(id, 2, gpGoalTex, ¢er, supX - center.x, 0.0f, 0.0f, center.y - supY, 0); +} + +void CTheScripts::HighlightImportantAngledArea(uint32 id, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float z) +{ + float infX, infY, supX, supY, X, Y; + X = (x1 + x2) / 2; + Y = (y1 + y2) / 2; + supX = infX = X; + supY = infY = Y; + X = (x2 + x3) / 2; + Y = (y2 + y3) / 2; + infX = Min(infX, X); + supX = Max(supX, X); + infY = Min(infY, Y); + supY = Max(supY, Y); + X = (x3 + x4) / 2; + Y = (y3 + y4) / 2; + infX = Min(infX, X); + supX = Max(supX, X); + infY = Min(infY, Y); + supY = Max(supY, Y); + X = (x4 + x1) / 2; + Y = (y4 + y1) / 2; + infX = Min(infX, X); + supX = Max(supX, X); + infY = Min(infY, Y); + supY = Max(supY, Y); + CVector center; + center.x = (infX + supX) / 2; + center.y = (infY + supY) / 2; + center.z = (z <= MAP_Z_LOW_LIMIT) ? CWorld::FindGroundZForCoord(center.x, center.y) : z; + CShadows::RenderIndicatorShadow(id, 2, gpGoalTex, ¢er, supX - center.x, 0.0f, 0.0f, center.y - supY, 0); +} + +#ifdef GTA_SCRIPT_COLLECTIVE +int CTheScripts::AddPedsInVehicleToCollective(int index) +{ + int colIndex = NextFreeCollectiveIndex; + AdvanceCollectiveIndex(); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(index); + script_assert(pVehicle); + CPed* pDriver = pVehicle->pDriver; + if (pDriver && !pDriver->IsPlayer() && pDriver->CharCreatedBy != MISSION_CHAR && pDriver->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pDriver); + } + } + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) { + CPed* pPassenger = pVehicle->pPassengers[i]; + if (pPassenger && !pPassenger->IsPlayer() && pPassenger->CharCreatedBy != MISSION_CHAR && pPassenger->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pPassenger); + } + } + } + return colIndex; +} + +int CTheScripts::AddPedsInAreaToCollective(float x, float y, float z, float radius) +{ + int16 numFound; + CEntity* pEntities[64]; + int colIndex = NextFreeCollectiveIndex; + AdvanceCollectiveIndex(); + CWorld::FindObjectsInRange(CVector(x, y, z), radius, true, &numFound, 64, pEntities, false, true, true, false, false); + for (int16 i = 0; i < numFound; i++) { + if (pEntities[i]->GetType() == ENTITY_TYPE_PED) { + CPed* pPed = (CPed*)pEntities[i]; + if (pPed && !pPed->IsPlayer() && pPed->CharCreatedBy != MISSION_CHAR && pPed->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pPed); + } + } + } + else if (pEntities[i]->GetType() == ENTITY_TYPE_VEHICLE) { + CVehicle* pVehicle = (CVehicle*)pEntities[i]; + CPed* pDriver = pVehicle->pDriver; + if (pDriver && !pDriver->IsPlayer() && pDriver->CharCreatedBy != MISSION_CHAR && pDriver->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pDriver); + } + } + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) { + CPed* pPassenger = pVehicle->pPassengers[i]; + if (pPassenger && !pPassenger->IsPlayer() && pPassenger->CharCreatedBy != MISSION_CHAR && pPassenger->m_nPedType != PEDTYPE_COP) { + int index = FindFreeSlotInCollectiveArray(); + if (index > -1) { + CollectiveArray[index].colIndex = colIndex; + CollectiveArray[index].pedIndex = CPools::GetPedPool()->GetIndex(pPassenger); + } + } + } + } + } + return colIndex; +} + +int CTheScripts::FindFreeSlotInCollectiveArray() +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == -1) + return i; + } + return -1; +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective, int16 p1, int16 p2) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective, p1, p2); + } + } + } +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective, CVector p1, float p2) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective, p1, p2); + } + } + } +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective, CVector p1) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective, p1); + } + } + } +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective, void* p1) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective, p1); + } + } + } +} + +void CTheScripts::SetObjectiveForAllPedsInCollective(int colIndex, eObjective objective) +{ + for (int i = 0; i < MAX_NUM_COLLECTIVES; i++) { + if (CollectiveArray[i].colIndex == colIndex) { + CPed* pPed = CPools::GetPedPool()->GetAt(CollectiveArray[i].pedIndex); + if (pPed == nil) { + CollectiveArray[i].colIndex = -1; + CollectiveArray[i].pedIndex = 0; + } + else { + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(objective); + } + } + } +} +#endif //GTA_SCRIPT_COLLECTIVE + +bool CTheScripts::IsPedStopped(CPed* pPed) +{ + if (pPed->bInVehicle) + return IsVehicleStopped(pPed->m_pMyVehicle); + return pPed->m_nMoveState == PEDMOVE_NONE || pPed->m_nMoveState == PEDMOVE_STILL; +} + +bool CTheScripts::IsPlayerStopped(CPlayerInfo* pPlayer) +{ + CPed* pPed = pPlayer->m_pPed; + if (pPed->bInVehicle) + return IsVehicleStopped(pPed->m_pMyVehicle); + if (RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_RUNSTOP1) || + RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_RUNSTOP2) || + RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_JUMP_LAUNCH) || + RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_JUMP_GLIDE)) + return false; + return pPed->m_nMoveState == PEDMOVE_NONE || pPed->m_nMoveState == PEDMOVE_STILL; +} + +bool CTheScripts::IsVehicleStopped(CVehicle* pVehicle) +{ + return 0.01f * CTimer::GetTimeStep() >= pVehicle->m_fDistanceTravelled; +} + +void CTheScripts::CleanUpThisPed(CPed* pPed) +{ + if (!pPed) + return; + if (pPed->CharCreatedBy != MISSION_CHAR) + return; + pPed->CharCreatedBy = RANDOM_CHAR; + if (pPed->m_nPedType == PEDTYPE_PROSTITUTE) + pPed->m_objectiveTimer = CTimer::GetTimeInMilliseconds() + 30000; + if (pPed->bInVehicle) { + if (pPed->m_pMyVehicle->pDriver == pPed) { + if (pPed->m_pMyVehicle->m_vehType == VEHICLE_TYPE_CAR) { + CCarCtrl::JoinCarWithRoadSystem(pPed->m_pMyVehicle); + pPed->m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + } + } + else { + if (pPed->m_pMyVehicle->m_vehType == VEHICLE_TYPE_CAR) { + pPed->SetObjective(OBJECTIVE_LEAVE_CAR, pPed->m_pMyVehicle); + pPed->bWanderPathAfterExitingCar = true; + } + } + } + bool flees = false; + PedState state; + eMoveState ms; + if (pPed->m_nPedState == PED_FLEE_ENTITY || pPed->m_nPedState == PED_FLEE_POS) { + ms = pPed->m_nMoveState; + state = pPed->m_nPedState; + flees = true; + } + pPed->ClearObjective(); + pPed->bRespondsToThreats = true; + pPed->bScriptObjectiveCompleted = false; + pPed->ClearLeader(); + if (pPed->IsPedInControl()) + pPed->SetWanderPath(CGeneral::GetRandomNumber() & 7); + if (flees) { + pPed->SetPedState(state); + pPed->SetMoveState(ms); + } + --CPopulation::ms_nTotalMissionPeds; +} + +void CTheScripts::CleanUpThisVehicle(CVehicle* pVehicle) +{ + if (!pVehicle) + return; + if (pVehicle->VehicleCreatedBy != MISSION_VEHICLE) + return; + pVehicle->bIsLocked = false; + CCarCtrl::RemoveFromInterestingVehicleList(pVehicle); + pVehicle->VehicleCreatedBy = RANDOM_VEHICLE; + ++CCarCtrl::NumRandomCars; + --CCarCtrl::NumMissionCars; +} + +void CTheScripts::CleanUpThisObject(CObject* pObject) +{ + if (!pObject) + return; + if (pObject->ObjectCreatedBy != MISSION_OBJECT) + return; + pObject->ObjectCreatedBy = TEMP_OBJECT; + pObject->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000; + pObject->m_nRefModelIndex = -1; + pObject->bUseVehicleColours = false; + ++CObject::nNoTempObjects; +} + +void CTheScripts::ReadObjectNamesFromScript() +{ + int32 varSpace = GetSizeOfVariableSpace(); + uint32 ip = varSpace + 8; + NumberOfUsedObjects = Read2BytesFromScript(&ip); + ip += 2; + for (uint16 i = 0; i < NumberOfUsedObjects; i++) { + for (int j = 0; j < USED_OBJECT_NAME_LENGTH; j++) + UsedObjectArray[i].name[j] = ScriptSpace[ip++]; + UsedObjectArray[i].index = 0; + } +} + +void CTheScripts::UpdateObjectIndices() +{ + char name[USED_OBJECT_NAME_LENGTH]; + char error[112]; + for (int i = 1; i < NumberOfUsedObjects; i++) { + bool found = false; + for (int j = 0; j < MODELINFOSIZE && !found; j++) { + CBaseModelInfo* pModel = CModelInfo::GetModelInfo(j); + if (!pModel) + continue; + strcpy(name, pModel->GetModelName()); +#ifdef FIX_BUGS + for (int k = 0; k < USED_OBJECT_NAME_LENGTH && name[k]; k++) +#else + for (int k = 0; k < USED_OBJECT_NAME_LENGTH; k++) +#endif + name[k] = toupper(name[k]); + if (strcmp(name, UsedObjectArray[i].name) == 0) { + found = true; + UsedObjectArray[i].index = j; + } + } + if (!found) { + sprintf(error, "CTheScripts::UpdateObjectIndices - Couldn't find %s", UsedObjectArray[i].name); + debug("%s\n", error); + } + } +} + +void CTheScripts::ReadMultiScriptFileOffsetsFromScript() +{ + int32 varSpace = GetSizeOfVariableSpace(); + uint32 ip = varSpace + 3; + int32 objectSize = Read4BytesFromScript(&ip); + ip = objectSize + 8; + MainScriptSize = Read4BytesFromScript(&ip); + LargestMissionScriptSize = Read4BytesFromScript(&ip); + NumberOfMissionScripts = Read2BytesFromScript(&ip); + ip += 2; + for (int i = 0; i < NumberOfMissionScripts; i++) { + MultiScriptArray[i] = Read4BytesFromScript(&ip); + } +} diff --git a/src/control/Script6.cpp b/src/control/Script6.cpp new file mode 100644 index 0000000..c576e3c --- /dev/null +++ b/src/control/Script6.cpp @@ -0,0 +1,1356 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "Bike.h" +#include "CarCtrl.h" +#include "Cranes.h" +#include "Credits.h" +#include "CutsceneMgr.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "Fire.h" +#include "Frontend.h" +#include "Garages.h" +#include "General.h" +#ifdef MISSION_REPLAY +#include "GenericGameStorage.h" +#endif +#include "Messages.h" +#include "Pad.h" +#include "Particle.h" +#include "Phones.h" +#include "Population.h" +#include "Pools.h" +#include "Record.h" +#include "Remote.h" +#include "Restart.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "Streaming.h" +#include "Weather.h" +#include "Zones.h" +#include "main.h" + +// NB: on PS2 this file did not exist; ProcessCommands1000To1099 was in Script5.cpp and ProcessCommands1100To1199 was only added on PC +// however to avoid redundant copies of code, Script6.cpp is used with PS2 defines + +int8 CRunningScript::ProcessCommands1000To1099(int32 command) +{ +#if GTA_VERSION <= GTA3_PS2_160 + char tmp[48]; +#endif + switch (command) { + //case COMMAND_FLASH_RADAR_BLIP: + case COMMAND_IS_CHAR_IN_CONTROL: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(pPed->IsPedInControl()); + return 0; + } + case COMMAND_SET_GENERATE_CARS_AROUND_CAMERA: + CollectParameters(&m_nIp, 1); + CCarCtrl::bCarsGeneratedAroundCamera = (ScriptParams[0] != 0); + return 0; + case COMMAND_CLEAR_SMALL_PRINTS: + CMessages::ClearSmallMessagesOnly(); + return 0; + case COMMAND_HAS_MILITARY_CRANE_COLLECTED_ALL_CARS: + UpdateCompareFlag(CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()); + return 0; + case COMMAND_SET_UPSIDEDOWN_CAR_NOT_DAMAGED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + CAutomobile* pCar = (CAutomobile*)pVehicle; + pCar->bNotDamagedUpsideDown = (ScriptParams[1] != 0); + return 0; + } + case COMMAND_CAN_PLAYER_START_MISSION: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPlayerPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPlayerPed); + UpdateCompareFlag(pPlayerPed->IsPedInControl() || pPlayerPed->m_nPedState == PED_DRIVING); + return 0; + } + case COMMAND_MAKE_PLAYER_SAFE_FOR_CUTSCENE: + { + CollectParameters(&m_nIp, 1); +#ifdef MISSION_REPLAY + AllowMissionReplay = MISSION_RETRY_STAGE_NORMAL; + SaveGameForPause(SAVE_TYPE_QUICKSAVE_FOR_MISSION_REPLAY); +#endif + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + CPad::GetPad(ScriptParams[0])->SetDisablePlayerControls(PLAYERCONTROL_CUTSCENE); + pPlayerInfo->MakePlayerSafe(true); + CCutsceneMgr::StartCutsceneProcessing(); + return 0; + } + case COMMAND_USE_TEXT_COMMANDS: + CollectParameters(&m_nIp, 1); + CTheScripts::UseTextCommands = (ScriptParams[0] != 0) ? 2 : 1; + return 0; + case COMMAND_SET_THREAT_FOR_PED_TYPE: + CollectParameters(&m_nIp, 2); + CPedType::AddThreat(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_CLEAR_THREAT_FOR_PED_TYPE: + CollectParameters(&m_nIp, 2); + CPedType::RemoveThreat(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_GET_CAR_COLOURS: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->m_currentColour1; + ScriptParams[1] = pVehicle->m_currentColour2; + StoreParameters(&m_nIp, 2); + return 0; + } + case COMMAND_SET_ALL_CARS_CAN_BE_DAMAGED: + CollectParameters(&m_nIp, 1); + CWorld::SetAllCarsCanBeDamaged(ScriptParams[0] != 0); + if (!ScriptParams[0]) + CWorld::ExtinguishAllCarFiresInArea(FindPlayerCoors(), 4000.0f); + return 0; + case COMMAND_SET_CAR_CAN_BE_DAMAGED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + pVehicle->bCanBeDamaged = ScriptParams[1] != 0; + if (!ScriptParams[1]) + pVehicle->ExtinguishCarFire(); + return 0; + } + //case COMMAND_MAKE_PLAYER_UNSAFE: + case COMMAND_LOAD_COLLISION: + { + CollectParameters(&m_nIp, 1); + CTimer::Stop(); + CGame::currLevel = (eLevelName)ScriptParams[0]; + ISLAND_LOADING_IS(LOW) + { + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + } + CCollision::SortOutCollisionAfterLoad(); + ISLAND_LOADING_ISNT(HIGH) + { + CStreaming::RequestIslands(CGame::currLevel); + CStreaming::LoadAllRequestedModels(true); + } + CTimer::Update(); + return 0; + } + case COMMAND_GET_BODY_CAST_HEALTH: + ScriptParams[0] = CObject::nBodyCastHealth; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_SET_CHARS_CHATTING: + { + CollectParameters(&m_nIp, 3); + CPed* pPed1 = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pPed2 = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pPed1 && pPed2); + pPed1->SetChat(pPed2, ScriptParams[2]); + pPed2->SetChat(pPed1, ScriptParams[2]); + return 0; + } + //case COMMAND_MAKE_PLAYER_SAFE: + case COMMAND_SET_CAR_STAYS_IN_CURRENT_LEVEL: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + if (ScriptParams[1]) + pVehicle->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pVehicle->GetPosition()); + else + pVehicle->m_nZoneLevel = LEVEL_GENERIC; + return 0; + } + case COMMAND_SET_CHAR_STAYS_IN_CURRENT_LEVEL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) + pPed->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pPed->GetPosition()); + else + pPed->m_nZoneLevel = LEVEL_GENERIC; + return 0; + } + case COMMAND_REGISTER_4X4_ONE_TIME: + CollectParameters(&m_nIp, 1); + CStats::Register4x4OneTime(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_4X4_TWO_TIME: + CollectParameters(&m_nIp, 1); + CStats::Register4x4TwoTime(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_4X4_THREE_TIME: + CollectParameters(&m_nIp, 1); + CStats::Register4x4ThreeTime(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_4X4_MAYHEM_TIME: + CollectParameters(&m_nIp, 1); + CStats::Register4x4MayhemTime(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_LIFE_SAVED: + CStats::AnotherLifeSavedWithAmbulance(); + return 0; + case COMMAND_REGISTER_CRIMINAL_CAUGHT: + CStats::AnotherCriminalCaught(); + return 0; + case COMMAND_REGISTER_AMBULANCE_LEVEL: + CollectParameters(&m_nIp, 1); + CStats::RegisterLevelAmbulanceMission(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_FIRE_EXTINGUISHED: + CStats::AnotherFireExtinguished(); + return 0; + case COMMAND_TURN_PHONE_ON: + CollectParameters(&m_nIp, 1); + gPhoneInfo.m_aPhones[ScriptParams[0]].m_nState = PHONE_STATE_9; + return 0; + case COMMAND_REGISTER_LONGEST_DODO_FLIGHT: + CollectParameters(&m_nIp, 1); + CStats::RegisterLongestFlightInDodo(ScriptParams[0]); + return 0; + case COMMAND_REGISTER_DEFUSE_BOMB_TIME: + CollectParameters(&m_nIp, 1); + CStats::RegisterTimeTakenDefuseMission(ScriptParams[0]); + return 0; + case COMMAND_SET_TOTAL_NUMBER_OF_KILL_FRENZIES: + CollectParameters(&m_nIp, 1); + CStats::SetTotalNumberKillFrenzies(ScriptParams[0]); + return 0; + case COMMAND_BLOW_UP_RC_BUGGY: + CWorld::Players[CWorld::PlayerInFocus].BlowUpRCBuggy(); + return 0; + case COMMAND_REMOVE_CAR_FROM_CHASE: + CollectParameters(&m_nIp, 1); + CRecordDataForChase::RemoveCarFromChase(ScriptParams[0]); + return 0; + case COMMAND_IS_FRENCH_GAME: + UpdateCompareFlag(CGame::frenchGame); + return 0; + case COMMAND_IS_GERMAN_GAME: + UpdateCompareFlag(CGame::germanGame); + return 0; + case COMMAND_CLEAR_MISSION_AUDIO: + DMAudio.ClearMissionAudio(); + return 0; + case COMMAND_SET_FADE_IN_AFTER_NEXT_ARREST: + CollectParameters(&m_nIp, 1); + CRestart::bFadeInAfterNextArrest = !!ScriptParams[0]; + return 0; + case COMMAND_SET_FADE_IN_AFTER_NEXT_DEATH: + CollectParameters(&m_nIp, 1); + CRestart::bFadeInAfterNextDeath = !!ScriptParams[0]; + return 0; + case COMMAND_SET_GANG_PED_MODEL_PREFERENCE: + CollectParameters(&m_nIp, 2); + CGangs::SetGangPedModelOverride(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_SET_CHAR_USE_PEDNODE_SEEK: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) + pPed->m_pNextPathNode = nil; + pPed->bUsePedNodeSeek = !!ScriptParams[1]; + return 0; + } + case COMMAND_SWITCH_VEHICLE_WEAPONS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bGunSwitchedOff = !ScriptParams[1]; + return 0; + } + case COMMAND_SET_GET_OUT_OF_JAIL_FREE: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_bGetOutOfJailFree = !!ScriptParams[1]; + return 0; + case COMMAND_SET_FREE_HEALTH_CARE: + CollectParameters(&m_nIp, 2); + CWorld::Players[ScriptParams[0]].m_bGetOutOfHospitalFree = !!ScriptParams[1]; + return 0; + case COMMAND_IS_CAR_DOOR_CLOSED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(!pVehicle->IsDoorMissing((eDoors)ScriptParams[1]) && pVehicle->IsDoorClosed((eDoors)ScriptParams[1])); + return 0; + } + case COMMAND_LOAD_AND_LAUNCH_MISSION: + return 0; + case COMMAND_LOAD_AND_LAUNCH_MISSION_INTERNAL: + { + CollectParameters(&m_nIp, 1); +#ifdef MISSION_REPLAY + missionRetryScriptIndex = ScriptParams[0]; + if (missionRetryScriptIndex == 19) + CStats::LastMissionPassedName[0] = '\0'; +#endif + CTimer::Suspend(); + int offset = CTheScripts::MultiScriptArray[ScriptParams[0]]; +#ifdef USE_DEBUG_SCRIPT_LOADER + int handle = CTheScripts::OpenScript(); +#else + CFileMgr::ChangeDir("\\"); + int handle = CFileMgr::OpenFile("data\\main.scm", "rb"); +#endif + CFileMgr::Seek(handle, offset, 0); + CFileMgr::Read(handle, (const char*)&CTheScripts::ScriptSpace[SIZE_MAIN_SCRIPT], SIZE_MISSION_SCRIPT); + CFileMgr::CloseFile(handle); + CRunningScript* pMissionScript = CTheScripts::StartNewScript(SIZE_MAIN_SCRIPT); + CTimer::Resume(); + pMissionScript->m_bIsMissionScript = true; + pMissionScript->m_bMissionFlag = true; + CTheScripts::bAlreadyRunningAMissionScript = true; + return 0; + } + case COMMAND_SET_OBJECT_DRAW_LAST: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->bDrawLast = !!ScriptParams[1]; + return 0; + } + case COMMAND_GET_AMMO_IN_PLAYER_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CWeapon* pWeaponSlot = &pPed->m_weapons[ScriptParams[1]]; + if (pWeaponSlot->m_eWeaponType == (eWeaponType)ScriptParams[1]) + ScriptParams[0] = pWeaponSlot->m_nAmmoTotal; + else + ScriptParams[0] = 0; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_AMMO_IN_CHAR_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CWeapon* pWeaponSlot = &pPed->m_weapons[ScriptParams[1]]; + if (pWeaponSlot->m_eWeaponType == (eWeaponType)ScriptParams[1]) + ScriptParams[0] = pWeaponSlot->m_nAmmoTotal; + else + ScriptParams[0] = 0; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_REGISTER_KILL_FRENZY_PASSED: + CStats::AnotherKillFrenzyPassed(); + return 0; + case COMMAND_SET_CHAR_SAY: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + switch (ScriptParams[1]) { + case SCRIPT_SOUND_CHUNKY_RUN_SHOUT: + pPed->Say(SOUND_PED_FLEE_RUN); + break; + case SCRIPT_SOUND_SECURITY_GUARD_AWAY_SHOUT: + pPed->Say(SOUND_PED_FLEE_RUN); + break; + case SCRIPT_SOUND_SWAT_PED_SHOUT: + pPed->Say(SOUND_PED_PURSUIT_SWAT); + break; + case SCRIPT_SOUND_AMMUNATION_CHAT_1: + pPed->Say(SOUND_AMMUNATION_WELCOME_1); + break; + case SCRIPT_SOUND_AMMUNATION_CHAT_2: + pPed->Say(SOUND_AMMUNATION_WELCOME_2); + break; + case SCRIPT_SOUND_AMMUNATION_CHAT_3: + pPed->Say(SOUND_AMMUNATION_WELCOME_3); + break; + default: + break; + } + return 0; + } + case COMMAND_SET_NEAR_CLIP: + CollectParameters(&m_nIp, 1); + TheCamera.SetNearClipScript(*(float*)&ScriptParams[0]); + return 0; + case COMMAND_SET_RADIO_CHANNEL: + CollectParameters(&m_nIp, 2); + DMAudio.SetRadioChannel(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_OVERRIDE_HOSPITAL_LEVEL: + CollectParameters(&m_nIp, 1); + CRestart::OverrideHospitalLevel = ScriptParams[0]; + return 0; + case COMMAND_OVERRIDE_POLICE_STATION_LEVEL: + CollectParameters(&m_nIp, 1); + CRestart::OverridePoliceStationLevel = ScriptParams[0]; + return 0; + case COMMAND_FORCE_RAIN: + CollectParameters(&m_nIp, 1); + CWeather::bScriptsForceRain = !!ScriptParams[0]; + return 0; + case COMMAND_DOES_GARAGE_CONTAIN_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + UpdateCompareFlag(CGarages::IsThisCarWithinGarageArea(ScriptParams[0], pVehicle)); + return 0; + } + case COMMAND_SET_CAR_TRACTION: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + float fTraction = *(float*)&ScriptParams[1]; + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR || pVehicle->m_vehType == VEHICLE_TYPE_BIKE); + if (pVehicle->m_vehType == VEHICLE_TYPE_CAR) + ((CAutomobile*)pVehicle)->m_fTraction = fTraction; + else + // this is certainly not a boat, trane, heli or plane field + ((CBike*)pVehicle)->m_fTraction = fTraction; + return 0; + } + case COMMAND_ARE_MEASUREMENTS_IN_METRES: +#ifdef USE_MEASUREMENTS_IN_METERS + UpdateCompareFlag(true); +#else + UpdateCompareFlag(false); +#endif + return 0; + case COMMAND_CONVERT_METRES_TO_FEET: + { + CollectParameters(&m_nIp, 1); + float fMeterValue = *(float*)&ScriptParams[0]; + float fFeetValue = fMeterValue / METERS_IN_FOOT; + *(float*)&ScriptParams[0] = fFeetValue; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_MARK_ROADS_BETWEEN_LEVELS: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.MarkRoadsBetweenLevelsInArea(infX, supX, infY, supY, infZ, supZ); + return 0; + } + case COMMAND_MARK_PED_ROADS_BETWEEN_LEVELS: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + ThePaths.PedMarkRoadsBetweenLevelsInArea(infX, supX, infY, supY, infZ, supZ); + return 0; + } + case COMMAND_SET_CAR_AVOID_LEVEL_TRANSITIONS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_bStayInCurrentLevel = !!ScriptParams[1]; + return 0; + } + case COMMAND_SET_CHAR_AVOID_LEVEL_TRANSITIONS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pPed); + // not implemented + return 0; + } + case COMMAND_IS_THREAT_FOR_PED_TYPE: + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(CPedType::IsThreat(ScriptParams[0], ScriptParams[1])); + return 0; + case COMMAND_CLEAR_AREA_OF_CHARS: + { + CollectParameters(&m_nIp, 6); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float supX = *(float*)&ScriptParams[3]; + float supY = *(float*)&ScriptParams[4]; + float supZ = *(float*)&ScriptParams[5]; + if (infX > supX) { + infX = *(float*)&ScriptParams[3]; + supX = *(float*)&ScriptParams[0]; + } + if (infY > supY) { + infY = *(float*)&ScriptParams[4]; + supY = *(float*)&ScriptParams[1]; + } + if (infZ > supZ) { + infZ = *(float*)&ScriptParams[5]; + supZ = *(float*)&ScriptParams[2]; + } + CWorld::ClearPedsFromArea(infX, infY, infZ, supX, supY, supZ); + return 0; + } + case COMMAND_SET_TOTAL_NUMBER_OF_MISSIONS: + CollectParameters(&m_nIp, 1); + CStats::SetTotalNumberMissions(ScriptParams[0]); + return 0; + case COMMAND_CONVERT_METRES_TO_FEET_INT: + CollectParameters(&m_nIp, 1); + ScriptParams[0] *= FEET_IN_METER; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_REGISTER_FASTEST_TIME: + CollectParameters(&m_nIp, 2); + CStats::RegisterFastestTime(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_REGISTER_HIGHEST_SCORE: + CollectParameters(&m_nIp, 2); + CStats::RegisterHighestScore(ScriptParams[0], ScriptParams[1]); + return 0; + //case COMMAND_WARP_CHAR_INTO_CAR_AS_PASSENGER: + case COMMAND_IS_CAR_PASSENGER_SEAT_FREE: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(ScriptParams[1] < pVehicle->m_nNumMaxPassengers && pVehicle->pPassengers[ScriptParams[1]] == nil); + return 0; + } + case COMMAND_GET_CHAR_IN_CAR_PASSENGER_SEAT: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(ScriptParams[1] >= 0 && ScriptParams[1] < ARRAY_SIZE(pVehicle->pPassengers)); + CPed* pPassenger = pVehicle->pPassengers[ScriptParams[1]]; + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pPassenger); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_IS_CHRIS_CRIMINAL: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bChrisCriminal = !!ScriptParams[1]; + return 0; + } + case COMMAND_START_CREDITS: + CCredits::Start(); + return 0; + case COMMAND_STOP_CREDITS: + CCredits::Stop(); + return 0; + case COMMAND_ARE_CREDITS_FINISHED: + UpdateCompareFlag(CCredits::AreCreditsDone()); + return 0; + case COMMAND_CREATE_SINGLE_PARTICLE: + CollectParameters(&m_nIp, 8); + CParticle::AddParticle((tParticleType)ScriptParams[0], *(CVector*)&ScriptParams[1], + *(CVector*)&ScriptParams[4], nil, *(float*)&ScriptParams[7], 0, 0, 0, 0); + return 0; + case COMMAND_SET_CHAR_IGNORE_LEVEL_TRANSITIONS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) + pPed->m_nZoneLevel = LEVEL_IGNORE; + else + pPed->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pPed->GetPosition()); + return 0; + } + case COMMAND_GET_CHASE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CRecordDataForChase::TurnChaseCarIntoScriptCar(ScriptParams[0]); + ScriptParams[0] = CPools::GetVehiclePool()->GetIndex(pVehicle); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CAR); + return 0; + } + case COMMAND_START_BOAT_FOAM_ANIMATION: + CSpecialParticleStuff::StartBoatFoamAnimation(); + return 0; + case COMMAND_UPDATE_BOAT_FOAM_ANIMATION: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CSpecialParticleStuff::UpdateBoatFoamAnimation(&pObject->GetMatrix()); + return 0; + } + case COMMAND_SET_MUSIC_DOES_FADE: + CollectParameters(&m_nIp, 1); + TheCamera.m_bIgnoreFadingStuffForMusic = (ScriptParams[0] == 0); + return 0; + case COMMAND_SET_INTRO_IS_PLAYING: + CollectParameters(&m_nIp, 1); + if (ScriptParams[0]) { + CGame::playingIntro = true; + CStreaming::RemoveCurrentZonesModels(); + } else { + CGame::playingIntro = false; + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + int mi; + CModelInfo::GetModelInfo("bridgefukb", &mi); + CStreaming::RequestModel(mi, STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + } + return 0; + case COMMAND_SET_PLAYER_HOOKER: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + if (ScriptParams[1] < 0) { + pPlayerInfo->m_pHooker = nil; + pPlayerInfo->m_nNextSexFrequencyUpdateTime = 0; + pPlayerInfo->m_nNextSexMoneyUpdateTime = 0; + } else { + CPed* pHooker = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pHooker); + pPlayerInfo->m_pHooker = (CCivilianPed*)pHooker; + pPlayerInfo->m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 1000; + pPlayerInfo->m_nNextSexMoneyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000; + } + return 0; + } + case COMMAND_PLAY_END_OF_GAME_TUNE: + DMAudio.PlayPreloadedCutSceneMusic(); + return 0; + case COMMAND_STOP_END_OF_GAME_TUNE: + DMAudio.StopCutSceneMusic(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + return 0; + case COMMAND_GET_CAR_MODEL: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + ScriptParams[0] = pVehicle->GetModelIndex(); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_PLAYER_SITTING_IN_CAR: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + UpdateCompareFlag(pPed->GetPedState() == PED_DRIVING && pPed->m_pMyVehicle == pVehicle); + return 0; + } + case COMMAND_IS_PLAYER_SITTING_IN_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->GetPedState() == PED_DRIVING); + return 0; + } + case COMMAND_SET_SCRIPT_FIRE_AUDIO: + CollectParameters(&m_nIp, 2); + gFireManager.SetScriptFireAudio(ScriptParams[0], !!ScriptParams[1]); + return 0; + case COMMAND_ARE_ANY_CAR_CHEATS_ACTIVATED: + UpdateCompareFlag(CVehicle::bAllDodosCheat || CVehicle::bCheat3); + return 0; + case COMMAND_SET_CHAR_SUFFERS_CRITICAL_HITS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bNoCriticalHits = (ScriptParams[0] == 0); + return 0; + } + case COMMAND_IS_PLAYER_LIFTING_A_PHONE: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->GetPedState() == PED_MAKE_CALL); + return 0; + } + case COMMAND_IS_CHAR_SITTING_IN_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + UpdateCompareFlag(pPed->GetPedState() == PED_DRIVING && pPed->m_pMyVehicle == pVehicle); + return 0; + } + case COMMAND_IS_CHAR_SITTING_IN_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->GetPedState() == PED_DRIVING); + return 0; + } + case COMMAND_IS_PLAYER_ON_FOOT: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(!pPed->bInVehicle && pPed->m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && + pPed->m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER); + return 0; + } + case COMMAND_IS_CHAR_ON_FOOT: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(!pPed->bInVehicle && pPed->m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && + pPed->m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER); + return 0; + } +#if GTA_VERSION > GTA3_PS2_160 + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands1100To1199(int32 command) +{ + char tmp[48]; + switch (command) { +#endif + case COMMAND_LOAD_COLLISION_WITH_SCREEN: + CollectParameters(&m_nIp, 1); + CTimer::Stop(); + CGame::currLevel = (eLevelName)ScriptParams[0]; + if (CGame::currLevel != CCollision::ms_collisionInMemory) { + ISLAND_LOADING_IS(LOW) + { + DMAudio.SetEffectsFadeVol(0); + CPad::StopPadsShaking(); + CCollision::LoadCollisionScreen(CGame::currLevel); + DMAudio.Service(); + } + CPopulation::DealWithZoneChange(CCollision::ms_collisionInMemory, CGame::currLevel, false); + + ISLAND_LOADING_IS(LOW) + { + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + } + CCollision::SortOutCollisionAfterLoad(); + + ISLAND_LOADING_ISNT(HIGH) + CStreaming::RequestIslands(CGame::currLevel); + + ISLAND_LOADING_IS(LOW) + CStreaming::RequestBigBuildings(CGame::currLevel); + + ISLAND_LOADING_ISNT(HIGH) + CStreaming::LoadAllRequestedModels(true); + + ISLAND_LOADING_IS(LOW) + DMAudio.SetEffectsFadeVol(127); + } + CTimer::Update(); + return 0; + case COMMAND_LOAD_SPLASH_SCREEN: + CTheScripts::ReadTextLabelFromScript(&m_nIp, tmp); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + tmp[i] = tolower(tmp[i]); + m_nIp += 8; + LoadSplash(tmp); + return 0; + case COMMAND_SET_CAR_IGNORE_LEVEL_TRANSITIONS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + if (ScriptParams[1]) + pVehicle->m_nZoneLevel = LEVEL_IGNORE; + else + pVehicle->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pVehicle->GetPosition()); + return 0; + } + case COMMAND_MAKE_CRAIGS_CAR_A_BIT_STRONGER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + script_assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + CAutomobile* pCar = (CAutomobile*)pVehicle; + pCar->bMoreResistantToDamage = ScriptParams[1]; + return 0; + } + case COMMAND_SET_JAMES_CAR_ON_PATH_TO_PLAYER: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CCarCtrl::JoinCarWithRoadSystemGotoCoors(pVehicle, FindPlayerCoors(), false); + return 0; + } + case COMMAND_LOAD_END_OF_GAME_TUNE: + DMAudio.ChangeMusicMode(MUSICMODE_CUTSCENE); + printf("Start preload end of game audio\n"); + DMAudio.PreloadCutSceneMusic(STREAMED_SOUND_GAME_COMPLETED); + printf("End preload end of game audio\n"); + return 0; + case COMMAND_ENABLE_PLAYER_CONTROL_CAMERA: + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_CAMERA); + return 0; +#if GTA_VERSION > GTA3_PS2_160 + // These are "beta" VC commands (with bugs) + case COMMAND_SET_OBJECT_ROTATION: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CWorld::Remove(pObject); + pObject->SetOrientation( + DEGTORAD(*(float*)&ScriptParams[1]), + DEGTORAD(*(float*)&ScriptParams[2]), + DEGTORAD(*(float*)&ScriptParams[3])); + pObject->GetMatrix().UpdateRW(); + pObject->UpdateRwFrame(); + CWorld::Add(pObject); + return 0; + } + case COMMAND_GET_DEBUG_CAMERA_COORDINATES: + *(CVector*)&ScriptParams[0] = TheCamera.Cams[2].Source; + StoreParameters(&m_nIp, 3); + return 0; + case COMMAND_GET_DEBUG_CAMERA_FRONT_VECTOR: + *(CVector*)&ScriptParams[0] = TheCamera.Cams[2].Front; + StoreParameters(&m_nIp, 3); + return 0; + case COMMAND_IS_PLAYER_TARGETTING_ANY_CHAR: + { + CollectParameters(&m_nIp, 1); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CEntity* pTarget = pPed->m_pPointGunAt; + UpdateCompareFlag(pTarget && pTarget->IsPed()); + return 0; + } + case COMMAND_IS_PLAYER_TARGETTING_CHAR: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CPed* pTestedPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTestedPed); + CEntity* pTarget = pPed->m_pPointGunAt; + UpdateCompareFlag(pTarget && pTarget->IsPed() && pTarget == pTestedPed); + return 0; + } + case COMMAND_IS_PLAYER_TARGETTING_OBJECT: + { + CollectParameters(&m_nIp, 2); + CPlayerPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CObject* pTestedObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pTestedObject); + CEntity* pTarget = pPed->m_pPointGunAt; + UpdateCompareFlag(pTarget && pTarget->IsObject() && pTarget == pTestedObject); + return 0; + } + case COMMAND_TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME: + { + CTheScripts::ReadTextLabelFromScript(&m_nIp, tmp); + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + tmp[i] = tolower(tmp[i]); + m_nIp += 8; + CRunningScript* pScript = CTheScripts::pActiveScripts; + while (pScript) { + CRunningScript* pNext = pScript->next; + if (strcmp(pScript->m_abScriptName, tmp) == 0) { + pScript->RemoveScriptFromList(&CTheScripts::pActiveScripts); + pScript->AddScriptToList(&CTheScripts::pIdleScripts); + } + pScript = pNext; + } + return 0; + } + case COMMAND_DISPLAY_TEXT_WITH_NUMBER: + { + CollectParameters(&m_nIp, 2); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtX = *(float*)&ScriptParams[0]; + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtY = *(float*)&ScriptParams[1]; + CollectParameters(&m_nIp, 1); + CMessages::InsertNumberInString(text, ScriptParams[0], -1, -1, -1, -1, -1, + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame++].m_Text); + return 0; + } + case COMMAND_DISPLAY_TEXT_WITH_2_NUMBERS: + { + CollectParameters(&m_nIp, 2); + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtX = *(float*)&ScriptParams[0]; + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame].m_fAtY = *(float*)&ScriptParams[1]; + CollectParameters(&m_nIp, 2); + CMessages::InsertNumberInString(text, ScriptParams[0], ScriptParams[1], -1, -1, -1, -1, + CTheScripts::IntroTextLines[CTheScripts::NumberOfIntroTextLinesThisFrame++].m_Text); + return 0; + } + case COMMAND_FAIL_CURRENT_MISSION: + CTheScripts::FailCurrentMission = 2; + return 0; + case COMMAND_GET_CLOSEST_OBJECT_OF_TYPE: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float range = *(float*)&ScriptParams[3]; + int mi = ScriptParams[4] < 0 ? CTheScripts::UsedObjectArray[-ScriptParams[4]].index : ScriptParams[4]; + int16 total; + CEntity* apEntities[16]; + CWorld::FindObjectsOfTypeInRange(mi, pos, range, true, &total, 16, apEntities, false, false, false, true, true); + CEntity* pClosestEntity = nil; + float min_dist = 2.0f * range; + for (int i = 0; i < total; i++) { + float dist = (apEntities[i]->GetPosition() - pos).Magnitude(); + if (dist < min_dist) { + min_dist = dist; + pClosestEntity = apEntities[i]; + } + } + if (pClosestEntity && pClosestEntity->IsDummy()) { + CPopulation::ConvertToRealObject((CDummyObject*)pClosestEntity); + CWorld::FindObjectsOfTypeInRange(mi, pos, range, true, &total, 16, apEntities, false, false, false, true, true); + pClosestEntity = nil; + float min_dist = 2.0f * range; + for (int i = 0; i < total; i++) { + float dist = (apEntities[i]->GetPosition() - pos).Magnitude(); + if (dist < min_dist) { + min_dist = dist; + pClosestEntity = apEntities[i]; + } + } + if (pClosestEntity->IsDummy()) + pClosestEntity = nil; + } + if (pClosestEntity) { + script_assert(pClosestEntity->IsObject()); + CObject* pObject = (CObject*)pClosestEntity; + pObject->ObjectCreatedBy = MISSION_OBJECT; + ScriptParams[0] = CPools::GetObjectPool()->GetIndex(pObject); + } else { + ScriptParams[0] = -1; + } + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_PLACE_OBJECT_RELATIVE_TO_OBJECT: + { + CollectParameters(&m_nIp, 5); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CObject* pTarget = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + script_assert(pTarget); + CVector offset = *(CVector*)&ScriptParams[2]; + CPhysical::PlacePhysicalRelativeToOtherPhysical(pTarget, pObject, offset); + return 0; + } + case COMMAND_SET_ALL_OCCUPANTS_OF_CAR_LEAVE_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + if (pVehicle->pDriver) { + pVehicle->pDriver->bScriptObjectiveCompleted = false; + pVehicle->pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + } + for (int i = 0; i < ARRAY_SIZE(pVehicle->pPassengers); i++) + { + if (pVehicle->pPassengers[i]) { + pVehicle->pPassengers[i]->bScriptObjectiveCompleted = false; + pVehicle->pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + } + } + return 0; + } + case COMMAND_SET_INTERPOLATION_PARAMETERS: + CollectParameters(&m_nIp, 2); + TheCamera.SetParametersForScriptInterpolation(*(float*)&ScriptParams[0], 50.0f - *(float*)&ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_TOWARDS_POINT: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float destX = *(float*)&ScriptParams[3]; + float destY = *(float*)&ScriptParams[4]; + int32 nid = ThePaths.FindNodeClosestToCoors(pos, 0, 999999.9f, true, true); + CPathNode* pNode = &ThePaths.m_pathNodes[nid]; + *(CVector*)&ScriptParams[0] = pNode->GetPosition(); + *(float*)&ScriptParams[3] = ThePaths.FindNodeOrientationForCarPlacementFacingDestination(nid, destX, destY, true); + StoreParameters(&m_nIp, 4); + return 0; + } + case COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_AWAY_POINT: + { + CollectParameters(&m_nIp, 5); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float destX = *(float*)&ScriptParams[3]; + float destY = *(float*)&ScriptParams[4]; + int32 nid = ThePaths.FindNodeClosestToCoors(pos, 0, 999999.9f, true, true); + CPathNode* pNode = &ThePaths.m_pathNodes[nid]; + *(CVector*)&ScriptParams[0] = pNode->GetPosition(); + *(float*)&ScriptParams[3] = ThePaths.FindNodeOrientationForCarPlacementFacingDestination(nid, destX, destY, false); + StoreParameters(&m_nIp, 4); + return 0; + } + case COMMAND_GET_DEBUG_CAMERA_POINT_AT: + *(CVector*)&ScriptParams[0] = TheCamera.Cams[2].Source + TheCamera.Cams[2].Front; + StoreParameters(&m_nIp, 3); + return 0; + case COMMAND_ATTACH_CHAR_TO_CAR: + // empty implementation + return 0; + case COMMAND_DETACH_CHAR_FROM_CAR: + // empty implementation + return 0; + case COMMAND_SET_CAR_CHANGE_LANE: // for some reason changed in SA + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_bStayInFastLane = !ScriptParams[1]; + return 0; + } + case COMMAND_CLEAR_CHAR_LAST_WEAPON_DAMAGE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_lastWepDam = -1; + return 0; + } + case COMMAND_CLEAR_CAR_LAST_WEAPON_DAMAGE: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + pVehicle->m_nLastWeaponDamage = -1; + return 0; + } + case COMMAND_GET_RANDOM_COP_IN_AREA: + { + CollectParameters(&m_nIp, 4); + int ped_handle = -1; + CVector pos = FindPlayerCoors(); + float x1 = *(float*)&ScriptParams[0]; + float y1 = *(float*)&ScriptParams[1]; + float x2 = *(float*)&ScriptParams[2]; + float y2 = *(float*)&ScriptParams[3]; + int i = CPools::GetPedPool()->GetSize(); + while (--i && ped_handle == -1) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (CTheScripts::LastRandomPedId == CPools::GetPedPool()->GetIndex(pPed)) + continue; + if (pPed->m_nPedType != PEDTYPE_COP) + continue; + if (pPed->CharCreatedBy != RANDOM_CHAR) + continue; + if (!pPed->IsPedInControl() && pPed->GetPedState() != PED_DRIVING) + continue; + if (pPed->bRemoveFromWorld) + continue; + if (pPed->bFadeOut) + continue; + if (pPed->bIsLeader || pPed->m_leader) + continue; + if (!pPed->IsWithinArea(x1, y1, x2, y2)) + continue; + if (pos.z - PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + PED_FIND_Z_OFFSET < pPed->GetPosition().z) + continue; + ped_handle = CPools::GetPedPool()->GetIndex(pPed); + CTheScripts::LastRandomPedId = ped_handle; + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + ++CPopulation::ms_nTotalMissionPeds; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ped_handle, CLEANUP_CHAR); + } + ScriptParams[0] = ped_handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_RANDOM_COP_IN_ZONE: + { + char zone[KEY_LENGTH_IN_SCRIPT]; + strncpy(zone, (const char*)&CTheScripts::ScriptSpace[m_nIp], KEY_LENGTH_IN_SCRIPT); + int nZone = CTheZones::FindZoneByLabelAndReturnIndex(zone); + if (nZone != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetZone(nZone); + int ped_handle = -1; + CVector pos = FindPlayerCoors(); + int i = CPools::GetPedPool()->GetSize(); + while (--i && ped_handle == -1) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (CTheScripts::LastRandomPedId == CPools::GetPedPool()->GetIndex(pPed)) + continue; + if (pPed->m_nPedType != PEDTYPE_COP) + continue; + if (pPed->CharCreatedBy != RANDOM_CHAR) + continue; + if (!pPed->IsPedInControl() && pPed->GetPedState() != PED_DRIVING) + continue; + if (pPed->bRemoveFromWorld) + continue; + if (pPed->bFadeOut) + continue; + if (pPed->bIsLeader || pPed->m_leader) + continue; + if (!CTheZones::PointLiesWithinZone(&pPed->GetPosition(), pZone)) + continue; + if (pos.z - PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + PED_FIND_Z_OFFSET < pPed->GetPosition().z) + continue; + ped_handle = CPools::GetPedPool()->GetIndex(pPed); + CTheScripts::LastRandomPedId = ped_handle; + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + ++CPopulation::ms_nTotalMissionPeds; + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ped_handle, CLEANUP_CHAR); + } + ScriptParams[0] = ped_handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_OBJ_FLEE_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_FLEE_CAR, pVehicle); + return 0; + } + case COMMAND_GET_DRIVER_OF_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CPed* pDriver = pVehicle->pDriver; + if (pDriver) + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pDriver); + else + ScriptParams[0] = -1; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_NUMBER_OF_FOLLOWERS: + { + CollectParameters(&m_nIp, 1); + CPed* pLeader = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pLeader); + int total = 0; + int i = CPools::GetPedPool()->GetSize(); + while (--i) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (pPed->m_leader == pLeader) + total++; + } + ScriptParams[0] = total; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GIVE_REMOTE_CONTROLLED_MODEL_TO_PLAYER: + { + CollectParameters(&m_nIp, 6); + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + CRemote::GivePlayerRemoteControlledCar(pos.x, pos.y, pos.z, DEGTORAD(*(float*)&ScriptParams[4]), ScriptParams[5]); + return 0; + } + case COMMAND_GET_CURRENT_PLAYER_WEAPON: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + ScriptParams[0] = pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_CURRENT_CHAR_WEAPON: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + ScriptParams[0] = pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_2D: + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_2D: + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_2D: + case COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D: + case COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D: + case COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D: + LocateCharObjectCommand(command, &m_nIp); + return 0; + case COMMAND_SET_CAR_HANDBRAKE_TURN_LEFT: // this will be changed in final VC version to a more general SET_TEMP_ACTION + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_nTempAction = TEMPACT_HANDBRAKETURNLEFT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + ScriptParams[1]; + return 0; + } + case COMMAND_SET_CAR_HANDBRAKE_TURN_RIGHT: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_nTempAction = TEMPACT_HANDBRAKETURNRIGHT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + ScriptParams[1]; + return 0; + } + case COMMAND_SET_CAR_HANDBRAKE_STOP: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_nTempAction = TEMPACT_HANDBRAKESTRAIGHT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + ScriptParams[1]; + return 0; + } + case COMMAND_IS_CHAR_ON_ANY_BIKE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle&& pPed->m_pMyVehicle->m_vehType == VEHICLE_TYPE_BIKE); + return 0; + } + case COMMAND_LOCATE_SNIPER_BULLET_2D: + case COMMAND_LOCATE_SNIPER_BULLET_3D: + LocateSniperBulletCommand(command, &m_nIp); + return 0; + case COMMAND_GET_NUMBER_OF_SEATS_IN_MODEL: + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CVehicleModelInfo::GetMaximumNumberOfPassengersFromNumberOfDoors(ScriptParams[0]) + 1; + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_IS_PLAYER_ON_ANY_BIKE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->m_vehType == VEHICLE_TYPE_BIKE); + return 0; + } + case COMMAND_IS_CHAR_LYING_DOWN: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bFallenDown); + return 0; + } + case COMMAND_CAN_CHAR_SEE_DEAD_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + int pedtype = ScriptParams[1]; + bool can = false; + for (int i = 0; i < pPed->m_numNearPeds; i++) { + CPed* pTestPed = pPed->m_nearPeds[i]; + if (pTestPed->m_fHealth <= 0.0f && pTestPed->m_nPedType == pedtype && pPed->OurPedCanSeeThisOne(pTestPed)) + can = true; + } + UpdateCompareFlag(can); + return 0; + } + case COMMAND_SET_ENTER_CAR_RANGE_MULTIPLIER: + CollectParameters(&m_nIp, 1); +#ifdef FIX_BUGS + CPed::nEnterCarRangeMultiplier = *(float*)&ScriptParams[0]; +#else + CPed::nEnterCarRangeMultiplier = (float)ScriptParams[0]; +#endif + return 0; +#if GTA_VERSION < GTA3_PC_11 + case COMMAND_SET_THREAT_REACTION_RANGE_MULTIPLIER: + CollectParameters(&m_nIp, 1); +#ifdef FIX_BUGS + CPed::nThreatReactionRangeMultiplier = *(float*)&ScriptParams[0]; +#else + CPed::nThreatReactionRangeMultiplier = (float)ScriptParams[0]; +#endif + return 0; +#endif +#endif + default: + script_assert(0); + } + return -1; +} diff --git a/src/control/ScriptCommands.h b/src/control/ScriptCommands.h new file mode 100644 index 0000000..a33275f --- /dev/null +++ b/src/control/ScriptCommands.h @@ -0,0 +1,1194 @@ +#pragma once + +enum { + COMMAND_NOP = 0, + COMMAND_WAIT, + COMMAND_GOTO, + COMMAND_SHAKE_CAM, + COMMAND_SET_VAR_INT, + COMMAND_SET_VAR_FLOAT, + COMMAND_SET_LVAR_INT, + COMMAND_SET_LVAR_FLOAT, + COMMAND_ADD_VAL_TO_INT_VAR, + COMMAND_ADD_VAL_TO_FLOAT_VAR, + COMMAND_ADD_VAL_TO_INT_LVAR, + COMMAND_ADD_VAL_TO_FLOAT_LVAR, + COMMAND_SUB_VAL_FROM_INT_VAR, + COMMAND_SUB_VAL_FROM_FLOAT_VAR, + COMMAND_SUB_VAL_FROM_INT_LVAR, + COMMAND_SUB_VAL_FROM_FLOAT_LVAR, + COMMAND_MULT_INT_VAR_BY_VAL, + COMMAND_MULT_FLOAT_VAR_BY_VAL, + COMMAND_MULT_INT_LVAR_BY_VAL, + COMMAND_MULT_FLOAT_LVAR_BY_VAL, + COMMAND_DIV_INT_VAR_BY_VAL, + COMMAND_DIV_FLOAT_VAR_BY_VAL, + COMMAND_DIV_INT_LVAR_BY_VAL, + COMMAND_DIV_FLOAT_LVAR_BY_VAL, + COMMAND_IS_INT_VAR_GREATER_THAN_NUMBER, + COMMAND_IS_INT_LVAR_GREATER_THAN_NUMBER, + COMMAND_IS_NUMBER_GREATER_THAN_INT_VAR, + COMMAND_IS_NUMBER_GREATER_THAN_INT_LVAR, + COMMAND_IS_INT_VAR_GREATER_THAN_INT_VAR, + COMMAND_IS_INT_LVAR_GREATER_THAN_INT_LVAR, + COMMAND_IS_INT_VAR_GREATER_THAN_INT_LVAR, + COMMAND_IS_INT_LVAR_GREATER_THAN_INT_VAR, + COMMAND_IS_FLOAT_VAR_GREATER_THAN_NUMBER, + COMMAND_IS_FLOAT_LVAR_GREATER_THAN_NUMBER, + COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_VAR, + COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_VAR, + COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_LVAR, + COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_VAR, + COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_NUMBER, + COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_NUMBER, + COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_VAR, + COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_VAR, + COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_VAR, + COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_NUMBER, + COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_INT_VAR_EQUAL_TO_NUMBER, + COMMAND_IS_INT_LVAR_EQUAL_TO_NUMBER, + COMMAND_IS_INT_VAR_EQUAL_TO_INT_VAR, + COMMAND_IS_INT_LVAR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_NOT_EQUAL_TO_NUMBER, + COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_NUMBER, + COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_VAR, + COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_INT_LVAR, + COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_LVAR, + COMMAND_IS_FLOAT_VAR_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_LVAR_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_FLOAT_LVAR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_NUMBER, + COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_VAR, + COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_FLOAT_LVAR, + COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_LVAR, + COMMAND_GOTO_IF_TRUE, + COMMAND_GOTO_IF_FALSE, + COMMAND_TERMINATE_THIS_SCRIPT, + COMMAND_START_NEW_SCRIPT, + COMMAND_GOSUB, + COMMAND_RETURN, + COMMAND_LINE, + COMMAND_CREATE_PLAYER, + COMMAND_GET_PLAYER_COORDINATES, + COMMAND_SET_PLAYER_COORDINATES, + COMMAND_IS_PLAYER_IN_AREA_2D, + COMMAND_IS_PLAYER_IN_AREA_3D, + COMMAND_ADD_INT_VAR_TO_INT_VAR, + COMMAND_ADD_FLOAT_VAR_TO_FLOAT_VAR, + COMMAND_ADD_INT_LVAR_TO_INT_LVAR, + COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_LVAR, + COMMAND_ADD_INT_VAR_TO_INT_LVAR, + COMMAND_ADD_FLOAT_VAR_TO_FLOAT_LVAR, + COMMAND_ADD_INT_LVAR_TO_INT_VAR, + COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_VAR, + COMMAND_SUB_INT_VAR_FROM_INT_VAR, + COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_VAR, + COMMAND_SUB_INT_LVAR_FROM_INT_LVAR, + COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_LVAR, + COMMAND_SUB_INT_VAR_FROM_INT_LVAR, + COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_LVAR, + COMMAND_SUB_INT_LVAR_FROM_INT_VAR, + COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_VAR, + COMMAND_MULT_INT_VAR_BY_INT_VAR, + COMMAND_MULT_FLOAT_VAR_BY_FLOAT_VAR, + COMMAND_MULT_INT_LVAR_BY_INT_LVAR, + COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_LVAR, + COMMAND_MULT_INT_VAR_BY_INT_LVAR, + COMMAND_MULT_FLOAT_VAR_BY_FLOAT_LVAR, + COMMAND_MULT_INT_LVAR_BY_INT_VAR, + COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_VAR, + COMMAND_DIV_INT_VAR_BY_INT_VAR, + COMMAND_DIV_FLOAT_VAR_BY_FLOAT_VAR, + COMMAND_DIV_INT_LVAR_BY_INT_LVAR, + COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_LVAR, + COMMAND_DIV_INT_VAR_BY_INT_LVAR, + COMMAND_DIV_FLOAT_VAR_BY_FLOAT_LVAR, + COMMAND_DIV_INT_LVAR_BY_INT_VAR, + COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_VAR, + COMMAND_ADD_TIMED_VAL_TO_FLOAT_VAR, + COMMAND_ADD_TIMED_VAL_TO_FLOAT_LVAR, + COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_VAR, + COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_LVAR, + COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_VAR, + COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_LVAR, + COMMAND_SUB_TIMED_VAL_FROM_FLOAT_VAR, + COMMAND_SUB_TIMED_VAL_FROM_FLOAT_LVAR, + COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_VAR, + COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_LVAR, + COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_VAR, + COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_LVAR, + COMMAND_SET_VAR_INT_TO_VAR_INT, + COMMAND_SET_LVAR_INT_TO_LVAR_INT, + COMMAND_SET_VAR_FLOAT_TO_VAR_FLOAT, + COMMAND_SET_LVAR_FLOAT_TO_LVAR_FLOAT, + COMMAND_SET_VAR_FLOAT_TO_LVAR_FLOAT, + COMMAND_SET_LVAR_FLOAT_TO_VAR_FLOAT, + COMMAND_SET_VAR_INT_TO_LVAR_INT, + COMMAND_SET_LVAR_INT_TO_VAR_INT, + COMMAND_CSET_VAR_INT_TO_VAR_FLOAT, + COMMAND_CSET_VAR_FLOAT_TO_VAR_INT, + COMMAND_CSET_LVAR_INT_TO_VAR_FLOAT, + COMMAND_CSET_LVAR_FLOAT_TO_VAR_INT, + COMMAND_CSET_VAR_INT_TO_LVAR_FLOAT, + COMMAND_CSET_VAR_FLOAT_TO_LVAR_INT, + COMMAND_CSET_LVAR_INT_TO_LVAR_FLOAT, + COMMAND_CSET_LVAR_FLOAT_TO_LVAR_INT, + COMMAND_ABS_VAR_INT, + COMMAND_ABS_LVAR_INT, + COMMAND_ABS_VAR_FLOAT, + COMMAND_ABS_LVAR_FLOAT, + COMMAND_GENERATE_RANDOM_FLOAT, + COMMAND_GENERATE_RANDOM_INT, + COMMAND_CREATE_CHAR, + COMMAND_DELETE_CHAR, + COMMAND_CHAR_WANDER_DIR, + COMMAND_CHAR_WANDER_RANGE, + COMMAND_CHAR_FOLLOW_PATH, + COMMAND_CHAR_SET_IDLE, + COMMAND_GET_CHAR_COORDINATES, + COMMAND_SET_CHAR_COORDINATES, + COMMAND_IS_CHAR_STILL_ALIVE, + COMMAND_IS_CHAR_IN_AREA_2D, + COMMAND_IS_CHAR_IN_AREA_3D, + COMMAND_CREATE_CAR, + COMMAND_DELETE_CAR, + COMMAND_CAR_GOTO_COORDINATES, + COMMAND_CAR_WANDER_RANDOMLY, + COMMAND_CAR_SET_IDLE, + COMMAND_GET_CAR_COORDINATES, + COMMAND_SET_CAR_COORDINATES, + COMMAND_IS_CAR_STILL_ALIVE, + COMMAND_SET_CAR_CRUISE_SPEED, + COMMAND_SET_CAR_DRIVING_STYLE, + COMMAND_SET_CAR_MISSION, + COMMAND_IS_CAR_IN_AREA_2D, + COMMAND_IS_CAR_IN_AREA_3D, + COMMAND_SPECIAL_0, + COMMAND_SPECIAL_1, + COMMAND_SPECIAL_2, + COMMAND_SPECIAL_3, + COMMAND_SPECIAL_4, + COMMAND_SPECIAL_5, + COMMAND_SPECIAL_6, + COMMAND_SPECIAL_7, + COMMAND_PRINT_BIG, + COMMAND_PRINT, + COMMAND_PRINT_NOW, + COMMAND_PRINT_SOON, + COMMAND_CLEAR_PRINTS, + COMMAND_GET_TIME_OF_DAY, + COMMAND_SET_TIME_OF_DAY, + COMMAND_GET_MINUTES_TO_TIME_OF_DAY, + COMMAND_IS_POINT_ON_SCREEN, + COMMAND_DEBUG_ON, + COMMAND_DEBUG_OFF, + COMMAND_RETURN_TRUE, + COMMAND_RETURN_FALSE, + COMMAND_VAR_INT, + COMMAND_VAR_FLOAT, + COMMAND_LVAR_INT, + COMMAND_LVAR_FLOAT, + COMMAND_LBRACKET, + COMMAND_RBRACKET, + COMMAND_REPEAT, + COMMAND_ENDREPEAT, + COMMAND_IF, + COMMAND_IFNOT, + COMMAND_ELSE, + COMMAND_ENDIF, + COMMAND_WHILE, + COMMAND_WHILENOT, + COMMAND_ENDWHILE, + COMMAND_ANDOR, + COMMAND_LAUNCH_MISSION, + COMMAND_MISSION_HAS_FINISHED, + COMMAND_STORE_CAR_CHAR_IS_IN, + COMMAND_STORE_CAR_PLAYER_IS_IN, + COMMAND_IS_CHAR_IN_CAR, + COMMAND_IS_PLAYER_IN_CAR, + COMMAND_IS_CHAR_IN_MODEL, + COMMAND_IS_PLAYER_IN_MODEL, + COMMAND_IS_CHAR_IN_ANY_CAR, + COMMAND_IS_PLAYER_IN_ANY_CAR, + COMMAND_IS_BUTTON_PRESSED, + COMMAND_GET_PAD_STATE, + COMMAND_LOCATE_PLAYER_ANY_MEANS_2D, + COMMAND_LOCATE_PLAYER_ON_FOOT_2D, + COMMAND_LOCATE_PLAYER_IN_CAR_2D, + COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D, + COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D, + COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D, + COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_2D, + COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_2D, + COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_2D, + COMMAND_LOCATE_CHAR_ANY_MEANS_2D, + COMMAND_LOCATE_CHAR_ON_FOOT_2D, + COMMAND_LOCATE_CHAR_IN_CAR_2D, + COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D, + COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D, + COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D, + COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_2D, + COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_2D, + COMMAND_LOCATE_CHAR_IN_CAR_CHAR_2D, + COMMAND_LOCATE_PLAYER_ANY_MEANS_3D, + COMMAND_LOCATE_PLAYER_ON_FOOT_3D, + COMMAND_LOCATE_PLAYER_IN_CAR_3D, + COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D, + COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D, + COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D, + COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D, + COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D, + COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D, + COMMAND_LOCATE_CHAR_ANY_MEANS_3D, + COMMAND_LOCATE_CHAR_ON_FOOT_3D, + COMMAND_LOCATE_CHAR_IN_CAR_3D, + COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D, + COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D, + COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D, + COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D, + COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D, + COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D, + COMMAND_CREATE_OBJECT, + COMMAND_DELETE_OBJECT, + COMMAND_ADD_SCORE, + COMMAND_IS_SCORE_GREATER, + COMMAND_STORE_SCORE, + COMMAND_GIVE_REMOTE_CONTROLLED_CAR_TO_PLAYER, + COMMAND_ALTER_WANTED_LEVEL, + COMMAND_ALTER_WANTED_LEVEL_NO_DROP, + COMMAND_IS_WANTED_LEVEL_GREATER, + COMMAND_CLEAR_WANTED_LEVEL, + COMMAND_SET_DEATHARREST_STATE, + COMMAND_HAS_DEATHARREST_BEEN_EXECUTED, + COMMAND_ADD_AMMO_TO_PLAYER, + COMMAND_ADD_AMMO_TO_CHAR, + COMMAND_ADD_AMMO_TO_CAR, + COMMAND_IS_PLAYER_STILL_ALIVE, + COMMAND_IS_PLAYER_DEAD, + COMMAND_IS_CHAR_DEAD, + COMMAND_IS_CAR_DEAD, + COMMAND_SET_CHAR_THREAT_SEARCH, + COMMAND_SET_CHAR_THREAT_REACTION, + COMMAND_SET_CHAR_OBJ_NO_OBJ, + COMMAND_ORDER_DRIVER_OUT_OF_CAR, + COMMAND_ORDER_CHAR_TO_DRIVE_CAR, + COMMAND_ADD_PATROL_POINT, + COMMAND_IS_PLAYER_IN_GANGZONE, + COMMAND_IS_PLAYER_IN_ZONE, + COMMAND_IS_PLAYER_PRESSING_HORN, + COMMAND_HAS_CHAR_SPOTTED_PLAYER, + COMMAND_ORDER_CHAR_TO_BACKDOOR, + COMMAND_ADD_CHAR_TO_GANG, + COMMAND_IS_CHAR_OBJECTIVE_PASSED, + COMMAND_SET_CHAR_DRIVE_AGGRESSION, + COMMAND_SET_CHAR_MAX_DRIVESPEED, + COMMAND_CREATE_CHAR_INSIDE_CAR, + COMMAND_WARP_PLAYER_FROM_CAR_TO_COORD, + COMMAND_MAKE_CHAR_DO_NOTHING, + COMMAND_SET_CHAR_INVINCIBLE, + COMMAND_SET_PLAYER_INVINCIBLE, + COMMAND_SET_CHAR_GRAPHIC_TYPE, + COMMAND_SET_PLAYER_GRAPHIC_TYPE, + COMMAND_HAS_PLAYER_BEEN_ARRESTED, + COMMAND_STOP_CHAR_DRIVING, + COMMAND_KILL_CHAR, + COMMAND_SET_FAVOURITE_CAR_MODEL_FOR_CHAR, + COMMAND_SET_CHAR_OCCUPATION, + COMMAND_CHANGE_CAR_LOCK, + COMMAND_SHAKE_CAM_WITH_POINT, + COMMAND_IS_CAR_MODEL, + COMMAND_IS_CAR_REMAP, + COMMAND_HAS_CAR_JUST_SUNK, + COMMAND_SET_CAR_NO_COLLIDE, + COMMAND_IS_CAR_DEAD_IN_AREA_2D, + COMMAND_IS_CAR_DEAD_IN_AREA_3D, + COMMAND_IS_TRAILER_ATTACHED, + COMMAND_IS_CAR_ON_TRAILER, + COMMAND_HAS_CAR_GOT_WEAPON, + COMMAND_PARK, + COMMAND_HAS_PARK_FINISHED, + COMMAND_KILL_ALL_PASSENGERS, + COMMAND_SET_CAR_BULLETPROOF, + COMMAND_SET_CAR_FLAMEPROOF, + COMMAND_SET_CAR_ROCKETPROOF, + COMMAND_IS_CARBOMB_ACTIVE, + COMMAND_GIVE_CAR_ALARM, + COMMAND_PUT_CAR_ON_TRAILER, + COMMAND_IS_CAR_CRUSHED, + COMMAND_CREATE_GANG_CAR, + COMMAND_CREATE_CAR_GENERATOR, + COMMAND_SWITCH_CAR_GENERATOR, + COMMAND_ADD_PAGER_MESSAGE, + COMMAND_DISPLAY_ONSCREEN_TIMER, + COMMAND_CLEAR_ONSCREEN_TIMER, + COMMAND_DISPLAY_ONSCREEN_COUNTER, + COMMAND_CLEAR_ONSCREEN_COUNTER, + COMMAND_SET_ZONE_CAR_INFO, + COMMAND_IS_CHAR_IN_GANG_ZONE, + COMMAND_IS_CHAR_IN_ZONE, + COMMAND_SET_CAR_DENSITY, + COMMAND_SET_PED_DENSITY, + COMMAND_POINT_CAMERA_AT_PLAYER, + COMMAND_POINT_CAMERA_AT_CAR, + COMMAND_POINT_CAMERA_AT_CHAR, + COMMAND_RESTORE_CAMERA, + COMMAND_SHAKE_PAD, + COMMAND_SET_ZONE_PED_INFO, + COMMAND_SET_TIME_SCALE, + COMMAND_IS_CAR_IN_AIR, + COMMAND_SET_FIXED_CAMERA_POSITION, + COMMAND_POINT_CAMERA_AT_POINT, + COMMAND_ADD_BLIP_FOR_CAR_OLD, + COMMAND_ADD_BLIP_FOR_CHAR_OLD, + COMMAND_ADD_BLIP_FOR_OBJECT_OLD, + COMMAND_REMOVE_BLIP, + COMMAND_CHANGE_BLIP_COLOUR, + COMMAND_DIM_BLIP, + COMMAND_ADD_BLIP_FOR_COORD_OLD, + COMMAND_CHANGE_BLIP_SCALE, + COMMAND_SET_FADING_COLOUR, + COMMAND_DO_FADE, + COMMAND_GET_FADING_STATUS, + COMMAND_ADD_HOSPITAL_RESTART, + COMMAND_ADD_POLICE_RESTART, + COMMAND_OVERRIDE_NEXT_RESTART, + COMMAND_DRAW_SHADOW, + COMMAND_GET_PLAYER_HEADING, + COMMAND_SET_PLAYER_HEADING, + COMMAND_GET_CHAR_HEADING, + COMMAND_SET_CHAR_HEADING, + COMMAND_GET_CAR_HEADING, + COMMAND_SET_CAR_HEADING, + COMMAND_GET_OBJECT_HEADING, + COMMAND_SET_OBJECT_HEADING, + COMMAND_IS_PLAYER_TOUCHING_OBJECT, + COMMAND_IS_CHAR_TOUCHING_OBJECT, + COMMAND_SET_PLAYER_AMMO, + COMMAND_SET_CHAR_AMMO, + COMMAND_SET_CAR_AMMO, + COMMAND_LOAD_CAMERA_SPLINE, + COMMAND_MOVE_CAMERA_ALONG_SPLINE, + COMMAND_GET_CAMERA_POSITION_ALONG_SPLINE, + COMMAND_DECLARE_MISSION_FLAG, + COMMAND_DECLARE_MISSION_FLAG_FOR_CONTACT, + COMMAND_DECLARE_BASE_BRIEF_ID_FOR_CONTACT, + COMMAND_IS_PLAYER_HEALTH_GREATER, + COMMAND_IS_CHAR_HEALTH_GREATER, + COMMAND_IS_CAR_HEALTH_GREATER, + COMMAND_ADD_BLIP_FOR_CAR, + COMMAND_ADD_BLIP_FOR_CHAR, + COMMAND_ADD_BLIP_FOR_OBJECT, + COMMAND_ADD_BLIP_FOR_CONTACT_POINT, + COMMAND_ADD_BLIP_FOR_COORD, + COMMAND_CHANGE_BLIP_DISPLAY, + COMMAND_ADD_ONE_OFF_SOUND, + COMMAND_ADD_CONTINUOUS_SOUND, + COMMAND_REMOVE_SOUND, + COMMAND_IS_CAR_STUCK_ON_ROOF, + COMMAND_ADD_UPSIDEDOWN_CAR_CHECK, + COMMAND_REMOVE_UPSIDEDOWN_CAR_CHECK, + COMMAND_SET_CHAR_OBJ_WAIT_ON_FOOT, + COMMAND_SET_CHAR_OBJ_FLEE_ON_FOOT_TILL_SAFE, + COMMAND_SET_CHAR_OBJ_GUARD_SPOT, + COMMAND_SET_CHAR_OBJ_GUARD_AREA, + COMMAND_SET_CHAR_OBJ_WAIT_IN_CAR, + COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_2D, + COMMAND_IS_PLAYER_IN_AREA_IN_CAR_2D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D, + COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D, + COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D, + COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D, + COMMAND_IS_CHAR_IN_AREA_ON_FOOT_2D, + COMMAND_IS_CHAR_IN_AREA_IN_CAR_2D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_2D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D, + COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D, + COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_3D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D, + COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D, + COMMAND_IS_CAR_STOPPED_IN_AREA_2D, + COMMAND_IS_CAR_STOPPED_IN_AREA_3D, + COMMAND_LOCATE_CAR_2D, + COMMAND_LOCATE_STOPPED_CAR_2D, + COMMAND_LOCATE_CAR_3D, + COMMAND_LOCATE_STOPPED_CAR_3D, + COMMAND_GIVE_WEAPON_TO_PLAYER, + COMMAND_GIVE_WEAPON_TO_CHAR, + COMMAND_GIVE_WEAPON_TO_CAR, + COMMAND_SET_PLAYER_CONTROL, + COMMAND_FORCE_WEATHER, + COMMAND_FORCE_WEATHER_NOW, + COMMAND_RELEASE_WEATHER, + COMMAND_SET_CURRENT_PLAYER_WEAPON, + COMMAND_SET_CURRENT_CHAR_WEAPON, + COMMAND_SET_CURRENT_CAR_WEAPON, + COMMAND_GET_OBJECT_COORDINATES, + COMMAND_SET_OBJECT_COORDINATES, + COMMAND_GET_GAME_TIMER, + COMMAND_TURN_CHAR_TO_FACE_COORD, + COMMAND_TURN_PLAYER_TO_FACE_COORD, + COMMAND_STORE_WANTED_LEVEL, + COMMAND_IS_CAR_STOPPED, + COMMAND_MARK_CHAR_AS_NO_LONGER_NEEDED, + COMMAND_MARK_CAR_AS_NO_LONGER_NEEDED, + COMMAND_MARK_OBJECT_AS_NO_LONGER_NEEDED, + COMMAND_DONT_REMOVE_CHAR, + COMMAND_DONT_REMOVE_CAR, + COMMAND_DONT_REMOVE_OBJECT, + COMMAND_CREATE_CHAR_AS_PASSENGER, + COMMAND_SET_CHAR_OBJ_KILL_CHAR_ON_FOOT, + COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ON_FOOT, + COMMAND_SET_CHAR_OBJ_KILL_CHAR_ANY_MEANS, + COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ANY_MEANS, + COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE, + COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE, + COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS, + COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS, + COMMAND_SET_CHAR_OBJ_GOTO_CHAR_ON_FOOT, + COMMAND_SET_CHAR_OBJ_GOTO_PLAYER_ON_FOOT, + COMMAND_SET_CHAR_OBJ_LEAVE_CAR, + COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_PASSENGER, + COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_DRIVER, + COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_IN_CAR, + COMMAND_SET_CHAR_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE, + COMMAND_SET_CHAR_OBJ_DESTROY_OBJECT, + COMMAND_SET_CHAR_OBJ_DESTROY_CAR, + COMMAND_SET_CHAR_OBJ_GOTO_AREA_ON_FOOT, + COMMAND_SET_CHAR_OBJ_GOTO_AREA_IN_CAR, + COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, + COMMAND_SET_CHAR_OBJ_GUARD_ATTACK, + COMMAND_SET_CHAR_AS_LEADER, + COMMAND_SET_PLAYER_AS_LEADER, + COMMAND_LEAVE_GROUP, + COMMAND_SET_CHAR_OBJ_FOLLOW_ROUTE, + COMMAND_ADD_ROUTE_POINT, + COMMAND_PRINT_WITH_NUMBER_BIG, + COMMAND_PRINT_WITH_NUMBER, + COMMAND_PRINT_WITH_NUMBER_NOW, + COMMAND_PRINT_WITH_NUMBER_SOON, + COMMAND_SWITCH_ROADS_ON, + COMMAND_SWITCH_ROADS_OFF, + COMMAND_GET_NUMBER_OF_PASSENGERS, + COMMAND_GET_MAXIMUM_NUMBER_OF_PASSENGERS, + COMMAND_SET_CAR_DENSITY_MULTIPLIER, + COMMAND_SET_CAR_HEAVY, + COMMAND_CLEAR_CHAR_THREAT_SEARCH, + COMMAND_ACTIVATE_CRANE, + COMMAND_DEACTIVATE_CRANE, + COMMAND_SET_MAX_WANTED_LEVEL, + COMMAND_SAVE_VAR_INT, + COMMAND_SAVE_VAR_FLOAT, + COMMAND_IS_CAR_IN_AIR_PROPER, + COMMAND_IS_CAR_UPSIDEDOWN, + COMMAND_GET_PLAYER_CHAR, + COMMAND_CANCEL_OVERRIDE_RESTART, + COMMAND_SET_POLICE_IGNORE_PLAYER, + COMMAND_ADD_PAGER_MESSAGE_WITH_NUMBER, + COMMAND_START_KILL_FRENZY, + COMMAND_READ_KILL_FRENZY_STATUS, + COMMAND_SQRT, + COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_2D, + COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_2D, + COMMAND_LOCATE_PLAYER_IN_CAR_CAR_2D, + COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D, + COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D, + COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D, + COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_2D, + COMMAND_LOCATE_CHAR_ON_FOOT_CAR_2D, + COMMAND_LOCATE_CHAR_IN_CAR_CAR_2D, + COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D, + COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D, + COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D, + COMMAND_GENERATE_RANDOM_FLOAT_IN_RANGE, + COMMAND_GENERATE_RANDOM_INT_IN_RANGE, + COMMAND_LOCK_CAR_DOORS, + COMMAND_EXPLODE_CAR, + COMMAND_ADD_EXPLOSION, + COMMAND_IS_CAR_UPRIGHT, + COMMAND_TURN_CHAR_TO_FACE_CHAR, + COMMAND_TURN_CHAR_TO_FACE_PLAYER, + COMMAND_TURN_PLAYER_TO_FACE_CHAR, + COMMAND_SET_CHAR_OBJ_GOTO_COORD_ON_FOOT, + COMMAND_SET_CHAR_OBJ_GOTO_COORD_IN_CAR, + COMMAND_CREATE_PICKUP, + COMMAND_HAS_PICKUP_BEEN_COLLECTED, + COMMAND_REMOVE_PICKUP, + COMMAND_SET_TAXI_LIGHTS, + COMMAND_PRINT_BIG_Q, + COMMAND_PRINT_WITH_NUMBER_BIG_Q, + COMMAND_SET_GARAGE, + COMMAND_SET_GARAGE_WITH_CAR_MODEL, + COMMAND_SET_TARGET_CAR_FOR_MISSION_GARAGE, + COMMAND_IS_CAR_IN_MISSION_GARAGE, + COMMAND_SET_FREE_BOMBS, + COMMAND_SET_POWERPOINT, + COMMAND_SET_ALL_TAXI_LIGHTS, + COMMAND_IS_CAR_ARMED_WITH_ANY_BOMB, + COMMAND_APPLY_BRAKES_TO_PLAYERS_CAR, + COMMAND_SET_PLAYER_HEALTH, + COMMAND_SET_CHAR_HEALTH, + COMMAND_SET_CAR_HEALTH, + COMMAND_GET_PLAYER_HEALTH, + COMMAND_GET_CHAR_HEALTH, + COMMAND_GET_CAR_HEALTH, + COMMAND_IS_CAR_ARMED_WITH_BOMB, + COMMAND_CHANGE_CAR_COLOUR, + COMMAND_SWITCH_PED_ROADS_ON, + COMMAND_SWITCH_PED_ROADS_OFF, + COMMAND_CHAR_LOOK_AT_CHAR_ALWAYS, + COMMAND_CHAR_LOOK_AT_PLAYER_ALWAYS, + COMMAND_PLAYER_LOOK_AT_CHAR_ALWAYS, + COMMAND_STOP_CHAR_LOOKING, + COMMAND_STOP_PLAYER_LOOKING, + COMMAND_SWITCH_HELICOPTER, + COMMAND_SET_GANG_ATTITUDE, + COMMAND_SET_GANG_GANG_ATTITUDE, + COMMAND_SET_GANG_PLAYER_ATTITUDE, + COMMAND_SET_GANG_PED_MODELS, + COMMAND_SET_GANG_CAR_MODEL, + COMMAND_SET_GANG_WEAPONS, + COMMAND_SET_CHAR_OBJ_RUN_TO_AREA, + COMMAND_SET_CHAR_OBJ_RUN_TO_COORD, + COMMAND_IS_PLAYER_TOUCHING_OBJECT_ON_FOOT, + COMMAND_IS_CHAR_TOUCHING_OBJECT_ON_FOOT, + COMMAND_LOAD_SPECIAL_CHARACTER, + COMMAND_HAS_SPECIAL_CHARACTER_LOADED, + COMMAND_FLASH_CAR, + COMMAND_FLASH_CHAR, + COMMAND_FLASH_OBJECT, + COMMAND_IS_PLAYER_IN_REMOTE_MODE, + COMMAND_ARM_CAR_WITH_BOMB, + COMMAND_SET_CHAR_PERSONALITY, + COMMAND_SET_CUTSCENE_OFFSET, + COMMAND_SET_ANIM_GROUP_FOR_CHAR, + COMMAND_SET_ANIM_GROUP_FOR_PLAYER, + COMMAND_REQUEST_MODEL, + COMMAND_HAS_MODEL_LOADED, + COMMAND_MARK_MODEL_AS_NO_LONGER_NEEDED, + COMMAND_GRAB_PHONE, + COMMAND_SET_REPEATED_PHONE_MESSAGE, + COMMAND_SET_PHONE_MESSAGE, + COMMAND_HAS_PHONE_DISPLAYED_MESSAGE, + COMMAND_TURN_PHONE_OFF, + COMMAND_DRAW_CORONA, + COMMAND_DRAW_LIGHT, + COMMAND_STORE_WEATHER, + COMMAND_RESTORE_WEATHER, + COMMAND_STORE_CLOCK, + COMMAND_RESTORE_CLOCK, + COMMAND_RESTART_CRITICAL_MISSION, + COMMAND_IS_PLAYER_PLAYING, + COMMAND_SET_COLL_OBJ_NO_OBJ, + COMMAND_SET_COLL_OBJ_WAIT_ON_FOOT, + COMMAND_SET_COLL_OBJ_FLEE_ON_FOOT_TILL_SAFE, + COMMAND_SET_COLL_OBJ_GUARD_SPOT, + COMMAND_SET_COLL_OBJ_GUARD_AREA, + COMMAND_SET_COLL_OBJ_WAIT_IN_CAR, + COMMAND_SET_COLL_OBJ_KILL_CHAR_ON_FOOT, + COMMAND_SET_COLL_OBJ_KILL_PLAYER_ON_FOOT, + COMMAND_SET_COLL_OBJ_KILL_CHAR_ANY_MEANS, + COMMAND_SET_COLL_OBJ_KILL_PLAYER_ANY_MEANS, + COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE, + COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE, + COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS, + COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS, + COMMAND_SET_COLL_OBJ_GOTO_CHAR_ON_FOOT, + COMMAND_SET_COLL_OBJ_GOTO_PLAYER_ON_FOOT, + COMMAND_SET_COLL_OBJ_LEAVE_CAR, + COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_PASSENGER, + COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_DRIVER, + COMMAND_SET_COLL_OBJ_FOLLOW_CAR_IN_CAR, + COMMAND_SET_COLL_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE, + COMMAND_SET_COLL_OBJ_DESTROY_OBJECT, + COMMAND_SET_COLL_OBJ_DESTROY_CAR, + COMMAND_SET_COLL_OBJ_GOTO_AREA_ON_FOOT, + COMMAND_SET_COLL_OBJ_GOTO_AREA_IN_CAR, + COMMAND_SET_COLL_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, + COMMAND_SET_COLL_OBJ_GUARD_ATTACK, + COMMAND_SET_COLL_OBJ_FOLLOW_ROUTE, + COMMAND_SET_COLL_OBJ_GOTO_COORD_ON_FOOT, + COMMAND_SET_COLL_OBJ_GOTO_COORD_IN_CAR, + COMMAND_SET_COLL_OBJ_RUN_TO_AREA, + COMMAND_SET_COLL_OBJ_RUN_TO_COORD, + COMMAND_ADD_PEDS_IN_AREA_TO_COLL, + COMMAND_ADD_PEDS_IN_VEHICLE_TO_COLL, + COMMAND_CLEAR_COLL, + COMMAND_IS_COLL_IN_CARS, + COMMAND_LOCATE_COLL_ANY_MEANS_2D, + COMMAND_LOCATE_COLL_ON_FOOT_2D, + COMMAND_LOCATE_COLL_IN_CAR_2D, + COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D, + COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D, + COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D, + COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D, + COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D, + COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D, + COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D, + COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D, + COMMAND_LOCATE_COLL_IN_CAR_CAR_2D, + COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D, + COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D, + COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D, + COMMAND_IS_COLL_IN_AREA_2D, + COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D, + COMMAND_IS_COLL_IN_AREA_IN_CAR_2D, + COMMAND_IS_COLL_STOPPED_IN_AREA_2D, + COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D, + COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D, + COMMAND_GET_NUMBER_OF_PEDS_IN_COLL, + COMMAND_SET_CHAR_HEED_THREATS, + COMMAND_SET_PLAYER_HEED_THREATS, + COMMAND_GET_CONTROLLER_MODE, + COMMAND_SET_CAN_RESPRAY_CAR, + COMMAND_IS_TAXI, + COMMAND_UNLOAD_SPECIAL_CHARACTER, + COMMAND_RESET_NUM_OF_MODELS_KILLED_BY_PLAYER, + COMMAND_GET_NUM_OF_MODELS_KILLED_BY_PLAYER, + COMMAND_ACTIVATE_GARAGE, + COMMAND_SWITCH_TAXI_TIMER, + COMMAND_CREATE_OBJECT_NO_OFFSET, + COMMAND_IS_BOAT, + COMMAND_SET_CHAR_OBJ_GOTO_AREA_ANY_MEANS, + COMMAND_SET_COLL_OBJ_GOTO_AREA_ANY_MEANS, + COMMAND_IS_PLAYER_STOPPED, + COMMAND_IS_CHAR_STOPPED, + COMMAND_MESSAGE_WAIT, + COMMAND_ADD_PARTICLE_EFFECT, + COMMAND_SWITCH_WIDESCREEN, + COMMAND_ADD_SPRITE_BLIP_FOR_CAR, + COMMAND_ADD_SPRITE_BLIP_FOR_CHAR, + COMMAND_ADD_SPRITE_BLIP_FOR_OBJECT, + COMMAND_ADD_SPRITE_BLIP_FOR_CONTACT_POINT, + COMMAND_ADD_SPRITE_BLIP_FOR_COORD, + COMMAND_SET_CHAR_ONLY_DAMAGED_BY_PLAYER, + COMMAND_SET_CAR_ONLY_DAMAGED_BY_PLAYER, + COMMAND_SET_CHAR_PROOFS, + COMMAND_SET_CAR_PROOFS, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_2D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_2D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_2D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D, + COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D, + COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D, + COMMAND_DEACTIVATE_GARAGE, + COMMAND_GET_NUMBER_OF_CARS_COLLECTED_BY_GARAGE, + COMMAND_HAS_CAR_BEEN_TAKEN_TO_GARAGE, + COMMAND_SET_SWAT_REQUIRED, + COMMAND_SET_FBI_REQUIRED, + COMMAND_SET_ARMY_REQUIRED, + COMMAND_IS_CAR_IN_WATER, + COMMAND_GET_CLOSEST_CHAR_NODE, + COMMAND_GET_CLOSEST_CAR_NODE, + COMMAND_CAR_GOTO_COORDINATES_ACCURATE, + COMMAND_START_PACMAN_RACE, + COMMAND_START_PACMAN_RECORD, + COMMAND_GET_NUMBER_OF_POWER_PILLS_EATEN, + COMMAND_CLEAR_PACMAN, + COMMAND_START_PACMAN_SCRAMBLE, + COMMAND_GET_NUMBER_OF_POWER_PILLS_CARRIED, + COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_CARRIED, + COMMAND_IS_CAR_ON_SCREEN, + COMMAND_IS_CHAR_ON_SCREEN, + COMMAND_IS_OBJECT_ON_SCREEN, + COMMAND_GOSUB_FILE, + COMMAND_GET_GROUND_Z_FOR_3D_COORD, + COMMAND_START_SCRIPT_FIRE, + COMMAND_IS_SCRIPT_FIRE_EXTINGUISHED, + COMMAND_REMOVE_SCRIPT_FIRE, + COMMAND_SET_COMEDY_CONTROLS, + COMMAND_BOAT_GOTO_COORDS, + COMMAND_BOAT_STOP, + COMMAND_IS_PLAYER_SHOOTING_IN_AREA, + COMMAND_IS_CHAR_SHOOTING_IN_AREA, + COMMAND_IS_CURRENT_PLAYER_WEAPON, + COMMAND_IS_CURRENT_CHAR_WEAPON, + COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_EATEN, + COMMAND_ADD_POWER_PILL, + COMMAND_SET_BOAT_CRUISE_SPEED, + COMMAND_GET_RANDOM_CHAR_IN_AREA, + COMMAND_GET_RANDOM_CHAR_IN_ZONE, + COMMAND_IS_PLAYER_IN_TAXI, + COMMAND_IS_PLAYER_SHOOTING, + COMMAND_IS_CHAR_SHOOTING, + COMMAND_CREATE_MONEY_PICKUP, + COMMAND_SET_CHAR_ACCURACY, + COMMAND_GET_CAR_SPEED, + COMMAND_LOAD_CUTSCENE, + COMMAND_CREATE_CUTSCENE_OBJECT, + COMMAND_SET_CUTSCENE_ANIM, + COMMAND_START_CUTSCENE, + COMMAND_GET_CUTSCENE_TIME, + COMMAND_HAS_CUTSCENE_FINISHED, + COMMAND_CLEAR_CUTSCENE, + COMMAND_RESTORE_CAMERA_JUMPCUT, + COMMAND_CREATE_COLLECTABLE1, + COMMAND_SET_COLLECTABLE1_TOTAL, + COMMAND_IS_PROJECTILE_IN_AREA, + COMMAND_DESTROY_PROJECTILES_IN_AREA, + COMMAND_DROP_MINE, + COMMAND_DROP_NAUTICAL_MINE, + COMMAND_IS_CHAR_MODEL, + COMMAND_LOAD_SPECIAL_MODEL, + COMMAND_CREATE_CUTSCENE_HEAD, + COMMAND_SET_CUTSCENE_HEAD_ANIM, + COMMAND_SIN, + COMMAND_COS, + COMMAND_GET_CAR_FORWARD_X, + COMMAND_GET_CAR_FORWARD_Y, + COMMAND_CHANGE_GARAGE_TYPE, + COMMAND_ACTIVATE_CRUSHER_CRANE, + COMMAND_PRINT_WITH_2_NUMBERS, + COMMAND_PRINT_WITH_2_NUMBERS_NOW, + COMMAND_PRINT_WITH_2_NUMBERS_SOON, + COMMAND_PRINT_WITH_3_NUMBERS, + COMMAND_PRINT_WITH_3_NUMBERS_NOW, + COMMAND_PRINT_WITH_3_NUMBERS_SOON, + COMMAND_PRINT_WITH_4_NUMBERS, + COMMAND_PRINT_WITH_4_NUMBERS_NOW, + COMMAND_PRINT_WITH_4_NUMBERS_SOON, + COMMAND_PRINT_WITH_5_NUMBERS, + COMMAND_PRINT_WITH_5_NUMBERS_NOW, + COMMAND_PRINT_WITH_5_NUMBERS_SOON, + COMMAND_PRINT_WITH_6_NUMBERS, + COMMAND_PRINT_WITH_6_NUMBERS_NOW, + COMMAND_PRINT_WITH_6_NUMBERS_SOON, + COMMAND_SET_CHAR_OBJ_FOLLOW_CHAR_IN_FORMATION, + COMMAND_PLAYER_MADE_PROGRESS, + COMMAND_SET_PROGRESS_TOTAL, + COMMAND_REGISTER_JUMP_DISTANCE, + COMMAND_REGISTER_JUMP_HEIGHT, + COMMAND_REGISTER_JUMP_FLIPS, + COMMAND_REGISTER_JUMP_SPINS, + COMMAND_REGISTER_JUMP_STUNT, + COMMAND_REGISTER_UNIQUE_JUMP_FOUND, + COMMAND_SET_UNIQUE_JUMPS_TOTAL, + COMMAND_REGISTER_PASSENGER_DROPPED_OFF_TAXI, + COMMAND_REGISTER_MONEY_MADE_TAXI, + COMMAND_REGISTER_MISSION_GIVEN, + COMMAND_REGISTER_MISSION_PASSED, + COMMAND_SET_CHAR_RUNNING, + COMMAND_REMOVE_ALL_SCRIPT_FIRES, + COMMAND_IS_FIRST_CAR_COLOUR, + COMMAND_IS_SECOND_CAR_COLOUR, + COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_WEAPON, + COMMAND_HAS_CAR_BEEN_DAMAGED_BY_WEAPON, + COMMAND_IS_CHAR_IN_CHARS_GROUP, + COMMAND_IS_CHAR_IN_PLAYERS_GROUP, + COMMAND_EXPLODE_CHAR_HEAD, + COMMAND_EXPLODE_PLAYER_HEAD, + COMMAND_ANCHOR_BOAT, + COMMAND_SET_ZONE_GROUP, + COMMAND_START_CAR_FIRE, + COMMAND_START_CHAR_FIRE, + COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_AREA, + COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_ZONE, + COMMAND_HAS_RESPRAY_HAPPENED, + COMMAND_SET_CAMERA_ZOOM, + COMMAND_CREATE_PICKUP_WITH_AMMO, + COMMAND_SET_CAR_RAM_CAR, + COMMAND_SET_CAR_BLOCK_CAR, + COMMAND_SET_CHAR_OBJ_CATCH_TRAIN, + COMMAND_SET_COLL_OBJ_CATCH_TRAIN, + COMMAND_SET_PLAYER_NEVER_GETS_TIRED, + COMMAND_SET_PLAYER_FAST_RELOAD, + COMMAND_SET_CHAR_BLEEDING, + COMMAND_SET_CAR_FUNNY_SUSPENSION, + COMMAND_SET_CAR_BIG_WHEELS, + COMMAND_SET_FREE_RESPRAYS, + COMMAND_SET_PLAYER_VISIBLE, + COMMAND_SET_CHAR_VISIBLE, + COMMAND_SET_CAR_VISIBLE, + COMMAND_IS_AREA_OCCUPIED, + COMMAND_START_DRUG_RUN, + COMMAND_HAS_DRUG_RUN_BEEN_COMPLETED, + COMMAND_HAS_DRUG_PLANE_BEEN_SHOT_DOWN, + COMMAND_SAVE_PLAYER_FROM_FIRES, + COMMAND_DISPLAY_TEXT, + COMMAND_SET_TEXT_SCALE, + COMMAND_SET_TEXT_COLOUR, + COMMAND_SET_TEXT_JUSTIFY, + COMMAND_SET_TEXT_CENTRE, + COMMAND_SET_TEXT_WRAPX, + COMMAND_SET_TEXT_CENTRE_SIZE, + COMMAND_SET_TEXT_BACKGROUND, + COMMAND_SET_TEXT_BACKGROUND_COLOUR, + COMMAND_SET_TEXT_BACKGROUND_ONLY_TEXT, + COMMAND_SET_TEXT_PROPORTIONAL, + COMMAND_SET_TEXT_FONT, + COMMAND_INDUSTRIAL_PASSED, + COMMAND_COMMERCIAL_PASSED, + COMMAND_SUBURBAN_PASSED, + COMMAND_ROTATE_OBJECT, + COMMAND_SLIDE_OBJECT, + COMMAND_REMOVE_CHAR_ELEGANTLY, + COMMAND_SET_CHAR_STAY_IN_SAME_PLACE, + COMMAND_IS_NASTY_GAME, + COMMAND_UNDRESS_CHAR, + COMMAND_DRESS_CHAR, + COMMAND_START_CHASE_SCENE, + COMMAND_STOP_CHASE_SCENE, + COMMAND_IS_EXPLOSION_IN_AREA, + COMMAND_IS_EXPLOSION_IN_ZONE, + COMMAND_START_DRUG_DROP_OFF, + COMMAND_HAS_DROP_OFF_PLANE_BEEN_SHOT_DOWN, + COMMAND_FIND_DROP_OFF_PLANE_COORDINATES, + COMMAND_CREATE_FLOATING_PACKAGE, + COMMAND_PLACE_OBJECT_RELATIVE_TO_CAR, + COMMAND_MAKE_OBJECT_TARGETTABLE, + COMMAND_ADD_ARMOUR_TO_PLAYER, + COMMAND_ADD_ARMOUR_TO_CHAR, + COMMAND_OPEN_GARAGE, + COMMAND_CLOSE_GARAGE, + COMMAND_WARP_CHAR_FROM_CAR_TO_COORD, + COMMAND_SET_VISIBILITY_OF_CLOSEST_OBJECT_OF_TYPE, + COMMAND_HAS_CHAR_SPOTTED_CHAR, + COMMAND_SET_CHAR_OBJ_HAIL_TAXI, + COMMAND_HAS_OBJECT_BEEN_DAMAGED, + COMMAND_START_KILL_FRENZY_HEADSHOT, + COMMAND_ACTIVATE_MILITARY_CRANE, + COMMAND_WARP_PLAYER_INTO_CAR, + COMMAND_WARP_CHAR_INTO_CAR, + COMMAND_SWITCH_CAR_RADIO, + COMMAND_SET_AUDIO_STREAM, + COMMAND_PRINT_WITH_2_NUMBERS_BIG, + COMMAND_PRINT_WITH_3_NUMBERS_BIG, + COMMAND_PRINT_WITH_4_NUMBERS_BIG, + COMMAND_PRINT_WITH_5_NUMBERS_BIG, + COMMAND_PRINT_WITH_6_NUMBERS_BIG, + COMMAND_SET_CHAR_WAIT_STATE, + COMMAND_SET_CAMERA_BEHIND_PLAYER, + COMMAND_SET_MOTION_BLUR, + COMMAND_PRINT_STRING_IN_STRING, + COMMAND_CREATE_RANDOM_CHAR, + COMMAND_SET_CHAR_OBJ_STEAL_ANY_CAR, + COMMAND_SET_2_REPEATED_PHONE_MESSAGES, + COMMAND_SET_2_PHONE_MESSAGES, + COMMAND_SET_3_REPEATED_PHONE_MESSAGES, + COMMAND_SET_3_PHONE_MESSAGES, + COMMAND_SET_4_REPEATED_PHONE_MESSAGES, + COMMAND_SET_4_PHONE_MESSAGES, + COMMAND_IS_SNIPER_BULLET_IN_AREA, + COMMAND_GIVE_PLAYER_DETONATOR, + COMMAND_SET_COLL_OBJ_STEAL_ANY_CAR, + COMMAND_SET_OBJECT_VELOCITY, + COMMAND_SET_OBJECT_COLLISION, + COMMAND_IS_ICECREAM_JINGLE_ON, + COMMAND_PRINT_STRING_IN_STRING_NOW, + COMMAND_PRINT_STRING_IN_STRING_SOON, + COMMAND_SET_5_REPEATED_PHONE_MESSAGES, + COMMAND_SET_5_PHONE_MESSAGES, + COMMAND_SET_6_REPEATED_PHONE_MESSAGES, + COMMAND_SET_6_PHONE_MESSAGES, + COMMAND_IS_POINT_OBSCURED_BY_A_MISSION_ENTITY, + COMMAND_LOAD_ALL_MODELS_NOW, + COMMAND_ADD_TO_OBJECT_VELOCITY, + COMMAND_DRAW_SPRITE, + COMMAND_DRAW_RECT, + COMMAND_LOAD_SPRITE, + COMMAND_LOAD_TEXTURE_DICTIONARY, + COMMAND_REMOVE_TEXTURE_DICTIONARY, + COMMAND_SET_OBJECT_DYNAMIC, + COMMAND_SET_CHAR_ANIM_SPEED, + COMMAND_PLAY_MISSION_PASSED_TUNE, + COMMAND_CLEAR_AREA, + COMMAND_FREEZE_ONSCREEN_TIMER, + COMMAND_SWITCH_CAR_SIREN, + COMMAND_SWITCH_PED_ROADS_ON_ANGLED, + COMMAND_SWITCH_PED_ROADS_OFF_ANGLED, + COMMAND_SWITCH_ROADS_ON_ANGLED, + COMMAND_SWITCH_ROADS_OFF_ANGLED, + COMMAND_SET_CAR_WATERTIGHT, + COMMAND_ADD_MOVING_PARTICLE_EFFECT, + COMMAND_SET_CHAR_CANT_BE_DRAGGED_OUT, + COMMAND_TURN_CAR_TO_FACE_COORD, + COMMAND_IS_CRANE_LIFTING_CAR, + COMMAND_DRAW_SPHERE, + COMMAND_SET_CAR_STATUS, + COMMAND_IS_CHAR_MALE, + COMMAND_SCRIPT_NAME, + COMMAND_CHANGE_GARAGE_TYPE_WITH_CAR_MODEL, + COMMAND_FIND_DRUG_PLANE_COORDINATES, + COMMAND_SAVE_INT_TO_DEBUG_FILE, + COMMAND_SAVE_FLOAT_TO_DEBUG_FILE, + COMMAND_SAVE_NEWLINE_TO_DEBUG_FILE, + COMMAND_POLICE_RADIO_MESSAGE, + COMMAND_SET_CAR_STRONG, + COMMAND_REMOVE_ROUTE, + COMMAND_SWITCH_RUBBISH, + COMMAND_REMOVE_PARTICLE_EFFECTS_IN_AREA, + COMMAND_SWITCH_STREAMING, + COMMAND_IS_GARAGE_OPEN, + COMMAND_IS_GARAGE_CLOSED, + COMMAND_START_CATALINA_HELI, + COMMAND_CATALINA_HELI_TAKE_OFF, + COMMAND_REMOVE_CATALINA_HELI, + COMMAND_HAS_CATALINA_HELI_BEEN_SHOT_DOWN, + COMMAND_SWAP_NEAREST_BUILDING_MODEL, + COMMAND_SWITCH_WORLD_PROCESSING, + COMMAND_REMOVE_ALL_PLAYER_WEAPONS, + COMMAND_GRAB_CATALINA_HELI, + COMMAND_CLEAR_AREA_OF_CARS, + COMMAND_SET_ROTATING_GARAGE_DOOR, + COMMAND_ADD_SPHERE, + COMMAND_REMOVE_SPHERE, + COMMAND_CATALINA_HELI_FLY_AWAY, + COMMAND_SET_EVERYONE_IGNORE_PLAYER, + COMMAND_STORE_CAR_CHAR_IS_IN_NO_SAVE, + COMMAND_STORE_CAR_PLAYER_IS_IN_NO_SAVE, + COMMAND_IS_PHONE_DISPLAYING_MESSAGE, + COMMAND_DISPLAY_ONSCREEN_TIMER_WITH_STRING, + COMMAND_DISPLAY_ONSCREEN_COUNTER_WITH_STRING, + COMMAND_CREATE_RANDOM_CAR_FOR_CAR_PARK, + COMMAND_IS_COLLISION_IN_MEMORY, + COMMAND_SET_WANTED_MULTIPLIER, + COMMAND_SET_CAMERA_IN_FRONT_OF_PLAYER, + COMMAND_IS_CAR_VISIBLY_DAMAGED, + COMMAND_DOES_OBJECT_EXIST, + COMMAND_LOAD_SCENE, + COMMAND_ADD_STUCK_CAR_CHECK, + COMMAND_REMOVE_STUCK_CAR_CHECK, + COMMAND_IS_CAR_STUCK, + COMMAND_LOAD_MISSION_AUDIO, + COMMAND_HAS_MISSION_AUDIO_LOADED, + COMMAND_PLAY_MISSION_AUDIO, + COMMAND_HAS_MISSION_AUDIO_FINISHED, + COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING, + COMMAND_HAS_IMPORT_GARAGE_SLOT_BEEN_FILLED, + COMMAND_CLEAR_THIS_PRINT, + COMMAND_CLEAR_THIS_BIG_PRINT, + COMMAND_SET_MISSION_AUDIO_POSITION, + COMMAND_ACTIVATE_SAVE_MENU, + COMMAND_HAS_SAVE_GAME_FINISHED, + COMMAND_NO_SPECIAL_CAMERA_FOR_THIS_GARAGE, + COMMAND_ADD_BLIP_FOR_PICKUP_OLD, + COMMAND_ADD_BLIP_FOR_PICKUP, + COMMAND_ADD_SPRITE_BLIP_FOR_PICKUP, + COMMAND_SET_PED_DENSITY_MULTIPLIER, + COMMAND_FORCE_RANDOM_PED_TYPE, + COMMAND_SET_TEXT_DRAW_BEFORE_FADE, + COMMAND_GET_COLLECTABLE1S_COLLECTED, + COMMAND_REGISTER_EL_BURRO_TIME, + COMMAND_SET_SPRITES_DRAW_BEFORE_FADE, + COMMAND_SET_TEXT_RIGHT_JUSTIFY, + COMMAND_PRINT_HELP, + COMMAND_CLEAR_HELP, + COMMAND_FLASH_HUD_OBJECT, + COMMAND_FLASH_RADAR_BLIP, + COMMAND_IS_CHAR_IN_CONTROL, + COMMAND_SET_GENERATE_CARS_AROUND_CAMERA, + COMMAND_CLEAR_SMALL_PRINTS, + COMMAND_HAS_MILITARY_CRANE_COLLECTED_ALL_CARS, + COMMAND_SET_UPSIDEDOWN_CAR_NOT_DAMAGED, + COMMAND_CAN_PLAYER_START_MISSION, + COMMAND_MAKE_PLAYER_SAFE_FOR_CUTSCENE, + COMMAND_USE_TEXT_COMMANDS, + COMMAND_SET_THREAT_FOR_PED_TYPE, + COMMAND_CLEAR_THREAT_FOR_PED_TYPE, + COMMAND_GET_CAR_COLOURS, + COMMAND_SET_ALL_CARS_CAN_BE_DAMAGED, + COMMAND_SET_CAR_CAN_BE_DAMAGED, + COMMAND_MAKE_PLAYER_UNSAFE, + COMMAND_LOAD_COLLISION, + COMMAND_GET_BODY_CAST_HEALTH, + COMMAND_SET_CHARS_CHATTING, + COMMAND_MAKE_PLAYER_SAFE, + COMMAND_SET_CAR_STAYS_IN_CURRENT_LEVEL, + COMMAND_SET_CHAR_STAYS_IN_CURRENT_LEVEL, + COMMAND_REGISTER_4X4_ONE_TIME, + COMMAND_REGISTER_4X4_TWO_TIME, + COMMAND_REGISTER_4X4_THREE_TIME, + COMMAND_REGISTER_4X4_MAYHEM_TIME, + COMMAND_REGISTER_LIFE_SAVED, + COMMAND_REGISTER_CRIMINAL_CAUGHT, + COMMAND_REGISTER_AMBULANCE_LEVEL, + COMMAND_REGISTER_FIRE_EXTINGUISHED, + COMMAND_TURN_PHONE_ON, + COMMAND_REGISTER_LONGEST_DODO_FLIGHT, + COMMAND_REGISTER_DEFUSE_BOMB_TIME, + COMMAND_SET_TOTAL_NUMBER_OF_KILL_FRENZIES, + COMMAND_BLOW_UP_RC_BUGGY, + COMMAND_REMOVE_CAR_FROM_CHASE, + COMMAND_IS_FRENCH_GAME, + COMMAND_IS_GERMAN_GAME, + COMMAND_CLEAR_MISSION_AUDIO, + COMMAND_SET_FADE_IN_AFTER_NEXT_ARREST, + COMMAND_SET_FADE_IN_AFTER_NEXT_DEATH, + COMMAND_SET_GANG_PED_MODEL_PREFERENCE, + COMMAND_SET_CHAR_USE_PEDNODE_SEEK, + COMMAND_SWITCH_VEHICLE_WEAPONS, + COMMAND_SET_GET_OUT_OF_JAIL_FREE, + COMMAND_SET_FREE_HEALTH_CARE, + COMMAND_IS_CAR_DOOR_CLOSED, + COMMAND_LOAD_AND_LAUNCH_MISSION, + COMMAND_LOAD_AND_LAUNCH_MISSION_INTERNAL, + COMMAND_SET_OBJECT_DRAW_LAST, + COMMAND_GET_AMMO_IN_PLAYER_WEAPON, + COMMAND_GET_AMMO_IN_CHAR_WEAPON, + COMMAND_REGISTER_KILL_FRENZY_PASSED, + COMMAND_SET_CHAR_SAY, + COMMAND_SET_NEAR_CLIP, + COMMAND_SET_RADIO_CHANNEL, + COMMAND_OVERRIDE_HOSPITAL_LEVEL, + COMMAND_OVERRIDE_POLICE_STATION_LEVEL, + COMMAND_FORCE_RAIN, + COMMAND_DOES_GARAGE_CONTAIN_CAR, + COMMAND_SET_CAR_TRACTION, + COMMAND_ARE_MEASUREMENTS_IN_METRES, + COMMAND_CONVERT_METRES_TO_FEET, + COMMAND_MARK_ROADS_BETWEEN_LEVELS, + COMMAND_MARK_PED_ROADS_BETWEEN_LEVELS, + COMMAND_SET_CAR_AVOID_LEVEL_TRANSITIONS, + COMMAND_SET_CHAR_AVOID_LEVEL_TRANSITIONS, + COMMAND_IS_THREAT_FOR_PED_TYPE, + COMMAND_CLEAR_AREA_OF_CHARS, + COMMAND_SET_TOTAL_NUMBER_OF_MISSIONS, + COMMAND_CONVERT_METRES_TO_FEET_INT, + COMMAND_REGISTER_FASTEST_TIME, + COMMAND_REGISTER_HIGHEST_SCORE, + COMMAND_WARP_CHAR_INTO_CAR_AS_PASSENGER, + COMMAND_IS_CAR_PASSENGER_SEAT_FREE, + COMMAND_GET_CHAR_IN_CAR_PASSENGER_SEAT, + COMMAND_SET_CHAR_IS_CHRIS_CRIMINAL, + COMMAND_START_CREDITS, + COMMAND_STOP_CREDITS, + COMMAND_ARE_CREDITS_FINISHED, + COMMAND_CREATE_SINGLE_PARTICLE, + COMMAND_SET_CHAR_IGNORE_LEVEL_TRANSITIONS, + COMMAND_GET_CHASE_CAR, + COMMAND_START_BOAT_FOAM_ANIMATION, + COMMAND_UPDATE_BOAT_FOAM_ANIMATION, + COMMAND_SET_MUSIC_DOES_FADE, + COMMAND_SET_INTRO_IS_PLAYING, + COMMAND_SET_PLAYER_HOOKER, + COMMAND_PLAY_END_OF_GAME_TUNE, + COMMAND_STOP_END_OF_GAME_TUNE, + COMMAND_GET_CAR_MODEL, + COMMAND_IS_PLAYER_SITTING_IN_CAR, + COMMAND_IS_PLAYER_SITTING_IN_ANY_CAR, + COMMAND_SET_SCRIPT_FIRE_AUDIO, + COMMAND_ARE_ANY_CAR_CHEATS_ACTIVATED, + COMMAND_SET_CHAR_SUFFERS_CRITICAL_HITS, + COMMAND_IS_PLAYER_LIFTING_A_PHONE, + COMMAND_IS_CHAR_SITTING_IN_CAR, + COMMAND_IS_CHAR_SITTING_IN_ANY_CAR, + COMMAND_IS_PLAYER_ON_FOOT, + COMMAND_IS_CHAR_ON_FOOT, + COMMAND_LOAD_COLLISION_WITH_SCREEN, + COMMAND_LOAD_SPLASH_SCREEN, + COMMAND_SET_CAR_IGNORE_LEVEL_TRANSITIONS, + COMMAND_MAKE_CRAIGS_CAR_A_BIT_STRONGER, + COMMAND_SET_JAMES_CAR_ON_PATH_TO_PLAYER, + COMMAND_LOAD_END_OF_GAME_TUNE, + COMMAND_ENABLE_PLAYER_CONTROL_CAMERA, +#if GTA_VERSION > GTA3_PS2_160 + COMMAND_SET_OBJECT_ROTATION, + COMMAND_GET_DEBUG_CAMERA_COORDINATES, + COMMAND_GET_DEBUG_CAMERA_FRONT_VECTOR, + COMMAND_IS_PLAYER_TARGETTING_ANY_CHAR, + COMMAND_IS_PLAYER_TARGETTING_CHAR, + COMMAND_IS_PLAYER_TARGETTING_OBJECT, + COMMAND_TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, + COMMAND_DISPLAY_TEXT_WITH_NUMBER, + COMMAND_DISPLAY_TEXT_WITH_2_NUMBERS, + COMMAND_FAIL_CURRENT_MISSION, + COMMAND_GET_CLOSEST_OBJECT_OF_TYPE, + COMMAND_PLACE_OBJECT_RELATIVE_TO_OBJECT, + COMMAND_SET_ALL_OCCUPANTS_OF_CAR_LEAVE_CAR, + COMMAND_SET_INTERPOLATION_PARAMETERS, + COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_TOWARDS_POINT, + COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_AWAY_POINT, + COMMAND_GET_DEBUG_CAMERA_POINT_AT, + COMMAND_ATTACH_CHAR_TO_CAR, + COMMAND_DETACH_CHAR_FROM_CAR, + COMMAND_SET_CAR_CHANGE_LANE, + COMMAND_CLEAR_CHAR_LAST_WEAPON_DAMAGE, + COMMAND_CLEAR_CAR_LAST_WEAPON_DAMAGE, + COMMAND_GET_RANDOM_COP_IN_AREA, + COMMAND_GET_RANDOM_COP_IN_ZONE, + COMMAND_SET_CHAR_OBJ_FLEE_CAR, + COMMAND_GET_DRIVER_OF_CAR, + COMMAND_GET_NUMBER_OF_FOLLOWERS, + COMMAND_GIVE_REMOTE_CONTROLLED_MODEL_TO_PLAYER, + COMMAND_GET_CURRENT_PLAYER_WEAPON, + COMMAND_GET_CURRENT_CHAR_WEAPON, + COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_2D, + COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_2D, + COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_2D, + COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D, + COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D, + COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D, + COMMAND_SET_CAR_HANDBRAKE_TURN_LEFT, + COMMAND_SET_CAR_HANDBRAKE_TURN_RIGHT, + COMMAND_SET_CAR_HANDBRAKE_STOP, + COMMAND_IS_CHAR_ON_ANY_BIKE, + COMMAND_LOCATE_SNIPER_BULLET_2D, + COMMAND_LOCATE_SNIPER_BULLET_3D, + COMMAND_GET_NUMBER_OF_SEATS_IN_MODEL, + COMMAND_IS_PLAYER_ON_ANY_BIKE, + COMMAND_IS_CHAR_LYING_DOWN, + COMMAND_CAN_CHAR_SEE_DEAD_CHAR, + COMMAND_SET_ENTER_CAR_RANGE_MULTIPLIER, +#if GTA_VERSION < GTA3_PC_11 + COMMAND_SET_THREAT_REACTION_RANGE_MULTIPLIER, +#endif +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LAST_SCRIPT_COMMAND +#endif +#endif +}; + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + +enum eScriptArgument +{ + ARGTYPE_NONE = 0, + ARGTYPE_INT, + ARGTYPE_FLOAT, + ARGTYPE_STRING, + ARGTYPE_LABEL, + ARGTYPE_BOOL, + ARGTYPE_PED_HANDLE, + ARGTYPE_VEHICLE_HANDLE, + ARGTYPE_OBJECT_HANDLE, + ARGTYPE_ANDOR +}; + +struct tScriptCommandData +{ + int id; + const char name[64]; + eScriptArgument input[18]; + eScriptArgument output[18]; + bool cond; + int position; + const char name_override[8]; +}; +#endif \ No newline at end of file diff --git a/src/control/ScriptDebug.cpp b/src/control/ScriptDebug.cpp new file mode 100644 index 0000000..6350821 --- /dev/null +++ b/src/control/ScriptDebug.cpp @@ -0,0 +1,1441 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "Debug.h" +#include "FileMgr.h" +#include "Messages.h" +#include "Timer.h" +#include "Stats.h" +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#include +#endif + +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + +char CRunningScript::commandInfo[1024]; +uint32 CRunningScript::storedIp; + +#define REGISTER_COMMAND(command, in, out, cond, ovrd, visual) { command, #command, in, out, cond, ovrd, visual } +#define INPUT_ARGUMENTS(...) { __VA_ARGS__ ARGTYPE_NONE } +#define OUTPUT_ARGUMENTS(...) { __VA_ARGS__ ARGTYPE_NONE } +const tScriptCommandData commands[] = { + REGISTER_COMMAND(COMMAND_NOP, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WAIT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GOTO, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SHAKE_CAM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_ADD_VAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_VAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_VAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_VAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_SUB_VAL_FROM_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_VAL_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_VAL_FROM_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_VAL_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_MULT_INT_VAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_VAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_INT_LVAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_LVAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_DIV_INT_VAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_VAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_INT_LVAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_LVAR_BY_VAL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_THAN_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_THAN_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_THAN_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_THAN_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_THAN_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_THAN_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_THAN_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_THAN_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_THAN_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_THAN_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_THAN_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_THAN_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_THAN_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_NUMBER_GREATER_OR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_GREATER_OR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_GREATER_OR_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_NOT_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_NOT_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_NOT_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " =="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_LVAR_NOT_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_IS_FLOAT_VAR_NOT_EQUAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, 0, " !="), + REGISTER_COMMAND(COMMAND_GOTO_IF_TRUE, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GOTO_IF_FALSE, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TERMINATE_THIS_SCRIPT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_NEW_SCRIPT, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GOSUB, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RETURN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LINE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_INT_VAR_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_FLOAT_VAR_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_INT_LVAR_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_INT_VAR_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_FLOAT_VAR_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_INT_LVAR_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_ADD_FLOAT_LVAR_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +="), + REGISTER_COMMAND(COMMAND_SUB_INT_VAR_FROM_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_INT_LVAR_FROM_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_INT_VAR_FROM_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_FLOAT_VAR_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_INT_LVAR_FROM_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_SUB_FLOAT_LVAR_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -="), + REGISTER_COMMAND(COMMAND_MULT_INT_VAR_BY_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_VAR_BY_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_INT_LVAR_BY_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_INT_VAR_BY_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_VAR_BY_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_INT_LVAR_BY_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_MULT_FLOAT_LVAR_BY_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " *="), + REGISTER_COMMAND(COMMAND_DIV_INT_VAR_BY_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_VAR_BY_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_INT_LVAR_BY_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_INT_VAR_BY_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_VAR_BY_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_INT_LVAR_BY_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_DIV_FLOAT_LVAR_BY_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " /="), + REGISTER_COMMAND(COMMAND_ADD_TIMED_VAL_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_VAL_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_FLOAT_LVAR_TO_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_ADD_TIMED_FLOAT_VAR_TO_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " +=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_VAL_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_VAL_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_FLOAT_LVAR_FROM_FLOAT_VAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SUB_TIMED_FLOAT_VAR_FROM_FLOAT_LVAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " -=@"), + REGISTER_COMMAND(COMMAND_SET_VAR_INT_TO_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_INT_TO_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_VAR_FLOAT_TO_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_FLOAT_TO_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_VAR_FLOAT_TO_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_FLOAT_TO_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_VAR_INT_TO_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_INT_TO_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_CSET_VAR_INT_TO_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_VAR_FLOAT_TO_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_LVAR_INT_TO_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_LVAR_FLOAT_TO_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_VAR_INT_TO_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_VAR_FLOAT_TO_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_LVAR_INT_TO_LVAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_CSET_LVAR_FLOAT_TO_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " =#"), + REGISTER_COMMAND(COMMAND_ABS_VAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_ABS_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_ABS_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_ABS_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_GENERATE_RANDOM_FLOAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GENERATE_RANDOM_INT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DELETE_CHAR, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_WANDER_DIR, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_WANDER_RANGE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_FOLLOW_PATH, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_SET_IDLE, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHAR_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STILL_ALIVE, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_PED_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DELETE_CAR, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAR_GOTO_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAR_WANDER_RANDOMLY, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAR_SET_IDLE, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STILL_ALIVE, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_CRUISE_SPEED, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_DRIVING_STYLE, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_MISSION, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_VEHICLE_HANDLE, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_BOOL,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_0, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_1, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_2, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_3, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_4, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_5, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_6, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SPECIAL_7, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_PRINTS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_TIME_OF_DAY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TIME_OF_DAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_MINUTES_TO_TIME_OF_DAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_POINT_ON_SCREEN, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DEBUG_ON, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DEBUG_OFF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RETURN_TRUE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RETURN_FALSE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_VAR_INT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_VAR_FLOAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LVAR_INT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LVAR_FLOAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LBRACKET, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RBRACKET, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REPEAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ENDREPEAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IFNOT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ELSE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ENDIF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WHILE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WHILENOT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ENDWHILE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ANDOR, INPUT_ARGUMENTS(ARGTYPE_ANDOR,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LAUNCH_MISSION, INPUT_ARGUMENTS(ARGTYPE_LABEL,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MISSION_HAS_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CAR_CHAR_IS_IN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CAR_PLAYER_IS_IN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_BUTTON_PRESSED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PAD_STATE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_ANY_MEANS_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_PLAYER_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_ANY_MEANS_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CHAR_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_CHAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), true, -1, ""), + REGISTER_COMMAND(COMMAND_DELETE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SCORE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_SCORE_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_SCORE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_REMOTE_CONTROLLED_CAR_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ALTER_WANTED_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ALTER_WANTED_LEVEL_NO_DROP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_WANTED_LEVEL_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_WANTED_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_DEATHARREST_STATE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_DEATHARREST_BEEN_EXECUTED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_AMMO_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_AMMO_TO_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_AMMO_TO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STILL_ALIVE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_DEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_DEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_DEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_THREAT_SEARCH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_THREAT_REACTION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_NO_OBJ, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ORDER_DRIVER_OUT_OF_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ORDER_CHAR_TO_DRIVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PATROL_POINT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_GANGZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_PRESSING_HORN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_SPOTTED_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ORDER_CHAR_TO_BACKDOOR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_CHAR_TO_GANG, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_OBJECTIVE_PASSED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_DRIVE_AGGRESSION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_MAX_DRIVESPEED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CHAR_INSIDE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_PLAYER_FROM_CAR_TO_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_CHAR_DO_NOTHING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_INVINCIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_INVINCIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_GRAPHIC_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_GRAPHIC_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PLAYER_BEEN_ARRESTED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_CHAR_DRIVING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_KILL_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FAVOURITE_CAR_MODEL_FOR_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OCCUPATION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_CAR_LOCK, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SHAKE_CAM_WITH_POINT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_REMAP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_JUST_SUNK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_NO_COLLIDE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_DEAD_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_DEAD_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_TRAILER_ATTACHED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_ON_TRAILER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_GOT_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_PARK, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PARK_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_KILL_ALL_PASSENGERS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_BULLETPROOF, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_FLAMEPROOF, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_ROCKETPROOF, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CARBOMB_ACTIVE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_CAR_ALARM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PUT_CAR_ON_TRAILER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_CRUSHED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_GANG_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CAR_GENERATOR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_CAR_GENERATOR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PAGER_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_ONSCREEN_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_ONSCREEN_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_ONSCREEN_COUNTER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_ONSCREEN_COUNTER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ZONE_CAR_INFO, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_GANG_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_DENSITY, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PED_DENSITY, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POINT_CAMERA_AT_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POINT_CAMERA_AT_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POINT_CAMERA_AT_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTORE_CAMERA, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SHAKE_PAD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ZONE_PED_INFO, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TIME_SCALE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_AIR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FIXED_CAMERA_POSITION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POINT_CAMERA_AT_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CAR_OLD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CHAR_OLD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_OBJECT_OLD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_BLIP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_BLIP_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DIM_BLIP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_COORD_OLD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_BLIP_SCALE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FADING_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DO_FADE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_FADING_STATUS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_HOSPITAL_RESTART, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_POLICE_RESTART, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_OVERRIDE_NEXT_RESTART, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_SHADOW, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHAR_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OBJECT_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_HEADING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TOUCHING_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_TOUCHING_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_AMMO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_AMMO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_AMMO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_CAMERA_SPLINE, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MOVE_CAMERA_ALONG_SPLINE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAMERA_POSITION_ALONG_SPLINE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DECLARE_MISSION_FLAG, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DECLARE_MISSION_FLAG_FOR_CONTACT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DECLARE_BASE_BRIEF_ID_FOR_CONTACT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_HEALTH_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_HEALTH_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_HEALTH_GREATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_CONTACT_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_COORD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_BLIP_DISPLAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_ONE_OFF_SOUND, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_CONTINUOUS_SOUND, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_SOUND, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STUCK_ON_ROOF, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_UPSIDEDOWN_CAR_CHECK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_UPSIDEDOWN_CAR_CHECK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_WAIT_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GUARD_SPOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GUARD_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_WAIT_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED_IN_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STOPPED_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STOPPED_IN_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_WEAPON_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_WEAPON_TO_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_WEAPON_TO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_CONTROL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FORCE_WEATHER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FORCE_WEATHER_NOW, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RELEASE_WEATHER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CURRENT_PLAYER_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CURRENT_CHAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CURRENT_CAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OBJECT_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_COORDINATES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_GAME_TIMER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_CHAR_TO_FACE_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_PLAYER_TO_FACE_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_WANTED_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STOPPED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_CHAR_AS_NO_LONGER_NEEDED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_CAR_AS_NO_LONGER_NEEDED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_OBJECT_AS_NO_LONGER_NEEDED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DONT_REMOVE_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DONT_REMOVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DONT_REMOVE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CHAR_AS_PASSENGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_KILL_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_KILL_CHAR_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_KILL_PLAYER_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_LEAVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_PASSENGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_ENTER_CAR_AS_DRIVER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_DESTROY_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_DESTROY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_AREA_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_AREA_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GUARD_ATTACK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_AS_LEADER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_AS_LEADER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LEAVE_GROUP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FOLLOW_ROUTE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_ROUTE_POINT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_ROADS_ON, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_ROADS_OFF, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_PASSENGERS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_MAXIMUM_NUMBER_OF_PASSENGERS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_DENSITY_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HEAVY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CHAR_THREAT_SEARCH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_CRANE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DEACTIVATE_CRANE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_MAX_WANTED_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_VAR_INT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_VAR_FLOAT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_AIR_PROPER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_UPSIDEDOWN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CANCEL_OVERRIDE_RESTART, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_POLICE_IGNORE_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PAGER_MESSAGE_WITH_NUMBER, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_KILL_FRENZY, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_READ_KILL_FRENZY_STATUS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SQRT, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ANY_MEANS_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_ON_FOOT_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_PLAYER_IN_CAR_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GENERATE_RANDOM_FLOAT_IN_RANGE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GENERATE_RANDOM_INT_IN_RANGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCK_CAR_DOORS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_EXPLODE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_EXPLOSION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_UPRIGHT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_CHAR_TO_FACE_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_CHAR_TO_FACE_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_PLAYER_TO_FACE_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_COORD_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_COORD_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_PICKUP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PICKUP_BEEN_COLLECTED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_PICKUP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TAXI_LIGHTS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_BIG_Q, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_NUMBER_BIG_Q, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GARAGE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GARAGE_WITH_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TARGET_CAR_FOR_MISSION_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_MISSION_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FREE_BOMBS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_POWERPOINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ALL_TAXI_LIGHTS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_ARMED_WITH_ANY_BOMB, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_APPLY_BRAKES_TO_PLAYERS_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHAR_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_ARMED_WITH_BOMB, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_CAR_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_PED_ROADS_ON, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_PED_ROADS_OFF, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_LOOK_AT_CHAR_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHAR_LOOK_AT_PLAYER_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLAYER_LOOK_AT_CHAR_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_CHAR_LOOKING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_PLAYER_LOOKING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_HELICOPTER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_ATTITUDE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_GANG_ATTITUDE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_PLAYER_ATTITUDE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_PED_MODELS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_WEAPONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_RUN_TO_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_RUN_TO_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TOUCHING_OBJECT_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_TOUCHING_OBJECT_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SPECIAL_CHARACTER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_SPECIAL_CHARACTER_LOADED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_REMOTE_MODE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ARM_CAR_WITH_BOMB, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_PERSONALITY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CUTSCENE_OFFSET, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ANIM_GROUP_FOR_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ANIM_GROUP_FOR_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REQUEST_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MODEL_LOADED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_MODEL_AS_NO_LONGER_NEEDED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GRAB_PHONE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_REPEATED_PHONE_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PHONE_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PHONE_DISPLAYED_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_PHONE_OFF, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_CORONA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_LIGHT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_WEATHER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTORE_WEATHER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CLOCK, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTORE_CLOCK, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTART_CRITICAL_MISSION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_PLAYING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_NO_OBJ, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_WAIT_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GUARD_SPOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GUARD_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_WAIT_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_KILL_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_KILL_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_KILL_CHAR_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_KILL_PLAYER_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_TILL_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_CHAR_ON_FOOT_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FLEE_PLAYER_ON_FOOT_ALWAYS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_LEAVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_PASSENGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_ENTER_CAR_AS_DRIVER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FOLLOW_CAR_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FIRE_AT_OBJECT_FROM_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_DESTROY_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_DESTROY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_AREA_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_AREA_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GUARD_ATTACK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_FOLLOW_ROUTE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_COORD_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_COORD_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_RUN_TO_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_RUN_TO_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PEDS_IN_AREA_TO_COLL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PEDS_IN_VEHICLE_TO_COLL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_COLL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_IN_CARS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_COLL_ANY_MEANS_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_COLL_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_STOPPED_COLL_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ANY_MEANS_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ON_FOOT_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_IN_CAR_CHAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ANY_MEANS_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ON_FOOT_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_IN_CAR_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ANY_MEANS_PLAYER_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_ON_FOOT_PLAYER_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_COLL_IN_CAR_PLAYER_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_STOPPED_IN_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_STOPPED_IN_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLL_STOPPED_IN_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_PEDS_IN_COLL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_HEED_THREATS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_HEED_THREATS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CONTROLLER_MODE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAN_RESPRAY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_TAXI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_UNLOAD_SPECIAL_CHARACTER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESET_NUM_OF_MODELS_KILLED_BY_PLAYER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUM_OF_MODELS_KILLED_BY_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_TAXI_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_OBJECT_NO_OFFSET, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_BOAT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_AREA_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_GOTO_AREA_ANY_MEANS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STOPPED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_MESSAGE_WAIT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PARTICLE_EFFECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_WIDESCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_CONTACT_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_COORD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_ONLY_DAMAGED_BY_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_ONLY_DAMAGED_BY_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_PROOFS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_PROOFS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANGLED_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_ON_FOOT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STOPPED_IN_ANGLED_AREA_IN_CAR_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DEACTIVATE_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_CARS_COLLECTED_BY_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_BEEN_TAKEN_TO_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_SWAT_REQUIRED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FBI_REQUIRED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ARMY_REQUIRED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_IN_WATER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CHAR_NODE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CAR_NODE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAR_GOTO_COORDINATES_ACCURATE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_PACMAN_RACE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_PACMAN_RECORD, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_POWER_PILLS_EATEN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_PACMAN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_PACMAN_SCRAMBLE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_POWER_PILLS_CARRIED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_CARRIED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_ON_SCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_ON_SCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_OBJECT_ON_SCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GOSUB_FILE, INPUT_ARGUMENTS(ARGTYPE_LABEL, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_GROUND_Z_FOR_3D_COORD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_SCRIPT_FIRE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_SCRIPT_FIRE_EXTINGUISHED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_SCRIPT_FIRE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COMEDY_CONTROLS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_BOAT_GOTO_COORDS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_BOAT_STOP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_SHOOTING_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_SHOOTING_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CURRENT_PLAYER_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CURRENT_CHAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_NUMBER_OF_POWER_PILLS_EATEN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_POWER_PILL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_BOAT_CRUISE_SPEED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CHAR_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CHAR_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_TAXI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_SHOOTING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_SHOOTING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_MONEY_PICKUP, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_ACCURACY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_SPEED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_CUTSCENE, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CUTSCENE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CUTSCENE_ANIM, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CUTSCENE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CUTSCENE_TIME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CUTSCENE_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CUTSCENE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESTORE_CAMERA_JUMPCUT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_COLLECTABLE1, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLLECTABLE1_TOTAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PROJECTILE_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DESTROY_PROJECTILES_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DROP_MINE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DROP_NAUTICAL_MINE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SPECIAL_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CUTSCENE_HEAD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CUTSCENE_HEAD_ANIM, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SIN, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_COS, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_FORWARD_X, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_FORWARD_Y, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_GARAGE_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_CRUSHER_CRANE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_2_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_2_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_2_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_3_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_3_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_3_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_4_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_4_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_4_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_5_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_5_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_5_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_6_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_6_NUMBERS_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_6_NUMBERS_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FOLLOW_CHAR_IN_FORMATION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLAYER_MADE_PROGRESS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PROGRESS_TOTAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_DISTANCE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_HEIGHT, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_FLIPS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_SPINS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_JUMP_STUNT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_UNIQUE_JUMP_FOUND, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_UNIQUE_JUMPS_TOTAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_PASSENGER_DROPPED_OFF_TAXI, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_MONEY_MADE_TAXI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_MISSION_GIVEN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_MISSION_PASSED, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_RUNNING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_ALL_SCRIPT_FIRES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_FIRST_CAR_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_SECOND_CAR_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_BEEN_DAMAGED_BY_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_CHARS_GROUP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_PLAYERS_GROUP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_EXPLODE_CHAR_HEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_EXPLODE_PLAYER_HEAD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ANCHOR_BOAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ZONE_GROUP, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CAR_FIRE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CHAR_FIRE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_RESPRAY_HAPPENED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAMERA_ZOOM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_PICKUP_WITH_AMMO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_RAM_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_BLOCK_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_CATCH_TRAIN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_CATCH_TRAIN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_NEVER_GETS_TIRED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_FAST_RELOAD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_BLEEDING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_FUNNY_SUSPENSION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_BIG_WHEELS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FREE_RESPRAYS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_VISIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_VISIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_VISIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_AREA_OCCUPIED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_START_DRUG_RUN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_DRUG_RUN_BEEN_COMPLETED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_DRUG_PLANE_BEEN_SHOT_DOWN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_PLAYER_FROM_FIRES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_TEXT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_SCALE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_JUSTIFY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_CENTRE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_WRAPX, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_CENTRE_SIZE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_BACKGROUND, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_BACKGROUND_COLOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_BACKGROUND_ONLY_TEXT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_PROPORTIONAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_FONT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_INDUSTRIAL_PASSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_COMMERCIAL_PASSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SUBURBAN_PASSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ROTATE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SLIDE_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_CHAR_ELEGANTLY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_STAY_IN_SAME_PLACE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_NASTY_GAME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_UNDRESS_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRESS_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CHASE_SCENE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_CHASE_SCENE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_EXPLOSION_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_EXPLOSION_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_START_DRUG_DROP_OFF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_DROP_OFF_PLANE_BEEN_SHOT_DOWN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FIND_DROP_OFF_PLANE_COORDINATES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_FLOATING_PACKAGE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLACE_OBJECT_RELATIVE_TO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_OBJECT_TARGETTABLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_ARMOUR_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_ARMOUR_TO_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_OPEN_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLOSE_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_CHAR_FROM_CAR_TO_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_VISIBILITY_OF_CLOSEST_OBJECT_OF_TYPE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_SPOTTED_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_HAIL_TAXI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_OBJECT_BEEN_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_START_KILL_FRENZY_HEADSHOT, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_MILITARY_CRANE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_PLAYER_INTO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_CHAR_INTO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_CAR_RADIO, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_AUDIO_STREAM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_2_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_3_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_4_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_5_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_WITH_6_NUMBERS_BIG, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_WAIT_STATE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAMERA_BEHIND_PLAYER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_MOTION_BLUR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_STRING_IN_STRING, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_RANDOM_CHAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_STEAL_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_2_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_2_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_3_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_3_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_4_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_4_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_SNIPER_BULLET_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_PLAYER_DETONATOR, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_COLL_OBJ_STEAL_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_VELOCITY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_ICECREAM_JINGLE_ON, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_STRING_IN_STRING_NOW, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_STRING_IN_STRING_SOON, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_5_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_5_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_6_REPEATED_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_6_PHONE_MESSAGES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_POINT_OBSCURED_BY_A_MISSION_ENTITY, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_ALL_MODELS_NOW, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_TO_OBJECT_VELOCITY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_SPRITE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_RECT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SPRITE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_TEXTURE_DICTIONARY, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_TEXTURE_DICTIONARY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_DYNAMIC, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_ANIM_SPEED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLAY_MISSION_PASSED_TUNE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FREEZE_ONSCREEN_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_CAR_SIREN, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_PED_ROADS_ON_ANGLED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_PED_ROADS_OFF_ANGLED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_ROADS_ON_ANGLED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_ROADS_OFF_ANGLED, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_WATERTIGHT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_MOVING_PARTICLE_EFFECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_CANT_BE_DRAGGED_OUT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_CAR_TO_FACE_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CRANE_LIFTING_CAR, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_SPHERE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_STATUS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_MALE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SCRIPT_NAME, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_GARAGE_TYPE_WITH_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FIND_DRUG_PLANE_COORDINATES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_INT_TO_DEBUG_FILE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_FLOAT_TO_DEBUG_FILE, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SAVE_NEWLINE_TO_DEBUG_FILE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POLICE_RADIO_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_STRONG, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_ROUTE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_RUBBISH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_PARTICLE_EFFECTS_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_STREAMING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_GARAGE_OPEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_GARAGE_CLOSED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_START_CATALINA_HELI, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CATALINA_HELI_TAKE_OFF, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_CATALINA_HELI, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CATALINA_HELI_BEEN_SHOT_DOWN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SWAP_NEAREST_BUILDING_MODEL, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_WORLD_PROCESSING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_ALL_PLAYER_WEAPONS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GRAB_CATALINA_HELI, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_AREA_OF_CARS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ROTATING_GARAGE_DOOR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPHERE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_SPHERE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CATALINA_HELI_FLY_AWAY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_EVERYONE_IGNORE_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CAR_CHAR_IS_IN_NO_SAVE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_STORE_CAR_PLAYER_IS_IN_NO_SAVE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PHONE_DISPLAYING_MESSAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_ONSCREEN_TIMER_WITH_STRING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_ONSCREEN_COUNTER_WITH_STRING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_RANDOM_CAR_FOR_CAR_PARK, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_COLLISION_IN_MEMORY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_WANTED_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAMERA_IN_FRONT_OF_PLAYER, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_VISIBLY_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DOES_OBJECT_EXIST, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SCENE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_STUCK_CAR_CHECK, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_STUCK_CAR_CHECK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_STUCK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_MISSION_AUDIO, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MISSION_AUDIO_LOADED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_PLAY_MISSION_AUDIO, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MISSION_AUDIO_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_IMPORT_GARAGE_SLOT_BEEN_FILLED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_THIS_PRINT, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_THIS_BIG_PRINT, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_MISSION_AUDIO_POSITION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ACTIVATE_SAVE_MENU, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_SAVE_GAME_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_NO_SPECIAL_CAMERA_FOR_THIS_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_PICKUP_OLD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLIP_FOR_PICKUP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SPRITE_BLIP_FOR_PICKUP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PED_DENSITY_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FORCE_RANDOM_PED_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_DRAW_BEFORE_FADE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_COLLECTABLE1S_COLLECTED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_EL_BURRO_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_SPRITES_DRAW_BEFORE_FADE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TEXT_RIGHT_JUSTIFY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_HELP, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_HELP, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_HUD_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FLASH_RADAR_BLIP, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_CONTROL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GENERATE_CARS_AROUND_CAMERA, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_SMALL_PRINTS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MILITARY_CRANE_COLLECTED_ALL_CARS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_UPSIDEDOWN_CAR_NOT_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CAN_PLAYER_START_MISSION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_PLAYER_SAFE_FOR_CUTSCENE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_USE_TEXT_COMMANDS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_THREAT_FOR_PED_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_THREAT_FOR_PED_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_COLOURS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ALL_CARS_CAN_BE_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_CAN_BE_DAMAGED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_PLAYER_UNSAFE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_BODY_CAST_HEALTH, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHARS_CHATTING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_PLAYER_SAFE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_STAYS_IN_CURRENT_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_STAYS_IN_CURRENT_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_4X4_ONE_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_4X4_TWO_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_4X4_THREE_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_4X4_MAYHEM_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_LIFE_SAVED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_CRIMINAL_CAUGHT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_AMBULANCE_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_FIRE_EXTINGUISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_TURN_PHONE_ON, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_LONGEST_DODO_FLIGHT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_DEFUSE_BOMB_TIME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TOTAL_NUMBER_OF_KILL_FRENZIES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_BLOW_UP_RC_BUGGY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_CAR_FROM_CHASE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_FRENCH_GAME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_GERMAN_GAME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_MISSION_AUDIO, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FADE_IN_AFTER_NEXT_ARREST, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FADE_IN_AFTER_NEXT_DEATH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_PED_MODEL_PREFERENCE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_USE_PEDNODE_SEEK, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_VEHICLE_WEAPONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GET_OUT_OF_JAIL_FREE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FREE_HEALTH_CARE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_DOOR_CLOSED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_AND_LAUNCH_MISSION, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_AND_LAUNCH_MISSION_INTERNAL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_DRAW_LAST, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_AMMO_IN_PLAYER_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_AMMO_IN_CHAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_KILL_FRENZY_PASSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_SAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_NEAR_CLIP, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_RADIO_CHANNEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_OVERRIDE_HOSPITAL_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_OVERRIDE_POLICE_STATION_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FORCE_RAIN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DOES_GARAGE_CONTAIN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_TRACTION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ARE_MEASUREMENTS_IN_METRES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CONVERT_METRES_TO_FEET, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_ROADS_BETWEEN_LEVELS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_PED_ROADS_BETWEEN_LEVELS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_AVOID_LEVEL_TRANSITIONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_AVOID_LEVEL_TRANSITIONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_THREAT_FOR_PED_TYPE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_AREA_OF_CHARS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TOTAL_NUMBER_OF_MISSIONS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CONVERT_METRES_TO_FEET_INT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_FASTEST_TIME, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_HIGHEST_SCORE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WARP_CHAR_INTO_CAR_AS_PASSENGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_PASSENGER_SEAT_FREE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHAR_IN_CAR_PASSENGER_SEAT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_IS_CHRIS_CRIMINAL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_CREDITS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_CREDITS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ARE_CREDITS_FINISHED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_SINGLE_PARTICLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_IGNORE_LEVEL_TRANSITIONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHASE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_BOAT_FOAM_ANIMATION, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_UPDATE_BOAT_FOAM_ANIMATION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_MUSIC_DOES_FADE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_INTRO_IS_PLAYING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_HOOKER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLAY_END_OF_GAME_TUNE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_STOP_END_OF_GAME_TUNE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CAR_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_SITTING_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_SITTING_IN_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_SCRIPT_FIRE_AUDIO, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ARE_ANY_CAR_CHEATS_ACTIVATED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_SUFFERS_CRITICAL_HITS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_LIFTING_A_PHONE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_SITTING_IN_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_SITTING_IN_ANY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_COLLISION_WITH_SCREEN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_SPLASH_SCREEN, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_IGNORE_LEVEL_TRANSITIONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_CRAIGS_CAR_A_BIT_STRONGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_JAMES_CAR_ON_PATH_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_END_OF_GAME_TUNE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ENABLE_PLAYER_CONTROL_CAMERA, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), +#if GTA_VERSION > GTA3_PS2_160 + REGISTER_COMMAND(COMMAND_SET_OBJECT_ROTATION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DEBUG_CAMERA_COORDINATES, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DEBUG_CAMERA_FRONT_VECTOR, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TARGETTING_ANY_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TARGETTING_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TARGETTING_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_TEXT_WITH_NUMBER, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_STRING, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_TEXT_WITH_2_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FAIL_CURRENT_MISSION, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_OBJECT_OF_TYPE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLACE_OBJECT_RELATIVE_TO_OBJECT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ALL_OCCUPANTS_OF_CAR_LEAVE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_INTERPOLATION_PARAMETERS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_TOWARDS_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_CAR_NODE_WITH_HEADING_AWAY_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DEBUG_CAMERA_POINT_AT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ATTACH_CHAR_TO_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DETACH_CHAR_FROM_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_CHANGE_LANE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CHAR_LAST_WEAPON_DAMAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CAR_LAST_WEAPON_DAMAGE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_COP_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_COP_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_FLEE_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DRIVER_OF_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_FOLLOWERS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GIVE_REMOTE_CONTROLLED_MODEL_TO_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CURRENT_PLAYER_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CURRENT_CHAR_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ANY_MEANS_OBJECT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_ON_FOOT_OBJECT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_CHAR_IN_CAR_OBJECT_3D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HANDBRAKE_TURN_LEFT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HANDBRAKE_TURN_RIGHT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_HANDBRAKE_STOP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_ON_ANY_BIKE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_SNIPER_BULLET_2D, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_SNIPER_BULLET_3D, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_SEATS_IN_MODEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_ON_ANY_BIKE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_LYING_DOWN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CAN_CHAR_SEE_DEAD_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ENTER_CAR_RANGE_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), +#if GTA_VERSION < GTA3_PC_11 + REGISTER_COMMAND(COMMAND_SET_THREAT_REACTION_RANGE_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), +#endif +#endif +}; +#undef REGISTER_COMMAND +#undef INPUT_ARGUMENTS +#undef OUTPUT_ARGUMENTS + +static_assert(ARRAY_SIZE(commands) == LAST_SCRIPT_COMMAND, "commands array not filled"); + +#if SCRIPT_LOG_FILE_LEVEL == 1 || SCRIPT_LOG_FILE_LEVEL == 2 +static FILE* dbg_log; +#endif + +static void PrintToLog(const char* format, ...) +{ + va_list va; + va_start(va, format); + char tmp[1024]; +#ifdef _WIN32 + vsprintf_s(tmp, 1024, format, va); +#else + vsprintf(tmp, format, va); +#endif + va_end(va); + +#if SCRIPT_LOG_FILE_LEVEL == 1 || SCRIPT_LOG_FILE_LEVEL == 2 + if (dbg_log) + fwrite(tmp, 1, strlen(tmp), dbg_log); +#endif +} + +int CRunningScript::CollectParameterForDebug(char* buf, bool& var) +{ + float tmp; + uint16 varIndex; + char tmpstr[24]; + var = false; + switch (CTheScripts::Read1ByteFromScript(&m_nIp)) + { + case ARGUMENT_INT32: + return CTheScripts::Read4BytesFromScript(&m_nIp); + case ARGUMENT_GLOBALVAR: + varIndex = CTheScripts::Read2BytesFromScript(&m_nIp); + script_assert(varIndex >= 8 && varIndex < CTheScripts::GetSizeOfVariableSpace()); + var = true; + sprintf(tmpstr, " $%d", varIndex / 4); + strcat(buf, tmpstr); + return *((int32*)&CTheScripts::ScriptSpace[varIndex]); + case ARGUMENT_LOCALVAR: + varIndex = CTheScripts::Read2BytesFromScript(&m_nIp); + script_assert(varIndex >= 0 && varIndex < ARRAY_SIZE(m_anLocalVariables)); + var = true; + sprintf(tmpstr, " %d@", varIndex); + strcat(buf, tmpstr); + return m_anLocalVariables[varIndex]; + case ARGUMENT_INT8: + return CTheScripts::Read1ByteFromScript(&m_nIp); + case ARGUMENT_INT16: + return CTheScripts::Read2BytesFromScript(&m_nIp); + case ARGUMENT_FLOAT: + tmp = CTheScripts::ReadFloatFromScript(&m_nIp); + return *(int32*)&tmp; + default: + PrintToLog("%s - script assertion failed in CollectParameterForDebug", buf); + script_assert(0); + break; + } + return 0; +} + +void CRunningScript::GetStoredParameterForDebug(char* buf) +{ + uint16 varIndex; + char tmpstr[24]; + switch (CTheScripts::Read1ByteFromScript(&m_nIp)) { + case ARGUMENT_GLOBALVAR: + varIndex = CTheScripts::Read2BytesFromScript(&m_nIp); + sprintf(tmpstr, " $%d", varIndex / 4); + strcat(buf, tmpstr); + break; + case ARGUMENT_LOCALVAR: + varIndex = CTheScripts::Read2BytesFromScript(&m_nIp); + sprintf(tmpstr, " %d@", varIndex); + strcat(buf, tmpstr); + break; + default: + PrintToLog("%s - script_assertion failed in GetStoredParameterForDebug", buf); + script_assert(0); + } +} + +void CTheScripts::LogAfterScriptInitializing() +{ +#if SCRIPT_LOG_FILE_LEVEL == 2 + CFileMgr::SetDirMyDocuments(); + if (dbg_log) + fclose(dbg_log); + dbg_log = fopen("SCRDBG.LOG", "w"); + static const char* init_msg = "Starting debug script log\n\n"; + PrintToLog(init_msg); + CFileMgr::SetDir(""); +#endif +} + +void CTheScripts::LogBeforeScriptProcessing() +{ + +#if SCRIPT_LOG_FILE_LEVEL == 1 + CFileMgr::SetDirMyDocuments(); + dbg_log = fopen("SCRDBG.LOG", "w"); + static const char* init_msg = "Starting debug script log\n\n"; + PrintToLog(init_msg); + CFileMgr::SetDir(""); +#endif + PrintToLog("------------------------\n"); + PrintToLog("CTheScripts::Process started, CTimer::GetTimeInMilliseconds == %u\n", CTimer::GetTimeInMilliseconds()); +} + +void CTheScripts::LogAfterScriptProcessing() +{ + PrintToLog("Script processing done, ScriptsUpdated: %d, CommandsExecuted: %d\n", ScriptsUpdated, CommandsExecuted); +#if SCRIPT_LOG_FILE_LEVEL == 1 + fclose(dbg_log); + dbg_log = nil; +#endif +} + +void CRunningScript::LogOnStartProcessing() +{ + PrintToLog("\n\nProcessing script %s (id %d)\n\n", m_abScriptName, this - CTheScripts::ScriptsArray); +} + +void CRunningScript::LogBeforeProcessingCommand(int32 command) +{ + storedIp = m_nIp; + if (command < ARRAY_SIZE(commands)) { + script_assert(commands[command].id == command); + m_nIp -= 2; + sprintf(commandInfo, m_nIp >= SIZE_MAIN_SCRIPT ? "M<%5d> " : "<%6d> ", m_nIp >= SIZE_MAIN_SCRIPT ? m_nIp - SIZE_MAIN_SCRIPT : m_nIp); + m_nIp += 2; + if (m_bNotFlag) + strcat(commandInfo, "NOT "); + if (commands[command].position == -1) + strcat(commandInfo, commands[command].name + sizeof("COMMAND_") - 1); + for (int i = 0; commands[command].input[i] != ARGTYPE_NONE; i++) { + char tmp[16]; + bool var = false; + int value; + switch (commands[command].input[i]) { + case ARGTYPE_INT: + case ARGTYPE_PED_HANDLE: + case ARGTYPE_VEHICLE_HANDLE: + case ARGTYPE_OBJECT_HANDLE: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, var ? " (%d)" : " %d", value); break; + case ARGTYPE_FLOAT: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, var ? " (%.3f)" : " %.3f", *(float*)&value); break; + case ARGTYPE_STRING: sprintf(tmp, " '%s'", (const char*)&CTheScripts::ScriptSpace[m_nIp]); m_nIp += KEY_LENGTH_IN_SCRIPT; break; + case ARGTYPE_LABEL: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, var ? " (%s(%d))" : " %s(%d)", value >= 0 ? "G" : "L", abs(value)); break; + case ARGTYPE_BOOL: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, var ? " (%s)" : " %s", value ? "TRUE" : "FALSE"); break; + case ARGTYPE_ANDOR: value = CollectParameterForDebug(commandInfo, var); sprintf(tmp, " %d %ss", (value + 1) % 10, value / 10 == 0 ? "AND" : "OR"); break; + default: script_assert(0); + } + strcat(commandInfo, tmp); + if (commands[command].position == i) + strcat(commandInfo, commands[command].name_override); + } + uint32 t = m_nIp; + m_nIp = storedIp; + storedIp = t; + } +} + +void CRunningScript::LogAfterProcessingCommand(int32 command) +{ + if (command < ARRAY_SIZE(commands)) { + if (commands[command].cond || commands[command].output[0] != ARGTYPE_NONE) { + strcat(commandInfo, " ->"); + if (commands[command].cond) + strcat(commandInfo, m_bCondResult ? " TRUE" : " FALSE"); + uint32 t = m_nIp; + m_nIp = storedIp; + storedIp = t; + for (int i = 0; commands[command].output[i] != ARGTYPE_NONE; i++) { + char tmp[16]; + switch (commands[command].output[i]) { + case ARGTYPE_INT: + case ARGTYPE_PED_HANDLE: + case ARGTYPE_VEHICLE_HANDLE: + case ARGTYPE_OBJECT_HANDLE: GetStoredParameterForDebug(commandInfo); sprintf(tmp, " (%d)", ScriptParams[i]); strcat(commandInfo, tmp); break; + case ARGTYPE_FLOAT: GetStoredParameterForDebug(commandInfo); sprintf(tmp, " (%8.3f)", *(float*)&ScriptParams[i]); strcat(commandInfo, tmp); break; + default: script_assert(0 && "Script only returns INTs and FLOATs"); + } + } + m_nIp = storedIp; + } + PrintToLog("%s\n", commandInfo); + if (m_bMissionFlag) { + for (int i = 0; commandInfo[i]; i++) { + if (commandInfo[i] == '_') + commandInfo[i] = ' '; + } + CDebug::DebugAddText(commandInfo); + } + } +} + +#endif + +void FlushLog() +{ +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#if SCRIPT_LOG_FILE_LEVEL == 1 || SCRIPT_LOG_FILE_LEVEL == 2 + if (dbg_log) + fflush(dbg_log); +#endif +#endif +} + + +#ifdef MISSION_SWITCHER +void +CTheScripts::SwitchToMission(int32 mission) +{ + for (CRunningScript* pScript = CTheScripts::pActiveScripts; pScript != nil; pScript = pScript->GetNext()) { + if (!pScript->m_bIsMissionScript || !pScript->m_bDeatharrestEnabled) { + continue; + } + while (pScript->m_nStackPointer > 0) + --pScript->m_nStackPointer; + + pScript->m_nIp = pScript->m_anStack[pScript->m_nStackPointer]; + *(int32*)&CTheScripts::ScriptSpace[CTheScripts::OnAMissionFlag] = 0; + pScript->m_nWakeTime = 0; + pScript->m_bDeatharrestExecuted = true; + + while (!pScript->ProcessOneCommand()); + + CMessages::ClearMessages(); + } + +#ifdef MISSION_REPLAY + missionRetryScriptIndex = mission; + if (missionRetryScriptIndex == 19) + CStats::LastMissionPassedName[0] = '\0'; +#endif + CTimer::Suspend(); + int offset = CTheScripts::MultiScriptArray[mission]; +#ifdef USE_DEBUG_SCRIPT_LOADER + int handle = OpenScript(); +#else + CFileMgr::ChangeDir("\\"); + int handle = CFileMgr::OpenFile("data\\main.scm", "rb"); +#endif + CFileMgr::Seek(handle, offset, 0); + CFileMgr::Read(handle, (const char*)&CTheScripts::ScriptSpace[SIZE_MAIN_SCRIPT], SIZE_MISSION_SCRIPT); + CFileMgr::CloseFile(handle); + CRunningScript* pMissionScript = CTheScripts::StartNewScript(SIZE_MAIN_SCRIPT); + CTimer::Resume(); + pMissionScript->m_bIsMissionScript = true; + pMissionScript->m_bMissionFlag = true; + CTheScripts::bAlreadyRunningAMissionScript = true; +} +#endif diff --git a/src/control/TrafficLights.cpp b/src/control/TrafficLights.cpp new file mode 100644 index 0000000..278366a --- /dev/null +++ b/src/control/TrafficLights.cpp @@ -0,0 +1,330 @@ +#include "common.h" + +#include "Camera.h" +#include "Clock.h" +#include "Coronas.h" +#include "General.h" +#include "PathFind.h" +#include "PointLights.h" +#include "Shadows.h" +#include "SpecialFX.h" +#include "Timecycle.h" +#include "Timer.h" +#include "TrafficLights.h" +#include "Vehicle.h" +#include "Weather.h" +#include "World.h" + +// TODO: figure out the meaning of this +enum { SOME_FLAG = 0x80 }; + +void +CTrafficLights::DisplayActualLight(CEntity *ent) +{ + if(ent->GetUp().z < 0.96f || ent->bRenderDamaged) + return; + + int phase; + if(FindTrafficLightType(ent) == 1) + phase = LightForCars1(); + else + phase = LightForCars2(); + + int i; + CBaseModelInfo *mi = CModelInfo::GetModelInfo(ent->GetModelIndex()); + float x = mi->Get2dEffect(0)->pos.x; + float yMin = mi->Get2dEffect(0)->pos.y; + float yMax = mi->Get2dEffect(0)->pos.y; + float zMin = mi->Get2dEffect(0)->pos.z; + float zMax = mi->Get2dEffect(0)->pos.z; + for(i = 1; i < 6; i++){ + assert(mi->Get2dEffect(i)); + yMin = Min(yMin, mi->Get2dEffect(i)->pos.y); + yMax = Max(yMax, mi->Get2dEffect(i)->pos.y); + zMin = Min(zMin, mi->Get2dEffect(i)->pos.z); + zMax = Max(zMax, mi->Get2dEffect(i)->pos.z); + } + + CVector pos1, pos2; + uint8 r, g; + int id; + switch(phase){ + case CAR_LIGHTS_GREEN: + r = 0; + g = 255; + pos1 = ent->GetMatrix() * CVector(x, yMax, zMin); + pos2 = ent->GetMatrix() * CVector(x, yMin, zMin); + id = 0; + break; + case CAR_LIGHTS_YELLOW: + r = 255; + g = 128; + pos1 = ent->GetMatrix() * CVector(x, yMax, (zMin+zMax)/2.0f); + pos2 = ent->GetMatrix() * CVector(x, yMin, (zMin+zMax)/2.0f); + id = 1; + break; + case CAR_LIGHTS_RED: + default: + r = 255; + g = 0; + pos1 = ent->GetMatrix() * CVector(x, yMax, zMax); + pos2 = ent->GetMatrix() * CVector(x, yMin, zMax); + id = 2; + break; + } + + if(CClock::GetHours() > 19 || CClock::GetHours() < 6 || CWeather::Foggyness > 0.05f) + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos1, CVector(0.0f, 0.0f, 0.0f), 8.0f, + r/255.0f, g/255.0f, 0/255.0f, CPointLights::FOG_NORMAL, true); + + CShadows::StoreStaticShadow((uintptr)ent, + SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos1, + 8.0f, 0.0f, 0.0f, -8.0f, 128, + r*CTimeCycle::GetLightOnGroundBrightness()/8.0f, + g*CTimeCycle::GetLightOnGroundBrightness()/8.0f, + 0*CTimeCycle::GetLightOnGroundBrightness()/8.0f, + 12.0f, 1.0f, 40.0f, false, 0.0f); + + if(DotProduct(TheCamera.GetForward(), ent->GetForward()) < 0.0f) + CCoronas::RegisterCorona((uintptr)ent + id, + r*CTimeCycle::GetSpriteBrightness()*0.7f, + g*CTimeCycle::GetSpriteBrightness()*0.7f, + 0*CTimeCycle::GetSpriteBrightness()*0.7f, + 255, + pos1, 1.75f*CTimeCycle::GetSpriteSize(), 50.0f, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::RegisterCorona((uintptr)ent + id + 3, + r*CTimeCycle::GetSpriteBrightness()*0.7f, + g*CTimeCycle::GetSpriteBrightness()*0.7f, + 0*CTimeCycle::GetSpriteBrightness()*0.7f, + 255, + pos2, 1.75f*CTimeCycle::GetSpriteSize(), 50.0f, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + + CBrightLights::RegisterOne(pos1, ent->GetUp(), ent->GetRight(), CVector(0.0f, 0.0f, 0.0f), id + BRIGHTLIGHT_TRAFFIC_GREEN); + CBrightLights::RegisterOne(pos2, ent->GetUp(), -ent->GetRight(), CVector(0.0f, 0.0f, 0.0f), id + BRIGHTLIGHT_TRAFFIC_GREEN); + + static const float top = -0.127f; + static const float bot = -0.539f; + static const float mid = bot + (top-bot)/3.0f; + static const float left = 1.256f; + static const float right = 0.706f; + phase = CTrafficLights::LightForPeds(); + if(phase == PED_LIGHTS_DONT_WALK){ + CVector p0(2.7f, right, top); + CVector p1(2.7f, left, top); + CVector p2(2.7f, right, mid); + CVector p3(2.7f, left, mid); + CShinyTexts::RegisterOne(ent->GetMatrix()*p0, ent->GetMatrix()*p1, ent->GetMatrix()*p2, ent->GetMatrix()*p3, + 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, + SHINYTEXT_WALK, 255, 0, 0, 60.0f); + }else if(phase == PED_LIGHTS_WALK || CTimer::GetTimeInMilliseconds() & 0x100){ + CVector p0(2.7f, right, mid); + CVector p1(2.7f, left, mid); + CVector p2(2.7f, right, bot); + CVector p3(2.7f, left, bot); + CShinyTexts::RegisterOne(ent->GetMatrix()*p0, ent->GetMatrix()*p1, ent->GetMatrix()*p2, ent->GetMatrix()*p3, + 1.0f, 0.5f, 0.0f, 0.5f, 1.0f, 1.0f, 0.0f, 1.0f, + SHINYTEXT_WALK, 255, 255, 255, 60.0f); + } +} + +void +CTrafficLights::ScanForLightsOnMap(void) +{ + int x, y; + int i, j, k, l; + CPtrNode *node; + + for(x = 0; x < NUMSECTORS_X; x++) + for(y = 0; y < NUMSECTORS_Y; y++){ + CPtrList &list = CWorld::GetSector(x, y)->m_lists[ENTITYLIST_DUMMIES]; + for(node = list.first; node; node = node->next){ + CEntity *light = (CEntity*)node->item; + if(light->GetModelIndex() != MI_TRAFFICLIGHTS) + continue; + + // Check cars + for(i = 0; i < ThePaths.m_numCarPathLinks; i++){ + CVector2D dist = ThePaths.m_carPathLinks[i].GetPosition() - light->GetPosition(); + float dotY = Abs(DotProduct2D(dist, light->GetForward())); // forward is direction of car light + float dotX = DotProduct2D(dist, light->GetRight()); // towards base of light + // it has to be on the correct side of the node and also not very far away + if(dotX < 0.0f && dotX > -15.0f && dotY < 3.0f){ + float dz = ThePaths.m_pathNodes[ThePaths.m_carPathLinks[i].pathNodeIndex].GetZ() - + light->GetPosition().z; + if(dz < 15.0f){ + ThePaths.m_carPathLinks[i].trafficLightType = FindTrafficLightType(light); + // Find two neighbour nodes of this one + int n1 = -1; + int n2 = -1; + for(j = 0; j < ThePaths.m_numPathNodes; j++) + for(l = 0; l < ThePaths.m_pathNodes[j].numLinks; l++) + if(ThePaths.m_carPathConnections[ThePaths.m_pathNodes[j].firstLink + l] == i){ + if(n1 == -1) + n1 = j; + else + n2 = j; + } + // What's going on here? + if(ThePaths.m_pathNodes[n1].numLinks <= ThePaths.m_pathNodes[n2].numLinks) + n1 = n2; + if(ThePaths.m_carPathLinks[i].pathNodeIndex != n1) + ThePaths.m_carPathLinks[i].trafficLightType |= SOME_FLAG; + } + } + } + + // Check peds + for(i = ThePaths.m_numCarPathNodes; i < ThePaths.m_numPathNodes; i++){ + float dist1, dist2; + dist1 = Abs(ThePaths.m_pathNodes[i].GetX() - light->GetPosition().x) + + Abs(ThePaths.m_pathNodes[i].GetY() - light->GetPosition().y); + if(dist1 < 50.0f){ + for(l = 0; l < ThePaths.m_pathNodes[i].numLinks; l++){ + j = ThePaths.m_pathNodes[i].firstLink + l; + if(ThePaths.ConnectionCrossesRoad(j)){ + k = ThePaths.ConnectedNode(j); + dist2 = Abs(ThePaths.m_pathNodes[k].GetX() - light->GetPosition().x) + + Abs(ThePaths.m_pathNodes[k].GetY() - light->GetPosition().y); + if(dist1 < 15.0f || dist2 < 15.0f) + ThePaths.ConnectionSetTrafficLight(j); + } + } + } + } + } + } +} + +bool +CTrafficLights::ShouldCarStopForLight(CVehicle *vehicle, bool alwaysStop) +{ + int node, type; + + node = vehicle->AutoPilot.m_nNextPathNodeInfo; + type = ThePaths.m_carPathLinks[node].trafficLightType; + if(type){ + if((type & SOME_FLAG || ThePaths.m_carPathLinks[node].pathNodeIndex == vehicle->AutoPilot.m_nNextRouteNode) && + (!(type & SOME_FLAG) || ThePaths.m_carPathLinks[node].pathNodeIndex != vehicle->AutoPilot.m_nNextRouteNode)) + if(alwaysStop || + (type&~SOME_FLAG) == 1 && LightForCars1() != CAR_LIGHTS_GREEN || + (type&~SOME_FLAG) == 2 && LightForCars2() != CAR_LIGHTS_GREEN){ + float dist = DotProduct2D(CVector2D(vehicle->GetPosition()) - ThePaths.m_carPathLinks[node].GetPosition(), + ThePaths.m_carPathLinks[node].GetDirection()); + if(vehicle->AutoPilot.m_nNextDirection == -1){ + if(dist > 0.0f && dist < 8.0f) + return true; + }else{ + if(dist < 0.0f && dist > -8.0f) + return true; + } + } + } + + node = vehicle->AutoPilot.m_nCurrentPathNodeInfo; + type = ThePaths.m_carPathLinks[node].trafficLightType; + if(type){ + if((type & SOME_FLAG || ThePaths.m_carPathLinks[node].pathNodeIndex == vehicle->AutoPilot.m_nCurrentRouteNode) && + (!(type & SOME_FLAG) || ThePaths.m_carPathLinks[node].pathNodeIndex != vehicle->AutoPilot.m_nCurrentRouteNode)) + if(alwaysStop || + (type&~SOME_FLAG) == 1 && LightForCars1() != CAR_LIGHTS_GREEN || + (type&~SOME_FLAG) == 2 && LightForCars2() != CAR_LIGHTS_GREEN){ + float dist = DotProduct2D(CVector2D(vehicle->GetPosition()) - ThePaths.m_carPathLinks[node].GetPosition(), + ThePaths.m_carPathLinks[node].GetDirection()); + if(vehicle->AutoPilot.m_nCurrentDirection == -1){ + if(dist > 0.0f && dist < 8.0f) + return true; + }else{ + if(dist < 0.0f && dist > -8.0f) + return true; + } + } + } + + if(vehicle->GetStatus() == STATUS_PHYSICS){ + node = vehicle->AutoPilot.m_nPreviousPathNodeInfo; + type = ThePaths.m_carPathLinks[node].trafficLightType; + if(type){ + if((type & SOME_FLAG || ThePaths.m_carPathLinks[node].pathNodeIndex == vehicle->AutoPilot.m_nPrevRouteNode) && + (!(type & SOME_FLAG) || ThePaths.m_carPathLinks[node].pathNodeIndex != vehicle->AutoPilot.m_nPrevRouteNode)) + if(alwaysStop || + (type&~SOME_FLAG) == 1 && LightForCars1() != CAR_LIGHTS_GREEN || + (type&~SOME_FLAG) == 2 && LightForCars2() != CAR_LIGHTS_GREEN){ + float dist = DotProduct2D(CVector2D(vehicle->GetPosition()) - ThePaths.m_carPathLinks[node].GetPosition(), + ThePaths.m_carPathLinks[node].GetDirection()); + if(vehicle->AutoPilot.m_nPreviousDirection == -1){ + if(dist > 0.0f && dist < 6.0f) + return true; + }else{ + if(dist < 0.0f && dist > -6.0f) + return true; + } + } + } + } + + return false; +} + +bool +CTrafficLights::ShouldCarStopForBridge(CVehicle *vehicle) +{ + return ThePaths.m_carPathLinks[vehicle->AutoPilot.m_nNextPathNodeInfo].bBridgeLights && + !ThePaths.m_carPathLinks[vehicle->AutoPilot.m_nCurrentPathNodeInfo].bBridgeLights; +} + +int +CTrafficLights::FindTrafficLightType(CEntity *light) +{ + float orientation = RADTODEG(CGeneral::GetATanOfXY(light->GetForward().x, light->GetForward().y)); + if((orientation > 60.0f && orientation < 60.0f + 90.0f) || + (orientation > 240.0f && orientation < 240.0f + 90.0f)) + return 1; + return 2; +} + +uint8 +CTrafficLights::LightForPeds(void) +{ + uint32 period = CTimer::GetTimeInMilliseconds() % 16384; + + if(period < 12000) + return PED_LIGHTS_DONT_WALK; + else if(period < 16384 - 1000) + return PED_LIGHTS_WALK; + else + return PED_LIGHTS_WALK_BLINK; +} + +uint8 +CTrafficLights::LightForCars1(void) +{ + uint32 period = CTimer::GetTimeInMilliseconds() % 16384; + + if(period < 5000) + return CAR_LIGHTS_GREEN; + else if(period < 5000 + 1000) + return CAR_LIGHTS_YELLOW; + else + return CAR_LIGHTS_RED; +} + +uint8 +CTrafficLights::LightForCars2(void) +{ + uint32 period = CTimer::GetTimeInMilliseconds() % 16384; + + if(period < 6000) + return CAR_LIGHTS_RED; + else if(period < 12000 - 1000) + return CAR_LIGHTS_GREEN; + else if(period < 12000) + return CAR_LIGHTS_YELLOW; + else + return CAR_LIGHTS_RED; +} diff --git a/src/control/TrafficLights.h b/src/control/TrafficLights.h new file mode 100644 index 0000000..f3df6cd --- /dev/null +++ b/src/control/TrafficLights.h @@ -0,0 +1,27 @@ +#pragma once + +class CEntity; +class CVehicle; + +enum { + PED_LIGHTS_WALK, + PED_LIGHTS_WALK_BLINK, + PED_LIGHTS_DONT_WALK, + + CAR_LIGHTS_GREEN = 0, + CAR_LIGHTS_YELLOW, + CAR_LIGHTS_RED +}; + +class CTrafficLights +{ +public: + static void DisplayActualLight(CEntity *ent); + static void ScanForLightsOnMap(void); + static int FindTrafficLightType(CEntity *light); + static uint8 LightForPeds(void); + static uint8 LightForCars1(void); + static uint8 LightForCars2(void); + static bool ShouldCarStopForLight(CVehicle*, bool); + static bool ShouldCarStopForBridge(CVehicle*); +}; diff --git a/src/core/Accident.cpp b/src/core/Accident.cpp new file mode 100644 index 0000000..c861132 --- /dev/null +++ b/src/core/Accident.cpp @@ -0,0 +1,128 @@ +#include "common.h" + +#include "Accident.h" + +#include "Ped.h" +#include "Pools.h" +#include "World.h" + +CAccidentManager gAccidentManager; + +CAccident* +CAccidentManager::GetNextFreeAccident() +{ + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim == nil) + return &m_aAccidents[i]; + } + + return nil; +} + +void +CAccidentManager::ReportAccident(CPed *ped) +{ + if (!ped->IsPlayer() && ped->CharCreatedBy != MISSION_CHAR && !ped->bRenderScorched && !ped->bBodyPartJustCameOff && ped->bAllowMedicsToReviveMe && !ped->bIsInWater) { + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim != nil && m_aAccidents[i].m_pVictim == ped) + return; + } + + if (ped->m_pCurrentPhysSurface == nil) { + CVector point = ped->GetPosition(); + point.z -= 2.0f; + + CColPoint colPoint; + CEntity *pEntity; + + if (!CWorld::ProcessVerticalLine(point, -100.0f, colPoint, pEntity, true, false, false, false, false, false, nil)) { + CAccident *accident = GetNextFreeAccident(); + if (accident != nil) { + accident->m_pVictim = ped; + ped->RegisterReference((CEntity**)&accident->m_pVictim); + accident->m_nMedicsPerformingCPR = 0; + accident->m_nMedicsAttending = 0; + ped->m_lastAccident = accident; + WorkToDoForMedics(); + } + } + } + } +} + +void +CAccidentManager::Update() +{ +#ifdef SQUEEZE_PERFORMANCE + // Handled after injury registered. + return; +#endif + int32 e; + if (CEventList::GetEvent(EVENT_INJURED_PED, &e)) { + CPed *ped = CPools::GetPed(gaEvent[e].entityRef); + if (ped) { + ReportAccident(ped); + CEventList::ClearEvent(e); + } + } +} + +CAccident* +CAccidentManager::FindNearestAccident(CVector vecPos, float *pDistance) +{ + for (int i = 0; i < MAX_MEDICS_TO_ATTEND_ACCIDENT; i++){ + int accidentId = -1; + float minDistance = 999999; + for (int j = 0; j < NUM_ACCIDENTS; j++){ + CPed* pVictim = m_aAccidents[j].m_pVictim; + if (!pVictim) + continue; + if (pVictim->CharCreatedBy == MISSION_CHAR) + continue; + if (pVictim->m_fHealth != 0.0f) + continue; + if (m_aAccidents[j].m_nMedicsPerformingCPR != i) + continue; + float distance = (pVictim->GetPosition() - vecPos).Magnitude2D(); + if (distance / 2 > pVictim->GetPosition().z - vecPos.z && distance < minDistance){ + minDistance = distance; + accidentId = j; + } + } + *pDistance = minDistance; + if (accidentId != -1) + return &m_aAccidents[accidentId]; + } + return nil; +} + +uint16 +CAccidentManager::CountActiveAccidents() +{ + uint16 accidents = 0; + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim) + accidents++; + } + return accidents; +} + +bool +CAccidentManager::WorkToDoForMedics() +{ + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim != nil && m_aAccidents[i].m_nMedicsAttending < MAX_MEDICS_TO_ATTEND_ACCIDENT) + return true; + } + return false; +} + +bool +CAccidentManager::UnattendedAccidents() +{ + for (int i = 0; i < NUM_ACCIDENTS; i++) { + if (m_aAccidents[i].m_pVictim != nil && m_aAccidents[i].m_nMedicsAttending == 0) + return true; + } + return false; +} diff --git a/src/core/Accident.h b/src/core/Accident.h new file mode 100644 index 0000000..568e114 --- /dev/null +++ b/src/core/Accident.h @@ -0,0 +1,31 @@ +#pragma once +#include "config.h" + +class CPed; + +class CAccident +{ +public: + CPed *m_pVictim; + uint32 m_nMedicsAttending; + uint32 m_nMedicsPerformingCPR; + CAccident() : m_pVictim(nil), m_nMedicsAttending(0), m_nMedicsPerformingCPR(0) {} +}; + +class CAccidentManager +{ + CAccident m_aAccidents[NUM_ACCIDENTS]; + enum { + MAX_MEDICS_TO_ATTEND_ACCIDENT = 2 + }; +public: + CAccident *GetNextFreeAccident(); + void ReportAccident(CPed *ped); + void Update(); + CAccident *FindNearestAccident(CVector vecPos, float *pDistance); + uint16 CountActiveAccidents(); + bool UnattendedAccidents(); + bool WorkToDoForMedics(); +}; + +extern CAccidentManager gAccidentManager; \ No newline at end of file diff --git a/src/core/AnimViewer.cpp b/src/core/AnimViewer.cpp new file mode 100644 index 0000000..946693a --- /dev/null +++ b/src/core/AnimViewer.cpp @@ -0,0 +1,422 @@ +#include "common.h" + +#include "Font.h" +#include "Pad.h" +#include "Text.h" +#include "main.h" +#include "Timer.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "Streaming.h" +#include "TxdStore.h" +#include "General.h" +#include "Camera.h" +#include "Vehicle.h" +#include "PlayerSkin.h" +#include "PlayerInfo.h" +#include "World.h" +#include "Renderer.h" +#include "AnimManager.h" +#include "AnimViewer.h" +#include "PlayerPed.h" +#include "Pools.h" +#include "References.h" +#include "PathFind.h" +#include "HandlingMgr.h" +#include "TempColModels.h" +#include "Particle.h" +#include "CdStream.h" +#include "Messages.h" +#include "CarCtrl.h" +#include "FileLoader.h" +#include "ModelIndices.h" +#include "Clock.h" +#include "Timecycle.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "Shadows.h" +#include "Radar.h" +#include "Hud.h" +#include "debugmenu.h" + +int CAnimViewer::animTxdSlot = 0; +CEntity *CAnimViewer::pTarget = nil; + +void +CAnimViewer::Render(void) { + if (pTarget) { +// pTarget->GetPosition() = CVector(0.0f, 0.0f, 0.0f); // Only on Mobile + if (pTarget) { +#ifdef FIX_BUGS +#ifdef PED_SKIN + if(pTarget->IsPed() && IsClumpSkinned(pTarget->GetClump())) + ((CPed*)pTarget)->UpdateRpHAnim(); +#endif +#endif + pTarget->Render(); + CRenderer::RenderOneNonRoad(pTarget); + } + } +} + +void +CAnimViewer::Initialise(void) { + // we need messages, messages needs hud, hud needs this + CHud::m_Wants_To_Draw_Hud = false; + + animTxdSlot = CTxdStore::AddTxdSlot("generic"); + CTxdStore::Create(animTxdSlot); + int hudSlot = CTxdStore::AddTxdSlot("hud"); + CTxdStore::LoadTxd(hudSlot, "MODELS/HUD.TXD"); + int particleSlot = CTxdStore::AddTxdSlot("particle"); + CTxdStore::LoadTxd(particleSlot, "MODELS/PARTICLE.TXD"); + CTxdStore::SetCurrentTxd(animTxdSlot); + CPools::Initialise(); + CReferences::Init(); + TheCamera.Init(); + TheCamera.SetRwCamera(Scene.camera); + TheCamera.Cams[TheCamera.ActiveCam].Distance = 5.0f; + + ThePaths.Init(); + ThePaths.AllocatePathFindInfoMem(4500); + CCollision::Init(); + CWorld::Initialise(); + mod_HandlingManager.Initialise(); + CTempColModels::Initialise(); + CAnimManager::Initialise(); + CModelInfo::Initialise(); + CParticle::Initialise(); + CCarCtrl::Init(); + CPedStats::Initialise(); + CMessages::Init(); + CdStreamAddImage("MODELS\\GTA3.IMG"); + CFileLoader::LoadLevel("DATA\\ANIMVIEWER.DAT"); + CStreaming::Init(); + CStreaming::LoadInitialPeds(); + CStreaming::RequestSpecialModel(MI_PLAYER, "player", STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + CRenderer::Init(); + CRadar::Initialise(); + CRadar::LoadTextures(); + CVehicleModelInfo::LoadVehicleColours(); +#ifdef FIX_BUGS + CVehicleModelInfo::LoadEnvironmentMaps(); +#endif + CAnimManager::LoadAnimFiles(); + CWorld::PlayerInFocus = 0; + CWeapon::InitialiseWeapons(); + CShadows::Init(); + CPed::Initialise(); + CTimer::Initialise(); + CClock::Initialise(60000); + CTimeCycle::Initialise(); + CCarCtrl::Init(); + CPlayerPed *player = new CPlayerPed(); + player->SetPosition(0.0f, 0.0f, 0.0f); // This is 1000.f for all axes on Xbox, but 0.f on mobile? + CWorld::Players[0].m_pPed = player; + CDraw::SetFOV(120.0f); + CDraw::ms_fLODDistance = 500.0f; + + int fd = CFileMgr::OpenFile("DATA\\SPECIAL.TXT", "r"); + char animGroup[32], modelName[32]; + if (fd) { + for (int lineId = 0; lineId < NUM_OF_SPECIAL_CHARS; lineId++) { + if (!CFileMgr::ReadLine(fd, gString, 255)) + break; + + sscanf(gString, "%s %s", modelName, animGroup); + int groupId; + for (groupId = 0; groupId < NUM_ANIM_ASSOC_GROUPS; groupId++) { + if (!strcmp(animGroup, CAnimManager::GetAnimGroupName((AssocGroupId)groupId))) + break; + } + + if (groupId != NUM_ANIM_ASSOC_GROUPS) + ((CPedModelInfo*)CModelInfo::GetModelInfo(MI_SPECIAL01 + lineId))->m_animGroup = groupId; + + CStreaming::RequestSpecialChar(lineId, modelName, STREAMFLAGS_DONT_REMOVE); + } + CFileMgr::CloseFile(fd); + } else { + // From xbox + CStreaming::RequestSpecialChar(0, "luigi", STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestSpecialChar(1, "joey", STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestSpecialChar(2, "tony", STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestSpecialChar(3, "curly", STREAMFLAGS_DONT_REMOVE); + } +} + +int +LastPedModelId(int modelId) +{ + CBaseModelInfo *model; + for(;;){ + assert(modelId < MODELINFOSIZE); + model = CModelInfo::GetModelInfo(modelId); + if (model && model->GetModelType() == MITYPE_PED) + break; + modelId--; + } + return modelId; +} + +int +FirstCarModelId(int modelId) +{ + CBaseModelInfo *model; + for(;;){ + assert(modelId < MODELINFOSIZE); + model = CModelInfo::GetModelInfo(modelId); + if (model && model->GetModelType() == MITYPE_VEHICLE) + break; + modelId++; + } + return modelId; +} + + +int +NextModelId(int modelId, int wantedChange) +{ + // Max. 2 trials wasn't here, it's me that added it. + + int tryCount = 2; + int ogModelId = modelId; + + while(tryCount != 0) { + modelId += wantedChange; + if (modelId < 0 || modelId >= MODELINFOSIZE) { + tryCount--; + wantedChange = -wantedChange; + } else if (modelId != 5 && modelId != 6 && modelId != 405) { + CBaseModelInfo *model = CModelInfo::GetModelInfo(modelId); + if (model) + { + //int type = model->m_type; + return modelId; + } + } + } + return ogModelId; +} + +void +PlayAnimation(RpClump *clump, AssocGroupId animGroup, AnimationId anim) +{ + CAnimBlendAssociation *currentAssoc = RpAnimBlendClumpGetAssociation(clump, anim); + + if (currentAssoc && currentAssoc->IsPartial()) + delete currentAssoc; + + RpAnimBlendClumpSetBlendDeltas(clump, ASSOC_PARTIAL, -8.0f); + + CAnimBlendAssociation *animAssoc = CAnimManager::BlendAnimation(clump, animGroup, anim, 8.0f); + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->SetCurrentTime(0.0f); + animAssoc->SetRun(); +} + +void +CAnimViewer::Update(void) +{ + static int modelId = 0; + static int animId = 0; + static bool reloadIFP = false; + + AssocGroupId animGroup = ASSOCGRP_STD; + int nextModelId = modelId; + CBaseModelInfo *modelInfo = CModelInfo::GetModelInfo(modelId); + + if (modelInfo->GetModelType() == MITYPE_PED) { + int animGroup = ((CPedModelInfo*)modelInfo)->m_animGroup; + + if (animId > ANIM_STD_IDLE) + animGroup = ASSOCGRP_STD; + + if (reloadIFP) { + if (pTarget) { + CWorld::Remove(pTarget); + if (pTarget) + delete pTarget; + } + pTarget = nil; + + // These calls were inside of LoadIFP function. + CAnimManager::Shutdown(); + CAnimManager::Initialise(); + CAnimManager::LoadAnimFiles(); + + reloadIFP = false; + } + } else { + animGroup = ASSOCGRP_STD; + } + CPad::UpdatePads(); + CPad* pad = CPad::GetPad(0); +#ifdef DEBUGMENU + DebugMenuProcess(); +#endif + + CStreaming::UpdateForAnimViewer(); + CStreaming::RequestModel(modelId, 0); + if (CStreaming::HasModelLoaded(modelId)) { + + if (!pTarget) { + + if (modelInfo->GetModelType() == MITYPE_VEHICLE) { + + CVehicleModelInfo* veh = (CVehicleModelInfo*)modelInfo; + if (veh->m_vehicleType == VEHICLE_TYPE_CAR) { + pTarget = new CAutomobile(modelId, RANDOM_VEHICLE); + } else if (veh->m_vehicleType == VEHICLE_TYPE_BOAT) { + pTarget = new CBoat(modelId, RANDOM_VEHICLE); + } else { + pTarget = new CObject(modelId, true); + if (!modelInfo->GetColModel()) { + modelInfo->SetColModel(&CTempColModels::ms_colModelWheel1); + } + } + pTarget->SetStatus(STATUS_ABANDONED); + } else if (modelInfo->GetModelType() == MITYPE_PED) { + pTarget = new CPed(PEDTYPE_CIVMALE); + pTarget->SetModelIndex(modelId); + } else { + pTarget = new CObject(modelId, true); + if (!modelInfo->GetColModel()) + { + modelInfo->SetColModel(&CTempColModels::ms_colModelWheel1); + } + pTarget->SetStatus(STATUS_ABANDONED); + } + pTarget->SetPosition(0.0f, 0.0f, 0.0f); + CWorld::Add(pTarget); + TheCamera.TakeControl(pTarget, CCam::MODE_MODELVIEW, JUMP_CUT, CAMCONTROL_SCRIPT); + } + if (pTarget->IsVehicle() || pTarget->IsPed() || pTarget->IsObject()) { + ((CPhysical*)pTarget)->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } +#ifdef FIX_BUGS + // so we don't end up in the water + pTarget->GetMatrix().GetPosition().z = 10.0f; +#else + pTarget->GetMatrix().GetPosition().z = 0.0f; + +#endif + + if (modelInfo->GetModelType() == MITYPE_PED) { + ((CPed*)pTarget)->bKindaStayInSamePlace = true; + + // Triangle in mobile + if (pad->GetSquareJustDown()) { + reloadIFP = true; + AsciiToUnicode("IFP reloaded", gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetCrossJustDown()) { + PlayAnimation(pTarget->GetClump(), animGroup, (AnimationId)animId); + AsciiToUnicode("Animation restarted", gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetCircleJustDown()) { + PlayAnimation(pTarget->GetClump(), animGroup, ANIM_STD_IDLE); + AsciiToUnicode("Idle animation playing", gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetDPadUpJustDown()) { + animId--; + if (animId < 0) { + animId = ANIM_STD_NUM - 1; + } + PlayAnimation(pTarget->GetClump(), animGroup, (AnimationId)animId); + + sprintf(gString, "Current anim: %d", animId); + AsciiToUnicode(gString, gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetDPadDownJustDown()) { + animId = (animId == (ANIM_STD_NUM - 1) ? 0 : animId + 1); + PlayAnimation(pTarget->GetClump(), animGroup, (AnimationId)animId); + + sprintf(gString, "Current anim: %d", animId); + AsciiToUnicode(gString, gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetStartJustDown()) { + + } else if (pad->GetLeftShoulder1JustDown()) { + nextModelId = FirstCarModelId(modelId); + AsciiToUnicode("Switched to vehicles", gUString); + CMessages::AddMessage(gUString, 1000, 0); + // Originally it was GetPad(1)->LeftShoulder2 + } else if (pad->NewState.Triangle) { +#ifdef PED_SKIN + if(IsClumpSkinned(pTarget->GetClump())) + ((CPedModelInfo *)CModelInfo::GetModelInfo(pTarget->GetModelIndex()))->AnimatePedColModelSkinned(pTarget->GetClump()); + else +#endif + CPedModelInfo::AnimatePedColModel(((CPedModelInfo *)CModelInfo::GetModelInfo(pTarget->GetModelIndex()))->GetHitColModel(), + RpClumpGetFrame(pTarget->GetClump())); + AsciiToUnicode("Ped Col model will be animated as long as you hold the button", gUString); + CMessages::AddMessage(gUString, 100, 0); + } + } else if (modelInfo->GetModelType() == MITYPE_VEHICLE) { + + if (pad->GetLeftShoulder1JustDown()) { + nextModelId = LastPedModelId(modelId); + AsciiToUnicode("Switched to peds", gUString); + CMessages::AddMessage(gUString, 1000, 0); + // Start in mobile + } else if (pad->GetSquareJustDown()) { + CVehicleModelInfo::LoadVehicleColours(); + AsciiToUnicode("Carcols.dat reloaded", gUString); + CMessages::AddMessage(gUString, 1000, 0); + } + } + } + + if (pad->GetDPadLeftJustDown()) { + nextModelId = NextModelId(modelId, -1); + + sprintf(gString, "Current model ID: %d", nextModelId); + AsciiToUnicode(gString, gUString); + CMessages::AddMessage(gUString, 1000, 0); + + } else if (pad->GetDPadRightJustDown()) { + nextModelId = NextModelId(modelId, 1); + + sprintf(gString, "Current model ID: %d", nextModelId); + AsciiToUnicode(gString, gUString); + CMessages::AddMessage(gUString, 1000, 0); + } + // There were extra codes here to let us change model id by 50, but xbox CPad struct is different, so I couldn't port. + + if (nextModelId != modelId) { + modelId = nextModelId; + if (pTarget) { + CWorld::Remove(pTarget); + if (pTarget) + delete pTarget; + } + pTarget = nil; + return; + } + + CTimeCycle::Update(); + CWorld::Process(); + if (pTarget) + TheCamera.Process(); +} + +void +CAnimViewer::Shutdown(void) +{ + if (CWorld::Players[0].m_pPed) + delete CWorld::Players[0].m_pPed; + + CWorld::ShutDown(); + CModelInfo::ShutDown(); + CAnimManager::Shutdown(); + CTimer::Shutdown(); + CStreaming::Shutdown(); + CTxdStore::RemoveTxdSlot(animTxdSlot); +} diff --git a/src/core/AnimViewer.h b/src/core/AnimViewer.h new file mode 100644 index 0000000..13dbb8f --- /dev/null +++ b/src/core/AnimViewer.h @@ -0,0 +1,12 @@ +#pragma once + +class CAnimViewer { +public: + static int animTxdSlot; + static CEntity *pTarget; + + static void Initialise(); + static void Render(); + static void Shutdown(); + static void Update(); +}; \ No newline at end of file diff --git a/src/core/Cam.cpp b/src/core/Cam.cpp new file mode 100644 index 0000000..d4be858 --- /dev/null +++ b/src/core/Cam.cpp @@ -0,0 +1,5410 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "World.h" +#include "Vehicle.h" +#include "Automobile.h" +#include "Boat.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "CopPed.h" +#include "RpAnimBlend.h" +#include "ControllerConfig.h" +#include "Pad.h" +#include "Frontend.h" +#include "General.h" +#include "Renderer.h" +#include "Shadows.h" +#include "Hud.h" +#include "ZoneCull.h" +#include "SurfaceTable.h" +#include "WaterLevel.h" +#include "MBlur.h" +#include "SceneEdit.h" +#include "Debug.h" +#include "Camera.h" +#include "DMAudio.h" + +bool PrintDebugCode = false; +int16 DebugCamMode; + +#ifdef FREE_CAM +bool CCamera::bFreeCam = false; +int nPreviousMode = -1; +#endif + +void +CCam::Init(void) +{ + Mode = MODE_FOLLOWPED; + Front = CVector(0.0f, 0.0f, -1.0f); + Up = CVector(0.0f, 0.0f, 1.0f); + Rotating = false; + m_iDoCollisionChecksOnFrameNum = 1; + m_iDoCollisionCheckEveryNumOfFrames = 9; + m_iFrameNumWereAt = 0; + m_bCollisionChecksOn = false; + m_fRealGroundDist = 0.0f; + BetaSpeed = 0.0f; + AlphaSpeed = 0.0f; + DistanceSpeed = 0.0f; + f_max_role_angle = DEGTORAD(5.0f); + Distance = 30.0f; + DistanceSpeed = 0.0f; + m_pLastCarEntered = nil; + m_pLastPedLookedAt = nil; + ResetStatics = true; + Beta = 0.0f; + m_bFixingBeta = false; + CA_MIN_DISTANCE = 0.0f; + CA_MAX_DISTANCE = 0.0f; + LookingBehind = false; + LookingLeft = false; + LookingRight = false; + m_fPlayerInFrontSyphonAngleOffSet = DEGTORAD(20.0f); + m_fSyphonModeTargetZOffSet = 0.5f; + m_fRadiusForDead = 1.5f; + DirectionWasLooking = LOOKING_FORWARD; + LookBehindCamWasInFront = false; + f_Roll = 0.0f; + f_rollSpeed = 0.0f; + m_fCloseInPedHeightOffset = 0.0f; + m_fCloseInPedHeightOffsetSpeed = 0.0f; + m_fCloseInCarHeightOffset = 0.0f; + m_fCloseInCarHeightOffsetSpeed = 0.0f; + m_fPedBetweenCameraHeightOffset = 0.0f; + m_fTargetBeta = 0.0f; + m_fBufferedTargetBeta = 0.0f; + m_fBufferedTargetOrientation = 0.0f; + m_fBufferedTargetOrientationSpeed = 0.0f; + m_fDimensionOfHighestNearCar = 0.0f; + m_fRoadOffSet = 0.0f; +} + +void +CCam::Process(void) +{ + CVector CameraTarget; + float TargetSpeedVar = 0.0f; + float TargetOrientation = 0.0f; + + if(CamTargetEntity == nil) + CamTargetEntity = TheCamera.pTargetEntity; + + m_iFrameNumWereAt++; + if(m_iFrameNumWereAt > m_iDoCollisionCheckEveryNumOfFrames) + m_iFrameNumWereAt = 1; + m_bCollisionChecksOn = m_iFrameNumWereAt == m_iDoCollisionChecksOnFrameNum; + + if(m_bCamLookingAtVector){ + CameraTarget = m_cvecCamFixedModeVector; + }else if(CamTargetEntity->IsVehicle()){ + CameraTarget = CamTargetEntity->GetPosition(); + + if(CamTargetEntity->GetForward().x == 0.0f && CamTargetEntity->GetForward().y == 0.0f) + TargetOrientation = 0.0f; + else + TargetOrientation = CGeneral::GetATanOfXY(CamTargetEntity->GetForward().x, CamTargetEntity->GetForward().y); + + CVector Fwd(0.0f, 0.0f, 0.0f); + Fwd.x = CamTargetEntity->GetForward().x; + Fwd.y = CamTargetEntity->GetForward().y; + Fwd.Normalise(); + float FwdLength = Fwd.Magnitude2D(); + if(FwdLength != 0.0f){ + Fwd.x /= FwdLength; + Fwd.y /= FwdLength; + } + + float FwdSpeedX = ((CVehicle*)CamTargetEntity)->GetMoveSpeed().x * Fwd.x; + float FwdSpeedY = ((CVehicle*)CamTargetEntity)->GetMoveSpeed().y * Fwd.y; + if(FwdSpeedX + FwdSpeedY > 0.0f) + TargetSpeedVar = Min(Sqrt(SQR(FwdSpeedX) + SQR(FwdSpeedY))/0.9f, 1.0f); + else + TargetSpeedVar = -Min(Sqrt(SQR(FwdSpeedX) + SQR(FwdSpeedY))/1.8f, 0.5f); + SpeedVar = 0.895f*SpeedVar + 0.105*TargetSpeedVar; + }else{ + CameraTarget = CamTargetEntity->GetPosition(); + + if(CamTargetEntity->GetForward().x == 0.0f && CamTargetEntity->GetForward().y == 0.0f) + TargetOrientation = 0.0f; + else + TargetOrientation = CGeneral::GetATanOfXY(CamTargetEntity->GetForward().x, CamTargetEntity->GetForward().y); + TargetSpeedVar = 0.0f; + SpeedVar = 0.0f; + } + + switch(Mode){ + case MODE_TOPDOWN: + case MODE_GTACLASSIC: + Process_TopDown(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_BEHINDCAR: + Process_BehindCar(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FOLLOWPED: +#ifdef PC_PLAYER_CONTROLS + if(CCamera::m_bUseMouse3rdPerson) + Process_FollowPedWithMouse(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + else +#endif +#ifdef FREE_CAM + if(CCamera::bFreeCam) + Process_FollowPed_Rotation(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + else +#endif + Process_FollowPed(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; +// case MODE_AIMING: + case MODE_DEBUG: + Process_Debug(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SNIPER: + Process_Sniper(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_ROCKETLAUNCHER: + Process_Rocket(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_MODELVIEW: + Process_ModelView(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_BILL: + Process_Bill(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SYPHON: + Process_Syphon(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CIRCLE: + Process_Circle(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; +// case MODE_CHEESYZOOM: + case MODE_WHEELCAM: + Process_WheelCam(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FIXED: + Process_Fixed(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_1STPERSON: + Process_1stPerson(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FLYBY: + Process_FlyBy(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CAM_ON_A_STRING: +#ifdef FREE_CAM + if(CCamera::bFreeCam && !CVehicle::bCheat5) + Process_FollowCar_SA(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + else +#endif + Process_Cam_On_A_String(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_REACTION: + Process_ReactionCam(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FOLLOW_PED_WITH_BIND: + Process_FollowPed_WithBinding(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CHRIS: + Process_Chris_With_Binding_PlusRotation(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_BEHINDBOAT: +#ifdef FREE_CAM + if (CCamera::bFreeCam) + Process_FollowCar_SA(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + else +#endif + Process_BehindBoat(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_PLAYER_FALLEN_WATER: + Process_Player_Fallen_Water(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CAM_ON_TRAIN_ROOF: + Process_Cam_On_Train_Roof(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_CAM_RUNNING_SIDE_TRAIN: + Process_Cam_Running_Side_Train(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_BLOOD_ON_THE_TRACKS: + Process_Blood_On_The_Tracks(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_IM_THE_PASSENGER_WOOWOO: + Process_Im_The_Passenger_Woo_Woo(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SYPHON_CRIM_IN_FRONT: + Process_Syphon_Crim_In_Front(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_PED_DEAD_BABY: + ProcessPedsDeadBaby(); + break; +// case MODE_PILLOWS_PAPS: +// case MODE_LOOK_AT_CARS: + case MODE_ARRESTCAM_ONE: + ProcessArrestCamOne(); + break; + case MODE_ARRESTCAM_TWO: + ProcessArrestCamTwo(); + break; + case MODE_M16_1STPERSON: + case MODE_HELICANNON_1STPERSON: // miami + Process_M16_1stPerson(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SPECIAL_FIXED_FOR_SYPHON: + Process_SpecialFixedForSyphon(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_FIGHT_CAM: + Process_Fight_Cam(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_TOP_DOWN_PED: + Process_TopDownPed(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; + case MODE_SNIPER_RUNABOUT: + case MODE_ROCKETLAUNCHER_RUNABOUT: + case MODE_1STPERSON_RUNABOUT: + case MODE_M16_1STPERSON_RUNABOUT: + case MODE_FIGHT_CAM_RUNABOUT: + Process_1rstPersonPedOnPC(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; +#ifdef GTA_SCENE_EDIT + case MODE_EDITOR: + Process_Editor(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); + break; +#endif + default: + Source = CVector(0.0f, 0.0f, 0.0f); + Front = CVector(0.0f, 1.0f, 0.0f); + Up = CVector(0.0f, 0.0f, 1.0f); + } + +#ifdef FREE_CAM + nPreviousMode = Mode; +#endif + CVector TargetToCam = Source - m_cvecTargetCoorsForFudgeInter; + float DistOnGround = TargetToCam.Magnitude2D(); + m_fTrueBeta = CGeneral::GetATanOfXY(TargetToCam.x, TargetToCam.y); + m_fTrueAlpha = CGeneral::GetATanOfXY(DistOnGround, TargetToCam.z); + if(TheCamera.m_uiTransitionState == 0) + KeepTrackOfTheSpeed(Source, m_cvecTargetCoorsForFudgeInter, Up, m_fTrueAlpha, m_fTrueBeta, FOV); + + // Look Behind, Left, Right + LookingBehind = false; + LookingLeft = false; + LookingRight = false; + SourceBeforeLookBehind = Source; + if(&TheCamera.Cams[TheCamera.ActiveCam] == this){ + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_1STPERSON || Mode == MODE_BEHINDBOAT) && + CamTargetEntity->IsVehicle()){ + if(CPad::GetPad(0)->GetLookBehindForCar()){ + LookBehind(); + if(DirectionWasLooking != LOOKING_BEHIND) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_BEHIND; + }else if(CPad::GetPad(0)->GetLookLeft()){ + LookLeft(); + if(DirectionWasLooking != LOOKING_LEFT) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_LEFT; + }else if(CPad::GetPad(0)->GetLookRight()){ + LookRight(); + if(DirectionWasLooking != LOOKING_RIGHT) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_RIGHT; + }else{ + if(DirectionWasLooking != LOOKING_FORWARD) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_FORWARD; + } + } + if(Mode == MODE_FOLLOWPED && CamTargetEntity->IsPed()){ + if(CPad::GetPad(0)->GetLookBehindForPed()){ + LookBehind(); + if(DirectionWasLooking != LOOKING_BEHIND) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_BEHIND; + }else + DirectionWasLooking = LOOKING_FORWARD; + } + } + + if(Mode == MODE_SNIPER || Mode == MODE_ROCKETLAUNCHER || Mode == MODE_M16_1STPERSON || + Mode == MODE_1STPERSON || Mode == MODE_HELICANNON_1STPERSON || GetWeaponFirstPersonOn()) + ClipIfPedInFrontOfPlayer(); +} + +// MaxSpeed is a limit of how fast the value is allowed to change. 1.0 = to Target in up to 1ms +// Acceleration is how fast the speed will change to MaxSpeed. 1.0 = to MaxSpeed in 1ms +void +WellBufferMe(float Target, float *CurrentValue, float *CurrentSpeed, float MaxSpeed, float Acceleration, bool IsAngle) +{ + float Delta = Target - *CurrentValue; + + if(IsAngle){ + while(Delta >= PI) Delta -= 2*PI; + while(Delta < -PI) Delta += 2*PI; + } + + float TargetSpeed = Delta * MaxSpeed; + // Add or subtract absolute depending on sign, genius! +// if(TargetSpeed - *CurrentSpeed > 0.0f) +// *CurrentSpeed += Acceleration * Abs(TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep(); +// else +// *CurrentSpeed -= Acceleration * Abs(TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep(); + // this is simpler: + *CurrentSpeed += Acceleration * (TargetSpeed - *CurrentSpeed) * CTimer::GetTimeStep(); + + // Clamp speed if we overshot + if(TargetSpeed < 0.0f && *CurrentSpeed < TargetSpeed) + *CurrentSpeed = TargetSpeed; + else if(TargetSpeed > 0.0f && *CurrentSpeed > TargetSpeed) + *CurrentSpeed = TargetSpeed; + + *CurrentValue += *CurrentSpeed * Min(10.0f, CTimer::GetTimeStep()); +} + +void +MakeAngleLessThan180(float &Angle) +{ + while(Angle >= PI) Angle -= 2*PI; + while(Angle < -PI) Angle += 2*PI; +} + +void +CCam::ProcessSpecialHeightRoutines(void) +{ + int i = 0; + bool StandingOnBoat = false; + static bool PreviouslyFailedRoadHeightCheck = false; + CVector CamToTarget, CamToPed; + float DistOnGround, BetaAngle; + CPed *Player; + int ClosestPed = 0; + bool FoundPed = false; + float ClosestPedDist, PedZDist; + CColPoint colPoint; + + CamToTarget = TheCamera.pTargetEntity->GetPosition() - TheCamera.GetGameCamPosition(); + DistOnGround = CamToTarget.Magnitude2D(); + BetaAngle = CGeneral::GetATanOfXY(CamToTarget.x, CamToTarget.y); + m_bTheHeightFixerVehicleIsATrain = false; + ClosestPedDist = 0.0f; + // CGeneral::GetATanOfXY(TheCamera.GetForward().x, TheCamera.GetForward().y); + Player = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + + if(DistOnGround > 10.0f) + DistOnGround = 10.0f; + + if(CamTargetEntity && CamTargetEntity->IsPed()){ + if(FindPlayerPed()->m_pCurSurface && FindPlayerPed()->m_pCurSurface->IsVehicle() && + ((CVehicle*)FindPlayerPed()->m_pCurSurface)->IsBoat()) + StandingOnBoat = true; + + // Move up the camera if there is a ped close to it + if(Mode == MODE_FOLLOWPED || Mode == MODE_FIGHT_CAM){ + // Find ped closest to camera + while(i < Player->m_numNearPeds){ + if(Player->m_nearPeds[i] && Player->m_nearPeds[i]->GetPedState() != PED_DEAD){ + CamToPed = Player->m_nearPeds[i]->GetPosition() - TheCamera.GetGameCamPosition(); + if(FoundPed){ + if(CamToPed.Magnitude2D() < ClosestPedDist){ + ClosestPed = i; + ClosestPedDist = CamToPed.Magnitude2D(); + } + }else{ + FoundPed = true; + ClosestPed = i; + ClosestPedDist = CamToPed.Magnitude2D(); + } + } + i++; + } + + if(FoundPed){ + float Offset = 0.0f; + CPed *Ped = Player->m_nearPeds[ClosestPed]; + CamToPed = Ped->GetPosition() - TheCamera.GetGameCamPosition(); + PedZDist = 0.0f; + float dist = CamToPed.Magnitude2D(); // should be same as ClosestPedDist + if(dist < 2.1f){ + // Ped is close to camera, move up + + // Z Distance between player and close ped + PedZDist = 0.0f; + if(Ped->bIsStanding) + PedZDist = Ped->GetPosition().z - Player->GetPosition().z; + // Ignore if too distant + if(PedZDist > 1.2f || PedZDist < -1.2f) + PedZDist = 0.0f; + + float DistScale = (2.1f - dist)/2.1f; + if(Mode == MODE_FOLLOWPED){ + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1) + Offset = 0.45f*DistScale + PedZDist; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_2) + Offset = 0.35f*DistScale + PedZDist; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_3) + Offset = 0.25f*DistScale + PedZDist; + if(Abs(CGeneral::GetRadianAngleBetweenPoints(CamToPed.x, CamToPed.y, CamToTarget.x, CamToTarget.y)) > HALFPI) + Offset += 0.3f; + m_fPedBetweenCameraHeightOffset = Offset + 1.3f; + PedZDist = 0.0f; + }else if(Mode == MODE_FIGHT_CAM) + m_fPedBetweenCameraHeightOffset = PedZDist + 1.3f + 0.5f; + }else + m_fPedBetweenCameraHeightOffset = 0.0f; + }else{ + PedZDist = 0.0f; + m_fPedBetweenCameraHeightOffset = 0.0f; + } + }else + PedZDist = 0.0f; + + + // Move camera up for vehicles in the way + if(m_bCollisionChecksOn && (Mode == MODE_FOLLOWPED || Mode == MODE_FIGHT_CAM)){ + bool FoundCar = false; + CEntity *vehicle = nil; + float TestDist = DistOnGround + 1.25f; + float HighestCar = 0.0f; + CVector TestBase = CamTargetEntity->GetPosition(); + CVector TestPoint; + TestBase.z -= 0.15f; + + TestPoint = TestBase - TestDist * CVector(Cos(BetaAngle), Sin(BetaAngle), 0.0f); + if(CWorld::ProcessLineOfSight(CamTargetEntity->GetPosition(), TestPoint, colPoint, vehicle, false, true, false, false, false, false) && + vehicle->IsVehicle()){ + float height = vehicle->GetColModel()->boundingBox.GetSize().z; + FoundCar = true; + HighestCar = height; + if(((CVehicle*)vehicle)->IsTrain()) + m_bTheHeightFixerVehicleIsATrain = true; + } + + TestPoint = TestBase - TestDist * CVector(Cos(BetaAngle+DEGTORAD(28.0f)), Sin(BetaAngle+DEGTORAD(28.0f)), 0.0f); + if(CWorld::ProcessLineOfSight(CamTargetEntity->GetPosition(), TestPoint, colPoint, vehicle, false, true, false, false, false, false) && + vehicle->IsVehicle()){ + float height = vehicle->GetColModel()->boundingBox.GetSize().z; + if(FoundCar){ + HighestCar = Max(HighestCar, height); + }else{ + FoundCar = true; + HighestCar = height; + } + if(((CVehicle*)vehicle)->IsTrain()) + m_bTheHeightFixerVehicleIsATrain = true; + } + + TestPoint = TestBase - TestDist * CVector(Cos(BetaAngle-DEGTORAD(28.0f)), Sin(BetaAngle-DEGTORAD(28.0f)), 0.0f); + if(CWorld::ProcessLineOfSight(CamTargetEntity->GetPosition(), TestPoint, colPoint, vehicle, false, true, false, false, false, false) && + vehicle->IsVehicle()){ + float height = vehicle->GetColModel()->boundingBox.GetSize().z; + if(FoundCar){ + HighestCar = Max(HighestCar, height); + }else{ + FoundCar = true; + HighestCar = height; + } + if(((CVehicle*)vehicle)->IsTrain()) + m_bTheHeightFixerVehicleIsATrain = true; + } + + if(FoundCar){ + m_fDimensionOfHighestNearCar = HighestCar + 0.1f; + if(Mode == MODE_FIGHT_CAM) + m_fDimensionOfHighestNearCar += 0.75f; + }else + m_fDimensionOfHighestNearCar = 0.0f; + } + + // Move up for road + if(Mode == MODE_FOLLOWPED || Mode == MODE_FIGHT_CAM || + Mode == MODE_SYPHON || Mode == MODE_SYPHON_CRIM_IN_FRONT || Mode == MODE_SPECIAL_FIXED_FOR_SYPHON){ + bool Inside = false; + bool OnRoad = false; + + switch(((CPhysical*)CamTargetEntity)->m_nSurfaceTouched) + case SURFACE_GRASS: + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: + case SURFACE_THICK_METAL_PLATE: + case SURFACE_RUBBER: + case SURFACE_STEEP_CLIFF: + OnRoad = true; + + if(CCullZones::PlayerNoRain()) + Inside = true; + + if((m_bCollisionChecksOn || PreviouslyFailedRoadHeightCheck || OnRoad) && + m_fCloseInPedHeightOffset < 0.0001f && !Inside){ + CVector TestPoint; + CEntity *road; + float GroundZ = 0.0f; + bool FoundGround = false; + float RoofZ = 0.0f; + bool FoundRoof = false; + static float MinHeightAboveRoad = 0.9f; + + TestPoint = CamTargetEntity->GetPosition() - DistOnGround * CVector(Cos(BetaAngle), Sin(BetaAngle), 0.0f); + m_fRoadOffSet = 0.0f; + + if(CWorld::ProcessVerticalLine(TestPoint, -1000.0f, colPoint, road, true, false, false, false, false, false, nil)){ + FoundGround = true; + GroundZ = colPoint.point.z; + } + // Move up if too close to ground + if(FoundGround){ + if(TestPoint.z - GroundZ < MinHeightAboveRoad){ + m_fRoadOffSet = GroundZ + MinHeightAboveRoad - TestPoint.z; + PreviouslyFailedRoadHeightCheck = true; + }else{ + if(m_bCollisionChecksOn) + PreviouslyFailedRoadHeightCheck = false; + else + m_fRoadOffSet = 0.0f; + } + }else{ + if(CWorld::ProcessVerticalLine(TestPoint, 1000.0f, colPoint, road, true, false, false, false, false, false, nil)){ + FoundRoof = true; + RoofZ = colPoint.point.z; + } + if(FoundRoof){ + if(TestPoint.z - RoofZ < MinHeightAboveRoad){ + m_fRoadOffSet = RoofZ + MinHeightAboveRoad - TestPoint.z; + PreviouslyFailedRoadHeightCheck = true; + }else{ + if(m_bCollisionChecksOn) + PreviouslyFailedRoadHeightCheck = false; + else + m_fRoadOffSet = 0.0f; + } + } + } + } + } + + if(PreviouslyFailedRoadHeightCheck && m_fCloseInPedHeightOffset < 0.0001f){ + if(colPoint.surfaceB != SURFACE_TARMAC && + colPoint.surfaceB != SURFACE_GRASS && + colPoint.surfaceB != SURFACE_GRAVEL && + colPoint.surfaceB != SURFACE_MUD_DRY && + colPoint.surfaceB != SURFACE_STEEP_CLIFF){ + if(m_fRoadOffSet > 1.4f) + m_fRoadOffSet = 1.4f; + }else{ + if(Mode == MODE_FOLLOWPED){ + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1) + m_fRoadOffSet += 0.2f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_2) + m_fRoadOffSet += 0.5f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_3) + m_fRoadOffSet += 0.95f; + } + } + } + } + + if(StandingOnBoat){ + m_fRoadOffSet = 0.0f; + m_fDimensionOfHighestNearCar = 1.0f; + m_fPedBetweenCameraHeightOffset = 0.0f; + } +} + +void +CCam::GetVectorsReadyForRW(void) +{ + CVector right; + Up = CVector(0.0f, 0.0f, 1.0f); + Front.Normalise(); + if(Front.x == 0.0f && Front.y == 0.0f){ + Front.x = 0.0001f; + Front.y = 0.0001f; + } + right = CrossProduct(Front, Up); + right.Normalise(); + Up = CrossProduct(right, Front); +} + +void +CCam::LookBehind(void) +{ + float Dist, DeltaBeta, TargetOrientation, Angle; + CVector TargetCoors, TargetFwd, TestCoors; + CColPoint colPoint; + CEntity *entity; + + TargetCoors = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_BEHINDBOAT) && CamTargetEntity->IsVehicle()){ + LookingBehind = true; + Dist = Mode == MODE_CAM_ON_A_STRING ? CA_MAX_DISTANCE : 15.5f; + TargetFwd = CamTargetEntity->GetForward(); + TargetFwd.Normalise(); + TargetOrientation = CGeneral::GetATanOfXY(TargetFwd.x, TargetFwd.y); + DeltaBeta = TargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(DirectionWasLooking != LOOKING_BEHIND) + LookBehindCamWasInFront = DeltaBeta <= -HALFPI || DeltaBeta >= HALFPI; + if(LookBehindCamWasInFront) + TargetOrientation += PI; + Source.x = Dist*Cos(TargetOrientation) + TargetCoors.x; + Source.y = Dist*Sin(TargetOrientation) + TargetCoors.y; + Source.z -= 1.0f; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + Source = colPoint.point; + } + Source.z += 1.0f; + Front = CamTargetEntity->GetPosition() - Source; + GetVectorsReadyForRW(); + } + if(Mode == MODE_1STPERSON && CamTargetEntity->IsVehicle()){ + LookingBehind = true; + RwCameraSetNearClipPlane(Scene.camera, 0.25f); + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + if(((CVehicle*)CamTargetEntity)->IsBoat()) + Source.z -= 0.5f; + Source += 0.25f*Front; + Front = -Front; +#ifdef FIX_BUGS + // not sure if this is a bug... + GetVectorsReadyForRW(); +#endif + } + if(CamTargetEntity->IsPed()){ + Angle = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y) + PI; + Source.x = 4.5f*Cos(Angle) + TargetCoors.x; + Source.y = 4.5f*Sin(Angle) + TargetCoors.y; + Source.z = 1.15f + TargetCoors.z; + TestCoors = TargetCoors; + TestCoors.z = Source.z; + if(CWorld::ProcessLineOfSight(TestCoors, Source, colPoint, entity, true, true, false, true, false, true, true)){ + Source.x = colPoint.point.x; + Source.y = colPoint.point.y; + if((TargetCoors - Source).Magnitude2D() < 1.15f) + RwCameraSetNearClipPlane(Scene.camera, 0.05f); + } + Front = TargetCoors - Source; + GetVectorsReadyForRW(); + } +} + +void +CCam::LookLeft(void) +{ + float Dist, TargetOrientation; + CVector TargetCoors, TargetFwd; + CColPoint colPoint; + CEntity *entity; + + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_BEHINDBOAT) && CamTargetEntity->IsVehicle()){ + LookingLeft = true; + TargetCoors = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + Dist = Mode == MODE_CAM_ON_A_STRING ? CA_MAX_DISTANCE : 9.0f; + TargetFwd = CamTargetEntity->GetForward(); + TargetFwd.Normalise(); + TargetOrientation = CGeneral::GetATanOfXY(TargetFwd.x, TargetFwd.y); + Source.x = Dist*Cos(TargetOrientation - HALFPI) + TargetCoors.x; + Source.y = Dist*Sin(TargetOrientation - HALFPI) + TargetCoors.y; + Source.z -= 1.0f; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source = colPoint.point; + } + Source.z += 1.0f; + Front = CamTargetEntity->GetPosition() - Source; + Front.z += 1.1f; + if(Mode == MODE_BEHINDBOAT) + Front.z += 1.2f; + GetVectorsReadyForRW(); + } + if(Mode == MODE_1STPERSON && CamTargetEntity->IsVehicle()){ + LookingLeft = true; + RwCameraSetNearClipPlane(Scene.camera, 0.25f); + if(((CVehicle*)CamTargetEntity)->IsBoat()) + Source.z -= 0.5f; + + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Front = -CrossProduct(Front, Up); + Front.Normalise(); +#ifdef FIX_BUGS + // not sure if this is a bug... + GetVectorsReadyForRW(); +#endif + } +} + +void +CCam::LookRight(void) +{ + float Dist, TargetOrientation; + CVector TargetCoors, TargetFwd; + CColPoint colPoint; + CEntity *entity; + + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_BEHINDBOAT) && CamTargetEntity->IsVehicle()){ + LookingRight = true; + TargetCoors = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + Dist = Mode == MODE_CAM_ON_A_STRING ? CA_MAX_DISTANCE : 9.0f; + TargetFwd = CamTargetEntity->GetForward(); + TargetFwd.Normalise(); + TargetOrientation = CGeneral::GetATanOfXY(TargetFwd.x, TargetFwd.y); + Source.x = Dist*Cos(TargetOrientation + HALFPI) + TargetCoors.x; + Source.y = Dist*Sin(TargetOrientation + HALFPI) + TargetCoors.y; + Source.z -= 1.0f; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source = colPoint.point; + } + Source.z += 1.0f; + Front = CamTargetEntity->GetPosition() - Source; + Front.z += 1.1f; + if(Mode == MODE_BEHINDBOAT) + Front.z += 1.2f; + GetVectorsReadyForRW(); + } + if(Mode == MODE_1STPERSON && CamTargetEntity->IsVehicle()){ + LookingRight = true; + RwCameraSetNearClipPlane(Scene.camera, 0.25f); + if(((CVehicle*)CamTargetEntity)->IsBoat()) + Source.z -= 0.5f; + + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Front = CrossProduct(Front, Up); + Front.Normalise(); +#ifdef FIX_BUGS + // not sure if this is a bug... + GetVectorsReadyForRW(); +#endif + } +} + +void +CCam::ClipIfPedInFrontOfPlayer(void) +{ + float FwdAngle, PedAngle, DeltaAngle, fDist, Near; + CVector vDist; + CPed *Player; + bool found = false; + int ped = 0; + + // unused: TheCamera.pTargetEntity->GetPosition() - TheCamera.GetGameCamPosition(); + + FwdAngle = CGeneral::GetATanOfXY(TheCamera.GetForward().x, TheCamera.GetForward().y); + Player = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + while(ped < Player->m_numNearPeds && !found) + if(Player->m_nearPeds[ped] && Player->m_nearPeds[ped]->GetPedState() != PED_DEAD) + found = true; + else + ped++; + if(found){ + vDist = Player->m_nearPeds[ped]->GetPosition() - TheCamera.GetGameCamPosition(); + PedAngle = CGeneral::GetATanOfXY(vDist.x, vDist.y); + DeltaAngle = FwdAngle - PedAngle; + while(DeltaAngle >= PI) DeltaAngle -= 2*PI; + while(DeltaAngle < -PI) DeltaAngle += 2*PI; + if(Abs(DeltaAngle) < HALFPI){ + fDist = vDist.Magnitude2D(); + if(fDist < 1.25f){ + Near = DEFAULT_NEAR - (1.25f - fDist); + if(Near < 0.05f) + Near = 0.05f; + RwCameraSetNearClipPlane(Scene.camera, Near); + } + } + } +} + +void +CCam::KeepTrackOfTheSpeed(const CVector &source, const CVector &target, const CVector &up, const float &alpha, const float &beta, const float &fov) +{ + static CVector PreviousSource = source; + static CVector PreviousTarget = target; + static CVector PreviousUp = up; + static float PreviousBeta = beta; + static float PreviousAlpha = alpha; + static float PreviousFov = fov; + + if(TheCamera.m_bJust_Switched){ + PreviousSource = source; + PreviousTarget = target; + PreviousUp = up; + } + + m_cvecSourceSpeedOverOneFrame = source - PreviousSource; + m_cvecTargetSpeedOverOneFrame = target - PreviousTarget; + m_cvecUpOverOneFrame = up - PreviousUp; + m_fFovSpeedOverOneFrame = fov - PreviousFov; + m_fBetaSpeedOverOneFrame = beta - PreviousBeta; + MakeAngleLessThan180(m_fBetaSpeedOverOneFrame); + m_fAlphaSpeedOverOneFrame = alpha - PreviousAlpha; + MakeAngleLessThan180(m_fAlphaSpeedOverOneFrame); + + PreviousSource = source; + PreviousTarget = target; + PreviousUp = up; + PreviousBeta = beta; + PreviousAlpha = alpha; + PreviousFov = fov; +} + +bool +CCam::Using3rdPersonMouseCam(void) +{ + return CCamera::m_bUseMouse3rdPerson && + (Mode == MODE_FOLLOWPED || + TheCamera.m_bPlayerIsInGarage && + FindPlayerPed() && FindPlayerPed()->m_nPedState != PED_DRIVING && + Mode != MODE_TOPDOWN && CamTargetEntity == FindPlayerPed()); +} + +bool +CCam::GetWeaponFirstPersonOn(void) +{ + return CamTargetEntity && CamTargetEntity->IsPed() && ((CPed*)CamTargetEntity)->GetWeapon()->m_bAddRotOffset; +} + +bool +CCam::IsTargetInWater(const CVector &CamCoors) +{ + if(CamTargetEntity == nil) + return false; + if(CamTargetEntity->IsPed()){ + if(!((CPed*)CamTargetEntity)->bIsInWater) + return false; + if(!((CPed*)CamTargetEntity)->bIsStanding) + return true; + return false; + } + return ((CPhysical*)CamTargetEntity)->bIsInWater; +} + +void +CCam::PrintMode(void) +{ + // Doesn't do anything + char buf[256]; + + if(PrintDebugCode){ + sprintf(buf, " "); + sprintf(buf, " "); + sprintf(buf, " "); + + static Const char *modes[] = { "None", + "Top Down", "GTA Classic", "Behind Car", "Follow Ped", + "Aiming", "Debug", "Sniper", "Rocket", "Model Viewer", "Bill", + "Syphon", "Circle", "Cheesy Zoom", "Wheel", "Fixed", + "1st Person", "Fly by", "on a String", "Reaction", + "Follow Ped with Bind", "Chris", "Behind Boat", + "Player fallen in Water", "Train Roof", "Train Side", + "Blood on the tracks", "Passenger", "Syphon Crim in Front", + "Dead Baby", "Pillow Paps", "Look at Cars", "Arrest One", + "Arrest Two", "M16", "Special fixed for Syphon", "Fight", + "Top Down Ped", + "Sniper run about", "Rocket run about", + "1st Person run about", "M16 run about", "Fight run about", + "Editor" + }; + sprintf(buf, "Cam: %s", modes[TheCamera.Cams[TheCamera.ActiveCam].Mode]); + CDebug::PrintAt(buf, 2, 5); + } + + if(DebugCamMode != MODE_NONE){ + switch(Mode){ + case MODE_FOLLOWPED: + sprintf(buf, "Debug:- Cam Choice1. No Locking, used as game default"); + break; + case MODE_REACTION: + sprintf(buf, "Debug:- Cam Choice2. Reaction Cam On A String "); + sprintf(buf, " Uses Locking Button LeftShoulder 1. "); // lie + break; + case MODE_FOLLOW_PED_WITH_BIND: + sprintf(buf, "Debug:- Cam Choice3. Game ReactionCam with Locking "); + sprintf(buf, " Uses Locking Button LeftShoulder 1. "); + break; + case MODE_CHRIS: + sprintf(buf, "Debug:- Cam Choice4. Chris's idea. "); + sprintf(buf, " Uses Locking Button LeftShoulder 1. "); + sprintf(buf, " Also control the camera using the right analogue stick."); + break; + } + } +} + +// This code is really bad. wtf R*? +CVector +CCam::DoAverageOnVector(const CVector &vec) +{ + int i; + CVector Average(0.0f, 0.0f, 0.0f); + + if(ResetStatics){ + m_iRunningVectorArrayPos = 0; + m_iRunningVectorCounter = 1; + } + + // TODO: make this work with NUMBER_OF_VECTORS_FOR_AVERAGE != 2 + if(m_iRunningVectorCounter == 3){ + m_arrPreviousVectors[0] = m_arrPreviousVectors[1]; + m_arrPreviousVectors[1] = vec; + }else + m_arrPreviousVectors[m_iRunningVectorArrayPos] = vec; + + for(i = 0; i <= m_iRunningVectorArrayPos; i++) + Average += m_arrPreviousVectors[i]; + Average /= i; + + m_iRunningVectorArrayPos++; + m_iRunningVectorCounter++; + if(m_iRunningVectorArrayPos >= NUMBER_OF_VECTORS_FOR_AVERAGE) + m_iRunningVectorArrayPos = NUMBER_OF_VECTORS_FOR_AVERAGE-1; + if(m_iRunningVectorCounter > NUMBER_OF_VECTORS_FOR_AVERAGE+1) + m_iRunningVectorCounter = NUMBER_OF_VECTORS_FOR_AVERAGE+1; + + return Average; +} + +// Rotate Beta in direction opposite of BetaOffset in 5 deg. steps. +// Return the first angle for which Beta + BetaOffset + Angle has a clear view. +// i.e. BetaOffset is a safe zone so that Beta + Angle is really clear. +// If BetaOffset == 0, try both directions. +float +CCam::GetPedBetaAngleForClearView(const CVector &Target, float Dist, float BetaOffset, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies) +{ + CColPoint point; + CEntity *ent = nil; + CVector ToSource; + float a; + + // This would be so much nicer if we just got the step variable before the loop...R* + + for(a = 0.0f; a <= PI; a += DEGTORAD(5.0f)){ + if(BetaOffset <= 0.0f){ + ToSource = CVector(Cos(Beta + BetaOffset + a), Sin(Beta + BetaOffset + a), 0.0f)*Dist; + if(!CWorld::ProcessLineOfSight(Target, Target + ToSource, + point, ent, checkBuildings, checkVehicles, checkPeds, + checkObjects, checkDummies, true, true)) + return a; + } + if(BetaOffset >= 0.0f){ + ToSource = CVector(Cos(Beta + BetaOffset - a), Sin(Beta + BetaOffset - a), 0.0f)*Dist; + if(!CWorld::ProcessLineOfSight(Target, Target + ToSource, + point, ent, checkBuildings, checkVehicles, checkPeds, + checkObjects, checkDummies, true, true)) + return -a; + } + } + return 0.0f; +} + +float DefaultAcceleration = 0.045f; +float DefaultMaxStep = 0.15f; + +void +CCam::Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + const float GroundDist = 1.85f; + + CVector TargetCoors, Dist, IdealSource; + float Length = 0.0f; + float LateralLeft = 0.0f; + float LateralRight = 0.0f; + float Center = 0.0f; + static bool PreviouslyObscured; + static bool PickedASide; + static float FixedTargetOrientation = 0.0f; + float AngleToGoTo = 0.0f; + float BetaOffsetAvoidBuildings = 0.45f; // ~25 deg + float BetaOffsetGoingBehind = 0.45f; + bool GoingBehind = false; + bool Obscured = false; + bool BuildingCheckObscured = false; + bool StandingInTrain = false; + static int TimeIndicatedWantedToGoDown = 0; + static bool StartedCountingForGoDown = false; + float DeltaBeta; + + m_bFixingBeta = false; + bBelowMinDist = false; + bBehindPlayerDesired = false; + + // CenterDist should be > LateralDist because we don't have an angle for safety in this case + float CenterDist, LateralDist; + float AngleToGoToSpeed; + if(m_fCloseInPedHeightOffset > 0.00001f){ + LateralDist = 0.55f; + CenterDist = 1.25f; + BetaOffsetAvoidBuildings = 0.9f; // ~50 deg + BetaOffsetGoingBehind = 0.9f; + AngleToGoToSpeed = 0.88254666f; + }else{ + LateralDist = 0.8f; + CenterDist = 1.35f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1 || TheCamera.PedZoomIndicator == CAM_ZOOM_TOPDOWN){ + LateralDist = 1.25f; + CenterDist = 1.6f; + } + AngleToGoToSpeed = 0.43254671f; + } + + FOV = DefaultFOV; + + if(ResetStatics){ + Rotating = false; + m_bCollisionChecksOn = true; + FixedTargetOrientation = 0.0f; + PreviouslyObscured = false; + PickedASide = false; + StartedCountingForGoDown = false; + AngleToGoTo = 0.0f; + // unused LastAngleWithNoPickedASide + } + + + TargetCoors = CameraTarget; + IdealSource = Source; + TargetCoors.z += m_fSyphonModeTargetZOffSet; + + TargetCoors = DoAverageOnVector(TargetCoors); + TargetCoors.z += m_fRoadOffSet; + + Dist.x = IdealSource.x - TargetCoors.x; + Dist.y = IdealSource.y - TargetCoors.y; + Length = Dist.Magnitude2D(); + + // Cam on a string. With a fixed distance. Zoom in/out is done later. + if(Length != 0.0f) + IdealSource = TargetCoors + CVector(Dist.x, Dist.y, 0.0f)/Length * GroundDist; + else + IdealSource = TargetCoors + CVector(1.0f, 1.0f, 0.0f); + + if(TheCamera.m_bUseTransitionBeta && ResetStatics){ + CVector VecDistance; + IdealSource.x = TargetCoors.x + GroundDist*Cos(m_fTransitionBeta); + IdealSource.y = TargetCoors.y + GroundDist*Sin(m_fTransitionBeta); + Beta = CGeneral::GetATanOfXY(IdealSource.x - TargetCoors.x, IdealSource.y - TargetCoors.y); + }else + Beta = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y); + + if(TheCamera.m_bCamDirectlyBehind){ + m_bCollisionChecksOn = true; + Beta = TargetOrientation + PI; + } + + if(FindPlayerVehicle()) + if(FindPlayerVehicle()->m_vehType == VEHICLE_TYPE_TRAIN) + StandingInTrain = true; + + if(TheCamera.m_bCamDirectlyInFront){ + m_bCollisionChecksOn = true; + Beta = TargetOrientation; + } + + while(Beta >= PI) Beta -= 2.0f * PI; + while(Beta < -PI) Beta += 2.0f * PI; + + // BUG? is this ever used? + // The values seem to be roughly m_fPedZoomValueSmooth + 1.85 + if(ResetStatics){ + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1) m_fRealGroundDist = 2.090556f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_2) m_fRealGroundDist = 3.34973f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_3) m_fRealGroundDist = 4.704914f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_TOPDOWN) m_fRealGroundDist = 2.090556f; + } + // And what is this? It's only used for collision and rotation it seems + float RealGroundDist; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1) RealGroundDist = 2.090556f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_2) RealGroundDist = 3.34973f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_3) RealGroundDist = 4.704914f; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_TOPDOWN) RealGroundDist = 2.090556f; + if(m_fCloseInPedHeightOffset > 0.00001f) + RealGroundDist = 1.7016f; + + + bool Shooting = false; + CPed *ped = (CPed*)CamTargetEntity; + if(ped->GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) + if(CPad::GetPad(0)->GetWeapon()) + Shooting = true; + if(ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_DETONATOR || + ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT) + Shooting = false; + + + if(m_fCloseInPedHeightOffset > 0.00001f) + TargetCoors.z -= m_fRoadOffSet; + + // Figure out if and where we want to rotate + + if(CPad::GetPad(0)->ForceCameraBehindPlayer() || Shooting){ + + // Center cam behind player + + GoingBehind = true; + m_bCollisionChecksOn = true; + float OriginalBeta = Beta; + // Set Beta behind player + Beta = TargetOrientation + PI; + TargetCoors.z -= 0.1f; + + AngleToGoTo = GetPedBetaAngleForClearView(TargetCoors, CenterDist * RealGroundDist, 0.0f, true, false, false, true, false); + if(AngleToGoTo != 0.0f){ + if(AngleToGoTo < 0.0f) + AngleToGoTo -= AngleToGoToSpeed; + else + AngleToGoTo += AngleToGoToSpeed; + }else{ + float LateralLeft = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, BetaOffsetGoingBehind, true, false, false, true, false); + float LateralRight = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, -BetaOffsetGoingBehind, true, false, false, true, false); + if(LateralLeft == 0.0f && LateralRight != 0.0f) + AngleToGoTo += LateralRight; + else if(LateralLeft != 0.0f && LateralRight == 0.0f) + AngleToGoTo += LateralLeft; + } + + TargetCoors.z += 0.1f; + Beta = OriginalBeta; + + if(PickedASide){ + if(AngleToGoTo == 0.0f) + FixedTargetOrientation = TargetOrientation + PI; + Rotating = true; + }else{ + FixedTargetOrientation = TargetOrientation + PI + AngleToGoTo; + Rotating = true; + PickedASide = true; + } + }else{ + + // Rotate cam to avoid clipping into buildings + + TargetCoors.z -= 0.1f; + + Center = GetPedBetaAngleForClearView(TargetCoors, CenterDist * RealGroundDist, 0.0f, true, false, false, true, false); + if(m_bCollisionChecksOn || PreviouslyObscured || Center != 0.0f || m_fCloseInPedHeightOffset > 0.00001f){ + if(Center != 0.0f){ + AngleToGoTo = Center; + }else{ + LateralLeft = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, BetaOffsetAvoidBuildings, true, false, false, true, false); + LateralRight = GetPedBetaAngleForClearView(TargetCoors, LateralDist * RealGroundDist, -BetaOffsetAvoidBuildings, true, false, false, true, false); + if(LateralLeft == 0.0f && LateralRight != 0.0f){ + AngleToGoTo += LateralRight; + if(m_fCloseInPedHeightOffset > 0.0f) + RwCameraSetNearClipPlane(Scene.camera, 0.7f); + }else if(LateralLeft != 0.0f && LateralRight == 0.0f){ + AngleToGoTo += LateralLeft; + if(m_fCloseInPedHeightOffset > 0.0f) + RwCameraSetNearClipPlane(Scene.camera, 0.7f); + } + } + if(LateralLeft != 0.0f || LateralRight != 0.0f || Center != 0.0f) + BuildingCheckObscured = true; + } + + TargetCoors.z += 0.1f; + } + + if(m_fCloseInPedHeightOffset > 0.00001f) + TargetCoors.z += m_fRoadOffSet; + + + // Have to fix to avoid collision + + if(AngleToGoTo != 0.0f){ + Obscured = true; + Rotating = true; + if(CPad::GetPad(0)->ForceCameraBehindPlayer() || Shooting){ + if(!PickedASide) + FixedTargetOrientation = Beta + AngleToGoTo; // can this even happen? + }else + FixedTargetOrientation = Beta + AngleToGoTo; + + // This calculation is only really used to figure out how fast to rotate out of collision + + m_fAmountFractionObscured = 1.0f; + CVector PlayerPos = FindPlayerPed()->GetPosition(); + float RotationDist = (AngleToGoTo == Center ? CenterDist : LateralDist) * RealGroundDist; + // What's going on here? - AngleToGoTo? + CVector RotatedSource = PlayerPos + CVector(Cos(Beta - AngleToGoTo), Sin(Beta - AngleToGoTo), 0.0f) * RotationDist; + + CColPoint colpoint; + CEntity *entity; + if(CWorld::ProcessLineOfSight(PlayerPos, RotatedSource, colpoint, entity, true, false, false, true, false, false, false)){ + if((PlayerPos - RotatedSource).Magnitude() != 0.0f) + m_fAmountFractionObscured = (PlayerPos - colpoint.point).Magnitude() / (PlayerPos - RotatedSource).Magnitude(); + else + m_fAmountFractionObscured = 1.0f; + } + } + if(m_fAmountFractionObscured < 0.0f) m_fAmountFractionObscured = 0.0f; + if(m_fAmountFractionObscured > 1.0f) m_fAmountFractionObscured = 1.0f; + + + + // Figure out speed values for Beta rotation + + float Acceleration, MaxSpeed; + static float AccelerationMult = 0.35f; + static float MaxSpeedMult = 0.85f; + static float AccelerationMultClose = 0.7f; + static float MaxSpeedMultClose = 1.6f; + float BaseAcceleration = 0.025f; + float BaseMaxSpeed = 0.09f; + if(m_fCloseInPedHeightOffset > 0.00001f){ + if(AngleToGoTo == 0.0f){ + BaseAcceleration = 0.022f; + BaseMaxSpeed = 0.04f; + }else{ + BaseAcceleration = DefaultAcceleration; + BaseMaxSpeed = DefaultMaxStep; + } + } + if(AngleToGoTo == 0.0f){ + Acceleration = BaseAcceleration; + MaxSpeed = BaseMaxSpeed; + }else if(CPad::GetPad(0)->ForceCameraBehindPlayer() && !Shooting){ + Acceleration = 0.051f; + MaxSpeed = 0.18f; + }else if(m_fCloseInPedHeightOffset > 0.00001f){ + Acceleration = BaseAcceleration + AccelerationMultClose*sq(m_fAmountFractionObscured - 1.05f); + MaxSpeed = BaseMaxSpeed + MaxSpeedMultClose*sq(m_fAmountFractionObscured - 1.05f); + }else{ + Acceleration = DefaultAcceleration + AccelerationMult*sq(m_fAmountFractionObscured - 1.05f); + MaxSpeed = DefaultMaxStep + MaxSpeedMult*sq(m_fAmountFractionObscured - 1.05f); + } + static float AccelerationLimit = 0.3f; + static float MaxSpeedLimit = 0.65f; + if(Acceleration > AccelerationLimit) Acceleration = AccelerationLimit; + if(MaxSpeed > MaxSpeedLimit) MaxSpeed = MaxSpeedLimit; + + + int MoveState = ((CPed*)CamTargetEntity)->m_nMoveState; + if(MoveState != PEDMOVE_NONE && MoveState != PEDMOVE_STILL && + !CPad::GetPad(0)->ForceCameraBehindPlayer() && !Obscured && !Shooting){ + Rotating = false; + BetaSpeed = 0.0f; + } + + // Now do the Beta rotation + + float RotDistance = (IdealSource - TargetCoors).Magnitude2D(); + m_fDistanceBeforeChanges = RotDistance; + + if(Rotating){ + m_bFixingBeta = true; + + while(FixedTargetOrientation >= PI) FixedTargetOrientation -= 2*PI; + while(FixedTargetOrientation < -PI) FixedTargetOrientation += 2*PI; + + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + +/* + // This is inlined WellBufferMe + DeltaBeta = FixedTargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + + float ReqSpeed = DeltaBeta * MaxSpeed; + // Add or subtract absolute depending on sign, genius! + if(ReqSpeed - BetaSpeed > 0.0f) + BetaSpeed += SpeedStep * Abs(ReqSpeed - BetaSpeed) * CTimer::GetTimeStep(); + else + BetaSpeed -= SpeedStep * Abs(ReqSpeed - BetaSpeed) * CTimer::GetTimeStep(); + // this would be simpler: + // BetaSpeed += SpeedStep * (ReqSpeed - BetaSpeed) * CTimer::ms_fTimeStep; + + if(ReqSpeed < 0.0f && BetaSpeed < ReqSpeed) + BetaSpeed = ReqSpeed; + else if(ReqSpeed > 0.0f && BetaSpeed > ReqSpeed) + BetaSpeed = ReqSpeed; + + Beta += BetaSpeed * Min(10.0f, CTimer::GetTimeStep()); +*/ + WellBufferMe(FixedTargetOrientation, &Beta, &BetaSpeed, MaxSpeed, Acceleration, true); + + if(ResetStatics){ + Beta = FixedTargetOrientation; + BetaSpeed = 0.0f; + } + + Source.x = TargetCoors.x + RotDistance * Cos(Beta); + Source.y = TargetCoors.y + RotDistance * Sin(Beta); + + // Check if we can stop rotating + DeltaBeta = FixedTargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < DEGTORAD(1.0f) && !bBehindPlayerDesired){ + // Stop rotation + PickedASide = false; + Rotating = false; + BetaSpeed = 0.0f; + } + } + + + if(TheCamera.m_bCamDirectlyBehind || TheCamera.m_bCamDirectlyInFront || + StandingInTrain || Rotating){ + if(TheCamera.m_bCamDirectlyBehind){ + Beta = TargetOrientation + PI; + Source.x = TargetCoors.x + RotDistance * Cos(Beta); + Source.y = TargetCoors.y + RotDistance * Sin(Beta); + } + if(TheCamera.m_bCamDirectlyInFront){ + Beta = TargetOrientation; + Source.x = TargetCoors.x + RotDistance * Cos(Beta); + Source.y = TargetCoors.y + RotDistance * Sin(Beta); + } + if(StandingInTrain){ + Beta = TargetOrientation + PI; + Source.x = TargetCoors.x + RotDistance * Cos(Beta); + Source.y = TargetCoors.y + RotDistance * Sin(Beta); + m_fDimensionOfHighestNearCar = 0.0f; + m_fCamBufferedHeight = 0.0f; + m_fCamBufferedHeightSpeed = 0.0f; + } + // Beta and Source already set in the rotation code + }else{ + Source = IdealSource; + BetaSpeed = 0.0f; + } + + // Subtract m_fRoadOffSet from both? + TargetCoors.z -= m_fRoadOffSet; + Source.z = IdealSource.z - m_fRoadOffSet; + + // Apply zoom now + // m_fPedZoomValueSmooth makes the cam go down the further out it is + // 0.25 -> 0.20 for nearest dist + // 1.50 -> -0.05 for mid dist + // 2.90 -> -0.33 for far dist + Source.z += (2.5f - TheCamera.m_fPedZoomValueSmooth)*0.2f - 0.25f; + // Zoom out camera + Front = TargetCoors - Source; + Front.Normalise(); + Source -= Front * TheCamera.m_fPedZoomValueSmooth; + // and then we move up again + // -0.375 + // 0.25 + // 0.95 + Source.z += (TheCamera.m_fPedZoomValueSmooth - 1.0f)*0.5f + m_fCloseInPedHeightOffset; + + + // Process height offset to avoid peds and cars + + float TargetZOffSet = m_fRoadOffSet + m_fDimensionOfHighestNearCar; + TargetZOffSet = Max(TargetZOffSet, m_fPedBetweenCameraHeightOffset); + float TargetHeight = CameraTarget.z + TargetZOffSet - Source.z; + + if(TargetHeight > m_fCamBufferedHeight){ + // Have to go up + if(TargetZOffSet == m_fPedBetweenCameraHeightOffset && TargetZOffSet > m_fCamBufferedHeight) + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.04f, false); + else if(TargetZOffSet == m_fRoadOffSet && TargetZOffSet > m_fCamBufferedHeight){ + // TODO: figure this out + bool foo = false; + switch(((CPhysical*)CamTargetEntity)->m_nSurfaceTouched) + case SURFACE_GRASS: + case SURFACE_GRAVEL: + case SURFACE_PAVEMENT: + case SURFACE_THICK_METAL_PLATE: + case SURFACE_RUBBER: + case SURFACE_STEEP_CLIFF: + foo = true; + if(foo) + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.4f, 0.05f, false); + else + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.025f, false); + }else + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.025f, false); + StartedCountingForGoDown = false; + }else{ + // Have to go down + if(StartedCountingForGoDown){ + if(CTimer::GetTimeInMilliseconds() != TimeIndicatedWantedToGoDown){ + if(TargetHeight > 0.0f) + WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.01f, false); + else + WellBufferMe(0.0f, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.2f, 0.01f, false); + } + }else{ + StartedCountingForGoDown = true; + TimeIndicatedWantedToGoDown = CTimer::GetTimeInMilliseconds(); + } + } + + Source.z += m_fCamBufferedHeight; + + + // Clip Source if necessary + + bool ClipSource = m_fCloseInPedHeightOffset > 0.00001f && m_fCamBufferedHeight > 0.001f; + if(GoingBehind || ResetStatics || ClipSource){ + CColPoint colpoint; + CEntity *entity; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colpoint, entity, true, false, false, true, false, true, true)){ + Source = colpoint.point; + if((TargetCoors - Source).Magnitude2D() < 1.0f) + RwCameraSetNearClipPlane(Scene.camera, 0.05f); + } + } + + TargetCoors.z += Min(1.0f, m_fCamBufferedHeight/2.0f); + m_cvecTargetCoorsForFudgeInter = TargetCoors; + + Front = TargetCoors - Source; + m_fRealGroundDist = Front.Magnitude2D(); + m_fMinDistAwayFromCamWhenInterPolating = m_fRealGroundDist; + Front.Normalise(); + GetVectorsReadyForRW(); + TheCamera.m_bCamDirectlyBehind = false; + TheCamera.m_bCamDirectlyInFront = false; + PreviouslyObscured = BuildingCheckObscured; + + ResetStatics = false; +} + +float fBaseDist = 1.7f; +float fAngleDist = 2.0f; +float fFalloff = 3.0f; +float fStickSens = 0.01f; +float fTweakFOV = 1.05f; +float fTranslateCamUp = 0.8f; +int16 nFadeControlThreshhold = 45; +float fDefaultAlphaOrient = -0.22f; + +void +CCam::Process_FollowPedWithMouse(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsPed()) + return; + + CVector TargetCoors; + float CamDist; + CColPoint colPoint; + CEntity *entity; + + if(ResetStatics){ + Rotating = false; + m_bCollisionChecksOn = true; + CPad::GetPad(0)->ClearMouseHistory(); + ResetStatics = false; + } + + bool OnTrain = FindPlayerVehicle() && FindPlayerVehicle()->IsTrain(); + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if((MouseX != 0.0f || MouseY != 0.0f) && !CPad::GetPad(0)->ArePlayerControlsDisabled()){ + UseMouse = true; + LookLeftRight = -2.5f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->LookAroundLeftRight(); + LookUpDown = CPad::GetPad(0)->LookAroundUpDown(); + } + float AlphaOffset, BetaOffset; + if(UseMouse){ + BetaOffset = LookLeftRight * TheCamera.m_fMouseAccelHorzntl * FOV/80.0f; + AlphaOffset = LookUpDown * TheCamera.m_fMouseAccelVertical * FOV/80.0f; + }else{ + BetaOffset = LookLeftRight * fStickSens * (1.0f/14.0f) * FOV/80.0f * CTimer::GetTimeStep(); + AlphaOffset = LookUpDown * fStickSens * (0.6f/14.0f) * FOV/80.0f * CTimer::GetTimeStep(); + } + + if(TheCamera.GetFading() && TheCamera.GetFadingDirection() == FADE_IN && nFadeControlThreshhold < CDraw::FadeValue || + CDraw::FadeValue > 200){ + if(Alpha < fDefaultAlphaOrient-0.05f) + AlphaOffset = 0.05f; + else if(Alpha < fDefaultAlphaOrient) + AlphaOffset = fDefaultAlphaOrient - Alpha; + else if(Alpha > fDefaultAlphaOrient+0.05f) + AlphaOffset = -0.05f; + else if(Alpha > fDefaultAlphaOrient) + AlphaOffset = fDefaultAlphaOrient - Alpha; + else + AlphaOffset = 0.0f; + } + + Alpha += AlphaOffset; + Beta += BetaOffset; + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(45.0f)) Alpha = DEGTORAD(45.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors = CameraTarget; + TargetCoors.z += fTranslateCamUp; + TargetCoors = DoAverageOnVector(TargetCoors); + + // SA code +#ifdef FREE_CAM + if((CCamera::bFreeCam && Alpha > 0.0f) || (!CCamera::bFreeCam && Alpha > fBaseDist)) +#else + if(Alpha > fBaseDist) // comparing an angle against a distance? +#endif + CamDist = fBaseDist + Cos(Min(Alpha*fFalloff, HALFPI))*fAngleDist; + else + CamDist = fBaseDist + Cos(Alpha)*fAngleDist; + + if(TheCamera.m_bUseTransitionBeta) + Beta = CGeneral::GetATanOfXY(-Cos(m_fTransitionBeta), -Sin(m_fTransitionBeta)); + + if(TheCamera.m_bCamDirectlyBehind) + Beta = TheCamera.m_PedOrientForBehindOrInFront; + if(TheCamera.m_bCamDirectlyInFront) + Beta = TheCamera.m_PedOrientForBehindOrInFront + PI; + if(OnTrain) + Beta = TargetOrientation; + + Front.x = Cos(Alpha) * Cos(Beta); + Front.y = Cos(Alpha) * Sin(Beta); + Front.z = Sin(Alpha); + Source = TargetCoors - Front*CamDist; + m_cvecTargetCoorsForFudgeInter = TargetCoors; + + // Clip Source and fix near clip + CWorld::pIgnoreEntity = CamTargetEntity; + entity = nil; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, true, true, true, false, false, true)){ + float PedColDist = (TargetCoors - colPoint.point).Magnitude(); + float ColCamDist = CamDist - PedColDist; + if(entity->IsPed() && ColCamDist > DEFAULT_NEAR + 0.1f){ + // Ped in the way but not clipping through + if(CWorld::ProcessLineOfSight(colPoint.point, Source, colPoint, entity, true, true, true, true, false, false, true)){ + PedColDist = (TargetCoors - colPoint.point).Magnitude(); + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + }else{ + RwCameraSetNearClipPlane(Scene.camera, Min(ColCamDist-0.35f, DEFAULT_NEAR)); + } + }else{ + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + } + } + CWorld::pIgnoreEntity = nil; + + float ViewPlaneHeight = Tan(DEGTORAD(FOV) / 2.0f); + float ViewPlaneWidth = ViewPlaneHeight * CDraw::FindAspectRatio() * fTweakFOV; + float Near = RwCameraGetNearClipPlane(Scene.camera); + float radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, false); + int i = 0; + while(entity){ + CVector CamToCol = gaTempSphereColPoints[0].point - Source; + float frontDist = DotProduct(CamToCol, Front); + float dist = (CamToCol - Front*frontDist).Magnitude() / ViewPlaneWidth; + + // Try to decrease near clip + dist = Max(Min(Near, dist), 0.1f); + if(dist < Near) + RwCameraSetNearClipPlane(Scene.camera, dist); + + // Move forward a bit + if(dist == 0.1f) + Source += (TargetCoors - Source)*0.3f; + + Near = RwCameraGetNearClipPlane(Scene.camera); +#ifndef FIX_BUGS + // this is totally wrong... + radius = Tan(FOV / 2.0f) * Near; +#else + radius = ViewPlaneWidth*Near; +#endif + // Keep testing + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, false); + + i++; + if(i > 5) + entity = nil; + } + + if(CamTargetEntity->m_rwObject){ + // what's going on here? + if(RpAnimBlendClumpGetAssociation(CamTargetEntity->GetClump(), ANIM_STD_WEAPON_PUMP) || + RpAnimBlendClumpGetAssociation(CamTargetEntity->GetClump(), ANIM_STD_WEAPON_THROW) || + RpAnimBlendClumpGetAssociation(CamTargetEntity->GetClump(), ANIM_STD_THROW_UNDER) || + RpAnimBlendClumpGetAssociation(CamTargetEntity->GetClump(), ANIM_STD_START_THROW)){ + CPed *player = FindPlayerPed(); + float PlayerDist = (Source - player->GetPosition()).Magnitude(); + if(PlayerDist < 2.75f) + Near = PlayerDist/2.75f * DEFAULT_NEAR - 0.3f; + RwCameraSetNearClipPlane(Scene.camera, Max(Near, 0.1f)); + } + } + + TheCamera.m_bCamDirectlyInFront = false; + TheCamera.m_bCamDirectlyBehind = false; + + GetVectorsReadyForRW(); + + if(((CPed*)CamTargetEntity)->CanStrafeOrMouseControl() && CDraw::FadeValue < 250 && + (TheCamera.GetFadingDirection() != FADE_OUT || CDraw::FadeValue <= 100)){ + float Heading = Front.Heading(); + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Heading; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Heading; + TheCamera.pTargetEntity->SetHeading(Heading); + TheCamera.pTargetEntity->GetMatrix().UpdateRW(); + } +} + +float fBillsBetaOffset; // made up name, actually in CCam + +void +CCam::Process_BehindCar(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsVehicle()) + return; + + CVector TargetCoors = CameraTarget; + TargetCoors.z -= 0.2f; + CA_MAX_DISTANCE = 9.95f; + CA_MIN_DISTANCE = 8.5f; + + CVector Dist = Source - TargetCoors; + float Length = Dist.Magnitude2D(); + m_fDistanceBeforeChanges = Length; + if(Length < 0.002f) + Length = 0.002f; + Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y); +#if 1 + // This is completely made up but Bill's cam manipulates an angle before calling this + // and otherwise calculating Beta doesn't make much sense. + Beta += fBillsBetaOffset; + fBillsBetaOffset = 0.0f; + Dist.x = -Length*Cos(Beta); + Dist.y = -Length*Sin(Beta); + Source = TargetCoors + Dist; +#endif + if(Length > CA_MAX_DISTANCE){ + Source.x = TargetCoors.x + Dist.x/Length * CA_MAX_DISTANCE; + Source.y = TargetCoors.y + Dist.y/Length * CA_MAX_DISTANCE; + }else if(Length < CA_MIN_DISTANCE){ + Source.x = TargetCoors.x + Dist.x/Length * CA_MIN_DISTANCE; + Source.y = TargetCoors.y + Dist.y/Length * CA_MIN_DISTANCE; + } + TargetCoors.z += 0.8f; + + WorkOutCamHeightWeeCar(TargetCoors, TargetOrientation); + RotCamIfInFrontCar(TargetCoors, TargetOrientation); + FixCamIfObscured(TargetCoors, 1.2f, TargetOrientation); + + Front = TargetCoors - Source; + m_cvecTargetCoorsForFudgeInter = TargetCoors; + ResetStatics = false; + GetVectorsReadyForRW(); +} + +void +CCam::WorkOutCamHeightWeeCar(CVector &TargetCoors, float TargetOrientation) +{ + CColPoint colpoint; + CEntity *ent; + float TargetZOffSet = 0.0f; + static bool PreviouslyFailedRoadHeightCheck = false; + static float RoadHeightFix = 0.0f; + static float RoadHeightFixSpeed = 0.0f; + + if(ResetStatics){ + RoadHeightFix = 0.0f; + RoadHeightFixSpeed = 0.0f; + Alpha = DEGTORAD(25.0f); + AlphaSpeed = 0.0f; + } + float AlphaTarget = DEGTORAD(25.0f); + if(CCullZones::CamNoRain() || CCullZones::PlayerNoRain()) + AlphaTarget = DEGTORAD(14.0f); + WellBufferMe(AlphaTarget, &Alpha, &AlphaSpeed, 0.1f, 0.05f, true); + Source.z = TargetCoors.z + CA_MAX_DISTANCE*Sin(Alpha); + + if(FindPlayerVehicle()){ + m_fRoadOffSet = 0.0f; + bool FoundRoad = false; + bool FoundRoof = false; + float RoadZ = 0.0f; + float RoofZ = 0.0f; + + if(CWorld::ProcessVerticalLine(Source, -1000.0f, colpoint, ent, true, false, false, false, false, false, nil) && + ent->IsBuilding()){ + FoundRoad = true; + RoadZ = colpoint.point.z; + } + + if(FoundRoad){ + if(Source.z - RoadZ < 0.9f){ + PreviouslyFailedRoadHeightCheck = true; + TargetZOffSet = RoadZ + 0.9f - Source.z; + }else{ + if(m_bCollisionChecksOn) + PreviouslyFailedRoadHeightCheck = false; + else + TargetZOffSet = 0.0f; + } + }else{ + if(CWorld::ProcessVerticalLine(Source, 1000.0f, colpoint, ent, true, false, false, false, false, false, nil) && + ent->IsBuilding()){ + FoundRoof = true; + RoofZ = colpoint.point.z; + } + if(FoundRoof){ + if(Source.z - RoofZ < 0.9f){ + PreviouslyFailedRoadHeightCheck = true; + TargetZOffSet = RoofZ + 0.9f - Source.z; + }else{ + if(m_bCollisionChecksOn) + PreviouslyFailedRoadHeightCheck = false; + else + TargetZOffSet = 0.0f; + } + } + } + } + + if(TargetZOffSet > RoadHeightFix) + RoadHeightFix = TargetZOffSet; + else + WellBufferMe(TargetZOffSet, &RoadHeightFix, &RoadHeightFixSpeed, 0.27f, 0.1f, false); + + if(colpoint.surfaceB != SURFACE_TARMAC && + colpoint.surfaceB != SURFACE_GRASS && + colpoint.surfaceB != SURFACE_GRAVEL && + colpoint.surfaceB != SURFACE_MUD_DRY && + colpoint.surfaceB != SURFACE_PAVEMENT && + colpoint.surfaceB != SURFACE_THICK_METAL_PLATE && + colpoint.surfaceB != SURFACE_STEEP_CLIFF && + RoadHeightFix > 1.4f) + RoadHeightFix = 1.4f; + + Source.z += RoadHeightFix; +} + +void +CCam::WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight) +{ + float AlphaOffset = 0.0f; + bool CamClear = true; + + static float LastTargetAlphaWithCollisionOn = 0.0f; + static float LastTopAlphaSpeed = 0.0f; + static float LastAlphaSpeedStep = 0.0f; + static bool PreviousNearCheckNearClipSmall = false; + + if(ResetStatics){ + LastTargetAlphaWithCollisionOn = 0.0f; + LastTopAlphaSpeed = 0.0f; + LastAlphaSpeedStep = 0.0f; + PreviousNearCheckNearClipSmall = false; + } + + float TopAlphaSpeed = 0.15f; + float AlphaSpeedStep = 0.015f; + + float zoomvalue = TheCamera.CarZoomValueSmooth; + if(zoomvalue < 0.1f) + zoomvalue = 0.1f; + if(TheCamera.CarZoomIndicator == CAM_ZOOM_1) + AlphaOffset = CGeneral::GetATanOfXY(23.0f, zoomvalue); // near + else if(TheCamera.CarZoomIndicator == CAM_ZOOM_2) + AlphaOffset = CGeneral::GetATanOfXY(10.8f, zoomvalue); // mid + else if(TheCamera.CarZoomIndicator == CAM_ZOOM_3) + AlphaOffset = CGeneral::GetATanOfXY(7.0f, zoomvalue); // far + + + float Length = (Source - TargetCoors).Magnitude2D(); + if(m_bCollisionChecksOn){ // there's another variable (on PC) but it's uninitialised + float CarAlpha = CGeneral::GetATanOfXY(CamTargetEntity->GetForward().Magnitude2D(), CamTargetEntity->GetForward().z); + // this shouldn't be necessary.... + while(CarAlpha >= PI) CarAlpha -= 2*PI; + while(CarAlpha < -PI) CarAlpha += 2*PI; + + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + float DeltaBeta = Beta - TargetOrientation; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + + float BehindCarNess = Cos(DeltaBeta); // 1 if behind car, 0 if side, -1 if in front + CarAlpha = -CarAlpha * BehindCarNess; + if(CarAlpha < -0.01f) + CarAlpha = -0.01f; + + float DeltaAlpha = CarAlpha - Alpha; + while(DeltaAlpha >= PI) DeltaAlpha -= 2*PI; + while(DeltaAlpha < -PI) DeltaAlpha += 2*PI; + // What's this?? wouldn't it make more sense to clamp? + float AngleLimit = DEGTORAD(1.8f); + if(DeltaAlpha > AngleLimit) + DeltaAlpha -= AngleLimit; + else if(DeltaAlpha < -AngleLimit) + DeltaAlpha += AngleLimit; + else + DeltaAlpha = 0.0f; + + // Now the collision + + float TargetAlpha = 0.0f; + bool FoundRoofCenter = false; + bool FoundRoofSide1 = false; + bool FoundRoofSide2 = false; + bool FoundCamRoof = false; + bool FoundCamGround = false; + float CamRoof = 0.0f; + float CarBottom = TargetCoors.z - TargetHeight/2.0f; + + // Check car center + float CarRoof = CWorld::FindRoofZFor3DCoord(TargetCoors.x, TargetCoors.y, CarBottom, &FoundRoofCenter); + + // Check sides of the car + CVector Forward = CamTargetEntity->GetForward(); + Forward.Normalise(); // shouldn't be necessary + float CarSideAngle = CGeneral::GetATanOfXY(Forward.x, Forward.y) + PI/2.0f; + float SideX = 2.5f * Cos(CarSideAngle); + float SideY = 2.5f * Sin(CarSideAngle); + CWorld::FindRoofZFor3DCoord(TargetCoors.x + SideX, TargetCoors.y + SideY, CarBottom, &FoundRoofSide1); + CWorld::FindRoofZFor3DCoord(TargetCoors.x - SideX, TargetCoors.y - SideY, CarBottom, &FoundRoofSide2); + + // Now find out at what height we'd like to place the camera + float CamGround = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, TargetCoors.z + Length*Sin(Alpha + AlphaOffset) + m_fCloseInCarHeightOffset, &FoundCamGround); + float CamTargetZ = 0.0f; + if(FoundCamGround){ + // This is the normal case + CamRoof = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, CamGround + TargetHeight, &FoundCamRoof); + CamTargetZ = CamGround + TargetHeight*1.5f + 0.1f; + }else{ + FoundCamRoof = false; + CamTargetZ = TargetCoors.z; + } + + if(FoundRoofCenter && !FoundCamRoof && (FoundRoofSide1 || FoundRoofSide2)){ + // Car is under something but camera isn't + // This seems weird... + TargetAlpha = CGeneral::GetATanOfXY(CA_MAX_DISTANCE, CarRoof - CamTargetZ - 1.5f); + CamClear = false; + } + if(FoundCamRoof){ + // Camera is under something + float roof = FoundRoofCenter ? Min(CamRoof, CarRoof) : CamRoof; + // Same weirdness again? + TargetAlpha = CGeneral::GetATanOfXY(CA_MAX_DISTANCE, roof - CamTargetZ - 1.5f); + CamClear = false; + } + while(TargetAlpha >= PI) TargetAlpha -= 2*PI; + while(TargetAlpha < -PI) TargetAlpha += 2*PI; + if(TargetAlpha < DEGTORAD(-7.0f)) + TargetAlpha = DEGTORAD(-7.0f); + + // huh? + if(TargetAlpha > AlphaOffset) + CamClear = true; + // Camera is constrained by collision in some way + PreviousNearCheckNearClipSmall = false; + if(!CamClear){ + PreviousNearCheckNearClipSmall = true; + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + + DeltaAlpha = TargetAlpha - (Alpha + AlphaOffset); + while(DeltaAlpha >= PI) DeltaAlpha -= 2*PI; + while(DeltaAlpha < -PI) DeltaAlpha += 2*PI; + + TopAlphaSpeed = 0.3f; + AlphaSpeedStep = 0.03f; + } + + // Now do things if CamClear...but what is that anyway? + float CamZ = TargetCoors.z + Length*Sin(Alpha + DeltaAlpha + AlphaOffset) + m_fCloseInCarHeightOffset; + bool FoundGround, FoundRoof; + float CamGround2 = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, CamZ, &FoundGround); + if(FoundGround && CamClear){ + if(CamZ - CamGround2 < 1.5f){ + PreviousNearCheckNearClipSmall = true; + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + + float dz = CamGround2 + 1.5f - TargetCoors.z; + float a; + if(Length == 0.0f || dz == 0.0f) + a = Alpha; + else + a = CGeneral::GetATanOfXY(Length, dz); + while(a > PI) a -= 2*PI; + while(a < -PI) a += 2*PI; + DeltaAlpha = a - Alpha; + } + }else if(CamClear){ + float CamRoof2 = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, CamZ, &FoundRoof); + if(FoundRoof && CamZ - CamRoof2 < 1.5f){ + PreviousNearCheckNearClipSmall = true; + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + + if(CamRoof2 > TargetCoors.z + 3.5f) + CamRoof2 = TargetCoors.z + 3.5f; + + float dz = CamRoof2 + 1.5f - TargetCoors.z; + float a; + if(Length == 0.0f || dz == 0.0f) + a = Alpha; + else + a = CGeneral::GetATanOfXY(Length, dz); + while(a > PI) a -= 2*PI; + while(a < -PI) a += 2*PI; + DeltaAlpha = a - Alpha; + } + } + + LastTargetAlphaWithCollisionOn = DeltaAlpha + Alpha; + LastTopAlphaSpeed = TopAlphaSpeed; + LastAlphaSpeedStep = AlphaSpeedStep; + }else{ + if(PreviousNearCheckNearClipSmall) + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + } + + WellBufferMe(LastTargetAlphaWithCollisionOn, &Alpha, &AlphaSpeed, LastTopAlphaSpeed, LastAlphaSpeedStep, true); + + Source.z = TargetCoors.z + Sin(Alpha + AlphaOffset)*Length + m_fCloseInCarHeightOffset; +} + +// Rotate cam behind the car when the car is moving forward +bool +CCam::RotCamIfInFrontCar(CVector &TargetCoors, float TargetOrientation) +{ + bool MovingForward = false; + CPhysical *phys = (CPhysical*)CamTargetEntity; + + float ForwardSpeed = DotProduct(phys->GetForward(), phys->GetSpeed(CVector(0.0f, 0.0f, 0.0f))); + if(ForwardSpeed > 0.02f) + MovingForward = true; + + float Dist = (Source - TargetCoors).Magnitude2D(); + + float DeltaBeta = TargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + + if(Abs(DeltaBeta) > DEGTORAD(20.0f) && MovingForward && TheCamera.m_uiTransitionState == 0) + m_bFixingBeta = true; + + CPad *pad = CPad::GetPad(0); + if(!(pad->GetLookBehindForCar() || pad->GetLookBehindForPed() || pad->GetLookLeft() || pad->GetLookRight())) + if(DirectionWasLooking != LOOKING_FORWARD) + TheCamera.m_bCamDirectlyBehind = true; + + if(!m_bFixingBeta && !TheCamera.m_bUseTransitionBeta && !TheCamera.m_bCamDirectlyBehind && !TheCamera.m_bCamDirectlyInFront) + return false; + + bool SetBeta = false; + if(TheCamera.m_bCamDirectlyBehind || TheCamera.m_bCamDirectlyInFront || TheCamera.m_bUseTransitionBeta) + if(&TheCamera.Cams[TheCamera.ActiveCam] == this) + SetBeta = true; + + if(m_bFixingBeta || SetBeta){ + WellBufferMe(TargetOrientation, &Beta, &BetaSpeed, 0.15f, 0.007f, true); + + if(TheCamera.m_bCamDirectlyBehind && &TheCamera.Cams[TheCamera.ActiveCam] == this) + Beta = TargetOrientation; + if(TheCamera.m_bCamDirectlyInFront && &TheCamera.Cams[TheCamera.ActiveCam] == this) + Beta = TargetOrientation + PI; + if(TheCamera.m_bUseTransitionBeta && &TheCamera.Cams[TheCamera.ActiveCam] == this) + Beta = m_fTransitionBeta; + + Source.x = TargetCoors.x - Cos(Beta)*Dist; + Source.y = TargetCoors.y - Sin(Beta)*Dist; + + // Check if we're done + DeltaBeta = TargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < DEGTORAD(2.0f)) + m_bFixingBeta = false; + } + TheCamera.m_bCamDirectlyBehind = false; + TheCamera.m_bCamDirectlyInFront = false; + return true; +} + +// Move the cam to avoid clipping through buildings +bool +CCam::FixCamIfObscured(CVector &TargetCoors, float TargetHeight, float TargetOrientation) +{ + CVector Target = TargetCoors; + bool UseEntityPos = false; + CVector EntityPos; + static CColPoint colPoint; + static bool LastObscured = false; + + if(Mode == MODE_BEHINDCAR) + Target.z += TargetHeight/2.0f; + if(Mode == MODE_CAM_ON_A_STRING){ + UseEntityPos = true; + Target.z += TargetHeight/2.0f; + EntityPos = CamTargetEntity->GetPosition(); + } + + CVector TempSource = Source; + + bool Obscured1 = false; + bool Obscured2 = false; + bool Fix1 = false; + float Dist1 = 0.0f; + float Dist2 = 0.0f; + CEntity *ent; + if(m_bCollisionChecksOn || LastObscured){ + Obscured1 = CWorld::ProcessLineOfSight(Target, TempSource, colPoint, ent, true, false, false, true, false, true, true); + if(Obscured1){ + Dist1 = (Target - colPoint.point).Magnitude2D(); + Fix1 = true; + if(UseEntityPos) + Obscured1 = CWorld::ProcessLineOfSight(EntityPos, TempSource, colPoint, ent, true, false, false, true, false, true, true); + }else if(m_bFixingBeta){ + float d = (TempSource - Target).Magnitude(); + TempSource.x = Target.x - d*Cos(TargetOrientation); + TempSource.y = Target.y - d*Sin(TargetOrientation); + + // same check again + Obscured2 = CWorld::ProcessLineOfSight(Target, TempSource, colPoint, ent, true, false, false, true, false, true, true); + if(Obscured2){ + Dist2 = (Target - colPoint.point).Magnitude2D(); + if(UseEntityPos) + Obscured2 = CWorld::ProcessLineOfSight(EntityPos, TempSource, colPoint, ent, true, false, false, true, false, true, true); + } + } + LastObscured = Obscured1 || Obscured2; + } + + // nothing to do + if(!LastObscured) + return false; + + if(Fix1){ + Source.x = Target.x - Cos(Beta)*Dist1; + Source.y = Target.y - Sin(Beta)*Dist1; + if(Mode == MODE_BEHINDCAR) + Source = colPoint.point; + }else{ + WellBufferMe(Dist2, &m_fDistanceBeforeChanges, &DistanceSpeed, 0.2f, 0.025f, false); + Source.x = Target.x - Cos(Beta)*m_fDistanceBeforeChanges; + Source.y = Target.y - Sin(Beta)*m_fDistanceBeforeChanges; + } + + if(ResetStatics){ + m_fDistanceBeforeChanges = (Source - Target).Magnitude2D(); + DistanceSpeed = 0.0f; + Source.x = colPoint.point.x; + Source.y = colPoint.point.y; + } + return true; +} + +void +CCam::Process_Cam_On_A_String(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsVehicle()) + return; + + FOV = DefaultFOV; + + if(ResetStatics){ + AlphaSpeed = 0.0f; + if(TheCamera.m_bIdleOn) + TheCamera.m_uiTimeWeEnteredIdle = CTimer::GetTimeInMilliseconds(); + } + + CBaseModelInfo *mi = CModelInfo::GetModelInfo(CamTargetEntity->GetModelIndex()); + CVector Dimensions = mi->GetColModel()->boundingBox.max - mi->GetColModel()->boundingBox.min; + CVector TargetCoors = CameraTarget; + float BaseDist = Dimensions.Magnitude2D(); + + TargetCoors.z += Dimensions.z - 0.1f; // final + Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y); + while(Alpha >= PI) Alpha -= 2*PI; + while(Alpha < -PI) Alpha += 2*PI; + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + m_fDistanceBeforeChanges = (Source - TargetCoors).Magnitude2D(); + + Cam_On_A_String_Unobscured(TargetCoors, BaseDist); + WorkOutCamHeight(TargetCoors, TargetOrientation, Dimensions.z); + RotCamIfInFrontCar(TargetCoors, TargetOrientation); + FixCamIfObscured(TargetCoors, Dimensions.z, TargetOrientation); + FixCamWhenObscuredByVehicle(TargetCoors); + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + Front.Normalise(); + GetVectorsReadyForRW(); + ResetStatics = false; +} + +// Basic Cam on a string algorithm +void +CCam::Cam_On_A_String_Unobscured(const CVector &TargetCoors, float BaseDist) +{ + CA_MAX_DISTANCE = BaseDist + 0.1f + TheCamera.CarZoomValueSmooth; + CA_MIN_DISTANCE = Min(BaseDist*0.6f, 3.5f); + + CVector Dist = Source - TargetCoors; + + if(ResetStatics) + Source = TargetCoors + Dist*(CA_MAX_DISTANCE + 1.0f); + + Dist = Source - TargetCoors; + + float Length = Dist.Magnitude2D(); + if(Length < 0.001f){ + // This probably shouldn't happen. reset view + CVector Forward = CamTargetEntity->GetForward(); + Forward.z = 0.0f; + Forward.Normalise(); + Source = TargetCoors - Forward*CA_MAX_DISTANCE; + Dist = Source - TargetCoors; + Length = Dist.Magnitude2D(); + } + + if(Length > CA_MAX_DISTANCE){ + Source.x = TargetCoors.x + Dist.x/Length * CA_MAX_DISTANCE; + Source.y = TargetCoors.y + Dist.y/Length * CA_MAX_DISTANCE; + }else if(Length < CA_MIN_DISTANCE){ + Source.x = TargetCoors.x + Dist.x/Length * CA_MIN_DISTANCE; + Source.y = TargetCoors.y + Dist.y/Length * CA_MIN_DISTANCE; + } +} + +void +CCam::FixCamWhenObscuredByVehicle(const CVector &TargetCoors) +{ + // BUG? is this never reset + static float HeightFixerCarsObscuring = 0.0f; + static float HeightFixerCarsObscuringSpeed = 0.0f; + CColPoint colPoint; + CEntity *entity = nil; + + float HeightTarget = 0.0f; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, false, true, false, false, false, false, false)){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(entity->GetModelIndex()); + HeightTarget = mi->GetColModel()->boundingBox.max.z + 1.0f + TargetCoors.z - Source.z; + if(HeightTarget < 0.0f) + HeightTarget = 0.0f; + } + WellBufferMe(HeightTarget, &HeightFixerCarsObscuring, &HeightFixerCarsObscuringSpeed, 0.2f, 0.025f, false); + Source.z += HeightFixerCarsObscuring; +} + +void +CCam::Process_TopDown(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsVehicle()) + return; + + float Dist; + float HeightTarget = 0.0f; + static float AdjustHeightTargetMoveBuffer = 0.0f; + static float AdjustHeightTargetMoveSpeed = 0.0f; + static float NearClipDistance = 1.5f; + const float FarClipDistance = 200.0f; + CVector TargetFront, Target; + CVector TestSource, TestTarget; + CColPoint colPoint; + CEntity *entity; + + TargetFront = CameraTarget; + TargetFront.x += 18.0f*CamTargetEntity->GetForward().x*SpeedVar; + TargetFront.y += 18.0f*CamTargetEntity->GetForward().y*SpeedVar; + + if(ResetStatics){ + AdjustHeightTargetMoveBuffer = 0.0f; + AdjustHeightTargetMoveSpeed = 0.0f; + } + + float f = Pow(0.8f, 4.0f); + Target = f*CameraTarget + (1.0f-f)*TargetFront; + if(Mode == MODE_GTACLASSIC) + SpeedVar = TargetSpeedVar; + Source = Target + CVector(0.0f, 0.0f, (40.0f*SpeedVar + 30.0f)*0.8f); + // What is this? looks horrible + if(Mode == MODE_GTACLASSIC) + Source.x += (uint8)(100.0f*CameraTarget.x)/500.0f; + + TestSource = Source; + TestTarget = TestSource; + TestTarget.z = Target.z; + if(CWorld::ProcessLineOfSight(TestTarget, TestSource, colPoint, entity, true, false, false, false, false, false, false)){ + if(Source.z < colPoint.point.z+3.0f) + HeightTarget = colPoint.point.z+3.0f - Source.z; + }else{ + TestSource = Source; + TestTarget = TestSource; + TestTarget.z += 10.0f; + if(CWorld::ProcessLineOfSight(TestTarget, TestSource, colPoint, entity, true, false, false, false, false, false, false)) + if(Source.z < colPoint.point.z+3.0f) + HeightTarget = colPoint.point.z+3.0f - Source.z; + } + WellBufferMe(HeightTarget, &AdjustHeightTargetMoveBuffer, &AdjustHeightTargetMoveSpeed, 0.2f, 0.02f, false); + Source.z += AdjustHeightTargetMoveBuffer; + + if(RwCameraGetFarClipPlane(Scene.camera) > FarClipDistance) + RwCameraSetFarClipPlane(Scene.camera, FarClipDistance); + RwCameraSetNearClipPlane(Scene.camera, NearClipDistance); + + Front = CVector(-0.01f, -0.01f, -1.0f); // look down + Front.Normalise(); + Dist = (Source - CameraTarget).Magnitude(); + m_cvecTargetCoorsForFudgeInter = Dist*Front + Source; + Up = CVector(0.0f, 1.0f, 0.0f); + + ResetStatics = false; +} + +void +CCam::AvoidWallsTopDownPed(const CVector &TargetCoors, const CVector &Offset, float *Adjuster, float *AdjusterSpeed, float yDistLimit) +{ + float Target = 0.0f; + float MaxSpeed = 0.13f; + float Acceleration = 0.015f; + float SpeedMult; + float dy; + CVector TestPoint2; + CVector TestPoint1; + CColPoint colPoint; + CEntity *entity; + + TestPoint2 = TargetCoors + Offset; + TestPoint1 = TargetCoors; + TestPoint1.z = TestPoint2.z; + if(CWorld::ProcessLineOfSight(TestPoint1, TestPoint2, colPoint, entity, true, false, false, false, false, false, false)){ + // What is this even? + dy = TestPoint1.y - colPoint.point.y; + if(dy > yDistLimit) + dy = yDistLimit; + SpeedMult = yDistLimit - Abs(dy/yDistLimit); + + Target = 2.5f; + MaxSpeed += SpeedMult*0.3f; + Acceleration += SpeedMult*0.03f; + } + WellBufferMe(Target, Adjuster, AdjusterSpeed, MaxSpeed, Acceleration, false); +} + +void +CCam::Process_TopDownPed(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + float Dist; + float HeightTarget; + static int NumPedPosCountsSoFar = 0; + static float PedAverageSpeed = 0.0f; + static float AdjustHeightTargetMoveBuffer = 0.0f; + static float AdjustHeightTargetMoveSpeed = 0.0f; + static float PedSpeedSoFar = 0.0f; + static float FarClipDistance = 200.0f; + static float NearClipDistance = 1.5f; + static float TargetAdjusterForSouth = 0.0f; + static float TargetAdjusterSpeedForSouth = 0.0f; + static float TargetAdjusterForNorth = 0.0f; + static float TargetAdjusterSpeedForNorth = 0.0f; + static float TargetAdjusterForEast = 0.0f; + static float TargetAdjusterSpeedForEast = 0.0f; + static float TargetAdjusterForWest = 0.0f; + static float TargetAdjusterSpeedForWest = 0.0f; + static CVector PreviousPlayerMoveSpeedVec; + CVector TargetCoors, PlayerMoveSpeed; + CVector TestSource, TestTarget; + CColPoint colPoint; + CEntity *entity; + + FOV = DefaultFOV; + TargetCoors = CameraTarget; + PlayerMoveSpeed = ((CPed*)CamTargetEntity)->GetMoveSpeed(); + + if(ResetStatics){ + PreviousPlayerMoveSpeedVec = PlayerMoveSpeed; + AdjustHeightTargetMoveBuffer = 0.0f; + AdjustHeightTargetMoveSpeed = 0.0f; + NumPedPosCountsSoFar = 0; + PedSpeedSoFar = 0.0f; + PedAverageSpeed = 0.0f; + TargetAdjusterForWest = 0.0f; + TargetAdjusterSpeedForWest = 0.0f; + TargetAdjusterForEast = 0.0f; + TargetAdjusterSpeedForEast = 0.0f; + TargetAdjusterForNorth = 0.0f; + TargetAdjusterSpeedForNorth = 0.0f; + TargetAdjusterForSouth = 0.0f; + TargetAdjusterSpeedForSouth = 0.0f; + } + + if(RwCameraGetFarClipPlane(Scene.camera) > FarClipDistance) + RwCameraSetFarClipPlane(Scene.camera, FarClipDistance); + RwCameraSetNearClipPlane(Scene.camera, NearClipDistance); + + // Average ped speed + NumPedPosCountsSoFar++; + PedSpeedSoFar += PlayerMoveSpeed.Magnitude(); + if(NumPedPosCountsSoFar == 5){ + PedAverageSpeed = 0.4f*PedAverageSpeed + 0.6*(PedSpeedSoFar/5.0f); + NumPedPosCountsSoFar = 0; + PedSpeedSoFar = 0.0f; + } + PreviousPlayerMoveSpeedVec = PlayerMoveSpeed; + + // Zoom out depending on speed + if(PedAverageSpeed > 0.01f && PedAverageSpeed <= 0.04f) + HeightTarget = 2.5f; + else if(PedAverageSpeed > 0.04f && PedAverageSpeed <= 0.145f) + HeightTarget = 4.5f; + else if(PedAverageSpeed > 0.145f) + HeightTarget = 7.0f; + else + HeightTarget = 0.0f; + + // Zoom out if locked on target is far away + if(FindPlayerPed()->m_pPointGunAt){ + Dist = (FindPlayerPed()->m_pPointGunAt->GetPosition() - CameraTarget).Magnitude2D(); + if(Dist > 6.0f) + HeightTarget = Max(HeightTarget, Dist/22.0f*37.0f); + } + + Source = TargetCoors + CVector(0.0f, -1.0f, 9.0f); + + // Collision checks + entity = nil; + TestSource = TargetCoors + CVector(0.0f, -1.0f, 9.0f); + TestTarget = TestSource; + TestTarget.z = TargetCoors.z; + if(CWorld::ProcessLineOfSight(TestTarget, TestSource, colPoint, entity, true, false, false, false, false, false, false)){ + if(TargetCoors.z+9.0f+HeightTarget < colPoint.point.z+3.0f) + HeightTarget = colPoint.point.z+3.0f - (TargetCoors.z+9.0f); + }else{ + TestSource = TargetCoors + CVector(0.0f, -1.0f, 9.0f); + TestTarget = TestSource; + TestSource.z += HeightTarget; + TestTarget.z = TestSource.z + 10.0f; + if(CWorld::ProcessLineOfSight(TestTarget, TestSource, colPoint, entity, true, false, false, false, false, false, false)){ + if(TargetCoors.z+9.0f+HeightTarget < colPoint.point.z+3.0f) + HeightTarget = colPoint.point.z+3.0f - (TargetCoors.z+9.0f); + } + } + + WellBufferMe(HeightTarget, &AdjustHeightTargetMoveBuffer, &AdjustHeightTargetMoveSpeed, 0.3f, 0.03f, false); + Source.z += AdjustHeightTargetMoveBuffer; + + // Wall checks + AvoidWallsTopDownPed(TargetCoors, CVector(0.0f, -3.0f, 3.0f), &TargetAdjusterForSouth, &TargetAdjusterSpeedForSouth, 1.0f); + Source.y += TargetAdjusterForSouth; + AvoidWallsTopDownPed(TargetCoors, CVector(0.0f, 3.0f, 3.0f), &TargetAdjusterForNorth, &TargetAdjusterSpeedForNorth, 1.0f); + Source.y -= TargetAdjusterForNorth; + // BUG: east and west flipped + AvoidWallsTopDownPed(TargetCoors, CVector(3.0f, 0.0f, 3.0f), &TargetAdjusterForWest, &TargetAdjusterSpeedForWest, 1.0f); + Source.x -= TargetAdjusterForWest; + AvoidWallsTopDownPed(TargetCoors, CVector(-3.0f, 0.0f, 3.0f), &TargetAdjusterForEast, &TargetAdjusterSpeedForEast, 1.0f); + Source.x += TargetAdjusterForEast; + + TargetCoors.y = Source.y + 1.0f; + TargetCoors.y += TargetAdjusterForSouth; + TargetCoors.x += TargetAdjusterForEast; + TargetCoors.x -= TargetAdjusterForWest; + + Front = TargetCoors - Source; + Front.Normalise(); +#ifdef FIX_BUGS + if(Front.x == 0.0f && Front.y == 0.0f) + Front.y = 0.0001f; +#else + // someone used = instead of == in the above check by accident + Front.x = 0.0f; +#endif + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Up = CrossProduct(Front, CVector(-1.0f, 0.0f, 0.0f)); + Up.Normalise(); + + ResetStatics = false; +} + +// Identical to M16 +void +CCam::Process_Rocket(const CVector &CameraTarget, float, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + static bool FailedTestTwelveFramesAgo = false; + RwV3d HeadPos; + CVector TargetCoors; + + FOV = DefaultFOV; + TargetCoors = CameraTarget; + + if(ResetStatics){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + FailedTestTwelveFramesAgo = false; + // static DPadVertical unused + // static DPadHorizontal unused + m_bCollisionChecksOn = true; + ResetStatics = false; + } + + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f*Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f*Sin(m_fInitialPlayerOrientation); + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if(MouseX != 0.0f || MouseY != 0.0f){ + UseMouse = true; + LookLeftRight = -3.0f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->SniperModeLookLeftRight(); + LookUpDown = CPad::GetPad(0)->SniperModeLookUpDown(); + } + if(UseMouse){ + Beta += TheCamera.m_fMouseAccelHorzntl * LookLeftRight * FOV/80.0f; + Alpha += TheCamera.m_fMouseAccelVertical * LookUpDown * FOV/80.0f; + }else{ + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + } + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + if(m_bCollisionChecksOn){ + if(!CWorld::GetIsLineOfSightClear(TargetCoors, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + CVector TestPoint; + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta + DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta + DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta - DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta - DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else + FailedTestTwelveFramesAgo = false; + } + } + } + + if(FailedTestTwelveFramesAgo) + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source -= Front*0.4f; + + GetVectorsReadyForRW(); + float Rotation = CGeneral::GetATanOfXY(Front.x, Front.y) - HALFPI; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Rotation; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Rotation; +} + +// Identical to Rocket +void +CCam::Process_M16_1stPerson(const CVector &CameraTarget, float, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + static bool FailedTestTwelveFramesAgo = false; + RwV3d HeadPos; + CVector TargetCoors; + + FOV = DefaultFOV; + TargetCoors = CameraTarget; + + if(ResetStatics){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + FailedTestTwelveFramesAgo = false; + // static DPadVertical unused + // static DPadHorizontal unused + m_bCollisionChecksOn = true; + ResetStatics = false; + } + +#if GTA_VERSION < GTA3_PC_11 + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f*Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f*Sin(m_fInitialPlayerOrientation); +#endif + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if(MouseX != 0.0f || MouseY != 0.0f){ + UseMouse = true; + LookLeftRight = -3.0f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->SniperModeLookLeftRight(); + LookUpDown = CPad::GetPad(0)->SniperModeLookUpDown(); + } + if(UseMouse){ + Beta += TheCamera.m_fMouseAccelHorzntl * LookLeftRight * FOV/80.0f; + Alpha += TheCamera.m_fMouseAccelVertical * LookUpDown * FOV/80.0f; + }else{ + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + } + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + +#if GTA_VERSION >= GTA3_PC_11 + HeadPos.x = 0.0f; + HeadPos.y = 0.0f; + HeadPos.z = 0.0f; + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f * Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f * Sin(m_fInitialPlayerOrientation); +#endif + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + if(m_bCollisionChecksOn){ + if(!CWorld::GetIsLineOfSightClear(TargetCoors, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + CVector TestPoint; + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta + DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta + DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta - DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta - DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else + FailedTestTwelveFramesAgo = false; + } + } + } + + if(FailedTestTwelveFramesAgo) + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source -= Front*0.4f; + + GetVectorsReadyForRW(); + float Rotation = CGeneral::GetATanOfXY(Front.x, Front.y) - HALFPI; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Rotation; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Rotation; +} + +void +CCam::Process_1stPerson(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float DontLookThroughWorldFixer = 0.0f; + CVector TargetCoors; + + FOV = DefaultFOV; + TargetCoors = CameraTarget; + if(CamTargetEntity->m_rwObject == nil) + return; + + if(ResetStatics){ + Beta = TargetOrientation; + Alpha = 0.0f; + m_fInitialPlayerOrientation = TargetOrientation; + if(CamTargetEntity->IsPed()){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + } + DontLookThroughWorldFixer = 0.0f; + } + + if(CamTargetEntity->IsPed()){ + static bool FailedTestTwelveFramesAgo = false; + RwV3d HeadPos; + + TargetCoors = CameraTarget; + + if(ResetStatics){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + FailedTestTwelveFramesAgo = false; + // static DPadVertical unused + // static DPadHorizontal unused + m_bCollisionChecksOn = true; + ResetStatics = false; + } + + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f*Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f*Sin(m_fInitialPlayerOrientation); + + float LookLeftRight, LookUpDown; + LookLeftRight = -CPad::GetPad(0)->LookAroundLeftRight(); + LookUpDown = CPad::GetPad(0)->LookAroundUpDown(); + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + if(m_bCollisionChecksOn){ + if(!CWorld::GetIsLineOfSightClear(TargetCoors, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + CVector TestPoint; + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta + DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta + DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta - DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta - DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else + FailedTestTwelveFramesAgo = false; + } + } + } + + if(FailedTestTwelveFramesAgo) + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source -= Front*0.4f; + + GetVectorsReadyForRW(); + float Rotation = CGeneral::GetATanOfXY(Front.x, Front.y) - HALFPI; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Rotation; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Rotation; + }else{ + assert(CamTargetEntity->IsVehicle()); + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(CamTargetEntity->GetModelIndex()); + CVector CamPos = mi->GetFrontSeatPosn(); + CamPos.x = 0.0f; + CamPos.y += 0.08f; + CamPos.z += 0.62f; + FOV = 60.0f; + Source = Multiply3x3(CamTargetEntity->GetMatrix(), CamPos); + Source += CamTargetEntity->GetPosition(); + if(((CVehicle*)CamTargetEntity)->IsBoat()) + Source.z += 0.5f; + + if(((CVehicle*)CamTargetEntity)->IsUpsideDown()){ + if(DontLookThroughWorldFixer < 0.5f) + DontLookThroughWorldFixer += 0.03f; + else + DontLookThroughWorldFixer = 0.5f; + }else{ + if(DontLookThroughWorldFixer < 0.0f) +#ifdef FIX_BUGS + DontLookThroughWorldFixer += 0.03f; +#else + DontLookThroughWorldFixer -= 0.03f; +#endif + else + DontLookThroughWorldFixer = 0.0f; + } + Source.z += DontLookThroughWorldFixer; + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + CVector Right = CrossProduct(Front, Up); + Right.Normalise(); + Up = CrossProduct(Right, Front); + Up.Normalise(); + } + + ResetStatics = false; +} + +static CVector vecHeadCamOffset(0.06f, 0.05f, 0.0f); + +void +CCam::Process_1rstPersonPedOnPC(const CVector&, float TargetOrientation, float, float) +{ + // static int DontLookThroughWorldFixer = 0; // unused + static CVector InitialHeadPos; + + if(Mode != MODE_SNIPER_RUNABOUT) + FOV = DefaultFOV; + TheCamera.m_1rstPersonRunCloseToAWall = false; + if(CamTargetEntity->m_rwObject == nil) + return; + + if(CamTargetEntity->IsPed()){ + // static bool FailedTestTwelveFramesAgo = false; // unused + CVector HeadPos = vecHeadCamOffset; + CVector TargetCoors; + + ((CPed*)CamTargetEntity)->TransformToNode(HeadPos, PED_HEAD); + // This is done on PC, but checking for the clump frame is not necessary apparently +/* + RwFrame *frm = ((CPed*)CamTargetEntity)->m_pFrames[PED_HEAD]->frame; + while(frm){ + RwV3dTransformPoints(&HeadPos, &HeadPos, 1, RwFrameGetMatrix(frm)); + frm = RwFrameGetParent(frm); + if(frm == RpClumpGetFrame(CamTargetEntity->GetClump())) + frm = nil; + } +*/ + + if(ResetStatics){ + Beta = TargetOrientation; + Alpha = 0.0f; + m_fInitialPlayerOrientation = TargetOrientation; + if(CamTargetEntity->IsPed()){ // useless check + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + // FailedTestTwelveFramesAgo = false; + m_bCollisionChecksOn = true; + } + // DontLookThroughWorldFixer = false; + m_vecBufferedPlayerBodyOffset = HeadPos; + InitialHeadPos = HeadPos; + } + + m_vecBufferedPlayerBodyOffset.y = HeadPos.y; + + if(TheCamera.m_bHeadBob){ + m_vecBufferedPlayerBodyOffset.x = + TheCamera.m_fGaitSwayBuffer * m_vecBufferedPlayerBodyOffset.x + + (1.0f-TheCamera.m_fGaitSwayBuffer) * HeadPos.x; + m_vecBufferedPlayerBodyOffset.z = + TheCamera.m_fGaitSwayBuffer * m_vecBufferedPlayerBodyOffset.z + + (1.0f-TheCamera.m_fGaitSwayBuffer) * HeadPos.z; + HeadPos = (CamTargetEntity->GetMatrix() * m_vecBufferedPlayerBodyOffset); + }else{ + float HeadDelta = (HeadPos - InitialHeadPos).Magnitude2D(); + CVector Fwd = CamTargetEntity->GetForward(); + Fwd.z = 0.0f; + Fwd.Normalise(); + HeadPos = HeadDelta*1.23f*Fwd + CamTargetEntity->GetPosition(); + HeadPos.z += 0.59f; + } + Source = HeadPos; + + // unused: + // ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(MidPos, PED_MID); + // Source - MidPos; + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if(MouseX != 0.0f || MouseY != 0.0f){ + UseMouse = true; + LookLeftRight = -3.0f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->LookAroundLeftRight(); + LookUpDown = CPad::GetPad(0)->LookAroundUpDown(); + } + if(UseMouse){ + Beta += TheCamera.m_fMouseAccelHorzntl * LookLeftRight * FOV/80.0f; + Alpha += TheCamera.m_fMouseAccelVertical * LookUpDown * FOV/80.0f; + }else{ + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + } + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + TheCamera.m_AlphaForPlayerAnim1rstPerson = Alpha; + + GetVectorsReadyForRW(); + + float Heading = Front.Heading(); + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Heading; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Heading; + TheCamera.pTargetEntity->SetHeading(Heading); + TheCamera.pTargetEntity->GetMatrix().UpdateRW(); + + if(Mode == MODE_SNIPER_RUNABOUT){ + // no mouse wheel FOV buffering here like in normal sniper mode + if(CPad::GetPad(0)->SniperZoomIn() || CPad::GetPad(0)->SniperZoomOut()){ + if(CPad::GetPad(0)->SniperZoomOut()) + FOV *= (255.0f*CTimer::GetTimeStep() + 10000.0f) / 10000.0f; + else + FOV /= (255.0f*CTimer::GetTimeStep() + 10000.0f) / 10000.0f; + } + + TheCamera.SetMotionBlur(180, 255, 180, 120, MOTION_BLUR_SNIPER); + + if(FOV > DefaultFOV) + FOV = DefaultFOV; + if(FOV < 15.0f) + FOV = 15.0f; + } + } + + ResetStatics = false; + RwCameraSetNearClipPlane(Scene.camera, 0.05f); +} + +void +CCam::Process_Sniper(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + static bool FailedTestTwelveFramesAgo = false; + RwV3d HeadPos; + CVector TargetCoors; + TargetCoors = CameraTarget; + + static float TargetFOV = 0.0f; + + if(ResetStatics){ + Beta = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + Alpha = 0.0f; + m_fInitialPlayerOrientation = ((CPed*)CamTargetEntity)->m_fRotationCur + HALFPI; + FailedTestTwelveFramesAgo = false; + // static DPadVertical unused + // static DPadHorizontal unused + m_bCollisionChecksOn = true; + FOVSpeed = 0.0f; + TargetFOV = FOV; + ResetStatics = false; + } + + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= 0.19f*Cos(m_fInitialPlayerOrientation); + Source.y -= 0.19f*Sin(m_fInitialPlayerOrientation); + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; + if(MouseX != 0.0f || MouseY != 0.0f){ + UseMouse = true; + LookLeftRight = -3.0f*MouseX; + LookUpDown = 4.0f*MouseY; + }else{ + LookLeftRight = -CPad::GetPad(0)->SniperModeLookLeftRight(); + LookUpDown = CPad::GetPad(0)->SniperModeLookUpDown(); + } + if(UseMouse){ + Beta += TheCamera.m_fMouseAccelHorzntl * LookLeftRight * FOV/80.0f; + Alpha += TheCamera.m_fMouseAccelVertical * LookUpDown * FOV/80.0f; + }else{ + float xdir = LookLeftRight < 0.0f ? -1.0f : 1.0f; + float ydir = LookUpDown < 0.0f ? -1.0f : 1.0f; + Beta += SQR(LookLeftRight/100.0f)*xdir*0.8f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += SQR(LookUpDown/150.0f)*ydir*1.0f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + } + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + TargetCoors.x = 3.0f * Cos(Alpha) * Cos(Beta) + Source.x; + TargetCoors.y = 3.0f * Cos(Alpha) * Sin(Beta) + Source.y; + TargetCoors.z = 3.0f * Sin(Alpha) + Source.z; + + UseMouse = false; + int ZoomInButton = ControlsManager.GetMouseButtonAssociatedWithAction(PED_SNIPER_ZOOM_IN); + int ZoomOutButton = ControlsManager.GetMouseButtonAssociatedWithAction(PED_SNIPER_ZOOM_OUT); + if(ZoomInButton == rsMOUSEWHEELUPBUTTON || ZoomInButton == rsMOUSEWHEELDOWNBUTTON || ZoomOutButton == rsMOUSEWHEELUPBUTTON || ZoomOutButton == rsMOUSEWHEELDOWNBUTTON){ + if(CPad::GetPad(0)->GetMouseWheelUp() || CPad::GetPad(0)->GetMouseWheelDown()){ + if(CPad::GetPad(0)->SniperZoomIn()){ + TargetFOV = FOV - 10.0f; + UseMouse = true; + } + if(CPad::GetPad(0)->SniperZoomOut()){ + TargetFOV = FOV + 10.0f; + UseMouse = true; + } + } + } + if((CPad::GetPad(0)->SniperZoomIn() || CPad::GetPad(0)->SniperZoomOut()) && !UseMouse){ + if(CPad::GetPad(0)->SniperZoomOut()){ + FOV *= (255.0f*CTimer::GetTimeStep() + 10000.0f) / 10000.0f; + TargetFOV = FOV; + FOVSpeed = 0.0f; + }else{ + FOV /= (255.0f*CTimer::GetTimeStep() + 10000.0f) / 10000.0f; + TargetFOV = FOV; + FOVSpeed = 0.0f; + } + }else{ + if(Abs(TargetFOV - FOV) > 0.5f) + WellBufferMe(TargetFOV, &FOV, &FOVSpeed, 0.5f, 0.25f, false); + else + FOVSpeed = 0.0f; + } + + TheCamera.SetMotionBlur(180, 255, 180, 120, MOTION_BLUR_SNIPER); + + if(FOV > DefaultFOV) + FOV = DefaultFOV; + if(FOV < 15.0f) + FOV = 15.0f; + + Front = TargetCoors - Source; + Front.Normalise(); + Source += Front*0.4f; + + if(m_bCollisionChecksOn){ + if(!CWorld::GetIsLineOfSightClear(TargetCoors, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + CVector TestPoint; + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta + DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta + DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else{ + TestPoint.x = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Cos(Beta - DEGTORAD(35.0f)) + Source.x; + TestPoint.y = 3.0f * Cos(Alpha - DEGTORAD(20.0f)) * Sin(Beta - DEGTORAD(35.0f)) + Source.y; + TestPoint.z = 3.0f * Sin(Alpha - DEGTORAD(20.0f)) + Source.z; + if(!CWorld::GetIsLineOfSightClear(TestPoint, Source, true, true, false, true, false, true, true)){ + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + FailedTestTwelveFramesAgo = true; + }else + FailedTestTwelveFramesAgo = false; + } + } + } + + if(FailedTestTwelveFramesAgo) + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + Source -= Front*0.4f; + + GetVectorsReadyForRW(); + float Rotation = CGeneral::GetATanOfXY(Front.x, Front.y) - HALFPI; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Rotation; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Rotation; +} + +void +CCam::Process_Syphon(const CVector &CameraTarget, float, float, float) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsPed()) + return; + + static bool CameraObscured = false; + // unused FailedClippingTestPrevously + static float BetaOffset = DEGTORAD(18.0f); + // unused AngleToGoTo + // unused AngleToGoToSpeed + // unused DistBetweenPedAndPlayerPreviouslyOn + static float HeightDown = -0.5f; + static float PreviousDistForInter; + CVector TargetCoors; + CVector2D vDist; + float fDist, fAimingDist; + float TargetAlpha; + CColPoint colPoint; + CEntity *entity; + + TargetCoors = CameraTarget; + + if(TheCamera.Cams[TheCamera.ActiveCam].Mode != MODE_SYPHON) + return; + + vDist = Source - TargetCoors; + fDist = vDist.Magnitude(); + if(fDist == 0.0f) + Source = TargetCoors + CVector(1.0f, 1.0f, 0.0f); + else + Source = TargetCoors + CVector(vDist.x/fDist * 1.7f, vDist.y/fDist * 1.7f, 0.0f); + if(fDist > 1.7f) + fDist = 1.7f; + + Beta = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y); + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + float NewBeta = CGeneral::GetATanOfXY(TheCamera.m_cvecAimingTargetCoors.x - TargetCoors.x, TheCamera.m_cvecAimingTargetCoors.y - TargetCoors.y) + PI; + if(ResetStatics){ + CameraObscured = false; + float TestBeta1 = NewBeta - BetaOffset - Beta; + float TestBeta2 = NewBeta + BetaOffset - Beta; + MakeAngleLessThan180(TestBeta1); + MakeAngleLessThan180(TestBeta2); + if(Abs(TestBeta1) < Abs(TestBeta2)) + BetaOffset = -BetaOffset; + // some unuseds + ResetStatics = false; + } + Beta = NewBeta + BetaOffset; + Source = TargetCoors; + Source.x += 1.7f*Cos(Beta); + Source.y += 1.7f*Sin(Beta); + TargetCoors.z += m_fSyphonModeTargetZOffSet; + fAimingDist = (TheCamera.m_cvecAimingTargetCoors - TargetCoors).Magnitude2D(); + if(fAimingDist < 6.5f) + fAimingDist = 6.5f; + TargetAlpha = CGeneral::GetATanOfXY(fAimingDist, TheCamera.m_cvecAimingTargetCoors.z - TargetCoors.z); + while(TargetAlpha >= PI) TargetAlpha -= 2*PI; + while(TargetAlpha < -PI) TargetAlpha += 2*PI; + + // inlined + WellBufferMe(-TargetAlpha, &Alpha, &AlphaSpeed, 0.07f, 0.015f, true); + + Source.z += fDist*Sin(Alpha) + fDist*0.2f; + if(Source.z < TargetCoors.z + HeightDown) + Source.z = TargetCoors.z + HeightDown; + + CameraObscured = CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true); + // PreviousDistForInter unused + if(CameraObscured){ + PreviousDistForInter = (TargetCoors - colPoint.point).Magnitude2D(); + Source = colPoint.point; + }else + PreviousDistForInter = 1.7f; + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + m_fMinDistAwayFromCamWhenInterPolating = Front.Magnitude2D(); + if(m_fMinDistAwayFromCamWhenInterPolating < 1.1f) + RwCameraSetNearClipPlane(Scene.camera, Max(m_fMinDistAwayFromCamWhenInterPolating - 0.35f, 0.05f)); + Front.Normalise(); + GetVectorsReadyForRW(); +} + +void +CCam::Process_Syphon_Crim_In_Front(const CVector &CameraTarget, float, float, float) +{ + FOV = DefaultFOV; + + if(!CamTargetEntity->IsPed()) + return; + + CVector TargetCoors = CameraTarget; + CVector vDist; + float fDist, TargetDist; + float zOffset; + float AimingAngle; + CColPoint colPoint; + CEntity *entity; + + TargetDist = TheCamera.m_fPedZoomValueSmooth * 0.5f + 4.0f; + vDist = Source - TargetCoors; + fDist = vDist.Magnitude2D(); + zOffset = TargetDist - 2.65f; + if(zOffset < 0.0f) + zOffset = 0.0f; + if(zOffset == 0.0f) + Source = TargetCoors + CVector(1.0f, 1.0f, zOffset); + else + Source = TargetCoors + CVector(vDist.x/fDist*TargetDist, vDist.y/fDist*TargetDist, zOffset); + + AimingAngle = CGeneral::GetATanOfXY(TheCamera.m_cvecAimingTargetCoors.x - TargetCoors.x, TheCamera.m_cvecAimingTargetCoors.y - TargetCoors.y); + while(AimingAngle >= PI) AimingAngle -= 2*PI; + while(AimingAngle < -PI) AimingAngle += 2*PI; + + if(ResetStatics){ + if(AimingAngle > 0.0f) + m_fPlayerInFrontSyphonAngleOffSet = -m_fPlayerInFrontSyphonAngleOffSet; + ResetStatics = false; + } + + if(TheCamera.PlayerWeaponMode.Mode == MODE_SYPHON) + Beta = AimingAngle + m_fPlayerInFrontSyphonAngleOffSet; + + Source.x = TargetCoors.x; + Source.y = TargetCoors.y; + Source.x += Cos(Beta) * TargetDist; + Source.y += Sin(Beta) * TargetDist; + + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, false, false, true, false, true, true)){ + Beta = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y); + fDist = (TargetCoors - colPoint.point).Magnitude2D(); + Source.x = TargetCoors.x; + Source.y = TargetCoors.y; + Source.x += Cos(Beta) * fDist; + Source.y += Sin(Beta) * fDist; + } + + TargetCoors = CameraTarget; + TargetCoors.z += m_fSyphonModeTargetZOffSet; + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + GetVectorsReadyForRW(); +} + +void +CCam::Process_BehindBoat(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsVehicle()){ + ResetStatics = false; + return; + } + + CVector TargetCoors = CameraTarget; + float DeltaBeta = 0.0f; + static CColPoint colPoint; + CEntity *entity; + static float TargetWhenChecksWereOn = 0.0f; + static float CenterObscuredWhenChecksWereOn = 0.0f; + static float WaterZAddition = 2.75f; + float WaterLevel = 0.0f; + float s, c; + + Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y); + FOV = DefaultFOV; + + if(ResetStatics){ + CenterObscuredWhenChecksWereOn = 0.0f; + TargetWhenChecksWereOn = 0.0f; + Beta = TargetOrientation + PI; + } + + CWaterLevel::GetWaterLevelNoWaves(TargetCoors.x, TargetCoors.y, TargetCoors.z, &WaterLevel); + WaterLevel += WaterZAddition; + static float FixerForGoingBelowGround = 0.4f; + if(-FixerForGoingBelowGround < TargetCoors.z-WaterLevel) + WaterLevel += TargetCoors.z-WaterLevel - FixerForGoingBelowGround; + + bool Obscured; + if(m_bCollisionChecksOn || ResetStatics){ + CVector TestPoint; + // Weird calculations here, also casting bool to float... + c = Cos(TargetOrientation); + s = Sin(TargetOrientation); + TestPoint = TheCamera.CarZoomValueSmooth * CVector(-c, -s, 0.0f) + + (TheCamera.CarZoomValueSmooth+7.0f) * CVector(-c, -s, 0.0f) + + TargetCoors; + TestPoint.z = WaterLevel + TheCamera.CarZoomValueSmooth; + float Test1 = CWorld::GetIsLineOfSightClear(TestPoint, TargetCoors, true, false, false, true, false, true, true); + + c = Cos(TargetOrientation + 0.8f); + s = Sin(TargetOrientation + DEGTORAD(40.0f)); + TestPoint = TheCamera.CarZoomValueSmooth * CVector(-c, -s, 0.0f) + + (TheCamera.CarZoomValueSmooth+7.0f) * CVector(-c, -s, 0.0f) + + TargetCoors; + TestPoint.z = WaterLevel + TheCamera.CarZoomValueSmooth; + float Test2 = CWorld::GetIsLineOfSightClear(TestPoint, TargetCoors, true, false, false, true, false, true, true); + + c = Cos(TargetOrientation - 0.8); + s = Sin(TargetOrientation - DEGTORAD(40.0f)); + TestPoint = TheCamera.CarZoomValueSmooth * CVector(-c, -s, 0.0f) + + (TheCamera.CarZoomValueSmooth+7.0f) * CVector(-c, -s, 0.0f) + + TargetCoors; + TestPoint.z = WaterLevel + TheCamera.CarZoomValueSmooth; + float Test3 = CWorld::GetIsLineOfSightClear(TestPoint, TargetCoors, true, false, false, true, false, true, true); + + if(Test2 == 0.0f){ + DeltaBeta = TargetOrientation - Beta - DEGTORAD(40.0f); + if(ResetStatics) + Beta = TargetOrientation - DEGTORAD(40.0f); + }else if(Test3 == 0.0f){ + DeltaBeta = TargetOrientation - Beta + DEGTORAD(40.0f); + if(ResetStatics) + Beta = TargetOrientation + DEGTORAD(40.0f); + }else if(Test1 == 0.0f){ + DeltaBeta = 0.0f; + }else if(Test2 != 0.0f && Test3 != 0.0f && Test1 != 0.0f){ + if(ResetStatics) + Beta = TargetOrientation; + DeltaBeta = TargetOrientation - Beta; + } + + c = Cos(Beta); + s = Sin(Beta); + TestPoint.x = TheCamera.CarZoomValueSmooth * -c + + (TheCamera.CarZoomValueSmooth + 7.0f) * -c + + TargetCoors.x; + TestPoint.y = TheCamera.CarZoomValueSmooth * -s + + (TheCamera.CarZoomValueSmooth + 7.0f) * -s + + TargetCoors.y; + TestPoint.z = WaterLevel + TheCamera.CarZoomValueSmooth; + Obscured = CWorld::ProcessLineOfSight(TestPoint, TargetCoors, colPoint, entity, true, false, false, true, false, true, true); + CenterObscuredWhenChecksWereOn = Obscured; + + // now DeltaBeta == TargetWhenChecksWereOn - Beta, which we need for WellBufferMe below + TargetWhenChecksWereOn = DeltaBeta + Beta; + }else{ + // DeltaBeta = TargetWhenChecksWereOn - Beta; // unneeded since we don't inline WellBufferMe + Obscured = CenterObscuredWhenChecksWereOn != 0.0f; + } + + if(Obscured){ + CWorld::ProcessLineOfSight(Source, TargetCoors, colPoint, entity, true, false, false, true, false, true, true); + Source = colPoint.point; + }else{ + // inlined + WellBufferMe(TargetWhenChecksWereOn, &Beta, &BetaSpeed, 0.07f, 0.015f, true); + + s = Sin(Beta); + c = Cos(Beta); + Source = TheCamera.CarZoomValueSmooth * CVector(-c, -s, 0.0f) + + (TheCamera.CarZoomValueSmooth+7.0f) * CVector(-c, -s, 0.0f) + + TargetCoors; + Source.z = WaterLevel + TheCamera.CarZoomValueSmooth; + } + + if(TheCamera.CarZoomValueSmooth < 0.05f){ + static float AmountUp = 2.2f; + TargetCoors.z += AmountUp * (0.0f - TheCamera.CarZoomValueSmooth); + } + TargetCoors.z += TheCamera.CarZoomValueSmooth + 0.5f; + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + GetVectorsReadyForRW(); + ResetStatics = false; +} + +void +CCam::Process_Fight_Cam(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + FOV = DefaultFOV; + float BetaLeft, BetaRight, DeltaBetaLeft, DeltaBetaRight; + float BetaFix; + float Dist; + float BetaMaxSpeed = 0.015f; + float BetaAcceleration = 0.007f; + static bool PreviouslyFailedBuildingChecks = false; + float TargetCamHeight; + CVector TargetCoors; + + m_fMinDistAwayFromCamWhenInterPolating = 4.0f; + Front = Source - CameraTarget; + Beta = CGeneral::GetATanOfXY(Front.x, Front.y); + while(TargetOrientation >= PI) TargetOrientation -= 2*PI; + while(TargetOrientation < -PI) TargetOrientation += 2*PI; + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + // Figure out Beta + BetaLeft = TargetOrientation - HALFPI; + BetaRight = TargetOrientation + HALFPI; + DeltaBetaLeft = Beta - BetaLeft; + DeltaBetaRight = Beta - BetaRight; + while(DeltaBetaLeft >= PI) DeltaBetaLeft -= 2*PI; + while(DeltaBetaLeft < -PI) DeltaBetaLeft += 2*PI; + while(DeltaBetaRight >= PI) DeltaBetaRight -= 2*PI; + while(DeltaBetaRight < -PI) DeltaBetaRight += 2*PI; + + if(ResetStatics){ + if(Abs(DeltaBetaLeft) < Abs(DeltaBetaRight)) + m_fTargetBeta = DeltaBetaLeft; + else + m_fTargetBeta = DeltaBetaRight; + m_fBufferedTargetOrientation = TargetOrientation; + m_fBufferedTargetOrientationSpeed = 0.0f; + m_bCollisionChecksOn = true; + BetaSpeed = 0.0f; + }else if(CPad::GetPad(0)->WeaponJustDown()){ + if(Abs(DeltaBetaLeft) < Abs(DeltaBetaRight)) + m_fTargetBeta = DeltaBetaLeft; + else + m_fTargetBeta = DeltaBetaRight; + } + + // Check collisions + BetaFix = 0.0f; + Dist = Front.Magnitude2D(); + if(m_bCollisionChecksOn || PreviouslyFailedBuildingChecks){ + BetaFix = GetPedBetaAngleForClearView(CameraTarget, Dist+0.25f, 0.0f, true, false, false, true, false); + if(BetaFix == 0.0f){ + BetaFix = GetPedBetaAngleForClearView(CameraTarget, Dist+0.5f, DEGTORAD(24.0f), true, false, false, true, false); + if(BetaFix == 0.0f) + BetaFix = GetPedBetaAngleForClearView(CameraTarget, Dist+0.5f, -DEGTORAD(24.0f), true, false, false, true, false); + } + } + if(BetaFix != 0.0f){ + BetaMaxSpeed = 0.1f; + PreviouslyFailedBuildingChecks = true; + BetaAcceleration = 0.025f; + m_fTargetBeta = Beta + BetaFix; + } + WellBufferMe(m_fTargetBeta, &Beta, &BetaSpeed, BetaMaxSpeed, BetaAcceleration, true); + + Source = CameraTarget + 4.0f*CVector(Cos(Beta), Sin(Beta), 0.0f); + Source.z -= 0.5f; + + WellBufferMe(TargetOrientation, &m_fBufferedTargetOrientation, &m_fBufferedTargetOrientationSpeed, 0.07f, 0.004f, true); + TargetCoors = CameraTarget + 0.5f*CVector(Cos(m_fBufferedTargetOrientation), Sin(m_fBufferedTargetOrientation), 0.0f); + + TargetCamHeight = CameraTarget.z - Source.z + Max(m_fPedBetweenCameraHeightOffset, m_fRoadOffSet + m_fDimensionOfHighestNearCar) - 0.5f; + if(TargetCamHeight > m_fCamBufferedHeight) + WellBufferMe(TargetCamHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.15f, 0.04f, false); + else + WellBufferMe(0.0f, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.08f, 0.0175f, false); + Source.z += m_fCamBufferedHeight; + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + Front.Normalise(); + GetVectorsReadyForRW(); + + ResetStatics = false; +} + +/* +// Spline format is this, but game doesn't seem to use any kind of struct: +struct Spline +{ + float numFrames; + struct { + float time; + float f[3]; // CVector for Vector spline + } frames[1]; // numFrames +}; +*/ + +// These two functions are pretty ugly + +#define MS(t) (uint32)((t)*1000.0f) + +void +FindSplinePathPositionFloat(float *out, float *spline, uint32 time, uint32 &marker) +{ + // marker is at time + uint32 numFrames = spline[0]; + uint32 timeDelta = MS(spline[marker] - spline[marker-4]); + uint32 endTime = MS(spline[4*(numFrames-1) + 1]); + if(time < endTime){ + bool canAdvance = true; + if((marker-1)/4 > numFrames){ + canAdvance = false; + marker = 4*(numFrames-1) + 1; + } + // skipping over small time deltas apparently? + while(timeDelta <= 75 && canAdvance){ + marker += 4; + if((marker-1)/4 > numFrames){ + canAdvance = false; + marker = 4*(numFrames-1) + 1; + } + timeDelta = (spline[marker] - spline[marker-4]) * 1000.0f; + } + } + float a = ((float)time - (float)MS(spline[marker-4])) / (float)MS(spline[marker] - spline[marker-4]); + a = Clamp(a, 0.0f, 1.0f); + float b = 1.0f - a; + *out = b*b*b * spline[marker-3] + + 3.0f*a*b*b * spline[marker-1] + + 3.0f*a*a*b * spline[marker+2] + + a*a*a * spline[marker+1]; +} + +void +FindSplinePathPositionVector(CVector *out, float *spline, uint32 time, uint32 &marker) +{ + // marker is at time + uint32 numFrames = spline[0]; + uint32 timeDelta = MS(spline[marker] - spline[marker-10]); + uint32 endTime = MS(spline[10*(numFrames-1) + 1]); + if(time < endTime){ + bool canAdvance = true; + if((marker-1)/10 > numFrames){ + canAdvance = false; + marker = 10*(numFrames-1) + 1; + } + // skipping over small time deltas apparently? + while(timeDelta <= 75 && canAdvance){ + marker += 10; + if((marker-1)/10 > numFrames){ + canAdvance = false; + marker = 10*(numFrames-1) + 1; + } + timeDelta = (spline[marker] - spline[marker-10]) * 1000.0f; + } + } + + if((marker-1)/10 > numFrames){ + printf("Arraymarker %i \n", marker); + printf("Path zero %i \n", numFrames); + } + + float a = ((float)time - (float)MS(spline[marker-10])) / (float)MS(spline[marker] - spline[marker-10]); + a = Clamp(a, 0.0f, 1.0f); + float b = 1.0f - a; + out->x = + b*b*b * spline[marker-9] + + 3.0f*a*b*b * spline[marker-3] + + 3.0f*a*a*b * spline[marker+4] + + a*a*a * spline[marker+1]; + out->y = + b*b*b * spline[marker-8] + + 3.0f*a*b*b * spline[marker-2] + + 3.0f*a*a*b * spline[marker+5] + + a*a*a * spline[marker+2]; + out->z = + b*b*b * spline[marker-7] + + 3.0f*a*b*b * spline[marker-1] + + 3.0f*a*a*b * spline[marker+6] + + a*a*a * spline[marker+3]; + *out += TheCamera.m_vecCutSceneOffset; +} + +void +CCam::Process_FlyBy(const CVector&, float, float, float) +{ + float UpAngle = 0.0f; + static float FirstFOVValue = 0.0f; + static float PsuedoFOV; + static uint32 ArrayMarkerFOV; + static uint32 ArrayMarkerUp; + static uint32 ArrayMarkerSource; + static uint32 ArrayMarkerFront; + + if(TheCamera.m_bcutsceneFinished) + return; + + Up = CVector(0.0f, 0.0f, 1.0f); + if(TheCamera.m_bStartingSpline) + m_fTimeElapsedFloat += CTimer::GetTimeStepNonClippedInMilliseconds(); + else{ + m_fTimeElapsedFloat = 0.0f; + m_uiFinishTime = MS(TheCamera.m_arrPathArray[2].m_arr_PathData[10*((int)TheCamera.m_arrPathArray[2].m_arr_PathData[0]-1) + 1]); + TheCamera.m_bStartingSpline = true; + FirstFOVValue = TheCamera.m_arrPathArray[0].m_arr_PathData[2]; + PsuedoFOV = TheCamera.m_arrPathArray[0].m_arr_PathData[2]; + ArrayMarkerFOV = 5; + ArrayMarkerUp = 5; + ArrayMarkerSource = 11; + ArrayMarkerFront = 11; + } + + float fTime = m_fTimeElapsedFloat; + uint32 uiFinishTime = m_uiFinishTime; + uint32 uiTime = fTime; + if(uiTime < uiFinishTime){ + TheCamera.m_fPositionAlongSpline = (float) uiTime / uiFinishTime; + + while(uiTime >= (TheCamera.m_arrPathArray[2].m_arr_PathData[ArrayMarkerSource] - TheCamera.m_arrPathArray[2].m_arr_PathData[1])*1000.0f) + ArrayMarkerSource += 10; + FindSplinePathPositionVector(&Source, TheCamera.m_arrPathArray[2].m_arr_PathData, uiTime, ArrayMarkerSource); + + while(uiTime >= (TheCamera.m_arrPathArray[3].m_arr_PathData[ArrayMarkerFront] - TheCamera.m_arrPathArray[3].m_arr_PathData[1])*1000.0f) + ArrayMarkerFront += 10; + FindSplinePathPositionVector(&Front, TheCamera.m_arrPathArray[3].m_arr_PathData, uiTime, ArrayMarkerFront); + + while(uiTime >= (TheCamera.m_arrPathArray[1].m_arr_PathData[ArrayMarkerUp] - TheCamera.m_arrPathArray[1].m_arr_PathData[1])*1000.0f) + ArrayMarkerUp += 4; + FindSplinePathPositionFloat(&UpAngle, TheCamera.m_arrPathArray[1].m_arr_PathData, uiTime, ArrayMarkerUp); + UpAngle = DEGTORAD(UpAngle) + HALFPI; + Up.x = Cos(UpAngle); + Up.z = Sin(UpAngle); + + while(uiTime >= (TheCamera.m_arrPathArray[0].m_arr_PathData[ArrayMarkerFOV] - TheCamera.m_arrPathArray[0].m_arr_PathData[1])*1000.0f) + ArrayMarkerFOV += 4; + FindSplinePathPositionFloat(&PsuedoFOV, TheCamera.m_arrPathArray[0].m_arr_PathData, uiTime, ArrayMarkerFOV); + + m_cvecTargetCoorsForFudgeInter = Front; + Front = Front - Source; + Front.Normalise(); + CVector Left = CrossProduct(Up, Front); + Up = CrossProduct(Front, Left); + Up.Normalise(); + }else if(uiTime >= uiFinishTime){ + // end + ArrayMarkerSource = (TheCamera.m_arrPathArray[2].m_arr_PathData[0] - 1)*10 + 1; + ArrayMarkerFront = (TheCamera.m_arrPathArray[3].m_arr_PathData[0] - 1)*10 + 1; + ArrayMarkerUp = (TheCamera.m_arrPathArray[1].m_arr_PathData[0] - 1)*4 + 1; + ArrayMarkerFOV = (TheCamera.m_arrPathArray[0].m_arr_PathData[0] - 1)*4 + 1; + + FindSplinePathPositionVector(&Source, TheCamera.m_arrPathArray[2].m_arr_PathData, uiTime, ArrayMarkerSource); + FindSplinePathPositionVector(&Front, TheCamera.m_arrPathArray[3].m_arr_PathData, uiTime, ArrayMarkerFront); + FindSplinePathPositionFloat(&UpAngle, TheCamera.m_arrPathArray[1].m_arr_PathData, uiTime, ArrayMarkerUp); + UpAngle = DEGTORAD(UpAngle) + HALFPI; + Up.x = Cos(UpAngle); + Up.z = Sin(UpAngle); + FindSplinePathPositionFloat(&PsuedoFOV, TheCamera.m_arrPathArray[0].m_arr_PathData, uiTime, ArrayMarkerFOV); + + TheCamera.m_fPositionAlongSpline = 1.0f; + ArrayMarkerFOV = 0; + ArrayMarkerUp = 0; + ArrayMarkerSource = 0; + ArrayMarkerFront = 0; + + m_cvecTargetCoorsForFudgeInter = Front; + Front = Front - Source; + Front.Normalise(); + CVector Left = CrossProduct(Up, Front); + Up = CrossProduct(Front, Left); + Up.Normalise(); + } + FOV = PsuedoFOV; +} + +void +CCam::Process_WheelCam(const CVector&, float, float, float) +{ + FOV = DefaultFOV; + + if(CamTargetEntity->IsPed()){ + // what? ped with wheels or what? + Source = Multiply3x3(CamTargetEntity->GetMatrix(), CVector(-0.3f, -0.5f, 0.1f)); + Source += CamTargetEntity->GetPosition(); + Front = CVector(1.0f, 0.0f, 0.0f); + }else{ + Source = Multiply3x3(CamTargetEntity->GetMatrix(), CVector(-1.4f, -2.3f, 0.3f)); + Source += CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetForward(); + } + + CVector NewUp(0.0f, 0.0f, 1.0f); + CVector Right = CrossProduct(Front, NewUp); + Right.Normalise(); + NewUp = CrossProduct(Right, Front); + NewUp.Normalise(); + + float Roll = Cos((CTimer::GetTimeInMilliseconds()&0x1FFFF)/(float)0x1FFFF * TWOPI); + Up = Cos(Roll*0.4f)*NewUp + Sin(Roll*0.4f)*Right; +} + +void +CCam::Process_Fixed(const CVector &CameraTarget, float, float, float) +{ + Source = m_cvecCamFixedModeSource; + Front = CameraTarget - Source; + m_cvecTargetCoorsForFudgeInter = CameraTarget; + GetVectorsReadyForRW(); + + Up = CVector(0.0f, 0.0f, 1.0f) + m_cvecCamFixedModeUpOffSet; + Up.Normalise(); + CVector Right = CrossProduct(Front, Up); + Right.Normalise(); + Up = CrossProduct(Right, Front); + + FOV = DefaultFOV; + if(TheCamera.m_bUseSpecialFovTrain) + FOV = TheCamera.m_fFovForTrain; + +#ifdef PC_PLAYER_CONTROLS + if(CMenuManager::m_ControlMethod == CONTROL_STANDARD && Using3rdPersonMouseCam()){ + CPed *player = FindPlayerPed(); + if(player && player->CanStrafeOrMouseControl()){ + float Heading = Front.Heading(); + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Heading; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Heading; + TheCamera.pTargetEntity->SetHeading(Heading); + TheCamera.pTargetEntity->GetMatrix().UpdateRW(); + } + } +#endif +} + +void +CCam::Process_Player_Fallen_Water(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + CColPoint colPoint; + CEntity *entity = nil; + + FOV = DefaultFOV; + Source = CameraTarget; + Source.x += -4.5f*Cos(TargetOrientation); + Source.y += -4.5f*Sin(TargetOrientation); + Source.z = m_vecLastAboveWaterCamPosition.z + 4.0f; + + m_cvecTargetCoorsForFudgeInter = CameraTarget; + Front = CameraTarget - Source; + Front.Normalise(); + if(CWorld::ProcessLineOfSight(CameraTarget, Source, colPoint, entity, true, false, false, true, false, true, true)) + Source = colPoint.point; + GetVectorsReadyForRW(); + Front = CameraTarget - Source; + Front.Normalise(); +} + +// unused +void +CCam::Process_Circle(const CVector &CameraTarget, float, float, float) +{ + FOV = DefaultFOV; + + Front.x = Cos(0.7f) * Cos((CTimer::GetTimeInMilliseconds()&0xFFF)/(float)0xFFF * TWOPI); + Front.y = Cos(0.7f) * Sin((CTimer::GetTimeInMilliseconds()&0xFFF)/(float)0xFFF * TWOPI); + Front.z = -Sin(0.7f); + Source = CameraTarget - 4.0f*Front; + Source.z += 1.0f; + GetVectorsReadyForRW(); +} + +void +CCam::Process_SpecialFixedForSyphon(const CVector &CameraTarget, float, float, float) +{ + Source = m_cvecCamFixedModeSource; + m_cvecTargetCoorsForFudgeInter = CameraTarget; + m_cvecTargetCoorsForFudgeInter.z += m_fSyphonModeTargetZOffSet; + Front = CameraTarget - Source; + Front.z += m_fSyphonModeTargetZOffSet; + + GetVectorsReadyForRW(); + + Up += m_cvecCamFixedModeUpOffSet; + Up.Normalise(); + CVector Left = CrossProduct(Up, Front); + Left.Normalise(); + Front = CrossProduct(Left, Up); + Front.Normalise(); + FOV = DefaultFOV; +} + +#ifdef IMPROVED_CAMERA + +#define KEYJUSTDOWN(k) ControlsManager.GetIsKeyboardKeyJustDown((RsKeyCodes)k) +#define KEYDOWN(k) ControlsManager.GetIsKeyboardKeyDown((RsKeyCodes)k) +#define CTRLJUSTDOWN(key) \ + ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYJUSTDOWN((RsKeyCodes)key) || \ + (KEYJUSTDOWN(rsLCTRL) || KEYJUSTDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) +#define CTRLDOWN(key) ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) + + +void +CCam::Process_Debug(const CVector&, float, float, float) +{ + static float Speed = 0.0f; + static float PanSpeedX = 0.0f; + static float PanSpeedY = 0.0f; + CVector TargetCoors; + + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + FOV = DefaultFOV; + Alpha += DEGTORAD(CPad::GetPad(1)->GetLeftStickY()) / 50.0f; + Beta += DEGTORAD(CPad::GetPad(1)->GetLeftStickX()*1.5f) / 19.0f; + if(CPad::GetPad(0)->GetLeftMouse()){ + Alpha += DEGTORAD(CPad::GetPad(0)->GetMouseY()/2.0f); + Beta += DEGTORAD(CPad::GetPad(0)->GetMouseX()/2.0f); + } + + TargetCoors.x = Source.x + Cos(Alpha) * Sin(Beta) * 7.0f; + TargetCoors.y = Source.y + Cos(Alpha) * Cos(Beta) * 7.0f; + TargetCoors.z = Source.z + Sin(Alpha) * 3.0f; + + if(Alpha > DEGTORAD(89.5f)) Alpha = DEGTORAD(89.5f); + else if(Alpha < DEGTORAD(-89.5f)) Alpha = DEGTORAD(-89.5f); + + if(CPad::GetPad(1)->GetSquare() || KEYDOWN('W')) + Speed += 0.1f; + else if(CPad::GetPad(1)->GetCross() || KEYDOWN('S')) + Speed -= 0.1f; + else + Speed = 0.0f; + if(Speed > 70.0f) Speed = 70.0f; + if(Speed < -70.0f) Speed = -70.0f; + + + if(KEYDOWN(rsRIGHT) || KEYDOWN('D')) + PanSpeedX += 0.1f; + else if(KEYDOWN(rsLEFT) || KEYDOWN('A')) + PanSpeedX -= 0.1f; + else + PanSpeedX = 0.0f; + if(PanSpeedX > 70.0f) PanSpeedX = 70.0f; + if(PanSpeedX < -70.0f) PanSpeedX = -70.0f; + + + if(KEYDOWN(rsUP)) + PanSpeedY += 0.1f; + else if(KEYDOWN(rsDOWN)) + PanSpeedY -= 0.1f; + else + PanSpeedY = 0.0f; + if(PanSpeedY > 70.0f) PanSpeedY = 70.0f; + if(PanSpeedY < -70.0f) PanSpeedY = -70.0f; + + + Front = TargetCoors - Source; + Front.Normalise(); + Source = Source + Front*Speed; + + Up = CVector{ 0.0f, 0.0f, 1.0f }; + CVector Right = CrossProduct(Front, Up); + Up = CrossProduct(Right, Front); + Source = Source + Up*PanSpeedY + Right*PanSpeedX; + + if(Source.z < -450.0f) + Source.z = -450.0f; + + if(CPad::GetPad(1)->GetRightShoulder2JustDown() || KEYJUSTDOWN(rsENTER)){ + if(FindPlayerVehicle()) + FindPlayerVehicle()->Teleport(Source); + else + CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetPosition(Source); + } + + // stay inside sectors + while(CWorld::GetSectorX(Source.x) > NUMSECTORS_X-5.0f) + Source.x -= 1.0f; + while(CWorld::GetSectorX(Source.x) < 5.0f) + Source.x += 1.0f; + while(CWorld::GetSectorY(Source.y) > NUMSECTORS_X-5.0f) + Source.y -= 1.0f; + while(CWorld::GetSectorY(Source.y) < 5.0f) + Source.y += 1.0f; + GetVectorsReadyForRW(); + +#ifdef FIX_BUGS + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_CAMERA); +#else + CPad::GetPad(0)->DisablePlayerControls = PLAYERCONTROL_CAMERA; +#endif + + if(CPad::GetPad(1)->GetLeftShockJustDown() && gbBigWhiteDebugLightSwitchedOn) + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &Source, + 12.0f, 0.0f, 0.0f, -12.0f, + 128, 128, 128, 128, 1000.0f, false, 1.0f); + + if(CHud::m_Wants_To_Draw_Hud){ + char str[256]; + sprintf(str, "CamX: %f CamY: %f CamZ: %f", Source.x, Source.y, Source.z); + sprintf(str, "Frontx: %f, Fronty: %f, Frontz: %f ", Front.x, Front.y, Front.z); + sprintf(str, "Look@: %f, Look@: %f, Look@: %f ", Front.x + Source.x, Front.y + Source.y, Front.z + Source.z); + } +} +#else +void +CCam::Process_Debug(const CVector&, float, float, float) +{ + static float Speed = 0.0f; + CVector TargetCoors; + + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + FOV = DefaultFOV; + Alpha += DEGTORAD(CPad::GetPad(1)->GetLeftStickY()) / 50.0f; + Beta += DEGTORAD(CPad::GetPad(1)->GetLeftStickX()*1.5f) / 19.0f; + + TargetCoors.x = Source.x + Cos(Alpha) * Sin(Beta) * 7.0f; + TargetCoors.y = Source.y + Cos(Alpha) * Cos(Beta) * 7.0f; + TargetCoors.z = Source.z + Sin(Alpha) * 3.0f; + + if(Alpha > DEGTORAD(89.5f)) Alpha = DEGTORAD(89.5f); + else if(Alpha < DEGTORAD(-89.5f)) Alpha = DEGTORAD(-89.5f); + + if(CPad::GetPad(1)->GetSquare() || CPad::GetPad(1)->GetLeftMouse()) + Speed += 0.1f; + else if(CPad::GetPad(1)->GetCross() || CPad::GetPad(1)->GetRightMouse()) + Speed -= 0.1f; + else + Speed = 0.0f; + if(Speed > 70.0f) Speed = 70.0f; + if(Speed < -70.0f) Speed = -70.0f; + + Front = TargetCoors - Source; + Front.Normalise(); + Source = Source + Front*Speed; + + if(Source.z < -450.0f) + Source.z = -450.0f; + + if(CPad::GetPad(1)->GetRightShoulder2JustDown()){ + if(FindPlayerVehicle()) + FindPlayerVehicle()->Teleport(Source); + else + CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetPosition(Source); + } + + // stay inside sectors + while(CWorld::GetSectorX(Source.x) > NUMSECTORS_X-5.0f) + Source.x -= 1.0f; + while(CWorld::GetSectorX(Source.x) < 5.0f) + Source.x += 1.0f; + while(CWorld::GetSectorY(Source.y) > NUMSECTORS_X-5.0f) + Source.y -= 1.0f; + while(CWorld::GetSectorY(Source.y) < 5.0f) + Source.y += 1.0f; + GetVectorsReadyForRW(); + + if(CPad::GetPad(1)->GetLeftShockJustDown() && gbBigWhiteDebugLightSwitchedOn) + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &Source, + 12.0f, 0.0f, 0.0f, -12.0f, + 128, 128, 128, 128, 1000.0f, false, 1.0f); + + if(CHud::m_Wants_To_Draw_Hud){ + char str[256]; + sprintf(str, "CamX: %f CamY: %f CamZ: %f", Source.x, Source.y, Source.z); + sprintf(str, "Frontx: %f, Fronty: %f, Frontz: %f ", Front.x, Front.y, Front.z); + sprintf(str, "Look@: %f, Look@: %f, Look@: %f ", Front.x + Source.x, Front.y + Source.y, Front.z + Source.z); + } +} +#endif + +#ifdef GTA_SCENE_EDIT +void +CCam::Process_Editor(const CVector&, float, float, float) +{ + static float Speed = 0.0f; + CVector TargetCoors; + + if(ResetStatics){ + Source = CVector(796.0f, -937.0, 40.0f); + CamTargetEntity = nil; + } + ResetStatics = false; + + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + FOV = DefaultFOV; + Alpha += DEGTORAD(CPad::GetPad(1)->GetLeftStickY()) / 50.0f; + Beta += DEGTORAD(CPad::GetPad(1)->GetLeftStickX()*1.5f) / 19.0f; + + if(CamTargetEntity && CSceneEdit::m_bCameraFollowActor){ + TargetCoors = CamTargetEntity->GetPosition(); + }else if(CSceneEdit::m_bRecording){ + TargetCoors.x = Source.x + Cos(Alpha) * Sin(Beta) * 7.0f; + TargetCoors.y = Source.y + Cos(Alpha) * Cos(Beta) * 7.0f; + TargetCoors.z = Source.z + Sin(Alpha) * 7.0f; + }else + TargetCoors = CSceneEdit::m_vecCamHeading + Source; + CSceneEdit::m_vecCurrentPosition = TargetCoors; + CSceneEdit::m_vecCamHeading = TargetCoors - Source; + + if(Alpha > DEGTORAD(89.5f)) Alpha = DEGTORAD(89.5f); + else if(Alpha < DEGTORAD(-89.5f)) Alpha = DEGTORAD(-89.5f); + + if(CPad::GetPad(1)->GetSquare() || CPad::GetPad(1)->GetLeftMouse()) + Speed += 0.1f; + else if(CPad::GetPad(1)->GetCross() || CPad::GetPad(1)->GetRightMouse()) + Speed -= 0.1f; + else + Speed = 0.0f; + if(Speed > 70.0f) Speed = 70.0f; + if(Speed < -70.0f) Speed = -70.0f; + + Front = TargetCoors - Source; + Front.Normalise(); + Source = Source + Front*Speed; + + if(Source.z < -450.0f) + Source.z = -450.0f; + + if(CPad::GetPad(1)->GetRightShoulder2JustDown()){ + if(FindPlayerVehicle()) + FindPlayerVehicle()->Teleport(Source); + else + CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetPosition(Source); + + } + + // stay inside sectors + while(CWorld::GetSectorX(Source.x) > NUMSECTORS_X-5.0f) + Source.x -= 1.0f; + while(CWorld::GetSectorX(Source.x) < 5.0f) + Source.x += 1.0f; + while(CWorld::GetSectorY(Source.y) > NUMSECTORS_X-5.0f) + Source.y -= 1.0f; + while(CWorld::GetSectorY(Source.y) < 5.0f) + Source.y += 1.0f; + GetVectorsReadyForRW(); + + if(CPad::GetPad(1)->GetLeftShockJustDown() && gbBigWhiteDebugLightSwitchedOn) + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &Source, + 12.0f, 0.0f, 0.0f, -12.0f, + 128, 128, 128, 128, 1000.0f, false, 1.0f); + + if(CHud::m_Wants_To_Draw_Hud){ + char str[256]; + sprintf(str, "CamX: %f CamY: %f CamZ: %f", Source.x, Source.y, Source.z); + sprintf(str, "Frontx: %f, Fronty: %f, Frontz: %f ", Front.x, Front.y, Front.z); + sprintf(str, "Look@: %f, Look@: %f, Look@: %f ", Front.x + Source.x, Front.y + Source.y, Front.z + Source.z); + } +} +#endif + +void +CCam::Process_ModelView(const CVector &CameraTarget, float, float, float) +{ + CVector TargetCoors = CameraTarget; + float Angle = Atan2(Front.x, Front.y); + FOV = DefaultFOV; + + Angle += CPad::GetPad(0)->GetLeftStickX()/1280.0f; + if(Distance < 10.0f) + Distance += CPad::GetPad(0)->GetLeftStickY()/1000.0f; + else + Distance += CPad::GetPad(0)->GetLeftStickY() * ((Distance - 10.0f)/20.0f + 1.0f) / 1000.0f; +#ifdef IMPROVED_CAMERA + if(CPad::GetPad(0)->GetLeftMouse()){ + Distance += DEGTORAD(CPad::GetPad(0)->GetMouseY()/2.0f); + Angle += DEGTORAD(CPad::GetPad(0)->GetMouseX()/2.0f); + } +#endif + if(Distance < 1.5f) + Distance = 1.5f; + + Front.x = Cos(0.3f) * Sin(Angle); + Front.y = Cos(0.3f) * Cos(Angle); + Front.z = -Sin(0.3f); + Source = CameraTarget - Distance*Front; + + GetVectorsReadyForRW(); +} + +void +CCam::ProcessPedsDeadBaby(void) +{ + float Distance = 0.0f; + static bool SafeToRotate = false; + CVector TargetDist, TestPoint; + + FOV = DefaultFOV; + TargetDist = Source - CamTargetEntity->GetPosition(); + Distance = TargetDist.Magnitude(); + Beta = CGeneral::GetATanOfXY(TargetDist.x, TargetDist.y); + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + if(ResetStatics){ + TestPoint = CamTargetEntity->GetPosition() + + CVector(4.0f * Cos(Alpha) * Cos(Beta), + 4.0f * Cos(Alpha) * Sin(Beta), + 4.0f * Sin(Alpha)); + bool Safe1 = CWorld::GetIsLineOfSightClear(TestPoint, CamTargetEntity->GetPosition(), true, false, false, true, false, true, true); + + TestPoint = CamTargetEntity->GetPosition() + + CVector(4.0f * Cos(Alpha) * Cos(Beta + DEGTORAD(120.0f)), + 4.0f * Cos(Alpha) * Sin(Beta + DEGTORAD(120.0f)), + 4.0f * Sin(Alpha)); + bool Safe2 = CWorld::GetIsLineOfSightClear(TestPoint, CamTargetEntity->GetPosition(), true, false, false, true, false, true, true); + + TestPoint = CamTargetEntity->GetPosition() + + CVector(4.0f * Cos(Alpha) * Cos(Beta - DEGTORAD(120.0f)), + 4.0f * Cos(Alpha) * Sin(Beta - DEGTORAD(120.0f)), + 4.0f * Sin(Alpha)); + bool Safe3 = CWorld::GetIsLineOfSightClear(TestPoint, CamTargetEntity->GetPosition(), true, false, false, true, false, true, true); + + SafeToRotate = Safe1 && Safe2 && Safe3; + + ResetStatics = false; + } + + if(SafeToRotate) + WellBufferMe(Beta + DEGTORAD(175.0f), &Beta, &BetaSpeed, 0.015f, 0.007f, true); + + WellBufferMe(DEGTORAD(89.5f), &Alpha, &AlphaSpeed, 0.015f, 0.07f, true); + WellBufferMe(35.0f, &Distance, &DistanceSpeed, 0.006f, 0.007f, false); + + Source = CamTargetEntity->GetPosition() + + CVector(Distance * Cos(Alpha) * Cos(Beta), + Distance * Cos(Alpha) * Sin(Beta), + Distance * Sin(Alpha)); + m_cvecTargetCoorsForFudgeInter = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + Front.Normalise(); + GetVectorsReadyForRW(); +} + +bool +CCam::ProcessArrestCamOne(void) +{ + FOV = 45.0f; + if(!ResetStatics) + return true; + +#ifdef FIX_BUGS + if(!CamTargetEntity->IsPed() || ((CPlayerPed*)TheCamera.pTargetEntity)->m_pArrestingCop == nil) + return true; +#endif + + bool found; + float Ground; + CVector PlayerCoors = TheCamera.pTargetEntity->GetPosition(); + CVector CopCoors = ((CPlayerPed*)TheCamera.pTargetEntity)->m_pArrestingCop->GetPosition(); + Beta = CGeneral::GetATanOfXY(PlayerCoors.x - CopCoors.x, PlayerCoors.y - CopCoors.y); + + Source = PlayerCoors + 9.5f*CVector(Cos(Beta), Sin(Beta), 0.0f); + Source.z += 6.0f; + Ground = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, Source.z, &found); + if(!found){ + Ground = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, Source.z, &found); + if(!found) + return false; + } + Source.z = Ground + 0.25f; + if(!CWorld::GetIsLineOfSightClear(Source, CopCoors, true, true, false, true, false, true, true)){ + Beta += DEGTORAD(115.0f); + Source = PlayerCoors + 9.5f*CVector(Cos(Beta), Sin(Beta), 0.0f); + Source.z += 6.0f; + Ground = CWorld::FindGroundZFor3DCoord(Source.x, Source.y, Source.z, &found); + if(!found){ + Ground = CWorld::FindRoofZFor3DCoord(Source.x, Source.y, Source.z, &found); + if(!found) + return false; + } + Source.z = Ground + 0.25f; + + CopCoors.z += 0.35f; + Front = CopCoors - Source; + if(!CWorld::GetIsLineOfSightClear(Source, CopCoors, true, true, false, true, false, true, true)) + return false; + } + CopCoors.z += 0.35f; + m_cvecTargetCoorsForFudgeInter = CopCoors; + Front = CopCoors - Source; + ResetStatics = false; + GetVectorsReadyForRW(); + return true; +} + +bool +CCam::ProcessArrestCamTwo(void) +{ + CPed *player = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + if(!ResetStatics) + return true; + ResetStatics = false; + + CVector TargetCoors, ToCamera; + float BetaOffset; + float SourceX, SourceY; + if(&TheCamera.Cams[TheCamera.ActiveCam] == this){ + SourceX = TheCamera.Cams[(TheCamera.ActiveCam + 1) % 2].Source.x; + SourceY = TheCamera.Cams[(TheCamera.ActiveCam + 1) % 2].Source.y; + }else{ + SourceX = TheCamera.Cams[TheCamera.ActiveCam].Source.x; + SourceY = TheCamera.Cams[TheCamera.ActiveCam].Source.y; + } + + for(int i = 0; i <= 1; i++){ + int Dir = i == 0 ? 1 : -1; + + FOV = 60.0f; + TargetCoors = player->GetPosition(); + Beta = CGeneral::GetATanOfXY(TargetCoors.x-SourceX, TargetCoors.y-SourceY); + BetaOffset = DEGTORAD(Dir*80); + Source = TargetCoors + 11.5f*CVector(Cos(Beta+BetaOffset), Sin(Beta+BetaOffset), 0.0f); + + ToCamera = Source - TargetCoors; + ToCamera.Normalise(); + TargetCoors.x += 0.4f*ToCamera.x; + TargetCoors.y += 0.4f*ToCamera.y; + if(CWorld::GetIsLineOfSightClear(Source, TargetCoors, true, true, false, true, false, true, true)){ + Source.z += 5.5f; + TargetCoors += CVector(-0.8f*ToCamera.x, -0.8f*ToCamera.y, 2.2f); + m_cvecTargetCoorsForFudgeInter = TargetCoors; + Front = TargetCoors - Source; + ResetStatics = false; + GetVectorsReadyForRW(); + return true; + } + } + return false; +} + + +/* + * Unused PS2 cams + */ + +void +CCam::Process_Chris_With_Binding_PlusRotation(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float AngleToBinned = 0.0f; + static float StartingAngleLastChange = 0.0f; + static float FixedTargetOrientation = 0.0f; + static float DeadZoneReachedOnePrevious; + + FOV = DefaultFOV; // missing in game + + bool FixOrientation = true; + if(ResetStatics){ + Rotating = false; + DeadZoneReachedOnePrevious = 0.0f; + FixedTargetOrientation = 0.0f; + ResetStatics = false; + } + + CVector TargetCoors = CameraTarget; + + float StickX = CPad::GetPad(0)->GetRightStickX(); + float StickY = CPad::GetPad(0)->GetRightStickY(); + float StickAngle; + if(StickX != 0.0 || StickY != 0.0f) // BUG: game checks StickX twice + StickAngle = CGeneral::GetATanOfXY(StickX, StickY); // result unused? + else + FixOrientation = false; + + CVector Dist = Source - TargetCoors; + Source.z = TargetCoors.z + 0.75f; + float Length = Dist.Magnitude2D(); + if(Length > 2.5f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.5f; + Source.y = TargetCoors.y + Dist.y/Length * 2.5f; + }else if(Length < 2.4f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.4f; + Source.y = TargetCoors.y + Dist.y/Length * 2.4f; + } + + Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + if(CPad::GetPad(0)->GetLeftShoulder1()){ + FixedTargetOrientation = TargetOrientation; + Rotating = true; + } + + if(FixOrientation){ + Rotating = true; + FixedTargetOrientation = StickX/128.0f + Beta - PI; + } + + if(Rotating){ + Dist = Source - TargetCoors; + Length = Dist.Magnitude2D(); + // inlined + WellBufferMe(FixedTargetOrientation+PI, &Beta, &BetaSpeed, 0.1f, 0.06f, true); + + Source.x = TargetCoors.x + Length*Cos(Beta); + Source.y = TargetCoors.y + Length*Sin(Beta); + + float DeltaBeta = FixedTargetOrientation+PI - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < 0.06f) + Rotating = false; + } + + Front = TargetCoors - Source; + Front.Normalise(); + CVector Front2 = Front; + Front2.Normalise(); // What? + // FIX: the meaning of this value must have changed somehow + Source -= Front2 * TheCamera.m_fPedZoomValueSmooth*1.5f; +// Source += Front2 * TheCamera.m_fPedZoomValueSmooth; + + GetVectorsReadyForRW(); +} + +void +CCam::Process_ReactionCam(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float AngleToBinned = 0.0f; + static float StartingAngleLastChange = 0.0f; + static float FixedTargetOrientation; + static float DeadZoneReachedOnePrevious; + static uint32 TimeOfLastChange; + uint32 Time; + bool DontBind = false; // BUG: left uninitialized + + FOV = DefaultFOV; // missing in game + + if(ResetStatics){ + Rotating = false; + DeadZoneReachedOnePrevious = 0.0f; + FixedTargetOrientation = 0.0f; + ResetStatics = false; + DontBind = false; + } + + CVector TargetCoors = CameraTarget; + + CVector Dist = Source - TargetCoors; + Source.z = TargetCoors.z + 0.75f; + float Length = Dist.Magnitude2D(); + if(Length > 2.5f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.5f; + Source.y = TargetCoors.y + Dist.y/Length * 2.5f; + }else if(Length < 2.4f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.4f; + Source.y = TargetCoors.y + Dist.y/Length * 2.4f; + } + + Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + + float StickX = CPad::GetPad(0)->GetLeftStickX(); + float StickY = CPad::GetPad(0)->GetLeftStickY(); + float StickAngle; + if(StickX != 0.0 || StickY != 0.0f){ + StickAngle = CGeneral::GetATanOfXY(StickX, StickY); + while(StickAngle >= PI) StickAngle -= 2*PI; + while(StickAngle < -PI) StickAngle += 2*PI; + }else + StickAngle = 1000.0f; + + if(Abs(StickAngle-AngleToBinned) > DEGTORAD(15.0f)){ + DontBind = true; + Time = CTimer::GetTimeInMilliseconds(); + } + + if(CTimer::GetTimeInMilliseconds()-TimeOfLastChange > 200){ + if(Abs(HALFPI-StickAngle) > DEGTORAD(50.0f)){ + FixedTargetOrientation = TargetOrientation; + Rotating = true; + TimeOfLastChange = CTimer::GetTimeInMilliseconds(); + } + } + + // These two together don't make much sense. + // Only prevents rotation for one frame + AngleToBinned = StickAngle; + if(DontBind) + TimeOfLastChange = Time; + + if(Rotating){ + Dist = Source - TargetCoors; + Length = Dist.Magnitude2D(); + // inlined + WellBufferMe(FixedTargetOrientation+PI, &Beta, &BetaSpeed, 0.1f, 0.06f, true); + + Source.x = TargetCoors.x + Length*Cos(Beta); + Source.y = TargetCoors.y + Length*Sin(Beta); + + float DeltaBeta = FixedTargetOrientation+PI - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < 0.06f) + Rotating = false; + } + + Front = TargetCoors - Source; + Front.Normalise(); + CVector Front2 = Front; + Front2.Normalise(); // What? + // FIX: the meaning of this value must have changed somehow + Source -= Front2 * TheCamera.m_fPedZoomValueSmooth*1.5f; +// Source += Front2 * TheCamera.m_fPedZoomValueSmooth; + + GetVectorsReadyForRW(); +} + +void +CCam::Process_FollowPed_WithBinding(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float AngleToBinned = 0.0f; + static float StartingAngleLastChange = 0.0f; + static float FixedTargetOrientation; + static float DeadZoneReachedOnePrevious; + static uint32 TimeOfLastChange; + uint32 Time; + bool DontBind = false; + + FOV = DefaultFOV; // missing in game + + if(ResetStatics){ + Rotating = false; + DeadZoneReachedOnePrevious = 0.0f; + FixedTargetOrientation = 0.0f; + ResetStatics = false; + } + + CVector TargetCoors = CameraTarget; + + CVector Dist = Source - TargetCoors; + Source.z = TargetCoors.z + 0.75f; + float Length = Dist.Magnitude2D(); + if(Length > 2.5f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.5f; + Source.y = TargetCoors.y + Dist.y/Length * 2.5f; + }else if(Length < 2.4f){ + Source.x = TargetCoors.x + Dist.x/Length * 2.4f; + Source.y = TargetCoors.y + Dist.y/Length * 2.4f; + } + + Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + + float StickX = CPad::GetPad(0)->GetLeftStickX(); + float StickY = CPad::GetPad(0)->GetLeftStickY(); + float StickAngle; + if(StickX != 0.0 || StickY != 0.0f){ + StickAngle = CGeneral::GetATanOfXY(StickX, StickY); + while(StickAngle >= PI) StickAngle -= 2*PI; + while(StickAngle < -PI) StickAngle += 2*PI; + }else + StickAngle = 1000.0f; + + if(Abs(StickAngle-AngleToBinned) > DEGTORAD(15.0f)){ + DontBind = true; + Time = CTimer::GetTimeInMilliseconds(); + } + + if(CTimer::GetTimeInMilliseconds()-TimeOfLastChange > 200){ + if(Abs(HALFPI-StickAngle) > DEGTORAD(50.0f)){ + FixedTargetOrientation = TargetOrientation; + Rotating = true; + TimeOfLastChange = CTimer::GetTimeInMilliseconds(); + } + } + + if(CPad::GetPad(0)->GetLeftShoulder1JustDown()){ + FixedTargetOrientation = TargetOrientation; + Rotating = true; + TimeOfLastChange = CTimer::GetTimeInMilliseconds(); + } + + // These two together don't make much sense. + // Only prevents rotation for one frame + AngleToBinned = StickAngle; + if(DontBind) + TimeOfLastChange = Time; + + if(Rotating){ + Dist = Source - TargetCoors; + Length = Dist.Magnitude2D(); + // inlined + WellBufferMe(FixedTargetOrientation+PI, &Beta, &BetaSpeed, 0.1f, 0.06f, true); + + Source.x = TargetCoors.x + Length*Cos(Beta); + Source.y = TargetCoors.y + Length*Sin(Beta); + + float DeltaBeta = FixedTargetOrientation+PI - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < 0.06f) + Rotating = false; + } + + Front = TargetCoors - Source; + Front.Normalise(); + CVector Front2 = Front; + Front2.Normalise(); // What? + // FIX: the meaning of this value must have changed somehow + Source -= Front2 * TheCamera.m_fPedZoomValueSmooth*1.5f; +// Source += Front2 * TheCamera.m_fPedZoomValueSmooth; + + GetVectorsReadyForRW(); +} + +void +CCam::Process_Bill(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar) +{ +#ifdef FIX_BUGS + fBillsBetaOffset += CPad::GetPad(0)->GetRightStickX()/1000.0f; +#else + // just wtf is this? this code must be ancient + if(CPad::GetPad(0)->GetStart()) + fBillsBetaOffset += CPad::GetPad(0)->GetLeftStickX()/1000.0f; +#endif + while(fBillsBetaOffset > TWOPI) fBillsBetaOffset -= TWOPI; + while(fBillsBetaOffset < 0.0f) fBillsBetaOffset += TWOPI; + TargetOrientation += fBillsBetaOffset; + while(TargetOrientation > TWOPI) TargetOrientation -= TWOPI; + while(TargetOrientation < 0.0f) TargetOrientation += TWOPI; + Process_BehindCar(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar); +} + +void +CCam::Process_Im_The_Passenger_Woo_Woo(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = 50.0f; + + Source = CamTargetEntity->GetPosition(); + Source.z += 2.5f; + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Source += 1.35f*Front; + float heading = CGeneral::GetATanOfXY(Front.x, Front.y) + DEGTORAD(45.0f); + Front.x = Cos(heading); + Front.y = Sin(heading); + Up = CamTargetEntity->GetUp(); + + GetVectorsReadyForRW(); +} + +void +CCam::Process_Blood_On_The_Tracks(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = 50.0f; + + Source = CamTargetEntity->GetPosition(); + Source.z += 5.45f; + + static CVector Test = -CamTargetEntity->GetForward(); +#ifdef FIX_BUGS + if(ResetStatics){ + Test = -CamTargetEntity->GetForward(); + ResetStatics = false; + } +#endif + + Source.x += 19.45*Test.x; + Source.y += 19.45*Test.y; + Front = Test; + Front.Normalise(); + Up = CamTargetEntity->GetUp(); + + GetVectorsReadyForRW(); +} + +void +CCam::Process_Cam_Running_Side_Train(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = 60.0f; + + Source = CamTargetEntity->GetPosition(); + Source.z += 4.0f; + CVector fwd = CamTargetEntity->GetForward(); + float heading = CGeneral::GetATanOfXY(fwd.x, fwd.y) - DEGTORAD(15.0f); + Source.x -= Cos(heading)*10.0f; + Source.y -= Sin(heading)*10.0f; + heading -= DEGTORAD(5.0f); + Front = fwd; + Front.x += Cos(heading); + Front.y += Sin(heading); + Front.z -= 0.056f; + Front.Normalise(); + Up = CamTargetEntity->GetUp(); + + GetVectorsReadyForRW(); +} + +void +CCam::Process_Cam_On_Train_Roof(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + static float RoofMultiplier = 1.5f; + + Source = CamTargetEntity->GetPosition(); + Source.z += 4.8f; + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Source += Front*RoofMultiplier; + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + + GetVectorsReadyForRW(); +} + + +#ifdef FREE_CAM +void +CCam::Process_FollowPed_Rotation(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + FOV = DefaultFOV; + + const float MinDist = 2.0f; + const float MaxDist = 2.0f + TheCamera.m_fPedZoomValueSmooth; + const float BaseOffset = 0.75f; // base height of camera above target + + CVector TargetCoors = CameraTarget; + + TargetCoors.z += m_fSyphonModeTargetZOffSet; + TargetCoors = DoAverageOnVector(TargetCoors); + TargetCoors.z += BaseOffset; // add offset so alpha evens out to 0 +// TargetCoors.z += m_fRoadOffSet; + + CVector Dist = Source - TargetCoors; + CVector ToCam; + + bool Shooting = false; + if(((CPed*)CamTargetEntity)->GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) + if(CPad::GetPad(0)->GetWeapon()) + Shooting = true; + if(((CPed*)CamTargetEntity)->GetWeapon()->m_eWeaponType == WEAPONTYPE_DETONATOR || + ((CPed*)CamTargetEntity)->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT) + Shooting = false; + + + if(ResetStatics){ + // Coming out of top down here probably + // so keep Beta, reset alpha and calculate vectors + Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + Alpha = 0.0f; + + Dist = MaxDist*CVector(Cos(Alpha) * Cos(Beta), Cos(Alpha) * Sin(Beta), Sin(Alpha)); + Source = TargetCoors + Dist; + + ResetStatics = false; + } + + // Drag the camera along at the look-down offset + float CamDist = Dist.Magnitude(); + if(CamDist == 0.0f) + Dist = CVector(1.0f, 1.0f, 0.0f); + else if(CamDist < MinDist) + Dist *= MinDist/CamDist; + else if(CamDist > MaxDist) + Dist *= MaxDist/CamDist; + CamDist = Dist.Magnitude(); + + // Beta = 0 is looking east, HALFPI is north, &c. + // Alpha positive is looking up + float GroundDist = Dist.Magnitude2D(); + Beta = CGeneral::GetATanOfXY(-Dist.x, -Dist.y); + Alpha = CGeneral::GetATanOfXY(GroundDist, -Dist.z); + while(Beta >= PI) Beta -= 2.0f*PI; + while(Beta < -PI) Beta += 2.0f*PI; + while(Alpha >= PI) Alpha -= 2.0f*PI; + while(Alpha < -PI) Alpha += 2.0f*PI; + + // Look around + bool UseMouse = false; + float MouseX = CPad::GetPad(0)->GetMouseX(); + float MouseY = CPad::GetPad(0)->GetMouseY(); + float LookLeftRight, LookUpDown; +/* + if((MouseX != 0.0f || MouseY != 0.0f) && !CPad::GetPad(0)->ArePlayerControlsDisabled()){ + UseMouse = true; + LookLeftRight = -2.5f*MouseX; + LookUpDown = 4.0f*MouseY; + }else +*/ + { + LookLeftRight = -CPad::GetPad(0)->LookAroundLeftRight(); + LookUpDown = CPad::GetPad(0)->LookAroundUpDown(); + } + float AlphaOffset, BetaOffset; + if(UseMouse){ + BetaOffset = LookLeftRight * TheCamera.m_fMouseAccelHorzntl * FOV/80.0f; + AlphaOffset = LookUpDown * TheCamera.m_fMouseAccelVertical * FOV/80.0f; + }else{ + BetaOffset = LookLeftRight * fStickSens * (1.0f/20.0f) * FOV/80.0f * CTimer::GetTimeStep(); + AlphaOffset = LookUpDown * fStickSens * (0.6f/20.0f) * FOV/80.0f * CTimer::GetTimeStep(); + } + + // Stop centering once stick has been touched + if(BetaOffset) + Rotating = false; + + Beta += BetaOffset; + Alpha += AlphaOffset; + while(Beta >= PI) Beta -= 2.0f*PI; + while(Beta < -PI) Beta += 2.0f*PI; + if(Alpha > DEGTORAD(45.0f)) Alpha = DEGTORAD(45.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + + float BetaDiff = TargetOrientation+PI - Beta; + while(BetaDiff >= PI) BetaDiff -= 2.0f*PI; + while(BetaDiff < -PI) BetaDiff += 2.0f*PI; + float TargetAlpha = Alpha; + // 12deg to account for our little height offset. we're not working on the true alpha here + const float AlphaLimitUp = DEGTORAD(15.0f) + DEGTORAD(12.0f); + const float AlphaLimitDown = -DEGTORAD(15.0f) + DEGTORAD(12.0f); + if(Abs(BetaDiff) < DEGTORAD(25.0f) && ((CPed*)CamTargetEntity)->GetMoveSpeed().Magnitude2D() > 0.01f){ + // Limit alpha when player is walking towards camera + if(TargetAlpha > AlphaLimitUp) TargetAlpha = AlphaLimitUp; + if(TargetAlpha < AlphaLimitDown) TargetAlpha = AlphaLimitDown; + } + + WellBufferMe(TargetAlpha, &Alpha, &AlphaSpeed, 0.2f, 0.1f, true); + + if(CPad::GetPad(0)->ForceCameraBehindPlayer() || Shooting){ + m_fTargetBeta = TargetOrientation; + Rotating = true; + } + + if(Rotating){ + WellBufferMe(m_fTargetBeta, &Beta, &BetaSpeed, 0.1f, 0.06f, true); + float DeltaBeta = m_fTargetBeta - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + if(Abs(DeltaBeta) < 0.06f) + Rotating = false; + } + + + if(TheCamera.m_bUseTransitionBeta) + Beta = CGeneral::GetATanOfXY(-Cos(m_fTransitionBeta), -Sin(m_fTransitionBeta)); + + Front = CVector(Cos(Alpha) * Cos(Beta), Cos(Alpha) * Sin(Beta), Sin(Alpha)); + Source = TargetCoors - Front*CamDist; + TargetCoors.z -= BaseOffset; // now get back to the real target coors again + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + + + Front = TargetCoors - Source; + Front.Normalise(); + + + + /* + * Handle collisions - taken from FollowPedWithMouse + */ + + CEntity *entity; + CColPoint colPoint; + // Clip Source and fix near clip + CWorld::pIgnoreEntity = CamTargetEntity; + entity = nil; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, true, true, true, false, false, true)){ + float PedColDist = (TargetCoors - colPoint.point).Magnitude(); + float ColCamDist = CamDist - PedColDist; + if(entity->IsPed() && ColCamDist > DEFAULT_NEAR + 0.1f){ + // Ped in the way but not clipping through + if(CWorld::ProcessLineOfSight(colPoint.point, Source, colPoint, entity, true, true, true, true, false, false, true)){ + PedColDist = (TargetCoors - colPoint.point).Magnitude(); + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + }else{ + RwCameraSetNearClipPlane(Scene.camera, Min(ColCamDist-0.35f, DEFAULT_NEAR)); + } + }else{ + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + } + } + CWorld::pIgnoreEntity = nil; + + float ViewPlaneHeight = Tan(DEGTORAD(FOV) / 2.0f); + float ViewPlaneWidth = ViewPlaneHeight * CDraw::FindAspectRatio() * fTweakFOV; + float Near = RwCameraGetNearClipPlane(Scene.camera); + float radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, false); + int i = 0; + while(entity){ + CVector CamToCol = gaTempSphereColPoints[0].point - Source; + float frontDist = DotProduct(CamToCol, Front); + float dist = (CamToCol - Front*frontDist).Magnitude() / ViewPlaneWidth; + + // Try to decrease near clip + dist = Max(Min(Near, dist), 0.1f); + if(dist < Near) + RwCameraSetNearClipPlane(Scene.camera, dist); + + // Move forward a bit + if(dist == 0.1f) + Source += (TargetCoors - Source)*0.3f; + + // Keep testing + Near = RwCameraGetNearClipPlane(Scene.camera); + radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, false); + + i++; + if(i > 5) + entity = nil; + } + + GetVectorsReadyForRW(); +} + +// LCS cam hehe +void +CCam::Process_FollowCar_SA(const CVector& CameraTarget, float TargetOrientation, float, float) +{ + // Missing things on III CCam + static CVector m_aTargetHistoryPosOne; + static CVector m_aTargetHistoryPosTwo; + static CVector m_aTargetHistoryPosThree; + static int m_nCurrentHistoryPoints = 0; + static float lastBeta = -9999.0f; + static float lastAlpha = -9999.0f; + static float stepsLeftToChangeBetaByMouse; + static float dontCollideWithCars; + static bool alphaCorrected; + static float heightIncreaseMult; + + if (!CamTargetEntity->IsVehicle()) + return; + + CVehicle* car = (CVehicle*)CamTargetEntity; + CVector TargetCoors = CameraTarget; + uint8 camSetArrPos = 0; + + // We may need those later + bool isPlane = car->GetModelIndex() == MI_DODO; + bool isHeli = false; + bool isBike = false; + bool isCar = car->IsCar() && !isPlane && !isHeli && !isBike; + + CPad* pad = CPad::GetPad(0); + + // Next direction is non-existent in III + uint8 nextDirectionIsForward = !(pad->GetLookBehindForCar() || pad->GetLookBehindForPed() || pad->GetLookLeft() || pad->GetLookRight()) && + DirectionWasLooking == LOOKING_FORWARD; + + if (car->GetModelIndex() == MI_FIRETRUCK) { + camSetArrPos = 7; + } else if (car->GetModelIndex() == MI_RCBANDIT) { + camSetArrPos = 5; + } else if (car->IsBoat()) { + camSetArrPos = 4; + } else if (isBike) { + camSetArrPos = 1; + } else if (isPlane) { + camSetArrPos = 3; + } else if (isHeli) { + camSetArrPos = 2; + } + + // LCS one but index 1(firetruck) moved to last + float CARCAM_SET[][15] = { + {1.3f, 1.0f, 0.4f, 10.0f, 15.0f, 0.5f, 1.0f, 1.0f, 0.85f, 0.2f, 0.075f, 0.05f, 0.8f, DEGTORAD(45.0f), DEGTORAD(89.0f)}, // cars + {1.1f, 1.0f, 0.1f, 10.0f, 11.0f, 0.5f, 1.0f, 1.0f, 0.85f, 0.2f, 0.075f, 0.05f, 0.75f, DEGTORAD(45.0f), DEGTORAD(89.0f)}, // bike + {1.1f, 1.0f, 0.2f, 10.0f, 15.0f, 0.05f, 0.05f, 0.0f, 0.9f, 0.05f, 0.01f, 0.05f, 1.0f, DEGTORAD(10.0f), DEGTORAD(70.0f)}, // heli (SA values) + {1.1f, 1.0f, 0.2f, 10.0f, 15.0f, 0.5f, 1.0f, 1.0f, 0.75f, 0.1f, 0.005f, 0.2f, 1.0f, DEGTORAD(89.0f), DEGTORAD(89.0f)}, // plane (SA values) + {0.9f, 1.0f, 0.1f, 10.0f, 15.0f, 0.5f, 1.0f, 0.0f, 0.9f, 0.05f, 0.005f, 0.05f, 1.0f, -0.2f, DEGTORAD(70.0f)}, // boat + {1.1f, 1.0f, 0.2f, 10.0f, 5.0f, 0.5f, 1.0f, 1.0f, 0.75f, 0.1f, 0.005f, 0.2f, 1.0f, DEGTORAD(45.0f), DEGTORAD(89.0f)}, // rc cars + {1.1f, 1.0f, 0.2f, 10.0f, 5.0f, 0.5f, 1.0f, 1.0f, 0.75f, 0.1f, 0.005f, 0.2f, 1.0f, DEGTORAD(20.0f), DEGTORAD(70.0f)}, // rc heli/planes + {1.3f, 1.0f, 0.4f, 10.0f, 15.0f, 0.5f, 1.0f, 1.0f, 0.85f, 0.2f, 0.075f, 0.05f, 0.8f, -0.18f, DEGTORAD(40.0f)}, // firetruck... + }; + + // RC Heli/planes use same alpha values with heli/planes (LCS firetruck will fallback to 0) + uint8 alphaArrPos = (camSetArrPos > 4 ? (isPlane ? 3 : (isHeli ? 2 : 0)) : camSetArrPos); + float zoomModeAlphaOffset = 0.0f; + static float ZmOneAlphaOffsetLCS[] = { 0.12f, 0.08f, 0.15f, 0.08f, 0.08f }; + static float ZmTwoAlphaOffsetLCS[] = { 0.1f, 0.08f, 0.3f, 0.08f, 0.08f }; + static float ZmThreeAlphaOffsetLCS[] = { 0.065f, 0.05f, 0.15f, 0.06f, 0.08f }; + + if (isHeli && car->GetStatus() == STATUS_PLAYER_REMOTE) + zoomModeAlphaOffset = ZmTwoAlphaOffsetLCS[alphaArrPos]; + else { + switch ((int)TheCamera.CarZoomIndicator) { + // near + case CAM_ZOOM_1: + zoomModeAlphaOffset = ZmOneAlphaOffsetLCS[alphaArrPos]; + break; + // mid + case CAM_ZOOM_2: + zoomModeAlphaOffset = ZmTwoAlphaOffsetLCS[alphaArrPos]; + break; + // far + case CAM_ZOOM_3: + zoomModeAlphaOffset = ZmThreeAlphaOffsetLCS[alphaArrPos]; + break; + default: + break; + } + } + + CColModel* carCol = (CColModel*)car->GetColModel(); + float colMaxZ = carCol->boundingBox.max.z; // As opposed to LCS and SA, VC does this: carCol->boundingBox.max.z - carCol->boundingBox.min.z; + float approxCarLength = 2.0f * Abs(carCol->boundingBox.min.y); // SA taxi min.y = -2.95, max.z = 0.883502f + + float newDistance = TheCamera.CarZoomValueSmooth + CARCAM_SET[camSetArrPos][1] + approxCarLength; + + float minDistForThisCar = approxCarLength * CARCAM_SET[camSetArrPos][3]; + + if (!isHeli || car->GetStatus() == STATUS_PLAYER_REMOTE) { + float radiusToStayOutside = colMaxZ * CARCAM_SET[camSetArrPos][0] - CARCAM_SET[camSetArrPos][2]; + if (radiusToStayOutside > 0.0f) { + TargetCoors.z += radiusToStayOutside; + newDistance += radiusToStayOutside; + zoomModeAlphaOffset += 0.3f / newDistance * radiusToStayOutside; + } + } else { + // 0.6f = fTestShiftHeliCamTarget + TargetCoors += 0.6f * car->GetUp() * colMaxZ; + } + + float minDistForVehType = CARCAM_SET[camSetArrPos][4]; + + if (TheCamera.CarZoomIndicator == CAM_ZOOM_1 && (camSetArrPos < 2 || camSetArrPos == 7)) { + minDistForVehType = minDistForVehType * 0.65f; + } + + float nextDistance = Max(newDistance, minDistForVehType); + + CA_MAX_DISTANCE = newDistance; + CA_MIN_DISTANCE = 3.5f; + + if (ResetStatics) { + FOV = DefaultFOV; + + // GTA 3 has this in veh. camera + if (TheCamera.m_bIdleOn) + TheCamera.m_uiTimeWeEnteredIdle = CTimer::GetTimeInMilliseconds(); + } else { + if (isCar || isBike) { + // 0.4f: CAR_FOV_START_SPEED + if (DotProduct(car->GetForward(), car->m_vecMoveSpeed) > 0.4f) + FOV += (DotProduct(car->GetForward(), car->m_vecMoveSpeed) - 0.4f) * CTimer::GetTimeStep(); + } + + if (FOV > DefaultFOV) + // 0.98f: CAR_FOV_FADE_MULT + FOV = Pow(0.98f, CTimer::GetTimeStep()) * (FOV - DefaultFOV) + DefaultFOV; + + FOV = Clamp(FOV, DefaultFOV, DefaultFOV + 30.0f); + } + + // WORKAROUND: I still don't know how looking behind works (m_bCamDirectlyInFront is unused in III, they seem to use m_bUseTransitionBeta) + if (pad->GetLookBehindForCar()) + if (DirectionWasLooking == LOOKING_FORWARD || !LookingBehind) + TheCamera.m_bCamDirectlyInFront = true; + + // Taken from RotCamIfInFrontCar, because we don't call it anymore + if (!(pad->GetLookBehindForCar() || pad->GetLookBehindForPed() || pad->GetLookLeft() || pad->GetLookRight())) + if (DirectionWasLooking != LOOKING_FORWARD) + TheCamera.m_bCamDirectlyBehind = true; + + // Called when we just entered the car, just started to look behind or returned back from looking left, right or behind + if (ResetStatics || TheCamera.m_bCamDirectlyBehind || TheCamera.m_bCamDirectlyInFront) { + ResetStatics = false; + Rotating = false; + m_bCollisionChecksOn = true; + // TheCamera.m_bResetOldMatrix = 1; + + // Garage exit cam is not working well in III... + // if (!TheCamera.m_bJustCameOutOfGarage) // && !sthForScript) + // { + Alpha = 0.0f; + Beta = car->GetForward().Heading() - HALFPI; + if (TheCamera.m_bCamDirectlyInFront) { + Beta += PI; + } + // } + + BetaSpeed = 0.0; + AlphaSpeed = 0.0; + Distance = 1000.0; + + Front.x = -(Cos(Beta) * Cos(Alpha)); + Front.y = -(Sin(Beta) * Cos(Alpha)); + Front.z = Sin(Alpha); + + m_aTargetHistoryPosOne = TargetCoors - nextDistance * Front; + + m_aTargetHistoryPosTwo = TargetCoors - newDistance * Front; + + m_nCurrentHistoryPoints = 0; + if (!TheCamera.m_bJustCameOutOfGarage) // && !sthForScript) + Alpha = -zoomModeAlphaOffset; + } + + Front = TargetCoors - m_aTargetHistoryPosOne; + Front.Normalise(); + + // Code that makes cam rotate around the car + float camRightHeading = Front.Heading() - HALFPI; + if (camRightHeading < -PI) + camRightHeading = camRightHeading + TWOPI; + + float velocityRightHeading; + if (car->m_vecMoveSpeed.Magnitude2D() <= 0.02f) + velocityRightHeading = camRightHeading; + else + velocityRightHeading = car->m_vecMoveSpeed.Heading() - HALFPI; + + if (velocityRightHeading < camRightHeading - PI) + velocityRightHeading = velocityRightHeading + TWOPI; + else if (velocityRightHeading > camRightHeading + PI) + velocityRightHeading = velocityRightHeading - TWOPI; + + float betaChangeMult1 = CTimer::GetTimeStep() * CARCAM_SET[camSetArrPos][10]; + float betaChangeLimit = CTimer::GetTimeStep() * CARCAM_SET[camSetArrPos][11]; + + float betaChangeMult2 = (car->m_vecMoveSpeed - DotProduct(car->m_vecMoveSpeed, Front) * Front).Magnitude(); + + float betaChange = Min(1.0f, betaChangeMult1 * betaChangeMult2) * (velocityRightHeading - camRightHeading); + if (betaChange <= betaChangeLimit) { + if (betaChange < -betaChangeLimit) + betaChange = -betaChangeLimit; + } else { + betaChange = betaChangeLimit; + } + float targetBeta = camRightHeading + betaChange; + + if (targetBeta < Beta - HALFPI) + targetBeta += TWOPI; + else if (targetBeta > Beta + PI) + targetBeta -= TWOPI; + + float carPosChange = (TargetCoors - m_aTargetHistoryPosTwo).Magnitude(); + if (carPosChange < newDistance && newDistance > minDistForThisCar) { + newDistance = Max(minDistForThisCar, carPosChange); + } + float maxAlphaAllowed = CARCAM_SET[camSetArrPos][13]; + + // Originally this is to prevent camera enter into car while we're stopping, but what about moving??? + // This is also original LCS and SA bug, or some attempt to fix lag. We'll never know + + // if (car->m_vecMoveSpeed.MagnitudeSqr() < sq(0.2f)) + if (car->GetModelIndex() != MI_FIRETRUCK) { + // if (!isBike || GetMysteriousWheelRelatedThingBike(car) > 3) + // if (!isHeli && (!isPlane || car->GetWheelsOnGround())) { + + CVector left = CrossProduct(car->GetForward(), CVector(0.0f, 0.0f, 1.0f)); + left.Normalise(); + CVector up = CrossProduct(left, car->GetForward()); + up.Normalise(); + float lookingUp = DotProduct(up, Front); + if (lookingUp > 0.0f) { + float v88 = Asin(Abs(Sin(Beta - (car->GetForward().Heading() - HALFPI)))); + float v200; + if (v88 <= Atan2(carCol->boundingBox.max.x, -carCol->boundingBox.min.y)) { + v200 = (1.5f - carCol->boundingBox.min.y) / Cos(v88); + } else { + float a6g = 1.2f + carCol->boundingBox.max.x; + v200 = a6g / Cos(Max(0.0f, HALFPI - v88)); + } + maxAlphaAllowed = Cos(Beta - (car->GetForward().Heading() - HALFPI)) * Atan2(car->GetForward().z, car->GetForward().Magnitude2D()) + + Atan2(TargetCoors.z - car->GetPosition().z + car->GetHeightAboveRoad(), v200 * 1.2f); + + if (isCar && ((CAutomobile*)car)->m_nWheelsOnGround > 1 && Abs(DotProduct(car->m_vecTurnSpeed, car->GetForward())) < 0.05f) { + maxAlphaAllowed += Cos(Beta - (car->GetForward().Heading() - HALFPI) + HALFPI) * Atan2(car->GetRight().z, car->GetRight().Magnitude2D()); + } + } + } + + float targetAlpha = Asin(Clamp(Front.z, -1.0f, 1.0f)) - zoomModeAlphaOffset; + if (targetAlpha <= maxAlphaAllowed) { + if (targetAlpha < -CARCAM_SET[camSetArrPos][14]) + targetAlpha = -CARCAM_SET[camSetArrPos][14]; + } else { + targetAlpha = maxAlphaAllowed; + } + float maxAlphaBlendAmount = CTimer::GetTimeStep() * CARCAM_SET[camSetArrPos][6]; + float targetAlphaBlendAmount = (1.0f - Pow(CARCAM_SET[camSetArrPos][5], CTimer::GetTimeStep())) * (targetAlpha - Alpha); + if (targetAlphaBlendAmount <= maxAlphaBlendAmount) { + if (targetAlphaBlendAmount < -maxAlphaBlendAmount) + targetAlphaBlendAmount = -maxAlphaBlendAmount; + } else { + targetAlphaBlendAmount = maxAlphaBlendAmount; + } + + // Using GetCarGun(LR/UD) will give us same unprocessed RightStick value as SA + float stickX = -(pad->GetCarGunLeftRight()); + float stickY = -pad->GetCarGunUpDown(); + + // In SA this is for not let num2/num8 move camera when Keyboard & Mouse controls are used. + // if (CCamera::m_bUseMouse3rdPerson) + // stickY = 0.0f; +#ifdef INVERT_LOOK_FOR_PAD + if (CPad::bInvertLook4Pad) + stickY = -stickY; +#endif + + float xMovement = Abs(stickX) * (FOV / 80.0f * 5.f / 70.f) * stickX * 0.007f * 0.007f; + float yMovement = Abs(stickY) * (FOV / 80.0f * 3.f / 70.f) * stickY * 0.007f * 0.007f; + + bool correctAlpha = true; + // if (SA checks if we aren't in work car, why?) { + if (!isCar || car->GetModelIndex() != MI_YARDIE) { + correctAlpha = false; + } + else { + xMovement = 0.0f; + yMovement = 0.0f; + } + // } else + // yMovement = 0.0; + + if (!nextDirectionIsForward) { + yMovement = 0.0f; + xMovement = 0.0f; + } + + if (camSetArrPos == 0 || camSetArrPos == 7) { + // This is not working on cars as SA + // Because III/VC doesn't have any buttons tied to LeftStick if you're not in Classic Configuration, using Dodo or using GInput/Pad, so :shrug: + if (Abs(pad->GetSteeringUpDown()) > 120.0f) { + if (car->pDriver && car->pDriver->m_objective != OBJECTIVE_LEAVE_CAR) { + yMovement += Abs(pad->GetSteeringUpDown()) * (FOV / 80.0f * 3.f / 70.f) * pad->GetSteeringUpDown() * 0.007f * 0.007f * 0.5; + } + } + } + + if (yMovement > 0.0) + yMovement = yMovement * 0.5; + + bool mouseChangesBeta = false; + + // FIX: Disable mouse movement in drive-by, it's buggy. Original SA bug. + if (/*bFreeMouseCam &&*/ CCamera::m_bUseMouse3rdPerson && !pad->ArePlayerControlsDisabled() && nextDirectionIsForward) { + float mouseY = pad->GetMouseY() * 2.0f; + float mouseX = pad->GetMouseX() * -2.0f; + + // If you want an ability to toggle free cam while steering with mouse, you can add an OR after DisableMouseSteering. + // There was a pad->NewState.m_bVehicleMouseLook in SA, which doesn't exists in III. + + if ((mouseX != 0.0 || mouseY != 0.0) && (CVehicle::m_bDisableMouseSteering)) { + yMovement = mouseY * FOV / 80.0f * TheCamera.m_fMouseAccelHorzntl; // Same as SA, horizontal sensitivity. + BetaSpeed = 0.0; + AlphaSpeed = 0.0; + xMovement = mouseX * FOV / 80.0f * TheCamera.m_fMouseAccelHorzntl; + targetAlpha = Alpha; + stepsLeftToChangeBetaByMouse = 1.0f * 50.0f; + mouseChangesBeta = true; + } else if (stepsLeftToChangeBetaByMouse > 0.0f) { + // Finish rotation by decreasing speed when we stopped moving mouse + BetaSpeed = 0.0; + AlphaSpeed = 0.0; + yMovement = 0.0; + xMovement = 0.0; + targetAlpha = Alpha; + stepsLeftToChangeBetaByMouse = Max(0.0f, stepsLeftToChangeBetaByMouse - CTimer::GetTimeStep()); + mouseChangesBeta = true; + } + } + + if (correctAlpha) { + if (nPreviousMode != MODE_CAM_ON_A_STRING) + alphaCorrected = false; + + if (!alphaCorrected && Abs(zoomModeAlphaOffset + Alpha) > 0.05f) { + yMovement = (-zoomModeAlphaOffset - Alpha) * 0.05f; + } else + alphaCorrected = true; + } + float alphaSpeedFromStickY = yMovement * CARCAM_SET[camSetArrPos][12]; + float betaSpeedFromStickX = xMovement * CARCAM_SET[camSetArrPos][12]; + + float newAngleSpeedMaxBlendAmount = CARCAM_SET[camSetArrPos][9]; + float angleChangeStep = Pow(CARCAM_SET[camSetArrPos][8], CTimer::GetTimeStep()); + float targetBetaWithStickBlendAmount = betaSpeedFromStickX + (targetBeta - Beta) / Max(CTimer::GetTimeStep(), 1.0f); + + if (targetBetaWithStickBlendAmount < -newAngleSpeedMaxBlendAmount) + targetBetaWithStickBlendAmount = -newAngleSpeedMaxBlendAmount; + else if (targetBetaWithStickBlendAmount > newAngleSpeedMaxBlendAmount) + targetBetaWithStickBlendAmount = newAngleSpeedMaxBlendAmount; + + float angleChangeStepLeft = 1.0f - angleChangeStep; + BetaSpeed = targetBetaWithStickBlendAmount * angleChangeStepLeft + angleChangeStep * BetaSpeed; + if (Abs(BetaSpeed) < 0.0001f) + BetaSpeed = 0.0f; + + float betaChangePerFrame; + if (mouseChangesBeta) + betaChangePerFrame = betaSpeedFromStickX; + else + betaChangePerFrame = CTimer::GetTimeStep() * BetaSpeed; + Beta = betaChangePerFrame + Beta; + + if (TheCamera.m_bJustCameOutOfGarage) { + float invHeading = Atan2(Front.y, Front.x); + if (invHeading < 0.0f) + invHeading += TWOPI; + + Beta = invHeading + PI; + } + + Beta = CGeneral::LimitRadianAngle(Beta); + if (Beta < 0.0f) + Beta += TWOPI; + + if ((camSetArrPos <= 1 || camSetArrPos == 7) && targetAlpha < Alpha && carPosChange >= newDistance) { + if (isCar && ((CAutomobile*)car)->m_nWheelsOnGround > 1) + // || isBike && GetMysteriousWheelRelatedThingBike(car) > 1) + alphaSpeedFromStickY += (targetAlpha - Alpha) * 0.075f; + } + + AlphaSpeed = angleChangeStepLeft * alphaSpeedFromStickY + angleChangeStep * AlphaSpeed; + float maxAlphaSpeed = newAngleSpeedMaxBlendAmount; + if (alphaSpeedFromStickY > 0.0f) + maxAlphaSpeed = maxAlphaSpeed * 0.5; + + if (AlphaSpeed <= maxAlphaSpeed) { + float minAlphaSpeed = -maxAlphaSpeed; + if (AlphaSpeed < minAlphaSpeed) + AlphaSpeed = minAlphaSpeed; + } else { + AlphaSpeed = maxAlphaSpeed; + } + + if (Abs(AlphaSpeed) < 0.0001f) + AlphaSpeed = 0.0f; + + float alphaWithSpeedAccounted; + if (mouseChangesBeta) { + alphaWithSpeedAccounted = alphaSpeedFromStickY + targetAlpha; + Alpha += alphaSpeedFromStickY; + } else { + alphaWithSpeedAccounted = CTimer::GetTimeStep() * AlphaSpeed + targetAlpha; + Alpha += targetAlphaBlendAmount; + } + + if (Alpha <= maxAlphaAllowed) { + float minAlphaAllowed = -CARCAM_SET[camSetArrPos][14]; + if (minAlphaAllowed > Alpha) { + Alpha = minAlphaAllowed; + AlphaSpeed = 0.0f; + } + } else { + Alpha = maxAlphaAllowed; + AlphaSpeed = 0.0f; + } + + // Prevent unsignificant angle changes + if (Abs(lastAlpha - Alpha) < 0.0001f) + Alpha = lastAlpha; + + lastAlpha = Alpha; + + if (Abs(lastBeta - Beta) < 0.0001f) + Beta = lastBeta; + + lastBeta = Beta; + + Front.x = -(Cos(Beta) * Cos(Alpha)); + Front.y = -(Sin(Beta) * Cos(Alpha)); + Front.z = Sin(Alpha); + GetVectorsReadyForRW(); + TheCamera.m_bCamDirectlyBehind = false; + TheCamera.m_bCamDirectlyInFront = false; + + Source = TargetCoors - newDistance * Front; + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + m_aTargetHistoryPosThree = m_aTargetHistoryPosOne; + float nextAlpha = alphaWithSpeedAccounted + zoomModeAlphaOffset; + float nextFrontX = -(Cos(Beta) * Cos(nextAlpha)); + float nextFrontY = -(Sin(Beta) * Cos(nextAlpha)); + float nextFrontZ = Sin(nextAlpha); + + m_aTargetHistoryPosOne.x = TargetCoors.x - nextFrontX * nextDistance; + m_aTargetHistoryPosOne.y = TargetCoors.y - nextFrontY * nextDistance; + m_aTargetHistoryPosOne.z = TargetCoors.z - nextFrontZ * nextDistance; + + m_aTargetHistoryPosTwo.x = TargetCoors.x - nextFrontX * newDistance; + m_aTargetHistoryPosTwo.y = TargetCoors.y - nextFrontY * newDistance; + m_aTargetHistoryPosTwo.z = TargetCoors.z - nextFrontZ * newDistance; + + // SA calls SetColVarsVehicle in here + if (nextDirectionIsForward) { + + // LCS uses exactly the same collision code as FollowPedWithMouse, so we will do so. + + // This is only in LCS! + float timestepFactor = Pow(0.99f, CTimer::GetTimeStep()); + dontCollideWithCars = (timestepFactor * dontCollideWithCars) + ((1.0f - timestepFactor) * car->m_vecMoveSpeed.Magnitude()); + + // Our addition +#define IS_TRAFFIC_LIGHT(ent) (ent->IsObject() && (IsStreetLight(ent->GetModelIndex()))) + + // Clip Source and fix near clip + CColPoint colPoint; + CEntity* entity; + CWorld::pIgnoreEntity = CamTargetEntity; + if(CWorld::ProcessLineOfSight(TargetCoors, Source, colPoint, entity, true, dontCollideWithCars < 0.1f, false, true, false, true, true) && !IS_TRAFFIC_LIGHT(entity)){ + float PedColDist = (TargetCoors - colPoint.point).Magnitude(); + float ColCamDist = newDistance - PedColDist; + if(entity->IsPed() && ColCamDist > DEFAULT_NEAR + 0.1f){ + // Ped in the way but not clipping through + if(CWorld::ProcessLineOfSight(colPoint.point, Source, colPoint, entity, true, dontCollideWithCars < 0.1f, false, true, false, true, true) || IS_TRAFFIC_LIGHT(entity)){ + PedColDist = (TargetCoors - colPoint.point).Magnitude(); + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + }else{ + RwCameraSetNearClipPlane(Scene.camera, Min(ColCamDist-0.35f, DEFAULT_NEAR)); + } + }else{ + Source = colPoint.point; + if(PedColDist < DEFAULT_NEAR + 0.3f) + RwCameraSetNearClipPlane(Scene.camera, Max(PedColDist-0.3f, 0.05f)); + } + } + + CWorld::pIgnoreEntity = nil; + + // If we're seeing blue hell due to camera intersects some surface, fix it. + // SA and LCS have this unrolled. + + float ViewPlaneHeight = Tan(DEGTORAD(FOV) / 2.0f); + float ViewPlaneWidth = ViewPlaneHeight * CDraw::FindAspectRatio() * fTweakFOV; + float Near = RwCameraGetNearClipPlane(Scene.camera); + float radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, true); + int i = 0; + while(entity){ + + if (IS_TRAFFIC_LIGHT(entity)) + break; + + CVector CamToCol = gaTempSphereColPoints[0].point - Source; + float frontDist = DotProduct(CamToCol, Front); + float dist = (CamToCol - Front*frontDist).Magnitude() / ViewPlaneWidth; + + // Try to decrease near clip + dist = Max(Min(Near, dist), 0.1f); + if(dist < Near) + RwCameraSetNearClipPlane(Scene.camera, dist); + + // Move forward a bit + if(dist == 0.1f) + Source += (TargetCoors - Source)*0.3f; + + // Keep testing + Near = RwCameraGetNearClipPlane(Scene.camera); + radius = ViewPlaneWidth*Near; + entity = CWorld::TestSphereAgainstWorld(Source + Front*Near, radius, nil, true, true, false, true, false, true); + + i++; + if(i > 5) + entity = nil; + } +#undef IS_TRAFFIC_LIGHT + } + TheCamera.m_bCamDirectlyBehind = false; + TheCamera.m_bCamDirectlyInFront = false; + + // ------- LCS specific part starts + + if (camSetArrPos == 5 && Source.z < 1.0f) // RC Bandit and Baron + Source.z = 1.0f; + + // Obviously some specific place in LC + if (Source.x > 11.0f && Source.x < 91.0f) { + if (Source.y > -680.0f && Source.y < -600.0f && Source.z < 24.4f) + Source.z = 24.4f; + } + + // CCam::FixSourceAboveWaterLevel + if (CameraTarget.z >= -2.0f) { + float level = -6000.0; + // +0.5f is needed for III + if (CWaterLevel::GetWaterLevelNoWaves(Source.x, Source.y, Source.z, &level)) { + if (Source.z < level + 0.5f) + Source.z = level + 0.5f; + } + } + Front = TargetCoors - Source; + + // -------- LCS specific part ends + + GetVectorsReadyForRW(); + // SA + // gTargetCoordsForLookingBehind = TargetCoors; + + // SA code from CAutomobile::TankControl/FireTruckControl. + if (car->GetModelIndex() == MI_RHINO || car->GetModelIndex() == MI_FIRETRUCK) { + + float &carGunLR = ((CAutomobile*)car)->m_fCarGunLR; + CVector hi = Multiply3x3(Front, car->GetMatrix()); + + // III/VC's firetruck turret angle is reversed + float angleToFace = (car->GetModelIndex() == MI_FIRETRUCK ? -hi.Heading() : hi.Heading()); + + if (angleToFace <= carGunLR + PI) { + if (angleToFace < carGunLR - PI) + angleToFace = angleToFace + TWOPI; + } else { + angleToFace = angleToFace - TWOPI; + } + + float neededTurn = angleToFace - carGunLR; + float turnPerFrame = CTimer::GetTimeStep() * (car->GetModelIndex() == MI_FIRETRUCK ? 0.05f : 0.015f); + if (neededTurn <= turnPerFrame) { + if (neededTurn < -turnPerFrame) + angleToFace = carGunLR - turnPerFrame; + } else { + angleToFace = turnPerFrame + carGunLR; + } + + if (car->GetModelIndex() == MI_RHINO && carGunLR != angleToFace) { + DMAudio.PlayOneShot(car->m_audioEntityId, SOUND_CAR_TANK_TURRET_ROTATE, Abs(angleToFace - carGunLR)); + } + carGunLR = angleToFace; + + if (carGunLR < -PI) { + carGunLR += TWOPI; + } else if (carGunLR > PI) { + carGunLR -= TWOPI; + } + + // Because firetruk turret also has Y movement + if (car->GetModelIndex() == MI_FIRETRUCK) { + float &carGunUD = ((CAutomobile*)car)->m_fCarGunUD; + + float alphaToFace = Atan2(hi.z, hi.Magnitude2D()) + DEGTORAD(15.0f); + float neededAlphaTurn = alphaToFace - carGunUD; + float alphaTurnPerFrame = CTimer::GetTimeStep() * 0.02f; + + if (neededAlphaTurn > alphaTurnPerFrame) { + neededTurn = alphaTurnPerFrame; + carGunUD = neededTurn + carGunUD; + } else { + if (neededAlphaTurn >= -alphaTurnPerFrame) { + carGunUD = alphaToFace; + } else { + carGunUD = carGunUD - alphaTurnPerFrame; + } + } + + float turretMinY = -DEGTORAD(20.0f); + float turretMaxY = DEGTORAD(20.0f); + if (turretMinY <= carGunUD) { + if (carGunUD > turretMaxY) + carGunUD = turretMaxY; + } else { + carGunUD = turretMinY; + } + } + } +} +#endif diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp new file mode 100644 index 0000000..f3b4165 --- /dev/null +++ b/src/core/Camera.cpp @@ -0,0 +1,3726 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "World.h" +#include "Vehicle.h" +#include "Train.h" +#include "Automobile.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Pad.h" +#include "ControllerConfig.h" +#include "General.h" +#include "ZoneCull.h" +#include "SurfaceTable.h" +#include "WaterLevel.h" +#include "World.h" +#include "Garages.h" +#include "Replay.h" +#include "CutsceneMgr.h" +#include "Renderer.h" +#include "MBlur.h" +#include "Text.h" +#include "Hud.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "Frontend.h" +#include "SceneEdit.h" +#include "Pools.h" +#include "Debug.h" +#include "GenericGameStorage.h" +#include "MemoryCard.h" +#include "Camera.h" + +enum +{ + // car + OBBE_WHEEL, + OBBE_1, + OBBE_2, + OBBE_3, + OBBE_1STPERSON, // unused + OBBE_5, + OBBE_ONSTRING, + OBBE_COPCAR, + OBBE_COPCAR_WHEEL, + // ped + OBBE_9, + OBBE_10, + OBBE_11, + OBBE_12, + OBBE_13, + + OBBE_INVALID +}; + +// abbreviate a few things +#define PLAYER (CWorld::Players[CWorld::PlayerInFocus].m_pPed) +// NB: removed explicit TheCamera from all functions + +CCamera TheCamera; +#ifdef PC_PLAYER_CONTROLS +bool CCamera::m_bUseMouse3rdPerson = true; +#else +bool CCamera::m_bUseMouse3rdPerson = false; +#endif +bool bDidWeProcessAnyCinemaCam; + +#ifdef IMPROVED_CAMERA +#define KEYJUSTDOWN(k) ControlsManager.GetIsKeyboardKeyJustDown((RsKeyCodes)k) +#define KEYDOWN(k) ControlsManager.GetIsKeyboardKeyDown((RsKeyCodes)k) +#define CTRLJUSTDOWN(key) \ + ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYJUSTDOWN((RsKeyCodes)key) || \ + (KEYJUSTDOWN(rsLCTRL) || KEYJUSTDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) +#define CTRLDOWN(key) ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) +#endif + +CCamera::CCamera(void) +{ +#if GTA_VERSION >= GTA3_PC_11 || defined(FIX_BUGS) + m_fMouseAccelHorzntl = 0.0025f; + m_fMouseAccelVertical = 0.003f; +#endif + Init(); +} + +CCamera::CCamera(float) +{ +} + +void +CCamera::Init(void) +{ +#if GTA_VERSION >= GTA3_PC_11 || defined(FIX_BUGS) + float fMouseAccelHorzntl = m_fMouseAccelHorzntl; + float fMouseAccelVertical = m_fMouseAccelVertical; +#endif + +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad && !FrontEndMenuManager.m_bWantToRestart ) +#endif + { + #ifdef FIX_BUGS + static const CCamera DummyCamera = CCamera(0.f); + *this = DummyCamera; + #else + memset(this, 0, sizeof(CCamera)); // getting rid of vtable, eh? + #endif + + #if GTA_VERSION >= GTA3_PC_11 || defined(FIX_BUGS) + m_fMouseAccelHorzntl = fMouseAccelHorzntl; + m_fMouseAccelVertical = fMouseAccelVertical; + #endif + m_pRwCamera = nil; + + } + + m_1rstPersonRunCloseToAWall = false; + m_fPositionAlongSpline = 0.0f; + m_bCameraJustRestored = false; + Cams[0].Init(); + Cams[1].Init(); + Cams[2].Init(); + Cams[0].Mode = CCam::MODE_FOLLOWPED; + Cams[1].Mode = CCam::MODE_FOLLOWPED; + unknown = 0; + m_bUnknown = false; + ClearPlayerWeaponMode(); + m_bInATunnelAndABigVehicle = false; + m_iModeObbeCamIsInForCar = OBBE_INVALID; + Cams[0].CamTargetEntity = nil; + Cams[1].CamTargetEntity = nil; + Cams[2].CamTargetEntity = nil; + Cams[0].m_fCamBufferedHeight = 0.0f; + Cams[0].m_fCamBufferedHeightSpeed = 0.0f; + Cams[1].m_fCamBufferedHeight = 0.0f; + Cams[1].m_fCamBufferedHeightSpeed = 0.0f; + Cams[0].m_bCamLookingAtVector = false; + Cams[1].m_bCamLookingAtVector = false; + Cams[2].m_bCamLookingAtVector = false; + Cams[0].m_fPlayerVelocity = 0.0f; + Cams[1].m_fPlayerVelocity = 0.0f; + Cams[2].m_fPlayerVelocity = 0.0f; + m_bHeadBob = false; + m_fFractionInterToStopMoving = 0.25f; + m_fFractionInterToStopCatchUp = 0.75f; + m_fGaitSwayBuffer = 0.85f; + m_bScriptParametersSetForInterPol = false; + m_uiCamShakeStart = 0; + m_fCamShakeForce = 0.0f; + m_iModeObbeCamIsInForCar = OBBE_INVALID; + m_bIgnoreFadingStuffForMusic = false; + m_bWaitForInterpolToFinish = false; + pToGarageWeAreIn = nil; + pToGarageWeAreInForHackAvoidFirstPerson = nil; + m_bPlayerIsInGarage = false; + m_bJustCameOutOfGarage = false; + m_fNearClipScript = DEFAULT_NEAR; + m_bUseNearClipScript = false; + m_vecDoingSpecialInterPolation = false; + m_bAboveGroundTrainNodesLoaded = false; + m_bBelowGroundTrainNodesLoaded = false; + m_WideScreenOn = false; + m_fFOV_Wide_Screen = 0.0f; + m_bRestoreByJumpCut = false; + CarZoomIndicator = CAM_ZOOM_2; + PedZoomIndicator = CAM_ZOOM_2; + CarZoomValueSmooth = 0.0f; + m_fPedZoomValueSmooth = 0.0f; + pTargetEntity = nil; + if(FindPlayerVehicle()) + pTargetEntity = FindPlayerVehicle(); + else + pTargetEntity = CWorld::Players[CWorld::PlayerInFocus].m_pPed; + m_bInitialNodeFound = false; + m_ScreenReductionPercentage = 0.0f; + m_ScreenReductionSpeed = 0.0f; + m_WideScreenOn = false; + m_bWantsToSwitchWidescreenOff = false; + WorldViewerBeingUsed = false; + PlayerExhaustion = 1.0f; + DebugCamMode = CCam::MODE_NONE; + m_PedOrientForBehindOrInFront = 0.0f; +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad && !FrontEndMenuManager.m_bWantToRestart ) +#else + if(!FrontEndMenuManager.m_bWantToRestart) +#endif + { + m_bFading = false; + CDraw::FadeValue = 0; + m_fFLOATingFade = 0.0f; + m_bMusicFading = false; + m_fTimeToFadeMusic = 0.0f; + m_fFLOATingFadeMusic = 0.0f; + } + m_bMoveCamToAvoidGeom = false; +#ifdef PS2_MENU + if ( TheMemoryCard.m_bWantToLoad || FrontEndMenuManager.m_bWantToRestart ) +#else + if(FrontEndMenuManager.m_bWantToRestart) +#endif + m_bMoveCamToAvoidGeom = true; + m_bStartingSpline = false; + m_iTypeOfSwitch = INTERPOLATION; + m_bUseScriptZoomValuePed = false; + m_bUseScriptZoomValueCar = false; + m_fPedZoomValueScript = 0.0f; + m_fCarZoomValueScript = 0.0f; + m_bUseSpecialFovTrain = false; + m_fFovForTrain = 70.0f; // or DefaultFOV from Cam.cpp + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + m_bJust_Switched = false; + m_bUseTransitionBeta = false; + GetMatrix().SetScale(1.0f); + m_bTargetJustBeenOnTrain = false; + m_bInitialNoNodeStaticsSet = false; + m_uiLongestTimeInMill = 5000; + m_uiTimeLastChange = 0; + m_uiTimeWeEnteredIdle = 0; + m_bIdleOn = false; + LODDistMultiplier = 1.0f; + m_bCamDirectlyBehind = false; + m_bCamDirectlyInFront = false; + m_motionBlur = 0; + m_bGarageFixedCamPositionSet = false; + SetMotionBlur(255, 255, 255, 0, 0); + m_bCullZoneChecksOn = false; + m_bFailedCullZoneTestPreviously = false; + m_iCheckCullZoneThisNumFrames = 6; + m_iZoneCullFrameNumWereAt = 0; + m_CameraAverageSpeed = 0.0f; + m_CameraSpeedSoFar = 0.0f; + m_PreviousCameraPosition = CVector(0.0f, 0.0f, 0.0f); + m_iWorkOutSpeedThisNumFrames = 4; + m_iNumFramesSoFar = 0; + m_bJustInitalised = true; + m_uiTransitionState = 0; + m_uiTimeTransitionStart = 0; + m_bLookingAtPlayer = true; +#if GTA_VERSION < GTA3_PC_11 && !defined(FIX_BUGS) + m_fMouseAccelHorzntl = 0.0025f; + m_fMouseAccelVertical = 0.003f; +#endif + m_f3rdPersonCHairMultX = 0.53f; + m_f3rdPersonCHairMultY = 0.4f; +} + +void +CCamera::Process(void) +{ + // static bool InterpolatorNotInitialised = true; // unused + static CVector PreviousFudgedTargetCoors; // only PS2 + static float PlayerMinDist = 1.6f; // not on PS2 + static bool WasPreviouslyInterSyhonFollowPed = false; // only used on PS2 + float FOV = 0.0f; + float oldBeta, newBeta; + float deltaBeta = 0.0f; + bool lookLRBVehicle = false; + CVector CamFront, CamUp, CamRight, CamSource, Target; + + m_bJust_Switched = false; + m_RealPreviousCameraPosition = GetPosition(); + + // Update target entity + if(m_bLookingAtPlayer || m_bTargetJustBeenOnTrain || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) + UpdateTargetEntity(); + if(pTargetEntity == nil) + pTargetEntity = FindPlayerPed(); + if(Cams[ActiveCam].CamTargetEntity == nil) + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + if(Cams[(ActiveCam+1)%2].CamTargetEntity == nil) + Cams[(ActiveCam+1)%2].CamTargetEntity = pTargetEntity; + + CamControl(); + if(m_bFading) + ProcessFade(); + if(m_bMusicFading) + ProcessMusicFade(); + if(m_WideScreenOn) + ProcessWideScreenOn(); + +#ifdef IMPROVED_CAMERA + if(CPad::GetPad(1)->GetCircleJustDown() || CTRLJUSTDOWN('B')){ +#else + if(CPad::GetPad(1)->GetCircleJustDown()){ +#endif + WorldViewerBeingUsed = !WorldViewerBeingUsed; + if(WorldViewerBeingUsed) + InitialiseCameraForDebugMode(); + else + CPad::m_bMapPadOneToPadTwo = false; + } + + RwCameraSetNearClipPlane(Scene.camera, DEFAULT_NEAR); + + if(Cams[ActiveCam].Front.x == 0.0f && Cams[ActiveCam].Front.y == 0.0f) + oldBeta = 0.0f; + else + oldBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + + Cams[ActiveCam].Process(); + Cams[ActiveCam].ProcessSpecialHeightRoutines(); + + if(Cams[ActiveCam].Front.x == 0.0f && Cams[ActiveCam].Front.y == 0.0f) + newBeta = 0.0f; + else + newBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + + + // Stop transition when it's done + if(m_uiTransitionState != 0){ +#ifdef PS2_CAM_TRANSITION + if(!m_bWaitForInterpolToFinish){ + Cams[(ActiveCam+1)%2].Process(); + Cams[(ActiveCam+1)%2].ProcessSpecialHeightRoutines(); + } +#else + // done in CamControl on PS2 it seems + if(CTimer::GetTimeInMilliseconds() > m_uiTransitionDuration+m_uiTimeTransitionStart){ + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + m_bWaitForInterpolToFinish = false; + } +#endif + } + + if(m_bUseNearClipScript) + RwCameraSetNearClipPlane(Scene.camera, m_fNearClipScript); + + deltaBeta = newBeta - oldBeta; + while(deltaBeta >= PI) deltaBeta -= 2*PI; + while(deltaBeta < -PI) deltaBeta += 2*PI; + if(Abs(deltaBeta) > 0.3f) + m_bJust_Switched = true; + + // Debug stuff + if(!gbModelViewer) + Cams[ActiveCam].PrintMode(); + if(WorldViewerBeingUsed) + Cams[2].Process(); + + if(Cams[ActiveCam].DirectionWasLooking != LOOKING_FORWARD && pTargetEntity->IsVehicle()) + lookLRBVehicle = true; + + if(m_uiTransitionState != 0 && !lookLRBVehicle){ + // Process transition +#ifdef PS2_CAM_TRANSITION + bool lookingAtPlayerNow = false; + bool wasLookingAtPlayer = false; + bool transitionPedMode = false; + bool setWait = false; + if(Cams[ActiveCam].CamTargetEntity == Cams[(ActiveCam+1)%2].CamTargetEntity){ + if(Cams[ActiveCam].Mode == CCam::MODE_SYPHON || + Cams[ActiveCam].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT || + Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED || + Cams[ActiveCam].Mode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON) + lookingAtPlayerNow = true; + if(Cams[(ActiveCam+1)%2].Mode == CCam::MODE_SYPHON || + Cams[(ActiveCam+1)%2].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT || + Cams[(ActiveCam+1)%2].Mode == CCam::MODE_FOLLOWPED || + Cams[(ActiveCam+1)%2].Mode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON) // checked twice for some reason + wasLookingAtPlayer = true; + + if(!m_vecDoingSpecialInterPolation && + (Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED || Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM) && + (Cams[(ActiveCam+1)%2].Mode == CCam::MODE_FOLLOWPED || Cams[(ActiveCam+1)%2].Mode == CCam::MODE_FIGHT_CAM)) + transitionPedMode = true; + } + + if(lookingAtPlayerNow && wasLookingAtPlayer){ + CVector playerDist; + playerDist.x = FindPlayerPed()->GetPosition().x - GetPosition().x; + playerDist.y = FindPlayerPed()->GetPosition().y - GetPosition().y; + playerDist.z = FindPlayerPed()->GetPosition().z - GetPosition().z; + if(playerDist.Magnitude() > 17.5f && + (Cams[ActiveCam].Mode == CCam::MODE_SYPHON || Cams[ActiveCam].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT)) + setWait = true; + } + if(setWait) + m_bWaitForInterpolToFinish = true; + + + uint32 currentTime = CTimer::GetTimeInMilliseconds() - m_uiTimeTransitionStart; + if(currentTime >= m_uiTransitionDuration) + currentTime = m_uiTransitionDuration; + float inter = (float) currentTime / m_uiTransitionDuration; + inter = 0.5f - 0.5*Cos(inter*PI); // smooth it + + if(m_vecDoingSpecialInterPolation){ + Cams[(ActiveCam+1)%2].Source = m_vecOldSourceForInter; + Cams[(ActiveCam+1)%2].Front = m_vecOldFrontForInter; + Cams[(ActiveCam+1)%2].Up = m_vecOldUpForInter; + Cams[(ActiveCam+1)%2].FOV = m_vecOldFOVForInter; + if(WasPreviouslyInterSyhonFollowPed) + Cams[(ActiveCam+1)%2].m_cvecTargetCoorsForFudgeInter.z = PreviousFudgedTargetCoors.z; + } + + CamSource = inter*Cams[ActiveCam].Source + (1.0f-inter)*Cams[(ActiveCam+1)%2].Source; + FOV = inter*Cams[ActiveCam].FOV + (1.0f-inter)*Cams[(ActiveCam+1)%2].FOV; + + CVector tmpFront = Cams[(ActiveCam+1)%2].Front; + float Alpha_other = CGeneral::GetATanOfXY(tmpFront.Magnitude2D(), tmpFront.z); + if(Alpha_other > PI) Alpha_other -= TWOPI; + float Beta_other = 0.0f; + if(tmpFront.x != 0.0f || tmpFront.y != 0.0f) + Beta_other = CGeneral::GetATanOfXY(-tmpFront.y, tmpFront.x); + tmpFront = Cams[ActiveCam].Front; + float Alpha_active = CGeneral::GetATanOfXY(tmpFront.Magnitude2D(), tmpFront.z); + if(Alpha_active > PI) Alpha_active -= TWOPI; + float Beta_active = 0.0f; + if(tmpFront.x != 0.0f || tmpFront.y != 0.0f) + Beta_active = CGeneral::GetATanOfXY(-tmpFront.y, tmpFront.x); + + float DeltaBeta = Beta_active - Beta_other; + float Alpha = inter*Alpha_active + (1.0f-inter)*Alpha_other; + + if(m_uiTransitionJUSTStarted){ + while(DeltaBeta > PI) DeltaBeta -= TWOPI; + while(DeltaBeta <= -PI) DeltaBeta += TWOPI; + m_uiTransitionJUSTStarted = false; + }else{ + if(DeltaBeta < m_fOldBetaDiff) + while(Abs(DeltaBeta - m_fOldBetaDiff) > PI) DeltaBeta += TWOPI; + else + while(Abs(DeltaBeta - m_fOldBetaDiff) > PI) DeltaBeta -= TWOPI; + } + m_fOldBetaDiff = DeltaBeta; + float Beta = inter*DeltaBeta + Beta_other; + + CVector FudgedTargetCoors; + if(lookingAtPlayerNow && wasLookingAtPlayer){ + // BUG? how is this interpolation ever used when values are overwritten below? + float PlayerDist = (pTargetEntity->GetPosition() - CamSource).Magnitude2D(); + float MinDist = Min(Cams[(ActiveCam+1)%2].m_fMinDistAwayFromCamWhenInterPolating, Cams[ActiveCam].m_fMinDistAwayFromCamWhenInterPolating); + if(PlayerDist < MinDist){ + CamSource.x = pTargetEntity->GetPosition().x - MinDist*Cos(Beta - HALFPI); + CamSource.y = pTargetEntity->GetPosition().y - MinDist*Sin(Beta - HALFPI); + }else{ + CamSource.x = pTargetEntity->GetPosition().x - PlayerDist*Cos(Beta - HALFPI); + CamSource.y = pTargetEntity->GetPosition().y - PlayerDist*Sin(Beta - HALFPI); + } + + CColPoint colpoint; + CEntity *entity = nil; + if(CWorld::ProcessLineOfSight(pTargetEntity->GetPosition(), CamSource, colpoint, entity, true, false, false, true, false, true, true)){ + CamSource = colpoint.point; + RwCameraSetNearClipPlane(Scene.camera, 0.05f); + } + + CamFront = pTargetEntity->GetPosition() - CamSource; + FudgedTargetCoors = inter*Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter + (1.0f-inter)*Cams[(ActiveCam+1)%2].m_cvecTargetCoorsForFudgeInter; + PreviousFudgedTargetCoors = FudgedTargetCoors; + CamFront.Normalise(); + CamUp = CVector(0.0f, 0.0f, 1.0f); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + + WasPreviouslyInterSyhonFollowPed = true; + }else + WasPreviouslyInterSyhonFollowPed = false; + + if(transitionPedMode){ + FudgedTargetCoors = inter*Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter + (1.0f-inter)*Cams[(ActiveCam+1)%2].m_cvecTargetCoorsForFudgeInter; + PreviousFudgedTargetCoors = FudgedTargetCoors; + CVector CamToTarget = pTargetEntity->GetPosition() - CamSource; + float tmpBeta = CGeneral::GetATanOfXY(CamToTarget.x, CamToTarget.y); + float PlayerDist = (pTargetEntity->GetPosition() - CamSource).Magnitude2D(); + float MinDist = Min(Cams[(ActiveCam+1)%2].m_fMinDistAwayFromCamWhenInterPolating, Cams[ActiveCam].m_fMinDistAwayFromCamWhenInterPolating); + if(PlayerDist < MinDist){ + CamSource.x = pTargetEntity->GetPosition().x - MinDist*Cos(tmpBeta - HALFPI); + CamSource.y = pTargetEntity->GetPosition().y - MinDist*Sin(tmpBeta - HALFPI); + } + CamFront = FudgedTargetCoors - CamSource; + CamFront.Normalise(); + CamUp = CVector(0.0f, 0.0f, 1.0f); + CamUp.Normalise(); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + CamUp.Normalise(); + }else{ + CamFront.x = Cos(Alpha) * Sin(Beta); + CamFront.y = Cos(Alpha) * -Cos(Beta); + CamFront.z = Sin(Alpha); + CamFront.Normalise(); + CamUp = inter*Cams[ActiveCam].Up + (1.0f-inter)*Cams[(ActiveCam+1)%2].Up; + CamUp.Normalise(); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + CamUp.Normalise(); + } +#else + uint32 currentTime = CTimer::GetTimeInMilliseconds() - m_uiTimeTransitionStart; + if(currentTime >= m_uiTransitionDuration) + currentTime = m_uiTransitionDuration; + float fractionInter = (float) currentTime / m_uiTransitionDuration; + + if(fractionInter <= m_fFractionInterToStopMoving){ + float inter; + if(m_fFractionInterToStopMoving == 0.0f) + inter = 0.0f; + else + inter = (m_fFractionInterToStopMoving - fractionInter)/m_fFractionInterToStopMoving; + inter = 0.5f - 0.5*Cos(inter*PI); // smooth it + + m_vecSourceWhenInterPol = m_cvecStartingSourceForInterPol + inter*m_cvecSourceSpeedAtStartInter; + m_vecTargetWhenInterPol = m_cvecStartingTargetForInterPol + inter*m_cvecTargetSpeedAtStartInter; + m_vecUpWhenInterPol = m_cvecStartingUpForInterPol + inter*m_cvecUpSpeedAtStartInter; + m_fFOVWhenInterPol = m_fStartingFOVForInterPol + inter*m_fFOVSpeedAtStartInter; + + CamSource = m_vecSourceWhenInterPol; + + if(m_bItsOkToLookJustAtThePlayer){ + m_vecTargetWhenInterPol.x = FindPlayerPed()->GetPosition().x; + m_vecTargetWhenInterPol.y = FindPlayerPed()->GetPosition().y; + m_fBetaWhenInterPol = m_fStartingBetaForInterPol + inter*m_fBetaSpeedAtStartInter; + + float dist = (CamSource - m_vecTargetWhenInterPol).Magnitude2D(); + if(dist < PlayerMinDist){ + if(dist > 0.0f){ + CamSource.x = m_vecTargetWhenInterPol.x + PlayerMinDist*Cos(m_fBetaWhenInterPol); + CamSource.y = m_vecTargetWhenInterPol.y + PlayerMinDist*Sin(m_fBetaWhenInterPol); + }else{ + // can only be 0.0 now... + float beta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + CamSource.x = m_vecTargetWhenInterPol.x + PlayerMinDist*Cos(beta); + CamSource.y = m_vecTargetWhenInterPol.y + PlayerMinDist*Sin(beta); + } + }else{ + CamSource.x = m_vecTargetWhenInterPol.x + dist*Cos(m_fBetaWhenInterPol); + CamSource.y = m_vecTargetWhenInterPol.y + dist*Sin(m_fBetaWhenInterPol); + } + } + + CamFront = m_vecTargetWhenInterPol - CamSource; + StoreValuesDuringInterPol(CamSource, m_vecTargetWhenInterPol, m_vecUpWhenInterPol, m_fFOVWhenInterPol); + Target = m_vecTargetWhenInterPol; + CamFront.Normalise(); + if(m_bLookingAtPlayer) + CamUp = CVector(0.0f, 0.0f, 1.0f); + else + CamUp = m_vecUpWhenInterPol; + CamUp.Normalise(); + + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CamFront.Normalise(); + CamRight = CVector(-1.0f, 0.0f, 0.0f); + CamUp = CrossProduct(CamFront, CamRight); + CamUp.Normalise(); + }else{ + CamFront.Normalise(); + CamUp.Normalise(); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + CamUp.Normalise(); + } + FOV = m_fFOVWhenInterPol; + }else if(fractionInter > m_fFractionInterToStopMoving && fractionInter <= 1.0f){ + float inter; + if(m_fFractionInterToStopCatchUp == 0.0f) + inter = 0.0f; + else + inter = (fractionInter - m_fFractionInterToStopMoving)/m_fFractionInterToStopCatchUp; + inter = 0.5f - 0.5*Cos(inter*PI); // smooth it + + CamSource = m_vecSourceWhenInterPol + inter*(Cams[ActiveCam].Source - m_vecSourceWhenInterPol); + FOV = m_fFOVWhenInterPol + inter*(Cams[ActiveCam].FOV - m_fFOVWhenInterPol); + Target = m_vecTargetWhenInterPol + inter*(Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter - m_vecTargetWhenInterPol); + CamUp = m_vecUpWhenInterPol + inter*(Cams[ActiveCam].Up - m_vecUpWhenInterPol); + deltaBeta = Cams[ActiveCam].m_fTrueBeta - m_fBetaWhenInterPol; + MakeAngleLessThan180(deltaBeta); + float interpBeta = m_fBetaWhenInterPol + inter*deltaBeta; + + if(m_bItsOkToLookJustAtThePlayer){ + Target.x = FindPlayerPed()->GetPosition().x; + Target.y = FindPlayerPed()->GetPosition().y; + + float dist = (CamSource - Target).Magnitude2D(); + if(dist < PlayerMinDist){ + if(dist > 0.0f){ + CamSource.x = Target.x + PlayerMinDist*Cos(interpBeta); + CamSource.y = Target.y + PlayerMinDist*Sin(interpBeta); + }else{ + // can only be 0.0 now... + float beta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + CamSource.x = Target.x + PlayerMinDist*Cos(beta); + CamSource.y = Target.y + PlayerMinDist*Sin(beta); + } + }else{ + CamSource.x = Target.x + dist*Cos(interpBeta); + CamSource.y = Target.y + dist*Sin(interpBeta); + } + } + + CamFront = Target - CamSource; + StoreValuesDuringInterPol(CamSource, Target, CamUp, FOV); + CamFront.Normalise(); + if(m_bLookingAtPlayer) + CamUp = CVector(0.0f, 0.0f, 1.0f); + + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CamFront.Normalise(); + CamRight = CVector(-1.0f, 0.0f, 0.0f); + CamUp = CrossProduct(CamFront, CamRight); + CamUp.Normalise(); + }else{ + CamFront.Normalise(); + CamUp.Normalise(); + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + CamUp.Normalise(); + } +#ifndef FIX_BUGS + // BUG: FOV was already interpolated but m_fFOVWhenInterPol was not + FOV = m_fFOVWhenInterPol; +#endif + } + + CVector Dist = CamSource - Target; + float DistOnGround = Dist.Magnitude2D(); + float Alpha = CGeneral::GetATanOfXY(DistOnGround, Dist.z); + float Beta = CGeneral::GetATanOfXY(Dist.x, Dist.y); + Cams[ActiveCam].KeepTrackOfTheSpeed(CamSource, Target, CamUp, Alpha, Beta, FOV); +#endif + }else{ + // No transition, take Cam values directly + if(WorldViewerBeingUsed){ + CamSource = Cams[2].Source; + CamFront = Cams[2].Front; + CamUp = Cams[2].Up; + FOV = Cams[2].FOV; + }else{ + CamSource = Cams[ActiveCam].Source; + CamFront = Cams[ActiveCam].Front; + CamUp = Cams[ActiveCam].Up; + FOV = Cams[ActiveCam].FOV; + } + WasPreviouslyInterSyhonFollowPed = false; // only used on PS2 + } + + if(m_uiTransitionState != 0) + if(!m_bLookingAtVector && m_bLookingAtPlayer && !CCullZones::CamStairsForPlayer() && !m_bPlayerIsInGarage){ + CEntity *entity = nil; + CColPoint colPoint; + if(CWorld::ProcessLineOfSight(pTargetEntity->GetPosition(), CamSource, colPoint, entity, true, false, false, true, false, true, true)){ + CamSource = colPoint.point; + RwCameraSetNearClipPlane(Scene.camera, 0.05f); + } + } + + GetMatrix().GetRight() = CrossProduct(CamUp, CamFront); // actually Left + GetMatrix().GetForward() = CamFront; + GetMatrix().GetUp() = CamUp; + GetMatrix().GetPosition() = CamSource; + + // Process Shake + float shakeStrength = m_fCamShakeForce - 0.28f*(CTimer::GetTimeInMilliseconds()-m_uiCamShakeStart)/1000.0f; + shakeStrength = Clamp(shakeStrength, 0.0f, 2.0f); + int shakeRand = CGeneral::GetRandomNumber(); + float shakeOffset = shakeStrength*0.1f; + GetMatrix().GetPosition().x += shakeOffset * ((shakeRand & 0xF) - 7); + GetMatrix().GetPosition().y += shakeOffset * (((shakeRand & 0xF0) >> 4) - 7); + GetMatrix().GetPosition().z += shakeOffset * (((shakeRand & 0xF00) >> 8) - 7); + + if(shakeOffset > 0.0f && m_BlurType != MOTION_BLUR_SNIPER) + SetMotionBlurAlpha(Min((int)(shakeStrength*255.0f) + 25, 150)); + if(Cams[ActiveCam].Mode == CCam::MODE_1STPERSON && FindPlayerVehicle() && FindPlayerVehicle()->GetUp().z < 0.2f) + SetMotionBlur(230, 230, 230, 215, MOTION_BLUR_LIGHT_SCENE); + + CalculateDerivedValues(); + CDraw::SetFOV(FOV); + + // Set RW camera + if(WorldViewerBeingUsed){ + RwFrame *frame = RwCameraGetFrame(m_pRwCamera); + CVector Source = Cams[2].Source; + CVector Front = Cams[2].Front; + CVector Up = Cams[2].Up; + + GetMatrix().GetRight() = CrossProduct(Up, Front); + GetMatrix().GetForward() = Front; + GetMatrix().GetUp() = Up; + GetMatrix().GetPosition() = Source; + + CDraw::SetFOV(Cams[2].FOV); + m_vecGameCamPos = Cams[ActiveCam].Source; + + *RwMatrixGetPos(RwFrameGetMatrix(frame)) = GetPosition(); + *RwMatrixGetAt(RwFrameGetMatrix(frame)) = GetForward(); + *RwMatrixGetUp(RwFrameGetMatrix(frame)) = GetUp(); + *RwMatrixGetRight(RwFrameGetMatrix(frame)) = GetRight(); + RwMatrixUpdate(RwFrameGetMatrix(frame)); + RwFrameUpdateObjects(frame); + }else{ + RwFrame *frame = RwCameraGetFrame(m_pRwCamera); + m_vecGameCamPos = GetPosition(); + *RwMatrixGetPos(RwFrameGetMatrix(frame)) = GetPosition(); + *RwMatrixGetAt(RwFrameGetMatrix(frame)) = GetForward(); + *RwMatrixGetUp(RwFrameGetMatrix(frame)) = GetUp(); + *RwMatrixGetRight(RwFrameGetMatrix(frame)) = GetRight(); + RwMatrixUpdate(RwFrameGetMatrix(frame)); + RwFrameUpdateObjects(frame); + } + + CDraw::SetNearClipZ(RwCameraGetNearClipPlane(m_pRwCamera)); + CDraw::SetFarClipZ(RwCameraGetFarClipPlane(m_pRwCamera)); + + UpdateSoundDistances(); + + if((CTimer::GetFrameCounter()&0xF) == 3) + DistanceToWater = CWaterLevel::CalcDistanceToWater(GetPosition().x, GetPosition().y); + + // LOD dist + if(!CCutsceneMgr::IsRunning() || CCutsceneMgr::UseLodMultiplier()){ + LODDistMultiplier = 70.0f/CDraw::GetFOV(); +#ifndef FIX_BUGS + // makes no sense and gone in VC + LODDistMultiplier *= CDraw::GetAspectRatio()/(4.0f/3.0f); +#endif + }else + LODDistMultiplier = 1.0f; +#if GTA_VERSION > GTA3_PS2_160 + GenerationDistMultiplier = LODDistMultiplier; + LODDistMultiplier *= CRenderer::ms_lodDistScale; +#endif + + // Keep track of speed + if(m_bJustInitalised || m_bJust_Switched){ + m_PreviousCameraPosition = GetPosition(); + m_bJustInitalised = false; + } + m_CameraSpeedSoFar += (GetPosition() - m_PreviousCameraPosition).Magnitude(); + m_iNumFramesSoFar++; + if(m_iNumFramesSoFar == m_iWorkOutSpeedThisNumFrames){ + m_CameraAverageSpeed = m_CameraSpeedSoFar / m_iWorkOutSpeedThisNumFrames; + m_CameraSpeedSoFar = 0.0f; + m_iNumFramesSoFar = 0; + } + m_PreviousCameraPosition = GetPosition(); + + // PS2 normalizes a CVector2D GetForward() here. is it used anywhere? + + if(Cams[ActiveCam].DirectionWasLooking != LOOKING_FORWARD && Cams[ActiveCam].Mode != CCam::MODE_TOP_DOWN_PED){ + Cams[ActiveCam].Source = Cams[ActiveCam].SourceBeforeLookBehind; + Orientation += PI; + } + + if(m_uiTransitionState != 0){ + int OtherCam = (ActiveCam+1)%2; + if(Cams[OtherCam].CamTargetEntity && + pTargetEntity && pTargetEntity->IsPed() && + !Cams[OtherCam].CamTargetEntity->IsVehicle() && + Cams[ActiveCam].Mode != CCam::MODE_TOP_DOWN_PED && Cams[ActiveCam].DirectionWasLooking != LOOKING_FORWARD){ + Cams[OtherCam].Source = Cams[ActiveCam%2].SourceBeforeLookBehind; + Orientation += PI; + } + } + + m_bCameraJustRestored = false; +} + +void +CCamera::CamControl(void) +{ + static bool PlaceForFixedWhenSniperFound = false; + static int16 ReqMode; + bool disableGarageCam = false; + bool switchByJumpCut = false; + bool stairs = false; + bool boatTarget = false; + CVector targetPos; + CVector garageCenter, garageDoorPos1, garageDoorPos2; + CVector garageCenterToDoor, garageCamPos; + int whichDoor; + + m_bObbeCinematicPedCamOn = false; + m_bObbeCinematicCarCamOn = false; + m_bUseTransitionBeta = false; + m_bUseSpecialFovTrain = false; + m_bJustCameOutOfGarage = false; + m_bTargetJustCameOffTrain = false; + m_bInATunnelAndABigVehicle = false; + + if(Cams[ActiveCam].CamTargetEntity == nil && pTargetEntity == nil) + pTargetEntity = PLAYER; + +#ifdef PS2_CAM_TRANSITION + // Stop transition when it's done + if(m_uiTransitionState != 0) + if(CTimer::GetTimeInMilliseconds() > m_uiTransitionDuration+m_uiTimeTransitionStart){ + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + m_bWaitForInterpolToFinish = false; + } +#endif + + m_iZoneCullFrameNumWereAt++; + if(m_iZoneCullFrameNumWereAt > m_iCheckCullZoneThisNumFrames) + m_iZoneCullFrameNumWereAt = 1; + m_bCullZoneChecksOn = m_iZoneCullFrameNumWereAt == m_iCheckCullZoneThisNumFrames; + if(m_bCullZoneChecksOn) + m_bFailedCullZoneTestPreviously = CCullZones::CamCloseInForPlayer(); + + if(m_bLookingAtPlayer){ + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_CAMERA); + FindPlayerPed()->bIsVisible = true; + } + + if(!CTimer::GetIsPaused()){ + float CloseInCarHeightTarget = 0.0f; + float CloseInPedHeightTarget = 0.0f; + + if(m_bTargetJustBeenOnTrain){ + // Getting off train + if(!pTargetEntity->IsVehicle() || !((CVehicle*)pTargetEntity)->IsTrain()){ + Restore(); + m_bTargetJustCameOffTrain = true; + m_bTargetJustBeenOnTrain = false; + SetWideScreenOff(); + } + } + + // Vehicle target + if(pTargetEntity->IsVehicle()){ + if(((CVehicle*)pTargetEntity)->IsTrain()){ + if(!m_bTargetJustBeenOnTrain){ + m_bInitialNodeFound = false; + m_bInitialNoNodeStaticsSet = false; + } + Process_Train_Camera_Control(); + }else{ + if(((CVehicle*)pTargetEntity)->IsBoat()) + boatTarget = true; + + // Change user selected mode + if(CPad::GetPad(0)->CycleCameraModeUpJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn) + CarZoomIndicator--; + if(CPad::GetPad(0)->CycleCameraModeDownJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn) + CarZoomIndicator++; + if(!m_bFailedCullZoneTestPreviously){ + if(CarZoomIndicator < CAM_ZOOM_1STPRS) CarZoomIndicator = CAM_ZOOM_CINEMATIC; + else if(CarZoomIndicator > CAM_ZOOM_CINEMATIC) CarZoomIndicator = CAM_ZOOM_1STPRS; + } + + if(m_bFailedCullZoneTestPreviously) + if(CarZoomIndicator != CAM_ZOOM_1STPRS && CarZoomIndicator != CAM_ZOOM_TOPDOWN) + ReqMode = CCam::MODE_CAM_ON_A_STRING; + + switch(((CVehicle*)pTargetEntity)->m_vehType){ + case VEHICLE_TYPE_CAR: + case VEHICLE_TYPE_BIKE: + if(CGarages::IsPointInAGarageCameraZone(pTargetEntity->GetPosition())){ + if(!m_bGarageFixedCamPositionSet && m_bLookingAtPlayer || + WhoIsInControlOfTheCamera == CAMCONTROL_OBBE){ + if(pToGarageWeAreIn){ + float ground; + bool foundGround; + + // This is all very strange.... + // targetPos = pTargetEntity->GetPosition(); // unused + if(pToGarageWeAreIn->m_pDoor1){ + whichDoor = 1; + garageDoorPos1.x = pToGarageWeAreIn->m_fDoor1X; + garageDoorPos1.y = pToGarageWeAreIn->m_fDoor1Y; + garageDoorPos1.z = 0.0f; + // targetPos.z = 0.0f; // unused + // (targetPos - doorPos1).Magnitude(); // unused + }else if(pToGarageWeAreIn->m_pDoor2){ + whichDoor = 2; +#ifdef FIX_BUGS + garageDoorPos2.x = pToGarageWeAreIn->m_fDoor2X; + garageDoorPos2.y = pToGarageWeAreIn->m_fDoor2Y; + garageDoorPos2.z = 0.0f; +#endif + }else{ + whichDoor = 1; + garageDoorPos1.x = pTargetEntity->GetPosition().x; + garageDoorPos1.y = pTargetEntity->GetPosition().y; +#ifdef FIX_BUGS + garageDoorPos1.z = 0.0f; +#else + garageDoorPos2.z = 0.0f; +#endif + } + garageCenter.x = (pToGarageWeAreIn->m_fX1 + pToGarageWeAreIn->m_fX2)/2.0f; + garageCenter.y = (pToGarageWeAreIn->m_fY1 + pToGarageWeAreIn->m_fY2)/2.0f; + garageCenter.z = 0.0f; + if(whichDoor == 1) + garageCenterToDoor = garageDoorPos1 - garageCenter; + else + garageCenterToDoor = garageDoorPos2 - garageCenter; + targetPos = pTargetEntity->GetPosition(); + ground = CWorld::FindGroundZFor3DCoord(targetPos.x, targetPos.y, targetPos.z, &foundGround); + if(!foundGround) + ground = targetPos.z - 0.2f; + garageCenterToDoor.z = 0.0f; + garageCenterToDoor.Normalise(); + if(whichDoor == 1) + garageCamPos = garageDoorPos1 + 13.0f*garageCenterToDoor; + else + garageCamPos = garageDoorPos2 + 13.0f*garageCenterToDoor; + garageCamPos.z = ground + 3.1f; + SetCamPositionForFixedMode(garageCamPos, CVector(0.0f, 0.0f, 0.0f)); + m_bGarageFixedCamPositionSet = true; + } + } + + if(CGarages::CameraShouldBeOutside() && m_bGarageFixedCamPositionSet && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE)){ + if(pToGarageWeAreIn){ + ReqMode = CCam::MODE_FIXED; + m_bPlayerIsInGarage = true; + } + }else{ + if(m_bPlayerIsInGarage){ + m_bJustCameOutOfGarage = true; + m_bPlayerIsInGarage = false; + } + ReqMode = CCam::MODE_CAM_ON_A_STRING; + } + }else{ + if(m_bPlayerIsInGarage){ + m_bJustCameOutOfGarage = true; + m_bPlayerIsInGarage = false; + } + m_bGarageFixedCamPositionSet = false; + ReqMode = CCam::MODE_CAM_ON_A_STRING; + } + break; + case VEHICLE_TYPE_BOAT: + ReqMode = CCam::MODE_BEHINDBOAT; + break; + default: break; + } + + // Car zoom value + if(CarZoomIndicator == CAM_ZOOM_1STPRS && !m_bPlayerIsInGarage){ + CarZoomValue = 0.0f; + ReqMode = CCam::MODE_1STPERSON; + } +#ifdef FREE_CAM + else if (bFreeCam) { + if (CarZoomIndicator == CAM_ZOOM_1) + CarZoomValue = ((CVehicle*)pTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_1 : FREE_CAR_ZOOM_VALUE_1; + else if (CarZoomIndicator == CAM_ZOOM_2) + CarZoomValue = ((CVehicle*)pTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_2 : FREE_CAR_ZOOM_VALUE_2; + else if (CarZoomIndicator == CAM_ZOOM_3) + CarZoomValue = ((CVehicle*)pTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_3 : FREE_CAR_ZOOM_VALUE_3; + } +#endif + else if(CarZoomIndicator == CAM_ZOOM_1) + CarZoomValue = DEFAULT_CAR_ZOOM_VALUE_1; + else if(CarZoomIndicator == CAM_ZOOM_2) + CarZoomValue = DEFAULT_CAR_ZOOM_VALUE_2; + else if(CarZoomIndicator == CAM_ZOOM_3) + CarZoomValue = DEFAULT_CAR_ZOOM_VALUE_3; + + if(CarZoomIndicator == CAM_ZOOM_TOPDOWN && !m_bPlayerIsInGarage){ + CarZoomValue = 1.0f; + ReqMode = CCam::MODE_TOPDOWN; + } + + // Check if we have to go into first person + if(((CVehicle*)pTargetEntity)->IsCar() && !m_bPlayerIsInGarage){ + if(CCullZones::Cam1stPersonForPlayer() && + pTargetEntity->GetColModel()->boundingBox.GetSize().z >= 3.026f && + pToGarageWeAreInForHackAvoidFirstPerson == nil){ + ReqMode = CCam::MODE_1STPERSON; + m_bInATunnelAndABigVehicle = true; + } + } + if(ReqMode == CCam::MODE_TOPDOWN && + (CCullZones::Cam1stPersonForPlayer() || CCullZones::CamNoRain() || CCullZones::PlayerNoRain())) + ReqMode = CCam::MODE_1STPERSON; + + // Smooth zoom value - ugly code + if(m_bUseScriptZoomValueCar){ + if(CarZoomValueSmooth < m_fCarZoomValueScript){ + CarZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Min(CarZoomValueSmooth, m_fCarZoomValueScript); + }else{ + CarZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Max(CarZoomValueSmooth, m_fCarZoomValueScript); + } + }else if(m_bFailedCullZoneTestPreviously){ + CloseInCarHeightTarget = 0.65f; + if(CarZoomValueSmooth < -0.65f){ + CarZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Min(CarZoomValueSmooth, -0.65f); + }else{ + CarZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Max(CarZoomValueSmooth, -0.65f); + } + }else{ + if(CarZoomValueSmooth < CarZoomValue){ + CarZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Min(CarZoomValueSmooth, CarZoomValue); + }else{ + CarZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + CarZoomValueSmooth = Max(CarZoomValueSmooth, CarZoomValue); + } + } + + WellBufferMe(CloseInCarHeightTarget, &Cams[ActiveCam].m_fCloseInCarHeightOffset, &Cams[ActiveCam].m_fCloseInCarHeightOffsetSpeed, 0.1f, 0.25f, false); + + // Fallen into water + if(Cams[ActiveCam].IsTargetInWater(Cams[ActiveCam].Source) && !boatTarget && + !Cams[ActiveCam].CamTargetEntity->IsPed()) + ReqMode = CCam::MODE_PLAYER_FALLEN_WATER; + } + } + + // Ped target + else if(pTargetEntity->IsPed()){ + // Change user selected mode + if(CPad::GetPad(0)->CycleCameraModeUpJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn && !m_bFailedCullZoneTestPreviously){ + if(FrontEndMenuManager.m_ControlMethod == CONTROL_STANDARD){ + if(PedZoomIndicator == CAM_ZOOM_TOPDOWN) + PedZoomIndicator = CAM_ZOOM_1; + else + PedZoomIndicator = CAM_ZOOM_TOPDOWN; + }else + PedZoomIndicator--; + } + if(CPad::GetPad(0)->CycleCameraModeDownJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn && !m_bFailedCullZoneTestPreviously){ + if(FrontEndMenuManager.m_ControlMethod == CONTROL_STANDARD){ + if(PedZoomIndicator == CAM_ZOOM_TOPDOWN) + PedZoomIndicator = CAM_ZOOM_1; + else + PedZoomIndicator = CAM_ZOOM_TOPDOWN; + }else + PedZoomIndicator++; + } + // disabled obbe's cam here + if(PedZoomIndicator < CAM_ZOOM_1) PedZoomIndicator = CAM_ZOOM_TOPDOWN; + else if(PedZoomIndicator > CAM_ZOOM_TOPDOWN) PedZoomIndicator = CAM_ZOOM_1; + + ReqMode = CCam::MODE_FOLLOWPED; + + // Check 1st person mode + if(m_bLookingAtPlayer && pTargetEntity->IsPed() && !m_WideScreenOn && !Cams[0].Using3rdPersonMouseCam() +#ifdef FREE_CAM + && !CCamera::bFreeCam +#endif + ){ + // See if we want to enter first person mode + if(CPad::GetPad(0)->LookAroundLeftRight() || CPad::GetPad(0)->LookAroundUpDown()){ + m_uiFirstPersonCamLastInputTime = CTimer::GetTimeInMilliseconds(); + m_bFirstPersonBeingUsed = true; + }else if(m_bFirstPersonBeingUsed){ + // Or if we want to go back to 3rd person + if(CPad::GetPad(0)->GetPedWalkLeftRight() || CPad::GetPad(0)->GetPedWalkUpDown() || + CPad::GetPad(0)->GetSquare() || CPad::GetPad(0)->GetTriangle() || + CPad::GetPad(0)->GetCross() || CPad::GetPad(0)->GetCircle() || + CTimer::GetTimeInMilliseconds() - m_uiFirstPersonCamLastInputTime > 2850.0f) + m_bFirstPersonBeingUsed = false; + } + }else + m_bFirstPersonBeingUsed = false; + + if(!FindPlayerPed()->IsPedInControl() || FindPlayerPed()->m_fMoveSpeed > 0.0f) + m_bFirstPersonBeingUsed = false; + if(m_bFirstPersonBeingUsed){ + ReqMode = CCam::MODE_1STPERSON; + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_CAMERA); + } + + // Zoom value + if(PedZoomIndicator == CAM_ZOOM_1) + m_fPedZoomValue = 0.25f; + else if(PedZoomIndicator == CAM_ZOOM_2) + m_fPedZoomValue = 1.5f; + else if(PedZoomIndicator == CAM_ZOOM_3) + m_fPedZoomValue = 2.9f; + + // Smooth zoom value - ugly code + if(m_bUseScriptZoomValuePed){ + if(m_fPedZoomValueSmooth < m_fPedZoomValueScript){ + m_fPedZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Min(m_fPedZoomValueSmooth, m_fPedZoomValueScript); + }else{ + m_fPedZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Max(m_fPedZoomValueSmooth, m_fPedZoomValueScript); + } + }else if(m_bFailedCullZoneTestPreviously){ + static float PedZoomedInVal = 0.5f; + CloseInPedHeightTarget = 0.7f; + if(m_fPedZoomValueSmooth < PedZoomedInVal){ + m_fPedZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Min(m_fPedZoomValueSmooth, PedZoomedInVal); + }else{ + m_fPedZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Max(m_fPedZoomValueSmooth, PedZoomedInVal); + } + }else{ + if(m_fPedZoomValueSmooth < m_fPedZoomValue){ + m_fPedZoomValueSmooth += 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Min(m_fPedZoomValueSmooth, m_fPedZoomValue); + }else{ + m_fPedZoomValueSmooth -= 0.12f * CTimer::GetTimeStep(); + m_fPedZoomValueSmooth = Max(m_fPedZoomValueSmooth, m_fPedZoomValue); + } + } + + WellBufferMe(CloseInPedHeightTarget, &Cams[ActiveCam].m_fCloseInPedHeightOffset, &Cams[ActiveCam].m_fCloseInPedHeightOffsetSpeed, 0.1f, 0.025f, false); + + // Check if entering fight cam + if(!m_bFirstPersonBeingUsed){ + if(FindPlayerPed()->GetPedState() == PED_FIGHT && !m_bUseMouse3rdPerson) + ReqMode = CCam::MODE_FIGHT_CAM; + if(((CPed*)pTargetEntity)->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT && + FindPlayerPed()->GetPedState() == PED_ATTACK && !m_bUseMouse3rdPerson) + ReqMode = CCam::MODE_FIGHT_CAM; + } + + // Garage cam + if(CCullZones::CamStairsForPlayer() && CCullZones::FindZoneWithStairsAttributeForPlayer()) + stairs = true; + // Some hack for Mr Whoopee in a bomb shop + if(Cams[ActiveCam].Using3rdPersonMouseCam() && CCollision::ms_collisionInMemory == LEVEL_COMMERCIAL){ + if(pTargetEntity->GetPosition().x < 83.0f && pTargetEntity->GetPosition().x > 18.0f && + pTargetEntity->GetPosition().y < -305.0f && pTargetEntity->GetPosition().y > -390.0f) + disableGarageCam = true; + } + if(!disableGarageCam && (CGarages::IsPointInAGarageCameraZone(pTargetEntity->GetPosition()) || stairs)){ + if(!m_bGarageFixedCamPositionSet && m_bLookingAtPlayer){ + if(pToGarageWeAreIn || stairs){ + float ground; + bool foundGround; + + if(pToGarageWeAreIn){ + // targetPos = pTargetEntity->GetPosition(); // unused + if(pToGarageWeAreIn->m_pDoor1){ + whichDoor = 1; + garageDoorPos1.x = pToGarageWeAreIn->m_fDoor1X; + garageDoorPos1.y = pToGarageWeAreIn->m_fDoor1Y; + garageDoorPos1.z = 0.0f; + // targetPos.z = 0.0f; // unused + // (targetPos - doorPos1).Magnitude(); // unused + }else if(pToGarageWeAreIn->m_pDoor2){ + whichDoor = 2; +#ifdef FIX_BUGS + garageDoorPos2.x = pToGarageWeAreIn->m_fDoor2X; + garageDoorPos2.y = pToGarageWeAreIn->m_fDoor2Y; + garageDoorPos2.z = 0.0f; +#endif + }else{ + whichDoor = 1; + garageDoorPos1.x = pTargetEntity->GetPosition().x; + garageDoorPos1.y = pTargetEntity->GetPosition().y; +#ifdef FIX_BUGS + garageDoorPos1.z = 0.0f; +#else + garageDoorPos2.z = 0.0f; +#endif + } + }else{ + whichDoor = 1; + garageDoorPos1 = Cams[ActiveCam].Source; + } + + if(pToGarageWeAreIn){ + garageCenter.x = (pToGarageWeAreIn->m_fX1 + pToGarageWeAreIn->m_fX2)/2.0f; + garageCenter.y = (pToGarageWeAreIn->m_fY1 + pToGarageWeAreIn->m_fY2)/2.0f; + garageCenter.z = 0.0f; + }else{ + garageDoorPos1.z = 0.0f; + if(stairs){ + CAttributeZone *az = CCullZones::FindZoneWithStairsAttributeForPlayer(); + garageCenter.x = (az->minx + az->maxx)/2.0f; + garageCenter.y = (az->miny + az->maxy)/2.0f; + garageCenter.z = 0.0f; + }else + garageCenter = CVector(pTargetEntity->GetPosition().x, pTargetEntity->GetPosition().y, 0.0f); + } + if(whichDoor == 1) + garageCenterToDoor = garageDoorPos1 - garageCenter; + else + garageCenterToDoor = garageDoorPos2 - garageCenter; + targetPos = pTargetEntity->GetPosition(); + ground = CWorld::FindGroundZFor3DCoord(targetPos.x, targetPos.y, targetPos.z, &foundGround); + if(!foundGround) + ground = targetPos.z - 0.2f; + garageCenterToDoor.z = 0.0f; + garageCenterToDoor.Normalise(); + if(whichDoor == 1){ + if(pToGarageWeAreIn == nil && stairs) + garageCamPos = garageDoorPos1 + 3.75f*garageCenterToDoor; + else + garageCamPos = garageDoorPos1 + 13.0f*garageCenterToDoor; + }else{ + garageCamPos = garageDoorPos2 + 13.0f*garageCenterToDoor; + } + if(PedZoomIndicator == CAM_ZOOM_TOPDOWN && !stairs){ + garageCamPos = garageCenter; + garageCamPos.z += FindPlayerPed()->GetPosition().z + 2.1f; + if(pToGarageWeAreIn && garageCamPos.z > pToGarageWeAreIn->m_fX2) // What? + garageCamPos.z = pToGarageWeAreIn->m_fX2; + }else + garageCamPos.z = ground + 3.1f; + SetCamPositionForFixedMode(garageCamPos, CVector(0.0f, 0.0f, 0.0f)); + m_bGarageFixedCamPositionSet = true; + } + } + + if((CGarages::CameraShouldBeOutside() || stairs) && m_bLookingAtPlayer && m_bGarageFixedCamPositionSet){ + if(pToGarageWeAreIn || stairs){ + ReqMode = CCam::MODE_FIXED; + m_bPlayerIsInGarage = true; + } + }else{ + if(m_bPlayerIsInGarage){ + m_bJustCameOutOfGarage = true; + m_bPlayerIsInGarage = false; + } + ReqMode = CCam::MODE_FOLLOWPED; + } + }else{ + if(m_bPlayerIsInGarage){ + m_bJustCameOutOfGarage = true; + m_bPlayerIsInGarage = false; + } + m_bGarageFixedCamPositionSet = false; + } + + // Fallen into water + if(Cams[ActiveCam].IsTargetInWater(Cams[ActiveCam].Source) && + Cams[ActiveCam].CamTargetEntity->IsPed()) + ReqMode = CCam::MODE_PLAYER_FALLEN_WATER; + + // Set top down + if(PedZoomIndicator == CAM_ZOOM_TOPDOWN && + !CCullZones::Cam1stPersonForPlayer() && + !CCullZones::CamNoRain() && + !CCullZones::PlayerNoRain() && + !m_bFirstPersonBeingUsed && + !m_bPlayerIsInGarage) + ReqMode = CCam::MODE_TOP_DOWN_PED; + + // Weapon mode + if(!CPad::GetPad(0)->GetTarget() && PlayerWeaponMode.Mode != CCam::MODE_HELICANNON_1STPERSON) + ClearPlayerWeaponMode(); + if(m_PlayerMode.Mode != CCam::MODE_NONE) + ReqMode = m_PlayerMode.Mode; + if(PlayerWeaponMode.Mode != CCam::MODE_NONE && !stairs){ + if(PlayerWeaponMode.Mode == CCam::MODE_SNIPER || + PlayerWeaponMode.Mode == CCam::MODE_ROCKETLAUNCHER || + PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON || + PlayerWeaponMode.Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].GetWeaponFirstPersonOn()){ + // First person weapon mode + if(PLAYER->GetPedState() == PED_SEEK_CAR){ + if(ReqMode == CCam::MODE_TOP_DOWN_PED || Cams[ActiveCam].GetWeaponFirstPersonOn()) + ReqMode = PlayerWeaponMode.Mode; + else + ReqMode = CCam::MODE_FOLLOWPED; + }else + ReqMode = PlayerWeaponMode.Mode; + }else if(ReqMode != CCam::MODE_TOP_DOWN_PED){ + // Syphon mode + float playerTargetDist; + float deadPedDist = 4.0f; + static float alivePedDist = 2.0f; // original name lost + float pedDist; // actually only used on dead target + bool targetDead = false; + float camAngle, targetAngle; + CVector playerToTarget = m_cvecAimingTargetCoors - pTargetEntity->GetPosition(); + CVector playerToCam = Cams[ActiveCam].Source - pTargetEntity->GetPosition(); + + if(PedZoomIndicator == CAM_ZOOM_1) + deadPedDist = 2.25f; + if(FindPlayerPed()->m_pPointGunAt){ + // BUG: this need not be a ped! + if(((CPed*)FindPlayerPed()->m_pPointGunAt)->DyingOrDead()){ + targetDead = true; + pedDist = deadPedDist; + }else + pedDist = alivePedDist; + playerTargetDist = playerToTarget.Magnitude2D(); + camAngle = CGeneral::GetATanOfXY(playerToCam.x, playerToCam.y); + targetAngle = CGeneral::GetATanOfXY(playerToTarget.x, playerToTarget.y); + ReqMode = PlayerWeaponMode.Mode; + + // Check whether to start aiming in crim-in-front mode + if(Cams[ActiveCam].Mode != CCam::MODE_SYPHON){ + float angleDiff = camAngle - targetAngle; + while(angleDiff >= PI) angleDiff -= 2*PI; + while(angleDiff < -PI) angleDiff += 2*PI; + if(Abs(angleDiff) < HALFPI && playerTargetDist < 3.5f && playerToTarget.z > -1.0f) + ReqMode = CCam::MODE_SYPHON_CRIM_IN_FRONT; + } + + // Check whether to go to special fixed mode + float fixedModeDist = 0.0f; + if((ReqMode == CCam::MODE_SYPHON_CRIM_IN_FRONT || ReqMode == CCam::MODE_SYPHON) && + (m_uiTransitionState == 0 || Cams[ActiveCam].Mode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON) && + playerTargetDist < pedDist && targetDead){ + if(ReqMode == CCam::MODE_SYPHON_CRIM_IN_FRONT) + fixedModeDist = 5.0f; + else + fixedModeDist = 3.0f; + ReqMode = CCam::MODE_SPECIAL_FIXED_FOR_SYPHON; + } + if(ReqMode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON){ + if(!PlaceForFixedWhenSniperFound){ + // Find position + CEntity *entity; + CColPoint colPoint; + CVector fixedPos = pTargetEntity->GetPosition(); + fixedPos.x += fixedModeDist*Cos(camAngle); + fixedPos.y += fixedModeDist*Sin(camAngle); + fixedPos.z += 1.15f; + if(CWorld::ProcessLineOfSight(pTargetEntity->GetPosition(), fixedPos, colPoint, entity, true, false, false, true, false, true, true)) + SetCamPositionForFixedMode(colPoint.point, CVector(0.0f, 0.0f, 0.0f)); + else + SetCamPositionForFixedMode(fixedPos, CVector(0.0f, 0.0f, 0.0f)); + PlaceForFixedWhenSniperFound = true; + } + }else + PlaceForFixedWhenSniperFound = false; + } + } + } + } + } + + m_bIdleOn = false; + + if(DebugCamMode) + ReqMode = DebugCamMode; + + + // Process arrested player + static int ThePickedArrestMode; + static int LastPedState; + bool startArrestCam = false; + + if(LastPedState != PED_ARRESTED && PLAYER->GetPedState() == PED_ARRESTED){ + if(CarZoomIndicator != CAM_ZOOM_1STPRS && pTargetEntity->IsVehicle()) + startArrestCam = true; + }else + startArrestCam = false; + LastPedState = PLAYER->GetPedState(); + if(startArrestCam){ + if(m_uiTransitionState) + ReqMode = Cams[ActiveCam].Mode; + else{ + bool valid; + if(pTargetEntity->IsPed()){ + // How can this happen if arrest cam is only done in cars? + Cams[(ActiveCam+1)%2].ResetStatics = true; + valid = Cams[(ActiveCam+1)%2].ProcessArrestCamOne(); + ReqMode = CCam::MODE_ARRESTCAM_ONE; + }else{ + Cams[(ActiveCam+1)%2].ResetStatics = true; + valid = Cams[(ActiveCam+1)%2].ProcessArrestCamTwo(); + ReqMode = CCam::MODE_ARRESTCAM_TWO; + } + if(!valid) + ReqMode = Cams[ActiveCam].Mode; + } + } + ThePickedArrestMode = ReqMode; + if(PLAYER->GetPedState() == PED_ARRESTED) + ReqMode = ThePickedArrestMode; // this is rather useless... + + // Process dead player + if(PLAYER->GetPedState() == PED_DEAD){ + if(Cams[ActiveCam].Mode == CCam::MODE_PED_DEAD_BABY) + ReqMode = CCam::MODE_PED_DEAD_BABY; + else{ + bool foundRoof; + CVector pos = FindPlayerPed()->GetPosition(); + CWorld::FindRoofZFor3DCoord(pos.x, pos.y, pos.z, &foundRoof); + if(!foundRoof) + ReqMode = CCam::MODE_PED_DEAD_BABY; + } + } + + // Restore with a jump cut + if(m_bRestoreByJumpCut){ + // PS2 just sets m_bCamDirectlyBehind here + if(ReqMode != CCam::MODE_FOLLOWPED && + ReqMode != CCam::MODE_M16_1STPERSON && + ReqMode != CCam::MODE_SNIPER && + ReqMode != CCam::MODE_ROCKETLAUNCHER || + !m_bUseMouse3rdPerson) + SetCameraDirectlyBehindForFollowPed_CamOnAString(); + + ReqMode = m_iModeToGoTo; + Cams[ActiveCam].Mode = ReqMode; + m_bJust_Switched = true; + Cams[ActiveCam].ResetStatics = true; + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + // PS2 sets this to m_bLookingAtVector + Cams[ActiveCam].m_bCamLookingAtVector = false; + Cams[ActiveCam].m_vecLastAboveWaterCamPosition = Cams[(ActiveCam+1)%2].m_vecLastAboveWaterCamPosition; + m_bRestoreByJumpCut = false; + Cams[ActiveCam].ResetStatics = true; + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + CarZoomValueSmooth = CarZoomValue; + m_fPedZoomValueSmooth = m_fPedZoomValue; + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + } + + if(gbModelViewer) + ReqMode = CCam::MODE_MODELVIEW; + + // Turn on Obbe's cam + bool canUseObbeCam = true; + if(pTargetEntity){ + if(pTargetEntity->IsVehicle()){ + if(CarZoomIndicator == CAM_ZOOM_CINEMATIC) + m_bObbeCinematicCarCamOn = true; + }else{ + if(PedZoomIndicator == CAM_ZOOM_CINEMATIC) + m_bObbeCinematicPedCamOn = true; + } + } + if(m_bTargetJustBeenOnTrain || + ReqMode == CCam::MODE_SYPHON || ReqMode == CCam::MODE_SYPHON_CRIM_IN_FRONT || ReqMode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON || + ReqMode == CCam::MODE_PED_DEAD_BABY || ReqMode == CCam::MODE_ARRESTCAM_ONE || ReqMode == CCam::MODE_ARRESTCAM_TWO || + ReqMode == CCam::MODE_FIGHT_CAM || ReqMode == CCam::MODE_PLAYER_FALLEN_WATER || + ReqMode == CCam::MODE_SNIPER || ReqMode == CCam::MODE_ROCKETLAUNCHER || ReqMode == CCam::MODE_M16_1STPERSON || + ReqMode == CCam::MODE_SNIPER_RUNABOUT || ReqMode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + ReqMode == CCam::MODE_1STPERSON_RUNABOUT || ReqMode == CCam::MODE_M16_1STPERSON_RUNABOUT || + ReqMode == CCam::MODE_FIGHT_CAM_RUNABOUT || ReqMode == CCam::MODE_HELICANNON_1STPERSON || + WhoIsInControlOfTheCamera == CAMCONTROL_SCRIPT || + m_bJustCameOutOfGarage || m_bPlayerIsInGarage) + canUseObbeCam = false; + + if(m_bObbeCinematicPedCamOn && canUseObbeCam) + ProcessObbeCinemaCameraPed(); + else if(m_bObbeCinematicCarCamOn && canUseObbeCam) + ProcessObbeCinemaCameraCar(); + else{ + if(m_bPlayerIsInGarage && m_bObbeCinematicCarCamOn) + switchByJumpCut = true; + canUseObbeCam = false; + DontProcessObbeCinemaCamera(); + } + + // Start the transition or do a jump cut + if(m_bLookingAtPlayer){ + // Going into top down modes normally needs a jump cut (but see below) + if(ReqMode == CCam::MODE_TOPDOWN || ReqMode == CCam::MODE_1STPERSON || ReqMode == CCam::MODE_TOP_DOWN_PED){ + switchByJumpCut = true; + } + // Going from top down to vehicle + else if(ReqMode == CCam::MODE_CAM_ON_A_STRING || ReqMode == CCam::MODE_BEHINDBOAT){ + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) + switchByJumpCut = true; + }else if(ReqMode == CCam::MODE_FIXED){ + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN) + switchByJumpCut = true; + } + + // Top down modes can interpolate between each other + if(ReqMode == CCam::MODE_TOPDOWN){ + if(Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED || Cams[ActiveCam].Mode == CCam::MODE_PED_DEAD_BABY) + switchByJumpCut = false; + }else if(ReqMode == CCam::MODE_TOP_DOWN_PED){ + if(Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || Cams[ActiveCam].Mode == CCam::MODE_PED_DEAD_BABY) + switchByJumpCut = false; + } + + if(ReqMode == CCam::MODE_1STPERSON || ReqMode == CCam::MODE_M16_1STPERSON || + ReqMode == CCam::MODE_SNIPER || ReqMode == CCam::MODE_ROCKETLAUNCHER || + ReqMode == CCam::MODE_SNIPER_RUNABOUT || ReqMode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + ReqMode == CCam::MODE_1STPERSON_RUNABOUT || ReqMode == CCam::MODE_M16_1STPERSON_RUNABOUT || + ReqMode == CCam::MODE_FIGHT_CAM_RUNABOUT || + ReqMode == CCam::MODE_HELICANNON_1STPERSON || + ReqMode == CCam::MODE_ARRESTCAM_ONE || ReqMode == CCam::MODE_ARRESTCAM_TWO){ + // Going into any 1st person mode is a jump cut + if(pTargetEntity->IsPed()) + switchByJumpCut = true; + }else if(ReqMode == CCam::MODE_FIXED && m_bPlayerIsInGarage){ + // Going from 1st peron mode into garage + if(Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED || + stairs || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT){ + if(pTargetEntity && pTargetEntity->IsVehicle()) + switchByJumpCut = true; + } + }else if(ReqMode == CCam::MODE_FOLLOWPED){ + if(Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER || + Cams[ActiveCam].Mode == CCam::MODE_ARRESTCAM_ONE || + Cams[ActiveCam].Mode == CCam::MODE_ARRESTCAM_TWO || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_PED_DEAD_BABY || + Cams[ActiveCam].Mode == CCam::MODE_PILLOWS_PAPS || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || + Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + if(!m_bJustCameOutOfGarage){ + if(Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON){ + float angle = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) - HALFPI; + ((CPed*)pTargetEntity)->m_fRotationCur = angle; + ((CPed*)pTargetEntity)->m_fRotationDest = angle; + } + m_bUseTransitionBeta = true; + switchByJumpCut = true; + if(Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CVector front = Cams[ActiveCam].Source - FindPlayerPed()->GetPosition(); + front.z = 0.0f; // missing on PS2 + front.Normalise(); +#ifdef FIX_BUGS + // this is almost as bad as the bugged code + if(front.x == 0.001f && front.y == 0.001f) + front.y = 1.0f; +#else + // someone used = instead of == in the above check by accident + front.x = 0.001f; + front.y = 1.0f; +#endif + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(front.x, front.y); + }else + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) + PI; + } + } + }else if(ReqMode == CCam::MODE_FIGHT_CAM){ + if(Cams[ActiveCam].Mode == CCam::MODE_1STPERSON) + switchByJumpCut = true; + } + + if(ReqMode != Cams[ActiveCam].Mode && Cams[ActiveCam].CamTargetEntity == nil) + switchByJumpCut = true; + if(m_bPlayerIsInGarage && pToGarageWeAreIn){ + if(pToGarageWeAreIn->m_eGarageType == GARAGE_BOMBSHOP1 || + pToGarageWeAreIn->m_eGarageType == GARAGE_BOMBSHOP2 || + pToGarageWeAreIn->m_eGarageType == GARAGE_BOMBSHOP3){ + if(pTargetEntity->IsVehicle() && pTargetEntity->GetModelIndex() == MI_MRWHOOP && + ReqMode != Cams[ActiveCam].Mode) + switchByJumpCut = true; + } + } +#ifdef GTA_SCENE_EDIT + if(CSceneEdit::m_bEditOn) + ReqMode = CCam::MODE_EDITOR; +#endif + + if((m_uiTransitionState == 0 || switchByJumpCut) && ReqMode != Cams[ActiveCam].Mode){ + if(switchByJumpCut){ + // PS2 just sets m_bCamDirectlyBehind here + if(!m_bPlayerIsInGarage || m_bJustCameOutOfGarage){ + if(ReqMode != CCam::MODE_FOLLOWPED && + ReqMode != CCam::MODE_M16_1STPERSON && + ReqMode != CCam::MODE_SNIPER && + ReqMode != CCam::MODE_ROCKETLAUNCHER || + !m_bUseMouse3rdPerson) + SetCameraDirectlyBehindForFollowPed_CamOnAString(); + } + Cams[ActiveCam].Mode = ReqMode; + m_bJust_Switched = true; + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + Cams[ActiveCam].m_bCamLookingAtVector = m_bLookingAtVector; + Cams[ActiveCam].m_vecLastAboveWaterCamPosition = Cams[(ActiveCam+1)%2].m_vecLastAboveWaterCamPosition; + CarZoomValueSmooth = CarZoomValue; + m_fPedZoomValueSmooth = m_fPedZoomValue; + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + m_bStartInterScript = false; + Cams[ActiveCam].ResetStatics = true; + + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + }else if(!m_bWaitForInterpolToFinish){ + StartTransition(ReqMode); + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + } + }else if(m_uiTransitionState != 0 && ReqMode != Cams[ActiveCam].Mode){ + bool startTransition = true; + + if(ReqMode == CCam::MODE_FIGHT_CAM || Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM) + startTransition = false; + if(ReqMode == CCam::MODE_FOLLOWPED && Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM) + startTransition = false; + +#ifndef PS2_CAM_TRANSITION + // done in Process on PS2 + if(!m_bWaitForInterpolToFinish && m_bLookingAtPlayer && m_uiTransitionState != 0){ + CVector playerDist; + playerDist.x = FindPlayerPed()->GetPosition().x - GetPosition().x; + playerDist.y = FindPlayerPed()->GetPosition().y - GetPosition().y; + playerDist.z = FindPlayerPed()->GetPosition().z - GetPosition().z; + // if player is too far away, keep interpolating and don't transition + if(pTargetEntity && pTargetEntity->IsPed()){ + if(playerDist.Magnitude() > 17.5f && + (ReqMode == CCam::MODE_SYPHON || ReqMode == CCam::MODE_SYPHON_CRIM_IN_FRONT)) + m_bWaitForInterpolToFinish = true; + } + } +#endif + if(m_bWaitForInterpolToFinish) + startTransition = false; + + if(startTransition){ + StartTransitionWhenNotFinishedInter(ReqMode); + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + } + }else if(ReqMode == CCam::MODE_FIXED && pTargetEntity != Cams[ActiveCam].CamTargetEntity && m_bPlayerIsInGarage){ +#ifdef PS2_CAM_TRANSITION + StartTransitionWhenNotFinishedInter(ReqMode); +#else + if(m_uiTransitionState != 0) + StartTransitionWhenNotFinishedInter(ReqMode); + else + StartTransition(ReqMode); +#endif + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + } + }else{ + // not following player + if(m_uiTransitionState == 0 && m_bStartInterScript && m_iTypeOfSwitch == INTERPOLATION){ + ReqMode = m_iModeToGoTo; + StartTransition(ReqMode); + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + }else if(m_uiTransitionState != 0 && m_bStartInterScript && m_iTypeOfSwitch == INTERPOLATION){ + ReqMode = m_iModeToGoTo; + StartTransitionWhenNotFinishedInter(ReqMode); + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + }else if(m_bStartInterScript && m_iTypeOfSwitch == JUMP_CUT){ + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + Cams[ActiveCam].Mode = m_iModeToGoTo; + m_bJust_Switched = true; + Cams[ActiveCam].ResetStatics = true; + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + Cams[ActiveCam].m_bCamLookingAtVector = m_bLookingAtVector; + Cams[ActiveCam].m_vecLastAboveWaterCamPosition = Cams[(ActiveCam+1)%2].m_vecLastAboveWaterCamPosition; + m_bJust_Switched = true; + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + CarZoomValueSmooth = CarZoomValue; + m_fPedZoomValueSmooth = m_fPedZoomValue; + } + } + + m_bStartInterScript = false; + + if(Cams[ActiveCam].CamTargetEntity == nil) + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + + // Ped visibility + if((Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER) && pTargetEntity->IsPed() || + Cams[ActiveCam].Mode == CCam::MODE_FLYBY) + FindPlayerPed()->bIsVisible = false; + else + FindPlayerPed()->bIsVisible = true; + + if(!canUseObbeCam && WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) + Restore(); +} + +// What a mess! +void +CCamera::UpdateTargetEntity(void) +{ + bool enteringCar = false; // not on PS2 but only used as && !enteringCar so we can keep it + bool obbeCam = false; + + if(WhoIsInControlOfTheCamera == CAMCONTROL_OBBE){ + obbeCam = true; + if(m_iModeObbeCamIsInForCar == OBBE_COPCAR_WHEEL || m_iModeObbeCamIsInForCar == OBBE_COPCAR){ + if(FindPlayerPed()->GetPedState() != PED_ARRESTED) + obbeCam = false; + if(FindPlayerVehicle() == nil) + pTargetEntity = FindPlayerPed(); + } + } + + if((m_bLookingAtPlayer || obbeCam) && m_uiTransitionState == 0 || + pTargetEntity == nil || + m_bTargetJustBeenOnTrain){ + if(FindPlayerVehicle()) + pTargetEntity = FindPlayerVehicle(); + else{ + pTargetEntity = FindPlayerPed(); +#ifndef GTA_PS2_STUFF + // this keeps the camera on the player while entering cars + if(PLAYER->GetPedState() == PED_ENTER_CAR || + PLAYER->GetPedState() == PED_CARJACK || + PLAYER->GetPedState() == PED_OPEN_DOOR) + enteringCar = true; + + if(!enteringCar) + if(Cams[ActiveCam].CamTargetEntity != pTargetEntity) + Cams[ActiveCam].CamTargetEntity = pTargetEntity; +#endif + } + + bool cantOpen = true; + if(PLAYER && + PLAYER->m_pMyVehicle && + PLAYER->m_pMyVehicle->CanPedOpenLocks(PLAYER)) + cantOpen = false; + + if(PLAYER->GetPedState() == PED_ENTER_CAR && !cantOpen){ + if(!enteringCar && CarZoomIndicator != CAM_ZOOM_1STPRS){ + pTargetEntity = PLAYER->m_pMyVehicle; + if(PLAYER->m_pMyVehicle == nil) + pTargetEntity = PLAYER; + } + } + + if((PLAYER->GetPedState() == PED_CARJACK || PLAYER->GetPedState() == PED_OPEN_DOOR) && !cantOpen){ + if(!enteringCar && CarZoomIndicator != CAM_ZOOM_1STPRS) +#ifdef GTA_PS2_STUFF +// dunno if this has any amazing effects + { +#endif + pTargetEntity = PLAYER->m_pMyVehicle; + if(PLAYER->m_pMyVehicle == nil) + pTargetEntity = PLAYER; +#ifdef GTA_PS2_STUFF + } +#endif + } + + if(PLAYER->GetPedState() == PED_EXIT_CAR) + pTargetEntity = FindPlayerPed(); + if(PLAYER->GetPedState() == PED_DRAG_FROM_CAR) + pTargetEntity = FindPlayerPed(); + if(pTargetEntity->IsVehicle() && CarZoomIndicator == CAM_ZOOM_1STPRS && FindPlayerPed()->GetPedState() == PED_ARRESTED) + pTargetEntity = FindPlayerPed(); + } +} + +const float SOUND_DIST = 20.0f; + +void +CCamera::UpdateSoundDistances(void) +{ + CVector center, end; + CEntity *entity; + CColPoint colPoint; + float f; + int n; + + if((Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER) && + pTargetEntity->IsPed()) + center = GetPosition() + 0.5f*GetForward(); + else + center = GetPosition() + 5.0f*GetForward(); + + // check up + n = CTimer::GetFrameCounter() % 12; + if(n == 0){ + SoundDistUpAsReadOld = SoundDistUpAsRead; + if(CWorld::ProcessVerticalLine(center, center.z+SOUND_DIST, colPoint, entity, true, false, false, false, true, false, nil)) + SoundDistUpAsRead = colPoint.point.z - center.z; + else + SoundDistUpAsRead = SOUND_DIST; + } + f = (n + 1) / 6.0f; + SoundDistUp = (1.0f-f)*SoundDistUpAsReadOld + f*SoundDistUpAsRead; + + // check left + n = (CTimer::GetFrameCounter()+2) % 12; + if(n == 0){ + SoundDistLeftAsReadOld = SoundDistLeftAsRead; + end = center + SOUND_DIST*GetRight(); + if(CWorld::ProcessLineOfSight(center, end, colPoint, entity, true, false, false, false, true, true, true)) + SoundDistLeftAsRead = (colPoint.point - center).Magnitude(); + else + SoundDistLeftAsRead = SOUND_DIST; + } + f = (n + 1) / 6.0f; + SoundDistLeft = (1.0f-f)*SoundDistLeftAsReadOld + f*SoundDistLeftAsRead; + + // check right + // end = center - SOUND_DIST*GetRight(); // useless + n = (CTimer::GetFrameCounter()+4) % 12; + if(n == 0){ + SoundDistRightAsReadOld = SoundDistRightAsRead; + end = center - SOUND_DIST*GetRight(); + if(CWorld::ProcessLineOfSight(center, end, colPoint, entity, true, false, false, false, true, true, true)) + SoundDistRightAsRead = (colPoint.point - center).Magnitude(); + else + SoundDistRightAsRead = SOUND_DIST; + } + f = (n + 1) / 6.0f; + SoundDistRight = (1.0f-f)*SoundDistRightAsReadOld + f*SoundDistRightAsRead; +} + +void +CCamera::InitialiseCameraForDebugMode(void) +{ + if(FindPlayerVehicle()) + Cams[2].Source = FindPlayerVehicle()->GetPosition(); + else if(FindPlayerPed()) + Cams[2].Source = FindPlayerPed()->GetPosition(); + Cams[2].Alpha = 0.0f; + Cams[2].Beta = 0.0f; + Cams[2].Mode = CCam::MODE_DEBUG; +} + +void +CCamera::CamShake(float strength, float x, float y, float z) +{ + CVector Dist = Cams[ActiveCam].Source - CVector(x, y, z); + // a bit complicated... + float dist2d = Sqrt(SQR(Dist.x) + SQR(Dist.y)); + float dist3d = Sqrt(SQR(dist2d) + SQR(Dist.z)); + if(dist3d > 100.0f) dist3d = 100.0f; + if(dist3d < 0.0f) dist3d = 0.0f; + float mult = 1.0f - dist3d/100.0f; + + float curForce = mult*(m_fCamShakeForce - (CTimer::GetTimeInMilliseconds() - m_uiCamShakeStart)/1000.0f); + strength = mult*strength; + if(Clamp(curForce, 0.0f, 2.0f) < strength){ + m_fCamShakeForce = strength; + m_uiCamShakeStart = CTimer::GetTimeInMilliseconds(); + } +} + +// This seems to be CCamera::CamShake(float) on PS2 +void +CamShakeNoPos(CCamera *cam, float strength) +{ + float curForce = cam->m_fCamShakeForce - (CTimer::GetTimeInMilliseconds() - cam->m_uiCamShakeStart)/1000.0f; + if(Clamp(curForce, 0.0f, 2.0f) < strength){ + cam->m_fCamShakeForce = strength; + cam->m_uiCamShakeStart = CTimer::GetTimeInMilliseconds(); + } +} + + + +void +CCamera::TakeControl(CEntity *target, int16 mode, int16 typeOfSwitch, int32 controller) +{ + bool doSwitch = true; + if(controller == CAMCONTROL_OBBE && WhoIsInControlOfTheCamera == CAMCONTROL_SCRIPT) + doSwitch = false; + if(doSwitch){ + WhoIsInControlOfTheCamera = controller; + if(target){ + if(mode == CCam::MODE_NONE){ + // Why are we checking the old entity? + if(pTargetEntity->IsPed()) + mode = CCam::MODE_FOLLOWPED; + else if(pTargetEntity->IsVehicle()) + mode = CCam::MODE_CAM_ON_A_STRING; + } + }else if(FindPlayerVehicle()) + target = FindPlayerVehicle(); + else + target = PLAYER; + + m_bLookingAtVector = false; + pTargetEntity = target; + m_iModeToGoTo = mode; + m_iTypeOfSwitch = typeOfSwitch; + m_bLookingAtPlayer = false; + m_bStartInterScript = true; + // FindPlayerPed(); // unused + } +} + +void +CCamera::TakeControlNoEntity(const CVector &position, int16 typeOfSwitch, int32 controller) +{ + bool doSwitch = true; + if(controller == CAMCONTROL_OBBE && WhoIsInControlOfTheCamera == CAMCONTROL_SCRIPT) + doSwitch = false; + if(doSwitch){ + WhoIsInControlOfTheCamera = controller; + m_bLookingAtVector = true; + m_bLookingAtPlayer = false; + m_iModeToGoTo = CCam::MODE_FIXED; + m_vecFixedModeVector = position; + m_iTypeOfSwitch = typeOfSwitch; + m_bStartInterScript = true; + } +} + +void +CCamera::TakeControlWithSpline(int16 typeOfSwitch) +{ + m_iModeToGoTo = CCam::MODE_FLYBY; + m_bLookingAtPlayer = false; + m_bLookingAtVector = false; + m_bcutsceneFinished = false; + m_iTypeOfSwitch = typeOfSwitch; + m_bStartInterScript = true; + + //FindPlayerPed(); // unused +}; + +void +CCamera::Restore(void) +{ + m_bLookingAtPlayer = true; + m_bLookingAtVector = false; + m_iTypeOfSwitch = INTERPOLATION; + m_bUseNearClipScript = false; + m_iModeObbeCamIsInForCar = OBBE_INVALID; + m_fPositionAlongSpline = 0.0; + m_bStartingSpline = false; + m_bScriptParametersSetForInterPol = false; + WhoIsInControlOfTheCamera = CAMCONTROL_GAME; + + if(FindPlayerVehicle()){ + m_iModeToGoTo = CCam::MODE_CAM_ON_A_STRING; + pTargetEntity = FindPlayerVehicle(); + }else{ + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + pTargetEntity = PLAYER; + } + + if(PLAYER->GetPedState() == PED_ENTER_CAR || + PLAYER->GetPedState() == PED_CARJACK || + PLAYER->GetPedState() == PED_OPEN_DOOR){ + m_iModeToGoTo = CCam::MODE_CAM_ON_A_STRING; + pTargetEntity = PLAYER->m_pSeekTarget; + } + if(PLAYER->GetPedState() == PED_EXIT_CAR){ + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + pTargetEntity = PLAYER; + } + + m_bUseScriptZoomValuePed = false; + m_bUseScriptZoomValueCar = false; + m_bStartInterScript = true; + m_bCameraJustRestored = true; +} + +void +CCamera::RestoreWithJumpCut(void) +{ + m_bRestoreByJumpCut = true; + m_bLookingAtPlayer = true; + m_bLookingAtVector = false; + m_iTypeOfSwitch = JUMP_CUT; + m_bUseNearClipScript = false; + m_iModeObbeCamIsInForCar = OBBE_INVALID; + m_fPositionAlongSpline = 0.0; + m_bStartingSpline = false; + m_bScriptParametersSetForInterPol = false; + WhoIsInControlOfTheCamera = CAMCONTROL_GAME; + m_bCameraJustRestored = true; + + if(FindPlayerVehicle()){ + m_iModeToGoTo = CCam::MODE_CAM_ON_A_STRING; + pTargetEntity = FindPlayerVehicle(); + }else{ + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + pTargetEntity = PLAYER; + } + + if(PLAYER->GetPedState() == PED_ENTER_CAR || + PLAYER->GetPedState() == PED_CARJACK || + PLAYER->GetPedState() == PED_OPEN_DOOR){ + m_iModeToGoTo = CCam::MODE_CAM_ON_A_STRING; + pTargetEntity = PLAYER->m_pSeekTarget; + } + if(PLAYER->GetPedState() == PED_EXIT_CAR){ + m_iModeToGoTo = CCam::MODE_FOLLOWPED; + pTargetEntity = PLAYER; + } + + m_bUseScriptZoomValuePed = false; + m_bUseScriptZoomValueCar = false; +} + +void +CCamera::SetCamPositionForFixedMode(const CVector &Source, const CVector &UpOffSet) +{ + m_vecFixedModeSource = Source; + m_vecFixedModeUpOffSet = UpOffSet; +} + + + +/* + * On PS2 the transition happens between Cams[0] and Cams[1]. + * On PC the whole system has been changed. + */ +void +CCamera::StartTransition(int16 newMode) +{ + bool switchSyphonMode = false; + bool switchPedToCar = false; + bool switchFromFight = false; + bool switchFromFixed = false; + bool switch1stPersonToVehicle = false; + float betaOffset, targetBeta, camBeta, deltaBeta; + int door; + bool vehicleVertical; + +#ifndef PS2_CAM_TRANSITION + m_bItsOkToLookJustAtThePlayer = false; + m_fFractionInterToStopMoving = 0.25f; + m_fFractionInterToStopCatchUp = 0.75f; + + if(Cams[ActiveCam].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT || + Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED || + Cams[ActiveCam].Mode == CCam::MODE_SYPHON || + Cams[ActiveCam].Mode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON){ + if(newMode == CCam::MODE_SYPHON_CRIM_IN_FRONT || + newMode == CCam::MODE_FOLLOWPED || + newMode == CCam::MODE_SYPHON || + newMode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON) + m_bItsOkToLookJustAtThePlayer = true; + if(newMode == CCam::MODE_CAM_ON_A_STRING) + switchPedToCar = true; + } +#endif + + if(Cams[ActiveCam].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT && newMode == CCam::MODE_SYPHON) + switchSyphonMode = true; + if(Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM && newMode == CCam::MODE_FOLLOWPED) + switchFromFight = true; +#ifndef PS2_CAM_TRANSITION + if(Cams[ActiveCam].Mode == CCam::MODE_FIXED) + switchFromFixed = true; +#endif + + m_bUseTransitionBeta = false; + + if((Cams[ActiveCam].Mode == CCam::MODE_SNIPER || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_SNIPER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_FIGHT_CAM_RUNABOUT || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON_RUNABOUT) && + pTargetEntity->IsPed()){ + float angle = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) - HALFPI; + ((CPed*)pTargetEntity)->m_fRotationCur = angle; + ((CPed*)pTargetEntity)->m_fRotationDest = angle; + } + +#ifdef PS2_CAM_TRANSITION + ActiveCam = (ActiveCam+1)%2; + Cams[ActiveCam].Init(); + Cams[ActiveCam].Mode = newMode; +#endif + + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + Cams[ActiveCam].m_bCamLookingAtVector = m_bLookingAtVector; + + if(newMode == CCam::MODE_SNIPER || + newMode == CCam::MODE_ROCKETLAUNCHER || + newMode == CCam::MODE_M16_1STPERSON || + newMode == CCam::MODE_SNIPER_RUNABOUT || + newMode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || + newMode == CCam::MODE_1STPERSON_RUNABOUT || + newMode == CCam::MODE_M16_1STPERSON_RUNABOUT || + newMode == CCam::MODE_FIGHT_CAM_RUNABOUT || + newMode == CCam::MODE_HELICANNON_1STPERSON) + Cams[ActiveCam].Alpha = 0.0f; + + // PS2 also copies values to ActiveCam here + switch(Cams[ActiveCam].Mode) + case CCam::MODE_SNIPER_RUNABOUT: + case CCam::MODE_ROCKETLAUNCHER_RUNABOUT: + case CCam::MODE_1STPERSON_RUNABOUT: + case CCam::MODE_M16_1STPERSON_RUNABOUT: + case CCam::MODE_FIGHT_CAM_RUNABOUT: + if(newMode == CCam::MODE_CAM_ON_A_STRING || newMode == CCam::MODE_BEHINDBOAT) + switch1stPersonToVehicle = true; + + switch(newMode){ + case CCam::MODE_BEHINDCAR: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; + Cams[ActiveCam].Beta = Cams[(ActiveCam+1)%2].Beta; +#endif + Cams[ActiveCam].BetaSpeed = 0.0f; + break; + + case CCam::MODE_BEHINDBOAT: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; + Cams[ActiveCam].Beta = Cams[(ActiveCam+1)%2].Beta; +#endif + Cams[ActiveCam].BetaSpeed = 0.0f; + break; + + case CCam::MODE_FOLLOWPED: + // Getting out of vehicle normally + betaOffset = DEGTORAD(55.0f); +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + if(m_bJustCameOutOfGarage){ + m_bUseTransitionBeta = true; +/* + // weird logic... + if(CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) + PI; + else if(Cams[ActiveCam].Front.x != 0.0f && Cams[ActiveCam].Front.y != 0.0f) // && is wrong here + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) + PI; + else + Cams[ActiveCam].m_fTransitionBeta = 0.0f; +*/ + // this is better: + if(Cams[ActiveCam].Front.x != 0.0f || Cams[ActiveCam].Front.y != 0.0f) +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[(ActiveCam+1)%2].Front.x, Cams[(ActiveCam+1)%2].Front.y) + PI; +#else + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) + PI; +#endif + else + Cams[ActiveCam].m_fTransitionBeta = 0.0f; + } + if(m_bTargetJustCameOffTrain) + m_bCamDirectlyInFront = true; +#ifdef PS2_CAM_TRANSITION + if(Cams[(ActiveCam+1)%2].Mode != CCam::MODE_CAM_ON_A_STRING) +#else + if(Cams[ActiveCam].Mode != CCam::MODE_CAM_ON_A_STRING) +#endif + break; + m_bUseTransitionBeta = true; + vehicleVertical = false; + if(((CPed*)pTargetEntity)->m_carInObjective && + ((CPed*)pTargetEntity)->m_carInObjective->GetForward().x == 0.0f && + ((CPed*)pTargetEntity)->m_carInObjective->GetForward().y == 0.0f) + vehicleVertical = true; + if(vehicleVertical){ + Cams[ActiveCam].m_fTransitionBeta = 0.0f; + break; + } +#ifdef PS2_CAM_TRANSITION + camBeta = CGeneral::GetATanOfXY(Cams[(ActiveCam+1)%2].Front.x, Cams[(ActiveCam+1)%2].Front.y); +#else + camBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); +#endif + if(((CPed*)pTargetEntity)->m_carInObjective) + targetBeta = CGeneral::GetATanOfXY(((CPed*)pTargetEntity)->m_carInObjective->GetForward().x, ((CPed*)pTargetEntity)->m_carInObjective->GetForward().y); + else + targetBeta = camBeta; + deltaBeta = targetBeta - camBeta; + while(deltaBeta >= PI) deltaBeta -= 2*PI; + while(deltaBeta < -PI) deltaBeta += 2*PI; + deltaBeta = Abs(deltaBeta); + + door = FindPlayerPed()->m_vehDoor; + if(deltaBeta > HALFPI){ + if(((CPed*)pTargetEntity)->m_carInObjective){ + if(((CPed*)pTargetEntity)->m_carInObjective->IsUpsideDown()){ + if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = -DEGTORAD(95.0f); + }else{ + if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = -DEGTORAD(95.0f); + } + } + Cams[ActiveCam].m_fTransitionBeta = targetBeta + betaOffset; + }else{ + if(((CPed*)pTargetEntity)->m_carInObjective){ + if(((CPed*)pTargetEntity)->m_carInObjective->IsUpsideDown()){ + if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = -DEGTORAD(55.0f); + else if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = DEGTORAD(95.0f); + }else{ + if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = -DEGTORAD(55.0f); + else if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = DEGTORAD(95.0f); + } + } + Cams[ActiveCam].m_fTransitionBeta = targetBeta + betaOffset + PI; + } + break; + + case CCam::MODE_SNIPER: + case CCam::MODE_ROCKETLAUNCHER: + case CCam::MODE_M16_1STPERSON: + case CCam::MODE_SNIPER_RUNABOUT: + case CCam::MODE_ROCKETLAUNCHER_RUNABOUT: + case CCam::MODE_1STPERSON_RUNABOUT: + case CCam::MODE_M16_1STPERSON_RUNABOUT: + case CCam::MODE_FIGHT_CAM_RUNABOUT: + case CCam::MODE_HELICANNON_1STPERSON: + if(FindPlayerVehicle()) + Cams[ActiveCam].Beta = Atan2(FindPlayerVehicle()->GetForward().x, FindPlayerVehicle()->GetForward().y); + else + Cams[ActiveCam].Beta = Atan2(PLAYER->GetForward().x, PLAYER->GetForward().y); + break; + + case CCam::MODE_SYPHON: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Beta = Cams[(ActiveCam+1)%2].Beta; + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + Cams[ActiveCam].Alpha = 0.0f; + Cams[ActiveCam].AlphaSpeed = 0.0f; + break; + + case CCam::MODE_CAM_ON_A_STRING: + // Get into vehicle + betaOffset = DEGTORAD(57.0f); +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + if(!m_bLookingAtPlayer || m_bJustCameOutOfGarage) + break; + m_bUseTransitionBeta = true; + targetBeta = CGeneral::GetATanOfXY(pTargetEntity->GetForward().x, pTargetEntity->GetForward().y); +#ifdef PS2_CAM_TRANSITION + camBeta = CGeneral::GetATanOfXY(Cams[(ActiveCam+1)%2].Front.x, Cams[(ActiveCam+1)%2].Front.y); +#else + camBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); +#endif + deltaBeta = targetBeta - camBeta; + while(deltaBeta >= PI) deltaBeta -= 2*PI; + while(deltaBeta < -PI) deltaBeta += 2*PI; + deltaBeta = Abs(deltaBeta); +#ifndef PS2_CAM_TRANSITION + switchFromFixed = Cams[ActiveCam].Mode == CCam::MODE_FIXED; + if(switchFromFixed){ + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + break; + } +#endif + + door = FindPlayerPed()->m_vehDoor; + if(deltaBeta > HALFPI){ + if(((CVehicle*)pTargetEntity)->IsUpsideDown()){ + if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) // BUG: game checks LF twice + betaOffset = -DEGTORAD(57.0f); + }else{ + if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = -DEGTORAD(57.0f); + } + Cams[ActiveCam].m_fTransitionBeta = targetBeta + betaOffset + PI; + }else{ + if(((CVehicle*)pTargetEntity)->IsUpsideDown()){ + if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = -DEGTORAD(57.0f); + else if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = DEGTORAD(57.0f); + }else{ + if(door == CAR_DOOR_LF || door == CAR_DOOR_LR) + betaOffset = -DEGTORAD(57.0f); + else if(door == CAR_DOOR_RF || door == CAR_DOOR_RR) + betaOffset = DEGTORAD(57.0f); + } + Cams[ActiveCam].m_fTransitionBeta = targetBeta + betaOffset; + } + break; + + case CCam::MODE_PED_DEAD_BABY: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + Cams[ActiveCam].Alpha = DEGTORAD(15.0f); + break; + +#ifdef PS2_CAM_TRANSITION + case CCam::MODE_PLAYER_FALLEN_WATER: + Cams[ActiveCam].m_vecLastAboveWaterCamPosition = Cams[(ActiveCam+1)%2].m_vecLastAboveWaterCamPosition; + break; +#endif + + case CCam::MODE_FIGHT_CAM: +#ifdef PS2_CAM_TRANSITION + Cams[ActiveCam].Source = Cams[(ActiveCam+1)%2].Source; +#endif + Cams[ActiveCam].Beta = 0.0f; + Cams[ActiveCam].BetaSpeed = 0.0f; + Cams[ActiveCam].Alpha = 0.0f; + Cams[ActiveCam].AlphaSpeed = 0.0f; + break; + } + +#ifndef PS2_CAM_TRANSITION + Cams[ActiveCam].Init(); + Cams[ActiveCam].Mode = newMode; + + m_uiTransitionDuration = 1350; + if(switchSyphonMode) + m_uiTransitionDuration = 1800; + else if(switchFromFight) + m_uiTransitionDuration = 750; + else if(switchPedToCar){ + m_fFractionInterToStopMoving = 0.2f; + m_fFractionInterToStopCatchUp = 0.8f; + m_uiTransitionDuration = 950; + }else if(switchFromFixed){ + m_fFractionInterToStopMoving = 0.05f; + m_fFractionInterToStopCatchUp = 0.95f; + }else if(switch1stPersonToVehicle){ + m_fFractionInterToStopMoving = 0.0f; + m_fFractionInterToStopCatchUp = 1.0f; + m_uiTransitionDuration = 1; + }else + m_uiTransitionDuration = 1350; // already set above +#else + if(switchSyphonMode) + m_uiTransitionDuration = 1800; + else if(switchFromFight) + m_uiTransitionDuration = 750; + else + m_uiTransitionDuration = 1350; +#endif + m_uiTransitionState = 1; + m_uiTimeTransitionStart = CTimer::GetTimeInMilliseconds(); + m_uiTransitionJUSTStarted = 1; +#ifndef PS2_CAM_TRANSITION + if(m_vecDoingSpecialInterPolation){ + m_cvecStartingSourceForInterPol = SourceDuringInter; + m_cvecStartingTargetForInterPol = TargetDuringInter; + m_cvecStartingUpForInterPol = UpDuringInter; + m_fStartingAlphaForInterPol = m_fAlphaDuringInterPol; + m_fStartingBetaForInterPol = m_fBetaDuringInterPol; + }else{ + m_cvecStartingSourceForInterPol = Cams[ActiveCam].Source; + m_cvecStartingTargetForInterPol = Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter; + m_cvecStartingUpForInterPol = Cams[ActiveCam].Up; + m_fStartingAlphaForInterPol = Cams[ActiveCam].m_fTrueAlpha; + m_fStartingBetaForInterPol = Cams[ActiveCam].m_fTrueBeta; + } + Cams[ActiveCam].m_bCamLookingAtVector = m_bLookingAtVector; + Cams[ActiveCam].m_cvecCamFixedModeVector = m_vecFixedModeVector; + Cams[ActiveCam].m_cvecCamFixedModeSource = m_vecFixedModeSource; + Cams[ActiveCam].m_cvecCamFixedModeUpOffSet = m_vecFixedModeUpOffSet; + Cams[ActiveCam].Mode = newMode; // already done above + Cams[ActiveCam].CamTargetEntity = pTargetEntity; + m_uiTransitionState = 1; // these three already done above + m_uiTimeTransitionStart = CTimer::GetTimeInMilliseconds(); + m_uiTransitionJUSTStarted = 1; + m_fStartingFOVForInterPol = Cams[ActiveCam].FOV; + m_cvecSourceSpeedAtStartInter = Cams[ActiveCam].m_cvecSourceSpeedOverOneFrame; + m_cvecTargetSpeedAtStartInter = Cams[ActiveCam].m_cvecTargetSpeedOverOneFrame; + m_cvecUpSpeedAtStartInter = Cams[ActiveCam].m_cvecUpOverOneFrame; + m_fAlphaSpeedAtStartInter = Cams[ActiveCam].m_fAlphaSpeedOverOneFrame; + m_fBetaSpeedAtStartInter = Cams[ActiveCam].m_fBetaSpeedOverOneFrame; + m_fFOVSpeedAtStartInter = Cams[ActiveCam].m_fFovSpeedOverOneFrame; + Cams[ActiveCam].ResetStatics = true; + if(!m_bLookingAtPlayer && m_bScriptParametersSetForInterPol){ + m_fFractionInterToStopMoving = m_fScriptPercentageInterToStopMoving; + m_fFractionInterToStopCatchUp = m_fScriptPercentageInterToCatchUp; + m_uiTransitionDuration = m_fScriptTimeForInterPolation; + } +#endif +} + +void +CCamera::StartTransitionWhenNotFinishedInter(int16 mode) +{ +#ifdef PS2_CAM_TRANSITION + m_vecOldSourceForInter = GetPosition(); + m_vecOldFrontForInter = GetForward(); + m_vecOldUpForInter = GetUp(); + m_vecOldFOVForInter = CDraw::GetFOV(); +#endif + m_vecDoingSpecialInterPolation = true; + StartTransition(mode); +} + +#ifndef PS2_CAM_TRANSITION +void +CCamera::StoreValuesDuringInterPol(CVector &source, CVector &target, CVector &up, float &FOV) +{ + SourceDuringInter = source; + TargetDuringInter = target; + UpDuringInter = up; + FOVDuringInter = FOV; + CVector Dist = source - TargetDuringInter; + float DistOnGround = Dist.Magnitude2D(); + m_fBetaDuringInterPol = CGeneral::GetATanOfXY(Dist.x, Dist.y); + m_fAlphaDuringInterPol = CGeneral::GetATanOfXY(DistOnGround, Dist.z); +} +#endif + + +void +CCamera::SetWideScreenOn(void) +{ + m_WideScreenOn = true; +} + +void +CCamera::SetWideScreenOff(void) +{ + m_bWantsToSwitchWidescreenOff = m_WideScreenOn; +} + +void +CCamera::ProcessWideScreenOn(void) +{ + if(m_bWantsToSwitchWidescreenOff){ + m_bWantsToSwitchWidescreenOff = false; + m_WideScreenOn = false; + m_ScreenReductionPercentage = 0.0f; + m_fFOV_Wide_Screen = 0.0f; + m_fWideScreenReductionAmount = 0.0f; + }else{ + m_fFOV_Wide_Screen = 0.3f*Cams[ActiveCam].FOV; + m_fWideScreenReductionAmount = 1.0f; + m_ScreenReductionPercentage = 30.0f; + } +} + +void +CCamera::DrawBordersForWideScreen(void) +{ + if(m_BlurType == MOTION_BLUR_NONE || m_BlurType == MOTION_BLUR_LIGHT_SCENE) + SetMotionBlurAlpha(80); + + CSprite2d::DrawRect( +#ifdef FIX_BUGS + CRect(0.0f, (SCREEN_HEIGHT/2) * m_ScreenReductionPercentage/100.0f - SCREEN_SCALE_Y(8.0f), +#else + CRect(0.0f, (SCREEN_HEIGHT/2) * m_ScreenReductionPercentage/100.0f - 8.0f, +#endif + SCREEN_WIDTH, 0.0f), + CRGBA(0, 0, 0, 255)); + + CSprite2d::DrawRect( + CRect(0.0f, SCREEN_HEIGHT, +#ifdef FIX_BUGS + SCREEN_WIDTH, SCREEN_HEIGHT - (SCREEN_HEIGHT/2) * m_ScreenReductionPercentage/100.0f - SCREEN_SCALE_Y(8.0f)), +#else + SCREEN_WIDTH, SCREEN_HEIGHT - (SCREEN_HEIGHT/2) * m_ScreenReductionPercentage/100.0f - 8.0f), +#endif + CRGBA(0, 0, 0, 255)); +} + + + +bool +CCamera::IsItTimeForNewcam(int32 obbeMode, int32 time) +{ + CVehicle *veh; + uint32 t = time; // no annoying compiler warnings + CVector fwd; + + if(obbeMode < 0) + return true; + switch(obbeMode){ + case OBBE_WHEEL: + veh = FindPlayerVehicle(); + if(veh){ + if(veh->IsBoat() || veh->GetModelIndex() == MI_RHINO) + return true; + if(!CWorld::GetIsLineOfSightClear(pTargetEntity->GetPosition(), Cams[ActiveCam].Source, true, false, false, false, false, false, false)) + return true; + } + if(CTimer::GetTimeInMilliseconds() > t+5000) + return true; + SetNearClipScript(0.6f); + return false; + case OBBE_1: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return true; + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 20.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + // too close + if(fwd.Magnitude() < 1.6f) + return true; + return false; + case OBBE_2: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return true; + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + if(fwd.Magnitude() < 2.0f) + // very close, fix near clip + SetNearClipScript(Max(fwd.Magnitude()*0.5f, 0.05f)); + // too far and driving away from cam + if(fwd.Magnitude() > 19.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + // too close + if(fwd.Magnitude() < 1.6f) + return true; + return false; + case OBBE_3: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 28.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_1STPERSON: + return CTimer::GetTimeInMilliseconds() > t+3000; + case OBBE_5: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return true; + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 28.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_ONSTRING: + return CTimer::GetTimeInMilliseconds() > t+3000; + case OBBE_COPCAR: + return CTimer::GetTimeInMilliseconds() > t+2000 && !FindPlayerVehicle()->GetIsOnScreen(); + case OBBE_COPCAR_WHEEL: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return true; + if(!CWorld::GetIsLineOfSightClear(pTargetEntity->GetPosition(), Cams[ActiveCam].Source, true, false, false, false, false, false, false)) + return true; + if(CTimer::GetTimeInMilliseconds() > t+1000) + return true; + SetNearClipScript(0.6f); + return false; + + // Ped modes + case OBBE_9: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 20.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_10: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 8.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_11: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 25.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_12: + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + + fwd = FindPlayerCoors() - m_vecFixedModeSource; + fwd.z = 0.0f; + + // too far and driving away from cam + if(fwd.Magnitude() > 8.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + return false; + case OBBE_13: + return CTimer::GetTimeInMilliseconds() > t+5000; + default: + return false; + } +} + +bool +CCamera::TryToStartNewCamMode(int obbeMode) +{ + CVehicle *veh; + CVector target, camPos, playerSpeed, fwd; + float ground; + bool foundGround; + int i; + + if(obbeMode < 0) + return true; + switch(obbeMode){ + case OBBE_WHEEL: + veh = FindPlayerVehicle(); + if(veh == nil || veh->IsBoat() || veh->GetModelIndex() == MI_RHINO) + return false; + target = Multiply3x3(FindPlayerVehicle()->GetMatrix(), CVector(-1.4f, -2.3f, 0.3f)); + target += FindPlayerVehicle()->GetPosition(); + if(!CWorld::GetIsLineOfSightClear(veh->GetPosition(), target, true, false, false, false, false, false, false)) + return false; + TakeControl(veh, CCam::MODE_WHEELCAM, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_1: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 20.0f*playerSpeed; + camPos += 3.0f*CVector(playerSpeed.y, -playerSpeed.x, 0.0f); + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return false; + + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 1.5f; + else{ + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z-5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 1.5f; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + fwd = FindPlayerCoors() - camPos; + fwd.z = 0.0f; + // too far and driving away from cam + if(fwd.Magnitude() > 20.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return false; + // too close + if(fwd.Magnitude() < 1.6f) + return true; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_2: + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return false; + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 16.0f*playerSpeed; + camPos += 2.5f*CVector(playerSpeed.y, -playerSpeed.x, 0.0f); + + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + else{ + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z-5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + fwd = FindPlayerCoors() - camPos; + fwd.z = 0.0f; + // too far and driving away from cam + if(fwd.Magnitude() > 19.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return false; + // too close + if(fwd.Magnitude() < 1.6f) + return true; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_3: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 30.0f*playerSpeed; + camPos += 8.0f*CVector(playerSpeed.y, -playerSpeed.x, 0.0f); + + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_1STPERSON: + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_5: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 30.0f*playerSpeed; + camPos += 6.0f*CVector(playerSpeed.y, -playerSpeed.x, 0.0f); + + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 3.5f; + else{ + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z-5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 3.5f; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_ONSTRING: + TakeControl(FindPlayerEntity(), CCam::MODE_CAM_ON_A_STRING, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_COPCAR: +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return false; +#endif + if(FindPlayerPed()->m_pWanted->GetWantedLevel() < 1) + return false; + if(FindPlayerVehicle() == nil) + return false; + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return false; + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + veh = CPools::GetVehiclePool()->GetSlot(i); + if(veh && veh->IsCar() && veh != FindPlayerVehicle() && veh->bIsLawEnforcer){ + float dx = veh->GetPosition().x - FindPlayerCoors().x; + float dy = veh->GetPosition().y - FindPlayerCoors().y; + float dist = (veh->GetPosition() - FindPlayerCoors()).Magnitude(); + if(dist < 30.0f){ + if(dx*FindPlayerVehicle()->GetForward().x + dy*FindPlayerVehicle()->GetForward().y < 0.0f && + veh->GetForward().x*FindPlayerVehicle()->GetForward().x + veh->GetForward().y*FindPlayerVehicle()->GetForward().y > 0.8f){ + TakeControl(veh, CCam::MODE_CAM_ON_A_STRING, JUMP_CUT, CAMCONTROL_OBBE); + return true; + } + } + } + } + return false; + case OBBE_COPCAR_WHEEL: +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return false; +#endif + if(FindPlayerPed()->m_pWanted->GetWantedLevel() < 1) + return false; + if(FindPlayerVehicle() == nil) + return false; + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + return false; + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + veh = CPools::GetVehiclePool()->GetSlot(i); + if(veh && veh->IsCar() && veh != FindPlayerVehicle() && veh->bIsLawEnforcer){ + float dx = veh->GetPosition().x - FindPlayerCoors().x; + float dy = veh->GetPosition().y - FindPlayerCoors().y; + float dist = (veh->GetPosition() - FindPlayerCoors()).Magnitude(); + if(dist < 30.0f){ + if(dx*FindPlayerVehicle()->GetForward().x + dy*FindPlayerVehicle()->GetForward().y < 0.0f && + veh->GetForward().x*FindPlayerVehicle()->GetForward().x + veh->GetForward().y*FindPlayerVehicle()->GetForward().y > 0.8f){ + target = Multiply3x3(veh->GetMatrix(), CVector(-1.4f, -2.3f, 0.3f)); + target += veh->GetPosition(); + if(!CWorld::GetIsLineOfSightClear(veh->GetPosition(), target, true, false, false, false, false, false, false)) + return false; + TakeControl(veh, CCam::MODE_WHEELCAM, JUMP_CUT, CAMCONTROL_OBBE); + return true; + } + } + } + } + return false; + + case OBBE_9: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 15.0f*playerSpeed; + camPos += CVector(2.0f, 1.0f, 0.0f); + + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + else{ + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z-5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_10: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 5.0f*playerSpeed; + camPos += CVector(2.0f, 1.0f, 0.5f); + + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_11: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 20.0f*playerSpeed; + camPos += CVector(2.0f, 1.0f, 20.0f); + + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_12: + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 5.0f*playerSpeed; + camPos += CVector(2.0f, 1.0f, 10.5f); + + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_13: +#ifdef FIX_BUGS + TakeControl(FindPlayerEntity(), CCam::MODE_TOP_DOWN_PED, JUMP_CUT, CAMCONTROL_OBBE); +#else + TakeControl(FindPlayerEntity(), CCam::MODE_TOPDOWN, JUMP_CUT, CAMCONTROL_OBBE); +#endif + return true; + default: + return false; + } +} + +int32 SequenceOfCams[16] = { + OBBE_WHEEL, OBBE_COPCAR, OBBE_3, OBBE_1, OBBE_3, OBBE_COPCAR_WHEEL, + OBBE_2, OBBE_3, OBBE_COPCAR_WHEEL, OBBE_COPCAR, OBBE_2, OBBE_3, + OBBE_5, OBBE_3, + OBBE_ONSTRING // actually unused... +}; + +void +CCamera::ProcessObbeCinemaCameraCar(void) +{ + static int OldMode = -1; + static int32 TimeForNext = 0; + int i = 0; + + if(!bDidWeProcessAnyCinemaCam){ + OldMode = -1; + CHud::SetHelpMessage(TheText.Get("CINCAM"), true); + } + + if(!bDidWeProcessAnyCinemaCam || IsItTimeForNewcam(SequenceOfCams[OldMode], TimeForNext)){ + // This is very strange code... + for(OldMode = (OldMode+1) % 14; + !TryToStartNewCamMode(SequenceOfCams[OldMode]) && i <= 14; + OldMode = (OldMode+1) % 14) + i++; + TimeForNext = CTimer::GetTimeInMilliseconds(); + if(i >= 14){ + OldMode = 14; + TryToStartNewCamMode(SequenceOfCams[14]); + } + } + + m_iModeObbeCamIsInForCar = OldMode; + bDidWeProcessAnyCinemaCam = true; +} + +int32 SequenceOfPedCams[5] = { OBBE_9, OBBE_10, OBBE_11, OBBE_12, OBBE_13 }; + +void +CCamera::ProcessObbeCinemaCameraPed(void) +{ + // static bool bObbePedProcessed = false; // unused + static int PedOldMode = -1; + static int32 PedTimeForNext = 0; + + if(!bDidWeProcessAnyCinemaCam) + PedOldMode = -1; + + if(!bDidWeProcessAnyCinemaCam || IsItTimeForNewcam(SequenceOfPedCams[PedOldMode], PedTimeForNext)){ + for(PedOldMode = (PedOldMode+1) % 5; + !TryToStartNewCamMode(SequenceOfPedCams[PedOldMode]); + PedOldMode = (PedOldMode+1) % 5); + PedTimeForNext = CTimer::GetTimeInMilliseconds(); + } + bDidWeProcessAnyCinemaCam = true; +} + +void +CCamera::DontProcessObbeCinemaCamera(void) +{ + bDidWeProcessAnyCinemaCam = false; +} + +void +CCamera::LoadTrainCamNodes(char const *name) +{ + CFileMgr::SetDir("data"); + + char token[16] = { 0 }; + char filename[16] = { 0 }; + uint8 *buf; + ssize_t bufpos = 0; + int field = 0; + int tokpos = 0; + char c; + int i; + ssize_t len; + + strcpy(filename, name); + len = (int)strlen(filename); + filename[len] = '.'; + filename[len+1] = 'd'; + filename[len+2] = 'a'; + filename[len+3] = 't'; + + m_uiNumberOfTrainCamNodes = 0; + + buf = new uint8[20000]; + len = CFileMgr::LoadFile(filename, buf, 20000, "r"); + + for(i = 0; i < MAX_NUM_OF_NODES; i++){ + m_arrTrainCamNode[i].m_cvecPointToLookAt = CVector(0.0f, 0.0f, 0.0f); + m_arrTrainCamNode[i].m_cvecMinPointInRange = CVector(0.0f, 0.0f, 0.0f); + m_arrTrainCamNode[i].m_cvecMaxPointInRange = CVector(0.0f, 0.0f, 0.0f); + m_arrTrainCamNode[i].m_fDesiredFOV = 0.0f; + m_arrTrainCamNode[i].m_fNearClip = 0.0f; + } + + while(bufpos <= len){ + c = buf[bufpos]; + switch(c){ + case '-': + case '.': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': +// case '10': case '11': case '12': case '13': // ahem... + token[tokpos++] = c; + bufpos++; + break; + + case ',': + case ';': // game has the code for this duplicated but we handle both under the same case + switch((field+14)%14){ + case 0: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecCamPosition.x = atof(token); + break; + case 1: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecCamPosition.y = atof(token); + break; + case 2: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecCamPosition.z = atof(token); + break; + case 3: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecPointToLookAt.x = atof(token); + break; + case 4: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecPointToLookAt.y = atof(token); + break; + case 5: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecPointToLookAt.z = atof(token); + break; + case 6: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMinPointInRange.x = atof(token); + break; + case 7: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMinPointInRange.y = atof(token); + break; + case 8: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMinPointInRange.z = atof(token); + break; + case 9: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMaxPointInRange.x = atof(token); + break; + case 10: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMaxPointInRange.y = atof(token); + break; + case 11: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_cvecMaxPointInRange.z = atof(token); + break; + case 12: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_fDesiredFOV = atof(token); + break; + case 13: + m_arrTrainCamNode[m_uiNumberOfTrainCamNodes].m_fNearClip = atof(token); + m_uiNumberOfTrainCamNodes++; + break; + } + field++; + bufpos++; + memset(token, 0, sizeof(token)); + tokpos = 0; + break; + + default: + bufpos++; + break; + } + } + + delete[] buf; + CFileMgr::SetDir(""); +} + +void +CCamera::Process_Train_Camera_Control(void) +{ + bool found = false; + CTrain *target = (CTrain*)pTargetEntity; + m_bUseSpecialFovTrain = true; + static bool OKtoGoBackToNodeCam = true; // only ever set to true + uint32 i; + + if(target->m_nTrackId == TRACK_ELTRAIN && !m_bAboveGroundTrainNodesLoaded){ + m_bAboveGroundTrainNodesLoaded = true; + m_bBelowGroundTrainNodesLoaded = false; + LoadTrainCamNodes("Train"); + m_uiTimeLastChange = CTimer::GetTimeInMilliseconds(); + OKtoGoBackToNodeCam = true; + m_iCurrentTrainCamNode = 0; + } + if(target->m_nTrackId == TRACK_SUBWAY && !m_bBelowGroundTrainNodesLoaded){ + m_bBelowGroundTrainNodesLoaded = true; + m_bAboveGroundTrainNodesLoaded = false; + LoadTrainCamNodes("Train2"); + m_uiTimeLastChange = CTimer::GetTimeInMilliseconds(); + OKtoGoBackToNodeCam = true; + m_iCurrentTrainCamNode = 0; + } + + m_bTargetJustBeenOnTrain = true; + uint32 node = m_iCurrentTrainCamNode; + for(i = 0; i < m_uiNumberOfTrainCamNodes && !found; i++){ + if(target->IsWithinArea(m_arrTrainCamNode[node].m_cvecMinPointInRange.x, + m_arrTrainCamNode[node].m_cvecMinPointInRange.y, + m_arrTrainCamNode[node].m_cvecMinPointInRange.z, + m_arrTrainCamNode[node].m_cvecMaxPointInRange.x, + m_arrTrainCamNode[node].m_cvecMaxPointInRange.y, + m_arrTrainCamNode[node].m_cvecMaxPointInRange.z)){ + m_iCurrentTrainCamNode = node; + found = true; + } + node++; + if(node >= m_uiNumberOfTrainCamNodes) + node = 0; + } +#ifdef FIX_BUGS + // Not really a bug but be nice and respect the debug mode + if(DebugCamMode){ + TakeControl(target, DebugCamMode, JUMP_CUT, CAMCONTROL_SCRIPT); + return; + } +#endif + + if(found){ + SetWideScreenOn(); + if(DotProduct(((CTrain*)pTargetEntity)->GetMoveSpeed(), pTargetEntity->GetForward()) < 0.001f){ + TakeControl(FindPlayerPed(), CCam::MODE_FOLLOWPED, JUMP_CUT, CAMCONTROL_SCRIPT); + if(target->Doors[0].IsFullyOpen()) + SetWideScreenOff(); + }else{ + SetCamPositionForFixedMode(m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecCamPosition, CVector(0.0f, 0.0f, 0.0f)); + if(m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecPointToLookAt.x == 999.0f && + m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecPointToLookAt.y == 999.0f && + m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecPointToLookAt.z == 999.0f) + TakeControl(target, CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_SCRIPT); + else + TakeControlNoEntity(m_arrTrainCamNode[m_iCurrentTrainCamNode].m_cvecPointToLookAt, JUMP_CUT, CAMCONTROL_SCRIPT); + RwCameraSetNearClipPlane(Scene.camera, m_arrTrainCamNode[m_iCurrentTrainCamNode].m_fNearClip); + } + }else{ + if(DotProduct(((CTrain*)pTargetEntity)->GetMoveSpeed(), pTargetEntity->GetForward()) < 0.001f){ + TakeControl(FindPlayerPed(), CCam::MODE_FOLLOWPED, JUMP_CUT, CAMCONTROL_SCRIPT); + if(target->Doors[0].IsFullyOpen()) + SetWideScreenOff(); + } + } +} + + +void +CCamera::LoadPathSplines(int file) +{ + bool reading = true; + char c, token[32] = { 0 }; + int i, j, n; + + n = 0; + + for(i = 0; i < MAX_NUM_OF_SPLINETYPES; i++) + for(j = 0; j < CCamPathSplines::MAXPATHLENGTH; j++) + m_arrPathArray[i].m_arr_PathData[j] = 0.0f; + + m_bStartingSpline = false; + + i = 0; + j = 0; + while(reading){ + CFileMgr::Read(file, &c, 1); + switch(c){ + case '\0': + reading = false; + break; + + case '+': case '-': case '.': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'e': case 'E': + token[n++] = c; + break; + + case ',': +#ifdef FIX_BUGS + if(i < MAX_NUM_OF_SPLINETYPES && j < CCamPathSplines::MAXPATHLENGTH) +#endif + m_arrPathArray[i].m_arr_PathData[j] = atof(token); + j++; + memset(token, 0, 32); + n = 0; + break; + + case ';': +#ifdef FIX_BUGS + if(i < MAX_NUM_OF_SPLINETYPES && j < CCamPathSplines::MAXPATHLENGTH) +#endif + m_arrPathArray[i].m_arr_PathData[j] = atof(token); + i++; + j = 0; + memset(token, 0, 32); + n = 0; + } + } +} + +void +CCamera::FinishCutscene(void) +{ + SetPercentAlongCutScene(100.0f); + m_fPositionAlongSpline = 1.0f; + m_bcutsceneFinished = true; +} + +uint32 +CCamera::GetCutSceneFinishTime(void) +{ + int cam = ActiveCam; + if (Cams[cam].Mode == CCam::MODE_FLYBY) + return Cams[cam].m_uiFinishTime; + cam = (cam + 1) % 2; + if (Cams[cam].Mode == CCam::MODE_FLYBY) + return Cams[cam].m_uiFinishTime; + + return 0; +} + +void +CCamera::SetCamCutSceneOffSet(const CVector &pos) +{ + m_vecCutSceneOffset = pos; +}; + +void +CCamera::SetPercentAlongCutScene(float percent) +{ + if(Cams[ActiveCam].Mode == CCam::MODE_FLYBY) + Cams[ActiveCam].m_fTimeElapsedFloat = percent/100.0f * Cams[ActiveCam].m_uiFinishTime; + else if(Cams[(ActiveCam+1)%2].Mode == CCam::MODE_FLYBY) + Cams[(ActiveCam+1)%2].m_fTimeElapsedFloat = percent/100.0f * Cams[(ActiveCam+1)%2].m_uiFinishTime; +} + +void +CCamera::SetParametersForScriptInterpolation(float stopMoving, float catchUp, int32 time) +{ + m_fScriptPercentageInterToStopMoving = stopMoving * 0.01f; + m_fScriptPercentageInterToCatchUp = catchUp * 0.01f; + m_fScriptTimeForInterPolation = time; + m_bScriptParametersSetForInterPol = true; +} + +void +CCamera::SetZoomValueFollowPedScript(int16 dist) +{ + switch (dist) { + case 0: m_fPedZoomValueScript = 0.25f; break; + case 1: m_fPedZoomValueScript = 1.5f; break; + case 2: m_fPedZoomValueScript = 2.9f; break; + default: break; + } + + m_bUseScriptZoomValuePed = true; +} + +void +CCamera::SetZoomValueCamStringScript(int16 dist) +{ +#ifdef FREE_CAM + if (bFreeCam) { + switch (dist) { + case 0: m_fCarZoomValueScript = ((CVehicle*)Cams[ActiveCam].CamTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_1 : FREE_CAR_ZOOM_VALUE_1; break; + case 1: m_fCarZoomValueScript = ((CVehicle*)Cams[ActiveCam].CamTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_2 : FREE_CAR_ZOOM_VALUE_2; break; + case 2: m_fCarZoomValueScript = ((CVehicle*)Cams[ActiveCam].CamTargetEntity)->IsBoat() ? FREE_BOAT_ZOOM_VALUE_3 : FREE_CAR_ZOOM_VALUE_3; break; + default: break; + } + } else +#endif + { + switch (dist) { + case 0: m_fCarZoomValueScript = DEFAULT_CAR_ZOOM_VALUE_1; break; + case 1: m_fCarZoomValueScript = DEFAULT_CAR_ZOOM_VALUE_2; break; + case 2: m_fCarZoomValueScript = DEFAULT_CAR_ZOOM_VALUE_3; break; + default: break; + } + } + + m_bUseScriptZoomValueCar = true; +} + +void +CCamera::SetNearClipScript(float clip) +{ + m_fNearClipScript = clip; + m_bUseNearClipScript = true; +} + + + +void +CCamera::ProcessFade(void) +{ + float fade = (CTimer::GetTimeInMilliseconds() - m_uiFadeTimeStarted)/1000.0f; + // Why even set CDraw::FadeValue if m_fFLOATingFade sets it anyway? + if(m_bFading){ + if(m_iFadingDirection == FADE_IN){ + if(m_fTimeToFadeOut != 0.0f){ + m_fFLOATingFade = 255.0f - 255.0f*fade/m_fTimeToFadeOut; + if(m_fFLOATingFade <= 0.0f){ + m_bFading = false; + CDraw::FadeValue = 0; + m_fFLOATingFade = 0.0f; + } + }else{ + m_bFading = false; + CDraw::FadeValue = 0; + m_fFLOATingFade = 0.0f; + } + }else if(m_iFadingDirection == FADE_OUT){ + if(m_fTimeToFadeOut != 0.0f){ + m_fFLOATingFade = 255.0f*fade/m_fTimeToFadeOut; + if(m_fFLOATingFade >= 255.0f){ + m_bFading = false; + CDraw::FadeValue = 255; + m_fFLOATingFade = 255.0f; + } + }else{ + m_bFading = false; + CDraw::FadeValue = 255; + m_fFLOATingFade = 255.0f; + } + } + CDraw::FadeValue = m_fFLOATingFade; + } +} + +void +CCamera::ProcessMusicFade(void) +{ + float fade = (CTimer::GetTimeInMilliseconds() - m_uiFadeTimeStartedMusic)/1000.0f; + if(m_bMusicFading){ + if(m_iMusicFadingDirection == FADE_IN){ + if(m_fTimeToFadeMusic == 0.0f) + m_fTimeToFadeMusic = 1.0f; + + m_fFLOATingFadeMusic = 255.0f*fade/m_fTimeToFadeMusic; + if(m_fFLOATingFadeMusic > 255.0f){ + m_bMusicFading = false; + m_fFLOATingFadeMusic = 0.0f; + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); + }else{ + DMAudio.SetEffectsFadeVol(m_fFLOATingFadeMusic/255.0f * 127); + DMAudio.SetMusicFadeVol(m_fFLOATingFadeMusic/255.0f * 127); + } + }else if(m_iMusicFadingDirection == FADE_OUT){ + if(m_fTimeToFadeMusic == 0.0f) + m_fTimeToFadeMusic = 1.0f; + +#ifdef PS2_MENU + if(m_bMoveCamToAvoidGeom || TheMemoryCard.StillToFadeOut){ +#else + if(m_bMoveCamToAvoidGeom || StillToFadeOut){ +#endif + m_fFLOATingFadeMusic = 256.0f; + m_bMoveCamToAvoidGeom = false; + }else + m_fFLOATingFadeMusic = 255.0f*fade/m_fTimeToFadeMusic; + + if(m_fFLOATingFadeMusic > 255.0f){ + m_bMusicFading = false; + m_fFLOATingFadeMusic = 255.0f; + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + }else{ + DMAudio.SetEffectsFadeVol(127 - m_fFLOATingFadeMusic/255.0f * 127); + DMAudio.SetMusicFadeVol(127 - m_fFLOATingFadeMusic/255.0f * 127); + } + } + } +} + +void +CCamera::Fade(float timeout, int16 direction) +{ + m_bFading = true; + m_iFadingDirection = direction; + m_fTimeToFadeOut = timeout; + m_uiFadeTimeStarted = CTimer::GetTimeInMilliseconds(); + if(!m_bIgnoreFadingStuffForMusic){ + m_bMusicFading = true; + m_iMusicFadingDirection = direction; + m_fTimeToFadeMusic = timeout; + m_uiFadeTimeStartedMusic = CTimer::GetTimeInMilliseconds(); +// Not on PS2 + if(!m_bUnknown && m_iMusicFadingDirection == FADE_OUT){ + unknown++; + if(unknown >= 2){ + m_bUnknown = true; + unknown = 0; + }else + m_bMoveCamToAvoidGeom = true; + } + } +} + +void +CCamera::SetFadeColour(uint8 r, uint8 g, uint8 b) +{ + m_FadeTargetIsSplashScreen = r == 0 && g == 0 && b == 0; + CDraw::FadeRed = r; + CDraw::FadeGreen = g; + CDraw::FadeBlue = b; +} + +bool +CCamera::GetFading(void) +{ + return m_bFading; +} + +int +CCamera::GetFadingDirection(void) +{ + if(m_bFading) + return m_iFadingDirection == FADE_IN ? FADE_IN : FADE_OUT; + else + return FADE_NONE; +} + +int +CCamera::GetScreenFadeStatus(void) +{ + if(m_fFLOATingFade == 0.0f) + return FADE_0; + if(m_fFLOATingFade == 255.0f) + return FADE_2; + return FADE_1; +} + + + +void +CCamera::RenderMotionBlur(void) +{ + if(m_BlurType == 0) + return; + + CMBlur::MotionBlurRender(m_pRwCamera, + m_BlurRed, m_BlurGreen, m_BlurBlue, + m_motionBlur, m_BlurType, m_imotionBlurAddAlpha); +} + +void +CCamera::SetMotionBlur(int r, int g, int b, int a, int type) +{ + m_BlurRed = r; + m_BlurGreen = g; + m_BlurBlue = b; + m_motionBlur = a; + m_BlurType = type; +} + +void +CCamera::SetMotionBlurAlpha(int a) +{ + m_imotionBlurAddAlpha = a; +} + + + +int +CCamera::GetLookDirection(void) +{ + if(Cams[ActiveCam].Mode == CCam::MODE_CAM_ON_A_STRING || + Cams[ActiveCam].Mode == CCam::MODE_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_BEHINDBOAT || + Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED) + return Cams[ActiveCam].DirectionWasLooking; + return LOOKING_FORWARD; +} + +bool +CCamera::GetLookingForwardFirstPerson(void) +{ + return Cams[ActiveCam].Mode == CCam::MODE_1STPERSON && + Cams[ActiveCam].DirectionWasLooking == LOOKING_FORWARD; +} + +bool +CCamera::GetLookingLRBFirstPerson(void) +{ + return Cams[ActiveCam].Mode == CCam::MODE_1STPERSON && Cams[ActiveCam].DirectionWasLooking != LOOKING_FORWARD; +} + +void +CCamera::SetCameraDirectlyBehindForFollowPed_CamOnAString(void) +{ + m_bCamDirectlyBehind = true; + CPlayerPed *player = FindPlayerPed(); + if (player) + m_PedOrientForBehindOrInFront = CGeneral::GetATanOfXY(player->GetForward().x, player->GetForward().y); +} + +void +CCamera::SetCameraDirectlyInFrontForFollowPed_CamOnAString(void) +{ + m_bCamDirectlyInFront = true; + CPlayerPed *player = FindPlayerPed(); + if (player) + m_PedOrientForBehindOrInFront = CGeneral::GetATanOfXY(player->GetForward().x, player->GetForward().y); +} + +void +CCamera::SetNewPlayerWeaponMode(int16 mode, int16 minZoom, int16 maxZoom) +{ + PlayerWeaponMode.Mode = mode; + PlayerWeaponMode.MaxZoom = maxZoom; + PlayerWeaponMode.MinZoom = minZoom; + PlayerWeaponMode.Duration = 0.0f; +} + +void +CCamera::ClearPlayerWeaponMode(void) +{ + PlayerWeaponMode.Mode = 0; + PlayerWeaponMode.MaxZoom = 1; + PlayerWeaponMode.MinZoom = -1; + PlayerWeaponMode.Duration = 0.0f; +} + +void +CCamera::UpdateAimingCoors(CVector const &coors) +{ + m_cvecAimingTargetCoors = coors; +} + +bool +CCamera::Find3rdPersonCamTargetVector(float dist, CVector pos, CVector &source, CVector &target) +{ + if(CPad::GetPad(0)->GetLookBehindForPed()){ + source = pos; + target = dist*Cams[ActiveCam].CamTargetEntity->GetForward() + source; + return false; + }else{ + float angleX = DEGTORAD((m_f3rdPersonCHairMultX-0.5f) * 1.8f * 0.5f * Cams[ActiveCam].FOV * CDraw::GetAspectRatio()); + float angleY = DEGTORAD((0.5f-m_f3rdPersonCHairMultY) * 1.8f * 0.5f * Cams[ActiveCam].FOV); + source = Cams[ActiveCam].Source; + target = Cams[ActiveCam].Front; + target += Cams[ActiveCam].Up * Tan(angleY); + target += CrossProduct(Cams[ActiveCam].Front, Cams[ActiveCam].Up) * Tan(angleX); + target.Normalise(); + source += DotProduct(pos - source, target)*target; + target = dist*target + source; + return true; + } +} + +float +CCamera::Find3rdPersonQuickAimPitch(void) +{ + float clampedFrontZ = Clamp(Cams[ActiveCam].Front.z, -1.0f, 1.0f); + + float rot = Asin(clampedFrontZ); + + return -(DEGTORAD(((0.5f - m_f3rdPersonCHairMultY) * 1.8f * 0.5f * Cams[ActiveCam].FOV)) + rot); +} + + + +void +CCamera::SetRwCamera(RwCamera *cam) +{ + m_pRwCamera = cam; + m_viewMatrix.Attach(RwCameraGetViewMatrix(m_pRwCamera), false); + CMBlur::MotionBlurOpen(m_pRwCamera); +} + +void +CCamera::CalculateDerivedValues(void) +{ + m_cameraMatrix = Invert(GetMatrix()); + + float hfov = DEGTORAD(CDraw::GetScaledFOV()/2.0f); + float c = Cos(hfov); + float s = Sin(hfov); + + // right plane + m_vecFrustumNormals[0] = CVector(c, -s, 0.0f); + // left plane + m_vecFrustumNormals[1] = CVector(-c, -s, 0.0f); + + c /= CDraw::FindAspectRatio(); + s /= CDraw::FindAspectRatio(); + // bottom plane + m_vecFrustumNormals[2] = CVector(0.0f, -s, -c); + // top plane + m_vecFrustumNormals[3] = CVector(0.0f, -s, c); + + if(GetForward().x == 0.0f && GetForward().y == 0.0f) + GetForward().x = 0.0001f; + else + Orientation = Atan2(GetForward().x, GetForward().y); + + CamFrontXNorm = GetForward().x; + CamFrontYNorm = GetForward().y; + float l = Sqrt(SQR(CamFrontXNorm) + SQR(CamFrontYNorm)); + if(l == 0.0f) + CamFrontXNorm = 1.0f; + else{ + CamFrontXNorm /= l; + CamFrontYNorm /= l; + } +} + +bool +CCamera::IsPointVisible(const CVector ¢er, const CMatrix *mat) +{ +#ifdef GTA_PS2 + CVuVector c; + TransformPoint(c, *mat, center); +#else + CVector c = center; + #ifdef FIX_BUGS + c = *mat * center; + #else + RwV3dTransformPoints(&c, &c, 1, (RwMatrix*)mat); + #endif +#endif + if(c.y < CDraw::GetNearClipZ()) return false; + if(c.y > CDraw::GetFarClipZ()) return false; + if(c.x*m_vecFrustumNormals[0].x + c.y*m_vecFrustumNormals[0].y > 0.0f) return false; + if(c.x*m_vecFrustumNormals[1].x + c.y*m_vecFrustumNormals[1].y > 0.0f) return false; + if(c.y*m_vecFrustumNormals[2].y + c.z*m_vecFrustumNormals[2].z > 0.0f) return false; + if(c.y*m_vecFrustumNormals[3].y + c.z*m_vecFrustumNormals[3].z > 0.0f) return false; + return true; +} + +bool +CCamera::IsSphereVisible(const CVector ¢er, float radius, Const CMatrix *mat) +{ +#ifdef GTA_PS2 + CVuVector c; + TransformPoint(c, *mat, center); +#else + CVector c = center; + #ifdef FIX_BUGS + c = *mat * center; + #else + RwV3dTransformPoints(&c, &c, 1, (RwMatrix*)mat); + #endif +#endif + if(c.y + radius < CDraw::GetNearClipZ()) return false; + if(c.y - radius > CDraw::GetFarClipZ()) return false; + if(c.x*m_vecFrustumNormals[0].x + c.y*m_vecFrustumNormals[0].y > radius) return false; + if(c.x*m_vecFrustumNormals[1].x + c.y*m_vecFrustumNormals[1].y > radius) return false; + if(c.y*m_vecFrustumNormals[2].y + c.z*m_vecFrustumNormals[2].z > radius) return false; + if(c.y*m_vecFrustumNormals[3].y + c.z*m_vecFrustumNormals[3].z > radius) return false; + return true; +} + +bool +CCamera::IsSphereVisible(const CVector ¢er, float radius) +{ +#if GTA_VERSION < GTA3_PC_10 // not sure this condition is the right one + // Maybe this was a copy of the other function with m_cameraMatrix + return IsSphereVisible(center, radius, &m_cameraMatrix); +#else + // ...and on PC they decided to call the other one with a default matrix. + CMatrix mat(GetCameraMatrix()); // this matrix construction is stupid and gone in VC + return IsSphereVisible(center, radius, &mat); +#endif +} + +bool +CCamera::IsBoxVisible(CVUVECTOR *box, const CMatrix *mat) +{ + int i; + int frustumTests[6] = { 0 }; +#ifdef GTA_PS2 + TransformPoints(box, 8, *mat, box); +#else + #ifdef FIX_BUGS + for (i = 0; i < 8; i++) + box[i] = *mat * box[i]; + #else + RwV3dTransformPoints(box, box, 8, (RwMatrix*)mat); + #endif +#endif + + for(i = 0; i < 8; i++){ + if(box[i].y < CDraw::GetNearClipZ()) frustumTests[0]++; + if(box[i].y > CDraw::GetFarClipZ()) frustumTests[1]++; + if(box[i].x*m_vecFrustumNormals[0].x + box[i].y*m_vecFrustumNormals[0].y > 0.0f) frustumTests[2]++; + if(box[i].x*m_vecFrustumNormals[1].x + box[i].y*m_vecFrustumNormals[1].y > 0.0f) frustumTests[3]++; +// Why not test z? +// if(box[i].y*m_vecFrustumNormals[2].y + box[i].z*m_vecFrustumNormals[2].z > 0.0f) frustumTests[4]++; +// if(box[i].y*m_vecFrustumNormals[3].y + box[i].z*m_vecFrustumNormals[3].z > 0.0f) frustumTests[5]++; + } + for(i = 0; i < 6; i++) + if(frustumTests[i] == 8) + return false; // Box is completely outside of one plane + return true; +} + + + +CCamPathSplines::CCamPathSplines(void) +{ + int i; + for(i = 0; i < MAXPATHLENGTH; i++) + m_arr_PathData[i] = 0.0f; +} diff --git a/src/core/Camera.h b/src/core/Camera.h new file mode 100644 index 0000000..07a05cb --- /dev/null +++ b/src/core/Camera.h @@ -0,0 +1,653 @@ +#pragma once +#include "Placeable.h" + +class CEntity; +class CPed; +class CAutomobile; +class CGarage; + +extern int16 DebugCamMode; + +enum +{ + NUMBER_OF_VECTORS_FOR_AVERAGE = 2, + MAX_NUM_OF_SPLINETYPES = 4, + MAX_NUM_OF_NODES = 800 // for trains +}; + +#define DEFAULT_NEAR (0.9f) +enum +{ + CAM_ZOOM_1STPRS, + CAM_ZOOM_1, + CAM_ZOOM_2, + CAM_ZOOM_3, + CAM_ZOOM_TOPDOWN, + CAM_ZOOM_CINEMATIC, +}; + +#ifdef FREE_CAM // LCS values +#define FREE_CAR_ZOOM_VALUE_1 (-1.0f) +#define FREE_CAR_ZOOM_VALUE_2 (2.0f) +#define FREE_CAR_ZOOM_VALUE_3 (6.0f) + +#define FREE_BOAT_ZOOM_VALUE_1 (-2.41f) +#define FREE_BOAT_ZOOM_VALUE_2 (6.49f) +#define FREE_BOAT_ZOOM_VALUE_3 (15.0f) +#endif + +#define DEFAULT_CAR_ZOOM_VALUE_1 (0.05f) +#define DEFAULT_CAR_ZOOM_VALUE_2 (1.9f) +#define DEFAULT_CAR_ZOOM_VALUE_3 (3.9f) + +const float DefaultFOV = 70.0f; // beta: 80.0f + +class CCam +{ +public: + enum + { + MODE_NONE = 0, + MODE_TOPDOWN, + MODE_GTACLASSIC, + MODE_BEHINDCAR, + MODE_FOLLOWPED, + MODE_AIMING, + MODE_DEBUG, + MODE_SNIPER, + MODE_ROCKETLAUNCHER, + MODE_MODELVIEW, + MODE_BILL, + MODE_SYPHON, + MODE_CIRCLE, + MODE_CHEESYZOOM, + MODE_WHEELCAM, + MODE_FIXED, + MODE_1STPERSON, + MODE_FLYBY, + MODE_CAM_ON_A_STRING, + MODE_REACTION, + MODE_FOLLOW_PED_WITH_BIND, + MODE_CHRIS, + MODE_BEHINDBOAT, + MODE_PLAYER_FALLEN_WATER, + MODE_CAM_ON_TRAIN_ROOF, + MODE_CAM_RUNNING_SIDE_TRAIN, + MODE_BLOOD_ON_THE_TRACKS, + MODE_IM_THE_PASSENGER_WOOWOO, + MODE_SYPHON_CRIM_IN_FRONT, + MODE_PED_DEAD_BABY, + MODE_PILLOWS_PAPS, + MODE_LOOK_AT_CARS, + MODE_ARRESTCAM_ONE, + MODE_ARRESTCAM_TWO, + MODE_M16_1STPERSON, + MODE_SPECIAL_FIXED_FOR_SYPHON, + MODE_FIGHT_CAM, + MODE_TOP_DOWN_PED, + MODE_SNIPER_RUNABOUT, + MODE_ROCKETLAUNCHER_RUNABOUT, + MODE_1STPERSON_RUNABOUT, + MODE_M16_1STPERSON_RUNABOUT, + MODE_FIGHT_CAM_RUNABOUT, + MODE_EDITOR, + MODE_HELICANNON_1STPERSON, // vice city leftover + }; + + bool bBelowMinDist; //used for follow ped mode + bool bBehindPlayerDesired; //used for follow ped mode + bool m_bCamLookingAtVector; + bool m_bCollisionChecksOn; + bool m_bFixingBeta; //used for camera on a string + bool m_bTheHeightFixerVehicleIsATrain; + bool LookBehindCamWasInFront; + bool LookingBehind; + bool LookingLeft; + bool LookingRight; + bool ResetStatics; //for interpolation type stuff to work + bool Rotating; + + int16 Mode; // CameraMode + uint32 m_uiFinishTime; + + int m_iDoCollisionChecksOnFrameNum; + int m_iDoCollisionCheckEveryNumOfFrames; + int m_iFrameNumWereAt; + int m_iRunningVectorArrayPos; + int m_iRunningVectorCounter; + int DirectionWasLooking; + + float f_max_role_angle; //=DEGTORAD(5.0f); + float f_Roll; //used for adding a slight roll to the camera in the + float f_rollSpeed; + float m_fSyphonModeTargetZOffSet; + float m_fRoadOffSet; + float m_fAmountFractionObscured; + float m_fAlphaSpeedOverOneFrame; + float m_fBetaSpeedOverOneFrame; + float m_fBufferedTargetBeta; + float m_fBufferedTargetOrientation; + float m_fBufferedTargetOrientationSpeed; + float m_fCamBufferedHeight; + float m_fCamBufferedHeightSpeed; + float m_fCloseInPedHeightOffset; + float m_fCloseInPedHeightOffsetSpeed; + float m_fCloseInCarHeightOffset; + float m_fCloseInCarHeightOffsetSpeed; + float m_fDimensionOfHighestNearCar; + float m_fDistanceBeforeChanges; + float m_fFovSpeedOverOneFrame; + float m_fMinDistAwayFromCamWhenInterPolating; + float m_fPedBetweenCameraHeightOffset; + float m_fPlayerInFrontSyphonAngleOffSet; + float m_fRadiusForDead; + float m_fRealGroundDist; //used for follow ped mode + float m_fTargetBeta; + float m_fTimeElapsedFloat; + + float m_fTransitionBeta; + float m_fTrueBeta; + float m_fTrueAlpha; + float m_fInitialPlayerOrientation; //used for first person + + float Alpha; + float AlphaSpeed; + float FOV; + float FOVSpeed; + float Beta; + float BetaSpeed; + float Distance; + float DistanceSpeed; + float CA_MIN_DISTANCE; + float CA_MAX_DISTANCE; + float SpeedVar; + + CVector m_cvecSourceSpeedOverOneFrame; + CVector m_cvecTargetSpeedOverOneFrame; + CVector m_cvecUpOverOneFrame; + + CVector m_cvecTargetCoorsForFudgeInter; + CVector m_cvecCamFixedModeVector; + CVector m_cvecCamFixedModeSource; + CVector m_cvecCamFixedModeUpOffSet; + CVector m_vecLastAboveWaterCamPosition; //helper for when the player has gone under the water + CVector m_vecBufferedPlayerBodyOffset; + + // The three vectors that determine this camera for this frame + CVector Front; // Direction of looking in + CVector Source; // Coors in world space + CVector SourceBeforeLookBehind; + CVector Up; // Just that + CVector m_arrPreviousVectors[NUMBER_OF_VECTORS_FOR_AVERAGE]; // used to average stuff + CEntity *CamTargetEntity; + + float m_fCameraDistance; + float m_fIdealAlpha; + float m_fPlayerVelocity; + CAutomobile *m_pLastCarEntered; // So interpolation works + CPed *m_pLastPedLookedAt;// So interpolation works + bool m_bFirstPersonRunAboutActive; + + CCam(void) { Init(); } + void Init(void); + void Process(void); + void ProcessSpecialHeightRoutines(void); + void GetVectorsReadyForRW(void); + CVector DoAverageOnVector(const CVector &vec); + float GetPedBetaAngleForClearView(const CVector &Target, float Dist, float BetaOffset, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies); + void WorkOutCamHeightWeeCar(CVector &TargetCoors, float TargetOrientation); + void WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight); + bool RotCamIfInFrontCar(CVector &TargetCoors, float TargetOrientation); + bool FixCamIfObscured(CVector &TargetCoors, float TargetHeight, float TargetOrientation); + void Cam_On_A_String_Unobscured(const CVector &TargetCoors, float BaseDist); + void FixCamWhenObscuredByVehicle(const CVector &TargetCoors); + void LookBehind(void); + void LookLeft(void); + void LookRight(void); + void ClipIfPedInFrontOfPlayer(void); + void KeepTrackOfTheSpeed(const CVector &source, const CVector &target, const CVector &up, const float &alpha, const float &beta, const float &fov); + bool Using3rdPersonMouseCam(void); + bool GetWeaponFirstPersonOn(void); + bool IsTargetInWater(const CVector &CamCoors); + void AvoidWallsTopDownPed(const CVector &TargetCoors, const CVector &Offset, float *Adjuster, float *AdjusterSpeed, float yDistLimit); + void PrintMode(void); + + void Process_Debug(const CVector&, float, float, float); +#ifdef GTA_SCENE_EDIT + void Process_Editor(const CVector&, float, float, float); +#endif + void Process_ModelView(const CVector &CameraTarget, float, float, float); + void Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_FollowPedWithMouse(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_BehindCar(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Cam_On_A_String(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_TopDown(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar); + void Process_TopDownPed(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Rocket(const CVector &CameraTarget, float, float, float); + void Process_M16_1stPerson(const CVector &CameraTarget, float, float, float); + void Process_1stPerson(const CVector &CameraTarget, float, float, float); + void Process_1rstPersonPedOnPC(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Sniper(const CVector &CameraTarget, float, float, float); + void Process_Syphon(const CVector &CameraTarget, float, float, float); + void Process_Syphon_Crim_In_Front(const CVector &CameraTarget, float, float, float); + void Process_BehindBoat(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Fight_Cam(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_FlyBy(const CVector&, float, float, float); + void Process_WheelCam(const CVector&, float, float, float); + void Process_Fixed(const CVector &CameraTarget, float, float, float); + void Process_Player_Fallen_Water(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Circle(const CVector &CameraTarget, float, float, float); + void Process_SpecialFixedForSyphon(const CVector &CameraTarget, float, float, float); + void ProcessPedsDeadBaby(void); + bool ProcessArrestCamOne(void); + bool ProcessArrestCamTwo(void); + + /* Some of the unused PS2 cams */ + void Process_Chris_With_Binding_PlusRotation(const CVector &CameraTarget, float, float, float); + void Process_ReactionCam(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_FollowPed_WithBinding(const CVector &CameraTarget, float TargetOrientation, float, float); + // TODO: + // CCam::Process_CushyPillows_Arse + // CCam::Process_Look_At_Cars + // CCam::Process_CheesyZoom + // CCam::Process_Aiming + void Process_Bill(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar); + void Process_Im_The_Passenger_Woo_Woo(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Blood_On_The_Tracks(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Cam_Running_Side_Train(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_Cam_On_Train_Roof(const CVector &CameraTarget, float TargetOrientation, float, float); + + // custom stuff + void Process_FollowPed_Rotation(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_FollowCar_SA(const CVector &CameraTarget, float TargetOrientation, float, float); +}; + +VALIDATE_SIZE(CCam, 0x1A4); + +class CCamPathSplines +{ +public: + enum {MAXPATHLENGTH=800}; + float m_arr_PathData[MAXPATHLENGTH]; + CCamPathSplines(void); +}; + +struct CTrainCamNode +{ + CVector m_cvecCamPosition; + CVector m_cvecPointToLookAt; + CVector m_cvecMinPointInRange; + CVector m_cvecMaxPointInRange; + float m_fDesiredFOV; + float m_fNearClip; +}; + +struct CQueuedMode +{ + int16 Mode; + float Duration; + int16 MinZoom; + int16 MaxZoom; +}; + +enum +{ + LOOKING_BEHIND, + LOOKING_LEFT, + LOOKING_RIGHT, + LOOKING_FORWARD, +}; + +enum +{ + // TODO: find better names + FADE_0, // faded in + FADE_1, // mid fade + FADE_2, // faded out + + // Direction + FADE_OUT = 0, + FADE_IN, + FADE_NONE +}; + +enum +{ + MOTION_BLUR_NONE = 0, + MOTION_BLUR_SNIPER, + MOTION_BLUR_LIGHT_SCENE, + MOTION_BLUR_SECURITY_CAM, + MOTION_BLUR_CUT_SCENE, + MOTION_BLUR_INTRO, + MOTION_BLUR_INTRO2, + MOTION_BLUR_SNIPER_ZOOM, + MOTION_BLUR_INTRO3, + MOTION_BLUR_INTRO4, +}; + +enum +{ + NONE = 0, + INTERPOLATION, + JUMP_CUT +}; + +enum +{ + CAMCONTROL_GAME, + CAMCONTROL_SCRIPT, + CAMCONTROL_OBBE +}; + +class CCamera : public CPlaceable +{ +public: + bool m_bAboveGroundTrainNodesLoaded; + bool m_bBelowGroundTrainNodesLoaded; + bool m_bCamDirectlyBehind; + bool m_bCamDirectlyInFront; + bool m_bCameraJustRestored; + bool m_bcutsceneFinished; + bool m_bCullZoneChecksOn; + bool m_bFirstPersonBeingUsed; + bool m_bUnknown; + bool m_bIdleOn; + bool m_bInATunnelAndABigVehicle; + bool m_bInitialNodeFound; + bool m_bInitialNoNodeStaticsSet; + bool m_bIgnoreFadingStuffForMusic; + bool m_bPlayerIsInGarage; + bool m_bJustCameOutOfGarage; + bool m_bJustInitalised; + bool m_bJust_Switched; + bool m_bLookingAtPlayer; + bool m_bLookingAtVector; + bool m_bMoveCamToAvoidGeom; + bool m_bObbeCinematicPedCamOn; + bool m_bObbeCinematicCarCamOn; + bool m_bRestoreByJumpCut; + bool m_bUseNearClipScript; + bool m_bStartInterScript; + bool m_bStartingSpline; + bool m_bTargetJustBeenOnTrain; + bool m_bTargetJustCameOffTrain; + bool m_bUseSpecialFovTrain; + bool m_bUseTransitionBeta; + bool m_bUseScriptZoomValuePed; + bool m_bUseScriptZoomValueCar; + bool m_bWaitForInterpolToFinish; + bool m_bItsOkToLookJustAtThePlayer; + bool m_bWantsToSwitchWidescreenOff; + bool m_WideScreenOn; + bool m_1rstPersonRunCloseToAWall; + bool m_bHeadBob; + bool m_bFailedCullZoneTestPreviously; + + bool m_FadeTargetIsSplashScreen; + + bool WorldViewerBeingUsed; + uint8 ActiveCam; + uint32 m_uiCamShakeStart; + uint32 m_uiFirstPersonCamLastInputTime; + + uint32 m_uiLongestTimeInMill; + uint32 m_uiNumberOfTrainCamNodes; + uint8 m_uiTransitionJUSTStarted; + uint8 m_uiTransitionState; // 0:one mode 1:transition + + uint32 m_uiTimeLastChange; + uint32 m_uiTimeWeEnteredIdle; + uint32 m_uiTimeTransitionStart; + uint32 m_uiTransitionDuration; + int m_BlurBlue; + int m_BlurGreen; + int m_BlurRed; + int m_BlurType; + + uint32 unknown; // some counter having to do with music + int m_iWorkOutSpeedThisNumFrames; + int m_iNumFramesSoFar; + + + int m_iCurrentTrainCamNode; + int m_motionBlur; + int m_imotionBlurAddAlpha; + int m_iCheckCullZoneThisNumFrames; + int m_iZoneCullFrameNumWereAt; + int WhoIsInControlOfTheCamera; + + float CamFrontXNorm; + float CamFrontYNorm; +#ifdef FIX_BUGS + int32 CarZoomIndicator; +#else + float CarZoomIndicator; +#endif + float CarZoomValue; + float CarZoomValueSmooth; + + float DistanceToWater; +#ifndef PS2_CAM_TRANSITION + float FOVDuringInter; +#endif + float LODDistMultiplier; + float GenerationDistMultiplier; +#ifndef PS2_CAM_TRANSITION + float m_fAlphaSpeedAtStartInter; + float m_fAlphaWhenInterPol; + float m_fAlphaDuringInterPol; + float m_fBetaDuringInterPol; + float m_fBetaSpeedAtStartInter; + float m_fBetaWhenInterPol; + float m_fFOVWhenInterPol; + float m_fFOVSpeedAtStartInter; + float m_fStartingBetaForInterPol; + float m_fStartingAlphaForInterPol; +#endif + float m_PedOrientForBehindOrInFront; + float m_CameraAverageSpeed; + float m_CameraSpeedSoFar; + float m_fCamShakeForce; + float m_fCarZoomValueScript; + float m_fFovForTrain; + float m_fFOV_Wide_Screen; + float m_fNearClipScript; + float m_fOldBetaDiff; + float m_fPedZoomValue; + + float m_fPedZoomValueScript; + float m_fPedZoomValueSmooth; + float m_fPositionAlongSpline; + float m_ScreenReductionPercentage; + float m_ScreenReductionSpeed; + float m_AlphaForPlayerAnim1rstPerson; + float Orientation; +#ifdef FIX_BUGS + int32 PedZoomIndicator; +#else + float PedZoomIndicator; +#endif + float PlayerExhaustion; + float SoundDistUp, SoundDistLeft, SoundDistRight; + float SoundDistUpAsRead, SoundDistLeftAsRead, SoundDistRightAsRead; + float SoundDistUpAsReadOld, SoundDistLeftAsReadOld, SoundDistRightAsReadOld; + float m_fWideScreenReductionAmount; + float m_fStartingFOVForInterPol; + + // not static yet + float m_fMouseAccelHorzntl;// acceleration multiplier for 1st person controls + float m_fMouseAccelVertical;// acceleration multiplier for 1st person controls + float m_f3rdPersonCHairMultX; + float m_f3rdPersonCHairMultY; + + + CCam Cams[3]; + CGarage *pToGarageWeAreIn; + CGarage *pToGarageWeAreInForHackAvoidFirstPerson; + CQueuedMode m_PlayerMode; + CQueuedMode PlayerWeaponMode; + CVector m_PreviousCameraPosition; + CVector m_RealPreviousCameraPosition; + CVector m_cvecAimingTargetCoors; + CVector m_vecFixedModeVector; + CVector m_vecFixedModeSource; + CVector m_vecFixedModeUpOffSet; + CVector m_vecCutSceneOffset; +#ifndef PS2_CAM_TRANSITION + CVector m_cvecStartingSourceForInterPol; + CVector m_cvecStartingTargetForInterPol; + CVector m_cvecStartingUpForInterPol; + CVector m_cvecSourceSpeedAtStartInter; + CVector m_cvecTargetSpeedAtStartInter; + CVector m_cvecUpSpeedAtStartInter; + CVector m_vecSourceWhenInterPol; + CVector m_vecTargetWhenInterPol; + CVector m_vecUpWhenInterPol; +#endif + CVector m_vecGameCamPos; +#ifndef PS2_CAM_TRANSITION + CVector SourceDuringInter; + CVector TargetDuringInter; + CVector UpDuringInter; +#endif + RwCamera *m_pRwCamera; + CEntity *pTargetEntity; + CCamPathSplines m_arrPathArray[MAX_NUM_OF_SPLINETYPES]; + CTrainCamNode m_arrTrainCamNode[MAX_NUM_OF_NODES]; + CMatrix m_cameraMatrix; + bool m_bGarageFixedCamPositionSet; + bool m_vecDoingSpecialInterPolation; + bool m_bScriptParametersSetForInterPol; + bool m_bFading; + bool m_bMusicFading; + CMatrix m_viewMatrix; + CVector m_vecFrustumNormals[4]; + CVector m_vecOldSourceForInter; + CVector m_vecOldFrontForInter; + CVector m_vecOldUpForInter; + float m_vecOldFOVForInter; + float m_fFLOATingFade; + float m_fFLOATingFadeMusic; + float m_fTimeToFadeOut; + float m_fTimeToFadeMusic; + float m_fFractionInterToStopMoving; + float m_fFractionInterToStopCatchUp; + float m_fGaitSwayBuffer; + float m_fScriptPercentageInterToStopMoving; + float m_fScriptPercentageInterToCatchUp; + + uint32 m_fScriptTimeForInterPolation; + + + int16 m_iFadingDirection; + int m_iModeObbeCamIsInForCar; + int16 m_iModeToGoTo; + int16 m_iMusicFadingDirection; + int16 m_iTypeOfSwitch; + + uint32 m_uiFadeTimeStarted; + uint32 m_uiFadeTimeStartedMusic; + + static bool m_bUseMouse3rdPerson; +#ifdef FREE_CAM + static bool bFreeCam; +#endif + + // High level and misc + CCamera(void); + CCamera(float); + void Init(void); + void Process(void); + void CamControl(void); + void UpdateTargetEntity(void); + void UpdateSoundDistances(void); + void InitialiseCameraForDebugMode(void); + void CamShake(float strength, float x, float y, float z); + bool Get_Just_Switched_Status() { return m_bJust_Switched; } + + // Who's in control + void TakeControl(CEntity *target, int16 mode, int16 typeOfSwitch, int32 controller); + void TakeControlNoEntity(const CVector &position, int16 typeOfSwitch, int32 controller); + void TakeControlWithSpline(int16 typeOfSwitch); + void Restore(void); + void RestoreWithJumpCut(void); + void SetCamPositionForFixedMode(const CVector &Source, const CVector &UppOffSet); + + // Transition + void StartTransition(int16 mode); + void StartTransitionWhenNotFinishedInter(int16 mode); + void StoreValuesDuringInterPol(CVector &source, CVector &target, CVector &up, float &FOV); + + // Widescreen borders + void SetWideScreenOn(void); + void SetWideScreenOff(void); + void ProcessWideScreenOn(void); + void DrawBordersForWideScreen(void); + + // Obbe's cam + bool IsItTimeForNewcam(int32 obbeMode, int32 time); + bool TryToStartNewCamMode(int32 obbeMode); + void DontProcessObbeCinemaCamera(void); + void ProcessObbeCinemaCameraCar(void); + void ProcessObbeCinemaCameraPed(void); + + // Train + void LoadTrainCamNodes(char const *name); + void Process_Train_Camera_Control(void); + + // Script + void LoadPathSplines(int file); + void FinishCutscene(void); + float GetPositionAlongSpline(void) { return m_fPositionAlongSpline; } + uint32 GetCutSceneFinishTime(void); + void SetCamCutSceneOffSet(const CVector &pos); + void SetPercentAlongCutScene(float percent); + void SetParametersForScriptInterpolation(float stopMoving, float catchUp, int32 time); + void SetZoomValueFollowPedScript(int16 dist); + void SetZoomValueCamStringScript(int16 dist); + void SetNearClipScript(float); + + // Fading + void ProcessFade(void); + void ProcessMusicFade(void); + void Fade(float timeout, int16 direction); + void SetFadeColour(uint8 r, uint8 g, uint8 b); + bool GetFading(void); + int GetFadingDirection(void); + int GetScreenFadeStatus(void); + + // Motion blur + void RenderMotionBlur(void); + void SetMotionBlur(int r, int g, int b, int a, int type); + void SetMotionBlurAlpha(int a); + + // Player looking and aiming + int GetLookDirection(void); + bool GetLookingForwardFirstPerson(void); + bool GetLookingLRBFirstPerson(void); + void SetCameraDirectlyInFrontForFollowPed_CamOnAString(void); + void SetCameraDirectlyBehindForFollowPed_CamOnAString(void); + void SetNewPlayerWeaponMode(int16 mode, int16 minZoom, int16 maxZoom); + void ClearPlayerWeaponMode(void); + void UpdateAimingCoors(CVector const &coors); + bool Find3rdPersonCamTargetVector(float dist, CVector pos, CVector &source, CVector &target); + float Find3rdPersonQuickAimPitch(void); + + // Physical camera + void SetRwCamera(RwCamera *cam); + const CMatrix& GetCameraMatrix(void) { return m_cameraMatrix; } + CVector &GetGameCamPosition(void) { return m_vecGameCamPos; } + void CalculateDerivedValues(void); + bool IsPointVisible(const CVector ¢er, const CMatrix *mat); + bool IsSphereVisible(const CVector ¢er, float radius, Const CMatrix *mat); + bool IsSphereVisible(const CVector ¢er, float radius); + bool IsBoxVisible(CVUVECTOR *box, const CMatrix *mat); +}; + +VALIDATE_SIZE(CCamera, 0xE9D8); + +extern CCamera TheCamera; + +void CamShakeNoPos(CCamera*, float); +void MakeAngleLessThan180(float &Angle); +void WellBufferMe(float Target, float *CurrentValue, float *CurrentSpeed, float MaxSpeed, float Acceleration, bool IsAngle); diff --git a/src/core/CdStream.cpp b/src/core/CdStream.cpp new file mode 100644 index 0000000..977f16c --- /dev/null +++ b/src/core/CdStream.cpp @@ -0,0 +1,538 @@ +#ifdef _WIN32 +#define WITHWINDOWS +#include "common.h" + +#include "CdStream.h" +#include "rwcore.h" +#include "RwHelper.h" +#include "MemoryMgr.h" + +struct CdReadInfo +{ + uint32 nSectorOffset; + uint32 nSectorsToRead; + void *pBuffer; + char field_C; + bool bLocked; + bool bReading; + int32 nStatus; + HANDLE pDoneSemaphore; // used for CdStreamSync + HANDLE hFile; + OVERLAPPED Overlapped; +}; + +VALIDATE_SIZE(CdReadInfo, 0x30); + +char gCdImageNames[MAX_CDIMAGES+1][64]; +int32 gNumImages; +int32 gNumChannels; + +HANDLE gImgFiles[MAX_CDIMAGES]; + +HANDLE _gCdStreamThread; +HANDLE gCdStreamSema; // released when we have new thing to read(so channel is set) +DWORD _gCdStreamThreadId; + +CdReadInfo *gpReadInfo; +Queue gChannelRequestQ; + +int32 lastPosnRead; + +BOOL _gbCdStreamOverlapped; +BOOL _gbCdStreamAsync; +DWORD _gdwCdStreamFlags; + +DWORD WINAPI CdStreamThread(LPVOID lpThreadParameter); + +void +CdStreamInitThread(void) +{ + SetLastError(0); + + if ( gNumChannels > 0 ) + { + for ( int32 i = 0; i < gNumChannels; i++ ) + { + gpReadInfo[i].pDoneSemaphore = CreateSemaphore(nil, 0, 2, nil); + + if ( gpReadInfo[i].pDoneSemaphore == nil ) + { + printf("%s: failed to create sync semaphore\n", "cdvd_stream"); + ASSERT(0); + return; + } + } + } + + gChannelRequestQ.items = (int32 *)LocalAlloc(LMEM_ZEROINIT, sizeof(int32) * (gNumChannels + 1)); + gChannelRequestQ.head = 0; + gChannelRequestQ.tail = 0; + gChannelRequestQ.size = gNumChannels + 1; + ASSERT(gChannelRequestQ.items != nil ); + +#ifdef FIX_BUGS + gCdStreamSema = CreateSemaphore(nil, 0, 5, nil); +#else + gCdStreamSema = CreateSemaphore(nil, 0, 5, "CdStream"); +#endif + + if ( gCdStreamSema == nil ) + { + printf("%s: failed to create stream semaphore\n", "cdvd_stream"); + ASSERT(0); + return; + } + + _gCdStreamThread = CreateThread(nil, 64*1024/*64KB*/, CdStreamThread, nil, CREATE_SUSPENDED, &_gCdStreamThreadId); + + if ( _gCdStreamThread == nil ) + { + printf("%s: failed to create streaming thread\n", "cdvd_stream"); + ASSERT(0); + return; + } + + SetThreadPriority(_gCdStreamThread, GetThreadPriority(GetCurrentThread()) - 1); + + ResumeThread(_gCdStreamThread); +} + +void +CdStreamInit(int32 numChannels) +{ + DWORD SectorsPerCluster; + DWORD BytesPerSector; + DWORD NumberOfFreeClusters; + DWORD TotalNumberOfClusters; + + GetDiskFreeSpace(nil, &SectorsPerCluster, &BytesPerSector, &NumberOfFreeClusters, &TotalNumberOfClusters); + + _gdwCdStreamFlags = 0; + +#ifndef FIX_BUGS // this just slows down streaming + if ( BytesPerSector <= CDSTREAM_SECTOR_SIZE ) + { + _gdwCdStreamFlags |= FILE_FLAG_NO_BUFFERING; + debug("Using no buffered loading for streaming\n"); + } +#endif + + _gbCdStreamOverlapped = TRUE; + + _gdwCdStreamFlags |= FILE_FLAG_OVERLAPPED; + + _gbCdStreamAsync = FALSE; + + void *pBuffer = (void *)RwMallocAlign(CDSTREAM_SECTOR_SIZE, BytesPerSector); + ASSERT( pBuffer != nil ); + + SetLastError(0); + + gNumImages = 0; + + gNumChannels = numChannels; + + gpReadInfo = (CdReadInfo *)LocalAlloc(LMEM_ZEROINIT, sizeof(CdReadInfo) * numChannels); + ASSERT( gpReadInfo != nil ); + + debug("%s: read info %p\n", "cdvd_stream", gpReadInfo); + + CdStreamAddImage("MODELS\\GTA3.IMG"); + + int32 nStatus = CdStreamRead(0, pBuffer, 0, 1); + + CdStreamRemoveImages(); + + if ( nStatus == STREAM_SUCCESS ) + { + _gbCdStreamAsync = TRUE; + + debug("Using async loading for streaming\n"); + } + else + { + _gdwCdStreamFlags &= ~FILE_FLAG_OVERLAPPED; + + _gbCdStreamOverlapped = FALSE; + + _gbCdStreamAsync = TRUE; + + debug("Using sync loading for streaming\n"); + } + + CdStreamInitThread(); + + ASSERT( pBuffer != nil ); + RwFreeAlign(pBuffer); +} + +uint32 +GetGTA3ImgSize(void) +{ + ASSERT( gImgFiles[0] != nil ); + return (uint32)GetFileSize(gImgFiles[0], nil); +} + +void +CdStreamShutdown(void) +{ + if ( _gbCdStreamAsync ) + { + LocalFree(gChannelRequestQ.items); + CloseHandle(gCdStreamSema); + CloseHandle(_gCdStreamThread); + + for ( int32 i = 0; i < gNumChannels; i++ ) + CloseHandle(gpReadInfo[i].pDoneSemaphore); + } + + LocalFree(gpReadInfo); +} + + + +int32 +CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size) +{ + ASSERT( channel < gNumChannels ); + ASSERT( buffer != nil ); + + lastPosnRead = size + offset; + + ASSERT( _GET_INDEX(offset) < MAX_CDIMAGES ); + HANDLE hImage = gImgFiles[_GET_INDEX(offset)]; + ASSERT( hImage != nil ); + + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + pChannel->hFile = hImage; + + SetLastError(0); + + if ( _gbCdStreamAsync ) + { + if ( pChannel->nSectorsToRead != 0 || pChannel->bReading ) + return STREAM_NONE; + + pChannel->nStatus = STREAM_NONE; + pChannel->nSectorOffset = _GET_OFFSET(offset); + pChannel->nSectorsToRead = size; + pChannel->pBuffer = buffer; + pChannel->bLocked = 0; + + AddToQueue(&gChannelRequestQ, channel); + + if ( !ReleaseSemaphore(gCdStreamSema, 1, nil) ) + printf("Signal Sema Error\n"); + + return STREAM_SUCCESS; + } + + if ( _gbCdStreamOverlapped ) + { + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + pChannel->Overlapped.Offset = _GET_OFFSET(offset) * CDSTREAM_SECTOR_SIZE; + + if ( !ReadFile(hImage, buffer, size * CDSTREAM_SECTOR_SIZE, NULL, &pChannel->Overlapped) + && GetLastError() != ERROR_IO_PENDING ) + return STREAM_NONE; + else + return STREAM_SUCCESS; + } + +#ifdef BIG_IMG + LARGE_INTEGER liDistanceToMove; + liDistanceToMove.QuadPart = _GET_OFFSET(offset); + liDistanceToMove.QuadPart *= CDSTREAM_SECTOR_SIZE; + SetFilePointerEx(hImage, liDistanceToMove, nil, FILE_BEGIN); +#else + SetFilePointer(hImage, _GET_OFFSET(offset) * CDSTREAM_SECTOR_SIZE, nil, FILE_BEGIN); +#endif + + DWORD NumberOfBytesRead; + + if ( !ReadFile(hImage, buffer, size * CDSTREAM_SECTOR_SIZE, &NumberOfBytesRead, nil) ) + return STREAM_NONE; + else + return STREAM_SUCCESS; +} + +int32 +CdStreamGetStatus(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + if ( _gbCdStreamAsync ) + { + if ( pChannel->bReading ) + return STREAM_READING; + + if ( pChannel->nSectorsToRead != 0 ) + return STREAM_WAITING; + + if ( pChannel->nStatus != STREAM_NONE ) + { + int32 status = pChannel->nStatus; + + pChannel->nStatus = STREAM_NONE; + + return status; + } + + return STREAM_NONE; + } + + if ( _gbCdStreamOverlapped ) + { + ASSERT( pChannel->hFile != nil ); + if ( WaitForSingleObjectEx(pChannel->hFile, 0, TRUE) == WAIT_OBJECT_0 ) + return STREAM_NONE; + else + return STREAM_READING; + } + + return STREAM_NONE; +} + +int32 +CdStreamGetLastPosn(void) +{ + return lastPosnRead; +} + +// wait for channel to finish reading +int32 +CdStreamSync(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + if ( _gbCdStreamAsync ) + { + if ( pChannel->nSectorsToRead != 0 ) + { + pChannel->bLocked = true; + + ASSERT( pChannel->pDoneSemaphore != nil ); + + // Deadlock fix 1 +#ifdef FIX_BUGS + // This is while loop on Posix streamer, for spurious wakeups + if (pChannel->bLocked && pChannel->nSectorsToRead != 0){ + WaitForSingleObject(pChannel->pDoneSemaphore, INFINITE); + } + pChannel->bLocked = false; +#else + WaitForSingleObject(pChannel->pDoneSemaphore, INFINITE); +#endif + } + + pChannel->bReading = false; + + return pChannel->nStatus; + } + + DWORD NumberOfBytesTransferred; + + if ( _gbCdStreamOverlapped && pChannel->hFile ) + { + ASSERT(pChannel->hFile != nil ); + // Beware: This is blocking call (because of last parameter) + if ( GetOverlappedResult(pChannel->hFile, &pChannel->Overlapped, &NumberOfBytesTransferred, TRUE) ) + return STREAM_NONE; + else + return STREAM_ERROR; + } + + return STREAM_NONE; +} + +void +AddToQueue(Queue *queue, int32 item) +{ + ASSERT( queue != nil ); + ASSERT( queue->items != nil ); + queue->items[queue->tail] = item; + + queue->tail = (queue->tail + 1) % queue->size; + + if ( queue->head == queue->tail ) + debug("Queue is full\n"); +} + +int32 +GetFirstInQueue(Queue *queue) +{ + ASSERT( queue != nil ); + if ( queue->head == queue->tail ) + return -1; + + ASSERT( queue->items != nil ); + return queue->items[queue->head]; +} + +void +RemoveFirstInQueue(Queue *queue) +{ + ASSERT( queue != nil ); + if ( queue->head == queue->tail ) + { + debug("Queue is empty\n"); + return; + } + + queue->head = (queue->head + 1) % queue->size; +} + +DWORD +WINAPI CdStreamThread(LPVOID lpThreadParameter) +{ + debug("Created cdstream thread\n"); + + while ( true ) + { + WaitForSingleObject(gCdStreamSema, INFINITE); + + int32 channel = GetFirstInQueue(&gChannelRequestQ); + ASSERT( channel < gNumChannels ); + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + pChannel->bReading = true; + + if ( pChannel->nStatus == STREAM_NONE ) + { + if ( _gbCdStreamOverlapped ) + { + pChannel->Overlapped.Offset = pChannel->nSectorOffset * CDSTREAM_SECTOR_SIZE; + + ASSERT(pChannel->hFile != nil ); + ASSERT(pChannel->pBuffer != nil ); + + DWORD NumberOfBytesTransferred; + + if ( ReadFile(pChannel->hFile, + pChannel->pBuffer, + pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE, + NULL, + &pChannel->Overlapped) ) + { + pChannel->nStatus = STREAM_NONE; + } + // Beware: This is blocking call (because of last parameter) + else if ( GetLastError() == ERROR_IO_PENDING + && GetOverlappedResult(pChannel->hFile, &pChannel->Overlapped, &NumberOfBytesTransferred, TRUE) ) + { + pChannel->nStatus = STREAM_NONE; + } + else + { + pChannel->nStatus = STREAM_ERROR; + } + } + else + { + ASSERT(pChannel->hFile != nil ); + ASSERT(pChannel->pBuffer != nil ); + + SetFilePointer(pChannel->hFile, pChannel->nSectorOffset * CDSTREAM_SECTOR_SIZE, nil, FILE_BEGIN); + + DWORD NumberOfBytesRead; + if ( ReadFile(pChannel->hFile, + pChannel->pBuffer, + pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE, + &NumberOfBytesRead, + NULL) ) + { + pChannel->nStatus = STREAM_NONE; + } + } + } + + RemoveFirstInQueue(&gChannelRequestQ); + + pChannel->nSectorsToRead = 0; + + if ( pChannel->bLocked ) + { + ASSERT( pChannel->pDoneSemaphore != nil ); + // Deadlock fix 2 +#ifdef FIX_BUGS + pChannel->bLocked = 0; +#endif + ReleaseSemaphore(pChannel->pDoneSemaphore, 1, NULL); + } + + pChannel->bReading = false; + } +} + +bool +CdStreamAddImage(char const *path) +{ + ASSERT(path != nil); + ASSERT(gNumImages < MAX_CDIMAGES); + + SetLastError(0); + + gImgFiles[gNumImages] = CreateFile(path, + GENERIC_READ, + FILE_SHARE_READ, + nil, + OPEN_EXISTING, + _gdwCdStreamFlags | FILE_FLAG_RANDOM_ACCESS | FILE_ATTRIBUTE_READONLY, + nil); + + ASSERT( gImgFiles[gNumImages] != nil ); + if ( gImgFiles[gNumImages] == NULL ) + return false; + + strcpy(gCdImageNames[gNumImages], path); + + gNumImages++; + + return true; +} + +char * +CdStreamGetImageName(int32 cd) +{ + ASSERT(cd < MAX_CDIMAGES); + if ( gImgFiles[cd] != nil ) + return gCdImageNames[cd]; + + return nil; +} + +void +CdStreamRemoveImages(void) +{ + for ( int32 i = 0; i < gNumChannels; i++ ) + CdStreamSync(i); + + for ( int32 i = 0; i < gNumImages; i++ ) + { + SetLastError(0); + + CloseHandle(gImgFiles[i]); + gImgFiles[i] = nil; + } + + gNumImages = 0; +} + +int32 +CdStreamGetNumImages(void) +{ + return gNumImages; +} +#endif diff --git a/src/core/CdStream.h b/src/core/CdStream.h new file mode 100644 index 0000000..516cef4 --- /dev/null +++ b/src/core/CdStream.h @@ -0,0 +1,48 @@ +#pragma once + +#define CDSTREAM_SECTOR_SIZE 2048 + +#define _GET_INDEX(a) (a >> 24) +#define _GET_OFFSET(a) (a & 0xFFFFFF) + +enum +{ + STREAM_NONE = uint8( 0), + STREAM_SUCCESS = uint8( 1), + STREAM_READING = uint8(-1), // 0xFF, + STREAM_ERROR = uint8(-2), // 0xFE, + STREAM_ERROR_NOCD = uint8(-3), // 0xFD, + STREAM_ERROR_WRONGCD = uint8(-4), // 0xFC, + STREAM_ERROR_OPENCD = uint8(-5), // 0xFB, + STREAM_WAITING = uint8(-6) // 0xFA, +}; + +struct Queue +{ + int32 *items; + int32 head; + int32 tail; + int32 size; +}; + +VALIDATE_SIZE(Queue, 0x10); + +void CdStreamInitThread(void); +void CdStreamInit(int32 numChannels); +uint32 GetGTA3ImgSize(void); +void CdStreamShutdown(void); +int32 CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size); +int32 CdStreamGetStatus(int32 channel); +int32 CdStreamGetLastPosn(void); +int32 CdStreamSync(int32 channel); +void AddToQueue(Queue *queue, int32 item); +int32 GetFirstInQueue(Queue *queue); +void RemoveFirstInQueue(Queue *queue); +bool CdStreamAddImage(char const *path); +char *CdStreamGetImageName(int32 cd); +void CdStreamRemoveImages(void); +int32 CdStreamGetNumImages(void); + +#ifdef FLUSHABLE_STREAMING +extern bool flushStream[MAX_CDCHANNELS]; +#endif diff --git a/src/core/CdStreamPosix.cpp b/src/core/CdStreamPosix.cpp new file mode 100644 index 0000000..bc9129e --- /dev/null +++ b/src/core/CdStreamPosix.cpp @@ -0,0 +1,604 @@ +#ifndef _WIN32 +#include "common.h" +#include "crossplatform.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#endif + +#include "CdStream.h" +#include "rwcore.h" +#include "MemoryMgr.h" + +#define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) +#define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) + +#ifdef FLUSHABLE_STREAMING +bool flushStream[MAX_CDCHANNELS]; +#endif + +#ifdef USE_UNNAMED_SEM + +#define RE3_SEM_OPEN(name, ...) re3_sem_open() +sem_t* +re3_sem_open(void) +{ + sem_t* sem = (sem_t*)malloc(sizeof(sem_t)); + if (sem_init(sem, 0, 1) == -1) { + sem = SEM_FAILED; + } + + return sem; +} + +#define RE3_SEM_CLOSE(sem, format, ...) re3_sem_close(sem) +void +re3_sem_close(sem_t* sem) +{ + sem_destroy(sem); + free(sem); +} + +#else + +#define RE3_SEM_OPEN re3_sem_open +sem_t* +re3_sem_open(const char* format, ...) +{ + char semName[21]; + va_list va; + va_start(va, format); + vsprintf(semName, format, va); + + return sem_open(semName, O_CREAT, 0644, 1); +} + +#define RE3_SEM_CLOSE re3_sem_close +void +re3_sem_close(sem_t* sem, const char* format, ...) +{ + sem_close(sem); + + char semName[21]; + va_list va; + va_start(va, format); + vsprintf(semName, format, va); + + sem_unlink(semName); +} + +#endif + +struct CdReadInfo +{ + uint32 nSectorOffset; + uint32 nSectorsToRead; + void *pBuffer; + bool bLocked; + bool bReading; + int32 nStatus; +#ifdef ONE_THREAD_PER_CHANNEL + int8 nThreadStatus; // 0: created 1:priority set up 2:abort now + pthread_t pChannelThread; + sem_t *pStartSemaphore; +#endif + sem_t *pDoneSemaphore; // used for CdStreamSync + int32 hFile; +}; + +char gCdImageNames[MAX_CDIMAGES+1][64]; +int32 gNumImages; +int32 gNumChannels; + +int32 gImgFiles[MAX_CDIMAGES]; // -1: error 0:unused otherwise: fd +char *gImgNames[MAX_CDIMAGES]; + +#ifndef ONE_THREAD_PER_CHANNEL +pthread_t _gCdStreamThread; +sem_t *gCdStreamSema; // released when we have new thing to read(so channel is set) +int8 gCdStreamThreadStatus; // 0: created 1:priority set up 2:abort now +Queue gChannelRequestQ; +bool _gbCdStreamOverlapped; +#endif + +CdReadInfo *gpReadInfo; + +int32 lastPosnRead; + +int _gdwCdStreamFlags; + +void *CdStreamThread(void* channelId); + +void +CdStreamInitThread(void) +{ + int status; +#ifndef ONE_THREAD_PER_CHANNEL + gChannelRequestQ.items = (int32 *)calloc(gNumChannels + 1, sizeof(int32)); + gChannelRequestQ.head = 0; + gChannelRequestQ.tail = 0; + gChannelRequestQ.size = gNumChannels + 1; + ASSERT(gChannelRequestQ.items != nil ); + gCdStreamSema = RE3_SEM_OPEN("/semaphore_cd_stream"); + + + if (gCdStreamSema == SEM_FAILED) { + CDTRACE("failed to create stream semaphore"); + ASSERT(0); + return; + } +#endif + + if ( gNumChannels > 0 ) + { + for ( int32 i = 0; i < gNumChannels; i++ ) + { + gpReadInfo[i].pDoneSemaphore = RE3_SEM_OPEN("/semaphore_done%d", i); + + if (gpReadInfo[i].pDoneSemaphore == SEM_FAILED) + { + CDTRACE("failed to create sync semaphore"); + ASSERT(0); + return; + } + +#ifdef ONE_THREAD_PER_CHANNEL + gpReadInfo[i].pStartSemaphore = RE3_SEM_OPEN("/semaphore_start%d", i); + + if (gpReadInfo[i].pStartSemaphore == SEM_FAILED) + { + CDTRACE("failed to create start semaphore"); + ASSERT(0); + return; + } + gpReadInfo[i].nThreadStatus = 0; + int *channelI = (int*)malloc(sizeof(int)); + *channelI = i; + status = pthread_create(&gpReadInfo[i].pChannelThread, NULL, CdStreamThread, (void*)channelI); + + if (status == -1) + { + CDTRACE("failed to create sync thread"); + ASSERT(0); + return; + } +#endif + } + } + +#ifndef ONE_THREAD_PER_CHANNEL + debug("Using one streaming thread for all channels\n"); + gCdStreamThreadStatus = 0; + status = pthread_create(&_gCdStreamThread, NULL, CdStreamThread, nil); + + if (status == -1) + { + CDTRACE("failed to create sync thread"); + ASSERT(0); + return; + } +#else + debug("Using separate streaming threads for each channel\n"); +#endif +} + +void +CdStreamInit(int32 numChannels) +{ + struct statvfs fsInfo; + + if((statvfs("models/gta3.img", &fsInfo)) < 0) + { + CDTRACE("can't get filesystem info"); + ASSERT(0); + return; + } +#ifdef __linux__ + _gdwCdStreamFlags = O_RDONLY | O_NOATIME; +#else + _gdwCdStreamFlags = O_RDONLY; +#endif + // People say it's slower +/* + if ( fsInfo.f_bsize <= CDSTREAM_SECTOR_SIZE ) + { + _gdwCdStreamFlags |= O_DIRECT; + debug("Using no buffered loading for streaming\n"); + } +*/ + void *pBuffer = (void *)RwMallocAlign(CDSTREAM_SECTOR_SIZE, (RwUInt32)fsInfo.f_bsize); + ASSERT( pBuffer != nil ); + + gNumImages = 0; + + gNumChannels = numChannels; + ASSERT( gNumChannels != 0 ); + + gpReadInfo = (CdReadInfo *)calloc(numChannels, sizeof(CdReadInfo)); + ASSERT( gpReadInfo != nil ); + + CDDEBUG("read info %p", gpReadInfo); + + CdStreamInitThread(); + + ASSERT( pBuffer != nil ); + RwFreeAlign(pBuffer); +} + +uint32 +GetGTA3ImgSize(void) +{ + ASSERT( gImgFiles[0] > 0 ); + struct stat statbuf; + + char path[PATH_MAX]; + realpath(gImgNames[0], path); + if (stat(path, &statbuf) == -1) { + // Try case-insensitivity + char* real = casepath(gImgNames[0], false); + if (real) + { + realpath(real, path); + free(real); + if (stat(path, &statbuf) != -1) + goto ok; + } + + CDTRACE("can't get size of gta3.img"); + ASSERT(0); + return 0; + } + ok: + return (uint32)statbuf.st_size; +} + +void +CdStreamShutdown(void) +{ + // Destroying semaphores and free(gpReadInfo) will be done at threads +#ifndef ONE_THREAD_PER_CHANNEL + gCdStreamThreadStatus = 2; + sem_post(gCdStreamSema); + pthread_join(_gCdStreamThread, nil); +#else + for ( int32 i = 0; i < gNumChannels; i++ ) { + gpReadInfo[i].nThreadStatus = 2; + sem_post(gpReadInfo[i].pStartSemaphore); + pthread_join(gpReadInfo[i].pChannelThread, nil); + } +#endif +} + + +int32 +CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size) +{ + ASSERT( channel < gNumChannels ); + ASSERT( buffer != nil ); + + lastPosnRead = size + offset; + + ASSERT( _GET_INDEX(offset) < MAX_CDIMAGES ); + int32 hImage = gImgFiles[_GET_INDEX(offset)]; + ASSERT( hImage > 0 ); + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + if ( pChannel->nSectorsToRead != 0 || pChannel->bReading ) { + if (pChannel->hFile == hImage - 1 && pChannel->nSectorOffset == _GET_OFFSET(offset) && pChannel->nSectorsToRead >= size) + return STREAM_SUCCESS; +#ifdef FLUSHABLE_STREAMING + flushStream[channel] = 1; + CdStreamSync(channel); +#else + return STREAM_NONE; +#endif + } + + pChannel->hFile = hImage - 1; + pChannel->nStatus = STREAM_NONE; + pChannel->nSectorOffset = _GET_OFFSET(offset); + pChannel->nSectorsToRead = size; + pChannel->pBuffer = buffer; + pChannel->bLocked = 0; + +#ifndef ONE_THREAD_PER_CHANNEL + AddToQueue(&gChannelRequestQ, channel); + if ( sem_post(gCdStreamSema) != 0 ) + printf("Signal Sema Error\n"); +#else + if ( sem_post(pChannel->pStartSemaphore) != 0 ) + printf("Signal Sema Error\n"); +#endif + + return STREAM_SUCCESS; +} + +int32 +CdStreamGetStatus(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + +#ifdef ONE_THREAD_PER_CHANNEL + if (pChannel->nThreadStatus == 2) + return STREAM_NONE; +#else + if (gCdStreamThreadStatus == 2) + return STREAM_NONE; +#endif + + if ( pChannel->bReading ) + return STREAM_READING; + + if ( pChannel->nSectorsToRead != 0 ) + return STREAM_WAITING; + + if ( pChannel->nStatus != STREAM_NONE ) + { + int32 status = pChannel->nStatus; + pChannel->nStatus = STREAM_NONE; + + return status; + } + + return STREAM_NONE; +} + +int32 +CdStreamGetLastPosn(void) +{ + return lastPosnRead; +} + +// wait for channel to finish reading +int32 +CdStreamSync(int32 channel) +{ + ASSERT( channel < gNumChannels ); + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + +#ifdef FLUSHABLE_STREAMING + if (flushStream[channel]) { + pChannel->nSectorsToRead = 0; +#ifdef ONE_THREAD_PER_CHANNEL + pthread_kill(pChannel->pChannelThread, SIGUSR1); + if (pChannel->bReading) { + pChannel->bLocked = true; +#else + if (pChannel->bReading) { + pChannel->bLocked = true; + pthread_kill(_gCdStreamThread, SIGUSR1); +#endif + while (pChannel->bLocked) + sem_wait(pChannel->pDoneSemaphore); + } + pChannel->bReading = false; + flushStream[channel] = false; + return STREAM_NONE; + } +#endif + + if ( pChannel->nSectorsToRead != 0 ) + { + pChannel->bLocked = true; + while (pChannel->bLocked && pChannel->nSectorsToRead != 0){ + sem_wait(pChannel->pDoneSemaphore); + } + pChannel->bLocked = false; + } + + pChannel->bReading = false; + + return pChannel->nStatus; +} + +void +AddToQueue(Queue *queue, int32 item) +{ + ASSERT( queue != nil ); + ASSERT( queue->items != nil ); + queue->items[queue->tail] = item; + + queue->tail = (queue->tail + 1) % queue->size; + + if ( queue->head == queue->tail ) + debug("Queue is full\n"); +} + +int32 +GetFirstInQueue(Queue *queue) +{ + ASSERT( queue != nil ); + if ( queue->head == queue->tail ) + return -1; + + ASSERT( queue->items != nil ); + return queue->items[queue->head]; +} + +void +RemoveFirstInQueue(Queue *queue) +{ + ASSERT( queue != nil ); + if ( queue->head == queue->tail ) + { + debug("Queue is empty\n"); + return; + } + + queue->head = (queue->head + 1) % queue->size; +} + +void *CdStreamThread(void *param) +{ + debug("Created cdstream thread\n"); + +#ifndef ONE_THREAD_PER_CHANNEL + while (gCdStreamThreadStatus != 2) { + sem_wait(gCdStreamSema); + + int32 channel = GetFirstInQueue(&gChannelRequestQ); + + // spurious wakeup + if (channel == -1) + continue; +#else + int channel = *((int*)param); + while (gpReadInfo[channel].nThreadStatus != 2){ + sem_wait(gpReadInfo[channel].pStartSemaphore); +#endif + + CdReadInfo *pChannel = &gpReadInfo[channel]; + ASSERT( pChannel != nil ); + + // spurious wakeup or we sent interrupt signal for flushing + if(pChannel->nSectorsToRead == 0) + continue; + + pChannel->bReading = true; + + // Not standard POSIX :shrug: +#ifdef __linux__ +#ifdef ONE_THREAD_PER_CHANNEL + if (gpReadInfo[channel].nThreadStatus == 0){ + gpReadInfo[channel].nThreadStatus = 1; +#else + if (gCdStreamThreadStatus == 0){ + gCdStreamThreadStatus = 1; +#endif + pid_t tid = syscall(SYS_gettid); + int ret = setpriority(PRIO_PROCESS, tid, getpriority(PRIO_PROCESS, getpid()) + 1); + } +#endif + if ( pChannel->nStatus == STREAM_NONE ) + { + ASSERT(pChannel->hFile >= 0); + ASSERT(pChannel->pBuffer != nil ); + + lseek(pChannel->hFile, (size_t)pChannel->nSectorOffset * (size_t)CDSTREAM_SECTOR_SIZE, SEEK_SET); + if (read(pChannel->hFile, pChannel->pBuffer, pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE) == -1) { + // pChannel->nSectorsToRead == 0 at this point means we wanted to flush channel + // STREAM_WAITING is a little hack to make CStreaming not process this data + pChannel->nStatus = pChannel->nSectorsToRead == 0 ? STREAM_WAITING : STREAM_ERROR; + } else { + pChannel->nStatus = STREAM_NONE; + } + } + +#ifndef ONE_THREAD_PER_CHANNEL + RemoveFirstInQueue(&gChannelRequestQ); +#endif + + pChannel->nSectorsToRead = 0; + if ( pChannel->bLocked ) + { + pChannel->bLocked = 0; + sem_post(pChannel->pDoneSemaphore); + } + pChannel->bReading = false; + } + char semName[20]; +#ifndef ONE_THREAD_PER_CHANNEL + for ( int32 i = 0; i < gNumChannels; i++ ) + { + RE3_SEM_CLOSE(gpReadInfo[i].pDoneSemaphore, "/semaphore_done%d", i); + } + RE3_SEM_CLOSE(gCdStreamSema, "/semaphore_cd_stream"); + free(gChannelRequestQ.items); +#else + RE3_SEM_CLOSE(gpReadInfo[channel].pStartSemaphore, "/semaphore_start%d", channel); + + RE3_SEM_CLOSE(gpReadInfo[channel].pDoneSemaphore, "/semaphore_done%d", channel); +#endif + if (gpReadInfo) + free(gpReadInfo); + gpReadInfo = nil; + pthread_exit(nil); +} + +bool +CdStreamAddImage(char const *path) +{ + ASSERT(path != nil); + ASSERT(gNumImages < MAX_CDIMAGES); + + gImgFiles[gNumImages] = open(path, _gdwCdStreamFlags); + + // Fix case sensitivity and backslashes. + if (gImgFiles[gNumImages] == -1) { + char* real = casepath(path, false); + if (real) + { + gImgFiles[gNumImages] = open(real, _gdwCdStreamFlags); + free(real); + } + } + + if ( gImgFiles[gNumImages] == -1 ) { + assert(false); + return false; + } + + gImgNames[gNumImages] = strdup(path); + gImgFiles[gNumImages]++; // because -1: error 0: not used + + strcpy(gCdImageNames[gNumImages], path); + + gNumImages++; + + return true; +} + +char * +CdStreamGetImageName(int32 cd) +{ + ASSERT(cd < MAX_CDIMAGES); + if ( gImgFiles[cd] > 0) + return gCdImageNames[cd]; + + return nil; +} + +void +CdStreamRemoveImages(void) +{ + for ( int32 i = 0; i < gNumChannels; i++ ) { +#ifdef FLUSHABLE_STREAMING + flushStream[i] = 1; +#endif + CdStreamSync(i); + } + + for ( int32 i = 0; i < gNumImages; i++ ) + { + close(gImgFiles[i] - 1); + free(gImgNames[i]); + gImgFiles[i] = 0; + } + + gNumImages = 0; +} + +int32 +CdStreamGetNumImages(void) +{ + return gNumImages; +} +#endif diff --git a/src/core/Clock.cpp b/src/core/Clock.cpp new file mode 100644 index 0000000..e4b908e --- /dev/null +++ b/src/core/Clock.cpp @@ -0,0 +1,117 @@ +#include "common.h" + +#include "Timer.h" +#include "Pad.h" +#include "Clock.h" +#include "Stats.h" + +_TODO("gbFastTime"); +bool gbFastTime; + +uint8 CClock::ms_nGameClockHours; +uint8 CClock::ms_nGameClockMinutes; +uint16 CClock::ms_nGameClockSeconds; +uint8 CClock::ms_Stored_nGameClockHours; +uint8 CClock::ms_Stored_nGameClockMinutes; +uint16 CClock::ms_Stored_nGameClockSeconds; +uint32 CClock::ms_nMillisecondsPerGameMinute; +uint32 CClock::ms_nLastClockTick; +bool CClock::ms_bClockHasBeenStored; + +void +CClock::Initialise(uint32 scale) +{ + debug("Initialising CClock...\n"); + ms_nGameClockHours = 12; + ms_nGameClockMinutes = 0; + ms_nGameClockSeconds = 0; + ms_nMillisecondsPerGameMinute = scale; + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); + ms_bClockHasBeenStored = false; + debug("CClock ready\n"); +} + +void +CClock::Update(void) +{ + if(CPad::GetPad(1)->GetRightShoulder1()) + { + ms_nGameClockMinutes += 8; + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); + + if(ms_nGameClockMinutes >= 60) + { + ms_nGameClockHours++; + ms_nGameClockMinutes = 0; + if(ms_nGameClockHours >= 24) + ms_nGameClockHours = 0; + } + + } + else if(CTimer::GetTimeInMilliseconds() - ms_nLastClockTick > ms_nMillisecondsPerGameMinute || gbFastTime) + { + ms_nGameClockMinutes++; + ms_nLastClockTick += ms_nMillisecondsPerGameMinute; + + if ( gbFastTime ) + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); + + if(ms_nGameClockMinutes >= 60) + { + ms_nGameClockHours++; + ms_nGameClockMinutes = 0; + if(ms_nGameClockHours >= 24) + { + CStats::DaysPassed++; + ms_nGameClockHours = 0; + } + } + } + ms_nGameClockSeconds = 60 * (CTimer::GetTimeInMilliseconds() - ms_nLastClockTick) / ms_nMillisecondsPerGameMinute; +} + +void +CClock::SetGameClock(uint8 h, uint8 m) +{ + ms_nGameClockHours = h; + ms_nGameClockMinutes = m; + ms_nGameClockSeconds = 0; + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); +} + +int32 +CClock::GetGameClockMinutesUntil(uint8 h, uint8 m) +{ + int32 now, then; + now = ms_nGameClockHours*60 + ms_nGameClockMinutes; + then = h*60 + m; + if(then < now) + then += 24*60; + return then-now; +} + +bool +CClock::GetIsTimeInRange(uint8 h1, uint8 h2) +{ + if(h1 > h2) + return ms_nGameClockHours >= h1 || ms_nGameClockHours < h2; + else + return ms_nGameClockHours >= h1 && ms_nGameClockHours < h2; +} + +void +CClock::StoreClock(void) +{ + ms_Stored_nGameClockHours = ms_nGameClockHours; + ms_Stored_nGameClockMinutes = ms_nGameClockMinutes; + ms_Stored_nGameClockSeconds = ms_nGameClockSeconds; + ms_bClockHasBeenStored = true; +} + +void +CClock::RestoreClock(void) +{ + ms_nGameClockHours = ms_Stored_nGameClockHours; + ms_nGameClockMinutes = ms_Stored_nGameClockMinutes; + ms_nGameClockSeconds = ms_Stored_nGameClockSeconds; +} diff --git a/src/core/Clock.h b/src/core/Clock.h new file mode 100644 index 0000000..a611cd5 --- /dev/null +++ b/src/core/Clock.h @@ -0,0 +1,31 @@ +#pragma once + +class CClock +{ +public: + static uint8 ms_nGameClockHours; + static uint8 ms_nGameClockMinutes; + static uint16 ms_nGameClockSeconds; + static uint8 ms_Stored_nGameClockHours; + static uint8 ms_Stored_nGameClockMinutes; + static uint16 ms_Stored_nGameClockSeconds; + static uint32 ms_nMillisecondsPerGameMinute; + static uint32 ms_nLastClockTick; + static bool ms_bClockHasBeenStored; + + static void Initialise(uint32 scale); + static void Update(void); + static void SetGameClock(uint8 h, uint8 m); + static int32 GetGameClockMinutesUntil(uint8 h, uint8 m); + static bool GetIsTimeInRange(uint8 h1, uint8 h2); + static void StoreClock(void); + static void RestoreClock(void); + + static uint8 GetHours(void) { return ms_nGameClockHours; } + static uint8 GetMinutes(void) { return ms_nGameClockMinutes; } + static int16 GetSeconds(void) { return ms_nGameClockSeconds; } + + + static uint8 &GetHoursRef(void) { return ms_nGameClockHours; } + static uint8 &GetMinutesRef(void) { return ms_nGameClockMinutes; } +}; diff --git a/src/core/ControllerConfig.cpp b/src/core/ControllerConfig.cpp new file mode 100644 index 0000000..8775792 --- /dev/null +++ b/src/core/ControllerConfig.cpp @@ -0,0 +1,2884 @@ +#define WITHDINPUT +#include "common.h" +#include "platform.h" +#include "crossplatform.h" +#include "ControllerConfig.h" +#include "Pad.h" +#include "FileMgr.h" +#include "Text.h" +#include "Font.h" +#include "Messages.h" +#include "Frontend.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Vehicle.h" +#include "World.h" +#include "ModelIndices.h" +#include "Camera.h" +#include "GenericGameStorage.h" + +CControllerConfigManager ControlsManager; + +CControllerConfigManager::CControllerConfigManager() +{ + m_bFirstCapture = false; + m_bMouseAssociated = false; + + MakeControllerActionsBlank(); + InitDefaultControlConfiguration(); + InitialiseControllerActionNameArray(); +} + +void CControllerConfigManager::MakeControllerActionsBlank() +{ +#ifdef LOAD_INI_SETTINGS + ms_padButtonsInited = 0; +#endif + for (int32 i = 0; i < MAX_CONTROLLERTYPES; i++) + { + for (int32 j = 0; j < MAX_CONTROLLERACTIONS; j++) + { + ClearSettingsAssociatedWithAction((e_ControllerAction)j, (eControllerType)i); + } + } +} + +#ifdef RW_GL3 +int MapIdToButtonId(int mapId) { + switch (mapId) { + case GLFW_GAMEPAD_BUTTON_A: // Cross + return 2; + case GLFW_GAMEPAD_BUTTON_B: // Circle + return 1; + case GLFW_GAMEPAD_BUTTON_X: // Square + return 3; + case GLFW_GAMEPAD_BUTTON_Y: // Triangle + return 4; + case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: + return 7; + case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: + return 8; + case GLFW_GAMEPAD_BUTTON_BACK: + return 9; + case GLFW_GAMEPAD_BUTTON_START: + return 12; + case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: + return 10; + case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: + return 11; + case GLFW_GAMEPAD_BUTTON_DPAD_UP: + return 13; + case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: + return 14; + case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: + return 15; + case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: + return 16; + // GLFW sends those as axes, so I added them here manually. + case 15: // Left trigger + return 5; + case 16: // Right trigger + return 6; + default: + return 0; + } +} +#endif + +int32 CControllerConfigManager::GetJoyButtonJustDown() +{ +#ifdef __DINPUT_INCLUDED__ +#ifdef FIX_BUGS + for (int32 i = 0; i < MAX_BUTTONS; i++) +#else + for (int32 i = 0; i < JOY_BUTTONS; i++) +#endif + { + if (m_NewState.rgbButtons[i] & 0x80 && !(m_OldState.rgbButtons[i] & 0x80)) + return i + 1; + } +#elif defined RW_GL3 + if (m_NewState.isGamepad) { + for (int32 i = 0; i < MAX_BUTTONS; i++) { + if (m_NewState.mappedButtons[i] && !(m_OldState.mappedButtons[i])) + return MapIdToButtonId(i); + } + } else { + for (int32 i = 0; i < Min(m_NewState.numButtons, MAX_BUTTONS); i++) { + if (m_NewState.buttons[i] && !(m_OldState.buttons[i])) + return i + 1; + } + } +#endif + return 0; +} + +void CControllerConfigManager::SaveSettings(int32 file) +{ + if (file) + { + for (int32 i = 0; i < MAX_CONTROLLERTYPES; i++) + { + for (int32 j = 0; j < MAX_CONTROLLERACTIONS; j++) + { + CFileMgr::Write(file, (char *)&ControlsManager.m_aSettings[j][i], sizeof(tControllerConfigBind)); + } + } + } +} + +void CControllerConfigManager::LoadSettings(int32 file) +{ + bool bValid = true; + +#ifdef BIND_VEHICLE_FIREWEAPON + bool skipVehicleFireWeapon = false; +#endif + + if (file) + { + char buff[29]; + CFileMgr::Read(file, buff, sizeof(buff)); + + if (!strncmp(buff, TopLineEmptyFile, sizeof(TopLineEmptyFile)-1)) + bValid = false; + else { + CFileMgr::Seek(file, 0, 0); + +#ifdef BIND_VEHICLE_FIREWEAPON + // HACK! + // All of this is hacky as fuck. + // We are checking the file size to read the .set file correctly. + // But because .set file is opened in text mode we have to read + // the WHOLE file to get the size we should be working with. + // Joy, ain't it? + char tempBuf[0x1000]; + size_t fileSize = 0, blockSize; + do + { + blockSize = CFileMgr::Read(file, tempBuf, sizeof(tempBuf)); + fileSize += blockSize; + } while (blockSize == sizeof(tempBuf)); + + CFileMgr::Seek(file, 0, 0); + + if (fileSize == 0x671) + skipVehicleFireWeapon = true; +#endif + } + } + + if (bValid) + { + ControlsManager.MakeControllerActionsBlank(); + +#ifdef BIND_VEHICLE_FIREWEAPON + // Set the default settings of VEHICLE_FIREWEAPON + if (skipVehicleFireWeapon) { + SetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, rsPADINS, KEYBOARD); + SetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, rsLCTRL, OPTIONAL_EXTRA); + if (m_bMouseAssociated) + SetMouseButtonAssociatedWithAction(VEHICLE_FIREWEAPON, 1); + } +#endif + + for (int32 i = 0; i < MAX_CONTROLLERTYPES; i++) + { + for (int32 j = 0; j < MAX_CONTROLLERACTIONS; j++) + { +#ifdef BIND_VEHICLE_FIREWEAPON + // Skip file read + if (skipVehicleFireWeapon && j == VEHICLE_FIREWEAPON) + continue; +#endif + CFileMgr::Read(file, (char *)&ControlsManager.m_aSettings[j][i], sizeof(tControllerConfigBind)); + } + } + } +} + +void CControllerConfigManager::InitDefaultControlConfiguration() +{ + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKLEFT, rsPADEND, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKLEFT, 'Q', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKRIGHT, rsPADDOWN, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_LOOKRIGHT, 'E', OPTIONAL_EXTRA); + + if ( _dwOperatingSystemVersion == OS_WIN98 ) + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsSHIFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + else + { + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsLSHIFT, OPTIONAL_EXTRA); + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, rsRSHIFT, KEYBOARD); + } + + SetControllerKeyAssociatedWithAction (VEHICLE_HANDBRAKE, rsRCTRL, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_HANDBRAKE, ' ', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_ENTER_EXIT, rsENTER, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_ENTER_EXIT, 'F', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_ACCELERATE, rsUP, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_ACCELERATE, 'W', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_CHANGE_RADIO_STATION, rsINS, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_CHANGE_RADIO_STATION, 'R', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (VEHICLE_BRAKE, rsDOWN, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_BRAKE, 'S', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (TOGGLE_SUBMISSIONS, rsPLUS, KEYBOARD); + SetControllerKeyAssociatedWithAction (TOGGLE_SUBMISSIONS, rsCAPSLK, OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (GO_LEFT, rsLEFT, KEYBOARD); + SetControllerKeyAssociatedWithAction (GO_LEFT, 'A', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (GO_RIGHT, rsRIGHT, KEYBOARD); + SetControllerKeyAssociatedWithAction (GO_RIGHT, 'D', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (GO_FORWARD, rsUP, KEYBOARD); + SetControllerKeyAssociatedWithAction (GO_FORWARD, 'W', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (GO_BACK, rsDOWN, KEYBOARD); + SetControllerKeyAssociatedWithAction (GO_BACK, 'S', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (PED_LOOKBEHIND, rsPADEND, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_LOOKBEHIND, rsCAPSLK, OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (PED_FIREWEAPON, rsPADINS, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_FIREWEAPON, rsLCTRL, OPTIONAL_EXTRA); +#ifdef BIND_VEHICLE_FIREWEAPON + SetControllerKeyAssociatedWithAction (VEHICLE_FIREWEAPON, rsPADINS, KEYBOARD); + SetControllerKeyAssociatedWithAction (VEHICLE_FIREWEAPON, rsLCTRL, OPTIONAL_EXTRA); +#endif + SetControllerKeyAssociatedWithAction (PED_CYCLE_WEAPON_LEFT, rsPADDEL, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_CYCLE_WEAPON_RIGHT, rsPADENTER, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + + SetControllerKeyAssociatedWithAction (PED_LOCK_TARGET, rsDEL, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_JUMPING, rsRCTRL, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_JUMPING, ' ', OPTIONAL_EXTRA); + + if ( _dwOperatingSystemVersion == OS_WIN98 ) + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsSHIFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + else + { + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsLSHIFT, OPTIONAL_EXTRA); +#ifndef FIX_BUGS + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsRSHIFT, OPTIONAL_EXTRA); // BUG: must be KEYBOARD +#else + SetControllerKeyAssociatedWithAction(PED_SPRINT, rsRSHIFT, KEYBOARD); +#endif + } + + SetControllerKeyAssociatedWithAction (PED_CYCLE_TARGET_LEFT, '[', KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_CYCLE_TARGET_RIGHT, ']', OPTIONAL_EXTRA); // BUG: must be KEYBOARD ? + + SetControllerKeyAssociatedWithAction (PED_CENTER_CAMERA_BEHIND_PLAYER, '#', KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_IN, rsPGUP, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_IN, 'Z', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_OUT, rsPGDN, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_SNIPER_ZOOM_OUT, 'X', OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_LEFT, rsPADLEFT, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_RIGHT, rsPADRIGHT, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_UP, rsPADUP, KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_1RST_PERSON_LOOK_DOWN, rsPAD5, KEYBOARD); + + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETLEFT, rsPADLEFT, KEYBOARD); + + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETRIGHT, rsPAD5, KEYBOARD); + + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETUP, rsPADPGUP, KEYBOARD); + + SetControllerKeyAssociatedWithAction (VEHICLE_TURRETDOWN, rsPADRIGHT, KEYBOARD); + + SetControllerKeyAssociatedWithAction (CAMERA_CHANGE_VIEW_ALL_SITUATIONS, rsHOME, KEYBOARD); + SetControllerKeyAssociatedWithAction (CAMERA_CHANGE_VIEW_ALL_SITUATIONS, 'C', OPTIONAL_EXTRA); + + for (int32 i = 0; i < MAX_SIMS; i++) + { + m_aSimCheckers[i][KEYBOARD] = false; + m_aSimCheckers[i][OPTIONAL_EXTRA] = false; + m_aSimCheckers[i][MOUSE] = false; + m_aSimCheckers[i][JOYSTICK] = false; + } +} + +void CControllerConfigManager::InitDefaultControlConfigMouse(CMouseControllerState const &availableButtons) +{ + if (availableButtons.LMB) + { + m_bMouseAssociated = true; + SetMouseButtonAssociatedWithAction(PED_FIREWEAPON, 1); +#ifdef BIND_VEHICLE_FIREWEAPON + SetMouseButtonAssociatedWithAction(VEHICLE_FIREWEAPON, 1); +#endif + } + + if (availableButtons.RMB) + { + SetMouseButtonAssociatedWithAction(PED_LOCK_TARGET, 3); + + SetMouseButtonAssociatedWithAction(VEHICLE_HANDBRAKE, 3); + } + + if (availableButtons.MMB) + { + SetMouseButtonAssociatedWithAction(VEHICLE_LOOKBEHIND, 2); + + SetMouseButtonAssociatedWithAction(PED_LOOKBEHIND, 2); + } + + if (availableButtons.WHEELUP || availableButtons.WHEELDN) + { + SetMouseButtonAssociatedWithAction(PED_CYCLE_WEAPON_LEFT, 4); + + SetMouseButtonAssociatedWithAction(PED_CYCLE_WEAPON_RIGHT, 5); + + SetMouseButtonAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, 4); + } +} + +#ifdef LOAD_INI_SETTINGS +uint32 CControllerConfigManager::ms_padButtonsInited = 0; +#endif + +void CControllerConfigManager::InitDefaultControlConfigJoyPad(uint32 buttons) +{ +#ifdef XINPUT + // No manual bindings for you, honey. + return; +#endif + + m_bFirstCapture = true; + + uint32 btn = buttons; + if (buttons > 16) + btn = 16; + +#ifdef LOAD_INI_SETTINGS + uint32 buttonMin = ms_padButtonsInited; + if (buttonMin >= btn) + return; + + ms_padButtonsInited = btn; + + #define IF_BTN_IN_RANGE(n) \ + case n: \ + if (n <= buttonMin) \ + return; +#else + #define IF_BTN_IN_RANGE(n) \ + case n: +#endif + + // Now we use SDL Game Controller DB +#if defined RW_D3D9 || defined RWLIBS + if ( AllValidWinJoys.m_aJoys[JOYSTICK1].m_nVendorID == 0x3427 + && AllValidWinJoys.m_aJoys[JOYSTICK1].m_nProductID == 0x1190) +#else + if (0) +#endif + { + //GIC USB Joystick, PS2 Gamepad ? + + switch (btn) + { + IF_BTN_IN_RANGE(16) + SetControllerKeyAssociatedWithAction(GO_LEFT, 16, JOYSTICK); + IF_BTN_IN_RANGE(15) + SetControllerKeyAssociatedWithAction(GO_BACK, 15, JOYSTICK); + IF_BTN_IN_RANGE(14) + SetControllerKeyAssociatedWithAction(GO_RIGHT, 14, JOYSTICK); + IF_BTN_IN_RANGE(13) + SetControllerKeyAssociatedWithAction(GO_FORWARD, 13, JOYSTICK); + IF_BTN_IN_RANGE(12) + IF_BTN_IN_RANGE(11) + SetControllerKeyAssociatedWithAction(PED_LOOKBEHIND, 11, JOYSTICK); + SetControllerKeyAssociatedWithAction(TOGGLE_SUBMISSIONS, 11, JOYSTICK); + IF_BTN_IN_RANGE(10) + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, 10, JOYSTICK); + IF_BTN_IN_RANGE(9) + SetControllerKeyAssociatedWithAction(CAMERA_CHANGE_VIEW_ALL_SITUATIONS, 9, JOYSTICK); + IF_BTN_IN_RANGE(8) + SetControllerKeyAssociatedWithAction(VEHICLE_HANDBRAKE, 8, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_LOCK_TARGET, 8, JOYSTICK); + IF_BTN_IN_RANGE(7) + SetControllerKeyAssociatedWithAction(PED_CENTER_CAMERA_BEHIND_PLAYER, 7, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, 7, JOYSTICK); + IF_BTN_IN_RANGE(6) + SetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_RIGHT, 6, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_LOOKRIGHT, 6, JOYSTICK); + IF_BTN_IN_RANGE(5) + SetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_LEFT, 5, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_LOOKLEFT, 5, JOYSTICK); + /*******************************************************************************************/ + IF_BTN_IN_RANGE(4) + SetControllerKeyAssociatedWithAction(VEHICLE_BRAKE, 4, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_JUMPING, 4, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_IN, 4, JOYSTICK); + IF_BTN_IN_RANGE(3) + SetControllerKeyAssociatedWithAction(VEHICLE_ACCELERATE, 3, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SPRINT, 3, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_OUT, 3, JOYSTICK); + IF_BTN_IN_RANGE(2) + SetControllerKeyAssociatedWithAction(PED_FIREWEAPON, 2, JOYSTICK); +#ifdef BIND_VEHICLE_FIREWEAPON + SetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, 2, JOYSTICK); +#endif + IF_BTN_IN_RANGE(1) + SetControllerKeyAssociatedWithAction(VEHICLE_ENTER_EXIT, 1, JOYSTICK); + /*******************************************************************************************/ + } + } + else + { + switch (btn) + { + IF_BTN_IN_RANGE(16) + SetControllerKeyAssociatedWithAction(GO_LEFT, 16, JOYSTICK); + IF_BTN_IN_RANGE(15) + SetControllerKeyAssociatedWithAction(GO_BACK, 15, JOYSTICK); + IF_BTN_IN_RANGE(14) + SetControllerKeyAssociatedWithAction(GO_RIGHT, 14, JOYSTICK); + IF_BTN_IN_RANGE(13) + SetControllerKeyAssociatedWithAction(GO_FORWARD, 13, JOYSTICK); + IF_BTN_IN_RANGE(12) + IF_BTN_IN_RANGE(11) + SetControllerKeyAssociatedWithAction(PED_LOOKBEHIND, 11, JOYSTICK); + SetControllerKeyAssociatedWithAction(TOGGLE_SUBMISSIONS, 11, JOYSTICK); + IF_BTN_IN_RANGE(10) + SetControllerKeyAssociatedWithAction(VEHICLE_HORN, 10, JOYSTICK); + IF_BTN_IN_RANGE(9) + SetControllerKeyAssociatedWithAction(CAMERA_CHANGE_VIEW_ALL_SITUATIONS, 9, JOYSTICK); + IF_BTN_IN_RANGE(8) + SetControllerKeyAssociatedWithAction(VEHICLE_HANDBRAKE, 8, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_LOCK_TARGET, 8, JOYSTICK); + IF_BTN_IN_RANGE(7) + SetControllerKeyAssociatedWithAction(PED_CENTER_CAMERA_BEHIND_PLAYER, 7, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, 7, JOYSTICK); + IF_BTN_IN_RANGE(6) + SetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_RIGHT, 6, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_LOOKRIGHT, 6, JOYSTICK); + IF_BTN_IN_RANGE(5) + SetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_LEFT, 5, JOYSTICK); + SetControllerKeyAssociatedWithAction(VEHICLE_LOOKLEFT, 5, JOYSTICK); + /*******************************************************************************************/ + IF_BTN_IN_RANGE(4) + SetControllerKeyAssociatedWithAction(VEHICLE_ENTER_EXIT, 4, JOYSTICK); + IF_BTN_IN_RANGE(3) + SetControllerKeyAssociatedWithAction(VEHICLE_BRAKE, 3, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_JUMPING, 3, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_IN, 3, JOYSTICK); + IF_BTN_IN_RANGE(2) + SetControllerKeyAssociatedWithAction(VEHICLE_ACCELERATE, 2, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SPRINT, 2, JOYSTICK); + SetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_OUT, 2, JOYSTICK); + IF_BTN_IN_RANGE(1) + SetControllerKeyAssociatedWithAction(PED_FIREWEAPON, 1, JOYSTICK); +#ifdef BIND_VEHICLE_FIREWEAPON + SetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, 1, JOYSTICK); +#endif + /*******************************************************************************************/ + } + } +} + +void CControllerConfigManager::InitialiseControllerActionNameArray() +{ + wchar buf[ACTIONNAME_LENGTH + 2]; + +#define SETACTIONNAME(name) AsciiToUnicode(#name, buf); CMessages::WideStringCopy(m_aActionNames[name], buf, ACTIONNAME_LENGTH); + + SETACTIONNAME(PED_LOOKBEHIND); + SETACTIONNAME(PED_CYCLE_WEAPON_LEFT); + SETACTIONNAME(PED_CYCLE_WEAPON_RIGHT); + SETACTIONNAME(PED_LOCK_TARGET); + SETACTIONNAME(PED_JUMPING); + SETACTIONNAME(PED_SPRINT); + SETACTIONNAME(PED_CYCLE_TARGET_LEFT); + SETACTIONNAME(PED_CYCLE_TARGET_RIGHT); + SETACTIONNAME(PED_CENTER_CAMERA_BEHIND_PLAYER); + SETACTIONNAME(VEHICLE_LOOKBEHIND); + SETACTIONNAME(VEHICLE_LOOKLEFT); + SETACTIONNAME(VEHICLE_LOOKRIGHT); + SETACTIONNAME(VEHICLE_HORN); + SETACTIONNAME(VEHICLE_HANDBRAKE); + SETACTIONNAME(VEHICLE_ACCELERATE); + SETACTIONNAME(VEHICLE_BRAKE); + SETACTIONNAME(VEHICLE_CHANGE_RADIO_STATION); + SETACTIONNAME(TOGGLE_SUBMISSIONS); + SETACTIONNAME(PED_SNIPER_ZOOM_IN); + SETACTIONNAME(PED_SNIPER_ZOOM_OUT); + SETACTIONNAME(PED_1RST_PERSON_LOOK_LEFT); + SETACTIONNAME(PED_1RST_PERSON_LOOK_RIGHT); + SETACTIONNAME(PED_1RST_PERSON_LOOK_UP); + SETACTIONNAME(PED_1RST_PERSON_LOOK_DOWN); + SETACTIONNAME(SHOW_MOUSE_POINTER_TOGGLE); + SETACTIONNAME(CAMERA_CHANGE_VIEW_ALL_SITUATIONS); + SETACTIONNAME(PED_FIREWEAPON); +#ifdef BIND_VEHICLE_FIREWEAPON + SETACTIONNAME(VEHICLE_FIREWEAPON); +#endif + SETACTIONNAME(VEHICLE_ENTER_EXIT); + SETACTIONNAME(GO_LEFT); + SETACTIONNAME(GO_RIGHT); + SETACTIONNAME(GO_FORWARD); + SETACTIONNAME(GO_BACK); + SETACTIONNAME(NETWORK_TALK); + SETACTIONNAME(TOGGLE_DPAD); + SETACTIONNAME(SWITCH_DEBUG_CAM_ON); + SETACTIONNAME(TAKE_SCREEN_SHOT); + +#undef SETACTIONNAME +} + +void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonDown(int32 button, int32 padnumber) +{ + if (button != 0) + { + CPad *pad = CPad::GetPad(padnumber); + if (pad != NULL) + { + switch (button) + { + case 16: + pad->PCTempJoyState.DPadLeft = 255; + break; + case 15: + pad->PCTempJoyState.DPadDown = 255; + break; + case 14: + pad->PCTempJoyState.DPadRight = 255; + break; + case 13: + pad->PCTempJoyState.DPadUp = 255; + break; +#ifdef REGISTER_START_BUTTON + case 12: + pad->PCTempJoyState.Start = 255; + break; +#endif + case 11: + pad->PCTempJoyState.RightShock = 255; + break; + case 10: + pad->PCTempJoyState.LeftShock = 255; + break; + case 9: + pad->PCTempJoyState.Select = 255; + break; + case 8: + pad->PCTempJoyState.RightShoulder1 = 255; + break; + case 7: + pad->PCTempJoyState.LeftShoulder1 = 255; + break; + case 6: + pad->PCTempJoyState.RightShoulder2 = 255; + break; + case 5: + pad->PCTempJoyState.LeftShoulder2 = 255; + break; + } + + // Now we use SDL Game Controller DB +#if defined RW_D3D9 || defined RWLIBS + if (AllValidWinJoys.m_aJoys[JOYSTICK1].m_nVendorID == 0x3427 + && AllValidWinJoys.m_aJoys[JOYSTICK1].m_nProductID == 0x1190) +#else + if (0) +#endif + { + //GIC USB Joystick, PS2 Gamepad ? + + switch (button) + { + case 4: + pad->PCTempJoyState.Square = 255; + break; + case 3: + pad->PCTempJoyState.Cross = 255; + break; + case 2: + pad->PCTempJoyState.Circle = 255; + break; + case 1: + pad->PCTempJoyState.Triangle = 255; + break; + } + } + else + { + switch (button) + { + case 4: + pad->PCTempJoyState.Triangle = 255; + break; + case 3: + pad->PCTempJoyState.Square = 255; + break; + case 2: + pad->PCTempJoyState.Cross = 255; + break; + case 1: + pad->PCTempJoyState.Circle = 255; + break; + } + } + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown(int32 button, eControllerType type) +{ + bool process = true; + + if ((type == KEYBOARD || type == OPTIONAL_EXTRA) && button == rsNULL) + process = false; + if (type == JOYSTICK && button == 0) + process = false; + if (type == MOUSE && button == 0) + process = false; + + if (process) + { + CPad *pad = CPad::GetPad(PAD1); + + bool firstPerson = false; + bool playerDriving = false; + + if (FindPlayerVehicle() != NULL) + { + CPlayerPed *plr = FindPlayerPed(); + if (plr != NULL) + { + if (plr->m_nPedState == PED_DRIVING) + playerDriving = true; + } + } + + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if ( mode == CCam::MODE_1STPERSON + || mode == CCam::MODE_SNIPER + || mode == CCam::MODE_ROCKETLAUNCHER + || mode == CCam::MODE_M16_1STPERSON) + { + firstPerson = true; + } + + CControllerState *state; + + switch (type) + { + case KEYBOARD: + case OPTIONAL_EXTRA: + state = &CPad::GetPad(PAD1)->PCTempKeyState; + break; + case JOYSTICK: + state = &CPad::GetPad(PAD1)->PCTempJoyState; + break; + case MOUSE: + state = &CPad::GetPad(PAD1)->PCTempMouseState; + break; + default: break; + } + + if (pad != NULL) + { + if (playerDriving) + { + AffectControllerStateOn_ButtonDown_Driving(button, type, *state); + AffectControllerStateOn_ButtonDown_VehicleAndThirdPersonOnly(button, type, *state); + } + else + { + AffectControllerStateOn_ButtonDown_FirstAndThirdPersonOnly(button, type, *state); + if (firstPerson) + AffectControllerStateOn_ButtonDown_FirstPersonOnly(button, type, *state); + else + { + AffectControllerStateOn_ButtonDown_ThirdPersonOnly(button, type, *state); + AffectControllerStateOn_ButtonDown_VehicleAndThirdPersonOnly(button, type, *state); + } + } + + AffectControllerStateOn_ButtonDown_AllStates(button, type, *state); + +#ifdef REGISTER_START_BUTTON + if (button == 12) + state->Start = 255; +#endif + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_Driving(int32 button, eControllerType type, CControllerState &state) +{ +#ifdef BIND_VEHICLE_FIREWEAPON + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_FIREWEAPON, type)) + state.Circle = 255; +#endif + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_LOOKBEHIND, type)) + { + state.LeftShoulder2 = 255; + state.RightShoulder2 = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_LOOKLEFT, type)) + state.LeftShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_LOOKRIGHT, type)) + state.RightShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_HORN, type)) + state.LeftShock = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_HANDBRAKE, type)) + state.RightShoulder1 = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_ACCELERATE, type)) + state.Cross = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, type)) + state.LeftShoulder1 = 255; + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_BRAKE, type)) + state.Square = 255; + if (button == GetControllerKeyAssociatedWithAction(TOGGLE_SUBMISSIONS, type)) + state.RightShock = 255; + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_TURRETLEFT, type)) + { + if (state.RightStickX == 128 || m_aSimCheckers[SIM_X2][type]) + { + state.RightStickX = 0; + m_aSimCheckers[SIM_X2][type] = true; + } + else + { + state.RightStickX = -128; + } + } + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_TURRETRIGHT, type)) + { + if (state.RightStickX == -128 || m_aSimCheckers[SIM_X2][type]) + { + state.RightStickX = 0; + m_aSimCheckers[SIM_X2][type] = true; + } + else + state.RightStickX = 128; + } + + bool isDodo = false; + if (FindPlayerVehicle() && (FindPlayerVehicle()->IsVehicle() && ( + FindPlayerVehicle()->GetModelIndex() == MI_DODO +#ifdef FIX_BUGS + || CVehicle::bAllDodosCheat +#ifdef ALLCARSHELI_CHEAT + || bAllCarCheat +#endif +#endif + ))) + { + isDodo = true; + } + + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_TURRETUP, type)) + { + if (isDodo == true) + { + if (state.LeftStickY == -128 || m_aSimCheckers[SIM_Y1][type]) // BUG: should be SIM_Y2. SIM_Y1 it's DPAD + { + state.LeftStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.LeftStickY = 128; + } + + else if (state.RightStickY == -128 || m_aSimCheckers[SIM_Y2][type]) + { + state.RightStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + { + state.RightStickY = 128; + } + } + + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_TURRETDOWN, type)) + { + if (isDodo == true) + { + if (state.LeftStickY == 128 || m_aSimCheckers[SIM_Y1][type]) // BUG: should be SIM_Y2. SIM_Y1 it's DPAD + { + state.LeftStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.LeftStickY = -128; + } + + else if (state.RightStickY == 128 || m_aSimCheckers[SIM_Y2][type]) + { + state.RightStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.RightStickY = -128; + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_FirstPersonOnly(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_IN, type)) + state.Square = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_SNIPER_ZOOM_OUT, type)) + state.Cross = 255; +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_ThirdPersonOnly(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(PED_LOOKBEHIND, type)) + state.RightShock = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_JUMPING, type)) + state.Square = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_LEFT, type)) + state.LeftShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_CYCLE_WEAPON_RIGHT, type)) + state.RightShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_SPRINT, type)) + state.Cross = 255; + + if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + { + if (button == GetControllerKeyAssociatedWithAction(PED_CYCLE_TARGET_LEFT, type)) + state.LeftShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_CYCLE_TARGET_RIGHT, type)) + state.RightShoulder2 = 255; + if (button == GetControllerKeyAssociatedWithAction(PED_CENTER_CAMERA_BEHIND_PLAYER, type)) + state.LeftShoulder1 = 255; + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_FirstAndThirdPersonOnly(int32 button, eControllerType type, CControllerState &state) +{ + CPad *pad = CPad::GetPad(PAD1); + +#ifdef BIND_VEHICLE_FIREWEAPON + if (button == GetControllerKeyAssociatedWithAction(PED_FIREWEAPON, type)) + state.Circle = 255; +#endif + if (button == GetControllerKeyAssociatedWithAction(PED_LOCK_TARGET, type)) + state.RightShoulder1 = 255; + + if (button == GetControllerKeyAssociatedWithAction(GO_FORWARD, type)) + { + if (state.DPadDown || m_aSimCheckers[SIM_Y1][type]) + { + m_aSimCheckers[SIM_Y1][type] = true; + state.DPadDown = 0; + state.DPadUp = 0; + } + else + state.DPadUp = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(GO_BACK, type)) + { + if (state.DPadUp || m_aSimCheckers[SIM_Y1][type]) + { + m_aSimCheckers[SIM_Y1][type] = true; + state.DPadDown = 0; + state.DPadUp = 0; + } + else + state.DPadDown = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(PED_1RST_PERSON_LOOK_LEFT, type)) + { + if (state.RightStickX == 128 || m_aSimCheckers[SIM_X2][type]) + { + state.RightStickX = 0; + m_aSimCheckers[SIM_X2][type] = true; + } + else + { + state.RightStickX = -128; + } + } + + if (button == GetControllerKeyAssociatedWithAction(PED_1RST_PERSON_LOOK_RIGHT, type)) + { + if (state.RightStickX == -128 || m_aSimCheckers[SIM_X2][type]) + { + state.RightStickX = 0; + m_aSimCheckers[SIM_X2][type] = true; + } + else + state.RightStickX = 128; + } + + if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + { + if (button == GetControllerKeyAssociatedWithAction(PED_1RST_PERSON_LOOK_UP, type)) + { + if (state.RightStickY == -128 || m_aSimCheckers[SIM_Y2][type]) + { + state.RightStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.RightStickY = 128; + } + + if (button == GetControllerKeyAssociatedWithAction(PED_1RST_PERSON_LOOK_DOWN, type)) + { + if (state.RightStickY == 128 || m_aSimCheckers[SIM_Y2][type]) + { + state.RightStickY = 0; + m_aSimCheckers[SIM_Y2][type] = true; + } + else + state.RightStickY = -128; + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_AllStates(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(CAMERA_CHANGE_VIEW_ALL_SITUATIONS, type)) + state.Select = 255; + +#ifndef BIND_VEHICLE_FIREWEAPON + if (button == GetControllerKeyAssociatedWithAction(PED_FIREWEAPON, type)) + state.Circle = 255; +#endif + + if (button == GetControllerKeyAssociatedWithAction(GO_LEFT, type)) + { + if (state.DPadRight || m_aSimCheckers[SIM_X1][type]) + { + m_aSimCheckers[SIM_X1][type] = true; + state.DPadLeft = 0; + state.DPadRight = 0; + } + else + state.DPadLeft = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(GO_RIGHT, type)) + { + if (state.DPadLeft || m_aSimCheckers[SIM_X1][type]) + { + m_aSimCheckers[SIM_X1][type] = true; + state.DPadLeft = 0; + state.DPadRight = 0; + } + else + state.DPadRight = 255; + } + + if (button == GetControllerKeyAssociatedWithAction(NETWORK_TALK, type)) + state.NetworkTalk = 255; +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonDown_VehicleAndThirdPersonOnly(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(VEHICLE_ENTER_EXIT, type)) + state.Triangle = 255; +} + +void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonUp(int32 button, int32 padnumber) +{ + if (button!=0) + { + CPad *pad = CPad::GetPad(padnumber); + + if (pad != NULL) + { + switch (button) + { + case 16: + pad->PCTempJoyState.DPadLeft = 0; + break; + case 15: + pad->PCTempJoyState.DPadDown = 0; + break; + case 14: + pad->PCTempJoyState.DPadRight = 0; + break; + case 13: + pad->PCTempJoyState.DPadUp = 0; + break; +#ifdef REGISTER_START_BUTTON + case 12: + pad->PCTempJoyState.Start = 0; + break; +#endif + case 11: + pad->PCTempJoyState.RightShock = 0; + break; + case 10: + pad->PCTempJoyState.LeftShock = 0; + break; + case 9: + pad->PCTempJoyState.Select = 0; + break; + case 8: + pad->PCTempJoyState.RightShoulder1 = 0; + break; + case 7: + pad->PCTempJoyState.LeftShoulder1 = 0; + break; + case 6: + pad->PCTempJoyState.RightShoulder2 = 0; + break; + case 5: + pad->PCTempJoyState.LeftShoulder2 = 0; + break; + } + + // Now we use SDL Game Controller DB +#if defined RW_D3D9 || defined RWLIBS + if (AllValidWinJoys.m_aJoys[JOYSTICK1].m_nVendorID == 0x3427 + && AllValidWinJoys.m_aJoys[JOYSTICK1].m_nProductID == 0x1190) +#else + if (0) +#endif + { + //GIC USB Joystick, PS2 Gamepad ? + + switch (button) + { + case 4: + pad->PCTempJoyState.Square = 0; + break; + case 3: + pad->PCTempJoyState.Cross = 0; + break; + case 2: + pad->PCTempJoyState.Circle = 0; + break; + case 1: + pad->PCTempJoyState.Triangle = 0; + break; + } + } + else + { + switch (button) + { + case 4: + pad->PCTempJoyState.Triangle = 0; + break; + case 3: + pad->PCTempJoyState.Square = 0; + break; + case 2: + pad->PCTempJoyState.Cross = 0; + break; + case 1: + pad->PCTempJoyState.Circle = 0; + break; + } + } + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonUp(int32 button, eControllerType type) +{ + bool process = true; + + if ((type == KEYBOARD || type == OPTIONAL_EXTRA) && button == rsNULL) + process = false; + if (type == JOYSTICK && button == 0) + process = false; + if (type == MOUSE && button == 0) + process = false; + + CControllerState *state; + + switch (type) + { + case KEYBOARD: + case OPTIONAL_EXTRA: + state = &CPad::GetPad(PAD1)->PCTempKeyState; + break; + case MOUSE: + state = &CPad::GetPad(PAD1)->PCTempMouseState; + break; + case JOYSTICK: + state = &CPad::GetPad(PAD1)->PCTempJoyState; + break; + default: break; + } + + if (process) + { + CPad *pad = CPad::GetPad(PAD1); + + if (pad != NULL) + { + if (FrontEndMenuManager.GetIsMenuActive()) + AffectControllerStateOn_ButtonUp_All_Player_States(button, type, *state); + +#ifdef REGISTER_START_BUTTON + if (button == 12) + state->Start = 0; +#endif + } + } +} + +void CControllerConfigManager::AffectControllerStateOn_ButtonUp_All_Player_States(int32 button, eControllerType type, CControllerState &state) +{ + if (button == GetControllerKeyAssociatedWithAction(NETWORK_TALK, type)) + state.NetworkTalk = 0; +} + +void CControllerConfigManager::AffectPadFromKeyBoard() +{ + RsKeyCodes kc; + _InputTranslateShiftKeyUpDown(&kc); + + bool processdown = false; + if (!CPad::m_bMapPadOneToPadTwo && !FrontEndMenuManager.GetIsMenuActive()) + processdown = true; + + for (int32 i = 0; i < MAX_CONTROLLERACTIONS; i++) + { + int32 key = GetControllerKeyAssociatedWithAction((e_ControllerAction)i, KEYBOARD); + if (GetIsKeyboardKeyDown((RsKeyCodes)key) && processdown) + AffectControllerStateOn_ButtonDown(key, KEYBOARD); + + int32 extrakey = GetControllerKeyAssociatedWithAction((e_ControllerAction)i, OPTIONAL_EXTRA); + if (GetIsKeyboardKeyDown((RsKeyCodes)extrakey) && processdown) + AffectControllerStateOn_ButtonDown(extrakey, OPTIONAL_EXTRA); + + if (!GetIsKeyboardKeyDown((RsKeyCodes)key)) + AffectControllerStateOn_ButtonUp(key, KEYBOARD); + else if ( !GetIsKeyboardKeyDown((RsKeyCodes)extrakey)) + AffectControllerStateOn_ButtonUp(key, OPTIONAL_EXTRA); + } +} + +void CControllerConfigManager::AffectPadFromMouse() +{ + bool processdown = false; + if (!CPad::m_bMapPadOneToPadTwo && !FrontEndMenuManager.GetIsMenuActive()) + processdown = true; + + for (int32 i = 0; i < MAX_CONTROLLERACTIONS; i++) + { + int32 button = GetControllerKeyAssociatedWithAction((e_ControllerAction)i, MOUSE); + if (GetIsMouseButtonDown((RsKeyCodes)button) && processdown) + AffectControllerStateOn_ButtonDown(button, MOUSE); + if (GetIsMouseButtonUp((RsKeyCodes)button)) + AffectControllerStateOn_ButtonUp(button, MOUSE); + } +} + +void CControllerConfigManager::ClearSimButtonPressCheckers() +{ + for (int32 i = 0; i < MAX_SIMS; i++) + { + m_aSimCheckers[i][KEYBOARD] = false; + m_aSimCheckers[i][OPTIONAL_EXTRA] = false; + m_aSimCheckers[i][MOUSE] = false; + m_aSimCheckers[i][JOYSTICK] = false; + } +} + +bool CControllerConfigManager::GetIsKeyboardKeyDown(RsKeyCodes keycode) +{ + if (keycode < 255) + { + if (CPad::GetPad(PAD1)->GetChar(keycode)) + return true; + } + + for (int32 i = 0; i < 12; i++) + { + if (i + rsF1 == keycode) + { + if (CPad::GetPad(PAD1)->GetF(i)) + return true; + } + } + + switch (keycode) + { + case rsESC: + if (CPad::GetPad(PAD1)->GetEscape()) + return true; + break; + case rsINS: + if (CPad::GetPad(PAD1)->GetInsert()) + return true; + break; + case rsDEL: + if (CPad::GetPad(PAD1)->GetDelete()) + return true; + break; + case rsHOME: + if (CPad::GetPad(PAD1)->GetHome()) + return true; + break; + case rsEND: + if (CPad::GetPad(PAD1)->GetEnd()) + return true; + break; + case rsPGUP: + if (CPad::GetPad(PAD1)->GetPageUp()) + return true; + break; + case rsPGDN: + if (CPad::GetPad(PAD1)->GetPageDown()) + return true; + break; + case rsUP: + if (CPad::GetPad(PAD1)->GetUp()) + return true; + break; + case rsDOWN: + if (CPad::GetPad(PAD1)->GetDown()) + return true; + break; + case rsLEFT: + if (CPad::GetPad(PAD1)->GetLeft()) + return true; + break; + case rsRIGHT: + if (CPad::GetPad(PAD1)->GetRight()) + return true; + break; + case rsSCROLL: + if (CPad::GetPad(PAD1)->GetScrollLock()) + return true; + break; + case rsPAUSE: + if (CPad::GetPad(PAD1)->GetPause()) + return true; + break; + case rsNUMLOCK: + if (CPad::GetPad(PAD1)->GetNumLock()) + return true; + break; + case rsDIVIDE: + if (CPad::GetPad(PAD1)->GetDivide()) + return true; + break; + case rsTIMES: + if (CPad::GetPad(PAD1)->GetTimes()) + return true; + break; + case rsMINUS: + if (CPad::GetPad(PAD1)->GetMinus()) + return true; + break; + case rsPLUS: + if (CPad::GetPad(PAD1)->GetPlus()) + return true; + break; + case rsPADENTER: + if (CPad::GetPad(PAD1)->GetPadEnter()) + return true; + break; + case rsPADDEL: + if (CPad::GetPad(PAD1)->GetPadDel()) + return true; + break; + case rsPADEND: + if (CPad::GetPad(PAD1)->GetPad1()) + return true; + break; + case rsPADDOWN: + if (CPad::GetPad(PAD1)->GetPad2()) + return true; + break; + case rsPADPGDN: + if (CPad::GetPad(PAD1)->GetPad3()) + return true; + break; + case rsPADLEFT: + if (CPad::GetPad(PAD1)->GetPad4()) + return true; + break; + case rsPAD5: + if (CPad::GetPad(PAD1)->GetPad5()) + return true; + break; + case rsPADRIGHT: + if (CPad::GetPad(PAD1)->GetPad6()) + return true; + break; + case rsPADHOME: + if (CPad::GetPad(PAD1)->GetPad7()) + return true; + break; + case rsPADUP: + if (CPad::GetPad(PAD1)->GetPad8()) + return true; + break; + case rsPADPGUP: + if (CPad::GetPad(PAD1)->GetPad9()) + return true; + break; + case rsPADINS: + if (CPad::GetPad(PAD1)->GetPad0()) + return true; + break; + case rsBACKSP: + if (CPad::GetPad(PAD1)->GetBackspace()) + return true; + break; + case rsTAB: + if (CPad::GetPad(PAD1)->GetTab()) + return true; + break; + case rsCAPSLK: + if (CPad::GetPad(PAD1)->GetCapsLock()) + return true; + break; + case rsENTER: + if (CPad::GetPad(PAD1)->GetEnter()) + return true; + break; + case rsLSHIFT: + if (CPad::GetPad(PAD1)->GetLeftShift()) + return true; + break; + case rsSHIFT: + if (CPad::GetPad(PAD1)->GetShift()) + return true; + break; + case rsRSHIFT: + if (CPad::GetPad(PAD1)->GetRightShift()) + return true; + break; + case rsLCTRL: + if (CPad::GetPad(PAD1)->GetLeftCtrl()) + return true; + break; + case rsRCTRL: + if (CPad::GetPad(PAD1)->GetRightCtrl()) + return true; + break; + case rsLALT: + if (CPad::GetPad(PAD1)->GetLeftAlt()) + return true; + break; + case rsRALT: + if (CPad::GetPad(PAD1)->GetRightAlt()) + return true; + break; + case rsLWIN: + if (CPad::GetPad(PAD1)->GetLeftWin()) + return true; + break; + case rsRWIN: + if (CPad::GetPad(PAD1)->GetRightWin()) + return true; + break; + case rsAPPS: + if (CPad::GetPad(PAD1)->GetApps()) + return true; + break; + default: break; + } + + return false; +} + +bool CControllerConfigManager::GetIsKeyboardKeyJustDown(RsKeyCodes keycode) +{ + if (keycode < 255) + { + if (CPad::GetPad(PAD1)->GetCharJustDown(keycode)) + return true; + } + + for (int32 i = 0; i < 12; i++) + { + if (i + rsF1 == keycode) + { + if (CPad::GetPad(PAD1)->GetFJustDown(i)) + return true; + } + } + + switch (keycode) + { + case rsESC: + if (CPad::GetPad(PAD1)->GetEscapeJustDown()) + return true; + break; + case rsINS: + if (CPad::GetPad(PAD1)->GetInsertJustDown()) + return true; + break; + case rsDEL: + if (CPad::GetPad(PAD1)->GetDeleteJustDown()) + return true; + break; + case rsHOME: + if (CPad::GetPad(PAD1)->GetHomeJustDown()) + return true; + break; + case rsEND: + if (CPad::GetPad(PAD1)->GetEndJustDown()) + return true; + break; + case rsPGUP: + if (CPad::GetPad(PAD1)->GetPageUpJustDown()) + return true; + break; + case rsPGDN: + if (CPad::GetPad(PAD1)->GetPageDownJustDown()) + return true; + break; + case rsUP: + if (CPad::GetPad(PAD1)->GetUpJustDown()) + return true; + break; + case rsDOWN: + if (CPad::GetPad(PAD1)->GetDownJustDown()) + return true; + break; + case rsLEFT: + if (CPad::GetPad(PAD1)->GetLeftJustDown()) + return true; + break; + case rsRIGHT: + if (CPad::GetPad(PAD1)->GetRightJustDown()) + return true; + break; + case rsSCROLL: + if (CPad::GetPad(PAD1)->GetScrollLockJustDown()) + return true; + break; + case rsPAUSE: + if (CPad::GetPad(PAD1)->GetPauseJustDown()) + return true; + break; + case rsNUMLOCK: + if (CPad::GetPad(PAD1)->GetNumLockJustDown()) + return true; + break; + case rsDIVIDE: + if (CPad::GetPad(PAD1)->GetDivideJustDown()) + return true; + break; + case rsTIMES: + if (CPad::GetPad(PAD1)->GetTimesJustDown()) + return true; + break; + case rsMINUS: + if (CPad::GetPad(PAD1)->GetMinusJustDown()) + return true; + break; + case rsPLUS: + if (CPad::GetPad(PAD1)->GetPlusJustDown()) + return true; + break; + case rsPADENTER: + if (CPad::GetPad(PAD1)->GetPadEnterJustDown()) + return true; + break; + case rsPADDEL: + if (CPad::GetPad(PAD1)->GetPadDelJustDown()) + return true; + break; + case rsPADEND: + if (CPad::GetPad(PAD1)->GetPad1JustDown()) + return true; + break; + case rsPADDOWN: + if (CPad::GetPad(PAD1)->GetPad2JustDown()) + return true; + break; + case rsPADPGDN: + if (CPad::GetPad(PAD1)->GetPad3JustDown()) + return true; + break; + case rsPADLEFT: + if (CPad::GetPad(PAD1)->GetPad4JustDown()) + return true; + break; + case rsPAD5: + if (CPad::GetPad(PAD1)->GetPad5JustDown()) + return true; + break; + case rsPADRIGHT: + if (CPad::GetPad(PAD1)->GetPad6JustDown()) + return true; + break; + case rsPADHOME: + if (CPad::GetPad(PAD1)->GetPad7JustDown()) + return true; + break; + case rsPADUP: + if (CPad::GetPad(PAD1)->GetPad8JustDown()) + return true; + break; + case rsPADPGUP: + if (CPad::GetPad(PAD1)->GetPad9JustDown()) + return true; + break; + case rsPADINS: + if (CPad::GetPad(PAD1)->GetPad0JustDown()) + return true; + break; + case rsBACKSP: + if (CPad::GetPad(PAD1)->GetBackspaceJustDown()) + return true; + break; + case rsTAB: + if (CPad::GetPad(PAD1)->GetTabJustDown()) + return true; + break; + case rsCAPSLK: + if (CPad::GetPad(PAD1)->GetCapsLockJustDown()) + return true; + break; + case rsENTER: + if (CPad::GetPad(PAD1)->GetReturnJustDown()) + return true; + break; + case rsLSHIFT: + if (CPad::GetPad(PAD1)->GetLeftShiftJustDown()) + return true; + break; + case rsSHIFT: + if (CPad::GetPad(PAD1)->GetShiftJustDown()) + return true; + break; + case rsRSHIFT: + if (CPad::GetPad(PAD1)->GetRightShiftJustDown()) + return true; + break; + case rsLCTRL: + if (CPad::GetPad(PAD1)->GetLeftCtrlJustDown()) + return true; + break; + case rsRCTRL: + if (CPad::GetPad(PAD1)->GetRightCtrlJustDown()) + return true; + break; + case rsLALT: + if (CPad::GetPad(PAD1)->GetLeftAltJustDown()) + return true; + break; + case rsRALT: + if (CPad::GetPad(PAD1)->GetRightAltJustDown()) + return true; + break; + case rsLWIN: + if (CPad::GetPad(PAD1)->GetLeftWinJustDown()) + return true; + break; + case rsRWIN: + if (CPad::GetPad(PAD1)->GetRightWinJustDown()) + return true; + break; + case rsAPPS: + if (CPad::GetPad(PAD1)->GetAppsJustDown()) + return true; + break; + default: break; + } + + return false; +} + +bool CControllerConfigManager::GetIsMouseButtonDown(RsKeyCodes keycode) +{ + switch (keycode) + { + case rsMOUSELEFTBUTTON: + if (CPad::GetPad(PAD1)->GetLeftMouse()) + return true; + break; + case rsMOUSMIDDLEBUTTON: + if (CPad::GetPad(PAD1)->GetMiddleMouse()) + return true; + break; + case rsMOUSERIGHTBUTTON: + if (CPad::GetPad(PAD1)->GetRightMouse()) + return true; + break; + case rsMOUSEWHEELUPBUTTON: + if (CPad::GetPad(PAD1)->GetMouseWheelUp()) + return true; + break; + case rsMOUSEWHEELDOWNBUTTON: + if (CPad::GetPad(PAD1)->GetMouseWheelDown()) + return true; + break; + case rsMOUSEX1BUTTON: + if (CPad::GetPad(PAD1)->GetMouseX1()) + return true; + break; + case rsMOUSEX2BUTTON: + if (CPad::GetPad(PAD1)->GetMouseX2()) + return true; + break; + default: break; + } + + return false; +} + +bool CControllerConfigManager::GetIsMouseButtonUp(RsKeyCodes keycode) +{ + switch (keycode) + { + case rsMOUSELEFTBUTTON: + if (CPad::GetPad(PAD1)->GetLeftMouseUp()) + return true; + break; + case rsMOUSMIDDLEBUTTON: + if (CPad::GetPad(PAD1)->GetMiddleMouseUp()) + return true; + break; + case rsMOUSERIGHTBUTTON: + if (CPad::GetPad(PAD1)->GetRightMouseUp()) + return true; + break; + case rsMOUSEWHEELUPBUTTON: + if (CPad::GetPad(PAD1)->GetMouseWheelUpUp()) + return true; + break; + case rsMOUSEWHEELDOWNBUTTON: + if (CPad::GetPad(PAD1)->GetMouseWheelDownUp()) + return true; + break; + case rsMOUSEX1BUTTON: + if (CPad::GetPad(PAD1)->GetMouseX1Up()) + return true; + break; + case rsMOUSEX2BUTTON: + if (CPad::GetPad(PAD1)->GetMouseX2Up()) + return true; + break; + default: break; + } + + return false; +} + +#define CLEAR_ACTION_IF_NEEDED(action) \ +if (key == GetControllerKeyAssociatedWithAction(action, type))\ + ClearSettingsAssociatedWithAction(action, type); + +void CControllerConfigManager::DeleteMatchingCommonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + CLEAR_ACTION_IF_NEEDED(CAMERA_CHANGE_VIEW_ALL_SITUATIONS); +#ifndef BIND_VEHICLE_FIREWEAPON + CLEAR_ACTION_IF_NEEDED(PED_FIREWEAPON); +#endif + CLEAR_ACTION_IF_NEEDED(GO_LEFT); + CLEAR_ACTION_IF_NEEDED(GO_RIGHT); + CLEAR_ACTION_IF_NEEDED(NETWORK_TALK); + CLEAR_ACTION_IF_NEEDED(SWITCH_DEBUG_CAM_ON); + CLEAR_ACTION_IF_NEEDED(TOGGLE_DPAD); + CLEAR_ACTION_IF_NEEDED(TAKE_SCREEN_SHOT); + CLEAR_ACTION_IF_NEEDED(SHOW_MOUSE_POINTER_TOGGLE); + } +} + +void CControllerConfigManager::DeleteMatching3rdPersonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + CLEAR_ACTION_IF_NEEDED(PED_LOOKBEHIND); + CLEAR_ACTION_IF_NEEDED(PED_CYCLE_WEAPON_LEFT); + CLEAR_ACTION_IF_NEEDED(PED_CYCLE_WEAPON_RIGHT); + CLEAR_ACTION_IF_NEEDED(PED_JUMPING); + CLEAR_ACTION_IF_NEEDED(PED_SPRINT); + + if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + { + CLEAR_ACTION_IF_NEEDED(PED_CYCLE_TARGET_LEFT); + CLEAR_ACTION_IF_NEEDED(PED_CYCLE_TARGET_RIGHT); + CLEAR_ACTION_IF_NEEDED(PED_CENTER_CAMERA_BEHIND_PLAYER); + } + } +} + +void CControllerConfigManager::DeleteMatching1rst3rdPersonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { +#ifdef BIND_VEHICLE_FIREWEAPON + CLEAR_ACTION_IF_NEEDED(PED_FIREWEAPON); +#endif + CLEAR_ACTION_IF_NEEDED(PED_LOCK_TARGET); + CLEAR_ACTION_IF_NEEDED(GO_FORWARD); + CLEAR_ACTION_IF_NEEDED(GO_BACK); + + if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) + { + CLEAR_ACTION_IF_NEEDED(PED_1RST_PERSON_LOOK_LEFT); + CLEAR_ACTION_IF_NEEDED(PED_1RST_PERSON_LOOK_RIGHT); + CLEAR_ACTION_IF_NEEDED(PED_1RST_PERSON_LOOK_DOWN); + CLEAR_ACTION_IF_NEEDED(PED_1RST_PERSON_LOOK_UP); + } + } +} + +void CControllerConfigManager::DeleteMatchingVehicleControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { +#ifdef BIND_VEHICLE_FIREWEAPON + CLEAR_ACTION_IF_NEEDED(VEHICLE_FIREWEAPON); +#endif + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKBEHIND); + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKLEFT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKRIGHT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKBEHIND); // note: duplicate + CLEAR_ACTION_IF_NEEDED(VEHICLE_HORN); + CLEAR_ACTION_IF_NEEDED(VEHICLE_HANDBRAKE); + CLEAR_ACTION_IF_NEEDED(VEHICLE_ACCELERATE); + CLEAR_ACTION_IF_NEEDED(VEHICLE_BRAKE); + CLEAR_ACTION_IF_NEEDED(VEHICLE_CHANGE_RADIO_STATION); + CLEAR_ACTION_IF_NEEDED(TOGGLE_SUBMISSIONS); + CLEAR_ACTION_IF_NEEDED(VEHICLE_TURRETLEFT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_TURRETRIGHT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_TURRETUP); + CLEAR_ACTION_IF_NEEDED(VEHICLE_TURRETDOWN); + } +} + +void CControllerConfigManager::DeleteMatchingVehicle_3rdPersonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + CLEAR_ACTION_IF_NEEDED(VEHICLE_ENTER_EXIT); + } +} + +void CControllerConfigManager::DeleteMatching1rstPersonControls(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + CLEAR_ACTION_IF_NEEDED(PED_SNIPER_ZOOM_IN); + CLEAR_ACTION_IF_NEEDED(PED_SNIPER_ZOOM_OUT); + } +} + +#undef CLEAR_ACTION_IF_NEEDED + +#ifdef RADIO_SCROLL_TO_PREV_STATION +#define CHECK_ACTION(action) \ +if (key == GetControllerKeyAssociatedWithAction(action, type))\ + return true; + +bool CControllerConfigManager::IsAnyVehicleActionAssignedToMouseKey(int32 key) +{ + const eControllerType type = MOUSE; + if (!GetIsKeyBlank(key, type)) + { +#ifdef BIND_VEHICLE_FIREWEAPON + CHECK_ACTION(VEHICLE_FIREWEAPON); +#endif + CHECK_ACTION(VEHICLE_LOOKBEHIND); + CHECK_ACTION(VEHICLE_LOOKLEFT); + CHECK_ACTION(VEHICLE_LOOKRIGHT); + CHECK_ACTION(VEHICLE_LOOKBEHIND); // note: duplicate + CHECK_ACTION(VEHICLE_HORN); + CHECK_ACTION(VEHICLE_HANDBRAKE); + CHECK_ACTION(VEHICLE_ACCELERATE); + CHECK_ACTION(VEHICLE_BRAKE); + CHECK_ACTION(VEHICLE_CHANGE_RADIO_STATION); + CHECK_ACTION(TOGGLE_SUBMISSIONS); + CHECK_ACTION(VEHICLE_TURRETLEFT); + CHECK_ACTION(VEHICLE_TURRETRIGHT); + CHECK_ACTION(VEHICLE_TURRETUP); + CHECK_ACTION(VEHICLE_TURRETDOWN); + CHECK_ACTION(VEHICLE_ENTER_EXIT); + CHECK_ACTION(CAMERA_CHANGE_VIEW_ALL_SITUATIONS); +#ifndef BIND_VEHICLE_FIREWEAPON + CHECK_ACTION(PED_FIREWEAPON); +#endif + CHECK_ACTION(GO_LEFT); + CHECK_ACTION(GO_RIGHT); + CHECK_ACTION(NETWORK_TALK); + CHECK_ACTION(SWITCH_DEBUG_CAM_ON); + CHECK_ACTION(TOGGLE_DPAD); + CHECK_ACTION(TAKE_SCREEN_SHOT); + CHECK_ACTION(SHOW_MOUSE_POINTER_TOGGLE); + } + return false; +} + +#undef CHECK_ACTION +#endif + +void CControllerConfigManager::DeleteMatchingActionInitiators(e_ControllerAction action, int32 key, eControllerType type) +{ + if (!GetIsKeyBlank(key, type)) + { + switch (GetActionType(action)) + { + case ACTIONTYPE_1RSTPERSON: + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rstPersonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + case ACTIONTYPE_3RDPERSON: + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + case ACTIONTYPE_VEHICLE: + DeleteMatchingVehicleControls (action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + break; + case ACTIONTYPE_VEHICLE_3RDPERSON: + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingVehicleControls (action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + case ACTIONTYPE_1RST3RDPERSON: + DeleteMatching1rstPersonControls (action, key, type); + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + case ACTIONTYPE_COMMON: + DeleteMatching1rstPersonControls (action, key, type); + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingVehicleControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + break; + default: break; + } + } +} + +bool CControllerConfigManager::GetIsKeyBlank(int32 key, eControllerType type) +{ + switch (type) + { + case KEYBOARD: + case OPTIONAL_EXTRA: + if (key != rsNULL) + return false; + break; + + case JOYSTICK: + if (key != 0) + return false; + break; + + case MOUSE: + if (key != 0) + return false; + break; + default: break; + } + + return true; +} + +e_ControllerActionType CControllerConfigManager::GetActionType(e_ControllerAction action) +{ + switch (action) + { + case CAMERA_CHANGE_VIEW_ALL_SITUATIONS: +#ifndef BIND_VEHICLE_FIREWEAPON + case PED_FIREWEAPON: +#endif + case GO_LEFT: + case GO_RIGHT: + case NETWORK_TALK: + case SWITCH_DEBUG_CAM_ON: + case TOGGLE_DPAD: + case TAKE_SCREEN_SHOT: + case SHOW_MOUSE_POINTER_TOGGLE: + return ACTIONTYPE_COMMON; + break; + + case PED_LOOKBEHIND: + case PED_CYCLE_WEAPON_LEFT: + case PED_CYCLE_WEAPON_RIGHT: + case PED_JUMPING: + case PED_SPRINT: + case PED_CYCLE_TARGET_LEFT: + case PED_CYCLE_TARGET_RIGHT: + case PED_CENTER_CAMERA_BEHIND_PLAYER: + return ACTIONTYPE_3RDPERSON; + break; + +#ifdef BIND_VEHICLE_FIREWEAPON + case VEHICLE_FIREWEAPON: +#endif + case VEHICLE_LOOKBEHIND: + case VEHICLE_LOOKLEFT: + case VEHICLE_LOOKRIGHT: + case VEHICLE_HORN: + case VEHICLE_HANDBRAKE: + case VEHICLE_ACCELERATE: + case VEHICLE_BRAKE: + case VEHICLE_CHANGE_RADIO_STATION: + case TOGGLE_SUBMISSIONS: + case VEHICLE_TURRETLEFT: + case VEHICLE_TURRETRIGHT: + case VEHICLE_TURRETUP: + case VEHICLE_TURRETDOWN: + return ACTIONTYPE_VEHICLE; + break; + + case VEHICLE_ENTER_EXIT: + return ACTIONTYPE_VEHICLE_3RDPERSON; + break; + +#ifdef BIND_VEHICLE_FIREWEAPON + case PED_FIREWEAPON: +#endif + case PED_LOCK_TARGET: + case GO_FORWARD: + case GO_BACK: + case PED_1RST_PERSON_LOOK_LEFT: + case PED_1RST_PERSON_LOOK_RIGHT: + case PED_1RST_PERSON_LOOK_DOWN: + case PED_1RST_PERSON_LOOK_UP: + return ACTIONTYPE_1RST3RDPERSON; + break; + + case PED_SNIPER_ZOOM_IN: + case PED_SNIPER_ZOOM_OUT: + return ACTIONTYPE_1RSTPERSON; + break; + default: break; + } + + return ACTIONTYPE_NONE; +} + +void CControllerConfigManager::ClearSettingsAssociatedWithAction(e_ControllerAction action, eControllerType type) +{ + switch (type) + { + case KEYBOARD: + m_aSettings[action][type].m_Key = rsNULL; + m_aSettings[action][type].m_ContSetOrder = SETORDER_NONE; + break; + case OPTIONAL_EXTRA: + m_aSettings[action][type].m_Key = rsNULL; + m_aSettings[action][type].m_ContSetOrder = SETORDER_NONE; + break; + case MOUSE: + m_aSettings[action][type].m_Key = 0; + m_aSettings[action][type].m_ContSetOrder = SETORDER_NONE; + break; + case JOYSTICK: + m_aSettings[action][type].m_Key = 0; + m_aSettings[action][type].m_ContSetOrder = SETORDER_NONE; + break; + default: break; + } + + ResetSettingOrder(action); +} + +wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_ControllerAction action, eContSetOrder setorder) +{ + for (int i = 0; i < MAX_CONTROLLERTYPES; i++) + { + if (m_aSettings[action][i].m_ContSetOrder == setorder) + { + switch (i) + { + case KEYBOARD: + case OPTIONAL_EXTRA: + return GetControllerSettingTextKeyBoard(action, (eControllerType)i); + case MOUSE: + return GetControllerSettingTextMouse (action); + case JOYSTICK: + return GetControllerSettingTextJoystick(action); + default: break; + } + } + } + + return NULL; +} + +wchar *CControllerConfigManager::GetControllerSettingTextKeyBoard(e_ControllerAction action, eControllerType type) +{ + static wchar ActionText[50]; + static wchar NewStringWithNumber[30]; + + for (int32 i = 0; i < ARRAY_SIZE(ActionText); i++) + ActionText[i] = '\0'; + + if (GetControllerKeyAssociatedWithAction(action, type) != rsNULL) + { + if ( GetControllerKeyAssociatedWithAction(action, type) >= 0 + && GetControllerKeyAssociatedWithAction(action, type) <= 255) + { + char c = GetControllerKeyAssociatedWithAction(action, type); + if (c == ' ') + return TheText.Get("FEC_SPC"); // "SPC" + else + { + ActionText[0] = CFont::character_code(c); + if (ActionText[0] == '\0') + ActionText[0] = CFont::character_code('#'); + ActionText[1] = '\0'; + return ActionText; + } + } + else + { + switch (GetControllerKeyAssociatedWithAction(action, type)) + { + case rsF1: + case rsF2: + case rsF3: + case rsF4: + case rsF5: + case rsF6: + case rsF7: + case rsF8: + case rsF9: + case rsF10: + case rsF11: + case rsF12: + { + CMessages::InsertNumberInString(TheText.Get("FEC_FNC"), // "F~1~" + GetControllerKeyAssociatedWithAction(action, type) - rsESC, + -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsINS: + { + return TheText.Get("FEC_IRT"); // "INS" + break; + } + + case rsDEL: + { + return TheText.Get("FEC_DLL"); // "DEL" + break; + } + + case rsHOME: + { + return TheText.Get("FEC_HME"); // "HOME" + break; + } + + case rsEND: + { + return TheText.Get("FEC_END"); // "END" + break; + } + + case rsPGUP: + { + return TheText.Get("FEC_PGU"); // "PGUP" + break; + } + + case rsPGDN: + { + return TheText.Get("FEC_PGD"); // "PGDN" + break; + } + + case rsUP: + { + return TheText.Get("FEC_UPA"); // "UP" + break; + } + + case rsDOWN: + { + return TheText.Get("FEC_DWA"); // "DOWN" + break; + } + + case rsLEFT: + { + return TheText.Get("FEC_LFA"); // "LEFT" + break; + } + + case rsRIGHT: + { + return TheText.Get("FEC_RFA"); // "RIGHT" + break; + } + + case rsDIVIDE: + { + return TheText.Get("FEC_FWS"); // "NUM /" + break; + } + + case rsTIMES: + { + return TheText.Get("FEC_STR"); // "NUM STAR" + break; + } + + case rsPLUS: + { + return TheText.Get("FEC_PLS"); // "NUM +" + break; + } + + case rsMINUS: + { + return TheText.Get("FEC_MIN"); // "NUM -" + break; + } + + case rsPADDEL: + { + return TheText.Get("FEC_DOT"); // "NUM ." + break; + } + + case rsPADEND: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 1, -1, -1, -1, -1, -1, NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADDOWN: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 2, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADPGDN: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 3, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADLEFT: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 4, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPAD5: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 5, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsNUMLOCK: + { + return TheText.Get("FEC_NLK"); // "NUMLOCK" + break; + } + + case rsPADRIGHT: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 6, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADHOME: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 7, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADUP: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 8, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADPGUP: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 9, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADINS: + { + CMessages::InsertNumberInString(TheText.Get("FEC_NMN"), // "NUM~1~" + 0, -1, -1, -1, -1, -1, + NewStringWithNumber); + return NewStringWithNumber; + break; + } + + case rsPADENTER: + { + return TheText.Get("FEC_ETR"); // "ENT" + break; + } + + case rsSCROLL: + { + return TheText.Get("FEC_SLK"); // "SCROLL LOCK" + break; + } + + case rsPAUSE: + { + return TheText.Get("FEC_PSB"); // "BREAK" + break; + } + + case rsBACKSP: + { + return TheText.Get("FEC_BSP"); // "BSPACE" + break; + } + + case rsTAB: + { + return TheText.Get("FEC_TAB"); // "TAB" + break; + } + + case rsCAPSLK: + { + return TheText.Get("FEC_CLK"); // "CAPSLOCK" + break; + } + + case rsENTER: + { + return TheText.Get("FEC_RTN"); // "RET" + break; + } + + case rsLSHIFT: + { + return TheText.Get("FEC_LSF"); // "LSHIFT" + break; + } + + case rsRSHIFT: + { + return TheText.Get("FEC_RSF"); // "RSHIFT" + break; + } + + case rsLCTRL: + { + return TheText.Get("FEC_LCT"); // "LCTRL" + break; + } + + case rsRCTRL: + { + return TheText.Get("FEC_RCT"); // "RCTRL" + break; + } + + case rsLALT: + { + return TheText.Get("FEC_LAL"); // "LALT" + break; + } + + case rsRALT: + { + return TheText.Get("FEC_RAL"); // "RALT" + break; + } + + case rsLWIN: + { + return TheText.Get("FEC_LWD"); // "LWIN" + break; + } + + case rsRWIN: + { + return TheText.Get("FEC_RWD"); // "RWIN" + break; + } + + case rsAPPS: + { + return TheText.Get("FEC_WRC"); // "WINCLICK" + break; + } + + case rsSHIFT: + { + return TheText.Get("FEC_SFT"); // "SHIFT" + break; + } + default: break; + } + } + } + + return NULL; +} + +wchar *CControllerConfigManager::GetControllerSettingTextMouse(e_ControllerAction action) +{ + switch (m_aSettings[action][MOUSE].m_Key) + { + case 1: + return TheText.Get("FEC_MSL"); // LMB + break; + case 2: + return TheText.Get("FEC_MSM"); // MMB + break; + case 3: + return TheText.Get("FEC_MSR"); // RMB + break; + case 4: + return TheText.Get("FEC_MWF"); // WHEEL UP + break; + case 5: + return TheText.Get("FEC_MWB"); // WHEEL DN + break; + case 6: + return TheText.Get("FEC_MXO"); // MXB1 + break; + case 7: + return TheText.Get("FEC_MXT"); // MXB2 + break; + default: break; + } + + return NULL; +} + +wchar *CControllerConfigManager::GetControllerSettingTextJoystick(e_ControllerAction action) +{ + if (m_aSettings[action][JOYSTICK].m_Key == 0) + return NULL; + + static wchar NewStringWithNumber[30]; + + CMessages::InsertNumberInString(TheText.Get("FEC_JBO"), // JOY ~1~ + m_aSettings[action][JOYSTICK].m_Key, -1, -1, -1, -1, -1, + NewStringWithNumber); + + return NewStringWithNumber; +} + +int32 CControllerConfigManager::GetNumOfSettingsForAction(e_ControllerAction action) +{ + int32 num = 0; + + if (m_aSettings[action][KEYBOARD].m_Key != rsNULL) num++; + if (m_aSettings[action][OPTIONAL_EXTRA].m_Key != rsNULL) num++; + if (m_aSettings[action][MOUSE].m_Key != 0) num++; + if (m_aSettings[action][JOYSTICK].m_Key != 0) num++; + + return num; +} + +#ifdef BIND_VEHICLE_FIREWEAPON +#define VFB(b) b, +#else +#define VFB(b) +#endif + +#define CONTROLLER_BUTTONS(T, O, X, Q, L1, L2, L3, R1, R2, R3, SELECT) \ + {{ \ + O, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + nil, /* GO_LEFT */ \ + nil, /* GO_RIGHT */ \ + Q, /* PED_SNIPER_ZOOM_IN */ \ + X, /* PED_SNIPER_ZOOM_OUT */ \ + T, /* VEHICLE_ENTER_EXIT */ \ + SELECT, /* CAMERA_CHANGE_VIEW_ALL_SITUATIONS */ \ + Q, /* PED_JUMPING */ \ + X, /* PED_SPRINT */ \ + R3, /* PED_LOOKBEHIND */ \ + VFB(O) /* VEHICLE_FIREWEAPON */ \ + X, /* VEHICLE_ACCELERATE */ \ + Q, /* VEHICLE_BRAKE */ \ + L1, /* VEHICLE_CHANGE_RADIO_STATION */ \ + L3, /* VEHICLE_HORN */ \ + R3, /* TOGGLE_SUBMISSIONS */ \ + R1, /* VEHICLE_HANDBRAKE */ \ + nil, /* PED_1RST_PERSON_LOOK_LEFT */ \ + nil, /* PED_1RST_PERSON_LOOK_RIGHT */ \ + L2, /* VEHICLE_LOOKLEFT */ \ + R2, /* VEHICLE_LOOKRIGHT */ \ + nil, /* VEHICLE_LOOKBEHIND */ \ + nil, /* VEHICLE_TURRETLEFT */ \ + nil, /* VEHICLE_TURRETRIGHT */ \ + nil, /* VEHICLE_TURRETUP */ \ + nil, /* VEHICLE_TURRETDOWN */ \ + L2, /* PED_CYCLE_TARGET_LEFT */ \ + R2, /* PED_CYCLE_TARGET_RIGHT */ \ + L1, /* PED_CENTER_CAMERA_BEHIND_PLAYER */ \ + R1, /* PED_LOCK_TARGET */ \ + nil, /* NETWORK_TALK */ \ + nil, /* PED_1RST_PERSON_LOOK_UP */ \ + nil, /* PED_1RST_PERSON_LOOK_DOWN */ \ + nil, /* _CONTROLLERACTION_36 */ \ + nil, /* TOGGLE_DPAD */ \ + nil, /* SWITCH_DEBUG_CAM_ON */ \ + nil, /* TAKE_SCREEN_SHOT */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }, \ + { \ + O, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + nil, /* GO_LEFT */ \ + nil, /* GO_RIGHT */ \ + Q, /* PED_SNIPER_ZOOM_IN */ \ + X, /* PED_SNIPER_ZOOM_OUT */ \ + T, /* VEHICLE_ENTER_EXIT */ \ + SELECT, /* CAMERA_CHANGE_VIEW_ALL_SITUATIONS */ \ + Q, /* PED_JUMPING */ \ + X, /* PED_SPRINT */ \ + R3, /* PED_LOOKBEHIND */ \ + VFB(O) /* VEHICLE_FIREWEAPON */ \ + X, /* VEHICLE_ACCELERATE */ \ + Q, /* VEHICLE_BRAKE */ \ + SELECT, /* VEHICLE_CHANGE_RADIO_STATION */ \ + L1, /* VEHICLE_HORN */ \ + R3, /* TOGGLE_SUBMISSIONS */ \ + R1, /* VEHICLE_HANDBRAKE */ \ + nil, /* PED_1RST_PERSON_LOOK_LEFT */ \ + nil, /* PED_1RST_PERSON_LOOK_RIGHT */ \ + L2, /* VEHICLE_LOOKLEFT */ \ + R2, /* VEHICLE_LOOKRIGHT */ \ + nil, /* VEHICLE_LOOKBEHIND */ \ + nil, /* VEHICLE_TURRETLEFT */ \ + nil, /* VEHICLE_TURRETRIGHT */ \ + nil, /* VEHICLE_TURRETUP */ \ + nil, /* VEHICLE_TURRETDOWN */ \ + L2, /* PED_CYCLE_TARGET_LEFT */ \ + R2, /* PED_CYCLE_TARGET_RIGHT */ \ + L1, /* PED_CENTER_CAMERA_BEHIND_PLAYER */ \ + R1, /* PED_LOCK_TARGET */ \ + nil, /* NETWORK_TALK */ \ + nil, /* PED_1RST_PERSON_LOOK_UP */ \ + nil, /* PED_1RST_PERSON_LOOK_DOWN */ \ + nil, /* _CONTROLLERACTION_36 */ \ + nil, /* TOGGLE_DPAD */ \ + nil, /* SWITCH_DEBUG_CAM_ON */ \ + nil, /* TAKE_SCREEN_SHOT */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }, \ + { \ + X, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + nil, /* GO_LEFT */ \ + nil, /* GO_RIGHT */ \ + T, /* PED_SNIPER_ZOOM_IN */ \ + Q, /* PED_SNIPER_ZOOM_OUT */ \ + L1, /* VEHICLE_ENTER_EXIT */ \ + SELECT, /* CAMERA_CHANGE_VIEW_ALL_SITUATIONS */ \ + Q, /* PED_JUMPING */ \ + O, /* PED_SPRINT */ \ + R3, /* PED_LOOKBEHIND */ \ + VFB(O) /* VEHICLE_FIREWEAPON */ \ + X, /* VEHICLE_ACCELERATE */ \ + Q, /* VEHICLE_BRAKE */ \ + L3, /* VEHICLE_CHANGE_RADIO_STATION */ \ + R1, /* VEHICLE_HORN */ \ + R3, /* TOGGLE_SUBMISSIONS */ \ + T, /* VEHICLE_HANDBRAKE */ \ + nil, /* PED_1RST_PERSON_LOOK_LEFT */ \ + nil, /* PED_1RST_PERSON_LOOK_RIGHT */ \ + L2, /* VEHICLE_LOOKLEFT */ \ + R2, /* VEHICLE_LOOKRIGHT */ \ + nil, /* VEHICLE_LOOKBEHIND */ \ + nil, /* VEHICLE_TURRETLEFT */ \ + nil, /* VEHICLE_TURRETRIGHT */ \ + nil, /* VEHICLE_TURRETUP */ \ + nil, /* VEHICLE_TURRETDOWN */ \ + L2, /* PED_CYCLE_TARGET_LEFT */ \ + R2, /* PED_CYCLE_TARGET_RIGHT */ \ + T, /* PED_CENTER_CAMERA_BEHIND_PLAYER */ \ + R1, /* PED_LOCK_TARGET */ \ + nil, /* NETWORK_TALK */ \ + nil, /* PED_1RST_PERSON_LOOK_UP */ \ + nil, /* PED_1RST_PERSON_LOOK_DOWN */ \ + nil, /* _CONTROLLERACTION_36 */ \ + nil, /* TOGGLE_DPAD */ \ + nil, /* SWITCH_DEBUG_CAM_ON */ \ + nil, /* TAKE_SCREEN_SHOT */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }, \ + { \ + R1, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + nil, /* GO_LEFT */ \ + nil, /* GO_RIGHT */ \ + Q, /* PED_SNIPER_ZOOM_IN */ \ + X, /* PED_SNIPER_ZOOM_OUT */ \ + T, /* VEHICLE_ENTER_EXIT */ \ + SELECT, /* CAMERA_CHANGE_VIEW_ALL_SITUATIONS */ \ + Q, /* PED_JUMPING */ \ + X, /* PED_SPRINT */ \ + R3, /* PED_LOOKBEHIND */ \ + VFB(R1) /* VEHICLE_FIREWEAPON */ \ + nil, /* VEHICLE_ACCELERATE */ \ + nil, /* VEHICLE_BRAKE */ \ + O, /* VEHICLE_CHANGE_RADIO_STATION */ \ + L3, /* VEHICLE_HORN */ \ + Q, /* TOGGLE_SUBMISSIONS */ \ + L1, /* VEHICLE_HANDBRAKE */ \ + nil, /* PED_1RST_PERSON_LOOK_LEFT */ \ + nil, /* PED_1RST_PERSON_LOOK_RIGHT */ \ + L2, /* VEHICLE_LOOKLEFT */ \ + R2, /* VEHICLE_LOOKRIGHT */ \ + nil, /* VEHICLE_LOOKBEHIND */ \ + nil, /* VEHICLE_TURRETLEFT */ \ + nil, /* VEHICLE_TURRETRIGHT */ \ + nil, /* VEHICLE_TURRETUP */ \ + nil, /* VEHICLE_TURRETDOWN */ \ + L2, /* PED_CYCLE_TARGET_LEFT */ \ + R2, /* PED_CYCLE_TARGET_RIGHT */ \ + O, /* PED_CENTER_CAMERA_BEHIND_PLAYER */ \ + L1, /* PED_LOCK_TARGET */ \ + nil, /* NETWORK_TALK */ \ + nil, /* PED_1RST_PERSON_LOOK_UP */ \ + nil, /* PED_1RST_PERSON_LOOK_DOWN */ \ + nil, /* _CONTROLLERACTION_36 */ \ + nil, /* TOGGLE_DPAD */ \ + nil, /* SWITCH_DEBUG_CAM_ON */ \ + nil, /* TAKE_SCREEN_SHOT */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }} + + +const char *XboxButtons_noIcons[][MAX_CONTROLLERACTIONS] = CONTROLLER_BUTTONS("Y", "B", "A", "X", "LB", "LT", "LS", "RB", "RT", "RS", "BACK"); + +#ifdef BUTTON_ICONS +const char *XboxButtons[][MAX_CONTROLLERACTIONS] = CONTROLLER_BUTTONS("~T~", "~O~", "~X~", "~Q~", "~K~", "~M~", "~A~", "~J~", "~V~", "~C~", "BACK"); +#endif + + +#if 0 // set 1 for ps2 fonts +#define PS2_TRIANGLE "\"" +#define PS2_CIRCLE "|" +#define PS2_CROSS "/" +#define PS2_SQUARE "^" +#else +#define PS2_TRIANGLE "TRIANGLE" +#define PS2_CIRCLE "CIRCLE" +#define PS2_CROSS "CROSS" +#define PS2_SQUARE "SQUARE" +#endif + +const char *PlayStationButtons_noIcons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS(PS2_TRIANGLE, PS2_CIRCLE, PS2_CROSS, PS2_SQUARE, "L1", "L2", "L3", "R1", "R2", "R3", "SELECT"); + +#ifdef BUTTON_ICONS +const char *PlayStationButtons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS("~T~", "~O~", "~X~", "~Q~", "~K~", "~M~", "~A~", "~J~", "~V~", "~C~", "SELECT"); +#endif + +#undef PS2_TRIANGLE +#undef PS2_CIRCLE +#undef PS2_CROSS +#undef PS2_SQUARE + +const char *NintendoSwitchButtons_noIcons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS("Y", "A", "B", "X", "L", "ZL", "LS", "R", "ZR", "RS", "BACK"); + +#ifdef BUTTON_ICONS +const char *NintendoSwitchButtons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS("~T~", "~O~", "~X~", "~Q~", "~K~", "~M~", "~A~", "~J~", "~V~", "~C~", "BACK"); +#endif + +#undef CONTROLLER_BUTTONS +#undef VFB + +void CControllerConfigManager::GetWideStringOfCommandKeys(uint16 action, wchar *text, uint16 leight) +{ +#ifdef DETECT_PAD_INPUT_SWITCH + if (CPad::GetPad(0)->IsAffectedByController) { + wchar wstr[16]; + + const char* (*Buttons)[MAX_CONTROLLERACTIONS]; + +#ifdef BUTTON_ICONS + #ifdef GAMEPAD_MENU + switch (FrontEndMenuManager.m_PrefsControllerType) + { + case CMenuManager::CONTROLLER_DUALSHOCK2: + case CMenuManager::CONTROLLER_DUALSHOCK3: + case CMenuManager::CONTROLLER_DUALSHOCK4: + Buttons = CFont::ButtonsSlot != -1 ? PlayStationButtons : PlayStationButtons_noIcons; + break; + case CMenuManager::CONTROLLER_NINTENDO_SWITCH: + Buttons = CFont::ButtonsSlot != -1 ? NintendoSwitchButtons : NintendoSwitchButtons_noIcons; + break; + default: + #endif + Buttons = CFont::ButtonsSlot != -1 ? XboxButtons : XboxButtons_noIcons; + #ifdef GAMEPAD_MENU + break; + } + #endif +#else + switch (FrontEndMenuManager.m_PrefsControllerType) + { + case CMenuManager::CONTROLLER_DUALSHOCK2: + case CMenuManager::CONTROLLER_DUALSHOCK3: + case CMenuManager::CONTROLLER_DUALSHOCK4: + Buttons = PlayStationButtons_noIcons; + break; + case CMenuManager::CONTROLLER_NINTENDO_SWITCH: + Buttons = NintendoSwitchButtons_noIcons; + break; + default: + Buttons = XboxButtons_noIcons; + break; + } +#endif + + assert(Buttons[CPad::GetPad(0)->Mode][action] != nil); // we cannot use these + AsciiToUnicode(Buttons[CPad::GetPad(0)->Mode][action], wstr); + + CMessages::WideStringCopy(text, wstr, leight); + return; + } +#endif + + int32 nums = GetNumOfSettingsForAction((e_ControllerAction)action); + + int32 sets = 0; + + for (int32 i = SETORDER_1; i < MAX_SETORDERS; i++) + { + wchar *textorder = ControlsManager.GetControllerSettingTextWithOrderNumber((e_ControllerAction)action, (eContSetOrder)i); + if (textorder != NULL) + { + uint16 len = CMessages::GetWideStringLength(text); + CMessages::WideStringCopy(&text[len], textorder, leight - len); + + if (++sets < nums) + { + if (sets == nums - 1) + { + // if last - text += ' or '; + uint16 pos1 = CMessages::GetWideStringLength(text); + text[pos1] = ' '; + + CMessages::WideStringCopy(&text[pos1 + 1], + TheText.Get("FEC_ORR"), // "or" + leight - (pos1 + 1)); + + uint16 pos2 = CMessages::GetWideStringLength(text); + text[pos2 + 0] = ' '; + text[pos2 + 1] = '\0'; + } + else + { + // text += ', '; + uint16 pos1 = CMessages::GetWideStringLength(text); + text[pos1 + 0] = ','; + text[pos1 + 1] = ' '; + text[pos1 + 2] = '\0'; + + uint16 pos2 = CMessages::GetWideStringLength(text); + } + } + } + } +} + +int32 CControllerConfigManager::GetControllerKeyAssociatedWithAction(e_ControllerAction action, eControllerType type) +{ + return m_aSettings[action][type].m_Key; +} + +void CControllerConfigManager::UpdateJoyButtonState(int32 padnumber) +{ + for (int32 i = 0; i < MAX_BUTTONS; i++) + m_aButtonStates[i] = false; + +#ifdef __DINPUT_INCLUDED__ + for (int32 i = 0; i < MAX_BUTTONS; i++) + { + if (m_NewState.rgbButtons[i] & 0x80) + m_aButtonStates[i] = true; + else + m_aButtonStates[i] = false; + } +#elif defined RW_GL3 + if (m_NewState.isGamepad) { + for (int32 i = 0; i < MAX_BUTTONS; i++) { + if (i == GLFW_GAMEPAD_BUTTON_GUIDE) + continue; + + m_aButtonStates[MapIdToButtonId(i)-1] = m_NewState.mappedButtons[i]; + } + } else { + for (int32 i = 0; i < Min(m_NewState.numButtons, MAX_BUTTONS); i++) { + m_aButtonStates[i] = m_NewState.buttons[i]; + } + } +#endif +} + +bool CControllerConfigManager::GetIsActionAButtonCombo(e_ControllerAction action) +{ + switch (action) + { + case VEHICLE_LOOKBEHIND: + case PED_CYCLE_TARGET_LEFT: + case PED_CYCLE_TARGET_RIGHT: + return true; + break; + default: break; + } + + return false; +} + +wchar *CControllerConfigManager::GetButtonComboText(e_ControllerAction action) +{ + switch (action) + { + case PED_CYCLE_TARGET_LEFT: + return TheText.Get("FEC_PTL"); // Use LockTarget with Weapon Switch Left. + break; + + case PED_CYCLE_TARGET_RIGHT: + return TheText.Get("FEC_PTR"); // Use LockTarget with Weapon Switch Right. + break; + + case VEHICLE_LOOKBEHIND: + return TheText.Get("FEC_LBC"); // Use Look Left With Look Right. + break; + default: break; + } + + return NULL; +} + +void CControllerConfigManager::SetControllerKeyAssociatedWithAction(e_ControllerAction action, int32 key, eControllerType type) +{ + ResetSettingOrder(action); + int numOfSettings = GetNumOfSettingsForAction(action); + + m_aSettings[action][type].m_Key = key; + m_aSettings[action][type].m_ContSetOrder = numOfSettings + 1; +} + +int32 CControllerConfigManager::GetMouseButtonAssociatedWithAction(e_ControllerAction action) +{ + return m_aSettings[action][MOUSE].m_Key; +} + +void CControllerConfigManager::SetMouseButtonAssociatedWithAction(e_ControllerAction action, int32 button) +{ + int numOfSettings = GetNumOfSettingsForAction(action); + + m_aSettings[action][MOUSE].m_Key = button; + m_aSettings[action][MOUSE].m_ContSetOrder = numOfSettings + 1; +} + +void CControllerConfigManager::ResetSettingOrder(e_ControllerAction action) +{ + int32 conttype = KEYBOARD; + + for (int32 i = SETORDER_1; i < MAX_SETORDERS; i++) + { + bool isexist = false; + for (int32 j = 0; j < MAX_CONTROLLERTYPES; j++) + { + if (m_aSettings[action][j].m_ContSetOrder == i) + isexist = true; + } + + bool init = false; + + if (!isexist) + { + for (int32 k = 0; k < MAX_CONTROLLERTYPES; k++) + { + int32 setorder = m_aSettings[action][k].m_ContSetOrder; + if (setorder > i && setorder != 0) + { + if (init) + { + if (setorder < m_aSettings[action][conttype].m_ContSetOrder) + conttype = k; + } + else + { + init = true; + conttype = k; + } + } + } + + if (init) + m_aSettings[action][conttype].m_ContSetOrder = i; + } + } +} diff --git a/src/core/ControllerConfig.h b/src/core/ControllerConfig.h new file mode 100644 index 0000000..295f03b --- /dev/null +++ b/src/core/ControllerConfig.h @@ -0,0 +1,226 @@ +#pragma once + +#if defined RW_D3D9 || defined RWLIBS +#define DIRECTINPUT_VERSION 0x0800 +#include +#endif + +// based on x-gtasa + +enum eControllerType +{ + KEYBOARD = 0, + OPTIONAL_EXTRA, + MOUSE, + JOYSTICK, + MAX_CONTROLLERTYPES, +}; + +enum e_ControllerAction +{ + PED_FIREWEAPON = 0, + PED_CYCLE_WEAPON_RIGHT, + PED_CYCLE_WEAPON_LEFT, + GO_FORWARD, + GO_BACK, + GO_LEFT, + GO_RIGHT, + PED_SNIPER_ZOOM_IN, + PED_SNIPER_ZOOM_OUT, + VEHICLE_ENTER_EXIT, + CAMERA_CHANGE_VIEW_ALL_SITUATIONS, + PED_JUMPING, + PED_SPRINT, + PED_LOOKBEHIND, +#ifdef BIND_VEHICLE_FIREWEAPON + VEHICLE_FIREWEAPON, +#endif + VEHICLE_ACCELERATE, + VEHICLE_BRAKE, + VEHICLE_CHANGE_RADIO_STATION, + VEHICLE_HORN, + TOGGLE_SUBMISSIONS, + VEHICLE_HANDBRAKE, + PED_1RST_PERSON_LOOK_LEFT, + PED_1RST_PERSON_LOOK_RIGHT, + VEHICLE_LOOKLEFT, + VEHICLE_LOOKRIGHT, + VEHICLE_LOOKBEHIND, + VEHICLE_TURRETLEFT, + VEHICLE_TURRETRIGHT, + VEHICLE_TURRETUP, + VEHICLE_TURRETDOWN, + PED_CYCLE_TARGET_LEFT, + PED_CYCLE_TARGET_RIGHT, + PED_CENTER_CAMERA_BEHIND_PLAYER, + PED_LOCK_TARGET, + NETWORK_TALK, + PED_1RST_PERSON_LOOK_UP, + PED_1RST_PERSON_LOOK_DOWN, + _CONTROLLERACTION_36, // Unused + TOGGLE_DPAD, + SWITCH_DEBUG_CAM_ON, + TAKE_SCREEN_SHOT, + SHOW_MOUSE_POINTER_TOGGLE, + MAX_CONTROLLERACTIONS, +}; + +enum e_ControllerActionType +{ + ACTIONTYPE_1RSTPERSON = 0, + ACTIONTYPE_3RDPERSON, + ACTIONTYPE_VEHICLE, + ACTIONTYPE_VEHICLE_3RDPERSON, + ACTIONTYPE_COMMON, + ACTIONTYPE_1RST3RDPERSON, + ACTIONTYPE_NONE, +}; + +enum eContSetOrder +{ + SETORDER_NONE = 0, + SETORDER_1, + SETORDER_2, + SETORDER_3, + SETORDER_4, + MAX_SETORDERS, +}; + +enum eSimCheckers +{ + SIM_X1 = 0, SIM_Y1, // DPad + SIM_X2, SIM_Y2, // LeftStick + + MAX_SIMS +}; + +class CMouseControllerState; +class CControllerState; + + +#define JOY_BUTTONS 16 +#define MAX_BUTTONS (JOY_BUTTONS+1) + +#define ACTIONNAME_LENGTH 40 + +#ifdef RW_GL3 +struct GlfwJoyState { + int8 id; + bool isGamepad; + uint8 numButtons; + uint8* buttons; + bool mappedButtons[17]; +}; +#endif + +class CControllerConfigManager +{ +public: + struct tControllerConfigBind + { + int32 m_Key; + int32 m_ContSetOrder; + + tControllerConfigBind() + { + m_Key = 0; + m_ContSetOrder = 0; + } + }; + + bool m_bFirstCapture; +#if defined RW_GL3 + GlfwJoyState m_OldState; + GlfwJoyState m_NewState; +#else + DIJOYSTATE2 m_OldState; + DIJOYSTATE2 m_NewState; +#endif + wchar m_aActionNames[MAX_CONTROLLERACTIONS][ACTIONNAME_LENGTH]; + bool m_aButtonStates[MAX_BUTTONS]; + tControllerConfigBind m_aSettings[MAX_CONTROLLERACTIONS][MAX_CONTROLLERTYPES]; + bool m_aSimCheckers[MAX_SIMS][MAX_CONTROLLERTYPES]; + bool m_bMouseAssociated; + +#ifdef LOAD_INI_SETTINGS + static uint32 ms_padButtonsInited; +#endif + + CControllerConfigManager(); + + void MakeControllerActionsBlank(); + + int32 GetJoyButtonJustDown(); + + void SaveSettings(int32 file); + void LoadSettings(int32 file); + + void InitDefaultControlConfiguration(); + void InitDefaultControlConfigMouse(CMouseControllerState const &availableButtons); + void InitDefaultControlConfigJoyPad(uint32 buttons); + void InitialiseControllerActionNameArray(); + + void UpdateJoyInConfigMenus_ButtonDown (int32 button, int32 padnumber); + void AffectControllerStateOn_ButtonDown (int32 button, eControllerType type); + void AffectControllerStateOn_ButtonDown_Driving (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_FirstPersonOnly (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_ThirdPersonOnly (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_FirstAndThirdPersonOnly (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_AllStates (int32 button, eControllerType type, CControllerState &state); + void AffectControllerStateOn_ButtonDown_VehicleAndThirdPersonOnly(int32 button, eControllerType type, CControllerState &state); + + void UpdateJoyInConfigMenus_ButtonUp(int32 button, int32 padnumber); + void AffectControllerStateOn_ButtonUp(int32 button, eControllerType type); + void AffectControllerStateOn_ButtonUp_All_Player_States(int32 button, eControllerType type, CControllerState &state); + + void AffectPadFromKeyBoard(); + void AffectPadFromMouse(); + + void ClearSimButtonPressCheckers(); + + bool GetIsKeyboardKeyDown (RsKeyCodes keycode); + bool GetIsKeyboardKeyJustDown(RsKeyCodes keycode); + bool GetIsMouseButtonDown (RsKeyCodes keycode); + bool GetIsMouseButtonUp (RsKeyCodes keycode); + + + void DeleteMatchingCommonControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatching3rdPersonControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatching1rst3rdPersonControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatchingVehicleControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatchingVehicle_3rdPersonControls(e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatching1rstPersonControls (e_ControllerAction action, int32 key, eControllerType type); + void DeleteMatchingActionInitiators (e_ControllerAction action, int32 key, eControllerType type); + +#ifdef RADIO_SCROLL_TO_PREV_STATION + bool IsAnyVehicleActionAssignedToMouseKey(int32 key); +#endif + + bool GetIsKeyBlank(int32 key, eControllerType type); + e_ControllerActionType GetActionType(e_ControllerAction action); + + void ClearSettingsAssociatedWithAction (e_ControllerAction action, eControllerType type); + wchar *GetControllerSettingTextWithOrderNumber(e_ControllerAction action, eContSetOrder setorder); + wchar *GetControllerSettingTextKeyBoard (e_ControllerAction action, eControllerType type); + wchar *GetControllerSettingTextMouse (e_ControllerAction action); + wchar *GetControllerSettingTextJoystick (e_ControllerAction action); + + int32 GetNumOfSettingsForAction(e_ControllerAction action); + void GetWideStringOfCommandKeys(uint16 action, wchar *text, uint16 leight); + int32 GetControllerKeyAssociatedWithAction(e_ControllerAction action, eControllerType type); + + void UpdateJoyButtonState(int32 padnumber); + + bool GetIsActionAButtonCombo (e_ControllerAction action); + wchar *GetButtonComboText (e_ControllerAction action); + void SetControllerKeyAssociatedWithAction(e_ControllerAction action, int32 key, eControllerType type); + int32 GetMouseButtonAssociatedWithAction (e_ControllerAction action); + void SetMouseButtonAssociatedWithAction (e_ControllerAction action, int32 button); + void ResetSettingOrder (e_ControllerAction action); +}; + +#ifndef RW_GL3 +VALIDATE_SIZE(CControllerConfigManager, 0x143C); +#endif + +extern CControllerConfigManager ControlsManager; \ No newline at end of file diff --git a/src/core/Crime.h b/src/core/Crime.h new file mode 100644 index 0000000..0582904 --- /dev/null +++ b/src/core/Crime.h @@ -0,0 +1,36 @@ +#pragma once + +enum eCrimeType { + CRIME_NONE, + CRIME_POSSESSION_GUN, + CRIME_HIT_PED, + CRIME_HIT_COP, + CRIME_SHOOT_PED, + CRIME_SHOOT_COP, + CRIME_STEAL_CAR, + CRIME_RUN_REDLIGHT, + CRIME_RECKLESS_DRIVING, + CRIME_SPEEDING, + CRIME_RUNOVER_PED, + CRIME_RUNOVER_COP, + CRIME_SHOOT_HELI, + CRIME_PED_BURNED, + CRIME_COP_BURNED, + CRIME_VEHICLE_BURNED, + CRIME_DESTROYED_CESSNA, + NUM_CRIME_TYPES +}; + +class CCrimeBeingQd +{ +public: + eCrimeType m_nType; + int32 m_nId; + uint32 m_nTime; + CVector m_vecPosn; + bool m_bReported; + bool m_bPoliceDoesntCare; + + CCrimeBeingQd() { }; + ~CCrimeBeingQd() { }; +}; diff --git a/src/core/Debug.cpp b/src/core/Debug.cpp new file mode 100644 index 0000000..e794dca --- /dev/null +++ b/src/core/Debug.cpp @@ -0,0 +1,170 @@ +#include "common.h" +#include "RwHelper.h" +#include "Debug.h" +#include "Lines.h" +#include "Font.h" +#include "main.h" +#include "Text.h" + +bool gbDebugStuffInRelease = false; + +#define DEBUG_X_POS (300) +#define DEBUG_Y_POS (41) +#define DEBUG_LINE_HEIGHT (22) + +int16 CDebug::ms_nCurrentTextLine; +char CDebug::ms_aTextBuffer[MAX_LINES][MAX_STR_LEN]; + +void +CDebug::DebugInitTextBuffer() +{ + ms_nCurrentTextLine = 0; +} + +void +CDebug::DebugAddText(const char *str) +{ + int32 i = 0; + if (*str != '\0') { + while (i < MAX_STR_LEN - 1) { + ms_aTextBuffer[ms_nCurrentTextLine][i++] = *(str++); + if (*str == '\0') + break; + } + } + + ms_aTextBuffer[ms_nCurrentTextLine++][i] = '\0'; + if (ms_nCurrentTextLine >= MAX_LINES) + ms_nCurrentTextLine = 0; +} + +void +CDebug::DebugDisplayTextBuffer() +{ +#ifndef MASTER + if (gbDebugStuffInRelease) + { + int32 i = 0; + int32 y = DEBUG_Y_POS; +#ifdef FIX_BUGS + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(1.0f, 1.0f); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); +#else + // this is not even readable + CFont::SetPropOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(1.0f, 1.0f); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetPropOff(); +#endif + do { + char *line; + while (true) { + line = ms_aTextBuffer[(ms_nCurrentTextLine + i++) % MAX_LINES]; + if (*line != '\0') + break; + y += DEBUG_LINE_HEIGHT; + if (i == MAX_LINES) { + CFont::DrawFonts(); + return; + } + } + AsciiToUnicode(line, gUString); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(DEBUG_X_POS, y-1, gUString); + CFont::SetColor(CRGBA(255, 128, 128, 255)); + CFont::PrintString(DEBUG_X_POS+1, y, gUString); + y += DEBUG_LINE_HEIGHT; + } while (i != MAX_LINES); + CFont::DrawFonts(); + } +#endif +} + + +// custom + +CDebug::ScreenStr CDebug::ms_aScreenStrs[MAX_SCREEN_STRS]; +int CDebug::ms_nScreenStrs; + +void +CDebug::DisplayScreenStrings() +{ + int i; + + + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(1.0f, 1.0f); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetJustifyOff(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetWrapx(9999.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + + for(i = 0; i < ms_nScreenStrs; i++){ +/* + AsciiToUnicode(ms_aScreenStrs[i].str, gUString); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(ms_aScreenStrs[i].x, ms_aScreenStrs[i].y, gUString); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::PrintString(ms_aScreenStrs[i].x+1, ms_aScreenStrs[i].y+1, gUString); +*/ + ObrsPrintfString(ms_aScreenStrs[i].str, ms_aScreenStrs[i].x, ms_aScreenStrs[i].y); + } + CFont::DrawFonts(); + + ms_nScreenStrs = 0; +} + +void +CDebug::PrintAt(const char *str, int x, int y) +{ + if(ms_nScreenStrs >= MAX_SCREEN_STRS) + return; + strncpy(ms_aScreenStrs[ms_nScreenStrs].str, str, 256); + ms_aScreenStrs[ms_nScreenStrs].x = x;//*12; + ms_aScreenStrs[ms_nScreenStrs].y = y;//*22; + ms_nScreenStrs++; +} + +CDebug::Line CDebug::ms_aLines[MAX_DEBUG_LINES]; +int CDebug::ms_nLines; + +void +CDebug::AddLine(CVector p1, CVector p2, uint32 c1, uint32 c2) +{ + if(ms_nLines >= MAX_DEBUG_LINES) + return; + ms_aLines[ms_nLines].p1 = p1; + ms_aLines[ms_nLines].p2 = p2; + ms_aLines[ms_nLines].c1 = c1; + ms_aLines[ms_nLines].c2 = c2; + ms_nLines++; +} + +void +CDebug::DrawLines(void) +{ + int i; + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + for(i = 0; i < ms_nLines; i++){ + Line *l = &ms_aLines[i]; + CLines::RenderLineWithClipping(l->p1.x, l->p1.y, l->p1.z, l->p2.x, l->p2.y, l->p2.z, l->c1, l->c2); + } + ms_nLines = 0; +} diff --git a/src/core/Debug.h b/src/core/Debug.h new file mode 100644 index 0000000..4a25bf4 --- /dev/null +++ b/src/core/Debug.h @@ -0,0 +1,45 @@ +#pragma once + +class CDebug +{ + enum + { + MAX_LINES = 15, + MAX_STR_LEN = 80, + + MAX_SCREEN_STRS = 100, + MAX_DEBUG_LINES = 100, + }; + + static int16 ms_nCurrentTextLine; + static char ms_aTextBuffer[MAX_LINES][MAX_STR_LEN]; + + // custom + struct ScreenStr { + int x, y; + char str[256]; + }; + static ScreenStr ms_aScreenStrs[MAX_SCREEN_STRS]; + static int ms_nScreenStrs; + + struct Line { + CVector p1, p2; + uint32 c1, c2; + }; + static Line ms_aLines[MAX_DEBUG_LINES]; + static int ms_nLines; + +public: + static void DebugInitTextBuffer(); + static void DebugDisplayTextBuffer(); + static void DebugAddText(const char *str); + + // custom + static void PrintAt(const char *str, int x, int y); + static void DisplayScreenStrings(); + + static void AddLine(CVector p1, CVector p2, uint32 c1, uint32 c2); + static void DrawLines(void); +}; + +extern bool gbDebugStuffInRelease; diff --git a/src/core/Directory.cpp b/src/core/Directory.cpp new file mode 100644 index 0000000..0534406 --- /dev/null +++ b/src/core/Directory.cpp @@ -0,0 +1,74 @@ +#include "common.h" + +#include "General.h" +#include "FileMgr.h" +#include "Directory.h" + +CDirectory::CDirectory(int32 maxEntries) + : numEntries(0), maxEntries(maxEntries) +{ + entries = new DirectoryInfo[maxEntries]; +} + +CDirectory::~CDirectory(void) +{ + delete[] entries; +} + +void +CDirectory::ReadDirFile(const char *filename) +{ + int fd; + DirectoryInfo dirinfo; + + fd = CFileMgr::OpenFile(filename, "rb"); + while(CFileMgr::Read(fd, (char*)&dirinfo, sizeof(dirinfo))) + AddItem(dirinfo); + CFileMgr::CloseFile(fd); +} + +bool +CDirectory::WriteDirFile(const char *filename) +{ + int fd; + size_t n; + fd = CFileMgr::OpenFileForWriting(filename); + n = CFileMgr::Write(fd, (char*)entries, numEntries*sizeof(DirectoryInfo)); + CFileMgr::CloseFile(fd); + return n == numEntries*sizeof(DirectoryInfo); +} + +void +CDirectory::AddItem(const DirectoryInfo &dirinfo) +{ + assert(numEntries < maxEntries); +#ifdef FIX_BUGS + // don't add if already exists + uint32 offset, size; + if(FindItem(dirinfo.name, offset, size)) + return; +#endif + entries[numEntries++] = dirinfo; +} + +void +CDirectory::AddItem(const DirectoryInfo &dirinfo, int32 imgId) +{ + DirectoryInfo di = dirinfo; + di.offset |= imgId<<24; + AddItem(di); +} + +bool +CDirectory::FindItem(const char *name, uint32 &offset, uint32 &size) +{ + int i; + + for(i = 0; i < numEntries; i++) + if(!CGeneral::faststricmp(entries[i].name, name)){ + offset = entries[i].offset; + size = entries[i].size; + return true; + } + return false; +} diff --git a/src/core/Directory.h b/src/core/Directory.h new file mode 100644 index 0000000..0fef080 --- /dev/null +++ b/src/core/Directory.h @@ -0,0 +1,23 @@ +#pragma once + +class CDirectory +{ +public: + struct DirectoryInfo { + uint32 offset; + uint32 size; + char name[24]; + }; + DirectoryInfo *entries; + int32 maxEntries; + int32 numEntries; + + CDirectory(int32 maxEntries); + ~CDirectory(void); + + void ReadDirFile(const char *filename); + bool WriteDirFile(const char *filename); + void AddItem(const DirectoryInfo &dirinfo); + void AddItem(const DirectoryInfo &dirinfo, int32 imgId); + bool FindItem(const char *name, uint32 &offset, uint32 &size); +}; diff --git a/src/core/EventList.cpp b/src/core/EventList.cpp new file mode 100644 index 0000000..93f72d4 --- /dev/null +++ b/src/core/EventList.cpp @@ -0,0 +1,237 @@ +#include "common.h" + +#include "Pools.h" +#include "ModelIndices.h" +#include "World.h" +#include "Wanted.h" +#include "EventList.h" +#include "Messages.h" +#include "Text.h" +#include "main.h" +#include "Accident.h" + +int32 CEventList::ms_nFirstFreeSlotIndex; +CEvent gaEvent[NUMEVENTS]; + +enum +{ + EVENT_STATE_0, + EVENT_STATE_CANDELETE, + EVENT_STATE_CLEAR, +}; + +void +CEventList::Initialise(void) +{ + int i; + + debug("Initialising CEventList..."); + for(i = 0; i < NUMEVENTS; i++){ + gaEvent[i].type = EVENT_NULL; + gaEvent[i].entityType = EVENT_ENTITY_NONE; + gaEvent[i].entityRef = 0; + gaEvent[i].posn.x = 0.0f; + gaEvent[i].posn.y = 0.0f; + gaEvent[i].posn.z = 0.0f; + gaEvent[i].timeout = 0; + gaEvent[i].state = EVENT_STATE_0; + } + ms_nFirstFreeSlotIndex = 0; +} + +void +CEventList::Update(void) +{ + int i; + + ms_nFirstFreeSlotIndex = 0; + for(i = 0; i < NUMEVENTS; i++){ + if(gaEvent[i].type == EVENT_NULL) + continue; + if(CTimer::GetTimeInMilliseconds() > gaEvent[i].timeout || gaEvent[i].state == EVENT_STATE_CANDELETE){ + gaEvent[i].type = EVENT_NULL; + gaEvent[i].state = EVENT_STATE_0; + } + if(gaEvent[i].state == EVENT_STATE_CLEAR) + gaEvent[i].state = EVENT_STATE_CANDELETE; + } +} + +void +CEventList::RegisterEvent(eEventType type, eEventEntity entityType, CEntity *ent, CPed *criminal, int32 timeout) +{ + int i; + int ref; + bool copsDontCare; + +#ifdef SQUEEZE_PERFORMANCE + if (type == EVENT_INJURED_PED) { + gAccidentManager.ReportAccident((CPed*)ent); + return; + } +#endif + + copsDontCare = false; + switch(entityType){ + case EVENT_ENTITY_PED: + ref = CPools::GetPedRef((CPed*)ent); + if(ent->GetModelIndex() >= MI_GANG01 && ent->GetModelIndex() <= MI_CRIMINAL02) + copsDontCare = true; + break; + case EVENT_ENTITY_VEHICLE: + ref = CPools::GetVehicleRef((CVehicle*)ent); + break; + case EVENT_ENTITY_OBJECT: + ref = CPools::GetObjectRef((CObject*)ent); + break; + default: + Error("Undefined entity type, RegisterEvent, EventList.cpp"); + ref = 0; + break; + } + + // only update time if event exists already + for(i = 0; i < NUMEVENTS; i++) + if(gaEvent[i].type == type && + gaEvent[i].entityType == entityType && + gaEvent[i].entityRef == ref){ + gaEvent[i].timeout = CTimer::GetTimeInMilliseconds() + timeout; + return; + } + + for(i = ms_nFirstFreeSlotIndex; i < NUMEVENTS; i++) + if(gaEvent[i].type == EVENT_NULL){ + ms_nFirstFreeSlotIndex = i; + break; + } + if(i < NUMEVENTS){ + gaEvent[i].type = type; + gaEvent[i].entityType = entityType; + gaEvent[i].timeout = CTimer::GetTimeInMilliseconds() + timeout; + gaEvent[i].entityRef = ref; + gaEvent[i].posn = ent->GetPosition(); + gaEvent[i].criminal = criminal; + if(gaEvent[i].criminal) + gaEvent[i].criminal->RegisterReference((CEntity**)&gaEvent[i].criminal); + if(type == EVENT_GUNSHOT) + gaEvent[i].state = EVENT_STATE_CLEAR; + else + gaEvent[i].state = EVENT_STATE_0; + } + + if(criminal == FindPlayerPed()) + ReportCrimeForEvent(type, (intptr)ent, copsDontCare); +} + +void +CEventList::RegisterEvent(eEventType type, CVector posn, int32 timeout) +{ + int i; + + // only update time if event exists already + for(i = 0; i < NUMEVENTS; i++) + if(gaEvent[i].type == type && + gaEvent[i].posn.x == posn.x && + gaEvent[i].posn.y == posn.y && + gaEvent[i].posn.z == posn.z && + gaEvent[i].entityType == EVENT_ENTITY_NONE){ + gaEvent[i].timeout = CTimer::GetTimeInMilliseconds() + timeout; + return; + } + + for(i = ms_nFirstFreeSlotIndex; i < NUMEVENTS; i++) + if(gaEvent[i].type == EVENT_NULL){ + ms_nFirstFreeSlotIndex = i; + break; + } + if(i < NUMEVENTS){ + gaEvent[i].type = type; + gaEvent[i].entityType = EVENT_ENTITY_NONE; + gaEvent[i].timeout = CTimer::GetTimeInMilliseconds() + timeout; + gaEvent[i].posn = posn; + gaEvent[i].entityRef = 0; + if(type == EVENT_GUNSHOT) + gaEvent[i].state = EVENT_STATE_CLEAR; + else + gaEvent[i].state = EVENT_STATE_0; + } +} + +bool +CEventList::GetEvent(eEventType type, int32 *event) +{ + int i; + for(i = 0; i < NUMEVENTS; i++) + if(gaEvent[i].type == type){ + *event = i; + return true; + } + return false; +} + +void +CEventList::ClearEvent(int32 event) +{ + if(gaEvent[event].state != EVENT_STATE_CANDELETE) + gaEvent[event].state = EVENT_STATE_CLEAR; +} + +bool +CEventList::FindClosestEvent(eEventType type, CVector posn, int32 *event) +{ + int i; + float dist; + bool found = false; + float mindist = 60.0f; + + for(i = 0; i < NUMEVENTS; i++){ + if(gaEvent[i].type != type) + continue; + dist = (posn - gaEvent[i].posn).Magnitude(); + if(dist < mindist){ + mindist = dist; + found = true; + *event = i; + } + } + return found; +} + +void +CEventList::ReportCrimeForEvent(eEventType type, intptr crimeId, bool copsDontCare) +{ + eCrimeType crime; + switch(type){ + case EVENT_ASSAULT: crime = CRIME_HIT_PED; break; + case EVENT_RUN_REDLIGHT: crime = CRIME_RUN_REDLIGHT; break; + case EVENT_ASSAULT_POLICE: crime = CRIME_HIT_COP; break; + case EVENT_GUNSHOT: crime = CRIME_POSSESSION_GUN; break; + case EVENT_STEAL_CAR: crime = CRIME_STEAL_CAR; break; + case EVENT_HIT_AND_RUN: crime = CRIME_RUNOVER_PED; break; + case EVENT_HIT_AND_RUN_COP: crime = CRIME_RUNOVER_COP; break; + case EVENT_SHOOT_PED: crime = CRIME_SHOOT_PED; break; + case EVENT_SHOOT_COP: crime = CRIME_SHOOT_COP; break; + case EVENT_PED_SET_ON_FIRE: crime = CRIME_PED_BURNED; break; + case EVENT_COP_SET_ON_FIRE: crime = CRIME_COP_BURNED; break; + case EVENT_CAR_SET_ON_FIRE: crime = CRIME_VEHICLE_BURNED; break; + default: crime = CRIME_NONE; break; + } + + if(crime == CRIME_NONE) + return; + + CVector playerPedCoors = FindPlayerPed()->GetPosition(); + CVector playerCoors = FindPlayerCoors(); + + if(CWanted::WorkOutPolicePresence(playerCoors, 14.0f) != 0){ + FindPlayerPed()->m_pWanted->RegisterCrime_Immediately(crime, playerPedCoors, crimeId, copsDontCare); + FindPlayerPed()->m_pWanted->SetWantedLevelNoDrop(1); + }else + FindPlayerPed()->m_pWanted->RegisterCrime(crime, playerPedCoors, crimeId, copsDontCare); + + if(type == EVENT_ASSAULT_POLICE) + FindPlayerPed()->SetWantedLevelNoDrop(1); + if(type == EVENT_SHOOT_COP) + FindPlayerPed()->SetWantedLevelNoDrop(2); + +} diff --git a/src/core/EventList.h b/src/core/EventList.h new file mode 100644 index 0000000..4ced3a8 --- /dev/null +++ b/src/core/EventList.h @@ -0,0 +1,65 @@ +#pragma once + +class CEntity; +class CPed; + +enum eEventType +{ + EVENT_NULL, + EVENT_ASSAULT, + EVENT_RUN_REDLIGHT, + EVENT_ASSAULT_POLICE, + EVENT_GUNSHOT, + EVENT_INJURED_PED, + EVENT_DEAD_PED, + EVENT_FIRE, + EVENT_STEAL_CAR, + EVENT_HIT_AND_RUN, + EVENT_HIT_AND_RUN_COP, + EVENT_SHOOT_PED, + EVENT_SHOOT_COP, + EVENT_EXPLOSION, + EVENT_PED_SET_ON_FIRE, + EVENT_COP_SET_ON_FIRE, + EVENT_CAR_SET_ON_FIRE, + EVENT_ASSAULT_NASTYWEAPON, // not sure + EVENT_ICECREAM, + EVENT_ATM, + EVENT_SHOPSTALL, // used on graffitis + EVENT_LAST_EVENT +}; + +enum eEventEntity +{ + EVENT_ENTITY_NONE, + EVENT_ENTITY_PED, + EVENT_ENTITY_VEHICLE, + EVENT_ENTITY_OBJECT +}; + +struct CEvent +{ + eEventType type; + eEventEntity entityType; + int32 entityRef; + CPed *criminal; + CVector posn; + uint32 timeout; + int32 state; +}; + +class CEventList +{ + static int32 ms_nFirstFreeSlotIndex; +public: + static void Initialise(void); + static void Update(void); + static void RegisterEvent(eEventType type, eEventEntity entityType, CEntity *ent, CPed *criminal, int32 timeout); + static void RegisterEvent(eEventType type, CVector posn, int32 timeout); + static bool GetEvent(eEventType type, int32 *event); + static void ClearEvent(int32 event); + static bool FindClosestEvent(eEventType type, CVector posn, int32 *event); + static void ReportCrimeForEvent(eEventType type, intptr, bool); +}; + +extern CEvent gaEvent[NUMEVENTS]; \ No newline at end of file diff --git a/src/core/FileLoader.cpp b/src/core/FileLoader.cpp new file mode 100644 index 0000000..afa2a66 --- /dev/null +++ b/src/core/FileLoader.cpp @@ -0,0 +1,1852 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "Quaternion.h" +#include "ModelInfo.h" +#include "ModelIndices.h" +#include "TempColModels.h" +#include "VisibilityPlugins.h" +#include "FileMgr.h" +#include "HandlingMgr.h" +#include "CarCtrl.h" +#include "PedType.h" +#include "AnimManager.h" +#include "Game.h" +#include "RwHelper.h" +#include "NodeName.h" +#include "TxdStore.h" +#include "PathFind.h" +#include "ObjectData.h" +#include "DummyObject.h" +#include "World.h" +#include "Zones.h" +#include "ZoneCull.h" +#include "CdStream.h" +#include "FileLoader.h" +#include "MemoryHeap.h" + +char CFileLoader::ms_line[256]; + +const char* +GetFilename(const char *filename) +{ + char *s = strrchr((char*)filename, '\\'); + return s ? s+1 : filename; +} + +void +LoadingScreenLoadingFile(const char *filename) +{ + sprintf(gString, "Loading %s", GetFilename(filename)); + LoadingScreen("Loading the Game", gString, nil); +} + +void +CFileLoader::LoadLevel(const char *filename) +{ + int fd; + RwTexDictionary *savedTxd; + eLevelName savedLevel; + bool objectsLoaded; + char *line; + char txdname[64]; + + savedTxd = RwTexDictionaryGetCurrent(); + objectsLoaded = false; + savedLevel = CGame::currLevel; + if(savedTxd == nil){ + savedTxd = RwTexDictionaryCreate(); + RwTexDictionarySetCurrent(savedTxd); + } +#if GTA_VERSION <= GTA3_PS2_160 + CFileMgr::ChangeDir("\\DATA\\"); + fd = CFileMgr::OpenFile(filename, "r"); + CFileMgr::ChangeDir("\\"); +#else + fd = CFileMgr::OpenFile(filename, "r"); +#endif + assert(fd > 0); + + for(line = LoadLine(fd); line; line = LoadLine(fd)){ + if(*line == '#') + continue; + +#ifdef FIX_BUGS + if(strncmp(line, "EXIT", 4) == 0) +#else + if(strncmp(line, "EXIT", 9) == 0) +#endif + break; + + if(strncmp(line, "IMAGEPATH", 9) == 0){ + RwImageSetPath(line + 10); + }else if(strncmp(line, "TEXDICTION", 10) == 0){ + PUSH_MEMID(MEMID_TEXTURES); + strcpy(txdname, line+11); + LoadingScreenLoadingFile(txdname); + RwTexDictionary *txd = LoadTexDictionary(txdname); + AddTexDictionaries(savedTxd, txd); + RwTexDictionaryDestroy(txd); + POP_MEMID(); + }else if(strncmp(line, "COLFILE", 7) == 0){ + int level; + sscanf(line+8, "%d", &level); + CGame::currLevel = (eLevelName)level; + LoadingScreenLoadingFile(line+10); + LoadCollisionFile(line+10); + CGame::currLevel = savedLevel; + }else if(strncmp(line, "MODELFILE", 9) == 0){ + LoadingScreenLoadingFile(line + 10); + LoadModelFile(line + 10); + }else if(strncmp(line, "HIERFILE", 8) == 0){ + LoadingScreenLoadingFile(line + 9); + LoadClumpFile(line + 9); + }else if(strncmp(line, "IDE", 3) == 0){ + LoadingScreenLoadingFile(line + 4); + LoadObjectTypes(line + 4); + }else if(strncmp(line, "IPL", 3) == 0){ + if(!objectsLoaded){ + PUSH_MEMID(MEMID_DEF_MODELS); + CModelInfo::ConstructMloClumps(); + POP_MEMID(); + CObjectData::Initialise("DATA\\OBJECT.DAT"); + objectsLoaded = true; + } + PUSH_MEMID(MEMID_WORLD); + LoadingScreenLoadingFile(line + 4); + LoadScene(line + 4); + POP_MEMID(); + }else if(strncmp(line, "MAPZONE", 7) == 0){ + LoadingScreenLoadingFile(line + 8); + LoadMapZones(line + 8); + }else if(strncmp(line, "SPLASH", 6) == 0){ +#ifndef DISABLE_LOADING_SCREEN + LoadSplash(GetRandomSplashScreen()); +#endif +#ifndef GTA_PS2 + }else if(strncmp(line, "CDIMAGE", 7) == 0){ + CdStreamAddImage(line + 8); +#endif + } + } + + CFileMgr::CloseFile(fd); + RwTexDictionarySetCurrent(savedTxd); +} + +void +CFileLoader::LoadCollisionFromDatFile(int currlevel) +{ + int fd; + char *line; + + fd = CFileMgr::OpenFile(CGame::aDatFile, "r"); + assert(fd > 0); + + for(line = LoadLine(fd); line; line = LoadLine(fd)){ + if(*line == '#') + continue; + + if(strncmp(line, "COLFILE", 7) == 0){ + int level; + sscanf(line+8, "%d", &level); + if(currlevel == level) + LoadCollisionFile(line+10); + } + } + + CFileMgr::CloseFile(fd); +} + +char* +CFileLoader::LoadLine(int fd) +{ + int i; + char *line; + + if(CFileMgr::ReadLine(fd, ms_line, 256) == false) + return nil; + for(i = 0; ms_line[i] != '\0'; i++) + if(ms_line[i] < ' ' || ms_line[i] == ',') + ms_line[i] = ' '; + for(line = ms_line; *line <= ' ' && *line != '\0'; line++); + return line; +} + +RwTexDictionary* +CFileLoader::LoadTexDictionary(const char *filename) +{ + RwTexDictionary *txd; + RwStream *stream; + + txd = nil; + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + debug("Loading texture dictionary file %s\n", filename); + if(stream){ + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)) + txd = RwTexDictionaryGtaStreamRead(stream); + RwStreamClose(stream, nil); + } + if(txd == nil) + txd = RwTexDictionaryCreate(); + return txd; +} + +struct ColHeader +{ + uint32 ident; + uint32 size; +}; + +void +CFileLoader::LoadCollisionFile(const char *filename) +{ + int fd; + char modelname[24]; + CBaseModelInfo *mi; + ColHeader header; + + PUSH_MEMID(MEMID_COLLISION); + + debug("Loading collision file %s\n", filename); + fd = CFileMgr::OpenFile(filename, "rb"); + + while(CFileMgr::Read(fd, (char*)&header, sizeof(header))){ + assert(header.ident == 'LLOC'); + CFileMgr::Read(fd, (char*)work_buff, header.size); + memcpy(modelname, work_buff, 24); + + mi = CModelInfo::GetModelInfo(modelname, nil); + if(mi){ + if(mi->GetColModel()){ + LoadCollisionModel(work_buff+24, *mi->GetColModel(), modelname); + }else{ + CColModel *model = new CColModel; + LoadCollisionModel(work_buff+24, *model, modelname); + mi->SetColModel(model, true); + } + }else{ + debug("colmodel %s can't find a modelinfo\n", modelname); + } + } + + CFileMgr::CloseFile(fd); + + POP_MEMID(); +} + +void +CFileLoader::LoadCollisionModel(uint8 *buf, CColModel &model, char *modelname) +{ + int i; + + model.boundingSphere.radius = *(float*)(buf); + model.boundingSphere.center.x = *(float*)(buf+4); + model.boundingSphere.center.y = *(float*)(buf+8); + model.boundingSphere.center.z = *(float*)(buf+12); + model.boundingBox.min.x = *(float*)(buf+16); + model.boundingBox.min.y = *(float*)(buf+20); + model.boundingBox.min.z = *(float*)(buf+24); + model.boundingBox.max.x = *(float*)(buf+28); + model.boundingBox.max.y = *(float*)(buf+32); + model.boundingBox.max.z = *(float*)(buf+36); + model.numSpheres = *(int16*)(buf+40); + buf += 44; + if(model.numSpheres > 0){ + model.spheres = (CColSphere*)RwMalloc(model.numSpheres*sizeof(CColSphere)); + REGISTER_MEMPTR(&model.spheres); + for(i = 0; i < model.numSpheres; i++){ + model.spheres[i].Set(*(float*)buf, *(CVector*)(buf+4), buf[16], buf[17]); + buf += 20; + } + }else + model.spheres = nil; + + model.numLines = *(int16*)buf; + buf += 4; + if(model.numLines > 0){ + model.lines = (CColLine*)RwMalloc(model.numLines*sizeof(CColLine)); + REGISTER_MEMPTR(&model.lines); + for(i = 0; i < model.numLines; i++){ + model.lines[i].Set(*(CVector*)buf, *(CVector*)(buf+12)); + buf += 24; + } + }else + model.lines = nil; + + model.numBoxes = *(int16*)buf; + buf += 4; + if(model.numBoxes > 0){ + model.boxes = (CColBox*)RwMalloc(model.numBoxes*sizeof(CColBox)); + REGISTER_MEMPTR(&model.boxes); + for(i = 0; i < model.numBoxes; i++){ + model.boxes[i].Set(*(CVector*)buf, *(CVector*)(buf+12), buf[24], buf[25]); + buf += 28; + } + }else + model.boxes = nil; + + int32 numVertices = *(int16*)buf; + buf += 4; + if(numVertices > 0){ + model.vertices = (CompressedVector*)RwMalloc(numVertices*sizeof(CompressedVector)); + REGISTER_MEMPTR(&model.vertices); + for(i = 0; i < numVertices; i++){ + model.vertices[i].Set(*(float*)buf, *(float*)(buf+4), *(float*)(buf+8)); + if(Abs(*(float*)buf) >= 256.0f || + Abs(*(float*)(buf+4)) >= 256.0f || + Abs(*(float*)(buf+8)) >= 256.0f) + printf("%s:Collision volume too big\n", modelname); + buf += 12; + } + }else + model.vertices = nil; + + model.numTriangles = *(int16*)buf; + buf += 4; + if(model.numTriangles > 0){ + model.triangles = (CColTriangle*)RwMalloc(model.numTriangles*sizeof(CColTriangle)); + REGISTER_MEMPTR(&model.triangles); + for(i = 0; i < model.numTriangles; i++){ + model.triangles[i].Set(model.vertices, *(int32*)buf, *(int32*)(buf+4), *(int32*)(buf+8), buf[12], buf[13]); + buf += 16; + } + }else + model.triangles = nil; +} + +static void +GetNameAndLOD(char *nodename, char *name, int *n) +{ + char *underscore = nil; + for(char *s = nodename; *s != '\0'; s++){ + if(s[0] == '_' && (s[1] == 'l' || s[1] == 'L')) + underscore = s; + } + if(underscore){ + strncpy(name, nodename, underscore - nodename); + name[underscore - nodename] = '\0'; + *n = atoi(underscore + 2); + }else{ + strncpy(name, nodename, 24); + *n = 0; + } +} + +RpAtomic* +CFileLoader::FindRelatedModelInfoCB(RpAtomic *atomic, void *data) +{ + CSimpleModelInfo *mi; + char *nodename, name[24]; + int n; + RpClump *clump = (RpClump*)data; + + nodename = GetFrameNodeName(RpAtomicGetFrame(atomic)); + GetNameAndLOD(nodename, name, &n); + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(name, nil); + if(mi){ + assert(mi->IsSimple()); + mi->SetAtomic(n, atomic); + RpClumpRemoveAtomic(clump, atomic); + RpAtomicSetFrame(atomic, RwFrameCreate()); + CVisibilityPlugins::SetAtomicModelInfo(atomic, mi); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + }else{ + debug("Can't find Atomic %s\n", name); + } + + return atomic; +} + +#ifdef LIBRW +void +InitClump(RpClump *clump) +{ + RpClumpForAllAtomics(clump, ConvertPlatformAtomic, nil); +} +#else +#define InitClump(clump) +#endif + +void +CFileLoader::LoadModelFile(const char *filename) +{ + RwStream *stream; + RpClump *clump; + + debug("Loading model file %s\n", filename); + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + clump = RpClumpStreamRead(stream); + if(clump){ + InitClump(clump); + RpClumpForAllAtomics(clump, FindRelatedModelInfoCB, clump); + RpClumpDestroy(clump); + } + } + RwStreamClose(stream, nil); +} + +void +CFileLoader::LoadClumpFile(const char *filename) +{ + RwStream *stream; + RpClump *clump; + char *nodename, name[24]; + int n; + CClumpModelInfo *mi; + + debug("Loading model file %s\n", filename); + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + while(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + clump = RpClumpStreamRead(stream); + if(clump){ + nodename = GetFrameNodeName(RpClumpGetFrame(clump)); + GetNameAndLOD(nodename, name, &n); + mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(name, nil); + if(mi){ + InitClump(clump); + assert(mi->IsClump()); + mi->SetClump(clump); + }else + RpClumpDestroy(clump); + } + } + RwStreamClose(stream, nil); +} + +bool +CFileLoader::LoadClumpFile(RwStream *stream, uint32 id) +{ + RpClump *clump; + CClumpModelInfo *mi; + + if(!RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)) + return false; + clump = RpClumpStreamRead(stream); + if(clump == nil) + return false; + mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(id); + mi->SetClump(clump); + if (mi->GetModelType() == MITYPE_PED && id != 0 && RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)) { + // Read LOD ped + clump = RpClumpStreamRead(stream); + InitClump(clump); + if(clump){ + ((CPedModelInfo*)mi)->SetLowDetailClump(clump); + RpClumpDestroy(clump); + } + } + return true; +} + +bool +CFileLoader::StartLoadClumpFile(RwStream *stream, uint32 id) +{ + if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + printf("Start loading %s\n", CModelInfo::GetModelInfo(id)->GetModelName()); + return RpClumpGtaStreamRead1(stream); + }else{ + printf("FAILED\n"); + return false; + } +} + +bool +CFileLoader::FinishLoadClumpFile(RwStream *stream, uint32 id) +{ + RpClump *clump; + CClumpModelInfo *mi; + + printf("Finish loading %s\n", CModelInfo::GetModelInfo(id)->GetModelName()); + clump = RpClumpGtaStreamRead2(stream); + + if(clump){ + InitClump(clump); + mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(id); + mi->SetClump(clump); + return true; + }else{ + printf("FAILED\n"); + return false; + } +} + +CSimpleModelInfo *gpRelatedModelInfo; + +bool +CFileLoader::LoadAtomicFile(RwStream *stream, uint32 id) +{ + RpClump *clump; + + if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){ + clump = RpClumpStreamRead(stream); + if(clump == nil) + return false; + InitClump(clump); + gpRelatedModelInfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + RpClumpForAllAtomics(clump, SetRelatedModelInfoCB, clump); + RpClumpDestroy(clump); + } + return true; +} + +#ifdef HARDCODED_MODEL_FLAGS +char *DoubleSidedNames[] = { + "chnabankdoor", + "Security_Hut", + "Hospital_Sub", + "phonebooth1", + "trafficlight1", + "sub_roadbarrier", + "redlightbuild09", + "doublestreetlght1", + "doc_shedbig31", + "com_land_128", + "garage7", + "proj_garage01", + "buildingground2", + "buildingground3", + "ch_roof_kb", + "overpassind", + "casino", + "ind_land100", + "fuckedup_skewlbus", + "Police_Station_ind", + "flagsitaly", + "sidebarrier_gaz1", + "bar_barrier12", + "bar_barrier10b", + "sidebarrier_gaz2", + "doc_shedbig3", + "doc_shedbig4", + "verticalift_bridge", + "verticalift_bridg2", + "usdcrdlrbuild01", + "apairporthanger", + "apairporthangerA", + "porthangerclosed", + "redlightbuild13", + "doc_rave", + "const_woodfence", + "const_woodfence2", + "const_woodfence3", + "subfraightback01", + "subfraightback02", + "subfraightback03", + "subfraightback04", + "subind_build03", + "chinabanner1", + "chinabanner2", + "chinabanner3", + "chinabanner4", + "Pumpfirescape", + "Pumphouse", + "amcounder", + "barrel1", + "barrel2", + "barrel3", + "barrel4", + "com_1way50", + "com_1way20", + "overpasscom01", + "overpasscom02", + "overpasscom03", + "overpasscom04", + "overpass_comse", + "newdockbuilding", + "newdockbuilding2", + "policeballhall", + "fuzballdoor", + "ind_land106", + "PoliceBallSigns", + "amcoudet", + "rustship_structure", + "impexpgrgesub", + "ind_land128", + "fshfctry_dstryd", + "railtrax_bentl", + "railtrax_lo4b", + "railtrax_straight", + "railtrax_bentrb", + "railtrax_skew", + "newtrackaaa", + "railtrax_skew5", + // these they forgot: + "railtrax_skewp", + "railtrax_ske2b", + "railtrax_strtshort", + "railtrax_2b", + "railtrax_straightss", + "railtrax_bentr", + "ind_land125", + "salvstrans", + "bridge_liftsec", + "subsign1", + "carparkfence", + "newairportwall4", + "apair_terminal", + "Helipad", + "bar_barrier10", + "damissionfence", + "sub_floodlite", + "suburbbridge1", + "damfencing", + "demfence08", + "damfence07", + "damfence06", + "damfence05", + "damfence04", + "damfence03", + "damfence02", + "damfence01", + "Dam_pod2", + "Dam_pod1", + "columansion_wall", + "wrckdhse020", + "wrckdhse01", + "arc_bridge", + "gRD_overpass19kbc", + "gRD_overpass19bkb", + "gRD_overpass19kb", + "gRD_overpass18kb", + "road_under", + "com_roadkb23", + "com_roadkb22", + "nbbridgerda", + "nbbridgerdb", + "policetenkb1", + "block3_scraper2", + "Clnm_cthdrlfcde", + "broadwaybuild", + "combillboard03", + "com_park3b", + "com_docksaa", + "newdockbuilding2", + "com_roadkb22", + "sidebarrier_gaz2", + "tunnelsupport1", + "skyscrpunbuilt2", + "cons_buid02", + "rail_platformw", + "railtrax_bent1", + "nrailstepswest", + "building_fucked", + "franksclb02", + "salvsdetail", + "crgoshp01", + "shp_wlkway", + "bar_barriergate1", + "plnt_pylon01", + "fishfctory", + "doc_crane_cab", + "nrailsteps", + "iten_club01", + "mak_Watertank", + "basketballcourt" + "carlift01", + "carlift02", + "iten_chinatown4", + "iten_details7", + "ind_customroad002" + "ind_brgrd1way", + "ind_customroad060", + "ind_customroad002", + "ind_land108", + "ind_customroad004", + "ind_customroad003", + "nbbridgcabls01", + "sbwy_tunl_bit", + "sbwy_tunl_bend", + "sbwy_tunl_cstm11", + "sbwy_tunl_cstm10", + "sbwy_tunl_cstm9", + "sbwy_tunl_cstm8", + "sbwy_tunl_cstm7", + "sbwy_tunl_cstm6", + "sbwy_tunl_cstm5", + "sbwy_tunl_cstm4", + "sbwy_tunl_cstm3", + "sbwy_tunl_cstm2", + "sbwy_tunl_cstm1", + "tenmnt6ad", + "" + +}; +char *TreeNames[] = { + "coast_treepatch", + "comparknewtrees", + "comtreepatchprk", + "condotree01", + "condotree1", + "indatree03", + "indtreepatch5", + "indtreepatch06f", + "new_carprktrees", + "new_carprktrees4", + "newcoasttrees1", + "newcoasttrees2", + "newcoasttrees3", + "newtreepatch_sub", + "newtrees1_sub", + "newunitrepatch", + "pinetree_narrow", + "pinetree_wide", + "treencom2", + "treepatch", + "treepatch01_sub", + "treepatch02_sub", + "treepatch2", + "treepatch2b", + "treepatch03", + "treepatch03_sub", + "treepatch04_sub", + "treepatch05_sub", + "treepatch06_sub", + "treepatch07_sub", + "treepatch08_sub", + "treepatch09_sub", + "treepatch10_sub", + "treepatch11_sub", + "treepatch12_sub", + "treepatch13_sub", + "treepatch14_sub", + "treepatch15_sub", + "treepatch16_sub", + "treepatch17_sub", + "treepatch18_sub", + "treepatch19_sub", + "treepatch20_sub", + "treepatch21_sub", + "treepatch22_sub", + "treepatch23_sub", + "treepatch24_sub", + "treepatch25_sub", + "treepatch26_sub", + "treepatch27_sub", + "treepatch28_sub", + "treepatch29_sub", + "treepatch30_sub", + "treepatch31_sub", + "treepatch32_sub", + "treepatch33_sub", + "treepatch34_sub", + "treepatch35_sub", + "treepatch69", + "treepatch152_sub", + "treepatch153_sub", + "treepatch171_sub", + "treepatch172_sub", + "treepatch173_sub", + "treepatch212_sub", + "treepatch213_sub", + "treepatch214_sub", + "treepatcha", + "treepatchb", + "treepatchcomtop1", + "treepatchd", + "treepatche", + "treepatchh", + "treepatchindaa2", + "treepatchindnew", + "treepatchindnew2", + "treepatchk", + "treepatchkb4", + "treepatchkb5", + "treepatchkb6", + "treepatchkb7", + "treepatchkb9", + "treepatchl", + "treepatchm", + "treepatchnew_sub", + "treepatchttwrs", + "treesuni1", + "trepatchindaa1", + "veg_bush2", + "veg_bush14", + "veg_tree1", + "veg_tree3", + "veg_treea1", + "veg_treea3", + "veg_treeb1", + "veg_treenew01", + "veg_treenew03", + "veg_treenew05", + "veg_treenew06", + "veg_treenew08", + "veg_treenew09", + "veg_treenew10", + "veg_treenew16", + "veg_treenew17", + "vegclubtree01", + "vegclubtree02", + "vegclubtree03", + "vegpathtree", + "" +}; +char *OptimizedNames[] = { + "coast_treepatch", + "comparknewtrees", + "comtreepatchprk", + "indtreepatch5", + "indtreepatch06f", + "new_carprktrees", + "new_carprktrees4", + "newcoasttrees1", + "newcoasttrees2", + "newcoasttrees3", + "newtreepatch_sub", + "newtrees1_sub", + "newunitrepatch", + "treepatch", + "treepatch01_sub", + "treepatch02_sub", + "treepatch2", + "treepatch2b", + "treepatch03", + "treepatch03_sub", + "treepatch04_sub", + "treepatch05_sub", + "treepatch06_sub", + "treepatch07_sub", + "treepatch08_sub", + "treepatch09_sub", + "treepatch10_sub", + "treepatch11_sub", + "treepatch12_sub", + "treepatch13_sub", + "treepatch14_sub", + "treepatch15_sub", + "treepatch16_sub", + "treepatch17_sub", + "treepatch18_sub", + "treepatch19_sub", + "treepatch20_sub", + "treepatch21_sub", + "treepatch22_sub", + "treepatch23_sub", + "treepatch24_sub", + "treepatch25_sub", + "treepatch26_sub", + "treepatch27_sub", + "treepatch28_sub", + "treepatch29_sub", + "treepatch30_sub", + "treepatch31_sub", + "treepatch32_sub", + "treepatch33_sub", + "treepatch34_sub", + "treepatch35_sub", + "treepatch69", + "treepatch152_sub", + "treepatch153_sub", + "treepatch171_sub", + "treepatch172_sub", + "treepatch173_sub", + "treepatch212_sub", + "treepatch213_sub", + "treepatch214_sub", + "treepatcha", + "treepatchb", + "treepatchcomtop1", + "treepatchd", + "treepatche", + "treepatchh", + "treepatchindaa2", + "treepatchindnew", + "treepatchindnew2", + "treepatchk", + "treepatchkb4", + "treepatchkb5", + "treepatchkb6", + "treepatchkb7", + "treepatchkb9", + "treepatchl", + "treepatchm", + "treepatchnew_sub", + "treepatchttwrs", + "treesuni1", + "trepatchindaa1", + "combtm_treeshad01", + "combtm_treeshad02", + "combtm_treeshad03", + "combtm_treeshad04", + "combtm_treeshad05", + "combtm_treeshad06", + "comtop_tshad", + "comtop_tshad2", + "comtop_tshad3", + "comtop_tshad4", + "comtop_tshad5", + "comtop_tshad6", + "se_treeshad01", + "se_treeshad02", + "se_treeshad03", + "se_treeshad04", + "se_treeshad05", + "se_treeshad06", + "treeshads01", + "treeshads02", + "treeshads03", + "treeshads04", + "treeshads05", + "" +}; +// not from mobile +static bool +MatchModelName(char *name, char **list) +{ + int i; + char *s; + for(i = 0; *list[i] != '\0'; i++) + if(strncmp(name, "LOD", 3) == 0){ + if(!CGeneral::faststricmp(name+3, list[i]+3)) + return true; + }else{ + if(!CGeneral::faststricmp(name, list[i])) + return true; + } + return false; +} +#endif + +RpAtomic* +CFileLoader::SetRelatedModelInfoCB(RpAtomic *atomic, void *data) +{ + char *nodename, name[24]; + int n; + RpClump *clump = (RpClump*)data; + + nodename = GetFrameNodeName(RpAtomicGetFrame(atomic)); + GetNameAndLOD(nodename, name, &n); + gpRelatedModelInfo->SetAtomic(n, atomic); + RpClumpRemoveAtomic(clump, atomic); + RpAtomicSetFrame(atomic, RwFrameCreate()); + CVisibilityPlugins::SetAtomicModelInfo(atomic, gpRelatedModelInfo); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + return atomic; +} + +RpClump* +CFileLoader::LoadAtomicFile2Return(const char *filename) +{ + RwStream *stream; + RpClump *clump; + + clump = nil; + debug("Loading model file %s\n", filename); + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)) + clump = RpClumpStreamRead(stream); + if(clump) + InitClump(clump); + RwStreamClose(stream, nil); + return clump; +} + +static RwTexture* +MoveTexturesCB(RwTexture *texture, void *pData) +{ + RwTexDictionaryAddTexture((RwTexDictionary*)pData, texture); + return texture; +} + +void +CFileLoader::AddTexDictionaries(RwTexDictionary *dst, RwTexDictionary *src) +{ + RwTexDictionaryForAllTextures(src, MoveTexturesCB, dst); +} + +#define isLine3(l, a, b, c) ((l[0] == a) && (l[1] == b) && (l[2] == c)) +#define isLine4(l, a, b, c, d) ((l[0] == a) && (l[1] == b) && (l[2] == c) && (l[3] == d)) + +void +CFileLoader::LoadObjectTypes(const char *filename) +{ + enum { + NONE, + OBJS, + MLO, + TOBJ, + HIER, + CARS, + PEDS, + PATH, + TWODFX + }; + char *line; + int fd; + int section; + int pathIndex; + char pathTypeStr[20]; + int id, pathType; + int mlo; + + section = NONE; + pathIndex = -1; + mlo = 0; + debug("Loading object types from %s...\n", filename); + + fd = CFileMgr::OpenFile(filename, "rb"); + for(line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)){ + if(*line == '\0' || *line == '#') + continue; + + if(section == NONE){ + if(isLine4(line, 'o','b','j','s')) section = OBJS; + else if(isLine4(line, 't','o','b','j')) section = TOBJ; + else if(isLine4(line, 'h','i','e','r')) section = HIER; + else if(isLine4(line, 'c','a','r','s')) section = CARS; + else if(isLine4(line, 'p','e','d','s')) section = PEDS; + else if(isLine4(line, 'p','a','t','h')) section = PATH; + else if(isLine4(line, '2','d','f','x')) section = TWODFX; + }else if(isLine3(line, 'e','n','d')){ + section = section == MLO ? OBJS : NONE; + }else switch(section){ + case OBJS: + if(isLine3(line, 's','t','a')) + mlo = LoadMLO(line); + else + LoadObject(line); + break; + case MLO: + LoadMLOInstance(mlo, line); + break; + case TOBJ: + LoadTimeObject(line); + break; + case HIER: + LoadClumpObject(line); + break; + case CARS: + LoadVehicleObject(line); + break; + case PEDS: + LoadPedObject(line); + break; + case PATH: + if(pathIndex == -1){ + id = LoadPathHeader(line, pathTypeStr); + if(strcmp(pathTypeStr, "ped") == 0) + pathType = 1; + else if(strcmp(pathTypeStr, "car") == 0) + pathType = 0; + pathIndex = 0; + }else{ + if(pathType == 1) + LoadPedPathNode(line, id, pathIndex); + else if(pathType == 0) + LoadCarPathNode(line, id, pathIndex); + pathIndex++; + if(pathIndex == 12) + pathIndex = -1; + } + break; + case TWODFX: + Load2dEffect(line); + break; + } + } + CFileMgr::CloseFile(fd); + + for(id = 0; id < MODELINFOSIZE; id++){ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + if(mi && mi->IsSimple()) + mi->SetupBigBuilding(); + } +} + +void +SetModelInfoFlags(CSimpleModelInfo *mi, uint32 flags) +{ + mi->m_normalCull = !!(flags & 1); + mi->m_noFade = !!(flags & 2); + mi->m_drawLast = !!(flags & (4|8)); + mi->m_additive = !!(flags & 8); + mi->m_isSubway = !!(flags & 0x10); + mi->m_ignoreLight = !!(flags & 0x20); + mi->m_noZwrite = !!(flags & 0x40); +#ifdef EXTRA_MODEL_FLAGS + // same flag values as SA + mi->m_bIsTree = !!(flags & 0x2000); + mi->m_bIsDoubleSided = !!(flags & 0x200000); + // new value otherwise unused + mi->m_bCanBeIgnored = !!(flags & 0x10000); + +#ifdef HARDCODED_MODEL_FLAGS + // mobile sets these flags in CFileLoader::SetRelatedModelInfoCB, but that's stupid + if(MatchModelName(mi->GetModelName(), DoubleSidedNames)) mi->m_bIsDoubleSided = true; + if(MatchModelName(mi->GetModelName(), TreeNames)) mi->m_bIsTree = true; + if(MatchModelName(mi->GetModelName(), OptimizedNames)) mi->m_bCanBeIgnored = true; +#endif + +#endif +} + +void +CFileLoader::LoadObject(const char *line) +{ + int id, numObjs; + char model[24], txd[24]; + float dist[3]; + uint32 flags; + int damaged; + CSimpleModelInfo *mi; + + if(sscanf(line, "%d %s %s %d", &id, model, txd, &numObjs) != 4) + return; + + switch(numObjs){ + case 1: + sscanf(line, "%d %s %s %d %f %d", + &id, model, txd, &numObjs, &dist[0], &flags); + damaged = 0; + break; + case 2: + sscanf(line, "%d %s %s %d %f %f %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &flags); + damaged = dist[0] < dist[1] ? // Are distances increasing? + 0 : // Yes, no damage model + 1; // No, 1 is damaged + break; + case 3: + sscanf(line, "%d %s %s %d %f %f %f %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &dist[2], &flags); + damaged = dist[0] < dist[1] ? // Are distances increasing? + (dist[1] < dist[2] ? 0 : 2) : // Yes, only 2 can still be a damage model + 1; // No, 1 and 2 are damaged + break; + } + + mi = CModelInfo::AddSimpleModel(id); + mi->SetModelName(model); + mi->SetNumAtomics(numObjs); + mi->SetLodDistances(dist); + SetModelInfoFlags(mi, flags); + mi->m_firstDamaged = damaged; + mi->SetTexDictionary(txd); + MatchModelString(model, id); +} + +int +CFileLoader::LoadMLO(const char *line) +{ + char smth[8]; + char name[24]; + int modelIndex; + float drawDist; + + sscanf(line, "%s %s %d %f", smth, name, &modelIndex, &drawDist); + CMloModelInfo *minfo = CModelInfo::AddMloModel(modelIndex); + minfo->SetModelName(name); + minfo->drawDist = drawDist; + int instId = CModelInfo::GetMloInstanceStore().allocPtr; + minfo->firstInstance = instId; + minfo->lastInstance = instId; + minfo->SetTexDictionary("generic"); + return modelIndex; +} + +void +CFileLoader::LoadMLOInstance(int id, const char *line) +{ + char name[24]; + RwV3d pos, scale, rot; + float angle; + int modelIndex; + + CMloModelInfo *minfo = (CMloModelInfo*)CModelInfo::GetModelInfo(id); + sscanf(line, "%d %s %f %f %f %f %f %f %f %f %f %f", + &modelIndex, + name, + &pos.x, &pos.y, &pos.z, + &scale.x, &scale.y, &scale.z, + &rot.x, &rot.y, &rot.z, + &angle); + float rad = Acos(angle) * 2.0f; + CInstance *inst = CModelInfo::GetMloInstanceStore().Alloc(); + minfo->lastInstance++; + + RwMatrix *matrix = RwMatrixCreate(); + RwMatrixScale(matrix, &scale, rwCOMBINEREPLACE); + RwMatrixRotate(matrix, &rot, -RADTODEG(rad), rwCOMBINEPOSTCONCAT); + RwMatrixTranslate(matrix, &pos, rwCOMBINEPOSTCONCAT); + + inst->GetMatrix() = CMatrix(matrix); + inst->GetMatrix().UpdateRW(); + + inst->m_modelIndex = modelIndex; + RwMatrixDestroy(matrix); +} + +void +CFileLoader::LoadTimeObject(const char *line) +{ + int id, numObjs; + char model[24], txd[24]; + float dist[3]; + uint32 flags; + int timeOn, timeOff; + int damaged; + CTimeModelInfo *mi, *other; + + if(sscanf(line, "%d %s %s %d", &id, model, txd, &numObjs) != 4) + return; + + switch(numObjs){ + case 1: + sscanf(line, "%d %s %s %d %f %d %d %d", + &id, model, txd, &numObjs, &dist[0], &flags, &timeOn, &timeOff); + damaged = 0; + break; + case 2: + sscanf(line, "%d %s %s %d %f %f %d %d %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &flags, &timeOn, &timeOff); + damaged = dist[0] < dist[1] ? // Are distances increasing? + 0 : // Yes, no damage model + 1; // No, 1 is damaged + break; + case 3: + sscanf(line, "%d %s %s %d %f %f %f %d %d %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &dist[2], &flags, &timeOn, &timeOff); + damaged = dist[0] < dist[1] ? // Are distances increasing? + (dist[1] < dist[2] ? 0 : 2) : // Yes, only 2 can still be a damage model + 1; // No, 1 and 2 are damaged + break; + } + + mi = CModelInfo::AddTimeModel(id); + mi->SetModelName(model); + mi->SetNumAtomics(numObjs); + mi->SetLodDistances(dist); + SetModelInfoFlags(mi, flags); + mi->m_firstDamaged = damaged; + mi->SetTimes(timeOn, timeOff); + mi->SetTexDictionary(txd); + other = mi->FindOtherTimeModel(); + if(other) + other->SetOtherTimeModel(id); + MatchModelString(model, id); +} + +void +CFileLoader::LoadClumpObject(const char *line) +{ + int id; + char model[24], txd[24]; + CClumpModelInfo *mi; + + if(sscanf(line, "%d %s %s", &id, model, txd) == 3){ + mi = CModelInfo::AddClumpModel(id); + mi->SetModelName(model); + mi->SetTexDictionary(txd); + mi->SetColModel(&CTempColModels::ms_colModelBBox); + } +} + +void +CFileLoader::LoadVehicleObject(const char *line) +{ + int id; + char model[24], txd[24]; + char type[8], handlingId[16], gamename[32], vehclass[12]; + uint32 frequency, comprules; + int32 level, misc; + float wheelScale; + CVehicleModelInfo *mi; + char *p; + + sscanf(line, "%d %s %s %s %s %s %s %d %d %x %d %f", + &id, model, txd, + type, handlingId, gamename, vehclass, + &frequency, &level, &comprules, &misc, &wheelScale); + + mi = CModelInfo::AddVehicleModel(id); + mi->SetModelName(model); + mi->SetTexDictionary(txd); + for(p = gamename; *p; p++) + if(*p == '_') *p = ' '; + strcpy(mi->m_gameName, gamename); + mi->m_level = level; + mi->m_compRules = comprules; + + if(strcmp(type, "car") == 0){ + mi->m_wheelId = misc; + mi->m_wheelScale = wheelScale; + mi->m_vehicleType = VEHICLE_TYPE_CAR; + }else if(strcmp(type, "boat") == 0){ + mi->m_vehicleType = VEHICLE_TYPE_BOAT; + }else if(strcmp(type, "train") == 0){ + mi->m_vehicleType = VEHICLE_TYPE_TRAIN; + }else if(strcmp(type, "heli") == 0){ + mi->m_vehicleType = VEHICLE_TYPE_HELI; + }else if(strcmp(type, "plane") == 0){ + mi->m_planeLodId = misc; + mi->m_wheelScale = 1.0f; + mi->m_vehicleType = VEHICLE_TYPE_PLANE; + }else if(strcmp(type, "bike") == 0){ + mi->m_bikeSteerAngle = misc; + mi->m_wheelScale = wheelScale; + mi->m_vehicleType = VEHICLE_TYPE_BIKE; + }else + assert(0); + + mi->m_handlingId = mod_HandlingManager.GetHandlingId(handlingId); + + // Well this is kinda dumb.... + if(strcmp(vehclass, "poorfamily") == 0){ + mi->m_vehicleClass = CCarCtrl::POOR; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::POOR); + }else if(strcmp(vehclass, "richfamily") == 0){ + mi->m_vehicleClass = CCarCtrl::RICH; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::RICH); + }else if(strcmp(vehclass, "executive") == 0){ + mi->m_vehicleClass = CCarCtrl::EXEC; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::EXEC); + }else if(strcmp(vehclass, "worker") == 0){ + mi->m_vehicleClass = CCarCtrl::WORKER; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::WORKER); + }else if(strcmp(vehclass, "special") == 0){ + mi->m_vehicleClass = CCarCtrl::SPECIAL; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::SPECIAL); + }else if(strcmp(vehclass, "big") == 0){ + mi->m_vehicleClass = CCarCtrl::BIG; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::BIG); + }else if(strcmp(vehclass, "taxi") == 0){ + mi->m_vehicleClass = CCarCtrl::TAXI; + while(frequency-- > 0) + CCarCtrl::AddToCarArray(id, CCarCtrl::TAXI); + } +} + +void +CFileLoader::LoadPedObject(const char *line) +{ + int id; + char model[24], txd[24]; + char pedType[24], pedStats[24], animGroup[24]; + int carsCanDrive; + CPedModelInfo *mi; + int animGroupId; + + if(sscanf(line, "%d %s %s %s %s %s %x", + &id, model, txd, + pedType, pedStats, animGroup, &carsCanDrive) != 7) + return; + + mi = CModelInfo::AddPedModel(id); + mi->SetModelName(model); + mi->SetTexDictionary(txd); + mi->SetColModel(&CTempColModels::ms_colModelPed1); + mi->m_pedType = CPedType::FindPedType(pedType); + mi->m_pedStatType = CPedStats::GetPedStatType(pedStats); + for(animGroupId = 0; animGroupId < NUM_ANIM_ASSOC_GROUPS; animGroupId++) + if(strcmp(animGroup, CAnimManager::GetAnimGroupName((AssocGroupId)animGroupId)) == 0) + break; + mi->m_animGroup = animGroupId; + mi->m_carsCanDrive = carsCanDrive; + + // ??? + CModelInfo::GetModelInfo(MI_LOPOLYGUY)->SetColModel(&CTempColModels::ms_colModelPed1); +} + +int +CFileLoader::LoadPathHeader(const char *line, char *type) +{ + int id; + char modelname[32]; + + sscanf(line, "%s %d %s", type, &id, modelname); + return id; +} + +void +CFileLoader::LoadPedPathNode(const char *line, int id, int node) +{ + int type, next, cross; + float x, y, z, width; + + sscanf(line, "%d %d %d %f %f %f %f", &type, &next, &cross, &x, &y, &z, &width); + ThePaths.StoreNodeInfoPed(id, node, type, next, x, y, z, 0, !!cross); +} + +void +CFileLoader::LoadCarPathNode(const char *line, int id, int node) +{ + int type, next, cross, numLeft, numRight; + float x, y, z, width; + + sscanf(line, "%d %d %d %f %f %f %f %d %d", &type, &next, &cross, &x, &y, &z, &width, &numLeft, &numRight); + ThePaths.StoreNodeInfoCar(id, node, type, next, x, y, z, 0, numLeft, numRight); +} + + +void +CFileLoader::Load2dEffect(const char *line) +{ + int id, r, g, b, a, type; + float x, y, z; + char corona[32], shadow[32]; + int shadowIntens, lightType, roadReflection, flare, flags, probability; + CBaseModelInfo *mi; + C2dEffect *effect; + char *p; + + sscanf(line, "%d %f %f %f %d %d %d %d %d", &id, &x, &y, &z, &r, &g, &b, &a, &type); + + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle")); + + mi = CModelInfo::GetModelInfo(id); + effect = CModelInfo::Get2dEffectStore().Alloc(); + mi->Add2dEffect(effect); + effect->pos = CVector(x, y, z); + effect->col = CRGBA(r, g, b, a); + effect->type = type; + + switch(effect->type){ + case EFFECT_LIGHT: + while(*line++ != '"'); + p = corona; + while(*line != '"') *p++ = *line++; + *p = '\0'; + line++; + + while(*line++ != '"'); + p = shadow; + while(*line != '"') *p++ = *line++; + *p = '\0'; + line++; + + sscanf(line, "%f %f %f %f %d %d %d %d %d", + &effect->light.dist, + &effect->light.range, + &effect->light.size, + &effect->light.shadowSize, + &shadowIntens, &lightType, &roadReflection, &flare, &flags); + effect->light.corona = RwTextureRead(corona, nil); + effect->light.shadow = RwTextureRead(shadow, nil); + effect->light.shadowIntensity = shadowIntens; + effect->light.lightType = lightType; + effect->light.roadReflection = roadReflection; + effect->light.flareType = flare; + + if(flags & LIGHTFLAG_FOG_ALWAYS) + flags &= ~LIGHTFLAG_FOG_NORMAL; + effect->light.flags = flags; + break; + + case EFFECT_PARTICLE: + sscanf(line, "%d %f %f %f %d %d %d %d %d %d %f %f %f %f", + &id, &x, &y, &z, &r, &g, &b, &a, &type, + &effect->particle.particleType, + &effect->particle.dir.x, + &effect->particle.dir.y, + &effect->particle.dir.z, + &effect->particle.scale); + break; + + case EFFECT_ATTRACTOR: + sscanf(line, "%d %f %f %f %d %d %d %d %d %d %f %f %f %d", + &id, &x, &y, &z, &r, &g, &b, &a, &type, + &flags, + &effect->attractor.dir.x, + &effect->attractor.dir.y, + &effect->attractor.dir.z, + &probability); + effect->attractor.type = flags; +#ifdef FIX_BUGS + effect->attractor.probability = Clamp(probability, 0, 255); +#else + effect->attractor.probability = probability; +#endif + break; + } + + CTxdStore::PopCurrentTxd(); +} + +void +CFileLoader::LoadScene(const char *filename) +{ + enum { + NONE, + INST, + ZONE, + CULL, + PICK, + PATH, + }; + char *line; + int fd; + int section; + int pathIndex; + char pathTypeStr[20]; + + section = NONE; + pathIndex = -1; + debug("Creating objects from %s...\n", filename); + + fd = CFileMgr::OpenFile(filename, "rb"); + for(line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)){ + if(*line == '\0' || *line == '#') + continue; + + if(section == NONE){ + if(isLine4(line, 'i','n','s','t')) section = INST; + else if(isLine4(line, 'z','o','n','e')) section = ZONE; + else if(isLine4(line, 'c','u','l','l')) section = CULL; + else if(isLine4(line, 'p','i','c','k')) section = PICK; + else if(isLine4(line, 'p','a','t','h')) section = PATH; + }else if(isLine3(line, 'e','n','d')){ + section = NONE; + }else switch(section){ + case INST: + LoadObjectInstance(line); + break; + case ZONE: + LoadZone(line); + break; + case CULL: + LoadCullZone(line); + break; + case PICK: + // unused + LoadPickup(line); + break; + case PATH: + // unfinished in the game + if(pathIndex == -1){ + LoadPathHeader(line, pathTypeStr); + strcmp(pathTypeStr, "ped"); + // type not set + pathIndex = 0; + }else{ + // nodes not loaded + pathIndex++; + if(pathIndex == 12) + pathIndex = -1; + } + break; + } + } + CFileMgr::CloseFile(fd); + + debug("Finished loading IPL\n"); +} + +void +CFileLoader::LoadObjectInstance(const char *line) +{ + int id; + char name[24]; + RwV3d trans, scale, axis; + float angle; + CSimpleModelInfo *mi; + RwMatrix *xform; + CEntity *entity; + if(sscanf(line, "%d %s %f %f %f %f %f %f %f %f %f %f", + &id, name, + &trans.x, &trans.y, &trans.z, + &scale.x, &scale.y, &scale.z, + &axis.x, &axis.y, &axis.z, &angle) != 12) + return; + + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + if(mi == nil) + return; + assert(mi->IsSimple()); + + angle = -RADTODEG(2.0f * acosf(angle)); + xform = RwMatrixCreate(); + RwMatrixRotate(xform, &axis, angle, rwCOMBINEREPLACE); + RwMatrixTranslate(xform, &trans, rwCOMBINEPOSTCONCAT); + + if(mi->GetObjectID() == -1){ + if(ThePaths.IsPathObject(id)){ + entity = new CTreadable; + ThePaths.RegisterMapObject((CTreadable*)entity); + }else + entity = new CBuilding; + entity->SetModelIndexNoCreate(id); + entity->GetMatrix() = CMatrix(xform); + entity->m_level = CTheZones::GetLevelFromPosition(&entity->GetPosition()); + if(mi->IsSimple()){ + if(mi->m_isBigBuilding) + entity->SetupBigBuilding(); + if(mi->m_isSubway) + entity->bIsSubway = true; + } + if(mi->GetLargestLodDistance() < 2.0f) + entity->bIsVisible = false; + CWorld::Add(entity); + }else{ + entity = new CDummyObject; + entity->SetModelIndexNoCreate(id); + entity->GetMatrix() = CMatrix(xform); + CWorld::Add(entity); + if(IsGlass(entity->GetModelIndex())) + entity->bIsVisible = false; + entity->m_level = CTheZones::GetLevelFromPosition(&entity->GetPosition()); + } + + RwMatrixDestroy(xform); +} + +void +CFileLoader::LoadZone(const char *line) +{ + char name[24]; + int type, level; + float minx, miny, minz; + float maxx, maxy, maxz; + + if(sscanf(line, "%s %d %f %f %f %f %f %f %d", name, &type, &minx, &miny, &minz, &maxx, &maxy, &maxz, &level) == 9) + CTheZones::CreateZone(name, (eZoneType)type, minx, miny, minz, maxx, maxy, maxz, (eLevelName)level); +} + +void +CFileLoader::LoadCullZone(const char *line) +{ + CVector pos; + float minx, miny, minz; + float maxx, maxy, maxz; + int flags; + int wantedLevelDrop = 0; + + sscanf(line, "%f %f %f %f %f %f %f %f %f %d %d", + &pos.x, &pos.y, &pos.z, + &minx, &miny, &minz, + &maxx, &maxy, &maxz, + &flags, &wantedLevelDrop); + CCullZones::AddCullZone(pos, minx, maxx, miny, maxy, minz, maxz, flags, wantedLevelDrop); +} + +// unused +void +CFileLoader::LoadPickup(const char *line) +{ + int id; + float x, y, z; + + sscanf(line, "%d %f %f %f", &id, &x, &y, &z); +} + +void +CFileLoader::LoadMapZones(const char *filename) +{ + enum { + NONE, + INST, + ZONE, + CULL, + PICK, + PATH, + }; + char *line; + int fd; + int section; + + section = NONE; + debug("Creating zones from %s...\n", filename); + + fd = CFileMgr::OpenFile(filename, "rb"); + for(line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)){ + if(*line == '\0' || *line == '#') + continue; + + if(section == NONE){ + if(isLine4(line, 'z','o','n','e')) section = ZONE; + }else if(isLine3(line, 'e','n','d')){ + section = NONE; + }else switch(section){ + case ZONE: { + char name[24]; + int type, level; + float minx, miny, minz; + float maxx, maxy, maxz; + if(sscanf(line, "%s %d %f %f %f %f %f %f %d", + name, &type, + &minx, &miny, &minz, + &maxx, &maxy, &maxz, + &level) == 9) + CTheZones::CreateMapZone(name, (eZoneType)type, minx, miny, minz, maxx, maxy, maxz, (eLevelName)level); + } + break; + } + } + CFileMgr::CloseFile(fd); + + debug("Finished loading IPL\n"); +} + +void +CFileLoader::ReloadPaths(const char *filename) +{ + enum { + NONE, + PATH, + }; + char *line; + int section = NONE; + int id, pathType, pathIndex = -1; + char pathTypeStr[20]; + debug("Reloading paths from %s...\n", filename); + + int fd = CFileMgr::OpenFile(filename, "r"); + for (line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)) { + if (*line == '\0' || *line == '#') + continue; + + if (section == NONE) { + if (isLine4(line, 'p','a','t','h')) { + section = PATH; + ThePaths.AllocatePathFindInfoMem(4500); + } + } else if (isLine3(line, 'e','n','d')) { + section = NONE; + } else { + switch (section) { + case PATH: + if (pathIndex == -1) { + id = LoadPathHeader(line, pathTypeStr); + if (strcmp(pathTypeStr, "ped") == 0) + pathType = 1; + else if (strcmp(pathTypeStr, "car") == 0) + pathType = 0; + pathIndex = 0; + } else { + if (pathType == 1) + LoadPedPathNode(line, id, pathIndex); + else if (pathType == 0) + LoadCarPathNode(line, id, pathIndex); + pathIndex++; + if (pathIndex == 12) + pathIndex = -1; + } + break; + default: + break; + } + } + } + CFileMgr::CloseFile(fd); +} + +void +CFileLoader::ReloadObjectTypes(const char *filename) +{ + enum { + NONE, + OBJS, + TOBJ, + TWODFX + }; + char *line; + int section = NONE; + CModelInfo::ReInit2dEffects(); + debug("Reloading object types from %s...\n", filename); + + CFileMgr::ChangeDir("\\DATA\\MAPS\\"); + int fd = CFileMgr::OpenFile(filename, "r"); + CFileMgr::ChangeDir("\\"); + for (line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)) { + if (*line == '\0' || *line == '#') + continue; + + if (section == NONE) { + if (isLine4(line, 'o','b','j','s')) section = OBJS; + else if (isLine4(line, 't','o','b','j')) section = TOBJ; + else if (isLine4(line, '2','d','f','x')) section = TWODFX; + } else if (isLine3(line, 'e','n','d')) { + section = NONE; + } else { + switch (section) { + case OBJS: + case TOBJ: + ReloadObject(line); + break; + case TWODFX: + Load2dEffect(line); + break; + default: + break; + } + } + } + CFileMgr::CloseFile(fd); +} + +void +CFileLoader::ReloadObject(const char *line) +{ + int id, numObjs; + char model[24], txd[24]; + float dist[3]; + uint32 flags; + CSimpleModelInfo *mi; + + if(sscanf(line, "%d %s %s %d", &id, model, txd, &numObjs) != 4) + return; + + switch(numObjs){ + case 1: + sscanf(line, "%d %s %s %d %f %d", + &id, model, txd, &numObjs, &dist[0], &flags); + break; + case 2: + sscanf(line, "%d %s %s %d %f %f %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &flags); + break; + case 3: + sscanf(line, "%d %s %s %d %f %f %f %d", + &id, model, txd, &numObjs, &dist[0], &dist[1], &dist[2], &flags); + break; + } + + mi = (CSimpleModelInfo*) CModelInfo::GetModelInfo(id); + if ( +#ifdef FIX_BUGS + mi && +#endif + mi->GetModelType() == MITYPE_SIMPLE && !strcmp(mi->GetModelName(), model) && mi->m_numAtomics == numObjs) { + mi->SetLodDistances(dist); + SetModelInfoFlags(mi, flags); + } else { + printf("Can't reload %s\n", model); + } +} + +// unused mobile function - crashes +void +CFileLoader::ReLoadScene(const char *filename) +{ + char *line; + CFileMgr::ChangeDir("\\DATA\\"); + int fd = CFileMgr::OpenFile(filename, "r"); + CFileMgr::ChangeDir("\\"); + + for (line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)) { + if (*line == '#') + continue; + +#ifdef FIX_BUGS + if (strncmp(line, "EXIT", 4) == 0) +#else + if (strncmp(line, "EXIT", 9) == 0) +#endif + break; + + if (strncmp(line, "IDE", 3) == 0) { + LoadObjectTypes(line + 4); + } + } + CFileMgr::CloseFile(fd); +} diff --git a/src/core/FileLoader.h b/src/core/FileLoader.h new file mode 100644 index 0000000..87b8fe6 --- /dev/null +++ b/src/core/FileLoader.h @@ -0,0 +1,49 @@ +#pragma once + +class CFileLoader +{ + static char ms_line[256]; +public: + static void LoadLevel(const char *filename); + static void LoadCollisionFromDatFile(int currlevel); + static char *LoadLine(int fd); + static RwTexDictionary *LoadTexDictionary(const char *filename); + static void LoadCollisionFile(const char *filename); + static void LoadCollisionModel(uint8 *buf, struct CColModel &model, char *name); + static void LoadModelFile(const char *filename); + static RpAtomic *FindRelatedModelInfoCB(RpAtomic *atomic, void *data); + static void LoadClumpFile(const char *filename); + static bool LoadClumpFile(RwStream *stream, uint32 id); + static bool StartLoadClumpFile(RwStream *stream, uint32 id); + static bool FinishLoadClumpFile(RwStream *stream, uint32 id); + static bool LoadAtomicFile(RwStream *stream, uint32 id); + static RpAtomic *SetRelatedModelInfoCB(RpAtomic *atomic, void *data); + static RpClump *LoadAtomicFile2Return(const char *filename); + static void AddTexDictionaries(RwTexDictionary *dst, RwTexDictionary *src); + + static void LoadObjectTypes(const char *filename); + static void LoadObject(const char *line); + static int LoadMLO(const char *line); + static void LoadMLOInstance(int id, const char *line); + static void LoadTimeObject(const char *line); + static void LoadClumpObject(const char *line); + static void LoadVehicleObject(const char *line); + static void LoadPedObject(const char *line); + static int LoadPathHeader(const char *line, char *type); + static void LoadPedPathNode(const char *line, int id, int node); + static void LoadCarPathNode(const char *line, int id, int node); + static void Load2dEffect(const char *line); + + static void LoadScene(const char *filename); + static void LoadObjectInstance(const char *line); + static void LoadZone(const char *line); + static void LoadCullZone(const char *line); + static void LoadPickup(const char *line); + + static void LoadMapZones(const char *filename); + + static void ReloadPaths(const char *filename); + static void ReloadObjectTypes(const char *filename); + static void ReloadObject(const char *line); + static void ReLoadScene(const char *filename); // unused mobile function +}; diff --git a/src/core/FileMgr.cpp b/src/core/FileMgr.cpp new file mode 100644 index 0000000..32aa404 --- /dev/null +++ b/src/core/FileMgr.cpp @@ -0,0 +1,313 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#ifdef _WIN32 +#include +#endif +#include "common.h" +#include "crossplatform.h" + +#include "FileMgr.h" + +const char *_psGetUserFilesFolder(); + +/* + * Windows FILE is BROKEN for GTA. + * + * We need to support mapping between LF and CRLF for text files + * but we do NOT want to end the file at the first sight of a SUB character. + * So here is a simple implementation of a FILE interface that works like GTA expects. + */ + +struct myFILE +{ + bool isText; + FILE *file; +}; + +#define NUMFILES 20 +static myFILE myfiles[NUMFILES]; + + +#if !defined(_WIN32) +#include +#include +#include +#define _getcwd getcwd + +// Case-insensitivity on linux (from https://github.com/OneSadCookie/fcaseopen) +void mychdir(char const *path) +{ + char* r = casepath(path, false); + if (r) { + chdir(r); + free(r); + } else { + errno = ENOENT; + } +} +#else +#define mychdir chdir +#endif + +/* Force file to open as binary but remember if it was text mode */ +static int +myfopen(const char *filename, const char *mode) +{ + int fd; + char realmode[10], *p; + + for(fd = 1; fd < NUMFILES; fd++) + if(myfiles[fd].file == nil) + goto found; + return 0; // no free fd +found: + myfiles[fd].isText = strchr(mode, 'b') == nil; + p = realmode; + while(*mode) + if(*mode != 't' && *mode != 'b') + *p++ = *mode++; + else + mode++; + *p++ = 'b'; + *p = '\0'; + + myfiles[fd].file = fcaseopen(filename, realmode); + if(myfiles[fd].file == nil) + return 0; + return fd; +} + +static int +myfclose(int fd) +{ + int ret; + assert(fd < NUMFILES); + if(myfiles[fd].file){ + ret = fclose(myfiles[fd].file); + myfiles[fd].file = nil; + return ret; + } + return EOF; +} + +static int +myfgetc(int fd) +{ + int c; + c = fgetc(myfiles[fd].file); + if(myfiles[fd].isText && c == 015){ + /* translate CRLF to LF */ + c = fgetc(myfiles[fd].file); + if(c == 012) + return c; + ungetc(c, myfiles[fd].file); + return 015; + } + return c; +} + +static int +myfputc(int c, int fd) +{ + /* translate LF to CRLF */ + if(myfiles[fd].isText && c == 012) + fputc(015, myfiles[fd].file); + return fputc(c, myfiles[fd].file); +} + +static char* +myfgets(char *buf, int len, int fd) +{ + int c; + char *p; + + p = buf; + len--; // NUL byte + while(len--){ + c = myfgetc(fd); + if(c == EOF){ + if(p == buf) + return nil; + break; + } + *p++ = c; + if(c == '\n') + break; + } + *p = '\0'; + return buf; +} + +static size_t +myfread(void *buf, size_t elt, size_t n, int fd) +{ + if(myfiles[fd].isText){ + unsigned char *p; + size_t i; + int c; + + n *= elt; + p = (unsigned char*)buf; + for(i = 0; i < n; i++){ + c = myfgetc(fd); + if(c == EOF) + break; + *p++ = (unsigned char)c; + } + return i / elt; + } + return fread(buf, elt, n, myfiles[fd].file); +} + +static size_t +myfwrite(void *buf, size_t elt, size_t n, int fd) +{ + if(myfiles[fd].isText){ + unsigned char *p; + size_t i; + int c; + + n *= elt; + p = (unsigned char*)buf; + for(i = 0; i < n; i++){ + c = *p++; + myfputc(c, fd); + if(feof(myfiles[fd].file)) // is this right? + break; + } + return i / elt; + } + return fwrite(buf, elt, n, myfiles[fd].file); +} + +static int +myfseek(int fd, long offset, int whence) +{ + return fseek(myfiles[fd].file, offset, whence); +} + +static int +myfeof(int fd) +{ + return feof(myfiles[fd].file); +// return ferror(myfiles[fd].file); +} + + +char CFileMgr::ms_rootDirName[128] = {'\0'}; +char CFileMgr::ms_dirName[128]; + +void +CFileMgr::Initialise(void) +{ + _getcwd(ms_rootDirName, 128); + strcat(ms_rootDirName, "\\"); +} + +void +CFileMgr::ChangeDir(const char *dir) +{ + if(*dir == '\\'){ + strcpy(ms_dirName, ms_rootDirName); + dir++; + } + if(*dir != '\0'){ + strcat(ms_dirName, dir); + // BUG in the game it seems, it's off by one + if(dir[strlen(dir)-1] != '\\') + strcat(ms_dirName, "\\"); + } + mychdir(ms_dirName); +} + +void +CFileMgr::SetDir(const char *dir) +{ + strcpy(ms_dirName, ms_rootDirName); + if(*dir != '\0'){ + strcat(ms_dirName, dir); + // BUG in the game it seems, it's off by one + if(dir[strlen(dir)-1] != '\\') + strcat(ms_dirName, "\\"); + } + mychdir(ms_dirName); +} + +void +CFileMgr::SetDirMyDocuments(void) +{ + SetDir(""); // better start at the root if user directory is relative + mychdir(_psGetUserFilesFolder()); +} + +ssize_t +CFileMgr::LoadFile(const char *file, uint8 *buf, int maxlen, const char *mode) +{ + int fd; + ssize_t n, len; + + fd = myfopen(file, mode); + if(fd == 0) + return -1; + len = 0; + do{ + n = myfread(buf + len, 1, 0x4000, fd); +#ifndef FIX_BUGS + if (n < 0) + return -1; +#endif + len += n; + assert(len < maxlen); + }while(n == 0x4000); + buf[len] = 0; + myfclose(fd); + return len; +} + +int +CFileMgr::OpenFile(const char *file, const char *mode) +{ + return myfopen(file, mode); +} + +int +CFileMgr::OpenFileForWriting(const char *file) +{ + return OpenFile(file, "wb"); +} + +size_t +CFileMgr::Read(int fd, const char *buf, ssize_t len) +{ + return myfread((void*)buf, 1, len, fd); +} + +size_t +CFileMgr::Write(int fd, const char *buf, ssize_t len) +{ + return myfwrite((void*)buf, 1, len, fd); +} + +bool +CFileMgr::Seek(int fd, int offset, int whence) +{ + return !!myfseek(fd, offset, whence); +} + +bool +CFileMgr::ReadLine(int fd, char *buf, int len) +{ + return myfgets(buf, len, fd) != nil; +} + +int +CFileMgr::CloseFile(int fd) +{ + return myfclose(fd); +} + +int +CFileMgr::GetErrorReadWrite(int fd) +{ + return myfeof(fd); +} diff --git a/src/core/FileMgr.h b/src/core/FileMgr.h new file mode 100644 index 0000000..f70451b --- /dev/null +++ b/src/core/FileMgr.h @@ -0,0 +1,23 @@ +#pragma once + +class CFileMgr +{ + static char ms_rootDirName[128]; + static char ms_dirName[128]; +public: + static void Initialise(void); + static void ChangeDir(const char *dir); + static void SetDir(const char *dir); + static void SetDirMyDocuments(void); + static ssize_t LoadFile(const char *file, uint8 *buf, int maxlen, const char *mode); + static int OpenFile(const char *file, const char *mode); + static int OpenFile(const char *file) { return OpenFile(file, "rb"); } + static int OpenFileForWriting(const char *file); + static size_t Read(int fd, const char *buf, ssize_t len); + static size_t Write(int fd, const char *buf, ssize_t len); + static bool Seek(int fd, int offset, int whence); + static bool ReadLine(int fd, char *buf, int len); + static int CloseFile(int fd); + static int GetErrorReadWrite(int fd); + static char *GetRootDirName() { return ms_rootDirName; } +}; diff --git a/src/core/Fire.cpp b/src/core/Fire.cpp new file mode 100644 index 0000000..8b18462 --- /dev/null +++ b/src/core/Fire.cpp @@ -0,0 +1,440 @@ +#include "common.h" + +#include "Vector.h" +#include "PlayerPed.h" +#include "Entity.h" +#include "PointLights.h" +#include "Particle.h" +#include "Timer.h" +#include "Vehicle.h" +#include "Shadows.h" +#include "Automobile.h" +#include "World.h" +#include "General.h" +#include "EventList.h" +#include "DamageManager.h" +#include "Ped.h" +#include "Fire.h" + +CFireManager gFireManager; + +CFire::CFire() +{ + m_bIsOngoing = false; + m_bIsScriptFire = false; + m_bPropagationFlag = true; + m_bAudioSet = true; + m_vecPos = CVector(0.0f, 0.0f, 0.0f); + m_pEntity = nil; + m_pSource = nil; + m_nFiremenPuttingOut = 0; + m_nExtinguishTime = 0; + m_nStartTime = 0; + field_20 = 1; + m_nNextTimeToAddFlames = 0; + m_fStrength = 0.8f; +} + +CFire::~CFire() {} + +void +CFire::ProcessFire(void) +{ + float fDamagePlayer; + float fDamagePeds; + float fDamageVehicle; + int16 nRandNumber; + float fGreen; + float fRed; + CVector lightpos; + CVector firePos; + CPed *ped = (CPed *)m_pEntity; + CVehicle *veh = (CVehicle*)m_pEntity; + + if (m_pEntity) { + m_vecPos = m_pEntity->GetPosition(); + + if (((CPed *)m_pEntity)->IsPed()) { + if (ped->m_pFire != this) { + Extinguish(); + return; + } + if (ped->m_nMoveState != PEDMOVE_RUN) + m_vecPos.z -= 1.0f; + if (ped->bInVehicle && ped->m_pMyVehicle) { + if (ped->m_pMyVehicle->IsCar()) + ped->m_pMyVehicle->m_fHealth = 75.0f; + } else if (m_pEntity == (CPed *)FindPlayerPed()) { + fDamagePlayer = 1.2f * CTimer::GetTimeStep(); + + ((CPlayerPed *)m_pEntity)->InflictDamage( + (CPlayerPed *)m_pSource, WEAPONTYPE_FLAMETHROWER, + fDamagePlayer, PEDPIECE_TORSO, 0); + } else { + fDamagePeds = 1.2f * CTimer::GetTimeStep(); + + if (((CPlayerPed *)m_pEntity)->InflictDamage( + (CPlayerPed *)m_pSource, WEAPONTYPE_FLAMETHROWER, + fDamagePeds, PEDPIECE_TORSO, 0)) { + m_pEntity->bRenderScorched = true; + } + } + } else if (m_pEntity->IsVehicle()) { + if (veh->m_pCarFire != this) { + Extinguish(); + return; + } + if (!m_bIsScriptFire) { + fDamageVehicle = 1.2f * CTimer::GetTimeStep(); + veh->InflictDamage((CVehicle *)m_pSource, WEAPONTYPE_FLAMETHROWER, fDamageVehicle); + } + } + } + if (!FindPlayerVehicle() && +#ifdef FIX_BUGS + FindPlayerPed() && +#endif + !FindPlayerPed()->m_pFire && !(FindPlayerPed()->bFireProof) + && ((FindPlayerPed()->GetPosition() - m_vecPos).MagnitudeSqr() < 2.0f)) { + FindPlayerPed()->DoStuffToGoOnFire(); + gFireManager.StartFire(FindPlayerPed(), m_pSource, 0.8f, 1); + } + if (CTimer::GetTimeInMilliseconds() > m_nNextTimeToAddFlames) { + m_nNextTimeToAddFlames = CTimer::GetTimeInMilliseconds() + 80; + firePos = m_vecPos; + + if (veh && veh->IsVehicle() && veh->IsCar()) { + CVehicleModelInfo *mi = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(veh->GetModelIndex())); + CVector ModelInfo = mi->m_positions[CAR_POS_HEADLIGHTS]; + ModelInfo = m_pEntity->GetMatrix() * ModelInfo; + + firePos.x = ModelInfo.x; + firePos.y = ModelInfo.y; + firePos.z = ModelInfo.z + 0.15f; + } + + CParticle::AddParticle(PARTICLE_CARFLAME, firePos, + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.0125f, 0.1f) * m_fStrength), + 0, m_fStrength, 0, 0, 0, 0); + + CGeneral::GetRandomNumber(); CGeneral::GetRandomNumber(); CGeneral::GetRandomNumber(); /* unsure why these three rands are called */ + + CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, firePos, + CVector(0.0f, 0.0f, 0.0f), 0, 0.0f, 0, 0, 0, 0); + } + if (CTimer::GetTimeInMilliseconds() < m_nExtinguishTime || m_bIsScriptFire) { + if (CTimer::GetTimeInMilliseconds() > m_nStartTime) + m_nStartTime = CTimer::GetTimeInMilliseconds() + 400; + + nRandNumber = CGeneral::GetRandomNumber() & 127; + lightpos.x = m_vecPos.x; + lightpos.y = m_vecPos.y; + lightpos.z = m_vecPos.z + 5.0f; + + if (!m_pEntity) { + CShadows::StoreStaticShadow((uintptr)this, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &lightpos, 7.0f, 0.0f, 0.0f, -7.0f, 0, nRandNumber / 2, + nRandNumber / 2, 0, 10.0f, 1.0f, 40.0f, 0, 0.0f); + } + fGreen = nRandNumber / 128.f; + fRed = nRandNumber / 128.f; + + CPointLights::AddLight(CPointLights::LIGHT_POINT, m_vecPos, CVector(0.0f, 0.0f, 0.0f), 12.0f, fRed, fGreen, 0, 0, 0); + } else { + Extinguish(); + } +} + +void +CFire::ReportThisFire(void) +{ + gFireManager.m_nTotalFires++; + CEventList::RegisterEvent(EVENT_FIRE, m_vecPos, 1000); +} + +void +CFire::Extinguish(void) +{ + if (m_bIsOngoing) { + if (!m_bIsScriptFire) + gFireManager.m_nTotalFires--; + + m_nExtinguishTime = 0; + m_bIsOngoing = false; + + if (m_pEntity) { + if (m_pEntity->IsPed()) { + ((CPed *)m_pEntity)->RestorePreviousState(); + ((CPed *)m_pEntity)->m_pFire = nil; + } else if (m_pEntity->IsVehicle()) { + ((CVehicle *)m_pEntity)->m_pCarFire = nil; + } + m_pEntity = nil; + } + } +} + +void +CFireManager::StartFire(CVector pos, float size, bool propagation) +{ + CFire *fire = GetNextFreeFire(); + + if (fire) { + fire->m_bIsOngoing = true; + fire->m_bIsScriptFire = false; + fire->m_bPropagationFlag = propagation; + fire->m_bAudioSet = true; + fire->m_vecPos = pos; + fire->m_nExtinguishTime = CTimer::GetTimeInMilliseconds() + 10000; + fire->m_nStartTime = CTimer::GetTimeInMilliseconds() + 400; + fire->m_pEntity = nil; + fire->m_pSource = nil; + fire->m_nNextTimeToAddFlames = 0; + fire->ReportThisFire(); + fire->m_fStrength = size; + } +} + +CFire * +CFireManager::StartFire(CEntity *entityOnFire, CEntity *fleeFrom, float strength, bool propagation) +{ + CPed *ped = (CPed *)entityOnFire; + CVehicle *veh = (CVehicle *)entityOnFire; + + if (entityOnFire->IsPed()) { + if (ped->m_pFire) + return nil; + if (!ped->IsPedInControl()) + return nil; + } else if (entityOnFire->IsVehicle()) { + if (veh->m_pCarFire) + return nil; + if (veh->IsCar() && ((CAutomobile *)veh)->Damage.GetEngineStatus() >= 225) + return nil; + } + CFire *fire = GetNextFreeFire(); + + if (fire) { + if (entityOnFire->IsPed()) { + ped->m_pFire = fire; + if (ped != FindPlayerPed()) { + if (fleeFrom) { + ped->SetFlee(fleeFrom, 10000); + } else { + CVector2D pos = entityOnFire->GetPosition(); + ped->SetFlee(pos, 10000); + ped->m_fleeFrom = nil; + } + ped->bDrawLast = false; + ped->SetMoveState(PEDMOVE_SPRINT); + ped->SetMoveAnim(); + ped->SetPedState(PED_ON_FIRE); + } + if (fleeFrom) { + if (ped->m_nPedType == PEDTYPE_COP) { + CEventList::RegisterEvent(EVENT_COP_SET_ON_FIRE, EVENT_ENTITY_PED, + entityOnFire, (CPed *)fleeFrom, 10000); + } else { + CEventList::RegisterEvent(EVENT_PED_SET_ON_FIRE, EVENT_ENTITY_PED, + entityOnFire, (CPed *)fleeFrom, 10000); + } + } + } else { + if (entityOnFire->IsVehicle()) { + veh->m_pCarFire = fire; + if (fleeFrom) { + CEventList::RegisterEvent(EVENT_CAR_SET_ON_FIRE, EVENT_ENTITY_VEHICLE, + entityOnFire, (CPed *)fleeFrom, 10000); + } + } + } + + fire->m_bIsOngoing = true; + fire->m_bIsScriptFire = false; + fire->m_vecPos = entityOnFire->GetPosition(); + + if (entityOnFire && entityOnFire->IsPed() && ped->IsPlayer()) { + fire->m_nExtinguishTime = CTimer::GetTimeInMilliseconds() + 3333; + } else if (entityOnFire->IsVehicle()) { + fire->m_nExtinguishTime = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(4000, 5000); + } else { + fire->m_nExtinguishTime = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(10000, 11000); + } + fire->m_nStartTime = CTimer::GetTimeInMilliseconds() + 400; + fire->m_pEntity = entityOnFire; + + entityOnFire->RegisterReference(&fire->m_pEntity); + fire->m_pSource = fleeFrom; + + if (fleeFrom) + fleeFrom->RegisterReference(&fire->m_pSource); + fire->ReportThisFire(); + fire->m_nNextTimeToAddFlames = 0; + fire->m_fStrength = strength; + fire->m_bPropagationFlag = propagation; + fire->m_bAudioSet = true; + } + return fire; +} + +void +CFireManager::Update(void) +{ + for (int i = 0; i < NUM_FIRES; i++) { + if (m_aFires[i].m_bIsOngoing) + m_aFires[i].ProcessFire(); + } +} + +CFire* CFireManager::FindNearestFire(CVector vecPos, float *pDistance) +{ + for (int i = 0; i < MAX_FIREMEN_ATTENDING; i++) { + int fireId = -1; + float minDistance = 999999; + for (int j = 0; j < NUM_FIRES; j++) { + if (!m_aFires[j].m_bIsOngoing) + continue; + if (m_aFires[j].m_bIsScriptFire) + continue; + if (m_aFires[j].m_nFiremenPuttingOut != i) + continue; + float distance = (m_aFires[j].m_vecPos - vecPos).Magnitude2D(); + if (distance < minDistance) { + minDistance = distance; + fireId = j; + } + } + *pDistance = minDistance; + if (fireId != -1) + return &m_aFires[fireId]; + } + return nil; +} + +CFire * +CFireManager::FindFurthestFire_NeverMindFireMen(CVector coords, float minRange, float maxRange) +{ + int furthestFire = -1; + float lastFireDist = 0.0f; + float fireDist; + + for (int i = 0; i < NUM_FIRES; i++) { + if (m_aFires[i].m_bIsOngoing && !m_aFires[i].m_bIsScriptFire) { + fireDist = (m_aFires[i].m_vecPos - coords).Magnitude2D(); + if (fireDist > minRange && fireDist < maxRange && fireDist > lastFireDist) { + lastFireDist = fireDist; + furthestFire = i; + } + } + } + if (furthestFire == -1) + return nil; + else + return &m_aFires[furthestFire]; +} + +CFire * +CFireManager::GetNextFreeFire(void) +{ + for (int i = 0; i < NUM_FIRES; i++) { + if (!m_aFires[i].m_bIsOngoing && !m_aFires[i].m_bIsScriptFire) + return &m_aFires[i]; + } + return nil; +} + +uint32 +CFireManager::GetTotalActiveFires(void) const +{ + return m_nTotalFires; +} + +void +CFireManager::ExtinguishPoint(CVector point, float range) +{ + for (int i = 0; i < NUM_FIRES; i++) { + if (m_aFires[i].m_bIsOngoing) { + if ((point - m_aFires[i].m_vecPos).MagnitudeSqr() < sq(range)) + m_aFires[i].Extinguish(); + } + } +} + +int32 +CFireManager::StartScriptFire(const CVector &pos, CEntity *target, float strength, bool propagation) +{ + CFire *fire; + CPed *ped = (CPed *)target; + CVehicle *veh = (CVehicle *)target; + + if (target) { + if (target->IsPed()) { + if (ped->m_pFire) + ped->m_pFire->Extinguish(); + } else if (target->IsVehicle()) { + if (veh->m_pCarFire) + veh->m_pCarFire->Extinguish(); + if (veh->IsCar() && ((CAutomobile *)veh)->Damage.GetEngineStatus() >= 225) { + ((CAutomobile *)veh)->Damage.SetEngineStatus(215); + } + } + } + + fire = GetNextFreeFire(); + fire->m_bIsOngoing = true; + fire->m_bIsScriptFire = true; + fire->m_bPropagationFlag = propagation; + fire->m_bAudioSet = true; + fire->m_vecPos = pos; + fire->m_nStartTime = CTimer::GetTimeInMilliseconds() + 400; + fire->m_pEntity = target; + + if (target) + target->RegisterReference(&fire->m_pEntity); + fire->m_pSource = nil; + fire->m_nNextTimeToAddFlames = 0; + fire->m_fStrength = strength; + if (target) { + if (target->IsPed()) { + ped->m_pFire = fire; + if (target != FindPlayerPed()) { + CVector2D pos = target->GetPosition(); + ped->SetFlee(pos, 10000); + ped->SetMoveAnim(); + ped->SetPedState(PED_ON_FIRE); + } + } else if (target->IsVehicle()) { + veh->m_pCarFire = fire; + } + } + return fire - m_aFires; +} + +bool +CFireManager::IsScriptFireExtinguish(int16 index) +{ + return !m_aFires[index].m_bIsOngoing; +} + +void +CFireManager::RemoveAllScriptFires(void) +{ + for (int i = 0; i < NUM_FIRES; i++) { + if (m_aFires[i].m_bIsScriptFire) { + m_aFires[i].Extinguish(); + m_aFires[i].m_bIsScriptFire = false; + } + } +} + +void +CFireManager::RemoveScriptFire(int16 index) +{ + m_aFires[index].Extinguish(); + m_aFires[index].m_bIsScriptFire = false; +} + +void +CFireManager::SetScriptFireAudio(int16 index, bool state) +{ + m_aFires[index].m_bAudioSet = state; +} diff --git a/src/core/Fire.h b/src/core/Fire.h new file mode 100644 index 0000000..85e53f6 --- /dev/null +++ b/src/core/Fire.h @@ -0,0 +1,51 @@ +#pragma once + +class CEntity; + +class CFire +{ +public: + bool m_bIsOngoing; + bool m_bIsScriptFire; + bool m_bPropagationFlag; + bool m_bAudioSet; + CVector m_vecPos; + CEntity *m_pEntity; + CEntity *m_pSource; + uint32 m_nExtinguishTime; + uint32 m_nStartTime; + int32 field_20; + uint32 m_nNextTimeToAddFlames; + uint32 m_nFiremenPuttingOut; + float m_fStrength; + + CFire(); + ~CFire(); + void ProcessFire(void); + void ReportThisFire(void); + void Extinguish(void); +}; + +class CFireManager +{ + enum { + MAX_FIREMEN_ATTENDING = 2, + }; +public: + uint32 m_nTotalFires; + CFire m_aFires[NUM_FIRES]; + void StartFire(CVector pos, float size, bool propagation); + CFire *StartFire(CEntity *entityOnFire, CEntity *fleeFrom, float strength, bool propagation); + void Update(void); + CFire *FindFurthestFire_NeverMindFireMen(CVector coords, float minRange, float maxRange); + CFire *FindNearestFire(CVector vecPos, float *pDistance); + CFire *GetNextFreeFire(void); + uint32 GetTotalActiveFires() const; + void ExtinguishPoint(CVector point, float range); + int32 StartScriptFire(const CVector &pos, CEntity *target, float strength, bool propagation); + bool IsScriptFireExtinguish(int16 index); + void RemoveAllScriptFires(void); + void RemoveScriptFire(int16 index); + void SetScriptFireAudio(int16 index, bool state); +}; +extern CFireManager gFireManager; diff --git a/src/core/FrontEndControls.cpp b/src/core/FrontEndControls.cpp new file mode 100644 index 0000000..18f6b3b --- /dev/null +++ b/src/core/FrontEndControls.cpp @@ -0,0 +1,1887 @@ +#include "common.h" +#include "main.h" +#include "Timer.h" +#include "Sprite2d.h" +#include "Text.h" +#include "Font.h" +#include "FrontEndControls.h" + +#define X SCREEN_SCALE_X +#define Y(x) SCREEN_SCALE_Y(float(x)*(float(DEFAULT_SCREEN_HEIGHT)/float(SCREEN_HEIGHT_PAL))) + +void +CPlaceableShText::Draw(float x, float y) +{ + if(m_text == nil) + return; + + if(m_bRightJustify) + CFont::SetRightJustifyOn(); + if(m_bDropShadow){ + CFont::SetDropShadowPosition(m_shadowOffset.x); + CFont::SetDropColor(m_shadowColor); + } + CFont::SetColor(m_color); + CFont::PrintString(x+m_position.x, y+m_position.y, m_text); + if(m_bDropShadow) + CFont::SetDropShadowPosition(0); + if(m_bRightJustify) + CFont::SetRightJustifyOff(); +} + +void +CPlaceableShText::Draw(const CRGBA &color, float x, float y) +{ + if(m_text == nil) + return; + + if(m_bRightJustify) + CFont::SetRightJustifyOn(); + if(m_bDropShadow){ + CFont::SetDropShadowPosition(m_shadowOffset.x); + CFont::SetDropColor(m_shadowColor); + } + CFont::SetColor(color); + CFont::PrintString(x+m_position.x, y+m_position.y, m_text); + if(m_bDropShadow) + CFont::SetDropShadowPosition(0); + if(m_bRightJustify) + CFont::SetRightJustifyOff(); +} + +void +CPlaceableShTextTwoLines::Draw(float x, float y) +{ + if(m_line1.m_text == nil && m_line2.m_text == nil) + return; + + if(m_bRightJustify) + CFont::SetRightJustifyOn(); + if(m_bDropShadow){ + CFont::SetDropShadowPosition(m_shadowOffset.x); + CFont::SetDropColor(m_shadowColor); + } + + if(m_line1.m_text){ + CFont::SetColor(m_line1.m_color); + CFont::PrintString(x+m_line1.m_position.x, y+m_line1.m_position.y, m_line1.m_text); + } + if(m_line2.m_text){ + CFont::SetColor(m_line2.m_color); + CFont::PrintString(x+m_line2.m_position.x, y+m_line2.m_position.y, m_line2.m_text); + } + + if(m_bDropShadow) + CFont::SetDropShadowPosition(0); + if(m_bRightJustify) + CFont::SetRightJustifyOff(); +} + +void +CPlaceableShTextTwoLines::Draw(const CRGBA &color, float x, float y) +{ + if(m_line1.m_text == nil && m_line2.m_text == nil) + return; + + if(m_bRightJustify) + CFont::SetRightJustifyOn(); + if(m_bDropShadow){ + CFont::SetDropShadowPosition(m_shadowOffset.x); + CFont::SetDropColor(m_shadowColor); + } + + if(m_line1.m_text){ + CFont::SetColor(color); + CFont::PrintString(x+m_line1.m_position.x, y+m_line1.m_position.y, m_line1.m_text); + } + if(m_line2.m_text){ + CFont::SetColor(color); + CFont::PrintString(x+m_line2.m_position.x, y+m_line2.m_position.y, m_line2.m_text); + } + + if(m_bDropShadow) + CFont::SetDropShadowPosition(0); + if(m_bRightJustify) + CFont::SetRightJustifyOff(); +} + +void +CPlaceableShOption::Draw(const CRGBA &highlightColor, float x, float y, bool bHighlight) +{ + if(bHighlight) + CPlaceableShText::Draw(highlightColor, x, y); + else if(m_bSelected) + CPlaceableShText::Draw(m_selectedColor, x, y); + else + CPlaceableShText::Draw(x, y); +} + +void +CPlaceableShOptionTwoLines::Draw(const CRGBA &highlightColor, float x, float y, bool bHighlight) +{ + if(bHighlight) + CPlaceableShTextTwoLines::Draw(highlightColor, x, y); + else if(m_bSelected) + CPlaceableShTextTwoLines::Draw(m_selectedColor, x, y); + else + CPlaceableShTextTwoLines::Draw(x, y); +} + +void +CPlaceableSprite::Draw(float x, float y) +{ + Draw(m_color, x, y); +} + +void +CPlaceableSprite::Draw(const CRGBA &color, float x, float y) +{ + if(m_pSprite) + m_pSprite->Draw(CRect(m_position.x+x, m_position.y+y, + m_position.x+x + m_size.x, m_position.y+y + m_size.y), + color); +} + +void +CPlaceableShSprite::Draw(float x, float y) +{ + if(m_bDropShadow) + m_shadow.Draw(m_shadow.m_color, m_sprite.m_position.x+x, m_sprite.m_position.y+y); + m_sprite.Draw(x, y); +} + + +/* + * CMenuPictureAndText + */ + +void +CMenuPictureAndText::SetNewOldShadowWrapX(bool bWrapX, float newWrapX, float oldWrapX) +{ + m_bWrap = bWrapX; + m_wrapX = newWrapX; + m_oldWrapx = oldWrapX; +} + +void +CMenuPictureAndText::SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale) +{ + m_bSetTextScale = bTextScale; + m_textScale = newScale; + m_oldTextScale = oldScale; +} + +void +CMenuPictureAndText::SetTextsColor(CRGBA const &color) +{ + int i; + for(i = 0; i < m_numTexts; i++) + m_texts[i].m_color = color; +} + +void +CMenuPictureAndText::AddText(wchar *text, float positionX, float positionY, CRGBA const &color, bool bRightJustify) +{ + int i; + if(m_numTexts >= 20) + return; + i = m_numTexts++; + m_texts[i].m_text = text; + m_texts[i].m_position.x = positionX; + m_texts[i].m_position.y = positionY; + m_texts[i].m_color = color; + m_texts[i].m_bRightJustify = bRightJustify; +} + +void +CMenuPictureAndText::AddPicture(CSprite2d *sprite, CSprite2d *shadow, float positionX, float positionY, float width, float height, CRGBA const &color) +{ + int i; + if(m_numSprites >= 5) + return; + i = m_numSprites++; + m_sprites[i].m_sprite.m_pSprite = sprite; + m_sprites[i].m_shadow.m_pSprite = shadow; + m_sprites[i].m_sprite.m_position.x = positionX; + m_sprites[i].m_sprite.m_position.y = positionY; + m_sprites[i].m_sprite.m_size.x = width; + m_sprites[i].m_sprite.m_size.y = height; + m_sprites[i].m_shadow.m_size.x = width; + m_sprites[i].m_shadow.m_size.y = height; + m_sprites[i].m_sprite.m_color = color; +} + +void +CMenuPictureAndText::AddPicture(CSprite2d *sprite, float positionX, float positionY, float width, float height, CRGBA const &color) +{ + int i; + if(m_numSprites >= 5) + return; + i = m_numSprites++; + m_sprites[i].m_sprite.m_pSprite = sprite; + m_sprites[i].m_shadow.m_pSprite = nil; + m_sprites[i].m_sprite.m_position.x = positionX; + m_sprites[i].m_sprite.m_position.y = positionY; + m_sprites[i].m_sprite.m_size.x = width; + m_sprites[i].m_sprite.m_size.y = height; + m_sprites[i].m_shadow.m_size.x = width; + m_sprites[i].m_shadow.m_size.y = height; + m_sprites[i].m_sprite.m_color = color; +} + +void +CMenuPictureAndText::Draw(CRGBA const &,CRGBA const &, float x, float y) +{ + int i; + + for(i = 0; i < m_numSprites; i++) + m_sprites[i].Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + for(i = 0; i < m_numTexts; i++) + if(m_bWrap) + m_texts[i].DrawShWrap(m_position.x+x, m_position.y+y, m_wrapX, m_oldWrapx); + else + m_texts[i].Draw(m_position.x+x, m_position.y+y); + if(m_bSetTextScale) + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); +} + +void +CMenuPictureAndText::SetAlpha(uint8 alpha) +{ + int i; + + for(i = 0; i < m_numSprites; i++) + m_sprites[i].SetAlpha(alpha); + for(i = 0; i < m_numTexts; i++) + m_texts[i].SetAlpha(alpha); +} + +void +CMenuPictureAndText::SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) +{ + int i; + + for(i = 0; i < 5; i++) + m_sprites[i].SetShadows(bDropShadows, shadowColor, shadowOffset); + for(i = 0; i < 20; i++) + m_texts[i].SetShadows(bDropShadows, shadowColor, shadowOffset); +} + +/* + * CMenuMultiChoice + */ + +void +CMenuMultiChoice::AddTitle(wchar *text, float positionX, float positionY, bool bRightJustify) +{ + m_title.m_text = text; + m_title.SetPosition(positionX, positionY, bRightJustify); +} + +CPlaceableShOption* +CMenuMultiChoice::AddOption(wchar *text, float positionX, float positionY, bool bSelected, bool bRightJustify) +{ + if(m_numOptions == NUM_MULTICHOICE_OPTIONS) + return nil; + m_options[m_numOptions].m_text = text; + m_options[m_numOptions].SetPosition(positionX, positionY); + m_options[m_numOptions].m_bSelected = bSelected; + m_options[m_numOptions].m_bRightJustify = bRightJustify; + return &m_options[m_numOptions++]; +} + +void +CMenuMultiChoice::SetColors(const CRGBA &title, const CRGBA &normal, const CRGBA &selected) +{ + int i; + m_title.SetColor(title); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetColors(normal, selected); +} + +void +CMenuMultiChoice::SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale) +{ + m_bSetTextScale = bTextScale; + m_textScale = newScale; + m_oldTextScale = oldScale; + m_bSetTitleTextScale = bTitleTextScale; +} + +void +CMenuMultiChoice::Draw(CRGBA const &optionHighlight ,CRGBA const &titleHighlight, float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_cursor == -1) + m_title.Draw(m_position.x+x, m_position.y+y); + else + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_cursor == -1) + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + else + for(i = 0; i < m_numOptions; i++){ + if(i == m_cursor) + m_options[i].Draw(optionHighlight, m_position.x+x, m_position.y+y); + else + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + } + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoice::DrawNormal(float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + m_title.Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoice::DrawHighlighted(CRGBA const &titleHighlight, float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_cursor == -1) + m_title.Draw(m_position.x+x, m_position.y+y); + else + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoice::SetAlpha(uint8 alpha) +{ + int i; + m_title.SetAlpha(alpha); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetAlpha(alpha); +} + +void +CMenuMultiChoice::SetShadows(bool bDropShadows, CRGBA const &shadowColor, CVector2D const &shadowOffset) +{ + int i; + m_title.SetShadows(bDropShadows, shadowColor, shadowOffset); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetShadows(bDropShadows, shadowColor, shadowOffset); +} + + +bool +CMenuMultiChoice::GoNext(void) +{ + if(m_cursor == m_numOptions-1){ + m_cursor = -1; + return false; + }else{ + m_cursor++; + return true; + } +} + +bool +CMenuMultiChoice::GoPrev(void) +{ + if(m_cursor == 0){ + m_cursor = -1; + return false; + }else{ + m_cursor--; + return true; + } +} + +void +CMenuMultiChoice::SelectCurrentOptionUnderCursor(void) +{ + int i; + if(m_cursor == -1) + return; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].m_bSelected = false; + m_options[m_cursor].m_bSelected = true; +} + +int +CMenuMultiChoice::GetMenuSelection(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + if(m_options[i].m_bSelected) + return i; + return -1; +} + +void +CMenuMultiChoice::SetMenuSelection(int selection) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].m_bSelected = false; + m_options[selection%NUM_MULTICHOICE_OPTIONS].m_bSelected = true; +} + +/* + * CMenuMultiChoiceTriggered + */ + +void +CMenuMultiChoiceTriggered::Initialise(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_triggers[i] = nil; + m_defaultCancel = nil; +} + +CPlaceableShOption* +CMenuMultiChoiceTriggered::AddOption(wchar *text, float positionX, float positionY, Trigger trigger, bool bSelected, bool bRightJustify) +{ + CPlaceableShOption *option; + option = CMenuMultiChoice::AddOption(text, positionX, positionY, bSelected, bRightJustify); + if(option) + m_triggers[m_numOptions-1] = trigger; + return option; +} + +void +CMenuMultiChoiceTriggered::SelectCurrentOptionUnderCursor(void) +{ + CMenuMultiChoice::SelectCurrentOptionUnderCursor(); + if(m_cursor != -1 && m_triggers[m_cursor] != nil ) + m_triggers[m_cursor](this); +} + +void +CMenuMultiChoiceTriggered::SelectDefaultCancelAction(void) +{ + if(m_defaultCancel) + m_defaultCancel(this); +} + +/* + * CMenuMultiChoiceTriggeredAlways + */ + +void +CMenuMultiChoiceTriggeredAlways::Draw(CRGBA const &optionHighlight, CRGBA const &titleHighlight, float x, float y) +{ + if(m_alwaysTrigger) + m_alwaysTrigger(this); + CMenuMultiChoiceTriggered::Draw(optionHighlight, titleHighlight, x, y); +} + +void +CMenuMultiChoiceTriggeredAlways::DrawNormal(float x, float y) +{ + if(m_alwaysNormalTrigger) + m_alwaysNormalTrigger(this); + CMenuMultiChoiceTriggered::DrawNormal(x, y); +} + +void +CMenuMultiChoiceTriggeredAlways::DrawHighlighted(CRGBA const &titleHighlight, float x, float y) +{ + if(m_alwaysHighlightTrigger) + m_alwaysHighlightTrigger(this); + CMenuMultiChoiceTriggered::DrawHighlighted(titleHighlight, x, y); +} + +/* + * CMenuMultiChoicePictured + */ + +void +CMenuMultiChoicePictured::Initialise(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_bHasSprite[i] = false; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_sprites[i].m_pSprite = nil; +} + +CPlaceableShOption* +CMenuMultiChoicePictured::AddOption(CSprite2d *sprite, float positionX, float positionY, const CVector2D &size, bool bSelected) +{ + CPlaceableShOption *option; + option = CMenuMultiChoice::AddOption(nil, 0.0f, 0.0f, bSelected, false); + if(option){ + m_sprites[m_numOptions-1].m_pSprite = sprite; + m_sprites[m_numOptions-1].SetPosition(positionX, positionY); + m_sprites[m_numOptions-1].m_size = size; + m_bHasSprite[m_numOptions-1] = true; + } + return option; +} + +void +CMenuMultiChoicePictured::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + int i; + + // The title and all the text + CMenuMultiChoice::Draw(optionHighlight, titleHighlight, x, y); + + CRGBA selectedColor = m_options[0].GetSelectedColor(); + CRGBA color = m_options[0].GetColor(); + + // The sprites + if(m_cursor == -1){ + for(i = 0; i < m_numOptions; i++) + if(m_bHasSprite[i]){ + if(m_options[i].m_bSelected) + m_sprites[i].Draw(selectedColor, m_position.x+x, m_position.y+y); + else + m_sprites[i].Draw(color, m_position.x+x, m_position.y+y); + } + }else{ + for(i = 0; i < m_numOptions; i++) + if(i == m_cursor){ + if(m_bHasSprite[i]) + { + uint8 color = Max(Max(optionHighlight.r, optionHighlight.g), optionHighlight.b); + m_sprites[i].Draw(CRGBA(color, color, color, optionHighlight.a), m_position.x+x, m_position.y+y); + } + }else{ + if(m_bHasSprite[i]){ + if(m_options[i].m_bSelected) + m_sprites[i].Draw(selectedColor, m_position.x+x, m_position.y+y); + else + m_sprites[i].Draw(color, m_position.x+x, m_position.y+y); + } + } + } +} + +void +CMenuMultiChoicePictured::DrawNormal(float x, float y) +{ + int i; + + // The title and all the text + CMenuMultiChoice::DrawNormal(x, y); + + CRGBA selectedColor = m_options[0].GetSelectedColor(); + CRGBA color = m_options[0].GetColor(); + + // The sprites + for(i = 0; i < m_numOptions; i++) + if(m_bHasSprite[i]){ + if(m_options[i].m_bSelected) + m_sprites[i].Draw(selectedColor, m_position.x+x, m_position.y+y); + else + m_sprites[i].Draw(color, m_position.x+x, m_position.y+y); + } +} + +void +CMenuMultiChoicePictured::DrawHighlighted(const CRGBA &titleHighlight, float x, float y) +{ + int i; + + // The title and all the text + CMenuMultiChoice::DrawHighlighted(titleHighlight, x, y); + + CRGBA selectedColor = m_options[0].GetSelectedColor(); + CRGBA color = m_options[0].GetColor(); + + // The sprites + for(i = 0; i < m_numOptions; i++) + if(m_bHasSprite[i]){ + if(m_options[i].m_bSelected) + m_sprites[i].Draw(selectedColor, m_position.x+x, m_position.y+y); + else + m_sprites[i].Draw(color, m_position.x+x, m_position.y+y); + } +} + +void +CMenuMultiChoicePictured::SetAlpha(uint8 alpha) +{ + int i; + CMenuMultiChoice::SetAlpha(alpha); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_sprites[i].SetAlpha(alpha); + +} + + +/* + * CMenuMultiChoicePicturedTriggered + */ + +void +CMenuMultiChoicePicturedTriggered::Initialise(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_triggers[i] = nil; + m_defaultCancel = nil; // missing on PS2 +} + +CPlaceableShOption* +CMenuMultiChoicePicturedTriggered::AddOption(CSprite2d *sprite, float positionX, float positionY, const CVector2D &size, Trigger trigger, bool bSelected) +{ + CPlaceableShOption *option; + option = CMenuMultiChoicePictured::AddOption(sprite, positionX, positionY, size, bSelected); + if(option) + m_triggers[m_numOptions-1] = trigger; + return option; +} + +void +CMenuMultiChoicePicturedTriggered::SelectCurrentOptionUnderCursor(void) +{ + CMenuMultiChoice::SelectCurrentOptionUnderCursor(); + if(m_cursor != -1) + m_triggers[m_cursor](this); +} + +void +CMenuMultiChoicePicturedTriggered::SelectDefaultCancelAction(void) +{ + if(m_defaultCancel) + m_defaultCancel(this); +} + +/* + * CMenuMultiChoicePicturedTriggeredAnyMove + */ + +void +CMenuMultiChoicePicturedTriggeredAnyMove::Initialise(void) +{ + int i; + CMenuMultiChoicePicturedTriggered::Initialise(); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++){ + m_moveTab[i].right = -1; + m_moveTab[i].left = -1; + m_moveTab[i].down = -1; + m_moveTab[i].up = -1; + } +} + +CPlaceableShOption* +CMenuMultiChoicePicturedTriggeredAnyMove::AddOption(CSprite2d *sprite, FEC_MOVETAB *moveTab, float positionX, float positionY, const CVector2D &size, Trigger trigger, bool bSelected) +{ + CPlaceableShOption *option; + option = CMenuMultiChoicePicturedTriggered::AddOption(sprite, positionX, positionY, size, trigger, bSelected); + if(option && moveTab) + m_moveTab[m_numOptions-1] = *moveTab; + return option; +} + +bool +CMenuMultiChoicePicturedTriggeredAnyMove::GoDown(void) +{ + int move = m_moveTab[m_cursor].down; + if(move == -1) + return GoNext(); + m_cursor = move; + return true; +} + +bool +CMenuMultiChoicePicturedTriggeredAnyMove::GoUp(void) +{ + int move = m_moveTab[m_cursor].up; + if(move == -1) + return GoPrev(); + m_cursor = move; + return true; +} + +bool +CMenuMultiChoicePicturedTriggeredAnyMove::GoLeft(void) +{ + int move = m_moveTab[m_cursor].left; + if(move == -1) + return GoPrev(); + m_cursor = move; + return true; +} + +bool +CMenuMultiChoicePicturedTriggeredAnyMove::GoRight(void) +{ + int move = m_moveTab[m_cursor].right; + if(move == -1) + return GoNext(); + m_cursor = move; + return true; +} + + +/* + * CMenuMultiChoiceTwoLines + */ + +void +CMenuMultiChoiceTwoLines::AddTitle(wchar *text, float positionX, float positionY, bool bRightJustify) +{ + m_title.m_text = text; + m_title.SetPosition(positionX, positionY, bRightJustify); +} + +CPlaceableShOptionTwoLines* +CMenuMultiChoiceTwoLines::AddOption(wchar *text, float positionX, float positionY, bool bSelected, bool bRightJustify) +{ + return AddOption(text, positionX, positionY, nil, 0.0f, 0.0f, bSelected, bRightJustify); +} + +CPlaceableShOptionTwoLines* +CMenuMultiChoiceTwoLines::AddOption(wchar *text1, float positionX1, float positionY1, wchar *text2, float positionX2, float positionY2, bool bSelected, bool bRightJustify) +{ + if(m_numOptions == NUM_MULTICHOICE_OPTIONS) + return nil; + m_options[m_numOptions].m_line1.m_text = text1; + m_options[m_numOptions].m_line2.m_text = text2; + m_options[m_numOptions].m_line1.SetPosition(positionX1, positionY1); + m_options[m_numOptions].m_line2.SetPosition(positionX2, positionY2); + m_options[m_numOptions].m_bSelected = bSelected; + m_options[m_numOptions].m_bRightJustify = bRightJustify; + return &m_options[m_numOptions++]; +} + + +void +CMenuMultiChoiceTwoLines::SetColors(const CRGBA &title, const CRGBA &normal, const CRGBA &selected) +{ + int i; + m_title.SetColor(title); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetColors(normal, selected); +} + +void +CMenuMultiChoiceTwoLines::SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale) +{ + m_bSetTextScale = bTextScale; + m_textScale = newScale; + m_oldTextScale = oldScale; + m_bSetTitleTextScale = bTitleTextScale; +} + +void +CMenuMultiChoiceTwoLines::Draw(CRGBA const &optionHighlight ,CRGBA const &titleHighlight, float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_cursor == -1) + m_title.Draw(m_position.x+x, m_position.y+y); + else + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_cursor == -1) + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + else + for(i = 0; i < m_numOptions; i++){ + if(i == m_cursor) + m_options[i].Draw(optionHighlight, m_position.x+x, m_position.y+y); + else + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + } + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoiceTwoLines::DrawNormal(float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + m_title.Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoiceTwoLines::DrawHighlighted(CRGBA const &titleHighlight, float x, float y) +{ + int i; + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_cursor == -1) + m_title.Draw(m_position.x+x, m_position.y+y); + else + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + for(i = 0; i < m_numOptions; i++) + m_options[i].Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale){ + CFont::DrawFonts(); + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); + } +} + +void +CMenuMultiChoiceTwoLines::SetAlpha(uint8 alpha) +{ + int i; + m_title.SetAlpha(alpha); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetAlpha(alpha); +} + +void +CMenuMultiChoiceTwoLines::SetShadows(bool bDropShadows, CRGBA const &shadowColor, CVector2D const &shadowOffset) +{ + int i; + m_title.SetShadows(bDropShadows, shadowColor, shadowOffset); + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].SetShadows(bDropShadows, shadowColor, shadowOffset); +} + + +bool +CMenuMultiChoiceTwoLines::GoNext(void) +{ + if(m_cursor == m_numOptions-1){ + m_cursor = -1; + return false; + }else{ + m_cursor++; + return true; + } +} + +bool +CMenuMultiChoiceTwoLines::GoPrev(void) +{ + if(m_cursor == 0){ + m_cursor = -1; + return false; + }else{ + m_cursor--; + return true; + } +} + +void +CMenuMultiChoiceTwoLines::SelectCurrentOptionUnderCursor(void) +{ + int i; + if(m_cursor == -1) + return; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].m_bSelected = false; + m_options[m_cursor].m_bSelected = true; +} + +int +CMenuMultiChoiceTwoLines::GetMenuSelection(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + if(m_options[i].m_bSelected) + return i; + return -1; +} + +void +CMenuMultiChoiceTwoLines::SetMenuSelection(int selection) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_options[i].m_bSelected = false; + m_options[selection%NUM_MULTICHOICE_OPTIONS].m_bSelected = true; +} + +/* + * CMenuMultiChoiceTwoLinesTriggered + */ + +void +CMenuMultiChoiceTwoLinesTriggered::Initialise(void) +{ + int i; + for(i = 0; i < NUM_MULTICHOICE_OPTIONS; i++) + m_triggers[i] = nil; + m_defaultCancel = nil; +} + +CPlaceableShOptionTwoLines* +CMenuMultiChoiceTwoLinesTriggered::AddOption(wchar *text, float positionX, float positionY, Trigger trigger, bool bSelected, bool bRightJustify) +{ + CPlaceableShOptionTwoLines *option; + option = CMenuMultiChoiceTwoLines::AddOption(text, positionX, positionY, bSelected, bRightJustify); + if(option) + m_triggers[m_numOptions-1] = trigger; + return option; +} + +CPlaceableShOptionTwoLines* +CMenuMultiChoiceTwoLinesTriggered::AddOption(wchar *text1, float positionX1, float positionY1, wchar *text2, float positionX2, float positionY2, Trigger trigger, bool bSelected, bool bRightJustify) +{ + CPlaceableShOptionTwoLines *option; + option = CMenuMultiChoiceTwoLines::AddOption(text1, positionX1, positionY1, text2, positionX2, positionY2, bSelected, bRightJustify); + if(option) + m_triggers[m_numOptions-1] = trigger; + return option; +} + +void +CMenuMultiChoiceTwoLinesTriggered::SelectCurrentOptionUnderCursor(void) +{ + CMenuMultiChoiceTwoLines::SelectCurrentOptionUnderCursor(); + if(m_cursor != -1) + m_triggers[m_cursor](this); +} + +void +CMenuMultiChoiceTwoLinesTriggered::SelectDefaultCancelAction(void) +{ + if(m_defaultCancel) + m_defaultCancel(this); +} + + +/* + * CMenuOnOff + */ + +void +CMenuOnOff::SetColors(const CRGBA &title, const CRGBA &options) +{ + m_title.SetColors(title, title); + m_options[0].SetColor(options); + m_options[1].SetColor(options); +} + +void +CMenuOnOff::SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale) +{ + m_bSetTextScale = bTextScale; + m_textScale = newScale; + m_oldTextScale = oldScale; + m_bSetTitleTextScale = bTitleTextScale; +} + +void +CMenuOnOff::SetOptionPosition(float x, float y, bool bRightJustify) +{ + m_options[0].SetPosition(x, y, bRightJustify); + m_options[1].SetPosition(x, y, bRightJustify); +} + +void +CMenuOnOff::AddTitle(wchar *text, bool bSelected, float positionX, float positionY, bool bRightJustify) +{ + m_title.m_text = text; + m_title.m_bSelected = bSelected; + m_title.SetPosition(positionX, positionY, bRightJustify); +} + +void +CMenuOnOff::Draw(CRGBA const &optionHighlight, CRGBA const &titleHighlight, float x, float y) +{ + if(m_type == 1){ + m_options[0].m_text = TheText.Get("FEM_NO"); + m_options[1].m_text = TheText.Get("FEM_YES"); + }else if(m_type == 0){ + m_options[0].m_text = TheText.Get("FEM_OFF"); + m_options[1].m_text = TheText.Get("FEM_ON"); + } + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_bActive) + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + else + m_title.Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_bActive){ + if(m_title.m_bSelected) + m_options[1].Draw(optionHighlight, m_position.x+x, m_position.y+y); + else + m_options[0].Draw(optionHighlight, m_position.x+x, m_position.y+y); + }else{ + if(m_title.m_bSelected) + m_options[1].Draw(m_position.x+x, m_position.y+y); + else + m_options[0].Draw(m_position.x+x, m_position.y+y); + } + + if(m_bSetTextScale) + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); +} + +void +CMenuOnOff::DrawNormal(float x, float y) +{ + if(m_type == 1){ + m_options[0].m_text = TheText.Get("FEM_NO"); + m_options[1].m_text = TheText.Get("FEM_YES"); + }else if(m_type == 0){ + m_options[0].m_text = TheText.Get("FEM_OFF"); + m_options[1].m_text = TheText.Get("FEM_ON"); + } + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + m_title.Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_title.m_bSelected) + m_options[1].Draw(m_position.x+x, m_position.y+y); + else + m_options[0].Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); +} + +void +CMenuOnOff::DrawHighlighted(CRGBA const &titleHighlight, float x, float y) +{ + if(m_type == 1){ + m_options[0].m_text = TheText.Get("FEM_NO"); + m_options[1].m_text = TheText.Get("FEM_YES"); + }else if(m_type == 0){ + m_options[0].m_text = TheText.Get("FEM_OFF"); + m_options[1].m_text = TheText.Get("FEM_ON"); + } + + if(m_bSetTextScale && m_bSetTitleTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + if(m_bActive) + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + else + m_title.Draw(CRGBA(0,0,0,0), m_position.x+x, m_position.y+y, false); + + if(m_bSetTextScale) + CFont::SetScale(m_textScale.x, m_textScale.y); + + if(m_title.m_bSelected) + m_options[1].Draw(m_position.x+x, m_position.y+y); + else + m_options[0].Draw(m_position.x+x, m_position.y+y); + + if(m_bSetTextScale) + CFont::SetScale(m_oldTextScale.x, m_oldTextScale.y); +} + +void +CMenuOnOff::SetAlpha(uint8 alpha) +{ + m_title.SetAlpha(alpha); + m_options[0].SetAlpha(alpha); + m_options[1].SetAlpha(alpha); +} + +void +CMenuOnOff::SetShadows(bool bDropShadows, CRGBA const &shadowColor, CVector2D const &shadowOffset) +{ + m_title.SetShadows(bDropShadows, shadowColor, shadowOffset); + m_options[0].SetShadows(bDropShadows, shadowColor, shadowOffset); + m_options[1].SetShadows(bDropShadows, shadowColor, shadowOffset); +} + +/* + * CMenuOnOffTriggered + */ + +void +CMenuOnOffTriggered::SetOptionPosition(float x, float y, Trigger trigger, bool bRightJustify) +{ + CMenuOnOff::SetOptionPosition(x, y, bRightJustify); + if(trigger) + m_trigger = trigger; +} + +void +CMenuOnOffTriggered::SelectCurrentOptionUnderCursor(void) +{ + CMenuOnOff::SelectCurrentOptionUnderCursor(); + if(m_trigger) + m_trigger(this); +} + + + +/* + * CMenuSlider + */ + +char CMenuSlider::Buf8[8]; +wchar CMenuSlider::Buf16[8]; + +void +CMenuSlider::SetColors(const CRGBA &title, const CRGBA &percentage, const CRGBA &left, const CRGBA &right) +{ + m_title.SetColor(title); + m_percentageText.SetColor(percentage); + m_colors[0] = left; + m_colors[1] = right; +} + + +void +CMenuSlider::AddTickBox(float positionX, float positionY, float width, float heightLeft, float heightRight) +{ + m_box.SetPosition(positionX, positionY); + m_size[0].x = width; + m_size[0].y = heightLeft; + m_size[1].x = width; + m_size[1].y = heightRight; +} + +void +CMenuSlider::AddTitle(wchar *text, float positionX, float positionY) +{ + m_title.m_text = text; + m_title.SetPosition(positionX, positionY); +} + +static CRGBA SELECTED_TEXT_COLOR_0(255, 182, 48, 255); + +void +CMenuSlider::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + if(m_bActive){ + CRGBA selectionCol = m_colors[0]; + if(optionHighlight.red == SELECTED_TEXT_COLOR_0.red && + optionHighlight.green == SELECTED_TEXT_COLOR_0.green && + optionHighlight.blue == SELECTED_TEXT_COLOR_0.blue && + optionHighlight.alpha == SELECTED_TEXT_COLOR_0.alpha) + selectionCol = m_colors[1]; + + if(m_style == 1){ + // solid bar + CRGBA shadowCol = m_box.GetShadowColor(); + float f = m_value/1000.0f; + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + if(m_box.m_bDropShadow) + CSprite2d::DrawRect( + CRect(boxPos.x + X(m_box.m_shadowOffset.x), + boxPos.y + Y(m_box.m_shadowOffset.y), + boxPos.x + X(m_box.m_shadowOffset.x) + m_size[0].x, + boxPos.y + Y(m_box.m_shadowOffset.y) + m_size[0].y), + shadowCol); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x, boxPos.y + m_size[0].y), + m_colors[1]); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x*f, boxPos.y + m_size[0].y), + selectionCol); + }else if(m_style == 0){ + // ticks... + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + DrawTicks(boxPos, m_size[0], m_size[1].y, + m_value/1000.0f, m_colors[0], selectionCol, m_colors[1], + m_box.m_bDropShadow, m_box.m_shadowOffset, m_box.GetShadowColor()); + } + + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + + if(m_bDrawPercentage){ + sprintf(Buf8, "%d%%", m_value/10); + AsciiToUnicode(Buf8, Buf16); + m_percentageText.m_text = Buf16; + m_percentageText.Draw(optionHighlight, m_position.x+x, m_position.y+y); + } + }else + CMenuSlider::DrawNormal(x, y); +} + +void +CMenuSlider::DrawNormal(float x, float y) +{ + if(m_style == 1){ + // solid bar + CRGBA shadowCol = m_box.GetShadowColor(); + float f = m_value/1000.0f; + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + if(m_box.m_bDropShadow) + CSprite2d::DrawRect( + CRect(boxPos.x + X(m_box.m_shadowOffset.x), + boxPos.y + Y(m_box.m_shadowOffset.y), + boxPos.x + X(m_box.m_shadowOffset.x) + m_size[0].x, + boxPos.y + Y(m_box.m_shadowOffset.y) + m_size[0].y), + shadowCol); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x, boxPos.y + m_size[0].y), + m_colors[1]); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x*f, boxPos.y + m_size[0].y), + m_colors[0]); + }else if(m_style == 0){ + // ticks... + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + DrawTicks(boxPos, m_size[0], m_size[1].y, + m_value/1000.0f, m_colors[0], m_colors[1], + m_box.m_bDropShadow, m_box.m_shadowOffset, m_box.GetShadowColor()); + } + + m_title.Draw(m_position.x+x, m_position.y+y); + + if(m_bDrawPercentage){ + sprintf(Buf8, "%d%%", m_value/10); + AsciiToUnicode(Buf8, Buf16); + m_percentageText.m_text = Buf16; + m_percentageText.Draw(m_percentageText.GetColor(), m_position.x+x, m_position.y+y); + } +} + +void +CMenuSlider::DrawHighlighted(const CRGBA &titleHighlight, float x, float y) +{ + if(m_bActive) + m_title.Draw(titleHighlight, m_position.x+x, m_position.y+y); + else + m_title.Draw(m_position.x+x, m_position.y+y); + + if(m_style == 1){ + // solid bar + CRGBA shadowCol = m_box.GetShadowColor(); + float f = m_value/1000.0f; + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + if(m_box.m_bDropShadow) + CSprite2d::DrawRect( + CRect(boxPos.x + X(m_box.m_shadowOffset.x), + boxPos.y + Y(m_box.m_shadowOffset.y), + boxPos.x + X(m_box.m_shadowOffset.x) + m_size[0].x, + boxPos.y + Y(m_box.m_shadowOffset.y) + m_size[0].y), + shadowCol); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x, boxPos.y + m_size[0].y), + m_colors[1]); + CSprite2d::DrawRect( + CRect(boxPos.x, boxPos.y, + boxPos.x + m_size[0].x*f, boxPos.y + m_size[0].y), + m_colors[0]); + }else if(m_style == 0){ + // ticks... + CVector2D boxPos = m_box.m_position + m_position + CVector2D(x,y); + DrawTicks(boxPos, m_size[0], m_size[1].y, + m_value/1000.0f, m_colors[0], m_colors[1], + m_box.m_bDropShadow, m_box.m_shadowOffset, m_box.GetShadowColor()); + } + + if(m_bDrawPercentage){ + sprintf(Buf8, "%d%%", m_value/10); + AsciiToUnicode(Buf8, Buf16); + m_percentageText.m_text = Buf16; + m_percentageText.Draw(m_percentageText.GetColor(), m_position.x+x, m_position.y+y); + } +} + +void +CMenuSlider::DrawTicks(const CVector2D &position, const CVector2D &size, float heightRight, float level, const CRGBA &leftCol, const CRGBA &selCol, const CRGBA &rightCol, bool bShadow, const CVector2D &shadowOffset, const CRGBA &shadowColor) +{ + int i; + int numTicks = size.x / X(8.0f); + float dy = heightRight - size.y; + float stepy = dy / numTicks; + int left = level*numTicks; + int drewSelection = 0; + for(i = 0; i < numTicks; i++){ + CRect rect(position.x + X(8.0f)*i, position.y + dy - stepy*i, + position.x + X(8.0f)*i + X(4.0f), position.y + dy + size.y); + if(bShadow){ + CRect shadowRect = rect; + shadowRect.left += X(shadowOffset.x); + shadowRect.right += X(shadowOffset.x); + shadowRect.top += Y(shadowOffset.y); + shadowRect.bottom += Y(shadowOffset.y); + CSprite2d::DrawRect(shadowRect, shadowColor); + } + if(i < left) + CSprite2d::DrawRect(rect, leftCol); + else if(!drewSelection){ + CSprite2d::DrawRect(rect, selCol); + drewSelection = 1; + }else + CSprite2d::DrawRect(rect, rightCol); + } +} + +void +CMenuSlider::DrawTicks(const CVector2D &position, const CVector2D &size, float heightRight, float level, const CRGBA &leftCol, const CRGBA &rightCol, bool bShadow, const CVector2D &shadowOffset, const CRGBA &shadowColor) +{ + int i; + int numTicks = size.x / X(8.0f); + float dy = heightRight - size.y; + float stepy = dy / numTicks; + int left = level*numTicks; + for(i = 0; i < numTicks; i++){ + CRect rect(position.x + X(8.0f)*i, position.y + dy - stepy*i, + position.x + X(8.0f)*i + X(4.0f), position.y + dy + size.y); + if(bShadow){ + CRect shadowRect = rect; + shadowRect.left += X(shadowOffset.x); + shadowRect.right += X(shadowOffset.x); + shadowRect.top += Y(shadowOffset.y); + shadowRect.bottom += Y(shadowOffset.y); + CSprite2d::DrawRect(shadowRect, shadowColor); + } + if(i < left) + CSprite2d::DrawRect(rect, leftCol); + else + CSprite2d::DrawRect(rect, rightCol); + } +} + +void +CMenuSlider::SetAlpha(uint8 alpha) +{ + m_title.SetAlpha(alpha); + m_box.SetAlpha(alpha); + m_someAlpha = alpha; + m_percentageText.SetAlpha(alpha); + m_colors[0].alpha = alpha; + m_colors[1].alpha = alpha; +} + +void +CMenuSlider::SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) +{ + m_title.SetShadows(bDropShadows, shadowColor, shadowOffset); + m_box.SetShadows(bDropShadows, shadowColor, shadowOffset); + m_percentageText.SetShadows(bDropShadows, shadowColor, shadowOffset); +} + +/* + * CMenuSliderTriggered + */ + +void +CMenuSliderTriggered::AddTickBox(float positionX, float positionY, float width, float heightLeft, float heightRight, Trigger trigger, Trigger alwaysTrigger) +{ + CMenuSlider::AddTickBox(positionX, positionY, width, heightLeft, heightRight); + m_trigger = trigger; + m_alwaysTrigger = alwaysTrigger; +} + +void +CMenuSliderTriggered::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + CMenuSlider::Draw(optionHighlight, titleHighlight, x, y); + if(m_alwaysTrigger) + m_alwaysTrigger(this); +} + +bool +CMenuSliderTriggered::GoLeft(void) +{ + CMenuSlider::GoLeft(); + if(m_trigger) + m_trigger(this); + return true; +} + +bool +CMenuSliderTriggered::GoRight(void) +{ + CMenuSlider::GoRight(); + if(m_trigger) + m_trigger(this); + return true; +} + +bool +CMenuSliderTriggered::GoLeftStill(void) +{ + CMenuSlider::GoLeftStill(); + if(m_trigger) + m_trigger(this); + return true; +} + +bool +CMenuSliderTriggered::GoRightStill(void) +{ + CMenuSlider::GoRightStill(); + if(m_trigger) + m_trigger(this); + return true; +} + +/* + * CMenuLineLister + */ + +CMenuLineLister::CMenuLineLister(void) + : m_numLines(0), m_width(0.0f), m_height(0.0f), + m_scrollPosition(0.0f), m_scrollSpeed(1.0f), m_lineSpacing(15.0f), field_10E8(0) +{ + int i; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_lineAlphas[i] = 0; + m_lineFade[i] = 0; + } +} + + +void +CMenuLineLister::SetLinesColor(const CRGBA &color) +{ + int i; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_linesLeft[i].SetColor(color); + m_linesRight[i].SetColor(color); + } +} + +void +CMenuLineLister::ResetNumberOfTextLines(void) +{ + int i; + m_numLines = 0; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_lineAlphas[i] = 0; + m_lineFade[i] = 0; + } + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + // note this doesn't clear lines 0-14, probably an oversight + GetLeftLine(i)->m_text = nil; + GetRightLine(i)->m_text = nil; + } +} + +bool +CMenuLineLister::AddTextLine(wchar *left, wchar *right) +{ + CPlaceableShText *leftLine, *rightLine; + if(m_numLines == NUM_LINELISTER_LINES) + return false; + leftLine = GetLeftLine(m_numLines); + leftLine->m_text = left; + leftLine->SetPosition(0.0f, m_lineSpacing*(m_numLines+15)); + rightLine = GetRightLine(m_numLines); + rightLine->m_text = right; + rightLine->SetPosition(leftLine->m_position.x, leftLine->m_position.y); + m_numLines++; + return true; +} + +void +CMenuLineLister::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + int i, n; + + m_scrollPosition += m_scrollSpeed; + n = m_numLines + 15; + if(m_scrollSpeed > 0.0f){ + if(m_scrollPosition > n*m_lineSpacing) + m_scrollPosition = 0.0f; + }else{ + if(m_scrollPosition < 0.0f) + m_scrollPosition = n*m_lineSpacing; + } + // this is a weird condition.... + for(i = 0; + m_scrollPosition < i*m_lineSpacing || m_scrollPosition >= (i+1)*m_lineSpacing; + i++); + + float screenPos = 0.0f; + for(; i < n; i++){ + CVector2D linePos = m_linesLeft[i].m_position; + + if(linePos.y+m_position.y - (m_scrollPosition+m_position.y) < Y(64.0f)) + m_lineFade[i] = -4.0f*Abs(m_scrollSpeed); + else + m_lineFade[i] = 4.0f*Abs(m_scrollSpeed); + int newAlpha = m_lineAlphas[i] + m_lineFade[i]; + if(newAlpha < 0) newAlpha = 0; + if(newAlpha > 255) newAlpha = 255; + m_lineAlphas[i] = newAlpha; + + uint8 alpha = m_linesLeft[i].m_shadowColor.alpha; + + // apply alpha + m_linesLeft[i].SetAlpha((alpha*m_lineAlphas[i])>>8); + m_linesRight[i].SetAlpha((alpha*m_lineAlphas[i])>>8); + + m_linesLeft[i].Draw(m_position.x+x, m_position.y+y - m_scrollPosition); + CFont::SetRightJustifyOn(); + m_linesRight[i].Draw(m_position.x+x + m_width, m_position.y+y - m_scrollPosition); + CFont::SetRightJustifyOff(); + + // restore alpha + m_linesLeft[i].SetAlpha(alpha); + m_linesRight[i].SetAlpha(alpha); + + screenPos += m_lineSpacing; + if(screenPos >= m_height) + break; + } + + m_scrollSpeed = 1.0f; +} + +void +CMenuLineLister::SetAlpha(uint8 alpha) +{ + int i; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_linesLeft[i].SetAlpha(alpha); + m_linesRight[i].SetAlpha(alpha); + } +} + +void +CMenuLineLister::SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) +{ + int i; + for(i = 0; i < NUM_LINELISTER_LINES_TOTAL; i++){ + m_linesLeft[i].SetShadows(bDropShadows, shadowColor, shadowOffset); + m_linesRight[i].SetShadows(bDropShadows, shadowColor, shadowOffset); + } +} + + +/* + * CMenuPage + */ + +void +CMenuPage::Initialise(void) +{ + int i; + m_numControls = 0; + m_pCurrentControl = nil; + m_cursor = 0; + for(i = 0; i < NUM_PAGE_WIDGETS; i++) + m_controls[i] = nil; +} + +bool +CMenuPage::AddMenu(CMenuBase *widget) +{ + if(m_numControls >= NUM_PAGE_WIDGETS) + return false; + m_controls[m_numControls] = widget; + if(m_numControls == 0){ + m_pCurrentControl = widget; + m_cursor = 0; + } + m_numControls++; + return true; +} + +bool +CMenuPage::IsActiveMenuTwoState(void) +{ + return m_pCurrentControl && m_pCurrentControl->m_bTwoState; +} + +void +CMenuPage::ActiveMenuTwoState_SelectNextPosition(void) +{ + int sel; + if(m_pCurrentControl == nil || !m_pCurrentControl->m_bTwoState) + return; + m_pCurrentControl->GoFirst(); + sel = m_pCurrentControl->GetMenuSelection(); + if(sel == 1) + m_pCurrentControl->SelectCurrentOptionUnderCursor(); + else if(sel == 0){ + if ( m_pCurrentControl ) + { + if ( !m_pCurrentControl->GoNext() ) + m_pCurrentControl->GoFirst(); + } + + m_pCurrentControl->SelectCurrentOptionUnderCursor(); + } +} + +void +CMenuPage::Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) +{ + int i; + for(i = 0; i < m_numControls; i++) + if(m_controls[i]){ + if(i == m_cursor) + m_controls[i]->Draw(optionHighlight, titleHighlight, x, y); + else + m_controls[i]->DrawNormal(x, y); + } +} + +void +CMenuPage::DrawHighlighted(const CRGBA &titleHighlight, float x, float y) +{ + int i; + for(i = 0; i< m_numControls; i++) + if(m_controls[i]){ + if(i == m_cursor) + m_controls[i]->DrawHighlighted(titleHighlight, x, y); + else + m_controls[i]->DrawNormal(x, y); + } +} + +void +CMenuPage::DrawNormal(float x, float y) +{ + int i; + for(i = 0; i< m_numControls; i++) + if(m_controls[i]) + m_controls[i]->DrawNormal(x, y); +} + +void +CMenuPage::ActivatePage(void) +{ + m_cursor = 0; + if(m_numControls == 0) + return; + for(;;){ + m_pCurrentControl = m_controls[m_cursor]; + if(m_pCurrentControl->GoFirst()) + return; + if(m_cursor == m_numControls-1) + m_cursor = 0; + else + m_cursor++; + } +} + +void +CMenuPage::SetAlpha(uint8 alpha) +{ + int i; + for(i = 0; i< m_numControls; i++) + if(m_controls[i]) + m_controls[i]->SetAlpha(alpha); +} + +void +CMenuPage::SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) +{ + int i; + for(i = 0; i< m_numControls; i++) + if(m_controls[i]) + m_controls[i]->SetShadows(bDropShadows, shadowColor, shadowOffset); +} + +void +CMenuPage::GoUpMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + do{ + if(m_cursor == 0) + m_cursor = m_numControls-1; + else + m_cursor--; + m_pCurrentControl = m_controls[m_cursor]; + }while(!m_pCurrentControl->GoLast()); +} + +void +CMenuPage::GoDownMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + do{ + if(m_cursor == m_numControls-1) + m_cursor = 0; + else + m_cursor++; + m_pCurrentControl = m_controls[m_cursor]; + }while(!m_pCurrentControl->GoFirst()); +} + +void +CMenuPage::GoLeftMenuOnPage(void) +{ + // same as up + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + do{ + if(m_cursor == 0) + m_cursor = m_numControls-1; + else + m_cursor--; + m_pCurrentControl = m_controls[m_cursor]; + }while(!m_pCurrentControl->GoLast()); +} + +void +CMenuPage::GoRightMenuOnPage(void) +{ + // same as right + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + do{ + if(m_cursor == m_numControls-1) + m_cursor = 0; + else + m_cursor++; + m_pCurrentControl = m_controls[m_cursor]; + }while(!m_pCurrentControl->GoFirst()); +} + +/* + * CMenuPageAnyMove + */ + +void +CMenuPageAnyMove::Initialise(void) +{ + int i; + CMenuPage::Initialise(); + for(i = 0; i < NUM_PAGE_WIDGETS; i++){ + m_moveTab[i].left = -1; + m_moveTab[i].right = -1; + m_moveTab[i].up = -1; + m_moveTab[i].down = -1; + } +} + +bool +CMenuPageAnyMove::AddMenu(CMenuBase *widget, FEC_MOVETAB *moveTab) +{ + if(AddMenu(widget)){ + m_moveTab[m_numControls-1] = *moveTab; + return true; + } + return false; +} + +void +CMenuPageAnyMove::GoUpMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + int move = m_moveTab[m_cursor].up; + if(move == -1) + CMenuPage::GoUpMenuOnPage(); + else{ // BUG: no else in original code + m_cursor = move; + m_pCurrentControl = m_controls[m_cursor]; + m_pCurrentControl->GoLast(); + } +} + +void +CMenuPageAnyMove::GoDownMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + int move = m_moveTab[m_cursor].down; + if(move == -1) + CMenuPage::GoDownMenuOnPage(); + else{ // BUG: no else in original code + m_cursor = move; + m_pCurrentControl = m_controls[m_cursor]; + m_pCurrentControl->GoLast(); + } +} + +void +CMenuPageAnyMove::GoLeftMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + int move = m_moveTab[m_cursor].left; + if(move == -1) + CMenuPage::GoLeftMenuOnPage(); + else{ // BUG: no else in original code + m_cursor = move; + m_pCurrentControl = m_controls[m_cursor]; + m_pCurrentControl->GoLast(); + } +} + +void +CMenuPageAnyMove::GoRightMenuOnPage(void) +{ + if(m_pCurrentControl == nil) + return; + m_pCurrentControl->DeactivateMenu(); + int move = m_moveTab[m_cursor].right; + if(move == -1) + CMenuPage::GoRightMenuOnPage(); + else{ // BUG: no else in original code + m_cursor = move; + m_pCurrentControl = m_controls[m_cursor]; + m_pCurrentControl->GoLast(); + } +} diff --git a/src/core/FrontEndControls.h b/src/core/FrontEndControls.h new file mode 100644 index 0000000..68dab90 --- /dev/null +++ b/src/core/FrontEndControls.h @@ -0,0 +1,750 @@ +#pragma once + +enum { + NUM_MULTICHOICE_OPTIONS = 16, + // 50 actual lines and 15 for spacing + NUM_LINELISTER_LINES = 50, + NUM_LINELISTER_LINES_TOTAL = NUM_LINELISTER_LINES + 15, + NUM_PAGE_WIDGETS = 10, +}; + +class CTriggerCaller +{ + bool bHasTrigger; + void *pTrigger; + void (*pFunc)(void *); + int field_C; +public: + + CTriggerCaller() : bHasTrigger(false), pFunc(nil) + {} + + void SetTrigger(void *func, void *trigger) + { + if ( !bHasTrigger ) + { + pFunc = (void (*)(void *))func; + pTrigger = trigger; + bHasTrigger = true; + } + } + + void CallTrigger(void) + { + if ( bHasTrigger && pFunc != nil ) + pFunc(pTrigger); + + bHasTrigger = false; + pFunc = nil; + } + + bool CanCall() + { + return bHasTrigger; + } +}; + +class CPlaceableText +{ +public: + CVector2D m_position; + CRGBA m_color; + wchar *m_text; + + CPlaceableText(void) + : m_position(0.0f, 0.0f), m_color(255, 255, 255, 255), m_text(nil) {} + void SetPosition(float x, float y) { m_position.x = x; m_position.y = y; } + void SetColor(const CRGBA &color) { m_color = color; } + CRGBA GetColor(void) { return m_color; } + void SetAlpha(uint8 alpha) { m_color.alpha = alpha; } +}; + +// No trace of this in the game but it makes the other classes simpler +class CPlaceableTextTwoLines +{ +public: + CPlaceableText m_line1; + CPlaceableText m_line2; + + void SetColor(const CRGBA &color) { m_line1.SetColor(color); m_line2.SetColor(color); } + void SetAlpha(uint8 alpha) { m_line1.SetAlpha(alpha); m_line2.SetAlpha(alpha); } +}; + +// No trace of this in the game but it makes the other classes simpler +class CShadowInfo +{ +public: + bool m_bRightJustify; + bool m_bDropShadow; + CRGBA m_shadowColor; + CVector2D m_shadowOffset; + + CShadowInfo(void) + : m_bRightJustify(false), m_bDropShadow(false), + m_shadowColor(255, 255, 255, 255), + m_shadowOffset(-1.0f, -1.0f) {} + CRGBA GetShadowColor(void) { return m_shadowColor; } + void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset){ + m_bDropShadow = bDropShadows; + m_shadowColor = shadowColor; + m_shadowOffset = shadowOffset; + } +}; + +// No trace of this in the game but it makes the other classes simpler +class CSelectable +{ +public: + bool m_bSelected; + CRGBA m_selectedColor; + + CSelectable(void) : m_bSelected(false) {} + CRGBA GetSelectedColor(void) { return m_selectedColor; } +}; + +class CPlaceableShText : public CPlaceableText, public CShadowInfo +{ +public: + using CPlaceableText::SetPosition; + void SetPosition(float x, float y, bool bRightJustify) { SetPosition(x, y); m_bRightJustify = bRightJustify; } + void SetAlpha(uint8 alpha) { m_shadowColor.alpha = alpha; CPlaceableText::SetAlpha(alpha); } + + void Draw(float x, float y); + void Draw(const CRGBA &color, float x, float y); + // unused arguments it seems + void DrawShWrap(float x, float y, float wrapX, float wrapY) { Draw(x, y); } +}; + +class CPlaceableShTextTwoLines : public CPlaceableTextTwoLines, public CShadowInfo +{ +public: + void SetAlpha(uint8 alpha) { m_shadowColor.alpha = alpha; CPlaceableTextTwoLines::SetAlpha(alpha); } + + void Draw(float x, float y); + void Draw(const CRGBA &color, float x, float y); +}; + +class CPlaceableShOption : public CPlaceableShText, public CSelectable +{ +public: + void SetColors(const CRGBA &normal, const CRGBA &selection) { CPlaceableShText::SetColor(normal); m_selectedColor = selection; } + void SetAlpha(uint8 alpha) { m_selectedColor.alpha = alpha; CPlaceableShText::SetAlpha(alpha); } + + using CPlaceableShText::Draw; + void Draw(const CRGBA &highlightColor, float x, float y, bool bHighlight); +}; + +class CPlaceableShOptionTwoLines : public CPlaceableShTextTwoLines, public CSelectable +{ +public: + void SetColors(const CRGBA &normal, const CRGBA &selection) { CPlaceableShTextTwoLines::SetColor(normal); m_selectedColor = selection; } + void SetAlpha(uint8 alpha) { m_selectedColor.alpha = alpha; CPlaceableShTextTwoLines::SetAlpha(alpha); } + + using CPlaceableShTextTwoLines::Draw; + void Draw(const CRGBA &highlightColor, float x, float y, bool bHighlight); +}; + +class CPlaceableSprite +{ +public: + CSprite2d *m_pSprite; + CVector2D m_position; + CVector2D m_size; + CRGBA m_color; + + CPlaceableSprite(void) + : m_pSprite(nil), m_position(0.0f, 0.0f), + m_size(0.0f, 0.0f), m_color(255, 255, 255, 255) {} + + void SetPosition(float x, float y) { m_position.x = x; m_position.y = y; } + void SetAlpha(uint8 alpha) { m_color.alpha = alpha; } + + void Draw(float x, float y); + void Draw(const CRGBA &color, float x, float y); +}; + +class CPlaceableShSprite +{ +public: + CPlaceableSprite m_sprite; + CPlaceableSprite m_shadow; + bool m_bDropShadow; + + CPlaceableShSprite(void) : m_bDropShadow(false) {} + + void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset){ + m_bDropShadow = bDropShadows; + m_shadow.m_color = shadowColor; + m_shadow.m_position = shadowOffset; + } + void SetAlpha(uint8 alpha) { m_sprite.SetAlpha(alpha); m_shadow.SetAlpha(alpha); } + + void Draw(float x, float y); +}; + + +class CMenuBase +{ +public: + CVector2D m_position; + bool m_bTwoState; + + CMenuBase(void) + : m_position(0.0f, 0.0f), m_bTwoState(false) {} + void SetPosition(float x, float y) { m_position.x = x; m_position.y = y; } + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y) = 0; + virtual void DrawNormal(float x, float y) = 0; + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y) = 0; + virtual void SetAlpha(uint8 alpha) = 0; + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) = 0; + virtual bool GoNext(void) = 0; + virtual bool GoPrev(void) = 0; + virtual bool GoDown(void) = 0; + virtual bool GoUp(void) = 0; + virtual bool GoDownStill(void) = 0; + virtual bool GoUpStill(void) = 0; + virtual bool GoLeft(void) = 0; + virtual bool GoRight(void) = 0; + virtual bool GoLeftStill(void) = 0; + virtual bool GoRightStill(void) = 0; + virtual bool GoFirst(void) = 0; + virtual bool GoLast(void) = 0; + virtual void SelectCurrentOptionUnderCursor(void) = 0; + virtual void SelectDefaultCancelAction(void) = 0; + virtual void ActivateMenu(bool first) = 0; + virtual void DeactivateMenu(void) = 0; + virtual int GetMenuSelection(void) = 0; + virtual void SetMenuSelection(int selection) = 0; +}; + +class CMenuDummy : public CMenuBase +{ +public: + bool m_bActive; + + virtual void Draw(const CRGBA &, const CRGBA &, float x, float y) {} + virtual void DrawNormal(float x, float y) {} + virtual void DrawHighlighted(const CRGBA &, float x, float y) {} + virtual void SetAlpha(uint8 alpha) {} + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset) {} + virtual bool GoNext(void) { DeactivateMenu(); return false; } + virtual bool GoPrev(void) { DeactivateMenu(); return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { return true; } + virtual bool GoRight(void) { return true; } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { ActivateMenu(true); return true; } + virtual bool GoLast(void) { ActivateMenu(true); return true; } + virtual void SelectCurrentOptionUnderCursor(void) {} + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_bActive = true; } + virtual void DeactivateMenu(void) { m_bActive = false; } + virtual int GetMenuSelection(void) { return -1; } + virtual void SetMenuSelection(int) {} +}; + +class CMenuPictureAndText : public CMenuBase +{ +public: + int m_numSprites; + CPlaceableShSprite m_sprites[5]; + int m_numTexts; + CPlaceableShText m_texts[20]; + + CVector2D m_oldTextScale; + CVector2D m_textScale; + bool m_bSetTextScale; + + float m_wrapX; + float m_oldWrapx; + bool m_bWrap; + // missing some? + + + CMenuPictureAndText(void) + : m_numSprites(0), m_numTexts(0), + m_bSetTextScale(false), m_bWrap(false) {} + + void SetNewOldShadowWrapX(bool bWrapX, float newWrapX, float oldWrapX); + void SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale); + void SetTextsColor(const CRGBA &color); + void AddText(wchar *text, float positionX, float positionY, const CRGBA &color, bool bRightJustify); + void AddPicture(CSprite2d *sprite, CSprite2d *shadow, float positionX, float positionY, float width, float height, const CRGBA &color); + void AddPicture(CSprite2d *sprite, float positionX, float positionY, float width, float height, const CRGBA &color); + + virtual void Draw(const CRGBA &, const CRGBA &, float x, float y); + virtual void DrawNormal(float x, float y) { Draw(CRGBA(0,0,0,0), CRGBA(0,0,0,0), x, y); } + virtual void DrawHighlighted(const CRGBA &, float x, float y) { Draw(CRGBA(0,0,0,0), CRGBA(0,0,0,0), x, y); } + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void) { return false; } + virtual bool GoPrev(void) { return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { return true; } + virtual bool GoRight(void) { return true; } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { return false; } + virtual bool GoLast(void) { return false; } + virtual void SelectCurrentOptionUnderCursor(void) {} + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) {} + virtual void DeactivateMenu(void) {} + virtual int GetMenuSelection(void) { return -1; } + virtual void SetMenuSelection(int) {} +}; + +class CMenuMultiChoice : public CMenuBase +{ +public: + int m_numOptions; + CPlaceableShText m_title; + CPlaceableShOption m_options[NUM_MULTICHOICE_OPTIONS]; + int m_cursor; + CVector2D m_oldTextScale; + CVector2D m_textScale; + bool m_bSetTextScale; + bool m_bSetTitleTextScale; + + CMenuMultiChoice(void) + : m_numOptions(0), m_cursor(-1), + m_bSetTextScale(false), m_bSetTitleTextScale(false) {} + + void AddTitle(wchar *text, float positionX, float positionY, bool bRightJustify); + CPlaceableShOption *AddOption(wchar *text, float positionX, float positionY, bool bSelected, bool bRightJustify); + void SetColors(const CRGBA &title, const CRGBA &normal, const CRGBA &selected); + void SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void); + virtual bool GoPrev(void); + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { return GoPrev(); } + virtual bool GoRight(void) { return GoNext(); } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { m_cursor = 0; return true; } + virtual bool GoLast(void) { m_cursor = m_numOptions-1; return true; } + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_cursor = first ? 0 : m_numOptions-1; } + virtual void DeactivateMenu(void) { m_cursor = -1; } + virtual int GetMenuSelection(void); + virtual void SetMenuSelection(int selection); +}; + +class CMenuMultiChoiceTriggered : public CMenuMultiChoice +{ +public: + typedef void (*Trigger)(CMenuMultiChoiceTriggered *); + + Trigger m_triggers[NUM_MULTICHOICE_OPTIONS]; + Trigger m_defaultCancel; + + CMenuMultiChoiceTriggered(void) { Initialise(); } + + void Initialise(void); + CPlaceableShOption *AddOption(wchar *text, float positionX, float positionY, Trigger trigger, bool bSelected, bool bRightJustify); + + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void); +}; + +class CMenuMultiChoiceTriggeredAlways : public CMenuMultiChoiceTriggered +{ +public: + Trigger m_alwaysNormalTrigger; + Trigger m_alwaysHighlightTrigger; + Trigger m_alwaysTrigger; + + CMenuMultiChoiceTriggeredAlways(void) + : m_alwaysNormalTrigger(nil), m_alwaysHighlightTrigger(nil), m_alwaysTrigger(nil) {} + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); +}; + +class CMenuMultiChoicePictured : public CMenuMultiChoice +{ +public: + CPlaceableSprite m_sprites[NUM_MULTICHOICE_OPTIONS]; + bool m_bHasSprite[NUM_MULTICHOICE_OPTIONS]; + + CMenuMultiChoicePictured(void) { Initialise(); } + void Initialise(void); + using CMenuMultiChoice::AddOption; + CPlaceableShOption *AddOption(CSprite2d *sprite, float positionX, float positionY, const CVector2D &size, bool bSelected); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + // unnecessary - same as base class +// virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); +}; + +class CMenuMultiChoicePicturedTriggered : public CMenuMultiChoicePictured +{ +public: + typedef void (*Trigger)(CMenuMultiChoicePicturedTriggered *); + + Trigger m_triggers[NUM_MULTICHOICE_OPTIONS]; + Trigger m_defaultCancel; + + CMenuMultiChoicePicturedTriggered(void) { Initialise(); } + + void Initialise(void); + using CMenuMultiChoicePictured::AddOption; + CPlaceableShOption *AddOption(CSprite2d *sprite, float positionX, float positionY, const CVector2D &size, Trigger trigger, bool bSelected); + + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void); +}; + +struct FEC_MOVETAB +{ + int8 right; + int8 left; + int8 down; + int8 up; +}; + +class CMenuMultiChoicePicturedTriggeredAnyMove : public CMenuMultiChoicePicturedTriggered +{ +public: + FEC_MOVETAB m_moveTab[NUM_MULTICHOICE_OPTIONS]; + + CMenuMultiChoicePicturedTriggeredAnyMove(void) { Initialise(); } + + void Initialise(void); + using CMenuMultiChoicePicturedTriggered::AddOption; + CPlaceableShOption *AddOption(CSprite2d *sprite, FEC_MOVETAB *moveTab, float positionX, float positionY, const CVector2D &size, Trigger trigger, bool bSelected); + + virtual bool GoDown(void); + virtual bool GoUp(void); + virtual bool GoLeft(void); + virtual bool GoRight(void); +}; + +// copy of CMenuMultiChoice pretty much except for m_options type +class CMenuMultiChoiceTwoLines : public CMenuBase +{ +public: + int m_numOptions; + CPlaceableShText m_title; + CPlaceableShOptionTwoLines m_options[NUM_MULTICHOICE_OPTIONS]; + int m_cursor; + CVector2D m_oldTextScale; + CVector2D m_textScale; + bool m_bSetTextScale; + bool m_bSetTitleTextScale; + + CMenuMultiChoiceTwoLines(void) + : m_numOptions(0), m_cursor(-1), + m_bSetTextScale(false), m_bSetTitleTextScale(false) {} + + void AddTitle(wchar *text, float positionX, float positionY, bool bRightJustify); + CPlaceableShOptionTwoLines *AddOption(wchar *text, float positionX, float positionY, bool bSelected, bool bRightJustify); + CPlaceableShOptionTwoLines *AddOption(wchar *text1, float positionX1, float positionY1, wchar *text2, float positionX2, float positionY2, bool bSelected, bool bRightJustify); + void SetColors(const CRGBA &title, const CRGBA &normal, const CRGBA &selected); + void SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void); + virtual bool GoPrev(void); + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return true; } + virtual bool GoUpStill(void) { return true; } + virtual bool GoLeft(void) { return GoPrev(); } + virtual bool GoRight(void) { return GoNext(); } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { m_cursor = 0; return true; } + virtual bool GoLast(void) { m_cursor = m_numOptions-1; return true; } + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_cursor = first ? 0 : m_numOptions-1; } + virtual void DeactivateMenu(void) { m_cursor = -1; } + virtual int GetMenuSelection(void); + virtual void SetMenuSelection(int selection); +}; + +// copy of CMenuMultiChoiceTriggered except for m_options +class CMenuMultiChoiceTwoLinesTriggered : public CMenuMultiChoiceTwoLines +{ +public: + typedef void (*Trigger)(CMenuMultiChoiceTwoLinesTriggered *); + + Trigger m_triggers[NUM_MULTICHOICE_OPTIONS]; + Trigger m_defaultCancel; + + CMenuMultiChoiceTwoLinesTriggered(void) { Initialise(); } + + void Initialise(void); + CPlaceableShOptionTwoLines *AddOption(wchar *text, float positionX, float positionY, Trigger trigger, bool bSelected, bool bRightJustify); + CPlaceableShOptionTwoLines *AddOption(wchar *text1, float positionX1, float positionY1, wchar *text2, float positionX2, float positionY2, Trigger trigger, bool bSelected, bool bRightJustify); + + virtual void SelectCurrentOptionUnderCursor(void); + virtual void SelectDefaultCancelAction(void); +}; + + +class CMenuOnOff : public CMenuBase +{ +public: + CPlaceableShOption m_title; + CPlaceableShText m_options[2]; + bool m_bActive; + bool m_bSetTextScale; + bool m_bSetTitleTextScale; + CVector2D m_textScale; + CVector2D m_oldTextScale; + int m_type; // 0: on/off 1: yes/no + + void SetColors(const CRGBA &title, const CRGBA &options); + void SetNewOldTextScale(bool bTextScale, const CVector2D &newScale, const CVector2D &oldScale, bool bTitleTextScale); + void SetOptionPosition(float x, float y, bool bRightJustify); + void AddTitle(wchar *text, bool bSelected, float positionX, float positionY, bool bRightJustify); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void) { DeactivateMenu(); return false; } + virtual bool GoPrev(void) { DeactivateMenu(); return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { SelectCurrentOptionUnderCursor(); return true; } + virtual bool GoRight(void) { SelectCurrentOptionUnderCursor(); return true; } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { ActivateMenu(true); return true; } + virtual bool GoLast(void) { ActivateMenu(true); return true; } + virtual void SelectCurrentOptionUnderCursor(void) { m_title.m_bSelected ^= 1; } + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_bActive = true; } + virtual void DeactivateMenu(void) { m_bActive = false; } + virtual int GetMenuSelection(void) { return m_title.m_bSelected; } + virtual void SetMenuSelection(int selection) { m_title.m_bSelected = selection; } +}; + +class CMenuOnOffTriggered : public CMenuOnOff +{ +public: + typedef void (*Trigger)(CMenuOnOffTriggered *); + + Trigger m_trigger; + + void SetOptionPosition(float x, float y, Trigger trigger, bool bRightJustify); + + virtual void SelectCurrentOptionUnderCursor(void); +}; + +class CMenuSlider : public CMenuBase +{ +public: + CPlaceableShText m_title; + CPlaceableShText m_box; // not really a text + CRGBA m_colors[2]; // left and right + CVector2D m_size[2]; // left and right + int m_value; + CPlaceableShText m_percentageText; + bool m_bDrawPercentage; +// char field_8D; +// char field_8E; +// char field_8F; + uint8 m_someAlpha; +// char field_91; +// char field_92; +// char field_93; + bool m_bActive; + int m_style; + + static char Buf8[8]; + static wchar Buf16[8]; + + CMenuSlider(void) + : m_value(0), m_bDrawPercentage(false), m_bActive(false), m_style(0) + { + AddTickBox(0.0f, 0.0f, 100.0f, 10.0f, 10.0f); //todo + } + + void SetColors(const CRGBA &title, const CRGBA &percentage, const CRGBA &left, const CRGBA &right); + void DrawTicks(const CVector2D &position, const CVector2D &size, float heightRight, float level, const CRGBA &leftCol, const CRGBA &selCol, const CRGBA &rightCol, bool bShadow, const CVector2D &shadowOffset, const CRGBA &shadowColor); + void DrawTicks(const CVector2D &position, const CVector2D &size, float heightRight, float level, const CRGBA &leftCol, const CRGBA &rightCol, bool bShadow, const CVector2D &shadowOffset, const CRGBA &shadowColor); + void AddTickBox(float positionX, float positionY, float width, float heigthLeft, float heightRight); + void AddTitle(wchar *text, float positionX, float positionY); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y); + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void) { DeactivateMenu(); return false; } + virtual bool GoPrev(void) { DeactivateMenu(); return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { return false; } + virtual bool GoUpStill(void) { return false; } + virtual bool GoLeft(void) { if(m_value < 0) m_value = 0; return true; } + virtual bool GoRight(void) { if(m_value > 1000) m_value = 1000; return true; } + virtual bool GoLeftStill(void) { m_value -= 8; if(m_value < 0) m_value = 0; return true; } + virtual bool GoRightStill(void) { m_value += 8; if(m_value > 1000) m_value = 1000; return true; } + virtual bool GoFirst(void) { ActivateMenu(true); return true; } + virtual bool GoLast(void) { ActivateMenu(true); return true; } + virtual void SelectCurrentOptionUnderCursor(void) {} + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) { m_bActive = true; } + virtual void DeactivateMenu(void) { m_bActive = false; } + virtual int GetMenuSelection(void) { return m_value/10; } + virtual void SetMenuSelection(int selection) { m_value = selection*10; } +}; + +class CMenuSliderTriggered : public CMenuSlider +{ +public: + typedef void (*Trigger)(CMenuSliderTriggered *); + + Trigger m_trigger; + Trigger m_alwaysTrigger; + + CMenuSliderTriggered(void) + : m_trigger(nil), m_alwaysTrigger(nil) {} + + void AddTickBox(float positionX, float positionY, float width, float heigthLeft, float heightRight, Trigger trigger, Trigger alwaysTrigger); + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual bool GoLeft(void); + virtual bool GoRight(void); + virtual bool GoLeftStill(void); + virtual bool GoRightStill(void); +}; + + +class CMenuLineLister : public CMenuBase +{ +public: + float m_width; + float m_height; + int m_numLines; + CPlaceableShText m_linesLeft[NUM_LINELISTER_LINES_TOTAL]; + CPlaceableShText m_linesRight[NUM_LINELISTER_LINES_TOTAL]; + uint8 m_lineAlphas[NUM_LINELISTER_LINES_TOTAL]; + int8 m_lineFade[NUM_LINELISTER_LINES_TOTAL]; + float m_scrollPosition; + float m_scrollSpeed; + int field_10E8; + float m_lineSpacing; + + CMenuLineLister(void); + + void SetLinesColor(const CRGBA &color); + void ResetNumberOfTextLines(void); + bool AddTextLine(wchar *left, wchar *right); + + CPlaceableShText *GetLeftLine(int i) { return &m_linesLeft[(i%NUM_LINELISTER_LINES) + 15]; }; + CPlaceableShText *GetRightLine(int i) { return &m_linesRight[(i%NUM_LINELISTER_LINES) + 15]; }; + + virtual void Draw(const CRGBA &optionHighlight, const CRGBA &titleHighlight, float x, float y); + virtual void DrawNormal(float x, float y) { Draw(CRGBA(0,0,0,0), CRGBA(0,0,0,0), x, y); } + virtual void DrawHighlighted(const CRGBA &titleHighlight, float x, float y) { Draw(CRGBA(0,0,0,0), CRGBA(0,0,0,0), x, y); } + virtual void SetAlpha(uint8 alpha); + virtual void SetShadows(bool bDropShadows, const CRGBA &shadowColor, const CVector2D &shadowOffset); + virtual bool GoNext(void) { return false; } + virtual bool GoPrev(void) { return false; } + virtual bool GoDown(void) { return GoNext(); } + virtual bool GoUp(void) { return GoPrev(); } + virtual bool GoDownStill(void) { m_scrollSpeed = 0.0f; return true; } + virtual bool GoUpStill(void) { m_scrollSpeed *= 6.0f; return true; } + virtual bool GoLeft(void) { return true; } + virtual bool GoRight(void) { return true; } + virtual bool GoLeftStill(void) { return true; } + virtual bool GoRightStill(void) { return true; } + virtual bool GoFirst(void) { return true; } + virtual bool GoLast(void) { return true; } + virtual void SelectCurrentOptionUnderCursor(void) {} + virtual void SelectDefaultCancelAction(void) {} + virtual void ActivateMenu(bool first) {} + virtual void DeactivateMenu(void) {} + virtual int GetMenuSelection(void) { return -1; } + virtual void SetMenuSelection(int selection) {} +}; + +class CMenuPage +{ +public: + CMenuBase *m_controls[NUM_PAGE_WIDGETS]; + int m_numControls; + CMenuBase *m_pCurrentControl; + int m_cursor; + + CMenuPage(void) { Initialise(); } + void Initialise(void); + bool AddMenu(CMenuBase *widget); + + bool IsActiveMenuTwoState(void); + void ActiveMenuTwoState_SelectNextPosition(void); + void Draw(const CRGBA &,const CRGBA &, float, float); + void DrawHighlighted(const CRGBA &titleHighlight, float x, float y); + void DrawNormal(float x, float y); + void ActivatePage(void); + void SetAlpha(uint8 alpha); + void SetShadows(bool, const CRGBA &, const CVector2D &); + void GoPrev(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoPrev()) m_pCurrentControl->GoLast(); } } + void GoNext(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoNext()) m_pCurrentControl->GoFirst(); } } + void GoLeft(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoLeft()) m_pCurrentControl->GoLast(); } } + void GoRight(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoRight()) m_pCurrentControl->GoFirst(); } } + void GoUp(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoUp()) m_pCurrentControl->GoLast(); } } + void GoDown(void) { if(m_pCurrentControl) { if(!m_pCurrentControl->GoDown()) m_pCurrentControl->GoFirst(); } } + void GoLeftStill(void) { if(m_pCurrentControl) m_pCurrentControl->GoLeftStill(); } + void GoRightStill(void) { if(m_pCurrentControl) m_pCurrentControl->GoRightStill(); } + void GoUpStill(void) { if(m_pCurrentControl) m_pCurrentControl->GoUpStill(); } + void GoDownStill(void) { if(m_pCurrentControl) m_pCurrentControl->GoDownStill(); } + void SelectDefaultCancelAction(void) { if(m_pCurrentControl) m_pCurrentControl->SelectDefaultCancelAction(); } + void SelectCurrentOptionUnderCursor(void) { if(m_pCurrentControl) m_pCurrentControl->SelectCurrentOptionUnderCursor(); } + + virtual void GoUpMenuOnPage(void); + virtual void GoDownMenuOnPage(void); + virtual void GoLeftMenuOnPage(void); + virtual void GoRightMenuOnPage(void); +}; + +class CMenuPageAnyMove : public CMenuPage +{ +public: + FEC_MOVETAB m_moveTab[NUM_PAGE_WIDGETS]; + + CMenuPageAnyMove(void) { Initialise(); } + void Initialise(void); + using CMenuPage::AddMenu; + bool AddMenu(CMenuBase *widget, FEC_MOVETAB *moveTab); + + virtual void GoUpMenuOnPage(void); + virtual void GoDownMenuOnPage(void); + virtual void GoLeftMenuOnPage(void); + virtual void GoRightMenuOnPage(void); +}; \ No newline at end of file diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp new file mode 100644 index 0000000..666774f --- /dev/null +++ b/src/core/Frontend.cpp @@ -0,0 +1,6802 @@ +#define FORCE_PC_SCALING +#define WITHWINDOWS +#define WITHDINPUT +#include "common.h" +#ifndef PS2_MENU +#include "crossplatform.h" +#include "platform.h" +#include "Frontend.h" +#include "Font.h" +#include "Pad.h" +#include "Text.h" +#include "main.h" +#include "RwHelper.h" +#include "Timer.h" +#include "Game.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "Streaming.h" +#include "TxdStore.h" +#include "General.h" +#include "GenericGameStorage.h" +#include "Script.h" +#include "Camera.h" +#include "ControllerConfig.h" +#include "Vehicle.h" +#include "MBlur.h" +#include "PlayerSkin.h" +#include "PlayerInfo.h" +#include "World.h" +#include "Renderer.h" +#include "CdStream.h" +#include "Radar.h" +#include "Stats.h" +#include "Messages.h" +#include "FileLoader.h" +#include "frontendoption.h" + +// Game has colors inlined in code. +// For easier modification we collect them here: +const CRGBA LABEL_COLOR(235, 170, 50, 255); +const CRGBA SELECTION_HIGHLIGHTBG_COLOR(100, 200, 50, 50); +const CRGBA MENUOPTION_COLOR = LABEL_COLOR; +const CRGBA SELECTEDMENUOPTION_COLOR(255, 217, 106, 255); +const CRGBA HEADER_COLOR(0, 0, 0, 255); +const CRGBA DARKMENUOPTION_COLOR(155, 117, 6, 255); +const CRGBA SLIDERON_COLOR = SELECTEDMENUOPTION_COLOR; +const CRGBA SLIDEROFF_COLOR(185, 120, 0, 255); +const CRGBA LIST_BACKGROUND_COLOR(200, 200, 50, 50); +const CRGBA LIST_OPTION_COLOR(155, 155, 155, 255); +const CRGBA INACTIVE_RADIO_COLOR(225, 0, 0, 170); +const CRGBA SCROLLBAR_COLOR = LABEL_COLOR; +const CRGBA CONTSETUP_HIGHLIGHTBG_COLOR(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, 210); +const CRGBA CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, 150); + +// This is PS2 menu leftover, and variable name is original. They forgot it here and used in PrintBriefs once (but didn't use the output) +#if defined(FIX_BUGS) && !defined(PS2_LIKE_MENU) +const CRGBA TEXT_COLOR = LABEL_COLOR; +#else +const CRGBA TEXT_COLOR = CRGBA(150, 110, 30, 255); // PS2 option color +#endif + +#define TIDY_UP_PBP // ProcessButtonPresses +#define MAX_VISIBLE_LIST_ROW 30 +#define SCROLLBAR_MAX_HEIGHT 263.0f // not in end result +#define SCROLLABLE_PAGES +#define RED_DELETE_BACKGROUND + +#ifdef SCROLLABLE_STATS_PAGE +#define isPlainTextScreen(screen) (screen == MENUPAGE_BRIEFS) +#else +#define isPlainTextScreen(screen) (screen == MENUPAGE_BRIEFS || screen == MENUPAGE_STATS) +#endif + +#define hasNativeList(screen) (screen == MENUPAGE_MULTIPLAYER_FIND_GAME || screen == MENUPAGE_SKIN_SELECT \ + || screen == MENUPAGE_KEYBOARD_CONTROLS) + +#ifdef SCROLLABLE_PAGES +#define MAX_VISIBLE_OPTION 12 +#define MAX_VISIBLE_OPTION_ON_SCREEN (hasNativeList(m_nCurrScreen) ? MAX_VISIBLE_LIST_ROW : MAX_VISIBLE_OPTION) +#define SCREEN_HAS_AUTO_SCROLLBAR (m_nTotalListRow > MAX_VISIBLE_OPTION && !hasNativeList(m_nCurrScreen)) + +int GetOptionCount(int screen) +{ + int i = 0; + for (; i < NUM_MENUROWS && aScreens[screen].m_aEntries[i].m_Action != MENUACTION_NOTHING; i++); + return i; +} + +#define SETUP_SCROLLING(screen) \ + if (!hasNativeList(screen)) { \ + m_nTotalListRow = GetOptionCount(screen); \ + if (m_nTotalListRow > MAX_VISIBLE_OPTION) { \ + m_nSelectedListRow = 0; \ + m_nFirstVisibleRowOnList = 0; \ + m_nScrollbarTopMargin = 0; \ + } \ + } +#else +#define MAX_VISIBLE_OPTION_ON_SCREEN MAX_VISIBLE_LIST_ROW +#define SETUP_SCROLLING(screen) +#endif + +#ifdef TRIANGLE_BACK_BUTTON +#define GetBackJustUp GetTriangleJustUp +#define GetBackJustDown GetTriangleJustDown +#elif defined(CIRCLE_BACK_BUTTON) +#define GetBackJustUp GetCircleJustUp +#define GetBackJustDown GetCircleJustDown +#else +#define GetBackJustUp GetSquareJustUp +#define GetBackJustDown GetSquareJustDown +#endif + +#ifdef MENU_MAP +bool CMenuManager::bMenuMapActive = false; +float CMenuManager::fMapSize; +float CMenuManager::fMapCenterY; +float CMenuManager::fMapCenterX; +#endif + +#ifdef PS2_LIKE_MENU +BottomBarOption bbNames[8]; +int bbTabCount = 0; +bool bottomBarActive = false; +int pendingScreen = -1; +int pendingOption = -1; +int curBottomBarOption = -1; +int hoveredBottomBarOption = -1; +#endif + +#ifdef CUTSCENE_BORDERS_SWITCH +bool CMenuManager::m_PrefsCutsceneBorders = true; +#endif + +#ifdef MULTISAMPLING +int8 CMenuManager::m_nPrefsMSAALevel = 0; +int8 CMenuManager::m_nDisplayMSAALevel = 0; +#endif + +#ifdef NO_ISLAND_LOADING +int8 CMenuManager::m_PrefsIslandLoading = ISLAND_LOADING_LOW; +#endif + +#ifdef GAMEPAD_MENU +#ifdef __SWITCH__ +int8 CMenuManager::m_PrefsControllerType = CONTROLLER_NINTENDO_SWITCH; +#else +int8 CMenuManager::m_PrefsControllerType = CONTROLLER_XBOXONE; +#endif +#endif + +int32 CMenuManager::OS_Language = LANG_ENGLISH; +int8 CMenuManager::m_PrefsUseVibration; +int8 CMenuManager::m_DisplayControllerOnFoot; +int8 CMenuManager::m_PrefsVsync = 1; +int8 CMenuManager::m_PrefsVsyncDisp = 1; +int8 CMenuManager::m_PrefsFrameLimiter = 1; +int8 CMenuManager::m_PrefsShowSubtitles = 1; +int8 CMenuManager::m_PrefsSpeakers; +int32 CMenuManager::m_ControlMethod; +int8 CMenuManager::m_PrefsDMA = 1; +int32 CMenuManager::m_PrefsLanguage; +uint8 CMenuManager::m_PrefsStereoMono; // unused except restore settings + +bool CMenuManager::m_PrefsAllowNastyGame = true; +bool CMenuManager::m_bStartUpFrontEndRequested; +bool CMenuManager::m_bShutDownFrontEndRequested; + +#ifdef ASPECT_RATIO_SCALE +int8 CMenuManager::m_PrefsUseWideScreen = AR_AUTO; +#else +int8 CMenuManager::m_PrefsUseWideScreen; +#endif + +int8 CMenuManager::m_PrefsRadioStation; +int32 CMenuManager::m_PrefsBrightness = 256; +float CMenuManager::m_PrefsLOD = CRenderer::ms_lodDistScale; +int8 CMenuManager::m_bFrontEnd_ReloadObrTxtGxt; +int32 CMenuManager::m_PrefsMusicVolume = 102; +int32 CMenuManager::m_PrefsSfxVolume = 102; + +char CMenuManager::m_PrefsSkinFile[256] = DEFAULT_SKIN_NAME; + +int32 CMenuManager::m_KeyPressedCode = -1; + +float MENU_TEXT_SIZE_X = SMALLTEXT_X_SCALE; +float MENU_TEXT_SIZE_Y = SMALLTEXT_Y_SCALE; + +bool holdingScrollBar; // *(bool*)0x628D59; // not original name +int32 CMenuManager::m_SelectedMap; +int32 CMenuManager::m_SelectedGameType; + +// Used in a hidden menu +uint8 CMenuManager::m_PrefsPlayerRed = 255; +uint8 CMenuManager::m_PrefsPlayerGreen = 128; +uint8 CMenuManager::m_PrefsPlayerBlue; // why?? + +CMenuManager FrontEndMenuManager; + +uint32 TimeToStopPadShaking; +char *pEditString; +int32 *pControlEdit; +bool DisplayComboButtonErrMsg; +int32 MouseButtonJustClicked; +int32 JoyButtonJustClicked; +//int32 *pControlTemp = 0; + +#ifndef MASTER +bool CMenuManager::m_PrefsMarketing = false; +bool CMenuManager::m_PrefsDisableTutorials = false; +#endif // !MASTER + +const char* FrontendFilenames[][2] = { + {"fe2_mainpanel_ul", "" }, + {"fe2_mainpanel_ur", "" }, + {"fe2_mainpanel_dl", "" }, + {"fe2_mainpanel_dr", "" }, + {"fe2_mainpanel_dr2", "" }, + {"fe2_tabactive", "" }, + {"fe_iconbrief", "" }, + {"fe_iconstats", "" }, + {"fe_iconcontrols", "" }, + {"fe_iconsave", "" }, + {"fe_iconaudio", "" }, + {"fe_icondisplay", "" }, + {"fe_iconlanguage", "" }, + {"fe_controller", "" }, + {"fe_controllersh", "" }, + {"fe_arrows1", "" }, + {"fe_arrows2", "" }, + {"fe_arrows3", "" }, + {"fe_arrows4", "" }, + {"fe_radio1", "" }, + {"fe_radio2", "" }, + {"fe_radio3", "" }, + {"fe_radio4", "" }, + {"fe_radio5", "" }, + {"fe_radio6", "" }, + {"fe_radio7", "" }, + {"fe_radio8", "" }, + {"fe_radio9", "" }, +}; + +#ifdef MENU_MAP +const char* MapFilenames[][2] = { + {"mapMid01", "mapMid01A"}, + {"mapMid02", "mapMid02A"}, + {"mapMid03", "mapMid03A"}, + {"mapBot01", "mapBot01A"}, + {"mapBot02", "mapBot02A"}, + {"mapBot03", "mapBot03A"}, + {"mapTop01", "mapTop01A"}, + {"mapTop02", "mapTop02A"}, + {"mapTop03", "mapTop03A"}, +}; +CSprite2d CMenuManager::m_aMapSprites[NUM_MAP_SPRITES]; +#endif + +// 0x5F3344 +const char* MenuFilenames[][2] = { + {"connection24", ""}, + {"findgame24", ""}, + {"hostgame24", ""}, + {"mainmenu24", ""}, + {"Playersetup24", ""}, + {"singleplayer24", ""}, + {"multiplayer24", ""}, + {"dmalogo128", "dmalogo128m"}, + {"gtaLogo128", "gtaLogo128"}, + {"rockstarLogo128", "rockstarlogo128m"}, + {"gamespy256", "gamespy256a"}, + {"mouse", "mousetimera"}, + {"mousetimer", "mousetimera"}, + {"mp3logo", "mp3logoA"}, + {"downOFF", "buttonA"}, + {"downON", "buttonA"}, + {"upOff", "buttonA"}, + {"upON", "buttonA"}, + {"gta3logo256", "gta3logo256m"}, + { nil, nil } +}; + +#define MENU_X_RIGHT_ALIGNED(x) SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH - (x)) + +#ifdef ASPECT_RATIO_SCALE +// All of the defines below replace the StretchX function. Otherwise use SCREEN_SCALE_X. +#define MENU_X_LEFT_ALIGNED(x) SCALE_AND_CENTER_X(x) +#define MENU_X(x) SCREEN_SCALE_X(x) +#define MENU_Y(y) SCREEN_SCALE_Y(y) +#else +#define MENU_X_LEFT_ALIGNED(x) StretchX(x) +#define MENU_X(x) StretchX(x) +#define MENU_Y(y) StretchY(y) +#endif + +#ifdef PS2_LIKE_MENU +#define PAGE_NAME_X MENU_X_RIGHT_ALIGNED +#else +#define PAGE_NAME_X SCREEN_SCALE_FROM_RIGHT +#endif + +// Seperate func. in VC +#define ChangeScreen(screen, option, updateDelay, clearAlpha) \ + do { \ + m_nPrevScreen = m_nCurrScreen; \ + int newOpt = option; \ + SETUP_SCROLLING(screen) \ + m_nCurrScreen = screen; \ + m_nCurrOption = newOpt; \ + if(updateDelay) \ + m_nScreenChangeDelayTimer = CTimer::GetTimeInMillisecondsPauseMode(); \ + if(clearAlpha) \ + m_nMenuFadeAlpha = 0; \ + } while(0) + +#define SET_FONT_FOR_MENU_HEADER \ + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, FadeIn(255))); \ + CFont::SetRightJustifyOn(); \ + CFont::SetScale(MENU_X(MENUHEADER_WIDTH), MENU_Y(MENUHEADER_HEIGHT)); \ + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + +#define RESET_FONT_FOR_NEW_PAGE \ + CFont::SetBackgroundOff(); \ + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT), MENU_Y(MENUACTION_SCALE_MULT)); \ + CFont::SetPropOn(); \ + CFont::SetCentreOff(); \ + CFont::SetJustifyOn(); \ + CFont::SetRightJustifyOff(); \ + CFont::SetBackGroundOnlyTextOn(); \ + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(MENU_X_MARGIN)); \ + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(MENU_X_MARGIN - 2.0f)); + +#define SET_FONT_FOR_HELPER_TEXT \ + CFont::SetCentreOn(); \ + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE), MENU_Y(SMALLESTTEXT_Y_SCALE)); \ + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + +#define SET_FONT_FOR_LIST_ITEM \ + CFont::SetRightJustifyOff(); \ + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE), MENU_Y(SMALLESTTEXT_Y_SCALE)); \ + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + +// value must be between 0.0-1.0 +#define ProcessSlider(value, increaseAction, decreaseAction, hoverStartX, hoverEndX) \ + do { \ + lastActiveBarX = DisplaySlider(MENU_X_RIGHT_ALIGNED(MENUSLIDER_X + columnWidth), MENU_Y(bitAboveNextItemY), MENU_Y(smallestSliderBar), MENU_Y(usableLineHeight), MENU_X(MENUSLIDER_UNK), value); \ + if (i != m_nCurrOption || !itemsAreSelectable) \ + break; \ + \ + if (CheckHover(hoverStartX, lastActiveBarX - MENU_X(10.0f), MENU_Y(nextYToUse), MENU_Y(28.0f + nextYToUse))) \ + m_nHoverOption = decreaseAction; \ + \ + if (!CheckHover(MENU_X(10.0f) + lastActiveBarX, hoverEndX, MENU_Y(nextYToUse), MENU_Y(28.0f + nextYToUse))) \ + break; \ + \ + m_nHoverOption = increaseAction; \ + if (m_nMousePosX < MENU_X_RIGHT_ALIGNED(MENUSLIDER_X + columnWidth)) \ + m_nHoverOption = HOVEROPTION_NOT_HOVERING; \ + } while(0) + +#define ProcessRadioIcon(sprite, x, y, radioId, hoverOpt) \ + do { \ + sprite.Draw(x, y, MENU_X(MENURADIO_ICON_SCALE), MENU_Y(MENURADIO_ICON_SCALE), radioId == m_PrefsRadioStation ? CRGBA(255, 255, 255, 255) : \ + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, INACTIVE_RADIO_COLOR.a)); \ + if (CheckHover(x, x + MENU_X(MENURADIO_ICON_SCALE), y, y + MENU_Y(MENURADIO_ICON_SCALE))) \ + m_nHoverOption = hoverOpt; \ + } while (0) + +// --- Functions not in the game/inlined starts + +void +CMenuManager::ScrollUpListByOne() +{ + if (m_nSelectedListRow == m_nFirstVisibleRowOnList) { + if (m_nFirstVisibleRowOnList > 0) { + m_nSelectedListRow--; + m_nFirstVisibleRowOnList--; + m_nScrollbarTopMargin -= SCROLLBAR_MAX_HEIGHT / m_nTotalListRow; + } + } else { + m_nSelectedListRow--; + } +} + +void +CMenuManager::ScrollDownListByOne() +{ + if (m_nSelectedListRow == m_nFirstVisibleRowOnList + MAX_VISIBLE_OPTION_ON_SCREEN - 1) { + if (m_nFirstVisibleRowOnList < m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN) { + m_nSelectedListRow++; + m_nFirstVisibleRowOnList++; + m_nScrollbarTopMargin += SCROLLBAR_MAX_HEIGHT / m_nTotalListRow; + } + } else { + if (m_nSelectedListRow < m_nTotalListRow - 1) { + m_nSelectedListRow++; + } + } +} + +void +CMenuManager::PageUpList(bool playSoundOnSuccess) +{ + if (m_nTotalListRow > MAX_VISIBLE_OPTION_ON_SCREEN) { + if (m_nFirstVisibleRowOnList > 0) { + if(playSoundOnSuccess) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nFirstVisibleRowOnList = Max(0, m_nFirstVisibleRowOnList - MAX_VISIBLE_OPTION_ON_SCREEN); + m_nSelectedListRow = Min(m_nSelectedListRow, m_nFirstVisibleRowOnList + MAX_VISIBLE_OPTION_ON_SCREEN - 1); + } else { + m_nFirstVisibleRowOnList = 0; + m_nSelectedListRow = 0; + } + m_nScrollbarTopMargin = (SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) * m_nFirstVisibleRowOnList; + } +} + +void +CMenuManager::PageDownList(bool playSoundOnSuccess) +{ + if (m_nTotalListRow > MAX_VISIBLE_OPTION_ON_SCREEN) { + if (m_nFirstVisibleRowOnList < m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN) { + if(playSoundOnSuccess) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nFirstVisibleRowOnList = Min(m_nFirstVisibleRowOnList + MAX_VISIBLE_OPTION_ON_SCREEN, m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN); + m_nSelectedListRow = Max(m_nSelectedListRow, m_nFirstVisibleRowOnList); + } else { + m_nFirstVisibleRowOnList = m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN; + m_nSelectedListRow = m_nTotalListRow - 1; + } + m_nScrollbarTopMargin = (SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) * m_nFirstVisibleRowOnList; + } +} + +#ifdef CUSTOM_FRONTEND_OPTIONS +bool ScreenHasOption(int screen, const char* gxtKey) +{ + for (int i = 0; i < NUM_MENUROWS; i++) { + if (strcmp(gxtKey, aScreens[screen].m_aEntries[i].m_EntryName) == 0) + return true; + } + return false; +} +#endif + +void +CMenuManager::ThingsToDoBeforeGoingBack() +{ + if ((m_nCurrScreen == MENUPAGE_SKIN_SELECT) && strcmp(m_aSkinName, m_PrefsSkinFile) != 0) { + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); +#ifdef CUSTOM_FRONTEND_OPTIONS + } else if (ScreenHasOption(m_nCurrScreen, "FEA_RSS")) { +#else + } else if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { +#endif + if (m_nPrefsAudio3DProviderIndex != -1) + m_nPrefsAudio3DProviderIndex = DMAudio.GetCurrent3DProviderIndex(); +#ifdef TIDY_UP_PBP + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + } else if (ScreenHasOption(m_nCurrScreen, "FED_RES")) { +#else + } else if (m_nCurrScreen == MENUPAGE_DISPLAY_SETTINGS) { +#endif + m_nDisplayVideoMode = m_nPrefsVideoMode; + } + + if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + CPlayerSkin::EndFrontendSkinEdit(); + } + + if ((m_nCurrScreen == MENUPAGE_SKIN_SELECT) || (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS)) { + m_nTotalListRow = 0; + } + +#ifdef SCROLLABLE_PAGES + if (SCREEN_HAS_AUTO_SCROLLBAR) { + m_nSelectedListRow = 0; + m_nFirstVisibleRowOnList = 0; + m_nScrollbarTopMargin = 0; + } +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption]; + + if (option.m_Action == MENUACTION_CFO_DYNAMIC) + if(option.m_CFODynamic->buttonPressFunc) + option.m_CFODynamic->buttonPressFunc(FEOPTION_ACTION_FOCUSLOSS); + + if (option.m_Action == MENUACTION_CFO_SELECT && option.m_CFOSelect->onlyApplyOnEnter && option.m_CFOSelect->lastSavedValue != option.m_CFOSelect->displayedValue) + option.m_CFOSelect->displayedValue = *(int8*)option.m_CFO->value = option.m_CFOSelect->lastSavedValue; + + if (aScreens[m_nCurrScreen].returnPrevPageFunc) { + aScreens[m_nCurrScreen].returnPrevPageFunc(); + } +#endif +} + +int8 +CMenuManager::GetPreviousPageOption() +{ +#ifndef CUSTOM_FRONTEND_OPTIONS + return !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_ParentEntry[1] : aScreens[m_nCurrScreen].m_ParentEntry[0]; +#else + int8 prevPage = !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0]; + + if (prevPage == -1) // Game also does same + return 0; + + prevPage = prevPage == MENUPAGE_NONE ? (!m_bGameNotLoaded ? MENUPAGE_PAUSE_MENU : MENUPAGE_START_MENU) : prevPage; + + for (int i = 0; i < NUM_MENUROWS; i++) { + if (aScreens[prevPage].m_aEntries[i].m_Action >= MENUACTION_NOTHING) { // CFO check + if (aScreens[prevPage].m_aEntries[i].m_TargetMenu == m_nCurrScreen) { + return i; + } + } + } + + // This shouldn't happen + return 0; +#endif +} + +void +CMenuManager::ProcessList(bool &goBack, bool &optionSelected) +{ + if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + m_nTotalListRow = m_nSkinsTotal; + } + if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + // GetNumOptionsCntrlConfigScreens would have been a better choice + m_nTotalListRow = m_ControlMethod == CONTROL_CLASSIC ? 30 : 25; + if (m_nSelectedListRow > m_nTotalListRow) + m_nSelectedListRow = m_nTotalListRow - 1; + } + +#ifndef TIDY_UP_PBP + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + m_bShowMouse = 0; + optionSelected = true; + } +#endif + if (CPad::GetPad(0)->GetBackspaceJustDown() && m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS && !field_535) { + if (m_nCurrExLayer == HOVEROPTION_LIST) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + m_bWaitingForNewKeyBind = true; + m_bStartWaitingForKeyBind = true; + m_bKeyChangeNotProcessed = true; + pControlEdit = &m_KeyPressedCode; + } + } else { + field_535 = false; + } + + static uint32 lastTimeClickedScrollButton = 0; + + if (CTimer::GetTimeInMillisecondsPauseMode() - lastTimeClickedScrollButton >= 200) { + m_bPressedPgUpOnList = false; + m_bPressedPgDnOnList = false; + m_bPressedUpOnList = false; + m_bPressedDownOnList = false; + m_bPressedScrollButton = false; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + } + + if (CPad::GetPad(0)->GetTabJustDown()) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + m_bShowMouse = false; + switch (m_nCurrExLayer) { + case HOVEROPTION_BACK: + default: + m_nCurrExLayer = HOVEROPTION_LIST; + break; + case HOVEROPTION_LIST: + m_nCurrExLayer = HOVEROPTION_USESKIN; + break; + case HOVEROPTION_USESKIN: + m_nCurrExLayer = HOVEROPTION_BACK; + } + if (((m_nCurrScreen == MENUPAGE_SKIN_SELECT) && (m_nCurrExLayer == HOVEROPTION_USESKIN)) && strcmp(m_aSkinName, m_PrefsSkinFile) == 0) { + m_nCurrExLayer = HOVEROPTION_BACK; + } + if ((m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) && (m_nCurrExLayer == HOVEROPTION_USESKIN)) { + m_nCurrExLayer = HOVEROPTION_BACK; + } + } + + bool pressed = false; + if (CPad::GetPad(0)->GetUp() || CPad::GetPad(0)->GetAnaloguePadUp() || CPad::GetPad(0)->GetDPadUpJustDown()) { + m_bShowMouse = false; + pressed = true; + } else if (CPad::GetPad(0)->GetMouseWheelUpJustUp()) { + m_bShowMouse = true; + pressed = true; + } + + // Up + if (pressed) { + m_nCurrExLayer = HOVEROPTION_LIST; + if (!m_bPressedUpOnList) { + m_bPressedUpOnList = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + ScrollUpListByOne(); + } + } else { + m_bPressedUpOnList = false; + } + + pressed = false; + if (CPad::GetPad(0)->GetDown() || CPad::GetPad(0)->GetAnaloguePadDown() || CPad::GetPad(0)->GetDPadDownJustDown()) { + m_bShowMouse = false; + pressed = true; + } else if (CPad::GetPad(0)->GetMouseWheelDownJustDown()) { + m_bShowMouse = true; + pressed = true; + } + + // Down + if (pressed) { + m_nCurrExLayer = HOVEROPTION_LIST; + if (!m_bPressedDownOnList) { + m_bPressedDownOnList = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + ScrollDownListByOne(); + } + } else { + m_bPressedDownOnList = false; + } + + if (m_nCurrScreen != MENUPAGE_KEYBOARD_CONTROLS) { + if (!CPad::GetPad(0)->GetPageUp()) { + m_bPressedPgUpOnList = false; + } else { + m_nCurrExLayer = HOVEROPTION_LIST; + if (!m_bPressedPgUpOnList) { + m_bPressedPgUpOnList = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + PageUpList(false); + } + } + if (!CPad::GetPad(0)->GetPageDown()) { + m_bPressedPgDnOnList = false; + } else { + m_nCurrExLayer = HOVEROPTION_LIST; + if (!m_bPressedPgDnOnList) { + m_bPressedPgDnOnList = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + PageDownList(false); + } + } + if (CPad::GetPad(0)->GetHome()) { + m_nCurrExLayer = HOVEROPTION_LIST; + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + if (m_nTotalListRow >= MAX_VISIBLE_OPTION_ON_SCREEN) { + m_nFirstVisibleRowOnList = 0; + } + m_nSelectedListRow = 0; + m_nScrollbarTopMargin = (SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) * m_nFirstVisibleRowOnList; + } + if (CPad::GetPad(0)->GetEnd()) { + m_nCurrExLayer = HOVEROPTION_LIST; + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + if (m_nTotalListRow >= MAX_VISIBLE_OPTION_ON_SCREEN) { + m_nFirstVisibleRowOnList = m_nTotalListRow - MAX_VISIBLE_OPTION_ON_SCREEN; + } + m_nSelectedListRow = m_nTotalListRow - 1; + m_nScrollbarTopMargin = (SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) * m_nFirstVisibleRowOnList; + } + } + +#ifndef TIDY_UP_PBP + if (CPad::GetPad(0)->GetEscapeJustDown() || CPad::GetPad(0)->GetBackJustDown()) { + m_bShowMouse = false; + goBack = true; + } +#endif + + if (CPad::GetPad(0)->GetLeftMouseJustDown()) { + switch (m_nHoverOption) { + case HOVEROPTION_BACK: + goBack = true; + break; + case HOVEROPTION_PAGEUP: + PageUpList(true); + break; + case HOVEROPTION_PAGEDOWN: + PageDownList(true); + break; + case HOVEROPTION_USESKIN: + if (m_nSkinsTotal > 0) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_pSelectedSkin = m_pSkinListHead.nextSkin; + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + SaveSettings(); + } + } + } + + if (CPad::GetPad(0)->GetLeftMouseJustDown()) { + switch (m_nHoverOption) { + case HOVEROPTION_OVER_SCROLL_UP: + m_nHoverOption = HOVEROPTION_CLICKED_SCROLL_UP; + break; + case HOVEROPTION_OVER_SCROLL_DOWN: + m_nHoverOption = HOVEROPTION_CLICKED_SCROLL_DOWN; + break; + case HOVEROPTION_LIST: + m_nHoverOption = HOVEROPTION_SKIN; + } + } else if ((CPad::GetPad(0)->GetLeftMouseJustUp()) + && ((m_nHoverOption == HOVEROPTION_CLICKED_SCROLL_UP || (m_nHoverOption == HOVEROPTION_CLICKED_SCROLL_DOWN)))) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + + if (!CPad::GetPad(0)->GetLeftMouse()) { + holdingScrollBar = false; + } else { + if ((m_nHoverOption == HOVEROPTION_HOLDING_SCROLLBAR) || holdingScrollBar) { + holdingScrollBar = true; + // TODO: This part is a bit hard to reverse. Not much code tho + assert(0 && "Holding scrollbar isn't done yet"); + } else { + switch (m_nHoverOption) { + case HOVEROPTION_OVER_SCROLL_UP: + case HOVEROPTION_CLICKED_SCROLL_UP: + if (!m_bPressedScrollButton) { + m_bPressedScrollButton = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + ScrollUpListByOne(); + } + break; + case HOVEROPTION_OVER_SCROLL_DOWN: + case HOVEROPTION_CLICKED_SCROLL_DOWN: + if (!m_bPressedScrollButton) { + m_bPressedScrollButton = true; + lastTimeClickedScrollButton = CTimer::GetTimeInMillisecondsPauseMode(); + ScrollDownListByOne(); + } + break; + default: + m_bPressedScrollButton = false; + } + } + } +} +// ------ Functions not in the game/inlined ends + +void +CMenuManager::BuildStatLine(Const char *text, void *stat, bool itsFloat, void *stat2) +{ + if (!text) + return; + +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese() && stat2) + if (itsFloat) + sprintf(gString2, " %.2f/%.2f", *(float*)stat, *(float*)stat2); + else + sprintf(gString2, " %d/%d", *(int*)stat, *(int*)stat2); + else +#endif + if (stat2) { + if (itsFloat) + sprintf(gString2, " %.2f %s %.2f", *(float*)stat, UnicodeToAscii(TheText.Get("FEST_OO")), *(float*)stat2); + else + sprintf(gString2, " %d %s %d", *(int*)stat, UnicodeToAscii(TheText.Get("FEST_OO")), *(int*)stat2); + } else if (stat) { + if (itsFloat) + sprintf(gString2, " %.2f", *(float*)stat); + else + sprintf(gString2, " %d", *(int*)stat); + } else + gString2[0] = '\0'; + + UnicodeStrcpy(gUString, TheText.Get(text)); + AsciiToUnicode(gString2, gUString2); +} + +void +CMenuManager::CentreMousePointer() +{ + if (SCREEN_WIDTH * 0.5f != 0.0f && 0.0f != SCREEN_HEIGHT * 0.5f) { +#if defined RW_D3D9 || defined RWLIBS + tagPOINT Point; + Point.x = SCREEN_WIDTH / 2; + Point.y = SCREEN_HEIGHT / 2; + ClientToScreen(PSGLOBAL(window), &Point); + SetCursorPos(Point.x, Point.y); +#elif defined RW_GL3 + glfwSetCursorPos(PSGLOBAL(window), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2); +#endif + + PSGLOBAL(lastMousePos.x) = SCREEN_WIDTH / 2; + PSGLOBAL(lastMousePos.y) = SCREEN_HEIGHT / 2; + } +} + +void +CMenuManager::CheckCodesForControls(int typeOfControl) +{ + DisplayComboButtonErrMsg = false; + bool invalidKey = false; + bool escPressed = false; + eControllerType typeToSave; + // GetStartOptionsCntrlConfigScreens(); + e_ControllerAction action = (e_ControllerAction) m_CurrCntrlAction; + + if (typeOfControl == KEYBOARD) { + if (*pControlEdit == rsESC) { + escPressed = true; + } else if (*pControlEdit != rsF1 && *pControlEdit != rsF2 && *pControlEdit != rsF3 && *pControlEdit != rsF9 && + *pControlEdit != rsLWIN && *pControlEdit != rsRWIN && *pControlEdit != rsRALT) { + typeToSave = KEYBOARD; + if (ControlsManager.GetControllerKeyAssociatedWithAction(action, KEYBOARD) != rsNULL && + *pControlEdit != ControlsManager.GetControllerKeyAssociatedWithAction(action, KEYBOARD)) { + typeToSave = OPTIONAL_EXTRA; + } + } else { + invalidKey = true; + } + } else if (typeOfControl == MOUSE) { + typeToSave = MOUSE; + } else if (typeOfControl == JOYSTICK) { + typeToSave = JOYSTICK; + if (ControlsManager.GetIsActionAButtonCombo(action)) + DisplayComboButtonErrMsg = true; + } + +#ifdef FIX_BUGS + if(!escPressed && !invalidKey) +#endif + ControlsManager.ClearSettingsAssociatedWithAction(action, typeToSave); + if (!DisplayComboButtonErrMsg && !escPressed && !invalidKey) { + if (typeOfControl == KEYBOARD) { + ControlsManager.DeleteMatchingActionInitiators(action, *pControlEdit, KEYBOARD); + ControlsManager.DeleteMatchingActionInitiators(action, *pControlEdit, OPTIONAL_EXTRA); + } else { + if (typeOfControl == MOUSE) { + ControlsManager.DeleteMatchingActionInitiators(action, MouseButtonJustClicked, MOUSE); + } else if (typeOfControl == JOYSTICK) { + ControlsManager.DeleteMatchingActionInitiators(action, JoyButtonJustClicked, JOYSTICK); + } + } + if (typeOfControl == KEYBOARD) { + ControlsManager.SetControllerKeyAssociatedWithAction(action, *pControlEdit, typeToSave); + + } else if (typeOfControl == MOUSE) { + ControlsManager.SetControllerKeyAssociatedWithAction(action, MouseButtonJustClicked, typeToSave); + } else { + if (typeOfControl == JOYSTICK) { + ControlsManager.SetControllerKeyAssociatedWithAction(action, JoyButtonJustClicked, typeToSave); + } + } + pControlEdit = nil; + m_bWaitingForNewKeyBind = false; + m_KeyPressedCode = -1; + m_bStartWaitingForKeyBind = false; +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#else + SaveSettings(); +#endif + } + + if (escPressed) { + pControlEdit = nil; + m_bWaitingForNewKeyBind = false; + m_KeyPressedCode = -1; + m_bStartWaitingForKeyBind = false; +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#else + SaveSettings(); +#endif + } +} + +bool +CMenuManager::CheckHover(int x1, int x2, int y1, int y2) +{ + return m_nMousePosX > x1 && m_nMousePosX < x2 && + m_nMousePosY > y1 && m_nMousePosY < y2; +} + +void +CMenuManager::CheckSliderMovement(int value) +{ + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { + case MENUACTION_BRIGHTNESS: + m_PrefsBrightness += value * (512/MENUSLIDER_LOGICAL_BARS); + m_PrefsBrightness = Clamp(m_PrefsBrightness, 0, 511); + break; + case MENUACTION_DRAWDIST: + if(value > 0) + m_PrefsLOD += ((1.8f - 0.8f) / MENUSLIDER_LOGICAL_BARS); + else + m_PrefsLOD -= ((1.8f - 0.8f) / MENUSLIDER_LOGICAL_BARS); + m_PrefsLOD = Clamp(m_PrefsLOD, 0.8f, 1.8f); + CRenderer::ms_lodDistScale = m_PrefsLOD; + break; + case MENUACTION_MUSICVOLUME: + m_PrefsMusicVolume += value * (128/MENUSLIDER_LOGICAL_BARS); + m_PrefsMusicVolume = Clamp(m_PrefsMusicVolume, 0, 127); + DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); + break; + case MENUACTION_SFXVOLUME: + m_PrefsSfxVolume += value * (128/MENUSLIDER_LOGICAL_BARS); + m_PrefsSfxVolume = Clamp(m_PrefsSfxVolume, 0, 127); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); + break; + case MENUACTION_MOUSESENS: + TheCamera.m_fMouseAccelHorzntl += value * 1.0f/200.0f/15.0f; // probably because diving it to 15 instead of 16(MENUSLIDER_LOGICAL_BARS) had more accurate steps + TheCamera.m_fMouseAccelHorzntl = Clamp(TheCamera.m_fMouseAccelHorzntl, 1.0f/3200.0f, 1.0f/200.0f); +#ifdef FIX_BUGS + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl + 0.0005f; +#else + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl; +#endif + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_SLIDER: + { + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption]; + float oldValue = *(float*)option.m_CFOSlider->value; + *(float*)option.m_CFOSlider->value += value * ((option.m_CFOSlider->max - option.m_CFOSlider->min) / MENUSLIDER_LOGICAL_BARS); + *(float*)option.m_CFOSlider->value = Clamp(*(float*)option.m_CFOSlider->value, option.m_CFOSlider->min, option.m_CFOSlider->max); + + if (*(float*)option.m_CFOSlider->value != oldValue && option.m_CFOSlider->changeFunc) + option.m_CFOSlider->changeFunc(oldValue, *(float*)option.m_CFOSlider->value); + + break; + } +#endif + default: + return; + } + SaveSettings(); +} + +void +CMenuManager::DisplayHelperText() +{ + // there was a unused static bool + static uint32 LastFlash = 0; + int32 alpha; + + if (m_nHelperTextMsgId != 0 && m_nHelperTextMsgId != 1) { + + // FIX: High fps bug +#ifndef FIX_BUGS + if (CTimer::GetTimeInMillisecondsPauseMode() - LastFlash > 10) { + LastFlash = CTimer::GetTimeInMillisecondsPauseMode(); + m_nHelperTextAlpha -= 2; + } +#else + m_nHelperTextAlpha -= 2 * CTimer::GetLogicalFramesPassed(); +#endif + if (m_nHelperTextAlpha < 1) + ResetHelperText(); + + alpha = m_nHelperTextAlpha > 255 ? 255 : m_nHelperTextAlpha; + } + + SET_FONT_FOR_HELPER_TEXT + // TODO: name this cases? + switch (m_nHelperTextMsgId) { + case 0: + { + int action = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + if (action != MENUACTION_CHANGEMENU && action != MENUACTION_KEYBOARDCTRLS && action != MENUACTION_RESTOREDEF) { + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_MIG")); + } + break; + } + case 1: + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_APP")); + break; + case 2: + CFont::SetColor(CRGBA(255, 255, 255, alpha)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_HRD")); + break; + case 3: + CFont::SetColor(CRGBA(255, 255, 255, alpha)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_RSO")); + break; + case 4: + CFont::SetColor(CRGBA(255, 255, 255, alpha)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(HELPER_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_RSC")); + break; + default: + break; + } + CFont::SetRightJustifyOff(); +} + +int +CMenuManager::DisplaySlider(float x, float y, float mostLeftBarSize, float mostRightBarSize, float rectSize, float progress) +{ + CRGBA color; + float maxBarHeight; + + int lastActiveBarX = 0; + float curBarX = 0.0f; + float spacing = SCREEN_SCALE_X(10.0f); + for (int i = 0; i < MENUSLIDER_BARS; i++) { + curBarX = i * rectSize/MENUSLIDER_BARS + x; + + if (i / (float)MENUSLIDER_BARS + 1 / (MENUSLIDER_BARS * 2.f) < progress) { + color = CRGBA(SLIDERON_COLOR.r, SLIDERON_COLOR.g, SLIDERON_COLOR.b, FadeIn(255)); + lastActiveBarX = curBarX; + } else + color = CRGBA(SLIDEROFF_COLOR.r, SLIDEROFF_COLOR.g, SLIDEROFF_COLOR.b, FadeIn(255)); + + maxBarHeight = Max(mostLeftBarSize, mostRightBarSize); + + float curBarFreeSpace = ((MENUSLIDER_BARS - i) * mostLeftBarSize + i * mostRightBarSize) / (float)MENUSLIDER_BARS; + float left = curBarX; + float top = y + maxBarHeight - curBarFreeSpace; + float right = spacing + curBarX; + float bottom = y + maxBarHeight; + float shadowOffset = SCREEN_SCALE_X(2.0f); + CSprite2d::DrawRect(CRect(left + shadowOffset, top + shadowOffset, right + shadowOffset, bottom + shadowOffset), CRGBA(0, 0, 0, FadeIn(200))); // Shadow + CSprite2d::DrawRect(CRect(left, top, right, bottom), color); + } + return lastActiveBarX; +} + +void +CMenuManager::DoSettingsBeforeStartingAGame() +{ +#ifdef PC_PLAYER_CONTROLS + CCamera::m_bUseMouse3rdPerson = m_ControlMethod == CONTROL_STANDARD; +#endif + if (m_PrefsVsyncDisp != m_PrefsVsync) + m_PrefsVsync = m_PrefsVsyncDisp; + + DMAudio.Service(); + m_bWantToRestart = true; + + ShutdownJustMenu(); + UnloadTextures(); + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); +} + +void +CMenuManager::Draw() +{ + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetBackGroundOnlyTextOn(); +#if GTA_VERSION >= GTA3_PC_11 && defined(DRAW_MENU_VERSION_TEXT) + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetScale(MENU_X(0.7f), MENU_Y(0.5f)); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::SetRightJustifyWrap(0.0f); + strcpy(gString, "V1.1"); + AsciiToUnicode(gString, gUString); + CFont::PrintString(SCREEN_WIDTH / 10, SCREEN_HEIGHT / 45, gUString); +#endif + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(MENU_X_MARGIN)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(MENU_X_MARGIN - 2.0f)); + + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + PrintStats(); + break; + case MENUPAGE_BRIEFS: + PrintBriefs(); + break; +#ifdef MENU_MAP + case MENUPAGE_MAP: + PrintMap(); + break; +#endif + } + + // Header height isn't accounted, we will add that later. + float nextYToUse = 40.0f; + + // Page name +#ifdef PS2_SAVE_DIALOG + if(!m_bRenderGameInMenu) +#endif + if (aScreens[m_nCurrScreen].m_ScreenName[0] != '\0') { + + SET_FONT_FOR_MENU_HEADER + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), TheText.Get(aScreens[m_nCurrScreen].m_ScreenName)); + + // Weird place to put that. + nextYToUse += 24.0f + 10.0f; + } + + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT * MENU_TEXT_SIZE_X), MENU_Y(MENUACTION_SCALE_MULT * MENU_TEXT_SIZE_Y)); + CFont::SetRightJustifyOff(); + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + + // Label + wchar *str; + if (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) { + switch (m_nCurrScreen) { + case MENUPAGE_LOAD_SLOT_CONFIRM: + if (m_bGameNotLoaded) + str = TheText.Get("FES_LCG"); + else + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + case MENUPAGE_SAVE_OVERWRITE_CONFIRM: + if (Slots[m_nCurrSaveSlot + 1] == SLOT_EMPTY) + str = TheText.Get("FESZ_QZ"); + else + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + case MENUPAGE_EXIT: + if (m_bGameNotLoaded) + str = TheText.Get("FEQ_SRW"); + else + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + default: + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + } + +#ifdef FIX_BUGS + // Label is wrapped from right by StretchX(40)px, but wrapped from left by 40px. And this is only place R* didn't use StretchX in here. + CFont::PrintString(MENU_X_LEFT_ALIGNED(MENU_X_MARGIN), MENU_Y(MENUACTION_POS_Y), str); +#else + CFont::PrintString(MENU_X_MARGIN, MENUACTION_POS_Y, str); +#endif + } + + // Not a bug, we just want HFoV+ on menu +#ifdef ASPECT_RATIO_SCALE + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH)); +#else + CFont::SetCentreSize(SCREEN_WIDTH); +#endif + +#ifdef PS2_LIKE_MENU + bool itemsAreSelectable = !bottomBarActive; +#else + bool itemsAreSelectable = true; +#endif + int lineHeight; + int headerHeight; + int columnWidth; + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + case MENUPAGE_BRIEFS: + columnWidth = 320; + headerHeight = 240; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + break; +#ifdef FIX_BUGS + case MENUPAGE_CONTROLLER_SETTINGS: + columnWidth = 50; + headerHeight = -50; + lineHeight = 20; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = MEDIUMTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = MEDIUMTEXT_Y_SCALE)); + CFont::SetRightJustifyOff(); + break; +#endif + case MENUPAGE_SOUND_SETTINGS: + case MENUPAGE_DISPLAY_SETTINGS: + case MENUPAGE_MULTIPLAYER_CREATE: + case MENUPAGE_SKIN_SELECT_OLD: + case MENUPAGE_CONTROLLER_PC_OLD1: + case MENUPAGE_CONTROLLER_PC_OLD2: + case MENUPAGE_CONTROLLER_PC_OLD3: + case MENUPAGE_CONTROLLER_PC_OLD4: + case MENUPAGE_CONTROLLER_DEBUG: + case MENUPAGE_MOUSE_CONTROLS: + columnWidth = 50; + headerHeight = 0; + lineHeight = 20; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = MEDIUMTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = MEDIUMTEXT_Y_SCALE)); + CFont::SetRightJustifyOff(); + break; + case MENUPAGE_CHOOSE_LOAD_SLOT: + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_CHOOSE_SAVE_SLOT: + columnWidth = 120; + headerHeight = 38; + lineHeight = 20; + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = SMALLTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = SMALLTEXT_Y_SCALE)); + CFont::SetRightJustifyOff(); + break; + case MENUPAGE_NEW_GAME_RELOAD: + case MENUPAGE_LOAD_SLOT_CONFIRM: + case MENUPAGE_DELETE_SLOT_CONFIRM: + case MENUPAGE_SAVE_OVERWRITE_CONFIRM: + case MENUPAGE_EXIT: + columnWidth = 320; + headerHeight = 60; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + break; + case MENUPAGE_START_MENU: + columnWidth = 320; + headerHeight = 140; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + break; + case MENUPAGE_PAUSE_MENU: + columnWidth = 320; + headerHeight = 117; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + break; +#ifdef PS2_SAVE_DIALOG + case MENUPAGE_SAVE: + columnWidth = 180; + headerHeight = 60; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + break; +#endif + default: +#ifdef CUSTOM_FRONTEND_OPTIONS + CCustomScreenLayout *custom = aScreens[m_nCurrScreen].layout; + if (custom) { + columnWidth = custom->columnWidth; + headerHeight = custom->headerHeight; + lineHeight = custom->lineHeight; + CFont::SetFontStyle(FONT_LOCALE(custom->font)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = custom->fontScaleX), MENU_Y(MENU_TEXT_SIZE_Y = custom->fontScaleY)); + if (custom->alignment == FESCREEN_LEFT_ALIGN) { + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + } else if (custom->alignment == FESCREEN_RIGHT_ALIGN) { + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + } else { + CFont::SetRightJustifyOff(); + CFont::SetCentreOn(); + } + } + if (!custom) +#endif + { + columnWidth = 320; + headerHeight = 40; + lineHeight = 24; + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X = BIGTEXT_X_SCALE), MENU_Y(MENU_TEXT_SIZE_Y = BIGTEXT_Y_SCALE)); + CFont::SetCentreOn(); + } + break; + } + +#ifdef PS2_LIKE_MENU + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); +#endif + + switch (m_nCurrScreen) { + case MENUPAGE_CONTROLLER_PC_OLD1: + case MENUPAGE_CONTROLLER_PC_OLD2: + case MENUPAGE_CONTROLLER_PC_OLD3: + case MENUPAGE_CONTROLLER_PC_OLD4: + case MENUPAGE_CONTROLLER_DEBUG: + if (m_bWaitingForNewKeyBind) + itemsAreSelectable = false; + + DrawControllerScreenExtraText(nextYToUse - 8.0f, MENU_X_LEFT_ALIGNED(350), lineHeight); + break; + default: + break; + } + + float usableLineHeight = lineHeight * 0.9f; // also height of biggest bar in slider + float smallestSliderBar = lineHeight * 0.1f; + bool foundTheHoveringItem = false; + wchar unicodeTemp[64]; +#ifdef ASPECT_RATIO_SCALE + char asciiTemp[32]; +#endif + +#ifdef MENU_MAP + if (m_nCurrScreen == MENUPAGE_MAP) { + // Back button + wchar *backTx = TheText.Get("FEDS_TB"); + CFont::SetDropShadowPosition(1); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::PrintString(MENU_X(60.0f), SCREEN_SCALE_FROM_BOTTOM(120.0f), backTx); + CFont::SetDropShadowPosition(0); + if (!CheckHover(MENU_X(30.0f), MENU_X(30.0f) + CFont::GetStringWidth(backTx), SCREEN_SCALE_FROM_BOTTOM(125.0f), SCREEN_SCALE_FROM_BOTTOM(105.0f))) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + m_nCurrOption = m_nOptionMouseHovering = 0; + } else { + m_nHoverOption = HOVEROPTION_RANDOM_ITEM; + m_nCurrOption = m_nOptionMouseHovering = 1; + } + return; + } +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + // Thanks R*, for checking mouse hovering in Draw(). + static int lastSelectedOpt = m_nCurrOption; +#endif + +#ifdef SCROLLABLE_PAGES + int firstOption = SCREEN_HAS_AUTO_SCROLLBAR ? m_nFirstVisibleRowOnList : 0; + for (int i = firstOption; i < firstOption + MAX_VISIBLE_OPTION && i < NUM_MENUROWS; ++i) { +#else + for (int i = 0; i < NUM_MENUROWS; ++i) { +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + bool isOptionDisabled = false; +#endif + // Hide back button +#ifdef PS2_LIKE_MENU + if ((i == NUM_MENUROWS - 1 || aScreens[m_nCurrScreen].m_aEntries[i+1].m_EntryName[0] == '\0') && strcmp(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName, "FEDS_TB") == 0) + break; +#endif + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action != MENUACTION_LABEL && aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName[0] != '\0') { + wchar *rightText = nil; + wchar *leftText; + + if (aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot <= SAVESLOT_8) { + CFont::SetRightJustifyOff(); + leftText = GetNameOfSavedGame(i - 1); + + if (Slots[i] != SLOT_EMPTY) + rightText = GetSavedGameDateAndTime(i - 1); + + if (leftText[0] == '\0') { + sprintf(gString, "FEM_SL%d", i); + leftText = TheText.Get(gString); + } + } else { + leftText = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName); + } + + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) { + case MENUACTION_CHANGEMENU: { + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_TargetMenu) { + case MENUPAGE_MULTIPLAYER_MAP: + switch (m_SelectedMap) { + case 0: + rightText = TheText.Get("FEM_MA0"); + break; + case 1: + rightText = TheText.Get("FEM_MA1"); + break; + case 2: + rightText = TheText.Get("FEM_MA2"); + break; + case 3: + rightText = TheText.Get("FEM_MA3"); + break; + case 4: + rightText = TheText.Get("FEM_MA4"); + break; + case 5: + rightText = TheText.Get("FEM_MA5"); + break; + case 6: + rightText = TheText.Get("FEM_MA6"); + break; + case 7: + rightText = TheText.Get("FEM_MA7"); + break; + default: + break; + } + break; + case MENUPAGE_MULTIPLAYER_MODE: + switch (m_SelectedGameType) { + case 0: + rightText = TheText.Get("FEN_TY0"); + break; + case 1: + rightText = TheText.Get("FEN_TY1"); + break; + case 2: + rightText = TheText.Get("FEN_TY2"); + break; + case 3: + rightText = TheText.Get("FEN_TY3"); + break; + case 4: + rightText = TheText.Get("FEN_TY4"); + break; + case 5: + rightText = TheText.Get("FEN_TY5"); + break; + case 6: + rightText = TheText.Get("FEN_TY6"); + break; + case 7: + rightText = TheText.Get("FEN_TY7"); + break; + default: + break; + } + break; + default: + break; + } + break; + } + case MENUACTION_CTRLVIBRATION: + if (m_PrefsUseVibration) + rightText = TheText.Get("FEM_ON"); + else + rightText = TheText.Get("FEM_OFF"); + break; + case MENUACTION_CTRLCONFIG: + switch (CPad::GetPad(0)->Mode) { + case 0: + rightText = TheText.Get("FEC_CF1"); + break; + case 1: + rightText = TheText.Get("FEC_CF2"); + break; + case 2: + rightText = TheText.Get("FEC_CF3"); + break; + case 3: + rightText = TheText.Get("FEC_CF4"); + break; + } + break; + case MENUACTION_CTRLDISPLAY: + if (m_DisplayControllerOnFoot) + rightText = TheText.Get("FEC_ONF"); + else + rightText = TheText.Get("FEC_INC"); + break; + case MENUACTION_FRAMESYNC: + rightText = TheText.Get(m_PrefsVsyncDisp ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_FRAMELIMIT: + rightText = TheText.Get(m_PrefsFrameLimiter ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_TRAILS: + rightText = TheText.Get(CMBlur::BlurOn ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SUBTITLES: + rightText = TheText.Get(m_PrefsShowSubtitles ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_WIDESCREEN: +#ifndef ASPECT_RATIO_SCALE + rightText = TheText.Get(m_PrefsUseWideScreen ? "FEM_ON" : "FEM_OFF"); +#else + switch (m_PrefsUseWideScreen) { + case AR_AUTO: + rightText = TheText.Get("FEM_AUT"); + break; + case AR_4_3: + sprintf(asciiTemp, "4:3"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + case AR_5_4: + sprintf(asciiTemp, "5:4"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + case AR_16_10: + sprintf(asciiTemp, "16:10"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + case AR_16_9: + sprintf(asciiTemp, "16:9"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + case AR_21_9: + sprintf(asciiTemp, "21:9"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; + break; + } +#endif + break; + case MENUACTION_RADIO: + if (m_PrefsRadioStation > USERTRACK) + break; + + sprintf(gString, "FEA_FM%d", m_PrefsRadioStation); + rightText = TheText.Get(gString); + break; + case MENUACTION_SETDBGFLAG: + rightText = TheText.Get(CTheScripts::IsDebugOn() ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SWITCHBIGWHITEDEBUGLIGHT: + rightText = TheText.Get(gbBigWhiteDebugLightSwitchedOn ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_PEDROADGROUPS: + rightText = TheText.Get(gbShowPedRoadGroups ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_CARROADGROUPS: + rightText = TheText.Get(gbShowCarRoadGroups ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_COLLISIONPOLYS: + rightText = TheText.Get(gbShowCollisionPolys ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SHOWCULL: + rightText = TheText.Get(gbShowCullZoneDebugStuff ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_SHOWHEADBOB: + rightText = TheText.Get(TheCamera.m_bHeadBob ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_INVVERT: + rightText = TheText.Get(MousePointerStateHelper.bInvertVertically ? "FEM_OFF" : "FEM_ON"); + break; + case MENUACTION_SCREENRES: + AsciiToUnicode(_psGetVideoModeList()[m_nDisplayVideoMode], unicodeTemp); + rightText = unicodeTemp; + break; + case MENUACTION_AUDIOHW: + if (m_nPrefsAudio3DProviderIndex == -1) + rightText = TheText.Get("FEA_NAH"); + else { + char *provider = DMAudio.Get3DProviderName(m_nPrefsAudio3DProviderIndex); + + if (!strcmp(strupr(provider), "DIRECTSOUND3D HARDWARE SUPPORT")) { + strcpy(provider, "DSOUND3D HARDWARE SUPPORT"); + } else if (!strcmp(strupr(provider), "DIRECTSOUND3D SOFTWARE EMULATION")) { + strcpy(provider, "DSOUND3D SOFTWARE EMULATION"); + } + AsciiToUnicode(provider, unicodeTemp); + rightText = unicodeTemp; + } + break; + case MENUACTION_SPEAKERCONF: { + if (m_nPrefsAudio3DProviderIndex == -1) + rightText = TheText.Get("FEA_NAH"); + else { + switch (m_PrefsSpeakers) { + case 0: + rightText = TheText.Get("FEA_2SP"); + break; + case 1: + rightText = TheText.Get("FEA_EAR"); + break; + case 2: + rightText = TheText.Get("FEA_4SP"); + break; + } + } + break; + } + case MENUACTION_CTRLMETHOD: { + switch (m_ControlMethod) { + case 0: + leftText = TheText.Get("FET_SCN"); + break; + case 1: + leftText = TheText.Get("FET_CCN"); + break; + } + break; + } + case MENUACTION_DYNAMICACOUSTIC: + rightText = TheText.Get(m_PrefsDMA ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_MOUSESTEER: + rightText = TheText.Get(CVehicle::m_bDisableMouseSteering ? "FEM_OFF" : "FEM_ON"); + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_DYNAMIC: + case MENUACTION_CFO_SELECT: + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[i]; + if (option.m_Action == MENUACTION_CFO_SELECT) { + + isOptionDisabled = option.m_CFOSelect->disableIfGameLoaded && !m_bGameNotLoaded; + if (option.m_CFOSelect->onlyApplyOnEnter){ + if (m_nCurrOption != i) { + if (option.m_CFOSelect->displayedValue != option.m_CFOSelect->lastSavedValue) + SetHelperText(3); // Restored original value + + // If that was previously selected option, restore it to default value. + // if (m_nCurrOption != lastSelectedOpt && lastSelectedOpt == i) + option.m_CFOSelect->displayedValue = option.m_CFOSelect->lastSavedValue = *(int8*)option.m_CFO->value; + + } else { + if (option.m_CFOSelect->displayedValue != *(int8*)option.m_CFO->value) + SetHelperText(1); // Enter to apply + else if (m_nHelperTextMsgId == 1) + ResetHelperText(); // Applied + } + } + + // To whom manipulate option.m_CFO->value of select options externally (like RestoreDef functions) + if (*(int8*)option.m_CFO->value != option.m_CFOSelect->lastSavedValue) + option.m_CFOSelect->displayedValue = option.m_CFOSelect->lastSavedValue = *(int8*)option.m_CFO->value; + + if (option.m_CFOSelect->displayedValue >= option.m_CFOSelect->numRightTexts || option.m_CFOSelect->displayedValue < 0) + option.m_CFOSelect->displayedValue = 0; + + rightText = TheText.Get(option.m_CFOSelect->rightTexts[option.m_CFOSelect->displayedValue]); + + } else if (option.m_Action == MENUACTION_CFO_DYNAMIC) { + if (m_nCurrOption != lastSelectedOpt && lastSelectedOpt == i) { + if(option.m_CFODynamic->buttonPressFunc) + option.m_CFODynamic->buttonPressFunc(FEOPTION_ACTION_FOCUSLOSS); + } + + if (option.m_CFODynamic->drawFunc) { + rightText = option.m_CFODynamic->drawFunc(&isOptionDisabled, m_nCurrOption == i); + } + } + break; +#endif + } + + float nextItemY = headerHeight + nextYToUse; + float bitAboveNextItemY = nextItemY - 2.0f; + int nextYToCheck = bitAboveNextItemY; + + if (!foundTheHoveringItem) { +#ifdef SCROLLABLE_PAGES + for (int rowToCheck = firstOption + (aScreens[m_nCurrScreen].m_aEntries[firstOption].m_Action == MENUACTION_LABEL); rowToCheck < firstOption + MAX_VISIBLE_OPTION && rowToCheck < NUM_MENUROWS; ++rowToCheck) { +#else + for (int rowToCheck = aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL; rowToCheck < NUM_MENUROWS; ++rowToCheck) { +#endif + if(aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_Action == MENUACTION_NOTHING) + break; + + // Hide back button +#ifdef PS2_LIKE_MENU + if ((rowToCheck == NUM_MENUROWS - 1 || aScreens[m_nCurrScreen].m_aEntries[rowToCheck+1].m_EntryName[0] == '\0') && + strcmp(aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_EntryName, "FEDS_TB") == 0) + break; +#endif + + int extraOffset = 0; + if (aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_Action == MENUACTION_RADIO) + extraOffset = MENURADIO_ICON_SCALE; + + // There were many unused codes in here to calculate how much space will texts gonna take. + + // FIX: nextYToCheck already starts with Y - 2, let's sync it with green bar bounds. +#ifdef FIX_BUGS + if (m_nMousePosY > MENU_Y(nextYToCheck) && +#else + if (m_nMousePosY > MENU_Y(nextYToCheck - 2) && +#endif + m_nMousePosY < MENU_Y((nextYToCheck + 2) + usableLineHeight)) { + + static int oldOption = -99; + static int oldScreen = m_nCurrScreen; + + m_nOptionMouseHovering = rowToCheck; + if (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY) { + m_nCurrOption = rowToCheck; + m_bShowMouse = true; + } + if (oldOption != m_nCurrOption) { + if (oldScreen == m_nCurrScreen && m_bShowMouse) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + oldOption = m_nCurrOption; + oldScreen = m_nCurrScreen; + } + if (oldScreen == m_nPrevScreen) + oldScreen = m_nCurrScreen; + + m_nHoverOption = HOVEROPTION_RANDOM_ITEM; + foundTheHoveringItem = true; + break; + } + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + nextYToCheck += extraOffset + lineHeight; + } + } + + // Green bar behind selected option +#ifdef PS2_SAVE_DIALOG + if (!m_bRenderGameInMenu) +#endif + if (i == m_nCurrOption && itemsAreSelectable) { +#ifdef PS2_LIKE_MENU + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(29.0f), MENU_Y(bitAboveNextItemY), + MENU_X_RIGHT_ALIGNED(29.0f), MENU_Y(usableLineHeight + nextItemY)), + CRGBA(SELECTION_HIGHLIGHTBG_COLOR.r, SELECTION_HIGHLIGHTBG_COLOR.g, SELECTION_HIGHLIGHTBG_COLOR.b, FadeIn(SELECTION_HIGHLIGHTBG_COLOR.a))); +#else + // We keep stretching, because we also stretch background image and we want that bar to be aligned with borders of background + CSprite2d::DrawRect(CRect(StretchX(10.0f), MENU_Y(bitAboveNextItemY), + SCREEN_STRETCH_FROM_RIGHT(11.0f), MENU_Y(usableLineHeight + nextItemY)), + CRGBA(SELECTION_HIGHLIGHTBG_COLOR.r, SELECTION_HIGHLIGHTBG_COLOR.g, SELECTION_HIGHLIGHTBG_COLOR.b, FadeIn(SELECTION_HIGHLIGHTBG_COLOR.a))); +#endif + } + + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(90))); + + // Button and it's shadow + for(int textLayer = 0; textLayer < 2; textLayer++) { + if (!CFont::Details.centre) + CFont::SetRightJustifyOff(); + + float itemY = MENU_Y(textLayer + nextItemY); + float itemX = MENU_X_LEFT_ALIGNED(textLayer + columnWidth); + CFont::PrintString(itemX, itemY, leftText); + if (rightText) { + if (!CFont::Details.centre) + CFont::SetRightJustifyOn(); + + if(textLayer == 1) + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName, "FED_RES") && !m_bGameNotLoaded +#ifdef CUSTOM_FRONTEND_OPTIONS + || isOptionDisabled +#endif + ) + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + + CFont::PrintString(MENU_X_RIGHT_ALIGNED(columnWidth - textLayer), itemY, rightText); + } + if (i == m_nCurrOption && itemsAreSelectable){ + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + } + + if (m_nPrefsAudio3DProviderIndex == DMAudio.GetCurrent3DProviderIndex()) { + if(!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEA_3DH") && m_nHelperTextMsgId == 1) + ResetHelperText(); + } + if (m_nDisplayVideoMode == m_nPrefsVideoMode) { + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FED_RES") && m_nHelperTextMsgId == 1) + ResetHelperText(); + } + if (m_nPrefsAudio3DProviderIndex != DMAudio.GetCurrent3DProviderIndex()) { + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEA_3DH")) + SetHelperText(1); + } + if (m_nDisplayVideoMode != m_nPrefsVideoMode) { + if (!strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FED_RES")) + SetHelperText(1); + } + if (m_nPrefsAudio3DProviderIndex != DMAudio.GetCurrent3DProviderIndex()) { + if (strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEA_3DH") != 0 + // To make assigning built-in actions to new custom options possible. +#ifdef CUSTOM_FRONTEND_OPTIONS + && ScreenHasOption(m_nCurrScreen, "FEA_3DH") +#else + && m_nCurrScreen == MENUPAGE_SOUND_SETTINGS +#endif + && m_nPrefsAudio3DProviderIndex != -1) { + + m_nPrefsAudio3DProviderIndex = DMAudio.GetCurrent3DProviderIndex(); + SetHelperText(3); + } + } + if (m_nDisplayVideoMode != m_nPrefsVideoMode) { + if (strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FED_RES") != 0 + // To make assigning built-in actions to new custom options possible. +#ifdef CUSTOM_FRONTEND_OPTIONS + && ScreenHasOption(m_nCurrScreen, "FED_RES") +#else + && m_nCurrScreen == MENUPAGE_DISPLAY_SETTINGS +#endif + ){ + m_nDisplayVideoMode = m_nPrefsVideoMode; + SetHelperText(3); + } + } + + // Sliders + int lastActiveBarX; + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) { + case MENUACTION_BRIGHTNESS: + ProcessSlider(m_PrefsBrightness / 512.0f, HOVEROPTION_INCREASE_BRIGHTNESS, HOVEROPTION_DECREASE_BRIGHTNESS, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; + case MENUACTION_DRAWDIST: + ProcessSlider((m_PrefsLOD - 0.8f) * 1.0f, HOVEROPTION_INCREASE_DRAWDIST, HOVEROPTION_DECREASE_DRAWDIST, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; + case MENUACTION_MUSICVOLUME: + ProcessSlider(m_PrefsMusicVolume / 128.0f, HOVEROPTION_INCREASE_MUSICVOLUME, HOVEROPTION_DECREASE_MUSICVOLUME, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; + case MENUACTION_SFXVOLUME: + ProcessSlider(m_PrefsSfxVolume / 128.0f, HOVEROPTION_INCREASE_SFXVOLUME, HOVEROPTION_DECREASE_SFXVOLUME, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; + case MENUACTION_MOUSESENS: + ProcessSlider(TheCamera.m_fMouseAccelHorzntl * 200.0f, HOVEROPTION_INCREASE_MOUSESENS, HOVEROPTION_DECREASE_MOUSESENS, MENU_X_LEFT_ALIGNED(200.0f), SCREEN_WIDTH); + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_SLIDER: + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[i]; + ProcessSlider((*(float*)option.m_CFOSlider->value - option.m_CFOSlider->min) / (option.m_CFOSlider->max - option.m_CFOSlider->min), HOVEROPTION_INCREASE_CFO_SLIDER, HOVEROPTION_DECREASE_CFO_SLIDER, MENU_X_LEFT_ALIGNED(170.0f), SCREEN_WIDTH); + break; +#endif + } + + // Needed after the bug fix in Font.cpp +#ifdef FIX_BUGS + if (!CFont::Details.centre) + CFont::SetRightJustifyOff(); +#endif + + // 60.0 is silly + nextYToUse += lineHeight * CFont::GetNumberLines(MENU_X_LEFT_ALIGNED(60.0f), MENU_Y(nextYToUse), leftText); + + // Radio icons + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action == MENUACTION_RADIO) { + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO1], MENU_X_LEFT_ALIGNED(30.0f), MENU_Y(nextYToUse), 0, HOVEROPTION_RADIO_0); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO2], MENU_X_LEFT_ALIGNED(90.0f), MENU_Y(nextYToUse), 1, HOVEROPTION_RADIO_1); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO5], MENU_X_LEFT_ALIGNED(150.0f), MENU_Y(nextYToUse), 2, HOVEROPTION_RADIO_2); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO7], MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(nextYToUse), 3, HOVEROPTION_RADIO_3); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO8], MENU_X_LEFT_ALIGNED(270.0f), MENU_Y(nextYToUse), 4, HOVEROPTION_RADIO_4); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO3], MENU_X_LEFT_ALIGNED(320.0f), MENU_Y(nextYToUse), 5, HOVEROPTION_RADIO_5); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO4], MENU_X_LEFT_ALIGNED(360.0f), MENU_Y(nextYToUse), 6, HOVEROPTION_RADIO_6); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO6], MENU_X_LEFT_ALIGNED(420.0f), MENU_Y(nextYToUse), 7, HOVEROPTION_RADIO_7); + ProcessRadioIcon(m_aFrontEndSprites[FE_RADIO9], MENU_X_LEFT_ALIGNED(480.0f), MENU_Y(nextYToUse), 8, HOVEROPTION_RADIO_8); + + if (DMAudio.IsMP3RadioChannelAvailable()) + ProcessRadioIcon(m_aMenuSprites[MENUSPRITE_MP3LOGO], MENU_X_LEFT_ALIGNED(540.0f), MENU_Y(nextYToUse), 9, HOVEROPTION_RADIO_9); + + nextYToUse += 70.0f; + } + } + } + +#ifdef CUSTOM_FRONTEND_OPTIONS + lastSelectedOpt = m_nCurrOption; +#endif + +#ifdef SCROLLABLE_PAGES + #define SCROLLBAR_BOTTOM_Y 125.0f // only for background, scrollbar's itself is calculated + #define SCROLLBAR_RIGHT_X 36.0f + #define SCROLLBAR_WIDTH 9.5f + #define SCROLLBAR_TOP_Y 64 + + if (SCREEN_HAS_AUTO_SCROLLBAR) { + // Scrollbar background + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 2), MENU_Y(SCROLLBAR_TOP_Y), + MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 2 - SCROLLBAR_WIDTH), SCREEN_SCALE_FROM_BOTTOM(SCROLLBAR_BOTTOM_Y)), CRGBA(100, 100, 66, FadeIn(205))); + + float scrollbarHeight = SCROLLBAR_MAX_HEIGHT / (m_nTotalListRow / (float) MAX_VISIBLE_OPTION); + float scrollbarBottom, scrollbarTop; + + scrollbarBottom = MENU_Y(SCROLLBAR_TOP_Y - 8 + m_nScrollbarTopMargin + scrollbarHeight); + scrollbarTop = MENU_Y(SCROLLBAR_TOP_Y + m_nScrollbarTopMargin); + // Scrollbar shadow + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 1 - SCROLLBAR_WIDTH), scrollbarBottom + MENU_Y(1.0f)), + CRGBA(50, 50, 50, FadeIn(255))); + + // Scrollbar + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(SCROLLBAR_RIGHT_X - SCROLLBAR_WIDTH), scrollbarBottom), + CRGBA(SCROLLBAR_COLOR.r, SCROLLBAR_COLOR.g, SCROLLBAR_COLOR.b, FadeIn(255))); + + } +#endif + + switch (m_nCurrScreen) { + case MENUPAGE_CONTROLLER_SETTINGS: + case MENUPAGE_SOUND_SETTINGS: + case MENUPAGE_DISPLAY_SETTINGS: + case MENUPAGE_SKIN_SELECT: + case MENUPAGE_CONTROLLER_PC: + case MENUPAGE_MOUSE_CONTROLS: + DisplayHelperText(); + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + default: + if (aScreens[m_nCurrScreen].layout) { + if (aScreens[m_nCurrScreen].layout->showLeftRightHelper) { + DisplayHelperText(); + } + } + break; +#endif + } + + if (m_nCurrScreen == MENUPAGE_CONTROLLER_SETTINGS) + PrintController(); + else if (m_nCurrScreen == MENUPAGE_SKIN_SELECT_OLD) { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(180), MENU_Y(98), MENU_X_LEFT_ALIGNED(230), MENU_Y(123)), CRGBA(255, 255, 255, FadeIn(255))); + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(181), MENU_Y(99), MENU_X_LEFT_ALIGNED(229), MENU_Y(122)), CRGBA(m_PrefsPlayerRed, m_PrefsPlayerGreen, m_PrefsPlayerBlue, FadeIn(255))); + } + +} + +int +CMenuManager::GetNumOptionsCntrlConfigScreens(void) +{ + int number = 0; + switch (m_nCurrScreen) { + case MENUPAGE_CONTROLLER_PC_OLD3: + number = 2; + break; + case MENUPAGE_CONTROLLER_DEBUG: + number = 4; + break; + case MENUPAGE_KEYBOARD_CONTROLS: + switch (m_ControlMethod) { + case CONTROL_STANDARD: + number = 25; + break; + case CONTROL_CLASSIC: + number = 30; + break; + } + break; + } + return number; +} + +void +CMenuManager::DrawControllerBound(int32 yStart, int32 xStart, int32 unused, int8 column) +{ + int controllerAction = PED_FIREWEAPON; + // GetStartOptionsCntrlConfigScreens(); + int numOptions = GetNumOptionsCntrlConfigScreens(); + int nextY = MENU_Y(yStart); + int bindingMargin = MENU_X(3.0f); + float rowHeight; + switch (m_ControlMethod) { + case CONTROL_STANDARD: + rowHeight = CONTSETUP_STANDARD_ROW_HEIGHT; + break; + case CONTROL_CLASSIC: + rowHeight = CONTSETUP_CLASSIC_ROW_HEIGHT; + break; + default: + break; + } + + // MENU_Y(rowHeight * 0.0f + yStart); + for (int optionIdx = 0; optionIdx < numOptions; nextY = MENU_Y(++optionIdx * rowHeight + yStart)) { + int nextX = xStart; + int bindingsForThisOpt = 0; + int contSetOrder = SETORDER_1; + CFont::SetColor(CRGBA(LIST_OPTION_COLOR.r, LIST_OPTION_COLOR.g, LIST_OPTION_COLOR.b, FadeIn(LIST_OPTION_COLOR.a))); + + if (column == CONTSETUP_PED_COLUMN) { + switch (optionIdx) { + case 0: + controllerAction = PED_FIREWEAPON; + break; + case 1: + controllerAction = PED_CYCLE_WEAPON_RIGHT; + break; + case 2: + controllerAction = PED_CYCLE_WEAPON_LEFT; + break; + case 3: + controllerAction = GO_FORWARD; + break; + case 4: + controllerAction = GO_BACK; + break; + case 5: + controllerAction = GO_LEFT; + break; + case 6: + controllerAction = GO_RIGHT; + break; + case 7: + controllerAction = PED_SNIPER_ZOOM_IN; + break; + case 8: + controllerAction = PED_SNIPER_ZOOM_OUT; + break; + case 9: + controllerAction = VEHICLE_ENTER_EXIT; + break; + case 10: + case 11: + case 12: + case 16: + case 18: + case 19: + case 20: + case 21: + controllerAction = -1; + break; + case 13: + controllerAction = CAMERA_CHANGE_VIEW_ALL_SITUATIONS; + break; + case 14: + controllerAction = PED_JUMPING; + break; + case 15: + controllerAction = PED_SPRINT; + break; + case 17: + controllerAction = PED_LOCK_TARGET; + break; + case 22: + controllerAction = PED_LOOKBEHIND; + break; + case 23: + if (m_ControlMethod == CONTROL_STANDARD) + controllerAction = -1; + else + controllerAction = PED_1RST_PERSON_LOOK_LEFT; + break; + case 24: + if (m_ControlMethod == CONTROL_STANDARD) + controllerAction = -1; + else + controllerAction = PED_1RST_PERSON_LOOK_RIGHT; + break; + case 25: + controllerAction = PED_1RST_PERSON_LOOK_UP; + break; + case 26: + controllerAction = PED_1RST_PERSON_LOOK_DOWN; + break; + case 27: + controllerAction = PED_CYCLE_TARGET_LEFT; + break; + case 28: + controllerAction = PED_CYCLE_TARGET_RIGHT; + break; + case 29: + controllerAction = PED_CENTER_CAMERA_BEHIND_PLAYER; + break; + default: + break; + } + } else if (column == CONTSETUP_VEHICLE_COLUMN) { + switch (optionIdx) { + case 0: +#ifdef BIND_VEHICLE_FIREWEAPON + controllerAction = VEHICLE_FIREWEAPON; +#else + controllerAction = PED_FIREWEAPON; +#endif + break; + case 1: + case 2: + case 7: + case 8: + case 14: + case 15: + case 17: + case 25: + case 26: + case 27: + case 28: + case 29: + controllerAction = -1; + break; + case 3: + controllerAction = VEHICLE_ACCELERATE; + break; + case 4: + controllerAction = VEHICLE_BRAKE; + break; + case 5: + controllerAction = GO_LEFT; + break; + case 6: + controllerAction = GO_RIGHT; + break; + case 9: + controllerAction = VEHICLE_ENTER_EXIT; + break; + case 10: + controllerAction = VEHICLE_CHANGE_RADIO_STATION; + break; + case 11: + controllerAction = VEHICLE_HORN; + break; + case 12: + controllerAction = TOGGLE_SUBMISSIONS; + break; + case 13: + controllerAction = CAMERA_CHANGE_VIEW_ALL_SITUATIONS; + break; + case 16: + controllerAction = VEHICLE_HANDBRAKE; + break; + case 18: + controllerAction = VEHICLE_TURRETLEFT; + break; + case 19: + controllerAction = VEHICLE_TURRETRIGHT; + break; + case 20: + controllerAction = VEHICLE_TURRETUP; + break; + case 21: + controllerAction = VEHICLE_TURRETDOWN; + break; + case 22: + controllerAction = -2; + break; + case 23: + controllerAction = VEHICLE_LOOKLEFT; + break; + case 24: + controllerAction = VEHICLE_LOOKRIGHT; + break; + default: + break; + } + } + int bindingWhite = 155; + + // Highlight selected column(and make its text black) + if (m_nSelectedListRow == optionIdx) { + int bgY = m_nSelectedListRow * rowHeight + yStart + 1.0f; + if (m_nCurrExLayer == HOVEROPTION_LIST) { + + if (column == CONTSETUP_PED_COLUMN && m_nSelectedContSetupColumn == CONTSETUP_PED_COLUMN) { +#ifdef FIX_BUGS + if (controllerAction == -1) { + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.r, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.g, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.a))); + } else { + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_HIGHLIGHTBG_COLOR.r, CONTSETUP_HIGHLIGHTBG_COLOR.g, CONTSETUP_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_HIGHLIGHTBG_COLOR.a))); + } +#else + if (controllerAction == -1) { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(bgY), + MENU_X_LEFT_ALIGNED(400.0f), MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.r, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.g, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.a))); + } else { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(bgY), + MENU_X_LEFT_ALIGNED(400.0f), MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_HIGHLIGHTBG_COLOR.r, CONTSETUP_HIGHLIGHTBG_COLOR.g, CONTSETUP_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_HIGHLIGHTBG_COLOR.a))); + } +#endif + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + bindingWhite = 0; + + } else if (column == CONTSETUP_VEHICLE_COLUMN && m_nSelectedContSetupColumn == CONTSETUP_VEHICLE_COLUMN) { +#ifdef FIX_BUGS + if (controllerAction == -1) { + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.r, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.g, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.a))); + } else { + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(CONTSETUP_HIGHLIGHTBG_COLOR.r, CONTSETUP_HIGHLIGHTBG_COLOR.g, CONTSETUP_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_HIGHLIGHTBG_COLOR.a))); + } +#else + if (controllerAction == -1) { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(410.0f), MENU_Y(bgY), MENU_X_LEFT_ALIGNED(600.0f), MENU_Y(bgY + 10)), CRGBA(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.r, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.g, CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_DISABLED_HIGHLIGHTBG_COLOR.a))); + } else { + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(410.0f), MENU_Y(bgY), MENU_X_LEFT_ALIGNED(600.0f), MENU_Y(bgY + 10)), CRGBA(CONTSETUP_HIGHLIGHTBG_COLOR.r, CONTSETUP_HIGHLIGHTBG_COLOR.g, CONTSETUP_HIGHLIGHTBG_COLOR.b, FadeIn(CONTSETUP_HIGHLIGHTBG_COLOR.a))); + } +#endif + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + bindingWhite = 0; + } + } + } + + // Print bindings, including seperator (-) between them + CFont::SetScale(MENU_X(0.25f), MENU_Y(SMALLESTTEXT_Y_SCALE)); +#ifdef FIX_BUGS + for (; contSetOrder < MAX_SETORDERS && controllerAction >= 0; contSetOrder++) { +#else + for (; contSetOrder < MAX_SETORDERS && controllerAction != -1; contSetOrder++) { +#endif + wchar *settingText = ControlsManager.GetControllerSettingTextWithOrderNumber((e_ControllerAction)controllerAction, (eContSetOrder)contSetOrder); + if (settingText) { + ++bindingsForThisOpt; + if (bindingsForThisOpt > 1) { + wchar *seperator = TheText.Get("FEC_IBT"); + CFont::SetColor(CRGBA(20, 20, 20, FadeIn(80))); + CFont::PrintString(nextX, nextY, seperator); + CFont::SetColor(CRGBA(bindingWhite, bindingWhite, bindingWhite, FadeIn(255))); + nextX += CFont::GetStringWidth(seperator, true) + bindingMargin; + } + CFont::PrintString(nextX, nextY, settingText); +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + nextX += CFont::GetStringWidth_Jap(settingText) + bindingMargin; + else +#endif + nextX += CFont::GetStringWidth(settingText, true) + bindingMargin; + } + } + if (controllerAction == -1) { + CFont::SetColor(CRGBA(20, 20, 20, FadeIn(80))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_NUS")); // not used + } else if (controllerAction == -2) { + CFont::SetColor(CRGBA(20, 20, 20, FadeIn(80))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_CMP")); // combo: l+r + } else if (bindingsForThisOpt == 0) { + if (m_nSelectedListRow != optionIdx) { + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_UNB")); // unbound + } else if (m_bWaitingForNewKeyBind) { + if (column != m_nSelectedContSetupColumn) { + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_UNB")); // unbound + } + } else { + if (column != m_nSelectedContSetupColumn) { + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + } + CFont::PrintString(nextX, nextY, TheText.Get("FEC_UNB")); // unbound + } + } + + if (column == CONTSETUP_PED_COLUMN && m_nSelectedContSetupColumn == CONTSETUP_PED_COLUMN || + column == CONTSETUP_VEHICLE_COLUMN && m_nSelectedContSetupColumn == CONTSETUP_VEHICLE_COLUMN) { + + if (optionIdx == m_nSelectedListRow && controllerAction != -1 && controllerAction != -2) { + m_CurrCntrlAction = controllerAction; + if (m_bWaitingForNewKeyBind) { + static bool showWaitingText = false; + if (bindingsForThisOpt > 0) { + wchar *seperator = TheText.Get("FEC_IBT"); + CFont::PrintString(nextX, nextY, seperator); + nextX += CFont::GetStringWidth(seperator, true) + bindingMargin; + } + static uint32 lastWaitingTextFlash = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastWaitingTextFlash > 150) { + showWaitingText = !showWaitingText; + lastWaitingTextFlash = CTimer::GetTimeInMillisecondsPauseMode(); + } + if (showWaitingText) { + CFont::SetColor(CRGBA(55, 55, 55, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_QUE")); // "???" + } + SET_FONT_FOR_HELPER_TEXT + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + if (m_bKeyChangeNotProcessed) { + CFont::PrintString(MENU_X_LEFT_ALIGNED(275.0f), SCREEN_SCALE_FROM_BOTTOM(114.0f), TheText.Get("FET_CIG")); // BACKSPACE TO CLEAR - LMB,RETURN TO CHANGE + } else { + CFont::PrintString(MENU_X_LEFT_ALIGNED(275.0f), SCREEN_SCALE_FROM_BOTTOM(114.0f), TheText.Get("FET_RIG")); // SELECT A NEW CONTROL FOR THIS ACTION OR ESC TO CANCEL + } + + SET_FONT_FOR_LIST_ITEM + if (!m_bKeyIsOK) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + + m_bKeyIsOK = true; + } else { + SET_FONT_FOR_HELPER_TEXT + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + CFont::PrintString(MENU_X_LEFT_ALIGNED(275.0f), SCREEN_SCALE_FROM_BOTTOM(114.0f), TheText.Get("FET_CIG")); // BACKSPACE TO CLEAR - LMB,RETURN TO CHANGE + SET_FONT_FOR_LIST_ITEM + m_bKeyIsOK = false; + m_bKeyChangeNotProcessed = false; + } + } else if (optionIdx == m_nSelectedListRow) { + SET_FONT_FOR_HELPER_TEXT + CFont::SetColor(CRGBA(55, 55, 55, FadeIn(255))); + CFont::PrintString(MENU_X_LEFT_ALIGNED(275.0f), SCREEN_SCALE_FROM_BOTTOM(114.0f), TheText.Get("FET_EIG")); // CANNOT SET A CONTROL FOR THIS ACTION + SET_FONT_FOR_LIST_ITEM + } + } + } +} + +void +CMenuManager::DrawControllerScreenExtraText(int yStart, int xStart, int lineHeight) +{ + int extraTextStart = GetStartOptionsCntrlConfigScreens(); + int numOpts = GetNumOptionsCntrlConfigScreens(); + int spacing = MENU_X(10.0f); + for (int i = extraTextStart; i < extraTextStart + numOpts; i++) { + int numTextsPrinted = 0; + int nextX = xStart; + for (int j = 1; j < 5; j++) { + wchar *text = ControlsManager.GetControllerSettingTextWithOrderNumber((e_ControllerAction)i, (eContSetOrder)j); + if (text) + ++numTextsPrinted; + + if (text) { + // Seperator + if (numTextsPrinted > 1) { + CFont::PrintString(nextX, MENU_Y(yStart), TheText.Get("FEC_IBT")); + nextX = CFont::GetStringWidth(TheText.Get("FEC_IBT"), true) + spacing + nextX; + } + CFont::PrintString(nextX, MENU_Y(yStart), text); + } + if (text) + nextX = CFont::GetStringWidth(text, true) + spacing + nextX; + } + if (m_nCurrOption == i - extraTextStart && m_bWaitingForNewKeyBind) { + static bool waitingTextVisible = false; + + // Seperator + if (numTextsPrinted > 0) { + CFont::PrintString(nextX, MENU_Y(yStart), TheText.Get("FEC_IBT")); + nextX = CFont::GetStringWidth(TheText.Get("FEC_IBT"), true) + spacing + nextX; + } + static uint32 lastStateChange = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastStateChange > 150) { + waitingTextVisible = !waitingTextVisible; + lastStateChange = CTimer::GetTimeInMillisecondsPauseMode(); + } + if (waitingTextVisible) { + CFont::SetColor(CRGBA(255, 255, 0, FadeIn(255))); + CFont::PrintString(nextX, MENU_Y(yStart), TheText.Get("FEC_QUE")); + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + } + yStart += lineHeight; + } + wchar *error = nil; + if (DisplayComboButtonErrMsg) + error = ControlsManager.GetButtonComboText((e_ControllerAction)(m_nCurrOption + extraTextStart)); + + if (error) { + CFont::SetColor(CRGBA(233, 22, 159, 255)); + CFont::PrintString(xStart, MENU_Y(yStart + 10), error); + } +} + +void +CMenuManager::DrawControllerSetupScreen() +{ + float rowHeight; + switch (m_ControlMethod) { + case CONTROL_STANDARD: + rowHeight = CONTSETUP_STANDARD_ROW_HEIGHT; + break; + case CONTROL_CLASSIC: + rowHeight = CONTSETUP_CLASSIC_ROW_HEIGHT; + break; + default: + break; + } + RESET_FONT_FOR_NEW_PAGE + + SET_FONT_FOR_MENU_HEADER + + switch (m_ControlMethod) { + case CONTROL_STANDARD: + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), + TheText.Get(aScreens[m_nCurrScreen].m_ScreenName)); + break; + case CONTROL_CLASSIC: + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), + TheText.Get("FET_CTI")); + break; + default: + break; + } + wchar *actionTexts[31]; + actionTexts[0] = TheText.Get("FEC_FIR"); + actionTexts[1] = TheText.Get("FEC_NWE"); + actionTexts[2] = TheText.Get("FEC_PWE"); + actionTexts[3] = TheText.Get("FEC_FOR"); + actionTexts[4] = TheText.Get("FEC_BAC"); + actionTexts[5] = TheText.Get("FEC_LEF"); + actionTexts[6] = TheText.Get("FEC_RIG"); + actionTexts[7] = TheText.Get("FEC_ZIN"); + actionTexts[8] = TheText.Get("FEC_ZOT"); + actionTexts[9] = TheText.Get("FEC_EEX"); + actionTexts[10] = TheText.Get("FEC_RAD"); + actionTexts[11] = TheText.Get("FEC_HRN"); + actionTexts[12] = TheText.Get("FEC_SUB"); + actionTexts[13] = TheText.Get("FEC_CMR"); + actionTexts[14] = TheText.Get("FEC_JMP"); + actionTexts[15] = TheText.Get("FEC_SPN"); + actionTexts[16] = TheText.Get("FEC_HND"); + actionTexts[17] = TheText.Get("FEC_TAR"); + if (m_ControlMethod == CONTROL_CLASSIC) { + actionTexts[18] = TheText.Get("FEC_TFL"); + actionTexts[19] = TheText.Get("FEC_TFR"); + actionTexts[20] = TheText.Get("FEC_TFU"); + actionTexts[21] = TheText.Get("FEC_TFD"); + actionTexts[22] = TheText.Get("FEC_LBA"); + actionTexts[23] = TheText.Get("FEC_LOL"); + actionTexts[24] = TheText.Get("FEC_LOR"); + actionTexts[25] = TheText.Get("FEC_LUD"); + actionTexts[26] = TheText.Get("FEC_LDU"); + actionTexts[27] = TheText.Get("FEC_NTR"); + actionTexts[28] = TheText.Get("FEC_PTT"); + actionTexts[29] = TheText.Get("FEC_CEN"); + actionTexts[30] = nil; + } else { + actionTexts[18] = TheText.Get("FEC_TFL"); + actionTexts[19] = TheText.Get("FEC_TFR"); + actionTexts[20] = TheText.Get("FEC_TFU"); + actionTexts[21] = TheText.Get("FEC_TFD"); + actionTexts[22] = TheText.Get("FEC_LBA"); + actionTexts[23] = TheText.Get("FEC_LOL"); + actionTexts[24] = TheText.Get("FEC_LOR"); + actionTexts[25] = nil; + } + + // Gray panel background + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(CONTSETUP_LIST_LEFT), MENU_Y(CONTSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(CONTSETUP_LIST_RIGHT), SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_LIST_BOTTOM)), + CRGBA(LIST_BACKGROUND_COLOR.r, LIST_BACKGROUND_COLOR.g, LIST_BACKGROUND_COLOR.b, FadeIn(LIST_BACKGROUND_COLOR.a))); + + if (m_nCurrExLayer == HOVEROPTION_LIST) + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + else + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + + // List header + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT), MENU_Y(MENUACTION_SCALE_MULT)); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_1_X), MENU_Y(CONTSETUP_LIST_TOP), TheText.Get("FET_CAC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_2_X), MENU_Y(CONTSETUP_LIST_TOP), TheText.Get("FET_CFT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_3_X), MENU_Y(CONTSETUP_LIST_TOP), TheText.Get("FET_CCR")); + SET_FONT_FOR_LIST_ITEM + + int yStart; + if (m_ControlMethod == CONTROL_CLASSIC) + yStart = CONTSETUP_LIST_TOP + CONTSETUP_LIST_HEADER_HEIGHT + 1; + else + yStart = CONTSETUP_LIST_TOP + CONTSETUP_LIST_HEADER_HEIGHT + 5; + + float optionYBottom = yStart + rowHeight; + for (int i = 0; i < ARRAY_SIZE(actionTexts); ++i) { + wchar *actionText = actionTexts[i]; + if (!actionText) + break; + + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(CONTSETUP_LIST_LEFT + 2.0f) && + m_nMousePosX < MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_3_X + CONTSETUP_BOUND_COLUMN_WIDTH)) { + + if (m_nMousePosY > MENU_Y(i * rowHeight + yStart) && m_nMousePosY < MENU_Y(i * rowHeight + optionYBottom)) { + if (m_nOptionMouseHovering != i && m_nCurrExLayer == HOVEROPTION_LIST) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nOptionMouseHovering = i; + if (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY) { + m_nCurrExLayer = HOVEROPTION_LIST; + m_nSelectedListRow = i; + + // why different number for 3rd column hovering X?? this function is a mess +#ifdef FIX_BUGS + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(0.0f) && m_nMousePosX < MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_2_X + CONTSETUP_BOUND_COLUMN_WIDTH)) { +#else + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(0.0f) && m_nMousePosX < MENU_X_LEFT_ALIGNED(370.0f)) { +#endif + if (m_nSelectedContSetupColumn != CONTSETUP_PED_COLUMN && m_nCurrExLayer == HOVEROPTION_LIST) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nSelectedContSetupColumn = CONTSETUP_PED_COLUMN; +#ifdef FIX_BUGS + } else if (m_nMousePosX > MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_2_X + CONTSETUP_BOUND_COLUMN_WIDTH) && m_nMousePosX < SCREEN_WIDTH) { +#else + } else if (m_nMousePosX > MENU_X_LEFT_ALIGNED(370.0f) && m_nMousePosX < SCREEN_WIDTH) { +#endif + if (m_nSelectedContSetupColumn != CONTSETUP_VEHICLE_COLUMN && m_nCurrExLayer == HOVEROPTION_LIST) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nSelectedContSetupColumn = CONTSETUP_VEHICLE_COLUMN; + } + } + // what?? + if (m_nHoverOption == HOVEROPTION_SKIN) { + if (i == m_nSelectedListRow) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + m_bWaitingForNewKeyBind = true; + m_bStartWaitingForKeyBind = true; + pControlEdit = &m_KeyPressedCode; + } + } else + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + } + if (m_nSelectedListRow != i) + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + else if (m_nCurrExLayer == HOVEROPTION_LIST) + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + + CFont::SetRightJustifyOff(); + if (m_PrefsLanguage == LANGUAGE_GERMAN && (i == 20 || i == 21)) + CFont::SetScale(MENU_X(0.32f), MENU_Y(SMALLESTTEXT_Y_SCALE)); + else + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE), MENU_Y(SMALLESTTEXT_Y_SCALE)); + + CFont::PrintString(MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_1_X), MENU_Y(i * rowHeight + yStart), actionText); + } + DrawControllerBound(yStart, MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_2_X), rowHeight, CONTSETUP_PED_COLUMN); + DrawControllerBound(yStart, MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_3_X), rowHeight, CONTSETUP_VEHICLE_COLUMN); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X), MENU_Y(MENU_TEXT_SIZE_Y)); + + if ((m_nMousePosX > MENU_X_RIGHT_ALIGNED(CONTSETUP_BACK_RIGHT) - CFont::GetStringWidth(TheText.Get("FEDS_TB"), true) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(CONTSETUP_BACK_RIGHT) && m_nMousePosY > SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_BACK_BOTTOM) + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_BACK_BOTTOM - CONTSETUP_BACK_HEIGHT)) || m_nCurrExLayer == HOVEROPTION_BACK) { + m_nHoverOption = HOVEROPTION_BACK; + + } else if (m_nMousePosX > MENU_X_LEFT_ALIGNED(CONTSETUP_LIST_LEFT + 2.0f) && m_nMousePosX < MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_3_X + CONTSETUP_BOUND_COLUMN_WIDTH) + && m_nMousePosY > MENU_Y(CONTSETUP_LIST_TOP + CONTSETUP_LIST_HEADER_HEIGHT) && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_LIST_BOTTOM + 5.0f)) { + m_nHoverOption = HOVEROPTION_LIST; + + } else { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + + // Back button and it's shadow + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X), MENU_Y(MENU_TEXT_SIZE_Y)); + CFont::SetRightJustifyOn(); + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(90))); + for (int i = 0; i < 2; i++) { + CFont::PrintString(MENU_X_RIGHT_ALIGNED(CONTSETUP_BACK_RIGHT - 2.0f - i), + SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_BACK_BOTTOM - 4.0f - i), TheText.Get("FEDS_TB")); + + if (m_nHoverOption == HOVEROPTION_BACK) + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + else + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } +} + +void +CMenuManager::DrawFrontEnd() +{ + CFont::SetAlphaFade(255.0f); + +#ifdef PS2_LIKE_MENU + #define setBbItem(a, b, c) strcpy(a.name, b); a.screenId = c; + if (m_nCurrScreen == MENUPAGE_NONE) { + if (m_bGameNotLoaded) { + if (bbTabCount != 6) { + setBbItem(bbNames[0], "FEB_SAV",MENUPAGE_NEW_GAME) + setBbItem(bbNames[1], "FEB_CON",MENUPAGE_CONTROLLER_PC) + setBbItem(bbNames[2], "FEB_AUD",MENUPAGE_SOUND_SETTINGS) + setBbItem(bbNames[3], "FEB_DIS",MENUPAGE_DISPLAY_SETTINGS) + setBbItem(bbNames[4], "FEB_LAN",MENUPAGE_LANGUAGE_SETTINGS) + setBbItem(bbNames[5], "FESZ_QU",MENUPAGE_EXIT) + bbTabCount = 6; + } + } else { + if (bbTabCount != 8) { + setBbItem(bbNames[0], "FEB_STA",MENUPAGE_STATS) + setBbItem(bbNames[1], "FEB_SAV",MENUPAGE_NEW_GAME) + setBbItem(bbNames[2], "FEB_BRI",MENUPAGE_BRIEFS) + setBbItem(bbNames[3], "FEB_CON",MENUPAGE_CONTROLLER_PC) + setBbItem(bbNames[4], "FEB_AUD",MENUPAGE_SOUND_SETTINGS) + setBbItem(bbNames[5], "FEB_DIS",MENUPAGE_DISPLAY_SETTINGS) + setBbItem(bbNames[6], "FEB_LAN",MENUPAGE_LANGUAGE_SETTINGS) + setBbItem(bbNames[7], "FESZ_QU",MENUPAGE_EXIT) + bbTabCount = 8; + } + } + m_nCurrScreen = bbNames[0].screenId; + bottomBarActive = true; + curBottomBarOption = 0; + } + #undef setBbItem +#else + if (m_nCurrScreen == MENUPAGE_NONE) { + if (m_bGameNotLoaded) { + m_nCurrScreen = MENUPAGE_START_MENU; + } else { + m_nCurrScreen = MENUPAGE_PAUSE_MENU; + } + } +#endif + + if (m_nCurrOption == 0 && aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) + m_nCurrOption = 1; + +#ifdef PS2_SAVE_DIALOG + if(m_bRenderGameInMenu) + DrawFrontEndSaveZone(); + else +#endif + DrawFrontEndNormal(); + + PrintErrorMessage(); +} + +#ifdef PS2_SAVE_DIALOG +void +CMenuManager::DrawFrontEndSaveZone() +{ + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + + // Not original dimensions, have been changed to fit PC screen & PC menu layout. + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(30.0f), MENU_Y(50.0f), MENU_X_RIGHT_ALIGNED(30.0f), SCREEN_SCALE_FROM_BOTTOM(50.0f)), CRGBA(0, 0, 0, 175)); + + m_nMenuFadeAlpha = 255; + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + Draw(); + + CFont::DrawFonts(); + + // Draw mouse + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + if (m_bShowMouse) { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + CRect mouse(0.0f, 0.0f, MENU_X(75.0f), MENU_Y(75.0f)); + CRect shad(MENU_X(10.0f), MENU_Y(3.0f), MENU_X(85.0f), MENU_Y(78.0f)); + + mouse.Translate(m_nMousePosX, m_nMousePosY); + shad.Translate(m_nMousePosX, m_nMousePosY); + if(field_518 == 4){ + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(mouse, CRGBA(255, 255, 255, 255)); + }else{ + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(mouse, CRGBA(255, 255, 255, 255)); + } + } +} +#endif + +#ifdef PS2_LIKE_MENU +void +CMenuManager::DrawFrontEndNormal() +{ + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + + if (!m_bGameNotLoaded) { + CSprite2d *bg = LoadSplash(nil); + bg->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(48, 48, 48, 255)); + } else { + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(0, 0, 0, 255)); + } + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + m_aFrontEndSprites[FE2_MAINPANEL_UL].Draw(CRect(MENU_X_LEFT_ALIGNED(0.0f), 0.0f, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2), CRGBA(255, 255, 255, 255)); + m_aFrontEndSprites[FE2_MAINPANEL_UR].Draw(CRect(SCREEN_WIDTH / 2, 0.0f, MENU_X_RIGHT_ALIGNED(0.0f), SCREEN_HEIGHT / 2), CRGBA(255, 255, 255, 255)); + m_aFrontEndSprites[FE2_MAINPANEL_DL].Draw(CRect(MENU_X_LEFT_ALIGNED(0.0f), SCREEN_HEIGHT / 2, SCREEN_WIDTH / 2, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + m_aFrontEndSprites[FE2_MAINPANEL_DR].Draw(CRect(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, MENU_X_RIGHT_ALIGNED(0.0f), SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + eFrontendSprites currentSprite; + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + case MENUPAGE_START_MENU: + case MENUPAGE_PAUSE_MENU: + case MENUPAGE_EXIT: + currentSprite = FE_ICONSTATS; + break; + case MENUPAGE_LANGUAGE_SETTINGS: + currentSprite = FE_ICONLANGUAGE; + break; + case MENUPAGE_CHOOSE_LOAD_SLOT: + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_NEW_GAME_RELOAD: + case MENUPAGE_LOAD_SLOT_CONFIRM: + case MENUPAGE_DELETE_SLOT_CONFIRM: + currentSprite = FE_ICONSAVE; + break; + case MENUPAGE_DISPLAY_SETTINGS: + currentSprite = FE_ICONDISPLAY; + break; + case MENUPAGE_SOUND_SETTINGS: + currentSprite = FE_ICONAUDIO; + break; + case MENUPAGE_CONTROLLER_PC: + case MENUPAGE_OPTIONS: + case MENUPAGE_CONTROLLER_SETTINGS: + case MENUPAGE_KEYBOARD_CONTROLS: + case MENUPAGE_MOUSE_CONTROLS: + currentSprite = FE_ICONCONTROLS; + break; + default: + /*case MENUPAGE_NEW_GAME: */ + /*case MENUPAGE_BRIEFS: */ + currentSprite = FE_ICONBRIEF; + break; + } + + static float fadeAlpha = 0.0f; + + if (m_nMenuFadeAlpha < 255) { + m_nMenuFadeAlpha += 20 * CTimer::GetLogicalFramesPassed(); + } else { + // TODO: what is this? waiting mouse? + if(field_518 == 4){ + if(m_nHoverOption == HOVEROPTION_3 || m_nHoverOption == HOVEROPTION_4 || + m_nHoverOption == HOVEROPTION_5 || m_nHoverOption == HOVEROPTION_6 || m_nHoverOption == HOVEROPTION_7) + + field_518 = 2; + else + field_518 = 1; + } + } + + m_aFrontEndSprites[currentSprite].Draw(CRect(MENU_X_LEFT_ALIGNED(50.0f), MENU_Y(50.0f), MENU_X_RIGHT_ALIGNED(50.0f), SCREEN_SCALE_FROM_BOTTOM(95.0f)), CRGBA(255, 255, 255, m_nMenuFadeAlpha > 255 ? 255 : m_nMenuFadeAlpha)); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + switch (m_nCurrScreen) { + case MENUPAGE_SKIN_SELECT: + DrawPlayerSetupScreen(); + break; + case MENUPAGE_KEYBOARD_CONTROLS: + DrawControllerSetupScreen(); + break; + default: + Draw(); + break; + } + + // Positions/style from PS2 menu, credits to Fire_Head + /* Draw controller buttons */ + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.35f), SCREEN_SCALE_Y(0.64f)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(MENU_X_MARGIN)); // 600.0f + CFont::SetColor(CRGBA(16, 16, 16, 255)); + switch (m_nCurrScreen) { + + // Page names overlaps buttons on those. + case MENUPAGE_MOUSE_CONTROLS: + case MENUPAGE_KEYBOARD_CONTROLS: + break; + + default: + { + CFont::PrintString(MENU_X_LEFT_ALIGNED(52.0f), MENU_Y(360.0f), TheText.Get("FEDS_SE")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(52.0f), MENU_Y(372.0f), TheText.Get("FEDS_BA")); + if (!m_bGameNotLoaded) + CFont::PrintString(MENU_X_LEFT_ALIGNED(52.0f), MENU_Y(384.0f), TheText.Get("FEDS_ST")); + + if (bottomBarActive) + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f), MENU_Y(372.0f), TheText.Get("FEDS_AM")); // <>-CHANGE MENU + else if (m_nCurrScreen != MENUPAGE_STATS && m_nCurrScreen != MENUPAGE_BRIEFS) { + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f), MENU_Y(360.0f + 3.5f), TheText.Get("FEA_UP")); // ; + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f), MENU_Y(384.0f - 3.5f), TheText.Get("FEA_DO")); // = + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f - 10.0f), MENU_Y(372.0f), TheText.Get("FEA_LE")); // < + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f + 11.0f), MENU_Y(372.0f), TheText.Get("FEA_RI")); // > + CFont::PrintString(MENU_X_LEFT_ALIGNED(242.0f + 20.0f), MENU_Y(372.0f), TheText.Get("FEDSAS3")); // - CHANGE SELECTION + } + + break; + } + } + + #define optionWidth MENU_X(66.0f) + #define rawOptionHeight 22.0f + #define optionBottom SCREEN_SCALE_FROM_BOTTOM(20.0f) + #define optionTop SCREEN_SCALE_FROM_BOTTOM(20.0f + rawOptionHeight) + #define leftPadding MENU_X_LEFT_ALIGNED(90.0f) + wchar *str; + hoveredBottomBarOption = -1; + if (curBottomBarOption != -1) { + + // This active tab sprite is needlessly big + m_aFrontEndSprites[FE2_TABACTIVE].Draw(CRect(leftPadding - MENU_X(2.0f) + (optionWidth) * curBottomBarOption, optionTop, + leftPadding - MENU_X(5.0f) + optionWidth * (curBottomBarOption + 2), optionBottom + MENU_Y(rawOptionHeight - 9.0f)), + CRGBA(CRGBA(255, 255, 255, 255))); + + for (int i = 0; i < bbTabCount; i++) { + float xStart = leftPadding + optionWidth * i; + if (CheckHover(xStart, xStart + optionWidth, optionTop, optionBottom)) + hoveredBottomBarOption = i; + + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(0.35f), MENU_Y(0.7f)); + CFont::SetRightJustifyOff(); + if (hoveredBottomBarOption == i && hoveredBottomBarOption != curBottomBarOption) + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, 255)); + else { + if(bottomBarActive || curBottomBarOption == i) + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, 255)); + else + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, 110)); + } + + str = TheText.Get(bbNames[i].name); + + CFont::PrintString(xStart + MENU_X(4.0f), SCREEN_SCALE_FROM_BOTTOM(39.0f), str); + + } + } + #undef optionBottom + #undef optionTop + #undef leftPadding + #undef optionWidth + #undef rawOptionHeight + + CFont::DrawFonts(); + + // Draw mouse + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + if (m_bShowMouse) { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + CRect mouse(0.0f, 0.0f, MENU_X(75.0f), MENU_Y(75.0f)); + CRect shad(MENU_X(10.0f), MENU_Y(3.0f), MENU_X(85.0f), MENU_Y(78.0f)); + + mouse.Translate(m_nMousePosX, m_nMousePosY); + shad.Translate(m_nMousePosX, m_nMousePosY); + if(field_518 == 4){ + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(mouse, CRGBA(255, 255, 255, 255)); + }else{ + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(mouse, CRGBA(255, 255, 255, 255)); + } + } +} +#else +void +CMenuManager::DrawFrontEndNormal() +{ + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + + LoadSplash(nil); + + eMenuSprites previousSprite; + if (m_nMenuFadeAlpha < 255) { + switch (m_nPrevScreen) { + case MENUPAGE_STATS: + case MENUPAGE_START_MENU: + case MENUPAGE_PAUSE_MENU: + previousSprite = MENUSPRITE_MAINMENU; + break; + case MENUPAGE_NEW_GAME: + case MENUPAGE_CHOOSE_LOAD_SLOT: + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_NEW_GAME_RELOAD: + case MENUPAGE_LOAD_SLOT_CONFIRM: + case MENUPAGE_DELETE_SLOT_CONFIRM: + case MENUPAGE_EXIT: + previousSprite = MENUSPRITE_SINGLEPLAYER; + break; + case MENUPAGE_MULTIPLAYER_MAIN: + previousSprite = MENUSPRITE_MULTIPLAYER; + break; + case MENUPAGE_MULTIPLAYER_MAP: + case MENUPAGE_MULTIPLAYER_FIND_GAME: + case MENUPAGE_SKIN_SELECT: + case MENUPAGE_KEYBOARD_CONTROLS: + case MENUPAGE_MOUSE_CONTROLS: + previousSprite = MENUSPRITE_FINDGAME; + break; + case MENUPAGE_MULTIPLAYER_CONNECTION: + case MENUPAGE_MULTIPLAYER_MODE: + previousSprite = MENUSPRITE_CONNECTION; + break; + case MENUPAGE_MULTIPLAYER_CREATE: + previousSprite = MENUSPRITE_HOSTGAME; + break; + case MENUPAGE_SKIN_SELECT_OLD: + case MENUPAGE_OPTIONS: + previousSprite = MENUSPRITE_PLAYERSET; + break; + default: +#ifdef CUSTOM_FRONTEND_OPTIONS + CCustomScreenLayout *custom = aScreens[m_nPrevScreen].layout; + if (custom) { + previousSprite = custom->sprite; + break; + } + if (!custom) +#endif + previousSprite = MENUSPRITE_MAINMENU; + break; + } + + if (m_nPrevScreen == m_nCurrScreen) + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(0, 0, 0, 255 - m_nMenuFadeAlpha)); + else + m_aMenuSprites[previousSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255 - m_nMenuFadeAlpha)); + } + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + + eMenuSprites currentSprite = MENUSPRITE_MAINMENU; // actually uninitialized + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + case MENUPAGE_START_MENU: + case MENUPAGE_PAUSE_MENU: + currentSprite = MENUSPRITE_MAINMENU; + break; + case MENUPAGE_NEW_GAME: + case MENUPAGE_CHOOSE_LOAD_SLOT: + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_NEW_GAME_RELOAD: + case MENUPAGE_LOAD_SLOT_CONFIRM: + case MENUPAGE_DELETE_SLOT_CONFIRM: + case MENUPAGE_EXIT: + currentSprite = MENUSPRITE_SINGLEPLAYER; + break; + case MENUPAGE_MULTIPLAYER_MAIN: + currentSprite = MENUSPRITE_MULTIPLAYER; + break; + case MENUPAGE_MULTIPLAYER_MAP: + case MENUPAGE_MULTIPLAYER_FIND_GAME: + case MENUPAGE_SKIN_SELECT: + case MENUPAGE_KEYBOARD_CONTROLS: + case MENUPAGE_MOUSE_CONTROLS: + currentSprite = MENUSPRITE_FINDGAME; + break; + case MENUPAGE_MULTIPLAYER_CONNECTION: + case MENUPAGE_MULTIPLAYER_MODE: + currentSprite = MENUSPRITE_CONNECTION; + break; + case MENUPAGE_MULTIPLAYER_CREATE: + currentSprite = MENUSPRITE_HOSTGAME; + break; + case MENUPAGE_SKIN_SELECT_OLD: + case MENUPAGE_OPTIONS: + currentSprite = MENUSPRITE_PLAYERSET; + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + default: + CCustomScreenLayout *custom = aScreens[m_nCurrScreen].layout; + if (custom) { + previousSprite = custom->sprite; + } + break; +#endif + } + + if (m_nMenuFadeAlpha < 255) { + + // Famous transparent menu bug +#ifdef FIX_BUGS + m_nMenuFadeAlpha += 20 * CTimer::GetLogicalFramesPassed(); +#else + static uint32 LastFade = 0; + + if(CTimer::GetTimeInMillisecondsPauseMode() - LastFade > 10){ + m_nMenuFadeAlpha += 20; + LastFade = CTimer::GetTimeInMillisecondsPauseMode(); + } +#endif + + if (m_nMenuFadeAlpha > 255){ + m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + } else { + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, m_nMenuFadeAlpha)); + } + } else { + m_aMenuSprites[currentSprite].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + // TODO: what is this? waiting mouse? + if(field_518 == 4){ + if(m_nHoverOption == HOVEROPTION_3 || m_nHoverOption == HOVEROPTION_4 || + m_nHoverOption == HOVEROPTION_5 || m_nHoverOption == HOVEROPTION_6 || m_nHoverOption == HOVEROPTION_7) + + field_518 = 2; + else + field_518 = 1; + } + } + +#ifdef RED_DELETE_BACKGROUND + if (m_nCurrScreen == MENUPAGE_CHOOSE_DELETE_SLOT || m_nCurrScreen == MENUPAGE_DELETE_SLOT_CONFIRM) { + CSprite2d::Draw2DPolygon(0.0f, 0.0f, + SCREEN_WIDTH, 0.0f, + 0.0f, SCREEN_HEIGHT, + SCREEN_WIDTH, SCREEN_HEIGHT, + CRGBA(150, 0, 0, 80)); + } +#endif + + // GTA LOGO + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + if (m_nCurrScreen == MENUPAGE_START_MENU || m_nCurrScreen == MENUPAGE_PAUSE_MENU) { + if (CGame::frenchGame || CGame::germanGame || !CGame::nastyGame) + m_aMenuSprites[MENUSPRITE_GTA3LOGO].Draw(CRect(MENU_X_LEFT_ALIGNED(205.0f), MENU_Y(70.0f), MENU_X_LEFT_ALIGNED(435.0f), MENU_Y(180.0f)), CRGBA(255, 255, 255, FadeIn(255))); + else + m_aMenuSprites[MENUSPRITE_GTALOGO].Draw(CRect(MENU_X_LEFT_ALIGNED(225.0f), MENU_Y(40.0f), MENU_X_LEFT_ALIGNED(415.0f), MENU_Y(210.0f)), CRGBA(255, 255, 255, FadeIn(255))); + } + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + switch (m_nCurrScreen) { + case MENUPAGE_SKIN_SELECT: + DrawPlayerSetupScreen(); + break; + case MENUPAGE_KEYBOARD_CONTROLS: + DrawControllerSetupScreen(); + break; + default: + Draw(); + break; + } + + CFont::DrawFonts(); + + // Draw mouse + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + if (m_bShowMouse) { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + CRect mouse(0.0f, 0.0f, MENU_X(75.0f), MENU_Y(75.0f)); + CRect shad(MENU_X(10.0f), MENU_Y(3.0f), MENU_X(85.0f), MENU_Y(78.0f)); + + mouse.Translate(m_nMousePosX, m_nMousePosY); + shad.Translate(m_nMousePosX, m_nMousePosY); + if(field_518 == 4){ + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSET].Draw(mouse, CRGBA(255, 255, 255, 255)); + }else{ + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(shad, CRGBA(100, 100, 100, 50)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + m_aMenuSprites[MENUSPRITE_MOUSE].Draw(mouse, CRGBA(255, 255, 255, 255)); + } + } +} +#endif + +void +CMenuManager::DrawPlayerSetupScreen() +{ + RESET_FONT_FOR_NEW_PAGE + + SET_FONT_FOR_MENU_HEADER + + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), TheText.Get("FET_PS")); + + // lstrcpy's changed with strcpy + + if (!m_bSkinsEnumerated) { + OutputDebugString("Enumerating skin filenames from skins..."); + m_pSkinListHead.nextSkin = nil; + m_pSelectedSkin = &m_pSkinListHead; + m_pSelectedSkin->nextSkin = new tSkinInfo; + m_pSelectedSkin = m_pSelectedSkin->nextSkin; + m_pSelectedSkin->skinId = 0; + strcpy(m_pSelectedSkin->skinNameOriginal, DEFAULT_SKIN_NAME); + strcpy(m_pSelectedSkin->skinNameDisplayed, UnicodeToAscii(TheText.Get("FET_DSN"))); + int nextSkinId = 1; + m_pSelectedSkin->nextSkin = nil; + + WIN32_FIND_DATA FindFileData; + SYSTEMTIME SystemTime; + HANDLE handle = FindFirstFile("skins\\*.bmp", &FindFileData); + for (int i = 1; handle != INVALID_HANDLE_VALUE && i; i = FindNextFile(handle, &FindFileData)) { + if (strcmp(FindFileData.cFileName, DEFAULT_SKIN_NAME) != 0) { + m_pSelectedSkin->nextSkin = new tSkinInfo; + m_pSelectedSkin = m_pSelectedSkin->nextSkin; + m_pSelectedSkin->skinId = nextSkinId; + strcpy(m_pSelectedSkin->skinNameOriginal, FindFileData.cFileName); + strcpy(m_pSelectedSkin->skinNameDisplayed, FindFileData.cFileName); + FileTimeToSystemTime(&FindFileData.ftLastWriteTime, &SystemTime); + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &SystemTime, 0, m_pSelectedSkin->date, 255); + ++nextSkinId; + m_pSelectedSkin->nextSkin = nil; + } + } + FindClose(handle); + m_nSkinsTotal = nextSkinId; + char nameTemp[256]; + for (m_pSelectedSkin = m_pSkinListHead.nextSkin; m_pSelectedSkin; m_pSelectedSkin = m_pSelectedSkin->nextSkin) { + // Drop extension + int oldLength = (int)strlen(m_pSelectedSkin->skinNameDisplayed); + m_pSelectedSkin->skinNameDisplayed[oldLength - 4] = '\0'; + m_pSelectedSkin->skinNameOriginal[oldLength - 4] = '\0'; + + // Fill to 40 bytes-39 chars, idk why. This is done in sepearate function in game. + strncpy(nameTemp, m_pSelectedSkin->skinNameDisplayed, 39); // game doesn't do that, but in our day strncpy to same string is forbidden + strncpy(m_pSelectedSkin->skinNameDisplayed, nameTemp, 39); + if (oldLength - 4 > 39) + m_pSelectedSkin->skinNameDisplayed[39] = '\0'; + + // Make string lowercase, except first letter + strlwr(m_pSelectedSkin->skinNameDisplayed); + strncpy(nameTemp, m_pSelectedSkin->skinNameDisplayed, 1); + strupr(nameTemp); + strncpy(m_pSelectedSkin->skinNameDisplayed, nameTemp, 1); + + // Change some chars +#ifdef FIX_BUGS + for (int k = 0; m_pSelectedSkin->skinNameDisplayed[k] != '\0'; ++k) { +#else + for (int k = 0; m_pSelectedSkin->skinNameOriginal[k] != '\0'; ++k) { +#endif + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "_", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], " ", 1); + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "@", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], " ", 1); + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "{", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], "(", 1); + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "}", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], ")", 1); + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[k], "£", 1)) + strncpy(&m_pSelectedSkin->skinNameDisplayed[k], "$", 1); + } + + // Make letters after whitespace uppercase + for (int l = 0; m_pSelectedSkin->skinNameDisplayed[l] != '\0'; ++l) { + if (!strncmp(&m_pSelectedSkin->skinNameDisplayed[l], " ", 1)) { + if (m_pSelectedSkin->skinNameDisplayed[l + 1]) { + strncpy(nameTemp, &m_pSelectedSkin->skinNameDisplayed[l + 1], 1); + strupr(nameTemp); + strncpy(&m_pSelectedSkin->skinNameDisplayed[l + 1], nameTemp, 1); + } + } + } + } + OutputDebugString("Finished enumerating skin files."); + m_bSkinsEnumerated = true; + } + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM)), + CRGBA(LIST_BACKGROUND_COLOR.r, LIST_BACKGROUND_COLOR.g, LIST_BACKGROUND_COLOR.b, FadeIn(LIST_BACKGROUND_COLOR.a))); + + // Header (Skin - Date) + if (m_nCurrExLayer == HOVEROPTION_LIST) { + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT), MENU_Y(MENUACTION_SCALE_MULT)); + CFont::SetRightJustifyOn(); + CFont::PrintString(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_DATE_COLUMN_RIGHT), MENU_Y(PLAYERSETUP_LIST_TOP), TheText.Get("FES_DAT")); + switch (m_PrefsLanguage) { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + CFont::SetScale(MENU_X(0.6f), MENU_Y(MENUACTION_SCALE_MULT)); + break; + default: + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT), MENU_Y(MENUACTION_SCALE_MULT)); + break; + } + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(PLAYERSETUP_SKIN_COLUMN_LEFT), MENU_Y(PLAYERSETUP_LIST_TOP), TheText.Get("FES_SKN")); + + // Skin list + SET_FONT_FOR_LIST_ITEM + if (m_nSkinsTotal > 0) { + for (m_pSelectedSkin = m_pSkinListHead.nextSkin; m_pSelectedSkin->skinId != m_nFirstVisibleRowOnList; + m_pSelectedSkin = m_pSelectedSkin->nextSkin); + + int rowTextY = PLAYERSETUP_LIST_BODY_TOP - 1; + int orderInVisibles = 0; + int rowEndY = PLAYERSETUP_LIST_BODY_TOP + PLAYERSETUP_ROW_HEIGHT + 1; + int rowStartY = PLAYERSETUP_LIST_BODY_TOP; + for (int rowIdx = m_nFirstVisibleRowOnList; + rowIdx < m_nFirstVisibleRowOnList + MAX_VISIBLE_LIST_ROW && m_pSelectedSkin; ) { + + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT) && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT)) { + if (m_nMousePosY > MENU_Y(rowStartY) && m_nMousePosY < MENU_Y(rowEndY)) { + m_nOptionMouseHovering = rowIdx; + if (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY) { + m_nCurrExLayer = HOVEROPTION_LIST; + } + if (m_nHoverOption == HOVEROPTION_SKIN) { + if (rowIdx == m_nSelectedListRow) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + if (m_nSkinsTotal > 0) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + SaveSettings(); + } + } else { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + m_nCurrExLayer = HOVEROPTION_LIST; + m_nSelectedListRow = rowIdx; + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + } + } + } + + // Preview skin/change color of row when we focused on another row. + if (orderInVisibles == m_nSelectedListRow - m_nFirstVisibleRowOnList) { + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + static int lastSelectedSkin = -1; + if (m_nSelectedListRow != lastSelectedSkin) { + strcpy(m_aSkinName, m_pSelectedSkin->skinNameOriginal); + CWorld::Players[0].SetPlayerSkin(m_aSkinName); + } + lastSelectedSkin = m_nSelectedListRow; + } else if (!strcmp(m_PrefsSkinFile, m_pSelectedSkin->skinNameOriginal)) { + CFont::SetColor(CRGBA(255, 255, 155, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(LIST_OPTION_COLOR.r, LIST_OPTION_COLOR.g, LIST_OPTION_COLOR.b, FadeIn(LIST_OPTION_COLOR.a))); + } + wchar unicodeTemp[80]; + AsciiToUnicode(m_pSelectedSkin->skinNameDisplayed, unicodeTemp); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(PLAYERSETUP_SKIN_COLUMN_LEFT), MENU_Y(rowTextY), unicodeTemp); + + // If not "Default skin" option + if (rowIdx != 0) { + char dateTemp[32]; + sprintf(dateTemp, "%s", m_pSelectedSkin->date); + AsciiToUnicode(dateTemp, unicodeTemp); + CFont::SetRightJustifyOn(); + CFont::PrintString(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_DATE_COLUMN_RIGHT), MENU_Y(rowTextY), unicodeTemp); + } + ++orderInVisibles; + rowEndY += PLAYERSETUP_ROW_HEIGHT; + rowStartY += PLAYERSETUP_ROW_HEIGHT; + rowTextY += PLAYERSETUP_ROW_HEIGHT; + ++rowIdx; + m_pSelectedSkin = m_pSelectedSkin->nextSkin; + } + // Scrollbar background + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2 - PLAYERSETUP_SCROLLBAR_WIDTH), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM)), CRGBA(100, 100, 66, FadeIn(205))); + + float scrollbarHeight = SCROLLBAR_MAX_HEIGHT / (m_nSkinsTotal / (float) MAX_VISIBLE_LIST_ROW); + float scrollbarBottom, scrollbarTop; + if (m_nSkinsTotal <= MAX_VISIBLE_LIST_ROW) { + scrollbarBottom = SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 4.0f); + scrollbarTop = MENU_Y(PLAYERSETUP_LIST_BODY_TOP); + + // Scrollbar shadow + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 1 - PLAYERSETUP_SCROLLBAR_WIDTH), scrollbarBottom + MENU_Y(1.0f)), CRGBA(50, 50, 50, FadeIn(255))); + } else { +#ifdef FIX_BUGS + scrollbarBottom = MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 8 + m_nScrollbarTopMargin + scrollbarHeight); + scrollbarTop = MENU_Y(PLAYERSETUP_LIST_BODY_TOP + m_nScrollbarTopMargin); +#else + scrollbarBottom = MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 4 + m_nScrollbarTopMargin + scrollbarHeight - SCROLLBAR_MAX_HEIGHT / m_nSkinsTotal); + scrollbarTop = MENU_Y(SCROLLBAR_MAX_HEIGHT / m_nSkinsTotal + PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin); +#endif + // Scrollbar shadow + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 1 - PLAYERSETUP_SCROLLBAR_WIDTH), scrollbarBottom + MENU_Y(1.0f)), + CRGBA(50, 50, 50, FadeIn(255))); + + } + // Scrollbar + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 4), scrollbarTop, + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH), scrollbarBottom), + CRGBA(SCROLLBAR_COLOR.r, SCROLLBAR_COLOR.g, SCROLLBAR_COLOR.b, FadeIn(255))); + + // FIX: Scroll button dimensions are buggy, because: + // 1 - stretches the original image + // 2 - leaves gap between button and scrollbar + if (m_nHoverOption == HOVEROPTION_CLICKED_SCROLL_UP) { +#ifdef FIX_BUGS + m_aMenuSprites[MENUSPRITE_UPON].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION), MENU_Y(PLAYERSETUP_LIST_TOP + PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION)), + CRGBA(255, 255, 255, FadeIn(255))); +#else + m_aMenuSprites[MENUSPRITE_UPON].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(-20.0f), MENU_Y(PLAYERSETUP_LIST_TOP + 58)), + CRGBA(255, 255, 255, FadeIn(255))); +#endif + } else { +#ifdef FIX_BUGS + m_aMenuSprites[MENUSPRITE_UPOFF].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION), MENU_Y(PLAYERSETUP_LIST_TOP + PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION)), + CRGBA(255, 255, 255, FadeIn(255))); +#else + m_aMenuSprites[MENUSPRITE_UPOFF].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3), MENU_Y(PLAYERSETUP_LIST_TOP), + MENU_X_RIGHT_ALIGNED(-21.0f), MENU_Y(PLAYERSETUP_LIST_TOP + 58)), + CRGBA(255, 255, 255, FadeIn(255))); +#endif + } + + if (m_nHoverOption == HOVEROPTION_CLICKED_SCROLL_DOWN) { +#ifdef FIX_BUGS + m_aMenuSprites[MENUSPRITE_DOWNON].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION)), + CRGBA(255, 255, 255, FadeIn(255))); +#else + m_aMenuSprites[MENUSPRITE_DOWNON].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2), SCREEN_SCALE_FROM_BOTTOM(141.0f), + MENU_X_RIGHT_ALIGNED(-20.0f), SCREEN_SCALE_FROM_BOTTOM(83.0f)), + CRGBA(255, 255, 255, FadeIn(255))); +#endif + } else { +#ifdef FIX_BUGS + m_aMenuSprites[MENUSPRITE_DOWNOFF].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1), + MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1 - PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION)), + CRGBA(255, 255, 255, FadeIn(255))); +#else + m_aMenuSprites[MENUSPRITE_DOWNOFF].Draw(CRect(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3), SCREEN_SCALE_FROM_BOTTOM(141.0f), + MENU_X_RIGHT_ALIGNED(-21.0f), SCREEN_SCALE_FROM_BOTTOM(83.0f)), + CRGBA(255, 255, 255, FadeIn(255))); +#endif + + } + CPlayerSkin::RenderFrontendSkinEdit(); + + // Big apply button + if (strcmp(m_aSkinName, m_PrefsSkinFile) != 0) { + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + switch (m_PrefsLanguage) { + case LANGUAGE_FRENCH: + CFont::SetScale(MENU_X(1.1f), MENU_Y(1.9f)); + break; + case LANGUAGE_GERMAN: + CFont::SetScale(MENU_X(0.85f), MENU_Y(1.9f)); + break; + case LANGUAGE_ITALIAN: + case LANGUAGE_SPANISH: + CFont::SetScale(MENU_X(1.4f), MENU_Y(1.9f)); + break; + default: + CFont::SetScale(MENU_X(1.9f), MENU_Y(1.9f)); + break; + } + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(120))); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(20.0f), MENU_Y(220.0f), TheText.Get("FET_APL")); + } + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + + CFont::SetScale(MENU_X(SMALLTEXT_X_SCALE), MENU_Y(SMALLTEXT_Y_SCALE)); + + if ((m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 1) - CFont::GetStringWidth(TheText.Get("FEDS_TB"), true) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 1) + && m_nMousePosY > SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 3) + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 26)) + || m_nCurrExLayer == HOVEROPTION_BACK) { + if (m_nHoverOption != HOVEROPTION_BACK) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nHoverOption = HOVEROPTION_BACK; + + } else if ((strcmp(m_aSkinName, m_PrefsSkinFile) != 0 + && m_nMousePosX > MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT) + && m_nMousePosX < MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT) + CFont::GetStringWidth(TheText.Get("FES_SET"), true) + && m_nMousePosY > SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 3) + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 26)) + || m_nCurrExLayer == HOVEROPTION_USESKIN) { + if (m_nHoverOption != HOVEROPTION_USESKIN) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + m_nHoverOption = HOVEROPTION_USESKIN; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH - 2) + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_TOP) + && m_nMousePosY < MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 3)) { + if (m_nHoverOption != HOVEROPTION_CLICKED_SCROLL_UP && m_nHoverOption != HOVEROPTION_CLICKED_SCROLL_DOWN) + m_nHoverOption = HOVEROPTION_OVER_SCROLL_UP; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH - 2) + && m_nMousePosY > SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1) + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM)) { + if (m_nHoverOption != HOVEROPTION_CLICKED_SCROLL_UP && m_nHoverOption != HOVEROPTION_CLICKED_SCROLL_DOWN) + m_nHoverOption = HOVEROPTION_OVER_SCROLL_DOWN; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH - 2) + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 3) +#ifdef FIX_BUGS + && m_nMousePosY < MENU_Y(PLAYERSETUP_LIST_BODY_TOP + m_nScrollbarTopMargin)) { +#else + && m_nMousePosY < MENU_Y(SCROLLBAR_MAX_HEIGHT / m_nTotalListRow + PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin)) { +#endif + m_nHoverOption = HOVEROPTION_PAGEUP; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 2) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH - 2) +#ifdef FIX_BUGS + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 8 + m_nScrollbarTopMargin + scrollbarHeight) +#else + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin + scrollbarHeight - SCROLLBAR_MAX_HEIGHT / m_nTotalListRow) +#endif + && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM + PLAYERSETUP_SCROLLBUTTON_HEIGHT + 1)) { + m_nHoverOption = HOVEROPTION_PAGEDOWN; + + } else if (m_nMousePosX > MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 4) + && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - PLAYERSETUP_SCROLLBAR_WIDTH) +#ifdef FIX_BUGS + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP + m_nScrollbarTopMargin) + && m_nMousePosY < MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 8 + m_nScrollbarTopMargin + scrollbarHeight)) { +#else + && m_nMousePosY > MENU_Y(SCROLLBAR_MAX_HEIGHT / m_nTotalListRow + PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin) + && m_nMousePosY < MENU_Y(PLAYERSETUP_LIST_BODY_TOP - 3 + m_nScrollbarTopMargin + scrollbarHeight - SCROLLBAR_MAX_HEIGHT / m_nTotalListRow)) { +#endif + m_nHoverOption = HOVEROPTION_HOLDING_SCROLLBAR; + + } else if (m_nMousePosX > MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT) && m_nMousePosX < MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT) + && m_nMousePosY > MENU_Y(PLAYERSETUP_LIST_BODY_TOP + 1) && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM)) { + m_nHoverOption = HOVEROPTION_LIST; + + } else { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + } + } + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(SMALLTEXT_X_SCALE), MENU_Y(SMALLTEXT_Y_SCALE)); + CFont::SetRightJustifyOn(); + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(90))); + + // Back button + for (int i = 0; i < 2; i++) { + CFont::PrintString(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3 - i), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 5 - i), TheText.Get("FEDS_TB")); + if (m_nHoverOption == HOVEROPTION_BACK) { + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + } + CFont::SetRightJustifyOff(); + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(90))); + + // Use skin button + for (int i = 0; i < 2; i++) { + CFont::PrintString(MENU_X_LEFT_ALIGNED(i + PLAYERSETUP_LIST_LEFT), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 5 - i), TheText.Get("FES_SET")); + if (!strcmp(m_aSkinName, m_PrefsSkinFile)) { + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + } else if (m_nHoverOption == HOVEROPTION_USESKIN) { + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + } + +} + +int +CMenuManager::FadeIn(int alpha) +{ + if (m_nCurrScreen == MENUPAGE_LOADING_IN_PROGRESS || + m_nCurrScreen == MENUPAGE_SAVING_IN_PROGRESS || + m_nCurrScreen == MENUPAGE_DELETING) + return alpha; + + return Min(m_nMenuFadeAlpha, alpha); +} + +void +CMenuManager::FilterOutColorMarkersFromString(wchar *str, CRGBA &newColor) +{ + int newIdx = 0; + wchar copy[256], *c; + UnicodeStrcpy(copy, str); + + for (c = copy; *c != '\0'; c++) { + if (*c == '~') { + c++; + switch (*c) { + case 'b': newColor = CRGBA(40, 40, 255, 255); break; + case 'g': newColor = CRGBA(40, 235, 40, 255); break; + // There is no case for "h", is that a mistake? + case 'l': newColor = CRGBA(0, 0, 0, 255); break; + case 'p': newColor = CRGBA(255, 0, 255, 255); break; + case 'r': newColor = CRGBA(255, 0, 0, 255); break; + case 'w': newColor = CRGBA(255, 255, 255, 255); break; + case 'y': newColor = CRGBA(255, 255, 0, 255); break; + } + while (*c != '~') c++; + } else { + str[newIdx++] = *c; + } + } + str[newIdx] = '\0'; +} + +int +CMenuManager::GetStartOptionsCntrlConfigScreens() +{ + int number = 0; + switch (m_nCurrScreen) { + case MENUPAGE_CONTROLLER_PC_OLD3: + number = 34; + break; + case MENUPAGE_CONTROLLER_DEBUG: + number = 35; + break; + case MENUPAGE_KEYBOARD_CONTROLS: + number = 0; + break; + default: + break; + } + return number; +} + +void +CMenuManager::InitialiseChangedLanguageSettings() +{ + if (m_bFrontEnd_ReloadObrTxtGxt) { + m_bFrontEnd_ReloadObrTxtGxt = false; +#ifdef FIX_BUGS + if (gGameState > GS_INIT_ONCE) +#endif + CTimer::Stop(); + TheText.Unload(); + TheText.Load(); +#ifdef FIX_BUGS + if (gGameState > GS_INIT_ONCE) +#endif + CTimer::Update(); + CGame::frenchGame = false; + CGame::germanGame = false; +#ifdef MORE_LANGUAGES + CGame::russianGame = false; + CGame::japaneseGame = false; + switch (m_PrefsLanguage) { + case LANGUAGE_POLISH: + CFont::ReloadFonts(FONT_LANGSET_POLISH); + break; + case LANGUAGE_RUSSIAN: + CFont::ReloadFonts(FONT_LANGSET_RUSSIAN); + break; + case LANGUAGE_JAPANESE: + CFont::ReloadFonts(FONT_LANGSET_JAPANESE); + break; + default: + CFont::ReloadFonts(FONT_LANGSET_EFIGS); + break; + } +#endif + + switch (m_PrefsLanguage) { + case LANGUAGE_FRENCH: + CGame::frenchGame = true; + break; + case LANGUAGE_GERMAN: + CGame::germanGame = true; + break; +#ifdef MORE_LANGUAGES + case LANGUAGE_RUSSIAN: + CGame::russianGame = true; + break; + case LANGUAGE_JAPANESE: + CGame::japaneseGame = true; + break; +#endif + default: + break; + } + } +} + +void +CMenuManager::LoadAllTextures() +{ + if (m_bSpritesLoaded) + return; + + CentreMousePointer(); + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); + m_nCurrOption = 0; + +#ifdef FIX_BUGS + static bool firstTime = true; + if (firstTime) { + DMAudio.SetRadioInCar(m_PrefsRadioStation); + firstTime = false; + } else +#endif + m_PrefsRadioStation = DMAudio.GetRadioInCar(); + + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (m_PrefsRadioStation > USERTRACK) + m_PrefsRadioStation = CGeneral::GetRandomNumber() % (USERTRACK + 1); + } else if (m_PrefsRadioStation > CHATTERBOX) + m_PrefsRadioStation = CGeneral::GetRandomNumber() % (CHATTERBOX + 1); + + CFileMgr::SetDir(""); + //CFileMgr::SetDir(""); + CTimer::Stop(); + CStreaming::MakeSpaceFor(350 * CDSTREAM_SECTOR_SIZE); // twice of it in mobile + CStreaming::ImGonnaUseStreamingMemory(); + CGame::TidyUpMemory(false, true); + CTxdStore::PushCurrentTxd(); + int frontendTxdSlot = CTxdStore::FindTxdSlot("frontend"); + + if(frontendTxdSlot == -1) + frontendTxdSlot = CTxdStore::AddTxdSlot("frontend"); + + printf("LOAD frontend\n"); + CTxdStore::LoadTxd(frontendTxdSlot, "MODELS/FRONTEND.TXD"); + CTxdStore::AddRef(frontendTxdSlot); + CTxdStore::SetCurrentTxd(frontendTxdSlot); +#if GTA_VERSION < GTA3_PC_11 + CStreaming::IHaveUsedStreamingMemory(); + CTimer::Update(); +#endif + + for (int i = 0; i < ARRAY_SIZE(FrontendFilenames); i++) { + m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); + m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } + +#ifdef GAMEPAD_MENU + LoadController(m_PrefsControllerType); +#endif + + int menuTxdSlot = CTxdStore::FindTxdSlot("menu"); + + if (menuTxdSlot == -1) + menuTxdSlot = CTxdStore::AddTxdSlot("menu"); + + printf("LOAD sprite\n"); + CTxdStore::LoadTxd(menuTxdSlot, "MODELS/MENU.TXD"); + CTxdStore::AddRef(menuTxdSlot); + CTxdStore::SetCurrentTxd(menuTxdSlot); + + for (int i = 0; i < ARRAY_SIZE(MenuFilenames); i++) { + m_aMenuSprites[i].SetTexture(MenuFilenames[i][0], MenuFilenames[i][1]); + m_aMenuSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } +#ifdef MENU_MAP + static bool menuOptionAdded = false; + for (int i = 0; i < ARRAY_SIZE(MapFilenames); i++) { + RwTexture *firstTile; + if (!menuOptionAdded && (firstTile = RwTextureRead(MapFilenames[i][0], MapFilenames[i][1]))) { + RwTextureDestroy(firstTile); + FrontendOptionSetCursor(MENUPAGE_PAUSE_MENU, 2, false); + FrontendOptionAddBuiltinAction("FEG_MAP", MENUACTION_CHANGEMENU, MENUPAGE_MAP, SAVESLOT_NONE); + menuOptionAdded = true; + } + m_aMapSprites[i].SetTexture(MapFilenames[i][0], MapFilenames[i][1]); + m_aMapSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } + fMapSize = SCREEN_HEIGHT * 2.0f; + fMapCenterX = 0.0f; + fMapCenterY = 0.0f; +#endif +#if GTA_VERSION >= GTA3_PC_11 + CStreaming::IHaveUsedStreamingMemory(); + CTimer::Update(); +#endif + m_bSpritesLoaded = true; + CTxdStore::PopCurrentTxd(); +} + +#ifdef GAMEPAD_MENU +const char* controllerTypesPaths[] = { + nil, + "MODELS/FRONTEND_DS3.TXD", + "MODELS/FRONTEND_DS4.TXD", + "MODELS/FRONTEND_X360.TXD", + "MODELS/FRONTEND_XONE.TXD", + "MODELS/FRONTEND_NSW.TXD", +}; + +void +CMenuManager::LoadController(int8 type) +{ + switch (type) + { + case CONTROLLER_DUALSHOCK2: + case CONTROLLER_DUALSHOCK3: + case CONTROLLER_DUALSHOCK4: + CFont::LoadButtons("MODELS/PS3BTNS.TXD"); + break; + case CONTROLLER_NINTENDO_SWITCH: + CFont::LoadButtons("MODELS/NSWBTNS.TXD"); + break; + default: + CFont::LoadButtons("MODELS/X360BTNS.TXD"); + break; + } + + // Unload current textures + for (int i = FE_CONTROLLER; i <= FE_ARROWS4; i++) + m_aFrontEndSprites[i].Delete(); + + // Unload txd + int frontend_controller = CTxdStore::FindTxdSlot("frontend_controller"); + if (frontend_controller != -1) + CTxdStore::RemoveTxd(frontend_controller); + + // Find the new txd to load + bool bTxdMissing = true; + if (controllerTypesPaths[type]) + if (int file = CFileMgr::OpenFile(controllerTypesPaths[type])) { + CFileMgr::CloseFile(file); + bTxdMissing = false; + } + + int txdSlot = -1; + + if (bTxdMissing) + // Not found, fall back to original textures + txdSlot = CTxdStore::FindTxdSlot("frontend"); + else { + // Found, load txd + txdSlot = frontend_controller; + if (txdSlot == -1) + txdSlot = CTxdStore::AddTxdSlot("frontend_controller"); + CTxdStore::LoadTxd(txdSlot, controllerTypesPaths[type]); + CTxdStore::AddRef(txdSlot); + } + + assert(txdSlot != -1); + // Load new textures + CTxdStore::SetCurrentTxd(txdSlot); + for (int i = FE_CONTROLLER; i <= FE_ARROWS4; i++) { + m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); + m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } +} +#endif // GAMEPAD_MENU + +void +CMenuManager::LoadSettings() +{ + CFileMgr::SetDirMyDocuments(); + int fileHandle = CFileMgr::OpenFile("gta3.set", "r"); + + int32 prevLang = m_PrefsLanguage; +#if GTA_VERSION >= GTA3_PC_11 + CMBlur::BlurOn = (_dwOperatingSystemVersion != OS_WIN98); +#else + CMBlur::BlurOn = true; +#endif + MousePointerStateHelper.bInvertVertically = true; + + // 50 is silly + char Ver[50]; + + if (fileHandle) { + CFileMgr::Read(fileHandle, Ver, 29); + + if (strncmp(Ver, TopLineEmptyFile, sizeof(TopLineEmptyFile) - 1)) { + CFileMgr::Seek(fileHandle, 0, 0); + ControlsManager.LoadSettings(fileHandle); +#ifdef IMPROVED_VIDEOMODE + CFileMgr::Read(fileHandle, (char*)&m_nPrefsWidth, sizeof(m_nPrefsWidth)); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsHeight, sizeof(m_nPrefsHeight)); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsDepth, sizeof(m_nPrefsDepth)); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsWindowed, sizeof(m_nPrefsWindowed)); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsSubsystem, sizeof(m_nPrefsSubsystem)); + if(m_nPrefsWindowed != 0 && m_nPrefsWindowed != 1){ + // garbage data from vanilla settings file + // let skeleton find something + m_nPrefsWidth = 0; + m_nPrefsHeight = 0; + m_nPrefsDepth = 0; + m_nPrefsWindowed = 0; + m_nPrefsSubsystem = 0; + } + m_nSelectedScreenMode = m_nPrefsWindowed; +#else + CFileMgr::Read(fileHandle, gString, 20); +#endif + CFileMgr::Read(fileHandle, gString, 20); + CFileMgr::Read(fileHandle, gString, 4); + CFileMgr::Read(fileHandle, gString, 4); + CFileMgr::Read(fileHandle, gString, 1); + CFileMgr::Read(fileHandle, gString, 1); + CFileMgr::Read(fileHandle, gString, 1); + CFileMgr::Read(fileHandle, (char*)&TheCamera.m_bHeadBob, 1); + CFileMgr::Read(fileHandle, (char*)&TheCamera.m_fMouseAccelHorzntl, 4); + CFileMgr::Read(fileHandle, (char*)&TheCamera.m_fMouseAccelVertical, 4); + CFileMgr::Read(fileHandle, (char*)&MousePointerStateHelper.bInvertVertically, 1); + CFileMgr::Read(fileHandle, (char*)&CVehicle::m_bDisableMouseSteering, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsSfxVolume, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsMusicVolume, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsRadioStation, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsSpeakers, 1); + CFileMgr::Read(fileHandle, (char*)&m_nPrefsAudio3DProviderIndex, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsDMA, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsBrightness, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsLOD, 4); + CFileMgr::Read(fileHandle, (char*)&m_PrefsShowSubtitles, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsUseWideScreen, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsVsyncDisp, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsFrameLimiter, 1); + CFileMgr::Read(fileHandle, (char*)&m_nDisplayVideoMode, 1); + CFileMgr::Read(fileHandle, (char*)&CMBlur::BlurOn, 1); + CFileMgr::Read(fileHandle, m_PrefsSkinFile, 256); + CFileMgr::Read(fileHandle, (char*)&m_ControlMethod, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsLanguage, 1); + } + } + + CFileMgr::CloseFile(fileHandle); + CFileMgr::SetDir(""); + +#ifdef LOAD_INI_SETTINGS + if (LoadINISettings()) { + LoadINIControllerSettings(); + } +#endif + + m_PrefsVsync = m_PrefsVsyncDisp; + CRenderer::ms_lodDistScale = m_PrefsLOD; + + if (m_nPrefsAudio3DProviderIndex == -1) + m_nPrefsAudio3DProviderIndex = -2; + + if (m_PrefsLanguage == prevLang) + m_bLanguageLoaded = false; + else { + m_bLanguageLoaded = true; + // Already called in InitialiseChangedLanguageSettings + /* + TheText.Unload(); + TheText.Load(); + */ + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + + OutputDebugString("The previously saved language is now in use"); + } + + WIN32_FIND_DATA FindFileData; + char skinfile[256+16]; // Stack analysis shows 16 bits gap, but I don't trust it. It may very well be MAX_PATH(260). + bool SkinFound = false; + HANDLE handle = FindFirstFile("skins\\*.bmp", &FindFileData); + for (int i = 1; handle != INVALID_HANDLE_VALUE && i; i = FindNextFile(handle, &FindFileData)) { + strcpy(skinfile, m_PrefsSkinFile); + strcat(skinfile, ".bmp"); + if (strcmp(FindFileData.cFileName, skinfile) == 0) + SkinFound = true; + } + FindClose(handle); + + if (!SkinFound) { + OutputDebugString("Default skin set as no other skins are available OR saved skin not found!"); + strcpy(m_PrefsSkinFile, DEFAULT_SKIN_NAME); + strcpy(m_aSkinName, DEFAULT_SKIN_NAME); + } +} + +void +CMenuManager::SaveSettings() +{ +#ifndef LOAD_INI_SETTINGS + static char RubbishString[48] = "stuffmorestuffevenmorestuff etc"; + + CFileMgr::SetDirMyDocuments(); + + int fileHandle = CFileMgr::OpenFile("gta3.set", "w+"); + if (fileHandle) { + ControlsManager.SaveSettings(fileHandle); +#ifdef IMPROVED_VIDEOMODE + CFileMgr::Write(fileHandle, (char*)&m_nPrefsWidth, sizeof(m_nPrefsWidth)); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsHeight, sizeof(m_nPrefsHeight)); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsDepth, sizeof(m_nPrefsDepth)); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsWindowed, sizeof(m_nPrefsWindowed)); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsSubsystem, sizeof(m_nPrefsSubsystem)); +#else + CFileMgr::Write(fileHandle, RubbishString, 20); +#endif + CFileMgr::Write(fileHandle, RubbishString, 20); + CFileMgr::Write(fileHandle, RubbishString, 4); + CFileMgr::Write(fileHandle, RubbishString, 4); + CFileMgr::Write(fileHandle, RubbishString, 1); + CFileMgr::Write(fileHandle, RubbishString, 1); + CFileMgr::Write(fileHandle, RubbishString, 1); + CFileMgr::Write(fileHandle, (char*)&TheCamera.m_bHeadBob, 1); + CFileMgr::Write(fileHandle, (char*)&TheCamera.m_fMouseAccelHorzntl, 4); + CFileMgr::Write(fileHandle, (char*)&TheCamera.m_fMouseAccelVertical, 4); + CFileMgr::Write(fileHandle, (char*)&MousePointerStateHelper.bInvertVertically, 1); + CFileMgr::Write(fileHandle, (char*)&CVehicle::m_bDisableMouseSteering, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsSfxVolume, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsMusicVolume, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsRadioStation, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsSpeakers, 1); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsAudio3DProviderIndex, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsDMA, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsBrightness, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsLOD, sizeof(m_PrefsLOD)); + CFileMgr::Write(fileHandle, (char*)&m_PrefsShowSubtitles, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsUseWideScreen, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsVsyncDisp, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsFrameLimiter, 1); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsVideoMode, 1); + CFileMgr::Write(fileHandle, (char*)&CMBlur::BlurOn, 1); + CFileMgr::Write(fileHandle, m_PrefsSkinFile, 256); + CFileMgr::Write(fileHandle, (char*)&m_ControlMethod, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsLanguage, 1); + } + + CFileMgr::CloseFile(fileHandle); + CFileMgr::SetDir(""); + +#else + SaveINISettings(); +#endif +} + +bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); + +void +CMenuManager::MessageScreen(const char *text) +{ + CSprite2d *splash = LoadSplash(nil); + if (!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) + return; + + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetJustifyOn(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCREEN_WIDTH - StretchX(170.0f)); // unused + CFont::SetRightJustifyWrap(SCREEN_WIDTH - StretchX(170.0f)); // unused + CSprite2d::DrawRect(CRect(StretchX(120.0f), StretchY(150.0f), SCREEN_WIDTH - StretchX(120.0f), SCREEN_HEIGHT - StretchY(220.0f)), CRGBA(50, 50, 50, 210)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetCentreSize(SCREEN_SCALE_X(380.0f)); + CFont::SetCentreOn(); + CFont::SetColor(CRGBA(SELECTEDMENUOPTION_COLOR.r, SELECTEDMENUOPTION_COLOR.g, SELECTEDMENUOPTION_COLOR.b, 255)); + CFont::SetScale(SCREEN_SCALE_X(SMALLTEXT_X_SCALE), SCREEN_SCALE_Y(SMALLTEXT_Y_SCALE)); + CFont::PrintString(StretchX(320.0f), StretchY(170.0f), TheText.Get(text)); + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +CMenuManager::PickNewPlayerColour() +{ + m_PrefsPlayerRed = 0; + m_PrefsPlayerGreen = 0; + m_PrefsPlayerBlue = 0; + while (true) { + int sum = m_PrefsPlayerRed + m_PrefsPlayerGreen + m_PrefsPlayerBlue; + if (sum >= 100 && sum <= 650) + break; + m_PrefsPlayerRed = CGeneral::GetRandomNumber(); + m_PrefsPlayerGreen = CGeneral::GetRandomNumber(); + m_PrefsPlayerBlue = CGeneral::GetRandomNumber(); + } +} + +void +CMenuManager::PrintBriefs() +{ + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetRightJustifyOff(); + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X * 0.7), MENU_Y(MENU_TEXT_SIZE_Y * 0.9)); // second mulipliers are double, idk why + + float nextY = BRIEFS_TOP_MARGIN; + CRGBA newColor; + for (int i = 4; i >= 0; i--) { + tPreviousBrief &brief = CMessages::PreviousBriefs[i]; + if (brief.m_pText) { + CMessages::InsertNumberInString(brief.m_pText, + brief.m_nNumber[0], brief.m_nNumber[1], + brief.m_nNumber[2], brief.m_nNumber[3], + brief.m_nNumber[4], brief.m_nNumber[5], gUString); + CMessages::InsertStringInString(gUString, brief.m_pString); + CMessages::InsertPlayerControlKeysInString(gUString); + newColor = TEXT_COLOR; + FilterOutColorMarkersFromString(gUString, newColor); + if (newColor != TEXT_COLOR) { + newColor.r /= 2; + newColor.g /= 2; + newColor.b /= 2; + } + +#ifdef PS2_LIKE_MENU + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetDropShadowPosition(1); +#endif + +#if defined(FIX_BUGS) || defined(PS2_LIKE_MENU) + newColor.a = FadeIn(255); + CFont::SetColor(newColor); +#endif + CFont::PrintString(MENU_X_LEFT_ALIGNED(BRIEFS_LINE_X), nextY, gUString); + nextY += MENU_Y(BRIEFS_LINE_HEIGHT); + } + } + +#ifdef PS2_LIKE_MENU + CFont::SetDropShadowPosition(0); +#endif +} + +// Not sure about name. Not to be confused with CPad::PrintErrorMessage +void +CMenuManager::PrintErrorMessage() +{ + if (!CPad::bDisplayNoControllerMessage && !CPad::bObsoleteControllerMessage) + return; + + CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(140.0f), SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(120.0f)), CRGBA(64, 16, 16, 224)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(MENU_X_MARGIN)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_X(50.0f), SCREEN_SCALE_Y(180.0f), TheText.Get(CPad::bDisplayNoControllerMessage ? "NOCONT" : "WRCONT")); +#else + CFont::PrintString(SCREEN_SCALE_X(50.0f), SCREEN_SCALE_Y(40.0f), TheText.Get(CPad::bDisplayNoControllerMessage ? "NOCONT" : "WRCONT")); +#endif + CFont::DrawFonts(); +} + +void +CMenuManager::PrintStats() +{ + int rowNum = ConstructStatLine(99999); +#if GTA_VERSION >= GTA3_PC_11 + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); +#endif + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X * 0.7), MENU_Y(MENU_TEXT_SIZE_Y * 0.9)); // second mulipliers are double, idk why + float nextYChange, y, alphaMult; + + // Scroll stats with mouse +#ifdef SCROLLABLE_STATS_PAGE + static float scrollY = 0; + static uint32 lastChange = m_nScreenChangeDelayTimer; + if (CPad::GetPad(0)->GetLeftMouse()) { + scrollY += (m_nMouseOldPosY - m_nMousePosY); + lastChange = CTimer::GetTimeInMillisecondsPauseMode(); + } else { + scrollY += MENU_Y(STATS_SLIDE_Y_PER_SECOND) / 1000.0f * (CTimer::GetTimeInMillisecondsPauseMode() - lastChange); + lastChange = CTimer::GetTimeInMillisecondsPauseMode(); + } +#else + // MENU_Y(30.0f) per second + float scrollY = MENU_Y(STATS_SLIDE_Y_PER_SECOND) * (CTimer::GetTimeInMillisecondsPauseMode() - m_nScreenChangeDelayTimer) / 1000.0f; +#endif + + for (int row = 0; row < rowNum; ++row) { + // Put just got hidden text at the top back to the bottom, in circular fashion + for (y = MENU_Y(STATS_ROW_HEIGHT - 1) * row + SCREEN_HEIGHT - scrollY; MENU_Y(STATS_PUT_BACK_TO_BOTTOM_Y) > y; y += nextYChange) { + nextYChange = (MENU_Y(STATS_ROW_HEIGHT) + rowNum) * MENU_Y(STATS_ROW_HEIGHT - 1); + } + + // If it's still on screen + if (y > 0.0f && SCREEN_HEIGHT > y) { + ConstructStatLine(row); + + // But about to dim from top + if (y - MENU_Y(STATS_BOTTOM_MARGIN) < MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH)) { + if ((y - MENU_Y(STATS_BOTTOM_MARGIN)) / MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH) < 0.0f) + alphaMult = 0.0f; + else + alphaMult = (y - MENU_Y(STATS_BOTTOM_MARGIN)) / MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH); + + // About to dim from bottom + } else if (y > SCREEN_SCALE_FROM_BOTTOM(STATS_TOP_DIMMING_AREA_LENGTH) - MENU_Y(STATS_BOTTOM_DIMMING_AREA_LENGTH)) { + if ((SCREEN_SCALE_FROM_BOTTOM(STATS_BOTTOM_DIMMING_AREA_LENGTH) - y) / MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH) < 0.0f) + alphaMult = 0.0f; + else + alphaMult = (SCREEN_SCALE_FROM_BOTTOM(STATS_BOTTOM_DIMMING_AREA_LENGTH) - y) / MENU_Y(STATS_TOP_DIMMING_AREA_LENGTH); + } else + alphaMult = 1.0f; + + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255.0f * alphaMult))); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(STATS_ROW_X_MARGIN), y - MENU_Y(STATS_BOTTOM_MARGIN - STATS_TOP_MARGIN), gUString); + CFont::SetRightJustifyOn(); + CFont::PrintString(MENU_X_RIGHT_ALIGNED(STATS_ROW_X_MARGIN), y - MENU_Y(STATS_BOTTOM_MARGIN - STATS_TOP_MARGIN), gUString2); + } + } + // Game doesn't do that, but it's better + float nextX = MENU_X_LEFT_ALIGNED(STATS_RATING_X); + + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + CFont::SetRightJustifyOff(); + CFont::PrintString(nextX, MENU_Y(STATS_RATING_Y), TheText.Get("CRIMRA")); +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + nextX += MENU_X(10.0f) + CFont::GetStringWidth_Jap(TheText.Get("CRIMRA")); + else +#endif + nextX += MENU_X(10.0f) + CFont::GetStringWidth(TheText.Get("CRIMRA"), true); + UnicodeStrcpy(gUString, CStats::FindCriminalRatingString()); + CFont::PrintString(nextX, MENU_Y(STATS_RATING_Y), gUString); +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + nextX += MENU_X(6.0f) + CFont::GetStringWidth_Jap(gUString); + else +#endif + nextX += MENU_X(6.0f) + CFont::GetStringWidth(gUString, true); + sprintf(gString, "%d", CStats::FindCriminalRatingNumber()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(nextX, MENU_Y(STATS_RATING_Y), gUString); + + // ::Draw already does that. + /* + SET_FONT_FOR_MENU_HEADER + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), TheText.Get(aScreens[m_nCurrScreen].m_ScreenName)); + */ + CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X), MENU_Y(MENU_TEXT_SIZE_Y)); +} + +void +CMenuManager::Process(void) +{ + m_bMenuStateChanged = false; + + if (!m_bSaveMenuActive && TheCamera.GetScreenFadeStatus() != FADE_0) + return; + + m_bWantToRestart = false; + InitialiseChangedLanguageSettings(); + + // Just a hack by R* to not make game continuously resume/pause. But we it seems we can live with it. + if (CPad::GetPad(0)->GetEscapeJustDown()) + RequestFrontEndStartUp(); + + SwitchMenuOnAndOff(); + + // Be able to re-open menu correctly. + if (m_bMenuActive) { + + // Load frontend textures. + LoadAllTextures(); + + // Set save/delete game pages. + if (m_nCurrScreen == MENUPAGE_DELETING) { + bool SlotPopulated = false; + + if (PcSaveHelper.DeleteSlot(m_nCurrSaveSlot)) { + PcSaveHelper.PopulateSlotInfo(); + SlotPopulated = true; + } + + if (SlotPopulated) + ChangeScreen(MENUPAGE_DELETE_SUCCESS, 0, true, false); + else + SaveLoadFileError_SetUpErrorScreen(); + } + if (m_nCurrScreen == MENUPAGE_SAVING_IN_PROGRESS) { + int8 SaveSlot = PcSaveHelper.SaveSlot(m_nCurrSaveSlot); + PcSaveHelper.PopulateSlotInfo(); + if (SaveSlot) + ChangeScreen(MENUPAGE_SAVE_SUCCESSFUL, 0, true, false); + else + SaveLoadFileError_SetUpErrorScreen(); + } + if (m_nCurrScreen == MENUPAGE_LOADING_IN_PROGRESS) { +#ifdef MISSION_REPLAY + if (doingMissionRetry) { + RetryMission(MISSION_RETRY_TYPE_BEGIN_RESTARTING); + m_nCurrSaveSlot = SLOT_COUNT; + doingMissionRetry = false; + } +#endif + if (CheckSlotDataValid(m_nCurrSaveSlot)) { +#ifdef USE_DEBUG_SCRIPT_LOADER + CTheScripts::ScriptToLoad = 0; +#endif +#ifdef PC_PLAYER_CONTROLS + TheCamera.m_bUseMouse3rdPerson = m_ControlMethod == CONTROL_STANDARD; +#endif + if (m_PrefsVsyncDisp != m_PrefsVsync) + m_PrefsVsync = m_PrefsVsyncDisp; + DMAudio.Service(); + m_bWantToRestart = true; + RequestFrontEndShutDown(); + m_bWantToLoad = true; + b_FoundRecentSavedGameWantToLoad = true; + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + } else + SaveLoadFileError_SetUpErrorScreen(); + } + + ProcessButtonPresses(); + + // Set binding keys. + if (pEditString && CPad::EditString(pEditString, 0) == nil) { + if (*pEditString == 0) + strcpy(pEditString, "NoName"); + pEditString = nil; + SaveSettings(); + } + + if (m_bWaitingForNewKeyBind) { + if (m_bStartWaitingForKeyBind) + m_bStartWaitingForKeyBind = false; + else { + pControlEdit = CPad::EditCodesForControls(pControlEdit, 1); + JoyButtonJustClicked = false; + MouseButtonJustClicked = false; + + if (CPad::GetPad(0)->GetLeftMouseJustDown()) + MouseButtonJustClicked = rsMOUSELEFTBUTTON; + else if (CPad::GetPad(0)->GetRightMouseJustUp()) + MouseButtonJustClicked = rsMOUSERIGHTBUTTON; + else if (CPad::GetPad(0)->GetMiddleMouseJustUp()) + MouseButtonJustClicked = rsMOUSMIDDLEBUTTON; + else if (CPad::GetPad(0)->GetMouseWheelUpJustUp()) + MouseButtonJustClicked = rsMOUSEWHEELUPBUTTON; + else if (CPad::GetPad(0)->GetMouseWheelDownJustUp()) + MouseButtonJustClicked = rsMOUSEWHEELDOWNBUTTON; + else if (CPad::GetPad(0)->GetMouseX1JustUp()) + MouseButtonJustClicked = rsMOUSEX1BUTTON; + else if (CPad::GetPad(0)->GetMouseX2JustUp()) + MouseButtonJustClicked = rsMOUSEX2BUTTON; + + JoyButtonJustClicked = ControlsManager.GetJoyButtonJustDown(); + + int32 TypeOfControl = KEYBOARD; + if (JoyButtonJustClicked) + TypeOfControl = JOYSTICK; + if (MouseButtonJustClicked) + TypeOfControl = MOUSE; + if (*pControlEdit != rsNULL) + TypeOfControl = KEYBOARD; + + if (!m_bKeyIsOK) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + pControlEdit = nil; + m_bWaitingForNewKeyBind = false; + m_KeyPressedCode = -1; + m_bStartWaitingForKeyBind = false; + } else if (!m_bKeyChangeNotProcessed) { + if (*pControlEdit != rsNULL || MouseButtonJustClicked || JoyButtonJustClicked) + CheckCodesForControls(TypeOfControl); + + field_535 = true; + } else { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + for (int i = 0; i < 4; i++) + ControlsManager.ClearSettingsAssociatedWithAction((e_ControllerAction)m_CurrCntrlAction, (eControllerType)i); + m_bKeyIsOK = false; + m_bKeyChangeNotProcessed = false; + pControlEdit = nil; + m_bWaitingForNewKeyBind = false; + m_KeyPressedCode = -1; + m_bStartWaitingForKeyBind = false; + } + } + } + + if ((m_nCurrScreen == MENUPAGE_NO_MEMORY_CARD || m_nCurrScreen == MENUPAGE_PS2_LOAD_FAILED) && CTimer::GetTimeInMillisecondsPauseMode() > field_558) { + m_nCurrScreen = m_nPrevScreen; + m_nCurrOption = 0; + } + + // Reset pad shaking. + if (TimeToStopPadShaking && TimeToStopPadShaking < CTimer::GetTimeInMillisecondsPauseMode()) { + CPad::StopPadsShaking(); + TimeToStopPadShaking = 0; + } + + } else { + UnloadTextures(); + m_bRenderGameInMenu = false; + // byte_5F33E4 = 1; // unused + ChangeScreen(MENUPAGE_NONE, 0, false, false); + pEditString = nil; + m_bWaitingForNewKeyBind = false; + } + + if (!m_bWantToRestart) { + if (m_bGameNotLoaded) + DMAudio.Service(); + } +} + +void +CMenuManager::ProcessButtonPresses(void) +{ + if (pEditString || pControlEdit) + return; + + bool goBack = false; + bool optionSelected = false; + bool goUp = false; + bool goDown = false; +#ifdef TIDY_UP_PBP + bool assumeIncrease = false; +#endif + +#ifdef USE_DEBUG_SCRIPT_LOADER + if (m_nCurrScreen == MENUPAGE_START_MENU || m_nCurrScreen == MENUPAGE_NEW_GAME || m_nCurrScreen == MENUPAGE_NEW_GAME_RELOAD) { + if (CPad::GetPad(0)->GetChar('R')) { + CTheScripts::ScriptToLoad = 1; + DoSettingsBeforeStartingAGame(); + return; + } + if (CPad::GetPad(0)->GetChar('D')) { + CTheScripts::ScriptToLoad = 2; + DoSettingsBeforeStartingAGame(); + return; + } + } +#endif + + if (!m_bShowMouse && (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY)) { + m_bShowMouse = true; + } + + m_nMouseOldPosX = m_nMousePosX; + m_nMouseOldPosY = m_nMousePosY; + m_nMousePosX = m_nMouseTempPosX; + m_nMousePosY = m_nMouseTempPosY; + + if (m_nMousePosX < 0) m_nMousePosX = 0; + if (m_nMousePosX > SCREEN_WIDTH) m_nMousePosX = SCREEN_WIDTH; + if (m_nMousePosY < 0) m_nMousePosY = 0; + if (m_nMousePosY > SCREEN_HEIGHT) m_nMousePosY = SCREEN_HEIGHT; + + if (hasNativeList(m_nCurrScreen)) { + // Not split to seperate function in III as in VC, but we need it for scrollable pages :) + ProcessList(goBack, optionSelected); + + } else if (isPlainTextScreen(m_nCurrScreen)) { +#ifndef TIDY_UP_PBP + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown() || CPad::GetPad(0)->GetLeftMouseJustDown()) { + optionSelected = true; + } + if (CPad::GetPad(0)->GetEscapeJustDown() || CPad::GetPad(0)->GetBackJustUp()) { + if (m_nCurrScreen != MENUPAGE_START_MENU) { + goBack = true; + } + } +#endif + } else { + if (CPad::GetPad(0)->GetDownJustDown() || CPad::GetPad(0)->GetAnaloguePadDown() || CPad::GetPad(0)->GetDPadDownJustDown()) { + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + goDown = true; + } else if (CPad::GetPad(0)->GetUpJustDown() || CPad::GetPad(0)->GetAnaloguePadUp() || CPad::GetPad(0)->GetDPadUpJustDown()) { + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + goUp = true; + } + +#ifndef TIDY_UP_PBP + if ((m_nCurrOption == 0) && (m_nCurrScreen == MENUPAGE_PAUSE_MENU)) { + if (CPad::GetPad(0)->GetEnterJustUp() || CPad::GetPad(0)->GetCrossJustUp()) { + m_bShowMouse = false; + optionSelected = true; + } + } else { + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + m_bShowMouse = false; + optionSelected = true; + } + } +#endif + + if (CPad::GetPad(0)->GetLeftMouseJustUp()) { +#ifndef TIDY_UP_PBP + if (((m_nCurrOption == 0) && (m_nCurrScreen == MENUPAGE_PAUSE_MENU)) && +#else + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_RESUME && +#endif + (m_nHoverOption == HOVEROPTION_RANDOM_ITEM)) { + m_nCurrOption = m_nOptionMouseHovering; + optionSelected = true; + } + } else if (CPad::GetPad(0)->GetLeftMouseJustDown()) { +#ifdef TIDY_UP_PBP + if (m_nHoverOption >= HOVEROPTION_RADIO_0 && m_nHoverOption <= HOVEROPTION_RADIO_9) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = m_nHoverOption - HOVEROPTION_RADIO_0; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + } else if (m_nHoverOption == HOVEROPTION_RANDOM_ITEM + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_RESUME) { + m_nCurrOption = m_nOptionMouseHovering; + optionSelected = true; + } +#else + switch (m_nHoverOption) { + case HOVEROPTION_RADIO_0: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = HEAD_RADIO; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_1: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = DOUBLE_CLEF; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_2: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = JAH_RADIO; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_3: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = RISE_FM; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_4: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = LIPS_106; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_5: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = GAME_FM; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_6: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = MSX_FM; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_7: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = FLASHBACK; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_8: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = CHATTERBOX; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RADIO_9: + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + m_PrefsRadioStation = USERTRACK; + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; + case HOVEROPTION_RANDOM_ITEM: + if (((m_nCurrOption != 0) || (m_nCurrScreen != MENUPAGE_PAUSE_MENU)) { + m_nCurrOption = m_nOptionMouseHovering; + optionSelected = true; + } + break; + } +#endif + } + + if (CPad::GetPad(0)->GetLeftMouse()) { +#ifndef TIDY_UP_PBP + switch (m_nHoverOption) { + case HOVEROPTION_INCREASE_BRIGHTNESS: + m_PrefsBrightness = m_PrefsBrightness + (512 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsBrightness < 0) { + m_PrefsBrightness = 0; + } + if (510 < m_PrefsBrightness) { + m_PrefsBrightness = 511; + } + SaveSettings(); + break; + case HOVEROPTION_DECREASE_BRIGHTNESS: + m_PrefsBrightness = m_PrefsBrightness - (512 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsBrightness < 0) { + m_PrefsBrightness = 0; + } + if (510 < m_PrefsBrightness) { + m_PrefsBrightness = 511; + } + SaveSettings(); + break; + case HOVEROPTION_INCREASE_DRAWDIST: + m_PrefsLOD = m_PrefsLOD + (1.0f / MENUSLIDER_LOGICAL_BARS); + m_PrefsLOD = min(1.8f, m_PrefsLOD); + CRenderer::ms_lodDistScale = m_PrefsLOD; + SaveSettings(); + break; + case HOVEROPTION_DECREASE_DRAWDIST: + m_PrefsLOD = m_PrefsLOD - (1.0f / MENUSLIDER_LOGICAL_BARS); + m_PrefsLOD = max(0.8f, m_PrefsLOD); + CRenderer::ms_lodDistScale = m_PrefsLOD; + SaveSettings(); + break; + case HOVEROPTION_INCREASE_MUSICVOLUME: + m_PrefsMusicVolume = m_PrefsMusicVolume + (128 / MENUSLIDER_LOGICAL_BARS); + m_PrefsMusicVolume = Clamp(m_PrefsMusicVolume, 0, 127); + DMAudio.SetMusicMasterVolume(uchar)(m_PrefsMusicVolume); + SaveSettings(); + break; + case HOVEROPTION_DECREASE_MUSICVOLUME: + m_PrefsMusicVolume = m_PrefsMusicVolume - (128 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsMusicVolume < 0) { + m_PrefsMusicVolume = 0; + } + if (126 < m_PrefsMusicVolume) { + m_PrefsMusicVolume = 127; + } + DMAudio.SetMusicMasterVolume(uchar)(m_PrefsMusicVolume); + SaveSettings(); + break; + case HOVEROPTION_INCREASE_SFXVOLUME: + m_PrefsSFXVolume = m_PrefsSFXVolume + (128 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsSFXVolume < 0) { + m_PrefsSFXVolume = 0; + } + if (126 < m_PrefsSFXVolume) { + m_PrefsSFXVolume = 127; + } + DMAudio.SetEffectsMasterVolume(uchar)(m_PrefsSFXVolume); + SaveSettings(); + break; + case HOVEROPTION_DECREASE_SFXVOLUME: + m_PrefsSFXVolume = m_PrefsSFXVolume - (128 / MENUSLIDER_LOGICAL_BARS); + if (m_PrefsSFXVolume < 0) { + m_PrefsSFXVolume = 0; + } + if (126 < m_PrefsSFXVolume) { + m_PrefsSFXVolume = 127; + } + DMAudio.SetEffectsMasterVolume(uchar)(m_PrefsSFXVolume); + SaveSettings(); + break; + case HOVEROPTION_INCREASE_MOUSESENS: + TheCamera.m_fMouseAccelHorzntl += 1.0f/200.0f/15.0f; // probably because diving it to 15 instead of 16(MENUSLIDER_LOGICAL_BARS) had more accurate steps + TheCamera.m_fMouseAccelHorzntl = Clamp(TheCamera.m_fMouseAccelHorzntl, 1.0f / 3200, 1.0f / 200); +#ifdef FIX_BUGS + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl + 0.0005f; +#else + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl; +#endif + SaveSettings(); + break; + case HOVEROPTION_DECREASE_MOUSESENS: + TheCamera.m_fMouseAccelHorzntl -= 1.0f/200.0f/15.0f; // probably because diving it to 15 instead of 16(MENUSLIDER_LOGICAL_BARS) had more accurate steps + TheCamera.m_fMouseAccelHorzntl = Clamp(TheCamera.m_fMouseAccelHorzntl, 1.0f / 3200, 1.0f / 200); +#ifdef FIX_BUGS + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl + 0.0005f; +#else + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl; +#endif + SaveSettings(); + break; + } +#else + switch (m_nHoverOption) { + case HOVEROPTION_INCREASE_BRIGHTNESS: + case HOVEROPTION_INCREASE_DRAWDIST: + case HOVEROPTION_INCREASE_MUSICVOLUME: + case HOVEROPTION_INCREASE_SFXVOLUME: + case HOVEROPTION_INCREASE_MOUSESENS: +#ifdef CUSTOM_FRONTEND_OPTIONS + case HOVEROPTION_INCREASE_CFO_SLIDER: +#endif + CheckSliderMovement(1); + break; + case HOVEROPTION_DECREASE_BRIGHTNESS: + case HOVEROPTION_DECREASE_DRAWDIST: + case HOVEROPTION_DECREASE_MUSICVOLUME: + case HOVEROPTION_DECREASE_SFXVOLUME: + case HOVEROPTION_DECREASE_MOUSESENS: +#ifdef CUSTOM_FRONTEND_OPTIONS + case HOVEROPTION_DECREASE_CFO_SLIDER: +#endif + CheckSliderMovement(-1); + break; + } +#endif + } + +#ifdef SCROLLABLE_PAGES + if (m_nTotalListRow > MAX_VISIBLE_OPTION) { + bool temp = false; + + m_nSelectedListRow = m_nCurrOption; + + // ignore detected back/select states, it's our screen's job + ProcessList(temp, temp); + + // and ignore our screen's goUp/Down, now it's ProcessList's job + goUp = false; + goDown = false; + m_nCurrOption = m_nSelectedListRow; + } + + // Prevent sound on scroll. Mouse wheel is now belongs to us! + if (!(m_nTotalListRow > MAX_VISIBLE_OPTION && (CPad::GetPad(0)->GetMouseWheelUpJustDown() || CPad::GetPad(0)->GetMouseWheelDownJustDown()))) +#endif + + if (CPad::GetPad(0)->GetLeftMouseJustUp() || CPad::GetPad(0)->GetLeftJustUp() || CPad::GetPad(0)->GetRightJustUp() + || CPad::GetPad(0)->GetDPadLeftJustUp() || CPad::GetPad(0)->GetDPadRightJustUp() + || CPad::GetPad(0)->GetAnaloguePadLeftJustUp() || CPad::GetPad(0)->GetAnaloguePadRightJustUp() + || CPad::GetPad(0)->GetMouseWheelUpJustDown() || CPad::GetPad(0)->GetMouseWheelDownJustDown()) { + int option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + if (option == MENUACTION_BRIGHTNESS || option == MENUACTION_DRAWDIST +#ifdef CUSTOM_FRONTEND_OPTIONS + || option == MENUACTION_CFO_SLIDER +#endif + ) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + else if (option == MENUACTION_SFXVOLUME) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_AUDIO_TEST, 0); + else if (option == MENUACTION_MOUSESENS) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + + } + +#ifndef TIDY_UP_PBP + if (CPad::GetPad(0)->GetBackJustDown()) { + if (m_nCurrScreen != MENUPAGE_START_MENU && m_nCurrScreen != MENUPAGE_PAUSE_MENU) { + m_bShowMouse = false; + goBack = true; + } + } + + if (CPad::GetPad(0)->GetEscapeJustDown()) { + if (m_nCurrScreen != MENUPAGE_START_MENU) { + m_bShowMouse = false; + goBack = true; + } + } + + if (((goDown) || (goUp)) || (optionSelected)) { + goBack = false; + } +#endif + } + + // Centralized enter/back (except some conditions) +#ifdef TIDY_UP_PBP + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_RESUME) { + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown() || + (isPlainTextScreen(m_nCurrScreen) && CPad::GetPad(0)->GetLeftMouseJustDown())) { + + if (!isPlainTextScreen(m_nCurrScreen)) + m_bShowMouse = false; + + optionSelected = true; + } + } else { + if (CPad::GetPad(0)->GetEnterJustUp() || CPad::GetPad(0)->GetCrossJustUp()) { + m_bShowMouse = false; + optionSelected = true; + } + } + + if (!goDown && !goUp && !optionSelected) { + if (m_nCurrScreen != MENUPAGE_START_MENU) { + if (isPlainTextScreen(m_nCurrScreen)) { + if (CPad::GetPad(0)->GetEscapeJustDown() || CPad::GetPad(0)->GetBackJustUp()) { + goBack = true; + } + } else { + if (CPad::GetPad(0)->GetEscapeJustDown() || (m_nCurrScreen != MENUPAGE_PAUSE_MENU && CPad::GetPad(0)->GetBackJustDown())) { + m_bShowMouse = false; + goBack = true; + } + } + } + } +#endif + +#ifdef PS2_LIKE_MENU + if (CPad::GetPad(0)->GetLeftMouseJustDown() && hoveredBottomBarOption != -1) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + bottomBarActive = false; + curBottomBarOption = hoveredBottomBarOption; + ChangeScreen(bbNames[curBottomBarOption].screenId, 0, true, false); + if (bbNames[curBottomBarOption].screenId == MENUPAGE_SOUND_SETTINGS) + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + return; + } else if (bottomBarActive) { + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + bottomBarActive = false; + + if (bbNames[curBottomBarOption].screenId == MENUPAGE_SOUND_SETTINGS) + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + + return; + } else if (CPad::GetPad(0)->GetLeftJustDown() || CPad::GetPad(0)->GetAnaloguePadLeft() || CPad::GetPad(0)->GetDPadLeftJustDown() + || CPad::GetPad(0)->GetUpJustDown() || CPad::GetPad(0)->GetAnaloguePadUp() || CPad::GetPad(0)->GetDPadUpJustDown()) { + + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + curBottomBarOption = ((curBottomBarOption + bbTabCount) - 1) % bbTabCount; + ChangeScreen(bbNames[curBottomBarOption].screenId, 0, true, true); + return; + } else if (CPad::GetPad(0)->GetRightJustDown() || CPad::GetPad(0)->GetAnaloguePadRight() || CPad::GetPad(0)->GetDPadRightJustDown() + || CPad::GetPad(0)->GetDownJustDown() || CPad::GetPad(0)->GetAnaloguePadDown() || CPad::GetPad(0)->GetDPadDownJustDown()) { + + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + curBottomBarOption = ((curBottomBarOption + bbTabCount) + 1) % bbTabCount; + ChangeScreen(bbNames[curBottomBarOption].screenId, 0, true, true); + return; + } + optionSelected = false; + goDown = false; + goUp = false; + } +#endif + + int prevOption = m_nCurrOption; + if (goDown && (m_nCurrScreen != MENUPAGE_MULTIPLAYER_FIND_GAME)) { + m_nCurrOption++; + if (m_nCurrOption == NUM_MENUROWS || (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_NOTHING)) { + m_nCurrOption = 0; + } + } + if (goUp && (m_nCurrScreen != MENUPAGE_MULTIPLAYER_FIND_GAME)) { + if (m_nCurrOption == (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL)) { + while (m_nCurrOption != NUM_MENUROWS - 1 + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption + 1].m_Action != MENUACTION_NOTHING) { + m_nCurrOption++; + } + } else { + m_nCurrOption--; + } + } + + // Hide back button +#ifdef PS2_LIKE_MENU + if ((goUp || goDown) && m_nCurrScreen != MENUPAGE_MULTIPLAYER_FIND_GAME && strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB") == 0) + m_nCurrOption = goUp ? m_nCurrOption - 1 : (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL); +#endif + + if (optionSelected) { + int option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + if ((option == MENUACTION_CHANGEMENU) || (option == MENUACTION_POPULATESLOTS_CHANGEMENU)) { + if (strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB") != 0 && + strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FESZ_CA") != 0) { + + if (m_nCurrScreen == MENUPAGE_CHOOSE_DELETE_SLOT) { + if (Slots[aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot - 1] == SLOT_EMPTY) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } else { + // This is duplicate, back button already processed below +#ifndef TIDY_UP_PBP + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_EXIT, 0); + if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); + } +#endif + } + } else if (option == MENUACTION_CHECKSAVE) { + if (Slots[aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot - 1] == SLOT_EMPTY) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + } else { + if (m_nCurrScreen != MENUPAGE_NEW_GAME_RELOAD) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + } + } else if (option != MENUACTION_CHANGEMENU && option != MENUACTION_BRIGHTNESS && option != MENUACTION_DRAWDIST + && option != MENUACTION_MUSICVOLUME && option != MENUACTION_SFXVOLUME + && option != MENUACTION_CHECKSAVE && option != MENUACTION_UNK24 + && option != MENUACTION_MOUSESENS && option != MENUACTION_SCREENRES +#ifdef CUSTOM_FRONTEND_OPTIONS + && option != MENUACTION_CFO_SLIDER +#endif + ) + { + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + + if ((m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) || (m_nCurrScreen == MENUPAGE_SKIN_SELECT)) { + switch (m_nCurrExLayer) { + default: + goBack = true; + break; + case HOVEROPTION_LIST: + if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + m_bWaitingForNewKeyBind = true; + m_bStartWaitingForKeyBind = true; + pControlEdit = &m_KeyPressedCode; + } + if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + m_nCurrExLayer = HOVEROPTION_BACK; + SaveSettings(); + } + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + break; + case HOVEROPTION_USESKIN: + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + m_nCurrExLayer = HOVEROPTION_BACK; + SaveSettings(); + break; + } + } else if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu == MENUPAGE_NEW_GAME_RELOAD && m_bGameNotLoaded) { + DoSettingsBeforeStartingAGame(); +/* } else if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + // .. either empty or there was some outer if. :shrug: pointless anyway, keyboard_controls is handled in first if. +*/ + } else if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + if (m_nSkinsTotal > 0) { + m_pSelectedSkin = m_pSkinListHead.nextSkin; + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + SaveSettings(); + } else { +#ifndef TIDY_UP_PBP + ChangeScreen(!m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0], + GetPreviousPageOption(), true, true); +#else + goBack = true; +#endif + } + } else if (m_nCurrScreen != MENUPAGE_MULTIPLAYER_FIND_GAME) { + option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + switch (option) { + case MENUACTION_RADIO: +#ifdef TIDY_UP_PBP + assumeIncrease = true; +#else + ++m_PrefsRadioStation; + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (m_PrefsRadioStation > USERTRACK) + m_PrefsRadioStation = HEAD_RADIO; + } else if (m_PrefsRadioStation > CHATTERBOX) { + m_PrefsRadioStation = USERTRACK; + } + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); +#endif + break; + case MENUACTION_LANG_ENG: + m_PrefsLanguage = LANGUAGE_AMERICAN; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_LANG_FRE: + m_PrefsLanguage = LANGUAGE_FRENCH; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_LANG_GER: + m_PrefsLanguage = LANGUAGE_GERMAN; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_LANG_ITA: + m_PrefsLanguage = LANGUAGE_ITALIAN; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_LANG_SPA: + m_PrefsLanguage = LANGUAGE_SPANISH; + m_bFrontEnd_ReloadObrTxtGxt = true; + InitialiseChangedLanguageSettings(); + SaveSettings(); + break; + case MENUACTION_POPULATESLOTS_CHANGEMENU: + PcSaveHelper.PopulateSlotInfo(); + + // fall through + case MENUACTION_CHANGEMENU: + { + bool changeMenu = true; + int saveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot; + + // This should be unused. + if (saveSlot >= 2 && saveSlot <= 9) { + m_nCurrSaveSlot = saveSlot - 2; + switch (m_nCurrScreen) { + case MENUPAGE_CHOOSE_LOAD_SLOT: + if (Slots[m_nCurrSaveSlot + 1] != SLOT_EMPTY) + changeMenu = false; + + break; + case MENUPAGE_CHOOSE_DELETE_SLOT: + if (Slots[m_nCurrSaveSlot + 1] == SLOT_EMPTY) + changeMenu = false; + + break; + } + } + if (changeMenu) { + if (strcmp(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName, "FEDS_TB") == 0) { +#ifndef TIDY_UP_PBP + ResetHelperText(); + ChangeScreen(!m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0], + GetPreviousPageOption(), true, true); +#else + goBack = true; + break; +#endif + } else { + ChangeScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, 0, true, true); + } + } + break; + } + case MENUACTION_CHECKSAVE: + { + int saveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot; + + if (saveSlot >= 2 && saveSlot <= 9) { + m_nCurrSaveSlot = saveSlot - 2; + if (Slots[m_nCurrSaveSlot + 1] != SLOT_EMPTY && Slots[m_nCurrSaveSlot + 1] != SLOT_CORRUPTED) { + ChangeScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, 0, true, true); + } + } + break; + } + case MENUACTION_NEWGAME: + DoSettingsBeforeStartingAGame(); + break; + case MENUACTION_RELOADIDE: + CFileLoader::ReloadObjectTypes("GTA3.IDE"); + break; + case MENUACTION_RELOADIPL: + CGame::ReloadIPLs(); + break; + case MENUACTION_SHOWCULL: + gbShowCullZoneDebugStuff = !gbShowCullZoneDebugStuff; + break; + case MENUACTION_MEMCARDSAVECONFIRM: + return; + case MENUACTION_RESUME_FROM_SAVEZONE: + RequestFrontEndShutDown(); + break; + case MENUACTION_MPMAP_LIBERTY: + case MENUACTION_MPMAP_REDLIGHT: + case MENUACTION_MPMAP_CHINATOWN: + case MENUACTION_MPMAP_TOWER: + case MENUACTION_MPMAP_SEWER: + case MENUACTION_MPMAP_INDUSTPARK: + case MENUACTION_MPMAP_DOCKS: + case MENUACTION_MPMAP_STAUNTON: + m_SelectedMap = option - MENUACTION_MPMAP_LIBERTY; + SaveSettings(); + ChangeScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, 0, true, true); + break; + case MENUACTION_MPMAP_DEATHMATCH1: + case MENUACTION_MPMAP_DEATHMATCH2: + case MENUACTION_MPMAP_TEAMDEATH1: + case MENUACTION_MPMAP_TEAMDEATH2: + case MENUACTION_MPMAP_STASH: + case MENUACTION_MPMAP_CAPTURE: + case MENUACTION_MPMAP_RATRACE: + case MENUACTION_MPMAP_DOMINATION: + m_SelectedGameType = option - MENUACTION_MPMAP_DEATHMATCH1; + SaveSettings(); + ChangeScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, 0, true, true); + break; + case MENUACTION_KEYBOARDCTRLS: + ChangeScreen(MENUPAGE_KEYBOARD_CONTROLS, 0, true, true); + m_nSelectedListRow = 0; + m_nCurrExLayer = HOVEROPTION_LIST; + break; + case MENUACTION_GETKEY: + m_CurrCntrlAction = GetStartOptionsCntrlConfigScreens() + m_nCurrOption; + m_bKeyIsOK = true; + m_bWaitingForNewKeyBind = true; + m_bStartWaitingForKeyBind = true; + pControlEdit = &m_KeyPressedCode; + break; + case MENUACTION_CANCELGAME: + DMAudio.Service(); + RsEventHandler(rsQUITAPP, nil); + break; + case MENUACTION_RESUME: +#ifndef TIDY_UP_PBP + if (m_PrefsVsyncDisp != m_PrefsVsync) { + m_PrefsVsync = m_PrefsVsyncDisp; + } + RequestFrontEndShutDown(); +#else + goBack = true; +#endif + break; + case MENUACTION_DONTCANCEL: + ChangeScreen(!m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0], + GetPreviousPageOption(), true, true); + break; + case MENUACTION_SCREENRES: + if (m_nDisplayVideoMode != m_nPrefsVideoMode) { + m_nPrefsVideoMode = m_nDisplayVideoMode; + _psSelectScreenVM(m_nPrefsVideoMode); + SetHelperText(0); + SaveSettings(); + } + break; + case MENUACTION_AUDIOHW: + { + int selectedProvider = m_nPrefsAudio3DProviderIndex; + if (selectedProvider != -1) { + m_nPrefsAudio3DProviderIndex = DMAudio.SetCurrent3DProvider(m_nPrefsAudio3DProviderIndex); + if (selectedProvider == m_nPrefsAudio3DProviderIndex) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SetHelperText(0); + } else { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + SetHelperText(4); + } + SaveSettings(); + } + break; + } + case MENUACTION_SPEAKERCONF: +#ifndef TIDY_UP_PBP + if (m_nPrefsAudio3DProviderIndex != -1) { + if (--m_PrefsSpeakers < 0) + m_PrefsSpeakers = 2; + DMAudio.SetSpeakerConfig(m_PrefsSpeakers); + SaveSettings(); + } +#else + assumeIncrease = true; +#endif + break; + case MENUACTION_PLAYERSETUP: + CPlayerSkin::BeginFrontendSkinEdit(); + ChangeScreen(MENUPAGE_SKIN_SELECT, 0, true, true); + m_nCurrExLayer = HOVEROPTION_LIST; + m_bSkinsEnumerated = false; + break; + case MENUACTION_RESTOREDEF: + if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + m_PrefsSfxVolume = 102; + m_PrefsSpeakers = 0; + m_PrefsMusicVolume = 102; + m_PrefsStereoMono = 0; + m_PrefsRadioStation = HEAD_RADIO; + DMAudio.SetMusicMasterVolume(102); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + SaveSettings(); + } else if (m_nCurrScreen == MENUPAGE_DISPLAY_SETTINGS) { + m_PrefsFrameLimiter = true; + m_PrefsBrightness = 256; + m_PrefsVsyncDisp = true; + m_PrefsLOD = 1.2f; + m_PrefsVsync = true; + CRenderer::ms_lodDistScale = 1.2f; +#ifdef ASPECT_RATIO_SCALE + m_PrefsUseWideScreen = AR_AUTO; +#else + m_PrefsUseWideScreen = false; +#endif + m_PrefsShowSubtitles = true; + m_nDisplayVideoMode = m_nPrefsVideoMode; +#if GTA_VERSION >= GTA3_PC_11 + if (_dwOperatingSystemVersion == OS_WIN98) { + CMBlur::BlurOn = false; + CMBlur::MotionBlurClose(); + } else { + CMBlur::BlurOn = true; + CMBlur::MotionBlurOpen(Scene.camera); + } +#else + CMBlur::BlurOn = true; +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + extern void RestoreDefGraphics(int8); + extern void RestoreDefDisplay(int8); + + RestoreDefGraphics(FEOPTION_ACTION_SELECT); + RestoreDefDisplay(FEOPTION_ACTION_SELECT); +#endif + SaveSettings(); + } else if ((m_nCurrScreen != MENUPAGE_SKIN_SELECT_OLD) && (m_nCurrScreen == MENUPAGE_CONTROLLER_PC)) { + ControlsManager.MakeControllerActionsBlank(); + ControlsManager.InitDefaultControlConfiguration(); + ControlsManager.InitDefaultControlConfigMouse(MousePointerStateHelper.GetMouseSetUp()); +#if !defined RW_GL3 + if (AllValidWinJoys.m_aJoys[JOYSTICK1].m_bInitialised) { + DIDEVCAPS devCaps; + devCaps.dwSize = sizeof(DIDEVCAPS); + PSGLOBAL(joy1)->GetCapabilities(&devCaps); + ControlsManager.InitDefaultControlConfigJoyPad(devCaps.dwButtons); + } +#else + if (PSGLOBAL(joy1id) != -1 && glfwJoystickPresent(PSGLOBAL(joy1id))) { + int count; + glfwGetJoystickButtons(PSGLOBAL(joy1id), &count); + ControlsManager.InitDefaultControlConfigJoyPad(count); + } +#endif + m_ControlMethod = CONTROL_STANDARD; +#ifdef FIX_BUGS + MousePointerStateHelper.bInvertVertically = true; + TheCamera.m_fMouseAccelVertical = 0.003f; +#else + MousePointerStateHelper.bInvertVertically = false; +#endif + TheCamera.m_fMouseAccelHorzntl = 0.0025f; + CVehicle::m_bDisableMouseSteering = true; + TheCamera.m_bHeadBob = false; + SaveSettings(); +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#endif + } + SetHelperText(2); + break; + case MENUACTION_CTRLMETHOD: +#ifndef TIDY_UP_PBP + if (m_ControlMethod == CONTROL_CLASSIC) { + CCamera::m_bUseMouse3rdPerson = true; + m_ControlMethod = CONTROL_STANDARD; + } else { + CCamera::m_bUseMouse3rdPerson = false; + m_ControlMethod = CONTROL_CLASSIC; + } + SaveSettings(); +#else + assumeIncrease = true; +#endif + break; + case MENUACTION_LOADRADIO: + ChangeScreen(MENUPAGE_SOUND_SETTINGS, 0, true, true); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("STARTED PLAYING FRONTEND AUDIO TRACK"); + break; +#ifdef MISSION_REPLAY + case MENUACTION_REJECT_RETRY: + doingMissionRetry = false; + AllowMissionReplay = 0; + RequestFrontEndShutDown(); + break; + case MENUACTION_UNK114: + doingMissionRetry = false; + RequestFrontEndShutDown(); + RetryMission(MISSION_RETRY_TYPE_BEGIN_RESTARTING); + return; +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_SELECT: + case MENUACTION_CFO_DYNAMIC: + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption]; + if (option.m_Action == MENUACTION_CFO_SELECT) { + if (option.m_CFOSelect->disableIfGameLoaded && !m_bGameNotLoaded) + break; + + if (!option.m_CFOSelect->onlyApplyOnEnter) { + option.m_CFOSelect->displayedValue++; + if (option.m_CFOSelect->displayedValue >= option.m_CFOSelect->numRightTexts || option.m_CFOSelect->displayedValue < 0) + option.m_CFOSelect->displayedValue = 0; + } + int8 oldValue = *(int8*)option.m_CFO->value; + + *(int8*)option.m_CFO->value = option.m_CFOSelect->lastSavedValue = option.m_CFOSelect->displayedValue; + + // Now everything is saved in .ini, and LOAD_INI_SETTINGS is fundamental for CFO + // if (option.m_CFOSelect->save) + SaveSettings(); + + if (option.m_CFOSelect->displayedValue != oldValue && option.m_CFOSelect->changeFunc) + option.m_CFOSelect->changeFunc(oldValue, option.m_CFOSelect->displayedValue); + + } else if (option.m_Action == MENUACTION_CFO_DYNAMIC) { + if (option.m_CFODynamic->buttonPressFunc) + option.m_CFODynamic->buttonPressFunc(FEOPTION_ACTION_SELECT); + } + + break; +#endif + } + } + ProcessOnOffMenuOptions(); + } + + if (goBack) { + ResetHelperText(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); +#ifdef PS2_LIKE_MENU + if (m_nCurrScreen == MENUPAGE_PAUSE_MENU || bottomBarActive) { +#else + if (m_nCurrScreen == MENUPAGE_PAUSE_MENU) { +#endif + if (!m_bGameNotLoaded && !m_bMenuStateChanged) { + if (m_PrefsVsyncDisp != m_PrefsVsync) { + m_PrefsVsync = m_PrefsVsyncDisp; + } + RequestFrontEndShutDown(); + } + + // We're already resuming, we don't need further processing. +#if defined(FIX_BUGS) || defined(PS2_LIKE_MENU) + return; +#endif + } +#ifdef PS2_SAVE_DIALOG + else if (m_nCurrScreen == MENUPAGE_CHOOSE_SAVE_SLOT || m_nCurrScreen == MENUPAGE_SAVE) { +#else + else if (m_nCurrScreen == MENUPAGE_CHOOSE_SAVE_SLOT) { +#endif + RequestFrontEndShutDown(); + } + // It's now in ThingsToDoBeforeGoingBack() +#ifndef TIDY_UP_PBP + else if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); + } +#endif + + int oldScreen = !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0]; + int oldOption = GetPreviousPageOption(); + + if (oldScreen != -1) { + ThingsToDoBeforeGoingBack(); + +#ifdef PS2_LIKE_MENU + if (!bottomBarActive && + (oldScreen == MENUPAGE_NONE || oldScreen == MENUPAGE_OPTIONS)) { + bottomBarActive = true; + } else +#endif + { + ChangeScreen(oldScreen, oldOption, true, true); + } + + // We will go back for sure at this point, why process other things?! +#ifdef FIX_BUGS + return; +#endif + } + } + +#ifdef PS2_LIKE_MENU + if (bottomBarActive) + return; +#endif + + int changeValueBy = 0; + bool decrease = false; +#ifdef TIDY_UP_PBP + bool increase = assumeIncrease; +#else + bool increase = false; +#endif + if (CPad::GetPad(0)->GetLeft() || CPad::GetPad(0)->GetPedWalkLeftRight() < 0 || CPad::GetPad(0)->GetDPadLeft()) { + static uint32 lastSliderDecrease = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastSliderDecrease > 150) { + CheckSliderMovement(-1); + lastSliderDecrease = CTimer::GetTimeInMillisecondsPauseMode(); + } + } else if (CPad::GetPad(0)->GetRight() || CPad::GetPad(0)->GetPedWalkLeftRight() > 0 || CPad::GetPad(0)->GetDPadRight()) { + static uint32 lastSliderIncrease = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastSliderIncrease > 150) { + CheckSliderMovement(1); + lastSliderIncrease = CTimer::GetTimeInMillisecondsPauseMode(); + } + } + + if (CPad::GetPad(0)->GetRightJustDown() || CPad::GetPad(0)->GetAnaloguePadRight() || CPad::GetPad(0)->GetDPadRightJustDown()) { + m_bShowMouse = false; + increase = true; + } else if ( +#ifdef SCROLLABLE_PAGES + !SCREEN_HAS_AUTO_SCROLLBAR && +#endif + CPad::GetPad(0)->GetMouseWheelUpJustDown() && m_nCurrScreen != MENUPAGE_KEYBOARD_CONTROLS) { + increase = true; + CheckSliderMovement(1); + m_bShowMouse = true; + } + + if (CPad::GetPad(0)->GetLeftJustDown() || CPad::GetPad(0)->GetAnaloguePadLeft() || CPad::GetPad(0)->GetDPadLeftJustDown()) { + m_bShowMouse = false; + decrease = true; + } else if ( +#ifdef SCROLLABLE_PAGES + !SCREEN_HAS_AUTO_SCROLLBAR && +#endif + CPad::GetPad(0)->GetMouseWheelDownJustDown() && m_nCurrScreen != MENUPAGE_KEYBOARD_CONTROLS) { + decrease = true; + CheckSliderMovement(-1); + m_bShowMouse = true; + } + + if (increase) + changeValueBy++; + else if (decrease) + changeValueBy--; + + if (changeValueBy != 0) { + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { +#ifdef FIX_BUGS + case MENUACTION_CTRLCONFIG: + CPad::GetPad(0)->Mode += changeValueBy; + if (CPad::GetPad(0)->Mode > 3) + CPad::GetPad(0)->Mode = 0; + else if (CPad::GetPad(0)->Mode < 0) + CPad::GetPad(0)->Mode = 3; + SaveSettings(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; +#endif + case MENUACTION_RADIO: + m_PrefsRadioStation += changeValueBy; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (m_PrefsRadioStation < HEAD_RADIO) + m_PrefsRadioStation = USERTRACK; + if (m_PrefsRadioStation > USERTRACK) + m_PrefsRadioStation = HEAD_RADIO; + } else { + if (m_PrefsRadioStation < HEAD_RADIO) + m_PrefsRadioStation = CHATTERBOX; + if (m_PrefsRadioStation > CHATTERBOX) + m_PrefsRadioStation = HEAD_RADIO; + } + SaveSettings(); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + break; +#ifdef ASPECT_RATIO_SCALE + case MENUACTION_WIDESCREEN: + if (changeValueBy > 0) { + m_PrefsUseWideScreen++; + if (m_PrefsUseWideScreen > AR_MAX-1) + m_PrefsUseWideScreen = 0; + } else { + m_PrefsUseWideScreen--; + if (m_PrefsUseWideScreen < 0) + m_PrefsUseWideScreen = AR_MAX-1; + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; +#endif + case MENUACTION_SCREENRES: + if (m_bGameNotLoaded) { + RwChar** videoMods = _psGetVideoModeList(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + if (changeValueBy > 0) { + do { + ++m_nDisplayVideoMode; + + if (m_nDisplayVideoMode >= _psGetNumVideModes()) + m_nDisplayVideoMode = 0; + } while (!videoMods[m_nDisplayVideoMode]); + } else { + do { + --m_nDisplayVideoMode; + + if (m_nDisplayVideoMode < 0) + m_nDisplayVideoMode = _psGetNumVideModes() - 1; + } while (!videoMods[m_nDisplayVideoMode]); + } + } + break; + case MENUACTION_AUDIOHW: + if (m_nPrefsAudio3DProviderIndex != -1) { + m_nPrefsAudio3DProviderIndex += changeValueBy; + m_nPrefsAudio3DProviderIndex = Clamp(m_nPrefsAudio3DProviderIndex, 0, DMAudio.GetNum3DProvidersAvailable() - 1); + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + break; + case MENUACTION_SPEAKERCONF: + if (m_nPrefsAudio3DProviderIndex != -1) { + m_PrefsSpeakers -= changeValueBy; + m_PrefsSpeakers = Clamp(m_PrefsSpeakers, 0, 2); + DMAudio.SetSpeakerConfig(m_PrefsSpeakers); + SaveSettings(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + break; + case MENUACTION_CTRLMETHOD: + m_ControlMethod = !m_ControlMethod; + CCamera::m_bUseMouse3rdPerson = !m_ControlMethod; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_CFO_SELECT: + case MENUACTION_CFO_DYNAMIC: + CMenuScreenCustom::CMenuEntry &option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption]; + if (option.m_Action == MENUACTION_CFO_SELECT) { + if (option.m_CFOSelect->disableIfGameLoaded && !m_bGameNotLoaded) + break; + + if (changeValueBy > 0) { + option.m_CFOSelect->displayedValue++; + if (option.m_CFOSelect->displayedValue >= option.m_CFOSelect->numRightTexts) + option.m_CFOSelect->displayedValue = 0; + } else { + option.m_CFOSelect->displayedValue--; + if (option.m_CFOSelect->displayedValue < 0) + option.m_CFOSelect->displayedValue = option.m_CFOSelect->numRightTexts - 1; + } + if (!option.m_CFOSelect->onlyApplyOnEnter) { + int8 oldValue = *(int8*)option.m_CFO->value; + + *(int8*)option.m_CFO->value = option.m_CFOSelect->lastSavedValue = option.m_CFOSelect->displayedValue; + + // Now everything is saved in .ini, and LOAD_INI_SETTINGS is fundamental for CFO + // if (option.m_CFOSelect->save) + SaveSettings(); + + if (option.m_CFOSelect->displayedValue != oldValue && option.m_CFOSelect->changeFunc) + option.m_CFOSelect->changeFunc(oldValue, option.m_CFOSelect->displayedValue); + } + } else if (option.m_Action == MENUACTION_CFO_DYNAMIC && option.m_CFODynamic->buttonPressFunc) { + option.m_CFODynamic->buttonPressFunc(changeValueBy > 0 ? FEOPTION_ACTION_RIGHT : FEOPTION_ACTION_LEFT); + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + + break; +#endif + } + ProcessOnOffMenuOptions(); + if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + if (changeValueBy < 1) { + m_nSelectedContSetupColumn = CONTSETUP_PED_COLUMN; + } else { + m_nSelectedContSetupColumn = CONTSETUP_VEHICLE_COLUMN; + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } +} + +void +CMenuManager::ProcessOnOffMenuOptions() +{ + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { + case MENUACTION_CTRLVIBRATION: + m_PrefsUseVibration = !m_PrefsUseVibration; + + if (m_PrefsUseVibration) { + CPad::GetPad(0)->StartShake(350, 150); + TimeToStopPadShaking = CTimer::GetTimeInMillisecondsPauseMode() + 500; + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); +#ifdef FIX_BUGS + SaveSettings(); +#endif // !FIX_BUGS + break; +#ifndef FIX_BUGS + case MENUACTION_CTRLCONFIG: + CPad::GetPad(0)->Mode++; + if (CPad::GetPad(0)->Mode > 3) + CPad::GetPad(0)->Mode = 0; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; +#endif // !FIX_BUGS + case MENUACTION_CTRLDISPLAY: + m_DisplayControllerOnFoot = !m_DisplayControllerOnFoot; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_FRAMESYNC: + m_PrefsVsyncDisp = !m_PrefsVsyncDisp; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_FRAMELIMIT: + m_PrefsFrameLimiter = !m_PrefsFrameLimiter; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_TRAILS: + CMBlur::BlurOn = !CMBlur::BlurOn; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + if (CMBlur::BlurOn) + CMBlur::MotionBlurOpen(Scene.camera); + else + CMBlur::MotionBlurClose(); + break; + case MENUACTION_SUBTITLES: + m_PrefsShowSubtitles = !m_PrefsShowSubtitles; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; +#ifndef ASPECT_RATIO_SCALE + case MENUACTION_WIDESCREEN: + m_PrefsUseWideScreen = !m_PrefsUseWideScreen; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; +#endif + case MENUACTION_SETDBGFLAG: + CTheScripts::InvertDebugFlag(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_SWITCHBIGWHITEDEBUGLIGHT: + gbBigWhiteDebugLightSwitchedOn = !gbBigWhiteDebugLightSwitchedOn; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_PEDROADGROUPS: + gbShowPedRoadGroups = !gbShowPedRoadGroups; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_CARROADGROUPS: + gbShowCarRoadGroups = !gbShowCarRoadGroups; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_COLLISIONPOLYS: + gbShowCollisionPolys = !gbShowCollisionPolys; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + case MENUACTION_MP_PLAYERCOLOR: + PickNewPlayerColour(); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_SHOWHEADBOB: + TheCamera.m_bHeadBob = !TheCamera.m_bHeadBob; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_INVVERT: + MousePointerStateHelper.bInvertVertically = !MousePointerStateHelper.bInvertVertically; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_DYNAMICACOUSTIC: + m_PrefsDMA = !m_PrefsDMA; + DMAudio.SetDynamicAcousticModelingStatus(m_PrefsDMA); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + case MENUACTION_MOUSESTEER: + CVehicle::m_bDisableMouseSteering = !CVehicle::m_bDisableMouseSteering; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + SaveSettings(); + break; + } +} + +void +CMenuManager::RequestFrontEndShutDown() +{ + m_bShutDownFrontEndRequested = true; + DMAudio.ChangeMusicMode(MUSICMODE_GAME); +} + +void +CMenuManager::RequestFrontEndStartUp() +{ + m_bStartUpFrontEndRequested = true; +} + +void +CMenuManager::ResetHelperText() +{ + m_nHelperTextMsgId = 0; + m_nHelperTextAlpha = 300; +} + +void +CMenuManager::SaveLoadFileError_SetUpErrorScreen() +{ + switch (PcSaveHelper.nErrorCode) { + case SAVESTATUS_ERR_SAVE_CREATE: + case SAVESTATUS_ERR_SAVE_WRITE: + case SAVESTATUS_ERR_SAVE_CLOSE: + ChangeScreen(MENUPAGE_SAVE_FAILED, 0, true, false); + break; + case SAVESTATUS_ERR_LOAD_OPEN: + case SAVESTATUS_ERR_LOAD_READ: + case SAVESTATUS_ERR_LOAD_CLOSE: + ChangeScreen(MENUPAGE_LOAD_FAILED, 0, true, false); + break; + case SAVESTATUS_ERR_DATA_INVALID: + ChangeScreen(MENUPAGE_LOAD_FAILED_2, 0, true, false); + break; + case SAVESTATUS_DELETEFAILED8: + case SAVESTATUS_DELETEFAILED9: + case SAVESTATUS_DELETEFAILED10: + ChangeScreen(MENUPAGE_DELETE_FAILED, 0, true, false); + break; + default: break; + } +} + +void +CMenuManager::SetHelperText(int text) +{ + m_nHelperTextMsgId = text; + m_nHelperTextAlpha = 300; +} + +void +CMenuManager::ShutdownJustMenu() +{ + // In case we're windowed, keep mouse centered while in game. Done in main.cpp in other conditions. +#if defined(RW_GL3) && defined(IMPROVED_VIDEOMODE) + glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, GLFW_CURSOR_DISABLED); +#endif + m_bMenuActive = false; + CTimer::EndUserPause(); +} + +float +CMenuManager::StretchX(float x) +{ + if (SCREEN_WIDTH == DEFAULT_SCREEN_WIDTH) + return x; + else + // We won't make this SCREEN_SCALE, because many cases relies on stretching and we want the code to be portable. + // Instead we will use MENU_X_LEFT_ALIGNED or SCREEN_SCALE_X when needed. + return SCREEN_STRETCH_X(x); +} + +float CMenuManager::StretchY(float y) +{ + if (SCREEN_HEIGHT == DEFAULT_SCREEN_HEIGHT) + return y; + else + return SCREEN_STRETCH_Y(y); +} + +void +CMenuManager::SwitchMenuOnAndOff() +{ + bool menuWasActive = GetIsMenuActive(); + + // Reminder: You need REGISTER_START_BUTTON defined to make it work. + if (CPad::GetPad(0)->GetStartJustDown() +#ifdef FIX_BUGS + && !m_bGameNotLoaded +#endif + || m_bShutDownFrontEndRequested || m_bStartUpFrontEndRequested) { + + m_bMenuActive = !m_bMenuActive; +#ifdef FIX_BUGS + CPad::StopPadsShaking(); +#endif + + if (m_bShutDownFrontEndRequested) + m_bMenuActive = false; + if (m_bStartUpFrontEndRequested) + m_bMenuActive = true; + + if (m_bMenuActive) { + CTimer::StartUserPause(); + } else { +#ifdef PS2_LIKE_MENU + bottomBarActive = false; +#endif +#ifdef FIX_BUGS + ThingsToDoBeforeGoingBack(); +#endif + ShutdownJustMenu(); + SaveSettings(); +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#endif + m_bStartUpFrontEndRequested = false; + pControlEdit = nil; + m_bShutDownFrontEndRequested = false; + DisplayComboButtonErrMsg = false; + +#ifdef REGISTER_START_BUTTON + int16 start1 = CPad::GetPad(0)->PCTempJoyState.Start, start2 = CPad::GetPad(0)->PCTempKeyState.Start, + start3 = CPad::GetPad(0)->OldState.Start, start4 = CPad::GetPad(0)->NewState.Start; +#endif + CPad::GetPad(0)->Clear(false); + CPad::GetPad(1)->Clear(false); +#ifdef REGISTER_START_BUTTON + CPad::GetPad(0)->PCTempJoyState.Start = start1; + CPad::GetPad(0)->PCTempKeyState.Start = start2; + CPad::GetPad(0)->OldState.Start = start3; + CPad::GetPad(0)->NewState.Start = start4; +#endif + m_nCurrScreen = MENUPAGE_NONE; + } + } + + // Just entered the save/safe zone + if (m_bSaveMenuActive && !m_bQuitGameNoCD) { + m_bSaveMenuActive = false; + m_bMenuActive = true; + CTimer::StartUserPause(); +#ifdef PS2_SAVE_DIALOG + m_nCurrScreen = MENUPAGE_SAVE; + m_bRenderGameInMenu = true; +#else + m_nCurrScreen = MENUPAGE_CHOOSE_SAVE_SLOT; +#endif + PcSaveHelper.PopulateSlotInfo(); + m_nCurrOption = 0; + } +/* // PS2 leftover + if (m_nCurrScreen != MENUPAGE_SOUND_SETTINGS && gMusicPlaying) + { + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); + gMusicPlaying = 0; + } +*/ + if (m_bMenuActive != menuWasActive) { + m_bMenuStateChanged = true; + + // In case we're windowed, keep mouse centered while in game. Done in main.cpp in other conditions. +#if defined(RW_GL3) && defined(IMPROVED_VIDEOMODE) + glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, m_bMenuActive && m_nPrefsWindowed ? GLFW_CURSOR_HIDDEN : GLFW_CURSOR_DISABLED); +#endif + } + + m_bStartUpFrontEndRequested = false; + m_bShutDownFrontEndRequested = false; +} + +void +CMenuManager::UnloadTextures() +{ + if (!m_bSpritesLoaded) + return; + + printf("REMOVE frontend\n"); + for (int i = 0; i < ARRAY_SIZE(FrontendFilenames); ++i) + m_aFrontEndSprites[i].Delete(); + + int frontend = CTxdStore::FindTxdSlot("frontend"); + CTxdStore::RemoveTxd(frontend); + +#ifdef GAMEPAD_MENU + int frontend_controllerTxdSlot = CTxdStore::FindTxdSlot("frontend_controller"); + if (frontend_controllerTxdSlot != -1) + CTxdStore::RemoveTxd(frontend_controllerTxdSlot); +#endif + + printf("REMOVE menu textures\n"); + for (int i = 0; i < ARRAY_SIZE(MenuFilenames); ++i) + m_aMenuSprites[i].Delete(); +#ifdef MENU_MAP + for (int i = 0; i < ARRAY_SIZE(MapFilenames); ++i) + m_aMapSprites[i].Delete(); +#endif + int menu = CTxdStore::FindTxdSlot("menu"); + CTxdStore::RemoveTxd(menu); + + m_bSpritesLoaded = false; +} + +void +CMenuManager::WaitForUserCD() +{ + CSprite2d *splash; + char *splashscreen = nil; + +#if (!(defined RANDOMSPLASH) && GTA_VERSION < GTA3_PC_11) + if (CGame::frenchGame || CGame::germanGame || !CGame::nastyGame) + splashscreen = "mainsc2"; + else + splashscreen = "mainsc1"; +#endif + + splash = LoadSplash(splashscreen); + + if (RsGlobal.quit) + return; + + HandleExit(); + CPad::UpdatePads(); + MessageScreen("NO_PCCD"); + + if (CPad::GetPad(0)->GetEscapeJustDown()) { + m_bQuitGameNoCD = true; + RsEventHandler(rsQUITAPP, nil); + } +} + +#ifdef GAMEPAD_MENU +void +CMenuManager::PrintController(void) +{ + const float scale = 0.9f; + const float CONTROLLER_SIZE_X = 235.2f; + const float CONTROLLER_SIZE_Y = 175.2f; + const float CONTROLLER_POS_X = (DEFAULT_SCREEN_WIDTH - CONTROLLER_SIZE_X) / 2.0f; + const float CONTROLLER_POS_Y = 160.0f; + + float centerX = CONTROLLER_POS_X + CONTROLLER_SIZE_X / 2; + float centerY = CONTROLLER_POS_Y + CONTROLLER_SIZE_Y / 2; + +#define X(f) ((f)*scale + centerX) +#define Y(f) ((f)*scale + centerY) + + m_aFrontEndSprites[FE_CONTROLLERSH].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X((CONTROLLER_SIZE_X + 4.8f) * scale), MENU_Y((CONTROLLER_SIZE_Y + 4.8f) * scale), CRGBA(0, 0, 0, 255)); + m_aFrontEndSprites[FE_CONTROLLER].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + if (m_DisplayControllerOnFoot) { + if (CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[FE_ARROWS1].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[FE_ARROWS3].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + } else { + if (CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[FE_ARROWS2].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[FE_ARROWS4].Draw(MENU_X_LEFT_ALIGNED(X(-CONTROLLER_SIZE_X / 2)), MENU_Y(Y(-CONTROLLER_SIZE_Y / 2)), MENU_X(CONTROLLER_SIZE_X * scale), MENU_Y(CONTROLLER_SIZE_Y * scale), CRGBA(255, 255, 255, 255)); + } + + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * scale), MENU_Y(SMALLESTTEXT_Y_SCALE * scale)); // X + + // CFont::SetColor(CRGBA(128, 128, 128, FadeIn(255))); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetDropShadowPosition(1); + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + CFont::SetWrapx(SCREEN_WIDTH); + + float TEXT_L2_X = 50.0f + CONTROLLER_POS_X - centerX, TEXT_L2_Y = -14.0f + CONTROLLER_POS_Y - centerY; + float TEXT_L1_X = -4.0f + CONTROLLER_POS_X - centerX, TEXT_L1_Y = 25.0f + CONTROLLER_POS_Y - centerY, TEXT_L1_Y_VEH = 3.0f + TEXT_L1_Y; + float TEXT_DPAD_X = -4.0f + CONTROLLER_POS_X - centerX, TEXT_DPAD_Y = 65.0f + CONTROLLER_POS_Y - centerY; + float TEXT_LSTICK_X = -4.0f + CONTROLLER_POS_X - centerX, TEXT_LSTICK_Y = 97.0f + CONTROLLER_POS_Y - centerY; + float TEXT_SELECT_X = 103.0f + CONTROLLER_POS_X - centerX, TEXT_SELECT_Y = 141.0f + CONTROLLER_POS_Y - centerY; + float TEXT_START_X = 130.0f + CONTROLLER_POS_X - centerX, TEXT_START_Y = 128.0f + CONTROLLER_POS_Y - centerY; + float TEXT_R2_X = 184.0F + CONTROLLER_POS_X - centerX, TEXT_R2_Y = -14.0f + CONTROLLER_POS_Y - centerY; + float TEXT_R1_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_R1_Y = 25.0f + CONTROLLER_POS_Y - centerY; + + float TEXT_SQUARE_X = 144.0f + CONTROLLER_POS_X - centerX, TEXT_SQUARE_Y = 18.0f + CONTROLLER_POS_Y - centerY; + float TEXT_TRIANGLE_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_TRIANGLE_Y = 52.0f + CONTROLLER_POS_Y - centerY; + float TEXT_CIRCLE_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_CIRCLE_Y = 65.0f + CONTROLLER_POS_Y - centerY; + float TEXT_CROSS_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_CROSS_Y = 78.0f + CONTROLLER_POS_Y - centerY; + float TEXT_RSTICK_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_RSTICK_Y = 94.0f + CONTROLLER_POS_Y - centerY; + float TEXT_R3_X = 238.0f + CONTROLLER_POS_X - centerX, TEXT_R3_Y = 109.0f + CONTROLLER_POS_Y - centerY; + float TEXT_L3_X = 84.0f + CONTROLLER_POS_X - centerX, TEXT_L3_Y = 162.0f + CONTROLLER_POS_Y - centerY; + float TEXT_L2R2_X = 74.0f + CONTROLLER_POS_X - centerX, TEXT_L2R2_Y = -6.0f + CONTROLLER_POS_Y - centerY; + + switch (m_PrefsControllerType) + { + case CONTROLLER_DUALSHOCK4: + TEXT_L1_Y += 7.0f; + TEXT_L1_Y_VEH = TEXT_L1_Y; + TEXT_R1_Y += 7.0f; + TEXT_TRIANGLE_Y -= 1.0f; + TEXT_CIRCLE_Y -= 1.0f; + TEXT_CROSS_Y -= 1.0f; + TEXT_RSTICK_Y -= 4.0f; + TEXT_R3_Y -= 4.0f; + TEXT_DPAD_Y -= 1.0f; + TEXT_LSTICK_Y -= 6.0f; + TEXT_L3_X -= 2.0f; + break; + case CONTROLLER_XBOXONE: + TEXT_L2_X -= 2.0f; + TEXT_R2_X += 2.0f; + TEXT_L1_Y += 15.0f; + TEXT_L1_Y_VEH = TEXT_L1_Y; + TEXT_R1_Y += 15.0f; + TEXT_TRIANGLE_Y += 4.0f; + TEXT_CIRCLE_Y += 4.0f; + TEXT_CROSS_Y += 4.0f; + TEXT_RSTICK_Y += 1.0f; + TEXT_R3_Y += 1.0f; + TEXT_DPAD_Y += 29.0f; + TEXT_LSTICK_Y -= 22.0f; + TEXT_L3_X -= 36.0f; + TEXT_L2R2_Y += 5.0f; + TEXT_SELECT_X += 4.0f; + break; + case CONTROLLER_XBOX360: + TEXT_L2_X += 8.0f; + TEXT_R2_X -= 8.0f; + TEXT_L1_Y += 15.0f; + TEXT_L1_Y_VEH = TEXT_L1_Y; + TEXT_R1_Y += 15.0f; + TEXT_TRIANGLE_Y += 4.0f; + TEXT_CIRCLE_Y += 4.0f; + TEXT_CROSS_Y += 4.0f; + TEXT_RSTICK_Y += 4.0f; + TEXT_R3_Y += 4.0f; + TEXT_DPAD_Y += 30.0f; + TEXT_LSTICK_Y -= 21.0f; + TEXT_L3_X -= 36.0f; + TEXT_L2R2_Y += 5.0f; + TEXT_SELECT_X += 3.0f; + break; + case CONTROLLER_NINTENDO_SWITCH: + TEXT_L1_Y += 5.0f; + TEXT_L1_Y_VEH = TEXT_L1_Y; + TEXT_R1_Y += 5.0f; + TEXT_TRIANGLE_Y += 3.0f; + TEXT_CIRCLE_Y += 3.0f; + TEXT_CROSS_Y += 3.0f; + TEXT_LSTICK_Y -= 23.0f; + TEXT_DPAD_Y += 25.0; + TEXT_RSTICK_Y += 1.0f; + TEXT_R3_Y += 1.0f; + break; + }; + + if (m_DisplayControllerOnFoot) { + switch (CPad::GetPad(0)->Mode) { + case 0: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y)), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_MOV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y + 13.0f)), TheText.Get("FEC_R3")); + break; + case 1: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y)), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y + 13.0f)), TheText.Get("FEC_R3")); + break; + case 2: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y)), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_MOV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y + 13.0f)), TheText.Get("FEC_R3")); + break; + case 3: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y)), TheText.Get("FEC_TAR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_ATT")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y + 13.0f)), TheText.Get("FEC_R3")); + break; + default: + return; + } + } else { + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2R2_X)), MENU_Y(Y(TEXT_L2R2_Y)), TheText.Get("FEC_LB")); + switch (CPad::GetPad(0)->Mode) { + case 0: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y_VEH)), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_VES")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_HO3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_LR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_HAB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_BRA")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_SM3")); + break; + case 1: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y_VEH)), TheText.Get("FEC_HOR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_LR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_HAB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_BRA")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_SM3")); + break; + case 2: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y_VEH)), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_VES")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_RS3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_LR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_HOR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_BRA")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_SM3")); + break; + case 3: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)), MENU_Y(Y(TEXT_L1_Y_VEH)), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_DPAD_X)), MENU_Y(Y(TEXT_DPAD_Y)), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_LSTICK_Y)), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_HO3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_LR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_CAW")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_SMT")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_TRIANGLE_X)), MENU_Y(Y(TEXT_TRIANGLE_Y)), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CIRCLE_X)), MENU_Y(Y(TEXT_CIRCLE_Y)), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_CROSS_X)), MENU_Y(Y(TEXT_CROSS_Y)), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_RSTICK_X)), MENU_Y(Y(TEXT_RSTICK_Y)), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R3_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_BRA")); + break; + default: + return; + } + } + + CFont::SetDropShadowPosition(0); // X + +#undef X +#undef Y +} +#else +void +CMenuManager::PrintController(void) +{ + // FIX: Originally this function doesn't have StretchX/Y, everything had constant pixel size (due to screen was abandoned early?) + // Also texts and their alignment were very bad, so I tried to make them readable (commented out the original code, and marked the ones I added with X) + + m_aFrontEndSprites[FE_CONTROLLERSH].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(240.0f), MENU_Y(180.0f), CRGBA(0, 0, 0, 255)); + m_aFrontEndSprites[FE_CONTROLLER].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + if (m_DisplayControllerOnFoot) { + if (CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[FE_ARROWS1].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[FE_ARROWS3].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + } else { + if (CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[FE_ARROWS2].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[FE_ARROWS4].Draw(MENU_X_LEFT_ALIGNED(160.0f), MENU_Y(160.0f), MENU_X(235.2f), MENU_Y(175.2f), CRGBA(255, 255, 255, 255)); + } + + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); // X + + // CFont::SetScale(0.4f, 0.4f); + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE), MENU_Y(SMALLESTTEXT_Y_SCALE)); // X + + // CFont::SetColor(CRGBA(128, 128, 128, FadeIn(255))); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); // X + CFont::SetDropShadowPosition(1); // X + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); // X + + if (m_DisplayControllerOnFoot) { + switch (CPad::GetPad(0)->Mode) { + case 0: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_MOV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_R3")); + break; + case 1: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_R3")); + break; + case 2: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_ENV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_MOV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_R3")); + break; + case 3: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_CWL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_MOV")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_CWR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_LOF")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_RUN")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_ATT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_FPC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_LB3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_R3")); + break; + default: + return; + } + } else { + switch (CPad::GetPad(0)->Mode) { + case 0: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_VES")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_HO3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_LB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_LR")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_BRA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_TUC")); + // FIX: Coordinates of this line is undefined in PC... + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(304.0f), TheText.Get("FEC_SM3")); + break; + case 1: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_HOR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_LB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_LR")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_BRA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_TUC")); + // FIX: Coordinates of this line is undefined in PC... + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(304.0f), TheText.Get("FEC_SM3")); + break; + case 2: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_VES")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_RS3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_LB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_LR")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_HOR")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_BRA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_ACC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_TUC")); + // FIX: Coordinates of this line is undefined in PC... + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(304.0f), TheText.Get("FEC_SM3")); + break; + case 3: + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(210.0f), MENU_Y(146.0f), TheText.Get("FEC_LL")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(185.0f), TheText.Get("FEC_HAB")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(225.0f), TheText.Get("FEC_TUC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(156.0f), MENU_Y(257.0f), TheText.Get("FEC_VES")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(263.0f), MENU_Y(301.0f), TheText.Get("FEC_HO3")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(290.0f), MENU_Y(288.0f), TheText.Get("FEC_CAM")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(146.0f), TheText.Get("FEC_PAU")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(185.0f), TheText.Get("FEC_LB")); + CFont::SetRightJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(304.0f), MENU_Y(178.0f), TheText.Get("FEC_LR")); + CFont::SetJustifyOn(); // X + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(212.0f), TheText.Get("FEC_CAW")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(225.0f), TheText.Get("FEC_SMT")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(238.0f), TheText.Get("FEC_EXV")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(254.0f), TheText.Get("FEC_RSC")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(269.0f), TheText.Get("FEC_NA")); + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(282.0f), TheText.Get("FEC_ACC")); + // FIX: Coordinates of this line is undefined in PC... + CFont::PrintString(MENU_X_LEFT_ALIGNED(398.0f), MENU_Y(304.0f), TheText.Get("FEC_BRA")); + break; + default: + return; + } + } + + CFont::SetDropShadowPosition(0); // X +} +#endif + +#ifdef MENU_MAP + +#define ZOOM(x, y, in) \ + do { \ + if(fMapSize > SCREEN_HEIGHT * 3.0f && in) \ + break; \ + float z2 = in? 1.1f : 1.f/1.1f; \ + fMapCenterX += (x - fMapCenterX) * (1.0f - z2); \ + fMapCenterY += (y - fMapCenterY) * (1.0f - z2); \ + \ + if (fMapSize < SCREEN_HEIGHT / 2 && !in) \ + break; \ + \ + fMapSize *= z2; \ + } while(0) \ + +void +CMenuManager::PrintMap(void) +{ + CFont::SetJustifyOn(); + bMenuMapActive = true; + CRadar::InitFrontEndMap(); + + if (m_nMenuFadeAlpha < 255 && fMapCenterX == 0.f && fMapCenterY == 0.f) { + // Just entered. We need to do these transformations in here, because Radar knows whether map is active or not + CVector2D radarSpacePlayer; + CVector2D screenSpacePlayer; + CRadar::TransformRealWorldPointToRadarSpace(radarSpacePlayer, CVector2D(FindPlayerCoors())); + CRadar::TransformRadarPointToScreenSpace(screenSpacePlayer, radarSpacePlayer); + fMapCenterX = (-screenSpacePlayer.x) + SCREEN_WIDTH / 2; + fMapCenterY = (-screenSpacePlayer.y) + SCREEN_HEIGHT / 2; + } + + // Because fMapSize is half of the map length, and map consists of 3x3 tiles. + float halfTile = fMapSize / 3.0f; + + // Darken background a bit + CSprite2d::DrawRect(CRect(0, 0, + SCREEN_WIDTH, SCREEN_HEIGHT), + CRGBA(0, 0, 0, FadeIn(128))); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + + if (SCREEN_WIDTH >= fMapCenterX - fMapSize || SCREEN_HEIGHT >= fMapCenterY - fMapSize) { + m_aMapSprites[MAPTOP1].Draw(CRect(fMapCenterX - fMapSize, fMapCenterY - fMapSize, + fMapCenterX - halfTile, fMapCenterY - halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - halfTile || SCREEN_HEIGHT >= fMapCenterY - fMapSize) { + m_aMapSprites[MAPTOP2].Draw(CRect(fMapCenterX - halfTile, fMapCenterY - fMapSize, + fMapCenterX + halfTile, fMapCenterY - halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX + halfTile || SCREEN_HEIGHT >= fMapCenterY - fMapSize) { + m_aMapSprites[MAPTOP3].Draw(CRect(fMapCenterX + halfTile, fMapCenterY - fMapSize, + fMapCenterX + fMapSize, fMapCenterY - halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - fMapSize || SCREEN_HEIGHT >= fMapCenterY - halfTile) { + m_aMapSprites[MAPMID1].Draw(CRect(fMapCenterX - fMapSize, fMapCenterY - halfTile, + fMapCenterX - halfTile, fMapCenterY + halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - halfTile || SCREEN_HEIGHT >= fMapCenterY - halfTile) { + m_aMapSprites[MAPMID2].Draw(CRect(fMapCenterX - halfTile, fMapCenterY - halfTile, + fMapCenterX + halfTile, fMapCenterY + halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX + halfTile || SCREEN_HEIGHT >= fMapCenterY - halfTile) { + m_aMapSprites[MAPMID3].Draw(CRect(fMapCenterX + halfTile, fMapCenterY - halfTile, + fMapCenterX + fMapSize, fMapCenterY + halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - fMapSize || SCREEN_HEIGHT >= fMapCenterY + halfTile) { + m_aMapSprites[MAPBOT1].Draw(CRect(fMapCenterX - fMapSize, fMapCenterY + halfTile, + fMapCenterX - halfTile, fMapCenterY + fMapSize), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX - halfTile || SCREEN_HEIGHT >= fMapCenterY + halfTile) { + m_aMapSprites[MAPBOT2].Draw(CRect(fMapCenterX - halfTile, fMapCenterY + halfTile, + fMapCenterX + halfTile, fMapCenterY + fMapSize), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= fMapCenterX + halfTile || SCREEN_HEIGHT >= fMapCenterY + halfTile) { + m_aMapSprites[MAPBOT3].Draw(CRect(fMapCenterX + halfTile, fMapCenterY + halfTile, + fMapCenterX + fMapSize, fMapCenterY + fMapSize), CRGBA(255, 255, 255, FadeIn(255))); + } + + CRadar::DrawBlips(); + static CVector2D mapCrosshair; + + if (m_nMenuFadeAlpha != 255 && !m_bShowMouse) { + mapCrosshair.x = SCREEN_WIDTH / 2; + mapCrosshair.y = SCREEN_HEIGHT / 2; + } else if (m_bShowMouse) { + mapCrosshair.x = m_nMousePosX; + mapCrosshair.y = m_nMousePosY; + } + + CSprite2d::DrawRect(CRect(mapCrosshair.x - MENU_X(1.0f), 0.0f, + mapCrosshair.x + MENU_X(1.0f), SCREEN_HEIGHT), + CRGBA(0, 0, 0, 150)); + CSprite2d::DrawRect(CRect(0.0f, mapCrosshair.y + MENU_X(1.0f), + SCREEN_WIDTH, mapCrosshair.y - MENU_X(1.0f)), + CRGBA(0, 0, 0, 150)); + + // Adding marker + if (m_nMenuFadeAlpha >= 255) { + if (CPad::GetPad(0)->GetRightMouseJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + if (mapCrosshair.y > fMapCenterY - fMapSize && mapCrosshair.y < fMapCenterY + fMapSize && + mapCrosshair.x > fMapCenterX - fMapSize && mapCrosshair.x < fMapCenterX + fMapSize) { + + float diffX = fMapCenterX - fMapSize, diffY = fMapCenterY - fMapSize; + float x = ((mapCrosshair.x - diffX) / (fMapSize * 2)) * 4000.0f - 2000.0f; + float y = 2000.0f - ((mapCrosshair.y - diffY) / (fMapSize * 2)) * 4000.0f; + CRadar::ToggleTargetMarker(x, y); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + } + } + + if (CPad::GetPad(0)->GetLeftMouse()) { + fMapCenterX += m_nMousePosX - m_nMouseOldPosX; + fMapCenterY += m_nMousePosY - m_nMouseOldPosY; + } else if (CPad::GetPad(0)->GetLeft() || CPad::GetPad(0)->GetDPadLeft()) { + fMapCenterX += 15.0f; + } else if (CPad::GetPad(0)->GetRight() || CPad::GetPad(0)->GetDPadRight()) { + fMapCenterX -= 15.0f; + } else if (CPad::GetPad(0)->GetLeftStickX()) { + fMapCenterX -= CPad::GetPad(0)->GetLeftStickX() / 128.0f * 20.0f; + } + + if (CPad::GetPad(0)->GetUp() || CPad::GetPad(0)->GetDPadUp()) { + fMapCenterY += 15.0f; + } else if (CPad::GetPad(0)->GetDown() || CPad::GetPad(0)->GetDPadDown()) { + fMapCenterY -= 15.0f; + } else if (CPad::GetPad(0)->GetLeftStickY()) { + fMapCenterY -= CPad::GetPad(0)->GetLeftStickY() / 128.0f * 20.0f; + } + + if (CPad::GetPad(0)->GetMouseWheelDown() || CPad::GetPad(0)->GetPageDown() || CPad::GetPad(0)->GetRightShoulder2()) { + if (CPad::GetPad(0)->GetMouseWheelDown()) + ZOOM(mapCrosshair.x, mapCrosshair.y, false); + else + ZOOM(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, false); + } else if (CPad::GetPad(0)->GetMouseWheelUp() || CPad::GetPad(0)->GetPageUp() || CPad::GetPad(0)->GetRightShoulder1()) { + if (CPad::GetPad(0)->GetMouseWheelUp()) + ZOOM(mapCrosshair.x, mapCrosshair.y, true); + else + ZOOM(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, true); + } + + if (fMapCenterX - fMapSize > SCREEN_WIDTH / 2) + fMapCenterX = fMapSize + SCREEN_WIDTH / 2; + + if (fMapCenterX + fMapSize < SCREEN_WIDTH / 2) + fMapCenterX = SCREEN_WIDTH / 2 - fMapSize; + + if (fMapCenterY + fMapSize < SCREEN_HEIGHT - MENU_Y(60.0f)) + fMapCenterY = SCREEN_HEIGHT - MENU_Y(60.0f) - fMapSize; + + fMapCenterY = Min(fMapCenterY, fMapSize); // To not show beyond north border + + bMenuMapActive = false; + + CSprite2d::DrawRect(CRect(MENU_X(14.0f), SCREEN_STRETCH_FROM_BOTTOM(95.0f), + SCREEN_STRETCH_FROM_RIGHT(11.0f), SCREEN_STRETCH_FROM_BOTTOM(59.0f)), + CRGBA(235, 170, 50, 255)); + + CFont::SetScale(MENU_X(0.4f), MENU_Y(0.7f)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, FadeIn(255))); + + float nextX = MENU_X(30.0f), nextY = 95.0f; + wchar *text; +#ifdef MORE_LANGUAGES +#define TEXT_PIECE(key,extraSpace) \ + text = TheText.Get(key);\ + CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), text);\ + if (CFont::IsJapanese())\ + nextX += CFont::GetStringWidth_Jap(text) + MENU_X(extraSpace);\ + else\ + nextX += CFont::GetStringWidth(text, true) + MENU_X(extraSpace); +#else +#define TEXT_PIECE(key,extraSpace) \ + text = TheText.Get(key); CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), text); nextX += CFont::GetStringWidth(text, true) + MENU_X(extraSpace); +#endif + + TEXT_PIECE("FEC_MWF", 3.0f); + TEXT_PIECE("FEC_PGU", 1.0f); + TEXT_PIECE("FEC_IBT", 1.0f); + TEXT_PIECE("FEC_ZIN", 20.0f); + TEXT_PIECE("FEC_MWB", 3.0f); + TEXT_PIECE("FEC_PGD", 1.0f); + TEXT_PIECE("FEC_IBT", 1.0f); + CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), TheText.Get("FEC_ZOT")); nextX = MENU_X(30.0f); nextY -= 11.0f; + TEXT_PIECE("FEC_UPA", 2.0f); + TEXT_PIECE("FEC_DWA", 2.0f); + TEXT_PIECE("FEC_LFA", 2.0f); + TEXT_PIECE("FEC_RFA", 2.0f); + TEXT_PIECE("FEC_MSL", 1.0f); + TEXT_PIECE("FEC_IBT", 1.0f); + CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), TheText.Get("FEC_MOV")); nextX = MENU_X(30.0f); nextY -= 11.0f; + TEXT_PIECE("FEC_MSR", 2.0f); + TEXT_PIECE("FEC_IBT", 1.0f); + CFont::PrintString(nextX, SCREEN_SCALE_FROM_BOTTOM(nextY), TheText.Get("FEM_TWP")); +#undef TEXT_PIECE +} + +#undef ZOOM +#endif + +// rowIdx 99999 returns total numbers of rows. otherwise it returns 0. +int +CMenuManager::ConstructStatLine(int rowIdx) +{ +#define int_STAT_IS_FLOAT false +#define float_STAT_IS_FLOAT true +#define STAT_LINE_1(varType, left, right1) \ + do { \ + if(counter == rowIdx){ \ + varType a = right1; \ + BuildStatLine(left, &a, varType##_STAT_IS_FLOAT, nil); \ + return 0; \ + } counter++; \ + } while(0) + +#define STAT_LINE_2(varType, left, right1, right2) \ + do { \ + if(counter == rowIdx){ \ + varType a = right1; \ + varType b = right2; \ + BuildStatLine(left, &a, varType##_STAT_IS_FLOAT, &b); \ + return 0; \ + } counter++; \ + } while(0) + +#define TEXT_ON_LEFT_GXT(name) \ + do { \ + if(counter == rowIdx){ \ + BuildStatLine(name, nil, false, nil); \ + return 0; \ + } counter++; \ + } while(0) + +#define TEXT_ON_RIGHT(text) \ + do { \ + if(counter == rowIdx){ \ + gUString[0] = '\0'; \ + UnicodeStrcpy(gUString2, text); \ + return 0; \ + } counter++; \ + } while(0) + + // Like TEXT_ON_LEFT_GXT, but counter wasn't initialized yet I think + if (rowIdx == 0) { + BuildStatLine("PL_STAT", nil, false, nil); + return 0; + } + + int percentCompleted = (CStats::TotalProgressInGame == 0 ? 0 : + CStats::ProgressMade * 100.0f / (CGame::nastyGame ? CStats::TotalProgressInGame : CStats::TotalProgressInGame - 1)); + percentCompleted = Min(percentCompleted, 100); + + switch (rowIdx) { + // 0 is the heading text above + case 1: { + BuildStatLine("PER_COM", &percentCompleted, false, nil); + return 0; + } + case 2: { + BuildStatLine("NMISON", &CStats::MissionsGiven, false, nil); + return 0; + } + case 3: { + BuildStatLine("FEST_MP", &CStats::MissionsPassed, false, &CStats::TotalNumberMissions); + return 0; + } + } + int counter = 4; + + if (CGame::nastyGame) + STAT_LINE_2(int, "FEST_RP", CStats::NumberKillFrenziesPassed, CStats::TotalNumberKillFrenzies); + + CPlayerInfo &player = CWorld::Players[CWorld::PlayerInFocus]; + + // Hidden packages shouldn't be shown with percent +#ifdef FIX_BUGS + STAT_LINE_2(int, "PERPIC", player.m_nCollectedPackages, player.m_nTotalPackages); +#else + float packagesPercent = 0.0f; + if (player.m_nTotalPackages != 0) + packagesPercent = player.m_nCollectedPackages * 100.0f / player.m_nTotalPackages; + + STAT_LINE_2(int, "PERPIC", packagesPercent, 100); +#endif + STAT_LINE_2(int, "NOUNIF", CStats::NumberOfUniqueJumpsFound, CStats::TotalNumberOfUniqueJumps); + STAT_LINE_1(int, "DAYSPS", CStats::DaysPassed); + if (CGame::nastyGame) { + STAT_LINE_1(int, "PE_WAST", CStats::PeopleKilledByPlayer); + STAT_LINE_1(int, "PE_WSOT", CStats::PeopleKilledByOthers); + } + STAT_LINE_1(int, "CAR_EXP", CStats::CarsExploded); + STAT_LINE_1(int, "TM_BUST", CStats::TimesArrested); + STAT_LINE_1(int, "TM_DED", CStats::TimesDied); + STAT_LINE_1(int, "GNG_WST", CStats::PedsKilledOfThisType[PEDTYPE_GANG9] + CStats::PedsKilledOfThisType[PEDTYPE_GANG8] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG7] + CStats::PedsKilledOfThisType[PEDTYPE_GANG6] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG5] + CStats::PedsKilledOfThisType[PEDTYPE_GANG4] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG3] + CStats::PedsKilledOfThisType[PEDTYPE_GANG2] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG1]); + STAT_LINE_1(int, "DED_CRI", CStats::PedsKilledOfThisType[PEDTYPE_CRIMINAL]); + STAT_LINE_1(int, "HEL_DST", CStats::HelisDestroyed); + STAT_LINE_1(int, "KGS_EXP", CStats::KgsOfExplosivesUsed); + STAT_LINE_1(int, "ACCURA", (CStats::InstantHitsFiredByPlayer == 0 ? 0 : + CStats::InstantHitsHitByPlayer * 100.0f / CStats::InstantHitsFiredByPlayer)); + + if (CStats::ElBurroTime > 0) { + STAT_LINE_1(int, "ELBURRO", CStats::ElBurroTime); + } + if (CStats::Record4x4One > 0) { + STAT_LINE_1(int, "FEST_R1", CStats::Record4x4One); + } + if (CStats::Record4x4Two > 0) { + STAT_LINE_1(int, "FEST_R2", CStats::Record4x4Two); + } + if (CStats::Record4x4Three > 0) { + STAT_LINE_1(int, "FEST_R3", CStats::Record4x4Three); + } + if (CStats::Record4x4Mayhem > 0) { + STAT_LINE_1(int, "FEST_RM", CStats::Record4x4Mayhem); + } + if (CStats::LongestFlightInDodo > 0) { + STAT_LINE_1(int, "FEST_LF", CStats::LongestFlightInDodo); + } + if (CStats::TimeTakenDefuseMission > 0) { + STAT_LINE_1(int, "FEST_BD", CStats::TimeTakenDefuseMission); + } + STAT_LINE_1(int, "CAR_CRU", CStats::CarsCrushed); + + if (CStats::HighestScores[0] > 0) { + TEXT_ON_LEFT_GXT("FEST_BB"); + STAT_LINE_1(int, "FEST_H0", CStats::HighestScores[0]); + } + if (CStats::HighestScores[4] + CStats::HighestScores[3] + CStats::HighestScores[2] + CStats::HighestScores[1] > 0) { + TEXT_ON_LEFT_GXT("FEST_GC"); + } + if (CStats::HighestScores[1] > 0) { + STAT_LINE_1(int, "FEST_H1", CStats::HighestScores[1]); + } + if (CStats::HighestScores[2] > 0) { + STAT_LINE_1(int, "FEST_H2", CStats::HighestScores[2]); + } + if (CStats::HighestScores[3] > 0) { + STAT_LINE_1(int, "FEST_H3", CStats::HighestScores[3]); + } + if (CStats::HighestScores[4] > 0) { + STAT_LINE_1(int, "FEST_H4", CStats::HighestScores[4]); + } + + switch (m_PrefsLanguage) { + case LANGUAGE_AMERICAN: +#ifndef USE_MEASUREMENTS_IN_METERS + STAT_LINE_1(float, "FEST_DF", CStats::DistanceTravelledOnFoot * MILES_IN_METER); + STAT_LINE_1(float, "FEST_DC", CStats::DistanceTravelledInVehicle * MILES_IN_METER); + STAT_LINE_1(int, "MMRAIN", CStats::mmRain); + STAT_LINE_1(float, "MXCARD", CStats::MaximumJumpDistance * FEET_IN_METER); + STAT_LINE_1(float, "MXCARJ", CStats::MaximumJumpHeight * FEET_IN_METER); + break; +#endif + case LANGUAGE_FRENCH: + case LANGUAGE_GERMAN: + case LANGUAGE_ITALIAN: + case LANGUAGE_SPANISH: +#ifdef MORE_LANGUAGES + case LANGUAGE_POLISH: + case LANGUAGE_RUSSIAN: + case LANGUAGE_JAPANESE: +#endif + STAT_LINE_1(float, "FESTDFM", CStats::DistanceTravelledOnFoot); + STAT_LINE_1(float, "FESTDCM", CStats::DistanceTravelledInVehicle); + STAT_LINE_1(int, "MMRAIN", CStats::mmRain); + STAT_LINE_1(float, "MXCARDM", CStats::MaximumJumpDistance); + STAT_LINE_1(float, "MXCARJM", CStats::MaximumJumpHeight); + break; + default: + break; + } + + STAT_LINE_1(int, "MXFLIP", CStats::MaximumJumpFlips); + STAT_LINE_1(int, "MXJUMP", CStats::MaximumJumpSpins); + TEXT_ON_LEFT_GXT("BSTSTU"); + + switch (CStats::BestStuntJump) { + case 1: + TEXT_ON_RIGHT(TheText.Get("INSTUN")); + break; + case 2: + TEXT_ON_RIGHT(TheText.Get("PRINST")); + break; + case 3: + TEXT_ON_RIGHT(TheText.Get("DBINST")); + break; + case 4: + TEXT_ON_RIGHT(TheText.Get("DBPINS")); + break; + case 5: + TEXT_ON_RIGHT(TheText.Get("TRINST")); + break; + case 6: + TEXT_ON_RIGHT(TheText.Get("PRTRST")); + break; + case 7: + TEXT_ON_RIGHT(TheText.Get("QUINST")); + break; + case 8: + TEXT_ON_RIGHT(TheText.Get("PQUINS")); + break; + default: + TEXT_ON_RIGHT(TheText.Get("NOSTUC")); + break; + } + + STAT_LINE_1(int, "PASDRO", CStats::PassengersDroppedOffWithTaxi); + STAT_LINE_1(int, "MONTAX", CStats::MoneyMadeWithTaxi); + STAT_LINE_1(int, "FEST_LS", CStats::LivesSavedWithAmbulance); + STAT_LINE_1(int, "FEST_HA", CStats::HighestLevelAmbulanceMission); + STAT_LINE_1(int, "FEST_CC", CStats::CriminalsCaught); + STAT_LINE_1(int, "FEST_FE", CStats::FiresExtinguished); + STAT_LINE_1(int, "DAYPLC", CTimer::GetTimeInMilliseconds() + 100); + return counter; + +#undef STAT_LINE_1 +#undef STAT_LINE_2 +#undef TEXT_ON_LEFT_GXT +#undef TEXT_ON_RIGHT +#undef int_STAT_IS_FLOAT +#undef float_STAT_IS_FLOAT +} + +#undef GetBackJustUp +#undef GetBackJustDown +#undef ChangeScreen + +#endif diff --git a/src/core/Frontend.h b/src/core/Frontend.h new file mode 100644 index 0000000..6e6c40f --- /dev/null +++ b/src/core/Frontend.h @@ -0,0 +1,839 @@ +#pragma once +#ifdef PS2_MENU +#include "Frontend_PS2.h" +#else + +#include "Sprite2d.h" + +#ifdef PS2_LIKE_MENU +#define MENUHEADER_POS_X 50.0f +#define MENUHEADER_POS_Y 75.0f +#define MENUHEADER_HEIGHT 1.3f +#else +#define MENUHEADER_POS_X 35.0f +#define MENUHEADER_POS_Y 93.0f +#define MENUHEADER_HEIGHT 1.6f +#endif +#define MENUHEADER_WIDTH 0.84f + +#define MENU_X_MARGIN 40.0f +#define MENUACTION_POS_Y 60.0f +#define MENUACTION_SCALE_MULT 0.9f + +#define MENURADIO_ICON_SCALE 60.0f + +#define MENUSLIDER_X 256.0f +#define MENUSLIDER_UNK 256.0f + +#define MENUSLIDER_BARS 16 +#define MENUSLIDER_LOGICAL_BARS MENUSLIDER_BARS + +#define BIGTEXT_X_SCALE 0.75f // For FONT_HEADING +#define BIGTEXT_Y_SCALE 0.9f +#define MEDIUMTEXT_X_SCALE 0.55f // For FONT_HEADING +#define MEDIUMTEXT_Y_SCALE 0.8f +#define SMALLTEXT_X_SCALE 0.45f // used for FONT_HEADING and FONT_BANK, but looks off for HEADING +#define SMALLTEXT_Y_SCALE 0.7f +#define SMALLESTTEXT_X_SCALE 0.4f // used for both FONT_HEADING and FONT_BANK +#define SMALLESTTEXT_Y_SCALE 0.6f + +#define HELPER_TEXT_LEFT_MARGIN 320.0f +#define HELPER_TEXT_BOTTOM_MARGIN 120.0f + +#define PLAYERSETUP_LIST_TOP 28.0f +#define PLAYERSETUP_LIST_BOTTOM 125.0f +#define PLAYERSETUP_LIST_LEFT 200.0f +#define PLAYERSETUP_LIST_RIGHT 36.0f +#ifdef FIX_BUGS // See the scrollbar button drawing code +#define PLAYERSETUP_SCROLLBAR_WIDTH 19.0f +#else +#define PLAYERSETUP_SCROLLBAR_WIDTH 16.0f +#endif +#define PLAYERSETUP_SCROLLBUTTON_HEIGHT 17.0f +#define PLAYERSETUP_SCROLLBUTTON_TXD_DIMENSION 64 +#define PLAYERSETUP_SKIN_COLUMN_LEFT 220.0f +#define PLAYERSETUP_DATE_COLUMN_RIGHT 56.0f +#define PLAYERSETUP_LIST_BODY_TOP 47 +#define PLAYERSETUP_ROW_HEIGHT 9 + +#define STATS_SLIDE_Y_PER_SECOND 30.0f +#define STATS_ROW_HEIGHT 20.0f +#define STATS_ROW_X_MARGIN 50.0f +#define STATS_BOTTOM_MARGIN 135.0f +#define STATS_TOP_MARGIN 40.0f +#define STATS_TOP_DIMMING_AREA_LENGTH (93.0f - STATS_TOP_MARGIN) +#define STATS_BOTTOM_DIMMING_AREA_LENGTH 55.0f +#define STATS_PUT_BACK_TO_BOTTOM_Y 50.0f +#define STATS_RATING_X 24.0f +#define STATS_RATING_Y 20.0f + +#define BRIEFS_TOP_MARGIN 40.0f +#define BRIEFS_LINE_X 50.0f +#define BRIEFS_LINE_HEIGHT 60.0f + +#define CONTSETUP_STANDARD_ROW_HEIGHT 10.7f +#define CONTSETUP_CLASSIC_ROW_HEIGHT 9.0f +#define CONTSETUP_BOUND_HIGHLIGHT_HEIGHT 10 +#define CONTSETUP_BOUND_COLUMN_WIDTH 190.0f +#define CONTSETUP_LIST_HEADER_HEIGHT 20.0f +#define CONTSETUP_LIST_TOP 28.0f +#define CONTSETUP_LIST_RIGHT 18.0f +#define CONTSETUP_LIST_BOTTOM 120.0f +#define CONTSETUP_LIST_LEFT 18.0f +#define CONTSETUP_COLUMN_1_X 40.0f +#define CONTSETUP_COLUMN_2_X 210.0f +#define CONTSETUP_COLUMN_3_X (CONTSETUP_COLUMN_2_X + CONTSETUP_BOUND_COLUMN_WIDTH + 10.0f) +#define CONTSETUP_BACK_RIGHT 35.0f +#define CONTSETUP_BACK_BOTTOM 122.0f +#define CONTSETUP_BACK_HEIGHT 25.0f + +enum eFrontendSprites +{ + FE2_MAINPANEL_UL, + FE2_MAINPANEL_UR, + FE2_MAINPANEL_DL, + FE2_MAINPANEL_DR, + FE2_MAINPANEL_DR2, + FE2_TABACTIVE, + FE_ICONBRIEF, + FE_ICONSTATS, + FE_ICONCONTROLS, + FE_ICONSAVE, + FE_ICONAUDIO, + FE_ICONDISPLAY, + FE_ICONLANGUAGE, + FE_CONTROLLER, + FE_CONTROLLERSH, + FE_ARROWS1, + FE_ARROWS2, + FE_ARROWS3, + FE_ARROWS4, + FE_RADIO1, + FE_RADIO2, + FE_RADIO3, + FE_RADIO4, + FE_RADIO5, + FE_RADIO6, + FE_RADIO7, + FE_RADIO8, + FE_RADIO9, + + NUM_FE_SPRITES +}; + +enum eMenuSprites +{ + MENUSPRITE_CONNECTION, + MENUSPRITE_FINDGAME, + MENUSPRITE_HOSTGAME, + MENUSPRITE_MAINMENU, + MENUSPRITE_PLAYERSET, + MENUSPRITE_SINGLEPLAYER, + MENUSPRITE_MULTIPLAYER, + MENUSPRITE_DMALOGO, + MENUSPRITE_GTALOGO, + MENUSPRITE_RSTARLOGO, + MENUSPRITE_GAMESPY, + MENUSPRITE_MOUSE, + MENUSPRITE_MOUSET, + MENUSPRITE_MP3LOGO, + MENUSPRITE_DOWNOFF, + MENUSPRITE_DOWNON, + MENUSPRITE_UPOFF, + MENUSPRITE_UPON, + MENUSPRITE_GTA3LOGO, + MENUSPRITE_UNUSED, + NUM_MENU_SPRITES +}; + +enum eSaveSlot +{ + SAVESLOT_NONE, + SAVESLOT_0, + SAVESLOT_1, + SAVESLOT_2, + SAVESLOT_3, + SAVESLOT_4, + SAVESLOT_5, + SAVESLOT_6, + SAVESLOT_7, + SAVESLOT_8, + SAVESLOT_LABEL = 36, +}; + +#ifdef MENU_MAP +enum MapSprites +{ + MAPMID1, + MAPMID2, + MAPMID3, + MAPBOT1, + MAPBOT2, + MAPBOT3, + MAPTOP1, + MAPTOP2, + MAPTOP3, + NUM_MAP_SPRITES +}; +#endif + +enum eMenuScreen +{ + MENUPAGE_DISABLED = -1, + MENUPAGE_NONE = 0, + MENUPAGE_STATS = 1, + MENUPAGE_NEW_GAME = 2, + MENUPAGE_BRIEFS = 3, + MENUPAGE_CONTROLLER_SETTINGS = 4, + MENUPAGE_SOUND_SETTINGS = 5, + MENUPAGE_DISPLAY_SETTINGS = 6, + MENUPAGE_LANGUAGE_SETTINGS = 7, + MENUPAGE_CHOOSE_LOAD_SLOT = 8, + MENUPAGE_CHOOSE_DELETE_SLOT = 9, + MENUPAGE_NEW_GAME_RELOAD = 10, + MENUPAGE_LOAD_SLOT_CONFIRM = 11, + MENUPAGE_DELETE_SLOT_CONFIRM = 12, + MENUPAGE_NO_MEMORY_CARD = 13, // hud adjustment page in mobile + MENUPAGE_LOADING_IN_PROGRESS = 14, + MENUPAGE_DELETING_IN_PROGRESS = 15, + MENUPAGE_PS2_LOAD_FAILED = 16, + MENUPAGE_DELETE_FAILED = 17, + MENUPAGE_DEBUG_MENU = 18, + MENUPAGE_MEMORY_CARD_DEBUG = 19, + MENUPAGE_MEMORY_CARD_TEST = 20, + MENUPAGE_MULTIPLAYER_MAIN = 21, + MENUPAGE_PS2_SAVE_FAILED = 22, + MENUPAGE_PS2_SAVE_FAILED_2 = 23, + MENUPAGE_SAVE = 24, + MENUPAGE_NO_MEMORY_CARD_2 = 25, + MENUPAGE_CHOOSE_SAVE_SLOT = 26, + MENUPAGE_SAVE_OVERWRITE_CONFIRM = 27, + MENUPAGE_MULTIPLAYER_MAP = 28, + MENUPAGE_MULTIPLAYER_CONNECTION = 29, + MENUPAGE_MULTIPLAYER_FIND_GAME = 30, + MENUPAGE_MULTIPLAYER_MODE = 31, + MENUPAGE_MULTIPLAYER_CREATE = 32, + MENUPAGE_MULTIPLAYER_START = 33, + MENUPAGE_SKIN_SELECT_OLD = 34, + MENUPAGE_CONTROLLER_PC = 35, + MENUPAGE_CONTROLLER_PC_OLD1 = 36, + MENUPAGE_CONTROLLER_PC_OLD2 = 37, + MENUPAGE_CONTROLLER_PC_OLD3 = 38, + MENUPAGE_CONTROLLER_PC_OLD4 = 39, + MENUPAGE_CONTROLLER_DEBUG = 40, + MENUPAGE_OPTIONS = 41, + MENUPAGE_EXIT = 42, + MENUPAGE_SAVING_IN_PROGRESS = 43, + MENUPAGE_SAVE_SUCCESSFUL = 44, + MENUPAGE_DELETING = 45, + MENUPAGE_DELETE_SUCCESS = 46, + MENUPAGE_SAVE_FAILED = 47, + MENUPAGE_LOAD_FAILED = 48, + MENUPAGE_LOAD_FAILED_2 = 49, + MENUPAGE_FILTER_GAME = 50, + MENUPAGE_START_MENU = 51, + MENUPAGE_PAUSE_MENU = 52, + MENUPAGE_CHOOSE_MODE = 53, + MENUPAGE_SKIN_SELECT = 54, + MENUPAGE_KEYBOARD_CONTROLS = 55, + MENUPAGE_MOUSE_CONTROLS = 56, + MENUPAGE_MISSION_RETRY = 57, +#ifdef CUSTOM_FRONTEND_OPTIONS + +#ifdef MENU_MAP + MENUPAGE_MAP = 58, +#endif +#ifdef GRAPHICS_MENU_OPTIONS + MENUPAGE_GRAPHICS_SETTINGS, +#endif +#ifdef DETECT_JOYSTICK_MENU + MENUPAGE_DETECT_JOYSTICK, +#endif + +#endif + MENUPAGE_UNK, // originally 58. Custom screens are inserted above, because last screen in CMenuScreens should always be empty to make CFO work + MENUPAGES + +}; + +enum eMenuAction +{ +#ifdef CUSTOM_FRONTEND_OPTIONS + MENUACTION_CFO_SLIDER = -3, + MENUACTION_CFO_SELECT = -2, + MENUACTION_CFO_DYNAMIC = -1, +#endif + MENUACTION_NOTHING, + MENUACTION_LABEL, + MENUACTION_CHANGEMENU, + MENUACTION_CTRLVIBRATION, + MENUACTION_CTRLCONFIG, + MENUACTION_CTRLDISPLAY, + MENUACTION_FRAMESYNC, + MENUACTION_FRAMELIMIT, + MENUACTION_TRAILS, + MENUACTION_SUBTITLES, + MENUACTION_WIDESCREEN, + MENUACTION_BRIGHTNESS, + MENUACTION_DRAWDIST, + MENUACTION_MUSICVOLUME, + MENUACTION_SFXVOLUME, + MENUACTION_UNK15, + MENUACTION_RADIO, + MENUACTION_LANG_ENG, + MENUACTION_LANG_FRE, + MENUACTION_LANG_GER, + MENUACTION_LANG_ITA, + MENUACTION_LANG_SPA, + MENUACTION_POPULATESLOTS_CHANGEMENU, + MENUACTION_CHECKSAVE, + MENUACTION_UNK24, + MENUACTION_NEWGAME, + MENUACTION_RELOADIDE, + MENUACTION_RELOADIPL, + MENUACTION_SETDBGFLAG, + MENUACTION_SWITCHBIGWHITEDEBUGLIGHT, + MENUACTION_PEDROADGROUPS, + MENUACTION_CARROADGROUPS, + MENUACTION_COLLISIONPOLYS, + MENUACTION_REGMEMCARD1, + MENUACTION_TESTFORMATMEMCARD1, + MENUACTION_TESTUNFORMATMEMCARD1, + MENUACTION_CREATEROOTDIR, + MENUACTION_CREATELOADICONS, + MENUACTION_FILLWITHGUFF, + MENUACTION_SAVEONLYTHEGAME, + MENUACTION_SAVEGAME, + MENUACTION_SAVEGAMEUNDERGTA, + MENUACTION_CREATECOPYPROTECTED, + MENUACTION_TESTSAVE, + MENUACTION_TESTLOAD, + MENUACTION_TESTDELETE, + MENUACTION_PARSEHEAP, + MENUACTION_SHOWCULL, + MENUACTION_MEMCARDSAVECONFIRM, + MENUACTION_RESUME_FROM_SAVEZONE, + MENUACTION_UNK50, + MENUACTION_DEBUGSTREAM, + MENUACTION_MPMAP_LIBERTY, + MENUACTION_MPMAP_REDLIGHT, + MENUACTION_MPMAP_CHINATOWN, + MENUACTION_MPMAP_TOWER, + MENUACTION_MPMAP_SEWER, + MENUACTION_MPMAP_INDUSTPARK, + MENUACTION_MPMAP_DOCKS, + MENUACTION_MPMAP_STAUNTON, + MENUACTION_MPMAP_DEATHMATCH1, + MENUACTION_MPMAP_DEATHMATCH2, + MENUACTION_MPMAP_TEAMDEATH1, + MENUACTION_MPMAP_TEAMDEATH2, + MENUACTION_MPMAP_STASH, + MENUACTION_MPMAP_CAPTURE, + MENUACTION_MPMAP_RATRACE, + MENUACTION_MPMAP_DOMINATION, + MENUACTION_STARTMP, + MENUACTION_UNK69, + MENUACTION_UNK70, + MENUACTION_FINDMP, + MENUACTION_KEYBOARDCTRLS, + MENUACTION_UNK73, + MENUACTION_INITMP, + MENUACTION_MP_PLAYERCOLOR, + MENUACTION_MP_PLAYERNAME, + MENUACTION_MP_GAMENAME, + MENUACTION_GETKEY, + MENUACTION_SHOWHEADBOB, + MENUACTION_UNK80, + MENUACTION_INVVERT, + MENUACTION_CANCELGAME, + MENUACTION_MP_PLAYERNUMBER, + MENUACTION_MOUSESENS, + MENUACTION_CHECKMPGAMES, + MENUACTION_CHECKMPPING, + MENUACTION_MP_SERVER, + MENUACTION_MP_MAP, + MENUACTION_MP_GAMETYPE, + MENUACTION_MP_LAN, + MENUACTION_MP_INTERNET, + MENUACTION_RESUME, + MENUACTION_DONTCANCEL, + MENUACTION_SCREENRES, + MENUACTION_AUDIOHW, + MENUACTION_SPEAKERCONF, + MENUACTION_PLAYERSETUP, + MENUACTION_RESTOREDEF, + MENUACTION_CTRLMETHOD, + MENUACTION_DYNAMICACOUSTIC, + MENUACTION_LOADRADIO, + MENUACTION_MOUSESTEER, + MENUACTION_UNK103, + MENUACTION_UNK104, + MENUACTION_UNK105, + MENUACTION_UNK106, + MENUACTION_UNK107, + MENUACTION_UNK108, + MENUACTION_UNK109, + MENUACTION_UNK110, + MENUACTION_UNK111, + MENUACTION_UNK112, + MENUACTION_REJECT_RETRY, + MENUACTION_UNK114, +//#ifdef ANISOTROPIC_FILTERING +// MENUACTION_MIPMAPS, +// MENUACTION_TEXTURE_FILTERING, +//#endif +}; + +enum eCheckHover +{ + HOVEROPTION_0, + HOVEROPTION_1, + HOVEROPTION_RANDOM_ITEM, + HOVEROPTION_3, + HOVEROPTION_4, + HOVEROPTION_5, + HOVEROPTION_6, + HOVEROPTION_7, + HOVEROPTION_8, + HOVEROPTION_BACK, // also layer in controller setup and skin menu + HOVEROPTION_10, + HOVEROPTION_11, + HOVEROPTION_OVER_SCROLL_UP, + HOVEROPTION_OVER_SCROLL_DOWN, + HOVEROPTION_CLICKED_SCROLL_UP, + HOVEROPTION_CLICKED_SCROLL_DOWN, + HOVEROPTION_HOLDING_SCROLLBAR, + HOVEROPTION_PAGEUP, + HOVEROPTION_PAGEDOWN, + HOVEROPTION_LIST, // also layer in controller setup and skin menu + HOVEROPTION_SKIN, + HOVEROPTION_USESKIN, // also layer in controller setup and skin menu + HOVEROPTION_RADIO_0, + HOVEROPTION_RADIO_1, + HOVEROPTION_RADIO_2, + HOVEROPTION_RADIO_3, + HOVEROPTION_RADIO_4, + HOVEROPTION_RADIO_5, + HOVEROPTION_RADIO_6, + HOVEROPTION_RADIO_7, + HOVEROPTION_RADIO_8, + HOVEROPTION_RADIO_9, + HOVEROPTION_INCREASE_BRIGHTNESS, + HOVEROPTION_DECREASE_BRIGHTNESS, + HOVEROPTION_INCREASE_DRAWDIST, + HOVEROPTION_DECREASE_DRAWDIST, + HOVEROPTION_INCREASE_MUSICVOLUME, + HOVEROPTION_DECREASE_MUSICVOLUME, + HOVEROPTION_INCREASE_SFXVOLUME, + HOVEROPTION_DECREASE_SFXVOLUME, + HOVEROPTION_INCREASE_MOUSESENS, + HOVEROPTION_DECREASE_MOUSESENS, +#ifdef CUSTOM_FRONTEND_OPTIONS + HOVEROPTION_INCREASE_CFO_SLIDER, + HOVEROPTION_DECREASE_CFO_SLIDER, +#endif + HOVEROPTION_NOT_HOVERING, +}; + +enum +{ + NUM_MENUROWS = 18, +}; + +enum eControlMethod +{ + CONTROL_STANDARD = 0, + CONTROL_CLASSIC, +}; + +// Why?? +enum ControllerSetupColumn +{ + CONTSETUP_PED_COLUMN = 0, + CONTSETUP_VEHICLE_COLUMN = 14, +}; + +struct tSkinInfo +{ + int32 skinId; + char skinNameDisplayed[256]; + char skinNameOriginal[256]; + char date[256]; + tSkinInfo *nextSkin; +}; + +struct BottomBarOption +{ + char name[8]; + int32 screenId; +}; + +#ifndef CUSTOM_FRONTEND_OPTIONS +struct CMenuScreen +{ + char m_ScreenName[8]; + int32 unk; // 2 on MENUPAGE_MULTIPLAYER_START, 1 on everywhere else, 0 on unused. + int32 m_PreviousPage[2]; // eMenuScreen + int32 m_ParentEntry[2]; // row + + struct CMenuEntry + { + int32 m_Action; // eMenuAction + char m_EntryName[8]; + int32 m_SaveSlot; // eSaveSlot + int32 m_TargetMenu; // eMenuScreen + } m_aEntries[NUM_MENUROWS]; +}; +extern CMenuScreen aScreens[MENUPAGES]; +#else +#include "frontendoption.h" +struct CCustomScreenLayout { + eMenuSprites sprite; + int columnWidth; + int headerHeight; + int lineHeight; + int8 font; + int8 alignment; + bool showLeftRightHelper; + float fontScaleX; + float fontScaleY; +}; + +struct CCFO +{ + void *value; + const char *saveCat; + const char *save; +}; + +struct CCFOSelect : CCFO +{ + char** rightTexts; + int8 numRightTexts; + bool onlyApplyOnEnter; + int8 displayedValue; // only if onlyApplyOnEnter enabled for now + int8 lastSavedValue; // only if onlyApplyOnEnter enabled + ChangeFunc changeFunc; + bool disableIfGameLoaded; + + CCFOSelect() {}; + CCFOSelect(int8* value, const char* saveCat, const char* save, const char** rightTexts, int8 numRightTexts, bool onlyApplyOnEnter, ChangeFunc changeFunc = nil, bool disableIfGameLoaded = false){ + this->value = value; + if (value) + this->lastSavedValue = this->displayedValue = *value; + + this->saveCat = saveCat; + this->save = save; + this->rightTexts = (char**)rightTexts; + this->numRightTexts = numRightTexts; + this->onlyApplyOnEnter = onlyApplyOnEnter; + this->changeFunc = changeFunc; + this->disableIfGameLoaded = disableIfGameLoaded; + } +}; + +// Value is float in here +struct CCFOSlider : CCFO +{ + ChangeFuncFloat changeFunc; + float min; + float max; + + CCFOSlider() {}; + CCFOSlider(float* value, const char* saveCat, const char* save, float min, float max, ChangeFuncFloat changeFunc = nil){ + this->value = value; + this->saveCat = saveCat; + this->save = save; + this->changeFunc = changeFunc; + this->min = min; + this->max = max; + } +}; + +struct CCFODynamic : CCFO +{ + DrawFunc drawFunc; + ButtonPressFunc buttonPressFunc; + + CCFODynamic() {}; + CCFODynamic(int8* value, const char* saveCat, const char* save, DrawFunc drawFunc, ButtonPressFunc buttonPressFunc){ + this->value = value; + this->saveCat = saveCat; + this->save = save; + this->drawFunc = drawFunc; + this->buttonPressFunc = buttonPressFunc; + } +}; + +struct CMenuScreenCustom +{ + char m_ScreenName[8]; + int32 m_PreviousPage[2]; // eMenuScreen + CCustomScreenLayout *layout; + ReturnPrevPageFunc returnPrevPageFunc; + + struct CMenuEntry + { + int32 m_Action; // eMenuAction - below zero is CFO + char m_EntryName[8]; + struct { + union { + CCFO *m_CFO; // for initializing + CCFOSelect *m_CFOSelect; + CCFODynamic *m_CFODynamic; + CCFOSlider *m_CFOSlider; + }; + int32 m_SaveSlot; // eSaveSlot + int32 m_TargetMenu; // eMenuScreen + }; + } m_aEntries[NUM_MENUROWS]; +}; +extern CMenuScreenCustom aScreens[MENUPAGES]; +#endif + +class CMenuManager +{ +public: + int32 m_nPrefsVideoMode; + int32 m_nDisplayVideoMode; + int8 m_nPrefsAudio3DProviderIndex; + bool m_bKeyChangeNotProcessed; + char m_aSkinName[256]; + int32 m_nHelperTextMsgId; + bool m_bLanguageLoaded; + bool m_bMenuActive; + bool m_bMenuStateChanged; + bool m_bWaitingForNewKeyBind; + bool m_bWantToRestart; + bool m_bFirstTime; + bool m_bGameNotLoaded; + int32 m_nMousePosX; + int32 m_nMousePosY; + int32 m_nMouseTempPosX; + int32 m_nMouseTempPosY; + bool m_bShowMouse; + tSkinInfo m_pSkinListHead; + tSkinInfo *m_pSelectedSkin; + int32 m_nFirstVisibleRowOnList; + float m_nScrollbarTopMargin; + int32 m_nTotalListRow; + int32 m_nSkinsTotal; + char _unk0[4]; + int32 m_nSelectedListRow; + bool m_bSkinsEnumerated; + bool m_bQuitGameNoCD; + bool m_bRenderGameInMenu; + bool m_bSaveMenuActive; + bool m_bWantToLoad; + char field_455; + bool m_bStartWaitingForKeyBind; + bool m_bSpritesLoaded; + CSprite2d m_aFrontEndSprites[NUM_FE_SPRITES]; + CSprite2d m_aMenuSprites[NUM_MENU_SPRITES]; + int32 field_518; + int32 m_nMenuFadeAlpha; + bool m_bPressedPgUpOnList; + bool m_bPressedPgDnOnList; + bool m_bPressedUpOnList; + bool m_bPressedDownOnList; + bool m_bPressedScrollButton; + int32 m_CurrCntrlAction; + char _unk1[4]; + int32 m_nSelectedContSetupColumn; + bool m_bKeyIsOK; + bool field_535; + int8 m_nCurrExLayer; + int32 m_nHelperTextAlpha; + int32 m_nMouseOldPosX; + int32 m_nMouseOldPosY; + int32 m_nHoverOption; + int32 m_nCurrScreen; + int32 m_nCurrOption; + int32 m_nOptionMouseHovering; + int32 m_nPrevScreen; + uint32 field_558; + int32 m_nCurrSaveSlot; + int32 m_nScreenChangeDelayTimer; + +#ifdef IMPROVED_VIDEOMODE + int32 m_nPrefsWidth; + int32 m_nPrefsHeight; + int32 m_nPrefsDepth; + int32 m_nPrefsWindowed; + int32 m_nPrefsSubsystem; + int32 m_nSelectedScreenMode; +#endif +#ifdef MULTISAMPLING + static int8 m_nPrefsMSAALevel; + static int8 m_nDisplayMSAALevel; +#endif + + enum LANGUAGE + { + LANGUAGE_AMERICAN, + LANGUAGE_FRENCH, + LANGUAGE_GERMAN, + LANGUAGE_ITALIAN, + LANGUAGE_SPANISH, +#ifdef MORE_LANGUAGES + LANGUAGE_POLISH, + LANGUAGE_RUSSIAN, + LANGUAGE_JAPANESE, +#endif + }; +public: + bool GetIsMenuActive() {return !!m_bMenuActive;} + +public: + static int32 OS_Language; + static int8 m_PrefsUseVibration; + static int8 m_DisplayControllerOnFoot; + static int8 m_PrefsUseWideScreen; + static int8 m_PrefsRadioStation; + static int8 m_PrefsVsync; + static int8 m_PrefsVsyncDisp; + static int8 m_PrefsFrameLimiter; + static int8 m_PrefsShowSubtitles; + static int8 m_PrefsSpeakers; + static int32 m_ControlMethod; + static int8 m_PrefsDMA; + static int32 m_PrefsLanguage; + static int32 m_PrefsBrightness; + static float m_PrefsLOD; + static int8 m_bFrontEnd_ReloadObrTxtGxt; + static int32 m_PrefsMusicVolume; + static int32 m_PrefsSfxVolume; + static char m_PrefsSkinFile[256]; + static int32 m_KeyPressedCode; + + static bool m_bStartUpFrontEndRequested; + static bool m_bShutDownFrontEndRequested; + static bool m_PrefsAllowNastyGame; + + static uint8 m_PrefsStereoMono; + static int32 m_SelectedMap; + static int32 m_SelectedGameType; + static uint8 m_PrefsPlayerRed; + static uint8 m_PrefsPlayerGreen; + static uint8 m_PrefsPlayerBlue; + +#ifdef CUTSCENE_BORDERS_SWITCH + static bool m_PrefsCutsceneBorders; +#endif + +#ifndef MASTER + static bool m_PrefsMarketing; + static bool m_PrefsDisableTutorials; +#endif // !MASTER + +#ifdef MENU_MAP + static bool bMenuMapActive; + static float fMapSize; + static float fMapCenterY; + static float fMapCenterX; + static CSprite2d m_aMapSprites[NUM_MAP_SPRITES]; + void PrintMap(); +#endif + +#ifdef NO_ISLAND_LOADING + enum + { + ISLAND_LOADING_LOW = 0, + ISLAND_LOADING_MEDIUM, + ISLAND_LOADING_HIGH + }; + + static int8 m_PrefsIslandLoading; + + #define ISLAND_LOADING_IS(p) if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_##p) + #define ISLAND_LOADING_ISNT(p) if (CMenuManager::m_PrefsIslandLoading != CMenuManager::ISLAND_LOADING_##p) +#else + #define ISLAND_LOADING_IS(p) + #define ISLAND_LOADING_ISNT(p) +#endif + +#ifdef GAMEPAD_MENU + enum + { + CONTROLLER_DUALSHOCK2 = 0, + CONTROLLER_DUALSHOCK3, + CONTROLLER_DUALSHOCK4, + CONTROLLER_XBOX360, + CONTROLLER_XBOXONE, + CONTROLLER_NINTENDO_SWITCH, + }; + + static int8 m_PrefsControllerType; +#endif + +public: + static void BuildStatLine(Const char *text, void *stat, bool itsFloat, void *stat2); + static void CentreMousePointer(); + void CheckCodesForControls(int); + bool CheckHover(int x1, int x2, int y1, int y2); + void CheckSliderMovement(int); + int CostructStatLine(int); + void DisplayHelperText(); + int DisplaySlider(float, float, float, float, float, float); + void DoSettingsBeforeStartingAGame(); + void Draw(); + void DrawControllerBound(int32, int32, int32, int8); + void DrawControllerScreenExtraText(int, int, int); + void DrawControllerSetupScreen(); + void DrawFrontEnd(); + void DrawFrontEndNormal(); +#ifdef PS2_SAVE_DIALOG + void DrawFrontEndSaveZone(); +#endif + void DrawPlayerSetupScreen(); + int FadeIn(int alpha); + void FilterOutColorMarkersFromString(wchar*, CRGBA &); + int GetStartOptionsCntrlConfigScreens(); + static void InitialiseChangedLanguageSettings(); + void LoadAllTextures(); + void LoadSettings(); + void MessageScreen(const char *); + void PickNewPlayerColour(); + void PrintBriefs(); + static void PrintErrorMessage(); + void PrintStats(); + void Process(); + void ProcessButtonPresses(); + void ProcessOnOffMenuOptions(); + static void RequestFrontEndShutDown(); + static void RequestFrontEndStartUp(); + void ResetHelperText(); + void SaveLoadFileError_SetUpErrorScreen(); + void SaveSettings(); + void SetHelperText(int text); + void ShutdownJustMenu(); + float StretchX(float); + float StretchY(float); + void SwitchMenuOnAndOff(); + void UnloadTextures(); + void WaitForUserCD(); + void PrintController(); + int GetNumOptionsCntrlConfigScreens(); + int ConstructStatLine(int); + + // Those are either inlined in game, not in function yet, or I can't believe that they're not inlined. + // Names were made up by me. + void ThingsToDoBeforeGoingBack(); + void ScrollUpListByOne(); + void ScrollDownListByOne(); + void PageUpList(bool); + void PageDownList(bool); + int8 GetPreviousPageOption(); + void ProcessList(bool &goBack, bool &optionSelected); +#ifdef GAMEPAD_MENU + void LoadController(int8 type); +#endif +}; + +#ifndef IMPROVED_VIDEOMODE +VALIDATE_SIZE(CMenuManager, 0x564); +#endif + +extern CMenuManager FrontEndMenuManager; + +#endif diff --git a/src/core/FrontendTriggers.h b/src/core/FrontendTriggers.h new file mode 100644 index 0000000..44bae54 --- /dev/null +++ b/src/core/FrontendTriggers.h @@ -0,0 +1,1393 @@ +CTriggerCaller MemCardAccessTriggerCaller; + +void InitialiseTextsInMenuControllerInCar(CMenuPictureAndText *widget, CMenuManager::CONTRCONFIG cont); +void InitialiseTextsInMenuControllerOnFoot(CMenuPictureAndText *widget, CMenuManager::CONTRCONFIG cont); +void TriggerSave_BackToMainMenu(CMenuMultiChoiceTriggered *widget); +void TriggerSave_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSave_LoadGameLoadGameSelect(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSave_DeleteGameDeleteGameSelect(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSaveZone_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSaveZone_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget); +void TriggerSaveZone_SaveSlots(CMenuMultiChoiceTwoLinesTriggered *widget); + +void +DisplayWarningControllerMsg() +{ + if ( CPad::bDisplayNoControllerMessage ) + { + CSprite2d::DrawRect(CRect(X(20.0f), Y(140.0f), X(620.0f), Y(328.0)), CRGBA(64, 16, 16, 224)); // CRect(20.0f, 160.0f, 620.0f, 374.857117f) + + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(0.84f), Y(1.26f)); // 1.440000 + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + + CPlaceableShText text; + text.SetPosition(X(60.0f), Y(180.0f), false); // 205.714294 + text.SetColor(CRGBA(152, 152, 152, 255)); + text.m_text = TheText.Get("NOCONTE"); // Please re-insert the analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2) in controller port 1 to continue + text.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR*2.0f); + text.SetAlpha(255); + text.DrawShWrap(0.0f, 0.0f, X(600.0f+SHADOW_VECTOR.x), YF(600.0f)); //TODO check + + CFont::DrawFonts(); + } + else if ( CPad::bObsoleteControllerMessage ) + { + CSprite2d::DrawRect(CRect(X(20.0f), Y(140.0f), X(620.0f), Y(328.0)), CRGBA(64, 16, 16, 224)); // CRect(20.0f, 160.0f, 620.0f, 374.857117f) + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(0.84f), Y(1.26f)); // 1.440000 + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + + CPlaceableShText text; + text.SetPosition(X(60.0f), Y(180.0f), false); // 205.714294 + text.SetColor(CRGBA(152, 152, 152, 255)); + text.m_text = TheText.Get("WRCONTE"); // Please re-insert the analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2) in controller port 1 to continue + text.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR*2.0f); + text.SetAlpha(255); + text.DrawShWrap(0.0f, 0.0f, X(600.0f+SHADOW_VECTOR.x), YF(600.0f)); //TODO check + + CFont::DrawFonts(); + } + +} + +void +TriggerMCSUM_Yes(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + bMemoryCardStartUpMenus_ExitNow = true; +} + +int32 nStatLinesIndex; +wchar aStatLines[50+1][50]; +wchar *PrintStatLine(char const *text, void *stat, unsigned char itsFloat, void *stat2) +{ + if (text && stat && nStatLinesIndex < 50) + { + char line [64]; + wchar uline[64]; + + memset(line, 0, sizeof(line)); + memset(uline, 0, sizeof(uline)); + + if (stat2) + { + if ( itsFloat ) + sprintf(line, " %.2f %s %.2f", *(float*)stat, UnicodeToAscii(TheText.Get("FEST_OO")), *(float*)stat2); + else + sprintf(line, " %d %s %d", *(int32*)stat, UnicodeToAscii(TheText.Get("FEST_OO")), *(int32*)stat2); + } + else + { + if (itsFloat) + sprintf(line, " %.2f", *(float*)stat); + else + sprintf(line, " %d", *(int32*)stat); + } + + wchar *pStatLine = aStatLines[nStatLinesIndex++]; + + AsciiToUnicode(line, uline); + UnicodeStrcpy(pStatLine, uline); + + return pStatLine; + } + + return nil; +} + +void +DisplayMemoryCardAccessMsg(wchar *msg, CRGBA const &color) +{ + CSprite2d::DrawRect(CRect(X(70.0f), Y(100.0f), X(570.0f), Y(270.0f)), color); + + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(MEMCARD_ACCESS_MSG_SIZE_X), Y(MEMCARD_ACCESS_MSG_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(90.0f)); // 550.0f + CFont::SetCentreOn(); + CFont::SetCentreSize(SCRW-X(180.0f)); // 460.0f + + CPlaceableShText text; + + text.SetPosition(X(320.0f), Y(120.0f), false); // 137.142868 + text.SetColor(CRGBA(200, 200, 200, 255)); + text.m_text = msg; + + text.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + text.SetAlpha(255); + text.Draw(0.0f, 0.0f); + + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +FillMenuWithMemCardFileListing(CMenuMultiChoiceTwoLinesTriggered *widget, void (*cancelTrigger)(CMenuMultiChoiceTwoLinesTriggered *), void (*selectTrigger)(CMenuMultiChoiceTwoLinesTriggered *), wchar *text, int32 y, int32 height, int32 offset) +{ + if ( widget ) + { + int32 selected = 0; + if ( bMemoryCardSpecialZone ) + selected = widget->m_cursor != -1 ? widget->m_cursor : 0; + + widget->DeactivateMenu(); // TODO check + widget->m_numOptions = 0; + widget->AddTitle(nil, 0.0f, 0.0f, 0); + + TheMemoryCard.PopulateSlotInfo(CARD_ONE); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS) + { + widget->AddOption(TheText.Get("FES_CAN"), 0.0f, YF(y), cancelTrigger, 0, 0); + + FrontEndMenuManager.field_3C = 0; + + y += offset; + + char buff[100]; + + for ( int32 i = 0; i < CMemoryCard::MAX_SLOTS; i++ ) + { + // SAVE FILE + sprintf(buff, "%s %d ", UnicodeToAscii(TheText.Get("FES_SLO")), i+1); + AsciiToUnicode(buff, MemoryCard_FileNames[i]); + + wchar *datetime = nil; + + switch ( TheMemoryCard.GetInfoOnSpecificSlot(i) ) + { + case CMemoryCard::SLOT_CORRUPTED: + { + UnicodeStrcat(MemoryCard_FileNames[i], TheText.Get("FES_ISC")); // IS CORRUPTED + datetime = TheMemoryCard.GetDateAndTimeOfSavedGame(i); + break; + } + case CMemoryCard::SLOT_PRESENT: + { + if ( TheMemoryCard.GetNameOfSavedGame(i) != nil ) + { + UnicodeStrcpy(MemoryCard_FileNames[i], TheMemoryCard.GetNameOfSavedGame(i)); + datetime = TheMemoryCard.GetDateAndTimeOfSavedGame(i); + } + else + { + UnicodeStrcpy(MemoryCard_FileNames[i], TheText.Get("FES_SAG")); // PRESENT + datetime = TheMemoryCard.GetDateAndTimeOfSavedGame(i); + } + break; + } + case CMemoryCard::SLOT_NOTPRESENT: + { + UnicodeStrcat(MemoryCard_FileNames[i], TheText.Get("FES_ISF")); + datetime = TheMemoryCard.GetDateAndTimeOfSavedGame(i); + break; + } + } + + widget->AddOption(MemoryCard_FileNames[i], 0.0f, YF(y), datetime, 0.0f, YF(float(y)+(0.44f*height)), selectTrigger, 0, 0); + y += height; + } + } + else + { + if ( !gErrorSampleTriggered ) + { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + gErrorSampleTriggered = true; + } + + // Cancel + widget->AddOption(TheText.Get("FES_CAN"), 0.0f, YF(y+(height*2)), cancelTrigger, 0, 0); + + FrontEndMenuManager.field_3C = 1; + + y += height; + + TheMemoryCard.PopulateErrorMessage(); + + // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + if ( TheMemoryCard.GetErrorMessage()) + widget->AddTitle(TheMemoryCard.GetErrorMessage(), 0.0f, YF(y), 0); + else + widget->AddTitle(TheText.Get("FES_GME"), 0.0f, YF(y), 0); + } + + widget->SetMenuSelection(0); + widget->ActivateMenu(1); + + if ( bMemoryCardSpecialZone ) + { + widget->GoFirst(); + + for ( int32 i = 0; i < selected; i++ ) + widget->GoNext(); + } + } +} + +void +TriggerSaveZone_FormatFailedOK(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + pActiveMenuPage = &MenuPageSaveZone_SaveGame; +} + +void +TriggerSaveZone_BackToMainMenu(CMenuMultiChoiceTriggered *widget) +{ + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + pActiveMenuPage = &MenuPageSaveZone_SaveGame; +} + +void +TriggerSaveZone_QuitMenu(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + FrontEndMenuManager.m_bMenuActive = false; + FrontEndMenuManager.m_bInSaveZone = false; + CTimer::EndUserPause(); + } +} + +void +TriggerSaveZone_FormatCard(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS) + { + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_AFO"), X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(5.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else if ( TheMemoryCard.GetError() != CMemoryCard::ERR_NOFORMAT) + { + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheMemoryCard.GetErrorMessage(), X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(15.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSaveZone_FormatCard, widget); + else + { + // Formatting Memory Card (PS2) in MEMORY CARD slot 1. Please do not remove the Memory Card (PS2), reset or switch off the console. + DisplayMemoryCardAccessMsg(TheText.Get("FEFD_WR"), CRGBA(200, 50, 50, 192)); + TheMemoryCard.FormatCard(CARD_ONE); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + pActiveMenuPage = &MenuPageSaveZone_SaveGame; + else + { + TheMemoryCard.PopulateErrorMessage(); + + wchar *error = TheText.Get("FESZ_FF"); // Format Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + + // missing switch + + if ( !error ) error = TheText.Get("FES_GME"); // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + pActiveMenuPage = &MenuPageSaveZone_SaveSlots; + bMemoryCardSpecialZone = true; + bIgnoreTriangleButton = true; + pActiveMenuPage->ActivatePage(); + } + else + { + TheMemoryCard.PopulateErrorMessage(); + + // Format Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + wchar *error = TheText.Get("FESZ_FF"); + + switch ( TheMemoryCard.GetError() ) + { + case CMemoryCard::ERR_WRITEFULLDEVICE: + case CMemoryCard::ERR_DIRFULLDEVICE: + case CMemoryCard::ERR_SAVEFAILED: + { + error = TheMemoryCard.GetErrorMessage(); + break; + } + } + + // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + if ( !error ) error = TheText.Get("FES_GME"); + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + } + } + } +} + +void +TriggerSaveZone_FormatCardSelect(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + { + // This Memory Card (PS2) is already formatted. + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_AFO"), X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(5.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else if ( TheMemoryCard.GetError() != CMemoryCard::ERR_NOFORMAT ) + { + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheMemoryCard.GetErrorMessage(), X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(15.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + // Are you sure you wish to format the Memory Card (PS2) in MEMORY CARD slot 1? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QF"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSaveZone_FormatCard, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_QuestionYesNo; + } + } +} + +void +TriggerSaveZone_DeleteSaveGame(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSaveZone_DeleteSaveGame, widget); + else + { + // Overwriting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + DisplayMemoryCardAccessMsg(TheText.Get("FESZ_OW"), CRGBA(200, 50, 50, 192)); + + TheMemoryCard.DeleteSlot(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() != CMemoryCard::NO_ERR_SUCCESS ) + { + TheMemoryCard.PopulateErrorMessage(); + + wchar *error = TheText.Get("FES_DEE"); // Deleting Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + + // switch missing + + if ( !error ) error = TheText.Get("FES_GME"); // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + TheMemoryCard.SaveSlot(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + { + // Game saved successfully! + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FESZ_L1"), X(-20.0f), YF(10.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(110.0f), 0.0f, TriggerSaveZone_QuitMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + TheMemoryCard.PopulateErrorMessage(); + + wchar *error = TheText.Get("FESZ_SR"); // Save Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + + switch ( TheMemoryCard.GetError() ) + { + case CMemoryCard::ERR_WRITEFULLDEVICE: + case CMemoryCard::ERR_DIRFULLDEVICE: + case CMemoryCard::ERR_SAVEFAILED: + { + error = TheMemoryCard.GetErrorMessage(); + break; + } + } + + if ( !error ) error = TheText.Get("FES_GME"); // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(120.0f), YF(30.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + } + } + } +} + +void +TriggerSaveZone_SaveGame(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSaveZone_SaveGame, widget); + else + { + DisplayMemoryCardAccessMsg(TheText.Get("FESZ_WR"), CRGBA(200, 50, 50, 192)); + + TheMemoryCard.SaveSlot(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS ) + { + // Game saved successfully! + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FESZ_L1"), X(-20.0f), YF(10.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(110.0f), 0.0f, TriggerSaveZone_QuitMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + else + { + TheMemoryCard.PopulateErrorMessage(); + + wchar *error = TheText.Get("FESZ_SR"); // Save Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + + switch ( TheMemoryCard.GetError() ) + { + case CMemoryCard::ERR_WRITEFULLDEVICE: + case CMemoryCard::ERR_DIRFULLDEVICE: + case CMemoryCard::ERR_SAVEFAILED: + { + error = TheMemoryCard.GetErrorMessage(); + break; + } + } + + if ( !error ) error = TheText.Get("FES_GME"); // Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(error, X(-80.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(120.0f), YF(30.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pActiveMenuPage = &MenuPageSaveZone_Message; + } + } + } +} + +void +TriggerSaveZone_SaveSlots(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + if ( widget ) + { + if ( widget->GetMenuSelection() > 0 ) + { + MemoryCardSlotSelected = widget->GetMenuSelection() - 1; + + switch ( TheMemoryCard.GetInfoOnSpecificSlot(MemoryCardSlotSelected) ) + { + case CMemoryCard::SLOT_PRESENT: + case CMemoryCard::SLOT_CORRUPTED: + { + // Proceed with overwriting this saved game? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QO"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSaveZone_DeleteSaveGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + bMemoryCardSpecialZone = false; + pActiveMenuPage = &MenuPageSaveZone_QuestionYesNo; + break; + } + + case CMemoryCard::SLOT_NOTPRESENT: + { + // PROCEED WITH SAVE ? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QS"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSaveZone_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSaveZone_SaveGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + bMemoryCardSpecialZone = false; + pActiveMenuPage = &MenuPageSaveZone_QuestionYesNo; + break; + } + } + } + } +} + +void +TriggerSaveZone_SaveGameSelect(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + + if ( TheMemoryCard.GetError() == CMemoryCard::ERR_NOFORMAT) + { + gErrorSampleTriggered = false; + pActiveMenuPage = &MenuPageSaveZone_FormatCard; + } + else + { + bMemoryCardSpecialZone = true; + bIgnoreTriangleButton = true; + pActiveMenuPage = &MenuPageSaveZone_SaveSlots; + } + + pActiveMenuPage->ActivatePage(); + } +} + +void +TriggerControls_Vibrations(CMenuOnOffTriggered *widget) +{ + if ( widget ) + { + CMenuManager::m_PrefsUseVibration = widget->GetMenuSelection(); + if ( CMenuManager::m_PrefsUseVibration ) + { + CPad::GetPad(0)->StartShake(300, 150); + TimeToStopPadShaking = CTimer::GetTimeInMillisecondsPauseMode() + 500; + } + } +} + +void +TriggerControls_ContrDisplay(CMenuMultiChoiceTriggeredAlways *widget) +{ + if ( widget ) + { + int32 conf = MenuControls_1.GetMenuSelection(); + int32 i = MenuControls_2.GetMenuSelection(); + if ( i == 1 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_7; + else + MenuPage_Controls.m_controls[0] = &MenuControls_4; + } + else if ( i == 0 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_6; + else + MenuPage_Controls.m_controls[0] = &MenuControls_3; + } + } +} + +void +TriggerControls_DrawHNContrConfig(CMenuMultiChoiceTriggeredAlways *widget) +{ + if ( widget ) + { + int32 conf = widget->GetMenuSelection(); + + InitialiseTextsInMenuControllerOnFoot(&MenuControls_3, (CMenuManager::CONTRCONFIG)conf); + InitialiseTextsInMenuControllerInCar (&MenuControls_4, (CMenuManager::CONTRCONFIG)conf); + + int32 i = MenuControls_2.GetMenuSelection(); + if ( i == 1 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_7; + else + MenuPage_Controls.m_controls[0] = &MenuControls_4; + } + else if ( i == 0 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_6; + else + MenuPage_Controls.m_controls[0] = &MenuControls_3; + } + } +} + +void +TriggerControls_DrawContrConfig(CMenuMultiChoiceTriggeredAlways *widget) +{ + if ( widget ) + { + int32 conf = widget->GetMenuSelection(); + if ( widget->m_cursor != -1 ) + conf = widget->m_cursor; + + InitialiseTextsInMenuControllerOnFoot(&MenuControls_3, (CMenuManager::CONTRCONFIG)conf); + InitialiseTextsInMenuControllerInCar(&MenuControls_4, (CMenuManager::CONTRCONFIG)conf); + + int32 i = MenuControls_2.GetMenuSelection(); + if ( i == 1 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_7; + else + MenuPage_Controls.m_controls[0] = &MenuControls_4; + } + else if ( i == 0 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_6; + else + MenuPage_Controls.m_controls[0] = &MenuControls_3; + } + } +} + +void +TriggerControls_ContrConfig(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + int32 conf = widget->GetMenuSelection(); + + InitialiseTextsInMenuControllerOnFoot(&MenuControls_3, (CMenuManager::CONTRCONFIG)conf); + InitialiseTextsInMenuControllerInCar(&MenuControls_4, (CMenuManager::CONTRCONFIG)conf); + + int32 i = MenuControls_2.GetMenuSelection(); + if ( i == 1 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_7; + else + MenuPage_Controls.m_controls[0] = &MenuControls_4; + } + else if ( i == 0 ) + { + if ( conf == CMenuManager::CONFIG_2 ) + MenuPage_Controls.m_controls[0] = &MenuControls_6; + else + MenuPage_Controls.m_controls[0] = &MenuControls_3; + } + } +} + +void +TriggerLanguage_Language(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + if ( CMenuManager::m_PrefsLanguage != widget->GetMenuSelection() ) + { + CMenuManager::m_PrefsLanguage = widget->GetMenuSelection(); + FrontEndMenuManager.m_bInitialised = false; + bFrontEnd_ReloadObrTxtGxt = true; + } + } +} + +void +TriggerAudio_RadioStation(CMenuMultiChoicePicturedTriggered *widget) +{ + if ( widget ) + { + if ( CMenuManager::m_PrefsRadioStation != widget->GetMenuSelection() ) + { + CMenuManager::m_PrefsRadioStation = widget->GetMenuSelection(); + DMAudio.PlayFrontEndTrack(CMenuManager::m_PrefsRadioStation, TRUE); + DMAudio.SetRadioInCar(CMenuManager::m_PrefsRadioStation); + } + } +} + +void +TriggerAudio_StereoMono(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + if (widget->GetMenuSelection() == 1) + { + DMAudio.SetMonoMode(TRUE); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MONO, 0); + } + else + { + DMAudio.SetMonoMode(FALSE); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_STEREO, 0); + } + } +} + +void +TriggerAudio_MusicVolumeAlways(CMenuSliderTriggered *widget) +{ + ; +} + +void +TriggerAudio_SfxVolumeAlways(CMenuSliderTriggered *widget) +{ + if ( widget ) + { + static bool bTriggerTest = false; + + CMenuManager::m_PrefsSfxVolume = float(widget->GetMenuSelection()) / 100.0f * 127.0f + 0.5f; + + if ( CMenuManager::m_PrefsSfxVolume == 102 && !CPad::GetPad(0)->GetDPadLeft()&& !CPad::GetPad(0)->GetDPadRight() ) + { + if ( bTriggerTest ) + { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_AUDIO_TEST, 0); + bTriggerTest = false; + } + } + else + bTriggerTest = true; + + FrontEndMenuManager.SetSoundLevelsForMusicMenu(); + } +} + +void +TriggerAudio_MusicVolume(CMenuSliderTriggered *widget) +{ + if ( widget ) + { + CMenuManager::m_PrefsMusicVolume = float(widget->GetMenuSelection()) / 100.0f * 127.0f + 0.5f; + FrontEndMenuManager.SetSoundLevelsForMusicMenu(); + } +} + +void +TriggerAudio_SfxVolume(CMenuSliderTriggered *widget) +{ + ; +} + +void +TriggerSave_NewGameNewGame(CMenuMultiChoiceTriggered *widget) +{ + FrontEndMenuManager.m_bWantToRestart = true; + FrontEndMenuManager.m_bMenuActive = false; + FrontEndMenuManager.m_bInSaveZone = false; + bIgnoreTriangleButton = false; + + CTimer::EndUserPause(); + + FrontEndMenuManager.AnaliseMenuContents(); + + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); +} + +void +TriggerSave_NewGameSelectYes(CMenuMultiChoiceTriggered *widget) +{ + // Are you sure you want to start a new game? All progress since the last save game will be lost. Proceed? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QR"), X(-100.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(30.0f), TriggerSave_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), YF(10.0f), TriggerSave_NewGameNewGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + pMenuSave = &MenuPageSaveZone_QuestionYesNo; + bIgnoreTriangleButton = true; +} + +void +TriggerSave_DeleteGameDeleteGame(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSave_DeleteGameDeleteGame, widget); + else + { + // Deleting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + DisplayMemoryCardAccessMsg(TheText.Get("FEDL_WR"), CRGBA(200, 50, 50, 192)); + + TheMemoryCard.DeleteSlot(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() != CMemoryCard::NO_ERR_SUCCESS) + { + // Deleting Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_DEE"), X(-80.0f), YF(20.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(15.0f), TriggerSave_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pMenuSave = &MenuPageSaveZone_Message; + + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = true; + } + else + { + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + + pMenuSave = &MenuPage_SaveBasic; + pMenuSave->ActivatePage(); + } + } + } +} + +void +TriggerSave_DeleteGameDeleteGameSelect(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + if ( widget ) + { + if ( widget->GetMenuSelection() > 0 ) + { + MemoryCardSlotSelected = widget->GetMenuSelection() - 1; + + switch ( TheMemoryCard.GetInfoOnSpecificSlot(MemoryCardSlotSelected) ) + { + case CMemoryCard::SLOT_NOTPRESENT: + { + break; + } + case CMemoryCard::SLOT_CORRUPTED: + case CMemoryCard::SLOT_PRESENT: + { + // Proceed with deleting this saved game? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QD"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSave_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSave_DeleteGameDeleteGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + pMenuSave = &MenuPageSaveZone_QuestionYesNo; + bMemoryCardSpecialZone = false; + break; + } + } + } + } +} + +void +TriggerSave_DeleteGameSelect(CMenuMultiChoiceTriggered *widget) +{ + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + + pMenuSave = &MenuPage_SaveDeleteGame; + pMenuSave->ActivatePage(); + + gErrorSampleTriggered = false; + bMemoryCardSpecialZone = true; + bIgnoreTriangleButton = true; +} + +void +TriggerSave_LoadGameLoadGame(CMenuMultiChoiceTriggered *widget) +{ + if ( widget ) + { + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + + if ( !MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.SetTrigger(TriggerSave_LoadGameLoadGame, widget); + else + { + // Loading data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + DisplayMemoryCardAccessMsg(TheText.Get("FELD_WR"), CRGBA(200, 50, 50, 192)); + TheMemoryCard.LoadSlotToBuffer(MemoryCardSlotSelected); + + if ( TheMemoryCard.GetError() == CMemoryCard::NO_ERR_SUCCESS) + { + FrontEndMenuManager.m_bWantToRestart = true; + FrontEndMenuManager.AnaliseMenuContents(); + FrontEndMenuManager.m_bMenuActive = false; + FrontEndMenuManager.m_bInSaveZone = false; + + CTimer::EndUserPause(); + + TheMemoryCard.m_bWantToLoad = true; + + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + } + else + { + // Load Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_LOE"), X(-80.0f), YF(20.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), YF(25.0f), TriggerSave_BackToMainMenu, 0, 0); + + pMenuSave = &MenuPageSaveZone_Message; + pMenuSave->ActivatePage(); + + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = true; + } + } + } +} + +void +TriggerSave_LoadGameLoadGameSelect(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + if ( widget ) + { + if ( widget->GetMenuSelection() > 0 ) + { + MemoryCardSlotSelected = widget->GetMenuSelection() - 1; + + switch ( TheMemoryCard.GetInfoOnSpecificSlot(MemoryCardSlotSelected) ) + { + case CMemoryCard::SLOT_NOTPRESENT: + { + break; + } + case CMemoryCard::SLOT_CORRUPTED: + { + // Load Failed. + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.AddText(TheText.Get("FES_LOF"), X(50.0f), YF(20.0f), TEXT_COLOR, 0); + + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(100.0f), 0.0f, TriggerSave_BackToMainMenu, 0, 0); + + MenuPageSaveZone_Message.ActivatePage(); + pMenuSave = &MenuPageSaveZone_Message; + bMemoryCardSpecialZone = false; + break; + } + case CMemoryCard::SLOT_PRESENT: + { + // All unsaved progress in your current game will be lost. Proceed with loading? + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_QL"), X(-40.0f), 0.0f, TEXT_COLOR, 0); + + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), YF(20.0f), TriggerSave_BackToMainMenu, 0, 0); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSave_LoadGameLoadGame, 0, 0); + + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + pMenuSave = &MenuPageSaveZone_QuestionYesNo; + bMemoryCardSpecialZone = false; + break; + } + } + } + } +} + +void +TriggerSave_LoadGameSelect(CMenuMultiChoiceTriggered *widget) +{ + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + + pMenuSave = &MenuPage_SaveLoadGame; + pMenuSave->ActivatePage(); + + gErrorSampleTriggered = false; + bMemoryCardSpecialZone = true; + bIgnoreTriangleButton = true; +} + +void +TriggerSave_BackToMainMenu(CMenuMultiChoiceTriggered *widget) +{ + pMenuSave = &MenuPage_SaveBasic; + pMenuSave->ActivatePage(); + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; +} + +void InitialiseTextsInMenuControllerInCar(CMenuPictureAndText *widget, CMenuManager::CONTRCONFIG cont) +{ + if ( widget ) + { + widget->m_numTexts = 0; + + switch ( cont ) + { + case CMenuManager::CONFIG_1: + { + widget->AddText(TheText.Get("FEC_LL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_RSC"), X(-4.0f), Y(29.0f), PAD_TEXT_COLOR, true); // 33.142860f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_HO3"), X(84.0f), Y(162.0f), PAD_TEXT_COLOR, false); // 185.142868f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_LB"), X(68.0f), Y(-6.0f), PAD_TEXT_COLOR, false); // -6.857143f + widget->AddText(TheText.Get("FEC_LR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_HAB"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_BRA"), X(155.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_EXV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_CAW"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_ACC"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_TUC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_SM3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + + break; + } + + case CMenuManager::CONFIG_2: + { + widget->AddText(TheText.Get("FEC_LL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_HOR"), X(-4.0f), Y(29.0f), PAD_TEXT_COLOR, true); // 33.142860f + widget->AddText(TheText.Get("FEC_CAM"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_NA"), X(84.0f), Y(162.0f), PAD_TEXT_COLOR, false); // 185.142868f + widget->AddText(TheText.Get("FEC_RSC"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_LB"), X(68.0f), Y(-6.0f), PAD_TEXT_COLOR, false); // -6.857143f + widget->AddText(TheText.Get("FEC_LR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_HAB"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_BRA"), X(155.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_EXV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_CAW"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_ACC"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_TUC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_SM3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + + break; + } + + case CMenuManager::CONFIG_3: + { + widget->AddText(TheText.Get("FEC_LL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_EXV"), X(-4.0f), Y(29.0f), PAD_TEXT_COLOR, true); // 33.142860f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_RS3"), X(84.0f), Y(162.0f), PAD_TEXT_COLOR, false); // 185.142868f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_LB"), X(68.0f), Y(-6.0f), PAD_TEXT_COLOR, false); // -6.857143f + widget->AddText(TheText.Get("FEC_LR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_HOR"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_BRA"), X(155.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_HAB"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_CAW"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_ACC"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_TUC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_SM3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + + break; + } + + case CMenuManager::CONFIG_4: + { + widget->AddText(TheText.Get("FEC_LL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_HAB"), X(-4.0f), Y(29.0f), PAD_TEXT_COLOR, true); // 33.142860f + widget->AddText(TheText.Get("FEC_TUC"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_VES"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_HO3"), X(84.0f), Y(162.0f), PAD_TEXT_COLOR, false); // 185.142868f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_LB"), X(68.0f), Y(-6.0f), PAD_TEXT_COLOR, false); // -6.857143f + widget->AddText(TheText.Get("FEC_LR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_CAW"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_SMT"), X(155.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_EXV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_RSC"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_NA"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_ACC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_BRA"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + + break; + } + } + } +} + +void InitialiseTextsInMenuControllerOnFoot(CMenuPictureAndText *widget, CMenuManager::CONTRCONFIG cont) +{ + if ( widget ) + { + widget->m_numTexts = 0; + + + switch ( cont ) + { + case CMenuManager::CONFIG_1: + { + widget->AddText(TheText.Get("FEC_CWL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_LOF"), X(-4.0f), Y(25.0f), PAD_TEXT_COLOR, true); // 28.571430f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_CWR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_TAR"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_JUM"), X(144.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_ENV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_ATT"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_RUN"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_FPC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_LB3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + widget->AddText(TheText.Get("FEC_R3"), X(238.0f), Y(122.0f), PAD_TEXT_COLOR, false); // 139.428574f + + break; + } + + case CMenuManager::CONFIG_2: + { + widget->AddText(TheText.Get("FEC_CWL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_LOF"), X(-4.0f), Y(25.0f), PAD_TEXT_COLOR, true); // 28.571430f + widget->AddText(TheText.Get("FEC_CAM"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_NA"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_CWR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_TAR"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_JUM"), X(144.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_ENV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_ATT"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_RUN"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_FPC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_LB3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + widget->AddText(TheText.Get("FEC_R3"), X(238.0f), Y(122.0f), PAD_TEXT_COLOR, false); // 139.428574f + + break; + } + + case CMenuManager::CONFIG_3: + { + widget->AddText(TheText.Get("FEC_CWL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_ENV"), X(-4.0f), Y(25.0f), PAD_TEXT_COLOR, true); // 28.571430f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_CWR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_TAR"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_JUM"), X(144.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_LOF"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_RUN"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_ATT"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_FPC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_LB3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + widget->AddText(TheText.Get("FEC_R3"), X(238.0f), Y(122.0f), PAD_TEXT_COLOR, false); // 139.428574f + + break; + } + + case CMenuManager::CONFIG_4: + { + widget->AddText(TheText.Get("FEC_CWL"), X(50.0f), Y(-14.0f), PAD_TEXT_COLOR, true); // -16.0f + widget->AddText(TheText.Get("FEC_TAR"), X(-4.0f), Y(25.0f), PAD_TEXT_COLOR, true); // 28.571430f + widget->AddText(TheText.Get("FEC_NA"), X(-4.0f), Y(65.0f), PAD_TEXT_COLOR, true); // 74.285721f + widget->AddText(TheText.Get("FEC_MOV"), X(-4.0f), Y(97.0f), PAD_TEXT_COLOR, true); // 110.857147f + widget->AddText(TheText.Get("FEC_CAM"), X(103.0f), Y(141.0f), PAD_TEXT_COLOR, false); // 161.142868f + widget->AddText(TheText.Get("FEC_PAU"), X(130.0f), Y(128.0f), PAD_TEXT_COLOR, false); // 146.285721f + widget->AddText(TheText.Get("FEC_CWR"), X(184.0f), Y(-14.0f), PAD_TEXT_COLOR, false); // -16.0f + widget->AddText(TheText.Get("FEC_ATT"), X(238.0f), Y(25.0f), PAD_TEXT_COLOR, false); // 28.571430f + widget->AddText(TheText.Get("FEC_JUM"), X(144.0f), Y(18.0f), PAD_TEXT_COLOR, true); // 20.571430f + widget->AddText(TheText.Get("FEC_ENV"), X(238.0f), Y(52.0f), PAD_TEXT_COLOR, false); // 59.428574f + widget->AddText(TheText.Get("FEC_LOF"), X(238.0f), Y(65.0f), PAD_TEXT_COLOR, false); // 74.285721f + widget->AddText(TheText.Get("FEC_RUN"), X(238.0f), Y(78.0f), PAD_TEXT_COLOR, false); // 89.142860f + widget->AddText(TheText.Get("FEC_FPC"), X(238.0f), Y(94.0f), PAD_TEXT_COLOR, false); // 107.428574f + widget->AddText(TheText.Get("FEC_LB3"), X(238.0f), Y(109.0f), PAD_TEXT_COLOR, false); // 124.571434f + widget->AddText(TheText.Get("FEC_R3"), X(238.0f), Y(122.0f), PAD_TEXT_COLOR, false); // 139.428574f + + break; + } + } + } +} + +void +TriggerSaveZone_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + pActiveMenuPage = &MenuPageSaveZone_SaveGame; +} + +void +TriggerSave_BackToMainMenuTwoLines(CMenuMultiChoiceTwoLinesTriggered *widget) +{ + pMenuSave = &MenuPage_SaveBasic; + pMenuSave->ActivatePage(); + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; +} + +void +SetRandomActiveTextlineColor(uint8 bText) +{ + if ( bMemoryCardSpecialZone ) + rgbaATC = SELECTED_TEXT_COLOR; + else + { + bool bSelected = false; + bool bHighlignted = false; + + switch ( FrontEndMenuManager.m_pageState ) + { + case PAGESTATE_NORMAL: + break; + case PAGESTATE_HIGHLIGHTED: + bHighlignted = true; + break; + case PAGESTATE_SELECTED: + bSelected = true; + break; + } + + if ( FrontEndMenuManager.m_bInSaveZone ) + bSelected = true; + + if ( bSelected || bText ) + { + static uint32 delayTime = 0; + static bool bAddVal = true; + + if ( delayTime < CTimer::GetTimeInMillisecondsPauseMode() ) + { + delayTime = CTimer::GetTimeInMillisecondsPauseMode() + 200; + + if ( bAddVal ) + rgbaATC = TEXT_COLOR; + else + rgbaATC = SELECTED_TEXT_COLOR; + + bAddVal = !bAddVal; + } + } + + if ( bHighlignted ) + { + static uint32 delayTime = 0; + static bool bAddVal = true; + + if ( delayTime < CTimer::GetTimeInMillisecondsPauseMode() ) + { + delayTime = CTimer::GetTimeInMillisecondsPauseMode() + 200; + + if ( bAddVal ) + rgbaATC = TITLE_TEXT_COLOR; + else + rgbaATC = MENU_SELECTED_COLOR; + + bAddVal = !bAddVal; + } + } + } +} + +#ifdef GTA_PC + +void +TriggerDisplay_Trails(CMenuOnOffTriggered *widget) +{ + if ( widget ) + { + CMenuManager::m_PrefsShowTrails = widget->GetMenuSelection(); + CMBlur::BlurOn = CMenuManager::m_PrefsShowTrails; + + if ( CMBlur::BlurOn ) + CMBlur::MotionBlurOpen(Scene.camera); + else + CMBlur::MotionBlurClose(); + } +} + +#endif \ No newline at end of file diff --git a/src/core/Frontend_PS2.cpp b/src/core/Frontend_PS2.cpp new file mode 100644 index 0000000..1da15fb --- /dev/null +++ b/src/core/Frontend_PS2.cpp @@ -0,0 +1,3033 @@ +#include "common.h" +#ifdef PS2_MENU +#include "platform.h" +#include "main.h" +#include "Timer.h" +#include "Pad.h" +#include "Sprite2d.h" +#include "Text.h" +#include "Font.h" +#include "Hud.h" +#include "MBlur.h" +#include "DMAudio.h" +#include "Streaming.h" +#include "Camera.h" +#include "Credits.h" +#include "General.h" +#include "TxdStore.h" +#include "FileMgr.h" +#include "Messages.h" +#include "Frontend_PS2.h" +#include "Stats.h" +#include "Game.h" +#include "World.h" +#include "PlayerInfo.h" +#include "FrontEndControls.h" +#include "MemoryCard.h" + +#define CRect_SZ(x, y, w, h) CRect(x, y, x+w, y+h) + +wchar MemoryCard_FileNames[8][100+1]; +CMenuManager FrontEndMenuManager; + +// TEMP: put into header +bool DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); + + +#define SCRW SCREEN_WIDTH +#define SCRH SCREEN_HEIGHT +//#define X SCREEN_STRETCH_X +//#define Y SCREEN_STRETCH_Y +#define X SCREEN_SCALE_X +#define Y SCREEN_SCALE_Y + +#define YF(x) Y(float(x)*(float(DEFAULT_SCREEN_HEIGHT)/float(SCREEN_HEIGHT_PAL))) +//#define X(x) ((x)/640.0f*SCRW) +//#define Y(y) ((y)/448.0f*SCRH) + + +static float MENU_TEXT_SIZE_X = 0.644f; +static float MENU_TEXT_SIZE_Y = 0.84f; //0.96f; +float BUTTONTAB_TEXT_SIZE_X = 0.35f; +float BUTTONTAB_TEXT_SIZE_Y = 0.7f; //0.8f; +float PANEL_TEXT_SIZE_X = 0.8f; +float PANEL_TEXT_SIZE_Y = 1.2f; //0.96f/0.7f; //?? +float MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; +float MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f; //1.28f; + +CRGBA SELECTED_TEXT_COLOR(255, 182, 48, 255); +CRGBA BACKGROUND_SPLASH_COLOR(48, 48, 48, 255); + +CVector2D CONTR_DESCR_NEW_TEXTSCALE(0.4564f, 0.63f); // 0.72 +CVector2D CONFIGS_NEW_TEXTSCALE(0.49f, 0.7f); // 0.8 +CVector2D AUDIO_OUTPUT_POS(0.0f, 0.0f); +CVector2D AUDIO_RSTATION_POS(154.0f, 0.0f); +CVector2D DISPLAY_BRIGHTNESS_POS(0.0f, 0.0f); + +CRGBA TEXT_COLOR(150, 110, 30, 255); +CRGBA PAD_TEXT_COLOR(200, 200, 200, 255); +CRGBA CRIM_RATING_TEXT_COLOR(255, 182, 48, 255); +CRGBA SCROLL_TEXT_COLOR(150, 110, 30, 255); +CRGBA TITLE_TEXT_COLOR(170, 130, 50, 255); +CRGBA TEXT_SHADOW_COLOR(0, 0, 0, 255); +CVector2D SHADOW_VECTOR(1.0f, 1.0f); +CRGBA SLIDER_RIGHT_COLOR(20, 94, 136, 255); +CRGBA SLIDER_LEFT_COLOR(86, 196, 255, 255); +CRGBA MENU_SELECTED_COLOR(255, 212, 88, 255); +CRGBA rgbaATC(96, 96, 96, 255); // active text color. not constant + +float BUTTONTAB_TEXT_X_SCALES[NUM_PAGES] = { 1.0f }; +float PANEL_TEXT_X_SCALES[NUM_PAGES] = { 1.0f }; + +int32 MemoryCardSlotSelected; +uint32 TimeToStopPadShaking; +bool bFrontEnd_ReloadObrTxtGxt; + +bool bMemoryCardStartUpMenus_ExitNow; + +extern CMenuPage MenuPage_SaveBasic; +CMenuPage *pActiveMenuPage; +CMenuPage *pMenuSave = &MenuPage_SaveBasic; +bool bMemoryCardSpecialZone; +bool bIgnoreTriangleButton; +bool gErrorSampleTriggered; + +bool gMusicPlaying; + +CMenuPage MenuPage_Stats; + CMenuLineLister MenuStats_1; + CMenuPictureAndText MenuStats_2; // criminal rating +CMenuPage MenuPage_Briefs; + CMenuPictureAndText MenuBriefs_1; + CMenuDummy MenuBriefs_2; +CMenuPage MenuPage_SaveBasic; + CMenuMultiChoiceTriggered MenuSaveB_1; // "Load Game", "Delete Game", "New Game" +CMenuPage MenuPage_SaveNewGame; + CMenuPictureAndText MenuSaveNG_1; // "Load Game", "Delete Game", "New Game" + CMenuMultiChoiceTriggered MenuSaveNG_2; // "No", "Yes" +CMenuPage MenuPage_SaveLoadGame; + CMenuPictureAndText MenuSaveLG_1; // "Load Game", "Delete Game", "New Game" + CMenuMultiChoiceTwoLinesTriggered MenuSaveLG_2; // save games +CMenuPage MenuPage_SaveDeleteGame; + CMenuPictureAndText MenuSaveDG_1; // "Load Game", "Delete Game", "New Game" + CMenuMultiChoiceTwoLinesTriggered MenuSaveDG_2; // save games +CMenuPage MenuPage_Controls; + CMenuPictureAndText MenuControls_3; // controller images + CMenuPictureAndText MenuControls_6; + CMenuPictureAndText MenuControls_4; + CMenuPictureAndText MenuControls_7; + CMenuMultiChoiceTriggeredAlways MenuControls_1; // "Configuration:" "Setup1", "Setup2", "Setup3", "Setup4" + CMenuMultiChoiceTriggered MenuControls_2; // "Controller Display:" "On Foot", "In Car" + CMenuOnOffTriggered MenuControls_5; // "Vibration:" +CMenuPageAnyMove MenuPage_Audio; + CMenuSliderTriggered MenuAudio_1; // "Music Volume" + CMenuMultiChoiceTriggered MenuAudio_4; // "Output:" "Stereo", "Mono" + CMenuSliderTriggered MenuAudio_2; // "SFX Volume" + CMenuMultiChoicePicturedTriggeredAnyMove MenuAudio_3; // "Radio station select:" +CMenuPage MenuPage_Display; + CMenuSlider MenuDisplay_1; // "Brightness" +#ifdef GTA_PC + CMenuOnOffTriggered MenuDisplay_2; // "Trails:" +#else + CMenuOnOff MenuDisplay_2; // "Trails:" +#endif + CMenuOnOff MenuDisplay_3; // "Subtitles:" + CMenuOnOff MenuDisplay_4; // "Wide Screen:" +CMenuPage MenuPage_Language; + CMenuMultiChoiceTriggered MenuLanguage_1; // "English", "French", "German", "Italian", "Spanish" + +CMenuPage MenuPageSaveZone_SaveGame; + CMenuMultiChoiceTriggered MenuSaveZoneSG_1; // "Save game", "Cancel" +CMenuPage MenuPageSaveZone_SaveSlots; + CMenuMultiChoiceTwoLinesTriggered MenuSaveZoneSSL_1; // "Cancel" +CMenuPage MenuPageSaveZone_SavedSuccessfully; + CMenuPictureAndText MenuSaveZoneSS_1; // "Game saved successfully!" "Your saved filename is:" + CMenuMultiChoiceTriggered MenuSaveZoneSS_2; // "Quit" +CMenuPage MenuPageSaveZone_Message; + CMenuPictureAndText MenuSaveZoneMSG_1; // "Save Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again." + CMenuMultiChoiceTriggered MenuSaveZoneMSG_2; // "OK" +CMenuPage MenuPageSaveZone_QuestionYesNo; + CMenuPictureAndText MenuSaveZoneQYN_1; // "Save Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again." + CMenuMultiChoiceTriggered MenuSaveZoneQYN_2; // "Yes", "No" +CMenuPage MenuPageSaveZone_FormatCard; + CMenuMultiChoiceTriggered MenuSaveZoneFC_1; // "Memory card (PS2) in MEMORY CARD slot 1 is unformatted. Would you like to format memory card (PS2) in MEMORY CARD slot 1?" "No" "Yes" +CMenuPage MenuPageSaveZone_ErrorFormat; + CMenuMultiChoiceTriggered MenuSaveZoneEF_1; // "Format Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again." "OK" + + +VALIDATE_SIZE(CPlaceableText, 0x10); +VALIDATE_SIZE(CPlaceableShText, 0x20); +VALIDATE_SIZE(CPlaceableShTextTwoLines, 0x30); +VALIDATE_SIZE(CPlaceableShOption, 0x28); +VALIDATE_SIZE(CPlaceableShOptionTwoLines, 0x38); +VALIDATE_SIZE(CPlaceableSprite, 0x18); +VALIDATE_SIZE(CPlaceableShSprite, 0x34); +VALIDATE_SIZE(CMenuMultiChoice, 0x2CC); +VALIDATE_SIZE(CMenuMultiChoiceTriggered, 0x310); +VALIDATE_SIZE(CMenuMultiChoiceTwoLines, 0x3CC); +VALIDATE_SIZE(CMenuOnOff, 0x90); + +#include "FrontendTriggers.h" + +static const char* FrontendFilenames[][2] = +{ + {"fe2_mainpanel_ul", "" }, + {"fe2_mainpanel_ur", "" }, + {"fe2_mainpanel_dl", "" }, + {"fe2_mainpanel_dr", "" }, + {"fe2_mainpanel_dr2", "" }, + {"fe2_tabactive", "" }, + {"fe_iconbrief", "" }, + {"fe_iconstats", "" }, + {"fe_iconcontrols", "" }, + {"fe_iconsave", "" }, + {"fe_iconaudio", "" }, + {"fe_icondisplay", "" }, + {"fe_iconlanguage", "" }, + {"fe_controller", "" }, + {"fe_controllersh", "" }, + {"fe_arrows1", "" }, + {"fe_arrows2", "" }, + {"fe_arrows3", "" }, + {"fe_arrows4", "" }, + {"fe_radio1", "" }, + {"fe_radio2", "" }, + {"fe_radio3", "" }, + {"fe_radio4", "" }, + {"fe_radio5", "" }, + {"fe_radio6", "" }, + {"fe_radio7", "" }, + {"fe_radio8", "" }, + {"fe_radio9", "" }, +}; + +int32 CMenuManager::m_PrefsSfxVolume = 102; +int32 CMenuManager::m_PrefsMusicVolume = 102; +int32 CMenuManager::m_PrefsBrightness = 256; +bool CMenuManager::m_PrefsShowTrails = true; +bool CMenuManager::m_PrefsShowSubtitles = true; +bool CMenuManager::m_PrefsAllowNastyGame = true; + +int32 CMenuManager::m_PrefsRadioStation = 0; +int32 CMenuManager::m_PrefsStereoMono = 0; +int8 CMenuManager::m_PrefsUseWideScreen = 0; +int32 CMenuManager::m_PrefsLanguage = 0; +CMenuManager::CONTRCONFIG CMenuManager::m_PrefsControllerConfig = CONFIG_1; +bool CMenuManager::m_PrefsUseVibration = false; + + +#ifdef GTA_PC +#include "PlayerSkin.h" +int32 CMenuManager::OS_Language = 0; +int8 CMenuManager::m_PrefsVsync = 1; +int8 CMenuManager::m_PrefsVsyncDisp = 1; +int8 CMenuManager::m_PrefsFrameLimiter = 1; +int8 CMenuManager::m_PrefsSpeakers; +int32 CMenuManager::m_ControlMethod = CONTROL_CLASSIC; +int8 CMenuManager::m_PrefsDMA = 1; +float CMenuManager::m_PrefsLOD = 1.0f; +char CMenuManager::m_PrefsSkinFile[256] = DEFAULT_SKIN_NAME; + +#ifndef MASTER +bool CMenuManager::m_PrefsMarketing; +bool CMenuManager::m_PrefsDisableTutorials; +#endif // !MASTER + +#ifdef MENU_MAP +bool CMenuManager::bMenuMapActive; +float CMenuManager::fMapSize; +float CMenuManager::fMapCenterY; +float CMenuManager::fMapCenterX; +#endif + +#endif + + +CMenuManager::CMenuManager(void) +{ + int32 i; + + SetSoundLevelsForMusicMenu(); + + m_pageState = PAGESTATE_NORMAL; + m_currentPage = PAGE_FIRST; + m_newPage = PAGE_FIRST; + m_bMenuActive = false; + m_bSaveMenuActive = false; + m_bRenderGameInMenu = false; + m_bTexturesLoaded = false; + m_nPageLeftTimer = 0; + m_nPageRightTimer = 0; + m_nChangePageTimer = 0; + field_18 = 0; + m_fade = 255; + m_someAlpha = 255; + m_position.x = 0.0f; + m_position.y = 0.0f; + m_nSlidingDir = SLIDE_TO_BOTTOM; + m_nStartPauseTimer = 0; + m_nEndPauseTimer = 0; + m_bInitialised = false; + m_bWantToUpdateContent = false; + field_3C = 0; + m_bInSaveZone = false; + + for(i = 0; i < NUM_PAGES; i++){ + BUTTONTAB_TEXT_X_SCALES[i] = 1.0f; + PANEL_TEXT_X_SCALES[i] = 1.0f; + } + +#ifdef GTA_PC + TheCamera.m_bUseMouse3rdPerson = m_ControlMethod == CONTROL_STANDARD; + CMBlur::BlurOn = m_PrefsShowTrails; +#endif +} + +void +CMenuManager::LoadAllTextures(void) +{ + int32 i; + + if(m_bTexturesLoaded) + return; + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); + DMAudio.Service(); + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + CSprite2d *splash = LoadSplash(nil); + if(splash) + splash->Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + else // doesn't exist!! + CHud::Sprites[19].Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERMIPNEAREST); + DoRWStuffEndOfFrame(); + + CFileMgr::SetDir(""); + CFileMgr::SetDir(""); + + CTimer::Stop(); + CStreaming::MakeSpaceFor(60*1024); + CStreaming::ImGonnaUseStreamingMemory(); + CGame::TidyUpMemory(false, true); + int32 slot = CTxdStore::FindTxdSlot("frontend"); + if(slot == -1) + slot = CTxdStore::AddTxdSlot("frontend"); + printf("LOAD frontend\n"); + CTxdStore::LoadTxd(slot, "MODELS/FRONTEND.TXD"); + CTxdStore::SetCurrentTxd(slot); + CStreaming::IHaveUsedStreamingMemory(); + CTimer::Update(); + + for(i = 0; i < NUM_SPRIRES; i++) + { + m_sprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); + m_sprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } + + m_bTexturesLoaded = true; +} + +void +CMenuManager::UnloadTextures(void) +{ + int32 slot; + int32 i; + + if ( !m_bTexturesLoaded ) + return; + + slot = CTxdStore::FindTxdSlot("frontend"); +#ifdef FIX_BUGS + for(i = 0; i < NUM_SPRIRES; i++) + m_sprites[i].Delete(); +#endif + + printf("REMOVE frontend\n"); + CTxdStore::RemoveTxd(slot); + m_bTexturesLoaded = false; +} + +void +CMenuManager::InitialiseMenusOnce(void) +{ + if(m_bInitialised) + return; + m_bInitialised = true; + + InitialiseChangedLanguageSettings(); + + // Normal menu + MenuPage_Stats.Initialise(); + MenuPage_Briefs.Initialise(); + MenuPage_SaveBasic.Initialise(); + MenuPage_SaveNewGame.Initialise(); + MenuPage_SaveLoadGame.Initialise(); + MenuPage_SaveDeleteGame.Initialise(); + MenuPage_Controls.Initialise(); + MenuPage_Audio.Initialise(); + MenuPage_Display.Initialise(); + MenuPage_Language.Initialise(); + + // Save menu + MenuPageSaveZone_SaveGame.Initialise(); + MenuPageSaveZone_SaveSlots.Initialise(); + MenuPageSaveZone_SavedSuccessfully.Initialise(); + MenuPageSaveZone_Message.Initialise(); + MenuPageSaveZone_QuestionYesNo.Initialise(); + MenuPageSaveZone_FormatCard.Initialise(); + MenuPageSaveZone_ErrorFormat.Initialise(); + + /* Stats */ + + MenuStats_1.ResetNumberOfTextLines(); + MenuStats_1.SetPosition(X(75.0f), Y(70.0f)); + MenuStats_1.m_width = X(480.0f); + MenuStats_1.m_height = Y(274.0f); + MenuStats_1.field_10E8 = 0; // unknown + MenuStats_1.m_lineSpacing = Y(20.0f); + MenuStats_1.m_scrollSpeed = 1.0f; + MenuStats_1.SetLinesColor(SCROLL_TEXT_COLOR); + MenuStats_1.ResetNumberOfTextLines(); + MenuPage_Stats.AddMenu(&MenuStats_1); + MenuStats_2.SetPosition(X(75.0f), Y(50.0f)); + MenuStats_2.SetTextsColor(CRIM_RATING_TEXT_COLOR); + MenuPage_Stats.AddMenu(&MenuStats_2); + MenuPage_Stats.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Stats.ActivatePage(); + + + CVector2D saveGameTextScale(X(0.49f), Y(0.7f)); + CVector2D defaultTextScale(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + + /* Basic Load/Delete/New Game */ + + MenuSaveB_1.m_numOptions = 0; + MenuSaveB_1.SetPosition(X(220.0f), Y(110.0f)); + MenuSaveB_1.AddOption(TheText.Get("FES_LGA"), 0.0f, Y(20.0f), TriggerSave_LoadGameSelect, false, true); + MenuSaveB_1.AddOption(TheText.Get("FES_DGA"), 0.0f, Y(40.0f), TriggerSave_DeleteGameSelect, false, true); + MenuSaveB_1.AddOption(TheText.Get("FES_NGA"), 0.0f, Y(60.0f), TriggerSave_NewGameSelectYes, false, true); + MenuSaveB_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuPage_SaveBasic.AddMenu(&MenuSaveB_1); + MenuPage_SaveBasic.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_SaveBasic.ActivatePage(); + + /* New Game - but unused */ + + MenuSaveNG_1.m_numTexts = 0; + MenuSaveNG_1.SetPosition(X(220.0f), Y(110.0f)); + MenuSaveNG_1.AddText(TheText.Get("FES_LGA"), 0.0f, Y(20.0f), TEXT_COLOR, true); + MenuSaveNG_1.AddText(TheText.Get("FES_DGA"), 0.0f, Y(40.0f), TEXT_COLOR, true); + MenuSaveNG_1.AddText(TheText.Get("FES_NGA"), 0.0f, Y(60.0f), SELECTED_TEXT_COLOR, true); + MenuPage_SaveNewGame.AddMenu(&MenuSaveNG_1); + MenuSaveNG_2.m_numOptions = 0; + MenuSaveNG_2.SetPosition(X(250.0f), Y(170.0f)); + MenuSaveNG_2.AddOption(TheText.Get("FEM_NO"), 0.0f, 0.0f, TriggerSave_BackToMainMenu, false, false); + MenuSaveNG_2.AddOption(TheText.Get("FEM_YES"), 0.0f, Y(20.0f), TriggerSave_NewGameSelectYes, false, false); + MenuSaveNG_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveNG_2.m_defaultCancel = TriggerSave_BackToMainMenu; + MenuPage_SaveNewGame.AddMenu(&MenuSaveNG_2); + MenuPage_SaveNewGame.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_SaveNewGame.ActivatePage(); + + /* Load Game */ + + MenuSaveLG_1.m_numTexts = 0; + MenuSaveLG_1.SetPosition(X(220.0f), Y(110.0f)); + MenuSaveLG_1.AddText(TheText.Get("FES_LGA"), 0.0f, Y(20.0f), SELECTED_TEXT_COLOR, true); + MenuSaveLG_1.AddText(TheText.Get("FES_DGA"), 0.0f, Y(40.0f), TEXT_COLOR, true); + MenuSaveLG_1.AddText(TheText.Get("FES_NGA"), 0.0f, Y(60.0f), TEXT_COLOR, true); + MenuPage_SaveLoadGame.AddMenu(&MenuSaveLG_1); + MenuSaveLG_2.m_numOptions = 0; + MenuSaveLG_2.SetPosition(X(250.0f), Y(60.0f)); + MenuSaveLG_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveLG_2.m_defaultCancel = TriggerSave_BackToMainMenuTwoLines; + MenuSaveLG_2.SetNewOldTextScale(true, saveGameTextScale, defaultTextScale, false); + MenuPage_SaveLoadGame.AddMenu(&MenuSaveLG_2); + MenuPage_SaveLoadGame.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_SaveLoadGame.ActivatePage(); + + /* Delete Game */ + + MenuSaveDG_1.m_numTexts = 0; + MenuSaveDG_1.SetPosition(X(220.0f), Y(110.0f)); + MenuSaveDG_1.AddText(TheText.Get("FES_LGA"), 0.0f, Y(20.0f), TEXT_COLOR, true); + MenuSaveDG_1.AddText(TheText.Get("FES_DGA"), 0.0f, Y(40.0f), SELECTED_TEXT_COLOR, true); + MenuSaveDG_1.AddText(TheText.Get("FES_NGA"), 0.0f, Y(60.0f), TEXT_COLOR, true); + MenuPage_SaveDeleteGame.AddMenu(&MenuSaveDG_1); + MenuSaveDG_2.m_numOptions = 0; + MenuSaveDG_2.SetPosition(X(250.0f), Y(60.0f)); + MenuSaveDG_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveDG_2.m_defaultCancel = TriggerSave_BackToMainMenuTwoLines; + MenuSaveDG_2.SetNewOldTextScale(true, saveGameTextScale, defaultTextScale, false); + MenuPage_SaveDeleteGame.AddMenu(&MenuSaveDG_2); + MenuPage_SaveDeleteGame.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_SaveDeleteGame.ActivatePage(); + + + CVector2D briefsTextScale(X(0.525f), Y(0.7f)); + CVector2D defaultTextScale1(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + + /* Briefs */ + + MenuBriefs_1.m_numTexts = 0; + MenuBriefs_1.SetPosition(X(60.0f), Y(60.0f)); + MenuBriefs_1.SetTextsColor(TEXT_COLOR); + MenuBriefs_1.SetNewOldTextScale(true, briefsTextScale, defaultTextScale1); + MenuBriefs_1.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + MenuPage_Briefs.AddMenu(&MenuBriefs_1); + MenuPage_Briefs.AddMenu(&MenuBriefs_2); + MenuPage_Briefs.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Briefs.ActivatePage(); + + + CVector2D defaultTextScale2(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CVector2D defaultTextScale3(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CVector2D CONTR_DESCR_NEW_TEXTSCALE_scaled(X(CONTR_DESCR_NEW_TEXTSCALE.x), Y(CONTR_DESCR_NEW_TEXTSCALE.y)); + CVector2D CONFIGS_NEW_TEXTSCALE_scaled(X(CONFIGS_NEW_TEXTSCALE.x), Y(CONFIGS_NEW_TEXTSCALE.y)); + + /* Controls */ + + MenuControls_3.m_numTexts = 0; + MenuControls_3.m_numSprites = 0; + MenuControls_3.SetPosition(X(170.0f), Y(88.0f)); + MenuControls_3.AddPicture(&m_sprites[FE_CONTROLLER], + &m_sprites[FE_CONTROLLERSH], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_3.AddPicture(&m_sprites[FE_ARROWS1], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_3.SetNewOldTextScale(true, CONTR_DESCR_NEW_TEXTSCALE_scaled, defaultTextScale2); + InitialiseTextsInMenuControllerOnFoot(&MenuControls_3, CMenuManager::m_PrefsControllerConfig); + MenuControls_3.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuControls_3.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + MenuPage_Controls.AddMenu(&MenuControls_3); + + MenuControls_6.m_numTexts = 0; + MenuControls_6.m_numSprites = 0; + MenuControls_6.SetPosition(X(170.0f), Y(88.0f)); + MenuControls_6.AddPicture(&m_sprites[FE_CONTROLLER], + &m_sprites[FE_CONTROLLERSH], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_6.AddPicture(&m_sprites[FE_ARROWS3], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_6.SetNewOldTextScale(true, CONTR_DESCR_NEW_TEXTSCALE_scaled, defaultTextScale2); + InitialiseTextsInMenuControllerOnFoot(&MenuControls_6, CMenuManager::CONFIG_2); + MenuControls_6.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuControls_6.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + + MenuControls_4.m_numTexts = 0; + MenuControls_4.m_numSprites = 0; + MenuControls_4.SetPosition(X(170.0f), Y(88.0f)); + MenuControls_4.AddPicture(&m_sprites[FE_CONTROLLER], + &m_sprites[FE_CONTROLLERSH], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_4.AddPicture(&m_sprites[FE_ARROWS2], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_4.SetNewOldTextScale(true, CONTR_DESCR_NEW_TEXTSCALE_scaled, defaultTextScale2); + InitialiseTextsInMenuControllerInCar(&MenuControls_4, CMenuManager::m_PrefsControllerConfig); + MenuControls_4.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuControls_4.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + + MenuControls_7.m_numTexts = 0; + MenuControls_7.m_numSprites = 0; + MenuControls_7.SetPosition(X(170.0f), Y(88.0f)); + MenuControls_7.AddPicture(&m_sprites[FE_CONTROLLER], + &m_sprites[FE_CONTROLLERSH], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_7.AddPicture(&m_sprites[FE_ARROWS4], + 0.0f, 0.0f, X(235.2f), Y(175.2), CRGBA(255, 255, 255, 255)); + MenuControls_7.SetNewOldTextScale(true, CONTR_DESCR_NEW_TEXTSCALE_scaled, defaultTextScale2); + InitialiseTextsInMenuControllerInCar(&MenuControls_7, CMenuManager::CONFIG_2); + MenuControls_7.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuControls_7.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x), X(600.0f)); + + MenuControls_1.m_numOptions = 0; + MenuControls_1.SetPosition(X(284.0f), Y(290.0f)); + MenuControls_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, SELECTED_TEXT_COLOR); + MenuControls_1.SetNewOldTextScale(true, CONFIGS_NEW_TEXTSCALE_scaled, defaultTextScale3, false); + MenuControls_1.AddTitle(TheText.Get("FEC_CCF"), 0.0f, 0.0f, true); + MenuControls_1.AddOption(TheText.Get("FEC_CF1"), X(15.0f), Y(2.0f), TriggerControls_ContrConfig, false, false); + MenuControls_1.AddOption(TheText.Get("FEC_CF2"), X(85.0f), Y(2.0f), TriggerControls_ContrConfig, false, false); + MenuControls_1.AddOption(TheText.Get("FEC_CF3"), X(155.0f), Y(2.0f), TriggerControls_ContrConfig, false, false); + MenuControls_1.AddOption(TheText.Get("FEC_CF4"), X(225.0f), Y(2.0f), TriggerControls_ContrConfig, false, false); + MenuPage_Controls.AddMenu(&MenuControls_1); + MenuControls_1.m_alwaysTrigger = (CMenuMultiChoiceTriggered::Trigger)TriggerControls_DrawContrConfig; + MenuControls_1.m_alwaysHighlightTrigger = (CMenuMultiChoiceTriggered::Trigger)TriggerControls_DrawHNContrConfig; + MenuControls_1.m_alwaysNormalTrigger = (CMenuMultiChoiceTriggered::Trigger)TriggerControls_DrawHNContrConfig; + + MenuControls_2.m_numOptions = 0; + MenuControls_2.SetPosition(X(284.0f), Y(310.0f)); + MenuControls_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, SELECTED_TEXT_COLOR); + MenuControls_2.SetNewOldTextScale(true, CONFIGS_NEW_TEXTSCALE_scaled, defaultTextScale3, false); + MenuControls_2.AddTitle(TheText.Get("FEC_CDP"), 0.0f, 0.0f, true); + MenuControls_2.AddOption(TheText.Get("FEC_ONF"), X(15.0f), Y(2.0f), (CMenuMultiChoiceTriggered::Trigger)TriggerControls_ContrDisplay, false, false); + MenuControls_2.AddOption(TheText.Get("FEC_INC"), X(105.0f), Y(2.0f), (CMenuMultiChoiceTriggered::Trigger)TriggerControls_ContrDisplay, false, false); + MenuPage_Controls.AddMenu(&MenuControls_2); + MenuControls_2.m_bTwoState = true; + MenuControls_2.SetMenuSelection(0); + + MenuControls_5.SetPosition(X(284.0f), Y(330.0f)); + MenuControls_5.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR); + MenuControls_5.SetNewOldTextScale(true, CONFIGS_NEW_TEXTSCALE_scaled, defaultTextScale3, false); + MenuControls_5.AddTitle(TheText.Get("FEC_VIB"), false, 0.0f, 0.0f, true); + MenuControls_5.SetOptionPosition(X(15.0f), Y(2.0f), TriggerControls_Vibrations, false); + MenuPage_Controls.AddMenu(&MenuControls_5); + MenuPage_Controls.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Controls.ActivatePage(); + + + /* Audio */ + + CVector2D audioOutputScale(X(0.49f), Y(0.63f)); + CVector2D defaultTextScale4(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + + FEC_MOVETAB movetab; + MenuAudio_1.SetPosition(X(70.0f), Y(80.0f)); + MenuAudio_1.SetColors(TEXT_COLOR, TEXT_COLOR, SLIDER_LEFT_COLOR, SLIDER_RIGHT_COLOR); + MenuAudio_1.AddTitle(TheText.Get("FEA_MUS"), 0.0f, 0.0f); + MenuAudio_1.AddTickBox(X(15.0f), Y(20.0f), X(150.0f), Y(5.0f), Y(45.0f), TriggerAudio_MusicVolume, TriggerAudio_MusicVolumeAlways); + movetab.right = 1; + movetab.left = 2; + movetab.down = 3; + movetab.up = 3; + MenuPage_Audio.AddMenu(&MenuAudio_1, &movetab); + + MenuAudio_4.m_numOptions = 0; + MenuAudio_4.SetPosition(X(280.0f), Y(80.0f)); + MenuAudio_4.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, SELECTED_TEXT_COLOR); + MenuAudio_4.SetNewOldTextScale(true, audioOutputScale, defaultTextScale4, false); + MenuAudio_4.AddTitle(TheText.Get("FEA_OUT"), X(AUDIO_OUTPUT_POS.x), Y(AUDIO_OUTPUT_POS.y), false); + MenuAudio_4.AddOption(TheText.Get("FEA_ST"), X(-15.0f), Y(30.0f), TriggerAudio_StereoMono, false, false); + MenuAudio_4.AddOption(TheText.Get("FEA_MNO"), X(55.0f), Y(30.0f), TriggerAudio_StereoMono, false, false); + movetab.right = 2; + movetab.left = 0; + movetab.down = 3; + movetab.up = 3; + MenuPage_Audio.AddMenu(&MenuAudio_4, &movetab); + MenuAudio_4.m_bTwoState = true; + + MenuAudio_2.SetPosition(X(410.0f), Y(80.0f)); + MenuAudio_2.SetColors(TEXT_COLOR, TEXT_COLOR, SLIDER_LEFT_COLOR, SLIDER_RIGHT_COLOR); + MenuAudio_2.AddTitle(TheText.Get("FEA_SFX"), 0.0f, 0.0f); + MenuAudio_2.AddTickBox(X(5.0f), Y(20.0f), X(150.0f), Y(5.0f), Y(45.0f), TriggerAudio_SfxVolume, TriggerAudio_SfxVolumeAlways); + movetab.right = 0; + movetab.left = 1; + movetab.down = 3; + movetab.up = 3; + MenuPage_Audio.AddMenu(&MenuAudio_2, &movetab); + + MenuAudio_3.m_numOptions = 0; + MenuAudio_3.SetPosition(X(50.0f), Y(170.0f)); + MenuAudio_3.SetColors(TITLE_TEXT_COLOR, CRGBA(64, 64, 64, 255), CRGBA(250, 250, 250, 255)); + MenuAudio_3.AddTitle(TheText.Get("FEA_RSS"), X(AUDIO_RSTATION_POS.x), Y(AUDIO_RSTATION_POS.y), false); + // first row + movetab.right = 1; + movetab.left = 4; + movetab.down = 5; + movetab.up = 5; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO1], &movetab, 0.0f, Y(18.0f), + CVector2D(X(96.0f), YF(72.0f)), TriggerAudio_RadioStation, false); + movetab.right = 2; + movetab.left = 0; + movetab.down = 6; + movetab.up = 6; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO2], &movetab, X(106.0f), Y(20.0f), + CVector2D(X(79.2f), YF(81.0f)), TriggerAudio_RadioStation, false); + movetab.right = 3; + movetab.left = 1; + movetab.down = 7; + movetab.up = 7; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO5], &movetab, X(210.0f), Y(20.0f), + CVector2D(X(86.4f), YF(72.0f)), TriggerAudio_RadioStation, false); + movetab.right = 4; + movetab.left = 2; + movetab.down = 8; + movetab.up = 8; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO7], &movetab, X(324.0f), Y(5.0f), + CVector2D(X(115.2f), YF(102.0f)), TriggerAudio_RadioStation, false); + movetab.right = 0; + movetab.left = 3; + movetab.down = 8; + movetab.up = 8; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO8], &movetab, X(446.0f), Y(5.0f), + CVector2D(X(102.96f), YF(101.4f)), TriggerAudio_RadioStation, false); + // second row + movetab.right = 6; + movetab.left = 8; + movetab.down = 0; + movetab.up = 0; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO3], &movetab, X(60.0f), Y(96.0f), + CVector2D(X(87.36f), YF(85.8f)), TriggerAudio_RadioStation, false); + movetab.right = 7; + movetab.left = 5; + movetab.down = 1; + movetab.up = 1; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO4], &movetab, X(130.0f), Y(72.0f), + CVector2D(X(129.6f), YF(129.0f)), TriggerAudio_RadioStation, false); + movetab.right = 8; + movetab.left = 6; + movetab.down = 2; + movetab.up = 2; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO6], &movetab, X(284.0f), Y(108.0f), + CVector2D(X(60.0f), YF(60.0f)), TriggerAudio_RadioStation, false); + movetab.right = 5; + movetab.left = 7; + movetab.down = 3; + movetab.up = 3; + MenuAudio_3.AddOption(&m_sprites[FE_RADIO9], &movetab, X(404.0f), Y(85.0f), + CVector2D(X(81.12f), YF(101.4f)), TriggerAudio_RadioStation, false); + movetab.right = 2; + movetab.left = 0; + movetab.down = 1; + movetab.up = 1; + MenuPage_Audio.AddMenu(&MenuAudio_3, &movetab); + MenuPage_Audio.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Audio.ActivatePage(); + + + /* Display */ + + MenuDisplay_1.SetPosition(X(240.0f), Y(140.0f)); + MenuDisplay_1.SetColors(TEXT_COLOR, TEXT_COLOR, SLIDER_LEFT_COLOR, SLIDER_RIGHT_COLOR); + MenuDisplay_1.m_style = 0; // ticks + MenuDisplay_1.AddTitle(TheText.Get("FED_BRI"), X(DISPLAY_BRIGHTNESS_POS.x), Y(DISPLAY_BRIGHTNESS_POS.y)); + MenuDisplay_1.AddTickBox(X(-30.0f), Y(20.0f), X(200.0f), Y(40.0f), Y(40.0f)); + MenuPage_Display.AddMenu(&MenuDisplay_1); + MenuDisplay_2.SetPosition(X(290.0f), Y(240.0f)); + MenuDisplay_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR); + MenuDisplay_2.AddTitle(TheText.Get("FED_TRA"), false, 0.0f, 0.0f, true); +#ifdef GTA_PC + MenuDisplay_2.SetOptionPosition(X(40.0f), 0.0f, TriggerDisplay_Trails, false); +#else + MenuDisplay_2.SetOptionPosition(X(40.0f), 0.0f, false); +#endif + MenuDisplay_2.m_bTwoState = true; + MenuPage_Display.AddMenu(&MenuDisplay_2); + MenuDisplay_3.SetPosition(X(290.0f), Y(260.0f)); + MenuDisplay_3.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR); + MenuDisplay_3.AddTitle(TheText.Get("FED_SUB"), false, 0.0f, 0.0f, true); + MenuDisplay_3.SetOptionPosition(X(40.0f), 0.0f, false); + MenuDisplay_3.m_bTwoState = true; + MenuPage_Display.AddMenu(&MenuDisplay_3); + MenuDisplay_4.SetPosition(X(290.0f), Y(280.0f)); + MenuDisplay_4.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR); + MenuDisplay_4.AddTitle(TheText.Get("FED_WIS"), false, 0.0f, 0.0f, true); + MenuDisplay_4.SetOptionPosition(X(40.0f), 0.0f, false); + MenuDisplay_4.m_bTwoState = true; + MenuPage_Display.AddMenu(&MenuDisplay_4); + MenuPage_Display.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Display.ActivatePage(); + + + /* Language */ + MenuLanguage_1.m_numOptions = 0; + MenuLanguage_1.SetPosition(X(288.0f), Y(160.0f)); + MenuLanguage_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, SELECTED_TEXT_COLOR); + MenuLanguage_1.AddOption(TheText.Get("FEL_ENG"), 0.0f, 0.0f, TriggerLanguage_Language, false, false); + MenuLanguage_1.AddOption(TheText.Get("FEL_FRE"), 0.0f, Y(20.0f), TriggerLanguage_Language, false, false); + MenuLanguage_1.AddOption(TheText.Get("FEL_GER"), 0.0f, Y(40.0f), TriggerLanguage_Language, false, false); + MenuLanguage_1.AddOption(TheText.Get("FEL_ITA"), 0.0f, Y(60.0f), TriggerLanguage_Language, false, false); + MenuLanguage_1.AddOption(TheText.Get("FEL_SPA"), 0.0f, Y(80.0f), TriggerLanguage_Language, false, false); + MenuPage_Language.AddMenu(&MenuLanguage_1); + MenuPage_Language.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPage_Language.ActivatePage(); + + + /* + * Save zone menu + */ + + CVector2D saveGameTextScale2(X(0.49f), Y(0.7f)); + CVector2D defaultTextScale5(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + + /* Save game */ + + MenuSaveZoneSG_1.m_numOptions = 0; + MenuSaveZoneSG_1.SetPosition(X(200.0f), Y(100.0f)); + MenuSaveZoneSG_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneSG_1.AddOption(TheText.Get("FESZ_SA"), 0.0f, Y(20.0f), TriggerSaveZone_SaveGameSelect, false, false); + MenuSaveZoneSG_1.AddOption(TheText.Get("FESZ_CA"), 0.0f, Y(40.0f), TriggerSaveZone_QuitMenu, false, false); + MenuSaveZoneSG_1.m_defaultCancel = TriggerSaveZone_QuitMenu; + MenuPageSaveZone_SaveGame.AddMenu(&MenuSaveZoneSG_1); + MenuSaveZoneSG_1.SetMenuSelection(1); + MenuPageSaveZone_SaveGame.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_SaveGame.ActivatePage(); + + /* Select slot */ + + MenuSaveZoneSSL_1.m_numOptions = 0; + MenuSaveZoneSSL_1.SetPosition(X(160.0f), Y(100.0f)); + MenuSaveZoneSSL_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneSSL_1.AddOption(TheText.Get("FESZ_CA"), 0.0f, 0.0f, TriggerSaveZone_BackToMainMenuTwoLines, false, false); + MenuSaveZoneSSL_1.SetNewOldTextScale(true, saveGameTextScale2, defaultTextScale5, true); + MenuPageSaveZone_SaveSlots.AddMenu(&MenuSaveZoneSSL_1); + MenuSaveZoneSSL_1.SetMenuSelection(0); + MenuPageSaveZone_SaveSlots.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_SaveSlots.ActivatePage(); + + /* Save successful */ + + MenuSaveZoneSS_1.m_numTexts = 0; + MenuSaveZoneSS_1.SetPosition(X(200.0f), Y(100.0f)); + MenuSaveZoneSS_1.AddText(TheText.Get("FESZ_L1"), X(-40.0f), 0.0f, TITLE_TEXT_COLOR, false); + MenuSaveZoneSS_1.AddText(TheText.Get("FESZ_L2"), X(-40.0f), Y(20.0f), TITLE_TEXT_COLOR, false); + // twice this line? + MenuSaveZoneSS_1.AddText(TheText.Get("FESZ_L2"), X(-40.0f), Y(40.0f), TEXT_COLOR, false); + MenuPageSaveZone_SavedSuccessfully.AddMenu(&MenuSaveZoneSS_1); + MenuSaveZoneSS_2.m_numOptions = 0; + MenuSaveZoneSS_2.SetPosition(X(200.0f), Y(170.0f)); + MenuSaveZoneSS_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneSS_2.AddOption(TheText.Get("FESZ_QU"), X(60.0f), 0.0f, TriggerSaveZone_QuitMenu, false, false); + MenuPageSaveZone_SavedSuccessfully.AddMenu(&MenuSaveZoneSS_2); + MenuPageSaveZone_SavedSuccessfully.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_SavedSuccessfully.ActivatePage(); + + + MenuSaveZoneMSG_1.m_numTexts = 0; + MenuSaveZoneMSG_1.SetPosition(X(170.0f), Y(130.0f)); + MenuSaveZoneMSG_1.AddText(TheText.Get("FESZ_SR"), X(-40.0f), 0.0f, TEXT_COLOR, false); + MenuSaveZoneMSG_1.SetTextsColor(TEXT_COLOR); + MenuSaveZoneMSG_1.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x-20.0f), X(580.0f)); + MenuPageSaveZone_Message.AddMenu(&MenuSaveZoneMSG_1); + MenuSaveZoneMSG_2.m_numOptions = 0; + MenuSaveZoneMSG_2.SetPosition(X(170.0f), Y(180.0f)); + MenuSaveZoneMSG_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneMSG_2.AddOption(TheText.Get("FESZ_OK"), X(40.0f), 0.0f, TriggerSaveZone_QuitMenu, false, false); + MenuPageSaveZone_Message.AddMenu(&MenuSaveZoneMSG_2); + MenuPageSaveZone_Message.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_Message.ActivatePage(); + + + MenuSaveZoneQYN_1.m_numTexts = 0; + MenuSaveZoneQYN_1.SetPosition(X(170.0f), Y(130.0f)); + MenuSaveZoneQYN_1.AddText(TheText.Get("FESZ_SR"), X(-40.0f), 0.0f, TEXT_COLOR, false); + MenuSaveZoneQYN_1.SetTextsColor(TEXT_COLOR); + MenuSaveZoneQYN_1.SetNewOldShadowWrapX(true, X(600.0f+SHADOW_VECTOR.x-20.0f), X(580.0f)); + MenuPageSaveZone_QuestionYesNo.AddMenu(&MenuSaveZoneQYN_1); + MenuSaveZoneQYN_2.m_numOptions = 0; + MenuSaveZoneQYN_2.SetPosition(X(170.0f), Y(180.0f)); + MenuSaveZoneQYN_2.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_YES"), X(80.0f), 0.0f, TriggerSaveZone_QuitMenu, false, false); + MenuSaveZoneQYN_2.AddOption(TheText.Get("FEM_NO"), X(80.0f), Y(20.0f), TriggerSaveZone_QuitMenu, false, false); + MenuPageSaveZone_QuestionYesNo.AddMenu(&MenuSaveZoneQYN_2); + MenuPageSaveZone_QuestionYesNo.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_QuestionYesNo.ActivatePage(); + + /* Format card */ + + MenuSaveZoneFC_1.m_numOptions = 0; + MenuSaveZoneFC_1.SetPosition(X(200.0f), Y(100.0f)); + MenuSaveZoneFC_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneFC_1.AddTitle(TheText.Get("FESZ_FM"), X(-100.0f), 0.0f, false); + MenuSaveZoneFC_1.AddOption(TheText.Get("FEM_NO"), X(40.0f), Y(95.0f), TriggerSaveZone_BackToMainMenu, false, false); + MenuSaveZoneFC_1.AddOption(TheText.Get("FEM_YES"), X(40.0f), Y(75.0f), TriggerSaveZone_FormatCardSelect, false, false); + MenuSaveZoneFC_1.m_defaultCancel = TriggerSaveZone_FormatCardSelect; + MenuPageSaveZone_FormatCard.AddMenu(&MenuSaveZoneFC_1); + MenuPageSaveZone_FormatCard.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_FormatCard.ActivatePage(); + + /* Format error */ + + MenuSaveZoneEF_1.m_numOptions = 0; + MenuSaveZoneEF_1.SetPosition(X(200.0f), Y(100.0f)); + MenuSaveZoneEF_1.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + MenuSaveZoneEF_1.AddTitle(TheText.Get("FESZ_FF"), X(-40.0f), 0.0f, false); + MenuSaveZoneEF_1.AddOption(TheText.Get("FESZ_OK"), X(70.0f), Y(20.0f), TriggerSaveZone_FormatFailedOK, false, false); + MenuPageSaveZone_ErrorFormat.AddMenu(&MenuSaveZoneEF_1); + MenuPageSaveZone_ErrorFormat.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + MenuPageSaveZone_ErrorFormat.ActivatePage(); + + pActiveMenuPage = &MenuPage_Stats; + pActiveMenuPage->ActivatePage(); + + InitialiseMenuContents(); + + m_bWantToUpdateContent = false; +} + +void +CMenuManager::InitialiseChangedLanguageSettings(void) +{ + if ( bFrontEnd_ReloadObrTxtGxt ) + { + bFrontEnd_ReloadObrTxtGxt = false; + + CTimer::Stop(); + TheText.Unload(); + TheText.Load(); + CTimer::Update(); + + FrontEndMenuManager.AnaliseMenuContents(); + CGame::frenchGame = false; + CGame::germanGame = false; + if ( m_PrefsAllowNastyGame ) + CGame::nastyGame = true; + + for ( int32 i = 0; i < NUM_PAGES; i++ ) + { + BUTTONTAB_TEXT_X_SCALES[i] = 1.0f; + PANEL_TEXT_X_SCALES[i] = 1.0f; + } + + switch ( m_PrefsLanguage ) + { + case LANGUAGE_AMERICAN: + { + MENU_TEXT_SIZE_X = 0.644f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.35f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + BUTTONTAB_TEXT_X_SCALES[6] = 0.94f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.4564f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.49f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = 0.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 154.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 0.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + + case LANGUAGE_FRENCH: + { + CGame::frenchGame = true; + if ( m_PrefsAllowNastyGame ) + CGame::nastyGame = false; + + MENU_TEXT_SIZE_X = 0.504f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.32f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + BUTTONTAB_TEXT_X_SCALES[0] = 0.84f; + BUTTONTAB_TEXT_X_SCALES[3] = 0.84f; + PANEL_TEXT_X_SCALES[1] = 0.8f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.385f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.455f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = -15.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 184.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 20.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + + case LANGUAGE_GERMAN: + { + CGame::germanGame = true; + if ( m_PrefsAllowNastyGame ) + CGame::nastyGame = false; + + MENU_TEXT_SIZE_X = 0.546f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.32f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.35f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.434f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = -15.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 154.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 20.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.7f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + + case LANGUAGE_ITALIAN: + { + MENU_TEXT_SIZE_X = 0.574f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.32f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + BUTTONTAB_TEXT_X_SCALES[0] = 0.86f; + PANEL_TEXT_X_SCALES[1] = 0.9f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.385f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.42f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = 10.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 194.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 10.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + + case LANGUAGE_SPANISH: + { + MENU_TEXT_SIZE_X = 0.546f; + MENU_TEXT_SIZE_Y = 0.84f;//0.96f; + + BUTTONTAB_TEXT_SIZE_X = 0.35f; + BUTTONTAB_TEXT_SIZE_Y = 0.7f;//0.8f; + + BUTTONTAB_TEXT_X_SCALES[0] = 0.78f; + PANEL_TEXT_X_SCALES[1] = 0.95f; + + CONTR_DESCR_NEW_TEXTSCALE.x = 0.364f; + CONTR_DESCR_NEW_TEXTSCALE.y = 0.63f;//0.72f; + + CONFIGS_NEW_TEXTSCALE.x = 0.455f; + CONFIGS_NEW_TEXTSCALE.y = 0.7f;//0.8f; + + AUDIO_OUTPUT_POS.x = 10.0f; + AUDIO_OUTPUT_POS.y = 0.0f; + + AUDIO_RSTATION_POS.x = 124.0f; + AUDIO_RSTATION_POS.y = 0.0f; + + DISPLAY_BRIGHTNESS_POS.x = 30.0f; + DISPLAY_BRIGHTNESS_POS.y = 0.0f; + + MEMCARD_ACCESS_MSG_SIZE_X = 0.84f; + MEMCARD_ACCESS_MSG_SIZE_Y = 1.12f;//1.28f; + + break; + } + } + } +} + +void +CMenuManager::InitialiseMenuContents(void) +{ + if ( m_bWantToUpdateContent == false ) + { + m_bWantToUpdateContent = true; + + m_pageState = PAGESTATE_NORMAL; + + switch ( CPad::GetPad(0)->GetMode() ) + { + case 3: m_PrefsControllerConfig = CONFIG_4; break; + case 2: m_PrefsControllerConfig = CONFIG_3; break; + case 1: m_PrefsControllerConfig = CONFIG_2; break; + case 0: m_PrefsControllerConfig = CONFIG_1; break; + } + + MenuControls_1.SetMenuSelection(m_PrefsControllerConfig); + MenuControls_5.SetMenuSelection(m_PrefsUseVibration); + + MenuAudio_1.SetMenuSelection(m_PrefsMusicVolume / 127.0f * 100.0f + 0.5f); + MenuAudio_2.SetMenuSelection(m_PrefsSfxVolume / 127.0f * 100.0f + 0.5f); + MenuAudio_3.SetMenuSelection(m_PrefsRadioStation); + MenuAudio_4.SetMenuSelection(m_PrefsStereoMono); + + MenuDisplay_1.SetMenuSelection(m_PrefsBrightness / 512.0f * 100.0f + 0.5f); +#ifdef PS2 + m_PrefsShowTrails = BlurOn; +#else + m_PrefsShowTrails = CMBlur::BlurOn; +#endif + MenuDisplay_2.SetMenuSelection(m_PrefsShowTrails); + MenuDisplay_3.SetMenuSelection(m_PrefsShowSubtitles); + MenuDisplay_4.SetMenuSelection(m_PrefsUseWideScreen); + + MenuLanguage_1.SetMenuSelection(m_PrefsLanguage); + + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + + MenuBriefs_1.m_numTexts = 0; + MenuBriefs_1.AddText(TheText.Get("FEB_PMB"), 0.0f, 0.0f, TITLE_TEXT_COLOR, 0); // Previous Mission Briefs: + + static wchar StringsToDisplay[NUMPREVIOUSBRIEFS][256]; + + CRGBA newColor; + int32 brierY = 36; + + for ( int32 i = NUMPREVIOUSBRIEFS-1; i >= 0; i-- ) + { + tPreviousBrief &brief = CMessages::PreviousBriefs[i]; + if (brief.m_pText) + { + CMessages::InsertNumberInString(brief.m_pText, + brief.m_nNumber[0], brief.m_nNumber[1], + brief.m_nNumber[2], brief.m_nNumber[3], + brief.m_nNumber[4], brief.m_nNumber[5], StringsToDisplay[i]); + CMessages::InsertStringInString(StringsToDisplay[i], brief.m_pString); + + newColor = TEXT_COLOR; + FilterOutColorMarkersFromString(StringsToDisplay[i], newColor); + + if (newColor != TEXT_COLOR) + { + newColor.r /= 2; + newColor.g /= 2; + newColor.b /= 2; + } + MenuBriefs_1.AddText(StringsToDisplay[i], 0.0f, YF((float)brierY), newColor, 0); + brierY += 54; + } + } + + MenuStats_1.m_scrollPosition = 0.0f; + MenuStats_1.ResetNumberOfTextLines(); + + nStatLinesIndex = 0; + + #define STAT_HEADER(str) do { MenuStats_1.AddTextLine(TheText.Get(str), nil); } while(0) + #define STAT_PARAM(str) do { MenuStats_1.AddTextLine(nil, TheText.Get(str)); } while(0) + #define STAT_LINE(str, left, isFloat, right) do { MenuStats_1.AddTextLine(TheText.Get(str), PrintStatLine(str, left, isFloat, right)); } while(0) + + int32 nTemp; + + STAT_HEADER("PL_STAT"); + + int32 percentCompleted = (CStats::TotalProgressInGame == 0 ? 0 : CStats::ProgressMade * 100.0f / (CGame::nastyGame ? CStats::TotalProgressInGame : CStats::TotalProgressInGame - 1)); + percentCompleted = Min(percentCompleted, 100); + + STAT_LINE("PER_COM", &percentCompleted, 0, nil); + + STAT_LINE("NMISON", &CStats::MissionsGiven, 0, nil); + + STAT_LINE("FEST_MP", &CStats::MissionsPassed, 0, &CStats::TotalNumberMissions); + + if ( CGame::nastyGame ) + STAT_LINE("FEST_RP", &CStats::NumberKillFrenziesPassed, 0, &CStats::TotalNumberKillFrenzies); + + CPlayerInfo &player = CWorld::Players[CWorld::PlayerInFocus]; + float packagesPercent = 0.0f; + if (player.m_nTotalPackages != 0) + packagesPercent = player.m_nCollectedPackages * 100.0f / player.m_nTotalPackages; + int32 nPackagesPercent = packagesPercent; + nTemp = 100; + + STAT_LINE("PERPIC", &nPackagesPercent, 0, &nTemp); + + STAT_LINE("NOUNIF", &CStats::NumberOfUniqueJumpsFound, 0, &CStats::TotalNumberOfUniqueJumps); + + STAT_LINE("DAYSPS", &CStats::DaysPassed, 0, nil); + + if ( CGame::nastyGame ) + { + STAT_LINE("PE_WAST", &CStats::PeopleKilledByPlayer, 0, nil); + STAT_LINE("PE_WSOT", &CStats::PeopleKilledByOthers, 0, nil); + } + + STAT_LINE("CAR_EXP", &CStats::CarsExploded, 0, nil); + + STAT_LINE("TM_BUST", &CStats::TimesArrested, 0, nil); + + STAT_LINE("TM_DED", &CStats::TimesDied, 0, nil); + + nTemp = CStats::PedsKilledOfThisType[PEDTYPE_GANG9] + CStats::PedsKilledOfThisType[PEDTYPE_GANG8] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG7] + CStats::PedsKilledOfThisType[PEDTYPE_GANG6] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG5] + CStats::PedsKilledOfThisType[PEDTYPE_GANG4] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG3] + CStats::PedsKilledOfThisType[PEDTYPE_GANG2] + + CStats::PedsKilledOfThisType[PEDTYPE_GANG1]; + STAT_LINE("GNG_WST", &nTemp, 0, nil); + + nTemp = CStats::PedsKilledOfThisType[PEDTYPE_CRIMINAL]; + STAT_LINE("DED_CRI", &nTemp, 0, nil); + + STAT_LINE("HEL_DST", &CStats::HelisDestroyed, 0, nil); + + STAT_LINE("KGS_EXP", &CStats::KgsOfExplosivesUsed, 0, nil); + + nTemp = (CStats::InstantHitsFiredByPlayer == 0 ? 0 : CStats::InstantHitsHitByPlayer * 100.0f / CStats::InstantHitsFiredByPlayer); + STAT_LINE("ACCURA", &nTemp, 0, nil); + + if (CStats::ElBurroTime > 0) + STAT_LINE("ELBURRO", &CStats::ElBurroTime, 0, nil); + + if (CStats::Record4x4One > 0) + STAT_LINE("FEST_R1", &CStats::Record4x4One, 0, nil); + + if (CStats::Record4x4Two > 0) + STAT_LINE("FEST_R2", &CStats::Record4x4Two, 0, nil); + + if (CStats::Record4x4Three > 0) + STAT_LINE("FEST_R3", &CStats::Record4x4Three, 0, nil); + + if (CStats::Record4x4Mayhem > 0) + STAT_LINE("FEST_RM", &CStats::Record4x4Mayhem, 0, nil); + + if (CStats::LongestFlightInDodo > 0) + STAT_LINE("FEST_LF", &CStats::LongestFlightInDodo, 0, nil); + + if (CStats::TimeTakenDefuseMission > 0) + STAT_LINE("FEST_BD", &CStats::TimeTakenDefuseMission, 0, nil); + + STAT_LINE("CAR_CRU", &CStats::CarsCrushed, 0, nil); + + if (CStats::HighestScores[0] > 0) + { + STAT_HEADER("FEST_BB"); + STAT_LINE("FEST_H0", &CStats::HighestScores[0], 0, nil); + } + + int32 hs = 0; + for ( int32 i = 1; i < 5; i++ ) + hs += CStats::HighestScores[i]; + + if (hs > 0) + STAT_HEADER("FEST_GC"); + + if (CStats::HighestScores[1] > 0) + STAT_LINE("FEST_H1", &CStats::HighestScores[1], 0, nil); + + if (CStats::HighestScores[2] > 0) + STAT_LINE("FEST_H2", &CStats::HighestScores[2], 0, nil); + + if (CStats::HighestScores[3] > 0) + STAT_LINE("FEST_H3", &CStats::HighestScores[3], 0, nil); + + if (CStats::HighestScores[4] > 0) + STAT_LINE("FEST_H4", &CStats::HighestScores[4], 0, nil); + + STAT_LINE("FESTDFM", &CStats::DistanceTravelledOnFoot, 0, nil); + STAT_LINE("FESTDCM", &CStats::DistanceTravelledInVehicle, 0, nil); + STAT_LINE("MMRAIN", &CStats::mmRain, 0, nil); + nTemp = (int32)CStats::MaximumJumpDistance; + STAT_LINE("MXCARDM", &nTemp, 0, nil); + nTemp = (int32)CStats::MaximumJumpHeight; + STAT_LINE("MXCARJM", &nTemp, 0, nil); + + STAT_LINE("MXFLIP", &CStats::MaximumJumpFlips, 0, nil); + STAT_LINE("MXJUMP", &CStats::MaximumJumpSpins, 0, nil); + + STAT_HEADER("BSTSTU"); + + switch (CStats::BestStuntJump) + { + case 1: STAT_PARAM("INSTUN"); break; + case 2: STAT_PARAM("PRINST"); break; + case 3: STAT_PARAM("DBINST"); break; + case 4: STAT_PARAM("DBPINS"); break; + case 5: STAT_PARAM("TRINST"); break; + case 6: STAT_PARAM("PRTRST"); break; + case 7: STAT_PARAM("QUINST"); break; + case 8: STAT_PARAM("PQUINS"); break; + default: STAT_PARAM("NOSTUC"); break; + } + + STAT_LINE("PASDRO", &CStats::PassengersDroppedOffWithTaxi, 0, nil); + STAT_LINE("MONTAX", &CStats::MoneyMadeWithTaxi, 0, nil); + STAT_LINE("FEST_LS", &CStats::LivesSavedWithAmbulance, 0, nil); + STAT_LINE("FEST_HA", &CStats::HighestLevelAmbulanceMission, 0, nil); + STAT_LINE("FEST_CC", &CStats::CriminalsCaught, 0, nil); + STAT_LINE("FEST_FE", &CStats::FiresExtinguished, 0, nil); + int32 rnd = ((CGeneral::GetRandomNumber() & 255) + 100) * 2384; + STAT_LINE("DAYPLC", &rnd, 0, nil); + + #undef STAT_LINE + + MenuStats_2.m_numTexts = 0; + MenuStats_2.AddText(TheText.Get("CRIMRA"), 0.0f, 0.0f, CRIM_RATING_TEXT_COLOR, 0); + + char rating[16]; + wchar urating[16]; + sprintf(rating, " %d", CStats::FindCriminalRatingNumber()); + AsciiToUnicode(rating, urating); + + wchar *pStatLine = aStatLines[nStatLinesIndex++]; + UnicodeStrcpy(pStatLine, CStats::FindCriminalRatingString()); + UnicodeStrcat(pStatLine, urating); + + MenuStats_2.AddText(pStatLine, X(MenuStats_1.m_width), 0.0f, CRIM_RATING_TEXT_COLOR, 1); + + MenuSaveZoneSG_1.SetMenuSelection(1); + MenuSaveZoneFC_1.SetMenuSelection(1); + } +} + + +void +CMenuManager::AnaliseMenuContents(void) +{ + if ( m_bWantToUpdateContent ) + { + m_bWantToUpdateContent = false; + + m_PrefsControllerConfig = (CONTRCONFIG)MenuControls_1.GetMenuSelection(); + switch ( m_PrefsControllerConfig ) + { + case CONFIG_4: CPad::GetPad(0)->SetMode(3); break; + case CONFIG_3: CPad::GetPad(0)->SetMode(2); break; + case CONFIG_2: CPad::GetPad(0)->SetMode(1); break; + case CONFIG_1: CPad::GetPad(0)->SetMode(0); break; + } + + m_PrefsUseVibration = MenuControls_5.m_title.m_bSelected; + + m_PrefsMusicVolume = float(MenuAudio_1.GetMenuSelection())/100.0f*127.0f+0.5f; + m_PrefsSfxVolume = float(MenuAudio_2.GetMenuSelection())/100.0f*127.0f+0.5f; + m_PrefsRadioStation = MenuAudio_3.GetMenuSelection(); + m_PrefsStereoMono = MenuAudio_4.GetMenuSelection(); + m_PrefsBrightness = float(MenuDisplay_1.GetMenuSelection()) / 100.0f*512.0f + 0.5f; + m_PrefsShowTrails = MenuDisplay_2.GetMenuSelection(); + m_PrefsShowSubtitles = MenuDisplay_3.GetMenuSelection(); + m_PrefsUseWideScreen = MenuDisplay_4.GetMenuSelection(); +#ifdef PS2 + BlurOn = m_PrefsShowTrails; +#else + CMBlur::BlurOn = m_PrefsShowTrails; +#endif + + if ( m_PrefsLanguage != MenuLanguage_1.GetMenuSelection() ) + { + m_PrefsLanguage = MenuLanguage_1.GetMenuSelection(); + m_bInitialised = false; + bFrontEnd_ReloadObrTxtGxt = true; + } + } +} + +void +CMenuManager::InitialiseMenuContentsAfterLoadingGame(void) +{ + if ( MenuLanguage_1.GetMenuSelection() != m_PrefsLanguage ) + { + m_bInitialised = false; + bFrontEnd_ReloadObrTxtGxt = true; + } +} + +void +CMenuManager::DrawFrontEnd(void) +{ + CFont::SetAlphaFade(255.0f); + if(m_bInSaveZone) + DrawFrontEndSaveZone(); + else + DrawFrontEndNormal(); + + if ( MemCardAccessTriggerCaller.CanCall() ) + MemCardAccessTriggerCaller.CallTrigger(); + + DisplayWarningControllerMsg(); +} + +void +CMenuManager::DrawFrontEndNormal(void) +{ + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + if ( bMemoryCardSpecialZone ) + { + static uint8 counter = 0; + + counter++; + + if ( (counter & 63 ) == 0 ) + { + FillMenuWithMemCardFileListing(&MenuSaveLG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_LoadGameLoadGameSelect, nil, 0, 34, 22); + FillMenuWithMemCardFileListing(&MenuSaveDG_2, TriggerSave_BackToMainMenuTwoLines, TriggerSave_DeleteGameDeleteGameSelect, nil, 0, 34, 22); + } + } + + m_fade = 255; + if ( m_nChangePageTimer != 0 && m_nChangePageTimer >= CTimer::GetTimeInMillisecondsPauseMode() ) + m_fade = uint32(float(m_nChangePageTimer - CTimer::GetTimeInMillisecondsPauseMode()) / 250.0f * 255.0f); + + m_someAlpha = 255; + + m_position.x = 0.0f; + m_position.y = 0.0f; + + if ( m_nStartPauseTimer != 0 && m_nStartPauseTimer >= CTimer::GetTimeInMillisecondsPauseMode() ) + { + float slide = float(m_nStartPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) / 800.0f; + float alpha = 1.0f; + + if ((m_nStartPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) <= 1600) + alpha = float(m_nStartPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) / 400.0f; + + m_someAlpha = 255 - Clamp(alpha, 0.0f, 1.0f) * 255.0f; + + switch ( m_nSlidingDir ) + { + case SLIDE_TO_RIGHT: m_position.x = slide * X(700.0f); break; + case SLIDE_TO_TOP: m_position.y = -(slide * Y(500.0f)); break; + case SLIDE_TO_LEFT: m_position.x = -(slide * X(700.0f)); break; + case SLIDE_TO_BOTTOM: m_position.y = slide * Y(500.0f); break; + default: m_position.y = slide * Y(500.0f); break; + } + } + + if ( m_nEndPauseTimer != 0 && m_nEndPauseTimer >= CTimer::GetTimeInMillisecondsPauseMode() ) + { + float slide = float(m_nEndPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) / 800.0f; + float alpha = float((int32)(m_nEndPauseTimer - CTimer::GetTimeInMillisecondsPauseMode()) + -266) / 533.0f; + + m_someAlpha = Clamp(alpha, 0.0f, 1.0f) * 255.0f; + + switch ( m_nSlidingDir ) + { + case SLIDE_TO_TOP: m_position.y = (1.0f - slide) * Y(500.0f); break; + case SLIDE_TO_RIGHT: m_position.x = (1.0f - slide) * X(700.0f); break; + case SLIDE_TO_LEFT: m_position.x = (1.0f - slide) * X(700.0f); break; + case SLIDE_TO_BOTTOM: m_position.y = -((1.0f - slide) * Y(500.0f)); break; + default: m_position.y = -((1.0f - slide) * Y(500.0f)); break; + } + } + + if ( m_someAlpha < 255 ) + m_fade = m_someAlpha; + + float posX, posY; + + /* Draw splash */ + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + CSprite2d *splash = LoadSplash(nil); + if(splash) + splash->Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + else + // doesn't exist!! + CHud::Sprites[19].Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERMIPNEAREST); + + /* Draw main panel */ + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + CRGBA panelColor(255, 255, 255, m_someAlpha); + m_sprites[FE2_MAINPANEL_UL].Draw( + CRect(m_position.x, m_position.y, m_position.x+SCRW/2.0f, m_position.y+SCRH/2.0f), + panelColor); + m_sprites[FE2_MAINPANEL_UR].Draw( + CRect(m_position.x+SCRW/2.0f, m_position.y, m_position.x+SCRW, m_position.y+SCRH/2.0f), + panelColor); + m_sprites[FE2_MAINPANEL_DL].Draw( + CRect(m_position.x, m_position.y+SCRH/2.0f, m_position.x+SCRW/2.0f, m_position.y+SCRH), + panelColor); + m_sprites[FE2_MAINPANEL_DR].Draw( + CRect(m_position.x+SCRW/2.0f, m_position.y+SCRH/2.0f, m_position.x+SCRW, m_position.y+SCRH), + panelColor); + + /* Draw icon backdrop */ + CRGBA iconColor(255, 255, 255, m_fade*0.75f); + float iconX = 48.0f; + float iconY = 54.0f; + float iconWidth = 540.0f; + float iconHeight = 296.0f; + int32 sprite = FE_ICONBRIEF; + +#ifdef PS2_MENU_USEALLPAGEICONS + switch(m_currentPage) + { + case PAGE_STATS: + sprite = FE_ICONSTATS; + break; + case PAGE_LOAD: + sprite = FE_ICONSAVE; + break; + case PAGE_CONTROLS: + sprite = FE_ICONCONTROLS; + break; + case PAGE_BRIEFS: + sprite = FE_ICONBRIEF; + break; + case PAGE_AUDIO: + sprite = FE_ICONAUDIO; + break; + case PAGE_DISPLAY: + sprite = FE_ICONDISPLAY; + break; + case PAGE_LANGUAGE: + sprite = FE_ICONLANGUAGE; + break; + } +#else + switch(m_currentPage) + { + case PAGE_STATS: + case PAGE_LOAD: + case PAGE_CONTROLS: + sprite = FE_ICONSTATS; // PS2 has the same texture for stats and brief + //sprite = FE_ICONBRIEF; + break; + case PAGE_BRIEFS: + sprite = FE_ICONBRIEF; + break; + case PAGE_AUDIO: + sprite = FE_ICONAUDIO; + break; + case PAGE_DISPLAY: + sprite = FE_ICONDISPLAY; + break; + case PAGE_LANGUAGE: + sprite = FE_ICONLANGUAGE; + break; + } +#endif + m_sprites[sprite].Draw( + CRect_SZ(m_position.x+X(iconX), m_position.y+Y(iconY), X(iconWidth), Y(iconHeight)), + iconColor); + + /* Overwrite tab buttons if entered page */ + bool bOverwriteTab = false; + + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + bOverwriteTab = true; + break; + } + + if( bOverwriteTab ) + { + CRGBA shadow(41, 101, 102, m_someAlpha); + CRGBA green(40, 48, 57, m_someAlpha); + CSprite2d::DrawRect( + CRect_SZ(m_position.x+X(82.0f), m_position.y+Y(408.0f), X(476.0f), Y(18.0f)), + shadow); + CSprite2d::DrawRect( + CRect_SZ(m_position.x+X(82.0f), m_position.y+Y(408.0f), X(476.0f), Y(5.0f)), + green); + } +// stats, load, briefs, controls, audio, display, language + + /* Shadow of panel on top of tab buttons */ + CRGBA panelShadow(96, 96, 96, m_someAlpha*0.375f); + CSprite2d::DrawRect(CRect_SZ(m_position.x+X(87.0f), m_position.y+Y(408.0f), X(464.0f), Y(3.0f)), panelShadow); + /* Draw second shadow - seems unused */ + if ( m_nChangePageTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() < m_nChangePageTimer ) + { + posX = 0.0f; + switch(field_18) + { + case PAGE_STATS: posX = 88.0f; break; + case PAGE_LOAD: posX = 286.0f; break; // actually controls + case PAGE_BRIEFS: posX = 154.0f; break; // actually load + case PAGE_CONTROLS: posX = 220.0f; break; // actually briefs + case PAGE_AUDIO: posX = 352.0f; break; + case PAGE_DISPLAY: posX = 418.0f; break; + case PAGE_LANGUAGE: posX = 484.0f; break; + } + CSprite2d::DrawRect(CRect_SZ(m_position.x+X(posX), m_position.y+Y(411.0f), X(65.0f), Y(3.0f)), panelShadow); + } + + /* Active tab */ + posX = 0.0f; + switch(m_currentPage) + { + case PAGE_STATS: posX = 88.0f; break; + case PAGE_LOAD: posX = 154.0f; break; + case PAGE_BRIEFS: posX = 220.0f; break; + case PAGE_CONTROLS: posX = 286.0f; break; + case PAGE_AUDIO: posX = 352.0f; break; + case PAGE_DISPLAY: posX = 418.0f; break; + case PAGE_LANGUAGE: posX = 484.0f; break; + } + // PAL has 465 for 407 here - and actually 406 seems right + m_sprites[FE2_TABACTIVE].Draw(CRect_SZ(m_position.x+X(posX), m_position.y+YF(465.0f), X(128.0f), Y(32.0f)), CRGBA(255, 255, 255, m_someAlpha)); + + /* Draw page title */ + posX = m_position.x + X(592.0f); + posY = m_position.y + Y(376.0f); + CRGBA fontCol1(255, 193, 71, m_someAlpha); + CRGBA fontCol2(0, 0, 0, m_someAlpha); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetBackgroundOff(); + CFont::SetScale(X(PANEL_TEXT_SIZE_X), Y(PANEL_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetRightJustifyOn(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + const char *key = nil; + switch(m_currentPage) + { + case PAGE_STATS: key = "FEP_STA"; break; + case PAGE_LOAD: key = "FEP_SAV"; break; + case PAGE_BRIEFS: key = "FEP_BRI"; break; + case PAGE_CONTROLS: key = "FEP_CON"; break; + case PAGE_AUDIO: key = "FEP_AUD"; break; + case PAGE_DISPLAY: key = "FEP_DIS"; break; + case PAGE_LANGUAGE: key = "FEP_LAN"; break; + } + CFont::SetScale(X(PANEL_TEXT_SIZE_X*PANEL_TEXT_X_SCALES[m_currentPage]), Y(PANEL_TEXT_SIZE_Y)); + CFont::SetColor(fontCol1); + CFont::PrintString(posX, posY, TheText.Get(key)); + CFont::SetColor(fontCol2); + CFont::PrintString(posX-X(1.0f), posY-Y(1.0f), TheText.Get(key)); + CFont::DrawFonts(); + + /* Draw controller buttons */ + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(0.35f), Y(0.64f)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + CFont::SetColor(CRGBA(16, 16, 16, m_someAlpha)); + switch(m_currentPage) + { + case PAGE_STATS: + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(360.0f), TheText.Get("FEDS_ST")); + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(372.0f), TheText.Get("FEDS_AM")); + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(360.0f), TheText.Get("FEDSSC1")); + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(372.0f), TheText.Get("FEDSSC2")); + break; + + case PAGE_BRIEFS: + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(360.0f), TheText.Get("FEDS_ST")); + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(372.0f), TheText.Get("FEDS_AM")); + break; + + case PAGE_LOAD: + case PAGE_CONTROLS: + case PAGE_AUDIO: + case PAGE_DISPLAY: + case PAGE_LANGUAGE: + { + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(360.0f), TheText.Get("FEDS_SE")); + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(372.0f), TheText.Get("FEDS_BA")); + CFont::PrintString(m_position.x+X(52.0f), m_position.y+Y(384.0f), TheText.Get("FEDS_ST")); + + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(372.0f), TheText.Get("FEDS_AM")); // <>-CHANGE MENU + break; + + case PAGESTATE_HIGHLIGHTED: + case PAGESTATE_SELECTED: + { + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(360.0f+3.5f), TheText.Get("FEA_UP")); // ; + CFont::PrintString(m_position.x+X(242.0f), m_position.y+Y(384.0f-3.5f), TheText.Get("FEA_DO")); // = + CFont::PrintString(m_position.x+X(242.0f-10.0f), m_position.y+Y(372.0f), TheText.Get("FEA_LE")); // < + CFont::PrintString(m_position.x+X(242.0f+11.0f), m_position.y+Y(372.0f), TheText.Get("FEA_RI")); // > + CFont::PrintString(m_position.x+X(242.0f+20.0f), m_position.y+Y(372.0f), TheText.Get("FEDSAS3")); // - CHANGE SELECTION + + break; + } + } + + break; + } + } + + CFont::DrawFonts(); + + /* Draw tab button texts */ + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetCentreOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + CFont::SetColor(CRGBA(16, 16, 16, m_someAlpha)); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_STATS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(92.0f), m_position.y+Y(408.0f), TheText.Get("FEB_STA")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_LOAD]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(158.0f), m_position.y+Y(408.0f), TheText.Get("FEB_SAV")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_BRIEFS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(224.0f), m_position.y+Y(408.0f), TheText.Get("FEB_BRI")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_CONTROLS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(290.0f), m_position.y+Y(408.0f), TheText.Get("FEB_CON")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_AUDIO]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(356.0f), m_position.y+Y(408.0f), TheText.Get("FEB_AUD")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_DISPLAY]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(422.0f), m_position.y+Y(408.0f), TheText.Get("FEB_DIS")); + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_LANGUAGE]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(488.0f), m_position.y+Y(408.0f), TheText.Get("FEB_LAN")); + + break; + } + + case PAGESTATE_HIGHLIGHTED: + case PAGESTATE_SELECTED: + { + CFont::SetColor(CRGBA(16, 16, 16, m_someAlpha)); + switch(m_currentPage) + { + // PAL has 466 for 408...probably rounded? + case PAGE_STATS: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_STATS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(92.0f), m_position.y+Y(408.0f), TheText.Get("FEB_STA")); + break; + case PAGE_LOAD: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_LOAD]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(158.0f), m_position.y+Y(408.0f), TheText.Get("FEB_SAV")); + break; + case PAGE_BRIEFS: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_BRIEFS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(224.0f), m_position.y+Y(408.0f), TheText.Get("FEB_BRI")); + break; + case PAGE_CONTROLS: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_CONTROLS]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(290.0f), m_position.y+Y(408.0f), TheText.Get("FEB_CON")); + break; + case PAGE_AUDIO: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_AUDIO]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(356.0f), m_position.y+Y(408.0f), TheText.Get("FEB_AUD")); + break; + case PAGE_DISPLAY: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_DISPLAY]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(422.0f), m_position.y+Y(408.0f), TheText.Get("FEB_DIS")); + break; + case PAGE_LANGUAGE: + CFont::SetScale(X(BUTTONTAB_TEXT_SIZE_X*BUTTONTAB_TEXT_X_SCALES[PAGE_LANGUAGE]), Y(BUTTONTAB_TEXT_SIZE_Y)); + CFont::PrintString(m_position.x+X(488.0f), m_position.y+Y(408.0f), TheText.Get("FEB_LAN")); + break; + } + + break; + } + } + + CFont::DrawFonts(); + + pActiveMenuPage = nil; + switch(m_currentPage) + { + case PAGE_STATS: pActiveMenuPage = &MenuPage_Stats; break; + case PAGE_LOAD: pActiveMenuPage = pMenuSave; break; + case PAGE_BRIEFS: pActiveMenuPage = &MenuPage_Briefs; break; + case PAGE_CONTROLS: pActiveMenuPage = &MenuPage_Controls; break; + case PAGE_AUDIO: pActiveMenuPage = &MenuPage_Audio; break; + case PAGE_DISPLAY: pActiveMenuPage = &MenuPage_Display; break; + case PAGE_LANGUAGE: pActiveMenuPage = &MenuPage_Language; break; + } + + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); // 600.0f + CFont::SetRightJustifyWrap(X(38.0f)); + + if(m_currentPage == PAGE_LANGUAGE) + { + CFont::SetCentreOn(); + CFont::SetCentreSize(SCRW-X(40.0f)); // 600.0f + } + + if ( m_nEndPauseTimer != 0 ) + { + switch ( m_currentPage ) + { + case PAGE_LOAD: + case PAGE_BRIEFS: + case PAGE_CONTROLS: + break; + + default: + CFont::SetWrapx(X(1200.0f)); + break; + } + } + + if(pActiveMenuPage) + { + pActiveMenuPage->SetAlpha(m_fade); + + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + pActiveMenuPage->DrawNormal(m_position.x, m_position.y); + break; + + case PAGESTATE_HIGHLIGHTED: + pActiveMenuPage->DrawHighlighted(CRGBA(rgbaATC.r, rgbaATC.g, rgbaATC.b, m_fade), m_position.x, m_position.y); + break; + + case PAGESTATE_SELECTED: + pActiveMenuPage->Draw(CRGBA(rgbaATC.r, rgbaATC.g, rgbaATC.b, m_fade), CRGBA(MENU_SELECTED_COLOR.r, MENU_SELECTED_COLOR.g, MENU_SELECTED_COLOR.b, m_fade), m_position.x, m_position.y); + break; + } + } + + CFont::DrawFonts(); + CFont::DrawFonts(); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSWRAP); +} + +void +CMenuManager::DrawFrontEndSaveZone(void) +{ + if ( bMemoryCardSpecialZone ) + { + static uint8 counter = 0; + counter++; + if ( counter & 63 ) + { + FillMenuWithMemCardFileListing(&MenuSaveZoneSSL_1, TriggerSaveZone_BackToMainMenuTwoLines, TriggerSaveZone_SaveSlots, nil, 0, 34, 22); + + if ( TheMemoryCard.GetError() == CMemoryCard::ERR_NOFORMAT ) + { + pActiveMenuPage = &MenuPageSaveZone_FormatCard; + pActiveMenuPage->ActivatePage(); + bMemoryCardSpecialZone = false; + } + } + } + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + + m_fade = 255; + + CSprite2d::DrawRect(CRect(X(50.0f), Y(50.0f), X(590.0f), Y(398.0f)), CRGBA(0, 0, 0, 175)); //CRect(50.0f, 57.142f, 590.0f, 454.857147f) + + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetRightJustifyWrap(X(70.0f)); + CFont::SetWrapx(SCRW-X(70.0f)); // 570.0f + + if ( pActiveMenuPage ) + { + pActiveMenuPage->SetAlpha(m_fade); + pActiveMenuPage->Draw(CRGBA(rgbaATC.r, rgbaATC.g, rgbaATC.b, m_fade), TITLE_TEXT_COLOR, 0.0f, 0.0f); + } + + + CFont::DrawFonts(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(0.44f), Y(0.68f)); // 0.44f, 0.777143f + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(40.0f)); //600.0f + CFont::SetColor(TEXT_COLOR); + + wchar *text; + if ( pActiveMenuPage == &MenuPageSaveZone_FormatCard + || pActiveMenuPage == &MenuPageSaveZone_SaveSlots + || pActiveMenuPage == &MenuPageSaveZone_SaveGame ) + { + text = TheText.Get("FEDS_SB"); // / button - SELECT " button - BACK + } + else + { + text = TheText.Get("FEDS_SE"); // / button - SELECT + } + + CFont::PrintString(X(180.0f), Y(376.0f), text); // 180.0f, 429.714294f + CFont::DrawFonts(); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); +} + +void +CMenuManager::DrawMemoryCardStartUpMenus() +{ + CFont::SetAlphaFade(255.0f); + bMemoryCardStartUpMenus_ExitNow = false; + + CMenuPage page; // + 0x40 data + CMenuMultiChoiceTriggered MCMenu; + MCMenu.SetPosition(X(320.0f), Y(150.0f)); //171.428574f + + switch ( TheMemoryCard.CheckCardStateAtGameStartUp(CARD_ONE) ) + { + case CMemoryCard::MCSTATE_NEED_200KB: // 200KB + { + // There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCGNSP"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_NEED_500KB: // 500KB + { + // There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCDNSP"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_OK: + case CMemoryCard::MCSTATE_NOCARD: + { + return; + break; + } + } + + MCMenu.AddOption(TheText.Get("FEM_NO"), X(30.0f), Y(110.0f), nil, 0, 0);// 125.714294f + MCMenu.AddOption(TheText.Get("FEM_YES"), X(-30.0f), Y(110.0f), TriggerMCSUM_Yes, 0, 0);// 125.714294f + MCMenu.SetColors(TITLE_TEXT_COLOR, TEXT_COLOR, TEXT_COLOR); + page.AddMenu(&MCMenu); + + MCMenu.GoFirst(); + + page.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + + CTimer::Initialise(); + CTimer::StartUserPause(); + + while ( !bMemoryCardStartUpMenus_ExitNow ) + { +#ifdef GTA_PC + HandleExit(); + + if(RsGlobal.quit) + return; +#endif + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadLeftJustDown() ) + page.GoLeft(); + if ( CPad::GetPad(0)->GetDPadRightJustDown() ) + page.GoRight(); + if ( CPad::GetPad(0)->GetDPadUpJustDown() ) + page.GoDown(); + if ( CPad::GetPad(0)->GetDPadDownJustDown() ) + page.GoUp(); + if ( CPad::GetPad(0)->GetCrossJustDown() || CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetRightMouseJustDown() ) + page.SelectCurrentOptionUnderCursor(); + + if ( CPad::GetPad(0)->GetCircleJustDown() || CPad::GetPad(0)->GetEscapeJustDown() ) + ; +#else + if ( CPad::GetPad(0)->GetDPadLeftJustDown() ) + page.GoLeft(); + if ( CPad::GetPad(0)->GetDPadRightJustDown() ) + page.GoRight(); + if ( CPad::GetPad(0)->GetDPadUpJustDown() ) + page.GoDown(); + if ( CPad::GetPad(0)->GetDPadDownJustDown() ) + page.GoUp(); + if ( CPad::GetPad(0)->GetCrossJustDown() ) + page.SelectCurrentOptionUnderCursor(); + if ( CPad::GetPad(0)->GetCircleJustDown() ) + ; +#endif + + static int32 MemCardStatusWaiter = 0; + + MemCardStatusWaiter++; + + if ( MemCardStatusWaiter > 120 ) + { + MemCardStatusWaiter = 0; + + switch ( TheMemoryCard.CheckCardStateAtGameStartUp(CARD_ONE) ) + { + case CMemoryCard::MCSTATE_NEED_200KB: + { + // There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCGNSP"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_NEED_500KB: + { + // There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCDNSP"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_NOCARD: + { + // There is no Memory Card (PS2) in MEMORY CARD slot 1. Do you wish to start? (YES or NO) + MCMenu.AddTitle(TheText.Get("MCSTNS"), 0.0f, 0.0f, 0); + break; + } + + case CMemoryCard::MCSTATE_OK: + { + bMemoryCardStartUpMenus_ExitNow = true; + break; + } + } + } + + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + CFont::InitPerFrame(); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + CSprite2d *splash = LoadSplash("splash1"); + splash->Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + + SetRandomActiveTextlineColor(1); + + CRGBA col(rgbaATC.r, rgbaATC.g, rgbaATC.b, 255); + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetScale(X(MENU_TEXT_SIZE_X), Y(MENU_TEXT_SIZE_Y)); + CFont::SetPropOn(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(60.0f)); // 580.0f + CFont::SetCentreOn(); + CFont::SetCentreSize(SCRW-X(120.0f)); // 520.0f + + MCMenu.Draw(col, TITLE_TEXT_COLOR, 0.0f, 0.0f); + CFont::DrawFonts(); + + CFont::SetFontStyle(FONT_BANK); + CFont::SetScale(X(0.4f), Y(0.64f)); // 0.731429 + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetWrapx(SCRW-X(60.0f)); // 580.0f + CFont::SetColor(TEXT_COLOR); + + + CPlaceableShText text; + text.SetPosition(X(240.0f), Y(378.0f), false); // 432.000000 + text.SetColor(TEXT_COLOR); + text.m_text = TheText.Get("FEDS_SE"); // / button - SELECT + text.SetShadows(true, TEXT_SHADOW_COLOR, SHADOW_VECTOR); + text.Draw(0.0f, 0.0f); + + CFont::DrawFonts(); + DisplayWarningControllerMsg(); + DoRWStuffEndOfFrame(); + CPad::UpdatePads(); + CTimer::Update(); + } + + CTimer::EndUserPause(); + CTimer::Stop(); + + for ( int32 i = 0; i < 100; i++ ) + { +#ifdef GTA_PC + HandleExit(); +#endif + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + CSprite2d *splash = LoadSplash("splash1"); + splash->Draw(CRect(0.0f, 0.0f, SCRW, SCRH), BACKGROUND_SPLASH_COLOR); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + + DoRWStuffEndOfFrame(); + } +} + +void +CMenuManager::Process(void) +{ + if ( m_bSaveMenuActive || m_bInSaveZone || TheCamera.GetScreenFadeStatus() == FADE_0 ) + { + InitialiseMenusOnce(); + m_bWantToRestart = false; + WorkOutMenuState(false); + + if ( m_bMenuActive ) + { + if ( !m_bInSaveZone ) + LoadAllTextures(); + InitialiseMenuContents(); + SetRandomActiveTextlineColor(0); + ProcessControllerInput(); + } + else + { + AnaliseMenuContents(); + pMenuSave = &MenuPage_SaveBasic; + m_pageState = PAGESTATE_NORMAL; + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + UnloadTextures(); + m_bInSaveZone = false; + m_bRenderGameInMenu = false; + gErrorSampleTriggered = true; + } + } +} + +void +CMenuManager::WorkOutMenuState(uint8 bExit) +{ +#ifdef GTA_PC + bool bIsStartPressed = CPad::GetPad(0)->GetStartJustDown() || (m_pageState == PAGESTATE_NORMAL && CPad::GetPad(0)->GetEscapeJustDown()); +#else + bool bIsStartPressed = CPad::GetPad(0)->GetStartJustDown(); +#endif + bool bIsCreditsOrDraw = CCredits::AreCreditsDone() || m_bMenuActive; + bool bIsDemoOrDraw = m_bMenuActive || CGame::bDemoMode; + + if ( (bIsStartPressed && bIsCreditsOrDraw) || bExit || (!bIsDemoOrDraw && CPad::IsNoOrObsolete()) ) + { + if ( m_nStartPauseTimer == 0 && m_nEndPauseTimer == 0 ) + { + m_bMenuActive = !m_bMenuActive; + + if ( !m_bMenuActive ) + { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + gMusicPlaying = false; + bMemoryCardSpecialZone = false; + bIgnoreTriangleButton = false; + + m_bMenuActive = true; + + m_nEndPauseTimer = CTimer::GetTimeInMillisecondsPauseMode() + 800; + + if ( m_currentPage == PAGE_CONTROLS || m_currentPage == PAGE_BRIEFS || m_currentPage == PAGE_LOAD ) + { + m_nSlidingDir = CGeneral::GetRandomNumber() & (SLIDE_MAX-1); + + switch ( m_nSlidingDir ) //m_nSlidingDir &= ~1; + { + case SLIDE_TO_LEFT: m_nSlidingDir = SLIDE_TO_TOP; break; + case SLIDE_TO_RIGHT: m_nSlidingDir = SLIDE_TO_BOTTOM; break; + } + + m_position.y = Y(500.0f); // 571.428589f; + } + } + else + { + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + + if ( DMAudio.GetRadioInCar() < 9 ) + m_PrefsRadioStation = DMAudio.GetRadioInCar(); + else + m_PrefsRadioStation = CGeneral::GetRandomNumber() % 9; + + CTimer::StartUserPause(); + CPad::StopPadsShaking(); + m_nStartPauseTimer = CTimer::GetTimeInMillisecondsPauseMode() + 800; + m_nSlidingDir = CGeneral::GetRandomNumber() & (SLIDE_MAX-1); + + switch ( m_nSlidingDir ) + { + case SLIDE_TO_RIGHT: m_position.y = Y(612.5f); break; + case SLIDE_TO_LEFT: m_position.y = Y(612.5f); break; + case SLIDE_TO_BOTTOM: m_position.y = Y(500.0f); break; + case SLIDE_TO_TOP: m_position.y = Y(500.0f); break; + default: m_position.y = Y(500.0f); break; + } + + if ( m_currentPage == PAGE_CONTROLS || m_currentPage == PAGE_BRIEFS ) + { + m_nSlidingDir = CGeneral::GetRandomNumber() & (SLIDE_MAX-1); + + switch ( m_nSlidingDir ) //m_nSlidingDir &= ~1; + { + case SLIDE_TO_LEFT: m_nSlidingDir = SLIDE_TO_TOP; break; + case SLIDE_TO_RIGHT: m_nSlidingDir = SLIDE_TO_BOTTOM; break; + } + + m_position.y = Y(500.0f); //571.428589f + } + } + } + } + + if ( m_bSaveMenuActive && !m_bInSaveZone && !TheMemoryCard._bunk2) + { + m_bSaveMenuActive = false; + m_bInSaveZone = true; + m_bRenderGameInMenu = true; + m_bMenuActive = true; + CTimer::StartUserPause(); + pActiveMenuPage = &MenuPageSaveZone_SaveGame; + } + + if ( m_pageState == PAGESTATE_NORMAL && gMusicPlaying ) + { + DMAudio.StopFrontEndTrack(); + gMusicPlaying = false; + } + + if ( m_nChangePageTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nChangePageTimer ) + { + m_nChangePageTimer = 0; + pMenuSave = &MenuPage_SaveBasic; + m_currentPage = m_newPage; + } + + if ( m_nPageLeftTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nPageLeftTimer ) + m_nPageLeftTimer = 0; + + if ( m_nPageRightTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nPageRightTimer ) + m_nPageRightTimer = 0; + + if ( m_nStartPauseTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nStartPauseTimer ) + m_nStartPauseTimer = 0; + + if ( m_nEndPauseTimer != 0 && CTimer::GetTimeInMillisecondsPauseMode() >= m_nEndPauseTimer ) + { + m_nEndPauseTimer = 0; + m_bMenuActive = false; + m_bMenuActive = false; + m_bInSaveZone = false; + CTimer::EndUserPause(); + } +} + +void +CMenuManager::ProcessControllerInput(void) +{ + if ( TimeToStopPadShaking != 0 && TimeToStopPadShaking < CTimer::GetTimeInMillisecondsPauseMode() ) + { + CPad::StopPadsShaking(); + TimeToStopPadShaking = 0; + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadLeft() || CPad::GetPad(0)->GetLeft() ) +#else + if ( CPad::GetPad(0)->GetDPadLeft() ) +#endif + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoLeftStill(); + break; + } + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadRight() || CPad::GetPad(0)->GetRight() ) +#else + if ( CPad::GetPad(0)->GetDPadRight() ) +#endif + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoRightStill(); + break; + } + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadLeftJustDown() || CPad::GetPad(0)->GetLeftJustDown() ) +#else + if ( CPad::GetPad(0)->GetDPadLeftJustDown() ) +#endif + ProcessDPadLeftJustDown(); + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadRightJustDown() || CPad::GetPad(0)->GetRightJustDown() ) +#else + if ( CPad::GetPad(0)->GetDPadRightJustDown() ) +#endif + ProcessDPadRightJustDown(); + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadUp() || CPad::GetPad(0)->GetUp() ) +#else + if ( CPad::GetPad(0)->GetDPadUp() ) +#endif + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + if ( m_currentPage == PAGE_STATS ) + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoUpStill(); + } + break; + } + + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoUpStill(); + break; + } + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadDown() || CPad::GetPad(0)->GetDown() ) +#else + if ( CPad::GetPad(0)->GetDPadDown() ) +#endif + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + if ( m_currentPage == PAGE_STATS ) + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoDownStill(); + } + + break; + } + case PAGESTATE_HIGHLIGHTED: + break; + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoDownStill(); + break; + } + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadUpJustDown() || CPad::GetPad(0)->GetUpJustDown() ) +#else + if ( CPad::GetPad(0)->GetDPadUpJustDown() ) +#endif + ProcessDPadUpJustDown(); + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetDPadDownJustDown() || CPad::GetPad(0)->GetDownJustDown() ) +#else + if ( CPad::GetPad(0)->GetDPadDownJustDown() ) +#endif + ProcessDPadDownJustDown(); + + if ( CPad::GetPad(0)->GetLeftShoulder1JustDown() ) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + ProcessDPadLeftJustDown(); + break; + + case PAGESTATE_HIGHLIGHTED: + case PAGESTATE_SELECTED: + break; + } + } + + if ( CPad::GetPad(0)->GetRightShoulder1JustDown() ) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + ProcessDPadRightJustDown(); + break; + + case PAGESTATE_HIGHLIGHTED: + case PAGESTATE_SELECTED: + break; + } + } + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetCrossJustDown() || CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetRightMouseJustDown() ) +#else + if ( CPad::GetPad(0)->GetCrossJustDown() ) +#endif + ProcessDPadCrossJustDown(); + +#ifdef GTA_PC + if ( CPad::GetPad(0)->GetTriangleJustDown() || CPad::GetPad(0)->GetBackspaceJustDown() || (m_pageState != PAGESTATE_NORMAL && CPad::GetPad(0)->GetEscapeJustDown()) ) +#else + if ( CPad::GetPad(0)->GetTriangleJustDown() ) +#endif + ProcessDPadTriangleJustDown(); +} + + +void +CMenuManager::ProcessDPadLeftJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoLeft(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveZoneSSL_1 ) + { + if ( MenuSaveZoneSSL_1.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + else + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + if ( !bMemoryCardSpecialZone && !m_bInSaveZone ) + { + if ( m_nChangePageTimer == 0 ) + { + if ( --m_newPage < PAGE_FIRST ) m_newPage = PAGE_LAST; + + m_nPageLeftTimer = CTimer::GetTimeInMillisecondsPauseMode() + 300; + m_nPageRightTimer = 0; + m_nChangePageTimer = CTimer::GetTimeInMillisecondsPauseMode() + 250; + field_18 = m_newPage; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } + } + + break; + } + + case PAGESTATE_HIGHLIGHTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoLeftMenuOnPage(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoLeft(); + + if ( m_currentPage == PAGE_AUDIO) + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_1 ) + ; + else if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( m_currentPage == PAGE_DISPLAY) + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuDisplay_1 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 ) + { + if ( MenuSaveDG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + { + if ( MenuSaveLG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + + break; + } + } + } +} + +void +CMenuManager::ProcessDPadRightJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoRight(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveZoneSSL_1 ) + { + if ( MenuSaveZoneSSL_1.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + else + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + if ( !bMemoryCardSpecialZone && !m_bInSaveZone ) + { + if ( m_nChangePageTimer == 0 ) + { + if ( ++m_newPage > PAGE_LAST ) m_newPage = PAGE_FIRST; + + m_nPageLeftTimer = 0; + m_nPageRightTimer = CTimer::GetTimeInMillisecondsPauseMode() + 300; + m_nChangePageTimer = CTimer::GetTimeInMillisecondsPauseMode() + 250; + field_18 = m_newPage; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } + } + + break; + } + + case PAGESTATE_HIGHLIGHTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoRightMenuOnPage(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoRight(); + + if ( m_currentPage == PAGE_AUDIO) + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_1 ) + ; + else if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( m_currentPage == PAGE_DISPLAY) + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuDisplay_1 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 ) + { + if ( MenuSaveDG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + { + if ( MenuSaveLG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + + break; + } + } + } +} + +void +CMenuManager::ProcessDPadUpJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoUp(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveZoneSSL_1 ) + { + if ( MenuSaveZoneSSL_1.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + else + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + break; + + case PAGESTATE_HIGHLIGHTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoUpMenuOnPage(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoUp(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 ) + { + if ( MenuSaveDG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + { + if ( MenuSaveLG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + + break; + } + } + } +} + +void +CMenuManager::ProcessDPadDownJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoDown(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveZoneSSL_1 ) + { + if ( MenuSaveZoneSSL_1.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + } + else + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + break; + + case PAGESTATE_HIGHLIGHTED: + { + if ( pActiveMenuPage ) + pActiveMenuPage->GoDownMenuOnPage(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->GoDown(); + + if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 ) + { + if ( MenuSaveDG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + { + if ( MenuSaveLG_2.m_numOptions < 2 ) + ; + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NAVIGATION, 0); + } + break; + } + } + } +} + +void +CMenuManager::ProcessDPadTriangleJustDown(void) +{ + if ( pActiveMenuPage ) + { + pActiveMenuPage->SelectDefaultCancelAction(); + + if ( m_bMenuActive || m_bInSaveZone ) + { + if ( bIgnoreTriangleButton ) + { + if ( m_bInSaveZone ) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); + else if ( pActiveMenuPage->m_pCurrentControl == &MenuSaveDG_2 || pActiveMenuPage->m_pCurrentControl == &MenuSaveLG_2 ) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); + } + else if ( !bIgnoreTriangleButton ) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + WorkOutMenuState(true); + break; + + case PAGESTATE_HIGHLIGHTED: + m_pageState = PAGESTATE_NORMAL; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + break; + + case PAGESTATE_SELECTED: + { + m_pageState = PAGESTATE_HIGHLIGHTED; + if ( pActiveMenuPage ) + { + if ( pActiveMenuPage->m_numControls == 1 ) + { + m_pageState = PAGESTATE_NORMAL; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); + } + break; + } + } + } + } + } + else + { + if ( !bIgnoreTriangleButton ) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + WorkOutMenuState(false); + break; + + case PAGESTATE_HIGHLIGHTED: + m_pageState = PAGESTATE_NORMAL; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + break; + + case PAGESTATE_SELECTED: + { + m_pageState = PAGESTATE_HIGHLIGHTED; + if ( pActiveMenuPage ) + { + if ( pActiveMenuPage->m_numControls == 1 ) + { + m_pageState = PAGESTATE_NORMAL; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_NEW_PAGE, 0); + } + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_BACK, 0); + } + break; + } + } + } + } +} + +void +CMenuManager::ProcessDPadCrossJustDown(void) +{ + if ( m_bInSaveZone ) + { + if ( pActiveMenuPage ) + pActiveMenuPage->SelectCurrentOptionUnderCursor(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + else + { + if ( m_currentPage != PAGE_STATS && m_currentPage != PAGE_BRIEFS) + { + switch ( m_pageState ) + { + case PAGESTATE_NORMAL: + { + m_pageState = PAGESTATE_HIGHLIGHTED; + if ( pActiveMenuPage ) + { + if ( pActiveMenuPage->m_numControls == 1 ) + m_pageState = PAGESTATE_SELECTED; + } + + switch ( m_currentPage ) + { + case PAGE_AUDIO: + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_1 + || pActiveMenuPage->m_pCurrentControl == &MenuAudio_2 + || pActiveMenuPage->m_pCurrentControl == &MenuAudio_3 + || pActiveMenuPage->m_pCurrentControl == &MenuAudio_4 ) + { + if ( !gMusicPlaying ) + { + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + gMusicPlaying = true; + } + } + else + { + DMAudio.StopFrontEndTrack(); + gMusicPlaying = false; + } + break; + } + } + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + } + + case PAGESTATE_HIGHLIGHTED: + { + m_pageState = PAGESTATE_SELECTED; + DoHackingMenusAtPageBrowse(); + if ( pActiveMenuPage ) + { + if ( pActiveMenuPage->IsActiveMenuTwoState()) + { + m_pageState = PAGESTATE_HIGHLIGHTED; + pActiveMenuPage->ActiveMenuTwoState_SelectNextPosition(); + } + } + + switch ( m_currentPage ) + { + case PAGE_AUDIO: + { + if ( pActiveMenuPage->m_pCurrentControl != &MenuAudio_4 ) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + + break; + } + + default: + { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + DMAudio.StopFrontEndTrack(); + gMusicPlaying = false; + break; + } + } + break; + } + + case PAGESTATE_SELECTED: + { + if ( pActiveMenuPage ) + { + pActiveMenuPage->SelectCurrentOptionUnderCursor(); + + switch ( m_currentPage ) + { + case PAGE_AUDIO: + { + if ( pActiveMenuPage->m_pCurrentControl != &MenuAudio_3 ) + m_pageState = PAGESTATE_HIGHLIGHTED; + break; + } + + case PAGE_LOAD: + case PAGE_LANGUAGE: + break; + + default: + m_pageState = PAGESTATE_HIGHLIGHTED; + break; + } + } + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + break; + } + } + } + } +} + +void +CMenuManager::DoHackingMenusAtPageBrowse(void) +{ + if ( pActiveMenuPage ) + { + switch ( m_currentPage ) + { + case PAGE_CONTROLS: + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuControls_1 ) + { + int32 sel = MenuControls_1.GetMenuSelection(); + MenuControls_1.GoFirst(); + + for ( int32 i = 0; i < sel; i++ ) + MenuControls_1.GoNext(); + } + break; + } + + case PAGE_AUDIO: + { + if ( pActiveMenuPage->m_pCurrentControl == &MenuAudio_3 ) + { + int32 sel = MenuAudio_3.GetMenuSelection(); + MenuAudio_3.GoFirst(); + + for ( int32 i = 0; i < sel; i++ ) + MenuAudio_3.GoNext(); + } + break; + } + } + } +} + +void +CMenuManager::SetSoundLevelsForMusicMenu(void) +{ + DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); +} + +void +CMenuManager::FilterOutColorMarkersFromString(wchar *string, CRGBA &color) +{ + wchar buf[300]; + UnicodeStrcpy(buf, string); + + wchar *src = buf; + wchar *dst = string; + while ( *src != '\0' ) + { + if ( *src == '~' ) + { + src++; + + if ( *src == 'l' ) color = CRGBA(0, 0, 0, 255); + else if ( *src == 'p' ) color = CRGBA(255, 0, 255, 255); + else if ( *src == 'y' ) color = CRGBA(255, 255, 0, 255); + else if ( *src == 'w' ) color = CRGBA(255, 255, 255, 255); + else if ( *src == 'b' ) color = CRGBA(40, 40, 255, 255); + else if ( *src == 'g' ) color = CRGBA(40, 235, 40, 255); + else if ( *src == 'r' ) color = CRGBA(255, 0, 0, 255); + + while ( *src++ != '~' ) + ; + } + else + *dst++ = *src++; + } + + *dst = '\0'; +} + +#endif diff --git a/src/core/Frontend_PS2.h b/src/core/Frontend_PS2.h new file mode 100644 index 0000000..4bab7df --- /dev/null +++ b/src/core/Frontend_PS2.h @@ -0,0 +1,245 @@ +#pragma once +#include "Sprite2d.h" + +enum +{ + PAGE_STATS, + PAGE_LOAD, + PAGE_BRIEFS, + PAGE_CONTROLS, + PAGE_AUDIO, + PAGE_DISPLAY, + PAGE_LANGUAGE, + + NUM_PAGES, + PAGE_FIRST = PAGE_STATS, + PAGE_LAST = PAGE_LANGUAGE, +}; + +enum +{ + PAGESTATE_NORMAL = 0, + PAGESTATE_HIGHLIGHTED, + PAGESTATE_SELECTED +}; + + +enum eFrontendSprites +{ + FE2_MAINPANEL_UL, + FE2_MAINPANEL_UR, + FE2_MAINPANEL_DL, + FE2_MAINPANEL_DR, + FE2_MAINPANEL_DR2, + FE2_TABACTIVE, + FE_ICONBRIEF, + FE_ICONSTATS, + FE_ICONCONTROLS, + FE_ICONSAVE, + FE_ICONAUDIO, + FE_ICONDISPLAY, + FE_ICONLANGUAGE, + FE_CONTROLLER, + FE_CONTROLLERSH, + FE_ARROWS1, + FE_ARROWS2, + FE_ARROWS3, + FE_ARROWS4, + FE_RADIO1, + FE_RADIO2, + FE_RADIO3, + FE_RADIO4, + FE_RADIO5, + FE_RADIO6, + FE_RADIO7, + FE_RADIO8, + FE_RADIO9, + + NUM_FE_SPRITES +}; + + +class CSprite2d; +class CVector2D; + +#ifdef GTA_PC +enum eControlMethod +{ + CONTROL_STANDARD = 0, + CONTROL_CLASSIC, +}; +#endif + +class CMenuManager +{ +public: + enum LANGUAGE + { + LANGUAGE_AMERICAN, + LANGUAGE_FRENCH, + LANGUAGE_GERMAN, + LANGUAGE_ITALIAN, + LANGUAGE_SPANISH, +#ifdef MORE_LANGUAGES + LANGUAGE_POLISH, + LANGUAGE_RUSSIAN, + LANGUAGE_JAPANESE, +#endif + }; + + enum CONTRCONFIG + { + CONFIG_1 = 0, + CONFIG_2, + CONFIG_3, + CONFIG_4, + }; + + enum + { + NUM_SPRIRES = 28, + }; + + enum + { + PAGESTATE_NORMAL = 0, + PAGESTATE_HIGHLIGHTED = 1, + PAGESTATE_SELELECTED = 2, + }; + + enum + { + SLIDE_TO_BOTTOM = 0, + SLIDE_TO_RIGHT, + SLIDE_TO_TOP, + SLIDE_TO_LEFT, + SLIDE_MAX + }; + + int32 m_currentPage; + int32 m_newPage; + int32 m_pageState; + uint32 m_nPageLeftTimer; + uint32 m_nPageRightTimer; + uint32 m_nChangePageTimer; + int field_18; + uint8 m_fade; + uint8 m_someAlpha; + //char field_1E; // unused ? + //char field_1F; // unused ? + uint32 m_nStartPauseTimer; + uint32 m_nEndPauseTimer; + CVector2D m_position; + uint8 m_nSlidingDir; + //char field_31; // unused ? + //char field_32; // unused ? + //char field_33; // unused ? + bool m_bInitialised; + bool m_bWantToUpdateContent; + bool m_bMenuActive; + bool m_bWantToRestart; + //char field_38; //unused ? + bool m_bRenderGameInMenu; + bool m_bSaveMenuActive; + bool m_bInSaveZone; + char field_3C; + bool m_bTexturesLoaded; + //char field_3E; //unused ? + //char field_3F; //unused ? + CSprite2d m_sprites[NUM_SPRIRES]; + + static int32 m_PrefsSfxVolume; + static int32 m_PrefsMusicVolume; + static int32 m_PrefsBrightness; + static bool m_PrefsShowTrails; + static bool m_PrefsShowSubtitles; + static bool m_PrefsAllowNastyGame; + static int32 m_PrefsRadioStation; + static int32 m_PrefsStereoMono; + static int8 m_PrefsUseWideScreen; + static int32 m_PrefsLanguage; + static CONTRCONFIG m_PrefsControllerConfig; + static bool m_PrefsUseVibration; + +#define ISLAND_LOADING_IS(p) +#define ISLAND_LOADING_ISNT(p) +#ifdef GTA_PC + bool m_bQuitGameNoCD; + + int32 m_nMouseTempPosX; + int32 m_nMouseTempPosY; + int32 m_nPrefsVideoMode; + int32 m_nDisplayVideoMode; + int8 m_nPrefsAudio3DProviderIndex; + + static int32 OS_Language; + static int8 m_PrefsVsync; + static int8 m_PrefsVsyncDisp; + static int8 m_PrefsFrameLimiter; + static int8 m_PrefsSpeakers; + static int32 m_ControlMethod; + static int8 m_PrefsDMA; + static float m_PrefsLOD; + static char m_PrefsSkinFile[256]; + +#ifndef MASTER + static bool m_PrefsMarketing; + static bool m_PrefsDisableTutorials; +#endif // !MASTER + +#ifdef MENU_MAP + static bool bMenuMapActive; + static float fMapSize; + static float fMapCenterY; + static float fMapCenterX; +#endif + +#ifdef IMPROVED_VIDEOMODE + int32 m_nPrefsWidth = 640; + int32 m_nPrefsHeight = 480; + int32 m_nPrefsDepth = 32; + int32 m_nPrefsWindowed = 1; + int32 m_nPrefsSubsystem; + int32 m_nSelectedScreenMode; +#endif + + void WaitForUserCD() { } +#endif + + bool GetIsMenuActive() {return !!m_bMenuActive;} + + CMenuManager(void); +#ifdef FIX_BUGS + ~CMenuManager(void) + { + UnloadTextures(); + } +#endif + + void LoadAllTextures(void); + void UnloadTextures(void); + + void InitialiseMenusOnce(void); + void InitialiseChangedLanguageSettings(void); + void InitialiseMenuContents(void); + void AnaliseMenuContents(void); + void InitialiseMenuContentsAfterLoadingGame(void); + void DrawFrontEnd(void); + void DrawFrontEndNormal(void); + void DrawFrontEndSaveZone(void); + void DrawMemoryCardStartUpMenus(void); + void Process(void); + void WorkOutMenuState(uint8 bExit); + void ProcessControllerInput(void); + void ProcessDPadLeftJustDown(void); + void ProcessDPadRightJustDown(void); + void ProcessDPadUpJustDown(void); + void ProcessDPadDownJustDown(void); + void ProcessDPadTriangleJustDown(void); + void ProcessDPadCrossJustDown(void); + void DoHackingMenusAtPageBrowse(void); + void SetSoundLevelsForMusicMenu(void); + void FilterOutColorMarkersFromString(wchar *string, CRGBA &color); +}; + +extern CMenuManager FrontEndMenuManager; \ No newline at end of file diff --git a/src/core/Game.cpp b/src/core/Game.cpp new file mode 100644 index 0000000..b3dd1ed --- /dev/null +++ b/src/core/Game.cpp @@ -0,0 +1,1464 @@ +#include "common.h" +#include "platform.h" + +#include "Game.h" +#include "main.h" +#include "RwHelper.h" +#include "Accident.h" +#include "Antennas.h" +#include "Bridge.h" +#include "CarCtrl.h" +#include "CarGen.h" +#include "CdStream.h" +#include "Clock.h" +#include "Clouds.h" +#include "Collision.h" +#include "Console.h" +#include "Coronas.h" +#include "Cranes.h" +#include "Credits.h" +#include "CutsceneMgr.h" +#include "DMAudio.h" +#include "Darkel.h" +#include "Debug.h" +#include "EventList.h" +#include "FileLoader.h" +#include "FileMgr.h" +#include "Fire.h" +#include "Fluff.h" +#include "Font.h" +#include "Frontend.h" +#include "frontendoption.h" +#include "GameLogic.h" +#include "Garages.h" +#include "GenericGameStorage.h" +#include "Glass.h" +#include "HandlingMgr.h" +#include "Heli.h" +#include "Hud.h" +#include "IniFile.h" +#include "Lights.h" +#include "MBlur.h" +#include "Messages.h" +#include "MemoryCard.h" +#include "Pad.h" +#include "Particle.h" +#include "ParticleObject.h" +#include "PedRoutes.h" +#include "Phones.h" +#include "Pickups.h" +#include "Plane.h" +#include "PlayerSkin.h" +#include "Population.h" +#include "Radar.h" +#include "Record.h" +#include "References.h" +#include "Renderer.h" +#include "Replay.h" +#include "Restart.h" +#include "RoadBlocks.h" +#include "Rubbish.h" +#include "SceneEdit.h" +#include "Script.h" +#include "Shadows.h" +#include "Skidmarks.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "Streaming.h" +#include "SurfaceTable.h" +#include "TempColModels.h" +#include "Timecycle.h" +#include "TrafficLights.h" +#include "Train.h" +#include "TxdStore.h" +#include "User.h" +#include "VisibilityPlugins.h" +#include "WaterCannon.h" +#include "WaterLevel.h" +#include "Weapon.h" +#include "WeaponEffects.h" +#include "Weather.h" +#include "World.h" +#include "ZoneCull.h" +#include "Zones.h" +#include "debugmenu.h" +#include "postfx.h" +#include "custompipes.h" +#include "screendroplets.h" +#include "crossplatform.h" +#include "MemoryHeap.h" +#ifdef USE_TEXTURE_POOL +#include "TexturePools.h" +#endif + +eLevelName CGame::currLevel; +bool CGame::bDemoMode = true; +bool CGame::nastyGame = true; +bool CGame::frenchGame; +bool CGame::germanGame; +bool CGame::noProstitutes; +bool CGame::playingIntro; +char CGame::aDatFile[32]; +#ifdef MORE_LANGUAGES +bool CGame::russianGame = false; +bool CGame::japaneseGame = false; +#endif + +int gameTxdSlot; + + +bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); +#ifdef PS2_MENU +void MessageScreen(char *msg) +{ + CRect rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + CRGBA color(255, 255, 255, 255); + + DoRWStuffStartOfFrame(50, 50, 50, 0, 0, 0, 255); + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + + CSprite2d *splash = LoadSplash(NULL); + splash->Draw(rect, color, color, color, color); +#ifdef FIX_BUGS + splash->DrawRect(CRect(SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(110.0f), SCREEN_WIDTH-SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(300.0f)), CRGBA(50, 50, 50, 192)); +#else + splash->DrawRect(CRect(20.0f, 110.0f, SCREEN_WIDTH-20.0f, 300.0f), CRGBA(50, 50, 50, 192)); +#endif + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(190)); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.0f), SCREEN_SCALE_Y(1.0f)); +#else + CFont::SetScale(1.0f, 1.0f); +#endif + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 190)); // 450.0f + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::SetDropColor(CRGBA(32, 32, 32, 255)); + CFont::SetDropShadowPosition(3); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetPropOn(); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH/2, SCREEN_SCALE_Y(130.0f), TheText.Get(msg)); +#else + CFont::PrintString(SCREEN_WIDTH/2, 130.0f, TheText.Get(msg)); +#endif + CFont::DrawFonts(); + + DoRWStuffEndOfFrame(); +} +#endif + +bool +CGame::InitialiseOnceBeforeRW(void) +{ + CFileMgr::Initialise(); + CdStreamInit(MAX_CDCHANNELS); + ValidateVersion(); +#ifdef EXTENDED_COLOURFILTER + CPostFX::InitOnce(); +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + // Not needed here but may be needed in future + // if (numCustomFrontendOptions == 0 && numCustomFrontendScreens == 0) + CustomFrontendOptionsPopulate(); +#endif + return true; +} + +#ifndef LIBRW +#ifdef PS2_MATFX +void ReplaceMatFxCallback(); +#endif // PS2_MATFX +#ifdef PS2_ALPHA_TEST +void ReplaceAtomicPipeCallback(); +#endif // PS2_ALPHA_TEST +#endif // !LIBRW + +bool +CGame::InitialiseRenderWare(void) +{ +#ifdef USE_TEXTURE_POOL + _TexturePoolsInitialise(); +#endif + +#if GTA_VERSION > GTA3_PS2_160 + CTxdStore::Initialise(); // in GameInit on ps2 + CVisibilityPlugins::Initialise(); // in plugin attach on ps2 +#endif + + //InitialiseScene(Scene); // PS2 only, only clears Scene.camera + +#ifdef GTA_PS2 + RpSkySelectTrueTSClipper(TRUE); + RpSkySelectTrueTLClipper(TRUE); + + // PS2ManagerApplyDirectionalLightingCB() uploads the GTA lights + // directly without going through RpWorld and all that + SetupPS2ManagerDefaultLightingCallback(); + PreAllocateRwObjects(); +#endif + + /* Create camera */ + Scene.camera = CameraCreate(SCREEN_WIDTH, SCREEN_HEIGHT, TRUE); + ASSERT(Scene.camera != nil); + if (!Scene.camera) + { + return (false); + } + + RwCameraSetFarClipPlane(Scene.camera, 2000.0f); // 250.0f on PS2 but who cares + RwCameraSetNearClipPlane(Scene.camera, 0.9f); + + CameraSize(Scene.camera, nil, DEFAULT_VIEWWINDOW, DEFAULT_ASPECT_RATIO); + + /* Create a world */ + RwBBox bbox; + + bbox.sup.x = bbox.sup.y = bbox.sup.z = 10000.0f; + bbox.inf.x = bbox.inf.y = bbox.inf.z = -10000.0f; + + Scene.world = RpWorldCreate(&bbox); + ASSERT(Scene.world != nil); + if (!Scene.world) + { + CameraDestroy(Scene.camera); + Scene.camera = nil; + return (false); + } + + /* Add the camera to the world */ + RpWorldAddCamera(Scene.world, Scene.camera); + LightsCreate(Scene.world); + +#if GTA_VERSION > GTA3_PS2_160 + CreateDebugFont(); // in GameInit on PS2 +#else + RwImageSetPath("textures"); +#endif + +#ifdef LIBRW +#ifdef PS2_MATFX + rw::MatFX::envMapApplyLight = true; + rw::MatFX::envMapUseMatColor = true; + rw::MatFX::envMapFlipU = true; +#else + rw::MatFX::envMapApplyLight = false; + rw::MatFX::envMapUseMatColor = false; + rw::MatFX::envMapFlipU = false; +#endif + rw::RGBA envcol = { 128, 128, 128, 255 }; + rw::MatFX::envMapColor = envcol; +#else +#ifdef PS2_MATFX + ReplaceMatFxCallback(); +#endif // PS2_MATFX +#ifdef PS2_ALPHA_TEST + ReplaceAtomicPipeCallback(); +#endif // PS2_ALPHA_TEST +#endif // LIBRW + + +#if GTA_VERSION > GTA3_PS2_160 + // in GameInit on PS2 + PUSH_MEMID(MEMID_TEXTURES); + CFont::Initialise(); + CHud::Initialise(); + POP_MEMID(); + // TODO: define + CPlayerSkin::Initialise(); +#endif + +#ifdef EXTENDED_PIPELINES + CustomPipes::CustomPipeInit(); // need Scene.world for this +#endif +#ifdef SCREEN_DROPLETS + ScreenDroplets::InitDraw(); +#endif + + return (true); +} + +// missing altogether on PS2 +void CGame::ShutdownRenderWare(void) +{ +#ifdef SCREEN_DROPLETS + ScreenDroplets::Shutdown(); +#endif +#ifdef EXTENDED_PIPELINES + CustomPipes::CustomPipeShutdown(); +#endif + + CMBlur::MotionBlurClose(); + DestroySplashScreen(); + CHud::Shutdown(); + CFont::Shutdown(); + + for ( int32 i = 0; i < NUMPLAYERS; i++ ) + CWorld::Players[i].DeletePlayerSkin(); + + // TODO: define + CPlayerSkin::Shutdown(); + + DestroyDebugFont(); + + /* Destroy world */ + LightsDestroy(Scene.world); + RpWorldRemoveCamera(Scene.world, Scene.camera); + RpWorldDestroy(Scene.world); + + /* destroy camera */ + CameraDestroy(Scene.camera); + + Scene.world = nil; + Scene.camera = nil; + + CVisibilityPlugins::Shutdown(); + +#ifdef USE_TEXTURE_POOL + _TexturePoolsShutdown(); +#endif +} + +// missing altogether on PS2 +bool CGame::InitialiseOnceAfterRW(void) +{ +#if GTA_VERSION > GTA3_PS2_160 + TheText.Load(); + DMAudio.Initialise(); // before TheGame() on PS2 + CTimer::Initialise(); + CTempColModels::Initialise(); + mod_HandlingManager.Initialise(); + CSurfaceTable::Initialise("DATA\\SURFACE.DAT"); + CPedStats::Initialise(); + CTimeCycle::Initialise(); + +#ifndef GTA_PS2 + if ( DMAudio.GetNum3DProvidersAvailable() == 0 ) + FrontEndMenuManager.m_nPrefsAudio3DProviderIndex = -1; + + if ( FrontEndMenuManager.m_nPrefsAudio3DProviderIndex == -99 || FrontEndMenuManager.m_nPrefsAudio3DProviderIndex == -2 ) { + CMenuManager::m_PrefsSpeakers = 0; + int32 i; + for (i = 0; i < DMAudio.GetNum3DProvidersAvailable(); i++) { + wchar buff[64]; + +#ifdef AUDIO_OAL + extern int defaultProvider; + if (defaultProvider >= 0 && defaultProvider < DMAudio.GetNum3DProvidersAvailable()) + break; +#endif + char *name = DMAudio.Get3DProviderName(i); + AsciiToUnicode(name, buff); + char *providername = UnicodeToAscii(buff); + strupr(providername); +#if defined(AUDIO_MSS) + if (strcmp(providername, "MILES FAST 2D POSITIONAL AUDIO") == 0) + break; +#elif defined(AUDIO_OAL) + if (strcmp(providername, "OPENAL SOFT") == 0) + break; +#endif + } + + FrontEndMenuManager.m_nPrefsAudio3DProviderIndex = i; + } + + DMAudio.SetCurrent3DProvider(FrontEndMenuManager.m_nPrefsAudio3DProviderIndex); + DMAudio.SetSpeakerConfig(CMenuManager::m_PrefsSpeakers); + DMAudio.SetDynamicAcousticModelingStatus(CMenuManager::m_PrefsDMA); + DMAudio.SetMusicMasterVolume(CMenuManager::m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(CMenuManager::m_PrefsSfxVolume); + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); +#endif + CWorld::Players[0].SetPlayerSkin(CMenuManager::m_PrefsSkinFile); +#endif + return true; +} + +// missing altogether on PS2 +void +CGame::FinalShutdown(void) +{ + CTxdStore::Shutdown(); + CPedStats::Shutdown(); + CdStreamShutdown(); +} + +#if GTA_VERSION <= GTA3_PS2_160 +bool CGame::Initialise(void) +#else +bool CGame::Initialise(const char* datFile) +#endif +{ +#ifdef GTA_PS2 + // TODO: upload VU0 collision code here +#endif + +#if GTA_VERSION > GTA3_PS2_160 + ResetLoadingScreenBar(); + strcpy(aDatFile, datFile); + CPools::Initialise(); // done in CWorld on PS2 +#endif + +#ifndef GTA_PS2 +#ifdef PED_CAR_DENSITY_SLIDERS + // Load density values from gta3.ini only if our re3.ini have them 1.f + if (CIniFile::PedNumberMultiplier == 1.f && CIniFile::CarNumberMultiplier == 1.f) +#endif + CIniFile::LoadIniFile(); +#endif + + currLevel = LEVEL_INDUSTRIAL; + + PUSH_MEMID(MEMID_TEXTURES); + LoadingScreen("Loading the Game", "Loading generic textures", GetRandomSplashScreen()); + gameTxdSlot = CTxdStore::AddTxdSlot("generic"); + CTxdStore::Create(gameTxdSlot); + CTxdStore::AddRef(gameTxdSlot); + +#ifdef EXTENDED_PIPELINES + // for generic fallback + CustomPipes::SetTxdFindCallback(); +#endif + + LoadingScreen("Loading the Game", "Loading particles", nil); + int particleTxdSlot = CTxdStore::AddTxdSlot("particle"); + CTxdStore::LoadTxd(particleTxdSlot, "MODELS/PARTICLE.TXD"); + CTxdStore::AddRef(particleTxdSlot); + CTxdStore::SetCurrentTxd(gameTxdSlot); + LoadingScreen("Loading the Game", "Setup game variables", nil); + POP_MEMID(); + +#ifdef GTA_PS2 + CDma::SyncChannel(0, true); +#endif + + CGameLogic::InitAtStartOfGame(); + CReferences::Init(); + TheCamera.Init(); + TheCamera.SetRwCamera(Scene.camera); + CDebug::DebugInitTextBuffer(); + ThePaths.Init(); + ThePaths.AllocatePathFindInfoMem(4500); + CWeather::Init(); + CCullZones::Init(); + CCollision::Init(); +#ifdef PS2_MENU // TODO: is this the right define? + TheText.Load(); +#endif + CTheZones::Init(); + CUserDisplay::Init(); + CMessages::Init(); +#if GTA_VERSION > GTA3_PS2_160 + CMessages::ClearAllMessagesDisplayedByGame(); +#endif + CRecordDataForGame::Init(); + CRestart::Initialise(); + + PUSH_MEMID(MEMID_WORLD); + CWorld::Initialise(); + POP_MEMID(); + +#if GTA_VERSION <= GTA3_PS2_160 + mod_HandlingManager.Initialise(); + CSurfaceTable::Initialise("DATA\\SURFACE.DAT"); + CTempColModels::Initialise(); +#endif + + PUSH_MEMID(MEMID_TEXTURES); + CParticle::Initialise(); + POP_MEMID(); + +#if GTA_VERSION <= GTA3_PS2_160 + gStartX = -180.0f; + gStartY = 180.0f; + gStartZ = 14.0f; +#endif + + PUSH_MEMID(MEMID_ANIMATION); + CAnimManager::Initialise(); + CCutsceneMgr::Initialise(); + POP_MEMID(); + + PUSH_MEMID(MEMID_CARS); + CCarCtrl::Init(); + POP_MEMID(); + + PUSH_MEMID(MEMID_DEF_MODELS); +#if GTA_VERSION > GTA3_PS2_160 + InitModelIndices(); +#endif + CModelInfo::Initialise(); + +#if GTA_VERSION > GTA3_PS2_160 + // probably moved before LoadLevel for multiplayer maps? + CPickups::Init(); + CTheCarGenerators::Init(); + + CdStreamAddImage("MODELS\\GTA3.IMG"); + + CFileLoader::LoadLevel("DATA\\DEFAULT.DAT"); + CFileLoader::LoadLevel(datFile); +#else + CPedStats::Initialise(); // InitialiseOnceAfterRW + + CFileLoader::LoadLevel("GTA3.DAT"); +#endif + + CWorld::AddParticles(); + CVehicleModelInfo::LoadVehicleColours(); + CVehicleModelInfo::LoadEnvironmentMaps(); + CTheZones::PostZoneCreation(); + POP_MEMID(); + +#if GTA_VERSION <= GTA3_PS2_160 + TestModelIndices(); +#endif + LoadingScreen("Loading the Game", "Setup paths", GetRandomSplashScreen()); + ThePaths.PreparePathData(); +#if GTA_VERSION > GTA3_PS2_160 + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); + CWorld::Players[0].LoadPlayerSkin(); + TestModelIndices(); +#endif + + LoadingScreen("Loading the Game", "Setup water", nil); + CWaterLevel::Initialise("DATA\\WATER.DAT"); +#if GTA_VERSION <= GTA3_PS2_160 + CTimeCycle::Initialise(); // InitialiseOnceAfterRW +#else + TheConsole.Init(); +#endif + CDraw::SetFOV(120.0f); + CDraw::ms_fLODDistance = 500.0f; + + LoadingScreen("Loading the Game", "Setup streaming", nil); + CStreaming::Init(); + CStreaming::LoadInitialVehicles(); + CStreaming::LoadInitialPeds(); + CStreaming::RequestBigBuildings(LEVEL_GENERIC); + CStreaming::LoadAllRequestedModels(false); +#if GTA_VERSION > GTA3_PS2_160 + printf("Streaming uses %zuK of its memory", CStreaming::ms_memoryUsed / 1024); // original modifier was %d +#endif + + LoadingScreen("Loading the Game", "Load animations", GetRandomSplashScreen()); + PUSH_MEMID(MEMID_ANIMATION); + CAnimManager::LoadAnimFiles(); + POP_MEMID(); + + CPed::Initialise(); + CRouteNode::Initialise(); + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + LoadingScreen("Loading the Game", "Find big buildings", nil); + CRenderer::Init(); + + LoadingScreen("Loading the Game", "Setup game variables", nil); + CRadar::Initialise(); + CRadar::LoadTextures(); + CWeapon::InitialiseWeapons(); + + LoadingScreen("Loading the Game", "Setup traffic lights", nil); + CTrafficLights::ScanForLightsOnMap(); + CRoadBlocks::Init(); + + LoadingScreen("Loading the Game", "Setup game variables", nil); + CPopulation::Initialise(); +#if GTA_VERSION <= GTA3_PS2_160 + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); +// CWorld::Players[0].LoadPlayerSkin(); // TODO: use a define for this +#endif + CWorld::PlayerInFocus = 0; + CCoronas::Init(); + CShadows::Init(); + CWeaponEffects::Init(); + CSkidmarks::Init(); + CAntennas::Init(); + CGlass::Init(); + gPhoneInfo.Initialise(); +#ifdef GTA_SCENE_EDIT + CSceneEdit::Initialise(); +#endif + + LoadingScreen("Loading the Game", "Load scripts", nil); + PUSH_MEMID(MEMID_SCRIPT); + CTheScripts::Init(); + CGangs::Initialise(); + POP_MEMID(); + + LoadingScreen("Loading the Game", "Setup game variables", nil); +#if GTA_VERSION <= GTA3_PS2_160 + CTimer::Initialise(); +#endif + CClock::Initialise(1000); +#if GTA_VERSION <= GTA3_PS2_160 + CTheCarGenerators::Init(); +#endif + CHeli::InitHelis(); + CCranes::InitCranes(); + CMovingThings::Init(); + CDarkel::Init(); + CStats::Init(); +#if GTA_VERSION <= GTA3_PS2_160 + CPickups::Init(); +#endif + CPacManPickups::Init(); +#if GTA_VERSION <= GTA3_PS2_160 + CGarages::Init(); +#endif + CRubbish::Init(); + CClouds::Init(); +#if GTA_VERSION <= GTA3_PS2_160 + CRemote::Init(); +#endif + CSpecialFX::Init(); + CWaterCannons::Init(); + CBridge::Init(); +#if GTA_VERSION > GTA3_PS2_160 + CGarages::Init(); +#endif + + LoadingScreen("Loading the Game", "Position dynamic objects", nil); + CWorld::RepositionCertainDynamicObjects(); +#if GTA_VERSION <= GTA3_PS2_160 + CCullZones::ResolveVisibilities(); +#endif + + LoadingScreen("Loading the Game", "Initialise vehicle paths", nil); +#if GTA_VERSION > GTA3_PS2_160 + CCullZones::ResolveVisibilities(); +#endif + CTrain::InitTrains(); + CPlane::InitPlanes(); + CCredits::Init(); + CRecordDataForChase::Init(); + CReplay::Init(); + + LoadingScreen("Loading the Game", "Start script", nil); +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad ) +#endif + { + CTheScripts::StartTestScript(); + CTheScripts::Process(); + TheCamera.Process(); + } + + LoadingScreen("Loading the Game", "Load scene", nil); + CModelInfo::RemoveColModelsFromOtherLevels(currLevel); + CCollision::ms_collisionInMemory = currLevel; + for (int i = 0; i < MAX_PADS; i++) + CPad::GetPad(i)->Clear(true); + return true; +} + +bool CGame::ShutDown(void) +{ + CReplay::FinishPlayback(); + CPlane::Shutdown(); + CTrain::Shutdown(); + CSpecialFX::Shutdown(); +#if GTA_VERSION > GTA3_PS2_160 + CGarages::Shutdown(); +#endif + CMovingThings::Shutdown(); + gPhoneInfo.Shutdown(); + CWeapon::ShutdownWeapons(); + CPedType::Shutdown(); + CMBlur::MotionBlurClose(); + + for (int32 i = 0; i < NUMPLAYERS; i++) + { + if ( CWorld::Players[i].m_pPed ) + { + CWorld::Remove(CWorld::Players[i].m_pPed); + delete CWorld::Players[i].m_pPed; + CWorld::Players[i].m_pPed = nil; + } + + CWorld::Players[i].Clear(); + } + + CRenderer::Shutdown(); + CWorld::ShutDown(); + DMAudio.DestroyAllGameCreatedEntities(); + CModelInfo::ShutDown(); + CAnimManager::Shutdown(); + CCutsceneMgr::Shutdown(); + CVehicleModelInfo::DeleteVehicleColourTextures(); + CVehicleModelInfo::ShutdownEnvironmentMaps(); + CRadar::Shutdown(); + CStreaming::Shutdown(); + CTxdStore::GameShutdown(); + CCollision::Shutdown(); + CWaterLevel::Shutdown(); + CRubbish::Shutdown(); + CClouds::Shutdown(); + CShadows::Shutdown(); + CCoronas::Shutdown(); + CSkidmarks::Shutdown(); + CWeaponEffects::Shutdown(); + CParticle::Shutdown(); +#if GTA_VERSION > GTA3_PS2_160 + CPools::ShutDown(); +#endif + CTxdStore::RemoveTxdSlot(gameTxdSlot); + CdStreamRemoveImages(); + return true; +} + +void CGame::ReInitGameObjectVariables(void) +{ + CGameLogic::InitAtStartOfGame(); +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad ) +#endif + { + TheCamera.Init(); + TheCamera.SetRwCamera(Scene.camera); + } + CDebug::DebugInitTextBuffer(); + CWeather::Init(); + CUserDisplay::Init(); + CMessages::Init(); + CRestart::Initialise(); + CWorld::bDoingCarCollisions = false; + CHud::ReInitialise(); + CRadar::Initialise(); +#if GTA_VERSION <= GTA3_PS2_160 + gStartX = -180.0f; + gStartY = 180.0f; + gStartZ = 14.0f; +#endif + CCarCtrl::ReInit(); + CTimeCycle::Initialise(); + CDraw::SetFOV(120.0f); + CDraw::ms_fLODDistance = 500.0f; + CStreaming::RequestBigBuildings(LEVEL_GENERIC); + CStreaming::LoadAllRequestedModels(false); + CPed::Initialise(); + CEventList::Initialise(); +#ifdef SCREEN_DROPLETS + ScreenDroplets::Initialise(); +#endif + CWeapon::InitialiseWeapons(); + CPopulation::Initialise(); + + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); + + CWorld::PlayerInFocus = 0; +#if GTA_VERSION <= GTA3_PS2_160 + CWeaponEffects::Init(); + CSkidmarks::Init(); +#endif + CAntennas::Init(); + CGlass::Init(); + gPhoneInfo.Initialise(); + + PUSH_MEMID(MEMID_SCRIPT); + CTheScripts::Init(); + CGangs::Initialise(); + POP_MEMID(); + + CTimer::Initialise(); + CClock::Initialise(1000); + CTheCarGenerators::Init(); + CHeli::InitHelis(); + CMovingThings::Init(); + CDarkel::Init(); + CStats::Init(); + CPickups::Init(); + CPacManPickups::Init(); + CGarages::Init(); +#if GTA_VERSION <= GTA3_PS2_160 + CClouds::Init(); + CRemote::Init(); +#endif + CSpecialFX::Init(); + CWaterCannons::Init(); + CParticle::ReloadConfig(); + CCullZones::ResolveVisibilities(); + +#ifdef PS2_MENU + if ( !TheMemoryCard.m_bWantToLoad ) +#else + if ( !FrontEndMenuManager.m_bWantToLoad ) +#endif + { + CCranes::InitCranes(); + CTheScripts::StartTestScript(); + CTheScripts::Process(); + TheCamera.Process(); + CTrain::InitTrains(); + CPlane::InitPlanes(); + } + + for (int32 i = 0; i < MAX_PADS; i++) + CPad::GetPad(i)->Clear(true); +} + +void CGame::ReloadIPLs(void) +{ + CTimer::Stop(); + CWorld::RemoveStaticObjects(); + ThePaths.Init(); + CCullZones::Init(); + CFileLoader::ReloadPaths("GTA3.IDE"); + CFileLoader::LoadScene("INDUST.IPL"); + CFileLoader::LoadScene("COMMER.IPL"); + CFileLoader::LoadScene("SUBURBAN.IPL"); + CFileLoader::LoadScene("CULL.IPL"); + ThePaths.PreparePathData(); + CTrafficLights::ScanForLightsOnMap(); + CRoadBlocks::Init(); + CCranes::InitCranes(); + CGarages::Init(); + CWorld::RepositionCertainDynamicObjects(); + CCullZones::ResolveVisibilities(); + CRenderer::SortBIGBuildings(); + CTimer::Update(); +} + +void CGame::ShutDownForRestart(void) +{ + CReplay::FinishPlayback(); + CReplay::EmptyReplayBuffer(); + DMAudio.DestroyAllGameCreatedEntities(); + + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); + + CGarages::SetAllDoorsBackToOriginalHeight(); + CTheScripts::UndoBuildingSwaps(); + CTheScripts::UndoEntityInvisibilitySettings(); + CWorld::ClearForRestart(); + CTimer::Shutdown(); + CStreaming::FlushRequestList(); + CStreaming::DeleteAllRwObjects(); + CStreaming::RemoveAllUnusedModels(); + CStreaming::ms_disableStreaming = false; + CRadar::RemoveRadarSections(); + FrontEndMenuManager.UnloadTextures(); + CParticleObject::RemoveAllParticleObjects(); +#if GTA_VERSION >= GTA3_PS2_160 + CPedType::Shutdown(); + CSpecialFX::Shutdown(); +#endif + TidyUpMemory(true, false); +} + +void CGame::InitialiseWhenRestarting(void) +{ + CRect rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + CRGBA color(255, 255, 255, 255); + + CTimer::Initialise(); + CSprite2d::SetRecipNearClip(); + +#ifdef PS2_MENU + if ( TheMemoryCard.b_FoundRecentSavedGameWantToLoad == true || TheMemoryCard.m_bWantToLoad == false ) + { + if ( TheMemoryCard.m_bWantToLoad == true ) + MessageScreen("MCLOAD"); // Loading Data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + else + MessageScreen("RESTART"); // Starting new game + } +#endif + +#ifdef PS2_MENU + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = false; +#else + b_FoundRecentSavedGameWantToLoad = false; +#endif + + TheCamera.Init(); + +#ifdef PS2_MENU + if ( TheMemoryCard.m_bWantToLoad == true ) + { + TheMemoryCard.RestoreForStartLoad(); + CStreaming::LoadScene(TheCamera.GetPosition()); + } +#else + if ( FrontEndMenuManager.m_bWantToLoad == true ) + { + RestoreForStartLoad(); + CStreaming::LoadScene(TheCamera.GetPosition()); + } +#endif + + ReInitGameObjectVariables(); + +#ifdef PS2_MENU + if ( TheMemoryCard.m_bWantToLoad == true ) + { + if ( TheMemoryCard.LoadSavedGame() == CMemoryCard::RES_SUCCESS ) + { + for ( int32 i = 0; i < 35; i++ ) + { + MessageScreen("FESZ_LS"); // Load Successful. + } + + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + CTrain::InitTrains(); + CPlane::InitPlanes(); + } + else + { + for ( int32 i = 0; i < 50; i++ ) + { + DoRWStuffStartOfFrame(50, 50, 50, 0, 0, 0, 255); + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + + CSprite2d *splash = LoadSplash(NULL); + splash->Draw(rect, color, color, color, color); +#ifdef FIX_BUGS + splash->DrawRect(CRect(SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(110.0f), SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_Y(300.0f)), CRGBA(50, 50, 50, 192)); +#else + splash->DrawRect(CRect(20.0f, 110.0f, SCREEN_WIDTH-20.0f, 300.0f), CRGBA(50, 50, 50, 192)); +#endif + + CFont::SetBackgroundOff(); +#ifdef ASPECT_RATIO_SCALE + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(160.0f)); // because SCREEN_SCALE_FROM_RIGHT(x) != SCREEN_SCALE_X(640-x) +#else + CFont::SetWrapx(SCREEN_SCALE_X(480.0f)); +#endif + CFont::SetScale(SCREEN_SCALE_X(1.0f), SCREEN_SCALE_Y(1.0f)); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(480.0f)); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetDropColor(CRGBA(32, 32, 32, 255)); + CFont::SetDropShadowPosition(3); + CFont::SetPropOn(); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_WIDTH/2, SCREEN_SCALE_Y(130.0f), TheText.Get("MC_LDFL")); // Load Failed! + CFont::PrintString(SCREEN_WIDTH/2, SCREEN_SCALE_Y(170.0f), TheText.Get("FES_NOC")); // No Memory Card (PS2) in MEMORY CARD slot 1. + CFont::PrintString(SCREEN_WIDTH/2, SCREEN_SCALE_Y(240.0f), TheText.Get("MC_NWRE")); // Now Restarting Game. +#else + CFont::PrintString(SCREEN_WIDTH/2, 130.0f, TheText.Get("MC_LDFL")); // Load Failed! + CFont::PrintString(SCREEN_WIDTH/2, 170.0f, TheText.Get("FES_NOC")); // No Memory Card (PS2) in MEMORY CARD slot 1. + CFont::PrintString(SCREEN_WIDTH/2, 240.0f, TheText.Get("MC_NWRE")); // Now Restarting Game. +#endif + CFont::DrawFonts(); + + DoRWStuffEndOfFrame(); + } + + ShutDownForRestart(); + CTimer::Stop(); + CTimer::Initialise(); + TheMemoryCard.m_bWantToLoad = false; + ReInitGameObjectVariables(); + currLevel = LEVEL_INDUSTRIAL; + CCollision::SortOutCollisionAfterLoad(); + + FrontEndMenuManager.SetSoundLevelsForMusicMenu(); + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + } + } +#else + if ( FrontEndMenuManager.m_bWantToLoad == true ) + { + if ( GenericLoad() == true ) + { + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + CTrain::InitTrains(); + CPlane::InitPlanes(); + } + else + { + for ( int32 i = 0; i < 50; i++ ) + { + HandleExit(); + FrontEndMenuManager.MessageScreen("FED_LFL"); // Loading save game has failed. The game will restart now. + } + + ShutDownForRestart(); + CTimer::Stop(); + CTimer::Initialise(); + FrontEndMenuManager.m_bWantToLoad = false; + ReInitGameObjectVariables(); + currLevel = LEVEL_INDUSTRIAL; + CCollision::SortOutCollisionAfterLoad(); + } + } +#endif + + CTimer::Update(); + + DMAudio.ChangeMusicMode(MUSICMODE_GAME); +} + +void CGame::Process(void) +{ + CPad::UpdatePads(); +#ifdef USE_CUSTOM_ALLOCATOR + ProcessTidyUpMemory(); +#endif + TheCamera.SetMotionBlurAlpha(0); + if (TheCamera.m_BlurType == MOTION_BLUR_NONE || TheCamera.m_BlurType == MOTION_BLUR_SNIPER || TheCamera.m_BlurType == MOTION_BLUR_LIGHT_SCENE) + TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE); +#ifdef DEBUGMENU + DebugMenuProcess(); +#endif + CCutsceneMgr::Update(); + + PUSH_MEMID(MEMID_FRONTEND); + if (!CCutsceneMgr::IsCutsceneProcessing() && !CTimer::GetIsCodePaused()) + FrontEndMenuManager.Process(); + POP_MEMID(); + + CStreaming::Update(); + if (!CTimer::GetIsPaused()) + { + CTheZones::Update(); + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + CRecordDataForGame::SaveOrRetrieveDataForThisFrame(); + CRecordDataForChase::SaveOrRetrieveDataForThisFrame(); + CPad::DoCheats(); + CClock::Update(); + CWeather::Update(); + + PUSH_MEMID(MEMID_SCRIPT); + CTheScripts::Process(); + POP_MEMID(); + + CCollision::Update(); + CTrain::UpdateTrains(); + CPlane::UpdatePlanes(); + CHeli::UpdateHelis(); + CDarkel::Update(); + CSkidmarks::Update(); + CAntennas::Update(); + CGlass::Update(); +#ifdef GTA_SCENE_EDIT + CSceneEdit::Update(); +#endif + CEventList::Update(); + CParticle::Update(); + gFireManager.Update(); + CPopulation::Update(); + CWeapon::UpdateWeapons(); + if (!CCutsceneMgr::IsRunning()) + CTheCarGenerators::Process(); + if (!CReplay::IsPlayingBack()) + CCranes::UpdateCranes(); + CClouds::Update(); + CMovingThings::Update(); + CWaterCannons::Update(); + CUserDisplay::Process(); + CReplay::Update(); + + PUSH_MEMID(MEMID_WORLD); + CWorld::Process(); + POP_MEMID(); + + gAccidentManager.Update(); + CPacManPickups::Update(); + CPickups::Update(); + CGarages::Update(); + CRubbish::Update(); + CSpecialFX::Update(); + CTimeCycle::Update(); + if (CReplay::ShouldStandardCameraBeProcessed()) + TheCamera.Process(); + CCullZones::Update(); + if (!CReplay::IsPlayingBack()) + CGameLogic::Update(); + CBridge::Update(); + CCoronas::DoSunAndMoon(); + CCoronas::Update(); + CShadows::UpdateStaticShadows(); + CShadows::UpdatePermanentShadows(); + gPhoneInfo.Update(); + if (!CReplay::IsPlayingBack()) + { + PUSH_MEMID(MEMID_CARS); + CCarCtrl::GenerateRandomCars(); + CRoadBlocks::GenerateRoadBlocks(); + CCarCtrl::RemoveDistantCars(); + POP_MEMID(); + } + } +#ifdef GTA_PS2 + CMemCheck::DoTest(); +#endif +} + +#ifdef USE_CUSTOM_ALLOCATOR + +int32 gNumMemMoved; + +bool +MoveMem(void **ptr) +{ + if(*ptr){ + gNumMemMoved++; + void *newPtr = gMainHeap.MoveMemory(*ptr); + if(*ptr != newPtr){ + *ptr = newPtr; + return true; + } + } + return false; +} + +// Some convenience structs +struct SkyDataPrefix +{ + uint32 pktSize1; + uint32 data; // pointer to data as read from TXD + uint32 pktSize2; + uint32 unused; +}; + +struct DMAGIFUpload +{ + uint32 tag1_qwc, tag1_addr; // dmaref + uint32 nop1, vif_direct1; + + uint32 giftag[4]; + uint32 gs_bitbltbuf[4]; + + uint32 tag2_qwc, tag2_addr; // dmaref + uint32 nop2, vif_direct2; +}; + +// This is very scary. it depends on the exact memory layout of the DMA chains and whatnot +RwTexture * +MoveTextureMemoryCB(RwTexture *texture, void *pData) +{ +#ifdef GTA_PS2 + bool *pRet = (bool*)pData; + RwRaster *raster = RwTextureGetRaster(texture); + _SkyRasterExt *rasterExt = RASTEREXTFROMRASTER(raster); + if(raster->originalPixels == nil || // the raw data + raster->cpPixels == raster->originalPixels || // old format, can't handle it + rasterExt->dmaRefCount != 0 && rasterExt->dmaClrCount != 0) + return texture; + + // this is the allocated pointer we will move + SkyDataPrefix *prefix = (SkyDataPrefix*)raster->originalPixels; + DMAGIFUpload *uploads = (DMAGIFUpload*)(prefix+1); + + // We have 4qw for each upload, + // i.e. for each buffer width of mip levels, + // and the palette if there is one. + // NB: this code does NOT support mipmaps! + // so we assume two uploads (pixels and palette) + // + // each upload looks like this: + // (DMAcnt; NOP; VIF DIRECT(2)) + // giftag (1, A+D) + // GS_BITBLTBUF + // (DMAref->pixel data; NOP; VIF DIRECT(5)) + // the DMArefs are what we have to adjust + uintptr dataDiff, upload1Diff, upload2Diff, pixelDiff, paletteDiff; + dataDiff = prefix->data - (uintptr)raster->originalPixels; + upload1Diff = uploads[0].tag2_addr - (uintptr)raster->originalPixels; + if(raster->palette) + upload2Diff = uploads[1].tag2_addr - (uintptr)raster->originalPixels; + pixelDiff = (uintptr)raster->cpPixels - (uintptr)raster->originalPixels; + if(raster->palette) + paletteDiff = (uintptr)raster->palette - (uintptr)raster->originalPixels; + uint8 *newptr = (uint8*)gMainHeap.MoveMemory(raster->originalPixels); + if(newptr != raster->originalPixels){ + // adjust everything + prefix->data = (uintptr)newptr + dataDiff; + uploads[0].tag2_addr = (uintptr)newptr + upload1Diff; + if(raster->palette) + uploads[1].tag2_addr = (uintptr)newptr + upload2Diff; + raster->originalPixels = newptr; + raster->cpPixels = newptr + pixelDiff; + if(raster->palette) + raster->palette = newptr + paletteDiff; + + if(pRet){ + *pRet = true; + return nil; + } + } +#else + // nothing to do here really, everything should be in videomemory +#endif + return texture; +} + +bool +MoveAtomicMemory(RpAtomic *atomic, bool onlyOne) +{ + RpGeometry *geo = RpAtomicGetGeometry(atomic); + +#if THIS_IS_COMPATIBLE_WITH_GTA3_RW31 + if(MoveMem((void**)&geo->triangles) && onlyOne) + return true; + if(MoveMem((void**)&geo->matList.materials) && onlyOne) + return true; + if(MoveMem((void**)&geo->preLitLum) && onlyOne) + return true; + if(MoveMem((void**)&geo->texCoords[0]) && onlyOne) + return true; + if(MoveMem((void**)&geo->texCoords[1]) && onlyOne) + return true; + + // verts and normals of morph target are allocated together + int vertDiff; + if(geo->morphTarget->normals) + vertDiff = geo->morphTarget->normals - geo->morphTarget->verts; + if(MoveMem((void**)&geo->morphTarget->verts)){ + if(geo->morphTarget->normals) + geo->morphTarget->normals = geo->morphTarget->verts + vertDiff; + if(onlyOne) + return true; + } + + RpMeshHeader *oldmesh = geo->mesh; + if(MoveMem((void**)&geo->mesh)){ + // index pointers are allocated together with meshes, + // have to relocate those too + RpMesh *mesh = (RpMesh*)(geo->mesh+1); + uintptr reloc = (uintptr)geo->mesh - (uintptr)oldmesh; + for(int i = 0; i < geo->mesh->numMeshes; i++) + mesh[i].indices = (RxVertexIndex*)((uintptr)mesh[i].indices + reloc); + if(onlyOne) + return true; + } +#else + // we could do something in librw here +#endif + return false; +} + +bool +MoveColModelMemory(CColModel &colModel, bool onlyOne) +{ +#if GTA_VERSION >= GTA3_PS2_160 + // hm...should probably only do this if ownsCollisionVolumes + // but it doesn't exist on PS2... + if(!colModel.ownsCollisionVolumes) + return false; +#endif + + if(MoveMem((void**)&colModel.spheres) && onlyOne) + return true; + if(MoveMem((void**)&colModel.lines) && onlyOne) + return true; + if(MoveMem((void**)&colModel.boxes) && onlyOne) + return true; + if(MoveMem((void**)&colModel.vertices) && onlyOne) + return true; + if(MoveMem((void**)&colModel.triangles) && onlyOne) + return true; + if(MoveMem((void**)&colModel.trianglePlanes) && onlyOne) + return true; + return false; +} + +RpAtomic* +MoveAtomicMemoryCB(RpAtomic *atomic, void *pData) +{ + bool *pRet = (bool*)pData; + if(pRet == nil) + MoveAtomicMemory(atomic, false); + else if(MoveAtomicMemory(atomic, true)){ + *pRet = true; + return nil; + } + return atomic; +} + +bool +TidyUpModelInfo(CBaseModelInfo *modelInfo, bool onlyone) +{ + if(modelInfo->GetColModel() && modelInfo->DoesOwnColModel()) + if(MoveColModelMemory(*modelInfo->GetColModel(), onlyone)) + return true; + + RwObject *rwobj = modelInfo->GetRwObject(); + if(RwObjectGetType(rwobj) == rpATOMIC) + if(MoveAtomicMemory((RpAtomic*)rwobj, onlyone)) + return true; + if(RwObjectGetType(rwobj) == rpCLUMP){ + bool ret = false; + if(onlyone) + RpClumpForAllAtomics((RpClump*)rwobj, MoveAtomicMemoryCB, &ret); + else + RpClumpForAllAtomics((RpClump*)rwobj, MoveAtomicMemoryCB, nil); + if(ret) + return true; + } + + if(modelInfo->GetModelType() == MITYPE_PED && ((CPedModelInfo*)modelInfo)->m_hitColModel) + if(MoveColModelMemory(*((CPedModelInfo*)modelInfo)->m_hitColModel, onlyone)) + return true; + + return false; +} +#endif + +void CGame::DrasticTidyUpMemory(bool flushDraw) +{ +#ifdef USE_CUSTOM_ALLOCATOR + bool removedCol = false; + + TidyUpMemory(true, flushDraw); + + if(gMainHeap.GetLargestFreeBlock() < 200000 && !playingIntro){ + CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + TidyUpMemory(true, flushDraw); + } + + if(gMainHeap.GetLargestFreeBlock() < 200000 && !playingIntro){ + CModelInfo::RemoveColModelsFromOtherLevels(LEVEL_GENERIC); + TidyUpMemory(true, flushDraw); + removedCol = true; + } + + if(gMainHeap.GetLargestFreeBlock() < 200000 && !playingIntro){ + CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL); + CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL); + CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN); + TidyUpMemory(true, flushDraw); + } + + if(removedCol){ + // different on PS2 + CFileLoader::LoadCollisionFromDatFile(CCollision::ms_collisionInMemory); + } + + if(!playingIntro) + CStreaming::RequestBigBuildings(currLevel); + + CStreaming::LoadAllRequestedModels(true); +#endif +} + +void CGame::TidyUpMemory(bool moveTextures, bool flushDraw) +{ +#ifdef USE_CUSTOM_ALLOCATOR + printf("Largest free block before tidy %d\n", gMainHeap.GetLargestFreeBlock()); + + if(moveTextures){ + if(flushDraw){ +#ifdef GTA_PS2 + for(int i = 0; i < sweMaxFlips+1; i++){ +#else + for(int i = 0; i < 5; i++){ // probably more than needed +#endif + RwCameraBeginUpdate(Scene.camera); + RwCameraEndUpdate(Scene.camera); + RwCameraShowRaster(Scene.camera, nil, 0); + } + } + int fontSlot = CTxdStore::FindTxdSlot("fonts"); + + for(int i = 0; i < TXDSTORESIZE; i++){ + if(i == fontSlot || + CTxdStore::GetSlot(i) == nil) + continue; + RwTexDictionary *txd = CTxdStore::GetSlot(i)->texDict; + if(txd) + RwTexDictionaryForAllTextures(txd, MoveTextureMemoryCB, nil); + } + } + + // animations + for(int i = 0; i < NUMANIMATIONS; i++){ + CAnimBlendHierarchy *anim = CAnimManager::GetAnimation(i); + if(anim == nil) + continue; // cannot happen + anim->MoveMemory(); + } + + // model info + for(int i = 0; i < MODELINFOSIZE; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(i); + if(mi == nil) + continue; + TidyUpModelInfo(mi, false); + } + + printf("Largest free block after tidy %d\n", gMainHeap.GetLargestFreeBlock()); +#endif +} + +void CGame::ProcessTidyUpMemory(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + static int32 modelIndex = 0; + static int32 animIndex = 0; + static int32 txdIndex = 0; + bool txdReturn = false; + RwTexDictionary *txd = nil; + gNumMemMoved = 0; + + // model infos + for(int numCleanedUp = 0; numCleanedUp < 10; numCleanedUp++){ + CBaseModelInfo *mi; + do{ + mi = CModelInfo::GetModelInfo(modelIndex); + modelIndex++; + if(modelIndex >= MODELINFOSIZE) + modelIndex = 0; + }while(mi == nil); + + if(TidyUpModelInfo(mi, true)) + return; + } + + // tex dicts + for(int numCleanedUp = 0; numCleanedUp < 3; numCleanedUp++){ + if(gNumMemMoved > 80) + break; + + do{ +#ifdef FIX_BUGS + txd = nil; +#endif + if(CTxdStore::GetSlot(txdIndex)) + txd = CTxdStore::GetSlot(txdIndex)->texDict; + txdIndex++; + if(txdIndex >= TXDSTORESIZE) + txdIndex = 0; + }while(txd == nil); + + RwTexDictionaryForAllTextures(txd, MoveTextureMemoryCB, &txdReturn); + if(txdReturn) + return; + } + + // animations + CAnimBlendHierarchy *anim; + do{ + anim = CAnimManager::GetAnimation(animIndex); + animIndex++; + if(animIndex >= NUMANIMATIONS) + animIndex = 0; + }while(anim == nil); // always != nil + anim->MoveMemory(true); +#endif +} diff --git a/src/core/Game.h b/src/core/Game.h new file mode 100644 index 0000000..002033a --- /dev/null +++ b/src/core/Game.h @@ -0,0 +1,49 @@ +#pragma once + +enum eLevelName { + LEVEL_IGNORE = -1, // beware, this is only used in CPhysical's m_nZoneLevel + LEVEL_GENERIC = 0, + LEVEL_INDUSTRIAL, + LEVEL_COMMERCIAL, + LEVEL_SUBURBAN, + NUM_LEVELS +}; + +class CGame +{ +public: + static eLevelName currLevel; + static bool bDemoMode; + static bool nastyGame; + static bool frenchGame; + static bool germanGame; +#ifdef MORE_LANGUAGES + static bool russianGame; + static bool japaneseGame; +#endif + static bool noProstitutes; + static bool playingIntro; + static char aDatFile[32]; + + static bool InitialiseOnceBeforeRW(void); + static bool InitialiseRenderWare(void); + static void ShutdownRenderWare(void); + static bool InitialiseOnceAfterRW(void); + static void FinalShutdown(void); +#if GTA_VERSION <= GTA3_PS2_160 + static bool Initialise(void); +#else + static bool Initialise(const char *datFile); +#endif + static bool ShutDown(void); + static void ReInitGameObjectVariables(void); + static void ReloadIPLs(void); + static void ShutDownForRestart(void); + static void InitialiseWhenRestarting(void); + static void Process(void); + + // NB: these do something on PS2 + static void TidyUpMemory(bool, bool); + static void DrasticTidyUpMemory(bool); + static void ProcessTidyUpMemory(void); +}; diff --git a/src/core/General.h b/src/core/General.h new file mode 100644 index 0000000..d4b941d --- /dev/null +++ b/src/core/General.h @@ -0,0 +1,157 @@ +#pragma once + +#include + +class CGeneral +{ +public: + static float GetATanOfXY(float x, float y){ + if(x == 0.0f && y == 0.0f) + return 0.0f; + float xabs = Abs(x); + float yabs = Abs(y); + + if(xabs < yabs){ + if(y > 0.0f){ + if(x > 0.0f) + return 0.5f*PI - Atan2(x / y, 1.0f); + else + return 0.5f*PI + Atan2(-x / y, 1.0f); + }else{ + if(x > 0.0f) + return 1.5f*PI + Atan2(x / -y, 1.0f); + else + return 1.5f*PI - Atan2(-x / -y, 1.0f); + } + }else{ + if(y > 0.0f){ + if(x > 0.0f) + return Atan2(y / x, 1.0f); + else + return PI - Atan2(y / -x, 1.0f); + }else{ + if(x > 0.0f) + return 2.0f*PI - Atan2(-y / x, 1.0f); + else + return PI + Atan2(-y / -x, 1.0f); + } + } + } + + static float LimitAngle(float angle) + { + float result = angle; + + while (result >= 180.0f) { + result -= 2 * 180.0f; + } + + while (result < -180.0f) { + result += 2 * 180.0f; + } + + return result; + } + + + static float LimitRadianAngle(float angle) + { + float result = Clamp(angle, -25.0f, 25.0f); + + while (result >= PI) { + result -= 2 * PI; + } + + while (result < -PI) { + result += 2 * PI; + } + + return result; + } + + // Returns an angle such that x2/y2 looks at x1/y1 with its forward vector if rotated by that angle + static float GetRadianAngleBetweenPoints(float x1, float y1, float x2, float y2) + { + float x = x2 - x1; + float y = y2 - y1; + + if (y == 0.0f) + y = 0.0001f; + + if (x > 0.0f) { + if (y > 0.0f) + return PI - Atan2(x / y, 1.0f); + else + return -Atan2(x / y, 1.0f); + } else { + if (y > 0.0f) + return -(PI + Atan2(x / y, 1.0f)); + else + return -Atan2(x / y, 1.0f); + } + } + + static float GetAngleBetweenPoints(float x1, float y1, float x2, float y2) + { + return RADTODEG(GetRadianAngleBetweenPoints(x1, y1, x2, y2)); + } + + // should return direction in 0-8 range. fits perfectly to peds' path directions. + static int GetNodeHeadingFromVector(float x, float y) + { + float angle = CGeneral::GetRadianAngleBetweenPoints(x, y, 0.0f, 0.0f); + if (angle < 0.0f) + angle += TWOPI; + + angle = DEGTORAD(22.5f) + TWOPI - angle; + + if (angle >= TWOPI) + angle -= TWOPI; + + return (int)Floor(angle / DEGTORAD(45.0f)); + } + + // Unlike usual string comparison functions, these don't care about greater or lesser + static bool faststrcmp(const char *str1, const char *str2) + { + for (; *str1; str1++, str2++) { + if (*str1 != *str2) + return true; + } + return *str2 != '\0'; + } + + static bool faststrncmp(const char *str1, const char *str2, uint32 count) + { + for(uint32 i = 0; *str1 && i < count; str1++, str2++, i++) { + if (*str1 != *str2) + return true; + } + return false; + } + + static bool faststricmp(const char *str1, const char *str2) + { + for (; *str1; str1++, str2++) { +#ifndef ASCII_STRCMP + if (toupper(*str1) != toupper(*str2)) +#else + if (__ascii_toupper(*str1) != __ascii_toupper(*str2)) +#endif + return true; + } + return *str2 != '\0'; + } + + // not too sure about all these... + static uint16 GetRandomNumber(void) + { return myrand() & MYRAND_MAX; } + static bool GetRandomTrueFalse(void) + { return GetRandomNumber() < MYRAND_MAX / 2; } + // Probably don't want to ever reach high + static float GetRandomNumberInRange(float low, float high) + { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } + + static int32 GetRandomNumberInRange(int32 low, int32 high) + { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } +}; diff --git a/src/core/IniFile.cpp b/src/core/IniFile.cpp new file mode 100644 index 0000000..524632f --- /dev/null +++ b/src/core/IniFile.cpp @@ -0,0 +1,28 @@ +#include "common.h" + +#include "IniFile.h" + +#include "CarCtrl.h" +#include "FileMgr.h" +#include "main.h" +#include "Population.h" + +float CIniFile::PedNumberMultiplier = 1.0f; +float CIniFile::CarNumberMultiplier = 1.0f; + +void CIniFile::LoadIniFile() +{ + CFileMgr::SetDir(""); + int f = CFileMgr::OpenFile("gta3.ini", "r"); + if (f){ + CFileMgr::ReadLine(f, gString, 200); + sscanf(gString, "%f", &PedNumberMultiplier); + PedNumberMultiplier = Min(3.0f, Max(0.5f, PedNumberMultiplier)); + CFileMgr::ReadLine(f, gString, 200); + sscanf(gString, "%f", &CarNumberMultiplier); + CarNumberMultiplier = Min(3.0f, Max(0.5f, CarNumberMultiplier)); + CFileMgr::CloseFile(f); + } + CPopulation::MaxNumberOfPedsInUse = DEFAULT_MAX_NUMBER_OF_PEDS * PedNumberMultiplier; + CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS * CarNumberMultiplier; +} \ No newline at end of file diff --git a/src/core/IniFile.h b/src/core/IniFile.h new file mode 100644 index 0000000..30dc8c2 --- /dev/null +++ b/src/core/IniFile.h @@ -0,0 +1,13 @@ +#pragma once + +#define DEFAULT_MAX_NUMBER_OF_PEDS 25.0f +#define DEFAULT_MAX_NUMBER_OF_CARS 12.0f + +class CIniFile +{ +public: + static void LoadIniFile(); + + static float PedNumberMultiplier; + static float CarNumberMultiplier; +}; diff --git a/src/core/Lists.cpp b/src/core/Lists.cpp new file mode 100644 index 0000000..448a0ff --- /dev/null +++ b/src/core/Lists.cpp @@ -0,0 +1,26 @@ +#include "common.h" +#include "Pools.h" +#include "Lists.h" + +void* +CPtrNode::operator new(size_t){ + CPtrNode *node = CPools::GetPtrNodePool()->New(); + assert(node); + return node; +} + +void +CPtrNode::operator delete(void *p, size_t){ + CPools::GetPtrNodePool()->Delete((CPtrNode*)p); +} + +void* +CEntryInfoNode::operator new(size_t){ + CEntryInfoNode *node = CPools::GetEntryInfoNodePool()->New(); + assert(node); + return node; +} +void +CEntryInfoNode::operator delete(void *p, size_t){ + CPools::GetEntryInfoNodePool()->Delete((CEntryInfoNode*)p); +} diff --git a/src/core/Lists.h b/src/core/Lists.h new file mode 100644 index 0000000..7572e88 --- /dev/null +++ b/src/core/Lists.h @@ -0,0 +1,130 @@ +#pragma once + +class CPtrNode +{ +public: + void *item; + CPtrNode *prev; + CPtrNode *next; + + void *operator new(size_t); + void operator delete(void *p, size_t); +}; + +class CPtrList +{ +public: + CPtrNode *first; + + CPtrList(void) { first = nil; } + ~CPtrList(void) { Flush(); } + CPtrNode *FindItem(void *item){ + CPtrNode *node; + for(node = first; node; node = node->next) + if(node->item == item) + return node; + return nil; + } + CPtrNode *InsertNode(CPtrNode *node){ + node->prev = nil; + node->next = first; + if(first) + first->prev = node; + first = node; + return node; + } + CPtrNode *InsertItem(void *item){ + CPtrNode *node = new CPtrNode; + node->item = item; + InsertNode(node); + return node; + } + void RemoveNode(CPtrNode *node){ + if(node == first) + first = node->next; + if(node->prev) + node->prev->next = node->next; + if(node->next) + node->next->prev = node->prev; + } + void DeleteNode(CPtrNode *node){ + RemoveNode(node); + delete node; + } + void RemoveItem(void *item){ + CPtrNode *node, *next; + for(node = first; node; node = next){ + next = node->next; + if(node->item == item) + DeleteNode(node); + } + } + void Flush(void){ + CPtrNode *node, *next; + for(node = first; node; node = next){ + next = node->next; + DeleteNode(node); + } + } +}; + +class CSector; + +// This records in which sector list a Physical is +class CEntryInfoNode +{ +public: + CPtrList *list; // list in sector + CPtrNode *listnode; // node in list + CSector *sector; + + CEntryInfoNode *prev; + CEntryInfoNode *next; + + void *operator new(size_t); + void operator delete(void *p, size_t); +}; + +class CEntryInfoList +{ +public: + CEntryInfoNode *first; + + CEntryInfoList(void) { first = nil; } + ~CEntryInfoList(void) { Flush(); } + CEntryInfoNode *InsertNode(CEntryInfoNode *node){ + node->prev = nil; + node->next = first; + if(first) + first->prev = node; + first = node; + return node; + } + CEntryInfoNode *InsertItem(CPtrList *list, CPtrNode *listnode, CSector *sect){ + CEntryInfoNode *node = new CEntryInfoNode; + node->list = list; + node->listnode = listnode; + node->sector = sect; + InsertNode(node); + return node; + } + void RemoveNode(CEntryInfoNode *node){ + if(node == first) + first = node->next; + if(node->prev) + node->prev->next = node->next; + if(node->next) + node->next->prev = node->prev; + } + void DeleteNode(CEntryInfoNode *node){ + RemoveNode(node); + delete node; + } + void Flush(void){ + CEntryInfoNode *node, *next; + for(node = first; node; node = next){ + next = node->next; + DeleteNode(node); + } + } +}; diff --git a/src/core/MenuScreens.cpp b/src/core/MenuScreens.cpp new file mode 100644 index 0000000..247de98 --- /dev/null +++ b/src/core/MenuScreens.cpp @@ -0,0 +1,453 @@ +#include "common.h" +#include "Frontend.h" +#ifdef PC_MENU + +// Please don't touch this file, except for bug fixing or ports. +// Check MenuScreensCustom.cpp + +#ifndef CUSTOM_FRONTEND_OPTIONS +CMenuScreen aScreens[MENUPAGES] = { + // MENUPAGE_NONE = 0 + { "", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, }, + + // MENUPAGE_STATS = 1 + { "FET_STA", 1, MENUPAGE_NONE, MENUPAGE_NONE, 5, 2, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_NEW_GAME = 2 + { "FET_SGA", 1, MENUPAGE_NONE, MENUPAGE_NONE, 0, 1, + MENUACTION_CHANGEMENU, "FES_SNG", SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD, + MENUACTION_POPULATESLOTS_CHANGEMENU, "GMLOAD", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT, + MENUACTION_POPULATESLOTS_CHANGEMENU, "FES_DGA", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_BRIEFS = 3 + { "FET_BRE", 1, MENUPAGE_NONE, MENUPAGE_NONE, 6, 3, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CONTROLLER_SETTINGS = 4 + { "FET_CON", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 0, 0, + MENUACTION_CTRLCONFIG, "FEC_CCF", SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS, + MENUACTION_CTRLDISPLAY, "FEC_CDP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS, + MENUACTION_CTRLVIBRATION, "FEC_VIB", SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SOUND_SETTINGS = 5 + { "FET_AUD", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 1, 1, + MENUACTION_MUSICVOLUME, "FEA_MUS", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_SFXVOLUME, "FEA_SFX", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, +#ifdef EXTERNAL_3D_SOUND + MENUACTION_AUDIOHW, "FEA_3DH", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_SPEAKERCONF, "FEA_SPK", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, +#endif +#ifdef AUDIO_REFLECTIONS + MENUACTION_DYNAMICACOUSTIC, "FET_DAM", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, +#endif + MENUACTION_RADIO, "FEA_RSS", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_DISPLAY_SETTINGS = 6 + { "FET_DIS", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 2, 2, + MENUACTION_BRIGHTNESS, "FED_BRI", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_DRAWDIST, "FEM_LOD", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_FRAMESYNC, "FEM_VSC", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_FRAMELIMIT, "FEM_FRM", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_TRAILS, "FED_TRA", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_SUBTITLES, "FED_SUB", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_WIDESCREEN, "FED_WIS", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_SCREENRES, "FED_RES", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_LANGUAGE_SETTINGS = 7 + { "FET_LAN", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 3, 3, + MENUACTION_LANG_ENG, "FEL_ENG", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_LANG_FRE, "FEL_FRE", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_LANG_GER, "FEL_GER", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_LANG_ITA, "FEL_ITA", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_LANG_SPA, "FEL_SPA", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CHOOSE_LOAD_SLOT = 8 + { "FET_LG", 1, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, 1, 1, + MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHECKSAVE, "FEM_SL0", SAVESLOT_1, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL1", SAVESLOT_2, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL2", SAVESLOT_3, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL3", SAVESLOT_4, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL4", SAVESLOT_5, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL5", SAVESLOT_6, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL6", SAVESLOT_7, MENUPAGE_LOAD_SLOT_CONFIRM, + MENUACTION_CHECKSAVE, "FEM_SL7", SAVESLOT_8, MENUPAGE_LOAD_SLOT_CONFIRM, + }, + + // MENUPAGE_CHOOSE_DELETE_SLOT = 9 + { "FET_DG", 1, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, 2, 2, + MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHANGEMENU, "FEM_SL0", SAVESLOT_1, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL1", SAVESLOT_2, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL2", SAVESLOT_3, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL3", SAVESLOT_4, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL4", SAVESLOT_5, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL5", SAVESLOT_6, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL6", SAVESLOT_7, MENUPAGE_DELETE_SLOT_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL7", SAVESLOT_8, MENUPAGE_DELETE_SLOT_CONFIRM, + }, + + // MENUPAGE_NEW_GAME_RELOAD = 10 + { "FET_NG", 1, MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, 0, 0, + MENUACTION_LABEL, "FESZ_QR", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_NEWGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD, + }, + + // MENUPAGE_LOAD_SLOT_CONFIRM = 11 + { "FET_LG", 1, MENUPAGE_CHOOSE_LOAD_SLOT, MENUPAGE_CHOOSE_LOAD_SLOT, 0, 0, + MENUACTION_LABEL, "FESZ_QL", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT, + MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS, + }, + + // MENUPAGE_DELETE_SLOT_CONFIRM = 12 + { "FET_DG", 1, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, 0, 0, + MENUACTION_LABEL, "FESZ_QD", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, + MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_DELETING, + }, + + // MENUPAGE_NO_MEMORY_CARD = 13 + { "FES_NOC", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + // hud adjustment page in mobile + }, + + // MENUPAGE_LOADING_IN_PROGRESS = 14 + { "FET_LG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FED_LDW", SAVESLOT_NONE, MENUPAGE_LOAD_SLOT_CONFIRM, + }, + + // MENUPAGE_DELETING_IN_PROGRESS = 15 + { "FET_DG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FEDL_WR", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_PS2_LOAD_FAILED = 16 + { "FET_LG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FES_LOE", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_DELETE_FAILED = 17 + { "FET_DG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FES_DEE", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, + }, + + // MENUPAGE_DEBUG_MENU = 18 + { "FED_DBG", 1, MENUPAGE_NONE, MENUPAGE_NONE, 4, 0, + MENUACTION_RELOADIDE, "FED_RID", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_RELOADIPL, "FED_RIP", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SETDBGFLAG, "FED_DFL", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SWITCHBIGWHITEDEBUGLIGHT, "FED_DLS", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_PEDROADGROUPS, "FED_SPR", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CARROADGROUPS, "FED_SCR", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_COLLISIONPOLYS, "FED_SCP", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_PARSEHEAP, "FED_PAH", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SHOWCULL, "FED_SCZ", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_DEBUGSTREAM, "FED_DSR", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_MEMORY_CARD_DEBUG = 19 + { "FEM_MCM", 1, MENUPAGE_NONE, MENUPAGE_NONE, 7, 0, + MENUACTION_REGMEMCARD1, "FEM_RMC", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_TESTFORMATMEMCARD1, "FEM_TFM", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_TESTUNFORMATMEMCARD1, "FEM_TUM", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CREATEROOTDIR, "FEM_CRD", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CREATELOADICONS, "FEM_CLI", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_FILLWITHGUFF, "FEM_FFF", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SAVEONLYTHEGAME, "FEM_SOG", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SAVEGAME, "FEM_STG", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_SAVEGAMEUNDERGTA, "FEM_STS", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CREATECOPYPROTECTED, "FEM_CPD", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_MEMORY_CARD_TEST = 20 + { "FEM_MC2", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_MAIN = 21 + { "FET_MP", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_PS2_SAVE_FAILED = 22 + { "MCDNSP", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_PS2_SAVE_FAILED_2 = 23 + { "MCGNSP", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // Unused in PC but anyway + // MENUPAGE_SAVE = 24 +#ifdef PS2_SAVE_DIALOG + { "FET_SG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_CHANGEMENU, "FESZ_SA", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + }, +#else + { "FET_SG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FES_SCG", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_POPULATESLOTS_CHANGEMENU, "GMSAVE", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + }, +#endif + + // MENUPAGE_NO_MEMORY_CARD_2 = 25 + { "FES_NOC", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_CHANGEMENU, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CHOOSE_SAVE_SLOT = 26 + { "FET_SG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_SL1", SAVESLOT_1, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL2", SAVESLOT_2, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL3", SAVESLOT_3, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL4", SAVESLOT_4, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL5", SAVESLOT_5, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL6", SAVESLOT_6, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL7", SAVESLOT_7, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + MENUACTION_CHANGEMENU, "FEM_SL8", SAVESLOT_8, MENUPAGE_SAVE_OVERWRITE_CONFIRM, + }, + + // MENUPAGE_SAVE_OVERWRITE_CONFIRM = 27 + { "FET_SG", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, + MENUACTION_LABEL, "FESZ_QO", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_SAVING_IN_PROGRESS, + MENUACTION_CHANGEMENU, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + }, + + // MENUPAGE_MULTIPLAYER_MAP = 28 + { "FET_MAP", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_CONNECTION = 29 + { "FET_CON", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_FIND_GAME = 30 + { "FET_FG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_MODE = 31 + { "FET_GT", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_CREATE = 32 + { "FET_HG", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_MULTIPLAYER_START = 33 + { "FEN_STA", 2, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_SKIN_SELECT_OLD = 34 + { "FET_PS", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_CONTROLLER_PC = 35 + { "FET_CTL", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 0, 0, +#ifdef PC_PLAYER_CONTROLS + MENUACTION_CTRLMETHOD, "FET_CME", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, +#endif + MENUACTION_KEYBOARDCTRLS,"FET_RDK", SAVESLOT_NONE, MENUPAGE_KEYBOARD_CONTROLS, + MENUACTION_CHANGEMENU, "FET_AMS", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CONTROLLER_PC_OLD1 = 36 + { "FET_CTL", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 0, 0, + MENUACTION_GETKEY, "FEC_PLB", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_CWL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_CWR", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_LKT", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_PJP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_PSP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_TLF", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_TRG", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_GETKEY, "FEC_CCM", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CONTROLLER_PC_OLD2 = 37 + { "FET_CTL", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 1, 1, + + }, + + // MENUPAGE_CONTROLLER_PC_OLD3 = 38 + { "FET_CTL", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 2, 2, + MENUACTION_GETKEY, "FEC_LUP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, + MENUACTION_GETKEY, "FEC_LDN", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, + MENUACTION_GETKEY, "FEC_SMS", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, + MENUACTION_SHOWHEADBOB, "FEC_GSL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_CONTROLLER_PC_OLD4 = 39 + { "FET_CTL", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 3, 3, + + }, + + // MENUPAGE_CONTROLLER_DEBUG = 40 + { "FEC_DBG", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 3, 3, + MENUACTION_GETKEY, "FEC_TGD", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, + MENUACTION_GETKEY, "FEC_TDO", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, + MENUACTION_GETKEY, "FEC_TSS", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, + MENUACTION_GETKEY, "FEC_SMS", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_OPTIONS = 41 + { "FET_OPT", 1, MENUPAGE_NONE, MENUPAGE_NONE, 1, 4, + MENUACTION_CHANGEMENU, "FET_CTL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, + MENUACTION_LOADRADIO, "FET_AUD", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, + MENUACTION_CHANGEMENU, "FET_DIS", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, + MENUACTION_CHANGEMENU, "FET_LAN", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, + MENUACTION_PLAYERSETUP, "FET_PSU", SAVESLOT_NONE, MENUPAGE_SKIN_SELECT, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_EXIT = 42 + { "FET_QG", 1, MENUPAGE_NONE, MENUPAGE_NONE, 2, 5, + MENUACTION_LABEL, "FEQ_SRE", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_DONTCANCEL, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CANCELGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SAVING_IN_PROGRESS = 43 + { "", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, + MENUACTION_LABEL, "FES_WAR", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SAVE_SUCCESSFUL = 44 + { "FET_SG", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, + MENUACTION_LABEL, "FES_SSC", SAVESLOT_LABEL, MENUPAGE_NONE, + MENUACTION_RESUME_FROM_SAVEZONE, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + }, + + // MENUPAGE_DELETING = 45 + { "FET_DG", 1, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, 0, 0, + MENUACTION_LABEL, "FED_DLW", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_DELETE_SUCCESS = 46 + { "FET_DG", 1, MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, 0, 0, + MENUACTION_LABEL, "DEL_FNM", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, + }, + + // MENUPAGE_SAVE_FAILED = 47 + { "FET_SG", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, + MENUACTION_LABEL, "FEC_SVU", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, + }, + + // MENUPAGE_LOAD_FAILED = 48 + { "FET_SG", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, + MENUACTION_LABEL, "FEC_SVU", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_LOAD_FAILED_2 = 49 + { "FET_LG", 1, MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, + MENUACTION_LABEL, "FEC_LUN", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT, + }, + + // MENUPAGE_FILTER_GAME = 50 + { "FIL_FLT", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + + }, + + // MENUPAGE_START_MENU = 51 + { "FEM_MM", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_CHANGEMENU, "FEN_STA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHANGEMENU, "FET_OPT", SAVESLOT_NONE, MENUPAGE_OPTIONS, + MENUACTION_CHANGEMENU, "FEM_QT", SAVESLOT_NONE, MENUPAGE_EXIT, + }, + + // MENUPAGE_PAUSE_MENU = 52 + { "FET_PAU", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_RESUME, "FEM_RES", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEN_STA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_CHANGEMENU, "FEP_STA", SAVESLOT_NONE, MENUPAGE_STATS, + MENUACTION_CHANGEMENU, "FEP_BRI", SAVESLOT_NONE, MENUPAGE_BRIEFS, + MENUACTION_CHANGEMENU, "FET_OPT", SAVESLOT_NONE, MENUPAGE_OPTIONS, + MENUACTION_CHANGEMENU, "FEM_QT", SAVESLOT_NONE, MENUPAGE_EXIT, + }, + + // MENUPAGE_CHOOSE_MODE = 53 + { "FEN_STA", 1, MENUPAGE_NONE, MENUPAGE_NONE, 0, 1, + MENUACTION_CHANGEMENU, "FET_SP", SAVESLOT_NONE, MENUPAGE_NEW_GAME, + MENUACTION_INITMP, "FET_MP", SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + + // MENUPAGE_SKIN_SELECT = 54 + { "FET_PSU", 1, MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, 4, 4, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN, + }, + + // MENUPAGE_KEYBOARD_CONTROLS = 55 + { "FET_STI", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 1, 1, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, + }, + + // MENUPAGE_MOUSE_CONTROLS = 56 + { "FET_MTI", 1, MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, 2, 2, + MENUACTION_MOUSESENS, "FEC_MSH", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, + MENUACTION_INVVERT, "FEC_IVV", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, + MENUACTION_MOUSESTEER, "FET_MST", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, + MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, + }, + // MENUPAGE_MISSION_RETRY = 57 +#ifdef MISSION_REPLAY + + { "M_FAIL", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, + MENUACTION_LABEL, "FESZ_RM", SAVESLOT_NONE, MENUPAGE_NONE, + MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS, + MENUACTION_REJECT_RETRY, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NONE + }, +#else + { "", 0, MENUPAGE_NONE, MENUPAGE_NONE, 0, 0, + // mission failed, wanna restart page in mobile + }, +#endif + + // MENUPAGE_UNK + { "", 0, MENUPAGE_NONE, MENUPAGE_NONE, 0, 0, + + }, + +}; + +#endif +#endif diff --git a/src/core/MenuScreensCustom.cpp b/src/core/MenuScreensCustom.cpp new file mode 100644 index 0000000..d33650f --- /dev/null +++ b/src/core/MenuScreensCustom.cpp @@ -0,0 +1,936 @@ +#include "common.h" +#if defined DETECT_JOYSTICK_MENU && defined XINPUT +#include +#include +#if !defined(PSAPI_VERSION) || (PSAPI_VERSION > 1) +#pragma comment( lib, "Xinput9_1_0.lib" ) +#else +#pragma comment( lib, "Xinput.lib" ) +#endif +#endif +#include "platform.h" +#include "crossplatform.h" +#include "Renderer.h" +#include "Frontend.h" +#include "Font.h" +#include "Camera.h" +#include "main.h" +#include "MBlur.h" +#include "postfx.h" +#include "custompipes.h" +#include "RwHelper.h" +#include "Text.h" +#include "Streaming.h" +#include "FileLoader.h" +#include "Collision.h" +#include "ModelInfo.h" +#include "Pad.h" +#include "ControllerConfig.h" +#include "IniFile.h" +#include "CarCtrl.h" +#include "Population.h" + +// Menu screens array is at the bottom of the file. + +#ifdef PC_MENU + +#ifdef CUSTOM_FRONTEND_OPTIONS + +#if defined(IMPROVED_VIDEOMODE) && !defined(GTA_HANDHELD) + #define VIDEOMODE_SELECTOR MENUACTION_CFO_SELECT, "FEM_SCF", { new CCFOSelect((int8*)&FrontEndMenuManager.m_nPrefsWindowed, "VideoMode", "Windowed", screenModes, 2, true, ScreenModeAfterChange, true) }, +#else + #define VIDEOMODE_SELECTOR +#endif + +#ifdef MULTISAMPLING + #define MULTISAMPLING_SELECTOR MENUACTION_CFO_DYNAMIC, "FED_AAS", { new CCFODynamic((int8*)&FrontEndMenuManager.m_nPrefsMSAALevel, "Graphics", "MultiSampling", MultiSamplingDraw, MultiSamplingButtonPress) }, +#else + #define MULTISAMPLING_SELECTOR +#endif + +#ifdef CUTSCENE_BORDERS_SWITCH + #define CUTSCENE_BORDERS_TOGGLE MENUACTION_CFO_SELECT, "FEM_CSB", { new CCFOSelect((int8 *)&CMenuManager::m_PrefsCutsceneBorders, "Display", "CutsceneBorders", off_on, 2, false) }, +#else + #define CUTSCENE_BORDERS_TOGGLE +#endif + +#ifdef FREE_CAM + #define FREE_CAM_TOGGLE MENUACTION_CFO_SELECT, "FEC_FRC", { new CCFOSelect((int8*)&TheCamera.bFreeCam, "Display", "FreeCam", off_on, 2, false) }, +#else + #define FREE_CAM_TOGGLE +#endif + +#ifdef PS2_ALPHA_TEST + #define DUALPASS_SELECTOR MENUACTION_CFO_SELECT, "FEM_2PR", { new CCFOSelect((int8*)&gPS2alphaTest, "Graphics", "PS2AlphaTest", off_on, 2, false) }, +#else + #define DUALPASS_SELECTOR +#endif + +#ifdef PED_CAR_DENSITY_SLIDERS + // 0.2f - 3.4f makes it possible to have 1.0f somewhere inbetween + #define DENSITY_SLIDERS \ + MENUACTION_CFO_SLIDER, "FEM_PED", { new CCFOSlider(&CIniFile::PedNumberMultiplier, "Display", "PedDensity", 0.2f, 3.4f, PedDensityChange) }, \ + MENUACTION_CFO_SLIDER, "FEM_CAR", { new CCFOSlider(&CIniFile::CarNumberMultiplier, "Display", "CarDensity", 0.2f, 3.4f, CarDensityChange) }, +#else + #define DENSITY_SLIDERS +#endif + +#ifdef NO_ISLAND_LOADING + #define ISLAND_LOADING_SELECTOR MENUACTION_CFO_SELECT, "FEM_ISL", { new CCFOSelect((int8*)&CMenuManager::m_PrefsIslandLoading, "Graphics", "IslandLoading", islandLoadingOpts, ARRAY_SIZE(islandLoadingOpts), true, IslandLoadingAfterChange) }, +#else + #define ISLAND_LOADING_SELECTOR +#endif + +#ifdef EXTENDED_COLOURFILTER + #define POSTFX_SELECTORS \ + MENUACTION_CFO_SELECT, "FED_CLF", { new CCFOSelect((int8*)&CPostFX::EffectSwitch, "Graphics", "ColourFilter", filterNames, ARRAY_SIZE(filterNames), false) }, \ + MENUACTION_CFO_SELECT, "FED_MBL", { new CCFOSelect((int8*)&CPostFX::MotionBlurOn, "Graphics", "MotionBlur", off_on, 2, false) }, +#else + #define POSTFX_SELECTORS +#endif + +#ifdef INVERT_LOOK_FOR_PAD + #define INVERT_PAD_SELECTOR MENUACTION_CFO_SELECT, "FEC_IVP", { new CCFOSelect((int8*)&CPad::bInvertLook4Pad, "Controller", "InvertPad", off_on, 2, false) }, +#else + #define INVERT_PAD_SELECTOR +#endif + +#ifdef GAMEPAD_MENU + #define SELECT_CONTROLLER_TYPE MENUACTION_CFO_SELECT, "FEC_TYP", { new CCFOSelect((int8*)&CMenuManager::m_PrefsControllerType, "Controller", "Type", controllerTypes, ARRAY_SIZE(controllerTypes), false, ControllerTypeAfterChange) }, +#else + #define SELECT_CONTROLLER_TYPE +#endif + +const char *filterNames[] = { "FEM_NON", "FEM_SIM", "FEM_NRM", "FEM_MOB" }; +const char *off_on[] = { "FEM_OFF", "FEM_ON" }; + +void RestoreDefGraphics(int8 action) { + if (action != FEOPTION_ACTION_SELECT) + return; + + #ifdef PS2_ALPHA_TEST + gPS2alphaTest = false; + #endif + #ifdef MULTISAMPLING + FrontEndMenuManager.m_nPrefsMSAALevel = FrontEndMenuManager.m_nDisplayMSAALevel = 0; + #endif + #ifdef NO_ISLAND_LOADING + if (!FrontEndMenuManager.m_bGameNotLoaded) { + FrontEndMenuManager.m_PrefsIslandLoading = FrontEndMenuManager.ISLAND_LOADING_LOW; + CCollision::bAlreadyLoaded = false; + CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel); + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + CStreaming::RequestIslands(CGame::currLevel); + CStreaming::LoadAllRequestedModels(true); + } else + FrontEndMenuManager.m_PrefsIslandLoading = FrontEndMenuManager.ISLAND_LOADING_LOW; + #endif + #ifdef GRAPHICS_MENU_OPTIONS // otherwise Frontend will handle those + CMenuManager::m_PrefsFrameLimiter = true; + CMenuManager::m_PrefsVsyncDisp = true; + CMenuManager::m_PrefsVsync = true; + CMenuManager::m_PrefsUseWideScreen = false; + FrontEndMenuManager.m_nDisplayVideoMode = FrontEndMenuManager.m_nPrefsVideoMode; + #if GTA_VERSION >= GTA3_PC_11 + if (_dwOperatingSystemVersion == OS_WIN98) { + CMBlur::BlurOn = false; + CMBlur::MotionBlurClose(); + } else { + CMBlur::BlurOn = true; + CMBlur::MotionBlurOpen(Scene.camera); + } + #else + CMBlur::BlurOn = true; + #endif + FrontEndMenuManager.SaveSettings(); + #endif +} + +void RestoreDefDisplay(int8 action) { + if (action != FEOPTION_ACTION_SELECT) + return; + + #ifdef CUTSCENE_BORDERS_SWITCH + CMenuManager::m_PrefsCutsceneBorders = true; + #endif + #ifdef FREE_CAM + TheCamera.bFreeCam = false; + #endif + #ifdef PED_CAR_DENSITY_SLIDERS + CIniFile::LoadIniFile(); + #endif + #ifdef GRAPHICS_MENU_OPTIONS // otherwise Frontend will handle those + CMenuManager::m_PrefsBrightness = 256; + CMenuManager::m_PrefsLOD = 1.2f; + CRenderer::ms_lodDistScale = 1.2f; + CMenuManager::m_PrefsShowSubtitles = true; + FrontEndMenuManager.SaveSettings(); + #endif +} + +#ifdef NO_ISLAND_LOADING +const char *islandLoadingOpts[] = { "FEM_LOW", "FEM_MED", "FEM_HIG" }; +void IslandLoadingAfterChange(int8 before, int8 after) { + if (!FrontEndMenuManager.m_bGameNotLoaded) { + if (after > FrontEndMenuManager.ISLAND_LOADING_LOW) { + FrontEndMenuManager.m_PrefsIslandLoading = before; // calls below needs previous mode :shrug: + + if (after == FrontEndMenuManager.ISLAND_LOADING_HIGH) + CStreaming::RemoveIslandsNotUsed(LEVEL_GENERIC); + if (before == FrontEndMenuManager.ISLAND_LOADING_LOW) { + if (CGame::currLevel != LEVEL_INDUSTRIAL) + CFileLoader::LoadCollisionFromDatFile(LEVEL_INDUSTRIAL); + if (CGame::currLevel != LEVEL_COMMERCIAL) + CFileLoader::LoadCollisionFromDatFile(LEVEL_COMMERCIAL); + if (CGame::currLevel != LEVEL_SUBURBAN) + CFileLoader::LoadCollisionFromDatFile(LEVEL_SUBURBAN); + CCollision::bAlreadyLoaded = true; + FrontEndMenuManager.m_PrefsIslandLoading = after; + CStreaming::RequestBigBuildings(CGame::currLevel); + + } else if (before == FrontEndMenuManager.ISLAND_LOADING_HIGH) { + FrontEndMenuManager.m_PrefsIslandLoading = after; + CStreaming::RequestIslands(CGame::currLevel); + } else + FrontEndMenuManager.m_PrefsIslandLoading = after; + + } else { // low + CCollision::bAlreadyLoaded = false; + CModelInfo::RemoveColModelsFromOtherLevels(CGame::currLevel); + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + CStreaming::RequestIslands(CGame::currLevel); + } + + CStreaming::LoadAllRequestedModels(true); + } + + FrontEndMenuManager.SetHelperText(0); +} +#endif + +#ifdef PED_CAR_DENSITY_SLIDERS +void PedDensityChange(float before, float after) { + CPopulation::MaxNumberOfPedsInUse = DEFAULT_MAX_NUMBER_OF_PEDS * after; +} + +void CarDensityChange(float before, float after) { + CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS * after; +} +#endif + +#ifndef MULTISAMPLING +void GraphicsGoBack() { +} +#else +void GraphicsGoBack() { + FrontEndMenuManager.m_nDisplayMSAALevel = FrontEndMenuManager.m_nPrefsMSAALevel; +} + +void MultiSamplingButtonPress(int8 action) { + if (action == FEOPTION_ACTION_SELECT) { + if (FrontEndMenuManager.m_nDisplayMSAALevel != FrontEndMenuManager.m_nPrefsMSAALevel) { + FrontEndMenuManager.m_nPrefsMSAALevel = FrontEndMenuManager.m_nDisplayMSAALevel; + _psSelectScreenVM(FrontEndMenuManager.m_nPrefsVideoMode); + FrontEndMenuManager.SetHelperText(0); + FrontEndMenuManager.SaveSettings(); + } + } else if (action == FEOPTION_ACTION_LEFT || action == FEOPTION_ACTION_RIGHT) { + if (FrontEndMenuManager.m_bGameNotLoaded) { + FrontEndMenuManager.m_nDisplayMSAALevel += (action == FEOPTION_ACTION_RIGHT ? 1 : -1); + + int i = 0; + int maxAA = RwD3D8EngineGetMaxMultiSamplingLevels(); + while (maxAA != 1) { + i++; + maxAA >>= 1; + } + + if (FrontEndMenuManager.m_nDisplayMSAALevel < 0) + FrontEndMenuManager.m_nDisplayMSAALevel = i; + else if (FrontEndMenuManager.m_nDisplayMSAALevel > i) + FrontEndMenuManager.m_nDisplayMSAALevel = 0; + } + } else if (action == FEOPTION_ACTION_FOCUSLOSS) { + if (FrontEndMenuManager.m_nDisplayMSAALevel != FrontEndMenuManager.m_nPrefsMSAALevel) { + FrontEndMenuManager.m_nDisplayMSAALevel = FrontEndMenuManager.m_nPrefsMSAALevel; + FrontEndMenuManager.SetHelperText(3); + } + } +} + +wchar* MultiSamplingDraw(bool *disabled, bool userHovering) { + static wchar unicodeTemp[64]; + if (userHovering) { + if (FrontEndMenuManager.m_nDisplayMSAALevel == FrontEndMenuManager.m_nPrefsMSAALevel) { + if (FrontEndMenuManager.m_nHelperTextMsgId == 1) // Press enter to apply + FrontEndMenuManager.ResetHelperText(); + } else { + FrontEndMenuManager.SetHelperText(1); + } + } else { + if (FrontEndMenuManager.m_nDisplayMSAALevel != FrontEndMenuManager.m_nPrefsMSAALevel) { + FrontEndMenuManager.m_nDisplayMSAALevel = FrontEndMenuManager.m_nPrefsMSAALevel; + } + } + + if (!FrontEndMenuManager.m_bGameNotLoaded) + *disabled = true; + + switch (FrontEndMenuManager.m_nDisplayMSAALevel) { + case 0: + return TheText.Get("FEM_OFF"); + default: + sprintf(gString, "%iX", 1 << (FrontEndMenuManager.m_nDisplayMSAALevel)); + AsciiToUnicode(gString, unicodeTemp); + return unicodeTemp; + } +} +#endif + +#ifdef IMPROVED_VIDEOMODE +const char* screenModes[] = { "FED_FLS", "FED_WND" }; +void ScreenModeAfterChange(int8 before, int8 after) +{ + _psSelectScreenVM(FrontEndMenuManager.m_nPrefsVideoMode); // apply same resolution + FrontEndMenuManager.SetHelperText(0); +} + +#endif + +#ifdef DETECT_JOYSTICK_MENU +wchar selectedJoystickUnicode[128]; +int cachedButtonNum = -1; + +wchar* DetectJoystickDraw(bool* disabled, bool userHovering) { + +#if defined RW_GL3 && !defined LIBRW_SDL2 + int numButtons; + int found = -1; + const char *joyname; + if (userHovering) { + for (int i = 0; i <= GLFW_JOYSTICK_LAST; i++) { + if ((joyname = glfwGetJoystickName(i))) { + const uint8* buttons = glfwGetJoystickButtons(i, &numButtons); + for (int j = 0; j < numButtons; j++) { + if (buttons[j]) { + found = i; + break; + } + } + if (found != -1) + break; + } + } + + if (found != -1 && PSGLOBAL(joy1id) != found) { + if (PSGLOBAL(joy1id) != -1 && PSGLOBAL(joy1id) != found) + PSGLOBAL(joy2id) = PSGLOBAL(joy1id); + else + PSGLOBAL(joy2id) = -1; + + strcpy(gSelectedJoystickName, joyname); + PSGLOBAL(joy1id) = found; + cachedButtonNum = numButtons; + } + } + if (PSGLOBAL(joy1id) == -1) +#elif defined XINPUT + int found = -1; + XINPUT_STATE xstate; + memset(&xstate, 0, sizeof(XINPUT_STATE)); + if (userHovering) { + for (int i = 0; i <= 3; i++) { + if (XInputGetState(i, &xstate) == ERROR_SUCCESS) { + if (xstate.Gamepad.bLeftTrigger || xstate.Gamepad.bRightTrigger) { + found = i; + break; + } + for (int j = XINPUT_GAMEPAD_DPAD_UP; j != XINPUT_GAMEPAD_Y << 1; j = (j << 1)) { + if (xstate.Gamepad.wButtons & j) { + found = i; + break; + } + } + if (found != -1) + break; + } + } + if (found != -1 && CPad::XInputJoy1 != found) { + // We should never leave pads -1, so we can process them when they're connected and kinda support hotplug. + CPad::XInputJoy2 = (CPad::XInputJoy1 == -1 ? (found + 1) % 4 : CPad::XInputJoy1); + CPad::XInputJoy1 = found; + cachedButtonNum = 0; // fake too, because xinput bypass CControllerConfig + } + } + sprintf(gSelectedJoystickName, "%d", CPad::XInputJoy1); // fake, on xinput we only store gamepad ids(thanks MS) so this is a temp variable to be used below + if (CPad::XInputJoy1 == -1) +#endif + AsciiToUnicode("Not found", selectedJoystickUnicode); + else + AsciiToUnicode(gSelectedJoystickName, selectedJoystickUnicode); + + return selectedJoystickUnicode; +} + +void DetectJoystickGoBack() { + if (cachedButtonNum != -1) { +#ifdef LOAD_INI_SETTINGS + ControlsManager.InitDefaultControlConfigJoyPad(cachedButtonNum); + SaveINIControllerSettings(); +#else + // Otherwise no way to save gSelectedJoystickName or ms_padButtonsInited anyway :shrug: Why do you even use this config.?? +#endif + cachedButtonNum = -1; + } +} +#endif + +#ifdef GAMEPAD_MENU +const char* controllerTypes[] = { "FEC_DS2", "FEC_DS3", "FEC_DS4", "FEC_360", "FEC_ONE", "FEC_NSW" }; +void ControllerTypeAfterChange(int8 before, int8 after) +{ + FrontEndMenuManager.LoadController(after); +} +#endif + +CMenuScreenCustom aScreens[MENUPAGES] = { + // MENUPAGE_NONE = 0 + { "", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, }, + + // MENUPAGE_STATS = 1 + { "FET_STA", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_NEW_GAME = 2 + { "FET_SGA", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_CHANGEMENU, "FES_SNG", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD }, + MENUACTION_POPULATESLOTS_CHANGEMENU, "GMLOAD", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT }, + MENUACTION_POPULATESLOTS_CHANGEMENU, "FES_DGA", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_BRIEFS = 3 + { "FET_BRE", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CONTROLLER_SETTINGS = 4 +#if defined(GAMEPAD_MENU) && !defined(GTA_HANDHELD) + { "FET_AGS", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, +#else + { "FET_AGS", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, +#endif + MENUACTION_CTRLCONFIG, "FEC_CCF", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, + MENUACTION_CTRLDISPLAY, "FEC_CDP", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, + INVERT_PAD_SELECTOR + MENUACTION_CTRLVIBRATION, "FEC_VIB", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, + SELECT_CONTROLLER_TYPE + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_SOUND_SETTINGS = 5 + { "FET_AUD", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_MUSICVOLUME, "FEA_MUS", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_SFXVOLUME, "FEA_SFX", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, +#ifdef EXTERNAL_3D_SOUND + MENUACTION_AUDIOHW, "FEA_3DH", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_SPEAKERCONF, "FEA_SPK", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, +#endif +#ifdef AUDIO_REFLECTIONS + MENUACTION_DYNAMICACOUSTIC, "FET_DAM", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, +#endif + MENUACTION_RADIO, "FEA_RSS", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_RESTOREDEF, "FET_DEF", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + +#ifndef GRAPHICS_MENU_OPTIONS + // MENUPAGE_DISPLAY_SETTINGS = 6 + { "FET_DIS", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_BRIGHTNESS, "FED_BRI", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_DRAWDIST, "FEM_LOD", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + DENSITY_SLIDERS + MENUACTION_FRAMESYNC, "FEM_VSC", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_FRAMELIMIT, "FEM_FRM", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, +#ifndef EXTENDED_COLOURFILTER + MENUACTION_TRAILS, "FED_TRA", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, +#endif + MENUACTION_SUBTITLES, "FED_SUB", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_WIDESCREEN, "FED_WIS", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_SCREENRES, "FED_RES", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + VIDEOMODE_SELECTOR + MULTISAMPLING_SELECTOR + ISLAND_LOADING_SELECTOR + DUALPASS_SELECTOR + CUTSCENE_BORDERS_TOGGLE + FREE_CAM_TOGGLE + POSTFX_SELECTORS + // re3.cpp inserts here pipeline selectors if neo/neo.txd exists and EXTENDED_PIPELINES defined + MENUACTION_RESTOREDEF, "FET_DEF", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#else + // MENUPAGE_DISPLAY_SETTINGS = 6 + { "FET_DIS", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_BRIGHTNESS, "FED_BRI", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_DRAWDIST, "FEM_LOD", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + DENSITY_SLIDERS + CUTSCENE_BORDERS_TOGGLE + FREE_CAM_TOGGLE + MENUACTION_SUBTITLES, "FED_SUB", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_CFO_DYNAMIC, "FET_DEF", { new CCFODynamic(nil, nil, nil, nil, RestoreDefDisplay) }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + + // MENUPAGE_LANGUAGE_SETTINGS = 7 + { "FET_LAN", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_LANG_ENG, "FEL_ENG", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_LANG_FRE, "FEL_FRE", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_LANG_GER, "FEL_GER", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_LANG_ITA, "FEL_ITA", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_LANG_SPA, "FEL_SPA", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + // CustomFrontendOptionsPopulate will add languages here, if files are found + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CHOOSE_LOAD_SLOT = 8 + { "FET_LG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_CHANGEMENU, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_CHECKSAVE, "FEM_SL0", { nil, SAVESLOT_1, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL1", { nil, SAVESLOT_2, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL2", { nil, SAVESLOT_3, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL3", { nil, SAVESLOT_4, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL4", { nil, SAVESLOT_5, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL5", { nil, SAVESLOT_6, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL6", { nil, SAVESLOT_7, MENUPAGE_LOAD_SLOT_CONFIRM }, + MENUACTION_CHECKSAVE, "FEM_SL7", { nil, SAVESLOT_8, MENUPAGE_LOAD_SLOT_CONFIRM }, + }, + + // MENUPAGE_CHOOSE_DELETE_SLOT = 9 + { "FET_DG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_CHANGEMENU, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_CHANGEMENU, "FEM_SL0", { nil, SAVESLOT_1, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL1", { nil, SAVESLOT_2, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL2", { nil, SAVESLOT_3, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL3", { nil, SAVESLOT_4, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL4", { nil, SAVESLOT_5, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL5", { nil, SAVESLOT_6, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL6", { nil, SAVESLOT_7, MENUPAGE_DELETE_SLOT_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL7", { nil, SAVESLOT_8, MENUPAGE_DELETE_SLOT_CONFIRM }, + }, + + // MENUPAGE_NEW_GAME_RELOAD = 10 + { "FET_NG", MENUPAGE_NEW_GAME, MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_LABEL, "FESZ_QR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_NEWGAME, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD }, + }, + + // MENUPAGE_LOAD_SLOT_CONFIRM = 11 + { "FET_LG", MENUPAGE_CHOOSE_LOAD_SLOT, MENUPAGE_CHOOSE_LOAD_SLOT, nil, nil, + MENUACTION_LABEL, "FESZ_QL", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT }, + MENUACTION_CHANGEMENU, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS }, + }, + + // MENUPAGE_DELETE_SLOT_CONFIRM = 12 + { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, nil, nil, + MENUACTION_LABEL, "FESZ_QD", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT }, + MENUACTION_CHANGEMENU, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_DELETING }, + }, + + // MENUPAGE_NO_MEMORY_CARD = 13 + { "FES_NOC", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + // hud adjustment page in mobile + }, + + // MENUPAGE_LOADING_IN_PROGRESS = 14 + { "FET_LG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FED_LDW", { nil, SAVESLOT_NONE, MENUPAGE_LOAD_SLOT_CONFIRM }, + }, + + // MENUPAGE_DELETING_IN_PROGRESS = 15 + { "FET_DG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FEDL_WR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_PS2_LOAD_FAILED = 16 + { "FET_LG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FES_LOE", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_DELETE_FAILED = 17 + { "FET_DG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FES_DEE", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEC_OKK", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT }, + }, + + // MENUPAGE_DEBUG_MENU = 18 + { "FED_DBG", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_RELOADIDE, "FED_RID", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_RELOADIPL, "FED_RIP", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SETDBGFLAG, "FED_DFL", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SWITCHBIGWHITEDEBUGLIGHT, "FED_DLS", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_PEDROADGROUPS, "FED_SPR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CARROADGROUPS, "FED_SCR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_COLLISIONPOLYS, "FED_SCP", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_PARSEHEAP, "FED_PAH", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SHOWCULL, "FED_SCZ", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_DEBUGSTREAM, "FED_DSR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_MEMORY_CARD_DEBUG = 19 + { "FEM_MCM", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_REGMEMCARD1, "FEM_RMC", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_TESTFORMATMEMCARD1, "FEM_TFM", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_TESTUNFORMATMEMCARD1, "FEM_TUM", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CREATEROOTDIR, "FEM_CRD", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CREATELOADICONS, "FEM_CLI", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_FILLWITHGUFF, "FEM_FFF", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SAVEONLYTHEGAME, "FEM_SOG", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SAVEGAME, "FEM_STG", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_SAVEGAMEUNDERGTA, "FEM_STS", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CREATECOPYPROTECTED, "FEM_CPD", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_MEMORY_CARD_TEST = 20 + { "FEM_MC2", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_MAIN = 21 + { "FET_MP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_PS2_SAVE_FAILED = 22 + { "MCDNSP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_PS2_SAVE_FAILED_2 = 23 + { "MCGNSP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_MEMCARDSAVECONFIRM, "JAILB_U", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // Unused in PC but anyway + // MENUPAGE_SAVE = 24 +#ifdef PS2_SAVE_DIALOG + { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_CHANGEMENU, "FESZ_SA", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#else + { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FES_SCG", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_POPULATESLOTS_CHANGEMENU, "GMSAVE", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + + // MENUPAGE_NO_MEMORY_CARD_2 = 25 + { "FES_NOC", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_CHANGEMENU, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CHOOSE_SAVE_SLOT = 26 + { "FET_SG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_RESUME_FROM_SAVEZONE, "FESZ_CA", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_SL1", { nil, SAVESLOT_1, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL2", { nil, SAVESLOT_2, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL3", { nil, SAVESLOT_3, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL4", { nil, SAVESLOT_4, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL5", { nil, SAVESLOT_5, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL6", { nil, SAVESLOT_6, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL7", { nil, SAVESLOT_7, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + MENUACTION_CHANGEMENU, "FEM_SL8", { nil, SAVESLOT_8, MENUPAGE_SAVE_OVERWRITE_CONFIRM }, + }, + + // MENUPAGE_SAVE_OVERWRITE_CONFIRM = 27 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FESZ_QO", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_SAVING_IN_PROGRESS }, + MENUACTION_CHANGEMENU, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + }, + + // MENUPAGE_MULTIPLAYER_MAP = 28 + { "FET_MAP", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_CONNECTION = 29 + { "FET_CON", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_FIND_GAME = 30 + { "FET_FG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_MODE = 31 + { "FET_GT", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_CREATE = 32 + { "FET_HG", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_MULTIPLAYER_START = 33 + { "FEN_STA", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_SKIN_SELECT_OLD = 34 + { "FET_PS", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_CONTROLLER_PC = 35 + { "FET_CTL", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, +#ifdef PC_PLAYER_CONTROLS + MENUACTION_CTRLMETHOD, "FET_CME", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC }, +#endif + MENUACTION_KEYBOARDCTRLS,"FET_RDK", { nil, SAVESLOT_NONE, MENUPAGE_KEYBOARD_CONTROLS }, +#ifdef GAMEPAD_MENU + MENUACTION_CHANGEMENU, "FET_AGS", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, +#endif +#ifdef DETECT_JOYSTICK_MENU + MENUACTION_CHANGEMENU, "FEC_JOD", { nil, SAVESLOT_NONE, MENUPAGE_DETECT_JOYSTICK }, +#endif + MENUACTION_CHANGEMENU, "FET_AMS", { nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS }, + MENUACTION_RESTOREDEF, "FET_DEF", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CONTROLLER_PC_OLD1 = 36 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_GETKEY, "FEC_PLB", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_CWL", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_CWR", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_LKT", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_PJP", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_PSP", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_TLF", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_TRG", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_GETKEY, "FEC_CCM", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1 }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CONTROLLER_PC_OLD2 = 37 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + + }, + + // MENUPAGE_CONTROLLER_PC_OLD3 = 38 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_GETKEY, "FEC_LUP", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3 }, + MENUACTION_GETKEY, "FEC_LDN", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3 }, + MENUACTION_GETKEY, "FEC_SMS", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3 }, + MENUACTION_SHOWHEADBOB, "FEC_GSL", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3 }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_CONTROLLER_PC_OLD4 = 39 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + + }, + + // MENUPAGE_CONTROLLER_DEBUG = 40 + { "FEC_DBG", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_GETKEY, "FEC_TGD", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG }, + MENUACTION_GETKEY, "FEC_TDO", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG }, + MENUACTION_GETKEY, "FEC_TSS", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG }, + MENUACTION_GETKEY, "FEC_SMS", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_OPTIONS = 41 + { "FET_OPT", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, +#ifdef GTA_HANDHELD + MENUACTION_CHANGEMENU, "FET_CTL", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, +#else + MENUACTION_CHANGEMENU, "FET_CTL", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC }, +#endif + MENUACTION_LOADRADIO, "FET_AUD", { nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS }, + MENUACTION_CHANGEMENU, "FET_DIS", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, +#ifdef GRAPHICS_MENU_OPTIONS + MENUACTION_CHANGEMENU, "FET_GFX", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, +#endif + MENUACTION_CHANGEMENU, "FET_LAN", { nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS }, + MENUACTION_PLAYERSETUP, "FET_PSU", { nil, SAVESLOT_NONE, MENUPAGE_SKIN_SELECT }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_EXIT = 42 + { "FET_QG", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_LABEL, "FEQ_SRE", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_DONTCANCEL, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CANCELGAME, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_SAVING_IN_PROGRESS = 43 + { "", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FES_WAR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_SAVE_SUCCESSFUL = 44 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FES_SSC", { nil, SAVESLOT_LABEL, MENUPAGE_NONE }, + MENUACTION_RESUME_FROM_SAVEZONE, "FEC_OKK", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + }, + + // MENUPAGE_DELETING = 45 + { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, nil, nil, + MENUACTION_LABEL, "FED_DLW", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_DELETE_SUCCESS = 46 + { "FET_DG", MENUPAGE_CHOOSE_DELETE_SLOT, MENUPAGE_CHOOSE_DELETE_SLOT, nil, nil, + MENUACTION_LABEL, "DEL_FNM", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEC_OKK", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT }, + }, + + // MENUPAGE_SAVE_FAILED = 47 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FEC_SVU", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEC_OKK", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT }, + }, + + // MENUPAGE_LOAD_FAILED = 48 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FEC_SVU", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_LOAD_FAILED_2 = 49 + { "FET_LG", MENUPAGE_CHOOSE_SAVE_SLOT, MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FEC_LUN", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT }, + }, + + // MENUPAGE_FILTER_GAME = 50 + { "FIL_FLT", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + + }, + + // MENUPAGE_START_MENU = 51 + { "FEM_MM", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_CHANGEMENU, "FEN_STA", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_CHANGEMENU, "FET_OPT", { nil, SAVESLOT_NONE, MENUPAGE_OPTIONS }, + MENUACTION_CHANGEMENU, "FEM_QT", { nil, SAVESLOT_NONE, MENUPAGE_EXIT }, + }, + + // MENUPAGE_PAUSE_MENU = 52 + { "FET_PAU", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_RESUME, "FEM_RES", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEN_STA", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + // CMenuManager::LoadAllTextures will add map here, if MENU_MAP enabled and map textures are found + MENUACTION_CHANGEMENU, "FEP_STA", { nil, SAVESLOT_NONE, MENUPAGE_STATS }, + MENUACTION_CHANGEMENU, "FEP_BRI", { nil, SAVESLOT_NONE, MENUPAGE_BRIEFS }, + MENUACTION_CHANGEMENU, "FET_OPT", { nil, SAVESLOT_NONE, MENUPAGE_OPTIONS }, + MENUACTION_CHANGEMENU, "FEM_QT", { nil, SAVESLOT_NONE, MENUPAGE_EXIT }, + }, + + // MENUPAGE_CHOOSE_MODE = 53 + { "FEN_STA", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_CHANGEMENU, "FET_SP", { nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME }, + MENUACTION_INITMP, "FET_MP", { nil, SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + + // MENUPAGE_SKIN_SELECT = 54 + { "FET_PSU", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, nil, nil, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_MULTIPLAYER_MAIN }, + }, + + // MENUPAGE_KEYBOARD_CONTROLS = 55 + { "FET_STI", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC }, + }, + + // MENUPAGE_MOUSE_CONTROLS = 56 + { "FET_MTI", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_MOUSESENS, "FEC_MSH", { nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS }, + MENUACTION_INVVERT, "FEC_IVV", { nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS }, +#ifndef GAMEPAD_MENU + INVERT_PAD_SELECTOR +#endif + MENUACTION_MOUSESTEER, "FET_MST", { nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, + // MENUPAGE_MISSION_RETRY = 57 +#ifdef MISSION_REPLAY + + { "M_FAIL", MENUPAGE_DISABLED, MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FESZ_RM", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CHANGEMENU, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS }, + MENUACTION_REJECT_RETRY, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#else + { "", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + // mission failed, wanna restart page in mobile + }, +#endif + +#ifdef MENU_MAP + // MENUPAGE_MAP + { "FEG_MAP", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + MENUACTION_UNK110, "", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, // to prevent cross/enter to go back + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + +#ifdef GRAPHICS_MENU_OPTIONS + // MENUPAGE_GRAPHICS_SETTINGS + { "FET_GFX", MENUPAGE_OPTIONS, MENUPAGE_OPTIONS, + new CCustomScreenLayout({MENUSPRITE_MAINMENU, 50, 0, 20, FONT_HEADING, FESCREEN_LEFT_ALIGN, true, MEDIUMTEXT_X_SCALE, MEDIUMTEXT_Y_SCALE}), GraphicsGoBack, + +#ifndef GTA_HANDHELD + MENUACTION_SCREENRES, "FED_RES", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, +#endif + MENUACTION_WIDESCREEN, "FED_WIS", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, + VIDEOMODE_SELECTOR + MENUACTION_FRAMESYNC, "FEM_VSC", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MENUACTION_FRAMELIMIT, "FEM_FRM", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, + MULTISAMPLING_SELECTOR + ISLAND_LOADING_SELECTOR + DUALPASS_SELECTOR +#ifdef EXTENDED_COLOURFILTER + POSTFX_SELECTORS +#else + MENUACTION_TRAILS, "FED_TRA", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, +#endif + // re3.cpp inserts here pipeline selectors if neo/neo.txd exists and EXTENDED_PIPELINES defined + MENUACTION_CFO_DYNAMIC, "FET_DEF", { new CCFODynamic(nil, nil, nil, nil, RestoreDefGraphics) }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + +#ifdef DETECT_JOYSTICK_MENU + // MENUPAGE_DETECT_JOYSTICK + { "FEC_JOD", MENUPAGE_CONTROLLER_PC, MENUPAGE_CONTROLLER_PC, + new CCustomScreenLayout({MENUSPRITE_MAINMENU, 40, 60, 20, FONT_BANK, FESCREEN_LEFT_ALIGN, false, MEDIUMTEXT_X_SCALE, MEDIUMTEXT_Y_SCALE}), DetectJoystickGoBack, + + MENUACTION_LABEL, "FEC_JPR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + MENUACTION_CFO_DYNAMIC, "FEC_JDE", { new CCFODynamic(nil, nil, nil, DetectJoystickDraw, nil) }, + MENUACTION_CHANGEMENU, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, + }, +#endif + + // MENUPAGE_UNK + { "", MENUPAGE_NONE, MENUPAGE_NONE, nil, nil, + + }, + +}; + +#endif +#endif diff --git a/src/core/Pad.cpp b/src/core/Pad.cpp new file mode 100644 index 0000000..60bb7a7 --- /dev/null +++ b/src/core/Pad.cpp @@ -0,0 +1,3025 @@ +#define WITHDINPUT +#include "common.h" +#include "crossplatform.h" +#include "platform.h" +#ifdef XINPUT +#include +#if !defined(PSAPI_VERSION) || (PSAPI_VERSION > 1) +#pragma comment( lib, "Xinput9_1_0.lib" ) +#else +#pragma comment( lib, "Xinput.lib" ) +#endif +#endif + +#include "Pad.h" +#include "ControllerConfig.h" +#include "Timer.h" +#include "Frontend.h" +#include "Camera.h" +#include "Game.h" +#include "CutsceneMgr.h" +#include "Font.h" +#include "Hud.h" +#include "Text.h" +#include "Timer.h" +#include "Record.h" +#include "World.h" +#include "Vehicle.h" +#include "Ped.h" +#include "Population.h" +#include "Record.h" +#include "Replay.h" +#include "Weather.h" +#include "Streaming.h" +#include "PathFind.h" +#include "Wanted.h" +#include "General.h" + +#ifdef GTA_PS2 +#include "eetypes.h" +#include "libpad.h" +#endif + +CPad Pads[MAX_PADS]; +#ifdef GTA_PS2 +u_long128 pad_dma_buf[scePadDmaBufferMax] __attribute__((aligned(64))); +u_long128 pad2_dma_buf[scePadDmaBufferMax] __attribute__((aligned(64))); +#endif + +CMousePointerStateHelper MousePointerStateHelper; + +bool CPad::bDisplayNoControllerMessage; +bool CPad::bObsoleteControllerMessage; +bool CPad::bOldDisplayNoControllerMessage; +bool CPad::m_bMapPadOneToPadTwo; +#ifdef INVERT_LOOK_FOR_PAD +bool CPad::bInvertLook4Pad; +#endif +#ifdef GTA_PS2 +unsigned char act_direct[6]; +unsigned char act_align[6]; +#endif + +CKeyboardState CPad::OldKeyState; +CKeyboardState CPad::NewKeyState; +CKeyboardState CPad::TempKeyState; + +char CPad::KeyBoardCheatString[20]; + +CMouseControllerState CPad::OldMouseControllerState; +CMouseControllerState CPad::NewMouseControllerState; +CMouseControllerState CPad::PCTempMouseControllerState; + +#ifdef DETECT_PAD_INPUT_SWITCH +bool CPad::IsAffectedByController = false; +#endif + +_TODO("gbFastTime"); +extern bool gbFastTime; + +void WeaponCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT2"), true); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_BASEBALLBAT, 0); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_COLT45, 100); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_UZI, 100); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_SHOTGUN, 20); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_AK47, 200); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_M16, 200); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_SNIPERRIFLE, 5); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_ROCKETLAUNCHER, 5); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_MOLOTOV, 5); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_GRENADE, 5); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_FLAMETHROWER, 200); +} + +void HealthCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT3"), true); + FindPlayerPed()->m_fHealth = 100.0f; + if (FindPlayerVehicle()) { + FindPlayerVehicle()->m_fHealth = 1000.0f; + if (FindPlayerVehicle()->m_vehType == VEHICLE_TYPE_CAR) + ((CAutomobile*)FindPlayerVehicle())->Damage.SetEngineStatus(0); + } +} + +void TankCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CStreaming::RequestModel(MI_RHINO, 0); + CStreaming::LoadAllRequestedModels(false); + if (CStreaming::ms_aInfoForModel[MI_RHINO].m_loadState == STREAMSTATE_LOADED) { + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + int32 node = ThePaths.FindNodeClosestToCoors(FindPlayerCoors(), PATH_CAR, 100.0f); + + if (node < 0) return; + +#ifdef FIX_BUGS + CAutomobile* tank = new CAutomobile(MI_RHINO, RANDOM_VEHICLE); +#else + CAutomobile *tank = new CAutomobile(MI_RHINO, MISSION_VEHICLE); +#endif + if (tank != nil) { + CVector pos = ThePaths.m_pathNodes[node].GetPosition(); + pos.z += 4.0f; + tank->SetPosition(pos); + tank->SetOrientation(0.0f, 0.0f, DEGTORAD(200.0f)); + + tank->SetStatus(STATUS_ABANDONED); + tank->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(tank); + } + } +} + +void BlowUpCarsCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + + int i = CPools::GetVehiclePool()->GetSize(); + while (i-- > 0) { + if (CVehicle *veh = CPools::GetVehiclePool()->GetSlot(i)) + veh->BlowUpCar(nil); + } +} + +void ChangePlayerCheat() +{ + int modelId; + + if (FindPlayerPed()->IsPedInControl() && CModelInfo::GetModelInfo("player", nil)) { + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CPlayerPed *ped = FindPlayerPed(); + AssocGroupId AnimGrp = ped->m_animGroup; + do + { + do + modelId = CGeneral::GetRandomNumberInRange(0, MI_CAS_WOM+1); + while (!CModelInfo::GetModelInfo(modelId)); + } while (modelId >= MI_SPECIAL01 && modelId <= MI_SPECIAL04 || modelId == MI_TAXI_D); + + uint8 flags = CStreaming::ms_aInfoForModel[modelId].m_flags; + ped->DeleteRwObject(); + CStreaming::RequestModel(modelId, STREAMFLAGS_DEPENDENCY| STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + ped->m_modelIndex = -1; + ped->SetModelIndex(modelId); + ped->m_animGroup = AnimGrp; + if (modelId != MI_PLAYER) { + if (!(flags & STREAMFLAGS_DONT_REMOVE)) + CStreaming::SetModelIsDeletable(modelId); + } + } +} + +void MayhemCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + for (int i = PEDTYPE_CIVMALE; i < PEDTYPE_SPECIAL; i++) + CPedType::SetThreats(i, PED_FLAG_PLAYER1 | PED_FLAG_PLAYER2 | PED_FLAG_PLAYER3 | PED_FLAG_PLAYER4 | + PED_FLAG_CIVMALE | PED_FLAG_CIVFEMALE | PED_FLAG_COP | PED_FLAG_GANG1 | + PED_FLAG_GANG2 | PED_FLAG_GANG3 | PED_FLAG_GANG4 | PED_FLAG_GANG5 | + PED_FLAG_GANG6 | PED_FLAG_GANG7 | PED_FLAG_GANG8 | PED_FLAG_GANG9 | + PED_FLAG_EMERGENCY | PED_FLAG_PROSTITUTE | PED_FLAG_CRIMINAL | PED_FLAG_SPECIAL ); +} + +void EverybodyAttacksPlayerCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + for (int i = PEDTYPE_CIVMALE; i < PEDTYPE_SPECIAL; i++) + CPedType::AddThreat(i, PED_FLAG_PLAYER1); +} + +void WeaponsForAllCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CPopulation::ms_bGivePedsWeapons = !CPopulation::ms_bGivePedsWeapons; +} + +void FastTimeCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + if (CTimer::GetTimeScale() < 4.0f) + CTimer::SetTimeScale(CTimer::GetTimeScale() * 2.0f); +} + +void SlowTimeCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + if (CTimer::GetTimeScale() > 0.25f) + CTimer::SetTimeScale(CTimer::GetTimeScale() * 0.5f); +} + +void MoneyCheat() +{ + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 250000; + CHud::SetHelpMessage(TheText.Get("CHEAT6"), true); +} + +void ArmourCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT4"), true); + FindPlayerPed()->m_fArmour = 100.0f; +} + +void WantedLevelUpCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT5"), true); + FindPlayerPed()->SetWantedLevel(Min(FindPlayerPed()->m_pWanted->GetWantedLevel() + 2, 6)); +} + +void WantedLevelDownCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT5"), true); + FindPlayerPed()->SetWantedLevel(0); +} + +void SunnyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_SUNNY); +} + +void CloudyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_CLOUDY); +} + +void RainyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_RAINY); +} + +void FoggyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_FOGGY); +} + +void FastWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + gbFastTime = !gbFastTime; +} + +void OnlyRenderWheelsCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bWheelsOnlyCheat = !CVehicle::bWheelsOnlyCheat; +} + + +void ChittyChittyBangBangCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bAllDodosCheat = !CVehicle::bAllDodosCheat; +} + +void StrongGripCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bCheat3 = !CVehicle::bCheat3; +} + +void NastyLimbsCheat() +{ + CPed::bNastyLimbsCheat = !CPed::bNastyLimbsCheat; +} +////////////////////////////////////////////////////////////////////////// + +#ifdef KANGAROO_CHEAT +void KangarooCheat() +{ + wchar *string; + CPed *playerPed = FindPlayerPed(); + int m_fMass; + + if (playerPed->m_ped_flagI80) { + string = TheText.Get("CHEATOF"); + m_fMass = 70.0f; + } else { + string = TheText.Get("CHEAT1"); + m_fMass = 15.0f; + } + CHud::SetHelpMessage(string, true); + playerPed->m_ped_flagI80 = !playerPed->m_ped_flagI80; + + playerPed->m_fMass = m_fMass; + playerPed->m_fAirResistance = 0.4f / m_fMass; +} +#endif + +#ifdef ALLCARSHELI_CHEAT +void AllCarsHeliCheat(void) +{ + wchar* string; + if (bAllCarCheat) { + string = TheText.Get("CHEATOF"); + bAllCarCheat = false; + } + else { + string = TheText.Get("CHEAT1"); + bAllCarCheat = true; + } + CHud::SetHelpMessage(string, true); +} +#endif + +#ifdef ALT_DODO_CHEAT +void AltDodoCheat(void) +{ + wchar* string; + if (CVehicle::bAltDodoCheat) { + string = TheText.Get("CHEATOF"); + CVehicle::bAltDodoCheat = false; + } + else { + string = TheText.Get("CHEAT1"); + CVehicle::bAltDodoCheat = true; + } + CHud::SetHelpMessage(string, true); +} +#endif + +bool +CControllerState::CheckForInput(void) +{ + return !!RightStickX || !!RightStickY || !!LeftStickX || !!LeftStickY + || !!DPadUp || !!DPadDown || !!DPadLeft || !!DPadRight + || !!Triangle || !!Cross || !!Circle || !!Square + || !!Start || !!Select + || !!LeftShoulder1 || !!LeftShoulder2 || !!RightShoulder1 || !!RightShoulder2 + || !!LeftShock || !!RightShock; +} + +void +CControllerState::Clear(void) +{ + LeftStickX = LeftStickY = RightStickX = RightStickY = 0; + LeftShoulder1 = LeftShoulder2 = RightShoulder1 = RightShoulder2 = 0; + DPadUp = DPadDown = DPadLeft = DPadRight = 0; + Start = Select = 0; + Square = Triangle = Cross = Circle = 0; + LeftShock = RightShock = 0; + NetworkTalk = 0; +} + +void CKeyboardState::Clear() +{ + for ( int32 i = 0; i < ARRAY_SIZE(F); i++ ) + F[i] = 0; + + for ( int32 i = 0; i < ARRAY_SIZE(VK_KEYS); i++ ) + VK_KEYS[i] = 0; + + ESC = INS = DEL = HOME = END = PGUP = PGDN = 0; + + UP = DOWN = LEFT = RIGHT = 0; + + NUMLOCK = 0; + + DIV = MUL = SUB = ADD = 0; + + DECIMAL = NUM1 = NUM2 = NUM3 = NUM4 = 0; + + NUM5 = NUM6 = NUM7 = NUM8 = 0; + + NUM9 = NUM0 = SCROLLLOCK = PAUSE = 0; + + BACKSP = TAB = CAPSLOCK = EXTENTER = 0; + + LSHIFT = SHIFT = RSHIFT = LCTRL = RCTRL = LALT = RALT = 0; + + LWIN = RWIN = APPS = 0; +} + +#ifdef GTA_PS2_STUFF +void CPad::Initialise(void) +{ +#ifdef GTA_PS2 + scePadInit(0); + + scePadPortOpen(0, 0, pad_dma_buf ); + scePadPortOpen(1, 0, pad2_dma_buf ); +#endif + for (int i = 0; i < MAX_PADS; i++) + { + CPad::GetPad(i)->Clear(true); + CPad::GetPad(i)->Mode = 0; + } + + bObsoleteControllerMessage = false; + bOldDisplayNoControllerMessage = false; + bDisplayNoControllerMessage = false; +} +#endif + +void CPad::Clear(bool bResetPlayerControls) +{ + NewState.Clear(); + OldState.Clear(); + + PCTempKeyState.Clear(); + PCTempJoyState.Clear(); + PCTempMouseState.Clear(); + + NewKeyState.Clear(); + OldKeyState.Clear(); + TempKeyState.Clear(); + + NewMouseControllerState.Clear(); + OldMouseControllerState.Clear(); + PCTempMouseControllerState.Clear(); + + Phase = 0; + ShakeFreq = 0; + ShakeDur = 0; + + if ( bResetPlayerControls ) + DisablePlayerControls = PLAYERCONTROL_ENABLED; + + bApplyBrakes = false; + + + for ( int32 i = 0; i < HORNHISTORY_SIZE; i++ ) + bHornHistory[i] = false; + + iCurrHornHistory = 0; + + for ( int32 i = 0; i < ARRAY_SIZE(CheatString); i++ ) + CheatString[i] = ' '; + + LastTimeTouched = CTimer::GetTimeInMilliseconds(); + AverageWeapon = 0; + AverageEntries = 0; +} + +void CPad::ClearMouseHistory() +{ + PCTempMouseControllerState.Clear(); + NewMouseControllerState.Clear(); + OldMouseControllerState.Clear(); +} + +CMouseControllerState::CMouseControllerState() +{ + LMB = 0; + RMB = 0; + MMB = 0; + WHEELUP = 0; + WHEELDN = 0; + MXB1 = 0; + MXB2 = 0; + + x = 0.0f; + y = 0.0f; +} + +void CMouseControllerState::Clear() +{ + LMB = 0; + RMB = 0; + MMB = 0; + WHEELUP = 0; + WHEELDN = 0; + MXB1 = 0; + MXB2 = 0; +} + +CMouseControllerState CMousePointerStateHelper::GetMouseSetUp() +{ + CMouseControllerState state; + +#if defined RW_D3D9 || defined RWLIBS + if ( PSGLOBAL(mouse) == nil ) + _InputInitialiseMouse(); + + if ( PSGLOBAL(mouse) != nil ) + { + DIDEVCAPS devCaps; + devCaps.dwSize = sizeof(DIDEVCAPS); + + PSGLOBAL(mouse)->GetCapabilities(&devCaps); + switch ( devCaps.dwButtons ) + { + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + state.MMB = true; + + case 2: + state.RMB = true; + + case 1: + state.LMB = true; + } + + if ( devCaps.dwAxes == 3 ) + { + state.WHEELDN = true; + state.WHEELUP = true; + } + } +#else + // It seems there is no way to get number of buttons on mouse, so assign all buttons if we have mouse. + double xpos = 1.0f, ypos; + glfwGetCursorPos(PSGLOBAL(window), &xpos, &ypos); + + if (xpos != 0.f) { + state.MMB = true; + state.RMB = true; + state.LMB = true; + state.WHEELDN = true; + state.WHEELUP = true; + } +#endif + + return state; +} + +void CPad::UpdateMouse() +{ +#if defined RW_D3D9 || defined RWLIBS + if ( IsForegroundApp() ) + { + if ( PSGLOBAL(mouse) == nil ) + _InputInitialiseMouse(); + + DIMOUSESTATE2 state; + + if ( PSGLOBAL(mouse) != nil && SUCCEEDED(_InputGetMouseState(&state)) ) + { + int32 signX = 1; + int32 signy = 1; + + if ( !FrontEndMenuManager.m_bMenuActive ) + { + if ( MousePointerStateHelper.bInvertVertically ) + signy = -1; + if ( MousePointerStateHelper.bInvertHorizontally ) + signX = -1; + } + + PCTempMouseControllerState.Clear(); + + PCTempMouseControllerState.x = (float)(signX * state.lX); + PCTempMouseControllerState.y = (float)(signy * state.lY); + PCTempMouseControllerState.LMB = state.rgbButtons[0] & 128; + PCTempMouseControllerState.RMB = state.rgbButtons[1] & 128; + PCTempMouseControllerState.MMB = state.rgbButtons[2] & 128; + PCTempMouseControllerState.MXB1 = state.rgbButtons[3] & 128; + PCTempMouseControllerState.MXB2 = state.rgbButtons[4] & 128; + + if ( state.lZ > 0 ) + PCTempMouseControllerState.WHEELUP = 1; + else if ( state.lZ < 0 ) + PCTempMouseControllerState.WHEELDN = 1; + + OldMouseControllerState = NewMouseControllerState; + NewMouseControllerState = PCTempMouseControllerState; + } + } +#else + if ( IsForegroundApp() && PSGLOBAL(cursorIsInWindow) ) + { + double xpos = 1.0f, ypos; + glfwGetCursorPos(PSGLOBAL(window), &xpos, &ypos); + if (xpos == 0.f) + return; + + int32 signX = 1; + int32 signy = 1; + + if (!FrontEndMenuManager.m_bMenuActive) + { + if (MousePointerStateHelper.bInvertVertically) + signy = -1; + if (MousePointerStateHelper.bInvertHorizontally) + signX = -1; + } + + PCTempMouseControllerState.Clear(); + + PCTempMouseControllerState.x = (float)(signX * (xpos - PSGLOBAL(lastMousePos.x))); + PCTempMouseControllerState.y = (float)(signy * (ypos - PSGLOBAL(lastMousePos.y))); + PCTempMouseControllerState.LMB = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_LEFT); + PCTempMouseControllerState.RMB = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_RIGHT); + PCTempMouseControllerState.MMB = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_MIDDLE); + PCTempMouseControllerState.MXB1 = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_4); + PCTempMouseControllerState.MXB2 = glfwGetMouseButton(PSGLOBAL(window), GLFW_MOUSE_BUTTON_5); + + if (PSGLOBAL(mouseWheel) > 0) + PCTempMouseControllerState.WHEELUP = 1; + else if (PSGLOBAL(mouseWheel) < 0) + PCTempMouseControllerState.WHEELDN = 1; + + PSGLOBAL(lastMousePos.x) = xpos; + PSGLOBAL(lastMousePos.y) = ypos; + PSGLOBAL(mouseWheel) = 0.0f; + + OldMouseControllerState = NewMouseControllerState; + NewMouseControllerState = PCTempMouseControllerState; + } +#endif +} + +CControllerState CPad::ReconcileTwoControllersInput(CControllerState const &State1, CControllerState const &State2) +{ + static CControllerState ReconState; + + ReconState.Clear(); + +#define _RECONCILE_BUTTON(button) \ + { if ( State1.button || State2.button ) ReconState.button = 255; } + +#define _RECONCILE_AXIS_POSITIVE(axis) \ + { if ( State1.axis >= 0 && State2.axis >= 0 ) ReconState.axis = Max(State1.axis, State2.axis); } + +#define _RECONCILE_AXIS_NEGATIVE(axis) \ + { if ( State1.axis <= 0 && State2.axis <= 0 ) ReconState.axis = Min(State1.axis, State2.axis); } + +#define _RECONCILE_AXIS(axis) \ + { _RECONCILE_AXIS_POSITIVE(axis); _RECONCILE_AXIS_NEGATIVE(axis); } + +#define _FIX_AXIS_DIR(axis) \ + { if ( State1.axis > 0 && State2.axis < 0 || State1.axis < 0 && State2.axis > 0 ) ReconState.axis = 0; } + +#define _FIX_RECON_DIR(pos, neg, axis) \ + { if ( (ReconState.pos || ReconState.axis < 0) && (ReconState.neg || ReconState.axis > 0) ) { ReconState.pos = 0; ReconState.neg = 0; ReconState.axis = 0; } } + + _RECONCILE_BUTTON(LeftShoulder1); + _RECONCILE_BUTTON(LeftShoulder2); + _RECONCILE_BUTTON(RightShoulder1); + _RECONCILE_BUTTON(RightShoulder2); + _RECONCILE_BUTTON(Start); + _RECONCILE_BUTTON(Select); + _RECONCILE_BUTTON(Square); + _RECONCILE_BUTTON(Triangle); + _RECONCILE_BUTTON(Cross); + _RECONCILE_BUTTON(Circle); + _RECONCILE_BUTTON(LeftShock); + _RECONCILE_BUTTON(RightShock); + _RECONCILE_BUTTON(NetworkTalk); + _RECONCILE_AXIS(LeftStickX); + _RECONCILE_AXIS(LeftStickY); + _FIX_AXIS_DIR(LeftStickX); + _FIX_AXIS_DIR(LeftStickY); + _RECONCILE_AXIS(RightStickX); + _RECONCILE_AXIS(RightStickY); + _FIX_AXIS_DIR(RightStickX); + _FIX_AXIS_DIR(RightStickY); + _RECONCILE_BUTTON(DPadUp); + _RECONCILE_BUTTON(DPadDown); + _RECONCILE_BUTTON(DPadLeft); + _RECONCILE_BUTTON(DPadRight); + _FIX_RECON_DIR(DPadUp, DPadDown, LeftStickY); + _FIX_RECON_DIR(DPadLeft, DPadRight, LeftStickX); + + return ReconState; + +#undef _RECONCILE_BUTTON +#undef _RECONCILE_AXIS_POSITIVE +#undef _RECONCILE_AXIS_NEGATIVE +#undef _RECONCILE_AXIS +#undef _FIX_AXIS_DIR +#undef _FIX_RECON_DIR +} + +void CPad::StartShake(int16 nDur, uint8 nFreq) +{ + if ( !CMenuManager::m_PrefsUseVibration ) + return; + + if ( CCutsceneMgr::IsRunning() || CGame::playingIntro ) + return; + + if ( nFreq == 0 ) + { + ShakeDur = 0; + ShakeFreq = 0; + return; + } + + if ( nDur > ShakeDur ) + { + ShakeDur = nDur; + ShakeFreq = nFreq; + } +} + +void CPad::StartShake_Distance(int16 nDur, uint8 nFreq, float fX, float fY, float fZ) +{ + if ( !CMenuManager::m_PrefsUseVibration ) + return; + + if ( CCutsceneMgr::IsRunning() || CGame::playingIntro ) + return; + + float fDist = ( TheCamera.GetPosition() - CVector(fX, fY, fZ) ).Magnitude(); + + if ( fDist < 70.0f ) + { + if ( nFreq == 0 ) + { + ShakeDur = 0; + ShakeFreq = 0; + return; + } + + if ( nDur > ShakeDur ) + { + ShakeDur = nDur; + ShakeFreq = nFreq; + } + } +} + +void CPad::StartShake_Train(float fX, float fY) +{ + if ( !CMenuManager::m_PrefsUseVibration ) + return; + + if ( CCutsceneMgr::IsRunning() || CGame::playingIntro ) + return; + + if (FindPlayerVehicle() != nil && FindPlayerVehicle()->IsTrain() ) + return; + + float fDist = ( TheCamera.GetPosition() - CVector(fX, fY, 0.0f) ).Magnitude2D(); + + if ( fDist < 70.0f ) + { + int32 freq = (int32)((70.0f - fDist) * 70.0f / 70.0f + 30.0f); + + if ( ShakeDur < 100 ) + { + ShakeDur = 100; + ShakeFreq = freq; + } + } +} + +#ifdef GTA_PS2_STUFF +void CPad::AddToCheatString(char c) +{ + for ( int32 i = ARRAY_SIZE(CheatString) - 2; i >= 0; i-- ) + CheatString[i + 1] = CheatString[i]; + + CheatString[0] = c; + +#define _CHEATCMP(str) strncmp(str, CheatString, sizeof(str)-1) + // "4414LDRULDRU" - R2 R2 L1 R2 LEFT DOWN RIGHT UP LEFT DOWN RIGHT UP + if ( !_CHEATCMP("URDLURDL4144") ) + WeaponCheat(); + + // "4411LDRULDRU" - R2 R2 L1 L1 LEFT DOWN RIGHT UP LEFT DOWN RIGHT UP + else if ( !_CHEATCMP("URDLURDL1144") ) + MoneyCheat(); + + // "4412LDRULDRU" - R2 R2 L1 L2 LEFT DOWN RIGHT UP LEFT DOWN RIGHT UP + else if ( !_CHEATCMP("URDLURDL2144") ) + ArmourCheat(); + + // "4413LDRULDRU" - R2 R2 L1 R1 LEFT DOWN RIGHT UP LEFT DOWN RIGHT UP + else if ( !_CHEATCMP("URDLURDL3144") ) + HealthCheat(); + + // "4414LRLRLR" - R2 R2 L1 R2 LEFT RIGHT LEFT RIGHT LEFT RIGHT + else if ( !_CHEATCMP("RLRLRL4144") ) + WantedLevelUpCheat(); + + // "4414UDUDUD" - R2 R2 L1 R2 UP DOWN UP DOWN UP DOWN + else if ( !_CHEATCMP("DUDUDU4144") ) + WantedLevelDownCheat(); + + // "1234432T" - L1 L2 R1 R2 R2 R1 L2 TRIANGLE + else if ( !_CHEATCMP("T2344321") ) + SunnyWeatherCheat(); + + // "1234432S" - L1 L2 R1 R2 R2 R1 L2 SQUARE + else if ( !_CHEATCMP("S2344321") ) + CloudyWeatherCheat(); + + // "1234432C" - L1 L2 R1 R2 R2 R1 L2 CIRCLE + else if ( !_CHEATCMP("C2344321") ) + RainyWeatherCheat(); + + // "1234432X" - L1 L2 R1 R2 R2 R1 L2 CROSS + else if ( !_CHEATCMP("X2344321") ) + FoggyWeatherCheat(); + + // "CCCCCC321TCT" - CIRCLE CIRCLE CIRCLE CIRCLE CIRCLE CIRCLE R1 L2 L1 TRIANGLE CIRCLE TRIANGLE + else if ( !_CHEATCMP("TCT123CCCCCC") ) + TankCheat(); + + // "CCCSSSSS1TCT" - CIRCLE CIRCLE CIRCLE SQUARE SQUARE SQUARE SQUARE SQUARE L1 TRIANGLE CIRCLE TRIANGLE + else if ( !_CHEATCMP("TCT1SSSSSCCC") ) + FastWeatherCheat(); + + // "241324TSCT21" - L2 R2 L1 R1 L2 R2 TRIANGLE SQUARE CIRCLE TRIANGLE L2 L1 + else if ( !_CHEATCMP("12TCST423142") ) + BlowUpCarsCheat(); + + // "RDLU12ULDR" - RIGHT DOWN LEFT UP L1 L2 UP LEFT DOWN RIGHT + else if ( !_CHEATCMP("RDLU21ULDR") ) + ChangePlayerCheat(); + + // "DULUX3421" - DOWN UP LEFT UP CROSS R1 R2 L2 L1 + else if ( !_CHEATCMP("1243XULUD") ) + MayhemCheat(); + + // "DULUX3412" - DOWN UP LEFT UP CROSS R1 R2 L1 L2 + else if ( !_CHEATCMP("2143XULUD") ) + EverybodyAttacksPlayerCheat(); + + // "43TX21UD" - R2 R1 TRIANGLE CROSS L2 L1 UP DOWN + else if ( !_CHEATCMP("DU12XT34") ) + WeaponsForAllCheat(); + + // "TURDS12" - TRIANGLE UP RIGHT DOWN SQUARE L1 L2 + else if ( !_CHEATCMP("21SDRUT") ) + FastTimeCheat(); + + // "TURDS34" - TRIANGLE UP RIGHT DOWN SQUARE R1 R2 + else if ( !_CHEATCMP("43SDRUT") ) + SlowTimeCheat(); + + // "11S4T1T" - L1 L1 SQUARE R2 TRIANGLE L1 TRIANGLE + else if ( !_CHEATCMP("T1T4S11") ) + OnlyRenderWheelsCheat(); + + // "R4C32D13" - RIGHT R2 CIRCLE R1 L2 DOWN L1 R1 + else if ( !_CHEATCMP("31D23C4R") ) + ChittyChittyBangBangCheat(); + + // "3141L33T" - R1 L1 R2 L1 LEFT R1 R1 TRIANGLE + else if ( !_CHEATCMP("T33L1413") ) + StrongGripCheat(); + + // "S1CD13TR1X" - SQUARE L1 CIRCLE DOWN L1 R1 TRIANGLE RIGHT L1 CROSS + else if ( !_CHEATCMP("X1RT31DC1S") ) + NastyLimbsCheat(); + +#ifdef KANGAROO_CHEAT + // "X1DUC3RLS3" - R1 SQUARE LEFT RIGHT R1 CIRCLE UP DOWN L1 CROSS + else if (!_CHEATCMP("X1DUC3RLS3")) + KangarooCheat(); +#endif + +#ifndef MASTER + // "31UD13XUD" - DOWN UP CROSS R1 L1 DOWN UP L1 R1 + else if (!_CHEATCMP("31UD13XUD")) + CPed::SwitchDebugDisplay(); +#endif + +#ifdef ALLCARSHELI_CHEAT + // "UCCL3R1TT" - TRIANGLE TRIANGLE L1 RIGHT R1 LEFT CIRCLE CIRCLE UP + else if (!_CHEATCMP("UCCL3R1TT")) + AllCarsHeliCheat(); +#endif + +#ifdef ALT_DODO_CHEAT + // "DUU31XX13" - R1 L1 CROSS CROSS L1 R1 UP UP DOWN + else if (!_CHEATCMP("DUU31XX13")) + AltDodoCheat(); +#endif +#undef _CHEATCMP +} +#endif + +void CPad::AddToPCCheatString(char c) +{ + for ( int32 i = ARRAY_SIZE(KeyBoardCheatString) - 2; i >= 0; i-- ) + KeyBoardCheatString[i + 1] = KeyBoardCheatString[i]; + + KeyBoardCheatString[0] = c; + + #define _CHEATCMP(str) strncmp(str, KeyBoardCheatString, sizeof(str)-1) + + // "GUNSGUNSGUNS" + if ( !_CHEATCMP("SNUGSNUGSNUG") ) + WeaponCheat(); + + // "IFIWEREARICHMAN" + if ( !_CHEATCMP("NAMHCIRAEREWIFI") ) + MoneyCheat(); + + // "GESUNDHEIT" + if ( !_CHEATCMP("TIEHDNUSEG") ) + HealthCheat(); + + // "MOREPOLICEPLEASE" + if ( !_CHEATCMP("ESAELPECILOPEROM") ) + WantedLevelUpCheat(); + + // "NOPOLICEPLEASE" + if ( !_CHEATCMP("ESAELPECILOPON") ) + WantedLevelDownCheat(); + + // "GIVEUSATANK" + if ( !_CHEATCMP("KNATASUEVIG") ) + TankCheat(); + + // "BANGBANGBANG" + if ( !_CHEATCMP("GNABGNABGNAB") ) + BlowUpCarsCheat(); + + // "ILIKEDRESSINGUP" + if ( !_CHEATCMP("PUGNISSERDEKILI") ) + ChangePlayerCheat(); + + // "ITSALLGOINGMAAAD" + if ( !_CHEATCMP("DAAAMGNIOGLLASTI") ) + MayhemCheat(); + + // "NOBODYLIKESME" + if ( !_CHEATCMP("EMSEKILYDOBON") ) + EverybodyAttacksPlayerCheat(); + + // "WEAPONSFORALL" + if ( !_CHEATCMP("LLAROFSNOPAEW") ) + WeaponsForAllCheat(); + + // "TIMEFLIESWHENYOU" + if ( !_CHEATCMP("UOYNEHWSEILFEMIT") ) + FastTimeCheat(); + + // "BOOOOORING" + if ( !_CHEATCMP("GNIROOOOOB") ) + SlowTimeCheat(); + +#if GTA_VERSION < GTA3_PC_11 + // "TURTOISE" + if ( !_CHEATCMP("ESIOTRUT") ) + ArmourCheat(); +#else + // "TORTOISE" + if ( !_CHEATCMP("ESIOTROT") ) + ArmourCheat(); +#endif + + // "SKINCANCERFORME" + if ( !_CHEATCMP("EMROFRECNACNIKS") ) + SunnyWeatherCheat(); + + // "ILIKESCOTLAND" + if ( !_CHEATCMP("DNALTOCSEKILI") ) + CloudyWeatherCheat(); + + // "ILOVESCOTLAND" + if ( !_CHEATCMP("DNALTOCSEVOLI") ) + RainyWeatherCheat(); + + // "PEASOUP" + if ( !_CHEATCMP("PUOSAEP") ) + FoggyWeatherCheat(); + + // "MADWEATHER" + if ( !_CHEATCMP("REHTAEWDAM") ) + FastWeatherCheat(); + + // "ANICESETOFWHEELS" + if ( !_CHEATCMP("SLEEHWFOTESECINA") ) + OnlyRenderWheelsCheat(); + + // "CHITTYCHITTYBB" + if ( !_CHEATCMP("BBYTTIHCYTTIHC") ) + ChittyChittyBangBangCheat(); + + // "CORNERSLIKEMAD" + if ( !_CHEATCMP("DAMEKILSRENROC") ) + StrongGripCheat(); + + // "NASTYLIMBSCHEAT" + if ( !_CHEATCMP("TAEHCSBMILYTSAN") ) + NastyLimbsCheat(); + +#ifdef KANGAROO_CHEAT + // "KANGAROO" + if (!_CHEATCMP("OORAGNAK")) + KangarooCheat(); +#endif + +#ifndef MASTER + // "PEDDEBUG" + if (!_CHEATCMP("GUBEDDEP")) + CPed::SwitchDebugDisplay(); +#endif + +#ifdef ALLCARSHELI_CHEAT + // "CARSAREHELI" + if (!_CHEATCMP("ILEHERASRAC")) + AllCarsHeliCheat(); +#endif + +#ifdef ALT_DODO_CHEAT + // "IWANTTOMASTERDODO" + if (!_CHEATCMP("ODODRETSAMOTTNAWI")) + AltDodoCheat(); +#endif + + #undef _CHEATCMP +} + +#ifdef XINPUT +int CPad::XInputJoy1 = 0; +int CPad::XInputJoy2 = 1; +void CPad::AffectFromXinput(uint32 pad) +{ + pad = pad == 0 ? XInputJoy1 : XInputJoy2; + if (pad == -1) // LoadINIControllerSettings can set it to -1 + return; + + XINPUT_STATE xstate; + memset(&xstate, 0, sizeof(XINPUT_STATE)); + if (XInputGetState(pad, &xstate) == ERROR_SUCCESS) + { + PCTempJoyState.Circle = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? 255 : 0; + PCTempJoyState.Cross = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? 255 : 0; + PCTempJoyState.Square = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? 255 : 0; + PCTempJoyState.Triangle = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? 255 : 0; + PCTempJoyState.DPadDown = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? 255 : 0; + PCTempJoyState.DPadLeft = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? 255 : 0; + PCTempJoyState.DPadRight = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? 255 : 0; + PCTempJoyState.DPadUp = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? 255 : 0; + PCTempJoyState.LeftShock = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? 255 : 0; + PCTempJoyState.LeftShoulder1 = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? 255 : 0; + PCTempJoyState.LeftShoulder2 = xstate.Gamepad.bLeftTrigger; + PCTempJoyState.RightShock = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? 255 : 0; + PCTempJoyState.RightShoulder1 = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? 255 : 0; + PCTempJoyState.RightShoulder2 = xstate.Gamepad.bRightTrigger; + + PCTempJoyState.Select = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? 255 : 0; +#ifdef REGISTER_START_BUTTON + PCTempJoyState.Start = (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? 255 : 0; +#endif + float lx = (float)xstate.Gamepad.sThumbLX / (float)0x7FFF; + float ly = (float)xstate.Gamepad.sThumbLY / (float)0x7FFF; + float rx = (float)xstate.Gamepad.sThumbRX / (float)0x7FFF; + float ry = (float)xstate.Gamepad.sThumbRY / (float)0x7FFF; + + if (Abs(lx) > 0.3f || Abs(ly) > 0.3f) { + PCTempJoyState.LeftStickX = (int32)(lx * 128.0f); + PCTempJoyState.LeftStickY = (int32)(-ly * 128.0f); + } + + if (Abs(rx) > 0.3f || Abs(ry) > 0.3f) { + PCTempJoyState.RightStickX = (int32)(rx * 128.0f); + PCTempJoyState.RightStickY = (int32)(-ry * 128.0f); + } + + XINPUT_VIBRATION VibrationState; + + memset(&VibrationState, 0, sizeof(XINPUT_VIBRATION)); + + uint16 iLeftMotor = (uint16)((float)ShakeFreq / 255.0f * (float)0xffff); + uint16 iRightMotor = (uint16)((float)ShakeFreq / 255.0f * (float)0xffff); + + if (ShakeDur < CTimer::GetTimeStepInMilliseconds()) + ShakeDur = 0; + else + ShakeDur -= CTimer::GetTimeStepInMilliseconds(); + if (ShakeDur == 0) ShakeFreq = 0; + + VibrationState.wLeftMotorSpeed = iLeftMotor; + VibrationState.wRightMotorSpeed = iRightMotor; + + XInputSetState(pad, &VibrationState); + } +} +#endif + +void CPad::UpdatePads(void) +{ + bool bUpdate = true; + + GetPad(0)->UpdateMouse(); +#ifdef XINPUT + GetPad(0)->AffectFromXinput(m_bMapPadOneToPadTwo ? 1 : 0); + GetPad(1)->AffectFromXinput(m_bMapPadOneToPadTwo ? 0 : 1); +#else + CapturePad(0); +#endif + + // Improve keyboard input latency part 1 +#ifdef FIX_BUGS + OldKeyState = NewKeyState; + NewKeyState = TempKeyState; +#endif + +#ifdef DETECT_PAD_INPUT_SWITCH + if (GetPad(0)->PCTempJoyState.CheckForInput()) + IsAffectedByController = true; + else { +#endif + ControlsManager.ClearSimButtonPressCheckers(); + ControlsManager.AffectPadFromKeyBoard(); + ControlsManager.AffectPadFromMouse(); + +#ifdef DETECT_PAD_INPUT_SWITCH + } + if (IsAffectedByController && (GetPad(0)->PCTempKeyState.CheckForInput() || GetPad(0)->PCTempMouseState.CheckForInput())) + IsAffectedByController = false; +#endif + + if ( CReplay::IsPlayingBackFromFile() ) + bUpdate = false; + + if ( bUpdate ) + GetPad(0)->Update(0); + +#ifndef MASTER + GetPad(1)->Update(1); +#else + GetPad(1)->NewState.Clear(); + GetPad(1)->OldState.Clear(); +#endif + + // Improve keyboard input latency part 2 +#ifndef FIX_BUGS + OldKeyState = NewKeyState; + NewKeyState = TempKeyState; +#endif +} + +void CPad::ProcessPCSpecificStuff(void) +{ + ; +} + +void CPad::Update(int16 pad) +{ + OldState = NewState; + +#ifdef GTA_PS2 + bObsoleteControllerMessage = false; + + //int iPressureBtn; + int id; + int ext_id=0; + int state; + int rterm_id = 0; + unsigned short paddata, tpad; + unsigned char rdata[32]; + + state = scePadGetState(pad, 0); + + switch(Phase) + { + case 0: + if (state != scePadStateStable && state != scePadStateFindCTP1) + break; + id = scePadInfoMode(pad, 0, InfoModeCurID, 0); + if (id==0) break; + + ext_id = scePadInfoMode(pad, 0, InfoModeCurExID, 0); + if (ext_id>0) id = ext_id; + + switch(id) + { + case 4: // Digital controller + Phase = 40; // Try for analog(dualshock) + break; + case 7: // Dualshock2 controller + Phase = 50; + break; + default: + Phase = 99; + break; + } + break; + + // Analog Controller (old dualshock) + case 40: // Analog Contoller check valid (otherwise fail phase) + if (scePadInfoMode(pad, 0, InfoModeIdTable, -1)==0) + { + Phase = 99; + break; + } + Phase++; + + case 41: // Analog controller: Request Lock analog mode (asynchronous) + if (scePadSetMainMode(pad, 0, 1, 3)==1) Phase++; + break; + + case 42: // Analog controller: Check state of previous request + if (scePadGetReqState(pad, 0)==scePadReqStateFaild) + { + Phase--; + } + + if (scePadGetReqState(pad, 0)==scePadReqStateComplete) + { + // Lock mode complete + Phase=0; // Accept normal dualshock + } + break; + + // DualShock 2 Controller + case 50: // Analog Contoller check valid (otherwise fail phase) + if (scePadInfoMode(pad, 0, InfoModeIdTable, -1)==0) + { + Phase = 99; + break; + } + Phase++; + + case 51: // Analog controller: Request Lock analog mode (asynchronous) + if (scePadSetMainMode(pad, 0, 1, 3)==1) Phase++; + break; + + case 52: // Analog controller: Check state of previous request + if (scePadGetReqState(pad, 0)==scePadReqStateFaild) + { + Phase--; + } + + if (scePadGetReqState(pad, 0)==scePadReqStateComplete) + { + // Lock mode complete + Phase=0; // Accept normal dualshock + } + break; + + case 70: // DualShock 2 check pressure sensitive possible + if (scePadInfoPressMode(pad, 0)==1) + { + Phase = 76; + break; + } + Phase = 99; + break; + + case 76: // DualShock2 enable pressure sensitive mode (asynchronous function) + if (scePadEnterPressMode(pad, 0)==1) Phase++; + break; + + case 77: // Dualshock2 check status of request pressure sensitive mode + if (scePadGetReqState(pad, 0)==scePadReqStateFaild) Phase--; + if (scePadGetReqState(pad, 0)==scePadReqStateComplete) + { + Phase=80; + } + break; + + // DualShock 2 Controller + case 80: // Set motors + if (scePadInfoAct(pad, 0, -1, 0)==0) + { + Phase = 99; + } + + act_align[0] = 0; // Offset 0 for motor0 + act_align[1] = 1; // Offset 1 for motor1 + + act_align[2] = 0xff; + act_align[3] = 0xff; + act_align[4] = 0xff; + act_align[5] = 0xff; + + // Asynchronous function + if (scePadSetActAlign(pad, 0, act_align)==0) break; + Phase++; + break; + + + case 81: + if ( scePadGetState(pad, 0) != scePadStateExecCmd ) + { + Phase = 99; + } + + break; + + default: + if ( state == scePadStateError ) break; + + if ( state == scePadStateStable || state == scePadStateFindCTP1 ) + { + if ( ShakeDur ) + { + ShakeDur = Max(ShakeDur - (int32)CTimer::GetTimeStepInMilliseconds(), 0); + + if ( ShakeDur == 0 ) + { + act_direct[0] = 0; + act_direct[1] = 0; + scePadSetActDirect(pad, 0, act_direct); + } + else + { + act_direct[0] = 0; + act_direct[1] = (unsigned char) ShakeFreq; + scePadSetActDirect(pad, 0, act_direct); + } + } + + if (scePadRead( pad, 0, rdata )==0) + { + NewState.Clear(); + break; + } + + if ((rdata[0] == 0)) + { + paddata = (unsigned short) ( 0xffff ^ ((rdata[2]<<8)|rdata[3]) ); + rterm_id = (rdata[1]); + + if ( (rterm_id>>4) == 7 ) // DUALSHOCK + { + if (!CRecordDataForGame::IsPlayingBack() && !CRecordDataForChase::ShouldThisPadBeLeftAlone(pad)) + { + tpad = paddata; + + NewState.DPadUp = ( tpad & SCE_PADLup ) ? 255 : 0; + NewState.DPadDown = ( tpad & SCE_PADLdown ) ? 255 : 0; + NewState.DPadLeft = ( tpad & SCE_PADLleft ) ? 255 : 0; + NewState.DPadRight = ( tpad & SCE_PADLright ) ? 255 : 0; + NewState.Triangle = ( tpad & SCE_PADRup ) ? 255 : 0; + NewState.Cross = ( tpad & SCE_PADRdown ) ? 255 : 0; + NewState.Square = ( tpad & SCE_PADRleft ) ? 255 : 0; + NewState.Circle = ( tpad & SCE_PADRright ) ? 255 : 0; + NewState.Start = ( tpad & SCE_PADstart ) ? 255 : 0; + NewState.Select = ( tpad & SCE_PADselect ) ? 255 : 0; + NewState.LeftShoulder1 = ( tpad & SCE_PADL1 ) ? 255 : 0; + NewState.LeftShoulder2 = ( tpad & SCE_PADL2 ) ? 255 : 0; + NewState.RightShoulder1 = ( tpad & SCE_PADR1 ) ? 255 : 0; + NewState.RightShoulder2 = ( tpad & SCE_PADR2 ) ? 255 : 0; + NewState.LeftShock = ( tpad & SCE_PADi ) ? 255 : 0; + NewState.RightShock = ( tpad & SCE_PADj ) ? 255 : 0; + NewState.RightStickX = (short)rdata[4]; + NewState.RightStickY = (short)rdata[5]; + NewState.LeftStickX = (short)rdata[6]; + NewState.LeftStickY = (short)rdata[7]; + + #define CLAMP_AXIS(x) (((x) < 43 && (x) >= -42) ? 0 : (((x) > 0) ? (Max((x)-42, 0)*127/85) : Min((x)+42, 0)*127/85)) + #define FIX_AXIS(x) CLAMP_AXIS((x)-128) + + NewState.RightStickX = FIX_AXIS(NewState.RightStickX); + NewState.RightStickY = FIX_AXIS(NewState.RightStickY); + NewState.LeftStickX = FIX_AXIS(NewState.LeftStickX); + NewState.LeftStickY = FIX_AXIS(NewState.LeftStickY); + + #undef FIX_AXIS + #undef CLAMP_AXIS + } + } + else if ( (rterm_id>>4) == 4 ) // Controller (digital) + { + if ( pad == 0 ) + bObsoleteControllerMessage = true; + NewState.Clear(); + } + + if ( NewState.IsAnyButtonPressed() ) + LastTimeTouched = CTimer::GetTimeInMilliseconds(); + + break; + } + + if ( ++iCurrHornHistory >= HORNHISTORY_SIZE ) + iCurrHornHistory = 0; + + bHornHistory[iCurrHornHistory] = GetHorn(); + NewState.Clear(); + return; + } + break; + } + + if ( pad == 0 ) + { + bOldDisplayNoControllerMessage = bDisplayNoControllerMessage; + if ( state == scePadStateDiscon ) + { + bDisplayNoControllerMessage = true; + Phase = 0; + } + else + bDisplayNoControllerMessage = false; + } + + if ( ++iCurrHornHistory >= HORNHISTORY_SIZE ) + iCurrHornHistory = 0; + + bHornHistory[iCurrHornHistory] = GetHorn(); + + if ( !bDisplayNoControllerMessage ) + CGame::bDemoMode = false; +#endif + +#if (defined GTA_PS2 || defined FIX_BUGS) + if (!CRecordDataForGame::IsPlayingBack() && !CRecordDataForChase::ShouldThisPadBeLeftAlone(pad)) +#endif + { + NewState = ReconcileTwoControllersInput(PCTempKeyState, PCTempJoyState); + NewState = ReconcileTwoControllersInput(PCTempMouseState, NewState); + } + + PCTempJoyState.Clear(); + PCTempKeyState.Clear(); + PCTempMouseState.Clear(); + + ProcessPCSpecificStuff(); + + if ( ++iCurrHornHistory >= HORNHISTORY_SIZE ) + iCurrHornHistory = 0; + + bHornHistory[iCurrHornHistory] = GetHorn(); + + + if ( !bDisplayNoControllerMessage ) + CGame::bDemoMode = false; +} + +void CPad::DoCheats(void) +{ +#ifdef DETECT_PAD_INPUT_SWITCH + if (IsAffectedByController) +#endif + GetPad(0)->DoCheats(0); +} + +void CPad::DoCheats(int16 unk) +{ +#ifdef GTA_PS2_STUFF + if ( GetTriangleJustDown() ) + AddToCheatString('T'); + + if ( GetCircleJustDown() ) + AddToCheatString('C'); + + if ( GetCrossJustDown() ) + AddToCheatString('X'); + + if ( GetSquareJustDown() ) + AddToCheatString('S'); + + if ( GetDPadUpJustDown() ) + AddToCheatString('U'); + + if ( GetDPadDownJustDown() ) + AddToCheatString('D'); + + if ( GetDPadLeftJustDown() ) + AddToCheatString('L'); + + if ( GetDPadRightJustDown() ) + AddToCheatString('R'); + + if ( GetLeftShoulder1JustDown() ) + AddToCheatString('1'); + + if ( GetLeftShoulder2JustDown() ) + AddToCheatString('2'); + + if ( GetRightShoulder1JustDown() ) + AddToCheatString('3'); + + if ( GetRightShoulder2JustDown() ) + AddToCheatString('4'); +#endif +} + +void CPad::StopPadsShaking(void) +{ + GetPad(0)->StopShaking(0); +} + +void CPad::StopShaking(int16 pad) +{ +#ifdef GTA_PS2_STUFF + ShakeFreq = 0; + ShakeDur = 0; + +#ifdef GTA_PS2 + if ( Phase == 99 ) + { + act_direct[0] = 0; + act_direct[1] = 0; + scePadSetActDirect(pad, 0, act_direct); + } +#endif + +#endif +} + +CPad *CPad::GetPad(int32 pad) +{ + return &Pads[pad]; +} +#ifdef DETECT_PAD_INPUT_SWITCH +#define CURMODE (IsAffectedByController ? Mode : 0) +#else +#define CURMODE (Mode) +#endif + + +int16 CPad::GetSteeringLeftRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickX; + int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2; + + if ( Abs(axis) > Abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickX; + + break; + } + } + + return 0; +} + +int16 CPad::GetSteeringUpDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickY; + int16 dpad = (NewState.DPadUp - NewState.DPadDown) / 2; + + if ( Abs(axis) > Abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickY; + + break; + } + } + + return 0; +} + +int16 CPad::GetCarGunUpDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return NewState.RightStickY; + + break; + } + + case 3: + { + return (NewState.DPadUp - NewState.DPadDown) / 2; + + break; + } + } + + return 0; +} + +int16 CPad::GetCarGunLeftRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return NewState.RightStickX; + + break; + } + + case 3: + { + return (NewState.DPadRight - NewState.DPadLeft) / 2; + + break; + } + } + + return 0; +} + +int16 CPad::GetPedWalkLeftRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickX; + int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2; + + if ( Abs(axis) > Abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickX; + + break; + } + } + + return 0; +} + + +int16 CPad::GetPedWalkUpDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickY; + int16 dpad = (NewState.DPadDown - NewState.DPadUp) / 2; + + if ( Abs(axis) > Abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickY; + + break; + } + } + + return 0; +} + +int16 CPad::GetAnalogueUpDown(void) +{ + switch (CURMODE) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickY; + int16 dpad = (NewState.DPadDown - NewState.DPadUp) / 2; + + if ( Abs(axis) > Abs(dpad) ) + return axis; + else + return dpad; + + break; + } + + case 1: + case 3: + { + return NewState.LeftStickY; + + break; + } + } + + return 0; +} + +bool CPad::GetLookLeft(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.LeftShoulder2 && !NewState.RightShoulder2); +} + +bool CPad::GetLookRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.RightShoulder2 && !NewState.LeftShoulder2); +} + + +bool CPad::GetLookBehindForCar(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.RightShoulder2 && NewState.LeftShoulder2); +} + +bool CPad::GetLookBehindForPed(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!NewState.RightShock; +} + +bool CPad::GetHorn(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + { + return !!NewState.LeftShock; + + break; + } + + case 1: + { + return !!NewState.LeftShoulder1; + + break; + } + + case 2: + { + return !!NewState.RightShoulder1; + + break; + } + + case 3: + { + return !!NewState.LeftShock; + + break; + } + } + + return false; +} + +bool CPad::HornJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + { + return !!(NewState.LeftShock && !OldState.LeftShock); + + break; + } + + case 1: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } + + case 2: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + + case 3: + { + return !!(NewState.LeftShock && !OldState.LeftShock); + + break; + } + } + + return false; +} + + +bool CPad::GetCarGunFired(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!NewState.Circle; + + break; + } + + case 3: + { + return !!NewState.RightShoulder1; + + break; + } + } + + return false; +} + +bool CPad::CarGunJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!(NewState.Circle && !OldState.Circle); + + break; + } + + case 3: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + } + + return false; +} + +int16 CPad::GetHandBrake(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + case 0: + case 1: + { + return NewState.RightShoulder1; + + break; + } + + case 2: + { + return NewState.Triangle; + + break; + } + + case 3: + { + return NewState.LeftShoulder1; + + break; + } + } + + return 0; +} + +int16 CPad::GetBrake(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + case 0: + case 2: + { + return NewState.Square; + + break; + } + + case 1: + { + return NewState.Square; + + break; + } + + case 3: + { + int16 axis = 2 * NewState.RightStickY; + + if ( axis < 0 ) + return 0; + else + return axis; + + break; + } + } + + return 0; +} + +bool CPad::GetExitVehicle(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!NewState.Triangle; + + break; + } + + case 2: + { + return !!NewState.LeftShoulder1; + + break; + } + } + + return false; +} + +bool CPad::ExitVehicleJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!(NewState.Triangle && !OldState.Triangle); + + break; + } + + case 2: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } + } + + return false; +} + +int32 CPad::GetWeapon(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + { + return NewState.Circle; + + break; + } + + case 2: + { + return NewState.Cross; + + break; + } + + case 3: + { + return NewState.RightShoulder1; + + break; + } + } + + return false; +} + +bool CPad::WeaponJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + { + return !!(NewState.Circle && !OldState.Circle); + + break; + } + + case 2: + { + return !!(NewState.Cross && !OldState.Cross); + + break; + } + + case 3: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + } + + return false; +} + +int16 CPad::GetAccelerate(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + + switch (CURMODE) + { + case 0: + case 2: + { + return NewState.Cross; + + break; + } + + case 1: + { + return NewState.Cross; + + break; + } + + case 3: + { + int16 axis = -2 * NewState.RightStickY; + + if ( axis < 0 ) + return 0; + else + return axis; + + break; + } + } + + return 0; +} + +bool CPad::CycleCameraModeUpJustDown(void) +{ + switch (CURMODE) + { + case 0: + case 2: + case 3: + { + return !!(NewState.Select && !OldState.Select); + + break; + } + + case 1: + { + return !!(NewState.DPadUp && !OldState.DPadUp); + + break; + } + } + + return false; +} + +bool CPad::CycleCameraModeDownJustDown(void) +{ + switch (CURMODE) + { + case 0: + case 2: + case 3: + { + return false; + + break; + } + + case 1: + { + return !!(NewState.DPadDown && !OldState.DPadDown); + + break; + } + } + + return false; +} + +bool CPad::ChangeStationJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } + + case 1: + { + return !!(NewState.Select && !OldState.Select); + + break; + } + + case 2: + { + return !!(NewState.LeftShock && !OldState.LeftShock); + + break; + } + + case 3: + { + return !!(NewState.Circle && !OldState.Circle); + + break; + } + } + + return false; +} + + +bool CPad::CycleWeaponLeftJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +} + +bool CPad::CycleWeaponRightJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +} + +bool CPad::GetTarget(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!NewState.RightShoulder1; + + break; + } + + case 3: + { + return !!NewState.LeftShoulder1; + + break; + } + } + + return false; +} + +bool CPad::TargetJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + + case 3: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } + } + + return false; +} + +bool CPad::JumpJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.Square && !OldState.Square); +} + +bool CPad::GetSprint(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!NewState.Cross; + + break; + } + + case 2: + { + return !!NewState.Circle; + + break; + } + } + + return false; +} + +bool CPad::ShiftTargetLeftJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +} + +bool CPad::ShiftTargetRightJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +} + +#ifdef FIX_BUGS +// FIX: fixes from VC for the bug of double switching the controller setup +bool CPad::GetAnaloguePadUp(void) +{ + static int16 oldfStickY = 0; + + int16 leftStickY = CPad::GetPad(0)->GetLeftStickY(); + + if ( leftStickY < -15 && oldfStickY >= -5 ) + { + oldfStickY = leftStickY; + return true; + } + else + { + oldfStickY = leftStickY; + return false; + } +} + +bool CPad::GetAnaloguePadDown(void) +{ + static int16 oldfStickY = 0; + + int16 leftStickY = CPad::GetPad(0)->GetLeftStickY(); + + if ( leftStickY > 15 && oldfStickY <= 5 ) + { + oldfStickY = leftStickY; + return true; + } + else + { + oldfStickY = leftStickY; + return false; + } +} + +bool CPad::GetAnaloguePadLeft(void) +{ + static int16 oldfStickX = 0; + + int16 leftStickX = CPad::GetPad(0)->GetLeftStickX(); + + if ( leftStickX < -15 && oldfStickX >= -5 ) + { + oldfStickX = leftStickX; + return true; + } + else + { + oldfStickX = leftStickX; + return false; + } +} + +bool CPad::GetAnaloguePadRight(void) +{ + static int16 oldfStickX = 0; + + int16 leftStickX = CPad::GetPad(0)->GetLeftStickX(); + + if ( leftStickX > 15 && oldfStickX <= 5 ) + { + oldfStickX = leftStickX; + return true; + } + else + { + oldfStickX = leftStickX; + return false; + } +} + +bool CPad::GetAnaloguePadLeftJustUp(void) +{ + static int16 oldfStickX = 0; + + int16 X = GetPad(0)->GetPedWalkLeftRight(); + + if ( X == 0 && oldfStickX < 0 ) + { + oldfStickX = 0; + + return true; + } + else + { + oldfStickX = X; + + return false; + } +} + +bool CPad::GetAnaloguePadRightJustUp(void) +{ + static int16 oldfStickX = 0; + + int16 X = GetPad(0)->GetPedWalkLeftRight(); + + if ( X == 0 && oldfStickX > 0 ) + { + oldfStickX = 0; + + return true; + } + else + { + oldfStickX = X; + + return false; + } +} + +#else +bool CPad::GetAnaloguePadUp(void) +{ + static int16 oldfStickY = 0; + + int16 Y = CPad::GetPad(0)->GetAnalogueUpDown(); + + if ( Y < 0 && oldfStickY >= 0 ) + { + oldfStickY = Y; + return true; + } + else + { + oldfStickY = Y; + return false; + } +} + +bool CPad::GetAnaloguePadDown(void) +{ + static int16 oldfStickY = 0; + + int16 Y = CPad::GetPad(0)->GetAnalogueUpDown(); + + if ( Y > 0 && oldfStickY <= 0 ) + { + oldfStickY = Y; + return true; + } + else + { + oldfStickY = Y; + return false; + } +} + +bool CPad::GetAnaloguePadLeft(void) +{ + static int16 oldfStickX = 0; + + int16 X = CPad::GetPad(0)->GetPedWalkLeftRight(); + + if ( X < 0 && oldfStickX >= 0 ) + { + oldfStickX = X; + return true; + } + else + { + oldfStickX = X; + return false; + } +} + +bool CPad::GetAnaloguePadRight(void) +{ + static int16 oldfStickX = 0; + + int16 X = CPad::GetPad(0)->GetPedWalkLeftRight(); + + if ( X > 0 && oldfStickX <= 0 ) + { + oldfStickX = X; + return true; + } + else + { + oldfStickX = X; + return false; + } +} + +bool CPad::GetAnaloguePadLeftJustUp(void) +{ + static int16 oldfStickX = 0; + + int16 X = GetPad(0)->GetPedWalkLeftRight(); + + if ( X == 0 && oldfStickX < 0 ) + { + oldfStickX = 0; + + return true; + } + else + { + oldfStickX = X; + + return false; + } +} + +bool CPad::GetAnaloguePadRightJustUp(void) +{ + static int16 oldfStickX = 0; + + int16 X = GetPad(0)->GetPedWalkLeftRight(); + + if ( X == 0 && oldfStickX > 0 ) + { + oldfStickX = 0; + + return true; + } + else + { + oldfStickX = X; + + return false; + } +} +#endif + +bool CPad::ForceCameraBehindPlayer(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + { + return !!NewState.LeftShoulder1; + + break; + } + + case 2: + { + return !!NewState.Triangle; + + break; + } + + case 3: + { + return !!NewState.Circle; + + break; + } + } + + return false; +} + +bool CPad::SniperZoomIn(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!NewState.Square; + + break; + } + + case 2: + { + return !!NewState.Triangle; + + break; + } + } + + return false; +} + +bool CPad::SniperZoomOut(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!NewState.Cross; + + break; + } + + case 2: + { + return !!NewState.Square; + + break; + } + } + + return false; +} + +#undef CURMODE + +int16 CPad::SniperModeLookLeftRight(void) +{ + int16 axis = NewState.LeftStickX; + int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2; + + if ( Abs(axis) > Abs(dpad) ) + return axis; + else + return dpad; +} + +int16 CPad::SniperModeLookUpDown(void) +{ + int16 axis = NewState.LeftStickY; + int16 dpad; +#ifdef FIX_BUGS + axis = -axis; +#endif +#ifndef INVERT_LOOK_FOR_PAD + dpad = (NewState.DPadUp - NewState.DPadDown) / 2; +#else + if (CPad::bInvertLook4Pad) { + axis = -axis; + dpad = (NewState.DPadDown - NewState.DPadUp) / 2; + } else { + dpad = (NewState.DPadUp - NewState.DPadDown) / 2; + } +#endif + + if ( Abs(axis) > Abs(dpad) ) + return axis; + else + return dpad; +} + +int16 CPad::LookAroundLeftRight(void) +{ + float axis = GetPad(0)->NewState.RightStickX; + + if ( Abs(axis) > 85 && !GetLookBehindForPed() ) + return (int16) ( (axis + ( ( axis > 0 ) ? -85 : 85) ) + * (127.0f / 32.0f) ); // 3.96875f + + else if ( TheCamera.Cams[0].Using3rdPersonMouseCam() && Abs(axis) > 10 ) + return (int16) ( (axis + ( ( axis > 0 ) ? -10 : 10) ) + * (127.0f / 64.0f) ); // 1.984375f + + return 0; +} + +int16 CPad::LookAroundUpDown(void) +{ + int16 axis = GetPad(0)->NewState.RightStickY; + +#ifdef FIX_BUGS + axis = -axis; +#endif +#ifdef INVERT_LOOK_FOR_PAD + if (CPad::bInvertLook4Pad) + axis = -axis; +#endif + + if ( Abs(axis) > 85 && !GetLookBehindForPed() ) + return (int16) ( (axis + ( ( axis > 0 ) ? -85 : 85) ) + * (127.0f / 32.0f) ); // 3.96875f + + else if ( TheCamera.Cams[0].Using3rdPersonMouseCam() && Abs(axis) > 40 ) + return (int16) ( (axis + ( ( axis > 0 ) ? -40 : 40) ) + * (127.0f / 64.0f) ); // 1.984375f + + return 0; +} + + +void CPad::ResetAverageWeapon(void) +{ + AverageWeapon = GetWeapon(); + AverageEntries = 1; +} + +void CPad::PrintErrorMessage(void) +{ + if ( bDisplayNoControllerMessage && !CGame::playingIntro && !FrontEndMenuManager.m_bMenuActive ) + { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.85f), SCREEN_SCALE_Y(1.0f)); +#else + CFont::SetScale(0.85f, 1.0f); +#endif + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 20); +#endif + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(255, 255, 200, 200)); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString + ( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2, + TheText.Get("NOCONT") // Please reconnect an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). to controller port 1 to continue + ); + } + else if ( bObsoleteControllerMessage ) + { +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.85f), SCREEN_SCALE_Y(1.0f)); +#else + CFont::SetScale(0.85f, 1.0f); +#endif + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 20); +#endif + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(255, 255, 200, 200)); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString + ( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2, + TheText.Get("WRCONT") // The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto III requires an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). + ); + } + +} + +void LittleTest(void) +{ + static int32 Cunt = 0; + + Cunt++; // ??? +} + +void CPad::ResetCheats(void) +{ + CWeather::ReleaseWeather(); + + CPopulation::ms_bGivePedsWeapons = false; + + CPed::bNastyLimbsCheat = false; + CPed::bPedCheat2 = false; + CPed::bPedCheat3 = false; + + CVehicle::bWheelsOnlyCheat = false; + CVehicle::bAllDodosCheat = false; + CVehicle::bCheat3 = false; + CVehicle::bCheat4 = false; + CVehicle::bCheat5 = false; + + gbFastTime = false; + CTimer::SetTimeScale(1.0f); +} + +char *CPad::EditString(char *pStr, int32 nSize) +{ + int32 pos = (int32)strlen(pStr); + + // letters + for ( int32 i = 0; i < ('Z' - 'A' + 1); i++ ) + { + if ( GetPad(0)->GetCharJustDown(i + 'A') && pos < nSize - 1 ) + { + pStr[pos++] = i + 'A'; + pStr[pos] = '\0'; + } + + if ( GetPad(0)->GetCharJustDown(i + 'a') && pos < nSize - 1 ) + { + pStr[pos++] = i + 'a'; + pStr[pos] = '\0'; + } + } + + // numbers + for ( int32 i = 0; i < ('9' - '0' + 1); i++ ) + { + if ( GetPad(0)->GetCharJustDown(i + '0') && pos < nSize - 1 ) + { + pStr[pos++] = i + '0'; + pStr[pos] = '\0'; + } + } + + // space + if ( GetPad(0)->GetCharJustDown(' ') && pos < nSize - 1 ) + { + pStr[pos++] = ' '; + pStr[pos] = '\0'; + } + + + // del + if ( GetPad(0)->GetDeleteJustDown() || GetPad(0)->GetBackspaceJustDown() ) + { + if ( pos > 0 ) + pStr[pos - 1] = '\0'; + } + + // extenter/up/down + if ( GetPad(0)->GetReturnJustDown() || GetPad(0)->GetUpJustDown() || GetPad(0)->GetDownJustDown() ) + return nil; + + return pStr; +} + +int32 *CPad::EditCodesForControls(int32 *pRsKeys, int32 nSize) +{ + *pRsKeys = rsNULL; + + for ( int32 i = 0; i < 255; i++ ) + { + if ( GetPad(0)->GetCharJustDown(i) ) + *pRsKeys = i; + } + + for ( int32 i = 0; i < 12; i++ ) + { + if ( GetPad(0)->GetFJustDown(i) ) + *pRsKeys = i + rsF1; + } + + if ( GetPad(0)->GetEscapeJustDown() ) + *pRsKeys = rsESC; + + if ( GetPad(0)->GetInsertJustDown() ) + *pRsKeys = rsINS; + + if ( GetPad(0)->GetDeleteJustDown() ) + *pRsKeys = rsDEL; + + if ( GetPad(0)->GetHomeJustDown() ) + *pRsKeys = rsHOME; + + if ( GetPad(0)->GetEndJustDown() ) + *pRsKeys = rsEND; + + if ( GetPad(0)->GetPageUpJustDown() ) + *pRsKeys = rsPGUP; + + if ( GetPad(0)->GetPageDownJustDown() ) + *pRsKeys = rsPGDN; + + if ( GetPad(0)->GetUpJustDown() ) + *pRsKeys = rsUP; + + if ( GetPad(0)->GetDownJustDown() ) + *pRsKeys = rsDOWN; + + if ( GetPad(0)->GetLeftJustDown() ) + *pRsKeys = rsLEFT; + + if ( GetPad(0)->GetRightJustDown() ) + *pRsKeys = rsRIGHT; + + if ( GetPad(0)->GetScrollLockJustDown() ) + *pRsKeys = rsSCROLL; + + if ( GetPad(0)->GetPauseJustDown() ) + *pRsKeys = rsPAUSE; + + if ( GetPad(0)->GetNumLockJustDown() ) + *pRsKeys = rsNUMLOCK; + + if ( GetPad(0)->GetDivideJustDown() ) + *pRsKeys = rsDIVIDE; + + if ( GetPad(0)->GetTimesJustDown() ) + *pRsKeys = rsTIMES; + + if ( GetPad(0)->GetMinusJustDown() ) + *pRsKeys = rsMINUS; + + if ( GetPad(0)->GetPlusJustDown() ) + *pRsKeys = rsPLUS; + + if ( GetPad(0)->GetPadEnterJustDown() ) + *pRsKeys = rsPADENTER; + + if ( GetPad(0)->GetPadDelJustDown() ) + *pRsKeys = rsPADDEL; + + if ( GetPad(0)->GetPad1JustDown() ) + *pRsKeys = rsPADEND; + + if ( GetPad(0)->GetPad2JustDown() ) + *pRsKeys = rsPADDOWN; + + if ( GetPad(0)->GetPad3JustDown() ) + *pRsKeys = rsPADPGDN; + + if ( GetPad(0)->GetPad4JustDown() ) + *pRsKeys = rsPADLEFT; + + if ( GetPad(0)->GetPad5JustDown() ) + *pRsKeys = rsPAD5; + + if ( GetPad(0)->GetPad6JustDown() ) + *pRsKeys = rsPADRIGHT; + + if ( GetPad(0)->GetPad7JustDown() ) + *pRsKeys = rsPADHOME; + + if ( GetPad(0)->GetPad8JustDown() ) + *pRsKeys = rsPADUP; + + if ( GetPad(0)->GetPad9JustDown() ) + *pRsKeys = rsPADPGUP; + + if ( GetPad(0)->GetPad0JustDown() ) + *pRsKeys = rsPADINS; + + if ( GetPad(0)->GetBackspaceJustDown() ) + *pRsKeys = rsBACKSP; + + if ( GetPad(0)->GetTabJustDown() ) + *pRsKeys = rsTAB; + + if ( GetPad(0)->GetCapsLockJustDown() ) + *pRsKeys = rsCAPSLK; + + if ( GetPad(0)->GetReturnJustDown() ) + *pRsKeys = rsENTER; + + if ( GetPad(0)->GetLeftShiftJustDown() ) + *pRsKeys = rsLSHIFT; + + if ( GetPad(0)->GetShiftJustDown() ) + *pRsKeys = rsSHIFT; + + if ( GetPad(0)->GetRightShiftJustDown() ) + *pRsKeys = rsRSHIFT; + + if ( GetPad(0)->GetLeftCtrlJustDown() ) + *pRsKeys = rsLCTRL; + + if ( GetPad(0)->GetRightCtrlJustDown() ) + *pRsKeys = rsRCTRL; + + if ( GetPad(0)->GetLeftAltJustDown() ) + *pRsKeys = rsLALT; + + if ( GetPad(0)->GetRightAltJustDown() ) + *pRsKeys = rsRALT; + + if ( GetPad(0)->GetLeftWinJustDown() ) + *pRsKeys = rsLWIN; + + if ( GetPad(0)->GetRightWinJustDown() ) + *pRsKeys = rsRWIN; + + if ( GetPad(0)->GetAppsJustDown() ) + *pRsKeys = rsAPPS; + + return pRsKeys; +} diff --git a/src/core/Pad.h b/src/core/Pad.h new file mode 100644 index 0000000..b37659c --- /dev/null +++ b/src/core/Pad.h @@ -0,0 +1,474 @@ +#pragma once + +enum { + PLAYERCONTROL_ENABLED = 0, + PLAYERCONTROL_CAMERA = 1, + PLAYERCONTROL_UNK2 = 2, + PLAYERCONTROL_GARAGE = 4, + PLAYERCONTROL_UNK8 = 8, + PLAYERCONTROL_UNK10 = 16, + PLAYERCONTROL_PLAYERINFO = 32, + PLAYERCONTROL_PHONE = 64, + PLAYERCONTROL_CUTSCENE = 128, +}; + +class CControllerState +{ +public: + int16 LeftStickX, LeftStickY; + int16 RightStickX, RightStickY; + int16 LeftShoulder1, LeftShoulder2; + int16 RightShoulder1, RightShoulder2; + int16 DPadUp, DPadDown, DPadLeft, DPadRight; + int16 Start, Select; + int16 Square, Triangle, Cross, Circle; + int16 LeftShock, RightShock; + int16 NetworkTalk; + float GetLeftStickX(void) { return LeftStickX/32767.0f; }; + float GetLeftStickY(void) { return LeftStickY/32767.0f; }; + float GetRightStickX(void) { return RightStickX/32767.0f; }; + float GetRightStickY(void) { return RightStickY/32767.0f; }; + + bool CheckForInput(); + void Clear(void); +}; +VALIDATE_SIZE(CControllerState, 0x2A); + +class CMouseControllerState +{ +public: + //uint32 btns; // bit 0-2 button 1-3 + + bool LMB; + bool RMB; + bool MMB; + bool WHEELUP; + bool WHEELDN; + bool MXB1; + bool MXB2; + char _pad0; + + float x, y; + + CMouseControllerState(); + void Clear(); +}; + +VALIDATE_SIZE(CMouseControllerState, 0x10); + +class CMousePointerStateHelper +{ +public: + bool bInvertHorizontally; + bool bInvertVertically; + + CMouseControllerState GetMouseSetUp(); +}; + +VALIDATE_SIZE(CMousePointerStateHelper, 0x2); + +extern CMousePointerStateHelper MousePointerStateHelper; + + +class CKeyboardState +{ +public: + int16 F[12]; + int16 VK_KEYS[256]; + int16 ESC; + int16 INS; + int16 DEL; + int16 HOME; + int16 END; + int16 PGUP; + int16 PGDN; + int16 UP; + int16 DOWN; + int16 LEFT; + int16 RIGHT; + int16 SCROLLLOCK; + int16 PAUSE; + int16 NUMLOCK; + int16 DIV; + int16 MUL; + int16 SUB; + int16 ADD; + int16 ENTER; + int16 DECIMAL; + int16 NUM1; + int16 NUM2; + int16 NUM3; + int16 NUM4; + int16 NUM5; + int16 NUM6; + int16 NUM7; + int16 NUM8; + int16 NUM9; + int16 NUM0; + int16 BACKSP; + int16 TAB; + int16 CAPSLOCK; + int16 EXTENTER; + int16 LSHIFT; + int16 RSHIFT; + int16 SHIFT; + int16 LCTRL; + int16 RCTRL; + int16 LALT; + int16 RALT; + int16 LWIN; + int16 RWIN; + int16 APPS; + + void Clear(); +}; + +VALIDATE_SIZE(CKeyboardState, 0x270); + +enum +{ + // taken from miss2 + PAD1 = 0, + PAD2, + + MAX_PADS +}; + +class CPad +{ +public: + enum + { + HORNHISTORY_SIZE = 5, + }; + CControllerState NewState; + CControllerState OldState; + CControllerState PCTempKeyState; + CControllerState PCTempJoyState; + CControllerState PCTempMouseState; + // straight out of my IDB + int16 Phase; + int16 Mode; + int16 ShakeDur; + uint8 ShakeFreq; + bool bHornHistory[HORNHISTORY_SIZE]; + uint8 iCurrHornHistory; + uint8 DisablePlayerControls; + int8 bApplyBrakes; + char CheatString[12]; + int32 LastTimeTouched; + int32 AverageWeapon; + int32 AverageEntries; + +#ifdef DETECT_PAD_INPUT_SWITCH + static bool IsAffectedByController; +#endif + CPad() { } + ~CPad() { } + + static bool bDisplayNoControllerMessage; + static bool bObsoleteControllerMessage; + static bool bOldDisplayNoControllerMessage; + static bool m_bMapPadOneToPadTwo; +#ifdef INVERT_LOOK_FOR_PAD + static bool bInvertLook4Pad; +#endif + + static CKeyboardState OldKeyState; + static CKeyboardState NewKeyState; + static CKeyboardState TempKeyState; + static char KeyBoardCheatString[20]; + static CMouseControllerState OldMouseControllerState; + static CMouseControllerState NewMouseControllerState; + static CMouseControllerState PCTempMouseControllerState; + + +#ifdef GTA_PS2_STUFF + static void Initialise(void); +#endif + void Clear(bool bResetPlayerControls); + void ClearMouseHistory(); + void UpdateMouse(); + CControllerState ReconcileTwoControllersInput(CControllerState const &State1, CControllerState const &State2); + void StartShake(int16 nDur, uint8 nFreq); + void StartShake_Distance(int16 nDur, uint8 nFreq, float fX, float fY, float fz); + void StartShake_Train(float fX, float fY); +#ifdef GTA_PS2_STUFF + void AddToCheatString(char c); +#endif + void AddToPCCheatString(char c); + + static void UpdatePads(void); + void ProcessPCSpecificStuff(void); + void Update(int16 pad); + + static void DoCheats(void); + void DoCheats(int16 unk); + + static void StopPadsShaking(void); + void StopShaking(int16 pad); + + static CPad *GetPad(int32 pad); + + int16 GetSteeringLeftRight(void); + int16 GetSteeringUpDown(void); + int16 GetCarGunUpDown(void); + int16 GetCarGunLeftRight(void); + int16 GetPedWalkLeftRight(void); + int16 GetPedWalkUpDown(void); + int16 GetAnalogueUpDown(void); + bool GetLookLeft(void); + bool GetLookRight(void); + bool GetLookBehindForCar(void); + bool GetLookBehindForPed(void); + bool GetHorn(void); + bool HornJustDown(void); + bool GetCarGunFired(void); + bool CarGunJustDown(void); + int16 GetHandBrake(void); + int16 GetBrake(void); + bool GetExitVehicle(void); + bool ExitVehicleJustDown(void); + int32 GetWeapon(void); + bool WeaponJustDown(void); + int16 GetAccelerate(void); + bool CycleCameraModeUpJustDown(void); + bool CycleCameraModeDownJustDown(void); + bool ChangeStationJustDown(void); + bool CycleWeaponLeftJustDown(void); + bool CycleWeaponRightJustDown(void); + bool GetTarget(void); + bool TargetJustDown(void); + bool JumpJustDown(void); + bool GetSprint(void); + bool ShiftTargetLeftJustDown(void); + bool ShiftTargetRightJustDown(void); + bool GetAnaloguePadUp(void); + bool GetAnaloguePadDown(void); + bool GetAnaloguePadLeft(void); + bool GetAnaloguePadRight(void); + bool GetAnaloguePadLeftJustUp(void); + bool GetAnaloguePadRightJustUp(void); + bool ForceCameraBehindPlayer(void); + bool SniperZoomIn(void); + bool SniperZoomOut(void); + int16 SniperModeLookLeftRight(void); + int16 SniperModeLookUpDown(void); + int16 LookAroundLeftRight(void); + int16 LookAroundUpDown(void); + void ResetAverageWeapon(void); + static void PrintErrorMessage(void); + static void ResetCheats(void); + static char *EditString(char *pStr, int32 nSize); + static int32 *EditCodesForControls(int32 *pRsKeys, int32 nSize); + +#ifdef XINPUT + static int XInputJoy1; + static int XInputJoy2; + void AffectFromXinput(uint32 pad); +#endif + + // mouse + bool GetLeftMouseJustDown() { return !!(NewMouseControllerState.LMB && !OldMouseControllerState.LMB); } + bool GetRightMouseJustDown() { return !!(NewMouseControllerState.RMB && !OldMouseControllerState.RMB); } + bool GetMiddleMouseJustDown() { return !!(NewMouseControllerState.MMB && !OldMouseControllerState.MMB); } + bool GetMouseWheelUpJustDown() { return !!(NewMouseControllerState.WHEELUP && !OldMouseControllerState.WHEELUP); } + bool GetMouseWheelDownJustDown() { return !!(NewMouseControllerState.WHEELDN && !OldMouseControllerState.WHEELDN);} + bool GetMouseX1JustDown() { return !!(NewMouseControllerState.MXB1 && !OldMouseControllerState.MXB1); } + bool GetMouseX2JustDown() { return !!(NewMouseControllerState.MXB2 && !OldMouseControllerState.MXB2); } + + bool GetLeftMouseJustUp() { return !!(!NewMouseControllerState.LMB && OldMouseControllerState.LMB); } + bool GetRightMouseJustUp() { return !!(!NewMouseControllerState.RMB && OldMouseControllerState.RMB); } + bool GetMiddleMouseJustUp() { return !!(!NewMouseControllerState.MMB && OldMouseControllerState.MMB); } + bool GetMouseWheelUpJustUp() { return !!(!NewMouseControllerState.WHEELUP && OldMouseControllerState.WHEELUP); } + bool GetMouseWheelDownJustUp() { return !!(!NewMouseControllerState.WHEELDN && OldMouseControllerState.WHEELDN); } + bool GetMouseX1JustUp() { return !!(!NewMouseControllerState.MXB1 && OldMouseControllerState.MXB1); } + bool GetMouseX2JustUp() { return !!(!NewMouseControllerState.MXB2 && OldMouseControllerState.MXB2); } + + bool GetLeftMouse() { return NewMouseControllerState.LMB; } + bool GetRightMouse() { return NewMouseControllerState.RMB; } + bool GetMiddleMouse() { return NewMouseControllerState.MMB; } + bool GetMouseWheelUp() { return NewMouseControllerState.WHEELUP; } + bool GetMouseWheelDown() { return NewMouseControllerState.WHEELDN; } + bool GetMouseX1() { return NewMouseControllerState.MXB1; } + bool GetMouseX2() { return NewMouseControllerState.MXB2; } + + bool GetLeftMouseUp() { return !OldMouseControllerState.LMB; } + bool GetRightMouseUp() { return !OldMouseControllerState.RMB; } + bool GetMiddleMouseUp() { return !OldMouseControllerState.MMB; } + bool GetMouseWheelUpUp() { return !OldMouseControllerState.WHEELUP; } + bool GetMouseWheelDownUp() { return !OldMouseControllerState.WHEELDN; } + bool GetMouseX1Up() { return !OldMouseControllerState.MXB1; } + bool GetMouseX2Up() { return !OldMouseControllerState.MXB2; } + + + float GetMouseX() { return NewMouseControllerState.x; } + float GetMouseY() { return NewMouseControllerState.y; } + + // keyboard + + bool GetCharJustDown(int32 c) { return !!(NewKeyState.VK_KEYS[c] && !OldKeyState.VK_KEYS[c]); } + bool GetFJustDown(int32 n) { return !!(NewKeyState.F[n] && !OldKeyState.F[n]); } + bool GetEscapeJustDown() { return !!(NewKeyState.ESC && !OldKeyState.ESC); } + bool GetInsertJustDown() { return !!(NewKeyState.INS && !OldKeyState.INS); } + bool GetDeleteJustDown() { return !!(NewKeyState.DEL && !OldKeyState.DEL); } + bool GetHomeJustDown() { return !!(NewKeyState.HOME && !OldKeyState.HOME); } + bool GetEndJustDown() { return !!(NewKeyState.END && !OldKeyState.END); } + bool GetPageUpJustDown() { return !!(NewKeyState.PGUP && !OldKeyState.PGUP); } + bool GetPageDownJustDown() { return !!(NewKeyState.PGDN && !OldKeyState.PGDN); } + bool GetUpJustDown() { return !!(NewKeyState.UP && !OldKeyState.UP); } + bool GetDownJustDown() { return !!(NewKeyState.DOWN && !OldKeyState.DOWN); } + bool GetLeftJustDown() { return !!(NewKeyState.LEFT && !OldKeyState.LEFT); } + bool GetRightJustDown() { return !!(NewKeyState.RIGHT && !OldKeyState.RIGHT); } + bool GetScrollLockJustDown() { return !!(NewKeyState.SCROLLLOCK && !OldKeyState.SCROLLLOCK); } + bool GetPauseJustDown() { return !!(NewKeyState.PAUSE && !OldKeyState.PAUSE); } + bool GetNumLockJustDown() { return !!(NewKeyState.NUMLOCK && !OldKeyState.NUMLOCK); } + bool GetDivideJustDown() { return !!(NewKeyState.DIV && !OldKeyState.DIV); } + bool GetTimesJustDown() { return !!(NewKeyState.MUL && !OldKeyState.MUL); } + bool GetMinusJustDown() { return !!(NewKeyState.SUB && !OldKeyState.SUB); } + bool GetPlusJustDown() { return !!(NewKeyState.ADD && !OldKeyState.ADD); } + bool GetPadEnterJustDown() { return !!(NewKeyState.ENTER && !OldKeyState.ENTER); } + bool GetPadDelJustDown() { return !!(NewKeyState.DECIMAL && !OldKeyState.DECIMAL); } + bool GetPad1JustDown() { return !!(NewKeyState.NUM1 && !OldKeyState.NUM1); } + bool GetPad2JustDown() { return !!(NewKeyState.NUM2 && !OldKeyState.NUM2); } + bool GetPad3JustDown() { return !!(NewKeyState.NUM3 && !OldKeyState.NUM3); } + bool GetPad4JustDown() { return !!(NewKeyState.NUM4 && !OldKeyState.NUM4); } + bool GetPad5JustDown() { return !!(NewKeyState.NUM5 && !OldKeyState.NUM5); } + bool GetPad6JustDown() { return !!(NewKeyState.NUM6 && !OldKeyState.NUM6); } + bool GetPad7JustDown() { return !!(NewKeyState.NUM7 && !OldKeyState.NUM7); } + bool GetPad8JustDown() { return !!(NewKeyState.NUM8 && !OldKeyState.NUM8); } + bool GetPad9JustDown() { return !!(NewKeyState.NUM9 && !OldKeyState.NUM9); } + bool GetPad0JustDown() { return !!(NewKeyState.NUM0 && !OldKeyState.NUM0); } + bool GetBackspaceJustDown() { return !!(NewKeyState.BACKSP && !OldKeyState.BACKSP); } + bool GetTabJustDown() { return !!(NewKeyState.TAB && !OldKeyState.TAB); } + bool GetCapsLockJustDown() { return !!(NewKeyState.CAPSLOCK && !OldKeyState.CAPSLOCK); } + bool GetReturnJustDown() { return !!(NewKeyState.EXTENTER && !OldKeyState.EXTENTER); } + bool GetLeftShiftJustDown() { return !!(NewKeyState.LSHIFT && !OldKeyState.LSHIFT); } + bool GetShiftJustDown() { return !!(NewKeyState.SHIFT && !OldKeyState.SHIFT); } + bool GetRightShiftJustDown() { return !!(NewKeyState.RSHIFT && !OldKeyState.RSHIFT); } + bool GetLeftCtrlJustDown() { return !!(NewKeyState.LCTRL && !OldKeyState.LCTRL); } + bool GetRightCtrlJustDown() { return !!(NewKeyState.RCTRL && !OldKeyState.RCTRL); } + bool GetLeftAltJustDown() { return !!(NewKeyState.LALT && !OldKeyState.LALT); } + bool GetRightAltJustDown() { return !!(NewKeyState.RALT && !OldKeyState.RALT); } + bool GetLeftWinJustDown() { return !!(NewKeyState.LWIN && !OldKeyState.LWIN); } + bool GetRightWinJustDown() { return !!(NewKeyState.RWIN && !OldKeyState.RWIN); } + bool GetAppsJustDown() { return !!(NewKeyState.APPS && !OldKeyState.APPS); } + bool GetEnterJustDown() { return GetPadEnterJustDown() || GetReturnJustDown(); } + bool GetAltJustDown() { return GetLeftAltJustDown() || GetRightAltJustDown(); } + + bool GetLeftJustUp() { return !!(!NewKeyState.LEFT && OldKeyState.LEFT); } + bool GetRightJustUp() { return !!(!NewKeyState.RIGHT && OldKeyState.RIGHT); } + bool GetEnterJustUp() { return GetPadEnterJustUp() || GetReturnJustUp(); } + bool GetReturnJustUp() { return !!(!NewKeyState.EXTENTER && OldKeyState.EXTENTER); } + bool GetPadEnterJustUp() { return !!(!NewKeyState.ENTER && OldKeyState.ENTER); } + + bool GetChar(int32 c) { return NewKeyState.VK_KEYS[c]; } + bool GetF(int32 n) { return NewKeyState.F[n]; } + bool GetEscape() { return NewKeyState.ESC; } + bool GetInsert() { return NewKeyState.INS; } + bool GetDelete() { return NewKeyState.DEL; } + bool GetHome() { return NewKeyState.HOME; } + bool GetEnd() { return NewKeyState.END; } + bool GetPageUp() { return NewKeyState.PGUP; } + bool GetPageDown() { return NewKeyState.PGDN; } + bool GetUp() { return NewKeyState.UP; } + bool GetDown() { return NewKeyState.DOWN; } + bool GetLeft() { return NewKeyState.LEFT; } + bool GetRight() { return NewKeyState.RIGHT; } + bool GetScrollLock() { return NewKeyState.SCROLLLOCK; } + bool GetPause() { return NewKeyState.PAUSE; } + bool GetNumLock() { return NewKeyState.NUMLOCK; } + bool GetDivide() { return NewKeyState.DIV; } + bool GetTimes() { return NewKeyState.MUL; } + bool GetMinus() { return NewKeyState.SUB; } + bool GetPlus() { return NewKeyState.ADD; } + bool GetPadEnter() { return NewKeyState.ENTER; } // GetEnterJustDown + bool GetPadDel() { return NewKeyState.DECIMAL; } + bool GetPad1() { return NewKeyState.NUM1; } + bool GetPad2() { return NewKeyState.NUM2; } + bool GetPad3() { return NewKeyState.NUM3; } + bool GetPad4() { return NewKeyState.NUM4; } + bool GetPad5() { return NewKeyState.NUM5; } + bool GetPad6() { return NewKeyState.NUM6; } + bool GetPad7() { return NewKeyState.NUM7; } + bool GetPad8() { return NewKeyState.NUM8; } + bool GetPad9() { return NewKeyState.NUM9; } + bool GetPad0() { return NewKeyState.NUM0; } + bool GetBackspace() { return NewKeyState.BACKSP; } + bool GetTab() { return NewKeyState.TAB; } + bool GetCapsLock() { return NewKeyState.CAPSLOCK; } + bool GetEnter() { return NewKeyState.EXTENTER; } + bool GetLeftShift() { return NewKeyState.LSHIFT; } + bool GetShift() { return NewKeyState.SHIFT; } + bool GetRightShift() { return NewKeyState.RSHIFT; } + bool GetLeftCtrl() { return NewKeyState.LCTRL; } + bool GetRightCtrl() { return NewKeyState.RCTRL; } + bool GetLeftAlt() { return NewKeyState.LALT; } + bool GetRightAlt() { return NewKeyState.RALT; } + bool GetLeftWin() { return NewKeyState.LWIN; } + bool GetRightWin() { return NewKeyState.RWIN; } + bool GetApps() { return NewKeyState.APPS; } + // pad + + bool GetTriangleJustDown() { return !!(NewState.Triangle && !OldState.Triangle); } + bool GetCircleJustDown() { return !!(NewState.Circle && !OldState.Circle); } + bool GetCrossJustDown() { return !!(NewState.Cross && !OldState.Cross); } + bool GetSquareJustDown() { return !!(NewState.Square && !OldState.Square); } + bool GetDPadUpJustDown() { return !!(NewState.DPadUp && !OldState.DPadUp); } + bool GetDPadDownJustDown() { return !!(NewState.DPadDown && !OldState.DPadDown); } + bool GetDPadLeftJustDown() { return !!(NewState.DPadLeft && !OldState.DPadLeft); } + bool GetDPadRightJustDown() { return !!(NewState.DPadRight && !OldState.DPadRight); } + bool GetLeftShoulder1JustDown() { return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); } + bool GetLeftShoulder2JustDown() { return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); } + bool GetRightShoulder1JustDown() { return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); } + bool GetRightShoulder2JustDown() { return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); } + bool GetLeftShockJustDown() { return !!(NewState.LeftShock && !OldState.LeftShock); } + bool GetRightShockJustDown() { return !!(NewState.RightShock && !OldState.RightShock); } + bool GetStartJustDown() { return !!(NewState.Start && !OldState.Start); } + bool GetLeftStickXJustDown() { return !!(NewState.LeftStickX && !OldState.LeftStickX); } + bool GetLeftStickYJustDown() { return !!(NewState.LeftStickY && !OldState.LeftStickY); } + + bool GetTriangleJustUp() { return !!(!NewState.Triangle && OldState.Triangle); } + bool GetCircleJustUp() { return !!(!NewState.Circle && OldState.Circle); } + bool GetCrossJustUp() { return !!(!NewState.Cross && OldState.Cross); } + bool GetSquareJustUp() { return !!(!NewState.Square && OldState.Square); } + bool GetDPadUpJustUp() { return !!(!NewState.DPadUp && OldState.DPadUp); } + bool GetDPadDownJustUp() { return !!(!NewState.DPadDown && OldState.DPadDown); } + bool GetDPadLeftJustUp() { return !!(!NewState.DPadLeft && OldState.DPadLeft); } + bool GetDPadRightJustUp() { return !!(!NewState.DPadRight && OldState.DPadRight); } + + bool GetTriangle() { return !!NewState.Triangle; } + bool GetCircle() { return !!NewState.Circle; } + bool GetCross() { return !!NewState.Cross; } + bool GetSquare() { return !!NewState.Square; } + bool GetDPadUp() { return !!NewState.DPadUp; } + bool GetDPadDown() { return !!NewState.DPadDown; } + bool GetDPadLeft() { return !!NewState.DPadLeft; } + bool GetDPadRight() { return !!NewState.DPadRight; } + bool GetLeftShoulder1(void) { return !!NewState.LeftShoulder1; } + bool GetLeftShoulder2(void) { return !!NewState.LeftShoulder2; } + bool GetRightShoulder1(void) { return !!NewState.RightShoulder1; } + bool GetRightShoulder2(void) { return !!NewState.RightShoulder2; } + bool GetStart() { return !!NewState.Start; } + int16 GetLeftStickX(void) { return NewState.LeftStickX; } + int16 GetLeftStickY(void) { return NewState.LeftStickY; } + int16 GetRightStickX(void) { return NewState.RightStickX; } + int16 GetRightStickY(void) { return NewState.RightStickY; } + + bool ArePlayerControlsDisabled(void) { return DisablePlayerControls != PLAYERCONTROL_ENABLED; } + void SetDisablePlayerControls(uint8 who) { DisablePlayerControls |= who; } + void SetEnablePlayerControls(uint8 who) { DisablePlayerControls &= ~who; } + bool IsPlayerControlsDisabledBy(uint8 who) { return DisablePlayerControls & who; } + + int16 GetMode() { return Mode; } + void SetMode(int16 mode) { Mode = mode; } + + static bool IsNoOrObsolete() { return bDisplayNoControllerMessage || bObsoleteControllerMessage; } +}; + +VALIDATE_SIZE(CPad, 0xFC); +extern CPad Pads[MAX_PADS]; + +#ifdef ALLCARSHELI_CHEAT +extern bool bAllCarCheat; +#endif diff --git a/src/core/Placeable.cpp b/src/core/Placeable.cpp new file mode 100644 index 0000000..162148f --- /dev/null +++ b/src/core/Placeable.cpp @@ -0,0 +1,66 @@ +#include "common.h" +#include "Placeable.h" + + +CPlaceable::CPlaceable(void) +{ + m_matrix.SetScale(1.0f); +} + +CPlaceable::~CPlaceable(void) +{ +} + +void +CPlaceable::SetHeading(float angle) +{ + CVector pos = GetMatrix().GetPosition(); + m_matrix.SetRotateZ(angle); + GetMatrix().Translate(pos); +} + +bool +CPlaceable::IsWithinArea(float x1, float y1, float x2, float y2) +{ + float tmp; + + if(x1 > x2){ + tmp = x1; + x1 = x2; + x2 = tmp; + } + if(y1 > y2){ + tmp = y1; + y1 = y2; + y2 = tmp; + } + + return x1 <= GetPosition().x && GetPosition().x <= x2 && + y1 <= GetPosition().y && GetPosition().y <= y2; +} + +bool +CPlaceable::IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2) +{ + float tmp; + + if(x1 > x2){ + tmp = x1; + x1 = x2; + x2 = tmp; + } + if(y1 > y2){ + tmp = y1; + y1 = y2; + y2 = tmp; + } + if(z1 > z2){ + tmp = z1; + z1 = z2; + z2 = tmp; + } + + return x1 <= GetPosition().x && GetPosition().x <= x2 && + y1 <= GetPosition().y && GetPosition().y <= y2 && + z1 <= GetPosition().z && GetPosition().z <= z2; +} diff --git a/src/core/Placeable.h b/src/core/Placeable.h new file mode 100644 index 0000000..2f246bc --- /dev/null +++ b/src/core/Placeable.h @@ -0,0 +1,37 @@ +#pragma once + +class CPlaceable +{ +protected: + CMatrix m_matrix; + +public: + // disable allocation + static void *operator new(size_t) throw(); + + CPlaceable(void); + virtual ~CPlaceable(void); + const CVector &GetPosition(void) { return m_matrix.GetPosition(); } + void SetPosition(float x, float y, float z) { + m_matrix.GetPosition().x = x; + m_matrix.GetPosition().y = y; + m_matrix.GetPosition().z = z; + } + void SetPosition(const CVector &pos) { m_matrix.GetPosition() = pos; } + CVector &GetRight(void) { return m_matrix.GetRight(); } + CVector &GetForward(void) { return m_matrix.GetForward(); } + CVector &GetUp(void) { return m_matrix.GetUp(); } + CMatrix &GetMatrix(void) { return m_matrix; } + void SetMatrix(CMatrix &newMatrix) { m_matrix = newMatrix; } + void SetTransform(RwMatrix *m) { m_matrix = CMatrix(m, false); } + void SetHeading(float angle); + void SetOrientation(float x, float y, float z){ + CVector pos = m_matrix.GetPosition(); + m_matrix.SetRotate(x, y, z); + m_matrix.Translate(pos); + } + bool IsWithinArea(float x1, float y1, float x2, float y2); + bool IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2); +}; + +VALIDATE_SIZE(CPlaceable, 0x4C); diff --git a/src/core/PlayerInfo.cpp b/src/core/PlayerInfo.cpp new file mode 100644 index 0000000..91bd069 --- /dev/null +++ b/src/core/PlayerInfo.cpp @@ -0,0 +1,662 @@ +#include "common.h" + +#include "Automobile.h" +#include "Bridge.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "Cranes.h" +#include "Darkel.h" +#include "Explosion.h" +#include "Fire.h" +#include "Frontend.h" +#include "General.h" +#include "HandlingMgr.h" +#include "Messages.h" +#include "Pad.h" +#include "PathFind.h" +#include "PlayerInfo.h" +#include "PlayerPed.h" +#include "PlayerSkin.h" +#include "ProjectileInfo.h" +#include "Remote.h" +#include "Renderer.h" +#include "Replay.h" +#include "Script.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "Streaming.h" +#include "Text.h" +#include "Wanted.h" +#include "WaterLevel.h" +#include "World.h" +#include "ZoneCull.h" +#include "main.h" + + +void +CPlayerInfo::Clear(void) +{ + m_pPed = nil; + m_pRemoteVehicle = nil; + if (m_pVehicleEx) { + m_pVehicleEx->bUsingSpecialColModel = false; + m_pVehicleEx = nil; + } + m_nVisibleMoney = 0; + m_nMoney = m_nVisibleMoney; + m_WBState = WBSTATE_PLAYING; + m_nWBTime = 0; + m_nTrafficMultiplier = 0; + m_fRoadDensity = 1.0f; + m_bInRemoteMode = false; + m_bUnusedTaxiThing = false; + m_nUnusedTaxiTimer = 0; + m_nCollectedPackages = 0; + m_nTotalPackages = 3; + m_nTimeLastHealthLoss = 0; + m_nTimeLastArmourLoss = 0; + m_nNextSexFrequencyUpdateTime = 0; + m_nNextSexMoneyUpdateTime = 0; + m_nSexFrequency = 0; + m_pHooker = nil; + m_nTimeTankShotGun = 0; + field_248 = 0; + m_nUpsideDownCounter = 0; + m_bInfiniteSprint = false; + m_bFastReload = false; + m_bGetOutOfJailFree = false; + m_bGetOutOfHospitalFree = false; + m_nPreviousTimeRewardedForExplosion = 0; + m_nExplosionsSinceLastReward = 0; +} + +void +CPlayerInfo::Process(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + // Unused taxi feature. Gives you a dollar for every second with a passenger. Can be toggled via 0x29A opcode. + bool startTaxiTimer = true; + if (m_bUnusedTaxiThing && m_pPed->bInVehicle) { + CVehicle *veh = m_pPed->m_pMyVehicle; + if ((veh->GetModelIndex() == MI_TAXI || veh->GetModelIndex() == MI_CABBIE || veh->GetModelIndex() == MI_BORGNINE) + && veh->pDriver == m_pPed && veh->m_nNumPassengers != 0) { + for (uint32 timePassed = CTimer::GetTimeInMilliseconds() - m_nUnusedTaxiTimer; timePassed >= 1000; m_nUnusedTaxiTimer += 1000) { + timePassed -= 1000; + ++m_nMoney; + } + startTaxiTimer = false; + } + } + if (startTaxiTimer) + m_nUnusedTaxiTimer = CTimer::GetTimeInMilliseconds(); + + // The effect that makes money counter does while earning/losing money + if (m_nVisibleMoney != m_nMoney) { + int diff = m_nMoney - m_nVisibleMoney; + int diffAbs = Abs(diff); + int changeBy; + + if (diffAbs > 100000) + changeBy = 12345; + else if (diffAbs > 10000) + changeBy = 1234; + else if (diffAbs > 1000) + changeBy = 123; + else if (diffAbs > 50) + changeBy = 42; + else + changeBy = 1; + + if (diff < 0) + m_nVisibleMoney -= changeBy; + else + m_nVisibleMoney += changeBy; + } + + if (!(CTimer::GetFrameCounter() & 15)) { + CVector2D playerPos = m_pPed->bInVehicle ? m_pPed->m_pMyVehicle->GetPosition() : m_pPed->GetPosition(); + m_fRoadDensity = ThePaths.CalcRoadDensity(playerPos.x, playerPos.y); + } + + m_fRoadDensity = Clamp(m_fRoadDensity, 0.4f, 1.45f); + + // Because vehicle enter/exit use same key binding. + bool enterOrExitVeh; + if (m_pPed->bVehExitWillBeInstant && m_pPed->bInVehicle) + enterOrExitVeh = CPad::GetPad(0)->ExitVehicleJustDown(); + else + enterOrExitVeh = CPad::GetPad(0)->GetExitVehicle(); + + if (enterOrExitVeh && m_pPed->m_nPedState != PED_SNIPER_MODE && m_pPed->m_nPedState != PED_ROCKET_MODE) { + if (m_pPed->bInVehicle) { + if (!m_pRemoteVehicle) { + CEntity *surfaceBelowVeh = m_pPed->m_pMyVehicle->m_pCurGroundEntity; + if (!surfaceBelowVeh || !CBridge::ThisIsABridgeObjectMovingUp(surfaceBelowVeh->GetModelIndex())) { + CVehicle *veh = m_pPed->m_pMyVehicle; + if (!veh->IsBoat() || veh->m_nDoorLock == CARLOCK_LOCKED_PLAYER_INSIDE) { + + // This condition will always return true, else block was probably WIP Miami code. + if (veh->m_vehType != VEHICLE_TYPE_BIKE || veh->m_nDoorLock == CARLOCK_LOCKED_PLAYER_INSIDE) { + if (veh->GetStatus() != STATUS_WRECKED && veh->GetStatus() != STATUS_TRAIN_MOVING && veh->m_nDoorLock != CARLOCK_LOCKED_PLAYER_INSIDE) { + if (veh->m_vecMoveSpeed.Magnitude() < 0.17f && CTimer::GetTimeScale() >= 0.5f && !veh->bIsInWater) { + m_pPed->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + } + } + } else { + CVector sth = 0.7f * veh->GetRight() + veh->GetPosition(); + bool found = false; + float groundZ = CWorld::FindGroundZFor3DCoord(sth.x, sth.y, 2.0f + sth.z, &found); + + if (found) + sth.z = 1.0f + groundZ; + m_pPed->SetPedState(PED_IDLE); + m_pPed->SetMoveState(PEDMOVE_STILL); + CPed::PedSetOutCarCB(0, m_pPed); + CAnimManager::BlendAnimation(m_pPed->GetClump(), m_pPed->m_animGroup, ANIM_STD_IDLE, 100.0f); + CAnimManager::BlendAnimation(m_pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_LAND, 100.0f); + m_pPed->SetPosition(sth); + m_pPed->SetMoveState(PEDMOVE_STILL); + m_pPed->m_vecMoveSpeed = veh->m_vecMoveSpeed; + } + } else { + // The code in here was under CPed::SetExitBoat in VC, did the same for here. + m_pPed->SetExitBoat(veh); + m_pPed->bTryingToReachDryLand = true; + } + } + } + } else { + // Enter vehicle + if (CPad::GetPad(0)->ExitVehicleJustDown()) { + bool weAreOnBoat = false; + float lastCloseness = 0.0f; + CVehicle *carBelow = nil; + CEntity *surfaceBelow = m_pPed->m_pCurrentPhysSurface; + if (surfaceBelow && surfaceBelow->IsVehicle()) { + carBelow = (CVehicle*)surfaceBelow; + if (carBelow->IsBoat()) { + weAreOnBoat = true; + m_pPed->bOnBoat = true; +#ifdef VC_PED_PORTS + if (carBelow->GetStatus() != STATUS_WRECKED && carBelow->GetUp().z > 0.3f) +#else + if (carBelow->GetStatus() != STATUS_WRECKED) +#endif + m_pPed->SetSeekBoatPosition(carBelow); + } + } + // Find closest car + if (!weAreOnBoat) { + float minX = m_pPed->GetPosition().x - 10.0f; + float maxX = 10.0f + m_pPed->GetPosition().x; + float minY = m_pPed->GetPosition().y - 10.0f; + float maxY = 10.0f + m_pPed->GetPosition().y; + + int minXSector = CWorld::GetSectorIndexX(minX); + if (minXSector < 0) minXSector = 0; + int minYSector = CWorld::GetSectorIndexY(minY); + if (minYSector < 0) minYSector = 0; + int maxXSector = CWorld::GetSectorIndexX(maxX); + if (maxXSector > NUMSECTORS_X - 1) maxXSector = NUMSECTORS_X - 1; + int maxYSector = CWorld::GetSectorIndexY(maxY); + if (maxYSector > NUMSECTORS_Y - 1) maxYSector = NUMSECTORS_Y - 1; + + CWorld::AdvanceCurrentScanCode(); + + for (int curY = minYSector; curY <= maxYSector; curY++) { + for (int curX = minXSector; curX <= maxXSector; curX++) { + CSector *sector = CWorld::GetSector(curX, curY); + FindClosestCarSectorList(sector->m_lists[ENTITYLIST_VEHICLES], m_pPed, + minX, minY, maxX, maxY, &lastCloseness, &carBelow); + FindClosestCarSectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], m_pPed, + minX, minY, maxX, maxY, &lastCloseness, &carBelow); + } + } + } + // carBelow is now closest vehicle + if (carBelow && !weAreOnBoat) { + if (carBelow->GetStatus() == STATUS_TRAIN_NOT_MOVING) { + m_pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, carBelow); + } else if (carBelow->IsBoat()) { + if (!carBelow->pDriver) { + m_pPed->m_vehDoor = 0; + m_pPed->SetEnterCar(carBelow, m_pPed->m_vehDoor); + } + } else { + m_pPed->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, carBelow); + } + } + } + } + } + if (m_bInRemoteMode) { + uint32 timeWithoutRemoteCar = CTimer::GetTimeInMilliseconds() - m_nTimeLostRemoteCar; + if (CTimer::GetPreviousTimeInMilliseconds() - m_nTimeLostRemoteCar < 1000 && timeWithoutRemoteCar >= 1000 && m_WBState == WBSTATE_PLAYING) { + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(1.0f, FADE_OUT); + } + if (timeWithoutRemoteCar > 2000) { + if (m_WBState == WBSTATE_PLAYING) { + TheCamera.RestoreWithJumpCut(); + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(1.0f, FADE_IN); + TheCamera.Process(); + CTimer::Stop(); + CCullZones::ForceCullZoneCoors(TheCamera.GetPosition()); + CRenderer::RequestObjectsInFrustum(); + CStreaming::LoadAllRequestedModels(false); + CTimer::Update(); + } + m_bInRemoteMode = false; + CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle = nil; + if (FindPlayerVehicle()) { + FindPlayerVehicle()->SetStatus(STATUS_PLAYER); + } + } + } + if (!(CTimer::GetFrameCounter() & 31)) { + CVehicle *veh = FindPlayerVehicle(); + if (veh && m_pPed->bInVehicle && veh->GetUp().z < 0.0f + && veh->m_vecMoveSpeed.Magnitude() < 0.05f && veh->IsCar() && !veh->bIsInWater) { + + if (veh->GetUp().z < -0.5f) { + m_nUpsideDownCounter += 2; + + } else { + m_nUpsideDownCounter++; + } + } else { + m_nUpsideDownCounter = 0; + } + + if (m_nUpsideDownCounter > 6 && veh->bCanBeDamaged) { + veh->m_fHealth = 249.0f < veh->m_fHealth ? 249.0f : veh->m_fHealth; + if (veh->IsCar()) { + CAutomobile* car = (CAutomobile*)veh; + car->Damage.SetEngineStatus(225); + car->m_pSetOnFireEntity = nil; + } + } + } + if (FindPlayerVehicle()) { + CVehicle *veh = FindPlayerVehicle(); + veh->m_nZoneLevel = LEVEL_IGNORE; + for (int i = 0; i < ARRAY_SIZE(veh->pPassengers); i++) { + if (veh->pPassengers[i]) + veh->pPassengers[i]->m_nZoneLevel = LEVEL_GENERIC; + } + CStats::DistanceTravelledInVehicle += veh->m_fDistanceTravelled; + } else { + CStats::DistanceTravelledOnFoot += FindPlayerPed()->m_fDistanceTravelled; + } +} + +bool +CPlayerInfo::IsPlayerInRemoteMode() +{ + return m_pRemoteVehicle || m_bInRemoteMode; +} + +void +CPlayerInfo::SavePlayerInfo(uint8 *buf, uint32 *size) +{ + // Interesting + *size = sizeof(CPlayerInfo); + +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); buf += sizeof(data); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nMoney); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_WBState); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nWBTime); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nTrafficMultiplier); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_fRoadDensity); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nTotalPackages); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bInfiniteSprint); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bFastReload); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfJailFree); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfHospitalFree); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_aPlayerName); +#undef CopyToBuf +} + +void +CPlayerInfo::LoadPlayerInfo(uint8 *buf, uint32 size) +{ +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); buf += sizeof(data); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nMoney); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_WBState); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nWBTime); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nTrafficMultiplier); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_fRoadDensity); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nTotalPackages); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bInfiniteSprint); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bFastReload); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfJailFree); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfHospitalFree); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_aPlayerName) +#undef CopyFromBuf +} + +void +CPlayerInfo::FindClosestCarSectorList(CPtrList& carList, CPed* ped, float unk1, float unk2, float unk3, float unk4, float* lastCloseness, CVehicle** closestCarOutput) +{ + for (CPtrNode* node = carList.first; node; node = node->next) { + CVehicle *car = (CVehicle*)node->item; + if(car->m_scanCode != CWorld::GetCurrentScanCode()) { + if (!car->bUsesCollision || !car->IsVehicle()) + continue; + + car->m_scanCode = CWorld::GetCurrentScanCode(); + if (car->GetStatus() != STATUS_WRECKED && car->GetStatus() != STATUS_TRAIN_MOVING + && (car->GetUp().z > 0.3f || (car->IsVehicle() && ((CVehicle*)car)->m_vehType == VEHICLE_TYPE_BIKE))) { + CVector carCentre = car->GetBoundCentre(); + + if (Abs(ped->GetPosition().z - carCentre.z) < 2.0f) { + float dist = (ped->GetPosition() - carCentre).Magnitude2D(); + if (dist <= 10.0f && !CCranes::IsThisCarBeingCarriedByAnyCrane(car)) { + EvaluateCarPosition(car, ped, dist, lastCloseness, closestCarOutput); + } + } + } + } + } +} + +// lastCloseness is passed to other calls of this function +void +CPlayerInfo::EvaluateCarPosition(CEntity *carToTest, CPed *player, float carBoundCentrePedDist, float *lastCloseness, CVehicle **closestCarOutput) +{ + // This dist used for determining the angle to face + CVector2D dist(carToTest->GetPosition() - player->GetPosition()); + float neededTurn = CGeneral::GetATanOfXY(player->GetForward().x, player->GetForward().y) - CGeneral::GetATanOfXY(dist.x, dist.y); + while (neededTurn >= PI) { + neededTurn -= 2 * PI; + } + + while (neededTurn < -PI) { + neededTurn += 2 * PI; + } + + // This dist used for evaluating cars' distances, weird... + // Accounts inverted needed turn (or needed turn in long way) and car dist. + float closeness = (1.0f - Abs(neededTurn) / TWOPI) * (10.0f - carBoundCentrePedDist); + if (closeness > *lastCloseness) { + *lastCloseness = closeness; + *closestCarOutput = (CVehicle*)carToTest; + } +} + +const CVector & +CPlayerInfo::GetPos() +{ +#ifdef FIX_BUGS + if (!m_pPed) + return TheCamera.GetPosition(); +#endif + if (m_pPed->InVehicle()) + return m_pPed->m_pMyVehicle->GetPosition(); + return m_pPed->GetPosition(); +} + +CVector +FindPlayerCoors(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return TheCamera.GetPosition(); +#endif + CPlayerPed *ped = FindPlayerPed(); + if(ped->InVehicle()) + return ped->m_pMyVehicle->GetPosition(); + else + return ped->GetPosition(); +} + +const CVector & +FindPlayerSpeed(void) +{ +#ifdef FIX_BUGS + static CVector vecTmpVector(0.0f, 0.0f, 0.0f); + if (CReplay::IsPlayingBack()) + return vecTmpVector; +#endif + CPlayerPed *ped = FindPlayerPed(); + if(ped->InVehicle()) + return ped->m_pMyVehicle->m_vecMoveSpeed; + else + return ped->m_vecMoveSpeed; +} + +CVehicle * +FindPlayerVehicle(void) +{ + CPlayerPed *ped = FindPlayerPed(); + if(ped && ped->InVehicle()) return ped->m_pMyVehicle; + return nil; +} + +CEntity * +FindPlayerEntity(void) +{ + CPlayerPed *ped = FindPlayerPed(); + if(ped->InVehicle()) + return ped->m_pMyVehicle; + else + return ped; +} + +CVehicle * +FindPlayerTrain(void) +{ + if(FindPlayerVehicle() && FindPlayerVehicle()->IsTrain()) + return FindPlayerVehicle(); + else + return nil; +} + +CPlayerPed * +FindPlayerPed(void) +{ + return CWorld::Players[CWorld::PlayerInFocus].m_pPed; +} + +const CVector & +FindPlayerCentreOfWorld(int32 player) +{ +#ifdef FIX_BUGS + if(CReplay::IsPlayingBack()) return TheCamera.GetPosition(); +#endif + if(CCarCtrl::bCarsGeneratedAroundCamera) return TheCamera.GetPosition(); + if(CWorld::Players[player].m_pRemoteVehicle) return CWorld::Players[player].m_pRemoteVehicle->GetPosition(); + if(FindPlayerVehicle()) return FindPlayerVehicle()->GetPosition(); + return CWorld::Players[player].m_pPed->GetPosition(); +} + +const CVector & +FindPlayerCentreOfWorld_NoSniperShift(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) return TheCamera.GetPosition(); +#endif + if(CCarCtrl::bCarsGeneratedAroundCamera) return TheCamera.GetPosition(); + if(CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle) + return CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->GetPosition(); + if(FindPlayerVehicle()) return FindPlayerVehicle()->GetPosition(); + return FindPlayerPed()->GetPosition(); +} + +float +FindPlayerHeading(void) +{ + if(CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle) + return CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->GetForward().Heading(); + if(FindPlayerVehicle()) return FindPlayerVehicle()->GetForward().Heading(); + return FindPlayerPed()->GetForward().Heading(); +} + +bool +CPlayerInfo::IsRestartingAfterDeath() +{ + return m_WBState == WBSTATE_WASTED; +} + +bool +CPlayerInfo::IsRestartingAfterArrest() +{ + return m_WBState == WBSTATE_BUSTED; +} + +void +CPlayerInfo::KillPlayer() +{ + if (m_WBState != WBSTATE_PLAYING) return; + + m_WBState = WBSTATE_WASTED; + m_nWBTime = CTimer::GetTimeInMilliseconds(); + CDarkel::ResetOnPlayerDeath(); + CMessages::AddBigMessage(TheText.Get("DEAD"), 4000, 2); + CStats::TimesDied++; +} + +void +CPlayerInfo::ArrestPlayer() +{ + if (m_WBState != WBSTATE_PLAYING) return; + + m_WBState = WBSTATE_BUSTED; + m_nWBTime = CTimer::GetTimeInMilliseconds(); + CDarkel::ResetOnPlayerDeath(); + CMessages::AddBigMessage(TheText.Get("BUSTED"), 5000, 2); + CStats::TimesArrested++; +} + +void +CPlayerInfo::PlayerFailedCriticalMission() +{ + if (m_WBState != WBSTATE_PLAYING) + return; + m_WBState = WBSTATE_FAILED_CRITICAL_MISSION; + m_nWBTime = CTimer::GetTimeInMilliseconds(); + CDarkel::ResetOnPlayerDeath(); +} + +void +CPlayerInfo::CancelPlayerEnteringCars(CVehicle *car) +{ + if (!car || car == m_pPed->m_pMyVehicle) { + if (m_pPed->EnteringCar()) + m_pPed->QuitEnteringCar(); + } + if (m_pPed->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER || m_pPed->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) + m_pPed->ClearObjective(); +} + +void +CPlayerInfo::MakePlayerSafe(bool toggle) +{ + if (toggle) { + CTheScripts::ResetCountdownToMakePlayerUnsafe(); + m_pPed->m_pWanted->m_bIgnoredByEveryone = true; + CWorld::StopAllLawEnforcersInTheirTracks(); + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_PLAYERINFO); + CPad::StopPadsShaking(); + m_pPed->bBulletProof = true; + m_pPed->bFireProof = true; + m_pPed->bCollisionProof = true; + m_pPed->bMeleeProof = true; + m_pPed->bOnlyDamagedByPlayer = true; + m_pPed->bExplosionProof = true; + m_pPed->m_bCanBeDamaged = false; + ((CPlayerPed*)m_pPed)->ClearAdrenaline(); + CancelPlayerEnteringCars(nil); + gFireManager.ExtinguishPoint(GetPos(), 4000.0f); + CExplosion::RemoveAllExplosionsInArea(GetPos(), 4000.0f); + CProjectileInfo::RemoveAllProjectiles(); + CWorld::SetAllCarsCanBeDamaged(false); + CWorld::ExtinguishAllCarFiresInArea(GetPos(), 4000.0f); + CReplay::DisableReplays(); + + } else if (!CGame::playingIntro && !CTheScripts::IsCountdownToMakePlayerUnsafeOn()) { + m_pPed->m_pWanted->m_bIgnoredByEveryone = false; + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_PLAYERINFO); + m_pPed->bBulletProof = false; + m_pPed->bFireProof = false; + m_pPed->bCollisionProof = false; + m_pPed->bMeleeProof = false; + m_pPed->bOnlyDamagedByPlayer = false; + m_pPed->bExplosionProof = false; + m_pPed->m_bCanBeDamaged = true; + CWorld::SetAllCarsCanBeDamaged(true); + CReplay::EnableReplays(); + } +} + +void +CPlayerInfo::BlowUpRCBuggy(void) +{ + if (!m_pRemoteVehicle || m_pRemoteVehicle->bRemoveFromWorld) + return; + + CRemote::TakeRemoteControlledCarFromPlayer(); + m_pRemoteVehicle->BlowUpCar(FindPlayerPed()); +} + +// There is something unfinished in here... Sadly all IDBs we have have it unfinished. +void +CPlayerInfo::AwardMoneyForExplosion(CVehicle *wreckedCar) +{ + if (CTimer::GetTimeInMilliseconds() - m_nPreviousTimeRewardedForExplosion < 6000) + ++m_nExplosionsSinceLastReward; + else + m_nExplosionsSinceLastReward = 1; + + m_nPreviousTimeRewardedForExplosion = CTimer::GetTimeInMilliseconds(); + int award = wreckedCar->pHandling->nMonetaryValue * 0.002f; + sprintf(gString, "$%d", award); +#ifdef MONEY_MESSAGES + // This line is a leftover from PS2, I don't know what it was meant to be. + // CVector sth(TheCamera.GetPosition() * 4.0f); + + CMoneyMessages::RegisterOne(wreckedCar->GetPosition() + CVector(0.0f, 0.0f, 2.0f), gString, 0, 255, 0, 2.0f, 0.5f); +#endif + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += award; + + for (int i = m_nExplosionsSinceLastReward; i > 1; --i) { + CGeneral::GetRandomNumber(); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += award; + } +} + +#ifdef GTA_PC +void +CPlayerInfo::SetPlayerSkin(const char *skin) +{ + strncpy(m_aSkinName, skin, 32); + LoadPlayerSkin(); +} + +void +CPlayerInfo::LoadPlayerSkin() +{ + DeletePlayerSkin(); + + m_pSkinTexture = CPlayerSkin::GetSkinTexture(m_aSkinName); + if (!m_pSkinTexture) + m_pSkinTexture = CPlayerSkin::GetSkinTexture(DEFAULT_SKIN_NAME); +} + +void +CPlayerInfo::DeletePlayerSkin() +{ + if (m_pSkinTexture) { + RwTextureDestroy(m_pSkinTexture); + m_pSkinTexture = nil; + } +} +#endif \ No newline at end of file diff --git a/src/core/PlayerInfo.h b/src/core/PlayerInfo.h new file mode 100644 index 0000000..956756e --- /dev/null +++ b/src/core/PlayerInfo.h @@ -0,0 +1,97 @@ +#pragma once + +#include "ColModel.h" + +enum eWastedBustedState +{ + WBSTATE_PLAYING, + WBSTATE_WASTED, + WBSTATE_BUSTED, + WBSTATE_FAILED_CRITICAL_MISSION, +}; + +class CEntity; +class CPed; +class CVehicle; +class CPlayerPed; +class CCivilianPed; + +class CPlayerInfo +{ +public: + CPlayerPed *m_pPed; + CVehicle *m_pRemoteVehicle; + CColModel m_ColModel; + CVehicle *m_pVehicleEx; // vehicle using the col model above + char m_aPlayerName[70]; + int32 m_nMoney; + int32 m_nVisibleMoney; + int32 m_nCollectedPackages; + int32 m_nTotalPackages; + uint32 m_nLastBumpPlayerCarTimer; + uint32 m_nUnusedTaxiTimer; + bool m_bUnusedTaxiThing; + uint32 m_nNextSexFrequencyUpdateTime; + uint32 m_nNextSexMoneyUpdateTime; + int32 m_nSexFrequency; + CCivilianPed *m_pHooker; + int8 m_WBState; // eWastedBustedState + uint32 m_nWBTime; + bool m_bInRemoteMode; + uint32 m_nTimeLostRemoteCar; + uint32 m_nTimeLastHealthLoss; + uint32 m_nTimeLastArmourLoss; + uint32 m_nTimeTankShotGun; + int32 m_nUpsideDownCounter; + int32 field_248; + int16 m_nTrafficMultiplier; + float m_fRoadDensity; + uint32 m_nPreviousTimeRewardedForExplosion; + int32 m_nExplosionsSinceLastReward; + int32 field_268; + int32 field_272; + bool m_bInfiniteSprint; + bool m_bFastReload; + bool m_bGetOutOfJailFree; + bool m_bGetOutOfHospitalFree; +#ifdef GTA_PC + char m_aSkinName[32]; + RwTexture *m_pSkinTexture; +#endif + + void MakePlayerSafe(bool); + void AwardMoneyForExplosion(CVehicle *vehicle); + const CVector &GetPos(); + void Process(void); + void KillPlayer(void); + void ArrestPlayer(void); + bool IsPlayerInRemoteMode(void); + void PlayerFailedCriticalMission(void); + void Clear(void); + void BlowUpRCBuggy(void); + void CancelPlayerEnteringCars(CVehicle*); + bool IsRestartingAfterDeath(void); + bool IsRestartingAfterArrest(void); + void EvaluateCarPosition(CEntity*, CPed*, float, float*, CVehicle**); + void LoadPlayerInfo(uint8 *buf, uint32 size); + void SavePlayerInfo(uint8 *buf, uint32* size); + void FindClosestCarSectorList(CPtrList&, CPed*, float, float, float, float, float*, CVehicle**); + +#ifdef GTA_PC + void LoadPlayerSkin(); + void SetPlayerSkin(const char *skin); + void DeletePlayerSkin(); +#endif +}; + +CPlayerPed *FindPlayerPed(void); +CVehicle *FindPlayerVehicle(void); +CVehicle *FindPlayerTrain(void); +CEntity *FindPlayerEntity(void); +CVector FindPlayerCoors(void); +const CVector &FindPlayerSpeed(void); +const CVector &FindPlayerCentreOfWorld(int32 player); +const CVector &FindPlayerCentreOfWorld_NoSniperShift(void); +float FindPlayerHeading(void); + +VALIDATE_SIZE(CPlayerInfo, 0x13C); diff --git a/src/core/Pools.cpp b/src/core/Pools.cpp new file mode 100644 index 0000000..b024866 --- /dev/null +++ b/src/core/Pools.cpp @@ -0,0 +1,585 @@ +#include "common.h" + +#include "Pools.h" + +#include "Boat.h" +#include "CarCtrl.h" +#ifdef MISSION_REPLAY +#include "GenericGameStorage.h" +#endif +#include "Population.h" +#include "ProjectileInfo.h" +#include "SaveBuf.h" +#include "Streaming.h" +#include "Wanted.h" +#include "World.h" +#include "MemoryHeap.h" + +CCPtrNodePool *CPools::ms_pPtrNodePool; +CEntryInfoNodePool *CPools::ms_pEntryInfoNodePool; +CPedPool *CPools::ms_pPedPool; +CVehiclePool *CPools::ms_pVehiclePool; +CBuildingPool *CPools::ms_pBuildingPool; +CTreadablePool *CPools::ms_pTreadablePool; +CObjectPool *CPools::ms_pObjectPool; +CDummyPool *CPools::ms_pDummyPool; +CAudioScriptObjectPool *CPools::ms_pAudioScriptObjectPool; + +#ifdef GTA_PS2 // or USE_CUSTOM_ALLOCATOR +#define CHECKMEM(msg) CMemCheck::AllocateMemCheckBlock(msg) +#else +#define CHECKMEM(msg) +#endif + +void +CPools::Initialise(void) +{ + PUSH_MEMID(MEMID_POOLS); + CHECKMEM("before pools"); + ms_pPtrNodePool = new CCPtrNodePool(NUMPTRNODES); + CHECKMEM("after CPtrNodePool"); + ms_pEntryInfoNodePool = new CEntryInfoNodePool(NUMENTRYINFOS); + CHECKMEM("after CEntryInfoNodePool"); + ms_pPedPool = new CPedPool(NUMPEDS); + CHECKMEM("after CPedPool"); + ms_pVehiclePool = new CVehiclePool(NUMVEHICLES); + CHECKMEM("after CVehiclePool"); + ms_pBuildingPool = new CBuildingPool(NUMBUILDINGS); + CHECKMEM("after CBuildingPool"); + ms_pTreadablePool = new CTreadablePool(NUMTREADABLES); + CHECKMEM("after CTreadablePool"); + ms_pObjectPool = new CObjectPool(NUMOBJECTS); + CHECKMEM("after CObjectPool"); + ms_pDummyPool = new CDummyPool(NUMDUMMIES); + CHECKMEM("after CDummyPool"); + ms_pAudioScriptObjectPool = new CAudioScriptObjectPool(NUMAUDIOSCRIPTOBJECTS); + CHECKMEM("after pools"); + POP_MEMID(); +} + +void +CPools::ShutDown(void) +{ + debug("PtrNodes left %d\n", ms_pPtrNodePool->GetNoOfUsedSpaces()); + debug("EntryInfoNodes left %d\n", ms_pEntryInfoNodePool->GetNoOfUsedSpaces()); + debug("Peds left %d\n", ms_pPedPool->GetNoOfUsedSpaces()); + debug("Vehicles left %d\n", ms_pVehiclePool->GetNoOfUsedSpaces()); + debug("Buildings left %d\n", ms_pBuildingPool->GetNoOfUsedSpaces()); + debug("Treadables left %d\n", ms_pTreadablePool->GetNoOfUsedSpaces()); + debug("Objects left %d\n", ms_pObjectPool->GetNoOfUsedSpaces()); + debug("Dummys left %d\n", ms_pDummyPool->GetNoOfUsedSpaces()); + debug("AudioScriptObjects left %d\n", ms_pAudioScriptObjectPool->GetNoOfUsedSpaces()); + printf("Shutdown pool started\n"); + + delete ms_pPtrNodePool; + delete ms_pEntryInfoNodePool; + delete ms_pPedPool; + delete ms_pVehiclePool; + delete ms_pBuildingPool; + delete ms_pTreadablePool; + delete ms_pObjectPool; + delete ms_pDummyPool; + delete ms_pAudioScriptObjectPool; + + printf("Shutdown pool done\n"); +} + +int32 CPools::GetPedRef(CPed *ped) { return ms_pPedPool->GetIndex(ped); } +CPed *CPools::GetPed(int32 handle) { return ms_pPedPool->GetAt(handle); } +int32 CPools::GetVehicleRef(CVehicle *vehicle) { return ms_pVehiclePool->GetIndex(vehicle); } +CVehicle *CPools::GetVehicle(int32 handle) { return ms_pVehiclePool->GetAt(handle); } +int32 CPools::GetObjectRef(CObject *object) { return ms_pObjectPool->GetIndex(object); } +CObject *CPools::GetObject(int32 handle) { return ms_pObjectPool->GetAt(handle); } + +void +CPools::CheckPoolsEmpty() +{ + assert(ms_pPedPool->GetNoOfUsedSpaces() == 0); + assert(ms_pVehiclePool->GetNoOfUsedSpaces() == 0); + printf("pools have been cleared\n"); +} + + +void +CPools::MakeSureSlotInObjectPoolIsEmpty(int32 slot) +{ + if (ms_pObjectPool->GetIsFree(slot)) return; + + CObject *object = ms_pObjectPool->GetSlot(slot); + if (object->ObjectCreatedBy == TEMP_OBJECT) { + CWorld::Remove(object); + delete object; + } else if (!CProjectileInfo::RemoveIfThisIsAProjectile(object)) { + // relocate to another slot?? + CObject *newObject = new CObject(object->GetModelIndex(), false); + CWorld::Remove(object); +#if 0 // todo better + *newObject = *object; +#else + memcpy(newObject, object, ms_pObjectPool->GetMaxEntrySize()); +#endif + CWorld::Add(newObject); + object->m_rwObject = nil; + delete object; + newObject->m_pFirstReference = nil; + } +} + +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); + +void CPools::LoadVehiclePool(uint8* buf, uint32 size) +{ +INITSAVEBUF + int nNumCars, nNumBoats; + ReadSaveBuf(&nNumCars, buf); + ReadSaveBuf(&nNumBoats, buf); + for (int i = 0; i < nNumCars + nNumBoats; i++) { + uint32 type; + int16 model; + int32 slot; + + ReadSaveBuf(&type, buf); + ReadSaveBuf(&model, buf); + CStreaming::RequestModel(model, STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + ReadSaveBuf(&slot, buf); + CVehicle* pVehicle; +#ifdef COMPATIBLE_SAVES + if (type == VEHICLE_TYPE_BOAT) + pVehicle = new(slot) CBoat(model, RANDOM_VEHICLE); + else if (type == VEHICLE_TYPE_CAR) + pVehicle = new(slot) CAutomobile(model, RANDOM_VEHICLE); + else + assert(0); + --CCarCtrl::NumRandomCars; + pVehicle->Load(buf); + CWorld::Add(pVehicle); +#else + char* vbuf = new char[Max(CAutomobile::nSaveStructSize, CBoat::nSaveStructSize)]; + if (type == VEHICLE_TYPE_BOAT) { + memcpy(vbuf, buf, sizeof(CBoat)); + SkipSaveBuf(buf, sizeof(CBoat)); + CBoat* pBoat = new(slot) CBoat(model, RANDOM_VEHICLE); + pVehicle = pBoat; + --CCarCtrl::NumRandomCars; + } + else if (type == VEHICLE_TYPE_CAR) { + memcpy(vbuf, buf, sizeof(CAutomobile)); + SkipSaveBuf(buf, sizeof(CAutomobile)); + CStreaming::RequestModel(model, 0); // is it needed? + CStreaming::LoadAllRequestedModels(false); + CAutomobile* pAutomobile = new(slot) CAutomobile(model, RANDOM_VEHICLE); + pVehicle = pAutomobile; + CCarCtrl::NumRandomCars--; // why? + pAutomobile->Damage = ((CAutomobile*)vbuf)->Damage; + pAutomobile->SetupDamageAfterLoad(); + } + else + assert(0); + CVehicle* pBufferVehicle = (CVehicle*)vbuf; + pVehicle->GetMatrix() = pBufferVehicle->GetMatrix(); + pVehicle->VehicleCreatedBy = pBufferVehicle->VehicleCreatedBy; + pVehicle->m_currentColour1 = pBufferVehicle->m_currentColour1; + pVehicle->m_currentColour2 = pBufferVehicle->m_currentColour2; + pVehicle->m_nAlarmState = pBufferVehicle->m_nAlarmState; + pVehicle->m_nNumMaxPassengers = pBufferVehicle->m_nNumMaxPassengers; + pVehicle->field_1D0[0] = pBufferVehicle->field_1D0[0]; + pVehicle->field_1D0[1] = pBufferVehicle->field_1D0[1]; + pVehicle->field_1D0[2] = pBufferVehicle->field_1D0[2]; + pVehicle->field_1D0[3] = pBufferVehicle->field_1D0[3]; + pVehicle->m_fSteerAngle = pBufferVehicle->m_fSteerAngle; + pVehicle->m_fGasPedal = pBufferVehicle->m_fGasPedal; + pVehicle->m_fBrakePedal = pBufferVehicle->m_fBrakePedal; + pVehicle->bIsLawEnforcer = pBufferVehicle->bIsLawEnforcer; + pVehicle->bIsLocked = pBufferVehicle->bIsLocked; + pVehicle->bEngineOn = pBufferVehicle->bEngineOn; + pVehicle->bIsHandbrakeOn = pBufferVehicle->bIsHandbrakeOn; + pVehicle->bLightsOn = pBufferVehicle->bLightsOn; + pVehicle->bFreebies = pBufferVehicle->bFreebies; + pVehicle->m_fHealth = pBufferVehicle->m_fHealth; + pVehicle->m_nCurrentGear = pBufferVehicle->m_nCurrentGear; + pVehicle->m_fChangeGearTime = pBufferVehicle->m_fChangeGearTime; + pVehicle->m_nTimeOfDeath = pBufferVehicle->m_nTimeOfDeath; +#ifdef FIX_BUGS //must be copypaste + pVehicle->m_nBombTimer = pBufferVehicle->m_nBombTimer; +#else + pVehicle->m_nTimeOfDeath = pBufferVehicle->m_nTimeOfDeath; +#endif + pVehicle->m_nDoorLock = pBufferVehicle->m_nDoorLock; + pVehicle->SetStatus(pBufferVehicle->GetStatus()); + pVehicle->SetType(pBufferVehicle->GetType()); + (pVehicle->GetAddressOfEntityProperties())[0] = (pBufferVehicle->GetAddressOfEntityProperties())[0]; + (pVehicle->GetAddressOfEntityProperties())[1] = (pBufferVehicle->GetAddressOfEntityProperties())[1]; + pVehicle->AutoPilot = pBufferVehicle->AutoPilot; + CWorld::Add(pVehicle); + delete[] vbuf; +#endif + } +VALIDATESAVEBUF(size) +} + +void CPools::SaveVehiclePool(uint8* buf, uint32* size) +{ +INITSAVEBUF + int nNumCars = 0; + int nNumBoats = 0; + int nPoolSize = GetVehiclePool()->GetSize(); + for (int i = 0; i < nPoolSize; i++) { + CVehicle* pVehicle = GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + bool bHasPassenger = false; + for (int j = 0; j < ARRAY_SIZE(pVehicle->pPassengers); j++) { + if (pVehicle->pPassengers[j]) + bHasPassenger = true; + } +#ifdef MISSION_REPLAY + bool bForceSaving = CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pMyVehicle == pVehicle && IsQuickSave; +#ifdef FIX_BUGS + if ((!pVehicle->pDriver && !bHasPassenger) || bForceSaving) { +#else + if (!pVehicle->pDriver && !bHasPassenger) { +#endif + if (pVehicle->IsCar() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) + ++nNumCars; + if (pVehicle->IsBoat() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) + ++nNumBoats; +#else + if (!pVehicle->pDriver && !bHasPassenger) { + if (pVehicle->IsCar() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) + ++nNumCars; + if (pVehicle->IsBoat() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) + ++nNumBoats; +#endif + } + } + *size = nNumCars * (sizeof(uint32) + sizeof(int16) + sizeof(int32) + CAutomobile::nSaveStructSize) + sizeof(int) + + nNumBoats * (sizeof(uint32) + sizeof(int16) + sizeof(int32) + CBoat::nSaveStructSize) + sizeof(int); + WriteSaveBuf(buf, nNumCars); + WriteSaveBuf(buf, nNumBoats); + for (int i = 0; i < nPoolSize; i++) { + CVehicle* pVehicle = GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + bool bHasPassenger = false; + for (int j = 0; j < ARRAY_SIZE(pVehicle->pPassengers); j++) { + if (pVehicle->pPassengers[j]) + bHasPassenger = true; + } +#ifdef MISSION_REPLAY + bool bForceSaving = CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pMyVehicle == pVehicle && IsQuickSave; +#endif +#if defined FIX_BUGS && defined MISSION_REPLAY + if ((!pVehicle->pDriver && !bHasPassenger) || bForceSaving) { +#else + if (!pVehicle->pDriver && !bHasPassenger) { +#endif +#ifdef COMPATIBLE_SAVES +#ifdef MISSION_REPLAY + if ((pVehicle->IsCar() || pVehicle->IsBoat()) && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) { +#else + if ((pVehicle->IsCar() || pVehicle->IsBoat()) && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) { +#endif + WriteSaveBuf(buf, pVehicle->m_vehType); + WriteSaveBuf(buf, pVehicle->GetModelIndex()); + WriteSaveBuf(buf, GetVehicleRef(pVehicle)); + pVehicle->Save(buf); + } +#else +#ifdef MISSION_REPLAY + if (pVehicle->IsCar() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) { +#else + if (pVehicle->IsCar() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) { +#endif + WriteSaveBuf(buf, pVehicle->m_vehType); + WriteSaveBuf(buf, pVehicle->GetModelIndex()); + WriteSaveBuf(buf, GetVehicleRef(pVehicle)); + memcpy(buf, pVehicle, sizeof(CAutomobile)); + SkipSaveBuf(buf, sizeof(CAutomobile)); + } +#ifdef MISSION_REPLAY + if (pVehicle->IsBoat() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) { +#else + if (pVehicle->IsBoat() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) { +#endif + WriteSaveBuf(buf, pVehicle->m_vehType); + WriteSaveBuf(buf, pVehicle->GetModelIndex()); + WriteSaveBuf(buf, GetVehicleRef(pVehicle)); + memcpy(buf, pVehicle, sizeof(CBoat)); + SkipSaveBuf(buf, sizeof(CBoat)); + } +#endif + } + } +VALIDATESAVEBUF(*size) +} + +void CPools::SaveObjectPool(uint8* buf, uint32* size) +{ +INITSAVEBUF + CProjectileInfo::RemoveAllProjectiles(); + CObject::DeleteAllTempObjects(); + int nObjects = 0; + int nPoolSize = GetObjectPool()->GetSize(); + for (int i = 0; i < nPoolSize; i++) { + CObject* pObject = GetObjectPool()->GetSlot(i); + if (!pObject) + continue; + if (pObject->ObjectCreatedBy == MISSION_OBJECT) + ++nObjects; + } + *size = nObjects * (sizeof(int16) + sizeof(int) + sizeof(CCompressedMatrix) + + sizeof(float) + sizeof(CCompressedMatrix) + sizeof(int8) + 7 * sizeof(bool) + sizeof(float) + + sizeof(int8) + sizeof(int8) + sizeof(uint32) + 2 * sizeof(uint32)) + sizeof(int); + CopyToBuf(buf, nObjects); + for (int i = 0; i < nPoolSize; i++) { + CObject* pObject = GetObjectPool()->GetSlot(i); + if (!pObject) + continue; + if (pObject->ObjectCreatedBy == MISSION_OBJECT) { + bool bIsPickup = pObject->bIsPickup; + bool bPickupObjWithMessage = pObject->bPickupObjWithMessage; + bool bOutOfStock = pObject->bOutOfStock; + bool bGlassCracked = pObject->bGlassCracked; + bool bGlassBroken = pObject->bGlassBroken; + bool bHasBeenDamaged = pObject->bHasBeenDamaged; + bool bUseVehicleColours = pObject->bUseVehicleColours; + CCompressedMatrix tmp; + CopyToBuf(buf, pObject->m_modelIndex); + int32 ref = GetObjectRef(pObject); + CopyToBuf(buf, ref); + tmp.CompressFromFullMatrix(pObject->GetMatrix()); + CopyToBuf(buf, tmp); + CopyToBuf(buf, pObject->m_fUprootLimit); + tmp.CompressFromFullMatrix(pObject->m_objectMatrix); + CopyToBuf(buf, tmp); + CopyToBuf(buf, pObject->ObjectCreatedBy); + CopyToBuf(buf, bIsPickup); + CopyToBuf(buf, bPickupObjWithMessage); + CopyToBuf(buf, bOutOfStock); + CopyToBuf(buf, bGlassCracked); + CopyToBuf(buf, bGlassBroken); + CopyToBuf(buf, bHasBeenDamaged); + CopyToBuf(buf, bUseVehicleColours); + CopyToBuf(buf, pObject->m_fCollisionDamageMultiplier); + CopyToBuf(buf, pObject->m_nCollisionDamageEffect); + CopyToBuf(buf, pObject->m_nSpecialCollisionResponseCases); + CopyToBuf(buf, pObject->m_nEndOfLifeTime); +#ifdef COMPATIBLE_SAVES + pObject->SaveEntityFlags(buf); +#else + CopyToBuf(buf, (pObject->GetAddressOfEntityProperties())[0]); + CopyToBuf(buf, (pObject->GetAddressOfEntityProperties())[1]); +#endif + } + } +VALIDATESAVEBUF(*size) +} + +void CPools::LoadObjectPool(uint8* buf, uint32 size) +{ +INITSAVEBUF + int nObjects; + CopyFromBuf(buf, nObjects); + for (int i = 0; i < nObjects; i++) { + int16 mi; + CopyFromBuf(buf, mi); + int ref; + CopyFromBuf(buf, ref); + char* obuf = new char[sizeof(CObject)]; + CObject* pBufferObject = (CObject*)obuf; + CCompressedMatrix tmp; + CopyFromBuf(buf, tmp); + tmp.DecompressIntoFullMatrix(pBufferObject->GetMatrix()); + CopyFromBuf(buf, pBufferObject->m_fUprootLimit); + CopyFromBuf(buf, tmp); + tmp.DecompressIntoFullMatrix(pBufferObject->m_objectMatrix); + CopyFromBuf(buf, pBufferObject->ObjectCreatedBy); + int8 bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bIsPickup = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bPickupObjWithMessage = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bOutOfStock = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bGlassCracked = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bGlassBroken = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bHasBeenDamaged = bitFlag; + CopyFromBuf(buf, bitFlag); + pBufferObject->bUseVehicleColours = bitFlag; + CopyFromBuf(buf, pBufferObject->m_fCollisionDamageMultiplier); + CopyFromBuf(buf, pBufferObject->m_nCollisionDamageEffect); + CopyFromBuf(buf, pBufferObject->m_nSpecialCollisionResponseCases); + CopyFromBuf(buf, pBufferObject->m_nEndOfLifeTime); +#ifndef COMPATIBLE_SAVES + CopyFromBuf(buf, (pBufferObject->GetAddressOfEntityProperties())[0]); + CopyFromBuf(buf, (pBufferObject->GetAddressOfEntityProperties())[1]); +#endif + if (GetObjectPool()->GetSlot(ref >> 8)) + CPopulation::ConvertToDummyObject(GetObjectPool()->GetSlot(ref >> 8)); + CObject* pObject = new(ref) CObject(mi, false); + pObject->GetMatrix() = pBufferObject->GetMatrix(); +#ifdef COMPATIBLE_SAVES + pObject->LoadEntityFlags(buf); +#endif + pObject->m_fUprootLimit = pBufferObject->m_fUprootLimit; + pObject->m_objectMatrix = pBufferObject->m_objectMatrix; + pObject->ObjectCreatedBy = pBufferObject->ObjectCreatedBy; + pObject->bIsPickup = pBufferObject->bIsPickup; + pObject->bPickupObjWithMessage = pBufferObject->bPickupObjWithMessage; + pObject->bOutOfStock = pBufferObject->bOutOfStock; + pObject->bGlassCracked = pBufferObject->bGlassCracked; + pObject->bGlassBroken = pBufferObject->bGlassBroken; + pObject->bHasBeenDamaged = pBufferObject->bHasBeenDamaged; + pObject->bUseVehicleColours = pBufferObject->bUseVehicleColours; + pObject->m_fCollisionDamageMultiplier = pBufferObject->m_fCollisionDamageMultiplier; + pObject->m_nCollisionDamageEffect = pBufferObject->m_nCollisionDamageEffect; + pObject->m_nSpecialCollisionResponseCases = pBufferObject->m_nSpecialCollisionResponseCases; + pObject->m_nEndOfLifeTime = pBufferObject->m_nEndOfLifeTime; +#ifndef COMPATIBLE_SAVES + (pObject->GetAddressOfEntityProperties())[0] = (pBufferObject->GetAddressOfEntityProperties())[0]; + (pObject->GetAddressOfEntityProperties())[1] = (pBufferObject->GetAddressOfEntityProperties())[1]; +#endif + pObject->bHasCollided = false; + CWorld::Add(pObject); + delete[] obuf; + } +VALIDATESAVEBUF(size) +} + +void CPools::SavePedPool(uint8* buf, uint32* size) +{ +INITSAVEBUF + int nNumPeds = 0; + int nPoolSize = GetPedPool()->GetSize(); + for (int i = 0; i < nPoolSize; i++) { + CPed* pPed = GetPedPool()->GetSlot(i); + if (!pPed) + continue; +#ifdef MISSION_REPLAY + if ((!pPed->bInVehicle || (pPed == CWorld::Players[CWorld::PlayerInFocus].m_pPed && IsQuickSave)) && pPed->m_nPedType == PEDTYPE_PLAYER1) +#else + if (!pPed->bInVehicle && pPed->m_nPedType == PEDTYPE_PLAYER1) +#endif + nNumPeds++; + } + *size = sizeof(int) + nNumPeds * (sizeof(uint32) + sizeof(int16) + sizeof(int) + CPlayerPed::nSaveStructSize + + sizeof(CWanted::MaximumWantedLevel) + sizeof(CWanted::nMaximumWantedLevel) + MAX_MODEL_NAME); + CopyToBuf(buf, nNumPeds); + for (int i = 0; i < nPoolSize; i++) { + CPed* pPed = GetPedPool()->GetSlot(i); + if (!pPed) + continue; +#ifdef MISSION_REPLAY + if ((!pPed->bInVehicle || (pPed == CWorld::Players[CWorld::PlayerInFocus].m_pPed && IsQuickSave)) && pPed->m_nPedType == PEDTYPE_PLAYER1) { +#else + if (!pPed->bInVehicle && pPed->m_nPedType == PEDTYPE_PLAYER1) { +#endif + CopyToBuf(buf, pPed->m_nPedType); + CopyToBuf(buf, pPed->m_modelIndex); + int32 ref = GetPedRef(pPed); + CopyToBuf(buf, ref); +#ifdef COMPATIBLE_SAVES + pPed->Save(buf); +#else + memcpy(buf, pPed, sizeof(CPlayerPed)); + SkipSaveBuf(buf, sizeof(CPlayerPed)); +#endif + CopyToBuf(buf, CWanted::MaximumWantedLevel); + CopyToBuf(buf, CWanted::nMaximumWantedLevel); + memcpy(buf, CModelInfo::GetModelInfo(pPed->GetModelIndex())->GetModelName(), MAX_MODEL_NAME); + SkipSaveBuf(buf, MAX_MODEL_NAME); + } + } +VALIDATESAVEBUF(*size); +#undef CopyToBuf +} + +void CPools::LoadPedPool(uint8* buf, uint32 size) +{ +INITSAVEBUF + int nPeds; + CopyFromBuf(buf, nPeds); + for (int i = 0; i < nPeds; i++) { + uint32 pedtype; + int16 model; + int ref; + + CopyFromBuf(buf, pedtype); + CopyFromBuf(buf, model); + CopyFromBuf(buf, ref); +#ifdef COMPATIBLE_SAVES + CPed* pPed; + + char name[MAX_MODEL_NAME]; + // Unfortunate hack: player model is stored after ped structure. + // It could be avoided by just using "player" because in practice it is always true. + memcpy(name, buf + CPlayerPed::nSaveStructSize + 2 * sizeof(int32), MAX_MODEL_NAME); + CStreaming::RequestSpecialModel(model, name, STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + + if (pedtype == PEDTYPE_PLAYER1) + pPed = new(ref) CPlayerPed(); + else + assert(0); + + pPed->Load(buf); + if (pedtype == PEDTYPE_PLAYER1) { + CopyFromBuf(buf, CWanted::MaximumWantedLevel); + CopyFromBuf(buf, CWanted::nMaximumWantedLevel); + SkipSaveBuf(buf, MAX_MODEL_NAME); + } + + if (pedtype == PEDTYPE_PLAYER1) { + pPed->m_wepAccuracy = 100; + CWorld::Players[0].m_pPed = (CPlayerPed*)pPed; + } + CWorld::Add(pPed); +#else + char* pbuf = new char[sizeof(CPlayerPed)]; + CPlayerPed* pBufferPlayer = (CPlayerPed*)pbuf; + CPed* pPed; + char name[MAX_MODEL_NAME]; + // the code implies that there was idea to load non-player ped + if (pedtype == PEDTYPE_PLAYER1) { // always true + memcpy(pbuf, buf, sizeof(CPlayerPed)); + SkipSaveBuf(buf, sizeof(CPlayerPed)); + CopyFromBuf(buf, CWanted::MaximumWantedLevel); + CopyFromBuf(buf, CWanted::nMaximumWantedLevel); + CopyFromBuf(buf, name); + } + CStreaming::RequestSpecialModel(model, name, STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + if (pedtype == PEDTYPE_PLAYER1) { + CPlayerPed* pPlayerPed = new(ref) CPlayerPed(); + for (int i = 0; i < ARRAY_SIZE(pPlayerPed->m_nTargettableObjects); i++) + pPlayerPed->m_nTargettableObjects[i] = pBufferPlayer->m_nTargettableObjects[i]; + pPlayerPed->m_fMaxStamina = pBufferPlayer->m_fMaxStamina; + pPed = pPlayerPed; + } + pPed->SetPosition(pBufferPlayer->GetPosition()); + pPed->m_fHealth = pBufferPlayer->m_fHealth; + pPed->m_fArmour = pBufferPlayer->m_fArmour; + pPed->CharCreatedBy = pBufferPlayer->CharCreatedBy; + pPed->m_currentWeapon = 0; + pPed->m_maxWeaponTypeAllowed = pBufferPlayer->m_maxWeaponTypeAllowed; + for (int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) + pPed->m_weapons[i] = pBufferPlayer->m_weapons[i]; + + if (pedtype == PEDTYPE_PLAYER1) { + pPed->m_wepAccuracy = 100; + CWorld::Players[0].m_pPed = (CPlayerPed*)pPed; + } + CWorld::Add(pPed); + delete[] pbuf; +#endif + } +VALIDATESAVEBUF(size) +} + +#undef CopyFromBuf +#undef CopyToBuf \ No newline at end of file diff --git a/src/core/Pools.h b/src/core/Pools.h new file mode 100644 index 0000000..b0ba659 --- /dev/null +++ b/src/core/Pools.h @@ -0,0 +1,61 @@ +#pragma once + +#include "templates.h" +#include "Lists.h" +#include "Treadable.h" +#include "Object.h" +#include "CutsceneHead.h" +#include "PlayerPed.h" +#include "Automobile.h" +#include "DummyPed.h" +#include "AudioScriptObject.h" + +typedef CPool CCPtrNodePool; +typedef CPool CEntryInfoNodePool; +typedef CPool CPedPool; +typedef CPool CVehiclePool; +typedef CPool CBuildingPool; +typedef CPool CTreadablePool; +typedef CPool CObjectPool; +typedef CPool CDummyPool; +typedef CPool CAudioScriptObjectPool; + +class CPools +{ + static CCPtrNodePool *ms_pPtrNodePool; + static CEntryInfoNodePool *ms_pEntryInfoNodePool; + static CPedPool *ms_pPedPool; + static CVehiclePool *ms_pVehiclePool; + static CBuildingPool *ms_pBuildingPool; + static CTreadablePool *ms_pTreadablePool; + static CObjectPool *ms_pObjectPool; + static CDummyPool *ms_pDummyPool; + static CAudioScriptObjectPool *ms_pAudioScriptObjectPool; +public: + static CCPtrNodePool *GetPtrNodePool(void) { return ms_pPtrNodePool; } + static CEntryInfoNodePool *GetEntryInfoNodePool(void) { return ms_pEntryInfoNodePool; } + static CPedPool *GetPedPool(void) { return ms_pPedPool; } + static CVehiclePool *GetVehiclePool(void) { return ms_pVehiclePool; } + static CBuildingPool *GetBuildingPool(void) { return ms_pBuildingPool; } + static CTreadablePool *GetTreadablePool(void) { return ms_pTreadablePool; } + static CObjectPool *GetObjectPool(void) { return ms_pObjectPool; } + static CDummyPool *GetDummyPool(void) { return ms_pDummyPool; } + static CAudioScriptObjectPool *GetAudioScriptObjectPool(void) { return ms_pAudioScriptObjectPool; } + + static void Initialise(void); + static void ShutDown(void); + static int32 GetPedRef(CPed *ped); + static CPed *GetPed(int32 handle); + static int32 GetVehicleRef(CVehicle *vehicle); + static CVehicle *GetVehicle(int32 handle); + static int32 GetObjectRef(CObject *object); + static CObject *GetObject(int32 handle); + static void CheckPoolsEmpty(); + static void MakeSureSlotInObjectPoolIsEmpty(int32 slot); + static void LoadObjectPool(uint8 *buf, uint32 size); + static void LoadPedPool(uint8 *buf, uint32 size); + static void LoadVehiclePool(uint8 *buf, uint32 size); + static void SaveObjectPool(uint8 *buf, uint32 *size); + static void SavePedPool(uint8 *buf, uint32 *size); + static void SaveVehiclePool(uint8 *buf, uint32 *size); +}; diff --git a/src/core/Profile.cpp b/src/core/Profile.cpp new file mode 100644 index 0000000..0aa18ab --- /dev/null +++ b/src/core/Profile.cpp @@ -0,0 +1,71 @@ +#include "common.h" +#include "Profile.h" + +#ifndef MASTER +float CProfile::ms_afStartTime[NUM_PROFILES]; +float CProfile::ms_afCumulativeTime[NUM_PROFILES]; +float CProfile::ms_afEndTime[NUM_PROFILES]; +float CProfile::ms_afMaxEndTime[NUM_PROFILES]; +float CProfile::ms_afMaxCumulativeTime[NUM_PROFILES]; +Const char *CProfile::ms_pProfileString[NUM_PROFILES]; +RwRGBA CProfile::ms_aBarColours[NUM_PROFILES]; + +void CProfile::Initialise() +{ + ms_afMaxEndTime[PROFILE_FRAME_RATE] = 0.0f; + ms_afMaxEndTime[PROFILE_PHYSICS] = 0.0f; + ms_afMaxEndTime[PROFILE_COLLISION] = 0.0f; + ms_afMaxEndTime[PROFILE_PED_AI] = 0.0f; + ms_afMaxEndTime[PROFILE_PROCESSING_TIME] = 0.0f; + ms_afMaxEndTime[PROFILE_RENDERING_TIME] = 0.0f; + ms_afMaxEndTime[PROFILE_TOTAL] = 0.0f; + + ms_pProfileString[PROFILE_FRAME_RATE] = "Frame rate"; + ms_pProfileString[PROFILE_PHYSICS] = "Physics"; + ms_pProfileString[PROFILE_COLLISION] = "Collision"; + ms_pProfileString[PROFILE_PED_AI] = "Ped AI"; + ms_pProfileString[PROFILE_PROCESSING_TIME] = "Processing time"; + ms_pProfileString[PROFILE_RENDERING_TIME] = "Rendering time"; + ms_pProfileString[PROFILE_TOTAL] = "Total"; + + ms_afMaxCumulativeTime[PROFILE_FRAME_RATE] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_PHYSICS] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_COLLISION] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_PED_AI] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_PROCESSING_TIME] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_RENDERING_TIME] = 0.0f; + ms_afMaxCumulativeTime[PROFILE_TOTAL] = 0.0f; + + ms_aBarColours[PROFILE_PHYSICS] = { 0, 127, 255, 255 }; + ms_aBarColours[PROFILE_COLLISION] = { 0, 255, 255, 255 }; + ms_aBarColours[PROFILE_PED_AI] = { 255, 0, 0, 255 }; + ms_aBarColours[PROFILE_PROCESSING_TIME] = { 0, 255, 0, 255 }; + ms_aBarColours[PROFILE_RENDERING_TIME] = { 0, 0, 255, 255 }; + ms_aBarColours[PROFILE_TOTAL] = { 255, 255, 255, 255 }; +} + +void CProfile::SuspendProfile(eProfile profile) +{ + ms_afEndTime[profile] = -ms_afStartTime[profile]; + ms_afCumulativeTime[profile] -= ms_afStartTime[profile]; +} + +void CProfile::ShowResults() +{ + ms_afMaxEndTime[PROFILE_FRAME_RATE] = Max(ms_afMaxEndTime[PROFILE_FRAME_RATE], ms_afEndTime[PROFILE_FRAME_RATE]); + ms_afMaxEndTime[PROFILE_PHYSICS] = Max(ms_afMaxEndTime[PROFILE_PHYSICS], ms_afEndTime[PROFILE_PHYSICS]); + ms_afMaxEndTime[PROFILE_COLLISION] = Max(ms_afMaxEndTime[PROFILE_COLLISION], ms_afEndTime[PROFILE_COLLISION]); + ms_afMaxEndTime[PROFILE_PED_AI] = Max(ms_afMaxEndTime[PROFILE_PED_AI], ms_afEndTime[PROFILE_PED_AI]); + ms_afMaxEndTime[PROFILE_PROCESSING_TIME] = Max(ms_afMaxEndTime[PROFILE_PROCESSING_TIME], ms_afEndTime[PROFILE_PROCESSING_TIME]); + ms_afMaxEndTime[PROFILE_RENDERING_TIME] = Max(ms_afMaxEndTime[PROFILE_RENDERING_TIME], ms_afEndTime[PROFILE_RENDERING_TIME]); + ms_afMaxEndTime[PROFILE_TOTAL] = Max(ms_afMaxEndTime[PROFILE_TOTAL], ms_afEndTime[PROFILE_TOTAL]); + + ms_afMaxCumulativeTime[PROFILE_FRAME_RATE] = Max(ms_afMaxCumulativeTime[PROFILE_FRAME_RATE], ms_afCumulativeTime[PROFILE_FRAME_RATE]); + ms_afMaxCumulativeTime[PROFILE_PHYSICS] = Max(ms_afMaxCumulativeTime[PROFILE_PHYSICS], ms_afCumulativeTime[PROFILE_PHYSICS]); + ms_afMaxCumulativeTime[PROFILE_COLLISION] = Max(ms_afMaxCumulativeTime[PROFILE_COLLISION], ms_afCumulativeTime[PROFILE_COLLISION]); + ms_afMaxCumulativeTime[PROFILE_PED_AI] = Max(ms_afMaxCumulativeTime[PROFILE_PED_AI], ms_afCumulativeTime[PROFILE_PED_AI]); + ms_afMaxCumulativeTime[PROFILE_PROCESSING_TIME] = Max(ms_afMaxCumulativeTime[PROFILE_PROCESSING_TIME], ms_afCumulativeTime[PROFILE_PROCESSING_TIME]); + ms_afMaxCumulativeTime[PROFILE_RENDERING_TIME] = Max(ms_afMaxCumulativeTime[PROFILE_RENDERING_TIME], ms_afCumulativeTime[PROFILE_RENDERING_TIME]); + ms_afMaxCumulativeTime[PROFILE_TOTAL] = Max(ms_afMaxCumulativeTime[PROFILE_TOTAL], ms_afCumulativeTime[PROFILE_TOTAL]); +} +#endif diff --git a/src/core/Profile.h b/src/core/Profile.h new file mode 100644 index 0000000..4fe693a --- /dev/null +++ b/src/core/Profile.h @@ -0,0 +1,28 @@ +#pragma once + +enum eProfile +{ + PROFILE_FRAME_RATE, + PROFILE_PHYSICS, + PROFILE_COLLISION, + PROFILE_PED_AI, + PROFILE_PROCESSING_TIME, + PROFILE_RENDERING_TIME, + PROFILE_TOTAL, + NUM_PROFILES, +}; + +class CProfile +{ + static float ms_afStartTime[NUM_PROFILES]; + static float ms_afCumulativeTime[NUM_PROFILES]; + static float ms_afEndTime[NUM_PROFILES]; + static float ms_afMaxEndTime[NUM_PROFILES]; + static float ms_afMaxCumulativeTime[NUM_PROFILES]; + static Const char *ms_pProfileString[NUM_PROFILES]; + static RwRGBA ms_aBarColours[NUM_PROFILES]; +public: + static void Initialise(); + static void SuspendProfile(eProfile profile); + static void ShowResults(); +}; diff --git a/src/core/Radar.cpp b/src/core/Radar.cpp new file mode 100644 index 0000000..b29c19e --- /dev/null +++ b/src/core/Radar.cpp @@ -0,0 +1,1568 @@ +#if (!defined(GTA_PS2_STUFF) && defined(RWLIBS)) || defined(__MWERKS__) +#define WITHD3D +#endif +#include "config.h" +#include "common.h" + +#include "RwHelper.h" +#include "Radar.h" +#include "Camera.h" +#include "Hud.h" +#include "World.h" +#include "Frontend.h" +#include "General.h" +#include "Vehicle.h" +#include "Pools.h" +#include "Script.h" +#include "TxdStore.h" +#include "World.h" +#include "SaveBuf.h" +#include "Streaming.h" +#include "SpecialFX.h" + +float CRadar::m_radarRange; +sRadarTrace CRadar::ms_RadarTrace[NUMRADARBLIPS]; +CVector2D vec2DRadarOrigin; +int32 gRadarTxdIds[64]; + +CSprite2d CRadar::AsukaSprite; +CSprite2d CRadar::BombSprite; +CSprite2d CRadar::CatSprite; +CSprite2d CRadar::CentreSprite; +CSprite2d CRadar::CopcarSprite; +CSprite2d CRadar::DonSprite; +CSprite2d CRadar::EightSprite; +CSprite2d CRadar::ElSprite; +CSprite2d CRadar::IceSprite; +CSprite2d CRadar::JoeySprite; +CSprite2d CRadar::KenjiSprite; +CSprite2d CRadar::LizSprite; +CSprite2d CRadar::LuigiSprite; +CSprite2d CRadar::NorthSprite; +CSprite2d CRadar::RaySprite; +CSprite2d CRadar::SalSprite; +CSprite2d CRadar::SaveSprite; +CSprite2d CRadar::SpraySprite; +CSprite2d CRadar::TonySprite; +CSprite2d CRadar::WeaponSprite; +#ifdef MENU_MAP +CSprite2d CRadar::WaypointSprite; +#endif + +CSprite2d *CRadar::RadarSprites[RADAR_SPRITE_COUNT] = { + nil, + &AsukaSprite, + &BombSprite, + &CatSprite, + &CentreSprite, + &CopcarSprite, + &DonSprite, + &EightSprite, + &ElSprite, + &IceSprite, + &JoeySprite, + &KenjiSprite, + &LizSprite, + &LuigiSprite, + &NorthSprite, + &RaySprite, + &SalSprite, + &SaveSprite, + &SpraySprite, + &TonySprite, + &WeaponSprite, +#ifdef MENU_MAP + &WaypointSprite +#endif +}; + +// Why this doesn't coincide with world coordinates i don't know +#define RADAR_MIN_X (-2000.0f) +#define RADAR_MIN_Y (-2000.0f) +#define RADAR_MAX_X (2000.0f) +#define RADAR_MAX_Y (2000.0f) +#define RADAR_SIZE_X (RADAR_MAX_X - RADAR_MIN_X) +#define RADAR_SIZE_Y (RADAR_MAX_Y - RADAR_MIN_Y) + +#define RADAR_NUM_TILES (8) +#define RADAR_TILE_SIZE (RADAR_SIZE_X / RADAR_NUM_TILES) +static_assert(RADAR_TILE_SIZE == (RADAR_SIZE_Y / RADAR_NUM_TILES), "CRadar: not a square"); + +#define RADAR_MIN_RANGE (120.0f) +#define RADAR_MAX_RANGE (350.0f) +#define RADAR_MIN_SPEED (0.3f) +#define RADAR_MAX_SPEED (0.9f) + +#ifdef MENU_MAP +int CRadar::TargetMarkerId = -1; +CVector CRadar::TargetMarkerPos; +#endif + +// taken from VC +float CRadar::cachedCos; +float CRadar::cachedSin; + +void ClipRadarTileCoords(int32 &x, int32 &y) +{ + if (x < 0) + x = 0; + if (x > RADAR_NUM_TILES-1) + x = RADAR_NUM_TILES-1; + if (y < 0) + y = 0; + if (y > RADAR_NUM_TILES-1) + y = RADAR_NUM_TILES-1; +} + +void RequestMapSection(int32 x, int32 y) +{ + ClipRadarTileCoords(x, y); + CStreaming::RequestTxd(gRadarTxdIds[x + RADAR_NUM_TILES * y], STREAMFLAGS_DONT_REMOVE | STREAMFLAGS_DEPENDENCY); +} + +void RemoveMapSection(int32 x, int32 y) +{ + if (x >= 0 && x <= RADAR_NUM_TILES - 1 && y >= 0 && y <= RADAR_NUM_TILES - 1) + CStreaming::RemoveTxd(gRadarTxdIds[x + RADAR_NUM_TILES * y]); +} + +// Transform from section indices to world coordinates +void GetTextureCorners(int32 x, int32 y, CVector2D *out) +{ + x = x - RADAR_NUM_TILES/2; + y = -(y - RADAR_NUM_TILES/2); + + // bottom left + out[0].x = RADAR_TILE_SIZE * (x); + out[0].y = RADAR_TILE_SIZE * (y - 1); + + // bottom right + out[1].x = RADAR_TILE_SIZE * (x + 1); + out[1].y = RADAR_TILE_SIZE * (y - 1); + + // top right + out[2].x = RADAR_TILE_SIZE * (x + 1); + out[2].y = RADAR_TILE_SIZE * (y); + + // top left + out[3].x = RADAR_TILE_SIZE * (x); + out[3].y = RADAR_TILE_SIZE * (y); +} + +uint8 CRadar::CalculateBlipAlpha(float dist) +{ +#ifdef MENU_MAP + if (CMenuManager::bMenuMapActive) + return 255; +#endif + if (dist <= 1.0f) + return 255; + + if (dist <= 5.0f) + return (128.0f * ((dist - 1.0f) / 4.0f)) + ((1.0f - (dist - 1.0f) / 4.0f) * 255.0f); + + return 128; +} + +void CRadar::ChangeBlipBrightness(int32 i, int32 bright) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) + ms_RadarTrace[index].m_bDim = bright != 1; +} + +void CRadar::ChangeBlipColour(int32 i, int32 color) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) + ms_RadarTrace[index].m_nColor = color; +} + +void CRadar::ChangeBlipDisplay(int32 i, eBlipDisplay display) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) + ms_RadarTrace[index].m_eBlipDisplay = display; +} + +void CRadar::ChangeBlipScale(int32 i, int32 scale) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) + ms_RadarTrace[index].m_wScale = scale; +} + +void CRadar::ClearBlip(int32 i) +{ + int index = GetActualBlipArrayIndex(i); + if (index != -1) { + SetRadarMarkerState(index, false); + ms_RadarTrace[index].m_bInUse = false; +#ifndef MENU_MAP + // Ssshhh + ms_RadarTrace[index].m_eBlipType = BLIP_NONE; + ms_RadarTrace[index].m_eBlipDisplay = BLIP_DISPLAY_NEITHER; + ms_RadarTrace[index].m_eRadarSprite = RADAR_SPRITE_NONE; +#endif + } +} + +void CRadar::ClearBlipForEntity(eBlipType type, int32 id) +{ + for (int i = 0; i < NUMRADARBLIPS; i++) { + if (type == ms_RadarTrace[i].m_eBlipType && id == ms_RadarTrace[i].m_nEntityHandle) { + SetRadarMarkerState(i, false); + ms_RadarTrace[i].m_bInUse = false; + ms_RadarTrace[i].m_eBlipType = BLIP_NONE; + ms_RadarTrace[i].m_eBlipDisplay = BLIP_DISPLAY_NEITHER; + ms_RadarTrace[i].m_eRadarSprite = RADAR_SPRITE_NONE; + } + }; +} + +// Why not a proper clipping algorithm? +#ifdef THIS_IS_STUPID + +bool IsPointInsideRadar(const CVector2D &point) +{ + if (point.x < -1.0f || point.x > 1.0f) return false; + if (point.y < -1.0f || point.y > 1.0f) return false; + return true; +} + +// clip line p1,p2 against (-1.0, 1.0) in x and y, set out to clipped point closest to p1 +int LineRadarBoxCollision(CVector2D &out, const CVector2D &p1, const CVector2D &p2) +{ + float d1, d2; + float t; + float x, y; + float shortest = 1.0f; + int edge = -1; + + // clip against left edge, x = -1.0 + d1 = -1.0f - p1.x; + d2 = -1.0f - p2.x; + if (d1 * d2 < 0.0f) { + // they are on opposite sides, get point of intersection + t = d1 / (d1 - d2); + y = (p2.y - p1.y)*t + p1.y; + if (y >= -1.0f && y <= 1.0f && t <= shortest) { + out.x = -1.0f; + out.y = y; + edge = 3; + shortest = t; + } + } + + // clip against right edge, x = 1.0 + d1 = p1.x - 1.0f; + d2 = p2.x - 1.0f; + if (d1 * d2 < 0.0f) { + // they are on opposite sides, get point of intersection + t = d1 / (d1 - d2); + y = (p2.y - p1.y)*t + p1.y; + if (y >= -1.0f && y <= 1.0f && t <= shortest) { + out.x = 1.0f; + out.y = y; + edge = 1; + shortest = t; + } + } + + // clip against top edge, y = -1.0 + d1 = -1.0f - p1.y; + d2 = -1.0f - p2.y; + if (d1 * d2 < 0.0f) { + // they are on opposite sides, get point of intersection + t = d1 / (d1 - d2); + x = (p2.x - p1.x)*t + p1.x; + if (x >= -1.0f && x <= 1.0f && t <= shortest) { + out.y = -1.0f; + out.x = x; + edge = 0; + shortest = t; + } + } + + // clip against bottom edge, y = 1.0 + d1 = p1.y - 1.0f; + d2 = p2.y - 1.0f; + if (d1 * d2 < 0.0f) { + // they are on opposite sides, get point of intersection + t = d1 / (d1 - d2); + x = (p2.x - p1.x)*t + p1.x; + if (x >= -1.0f && x <= 1.0f && t <= shortest) { + out.y = 1.0f; + out.x = x; + edge = 2; + shortest = t; + } + } + + return edge; +} + +int CRadar::ClipRadarPoly(CVector2D *poly, const CVector2D *rect) +{ + CVector2D corners[4] = { + CVector2D( 1.0f, -1.0f ), // top right + CVector2D( 1.0f, 1.0f ), // bottom right + CVector2D( -1.0f, 1.0f ), // bottom left + CVector2D( -1.0f, -1.0f ), // top left + }; + CVector2D tmp; + int i, j, n; + int laste, e, e1, e2;; + bool inside[4]; + + for (i = 0; i < 4; i++) + inside[i] = IsPointInsideRadar(rect[i]); + + laste = -1; + n = 0; + for (i = 0; i < 4; i++) + if (inside[i]) { + // point is inside, just add + poly[n++] = rect[i]; + } + else { + // point is outside but line to this point might be clipped + e1 = LineRadarBoxCollision(poly[n], rect[i], rect[(i + 4 - 1) % 4]); + if (e1 != -1) { + laste = e1; + n++; + } + // and line from this point might be clipped as well + e2 = LineRadarBoxCollision(poly[n], rect[i], rect[(i + 1) % 4]); + if (e2 != -1) { + if (e1 == -1) { + // if other line wasn't clipped, i.e. it was complete outside, + // we may have to insert another vertex if last clipped line + // was on a different edge + + // find the last intersection if we haven't seen it yet + if (laste == -1) + for (j = 3; j >= i; j--) { + // game uses an if here for j == 0 + e = LineRadarBoxCollision(tmp, rect[j], rect[(j + 4 - 1) % 4]); + if (e != -1) { + laste = e; + break; + } + } + assert(laste != -1); + + // insert corners that were skipped + tmp = poly[n]; + for (e = laste; e != e2; e = (e + 1) % 4) + poly[n++] = corners[e]; + poly[n] = tmp; + } + n++; + } + } + + if (n == 0) { + // If no points, either the rectangle is completely outside or completely surrounds the radar + // no idea what's going on here... + float m = (rect[0].y - rect[1].y) / (rect[0].x - rect[1].x); + if ((m*rect[3].x - rect[3].y) * (m*rect[0].x - rect[0].y) < 0.0f) { + m = (rect[0].y - rect[3].y) / (rect[0].x - rect[3].x); + if ((m*rect[1].x - rect[1].y) * (m*rect[0].x - rect[0].y) < 0.0f) { + poly[0] = corners[0]; + poly[1] = corners[1]; + poly[2] = corners[2]; + poly[3] = corners[3]; + n = 4; + } + } + } + + return n; +} +#else + +int +ClipPolyPlane(const CVector2D *in, int nin, CVector2D *out, CVector *plane) +{ + int j; + int nout; + int x1, x2; + float d1, d2, t; + + nout = 0; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + + d1 = plane->x*in[x1].x + plane->y*in[x1].y + plane->z; + d2 = plane->x*in[x2].x + plane->y*in[x2].y + plane->z; + if(d1*d2 < 0.0f){ + t = d1/(d1 - d2); + out[nout++] = in[x1]*(1.0f-t) + in[x2]*t; + } + if(d2 >= 0.0f) + out[nout++] = in[x2]; + } + return nout; +} + +int CRadar::ClipRadarPoly(CVector2D *poly, const CVector2D *rect) +{ + CVector planes[4] = { + CVector(-1.0f, 0.0f, 1.0f), + CVector( 1.0f, 0.0f, 1.0f), + CVector(0.0f, -1.0f, 1.0f), + CVector(0.0f, 1.0f, 1.0f) + }; + CVector2D tmp[8]; + int n; + if(n = ClipPolyPlane(rect, 4, tmp, &planes[0]), n == 0) return 0; + if(n = ClipPolyPlane(tmp, n, poly, &planes[1]), n == 0) return 0; + if(n = ClipPolyPlane(poly, n, tmp, &planes[2]), n == 0) return 0; + if(n = ClipPolyPlane(tmp, n, poly, &planes[3]), n == 0) return 0; + return n; +} +#endif + +bool CRadar::DisplayThisBlip(int32 counter) +{ + switch (ms_RadarTrace[counter].m_eRadarSprite) { + case RADAR_SPRITE_BOMB: + case RADAR_SPRITE_SPRAY: + case RADAR_SPRITE_WEAPON: + return true; + default: + return false; + } +} + +void CRadar::Draw3dMarkers() +{ + for (int i = 0; i < NUMRADARBLIPS; i++) { + if (ms_RadarTrace[i].m_bInUse) { + switch (ms_RadarTrace[i].m_eBlipType) { + case BLIP_CAR: + { + CEntity *entity = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[i].m_nEntityHandle); + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + CVector pos = entity->GetPosition(); + pos.z += 1.2f * CModelInfo::GetColModel(entity->GetModelIndex())->boundingBox.max.z + 2.5f; + C3dMarkers::PlaceMarker(i | (ms_RadarTrace[i].m_BlipIndex << 16), MARKERTYPE_ARROW, pos, 2.5f, 0, 128, 255, 255, 1024, 0.2f, 5); + } + break; + } + case BLIP_CHAR: + { + CEntity *entity = CPools::GetPedPool()->GetAt(ms_RadarTrace[i].m_nEntityHandle); + if (entity != nil) { + if (((CPed*)entity)->InVehicle()) + entity = ((CPed * )entity)->m_pMyVehicle; + } + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + CVector pos = entity->GetPosition(); + pos.z += 3.0f; + C3dMarkers::PlaceMarker(i | (ms_RadarTrace[i].m_BlipIndex << 16), MARKERTYPE_ARROW, pos, 1.5f, 0, 128, 255, 255, 1024, 0.2f, 5); + } + break; + } + case BLIP_OBJECT: + { + CEntity *entity = CPools::GetObjectPool()->GetAt(ms_RadarTrace[i].m_nEntityHandle); + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + CVector pos = entity->GetPosition(); + pos.z += CModelInfo::GetColModel(entity->GetModelIndex())->boundingBox.max.z + 1.0f + 1.0f; + C3dMarkers::PlaceMarker(i | (ms_RadarTrace[i].m_BlipIndex << 16), MARKERTYPE_ARROW, pos, 1.0f, 0, 128, 255, 255, 1024, 0.2f, 5); + } + break; + } + case BLIP_COORD: + break; + case BLIP_CONTACT_POINT: + if (!CTheScripts::IsPlayerOnAMission()) { + if (ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[i].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) + C3dMarkers::PlaceMarkerSet(i | (ms_RadarTrace[i].m_BlipIndex << 16), MARKERTYPE_CYLINDER, ms_RadarTrace[i].m_vecPos, 2.0f, 0, 128, 255, 128, 2048, 0.2f, 0); + } + break; + } + } + } +} + +void CRadar::DrawBlips() +{ + if ((!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) +#ifdef MENU_MAP + || CMenuManager::bMenuMapActive +#endif + ) { + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + + CVector2D out; + CVector2D in = CVector2D(0.0f, 0.0f); + TransformRadarPointToScreenSpace(out, in); + +#ifdef MENU_MAP + if (!CMenuManager::bMenuMapActive) { +#endif + float angle; + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN) + angle = PI + FindPlayerHeading(); +#ifdef FIX_BUGS + else if (TheCamera.GetLookDirection() != LOOKING_FORWARD) + angle = FindPlayerHeading() - (PI + (TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].SourceBeforeLookBehind).Heading()); +#endif + else + angle = FindPlayerHeading() - (PI + TheCamera.GetForward().Heading()); + + DrawRotatingRadarSprite(&CentreSprite, out.x, out.y, angle, 255); + + CVector2D vec2d; + vec2d.x = vec2DRadarOrigin.x; + vec2d.y = M_SQRT2 * m_radarRange + vec2DRadarOrigin.y; + TransformRealWorldPointToRadarSpace(in, vec2d); + LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + DrawRadarSprite(RADAR_SPRITE_NORTH, out.x, out.y, 255); +#ifdef MENU_MAP + } +#endif + + CEntity *blipEntity = nil; + for(int blipId = 0; blipId < NUMRADARBLIPS; blipId++) { +#ifdef MENU_MAP + // A little hack to reuse cleared blips in menu map. hehe + if (!CMenuManager::bMenuMapActive || ms_RadarTrace[blipId].m_eBlipType == BLIP_CAR || + ms_RadarTrace[blipId].m_eBlipType == BLIP_CHAR || ms_RadarTrace[blipId].m_eBlipType == BLIP_OBJECT) +#endif + if (!ms_RadarTrace[blipId].m_bInUse) + continue; + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_CAR: + case BLIP_CHAR: + case BLIP_OBJECT: + if (ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_BOMB || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_SAVE + || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_SPRAY || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_WEAPON) { + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_CAR: + blipEntity = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + break; + case BLIP_CHAR: + blipEntity = CPools::GetPedPool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + if (blipEntity != nil) { + if (((CPed*)blipEntity)->InVehicle()) + blipEntity = ((CPed*)blipEntity)->m_pMyVehicle; + } + break; + case BLIP_OBJECT: + blipEntity = CPools::GetObjectPool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + break; + default: + break; + } + if (blipEntity) { + uint32 color = GetRadarTraceColour(ms_RadarTrace[blipId].m_nColor, ms_RadarTrace[blipId].m_bDim); + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::IsDebugOn()) { + ShowRadarMarker(blipEntity->GetPosition(), color, ms_RadarTrace[blipId].m_Radius); + ms_RadarTrace[blipId].m_Radius = ms_RadarTrace[blipId].m_Radius - 0.1f; + if (ms_RadarTrace[blipId].m_Radius < 1.0f) + ms_RadarTrace[blipId].m_Radius = 5.0f; + } + } + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + TransformRealWorldPointToRadarSpace(in, blipEntity->GetPosition()); + float dist = LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) { + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + } else { +#ifdef TRIANGULAR_BLIPS + const CVector &pos = FindPlayerCentreOfWorld_NoSniperShift(); + const CVector &blipPos = blipEntity->GetPosition(); + uint8 mode = BLIP_MODE_TRIANGULAR_UP; + if (blipPos.z - pos.z <= 2.0f) { + if (blipPos.z - pos.z < -4.0f) mode = BLIP_MODE_TRIANGULAR_DOWN; + else mode = BLIP_MODE_SQUARE; + } + ShowRadarTraceWithHeight(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255, mode); +#else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); +#endif + } + } + } + } + break; + case BLIP_COORD: + case BLIP_CONTACT_POINT: + if ((ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_BOMB || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_SAVE + || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_SPRAY || ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_WEAPON) + && (ms_RadarTrace[blipId].m_eBlipType != BLIP_CONTACT_POINT || !CTheScripts::IsPlayerOnAMission())) { + + uint32 color = GetRadarTraceColour(ms_RadarTrace[blipId].m_nColor, ms_RadarTrace[blipId].m_bDim); + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::IsDebugOn()) { + ShowRadarMarker(ms_RadarTrace[blipId].m_vecPos, color, ms_RadarTrace[blipId].m_Radius); + ms_RadarTrace[blipId].m_Radius = ms_RadarTrace[blipId].m_Radius - 0.1f; + if (ms_RadarTrace[blipId].m_Radius < 1.0f) + ms_RadarTrace[blipId].m_Radius = 5.0f; + } + } + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + TransformRealWorldPointToRadarSpace(in, ms_RadarTrace[blipId].m_vec2DPos); + float dist = LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) { + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + } else { +#ifdef TRIANGULAR_BLIPS + const CVector &pos = FindPlayerCentreOfWorld_NoSniperShift(); + const CVector &blipPos = ms_RadarTrace[blipId].m_vecPos; + uint8 mode = BLIP_MODE_TRIANGULAR_UP; + if (blipPos.z - pos.z <= 2.0f) { + if (blipPos.z - pos.z < -4.0f) mode = BLIP_MODE_TRIANGULAR_DOWN; + else mode = BLIP_MODE_SQUARE; + } + ShowRadarTraceWithHeight(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255, mode); +#else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); +#endif + } + } + } + break; + default: + break; + } + } + for(int blipId = 0; blipId < NUMRADARBLIPS; blipId++) { + if (!ms_RadarTrace[blipId].m_bInUse) + continue; + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_CAR: + case BLIP_CHAR: + case BLIP_OBJECT: + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_BOMB && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SAVE + && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SPRAY && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_WEAPON) { + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_CAR: + blipEntity = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + break; + case BLIP_CHAR: + blipEntity = CPools::GetPedPool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + if (blipEntity != nil) { + if (((CPed*)blipEntity)->InVehicle()) + blipEntity = ((CPed*)blipEntity)->m_pMyVehicle; + } + break; + case BLIP_OBJECT: + blipEntity = CPools::GetObjectPool()->GetAt(ms_RadarTrace[blipId].m_nEntityHandle); + break; + default: + break; + } + + if (blipEntity) { + uint32 color = GetRadarTraceColour(ms_RadarTrace[blipId].m_nColor, ms_RadarTrace[blipId].m_bDim); + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::IsDebugOn()) { + ShowRadarMarker(blipEntity->GetPosition(), color, ms_RadarTrace[blipId].m_Radius); + ms_RadarTrace[blipId].m_Radius = ms_RadarTrace[blipId].m_Radius - 0.1f; + if (ms_RadarTrace[blipId].m_Radius < 1.0f) + ms_RadarTrace[blipId].m_Radius = 5.0f; + } + } + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + TransformRealWorldPointToRadarSpace(in, blipEntity->GetPosition()); + float dist = LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + else +#ifdef TRIANGULAR_BLIPS + { + const CVector &pos = FindPlayerCentreOfWorld_NoSniperShift(); + const CVector &blipPos = blipEntity->GetPosition(); + uint8 mode = BLIP_MODE_TRIANGULAR_UP; + if (blipPos.z - pos.z <= 2.0f) { + if (blipPos.z - pos.z < -4.0f) mode = BLIP_MODE_TRIANGULAR_DOWN; + else mode = BLIP_MODE_SQUARE; + } + ShowRadarTraceWithHeight(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255, mode); + } +#else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); +#endif + } + } + } + break; + default: + break; + } + } + for (int blipId = 0; blipId < NUMRADARBLIPS; blipId++) { + if (!ms_RadarTrace[blipId].m_bInUse) + continue; + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_COORD: + case BLIP_CONTACT_POINT: + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_BOMB && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SAVE + && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SPRAY && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_WEAPON + && (ms_RadarTrace[blipId].m_eBlipType != BLIP_CONTACT_POINT || !CTheScripts::IsPlayerOnAMission())) { + + uint32 color = GetRadarTraceColour(ms_RadarTrace[blipId].m_nColor, ms_RadarTrace[blipId].m_bDim); + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_MARKER_ONLY) { + if (CTheScripts::IsDebugOn()) { + ShowRadarMarker(ms_RadarTrace[blipId].m_vecPos, color, ms_RadarTrace[blipId].m_Radius); + ms_RadarTrace[blipId].m_Radius = ms_RadarTrace[blipId].m_Radius - 0.1f; + if (ms_RadarTrace[blipId].m_Radius < 1.0f) + ms_RadarTrace[blipId].m_Radius = 5.0f; + } + } + if (ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BOTH || ms_RadarTrace[blipId].m_eBlipDisplay == BLIP_DISPLAY_BLIP_ONLY) { + TransformRealWorldPointToRadarSpace(in, ms_RadarTrace[blipId].m_vec2DPos); + float dist = LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + else +#ifdef TRIANGULAR_BLIPS + { + const CVector &pos = FindPlayerCentreOfWorld_NoSniperShift(); + const CVector &blipPos = ms_RadarTrace[blipId].m_vecPos; + uint8 mode = BLIP_MODE_TRIANGULAR_UP; + if (blipPos.z - pos.z <= 2.0f) { + if (blipPos.z - pos.z < -4.0f) mode = BLIP_MODE_TRIANGULAR_DOWN; + else mode = BLIP_MODE_SQUARE; + } + ShowRadarTraceWithHeight(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255, mode); + } +#else + ShowRadarTrace(out.x, out.y, ms_RadarTrace[blipId].m_wScale, (uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); +#endif + } + } + break; + default: + break; + } + } +#ifdef MENU_MAP + if (CMenuManager::bMenuMapActive) { + CVector2D in, out; + TransformRealWorldPointToRadarSpace(in, FindPlayerCentreOfWorld_NoSniperShift()); + LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + DrawYouAreHereSprite(out.x, out.y); + } +#endif + } +} + +void CRadar::DrawMap() +{ + if (!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) { +#if 1 // from VC + CalculateCachedSinCos(); +#endif + if (FindPlayerVehicle()) { + float speed = FindPlayerSpeed().Magnitude(); + if (speed < RADAR_MIN_SPEED) + m_radarRange = RADAR_MIN_RANGE; + else if (speed < RADAR_MAX_SPEED) + m_radarRange = (speed - RADAR_MIN_SPEED)/(RADAR_MAX_SPEED-RADAR_MIN_SPEED) * (RADAR_MAX_RANGE-RADAR_MIN_RANGE) + RADAR_MIN_RANGE; + else + m_radarRange = RADAR_MAX_RANGE; + } + else + m_radarRange = RADAR_MIN_RANGE; + + vec2DRadarOrigin = CVector2D(FindPlayerCentreOfWorld_NoSniperShift()); + DrawRadarMap(); + } +} + +void CRadar::DrawRadarMap() +{ + // Game calculates an unused CRect here + + DrawRadarMask(); + + // top left ist (0, 0) + int x = Floor((vec2DRadarOrigin.x - RADAR_MIN_X) / RADAR_TILE_SIZE); + int y = Ceil((RADAR_NUM_TILES - 1) - (vec2DRadarOrigin.y - RADAR_MIN_Y) / RADAR_TILE_SIZE); + StreamRadarSections(x, y); + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATETEXTUREPERSPECTIVE, (void*)FALSE); + + DrawRadarSection(x - 1, y - 1); + DrawRadarSection(x, y - 1); + DrawRadarSection(x + 1, y - 1); + DrawRadarSection(x - 1, y); + DrawRadarSection(x, y); + DrawRadarSection(x + 1, y); + DrawRadarSection(x - 1, y + 1); + DrawRadarSection(x, y + 1); + DrawRadarSection(x + 1, y + 1); +} + +void CRadar::DrawRadarMask() +{ + CVector2D corners[4] = { + CVector2D(1.0f, -1.0f), + CVector2D(1.0f, 1.0f), + CVector2D(-1.0f, 1.0f), + CVector2D(-1.0, -1.0f) + }; + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void*)nil); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); +#if !defined(GTA_PS2_STUFF) && defined(RWLIBS) + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS); +#else + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); +#endif + + CVector2D out[8]; + CVector2D in; + + // Draw the shape we want to mask out from the radar in four segments + for (int i = 0; i < 4; i++) { + // First point is always the corner itself + in.x = corners[i].x; + in.y = corners[i].y; + TransformRadarPointToScreenSpace(out[0], in); + + // Then generate a quarter of the circle + for (int j = 0; j < 7; j++) { + in.x = corners[i].x * Cos(j * (PI / 2.0f / 6.0f)); + in.y = corners[i].y * Sin(j * (PI / 2.0f / 6.0f)); + TransformRadarPointToScreenSpace(out[j + 1], in); + }; + + CSprite2d::SetMaskVertices(8, (float *)out); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::GetVertices(), 8); + } +#if !defined(GTA_PS2_STUFF) && defined(RWLIBS) + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); +#endif +} + +void CRadar::DrawRadarSection(int32 x, int32 y) +{ + int i; + RwTexDictionary *txd; + CVector2D worldPoly[8]; + CVector2D radarCorners[4]; + CVector2D radarPoly[8]; + CVector2D texCoords[8]; + CVector2D screenPoly[8]; + int numVertices; + RwTexture *texture = nil; + + GetTextureCorners(x, y, worldPoly); + ClipRadarTileCoords(x, y); + + assert(CTxdStore::GetSlot(gRadarTxdIds[x + RADAR_NUM_TILES * y])); + txd = CTxdStore::GetSlot(gRadarTxdIds[x + RADAR_NUM_TILES * y])->texDict; + if (txd) + texture = GetFirstTexture(txd); + if (texture == nil) + return; + + for (i = 0; i < 4; i++) + TransformRealWorldPointToRadarSpace(radarCorners[i], worldPoly[i]); + + numVertices = ClipRadarPoly(radarPoly, radarCorners); + + // FIX: can return earlier here +// if(numVertices == 0) + if (numVertices < 3) + return; + + for (i = 0; i < numVertices; i++) { + TransformRadarPointToRealWorldSpace(worldPoly[i], radarPoly[i]); + TransformRealWorldToTexCoordSpace(texCoords[i], worldPoly[i], x, y); + TransformRadarPointToScreenSpace(screenPoly[i], radarPoly[i]); + } + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(texture)); + CSprite2d::SetVertices(numVertices, (float*)screenPoly, (float*)texCoords, CRGBA(255, 255, 255, 255)); + // check done above now +// if(numVertices > 2) + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::GetVertices(), numVertices); +} + +void CRadar::DrawRadarSprite(uint16 sprite, float x, float y, uint8 alpha) +{ +#ifdef MENU_MAP + if(sprite == RADAR_SPRITE_WAYPOINT) alpha = 255; +#endif + RadarSprites[sprite]->Draw(CRect(x - SCREEN_SCALE_X(8.0f), y - SCREEN_SCALE_Y(8.0f), x + SCREEN_SCALE_X(8.0f), y + SCREEN_SCALE_Y(8.0f)), CRGBA(255, 255, 255, alpha)); +} + +void CRadar::DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha) +{ + CVector curPosn[4]; + const float sizeX = SCREEN_SCALE_X(8.0f); + const float correctedAngle = angle - PI / 4.f; + const float sizeY = SCREEN_SCALE_Y(8.0f); + + for (uint32 i = 0; i < 4; i++) { + const float cornerAngle = i * HALFPI + correctedAngle; + curPosn[i].x = x + (0.0f * Cos(cornerAngle) + 1.0f * Sin(cornerAngle)) * sizeX; + curPosn[i].y = y - (0.0f * Sin(cornerAngle) - 1.0f * Cos(cornerAngle)) * sizeY; + } + + sprite->Draw(curPosn[3].x, curPosn[3].y, curPosn[2].x, curPosn[2].y, curPosn[0].x, curPosn[0].y, curPosn[1].x, curPosn[1].y, CRGBA(255, 255, 255, alpha)); +} + +int32 CRadar::GetActualBlipArrayIndex(int32 i) +{ + if (i == -1) + return -1; + else if ((i & 0xFFFF0000) >> 16 != ms_RadarTrace[(uint16)i].m_BlipIndex) + return -1; + else + return (uint16)i; +} + +int32 CRadar::GetNewUniqueBlipIndex(int32 i) +{ + if (ms_RadarTrace[i].m_BlipIndex >= UINT16_MAX - 1) + ms_RadarTrace[i].m_BlipIndex = 1; + else + ms_RadarTrace[i].m_BlipIndex++; + return i | (ms_RadarTrace[i].m_BlipIndex << 16); +} + +uint32 CRadar::GetRadarTraceColour(uint32 color, bool bright) +{ + uint32 c; + switch (color) { + case RADAR_TRACE_RED: + if (bright) + c = 0x712B49FF; + else + c = 0x7F0000FF; + break; + case RADAR_TRACE_GREEN: + if (bright) + c = 0x5FA06AFF; + else + c = 0x007F00FF; + break; + case RADAR_TRACE_LIGHT_BLUE: + if (bright) + c = 0x80A7F3FF; + else + c = 0x00007FFF; + break; + case RADAR_TRACE_GRAY: + if (bright) + c = 0xE1E1E1FF; + else + c = 0x7F7F7FFF; + break; + case RADAR_TRACE_YELLOW: + if (bright) + c = 0xFFFF00FF; + else + c = 0x7F7F00FF; + break; + case RADAR_TRACE_MAGENTA: + if (bright) + c = 0xFF00FFFF; + else + c = 0x7F007FFF; + break; + case RADAR_TRACE_CYAN: + if (bright) + c = 0x00FFFFFF; + else + c = 0x007F7FFF; + break; + default: + c = color; + break; + }; + return c; +} + +const char* gRadarTexNames[] = { + "radar00", "radar01", "radar02", "radar03", "radar04", "radar05", "radar06", "radar07", + "radar08", "radar09", "radar10", "radar11", "radar12", "radar13", "radar14", "radar15", + "radar16", "radar17", "radar18", "radar19", "radar20", "radar21", "radar22", "radar23", + "radar24", "radar25", "radar26", "radar27", "radar28", "radar29", "radar30", "radar31", + "radar32", "radar33", "radar34", "radar35", "radar36", "radar37", "radar38", "radar39", + "radar40", "radar41", "radar42", "radar43", "radar44", "radar45", "radar46", "radar47", + "radar48", "radar49", "radar50", "radar51", "radar52", "radar53", "radar54", "radar55", + "radar56", "radar57", "radar58", "radar59", "radar60", "radar61", "radar62", "radar63", +}; + +void +CRadar::Initialise() +{ +#ifdef MENU_MAP + TargetMarkerId = -1; +#endif + + for (int i = 0; i < NUMRADARBLIPS; i++) { + ms_RadarTrace[i].m_BlipIndex = 1; + SetRadarMarkerState(i, false); + ms_RadarTrace[i].m_bInUse = false; + ms_RadarTrace[i].m_eBlipType = BLIP_NONE; + ms_RadarTrace[i].m_eBlipDisplay = BLIP_DISPLAY_NEITHER; + ms_RadarTrace[i].m_eRadarSprite = RADAR_SPRITE_NONE; + } + + m_radarRange = 350.0f; + for (int i = 0; i < 64; i++) + gRadarTxdIds[i] = CTxdStore::FindTxdSlot(gRadarTexNames[i]); +} + +float CRadar::LimitRadarPoint(CVector2D &point) +{ + float dist, invdist; + + dist = point.Magnitude(); +#ifdef MENU_MAP + if (CMenuManager::bMenuMapActive) + return dist; +#endif + if (dist > 1.0f) { + invdist = 1.0f / dist; + point.x *= invdist; + point.y *= invdist; + } + return dist; +} + +void CRadar::LoadAllRadarBlips(uint8 *buf, uint32 size) +{ + Initialise(); +INITSAVEBUF + CheckSaveHeader(buf, 'R', 'D', 'R', '\0', size - SAVE_HEADER_SIZE); + + for (int i = 0; i < NUMRADARBLIPS; i++) + ReadSaveBuf(&ms_RadarTrace[i], buf); + +VALIDATESAVEBUF(size); +} + +void +CRadar::LoadTextures() +{ + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("hud")); + AsukaSprite.SetTexture("radar_asuka"); + BombSprite.SetTexture("radar_bomb"); + CatSprite.SetTexture("radar_cat"); + CentreSprite.SetTexture("radar_centre"); + CopcarSprite.SetTexture("radar_copcar"); + DonSprite.SetTexture("radar_don"); + EightSprite.SetTexture("radar_eight"); + ElSprite.SetTexture("radar_el"); + IceSprite.SetTexture("radar_ice"); + JoeySprite.SetTexture("radar_joey"); + KenjiSprite.SetTexture("radar_kenji"); + LizSprite.SetTexture("radar_liz"); + LuigiSprite.SetTexture("radar_luigi"); + NorthSprite.SetTexture("radar_north"); + RaySprite.SetTexture("radar_ray"); + SalSprite.SetTexture("radar_sal"); + SaveSprite.SetTexture("radar_save"); + SpraySprite.SetTexture("radar_spray"); + TonySprite.SetTexture("radar_tony"); + WeaponSprite.SetTexture("radar_weapon"); +#ifdef MENU_MAP + WaypointSprite.SetTexture("radar_waypoint"); + if(!WaypointSprite.m_pTexture) { + // create the texture if it's missing in TXD +#define WAYPOINT_R (255) +#define WAYPOINT_G (72) +#define WAYPOINT_B (77) + + RwRaster *raster = RwRasterCreate(16, 16, 0, rwRASTERTYPETEXTURE | rwRASTERFORMAT8888); + + RwUInt32 *pixels = (RwUInt32 *)RwRasterLock(raster, 0, rwRASTERLOCKWRITE); + for(int x = 0; x < 16; x++) + for(int y = 0; y < 16; y++) + { + int x2 = x < 8 ? x : 7 - (x & 7); + int y2 = y < 8 ? y : 7 - (y & 7); + if ((y2 >= 4 && x2 >= 4) // square in the center is transparent + || (x2 < 2 && y2 == 0) // two pixels on each side of first/last line are transparent + || (x2 < 1 && y2 == 1)) // one pixel on each side of second to first/last line is transparent + pixels[x + y * 16] = 0; + else if((x2 == 2 && y2 >= 2)|| (y2 == 2 && x2 >= 2) )// colored square inside +#ifdef RW_GL3 + pixels[x + y * 16] = WAYPOINT_R | (WAYPOINT_G << 8) | (WAYPOINT_B << 16) | (255 << 24); +#else + pixels[x + y * 16] = WAYPOINT_B | (WAYPOINT_G << 8) | (WAYPOINT_R << 16) | (255 << 24); +#endif + else + pixels[x + y * 16] = 0xFF000000; // black + } + RwRasterUnlock(raster); + WaypointSprite.m_pTexture = RwTextureCreate(raster); + RwTextureSetFilterMode(WaypointSprite.m_pTexture, rwFILTERLINEAR); +#undef WAYPOINT_R +#undef WAYPOINT_G +#undef WAYPOINT_B + } +#endif + CTxdStore::PopCurrentTxd(); +} + +void CRadar::RemoveRadarSections() +{ + for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) + RemoveMapSection(i, j); +} + +void CRadar::SaveAllRadarBlips(uint8 *buf, uint32 *size) +{ + *size = SAVE_HEADER_SIZE + sizeof(ms_RadarTrace); +INITSAVEBUF + WriteSaveHeader(buf, 'R', 'D', 'R', '\0', *size - SAVE_HEADER_SIZE); + +#ifdef MENU_MAP + bool bWaypointDeleted = false; + if (TargetMarkerId != -1) { + ClearBlip(TargetMarkerId); + TargetMarkerId = -1; + bWaypointDeleted = true; + } +#endif + + for (int i = 0; i < NUMRADARBLIPS; i++) + WriteSaveBuf(buf, ms_RadarTrace[i]); + + +#ifdef MENU_MAP + if(bWaypointDeleted) + ToggleTargetMarker(TargetMarkerPos.x, TargetMarkerPos.y); +#endif + +VALIDATESAVEBUF(*size); +} + +void CRadar::SetBlipSprite(int32 i, int32 icon) +{ + int index = CRadar::GetActualBlipArrayIndex(i); + if (index != -1) { + ms_RadarTrace[index].m_eRadarSprite = icon; + } +} + +int CRadar::SetCoordBlip(eBlipType type, CVector pos, int32 color, eBlipDisplay display) +{ + int nextBlip; + for (nextBlip = 0; nextBlip < NUMRADARBLIPS; nextBlip++) { + if (!ms_RadarTrace[nextBlip].m_bInUse) + break; + } +#ifdef FIX_BUGS + if (nextBlip == NUMRADARBLIPS) + return -1; +#endif + ms_RadarTrace[nextBlip].m_eBlipType = type; + ms_RadarTrace[nextBlip].m_nColor = color; + ms_RadarTrace[nextBlip].m_bDim = 1; + ms_RadarTrace[nextBlip].m_bInUse = 1; + ms_RadarTrace[nextBlip].m_Radius = 1.0f; + ms_RadarTrace[nextBlip].m_vec2DPos = pos; + ms_RadarTrace[nextBlip].m_vecPos = pos; + ms_RadarTrace[nextBlip].m_nEntityHandle = 0; + ms_RadarTrace[nextBlip].m_wScale = 1; + ms_RadarTrace[nextBlip].m_eBlipDisplay = display; + ms_RadarTrace[nextBlip].m_eRadarSprite = RADAR_SPRITE_NONE; + return CRadar::GetNewUniqueBlipIndex(nextBlip); +} + +int CRadar::SetEntityBlip(eBlipType type, int32 handle, int32 color, eBlipDisplay display) +{ + int nextBlip; + for (nextBlip = 0; nextBlip < NUMRADARBLIPS; nextBlip++) { + if (!ms_RadarTrace[nextBlip].m_bInUse) + break; + } +#ifdef FIX_BUGS + if (nextBlip == NUMRADARBLIPS) + return -1; +#endif + ms_RadarTrace[nextBlip].m_eBlipType = type; + ms_RadarTrace[nextBlip].m_nColor = color; + ms_RadarTrace[nextBlip].m_bDim = 1; + ms_RadarTrace[nextBlip].m_bInUse = 1; + ms_RadarTrace[nextBlip].m_Radius = 1.0f; + ms_RadarTrace[nextBlip].m_nEntityHandle = handle; + ms_RadarTrace[nextBlip].m_wScale = 1; + ms_RadarTrace[nextBlip].m_eBlipDisplay = display; + ms_RadarTrace[nextBlip].m_eRadarSprite = RADAR_SPRITE_NONE; + return GetNewUniqueBlipIndex(nextBlip); +} + +void CRadar::SetRadarMarkerState(int32 counter, bool flag) +{ + CEntity *e; + switch (ms_RadarTrace[counter].m_eBlipType) { + case BLIP_CAR: + e = CPools::GetVehiclePool()->GetAt(ms_RadarTrace[counter].m_nEntityHandle); + break; + case BLIP_CHAR: + e = CPools::GetPedPool()->GetAt(ms_RadarTrace[counter].m_nEntityHandle); + break; + case BLIP_OBJECT: + e = CPools::GetObjectPool()->GetAt(ms_RadarTrace[counter].m_nEntityHandle); + break; + default: + return; + } + + if (e) + e->bHasBlip = flag; +} + +void CRadar::ShowRadarMarker(CVector pos, uint32 color, float radius) { + float f1 = radius * 1.4f; + float f2 = radius * 0.5f; + CVector p1, p2; + + p1 = pos + TheCamera.GetUp()*f1; + p2 = pos + TheCamera.GetUp()*f2; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color); + + p1 = pos - TheCamera.GetUp()*f1; + p2 = pos - TheCamera.GetUp()*f2; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color); + + p1 = pos + TheCamera.GetRight()*f1; + p2 = pos + TheCamera.GetRight()*f2; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color); + + p1 = pos - TheCamera.GetRight()*f1; + p2 = pos - TheCamera.GetRight()*f2; + CTheScripts::ScriptDebugLine3D(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, color, color); +} + +void CRadar::ShowRadarTrace(float x, float y, uint32 size, uint8 red, uint8 green, uint8 blue, uint8 alpha) +{ + if ((TheCamera.m_WideScreenOn || !CHud::m_Wants_To_Draw_Hud) +#ifdef MENU_MAP + && !CMenuManager::bMenuMapActive +#endif + ) + return; + + CSprite2d::DrawRect(CRect(x - SCREEN_SCALE_X(size + 1.0f), y - SCREEN_SCALE_Y(size + 1.0f), SCREEN_SCALE_X(size + 1.0f) + x, SCREEN_SCALE_Y(size + 1.0f) + y), CRGBA(0, 0, 0, alpha)); + CSprite2d::DrawRect(CRect(x - SCREEN_SCALE_X(size), y - SCREEN_SCALE_Y(size), SCREEN_SCALE_X(size) + x, SCREEN_SCALE_Y(size) + y), CRGBA(red, green, blue, alpha)); +} + +void CRadar::ShowRadarTraceWithHeight(float x, float y, uint32 size, uint8 red, uint8 green, uint8 blue, uint8 alpha, uint8 mode) +{ + if ((TheCamera.m_WideScreenOn || !CHud::m_Wants_To_Draw_Hud) +#ifdef MENU_MAP + && !CMenuManager::bMenuMapActive +#endif + ) + return; + + switch (mode) + { + case BLIP_MODE_TRIANGULAR_UP: + // size++; // VC does size + 1 for triangles + CSprite2d::Draw2DPolygon(x + SCREEN_SCALE_X(size + 3.0f), y + SCREEN_SCALE_Y(size + 2.0f), x - (SCREEN_SCALE_X(size + 3.0f)), y + SCREEN_SCALE_Y(size + 2.0f), x, y - (SCREEN_SCALE_Y(size + 3.0f)), x, y - (SCREEN_SCALE_Y(size + 3.0f)), CRGBA(0, 0, 0, alpha)); + CSprite2d::Draw2DPolygon(x + SCREEN_SCALE_X(size + 1.0f), y + SCREEN_SCALE_Y(size + 1.0f), x - (SCREEN_SCALE_X(size + 1.0f)), y + SCREEN_SCALE_Y(size + 1.0f), x, y - (SCREEN_SCALE_Y(size + 1.0f)), x, y - (SCREEN_SCALE_Y(size + 1.0f)), CRGBA(red, green, blue, alpha)); + break; + case BLIP_MODE_TRIANGULAR_DOWN: + // size++; // VC does size + 1 for triangles + CSprite2d::Draw2DPolygon(x, y + SCREEN_SCALE_Y(size + 2.0f), x, y + SCREEN_SCALE_Y(size + 3.0f), x + SCREEN_SCALE_X(size + 3.0f), y - (SCREEN_SCALE_Y(size + 2.0f)), x - (SCREEN_SCALE_X(size + 3.0f)), y - (SCREEN_SCALE_Y(size + 2.0f)), CRGBA(0, 0, 0, alpha)); + CSprite2d::Draw2DPolygon(x, y + SCREEN_SCALE_Y(size + 1.0f), x, y + SCREEN_SCALE_Y(size + 1.0f), x + SCREEN_SCALE_X(size + 1.0f), y - (SCREEN_SCALE_Y(size + 1.0f)), x - (SCREEN_SCALE_X(size + 1.0f)), y - (SCREEN_SCALE_Y(size + 1.0f)), CRGBA(red, green, blue, alpha)); + break; + case BLIP_MODE_SQUARE: + CSprite2d::DrawRect(CRect(x - SCREEN_SCALE_X(size + 1.0f), y - SCREEN_SCALE_Y(size + 1.0f), SCREEN_SCALE_X(size + 1.0f) + x, SCREEN_SCALE_Y(size + 1.0f) + y), CRGBA(0, 0, 0, alpha)); + CSprite2d::DrawRect(CRect(x - SCREEN_SCALE_X(size), y - SCREEN_SCALE_Y(size), SCREEN_SCALE_X(size) + x, SCREEN_SCALE_Y(size) + y), CRGBA(red, green, blue, alpha)); + break; + } +} + +void CRadar::Shutdown() +{ + AsukaSprite.Delete(); + BombSprite.Delete(); + CatSprite.Delete(); + CentreSprite.Delete(); + CopcarSprite.Delete(); + DonSprite.Delete(); + EightSprite.Delete(); + ElSprite.Delete(); + IceSprite.Delete(); + JoeySprite.Delete(); + KenjiSprite.Delete(); + LizSprite.Delete(); + LuigiSprite.Delete(); + NorthSprite.Delete(); + RaySprite.Delete(); + SalSprite.Delete(); + SaveSprite.Delete(); + SpraySprite.Delete(); + TonySprite.Delete(); + WeaponSprite.Delete(); +#ifdef MENU_MAP + WaypointSprite.Delete(); +#endif + RemoveRadarSections(); +} + +void CRadar::StreamRadarSections(const CVector &posn) +{ + StreamRadarSections(Floor((2000.0f + posn.x) / 500.0f), Ceil(7.0f - (2000.0f + posn.y) / 500.0f)); +} + +void CRadar::StreamRadarSections(int32 x, int32 y) +{ + for (int i = 0; i < RADAR_NUM_TILES; ++i) { + for (int j = 0; j < RADAR_NUM_TILES; ++j) { + if ((i >= x - 1 && i <= x + 1) && (j >= y - 1 && j <= y + 1)) + RequestMapSection(i, j); + else + RemoveMapSection(i, j); + }; + }; +} + +void CRadar::TransformRealWorldToTexCoordSpace(CVector2D &out, const CVector2D &in, int32 x, int32 y) +{ + out.x = in.x - (x * RADAR_TILE_SIZE + RADAR_MIN_X); + out.y = -(in.y - ((RADAR_NUM_TILES - y) * RADAR_TILE_SIZE + RADAR_MIN_Y)); + out.x /= RADAR_TILE_SIZE; + out.y /= RADAR_TILE_SIZE; +} + +void CRadar::TransformRadarPointToRealWorldSpace(CVector2D &out, const CVector2D &in) +{ + float s, c; +#if 1 + s = -cachedSin; + c = cachedCos; +#else + // Original code + + s = -Sin(TheCamera.GetForward().Heading()); + c = Cos(TheCamera.GetForward().Heading()); + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) { + s = 0.0f; + c = 1.0f; + } + else if (TheCamera.GetLookDirection() != LOOKING_FORWARD) { + CVector forward; + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON) { + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetForward(); + forward.Normalise(); // a bit useless... + } + else + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].SourceBeforeLookBehind; + + s = -Sin(forward.Heading()); + c = Cos(forward.Heading()); + } +#endif + + out.x = s * in.y + c * in.x; + out.y = c * in.y - s * in.x; + + out = out * m_radarRange + vec2DRadarOrigin; +} + +// Radar space goes from -1.0 to 1.0 in x and y, top right is (1.0, 1.0) +void CRadar::TransformRadarPointToScreenSpace(CVector2D &out, const CVector2D &in) +{ +#ifdef MENU_MAP + if (CMenuManager::bMenuMapActive) { + // fMapSize is actually half map size. Radar range is 1000, so if x is -2000, in.x + 2.0f is 0. + out.x = (CMenuManager::fMapCenterX - CMenuManager::fMapSize) + (in.x + 2.0f) * CMenuManager::fMapSize * 2.0f / 4.0f; + out.y = (CMenuManager::fMapCenterY - CMenuManager::fMapSize) + (2.0f - in.y) * CMenuManager::fMapSize * 2.0f / 4.0f; + } else +#endif + { +#ifdef FIX_BUGS + out.x = (in.x + 1.0f) * 0.5f * SCREEN_SCALE_X(RADAR_WIDTH) + SCREEN_SCALE_X(RADAR_LEFT); +#else + out.x = (in.x + 1.0f) * 0.5f * SCREEN_SCALE_X(RADAR_WIDTH) + RADAR_LEFT; +#endif + out.y = (1.0f - in.y) * 0.5f * SCREEN_SCALE_Y(RADAR_HEIGHT) + SCREEN_SCALE_FROM_BOTTOM(RADAR_BOTTOM + RADAR_HEIGHT); + } +} + +void CRadar::TransformRealWorldPointToRadarSpace(CVector2D &out, const CVector2D &in) +{ + float s, c; +#if 1 + s = cachedSin; + c = cachedCos; +#else + // Original code + + float s, c; + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) { + s = 0.0f; + c = 1.0f; + } + else if (TheCamera.GetLookDirection() == LOOKING_FORWARD) { + s = Sin(TheCamera.GetForward().Heading()); + c = Cos(TheCamera.GetForward().Heading()); + } + else { + CVector forward; + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON) { + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetForward(); + forward.Normalise(); // a bit useless... + } + else + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].SourceBeforeLookBehind; + + s = Sin(forward.Heading()); + c = Cos(forward.Heading()); + } +#endif + + float x = (in.x - vec2DRadarOrigin.x) * (1.0f / m_radarRange); + float y = (in.y - vec2DRadarOrigin.y) * (1.0f / m_radarRange); + + out.x = s * y + c * x; + out.y = c * y - s * x; +} + +void +CRadar::CalculateCachedSinCos() +{ + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED +#ifdef MENU_MAP + || CMenuManager::bMenuMapActive +#endif + ) { + cachedSin = 0.0f; + cachedCos = 1.0f; + } else if (TheCamera.GetLookDirection() == LOOKING_FORWARD) { + cachedSin = Sin(TheCamera.GetForward().Heading()); + cachedCos = Cos(TheCamera.GetForward().Heading()); + } else { + CVector forward; + + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON) { + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetForward(); + forward.Normalise(); // a bit useless... + } + else + forward = TheCamera.Cams[TheCamera.ActiveCam].CamTargetEntity->GetPosition() - TheCamera.Cams[TheCamera.ActiveCam].SourceBeforeLookBehind; + + cachedSin = Sin(forward.Heading()); + cachedCos = Cos(forward.Heading()); + } +} + +#ifdef MENU_MAP +void +CRadar::InitFrontEndMap() +{ + CalculateCachedSinCos(); + vec2DRadarOrigin.x = 0.0f; + vec2DRadarOrigin.y = 0.0f; + m_radarRange = 1000.0f; // doesn't mean anything, just affects the calculation in TransformRadarPointToScreenSpace +} + +void +CRadar::DrawYouAreHereSprite(float x, float y) +{ + static uint32 lastChange = 0; + static bool show = true; + + if (show) { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastChange > 500) { + lastChange = CTimer::GetTimeInMillisecondsPauseMode(); + show = !show; + } + } else { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastChange > 200) { + lastChange = CTimer::GetTimeInMillisecondsPauseMode(); + show = !show; + } + } + + if (show) { + float left = x - SCREEN_SCALE_X(12.0f); + float top = y; + float right = SCREEN_SCALE_X(12.0) + x; + float bottom = y - SCREEN_SCALE_Y(24.0f); + CentreSprite.Draw(CRect(left, top, right, bottom), CRGBA(255, 255, 255, 255)); + } +} + +void +CRadar::ToggleTargetMarker(float x, float y) +{ + if (TargetMarkerId == -1) { + int nextBlip; + for (nextBlip = NUMRADARBLIPS-1; nextBlip >= 0; nextBlip--) { + if (!ms_RadarTrace[nextBlip].m_bInUse) + break; + } +#ifdef FIX_BUGS + if (nextBlip == 0) + return; +#endif + ms_RadarTrace[nextBlip].m_eBlipType = BLIP_COORD; + ms_RadarTrace[nextBlip].m_nColor = RADAR_TRACE_GRAY; + ms_RadarTrace[nextBlip].m_bDim = 0; + ms_RadarTrace[nextBlip].m_bInUse = 1; + ms_RadarTrace[nextBlip].m_Radius = 1.0f; + CVector pos(x, y, CWorld::FindGroundZForCoord(x,y)); + TargetMarkerPos = pos; + ms_RadarTrace[nextBlip].m_vec2DPos = pos; + ms_RadarTrace[nextBlip].m_vecPos = pos; + ms_RadarTrace[nextBlip].m_nEntityHandle = 0; + ms_RadarTrace[nextBlip].m_wScale = 5; + ms_RadarTrace[nextBlip].m_eBlipDisplay = BLIP_DISPLAY_BLIP_ONLY; + ms_RadarTrace[nextBlip].m_eRadarSprite = RADAR_SPRITE_WAYPOINT; + TargetMarkerId = CRadar::GetNewUniqueBlipIndex(nextBlip); + } else { + ClearBlip(TargetMarkerId); + TargetMarkerId = -1; + } +} +#endif + diff --git a/src/core/Radar.h b/src/core/Radar.h new file mode 100644 index 0000000..ae87d0f --- /dev/null +++ b/src/core/Radar.h @@ -0,0 +1,201 @@ +#pragma once +#include "Sprite2d.h" +#include "Draw.h" + +enum eBlipType +{ + BLIP_NONE, + BLIP_CAR, + BLIP_CHAR, + BLIP_OBJECT, + BLIP_COORD, + BLIP_CONTACT_POINT +}; + +enum eBlipDisplay +{ + BLIP_DISPLAY_NEITHER = 0, + BLIP_DISPLAY_MARKER_ONLY = 1, + BLIP_DISPLAY_BLIP_ONLY = 2, + BLIP_DISPLAY_BOTH = 3, +}; + +enum eRadarSprite +{ +#ifdef MENU_MAP + RADAR_SPRITE_ENTITY_BLIP = -2, + RADAR_SPRITE_COORD_BLIP = -1, +#endif + RADAR_SPRITE_NONE = 0, + RADAR_SPRITE_ASUKA, + RADAR_SPRITE_BOMB, + RADAR_SPRITE_CAT, + RADAR_SPRITE_CENTRE, + RADAR_SPRITE_COPCAR, + RADAR_SPRITE_DON, + RADAR_SPRITE_EIGHT, + RADAR_SPRITE_EL, + RADAR_SPRITE_ICE, + RADAR_SPRITE_JOEY, + RADAR_SPRITE_KENJI, + RADAR_SPRITE_LIZ, + RADAR_SPRITE_LUIGI, + RADAR_SPRITE_NORTH, + RADAR_SPRITE_RAY, + RADAR_SPRITE_SAL, + RADAR_SPRITE_SAVE, + RADAR_SPRITE_SPRAY, + RADAR_SPRITE_TONY, + RADAR_SPRITE_WEAPON, +#ifdef MENU_MAP + RADAR_SPRITE_WAYPOINT, +#endif + RADAR_SPRITE_COUNT +}; + +enum +{ + RADAR_TRACE_RED, + RADAR_TRACE_GREEN, + RADAR_TRACE_LIGHT_BLUE, + RADAR_TRACE_GRAY, + RADAR_TRACE_YELLOW, + RADAR_TRACE_MAGENTA, + RADAR_TRACE_CYAN +}; + +enum +{ + BLIP_MODE_TRIANGULAR_UP = 0, + BLIP_MODE_TRIANGULAR_DOWN, + BLIP_MODE_SQUARE, +}; + +struct sRadarTrace +{ + uint32 m_nColor; + uint32 m_eBlipType; // eBlipType + int32 m_nEntityHandle; + CVector2D m_vec2DPos; + CVector m_vecPos; + uint16 m_BlipIndex; + bool m_bDim; + bool m_bInUse; + float m_Radius; + int16 m_wScale; + uint16 m_eBlipDisplay; // eBlipDisplay + uint16 m_eRadarSprite; // eRadarSprite +}; +VALIDATE_SIZE(sRadarTrace, 0x30); + +// Values for screen space +#define RADAR_LEFT (40.0f) +#ifdef PS2_HUD +#define RADAR_BOTTOM (44.0f) +#else +#define RADAR_BOTTOM (47.0f) +#endif + +#ifdef FIX_RADAR +/* + The values are from an early screenshot taken before R* broke radar + #define RADAR_WIDTH (82.0f) + #define RADAR_HEIGHT (82.0f) +*/ +#define RADAR_WIDTH ((CDraw::ms_bFixRadar) ? (82.0f) : (94.0f)) +#define RADAR_HEIGHT ((CDraw::ms_bFixRadar) ? (82.0f) : (76.0f)) +#else +/* + broken since forever, someone tried to fix size for 640x512(PAL) + http://aap.rockstarvision.com/pics/gta3/ps2screens/gta3_interface.jpg + but failed: + http://aap.rockstarvision.com/pics/gta3/artwork/gta3_artwork_16.jpg + most likely the guy used something like this: + int y = 82 * (640.0/512.0)/(640.0/480.0); + int x = y * (640.0/512.0); +*/ +#define RADAR_WIDTH (94.0f) +#define RADAR_HEIGHT (76.0f) +#endif + +class CRadar +{ +public: + static float m_radarRange; + static sRadarTrace ms_RadarTrace[NUMRADARBLIPS]; + static CSprite2d AsukaSprite; + static CSprite2d BombSprite; + static CSprite2d CatSprite; + static CSprite2d CentreSprite; + static CSprite2d CopcarSprite; + static CSprite2d DonSprite; + static CSprite2d EightSprite; + static CSprite2d ElSprite; + static CSprite2d IceSprite; + static CSprite2d JoeySprite; + static CSprite2d KenjiSprite; + static CSprite2d LizSprite; + static CSprite2d LuigiSprite; + static CSprite2d NorthSprite; + static CSprite2d RaySprite; + static CSprite2d SalSprite; + static CSprite2d SaveSprite; + static CSprite2d SpraySprite; + static CSprite2d TonySprite; + static CSprite2d WeaponSprite; + static CSprite2d *RadarSprites[RADAR_SPRITE_COUNT]; + static float cachedCos; + static float cachedSin; +#ifdef MENU_MAP + static CSprite2d WaypointSprite; + static int TargetMarkerId; + static CVector TargetMarkerPos; + + static void InitFrontEndMap(); + static void DrawYouAreHereSprite(float, float); + static void ToggleTargetMarker(float, float); +#endif + static uint8 CalculateBlipAlpha(float dist); + static void ChangeBlipBrightness(int32 i, int32 bright); + static void ChangeBlipColour(int32 i, int32); + static void ChangeBlipDisplay(int32 i, eBlipDisplay display); + static void ChangeBlipScale(int32 i, int32 scale); + static void ClearBlip(int32 i); + static void ClearBlipForEntity(eBlipType type, int32 id); + static int ClipRadarPoly(CVector2D *out, const CVector2D *in); + static bool DisplayThisBlip(int32 i); + static void Draw3dMarkers(); + static void DrawBlips(); + static void DrawMap(); + static void DrawRadarMap(); + static void DrawRadarMask(); + static void DrawRadarSection(int32 x, int32 y); + static void DrawRadarSprite(uint16 sprite, float x, float y, uint8 alpha); + static void DrawRotatingRadarSprite(CSprite2d* sprite, float x, float y, float angle, int32 alpha); + static int32 GetActualBlipArrayIndex(int32 i); + static int32 GetNewUniqueBlipIndex(int32 i); + static uint32 GetRadarTraceColour(uint32 color, bool bright); + static void Initialise(); + static float LimitRadarPoint(CVector2D &point); + static void LoadAllRadarBlips(uint8 *buf, uint32 size); + static void LoadTextures(); + static void RemoveRadarSections(); + static void SaveAllRadarBlips(uint8*, uint32*); + static void SetBlipSprite(int32 i, int32 icon); + static int32 SetCoordBlip(eBlipType type, CVector pos, int32, eBlipDisplay); + static int32 SetEntityBlip(eBlipType type, int32, int32, eBlipDisplay); + static void SetRadarMarkerState(int32 i, bool flag); + static void ShowRadarMarker(CVector pos, uint32 color, float radius); + static void ShowRadarTrace(float x, float y, uint32 size, uint8 red, uint8 green, uint8 blue, uint8 alpha); + static void ShowRadarTraceWithHeight(float x, float y, uint32 size, uint8 red, uint8 green, uint8 blue, uint8 alpha, uint8 mode); + static void Shutdown(); + static void StreamRadarSections(const CVector &posn); + static void StreamRadarSections(int32 x, int32 y); + static void TransformRealWorldToTexCoordSpace(CVector2D &out, const CVector2D &in, int32 x, int32 y); + static void TransformRadarPointToRealWorldSpace(CVector2D &out, const CVector2D &in); + static void TransformRadarPointToScreenSpace(CVector2D &out, const CVector2D &in); + static void TransformRealWorldPointToRadarSpace(CVector2D &out, const CVector2D &in); + + // no in CRadar in the game: + static void CalculateCachedSinCos(); +}; diff --git a/src/core/Range2D.cpp b/src/core/Range2D.cpp new file mode 100644 index 0000000..33eafd0 --- /dev/null +++ b/src/core/Range2D.cpp @@ -0,0 +1,28 @@ +#include "common.h" +#include "Range2D.h" +#include "General.h" + +CRange2D::CRange2D(CVector2D _min, CVector2D _max) : min(_min), max(_max) {} + +bool +CRange2D::IsInRange(CVector2D vec) +{ + return min.x < vec.x && max.x > vec.x && min.y < vec.y && max.y > vec.y; +} + +void +CRange2D::DebugShowRange(float, int) +{ +} + +CVector2D +CRange2D::GetRandomPointInRange() +{ + int distX = Abs(max.x - min.x); + int distY = Abs(max.y - min.y); + + float outX = CGeneral::GetRandomNumber() % distX + min.x; + float outY = CGeneral::GetRandomNumber() % distY + min.y; + + return CVector2D(outX, outY); +} diff --git a/src/core/Range2D.h b/src/core/Range2D.h new file mode 100644 index 0000000..f239e7d --- /dev/null +++ b/src/core/Range2D.h @@ -0,0 +1,11 @@ +#pragma once + +class CRange2D +{ + CVector2D min, max; +public: + CRange2D(CVector2D _min, CVector2D _max); + bool IsInRange(CVector2D vec); + void DebugShowRange(float, int); + CVector2D GetRandomPointInRange(); +}; \ No newline at end of file diff --git a/src/core/Range3D.cpp b/src/core/Range3D.cpp new file mode 100644 index 0000000..7fa28d6 --- /dev/null +++ b/src/core/Range3D.cpp @@ -0,0 +1,30 @@ +#include "common.h" +#include "Range3D.h" +#include "General.h" + +CRange3D::CRange3D(CVector _min, CVector _max) : min(_min), max(_max) {} + +bool +CRange3D::IsInRange(CVector vec) +{ + return min.x < vec.x && max.x > vec.x && min.y < vec.y && max.y > vec.y && min.z < vec.z && max.z > vec.z; +} + +void +CRange3D::DebugShowRange(float, int) +{ +} + +CVector +CRange3D::GetRandomPointInRange() +{ + int distX = Abs(max.x - min.x); + int distY = Abs(max.y - min.y); + int distZ = Abs(max.z - min.z); + + float outX = CGeneral::GetRandomNumber() % distX + min.x; + float outY = CGeneral::GetRandomNumber() % distY + min.y; + float outZ = CGeneral::GetRandomNumber() % distZ + min.z; + + return CVector(outX, outY, outZ); +} diff --git a/src/core/Range3D.h b/src/core/Range3D.h new file mode 100644 index 0000000..f42b523 --- /dev/null +++ b/src/core/Range3D.h @@ -0,0 +1,11 @@ +#pragma once + +class CRange3D +{ + CVector min, max; +public: + CRange3D(CVector _min, CVector _max); + bool IsInRange(CVector vec); + void DebugShowRange(float, int); + CVector GetRandomPointInRange(); +}; \ No newline at end of file diff --git a/src/core/References.cpp b/src/core/References.cpp new file mode 100644 index 0000000..b778209 --- /dev/null +++ b/src/core/References.cpp @@ -0,0 +1,126 @@ +#include "common.h" + +#include "World.h" +#include "Vehicle.h" +#include "PlayerPed.h" +#include "Pools.h" +#include "References.h" + +CReference CReferences::aRefs[NUMREFERENCES]; +CReference *CReferences::pEmptyList; + +void +CReferences::Init(void) +{ + int i; + pEmptyList = &aRefs[0]; + for(i = 0; i < NUMREFERENCES; i++){ + aRefs[i].pentity = nil; + aRefs[i].next = &aRefs[i+1]; + } + aRefs[NUMREFERENCES-1].next = nil; +} + +void +CEntity::RegisterReference(CEntity **pent) +{ + if(IsBuilding()) + return; + CReference *ref; + // check if already registered + for(ref = m_pFirstReference; ref; ref = ref->next) + if(ref->pentity == pent) + return; + // have to allocate new reference + ref = CReferences::pEmptyList; + if(ref){ + CReferences::pEmptyList = ref->next; + + ref->pentity = pent; + ref->next = m_pFirstReference; + m_pFirstReference = ref; + } +} + +// Clear all references to this entity +void +CEntity::ResolveReferences(void) +{ + CReference *ref; + // clear pointers to this entity + for(ref = m_pFirstReference; ref; ref = ref->next) + if(*ref->pentity == this) + *ref->pentity = nil; + // free list + if(m_pFirstReference){ + for(ref = m_pFirstReference; ref->next; ref = ref->next) + ; + ref->next = CReferences::pEmptyList; + CReferences::pEmptyList = m_pFirstReference; + m_pFirstReference = nil; + } +} + +// Free all references that no longer point to this entity +void +CEntity::PruneReferences(void) +{ + CReference *ref, *next, **lastnextp; + lastnextp = &m_pFirstReference; + for(ref = m_pFirstReference; ref; ref = next){ + next = ref->next; + if(*ref->pentity == this) + lastnextp = &ref->next; + else{ + *lastnextp = ref->next; + ref->next = CReferences::pEmptyList; + CReferences::pEmptyList = ref; + } + } +} + +void +CReferences::RemoveReferencesToPlayer(void) +{ + if(FindPlayerVehicle()) + FindPlayerVehicle()->ResolveReferences(); +#ifdef FIX_BUGS + if (FindPlayerPed()) { + CPlayerPed* pPlayerPed = FindPlayerPed(); + FindPlayerPed()->ResolveReferences(); + CWorld::Players[CWorld::PlayerInFocus].m_pPed = pPlayerPed; + pPlayerPed->RegisterReference((CEntity**)&CWorld::Players[CWorld::PlayerInFocus].m_pPed); + } +#else + if(FindPlayerPed()) + FindPlayerPed()->ResolveReferences(); +#endif +} + +void +CReferences::PruneAllReferencesInWorld(void) +{ + int i; + CEntity *e; + + i = CPools::GetPedPool()->GetSize(); + while(--i >= 0){ + e = CPools::GetPedPool()->GetSlot(i); + if(e) + e->PruneReferences(); + } + + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + e = CPools::GetVehiclePool()->GetSlot(i); + if(e) + e->PruneReferences(); + } + + i = CPools::GetObjectPool()->GetSize(); + while(--i >= 0){ + e = CPools::GetObjectPool()->GetSlot(i); + if(e) + e->PruneReferences(); + } +} diff --git a/src/core/References.h b/src/core/References.h new file mode 100644 index 0000000..e7a64de --- /dev/null +++ b/src/core/References.h @@ -0,0 +1,20 @@ +#pragma once + +class CEntity; + +struct CReference +{ + CReference *next; + CEntity **pentity; +}; + +class CReferences +{ +public: + static CReference aRefs[NUMREFERENCES]; + static CReference *pEmptyList; + + static void Init(void); + static void RemoveReferencesToPlayer(void); + static void PruneAllReferencesInWorld(void); +}; diff --git a/src/core/Stats.cpp b/src/core/Stats.cpp new file mode 100644 index 0000000..9afd8ac --- /dev/null +++ b/src/core/Stats.cpp @@ -0,0 +1,420 @@ +#include "common.h" + +#include "Stats.h" +#include "Text.h" +#include "World.h" + +int32 CStats::DaysPassed; +int32 CStats::HeadsPopped; +int32 CStats::CommercialPassed; +int32 CStats::IndustrialPassed; +int32 CStats::SuburbanPassed; +int32 CStats::NumberKillFrenziesPassed; +int32 CStats::PeopleKilledByOthers; +int32 CStats::HelisDestroyed; +int32 CStats::PedsKilledOfThisType[NUM_PEDTYPES]; +int32 CStats::TimesDied; +int32 CStats::TimesArrested; +int32 CStats::KillsSinceLastCheckpoint; +float CStats::DistanceTravelledInVehicle; +float CStats::DistanceTravelledOnFoot; +int32 CStats::ProgressMade; +int32 CStats::TotalProgressInGame; +int32 CStats::CarsExploded; +int32 CStats::PeopleKilledByPlayer; +float CStats::MaximumJumpDistance; +float CStats::MaximumJumpHeight; +int32 CStats::MaximumJumpFlips; +int32 CStats::MaximumJumpSpins; +int32 CStats::BestStuntJump; +int32 CStats::NumberOfUniqueJumpsFound; +int32 CStats::TotalNumberOfUniqueJumps; +int32 CStats::PassengersDroppedOffWithTaxi; +int32 CStats::MoneyMadeWithTaxi; +int32 CStats::MissionsGiven; +int32 CStats::MissionsPassed; +char CStats::LastMissionPassedName[8]; +int32 CStats::TotalLegitimateKills; +int32 CStats::ElBurroTime; +int32 CStats::Record4x4One; +int32 CStats::Record4x4Two; +int32 CStats::Record4x4Three; +int32 CStats::Record4x4Mayhem; +int32 CStats::LivesSavedWithAmbulance; +int32 CStats::CriminalsCaught; +int32 CStats::HighestLevelAmbulanceMission; +int32 CStats::FiresExtinguished; +int32 CStats::LongestFlightInDodo; +int32 CStats::TimeTakenDefuseMission; +int32 CStats::TotalNumberKillFrenzies; +int32 CStats::TotalNumberMissions; +int32 CStats::RoundsFiredByPlayer; +int32 CStats::KgsOfExplosivesUsed; +int32 CStats::InstantHitsFiredByPlayer; +int32 CStats::InstantHitsHitByPlayer; +int32 CStats::BestTimeBombDefusal; +int32 CStats::mmRain; +int32 CStats::CarsCrushed; +int32 CStats::FastestTimes[CStats::TOTAL_FASTEST_TIMES]; +int32 CStats::HighestScores[CStats::TOTAL_HIGHEST_SCORES]; + +void CStats::Init() +{ + PeopleKilledByOthers = 0; + PeopleKilledByPlayer = 0; + RoundsFiredByPlayer = 0; + CarsExploded = 0; + HelisDestroyed = 0; + ProgressMade = 0; + KgsOfExplosivesUsed = 0; + InstantHitsFiredByPlayer = 0; + InstantHitsHitByPlayer = 0; + CarsCrushed = 0; + HeadsPopped = 0; + TimesArrested = 0; + TimesDied = 0; + DaysPassed = 0; + NumberOfUniqueJumpsFound = 0; + mmRain = 0; + MaximumJumpFlips = 0; + MaximumJumpSpins = 0; + MaximumJumpDistance = 0; + MaximumJumpHeight = 0; + BestStuntJump = 0; + TotalNumberOfUniqueJumps = 0; + Record4x4One = 0; + LongestFlightInDodo = 0; + Record4x4Two = 0; + PassengersDroppedOffWithTaxi = 0; + Record4x4Three = 0; + MoneyMadeWithTaxi = 0; + Record4x4Mayhem = 0; + LivesSavedWithAmbulance = 0; + ElBurroTime = 0; + CriminalsCaught = 0; + MissionsGiven = 0; + HighestLevelAmbulanceMission = 0; + MissionsPassed = 0; + FiresExtinguished = 0; + DistanceTravelledOnFoot = 0; + TimeTakenDefuseMission = 0; + NumberKillFrenziesPassed = 0; + DistanceTravelledInVehicle = 0; + TotalNumberKillFrenzies = 0; + TotalNumberMissions = 0; + KillsSinceLastCheckpoint = 0; + TotalLegitimateKills = 0; + for (int i = 0; i < TOTAL_FASTEST_TIMES; i++) + FastestTimes[i] = 0; + for (int i = 0; i < TOTAL_HIGHEST_SCORES; i++) + HighestScores[i] = 0; + for (int i = 0; i < NUM_PEDTYPES; i++) + PedsKilledOfThisType[i] = 0; + IndustrialPassed = 0; + CommercialPassed = 0; + SuburbanPassed = 0; +} + +void CStats::RegisterFastestTime(int32 index, int32 time) +{ + assert(index >= 0 && index < TOTAL_FASTEST_TIMES); + if (FastestTimes[index] == 0) + FastestTimes[index] = time; + else + FastestTimes[index] = Min(FastestTimes[index], time); +} + +void CStats::RegisterHighestScore(int32 index, int32 score) +{ + assert(index >= 0 && index < TOTAL_HIGHEST_SCORES); + HighestScores[index] = Max(HighestScores[index], score); +} + +void CStats::RegisterElBurroTime(int32 time) +{ + ElBurroTime = (ElBurroTime && ElBurroTime < time) ? ElBurroTime : time; +} + +void CStats::Register4x4OneTime(int32 time) +{ + Record4x4One = (Record4x4One && Record4x4One < time) ? Record4x4One : time; +} + +void CStats::Register4x4TwoTime(int32 time) +{ + Record4x4Two = (Record4x4Two && Record4x4Two < time) ? Record4x4Two : time; +} + +void CStats::Register4x4ThreeTime(int32 time) +{ + Record4x4Three = (Record4x4Three && Record4x4Three < time) ? Record4x4Three : time; +} + +void CStats::Register4x4MayhemTime(int32 time) +{ + Record4x4Mayhem = (Record4x4Mayhem && Record4x4Mayhem < time) ? Record4x4Mayhem : time; +} + +void CStats::AnotherLifeSavedWithAmbulance() +{ + ++LivesSavedWithAmbulance; +} + +void CStats::AnotherCriminalCaught() +{ + ++CriminalsCaught; +} + +void CStats::RegisterLevelAmbulanceMission(int32 level) +{ + HighestLevelAmbulanceMission = Max(HighestLevelAmbulanceMission, level); +} + +void CStats::AnotherFireExtinguished() +{ + ++FiresExtinguished; +} + +void CStats::RegisterLongestFlightInDodo(int32 time) +{ + LongestFlightInDodo = Max(LongestFlightInDodo, time); +} + +void CStats::RegisterTimeTakenDefuseMission(int32 time) +{ + TimeTakenDefuseMission = (TimeTakenDefuseMission && TimeTakenDefuseMission < time) ? TimeTakenDefuseMission : time; +} + +void CStats::AnotherKillFrenzyPassed() +{ + ++NumberKillFrenziesPassed; +} + +void CStats::SetTotalNumberKillFrenzies(int32 total) +{ + TotalNumberKillFrenzies = total; +} + +void CStats::SetTotalNumberMissions(int32 total) +{ + TotalNumberMissions = total; +} + +wchar *CStats::FindCriminalRatingString() +{ + int rating = FindCriminalRatingNumber(); + + if (rating < 10) return TheText.Get("RATNG1"); + if (rating < 25) return TheText.Get("RATNG2"); + if (rating < 70) return TheText.Get("RATNG3"); + if (rating < 150) return TheText.Get("RATNG4"); + if (rating < 250) return TheText.Get("RATNG5"); + if (rating < 450) return TheText.Get("RATNG6"); + if (rating < 700) return TheText.Get("RATNG7"); + if (rating < 1000) return TheText.Get("RATNG8"); + if (rating < 1400) return TheText.Get("RATNG9"); + if (rating < 1900) return TheText.Get("RATNG10"); + if (rating < 2500) return TheText.Get("RATNG11"); + if (rating < 3200) return TheText.Get("RATNG12"); + if (rating < 4000) return TheText.Get("RATNG13"); + if (rating < 5000) return TheText.Get("RATNG14"); + return TheText.Get("RATNG15"); +} + +int32 CStats::FindCriminalRatingNumber() +{ + int32 rating; + + rating = FiresExtinguished + 10 * HighestLevelAmbulanceMission + CriminalsCaught + LivesSavedWithAmbulance + + 30 * HelisDestroyed + TotalLegitimateKills - 3 * TimesArrested - 3 * TimesDied + + CWorld::Players[CWorld::PlayerInFocus].m_nMoney / 5000; + if (rating <= 0) rating = 0; + + if (InstantHitsFiredByPlayer > 100) + rating += (float)CStats::InstantHitsHitByPlayer / (float)CStats::InstantHitsFiredByPlayer * 500.0f; + if (TotalProgressInGame) + rating += (float)CStats::ProgressMade / (float)CStats::TotalProgressInGame * 1000.0f; + if (!IndustrialPassed && rating >= 3521) + rating = 3521; + if (!CommercialPassed && rating >= 4552) + rating = 4552; + return rating; +} + +void CStats::SaveStats(uint8 *buf, uint32 *size) +{ + CheckPointReachedSuccessfully(); + uint8 *buf_start = buf; + *size = sizeof(PeopleKilledByPlayer) + + sizeof(PeopleKilledByOthers) + + sizeof(CarsExploded) + + sizeof(RoundsFiredByPlayer) + + sizeof(PedsKilledOfThisType) + + sizeof(HelisDestroyed) + + sizeof(ProgressMade) + + sizeof(TotalProgressInGame) + + sizeof(KgsOfExplosivesUsed) + + sizeof(InstantHitsFiredByPlayer) + + sizeof(InstantHitsHitByPlayer) + + sizeof(CarsCrushed) + + sizeof(HeadsPopped) + + sizeof(TimesArrested) + + sizeof(TimesDied) + + sizeof(DaysPassed) + + sizeof(mmRain) + + sizeof(MaximumJumpDistance) + + sizeof(MaximumJumpHeight) + + sizeof(MaximumJumpFlips) + + sizeof(MaximumJumpSpins) + + sizeof(BestStuntJump) + + sizeof(NumberOfUniqueJumpsFound) + + sizeof(TotalNumberOfUniqueJumps) + + sizeof(MissionsGiven) + + sizeof(MissionsPassed) + + sizeof(PassengersDroppedOffWithTaxi) + + sizeof(MoneyMadeWithTaxi) + + sizeof(IndustrialPassed) + + sizeof(CommercialPassed) + + sizeof(SuburbanPassed) + + sizeof(ElBurroTime) + + sizeof(DistanceTravelledOnFoot) + + sizeof(DistanceTravelledInVehicle) + + sizeof(Record4x4One) + + sizeof(Record4x4Two) + + sizeof(Record4x4Three) + + sizeof(Record4x4Mayhem) + + sizeof(LivesSavedWithAmbulance) + + sizeof(CriminalsCaught) + + sizeof(HighestLevelAmbulanceMission) + + sizeof(FiresExtinguished) + + sizeof(LongestFlightInDodo) + + sizeof(TimeTakenDefuseMission) + + sizeof(NumberKillFrenziesPassed) + + sizeof(TotalNumberKillFrenzies) + + sizeof(TotalNumberMissions) + + sizeof(FastestTimes) + + sizeof(HighestScores) + + sizeof(KillsSinceLastCheckpoint) + + sizeof(TotalLegitimateKills) + + sizeof(LastMissionPassedName); + +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); buf += sizeof(data); + CopyToBuf(buf, PeopleKilledByPlayer); + CopyToBuf(buf, PeopleKilledByOthers); + CopyToBuf(buf, CarsExploded); + CopyToBuf(buf, RoundsFiredByPlayer); + CopyToBuf(buf, PedsKilledOfThisType); + CopyToBuf(buf, HelisDestroyed); + CopyToBuf(buf, ProgressMade); + CopyToBuf(buf, TotalProgressInGame); + CopyToBuf(buf, KgsOfExplosivesUsed); + CopyToBuf(buf, InstantHitsFiredByPlayer); + CopyToBuf(buf, InstantHitsHitByPlayer); + CopyToBuf(buf, CarsCrushed); + CopyToBuf(buf, HeadsPopped); + CopyToBuf(buf, TimesArrested); + CopyToBuf(buf, TimesDied); + CopyToBuf(buf, DaysPassed); + CopyToBuf(buf, mmRain); + CopyToBuf(buf, MaximumJumpDistance); + CopyToBuf(buf, MaximumJumpHeight); + CopyToBuf(buf, MaximumJumpFlips); + CopyToBuf(buf, MaximumJumpSpins); + CopyToBuf(buf, BestStuntJump); + CopyToBuf(buf, NumberOfUniqueJumpsFound); + CopyToBuf(buf, TotalNumberOfUniqueJumps); + CopyToBuf(buf, MissionsGiven); + CopyToBuf(buf, MissionsPassed); + CopyToBuf(buf, PassengersDroppedOffWithTaxi); + CopyToBuf(buf, MoneyMadeWithTaxi); + CopyToBuf(buf, IndustrialPassed); + CopyToBuf(buf, CommercialPassed); + CopyToBuf(buf, SuburbanPassed); + CopyToBuf(buf, ElBurroTime); + CopyToBuf(buf, DistanceTravelledOnFoot); + CopyToBuf(buf, DistanceTravelledInVehicle); + CopyToBuf(buf, Record4x4One); + CopyToBuf(buf, Record4x4Two); + CopyToBuf(buf, Record4x4Three); + CopyToBuf(buf, Record4x4Mayhem); + CopyToBuf(buf, LivesSavedWithAmbulance); + CopyToBuf(buf, CriminalsCaught); + CopyToBuf(buf, HighestLevelAmbulanceMission); + CopyToBuf(buf, FiresExtinguished); + CopyToBuf(buf, LongestFlightInDodo); + CopyToBuf(buf, TimeTakenDefuseMission); + CopyToBuf(buf, NumberKillFrenziesPassed); + CopyToBuf(buf, TotalNumberKillFrenzies); + CopyToBuf(buf, TotalNumberMissions); + CopyToBuf(buf, FastestTimes); + CopyToBuf(buf, HighestScores); + CopyToBuf(buf, KillsSinceLastCheckpoint); + CopyToBuf(buf, TotalLegitimateKills); + CopyToBuf(buf, LastMissionPassedName); + + assert(buf - buf_start == *size); +#undef CopyToBuf +} + +void CStats::LoadStats(uint8 *buf, uint32 size) +{ + uint8* buf_start = buf; + +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); buf += sizeof(data); + + CopyFromBuf(buf, PeopleKilledByPlayer); + CopyFromBuf(buf, PeopleKilledByOthers); + CopyFromBuf(buf, CarsExploded); + CopyFromBuf(buf, RoundsFiredByPlayer); + CopyFromBuf(buf, PedsKilledOfThisType); + CopyFromBuf(buf, HelisDestroyed); + CopyFromBuf(buf, ProgressMade); + CopyFromBuf(buf, TotalProgressInGame); + CopyFromBuf(buf, KgsOfExplosivesUsed); + CopyFromBuf(buf, InstantHitsFiredByPlayer); + CopyFromBuf(buf, InstantHitsHitByPlayer); + CopyFromBuf(buf, CarsCrushed); + CopyFromBuf(buf, HeadsPopped); + CopyFromBuf(buf, TimesArrested); + CopyFromBuf(buf, TimesDied); + CopyFromBuf(buf, DaysPassed); + CopyFromBuf(buf, mmRain); + CopyFromBuf(buf, MaximumJumpDistance); + CopyFromBuf(buf, MaximumJumpHeight); + CopyFromBuf(buf, MaximumJumpFlips); + CopyFromBuf(buf, MaximumJumpSpins); + CopyFromBuf(buf, BestStuntJump); + CopyFromBuf(buf, NumberOfUniqueJumpsFound); + CopyFromBuf(buf, TotalNumberOfUniqueJumps); + CopyFromBuf(buf, MissionsGiven); + CopyFromBuf(buf, MissionsPassed); + CopyFromBuf(buf, PassengersDroppedOffWithTaxi); + CopyFromBuf(buf, MoneyMadeWithTaxi); + CopyFromBuf(buf, IndustrialPassed); + CopyFromBuf(buf, CommercialPassed); + CopyFromBuf(buf, SuburbanPassed); + CopyFromBuf(buf, ElBurroTime); + CopyFromBuf(buf, DistanceTravelledOnFoot); + CopyFromBuf(buf, DistanceTravelledInVehicle); + CopyFromBuf(buf, Record4x4One); + CopyFromBuf(buf, Record4x4Two); + CopyFromBuf(buf, Record4x4Three); + CopyFromBuf(buf, Record4x4Mayhem); + CopyFromBuf(buf, LivesSavedWithAmbulance); + CopyFromBuf(buf, CriminalsCaught); + CopyFromBuf(buf, HighestLevelAmbulanceMission); + CopyFromBuf(buf, FiresExtinguished); + CopyFromBuf(buf, LongestFlightInDodo); + CopyFromBuf(buf, TimeTakenDefuseMission); + CopyFromBuf(buf, NumberKillFrenziesPassed); + CopyFromBuf(buf, TotalNumberKillFrenzies); + CopyFromBuf(buf, TotalNumberMissions); + CopyFromBuf(buf, FastestTimes); + CopyFromBuf(buf, HighestScores); + CopyFromBuf(buf, KillsSinceLastCheckpoint); + CopyFromBuf(buf, TotalLegitimateKills); + CopyFromBuf(buf, LastMissionPassedName); + + assert(buf - buf_start == size); +#undef CopyFromBuf +} diff --git a/src/core/Stats.h b/src/core/Stats.h new file mode 100644 index 0000000..6abcfb6 --- /dev/null +++ b/src/core/Stats.h @@ -0,0 +1,90 @@ +#pragma once + +#include "PedType.h" + +class CStats +{ +public: + enum { + TOTAL_FASTEST_TIMES = 16, + TOTAL_HIGHEST_SCORES = 16 + }; + static int32 DaysPassed; + static int32 HeadsPopped; + static int32 CommercialPassed; + static int32 IndustrialPassed; + static int32 SuburbanPassed; + static int32 NumberKillFrenziesPassed; + static int32 PeopleKilledByOthers; + static int32 HelisDestroyed; + static int32 PedsKilledOfThisType[NUM_PEDTYPES]; + static int32 TimesDied; + static int32 TimesArrested; + static int32 KillsSinceLastCheckpoint; + static float DistanceTravelledInVehicle; + static float DistanceTravelledOnFoot; + static int32 CarsExploded; + static int32 PeopleKilledByPlayer; + static int32 ProgressMade; + static int32 TotalProgressInGame; + static float MaximumJumpDistance; + static float MaximumJumpHeight; + static int32 MaximumJumpFlips; + static int32 MaximumJumpSpins; + static int32 BestStuntJump; + static int32 NumberOfUniqueJumpsFound; + static int32 TotalNumberOfUniqueJumps; + static int32 PassengersDroppedOffWithTaxi; + static int32 MoneyMadeWithTaxi; + static int32 MissionsGiven; + static int32 MissionsPassed; + static char LastMissionPassedName[8]; + static int32 TotalLegitimateKills; + static int32 ElBurroTime; + static int32 Record4x4One; + static int32 Record4x4Two; + static int32 Record4x4Three; + static int32 Record4x4Mayhem; + static int32 LivesSavedWithAmbulance; + static int32 CriminalsCaught; + static int32 HighestLevelAmbulanceMission; + static int32 FiresExtinguished; + static int32 LongestFlightInDodo; + static int32 TimeTakenDefuseMission; + static int32 TotalNumberKillFrenzies; + static int32 TotalNumberMissions; + static int32 RoundsFiredByPlayer; + static int32 KgsOfExplosivesUsed; + static int32 InstantHitsFiredByPlayer; + static int32 InstantHitsHitByPlayer; + static int32 BestTimeBombDefusal; + static int32 mmRain; + static int32 CarsCrushed; + static int32 FastestTimes[TOTAL_FASTEST_TIMES]; + static int32 HighestScores[TOTAL_HIGHEST_SCORES]; + +public: + static void Init(void); + static void RegisterFastestTime(int32, int32); + static void RegisterHighestScore(int32, int32); + static void RegisterElBurroTime(int32); + static void Register4x4OneTime(int32); + static void Register4x4TwoTime(int32); + static void Register4x4ThreeTime(int32); + static void Register4x4MayhemTime(int32); + static void AnotherLifeSavedWithAmbulance(); + static void AnotherCriminalCaught(); + static void RegisterLevelAmbulanceMission(int32); + static void AnotherFireExtinguished(); + static wchar *FindCriminalRatingString(); + static void RegisterLongestFlightInDodo(int32); + static void RegisterTimeTakenDefuseMission(int32); + static void AnotherKillFrenzyPassed(); + static void SetTotalNumberKillFrenzies(int32); + static void SetTotalNumberMissions(int32); + static void CheckPointReachedSuccessfully() { TotalLegitimateKills += KillsSinceLastCheckpoint; KillsSinceLastCheckpoint = 0; }; + static void CheckPointReachedUnsuccessfully() { KillsSinceLastCheckpoint = 0; }; + static int32 FindCriminalRatingNumber(); + static void SaveStats(uint8 *buf, uint32 *size); + static void LoadStats(uint8 *buf, uint32 size); +}; diff --git a/src/core/Streaming.cpp b/src/core/Streaming.cpp new file mode 100644 index 0000000..9ac2209 --- /dev/null +++ b/src/core/Streaming.cpp @@ -0,0 +1,2831 @@ +#include "common.h" + +#include "General.h" +#include "Pad.h" +#include "Hud.h" +#include "Text.h" +#include "Clock.h" +#include "Renderer.h" +#include "ModelInfo.h" +#include "TxdStore.h" +#include "ModelIndices.h" +#include "Pools.h" +#include "Wanted.h" +#include "Directory.h" +#include "RwHelper.h" +#include "World.h" +#include "Entity.h" +#include "FileMgr.h" +#include "FileLoader.h" +#include "Zones.h" +#include "ZoneCull.h" +#include "Radar.h" +#include "Camera.h" +#include "Record.h" +#include "CarCtrl.h" +#include "Population.h" +#include "Gangs.h" +#include "CutsceneMgr.h" +#include "CdStream.h" +#include "Streaming.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif +#include "main.h" +#include "Frontend.h" +#include "Font.h" +#include "MemoryMgr.h" +#include "MemoryHeap.h" + +bool CStreaming::ms_disableStreaming; +bool CStreaming::ms_bLoadingBigModel; +int32 CStreaming::ms_numModelsRequested; +CStreamingInfo CStreaming::ms_aInfoForModel[NUMSTREAMINFO]; +CStreamingInfo CStreaming::ms_startLoadedList; +CStreamingInfo CStreaming::ms_endLoadedList; +CStreamingInfo CStreaming::ms_startRequestedList; +CStreamingInfo CStreaming::ms_endRequestedList; +int32 CStreaming::ms_oldSectorX; +int32 CStreaming::ms_oldSectorY; +int32 CStreaming::ms_streamingBufferSize; +#ifndef ONE_THREAD_PER_CHANNEL +int8 *CStreaming::ms_pStreamingBuffer[2]; +#else +int8 *CStreaming::ms_pStreamingBuffer[4]; +#endif +size_t CStreaming::ms_memoryUsed; +CStreamingChannel CStreaming::ms_channel[2]; +int32 CStreaming::ms_channelError; +int32 CStreaming::ms_numVehiclesLoaded; +int32 CStreaming::ms_vehiclesLoaded[MAXVEHICLESLOADED]; +int32 CStreaming::ms_lastVehicleDeleted; +CDirectory *CStreaming::ms_pExtraObjectsDir; +int32 CStreaming::ms_numPriorityRequests; +bool CStreaming::ms_hasLoadedLODs; +int32 CStreaming::ms_currentPedGrp; +int32 CStreaming::ms_currentPedLoading; +int32 CStreaming::ms_lastCullZone; +uint16 CStreaming::ms_loadedGangs; +uint16 CStreaming::ms_loadedGangCars; +int32 CStreaming::ms_imageOffsets[NUMCDIMAGES]; +int32 CStreaming::ms_lastImageRead; +int32 CStreaming::ms_imageSize; +size_t CStreaming::ms_memoryAvailable; + +int32 desiredNumVehiclesLoaded = 12; + +CEntity *pIslandLODindustEntity; +CEntity *pIslandLODcomIndEntity; +CEntity *pIslandLODcomSubEntity; +CEntity *pIslandLODsubIndEntity; +CEntity *pIslandLODsubComEntity; +int32 islandLODindust; +int32 islandLODcomInd; +int32 islandLODcomSub; +int32 islandLODsubInd; +int32 islandLODsubCom; + +bool +CStreamingInfo::GetCdPosnAndSize(uint32 &posn, uint32 &size) +{ + if(m_size == 0) + return false; + posn = m_position; + size = m_size; + return true; +} + +void +CStreamingInfo::SetCdPosnAndSize(uint32 posn, uint32 size) +{ + m_position = posn; + m_size = size; +} + +void +CStreamingInfo::AddToList(CStreamingInfo *link) +{ + // Insert this after link + m_next = link->m_next; + m_prev = link; + link->m_next = this; + m_next->m_prev = this; +} + +void +CStreamingInfo::RemoveFromList(void) +{ + m_next->m_prev = m_prev; + m_prev->m_next = m_next; + m_next = nil; + m_prev = nil; +} + +void +CStreaming::Init2(void) +{ + int i; + + for(i = 0; i < NUMSTREAMINFO; i++){ + ms_aInfoForModel[i].m_loadState = STREAMSTATE_NOTLOADED; + ms_aInfoForModel[i].m_next = nil; + ms_aInfoForModel[i].m_prev = nil; + ms_aInfoForModel[i].m_nextID = -1; + ms_aInfoForModel[i].m_size = 0; + ms_aInfoForModel[i].m_position = 0; + } + + ms_channelError = -1; + + // init lists + + ms_startLoadedList.m_next = &ms_endLoadedList; + ms_startLoadedList.m_prev = nil; + ms_endLoadedList.m_prev = &ms_startLoadedList; + ms_endLoadedList.m_next = nil; + + ms_startRequestedList.m_next = &ms_endRequestedList; + ms_startRequestedList.m_prev = nil; + ms_endRequestedList.m_prev = &ms_startRequestedList; + ms_endRequestedList.m_next = nil; + + // init misc + + ms_oldSectorX = 0; + ms_oldSectorY = 0; + ms_streamingBufferSize = 0; + ms_disableStreaming = false; + ms_memoryUsed = 0; + ms_bLoadingBigModel = false; + + // init channels + + ms_channel[0].state = CHANNELSTATE_IDLE; + ms_channel[1].state = CHANNELSTATE_IDLE; + for(i = 0; i < 4; i++){ + ms_channel[0].streamIds[i] = -1; + ms_channel[0].offsets[i] = -1; + ms_channel[1].streamIds[i] = -1; + ms_channel[1].offsets[i] = -1; + } + + // init stream info, mark things that are already loaded + + for(i = 0; i < MODELINFOSIZE; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(i); + if(mi && mi->GetRwObject()){ + ms_aInfoForModel[i].m_loadState = STREAMSTATE_LOADED; + ms_aInfoForModel[i].m_flags = STREAMFLAGS_DONT_REMOVE; + if(mi->IsSimple()) + ((CSimpleModelInfo*)mi)->m_alpha = 255; + } + } + + for(i = 0; i < TXDSTORESIZE; i++) + if(CTxdStore::GetSlot(i) && CTxdStore::GetSlot(i)->texDict) + ms_aInfoForModel[i + STREAM_OFFSET_TXD].m_loadState = STREAMSTATE_LOADED; + + + for(i = 0; i < MAXVEHICLESLOADED; i++) + ms_vehiclesLoaded[i] = -1; + ms_numVehiclesLoaded = 0; + + ms_pExtraObjectsDir = new CDirectory(EXTRADIRSIZE); + ms_numPriorityRequests = 0; + ms_hasLoadedLODs = true; + ms_currentPedGrp = -1; + ms_lastCullZone = -1; // unused because RemoveModelsNotVisibleFromCullzone is gone + ms_loadedGangs = 0; + ms_currentPedLoading = 8; // unused, whatever it is + + LoadCdDirectory(); + + // allocate streaming buffers + if(ms_streamingBufferSize & 1) ms_streamingBufferSize++; +#ifndef ONE_THREAD_PER_CHANNEL + ms_pStreamingBuffer[0] = (int8*)RwMallocAlign(ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE, CDSTREAM_SECTOR_SIZE); + ms_streamingBufferSize /= 2; + ms_pStreamingBuffer[1] = ms_pStreamingBuffer[0] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; +#else + ms_pStreamingBuffer[0] = (int8*)RwMallocAlign(ms_streamingBufferSize*2*CDSTREAM_SECTOR_SIZE, CDSTREAM_SECTOR_SIZE); + ms_streamingBufferSize /= 2; + ms_pStreamingBuffer[1] = ms_pStreamingBuffer[0] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; + ms_pStreamingBuffer[2] = ms_pStreamingBuffer[1] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; + ms_pStreamingBuffer[3] = ms_pStreamingBuffer[2] + ms_streamingBufferSize*CDSTREAM_SECTOR_SIZE; +#endif + debug("Streaming buffer size is %d sectors", ms_streamingBufferSize); + + // PC only, figure out how much memory we got +#ifdef GTA_PC +#define MB (1024*1024) + + extern size_t _dwMemAvailPhys; + ms_memoryAvailable = (_dwMemAvailPhys - 10*MB)/2; + if(ms_memoryAvailable < 50*MB) + ms_memoryAvailable = 50*MB; + desiredNumVehiclesLoaded = (int32)((ms_memoryAvailable / MB - 50) / 3 + 12); + if(desiredNumVehiclesLoaded > MAXVEHICLESLOADED) + desiredNumVehiclesLoaded = MAXVEHICLESLOADED; + debug("Memory allocated to Streaming is %zuMB", ms_memoryAvailable/MB); // original modifier was %d +#undef MB +#endif + + // find island LODs + + pIslandLODindustEntity = nil; + pIslandLODcomIndEntity = nil; + pIslandLODcomSubEntity = nil; + pIslandLODsubIndEntity = nil; + pIslandLODsubComEntity = nil; + islandLODindust = -1; + islandLODcomInd = -1; + islandLODcomSub = -1; + islandLODsubInd = -1; + islandLODsubCom = -1; + CModelInfo::GetModelInfo("IslandLODInd", &islandLODindust); + CModelInfo::GetModelInfo("IslandLODcomIND", &islandLODcomInd); + CModelInfo::GetModelInfo("IslandLODcomSUB", &islandLODcomSub); + CModelInfo::GetModelInfo("IslandLODsubIND", &islandLODsubInd); + CModelInfo::GetModelInfo("IslandLODsubCOM", &islandLODsubCom); + + for(i = CPools::GetBuildingPool()->GetSize()-1; i >= 0; i--){ + CBuilding *building = CPools::GetBuildingPool()->GetSlot(i); + if(building == nil) + continue; + if(building->GetModelIndex() == islandLODindust) pIslandLODindustEntity = building; + if(building->GetModelIndex() == islandLODcomInd) pIslandLODcomIndEntity = building; + if(building->GetModelIndex() == islandLODcomSub) pIslandLODcomSubEntity = building; + if(building->GetModelIndex() == islandLODsubInd) pIslandLODsubIndEntity = building; + if(building->GetModelIndex() == islandLODsubCom) pIslandLODsubComEntity = building; + } +} + +void +CStreaming::Init(void) +{ +#ifdef USE_TXD_CDIMAGE + int txdHandle = CFileMgr::OpenFile("MODELS\\TXD.IMG", "r"); + if (txdHandle) + CFileMgr::CloseFile(txdHandle); + if (!CheckVideoCardCaps() && txdHandle) { + CdStreamAddImage("MODELS\\TXD.IMG"); + CStreaming::Init2(); + } else { + CStreaming::Init2(); + if (CreateTxdImageForVideoCard()) { + CStreaming::Shutdown(); + CdStreamAddImage("MODELS\\TXD.IMG"); + CStreaming::Init2(); + } + } +#else + CStreaming::Init2(); +#endif +} + +void +CStreaming::Shutdown(void) +{ + RwFreeAlign(ms_pStreamingBuffer[0]); + ms_streamingBufferSize = 0; + if(ms_pExtraObjectsDir){ + delete ms_pExtraObjectsDir; +#ifdef FIX_BUGS + ms_pExtraObjectsDir = nil; +#endif + } +} + +#ifndef MASTER +uint64 timeProcessingTXD; +uint64 timeProcessingDFF; +#endif + +void +CStreaming::Update(void) +{ + CEntity *train; + CStreamingInfo *si, *prev; + bool requestedSubway = false; + +#ifndef MASTER + timeProcessingTXD = 0; + timeProcessingDFF = 0; +#endif + + UpdateMemoryUsed(); + + if(ms_channelError != -1){ + RetryLoadFile(ms_channelError); + return; + } + + if(CTimer::GetIsPaused()) + return; + + train = FindPlayerTrain(); + if(train && train->GetPosition().z < 0.0f){ + RequestSubway(); + requestedSubway = true; + }else if(!ms_disableStreaming) + AddModelsToRequestList(TheCamera.GetPosition()); + + DeleteFarAwayRwObjects(TheCamera.GetPosition()); + + if(!ms_disableStreaming && + !CCutsceneMgr::IsRunning() && + !requestedSubway && + !CGame::playingIntro && + ms_numModelsRequested < 5 && + !CRenderer::m_loadingPriority +#ifdef FIX_BUGS + && !CReplay::IsPlayingBack() +#endif + ){ + StreamVehiclesAndPeds(); + StreamZoneModels(FindPlayerCoors()); + } + + LoadRequestedModels(); + +#ifndef MASTER + if (CPad::GetPad(1)->GetLeftShoulder1JustDown() && CPad::GetPad(1)->GetRightShoulder1() && CPad::GetPad(1)->GetRightShoulder2()) + PrintStreamingBufferState(); + + // TODO: PrintRequestList + //if (CPad::GetPad(1)->GetLeftShoulder2JustDown() && CPad::GetPad(1)->GetRightShoulder1() && CPad::GetPad(1)->GetRightShoulder2()) + // PrintRequestList(); +#endif + + for(si = ms_endRequestedList.m_prev; si != &ms_startRequestedList; si = prev){ + prev = si->m_prev; + if((si->m_flags & (STREAMFLAGS_KEEP_IN_MEMORY|STREAMFLAGS_PRIORITY)) == 0) + RemoveModel(si - ms_aInfoForModel); + } +} + +void +CStreaming::LoadCdDirectory(void) +{ + char dirname[132]; + int i; + +#ifdef GTA_PC + ms_imageOffsets[0] = 0; + ms_imageOffsets[1] = -1; + ms_imageOffsets[2] = -1; + ms_imageOffsets[3] = -1; + ms_imageOffsets[4] = -1; + ms_imageOffsets[5] = -1; + ms_imageOffsets[6] = -1; + ms_imageOffsets[7] = -1; + ms_imageOffsets[8] = -1; + ms_imageOffsets[9] = -1; + ms_imageOffsets[10] = -1; + ms_imageOffsets[11] = -1; + ms_imageSize = GetGTA3ImgSize(); + // PS2 uses CFileMgr::GetCdFile on all IMG files to fill the array +#endif + + i = CdStreamGetNumImages(); + while(i-- >= 1){ + strcpy(dirname, CdStreamGetImageName(i)); + strncpy(strrchr(dirname, '.') + 1, "DIR", 3); + LoadCdDirectory(dirname, i); + } + + ms_lastImageRead = 0; + ms_imageSize /= CDSTREAM_SECTOR_SIZE; +} + +void +CStreaming::LoadCdDirectory(const char *dirname, int n) +{ + int fd, lastID, imgSelector; + int modelId, txdId; + uint32 posn, size; + CDirectory::DirectoryInfo direntry; + char *dot; + + lastID = -1; + fd = CFileMgr::OpenFile(dirname, "rb"); + assert(fd > 0); + + imgSelector = n<<24; + assert(sizeof(direntry) == 32); + while(CFileMgr::Read(fd, (char*)&direntry, sizeof(direntry))){ + dot = strchr(direntry.name, '.'); + assert(dot); + if(dot) *dot = '\0'; + if(direntry.size > (uint32)ms_streamingBufferSize) + ms_streamingBufferSize = direntry.size; + + if(!CGeneral::faststrcmp(dot+1, "DFF") || !CGeneral::faststrcmp(dot+1, "dff")){ + if(CModelInfo::GetModelInfo(direntry.name, &modelId)){ + if(ms_aInfoForModel[modelId].GetCdPosnAndSize(posn, size)){ + debug("%s appears more than once in %s\n", direntry.name, dirname); + lastID = -1; + }else{ + direntry.offset |= imgSelector; + ms_aInfoForModel[modelId].SetCdPosnAndSize(direntry.offset, direntry.size); + if(lastID != -1) + ms_aInfoForModel[lastID].m_nextID = modelId; + lastID = modelId; + } + }else{ +#ifdef FIX_BUGS + // remember which cdimage this came from + ms_pExtraObjectsDir->AddItem(direntry, n); +#else + ms_pExtraObjectsDir->AddItem(direntry); +#endif + lastID = -1; + } + }else if(!CGeneral::faststrcmp(dot+1, "TXD") || !CGeneral::faststrcmp(dot+1, "txd")){ + txdId = CTxdStore::FindTxdSlot(direntry.name); + if(txdId == -1) + txdId = CTxdStore::AddTxdSlot(direntry.name); + if(ms_aInfoForModel[txdId + STREAM_OFFSET_TXD].GetCdPosnAndSize(posn, size)){ + debug("%s appears more than once in %s\n", direntry.name, dirname); + lastID = -1; + }else{ + direntry.offset |= imgSelector; + ms_aInfoForModel[txdId + STREAM_OFFSET_TXD].SetCdPosnAndSize(direntry.offset, direntry.size); + if(lastID != -1) + ms_aInfoForModel[lastID].m_nextID = txdId + STREAM_OFFSET_TXD; + lastID = txdId + STREAM_OFFSET_TXD; + } + }else + lastID = -1; + } + + CFileMgr::CloseFile(fd); +} + +#ifdef USE_CUSTOM_ALLOCATOR +RpAtomic* +RegisterAtomicMemPtrsCB(RpAtomic *atomic, void *data) +{ +#if THIS_IS_COMPATIBLE_WITH_GTA3_RW31 + // not quite sure what's going on here: + // gta3's RW 3.1 allocates separate memory for geometry data of RpGeometry. + // Is that a R* change? rpDefaultGeometryInstance also depends on it + RpGeometry *geo = RpAtomicGetGeometry(atomic); + if(geo->triangles) + REGISTER_MEMPTR(&geo->triangles); + if(geo->matList.materials) + REGISTER_MEMPTR(&geo->matList.materials); + if(geo->preLitLum) + REGISTER_MEMPTR(&geo->preLitLum); + if(geo->texCoords[0]) + REGISTER_MEMPTR(&geo->texCoords[0]); + if(geo->texCoords[1]) + REGISTER_MEMPTR(&geo->texCoords[1]); +#else + // normally RpGeometry is allocated in one block (excluding morph targets) + // so we don't really have allocated pointers in the struct. + // NB: in librw we actually do it in two allocations (geometry itself and data) + // so we could conceivably come up with something here +#endif + return atomic; +} +#endif + +bool +CStreaming::ConvertBufferToObject(int8 *buf, int32 streamId) +{ + RwMemory mem; + RwStream *stream; + int cdsize; + uint32 startTime, endTime, timeDiff; + CBaseModelInfo *mi; + bool success; + + startTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + + cdsize = ms_aInfoForModel[streamId].GetCdSize(); + mem.start = (uint8*)buf; + mem.length = cdsize * CDSTREAM_SECTOR_SIZE; + stream = RwStreamOpen(rwSTREAMMEMORY, rwSTREAMREAD, &mem); + + if(streamId < STREAM_OFFSET_TXD){ + // Model + mi = CModelInfo::GetModelInfo(streamId); + + // Txd has to be loaded +#ifdef FIX_BUGS + if(!HasTxdLoaded(mi->GetTxdSlot())){ +#else + // texDict will exist even if only first part has loaded + if(CTxdStore::GetSlot(mi->GetTxdSlot())->texDict == nil){ +#endif + debug("failed to load %s because TXD %s is not in memory\n", mi->GetModelName(), CTxdStore::GetTxdName(mi->GetTxdSlot())); + RemoveModel(streamId); +#ifndef FIX_BUGS + // if we're just waiting for it to load, don't remove this + RemoveTxd(mi->GetTxdSlot()); +#endif + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + + // Set Txd to use + CTxdStore::AddRef(mi->GetTxdSlot()); + + PUSH_MEMID(MEMID_STREAM_MODELS); + CTxdStore::SetCurrentTxd(mi->GetTxdSlot()); + if(mi->IsSimple()){ + success = CFileLoader::LoadAtomicFile(stream, streamId); +#ifdef USE_CUSTOM_ALLOCATOR + RegisterAtomicMemPtrsCB(((CSimpleModelInfo*)mi)->m_atomics[0], nil); +#endif + } else if (mi->GetModelType() == MITYPE_VEHICLE) { + // load vehicles in two parts + CModelInfo::GetModelInfo(streamId)->AddRef(); + success = CFileLoader::StartLoadClumpFile(stream, streamId); + if(success) + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_STARTED; + }else{ + success = CFileLoader::LoadClumpFile(stream, streamId); +#ifdef USE_CUSTOM_ALLOCATOR + if(success) + RpClumpForAllAtomics((RpClump*)mi->GetRwObject(), RegisterAtomicMemPtrsCB, nil); +#endif + } + POP_MEMID(); + UpdateMemoryUsed(); + + // Txd no longer needed unless we only read part of the file + if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED) + CTxdStore::RemoveRefWithoutDelete(mi->GetTxdSlot()); + + if(!success){ + debug("Failed to load %s\n", CModelInfo::GetModelInfo(streamId)->GetModelName()); + RemoveModel(streamId); + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + }else{ + // Txd + assert(streamId < NUMSTREAMINFO); + if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_KEEP_IN_MEMORY) == 0 && + !IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)){ + RemoveModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + + PUSH_MEMID(MEMID_STREAM_TEXUTRES); + if(ms_bLoadingBigModel || cdsize > 200){ + success = CTxdStore::StartLoadTxd(streamId - STREAM_OFFSET_TXD, stream); + if(success) + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_STARTED; + }else + success = CTxdStore::LoadTxd(streamId - STREAM_OFFSET_TXD, stream); + POP_MEMID(); + UpdateMemoryUsed(); + + if(!success){ + debug("Failed to load %s.txd\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD)); + RemoveModel(streamId); + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + } + + RwStreamClose(stream, &mem); + + // We shouldn't even end up here unless load was successful + if(!success){ + ReRequestModel(streamId); + if(streamId < STREAM_OFFSET_TXD) + debug("Failed to load %s.dff\n", mi->GetModelName()); + else + debug("Failed to load %s.txd\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD)); + return false; + } + + if(streamId < STREAM_OFFSET_TXD){ + // Model + // Vehicles and Peds not in loaded list + if (mi->GetModelType() != MITYPE_VEHICLE && mi->GetModelType() != MITYPE_PED) { + CSimpleModelInfo *smi = (CSimpleModelInfo*)mi; + + // Set fading for some objects + if(mi->IsSimple() && !smi->m_isBigBuilding){ + if(ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_NOFADE) + smi->m_alpha = 255; + else + smi->m_alpha = 0; + } + + if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_CANT_REMOVE) == 0) + ms_aInfoForModel[streamId].AddToList(&ms_startLoadedList); + } + }else{ + // Txd + if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_CANT_REMOVE) == 0) + ms_aInfoForModel[streamId].AddToList(&ms_startLoadedList); + } + + // Mark objects as loaded + if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED){ + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; +#ifndef USE_CUSTOM_ALLOCATOR + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; +#endif + } + + endTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + timeDiff = endTime - startTime; + if(timeDiff > 5){ + if(streamId < STREAM_OFFSET_TXD) + debug("model %s took %d ms\n", CModelInfo::GetModelInfo(streamId)->GetModelName(), timeDiff); + else + debug("txd %s took %d ms\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD), timeDiff); + } + + return true; +} + + +bool +CStreaming::FinishLoadingLargeFile(int8 *buf, int32 streamId) +{ + RwMemory mem; + RwStream *stream; + uint32 startTime, endTime, timeDiff; + CBaseModelInfo *mi; + bool success; + + startTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + + if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED){ + if(streamId < STREAM_OFFSET_TXD) + CModelInfo::GetModelInfo(streamId)->RemoveRef(); + return false; + } + + mem.start = (uint8*)buf; + mem.length = ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + stream = RwStreamOpen(rwSTREAMMEMORY, rwSTREAMREAD, &mem); + + if(streamId < STREAM_OFFSET_TXD){ + // Model + mi = CModelInfo::GetModelInfo(streamId); + PUSH_MEMID(MEMID_STREAM_MODELS); + CTxdStore::SetCurrentTxd(mi->GetTxdSlot()); + success = CFileLoader::FinishLoadClumpFile(stream, streamId); + if(success){ +#ifdef USE_CUSTOM_ALLOCATOR + RpClumpForAllAtomics((RpClump*)mi->GetRwObject(), RegisterAtomicMemPtrsCB, nil); +#endif + success = AddToLoadedVehiclesList(streamId); + } + POP_MEMID(); + mi->RemoveRef(); + CTxdStore::RemoveRefWithoutDelete(mi->GetTxdSlot()); + }else{ + // Txd + CTxdStore::AddRef(streamId - STREAM_OFFSET_TXD); + PUSH_MEMID(MEMID_STREAM_TEXUTRES); + success = CTxdStore::FinishLoadTxd(streamId - STREAM_OFFSET_TXD, stream); + POP_MEMID(); + CTxdStore::RemoveRefWithoutDelete(streamId - STREAM_OFFSET_TXD); + } + + RwStreamClose(stream, &mem); + + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; // only done if success on PS2 +#ifndef USE_CUSTOM_ALLOCATOR + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; +#endif + + if(!success){ + RemoveModel(streamId); + ReRequestModel(streamId); + UpdateMemoryUsed(); // directly after pop on PS2 + return false; + } + + UpdateMemoryUsed(); // directly after pop on PS2 + + endTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + timeDiff = endTime - startTime; + if(timeDiff > 5){ + if(streamId < STREAM_OFFSET_TXD) + debug("finish model %s took %d ms\n", CModelInfo::GetModelInfo(streamId)->GetModelName(), timeDiff); + else + debug("finish txd %s took %d ms\n", CTxdStore::GetTxdName(streamId - STREAM_OFFSET_TXD), timeDiff); + } + + return true; +} + +void +CStreaming::RequestModel(int32 id, int32 flags) +{ + CSimpleModelInfo *mi; + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_INQUEUE){ + // updgrade to priority + if(flags & STREAMFLAGS_PRIORITY && !ms_aInfoForModel[id].IsPriority()){ + ms_numPriorityRequests++; + ms_aInfoForModel[id].m_flags |= STREAMFLAGS_PRIORITY; + } + }else if(ms_aInfoForModel[id].m_loadState != STREAMSTATE_NOTLOADED){ + flags &= ~STREAMFLAGS_PRIORITY; + } + ms_aInfoForModel[id].m_flags |= flags; + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED){ + // Already loaded, only check changed flags + + if(ms_aInfoForModel[id].m_flags & STREAMFLAGS_NOFADE && id < STREAM_OFFSET_TXD){ + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + if(mi->IsSimple()) + mi->m_alpha = 255; + } + + // reinsert into list + if(ms_aInfoForModel[id].m_next){ + ms_aInfoForModel[id].RemoveFromList(); + if((ms_aInfoForModel[id].m_flags & STREAMFLAGS_CANT_REMOVE) == 0) + ms_aInfoForModel[id].AddToList(&ms_startLoadedList); + } + }else if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_NOTLOADED || + ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED){ // how can this be true again? + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_NOTLOADED){ + if(id < STREAM_OFFSET_TXD) + RequestTxd(CModelInfo::GetModelInfo(id)->GetTxdSlot(), flags); + ms_aInfoForModel[id].AddToList(&ms_startRequestedList); + ms_numModelsRequested++; + if(flags & STREAMFLAGS_PRIORITY) + ms_numPriorityRequests++; + } + + ms_aInfoForModel[id].m_loadState = STREAMSTATE_INQUEUE; + ms_aInfoForModel[id].m_flags = flags; + } +} + +void +CStreaming::RequestSubway(void) +{ + RequestModel(MI_SUBWAY1, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY2, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY3, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY4, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY5, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY6, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY7, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY8, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY9, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY10, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY11, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY12, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY13, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY14, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY15, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY16, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY17, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBWAY18, STREAMFLAGS_NOFADE); + + switch(CGame::currLevel){ + case LEVEL_INDUSTRIAL: + RequestModel(MI_SUBPLATFORM_IND, STREAMFLAGS_NOFADE); + break; + case LEVEL_COMMERCIAL: + if(FindPlayerTrain()->GetPosition().y < -700.0f){ + RequestModel(MI_SUBPLATFORM_COMS, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBPLATFORM_COMS2, STREAMFLAGS_NOFADE); + }else{ + RequestModel(MI_SUBPLATFORM_COMN, STREAMFLAGS_NOFADE); + } + break; + case LEVEL_SUBURBAN: + RequestModel(MI_SUBPLATFORM_SUB, STREAMFLAGS_NOFADE); + RequestModel(MI_SUBPLATFORM_SUB2, STREAMFLAGS_NOFADE); + break; + default: break; + } +} + +#define BIGBUILDINGFLAGS STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_PRIORITY + +void +CStreaming::RequestBigBuildings(eLevelName level) +{ + int i, n; + CBuilding *b; + + n = CPools::GetBuildingPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + b = CPools::GetBuildingPool()->GetSlot(i); + if(b && b->bIsBIGBuilding +#ifdef NO_ISLAND_LOADING + && (((CMenuManager::m_PrefsIslandLoading != CMenuManager::ISLAND_LOADING_LOW) && (b != pIslandLODindustEntity) && (b != pIslandLODcomIndEntity) && + (b != pIslandLODcomSubEntity) && (b != pIslandLODsubIndEntity) && (b != pIslandLODsubComEntity) + ) || (b->m_level == level)) +#else + && b->m_level == level +#endif + ) + RequestModel(b->GetModelIndex(), BIGBUILDINGFLAGS); + } + RequestIslands(level); + ms_hasLoadedLODs = false; +} + +void +CStreaming::RequestIslands(eLevelName level) +{ + ISLAND_LOADING_ISNT(HIGH) + switch(level){ + case LEVEL_INDUSTRIAL: + RequestModel(islandLODcomInd, BIGBUILDINGFLAGS); + RequestModel(islandLODsubInd, BIGBUILDINGFLAGS); + break; + case LEVEL_COMMERCIAL: + RequestModel(islandLODindust, BIGBUILDINGFLAGS); + RequestModel(islandLODsubCom, BIGBUILDINGFLAGS); + break; + case LEVEL_SUBURBAN: + RequestModel(islandLODindust, BIGBUILDINGFLAGS); + RequestModel(islandLODcomSub, BIGBUILDINGFLAGS); + break; + default: break; + } +} + +void +CStreaming::RequestSpecialModel(int32 modelId, const char *modelName, int32 flags) +{ + CBaseModelInfo *mi; + int txdId; + char oldName[48]; + uint32 pos, size; + + mi = CModelInfo::GetModelInfo(modelId); + if(!CGeneral::faststrcmp(mi->GetModelName(), modelName)){ + // Already have the correct name, just request it + RequestModel(modelId, flags); + return; + } + + strcpy(oldName, mi->GetModelName()); + mi->SetModelName(modelName); + + // What exactly is going on here? + if(CModelInfo::GetModelInfo(oldName, nil)){ + txdId = CTxdStore::FindTxdSlot(oldName); + if(txdId != -1 && CTxdStore::GetSlot(txdId)->texDict){ + CTxdStore::AddRef(txdId); + RemoveModel(modelId); + CTxdStore::RemoveRefWithoutDelete(txdId); + }else + RemoveModel(modelId); + }else + RemoveModel(modelId); + + bool found = ms_pExtraObjectsDir->FindItem(modelName, pos, size); + assert(found); + mi->ClearTexDictionary(); + if(CTxdStore::FindTxdSlot(modelName) == -1) + mi->SetTexDictionary("generic"); + else + mi->SetTexDictionary(modelName); + ms_aInfoForModel[modelId].SetCdPosnAndSize(pos, size); + RequestModel(modelId, flags); +} + +void +CStreaming::RequestSpecialChar(int32 charId, const char *modelName, int32 flags) +{ + RequestSpecialModel(charId + MI_SPECIAL01, modelName, flags); +} + +bool +CStreaming::HasSpecialCharLoaded(int32 id) +{ + return HasModelLoaded(id + MI_SPECIAL01); +} + +void +CStreaming::SetMissionDoesntRequireSpecialChar(int32 id) +{ + return SetMissionDoesntRequireModel(id + MI_SPECIAL01); +} + +void +CStreaming::DecrementRef(int32 id) +{ + ms_numModelsRequested--; + if(ms_aInfoForModel[id].IsPriority()){ + ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_PRIORITY; + ms_numPriorityRequests--; + } +} + +void +CStreaming::RemoveModel(int32 id) +{ + int i; + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_NOTLOADED) + return; + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED){ + if(id < STREAM_OFFSET_TXD) + CModelInfo::GetModelInfo(id)->DeleteRwObject(); + else + CTxdStore::RemoveTxd(id - STREAM_OFFSET_TXD); +#ifdef USE_CUSTOM_ALLOCATOR + UpdateMemoryUsed(); +#else + ms_memoryUsed -= ms_aInfoForModel[id].GetCdSize()*CDSTREAM_SECTOR_SIZE; +#endif + } + + if(ms_aInfoForModel[id].m_next){ + // Remove from list, model is neither loaded nor requested + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_INQUEUE) + DecrementRef(id); + ms_aInfoForModel[id].RemoveFromList(); + }else if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_READING){ + for(i = 0; i < 4; i++){ + if(ms_channel[0].streamIds[i] == id) + ms_channel[0].streamIds[i] = -1; + if(ms_channel[1].streamIds[i] == id) + ms_channel[1].streamIds[i] = -1; + } + } + + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_STARTED){ + if(id < STREAM_OFFSET_TXD) + RpClumpGtaCancelStream(); + else + CTxdStore::RemoveTxd(id - STREAM_OFFSET_TXD); +#ifdef USE_CUSTOM_ALLOCATOR + UpdateMemoryUsed(); +#endif + } + + ms_aInfoForModel[id].m_loadState = STREAMSTATE_NOTLOADED; +} + +void +CStreaming::RemoveUnusedBuildings(eLevelName level) +{ + if(level != LEVEL_INDUSTRIAL) + RemoveBuildings(LEVEL_INDUSTRIAL); + if(level != LEVEL_COMMERCIAL) + RemoveBuildings(LEVEL_COMMERCIAL); + if(level != LEVEL_SUBURBAN) + RemoveBuildings(LEVEL_SUBURBAN); +} + +void +CStreaming::RemoveBuildings(eLevelName level) +{ + int i, n; + CEntity *e; + CBaseModelInfo *mi; + + n = CPools::GetBuildingPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if (mi->GetNumRefs() == 0) + RemoveModel(e->GetModelIndex()); + } + } + } + + n = CPools::GetTreadablePool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetTreadablePool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if (mi->GetNumRefs() == 0) + RemoveModel(e->GetModelIndex()); + } + } + } + + n = CPools::GetObjectPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetObjectPool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered && ((CObject*)e)->ObjectCreatedBy == GAME_OBJECT){ + e->DeleteRwObject(); + if (mi->GetNumRefs() == 0) + RemoveModel(e->GetModelIndex()); + } + } + } + + n = CPools::GetDummyPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetDummyPool()->GetSlot(i); + if(e && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if (mi->GetNumRefs() == 0) + RemoveModel(e->GetModelIndex()); + } + } + } +} + +void +CStreaming::RemoveUnusedBigBuildings(eLevelName level) +{ + ISLAND_LOADING_IS(LOW) + { + if (level != LEVEL_INDUSTRIAL) + RemoveBigBuildings(LEVEL_INDUSTRIAL); + if (level != LEVEL_COMMERCIAL) + RemoveBigBuildings(LEVEL_COMMERCIAL); + if (level != LEVEL_SUBURBAN) + RemoveBigBuildings(LEVEL_SUBURBAN); + } + RemoveIslandsNotUsed(level); +} + +void +DeleteIsland(CEntity *island) +{ + if(island == nil) + return; + if(island->bImBeingRendered) + debug("Didn't delete island because it was being rendered\n"); + else{ + island->DeleteRwObject(); + CStreaming::RemoveModel(island->GetModelIndex()); + } +} + +void +CStreaming::RemoveIslandsNotUsed(eLevelName level) +{ +#ifdef NO_ISLAND_LOADING + if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_HIGH) { + DeleteIsland(pIslandLODindustEntity); + DeleteIsland(pIslandLODcomIndEntity); + DeleteIsland(pIslandLODcomSubEntity); + DeleteIsland(pIslandLODsubIndEntity); + DeleteIsland(pIslandLODsubComEntity); + } else +#endif + switch(level){ + case LEVEL_INDUSTRIAL: + DeleteIsland(pIslandLODindustEntity); + DeleteIsland(pIslandLODcomSubEntity); + DeleteIsland(pIslandLODsubComEntity); + break; + case LEVEL_COMMERCIAL: + DeleteIsland(pIslandLODcomIndEntity); + DeleteIsland(pIslandLODcomSubEntity); + DeleteIsland(pIslandLODsubIndEntity); + break; + case LEVEL_SUBURBAN: + DeleteIsland(pIslandLODsubIndEntity); + DeleteIsland(pIslandLODsubComEntity); + DeleteIsland(pIslandLODcomIndEntity); + break; + default: + DeleteIsland(pIslandLODindustEntity); + DeleteIsland(pIslandLODcomIndEntity); + DeleteIsland(pIslandLODcomSubEntity); + DeleteIsland(pIslandLODsubIndEntity); + DeleteIsland(pIslandLODsubComEntity); + break; + } +} + +void +CStreaming::RemoveBigBuildings(eLevelName level) +{ + int i, n; + CEntity *e; + CBaseModelInfo *mi; + + n = CPools::GetBuildingPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->bIsBIGBuilding && e->m_level == level){ + mi = CModelInfo::GetModelInfo(e->GetModelIndex()); + if(!e->bImBeingRendered){ + e->DeleteRwObject(); + if (mi->GetNumRefs() == 0) + RemoveModel(e->GetModelIndex()); + } + } + } +} + +bool +CStreaming::RemoveLoadedVehicle(void) +{ + int i, id; + + for(i = 0; i < MAXVEHICLESLOADED; i++){ + ms_lastVehicleDeleted++; + if(ms_lastVehicleDeleted == MAXVEHICLESLOADED) + ms_lastVehicleDeleted = 0; + id = ms_vehiclesLoaded[ms_lastVehicleDeleted]; + if(id != -1 && + (ms_aInfoForModel[id].m_flags & STREAMFLAGS_CANT_REMOVE) == 0 && CModelInfo::GetModelInfo(id)->GetNumRefs() == 0 && + ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED) + goto found; + } + return false; +found: + RemoveModel(ms_vehiclesLoaded[ms_lastVehicleDeleted]); + ms_numVehiclesLoaded--; + ms_vehiclesLoaded[ms_lastVehicleDeleted] = -1; + return true; +} + +bool +CStreaming::RemoveLeastUsedModel(void) +{ + CStreamingInfo *si; + int streamId; + + for(si = ms_endLoadedList.m_prev; si != &ms_startLoadedList; si = si->m_prev){ + streamId = si - ms_aInfoForModel; + if(streamId < STREAM_OFFSET_TXD){ + if (CModelInfo::GetModelInfo(streamId)->GetNumRefs() == 0) { + RemoveModel(streamId); + return true; + } + }else{ + if(CTxdStore::GetNumRefs(streamId - STREAM_OFFSET_TXD) == 0 && + !IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)){ + RemoveModel(streamId); + return true; + } + } + } + return ms_numVehiclesLoaded > 7 && RemoveLoadedVehicle(); +} + +void +CStreaming::RemoveAllUnusedModels(void) +{ + int i; + + for(i = 0; i < MAXVEHICLESLOADED; i++) + RemoveLoadedVehicle(); + + for(i = NUM_DEFAULT_MODELS; i < MODELINFOSIZE; i++){ + if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED && + ms_aInfoForModel[i].m_flags & STREAMFLAGS_DONT_REMOVE && + CModelInfo::GetModelInfo(i)->GetNumRefs() == 0) { + RemoveModel(i); + ms_aInfoForModel[i].m_loadState = STREAMSTATE_NOTLOADED; + } + } +} + +bool +CStreaming::RemoveReferencedTxds(size_t mem) +{ + CStreamingInfo *si; + int streamId; + + for(si = ms_endLoadedList.m_prev; si != &ms_startLoadedList; si = si->m_prev){ + streamId = si - ms_aInfoForModel; + if(streamId >= STREAM_OFFSET_TXD && + CTxdStore::GetNumRefs(streamId-STREAM_OFFSET_TXD) == 0){ + RemoveModel(streamId); + if(ms_memoryUsed < mem) + return true; + } + } + return false; +} + +void +CStreaming::RemoveUnusedModelsInLoadedList(void) +{ + // empty +} + +bool +CStreaming::IsTxdUsedByRequestedModels(int32 txdId) +{ + CStreamingInfo *si; + int streamId; + int i; + + for(si = ms_startRequestedList.m_next; si != &ms_endRequestedList; si = si->m_next){ + streamId = si - ms_aInfoForModel; + if(streamId < STREAM_OFFSET_TXD && + CModelInfo::GetModelInfo(streamId)->GetTxdSlot() == txdId) + return true; + } + + for(i = 0; i < 4; i++){ + streamId = ms_channel[0].streamIds[i]; + if(streamId != -1 && streamId < STREAM_OFFSET_TXD && + CModelInfo::GetModelInfo(streamId)->GetTxdSlot() == txdId) + return true; + streamId = ms_channel[1].streamIds[i]; + if(streamId != -1 && streamId < STREAM_OFFSET_TXD && + CModelInfo::GetModelInfo(streamId)->GetTxdSlot() == txdId) + return true; + } + + return false; +} + +int32 +CStreaming::GetAvailableVehicleSlot(void) +{ + int i; + for(i = 0; i < MAXVEHICLESLOADED; i++) + if(ms_vehiclesLoaded[i] == -1) + return i; + return -1; +} + +bool +CStreaming::AddToLoadedVehiclesList(int32 modelId) +{ + int i; + int id; + + if(ms_numVehiclesLoaded < desiredNumVehiclesLoaded){ + // still room for vehicles + for(i = 0; i < MAXVEHICLESLOADED; i++){ + if(ms_vehiclesLoaded[ms_lastVehicleDeleted] == -1) + break; + ms_lastVehicleDeleted++; + if(ms_lastVehicleDeleted == MAXVEHICLESLOADED) + ms_lastVehicleDeleted = 0; + } + assert(ms_vehiclesLoaded[ms_lastVehicleDeleted] == -1); + ms_numVehiclesLoaded++; + }else{ + // find vehicle we can remove + for(i = 0; i < MAXVEHICLESLOADED; i++){ + id = ms_vehiclesLoaded[ms_lastVehicleDeleted]; + if(id != -1 && + (ms_aInfoForModel[id].m_flags & STREAMFLAGS_CANT_REMOVE) == 0 && CModelInfo::GetModelInfo(id)->GetNumRefs() == 0) + goto found; + ms_lastVehicleDeleted++; + if(ms_lastVehicleDeleted == MAXVEHICLESLOADED) + ms_lastVehicleDeleted = 0; + } + id = -1; +found: + if(id == -1){ + // didn't find anything, try a free slot + id = GetAvailableVehicleSlot(); + if(id == -1) + return false; // still no luck + ms_lastVehicleDeleted = id; + // this is more than we wanted actually + ms_numVehiclesLoaded++; + }else + RemoveModel(id); + } + + ms_vehiclesLoaded[ms_lastVehicleDeleted++] = modelId; + if(ms_lastVehicleDeleted == MAXVEHICLESLOADED) + ms_lastVehicleDeleted = 0; + return true; +} + +bool +CStreaming::IsObjectInCdImage(int32 id) +{ + uint32 posn, size; + return ms_aInfoForModel[id].GetCdPosnAndSize(posn, size); +} + +void +CStreaming::HaveAllBigBuildingsLoaded(eLevelName level) +{ + int i, n; + CEntity *e; + + if(ms_hasLoadedLODs) + return; + + if(level == LEVEL_INDUSTRIAL){ + if(ms_aInfoForModel[islandLODcomInd].m_loadState != STREAMSTATE_LOADED || + ms_aInfoForModel[islandLODsubInd].m_loadState != STREAMSTATE_LOADED) + return; + }else if(level == LEVEL_COMMERCIAL){ + if(ms_aInfoForModel[islandLODindust].m_loadState != STREAMSTATE_LOADED || + ms_aInfoForModel[islandLODsubCom].m_loadState != STREAMSTATE_LOADED) + return; + }else if(level == LEVEL_SUBURBAN){ + if(ms_aInfoForModel[islandLODindust].m_loadState != STREAMSTATE_LOADED || + ms_aInfoForModel[islandLODcomSub].m_loadState != STREAMSTATE_LOADED) + return; + } + + n = CPools::GetBuildingPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->bIsBIGBuilding && e->m_level == level && + ms_aInfoForModel[e->GetModelIndex()].m_loadState != STREAMSTATE_LOADED) + return; + } + + RemoveUnusedBigBuildings(level); + ms_hasLoadedLODs = true; +} + +void +CStreaming::SetModelIsDeletable(int32 id) +{ + ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_DONT_REMOVE; + if ((id >= STREAM_OFFSET_TXD || CModelInfo::GetModelInfo(id)->GetModelType() != MITYPE_VEHICLE) && + (ms_aInfoForModel[id].m_flags & STREAMFLAGS_SCRIPTOWNED) == 0){ + if(ms_aInfoForModel[id].m_loadState != STREAMSTATE_LOADED) + RemoveModel(id); + else if(ms_aInfoForModel[id].m_next == nil) + ms_aInfoForModel[id].AddToList(&ms_startLoadedList); + } +} + +void +CStreaming::SetModelTxdIsDeletable(int32 id) +{ + SetModelIsDeletable(CModelInfo::GetModelInfo(id)->GetTxdSlot() + STREAM_OFFSET_TXD); +} + +void +CStreaming::SetMissionDoesntRequireModel(int32 id) +{ + ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_SCRIPTOWNED; + if ((id >= STREAM_OFFSET_TXD || CModelInfo::GetModelInfo(id)->GetModelType() != MITYPE_VEHICLE) && + (ms_aInfoForModel[id].m_flags & STREAMFLAGS_DONT_REMOVE) == 0){ + if(ms_aInfoForModel[id].m_loadState != STREAMSTATE_LOADED) + RemoveModel(id); + else if(ms_aInfoForModel[id].m_next == nil) + ms_aInfoForModel[id].AddToList(&ms_startLoadedList); + } +} + +void +CStreaming::LoadInitialPeds(void) +{ + RequestModel(MI_COP, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_MALE01, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_TAXI_D, STREAMFLAGS_DONT_REMOVE); +} + +void +CStreaming::LoadInitialVehicles(void) +{ + int id; + + ms_numVehiclesLoaded = 0; + ms_lastVehicleDeleted = 0; + + if(CModelInfo::GetModelInfo("taxi", &id)) + RequestModel(id, STREAMFLAGS_DONT_REMOVE); + if(CModelInfo::GetModelInfo("police", &id)) + RequestModel(id, STREAMFLAGS_DONT_REMOVE); +} + +void +CStreaming::StreamVehiclesAndPeds(void) +{ + int i, model; + static int timeBeforeNextLoad = 0; + static int modelQualityClass = 0; + + if(CRecordDataForGame::IsRecording() || + CRecordDataForGame::IsPlayingBack() +#ifdef FIX_BUGS + || CReplay::IsPlayingBack() +#endif + ) + return; + + if(FindPlayerPed()->m_pWanted->AreSwatRequired()){ + RequestModel(MI_ENFORCER, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_SWAT, STREAMFLAGS_DONT_REMOVE); + }else{ + SetModelIsDeletable(MI_ENFORCER); + if(!HasModelLoaded(MI_ENFORCER)) + SetModelIsDeletable(MI_SWAT); + } + + if(FindPlayerPed()->m_pWanted->AreFbiRequired()){ + RequestModel(MI_FBICAR, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_FBI, STREAMFLAGS_DONT_REMOVE); + }else{ + SetModelIsDeletable(MI_FBICAR); + if(!HasModelLoaded(MI_FBICAR)) + SetModelIsDeletable(MI_FBI); + } + + if(FindPlayerPed()->m_pWanted->AreArmyRequired()){ + RequestModel(MI_RHINO, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_BARRACKS, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_ARMY, STREAMFLAGS_DONT_REMOVE); + }else{ + SetModelIsDeletable(MI_RHINO); + SetModelIsDeletable(MI_BARRACKS); + if(!HasModelLoaded(MI_RHINO) && !HasModelLoaded(MI_BARRACKS)) + SetModelIsDeletable(MI_ARMY); + } + + if(FindPlayerPed()->m_pWanted->NumOfHelisRequired() > 0) + RequestModel(MI_CHOPPER, STREAMFLAGS_DONT_REMOVE); + else + SetModelIsDeletable(MI_CHOPPER); + + if(timeBeforeNextLoad >= 0) + timeBeforeNextLoad--; + else if(ms_numVehiclesLoaded <= desiredNumVehiclesLoaded){ + for(i = 1; i <= 10; i++){ + model = CCarCtrl::ChooseCarModel(modelQualityClass); + modelQualityClass++; + if(modelQualityClass >= CCarCtrl::TOTAL_CUSTOM_CLASSES) + modelQualityClass = 0; + + // check if we want to load this model + if(ms_aInfoForModel[model].m_loadState == STREAMSTATE_NOTLOADED && + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(model))->m_level & (1 << (CGame::currLevel-1))) + break; + } + + if(i <= 10){ + RequestModel(model, STREAMFLAGS_DEPENDENCY); + timeBeforeNextLoad = 500; + } + } +} + +void +CStreaming::StreamZoneModels(const CVector &pos) +{ + int i; + uint16 gangsToLoad, gangCarsToLoad, bit; + CZoneInfo info; + + CTheZones::GetZoneInfoForTimeOfDay(&pos, &info); + + if(info.pedGroup != ms_currentPedGrp){ + + // unload pevious group + if(ms_currentPedGrp != -1) + for(i = 0; i < NUMMODELSPERPEDGROUP; i++){ + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] != -1){ + SetModelIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]); + SetModelTxdIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]); + } + } + + ms_currentPedGrp = info.pedGroup; + + for(i = 0; i < NUMMODELSPERPEDGROUP; i++){ + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] != -1) + RequestModel(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i], STREAMFLAGS_DONT_REMOVE); + } + } + RequestModel(MI_MALE01, STREAMFLAGS_DONT_REMOVE); + + gangsToLoad = 0; + gangCarsToLoad = 0; + if(info.gangDensity[0] != 0) gangsToLoad |= 1<<0; + if(info.gangDensity[1] != 0) gangsToLoad |= 1<<1; + if(info.gangDensity[2] != 0) gangsToLoad |= 1<<2; + if(info.gangDensity[3] != 0) gangsToLoad |= 1<<3; + if(info.gangDensity[4] != 0) gangsToLoad |= 1<<4; + if(info.gangDensity[5] != 0) gangsToLoad |= 1<<5; + if(info.gangDensity[6] != 0) gangsToLoad |= 1<<6; + if(info.gangDensity[7] != 0) gangsToLoad |= 1<<7; + if(info.gangDensity[8] != 0) gangsToLoad |= 1<<8; + if(info.gangThreshold[0] != info.copDensity) gangCarsToLoad |= 1<<0; + if(info.gangThreshold[1] != info.gangThreshold[0]) gangCarsToLoad |= 1<<1; + if(info.gangThreshold[2] != info.gangThreshold[1]) gangCarsToLoad |= 1<<2; + if(info.gangThreshold[3] != info.gangThreshold[2]) gangCarsToLoad |= 1<<3; + if(info.gangThreshold[4] != info.gangThreshold[3]) gangCarsToLoad |= 1<<4; + if(info.gangThreshold[5] != info.gangThreshold[4]) gangCarsToLoad |= 1<<5; + if(info.gangThreshold[6] != info.gangThreshold[5]) gangCarsToLoad |= 1<<6; + if(info.gangThreshold[7] != info.gangThreshold[6]) gangCarsToLoad |= 1<<7; + if(info.gangThreshold[8] != info.gangThreshold[7]) gangCarsToLoad |= 1<<8; + + if(gangsToLoad == ms_loadedGangs && gangCarsToLoad == ms_loadedGangCars) + return; + + // This makes things simpler than the game does it + gangsToLoad |= gangCarsToLoad; + + for(i = 0; i < NUM_GANGS; i++){ + bit = 1<m_nVehicleMI, STREAMFLAGS_DONT_REMOVE); + }else if((gangCarsToLoad & bit) == 0 && ms_loadedGangCars & bit){ + SetModelIsDeletable(CGangs::GetGangInfo(i)->m_nVehicleMI); + SetModelTxdIsDeletable(CGangs::GetGangInfo(i)->m_nVehicleMI); + } + } + ms_loadedGangCars = gangCarsToLoad; +} + +void +CStreaming::RemoveCurrentZonesModels(void) +{ + int i; + + if(ms_currentPedGrp != -1) + for(i = 0; i < 8; i++){ + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] == -1) + break; + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i] != MI_MALE01) + SetModelIsDeletable(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]); + } + + for(i = 0; i < NUM_GANGS; i++){ + SetModelIsDeletable(MI_GANG01 + i*2); + SetModelIsDeletable(MI_GANG01 + i*2 + 1); + if(CGangs::GetGangInfo(i)->m_nVehicleMI != -1) + SetModelIsDeletable(CGangs::GetGangInfo(i)->m_nVehicleMI); + } + + ms_currentPedGrp = -1; + ms_loadedGangs = 0; + ms_loadedGangCars = 0; +} + + +// Find starting offset of the cdimage we next want to read +// Not useful at all on PC... +int32 +CStreaming::GetCdImageOffset(int32 lastPosn) +{ + int offset, off; + int i, img; + int dist, mindist; + + img = -1; + mindist = INT32_MAX; + offset = ms_imageOffsets[ms_lastImageRead]; + if(lastPosn <= offset || lastPosn > offset + ms_imageSize){ + // last read position is not in last image + for(i = 0; i < NUMCDIMAGES; i++){ + off = ms_imageOffsets[i]; + if(off == -1) continue; + if((uint32)lastPosn > (uint32)off) + // after start of image, get distance from end + // negative if before end! + dist = lastPosn - (off + ms_imageSize); + else + // before image, get offset to start + // this will never be negative + dist = off - lastPosn; + if(dist < mindist){ + img = i; + mindist = dist; + } + } + assert(img >= 0); + offset = ms_imageOffsets[img]; + ms_lastImageRead = img; + } + return offset; +} + +inline bool +ModelNotLoaded(int32 modelId) +{ + CStreamingInfo *si = &CStreaming::ms_aInfoForModel[modelId]; + return si->m_loadState != STREAMSTATE_LOADED && si->m_loadState != STREAMSTATE_READING; +} + +inline bool TxdNotLoaded(int32 txdId) { return ModelNotLoaded(txdId + STREAM_OFFSET_TXD); } + +// Find stream id of next requested file in cdimage +int32 +CStreaming::GetNextFileOnCd(int32 lastPosn, bool priority) +{ + CStreamingInfo *si, *next; + int streamId; + uint32 posn, size; + int streamIdFirst, streamIdNext; + uint32 posnFirst, posnNext; + + streamIdFirst = -1; + streamIdNext = -1; + posnFirst = UINT32_MAX; + posnNext = UINT32_MAX; + + for(si = ms_startRequestedList.m_next; si != &ms_endRequestedList; si = next){ + next = si->m_next; + streamId = si - ms_aInfoForModel; + + // only priority requests if there are any + if(priority && ms_numPriorityRequests != 0 && !si->IsPriority()) + continue; + + // request Txd if necessary + if(streamId < STREAM_OFFSET_TXD){ + int txdId = CModelInfo::GetModelInfo(streamId)->GetTxdSlot(); + if(TxdNotLoaded(txdId)){ + ReRequestTxd(txdId); + continue; + } + } + + if(ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size)){ + if(posn < posnFirst){ + // find first requested file in image + streamIdFirst = streamId; + posnFirst = posn; + } + if(posn < posnNext && posn >= (uint32)lastPosn){ + // find first requested file after last read position + streamIdNext = streamId; + posnNext = posn; + } + }else{ + // empty file + DecrementRef(streamId); + si->RemoveFromList(); + si->m_loadState = STREAMSTATE_LOADED; + } + } + + // wrap around + if(streamIdNext == -1) + streamIdNext = streamIdFirst; + + if(streamIdNext == -1 && ms_numPriorityRequests != 0){ + // try non-priority files + ms_numPriorityRequests = 0; + streamIdNext = GetNextFileOnCd(lastPosn, false); + } + + return streamIdNext; +} + +/* + * Streaming buffer size is half of the largest file. + * Files larger than the buffer size can only be loaded by channel 0, + * which then uses both buffers, while channel 1 is idle. + * ms_bLoadingBigModel is set to true to indicate this state. + */ + +// Make channel read from disc +void +CStreaming::RequestModelStream(int32 ch) +{ + int lastPosn, imgOffset, streamId; + int totalSize; + uint32 posn, size, unused; + int i; + int haveBigFile, havePed; + + lastPosn = CdStreamGetLastPosn(); + imgOffset = GetCdImageOffset(lastPosn); + streamId = GetNextFileOnCd(lastPosn - imgOffset, true); + + if(streamId == -1) + return; + + // remove Txds that aren't requested anymore + while(streamId >= STREAM_OFFSET_TXD){ + if(ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_KEEP_IN_MEMORY || + IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)) + break; + RemoveModel(streamId); + // so try next file + ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size); + streamId = GetNextFileOnCd(posn + size, true); + } + + if(streamId == -1) + return; + + ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size); + if(size > (uint32)ms_streamingBufferSize){ + // Can only load big models on channel 0, and 1 has to be idle + if(ch == 1 || ms_channel[1].state != CHANNELSTATE_IDLE) + return; + ms_bLoadingBigModel = true; + } + + // Load up to 4 adjacent files + haveBigFile = 0; + havePed = 0; + totalSize = 0; + for(i = 0; i < 4; i++){ + // no more files we can read + if(streamId == -1 || ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_INQUEUE) + break; + + // also stop at non-priority files + ms_aInfoForModel[streamId].GetCdPosnAndSize(unused, size); + if(ms_numPriorityRequests != 0 && !ms_aInfoForModel[streamId].IsPriority()) + break; + + // Can't load certain combinations of files together + if(streamId < STREAM_OFFSET_TXD){ + if (havePed && CModelInfo::GetModelInfo(streamId)->GetModelType() == MITYPE_PED || + haveBigFile && CModelInfo::GetModelInfo(streamId)->GetModelType() == MITYPE_VEHICLE || + TxdNotLoaded(CModelInfo::GetModelInfo(streamId)->GetTxdSlot())) + break; + }else{ + if(haveBigFile && size > 200) + break; + } + + // Now add the file + ms_channel[ch].streamIds[i] = streamId; + ms_channel[ch].offsets[i] = totalSize; + totalSize += size; + + // To big for buffer, remove again + if(totalSize > ms_streamingBufferSize && i > 0){ + totalSize -= size; + break; + } + if(streamId < STREAM_OFFSET_TXD){ + if (CModelInfo::GetModelInfo(streamId)->GetModelType() == MITYPE_PED) + havePed = 1; + if (CModelInfo::GetModelInfo(streamId)->GetModelType() == MITYPE_VEHICLE) + haveBigFile = 1; + }else{ + if(size > 200) + haveBigFile = 1; + } + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_READING; + ms_aInfoForModel[streamId].RemoveFromList(); + DecrementRef(streamId); + + streamId = ms_aInfoForModel[streamId].m_nextID; + } + + // clear remaining slots + for(; i < 4; i++) + ms_channel[ch].streamIds[i] = -1; + // Now read the data + assert(!(ms_bLoadingBigModel && ch == 1)); // this would clobber the buffer + if(CdStreamRead(ch, ms_pStreamingBuffer[ch], imgOffset+posn, totalSize) == STREAM_NONE) + debug("FUCKFUCKFUCK\n"); + ms_channel[ch].state = CHANNELSTATE_READING; + ms_channel[ch].field24 = 0; + ms_channel[ch].size = totalSize; + ms_channel[ch].position = imgOffset+posn; + ms_channel[ch].numTries = 0; +} + +// Load data previously read from disc +bool +CStreaming::ProcessLoadingChannel(int32 ch) +{ + int status; + int i, id, cdsize; + + status = CdStreamGetStatus(ch); + if(status != STREAM_NONE){ + // busy + if(status != STREAM_READING && status != STREAM_WAITING){ + ms_channelError = ch; + ms_channel[ch].state = CHANNELSTATE_ERROR; + ms_channel[ch].status = status; + } + return false; + } + + if(ms_channel[ch].state == CHANNELSTATE_STARTED){ + ms_channel[ch].state = CHANNELSTATE_IDLE; + FinishLoadingLargeFile(&ms_pStreamingBuffer[ch][ms_channel[ch].offsets[0]*CDSTREAM_SECTOR_SIZE], + ms_channel[ch].streamIds[0]); + ms_channel[ch].streamIds[0] = -1; + }else{ + ms_channel[ch].state = CHANNELSTATE_IDLE; + for(i = 0; i < 4; i++){ + id = ms_channel[ch].streamIds[i]; + if(id == -1) + continue; + + cdsize = ms_aInfoForModel[id].GetCdSize(); + if(id < STREAM_OFFSET_TXD && CModelInfo::GetModelInfo(id)->GetModelType() == MITYPE_VEHICLE && + ms_numVehiclesLoaded >= desiredNumVehiclesLoaded && + !RemoveLoadedVehicle() && + ((ms_aInfoForModel[id].m_flags & STREAMFLAGS_CANT_REMOVE) == 0 || GetAvailableVehicleSlot() == -1)){ + // can't load vehicle + RemoveModel(id); + if(ms_aInfoForModel[id].m_flags & STREAMFLAGS_CANT_REMOVE) + ReRequestModel(id); + else if(CTxdStore::GetNumRefs(CModelInfo::GetModelInfo(id)->GetTxdSlot()) == 0) + RemoveTxd(CModelInfo::GetModelInfo(id)->GetTxdSlot()); + }else{ + MakeSpaceFor(cdsize * CDSTREAM_SECTOR_SIZE); + ConvertBufferToObject(&ms_pStreamingBuffer[ch][ms_channel[ch].offsets[i]*CDSTREAM_SECTOR_SIZE], + id); + if(ms_aInfoForModel[id].m_loadState == STREAMSTATE_STARTED){ + // queue for second part + ms_channel[ch].state = CHANNELSTATE_STARTED; + ms_channel[ch].offsets[0] = ms_channel[ch].offsets[i]; + ms_channel[ch].streamIds[0] = id; + if(i != 0) + ms_channel[ch].streamIds[i] = -1; + }else + ms_channel[ch].streamIds[i] = -1; + } + } + } + + if(ms_bLoadingBigModel && ms_channel[ch].state != CHANNELSTATE_STARTED){ + ms_bLoadingBigModel = false; + // reset channel 1 after loading a big model + for(i = 0; i < 4; i++) + ms_channel[1].streamIds[i] = -1; + ms_channel[1].state = CHANNELSTATE_IDLE; + } + + return true; +} + +void +CStreaming::RetryLoadFile(int32 ch) +{ + Const char *key; + + CPad::StopPadsShaking(); + + if(ms_channel[ch].numTries >= 3){ + switch(ms_channel[ch].status){ + case STREAM_ERROR_NOCD: key = "NOCD"; break; + case STREAM_ERROR_OPENCD: key = "OPENCD"; break; + case STREAM_ERROR_WRONGCD: key = "WRONGCD"; break; + default: key = "CDERROR"; break; + } + CHud::SetMessage(TheText.Get(key)); + CTimer::SetCodePause(true); + } + + switch(ms_channel[ch].state){ + case CHANNELSTATE_ERROR: + ms_channel[ch].numTries++; + if (CdStreamGetStatus(ch) == STREAM_READING || CdStreamGetStatus(ch) == STREAM_WAITING) break; + case CHANNELSTATE_IDLE: + CdStreamRead(ch, ms_pStreamingBuffer[ch], ms_channel[ch].position, ms_channel[ch].size); + ms_channel[ch].state = CHANNELSTATE_READING; + ms_channel[ch].field24 = -600; + break; + case CHANNELSTATE_READING: + if(ProcessLoadingChannel(ch)){ + ms_channelError = -1; + CTimer::SetCodePause(false); + } + break; + } +} + +void +CStreaming::LoadRequestedModels(void) +{ + static int currentChannel = 0; + + // We can't read with channel 1 while channel 0 is using its buffer + if(ms_bLoadingBigModel) + currentChannel = 0; + + // We have data, load + if(ms_channel[currentChannel].state == CHANNELSTATE_READING || + ms_channel[currentChannel].state == CHANNELSTATE_STARTED) + ProcessLoadingChannel(currentChannel); + + if(ms_channelError == -1){ + // Channel is idle, read more data + if(ms_channel[currentChannel].state == CHANNELSTATE_IDLE) + RequestModelStream(currentChannel); + // Switch channel + if(ms_channel[currentChannel].state != CHANNELSTATE_STARTED) + currentChannel = 1 - currentChannel; + } +} + + +// Let's load models in 4 threads; when one of them becomes idle, process the file, and fill thread with another file. Unfortunately processing models are still single-threaded. +// Currently only supported on POSIX streamer. +// WIP - some files are loaded swapped (CdStreamPosix problem?) +#if 0 //def ONE_THREAD_PER_CHANNEL +void +CStreaming::LoadAllRequestedModels(bool priority) +{ + static bool bInsideLoadAll = false; + int imgOffset, streamId, status; + int i; + uint32 posn, size; + + if(bInsideLoadAll) + return; + + FlushChannels(); + imgOffset = GetCdImageOffset(CdStreamGetLastPosn()); + + int streamIds[ARRAY_SIZE(ms_pStreamingBuffer)]; + int streamSizes[ARRAY_SIZE(ms_pStreamingBuffer)]; + int streamPoses[ARRAY_SIZE(ms_pStreamingBuffer)]; + int readOrder[4] = {-1}; // Channel IDs ordered by read time + int readI = 0; + int processI = 0; + bool first = true; + + // All those "first" checks are because of variables aren't initialized in first pass. + + while (true) { + for (int i=0; i (uint32)ms_streamingBufferSize) { + if (i + 1 == ARRAY_SIZE(ms_pStreamingBuffer)) + break; + else if (!first && streamIds[i+1] != -1) + continue; + + } else { + // Buffer of current channel is part of a "big file", pass + if (i != 0 && streamIds[i-1] != -1 && streamSizes[i-1] > (uint32)ms_streamingBufferSize) + continue; + } + ms_aInfoForModel[streamId].RemoveFromList(); + DecrementRef(streamId); + + streamIds[i] = streamId; + streamSizes[i] = size; + streamPoses[i] = posn; + + if (!first) + assert(readOrder[readI] == -1); + + //printf("read: order %d, ch %d, id %d, size %d\n", readI, i, streamId, size); + + CdStreamRead(i, ms_pStreamingBuffer[i], imgOffset+posn, size); + readOrder[readI] = i; + if (first && readI+1 != ARRAY_SIZE(readOrder)) + readOrder[readI+1] = -1; + + readI = (readI + 1) % ARRAY_SIZE(readOrder); + } else { + ms_aInfoForModel[streamId].RemoveFromList(); + DecrementRef(streamId); + + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; + streamIds[i] = -1; + } + } else { + streamIds[i] = -1; + break; + } + } + + first = false; + int nextChannel = readOrder[processI]; + + // Now start processing + if (nextChannel == -1 || streamIds[nextChannel] == -1) + break; + + //printf("process: order %d, ch %d, id %d\n", processI, nextChannel, streamIds[nextChannel]); + + // Try again on error + while (CdStreamSync(nextChannel) != STREAM_NONE) { + CdStreamRead(nextChannel, ms_pStreamingBuffer[nextChannel], imgOffset+streamPoses[nextChannel], streamSizes[nextChannel]); + } + ms_aInfoForModel[streamIds[nextChannel]].m_loadState = STREAMSTATE_READING; + + MakeSpaceFor(streamSizes[nextChannel] * CDSTREAM_SECTOR_SIZE); + ConvertBufferToObject(ms_pStreamingBuffer[nextChannel], streamIds[nextChannel]); + if(ms_aInfoForModel[streamIds[nextChannel]].m_loadState == STREAMSTATE_STARTED) + FinishLoadingLargeFile(ms_pStreamingBuffer[nextChannel], streamIds[nextChannel]); + + if(streamIds[nextChannel] < STREAM_OFFSET_TXD){ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(streamIds[nextChannel]); + if(mi->IsSimple()) + mi->m_alpha = 255; + } + streamIds[nextChannel] = -1; + readOrder[processI] = -1; + processI = (processI + 1) % ARRAY_SIZE(readOrder); + } + + ms_bLoadingBigModel = false; + for(i = 0; i < 4; i++){ + ms_channel[1].streamIds[i] = -1; + ms_channel[1].offsets[i] = -1; + } + ms_channel[1].state = CHANNELSTATE_IDLE; + bInsideLoadAll = false; +} +#else +void +CStreaming::LoadAllRequestedModels(bool priority) +{ + static bool bInsideLoadAll = false; + int imgOffset, streamId, status; + int i; + uint32 posn, size; + + if(bInsideLoadAll) + return; + + FlushChannels(); + imgOffset = GetCdImageOffset(CdStreamGetLastPosn()); + + while(ms_endRequestedList.m_prev != &ms_startRequestedList){ + streamId = GetNextFileOnCd(0, priority); + if(streamId == -1) + break; + + ms_aInfoForModel[streamId].RemoveFromList(); + DecrementRef(streamId); + + if(ms_aInfoForModel[streamId].GetCdPosnAndSize(posn, size)){ + do + status = CdStreamRead(0, ms_pStreamingBuffer[0], imgOffset+posn, size); + while(CdStreamSync(0) || status == STREAM_NONE); + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_READING; + + MakeSpaceFor(size * CDSTREAM_SECTOR_SIZE); + ConvertBufferToObject(ms_pStreamingBuffer[0], streamId); + if(ms_aInfoForModel[streamId].m_loadState == STREAMSTATE_STARTED) + FinishLoadingLargeFile(ms_pStreamingBuffer[0], streamId); + + if(streamId < STREAM_OFFSET_TXD){ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(streamId); + if(mi->IsSimple()) + mi->m_alpha = 255; + } + }else{ + // empty + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; + } + } + + ms_bLoadingBigModel = false; + for(i = 0; i < 4; i++){ + ms_channel[1].streamIds[i] = -1; + ms_channel[1].offsets[i] = -1; + } + ms_channel[1].state = CHANNELSTATE_IDLE; + bInsideLoadAll = false; +} +#endif + +void +CStreaming::FlushChannels(void) +{ + if(ms_channel[1].state == CHANNELSTATE_STARTED) + ProcessLoadingChannel(1); + + if(ms_channel[0].state == CHANNELSTATE_READING){ + CdStreamSync(0); + ProcessLoadingChannel(0); + } + if(ms_channel[0].state == CHANNELSTATE_STARTED) + ProcessLoadingChannel(0); + + if(ms_channel[1].state == CHANNELSTATE_READING){ + CdStreamSync(1); + ProcessLoadingChannel(1); + } + if(ms_channel[1].state == CHANNELSTATE_STARTED) + ProcessLoadingChannel(1); +} + +void +CStreaming::FlushRequestList(void) +{ + CStreamingInfo *si, *next; + + for(si = ms_startRequestedList.m_next; si != &ms_endRequestedList; si = next){ + next = si->m_next; + RemoveModel(si - ms_aInfoForModel); + } +#ifdef FLUSHABLE_STREAMING + if(ms_channel[0].state == CHANNELSTATE_READING) { + flushStream[0] = 1; + } + if(ms_channel[1].state == CHANNELSTATE_READING) { + flushStream[1] = 1; + } +#endif + FlushChannels(); +} + + +void +CStreaming::ImGonnaUseStreamingMemory(void) +{ + PUSH_MEMID(MEMID_STREAM); +} + +void +CStreaming::IHaveUsedStreamingMemory(void) +{ + POP_MEMID(); + UpdateMemoryUsed(); +} + +void +CStreaming::UpdateMemoryUsed(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + ms_memoryUsed = + gMainHeap.GetMemoryUsed(MEMID_STREAM) + + gMainHeap.GetMemoryUsed(MEMID_STREAM_MODELS) + + gMainHeap.GetMemoryUsed(MEMID_STREAM_TEXUTRES); +#endif +} + +#define STREAM_DIST 80.0f + +void +CStreaming::AddModelsToRequestList(const CVector &pos) +{ + float xmin, xmax, ymin, ymax; + int ixmin, ixmax, iymin, iymax; + int ix, iy; + int dx, dy, d; + CSector *sect; + + xmin = pos.x - STREAM_DIST; + ymin = pos.y - STREAM_DIST; + xmax = pos.x + STREAM_DIST; + ymax = pos.y + STREAM_DIST; + + ixmin = CWorld::GetSectorIndexX(xmin); + if(ixmin < 0) ixmin = 0; + ixmax = CWorld::GetSectorIndexX(xmax); + if(ixmax >= NUMSECTORS_X) ixmax = NUMSECTORS_X-1; + iymin = CWorld::GetSectorIndexY(ymin); + if(iymin < 0) iymin = 0; + iymax = CWorld::GetSectorIndexY(ymax); + if(iymax >= NUMSECTORS_Y) iymax = NUMSECTORS_Y-1; + + CWorld::AdvanceCurrentScanCode(); + + for(iy = iymin; iy <= iymax; iy++){ + dy = iy - CWorld::GetSectorIndexY(pos.y); + for(ix = ixmin; ix <= ixmax; ix++){ + + if(CRenderer::m_loadingPriority && ms_numModelsRequested > 5) + return; + + dx = ix - CWorld::GetSectorIndexX(pos.x); + d = dx*dx + dy*dy; + sect = CWorld::GetSector(ix, iy); + if(d <= 1){ + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + }else if(d <= 4*4){ + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], pos.x, pos.y, xmin, ymin, xmax, ymax); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], pos.x, pos.y, xmin, ymin, xmax, ymax); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], pos.x, pos.y, xmin, ymin, xmax, ymax); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], pos.x, pos.y, xmin, ymin, xmax, ymax); + } + } + } +} + +void +CStreaming::ProcessEntitiesInSectorList(CPtrList &list, float x, float y, float xmin, float ymin, float xmax, float ymax) +{ + CPtrNode *node; + CEntity *e; + float lodDistSq; + CVector2D pos; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + + if(e->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + + e->m_scanCode = CWorld::GetCurrentScanCode(); + if(!e->bStreamingDontDelete && !e->bIsSubway && + (!e->IsObject() || ((CObject*)e)->ObjectCreatedBy != TEMP_OBJECT)){ + CTimeModelInfo *mi = (CTimeModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex()); + if (mi->GetModelType() != MITYPE_TIME || CClock::GetIsTimeInRange(mi->GetTimeOn(), mi->GetTimeOff())) { + lodDistSq = sq(mi->GetLargestLodDistance()); + lodDistSq = Min(lodDistSq, sq(STREAM_DIST)); + pos = CVector2D(e->GetPosition()); + if(xmin < pos.x && pos.x < xmax && + ymin < pos.y && pos.y < ymax && + (CVector2D(x, y) - pos).MagnitudeSqr() < lodDistSq) + if(CRenderer::IsEntityCullZoneVisible(e)) + RequestModel(e->GetModelIndex(), 0); + } + } + } +} + +void +CStreaming::ProcessEntitiesInSectorList(CPtrList &list) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + + if(e->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + + e->m_scanCode = CWorld::GetCurrentScanCode(); + if(!e->bStreamingDontDelete && !e->bIsSubway && + (!e->IsObject() || ((CObject*)e)->ObjectCreatedBy != TEMP_OBJECT)){ + CTimeModelInfo *mi = (CTimeModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex()); + if (mi->GetModelType() != MITYPE_TIME || CClock::GetIsTimeInRange(mi->GetTimeOn(), mi->GetTimeOff())) + if(CRenderer::IsEntityCullZoneVisible(e)) + RequestModel(e->GetModelIndex(), 0); + } + } +} + +void +CStreaming::DeleteFarAwayRwObjects(const CVector &pos) +{ + int posx, posy; + int x, y; + int r, i; + CSector *sect; + + posx = CWorld::GetSectorIndexX(pos.x); + posy = CWorld::GetSectorIndexY(pos.y); + + // Move oldSectorX/Y to new sector and delete RW objects in its "wake" for every step: + // O is the old sector, <- is the direction in which we move it, + // X are the sectors we delete RW objects from (except we go up to 10) + // X + // X X + // X X X + // X X X + // <- O X X X + // X X X + // X X X + // X X + // X + + while(posx != ms_oldSectorX){ + if(posx < ms_oldSectorX){ + for(r = 2; r <= 10; r++){ + x = ms_oldSectorX + r; + if(x < 0) + continue; + if(x >= NUMSECTORS_X) + break; + + for(i = -r; i <= r; i++){ + y = ms_oldSectorY + i; + if(y < 0) + continue; + if(y >= NUMSECTORS_Y) + break; + + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + } + } + ms_oldSectorX--; + }else{ + for(r = 2; r <= 10; r++){ + x = ms_oldSectorX - r; + if(x < 0) + break; + if(x >= NUMSECTORS_X) + continue; + + for(i = -r; i <= r; i++){ + y = ms_oldSectorY + i; + if(y < 0) + continue; + if(y >= NUMSECTORS_Y) + break; + + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + } + } + ms_oldSectorX++; + } + } + + while(posy != ms_oldSectorY){ + if(posy < ms_oldSectorY){ + for(r = 2; r <= 10; r++){ + y = ms_oldSectorY + r; + if(y < 0) + continue; + if(y >= NUMSECTORS_Y) + break; + + for(i = -r; i <= r; i++){ + x = ms_oldSectorX + i; + if(x < 0) + continue; + if(x >= NUMSECTORS_X) + break; + + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + } + } + ms_oldSectorY--; + }else{ + for(r = 2; r <= 10; r++){ + y = ms_oldSectorY - r; + if(y < 0) + break; + if(y >= NUMSECTORS_Y) + continue; + + for(i = -r; i <= r; i++){ + x = ms_oldSectorX + i; + if(x < 0) + continue; + if(x >= NUMSECTORS_X) + break; + + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInOverlapSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], ms_oldSectorX, ms_oldSectorY); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + } + } + ms_oldSectorY++; + } + } +} + +void +CStreaming::DeleteAllRwObjects(void) +{ + int x, y; + CSector *sect; + + for(x = 0; x < NUMSECTORS_X; x++) + for(y = 0; y < NUMSECTORS_Y; y++){ + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS_OVERLAP]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES_OVERLAP]); + } +} + +void +CStreaming::DeleteRwObjectsAfterDeath(const CVector &pos) +{ + int ix, iy; + int x, y; + CSector *sect; + + ix = CWorld::GetSectorIndexX(pos.x); + iy = CWorld::GetSectorIndexY(pos.y); + + for(x = 0; x < NUMSECTORS_X; x++) + for(y = 0; y < NUMSECTORS_Y; y++) + if(Abs(ix - x) > 3.0f && + Abs(iy - y) > 3.0f){ + sect = CWorld::GetSector(x, y); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_OBJECTS_OVERLAP]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES]); + DeleteRwObjectsInSectorList(sect->m_lists[ENTITYLIST_DUMMIES_OVERLAP]); + } +} + +void +CStreaming::DeleteRwObjectsBehindCamera(size_t mem) +{ + int ix, iy; + int x, y; + int xmin, xmax, ymin, ymax; + int inc; + CSector *sect; + + if(ms_memoryUsed < mem) + return; + + ix = CWorld::GetSectorIndexX(TheCamera.GetPosition().x); + iy = CWorld::GetSectorIndexY(TheCamera.GetPosition().y); + + if(Abs(TheCamera.GetForward().x) > Abs(TheCamera.GetForward().y)){ + // looking west/east + + ymin = Max(iy - 10, 0); + ymax = Min(iy + 10, NUMSECTORS_Y - 1); + assert(ymin <= ymax); + + // Delete a block of sectors that we know is behind the camera + if(TheCamera.GetForward().x > 0.0f){ + // looking east + xmax = Max(ix - 2, 0); + xmin = Max(ix - 10, 0); + inc = 1; + }else{ + // looking west + xmax = Min(ix + 2, NUMSECTORS_X - 1); + xmin = Min(ix + 10, NUMSECTORS_X - 1); + inc = -1; + } + for(y = ymin; y <= ymax; y++){ + for(x = xmin; x != xmax; x += inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + + // Now a block that intersects with the camera's frustum + if(TheCamera.GetForward().x > 0.0f){ + // looking east + xmax = Max(ix + 10, 0); + xmin = Max(ix - 2, 0); + inc = 1; + }else{ + // looking west + xmax = Min(ix - 10, NUMSECTORS_X - 1); + xmin = Min(ix + 2, NUMSECTORS_X - 1); + inc = -1; + } + for(y = ymin; y <= ymax; y++){ + for(x = xmin; x != xmax; x += inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + + if(RemoveReferencedTxds(mem)) + return; + + // As last resort, delete objects from the last step more aggressively + for(y = ymin; y <= ymax; y++){ + for(x = xmax; x != xmin; x -= inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + }else{ + // looking north/south + + xmin = Max(ix - 10, 0); + xmax = Min(ix + 10, NUMSECTORS_X - 1); + assert(xmin <= xmax); + + // Delete a block of sectors that we know is behind the camera + if(TheCamera.GetForward().y > 0.0f){ + // looking north + ymax = Max(iy - 2, 0); + ymin = Max(iy - 10, 0); + inc = 1; + }else{ + // looking south + ymax = Min(iy + 2, NUMSECTORS_Y - 1); + ymin = Min(iy + 10, NUMSECTORS_Y - 1); + inc = -1; + } + for(x = xmin; x <= xmax; x++){ + for(y = ymin; y != ymax; y += inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + + // Now a block that intersects with the camera's frustum + if(TheCamera.GetForward().y > 0.0f){ + // looking north + ymax = Max(iy + 10, 0); + ymin = Max(iy - 2, 0); + inc = 1; + }else{ + // looking south + ymax = Min(iy - 10, NUMSECTORS_Y - 1); + ymin = Min(iy + 2, NUMSECTORS_Y - 1); + inc = -1; + } + for(x = xmin; x <= xmax; x++){ + for(y = ymin; y != ymax; y += inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsNotInFrustumInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + + if(RemoveReferencedTxds(mem)) + return; + + // As last resort, delete objects from the last step more aggressively + for(x = xmin; x <= xmax; x++){ + for(y = ymax; y != ymin; y -= inc){ + sect = CWorld::GetSector(x, y); + if(DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], mem) || + DeleteRwObjectsBehindCameraInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], mem)) + return; + } + } + } +} + +void +CStreaming::DeleteRwObjectsInSectorList(CPtrList &list) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(!e->bStreamingDontDelete && !e->bImBeingRendered) + e->DeleteRwObject(); + } +} + +void +CStreaming::DeleteRwObjectsInOverlapSectorList(CPtrList &list, int32 x, int32 y) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(e->m_rwObject && !e->bStreamingDontDelete && !e->bImBeingRendered){ + // Now this is pretty weird... + if(Abs(CWorld::GetSectorIndexX(e->GetPosition().x) - x) >= 2.0f) +// { + e->DeleteRwObject(); +// return; // BUG? +// } + else // FIX? + if(Abs(CWorld::GetSectorIndexY(e->GetPosition().y) - y) >= 2.0f) + e->DeleteRwObject(); + } + } +} + +bool +CStreaming::DeleteRwObjectsBehindCameraInSectorList(CPtrList &list, size_t mem) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(!e->bStreamingDontDelete && !e->bImBeingRendered && + e->m_rwObject && ms_aInfoForModel[e->GetModelIndex()].m_next){ + e->DeleteRwObject(); + if (CModelInfo::GetModelInfo(e->GetModelIndex())->GetNumRefs() == 0) { + RemoveModel(e->GetModelIndex()); + if(ms_memoryUsed < mem) + return true; + } + } + } + return false; +} + +bool +CStreaming::DeleteRwObjectsNotInFrustumInSectorList(CPtrList &list, size_t mem) +{ + CPtrNode *node; + CEntity *e; + + for(node = list.first; node; node = node->next){ + e = (CEntity*)node->item; + if(!e->bStreamingDontDelete && !e->bImBeingRendered && + e->m_rwObject && !e->IsVisible() && ms_aInfoForModel[e->GetModelIndex()].m_next){ + e->DeleteRwObject(); + if (CModelInfo::GetModelInfo(e->GetModelIndex())->GetNumRefs() == 0) { + RemoveModel(e->GetModelIndex()); + if(ms_memoryUsed < mem) + return true; + } + } + } + return false; +} + +void +CStreaming::MakeSpaceFor(int32 size) +{ +#ifdef FIX_BUGS +#define MB (1024 * 1024) + if(ms_memoryAvailable == 0) { + extern size_t _dwMemAvailPhys; + ms_memoryAvailable = (_dwMemAvailPhys - 10 * MB) / 2; + if(ms_memoryAvailable < 50 * MB) ms_memoryAvailable = 50 * MB; + } +#undef MB +#endif + while(ms_memoryUsed >= ms_memoryAvailable - size) + if(!RemoveLeastUsedModel()) { + DeleteRwObjectsBehindCamera(ms_memoryAvailable - size); + return; + } +} + +void +CStreaming::LoadScene(const CVector &pos) +{ + CStreamingInfo *si, *prev; + eLevelName level; + + level = CTheZones::GetLevelFromPosition(&pos); + debug("Start load scene\n"); + for(si = ms_endRequestedList.m_prev; si != &ms_startRequestedList; si = prev){ + prev = si->m_prev; + if((si->m_flags & (STREAMFLAGS_KEEP_IN_MEMORY|STREAMFLAGS_PRIORITY)) == 0) + RemoveModel(si - ms_aInfoForModel); + } + CRenderer::m_loadingPriority = false; + CCullZones::ForceCullZoneCoors(pos); + DeleteAllRwObjects(); + AddModelsToRequestList(pos); + CRadar::StreamRadarSections(pos); + RemoveUnusedBigBuildings(level); + RequestBigBuildings(level); + LoadAllRequestedModels(false); + debug("End load scene\n"); +} + +void +CStreaming::MemoryCardSave(uint8 *buf, uint32 *size) +{ + int i; + + *size = NUM_DEFAULT_MODELS; + for(i = 0; i < NUM_DEFAULT_MODELS; i++) + if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED) + buf[i] = ms_aInfoForModel[i].m_flags; + else + buf[i] = 0xFF; +} + +void +CStreaming::MemoryCardLoad(uint8 *buf, uint32 size) +{ + uint32 i; + + assert(size == NUM_DEFAULT_MODELS); + for(i = 0; i < size; i++) + if(ms_aInfoForModel[i].m_loadState == STREAMSTATE_LOADED) + if(buf[i] != 0xFF) + ms_aInfoForModel[i].m_flags = buf[i]; +} + +void +CStreaming::UpdateForAnimViewer(void) +{ + if (CStreaming::ms_channelError == -1) { + CStreaming::AddModelsToRequestList(CVector(0.0f, 0.0f, 0.0f)); + CStreaming::LoadRequestedModels(); + sprintf(gString, "Requested %d, memory size %zuK\n", CStreaming::ms_numModelsRequested, 2 * CStreaming::ms_memoryUsed); // original modifier was %d + } + else { + CStreaming::RetryLoadFile(CStreaming::ms_channelError); + } +} + + +void +CStreaming::PrintStreamingBufferState() +{ + char str[128]; + wchar wstr[128]; + uint32 offset, size; + + CTimer::Stop(); + int i = 0; + while (i < NUMSTREAMINFO) { + while (true) { + int j = 0; + DoRWStuffStartOfFrame(50, 50, 50, 0, 0, 0, 255); + CPad::UpdatePads(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + + CRect unusedRect(0, 0, RsGlobal.maximumWidth, RsGlobal.maximumHeight); + CRGBA unusedColor(255, 255, 255, 255); + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetWrapx(DEFAULT_SCREEN_WIDTH); + CFont::SetScale(0.5f, 0.75f); + CFont::SetCentreOff(); + CFont::SetCentreSize(DEFAULT_SCREEN_WIDTH); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(200, 200, 200, 200)); + CFont::SetBackGroundOnlyTextOff(); + int modelIndex = i; + if (modelIndex < NUMSTREAMINFO) { + int y = 24; + for ( ; j < 34 && modelIndex < NUMSTREAMINFO; modelIndex++) { + CStreamingInfo *streamingInfo = &ms_aInfoForModel[modelIndex]; + CBaseModelInfo *modelInfo = CModelInfo::GetModelInfo(modelIndex); + if (streamingInfo->m_loadState != STREAMSTATE_LOADED || !streamingInfo->GetCdPosnAndSize(offset, size)) + continue; + + if (modelIndex >= STREAM_OFFSET_TXD) + sprintf(str, "txd %s, refs %d, size %dK, flags 0x%x", CTxdStore::GetTxdName(modelIndex - STREAM_OFFSET_TXD), + CTxdStore::GetNumRefs(modelIndex - STREAM_OFFSET_TXD), 2 * size, streamingInfo->m_flags); + else + sprintf(str, "model %d,%s, refs%d, size%dK, flags%x", modelIndex, modelInfo->GetModelName(), modelInfo->GetNumRefs(), 2 * size, + streamingInfo->m_flags); + AsciiToUnicode(str, wstr); + CFont::PrintString(24.0f, y, wstr); + y += 12; + j++; + } + } + + if (CPad::GetPad(1)->GetCrossJustDown()) + i = modelIndex; + + if (!CPad::GetPad(1)->GetTriangleJustDown()) + break; + + i = 0; + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); + } + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); + } + CTimer::Update(); +} diff --git a/src/core/Streaming.h b/src/core/Streaming.h new file mode 100644 index 0000000..3294a88 --- /dev/null +++ b/src/core/Streaming.h @@ -0,0 +1,197 @@ +#pragma once + +#include "Game.h" + +enum { + STREAM_OFFSET_TXD = MODELINFOSIZE, + NUMSTREAMINFO = STREAM_OFFSET_TXD+TXDSTORESIZE +}; + +enum StreamFlags +{ + STREAMFLAGS_DONT_REMOVE = 0x01, + STREAMFLAGS_SCRIPTOWNED = 0x02, + STREAMFLAGS_DEPENDENCY = 0x04, // Is this right? + STREAMFLAGS_PRIORITY = 0x08, + STREAMFLAGS_NOFADE = 0x10, + + STREAMFLAGS_CANT_REMOVE = STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_SCRIPTOWNED, + STREAMFLAGS_KEEP_IN_MEMORY = STREAMFLAGS_DONT_REMOVE|STREAMFLAGS_SCRIPTOWNED|STREAMFLAGS_DEPENDENCY, +}; + +enum StreamLoadState +{ + STREAMSTATE_NOTLOADED = 0, + STREAMSTATE_LOADED = 1, + STREAMSTATE_INQUEUE = 2, + STREAMSTATE_READING = 3, // channel is reading + STREAMSTATE_STARTED = 4, // first part loaded +}; + +enum ChannelState +{ + CHANNELSTATE_IDLE = 0, + CHANNELSTATE_READING = 1, + CHANNELSTATE_STARTED = 2, + CHANNELSTATE_ERROR = 3, +}; + +class CStreamingInfo +{ +public: + CStreamingInfo *m_next; + CStreamingInfo *m_prev; + uint8 m_loadState; + uint8 m_flags; + + int16 m_nextID; + uint32 m_position; + uint32 m_size; + + bool GetCdPosnAndSize(uint32 &posn, uint32 &size); + void SetCdPosnAndSize(uint32 posn, uint32 size); + void AddToList(CStreamingInfo *link); + void RemoveFromList(void); + uint32 GetCdSize(void) { return m_size; } + bool IsPriority(void) { return !!(m_flags & STREAMFLAGS_PRIORITY); } +}; + +struct CStreamingChannel +{ + int32 streamIds[4]; + int32 offsets[4]; + int32 state; + int32 field24; + int32 position; + int32 size; + int32 numTries; + int32 status; // from CdStream +}; + +class CDirectory; +class CPtrList; + +class CStreaming +{ +public: + static bool ms_disableStreaming; + static bool ms_bLoadingBigModel; + static int32 ms_numModelsRequested; + static CStreamingInfo ms_aInfoForModel[NUMSTREAMINFO]; + static CStreamingInfo ms_startLoadedList; + static CStreamingInfo ms_endLoadedList; + static CStreamingInfo ms_startRequestedList; + static CStreamingInfo ms_endRequestedList; + static int32 ms_oldSectorX; + static int32 ms_oldSectorY; + static int32 ms_streamingBufferSize; +#ifndef ONE_THREAD_PER_CHANNEL + static int8 *ms_pStreamingBuffer[2]; +#else + static int8 *ms_pStreamingBuffer[4]; +#endif + static size_t ms_memoryUsed; + static CStreamingChannel ms_channel[2]; + static int32 ms_channelError; + static int32 ms_numVehiclesLoaded; + static int32 ms_vehiclesLoaded[MAXVEHICLESLOADED]; + static int32 ms_lastVehicleDeleted; + static CDirectory *ms_pExtraObjectsDir; + static int32 ms_numPriorityRequests; + static bool ms_hasLoadedLODs; + static int32 ms_currentPedGrp; + static int32 ms_lastCullZone; + static uint16 ms_loadedGangs; + static uint16 ms_loadedGangCars; + static int32 ms_currentPedLoading; + static int32 ms_imageOffsets[NUMCDIMAGES]; + static int32 ms_lastImageRead; + static int32 ms_imageSize; + static size_t ms_memoryAvailable; + + static void Init(void); + static void Init2(void); + static void Shutdown(void); + static void Update(void); + static void LoadCdDirectory(void); + static void LoadCdDirectory(const char *dirname, int32 n); + static bool ConvertBufferToObject(int8 *buf, int32 streamId); + static bool FinishLoadingLargeFile(int8 *buf, int32 streamId); + static bool HasModelLoaded(int32 id) { return ms_aInfoForModel[id].m_loadState == STREAMSTATE_LOADED; } + static bool HasTxdLoaded(int32 id) { return HasModelLoaded(id+STREAM_OFFSET_TXD); } + static bool CanRemoveModel(int32 id) { return (ms_aInfoForModel[id].m_flags & STREAMFLAGS_CANT_REMOVE) == 0; } + static bool CanRemoveTxd(int32 id) { return CanRemoveModel(id+STREAM_OFFSET_TXD); } + static void RequestModel(int32 model, int32 flags); + static void ReRequestModel(int32 model) { RequestModel(model, ms_aInfoForModel[model].m_flags); } + static void RequestTxd(int32 txd, int32 flags) { RequestModel(txd + STREAM_OFFSET_TXD, flags); } + static void ReRequestTxd(int32 txd) { ReRequestModel(txd + STREAM_OFFSET_TXD); } + static void RequestSubway(void); + static void RequestBigBuildings(eLevelName level); + static void RequestIslands(eLevelName level); + static void RequestSpecialModel(int32 modelId, const char *modelName, int32 flags); + static void RequestSpecialChar(int32 charId, const char *modelName, int32 flags); + static bool HasSpecialCharLoaded(int32 id); + static void SetMissionDoesntRequireSpecialChar(int32 id); + static void DecrementRef(int32 id); + static void RemoveModel(int32 id); + static void RemoveTxd(int32 id) { RemoveModel(id + STREAM_OFFSET_TXD); } + static void RemoveUnusedBuildings(eLevelName level); + static void RemoveBuildings(eLevelName level); + static void RemoveUnusedBigBuildings(eLevelName level); + static void RemoveIslandsNotUsed(eLevelName level); + static void RemoveBigBuildings(eLevelName level); + static bool RemoveLoadedVehicle(void); + static bool RemoveLeastUsedModel(void); + static void RemoveAllUnusedModels(void); + static void RemoveUnusedModelsInLoadedList(void); + static bool RemoveReferencedTxds(size_t mem); // originally signed + static int32 GetAvailableVehicleSlot(void); + static bool IsTxdUsedByRequestedModels(int32 txdId); + static bool AddToLoadedVehiclesList(int32 modelId); + static bool IsObjectInCdImage(int32 id); + static void HaveAllBigBuildingsLoaded(eLevelName level); + static void SetModelIsDeletable(int32 id); + static void SetModelTxdIsDeletable(int32 id); + static void SetMissionDoesntRequireModel(int32 id); + static void LoadInitialPeds(void); + static void LoadInitialVehicles(void); + static void StreamVehiclesAndPeds(void); + static void StreamZoneModels(const CVector &pos); + static void RemoveCurrentZonesModels(void); + + static int32 GetCdImageOffset(int32 lastPosn); + static int32 GetNextFileOnCd(int32 position, bool priority); + static void RequestModelStream(int32 ch); + static bool ProcessLoadingChannel(int32 ch); + static void RetryLoadFile(int32 ch); + static void LoadRequestedModels(void); + static void LoadAllRequestedModels(bool priority); + static void FlushChannels(void); + static void FlushRequestList(void); + + static void MakeSpaceFor(int32 size); + static void ImGonnaUseStreamingMemory(void); + static void IHaveUsedStreamingMemory(void); + static void UpdateMemoryUsed(void); + + static void AddModelsToRequestList(const CVector &pos); + static void ProcessEntitiesInSectorList(CPtrList &list, float x, float y, float xmin, float ymin, float xmax, float ymax); + static void ProcessEntitiesInSectorList(CPtrList &list); + static void DeleteFarAwayRwObjects(const CVector &pos); + static void DeleteAllRwObjects(void); + static void DeleteRwObjectsAfterDeath(const CVector &pos); + static void DeleteRwObjectsBehindCamera(size_t mem); // originally signed + static void DeleteRwObjectsInSectorList(CPtrList &list); + static void DeleteRwObjectsInOverlapSectorList(CPtrList &list, int32 x, int32 y); + static bool DeleteRwObjectsBehindCameraInSectorList(CPtrList &list, size_t mem); // originally signed + static bool DeleteRwObjectsNotInFrustumInSectorList(CPtrList &list, size_t mem); // originally signed + + static void LoadScene(const CVector &pos); + + static void MemoryCardSave(uint8 *buffer, uint32 *length); + static void MemoryCardLoad(uint8 *buffer, uint32 length); + + static void UpdateForAnimViewer(void); + + static void PrintStreamingBufferState(); +}; diff --git a/src/core/SurfaceTable.cpp b/src/core/SurfaceTable.cpp new file mode 100644 index 0000000..b1bcceb --- /dev/null +++ b/src/core/SurfaceTable.cpp @@ -0,0 +1,143 @@ +#include "common.h" + +#include "main.h" +#include "FileMgr.h" +#include "Weather.h" +#include "Collision.h" +#include "SurfaceTable.h" + +float CSurfaceTable::ms_aAdhesiveLimitTable[NUMADHESIVEGROUPS][NUMADHESIVEGROUPS]; + +void +CSurfaceTable::Initialise(Const char *filename) +{ + int lineno, fieldno; + char *line; + char surfname[256]; + float adhesiveLimit; + + CFileMgr::SetDir(""); + CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r"); + + line = (char*)work_buff; + for(lineno = 0; lineno < NUMADHESIVEGROUPS; lineno++){ + // skip white space and comments + while(*line == ' ' || *line == '\t' || *line == '\n' || *line == '\r' || *line == ';'){ + if(*line == ';'){ + while(*line != '\n' && *line != '\r') + line++; + }else + line++; + } + + sscanf(line, "%s", surfname); + // skip what we just read + while(!(*line == ' ' || *line == '\t' || *line == ',')) + line++; + + for(fieldno = 0; fieldno <= lineno; fieldno++){ + // skip white space + while(*line == ' ' || *line == '\t' || *line == ',') + line++; + adhesiveLimit = 0.0f; + if(*line != '-') + sscanf(line, "%f", &adhesiveLimit); + // skip what we just read + while(!(*line == ' ' || *line == '\t' || *line == ',' || *line == '\n')) + line++; + + ms_aAdhesiveLimitTable[lineno][fieldno] = adhesiveLimit; + ms_aAdhesiveLimitTable[fieldno][lineno] = adhesiveLimit; + } + } +} + +int +CSurfaceTable::GetAdhesionGroup(uint8 surfaceType) +{ + switch(surfaceType){ + case SURFACE_DEFAULT: return ADHESIVE_ROAD; + case SURFACE_TARMAC: return ADHESIVE_ROAD; + case SURFACE_GRASS: return ADHESIVE_LOOSE; + case SURFACE_GRAVEL: return ADHESIVE_LOOSE; + case SURFACE_MUD_DRY: return ADHESIVE_HARD; + case SURFACE_PAVEMENT: return ADHESIVE_ROAD; + case SURFACE_CAR: return ADHESIVE_HARD; + case SURFACE_GLASS: return ADHESIVE_HARD; + case SURFACE_TRANSPARENT_CLOTH: return ADHESIVE_HARD; + case SURFACE_GARAGE_DOOR: return ADHESIVE_HARD; + case SURFACE_CAR_PANEL: return ADHESIVE_HARD; + case SURFACE_THICK_METAL_PLATE: return ADHESIVE_HARD; + case SURFACE_SCAFFOLD_POLE: return ADHESIVE_HARD; + case SURFACE_LAMP_POST: return ADHESIVE_HARD; + case SURFACE_FIRE_HYDRANT: return ADHESIVE_HARD; + case SURFACE_GIRDER: return ADHESIVE_HARD; + case SURFACE_METAL_CHAIN_FENCE: return ADHESIVE_HARD; + case SURFACE_PED: return ADHESIVE_RUBBER; + case SURFACE_SAND: return ADHESIVE_LOOSE; + case SURFACE_WATER: return ADHESIVE_WET; + case SURFACE_WOOD_CRATES: return ADHESIVE_ROAD; + case SURFACE_WOOD_BENCH: return ADHESIVE_ROAD; + case SURFACE_WOOD_SOLID: return ADHESIVE_ROAD; + case SURFACE_RUBBER: return ADHESIVE_RUBBER; + case SURFACE_PLASTIC: return ADHESIVE_HARD; + case SURFACE_HEDGE: return ADHESIVE_LOOSE; + case SURFACE_STEEP_CLIFF: return ADHESIVE_LOOSE; + case SURFACE_CONTAINER: return ADHESIVE_HARD; + case SURFACE_NEWS_VENDOR: return ADHESIVE_HARD; + case SURFACE_WHEELBASE: return ADHESIVE_RUBBER; + case SURFACE_CARDBOARDBOX: return ADHESIVE_LOOSE; + case SURFACE_TRANSPARENT_STONE: return ADHESIVE_HARD; + case SURFACE_METAL_GATE: return ADHESIVE_HARD; + default: return ADHESIVE_ROAD; + } +} + +float +CSurfaceTable::GetWetMultiplier(uint8 surfaceType) +{ + switch(surfaceType){ + case SURFACE_DEFAULT: + case SURFACE_TARMAC: + case SURFACE_MUD_DRY: + case SURFACE_PAVEMENT: + case SURFACE_TRANSPARENT_CLOTH: + case SURFACE_WOOD_CRATES: + case SURFACE_WOOD_BENCH: + case SURFACE_WOOD_SOLID: + case SURFACE_HEDGE: + case SURFACE_CARDBOARDBOX: + case SURFACE_TRANSPARENT_STONE: + return 1.0f - CWeather::WetRoads*0.25f; + + case SURFACE_GRASS: + case SURFACE_CAR: + case SURFACE_GLASS: + case SURFACE_GARAGE_DOOR: + case SURFACE_CAR_PANEL: + case SURFACE_THICK_METAL_PLATE: + case SURFACE_SCAFFOLD_POLE: + case SURFACE_LAMP_POST: + case SURFACE_FIRE_HYDRANT: + case SURFACE_GIRDER: + case SURFACE_METAL_CHAIN_FENCE: + case SURFACE_PED: + case SURFACE_RUBBER: + case SURFACE_PLASTIC: + case SURFACE_STEEP_CLIFF: + case SURFACE_CONTAINER: + case SURFACE_NEWS_VENDOR: + case SURFACE_WHEELBASE: + case SURFACE_METAL_GATE: + return 1.0f - CWeather::WetRoads*0.4f; + + default: + return 1.0f; + } +} + +float +CSurfaceTable::GetAdhesiveLimit(CColPoint &colpoint) +{ + return ms_aAdhesiveLimitTable[GetAdhesionGroup(colpoint.surfaceB)][GetAdhesionGroup(colpoint.surfaceA)]; +} diff --git a/src/core/SurfaceTable.h b/src/core/SurfaceTable.h new file mode 100644 index 0000000..8ee1724 --- /dev/null +++ b/src/core/SurfaceTable.h @@ -0,0 +1,80 @@ +#pragma once + +enum eSurfaceType +{ + SURFACE_DEFAULT, + SURFACE_TARMAC, + SURFACE_GRASS, + SURFACE_GRAVEL, + SURFACE_MUD_DRY, + SURFACE_PAVEMENT, + SURFACE_CAR, + SURFACE_GLASS, + SURFACE_TRANSPARENT_CLOTH, + SURFACE_GARAGE_DOOR, + SURFACE_CAR_PANEL, + SURFACE_THICK_METAL_PLATE, + SURFACE_SCAFFOLD_POLE, + SURFACE_LAMP_POST, + SURFACE_FIRE_HYDRANT, + SURFACE_GIRDER, + SURFACE_METAL_CHAIN_FENCE, + SURFACE_PED, + SURFACE_SAND, + SURFACE_WATER, + SURFACE_WOOD_CRATES, + SURFACE_WOOD_BENCH, + SURFACE_WOOD_SOLID, + SURFACE_RUBBER, + SURFACE_PLASTIC, + SURFACE_HEDGE, + SURFACE_STEEP_CLIFF, + SURFACE_CONTAINER, + SURFACE_NEWS_VENDOR, + SURFACE_WHEELBASE, + SURFACE_CARDBOARDBOX, + SURFACE_TRANSPARENT_STONE, + SURFACE_METAL_GATE, + + // These are illegal + SURFACE_SAND_BEACH, + SURFACE_CONCRETE_BEACH, +}; + +enum +{ + ADHESIVE_RUBBER, + ADHESIVE_HARD, + ADHESIVE_ROAD, + ADHESIVE_LOOSE, + ADHESIVE_WET, + + NUMADHESIVEGROUPS +}; + +struct CColPoint; + +inline bool +IsSeeThrough(uint8 surfType) +{ + switch(surfType) + case SURFACE_GLASS: + case SURFACE_TRANSPARENT_CLOTH: +#if defined(FIX_BUGS) || defined(GTA_PS2) + case SURFACE_METAL_CHAIN_FENCE: + case SURFACE_TRANSPARENT_STONE: + case SURFACE_SCAFFOLD_POLE: +#endif + return true; + return false; +} + +class CSurfaceTable +{ + static float ms_aAdhesiveLimitTable[NUMADHESIVEGROUPS][NUMADHESIVEGROUPS]; +public: + static void Initialise(Const char *filename); + static int GetAdhesionGroup(uint8 surfaceType); + static float GetWetMultiplier(uint8 surfaceType); + static float GetAdhesiveLimit(CColPoint &colpoint); +}; diff --git a/src/core/TimeStep.cpp b/src/core/TimeStep.cpp new file mode 100644 index 0000000..09dae91 --- /dev/null +++ b/src/core/TimeStep.cpp @@ -0,0 +1,5 @@ +#include "TimeStep.h" + +float CTimeStep::ms_fTimeScale = 1.0f; +float CTimeStep::ms_fFramesPerUpdate = 1.0f; +float CTimeStep::ms_fTimeStep = 1.0f; diff --git a/src/core/TimeStep.h b/src/core/TimeStep.h new file mode 100644 index 0000000..6101b4c --- /dev/null +++ b/src/core/TimeStep.h @@ -0,0 +1,10 @@ +#pragma once + +// Pretty sure this class is not used by the game +class CTimeStep +{ +public: + static float ms_fTimeScale; + static float ms_fFramesPerUpdate; + static float ms_fTimeStep; +}; diff --git a/src/core/Timer.cpp b/src/core/Timer.cpp new file mode 100644 index 0000000..e4f5b01 --- /dev/null +++ b/src/core/Timer.cpp @@ -0,0 +1,329 @@ +#define WITHWINDOWS +#include "common.h" +#include "crossplatform.h" + +#include "DMAudio.h" +#include "Record.h" +#include "Timer.h" + +uint32 CTimer::m_snTimeInMilliseconds; +uint32 CTimer::m_snTimeInMillisecondsPauseMode = 1; +uint32 CTimer::m_snTimeInMillisecondsNonClipped; +uint32 CTimer::m_snPreviousTimeInMilliseconds; +uint32 CTimer::m_FrameCounter; +float CTimer::ms_fTimeScale; +float CTimer::ms_fTimeStep; +float CTimer::ms_fTimeStepNonClipped; +bool CTimer::m_UserPause; +bool CTimer::m_CodePause; +#ifdef FIX_BUGS +uint32 CTimer::m_LogicalFrameCounter; +uint32 CTimer::m_LogicalFramesPassed; +#endif + +uint32 _nCyclesPerMS = 1; + +#ifdef _WIN32 +LARGE_INTEGER _oldPerfCounter; +LARGE_INTEGER perfSuspendCounter; +#define RsTimerType uint32 +#else +#define RsTimerType double +#endif + +RsTimerType oldPcTimer; + +RsTimerType suspendPcTimer; + +uint32 suspendDepth; + +void CTimer::Initialise(void) +{ + debug("Initialising CTimer...\n"); + + ms_fTimeScale = 1.0f; + ms_fTimeStep = 1.0f; + suspendDepth = 0; + m_UserPause = false; + m_CodePause = false; + m_snTimeInMillisecondsNonClipped = 0; + m_snPreviousTimeInMilliseconds = 0; + m_snTimeInMilliseconds = 1; +#ifdef FIX_BUGS + m_LogicalFrameCounter = 0; + m_LogicalFramesPassed = 0; +#endif + +#ifdef _WIN32 + LARGE_INTEGER perfFreq; + if ( QueryPerformanceFrequency(&perfFreq) ) + { + OutputDebugString("Performance counter available\n"); + _nCyclesPerMS = uint32(perfFreq.QuadPart / 1000); + QueryPerformanceCounter(&_oldPerfCounter); + } + else +#endif + { + OutputDebugString("Performance counter not available, using millesecond timer\n"); + _nCyclesPerMS = 0; + oldPcTimer = RsTimer(); + } + + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds; + + m_FrameCounter = 0; + + DMAudio.ResetTimers(m_snPreviousTimeInMilliseconds); + + debug("CTimer ready\n"); +} + +void CTimer::Shutdown(void) +{ + ; +} +#ifdef FIX_BUGS +void CTimer::Update(void) +{ + static double frameTimeLogical = 0.0; + static double frameTimeFraction = 0.0; + static double frameTimeFractionScaled = 0.0; + double frameTime; + double dblUpdInMs; + + m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds; + +#ifdef _WIN32 + if ( (double)_nCyclesPerMS != 0.0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + + int32 updInCycles = (pc.LowPart - _oldPerfCounter.LowPart); // & 0x7FFFFFFF; pointless + + _oldPerfCounter = pc; + + // bugfix from VC + double updInCyclesScaled = GetIsPaused() ? updInCycles : updInCycles * ms_fTimeScale; + + frameTime = updInCyclesScaled / (double)_nCyclesPerMS; + + dblUpdInMs = (double)updInCycles / (double)_nCyclesPerMS; + } + else +#endif + { + RsTimerType timer = RsTimer(); + + RsTimerType updInMs = timer - oldPcTimer; + + // bugfix from VC + frameTime = GetIsPaused() ? (double)updInMs : (double)updInMs * ms_fTimeScale; + + oldPcTimer = timer; + + dblUpdInMs = (double)updInMs; + } + + // count frames as if we're running at 30 fps + m_LogicalFramesPassed = 0; + frameTimeLogical += dblUpdInMs; + while(frameTimeLogical >= 1000.0 / 30.0) { + frameTimeLogical -= 1000.0 / 30.0; + m_LogicalFramesPassed++; + } + m_LogicalFrameCounter += m_LogicalFramesPassed; + + frameTimeFraction += dblUpdInMs; + frameTimeFractionScaled += frameTime; + + m_snTimeInMillisecondsPauseMode += uint32(frameTimeFraction); + + if ( GetIsPaused() ) + ms_fTimeStep = 0.0f; + else + { + m_snTimeInMilliseconds += uint32(frameTimeFractionScaled); + m_snTimeInMillisecondsNonClipped += uint32(frameTimeFractionScaled); + ms_fTimeStep = frameTime / 1000.0f * 50.0f; + } + frameTimeFraction -= uint32(frameTimeFraction); + frameTimeFractionScaled -= uint32(frameTimeFractionScaled); + + if ( ms_fTimeStep < 0.01f && !GetIsPaused() ) + ms_fTimeStep = 0.01f; + + ms_fTimeStepNonClipped = ms_fTimeStep; + + if ( !CRecordDataForGame::IsPlayingBack() ) + { + ms_fTimeStep = Min(3.0f, ms_fTimeStep); + + if ( (m_snTimeInMilliseconds - m_snPreviousTimeInMilliseconds) > 60 ) + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 60; + } + + if ( CRecordDataForChase::IsRecording() ) + { + ms_fTimeStep = 1.0f; + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 16; + } + + m_FrameCounter++; +} +#else +void CTimer::Update(void) +{ + m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds; + +#ifdef _WIN32 + if ( (double)_nCyclesPerMS != 0.0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + + int32 updInCycles = (pc.LowPart - _oldPerfCounter.LowPart); // & 0x7FFFFFFF; pointless + + _oldPerfCounter = pc; + + float updInCyclesScaled = updInCycles * ms_fTimeScale; + + double frameTime = updInCyclesScaled / (double)_nCyclesPerMS; + m_snTimeInMillisecondsPauseMode = m_snTimeInMillisecondsPauseMode + frameTime; + + if ( GetIsPaused() ) + ms_fTimeStep = 0.0f; + else + { + m_snTimeInMilliseconds = m_snTimeInMilliseconds + frameTime; + m_snTimeInMillisecondsNonClipped = m_snTimeInMillisecondsNonClipped + frameTime; + ms_fTimeStep = frameTime / 1000.0f * 50.0f; + } + } + else +#endif + { + RsTimerType timer = RsTimer(); + + RsTimerType updInMs = timer - oldPcTimer; + + double frameTime = (double)updInMs * ms_fTimeScale; + + oldPcTimer = timer; + + m_snTimeInMillisecondsPauseMode = m_snTimeInMillisecondsPauseMode + frameTime; + + if ( GetIsPaused() ) + ms_fTimeStep = 0.0f; + else + { + m_snTimeInMilliseconds = m_snTimeInMilliseconds + frameTime; + m_snTimeInMillisecondsNonClipped = m_snTimeInMillisecondsNonClipped + frameTime; + ms_fTimeStep = frameTime / 1000.0f * 50.0f; + } + } + + if ( ms_fTimeStep < 0.01f && !GetIsPaused() ) + ms_fTimeStep = 0.01f; + + ms_fTimeStepNonClipped = ms_fTimeStep; + + if ( !CRecordDataForGame::IsPlayingBack() ) + { + ms_fTimeStep = Min(3.0f, ms_fTimeStep); + + if ( (m_snTimeInMilliseconds - m_snPreviousTimeInMilliseconds) > 60 ) + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 60; + } + + if ( CRecordDataForChase::IsRecording() ) + { + ms_fTimeStep = 1.0f; + m_snTimeInMilliseconds = m_snPreviousTimeInMilliseconds + 16; + } + + m_FrameCounter++; +} +#endif + +void CTimer::Suspend(void) +{ + if ( ++suspendDepth > 1 ) + return; + +#ifdef _WIN32 + if ( (double)_nCyclesPerMS != 0.0 ) + QueryPerformanceCounter(&perfSuspendCounter); + else +#endif + suspendPcTimer = RsTimer(); +} + +void CTimer::Resume(void) +{ + if ( --suspendDepth != 0 ) + return; + +#ifdef _WIN32 + if ( (double)_nCyclesPerMS != 0.0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + + _oldPerfCounter.LowPart += pc.LowPart - perfSuspendCounter.LowPart; + } + else +#endif + oldPcTimer += RsTimer() - suspendPcTimer; +} + +uint32 CTimer::GetCyclesPerMillisecond(void) +{ +#ifdef _WIN32 + if (_nCyclesPerMS != 0) + return _nCyclesPerMS; + else +#endif + return 1; +} + +uint32 CTimer::GetCurrentTimeInCycles(void) +{ +#ifdef _WIN32 + if ( _nCyclesPerMS != 0 ) + { + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + return (pc.LowPart - _oldPerfCounter.LowPart); // & 0x7FFFFFFF; pointless + } + else +#endif + return RsTimer() - oldPcTimer; +} + +bool CTimer::GetIsSlowMotionActive(void) +{ + return ms_fTimeScale < 1.0f; +} + +void CTimer::Stop(void) +{ + m_snPreviousTimeInMilliseconds = m_snTimeInMilliseconds; +} + +void CTimer::StartUserPause(void) +{ + m_UserPause = true; +} + +void CTimer::EndUserPause(void) +{ + m_UserPause = false; +} + +uint32 CTimer::GetCyclesPerFrame() +{ + return 20; +} + diff --git a/src/core/Timer.h b/src/core/Timer.h new file mode 100644 index 0000000..819bd30 --- /dev/null +++ b/src/core/Timer.h @@ -0,0 +1,71 @@ +#pragma once + +class CTimer +{ + + static uint32 m_snTimeInMilliseconds; + static uint32 m_snTimeInMillisecondsPauseMode; + static uint32 m_snTimeInMillisecondsNonClipped; + static uint32 m_snPreviousTimeInMilliseconds; + static uint32 m_FrameCounter; + static float ms_fTimeScale; + static float ms_fTimeStep; + static float ms_fTimeStepNonClipped; +#ifdef FIX_BUGS + static uint32 m_LogicalFrameCounter; + static uint32 m_LogicalFramesPassed; +#endif +public: + static bool m_UserPause; + static bool m_CodePause; + + static const float &GetTimeStep(void) { return ms_fTimeStep; } + static void SetTimeStep(float ts) { ms_fTimeStep = ts; } + static float GetTimeStepInSeconds() { return ms_fTimeStep / 50.0f; } + static uint32 GetTimeStepInMilliseconds() { return ms_fTimeStep / 50.0f * 1000.0f; } + static const float &GetTimeStepNonClipped(void) { return ms_fTimeStepNonClipped; } + static float GetTimeStepNonClippedInSeconds(void) { return ms_fTimeStepNonClipped / 50.0f; } + static float GetTimeStepNonClippedInMilliseconds(void) { return ms_fTimeStepNonClipped / 50.0f * 1000.0f; } + static void SetTimeStepNonClipped(float ts) { ms_fTimeStepNonClipped = ts; } + static const uint32 &GetFrameCounter(void) { return m_FrameCounter; } + static void SetFrameCounter(uint32 fc) { m_FrameCounter = fc; } + static const uint32 &GetTimeInMilliseconds(void) { return m_snTimeInMilliseconds; } + static void SetTimeInMilliseconds(uint32 t) { m_snTimeInMilliseconds = t; } + static uint32 GetTimeInMillisecondsNonClipped(void) { return m_snTimeInMillisecondsNonClipped; } + static void SetTimeInMillisecondsNonClipped(uint32 t) { m_snTimeInMillisecondsNonClipped = t; } + static uint32 GetTimeInMillisecondsPauseMode(void) { return m_snTimeInMillisecondsPauseMode; } + static void SetTimeInMillisecondsPauseMode(uint32 t) { m_snTimeInMillisecondsPauseMode = t; } + static uint32 GetPreviousTimeInMilliseconds(void) { return m_snPreviousTimeInMilliseconds; } + static void SetPreviousTimeInMilliseconds(uint32 t) { m_snPreviousTimeInMilliseconds = t; } + static const float &GetTimeScale(void) { return ms_fTimeScale; } + static void SetTimeScale(float ts) { ms_fTimeScale = ts; } + static uint32 GetCyclesPerFrame(); + + static bool GetIsPaused() { return m_UserPause || m_CodePause; } + static bool GetIsUserPaused() { return m_UserPause; } + static bool GetIsCodePaused() { return m_CodePause; } + static void SetCodePause(bool pause) { m_CodePause = pause; } + + static void Initialise(void); + static void Shutdown(void); + static void Update(void); + static void Suspend(void); + static void Resume(void); + static uint32 GetCyclesPerMillisecond(void); + static uint32 GetCurrentTimeInCycles(void); + static bool GetIsSlowMotionActive(void); + static void Stop(void); + static void StartUserPause(void); + static void EndUserPause(void); + + friend bool GenericLoad(void); + friend bool GenericSave(int file); + friend class CMemoryCard; + +#ifdef FIX_BUGS + static float GetDefaultTimeStep(void) { return 50.0f / 30.0f; } + static float GetTimeStepFix(void) { return GetTimeStep() / GetDefaultTimeStep(); } + static uint32 GetLogicalFrameCounter(void) { return m_LogicalFrameCounter; } + static uint32 GetLogicalFramesPassed(void) { return m_LogicalFramesPassed; } +#endif +}; diff --git a/src/core/User.cpp b/src/core/User.cpp new file mode 100644 index 0000000..f906ae4 --- /dev/null +++ b/src/core/User.cpp @@ -0,0 +1,127 @@ +#include "common.h" + + +#include "Hud.h" +#include "PlayerPed.h" +#include "Replay.h" +#include "Text.h" +#include "User.h" +#include "Vehicle.h" +#include "World.h" +#include "Zones.h" + +CPlaceName CUserDisplay::PlaceName; +COnscreenTimer CUserDisplay::OnscnTimer; +CPager CUserDisplay::Pager; +CCurrentVehicle CUserDisplay::CurrentVehicle; + +CPlaceName::CPlaceName() +{ + Init(); +} + +void +CPlaceName::Init() +{ + m_pZone = nil; + m_pZone2 = nil; + m_nAdditionalTimer = 0; +} + +void +CPlaceName::Process() +{ + CVector pos = CWorld::Players[CWorld::PlayerInFocus].GetPos(); + CZone *navigZone = CTheZones::FindSmallestZonePositionType(&pos, ZONE_NAVIG); + CZone *defaultZone = CTheZones::FindSmallestZonePositionType(&pos, ZONE_DEFAULT); + + if (navigZone == nil) m_pZone = nil; + if (defaultZone == nil) m_pZone2 = nil; + + if (navigZone == m_pZone) { + if (defaultZone == m_pZone2 || m_pZone != nil) { + if (navigZone != nil || defaultZone != nil) { + if (m_nAdditionalTimer != 0) + m_nAdditionalTimer--; + } else { + m_nAdditionalTimer = 0; + m_pZone = nil; + m_pZone2 = nil; + } + } else { + m_pZone2 = defaultZone; + m_nAdditionalTimer = 250; + } + } else { + m_pZone = navigZone; + m_nAdditionalTimer = 250; + } + Display(); +} + +void +CPlaceName::Display() +{ + wchar *text; + if (m_pZone != nil) + text = m_pZone->GetTranslatedName(); + else if (m_pZone2 != nil) + text = m_pZone2->GetTranslatedName(); +#ifdef FIX_BUGS + else + text = nil; +#endif + CHud::SetZoneName(text); +} + +CCurrentVehicle::CCurrentVehicle() +{ + Init(); +} + +void +CCurrentVehicle::Init() +{ + m_pCurrentVehicle = nil; +} + +void +CCurrentVehicle::Process() +{ + if (CWorld::Players[CWorld::PlayerInFocus].m_pPed->InVehicle()) + m_pCurrentVehicle = CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pMyVehicle; + else + m_pCurrentVehicle = nil; + Display(); +} + +void +CCurrentVehicle::Display() +{ + wchar *text = nil; + if (m_pCurrentVehicle != nil) + text = TheText.Get(((CVehicleModelInfo*)CModelInfo::GetModelInfo(m_pCurrentVehicle->GetModelIndex()))->m_gameName); + CHud::SetVehicleName(text); +} + +void +CUserDisplay::Init() +{ + PlaceName.Init(); + OnscnTimer.Init(); + Pager.Init(); + CurrentVehicle.Init(); +} + +void +CUserDisplay::Process() +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + PlaceName.Process(); + OnscnTimer.Process(); + Pager.Process(); + CurrentVehicle.Process(); +} diff --git a/src/core/User.h b/src/core/User.h new file mode 100644 index 0000000..153ef57 --- /dev/null +++ b/src/core/User.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Pager.h" +#include "OnscreenTimer.h" + +class CZone; +class CVehicle; + +class CPlaceName +{ + CZone *m_pZone; + CZone *m_pZone2; + int16 m_nAdditionalTimer; +public: + CPlaceName(); + void Init(); + void Process(); + void Display(); +}; + +class CCurrentVehicle +{ + CVehicle *m_pCurrentVehicle; +public: + CCurrentVehicle(); + void Init(); + void Process(); + void Display(); +}; + +class CUserDisplay +{ +public: + static CPlaceName PlaceName; + static COnscreenTimer OnscnTimer; + static CPager Pager; + static CCurrentVehicle CurrentVehicle; + + static void Init(); + static void Process(); +}; diff --git a/src/core/Wanted.cpp b/src/core/Wanted.cpp new file mode 100644 index 0000000..909674d --- /dev/null +++ b/src/core/Wanted.cpp @@ -0,0 +1,492 @@ +#include "common.h" + +#include "Pools.h" +#include "ModelIndices.h" +#include "Timer.h" +#include "World.h" +#include "ZoneCull.h" +#include "Darkel.h" +#include "DMAudio.h" +#include "CopPed.h" +#include "Wanted.h" +#include "General.h" + +int32 CWanted::MaximumWantedLevel = 6; +int32 CWanted::nMaximumWantedLevel = 6400; + +void +CWanted::Initialise() +{ + m_nChaos = 0; + m_nLastUpdateTime = 0; + m_nLastWantedLevelChange = 0; + m_CurrentCops = 0; + m_MaxCops = 0; + m_MaximumLawEnforcerVehicles = 0; + m_RoadblockDensity = 0; + m_bIgnoredByCops = false; + m_bIgnoredByEveryone = false; + m_bSwatRequired = false; + m_bFbiRequired = false; + m_bArmyRequired = false; + m_fCrimeSensitivity = 1.0f; + m_nWantedLevel = 0; + m_CopsBeatingSuspect = 0; + + for (int i = 0; i < ARRAY_SIZE(m_pCops); i++) + m_pCops[i] = nil; + + ClearQdCrimes(); +} + +bool +CWanted::AreSwatRequired() +{ + return m_nWantedLevel == 4 || m_bSwatRequired; +} + +bool +CWanted::AreFbiRequired() +{ + return m_nWantedLevel == 5 || m_bFbiRequired; +} + +bool +CWanted::AreArmyRequired() +{ + return m_nWantedLevel == 6 || m_bArmyRequired; +} + +int32 +CWanted::NumOfHelisRequired() +{ + if (m_bIgnoredByCops || m_bIgnoredByEveryone) + return 0; + + switch (m_nWantedLevel) { + case 3: + case 4: + return 1; + case 5: + case 6: + return 2; + default: + return 0; + } +} + +void +CWanted::SetWantedLevel(int32 level) +{ + if (level > MaximumWantedLevel) + level = MaximumWantedLevel; + + ClearQdCrimes(); + switch (level) { + case 0: + m_nChaos = 0; + break; + case 1: + m_nChaos = 60; + break; + case 2: + m_nChaos = 220; + break; + case 3: + m_nChaos = 420; + break; + case 4: + m_nChaos = 820; + break; + case 5: + m_nChaos = 1620; + break; + case 6: + m_nChaos = 3220; + break; + default: + break; + } + UpdateWantedLevel(); +} + +void +CWanted::SetWantedLevelNoDrop(int32 level) +{ + if (level > m_nWantedLevel) + SetWantedLevel(level); +} + +void +CWanted::SetMaximumWantedLevel(int32 level) +{ + switch(level){ + case 0: + nMaximumWantedLevel = 0; + MaximumWantedLevel = 0; + break; + case 1: + nMaximumWantedLevel = 120; + MaximumWantedLevel = 1; + break; + case 2: + nMaximumWantedLevel = 300; + MaximumWantedLevel = 2; + break; + case 3: + nMaximumWantedLevel = 600; + MaximumWantedLevel = 3; + break; + case 4: + nMaximumWantedLevel = 1200; + MaximumWantedLevel = 4; + break; + case 5: + nMaximumWantedLevel = 2400; + MaximumWantedLevel = 5; + break; + case 6: + nMaximumWantedLevel = 4800; + MaximumWantedLevel = 6; + break; + } +} + +void +CWanted::RegisterCrime(eCrimeType type, const CVector &coors, uint32 id, bool policeDoesntCare) +{ + AddCrimeToQ(type, id, coors, false, policeDoesntCare); +} + +void +CWanted::RegisterCrime_Immediately(eCrimeType type, const CVector &coors, uint32 id, bool policeDoesntCare) +{ +#if defined FIX_SIGNIFICANT_BUGS || defined PEDS_REPORT_CRIMES_ON_PHONE + if (!AddCrimeToQ(type, id, coors, true, policeDoesntCare)) +#else + if (!AddCrimeToQ(type, id, coors, false, policeDoesntCare)) +#endif + ReportCrimeNow(type, coors, policeDoesntCare); +} + +void +CWanted::ClearQdCrimes() +{ + for (int i = 0; i < 16; i++) + m_aCrimes[i].m_nType = CRIME_NONE; +} + +// returns whether the crime had been reported already +bool +CWanted::AddCrimeToQ(eCrimeType type, int32 id, const CVector &coors, bool reported, bool policeDoesntCare) +{ + int i; + + for(i = 0; i < 16; i++) + if(m_aCrimes[i].m_nType == type && m_aCrimes[i].m_nId == id){ + if(m_aCrimes[i].m_bReported) + return true; + if(reported) + m_aCrimes[i].m_bReported = reported; + return false; + } + + for(i = 0; i < 16; i++) + if(m_aCrimes[i].m_nType == CRIME_NONE) + break; + if(i < 16){ + m_aCrimes[i].m_nType = type; + m_aCrimes[i].m_nId = id; + m_aCrimes[i].m_vecPosn = coors; + m_aCrimes[i].m_nTime = CTimer::GetTimeInMilliseconds(); + m_aCrimes[i].m_bReported = reported; + m_aCrimes[i].m_bPoliceDoesntCare = policeDoesntCare; + } + return false; +} + +void +CWanted::ReportCrimeNow(eCrimeType type, const CVector &coors, bool policeDoesntCare) +{ + float sensitivity, chaos; + int wantedLevelDrop; + + if(CDarkel::FrenzyOnGoing()) + sensitivity = m_fCrimeSensitivity*0.3f; + else + sensitivity = m_fCrimeSensitivity; + + wantedLevelDrop = Min(CCullZones::GetWantedLevelDrop(), 100); + + chaos = (1.0f - wantedLevelDrop/100.0f) * sensitivity; + if (policeDoesntCare) + chaos *= 0.333f; + switch(type){ + case CRIME_POSSESSION_GUN: +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + m_nChaos += 5.0f*chaos; +#endif + break; + case CRIME_HIT_PED: + m_nChaos += 5.0f*chaos; + break; + case CRIME_HIT_COP: + m_nChaos += 45.0f*chaos; + break; + case CRIME_SHOOT_PED: + m_nChaos += 30.0f*chaos; + break; + case CRIME_SHOOT_COP: + m_nChaos += 80.0f*chaos; + break; + case CRIME_STEAL_CAR: + m_nChaos += 15.0f*chaos; + break; + case CRIME_RUN_REDLIGHT: + m_nChaos += 10.0f*chaos; + break; + case CRIME_RECKLESS_DRIVING: + m_nChaos += 5.0f*chaos; + break; + case CRIME_SPEEDING: + m_nChaos += 5.0f*chaos; + break; + case CRIME_RUNOVER_PED: + m_nChaos += 18.0f*chaos; + break; + case CRIME_RUNOVER_COP: + m_nChaos += 80.0f*chaos; + break; + case CRIME_SHOOT_HELI: + m_nChaos += 400.0f*chaos; + break; + case CRIME_PED_BURNED: + m_nChaos += 20.0f*chaos; + break; + case CRIME_COP_BURNED: + m_nChaos += 80.0f*chaos; + break; + case CRIME_VEHICLE_BURNED: + m_nChaos += 20.0f*chaos; + break; + case CRIME_DESTROYED_CESSNA: + m_nChaos += 500.0f*chaos; + break; + default: + // Error("Undefined crime type, RegisterCrime, Crime.cpp"); // different file for some reason + Error("Undefined crime type, RegisterCrime, Wanted.cpp"); + } + DMAudio.ReportCrime(type, coors); + UpdateWantedLevel(); +} + +void +CWanted::UpdateWantedLevel() +{ + int32 CurrWantedLevel = m_nWantedLevel; + + if (m_nChaos > nMaximumWantedLevel) + m_nChaos = nMaximumWantedLevel; + + if (m_nChaos >= 0 && m_nChaos < 40) { + m_nWantedLevel = 0; + m_MaximumLawEnforcerVehicles = 0; + m_MaxCops = 0; + m_RoadblockDensity = 0; + } + else if (m_nChaos >= 40 && m_nChaos < 200) { + m_nWantedLevel = 1; + m_MaximumLawEnforcerVehicles = 1; + m_MaxCops = 1; + m_RoadblockDensity = 0; + } + else if (m_nChaos >= 200 && m_nChaos < 400) { + m_nWantedLevel = 2; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 3; + m_RoadblockDensity = 0; + } + else if (m_nChaos >= 400 && m_nChaos < 800) { + m_nWantedLevel = 3; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 4; + m_RoadblockDensity = 4; + } + else if (m_nChaos >= 800 && m_nChaos < 1600) { + m_nWantedLevel = 4; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 6; + m_RoadblockDensity = 8; + } + else if (m_nChaos >= 1600 && m_nChaos < 3200) { + m_nWantedLevel = 5; + m_MaximumLawEnforcerVehicles = 3; + m_MaxCops = 8; + m_RoadblockDensity = 10; + } + else if (m_nChaos >= 3200) { + m_nWantedLevel = 6; + m_MaximumLawEnforcerVehicles = 3; + m_MaxCops = 10; + m_RoadblockDensity = 12; + } + + if (CurrWantedLevel != m_nWantedLevel) + m_nLastWantedLevelChange = CTimer::GetTimeInMilliseconds(); +} + +int32 +CWanted::WorkOutPolicePresence(CVector posn, float radius) +{ + int i; + CPed *ped; + CVehicle *vehicle; + int numPolice = 0; + + i = CPools::GetPedPool()->GetSize(); + while(--i >= 0){ + ped = CPools::GetPedPool()->GetSlot(i); + if(ped && + IsPolicePedModel(ped->GetModelIndex()) && + (posn - ped->GetPosition()).Magnitude() < radius) + numPolice++; + } + + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + vehicle = CPools::GetVehiclePool()->GetSlot(i); + if(vehicle && + vehicle->bIsLawEnforcer && + IsPoliceVehicleModel(vehicle->GetModelIndex()) && + vehicle != FindPlayerVehicle() && + vehicle->GetStatus() != STATUS_ABANDONED && vehicle->GetStatus() != STATUS_WRECKED && + (posn - vehicle->GetPosition()).Magnitude() < radius) + numPolice++; + } + + return numPolice; +} + +void +CWanted::Update(void) +{ + if (CTimer::GetTimeInMilliseconds() - m_nLastUpdateTime > 1000) { + if (m_nWantedLevel > 1) { + m_nLastUpdateTime = CTimer::GetTimeInMilliseconds(); + } else { + float radius = 18.0f; + CVector playerPos = FindPlayerCoors(); + if (WorkOutPolicePresence(playerPos, radius) == 0) { + m_nLastUpdateTime = CTimer::GetTimeInMilliseconds(); + m_nChaos = Max(0, m_nChaos - 1); + UpdateWantedLevel(); + } + } + UpdateCrimesQ(); + bool orderMessedUp = false; + int currCopNum = 0; + bool foundEmptySlot = false; + for (int i = 0; i < ARRAY_SIZE(m_pCops); i++) { + if (m_pCops[i]) { + ++currCopNum; + if (foundEmptySlot) + orderMessedUp = true; + } else { + foundEmptySlot = true; + } + } + if (currCopNum != m_CurrentCops) { + printf("CopPursuit total messed up: re-setting\n"); + m_CurrentCops = currCopNum; + } + if (orderMessedUp) { + printf("CopPursuit pointer list messed up: re-sorting\n"); + bool fixed = true; + for (int i = 0; i < ARRAY_SIZE(m_pCops); i++) { + if (!m_pCops[i]) { + for (int j = i; j < ARRAY_SIZE(m_pCops); j++) { + if (m_pCops[j]) { + m_pCops[i] = m_pCops[j]; + m_pCops[j] = nil; + fixed = false; + break; + } + } + if (fixed) + break; + } + } + } + } +} + +void +CWanted::ResetPolicePursuit(void) +{ + for(int i = 0; i < ARRAY_SIZE(m_pCops); i++) { + CCopPed *cop = m_pCops[i]; + if (!cop) + continue; + + cop->m_bIsInPursuit = false; + cop->m_objective = OBJECTIVE_NONE; + cop->m_prevObjective = OBJECTIVE_NONE; + cop->m_nLastPedState = PED_NONE; + if (!cop->DyingOrDead()) { + cop->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + } + m_pCops[i] = nil; + } + m_CurrentCops = 0; +} + +void +CWanted::Reset(void) +{ + ResetPolicePursuit(); + Initialise(); +} + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +bool +CrimeShouldBeReportedOnPhone(eCrimeType crime) +{ + switch (crime) { + case CRIME_POSSESSION_GUN: + case CRIME_HIT_PED: + case CRIME_HIT_COP: + case CRIME_SHOOT_PED: + case CRIME_SHOOT_COP: + case CRIME_STEAL_CAR: + case CRIME_RECKLESS_DRIVING: + case CRIME_RUNOVER_PED: + case CRIME_RUNOVER_COP: + case CRIME_PED_BURNED: + case CRIME_COP_BURNED: + case CRIME_VEHICLE_BURNED: + return true; + default: + return false; + } +} +#endif + +void +CWanted::UpdateCrimesQ(void) +{ + for(int i = 0; i < ARRAY_SIZE(m_aCrimes); i++) { + + CCrimeBeingQd &crime = m_aCrimes[i]; + if (crime.m_nType != CRIME_NONE) { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (!CrimeShouldBeReportedOnPhone(crime.m_nType)) +#endif + if (CTimer::GetTimeInMilliseconds() > crime.m_nTime + 500 && !crime.m_bReported) { + ReportCrimeNow(crime.m_nType, crime.m_vecPosn, crime.m_bPoliceDoesntCare); + crime.m_bReported = true; + } + if (CTimer::GetTimeInMilliseconds() > crime.m_nTime + 10000) + crime.m_nType = CRIME_NONE; + } + } +} diff --git a/src/core/Wanted.h b/src/core/Wanted.h new file mode 100644 index 0000000..9f08e75 --- /dev/null +++ b/src/core/Wanted.h @@ -0,0 +1,58 @@ +#pragma once + +#include "Crime.h" + +class CEntity; +class CCopPed; + +class CWanted +{ +public: + int32 m_nChaos; + int32 m_nLastUpdateTime; + uint32 m_nLastWantedLevelChange; + float m_fCrimeSensitivity; + uint8 m_CurrentCops; + uint8 m_MaxCops; + uint8 m_MaximumLawEnforcerVehicles; + uint8 m_CopsBeatingSuspect; + int16 m_RoadblockDensity; + uint8 m_bIgnoredByCops : 1; + uint8 m_bIgnoredByEveryone : 1; + uint8 m_bSwatRequired : 1; + uint8 m_bFbiRequired : 1; + uint8 m_bArmyRequired : 1; + int32 m_nWantedLevel; + CCrimeBeingQd m_aCrimes[16]; + CCopPed *m_pCops[10]; + + static int32 MaximumWantedLevel; + static int32 nMaximumWantedLevel; + +public: + void Initialise(); + bool AreSwatRequired(); + bool AreFbiRequired(); + bool AreArmyRequired(); + int32 NumOfHelisRequired(); + void SetWantedLevel(int32); + void SetWantedLevelNoDrop(int32 level); + int32 GetWantedLevel() { return m_nWantedLevel; } + void RegisterCrime(eCrimeType type, const CVector &coors, uint32 id, bool policeDoesntCare); + void RegisterCrime_Immediately(eCrimeType type, const CVector &coors, uint32 id, bool policeDoesntCare); + void ClearQdCrimes(); + bool AddCrimeToQ(eCrimeType type, int32 id, const CVector &pos, bool reported, bool policeDoesntCare); + void ReportCrimeNow(eCrimeType type, const CVector &coors, bool policeDoesntCare); + void UpdateWantedLevel(); + void Reset(); + void ResetPolicePursuit(); + void UpdateCrimesQ(); + void Update(); + + bool IsIgnored(void) { return m_bIgnoredByCops || m_bIgnoredByEveryone; } + + static int32 WorkOutPolicePresence(CVector posn, float radius); + static void SetMaximumWantedLevel(int32 level); +}; + +VALIDATE_SIZE(CWanted, 0x204); diff --git a/src/core/World.cpp b/src/core/World.cpp new file mode 100644 index 0000000..1c34a63 --- /dev/null +++ b/src/core/World.cpp @@ -0,0 +1,2162 @@ +#include "common.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "CopPed.h" +#include "CutsceneMgr.h" +#include "DMAudio.h" +#include "EventList.h" +#include "Explosion.h" +#include "Fire.h" +#include "Garages.h" +#include "Glass.h" +#include "Messages.h" +#include "ModelIndices.h" +#include "ParticleObject.h" +#include "Population.h" +#include "ProjectileInfo.h" +#include "Record.h" +#include "References.h" +#include "Replay.h" +#include "RpAnimBlend.h" +#include "Shadows.h" +#include "TempColModels.h" +#include "WaterLevel.h" +#include "World.h" + + +#define OBJECT_REPOSITION_OFFSET_Z 2.0f + +CColPoint gaTempSphereColPoints[MAX_COLLISION_POINTS]; + +CPtrList CWorld::ms_bigBuildingsList[NUM_LEVELS]; +CPtrList CWorld::ms_listMovingEntityPtrs; +CSector CWorld::ms_aSectors[NUMSECTORS_Y][NUMSECTORS_X]; +uint16 CWorld::ms_nCurrentScanCode; + +uint8 CWorld::PlayerInFocus; +CPlayerInfo CWorld::Players[NUMPLAYERS]; +bool CWorld::bNoMoreCollisionTorque; +CEntity *CWorld::pIgnoreEntity; +bool CWorld::bIncludeDeadPeds; +bool CWorld::bSecondShift; +bool CWorld::bForceProcessControl; +bool CWorld::bProcessCutsceneOnly; + +bool CWorld::bDoingCarCollisions; +bool CWorld::bIncludeCarTyres; + +void +CWorld::Initialise() +{ +#if GTA_VERSION <= GTA3_PS2_160 + CPools::Initialise(); +#endif + pIgnoreEntity = nil; + bDoingCarCollisions = false; + bSecondShift = false; + bNoMoreCollisionTorque = false; + bProcessCutsceneOnly = false; + bIncludeDeadPeds = false; + bForceProcessControl = false; + bIncludeCarTyres = false; +} + +void +CWorld::Add(CEntity *ent) +{ + if(ent->IsVehicle() || ent->IsPed()) DMAudio.SetEntityStatus(((CPhysical *)ent)->m_audioEntityId, TRUE); + + if(ent->bIsBIGBuilding) + ms_bigBuildingsList[ent->m_level].InsertItem(ent); + else + ent->Add(); + + if(ent->IsBuilding() || ent->IsDummy()) return; + + if(!ent->GetIsStatic()) ((CPhysical *)ent)->AddToMovingList(); +} + +void +CWorld::Remove(CEntity *ent) +{ + if(ent->IsVehicle() || ent->IsPed()) DMAudio.SetEntityStatus(((CPhysical *)ent)->m_audioEntityId, FALSE); + + if(ent->bIsBIGBuilding) + ms_bigBuildingsList[ent->m_level].RemoveItem(ent); + else + ent->Remove(); + + if(ent->IsBuilding() || ent->IsDummy()) return; + + if(!ent->GetIsStatic()) ((CPhysical *)ent)->RemoveFromMovingList(); +} + +void +CWorld::ClearScanCodes(void) +{ + CPtrNode *node; + for(int i = 0; i < NUMSECTORS_Y; i++) + for(int j = 0; j < NUMSECTORS_X; j++) { + CSector *s = &ms_aSectors[i][j]; + for(node = s->m_lists[ENTITYLIST_BUILDINGS].first; node; node = node->next) + ((CEntity *)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_VEHICLES].first; node; node = node->next) + ((CEntity *)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_PEDS].first; node; node = node->next) + ((CEntity *)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_OBJECTS].first; node; node = node->next) + ((CEntity *)node->item)->m_scanCode = 0; + for(node = s->m_lists[ENTITYLIST_DUMMIES].first; node; node = node->next) + ((CEntity *)node->item)->m_scanCode = 0; + } +} + +void +CWorld::ClearExcitingStuffFromArea(const CVector &pos, float radius, bool bRemoveProjectilesAndTidyUpShadows) +{ + CPedPool *pedPool = CPools::GetPedPool(); + for(int32 i = 0; i < pedPool->GetSize(); i++) { + CPed *pPed = pedPool->GetSlot(i); + if(pPed && !pPed->IsPlayer() && pPed->CanBeDeleted() && + CVector2D(pPed->GetPosition() - pos).MagnitudeSqr() < SQR(radius)) { + CPopulation::RemovePed(pPed); + } + } + CVehiclePool *VehiclePool = CPools::GetVehiclePool(); + for(int32 i = 0; i < VehiclePool->GetSize(); i++) { + CVehicle *pVehicle = VehiclePool->GetSlot(i); + if(pVehicle && CVector2D(pVehicle->GetPosition() - pos).MagnitudeSqr() < SQR(radius) && + !pVehicle->bIsLocked && pVehicle->CanBeDeleted()) { + if(pVehicle->pDriver) { + CPopulation::RemovePed(pVehicle->pDriver); + pVehicle->pDriver = nil; + } + for(int32 j = 0; j < pVehicle->m_nNumMaxPassengers; ++j) { + if(pVehicle->pPassengers[j]) { + CPopulation::RemovePed(pVehicle->pPassengers[j]); + pVehicle->pPassengers[j] = nil; + --pVehicle->m_nNumPassengers; + } + } + CCarCtrl::RemoveFromInterestingVehicleList(pVehicle); + Remove(pVehicle); + delete pVehicle; + } + } + CObject::DeleteAllTempObjectsInArea(pos, radius); + gFireManager.ExtinguishPoint(pos, radius); + ExtinguishAllCarFiresInArea(pos, radius); + CExplosion::RemoveAllExplosionsInArea(pos, radius); + if(bRemoveProjectilesAndTidyUpShadows) { + CProjectileInfo::RemoveAllProjectiles(); + CShadows::TidyUpShadows(); + } +} + +bool +CWorld::CameraToIgnoreThisObject(CEntity *ent) +{ + if(CGarages::IsModelIndexADoor(ent->GetModelIndex())) return false; + return ((CObject *)ent)->m_bCameraToAvoidThisObject != 1; +} + +bool +CWorld::ProcessLineOfSight(const CVector &point1, const CVector &point2, CColPoint &point, CEntity *&entity, + bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, + bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + int x, xstart, xend; + int y, ystart, yend; + int y1, y2; + float dist; + + AdvanceCurrentScanCode(); + + entity = nil; + dist = 1.0f; + + xstart = GetSectorIndexX(point1.x); + ystart = GetSectorIndexY(point1.y); + xend = GetSectorIndexX(point2.x); + yend = GetSectorIndexY(point2.y); + +#define LOSARGS \ + CColLine(point1, point2), point, dist, entity, checkBuildings, checkVehicles, checkPeds, checkObjects, \ + checkDummies, ignoreSeeThrough, ignoreSomeObjects + + if(xstart == xend && ystart == yend) { + // Only one sector + return ProcessLineOfSightSector(*GetSector(xstart, ystart), LOSARGS); + } else if(xstart == xend) { + // Only step in y + if(ystart < yend) + for(y = ystart; y <= yend; y++) ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + else + for(y = ystart; y >= yend; y--) ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + return dist < 1.0f; + } else if(ystart == yend) { + // Only step in x + if(xstart < xend) + for(x = xstart; x <= xend; x++) ProcessLineOfSightSector(*GetSector(x, ystart), LOSARGS); + else + for(x = xstart; x >= xend; x--) ProcessLineOfSightSector(*GetSector(x, ystart), LOSARGS); + return dist < 1.0f; + } else { + if(point1.x < point2.x) { + // Step from left to right + float m = (point2.y - point1.y) / (point2.x - point1.x); + + y1 = ystart; + y2 = GetSectorIndexY((GetWorldX(xstart + 1) - point1.x) * m + point1.y); + if(y1 < y2) + for(y = y1; y <= y2; y++) ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + else + for(y = y1; y >= y2; y--) ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + + for(x = xstart + 1; x < xend; x++) { + y1 = y2; + y2 = GetSectorIndexY((GetWorldX(x + 1) - point1.x) * m + point1.y); + if(y1 < y2) + for(y = y1; y <= y2; y++) ProcessLineOfSightSector(*GetSector(x, y), LOSARGS); + else + for(y = y1; y >= y2; y--) ProcessLineOfSightSector(*GetSector(x, y), LOSARGS); + } + + y1 = y2; + y2 = yend; + if(y1 < y2) + for(y = y1; y <= y2; y++) ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS); + else + for(y = y1; y >= y2; y--) ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS); + } else { + // Step from right to left + float m = (point2.y - point1.y) / (point2.x - point1.x); + + y1 = ystart; + y2 = GetSectorIndexY((GetWorldX(xstart) - point1.x) * m + point1.y); + if(y1 < y2) + for(y = y1; y <= y2; y++) ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + else + for(y = y1; y >= y2; y--) ProcessLineOfSightSector(*GetSector(xstart, y), LOSARGS); + + for(x = xstart - 1; x > xend; x--) { + y1 = y2; + y2 = GetSectorIndexY((GetWorldX(x) - point1.x) * m + point1.y); + if(y1 < y2) + for(y = y1; y <= y2; y++) ProcessLineOfSightSector(*GetSector(x, y), LOSARGS); + else + for(y = y1; y >= y2; y--) ProcessLineOfSightSector(*GetSector(x, y), LOSARGS); + } + + y1 = y2; + y2 = yend; + if(y1 < y2) + for(y = y1; y <= y2; y++) ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS); + else + for(y = y1; y >= y2; y--) ProcessLineOfSightSector(*GetSector(xend, y), LOSARGS); + } + return dist < 1.0f; + } + +#undef LOSARGS +} + +bool +CWorld::ProcessLineOfSightSector(CSector §or, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, + bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, + bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + float mindist = dist; + bool deadPeds = !!bIncludeDeadPeds; + bIncludeDeadPeds = false; + + if(checkBuildings) { + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_BUILDINGS], line, point, mindist, entity, + ignoreSeeThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough); + } + + if(checkVehicles) { + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_VEHICLES], line, point, mindist, entity, + ignoreSeeThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough); + } + + if(checkPeds) { + if(deadPeds) bIncludeDeadPeds = true; + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_PEDS], line, point, mindist, entity, + ignoreSeeThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough); + bIncludeDeadPeds = false; + } + + if(checkObjects) { + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_OBJECTS], line, point, mindist, entity, + ignoreSeeThrough, ignoreSomeObjects); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, ignoreSomeObjects); + } + + if(checkDummies) { + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_DUMMIES], line, point, mindist, entity, + ignoreSeeThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough); + } + + bIncludeDeadPeds = deadPeds; + + if(mindist < dist) { + dist = mindist; + return true; + } else + return false; +} + +bool +CWorld::ProcessLineOfSightSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, + CEntity *&entity, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + bool deadPeds = false; + float mindist = dist; + CPtrNode *node; + CEntity *e; + CColModel *colmodel; + + if(list.first && bIncludeDeadPeds && ((CEntity *)list.first->item)->IsPed()) deadPeds = true; + + for(node = list.first; node; node = node->next) { + e = (CEntity *)node->item; + if(e->m_scanCode != GetCurrentScanCode() && e != pIgnoreEntity && (e->bUsesCollision || deadPeds) && + !(ignoreSomeObjects && CameraToIgnoreThisObject(e))) { + colmodel = nil; + e->m_scanCode = GetCurrentScanCode(); + + if(e->IsPed()) { + if(e->bUsesCollision || deadPeds && ((CPed *)e)->m_nPedState == PED_DEAD) { +#ifdef PED_SKIN + if(IsClumpSkinned(e->GetClump())) + colmodel = ((CPedModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex()))->AnimatePedColModelSkinned(e->GetClump()); + else +#endif + if(((CPed *)e)->UseGroundColModel()) + colmodel = &CTempColModels::ms_colModelPedGroundHit; + else +#ifdef ANIMATE_PED_COL_MODEL + colmodel = CPedModelInfo::AnimatePedColModel( + ((CPedModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex())) + ->GetHitColModel(), + RpClumpGetFrame(e->GetClump())); +#else + colmodel = + ((CPedModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex())) + ->GetHitColModel(); +#endif + } else + colmodel = nil; + } else if(e->bUsesCollision) + colmodel = CModelInfo::GetColModel(e->GetModelIndex()); + + if(colmodel && CCollision::ProcessLineOfSight(line, e->GetMatrix(), *colmodel, point, mindist, + ignoreSeeThrough)) + entity = e; + } + } + + if(mindist < dist) { + dist = mindist; + return true; + } else + return false; +} + +bool +CWorld::ProcessVerticalLine(const CVector &point1, float z2, CColPoint &point, CEntity *&entity, bool checkBuildings, + bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, + bool ignoreSeeThrough, CStoredCollPoly *poly) +{ + AdvanceCurrentScanCode(); + CVector point2(point1.x, point1.y, z2); + return ProcessVerticalLineSector(*GetSector(GetSectorIndexX(point1.x), GetSectorIndexY(point1.y)), + CColLine(point1, point2), point, entity, checkBuildings, checkVehicles, + checkPeds, checkObjects, checkDummies, ignoreSeeThrough, poly); +} + +bool +CWorld::ProcessVerticalLineSector(CSector §or, const CColLine &line, CColPoint &point, CEntity *&entity, + bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, + bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly) +{ + float mindist = 1.0f; + + if(checkBuildings) { + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_BUILDINGS], line, point, mindist, entity, + ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, point, mindist, + entity, ignoreSeeThrough, poly); + } + + if(checkVehicles) { + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_VEHICLES], line, point, mindist, entity, + ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, poly); + } + + if(checkPeds) { + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_PEDS], line, point, mindist, entity, + ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, poly); + } + + if(checkObjects) { + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_OBJECTS], line, point, mindist, entity, + ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, poly); + } + + if(checkDummies) { + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_DUMMIES], line, point, mindist, entity, + ignoreSeeThrough, poly); + ProcessVerticalLineSectorList(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, poly); + } + + return mindist < 1.0f; +} + +bool +CWorld::ProcessVerticalLineSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, + CEntity *&entity, bool ignoreSeeThrough, CStoredCollPoly *poly) +{ + float mindist = dist; + CPtrNode *node; + CEntity *e; + CColModel *colmodel; + + for(node = list.first; node; node = node->next) { + e = (CEntity *)node->item; + if(e->m_scanCode != GetCurrentScanCode() && e->bUsesCollision) { + e->m_scanCode = GetCurrentScanCode(); + + colmodel = CModelInfo::GetColModel(e->GetModelIndex()); + if(CCollision::ProcessVerticalLine(line, e->GetMatrix(), *colmodel, point, mindist, + ignoreSeeThrough, poly)) + entity = e; + } + } + + if(mindist < dist) { + dist = mindist; + return true; + } else + return false; +} + +bool +CWorld::GetIsLineOfSightClear(const CVector &point1, const CVector &point2, bool checkBuildings, bool checkVehicles, + bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, + bool ignoreSomeObjects) +{ + int x, xstart, xend; + int y, ystart, yend; + int y1, y2; + + AdvanceCurrentScanCode(); + + xstart = GetSectorIndexX(point1.x); + ystart = GetSectorIndexY(point1.y); + xend = GetSectorIndexX(point2.x); + yend = GetSectorIndexY(point2.y); + +#define LOSARGS \ + CColLine(point1, point2), checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, \ + ignoreSeeThrough, ignoreSomeObjects + + if(xstart == xend && ystart == yend) { + // Only one sector + return GetIsLineOfSightSectorClear(*GetSector(xstart, ystart), LOSARGS); + } else if(xstart == xend) { + // Only step in y + if(ystart < yend) { + for(y = ystart; y <= yend; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) return false; + } else { + for(y = ystart; y >= yend; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) return false; + } + } else if(ystart == yend) { + // Only step in x + if(xstart < xend) { + for(x = xstart; x <= xend; x++) + if(!GetIsLineOfSightSectorClear(*GetSector(x, ystart), LOSARGS)) return false; + } else { + for(x = xstart; x >= xend; x--) + if(!GetIsLineOfSightSectorClear(*GetSector(x, ystart), LOSARGS)) return false; + } + } else { + if(point1.x < point2.x) { + // Step from left to right + float m = (point2.y - point1.y) / (point2.x - point1.x); + + y1 = ystart; + y2 = GetSectorIndexY((GetWorldX(xstart + 1) - point1.x) * m + point1.y); + if(y1 < y2) { + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) return false; + } else { + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) return false; + } + + for(x = xstart + 1; x < xend; x++) { + y1 = y2; + y2 = GetSectorIndexY((GetWorldX(x + 1) - point1.x) * m + point1.y); + if(y1 < y2) { + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS)) + return false; + } else { + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS)) + return false; + } + } + + y1 = y2; + y2 = yend; + if(y1 < y2) { + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS)) return false; + } else { + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS)) return false; + } + } else { + // Step from right to left + float m = (point2.y - point1.y) / (point2.x - point1.x); + + y1 = ystart; + y2 = GetSectorIndexY((GetWorldX(xstart) - point1.x) * m + point1.y); + if(y1 < y2) { + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) return false; + } else { + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xstart, y), LOSARGS)) return false; + } + + for(x = xstart - 1; x > xend; x--) { + y1 = y2; + y2 = GetSectorIndexY((GetWorldX(x) - point1.x) * m + point1.y); + if(y1 < y2) { + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS)) + return false; + } else { + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(x, y), LOSARGS)) + return false; + } + } + + y1 = y2; + y2 = yend; + if(y1 < y2) { + for(y = y1; y <= y2; y++) + if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS)) return false; + } else { + for(y = y1; y >= y2; y--) + if(!GetIsLineOfSightSectorClear(*GetSector(xend, y), LOSARGS)) return false; + } + } + } + + return true; + +#undef LOSARGS +} + +bool +CWorld::GetIsLineOfSightSectorClear(CSector §or, const CColLine &line, bool checkBuildings, bool checkVehicles, + bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, + bool ignoreSomeObjects) +{ + if(checkBuildings) { + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_BUILDINGS], line, ignoreSeeThrough)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, + ignoreSeeThrough)) + return false; + } + + if(checkVehicles) { + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_VEHICLES], line, ignoreSeeThrough)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, + ignoreSeeThrough)) + return false; + } + + if(checkPeds) { + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_PEDS], line, ignoreSeeThrough)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, ignoreSeeThrough)) + return false; + } + + if(checkObjects) { + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_OBJECTS], line, ignoreSeeThrough, + ignoreSomeObjects)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, ignoreSeeThrough, + ignoreSomeObjects)) + return false; + } + + if(checkDummies) { + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_DUMMIES], line, ignoreSeeThrough)) + return false; + if(!GetIsLineOfSightSectorListClear(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, ignoreSeeThrough)) + return false; + } + + return true; +} + +bool +CWorld::GetIsLineOfSightSectorListClear(CPtrList &list, const CColLine &line, bool ignoreSeeThrough, + bool ignoreSomeObjects) +{ + CPtrNode *node; + CEntity *e; + CColModel *colmodel; + + for(node = list.first; node; node = node->next) { + e = (CEntity *)node->item; + if(e->m_scanCode != GetCurrentScanCode() && e->bUsesCollision) { + + e->m_scanCode = GetCurrentScanCode(); + + if(e != pIgnoreEntity && !(ignoreSomeObjects && CameraToIgnoreThisObject(e))) { + + colmodel = CModelInfo::GetColModel(e->GetModelIndex()); + + if(CCollision::TestLineOfSight(line, e->GetMatrix(), *colmodel, ignoreSeeThrough)) + return false; + } + } + } + + return true; +} + +void +CWorld::FindObjectsInRangeSectorList(CPtrList &list, Const CVector ¢re, float radius, bool ignoreZ, int16 *numObjects, + int16 lastObject, CEntity **objects) +{ + float radiusSqr = radius * radius; + float objDistSqr; + + for(CPtrNode *node = list.first; node; node = node->next) { + CEntity *object = (CEntity *)node->item; + if(object->m_scanCode != GetCurrentScanCode()) { + object->m_scanCode = GetCurrentScanCode(); + + CVector diff = centre - object->GetPosition(); + if(ignoreZ) + objDistSqr = diff.MagnitudeSqr2D(); + else + objDistSqr = diff.MagnitudeSqr(); + + if(objDistSqr < radiusSqr && *numObjects < lastObject) { + if(objects) { objects[*numObjects] = object; } + (*numObjects)++; + } + } + } +} + +void +CWorld::FindObjectsInRange(Const CVector ¢re, float radius, bool ignoreZ, int16 *numObjects, int16 lastObject, + CEntity **objects, bool checkBuildings, bool checkVehicles, bool checkPeds, + bool checkObjects, bool checkDummies) +{ + int minX = GetSectorIndexX(centre.x - radius); + if(minX <= 0) minX = 0; + + int minY = GetSectorIndexY(centre.y - radius); + if(minY <= 0) minY = 0; + + int maxX = GetSectorIndexX(centre.x + radius); +#ifdef FIX_BUGS + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; +#else + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X; +#endif + + int maxY = GetSectorIndexY(centre.y + radius); +#ifdef FIX_BUGS + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; +#else + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y; +#endif + + AdvanceCurrentScanCode(); + + *numObjects = 0; + for(int curY = minY; curY <= maxY; curY++) { + for(int curX = minX; curX <= maxX; curX++) { + CSector *sector = GetSector(curX, curY); + if(checkBuildings) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_BUILDINGS], centre, radius, + ignoreZ, numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], centre, + radius, ignoreZ, numObjects, lastObject, objects); + } + if(checkVehicles) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_VEHICLES], centre, radius, + ignoreZ, numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], centre, + radius, ignoreZ, numObjects, lastObject, objects); + } + if(checkPeds) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_PEDS], centre, radius, ignoreZ, + numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_PEDS_OVERLAP], centre, radius, + ignoreZ, numObjects, lastObject, objects); + } + if(checkObjects) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_OBJECTS], centre, radius, + ignoreZ, numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], centre, + radius, ignoreZ, numObjects, lastObject, objects); + } + if(checkDummies) { + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_DUMMIES], centre, radius, + ignoreZ, numObjects, lastObject, objects); + FindObjectsInRangeSectorList(sector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], centre, + radius, ignoreZ, numObjects, lastObject, objects); + } + } + } +} + +void +CWorld::FindObjectsOfTypeInRangeSectorList(uint32 modelId, CPtrList &list, const CVector &position, float radius, + bool bCheck2DOnly, int16 *nEntitiesFound, int16 maxEntitiesToFind, + CEntity **aEntities) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + if (modelId == pEntity->GetModelIndex()) { + float fMagnitude = 0.0f; + if(bCheck2DOnly) + fMagnitude = (position - pEntity->GetPosition()).MagnitudeSqr2D(); + else + fMagnitude = (position - pEntity->GetPosition()).MagnitudeSqr(); + if(fMagnitude < radius * radius && *nEntitiesFound < maxEntitiesToFind) { + if(aEntities) aEntities[*nEntitiesFound] = pEntity; + ++*nEntitiesFound; + } + } + } + } +} + +void +CWorld::FindObjectsOfTypeInRange(uint32 modelId, const CVector &position, float radius, bool bCheck2DOnly, + int16 *nEntitiesFound, int16 maxEntitiesToFind, CEntity **aEntities, bool bBuildings, + bool bVehicles, bool bPeds, bool bObjects, bool bDummies) +{ + AdvanceCurrentScanCode(); + *nEntitiesFound = 0; + const CVector2D vecSectorStartPos(position.x - radius, position.y - radius); + const CVector2D vecSectorEndPos(position.x + radius, position.y + radius); + const int32 nStartX = Max(GetSectorIndexX(vecSectorStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecSectorStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bBuildings) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_BUILDINGS], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], position, radius, + bCheck2DOnly, nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bVehicles) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_VEHICLES], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], position, radius, + bCheck2DOnly, nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bPeds) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_PEDS], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bObjects) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_OBJECTS], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], position, radius, + bCheck2DOnly, nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bDummies) { + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_DUMMIES], position, radius, bCheck2DOnly, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsOfTypeInRangeSectorList( + modelId, pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], position, radius, + bCheck2DOnly, nEntitiesFound, maxEntitiesToFind, aEntities); + } + } + } +} + +CEntity * +CWorld::TestSphereAgainstWorld(CVector centre, float radius, CEntity *entityToIgnore, bool checkBuildings, + bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, + bool ignoreSomeObjects) +{ + CEntity *foundE = nil; + + int minX = GetSectorIndexX(centre.x - radius); + if(minX <= 0) minX = 0; + + int minY = GetSectorIndexY(centre.y - radius); + if(minY <= 0) minY = 0; + + int maxX = GetSectorIndexX(centre.x + radius); +#ifdef FIX_BUGS + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; +#else + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X; +#endif + + int maxY = GetSectorIndexY(centre.y + radius); +#ifdef FIX_BUGS + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; +#else + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y; +#endif + + AdvanceCurrentScanCode(); + + for(int curY = minY; curY <= maxY; curY++) { + for(int curX = minX; curX <= maxX; curX++) { + CSector *sector = GetSector(curX, curY); + if(checkBuildings) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_BUILDINGS], centre, + radius, entityToIgnore, false); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], + centre, radius, entityToIgnore, false); + if(foundE) return foundE; + } + if(checkVehicles) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_VEHICLES], centre, + radius, entityToIgnore, false); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], + centre, radius, entityToIgnore, false); + if(foundE) return foundE; + } + if(checkPeds) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_PEDS], centre, radius, + entityToIgnore, false); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_PEDS_OVERLAP], centre, + radius, entityToIgnore, false); + if(foundE) return foundE; + } + if(checkObjects) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_OBJECTS], centre, + radius, entityToIgnore, ignoreSomeObjects); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], + centre, radius, entityToIgnore, ignoreSomeObjects); + if(foundE) return foundE; + } + if(checkDummies) { + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_DUMMIES], centre, + radius, entityToIgnore, false); + if(foundE) return foundE; + + foundE = TestSphereAgainstSectorList(sector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], + centre, radius, entityToIgnore, false); + if(foundE) return foundE; + } + } + } + return foundE; +} + +CEntity * +CWorld::TestSphereAgainstSectorList(CPtrList &list, CVector spherePos, float radius, CEntity *entityToIgnore, + bool ignoreSomeObjects) +{ + static CColModel OurColModel; + + OurColModel.boundingSphere.center.x = 0.0f; + OurColModel.boundingSphere.center.y = 0.0f; + OurColModel.boundingSphere.center.z = 0.0f; + OurColModel.boundingSphere.radius = radius; + OurColModel.boundingBox.min.x = -radius; + OurColModel.boundingBox.min.y = -radius; + OurColModel.boundingBox.min.z = -radius; + OurColModel.boundingBox.max.x = radius; + OurColModel.boundingBox.max.y = radius; + OurColModel.boundingBox.max.z = radius; + OurColModel.numSpheres = 1; + OurColModel.spheres = &OurColModel.boundingSphere; + OurColModel.numLines = 0; + OurColModel.numBoxes = 0; + OurColModel.numTriangles = 0; + OurColModel.ownsCollisionVolumes = false; + + CMatrix sphereMat; + sphereMat.SetTranslate(spherePos); + + for(CPtrNode *node = list.first; node; node = node->next) { + CEntity *e = (CEntity *)node->item; + + if(e->m_scanCode != GetCurrentScanCode()) { + e->m_scanCode = GetCurrentScanCode(); + + if(e != entityToIgnore && e->bUsesCollision && + !(ignoreSomeObjects && CameraToIgnoreThisObject(e))) { +#ifdef FIX_BUGS + CVector diff = spherePos - e->GetBoundCentre(); +#else + CVector diff = spherePos - e->GetPosition(); +#endif + float distance = diff.Magnitude(); + + if(e->GetBoundRadius() + radius > distance) { + CColModel *eCol = CModelInfo::GetColModel(e->GetModelIndex()); + int collidedSpheres = + CCollision::ProcessColModels(sphereMat, OurColModel, e->GetMatrix(), *eCol, + gaTempSphereColPoints, nil, nil); + + if(collidedSpheres != 0 || + (e->IsVehicle() && ((CVehicle *)e)->m_vehType == VEHICLE_TYPE_CAR && e->GetModelIndex() != MI_DODO && + radius + eCol->boundingBox.max.x > distance)) { + return e; + } + } + } + } + } + + return nil; +} + +float +CWorld::FindGroundZForCoord(float x, float y) +{ + CColPoint point; + CEntity *ent; + if(ProcessVerticalLine(CVector(x, y, 1000.0f), -1000.0f, point, ent, true, false, false, false, true, false, + nil)) + return point.point.z; + else + return 20.0f; +} + +float +CWorld::FindGroundZFor3DCoord(float x, float y, float z, bool *found) +{ + CColPoint point; + CEntity *ent; + if(ProcessVerticalLine(CVector(x, y, z), -1000.0f, point, ent, true, false, false, false, false, false, nil)) { + if(found) *found = true; + return point.point.z; + } else { + if(found) *found = false; + return 0.0f; + } +} + +float +CWorld::FindRoofZFor3DCoord(float x, float y, float z, bool *found) +{ + CColPoint point; + CEntity *ent; + if(ProcessVerticalLine(CVector(x, y, z), 1000.0f, point, ent, true, false, false, false, true, false, nil)) { + if(found) *found = true; + return point.point.z; + } else { + if(found == nil) + printf("THERE IS NO MAP BELOW THE FOLLOWING COORS:%f %f %f. (FindGroundZFor3DCoord)\n", x, y, + z); + if(found) *found = false; + return 20.0f; + } +} + +void +CWorld::RemoveReferencesToDeletedObject(CEntity *pDeletedObject) +{ + int32 i = CPools::GetPedPool()->GetSize(); + while(--i >= 0) { + CPed *pPed = CPools::GetPedPool()->GetSlot(i); + if(pPed && pPed != pDeletedObject) { + pPed->RemoveRefsToEntity(pDeletedObject); + if(pPed->m_pCurrentPhysSurface == pDeletedObject) pPed->m_pCurrentPhysSurface = nil; + } + } + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0) { + CVehicle *pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if(pVehicle && pVehicle != pDeletedObject) { + pVehicle->RemoveRefsToEntity(pDeletedObject); + pVehicle->RemoveRefsToVehicle(pDeletedObject); + } + } + i = CPools::GetObjectPool()->GetSize(); + while(--i >= 0) { + CObject *pObject = CPools::GetObjectPool()->GetSlot(i); + if(pObject && pObject != pDeletedObject) { pObject->RemoveRefsToEntity(pDeletedObject); } + } +} + +void +CWorld::FindObjectsKindaColliding(const CVector &position, float radius, bool bCheck2DOnly, int16 *nCollidingEntities, + int16 maxEntitiesToFind, CEntity **aEntities, bool bBuildings, bool bVehicles, + bool bPeds, bool bObjects, bool bDummies) +{ + AdvanceCurrentScanCode(); + *nCollidingEntities = 0; + const CVector2D vecSectorStartPos(position.x - radius, position.y - radius); + const CVector2D vecSectorEndPos(position.x + radius, position.y + radius); + const int32 nStartX = Max(GetSectorIndexX(vecSectorStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecSectorStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bBuildings) { + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + if(bVehicles) { + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + if(bPeds) { + FindObjectsKindaCollidingSectorList(pSector->m_lists[ENTITYLIST_PEDS], position, + radius, bCheck2DOnly, nCollidingEntities, + maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + if(bObjects) { + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + if(bDummies) { + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + FindObjectsKindaCollidingSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], position, radius, bCheck2DOnly, + nCollidingEntities, maxEntitiesToFind, aEntities); + } + } + } +} + +void +CWorld::FindObjectsKindaCollidingSectorList(CPtrList &list, const CVector &position, float radius, bool bCheck2DOnly, + int16 *nCollidingEntities, int16 maxEntitiesToFind, CEntity **aEntities) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + float fMagnitude = 0.0f; + if(bCheck2DOnly) + fMagnitude = (position - pEntity->GetPosition()).Magnitude2D(); + else + fMagnitude = (position - pEntity->GetPosition()).Magnitude(); + if(pEntity->GetBoundRadius() + radius > fMagnitude && *nCollidingEntities < maxEntitiesToFind) { + if(aEntities) aEntities[*nCollidingEntities] = pEntity; + ++*nCollidingEntities; + } + } + } +} + +void +CWorld::FindObjectsIntersectingCube(const CVector &vecStartPos, const CVector &vecEndPos, int16 *nIntersecting, + int16 maxEntitiesToFind, CEntity **aEntities, bool bBuildings, bool bVehicles, + bool bPeds, bool bObjects, bool bDummies) +{ + AdvanceCurrentScanCode(); + *nIntersecting = 0; + const int32 nStartX = Max(GetSectorIndexX(vecStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bBuildings) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_BUILDINGS], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], vecStartPos, vecEndPos, + nIntersecting, maxEntitiesToFind, aEntities); + } + if(bVehicles) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_VEHICLES], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], vecStartPos, vecEndPos, + nIntersecting, maxEntitiesToFind, aEntities); + } + if(bPeds) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_PEDS], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + } + if(bObjects) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_OBJECTS], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + } + if(bDummies) { + FindObjectsIntersectingCubeSectorList(pSector->m_lists[ENTITYLIST_DUMMIES], + vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities); + } + } + } +} + +void +CWorld::FindObjectsIntersectingCubeSectorList(CPtrList &list, const CVector &vecStartPos, const CVector &vecEndPos, + int16 *nIntersecting, int16 maxEntitiesToFind, CEntity **aEntities) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + float fRadius = pEntity->GetBoundRadius(); + const CVector &entityPos = pEntity->GetPosition(); + if(fRadius + entityPos.x >= vecStartPos.x && entityPos.x - fRadius <= vecEndPos.x && + fRadius + entityPos.y >= vecStartPos.y && entityPos.y - fRadius <= vecEndPos.y && + fRadius + entityPos.z >= vecStartPos.z && entityPos.z - fRadius <= vecEndPos.z && + *nIntersecting < maxEntitiesToFind) { + if(aEntities) aEntities[*nIntersecting] = pEntity; + ++*nIntersecting; + } + } + } +} + +void +CWorld::FindObjectsIntersectingAngledCollisionBox(const CColBox &boundingBox, const CMatrix &matrix, + const CVector &position, float fStartX, float fStartY, float fEndX, + float fEndY, int16 *nEntitiesFound, int16 maxEntitiesToFind, + CEntity **aEntities, bool bBuildings, bool bVehicles, bool bPeds, + bool bObjects, bool bDummies) +{ + AdvanceCurrentScanCode(); + *nEntitiesFound = 0; + const int32 nStartX = Max(GetSectorIndexX(fStartX), 0); + const int32 nStartY = Max(GetSectorIndexY(fStartY), 0); + const int32 nEndX = Min(GetSectorIndexX(fEndX), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(fEndY), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bBuildings) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bVehicles) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bPeds) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_PEDS], boundingBox, matrix, position, nEntitiesFound, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bObjects) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS], boundingBox, matrix, position, nEntitiesFound, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + if(bDummies) { + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES], boundingBox, matrix, position, nEntitiesFound, + maxEntitiesToFind, aEntities); + FindObjectsIntersectingAngledCollisionBoxSectorList( + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP], boundingBox, matrix, position, + nEntitiesFound, maxEntitiesToFind, aEntities); + } + } + } +} + +void +CWorld::FindObjectsIntersectingAngledCollisionBoxSectorList(CPtrList &list, const CColBox &boundingBox, + const CMatrix &matrix, const CVector &position, + int16 *nEntitiesFound, int16 maxEntitiesToFind, + CEntity **aEntities) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + CColSphere sphere; + CVector vecDistance = pEntity->GetPosition() - position; + sphere.radius = pEntity->GetBoundRadius(); + sphere.center = Multiply3x3(vecDistance, matrix); + if(CCollision::TestSphereBox(sphere, boundingBox) && *nEntitiesFound < maxEntitiesToFind) { + if(aEntities) aEntities[*nEntitiesFound] = pEntity; + ++*nEntitiesFound; + } + } + } +} + +void +CWorld::FindMissionEntitiesIntersectingCube(const CVector &vecStartPos, const CVector &vecEndPos, int16 *nIntersecting, + int16 maxEntitiesToFind, CEntity **aEntities, bool bVehicles, bool bPeds, + bool bObjects) +{ + AdvanceCurrentScanCode(); + *nIntersecting = 0; + const int32 nStartX = Max(GetSectorIndexX(vecStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + if(bVehicles) { + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, true, false); + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], vecStartPos, vecEndPos, + nIntersecting, maxEntitiesToFind, aEntities, true, false); + } + if(bPeds) { + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_PEDS], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, false, true); + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, false, true); + } + if(bObjects) { + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, false, false); + FindMissionEntitiesIntersectingCubeSectorList( + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], vecStartPos, vecEndPos, nIntersecting, + maxEntitiesToFind, aEntities, false, false); + } + } + } +} + +void +CWorld::FindMissionEntitiesIntersectingCubeSectorList(CPtrList &list, const CVector &vecStartPos, + const CVector &vecEndPos, int16 *nIntersecting, + int16 maxEntitiesToFind, CEntity **aEntities, bool bIsVehicleList, + bool bIsPedList) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + if(pEntity->m_scanCode != GetCurrentScanCode()) { + pEntity->m_scanCode = GetCurrentScanCode(); + bool bIsMissionEntity = false; + if(bIsVehicleList) + bIsMissionEntity = ((CVehicle *)pEntity)->VehicleCreatedBy == MISSION_VEHICLE; + else if(bIsPedList) + bIsMissionEntity = ((CPed *)pEntity)->CharCreatedBy == MISSION_CHAR; + else + bIsMissionEntity = ((CObject *)pEntity)->ObjectCreatedBy == MISSION_OBJECT; + float fRadius = pEntity->GetBoundRadius(); + const CVector &entityPos = pEntity->GetPosition(); + if(bIsMissionEntity && fRadius + entityPos.x >= vecStartPos.x && + entityPos.x - fRadius <= vecEndPos.x && fRadius + entityPos.y >= vecStartPos.y && + entityPos.y - fRadius <= vecEndPos.y && fRadius + entityPos.z >= vecStartPos.z && + entityPos.z - fRadius <= vecEndPos.z && *nIntersecting < maxEntitiesToFind) { + if(aEntities) aEntities[*nIntersecting] = pEntity; + ++*nIntersecting; + } + } + } +} + +void +CWorld::ClearCarsFromArea(float x1, float y1, float z1, float x2, float y2, float z2) +{ + CVehiclePool *pVehiclePool = CPools::GetVehiclePool(); + for(int32 i = 0; i < pVehiclePool->GetSize(); i++) { + CVehicle *pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if(pVehicle) { + const CVector &position = pVehicle->GetPosition(); + if(position.x >= x1 && position.x <= x2 && position.y >= y1 && position.y <= y2 && + position.z >= z1 && position.z <= z2 && !pVehicle->bIsLocked && pVehicle->CanBeDeleted()) { + if(pVehicle->pDriver) { + CPopulation::RemovePed(pVehicle->pDriver); + pVehicle->pDriver = nil; + } + for(int32 j = 0; j < pVehicle->m_nNumMaxPassengers; ++j) { + if(pVehicle->pPassengers[j]) { + CPopulation::RemovePed(pVehicle->pPassengers[j]); + pVehicle->pPassengers[j] = nil; + --pVehicle->m_nNumPassengers; + } + } + CCarCtrl::RemoveFromInterestingVehicleList(pVehicle); + Remove(pVehicle); + delete pVehicle; + } + } + } +} + +void +CWorld::ClearPedsFromArea(float x1, float y1, float z1, float x2, float y2, float z2) +{ + CPedPool *pPedPool = CPools::GetPedPool(); + for(int32 i = 0; i < pPedPool->GetSize(); i++) { + CPed *pPed = CPools::GetPedPool()->GetSlot(i); + if(pPed) { + const CVector &position = pPed->GetPosition(); + if(!pPed->IsPlayer() && pPed->CanBeDeleted() && position.x >= x1 && position.x <= x2 && + position.y >= y1 && position.y <= y2 && position.z >= z1 && position.z <= z2) { + CPopulation::RemovePed(pPed); + } + } + } +} + +void +CWorld::CallOffChaseForArea(float x1, float y1, float x2, float y2) +{ + AdvanceCurrentScanCode(); + float fStartX = x1 - 10.0f; + float fStartY = y1 - 10.0f; + float fEndX = x2 + 10.0f; + float fEndY = y2 + 10.0f; + const int32 nStartX = Max(GetSectorIndexX(fStartX), 0); + const int32 nStartY = Max(GetSectorIndexY(fStartY), 0); + const int32 nEndX = Min(GetSectorIndexX(fEndX), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(fEndY), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + CallOffChaseForAreaSectorListVehicles(pSector->m_lists[ENTITYLIST_VEHICLES], x1, y1, x2, + y2, fStartX, fStartY, fEndX, fEndY); + CallOffChaseForAreaSectorListVehicles(pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], x1, + y1, x2, y2, fStartX, fStartY, fEndX, fEndY); + CallOffChaseForAreaSectorListPeds(pSector->m_lists[ENTITYLIST_PEDS], x1, y1, x2, y2); + CallOffChaseForAreaSectorListPeds(pSector->m_lists[ENTITYLIST_PEDS_OVERLAP], x1, y1, x2, + y2); + } + } +} + +void +CWorld::CallOffChaseForAreaSectorListVehicles(CPtrList &list, float x1, float y1, float x2, float y2, float fStartX, + float fStartY, float fEndX, float fEndY) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CVehicle *pVehicle = (CVehicle *)pNode->item; + if(pVehicle->m_scanCode != GetCurrentScanCode()) { + pVehicle->m_scanCode = GetCurrentScanCode(); + const CVector &vehiclePos = pVehicle->GetPosition(); + uint8 carMission = pVehicle->AutoPilot.m_nCarMission; + if(pVehicle != FindPlayerVehicle() && vehiclePos.x > fStartX && vehiclePos.x < fEndX && + vehiclePos.y > fStartY && vehiclePos.y < fEndY && pVehicle->bIsLawEnforcer && + (carMission == MISSION_RAMPLAYER_FARAWAY || carMission == MISSION_RAMPLAYER_CLOSE || + carMission == MISSION_BLOCKPLAYER_FARAWAY || carMission == MISSION_BLOCKPLAYER_CLOSE)) { + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + CColModel *pColModel = pVehicle->GetColModel(); + bool bInsideSphere = false; + for(int32 i = 0; i < pColModel->numSpheres; i++) { + CVector pos = pVehicle->GetMatrix() * pColModel->spheres[i].center; + float fRadius = pColModel->spheres[i].radius; + if(pos.x + fRadius > x1 && pos.x - fRadius < x2 && pos.y + fRadius > y1 && + pos.y - fRadius < y2) + bInsideSphere = true; + // Maybe break the loop when bInsideSphere is set to true? + } + if(bInsideSphere) { + if(pVehicle->GetPosition().x <= (x1 + x2) * 0.5f) + pVehicle->m_vecMoveSpeed.x = Min(pVehicle->m_vecMoveSpeed.x, 0.0f); + else + pVehicle->m_vecMoveSpeed.x = Max(pVehicle->m_vecMoveSpeed.x, 0.0f); + if(pVehicle->GetPosition().y <= (y1 + y2) * 0.5f) + pVehicle->m_vecMoveSpeed.y = Min(pVehicle->m_vecMoveSpeed.y, 0.0f); + else + pVehicle->m_vecMoveSpeed.y = Max(pVehicle->m_vecMoveSpeed.y, 0.0f); + } + } + } + } +} + +void +CWorld::CallOffChaseForAreaSectorListPeds(CPtrList &list, float x1, float y1, float x2, float y2) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CPed *pPed = (CPed *)pNode->item; + const CVector &pedPos = pPed->GetPosition(); + if(pPed->m_scanCode != GetCurrentScanCode()) { + pPed->m_scanCode = GetCurrentScanCode(); + if(pPed != FindPlayerPed() && pPed->m_leader != FindPlayerPed() && pedPos.x > x1 && + pedPos.x < x2 && pedPos.y > y1 && pedPos.y < y2 && + (pPed->m_pedInObjective == FindPlayerPed() || + pPed->m_carInObjective && pPed->m_carInObjective == FindPlayerVehicle()) && + pPed->m_nPedState != PED_DEAD && pPed->m_nPedState != PED_DIE && + (pPed->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || + pPed->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || + pPed->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS)) { + if(pPed->IsPedInControl()) { + if(pPed->m_nPedType == PEDTYPE_COP) + ((CCopPed *)pPed)->ClearPursuit(); + else + pPed->SetIdle(); + pPed->SetObjective(OBJECTIVE_NONE); + } else { + pPed->m_prevObjective = OBJECTIVE_NONE; + pPed->m_nLastPedState = PED_IDLE; + } + } + } + } +} + +void +CWorld::RemoveEntityInsteadOfProcessingIt(CEntity *ent) +{ + if(ent->IsPed()) { + if(FindPlayerPed() == ent) + Remove(ent); + else + CPopulation::RemovePed((CPed *)ent); + } else { + Remove(ent); + delete ent; + } +} + +void +CWorld::RemoveFallenPeds(void) +{ + int poolSize = CPools::GetPedPool()->GetSize(); + for(int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + CPed *ped = CPools::GetPedPool()->GetSlot(poolIndex); + if(ped) { + if(ped->GetPosition().z < MAP_Z_LOW_LIMIT) { + if(ped->CharCreatedBy != RANDOM_CHAR || ped->IsPlayer()) { + int closestNode = ThePaths.FindNodeClosestToCoors(ped->GetPosition(), PATH_PED, + 999999.9f, false, false); + CVector newPos = ThePaths.m_pathNodes[closestNode].GetPosition(); + newPos.z += 2.0f; + ped->Teleport(newPos); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } else { + CPopulation::RemovePed(ped); + } + } + } + } +} + +void +CWorld::RemoveFallenCars(void) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh) { + if(veh->GetPosition().z < MAP_Z_LOW_LIMIT) { + if(veh->VehicleCreatedBy == MISSION_VEHICLE || veh == FindPlayerVehicle() || + (veh->pDriver && veh->pDriver->IsPlayer())) { + int closestNode = ThePaths.FindNodeClosestToCoors(veh->GetPosition(), PATH_CAR, + 999999.9f, false, false); + CVector newPos = ThePaths.m_pathNodes[closestNode].GetPosition(); + newPos.z += 3.0f; + veh->Teleport(newPos); + veh->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } else if(veh->VehicleCreatedBy == RANDOM_VEHICLE || + veh->VehicleCreatedBy == PARKED_VEHICLE) { + Remove(veh); + delete veh; + } + } + } + } +} + +void +CWorld::StopAllLawEnforcersInTheirTracks(void) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh) { + if(veh->bIsLawEnforcer) veh->SetMoveSpeed(0.0f, 0.0f, 0.0f); + } + } +} + +void +CWorld::SetAllCarsCanBeDamaged(bool toggle) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = 0; poolIndex < poolSize; poolIndex++) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh) veh->bCanBeDamaged = toggle; + } +} + +void +CWorld::ExtinguishAllCarFiresInArea(CVector point, float range) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = 0; poolIndex < poolSize; poolIndex++) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh) { + if((point - veh->GetPosition()).MagnitudeSqr() < sq(range)) veh->ExtinguishCarFire(); + } + } +} + +inline void +AddSteamsFromGround(CPtrList& list) +{ + CPtrNode *pNode = list.first; + while (pNode) { + ((CEntity*)pNode->item)->AddSteamsFromGround(nil); + pNode = pNode->next; + } +} + +void +CWorld::AddParticles(void) +{ + for(int32 y = 0; y < NUMSECTORS_Y; y++) { + for(int32 x = 0; x < NUMSECTORS_X; x++) { + CSector *pSector = GetSector(x, y); + AddSteamsFromGround(pSector->m_lists[ENTITYLIST_BUILDINGS]); + AddSteamsFromGround(pSector->m_lists[ENTITYLIST_DUMMIES]); + } + } +} + +void +CWorld::ShutDown(void) +{ + for(int i = 0; i < NUMSECTORS_X * NUMSECTORS_Y; i++) { + CSector *pSector = GetSector(i % NUMSECTORS_X, i / NUMSECTORS_Y); + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_BUILDINGS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_VEHICLES].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_PEDS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_OBJECTS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_DUMMIES].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } +#ifndef FIX_BUGS + pSector->m_lists[ENTITYLIST_BUILDINGS].Flush(); + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].Flush(); +#endif + } + for(int32 i = 0; i < NUM_LEVELS; i++) { + for(CPtrNode *pNode = ms_bigBuildingsList[i].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + // Maybe remove from world here? + delete pEntity; + } + ms_bigBuildingsList[i].Flush(); + } + for(int i = 0; i < NUMSECTORS_X * NUMSECTORS_Y; i++) { + CSector *pSector = GetSector(i % NUMSECTORS_X, i / NUMSECTORS_Y); +#ifdef FIX_BUGS + pSector->m_lists[ENTITYLIST_BUILDINGS].Flush(); + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].Flush(); +#endif + if(pSector->m_lists[ENTITYLIST_BUILDINGS].first) { + sprintf(gString, "Building list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_BUILDINGS].Flush(); + } + if(pSector->m_lists[ENTITYLIST_DUMMIES].first) { + sprintf(gString, "Dummy list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_DUMMIES].Flush(); + } + if(pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].first) { + sprintf(gString, "Building overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].Flush(); + } + if(pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP].first) { + sprintf(gString, "Vehicle overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_VEHICLES_OVERLAP].Flush(); + } + if(pSector->m_lists[ENTITYLIST_PEDS_OVERLAP].first) { + sprintf(gString, "Ped overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_PEDS_OVERLAP].Flush(); + } + if(pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP].first) { + sprintf(gString, "Object overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_OBJECTS_OVERLAP].Flush(); + } + if(pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].first) { + sprintf(gString, "Dummy overlap list %d,%d not empty\n", i % NUMSECTORS_X, i / NUMSECTORS_Y); + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].Flush(); + } + } + ms_listMovingEntityPtrs.Flush(); +#if GTA_VERSION <= GTA3_PS2_160 + CPools::Shutdown(); +#endif +} + +void +CWorld::ClearForRestart(void) +{ + if(CCutsceneMgr::HasLoaded()) CCutsceneMgr::DeleteCutsceneData(); + CProjectileInfo::RemoveAllProjectiles(); + CObject::DeleteAllTempObjects(); + CObject::DeleteAllMissionObjects(); + CPopulation::ConvertAllObjectsToDummyObjects(); + for(int i = 0; i < NUMSECTORS_X * NUMSECTORS_Y; i++) { + CSector *pSector = GetSector(i % NUMSECTORS_X, i / NUMSECTORS_Y); + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_PEDS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = GetBigBuildingList(LEVEL_GENERIC).first; pNode; pNode = pNode->next) { + CVehicle *pVehicle = (CVehicle *)pNode->item; + if(pVehicle && pVehicle->IsVehicle() && pVehicle->IsPlane()) { + Remove(pVehicle); + delete pVehicle; + } + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_VEHICLES].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + } + CPools::CheckPoolsEmpty(); +} + +void +CWorld::RepositionCertainDynamicObjects() +{ + int32 i = CPools::GetDummyPool()->GetSize(); + while(--i >= 0) { + CDummy *dummy = CPools::GetDummyPool()->GetSlot(i); + if(dummy) { RepositionOneObject(dummy); } + } +} + +void +CWorld::RepositionOneObject(CEntity *pEntity) +{ + int16 modelId = pEntity->GetModelIndex(); + if (IsStreetLight(modelId) || IsTreeModel(modelId) || modelId == MI_PARKINGMETER || + modelId == MI_PHONEBOOTH1 || modelId == MI_WASTEBIN || modelId == MI_BIN || modelId == MI_POSTBOX1 || + modelId == MI_NEWSSTAND || modelId == MI_TRAFFICCONE || modelId == MI_DUMP1 || + modelId == MI_ROADWORKBARRIER1 || modelId == MI_BUSSIGN1 || modelId == MI_NOPARKINGSIGN1 || + modelId == MI_PHONESIGN || modelId == MI_TAXISIGN || modelId == MI_FISHSTALL01 || + modelId == MI_FISHSTALL02 || modelId == MI_FISHSTALL03 || modelId == MI_FISHSTALL04 || + modelId == MI_BAGELSTAND2 || modelId == MI_FIRE_HYDRANT || modelId == MI_BOLLARDLIGHT || + modelId == MI_PARKTABLE) { + CVector &position = pEntity->GetMatrix().GetPosition(); + float fBoundingBoxMinZ = pEntity->GetColModel()->boundingBox.min.z; + position.z = FindGroundZFor3DCoord(position.x, position.y, + position.z + OBJECT_REPOSITION_OFFSET_Z, nil) - + fBoundingBoxMinZ; + pEntity->GetMatrix().UpdateRW(); + pEntity->UpdateRwFrame(); + } else if(modelId == MI_BUOY) { + float fWaterLevel = 0.0f; + bool bFound = true; + const CVector &position = pEntity->GetPosition(); + float fGroundZ = FindGroundZFor3DCoord(position.x, position.y, + position.z + OBJECT_REPOSITION_OFFSET_Z, &bFound); + if(CWaterLevel::GetWaterLevelNoWaves(position.x, position.y, position.z + OBJECT_REPOSITION_OFFSET_Z, + &fWaterLevel)) { + if(!bFound || fWaterLevel > fGroundZ) { + CColModel *pColModel = pEntity->GetColModel(); + float fHeight = pColModel->boundingBox.max.z - pColModel->boundingBox.min.z; + pEntity->GetMatrix().GetPosition().z = 0.2f * fHeight + fWaterLevel - 0.5f * fHeight; + } + } + } +} + +void +CWorld::SetCarsOnFire(float x, float y, float z, float radius, CEntity *reason) +{ + int poolSize = CPools::GetVehiclePool()->GetSize(); + for(int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + CVehicle *veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if(veh && veh->GetStatus() != STATUS_WRECKED && !veh->m_pCarFire && !veh->bFireProof) { + if(Abs(veh->GetPosition().z - z) < 5.0f && Abs(veh->GetPosition().x - x) < radius && + Abs(veh->GetPosition().y - y) < radius) + gFireManager.StartFire(veh, reason, 0.8f, true); + } + } +} + +void +CWorld::SetPedsOnFire(float x, float y, float z, float radius, CEntity *reason) +{ + int32 poolSize = CPools::GetPedPool()->GetSize(); + for(int32 i = poolSize - 1; i >= 0; i--) { + CPed *pPed = CPools::GetPedPool()->GetSlot(i); + if(pPed && pPed->m_nPedState != PED_DEAD && !pPed->bInVehicle && !pPed->m_pFire && !pPed->bFireProof) { + if(Abs(pPed->GetPosition().z - z) < 5.0f && Abs(pPed->GetPosition().x - x) < radius && + Abs(pPed->GetPosition().y - y) < radius) + gFireManager.StartFire(pPed, reason, 0.8f, true); + } + } +} + +void +CWorld::RemoveStaticObjects() +{ + for(int i = 0; i < NUMSECTORS_X * NUMSECTORS_Y; i++) { + CSector *pSector = GetSector(i % NUMSECTORS_X, i / NUMSECTORS_Y); + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_BUILDINGS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_OBJECTS].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + for(CPtrNode *pNode = pSector->m_lists[ENTITYLIST_DUMMIES].first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + Remove(pEntity); + delete pEntity; + } + pSector->m_lists[ENTITYLIST_BUILDINGS].Flush(); + pSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES].Flush(); + pSector->m_lists[ENTITYLIST_DUMMIES_OVERLAP].Flush(); + } +} + +void +CWorld::Process(void) +{ + if(!(CTimer::GetFrameCounter() & 63)) CReferences::PruneAllReferencesInWorld(); + + if(bProcessCutsceneOnly) { + for(int i = 0; i < NUMCUTSCENEOBJECTS; i++) { + CCutsceneObject *csObj = CCutsceneMgr::GetCutsceneObject(i); + if(csObj && csObj->m_entryInfoList.first) { + if(csObj->m_rwObject && RwObjectGetType(csObj->m_rwObject) == rpCLUMP && + RpAnimBlendClumpGetFirstAssociation(csObj->GetClump())) { + RpAnimBlendClumpUpdateAnimations(csObj->GetClump(), + csObj->IsObject() + ? CTimer::GetTimeStepNonClippedInSeconds() + : CTimer::GetTimeStepInSeconds()); + } + csObj->ProcessControl(); + csObj->ProcessCollision(); + csObj->GetMatrix().UpdateRW(); + csObj->UpdateRwFrame(); + } + } + CRecordDataForChase::ProcessControlCars(); + CRecordDataForChase::SaveOrRetrieveCarPositions(); + } else { + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; +#ifdef FIX_BUGS // from VC + if(!movingEnt->bRemoveFromWorld && movingEnt->m_rwObject && RwObjectGetType(movingEnt->m_rwObject) == rpCLUMP && +#else + if(movingEnt->m_rwObject && RwObjectGetType(movingEnt->m_rwObject) == rpCLUMP && +#endif + RpAnimBlendClumpGetFirstAssociation(movingEnt->GetClump())) { + RpAnimBlendClumpUpdateAnimations(movingEnt->GetClump(), + movingEnt->IsObject() + ? CTimer::GetTimeStepNonClippedInSeconds() + : CTimer::GetTimeStepInSeconds()); + } + } + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CPhysical *movingEnt = (CPhysical *)node->item; + if(movingEnt->bRemoveFromWorld) { + RemoveEntityInsteadOfProcessingIt(movingEnt); + } else { + movingEnt->ProcessControl(); + if(movingEnt->GetIsStatic()) { movingEnt->RemoveFromMovingList(); } + } + } + bForceProcessControl = true; + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CPhysical *movingEnt = (CPhysical *)node->item; + if(movingEnt->bWasPostponed) { + if(movingEnt->bRemoveFromWorld) { + RemoveEntityInsteadOfProcessingIt(movingEnt); + } else { + movingEnt->ProcessControl(); + if(movingEnt->GetIsStatic()) { movingEnt->RemoveFromMovingList(); } + } + } + } + bForceProcessControl = false; + if(CReplay::IsPlayingBack()) { + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + movingEnt->bIsInSafePosition = true; + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + } + } else { + bNoMoreCollisionTorque = false; + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->ProcessCollision(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + } + } + bNoMoreCollisionTorque = true; + for(int i = 0; i < 4; i++) { + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->ProcessCollision(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + } + } + } + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->bIsStuck = true; + movingEnt->ProcessCollision(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + if(!movingEnt->bIsInSafePosition) { movingEnt->bIsStuck = true; } + } + } + bSecondShift = false; + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CEntity *movingEnt = (CEntity *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->ProcessShift(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + if(!movingEnt->bIsInSafePosition) { movingEnt->bIsStuck = true; } + } + } + bSecondShift = true; + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CPhysical *movingEnt = (CPhysical *)node->item; + if(!movingEnt->bIsInSafePosition) { + movingEnt->ProcessShift(); + movingEnt->GetMatrix().UpdateRW(); + movingEnt->UpdateRwFrame(); + if(!movingEnt->bIsInSafePosition) { + movingEnt->bIsStuck = true; + if(movingEnt->GetStatus() == STATUS_PLAYER) { + printf("STUCK: Final Step: Player Entity %d Is Stuck\n", movingEnt->GetModelIndex()); + movingEnt->m_vecMoveSpeed *= 0.3f; + movingEnt->ApplyMoveSpeed(); + movingEnt->ApplyTurnSpeed(); + } + } + } + } + } + for(CPtrNode *node = ms_listMovingEntityPtrs.first; node; node = node->next) { + CPed *movingPed = (CPed *)node->item; + if(movingPed->IsPed()) { + if(movingPed->bInVehicle && movingPed->m_nPedState != PED_EXIT_TRAIN || + movingPed->EnteringCar()) { + CVehicle *movingCar = movingPed->m_pMyVehicle; + if(movingCar) { + if(movingCar->IsTrain()) { + movingPed->SetPedPositionInTrain(); + } else { + switch(movingPed->m_nPedState) { + case PED_ENTER_CAR: + case PED_CARJACK: movingPed->EnterCar(); break; + case PED_DRAG_FROM_CAR: movingPed->BeingDraggedFromCar(); break; + case PED_EXIT_CAR: movingPed->ExitCar(); break; + case PED_ARRESTED: + if(movingPed->m_nLastPedState == PED_DRAG_FROM_CAR) { + movingPed->BeingDraggedFromCar(); + break; + } + // fall through + default: movingPed->SetPedPositionInCar(); break; + } + } + movingPed->GetMatrix().UpdateRW(); + movingPed->UpdateRwFrame(); + } else { + movingPed->bInVehicle = false; + movingPed->QuitEnteringCar(); + } + } + } + } + CMessages::Process(); + Players[PlayerInFocus].Process(); + CRecordDataForChase::SaveOrRetrieveCarPositions(); + if((CTimer::GetFrameCounter() & 7) == 1) { + RemoveFallenPeds(); + } else if((CTimer::GetFrameCounter() & 7) == 5) { + RemoveFallenCars(); + } + } +} + +void +CWorld::TriggerExplosion(const CVector &position, float fRadius, float fPower, CEntity *pCreator, + bool bProcessVehicleBombTimer) +{ + CVector2D vecStartPos(position.x - fRadius, position.y - fRadius); + CVector2D vecEndPos(position.x + fRadius, position.y + fRadius); + const int32 nStartX = Max(GetSectorIndexX(vecStartPos.x), 0); + const int32 nStartY = Max(GetSectorIndexY(vecStartPos.y), 0); + const int32 nEndX = Min(GetSectorIndexX(vecEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecEndPos.y), NUMSECTORS_Y - 1); + for(int32 y = nStartY; y <= nEndY; y++) { + for(int32 x = nStartX; x <= nEndX; x++) { + CSector *pSector = GetSector(x, y); + TriggerExplosionSectorList(pSector->m_lists[ENTITYLIST_VEHICLES], position, fRadius, + fPower, pCreator, bProcessVehicleBombTimer); + TriggerExplosionSectorList(pSector->m_lists[ENTITYLIST_PEDS], position, fRadius, fPower, + pCreator, bProcessVehicleBombTimer); + TriggerExplosionSectorList(pSector->m_lists[ENTITYLIST_OBJECTS], position, fRadius, + fPower, pCreator, bProcessVehicleBombTimer); + } + } +} + +void +CWorld::TriggerExplosionSectorList(CPtrList &list, const CVector &position, float fRadius, float fPower, + CEntity *pCreator, bool bProcessVehicleBombTimer) +{ + for(CPtrNode *pNode = list.first; pNode; pNode = pNode->next) { + CPhysical *pEntity = (CPhysical *)pNode->item; + CVector vecDistance = pEntity->GetPosition() - position; + float fMagnitude = vecDistance.Magnitude(); + if(fRadius > fMagnitude) { + CWeapon::BlowUpExplosiveThings(pEntity); + CPed *pPed = (CPed *)pEntity; + CObject *pObject = (CObject *)pEntity; + CVehicle *pVehicle = (CVehicle *)pEntity; + if(!pEntity->bExplosionProof && (!pEntity->IsPed() || !pPed->bInVehicle)) { + if(pEntity->GetIsStatic()) { + if(pEntity->IsObject()) { + if (fPower > pObject->m_fUprootLimit || IsFence(pObject->GetModelIndex())) { + if (IsGlass(pObject->GetModelIndex())) { + CGlass::WindowRespondsToExplosion(pObject, position); + } else { + pObject->SetIsStatic(false); + pObject->AddToMovingList(); + int16 modelId = pEntity->GetModelIndex(); + if(modelId != MI_FIRE_HYDRANT || + pObject->bHasBeenDamaged) { + if(pEntity->IsObject() && + modelId != MI_EXPLODINGBARREL && + modelId != MI_PETROLPUMP) + pObject->bHasBeenDamaged = true; + } else { + CVector pos = pEntity->GetPosition(); + pos.z -= 0.5f; + CParticleObject::AddObject(POBJECT_FIRE_HYDRANT, + pos, true); + pObject->bHasBeenDamaged = true; + } + } + } + if(pEntity->GetIsStatic()) { + float fDamageMultiplier = + (fRadius - fMagnitude) * 2.0f / fRadius; + float fDamage = 300.0f * Min(fDamageMultiplier, 1.0f); + pObject->ObjectDamage(fDamage); + } + } else { + pEntity->SetIsStatic(false); + pEntity->AddToMovingList(); + } + } + if(!pEntity->GetIsStatic()) { + float fDamageMultiplier = Min((fRadius - fMagnitude) * 2.0f / fRadius, 1.0f); + CVector vecForceDir = + vecDistance * (fPower * pEntity->m_fMass / 1400.0f * fDamageMultiplier / + Max(fMagnitude, 0.01f)); + vecForceDir.z = Max(vecForceDir.z, 0.0f); + if(pEntity == FindPlayerPed()) vecForceDir.z = Min(vecForceDir.z, 1.0f); + pEntity->ApplyMoveForce(vecForceDir); + if(!pEntity->bPedPhysics) { + float fBoundRadius = pEntity->GetBoundRadius(); + float fDistanceZ = position.z - pEntity->GetPosition().z; + float fPointZ = fBoundRadius; + if(Max(fDistanceZ, -fBoundRadius) < fBoundRadius) { + if(fDistanceZ <= -fBoundRadius) + fPointZ = -fBoundRadius; + else + fPointZ = fDistanceZ; + } + pEntity->ApplyTurnForce(vecForceDir.x, vecForceDir.y, vecForceDir.z, + 0.0f, 0.0f, fPointZ); + } + switch(pEntity->GetType()) { + case ENTITY_TYPE_VEHICLE: + if(pEntity->GetStatus() == STATUS_SIMPLE) { + pEntity->SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(pVehicle); + } + pVehicle->InflictDamage(pCreator, WEAPONTYPE_EXPLOSION, + 1100.0f * fDamageMultiplier); + if(bProcessVehicleBombTimer) { + if(pVehicle->m_nBombTimer) pVehicle->m_nBombTimer /= 10; + } + break; + case ENTITY_TYPE_PED: { + int8 direction = pPed->GetLocalDirection(-vecForceDir); + pPed->bIsStanding = false; + pPed->ApplyMoveForce(0.0, 0.0, 2.0f); + float fDamage = 250.0f * fDamageMultiplier; + pPed->InflictDamage(pCreator, WEAPONTYPE_EXPLOSION, fDamage, + PEDPIECE_TORSO, direction); + if(pPed->m_nPedState != PED_DIE) + pPed->SetFall(2000, + (AnimationId)(direction + ANIM_STD_HIGHIMPACT_FRONT), 0); + if(pCreator && pCreator->IsPed()) { + eEventType eventType = EVENT_SHOOT_PED; + if(pPed->m_nPedType == PEDTYPE_COP) eventType = EVENT_SHOOT_COP; + CEventList::RegisterEvent(eventType, EVENT_ENTITY_PED, pEntity, + (CPed *)pCreator, 10000); + pPed->RegisterThreatWithGangPeds(pCreator); + } + break; + } + case ENTITY_TYPE_OBJECT: + pObject->ObjectDamage(300.0f * fDamageMultiplier); + break; + default: break; + } + } + } + } + } +} + +void +CWorld::UseDetonator(CEntity *pEntity) +{ + int32 i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0) { + CAutomobile *pVehicle = (CAutomobile *)CPools::GetVehiclePool()->GetSlot(i); + if(pVehicle && !pVehicle->m_vehType && pVehicle->m_bombType == CARBOMB_REMOTE && + pVehicle->m_pBombRigger == pEntity) { + pVehicle->m_bombType = CARBOMB_NONE; + pVehicle->m_nBombTimer = 500; + pVehicle->m_pBlowUpEntity = pVehicle->m_pBombRigger; + if(pVehicle->m_pBlowUpEntity) + pVehicle->m_pBlowUpEntity->RegisterReference(&pVehicle->m_pBlowUpEntity); + } + } +} diff --git a/src/core/World.h b/src/core/World.h new file mode 100644 index 0000000..3d55375 --- /dev/null +++ b/src/core/World.h @@ -0,0 +1,155 @@ +#pragma once + +#include "Game.h" +#include "Lists.h" +#include "PlayerInfo.h" +#include "Collision.h" + +/* Sectors span from -2000 to 2000 in x and y. + * With 100x100 sectors, each is 40x40 units. */ + +#define SECTOR_SIZE_X (40.0f) +#define SECTOR_SIZE_Y (40.0f) + +#define NUMSECTORS_X (100) +#define NUMSECTORS_Y (100) + +#define WORLD_SIZE_X (NUMSECTORS_X * SECTOR_SIZE_X) +#define WORLD_SIZE_Y (NUMSECTORS_Y * SECTOR_SIZE_Y) + +#define WORLD_MIN_X (-2000.0f) +#define WORLD_MIN_Y (-2000.0f) + +#define WORLD_MAX_X (WORLD_MIN_X + WORLD_SIZE_X) +#define WORLD_MAX_Y (WORLD_MIN_Y + WORLD_SIZE_Y) + +#define MAP_Z_LOW_LIMIT -100.0f + +enum +{ + ENTITYLIST_BUILDINGS, + ENTITYLIST_BUILDINGS_OVERLAP, + ENTITYLIST_OBJECTS, + ENTITYLIST_OBJECTS_OVERLAP, + ENTITYLIST_VEHICLES, + ENTITYLIST_VEHICLES_OVERLAP, + ENTITYLIST_PEDS, + ENTITYLIST_PEDS_OVERLAP, + ENTITYLIST_DUMMIES, + ENTITYLIST_DUMMIES_OVERLAP, + + NUMSECTORENTITYLISTS +}; + +class CSector +{ +public: + CPtrList m_lists[NUMSECTORENTITYLISTS]; +}; + +VALIDATE_SIZE(CSector, 0x28); + +class CWorld +{ + static CPtrList ms_bigBuildingsList[NUM_LEVELS]; + static CPtrList ms_listMovingEntityPtrs; + static CSector ms_aSectors[NUMSECTORS_Y][NUMSECTORS_X]; + static uint16 ms_nCurrentScanCode; + +public: + static uint8 PlayerInFocus; + static CPlayerInfo Players[NUMPLAYERS]; + static CEntity *pIgnoreEntity; + static bool bIncludeDeadPeds; + static bool bNoMoreCollisionTorque; + static bool bSecondShift; + static bool bForceProcessControl; + static bool bProcessCutsceneOnly; + static bool bDoingCarCollisions; + static bool bIncludeCarTyres; + + static void Remove(CEntity *entity); + static void Add(CEntity *entity); + + static CSector *GetSector(int x, int y) { return &ms_aSectors[y][x]; } + static CPtrList &GetBigBuildingList(eLevelName i) { return ms_bigBuildingsList[i]; } + static CPtrList &GetMovingEntityList(void) { return ms_listMovingEntityPtrs; } + static uint16 GetCurrentScanCode(void) { return ms_nCurrentScanCode; } + static void AdvanceCurrentScanCode(void){ + if(++CWorld::ms_nCurrentScanCode == 0){ + CWorld::ClearScanCodes(); + CWorld::ms_nCurrentScanCode = 1; + } + } + static void ClearScanCodes(void); + static void ClearExcitingStuffFromArea(const CVector &pos, float radius, bool bRemoveProjectilesAndTidyUpShadows); + + static bool CameraToIgnoreThisObject(CEntity *ent); + + static bool ProcessLineOfSight(const CVector &point1, const CVector &point2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool ProcessLineOfSightSector(CSector §or, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool ProcessLineOfSightSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool ProcessVerticalLine(const CVector &point1, float z2, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly); + static bool ProcessVerticalLineSector(CSector §or, const CColLine &line, CColPoint &point, CEntity *&entity, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, CStoredCollPoly *poly); + static bool ProcessVerticalLineSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, CStoredCollPoly *poly); + static bool GetIsLineOfSightClear(const CVector &point1, const CVector &point2, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool GetIsLineOfSightSectorClear(CSector §or, const CColLine &line, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + static bool GetIsLineOfSightSectorListClear(CPtrList &list, const CColLine &line, bool ignoreSeeThrough, bool ignoreSomeObjects = false); + + static CEntity *TestSphereAgainstWorld(CVector centre, float radius, CEntity *entityToIgnore, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSomeObjects); + static CEntity *TestSphereAgainstSectorList(CPtrList&, CVector, float, CEntity*, bool); + static void FindObjectsInRangeSectorList(CPtrList &list, Const CVector ¢re, float radius, bool ignoreZ, int16 *numObjects, int16 lastObject, CEntity **objects); + static void FindObjectsInRange(Const CVector ¢re, float radius, bool ignoreZ, int16 *numObjects, int16 lastObject, CEntity **objects, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies); + static void FindObjectsOfTypeInRangeSectorList(uint32 modelId, CPtrList& list, const CVector& position, float radius, bool bCheck2DOnly, int16* nEntitiesFound, int16 maxEntitiesToFind, CEntity** aEntities); + static void FindObjectsOfTypeInRange(uint32 modelId, const CVector& position, float radius, bool bCheck2DOnly, int16* nEntitiesFound, int16 maxEntitiesToFind, CEntity** aEntities, bool bBuildings, bool bVehicles, bool bPeds, bool bObjects, bool bDummies); + static float FindGroundZForCoord(float x, float y); + static float FindGroundZFor3DCoord(float x, float y, float z, bool *found); + static float FindRoofZFor3DCoord(float x, float y, float z, bool *found); + static void RemoveReferencesToDeletedObject(CEntity*); + static void FindObjectsKindaColliding(const CVector& position, float radius, bool bCheck2DOnly, int16* nCollidingEntities, int16 maxEntitiesToFind, CEntity** aEntities, bool bBuildings, bool bVehicles, bool bPeds, bool bObjects, bool bDummies); + static void FindObjectsKindaCollidingSectorList(CPtrList& list, const CVector& position, float radius, bool bCheck2DOnly, int16* nCollidingEntities, int16 maxEntitiesToFind, CEntity** aEntities); + static void FindObjectsIntersectingCube(const CVector& vecStartPos, const CVector& vecEndPos, int16* nIntersecting, int16 maxEntitiesToFind, CEntity** aEntities, bool bBuildings, bool bVehicles, bool bPeds, bool bObjects, bool bDummies); + static void FindObjectsIntersectingCubeSectorList(CPtrList& list, const CVector& vecStartPos, const CVector& vecEndPos, int16* nIntersecting, int16 maxEntitiesToFind, CEntity** aEntities); + static void FindObjectsIntersectingAngledCollisionBox(const CColBox &, const CMatrix &, const CVector &, float, float, float, float, int16*, int16, CEntity **, bool, bool, bool, bool, bool); + static void FindObjectsIntersectingAngledCollisionBoxSectorList(CPtrList& list, const CColBox& boundingBox, const CMatrix& matrix, const CVector& position, int16* nEntitiesFound, int16 maxEntitiesToFind, CEntity** aEntities); + static void FindMissionEntitiesIntersectingCube(const CVector& vecStartPos, const CVector& vecEndPos, int16* nIntersecting, int16 maxEntitiesToFind, CEntity** aEntities, bool bVehicles, bool bPeds, bool bObjects); + static void FindMissionEntitiesIntersectingCubeSectorList(CPtrList& list, const CVector& vecStartPos, const CVector& vecEndPos, int16* nIntersecting, int16 maxEntitiesToFind, CEntity** aEntities, bool bIsVehicleList, bool bIsPedList); + + static void ClearCarsFromArea(float x1, float y1, float z1, float x2, float y2, float z2); + static void ClearPedsFromArea(float x1, float y1, float z1, float x2, float y2, float z2); + static void CallOffChaseForArea(float x1, float y1, float x2, float y2); + static void CallOffChaseForAreaSectorListVehicles(CPtrList& list, float x1, float y1, float x2, float y2, float fStartX, float fStartY, float fEndX, float fEndY); + static void CallOffChaseForAreaSectorListPeds(CPtrList& list, float x1, float y1, float x2, float y2); + + static float GetSectorX(float f) { return ((f - WORLD_MIN_X)/SECTOR_SIZE_X); } + static float GetSectorY(float f) { return ((f - WORLD_MIN_Y)/SECTOR_SIZE_Y); } + static int GetSectorIndexX(float f) { return (int)GetSectorX(f); } + static int GetSectorIndexY(float f) { return (int)GetSectorY(f); } + static float GetWorldX(int x) { return x*SECTOR_SIZE_X + WORLD_MIN_X; } + static float GetWorldY(int y) { return y*SECTOR_SIZE_Y + WORLD_MIN_Y; } + + static void RemoveEntityInsteadOfProcessingIt(CEntity* ent); + static void RemoveFallenPeds(); + static void RemoveFallenCars(); + + static void StopAllLawEnforcersInTheirTracks(); + static void SetAllCarsCanBeDamaged(bool); + static void ExtinguishAllCarFiresInArea(CVector, float); + static void SetCarsOnFire(float x, float y, float z, float radius, CEntity* reason); + static void SetPedsOnFire(float x, float y, float z, float radius, CEntity* reason); + + static void Initialise(); + static void AddParticles(); + static void ShutDown(); + static void ClearForRestart(void); + static void RepositionCertainDynamicObjects(); + static void RepositionOneObject(CEntity* pEntity); + static void RemoveStaticObjects(); + static void Process(); + static void TriggerExplosion(const CVector& position, float fRadius, float fPower, CEntity* pCreator, bool bProcessVehicleBombTimer); + static void TriggerExplosionSectorList(CPtrList& list, const CVector& position, float fRadius, float fPower, CEntity* pCreator, bool bProcessVehicleBombTimer); + static void UseDetonator(CEntity *pEntity); +}; + +extern CColPoint gaTempSphereColPoints[MAX_COLLISION_POINTS]; + diff --git a/src/core/ZoneCull.cpp b/src/core/ZoneCull.cpp new file mode 100644 index 0000000..5a76e5e --- /dev/null +++ b/src/core/ZoneCull.cpp @@ -0,0 +1,1589 @@ +#include "common.h" + +#include "General.h" +#include "Building.h" +#include "Treadable.h" +#include "Train.h" +#include "Pools.h" +#include "Timer.h" +#include "Camera.h" +#include "World.h" +#include "FileMgr.h" +#include "ZoneCull.h" +#include "Zones.h" + +#include "Debug.h" +#include "Renderer.h" + +int32 CCullZones::NumCullZones; +CCullZone CCullZones::aZones[NUMCULLZONES]; +int32 CCullZones::NumAttributeZones; +CAttributeZone CCullZones::aAttributeZones[NUMATTRIBZONES]; +uint16 CCullZones::aIndices[NUMZONEINDICES]; +int16 CCullZones::aPointersToBigBuildingsForBuildings[NUMBUILDINGS]; +int16 CCullZones::aPointersToBigBuildingsForTreadables[NUMTREADABLES]; + +int32 CCullZones::CurrentWantedLevelDrop_Player; +int32 CCullZones::CurrentFlags_Camera; +int32 CCullZones::CurrentFlags_Player; +int32 CCullZones::OldCullZone; +int32 CCullZones::EntityIndicesUsed; +bool CCullZones::bCurrentSubwayIsInvisible; +bool CCullZones::bCullZonesDisabled; + +#define NUMUNCOMPRESSED (6000) +#define NUMTEMPINDICES (140000) + +void +CCullZones::Init(void) +{ + int i; + + NumAttributeZones = 0; + CurrentWantedLevelDrop_Player = 0; + CurrentFlags_Camera = 0; + CurrentFlags_Player = 0; + bCurrentSubwayIsInvisible = false; + NumCullZones = 0; + OldCullZone = -1; + EntityIndicesUsed = 0; + + for(i = 0; i < NUMBUILDINGS; i++) + aPointersToBigBuildingsForBuildings[i] = -1; + for(i = 0; i < NUMTREADABLES; i++) + aPointersToBigBuildingsForTreadables[i] = -1; +} + + +uint16* pTempArrayIndices; +int TempEntityIndicesUsed; + +void +CCullZones::ResolveVisibilities(void) +{ + int fd; + + CFileMgr::SetDir(""); + fd = CFileMgr::OpenFile("DATA\\cullzone.dat", "rb"); + if(fd > 0){ + CFileMgr::Read(fd, (char*)&NumCullZones, sizeof(NumCullZones)); + CFileMgr::Read(fd, (char*)aZones, sizeof(aZones)); + CFileMgr::Read(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones)); + CFileMgr::Read(fd, (char*)aAttributeZones, sizeof(aAttributeZones)); + CFileMgr::Read(fd, (char*)aIndices, sizeof(aIndices)); + CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings)); + CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables)); + CFileMgr::CloseFile(fd); + }else{ +#ifndef MASTER + EntityIndicesUsed = 0; + BuildListForBigBuildings(); + pTempArrayIndices = new uint16[NUMTEMPINDICES]; + TempEntityIndicesUsed = 0; + +// if(!LoadTempFile()) // not in final game + { + for (int i = 0; i < NumCullZones; i++) { +//printf("testing zone %d (%d indices)\n", i, TempEntityIndicesUsed); + DoVisibilityTestCullZone(i, true); + } + +// SaveTempFile(); // not in final game + } + + CompressIndicesArray(); + delete[] pTempArrayIndices; + pTempArrayIndices = nil; + + fd = CFileMgr::OpenFileForWriting("data\\cullzone.dat"); + if (fd != 0) { + CFileMgr::Write(fd, (char*)&NumCullZones, sizeof(NumCullZones)); + CFileMgr::Write(fd, (char*)aZones, sizeof(aZones)); + CFileMgr::Write(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones)); + CFileMgr::Write(fd, (char*)&aAttributeZones, sizeof(aAttributeZones)); + CFileMgr::Write(fd, (char*)&aIndices, sizeof(aIndices)); + CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings)); + CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables)); + CFileMgr::CloseFile(fd); + } +#endif + } +} + +bool +CCullZones::LoadTempFile(void) +{ + int fd = CFileMgr::OpenFile("cullzone.tmp"); + if (fd != 0) { + CFileMgr::Read(fd, (char*)&NumCullZones, sizeof(NumCullZones)); + CFileMgr::Read(fd, (char*)aZones, sizeof(aZones)); + CFileMgr::Read(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones)); + CFileMgr::Read(fd, (char*)&aAttributeZones, sizeof(aAttributeZones)); + CFileMgr::Read(fd, (char*)pTempArrayIndices, NUMTEMPINDICES*sizeof(uint16)); + CFileMgr::Read(fd, (char*)&TempEntityIndicesUsed, sizeof(TempEntityIndicesUsed)); + CFileMgr::Read(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings)); + CFileMgr::Read(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables)); + CFileMgr::CloseFile(fd); + return true; + } + return false; +} + +void +CCullZones::SaveTempFile(void) +{ + int fd = CFileMgr::OpenFileForWriting("cullzone.tmp"); + if (fd != 0) { + CFileMgr::Write(fd, (char*)&NumCullZones, sizeof(NumCullZones)); + CFileMgr::Write(fd, (char*)aZones, sizeof(aZones)); + CFileMgr::Write(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones)); + CFileMgr::Write(fd, (char*)&aAttributeZones, sizeof(aAttributeZones)); + CFileMgr::Write(fd, (char*)pTempArrayIndices, NUMTEMPINDICES*sizeof(uint16)); + CFileMgr::Write(fd, (char*)&TempEntityIndicesUsed, sizeof(TempEntityIndicesUsed)); + CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings)); + CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables)); + CFileMgr::CloseFile(fd); + } +} + + +void +CCullZones::BuildListForBigBuildings() +{ + for (int i = CPools::GetBuildingPool()->GetSize()-1; i >= 0; i--) { + CBuilding *building = CPools::GetBuildingPool()->GetSlot(i); + if (building == nil || !building->bIsBIGBuilding) continue; + CSimpleModelInfo *nonlod = ((CSimpleModelInfo *)CModelInfo::GetModelInfo(building->GetModelIndex()))->GetRelatedModel(); + if (nonlod == nil) continue; + + for (int j = CPools::GetBuildingPool()->GetSize()-1; j >= 0; j--) { + CBuilding *building2 = CPools::GetBuildingPool()->GetSlot(j); + if (building2 == nil || building2->bIsBIGBuilding) continue; + if (CModelInfo::GetModelInfo(building2->GetModelIndex()) == nonlod) { + if ((building2->GetPosition() - building->GetPosition()).Magnitude() < 5.0f) { + aPointersToBigBuildingsForBuildings[j] = i; + } + } + } + + for (int j = CPools::GetTreadablePool()->GetSize()-1; j >= 0; j--) { + CTreadable *treadable = CPools::GetTreadablePool()->GetSlot(j); + if (treadable == nil || treadable->bIsBIGBuilding) continue; + if (CModelInfo::GetModelInfo(treadable->GetModelIndex()) == nonlod) { + if ((treadable->GetPosition() - building->GetPosition()).Magnitude() < 5.0f) { + aPointersToBigBuildingsForTreadables[j] = i; + } + } + } + } +} + +void +CCullZones::DoVisibilityTestCullZone(int zoneId, bool findIndices) +{ + aZones[zoneId].m_groupIndexCount[0] = 0; + aZones[zoneId].m_groupIndexCount[1] = 0; + aZones[zoneId].m_groupIndexCount[2] = 0; + aZones[zoneId].m_indexStart = TempEntityIndicesUsed; + aZones[zoneId].FindTestPoints(); + + if (!findIndices) return; + + for (int i = CPools::GetBuildingPool()->GetSize() - 1; i >= 0; i--) { + CBuilding *building = CPools::GetBuildingPool()->GetSlot(i); + if (building != nil && !building->bIsBIGBuilding && aZones[zoneId].IsEntityCloseEnoughToZone(building, aPointersToBigBuildingsForBuildings[i] != -1)) { + CBuilding *LODbuilding = nil; + if (aPointersToBigBuildingsForBuildings[i] != -1) + LODbuilding = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForBuildings[i]); + + if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 0.0f, LODbuilding)) { + assert(TempEntityIndicesUsed < NUMTEMPINDICES); + pTempArrayIndices[TempEntityIndicesUsed++] = i; + aZones[zoneId].m_groupIndexCount[0]++; + } + } + } + + for (int i = CPools::GetTreadablePool()->GetSize() - 1; i >= 0; i--) { + CBuilding* building = CPools::GetTreadablePool()->GetSlot(i); + if (building != nil && aZones[zoneId].IsEntityCloseEnoughToZone(building, aPointersToBigBuildingsForTreadables[i] != -1)) { + CBuilding *LODbuilding = nil; + if (aPointersToBigBuildingsForTreadables[i] != -1) + LODbuilding = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForTreadables[i]); + + if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 10.0f, LODbuilding)) { + assert(TempEntityIndicesUsed < NUMTEMPINDICES); + pTempArrayIndices[TempEntityIndicesUsed++] = i; + aZones[zoneId].m_groupIndexCount[1]++; + } + } + } + + for (int i = CPools::GetTreadablePool()->GetSize() - 1; i >= 0; i--) { + CBuilding *building = CPools::GetTreadablePool()->GetSlot(i); + if (building != nil && aZones[zoneId].CalcDistToCullZoneSquared(building->GetPosition().x, building->GetPosition().y) < SQR(200.0f)) { + int start = aZones[zoneId].m_groupIndexCount[0] + aZones[zoneId].m_indexStart; + int end = aZones[zoneId].m_groupIndexCount[1] + start; + + bool alreadyAdded = false; + + for (int k = start; k < end; k++) { +#ifdef FIX_BUGS + if (pTempArrayIndices[k] == i) +#else + if (aIndices[k] == i) +#endif + alreadyAdded = true; + } + + if (!alreadyAdded) { + CBuilding *LODbuilding = nil; + if (aPointersToBigBuildingsForTreadables[i] != -1) + LODbuilding = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForTreadables[i]); + if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 0.0f, LODbuilding)) { + assert(TempEntityIndicesUsed < NUMTEMPINDICES); + pTempArrayIndices[TempEntityIndicesUsed++] = i; + aZones[zoneId].m_groupIndexCount[2]++; + } + } + } + } +} + +void +CCullZones::Update(void) +{ + bool invisible; + + if(bCullZonesDisabled) + return; + + switch(CTimer::GetFrameCounter() & 7){ + case 0: + case 4: + /* Update Cull zone */ + ForceCullZoneCoors(TheCamera.GetGameCamPosition()); + break; + + case 2: + /* Update camera attributes */ + CurrentFlags_Camera = FindAttributesForCoors(TheCamera.GetGameCamPosition(), nil); + invisible = (CurrentFlags_Camera & ATTRZONE_SUBWAYVISIBLE) == 0; + if(invisible != bCurrentSubwayIsInvisible){ + MarkSubwayAsInvisible(!invisible); + bCurrentSubwayIsInvisible = invisible; + } + break; + + case 6: + /* Update player attributes */ + CurrentFlags_Player = FindAttributesForCoors(FindPlayerCoors(), + &CurrentWantedLevelDrop_Player); + break; + } +} + +void +CCullZones::ForceCullZoneCoors(CVector coors) +{ + int32 z; + z = FindCullZoneForCoors(coors); + if(z != OldCullZone){ + if(OldCullZone >= 0) + aZones[OldCullZone].DoStuffLeavingZone(); + if(z >= 0) + aZones[z].DoStuffEnteringZone(); + OldCullZone = z; + } +} + +int32 +CCullZones::FindCullZoneForCoors(CVector coors) +{ + int i; + + for(i = 0; i < NumCullZones; i++) + if(coors.x >= aZones[i].minx && coors.x <= aZones[i].maxx && + coors.y >= aZones[i].miny && coors.y <= aZones[i].maxy && + coors.z >= aZones[i].minz && coors.z <= aZones[i].maxz) + return i; + return -1; +} + +int32 +CCullZones::FindAttributesForCoors(CVector coors, int32 *wantedLevel) +{ + int i; + int32 attribs; + + if (wantedLevel) + *wantedLevel = 0; + attribs = 0; + for(i = 0; i < NumAttributeZones; i++) + if(coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx && + coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy && + coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz){ + attribs |= aAttributeZones[i].attributes; + if(wantedLevel) + *wantedLevel = Max(*wantedLevel, aAttributeZones[i].wantedLevel); + } + return attribs; +} + +CAttributeZone* +CCullZones::FindZoneWithStairsAttributeForPlayer(void) +{ + int i; + CVector coors; + + coors = FindPlayerCoors(); + for(i = 0; i < NumAttributeZones; i++) + if(aAttributeZones[i].attributes & ATTRZONE_STAIRS && + coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx && + coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy && + coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz) + return &aAttributeZones[i]; + return nil; +} + +void +CCullZones::MarkSubwayAsInvisible(bool visible) +{ + int i, n; + CEntity *e; + CVehicle *v; + + n = CPools::GetBuildingPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->bIsSubway) + e->bIsVisible = visible; + } + + n = CPools::GetTreadablePool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetTreadablePool()->GetSlot(i); + if(e && e->bIsSubway) + e->bIsVisible = visible; + } + + n = CPools::GetVehiclePool()->GetSize()-1; + for(i = n; i >= 0; i--){ + v = CPools::GetVehiclePool()->GetSlot(i); + if(v && v->IsTrain() && ((CTrain*)v)->m_nTrackId != TRACK_ELTRAIN) + v->bIsVisible = visible; + } +} + +void +CCullZones::AddCullZone(CVector const &position, + float minx, float maxx, + float miny, float maxy, + float minz, float maxz, + uint16 flag, int16 wantedLevel) +{ + CCullZone *cull; + CAttributeZone *attrib; + + CVector v; + if((flag & ATTRZONE_NOTCULLZONE) == 0){ + cull = &aZones[NumCullZones++]; + v = position; + // reposition start point to the start/end of the + // alley next to the big building in the industrial district. + // probably isn't analyzed correctly otherwise?s + if((v-CVector(1032.14f, -624.255f, 24.93f)).Magnitude() < 1.0f) + v = CVector(1061.7f, -613.0f, 19.0f); + if((v-CVector(1029.48f, -495.757f, 21.98f)).Magnitude() < 1.0f) + v = CVector(1061.4f, -506.0f, 18.5f); + cull->position.x = Clamp(v.x, minx, maxx); + cull->position.y = Clamp(v.y, miny, maxy); + cull->position.z = Clamp(v.z, minz, maxz); + cull->minx = minx; + cull->maxx = maxx; + cull->miny = miny; + cull->maxy = maxy; + cull->minz = minz; + cull->maxz = maxz; + cull->m_groupIndexCount[0] = 0; + cull->m_groupIndexCount[1] = 0; + cull->m_groupIndexCount[2] = 0; + cull->m_indexStart = 0; + } + if(flag & ~ATTRZONE_NOTCULLZONE){ + attrib = &aAttributeZones[NumAttributeZones++]; + attrib->minx = minx; + attrib->maxx = maxx; + attrib->miny = miny; + attrib->maxy = maxy; + attrib->minz = minz; + attrib->maxz = maxz; + attrib->attributes = flag; + attrib->wantedLevel = wantedLevel; + } +} + +uint16 *pExtraArrayIndices; + +void +CCullZones::CompressIndicesArray() +{ + uint16 set[3]; + + // These are used to hold the compressed groups in sets of 3 + int numExtraIndices = 0; + pExtraArrayIndices = new uint16[NUMTEMPINDICES]; + + for(int numOccurrences = 6; numOccurrences > 1; numOccurrences--){ + if(NumCullZones == 0) + break; + +//printf("checking occurrences %d\n", numOccurrences); + int attempt = 0; + while(attempt < 10000){ + for(;;){ + attempt++; + + int zone = CGeneral::GetRandomNumber() % NumCullZones; + int group = CGeneral::GetRandomNumber() % 3; + if(!PickRandomSetForGroup(zone, group, set)) + break; + if(!DoWeHaveMoreThanXOccurencesOfSet(numOccurrences, set)) + break; + + // add this set + attempt = 1; + int setId = numExtraIndices + NUMUNCOMPRESSED; + pExtraArrayIndices[numExtraIndices++] = set[0]; + pExtraArrayIndices[numExtraIndices++] = set[1]; + pExtraArrayIndices[numExtraIndices++] = set[2]; + ReplaceSetForAllGroups(set, setId); + } + } + } + + TidyUpAndMergeLists(pExtraArrayIndices, numExtraIndices); + + delete[] pExtraArrayIndices; +} + +// Get three random indices for this group of a zone +bool +CCullZones::PickRandomSetForGroup(int32 zone, int32 group, uint16 *set) +{ + int32 start; + int32 size; + + aZones[zone].GetGroupStartAndSize(group, start, size); + if(size <= 0) + return false; + + int numIndices = 0; + for(int i = 0; i < size; i++) + if(pTempArrayIndices[start + i] != 0xFFFF) + numIndices++; + if(numIndices < 3) + return false; + + int first = CGeneral::GetRandomNumber() % (numIndices-2); + + numIndices = 0; + int n = 0; + for(int i = 0; i < size; i++) + if(pTempArrayIndices[start + i] != 0xFFFF){ + if(n++ < first) continue; + + set[numIndices++] = pTempArrayIndices[start + i]; + if(numIndices == 3) + break; + } + return true; +} + +bool +CCullZones::DoWeHaveMoreThanXOccurencesOfSet(int32 count, uint16 *set) +{ + int32 curCount; + int32 start; + int32 size; + + curCount = 0; + for (int i = 0; i < NumCullZones; i++) { + for (int group = 0; group < 3; group++) { + aZones[i].GetGroupStartAndSize(group, start, size); + if(size <= 0) continue; + + // check if the set is a subset of the group + int n = 0; + for (int j = 0; j < size; j++) { + for (int k = 0; k < 3; k++) { + if (pTempArrayIndices[start+j] == set[k]) + n++; + } + } + // yes it is + if(n == 3){ + curCount++; + // check if we have seen this set often enough + if(curCount >= count) + return true; + } + } + } + return false; +} + +void +CCullZones::ReplaceSetForAllGroups(uint16 *set, uint16 setid) +{ + int32 start; + int32 size; + + for(int i = 0; i < NumCullZones; i++) + for(int group = 0; group < 3; group++){ + aZones[i].GetGroupStartAndSize(group, start, size); + if(size <= 0) continue; + + // check if the set is a subset of the group + int n = 0; + for(int j = 0; j < size; j++){ + for(int k = 0; k < 3; k++){ + if(pTempArrayIndices[start+j] == set[k]) + n++; + } + } + + // yes it is, so replace it + if(n == 3){ + bool insertedSet = false; + for(int j = 0; j < size; j++){ + for(int k = 0; k < 3; k++){ + // replace first element by set, invalidate others + if(pTempArrayIndices[start+j] == set[k]){ + if(!insertedSet) + pTempArrayIndices[start+j] = setid; + else + pTempArrayIndices[start+j] = 0xFFFF; + insertedSet = true; + } + } + } + } + } +} + +void +CCullZones::TidyUpAndMergeLists(uint16 *extraIndices, int32 numExtraIndices) +{ + int numTempIndices = 0; + for(int i = 0; i < TempEntityIndicesUsed; i++) + if(pTempArrayIndices[i] != 0xFFFF) + numTempIndices++; + + // Fix up zone ranges such that there are no holes + for(int i = 0; i < NumCullZones; i++){ + int j; + int start = 0; + for(j = 0; j < aZones[i].m_indexStart; j++) + if(pTempArrayIndices[j] != 0xFFFF) + start++; + + aZones[i].m_indexStart = start; + aZones[i].m_numBuildings = 0; + aZones[i].m_numTreadablesPlus10m = 0; + aZones[i].m_numTreadables = 0; + + for(int k = 0; k < aZones[i].m_groupIndexCount[0]; k++) + if(pTempArrayIndices[j++] != 0xFFFF) + aZones[i].m_numBuildings++; + for(int k = 0; k < aZones[i].m_groupIndexCount[1]; k++) + if(pTempArrayIndices[j++] != 0xFFFF) + aZones[i].m_numTreadablesPlus10m++; + for(int k = 0; k < aZones[i].m_groupIndexCount[2]; k++) + if(pTempArrayIndices[j++] != 0xFFFF) + aZones[i].m_numTreadables++; + } + + // Now copy the actually used indices + EntityIndicesUsed = 0; + for(int i = 0; i < TempEntityIndicesUsed; i++) + if(pTempArrayIndices[i] != 0xFFFF){ + assert(EntityIndicesUsed < NUMZONEINDICES); + if(pTempArrayIndices[i] < NUMUNCOMPRESSED) + aIndices[EntityIndicesUsed++] = pTempArrayIndices[i]; + else + aIndices[EntityIndicesUsed++] = pTempArrayIndices[i] + numTempIndices; + } + for(int i = 0; i < numExtraIndices; i++) + if(extraIndices[i] != 0xFFFF){ + assert(EntityIndicesUsed < NUMZONEINDICES); + if(extraIndices[i] < NUMUNCOMPRESSED) + aIndices[EntityIndicesUsed++] = extraIndices[i]; + else + aIndices[EntityIndicesUsed++] = extraIndices[i] + numTempIndices; + } +} + + + +void +CCullZone::DoStuffLeavingZone(void) +{ + int i; + + for(i = 0; i < m_numBuildings; i++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]); + for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables ; i++) + DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[m_indexStart + i]); +} + +void +CCullZone::DoStuffLeavingZone_OneBuilding(uint16 i) +{ + int16 bb; + int j; + + + if(i < NUMUNCOMPRESSED){ + CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = false; + bb = CCullZones::aPointersToBigBuildingsForBuildings[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false; + }else{ + i -= NUMUNCOMPRESSED; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffLeavingZone_OneTreadableBoth(uint16 i) +{ + int16 bb; + int j; + + if(i < NUMUNCOMPRESSED){ + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = false; + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = false; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false; + }else{ + i -= NUMUNCOMPRESSED; + for(j = 0; j < 3; j++) + DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone(void) +{ + int i; + + for(i = 0; i < m_numBuildings; i++) + DoStuffEnteringZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]); + for(; i < m_numBuildings + m_numTreadablesPlus10m; i++) + DoStuffEnteringZone_OneTreadablePlus10m(CCullZones::aIndices[m_indexStart + i]); + for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables; i++) + DoStuffEnteringZone_OneTreadable(CCullZones::aIndices[m_indexStart + i]); +} + +void +CCullZone::DoStuffEnteringZone_OneBuilding(uint16 i) +{ + int16 bb; + int j; + + if(i < NUMUNCOMPRESSED){ + CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = true; + bb = CCullZones::aPointersToBigBuildingsForBuildings[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true; + }else{ + i -= NUMUNCOMPRESSED; + for(j = 0; j < 3; j++) + DoStuffEnteringZone_OneBuilding(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone_OneTreadablePlus10m(uint16 i) +{ + int16 bb; + int j; + + if(i < NUMUNCOMPRESSED){ + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true; + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = true; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true; + }else{ + i -= NUMUNCOMPRESSED; + for(j = 0; j < 3; j++) + DoStuffEnteringZone_OneTreadablePlus10m(CCullZones::aIndices[i+j]); + } +} + +void +CCullZone::DoStuffEnteringZone_OneTreadable(uint16 i) +{ + int16 bb; + int j; + + if(i < NUMUNCOMPRESSED){ + CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true; + bb = CCullZones::aPointersToBigBuildingsForTreadables[i]; + if(bb != -1) + CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true; + }else{ + i -= NUMUNCOMPRESSED; + for(j = 0; j < 3; j++) + DoStuffEnteringZone_OneTreadable(CCullZones::aIndices[i+j]); + } +} + +float +CCullZone::CalcDistToCullZoneSquared(float x, float y) +{ + float rx, ry; + + if (x < minx) rx = sq(x - minx); + else if (x > maxx) rx = sq(x - maxx); + else rx = 0.0f; + + if (y < miny) ry = sq(y - miny); + else if (y > maxy) ry = sq(y - maxy); + else ry = 0.0f; + + return rx + ry; +} + +bool +CCullZone::TestLine(CVector vec1, CVector vec2) +{ + CColPoint colPoint; + CEntity *entity; + + if (CWorld::ProcessLineOfSight(vec1, vec2, colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x + 0.05f, vec1.y, vec1.z), CVector(vec2.x + 0.05f, vec2.y, vec2.z), colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x - 0.05f, vec1.y, vec1.z), CVector(vec2.x - 0.05f, vec2.y, vec2.z), colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y + 0.05f, vec1.z), CVector(vec2.x, vec2.y + 0.05f, vec2.z), colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y - 0.05f, vec1.z), CVector(vec2.x, vec2.y - 0.05f, vec2.z), colPoint, entity, true, false, false, false, false, true, false)) + return true; + if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y, vec1.z + 0.05f), CVector(vec2.x, vec2.y, vec2.z + 0.05f), colPoint, entity, true, false, false, false, false, true, false)) + return true; + return CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y, vec1.z - 0.05f), CVector(vec2.x, vec2.y, vec2.z - 0.05f), colPoint, entity, true, false, false, false, false, true, false); +} + +bool +CCullZone::DoThoroughLineTest(CVector start, CVector end, CEntity *testEntity) +{ + CColPoint colPoint; + CEntity *entity; + + if(CWorld::ProcessLineOfSight(start, end, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + + CVector side; +#ifdef FIX_BUGS + if(start.x != end.x || start.y != end.y) +#else + if(start.x != end.x && start.y != end.y) +#endif + side = CVector(0.0f, 0.0f, 1.0f); + else + side = CVector(1.0f, 0.0f, 0.0f); + CVector up = CrossProduct(side, end - start); + side = CrossProduct(up, end - start); + side.Normalise(); + up.Normalise(); + side *= 0.1f; + up *= 0.1f; + + if(CWorld::ProcessLineOfSight(start+side, end+side, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + if(CWorld::ProcessLineOfSight(start-side, end-side, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + if(CWorld::ProcessLineOfSight(start+up, end+up, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + if(CWorld::ProcessLineOfSight(start-up, end-up, colPoint, entity, true, false, false, false, false, true, false) && + testEntity != entity) + return false; + return true; +} + +bool +CCullZone::IsEntityCloseEnoughToZone(CEntity *entity, bool checkLevel) +{ + const CVector &pos = entity->GetPosition(); + + CSimpleModelInfo *minfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(entity->GetModelIndex()); + float distToZone = CalcDistToCullZone(pos.x, pos.y); + float lodDist; + if (minfo->m_noFade) + lodDist = minfo->GetLargestLodDistance() + STREAM_DISTANCE; + else + lodDist = minfo->GetLargestLodDistance() + STREAM_DISTANCE + FADE_DISTANCE; + + if (lodDist > distToZone) return true; + if (!checkLevel) return false; + CVector tempPos(minx, miny, minz); + return CTheZones::GetLevelFromPosition(&pos) == CTheZones::GetLevelFromPosition(&tempPos); +} + +bool +CCullZone::PointFallsWithinZone(CVector pos, float radius) +{ + if(minx - radius > pos.x || + maxx + radius < pos.x || + miny - radius > pos.y || + maxy + radius < pos.y || + minz - radius > pos.z || + maxz + radius < pos.z) + return false; + return true; +} + + +CVector ExtraFudgePointsCoors[] = { + CVector(978.0f, -394.0f, 18.0f), + CVector(1189.7f, -414.6f, 27.0f), + CVector(978.8f, -391.0f, 19.0f), + CVector(1199.0f, -502.3f, 28.0f), + CVector(1037.0f, -391.9f, 18.4f), + CVector(1140.0f, -608.7f, 16.0f), + CVector(1051.0f, -26.0f, 11.0f), + CVector(951.5f, -345.1f, 12.0f), + CVector(958.2f, -394.6f, 16.0f), + CVector(1036.5f, -390.0f, 15.2f), + CVector(960.6f, -390.5f, 20.9f), + CVector(1061.0f, -640.6f, 16.3f), + CVector(1034.5f, -388.96f, 14.78f), + CVector(1038.4f, -13.98f, 12.2f), + CVector(1047.2f, -16.7f, 10.6f), + CVector(1257.9f, -333.3f, 40.0f), + CVector(885.6f, -424.9f, 17.0f), + CVector(1127.5f, -795.8f, 17.7f), + CVector(1133.0f, -716.0f, 19.0f), + CVector(1125.0f, -694.0f, 18.5f), + CVector(1125.0f, -670.0f, 16.3f), + CVector(1051.6f, 36.3f, 17.9f), + CVector(1054.6f, -11.4f, 15.0f), + CVector(1058.9f, -278.0f, 15.0f), + CVector(1059.4f, -261.0f, 10.9f), + CVector(1051.5f, -638.5f, 16.5f), + CVector(1058.2f, -643.4f, 15.5f), + CVector(1058.2f, -643.4f, 18.0f), + CVector(826.0f, -260.0f, 7.0f), + CVector(826.0f, -260.0f, 11.0f), + CVector(833.0f, -603.6f, 16.4f), + CVector(833.0f, -603.6f, 20.0f), + CVector(1002.0f, -318.5f, 10.5f), + CVector(998.0f, -318.0f, 9.8f), + CVector(1127.0f, -183.0f, 18.1f), + CVector(1123.0f, -331.5f, 23.8f), + CVector(1123.8f, -429.0f, 24.0f), + CVector(1197.0f, -30.0f, 13.7f), + CVector(1117.5f, -230.0f, 17.3f), + CVector(1117.5f, -230.0f, 20.0f), + CVector(1120.0f, -281.6f, 21.5f), + CVector(1120.0f, -281.6f, 24.0f), + CVector(1084.5f, -1022.7f, 17.0f), + CVector(1071.5f, 5.4f, 4.6f), + CVector(1177.2f, -215.7f, 27.6f), + CVector(841.6f, -460.0f, 19.7f), + CVector(874.8f, -456.6f, 16.6f), + CVector(918.3f, -451.8f, 17.8f), + CVector(844.0f, -495.7f, 16.7f), + CVector(842.0f, -493.4f, 21.0f), + CVector(1433.5f, -774.4f, 16.9f), + CVector(1051.0f, -205.0f, 7.5f), + CVector(885.5f, -425.6f, 15.6f), + CVector(182.6f, -470.4f, 27.8f), + CVector(132.5f, -930.2f, 29.0f), + CVector(124.7f, -904.0f, 28.0f), + CVector(-50.0f, -686.0f, 22.0f), + CVector(-49.1f, -694.5f, 22.5f), + CVector(1063.8f, -404.45f, 16.2f), + CVector(1062.2f, -405.5f, 17.0f) +}; +int32 NumTestPoints; +int32 aTestPointsX[100]; +int32 aTestPointsY[100]; +int32 aTestPointsZ[100]; +CVector aTestPoints[100]; +int32 ElementsX, ElementsY, ElementsZ; +float StepX, StepY, StepZ; +int32 Memsize; +uint8 *pMem; +#define MEM(x, y, z) pMem[((x)*ElementsY + (y))*ElementsZ + (z)] +#define FLAG_FREE 1 +#define FLAG_PROCESSED 2 + +int32 MinValX, MaxValX; +int32 MinValY, MaxValY; +int32 MinValZ, MaxValZ; +int32 Point1, Point2; +int32 NewPointX, NewPointY, NewPointZ; + + +void +CCullZone::FindTestPoints() +{ + static int CZNumber; + + NumTestPoints = 0; + ElementsX = (maxx-minx) < 1.0f ? 2 : (maxx-minx)+1.0f; + ElementsY = (maxy-miny) < 1.0f ? 2 : (maxy-miny)+1.0f; + ElementsZ = (maxz-minz) < 1.0f ? 2 : (maxz-minz)+1.0f; + if(ElementsX > 32) ElementsX = 32; + if(ElementsY > 32) ElementsY = 32; + if(ElementsZ > 32) ElementsZ = 32; + Memsize = ElementsX * ElementsY * ElementsZ; + StepX = (maxx-minx)/(ElementsX-1); + StepY = (maxy-miny)/(ElementsY-1); + StepZ = (maxz-minz)/(ElementsZ-1); + + pMem = new uint8[Memsize]; + memset(pMem, 0, Memsize); + + // indices of center + int x = ElementsX * (position.x-minx)/(maxx-minx); + x = Clamp(x, 0, ElementsX-1); + int y = ElementsY * (position.y-miny)/(maxy-miny); + y = Clamp(y, 0, ElementsY-1); + int z = ElementsZ * (position.z-minz)/(maxz-minz); + z = Clamp(z, 0, ElementsZ-1); + + // Mark which test points inside the zone are not occupied by buildings. + // To do this, mark the start point as free and do a food fill. + + // NB: we just assume the start position is free here! + MEM(x, y, z) |= FLAG_FREE; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + NumTestPoints++; + + bool notDoneYet; + do{ + notDoneYet = false; + for(x = 0; x < ElementsX; x++){ + for(y = 0; y < ElementsY; y++){ + for(z = 0; z < ElementsZ; z++){ + if(!(MEM(x, y, z) & FLAG_FREE) || MEM(x, y, z) & FLAG_PROCESSED) + continue; + + float pX = x*StepX + minx; + float pY = y*StepY + miny; + float pZ = z*StepZ + minz; + + if(x > 0 && !(MEM(x-1, y, z) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX-StepX, pY, pZ))) + MEM(x-1, y, z) |= FLAG_FREE; + if(x < ElementsX-1 && !(MEM(x+1, y, z) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX+StepX, pY, pZ))) + MEM(x+1, y, z) |= FLAG_FREE; + + if(y > 0 && !(MEM(x, y-1, z) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX, pY-StepY, pZ))) + MEM(x, y-1, z) |= FLAG_FREE; + if(y < ElementsY-1 && !(MEM(x, y+1, z) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX, pY+StepY, pZ))) + MEM(x, y+1, z) |= FLAG_FREE; + + if(z > 0 && !(MEM(x, y, z-1) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX, pY, pZ-StepZ))) + MEM(x, y, z-1) |= FLAG_FREE; + if(z < ElementsZ-1 && !(MEM(x, y, z+1) & (FLAG_FREE | FLAG_PROCESSED)) && + !TestLine(CVector(pX, pY, pZ), CVector(pX, pY, pZ+StepZ))) + MEM(x, y, z+1) |= FLAG_FREE; + + notDoneYet = true; + MEM(x, y, z) |= FLAG_PROCESSED; + } + } + } + }while(notDoneYet); + + bool done; + + // Find bound planes of free space + + // increase x, bounds in y and z + x = 0; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(y = 0; y < ElementsY; y++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, y, z) & FLAG_FREE){ + if(y + z < minA){ + minA = y + z; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(y + z > maxA){ + maxA = y + z; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(y - z < minB){ + minB = y - z; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(y - z > maxB){ + maxB = y - z; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + x++; + }while(!done); + NumTestPoints += 4; + + // decrease x, bounds in y and z + x = ElementsX-1; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(y = 0; y < ElementsY; y++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, y, z) & FLAG_FREE){ + if(y + z < minA){ + minA = y + z; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(y + z > maxA){ + maxA = y + z; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(y - z < minB){ + minB = y - z; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(y - z > maxB){ + maxB = y - z; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + x--; + }while(!done); + NumTestPoints += 4; + + // increase y, bounds in x and z + y = 0; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(x = 0; x < ElementsX; x++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, y, z) & FLAG_FREE){ + if(x + z < minA){ + minA = x + z; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(x + z > maxA){ + maxA = x + z; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(x - z < minB){ + minB = x - z; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(x - z > maxB){ + maxB = x - z; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + y++; + }while(!done); + NumTestPoints += 4; + + // decrease y, bounds in x and z + y = ElementsY-1; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(x = 0; x < ElementsX; x++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, y, z) & FLAG_FREE){ + if(x + z < minA){ + minA = x + z; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(x + z > maxA){ + maxA = x + z; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(x - z < minB){ + minB = x - z; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(x - z > maxB){ + maxB = x - z; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + y--; + }while(!done); + NumTestPoints += 4; + + // increase z, bounds in x and y + z = 0; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(x = 0; x < ElementsX; x++) + for(y = 0; y < ElementsY; y++) + if(MEM(x, y, z) & FLAG_FREE){ + if(x + y < minA){ + minA = x + y; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(x + y > maxA){ + maxA = x + y; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(x - y < minB){ + minB = x - y; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(x - y > maxB){ + maxB = x - y; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + z++; + }while(!done); + NumTestPoints += 4; + + // decrease z, bounds in x and y + z = ElementsZ-1; + do{ + done = false; + int minA = 10000; + int minB = 10000; + int maxA = -10000; + int maxB = -10000; + for(x = 0; x < ElementsX; x++) + for(y = 0; y < ElementsY; y++) + if(MEM(x, y, z) & FLAG_FREE){ + if(x + y < minA){ + minA = x + y; + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + } + if(x + y > maxA){ + maxA = x + y; + aTestPointsX[NumTestPoints+1] = x; + aTestPointsY[NumTestPoints+1] = y; + aTestPointsZ[NumTestPoints+1] = z; + } + if(x - y < minB){ + minB = x - y; + aTestPointsX[NumTestPoints+2] = x; + aTestPointsY[NumTestPoints+2] = y; + aTestPointsZ[NumTestPoints+2] = z; + } + if(x - y > maxB){ + maxB = x - y; + aTestPointsX[NumTestPoints+3] = x; + aTestPointsY[NumTestPoints+3] = y; + aTestPointsZ[NumTestPoints+3] = z; + } + done = true; + } + z--; + }while(!done); + NumTestPoints += 4; + + // divide the axis aligned bounding planes into 4 and place some test points + + // x = 0 plane + MinValY = 999999; + MinValZ = 999999; + MaxValY = 0; + MaxValZ = 0; + for(y = 0; y < ElementsY; y++) + for(z = 0; z < ElementsZ; z++) + if(MEM(0, y, z) & FLAG_FREE){ + if(y < MinValY) MinValY = y; + if(z < MinValZ) MinValZ = z; + if(y > MaxValY) MaxValY = y; + if(z > MaxValZ) MaxValZ = z; + } + // pick 4 points in the found bounds and add new test points + if(MaxValY != 0 && MaxValZ != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointY = (Point1 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY; + NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ; + if(MEM(0, NewPointY, NewPointZ) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = 0; + aTestPointsY[NumTestPoints] = NewPointY; + aTestPointsZ[NumTestPoints] = NewPointZ; + NumTestPoints++; + } + } + + // x = ElementsX-1 plane + MinValY = 999999; + MinValZ = 999999; + MaxValY = 0; + MaxValZ = 0; + for(y = 0; y < ElementsY; y++) + for(z = 0; z < ElementsZ; z++) + if(MEM(ElementsX-1, y, z) & FLAG_FREE){ + if(y < MinValY) MinValY = y; + if(z < MinValZ) MinValZ = z; + if(y > MaxValY) MaxValY = y; + if(z > MaxValZ) MaxValZ = z; + } + // pick 4 points in the found bounds and add new test points + if(MaxValY != 0 && MaxValZ != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointY = (Point1 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY; + NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ; + if(MEM(ElementsX-1, NewPointY, NewPointZ) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = ElementsX-1; + aTestPointsY[NumTestPoints] = NewPointY; + aTestPointsZ[NumTestPoints] = NewPointZ; + NumTestPoints++; + } + } + + // y = 0 plane + MinValX = 999999; + MinValZ = 999999; + MaxValX = 0; + MaxValZ = 0; + for(x = 0; x < ElementsX; x++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, 0, z) & FLAG_FREE){ + if(x < MinValX) MinValX = x; + if(z < MinValZ) MinValZ = z; + if(x > MaxValX) MaxValX = x; + if(z > MaxValZ) MaxValZ = z; + } + // pick 4 points in the found bounds and add new test points + if(MaxValX != 0 && MaxValZ != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX; + NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ; + if(MEM(NewPointX, 0, NewPointZ) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = NewPointX; + aTestPointsY[NumTestPoints] = 0; + aTestPointsZ[NumTestPoints] = NewPointZ; + NumTestPoints++; + } + } + + // y = ElementsY-1 plane + MinValX = 999999; + MinValZ = 999999; + MaxValX = 0; + MaxValZ = 0; + for(x = 0; x < ElementsX; x++) + for(z = 0; z < ElementsZ; z++) + if(MEM(x, ElementsY-1, z) & FLAG_FREE){ + if(x < MinValX) MinValX = x; + if(z < MinValZ) MinValZ = z; + if(x > MaxValX) MaxValX = x; + if(z > MaxValZ) MaxValZ = z; + } + // pick 4 points in the found bounds and add new test points + if(MaxValX != 0 && MaxValZ != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX; + NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ; + if(MEM(NewPointX, ElementsY-1, NewPointZ) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = NewPointX; + aTestPointsY[NumTestPoints] = ElementsY-1; + aTestPointsZ[NumTestPoints] = NewPointZ; + NumTestPoints++; + } + } + + // z = 0 plane + MinValX = 999999; + MinValY = 999999; + MaxValX = 0; + MaxValY = 0; + for(x = 0; x < ElementsX; x++) + for(y = 0; y < ElementsY; y++) + if(MEM(x, y, 0) & FLAG_FREE){ + if(x < MinValX) MinValX = x; + if(y < MinValY) MinValY = y; + if(x > MaxValX) MaxValX = x; + if(y > MaxValY) MaxValY = y; + } + // pick 4 points in the found bounds and add new test points + if(MaxValX != 0 && MaxValY != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX; + NewPointY = (Point2 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY; + if(MEM(NewPointX, NewPointY, 0) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = NewPointX; + aTestPointsY[NumTestPoints] = NewPointY; + aTestPointsZ[NumTestPoints] = 0; + NumTestPoints++; + } + } + + // z = ElementsZ-1 plane + MinValX = 999999; + MinValY = 999999; + MaxValX = 0; + MaxValY = 0; + for(x = 0; x < ElementsX; x++) + for(y = 0; y < ElementsY; y++) + if(MEM(x, y, ElementsZ-1) & FLAG_FREE){ + if(x < MinValX) MinValX = x; + if(y < MinValY) MinValY = y; + if(x > MaxValX) MaxValX = x; + if(y > MaxValY) MaxValY = y; + } + // pick 4 points in the found bounds and add new test points + if(MaxValX != 0 && MaxValY != 0) + for(Point1 = 0; Point1 < 2; Point1++) + for(Point2 = 0; Point2 < 2; Point2++){ + NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX; + NewPointY = (Point2 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY; + if(MEM(NewPointX, NewPointY, ElementsZ-1) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = NewPointX; + aTestPointsY[NumTestPoints] = NewPointY; + aTestPointsZ[NumTestPoints] = ElementsZ-1; + NumTestPoints++; + } + } + + // add some hardcoded test points + for(int i = 0; i < ARRAY_SIZE(ExtraFudgePointsCoors); i++) + if(PointFallsWithinZone(ExtraFudgePointsCoors[i], 0.0f)){ + x = ElementsX * (ExtraFudgePointsCoors[i].x-minx)/(maxx-minx); + y = ElementsY * (ExtraFudgePointsCoors[i].y-miny)/(maxy-miny); + z = ElementsZ * (ExtraFudgePointsCoors[i].z-minz)/(maxz-minz); + if(MEM(x, y, z) & FLAG_FREE){ + aTestPointsX[NumTestPoints] = x; + aTestPointsY[NumTestPoints] = y; + aTestPointsZ[NumTestPoints] = z; + NumTestPoints++; + } + } + + // remove duplicate points + for(int i = 0; i < NumTestPoints; i++) + for(int j = i+1; j < NumTestPoints; j++) + if(aTestPointsX[j] == aTestPointsX[i] && + aTestPointsY[j] == aTestPointsY[i] && + aTestPointsZ[j] == aTestPointsZ[i]){ + // get rid of [j] + for(int k = j; k < NumTestPoints-1; k++){ + aTestPointsX[k] = aTestPointsX[k+1]; + aTestPointsY[k] = aTestPointsY[k+1]; + aTestPointsZ[k] = aTestPointsZ[k+1]; + } + NumTestPoints--; + } + + // convert points to floating point + for(int i = 0; i < NumTestPoints; i++){ + aTestPoints[i].x = aTestPointsX[i]*StepX + minx; + aTestPoints[i].y = aTestPointsY[i]*StepY + miny; + aTestPoints[i].z = aTestPointsZ[i]*StepZ + minz; + } + + CZNumber++; + + delete[] pMem; + pMem = nil; +} + +bool +CCullZone::TestEntityVisibilityFromCullZone(CEntity *entity, float extraDist, CEntity *LODentity) +{ + CColModel *colmodel = entity->GetColModel(); + float boundMaxX = colmodel->boundingBox.max.x; + float boundMaxY = colmodel->boundingBox.max.y; + float boundMaxZ = colmodel->boundingBox.max.z; + float boundMinX = colmodel->boundingBox.min.x; + float boundMinY = colmodel->boundingBox.min.y; + float boundMinZ = colmodel->boundingBox.min.z; + if(LODentity){ + colmodel = LODentity->GetColModel(); + boundMaxX = Max(boundMaxX, colmodel->boundingBox.max.x); + boundMaxY = Max(boundMaxY, colmodel->boundingBox.max.y); + boundMaxZ = Max(boundMaxZ, colmodel->boundingBox.max.z); + boundMinX = Min(boundMinX, colmodel->boundingBox.min.x); + boundMinY = Min(boundMinY, colmodel->boundingBox.min.y); + boundMinZ = Min(boundMinZ, colmodel->boundingBox.min.z); + } + + if(boundMaxZ-boundMinZ + extraDist < 0.5f) + boundMaxZ = boundMinZ + 0.5f; + else + boundMaxZ += extraDist; + + CVector vecMin = entity->GetMatrix() * CVector(boundMinX, boundMinY, boundMinZ); + CVector vecMaxX = entity->GetMatrix() * CVector(boundMaxX, boundMinY, boundMinZ); + CVector vecMaxY = entity->GetMatrix() * CVector(boundMinX, boundMaxY, boundMinZ); + CVector vecMaxZ = entity->GetMatrix() * CVector(boundMinX, boundMinY, boundMaxZ); + CVector dirx = vecMaxX - vecMin; + CVector diry = vecMaxY - vecMin; + CVector dirz = vecMaxZ - vecMin; + + // If building intersects zone at all, it's visible + int x, y, z; + for(x = 0; x < 9; x++){ + CVector posX = vecMin + x/8.0f*dirx; + for(y = 0; y < 9; y++){ + CVector posY = posX + y/8.0f*diry; + for(z = 0; z < 9; z++){ + CVector posZ = posY + z/8.0f*dirz; + if(PointFallsWithinZone(posZ, 2.0f)) + return true; + } + } + } + + float distToZone = CalcDistToCullZone(entity->GetPosition().x, entity->GetPosition().y)/15.0f; + distToZone = Max(distToZone, 7.0f); + int numX = (boundMaxX - boundMinX)/distToZone + 2.0f; + int numY = (boundMaxY - boundMinY)/distToZone + 2.0f; + int numZ = (boundMaxZ - boundMinZ)/distToZone + 2.0f; + + float stepX = 1.0f/(numX-1); + float stepY = 1.0f/(numY-1); + float stepZ = 1.0f/(numZ-1); + float midX = (boundMaxX + boundMinX)/2.0f; + float midY = (boundMaxY + boundMinY)/2.0f; + float midZ = (boundMaxZ + boundMinZ)/2.0f; + + // check both xy planes + for(int i = 0; i < NumTestPoints; i++){ + CVector testPoint = aTestPoints[i]; + CVector mid = entity->GetMatrix() * CVector(midX, midY, midZ); + mid.z += 0.1f; + if(DoThoroughLineTest(testPoint, mid, entity)) + return true; + + CVector ray = entity->GetPosition() - testPoint; + + float dotX = DotProduct(ray, dirx); + float dotY = DotProduct(ray, diry); + float dotZ = DotProduct(ray, dirz); + + for(x = 0; x < numX; x++){ + CVector pMinZ = vecMin + x*stepX*dirx; + CVector pMaxZ = vecMin + x*stepX*dirx + dirz; + for(y = 0; y < numY; y++) + if(dotZ > 0.0f){ + if(DoThoroughLineTest(testPoint, pMinZ + y*stepY*diry, entity)) + return true; + }else{ + if(DoThoroughLineTest(testPoint, pMaxZ + y*stepY*diry, entity)) + return true; + } + } + + for(x = 0; x < numX; x++){ + CVector pMinY = vecMin + x*stepX*dirx; + CVector pMaxY = vecMin + x*stepX*dirx + diry; + for(z = 1; z < numZ-1; z++) // edge cases already handled + if(dotY > 0.0f){ + if(DoThoroughLineTest(testPoint, pMinY + z*stepZ*dirz, entity)) + return true; + }else{ + if(DoThoroughLineTest(testPoint, pMaxY + z*stepZ*dirz, entity)) + return true; + } + } + + for(y = 1; y < numY-1; y++){ // edge cases already handled + CVector pMinX = vecMin + y*stepY*diry; + CVector pMaxX = vecMin + y*stepY*diry + dirx; + for(z = 1; z < numZ-1; z++) // edge cases already handled + if(dotX > 0.0f){ + if(DoThoroughLineTest(testPoint, pMinX + z*stepZ*dirz, entity)) + return true; + }else{ + if(DoThoroughLineTest(testPoint, pMaxX + z*stepZ*dirz, entity)) + return true; + } + } + } + + return false; +} diff --git a/src/core/ZoneCull.h b/src/core/ZoneCull.h new file mode 100644 index 0000000..10742ff --- /dev/null +++ b/src/core/ZoneCull.h @@ -0,0 +1,137 @@ +class CEntity; + +class CCullZone +{ +public: + CVector position; + float minx; + float maxx; + float miny; + float maxy; + float minz; + float maxz; + + int32 m_indexStart; + int16 m_groupIndexCount[3]; // only useful during resolution stage + int16 m_numBuildings; + int16 m_numTreadablesPlus10m; + int16 m_numTreadables; + + void DoStuffLeavingZone(void); + static void DoStuffLeavingZone_OneBuilding(uint16 i); + static void DoStuffLeavingZone_OneTreadableBoth(uint16 i); + void DoStuffEnteringZone(void); + static void DoStuffEnteringZone_OneBuilding(uint16 i); + static void DoStuffEnteringZone_OneTreadablePlus10m(uint16 i); + static void DoStuffEnteringZone_OneTreadable(uint16 i); + + + static bool TestLine(CVector vec1, CVector vec2); + static bool DoThoroughLineTest(CVector vec1, CVector vec2, CEntity *testEntity); + float CalcDistToCullZoneSquared(float x, float y); + float CalcDistToCullZone(float x, float y) { return Sqrt(CalcDistToCullZoneSquared(x, y)); }; + bool IsEntityCloseEnoughToZone(CEntity* entity, bool checkLevel); + bool PointFallsWithinZone(CVector pos, float radius); + bool TestEntityVisibilityFromCullZone(CEntity *entity, float extraDist, CEntity *LODentity); + void FindTestPoints(); + + void GetGroupStartAndSize(int32 groupid, int32 &start, int32 &size) { + switch (groupid) { + case 0: + default: + // buildings + start = m_indexStart; + size = m_groupIndexCount[0]; + break; + case 1: + // treadables + 10m + start = m_groupIndexCount[0] + m_indexStart; + size = m_groupIndexCount[1]; + break; + case 2: + // treadables + start = m_groupIndexCount[0] + m_groupIndexCount[1] + m_indexStart; + size = m_groupIndexCount[2]; + break; + } + } +}; + +enum eZoneAttribs +{ + ATTRZONE_CAMCLOSEIN = 1, + ATTRZONE_STAIRS = 2, + ATTRZONE_1STPERSON = 4, + ATTRZONE_NORAIN = 8, + ATTRZONE_NOPOLICE = 0x10, + ATTRZONE_NOTCULLZONE = 0x20, + ATTRZONE_DOINEEDCOLLISION = 0x40, + ATTRZONE_SUBWAYVISIBLE = 0x80, +}; + +struct CAttributeZone +{ + float minx; + float maxx; + float miny; + float maxy; + float minz; + float maxz; + int16 attributes; + int16 wantedLevel; +}; + +class CCullZones +{ +public: + static int32 NumCullZones; + static CCullZone aZones[NUMCULLZONES]; + static int32 NumAttributeZones; + static CAttributeZone aAttributeZones[NUMATTRIBZONES]; + static uint16 aIndices[NUMZONEINDICES]; + static int16 aPointersToBigBuildingsForBuildings[NUMBUILDINGS]; + static int16 aPointersToBigBuildingsForTreadables[NUMTREADABLES]; + + static int32 CurrentWantedLevelDrop_Player; + static int32 CurrentFlags_Camera; + static int32 CurrentFlags_Player; + static int32 OldCullZone; + static int32 EntityIndicesUsed; + static bool bCurrentSubwayIsInvisible; + static bool bCullZonesDisabled; + + static void Init(void); + static void ResolveVisibilities(void); + static void Update(void); + static void ForceCullZoneCoors(CVector coors); + static int32 FindCullZoneForCoors(CVector coors); + static int32 FindAttributesForCoors(CVector coors, int32 *wantedLevel); + static CAttributeZone *FindZoneWithStairsAttributeForPlayer(void); + static void MarkSubwayAsInvisible(bool visible); + static void AddCullZone(CVector const &position, + float minx, float maxx, + float miny, float maxy, + float minz, float maxz, + uint16 flag, int16 wantedLevel); + static bool CamCloseInForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_CAMCLOSEIN) != 0; } + static bool CamStairsForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_STAIRS) != 0; } + static bool Cam1stPersonForPlayer(void) { return (CurrentFlags_Player & ATTRZONE_1STPERSON) != 0; } + static bool NoPolice(void) { return (CurrentFlags_Player & ATTRZONE_NOPOLICE) != 0; } + static bool DoINeedToLoadCollision(void) { return (CurrentFlags_Player & ATTRZONE_DOINEEDCOLLISION) != 0; } + static bool PlayerNoRain(void) { return (CurrentFlags_Player & ATTRZONE_NORAIN) != 0; } + static bool CamNoRain(void) { return (CurrentFlags_Camera & ATTRZONE_NORAIN) != 0; } + static int32 GetWantedLevelDrop(void) { return CurrentWantedLevelDrop_Player; } + + static void BuildListForBigBuildings(); + static void DoVisibilityTestCullZone(int zoneId, bool doIt); + static bool DoWeHaveMoreThanXOccurencesOfSet(int32 count, uint16 *set); + + static void CompressIndicesArray(); + static bool PickRandomSetForGroup(int32 zone, int32 group, uint16 *set); + static void ReplaceSetForAllGroups(uint16 *set, uint16 setid); + static void TidyUpAndMergeLists(uint16 *extraIndices, int32 numExtraIndices); + + // debug + static bool LoadTempFile(void); + static void SaveTempFile(void); +}; diff --git a/src/core/Zones.cpp b/src/core/Zones.cpp new file mode 100644 index 0000000..82fbc04 --- /dev/null +++ b/src/core/Zones.cpp @@ -0,0 +1,821 @@ +#include "common.h" + +#include + +#include "Zones.h" + +#include "Clock.h" +#include "Text.h" +#include "World.h" +#include "Timer.h" +#include "SaveBuf.h" + +#ifdef COMPATIBLE_SAVES +#define ZONEARRAY_SAVE_SIZE 0xAF0 +#define MAPZONEARRAY_SAVE_SIZE 0x578 +#else +#define ZONEARRAY_SAVE_SIZE sizeof(ZoneArray) +#define MAPZONEARRAY_SAVE_SIZE sizeof(MapZoneArray) +#endif + +eLevelName CTheZones::m_CurrLevel; +CZone *CTheZones::m_pPlayersZone; +int16 CTheZones::FindIndex; + +uint16 CTheZones::NumberOfAudioZones; +int16 CTheZones::AudioZoneArray[NUMAUDIOZONES]; +uint16 CTheZones::TotalNumberOfMapZones; +uint16 CTheZones::TotalNumberOfZones; +CZone CTheZones::ZoneArray[NUMZONES]; +CZone CTheZones::MapZoneArray[NUMMAPZONES]; +uint16 CTheZones::TotalNumberOfZoneInfos; +CZoneInfo CTheZones::ZoneInfoArray[2*NUMZONES]; + +#define SWAPF(a, b) { float t; t = a; a = b; b = t; } + +inline bool IsNormalZone(int type) { return type == ZONE_DEFAULT || type == ZONE_NAVIG || type == ZONE_INFO; } + +static void +CheckZoneInfo(CZoneInfo *info) +{ + assert(info->carThreshold[0] >= 0); + assert(info->carThreshold[0] <= info->carThreshold[1]); + assert(info->carThreshold[1] <= info->carThreshold[2]); + assert(info->carThreshold[2] <= info->carThreshold[3]); + assert(info->carThreshold[3] <= info->carThreshold[4]); + assert(info->carThreshold[4] <= info->carThreshold[5]); + assert(info->carThreshold[5] <= info->copThreshold); + assert(info->copThreshold <= info->gangThreshold[0]); + assert(info->gangThreshold[0] <= info->gangThreshold[1]); + assert(info->gangThreshold[1] <= info->gangThreshold[2]); + assert(info->gangThreshold[2] <= info->gangThreshold[3]); + assert(info->gangThreshold[3] <= info->gangThreshold[4]); + assert(info->gangThreshold[4] <= info->gangThreshold[5]); + assert(info->gangThreshold[5] <= info->gangThreshold[6]); + assert(info->gangThreshold[6] <= info->gangThreshold[7]); + assert(info->gangThreshold[7] <= info->gangThreshold[8]); +} + +wchar* +CZone::GetTranslatedName(void) +{ + return TheText.Get(name); +} + +void +CTheZones::Init(void) +{ + int i; + for(i = 0; i < NUMAUDIOZONES; i++) + AudioZoneArray[i] = -1; + NumberOfAudioZones = 0; + + for(i = 0; i < NUMZONES; i++) + memset(&ZoneArray[i], 0, sizeof(CZone)); + + CZoneInfo *zonei; + int x = 1000/6; + for(i = 0; i < 2*NUMZONES; i++){ + zonei = &ZoneInfoArray[i]; + zonei->carDensity = 10; + zonei->carThreshold[0] = x; + zonei->carThreshold[1] = zonei->carThreshold[0] + x; + zonei->carThreshold[2] = zonei->carThreshold[1] + x; + zonei->carThreshold[3] = zonei->carThreshold[2] + x; + zonei->carThreshold[4] = zonei->carThreshold[3]; + zonei->carThreshold[5] = zonei->carThreshold[4]; + zonei->copThreshold = zonei->carThreshold[5] + x; + zonei->gangThreshold[0] = zonei->copThreshold; + zonei->gangThreshold[1] = zonei->gangThreshold[0]; + zonei->gangThreshold[2] = zonei->gangThreshold[1]; + zonei->gangThreshold[3] = zonei->gangThreshold[2]; + zonei->gangThreshold[4] = zonei->gangThreshold[3]; + zonei->gangThreshold[5] = zonei->gangThreshold[4]; + zonei->gangThreshold[6] = zonei->gangThreshold[5]; + zonei->gangThreshold[7] = zonei->gangThreshold[6]; + zonei->gangThreshold[8] = zonei->gangThreshold[7]; + CheckZoneInfo(zonei); + } + + TotalNumberOfZoneInfos = 1; // why 1? + TotalNumberOfZones = 1; + + m_CurrLevel = LEVEL_GENERIC; + m_pPlayersZone = &ZoneArray[0]; + + strcpy(ZoneArray[0].name, "CITYZON"); + ZoneArray[0].minx = -4000.0f; + ZoneArray[0].miny = -4000.0f; + ZoneArray[0].minz = -500.0f; + ZoneArray[0].maxx = 4000.0f; + ZoneArray[0].maxy = 4000.0f; + ZoneArray[0].maxz = 500.0f; + ZoneArray[0].level = LEVEL_GENERIC; + + for(i = 0; i < NUMMAPZONES; i++){ + memset(&MapZoneArray[i], 0, sizeof(CZone)); + MapZoneArray[i].type = ZONE_MAPZONE; + } + + TotalNumberOfMapZones = 1; + + strcpy(MapZoneArray[0].name, "THEMAP"); + MapZoneArray[0].minx = -4000.0f; + MapZoneArray[0].miny = -4000.0f; + MapZoneArray[0].minz = -500.0f; + MapZoneArray[0].maxx = 4000.0f; + MapZoneArray[0].maxy = 4000.0f; + MapZoneArray[0].maxz = 500.0f; + MapZoneArray[0].level = LEVEL_GENERIC; +} + +void +CTheZones::Update(void) +{ +#ifdef SQUEEZE_PERFORMANCE + if (CTimer::GetFrameCounter() % 5 != 0) + return; +#endif + CVector pos; + pos = FindPlayerCoors(); + m_pPlayersZone = FindSmallestZonePosition(&pos); + m_CurrLevel = GetLevelFromPosition(&pos); +} + +void +CTheZones::CreateZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level) +{ + char *p; + char tmpname[8]; + + if(minx > maxx) SWAPF(minx, maxx); + if(miny > maxy) SWAPF(miny, maxy); + if(minz > maxz) SWAPF(minz, maxz); + + // make upper case + for(p = name; *p; p++) if(islower(*p)) *p = toupper(*p); + + // add zone + strncpy(tmpname, name, 7); + tmpname[7] = '\0'; + strcpy(ZoneArray[TotalNumberOfZones].name, tmpname); + ZoneArray[TotalNumberOfZones].type = type; + ZoneArray[TotalNumberOfZones].minx = minx; + ZoneArray[TotalNumberOfZones].miny = miny; + ZoneArray[TotalNumberOfZones].minz = minz; + ZoneArray[TotalNumberOfZones].maxx = maxx; + ZoneArray[TotalNumberOfZones].maxy = maxy; + ZoneArray[TotalNumberOfZones].maxz = maxz; + ZoneArray[TotalNumberOfZones].level = level; + if(IsNormalZone(type)){ + ZoneArray[TotalNumberOfZones].zoneinfoDay = TotalNumberOfZoneInfos++; + ZoneArray[TotalNumberOfZones].zoneinfoNight = TotalNumberOfZoneInfos++; + } + TotalNumberOfZones++; +} + +void +CTheZones::CreateMapZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level) +{ + CZone *zone; + char *p; + + if(minx > maxx) SWAPF(minx, maxx); + if(miny > maxy) SWAPF(miny, maxy); + if(minz > maxz) SWAPF(minz, maxz); + + // make upper case + for(p = name; *p; p++) if(islower(*p)) *p = toupper(*p); + + // add zone + zone = &MapZoneArray[TotalNumberOfMapZones++]; + strncpy(zone->name, name, 7); + zone->name[7] = '\0'; + zone->type = type; + zone->minx = minx; + zone->miny = miny; + zone->minz = minz; + zone->maxx = maxx; + zone->maxy = maxy; + zone->maxz = maxz; + zone->level = level; +} + +void +CTheZones::PostZoneCreation(void) +{ + int i; + for(i = 1; i < TotalNumberOfZones; i++) + InsertZoneIntoZoneHierarchy(&ZoneArray[i]); + InitialiseAudioZoneArray(); +} + +void +CTheZones::InsertZoneIntoZoneHierarchy(CZone *zone) +{ + zone->child = nil; + zone->parent = nil; + zone->next = nil; + InsertZoneIntoZoneHierRecursive(zone, &ZoneArray[0]); +} + +bool +CTheZones::InsertZoneIntoZoneHierRecursive(CZone *inner, CZone *outer) +{ + int n; + CZone *child, *next, *insert; + + // return false if inner was not inserted into outer + if(outer == nil || + !ZoneIsEntirelyContainedWithinOtherZone(inner, outer)) + return false; + + // try to insert inner into children of outer + for(child = outer->child; child; child = child->next) + if(InsertZoneIntoZoneHierRecursive(inner, child)) + return true; + + // insert inner as child of outer + // count number of outer's children contained within inner + n = 0; + for(child = outer->child; child; child = child->next) + if(ZoneIsEntirelyContainedWithinOtherZone(child, inner)) + n++; + inner->next = outer->child; + inner->parent = outer; + outer->child = inner; + // move children from outer to inner + if(n){ + insert = inner; + for(child = inner->next; child; child = next){ + next = child->next; + if(ZoneIsEntirelyContainedWithinOtherZone(child,inner)){ + insert->next = child->next; + child->parent = inner; + child->next = inner->child; + inner->child = child; + }else + insert = child; + } + } + + return true; +} + +bool +CTheZones::ZoneIsEntirelyContainedWithinOtherZone(CZone *inner, CZone *outer) +{ + char tmp[100]; + + if(inner->minx < outer->minx || + inner->maxx > outer->maxx || + inner->miny < outer->miny || + inner->maxy > outer->maxy || + inner->minz < outer->minz || + inner->maxz > outer->maxz){ + CVector vmin(inner->minx, inner->miny, inner->minz); + if(PointLiesWithinZone(&vmin, outer)) + sprintf(tmp, "Overlapping zones %s and %s\n", + inner->name, outer->name); + CVector vmax(inner->maxx, inner->maxy, inner->maxz); + if(PointLiesWithinZone(&vmax, outer)) + sprintf(tmp, "Overlapping zones %s and %s\n", + inner->name, outer->name); + return false; + } + return true; +} + +bool +CTheZones::PointLiesWithinZone(const CVector *v, CZone *zone) +{ + return zone->minx <= v->x && v->x <= zone->maxx && + zone->miny <= v->y && v->y <= zone->maxy && + zone->minz <= v->z && v->z <= zone->maxz; +} + +eLevelName +CTheZones::GetLevelFromPosition(CVector const *v) +{ + int i; +// char tmp[116]; +// if(!PointLiesWithinZone(v, &MapZoneArray[0])) +// sprintf(tmp, "x = %.3f y= %.3f z = %.3f\n", v.x, v.y, v.z); + for(i = 1; i < TotalNumberOfMapZones; i++) + if(PointLiesWithinZone(v, &MapZoneArray[i])) + return MapZoneArray[i].level; + return MapZoneArray[0].level; +} + +CZone* +CTheZones::FindSmallestZonePosition(const CVector *v) +{ + CZone *best = &ZoneArray[0]; + // zone to test next + CZone *zone = ZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(v, zone)){ + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +CZone* +CTheZones::FindSmallestZonePositionType(const CVector *v, eZoneType type) +{ + CZone *best = nil; + if(ZoneArray[0].type == type) + best = &ZoneArray[0]; + // zone to test next + CZone *zone = ZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(v, zone)){ + if(zone->type == type) + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +CZone* +CTheZones::FindSmallestZonePositionILN(const CVector *v) +{ + CZone *best = nil; + if(IsNormalZone(ZoneArray[0].type)) + best = &ZoneArray[0]; + // zone to test next + CZone *zone = ZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(v, zone)){ + if(IsNormalZone(zone->type)) + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +int16 +CTheZones::FindZoneByLabelAndReturnIndex(Const char *name) +{ + char str[8]; + memset(str, 0, 8); + strncpy(str, name, 8); + for(FindIndex = 0; FindIndex < TotalNumberOfZones; FindIndex++) + if(strcmp(GetZone(FindIndex)->name, name) == 0) + return FindIndex; + return -1; +} + +CZoneInfo* +CTheZones::GetZoneInfo(const CVector *v, uint8 day) +{ + CZone *zone; + zone = FindSmallestZonePositionILN(v); + if(zone == nil) + return &ZoneInfoArray[0]; + return &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; +} + +void +CTheZones::GetZoneInfoForTimeOfDay(const CVector *pos, CZoneInfo *info) +{ + CZoneInfo *day, *night; + float d, n; + + day = GetZoneInfo(pos, 1); + night = GetZoneInfo(pos, 0); + + if(CClock::GetIsTimeInRange(8, 19)) + *info = *day; + else if(CClock::GetIsTimeInRange(22, 5)) + *info = *night; + else{ + if(CClock::GetIsTimeInRange(19, 22)){ + n = (CClock::GetHours() - 19) / 3.0f; + assert(n >= 0.0f && n <= 1.0f); + d = 1.0f - n; + }else{ + d = (CClock::GetHours() - 5) / 3.0f; + assert(d >= 0.0f && d <= 1.0f); + n = 1.0f - d; + } + info->carDensity = day->carDensity * d + night->carDensity * n; + info->carThreshold[0] = day->carThreshold[0] * d + night->carThreshold[0] * n; + info->carThreshold[1] = day->carThreshold[1] * d + night->carThreshold[1] * n; + info->carThreshold[2] = day->carThreshold[2] * d + night->carThreshold[2] * n; + info->carThreshold[3] = day->carThreshold[3] * d + night->carThreshold[3] * n; + info->carThreshold[4] = day->carThreshold[4] * d + night->carThreshold[4] * n; + info->carThreshold[5] = day->carThreshold[5] * d + night->carThreshold[5] * n; + info->copThreshold = day->copThreshold * d + night->copThreshold * n; + info->gangThreshold[0] = day->gangThreshold[0] * d + night->gangThreshold[0] * n; + info->gangThreshold[1] = day->gangThreshold[1] * d + night->gangThreshold[1] * n; + info->gangThreshold[2] = day->gangThreshold[2] * d + night->gangThreshold[2] * n; + info->gangThreshold[3] = day->gangThreshold[3] * d + night->gangThreshold[3] * n; + info->gangThreshold[4] = day->gangThreshold[4] * d + night->gangThreshold[4] * n; + info->gangThreshold[5] = day->gangThreshold[5] * d + night->gangThreshold[5] * n; + info->gangThreshold[6] = day->gangThreshold[6] * d + night->gangThreshold[6] * n; + info->gangThreshold[7] = day->gangThreshold[7] * d + night->gangThreshold[7] * n; + info->gangThreshold[8] = day->gangThreshold[8] * d + night->gangThreshold[8] * n; + + info->pedDensity = day->pedDensity * d + night->pedDensity * n; + info->copDensity = day->copDensity * d + night->copDensity * n; + info->gangDensity[0] = day->gangDensity[0] * d + night->gangDensity[0] * n; + info->gangDensity[1] = day->gangDensity[1] * d + night->gangDensity[1] * n; + info->gangDensity[2] = day->gangDensity[2] * d + night->gangDensity[2] * n; + info->gangDensity[3] = day->gangDensity[3] * d + night->gangDensity[3] * n; + info->gangDensity[4] = day->gangDensity[4] * d + night->gangDensity[4] * n; + info->gangDensity[5] = day->gangDensity[5] * d + night->gangDensity[5] * n; + info->gangDensity[6] = day->gangDensity[6] * d + night->gangDensity[6] * n; + info->gangDensity[7] = day->gangDensity[7] * d + night->gangDensity[7] * n; + info->gangDensity[8] = day->gangDensity[8] * d + night->gangDensity[8] * n; + } + if(CClock::GetIsTimeInRange(5, 19)) + info->pedGroup = day->pedGroup; + else + info->pedGroup = night->pedGroup; + + CheckZoneInfo(info); +} + +void +CTheZones::SetZoneCarInfo(uint16 zoneid, uint8 day, int16 carDensity, + int16 gang0Num, int16 gang1Num, int16 gang2Num, + int16 gang3Num, int16 gang4Num, int16 gang5Num, + int16 gang6Num, int16 gang7Num, int16 gang8Num, + int16 copNum, + int16 car0Num, int16 car1Num, int16 car2Num, + int16 car3Num, int16 car4Num, int16 car5Num) +{ + CZone *zone; + CZoneInfo *info; + zone = GetZone(zoneid); + info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; + + CheckZoneInfo(info); + + if(carDensity != -1) info->carDensity = carDensity; + int16 oldCar1Num = info->carThreshold[1] - info->carThreshold[0]; + int16 oldCar2Num = info->carThreshold[2] - info->carThreshold[1]; + int16 oldCar3Num = info->carThreshold[3] - info->carThreshold[2]; + int16 oldCar4Num = info->carThreshold[4] - info->carThreshold[3]; + int16 oldCar5Num = info->carThreshold[5] - info->carThreshold[4]; + int16 oldCopNum = info->copThreshold - info->carThreshold[5]; + int16 oldGang0Num = info->gangThreshold[0] - info->copThreshold; + int16 oldGang1Num = info->gangThreshold[1] - info->gangThreshold[0]; + int16 oldGang2Num = info->gangThreshold[2] - info->gangThreshold[1]; + int16 oldGang3Num = info->gangThreshold[3] - info->gangThreshold[2]; + int16 oldGang4Num = info->gangThreshold[4] - info->gangThreshold[3]; + int16 oldGang5Num = info->gangThreshold[5] - info->gangThreshold[4]; + int16 oldGang6Num = info->gangThreshold[6] - info->gangThreshold[5]; + int16 oldGang7Num = info->gangThreshold[7] - info->gangThreshold[6]; + int16 oldGang8Num = info->gangThreshold[8] - info->gangThreshold[7]; + + if(car0Num != -1) info->carThreshold[0] = car0Num; + if(car1Num != -1) info->carThreshold[1] = info->carThreshold[0] + car1Num; + else info->carThreshold[1] = info->carThreshold[0] + oldCar1Num; + if(car2Num != -1) info->carThreshold[2] = info->carThreshold[1] + car2Num; + else info->carThreshold[2] = info->carThreshold[1] + oldCar2Num; + if(car3Num != -1) info->carThreshold[3] = info->carThreshold[2] + car3Num; + else info->carThreshold[3] = info->carThreshold[2] + oldCar3Num; + if(car4Num != -1) info->carThreshold[4] = info->carThreshold[3] + car4Num; + else info->carThreshold[4] = info->carThreshold[3] + oldCar4Num; + if(car5Num != -1) info->carThreshold[5] = info->carThreshold[4] + car5Num; + else info->carThreshold[5] = info->carThreshold[4] + oldCar5Num; + if(copNum != -1) info->copThreshold = info->carThreshold[5] + copNum; + else info->copThreshold = info->carThreshold[5] + oldCopNum; + if(gang0Num != -1) info->gangThreshold[0] = info->copThreshold + gang0Num; + else info->gangThreshold[0] = info->copThreshold + oldGang0Num; + if(gang1Num != -1) info->gangThreshold[1] = info->gangThreshold[0] + gang1Num; + else info->gangThreshold[1] = info->gangThreshold[0] + oldGang1Num; + if(gang2Num != -1) info->gangThreshold[2] = info->gangThreshold[1] + gang2Num; + else info->gangThreshold[2] = info->gangThreshold[1] + oldGang2Num; + if(gang3Num != -1) info->gangThreshold[3] = info->gangThreshold[2] + gang3Num; + else info->gangThreshold[3] = info->gangThreshold[2] + oldGang3Num; + if(gang4Num != -1) info->gangThreshold[4] = info->gangThreshold[3] + gang4Num; + else info->gangThreshold[4] = info->gangThreshold[3] + oldGang4Num; + if(gang5Num != -1) info->gangThreshold[5] = info->gangThreshold[4] + gang5Num; + else info->gangThreshold[5] = info->gangThreshold[4] + oldGang5Num; + if(gang6Num != -1) info->gangThreshold[6] = info->gangThreshold[5] + gang6Num; + else info->gangThreshold[6] = info->gangThreshold[5] + oldGang6Num; + if(gang7Num != -1) info->gangThreshold[7] = info->gangThreshold[6] + gang7Num; + else info->gangThreshold[7] = info->gangThreshold[6] + oldGang7Num; + if(gang8Num != -1) info->gangThreshold[8] = info->gangThreshold[7] + gang8Num; + else info->gangThreshold[8] = info->gangThreshold[7] + oldGang8Num; + + CheckZoneInfo(info); +} + +void +CTheZones::SetZonePedInfo(uint16 zoneid, uint8 day, int16 pedDensity, + int16 gang0Density, int16 gang1Density, int16 gang2Density, int16 gang3Density, + int16 gang4Density, int16 gang5Density, int16 gang6Density, int16 gang7Density, + int16 gang8Density, int16 copDensity) +{ + CZone *zone; + CZoneInfo *info; + zone = GetZone(zoneid); + info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; + if(pedDensity != -1) info->pedDensity = pedDensity; + if(copDensity != -1) info->copDensity = copDensity; + if(gang0Density != -1) info->gangDensity[0] = gang0Density; + if(gang1Density != -1) info->gangDensity[1] = gang1Density; + if(gang2Density != -1) info->gangDensity[2] = gang2Density; + if(gang3Density != -1) info->gangDensity[3] = gang3Density; + if(gang4Density != -1) info->gangDensity[4] = gang4Density; + if(gang5Density != -1) info->gangDensity[5] = gang5Density; + if(gang6Density != -1) info->gangDensity[6] = gang6Density; + if(gang7Density != -1) info->gangDensity[7] = gang7Density; + if(gang8Density != -1) info->gangDensity[8] = gang8Density; +} + +void +CTheZones::SetCarDensity(uint16 zoneid, uint8 day, uint16 cardensity) +{ + CZone *zone; + zone = GetZone(zoneid); + if(IsNormalZone(zone->type)) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].carDensity = cardensity; +} + +void +CTheZones::SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity) +{ + CZone *zone; + zone = GetZone(zoneid); + if(IsNormalZone(zone->type)) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedDensity = peddensity; +} + +void +CTheZones::SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup) +{ + CZone *zone; + zone = GetZone(zoneid); + if(IsNormalZone(zone->type)) + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedGroup = pedgroup; +} + +int16 +CTheZones::FindAudioZone(CVector *pos) +{ + int i; + + for(i = 0; i < NumberOfAudioZones; i++) + if(PointLiesWithinZone(pos, GetAudioZone(i))) + return i; + return -1; +} + +eLevelName +CTheZones::FindZoneForPoint(const CVector &pos) +{ + if(PointLiesWithinZone(&pos, GetZone(FindZoneByLabelAndReturnIndex("IND_ZON")))) + return LEVEL_INDUSTRIAL; + if(PointLiesWithinZone(&pos, GetZone(FindZoneByLabelAndReturnIndex("COM_ZON")))) + return LEVEL_COMMERCIAL; + if(PointLiesWithinZone(&pos, GetZone(FindZoneByLabelAndReturnIndex("SUB_ZON")))) + return LEVEL_SUBURBAN; + return LEVEL_GENERIC; +} + +void +CTheZones::AddZoneToAudioZoneArray(CZone *zone) +{ + int i, z; + + if(zone->type != ZONE_DEFAULT) + return; + + /* This is a bit stupid */ + z = -1; + for(i = 0; i < NUMZONES; i++) + if(&ZoneArray[i] == zone) + z = i; + AudioZoneArray[NumberOfAudioZones++] = z; +} + +void +CTheZones::InitialiseAudioZoneArray(void) +{ + bool gonext; + CZone *zone; + + gonext = false; + zone = &ZoneArray[0]; + // Go deep first, + // set gonext when backing up a level to visit the next child + while(zone) + if(gonext){ + AddZoneToAudioZoneArray(zone); + if(zone->next){ + gonext = false; + zone = zone->next; + }else + zone = zone->parent; + }else if(zone->child) + zone = zone->child; + else{ + AddZoneToAudioZoneArray(zone); + if(zone->next) + zone = zone->next; + else{ + gonext = true; + zone = zone->parent; + } + } +} + +#ifdef COMPATIBLE_SAVES +static inline void +SaveOneZone(CZone &zone, uint8 *&buffer) +{ + memcpy(buffer, zone.name, sizeof(zone.name)); + SkipSaveBuf(buffer, sizeof(zone.name)); + WriteSaveBuf(buffer, zone.minx); + WriteSaveBuf(buffer, zone.miny); + WriteSaveBuf(buffer, zone.minz); + WriteSaveBuf(buffer, zone.maxx); + WriteSaveBuf(buffer, zone.maxy); + WriteSaveBuf(buffer, zone.maxz); + WriteSaveBuf(buffer, zone.type); + WriteSaveBuf(buffer, zone.level); + WriteSaveBuf(buffer, zone.zoneinfoDay); + WriteSaveBuf(buffer, zone.zoneinfoNight); + WriteSaveBuf(buffer, (int32)CTheZones::GetIndexForZonePointer(zone.child)); + WriteSaveBuf(buffer, (int32)CTheZones::GetIndexForZonePointer(zone.parent)); + WriteSaveBuf(buffer, (int32)CTheZones::GetIndexForZonePointer(zone.next)); +} +#endif + +void +CTheZones::SaveAllZones(uint8 *buffer, uint32 *size) +{ + INITSAVEBUF + int i; + + *size = SAVE_HEADER_SIZE + + sizeof(int32) // GetIndexForZonePointer + + sizeof(m_CurrLevel) + sizeof(FindIndex) + + sizeof(int16) // padding + + ZONEARRAY_SAVE_SIZE + sizeof(ZoneInfoArray) + + sizeof(TotalNumberOfZones) + sizeof(TotalNumberOfZoneInfos) + + MAPZONEARRAY_SAVE_SIZE + sizeof(AudioZoneArray) + + sizeof(TotalNumberOfMapZones) + sizeof(NumberOfAudioZones); + + WriteSaveHeader(buffer, 'Z', 'N', 'S', '\0', *size - SAVE_HEADER_SIZE); + + WriteSaveBuf(buffer, (int32)GetIndexForZonePointer(m_pPlayersZone)); + WriteSaveBuf(buffer, m_CurrLevel); + WriteSaveBuf(buffer, FindIndex); + WriteSaveBuf(buffer, (int16)0); // padding + + for(i = 0; i < ARRAY_SIZE(ZoneArray); i++){ +#ifdef COMPATIBLE_SAVES + SaveOneZone(ZoneArray[i], buffer); +#else + CZone *zone = WriteSaveBuf(buffer, ZoneArray[i]); + zone->child = (CZone*)GetIndexForZonePointer(ZoneArray[i].child); + zone->parent = (CZone*)GetIndexForZonePointer(ZoneArray[i].parent); + zone->next = (CZone*)GetIndexForZonePointer(ZoneArray[i].next); +#endif + } + + for(i = 0; i < ARRAY_SIZE(ZoneInfoArray); i++) + WriteSaveBuf(buffer, ZoneInfoArray[i]); + + WriteSaveBuf(buffer, TotalNumberOfZones); + WriteSaveBuf(buffer, TotalNumberOfZoneInfos); + + for(i = 0; i < ARRAY_SIZE(MapZoneArray); i++) { +#ifndef COMPATIBLE_SAVES + CZone* zone = WriteSaveBuf(buffer, MapZoneArray[i]); +#endif + + /* + The call of GetIndexForZonePointer is wrong, as it is + meant for a different array, but the game doesn't brake + if those fields are nil. Let's make sure they are. + */ + assert(MapZoneArray[i].child == nil); + assert(MapZoneArray[i].parent == nil); + assert(MapZoneArray[i].next == nil); +#ifndef COMPATIBLE_SAVES + zone->child = (CZone*)GetIndexForZonePointer(MapZoneArray[i].child); + zone->parent = (CZone*)GetIndexForZonePointer(MapZoneArray[i].parent); + zone->next = (CZone*)GetIndexForZonePointer(MapZoneArray[i].next); +#else + SaveOneZone(MapZoneArray[i], buffer); +#endif + } + + for(i = 0; i < ARRAY_SIZE(AudioZoneArray); i++) + WriteSaveBuf(buffer, AudioZoneArray[i]); + + WriteSaveBuf(buffer, TotalNumberOfMapZones); + WriteSaveBuf(buffer, NumberOfAudioZones); + + VALIDATESAVEBUF(*size) +} + +#ifdef COMPATIBLE_SAVES +static inline void +LoadOneZone(CZone &zone, uint8 *&buffer) +{ + memcpy(zone.name, buffer, sizeof(zone.name)); + SkipSaveBuf(buffer, sizeof(zone.name)); + ReadSaveBuf(&zone.minx, buffer); + ReadSaveBuf(&zone.miny, buffer); + ReadSaveBuf(&zone.minz, buffer); + ReadSaveBuf(&zone.maxx, buffer); + ReadSaveBuf(&zone.maxy, buffer); + ReadSaveBuf(&zone.maxz, buffer); + ReadSaveBuf(&zone.type, buffer); + ReadSaveBuf(&zone.level, buffer); + ReadSaveBuf(&zone.zoneinfoDay, buffer); + ReadSaveBuf(&zone.zoneinfoNight, buffer); + int32 tmp; + ReadSaveBuf(&tmp, buffer); + zone.child = CTheZones::GetPointerForZoneIndex(tmp); + ReadSaveBuf(&tmp, buffer); + zone.parent = CTheZones::GetPointerForZoneIndex(tmp); + ReadSaveBuf(&tmp, buffer); + zone.next = CTheZones::GetPointerForZoneIndex(tmp); +} +#endif + +void +CTheZones::LoadAllZones(uint8 *buffer, uint32 size) +{ + INITSAVEBUF + int32 i; + + CheckSaveHeader(buffer, 'Z', 'N', 'S', '\0', size - SAVE_HEADER_SIZE); + + ReadSaveBuf(&i, buffer); + m_pPlayersZone = GetPointerForZoneIndex(i); + ReadSaveBuf(&m_CurrLevel, buffer); + ReadSaveBuf(&FindIndex, buffer); + SkipSaveBuf(buffer, 2); + + for(i = 0; i < ARRAY_SIZE(ZoneArray); i++){ +#ifdef COMPATIBLE_SAVES + LoadOneZone(ZoneArray[i], buffer); +#else + ReadSaveBuf(&ZoneArray[i], buffer); + + ZoneArray[i].child = GetPointerForZoneIndex((uintptr)ZoneArray[i].child); + ZoneArray[i].parent = GetPointerForZoneIndex((uintptr)ZoneArray[i].parent); + ZoneArray[i].next = GetPointerForZoneIndex((uintptr)ZoneArray[i].next); +#endif + } + + for(i = 0; i < ARRAY_SIZE(ZoneInfoArray); i++) + ReadSaveBuf(&ZoneInfoArray[i], buffer); + + ReadSaveBuf(&TotalNumberOfZones, buffer); + ReadSaveBuf(&TotalNumberOfZoneInfos, buffer); + + for(i = 0; i < ARRAY_SIZE(MapZoneArray); i++){ +#ifdef COMPATIBLE_SAVES + LoadOneZone(MapZoneArray[i], buffer); +#else + ReadSaveBuf(&MapZoneArray[i], buffer); + + /* + The call of GetPointerForZoneIndex is wrong, as it is + meant for a different array, but the game doesn't brake + if save data stored is -1. + */ + MapZoneArray[i].child = GetPointerForZoneIndex((uintptr)MapZoneArray[i].child); + MapZoneArray[i].parent = GetPointerForZoneIndex((uintptr)MapZoneArray[i].parent); + MapZoneArray[i].next = GetPointerForZoneIndex((uintptr)MapZoneArray[i].next); +#endif + assert(MapZoneArray[i].child == nil); + assert(MapZoneArray[i].parent == nil); + assert(MapZoneArray[i].next == nil); + } + + for(i = 0; i < ARRAY_SIZE(AudioZoneArray); i++) + ReadSaveBuf(&AudioZoneArray[i], buffer); + + ReadSaveBuf(&TotalNumberOfMapZones, buffer); + ReadSaveBuf(&NumberOfAudioZones, buffer); + + VALIDATESAVEBUF(size) +} diff --git a/src/core/Zones.h b/src/core/Zones.h new file mode 100644 index 0000000..aa0466e --- /dev/null +++ b/src/core/Zones.h @@ -0,0 +1,114 @@ +#pragma once + +#include "Game.h" +#include "Gangs.h" + +enum eZoneType +{ + ZONE_DEFAULT, + ZONE_NAVIG, + ZONE_INFO, + ZONE_MAPZONE, +}; + +class CZone +{ +public: + char name[8]; + float minx; + float miny; + float minz; + float maxx; + float maxy; + float maxz; + eZoneType type; + eLevelName level; + int16 zoneinfoDay; + int16 zoneinfoNight; + CZone *child; + CZone *parent; + CZone *next; + + wchar *GetTranslatedName(void); +}; + +class CZoneInfo +{ +public: + // Car data + int16 carDensity; + int16 carThreshold[6]; + int16 copThreshold; + int16 gangThreshold[NUM_GANGS]; + + // Ped data + uint16 pedDensity; + uint16 copDensity; + uint16 gangDensity[NUM_GANGS]; + uint16 pedGroup; +}; + + +class CTheZones +{ + static CZone *m_pPlayersZone; + static int16 FindIndex; + + static uint16 NumberOfAudioZones; + static int16 AudioZoneArray[NUMAUDIOZONES]; + static uint16 TotalNumberOfMapZones; + static uint16 TotalNumberOfZones; + static CZone ZoneArray[NUMZONES]; + static CZone MapZoneArray[NUMMAPZONES]; + static uint16 TotalNumberOfZoneInfos; + static CZoneInfo ZoneInfoArray[2*NUMZONES]; +public: + static eLevelName m_CurrLevel; + + static void Init(void); + static void Update(void); + static void CreateZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level); + static void CreateMapZone(char *name, eZoneType type, + float minx, float miny, float minz, + float maxx, float maxy, float maxz, + eLevelName level); + static CZone *GetZone(uint16 i) { return &ZoneArray[i]; } + static CZone *GetAudioZone(uint16 i) { return &ZoneArray[AudioZoneArray[i]]; } + static void PostZoneCreation(void); + static void InsertZoneIntoZoneHierarchy(CZone *zone); + static bool InsertZoneIntoZoneHierRecursive(CZone *z1, CZone *z2); + static bool ZoneIsEntirelyContainedWithinOtherZone(CZone *z1, CZone *z2); + static bool PointLiesWithinZone(const CVector *v, CZone *zone); + static eLevelName GetLevelFromPosition(const CVector *v); + static CZone *FindSmallestZonePosition(const CVector *v); + static CZone *FindSmallestZonePositionType(const CVector *v, eZoneType type); + static CZone *FindSmallestZonePositionILN(const CVector *v); + static int16 FindZoneByLabelAndReturnIndex(Const char *name); + static CZoneInfo *GetZoneInfo(const CVector *v, uint8 day); + static void GetZoneInfoForTimeOfDay(const CVector *pos, CZoneInfo *info); + static void SetZoneCarInfo(uint16 zoneid, uint8 day, int16 carDensity, + int16 gang0Num, int16 gang1Num, int16 gang2Num, + int16 gang3Num, int16 gang4Num, int16 gang5Num, + int16 gang6Num, int16 gang7Num, int16 gang8Num, + int16 copNum, + int16 car0Num, int16 car1Num, int16 car2Num, + int16 car3Num, int16 car4Num, int16 car5Num); + static void SetZonePedInfo(uint16 zoneid, uint8 day, int16 pedDensity, + int16 gang0Density, int16 gang1Density, int16 gang2Density, int16 gang3Density, + int16 gang4Density, int16 gang5Density, int16 gang6Density, int16 gang7Density, + int16 gang8Density, int16 copDensity); + static void SetCarDensity(uint16 zoneid, uint8 day, uint16 cardensity); + static void SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity); + static void SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup); + static int16 FindAudioZone(CVector *pos); + static eLevelName FindZoneForPoint(const CVector &pos); + static CZone *GetPointerForZoneIndex(ssize_t i) { return i == -1 ? nil : &ZoneArray[i]; } + static ssize_t GetIndexForZonePointer(CZone *zone) { return zone == nil ? -1 : zone - ZoneArray; } + static void AddZoneToAudioZoneArray(CZone *zone); + static void InitialiseAudioZoneArray(void); + static void SaveAllZones(uint8 *buffer, uint32 *length); + static void LoadAllZones(uint8 *buffer, uint32 length); +}; diff --git a/src/core/common.h b/src/core/common.h new file mode 100644 index 0000000..0d0528b --- /dev/null +++ b/src/core/common.h @@ -0,0 +1,401 @@ +#pragma once + +#define _CRT_SECURE_NO_WARNINGS +#define _USE_MATH_DEFINES +#pragma warning(disable: 4244) // int to float +#pragma warning(disable: 4800) // int to bool +#pragma warning(disable: 4838) // narrowing conversion +#pragma warning(disable: 4996) // POSIX names + +#ifdef __MWERKS__ +#define __STDC_LIMIT_MACROS // so we get UINT32_MAX etc +#endif + +#ifdef __SWITCH__ +#include +#endif + +#include +#include +#include + +#ifdef __MWERKS__ +#define AUDIO_MSS +#define RWLIBS // codewarrior doesn't support project level defines - so not even this is enough, but still catches most ifdefs +#endif + +#if !defined RW_D3D9 && defined LIBRW +#undef WITHD3D +#undef WITHDINPUT +#endif + +#if (defined WITHD3D && !defined LIBRW) +#define WITHWINDOWS +#endif + +#if defined _WIN32 && defined WITHWINDOWS && !defined _INC_WINDOWS +#include +#endif + +#ifdef WITHD3D + #ifdef LIBRW + #define WITH_D3D // librw includes d3d9 itself via this right now + #else + #ifndef USE_D3D9 + #include + #else + #include + #endif + #endif +#endif + +#ifdef WITHDINPUT +#define DIRECTINPUT_VERSION 0x0800 +#include +#endif + +#include +#include + +// gotta put this somewhere +#ifdef LIBRW +#define STREAMPOS(str) ((str)->tell()) +#define STREAMFILE(str) (((rw::StreamFile*)(str))->file) +#define HIERNODEINFO(hier) ((hier)->nodeInfo) +#define HIERNODEID(hier, i) ((hier)->nodeInfo[i].id) +#define HANIMFRAME(anim, i) ((RwUInt8*)(anim)->keyframes + (i)*(anim)->interpInfo->animKeyFrameSize) +#else +#define RWHALFPIXEL // always d3d +#define STREAMPOS(str) ((str)->Type.memory.position) +#define STREAMFILE(str) ((str)->Type.file.fpFile) +#define HIERNODEINFO(hier) ((hier)->pNodeInfo) +#define HIERNODEID(hier, i) ((hier)->pNodeInfo[i].nodeID) +#define HANIMFRAME(anim, i) ((RwUInt8*)(anim)->pFrames + (i)*(anim)->interpInfo->keyFrameSize) +#define RpHAnimStdInterpFrame RpHAnimStdKeyFrame +#endif + +#ifdef RWHALFPIXEL +#define HALFPX (0.5f) +#else +#define HALFPX (0.0f) +#endif + +#define rwVENDORID_ROCKSTAR 0x0253F2 + +#define Max(a,b) ((a) > (b) ? (a) : (b)) +#define Min(a,b) ((a) < (b) ? (a) : (b)) + +// Use this to add const that wasn't there in the original code +#define Const const + +typedef uint8_t uint8; +typedef int8_t int8; +typedef uint16_t uint16; +typedef int16_t int16; +#ifndef __MWERKS__ +typedef uint32_t uint32; +typedef int32_t int32; +#else +typedef unsigned int uint32; +typedef int int32; +#endif +typedef uintptr_t uintptr; +typedef intptr_t intptr; +typedef uint64_t uint64; +typedef int64_t int64; +// hardcode ucs-2 +typedef uint16_t wchar; + +typedef uint8 bool8; +typedef uint16 bool16; +typedef uint32 bool32; + +#if defined(_MSC_VER) || defined (__MWERKS__) +typedef ptrdiff_t ssize_t; +#endif + +#ifndef nil +#define nil NULL +#endif + +#include "config.h" + +#ifdef PED_SKIN +#include +#include +#endif + +#ifdef __GNUC__ +#define TYPEALIGN(n) __attribute__ ((aligned (n))) +#else +#ifdef _MSC_VER +#define TYPEALIGN(n) __declspec(align(n)) +#else +#define TYPEALIGN(n) // unknown compiler...ignore +#endif +#endif + +#define ALIGNPTR(p) (void*)((((uintptr)(void*)p) + sizeof(void*)-1) & ~(sizeof(void*)-1)) + +// PDP-10 like byte functions +#define MASK(p, s) (((1<<(s))-1) << (p)) +inline uint32 dpb(uint32 b, uint32 p, uint32 s, uint32 w) +{ + uint32 m = MASK(p,s); + return (w & ~m) | ((b<>p & (1<r == right.r && this->g == right.g && this->b == right.b && this->a == right.a; + } + + bool operator !=(const CRGBA &right) + { + return !(*this == right); + } + + CRGBA &operator =(const CRGBA &right) + { + this->r = right.r; + this->g = right.g; + this->b = right.b; + this->a = right.a; + return *this; + } +#ifdef RWCORE_H + operator RwRGBA &(void) { + return rwRGBA; + } + + operator RwRGBA *(void) { + return &rwRGBA; + } + + operator RwRGBA (void) const { + return rwRGBA; + } + + CRGBA &operator =(const RwRGBA &right) + { + this->r = right.red; + this->g = right.green; + this->b = right.blue; + this->a = right.alpha; + return *this; + } +#endif +}; + +#if (defined(_MSC_VER)) +extern int strcasecmp(const char *str1, const char *str2); +#endif + +extern wchar *AllocUnicode(const char*src); + +#define Clamp(v, low, high) ((v)<(low) ? (low) : (v)>(high) ? (high) : (v)) + +#define Clamp2(v, center, radius) ((v) > (center) ? Min(v, center + radius) : Max(v, center - radius)) + +inline float sq(float x) { return x*x; } +#define SQR(x) ((x) * (x)) + +#ifdef __MWERKS__ +#define M_E 2.71828182845904523536 // e +#define M_LOG2E 1.44269504088896340736 // log2(e) +#define M_LOG10E 0.434294481903251827651 // log10(e) +#define M_LN2 0.693147180559945309417 // ln(2) +#define M_LN10 2.30258509299404568402 // ln(10) +#define M_PI 3.14159265358979323846 // pi +#define M_PI_2 1.57079632679489661923 // pi/2 +#define M_PI_4 0.785398163397448309616 // pi/4 +#define M_1_PI 0.318309886183790671538 // 1/pi +#define M_2_PI 0.636619772367581343076 // 2/pi +#define M_2_SQRTPI 1.12837916709551257390 // 2/sqrt(pi) +#define M_SQRT2 1.41421356237309504880 // sqrt(2) +#define M_SQRT1_2 0.707106781186547524401 // 1/sqrt(2) +#endif + +#define PI (float)M_PI +#define TWOPI (PI*2) +#define HALFPI (PI/2) +#define DEGTORAD(x) ((x) * PI / 180.0f) +#define RADTODEG(x) ((x) * 180.0f / PI) + +#ifdef USE_PS2_RAND +#define MYRAND_MAX 65535 +#else +#define MYRAND_MAX 32767 +#endif + +int myrand(void); +void mysrand(unsigned int seed); + +void re3_debug(const char *format, ...); +void re3_trace(const char *filename, unsigned int lineno, const char *func, const char *format, ...); +void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func); +void re3_usererror(const char *format, ...); + +#define DEBUGBREAK() __debugbreak(); + +// Switch to enable development messages. +#if 1 +#define DEV(f, ...) +#else +#define DEV(f, ...) re3_debug("[DEV]: " f, ## __VA_ARGS__) +#endif + +#ifdef __MWERKS__ +void debug(char *f, ...); +void Error(char *f, ...); +__inline__ void TRACE(char *f, ...) { } // this is re3 only, and so the function needs to be inline - this way no call actually gets placed +// USERERROR only gets used in oal builds ... once +#else +#define debug(f, ...) re3_debug("[DBG]: " f, ## __VA_ARGS__) +#define Error(f, ...) re3_debug("[ERROR]: " f, ## __VA_ARGS__) +#ifndef MASTER +#define TRACE(f, ...) re3_trace(__FILE__, __LINE__, __FUNCTION__, f, ## __VA_ARGS__) +#define USERERROR(f, ...) re3_usererror(f, ## __VA_ARGS__) +#else +#define TRACE(f, ...) +#define USERERROR(f, ...) +#endif +#endif + +#ifndef MASTER +#define assert(_Expression) (void)( (!!(_Expression)) || (re3_assert(#_Expression, __FILE__, __LINE__, __FUNCTION__), 0) ) +#else +#define assert(_Expression) (_Expression) +#endif +#define ASSERT assert + +#ifdef __MWERKS__ +#define static_assert(bool_constexpr, message) +#endif + +#define _TODO(x) +#define _TODOCONST(x) (x) + +#ifdef CHECK_STRUCT_SIZES +template struct check_size { + static_assert(s == t, "Invalid structure size"); +}; +#define VALIDATE_SIZE(struc, size) check_size struc ## Check +#else +#define VALIDATE_SIZE(struc, size) +#endif +#define VALIDATE_OFFSET(struc, member, offset) static_assert(offsetof(struc, member) == offset, "The offset of " #member " in " #struc " is not " #offset "...") + +#define PERCENT(x, p) ((float(x) * (float(p) / 100.0f))) +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#define BIT(num) (1<<(num)) + +#define ABS(a) (((a) < 0) ? (-(a)) : (a)) +#define norm(value, min, max) (((value) < (min)) ? 0 : (((value) > (max)) ? 1 : (((value) - (min)) / ((max) - (min))))) +#define lerp(norm, min, max) ( (norm) * ((max) - (min)) + (min) ) + +#define STRINGIFY(x) #x +#define STR(x) STRINGIFY(x) +#define CONCAT_(x,y) x##y +#define CONCAT(x,y) CONCAT_(x,y) diff --git a/src/core/config.h b/src/core/config.h new file mode 100644 index 0000000..9f1981b --- /dev/null +++ b/src/core/config.h @@ -0,0 +1,498 @@ +#pragma once + +// disables (most) stuff that wasn't in original gta3.exe +#ifdef __MWERKS__ +#define VANILLA_DEFINES +#endif + +enum Config { + NUMPLAYERS = 1, // 4 on PS2 + + NUMCDIMAGES = 12, // gta3.img duplicates (not used on PC) + MAX_CDIMAGES = 8, // additional cdimages + MAX_CDCHANNELS = 5, + + MODELINFOSIZE = 5500, // 3150 on PS2 +#ifdef VANILLA_DEFINES + TXDSTORESIZE = 850, +#else + TXDSTORESIZE = 1024, // for Xbox map +#endif + EXTRADIRSIZE = 128, + CUTSCENEDIRSIZE = 512, + + SIMPLEMODELSIZE = 5000, // 2910 on PS2 + MLOMODELSIZE = 1, + MLOINSTANCESIZE = 1, + TIMEMODELSIZE = 30, + CLUMPMODELSIZE = 5, + PEDMODELSIZE = 90, + VEHICLEMODELSIZE = 120, // 70 on PS2 + XTRACOMPSMODELSIZE = 2, + TWODFXSIZE = 2000, // 1210 on PS2 + + MAXVEHICLESLOADED = 50, // 70 on mobile + + NUMOBJECTINFO = 168, // object.dat + + // Pool sizes + NUMPTRNODES = 30000, // 26000 on PS2 + NUMENTRYINFOS = 5400, // 3200 on PS2 + NUMPEDS = 140, // 90 on PS2 + NUMVEHICLES = 110, // 70 on PS2 + NUMBUILDINGS = 5500, // 4915 on PS2 + NUMTREADABLES = 1214, + NUMOBJECTS = 450, + NUMDUMMIES = 2802, // 2368 on PS2 + NUMAUDIOSCRIPTOBJECTS = 256, + NUMCUTSCENEOBJECTS = 50, + + NUMANIMBLOCKS = 2, + NUMANIMATIONS = 250, + + NUMTEMPOBJECTS = 30, + + // Path data + NUM_PATHNODES = 4930, + NUM_CARPATHLINKS = 2076, + NUM_MAPOBJECTS = 1250, + NUM_PATHCONNECTIONS = 10260, + + // Link list lengths + NUMALPHALIST = 20, + NUMALPHAENTITYLIST = 150, + NUMCOLCACHELINKS = 200, + NUMREFERENCES = 800, + + // Zones + NUMAUDIOZONES = 36, + NUMZONES = 50, + NUMMAPZONES = 25, + + // Cull zones + NUMCULLZONES = 512, + NUMATTRIBZONES = 288, + NUMZONEINDICES = 55000, + + PATHNODESIZE = 4500, + + NUMWEATHERS = 4, + NUMHOURS = 24, + + NUMEXTRADIRECTIONALS = 4, + NUMANTENNAS = 8, + NUMCORONAS = 56, + NUMPOINTLIGHTS = 32, + NUM3DMARKERS = 32, + NUMBRIGHTLIGHTS = 32, + NUMSHINYTEXTS = 32, + NUMMONEYMESSAGES = 16, + NUMPICKUPMESSAGES = 16, + NUMBULLETTRACES = 16, + NUMMBLURSTREAKS = 4, + NUMSKIDMARKS = 32, + + NUMONSCREENTIMERENTRIES = 1, + NUMRADARBLIPS = 32, + NUMGENERALPICKUPS = 320, + NUMSCRIPTEDPICKUPS = 16, + NUMPICKUPS = NUMGENERALPICKUPS + NUMSCRIPTEDPICKUPS, + NUMCOLLECTEDPICKUPS = 20, + NUMPACMANPICKUPS = 256, + NUMEVENTS = 64, + + NUM_CARGENS = 160, + + NUM_PATH_NODES_IN_AUTOPILOT = 8, + + NUM_ACCIDENTS = 20, + NUM_FIRES = 40, + NUM_GARAGES = 32, + NUM_PROJECTILES = 32, + + NUM_GLASSPANES = 45, + NUM_GLASSENTITIES = 32, + NUM_WATERCANNONS = 3, + + NUMPEDROUTES = 200, + NUMPHONES = 50, + NUMPEDGROUPS = 31, + NUMMODELSPERPEDGROUP = 8, + NUMSHOTINFOS = 100, + + NUMROADBLOCKS = 600, + + NUMVISIBLEENTITIES = 2000, + NUMINVISIBLEENTITIES = 150, + + NUM_AUDIOENTITY_EVENTS = 4, + NUM_PED_COMMENTS_SLOTS = 20, + + NUM_SOUND_QUEUES = 2, + NUM_AUDIOENTITIES = 200, + + NUM_SCRIPT_MAX_ENTITIES = 40, + + NUM_GARAGE_STORED_CARS = 6, + + NUM_CRANES = 8, + + NUM_EXPLOSIONS = 48, +}; + +// We don't expect to compile for PS2 or Xbox +// but it might be interesting for documentation purposes +#define GTA_PC +//#define GTA_PS2 +//#define GTA_XBOX + +// Version defines +#define GTA3_PS2_140 300 +#define GTA3_PS2_160 301 +#define GTA3_PC_10 310 +#define GTA3_PC_11 311 +#define GTA3_PC_STEAM 312 +// TODO? maybe something for xbox or android? + +#define GTA_VERSION GTA3_PC_11 + +// Enable configuration for handheld console ports +#if defined(__SWITCH__) || defined(PSP2) + #define GTA_HANDHELD +#endif + +#if defined GTA_PS2 +# define GTA_PS2_STUFF +# define RANDOMSPLASH +# define USE_CUSTOM_ALLOCATOR +# define VU_COLLISION +# define ANIM_COMPRESSION +# define PS2_MENU +#elif defined GTA_PC +# define EXTERNAL_3D_SOUND +# define AUDIO_REFLECTIONS +# ifndef GTA_HANDHELD +# define PC_PLAYER_CONTROLS // mouse player/cam mode +# endif +# define GTA_REPLAY +# define GTA_SCENE_EDIT +# define PC_MENU +#elif defined GTA_XBOX +#endif + +// This is enabled for all released games. +// any debug stuff that isn't left in any game is not in FINAL +//#define FINAL + +// This is enabled for all released games except mobile +// any debug stuff that is only left in mobile, is not in MASTER +//#define MASTER + +// once and for all: +// pc: FINAL & MASTER +// mobile: FINAL + +// MASTER builds must be FINAL +#ifdef MASTER +#define FINAL +#endif + +// these are placed here to work with VANILLA_DEFINES for compatibility +#define NO_CDCHECK // skip audio CD check +#define DEFAULT_NATIVE_RESOLUTION // Set default video mode to your native resolution (fixes Windows 10 launch) + +#ifdef VANILLA_DEFINES +#if !defined(_WIN32) || defined(__LP64__) || defined(_WIN64) +#error Vanilla can only be built for win-x86 +#endif + +#define FINAL +#define MASTER +//#define USE_MY_DOCUMENTS +#define THIS_IS_STUPID +#define PC_PARTICLE +#define DONT_FIX_REPLAY_BUGS +#define USE_TXD_CDIMAGE // generate and load textures from txd.img +//#define USE_TEXTURE_POOL // not possible because R* used custom RW33 +#else +// This enables things from the PS2 version on PC +#define GTA_PS2_STUFF + +// quality of life fixes that should also be in FINAL +#define NASTY_GAME // nasty game for all languages + +// those infamous texts +#define DRAW_GAME_VERSION_TEXT +#ifdef DRAW_GAME_VERSION_TEXT + // unlike R* development builds, ours has runtime switch on debug menu & .ini, and disabled as default. + // If you disable this then game will fetch version from peds.col, as R* did while in development. + //#define USE_OUR_VERSIONING // enabled from buildfiles by default +#endif +//#define DRAW_MENU_VERSION_TEXT + +// Memory allocation and compression +// #define USE_CUSTOM_ALLOCATOR // use CMemoryHeap for allocation. use with care, not finished yet +//#define COMPRESSED_COL_VECTORS // use compressed vectors for collision vertices +//#define ANIM_COMPRESSION // only keep most recently used anims uncompressed + +#if defined GTA_PC && defined GTA_PS2_STUFF +# define USE_PS2_RAND +# define RANDOMSPLASH // use random splash as on PS2 +# define PS2_MATFX +#endif + +#ifdef VU_COLLISION +#define COMPRESSED_COL_VECTORS // currently need compressed vectors in this code +#endif + +#ifdef MASTER + // only in master builds + #undef DRAW_GAME_VERSION_TEXT +#else + // not in master builds + #define VALIDATE_SAVE_SIZE + + #define DEBUGMENU +#endif + +#ifdef FINAL + // in all games +# define USE_MY_DOCUMENTS // use my documents directory for user files +#else + // not in any game +# define CHATTYSPLASH // print what the game is loading +# define TIMEBARS // print debug timers +#endif + +#define FIX_BUGS // fixes bugs that we've came across during reversing. You can undefine this only on release builds. +#define MORE_LANGUAGES // Add more translations to the game +#define COMPATIBLE_SAVES // this allows changing structs while keeping saves compatible, and keeps saves compatible between platforms, needs to be enabled on 64bit builds! +#define FIX_INCOMPATIBLE_SAVES // try to fix incompatible saves, requires COMPATIBLE_SAVES +#define LOAD_INI_SETTINGS // as the name suggests. fundamental for CUSTOM_FRONTEND_OPTIONS + +#define NO_MOVIES // add option to disable intro videos + +#define EXTENDED_OFFSCREEN_DESPAWN_RANGE // Use onscreen despawn range for offscreen peds and vehicles to avoid them despawning in the distance when you look + // away + +#if defined(__LP64__) || defined(_WIN64) +#define FIX_BUGS_64 // Must have fixes to be able to run 64 bit build +#endif + +#define ASCII_STRCMP // use faster ascii str comparisons + +#if !defined _WIN32 || defined __MINGW32__ +#undef ASCII_STRCMP +#endif + +// Just debug menu entries +#ifdef DEBUGMENU +#define MISSION_SWITCHER // from debug menu +#endif + +// Rendering/display +//#define EXTRA_MODEL_FLAGS // from mobile to optimize rendering +//# define HARDCODED_MODEL_FLAGS // sets the flags enabled above from hardcoded model names. + // NB: keep this enabled unless your map IDEs have these flags baked in +#define ASPECT_RATIO_SCALE // Not just makes everything scale with aspect ratio, also adds support for all aspect ratios +#define PROPER_SCALING // use original DEFAULT_SCREEN_WIDTH/DEFAULT_SCREEN_HEIGHT from PS2 instead of PC(R* changed HEIGHT here to make radar look better, but broke other hud elements aspect ratio). +#define DEFAULT_NATIVE_RESOLUTION // Set default video mode to your native resolution (fixes Windows 10 launch) +#define USE_TXD_CDIMAGE // generate and load textures from txd.img +#define PS2_ALPHA_TEST // emulate ps2 alpha test +#define IMPROVED_VIDEOMODE // save and load videomode parameters instead of a magic number +#define DISABLE_LOADING_SCREEN // disable the loading screen which vastly improves the loading time +#ifdef DISABLE_LOADING_SCREEN +// enable the PC splash +#undef RANDOMSPLASH +#endif +#define DISABLE_VSYNC_ON_TEXTURE_CONVERSION // make texture conversion work faster by disabling vsync +#define ANISOTROPIC_FILTERING // set all textures to max anisotropic filtering +//#define USE_TEXTURE_POOL +#ifdef LIBRW +#define EXTENDED_COLOURFILTER // more options for colour filter (replaces mblur) +#define EXTENDED_PIPELINES // custom render pipelines (includes Neo) +#define SCREEN_DROPLETS // neo water droplets +#define NEW_RENDERER // leeds-like world rendering, needs librw +#endif + +#define FIX_SPRITES // fix sprites aspect ratio(moon, coronas, particle etc) + +#ifndef EXTENDED_COLOURFILTER +#undef SCREEN_DROPLETS // we need the backbuffer for this effect +#endif + +// Particle +//#define PC_PARTICLE +//#define PS2_ALTERNATIVE_CARSPLASH // unused on PS2 + +// Pad +#if !defined(RW_GL3) && defined(_WIN32) +#define XINPUT +#endif +#if defined XINPUT || (defined RW_GL3 && !defined LIBRW_SDL2 && !defined GTA_HANDHELD) +#define DETECT_JOYSTICK_MENU // Then we'll expect user to enter Controller->Detect joysticks if his joystick isn't detected at the start. +#endif +#define DETECT_PAD_INPUT_SWITCH // Adds automatic switch of pad related stuff between controller and kb/m +#define KANGAROO_CHEAT +#define ALLCARSHELI_CHEAT +#define ALT_DODO_CHEAT +#define REGISTER_START_BUTTON +#define BIND_VEHICLE_FIREWEAPON // Adds ability to rebind fire key for 'in vehicle' controls +#define BUTTON_ICONS // use textures to show controller buttons + +// Hud, frontend and radar +//#define PS2_HUD +#define HUD_ENHANCEMENTS // Adjusts some aspects to make the HUD look/behave a little bit better. +// #define BETA_SLIDING_TEXT +#define TRIANGULAR_BLIPS // height indicating triangular radar blips, as in VC +#define FIX_RADAR // use radar size from early version before R* broke it +// #define XBOX_SUBTITLES // the infamous outlines +#define RADIO_OFF_TEXT +#define PC_MENU + +#ifndef PC_MENU +# define PS2_MENU +//# define PS2_MENU_USEALLPAGEICONS +#else + +# if defined(XINPUT) || defined(GTA_HANDHELD) +# define GAMEPAD_MENU // Add gamepad menu +# endif + +# define SCROLLABLE_STATS_PAGE // only draggable by mouse atm +# define TRIANGLE_BACK_BUTTON +//# define CIRCLE_BACK_BUTTON +//# define PS2_LIKE_MENU // An effort to recreate PS2 menu, cycling through tabs, different bg etc. +//# define PS2_SAVE_DIALOG // PS2 style save dialog with transparent black box +# define CUSTOM_FRONTEND_OPTIONS + +# ifdef CUSTOM_FRONTEND_OPTIONS +# define MENU_MAP // VC-like menu map. Won't appear if you don't have our menu.txd +# define GRAPHICS_MENU_OPTIONS // otherwise Display settings will be scrollable +# define NO_ISLAND_LOADING // disable loadscreen between islands via loading all island data at once, consumes more memory and CPU +# define CUTSCENE_BORDERS_SWITCH +# define MULTISAMPLING // adds MSAA option +# define INVERT_LOOK_FOR_PAD // add bInvertLook4Pad from VC +# define PED_CAR_DENSITY_SLIDERS +# endif +#endif + +// Script +#define USE_DEBUG_SCRIPT_LOADER // Loads main.scm by default. Hold R for main_freeroam.scm and D for main_d.scm +#define USE_MEASUREMENTS_IN_METERS // makes game use meters instead of feet in script +#define USE_PRECISE_MEASUREMENT_CONVERTION // makes game convert feet to meeters more precisely +#ifdef PC_MENU +# define MISSION_REPLAY // mobile feature +#endif +//#define SIMPLIER_MISSIONS // apply simplifications from mobile +#define USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#define SCRIPT_LOG_FILE_LEVEL 0 // 0 == no log, 1 == overwrite every frame, 2 == full log + +#if SCRIPT_LOG_FILE_LEVEL == 0 +#undef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#endif + +#ifndef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#define USE_BASIC_SCRIPT_DEBUG_OUTPUT +#endif + +#ifdef MASTER +#undef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#undef USE_BASIC_SCRIPT_DEBUG_OUTPUT +#endif + +// Replay +//#define DONT_FIX_REPLAY_BUGS // keeps various bugs in CReplay, some of which are fairly cool! +//#define USE_BETA_REPLAY_MODE // adds another replay mode, a few seconds slomo (caution: buggy!) + +// Vehicles +#define EXPLODING_AIRTRAIN // can blow up jumbo jet with rocket launcher +//#define REMOVE_TREADABLE_PATHFIND + +// Pickups +//#define MONEY_MESSAGES +#define CAMERA_PICKUP + +// Peds +#define PED_SKIN // support for skinned geometry on peds, requires COMPATIBLE_SAVES +#define ANIMATE_PED_COL_MODEL +// #define VC_PED_PORTS // various ports from VC's CPed, mostly subtle +// #define NEW_WALK_AROUND_ALGORITHM // to make walking around vehicles/objects less awkward +#define CANCELLABLE_CAR_ENTER +//#define PEDS_REPORT_CRIMES_ON_PHONE // requires COMPATIBLE_SAVES + +// Camera +//#define PS2_CAM_TRANSITION // old way of transitioning between cam modes +#define IMPROVED_CAMERA // Better Debug cam, and maybe more in the future +#define FREE_CAM // Rotating cam + +// Audio +#define EXTERNAL_3D_SOUND // use external engine to simulate 3d audio spatialization. OpenAL would not work without it (because it works in a 3d space + // originally and making it work in 2d only requires more resource). Will not work on PS2 +#define AUDIO_REFLECTIONS // Enable audio reflections. Disabled on mobile, didn't exist yet on PS2. +#define RADIO_SCROLL_TO_PREV_STATION +#define AUDIO_CACHE +#define PS2_AUDIO_CHANNELS // increases the maximum number of audio channels to PS2 value of 44 (PC has 28 originally) +#define PS2_AUDIO_PATHS // changes audio paths for cutscenes and radio to PS2 paths (needs vbdec on MSS builds) +//#define AUDIO_OAL_USE_SNDFILE // use libsndfile to decode WAVs instead of our internal decoder +#define AUDIO_OAL_USE_MPG123 // use mpg123 to support mp3 files +#define PAUSE_RADIO_IN_FRONTEND // pause radio when game is paused +#define ATTACH_RELEASING_SOUNDS_TO_ENTITIES // sounds would follow ped and vehicles coordinates if not being queued otherwise +#define USE_TIME_SCALE_FOR_AUDIO // slow down/speed up sounds according to the speed of the game +#define MULTITHREADED_AUDIO // for streams. requires C++11 or later + +#ifdef AUDIO_OPUS +#define AUDIO_OAL_USE_OPUS // enable support of opus files +#define OPUS_AUDIO_PATHS // changes audio paths to opus paths (doesn't work if AUDIO_OAL_USE_OPUS isn't enabled) +#define OPUS_SFX // enable if your sfx.raw is encoded with opus (doesn't work if AUDIO_OAL_USE_OPUS isn't enabled) + +#ifndef AUDIO_OAL_USE_OPUS +#undef OPUS_AUDIO_PATHS +#undef OPUS_SFX +#endif + +#endif + +// Streaming +#if !defined(_WIN32) && !defined(__SWITCH__) + //#define ONE_THREAD_PER_CHANNEL // Don't use if you're not on SSD/Flash - also not utilized too much right now(see commented LoadAllRequestedModels in Streaming.cpp) + #define FLUSHABLE_STREAMING // Make it possible to interrupt reading when processing file isn't needed anymore. +#endif +#define BIG_IMG // Not complete - allows to read larger img files + +//#define SQUEEZE_PERFORMANCE +#ifdef SQUEEZE_PERFORMANCE + #undef PS2_ALPHA_TEST + #undef NO_ISLAND_LOADING + #undef PS2_AUDIO_CHANNELS + #undef EXTENDED_OFFSCREEN_DESPAWN_RANGE + #define PC_PARTICLE + #define VC_PED_PORTS // To not process collisions always. But should be tested if that's really beneficial + #define VC_RAIN_NERF // Reduces number of rain particles +#endif + +// if these defines are enabled saves are not vanilla compatible without COMPATIBLE_SAVES +#ifndef COMPATIBLE_SAVES +#undef PED_SKIN +#undef PEDS_REPORT_CRIMES_ON_PHONE +#endif + +#ifdef GTA_HANDHELD + #define IGNORE_MOUSE_KEYBOARD // ignore mouse & keyboard input +#endif + +#ifdef __SWITCH__ + #define USE_UNNAMED_SEM // named semaphores are unsupported on the switch +#endif + +#endif // VANILLA_DEFINES + +#if defined(AUDIO_OAL) && !defined(EXTERNAL_3D_SOUND) +#error AUDIO_OAL cannot work without EXTERNAL_3D_SOUND +#endif +#if defined(GTA_PS2) && defined(EXTERNAL_3D_SOUND) +#error EXTERNAL_3D_SOUND cannot work on PS2 +#endif +#if defined(AUDIO_REFLECTIONS) && GTA_VERSION < GTA3_PC_10 +#error AUDIO_REFLECTIONS cannot work with versions below GTA3_PC_10 +#endif \ No newline at end of file diff --git a/src/core/main.cpp b/src/core/main.cpp new file mode 100644 index 0000000..2a0a77c --- /dev/null +++ b/src/core/main.cpp @@ -0,0 +1,2493 @@ +#include "common.h" +#include +#include "rpmatfx.h" +#include "rphanim.h" +#include "rpskin.h" +#include "rtbmp.h" +#include "rtpng.h" +#ifdef ANISOTROPIC_FILTERING +#include "rpanisot.h" +#endif + +#include "main.h" +#include "CdStream.h" +#include "General.h" +#include "RwHelper.h" +#include "Clouds.h" +#include "Draw.h" +#include "Sprite2d.h" +#include "Renderer.h" +#include "Coronas.h" +#include "WaterLevel.h" +#include "Weather.h" +#include "Glass.h" +#include "WaterCannon.h" +#include "SpecialFX.h" +#include "Shadows.h" +#include "Skidmarks.h" +#include "Antennas.h" +#include "Rubbish.h" +#include "Particle.h" +#include "Pickups.h" +#include "WeaponEffects.h" +#include "PointLights.h" +#include "Fluff.h" +#include "Replay.h" +#include "Camera.h" +#include "World.h" +#include "Ped.h" +#include "Font.h" +#include "Pad.h" +#include "Hud.h" +#include "User.h" +#include "Messages.h" +#include "Darkel.h" +#include "Garages.h" +#include "MusicManager.h" +#include "VisibilityPlugins.h" +#include "NodeName.h" +#include "DMAudio.h" +#include "CutsceneMgr.h" +#include "Lights.h" +#include "Credits.h" +#include "ZoneCull.h" +#include "Timecycle.h" +#include "TxdStore.h" +#include "FileMgr.h" +#include "Text.h" +#include "RpAnimBlend.h" +#include "Frontend.h" +#include "AnimViewer.h" +#include "Script.h" +#include "PathFind.h" +#include "Debug.h" +#include "Console.h" +#include "timebars.h" +#include "GenericGameStorage.h" +#include "MemoryCard.h" +#include "SceneEdit.h" +#include "debugmenu.h" +#include "Clock.h" +#include "postfx.h" +#include "custompipes.h" +#include "screendroplets.h" +#include "MemoryHeap.h" +#ifdef USE_OUR_VERSIONING +#include "GitSHA1.h" +#endif + +GlobalScene Scene; + +uint8 work_buff[55000]; +char gString[256]; +char gString2[512]; +wchar gUString[256]; +wchar gUString2[256]; + +float FramesPerSecond = 30.0f; + +bool gbPrintShite = false; +bool gbModelViewer; +#ifdef TIMEBARS +bool gbShowTimebars; +#endif +#ifdef DRAW_GAME_VERSION_TEXT +bool gbDrawVersionText; // Our addition, we think it was always enabled on !MASTER builds +#endif +#ifdef NO_MOVIES +bool gbNoMovies; +#endif + +volatile int32 frameCount; + +RwRGBA gColourTop; + +bool gameAlreadyInitialised; + +float NumberOfChunksLoaded; +#ifdef GTA_PS2 +#define TOTALNUMCHUNKS 48.0f +#else +#define TOTALNUMCHUNKS 73.0f +#endif + +bool g_SlowMode = false; +char version_name[64]; + + +void GameInit(void); +void SystemInit(void); +void TheGame(void); + +#ifdef DEBUGMENU +void DebugMenuPopulate(void); +#endif + +#ifndef FINAL +bool gbPrintMemoryUsage; +#endif + +#ifdef PS2_MENU +#define WANT_TO_LOAD TheMemoryCard.m_bWantToLoad +#define FOUND_GAME_TO_LOAD TheMemoryCard.b_FoundRecentSavedGameWantToLoad +#else +#define WANT_TO_LOAD FrontEndMenuManager.m_bWantToLoad +#define FOUND_GAME_TO_LOAD b_FoundRecentSavedGameWantToLoad +#endif + +#ifdef NEW_RENDERER +bool gbNewRenderer; +#define CLEARMODE (rwCAMERACLEARZ | rwCAMERACLEARSTENCIL) +#else +#define CLEARMODE (rwCAMERACLEARZ) +#endif + +#ifdef __MWERKS__ +void +debug(char *fmt, ...) +{ +#ifndef MASTER + // TODO put something here +#endif +} + +void +Error(char *fmt, ...) +{ +#ifndef MASTER + // TODO put something here +#endif +} +#endif + +void +ValidateVersion() +{ + int32 file = CFileMgr::OpenFile("models\\coll\\peds.col", "rb"); + char buff[128]; + + if ( file != -1 ) + { + CFileMgr::Seek(file, 100, SEEK_SET); + + for ( int i = 0; i < 128; i++ ) + { + CFileMgr::Read(file, &buff[i], sizeof(char)); + buff[i] -= 23; + if ( buff[i] == '\0' ) + break; + CFileMgr::Seek(file, 99, SEEK_CUR); + } + + if ( !strncmp(buff, "grandtheftauto3", 15) ) + { + strncpy(version_name, &buff[15], 64); + CFileMgr::CloseFile(file); + return; + } + } + + LoadingScreen("Invalid version", NULL, NULL); + + while(true) + { + ; + } +} + +bool +DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha) +{ + CRGBA TopColor(TopRed, TopGreen, TopBlue, Alpha); + CRGBA BottomColor(BottomRed, BottomGreen, BottomBlue, Alpha); + +#ifndef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, (CMenuManager::m_PrefsUseWideScreen ? 16.f / 9.f : 4.f / 3.f)); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &TopColor.rwRGBA, CLEARMODE); + + if(!RsCameraBeginUpdate(Scene.camera)) + return false; + +#ifdef FIX_BUGS + CSprite2d::SetRecipNearClip(); +#endif + CSprite2d::InitPerFrame(); + + if(Alpha != 0) + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), BottomColor, BottomColor, TopColor, TopColor); + + return true; +} + +bool +DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha) +{ +#ifndef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, (CMenuManager::m_PrefsUseWideScreen ? 16.f/9.f : 4.f/3.f)); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, CLEARMODE); + + if(!RsCameraBeginUpdate(Scene.camera)) + return false; + + TheCamera.m_viewMatrix.Update(); + CClouds::RenderBackground(TopRed, TopGreen, TopBlue, BottomRed, BottomGreen, BottomBlue, Alpha); + + return true; +} + +// This is certainly a very useful function +void +DoRWRenderHorizon(void) +{ + CClouds::RenderHorizon(); +} + +void +DoFade(void) +{ + if(CTimer::GetIsPaused()) + return; + +#ifdef PS2_MENU + if(TheMemoryCard.JustLoadedDontFadeInYet){ + TheMemoryCard.JustLoadedDontFadeInYet = false; + TheMemoryCard.TimeStartedCountingForFade = CTimer::GetTimeInMilliseconds(); + } +#else + if(JustLoadedDontFadeInYet){ + JustLoadedDontFadeInYet = false; + TimeStartedCountingForFade = CTimer::GetTimeInMilliseconds(); + } +#endif + +#ifdef PS2_MENU + if(TheMemoryCard.StillToFadeOut){ + if(CTimer::GetTimeInMilliseconds() - TheMemoryCard.TimeStartedCountingForFade > TheMemoryCard.TimeToStayFadedBeforeFadeOut){ + TheMemoryCard.StillToFadeOut = false; +#else + if(StillToFadeOut){ + if(CTimer::GetTimeInMilliseconds() - TimeStartedCountingForFade > TimeToStayFadedBeforeFadeOut){ + StillToFadeOut = false; +#endif + TheCamera.Fade(3.0f, FADE_IN); + TheCamera.ProcessFade(); + TheCamera.ProcessMusicFade(); + }else{ + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(0.0f, FADE_OUT); + TheCamera.ProcessFade(); + } + } + + if(CDraw::FadeValue != 0 || CMenuManager::m_PrefsBrightness < 256){ + CSprite2d *splash = LoadSplash(nil); + + CRGBA fadeColor; + CRect rect; + int fadeValue = CDraw::FadeValue; + float brightness = Min(CMenuManager::m_PrefsBrightness, 256); + if(brightness <= 50) + brightness = 50; + if(FrontEndMenuManager.m_bMenuActive) + brightness = 256; + + if(TheCamera.m_FadeTargetIsSplashScreen) + fadeValue = 0; + + float fade = fadeValue + 256 - brightness; + if(fade == 0){ + fadeColor.r = 0; + fadeColor.g = 0; + fadeColor.b = 0; + fadeColor.a = 0; + }else{ + fadeColor.r = fadeValue * CDraw::FadeRed / fade; + fadeColor.g = fadeValue * CDraw::FadeGreen / fade; + fadeColor.b = fadeValue * CDraw::FadeBlue / fade; + int alpha = 255 - brightness*(256 - fadeValue)/256; + if(alpha < 0) + alpha = 0; + fadeColor.a = alpha; + } + + if(TheCamera.m_WideScreenOn +#ifdef CUTSCENE_BORDERS_SWITCH + && CMenuManager::m_PrefsCutsceneBorders +#endif + ){ + // what's this? + float y = SCREEN_HEIGHT/2 * TheCamera.m_ScreenReductionPercentage/100.0f; + rect.left = 0.0f; + rect.right = SCREEN_WIDTH; +#ifdef FIX_BUGS + rect.top = y - SCREEN_SCALE_Y(8.0f); + rect.bottom = SCREEN_HEIGHT - y - SCREEN_SCALE_Y(8.0f); +#else + rect.top = y - 8.0f; + rect.bottom = SCREEN_HEIGHT - y - 8.0f; +#endif // FIX_BUGS + }else{ + rect.left = 0.0f; + rect.right = SCREEN_WIDTH; + rect.top = 0.0f; + rect.bottom = SCREEN_HEIGHT; + } + CSprite2d::DrawRect(rect, fadeColor); + + if(CDraw::FadeValue != 0 && TheCamera.m_FadeTargetIsSplashScreen){ + fadeColor.r = 255; + fadeColor.g = 255; + fadeColor.b = 255; + fadeColor.a = CDraw::FadeValue; + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), fadeColor, fadeColor, fadeColor, fadeColor); + } + } +} + +bool +RwGrabScreen(RwCamera *camera, RwChar *filename) +{ + char temp[255]; + RwImage *pImage = RsGrabScreen(camera); + bool result = true; + + if (pImage == nil) + return false; + + strcpy(temp, CFileMgr::GetRootDirName()); + strcat(temp, filename); + +#ifndef LIBRW + if (RtBMPImageWrite(pImage, &temp[0]) == nil) +#else + if (RtPNGImageWrite(pImage, &temp[0]) == nil) +#endif + result = false; + RwImageDestroy(pImage); + return result; +} + +#define TILE_WIDTH 576 +#define TILE_HEIGHT 432 + +void +DoRWStuffEndOfFrame(void) +{ + CDebug::DisplayScreenStrings(); // custom + CDebug::DebugDisplayTextBuffer(); + FlushObrsPrintfs(); + RwCameraEndUpdate(Scene.camera); + RsCameraShowRaster(Scene.camera); +#ifndef MASTER + char s[48]; +#ifdef THIS_IS_STUPID + if (CPad::GetPad(1)->GetLeftShockJustDown()) { + // try using both controllers for this thing... crazy bastards + if (CPad::GetPad(0)->GetRightStickY() > 0) { + sprintf(s, "screen%d%d.ras", CClock::ms_nGameClockHours, CClock::ms_nGameClockMinutes); + // TODO + //RtTileRender(Scene.camera, TILE_WIDTH * 2, TILE_HEIGHT * 2, TILE_WIDTH, TILE_HEIGHT, &NewTileRendererCB, nil, s); + } else { + sprintf(s, "screen%d%d.bmp", CClock::ms_nGameClockHours, CClock::ms_nGameClockMinutes); + RwGrabScreen(Scene.camera, s); + } + } +#else + if (CPad::GetPad(1)->GetLeftShockJustDown() || CPad::GetPad(0)->GetFJustDown(11)) { + sprintf(s, "screen_%011lld.png", time(nil)); + RwGrabScreen(Scene.camera, s); + } +#endif +#endif // !MASTER +} + +static RwBool +PluginAttach(void) +{ + if( !RpWorldPluginAttach() ) + { + printf("Couldn't attach world plugin\n"); + + return FALSE; + } + + if( !RpSkinPluginAttach() ) + { + printf("Couldn't attach RpSkin plugin\n"); + + return FALSE; + } + + if( !RpHAnimPluginAttach() ) + { + printf("Couldn't attach RpHAnim plugin\n"); + + return FALSE; + } + + if( !NodeNamePluginAttach() ) + { + printf("Couldn't attach node name plugin\n"); + + return FALSE; + } + + if( !CVisibilityPlugins::PluginAttach() ) + { + printf("Couldn't attach visibility plugins\n"); + + return FALSE; + } + + if( !RpAnimBlendPluginAttach() ) + { + printf("Couldn't attach RpAnimBlend plugin\n"); + + return FALSE; + } + + if( !RpMatFXPluginAttach() ) + { + printf("Couldn't attach RpMatFX plugin\n"); + + return FALSE; + } +#ifdef ANISOTROPIC_FILTERING + RpAnisotPluginAttach(); +#endif +#ifdef EXTENDED_PIPELINES + CustomPipes::CustomPipeRegister(); +#endif + + return TRUE; +} + +#ifdef GTA_PS2 +#define NUM_PREALLOC_ATOMICS 3245 +#define NUM_PREALLOC_CLUMPS 101 +#define NUM_PREALLOC_FRAMES 2821 +#define NUM_PREALLOC_GEOMETRIES 1404 +#define NUM_PREALLOC_TEXDICTS 106 +#define NUM_PREALLOC_TEXTURES 1900 +#define NUM_PREALLOC_MATERIALS 3300 +bool preAlloc; + +void +PreAllocateRwObjects(void) +{ + int i; + void **tmp = new void*[0x8000]; + preAlloc = true; + + for(i = 0; i < NUM_PREALLOC_ATOMICS; i++) + tmp[i] = RpAtomicCreate(); + for(i = 0; i < NUM_PREALLOC_ATOMICS; i++) + RpAtomicDestroy((RpAtomic*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_CLUMPS; i++) + tmp[i] = RpClumpCreate(); + for(i = 0; i < NUM_PREALLOC_CLUMPS; i++) + RpClumpDestroy((RpClump*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_FRAMES; i++) + tmp[i] = RwFrameCreate(); + for(i = 0; i < NUM_PREALLOC_FRAMES; i++) + RwFrameDestroy((RwFrame*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_GEOMETRIES; i++) + tmp[i] = RpGeometryCreate(0, 0, 0); + for(i = 0; i < NUM_PREALLOC_GEOMETRIES; i++) + RpGeometryDestroy((RpGeometry*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_TEXDICTS; i++) + tmp[i] = RwTexDictionaryCreate(); + for(i = 0; i < NUM_PREALLOC_TEXDICTS; i++) + RwTexDictionaryDestroy((RwTexDictionary*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_TEXTURES; i++) + tmp[i] = RwTextureCreate(RwRasterCreate(0, 0, 0, 0)); + for(i = 0; i < NUM_PREALLOC_TEXDICTS; i++) + RwTextureDestroy((RwTexture*)tmp[i]); + + for(i = 0; i < NUM_PREALLOC_MATERIALS; i++) + tmp[i] = RpMaterialCreate(); + for(i = 0; i < NUM_PREALLOC_MATERIALS; i++) + RpMaterialDestroy((RpMaterial*)tmp[i]); + + delete[] tmp; + preAlloc = false; +} +#endif + +static RwBool +Initialise3D(void *param) +{ + if (RsRwInitialize(param)) + { +#ifdef DEBUGMENU + DebugMenuInit(); + DebugMenuPopulate(); +#endif // !DEBUGMENU + return CGame::InitialiseRenderWare(); + } + + return (FALSE); +} + +static void +Terminate3D(void) +{ + CGame::ShutdownRenderWare(); +#ifdef DEBUGMENU + DebugMenuShutdown(); +#endif // !DEBUGMENU + + RsRwTerminate(); + + return; +} + +CSprite2d splash; +int splashTxdId = -1; + +CSprite2d* +LoadSplash(const char *name) +{ + RwTexDictionary *txd; + char filename[140]; + RwTexture *tex = nil; + + if(name == nil) + return &splash; + if(splashTxdId == -1) + splashTxdId = CTxdStore::AddTxdSlot("splash"); + + txd = CTxdStore::GetSlot(splashTxdId)->texDict; + if(txd) + tex = RwTexDictionaryFindNamedTexture(txd, name); + // if texture is found, splash was already set up below + + if(tex == nil){ + CFileMgr::SetDir("TXD\\"); + sprintf(filename, "%s.txd", name); + if(splash.m_pTexture) + splash.Delete(); + if(txd) + CTxdStore::RemoveTxd(splashTxdId); + CTxdStore::LoadTxd(splashTxdId, filename); + CTxdStore::AddRef(splashTxdId); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(splashTxdId); + splash.SetTexture(name); + CTxdStore::PopCurrentTxd(); + CFileMgr::SetDir(""); + } + + return &splash; +} + +void +DestroySplashScreen(void) +{ + splash.Delete(); + if(splashTxdId != -1) + CTxdStore::RemoveTxdSlot(splashTxdId); + splashTxdId = -1; +} + +Const char* +GetRandomSplashScreen(void) +{ + int index; + static int index2 = 0; + static char splashName[128]; + static int splashIndex[24] = { + 25, 22, 4, 13, + 1, 21, 14, 16, + 10, 12, 5, 9, + 11, 18, 3, 2, + 19, 23, 7, 17, + 15, 6, 8, 20 + }; + + index = splashIndex[4*index2 + CGeneral::GetRandomNumberInRange(0, 3)]; + index2++; + if(index2 == 6) + index2 = 0; + sprintf(splashName, "loadsc%d", index); + return splashName; +} + +Const char* +GetLevelSplashScreen(int level) +{ + static Const char *splashScreens[4] = { + nil, + "splash1", + "splash2", + "splash3", + }; + + return splashScreens[level]; +} + +void +ResetLoadingScreenBar() +{ + NumberOfChunksLoaded = 0.0f; +} + +void +LoadingScreen(const char *str1, const char *str2, const char *splashscreen) +{ + CSprite2d *splash; + +#ifdef DISABLE_LOADING_SCREEN + if (str1 && str2) + return; +#endif + +#ifndef RANDOMSPLASH + if(CGame::frenchGame || CGame::germanGame || !CGame::nastyGame) + splashscreen = "mainsc2"; + else + splashscreen = "mainsc1"; +#endif + + splash = LoadSplash(splashscreen); + +#ifndef GTA_PS2 + if(RsGlobal.quit) + return; +#endif + +#ifndef GTA_PS2 + if(DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) +#else + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); +#endif + { + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + + if(str1){ + NumberOfChunksLoaded += 1; + + float hpos = SCREEN_SCALE_X(40); + float length = SCREEN_WIDTH - SCREEN_SCALE_X(100); + float vpos = SCREEN_HEIGHT - SCREEN_SCALE_Y(13); + float height = SCREEN_SCALE_Y(7); + CSprite2d::DrawRect(CRect(hpos, vpos, hpos + length, vpos + height), CRGBA(40, 53, 68, 255)); + + length *= NumberOfChunksLoaded/TOTALNUMCHUNKS; + CSprite2d::DrawRect(CRect(hpos, vpos, hpos + length, vpos + height), CRGBA(81, 106, 137, 255)); + + // this is done by the game but is unused + CFont::SetScale(SCREEN_SCALE_X(2), SCREEN_SCALE_Y(2)); + CFont::SetPropOn(); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + +#ifdef CHATTYSPLASH + // my attempt + static wchar tmpstr[80]; + float yscale = SCREEN_SCALE_Y(0.9f); + vpos -= 45*yscale; + CFont::SetScale(SCREEN_SCALE_X(0.75f), yscale); + CFont::SetPropOn(); + CFont::SetRightJustifyOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + AsciiToUnicode(str1, tmpstr); + CFont::PrintString(hpos, vpos, tmpstr); + vpos += 22*yscale; + if (str2) { + AsciiToUnicode(str2, tmpstr); + CFont::PrintString(hpos, vpos, tmpstr); + } +#endif + } + + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); + } +} + +void +LoadingIslandScreen(const char *levelName) +{ + CSprite2d *splash; + wchar *name; + char str[100]; + wchar wstr[80]; + CRGBA col; + + splash = LoadSplash(nil); + name = TheText.Get(levelName); + +#ifndef GTA_PS2 + if(!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) + return; +#else + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); +#endif + + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + col = CRGBA(255, 255, 255, 255); + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), col, col, col, col); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(1.5f, 1.5f); +#endif + CFont::SetPropOn(); + CFont::SetRightJustifyOn(); +#ifdef FIX_BUGS + CFont::SetRightJustifyWrap(SCREEN_SCALE_X(150.0f)); +#else + CFont::SetRightJustifyWrap(150.0f); +#endif + CFont::SetFontStyle(FONT_HEADING); + sprintf(str, "WELCOME TO"); + AsciiToUnicode(str, wstr); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetDropShadowPosition(3); + CFont::SetColor(CRGBA(243, 237, 71, 255)); +#if !defined(PS2_HUD) && defined(GTA_PC) + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.2f)); +#endif + +#ifdef PS2_HUD + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(140.0f), TheText.Get("WELCOME")); + #else + CFont::PrintString(SCREEN_WIDTH - 20, SCREEN_HEIGHT - 140, TheText.Get("WELCOME")); + #endif +#else + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(110.0f), TheText.Get("WELCOME")); + #else + CFont::PrintString(SCREEN_WIDTH - 20, SCREEN_SCALE_FROM_BOTTOM(110.0f), TheText.Get("WELCOME")); + #endif +#endif + TextCopy(wstr, name); + TheText.UpperCase(wstr); + CFont::SetColor(CRGBA(243, 237, 71, 255)); +#if !defined(PS2_HUD) && defined(GTA_PC) + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.2f)); +#endif + +#ifdef PS2_HUD + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(110.0f), wstr); + #else + CFont::PrintString(SCREEN_WIDTH-20, SCREEN_HEIGHT - 110, wstr); + #endif +#else + #ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(80.0f), wstr); + #else + CFont::PrintString(SCREEN_WIDTH-20, SCREEN_SCALE_FROM_BOTTOM(80.0f), wstr); + #endif +#endif + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +ProcessSlowMode(void) +{ + int16 lX = CPad::GetPad(0)->NewState.LeftStickX; + int16 lY = CPad::GetPad(0)->NewState.LeftStickY; + int16 rX = CPad::GetPad(0)->NewState.RightStickX; + int16 rY = CPad::GetPad(0)->NewState.RightStickY; + int16 L1 = CPad::GetPad(0)->NewState.LeftShoulder1; + int16 L2 = CPad::GetPad(0)->NewState.LeftShoulder2; + int16 R1 = CPad::GetPad(0)->NewState.RightShoulder1; + int16 R2 = CPad::GetPad(0)->NewState.RightShoulder2; + int16 up = CPad::GetPad(0)->NewState.DPadUp; + int16 down = CPad::GetPad(0)->NewState.DPadDown; + int16 left = CPad::GetPad(0)->NewState.DPadLeft; + int16 right = CPad::GetPad(0)->NewState.DPadRight; + int16 start = CPad::GetPad(0)->NewState.Start; + int16 select = CPad::GetPad(0)->NewState.Select; + int16 square = CPad::GetPad(0)->NewState.Square; + int16 triangle = CPad::GetPad(0)->NewState.Triangle; + int16 cross = CPad::GetPad(0)->NewState.Cross; + int16 circle = CPad::GetPad(0)->NewState.Circle; + int16 L3 = CPad::GetPad(0)->NewState.LeftShock; + int16 R3 = CPad::GetPad(0)->NewState.RightShock; + int16 networktalk = CPad::GetPad(0)->NewState.NetworkTalk; + int16 stop = true; + + do + { + if ( CPad::GetPad(1)->GetLeftShoulder1JustDown() || CPad::GetPad(1)->GetRightShoulder1() ) + break; + + if ( stop ) + { + CTimer::Stop(); + stop = false; + } + + CPad::UpdatePads(); + + RwCameraBeginUpdate(Scene.camera); + RwCameraEndUpdate(Scene.camera); + + if ( CPad::GetPad(1)->GetLeftShoulder1JustDown() || CPad::GetPad(1)->GetRightShoulder1() ) + break; + + } while (!CPad::GetPad(1)->GetRightShoulder1()); + + + CPad::GetPad(0)->OldState.LeftStickX = lX; + CPad::GetPad(0)->OldState.LeftStickY = lY; + CPad::GetPad(0)->OldState.RightStickX = rX; + CPad::GetPad(0)->OldState.RightStickY = rY; + CPad::GetPad(0)->OldState.LeftShoulder1 = L1; + CPad::GetPad(0)->OldState.LeftShoulder2 = L2; + CPad::GetPad(0)->OldState.RightShoulder1 = R1; + CPad::GetPad(0)->OldState.RightShoulder2 = R2; + CPad::GetPad(0)->OldState.DPadUp = up; + CPad::GetPad(0)->OldState.DPadDown = down; + CPad::GetPad(0)->OldState.DPadLeft = left; + CPad::GetPad(0)->OldState.DPadRight = right; + CPad::GetPad(0)->OldState.Start = start; + CPad::GetPad(0)->OldState.Select = select; + CPad::GetPad(0)->OldState.Square = square; + CPad::GetPad(0)->OldState.Triangle = triangle; + CPad::GetPad(0)->OldState.Cross = cross; + CPad::GetPad(0)->OldState.Circle = circle; + CPad::GetPad(0)->OldState.LeftShock = L3; + CPad::GetPad(0)->OldState.RightShock = R3; + CPad::GetPad(0)->OldState.NetworkTalk = networktalk; + CPad::GetPad(0)->NewState.LeftStickX = lX; + CPad::GetPad(0)->NewState.LeftStickY = lY; + CPad::GetPad(0)->NewState.RightStickX = rX; + CPad::GetPad(0)->NewState.RightStickY = rY; + CPad::GetPad(0)->NewState.LeftShoulder1 = L1; + CPad::GetPad(0)->NewState.LeftShoulder2 = L2; + CPad::GetPad(0)->NewState.RightShoulder1 = R1; + CPad::GetPad(0)->NewState.RightShoulder2 = R2; + CPad::GetPad(0)->NewState.DPadUp = up; + CPad::GetPad(0)->NewState.DPadDown = down; + CPad::GetPad(0)->NewState.DPadLeft = left; + CPad::GetPad(0)->NewState.DPadRight = right; + CPad::GetPad(0)->NewState.Start = start; + CPad::GetPad(0)->NewState.Select = select; + CPad::GetPad(0)->NewState.Square = square; + CPad::GetPad(0)->NewState.Triangle = triangle; + CPad::GetPad(0)->NewState.Cross = cross; + CPad::GetPad(0)->NewState.Circle = circle; + CPad::GetPad(0)->NewState.LeftShock = L3; + CPad::GetPad(0)->NewState.RightShock = R3; + CPad::GetPad(0)->NewState.NetworkTalk = networktalk; +} + + +float FramesPerSecondCounter; +int32 FrameSamples; + +#ifndef MASTER +struct tZonePrint +{ + char name[12]; + CRect rect; +}; + +tZonePrint ZonePrint[] = +{ + { "suburban", CRect(-1639.4f, 1014.3f, -226.23f, -1347.9f) }, + { "comntop", CRect(-223.52f, 203.62f, 616.79f, -413.6f) }, + { "comnbtm", CRect(-227.24f, -413.6f, 620.51f, -911.84f) }, + { "comse", CRect( 200.35f, -911.84f, 620.51f, -1737.3f) }, + { "comsw", CRect(-223.52f, -911.84f, 200.35f, -1737.3f) }, + { "industsw", CRect( 744.05f, -473.0f, 1067.5f, -1331.5f) }, + { "industne", CRect( 1067.5f, 282.19f, 1915.3f, -473.0f) }, + { "industnw", CRect( 744.05f, 324.95f, 1067.5f, -473.0f) }, + { "industse", CRect( 1070.3f, -473.0f, 1918.1f, -1331.5f) }, + { "no zone", CRect( 0.0f, 0.0f, 0.0f, 0.0f) } +}; + +void +PrintMemoryUsage(void) +{ +// little hack +if(CPools::GetPtrNodePool() == nil) +return; + + // Style taken from LCS, modified for III +// CFont::SetFontStyle(FONT_PAGER); + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetWrapx(640.0f); +// CFont::SetScale(0.5f, 0.75f); + CFont::SetScale(0.4f, 0.75f); + CFont::SetCentreOff(); + CFont::SetCentreSize(640.0f); + CFont::SetJustifyOff(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(200, 200, 200, 200)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetDropShadowPosition(0); + + float y; + +#ifdef USE_CUSTOM_ALLOCATOR + y = 24.0f; + sprintf(gString, "Total: %d blocks, %d bytes", gMainHeap.m_totalBlocksUsed, gMainHeap.m_totalMemUsed); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Game: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_GAME), gMainHeap.GetMemoryUsed(MEMID_GAME)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "World: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_WORLD), gMainHeap.GetMemoryUsed(MEMID_WORLD)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Render: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_RENDER), gMainHeap.GetMemoryUsed(MEMID_RENDER)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Render List: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_RENDERLIST), gMainHeap.GetMemoryUsed(MEMID_RENDERLIST)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Default Models: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_DEF_MODELS), gMainHeap.GetMemoryUsed(MEMID_DEF_MODELS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Textures: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_TEXTURES), gMainHeap.GetMemoryUsed(MEMID_TEXTURES)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Streaming: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_STREAM), gMainHeap.GetMemoryUsed(MEMID_STREAM)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Streamed Models: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_STREAM_MODELS), gMainHeap.GetMemoryUsed(MEMID_STREAM_MODELS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Streamed Textures: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_STREAM_TEXUTRES), gMainHeap.GetMemoryUsed(MEMID_STREAM_TEXUTRES)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Animation: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_ANIMATION), gMainHeap.GetMemoryUsed(MEMID_ANIMATION)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Pools: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_POOLS), gMainHeap.GetMemoryUsed(MEMID_POOLS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Collision: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_COLLISION), gMainHeap.GetMemoryUsed(MEMID_COLLISION)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Game Process: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_GAME_PROCESS), gMainHeap.GetMemoryUsed(MEMID_GAME_PROCESS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Script: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_SCRIPT), gMainHeap.GetMemoryUsed(MEMID_SCRIPT)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Cars: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_CARS), gMainHeap.GetMemoryUsed(MEMID_CARS)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Frontend: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_FRONTEND), gMainHeap.GetMemoryUsed(MEMID_FRONTEND)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; +#endif + + y = 132.0f; + AsciiToUnicode("Pools usage:", gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "PtrNode: %d/%d", CPools::GetPtrNodePool()->GetNoOfUsedSpaces(), CPools::GetPtrNodePool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "EntryInfoNode: %d/%d", CPools::GetEntryInfoNodePool()->GetNoOfUsedSpaces(), CPools::GetEntryInfoNodePool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Ped: %d/%d", CPools::GetPedPool()->GetNoOfUsedSpaces(), CPools::GetPedPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Vehicle: %d/%d", CPools::GetVehiclePool()->GetNoOfUsedSpaces(), CPools::GetVehiclePool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Building: %d/%d", CPools::GetBuildingPool()->GetNoOfUsedSpaces(), CPools::GetBuildingPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Treadable: %d/%d", CPools::GetTreadablePool()->GetNoOfUsedSpaces(), CPools::GetTreadablePool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Object: %d/%d", CPools::GetObjectPool()->GetNoOfUsedSpaces(), CPools::GetObjectPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Dummy: %d/%d", CPools::GetDummyPool()->GetNoOfUsedSpaces(), CPools::GetDummyPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "AudioScriptObjects: %d/%d", CPools::GetAudioScriptObjectPool()->GetNoOfUsedSpaces(), CPools::GetAudioScriptObjectPool()->GetSize()); + AsciiToUnicode(gString, gUString); + CFont::PrintString(400.0f, y, gUString); + y += 12.0f; +} + +void +DisplayGameDebugText() +{ + static bool bDisplayPosn = false; + static bool bDisplayRate = false; +#ifndef FINAL + { + SETTWEAKPATH("Debug"); + TWEAKBOOL(bDisplayPosn); + TWEAKBOOL(bDisplayRate); + } + + if(gbPrintMemoryUsage) + PrintMemoryUsage(); +#endif + + char str[200]; + wchar ustr[200]; + +#ifdef DRAW_GAME_VERSION_TEXT + wchar ver[200]; + + if(gbDrawVersionText) // This realtime switch is our thing + { + +#ifdef USE_OUR_VERSIONING + char verA[200]; + sprintf(verA, +#if defined _WIN32 + "Win " +#elif defined __linux__ + "Linux " +#elif defined __APPLE__ + "Mac OS X " +#elif defined __FreeBSD__ + "FreeBSD " +#else + "Posix-compliant " +#endif +#if defined __LP64__ || defined _WIN64 + "64-bit " +#else + "32-bit " +#endif +#if defined RW_D3D9 + "D3D9 " +#elif defined RWLIBS + "D3D8 " +#elif defined RW_GL3 + "OpenGL " +#endif +#if defined AUDIO_OAL + "OAL " +#elif defined AUDIO_MSS + "MSS " +#endif +#if defined _DEBUG || defined DEBUG + "DEBUG " +#endif + "%.8s", + g_GIT_SHA1); + AsciiToUnicode(verA, ver); + CFont::SetScale(SCREEN_SCALE_X(0.5f), SCREEN_SCALE_Y(0.7f)); +#else + AsciiToUnicode(version_name, ver); + CFont::SetScale(SCREEN_SCALE_X(0.5f), SCREEN_SCALE_Y(0.5f)); +#endif + + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::SetJustifyOff(); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetColor(CRGBA(255, 108, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_X(10.0f), SCREEN_SCALE_Y(10.0f), ver); +#else + CFont::PrintString(10.0f, 10.0f, ver); +#endif + } +#endif // #ifdef DRAW_GAME_VERSION_TEXT + + FrameSamples++; +#ifdef FIX_BUGS + // this is inaccurate with over 1000 fps + static uint32 PreviousTimeInMillisecondsPauseMode = 0; + FramesPerSecondCounter += (CTimer::GetTimeInMillisecondsPauseMode() - PreviousTimeInMillisecondsPauseMode) / 1000.0f; // convert to seconds + PreviousTimeInMillisecondsPauseMode = CTimer::GetTimeInMillisecondsPauseMode(); + FramesPerSecond = FrameSamples / FramesPerSecondCounter; +#else + FramesPerSecondCounter += 1000.0f / CTimer::GetTimeStepNonClippedInMilliseconds(); + FramesPerSecond = FramesPerSecondCounter / FrameSamples; +#endif + + if ( FrameSamples > 30 ) + { + FramesPerSecondCounter = 0.0f; + FrameSamples = 0; + } + + if ( !TheCamera.WorldViewerBeingUsed + && CPad::GetPad(1)->GetSquare() + && CPad::GetPad(1)->GetTriangle() + && CPad::GetPad(1)->GetLeftShoulder2JustDown() ) + { + bDisplayPosn = !bDisplayPosn; + } + + if ( CPad::GetPad(1)->GetSquare() + && CPad::GetPad(1)->GetTriangle() + && CPad::GetPad(1)->GetRightShoulder2JustDown() ) + { + bDisplayRate = !bDisplayRate; + } + + if ( bDisplayPosn || bDisplayRate ) + { + CVector pos = FindPlayerCoors(); + int32 ZoneId = ARRAY_SIZE(ZonePrint)-1; // no zone + + for ( int32 i = 0; i < ARRAY_SIZE(ZonePrint)-1; i++ ) + { + if ( pos.x > ZonePrint[i].rect.left + && pos.x < ZonePrint[i].rect.right + && pos.y > ZonePrint[i].rect.bottom + && pos.y < ZonePrint[i].rect.top ) + { + ZoneId = i; + } + } + + //NOTE: fps should be 30, but its 29 due to different fp2int conversion + if ( bDisplayRate ) + sprintf(str, "X:%5.1f, Y:%5.1f, Z:%5.1f, F-%d, %s", pos.x, pos.y, pos.z, (int32)FramesPerSecond, ZonePrint[ZoneId].name); + else + sprintf(str, "X:%5.1f, Y:%5.1f, Z:%5.1f, %s", pos.x, pos.y, pos.z, ZonePrint[ZoneId].name); + + AsciiToUnicode(str, ustr); + + CFont::SetPropOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(0.7f), SCREEN_SCALE_Y(1.5f)); +#else + CFont::SetScale(0.7f, 1.5f); +#endif + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetJustifyOff(); + CFont::SetBackGroundOnlyTextOff(); +#ifdef FIX_BUGS + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); +#else + CFont::SetWrapx(DEFAULT_SCREEN_WIDTH); +#endif + CFont::SetFontStyle(FONT_HEADING); + + CFont::SetColor(CRGBA(0, 0, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_X(40.0f+2.0f), SCREEN_SCALE_Y(40.0f+2.0f), ustr); +#else + CFont::PrintString(40.0f+2.0f, 40.0f+2.0f, ustr); +#endif + + CFont::SetColor(CRGBA(255, 108, 0, 255)); +#ifdef FIX_BUGS + CFont::PrintString(SCREEN_SCALE_X(40.0f), SCREEN_SCALE_Y(40.0f), ustr); +#else + CFont::PrintString(40.0f, 40.0f, ustr); +#endif + } +} +#endif + +#ifdef NEW_RENDERER +bool gbRenderRoads = true; +bool gbRenderEverythingBarRoads = true; +//bool gbRenderFadingInUnderwaterEntities = true; +bool gbRenderFadingInEntities = true; +bool gbRenderWater = true; +bool gbRenderBoats = true; +bool gbRenderVehicles = true; +bool gbRenderWorld0 = true; +bool gbRenderWorld1 = true; +bool gbRenderWorld2 = true; + +void +MattRenderScene(void) +{ + // this calls CMattRenderer::Render + /// CWorld::AdvanceCurrentScanCode(); + // CMattRenderer::ResetRenderStates + /// CRenderer::ClearForFrame(); // before ConstructRenderList + // CClock::CalcEnvMapTimeMultiplicator +if(gbRenderWater) + CRenderer::RenderWater(); // actually CMattRenderer::RenderWater + // CClock::ms_EnvMapTimeMultiplicator = 1.0f; + // cWorldStream::ClearDynamics + /// CRenderer::ConstructRenderList(); // before PreRender +if(gbRenderWorld0) + CRenderer::RenderWorld(0); // roads + // CMattRenderer::ResetRenderStates + /// CRenderer::PreRender(); // has to be called before BeginUpdate because of cutscene shadows + CCoronas::RenderReflections(); +if(gbRenderWorld1) + CRenderer::RenderWorld(1); // opaque +if(gbRenderRoads) + CRenderer::RenderRoads(); + + CRenderer::RenderPeds(); + +if(gbRenderBoats) + CRenderer::RenderBoats(); +//if(gbRenderFadingInUnderwaterEntities) +// CRenderer::RenderFadingInUnderwaterEntities(); + +if(gbRenderEverythingBarRoads) + CRenderer::RenderEverythingBarRoads(); + // seam fixer + // moved this: + // CRenderer::RenderFadingInEntities(); +} + +void +RenderScene_new(void) +{ + PUSH_RENDERGROUP("RenderScene_new"); + CClouds::Render(); + DoRWRenderHorizon(); + + MattRenderScene(); + DefinedState(); + // CMattRenderer::ResetRenderStates + // moved CRenderer::RenderBoats to before transparent water + POP_RENDERGROUP(); +} + +// TODO +bool FredIsInFirstPersonCam(void) { return false; } +void +RenderEffects_new(void) +{ + PUSH_RENDERGROUP("RenderEffects_new"); +/* // stupid to do this before the whole world is drawn! + CShadows::RenderStaticShadows(); + // CRenderer::GenerateEnvironmentMap + CShadows::RenderStoredShadows(); + CSkidmarks::Render(); + CRubbish::Render(); +*/ + + // these aren't really effects + DefinedState(); + if(FredIsInFirstPersonCam()){ + DefinedState(); + C3dMarkers::Render(); // normally rendered in CSpecialFX::Render() +if(gbRenderWorld2) + CRenderer::RenderWorld(2); // transparent +if(gbRenderVehicles) + CRenderer::RenderVehicles(); + }else{ + // flipped these two, seems to give the best result +if(gbRenderWorld2) + CRenderer::RenderWorld(2); // transparent +if(gbRenderVehicles) + CRenderer::RenderVehicles(); + } + // better render these after transparent world +if(gbRenderFadingInEntities) + CRenderer::RenderFadingInEntities(); + + // actual effects here + + // from above + CShadows::RenderStaticShadows(); + CShadows::RenderStoredShadows(); + CSkidmarks::Render(); + CRubbish::Render(); + + CGlass::Render(); + // CMattRenderer::ResetRenderStates + DefinedState(); + CWeather::RenderRainStreaks(); + // CWeather::AddSnow + CWaterCannons::Render(); + CAntennas::Render(); + CSpecialFX::Render(); + CCoronas::Render(); + CParticle::Render(); + CPacManPickups::Render(); + CWeaponEffects::Render(); + CPointLights::RenderFogEffect(); + CMovingThings::Render(); + CRenderer::RenderFirstPersonVehicle(); + POP_RENDERGROUP(); +} +#endif + +void +RenderScene(void) +{ +#ifdef NEW_RENDERER + if(gbNewRenderer){ + RenderScene_new(); + return; + } +#endif + PUSH_RENDERGROUP("RenderScene"); + CClouds::Render(); + DoRWRenderHorizon(); + CRenderer::RenderRoads(); + CCoronas::RenderReflections(); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + CRenderer::RenderEverythingBarRoads(); + CRenderer::RenderBoats(); + DefinedState(); + CWaterLevel::RenderWater(); + CRenderer::RenderFadingInEntities(); +#ifndef SQUEEZE_PERFORMANCE + CRenderer::RenderVehiclesButNotBoats(); +#endif + CWeather::RenderRainStreaks(); + POP_RENDERGROUP(); +} + +void +RenderDebugShit(void) +{ + PUSH_RENDERGROUP("RenderDebugShit"); + CTheScripts::RenderTheScriptDebugLines(); +#ifndef FINAL + if(gbShowCollisionLines) + CRenderer::RenderCollisionLines(); + ThePaths.DisplayPathData(); + CDebug::DrawLines(); + DefinedState(); +#endif + POP_RENDERGROUP(); +} + +void +RenderEffects(void) +{ +#ifdef NEW_RENDERER + if(gbNewRenderer){ + RenderEffects_new(); + return; + } +#endif + PUSH_RENDERGROUP("RenderEffects"); + CGlass::Render(); + CWaterCannons::Render(); + CSpecialFX::Render(); + CShadows::RenderStaticShadows(); + CShadows::RenderStoredShadows(); + CSkidmarks::Render(); + CAntennas::Render(); + CRubbish::Render(); + CCoronas::Render(); + CParticle::Render(); + CPacManPickups::Render(); + CWeaponEffects::Render(); + CPointLights::RenderFogEffect(); + CMovingThings::Render(); + CRenderer::RenderFirstPersonVehicle(); + POP_RENDERGROUP(); +} + +void +Render2dStuff(void) +{ + PUSH_RENDERGROUP("Render2dStuff"); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + + CReplay::Display(); + CPickups::RenderPickUpText(); + + if(TheCamera.m_WideScreenOn +#ifdef CUTSCENE_BORDERS_SWITCH + && CMenuManager::m_PrefsCutsceneBorders +#endif + ) + TheCamera.DrawBordersForWideScreen(); + + CPed *player = FindPlayerPed(); + int weaponType = 0; + if(player) + weaponType = player->GetWeapon()->m_eWeaponType; + + bool firstPersonWeapon = false; + int cammode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if(cammode == CCam::MODE_SNIPER || + cammode == CCam::MODE_SNIPER_RUNABOUT || + cammode == CCam::MODE_ROCKETLAUNCHER || + cammode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT) + firstPersonWeapon = true; + + // Draw black border for sniper and rocket launcher + if((weaponType == WEAPONTYPE_SNIPERRIFLE || weaponType == WEAPONTYPE_ROCKETLAUNCHER) && firstPersonWeapon){ + CRGBA black(0, 0, 0, 255); + + // top and bottom strips + if (weaponType == WEAPONTYPE_ROCKETLAUNCHER) { + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(180)), black); + CSprite2d::DrawRect(CRect(0.0f, SCREEN_HEIGHT / 2 + SCREEN_SCALE_Y(170), SCREEN_WIDTH, SCREEN_HEIGHT), black); + } + else { + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(210)), black); + CSprite2d::DrawRect(CRect(0.0f, SCREEN_HEIGHT / 2 + SCREEN_SCALE_Y(210), SCREEN_WIDTH, SCREEN_HEIGHT), black); + } + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH / 2 - SCREEN_SCALE_X(210), SCREEN_HEIGHT), black); + CSprite2d::DrawRect(CRect(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(210), 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), black); + } + + MusicManager.DisplayRadioStationName(); + TheConsole.Display(); +#ifdef GTA_SCENE_EDIT + if(CSceneEdit::m_bEditOn) + CSceneEdit::Draw(); + else +#endif + CHud::Draw(); + CUserDisplay::OnscnTimer.ProcessForDisplay(); + CMessages::Display(); + CDarkel::DrawMessages(); + CGarages::PrintMessages(); + CPad::PrintErrorMessage(); + CFont::DrawFonts(); + +#ifdef DEBUGMENU + DebugMenuRender(); +#endif + POP_RENDERGROUP(); +} + +void +RenderMenus(void) +{ + if (FrontEndMenuManager.m_bMenuActive) + { + PUSH_RENDERGROUP("RenderMenus"); + PUSH_MEMID(MEMID_FRONTEND); + FrontEndMenuManager.DrawFrontEnd(); + POP_MEMID(); + POP_RENDERGROUP(); + } +} + +void +Render2dStuffAfterFade(void) +{ + PUSH_RENDERGROUP("Render2dStuffAfterFade"); +#ifndef MASTER + DisplayGameDebugText(); +#endif + + CHud::DrawAfterFade(); + CFont::DrawFonts(); + POP_RENDERGROUP(); +} + +void +Idle(void *arg) +{ +#ifdef ASPECT_RATIO_SCALE + CDraw::SetAspectRatio(CDraw::FindAspectRatio()); +#endif + + CTimer::Update(); + + tbInit(); + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + // We're basically merging FrontendIdle and Idle (just like TheGame on PS2) +#ifdef PS2_SAVE_DIALOG + // Only exists on PC FrontendIdle, probably some PS2 bug fix + if (FrontEndMenuManager.m_bMenuActive) + CSprite2d::SetRecipNearClip(); + + if (FrontEndMenuManager.m_bGameNotLoaded) { + CPad::UpdatePads(); + FrontEndMenuManager.Process(); + } else { + PUSH_MEMID(MEMID_GAME_PROCESS); + CPointLights::InitPerFrame(); + tbStartTimer(0, "CGame::Process"); + CGame::Process(); + tbEndTimer("CGame::Process"); + POP_MEMID(); + + tbStartTimer(0, "DMAudio.Service"); + DMAudio.Service(); + tbEndTimer("DMAudio.Service"); + } + + if (RsGlobal.quit) + return; +#else + + PUSH_MEMID(MEMID_GAME_PROCESS); + CPointLights::InitPerFrame(); + + tbStartTimer(0, "CGame::Process"); + CGame::Process(); + tbEndTimer("CGame::Process"); + POP_MEMID(); + + tbStartTimer(0, "DMAudio.Service"); + DMAudio.Service(); + tbEndTimer("DMAudio.Service"); +#endif + + if(CGame::bDemoMode && CTimer::GetTimeInMilliseconds() > (3*60 + 30)*1000 && !CCutsceneMgr::IsCutsceneProcessing()){ + WANT_TO_LOAD = false; + FrontEndMenuManager.m_bWantToRestart = true; + return; + } + + if(FrontEndMenuManager.m_bWantToRestart || FOUND_GAME_TO_LOAD) + { + return; + } + + SetLightsWithTimeOfDayColour(Scene.world); + + if(arg == nil) + return; + + PUSH_MEMID(MEMID_RENDER); + + if((!FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bRenderGameInMenu) && + TheCamera.GetScreenFadeStatus() != FADE_2) + { +#if defined(GTA_PC) && !defined(RW_GL3) && defined(FIX_BUGS) + // This is from SA, but it's nice for windowed mode + if (!FrontEndMenuManager.m_bRenderGameInMenu) { + RwV2d pos; + pos.x = SCREEN_WIDTH / 2.0f; + pos.y = SCREEN_HEIGHT / 2.0f; + RsMouseSetPos(&pos); + } +#endif + + PUSH_MEMID(MEMID_RENDERLIST); + tbStartTimer(0, "CnstrRenderList"); +#ifdef NEW_RENDERER + if(gbNewRenderer){ + CWorld::AdvanceCurrentScanCode(); // don't think this is even necessary + CRenderer::ClearForFrame(); + } +#endif + CRenderer::ConstructRenderList(); + tbEndTimer("CnstrRenderList"); + + tbStartTimer(0, "PreRender"); + CRenderer::PreRender(); + tbEndTimer("PreRender"); + POP_MEMID(); + +#ifdef FIX_BUGS + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); // TODO: temp? this fixes OpenGL render but there should be a better place for this + // This has to be done BEFORE RwCameraBeginUpdate + RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip()); + RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart()); +#endif + + if(CWeather::LightningFlash && !CCullZones::CamNoRain()){ + if(!DoRWStuffStartOfFrame_Horizon(255, 255, 255, 255, 255, 255, 255)) + goto popret; + }else{ + if(!DoRWStuffStartOfFrame_Horizon(CTimeCycle::GetSkyTopRed(), CTimeCycle::GetSkyTopGreen(), CTimeCycle::GetSkyTopBlue(), + CTimeCycle::GetSkyBottomRed(), CTimeCycle::GetSkyBottomGreen(), CTimeCycle::GetSkyBottomBlue(), + 255)) + goto popret; + } + + DefinedState(); + +#ifndef FIX_BUGS + RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip()); + RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart()); +#endif + + tbStartTimer(0, "RenderScene"); + RenderScene(); + tbEndTimer("RenderScene"); + +#ifdef EXTENDED_PIPELINES + CustomPipes::EnvMapRender(); +#endif + + RenderDebugShit(); + RenderEffects(); + + if((TheCamera.m_BlurType == MOTION_BLUR_NONE || TheCamera.m_BlurType == MOTION_BLUR_LIGHT_SCENE) && + TheCamera.m_ScreenReductionPercentage > 0.0f) + TheCamera.SetMotionBlurAlpha(150); + +#ifdef SCREEN_DROPLETS + CPostFX::GetBackBuffer(Scene.camera); + ScreenDroplets::Process(); + ScreenDroplets::Render(); +#endif + + tbStartTimer(0, "RenderMotionBlur"); + TheCamera.RenderMotionBlur(); + tbEndTimer("RenderMotionBlur"); + + tbStartTimer(0, "Render2dStuff"); + Render2dStuff(); + tbEndTimer("Render2dStuff"); + }else{ +#ifdef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, DEFAULT_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, CLEARMODE); + if(!RsCameraBeginUpdate(Scene.camera)) + goto popret; + } + +#ifdef PS2_SAVE_DIALOG + if (FrontEndMenuManager.m_bMenuActive) + DefinedState(); +#endif + tbStartTimer(0, "RenderMenus"); + RenderMenus(); + tbEndTimer("RenderMenus"); + +#ifdef PS2_MENU + if ( TheMemoryCard.m_bWantToLoad ) + goto popret; +#endif + + tbStartTimer(0, "DoFade"); + DoFade(); + tbEndTimer("DoFade"); + + tbStartTimer(0, "Render2dStuff-Fade"); + Render2dStuffAfterFade(); + tbEndTimer("Render2dStuff-Fade"); + + CCredits::Render(); + + + if (gbShowTimebars) + tbDisplay(); + + DoRWStuffEndOfFrame(); + + POP_MEMID(); // MEMID_RENDER + + if(g_SlowMode) + ProcessSlowMode(); + return; + +popret: POP_MEMID(); // MEMID_RENDER +} + +void +FrontendIdle(void) +{ +#ifdef ASPECT_RATIO_SCALE + CDraw::SetAspectRatio(CDraw::FindAspectRatio()); +#endif + + CTimer::Update(); + CSprite2d::SetRecipNearClip(); // this should be on InitialiseRenderWare according to PS2 asm. seems like a bug fix + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + CPad::UpdatePads(); + FrontEndMenuManager.Process(); + + if(RsGlobal.quit) + return; + +#ifdef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, DEFAULT_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, CLEARMODE); + if(!RsCameraBeginUpdate(Scene.camera)) + return; + + DefinedState(); // seems redundant, but breaks resolution change. + RenderMenus(); + DoFade(); + Render2dStuffAfterFade(); +// CFont::DrawFonts(); // redundant + DoRWStuffEndOfFrame(); +} + +void +InitialiseGame(void) +{ + LoadingScreen(nil, nil, "loadsc0"); + CGame::Initialise("DATA\\GTA3.DAT"); +} + +RsEventStatus +AppEventHandler(RsEvent event, void *param) +{ + switch( event ) + { + case rsINITIALIZE: + { + CGame::InitialiseOnceBeforeRW(); + return RsInitialize() ? rsEVENTPROCESSED : rsEVENTERROR; + } + + case rsCAMERASIZE: + { + + CameraSize(Scene.camera, (RwRect *)param, + SCREEN_VIEWWINDOW, DEFAULT_ASPECT_RATIO); + + return rsEVENTPROCESSED; + } + + case rsRWINITIALIZE: + { + return Initialise3D(param) ? rsEVENTPROCESSED : rsEVENTERROR; + } + + case rsRWTERMINATE: + { + Terminate3D(); + + return rsEVENTPROCESSED; + } + + case rsTERMINATE: + { + CGame::FinalShutdown(); + + return rsEVENTPROCESSED; + } + + case rsPLUGINATTACH: + { + return PluginAttach() ? rsEVENTPROCESSED : rsEVENTERROR; + } + + case rsINPUTDEVICEATTACH: + { + AttachInputDevices(); + + return rsEVENTPROCESSED; + } + + case rsIDLE: + { + Idle(param); + + return rsEVENTPROCESSED; + } + + case rsFRONTENDIDLE: + { +#ifdef PS2_SAVE_DIALOG + Idle((void*)1); +#else + FrontendIdle(); +#endif + + return rsEVENTPROCESSED; + } + + case rsACTIVATE: + { + param ? DMAudio.ReacquireDigitalHandle() : DMAudio.ReleaseDigitalHandle(); + + return rsEVENTPROCESSED; + } + + default: + { + return rsEVENTNOTPROCESSED; + } + } +} + +#ifndef MASTER +void +TheModelViewer(void) +{ +#if (defined(GTA_PS2) || defined(GTA_XBOX)) + //TODO +#else + // This is III Mobile code. III Xbox code run it like main function, which is impossible to implement on PC's state machine implementation. + // Also we want 2D things initialized in here to print animation ids etc., our additions for that marked with X + +#ifdef ASPECT_RATIO_SCALE + CDraw::SetAspectRatio(CDraw::FindAspectRatio()); // X +#endif + CAnimViewer::Update(); + CTimer::Update(); + SetLightsWithTimeOfDayColour(Scene.world); + CRenderer::ConstructRenderList(); + DoRWStuffStartOfFrame(CTimeCycle::GetSkyTopRed(), CTimeCycle::GetSkyTopGreen(), CTimeCycle::GetSkyTopBlue(), + CTimeCycle::GetSkyBottomRed(), CTimeCycle::GetSkyBottomGreen(), CTimeCycle::GetSkyBottomBlue(), + 255); + + CSprite2d::InitPerFrame(); // X + CFont::InitPerFrame(); // X + DefinedState(); + CVisibilityPlugins::InitAlphaEntityList(); + CAnimViewer::Render(); + Render2dStuff(); // X + DoRWStuffEndOfFrame(); +#endif +} +#endif + + +#ifdef GTA_PS2 +void TheGame(void) +{ + printf("Into TheGame!!!\n"); + + PUSH_MEMID(MEMID_GAME); // NB: not popped + + CTimer::Initialise(); + +#if GTA_VERSION <= GTA3_PS2_160 + CGame::Initialise(); +#else + CGame::Initialise("DATA\\GTA3.DAT"); +#endif + + Const char *splash = GetRandomSplashScreen(); // inlined here + + LoadingScreen("Starting Game", NULL, splash); + +#ifdef GTA_PS2 + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) == CMemoryCard::NO_ERR_SUCCESS + && TheMemoryCard.ChangeDirectory(CARD_ONE, TheMemoryCard.Cards[CARD_ONE].dir) + && TheMemoryCard.FindMostRecentFileName(CARD_ONE, TheMemoryCard.MostRecentFile) == true + && TheMemoryCard.CheckDataNotCorrupt(TheMemoryCard.MostRecentFile)) + { + strcpy(TheMemoryCard.LoadFileName, TheMemoryCard.MostRecentFile); + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = true; + + if (CMenuManager::m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad()) + { + CMenuManager::m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad(); + TheText.Unload(); + TheText.Load(); + } + + CGame::currLevel = TheMemoryCard.GetLevelToLoad(); + } +#else + //TODO +#endif + + while (true) + { + if (WANT_TO_LOAD) + { + Const char *splash1 = GetLevelSplashScreen(CGame::currLevel); + LoadSplash(splash1); + } + + WANT_TO_LOAD = false; + + CTimer::Update(); + + while (!(FrontEndMenuManager.m_bWantToRestart || FOUND_GAME_TO_LOAD)) + { + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + PUSH_MEMID(MEMID_GAME_PROCESS) + CPointLights::InitPerFrame(); + CGame::Process(); + POP_MEMID(); + + DMAudio.Service(); + + if (CGame::bDemoMode && CTimer::GetTimeInMilliseconds() > (3*60 + 30)*1000 && !CCutsceneMgr::IsCutsceneProcessing()) + { + WANT_TO_LOAD = false; + FrontEndMenuManager.m_bWantToRestart = true; + break; + } + + if (FrontEndMenuManager.m_bWantToRestart || FOUND_GAME_TO_LOAD) + break; + + SetLightsWithTimeOfDayColour(Scene.world); + + PUSH_MEMID(MEMID_RENDER); + + if ((!FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bRenderGameInMenu == true) && TheCamera.GetScreenFadeStatus() != FADE_2 ) + { + + PUSH_MEMID(MEMID_RENDERLIST); + CRenderer::ConstructRenderList(); + CRenderer::PreRender(); + POP_MEMID(); + +#ifdef FIX_BUGS + // This has to be done BEFORE RwCameraBeginUpdate + RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip()); + RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart()); +#endif + + if (CWeather::LightningFlash && !CCullZones::CamNoRain()) + DoRWStuffStartOfFrame_Horizon(255, 255, 255, 255, 255, 255, 255); + else + DoRWStuffStartOfFrame_Horizon(CTimeCycle::GetSkyTopRed(), CTimeCycle::GetSkyTopGreen(), CTimeCycle::GetSkyTopBlue(), CTimeCycle::GetSkyBottomRed(), CTimeCycle::GetSkyBottomGreen(), CTimeCycle::GetSkyBottomBlue(), 255); + + DefinedState(); +#ifndef FIX_BUGS + RwCameraSetFarClipPlane(Scene.camera, CTimeCycle::GetFarClip()); + RwCameraSetFogDistance(Scene.camera, CTimeCycle::GetFogStart()); +#endif + + RenderScene(); + RenderDebugShit(); + RenderEffects(); + + if ((TheCamera.m_BlurType == MOTION_BLUR_NONE || TheCamera.m_BlurType == MOTION_BLUR_LIGHT_SCENE) && TheCamera.m_ScreenReductionPercentage > 0.0f) + TheCamera.SetMotionBlurAlpha(150); + TheCamera.RenderMotionBlur(); + + Render2dStuff(); + } + else + { +#ifdef ASPECT_RATIO_SCALE + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); +#else + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, DEFAULT_ASPECT_RATIO); +#endif + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, CLEARMODE); + RsCameraBeginUpdate(Scene.camera); + } + + RenderMenus(); + + if (WANT_TO_LOAD) + { + POP_MEMID(); // MEMID_RENDER + break; + } + + DoFade(); + Render2dStuffAfterFade(); + CCredits::Render(); + + DoRWStuffEndOfFrame(); + + while (frameCount < 2) + ; + + frameCount = 0; + + CTimer::Update(); + + POP_MEMID(): // MEMID_RENDER + + if (g_SlowMode) + ProcessSlowMode(); + } + + CPad::ResetCheats(); + CPad::StopPadsShaking(); + DMAudio.ChangeMusicMode(MUSICMODE_DISABLE); + CGame::ShutDownForRestart(); + CTimer::Stop(); + + if (FrontEndMenuManager.m_bWantToRestart || FOUND_GAME_TO_LOAD) + { + if (FOUND_GAME_TO_LOAD) + { + FrontEndMenuManager.m_bWantToRestart = true; + WANT_TO_LOAD = true; + } + + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + FrontEndMenuManager.m_bWantToRestart = false; + + continue; + } + + break; + } + + DMAudio.Terminate(); +} + + +void SystemInit() +{ +#ifdef __MWERKS__ + mwInit(); +#endif + +#ifdef USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + +#ifdef GTA_PS2 + CFileMgr::InitCdSystem(); + + char path[256]; + + sprintf(path, "cdrom0:\\%s%s;1", "SYSTEM\\", "IOPRP23.IMG"); + + sceSifInitRpc(0); + + while ( !sceSifRebootIop(path) ) + ; + while( !sceSifSyncIop() ) + ; + + sceSifInitRpc(0); + + CFileMgr::InitCdSystem(); + + sceFsReset(); +#endif + + CFileMgr::Initialise(); + +#ifdef GTA_PS2 + CFileMgr::InitCd(); + + char modulepath[256]; + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "SIO2MAN.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "PADMAN.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "LIBSD.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "SDRDRV.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "MCMAN.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "MCSERV.IRX"); + LoadModule(modulepath); +#endif + + +#ifdef GTA_PS2 + ThreadParam param; + + param.entry = &IdleThread; + param.stack = idleThreadStack; + param.stackSize = 2048; + param.initPriority = 127; + param.gpReg = &_gp; + + int thread = CreateThread(¶m); + StartThread(thread, NULL); +#else + // +#endif + +#ifdef GTA_PS2_STUFF + CPad::Initialise(); +#endif + CPad::GetPad(0)->Mode = 0; + + CGame::frenchGame = false; + CGame::germanGame = false; + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + +#ifdef GTA_PS2 + int32 lang = sceScfGetLanguage(); + if ( lang == SCE_ITALIAN_LANGUAGE ) + CMenuManager::m_PrefsLanguage = LANGUAGE_ITALIAN; + else if ( lang == SCE_SPANISH_LANGUAGE ) + CMenuManager::m_PrefsLanguage = LANGUAGE_SPANISH; + else if ( lang == SCE_GERMAN_LANGUAGE ) + { + CGame::germanGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = LANGUAGE_GERMAN; + } + else if ( lang == SCE_FRENCH_LANGUAGE ) + { + CGame::frenchGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = LANGUAGE_FRENCH; + } + else + CMenuManager::m_PrefsLanguage = LANGUAGE_AMERICAN; + + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); +#else + // +#endif + +#ifdef GTA_PS2 + TheMemoryCard.Init(); +#endif +} + +int VBlankCounter(int ca) +{ + frameCount++; + ExitHandler(); + return 0; +} + +// linked against by RW! +extern "C" void WaitVBlank(void) +{ + int32 startFrame = frameCount; + while(startFrame == frameCount); +} + +void GameInit() +{ + if ( !gameAlreadyInitialised ) + { +#ifdef GTA_PS2 + char path[256]; + + strcpy(path, "cdrom0:\\"); + strcat(path, "SYSTEM\\"); + strcat(path, "CDSTREAM.IRX"); + LoadModule(path); + + strcpy(path, "cdrom0:\\"); + strcat(path, "SYSTEM\\"); + strcat(path, "SAMPMAN.IRX"); + LoadModule(path); + + strcpy(path, "cdrom0:\\"); + strcat(path, "SYSTEM\\"); + strcat(path, "MUSICSTR.IRX"); + LoadModule(path); +#endif + CdStreamInit(MAX_CDCHANNELS); + +#ifdef GTA_PS2 + Initialise3D(); //no params +#else + //TODO +#endif + +#ifdef GTA_PS2 + char *files[] = + { + "\\ANIM\\CUTS.IMG;1", + "\\ANIM\\CUTS.DIR;1", + "\\ANIM\\PED.IFP;1", + "\\MODELS\\FRONTEND.TXD;1", + "\\MODELS\\FONTS.TXD;1", + "\\MODELS\\HUD.TXD;1", + "\\MODELS\\PARTICLE.TXD;1", + "\\MODELS\\MISC.TXD;1", + "\\MODELS\\GENERIC.TXD;1", + "\\MODELS\\GTA3.DIR;1", + // TODO: japanese? +#ifdef GTA_PAL + "\\TEXT\\ENGLISH.GXT;1", + "\\TEXT\\FRENCH.GXT;1", + "\\TEXT\\GERMAN.GXT;1", + "\\TEXT\\ITALIAN.GXT;1", + "\\TEXT\\SPANISH.GXT;1", +#else + "\\TEXT\\AMERICAN.GXT;1", +#endif + "\\TXD\\LOADSC0.TXD;1", + "\\TXD\\LOADSC1.TXD;1", + "\\TXD\\LOADSC2.TXD;1", + "\\TXD\\LOADSC3.TXD;1", + "\\TXD\\LOADSC4.TXD;1", + "\\TXD\\LOADSC5.TXD;1", + "\\TXD\\LOADSC6.TXD;1", + "\\TXD\\LOADSC7.TXD;1", + "\\TXD\\LOADSC8.TXD;1", + "\\TXD\\LOADSC9.TXD;1", + "\\TXD\\LOADSC10.TXD;1", + "\\TXD\\LOADSC11.TXD;1", + "\\TXD\\LOADSC12.TXD;1", + "\\TXD\\LOADSC13.TXD;1", + "\\TXD\\LOADSC14.TXD;1", + "\\TXD\\LOADSC15.TXD;1", + "\\TXD\\LOADSC16.TXD;1", + "\\TXD\\LOADSC17.TXD;1", + "\\TXD\\LOADSC18.TXD;1", + "\\TXD\\LOADSC19.TXD;1", + "\\TXD\\LOADSC20.TXD;1", + "\\TXD\\LOADSC21.TXD;1", + "\\TXD\\LOADSC22.TXD;1", + "\\TXD\\LOADSC23.TXD;1", + "\\TXD\\LOADSC24.TXD;1", + "\\TXD\\LOADSC25.TXD;1", + "\\TXD\\NEWS.TXD;1", + "\\MODELS\\COLL\\GENERIC.COL;1", + "\\MODELS\\COLL\\INDUST.COL;1", + "\\MODELS\\COLL\\COMMER.COL;1", + "\\MODELS\\COLL\\SUBURB.COL;1", + "\\MODELS\\COLL\\WEAPONS.COL;1", + "\\MODELS\\COLL\\VEHICLES.COL;1", + "\\MODELS\\COLL\\PEDS.COL;1", + "\\MODELS\\GENERIC\\AIR_VLO.DFF;1", + "\\MODELS\\GENERIC\\WEAPONS.DFF;1", + "\\MODELS\\GENERIC\\WHEELS.DFF;1", + "\\MODELS\\GENERIC\\LOPLYGUY.DFF;1", + "\\MODELS\\GENERIC\\ARROW.DFF;1", + "\\MODELS\\GENERIC\\ZONECYLB.DFF;1", + "\\DATA\\MAPS\\COMNTOP.IPL;1", + "\\DATA\\MAPS\\COMNBTM.IPL;1", + "\\DATA\\MAPS\\COMSE.IPL;1", + "\\DATA\\MAPS\\COMSW.IPL;1", + "\\DATA\\MAPS\\CULL.IPL;1", + "\\DATA\\MAPS\\INDUSTNE.IPL;1", + "\\DATA\\MAPS\\INDUSTNW.IPL;1", + "\\DATA\\MAPS\\INDUSTSE.IPL;1", + "\\DATA\\MAPS\\INDUSTSW.IPL;1", + "\\DATA\\MAPS\\SUBURBNE.IPL;1", + "\\DATA\\MAPS\\SUBURBSW.IPL;1", + "\\DATA\\MAPS\\OVERVIEW.IPL;1", + "\\DATA\\MAPS\\PROPS.IPL;1", + "\\DATA\\MAPS\\GTA3.IDE;1", + "\\DATA\\PATHS\\FLIGHT.DAT;1", + "\\DATA\\PATHS\\FLIGHT2.DAT;1", + "\\DATA\\PATHS\\FLIGHT3.DAT;1", + "\\DATA\\PATHS\\FLIGHT4.DAT;1", + "\\DATA\\PATHS\\TRACKS.DAT;1", + "\\DATA\\PATHS\\TRACKS2.DAT;1", + "\\DATA\\PATHS\\CHASE0.DAT;1", + "\\DATA\\PATHS\\CHASE1.DAT;1", + "\\DATA\\PATHS\\CHASE2.DAT;1", + "\\DATA\\PATHS\\CHASE3.DAT;1", + "\\DATA\\PATHS\\CHASE4.DAT;1", + "\\DATA\\PATHS\\CHASE5.DAT;1", + "\\DATA\\PATHS\\CHASE6.DAT;1", + "\\DATA\\PATHS\\CHASE7.DAT;1", + "\\DATA\\PATHS\\CHASE10.DAT;1", + "\\DATA\\PATHS\\CHASE11.DAT;1", + "\\DATA\\PATHS\\CHASE14.DAT;1", + "\\DATA\\PATHS\\CHASE16.DAT;1", + "\\DATA\\PATHS\\CHASE18.DAT;1", + "\\DATA\\PATHS\\CHASE19.DAT;1" + }; + + for ( int32 i = 0; i < ARRAY_SIZE(files); i++ ) + SkyRegisterFileOnCd([i]); +#endif + + CreateDebugFont(); + +#ifdef GTA_PS2 + AddIntcHandler(INTC_VBLANK_S, VBlankCounter, 0); +#endif + + CameraSize(Scene.camera, NULL, DEFAULT_VIEWWINDOW, DEFAULT_ASPECT_RATIO); + + CSprite2d::SetRecipNearClip(); + CTxdStore::Initialise(); + + PUSH_MEMID(MEMID_TEXTURES); + CFont::Initialise(); + CHud::Initialise(); + POP_MEMID(); + + ValidateVersion(); + +#ifdef GTA_PS2 + sceCdCLOCK rtc; + sceCdReadClock(&rtc); + uint32 seed = rtc.minute + rtc.day; + uint32 seed2 = (seed << 4)-seed; + uint32 seed3 = (seed2 << 4)-seed2; + srand ((seed3<<4)+rtc.second); +#else + //TODO: mysrand(); +#endif + + gameAlreadyInitialised = true; + } +} + +void PlayIntroMPEGs() +{ +#ifdef GTA_PS2 + if (gameAlreadyInitialised) + RpSkySuspend(); + + InitMPEGPlayer(); + +#ifdef GTA_PAL + PlayMPEG("cdrom0:\\MOVIES\\DMAPAL.PSS;1", false); + + if (CGame::frenchGame || CGame::germanGame) + PlayMPEG("cdrom0:\\MOVIES\\INTROPAF.PSS;1", true); + else + PlayMPEG("cdrom0:\\MOVIES\\INTROPAL.PSS;1", true); +#else + PlayMPEG("cdrom0:\\MOVIES\\DMANTSC.PSS;1", false); + + PlayMPEG("cdrom0:\\MOVIES\\INTRNTSC.PSS;1", true); +#endif + + ShutdownMPEGPlayer(); + + if ( gameAlreadyInitialised ) + RpSkyResume(); +#else + //TODO +#endif +} + +int +main(int argc, char *argv[]) +{ +#ifdef __MWERKS__ + mwInit(); // metrowerks initialisation +#endif + + SystemInit(); + +#ifdef GTA_PS2 + int32 r = TheMemoryCard.CheckCardStateAtGameStartUp(CARD_ONE); + + if ( r == CMemoryCard::ERR_DIRNOENTRY || r == CMemoryCard::ERR_NOFORMAT ) + { + GameInit(); + + TheText.Unload(); + TheText.Load(); + + CFont::Initialise(); + + FrontEndMenuManager.DrawMemoryCardStartUpMenus(); + }else if(r == CMemoryCard::ERR_OPENNOENTRY || r == CMemoryCard::ERR_NONE){ + // eh? + } +#endif + + PlayIntroMPEGs(); + + GameInit(); + + if ( CGame::frenchGame || CGame::germanGame ) + LoadingScreen(NULL, version_name, "loadsc24"); + else + LoadingScreen(NULL, version_name, "loadsc0"); + + DMAudio.Initialise(); + + TheGame(); + + CGame::ShutDown(); + + RwEngineStop(); + RwEngineClose(); + RwEngineTerm(); + +#ifdef __MWERKS__ + mwExit(); // metrowerks shutdown +#endif + + return 0; +} +#endif diff --git a/src/core/main.h b/src/core/main.h new file mode 100644 index 0000000..803afb1 --- /dev/null +++ b/src/core/main.h @@ -0,0 +1,76 @@ +#pragma once + +#ifndef FINAL +// defined in RwHelpder.cpp +void PushRendergroup(const char *name); +void PopRendergroup(void); +#define PUSH_RENDERGROUP(str) PushRendergroup(str) +#define POP_RENDERGROUP() PopRendergroup() +#else +#define PUSH_RENDERGROUP(str) +#define POP_RENDERGROUP() +#endif + +struct GlobalScene +{ + RpWorld *world; + RwCamera *camera; +}; +extern GlobalScene Scene; + +extern uint8 work_buff[55000]; +extern char gString[256]; +extern char gString2[512]; +extern wchar gUString[256]; +extern wchar gUString2[256]; +extern bool gbPrintShite; +extern bool gbModelViewer; +#ifdef TIMEBARS +extern bool gbShowTimebars; +#else +#define gbShowTimebars false +#endif + +#ifndef FINAL +extern bool gbPrintMemoryUsage; +#endif + +class CSprite2d; + +bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +bool DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); +void PreAllocateRwObjects(void); +void InitialiseGame(void); +void LoadingScreen(const char *str1, const char *str2, const char *splashscreen); +void LoadingIslandScreen(const char *levelName); +CSprite2d *LoadSplash(const char *name); +void DestroySplashScreen(void); +Const char *GetLevelSplashScreen(int level); +Const char *GetRandomSplashScreen(void); +void LittleTest(void); +void ValidateVersion(); +void ResetLoadingScreenBar(void); +#ifndef MASTER +void TheModelViewer(void); +#endif + +#ifdef LOAD_INI_SETTINGS +bool LoadINISettings(); +void SaveINISettings(); +void LoadINIControllerSettings(); +void SaveINIControllerSettings(); +#endif + +#ifdef NEW_RENDERER +extern bool gbNewRenderer; +bool FredIsInFirstPersonCam(void); +#endif + +#ifdef DRAW_GAME_VERSION_TEXT +extern bool gbDrawVersionText; +#endif + +#ifdef NO_MOVIES +extern bool gbNoMovies; +#endif diff --git a/src/core/obrstr.cpp b/src/core/obrstr.cpp new file mode 100644 index 0000000..d9f7e9b --- /dev/null +++ b/src/core/obrstr.cpp @@ -0,0 +1,119 @@ +#include "common.h" +#include "Debug.h" +#include "obrstr.h" + +char obrstr[128]; +char obrstr2[128]; + +void ObrInt(int32 n1) +{ + IntToStr(n1, obrstr); + CDebug::DebugAddText(obrstr); +} + +void ObrInt2(int32 n1, int32 n2) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void ObrInt3(int32 n1, int32 n2, int32 n3) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n3, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void ObrInt4(int32 n1, int32 n2, int32 n3, int32 n4) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n3, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n4, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void ObrInt5(int32 n1, int32 n2, int32 n3, int32 n4, int32 n5) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n3, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n4, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n5, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void ObrInt6(int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + IntToStr(n1, obrstr); + strcat(obrstr, " "); + IntToStr(n2, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n3, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n4, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n5, obrstr2); + strcat(obrstr, obrstr2); + strcat(obrstr, " "); + IntToStr(n6, obrstr2); + strcat(obrstr, obrstr2); + CDebug::DebugAddText(obrstr); +} + +void IntToStr(int32 inNum, char *outStr) +{ + bool isNeg = inNum < 0; + + if (isNeg) { + inNum = -inNum; + *outStr = '-'; + } + + int16 digits = 1; + + if (inNum > 9) { + int32 _inNum = inNum; + do { + digits++; + _inNum /= 10; + } while (_inNum > 9); + } + + int32 strSize = digits; + if (isNeg) + strSize++; + + char *pStr = &outStr[strSize]; + int32 i = 0; + do { + *(pStr-- - 1) = (inNum % 10) + '0'; + inNum /= 10; + } while (++i < strSize); + outStr[strSize] = '\0'; +} \ No newline at end of file diff --git a/src/core/obrstr.h b/src/core/obrstr.h new file mode 100644 index 0000000..c163361 --- /dev/null +++ b/src/core/obrstr.h @@ -0,0 +1,9 @@ +#pragma once + +void ObrInt(int32 n1); +void ObrInt2(int32 n1, int32 n2); +void ObrInt3(int32 n1, int32 n2, int32 n3); +void ObrInt4(int32 n1, int32 n2, int32 n3, int32 n4); +void ObrInt5(int32 n1, int32 n2, int32 n3, int32 n4, int32 n5); +void ObrInt6(int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); +void IntToStr(int32 inNum, char *outStr); \ No newline at end of file diff --git a/src/core/re3.cpp b/src/core/re3.cpp new file mode 100644 index 0000000..a721c23 --- /dev/null +++ b/src/core/re3.cpp @@ -0,0 +1,1233 @@ +#include +#define WITHWINDOWS +#include "common.h" +#if defined DETECT_JOYSTICK_MENU && defined XINPUT +#include +#if !defined(PSAPI_VERSION) || (PSAPI_VERSION > 1) +#pragma comment( lib, "Xinput9_1_0.lib" ) +#else +#pragma comment( lib, "Xinput.lib" ) +#endif +#endif +#include "Renderer.h" +#include "Credits.h" +#include "Camera.h" +#include "Weather.h" +#include "Clock.h" +#include "World.h" +#include "Vehicle.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "PathFind.h" +#include "Boat.h" +#include "Heli.h" +#include "Automobile.h" +#include "Console.h" +#include "Debug.h" +#include "Hud.h" +#include "SceneEdit.h" +#include "Pad.h" +#include "PlayerPed.h" +#include "Radar.h" +#include "debugmenu.h" +#include "Frontend.h" +#include "WaterLevel.h" +#include "main.h" +#include "Script.h" +#include "postfx.h" +#include "custompipes.h" +#include "MemoryHeap.h" +#include "FileMgr.h" +#include "Camera.h" +#include "MBlur.h" +#include "ControllerConfig.h" +#include "CarCtrl.h" +#include "Population.h" +#include "IniFile.h" +#include "Zones.h" + +#include "crossplatform.h" + +#ifndef _WIN32 +#include "assert.h" +#include +#endif + +#ifdef RWLIBS +extern "C" int vsprintf(char* const _Buffer, char const* const _Format, va_list _ArgList); +#endif + + +#ifdef USE_PS2_RAND +unsigned long long myrand_seed = 1; +#else +unsigned long int myrand_seed = 1; +#endif + +int +myrand(void) +{ +#ifdef USE_PS2_RAND + // Use our own implementation of rand, stolen from PS2 + myrand_seed = 0x5851F42D4C957F2D * myrand_seed + 1; + return ((myrand_seed >> 32) & 0x7FFFFFFF); +#else + // or original codewarrior rand + myrand_seed = myrand_seed * 1103515245 + 12345; + return((myrand_seed >> 16) & 0x7FFF); +#endif +} + +void +mysrand(unsigned int seed) +{ + myrand_seed = seed; +} + +#ifdef CUSTOM_FRONTEND_OPTIONS +#include "frontendoption.h" + +#ifdef MORE_LANGUAGES +void LangPolSelect(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_POLISH; + FrontEndMenuManager.m_bFrontEnd_ReloadObrTxtGxt = true; + FrontEndMenuManager.InitialiseChangedLanguageSettings(); + FrontEndMenuManager.SaveSettings(); + } +} + +void LangRusSelect(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_RUSSIAN; + FrontEndMenuManager.m_bFrontEnd_ReloadObrTxtGxt = true; + FrontEndMenuManager.InitialiseChangedLanguageSettings(); + FrontEndMenuManager.SaveSettings(); + } +} + +void LangJapSelect(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_JAPANESE; + FrontEndMenuManager.m_bFrontEnd_ReloadObrTxtGxt = true; + FrontEndMenuManager.InitialiseChangedLanguageSettings(); + FrontEndMenuManager.SaveSettings(); + } +} +#endif + +void +CustomFrontendOptionsPopulate(void) +{ + // Most of custom options are done statically in MenuScreensCustom.cpp, we add them here only if they're dependent to extra files + + // These work only if we have neo folder + int fd; +#ifdef EXTENDED_PIPELINES + const char *vehPipelineNames[] = { "FED_MFX", "FED_NEO" }; + const char *off_on[] = { "FEM_OFF", "FEM_ON" }; + fd = CFileMgr::OpenFile("neo/neo.txd","r"); + if (fd) { +#ifdef GRAPHICS_MENU_OPTIONS + FrontendOptionSetCursor(MENUPAGE_GRAPHICS_SETTINGS, -3, false); + FrontendOptionAddSelect("FED_VPL", vehPipelineNames, ARRAY_SIZE(vehPipelineNames), (int8*)&CustomPipes::VehiclePipeSwitch, false, nil, "Graphics", "VehiclePipeline"); + FrontendOptionAddSelect("FED_PRM", off_on, 2, (int8*)&CustomPipes::RimlightEnable, false, nil, "Graphics", "NeoRimLight"); + FrontendOptionAddSelect("FED_WLM", off_on, 2, (int8*)&CustomPipes::LightmapEnable, false, nil, "Graphics", "NeoLightMaps"); + FrontendOptionAddSelect("FED_RGL", off_on, 2, (int8*)&CustomPipes::GlossEnable, false, nil, "Graphics", "NeoRoadGloss"); +#else + FrontendOptionSetCursor(MENUPAGE_DISPLAY_SETTINGS, -3, false); + FrontendOptionAddSelect("FED_VPL", vehPipelineNames, ARRAY_SIZE(vehPipelineNames), (int8*)&CustomPipes::VehiclePipeSwitch, false, nil, "Graphics", "VehiclePipeline"); + FrontendOptionAddSelect("FED_PRM", off_on, 2, (int8*)&CustomPipes::RimlightEnable, false, nil, "Graphics", "NeoRimLight"); + FrontendOptionAddSelect("FED_WLM", off_on, 2, (int8*)&CustomPipes::LightmapEnable, false, nil, "Graphics", "NeoLightMaps"); + FrontendOptionAddSelect("FED_RGL", off_on, 2, (int8*)&CustomPipes::GlossEnable, false, nil, "Graphics", "NeoRoadGloss"); +#endif + CFileMgr::CloseFile(fd); + } +#endif + + // Add outsourced language translations, if files are found +#ifdef MORE_LANGUAGES + int fd2; + FrontendOptionSetCursor(MENUPAGE_LANGUAGE_SETTINGS, 5, false); + if (fd = CFileMgr::OpenFile("text/polish.gxt","r")) { + if (fd2 = CFileMgr::OpenFile("models/fonts_p.txd","r")) { + FrontendOptionAddDynamic("FEL_POL", nil, nil, LangPolSelect, nil, nil); + CFileMgr::CloseFile(fd2); + } + CFileMgr::CloseFile(fd); + } + + if (fd = CFileMgr::OpenFile("text/russian.gxt","r")) { + if (fd2 = CFileMgr::OpenFile("models/fonts_r.txd","r")) { + FrontendOptionAddDynamic("FEL_RUS", nil, nil, LangRusSelect, nil, nil); + CFileMgr::CloseFile(fd2); + } + CFileMgr::CloseFile(fd); + } + + if (fd = CFileMgr::OpenFile("text/japanese.gxt","r")) { + if (fd2 = CFileMgr::OpenFile("models/fonts_j.txd","r")) { + FrontendOptionAddDynamic("FEL_JAP", nil, nil, LangJapSelect, nil, nil); + CFileMgr::CloseFile(fd2); + } + CFileMgr::CloseFile(fd); + } +#endif + +} +#endif + +#ifdef LOAD_INI_SETTINGS +#define MINI_CASE_SENSITIVE +#include "ini.h" + +mINI::INIFile ini("re3.ini"); +mINI::INIStructure cfg; + +bool ReadIniIfExists(const char *cat, const char *key, uint32 *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtoul(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, uint8 *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtoul(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, bool *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtoul(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, int32 *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtol(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, int8 *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtol(section.get(key).c_str(), &endPtr, 0); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, float *out) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + char *endPtr; + *out = strtof(section.get(key).c_str(), &endPtr); + return true; + } + return false; +} + +bool ReadIniIfExists(const char *cat, const char *key, char *out, int size) +{ + mINI::INIMap section = cfg.get(cat); + if (section.has(key)) { + strncpy(out, section.get(key).c_str(), size - 1); + out[size - 1] = '\0'; + return true; + } + return false; +} + +void StoreIni(const char *cat, const char *key, uint32 val) +{ + char temp[11]; + sprintf(temp, "%u", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, uint8 val) +{ + char temp[11]; + sprintf(temp, "%u", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, int32 val) +{ + char temp[11]; + sprintf(temp, "%d", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, int8 val) +{ + char temp[11]; + sprintf(temp, "%d", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, float val) +{ + char temp[50]; + sprintf(temp, "%f", val); + cfg[cat][key] = temp; +} + +void StoreIni(const char *cat, const char *key, char *val, int size) +{ + cfg[cat][key] = val; +} + +const char *iniControllerActions[] = { "PED_FIREWEAPON", "PED_CYCLE_WEAPON_RIGHT", "PED_CYCLE_WEAPON_LEFT", "GO_FORWARD", "GO_BACK", "GO_LEFT", "GO_RIGHT", "PED_SNIPER_ZOOM_IN", + "PED_SNIPER_ZOOM_OUT", "VEHICLE_ENTER_EXIT", "CAMERA_CHANGE_VIEW_ALL_SITUATIONS", "PED_JUMPING", "PED_SPRINT", "PED_LOOKBEHIND", +#ifdef BIND_VEHICLE_FIREWEAPON + "VEHICLE_FIREWEAPON", +#endif + "VEHICLE_ACCELERATE", "VEHICLE_BRAKE", "VEHICLE_CHANGE_RADIO_STATION", "VEHICLE_HORN", "TOGGLE_SUBMISSIONS", "VEHICLE_HANDBRAKE", "PED_1RST_PERSON_LOOK_LEFT", + "PED_1RST_PERSON_LOOK_RIGHT", "VEHICLE_LOOKLEFT", "VEHICLE_LOOKRIGHT", "VEHICLE_LOOKBEHIND", "VEHICLE_TURRETLEFT", "VEHICLE_TURRETRIGHT", "VEHICLE_TURRETUP", "VEHICLE_TURRETDOWN", + "PED_CYCLE_TARGET_LEFT", "PED_CYCLE_TARGET_RIGHT", "PED_CENTER_CAMERA_BEHIND_PLAYER", "PED_LOCK_TARGET", "NETWORK_TALK", "PED_1RST_PERSON_LOOK_UP", "PED_1RST_PERSON_LOOK_DOWN", + "_CONTROLLERACTION_36", "TOGGLE_DPAD", "SWITCH_DEBUG_CAM_ON", "TAKE_SCREEN_SHOT", "SHOW_MOUSE_POINTER_TOGGLE" }; + +const char *iniControllerTypes[] = { "kbd:", "2ndKbd:", "mouse:", "joy:" }; + +const char *iniMouseButtons[] = {"LEFT","MIDDLE","RIGHT","WHLUP","WHLDOWN","X1","X2"}; + +const char *iniKeyboardButtons[] = {"ESC","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12", + "INS","DEL","HOME","END","PGUP","PGDN","UP","DOWN","LEFT","RIGHT","DIVIDE","TIMES","PLUS","MINUS","PADDEL", + "PADEND","PADDOWN","PADPGDN","PADLEFT","PAD5","NUMLOCK","PADRIGHT","PADHOME","PADUP","PADPGUP","PADINS", + "PADENTER", "SCROLL","PAUSE","BACKSP","TAB","CAPSLK","ENTER","LSHIFT","RSHIFT","SHIFT","LCTRL","RCTRL","LALT", + "RALT", "LWIN", "RWIN", "APPS", "NULL"}; + +void LoadINIControllerSettings() +{ +#ifdef DETECT_JOYSTICK_MENU +#ifdef XINPUT + int storedJoy1 = -1; + if (ReadIniIfExists("Controller", "JoystickName", &storedJoy1)) { + CPad::XInputJoy1 = -1; + CPad::XInputJoy2 = -1; + XINPUT_STATE xstate; + memset(&xstate, 0, sizeof(XINPUT_STATE)); + + // Firstly confirm & set joy 1 + if (XInputGetState(storedJoy1, &xstate) == ERROR_SUCCESS) { + CPad::XInputJoy1 = storedJoy1; + } + + for (int i = 0; i <= 3; i++) { + if (XInputGetState(i, &xstate) == ERROR_SUCCESS) { + if (CPad::XInputJoy1 == -1) + CPad::XInputJoy1 = i; + else if (CPad::XInputJoy2 == -1 && i != CPad::XInputJoy1) + CPad::XInputJoy2 = i; + } + } + + // There is no plug event on XInput, so let's leave XInputJoy1/2 as 0/1 respectively, and hotplug will be possible. + if (CPad::XInputJoy1 == -1) { + CPad::XInputJoy1 = 0; + CPad::XInputJoy2 = 1; + } else if (CPad::XInputJoy2 == -1) { + CPad::XInputJoy2 = (CPad::XInputJoy1 + 1) % 4; + } + } +#else + ReadIniIfExists("Controller", "JoystickName", gSelectedJoystickName, 128); +#endif +#endif + // force to default GTA behaviour (never overwrite bindings on joy change/initialization) if user init'ed/set bindings before we introduced that + if (!ReadIniIfExists("Controller", "PadButtonsInited", &ControlsManager.ms_padButtonsInited)) { + ControlsManager.ms_padButtonsInited = cfg.get("Bindings").size() != 0 ? 16 : 0; + } + + for (int32 i = 0; i < MAX_CONTROLLERACTIONS; i++) { + char value[128]; + if (ReadIniIfExists("Bindings", iniControllerActions[i], value, 128)) { + for (int32 j = 0; j < MAX_CONTROLLERTYPES; j++){ + ControlsManager.ClearSettingsAssociatedWithAction((e_ControllerAction)i, (eControllerType)j); + } + + for (char *binding = strtok(value,", "); binding != nil; binding = strtok(nil, ", ")) { + int contType = -1; + for (int32 k = 0; k < ARRAY_SIZE(iniControllerTypes); k++) { + int len = strlen(iniControllerTypes[k]); + if (strncmp(binding, iniControllerTypes[k], len) == 0) { + contType = k; + binding += len; + break; + } + } + if (contType == -1) + continue; + + int contKey; + if (contType == JOYSTICK) { + char *temp; + contKey = strtol(binding, &temp, 0); + + } else if (contType == KEYBOARD || contType == OPTIONAL_EXTRA) { + if (strlen(binding) == 1) { + contKey = binding[0]; + } else if(strcmp(binding, "SPC") == 0) { + contKey = ' '; + } else { + for (int32 k = 0; k < ARRAY_SIZE(iniKeyboardButtons); k++) { + if(strcmp(binding, iniKeyboardButtons[k]) == 0) { + contKey = 1000 + k; + break; + } + } + } + } else if (contType == MOUSE) { + for (int32 k = 0; k < ARRAY_SIZE(iniMouseButtons); k++) { + if(strcmp(binding, iniMouseButtons[k]) == 0) { + contKey = 1 + k; + break; + } + } + } + + ControlsManager.SetControllerKeyAssociatedWithAction((e_ControllerAction)i, contKey, (eControllerType)contType); + } + } + } +} + +void SaveINIControllerSettings() +{ + for (int32 i = 0; i < MAX_CONTROLLERACTIONS; i++) { + char value[128] = { '\0' }; + + // upper limit should've been GetNumOfSettingsForAction(i), but sadly even R* doesn't use it's own system correctly, and there are gaps between orders. + for (int32 j = SETORDER_1; j < MAX_SETORDERS; j++){ + + // We respect the m_ContSetOrder, and join/implode/order the bindings according to that; using comma as seperator. + for (int32 k = 0; k < MAX_CONTROLLERTYPES; k++){ + if (ControlsManager.m_aSettings[i][k].m_ContSetOrder == j) { + char next[32]; + if (k == JOYSTICK) { + snprintf(next, 32, "%s%d,", iniControllerTypes[k], ControlsManager.m_aSettings[i][k].m_Key); + + } else if (k == KEYBOARD || k == OPTIONAL_EXTRA) { + if (ControlsManager.m_aSettings[i][k].m_Key == ' ') + snprintf(next, 32, "%sSPC,", iniControllerTypes[k]); + else if (ControlsManager.m_aSettings[i][k].m_Key < 256) + snprintf(next, 32, "%s%c,", iniControllerTypes[k], ControlsManager.m_aSettings[i][k].m_Key); + else + snprintf(next, 32, "%s%s,", iniControllerTypes[k], iniKeyboardButtons[ControlsManager.m_aSettings[i][k].m_Key - 1000]); + + } else if (k == MOUSE) { + snprintf(next, 32, "%s%s,", iniControllerTypes[k], iniMouseButtons[ControlsManager.m_aSettings[i][k].m_Key - 1]); + } + strcat(value, next); + break; + } + } + } + int len = strlen(value); + if (len > 0) + value[len - 1] = '\0'; // to remove comma + + StoreIni("Bindings", iniControllerActions[i], value, 128); + } + +#ifdef DETECT_JOYSTICK_MENU +#ifdef XINPUT + StoreIni("Controller", "JoystickName", CPad::XInputJoy1); +#else + StoreIni("Controller", "JoystickName", gSelectedJoystickName, 128); +#endif +#endif + StoreIni("Controller", "PadButtonsInited", ControlsManager.ms_padButtonsInited); + + ini.write(cfg); +} + +bool LoadINISettings() +{ + if (!ini.read(cfg)) + return false; + +#ifdef IMPROVED_VIDEOMODE + ReadIniIfExists("VideoMode", "Width", &FrontEndMenuManager.m_nPrefsWidth); + ReadIniIfExists("VideoMode", "Height", &FrontEndMenuManager.m_nPrefsHeight); + ReadIniIfExists("VideoMode", "Depth", &FrontEndMenuManager.m_nPrefsDepth); + ReadIniIfExists("VideoMode", "Subsystem", &FrontEndMenuManager.m_nPrefsSubsystem); + // Windowed mode is loaded below in CUSTOM_FRONTEND_OPTIONS section +#else + ReadIniIfExists("Graphics", "VideoMode", &FrontEndMenuManager.m_nDisplayVideoMode); +#endif + ReadIniIfExists("Controller", "HeadBob1stPerson", &TheCamera.m_bHeadBob); + ReadIniIfExists("Controller", "VerticalMouseSens", &TheCamera.m_fMouseAccelVertical); + ReadIniIfExists("Controller", "HorizantalMouseSens", &TheCamera.m_fMouseAccelHorzntl); + ReadIniIfExists("Controller", "InvertMouseVertically", &MousePointerStateHelper.bInvertVertically); + ReadIniIfExists("Controller", "DisableMouseSteering", &CVehicle::m_bDisableMouseSteering); + ReadIniIfExists("Controller", "Vibration", &FrontEndMenuManager.m_PrefsUseVibration); + ReadIniIfExists("Audio", "SfxVolume", &FrontEndMenuManager.m_PrefsSfxVolume); + ReadIniIfExists("Audio", "MusicVolume", &FrontEndMenuManager.m_PrefsMusicVolume); + ReadIniIfExists("Audio", "Radio", &FrontEndMenuManager.m_PrefsRadioStation); +#ifdef EXTERNAL_3D_SOUND + ReadIniIfExists("Audio", "SpeakerType", &FrontEndMenuManager.m_PrefsSpeakers); + ReadIniIfExists("Audio", "Provider", &FrontEndMenuManager.m_nPrefsAudio3DProviderIndex); +#endif + ReadIniIfExists("Audio", "DynamicAcoustics", &FrontEndMenuManager.m_PrefsDMA); + ReadIniIfExists("Display", "Brightness", &FrontEndMenuManager.m_PrefsBrightness); + ReadIniIfExists("Display", "DrawDistance", &FrontEndMenuManager.m_PrefsLOD); + ReadIniIfExists("Display", "Subtitles", &FrontEndMenuManager.m_PrefsShowSubtitles); + ReadIniIfExists("Graphics", "AspectRatio", &FrontEndMenuManager.m_PrefsUseWideScreen); + ReadIniIfExists("Graphics", "VSync", &FrontEndMenuManager.m_PrefsVsyncDisp); + ReadIniIfExists("Graphics", "FrameLimiter", &FrontEndMenuManager.m_PrefsFrameLimiter); + ReadIniIfExists("Graphics", "Trails", &CMBlur::BlurOn); + ReadIniIfExists("General", "SkinFile", FrontEndMenuManager.m_PrefsSkinFile, 256); + ReadIniIfExists("Controller", "Method", &FrontEndMenuManager.m_ControlMethod); + ReadIniIfExists("General", "Language", &FrontEndMenuManager.m_PrefsLanguage); + +#ifdef EXTENDED_COLOURFILTER + ReadIniIfExists("CustomPipesValues", "PostFXIntensity", &CPostFX::Intensity); +#endif +#ifdef EXTENDED_PIPELINES + ReadIniIfExists("CustomPipesValues", "NeoVehicleShininess", &CustomPipes::VehicleShininess); + ReadIniIfExists("CustomPipesValues", "NeoVehicleSpecularity", &CustomPipes::VehicleSpecularity); + ReadIniIfExists("CustomPipesValues", "RimlightMult", &CustomPipes::RimlightMult); + ReadIniIfExists("CustomPipesValues", "LightmapMult", &CustomPipes::LightmapMult); + ReadIniIfExists("CustomPipesValues", "GlossMult", &CustomPipes::GlossMult); +#endif +#ifdef NEW_RENDERER + ReadIniIfExists("Rendering", "NewRenderer", &gbNewRenderer); +#endif + +#ifdef PROPER_SCALING + ReadIniIfExists("Draw", "ProperScaling", &CDraw::ms_bProperScaling); +#endif +#ifdef FIX_RADAR + ReadIniIfExists("Draw", "FixRadar", &CDraw::ms_bFixRadar); +#endif +#ifdef FIX_SPRITES + ReadIniIfExists("Draw", "FixSprites", &CDraw::ms_bFixSprites); +#endif +#ifdef DRAW_GAME_VERSION_TEXT + ReadIniIfExists("General", "DrawVersionText", &gbDrawVersionText); +#endif +#ifdef NO_MOVIES + ReadIniIfExists("General", "NoMovies", &gbNoMovies); +#endif + +#ifdef CUSTOM_FRONTEND_OPTIONS + bool migrate = cfg.get("FrontendOptions").size() != 0; + for (int i = 0; i < MENUPAGES; i++) { + for (int j = 0; j < NUM_MENUROWS; j++) { + CMenuScreenCustom::CMenuEntry &option = aScreens[i].m_aEntries[j]; + if (option.m_Action == MENUACTION_NOTHING) + break; + + // CFO check + if (option.m_Action < MENUACTION_NOTHING && option.m_CFO->save) { + // Migrate from old .ini to new .ini + // Old values can only be int8, new ones can contain float if it is slider + if (migrate && ReadIniIfExists("FrontendOptions", option.m_CFO->save, (int8*)option.m_CFO->value)) + cfg["FrontendOptions"].remove(option.m_CFO->save); + else if (option.m_Action == MENUACTION_CFO_SLIDER) + ReadIniIfExists(option.m_CFO->saveCat, option.m_CFO->save, (float*)option.m_CFO->value); + else + ReadIniIfExists(option.m_CFO->saveCat, option.m_CFO->save, (int8*)option.m_CFO->value); + + if (option.m_Action == MENUACTION_CFO_SELECT) { + option.m_CFOSelect->lastSavedValue = option.m_CFOSelect->displayedValue = *(int8*)option.m_CFO->value; + } + } + } + } +#endif + + // Fetched in above block, but needs evaluation +#ifdef PED_CAR_DENSITY_SLIDERS + CPopulation::MaxNumberOfPedsInUse = DEFAULT_MAX_NUMBER_OF_PEDS * CIniFile::PedNumberMultiplier; + CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS * CIniFile::CarNumberMultiplier; +#endif + + return true; +} + +void SaveINISettings() +{ +#ifdef IMPROVED_VIDEOMODE + StoreIni("VideoMode", "Width", FrontEndMenuManager.m_nPrefsWidth); + StoreIni("VideoMode", "Height", FrontEndMenuManager.m_nPrefsHeight); + StoreIni("VideoMode", "Depth", FrontEndMenuManager.m_nPrefsDepth); + StoreIni("VideoMode", "Subsystem", FrontEndMenuManager.m_nPrefsSubsystem); + // Windowed mode is loaded below in CUSTOM_FRONTEND_OPTIONS section +#else + StoreIni("Graphics", "VideoMode", FrontEndMenuManager.m_nDisplayVideoMode); +#endif + StoreIni("Controller", "HeadBob1stPerson", TheCamera.m_bHeadBob); + StoreIni("Controller", "VerticalMouseSens", TheCamera.m_fMouseAccelVertical); + StoreIni("Controller", "HorizantalMouseSens", TheCamera.m_fMouseAccelHorzntl); + StoreIni("Controller", "InvertMouseVertically", MousePointerStateHelper.bInvertVertically); + StoreIni("Controller", "DisableMouseSteering", CVehicle::m_bDisableMouseSteering); + StoreIni("Controller", "Vibration", FrontEndMenuManager.m_PrefsUseVibration); + StoreIni("Audio", "SfxVolume", FrontEndMenuManager.m_PrefsSfxVolume); + StoreIni("Audio", "MusicVolume", FrontEndMenuManager.m_PrefsMusicVolume); + StoreIni("Audio", "Radio", FrontEndMenuManager.m_PrefsRadioStation); +#ifdef EXTERNAL_3D_SOUND + StoreIni("Audio", "SpeakerType", FrontEndMenuManager.m_PrefsSpeakers); + StoreIni("Audio", "Provider", FrontEndMenuManager.m_nPrefsAudio3DProviderIndex); +#endif + StoreIni("Audio", "DynamicAcoustics", FrontEndMenuManager.m_PrefsDMA); + StoreIni("Display", "Brightness", FrontEndMenuManager.m_PrefsBrightness); + StoreIni("Display", "DrawDistance", FrontEndMenuManager.m_PrefsLOD); + StoreIni("Display", "Subtitles", FrontEndMenuManager.m_PrefsShowSubtitles); + StoreIni("Graphics", "AspectRatio", FrontEndMenuManager.m_PrefsUseWideScreen); + StoreIni("Graphics", "VSync", FrontEndMenuManager.m_PrefsVsyncDisp); + StoreIni("Graphics", "FrameLimiter", FrontEndMenuManager.m_PrefsFrameLimiter); + StoreIni("Graphics", "Trails", CMBlur::BlurOn); + StoreIni("General", "SkinFile", FrontEndMenuManager.m_PrefsSkinFile, 256); + StoreIni("Controller", "Method", FrontEndMenuManager.m_ControlMethod); + StoreIni("General", "Language", FrontEndMenuManager.m_PrefsLanguage); + +#ifdef EXTENDED_COLOURFILTER + StoreIni("CustomPipesValues", "PostFXIntensity", CPostFX::Intensity); +#endif +#ifdef EXTENDED_PIPELINES + StoreIni("CustomPipesValues", "NeoVehicleShininess", CustomPipes::VehicleShininess); + StoreIni("CustomPipesValues", "NeoVehicleSpecularity", CustomPipes::VehicleSpecularity); + StoreIni("CustomPipesValues", "RimlightMult", CustomPipes::RimlightMult); + StoreIni("CustomPipesValues", "LightmapMult", CustomPipes::LightmapMult); + StoreIni("CustomPipesValues", "GlossMult", CustomPipes::GlossMult); +#endif +#ifdef NEW_RENDERER + StoreIni("Rendering", "NewRenderer", gbNewRenderer); +#endif + +#ifdef PROPER_SCALING + StoreIni("Draw", "ProperScaling", CDraw::ms_bProperScaling); +#endif +#ifdef FIX_RADAR + StoreIni("Draw", "FixRadar", CDraw::ms_bFixRadar); +#endif +#ifdef FIX_SPRITES + StoreIni("Draw", "FixSprites", CDraw::ms_bFixSprites); +#endif +#ifdef DRAW_GAME_VERSION_TEXT + StoreIni("General", "DrawVersionText", gbDrawVersionText); +#endif +#ifdef NO_MOVIES + StoreIni("General", "NoMovies", gbNoMovies); +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + for (int i = 0; i < MENUPAGES; i++) { + for (int j = 0; j < NUM_MENUROWS; j++) { + CMenuScreenCustom::CMenuEntry &option = aScreens[i].m_aEntries[j]; + if (option.m_Action == MENUACTION_NOTHING) + break; + + if (option.m_Action < MENUACTION_NOTHING && option.m_CFO->save) { + if (option.m_Action == MENUACTION_CFO_SLIDER) + StoreIni(option.m_CFO->saveCat, option.m_CFO->save, *(float*)option.m_CFO->value); + else + StoreIni(option.m_CFO->saveCat, option.m_CFO->save, *(int8*)option.m_CFO->value); + } + } + } +#endif + + ini.write(cfg); +} + +#endif + + +#ifdef DEBUGMENU +void WeaponCheat(); +void HealthCheat(); +void TankCheat(); +void BlowUpCarsCheat(); +void ChangePlayerCheat(); +void MayhemCheat(); +void EverybodyAttacksPlayerCheat(); +void WeaponsForAllCheat(); +void FastTimeCheat(); +void SlowTimeCheat(); +void MoneyCheat(); +void ArmourCheat(); +void WantedLevelUpCheat(); +void WantedLevelDownCheat(); +void SunnyWeatherCheat(); +void CloudyWeatherCheat(); +void RainyWeatherCheat(); +void FoggyWeatherCheat(); +void FastWeatherCheat(); +void OnlyRenderWheelsCheat(); +void ChittyChittyBangBangCheat(); +void StrongGripCheat(); +void NastyLimbsCheat(); + +DebugMenuEntry *carCol1; +DebugMenuEntry *carCol2; + +void +SpawnCar(int id) +{ + CVector playerpos; + CStreaming::RequestModel(id, 0); + CStreaming::LoadAllRequestedModels(false); + if(CStreaming::HasModelLoaded(id)){ + playerpos = FindPlayerCoors(); + int node; + if(!CModelInfo::IsBoatModel(id)){ + node = ThePaths.FindNodeClosestToCoors(playerpos, 0, 100.0f, false, false); + if(node < 0) + return; + } + + CVehicle *v; + if(CModelInfo::IsBoatModel(id)) + v = new CBoat(id, RANDOM_VEHICLE); + else + v = new CAutomobile(id, RANDOM_VEHICLE); + + v->bHasBeenOwnedByPlayer = true; + if(carCol1) + DebugMenuEntrySetAddress(carCol1, &v->m_currentColour1); + if(carCol2) + DebugMenuEntrySetAddress(carCol2, &v->m_currentColour2); + + if(CModelInfo::IsBoatModel(id)) + v->SetPosition(TheCamera.GetPosition() + TheCamera.GetForward()*15.0f); + else + v->SetPosition(ThePaths.m_pathNodes[node].GetPosition()); + + v->GetMatrix().GetPosition().z += 4.0f; + v->SetOrientation(0.0f, 0.0f, 3.49f); + v->SetStatus(STATUS_ABANDONED); + v->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(v); + } +} + +static void +FixCar(void) +{ + CVehicle *veh = FindPlayerVehicle(); + if(veh == nil) + return; + veh->m_fHealth = 1000.0f; + if(!veh->IsCar()) + return; + ((CAutomobile*)veh)->Damage.SetEngineStatus(0); + ((CAutomobile*)veh)->Fix(); +} + +#ifdef MENU_MAP +static void +TeleportToWaypoint(void) +{ + if (CRadar::TargetMarkerId == -1) + return; + CEntity* pEntityToTeleport = FindPlayerEntity(); + CVector vNewPos = CRadar::TargetMarkerPos; + CGame::currLevel = CTheZones::GetLevelFromPosition(&vNewPos); + CCollision::SortOutCollisionAfterLoad(); + CStreaming::LoadScene(vNewPos); + vNewPos.z = CWorld::FindGroundZForCoord(vNewPos.x, vNewPos.y) + pEntityToTeleport->GetDistanceFromCentreOfMassToBaseOfModel(); + pEntityToTeleport->Teleport(vNewPos); +} +#endif + +static void +SwitchCarCollision(void) +{ + if (FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) + FindPlayerVehicle()->bUsesCollision = !FindPlayerVehicle()->bUsesCollision; +} + +static void +ToggleComedy(void) +{ + CVehicle *veh = FindPlayerVehicle(); + if(veh == nil) + return; + veh->bComedyControls = !veh->bComedyControls; +} + +static void +PlaceOnRoad(void) +{ + CVehicle *veh = FindPlayerVehicle(); + if(veh == nil) + return; + + if(veh->IsCar()) + ((CAutomobile*)veh)->PlaceOnRoadProperly(); +} + +static void +ResetCamStatics(void) +{ + TheCamera.Cams[TheCamera.ActiveCam].ResetStatics = true; +} + +#ifdef MISSION_SWITCHER +int8 nextMissionToSwitch = 0; +static void +SwitchToMission(void) +{ + CTheScripts::SwitchToMission(nextMissionToSwitch); +} +#endif + +#ifdef USE_CUSTOM_ALLOCATOR +static void ParseHeap(void) { gMainHeap.ParseHeap(); } +#endif + +static const char *carnames[] = { + "landstal", "idaho", "stinger", "linerun", "peren", "sentinel", "patriot", "firetruk", "trash", "stretch", "manana", "infernus", "blista", "pony", + "mule", "cheetah", "ambulan", "fbicar", "moonbeam", "esperant", "taxi", "kuruma", "bobcat", "mrwhoop", "bfinject", "corpse", "police", "enforcer", + "securica", "banshee", "predator", "bus", "rhino", "barracks", "train", "chopper", "dodo", "coach", "cabbie", "stallion", "rumpo", "rcbandit", + "bellyup", "mrwongs", "mafia", "yardie", "yakuza", "diablos", "columb", "hoods", "airtrain", "deaddodo", "speeder", "reefer", "panlant", "flatbed", + "yankee", "escape", "borgnine", "toyz", "ghost", +}; + +//#include + +static CTweakVar** TweakVarsList; +static int TweakVarsListSize = -1; +static bool bAddTweakVarsNow = false; +static const char *pTweakVarsDefaultPath = NULL; + +void CTweakVars::Add(CTweakVar *var) +{ + if(TweakVarsListSize == -1) { + TweakVarsList = (CTweakVar**)malloc(64 * sizeof(CTweakVar*)); + TweakVarsListSize = 0; + } + if(TweakVarsListSize > 63) + TweakVarsList = (CTweakVar**) realloc(TweakVarsList, (TweakVarsListSize + 1) * sizeof(CTweakVar*)); + + TweakVarsList[TweakVarsListSize++] = var; +// TweakVarsList.push_back(var); + + if ( bAddTweakVarsNow ) + var->AddDBG(pTweakVarsDefaultPath); +} + +void CTweakVars::AddDBG(const char *path) +{ + pTweakVarsDefaultPath = path; + + for(int i = 0; i < TweakVarsListSize; ++i) + TweakVarsList[i]->AddDBG(pTweakVarsDefaultPath); + + bAddTweakVarsNow = true; +} + +void CTweakSwitch::AddDBG(const char *path) +{ + DebugMenuEntry *e = DebugMenuAddVar(m_pPath == NULL ? path : m_pPath, m_pVarName, (int32_t *)m_pIntVar, m_pFunc, 1, m_nMin, m_nMax, m_aStr); + DebugMenuEntrySetWrap(e, true); +} + +void CTweakFunc::AddDBG (const char *path) { DebugMenuAddCmd (m_pPath == NULL ? path : m_pPath, m_pVarName, m_pFunc); } +void CTweakBool::AddDBG (const char *path) { DebugMenuAddVarBool8(m_pPath == NULL ? path : m_pPath, m_pVarName, (int8_t *)m_pBoolVar, NULL); } +void CTweakInt8::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (int8_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakUInt8::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (uint8_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakInt16::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (int16_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakUInt16::AddDBG(const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (uint16_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakInt32::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (int32_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakUInt32::AddDBG(const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (uint32_t *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound, NULL); } +void CTweakFloat::AddDBG (const char *path) { DebugMenuAddVar (m_pPath == NULL ? path : m_pPath, m_pVarName, (float *)m_pIntVar, NULL, m_nStep, m_nLoawerBound, m_nUpperBound); } + +/* +static const char *wt[] = { + "Sunny", "Cloudy", "Rainy", "Foggy" + }; + +SETTWEAKPATH("TEST"); +TWEAKSWITCH(CWeather::NewWeatherType, 0, 3, wt, NULL); +*/ + +void +DebugMenuPopulate(void) +{ + if(1){ + static const char *weathers[] = { + "Sunny", "Cloudy", "Rainy", "Foggy" + }; + DebugMenuEntry *e; + e = DebugMenuAddVar("Time & Weather", "Current Hour", &CClock::GetHoursRef(), nil, 1, 0, 23, nil); + DebugMenuEntrySetWrap(e, true); + e = DebugMenuAddVar("Time & Weather", "Current Minute", &CClock::GetMinutesRef(), + [](){ CWeather::InterpolationValue = CClock::GetMinutes()/60.0f; }, 1, 0, 59, nil); + DebugMenuEntrySetWrap(e, true); + e = DebugMenuAddVar("Time & Weather", "Old Weather", (int16*)&CWeather::OldWeatherType, nil, 1, 0, 3, weathers); + DebugMenuEntrySetWrap(e, true); + e = DebugMenuAddVar("Time & Weather", "New Weather", (int16*)&CWeather::NewWeatherType, nil, 1, 0, 3, weathers); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddVar("Time & Weather", "Wind", (float*)&CWeather::Wind, nil, 0.1f, 0.0f, 1.0f); + DebugMenuAddVar("Time & Weather", "Time scale", (float*)&CTimer::GetTimeScale(), nil, 0.1f, 0.0f, 10.0f); + + DebugMenuAddCmd("Cheats", "Weapons", WeaponCheat); + DebugMenuAddCmd("Cheats", "Money", MoneyCheat); + DebugMenuAddCmd("Cheats", "Health", HealthCheat); + DebugMenuAddCmd("Cheats", "Wanted level up", WantedLevelUpCheat); + DebugMenuAddCmd("Cheats", "Wanted level down", WantedLevelDownCheat); + DebugMenuAddCmd("Cheats", "Tank", TankCheat); + DebugMenuAddCmd("Cheats", "Blow up cars", BlowUpCarsCheat); + DebugMenuAddCmd("Cheats", "Change player", ChangePlayerCheat); + DebugMenuAddCmd("Cheats", "Mayhem", MayhemCheat); + DebugMenuAddCmd("Cheats", "Everybody attacks player", EverybodyAttacksPlayerCheat); + DebugMenuAddCmd("Cheats", "Weapons for all", WeaponsForAllCheat); + DebugMenuAddCmd("Cheats", "Fast time", FastTimeCheat); + DebugMenuAddCmd("Cheats", "Slow time", SlowTimeCheat); + DebugMenuAddCmd("Cheats", "Armour", ArmourCheat); + DebugMenuAddCmd("Cheats", "Sunny weather", SunnyWeatherCheat); + DebugMenuAddCmd("Cheats", "Cloudy weather", CloudyWeatherCheat); + DebugMenuAddCmd("Cheats", "Rainy weather", RainyWeatherCheat); + DebugMenuAddCmd("Cheats", "Foggy weather", FoggyWeatherCheat); + DebugMenuAddCmd("Cheats", "Fast weather", FastWeatherCheat); + DebugMenuAddCmd("Cheats", "Only render wheels", OnlyRenderWheelsCheat); + DebugMenuAddCmd("Cheats", "Chitty chitty bang bang", ChittyChittyBangBangCheat); + DebugMenuAddCmd("Cheats", "Strong grip", StrongGripCheat); + DebugMenuAddCmd("Cheats", "Nasty limbs", NastyLimbsCheat); + + static int spawnCarId = MI_LANDSTAL; + e = DebugMenuAddVar("Spawn", "Spawn Car ID", &spawnCarId, nil, 1, MI_LANDSTAL, MI_GHOST, carnames); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddCmd("Spawn", "Spawn Car", [](){ + if(spawnCarId == MI_TRAIN || + spawnCarId == MI_CHOPPER || + spawnCarId == MI_AIRTRAIN || + spawnCarId == MI_DEADDODO || + spawnCarId == MI_ESCAPE) + return; + SpawnCar(spawnCarId); + }); + static uint8 dummy; + carCol1 = DebugMenuAddVar("Spawn", "First colour", &dummy, nil, 1, 0, 255, nil); + carCol2 = DebugMenuAddVar("Spawn", "Second colour", &dummy, nil, 1, 0, 255, nil); + DebugMenuAddCmd("Spawn", "Spawn Stinger", [](){ SpawnCar(MI_STINGER); }); + DebugMenuAddCmd("Spawn", "Spawn Infernus", [](){ SpawnCar(MI_INFERNUS); }); + DebugMenuAddCmd("Spawn", "Spawn Cheetah", [](){ SpawnCar(MI_CHEETAH); }); + DebugMenuAddCmd("Spawn", "Spawn Esperanto", [](){ SpawnCar(MI_ESPERANT); }); + DebugMenuAddCmd("Spawn", "Spawn Stallion", [](){ SpawnCar(MI_STALLION); }); + DebugMenuAddCmd("Spawn", "Spawn Kuruma", [](){ SpawnCar(MI_KURUMA); }); + DebugMenuAddCmd("Spawn", "Spawn Taxi", [](){ SpawnCar(MI_TAXI); }); + DebugMenuAddCmd("Spawn", "Spawn Police", [](){ SpawnCar(MI_POLICE); }); + DebugMenuAddCmd("Spawn", "Spawn Enforcer", [](){ SpawnCar(MI_ENFORCER); }); + DebugMenuAddCmd("Spawn", "Spawn Banshee", [](){ SpawnCar(MI_BANSHEE); }); + DebugMenuAddCmd("Spawn", "Spawn Yakuza", [](){ SpawnCar(MI_YAKUZA); }); + DebugMenuAddCmd("Spawn", "Spawn Yardie", [](){ SpawnCar(MI_YARDIE); }); + DebugMenuAddCmd("Spawn", "Spawn Dodo", [](){ SpawnCar(MI_DODO); }); + DebugMenuAddCmd("Spawn", "Spawn Rhino", [](){ SpawnCar(MI_RHINO); }); + DebugMenuAddCmd("Spawn", "Spawn Firetruck", [](){ SpawnCar(MI_FIRETRUCK); }); + DebugMenuAddCmd("Spawn", "Spawn Predator", [](){ SpawnCar(MI_PREDATOR); }); + + DebugMenuAddVarBool8("Render", "Draw hud", &CHud::m_Wants_To_Draw_Hud, nil); + +#ifdef PROPER_SCALING + DebugMenuAddVarBool8("Render", "Proper Scaling", &CDraw::ms_bProperScaling, nil); +#endif +#ifdef FIX_RADAR + DebugMenuAddVarBool8("Render", "Fix Radar", &CDraw::ms_bFixRadar, nil); +#endif +#ifdef FIX_SPRITES + DebugMenuAddVarBool8("Render", "Fix Sprites", &CDraw::ms_bFixSprites, nil); +#endif + DebugMenuAddVarBool8("Render", "PS2 Alpha test Emu", &gPS2alphaTest, nil); + DebugMenuAddVarBool8("Render", "Frame limiter", &FrontEndMenuManager.m_PrefsFrameLimiter, nil); + DebugMenuAddVarBool8("Render", "VSynch", &FrontEndMenuManager.m_PrefsVsync, nil); + DebugMenuAddVar("Render", "Max FPS", &RsGlobal.maxFPS, nil, 1, 1, 1000, nil); +#ifdef NEW_RENDERER + DebugMenuAddVarBool8("Render", "New Renderer", &gbNewRenderer, nil); +extern bool gbRenderRoads; +extern bool gbRenderEverythingBarRoads; +//extern bool gbRenderFadingInUnderwaterEntities; +extern bool gbRenderFadingInEntities; +extern bool gbRenderWater; +extern bool gbRenderBoats; +extern bool gbRenderVehicles; +extern bool gbRenderWorld0; +extern bool gbRenderWorld1; +extern bool gbRenderWorld2; + DebugMenuAddVarBool8("Debug Render", "gbRenderRoads", &gbRenderRoads, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderEverythingBarRoads", &gbRenderEverythingBarRoads, nil); +// DebugMenuAddVarBool8("Debug Render", "gbRenderFadingInUnderwaterEntities", &gbRenderFadingInUnderwaterEntities, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderFadingInEntities", &gbRenderFadingInEntities, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderWater", &gbRenderWater, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderBoats", &gbRenderBoats, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderVehicles", &gbRenderVehicles, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderWorld0", &gbRenderWorld0, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderWorld1", &gbRenderWorld1, nil); + DebugMenuAddVarBool8("Debug Render", "gbRenderWorld2", &gbRenderWorld2, nil); +#endif + +#ifdef EXTENDED_COLOURFILTER + static const char *filternames[] = { "None", "Simple", "Normal", "Mobile" }; + e = DebugMenuAddVar("Render", "Colourfilter", &CPostFX::EffectSwitch, nil, 1, CPostFX::POSTFX_OFF, CPostFX::POSTFX_MOBILE, filternames); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddVar("Render", "Intensity", &CPostFX::Intensity, nil, 0.05f, 0, 10.0f); + DebugMenuAddVarBool8("Render", "Motion Blur", &CPostFX::MotionBlurOn, nil); +#endif +#ifdef LIBRW + DebugMenuAddVarBool32("Render", "MatFX env map apply light", &rw::MatFX::envMapApplyLight, nil); + DebugMenuAddVarBool32("Render", "MatFX env map flip U", &rw::MatFX::envMapFlipU, nil); + DebugMenuAddVarBool32("Render", "MatFX env map use matcolor", &rw::MatFX::envMapUseMatColor, nil); +#endif +#ifdef EXTENDED_PIPELINES + static const char *vehpipenames[] = { "MatFX", "Neo" }; + e = DebugMenuAddVar("Render", "Vehicle Pipeline", &CustomPipes::VehiclePipeSwitch, nil, + 1, CustomPipes::VEHICLEPIPE_MATFX, CustomPipes::VEHICLEPIPE_NEO, vehpipenames); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddVar("Render", "Neo Vehicle Shininess", &CustomPipes::VehicleShininess, nil, 0.1f, 0, 1.0f); + DebugMenuAddVar("Render", "Neo Vehicle Specularity", &CustomPipes::VehicleSpecularity, nil, 0.1f, 0, 1.0f); + DebugMenuAddVarBool8("Render", "Neo Ped Rim light enable", &CustomPipes::RimlightEnable, nil); + DebugMenuAddVar("Render", "Mult", &CustomPipes::RimlightMult, nil, 0.1f, 0, 1.0f); + DebugMenuAddVarBool8("Render", "Neo World Lightmaps enable", &CustomPipes::LightmapEnable, nil); + DebugMenuAddVar("Render", "Mult", &CustomPipes::LightmapMult, nil, 0.1f, 0, 1.0f); + DebugMenuAddVarBool8("Render", "Neo Road Gloss enable", &CustomPipes::GlossEnable, nil); + DebugMenuAddVar("Render", "Mult", &CustomPipes::GlossMult, nil, 0.1f, 0, 1.0f); +#endif + DebugMenuAddVarBool8("Debug Render", "Show Ped Paths", &gbShowPedPaths, nil); + DebugMenuAddVarBool8("Debug Render", "Show Car Paths", &gbShowCarPaths, nil); + DebugMenuAddVarBool8("Debug Render", "Show Car Path Links", &gbShowCarPathsLinks, nil); + DebugMenuAddVarBool8("Debug Render", "Show Ped Road Groups", &gbShowPedRoadGroups, nil); + DebugMenuAddVarBool8("Debug Render", "Show Car Road Groups", &gbShowCarRoadGroups, nil); + DebugMenuAddVarBool8("Debug Render", "Show Collision Lines", &gbShowCollisionLines, nil); + DebugMenuAddVarBool8("Debug Render", "Show Collision Polys", &gbShowCollisionPolys, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Buildings", &gbDontRenderBuildings, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Big Buildings", &gbDontRenderBigBuildings, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Peds", &gbDontRenderPeds, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Vehicles", &gbDontRenderVehicles, nil); + DebugMenuAddVarBool8("Debug Render", "Don't render Objects", &gbDontRenderObjects, nil); + DebugMenuAddVarBool8("Debug Render", "Don't Render Water", &gbDontRenderWater, nil); + + +#ifdef DRAW_GAME_VERSION_TEXT + DebugMenuAddVarBool8("Debug", "Version Text", &gbDrawVersionText, nil); +#endif + DebugMenuAddVarBool8("Debug", "Show DebugStuffInRelease", &gbDebugStuffInRelease, nil); +#ifdef TIMEBARS + DebugMenuAddVarBool8("Debug", "Show Timebars", &gbShowTimebars, nil); +#endif +#ifndef FINAL + DebugMenuAddVarBool8("Debug", "Use debug render groups", &bDebugRenderGroups, nil); + DebugMenuAddVarBool8("Debug", "Print Memory Usage", &gbPrintMemoryUsage, nil); +#ifdef USE_CUSTOM_ALLOCATOR + DebugMenuAddCmd("Debug", "Parse Heap", ParseHeap); +#endif +#endif + DebugMenuAddVarBool8("Debug", "Show cullzone debug stuff", &gbShowCullZoneDebugStuff, nil); + DebugMenuAddVarBool8("Debug", "Disable zone cull", &gbDisableZoneCull, nil); + + DebugMenuAddVarBool8("Debug", "pad 1 -> pad 2", &CPad::m_bMapPadOneToPadTwo, nil); +#ifdef GTA_SCENE_EDIT + DebugMenuAddVarBool8("Debug", "Edit on", &CSceneEdit::m_bEditOn, nil); +#endif + //DebugMenuAddCmd("Debug", "Start Credits", CCredits::Start); + //DebugMenuAddCmd("Debug", "Stop Credits", CCredits::Stop); + +#ifdef MENU_MAP + DebugMenuAddCmd("Game", "Teleport to map waypoint", TeleportToWaypoint); +#endif + DebugMenuAddCmd("Game", "Fix Car", FixCar); + DebugMenuAddCmd("Game", "Place Car on Road", PlaceOnRoad); + DebugMenuAddCmd("Game", "Switch car collision", SwitchCarCollision); + DebugMenuAddCmd("Game", "Toggle Comedy Controls", ToggleComedy); + + DebugMenuAddVarBool8("Game", "Toggle popping heads on headshot", &CPed::bPopHeadsOnHeadshot, nil); + +#ifdef MISSION_SWITCHER + DebugMenuEntry *missionEntry; + static const char* missions[] = { + "Intro Movie", "Hospital Info Scene", "Police Station Info Scene", + "RC Diablo Destruction", "RC Mafia Massacre", "RC Rumpo Rampage", "RC Casino Calamity", + "Patriot Playground", "A Ride In The Park", "Gripped!", "Multistorey Mayhem", + "Paramedic", "Firefighter", "Vigilante", "Taxi Driver", + "The Crook", "The Thieves", "The Wife", "Her Lover", + "Give Me Liberty and Luigi's Girls", "Don't Spank My Bitch Up", "Drive Misty For Me", "Pump-Action Pimp", "The Fuzz Ball", + "Mike Lips Last Lunch", "Farewell 'Chunky' Lee Chong", "Van Heist", "Cipriani's Chauffeur", "Dead Skunk In The Trunk", "The Getaway", + "Taking Out The Laundry", "The Pick-Up", "Salvatore's Called A Meeting", "Triads And Tribulations", "Blow Fish", "Chaperone", "Cutting The Grass", + "Bomb Da Base: Act I", "Bomb Da Base: Act II", "Last Requests", "Turismo", "I Scream, You Scream", "Trial By Fire", "Big'N'Veiny", "Sayonara Salvatore", + "Under Surveillance", "Paparazzi Purge", "Payday For Ray", "Two-Faced Tanner", "Kanbu Bust-Out", "Grand Theft Auto", "Deal Steal", "Shima", "Smack Down", + "Silence The Sneak", "Arms Shortage", "Evidence Dash", "Gone Fishing", "Plaster Blaster", "Marked Man", + "Liberator", "Waka-Gashira Wipeout!", "A Drop In The Ocean", "Bling-Bling Scramble", "Uzi Rider", "Gangcar Round-Up", "Kingdom Come", + "Grand Theft Aero", "Escort Service", "Decoy", "Love's Disappearance", "Bait", "Espresso-2-Go!", "S.A.M.", + "Uzi Money", "Toyminator", "Rigged To Blow", "Bullion Run", "Rumble", "The Exchange" + }; + + missionEntry = DebugMenuAddVar("Game", "Select mission", &nextMissionToSwitch, nil, 1, 0, ARRAY_SIZE(missions) - 1, missions); + DebugMenuEntrySetWrap(missionEntry, true); + DebugMenuAddCmd("Game", "Start selected mission ", SwitchToMission); +#endif + + extern bool PrintDebugCode; + extern int16 DebugCamMode; + DebugMenuAddVarBool8("Cam", "Use mouse Cam", &CCamera::m_bUseMouse3rdPerson, nil); +#ifdef FREE_CAM + DebugMenuAddVarBool8("Cam", "Free Cam", &CCamera::bFreeCam, nil); +#endif + DebugMenuAddVarBool8("Cam", "Print Debug Code", &PrintDebugCode, nil); + DebugMenuAddVar("Cam", "Cam Mode", &DebugCamMode, nil, 1, 0, CCam::MODE_EDITOR, nil); + DebugMenuAddCmd("Cam", "Normal", []() { DebugCamMode = 0; }); + // DebugMenuAddCmd("Cam", "Follow Ped With Bind", []() { DebugCamMode = CCam::MODE_FOLLOW_PED_WITH_BIND; }); + // DebugMenuAddCmd("Cam", "Reaction", []() { DebugCamMode = CCam::MODE_REACTION; }); + // DebugMenuAddCmd("Cam", "Chris", []() { DebugCamMode = CCam::MODE_CHRIS; }); + DebugMenuAddCmd("Cam", "Reset Statics", ResetCamStatics); + + CTweakVars::AddDBG("Debug"); + } +} +#endif + +#ifndef __MWERKS__ +#ifndef MASTER +const int re3_buffsize = 1024; +static char re3_buff[re3_buffsize]; +#endif + +#ifndef MASTER +void re3_assert(const char *expr, const char *filename, unsigned int lineno, const char *func) +{ +#ifdef _WIN32 + int nCode; + + strcpy_s(re3_buff, re3_buffsize, "Assertion failed!" ); + strcat_s(re3_buff, re3_buffsize, "\n" ); + + strcat_s(re3_buff, re3_buffsize, "File: "); + strcat_s(re3_buff, re3_buffsize, filename ); + strcat_s(re3_buff, re3_buffsize, "\n" ); + + strcat_s(re3_buff, re3_buffsize, "Line: " ); + _itoa_s( lineno, re3_buff + strlen(re3_buff), re3_buffsize - strlen(re3_buff), 10 ); + strcat_s(re3_buff, re3_buffsize, "\n"); + + strcat_s(re3_buff, re3_buffsize, "Function: "); + strcat_s(re3_buff, re3_buffsize, func ); + strcat_s(re3_buff, re3_buffsize, "\n" ); + + strcat_s(re3_buff, re3_buffsize, "Expression: "); + strcat_s(re3_buff, re3_buffsize, expr); + strcat_s(re3_buff, re3_buffsize, "\n"); + + strcat_s(re3_buff, re3_buffsize, "\n" ); + strcat_s(re3_buff, re3_buffsize, "(Press Retry to debug the application)"); + + + nCode = ::MessageBoxA(nil, re3_buff, "RE3 Assertion Failed!", + MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL); + + if (nCode == IDABORT) + { + raise(SIGABRT); + _exit(3); + } + + if (nCode == IDRETRY) + { + __debugbreak(); + return; + } + + if (nCode == IDIGNORE) + return; + + abort(); +#else + // TODO + printf("\nRE3 ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); + assert(false); +#endif +} +#endif + +void re3_debug(const char *format, ...) +{ +#ifndef MASTER + va_list va; + va_start(va, format); +#ifdef _WIN32 + vsprintf_s(re3_buff, re3_buffsize, format, va); +#else + vsprintf(re3_buff, format, va); +#endif + va_end(va); + + printf("%s", re3_buff); + CDebug::DebugAddText(re3_buff); +#endif +} + +#ifndef MASTER +void re3_trace(const char *filename, unsigned int lineno, const char *func, const char *format, ...) +{ + char buff[re3_buffsize *2]; + va_list va; + va_start(va, format); +#ifdef _WIN32 + vsprintf_s(re3_buff, re3_buffsize, format, va); + va_end(va); + + sprintf_s(buff, re3_buffsize * 2, "[%s.%s:%d]: %s", filename, func, lineno, re3_buff); +#else + vsprintf(re3_buff, format, va); + va_end(va); + + sprintf(buff, "[%s.%s:%d]: %s", filename, func, lineno, re3_buff); +#endif + + OutputDebugString(buff); +} +#endif + +#ifndef MASTER +void re3_usererror(const char *format, ...) +{ + va_list va; + va_start(va, format); +#ifdef _WIN32 + vsprintf_s(re3_buff, re3_buffsize, format, va); + va_end(va); + + ::MessageBoxA(nil, re3_buff, "RE3 Error!", + MB_OK|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL); + + raise(SIGABRT); + _exit(3); +#else + vsprintf(re3_buff, format, va); + printf("\nRE3 Error!\n\t%s\n",re3_buff); + assert(false); +#endif +} +#endif +#endif + +#ifdef VALIDATE_SAVE_SIZE +int32 _saveBufCount; +#endif diff --git a/src/core/templates.h b/src/core/templates.h new file mode 100644 index 0000000..545dac3 --- /dev/null +++ b/src/core/templates.h @@ -0,0 +1,269 @@ +#pragma once + +template +class CStore +{ +public: + int32 allocPtr; + T store[n]; + + T *Alloc(void){ + if(allocPtr >= n){ + printf("Size of this thing:%d needs increasing\n", n); + assert(0); + } + return &store[allocPtr++]; + } + void Clear(void){ + allocPtr = 0; + } + int32 GetIndex(T *item){ + assert(item >= &store[0]); + assert(item < &store[n]); + return item - store; + } + T *GetItem(int32 index){ + assert(index >= 0); + assert(index < n); + return &store[index]; + } +}; + +#define POOLFLAG_ID 0x7f +#define POOLFLAG_ISFREE 0x80 + +template +class CPool +{ + U *m_entries; + uint8 *m_flags; + int32 m_size; + int32 m_allocPtr; + +public: + CPool(int32 size){ + m_entries = (U*)new uint8[sizeof(U)*size]; + m_flags = new uint8[size]; + m_size = size; + m_allocPtr = 0; + for(int i = 0; i < size; i++){ + SetId(i, 0); + SetIsFree(i, true); + } + } + + int GetId(int i) const + { + return m_flags[i] & POOLFLAG_ID; + } + + bool GetIsFree(int i) const + { + return !!(m_flags[i] & POOLFLAG_ISFREE); + } + + void SetId(int i, int id) + { + m_flags[i] = (m_flags[i] & POOLFLAG_ISFREE) | (id & POOLFLAG_ID); + } + + void SetIsFree(int i, bool isFree) + { + if (isFree) + m_flags[i] |= POOLFLAG_ISFREE; + else + m_flags[i] &= ~POOLFLAG_ISFREE; + } + + ~CPool() { + Flush(); + } + void Flush() { + if (m_size > 0) { + delete[] (uint8*)m_entries; + delete[] m_flags; + m_entries = nil; + m_flags = nil; + m_size = 0; + m_allocPtr = 0; + } + } + int32 GetSize(void) const { return m_size; } + T *New(void){ + bool wrapped = false; + do +#ifdef FIX_BUGS + if (++m_allocPtr >= m_size) { + m_allocPtr = 0; + if (wrapped) + return nil; + wrapped = true; + } +#else + if(++m_allocPtr == m_size){ + if(wrapped) + return nil; + wrapped = true; + m_allocPtr = 0; + } +#endif + while(!GetIsFree(m_allocPtr)); + SetIsFree(m_allocPtr, false); + SetId(m_allocPtr, GetId(m_allocPtr)+1); + return (T*)&m_entries[m_allocPtr]; + } + T *New(int32 handle){ + T *entry = (T*)&m_entries[handle>>8]; + SetNotFreeAt(handle); + return entry; + } + void SetNotFreeAt(int32 handle){ + int idx = handle>>8; + SetIsFree(idx, false); + SetId(idx, handle & POOLFLAG_ID); + for(m_allocPtr = 0; m_allocPtr < m_size; m_allocPtr++) + if(GetIsFree(m_allocPtr)) + return; + } + void Delete(T *entry){ + int i = GetJustIndex(entry); + SetIsFree(i, true); + if(i < m_allocPtr) + m_allocPtr = i; + } + T *GetSlot(int i){ + return GetIsFree(i) ? nil : (T*)&m_entries[i]; + } + T *GetAt(int handle){ +#ifdef FIX_BUGS + if (handle == -1) + return nil; +#endif + return m_flags[handle>>8] == (handle & 0xFF) ? + (T*)&m_entries[handle >> 8] : nil; + } + int32 GetIndex(T *entry){ + int i = GetJustIndex_NoFreeAssert(entry); + return m_flags[i] + (i<<8); + } + int32 GetJustIndex(T *entry){ + int index = GetJustIndex_NoFreeAssert(entry); + assert(!GetIsFree(index)); + return index; + } + int32 GetJustIndex_NoFreeAssert(T* entry){ + int index = ((U*)entry - m_entries); + assert((U*)entry == (U*)&m_entries[index]); // cast is unsafe - check required + return index; + } + int32 GetNoOfUsedSpaces(void) const{ + int i; + int n = 0; + for(i = 0; i < m_size; i++) + if(!GetIsFree(i)) + n++; + return n; + } + void ClearStorage(uint8 *&flags, U *&entries){ + delete[] flags; + delete[] (uint8*)entries; + flags = nil; + entries = nil; + } + uint32 GetMaxEntrySize() const { return sizeof(U); } + void CopyBack(uint8 *&flags, U *&entries){ + memcpy(m_flags, flags, sizeof(uint8)*m_size); + memcpy(m_entries, entries, sizeof(U)*m_size); + debug("Size copied:%d (%d)\n", sizeof(U)*m_size, m_size); + m_allocPtr = 0; + ClearStorage(flags, entries); + debug("CopyBack:%d (/%d)\n", GetNoOfUsedSpaces(), m_size); /* Assumed inlining */ + } + void Store(uint8 *&flags, U *&entries){ + flags = (uint8*)new uint8[sizeof(uint8)*m_size]; + entries = (U*)new uint8[sizeof(U)*m_size]; + memcpy(flags, m_flags, sizeof(uint8)*m_size); + memcpy(entries, m_entries, sizeof(U)*m_size); + debug("Stored:%d (/%d)\n", GetNoOfUsedSpaces(), m_size); /* Assumed inlining */ + } +}; + +template +class CLink +{ +public: + T item; + CLink *prev; + CLink *next; + + void Insert(CLink *link){ + link->next = this->next; + this->next->prev = link; + link->prev = this; + this->next = link; + } + void Remove(void){ + this->prev->next = this->next; + this->next->prev = this->prev; + } +}; + +template +class CLinkList +{ +public: + CLink head, tail; + CLink freeHead, freeTail; + CLink *links; + + void Init(int n){ + links = new CLink[n]; + head.next = &tail; + tail.prev = &head; + freeHead.next = &freeTail; + freeTail.prev = &freeHead; + while(n--) + freeHead.Insert(&links[n]); + } + void Shutdown(void){ + delete[] links; + links = nil; + } + void Clear(void){ + while(head.next != &tail) + Remove(head.next); + } + CLink *Insert(T const &item){ + CLink *node = freeHead.next; + if(node == &freeTail) + return nil; + node->item = item; + node->Remove(); // remove from free list + head.Insert(node); + return node; + } + CLink *InsertSorted(T const &item){ + CLink *sort; + for(sort = head.next; sort != &tail; sort = sort->next) + if(sort->item.sort >= item.sort) + break; + CLink *node = freeHead.next; + if(node == &freeTail) + return nil; + node->item = item; + node->Remove(); // remove from free list + sort->prev->Insert(node); + return node; + } + void Remove(CLink *link){ + link->Remove(); // remove from list + freeHead.Insert(link); // insert into free list + } + int32 Count(void){ + int n = 0; + CLink *lnk; + for(lnk = head.next; lnk != &tail; lnk = lnk->next) + n++; + return n; + } +}; diff --git a/src/core/timebars.cpp b/src/core/timebars.cpp new file mode 100644 index 0000000..94051b2 --- /dev/null +++ b/src/core/timebars.cpp @@ -0,0 +1,121 @@ +#include "common.h" +#ifndef MASTER +#include "Font.h" +#include "Frontend.h" +#include "Timer.h" +#include "Text.h" + +#define MAX_TIMERS (50) +#define MAX_MS_COLLECTED (40) + +// enables frame time output +#define FRAMETIME + +struct sTimeBar +{ + char name[20]; + float startTime; + float endTime; + int32 unk; +}; + +struct +{ + sTimeBar Timers[MAX_TIMERS]; + uint32 count; +} TimerBar; +float MaxTimes[MAX_TIMERS]; +float MaxFrameTime; + +uint32 curMS; +uint32 msCollected[MAX_MS_COLLECTED]; +#ifdef FRAMETIME +float FrameInitTime; +#endif + +void tbInit() +{ + TimerBar.count = 0; + uint32 i = CTimer::GetFrameCounter() & 0x7F; + if (i == 0) { + do + MaxTimes[i++] = 0.0f; + while (i != MAX_TIMERS); +#ifdef FRAMETIME + MaxFrameTime = 0.0f; +#endif + } +#ifdef FRAMETIME + FrameInitTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerFrame(); +#endif +} + +void tbStartTimer(int32 unk, Const char *name) +{ + strcpy(TimerBar.Timers[TimerBar.count].name, name); + TimerBar.Timers[TimerBar.count].unk = unk; + TimerBar.Timers[TimerBar.count].startTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerFrame(); + TimerBar.count++; +} + +void tbEndTimer(Const char* name) +{ + uint32 n = 1500; + for (uint32 i = 0; i < TimerBar.count; i++) { + if (strcmp(name, TimerBar.Timers[i].name) == 0) + n = i; + } + assert(n != 1500); + TimerBar.Timers[n].endTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerFrame(); +} + +float Diag_GetFPS() +{ + return 39000.0f / (msCollected[(curMS - 1) % MAX_MS_COLLECTED] - msCollected[curMS % MAX_MS_COLLECTED]); +} + +void tbDisplay() +{ + char temp[200]; + wchar wtemp[200]; + +#ifdef FRAMETIME + float FrameEndTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerFrame(); +#endif + + msCollected[(curMS++) % MAX_MS_COLLECTED] = RsTimer(); + CFont::SetBackgroundOff(); + CFont::SetBackgroundColor(CRGBA(0, 0, 0, 128)); + CFont::SetScale(0.48f, 1.12f); + CFont::SetCentreOff(); + CFont::SetJustifyOff(); + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetRightJustifyOff(); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_BANK); + sprintf(temp, "FPS: %.2f", Diag_GetFPS()); + AsciiToUnicode(temp, wtemp); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + if (!CMenuManager::m_PrefsMarketing || !CMenuManager::m_PrefsDisableTutorials) { + CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * (4.0f / DEFAULT_SCREEN_HEIGHT), wtemp); + +#ifndef FINAL + // Timers output (my own implementation) + for (uint32 i = 0; i < TimerBar.count; i++) { + MaxTimes[i] = Max(MaxTimes[i], TimerBar.Timers[i].endTime - TimerBar.Timers[i].startTime); + sprintf(temp, "%s: %.2f", &TimerBar.Timers[i].name[0], MaxTimes[i]); + AsciiToUnicode(temp, wtemp); + CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * ((8.0f * (i + 2)) / DEFAULT_SCREEN_HEIGHT), wtemp); + } + +#ifdef FRAMETIME + MaxFrameTime = Max(MaxFrameTime, FrameEndTime - FrameInitTime); + sprintf(temp, "Frame Time: %.2f", MaxFrameTime); + AsciiToUnicode(temp, wtemp); + + CFont::PrintString(RsGlobal.maximumWidth * (4.0f / DEFAULT_SCREEN_WIDTH), RsGlobal.maximumHeight * ((8.0f * (TimerBar.count + 4)) / DEFAULT_SCREEN_HEIGHT), wtemp); +#endif // FRAMETIME +#endif // !FINAL + } +} +#endif // !MASTER \ No newline at end of file diff --git a/src/core/timebars.h b/src/core/timebars.h new file mode 100644 index 0000000..c493980 --- /dev/null +++ b/src/core/timebars.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef TIMEBARS +void tbInit(); +void tbStartTimer(int32, Const char*); +void tbEndTimer(Const char*); +void tbDisplay(); +#else +#define tbInit() +#define tbStartTimer(a, b) +#define tbEndTimer(a) +#define tbDisplay() +#endif diff --git a/src/entities/Dummy.cpp b/src/entities/Dummy.cpp new file mode 100644 index 0000000..d5fad3e --- /dev/null +++ b/src/entities/Dummy.cpp @@ -0,0 +1,52 @@ +#include "common.h" + +#include "Pools.h" +#include "World.h" +#include "Dummy.h" + +void *CDummy::operator new(size_t sz) throw() { return CPools::GetDummyPool()->New(); } +void CDummy::operator delete(void *p, size_t sz) throw() { CPools::GetDummyPool()->Delete((CDummy*)p); } + +void +CDummy::Add(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) + list = &s->m_lists[ENTITYLIST_DUMMIES]; + else + list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP]; + CPtrNode *node = list->InsertItem(this); + assert(node); + m_entryInfoList.InsertItem(list, node, s); + } +} + +void +CDummy::Remove(void) +{ + CEntryInfoNode *node, *next; + for(node = m_entryInfoList.first; node; node = next){ + next = node->next; + node->list->DeleteNode(node->listnode); + m_entryInfoList.DeleteNode(node); + } +} diff --git a/src/entities/Dummy.h b/src/entities/Dummy.h new file mode 100644 index 0000000..6c3f12e --- /dev/null +++ b/src/entities/Dummy.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Lists.h" +#include "Entity.h" + +class CDummy : public CEntity +{ +public: + CEntryInfoList m_entryInfoList; + + CDummy(void) { m_type = ENTITY_TYPE_DUMMY; } + void Add(void); + void Remove(void); + + static void *operator new(size_t) throw(); + static void operator delete(void*, size_t) throw(); +}; + +VALIDATE_SIZE(CDummy, 0x68); + diff --git a/src/entities/Entity.cpp b/src/entities/Entity.cpp new file mode 100644 index 0000000..c38f12c --- /dev/null +++ b/src/entities/Entity.cpp @@ -0,0 +1,804 @@ +#include "common.h" + +#include "General.h" +#include "RwHelper.h" +#include "ModelIndices.h" +#include "Timer.h" +#include "Entity.h" +#include "Object.h" +#include "World.h" +#include "Camera.h" +#include "Glass.h" +#include "Weather.h" +#include "Timecycle.h" +#include "TrafficLights.h" +#include "Coronas.h" +#include "PointLights.h" +#include "Shadows.h" +#include "Pickups.h" +#include "SpecialFX.h" +#include "TxdStore.h" +#include "Zones.h" +#include "MemoryHeap.h" +#include "Bones.h" +#include "Debug.h" +#include "SaveBuf.h" + +int gBuildings; + +CEntity::CEntity(void) +{ + m_type = ENTITY_TYPE_NOTHING; + m_status = STATUS_ABANDONED; + + bUsesCollision = false; + bCollisionProcessed = false; + bIsStatic = false; + bHasContacted = false; + bPedPhysics = false; + bIsStuck = false; + bIsInSafePosition = false; + bUseCollisionRecords = false; + + bWasPostponed = false; + bExplosionProof = false; + bIsVisible = true; + bHasCollided = false; + bRenderScorched = false; + bHasBlip = false; + bIsBIGBuilding = false; + bRenderDamaged = false; + + bBulletProof = false; + bFireProof = false; + bCollisionProof = false; + bMeleeProof = false; + bOnlyDamagedByPlayer = false; + bStreamingDontDelete = false; + bZoneCulled = false; + bZoneCulled2 = false; + + bRemoveFromWorld = false; + bHasHitWall = false; + bImBeingRendered = false; + bTouchingWater = false; + bIsSubway = false; + bDrawLast = false; + bNoBrightHeadLights = false; + bDoNotRender = false; + + bDistanceFade = false; + m_flagE2 = false; + + m_scanCode = 0; + m_modelIndex = -1; + m_rwObject = nil; + m_randomSeed = CGeneral::GetRandomNumber(); + m_pFirstReference = nil; +} + +CEntity::~CEntity(void) +{ + DeleteRwObject(); + ResolveReferences(); +} + +void +CEntity::SetModelIndex(uint32 id) +{ + m_modelIndex = id; + CreateRwObject(); +} + +void +CEntity::SetModelIndexNoCreate(uint32 id) +{ + m_modelIndex = id; +} + +void +CEntity::CreateRwObject(void) +{ + CBaseModelInfo *mi; + + mi = CModelInfo::GetModelInfo(m_modelIndex); + + PUSH_MEMID(MEMID_WORLD); + m_rwObject = mi->CreateInstance(); + POP_MEMID(); + + if(m_rwObject){ + if(IsBuilding()) + gBuildings++; + if(RwObjectGetType(m_rwObject) == rpATOMIC) + GetMatrix().AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic *)m_rwObject)), false); + else if(RwObjectGetType(m_rwObject) == rpCLUMP) + GetMatrix().AttachRW(RwFrameGetMatrix(RpClumpGetFrame((RpClump *)m_rwObject)), false); + mi->AddRef(); + } +} + +void +CEntity::AttachToRwObject(RwObject *obj) +{ + m_rwObject = obj; + if(m_rwObject){ + if(RwObjectGetType(m_rwObject) == rpATOMIC) + GetMatrix().Attach(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic *)m_rwObject)), false); + else if(RwObjectGetType(m_rwObject) == rpCLUMP) + GetMatrix().Attach(RwFrameGetMatrix(RpClumpGetFrame((RpClump *)m_rwObject)), false); + CModelInfo::GetModelInfo(m_modelIndex)->AddRef(); + } +} + +void +CEntity::DetachFromRwObject(void) +{ + if(m_rwObject) + CModelInfo::GetModelInfo(m_modelIndex)->RemoveRef(); + m_rwObject = nil; + GetMatrix().Detach(); +} + +#ifdef PED_SKIN +RpAtomic* +AtomicRemoveAnimFromSkinCB(RpAtomic *atomic, void *data) +{ + if(RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic))){ + RpHAnimHierarchy *hier = RpSkinAtomicGetHAnimHierarchy(atomic); +#ifdef LIBRW + if(hier && hier->interpolator->currentAnim){ + RpHAnimAnimationDestroy(hier->interpolator->currentAnim); + hier->interpolator->currentAnim = nil; + } +#else + if(hier && hier->pCurrentAnim){ + RpHAnimAnimationDestroy(hier->pCurrentAnim); + hier->pCurrentAnim = nil; + } +#endif + } + return atomic; +} +#endif + +void +CEntity::DeleteRwObject(void) +{ + RwFrame *f; + + GetMatrix().Detach(); + if(m_rwObject){ + if(RwObjectGetType(m_rwObject) == rpATOMIC){ + f = RpAtomicGetFrame((RpAtomic*)m_rwObject); + RpAtomicDestroy((RpAtomic*)m_rwObject); + RwFrameDestroy(f); + }else if(RwObjectGetType(m_rwObject) == rpCLUMP){ +#ifdef PED_SKIN + if(IsClumpSkinned((RpClump*)m_rwObject)) + RpClumpForAllAtomics((RpClump*)m_rwObject, AtomicRemoveAnimFromSkinCB, nil); +#endif + RpClumpDestroy((RpClump*)m_rwObject); + } + m_rwObject = nil; + CModelInfo::GetModelInfo(m_modelIndex)->RemoveRef(); + if(IsBuilding()) + gBuildings--; + } +} + +CRect +CEntity::GetBoundRect(void) +{ + CRect rect; + CVector v; + CColModel *col = CModelInfo::GetColModel(m_modelIndex); + + rect.ContainPoint(GetMatrix() * col->boundingBox.min); + rect.ContainPoint(GetMatrix() * col->boundingBox.max); + + v = col->boundingBox.min; + v.x = col->boundingBox.max.x; + rect.ContainPoint(GetMatrix() * v); + + v = col->boundingBox.max; + v.x = col->boundingBox.min.x; + rect.ContainPoint(GetMatrix() * v); + + return rect; +} + +CVector +CEntity::GetBoundCentre(void) +{ + return GetMatrix() * CModelInfo::GetColModel(m_modelIndex)->boundingSphere.center; +} + +#ifdef GTA_PS2 +void +CEntity::GetBoundCentre(CVuVector &out) +{ + TransformPoint(out, GetMatrix(), CModelInfo::GetColModel(m_modelIndex)->boundingSphere.center); +} +#else +void +CEntity::GetBoundCentre(CVector &out) +{ + out = GetMatrix() * CModelInfo::GetColModel(m_modelIndex)->boundingSphere.center; +} +#endif + +float +CEntity::GetBoundRadius(void) +{ + return CModelInfo::GetColModel(m_modelIndex)->boundingSphere.radius; +} + +void +CEntity::UpdateRwFrame(void) +{ + if(m_rwObject) + RwFrameUpdateObjects((RwFrame*)rwObjectGetParent(m_rwObject)); +} + +#ifdef PED_SKIN +void +CEntity::UpdateRpHAnim(void) +{ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + RpHAnimHierarchyUpdateMatrices(hier); + +#if 0 + int i; + char buf[256]; + if(this == (CEntity*)FindPlayerPed()) + for(i = 0; i < hier->numNodes; i++){ + RpHAnimStdInterpFrame *kf = (RpHAnimStdInterpFrame*)rpHANIMHIERARCHYGETINTERPFRAME(hier, i); + sprintf(buf, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %d %s", + kf->q.imag.x, kf->q.imag.y, kf->q.imag.z, kf->q.real, + kf->t.x, kf->t.y, kf->t.z, + HIERNODEID(hier, i), + ConvertBoneTag2BoneName(HIERNODEID(hier, i))); + CDebug::PrintAt(buf, 10, 1+i*3); + + RwMatrix *m = &RpHAnimHierarchyGetMatrixArray(hier)[i]; + sprintf(buf, "%6.3f %6.3f %6.3f %6.3f", + m->right.x, m->up.x, m->at.x, m->pos.x); + CDebug::PrintAt(buf, 80, 1+i*3+0); + sprintf(buf, "%6.3f %6.3f %6.3f %6.3f", + m->right.y, m->up.y, m->at.y, m->pos.y); + CDebug::PrintAt(buf, 80, 1+i*3+1); + sprintf(buf, "%6.3f %6.3f %6.3f %6.3f", + m->right.z, m->up.z, m->at.z, m->pos.z); + CDebug::PrintAt(buf, 80, 1+i*3+2); + } + + void RenderSkeleton(RpHAnimHierarchy *hier); + RenderSkeleton(hier); +#endif +} +#endif + +void +CEntity::PreRender(void) +{ + switch(m_type){ + case ENTITY_TYPE_BUILDING: + if(GetModelIndex() == MI_RAILTRACKS){ + CShadows::StoreShadowForPole(this, 0.0f, -10.949f, 5.0f, 8.0f, 1.0f, 0); + CShadows::StoreShadowForPole(this, 0.0f, 10.949f, 5.0f, 8.0f, 1.0f, 1); + }else if(IsTreeModel(GetModelIndex())){ + CShadows::StoreShadowForTree(this); + ModifyMatrixForTreeInWind(); + }else if(IsBannerModel(GetModelIndex())){ + ModifyMatrixForBannerInWind(); + } + break; + case ENTITY_TYPE_OBJECT: + if(GetModelIndex() == MI_COLLECTABLE1){ + CPickups::DoCollectableEffects(this); + GetMatrix().UpdateRW(); + UpdateRwFrame(); + }else if(GetModelIndex() == MI_MONEY){ + CPickups::DoMoneyEffects(this); + GetMatrix().UpdateRW(); + UpdateRwFrame(); + }else if(GetModelIndex() == MI_NAUTICALMINE || + GetModelIndex() == MI_CARMINE || + GetModelIndex() == MI_BRIEFCASE){ + if(((CObject*)this)->bIsPickup){ + CPickups::DoMineEffects(this); + GetMatrix().UpdateRW(); + UpdateRwFrame(); + } + }else if(IsPickupModel(GetModelIndex())){ + if(((CObject*)this)->bIsPickup){ + CPickups::DoPickUpEffects(this); + GetMatrix().UpdateRW(); + UpdateRwFrame(); + }else if(GetModelIndex() == MI_GRENADE){ + CMotionBlurStreaks::RegisterStreak((uintptr)this, + 100, 100, 100, + GetPosition() - 0.07f*TheCamera.GetRight(), + GetPosition() + 0.07f*TheCamera.GetRight()); + }else if(GetModelIndex() == MI_MOLOTOV){ + CMotionBlurStreaks::RegisterStreak((uintptr)this, + 0, 100, 0, + GetPosition() - 0.07f*TheCamera.GetRight(), + GetPosition() + 0.07f*TheCamera.GetRight()); + } + }else if(GetModelIndex() == MI_MISSILE){ + CVector pos = GetPosition(); + float flicker = (CGeneral::GetRandomNumber() & 0xF)/(float)0x10; + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, + gpShadowExplosionTex, &pos, + 8.0f, 0.0f, 0.0f, -8.0f, + 255, 200.0f*flicker, 160.0f*flicker, 120.0f*flicker, + 20.0f, false, 1.0f); + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos, CVector(0.0f, 0.0f, 0.0f), + 8.0f, + 1.0f*flicker, + 0.8f*flicker, + 0.6f*flicker, + CPointLights::FOG_NONE, true); + CCoronas::RegisterCorona((uintptr)this, + 255.0f*flicker, 220.0f*flicker, 190.0f*flicker, 255, + pos, 6.0f*flicker, 80.0f, gpCoronaTexture[CCoronas::TYPE_STAR], + CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + }else if(IsGlass(GetModelIndex())){ + PreRenderForGlassWindow(); + } + // fall through + case ENTITY_TYPE_DUMMY: + if(GetModelIndex() == MI_TRAFFICLIGHTS){ + CTrafficLights::DisplayActualLight(this); + CShadows::StoreShadowForPole(this, 2.957f, 0.147f, 0.0f, 16.0f, 0.4f, 0); + }else if(GetModelIndex() == MI_SINGLESTREETLIGHTS1) + CShadows::StoreShadowForPole(this, 0.744f, 0.0f, 0.0f, 16.0f, 0.4f, 0); + else if(GetModelIndex() == MI_SINGLESTREETLIGHTS2) + CShadows::StoreShadowForPole(this, 0.043f, 0.0f, 0.0f, 16.0f, 0.4f, 0); + else if(GetModelIndex() == MI_SINGLESTREETLIGHTS3) + CShadows::StoreShadowForPole(this, 1.143f, 0.145f, 0.0f, 16.0f, 0.4f, 0); + else if(GetModelIndex() == MI_DOUBLESTREETLIGHTS) + CShadows::StoreShadowForPole(this, 0.0f, -0.048f, 0.0f, 16.0f, 0.4f, 0); + else if(GetModelIndex() == MI_STREETLAMP1 || + GetModelIndex() == MI_STREETLAMP2) + CShadows::StoreShadowForPole(this, 0.0f, 0.0f, 0.0f, 16.0f, 0.4f, 0); + break; + } + + if (CModelInfo::GetModelInfo(GetModelIndex())->GetNum2dEffects() != 0) + ProcessLightsForEntity(); +} + +void +CEntity::Render(void) +{ + if(m_rwObject){ + bImBeingRendered = true; + if(RwObjectGetType(m_rwObject) == rpATOMIC) + RpAtomicRender((RpAtomic*)m_rwObject); + else + RpClumpRender((RpClump*)m_rwObject); + bImBeingRendered = false; + } +} + + +bool +CEntity::GetIsTouching(CVUVECTOR const ¢er, float radius) +{ + CVUVECTOR boundCenter; + GetBoundCentre(boundCenter); + return sq(GetBoundRadius()+radius) > (boundCenter-center).MagnitudeSqr(); +} + +bool +CEntity::IsVisible(void) +{ + return m_rwObject && bIsVisible && GetIsOnScreen(); +} + +bool +CEntity::IsVisibleComplex(void) +{ + return m_rwObject && bIsVisible && GetIsOnScreenComplex(); +} + +bool +CEntity::GetIsOnScreen(void) +{ + return TheCamera.IsSphereVisible(GetBoundCentre(), GetBoundRadius()); +} + +bool +CEntity::GetIsOnScreenComplex(void) +{ +#ifdef GTA_PS2 + CVuVector boundBox[8]; +#else + CVector boundBox[8]; +#endif + + if(TheCamera.IsPointVisible(GetBoundCentre(), &TheCamera.GetCameraMatrix())) + return true; + + CRect rect = GetBoundRect(); + CColModel *colmodel = CModelInfo::GetColModel(m_modelIndex); + float z = GetPosition().z; + float minz = z + colmodel->boundingBox.min.z; + float maxz = z + colmodel->boundingBox.max.z; + boundBox[0].x = rect.left; + boundBox[0].y = rect.bottom; + boundBox[0].z = minz; + boundBox[1].x = rect.left; + boundBox[1].y = rect.top; + boundBox[1].z = minz; + boundBox[2].x = rect.right; + boundBox[2].y = rect.bottom; + boundBox[2].z = minz; + boundBox[3].x = rect.right; + boundBox[3].y = rect.top; + boundBox[3].z = minz; + boundBox[4].x = rect.left; + boundBox[4].y = rect.bottom; + boundBox[4].z = maxz; + boundBox[5].x = rect.left; + boundBox[5].y = rect.top; + boundBox[5].z = maxz; + boundBox[6].x = rect.right; + boundBox[6].y = rect.bottom; + boundBox[6].z = maxz; + boundBox[7].x = rect.right; + boundBox[7].y = rect.top; + boundBox[7].z = maxz; + + return TheCamera.IsBoxVisible(boundBox, &TheCamera.GetCameraMatrix()); +} + +void +CEntity::Add(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES]; + break; + }else switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP]; + break; + } + list->InsertItem(this); + } +} + +void +CEntity::Remove(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES]; + break; + }else switch(m_type){ + case ENTITY_TYPE_BUILDING: + list = &s->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]; + break; + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + case ENTITY_TYPE_DUMMY: + list = &s->m_lists[ENTITYLIST_DUMMIES_OVERLAP]; + break; + } + list->RemoveItem(this); + } +} + +float +CEntity::GetDistanceFromCentreOfMassToBaseOfModel(void) +{ + return -CModelInfo::GetColModel(m_modelIndex)->boundingBox.min.z; +} + +void +CEntity::SetupBigBuilding(void) +{ + CSimpleModelInfo *mi; + + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_modelIndex); + bIsBIGBuilding = true; + bStreamingDontDelete = true; + bUsesCollision = false; + m_level = CTheZones::GetLevelFromPosition(&GetPosition()); + if(m_level == LEVEL_GENERIC){ + if(mi->GetTxdSlot() != CTxdStore::FindTxdSlot("generic")){ + mi->SetTexDictionary("generic"); + printf("%d:%s txd has been set to generic\n", m_modelIndex, mi->GetModelName()); + } + } + if(mi->m_lodDistances[0] > 2000.0f) + m_level = LEVEL_GENERIC; +} + +float WindTabel[] = { + 1.0f, 0.5f, 0.2f, 0.7f, 0.4f, 1.0f, 0.5f, 0.3f, + 0.2f, 0.1f, 0.7f, 0.6f, 0.3f, 1.0f, 0.5f, 0.2f, +}; + +void +CEntity::ModifyMatrixForTreeInWind(void) +{ + uint16 t; + float f; + float strength, flutter; + + if(CTimer::GetIsPaused()) + return; + + CMatrix mat(GetMatrix().m_attachment); + + if(CWeather::Wind >= 0.5){ + t = m_randomSeed + 16*CTimer::GetTimeInMilliseconds(); + f = (t & 0xFFF)/(float)0x1000; + flutter = f * WindTabel[(t>>12)+1 & 0xF] + + (1.0f - f) * WindTabel[(t>>12) & 0xF] + + 1.0f; + strength = CWeather::Wind < 0.8f ? 0.008f : 0.014f; + }else if(CWeather::Wind >= 0.2){ + t = (uintptr)this + CTimer::GetTimeInMilliseconds(); + f = (t & 0xFFF)/(float)0x1000; + flutter = Sin(f * 6.28f); + strength = 0.008f; + }else{ + t = (uintptr)this + CTimer::GetTimeInMilliseconds(); + f = (t & 0xFFF)/(float)0x1000; + flutter = Sin(f * 6.28f); + strength = 0.005f; + } + + mat.GetUp().x = strength * flutter; + mat.GetUp().y = mat.GetUp().x; + + mat.UpdateRW(); + UpdateRwFrame(); +} + +float BannerWindTabel[] = { + 0.0f, 0.3f, 0.6f, 0.85f, 0.99f, 0.97f, 0.65f, 0.15f, + -0.1f, 0.0f, 0.35f, 0.57f, 0.55f, 0.35f, 0.45f, 0.67f, + 0.73f, 0.45f, 0.25f, 0.35f, 0.35f, 0.11f, 0.13f, 0.21f, + 0.28f, 0.28f, 0.22f, 0.1f, 0.0f, -0.1f, -0.17f, -0.12f +}; + +void +CEntity::ModifyMatrixForBannerInWind(void) +{ + uint16 t; + float f; + float strength, flutter; + CVector right, up; + + if(CTimer::GetIsPaused()) + return; + + if(CWeather::Wind < 0.1f) + strength = 0.2f; + else if(CWeather::Wind < 0.8f) + strength = 0.43f; + else + strength = 0.66f; + + t = ((int)(GetMatrix().GetPosition().x + GetMatrix().GetPosition().y) << 10) + 16*CTimer::GetTimeInMilliseconds(); + f = (t & 0x7FF)/(float)0x800; + flutter = f * BannerWindTabel[(t>>11)+1 & 0x1F] + + (1.0f - f) * BannerWindTabel[(t>>11) & 0x1F]; + flutter *= strength; + + right = CrossProduct(GetForward(), GetUp()); + right.z = 0.0f; + right.Normalise(); + up = right * flutter; + up.z = Sqrt(sq(1.0f) - sq(flutter)); + GetRight() = CrossProduct(GetForward(), up); + GetUp() = up; + + GetMatrix().UpdateRW(); + UpdateRwFrame(); +} + +void +CEntity::PreRenderForGlassWindow(void) +{ + CGlass::AskForObjectToBeRenderedInGlass(this); + bIsVisible = false; +} + +#ifdef COMPATIBLE_SAVES +void +CEntity::SaveEntityFlags(uint8*& buf) +{ + uint32 tmp = 0; + tmp |= (m_type & (BIT(3) - 1)); + tmp |= (m_status & (BIT(5) - 1)) << 3; + + if (bUsesCollision) tmp |= BIT(8); + if (bCollisionProcessed) tmp |= BIT(9); + if (bIsStatic) tmp |= BIT(10); + if (bHasContacted) tmp |= BIT(11); + if (bPedPhysics) tmp |= BIT(12); + if (bIsStuck) tmp |= BIT(13); + if (bIsInSafePosition) tmp |= BIT(14); + if (bUseCollisionRecords) tmp |= BIT(15); + + if (bWasPostponed) tmp |= BIT(16); + if (bExplosionProof) tmp |= BIT(17); + if (bIsVisible) tmp |= BIT(18); + if (bHasCollided) tmp |= BIT(19); + if (bRenderScorched) tmp |= BIT(20); + if (bHasBlip) tmp |= BIT(21); + if (bIsBIGBuilding) tmp |= BIT(22); + if (bRenderDamaged) tmp |= BIT(23); + + if (bBulletProof) tmp |= BIT(24); + if (bFireProof) tmp |= BIT(25); + if (bCollisionProof) tmp |= BIT(26); + if (bMeleeProof) tmp |= BIT(27); + if (bOnlyDamagedByPlayer) tmp |= BIT(28); + if (bStreamingDontDelete) tmp |= BIT(29); + if (bZoneCulled) tmp |= BIT(30); + if (bZoneCulled2) tmp |= BIT(31); + + WriteSaveBuf(buf, tmp); + + tmp = 0; + + if (bRemoveFromWorld) tmp |= BIT(0); + if (bHasHitWall) tmp |= BIT(1); + if (bImBeingRendered) tmp |= BIT(2); + if (bTouchingWater) tmp |= BIT(3); + if (bIsSubway) tmp |= BIT(4); + if (bDrawLast) tmp |= BIT(5); + if (bNoBrightHeadLights) tmp |= BIT(6); + if (bDoNotRender) tmp |= BIT(7); + + if (bDistanceFade) tmp |= BIT(8); + if (m_flagE2) tmp |= BIT(9); + + WriteSaveBuf(buf, tmp); +} + +void +CEntity::LoadEntityFlags(uint8*& buf) +{ + uint32 tmp; + ReadSaveBuf(&tmp, buf); + m_type = (tmp & ((BIT(3) - 1))); + m_status = ((tmp >> 3) & (BIT(5) - 1)); + + bUsesCollision = !!(tmp & BIT(8)); + bCollisionProcessed = !!(tmp & BIT(9)); + bIsStatic = !!(tmp & BIT(10)); + bHasContacted = !!(tmp & BIT(11)); + bPedPhysics = !!(tmp & BIT(12)); + bIsStuck = !!(tmp & BIT(13)); + bIsInSafePosition = !!(tmp & BIT(14)); + bUseCollisionRecords = !!(tmp & BIT(15)); + + bWasPostponed = !!(tmp & BIT(16)); + bExplosionProof = !!(tmp & BIT(17)); + bIsVisible = !!(tmp & BIT(18)); + bHasCollided = !!(tmp & BIT(19)); + bRenderScorched = !!(tmp & BIT(20)); + bHasBlip = !!(tmp & BIT(21)); + bIsBIGBuilding = !!(tmp & BIT(22)); + bRenderDamaged = !!(tmp & BIT(23)); + + bBulletProof = !!(tmp & BIT(24)); + bFireProof = !!(tmp & BIT(25)); + bCollisionProof = !!(tmp & BIT(26)); + bMeleeProof = !!(tmp & BIT(27)); + bOnlyDamagedByPlayer = !!(tmp & BIT(28)); + bStreamingDontDelete = !!(tmp & BIT(29)); + bZoneCulled = !!(tmp & BIT(30)); + bZoneCulled2 = !!(tmp & BIT(31)); + + ReadSaveBuf(&tmp, buf); + + bRemoveFromWorld = !!(tmp & BIT(0)); + bHasHitWall = !!(tmp & BIT(1)); + bImBeingRendered = !!(tmp & BIT(2)); + bTouchingWater = !!(tmp & BIT(3)); + bIsSubway = !!(tmp & BIT(4)); + bDrawLast = !!(tmp & BIT(5)); + bNoBrightHeadLights = !!(tmp & BIT(6)); + bDoNotRender = !!(tmp & BIT(7)); + + bDistanceFade = !!(tmp & BIT(8)); + m_flagE2 = !!(tmp & BIT(9)); +} + +#endif diff --git a/src/entities/Entity.h b/src/entities/Entity.h new file mode 100644 index 0000000..6174b61 --- /dev/null +++ b/src/entities/Entity.h @@ -0,0 +1,175 @@ +#pragma once + +#include "ModelInfo.h" +#include "Placeable.h" + +struct CReference; +class CPtrList; + +enum eEntityType +{ + ENTITY_TYPE_NOTHING = 0, + ENTITY_TYPE_BUILDING, + ENTITY_TYPE_VEHICLE, + ENTITY_TYPE_PED, + ENTITY_TYPE_OBJECT, + ENTITY_TYPE_DUMMY, +}; + +enum eEntityStatus +{ + STATUS_PLAYER, + STATUS_PLAYER_PLAYBACKFROMBUFFER, + STATUS_SIMPLE, + STATUS_PHYSICS, + STATUS_ABANDONED, + STATUS_WRECKED, + STATUS_TRAIN_MOVING, + STATUS_TRAIN_NOT_MOVING, + STATUS_HELI, + STATUS_PLANE, + STATUS_PLAYER_REMOTE, + STATUS_PLAYER_DISABLED, +}; + +class CEntity : public CPlaceable +{ +public: + RwObject *m_rwObject; +protected: + uint32 m_type : 3; +private: + uint32 m_status : 5; +public: + // flagsA + uint32 bUsesCollision : 1; // does entity use collision + uint32 bCollisionProcessed : 1; // has object been processed by a ProcessEntityCollision function + uint32 bIsStatic : 1; // is entity static + uint32 bHasContacted : 1; // has entity processed some contact forces + uint32 bPedPhysics : 1; + uint32 bIsStuck : 1; // is entity stuck + uint32 bIsInSafePosition : 1; // is entity in a collision free safe position + uint32 bUseCollisionRecords : 1; + + // flagsB + uint32 bWasPostponed : 1; // was entity control processing postponed + uint32 bExplosionProof : 1; + uint32 bIsVisible : 1; //is the entity visible + uint32 bHasCollided : 1; + uint32 bRenderScorched : 1; + uint32 bHasBlip : 1; + uint32 bIsBIGBuilding : 1; // Set if this entity is a big building + uint32 bRenderDamaged : 1; // use damaged LOD models for objects with applicable damage + + // flagsC + uint32 bBulletProof : 1; + uint32 bFireProof : 1; + uint32 bCollisionProof : 1; + uint32 bMeleeProof : 1; + uint32 bOnlyDamagedByPlayer : 1; + uint32 bStreamingDontDelete : 1; // Dont let the streaming remove this + uint32 bZoneCulled : 1; + uint32 bZoneCulled2 : 1; // only treadables+10m + + // flagsD + uint32 bRemoveFromWorld : 1; // remove this entity next time it should be processed + uint32 bHasHitWall : 1; // has collided with a building (changes subsequent collisions) + uint32 bImBeingRendered : 1; // don't delete me because I'm being rendered + uint32 bTouchingWater : 1; // used by cBuoyancy::ProcessBuoyancy + uint32 bIsSubway : 1; // set when subway, but maybe different meaning? + uint32 bDrawLast : 1; // draw object last + uint32 bNoBrightHeadLights : 1; + uint32 bDoNotRender : 1; + + // flagsE + uint32 bDistanceFade : 1; // Fade entity because it is far away + uint32 m_flagE2 : 1; + + uint16 m_scanCode; + uint16 m_randomSeed; + int16 m_modelIndex; + uint16 m_level; // int16 + CReference *m_pFirstReference; + +public: + uint8 GetType() const { return m_type; } + void SetType(uint8 type) { m_type = type; } + uint8 GetStatus() const { return m_status; } + void SetStatus(uint8 status) { m_status = status; } + CColModel *GetColModel(void) { return CModelInfo::GetModelInfo(m_modelIndex)->GetColModel(); } + bool GetIsStatic(void) const { return bIsStatic; } + void SetIsStatic(bool state) { bIsStatic = state; } +#ifdef COMPATIBLE_SAVES + void SaveEntityFlags(uint8*& buf); + void LoadEntityFlags(uint8*& buf); +#else + uint32* GetAddressOfEntityProperties() { /* AWFUL */ return (uint32*)((char*)&m_rwObject + sizeof(m_rwObject)); } +#endif + + CEntity(void); + ~CEntity(void); + + virtual void Add(void); + virtual void Remove(void); + virtual void SetModelIndex(uint32 id); + virtual void SetModelIndexNoCreate(uint32 id); + virtual void CreateRwObject(void); + virtual void DeleteRwObject(void); + virtual CRect GetBoundRect(void); + virtual void ProcessControl(void) {} + virtual void ProcessCollision(void) {} + virtual void ProcessShift(void) {} + virtual void Teleport(CVector v) {} + virtual void PreRender(void); + virtual void Render(void); + virtual bool SetupLighting(void); + virtual void RemoveLighting(bool); + virtual void FlagToDestroyWhenNextProcessed(void) {} + + bool IsBuilding(void) { return m_type == ENTITY_TYPE_BUILDING; } + bool IsVehicle(void) { return m_type == ENTITY_TYPE_VEHICLE; } + bool IsPed(void) { return m_type == ENTITY_TYPE_PED; } + bool IsObject(void) { return m_type == ENTITY_TYPE_OBJECT; } + bool IsDummy(void) { return m_type == ENTITY_TYPE_DUMMY; } + + RpAtomic *GetAtomic(void) { + assert(RwObjectGetType(m_rwObject) == rpATOMIC); + return (RpAtomic*)m_rwObject; + } + RpClump *GetClump(void) { + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + return (RpClump*)m_rwObject; + } + + void GetBoundCentre(CVUVECTOR &out); + CVector GetBoundCentre(void); + float GetBoundRadius(void); + float GetDistanceFromCentreOfMassToBaseOfModel(void); + bool GetIsTouching(CVUVECTOR const ¢er, float r); + bool GetIsOnScreen(void); + bool GetIsOnScreenComplex(void); + bool IsVisible(void); + bool IsVisibleComplex(void); + int16 GetModelIndex(void) const { return m_modelIndex; } + void UpdateRwFrame(void); + void SetupBigBuilding(void); + + void AttachToRwObject(RwObject *obj); + void DetachFromRwObject(void); + + void RegisterReference(CEntity **pent); + void ResolveReferences(void); + void PruneReferences(void); + +#ifdef PED_SKIN + void UpdateRpHAnim(void); +#endif + + void PreRenderForGlassWindow(void); + void AddSteamsFromGround(CVector *unused); + void ModifyMatrixForTreeInWind(void); + void ModifyMatrixForBannerInWind(void); + void ProcessLightsForEntity(void); +}; + +VALIDATE_SIZE(CEntity, 0x64); diff --git a/src/entities/Physical.cpp b/src/entities/Physical.cpp new file mode 100644 index 0000000..32a3df3 --- /dev/null +++ b/src/entities/Physical.cpp @@ -0,0 +1,2002 @@ +#include "common.h" + +#include "World.h" +#include "Timer.h" +#include "ModelIndices.h" +#include "Treadable.h" +#include "Vehicle.h" +#include "Ped.h" +#include "Object.h" +#include "Glass.h" +#include "ParticleObject.h" +#include "Particle.h" +#include "SurfaceTable.h" +#include "PathFind.h" +#include "CarCtrl.h" +#include "DMAudio.h" +#include "Automobile.h" +#include "Physical.h" +#include "Bike.h" + +CPhysical::CPhysical(void) +{ + int i; + +#ifdef FIX_BUGS + m_nLastTimeCollided = 0; +#endif + + m_fForceMultiplier = 1.0f; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveSpeedAvg = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeedAvg = CVector(0.0f, 0.0f, 0.0f); + + m_movingListNode = nil; + m_nStaticFrames = 0; + + m_nCollisionRecords = 0; + for(i = 0; i < 6; i++) + m_aCollisionRecords[i] = nil; + + m_bIsVehicleBeingShifted = false; + + m_nDamagePieceType = 0; + m_fDamageImpulse = 0.0f; + m_pDamageEntity = nil; + m_vecDamageNormal = CVector(0.0f, 0.0f, 0.0f); + + bUsesCollision = true; + m_audioEntityId = -5; + m_phys_unused1 = 100.0f; + m_vecCentreOfMass = CVector(0.0f, 0.0f, 0.0f); + m_phys_unused2 = 0; + + bIsHeavy = false; + bAffectedByGravity = true; + bInfiniteMass = false; + bIsInWater = false; + bHitByTrain = false; + bSkipLineCol = false; + + m_fDistanceTravelled = 0.0f; + m_treadable[PATH_CAR] = nil; + m_treadable[PATH_PED] = nil; + + m_phy_flagA10 = false; + m_phy_flagA20 = false; + +#ifdef FIX_BUGS + m_nSurfaceTouched = SURFACE_DEFAULT; +#endif + m_nZoneLevel = LEVEL_GENERIC; +} + +CPhysical::~CPhysical(void) +{ + m_entryInfoList.Flush(); +} + +void +CPhysical::Add(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + default: + assert(0); + }else switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + default: + assert(0); + } + CPtrNode *node = list->InsertItem(this); + assert(node); + m_entryInfoList.InsertItem(list, node, s); + } +} + +void +CPhysical::Remove(void) +{ + CEntryInfoNode *node, *next; + for(node = m_entryInfoList.first; node; node = next){ + next = node->next; + node->list->DeleteNode(node->listnode); + m_entryInfoList.DeleteNode(node); + } +} + +void +CPhysical::RemoveAndAdd(void) +{ + int x, xstart, xmid, xend; + int y, ystart, ymid, yend; + CSector *s; + CPtrList *list; + + CRect bounds = GetBoundRect(); + xstart = CWorld::GetSectorIndexX(bounds.left); + xend = CWorld::GetSectorIndexX(bounds.right); + xmid = CWorld::GetSectorIndexX((bounds.left + bounds.right)/2.0f); + ystart = CWorld::GetSectorIndexY(bounds.top); + yend = CWorld::GetSectorIndexY(bounds.bottom); + ymid = CWorld::GetSectorIndexY((bounds.top + bounds.bottom)/2.0f); + assert(xstart >= 0); + assert(xend < NUMSECTORS_X); + assert(ystart >= 0); + assert(yend < NUMSECTORS_Y); + + // we'll try to recycle nodes from here + CEntryInfoNode *next = m_entryInfoList.first; + + for(y = ystart; y <= yend; y++) + for(x = xstart; x <= xend; x++){ + s = CWorld::GetSector(x, y); + if(x == xmid && y == ymid) switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS]; + break; + }else switch(m_type){ + case ENTITY_TYPE_VEHICLE: + list = &s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]; + break; + case ENTITY_TYPE_PED: + list = &s->m_lists[ENTITYLIST_PEDS_OVERLAP]; + break; + case ENTITY_TYPE_OBJECT: + list = &s->m_lists[ENTITYLIST_OBJECTS_OVERLAP]; + break; + } + if(next){ + // If we still have old nodes, use them + next->list->RemoveNode(next->listnode); + list->InsertNode(next->listnode); + next->list = list; + next->sector = s; + next = next->next; + }else{ + CPtrNode *node = list->InsertItem(this); + m_entryInfoList.InsertItem(list, node, s); + } + } + + // Remove old nodes we no longer need + CEntryInfoNode *node; + for(node = next; node; node = next){ + next = node->next; + node->list->DeleteNode(node->listnode); + m_entryInfoList.DeleteNode(node); + } +} + +CRect +CPhysical::GetBoundRect(void) +{ + CVector center; + float radius; + center = GetBoundCentre(); + radius = GetBoundRadius(); + return CRect(center.x-radius, center.y-radius, center.x+radius, center.y+radius); +} + +void +CPhysical::AddToMovingList(void) +{ + m_movingListNode = CWorld::GetMovingEntityList().InsertItem(this); +} + +void +CPhysical::RemoveFromMovingList(void) +{ + if(m_movingListNode){ + CWorld::GetMovingEntityList().DeleteNode(m_movingListNode); + m_movingListNode = nil; + } +} + +void +CPhysical::SetDamagedPieceRecord(uint16 piece, float impulse, CEntity *entity, CVector dir) +{ + m_nDamagePieceType = piece; + m_fDamageImpulse = impulse; + m_pDamageEntity = entity; + entity->RegisterReference(&m_pDamageEntity); + m_vecDamageNormal = dir; +} + +void +CPhysical::AddCollisionRecord(CEntity *ent) +{ + AddCollisionRecord_Treadable(ent); + this->bHasCollided = true; + ent->bHasCollided = true; + if(IsVehicle() && ent->IsVehicle()){ + if(((CVehicle*)this)->m_nAlarmState == -1) + ((CVehicle*)this)->m_nAlarmState = 15000; + if(((CVehicle*)ent)->m_nAlarmState == -1) + ((CVehicle*)ent)->m_nAlarmState = 15000; + } + if(bUseCollisionRecords){ + int i; + for(i = 0; i < m_nCollisionRecords; i++) + if(m_aCollisionRecords[i] == ent) + return; + if(m_nCollisionRecords < PHYSICAL_MAX_COLLISIONRECORDS) + m_aCollisionRecords[m_nCollisionRecords++] = ent; + m_nLastTimeCollided = CTimer::GetTimeInMilliseconds(); + } +} + +void +CPhysical::AddCollisionRecord_Treadable(CEntity *ent) +{ + if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){ + CTreadable *t = (CTreadable*)ent; + if(t->m_nodeIndices[PATH_PED][0] >= 0 || + t->m_nodeIndices[PATH_PED][1] >= 0 || + t->m_nodeIndices[PATH_PED][2] >= 0 || + t->m_nodeIndices[PATH_PED][3] >= 0) + m_treadable[PATH_PED] = t; + if(t->m_nodeIndices[PATH_CAR][0] >= 0 || + t->m_nodeIndices[PATH_CAR][1] >= 0 || + t->m_nodeIndices[PATH_CAR][2] >= 0 || + t->m_nodeIndices[PATH_CAR][3] >= 0) + m_treadable[PATH_CAR] = t; + } +} + +bool +CPhysical::GetHasCollidedWith(CEntity *ent) +{ + int i; + if(bUseCollisionRecords) + for(i = 0; i < m_nCollisionRecords; i++) + if(m_aCollisionRecords[i] == ent) + return true; + return false; +} + +void +CPhysical::RemoveRefsToEntity(CEntity *ent) +{ + int i = 0, j; + + while(i < m_nCollisionRecords) { + if(m_aCollisionRecords[i] == ent){ + for(j = i; j < m_nCollisionRecords-1; j++) + m_aCollisionRecords[j] = m_aCollisionRecords[j+1]; + m_nCollisionRecords--; + } else + i++; + } +} + +void +CPhysical::PlacePhysicalRelativeToOtherPhysical(CPhysical *other, CPhysical *phys, CVector localPos) +{ + CVector worldPos = other->GetMatrix() * localPos; + float step = 0.9f * CTimer::GetTimeStep(); + CVector pos = other->m_vecMoveSpeed*step + worldPos; + + CWorld::Remove(phys); + phys->GetMatrix() = other->GetMatrix(); + phys->SetPosition(pos); + phys->m_vecMoveSpeed = other->m_vecMoveSpeed; + phys->GetMatrix().UpdateRW(); + phys->UpdateRwFrame(); + CWorld::Add(phys); +} + +int32 +CPhysical::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints) +{ + int32 numSpheres = CCollision::ProcessColModels( + GetMatrix(), *GetColModel(), + ent->GetMatrix(), *ent->GetColModel(), + colpoints, + nil, nil); // No Lines allowed! + if(numSpheres > 0){ + AddCollisionRecord(ent); + if(!ent->IsBuilding()) // Can't this catch dummies too? + ((CPhysical*)ent)->AddCollisionRecord(this); + if(ent->IsBuilding() || ent->GetIsStatic()) + this->bHasHitWall = true; + } + return numSpheres; +} + +void +CPhysical::ProcessControl(void) +{ + if(!IsPed()) + bIsInWater = false; + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + + if(GetStatus() == STATUS_SIMPLE) + return; + + m_nCollisionRecords = 0; + bHasCollided = false; + m_nDamagePieceType = 0; + m_fDamageImpulse = 0.0f; + m_pDamageEntity = nil; + + if(!bIsStuck){ + if(IsObject() || + IsPed() && !bPedPhysics){ + m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f; + m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f; + float step = CTimer::GetTimeStep() * 0.003f; + if(m_vecMoveSpeedAvg.MagnitudeSqr() < step*step && + m_vecTurnSpeedAvg.MagnitudeSqr() < step*step){ + m_nStaticFrames++; + if(m_nStaticFrames > 10){ + m_nStaticFrames = 10; + SetIsStatic(true); + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = m_vecMoveSpeed; + m_vecTurnFriction = m_vecTurnSpeed; + return; + } + }else + m_nStaticFrames = 0; + } + } + ApplyGravity(); + ApplyFriction(); + ApplyAirResistance(); +} + +/* + * Some quantities (german in parens): + * + * acceleration: distance/time^2: a + * velocity: distance/time: v (GTA: speed) + * momentum (impuls): velocity*mass: p + * impulse (kraftstoss): delta momentum, force*time: J + * + * angular equivalents: + * velocity -> angular velocity (GTA: turn speed) + * momentum -> angular momentum (drehimpuls): L = r cross p + * force -> torque (drehmoment): tau = r cross F + * mass -> moment of inertia, angular mass (drehmoment, drehmasse): I = L/omega (GTA: turn mass) + */ + +CVector +CPhysical::GetSpeed(const CVector &r) +{ + return m_vecMoveSpeed + m_vecMoveFriction + CrossProduct(m_vecTurnFriction + m_vecTurnSpeed, r); +} + +void +CPhysical::ApplyMoveSpeed(void) +{ + GetMatrix().Translate(m_vecMoveSpeed * CTimer::GetTimeStep()); +} + +void +CPhysical::ApplyTurnSpeed(void) +{ + // Move the coordinate axes by their speed + // Note that this denormalizes the matrix + CVector turnvec = m_vecTurnSpeed*CTimer::GetTimeStep(); + GetRight() += CrossProduct(turnvec, GetRight()); + GetForward() += CrossProduct(turnvec, GetForward()); + GetUp() += CrossProduct(turnvec, GetUp()); +} + +void +CPhysical::ApplyMoveForce(float jx, float jy, float jz) +{ + m_vecMoveSpeed += CVector(jx, jy, jz)*(1.0f/m_fMass); +} + +void +CPhysical::ApplyTurnForce(float jx, float jy, float jz, float px, float py, float pz) +{ + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz)); + m_vecTurnSpeed += turnimpulse*(1.0f/m_fTurnMass); +} + +void +CPhysical::ApplyFrictionMoveForce(float jx, float jy, float jz) +{ + m_vecMoveFriction += CVector(jx, jy, jz)*(1.0f/m_fMass); +} + +void +CPhysical::ApplyFrictionTurnForce(float jx, float jy, float jz, float px, float py, float pz) +{ + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + CVector turnimpulse = CrossProduct(CVector(px, py, pz)-com, CVector(jx, jy, jz)); + m_vecTurnFriction += turnimpulse*(1.0f/m_fTurnMass); +} + +bool +CPhysical::ApplySpringCollision(float springConst, CVector &springDir, CVector &point, float springRatio, float bias) +{ + float compression = 1.0f - springRatio; + if(compression > 0.0f){ + float step = Min(CTimer::GetTimeStep(), 3.0f); + float impulse = -GRAVITY*m_fMass*step * springConst * compression * bias*2.0f; + ApplyMoveForce(springDir*impulse); + ApplyTurnForce(springDir*impulse, point); + } + return true; +} + +// What exactly is speed? +bool +CPhysical::ApplySpringDampening(float damping, CVector &springDir, CVector &point, CVector &speed) +{ + float speedA = DotProduct(speed, springDir); + float speedB = DotProduct(GetSpeed(point), springDir); +#ifdef FIX_BUGS + if (speedB == 0.0f) + return true; +#endif + float step = Min(CTimer::GetTimeStep(), 3.0f); + float impulse = -damping * (speedA + speedB)/2.0f * m_fMass * step * 0.53f; + + // what is this? + float a = m_fTurnMass / ((point.MagnitudeSqr() + 1.0f) * 2.0f * m_fMass); + a = Min(a, 1.0f); + float b = Abs(impulse / (speedB * m_fMass)); + if(a < b) + impulse *= a/b; + + ApplyMoveForce(springDir*impulse); + ApplyTurnForce(springDir*impulse, point); + return true; +} + +void +CPhysical::ApplyGravity(void) +{ + if(bAffectedByGravity) + m_vecMoveSpeed.z -= GRAVITY * CTimer::GetTimeStep(); +} + +void +CPhysical::ApplyFriction(void) +{ + m_vecMoveSpeed += m_vecMoveFriction; + m_vecTurnSpeed += m_vecTurnFriction; + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); +} + +void +CPhysical::ApplyAirResistance(void) +{ + if(m_fAirResistance > 0.1f){ + float f = Pow(m_fAirResistance, CTimer::GetTimeStep()); + m_vecMoveSpeed *= f; + m_vecTurnSpeed *= f; + }else{ + float f = Pow(1.0f/Abs(m_fAirResistance*0.5f*m_vecMoveSpeed.MagnitudeSqr() + 1.0f), CTimer::GetTimeStep()); + m_vecMoveSpeed *= f; + m_vecTurnSpeed *= 0.99f; + } +} + +bool +CPhysical::ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB) +{ + float eA, eB; + CPhysical *A = this; + CObject *Bobj = (CObject*)B; + + bool ispedcontactA = false; + bool ispedcontactB = false; + + float massFactorA; + if(B->bPedPhysics){ + massFactorA = 10.0f; + if(B->IsPed() && ((CPed*)B)->m_pCurrentPhysSurface == A) + ispedcontactA = true; + }else + massFactorA = A->bIsHeavy ? 2.0f : 1.0f; + + float massFactorB; + if(A->bPedPhysics){ + if(A->IsPed() && ((CPed*)A)->IsPlayer() && B->IsVehicle() && + (B->GetStatus() == STATUS_ABANDONED || B->GetStatus() == STATUS_WRECKED || A->bHasHitWall)) + massFactorB = 2200.0f / B->m_fMass; + else + massFactorB = 10.0f; + + if(A->IsPed() && ((CPed*)A)->m_pCurrentPhysSurface == B) + ispedcontactB = true; + }else + massFactorB = B->bIsHeavy ? 2.0f : 1.0f; + + float speedA, speedB; + if(B->GetIsStatic()){ + if(A->bPedPhysics){ + speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + if(speedA < 0.0f){ + if(B->IsObject()){ + impulseA = -speedA * A->m_fMass; + impulseB = impulseA; + if(impulseA > Bobj->m_fUprootLimit){ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false); + else if(!B->bInfiniteMass) + B->SetIsStatic(false); + }else{ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToSoftCollision(B, impulseA); + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.GetNormal() * (1.0f + A->m_fElasticity) * impulseA); + return true; + } + }else if(!B->bInfiniteMass) + B->SetIsStatic(false); + + if(B->bInfiniteMass){ + impulseA = -speedA * A->m_fMass; + impulseB = 0.0f; + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.normal*(1.0f + A->m_fElasticity)*impulseA); + return true; + } + } + }else{ + CVector pointposA = colpoint.point - A->GetPosition(); + speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal); + if(speedA < 0.0f){ + if(B->IsObject()){ + if(A->bHasHitWall) + eA = -1.0f; + else + eA = -(1.0f + A->m_fElasticity); + impulseA = eA * speedA * A->GetMass(pointposA, colpoint.normal); + impulseB = impulseA; + + if(Bobj->m_nCollisionDamageEffect && impulseA > 20.0f){ + Bobj->ObjectDamage(impulseA); + if(!B->bUsesCollision){ + if(!A->bInfiniteMass){ + A->ApplyMoveForce(colpoint.normal*0.2f*impulseA); + A->ApplyTurnForce(colpoint.normal*0.2f*impulseA, pointposA); + } + return false; + } + } + + if((impulseA > Bobj->m_fUprootLimit || A->bIsStuck) && + !B->bInfiniteMass){ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToCollision(B, impulseA, A->m_vecMoveSpeed, colpoint.point, false); + else + B->SetIsStatic(false); + int16 model = B->GetModelIndex(); + if(model == MI_FIRE_HYDRANT && !Bobj->bHasBeenDamaged){ + CParticleObject::AddObject(POBJECT_FIRE_HYDRANT, B->GetPosition() - CVector(0.0f, 0.0f, 0.5f), true); + Bobj->bHasBeenDamaged = true; + }else if(B->IsObject() && !IsExplosiveThingModel(model)) + Bobj->bHasBeenDamaged = true; + }else{ + if(IsGlass(B->GetModelIndex())) + CGlass::WindowRespondsToSoftCollision(B, impulseA); + CVector f = colpoint.GetNormal() * impulseA; + if(A->IsVehicle() && colpoint.normal.z < 0.7f) + f.z *= 0.3f; + if(!A->bInfiniteMass){ + A->ApplyMoveForce(f); + if(!A->IsVehicle() || !CWorld::bNoMoreCollisionTorque) + A->ApplyTurnForce(f, pointposA); + } + return true; + } + }else if(!B->bInfiniteMass) + B->SetIsStatic(false); + } + } + + if(B->GetIsStatic()) + return false; + if(!B->bInfiniteMass) + B->AddToMovingList(); + } + + // B is not static + + if(A->bPedPhysics && B->bPedPhysics){ + // negative if A is moving towards B + speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + // positive if B is moving towards A + // not interested in how much B moves into A apparently? + // only interested in cases where A collided into B + speedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + float speedSum = Max(0.0f, DotProduct(B->m_vecMoveSpeed, colpoint.normal)); + // A has moved into B + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA-speedA) * A->m_fMass * massFactorA; + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.normal*(impulseA/massFactorA)); + return true; + } + }else if(A->bPedPhysics){ + CVector pointposB = colpoint.point - B->GetPosition(); + speedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal); + + float mA = A->m_fMass*massFactorA; + float mB = B->GetMassTweak(pointposB, colpoint.normal, massFactorB); + float speedSum = (mB*speedB + mA*speedA)/(mA + mB); + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA - speedA) * mA; + impulseB = -(eB - speedB) * mB; + CVector fA = colpoint.normal*(impulseA/massFactorA); + CVector fB = colpoint.normal*(-impulseB/massFactorB); + if(!A->bInfiniteMass){ + if(fA.z < 0.0f) fA.z = 0.0f; + if(ispedcontactB){ + fA.x *= 2.0f; + fA.y *= 2.0f; + } + A->ApplyMoveForce(fA); + } + if(!B->bInfiniteMass && !ispedcontactB){ + B->ApplyMoveForce(fB); + B->ApplyTurnForce(fB, pointposB); + } + return true; + } + }else if(B->bPedPhysics){ + CVector pointposA = colpoint.point - A->GetPosition(); + speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal); + speedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + + float mA = A->GetMassTweak(pointposA, colpoint.normal, massFactorA); + float mB = B->m_fMass*massFactorB; + float speedSum = (mB*speedB + mA*speedA)/(mA + mB); + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA - speedA) * mA; + impulseB = -(eB - speedB) * mB; + CVector fA = colpoint.normal*(impulseA/massFactorA); + CVector fB = colpoint.normal*(-impulseB/massFactorB); + if(!A->bInfiniteMass && !ispedcontactA){ + if(fA.z < 0.0f) fA.z = 0.0f; + A->ApplyMoveForce(fA); + A->ApplyTurnForce(fA, pointposA); + } + if(!B->bInfiniteMass){ + if(fB.z < 0.0f){ + fB.z = 0.0f; + if(Abs(speedA) < 0.01f) + fB *= 0.5f; + } + if(ispedcontactA){ + fB.x *= 2.0f; + fB.y *= 2.0f; + } + B->ApplyMoveForce(fB); + } + return true; + } + }else{ + CVector pointposA = colpoint.point - A->GetPosition(); + CVector pointposB = colpoint.point - B->GetPosition(); + speedA = DotProduct(A->GetSpeed(pointposA), colpoint.normal); + speedB = DotProduct(B->GetSpeed(pointposB), colpoint.normal); + float mA = A->GetMassTweak(pointposA, colpoint.normal, massFactorA); + float mB = B->GetMassTweak(pointposB, colpoint.normal, massFactorB); + float speedSum = (mB*speedB + mA*speedA)/(mA + mB); + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA - speedA) * mA; + impulseB = -(eB - speedB) * mB; + CVector fA = colpoint.normal*(impulseA/massFactorA); + CVector fB = colpoint.normal*(-impulseB/massFactorB); + if(A->IsVehicle() && !A->bHasHitWall){ + fA.x *= 1.4f; + fA.y *= 1.4f; + if(colpoint.normal.z < 0.7f) + fA.z *= 0.3f; + if(A->GetStatus() == STATUS_PLAYER) + pointposA *= 0.8f; + if(CWorld::bNoMoreCollisionTorque){ + A->ApplyFrictionMoveForce(fA*-0.3f); + A->ApplyFrictionTurnForce(fA*-0.3f, pointposA); + } + } + if(B->IsVehicle() && !B->bHasHitWall){ + fB.x *= 1.4f; + fB.y *= 1.4f; + if(-colpoint.normal.z < 0.7f) + fB.z *= 0.3f; + if(B->GetStatus() == STATUS_PLAYER) + pointposB *= 0.8f; + if(CWorld::bNoMoreCollisionTorque){ +#ifdef FIX_BUGS + B->ApplyFrictionMoveForce(fB*-0.3f); + B->ApplyFrictionTurnForce(fB*-0.3f, pointposB); +#else + A->ApplyFrictionMoveForce(fB*-0.3f); + A->ApplyFrictionTurnForce(fB*-0.3f, pointposB); +#endif + } + } + if(!A->bInfiniteMass){ + A->ApplyMoveForce(fA); + A->ApplyTurnForce(fA, pointposA); + } + if(!B->bInfiniteMass){ + if(B->bIsInSafePosition) + B->UnsetIsInSafePosition(); + B->ApplyMoveForce(fB); + B->ApplyTurnForce(fB, pointposB); + } + return true; + } + } + return false; +} + +bool +CPhysical::ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed) +{ + float normalSpeed; + float e; + CVector speed; + CVector vImpulse; + + if(bPedPhysics){ + normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal); + if(normalSpeed < 0.0f){ + impulse = -normalSpeed * m_fMass; + ApplyMoveForce(colpoint.normal * impulse); + return true; + } + }else{ + CVector pointpos = colpoint.point - GetPosition(); + speed = GetSpeed(pointpos); + normalSpeed = DotProduct(speed, colpoint.normal); + if(normalSpeed < 0.0f){ + float minspeed = 1.3f*GRAVITY * CTimer::GetTimeStep(); +#if GTA_VERSION >= GTA3_PC_11 + if ((IsObject() || IsVehicle() && (GetUp().z < -0.3f || ((CVehicle*)this)->IsBike() && (GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED))) && +#else + if((IsObject() || IsVehicle() && GetUp().z < -0.3f) && +#endif + !bHasContacted && + Abs(m_vecMoveSpeed.x) < minspeed && + Abs(m_vecMoveSpeed.y) < minspeed && + Abs(m_vecMoveSpeed.z) < minspeed*2.0f) + e = -1.0f; + else + e = -(m_fElasticity + 1.0f); + impulse = normalSpeed * e * GetMass(pointpos, colpoint.normal); + + // ApplyMoveForce + vImpulse = colpoint.normal*impulse; + if(IsVehicle() && + (!bHasHitWall || + !(m_vecMoveSpeed.MagnitudeSqr() > 0.1 || !(B->IsBuilding() || ((CPhysical*)B)->bInfiniteMass)))) + moveSpeed += vImpulse * 1.2f * (1.0f/m_fMass); + else + moveSpeed += vImpulse * (1.0f/m_fMass); + + // ApplyTurnForce + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + CVector turnimpulse = CrossProduct(pointpos-com, vImpulse); + turnSpeed += turnimpulse*(1.0f/m_fTurnMass); + + return true; + } + } + return false; +} + +bool +CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint) +{ + CVector speedA, speedB; + float normalSpeedA, normalSpeedB; + CVector vOtherSpeedA, vOtherSpeedB; + float fOtherSpeedA, fOtherSpeedB; + float speedSum; + CVector frictionDir; + float impulseA, impulseB; + float impulseLimit; + CPhysical *A = this; + + if(A->bPedPhysics && B->bPedPhysics){ + normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA; + vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeedA; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); +#endif + + speedSum = (B->m_fMass*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(B->m_fMass + A->m_fMass); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * A->m_fMass; + impulseB = (speedSum - fOtherSpeedB) * B->m_fMass; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; +#ifdef FIX_BUGS + if(impulseB > impulseLimit) impulseB = impulseLimit; +#else + if(impulseA < -impulseLimit) impulseA = -impulseLimit; // duplicate +#endif + A->ApplyFrictionMoveForce(frictionDir*impulseA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + return true; + } + }else if(A->bPedPhysics){ + if(B->IsVehicle()) + return false; + CVector pointposB = colpoint.point - B->GetPosition(); + speedB = B->GetSpeed(pointposB); + + normalSpeedA = DotProduct(A->m_vecMoveSpeed, colpoint.normal); + normalSpeedB = DotProduct(speedB, colpoint.normal); + vOtherSpeedA = A->m_vecMoveSpeed - colpoint.normal*normalSpeedA; + vOtherSpeedB = speedB - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeedA; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); +#endif + float massB = B->GetMass(pointposB, frictionDir); + speedSum = (massB*fOtherSpeedB + A->m_fMass*fOtherSpeedA)/(massB + A->m_fMass); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * A->m_fMass; + impulseB = (speedSum - fOtherSpeedB) * massB; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; + A->ApplyFrictionMoveForce(frictionDir*impulseA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB); + return true; + } + }else if(B->bPedPhysics){ + if(A->IsVehicle()) + return false; + CVector pointposA = colpoint.point - A->GetPosition(); + speedA = A->GetSpeed(pointposA); + + normalSpeedA = DotProduct(speedA, colpoint.normal); + normalSpeedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + vOtherSpeedA = speedA - colpoint.normal*normalSpeedA; + vOtherSpeedB = B->m_vecMoveSpeed - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeedA; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); +#endif + float massA = A->GetMass(pointposA, frictionDir); + speedSum = (B->m_fMass*fOtherSpeedB + massA*fOtherSpeedA)/(B->m_fMass + massA); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * massA; + impulseB = (speedSum - fOtherSpeedB) * B->m_fMass; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; + A->ApplyFrictionMoveForce(frictionDir*impulseA); + A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + return true; + } + }else{ + CVector pointposA = colpoint.point - A->GetPosition(); + CVector pointposB = colpoint.point - B->GetPosition(); + speedA = A->GetSpeed(pointposA); + speedB = B->GetSpeed(pointposB); + + normalSpeedA = DotProduct(speedA, colpoint.normal); + normalSpeedB = DotProduct(speedB, colpoint.normal); + vOtherSpeedA = speedA - colpoint.normal*normalSpeedA; + vOtherSpeedB = speedB - colpoint.normal*normalSpeedB; + + fOtherSpeedA = vOtherSpeedA.Magnitude(); + fOtherSpeedB = vOtherSpeedB.Magnitude(); + +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeedA; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeedA * (1.0f/fOtherSpeedA); +#endif + float massA = A->GetMass(pointposA, frictionDir); + float massB = B->GetMass(pointposB, frictionDir); + speedSum = (massB*fOtherSpeedB + massA*fOtherSpeedA)/(massB + massA); + if(fOtherSpeedA > speedSum){ + impulseA = (speedSum - fOtherSpeedA) * massA; + impulseB = (speedSum - fOtherSpeedB) * massB; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); + if(impulseA < -impulseLimit) impulseA = -impulseLimit; + if(impulseB > impulseLimit) impulseB = impulseLimit; + A->ApplyFrictionMoveForce(frictionDir*impulseA); + A->ApplyFrictionTurnForce(frictionDir*impulseA, pointposA); + B->ApplyFrictionMoveForce(frictionDir*impulseB); + B->ApplyFrictionTurnForce(frictionDir*impulseB, pointposB); + return true; + } + } + return false; +} + +bool +CPhysical::ApplyFriction(float adhesiveLimit, CColPoint &colpoint) +{ + CVector speed; + float normalSpeed; + CVector vOtherSpeed; + float fOtherSpeed; + CVector frictionDir; + float fImpulse; + float impulseLimit; + + if(bPedPhysics){ + normalSpeed = DotProduct(m_vecMoveSpeed, colpoint.normal); + vOtherSpeed = m_vecMoveSpeed - colpoint.normal*normalSpeed; + + fOtherSpeed = vOtherSpeed.Magnitude(); + if(fOtherSpeed > 0.0f){ +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeed; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeed * (1.0f/fOtherSpeed); +#endif + // not really impulse but speed + // maybe use ApplyFrictionMoveForce instead? + fImpulse = -fOtherSpeed; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep() / m_fMass; + if(fImpulse < -impulseLimit) fImpulse = -impulseLimit; + CVector vImpulse = frictionDir*fImpulse; + m_vecMoveFriction += CVector(vImpulse.x, vImpulse.y, 0.0f); + return true; + } + }else{ + CVector pointpos = colpoint.point - GetPosition(); + speed = GetSpeed(pointpos); + normalSpeed = DotProduct(speed, colpoint.normal); + vOtherSpeed = speed - colpoint.normal*normalSpeed; + + fOtherSpeed = vOtherSpeed.Magnitude(); + if(fOtherSpeed > 0.0f){ +#ifdef FIX_BUGS // division by 0 + frictionDir = vOtherSpeed; + frictionDir.Normalise(); +#else + frictionDir = vOtherSpeed * (1.0f/fOtherSpeed); +#endif + fImpulse = -fOtherSpeed * m_fMass; + impulseLimit = adhesiveLimit*CTimer::GetTimeStep() * 1.5; + if(fImpulse < -impulseLimit) fImpulse = -impulseLimit; + ApplyFrictionMoveForce(frictionDir*fImpulse); + ApplyFrictionTurnForce(frictionDir*fImpulse, pointpos); + + if(fOtherSpeed > 0.1f && + colpoint.surfaceB != SURFACE_GRASS && colpoint.surfaceB != SURFACE_MUD_DRY && + CSurfaceTable::GetAdhesionGroup(colpoint.surfaceA) == ADHESIVE_HARD){ + CVector v = frictionDir * fOtherSpeed * 0.25f; + for(int i = 0; i < 4; i++) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, colpoint.point, v); + } + return true; + } + } + return false; +} + +bool +CPhysical::ProcessShiftSectorList(CPtrList *lists) +{ + int i, j; + CPtrList *list; + CPtrNode *node; + CPhysical *A, *B; + CObject *Bobj; + bool canshift; + CVUVECTOR center; + float radius; + + int numCollisions; + int mostColliding; + CColPoint colpoints[MAX_COLLISION_POINTS]; + CVector shift = CVector(0.0f, 0.0f, 0.0f); + bool doShift = false; + CEntity *boat = nil; + + bool skipShift; + + A = this; + + A->GetBoundCentre(center); + radius = A->GetBoundRadius(); + for(i = 0; i <= ENTITYLIST_PEDS_OVERLAP; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + B = (CPhysical*)node->item; + Bobj = (CObject*)B; + skipShift = false; + + if(B->IsBuilding() || + B->IsObject() && B->bInfiniteMass || + A->IsPed() && B->IsObject() && B->GetIsStatic() && !Bobj->bHasBeenDamaged) + canshift = true; + else + canshift = false; + + if(B == A || + B->m_scanCode == CWorld::GetCurrentScanCode() || + !B->bUsesCollision || + (A->bHasHitWall && !canshift) || + !B->GetIsTouching(center, radius)) + continue; + + // This could perhaps be done a bit nicer + + if(B->IsBuilding()) + skipShift = false; + else if(IsStreetLight(A->GetModelIndex()) && + (B->IsVehicle() || B->IsPed()) && + A->GetUp().z < 0.66f) + skipShift = true; + else if((A->IsVehicle() || A->IsPed()) && + B->GetUp().z < 0.66f && + IsStreetLight(B->GetModelIndex())) + skipShift = true; + else if(A->IsObject() && B->IsVehicle()){ + CObject *Aobj = (CObject*)A; + if(Aobj->ObjectCreatedBy != TEMP_OBJECT && + !Aobj->bHasBeenDamaged && + Aobj->GetIsStatic()){ + if(Aobj->m_pCollidingEntity == B) + Aobj->m_pCollidingEntity = nil; + }else if(Aobj->m_pCollidingEntity != B){ + CMatrix inv; + CVector size = CModelInfo::GetColModel(A->GetModelIndex())->boundingBox.GetSize(); + size = A->GetMatrix() * size; + if(size.z < B->GetPosition().z || + (Invert(B->GetMatrix(), inv) * size).z < 0.0f){ + skipShift = true; + Aobj->m_pCollidingEntity = B; + } + } else + skipShift = true; + }else if(B->IsObject() && A->IsVehicle()){ + CObject *Bobj = (CObject*)B; + if(Bobj->ObjectCreatedBy != TEMP_OBJECT && + !Bobj->bHasBeenDamaged && + Bobj->GetIsStatic()){ + if(Bobj->m_pCollidingEntity == A) + Bobj->m_pCollidingEntity = nil; + }else if(Bobj->m_pCollidingEntity != A){ + CMatrix inv; + CVector size = CModelInfo::GetColModel(B->GetModelIndex())->boundingBox.GetSize(); + size = B->GetMatrix() * size; + if(size.z < A->GetPosition().z || + (Invert(A->GetMatrix(), inv) * size).z < 0.0f) + skipShift = true; + } else + skipShift = true; + }else if(IsBodyPart(A->GetModelIndex()) && B->IsPed()) + skipShift = true; + else if(A->IsPed() && IsBodyPart(B->GetModelIndex())) + skipShift = true; + else if(A->IsPed() && ((CPed*)A)->m_pCollidingEntity == B || + B->IsPed() && ((CPed*)B)->m_pCollidingEntity == A) + skipShift = true; + else if(A->GetModelIndex() == MI_RCBANDIT && B->IsVehicle() || + B->GetModelIndex() == MI_RCBANDIT && (A->IsPed() || A->IsVehicle())) + skipShift = true; + + if(skipShift) + continue; + + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, colpoints); + if(numCollisions <= 0) + continue; + + mostColliding = 0; + for(j = 1; j < numCollisions; j++) + if (colpoints[j].GetDepth() > colpoints[mostColliding].GetDepth()) + mostColliding = j; + + if(CWorld::bSecondShift) + for(j = 0; j < numCollisions; j++) + shift += colpoints[j].GetNormal() * colpoints[j].GetDepth() * 1.5f / numCollisions; + else + for(j = 0; j < numCollisions; j++) + shift += colpoints[j].GetNormal() * colpoints[j].GetDepth() * 1.2f / numCollisions; + + if(A->IsVehicle() && B->IsVehicle()){ + CVector dir = A->GetPosition() - B->GetPosition(); + dir.Normalise(); + if(dir.z < 0.0f && dir.z < A->GetForward().z && dir.z < A->GetRight().z) + dir.z = Min(0.0f, Min(A->GetForward().z, A->GetRight().z)); + shift += dir * colpoints[mostColliding].GetDepth() * 0.5f; + }else if(A->IsPed() && B->IsVehicle() && ((CVehicle*)B)->IsBoat()){ + CVector dir = colpoints[mostColliding].GetNormal(); + float f = Min(Abs(dir.z), 0.9f); + dir.z = 0.0f; + dir.Normalise(); + shift += dir * colpoints[mostColliding].GetDepth() / (1.0f - f); + boat = B; + }else if(B->IsPed() && A->IsVehicle() && ((CVehicle*)A)->IsBoat()){ + CVector dir = colpoints[mostColliding].GetNormal() * -1.0f; + float f = Min(Abs(dir.z), 0.9f); + dir.z = 0.0f; + dir.Normalise(); + B->GetMatrix().Translate(dir * colpoints[mostColliding].GetDepth() / (1.0f - f)); + // BUG? how can that ever happen? A is a Ped + if(B->IsVehicle()) + B->ProcessEntityCollision(A, colpoints); + }else{ + if(CWorld::bSecondShift) + shift += colpoints[mostColliding].GetNormal() * colpoints[mostColliding].GetDepth() * 0.4f; + else + shift += colpoints[mostColliding].GetNormal() * colpoints[mostColliding].GetDepth() * 0.2f; + } + + doShift = true; + } + } + + if(!doShift) + return false; + GetMatrix().Translate(shift); + if(boat) + ProcessEntityCollision(boat, colpoints); + return true; +} + +bool +CPhysical::ProcessCollisionSectorList_SimpleCar(CPtrList *lists) +{ + static CColPoint aColPoints[MAX_COLLISION_POINTS]; + float radius; + CVUVECTOR center; + int listtype; + CPhysical *A, *B; + int numCollisions; + int i; + float impulseA = -1.0f; + float impulseB = -1.0f; + + A = (CPhysical*)this; + + radius = A->GetBoundRadius(); + A->GetBoundCentre(center); + + for(listtype = 3; listtype >= 0; listtype--){ + // Go through vehicles and objects + CPtrList *list; + switch(listtype){ + case 0: list = &lists[ENTITYLIST_VEHICLES]; break; + case 1: list = &lists[ENTITYLIST_VEHICLES_OVERLAP]; break; + case 2: list = &lists[ENTITYLIST_OBJECTS]; break; + case 3: list = &lists[ENTITYLIST_OBJECTS_OVERLAP]; break; + } + + // Find first collision in list + CPtrNode *listnode; + for(listnode = list->first; listnode; listnode = listnode->next){ + B = (CPhysical*)listnode->item; + if(B != A && + B->m_scanCode != CWorld::GetCurrentScanCode() && + B->bUsesCollision && + B->GetIsTouching(center, radius)){ + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, aColPoints); + if(numCollisions > 0) + goto collision; + } + } + } + // no collision + return false; + +collision: + + if(A->bHasContacted && B->bHasContacted){ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + } + }else if(A->bHasContacted){ + CVector savedMoveFriction = A->m_vecMoveFriction; + CVector savedTurnFriction = A->m_vecTurnFriction; + A->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + A->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + A->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!A->bHasContacted){ + A->bHasContacted = true; + A->m_vecMoveFriction = savedMoveFriction; + A->m_vecTurnFriction = savedTurnFriction; + } + }else if(B->bHasContacted){ + CVector savedMoveFriction = B->m_vecMoveFriction; + CVector savedTurnFriction = B->m_vecTurnFriction; + B->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + B->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + B->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!B->bHasContacted){ + B->bHasContacted = true; + B->m_vecMoveFriction = savedMoveFriction; + B->m_vecTurnFriction = savedTurnFriction; + } + }else{ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + } + + if(B->GetStatus() == STATUS_SIMPLE){ + B->SetStatus(STATUS_PHYSICS); + if(B->IsVehicle()) + CCarCtrl::SwitchVehicleToRealPhysics((CVehicle*)B); + } + + return true; +} + +bool +CPhysical::ProcessCollisionSectorList(CPtrList *lists) +{ + static CColPoint aColPoints[MAX_COLLISION_POINTS]; + float radius; + CVUVECTOR center; + CPtrList *list; + CPhysical *A, *B; + CObject *Aobj, *Bobj; + CPed *Aped, *Bped; + int numCollisions; + int numResponses; + int i, j; + bool skipCollision, altcollision; + float impulseA = -1.0f; + float impulseB = -1.0f; + + A = (CPhysical*)this; + Aobj = (CObject*)A; + Aped = (CPed*)A; + + radius = A->GetBoundRadius(); + A->GetBoundCentre(center); + + for(j = 0; j <= ENTITYLIST_PEDS_OVERLAP; j++){ + list = &lists[j]; + + CPtrNode *listnode; + for(listnode = list->first; listnode; listnode = listnode->next){ + B = (CPhysical*)listnode->item; + Bobj = (CObject*)B; + Bped = (CPed*)B; + + bool isTouching = true; + if(B == A || + B->m_scanCode == CWorld::GetCurrentScanCode() || + !B->bUsesCollision) + continue; + if(!B->GetIsTouching(center, radius)){ + if(A->IsObject() && Aobj->m_pCollidingEntity == B) + Aobj->m_pCollidingEntity = nil; + else if(B->IsObject() && Bobj->m_pCollidingEntity == A) + Bobj->m_pCollidingEntity = nil; + else if(A->IsPed() && Aped->m_pCollidingEntity == B) + Aped->m_pCollidingEntity = nil; + else if(B->IsPed() && Bped->m_pCollidingEntity == A) + Bped->m_pCollidingEntity = nil; + continue; + } + + A->bSkipLineCol = false; + skipCollision = false; + altcollision = false; + + if(B->IsBuilding()) + skipCollision = false; + else if(IsStreetLight(A->GetModelIndex()) && + (B->IsVehicle() || B->IsPed()) && + A->GetUp().z < 0.66f){ + skipCollision = true; + A->bSkipLineCol = true; + Aobj->m_pCollidingEntity = B; + }else if((A->IsVehicle() || A->IsPed()) && + B->GetUp().z < 0.66f && + IsStreetLight(B->GetModelIndex())){ + skipCollision = true; + A->bSkipLineCol = true; + Bobj->m_pCollidingEntity = A; + }else if(A->IsObject() && B->IsVehicle()){ + if(A->GetModelIndex() == MI_CAR_BUMPER || A->GetModelIndex() == MI_FILES) + skipCollision = true; + else if(Aobj->ObjectCreatedBy == TEMP_OBJECT || + Aobj->bHasBeenDamaged || + !Aobj->GetIsStatic()){ + if(Aobj->m_pCollidingEntity == B) + skipCollision = true; + else{ + CMatrix inv; + CVector size = CModelInfo::GetColModel(A->GetModelIndex())->boundingBox.GetSize(); + size = A->GetMatrix() * size; + if(size.z < B->GetPosition().z || + (Invert(B->GetMatrix(), inv) * size).z < 0.0f){ + skipCollision = true; + Aobj->m_pCollidingEntity = B; + } + } + } + }else if(B->IsObject() && A->IsVehicle()){ + if(B->GetModelIndex() == MI_CAR_BUMPER || B->GetModelIndex() == MI_FILES) + skipCollision = true; + else if(Bobj->ObjectCreatedBy == TEMP_OBJECT || + Bobj->bHasBeenDamaged || + !Bobj->GetIsStatic()){ + if(Bobj->m_pCollidingEntity == A) + skipCollision = true; + else{ + CMatrix inv; + CVector size = CModelInfo::GetColModel(B->GetModelIndex())->boundingBox.GetSize(); + size = B->GetMatrix() * size; + if(size.z < A->GetPosition().z || + (Invert(A->GetMatrix(), inv) * size).z < 0.0f){ + skipCollision = true; + } + } + } + }else if(IsBodyPart(A->GetModelIndex()) && B->IsPed()){ + skipCollision = true; + }else if(A->IsPed() && IsBodyPart(B->GetModelIndex())){ + skipCollision = true; + A->bSkipLineCol = true; + }else if(A->IsPed() && Aped->m_pCollidingEntity == B){ + skipCollision = true; + if(!Aped->bKnockedUpIntoAir) + A->bSkipLineCol = true; + }else if(B->IsPed() && Bped->m_pCollidingEntity == A){ + skipCollision = true; + A->bSkipLineCol = true; + }else if(A->GetModelIndex() == MI_RCBANDIT && (B->IsPed() || B->IsVehicle()) || + B->GetModelIndex() == MI_RCBANDIT && (A->IsPed() || A->IsVehicle())){ + skipCollision = true; + A->bSkipLineCol = true; + }else if(A->IsPed() && B->IsObject() && Bobj->m_fUprootLimit > 0.0f) + altcollision = true; + + + if(!A->bUsesCollision || skipCollision){ + B->m_scanCode = CWorld::GetCurrentScanCode(); + A->ProcessEntityCollision(B, aColPoints); + }else if(B->IsBuilding() || B->bIsStuck || B->bInfiniteMass || altcollision){ + + // This is the case where B doesn't move + + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, aColPoints); + if(numCollisions <= 0) + continue; + + CVector moveSpeed = CVector(0.0f, 0.0f, 0.0f); + CVector turnSpeed = CVector(0.0f, 0.0f, 0.0f); + numResponses = 0; + if(A->bHasContacted){ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollisionAlt(B, aColPoints[i], impulseA, moveSpeed, turnSpeed)) + continue; + + numResponses++; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + float imp = impulseA; + if(A->IsVehicle() && A->GetUp().z < -0.6f && + Abs(A->m_vecMoveSpeed.x) < 0.05f && + Abs(A->m_vecMoveSpeed.y) < 0.05f) + imp *= 0.1f; + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, imp, Max(turnSpeedDiff, moveSpeedDiff)); + } + }else{ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollisionAlt(B, aColPoints[i], impulseA, moveSpeed, turnSpeed)) + continue; + + numResponses++; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + float imp = impulseA; + if(A->IsVehicle() && A->GetUp().z < -0.6f && + Abs(A->m_vecMoveSpeed.x) < 0.05f && + Abs(A->m_vecMoveSpeed.y) < 0.05f) + imp *= 0.1f; + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, imp, Max(turnSpeedDiff, moveSpeedDiff)); + + float adhesion = CSurfaceTable::GetAdhesiveLimit(aColPoints[i]) / numCollisions; + + if(A->GetModelIndex() == MI_RCBANDIT) + adhesion *= 0.2f; + else if(IsBoatModel(A->GetModelIndex())){ + if(aColPoints[i].normal.z > 0.6f){ + if(CSurfaceTable::GetAdhesionGroup(aColPoints[i].surfaceB) == ADHESIVE_LOOSE) + adhesion *= 3.0f; + }else + adhesion = 0.0f; + }else if(A->IsVehicle()){ + if(A->GetStatus() == STATUS_WRECKED) + adhesion *= 3.0f; + else if(A->GetUp().z > 0.3f) + adhesion = 0.0f; + else + adhesion *= Min(5.0f, 0.03f*impulseA + 1.0f); + } + + if(A->ApplyFriction(adhesion, aColPoints[i])) + A->bHasContacted = true; + } + } + + if(numResponses){ + m_vecMoveSpeed += moveSpeed / numResponses; + m_vecTurnSpeed += turnSpeed / numResponses; + if(!CWorld::bNoMoreCollisionTorque && + A->GetStatus() == STATUS_PLAYER && A->IsVehicle() && + Abs(A->m_vecMoveSpeed.x) > 0.2f && + Abs(A->m_vecMoveSpeed.y) > 0.2f){ + A->m_vecMoveFriction.x += moveSpeed.x * -0.3f / numCollisions; + A->m_vecMoveFriction.y += moveSpeed.y * -0.3f / numCollisions; + A->m_vecTurnFriction += turnSpeed * -0.3f / numCollisions; + } + return true; + } + }else{ + + // B can move + + B->m_scanCode = CWorld::GetCurrentScanCode(); + numCollisions = A->ProcessEntityCollision(B, aColPoints); + if(numCollisions <= 0) + continue; + + float maxImpulseA = 0.0f; + float maxImpulseB = 0.0f; + if(A->bHasContacted && B->bHasContacted){ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + } + }else if(A->bHasContacted){ + CVector savedMoveFriction = A->m_vecMoveFriction; + CVector savedTurnFriction = A->m_vecTurnFriction; + A->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + A->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + A->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!A->bHasContacted){ + A->bHasContacted = true; + A->m_vecMoveFriction = savedMoveFriction; + A->m_vecTurnFriction = savedTurnFriction; + } + }else if(B->bHasContacted){ + CVector savedMoveFriction = B->m_vecMoveFriction; + CVector savedTurnFriction = B->m_vecTurnFriction; + B->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + B->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + B->bHasContacted = false; + + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + + if(!B->bHasContacted){ + B->bHasContacted = true; + B->m_vecMoveFriction = savedMoveFriction; + B->m_vecTurnFriction = savedTurnFriction; + } + }else{ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollision(B, aColPoints[i], impulseA, impulseB)) + continue; + + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + if(impulseB > maxImpulseB) maxImpulseB = impulseB; + + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(impulseB > B->m_fDamageImpulse) + B->SetDamagedPieceRecord(aColPoints[i].pieceB, impulseB, A, aColPoints[i].normal); + + float turnSpeedDiff = (B->m_vecTurnSpeed - A->m_vecTurnSpeed).MagnitudeSqr(); + float moveSpeedDiff = (B->m_vecMoveSpeed - A->m_vecMoveSpeed).MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + if(A->ApplyFriction(B, CSurfaceTable::GetAdhesiveLimit(aColPoints[i])/numCollisions, aColPoints[i])){ + A->bHasContacted = true; + B->bHasContacted = true; + } + } + } + + if(B->IsPed() && A->IsVehicle() && + (!Bped->IsPlayer() || B->bHasHitWall && A->m_vecMoveSpeed.MagnitudeSqr() > SQR(0.05f))) + Bped->KillPedWithCar((CVehicle*)A, maxImpulseB); + else if(B->GetModelIndex() == MI_TRAIN && A->IsPed() && + (!Aped->IsPlayer() || A->bHasHitWall)) + Aped->KillPedWithCar((CVehicle*)B, maxImpulseA*2.0f); + else if(B->IsObject() && B->bUsesCollision && A->IsVehicle()){ + // BUG? not impulseA? + if(Bobj->m_nCollisionDamageEffect && maxImpulseB > 20.0f) + Bobj->ObjectDamage(maxImpulseB); + }else if(A->IsObject() && A->bUsesCollision && B->IsVehicle()){ + if(Aobj->m_nCollisionDamageEffect && maxImpulseB > 20.0f) + Aobj->ObjectDamage(maxImpulseB); + } + + if(B->GetStatus() == STATUS_SIMPLE){ + B->SetStatus(STATUS_PHYSICS); + if(B->IsVehicle()) + CCarCtrl::SwitchVehicleToRealPhysics((CVehicle*)B); + } + + return true; + } + + } + } + + return false; +} + +bool +CPhysical::CheckCollision(void) +{ + CEntryInfoNode *node; + + bCollisionProcessed = false; + CWorld::AdvanceCurrentScanCode(); + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList(node->sector->m_lists)) + return true; + return false; +} + +bool +CPhysical::CheckCollision_SimpleCar(void) +{ + CEntryInfoNode *node; + + bCollisionProcessed = false; + CWorld::AdvanceCurrentScanCode(); + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList_SimpleCar(node->sector->m_lists)) + return true; + return false; +} + +void +CPhysical::ProcessShift(void) +{ + m_fDistanceTravelled = 0.0f; + if(GetStatus() == STATUS_SIMPLE){ + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + }else{ + CMatrix matrix(GetMatrix()); + ApplyMoveSpeed(); + ApplyTurnSpeed(); + GetMatrix().Reorthogonalise(); + + CWorld::AdvanceCurrentScanCode(); + + if(IsVehicle()) + m_bIsVehicleBeingShifted = true; + + CEntryInfoNode *node; + bool hasshifted = false; + for(node = m_entryInfoList.first; node; node = node->next) + hasshifted |= ProcessShiftSectorList(node->sector->m_lists); + m_bIsVehicleBeingShifted = false; + if(hasshifted){ + CWorld::AdvanceCurrentScanCode(); + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList(node->sector->m_lists)){ + GetMatrix() = matrix; + return; + } + } + bIsStuck = false; + bIsInSafePosition = true; + m_fDistanceTravelled = (GetPosition() - matrix.GetPosition()).Magnitude(); + RemoveAndAdd(); + } +} + +// x is the number of units (m) we would like to step +#define NUMSTEPS(x) Ceil(Sqrt(distSq) * (1.0f/(x))) + +void +CPhysical::ProcessCollision(void) +{ + int i; + CPed *ped = (CPed*)this; + + m_fDistanceTravelled = 0.0f; + m_bIsVehicleBeingShifted = false; + bSkipLineCol = false; + + if(!bUsesCollision){ + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + return; + } + + if(GetStatus() == STATUS_SIMPLE){ + if(CheckCollision_SimpleCar() && GetStatus() == STATUS_SIMPLE){ + SetStatus(STATUS_PHYSICS); + if(IsVehicle()) + CCarCtrl::SwitchVehicleToRealPhysics((CVehicle*)this); + } + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + return; + } + + // Save current state + CMatrix savedMatrix(GetMatrix()); + float savedTimeStep = CTimer::GetTimeStep(); + + int8 n = 1; // The number of steps we divide the time step into + float step = 0.0f; // divided time step + float distSq = m_vecMoveSpeed.MagnitudeSqr() * sq(CTimer::GetTimeStep()); + + if(IsPed() && (distSq >= sq(0.2f) || ped->IsPlayer())){ + if(ped->IsPlayer()) + n = Max(NUMSTEPS(0.2f), 2.0f); + else + n = NUMSTEPS(0.3f); + step = savedTimeStep / n; + }else if(IsVehicle() && distSq >= sq(0.4f)){ + if(GetStatus() == STATUS_PLAYER) + n = NUMSTEPS(0.2f); + else + n = distSq > 0.32f ? NUMSTEPS(0.3f) : NUMSTEPS(0.4f); + step = savedTimeStep / n; + }else if(IsObject()){ + int responsecase = ((CObject*)this)->m_nSpecialCollisionResponseCases; + if(responsecase == COLLRESPONSE_LAMPOST){ + CVector speedUp = CVector(0.0f, 0.0f, 0.0f); + CVector speedDown = CVector(0.0f, 0.0f, 0.0f); + speedUp.z = GetBoundRadius(); + speedDown.z = -speedUp.z; + speedUp = Multiply3x3(GetMatrix(), speedUp); + speedDown = Multiply3x3(GetMatrix(), speedDown); + speedUp = GetSpeed(speedUp); + speedDown = GetSpeed(speedDown); + distSq = Max(speedUp.MagnitudeSqr(), speedDown.MagnitudeSqr()) * sq(CTimer::GetTimeStep()); + if(distSq >= sq(0.3f)){ + n = NUMSTEPS(0.3f); + step = savedTimeStep / n; + } + }else if(responsecase == COLLRESPONSE_UNKNOWN5){ + if(distSq >= 0.009f){ + n = NUMSTEPS(0.09f); + step = savedTimeStep / n; + } + }else if(responsecase == COLLRESPONSE_SMALLBOX || responsecase == COLLRESPONSE_FENCEPART){ + if(distSq >= sq(0.15f)){ + n = NUMSTEPS(0.15f); + step = savedTimeStep / n; + } + }else{ + if(distSq >= sq(0.3f)){ + n = NUMSTEPS(0.3f); + step = savedTimeStep / n; + } + } + } + + for(i = 1; i < n; i++){ + CTimer::SetTimeStep(i * step); + ApplyMoveSpeed(); + ApplyTurnSpeed(); + // TODO: get rid of copy paste? + if(CheckCollision()){ + if(IsPed() && m_vecMoveSpeed.z == 0.0f && + !ped->bWasStanding && + ped->bIsStanding) + savedMatrix.GetPosition().z = GetPosition().z; + GetMatrix() = savedMatrix; + CTimer::SetTimeStep(savedTimeStep); + return; + } + if(IsPed() && m_vecMoveSpeed.z == 0.0f && + !ped->bWasStanding && + ped->bIsStanding) + savedMatrix.GetPosition().z = GetPosition().z; + GetMatrix() = savedMatrix; + CTimer::SetTimeStep(savedTimeStep); + if(IsVehicle()){ + CVehicle *veh = (CVehicle*)this; + if(veh->m_vehType == VEHICLE_TYPE_CAR){ + CAutomobile *car = (CAutomobile*)this; + car->m_aSuspensionSpringRatio[0] = 1.0f; + car->m_aSuspensionSpringRatio[1] = 1.0f; + car->m_aSuspensionSpringRatio[2] = 1.0f; + car->m_aSuspensionSpringRatio[3] = 1.0f; + }else if(veh->m_vehType == VEHICLE_TYPE_BIKE){ + CBike* bike = (CBike*)this; + bike->m_aSuspensionSpringRatio[0] = 1.0f; + bike->m_aSuspensionSpringRatio[1] = 1.0f; + bike->m_aSuspensionSpringRatio[2] = 1.0f; + bike->m_aSuspensionSpringRatio[3] = 1.0f; + } + } + } + + ApplyMoveSpeed(); + ApplyTurnSpeed(); + GetMatrix().Reorthogonalise(); + m_bIsVehicleBeingShifted = false; + bSkipLineCol = false; + if(!m_vecMoveSpeed.IsZero() || + !m_vecTurnSpeed.IsZero() || +#ifdef GTA_TRAIN + bHitByTrain || +#endif + GetStatus() == STATUS_PLAYER || + IsPed() && ped->IsPlayer()){ + if(IsVehicle()) + ((CVehicle*)this)->bVehicleColProcessed = true; + if(CheckCollision()){ + GetMatrix() = savedMatrix; + return; + } + } + bHitByTrain = false; + m_fDistanceTravelled = (GetPosition() - savedMatrix.GetPosition()).Magnitude(); + bSkipLineCol = false; + + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); +} diff --git a/src/entities/Physical.h b/src/entities/Physical.h new file mode 100644 index 0000000..a16bb21 --- /dev/null +++ b/src/entities/Physical.h @@ -0,0 +1,161 @@ +#pragma once + +#include "Lists.h" +#include "Timer.h" +#include "Entity.h" + +enum { + PHYSICAL_MAX_COLLISIONRECORDS = 6 +}; + +#define GRAVITY (0.008f) + +class CTreadable; + +class CPhysical : public CEntity +{ +public: + int32 m_audioEntityId; + float m_phys_unused1; + CTreadable *m_treadable[2]; // car and ped + uint32 m_nLastTimeCollided; + CVector m_vecMoveSpeed; // velocity + CVector m_vecTurnSpeed; // angular velocity + CVector m_vecMoveFriction; + CVector m_vecTurnFriction; + CVector m_vecMoveSpeedAvg; + CVector m_vecTurnSpeedAvg; + float m_fMass; + float m_fTurnMass; // moment of inertia + float m_fForceMultiplier; + float m_fAirResistance; + float m_fElasticity; + float m_fBuoyancy; + CVector m_vecCentreOfMass; + CEntryInfoList m_entryInfoList; + CPtrNode *m_movingListNode; + + int8 m_phys_unused2; + uint8 m_nStaticFrames; + uint8 m_nCollisionRecords; + bool m_bIsVehicleBeingShifted; + CEntity *m_aCollisionRecords[PHYSICAL_MAX_COLLISIONRECORDS]; + + float m_fDistanceTravelled; + + // damaged piece + float m_fDamageImpulse; + CEntity *m_pDamageEntity; + CVector m_vecDamageNormal; + int16 m_nDamagePieceType; + + uint8 bIsHeavy : 1; + uint8 bAffectedByGravity : 1; + uint8 bInfiniteMass : 1; + uint8 bIsInWater : 1; + uint8 m_phy_flagA10 : 1; // unused + uint8 m_phy_flagA20 : 1; // unused + uint8 bHitByTrain : 1; + uint8 bSkipLineCol : 1; + + uint8 m_nSurfaceTouched; + int8 m_nZoneLevel; + + CPhysical(void); + ~CPhysical(void); + + // from CEntity + void Add(void); + void Remove(void); + CRect GetBoundRect(void); + void ProcessControl(void); + void ProcessShift(void); + void ProcessCollision(void); + + virtual int32 ProcessEntityCollision(CEntity *ent, CColPoint *colpoints); + + void RemoveAndAdd(void); + void AddToMovingList(void); + void RemoveFromMovingList(void); + void SetDamagedPieceRecord(uint16 piece, float impulse, CEntity *entity, CVector dir); + void AddCollisionRecord(CEntity *ent); + void AddCollisionRecord_Treadable(CEntity *ent); + bool GetHasCollidedWith(CEntity *ent); + void RemoveRefsToEntity(CEntity *ent); + static void PlacePhysicalRelativeToOtherPhysical(CPhysical *other, CPhysical *phys, CVector localPos); + + // get speed of point p relative to entity center + CVector GetSpeed(const CVector &r); + CVector GetSpeed(void) { return GetSpeed(CVector(0.0f, 0.0f, 0.0f)); } + float GetMass(const CVector &pos, const CVector &dir) { + return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/m_fTurnMass + + 1.0f/m_fMass); + } + float GetMassTweak(const CVector &pos, const CVector &dir, float t) { + return 1.0f / (CrossProduct(pos, dir).MagnitudeSqr()/(m_fTurnMass*t) + + 1.0f/(m_fMass*t)); + } + void UnsetIsInSafePosition(void) { + m_vecMoveSpeed *= -1.0f; + m_vecTurnSpeed *= -1.0f; + ApplyTurnSpeed(); + ApplyMoveSpeed(); + m_vecMoveSpeed *= -1.0f; + m_vecTurnSpeed *= -1.0f; + bIsInSafePosition = false; + } + + const CVector &GetMoveSpeed() { return m_vecMoveSpeed; } + void SetMoveSpeed(float x, float y, float z) { + m_vecMoveSpeed.x = x; + m_vecMoveSpeed.y = y; + m_vecMoveSpeed.z = z; + } + void SetMoveSpeed(const CVector& speed) { + m_vecMoveSpeed = speed; + } + const CVector &GetTurnSpeed() { return m_vecTurnSpeed; } + void SetTurnSpeed(float x, float y, float z) { + m_vecTurnSpeed.x = x; + m_vecTurnSpeed.y = y; + m_vecTurnSpeed.z = z; + } + const CVector &GetCenterOfMass() { return m_vecCentreOfMass; } + void SetCenterOfMass(float x, float y, float z) { + m_vecCentreOfMass.x = x; + m_vecCentreOfMass.y = y; + m_vecCentreOfMass.z = z; + } + + void ApplyMoveSpeed(void); + void ApplyTurnSpeed(void); + // Force actually means Impulse here + void ApplyMoveForce(float jx, float jy, float jz); + void ApplyMoveForce(const CVector &j) { ApplyMoveForce(j.x, j.y, j.z); } + // j(x,y,z) is direction of force, p(x,y,z) is point relative to model center where force is applied + void ApplyTurnForce(float jx, float jy, float jz, float px, float py, float pz); + // j is direction of force, p is point relative to model center where force is applied + void ApplyTurnForce(const CVector &j, const CVector &p) { ApplyTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); } + void ApplyFrictionMoveForce(float jx, float jy, float jz); + void ApplyFrictionMoveForce(const CVector &j) { ApplyFrictionMoveForce(j.x, j.y, j.z); } + void ApplyFrictionTurnForce(float jx, float jy, float jz, float rx, float ry, float rz); + void ApplyFrictionTurnForce(const CVector &j, const CVector &p) { ApplyFrictionTurnForce(j.x, j.y, j.z, p.x, p.y, p.z); } + // springRatio: 1.0 fully extended, 0.0 fully compressed + bool ApplySpringCollision(float springConst, CVector &springDir, CVector &point, float springRatio, float bias); + bool ApplySpringDampening(float damping, CVector &springDir, CVector &point, CVector &speed); + void ApplyGravity(void); + void ApplyFriction(void); + void ApplyAirResistance(void); + bool ApplyCollision(CPhysical *B, CColPoint &colpoint, float &impulseA, float &impulseB); + bool ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed); + bool ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint); + bool ApplyFriction(float adhesiveLimit, CColPoint &colpoint); + + bool ProcessShiftSectorList(CPtrList *ptrlists); + bool ProcessCollisionSectorList_SimpleCar(CPtrList *lists); + bool ProcessCollisionSectorList(CPtrList *lists); + bool CheckCollision(void); + bool CheckCollision_SimpleCar(void); +}; + +VALIDATE_SIZE(CPhysical, 0x128); diff --git a/src/extras/GitSHA1.cpp.in b/src/extras/GitSHA1.cpp.in new file mode 100644 index 0000000..6168dc7 --- /dev/null +++ b/src/extras/GitSHA1.cpp.in @@ -0,0 +1,2 @@ +#define GIT_SHA1 "@GIT_SHA1@" +const char* g_GIT_SHA1 = GIT_SHA1; diff --git a/src/extras/GitSHA1.h b/src/extras/GitSHA1.h new file mode 100644 index 0000000..359bfaf --- /dev/null +++ b/src/extras/GitSHA1.h @@ -0,0 +1 @@ +extern const char* g_GIT_SHA1; \ No newline at end of file diff --git a/src/extras/arrow.inc b/src/extras/arrow.inc new file mode 100644 index 0000000..8ea7828 --- /dev/null +++ b/src/extras/arrow.inc @@ -0,0 +1,16 @@ +255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 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, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 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, 255, 255, 255, 255, 255, 255, 255, 255, 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, diff --git a/src/extras/cursor.inc b/src/extras/cursor.inc new file mode 100644 index 0000000..e8afd39 --- /dev/null +++ b/src/extras/cursor.inc @@ -0,0 +1,16 @@ +255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, +255, 255, 255, 255, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 255, 255, 255, 255, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, 119, 119, 119, 0, diff --git a/src/extras/custompipes.cpp b/src/extras/custompipes.cpp new file mode 100644 index 0000000..092b3e2 --- /dev/null +++ b/src/extras/custompipes.cpp @@ -0,0 +1,543 @@ +#define WITHD3D +#include "common.h" + +#ifdef EXTENDED_PIPELINES + +#include "main.h" +#include "RwHelper.h" +#include "Lights.h" +#include "Timecycle.h" +#include "FileMgr.h" +#include "Clock.h" +#include "Weather.h" +#include "TxdStore.h" +#include "Renderer.h" +#include "World.h" +#include "custompipes.h" + +#ifndef LIBRW +#error "Need librw for EXTENDED_PIPELINES" +#endif + +namespace CustomPipes { + +rw::int32 CustomMatOffset; + +void* +CustomMatCtor(void *object, int32, int32) +{ + CustomMatExt *ext = GetCustomMatExt((rw::Material*)object); + ext->glossTex = nil; + ext->haveGloss = false; + return object; +} + +void* +CustomMatCopy(void *dst, void *src, int32, int32) +{ + CustomMatExt *srcext = GetCustomMatExt((rw::Material*)src); + CustomMatExt *dstext = GetCustomMatExt((rw::Material*)dst); + dstext->glossTex = srcext->glossTex; + dstext->haveGloss = srcext->haveGloss; + return dst; +} + + + +rw::TexDictionary *neoTxd; + +bool bRenderingEnvMap; +int32 EnvMapSize = 128; +rw::Camera *EnvMapCam; +rw::Texture *EnvMapTex; +rw::Texture *EnvMaskTex; +static rw::RWDEVICE::Im2DVertex EnvScreenQuad[4]; +static int16 QuadIndices[6] = { 0, 1, 2, 0, 2, 3 }; + +static rw::Camera* +CreateEnvMapCam(rw::World *world) +{ + rw::Raster *fbuf = rw::Raster::create(EnvMapSize, EnvMapSize, 0, rw::Raster::CAMERATEXTURE); + if(fbuf){ + rw::Raster *zbuf = rw::Raster::create(EnvMapSize, EnvMapSize, 0, rw::Raster::ZBUFFER); + if(zbuf){ + rw::Frame *frame = rw::Frame::create(); + if(frame){ + rw::Camera *cam = rw::Camera::create(); + if(cam){ + cam->frameBuffer = fbuf; + cam->zBuffer = zbuf; + cam->setFrame(frame); + cam->setNearPlane(0.1f); + cam->setFarPlane(250.0f); + rw::V2d vw = { 2.0f, 2.0f }; + cam->setViewWindow(&vw); + world->addCamera(cam); + EnvMapTex = rw::Texture::create(fbuf); + EnvMapTex->setFilter(rw::Texture::LINEAR); + + frame->matrix.right.x = -1.0f; + frame->matrix.up.y = -1.0f; + frame->matrix.update(); + return cam; + } + frame->destroy(); + } + zbuf->destroy(); + } + fbuf->destroy(); + } + return nil; +} + +static void +DestroyCam(rw::Camera *cam) +{ + if(cam == nil) + return; + if(cam->frameBuffer){ + cam->frameBuffer->destroy(); + cam->frameBuffer = nil; + } + if(cam->zBuffer){ + cam->zBuffer->destroy(); + cam->zBuffer = nil; + } + rw::Frame *f = cam->getFrame(); + if(f){ + cam->setFrame(nil); + f->destroy(); + } + cam->world->removeCamera(cam); + cam->destroy(); +} + +void +RenderEnvMapScene(void) +{ + CRenderer::RenderRoads(); + CRenderer::RenderEverythingBarRoads(); + CRenderer::RenderFadingInEntities(); +} + +void +EnvMapRender(void) +{ + if(VehiclePipeSwitch != VEHICLEPIPE_NEO) + return; + + RwCameraEndUpdate(Scene.camera); + + // Neo does this differently, but i'm not quite convinced it's much better + rw::V3d camPos = FindPlayerCoors(); + EnvMapCam->getFrame()->matrix.pos = camPos; + EnvMapCam->getFrame()->transform(&EnvMapCam->getFrame()->matrix, rw::COMBINEREPLACE); + + rw::RGBA skycol; + skycol.red = CTimeCycle::GetSkyBottomRed(); + skycol.green = CTimeCycle::GetSkyBottomGreen(); + skycol.blue = CTimeCycle::GetSkyBottomBlue(); + skycol.alpha = 255; + EnvMapCam->clear(&skycol, rwCAMERACLEARZ|rwCAMERACLEARIMAGE); + RwCameraBeginUpdate(EnvMapCam); + bRenderingEnvMap = true; + RenderEnvMapScene(); + bRenderingEnvMap = false; + + if(EnvMaskTex){ + rw::SetRenderState(rw::VERTEXALPHA, TRUE); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDZERO); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDSRCCOLOR); + rw::SetRenderStatePtr(rw::TEXTURERASTER, EnvMaskTex->raster); + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, EnvScreenQuad, 4, QuadIndices, 6); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDSRCALPHA); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDINVSRCALPHA); + } + RwCameraEndUpdate(EnvMapCam); + + + RwCameraBeginUpdate(Scene.camera); + + // debug env map +// rw::SetRenderStatePtr(rw::TEXTURERASTER, EnvMapTex->raster); +// rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, EnvScreenQuad, 4, QuadIndices, 6); +} + +static void +EnvMapInit(void) +{ + if(neoTxd) + EnvMaskTex = neoTxd->find("CarReflectionMask"); + + EnvMapCam = CreateEnvMapCam(Scene.world); + + int width = EnvMapCam->frameBuffer->width; + int height = EnvMapCam->frameBuffer->height; + float screenZ = RwIm2DGetNearScreenZ(); + float recipZ = 1.0f/EnvMapCam->nearPlane; + + EnvScreenQuad[0].setScreenX(0.0f); + EnvScreenQuad[0].setScreenY(0.0f); + EnvScreenQuad[0].setScreenZ(screenZ); + EnvScreenQuad[0].setCameraZ(EnvMapCam->nearPlane); + EnvScreenQuad[0].setRecipCameraZ(recipZ); + EnvScreenQuad[0].setColor(255, 255, 255, 255); + EnvScreenQuad[0].setU(0.0f, recipZ); + EnvScreenQuad[0].setV(0.0f, recipZ); + + EnvScreenQuad[1].setScreenX(0.0f); + EnvScreenQuad[1].setScreenY(height); + EnvScreenQuad[1].setScreenZ(screenZ); + EnvScreenQuad[1].setCameraZ(EnvMapCam->nearPlane); + EnvScreenQuad[1].setRecipCameraZ(recipZ); + EnvScreenQuad[1].setColor(255, 255, 255, 255); + EnvScreenQuad[1].setU(0.0f, recipZ); + EnvScreenQuad[1].setV(1.0f, recipZ); + + EnvScreenQuad[2].setScreenX(width); + EnvScreenQuad[2].setScreenY(height); + EnvScreenQuad[2].setScreenZ(screenZ); + EnvScreenQuad[2].setCameraZ(EnvMapCam->nearPlane); + EnvScreenQuad[2].setRecipCameraZ(recipZ); + EnvScreenQuad[2].setColor(255, 255, 255, 255); + EnvScreenQuad[2].setU(1.0f, recipZ); + EnvScreenQuad[2].setV(1.0f, recipZ); + + EnvScreenQuad[3].setScreenX(width); + EnvScreenQuad[3].setScreenY(0.0f); + EnvScreenQuad[3].setScreenZ(screenZ); + EnvScreenQuad[3].setCameraZ(EnvMapCam->nearPlane); + EnvScreenQuad[3].setRecipCameraZ(recipZ); + EnvScreenQuad[3].setColor(255, 255, 255, 255); + EnvScreenQuad[3].setU(1.0f, recipZ); + EnvScreenQuad[3].setV(0.0f, recipZ); +} + +static void +EnvMapShutdown(void) +{ + EnvMapTex->raster = nil; + EnvMapTex->destroy(); + EnvMapTex = nil; + DestroyCam(EnvMapCam); + EnvMapCam = nil; +} + +/* + * Tweak values + */ + +#define INTERP_SETUP \ + int h1 = CClock::GetHours(); \ + int h2 = (h1+1)%24; \ + int w1 = CWeather::OldWeatherType; \ + int w2 = CWeather::NewWeatherType; \ + float timeInterp = (CClock::GetSeconds()/60.0f + CClock::GetMinutes())/60.0f; \ + float c0 = (1.0f-timeInterp)*(1.0f-CWeather::InterpolationValue); \ + float c1 = timeInterp*(1.0f-CWeather::InterpolationValue); \ + float c2 = (1.0f-timeInterp)*CWeather::InterpolationValue; \ + float c3 = timeInterp*CWeather::InterpolationValue; +#define INTERP(v) v[h1][w1]*c0 + v[h2][w1]*c1 + v[h1][w2]*c2 + v[h2][w2]*c3; +#define INTERPF(v,f) v[h1][w1].f*c0 + v[h2][w1].f*c1 + v[h1][w2].f*c2 + v[h2][w2].f*c3; + +InterpolatedFloat::InterpolatedFloat(float init) +{ + curInterpolator = 61; // compared against second + for(int h = 0; h < 24; h++) + for(int w = 0; w < NUMWEATHERS; w++) + data[h][w] = init; +} + +void +InterpolatedFloat::Read(char *s, int line, int field) +{ + sscanf(s, "%f", &data[line][field]); +} + +float +InterpolatedFloat::Get(void) +{ + if(curInterpolator != CClock::GetSeconds()){ + INTERP_SETUP + curVal = INTERP(data); + curInterpolator = CClock::GetSeconds(); + } + return curVal; +} + +InterpolatedColor::InterpolatedColor(const Color &init) +{ + curInterpolator = 61; // compared against second + for(int h = 0; h < 24; h++) + for(int w = 0; w < NUMWEATHERS; w++) + data[h][w] = init; +} + +void +InterpolatedColor::Read(char *s, int line, int field) +{ + int r, g, b, a; + sscanf(s, "%i, %i, %i, %i", &r, &g, &b, &a); + data[line][field] = Color(r/255.0f, g/255.0f, b/255.0f, a/255.0f); +} + +Color +InterpolatedColor::Get(void) +{ + if(curInterpolator != CClock::GetSeconds()){ + INTERP_SETUP + curVal.r = INTERPF(data, r); + curVal.g = INTERPF(data, g); + curVal.b = INTERPF(data, b); + curVal.a = INTERPF(data, a); + curInterpolator = CClock::GetSeconds(); + } + return curVal; +} + +void +InterpolatedLight::Read(char *s, int line, int field) +{ + int r, g, b, a; + sscanf(s, "%i, %i, %i, %i", &r, &g, &b, &a); + data[line][field] = Color(r/255.0f, g/255.0f, b/255.0f, a/100.0f); +} + +char* +ReadTweakValueTable(char *fp, InterpolatedValue &interp) +{ + char buf[24], *p; + int c; + int line, field; + + line = 0; + c = *fp++; + while(c != '\0' && line < 24){ + field = 0; + if(c != '\0' && c != '#'){ + while(c != '\0' && c != '\n' && field < NUMWEATHERS){ + p = buf; + while(c != '\0' && c == '\t') + c = *fp++; + *p++ = c; + while(c = *fp++, c != '\0' && c != '\t' && c != '\n') + *p++ = c; + *p++ = '\0'; + interp.Read(buf, line, field); + field++; + } + line++; + } + while(c != '\0' && c != '\n') + c = *fp++; + c = *fp++; + } + return fp-1; +} + + + +/* + * Neo Vehicle pipe + */ + +int32 VehiclePipeSwitch = VEHICLEPIPE_MATFX; +float VehicleShininess = 0.7f; // the default is a bit extreme +float VehicleSpecularity = 1.0f; +InterpolatedFloat Fresnel(0.4f); +InterpolatedFloat Power(18.0f); +InterpolatedLight DiffColor(Color(0.0f, 0.0f, 0.0f, 0.0f)); +InterpolatedLight SpecColor(Color(0.7f, 0.7f, 0.7f, 1.0f)); +rw::ObjPipeline *vehiclePipe; + +void +AttachVehiclePipe(rw::Atomic *atomic) +{ + atomic->pipeline = vehiclePipe; +} + +void +AttachVehiclePipe(rw::Clump *clump) +{ + FORLIST(lnk, clump->atomics) + AttachVehiclePipe(rw::Atomic::fromClump(lnk)); +} + + + +/* + * Neo World pipe + */ + +bool LightmapEnable; +float LightmapMult = 1.0f; +InterpolatedFloat WorldLightmapBlend(1.0f); +rw::ObjPipeline *worldPipe; + +void +AttachWorldPipe(rw::Atomic *atomic) +{ + atomic->pipeline = worldPipe; +} + +void +AttachWorldPipe(rw::Clump *clump) +{ + FORLIST(lnk, clump->atomics) + AttachWorldPipe(rw::Atomic::fromClump(lnk)); +} + + + + +/* + * Neo Gloss pipe + */ + +bool GlossEnable; +float GlossMult = 1.0f; +rw::ObjPipeline *glossPipe; + +rw::Texture* +GetGlossTex(rw::Material *mat) +{ + if(neoTxd == nil) + return nil; + CustomMatExt *ext = GetCustomMatExt(mat); + if(!ext->haveGloss){ + char glossname[128]; + strcpy(glossname, mat->texture->name); + strcat(glossname, "_gloss"); + ext->glossTex = neoTxd->find(glossname); + ext->haveGloss = true; + } + return ext->glossTex; +} + +void +AttachGlossPipe(rw::Atomic *atomic) +{ + atomic->pipeline = glossPipe; +} + +void +AttachGlossPipe(rw::Clump *clump) +{ + FORLIST(lnk, clump->atomics) + AttachWorldPipe(rw::Atomic::fromClump(lnk)); +} + + + +/* + * Neo Rim pipes + */ + +bool RimlightEnable; +float RimlightMult = 1.0f; +InterpolatedColor RampStart(Color(0.0f, 0.0f, 0.0f, 1.0f)); +InterpolatedColor RampEnd(Color(1.0f, 1.0f, 1.0f, 1.0f)); +InterpolatedFloat Offset(0.5f); +InterpolatedFloat Scale(1.5f); +InterpolatedFloat Scaling(2.0f); +rw::ObjPipeline *rimPipe; +rw::ObjPipeline *rimSkinPipe; + +void +AttachRimPipe(rw::Atomic *atomic) +{ + if(rw::Skin::get(atomic->geometry)) + atomic->pipeline = rimSkinPipe; + else + atomic->pipeline = rimPipe; +} + +void +AttachRimPipe(rw::Clump *clump) +{ + FORLIST(lnk, clump->atomics) + AttachRimPipe(rw::Atomic::fromClump(lnk)); +} + +/* + * High level stuff + */ + +void +CustomPipeInit(void) +{ + RwStream *stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "neo/neo.txd"); + if(stream == nil) + printf("Error: couldn't open 'neo/neo.txd'\n"); + else{ + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)) + neoTxd = RwTexDictionaryGtaStreamRead(stream); + RwStreamClose(stream, nil); + } + + EnvMapInit(); + + CreateVehiclePipe(); + CreateWorldPipe(); + CreateGlossPipe(); + CreateRimLightPipes(); +} + +void +CustomPipeShutdown(void) +{ + DestroyVehiclePipe(); + DestroyWorldPipe(); + DestroyGlossPipe(); + DestroyRimLightPipes(); + + EnvMapShutdown(); + + if(neoTxd){ + neoTxd->destroy(); + neoTxd = nil; + } +} + +void +CustomPipeRegister(void) +{ +#ifdef RW_OPENGL + CustomPipeRegisterGL(); +#endif + + CustomMatOffset = rw::Material::registerPlugin(sizeof(CustomMatExt), MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x80), + CustomMatCtor, nil, CustomMatCopy); +} + + +// Load textures from generic as fallback + +rw::TexDictionary *genericTxd; +rw::Texture *(*defaultFindCB)(const char *name); + +static rw::Texture* +customFindCB(const char *name) +{ + rw::Texture *res = defaultFindCB(name); + if(res == nil) + res = genericTxd->find(name); + return res; +} + +void +SetTxdFindCallback(void) +{ + int slot = CTxdStore::FindTxdSlot("generic"); + CTxdStore::AddRef(slot); + // TODO: function for this + genericTxd = CTxdStore::GetSlot(slot)->texDict; + assert(genericTxd); + if(defaultFindCB == nil) + defaultFindCB = rw::Texture::findCB; + rw::Texture::findCB = customFindCB; +} + +} + +#endif diff --git a/src/extras/custompipes.h b/src/extras/custompipes.h new file mode 100644 index 0000000..7ad239f --- /dev/null +++ b/src/extras/custompipes.h @@ -0,0 +1,145 @@ +#pragma once + +#ifdef LIBRW +#ifdef EXTENDED_PIPELINES + +namespace CustomPipes { + + +extern rw::TexDictionary *neoTxd; + +struct CustomMatExt +{ + rw::Texture *glossTex; + bool haveGloss; +}; +extern rw::int32 CustomMatOffset; +inline CustomMatExt *GetCustomMatExt(rw::Material *mat) { + return PLUGINOFFSET(CustomMatExt, mat, CustomMatOffset); +} + + +struct Color +{ + float r, g, b, a; + Color(void) {} + Color(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) {} +}; + +class InterpolatedValue +{ +public: + virtual void Read(char *s, int line, int field) = 0; +}; + +class InterpolatedFloat : public InterpolatedValue +{ +public: + float data[24][NUMWEATHERS]; + float curInterpolator; + float curVal; + + InterpolatedFloat(float init); + void Read(char *s, int line, int field); + float Get(void); +}; + +class InterpolatedColor : public InterpolatedValue +{ +public: + Color data[24][NUMWEATHERS]; + float curInterpolator; + Color curVal; + + InterpolatedColor(const Color &init); + void Read(char *s, int line, int field); + Color Get(void); +}; + +class InterpolatedLight : public InterpolatedColor +{ +public: + InterpolatedLight(const Color &init) : InterpolatedColor(init) {} + void Read(char *s, int line, int field); +}; + +char *ReadTweakValueTable(char *fp, InterpolatedValue &interp); + + + + + +void CustomPipeRegister(void); +void CustomPipeRegisterGL(void); +void CustomPipeInit(void); +void CustomPipeShutdown(void); +void SetTxdFindCallback(void); + +extern bool bRenderingEnvMap; +extern int32 EnvMapSize; +extern rw::Camera *EnvMapCam; +extern rw::Texture *EnvMapTex; +extern rw::Texture *EnvMaskTex; +void EnvMapRender(void); + +enum { + VEHICLEPIPE_MATFX, + VEHICLEPIPE_NEO +}; +extern int32 VehiclePipeSwitch; +extern float VehicleShininess; +extern float VehicleSpecularity; +extern InterpolatedFloat Fresnel; +extern InterpolatedFloat Power; +extern InterpolatedLight DiffColor; +extern InterpolatedLight SpecColor; +extern rw::ObjPipeline *vehiclePipe; +void CreateVehiclePipe(void); +void DestroyVehiclePipe(void); +void AttachVehiclePipe(rw::Atomic *atomic); +void AttachVehiclePipe(rw::Clump *clump); + +extern bool LightmapEnable; +extern float LightmapMult; +extern InterpolatedFloat WorldLightmapBlend; +extern rw::ObjPipeline *worldPipe; +void CreateWorldPipe(void); +void DestroyWorldPipe(void); +void AttachWorldPipe(rw::Atomic *atomic); +void AttachWorldPipe(rw::Clump *clump); + +extern bool GlossEnable; +extern float GlossMult; +extern rw::ObjPipeline *glossPipe; +void CreateGlossPipe(void); +void DestroyGlossPipe(void); +void AttachGlossPipe(rw::Atomic *atomic); +void AttachGlossPipe(rw::Clump *clump); +rw::Texture *GetGlossTex(rw::Material *mat); + +extern bool RimlightEnable; +extern float RimlightMult; +extern InterpolatedColor RampStart; +extern InterpolatedColor RampEnd; +extern InterpolatedFloat Offset; +extern InterpolatedFloat Scale; +extern InterpolatedFloat Scaling; +extern rw::ObjPipeline *rimPipe; +extern rw::ObjPipeline *rimSkinPipe; +void CreateRimLightPipes(void); +void DestroyRimLightPipes(void); +void AttachRimPipe(rw::Atomic *atomic); +void AttachRimPipe(rw::Clump *clump); + +} + +#endif + +namespace WorldRender{ +extern int numBlendInsts[3]; +void AtomicFirstPass(RpAtomic *atomic, int pass); +void AtomicFullyTransparent(RpAtomic *atomic, int pass, int fadeAlpha); +void RenderBlendPass(int pass); +} + +#endif diff --git a/src/extras/custompipes_d3d9.cpp b/src/extras/custompipes_d3d9.cpp new file mode 100644 index 0000000..8b98444 --- /dev/null +++ b/src/extras/custompipes_d3d9.cpp @@ -0,0 +1,728 @@ +#define WITHD3D +#include "common.h" + +#ifdef RW_D3D9 +#include "main.h" +#include "RwHelper.h" +#include "Lights.h" +#include "Timecycle.h" +#include "FileMgr.h" +#include "Clock.h" +#include "Weather.h" +#include "TxdStore.h" +#include "Renderer.h" +#include "World.h" +#include "custompipes.h" + +#ifdef EXTENDED_PIPELINES + +#ifndef LIBRW +#error "Need librw for EXTENDED_PIPELINES" +#endif + +extern RwTexture *gpWhiteTexture; // from vehicle model info + +namespace CustomPipes { + +enum { + // rim pipe + VSLOC_boneMatrices = rw::d3d::VSLOC_afterLights, + VSLOC_viewVec = VSLOC_boneMatrices + 64*3, + VSLOC_rampStart, + VSLOC_rampEnd, + VSLOC_rimData, + + // gloss pipe + VSLOC_eye = rw::d3d::VSLOC_afterLights, + + VSLOC_reflProps, + VSLOC_specLights +}; + +/* + * Neo Vehicle pipe + */ + +static void *neoVehicle_VS; +static void *neoVehicle_PS; + +void +uploadSpecLights(void) +{ + struct VsLight { + rw::RGBAf color; + float pos[4]; // unused + rw::V3d dir; + float power; + } specLights[1 + NUMEXTRADIRECTIONALS]; + memset(specLights, 0, sizeof(specLights)); + for(int i = 0; i < 1+NUMEXTRADIRECTIONALS; i++) + specLights[i].power = 1.0f; + float power = Power.Get(); + Color speccol = SpecColor.Get(); + specLights[0].color.red = speccol.r; + specLights[0].color.green = speccol.g; + specLights[0].color.blue = speccol.b; + specLights[0].dir = pDirect->getFrame()->getLTM()->at; + specLights[0].power = power; + for(int i = 0; i < NUMEXTRADIRECTIONALS; i++){ + if(pExtraDirectionals[i]->getFlags() & rw::Light::LIGHTATOMICS){ + specLights[1+i].color = pExtraDirectionals[i]->color; + specLights[1+i].dir = pExtraDirectionals[i]->getFrame()->getLTM()->at; + specLights[1+i].power = power*2.0f; + } + } + rw::d3d::d3ddevice->SetVertexShaderConstantF(VSLOC_specLights, (float*)&specLights, 3*(1 + NUMEXTRADIRECTIONALS)); +} + +void +vehicleRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + // TODO: make this less of a kludge + if(VehiclePipeSwitch == VEHICLEPIPE_MATFX){ + matFXGlobals.pipelines[rw::platform]->render(atomic); + return; + } + + int vsBits; + rw::uint32 flags = atomic->geometry->flags; + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadSpecLights(); + uploadMatrices(atomic->getFrame()->getLTM()); + + setVertexShader(neoVehicle_VS); + + V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos; + d3ddevice->SetVertexShaderConstantF(VSLOC_eye, (float*)&eyePos, 1); + + float reflProps[4]; + reflProps[0] = Fresnel.Get(); + reflProps[1] = SpecColor.Get().a; + + d3d::setTexture(1, EnvMapTex); + + SetRenderState(SRCBLEND, BLENDONE); + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + reflProps[2] = m->surfaceProps.specular * VehicleShininess; + reflProps[3] = m->surfaceProps.specular == 0.0f ? 0.0f : VehicleSpecularity; + d3ddevice->SetVertexShaderConstantF(VSLOC_reflProps, reflProps, 1); + + setMaterial(flags, m->color, m->surfaceProps); + + if(m->texture) + d3d::setTexture(0, m->texture); + else + d3d::setTexture(0, gpWhiteTexture); + setPixelShader(neoVehicle_PS); + + drawInst(header, inst); + inst++; + } + d3d::setTexture(1, nil); + + SetRenderState(SRCBLEND, BLENDSRCALPHA); +} + +void +CreateVehiclePipe(void) +{ + if(CFileMgr::LoadFile("neo/carTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/carTweakingTable.dat'\n"); + else{ + char *fp = (char*)work_buff; + fp = ReadTweakValueTable(fp, Fresnel); + fp = ReadTweakValueTable(fp, Power); + fp = ReadTweakValueTable(fp, DiffColor); + fp = ReadTweakValueTable(fp, SpecColor); + } + +#include "shaders/obj/neoVehicle_VS.inc" + neoVehicle_VS = rw::d3d::createVertexShader(neoVehicle_VS_cso); + assert(neoVehicle_VS); + +#include "shaders/obj/neoVehicle_PS.inc" + neoVehicle_PS = rw::d3d::createPixelShader(neoVehicle_PS_cso); + assert(neoVehicle_PS); + + + rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::defaultInstanceCB; + pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB; + pipe->renderCB = vehicleRenderCB; + vehiclePipe = pipe; +} + +void +DestroyVehiclePipe(void) +{ + rw::d3d::destroyVertexShader(neoVehicle_VS); + neoVehicle_VS = nil; + + rw::d3d::destroyPixelShader(neoVehicle_PS); + neoVehicle_PS = nil; + + ((rw::d3d9::ObjPipeline*)vehiclePipe)->destroy(); + vehiclePipe = nil; +} + + + +/* + * Neo World pipe + */ + +static void *neoWorld_VS; +static void *neoWorldIII_PS; + +static void +worldRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + if(!LightmapEnable){ + defaultRenderCB_Shader(atomic, header); + return; + } + + int vsBits; + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + + float lightfactor[4]; + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + if(MatFX::getEffects(m) == MatFX::DUAL){ + setVertexShader(neoWorld_VS); + + MatFX *matfx = MatFX::get(m); + Texture *dualtex = matfx->getDualTexture(); + if(dualtex == nil) + goto notex; + d3d::setTexture(1, dualtex); + lightfactor[0] = lightfactor[1] = lightfactor[2] = WorldLightmapBlend.Get()*LightmapMult; + }else{ + notex: + setVertexShader(default_amb_VS); + + d3d::setTexture(1, nil); + lightfactor[0] = lightfactor[1] = lightfactor[2] = 0.0f; + } + lightfactor[3] = m->color.alpha/255.0f; + d3d::setTexture(0, m->texture); + d3ddevice->SetPixelShaderConstantF(1, lightfactor, 1); + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + RGBA color = { 255, 255, 255, m->color.alpha }; + setMaterial(color, m->surfaceProps); + + if(m->texture) + d3d::setTexture(0, m->texture); + else + d3d::setTexture(0, gpWhiteTexture); + setPixelShader(neoWorldIII_PS); + + drawInst(header, inst); + inst++; + } + d3d::setTexture(1, nil); +} + +void +CreateWorldPipe(void) +{ + if(CFileMgr::LoadFile("neo/worldTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/worldTweakingTable.dat'\n"); + else + ReadTweakValueTable((char*)work_buff, WorldLightmapBlend); + +#include "shaders/obj/default_UV2_VS.inc" + neoWorld_VS = rw::d3d::createVertexShader(default_UV2_VS_cso); + assert(neoWorld_VS); + +#include "shaders/obj/neoWorldIII_PS.inc" + neoWorldIII_PS = rw::d3d::createPixelShader(neoWorldIII_PS_cso); + assert(neoWorldIII_PS); + + + rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::defaultInstanceCB; + pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB; + pipe->renderCB = worldRenderCB; + worldPipe = pipe; +} + +void +DestroyWorldPipe(void) +{ + rw::d3d::destroyVertexShader(neoWorld_VS); + neoWorld_VS = nil; + rw::d3d::destroyPixelShader(neoWorldIII_PS); + neoWorldIII_PS = nil; + + + ((rw::d3d9::ObjPipeline*)worldPipe)->destroy(); + worldPipe = nil; +} + + + + +/* + * Neo Gloss pipe + */ + +static void *neoGloss_VS; +static void *neoGloss_PS; + +static void +glossRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + worldRenderCB(atomic, header); + + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + if(!GlossEnable) + return; + + setVertexShader(neoGloss_VS); + setPixelShader(neoGloss_PS); + + V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos; + d3ddevice->SetVertexShaderConstantF(VSLOC_eye, (float*)&eyePos, 1); + d3ddevice->SetPixelShaderConstantF(1, (float*)&GlossMult, 1); + + SetRenderState(VERTEXALPHA, TRUE); + SetRenderState(SRCBLEND, BLENDONE); + SetRenderState(DESTBLEND, BLENDONE); + SetRenderState(ZWRITEENABLE, FALSE); + SetRenderState(ALPHATESTFUNC, ALPHAALWAYS); + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + if(m->texture){ + Texture *tex = GetGlossTex(m); + if(tex){ + d3d::setTexture(0, tex); + drawInst(header, inst); + } + } + inst++; + } + + SetRenderState(ZWRITEENABLE, TRUE); + SetRenderState(ALPHATESTFUNC, ALPHAGREATEREQUAL); + SetRenderState(SRCBLEND, BLENDSRCALPHA); + SetRenderState(DESTBLEND, BLENDINVSRCALPHA); +} + +void +CreateGlossPipe(void) +{ +#include "shaders/obj/neoGloss_VS.inc" + neoGloss_VS = rw::d3d::createVertexShader(neoGloss_VS_cso); + assert(neoGloss_VS); + +#include "shaders/obj/neoGloss_PS.inc" + neoGloss_PS = rw::d3d::createPixelShader(neoGloss_PS_cso); + assert(neoGloss_PS); + + + rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::defaultInstanceCB; + pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB; + pipe->renderCB = glossRenderCB; + glossPipe = pipe; +} + +void +DestroyGlossPipe(void) +{ + rw::d3d::destroyVertexShader(neoGloss_VS); + neoGloss_VS = nil; + + rw::d3d::destroyPixelShader(neoGloss_PS); + neoGloss_PS = nil; + + ((rw::d3d9::ObjPipeline*)glossPipe)->destroy(); + glossPipe = nil; +} + + + +/* + * Neo Rim pipes + */ + +static void *neoRim_VS; +static void *neoRimSkin_VS; + +static void +uploadRimData(bool enable) +{ + using namespace rw; + using namespace rw::d3d; + + V3d viewVec = rw::engine->currentCamera->getFrame()->getLTM()->at; + d3ddevice->SetVertexShaderConstantF(VSLOC_viewVec, (float*)&viewVec, 1); + float rimData[4]; + rimData[0] = Offset.Get(); + rimData[1] = Scale.Get(); + if(enable) + rimData[2] = Scaling.Get()*RimlightMult; + else + rimData[2] = 0.0f; + rimData[3] = 0.0f; + d3ddevice->SetVertexShaderConstantF(VSLOC_rimData, rimData, 1); + Color col = RampStart.Get(); + d3ddevice->SetVertexShaderConstantF(VSLOC_rampStart, (float*)&col, 1); + col = RampEnd.Get(); + d3ddevice->SetVertexShaderConstantF(VSLOC_rampEnd, (float*)&col, 1); +} + +static void +rimRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + if(!RimlightEnable){ + defaultRenderCB_Shader(atomic, header); + return; + } + + int vsBits; + rw::uint32 flags = atomic->geometry->flags; + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + setVertexShader(neoRim_VS); + + uploadRimData(atomic->geometry->flags & Geometry::LIGHT); + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + setMaterial(flags, m->color, m->surfaceProps); + + if(m->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); + inst++; + } +} + +static void +rimSkinRenderCB(rw::Atomic *atomic, rw::d3d9::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + if(!RimlightEnable){ + skinRenderCB(atomic, header); + return; + } + + int vsBits; + rw::uint32 flags = atomic->geometry->flags; + setStreamSource(0, (IDirect3DVertexBuffer9*)header->vertexStream[0].vertexBuffer, + 0, header->vertexStream[0].stride); + setIndices((IDirect3DIndexBuffer9*)header->indexBuffer); + setVertexDeclaration((IDirect3DVertexDeclaration9*)header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + uploadSkinMatrices(atomic); + + setVertexShader(neoRimSkin_VS); + + uploadRimData(atomic->geometry->flags & Geometry::LIGHT); + + InstanceData *inst = header->inst; + for(rw::uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + setMaterial(flags, m->color, m->surfaceProps); + + if(inst->material->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); + inst++; + } +} + +void +CreateRimLightPipes(void) +{ + if(CFileMgr::LoadFile("neo/rimTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/rimTweakingTable.dat'\n"); + else{ + char *fp = (char*)work_buff; + fp = ReadTweakValueTable(fp, RampStart); + fp = ReadTweakValueTable(fp, RampEnd); + fp = ReadTweakValueTable(fp, Offset); + fp = ReadTweakValueTable(fp, Scale); + fp = ReadTweakValueTable(fp, Scaling); + } + + +#include "shaders/obj/neoRim_VS.inc" + neoRim_VS = rw::d3d::createVertexShader(neoRim_VS_cso); + assert(neoRim_VS); + +#include "shaders/obj/neoRimSkin_VS.inc" + neoRimSkin_VS = rw::d3d::createVertexShader(neoRimSkin_VS_cso); + assert(neoRimSkin_VS); + + + rw::d3d9::ObjPipeline *pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::defaultInstanceCB; + pipe->uninstanceCB = rw::d3d9::defaultUninstanceCB; + pipe->renderCB = rimRenderCB; + rimPipe = pipe; + + pipe = rw::d3d9::ObjPipeline::create(); + pipe->instanceCB = rw::d3d9::skinInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = rimSkinRenderCB; + rimSkinPipe = pipe; +} + +void +DestroyRimLightPipes(void) +{ + rw::d3d::destroyVertexShader(neoRim_VS); + neoRim_VS = nil; + + rw::d3d::destroyVertexShader(neoRimSkin_VS); + neoRimSkin_VS = nil; + + ((rw::d3d9::ObjPipeline*)rimPipe)->destroy(); + rimPipe = nil; + + ((rw::d3d9::ObjPipeline*)rimSkinPipe)->destroy(); + rimSkinPipe = nil; +} + +} + +#endif + +#ifdef NEW_RENDERER +#ifndef LIBRW +#error "Need librw for NEW_PIPELINES" +#endif + +namespace WorldRender +{ + +struct BuildingInst +{ + rw::RawMatrix combinedMat; + rw::d3d9::InstanceDataHeader *instHeader; + uint32 cullMode; + uint8 fadeAlpha; + bool lighting; +}; +BuildingInst blendInsts[3][2000]; +int numBlendInsts[3]; + +static RwRGBAReal black; + +static void +SetMatrix(BuildingInst *building, rw::Matrix *worldMat) +{ + using namespace rw; + RawMatrix world, worldview; + Camera *cam = engine->currentCamera; + convMatrix(&world, worldMat); + RawMatrix::mult(&worldview, &world, &cam->devView); + RawMatrix::mult(&building->combinedMat, &worldview, &cam->devProj); +} + +static bool +IsTextureTransparent(RwTexture *tex) +{ + if(tex == nil || tex->raster == nil) + return false; + return PLUGINOFFSET(rw::d3d::D3dRaster, tex->raster, rw::d3d::nativeRasterOffset)->hasAlpha; +} + +// Render all opaque meshes and put atomics that needs blending +// into the deferred list. +void +AtomicFirstPass(RpAtomic *atomic, int pass) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]]; + + atomic->getPipeline()->instance(atomic); + building->instHeader = (d3d9::InstanceDataHeader*)atomic->geometry->instData; + assert(building->instHeader != nil); + assert(building->instHeader->platform == PLATFORM_D3D9); + building->fadeAlpha = 255; + building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT); + building->cullMode = rw::GetRenderState(rw::CULLMODE); + rw::uint32 flags = atomic->geometry->flags; + + bool setupDone = false; + bool defer = false; + SetMatrix(building, atomic->getFrame()->getLTM()); + + InstanceData *inst = building->instHeader->inst; + for(rw::uint32 i = 0; i < building->instHeader->numMeshes; i++, inst++){ + Material *m = inst->material; + + if(inst->vertexAlpha || m->color.alpha != 255 || + IsTextureTransparent(m->texture)){ + defer = true; + continue; + } + + // alright we're rendering this atomic + if(!setupDone){ + rw::SetRenderState(rw::CULLMODE, building->cullMode); + setStreamSource(0, building->instHeader->vertexStream[0].vertexBuffer, 0, building->instHeader->vertexStream[0].stride); + setIndices(building->instHeader->indexBuffer); + setVertexDeclaration(building->instHeader->vertexDeclaration); + setVertexShader(default_amb_VS); + d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&building->combinedMat, 4); + if(building->lighting) + setAmbient(pAmbient->color); + else + setAmbient(black); + setupDone = true; + } + + setMaterial(flags, m->color, m->surfaceProps); + + if(m->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(building->instHeader, inst); + } + if(defer) + numBlendInsts[pass]++; +} + +void +AtomicFullyTransparent(RpAtomic *atomic, int pass, int fadeAlpha) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]]; + + atomic->getPipeline()->instance(atomic); + building->instHeader = (d3d9::InstanceDataHeader*)atomic->geometry->instData; + assert(building->instHeader != nil); + assert(building->instHeader->platform == PLATFORM_D3D9); + building->fadeAlpha = fadeAlpha; + building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT); + building->cullMode = rw::GetRenderState(rw::CULLMODE); + SetMatrix(building, atomic->getFrame()->getLTM()); + numBlendInsts[pass]++; +} + +void +RenderBlendPass(int pass) +{ + using namespace rw; + using namespace rw::d3d; + using namespace rw::d3d9; + + setVertexShader(default_amb_VS); + + int i; + for(i = 0; i < numBlendInsts[pass]; i++){ + BuildingInst *building = &blendInsts[pass][i]; + + rw::SetRenderState(rw::CULLMODE, building->cullMode); + setStreamSource(0, building->instHeader->vertexStream[0].vertexBuffer, 0, building->instHeader->vertexStream[0].stride); + setIndices(building->instHeader->indexBuffer); + setVertexDeclaration(building->instHeader->vertexDeclaration); + d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&building->combinedMat, 4); + if(building->lighting) + setAmbient(pAmbient->color); + else + setAmbient(black); + + InstanceData *inst = building->instHeader->inst; + for(rw::uint32 j = 0; j < building->instHeader->numMeshes; j++, inst++){ + Material *m = inst->material; + if(!inst->vertexAlpha && m->color.alpha == 255 && !IsTextureTransparent(m->texture) && building->fadeAlpha == 255) + continue; // already done this one + + rw::RGBA color = m->color; + color.alpha = (color.alpha * building->fadeAlpha)/255; + setMaterial(color, m->surfaceProps); // always modulate here + + if(m->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(building->instHeader, inst); + } + } +} +} +#endif + +#endif diff --git a/src/extras/custompipes_gl.cpp b/src/extras/custompipes_gl.cpp new file mode 100644 index 0000000..2b28cb5 --- /dev/null +++ b/src/extras/custompipes_gl.cpp @@ -0,0 +1,742 @@ +#include "common.h" + +#ifdef RW_OPENGL +#include "main.h" +#include "RwHelper.h" +#include "Lights.h" +#include "Timecycle.h" +#include "FileMgr.h" +#include "Clock.h" +#include "Weather.h" +#include "TxdStore.h" +#include "Renderer.h" +#include "World.h" +#include "custompipes.h" + +#ifdef EXTENDED_PIPELINES + +#ifndef LIBRW +#error "Need librw for EXTENDED_PIPELINES" +#endif + +namespace CustomPipes { + +static int32 u_viewVec; +static int32 u_rampStart; +static int32 u_rampEnd; +static int32 u_rimData; + +static int32 u_lightMap; + +static int32 u_eye; +static int32 u_reflProps; +static int32 u_specDir; +static int32 u_specColor; + +#define U(i) currentShader->uniformLocations[i] + +/* + * Neo Vehicle pipe + */ + +rw::gl3::Shader *neoVehicleShader; + +static void +uploadSpecLights(void) +{ + using namespace rw::gl3; + + rw::RGBAf colors[1 + NUMEXTRADIRECTIONALS]; + struct { + rw::V3d dir; + float power; + } dirs[1 + NUMEXTRADIRECTIONALS]; + memset(colors, 0, sizeof(colors)); + memset(dirs, 0, sizeof(dirs)); + for(int i = 0; i < 1+NUMEXTRADIRECTIONALS; i++) + dirs[i].power = 1.0f; + float power = Power.Get(); + Color speccol = SpecColor.Get(); + colors[0].red = speccol.r; + colors[0].green = speccol.g; + colors[0].blue = speccol.b; + dirs[0].dir = pDirect->getFrame()->getLTM()->at; + dirs[0].power = power; + for(int i = 0; i < NUMEXTRADIRECTIONALS; i++){ + if(pExtraDirectionals[i]->getFlags() & rw::Light::LIGHTATOMICS){ + colors[1+i] = pExtraDirectionals[i]->color; + dirs[1+i].dir = pExtraDirectionals[i]->getFrame()->getLTM()->at; + dirs[1+i].power = power*2.0f; + } + } + glUniform4fv(U(u_specDir), 1 + NUMEXTRADIRECTIONALS, (float*)&dirs); + glUniform4fv(U(u_specColor), 1 + NUMEXTRADIRECTIONALS, (float*)&colors); +} + +static void +vehicleRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + // TODO: make this less of a kludge + if(VehiclePipeSwitch == VEHICLEPIPE_MATFX){ + matFXGlobals.pipelines[rw::platform]->render(atomic); + return; + } + + Material *m; + + rw::uint32 flags = atomic->geometry->flags; + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoVehicleShader->use(); + + V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos; + glUniform3fv(U(u_eye), 1, (float*)&eyePos); + + uploadSpecLights(); + + float reflProps[4]; + reflProps[0] = Fresnel.Get(); + reflProps[1] = SpecColor.Get().a; + + setTexture(1, EnvMapTex); + + SetRenderState(SRCBLEND, BLENDONE); + + while(n--){ + m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + reflProps[2] = m->surfaceProps.specular * VehicleShininess; + reflProps[3] = m->surfaceProps.specular == 0.0f ? 0.0f : VehicleSpecularity; + glUniform4fv(U(u_reflProps), 1, reflProps); + + drawInst(header, inst); + inst++; + } + + SetRenderState(SRCBLEND, BLENDSRCALPHA); + setTexture(1, nil); + + teardownVertexInput(header); +} + +void +CreateVehiclePipe(void) +{ + using namespace rw; + using namespace rw::gl3; + + if(CFileMgr::LoadFile("neo/carTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/carTweakingTable.dat'\n"); + else{ + char *fp = (char*)work_buff; + fp = ReadTweakValueTable(fp, Fresnel); + fp = ReadTweakValueTable(fp, Power); + fp = ReadTweakValueTable(fp, DiffColor); + fp = ReadTweakValueTable(fp, SpecColor); + } + + + { +#include "shaders/obj/neoVehicle_frag.inc" +#include "shaders/obj/neoVehicle_vert.inc" + const char *vs[] = { shaderDecl, "#define DIRECTIONALS\n", header_vert_src, neoVehicle_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, neoVehicle_frag_src, nil }; + neoVehicleShader = Shader::create(vs, fs); + assert(neoVehicleShader); + } + + + rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::defaultInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = vehicleRenderCB; + vehiclePipe = pipe; +} + +void +DestroyVehiclePipe(void) +{ + neoVehicleShader->destroy(); + neoVehicleShader = nil; + + ((rw::gl3::ObjPipeline*)vehiclePipe)->destroy(); + vehiclePipe = nil; +} + + + +/* + * Neo World pipe + */ + +rw::gl3::Shader *neoWorldShader; + +static void +worldRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + if(!LightmapEnable){ + gl3::defaultRenderCB(atomic, header); + return; + } + + Material *m; + + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoWorldShader->use(); + + float lightfactor[4]; + + while(n--){ + m = inst->material; + + if(MatFX::getEffects(m) == MatFX::DUAL){ + MatFX *matfx = MatFX::get(m); + Texture *dualtex = matfx->getDualTexture(); + if(dualtex == nil) + goto notex; + setTexture(1, dualtex); + lightfactor[0] = lightfactor[1] = lightfactor[2] = WorldLightmapBlend.Get()*LightmapMult; + }else{ + notex: + setTexture(1, nil); + lightfactor[0] = lightfactor[1] = lightfactor[2] = 0.0f; + } + lightfactor[3] = m->color.alpha/255.0f; + glUniform4fv(U(u_lightMap), 1, lightfactor); + + RGBA color = { 255, 255, 255, m->color.alpha }; + setMaterial(color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + drawInst(header, inst); + inst++; + } + setTexture(1, nil); + teardownVertexInput(header); +} + +void +CreateWorldPipe(void) +{ + using namespace rw; + using namespace rw::gl3; + + if(CFileMgr::LoadFile("neo/worldTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/worldTweakingTable.dat'\n"); + else + ReadTweakValueTable((char*)work_buff, WorldLightmapBlend); + + { +#include "shaders/obj/neoWorldIII_frag.inc" +#include "shaders/obj/default_UV2_vert.inc" + const char *vs[] = { shaderDecl, header_vert_src, default_UV2_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, neoWorldIII_frag_src, nil }; + neoWorldShader = Shader::create(vs, fs); + assert(neoWorldShader); + } + + + rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::defaultInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = worldRenderCB; + worldPipe = pipe; +} + +void +DestroyWorldPipe(void) +{ + neoWorldShader->destroy(); + neoWorldShader = nil; + + ((rw::gl3::ObjPipeline*)worldPipe)->destroy(); + worldPipe = nil; +} + + + + +/* + * Neo Gloss pipe + */ + +rw::gl3::Shader *neoGlossShader; + +static void +glossRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + worldRenderCB(atomic, header); + if(!GlossEnable) + return; + + Material *m; + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoGlossShader->use(); + + V3d eyePos = rw::engine->currentCamera->getFrame()->getLTM()->pos; + glUniform3fv(U(u_eye), 1, (float*)&eyePos); + float reflProps[4]; + reflProps[0] = GlossMult; + reflProps[1] = 0.0f; + reflProps[2] = 0.0f; + reflProps[3] = 0.0f; + glUniform4fv(U(u_reflProps), 1, reflProps); + + SetRenderState(VERTEXALPHA, TRUE); + SetRenderState(SRCBLEND, BLENDONE); + SetRenderState(DESTBLEND, BLENDONE); + SetRenderState(ZWRITEENABLE, FALSE); + SetRenderState(ALPHATESTFUNC, ALPHAALWAYS); + + while(n--){ + m = inst->material; + + RGBA color = { 255, 255, 255, m->color.alpha }; + setMaterial(color, m->surfaceProps); + + if(m->texture){ + Texture *tex = GetGlossTex(m); + if(tex){ + setTexture(0, tex); + drawInst(header, inst); + } + } + inst++; + } + + SetRenderState(ZWRITEENABLE, TRUE); + SetRenderState(ALPHATESTFUNC, ALPHAGREATEREQUAL); + SetRenderState(SRCBLEND, BLENDSRCALPHA); + SetRenderState(DESTBLEND, BLENDINVSRCALPHA); + + teardownVertexInput(header); +} + +void +CreateGlossPipe(void) +{ + using namespace rw; + using namespace rw::gl3; + + { +#include "shaders/obj/neoGloss_frag.inc" +#include "shaders/obj/neoGloss_vert.inc" + const char *vs[] = { shaderDecl, header_vert_src, neoGloss_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, neoGloss_frag_src, nil }; + neoGlossShader = Shader::create(vs, fs); + assert(neoGlossShader); + } + + rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::defaultInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = glossRenderCB; + glossPipe = pipe; +} + +void +DestroyGlossPipe(void) +{ + neoGlossShader->destroy(); + neoGlossShader = nil; + + ((rw::gl3::ObjPipeline*)glossPipe)->destroy(); + glossPipe = nil; +} + + + +/* + * Neo Rim pipes + */ + +rw::gl3::Shader *neoRimShader; +rw::gl3::Shader *neoRimSkinShader; + +static void +uploadRimData(bool enable) +{ + using namespace rw; + using namespace rw::gl3; + + V3d viewVec = rw::engine->currentCamera->getFrame()->getLTM()->at; + glUniform3fv(U(u_viewVec), 1, (float*)&viewVec); + float rimData[4]; + rimData[0] = Offset.Get(); + rimData[1] = Scale.Get(); + if(enable) + rimData[2] = Scaling.Get()*RimlightMult; + else + rimData[2] = 0.0f; + rimData[3] = 0.0f; + glUniform3fv(U(u_rimData), 1, rimData); + Color col = RampStart.Get(); + glUniform4fv(U(u_rampStart), 1, (float*)&col); + col = RampEnd.Get(); + glUniform4fv(U(u_rampEnd), 1, (float*)&col); +} + +static void +rimSkinRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + if(!RimlightEnable){ + gl3::skinRenderCB(atomic, header); + return; + } + + Material *m; + + rw::uint32 flags = atomic->geometry->flags; + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoRimSkinShader->use(); + + uploadRimData(atomic->geometry->flags & Geometry::LIGHT); + + uploadSkinMatrices(atomic); + + while(n--){ + m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + drawInst(header, inst); + inst++; + } + teardownVertexInput(header); +} + +static void +rimRenderCB(rw::Atomic *atomic, rw::gl3::InstanceDataHeader *header) +{ + using namespace rw; + using namespace rw::gl3; + + if(!RimlightEnable){ + gl3::defaultRenderCB(atomic, header); + return; + } + + Material *m; + + rw::uint32 flags = atomic->geometry->flags; + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + rw::int32 n = header->numMeshes; + + neoRimShader->use(); + + uploadRimData(atomic->geometry->flags & Geometry::LIGHT); + + while(n--){ + m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + drawInst(header, inst); + inst++; + } + teardownVertexInput(header); +} + +void +CreateRimLightPipes(void) +{ + using namespace rw::gl3; + + if(CFileMgr::LoadFile("neo/rimTweakingTable.dat", work_buff, sizeof(work_buff), "r") <= 0) + printf("Error: couldn't open 'neo/rimTweakingTable.dat'\n"); + else{ + char *fp = (char*)work_buff; + fp = ReadTweakValueTable(fp, RampStart); + fp = ReadTweakValueTable(fp, RampEnd); + fp = ReadTweakValueTable(fp, Offset); + fp = ReadTweakValueTable(fp, Scale); + fp = ReadTweakValueTable(fp, Scaling); + } + + { +#include "shaders/obj/simple_frag.inc" +#include "shaders/obj/neoRimSkin_vert.inc" + const char *vs[] = { shaderDecl, "#define DIRECTIONALS\n", header_vert_src, neoRimSkin_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + neoRimSkinShader = Shader::create(vs, fs); + assert(neoRimSkinShader); + } + + { +#include "shaders/obj/simple_frag.inc" +#include "shaders/obj/neoRim_vert.inc" + const char *vs[] = { shaderDecl, "#define DIRECTIONALS\n", header_vert_src, neoRim_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + neoRimShader = Shader::create(vs, fs); + assert(neoRimShader); + } + + + rw::gl3::ObjPipeline *pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::defaultInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = rimRenderCB; + rimPipe = pipe; + + pipe = rw::gl3::ObjPipeline::create(); + pipe->instanceCB = rw::gl3::skinInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = rimSkinRenderCB; + rimSkinPipe = pipe; +} + +void +DestroyRimLightPipes(void) +{ + neoRimShader->destroy(); + neoRimShader = nil; + + neoRimSkinShader->destroy(); + neoRimSkinShader = nil; + + ((rw::gl3::ObjPipeline*)rimPipe)->destroy(); + rimPipe = nil; + + ((rw::gl3::ObjPipeline*)rimSkinPipe)->destroy(); + rimSkinPipe = nil; +} + + + +void +CustomPipeRegisterGL(void) +{ + u_viewVec = rw::gl3::registerUniform("u_viewVec"); + u_rampStart = rw::gl3::registerUniform("u_rampStart"); + u_rampEnd = rw::gl3::registerUniform("u_rampEnd"); + u_rimData = rw::gl3::registerUniform("u_rimData"); + + u_lightMap = rw::gl3::registerUniform("u_lightMap"); + + u_eye = rw::gl3::registerUniform("u_eye"); + u_reflProps = rw::gl3::registerUniform("u_reflProps"); + u_specDir = rw::gl3::registerUniform("u_specDir"); + u_specColor = rw::gl3::registerUniform("u_specColor"); +} + + +} + +#endif + +#ifdef NEW_RENDERER +#ifndef LIBRW +#error "Need librw for NEW_PIPELINES" +#endif + +namespace WorldRender +{ + +struct BuildingInst +{ + rw::Matrix matrix; + rw::gl3::InstanceDataHeader *instHeader; + uint32 cullMode; + uint8 fadeAlpha; + bool lighting; +}; +BuildingInst blendInsts[3][2000]; +int numBlendInsts[3]; + +static RwRGBAReal black; + +static bool +IsTextureTransparent(RwTexture *tex) +{ + if(tex == nil || tex->raster == nil) + return false; + return PLUGINOFFSET(rw::gl3::Gl3Raster, tex->raster, rw::gl3::nativeRasterOffset)->hasAlpha; +} + +// Render all opaque meshes and put atomics that needs blending +// into the deferred list. +void +AtomicFirstPass(RpAtomic *atomic, int pass) +{ + using namespace rw; + using namespace rw::gl3; + + BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]]; + + atomic->getPipeline()->instance(atomic); + building->instHeader = (gl3::InstanceDataHeader*)atomic->geometry->instData; + assert(building->instHeader != nil); + assert(building->instHeader->platform == PLATFORM_GL3); + building->fadeAlpha = 255; + building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT); + building->cullMode = rw::GetRenderState(rw::CULLMODE); + rw::uint32 flags = atomic->geometry->flags; + + WorldLights lights; + lights.numAmbients = 1; + lights.numDirectionals = 0; + lights.numLocals = 0; + if(building->lighting) + lights.ambient = pAmbient->color; + else + lights.ambient = black; + + bool setupDone = false; + bool defer = false; + building->matrix = *atomic->getFrame()->getLTM(); + + InstanceData *inst = building->instHeader->inst; + for(rw::uint32 i = 0; i < building->instHeader->numMeshes; i++, inst++){ + Material *m = inst->material; + + if(inst->vertexAlpha || m->color.alpha != 255 || + IsTextureTransparent(m->texture)){ + defer = true; + continue; + } + + // alright we're rendering this atomic + if(!setupDone){ + rw::SetRenderState(rw::CULLMODE, building->cullMode); + defaultShader->use(); + setWorldMatrix(&building->matrix); + setupVertexInput(building->instHeader); + setLights(&lights); + setupDone = true; + } + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + drawInst(building->instHeader, inst); + } + teardownVertexInput(building->instHeader); + if(defer) + numBlendInsts[pass]++; +} + +void +AtomicFullyTransparent(RpAtomic *atomic, int pass, int fadeAlpha) +{ + using namespace rw; + using namespace rw::gl3; + + BuildingInst *building = &blendInsts[pass][numBlendInsts[pass]]; + + atomic->getPipeline()->instance(atomic); + building->instHeader = (gl3::InstanceDataHeader*)atomic->geometry->instData; + assert(building->instHeader != nil); + assert(building->instHeader->platform == PLATFORM_GL3); + building->fadeAlpha = fadeAlpha; + building->lighting = !!(atomic->geometry->flags & rw::Geometry::LIGHT); + building->cullMode = rw::GetRenderState(rw::CULLMODE); + building->matrix = *atomic->getFrame()->getLTM(); + numBlendInsts[pass]++; +} + +void +RenderBlendPass(int pass) +{ + using namespace rw; + using namespace rw::gl3; + + defaultShader->use(); + WorldLights lights; + lights.numAmbients = 1; + lights.numDirectionals = 0; + lights.numLocals = 0; + + int i; + for(i = 0; i < numBlendInsts[pass]; i++){ + BuildingInst *building = &blendInsts[pass][i]; + + rw::SetRenderState(rw::CULLMODE, building->cullMode); + setupVertexInput(building->instHeader); + setWorldMatrix(&building->matrix); + if(building->lighting) + lights.ambient = pAmbient->color; + else + lights.ambient = black; + setLights(&lights); + + InstanceData *inst = building->instHeader->inst; + for(rw::uint32 j = 0; j < building->instHeader->numMeshes; j++, inst++){ + Material *m = inst->material; + if(!inst->vertexAlpha && m->color.alpha == 255 && !IsTextureTransparent(m->texture) && building->fadeAlpha == 255) + continue; // already done this one + + rw::RGBA color = m->color; + color.alpha = (color.alpha * building->fadeAlpha)/255; + setMaterial(color, m->surfaceProps); // always modulate here + + setTexture(0, m->texture); + + drawInst(building->instHeader, inst); + } + teardownVertexInput(building->instHeader); + } +} +} +#endif + +#endif diff --git a/src/extras/debugmenu.cpp b/src/extras/debugmenu.cpp new file mode 100644 index 0000000..533b97f --- /dev/null +++ b/src/extras/debugmenu.cpp @@ -0,0 +1,1312 @@ +#include "common.h" +#ifdef DEBUGMENU +#include "RwHelper.h" +#include "Pad.h" +#include "ControllerConfig.h" +#include "Timer.h" +#include "rtcharse.h" +#include "re3_inttypes.h" +#include "debugmenu.h" +#include + +#ifdef _WIN32 +#define snprintf _snprintf + +#define strdup _strdup +#endif + + +// Font stuff +struct Pt +{ + int x, y; +}; + +enum MenuFontStyle +{ + MENUFONT_NORMAL, + MENUFONT_SEL_ACTIVE, + MENUFONT_SEL_INACTIVE, + MENUFONT_MOUSE +}; + +RtCharset *fontStyles[4]; +RtCharsetDesc fontDesc; +int fontscale = 1; // not supported right now + +Pt +fontGetStringSize(const char *s) +{ + Pt sz = { 0, 0 }; + int x; + char c; + sz.y = fontDesc.height*fontscale; // always assume one line; + x = 0; + while(c = *s++){ + if(c == '\n'){ + sz.y += fontDesc.height*fontscale; + if(x > sz.x) + sz.x = x; + x = 0; + }else + x += fontDesc.width*fontscale; + } + if(x > sz.x) + sz.x = x; + return sz; +} + +Pt +fontPrint(const char *s, float x, float y, int style) +{ + RtCharsetPrintBuffered(fontStyles[style], s, x, y, false); + return fontGetStringSize(s); +} + +int +fontGetLen(int len) +{ + return len*fontDesc.width*fontscale; +} + + +void +createMenuFont(void) +{ + OpenCharsetSafe(); + + RwRGBA fg_normal = { 255, 255, 255, 255 }; + RwRGBA bg_normal = { 255, 255, 255, 0 }; + fontStyles[MENUFONT_NORMAL] = RtCharsetCreate(&fg_normal, &bg_normal); + assert(fontStyles[MENUFONT_NORMAL]); + + RwRGBA fg_sel_active = { 200, 200, 200, 255 }; + RwRGBA bg_sel_active = { 132, 132, 132, 255 }; + fontStyles[MENUFONT_SEL_ACTIVE] = RtCharsetCreate(&fg_sel_active, &bg_sel_active); + assert(fontStyles[MENUFONT_SEL_ACTIVE]); + + RwRGBA fg_sel_inactive = { 200, 200, 200, 255 }; + RwRGBA bg_sel_inactive = { 200, 200, 200, 0 }; + fontStyles[MENUFONT_SEL_INACTIVE] = RtCharsetCreate(&fg_sel_inactive, &bg_sel_inactive); + assert(fontStyles[MENUFONT_SEL_INACTIVE]); + + RwRGBA fg_mouse = { 255, 255, 255, 255 }; + RwRGBA bg_mouse = { 132, 132, 132, 255 }; + fontStyles[MENUFONT_MOUSE] = RtCharsetCreate(&fg_mouse, &bg_mouse); + assert(fontStyles[MENUFONT_MOUSE]); + + RtCharsetGetDesc(fontStyles[MENUFONT_NORMAL], &fontDesc); +} + +void +destroyMenuFont(void) +{ + RtCharsetDestroy(fontStyles[MENUFONT_NORMAL]); + fontStyles[MENUFONT_NORMAL] = nil; + RtCharsetDestroy(fontStyles[MENUFONT_SEL_ACTIVE]); + fontStyles[MENUFONT_SEL_ACTIVE] = nil; + RtCharsetDestroy(fontStyles[MENUFONT_SEL_INACTIVE]); + fontStyles[MENUFONT_SEL_INACTIVE] = nil; + RtCharsetDestroy(fontStyles[MENUFONT_MOUSE]); + fontStyles[MENUFONT_MOUSE] = nil; +} + + + + + + +enum EntryType +{ + MENUEMPTY = 0, + MENUSUB, + MENUVAR, + + MENUVAR_INT, + MENUVAR_FLOAT, + MENUVAR_CMD, + + MENUSCROLL // dummy +}; + +struct Menu +{ + Menu *parent; + RwRect r; + MenuEntry *entries; + int numEntries; + int maxNameWidth, maxValWidth; + + MenuEntry *findEntry(const char *entryname); + void insertEntrySorted(MenuEntry *entry); + void appendEntry(MenuEntry *entry); + + bool isScrollingUp, isScrollingDown; + int scrollStart; + int numVisible; + RwRect scrollUpR, scrollDownR; + void scroll(int off); + + int selection; + MenuEntry *selectedEntry; // updated by update + void changeSelection(int newsel); + void changeSelection(MenuEntry *e); + + void update(void); + void draw(void); + Menu(void){ memset(this, 0, sizeof(Menu)); } + ~Menu(void); +}; +extern Menu toplevel; + +struct MenuEntry_Sub : MenuEntry +{ + Menu *submenu; + + MenuEntry_Sub(const char *name, Menu *menu); + ~MenuEntry_Sub(void) { delete submenu; } +}; + +struct MenuEntry_Var : MenuEntry +{ + int maxvallen; + int vartype; + bool wrapAround; + + virtual void processInput(bool mouseOver, bool selected) = 0; + int getValWidth(void) { return maxvallen; } + virtual void getValStr(char *str, int len) = 0; + MenuEntry_Var(const char *name, int type); +}; + +struct MenuEntry_Int : MenuEntry_Var +{ + virtual void setStrings(const char **strings) = 0; + virtual int findStringLen(void) = 0; + MenuEntry_Int(const char *name); +}; + +#define INTTYPES \ + X(Int8, int8, 4, "%4" PRId8) \ + X(Int16, int16, 6, "%6" PRId16) \ + X(Int32, int32, 11, "%11" PRId32) \ + X(Int64, int64, 21, "%21" PRId64) \ + X(UInt8, uint8, 4, "%4" PRIu8) \ + X(UInt16, uint16, 6, "%6" PRIu16) \ + X(UInt32, uint32, 11, "%11" PRIu32) \ + X(UInt64, uint64, 21, "%21" PRIu64) +#define FLOATTYPES \ + X(Float32, float, 11, "%11.3f") \ + X(Float64, double, 11, "%11.3lf") + +#define X(NAME, TYPE, MAXLEN, FMT) \ +struct MenuEntry_##NAME : MenuEntry_Int \ +{ \ + TYPE *variable; \ + TYPE lowerBound, upperBound; \ + TYPE step; \ + TriggerFunc triggerFunc; \ + const char *fmt; \ + const char **strings; \ + \ + void processInput(bool mouseOver, bool selected); \ + void getValStr(char *str, int len); \ + \ + void setStrings(const char **strings); \ + int findStringLen(void); \ + MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings); \ +}; +INTTYPES +#undef X + +#define X(NAME, TYPE, MAXLEN, FMT) \ +struct MenuEntry_##NAME : MenuEntry_Var \ +{ \ + TYPE *variable; \ + TYPE lowerBound, upperBound; \ + TYPE step; \ + TriggerFunc triggerFunc; \ + const char *fmt; \ + \ + void processInput(bool mouseOver, bool selected); \ + void getValStr(char *str, int len); \ + \ + MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound); \ +}; +FLOATTYPES +#undef X + +struct MenuEntry_Cmd : MenuEntry_Var +{ + TriggerFunc triggerFunc; + + void processInput(bool mouseOver, bool selected); + void getValStr(char *str, int len); + + MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc); +}; + + +Menu *findMenu(const char *name); + + + +#define MUHKEYS \ + X(leftjustdown, rsLEFT) \ + X(rightjustdown, rsRIGHT) \ + X(upjustdown, rsUP) \ + X(downjustdown, rsDOWN) \ + X(pgupjustdown, rsPGUP) \ + X(pgdnjustdown, rsPGDN) + +#define MUHBUTTONS \ + X(button1justdown, 1) \ + X(button2justdown, 2) \ + X(button3justdown, 3) + +#define REPEATDELAY 700 +#define REPEATINTERVAL 50 +#define X(var, keycode) static int var; +MUHKEYS +#undef X +static int downtime; +static int repeattime; +static int lastkeydown; +static int *keyptr; + +static int buttondown[3]; +static int lastbuttondown; +static int *buttonptr; +static int button1justdown, button2justdown, button3justdown; +static float mouseX, mouseY; + +static int menuOn; +static int menuInitialized; +static int screenWidth, screenHeight; +static RwRaster *cursor, *arrow; + +static int firstBorder = 10; +static int topBorder = 40; //10; +static int leading = 4; +static int gap = 10; +static int minwidth = 100; + +void drawMouse(void); +void drawArrow(RwRect r, int direction, int style); + +Menu toplevel; +Menu *activeMenu = &toplevel; +Menu *deepestMenu = &toplevel; +Menu *mouseOverMenu; +MenuEntry *mouseOverEntry; +MenuEntry scrollUpEntry("SCROLLUP"), scrollDownEntry("SCROLLDOWN"); // dummies + + +#define KEYJUSTDOWN(k) ControlsManager.GetIsKeyboardKeyJustDown((RsKeyCodes)k) +#define KEYDOWN(k) ControlsManager.GetIsKeyboardKeyDown((RsKeyCodes)k) +#define CTRLJUSTDOWN(key) \ + ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYJUSTDOWN((RsKeyCodes)key) || \ + (KEYJUSTDOWN(rsLCTRL) || KEYJUSTDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) +#define CTRLDOWN(key) ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key)) + + +bool +isMouseInRect(RwRect r) +{ + return (mouseX >= r.x && mouseX < r.x+r.w && + mouseY >= r.y && mouseY < r.y+r.h); +} + +/* + * MenuEntry + */ + +MenuEntry::MenuEntry(const char *name) +{ + this->type = MENUEMPTY; + this->name = strdup(name); + this->next = nil; + this->menu = nil; +} + +MenuEntry_Sub::MenuEntry_Sub(const char *name, Menu *menu) +: MenuEntry(name) +{ + this->type = MENUSUB; + this->submenu = menu; +} + +MenuEntry_Var::MenuEntry_Var(const char *name, int vartype) +: MenuEntry(name) +{ + this->type = MENUVAR; + this->vartype = vartype; + this->maxvallen = 0; + this->wrapAround = false; +} + +/* + * ***************************** + * MenuEntry_Int + * ***************************** + */ + +MenuEntry_Int::MenuEntry_Int(const char *name) +: MenuEntry_Var(name, MENUVAR_INT) +{ +} + +#define X(NAME, TYPE, MAXLEN, FMT) \ +int \ +MenuEntry_##NAME::findStringLen(void){ \ + TYPE i; \ + int len, maxlen = 0; \ + for(i = this->lowerBound; i <= this->upperBound; i++){ \ + len = strlen(this->strings[i-this->lowerBound]); \ + if(len > maxlen) \ + maxlen = len; \ + } \ + return maxlen; \ +} \ +void \ +MenuEntry_##NAME::processInput(bool mouseOver, bool selected) \ +{ \ + TYPE v, oldv; \ + int overflow = 0; \ + int underflow = 0; \ + \ + v = *this->variable; \ + oldv = v; \ + \ + if((selected && leftjustdown) || (mouseOver && button3justdown)){ \ + v -= this->step; \ + if(v > oldv) \ + underflow = 1; \ + } \ + if((selected && rightjustdown) || (mouseOver && button1justdown)){ \ + v += this->step; \ + if(v < oldv) \ + overflow = 1; \ + } \ + if(this->wrapAround){ \ + if(v > this->upperBound || overflow) v = this->lowerBound; \ + if(v < this->lowerBound || underflow) v = this->upperBound; \ + }else{ \ + if(v > this->upperBound || overflow) v = this->upperBound; \ + if(v < this->lowerBound || underflow) v = this->lowerBound; \ + } \ + \ + *this->variable = v; \ + if(oldv != v && this->triggerFunc) \ + this->triggerFunc(); \ +} \ +void \ +MenuEntry_##NAME::getValStr(char *str, int len) \ +{ \ + static char tmp[20]; \ + if(this->strings){ \ + snprintf(tmp, 20, "%%%ds", this->maxvallen); \ + if(*this->variable < this->lowerBound || *this->variable > this->upperBound){ \ + snprintf(str, len, "ERROR"); \ + return; \ + } \ + snprintf(str, len, tmp, this->strings[*this->variable-this->lowerBound]); \ + }else \ + snprintf(str, len, this->fmt, *this->variable); \ +} \ +void \ +MenuEntry_##NAME::setStrings(const char **strings) \ +{ \ + this->strings = strings; \ + if(this->strings) \ + this->maxvallen = findStringLen(); \ +} \ +MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings) \ +: MenuEntry_Int(name) \ +{ \ + this->variable = ptr; \ + this->step = step; \ + this->lowerBound = lowerBound; \ + this->upperBound = upperBound; \ + this->triggerFunc = triggerFunc; \ + this->maxvallen = MAXLEN; \ + this->fmt = FMT; \ + this->setStrings(strings); \ +} +INTTYPES +#undef X + +/* + * ***************************** + * MenuEntry_Float + * ***************************** + */ + +#define X(NAME, TYPE, MAXLEN, FMT) \ +MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound) \ +: MenuEntry_Var(name, MENUVAR_FLOAT) \ +{ \ + this->variable = ptr; \ + this->step = step; \ + this->lowerBound = lowerBound; \ + this->upperBound = upperBound; \ + this->triggerFunc = triggerFunc; \ + this->maxvallen = MAXLEN; \ + this->fmt = FMT; \ +} \ +void \ +MenuEntry_##NAME::getValStr(char *str, int len) \ +{ \ + snprintf(str, len, this->fmt, *this->variable); \ +} \ +void \ +MenuEntry_##NAME::processInput(bool mouseOver, bool selected) \ +{ \ + float v, oldv; \ + int overflow = 0; \ + int underflow = 0; \ + \ + v = *this->variable; \ + oldv = v; \ + \ + if((selected && leftjustdown) || (mouseOver && button3justdown)){ \ + v -= this->step; \ + if(v > oldv) \ + underflow = 1; \ + } \ + if((selected && rightjustdown) || (mouseOver && button1justdown)){ \ + v += this->step; \ + if(v < oldv) \ + overflow = 1; \ + } \ + if(this->wrapAround){ \ + if(v > this->upperBound || overflow) v = this->lowerBound; \ + if(v < this->lowerBound || underflow) v = this->upperBound; \ + }else{ \ + if(v > this->upperBound || overflow) v = this->upperBound; \ + if(v < this->lowerBound || underflow) v = this->lowerBound; \ + } \ + \ + *this->variable = v; \ + if(oldv != v && this->triggerFunc) \ + this->triggerFunc(); \ +} + +FLOATTYPES +#undef X + +/* + * ***************************** + * MenuEntry_Cmd + * ***************************** + */ + +void +MenuEntry_Cmd::processInput(bool mouseOver, bool selected) +{ + // Don't execute on button3 + if(this->triggerFunc && (selected && (leftjustdown || rightjustdown) || (mouseOver && button1justdown))) + this->triggerFunc(); +} + +void +MenuEntry_Cmd::getValStr(char *str, int len) +{ + strncpy(str, "<", len); +} + +MenuEntry_Cmd::MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc) +: MenuEntry_Var(name, MENUVAR_CMD) +{ + this->maxvallen = 1; + this->triggerFunc = triggerFunc; +} + + +/* + * ***************************** + * Menu + * ***************************** + */ + +void +Menu::scroll(int off) { + if(isScrollingUp && off < 0) + scrollStart += off; + if(isScrollingDown && off > 0) + scrollStart += off; + if(scrollStart < 0) scrollStart = 0; + if(scrollStart > numEntries-numVisible) scrollStart = numEntries-numVisible; +} + +void +Menu::changeSelection(int newsel){ + selection = newsel; + if(selection < 0) selection = 0; + if(selection >= numEntries) selection = numEntries-1; + if(selection < scrollStart) scrollStart = selection; + if(selection >= scrollStart+numVisible) scrollStart = selection-numVisible+1; +} + +void +Menu::changeSelection(MenuEntry *sel) +{ + MenuEntry *e; + int i = 0; + for(e = this->entries; e; e = e->next){ + if(e == sel){ + this->selection = i; + this->selectedEntry = sel; + break; + } + i++; + } +} + + + +MenuEntry* +Menu::findEntry(const char *entryname) +{ + MenuEntry *m; + for(m = this->entries; m; m = m->next) + if(strcmp(entryname, m->name) == 0) + return m; + return nil; +} + +void +Menu::insertEntrySorted(MenuEntry *entry) +{ + MenuEntry **mp; + int cmp; + for(mp = &this->entries; *mp; mp = &(*mp)->next){ + cmp = strcmp(entry->name, (*mp)->name); + if(cmp == 0) + return; + if(cmp < 0) + break; + } + entry->next = *mp; + *mp = entry; + entry->menu = this; + this->numEntries++; +} + +void +Menu::appendEntry(MenuEntry *entry) +{ + MenuEntry **mp; + for(mp = &this->entries; *mp; mp = &(*mp)->next); + entry->next = *mp; + *mp = entry; + entry->menu = this; + this->numEntries++; +} + +void +Menu::update(void) +{ + int i; + int x, y; + Pt sz; + MenuEntry *e; + int onscreen; + x = this->r.x; + y = this->r.y + 18; + int end = this->r.y+this->r.h - 18; + this->numVisible = 0; + + deepestMenu = this; + + int bottomy = end; + onscreen = 1; + i = 0; + this->maxNameWidth = 0; + this->maxValWidth = 0; + this->isScrollingUp = this->scrollStart > 0; + this->isScrollingDown = false; + this->selectedEntry = nil; + for(e = this->entries; e; e = e->next){ + sz = fontGetStringSize(e->name); + e->r.x = x; + e->r.y = y; + e->r.w = sz.x; + e->r.h = sz.y; + + if(i == this->selection) + this->selectedEntry = e; + + if(i >= this->scrollStart) + y += sz.y + leading*fontscale; + if(y >= end){ + this->isScrollingDown = true; + onscreen = 0; + }else + bottomy = y; + if(i >= this->scrollStart && onscreen) + this->numVisible++; + + if(e->type == MENUVAR){ + int valwidth = fontGetLen(((MenuEntry_Var*)e)->getValWidth()); + if(valwidth > maxValWidth) + maxValWidth = valwidth; + } + if(e->r.w > maxNameWidth) + maxNameWidth = e->r.w; + i++; + } + if(this->r.w < maxNameWidth + maxValWidth + gap*fontscale) + this->r.w = maxNameWidth + maxValWidth + gap*fontscale; + + this->scrollUpR = this->r; + this->scrollUpR.h = 16; + this->scrollDownR = this->scrollUpR; + this->scrollDownR.y = bottomy; + + // Update active submenu + if(this->selectedEntry && this->selectedEntry->type == MENUSUB){ + Menu *submenu = ((MenuEntry_Sub*)this->selectedEntry)->submenu; + submenu->r.x = this->r.x+this->r.w + 10; + submenu->r.y = this->r.y; + submenu->r.w = minwidth; // update menu will expand + submenu->r.h = this->r.h; + submenu->update(); + } +} + +void +Menu::draw(void) +{ + static char val[100]; + int i; + MenuEntry *e; + i = 0; + for(e = this->entries; e; e = e->next){ + if(i >= this->scrollStart+this->numVisible) + break; + if(i >= this->scrollStart){ + int style = MENUFONT_NORMAL; + if(i == this->selection) + style = this == activeMenu ? MENUFONT_SEL_ACTIVE : MENUFONT_SEL_INACTIVE; + if(style != MENUFONT_SEL_ACTIVE && e == mouseOverEntry) + style = MENUFONT_MOUSE; + fontPrint(e->name, e->r.x, e->r.y, style); + if(e->type == MENUVAR){ + int valw = fontGetLen(((MenuEntry_Var*)e)->getValWidth()); + ((MenuEntry_Var*)e)->getValStr(val, 100); + fontPrint(val, e->r.x+this->r.w-valw, e->r.y, style); + } + } + i++; + } + + if(this->isScrollingUp) + drawArrow(this->scrollUpR, -1, isMouseInRect(this->scrollUpR)); + if(this->isScrollingDown) + drawArrow(this->scrollDownR, 1, isMouseInRect(this->scrollDownR)); + + if(this->selectedEntry && this->selectedEntry->type == MENUSUB) + ((MenuEntry_Sub*)this->selectedEntry)->submenu->draw(); +} + +Menu::~Menu(void) +{ + MenuEntry *e, *next; + for(e = entries; e; e = next){ + next = e->next; + delete e; + } + memset(this, 0, sizeof(Menu)); +} + +Menu* +findMenu(const char *name) +{ + Menu *m; + MenuEntry *e; + char *tmppath = strdup(name); + char *next, *curname; + + curname = tmppath; + next = curname; + + m = &toplevel; + while(*next){ + curname = next; + while(*next){ + if(*next == '|'){ + *next++ = '\0'; + break; + } + next++; + } + e = m->findEntry(curname); + if(e){ + // return an error if the entry exists but isn't a menu + if(e->type != MENUSUB){ + free(tmppath); + return nil; + } + m = ((MenuEntry_Sub*)e)->submenu; + }else{ + // Create submenus that don't exist yet + Menu *submenu = new Menu(); + submenu->parent = m; + MenuEntry *me = new MenuEntry_Sub(curname, submenu); + // Don't sort submenus outside the toplevel menu + if(m == &toplevel) + m->insertEntrySorted(me); + else + m->appendEntry(me); + m = submenu; + } + } + + free(tmppath); + return m; +} + +/* + * **************** + * debug menu + * **************** + */ + +static uint8 cursorPx[] = { +#include "cursor.inc" +}; + +static uint8 arrowPx[] = { +#include "arrow.inc" +}; + +void +DebugMenuInit(void) +{ + createMenuFont(); + + RwInt32 w, h, d, flags; + RwImage *img = RwImageCreate(16, 16, 32); + assert(img); + RwImageSetPixels(img, cursorPx); + RwImageSetStride(img, RwImageGetWidth(img)*4); + RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags); + cursor = RwRasterCreate(w, h, d, flags); + cursor = RwRasterSetFromImage(cursor, img); + assert(cursor); + RwImageDestroy(img); + + img = RwImageCreate(32, 16, 32); + assert(img); + RwImageSetPixels(img, arrowPx); + RwImageSetStride(img, RwImageGetWidth(img)*4); + RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags); + arrow = RwRasterCreate(w, h, d, flags); + arrow = RwRasterSetFromImage(arrow, img); + assert(arrow); + RwImageDestroy(img); + + + menuInitialized = true; +} + +void +DebugMenuShutdown(void) +{ + if(menuInitialized){ + destroyMenuFont(); + RwRasterDestroy(cursor); + cursor = nil; + RwRasterDestroy(arrow); + arrow = nil; + + toplevel.~Menu(); + new (&toplevel) Menu(); + + activeMenu = &toplevel; + deepestMenu = &toplevel; + mouseOverMenu = nil; + mouseOverEntry = nil; + } + menuInitialized = false; +} + +void +processInput(void) +{ + int shift = KEYDOWN(rsRSHIFT) || KEYDOWN(rsLSHIFT); +#define X(var, keycode) var = KEYJUSTDOWN(keycode); + MUHKEYS +#undef X + + // Implement auto-repeat +#define X(var, keycode) \ + if(var){ \ + repeattime = downtime = CTimer::GetTimeInMilliseconds(); \ + lastkeydown = keycode; \ + keyptr = &var; \ + } + MUHKEYS +#undef X + if(lastkeydown){ + if(KEYDOWN(lastkeydown)){ + int curtime = CTimer::GetTimeInMilliseconds(); + if(curtime - downtime > REPEATDELAY){ + if(curtime - repeattime > REPEATINTERVAL){ + repeattime = curtime; + *keyptr = 1; + } + } + }else{ + lastkeydown = 0; + } + } + + // Also for mouse buttons +#define X(var, num) \ + if(var){ \ + repeattime = downtime = CTimer::GetTimeInMilliseconds(); \ + lastbuttondown = num; \ + buttonptr = &var; \ + } + MUHBUTTONS +#undef X + if(lastbuttondown){ + if(buttondown[lastbuttondown-1]){ + int curtime = CTimer::GetTimeInMilliseconds(); + if(curtime - downtime > REPEATDELAY){ + if(curtime - repeattime > REPEATINTERVAL){ + repeattime = curtime; + *buttonptr = 1; + } + } + }else{ + lastbuttondown = 0; + } + } + + // Walk through all visible menus and figure out which one the mouse is over + mouseOverMenu = nil; + mouseOverEntry = nil; + Menu *menu; + for(menu = deepestMenu; menu; menu = menu->parent) + if(isMouseInRect(menu->r)){ + mouseOverMenu = menu; + break; + } + if(mouseOverMenu){ + // Walk all visibile entries and figure out which one the mouse is over + MenuEntry *e; + int i = 0; + for(e = mouseOverMenu->entries; e; e = e->next){ + if(i >= mouseOverMenu->scrollStart+mouseOverMenu->numVisible) + break; + if(i >= mouseOverMenu->scrollStart){ + RwRect r = e->r; + r.w = mouseOverMenu->r.w; // span the whole menu + if(isMouseInRect(r)){ + mouseOverEntry = e; + break; + } + } + i++; + } + if(mouseOverMenu->isScrollingUp && isMouseInRect(mouseOverMenu->scrollUpR)){ + mouseOverEntry = &scrollUpEntry; + mouseOverEntry->r = mouseOverMenu->scrollUpR; + mouseOverEntry->menu = mouseOverMenu; + mouseOverEntry->type = MENUSCROLL; + } + if(mouseOverMenu->isScrollingDown && isMouseInRect(mouseOverMenu->scrollDownR)){ + mouseOverEntry = &scrollDownEntry; + mouseOverEntry->r = mouseOverMenu->scrollDownR; + mouseOverEntry->menu = mouseOverMenu; + mouseOverEntry->type = MENUSCROLL; + } + } + + if(pgupjustdown) + activeMenu->scroll(shift ? -5 : -1); + if(pgdnjustdown) + activeMenu->scroll(shift ? 5 : 1); + if(downjustdown) + activeMenu->changeSelection(activeMenu->selection + (shift ? 5 : 1)); + if(upjustdown) + activeMenu->changeSelection(activeMenu->selection - (shift ? 5 : 1)); + + if(CPad::NewMouseControllerState.WHEELUP){ + if(mouseOverMenu) + activeMenu = mouseOverMenu; + activeMenu->scroll(shift ? -5 : -1); + } + if(CPad::NewMouseControllerState.WHEELDN){ + if(mouseOverMenu) + activeMenu = mouseOverMenu; + activeMenu->scroll(shift ? 5 : 1); + } + + if(mouseOverEntry == &scrollUpEntry){ + if(button1justdown){ + activeMenu = mouseOverEntry->menu; + activeMenu->scroll(shift ? -5 : -1); + } + } + if(mouseOverEntry == &scrollDownEntry){ + if(button1justdown){ + activeMenu = mouseOverEntry->menu; + activeMenu->scroll(shift ? 5 : 1); + } + } + + // Have to call this before processInput below because menu entry can change + if((button1justdown || button3justdown) && mouseOverEntry){ + activeMenu = mouseOverEntry->menu; + activeMenu->changeSelection(mouseOverEntry); + } + if(KEYJUSTDOWN(rsENTER)){ + if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUSUB) + activeMenu = ((MenuEntry_Sub*)activeMenu->selectedEntry)->submenu; + }else if(KEYJUSTDOWN(rsBACKSP)){ + if(activeMenu->parent) + activeMenu = activeMenu->parent; + }else{ + if(mouseOverEntry && mouseOverEntry->type == MENUVAR) + ((MenuEntry_Var*)mouseOverEntry)->processInput(true, mouseOverEntry == activeMenu->selectedEntry); + if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUVAR && + mouseOverEntry != activeMenu->selectedEntry) + ((MenuEntry_Var*)activeMenu->selectedEntry)->processInput(false, true); + } +} + +void +updateMouse(void) +{ + CPad *pad = CPad::GetPad(0); + int dirX = 1; + int dirY = 1; + + if(MousePointerStateHelper.bInvertHorizontally) dirX = -1; + if(MousePointerStateHelper.bInvertVertically) dirY = -1; + + mouseX += pad->NewMouseControllerState.x*dirX; + mouseY += pad->NewMouseControllerState.y*dirY; + + if(mouseX < 0.0f) mouseX = 0.0f; + if(mouseY < 0.0f) mouseY = 0.0f; + if(mouseX >= screenWidth) mouseX = screenWidth; + if(mouseY >= screenHeight) mouseY = screenHeight; + + button1justdown = pad->NewMouseControllerState.LMB && !pad->OldMouseControllerState.LMB; + button2justdown = pad->NewMouseControllerState.MMB && !pad->OldMouseControllerState.MMB; + button3justdown = pad->NewMouseControllerState.RMB && !pad->OldMouseControllerState.RMB; + buttondown[0] = pad->NewMouseControllerState.LMB; + buttondown[1] = pad->NewMouseControllerState.MMB; + buttondown[2] = pad->NewMouseControllerState.RMB; + + // Zero the mouse position so the camera won't move + pad->NewMouseControllerState.x = 0.0f; + pad->NewMouseControllerState.y = 0.0f; +} + +void +DebugMenuProcess(void) +{ + // We only process some input here + + CPad *pad = CPad::GetPad(0); + if(CTRLJUSTDOWN('M')) + menuOn = !menuOn; + if(!menuOn) + return; + + pad->DisablePlayerControls = 1; + // TODO: this could happen earlier + if(!menuInitialized) + DebugMenuInit(); + updateMouse(); + +} + +void +DebugMenuRender(void) +{ + if(!menuOn) + return; + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, 0); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, 0); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, 0); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + + RwCamera *cam = RwCameraGetCurrentCamera(); + screenWidth = RwRasterGetWidth(RwCameraGetRaster(cam)); + screenHeight = RwRasterGetHeight(RwCameraGetRaster(cam)); + +// if(screenHeight > 1080) +// fontscale = 2; +// else + fontscale = 1; + + Pt sz; + sz = fontPrint("Debug Menu", firstBorder*fontscale+30, topBorder, 0); + + toplevel.r.x = firstBorder*fontscale; + toplevel.r.y = topBorder + sz.y + 10; + toplevel.r.w = minwidth; // update menu will expand + toplevel.r.h = screenHeight - 10 - toplevel.r.y; + toplevel.update(); + toplevel.draw(); + processInput(); + RtCharsetBufferFlush(); + + drawMouse(); +} + + + +void +drawArrow(RwRect r, int direction, int style) +{ + static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 }; + static RwIm2DVertex arrowVerts[4]; + + RwCamera *cam = RwCameraGetCurrentCamera(); + float recipz = 1.0f/RwCameraGetNearClipPlane(cam); + + int width = RwRasterGetWidth(arrow); + int height = RwRasterGetHeight(arrow); + + int left = r.x + (r.w - width)/2; + int right = left + width; + int top = r.y; + int bottom = r.y+r.h; + + float umin = HALFPX / width; + float vmin = HALFPX / height; + float umax = (width + HALFPX) / width; + float vmax = (height + HALFPX) / height; + if(direction < 0){ + vmin = (height - HALFPX) / height; + vmax = -HALFPX / height; + } + + if(style){ + RwIm2DVertexSetScreenX(&arrowVerts[0], r.x); + RwIm2DVertexSetScreenY(&arrowVerts[0], r.y-1); + RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[0], 132, 132, 132, 255); + + RwIm2DVertexSetScreenX(&arrowVerts[1], r.x+r.w); + RwIm2DVertexSetScreenY(&arrowVerts[1], r.y-1); + RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[1], 132, 132, 132, 255); + + RwIm2DVertexSetScreenX(&arrowVerts[2], r.x); + RwIm2DVertexSetScreenY(&arrowVerts[2], r.y+r.h+1); + RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[2], 132, 132, 132, 255); + + RwIm2DVertexSetScreenX(&arrowVerts[3], r.x+r.w); + RwIm2DVertexSetScreenY(&arrowVerts[3], r.y+r.h+1); + RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[3], 132, 132, 132, 255); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6); + } + + + RwIm2DVertexSetScreenX(&arrowVerts[0], left); + RwIm2DVertexSetScreenY(&arrowVerts[0], top); + RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[0], 255, 255, 255, 255); + RwIm2DVertexSetU(&arrowVerts[0], umin, recipz); + RwIm2DVertexSetV(&arrowVerts[0], vmin, recipz); + + RwIm2DVertexSetScreenX(&arrowVerts[1], right); + RwIm2DVertexSetScreenY(&arrowVerts[1], top); + RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[1], 255, 255, 255, 255); + RwIm2DVertexSetU(&arrowVerts[1], umax, recipz); + RwIm2DVertexSetV(&arrowVerts[1], vmin, recipz); + + RwIm2DVertexSetScreenX(&arrowVerts[2], left); + RwIm2DVertexSetScreenY(&arrowVerts[2], bottom); + RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[2], 255, 255, 255, 255); + RwIm2DVertexSetU(&arrowVerts[2], umin, recipz); + RwIm2DVertexSetV(&arrowVerts[2], vmax, recipz); + + RwIm2DVertexSetScreenX(&arrowVerts[3], right); + RwIm2DVertexSetScreenY(&arrowVerts[3], bottom); + RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz); + RwIm2DVertexSetIntRGBA(&arrowVerts[3], 255, 255, 255, 255); + RwIm2DVertexSetU(&arrowVerts[3], umax, recipz); + RwIm2DVertexSetV(&arrowVerts[3], vmax, recipz); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, arrow); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6); +} + +void +drawMouse(void) +{ + static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 }; + static RwIm2DVertex vertices[4]; + RwIm2DVertex *vert; + RwCamera *cam; + cam = RwCameraGetCurrentCamera(); + float x = mouseX; + float y = mouseY; + float w = RwRasterGetWidth(cursor); + float h = RwRasterGetHeight(cursor); + float recipz = 1.0f/RwCameraGetNearClipPlane(cam); + + float umin = HALFPX / w; + float vmin = HALFPX / h; + float umax = (w + HALFPX) / w; + float vmax = (h + HALFPX) / h; + + vert = vertices; + RwIm2DVertexSetScreenX(vert, x); + RwIm2DVertexSetScreenY(vert, y); + RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(vert, recipz); + RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255); + RwIm2DVertexSetU(vert, umin, recipz); + RwIm2DVertexSetV(vert, vmin, recipz); + vert++; + + RwIm2DVertexSetScreenX(vert, x+w); + RwIm2DVertexSetScreenY(vert, y); + RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(vert, recipz); + RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255); + RwIm2DVertexSetU(vert, umax, recipz); + RwIm2DVertexSetV(vert, vmin, recipz); + vert++; + + RwIm2DVertexSetScreenX(vert, x); + RwIm2DVertexSetScreenY(vert, y+h); + RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(vert, recipz); + RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255); + RwIm2DVertexSetU(vert, umin, recipz); + RwIm2DVertexSetV(vert, vmax, recipz); + vert++; + + RwIm2DVertexSetScreenX(vert, x+w); + RwIm2DVertexSetScreenY(vert, y+h); + RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(vert, recipz); + RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255); + RwIm2DVertexSetU(vert, umax, recipz); + RwIm2DVertexSetV(vert, vmax, recipz); + vert++; + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, cursor); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, vertices, 4, indices, 6); +} + + + + +/* + * Generate interfaces + */ + + +#define X(NAME, TYPE, unused1, unused2) \ +MenuEntry* \ +DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings) \ +{ \ + Menu *m = findMenu(path); \ + if(m == nil) \ + return nil; \ + MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound, strings); \ + m->appendEntry(e); \ + return e; \ +} +INTTYPES +#undef X + +#define X(NAME, TYPE, unused1, unused2) \ +MenuEntry* \ +DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound) \ +{ \ + Menu *m = findMenu(path); \ + if(m == nil) \ + return nil; \ + MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound); \ + m->appendEntry(e); \ + return e; \ +} +FLOATTYPES +#undef X + +MenuEntry* \ +DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc) +{ + Menu *m = findMenu(path); + if(m == nil) + return nil; + MenuEntry *e = new MenuEntry_Cmd(name, triggerFunc); + m->appendEntry(e); + return e; +} + +void +DebugMenuEntrySetWrap(MenuEntry *e, bool wrap) +{ + if(e && e->type == MENUVAR) + ((MenuEntry_Var*)e)->wrapAround = wrap; +} + +void +DebugMenuEntrySetStrings(MenuEntry *e, const char **strings) +{ + if(e && e->type == MENUVAR_INT) + ((MenuEntry_Int*)e)->setStrings(strings); +} + +void +DebugMenuEntrySetAddress(MenuEntry *e, void *addr) +{ + if(e && e->type == MENUVAR){ + MenuEntry_Var *ev = (MenuEntry_Var*)e; + // HACK - we know the variable field is at the same address + // for all int/float classes. let's hope it stays that way. + if(ev->vartype = MENUVAR_INT) + ((MenuEntry_Int32*)e)->variable = (int32*)addr; + else if(ev->vartype = MENUVAR_FLOAT) + ((MenuEntry_Float32*)e)->variable = (float*)addr; + } +} +#endif \ No newline at end of file diff --git a/src/extras/debugmenu.h b/src/extras/debugmenu.h new file mode 100644 index 0000000..45b65d0 --- /dev/null +++ b/src/extras/debugmenu.h @@ -0,0 +1,204 @@ +#pragma once + +#ifdef DEBUGMENU + +// Tweaking stuff for debugmenu +#define TWEAKPATH ___tw___TWEAKPATH +#define SETTWEAKPATH(path) static const char *___tw___TWEAKPATH = path; +#define TWEAKFUNC(v) static CTweakFunc CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), TWEAKPATH); +#define TWEAKFUNCN(v, name) static CTweakFunc CONCAT(___tw___tweak, __COUNTER__)(&v, name, TWEAKPATH); +#define TWEAKBOOL(v) static CTweakBool CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), TWEAKPATH); +#define TWEAKBOOLN(v, name) static CTweakBool CONCAT(___tw___tweak, __COUNTER__)(&v, name, TWEAKPATH); +#define TWEAKINT32(v, lower, upper, step) static CTweakInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKINT32N(v, lower, upper, step, name) static CTweakInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKUINT32(v, lower, upper, step) static CTweakUInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKUINT32N(v, lower, upper, step, name) static CTweakUInt32 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKINT16(v, lower, upper, step) static CTweakInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKINT16N(v, lower, upper, step, name) static CTweakInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKUINT16(v, lower, upper, step) static CTweakUInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKUINT16N(v, lower, upper, step, name) static CTweakUInt16 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKINT8(v, lower, upper, step) static CTweakInt8 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKINT8N(v, lower, upper, step, name) static CTweakInt8 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKUINT8(v, lower, upper, step) static CTweakUInt8 CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKUINT8N(v, lower, upper, step, name) static CTweakUInt8 CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKFLOAT(v, lower, upper, step) static CTweakFloat CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, step, TWEAKPATH); +#define TWEAKFLOATN(v, lower, upper, step, name) static CTweakFloat CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, step, TWEAKPATH); +#define TWEAKSWITCH(v, lower, upper, str, f) static CTweakSwitch CONCAT(___tw___tweak, __COUNTER__)(&v, STR(v), lower, upper, str, f, TWEAKPATH); +#define TWEAKSWITCHN(v, lower, upper, str, f, name) static CTweakSwitch CONCAT(___tw___tweak, __COUNTER__)(&v, name, lower, upper, str, f, TWEAKPATH); + +// interface +class CTweakVar +{ +public: + virtual void AddDBG(const char* path) = 0; +}; + +class CTweakVars +{ +public: + static void Add(CTweakVar* var); + static void AddDBG(const char* path); +}; + +class CTweakFunc : public CTweakVar +{ + const char* m_pPath, * m_pVarName; + void (*m_pFunc)(); +public: + CTweakFunc(void (*pFunc)(), const char* strName, const char* strPath) : + m_pPath(strPath), m_pVarName(strName), m_pFunc(pFunc) + { + CTweakVars::Add(this); + } + + void AddDBG(const char* path); +}; + +class CTweakBool : public CTweakVar +{ + const char* m_pPath, * m_pVarName; + bool* m_pBoolVar; +public: + CTweakBool(bool* pBool, const char* strName, const char* strPath) : + m_pPath(strPath), m_pVarName(strName), m_pBoolVar(pBool) + { + CTweakVars::Add(this); + } + + void AddDBG(const char* path); +}; + +class CTweakSwitch : public CTweakVar +{ + const char* m_pPath, * m_pVarName; + void* m_pIntVar; + int32 m_nMin, m_nMax; + const char** m_aStr; + void (*m_pFunc)(); +public: + CTweakSwitch(void* pInt, const char* strName, int32 nMin, int32 nMax, const char** aStr, + void (*pFunc)(), const char* strPath) + : m_pPath(strPath), m_pVarName(strName), m_pIntVar(pInt), m_nMin(nMin), m_nMax(nMax), + m_aStr(aStr) + { + CTweakVars::Add(this); + } + + void AddDBG(const char* path); +}; + +#define _TWEEKCLASS(name, type) \ + class name : public CTweakVar \ + { \ + public: \ + const char *m_pPath, *m_pVarName; \ + type *m_pIntVar, m_nLoawerBound, m_nUpperBound, m_nStep; \ + \ + name(type *pInt, const char *strName, type nLower, type nUpper, type nStep, \ + const char *strPath) \ + : m_pPath(strPath), m_pVarName(strName), m_pIntVar(pInt), \ + m_nLoawerBound(nLower), m_nUpperBound(nUpper), m_nStep(nStep) \ + \ + { \ + CTweakVars::Add(this); \ + } \ + \ + void AddDBG(const char *path); \ + }; + +_TWEEKCLASS(CTweakInt8, int8); +_TWEEKCLASS(CTweakUInt8, uint8); +_TWEEKCLASS(CTweakInt16, int16); +_TWEEKCLASS(CTweakUInt16, uint16); +_TWEEKCLASS(CTweakInt32, int32); +_TWEEKCLASS(CTweakUInt32, uint32); +_TWEEKCLASS(CTweakFloat, float); + +#undef _TWEEKCLASS + +typedef void (*TriggerFunc)(void); + +struct Menu; + +struct MenuEntry +{ + int type; + const char *name; + MenuEntry *next; + RwRect r; + Menu *menu; + + MenuEntry(const char *name); + virtual ~MenuEntry(void) { free((void*)name); } +}; + +typedef MenuEntry DebugMenuEntry; + +MenuEntry *DebugMenuAddInt8(const char *path, const char *name, int8 *ptr, TriggerFunc triggerFunc, int8 step, int8 lowerBound, int8 upperBound, const char **strings); +MenuEntry *DebugMenuAddInt16(const char *path, const char *name, int16 *ptr, TriggerFunc triggerFunc, int16 step, int16 lowerBound, int16 upperBound, const char **strings); +MenuEntry *DebugMenuAddInt32(const char *path, const char *name, int32 *ptr, TriggerFunc triggerFunc, int32 step, int32 lowerBound, int32 upperBound, const char **strings); +MenuEntry *DebugMenuAddInt64(const char *path, const char *name, int64 *ptr, TriggerFunc triggerFunc, int64 step, int64 lowerBound, int64 upperBound, const char **strings); +MenuEntry *DebugMenuAddUInt8(const char *path, const char *name, uint8 *ptr, TriggerFunc triggerFunc, uint8 step, uint8 lowerBound, uint8 upperBound, const char **strings); +MenuEntry *DebugMenuAddUInt16(const char *path, const char *name, uint16 *ptr, TriggerFunc triggerFunc, uint16 step, uint16 lowerBound, uint16 upperBound, const char **strings); +MenuEntry *DebugMenuAddUInt32(const char *path, const char *name, uint32 *ptr, TriggerFunc triggerFunc, uint32 step, uint32 lowerBound, uint32 upperBound, const char **strings); +MenuEntry *DebugMenuAddUInt64(const char *path, const char *name, uint64 *ptr, TriggerFunc triggerFunc, uint64 step, uint64 lowerBound, uint64 upperBound, const char **strings); +MenuEntry *DebugMenuAddFloat32(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound); +MenuEntry *DebugMenuAddFloat64(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound); +MenuEntry *DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc); +void DebugMenuEntrySetWrap(MenuEntry *e, bool wrap); +void DebugMenuEntrySetStrings(MenuEntry *e, const char **strings); +void DebugMenuEntrySetAddress(MenuEntry *e, void *addr); +void DebugMenuInit(void); +void DebugMenuShutdown(void); +void DebugMenuProcess(void); +void DebugMenuRender(void); + + +// Some overloads for simplicity +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings) +{ return DebugMenuAddInt8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings) +{ return DebugMenuAddInt16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings) +{ return DebugMenuAddInt32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings) +{ return DebugMenuAddInt64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings) +{ return DebugMenuAddUInt8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings) +{ return DebugMenuAddUInt16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings) +{ return DebugMenuAddUInt32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings) +{ return DebugMenuAddUInt64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound) +{ return DebugMenuAddFloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } +inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound) +{ return DebugMenuAddFloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); } + +inline DebugMenuEntry *DebugMenuAddVarBool32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc) +{ + static const char *boolstr[] = { "Off", "On" }; + DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr); + DebugMenuEntrySetWrap(e, true); + return e; +} +inline DebugMenuEntry *DebugMenuAddVarBool16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc) +{ + static const char *boolstr[] = { "Off", "On" }; + DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr); + DebugMenuEntrySetWrap(e, true); + return e; +} +inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc) +{ + static const char *boolstr[] = { "Off", "On" }; + DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr); + DebugMenuEntrySetWrap(e, true); + return e; +} +inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, bool *ptr, TriggerFunc triggerFunc) +{ + return DebugMenuAddVarBool8(path, name, (int8_t*)ptr, triggerFunc); +} +#endif \ No newline at end of file diff --git a/src/extras/frontendoption.cpp b/src/extras/frontendoption.cpp new file mode 100644 index 0000000..5d388bf --- /dev/null +++ b/src/extras/frontendoption.cpp @@ -0,0 +1,182 @@ +#include "common.h" + +#ifdef CUSTOM_FRONTEND_OPTIONS +#include "Frontend.h" +#include "Text.h" + +int lastOgScreen = MENUPAGES; // means no new pages + +int numCustomFrontendOptions = 0; +int numCustomFrontendScreens = 0; + +int optionCursor = -2; +int currentMenu; +bool optionOverwrite = false; + +void ChangeScreen(int screen, int option, bool fadeIn) +{ + FrontEndMenuManager.m_nPrevScreen = FrontEndMenuManager.m_nCurrScreen; + FrontEndMenuManager.m_nCurrScreen = screen; + FrontEndMenuManager.m_nCurrOption = option; + if (fadeIn) + FrontEndMenuManager.m_nMenuFadeAlpha = 0; +} + +void GoBack(bool fadeIn) +{ + int screen = !FrontEndMenuManager.m_bGameNotLoaded ? + aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[1] : aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[0]; + int option = FrontEndMenuManager.GetPreviousPageOption(); + + FrontEndMenuManager.ThingsToDoBeforeGoingBack(); + + ChangeScreen(screen, option, fadeIn); +} + +uint8 +GetNumberOfMenuOptions(int screen) +{ + uint8 Rows = 0; + for (int i = 0; i < NUM_MENUROWS; i++) { + if (aScreens[screen].m_aEntries[i].m_Action == MENUACTION_NOTHING) + break; + + ++Rows; + } + return Rows; +} + +uint8 +GetLastMenuScreen() +{ + int8 page = -1; + for (int i = 0; i < MENUPAGES; i++) { + if (strcmp(aScreens[i].m_ScreenName, "") == 0 && aScreens[i].m_PreviousPage[0] == MENUPAGE_NONE) + break; + + ++page; + } + return page; +} + +int8 RegisterNewScreen(const char *name, int prevPage, ReturnPrevPageFunc returnPrevPageFunc) +{ + if (lastOgScreen == MENUPAGES) + lastOgScreen = GetLastMenuScreen(); + + numCustomFrontendScreens++; + int id = lastOgScreen + numCustomFrontendScreens; + assert(id < MENUPAGES && "No room for new custom frontend screens! Increase MENUPAGES"); + strncpy(aScreens[id].m_ScreenName, name, 8); + aScreens[id].m_PreviousPage[0] = aScreens[id].m_PreviousPage[1] = prevPage; + aScreens[id].returnPrevPageFunc = returnPrevPageFunc; + return id; +} + +int8 RegisterNewOption() +{ + numCustomFrontendOptions++; + uint8 numOptions = GetNumberOfMenuOptions(currentMenu); + uint8 curIdx; + if (optionCursor < 0) { + optionCursor = curIdx = numOptions + optionCursor + 1; + } else + curIdx = optionCursor; + + if (!optionOverwrite) { + if (aScreens[currentMenu].m_aEntries[curIdx].m_Action != MENUACTION_NOTHING) { + for (int i = numOptions - 1; i >= curIdx; i--) { + memcpy(&aScreens[currentMenu].m_aEntries[i + 1], &aScreens[currentMenu].m_aEntries[i], sizeof(CMenuScreenCustom::CMenuEntry)); + } + } + } + optionCursor++; + return curIdx; +} + +void FrontendOptionSetCursor(int screen, int8 option, bool overwrite) +{ + currentMenu = screen; + optionCursor = option; + optionOverwrite = overwrite; +} + +void FrontendOptionAddBuiltinAction(const char* gxtKey, int action, int targetMenu, int saveSlot) { + int8 screenOptionOrder = RegisterNewOption(); + + CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder]; + + // We can't use custom text on those :shrug: + switch (action) { + case MENUACTION_SCREENRES: + strcpy(option.m_EntryName, "FED_RES"); + break; + case MENUACTION_AUDIOHW: + strcpy(option.m_EntryName, "FEA_3DH"); + break; + default: + strncpy(option.m_EntryName, gxtKey, 8); + break; + } + option.m_Action = action; + option.m_SaveSlot = saveSlot; + option.m_TargetMenu = targetMenu; +} + +void FrontendOptionAddSelect(const char* gxtKey, const char** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, const char* saveCat, const char* saveKey, bool disableIfGameLoaded) +{ + int8 screenOptionOrder = RegisterNewOption(); + + CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder]; + option.m_Action = MENUACTION_CFO_SELECT; + strncpy(option.m_EntryName, gxtKey, 8); + option.m_CFOSelect = new CCFOSelect(); + option.m_CFOSelect->rightTexts = (char**)malloc(numRightTexts * sizeof(char*)); + memcpy(option.m_CFOSelect->rightTexts, rightTexts, numRightTexts * sizeof(char*)); + option.m_CFOSelect->numRightTexts = numRightTexts; + option.m_CFOSelect->value = var; + if (var) { + option.m_CFOSelect->displayedValue = *var; + option.m_CFOSelect->lastSavedValue = *var; + } + option.m_CFOSelect->saveCat = saveCat; + option.m_CFOSelect->save = saveKey; + option.m_CFOSelect->onlyApplyOnEnter = onlyApplyOnEnter; + option.m_CFOSelect->changeFunc = changeFunc; + option.m_CFOSelect->disableIfGameLoaded = disableIfGameLoaded; +} + +void FrontendOptionAddDynamic(const char* gxtKey, DrawFunc drawFunc, int8 *var, ButtonPressFunc buttonPressFunc, const char* saveCat, const char* saveKey) +{ + int8 screenOptionOrder = RegisterNewOption(); + + CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder]; + option.m_Action = MENUACTION_CFO_DYNAMIC; + strncpy(option.m_EntryName, gxtKey, 8); + option.m_CFODynamic = new CCFODynamic(); + option.m_CFODynamic->drawFunc = drawFunc; + option.m_CFODynamic->buttonPressFunc = buttonPressFunc; + option.m_CFODynamic->value = var; + option.m_CFODynamic->saveCat = saveCat; + option.m_CFODynamic->save = saveKey; +} + +uint8 FrontendScreenAdd(const char* gxtKey, eMenuSprites sprite, int prevPage, int columnWidth, int headerHeight, int lineHeight, + int8 font, float fontScaleX, float fontScaleY, int8 alignment, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc) { + + uint8 screenOrder = RegisterNewScreen(gxtKey, prevPage, returnPrevPageFunc); + + CCustomScreenLayout *screen = new CCustomScreenLayout(); + aScreens[screenOrder].layout = screen; + screen->sprite = sprite; + screen->columnWidth = columnWidth; + screen->headerHeight = headerHeight; + screen->lineHeight = lineHeight; + screen->font = font; + screen->fontScaleX = fontScaleX; + screen->fontScaleY = fontScaleY; + screen->alignment = alignment; + + return screenOrder; +} +#endif diff --git a/src/extras/frontendoption.h b/src/extras/frontendoption.h new file mode 100644 index 0000000..a571170 --- /dev/null +++ b/src/extras/frontendoption.h @@ -0,0 +1,93 @@ +#pragma once +#include "common.h" + +#ifdef CUSTOM_FRONTEND_OPTIONS + +// ! There are 2 ways to use CFO, +// 1st; by adding a new option to the array in MenuScreensCustom.cpp and passing attributes/CBs to it +// 2nd; by calling the functions listed at the bottom of this file. + +// -- Option types +// +// Static/select: You allocate the variable, pass it to function and game sets it from user input among the strings given to function, +// optionally you can add post-change event via ChangeFunc(only called on enter if onlyApplyOnEnter set, or set immediately) +// You can store the option in an INI file if you pass the key(as a char array) to corresponding parameter. +// +// Dynamic: Passing variable to function is only needed if you want to store it, otherwise you should do +// all the operations with ButtonPressFunc, this includes allocating the variable. +// Left-side text is passed while creating and static, but ofc right-side text is dynamic - +// you should return it in DrawFunc, which is called on every draw. +// +// Built-in action: As the name suggests, any action that game has built-in. But as an extra you can set the option text, + +// -- Returned via ButtonPressFunc() action param. +#define FEOPTION_ACTION_LEFT 0 +#define FEOPTION_ACTION_RIGHT 1 +#define FEOPTION_ACTION_SELECT 2 +#define FEOPTION_ACTION_FOCUSLOSS 3 + +// -- Passed via FrontendScreenAdd() +#define FESCREEN_CENTER 0 +#define FESCREEN_LEFT_ALIGN 1 +#define FESCREEN_RIGHT_ALIGN 2 + +// -- Callbacks + +// pretty much in everything I guess, and optional in all of them +typedef void (*ReturnPrevPageFunc)(); + +// for static options +typedef void (*ChangeFunc)(int8 before, int8 after); // called after updating the value. + // only called on enter if onlyApplyOnEnter set, otherwise called on every value change + +typedef void (*ChangeFuncFloat)(float before, float after); // called after updating the value. + +// for dynamic options +typedef wchar* (*DrawFunc)(bool* disabled, bool userHovering); // you must return a pointer for right text. + // you can also set *disabled if you want to gray it out. +typedef void (*ButtonPressFunc)(int8 action); // see FEOPTION_ACTIONs above + +// -- Internal things +void CustomFrontendOptionsPopulate(); +extern int lastOgScreen; // for reloading +extern int numCustomFrontendOptions; +extern int numCustomFrontendScreens; + +// -- To be used in ButtonPressFunc / ChangeFunc(this one would be weird): +void ChangeScreen(int screen, int option = 0, bool fadeIn = true); +void GoBack(bool fadeIn = true); + +uint8 GetNumberOfMenuOptions(int screen); + +// !!! We're now moved to MenuScreensCustom.cpp, which houses an array that keeps all original+custom options. +// But you can still use the APIs below, and manipulate aScreens while in game. + +// Limits: +// The code relies on that you won't use more then NUM_MENUROWS(18) options on one page, and won't exceed the MENUPAGES of pages. +// Also congrats if you can make 18 options visible at once. + +// Texts: +// All text parameters accept char[8] GXT key. + +// Execute direction: +// All of the calls below eventually manipulate the aScreens array, so keep in mind to add/replace options in order, +// i.e. don't set cursor to 8 first and then 3. + + +// -- Placing the cursor to append/overwrite option +// +// Done via FrontendOptionSetCursor(screen, position, overwrite = false), parameters explained below: +// Screen: as the name suggests. Also accepts the screen IDs returned from FrontendScreenAdd. +// Option: if positive, next AddOption call will put the option to there and progress the cursor. +// if negative, cursor will be placed on bottom-(pos+1), so -1 means the very bottom, -2 means before the back button etc. +// Overwrite: Use to overwrite the options, not appending a new one. AddOption calls will still progress the cursor. + +void FrontendOptionSetCursor(int screen, int8 option, bool overwrite = false); + +// var is optional in AddDynamic, enables you to save them in an INI file(also needs passing char array to and saveCat saveKey param. obv), otherwise pass nil/0 +void FrontendOptionAddBuiltinAction(const char* gxtKey, int action, int targetMenu = MENUPAGE_NONE, int saveSlot = SAVESLOT_NONE); +void FrontendOptionAddSelect(const char* gxtKey, const char** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, const char* saveCat = nil, const char* saveKey = nil, bool disableIfGameLoaded = false); +void FrontendOptionAddDynamic(const char* gxtKey, DrawFunc rightTextDrawFunc, int8 *var, ButtonPressFunc buttonPressFunc, const char* saveCat = nil, const char* saveKey = nil); + +uint8 FrontendScreenAdd(const char* gxtKey, eMenuSprites sprite, int prevPage, int columnWidth, int headerHeight, int lineHeight, int8 font, float fontScaleX, float fontScaleY, int8 alignment, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc = nil); +#endif diff --git a/src/extras/ini.h b/src/extras/ini.h new file mode 100644 index 0000000..44dd3d5 --- /dev/null +++ b/src/extras/ini.h @@ -0,0 +1,761 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2018 Danijel Durakovic + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/////////////////////////////////////////////////////////////////////////////// +// +// /mINI/ v0.9.10 +// An INI file reader and writer for the modern age. +// +/////////////////////////////////////////////////////////////////////////////// +// +// A tiny utility library for manipulating INI files with a straightforward +// API and a minimal footprint. It conforms to the (somewhat) standard INI +// format - sections and keys are case insensitive and all leading and +// trailing whitespace is ignored. Comments are lines that begin with a +// semicolon. Trailing comments are allowed on section lines. +// +// Files are read on demand, upon which data is kept in memory and the file +// is closed. This utility supports lazy writing, which only writes changes +// and updates to a file and preserves custom formatting and comments. A lazy +// write invoked by a write() call will read the output file, find what +// changes have been made and update the file accordingly. If you only need to +// generate files, use generate() instead. Section and key order is preserved +// on read, write and insert. +// +/////////////////////////////////////////////////////////////////////////////// +// +// /* BASIC USAGE EXAMPLE: */ +// +// /* read from file */ +// mINI::INIFile file("myfile.ini"); +// mINI::INIStructure ini; +// file.read(ini); +// +// /* read value; gets a reference to actual value in the structure. +// if key or section don't exist, a new empty value will be created */ +// std::string& value = ini["section"]["key"]; +// +// /* read value safely; gets a copy of value in the structure. +// does not alter the structure */ +// std::string value = ini.get("section").get("key"); +// +// /* set or update values */ +// ini["section"]["key"] = "value"; +// +// /* set multiple values */ +// ini["section2"].set({ +// {"key1", "value1"}, +// {"key2", "value2"} +// }); +// +// /* write updates back to file, preserving comments and formatting */ +// file.write(ini); +// +// /* or generate a file (overwrites the original) */ +// file.generate(ini); +// +/////////////////////////////////////////////////////////////////////////////// +// +// Long live the INI file!!! +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MINI_INI_H_ +#define MINI_INI_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mINI +{ + namespace INIStringUtil + { + const char* const whitespaceDelimiters = " \t\n\r\f\v"; + inline void trim(std::string& str) + { + str.erase(str.find_last_not_of(whitespaceDelimiters) + 1); + str.erase(0, str.find_first_not_of(whitespaceDelimiters)); + } +#ifndef MINI_CASE_SENSITIVE + inline void toLower(std::string& str) + { + std::transform(str.begin(), str.end(), str.begin(), [](const char c) { + return static_cast(std::tolower(c)); + }); + } +#endif + inline void replace(std::string& str, std::string const& a, std::string const& b) + { + if (!a.empty()) + { + std::size_t pos = 0; + while ((pos = str.find(a, pos)) != std::string::npos) + { + str.replace(pos, a.size(), b); + pos += b.size(); + } + } + } +#ifdef _WIN32 + const char* const endl = "\r\n"; +#else + const char* const endl = "\n"; +#endif + }; + + template + class INIMap + { + private: + using T_DataIndexMap = std::unordered_map; + using T_DataItem = std::pair; + using T_DataContainer = std::vector; + using T_MultiArgs = typename std::vector>; + + T_DataIndexMap dataIndexMap; + T_DataContainer data; + + inline std::size_t setEmpty(std::string& key) + { + std::size_t index = data.size(); + dataIndexMap[key] = index; + data.emplace_back(key, T()); + return index; + } + + public: + using const_iterator = typename T_DataContainer::const_iterator; + + INIMap() { } + + INIMap(INIMap const& other) + { + std::size_t data_size = other.data.size(); + for (std::size_t i = 0; i < data_size; ++i) + { + auto const& key = other.data[i].first; + auto const& obj = other.data[i].second; + data.emplace_back(key, obj); + } + dataIndexMap = T_DataIndexMap(other.dataIndexMap); + } + + T& operator[](std::string key) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + bool hasIt = (it != dataIndexMap.end()); + std::size_t index = (hasIt) ? it->second : setEmpty(key); + return data[index].second; + } + T get(std::string key) const + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it == dataIndexMap.end()) + { + return T(); + } + return T(data[it->second].second); + } + bool has(std::string key) const + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + return (dataIndexMap.count(key) == 1); + } + void set(std::string key, T obj) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it != dataIndexMap.end()) + { + data[it->second].second = obj; + } + else + { + dataIndexMap[key] = data.size(); + data.emplace_back(key, obj); + } + } + void set(T_MultiArgs const& multiArgs) + { + for (auto const& it : multiArgs) + { + auto const& key = it.first; + auto const& obj = it.second; + set(key, obj); + } + } + bool remove(std::string key) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it != dataIndexMap.end()) + { + std::size_t index = it->second; + data.erase(data.begin() + index); + dataIndexMap.erase(it); + for (auto& it2 : dataIndexMap) + { + auto& vi = it2.second; + if (vi > index) + { + vi--; + } + } + return true; + } + return false; + } + void clear() + { + data.clear(); + dataIndexMap.clear(); + } + std::size_t size() const + { + return data.size(); + } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + }; + + using INIStructure = INIMap>; + + namespace INIParser + { + using T_ParseValues = std::pair; + + enum class PDataType : char + { + PDATA_NONE, + PDATA_COMMENT, + PDATA_SECTION, + PDATA_KEYVALUE, + PDATA_UNKNOWN + }; + + inline PDataType parseLine(std::string line, T_ParseValues& parseData) + { + parseData.first.clear(); + parseData.second.clear(); + INIStringUtil::trim(line); + if (line.empty()) + { + return PDataType::PDATA_NONE; + } + char firstCharacter = line[0]; + if (firstCharacter == ';') + { + return PDataType::PDATA_COMMENT; + } + if (firstCharacter == '[') + { + auto commentAt = line.find_first_of(';'); + if (commentAt != std::string::npos) + { + line = line.substr(0, commentAt); + } + auto closingBracketAt = line.find_last_of(']'); + if (closingBracketAt != std::string::npos) + { + auto section = line.substr(1, closingBracketAt - 1); + INIStringUtil::trim(section); + parseData.first = section; + return PDataType::PDATA_SECTION; + } + } + auto lineNorm = line; + INIStringUtil::replace(lineNorm, "\\=", " "); + auto equalsAt = lineNorm.find_first_of('='); + if (equalsAt != std::string::npos) + { + auto key = line.substr(0, equalsAt); + INIStringUtil::trim(key); + INIStringUtil::replace(key, "\\=", "="); + auto value = line.substr(equalsAt + 1); + INIStringUtil::trim(value); + parseData.first = key; + parseData.second = value; + return PDataType::PDATA_KEYVALUE; + } + return PDataType::PDATA_UNKNOWN; + } + }; + + class INIReader + { + public: + using T_LineData = std::vector; + using T_LineDataPtr = std::shared_ptr; + + private: + std::ifstream fileReadStream; + T_LineDataPtr lineData; + + T_LineData readFile() + { + std::string fileContents; + fileReadStream.seekg(0, std::ios::end); + fileContents.resize(fileReadStream.tellg()); + fileReadStream.seekg(0, std::ios::beg); + std::size_t fileSize = fileContents.size(); + fileReadStream.read(&fileContents[0], fileSize); + fileReadStream.close(); + T_LineData output; + if (fileSize == 0) + { + return output; + } + std::string buffer; + buffer.reserve(50); + for (std::size_t i = 0; i < fileSize; ++i) + { + char& c = fileContents[i]; + if (c == '\n') + { + output.emplace_back(buffer); + buffer.clear(); + continue; + } + if (c != '\0' && c != '\r') + { + buffer += c; + } + } + output.emplace_back(buffer); + return output; + } + + public: + INIReader(std::string const& filename, bool keepLineData = false) + { + fileReadStream.open(filename, std::ios::in | std::ios::binary); + if (keepLineData) + { + lineData = std::make_shared(); + } + } + ~INIReader() { } + + bool operator>>(INIStructure& data) + { + if (!fileReadStream.is_open()) + { + return false; + } + T_LineData fileLines = readFile(); + std::string section; + bool inSection = false; + INIParser::T_ParseValues parseData; + for (auto const& line : fileLines) + { + auto parseResult = INIParser::parseLine(line, parseData); + if (parseResult == INIParser::PDataType::PDATA_SECTION) + { + inSection = true; + data[section = parseData.first]; + } + else if (inSection && parseResult == INIParser::PDataType::PDATA_KEYVALUE) + { + auto const& key = parseData.first; + auto const& value = parseData.second; + data[section][key] = value; + } + if (lineData && parseResult != INIParser::PDataType::PDATA_UNKNOWN) + { + if (parseResult == INIParser::PDataType::PDATA_KEYVALUE && !inSection) + { + continue; + } + lineData->emplace_back(line); + } + } + return true; + } + T_LineDataPtr getLines() + { + return lineData; + } + }; + + class INIGenerator + { + private: + std::ofstream fileWriteStream; + + public: + bool prettyPrint = false; + + INIGenerator(std::string const& filename) + { + fileWriteStream.open(filename, std::ios::out | std::ios::binary); + } + ~INIGenerator() { } + + bool operator<<(INIStructure const& data) + { + if (!fileWriteStream.is_open()) + { + return false; + } + if (!data.size()) + { + return true; + } + auto it = data.begin(); + for (;;) + { + auto const& section = it->first; + auto const& collection = it->second; + fileWriteStream + << "[" + << section + << "]"; + if (collection.size()) + { + fileWriteStream << INIStringUtil::endl; + auto it2 = collection.begin(); + for (;;) + { + auto key = it2->first; + INIStringUtil::replace(key, "=", "\\="); + auto value = it2->second; + INIStringUtil::trim(value); + fileWriteStream + << key + << ((prettyPrint) ? " = " : "=") + << value; + if (++it2 == collection.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + } + } + if (++it == data.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + if (prettyPrint) + { + fileWriteStream << INIStringUtil::endl; + } + } + return true; + } + }; + + class INIWriter + { + private: + using T_LineData = std::vector; + using T_LineDataPtr = std::shared_ptr; + + std::string filename; + + T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, INIStructure& original) + { + T_LineData output; + INIParser::T_ParseValues parseData; + std::string sectionCurrent; + bool parsingSection = false; + bool continueToNextSection = false; + bool discardNextEmpty = false; + bool writeNewKeys = false; + std::size_t lastKeyLine = 0; + for (auto line = lineData->begin(); line != lineData->end(); ++line) + { + if (!writeNewKeys) + { + auto parseResult = INIParser::parseLine(*line, parseData); + if (parseResult == INIParser::PDataType::PDATA_SECTION) + { + if (parsingSection) + { + writeNewKeys = true; + parsingSection = false; + --line; + continue; + } + sectionCurrent = parseData.first; + if (data.has(sectionCurrent)) + { + parsingSection = true; + continueToNextSection = false; + discardNextEmpty = false; + output.emplace_back(*line); + lastKeyLine = output.size(); + } + else + { + continueToNextSection = true; + discardNextEmpty = true; + continue; + } + } + else if (parseResult == INIParser::PDataType::PDATA_KEYVALUE) + { + if (continueToNextSection) + { + continue; + } + if (data.has(sectionCurrent)) + { + auto& collection = data[sectionCurrent]; + auto const& key = parseData.first; + auto const& value = parseData.second; + if (collection.has(key)) + { + auto outputValue = collection[key]; + if (value == outputValue) + { + output.emplace_back(*line); + } + else + { + INIStringUtil::trim(outputValue); + auto lineNorm = *line; + INIStringUtil::replace(lineNorm, "\\=", " "); + auto equalsAt = lineNorm.find_first_of('='); + auto valueAt = lineNorm.find_first_not_of( + INIStringUtil::whitespaceDelimiters, + equalsAt + 1 + ); + std::string outputLine = line->substr(0, valueAt); + if (prettyPrint && equalsAt + 1 == valueAt) + { + outputLine += " "; + } + outputLine += outputValue; + output.emplace_back(outputLine); + } + lastKeyLine = output.size(); + } + } + } + else + { + if (discardNextEmpty && line->empty()) + { + discardNextEmpty = false; + } + else if (parseResult != INIParser::PDataType::PDATA_UNKNOWN) + { + output.emplace_back(*line); + } + } + } + if (writeNewKeys || std::next(line) == lineData->end()) + { + T_LineData linesToAdd; + if (data.has(sectionCurrent) && original.has(sectionCurrent)) + { + auto const& collection = data[sectionCurrent]; + auto const& collectionOriginal = original[sectionCurrent]; + for (auto const& it : collection) + { + auto key = it.first; + if (collectionOriginal.has(key)) + { + continue; + } + auto value = it.second; + INIStringUtil::replace(key, "=", "\\="); + INIStringUtil::trim(value); + linesToAdd.emplace_back( + key + ((prettyPrint) ? " = " : "=") + value + ); + } + } + if (!linesToAdd.empty()) + { + output.insert( + output.begin() + lastKeyLine, + linesToAdd.begin(), + linesToAdd.end() + ); + } + if (writeNewKeys) + { + writeNewKeys = false; + --line; + } + } + } + for (auto const& it : data) + { + auto const& section = it.first; + if (original.has(section)) + { + continue; + } + if (prettyPrint && output.size() > 0 && !output.back().empty()) + { + output.emplace_back(); + } + output.emplace_back("[" + section + "]"); + auto const& collection = it.second; + for (auto const& it2 : collection) + { + auto key = it2.first; + auto value = it2.second; + INIStringUtil::replace(key, "=", "\\="); + INIStringUtil::trim(value); + output.emplace_back( + key + ((prettyPrint) ? " = " : "=") + value + ); + } + } + return output; + } + + public: + bool prettyPrint = false; + + INIWriter(std::string const& filename) + : filename(filename) + { + } + ~INIWriter() { } + + bool operator<<(INIStructure& data) + { + struct stat buf; + bool fileExists = (stat(filename.c_str(), &buf) == 0); + if (!fileExists) + { + INIGenerator generator(filename); + generator.prettyPrint = prettyPrint; + return generator << data; + } + INIStructure originalData; + T_LineDataPtr lineData; + bool readSuccess = false; + { + INIReader reader(filename, true); + if ((readSuccess = reader >> originalData)) + { + lineData = reader.getLines(); + } + } + if (!readSuccess) + { + return false; + } + T_LineData output = getLazyOutput(lineData, data, originalData); + std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary); + if (fileWriteStream.is_open()) + { + if (output.size()) + { + auto line = output.begin(); + for (;;) + { + fileWriteStream << *line; + if (++line == output.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + } + } + return true; + } + return false; + } + }; + + class INIFile + { + private: + std::string filename; + + public: + INIFile(std::string const& filename) + : filename(filename) + { } + + ~INIFile() { } + + bool read(INIStructure& data) const + { + if (data.size()) + { + data.clear(); + } + if (filename.empty()) + { + return false; + } + INIReader reader(filename); + return reader >> data; + } + bool generate(INIStructure const& data, bool pretty = false) const + { + if (filename.empty()) + { + return false; + } + INIGenerator generator(filename); + generator.prettyPrint = pretty; + return generator << data; + } + bool write(INIStructure& data, bool pretty = false) const + { + if (filename.empty()) + { + return false; + } + INIWriter writer(filename); + writer.prettyPrint = pretty; + return writer << data; + } + }; +} + +#endif // MINI_INI_H_ diff --git a/src/extras/postfx.cpp b/src/extras/postfx.cpp new file mode 100644 index 0000000..425a22d --- /dev/null +++ b/src/extras/postfx.cpp @@ -0,0 +1,476 @@ +#define WITHD3D +#include "common.h" + +#ifdef EXTENDED_COLOURFILTER + +#ifndef LIBRW +#error "Need librw for EXTENDED_COLOURFILTER" +#endif + +#include "main.h" +#include "RwHelper.h" +#include "Camera.h" +#include "MBlur.h" +#include "postfx.h" + +RwRaster *CPostFX::pFrontBuffer; +RwRaster *CPostFX::pBackBuffer; +bool CPostFX::bJustInitialised; +int CPostFX::EffectSwitch = POSTFX_NORMAL; +bool CPostFX::MotionBlurOn = false; + +static RwIm2DVertex Vertex[4]; +static RwIm2DVertex Vertex2[4]; +static RwImVertexIndex Index[6] = { 0, 1, 2, 0, 2, 3 }; + +#ifdef RW_D3D9 +void *colourfilterIII_PS; +void *contrast_PS; +#endif +#ifdef RW_OPENGL +int32 u_blurcolor; +int32 u_contrastAdd; +int32 u_contrastMult; +rw::gl3::Shader *colourFilterIII; +rw::gl3::Shader *contrast; +#endif + +void +CPostFX::InitOnce(void) +{ +#ifdef RW_OPENGL + u_blurcolor = rw::gl3::registerUniform("u_blurcolor"); + u_contrastAdd = rw::gl3::registerUniform("u_contrastAdd"); + u_contrastMult = rw::gl3::registerUniform("u_contrastMult"); +#endif +} + +void +CPostFX::Open(RwCamera *cam) +{ + if(pFrontBuffer) + Close(); + + uint32 width = Pow(2.0f, int32(log2(RwRasterGetWidth (RwCameraGetRaster(cam))))+1); + uint32 height = Pow(2.0f, int32(log2(RwRasterGetHeight(RwCameraGetRaster(cam))))+1); + uint32 depth = RwRasterGetDepth(RwCameraGetRaster(cam)); + pFrontBuffer = RwRasterCreate(width, height, depth, rwRASTERTYPECAMERATEXTURE); + pBackBuffer = RwRasterCreate(width, height, depth, rwRASTERTYPECAMERATEXTURE); + bJustInitialised = true; + + float zero, xmax, ymax; + + if(RwRasterGetDepth(RwCameraGetRaster(cam)) == 16){ + zero = HALFPX; + xmax = width + HALFPX; + ymax = height + HALFPX; + }else{ + zero = -HALFPX; + xmax = width - HALFPX; + ymax = height - HALFPX; + } + + RwIm2DVertexSetScreenX(&Vertex[0], zero); + RwIm2DVertexSetScreenY(&Vertex[0], zero); + RwIm2DVertexSetScreenZ(&Vertex[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[0], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[1], zero); + RwIm2DVertexSetScreenY(&Vertex[1], ymax); + RwIm2DVertexSetScreenZ(&Vertex[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[1], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[1], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[1], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[2], xmax); + RwIm2DVertexSetScreenY(&Vertex[2], ymax); + RwIm2DVertexSetScreenZ(&Vertex[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[2], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[3], xmax); + RwIm2DVertexSetScreenY(&Vertex[3], zero); + RwIm2DVertexSetScreenZ(&Vertex[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[3], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[3], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[3], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, 255); + + + RwIm2DVertexSetScreenX(&Vertex2[0], zero + 2.0f); + RwIm2DVertexSetScreenY(&Vertex2[0], zero + 2.0f); + RwIm2DVertexSetScreenZ(&Vertex2[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex2[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex2[0], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex2[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex2[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex2[0], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex2[1], 2.0f); + RwIm2DVertexSetScreenY(&Vertex2[1], ymax + 2.0f); + RwIm2DVertexSetScreenZ(&Vertex2[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex2[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex2[1], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex2[1], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex2[1], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex2[1], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex2[2], xmax + 2.0f); + RwIm2DVertexSetScreenY(&Vertex2[2], ymax + 2.0f); + RwIm2DVertexSetScreenZ(&Vertex2[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex2[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex2[2], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex2[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex2[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex2[2], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex2[3], xmax + 2.0f); + RwIm2DVertexSetScreenY(&Vertex2[3], zero + 2.0f); + RwIm2DVertexSetScreenZ(&Vertex2[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex2[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex2[3], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex2[3], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex2[3], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex2[3], 255, 255, 255, 255); + + +#ifdef RW_D3D9 +#include "shaders/obj/colourfilterIII_PS.inc" + colourfilterIII_PS = rw::d3d::createPixelShader(colourfilterIII_PS_cso); +#include "shaders/obj/contrastPS.inc" + contrast_PS = rw::d3d::createPixelShader(contrastPS_cso); +#endif +#ifdef RW_OPENGL + using namespace rw::gl3; + { +#include "shaders/obj/im2d_vert.inc" +#include "shaders/obj/colourfilterIII_frag.inc" + const char *vs[] = { shaderDecl, header_vert_src, im2d_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, colourfilterIII_frag_src, nil }; + colourFilterIII = Shader::create(vs, fs); + assert(colourFilterIII); + } + + { +#include "shaders/obj/im2d_vert.inc" +#include "shaders/obj/contrast_frag.inc" + const char *vs[] = { shaderDecl, header_vert_src, im2d_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, contrast_frag_src, nil }; + contrast = Shader::create(vs, fs); + assert(contrast); + } + +#endif +} + +void +CPostFX::Close(void) +{ + if(pFrontBuffer){ + RwRasterDestroy(pFrontBuffer); + pFrontBuffer = nil; + } + if(pBackBuffer){ + RwRasterDestroy(pBackBuffer); + pBackBuffer = nil; + } +#ifdef RW_D3D9 + if(colourfilterIII_PS){ + rw::d3d::destroyPixelShader(colourfilterIII_PS); + colourfilterIII_PS = nil; + } + if(contrast_PS){ + rw::d3d::destroyPixelShader(contrast_PS); + contrast_PS = nil; + } +#endif +#ifdef RW_OPENGL + if(colourFilterIII){ + colourFilterIII->destroy(); + colourFilterIII = nil; + } + if(contrast){ + contrast->destroy(); + contrast = nil; + } +#endif +} + +void +CPostFX::RenderOverlayBlur(RwCamera *cam, int32 r, int32 g, int32 b, int32 a) +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, pFrontBuffer); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, a); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +} + +void +CPostFX::RenderOverlaySimple(RwCamera *cam, int32 r, int32 g, int32 b, int32 a) +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + r *= 0.6f; + g *= 0.6f; + b *= 0.6f; + a *= 0.6f; + + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, a); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +} + +void +CPostFX::RenderOverlaySniper(RwCamera *cam, int32 r, int32 g, int32 b, int32 a) +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, pFrontBuffer); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, 80); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, 80); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, 80); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, 80); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +} + +float CPostFX::Intensity = 1.0f; + +void +CPostFX::RenderOverlayShader(RwCamera *cam, int32 r, int32 g, int32 b, int32 a) +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, pBackBuffer); + + if(EffectSwitch == POSTFX_MOBILE){ + float mult[3], add[3]; + mult[0] = (r-64)/384.0f + 1.14f; + mult[1] = (g-64)/384.0f + 1.14f; + mult[2] = (b-64)/384.0f + 1.14f; + add[0] = r/1536.f; + add[1] = g/1536.f; + add[2] = b/1536.f; +#ifdef RW_D3D9 + rw::d3d::d3ddevice->SetPixelShaderConstantF(10, mult, 1); + rw::d3d::d3ddevice->SetPixelShaderConstantF(11, add, 1); + + rw::d3d::im2dOverridePS = contrast_PS; +#endif +#ifdef RW_OPENGL + rw::gl3::im2dOverrideShader = contrast; + contrast->use(); + glUniform3fv(contrast->uniformLocations[u_contrastMult], 1, mult); + glUniform3fv(contrast->uniformLocations[u_contrastAdd], 1, add); +#endif + }else{ + float f = Intensity; + float blurcolors[4]; + blurcolors[0] = r/255.0f; + blurcolors[1] = g/255.0f; + blurcolors[2] = b/255.0f; + blurcolors[3] = a*f/255.0f; +#ifdef RW_D3D9 + rw::d3d::d3ddevice->SetPixelShaderConstantF(10, blurcolors, 1); + rw::d3d::im2dOverridePS = colourfilterIII_PS; +#endif +#ifdef RW_OPENGL + rw::gl3::im2dOverrideShader = colourFilterIII; + colourFilterIII->use(); + glUniform4fv(colourFilterIII->uniformLocations[u_blurcolor], 1, blurcolors); +#endif + } + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +#ifdef RW_D3D9 + rw::d3d::im2dOverridePS = nil; +#endif +#ifdef RW_OPENGL + rw::gl3::im2dOverrideShader = nil; +#endif +} + +void +CPostFX::RenderMotionBlur(RwCamera *cam, uint32 blur) +{ + if(blur == 0) + return; + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, pFrontBuffer); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, blur); + RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, blur); + RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, blur); + RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, blur); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); +} + +bool +CPostFX::NeedBackBuffer(void) +{ + // Current frame -- needed for non-blur effect + switch(EffectSwitch){ + case POSTFX_OFF: + // no actual rendering here + return false; + case POSTFX_SIMPLE: + return false; + case POSTFX_NORMAL: + if(MotionBlurOn) + return false; + else + return true; + case POSTFX_MOBILE: + return true; + } + return false; +} + +bool +CPostFX::NeedFrontBuffer(int32 type) +{ + // Last frame -- needed for motion blur + if(MotionBlurOn) + return true; + if(type == MOTION_BLUR_SNIPER) + return true; + + return false; +} + +void +CPostFX::GetBackBuffer(RwCamera *cam) +{ + RwRasterPushContext(pBackBuffer); + RwRasterRenderFast(RwCameraGetRaster(cam), 0, 0); + RwRasterPopContext(); +} + +void +CPostFX::Render(RwCamera *cam, uint32 red, uint32 green, uint32 blue, uint32 blur, int32 type, uint32 bluralpha) +{ + switch(type) + { + case MOTION_BLUR_SECURITY_CAM: + red = 0; + green = 255; + blue = 0; + blur = 128; + break; + case MOTION_BLUR_INTRO: + red = 100; + green = 220; + blue = 230; + blur = 158; + break; + case MOTION_BLUR_INTRO2: + red = 80; + green = 255; + blue = 230; + blur = 138; + break; + case MOTION_BLUR_INTRO3: + red = 255; + green = 60; + blue = 60; + blur = 200; + break; + case MOTION_BLUR_INTRO4: + red = 255; + green = 180; + blue = 180; + blur = 128; + break; + } + + PUSH_RENDERGROUP("CPostFX::Render"); + if(pFrontBuffer == nil) + Open(cam); + assert(pFrontBuffer); + assert(pBackBuffer); + + if(NeedBackBuffer()) + GetBackBuffer(cam); + + DefinedState(); + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + + if(type == MOTION_BLUR_SNIPER){ + if(!bJustInitialised) + RenderOverlaySniper(cam, red, green, blue, blur); + }else switch(EffectSwitch){ + case POSTFX_OFF: + // no actual rendering here + break; + case POSTFX_SIMPLE: + RenderOverlaySimple(cam, red, green, blue, blur); + break; + case POSTFX_NORMAL: + if(MotionBlurOn){ + if(!bJustInitialised) + RenderOverlayBlur(cam, red, green, blue, blur); + }else{ + RenderOverlayShader(cam, red, green, blue, blur); + } + break; + case POSTFX_MOBILE: + RenderOverlayShader(cam, red, green, blue, blur); + break; + } + + // TODO? maybe we want this even without motion blur on sometimes? + if(MotionBlurOn) + if(!bJustInitialised) + RenderMotionBlur(cam, bluralpha); + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + if(NeedFrontBuffer(type)){ + RwRasterPushContext(pFrontBuffer); + RwRasterRenderFast(RwCameraGetRaster(cam), 0, 0); + RwRasterPopContext(); + bJustInitialised = false; + }else + bJustInitialised = true; + + POP_RENDERGROUP(); +} + +#endif diff --git a/src/extras/postfx.h b/src/extras/postfx.h new file mode 100644 index 0000000..f8779a6 --- /dev/null +++ b/src/extras/postfx.h @@ -0,0 +1,36 @@ +#pragma once + +#ifdef EXTENDED_COLOURFILTER + +class CPostFX +{ +public: + enum { + POSTFX_OFF, + POSTFX_SIMPLE, + POSTFX_NORMAL, + POSTFX_MOBILE + }; + static RwRaster *pFrontBuffer; + static RwRaster *pBackBuffer; + static bool bJustInitialised; + static int EffectSwitch; + static bool MotionBlurOn; // or use CMblur for that? + static float Intensity; + + static void InitOnce(void); + static void Open(RwCamera *cam); + static void Close(void); + static void RenderOverlayBlur(RwCamera *cam, int32 r, int32 g, int32 b, int32 a); + static void RenderOverlaySimple(RwCamera *cam, int32 r, int32 g, int32 b, int32 a); + static void RenderOverlaySniper(RwCamera *cam, int32 r, int32 g, int32 b, int32 a); + static void RenderOverlayShader(RwCamera *cam, int32 r, int32 g, int32 b, int32 a); + static void RenderMotionBlur(RwCamera *cam, uint32 blur); + static void Render(RwCamera *cam, uint32 red, uint32 green, uint32 blue, uint32 blur, int32 type, uint32 bluralpha); + static bool NeedBackBuffer(void); + static bool NeedFrontBuffer(int32 type); + static void GetBackBuffer(RwCamera *cam); + static bool UseBlurColours(void) { return EffectSwitch != POSTFX_SIMPLE; } +}; + +#endif diff --git a/src/extras/re3_inttypes.h b/src/extras/re3_inttypes.h new file mode 100644 index 0000000..bf0c53e --- /dev/null +++ b/src/extras/re3_inttypes.h @@ -0,0 +1,216 @@ +#define PRId8 "hhd" +#define PRId16 "hd" +#define PRId32 "ld" +#define PRId64 "lld" + +#define PRIdFAST8 "hhd" +#define PRIdFAST16 "hd" +#define PRIdFAST32 "ld" +#define PRIdFAST64 "lld" + +#define PRIdLEAST8 "hhd" +#define PRIdLEAST16 "hd" +#define PRIdLEAST32 "ld" +#define PRIdLEAST64 "lld" + +#define PRIdMAX "lld" +#define PRIdPTR "lld" + +#define PRIi8 "hhi" +#define PRIi16 "hi" +#define PRIi32 "li" +#define PRIi64 "lli" + +#define PRIiFAST8 "hhi" +#define PRIiFAST16 "hi" +#define PRIiFAST32 "li" +#define PRIiFAST64 "lli" + +#define PRIiLEAST8 "hhi" +#define PRIiLEAST16 "hi" +#define PRIiLEAST32 "li" +#define PRIiLEAST64 "lli" + +#define PRIiMAX "lli" +#define PRIiPTR "lli" + +#define PRIo8 "hho" +#define PRIo16 "ho" +#define PRIo32 "lo" +#define PRIo64 "llo" + +#define PRIoFAST8 "hho" +#define PRIoFAST16 "ho" +#define PRIoFAST32 "lo" +#define PRIoFAST64 "llo" + +#define PRIoLEAST8 "hho" +#define PRIoLEAST16 "ho" +#define PRIoLEAST32 "lo" +#define PRIoLEAST64 "llo" + +#define PRIoMAX "llo" +#define PRIoPTR "llo" + +#define PRIu8 "hhu" +#define PRIu16 "hu" +#define PRIu32 "lu" +#define PRIu64 "llu" + +#define PRIuFAST8 "hhu" +#define PRIuFAST16 "hu" +#define PRIuFAST32 "lu" +#define PRIuFAST64 "llu" + +#define PRIuLEAST8 "hhu" +#define PRIuLEAST16 "hu" +#define PRIuLEAST32 "lu" +#define PRIuLEAST64 "llu" + +#define PRIuMAX "llu" +#define PRIuPTR "llu" + +#define PRIx8 "hhx" +#define PRIx16 "hx" +#define PRIx32 "lx" +#define PRIx64 "llx" + +#define PRIxFAST8 "hhx" +#define PRIxFAST16 "hx" +#define PRIxFAST32 "lx" +#define PRIxFAST64 "llx" + +#define PRIxLEAST8 "hhx" +#define PRIxLEAST16 "hx" +#define PRIxLEAST32 "lx" +#define PRIxLEAST64 "llx" + +#define PRIxMAX "llx" +#define PRIxPTR "llx" + +#define PRIX8 "hhX" +#define PRIX16 "hX" +#define PRIX32 "lX" +#define PRIX64 "llX" + +#define PRIXFAST8 "hhX" +#define PRIXFAST16 "hX" +#define PRIXFAST32 "lX" +#define PRIXFAST64 "llX" + +#define PRIXLEAST8 "hhX" +#define PRIXLEAST16 "hX" +#define PRIXLEAST32 "lX" +#define PRIXLEAST64 "llX" + +#define PRIXMAX "llX" +#define PRIXPTR "llX" + + /* SCAN FORMAT MACROS */ +#define SCNd8 "hhd" +#define SCNd16 "hd" +#define SCNd32 "ld" +#define SCNd64 "lld" + +#define SCNdFAST8 "hhd" +#define SCNdFAST16 "hd" +#define SCNdFAST32 "ld" +#define SCNdFAST64 "lld" + +#define SCNdLEAST8 "hhd" +#define SCNdLEAST16 "hd" +#define SCNdLEAST32 "ld" +#define SCNdLEAST64 "lld" + +#define SCNdMAX "lld" +#define SCNdPTR "lld" + +#define SCNi8 "hhi" +#define SCNi16 "hi" +#define SCNi32 "li" +#define SCNi64 "lli" + +#define SCNiFAST8 "hhi" +#define SCNiFAST16 "hi" +#define SCNiFAST32 "li" +#define SCNiFAST64 "lli" + +#define SCNiLEAST8 "hhi" +#define SCNiLEAST16 "hi" +#define SCNiLEAST32 "li" +#define SCNiLEAST64 "lli" + +#define SCNiMAX "lli" +#define SCNiPTR "lli" + +#define SCNo8 "hho" +#define SCNo16 "ho" +#define SCNo32 "lo" +#define SCNo64 "llo" + +#define SCNoFAST8 "hho" +#define SCNoFAST16 "ho" +#define SCNoFAST32 "lo" +#define SCNoFAST64 "llo" + +#define SCNoLEAST8 "hho" +#define SCNoLEAST16 "ho" +#define SCNoLEAST32 "lo" +#define SCNoLEAST64 "llo" + +#define SCNoMAX "llo" +#define SCNoPTR "llo" + +#define SCNu8 "hhu" +#define SCNu16 "hu" +#define SCNu32 "lu" +#define SCNu64 "llu" + +#define SCNuFAST8 "hhu" +#define SCNuFAST16 "hu" +#define SCNuFAST32 "lu" +#define SCNuFAST64 "llu" + +#define SCNuLEAST8 "hhu" +#define SCNuLEAST16 "hu" +#define SCNuLEAST32 "lu" +#define SCNuLEAST64 "llu" + +#define SCNuMAX "llu" +#define SCNuPTR "llu" + +#define SCNx8 "hhx" +#define SCNx16 "hx" +#define SCNx32 "lx" +#define SCNx64 "llx" + +#define SCNxFAST8 "hhx" +#define SCNxFAST16 "hx" +#define SCNxFAST32 "lx" +#define SCNxFAST64 "llx" + +#define SCNxLEAST8 "hhx" +#define SCNxLEAST16 "hx" +#define SCNxLEAST32 "lx" +#define SCNxLEAST64 "llx" + +#define SCNxMAX "llx" +#define SCNxPTR "llx" + +#define SCNX8 "hhX" +#define SCNX16 "hX" +#define SCNX32 "lX" +#define SCNX64 "llX" + +#define SCNXFAST8 "hhX" +#define SCNXFAST16 "hX" +#define SCNXFAST32 "lX" +#define SCNXFAST64 "llX" + +#define SCNXLEAST8 "hhX" +#define SCNXLEAST16 "hX" +#define SCNXLEAST32 "lX" +#define SCNXLEAST64 "llX" + +#define SCNXMAX "llX" +#define SCNXPTR "llX" \ No newline at end of file diff --git a/src/extras/screendroplets.cpp b/src/extras/screendroplets.cpp new file mode 100644 index 0000000..cc86808 --- /dev/null +++ b/src/extras/screendroplets.cpp @@ -0,0 +1,817 @@ +#define WITHD3D +#include "common.h" + +#ifdef SCREEN_DROPLETS + +#ifndef LIBRW +#error "Need librw for SCREEN_DROPLETS" +#endif + +#include "General.h" +#include "main.h" +#include "RwHelper.h" +#include "Timer.h" +#include "Camera.h" +#include "World.h" +#include "ZoneCull.h" +#include "Weather.h" +#include "ParticleObject.h" + #include "Pad.h" +#include "RenderBuffer.h" +#include "custompipes.h" +#include "postfx.h" +#include "screendroplets.h" + +// for 640 +#define MAXSIZE 15 +#define MINSIZE 4 + +int ScreenDroplets::ms_initialised; +RwTexture *ScreenDroplets::ms_maskTex; +RwTexture *ScreenDroplets::ms_screenTex; + +bool ScreenDroplets::ms_enabled = true; +bool ScreenDroplets::ms_movingEnabled = true; + +ScreenDroplets::ScreenDrop ScreenDroplets::ms_drops[ScreenDroplets::MAXDROPS]; +int ScreenDroplets::ms_numDrops; +ScreenDroplets::ScreenDropMoving ScreenDroplets::ms_dropsMoving[ScreenDroplets::MAXDROPSMOVING]; +int ScreenDroplets::ms_numDropsMoving; + +CVector ScreenDroplets::ms_prevCamUp; +CVector ScreenDroplets::ms_prevCamPos; +CVector ScreenDroplets::ms_camMoveDelta; +float ScreenDroplets::ms_camMoveDist; +CVector ScreenDroplets::ms_screenMoveDelta; +float ScreenDroplets::ms_screenMoveDist; +float ScreenDroplets::ms_camUpAngle; + +int ScreenDroplets::ms_splashDuration; +CParticleObject *ScreenDroplets::ms_splashObject; + +struct Im2DVertexUV2 : rw::RWDEVICE::Im2DVertex +{ + rw::float32 u2, v2; +}; + +#ifdef RW_D3D9 +static void *screenDroplet_PS; +#endif +#ifdef RW_GL3 +static rw::gl3::Shader *screenDroplet; +#endif + +// platform specific +static void openim2d_uv2(void); +static void closeim2d_uv2(void); +static void RenderIndexedPrimitive_UV2(RwPrimitiveType primType, Im2DVertexUV2 *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices); + +static Im2DVertexUV2 VertexBuffer[TEMPBUFFERVERTSIZE]; + +void +ScreenDroplets::Initialise(void) +{ + Clear(); + ms_splashDuration = -1; + ms_splashObject = nil; +} + +// Create white circle mask for rain drops +static RwTexture* +CreateDropMask(int32 size) +{ + RwImage *img = RwImageCreate(size, size, 32); + RwImageAllocatePixels(img); + + uint8 *pixels = RwImageGetPixels(img); + int32 stride = RwImageGetStride(img); + + for(int y = 0; y < size; y++){ + float yf = ((y + 0.5f)/size - 0.5f)*2.0f; + for(int x = 0; x < size; x++){ + float xf = ((x + 0.5f)/size - 0.5f)*2.0f; + memset(&pixels[y*stride + x*4], xf*xf + yf*yf < 1.0f ? 0xFF : 0x00, 4); + } + } + + int32 width, height, depth, format; + RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &width, &height, &depth, &format); + RwRaster *ras = RwRasterCreate(width, height, depth, format); + RwRasterSetFromImage(ras, img); + RwImageDestroy(img); + return RwTextureCreate(ras); +} + +void +ScreenDroplets::InitDraw(void) +{ + ms_maskTex = CreateDropMask(64); + + ms_screenTex = RwTextureCreate(nil); + RwTextureSetFilterMode(ms_screenTex, rwFILTERLINEAR); + + openim2d_uv2(); +#ifdef RW_D3D9 +#include "shaders/obj/screenDroplet_PS.inc" + screenDroplet_PS = rw::d3d::createPixelShader(screenDroplet_PS_cso); +#endif +#ifdef RW_GL3 + using namespace rw::gl3; + { +#include "shaders/obj/im2d_UV2_vert.inc" +#include "shaders/obj/screenDroplet_frag.inc" + const char *vs[] = { shaderDecl, header_vert_src, im2d_UV2_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, screenDroplet_frag_src, nil }; + screenDroplet = Shader::create(vs, fs); + assert(screenDroplet); + } +#endif + + ms_initialised = 1; +} + +void +ScreenDroplets::Shutdown(void) +{ + if(ms_maskTex){ + RwTextureDestroy(ms_maskTex); + ms_maskTex = nil; + } + if(ms_screenTex){ + RwTextureSetRaster(ms_screenTex, nil); + RwTextureDestroy(ms_screenTex); + ms_screenTex = nil; + } +#ifdef RW_D3D9 + if(screenDroplet_PS){ + rw::d3d::destroyPixelShader(screenDroplet_PS); + screenDroplet_PS = nil; + } +#endif +#ifdef RW_GL3 + if(screenDroplet){ + screenDroplet->destroy(); + screenDroplet = nil; + } +#endif + + closeim2d_uv2(); +} + +void +ScreenDroplets::Process(void) +{ + ProcessCameraMovement(); + SprayDrops(); + ProcessMoving(); + Fade(); +} + +static void +FlushBuffer(void) +{ + if(TempBufferIndicesStored){ + RenderIndexedPrimitive_UV2(rwPRIMTYPETRILIST, + VertexBuffer, TempBufferVerticesStored, + TempBufferRenderIndexList, TempBufferIndicesStored); + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + } +} + +static int +StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, Im2DVertexUV2 **vertexStart) +{ + if(TempBufferIndicesStored + numIndices >= TEMPBUFFERINDEXSIZE || + TempBufferVerticesStored + numVertices >= TEMPBUFFERVERTSIZE) + FlushBuffer(); + *indexStart = &TempBufferRenderIndexList[TempBufferIndicesStored]; + *vertexStart = &VertexBuffer[TempBufferVerticesStored]; + int vertOffset = TempBufferVerticesStored; + TempBufferIndicesStored += numIndices; + TempBufferVerticesStored += numVertices; + return vertOffset; +} + +void +ScreenDroplets::Render(void) +{ + ScreenDrop *drop; + + DefinedState(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(ms_maskTex)); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + RwTextureSetRaster(ms_screenTex, CPostFX::pBackBuffer); +#ifdef RW_D3D9 + rw::d3d::im2dOverridePS = screenDroplet_PS; + rw::d3d::setTexture(1, ms_screenTex); +#endif +#ifdef RW_GL3 + rw::gl3::im2dOverrideShader = screenDroplet; + rw::gl3::setTexture(1, ms_screenTex); +#endif + + RenderBuffer::ClearRenderBuffer(); + for(drop = &ms_drops[0]; drop < &ms_drops[MAXDROPS]; drop++) + if(drop->active) + AddToRenderList(drop); + FlushBuffer(); + +#ifdef RW_D3D9 + rw::d3d::im2dOverridePS = nil; + rw::d3d::setTexture(1, nil); +#endif +#ifdef RW_GL3 + rw::gl3::im2dOverrideShader = nil; + rw::gl3::setTexture(1, nil); +#endif + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, FALSE); +} + +void +ScreenDroplets::AddToRenderList(ScreenDroplets::ScreenDrop *drop) +{ + static float xy[] = { + -1.0f, -1.0f, + -1.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, -1.0f + }; + static float uv[] = { + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f + }; + + int i; + RwImVertexIndex *indices; + Im2DVertexUV2 *verts; + int first = StartStoring(6, 4, &indices, &verts); + + float scale = 0.5f*SCREEN_SCALE_X(drop->size); + + float screenz = RwIm2DGetNearScreenZ(); + float z = RwCameraGetNearClipPlane(Scene.camera); + float recipz = 1.0f/z; + + float magSize = SCREEN_SCALE_Y(drop->magnification*(300.0f-40.0f) + 40.0f); + float ul = drop->x - magSize; + float vt = drop->y - magSize; + float ur = drop->x + magSize; + float vb = drop->y + magSize; + ul = Max(ul, 0.0f)/RwRasterGetWidth(CPostFX::pBackBuffer); + vt = Max(vt, 0.0f)/RwRasterGetHeight(CPostFX::pBackBuffer); + ur = Min(ur, SCREEN_WIDTH)/RwRasterGetWidth(CPostFX::pBackBuffer); + vb = Min(vb, SCREEN_HEIGHT)/RwRasterGetHeight(CPostFX::pBackBuffer); + + for(i = 0; i < 4; i++){ + RwIm2DVertexSetScreenX(&verts[i], drop->x + xy[i*2]*scale); + RwIm2DVertexSetScreenY(&verts[i], drop->y + xy[i*2+1]*scale); + RwIm2DVertexSetScreenZ(&verts[i], screenz); + RwIm2DVertexSetCameraZ(&verts[i], z); + RwIm2DVertexSetRecipCameraZ(&verts[i], recipz); + RwIm2DVertexSetIntRGBA(&verts[i], drop->color.r, drop->color.g, drop->color.b, drop->color.a); + RwIm2DVertexSetU(&verts[i], uv[i*2], recipz); + RwIm2DVertexSetV(&verts[i], uv[i*2+1], recipz); + + verts[i].u2 = i < 2 ? ul : ur; + verts[i].v2 = i % 3 ? vt : vb; + } + indices[0] = first + 0; + indices[1] = first + 1; + indices[2] = first + 2; + indices[3] = first + 2; + indices[4] = first + 3; + indices[5] = first + 0; +} + +void +ScreenDroplets::Clear(void) +{ + ScreenDrop *drop; + for(drop = &ms_drops[0]; drop < &ms_drops[MAXDROPS]; drop++) + drop->active = false; + ms_numDrops = 0; +} + +ScreenDroplets::ScreenDrop* +ScreenDroplets::NewDrop(float x, float y, float size, float lifetime, bool fades, int r, int g, int b) +{ + ScreenDrop *drop; + int i; + + for(i = 0, drop = ms_drops; i < MAXDROPS; i++, drop++) + if(!ms_drops[i].active) + goto found; + return nil; +found: + ms_numDrops++; + drop->x = x; + drop->y = y; + drop->size = size; + drop->magnification = (MAXSIZE - size + 1.0f) / (MAXSIZE - MINSIZE + 1.0f); + drop->fades = fades; + drop->active = true; + drop->color.r = r; + drop->color.g = g; + drop->color.b = b; + drop->color.a = 255; + drop->time = 0.0f; + drop->lifetime = lifetime; + return drop; +} + +void +ScreenDroplets::SetMoving(ScreenDroplets::ScreenDrop *drop) +{ + ScreenDropMoving *moving; + for(moving = ms_dropsMoving; moving < &ms_dropsMoving[MAXDROPSMOVING]; moving++) + if(moving->drop == nil) + goto found; + return; +found: + ms_numDropsMoving++; + moving->drop = drop; + moving->dist = 0.0f; +} + +void +ScreenDroplets::FillScreen(int n) +{ + float x, y, size; + ScreenDrop *drop; + + if(!ms_initialised) + return; + ms_numDrops = 0; + for(drop = &ms_drops[0]; drop < &ms_drops[MAXDROPS]; drop++){ + drop->active = false; + if(drop < &ms_drops[n]){ + x = CGeneral::GetRandomNumber() % (int)SCREEN_WIDTH; + y = CGeneral::GetRandomNumber() % (int)SCREEN_HEIGHT; + size = CGeneral::GetRandomNumberInRange(MINSIZE, MAXSIZE); + NewDrop(x, y, size, 2000.0f, true); + } + } +} + +void +ScreenDroplets::FillScreenMoving(float amount, bool isBlood) +{ + int n = (ms_screenMoveDelta.z > 5.0f ? 1.5f : 1.0f)*amount*20.0f; + float x, y, size; + ScreenDrop *drop; + + while(n--) + if(ms_numDrops < MAXDROPS && ms_numDropsMoving < MAXDROPSMOVING){ + x = CGeneral::GetRandomNumber() % (int)SCREEN_WIDTH; + y = CGeneral::GetRandomNumber() % (int)SCREEN_HEIGHT; + size = CGeneral::GetRandomNumberInRange(MINSIZE, MAXSIZE); + drop = nil; + if(isBlood) + drop = NewDrop(x, y, size, 2000.0f, true, 255, 0, 0); + else + drop = NewDrop(x, y, size, 2000.0f, true); + if(drop) + SetMoving(drop); + } +} + +void +ScreenDroplets::RegisterSplash(CParticleObject *pobj) +{ + CVector dist = pobj->GetPosition() - ms_prevCamPos; + if(dist.MagnitudeSqr() < 50.0f){ // 20 originally + ms_splashDuration = 14; + ms_splashObject = pobj; + } +} + +void +ScreenDroplets::ProcessCameraMovement(void) +{ + RwMatrix *camMat = RwFrameGetMatrix(RwCameraGetFrame(Scene.camera)); + CVector camPos = camMat->pos; + CVector camUp = camMat->at; + ms_camMoveDelta = camPos - ms_prevCamPos; + ms_camMoveDist = ms_camMoveDelta.Magnitude(); + + ms_prevCamUp = camUp; + ms_prevCamPos = camPos; + + ms_screenMoveDelta.x = -RwV3dDotProduct(&camMat->right, &ms_camMoveDelta); + ms_screenMoveDelta.y = RwV3dDotProduct(&camMat->up, &ms_camMoveDelta); + ms_screenMoveDelta.z = RwV3dDotProduct(&camMat->at, &ms_camMoveDelta); + ms_screenMoveDelta *= 10.0f; + ms_screenMoveDist = ms_screenMoveDelta.Magnitude2D(); + + uint16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + bool isTopDown = mode == CCam::MODE_TOPDOWN || mode == CCam::MODE_GTACLASSIC || mode == CCam::MODE_TOP_DOWN_PED; + bool isLookingInDirection = FindPlayerVehicle() && mode == CCam::MODE_1STPERSON && + (CPad::GetPad(0)->GetLookBehindForCar() || CPad::GetPad(0)->GetLookLeft() || CPad::GetPad(0)->GetLookRight()); + ms_enabled = !isTopDown && !isLookingInDirection; + ms_movingEnabled = !isTopDown && !isLookingInDirection; + + // 0 when looking stright up, 180 when looking up or down + ms_camUpAngle = RADTODEG(Acos(Clamp(camUp.z, -1.0f, 1.0f))); +} + +void +ScreenDroplets::SprayDrops(void) +{ + bool noRain = CCullZones::PlayerNoRain() || CCullZones::CamNoRain(); + if(!noRain && CWeather::Rain > 0.0f && ms_enabled){ + // 180 when looking stright up, 0 when looking up or down + float angle = 180.0f - ms_camUpAngle; + angle = Max(angle, 40.0f); // want at least some rain + FillScreenMoving((angle - 40.0f) / 150.0f * CWeather::Rain * 0.5f); + } + + int i; + for(i = 0; i < MAX_AUDIOHYDRANTS; i++){ + CAudioHydrant *hyd = CAudioHydrant::Get(i); + if (hyd->pParticleObject){ + CVector dist = hyd->pParticleObject->GetPosition() - ms_prevCamPos; + if(dist.MagnitudeSqr() > 40.0f || + DotProduct(dist, ms_prevCamUp) < 0.0f) continue; + + FillScreenMoving(1.0f); + } + } + + static int ndrops[] = { + 125, 250, 500, 1000, 1000, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if(ms_splashDuration >= 0){ + if(ms_numDrops < MAXDROPS) { + float numDropMult = 1.0f; + if(ms_splashObject){ + float dist = (ms_splashObject->GetPosition() - ms_prevCamPos).Magnitude(); + numDropMult = 1.0f - (dist - 5.0f)/15.0f; + if(numDropMult < 0) numDropMult = 0.0f; // fix + } + int n = ndrops[ms_splashDuration] * numDropMult; + while(n--) + if(ms_numDrops < MAXDROPS){ + float x = CGeneral::GetRandomNumber() % (int)SCREEN_WIDTH; + float y = CGeneral::GetRandomNumber() % (int)SCREEN_HEIGHT; + float size = CGeneral::GetRandomNumberInRange(MINSIZE, MAXSIZE); + NewDrop(x, y, size, 10000.0f, false); + } + } + ms_splashDuration--; + } +} + +void +ScreenDroplets::NewTrace(ScreenDroplets::ScreenDropMoving *moving) +{ + if(ms_numDrops < MAXDROPS){ + moving->dist = 0.0f; + NewDrop(moving->drop->x, moving->drop->y, MINSIZE, 500.0f, true, + moving->drop->color.r, moving->drop->color.g, moving->drop->color.b); + } +} + +void +ScreenDroplets::MoveDrop(ScreenDroplets::ScreenDropMoving *moving) +{ + ScreenDrop *drop = moving->drop; + if(!ms_movingEnabled) + return; + if(!drop->active){ + moving->drop = nil; + ms_numDropsMoving--; + return; + } + if(ms_screenMoveDelta.z > 0.0f && ms_camMoveDist > 0.3f){ + if(ms_screenMoveDist > 0.5f && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_1STPERSON){ + // movement when camera turns + moving->dist += ms_screenMoveDist; + if(moving->dist > 20.0f && drop->color.a > 100) + NewTrace(moving); + + drop->x -= ms_screenMoveDelta.x; + drop->y += ms_screenMoveDelta.y; + }else{ + // movement out of center + float d = ms_screenMoveDelta.z*0.2f; + float dx, dy, sum; + dx = drop->x - SCREEN_WIDTH*0.5f + ms_screenMoveDelta.x; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON) + dy = drop->y - SCREEN_HEIGHT*1.2f - ms_screenMoveDelta.y; + else + dy = drop->y - SCREEN_HEIGHT*0.5f - ms_screenMoveDelta.y; + sum = fabs(dx) + fabs(dy); + if(sum > 0.001f){ + dx /= sum; + dy /= sum; + } + moving->dist += d; + if(moving->dist > 20.0f && drop->color.a > 100) + NewTrace(moving); + drop->x += dx * d; + drop->y += dy * d; + } + + if(drop->x < 0.0f || drop->y < 0.0f || + drop->x > SCREEN_WIDTH || drop->y > SCREEN_HEIGHT){ + moving->drop = nil; + ms_numDropsMoving--; + } + } +} + +void +ScreenDroplets::ProcessMoving(void) +{ + ScreenDropMoving *moving; + if(!ms_movingEnabled) + return; + for(moving = ms_dropsMoving; moving < &ms_dropsMoving[MAXDROPSMOVING]; moving++) + if(moving->drop) + MoveDrop(moving); +} + +void +ScreenDroplets::Fade(void) +{ + ScreenDrop *drop; + for(drop = &ms_drops[0]; drop < &ms_drops[MAXDROPS]; drop++) + if(drop->active) + drop->Fade(); +} + +void +ScreenDroplets::ScreenDrop::Fade(void) +{ + int delta = CTimer::GetTimeStepInMilliseconds(); + time += delta; + if(time < lifetime){ + color.a = 255 - time/lifetime*255; + }else if(fades){ + ScreenDroplets::ms_numDrops--; + active = false; + } +} + + +/* + * Im2D with two uv coors + */ + +#ifdef RW_D3D9 +// stolen from RW, not in a public header +namespace rw { +namespace d3d { +void addDynamicVB(uint32 length, uint32 fvf, IDirect3DVertexBuffer9 **buf); // NB: don't share this pointer +void removeDynamicVB(IDirect3DVertexBuffer9 **buf); +void addDynamicIB(uint32 length, IDirect3DIndexBuffer9 **buf); // NB: don't share this pointer +void removeDynamicIB(IDirect3DIndexBuffer9 **buf); +} +} +// different than im2d +#define NUMINDICES 1024 +#define NUMVERTICES 1024 + +static int primTypeMap[] = { + D3DPT_POINTLIST, // invalid + D3DPT_LINELIST, + D3DPT_LINESTRIP, + D3DPT_TRIANGLELIST, + D3DPT_TRIANGLESTRIP, + D3DPT_TRIANGLEFAN, + D3DPT_POINTLIST, // actually not supported! +}; +// end of stolen stuff + + +static IDirect3DVertexDeclaration9 *im2ddecl_uv2; +static IDirect3DVertexBuffer9 *im2dvertbuf_uv2; +static IDirect3DIndexBuffer9 *im2dindbuf_uv2; + +void +openim2d_uv2(void) +{ + using namespace rw; + using namespace d3d; + D3DVERTEXELEMENT9 elements[5] = { + { 0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT, 0 }, + { 0, offsetof(Im2DVertexUV2, color), D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 }, + { 0, offsetof(Im2DVertexUV2, u), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, + { 0, offsetof(Im2DVertexUV2, u2), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 }, + D3DDECL_END() + }; + assert(im2ddecl_uv2 == nil); + im2ddecl_uv2 = (IDirect3DVertexDeclaration9*)d3d9::createVertexDeclaration((d3d9::VertexElement*)elements); + assert(im2ddecl_uv2); + + assert(im2dvertbuf_uv2 == nil); + im2dvertbuf_uv2 = (IDirect3DVertexBuffer9*)createVertexBuffer(NUMVERTICES*sizeof(Im2DVertexUV2), 0, true); + assert(im2dvertbuf_uv2); + addDynamicVB(NUMVERTICES*sizeof(Im2DVertexUV2), 0, &im2dvertbuf_uv2); + + assert(im2dindbuf_uv2 == nil); + im2dindbuf_uv2 = (IDirect3DIndexBuffer9*)createIndexBuffer(NUMINDICES*sizeof(rw::uint16), true); + assert(im2dindbuf_uv2); + addDynamicIB(NUMINDICES*sizeof(rw::uint16), &im2dindbuf_uv2); +} + +void +closeim2d_uv2(void) +{ + using namespace rw; + using namespace d3d; + + d3d9::destroyVertexDeclaration(im2ddecl_uv2); + im2ddecl_uv2 = nil; + + removeDynamicVB(&im2dvertbuf_uv2); + destroyVertexBuffer(im2dvertbuf_uv2); + im2dvertbuf_uv2 = nil; + + removeDynamicIB(&im2dindbuf_uv2); + destroyIndexBuffer(im2dindbuf_uv2); + im2dindbuf_uv2 = nil; +} + +void +RenderIndexedPrimitive_UV2(RwPrimitiveType primType, Im2DVertexUV2 *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices) +{ + using namespace rw; + using namespace d3d; + + if(numVertices > NUMVERTICES || + numIndices > NUMINDICES){ + // TODO: error + return; + } + rw::uint16 *lockedindices = lockIndices(im2dindbuf_uv2, 0, numIndices*sizeof(rw::uint16), D3DLOCK_DISCARD); + memcpy(lockedindices, indices, numIndices*sizeof(rw::uint16)); + unlockIndices(im2dindbuf_uv2); + + rw::uint8 *lockedvertices = lockVertices(im2dvertbuf_uv2, 0, numVertices*sizeof(Im2DVertexUV2), D3DLOCK_DISCARD); + memcpy(lockedvertices, vertices, numVertices*sizeof(Im2DVertexUV2)); + unlockVertices(im2dvertbuf_uv2); + + setStreamSource(0, im2dvertbuf_uv2, 0, sizeof(Im2DVertexUV2)); + setIndices(im2dindbuf_uv2); + setVertexDeclaration(im2ddecl_uv2); + + if(im2dOverridePS) + setPixelShader(im2dOverridePS); + else if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(im2d_tex_PS); + else + setPixelShader(im2d_PS); + + d3d::flushCache(); + + rw::uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = numIndices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = numIndices-1; + break; + case PRIMTYPETRILIST: + primCount = numIndices/3; + break; + case PRIMTYPETRISTRIP: + primCount = numIndices-2; + break; + case PRIMTYPETRIFAN: + primCount = numIndices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = numIndices; + break; + } + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, + 0, numVertices, + 0, primCount); +} +#endif + +#ifdef RW_GL3 +// different than im2d +#define NUMINDICES 1024 +#define NUMVERTICES 1024 + +static rw::gl3::AttribDesc im2d_UV2_attribDesc[4] = { + { rw::gl3::ATTRIB_POS, GL_FLOAT, GL_FALSE, 4, + sizeof(Im2DVertexUV2), 0 }, + { rw::gl3::ATTRIB_COLOR, GL_UNSIGNED_BYTE, GL_TRUE, 4, + sizeof(Im2DVertexUV2), offsetof(Im2DVertexUV2, r) }, + { rw::gl3::ATTRIB_TEXCOORDS0, GL_FLOAT, GL_FALSE, 2, + sizeof(Im2DVertexUV2), offsetof(Im2DVertexUV2, u) }, + { rw::gl3::ATTRIB_TEXCOORDS1, GL_FLOAT, GL_FALSE, 2, + sizeof(Im2DVertexUV2), offsetof(Im2DVertexUV2, u2) } +}; + +static int primTypeMap[] = { + GL_POINTS, // invalid + GL_LINES, + GL_LINE_STRIP, + GL_TRIANGLES, + GL_TRIANGLE_STRIP, + GL_TRIANGLE_FAN, + GL_POINTS +}; + +static int32 u_xform; + +uint32 im2D_UV2_Vbo, im2D_UV2_Ibo; +#ifdef RW_GL_USE_VAOS +uint32 im2D_UV2_Vao; +#endif + +void +openim2d_uv2(void) +{ + u_xform = rw::gl3::registerUniform("u_xform", rw::gl3::UNIFORM_VEC4); // this doesn't add a new one, so it's safe + + glGenBuffers(1, &im2D_UV2_Ibo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im2D_UV2_Ibo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, NUMINDICES*2, nil, GL_STREAM_DRAW); + + glGenBuffers(1, &im2D_UV2_Vbo); + glBindBuffer(GL_ARRAY_BUFFER, im2D_UV2_Vbo); + glBufferData(GL_ARRAY_BUFFER, NUMVERTICES*sizeof(Im2DVertexUV2), nil, GL_STREAM_DRAW); + +#ifdef RW_GL_USE_VAOS + glGenVertexArrays(1, &im2D_UV2_Vao); + glBindVertexArray(im2D_UV2_Vao); + setAttribPointers(im2d_UV2_attribDesc, 4); +#endif +} + +void +closeim2d_uv2(void) +{ + glDeleteBuffers(1, &im2D_UV2_Ibo); + glDeleteBuffers(1, &im2D_UV2_Vbo); +#ifdef RW_GL_USE_VAOS + glDeleteVertexArrays(1, &im2D_UV2_Vao); +#endif +} + +void +RenderIndexedPrimitive_UV2(RwPrimitiveType primType, Im2DVertexUV2 *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices) +{ + using namespace rw; + using namespace gl3; + + GLfloat xform[4]; + Camera *cam; + cam = (Camera*)engine->currentCamera; + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(im2D_UV2_Vao); +#endif + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im2D_UV2_Ibo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, NUMINDICES*2, nil, GL_STREAM_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numIndices*2, indices); + + glBindBuffer(GL_ARRAY_BUFFER, im2D_UV2_Vbo); + glBufferData(GL_ARRAY_BUFFER, NUMVERTICES*sizeof(Im2DVertexUV2), nil, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices*sizeof(Im2DVertexUV2), vertices); + + xform[0] = 2.0f/cam->frameBuffer->width; + xform[1] = -2.0f/cam->frameBuffer->height; + xform[2] = -1.0f; + xform[3] = 1.0f; + + if(im2dOverrideShader) + im2dOverrideShader->use(); + else + assert(0);//im2dShader->use(); +#ifndef RW_GL_USE_VAOS + setAttribPointers(im2d_UV2_attribDesc, 4); +#endif + + setUniform(u_xform, xform); + + flushCache(); + glDrawElements(primTypeMap[primType], numIndices, + GL_UNSIGNED_SHORT, nil); +#ifndef RW_GL_USE_VAOS + disableAttribPointers(im2d_UV2_attribDesc, 4); +#endif +} +#endif + +#endif diff --git a/src/extras/screendroplets.h b/src/extras/screendroplets.h new file mode 100644 index 0000000..090b192 --- /dev/null +++ b/src/extras/screendroplets.h @@ -0,0 +1,78 @@ +#pragma once + +#ifdef SCREEN_DROPLETS + +class CParticleObject; + +class ScreenDroplets +{ +public: + enum { + MAXDROPS = 2000, + MAXDROPSMOVING = 700 + }; + + class ScreenDrop + { + public: + float x, y, time; // shorts on xbox (short float?) + float size, magnification, lifetime; // " + CRGBA color; + bool active; + bool fades; + + void Fade(void); + }; + + struct ScreenDropMoving + { + ScreenDrop *drop; + float dist; + }; + + static int ms_initialised; + static RwTexture *ms_maskTex; + static RwTexture *ms_screenTex; + + static bool ms_enabled; + static bool ms_movingEnabled; + + static ScreenDrop ms_drops[MAXDROPS]; + static int ms_numDrops; + static ScreenDropMoving ms_dropsMoving[MAXDROPSMOVING]; + static int ms_numDropsMoving; + + static CVector ms_prevCamUp; + static CVector ms_prevCamPos; + static CVector ms_camMoveDelta; + static float ms_camMoveDist; + static CVector ms_screenMoveDelta; + static float ms_screenMoveDist; + static float ms_camUpAngle; + + static int ms_splashDuration; + static CParticleObject *ms_splashObject; + + static void Initialise(void); + static void InitDraw(void); + static void Shutdown(void); + static void Process(void); + static void Render(void); + static void AddToRenderList(ScreenDrop *drop); + + static void Clear(void); + static ScreenDrop *NewDrop(float x, float y, float size, float lifetime, bool fades, int r = 255, int g = 255, int b = 255); + static void SetMoving(ScreenDroplets::ScreenDrop *drop); + static void FillScreen(int n); + static void FillScreenMoving(float amount, bool isBlood = false); + static void RegisterSplash(CParticleObject *pobj); + + static void ProcessCameraMovement(void); + static void SprayDrops(void); + static void NewTrace(ScreenDroplets::ScreenDropMoving *moving); + static void MoveDrop(ScreenDropMoving *moving); + static void ProcessMoving(void); + static void Fade(void); +}; + +#endif diff --git a/src/extras/shaders/colourfilterIII.frag b/src/extras/shaders/colourfilterIII.frag new file mode 100644 index 0000000..95e5d05 --- /dev/null +++ b/src/extras/shaders/colourfilterIII.frag @@ -0,0 +1,24 @@ +uniform sampler2D tex0; +uniform vec4 u_blurcolor; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN float v_fog; + +void +main(void) +{ + float a = u_blurcolor.a; + vec4 dst = texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec4 prev = dst; + for(int i = 0; i < 5; i++){ + vec4 tmp = dst*(1.0-a) + prev*u_blurcolor*a; + prev = clamp(tmp, 0.0, 1.0); + } + vec4 color; + color.rgb = prev.rgb; + color.a = 1.0; + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/colourfilterIII_PS.hlsl b/src/extras/shaders/colourfilterIII_PS.hlsl new file mode 100644 index 0000000..3d893c3 --- /dev/null +++ b/src/extras/shaders/colourfilterIII_PS.hlsl @@ -0,0 +1,15 @@ +sampler2D tex : register(s0); +float4 blurcol : register(c10); + +float4 main(in float2 texcoord : TEXCOORD0) : COLOR0 +{ + float a = blurcol.a; + float4 dst = tex2D(tex, texcoord.xy); + float4 prev = dst; + for(int i = 0; i < 5; i++){ + float4 tmp = dst*(1-a) + prev*blurcol*a; + prev = saturate(tmp); + } + prev.a = 1.0; + return prev; +} diff --git a/src/extras/shaders/contrast.frag b/src/extras/shaders/contrast.frag new file mode 100644 index 0000000..2d394f6 --- /dev/null +++ b/src/extras/shaders/contrast.frag @@ -0,0 +1,19 @@ +uniform sampler2D tex0; +uniform vec3 u_contrastAdd; +uniform vec3 u_contrastMult; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN float v_fog; + +void +main(void) +{ + vec4 dst = texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec4 color; + color.rgb = dst.rgb*u_contrastMult + u_contrastAdd; + color.a = 1.0; + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/contrastPS.hlsl b/src/extras/shaders/contrastPS.hlsl new file mode 100644 index 0000000..a1de1d8 --- /dev/null +++ b/src/extras/shaders/contrastPS.hlsl @@ -0,0 +1,21 @@ +struct PS_INPUT +{ + float4 position : POSITION; + float3 texcoord0 : TEXCOORD0; + float4 color : COLOR0; +}; + +uniform float3 contrastMult : register(c10); +uniform float3 contrastAdd : register(c11); + +sampler2D tex : register(s0); + +float4 +main(PS_INPUT In) : COLOR +{ + float4 dst = tex2D(tex, In.texcoord0.xy); + + dst.rgb = dst.rgb*contrastMult + contrastAdd; + dst.a = 1.0; + return dst; +} diff --git a/src/extras/shaders/default_UV2.vert b/src/extras/shaders/default_UV2.vert new file mode 100644 index 0000000..694c012 --- /dev/null +++ b/src/extras/shaders/default_UV2.vert @@ -0,0 +1,25 @@ +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT vec2 v_tex1; +VSOUT float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + v_tex1 = in_tex1; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/extras/shaders/default_UV2_VS.hlsl b/src/extras/shaders/default_UV2_VS.hlsl new file mode 100644 index 0000000..e78a990 --- /dev/null +++ b/src/extras/shaders/default_UV2_VS.hlsl @@ -0,0 +1,54 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float2 TexCoord1 : TEXCOORD1; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; +}; + + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + float3 Normal = mul(normalMat, input.Normal); + + output.TexCoord0.xy = input.TexCoord; + output.TexCoord1.xy = input.TexCoord1; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +#endif +#ifdef POINTLIGHTS + for(i = 0; i < numPointLights; i++) + output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +#endif +#ifdef SPOTLIGHTS + for(i = 0; i < numSpotLights; i++) + output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +#endif + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/im2d.vert b/src/extras/shaders/im2d.vert new file mode 100644 index 0000000..fcd81c2 --- /dev/null +++ b/src/extras/shaders/im2d.vert @@ -0,0 +1,19 @@ +uniform vec4 u_xform; + +VSIN(ATTRIB_POS) vec4 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + gl_Position = in_pos; + gl_Position.w = 1.0; + gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw; + v_fog = DoFog(gl_Position.z); + gl_Position.xyz *= gl_Position.w; + v_color = in_color; + v_tex0 = in_tex0; +} diff --git a/src/extras/shaders/im2d_UV2.vert b/src/extras/shaders/im2d_UV2.vert new file mode 100644 index 0000000..e5fd4d0 --- /dev/null +++ b/src/extras/shaders/im2d_UV2.vert @@ -0,0 +1,21 @@ +uniform vec4 u_xform; + +VSIN(ATTRIB_POS) vec4 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT vec2 v_tex1; +VSOUT float v_fog; + +void +main(void) +{ + gl_Position = in_pos; + gl_Position.w = 1.0; + gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw; + v_fog = DoFog(gl_Position.z); + gl_Position.xyz *= gl_Position.w; + v_color = in_color; + v_tex0 = in_tex0; + v_tex1 = in_tex1; +} diff --git a/src/extras/shaders/lighting.h b/src/extras/shaders/lighting.h new file mode 100644 index 0000000..4b08196 --- /dev/null +++ b/src/extras/shaders/lighting.h @@ -0,0 +1,44 @@ +struct Light +{ + float4 color; // and radius + float4 position; // and -cos(angle) + float4 direction; // and falloff clamp +}; + +float3 DoDirLight(Light L, float3 N) +{ + float l = max(0.0, dot(N, -L.direction.xyz)); + return l*L.color.xyz; +} + +float3 DoDirLightSpec(Light L, float3 N, float3 V, float power) +{ + return pow(saturate(dot(N, normalize(V + -L.direction.xyz))), power)*L.color.xyz; +} + +float3 DoPointLight(Light L, float3 V, float3 N) +{ + // As on PS2 + float3 dir = V - L.position.xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/L.color.w)); + float l = max(0.0, dot(N, -normalize(dir))); + return l*L.color.xyz*atten; +} + +float3 DoSpotLight(Light L, float3 V, float3 N) +{ + // As on PS2 + float3 dir = V - L.position.xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/L.color.w)); + dir /= dist; + float l = max(0.0, dot(N, -dir)); + float pcos = dot(dir, L.direction.xyz); // cos to point + float ccos = -L.position.w; // cos of cone + float falloff = (pcos-ccos)/(1.0-ccos); + if(falloff < 0) // outside of cone + l = 0; + l *= max(falloff, L.direction.w); // falloff clamp + return l*L.color.xyz*atten; +} diff --git a/src/extras/shaders/make_glsl.sh b/src/extras/shaders/make_glsl.sh new file mode 100644 index 0000000..0af9896 --- /dev/null +++ b/src/extras/shaders/make_glsl.sh @@ -0,0 +1,9 @@ +#!sh +for i in *.vert; do + echo $i + ./makeinc_glsl.sh $i +done +for i in *.frag; do + echo $i + ./makeinc_glsl.sh $i +done diff --git a/src/extras/shaders/make_hlsl.cmd b/src/extras/shaders/make_hlsl.cmd new file mode 100644 index 0000000..dee9528 --- /dev/null +++ b/src/extras/shaders/make_hlsl.cmd @@ -0,0 +1,3 @@ +@echo off +for %%f in (*PS.hlsl) do "%DXSDK_DIR%\Utilities\bin\x86\fxc.exe" /T ps_2_0 /nologo /E main /Fo obj\%%~nf.cso %%f +for %%f in (*VS.hlsl) do "%DXSDK_DIR%\Utilities\bin\x86\fxc.exe" /T vs_2_0 /nologo /E main /Fo obj\%%~nf.cso %%f diff --git a/src/extras/shaders/makeinc_glsl.sh b/src/extras/shaders/makeinc_glsl.sh new file mode 100644 index 0000000..2bc6a38 --- /dev/null +++ b/src/extras/shaders/makeinc_glsl.sh @@ -0,0 +1,6 @@ +#!sh +ext=${1##*.} +name=${1%.*} +(echo "const char *${name}_${ext}_src =";\ +sed 's/..*/"&\\n"/' $1;\ +echo ';') > obj/${name}_${ext}.inc diff --git a/src/extras/shaders/makeinc_hlsl.sh b/src/extras/shaders/makeinc_hlsl.sh new file mode 100644 index 0000000..a5b1286 --- /dev/null +++ b/src/extras/shaders/makeinc_hlsl.sh @@ -0,0 +1,6 @@ +#!sh +cd obj +for i in *cso; do + (echo -n 'static ' + xxd -i $i | grep -v '_len = ') > ${i%cso}inc +done diff --git a/src/extras/shaders/neoGloss.frag b/src/extras/shaders/neoGloss.frag new file mode 100644 index 0000000..4f097b0 --- /dev/null +++ b/src/extras/shaders/neoGloss.frag @@ -0,0 +1,26 @@ +uniform sampler2D tex0; + +uniform vec4 u_reflProps; + +#define glossMult (u_reflProps.x) + +FSIN vec3 v_normal; +FSIN vec3 v_light; +FSIN vec2 v_tex0; +FSIN float v_fog; + +void +main(void) +{ + vec4 color = texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec3 n = 2.0*v_normal-1.0; // unpack + vec3 v = 2.0*v_light-1.0; // + + float s = dot(n, v); + color = s*s*s*s*s*s*s*s*color*v_fog*glossMult; + + DoAlphaTest(color.a); + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/neoGloss.vert b/src/extras/shaders/neoGloss.vert new file mode 100644 index 0000000..41102f3 --- /dev/null +++ b/src/extras/shaders/neoGloss.vert @@ -0,0 +1,25 @@ +uniform vec3 u_eye; + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec3 v_normal; +VSOUT vec3 v_light; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + + vec3 viewVec = normalize(u_eye - Vertex.xyz); + vec3 Light = normalize(viewVec - u_lightDirection[0].xyz); + v_normal = 0.5*(1.0 + vec3(0.0, 0.0, 1.0)); // compress + v_light = 0.5*(1.0 + Light); // + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/extras/shaders/neoGloss_PS.hlsl b/src/extras/shaders/neoGloss_PS.hlsl new file mode 100644 index 0000000..b3c9763 --- /dev/null +++ b/src/extras/shaders/neoGloss_PS.hlsl @@ -0,0 +1,20 @@ +sampler2D tex0 : register(s0); +float glossMult : register(c1); + +struct VS_out +{ + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float3 Normal : COLOR0; + float3 Light : COLOR1; +}; + +float4 main(VS_out input) : COLOR +{ + float4 color = tex2D(tex0, input.TexCoord0.xy); + float3 n = 2.0*input.Normal-1.0; // unpack + float3 v = 2.0*input.Light-1.0; // + + float s = dot(n, v); + return s*s*s*s*s*s*s*s*color*input.TexCoord0.z*glossMult; +} diff --git a/src/extras/shaders/neoGloss_VS.hlsl b/src/extras/shaders/neoGloss_VS.hlsl new file mode 100644 index 0000000..d166171 --- /dev/null +++ b/src/extras/shaders/neoGloss_VS.hlsl @@ -0,0 +1,35 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float2 TexCoord : TEXCOORD0; +}; + +struct VS_out +{ + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float3 Normal : COLOR0; + float3 Light : COLOR1; +}; + +float3 eye : register(c41); + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + output.TexCoord0.xy = input.TexCoord; + + float3 viewVec = normalize(eye - Vertex); + float3 Light = normalize(viewVec - lights[0].direction.xyz); + output.Normal = 0.5*(1.0 + float3(0.0, 0.0, 1.0)); // compress + output.Light = 0.5*(1.0 + Light); // + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/neoRim.vert b/src/extras/shaders/neoRim.vert new file mode 100644 index 0000000..81ee109 --- /dev/null +++ b/src/extras/shaders/neoRim.vert @@ -0,0 +1,34 @@ +uniform vec3 u_viewVec; +uniform vec4 u_rampStart; +uniform vec4 u_rampEnd; +uniform vec3 u_rimData; + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + + // rim light + float f = u_rimData.x - u_rimData.y*dot(Normal, u_viewVec); + vec4 rimlight = clamp(mix(u_rampEnd, u_rampStart, f)*u_rimData.z, 0.0, 1.0); + v_color.rgb += rimlight.rgb; + + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/extras/shaders/neoRimSkin.vert b/src/extras/shaders/neoRimSkin.vert new file mode 100644 index 0000000..1515ad7 --- /dev/null +++ b/src/extras/shaders/neoRimSkin.vert @@ -0,0 +1,43 @@ +uniform mat4 u_boneMatrices[64]; + +uniform vec3 u_viewVec; +uniform vec4 u_rampStart; +uniform vec4 u_rampEnd; +uniform vec3 u_rimData; + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + vec3 SkinVertex = vec3(0.0, 0.0, 0.0); + vec3 SkinNormal = vec3(0.0, 0.0, 0.0); + for(int i = 0; i < 4; i++){ + SkinVertex += (u_boneMatrices[int(in_indices[i])] * vec4(in_pos, 1.0)).xyz * in_weights[i]; + SkinNormal += (mat3(u_boneMatrices[int(in_indices[i])]) * in_normal) * in_weights[i]; + } + + vec4 Vertex = u_world * vec4(SkinVertex, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * SkinNormal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + + // rim light + float f = u_rimData.x - u_rimData.y*dot(Normal, u_viewVec); + vec4 rimlight = clamp(mix(u_rampEnd, u_rampStart, f)*u_rimData.z, 0.0, 1.0); + v_color.rgb += rimlight.rgb; + + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.z); +} diff --git a/src/extras/shaders/neoRimSkin_VS.hlsl b/src/extras/shaders/neoRimSkin_VS.hlsl new file mode 100644 index 0000000..87cc093 --- /dev/null +++ b/src/extras/shaders/neoRimSkin_VS.hlsl @@ -0,0 +1,73 @@ +#include "standardConstants.h" + +float4x3 boneMatrices[64] : register(c41); + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; + float4 Weights : BLENDWEIGHT; + int4 Indices : BLENDINDICES; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float4 Color : COLOR0; +}; + +float3 viewVec : register(c233); +float4 rampStart : register(c234); +float4 rampEnd : register(c235); +float3 rimData : register(c236); + +VS_out main(in VS_in input) +{ + VS_out output; + + int j; + float3 SkinVertex = float3(0.0, 0.0, 0.0); + float3 SkinNormal = float3(0.0, 0.0, 0.0); + for(j = 0; j < 4; j++){ + SkinVertex += mul(input.Position, boneMatrices[input.Indices[j]]).xyz * input.Weights[j]; + SkinNormal += mul(input.Normal, (float3x3)boneMatrices[input.Indices[j]]).xyz * input.Weights[j]; + } + + output.Position = mul(combinedMat, float4(SkinVertex, 1.0)); + float3 Vertex = mul(worldMat, float4(SkinVertex, 1.0)).xyz; + float3 Normal = mul(normalMat, SkinNormal); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +//#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +//#endif +//#ifdef POINTLIGHTS +// for(i = 0; i < numPointLights; i++) +// output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +//#endif +//#ifdef SPOTLIGHTS +// for(i = 0; i < numSpotLights; i++) +// output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +//#endif + + // rim light + float f = rimData.x - rimData.y*dot(Normal, viewVec); + float4 rimlight = saturate(lerp(rampEnd, rampStart, f)*rimData.z); + output.Color.xyz += rimlight.xyz; + + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/neoRim_VS.hlsl b/src/extras/shaders/neoRim_VS.hlsl new file mode 100644 index 0000000..7f95166 --- /dev/null +++ b/src/extras/shaders/neoRim_VS.hlsl @@ -0,0 +1,61 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float4 Color : COLOR0; +}; + +float3 viewVec : register(c233); +float4 rampStart : register(c234); +float4 rampEnd : register(c235); +float3 rimData : register(c236); + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + float3 Normal = mul(normalMat, input.Normal); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +//#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +//#endif +//#ifdef POINTLIGHTS +// for(i = 0; i < numPointLights; i++) +// output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +//#endif +//#ifdef SPOTLIGHTS +// for(i = 0; i < numSpotLights; i++) +// output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +//#endif + + // rim light + float f = rimData.x - rimData.y*dot(Normal, viewVec); + float4 rimlight = saturate(lerp(rampEnd, rampStart, f)*rimData.z); + output.Color.xyz += rimlight.xyz; + + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/neoVehicle.frag b/src/extras/shaders/neoVehicle.frag new file mode 100644 index 0000000..2ac24f7 --- /dev/null +++ b/src/extras/shaders/neoVehicle.frag @@ -0,0 +1,29 @@ +uniform sampler2D tex0; +uniform sampler2D tex1; + +FSIN vec4 v_color; +FSIN vec4 v_reflcolor; +FSIN vec2 v_tex0; +FSIN vec2 v_tex1; +FSIN float v_fog; + +void +main(void) +{ + vec4 pass1 = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec3 envmap = texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y)).rgb; + pass1.rgb = mix(pass1.rgb, envmap, v_reflcolor.a); + pass1.rgb = mix(u_fogColor.rgb, pass1.rgb, v_fog); +// pass1.rgb += v_reflcolor.rgb * v_fog; + + vec3 pass2 = v_reflcolor.rgb * v_fog; + + vec4 color; + color.rgb = pass1.rgb*pass1.a + pass2; + color.a = pass1.a; + +// color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog); + DoAlphaTest(color.a); + + FRAGCOLOR(color); +} diff --git a/src/extras/shaders/neoVehicle.vert b/src/extras/shaders/neoVehicle.vert new file mode 100644 index 0000000..6985a68 --- /dev/null +++ b/src/extras/shaders/neoVehicle.vert @@ -0,0 +1,51 @@ +uniform vec3 u_eye; +uniform vec4 u_reflProps; +uniform vec4 u_specDir[5]; +uniform vec4 u_specColor[5]; + +#define fresnel (u_reflProps.x) +#define lightStrength (u_reflProps.y) // speclight alpha +#define shininess (u_reflProps.z) +#define specularity (u_reflProps.w) + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec4 v_reflcolor; +VSOUT vec2 v_tex0; +VSOUT vec2 v_tex1; +VSOUT float v_fog; + +vec3 DoDirLightSpec(vec3 Ldir, vec3 Lcol, vec3 N, vec3 V, float power) +{ + return pow(clamp(dot(N, normalize(V + -Ldir)), 0.0, 1.0), power)*Lcol; +} + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + vec3 viewVec = normalize(u_eye - Vertex.xyz); + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse*lightStrength; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + // reflect V along Normal + vec3 uv2 = Normal*dot(viewVec, Normal)*2.0 - viewVec; + v_tex1 = uv2.xy*0.5 + 0.5; + float b = 1.0 - clamp(dot(viewVec, Normal), 0.0, 1.0); + v_reflcolor = vec4(0.0, 0.0, 0.0, 1.0); + v_reflcolor.a = mix(b*b*b*b*b, 1.0, fresnel)*shininess; + + for(int i = 0; i < 5; i++) + v_reflcolor.rgb += DoDirLightSpec(u_specDir[i].xyz, u_specColor[i].rgb, Normal, viewVec, u_specDir[i].w)*specularity*lightStrength; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/extras/shaders/neoVehicle_PS.hlsl b/src/extras/shaders/neoVehicle_PS.hlsl new file mode 100644 index 0000000..fa030dd --- /dev/null +++ b/src/extras/shaders/neoVehicle_PS.hlsl @@ -0,0 +1,34 @@ +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; + float4 ReflColor : COLOR1; +}; + +sampler2D tex0 : register(s0); +sampler2D tex1 : register(s1); + +float4 fogColor : register(c0); + +float4 main(VS_out input) : COLOR +{ + float4 pass1 = input.Color; +//#ifdef TEX + pass1 *= tex2D(tex0, input.TexCoord0.xy); +//#endif + float3 envmap = tex2D(tex1, input.TexCoord1).rgb; + pass1.rgb = lerp(pass1.rgb, envmap, input.ReflColor.a); +// pass1.rgb = envmap; +// pass1.rgb *= input.ReflColor.a; + pass1.rgb = lerp(fogColor.rgb, pass1.rgb, input.TexCoord0.z); +// pass1.rgb += input.ReflColor.rgb * input.TexCoord0.z; + + float3 pass2 = input.ReflColor.rgb*input.TexCoord0.z; + + float4 color; + color.rgb = pass1.rgb*pass1.a + pass2; + color.a = pass1.a; + + return color; +} diff --git a/src/extras/shaders/neoVehicle_VS.hlsl b/src/extras/shaders/neoVehicle_VS.hlsl new file mode 100644 index 0000000..fb73009 --- /dev/null +++ b/src/extras/shaders/neoVehicle_VS.hlsl @@ -0,0 +1,64 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; + float4 ReflColor : COLOR1; +}; + +float3 eye : register(c41); +float4 reflProps : register(c42); +Light specLights[5] : register(c43); + + +#define fresnel (reflProps.x) +#define lightStrength (reflProps.y) // speclight alpha +#define shininess (reflProps.z) +#define specularity (reflProps.w) + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + float3 Normal = mul(normalMat, input.Normal); + float3 viewVec = normalize(eye - Vertex); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient*lightStrength; + + int i; + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse*lightStrength; + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + // reflect V along Normal + float3 uv2 = Normal*dot(viewVec, Normal)*2.0 - viewVec; + output.TexCoord1 = uv2.xy*0.5 + 0.5; + float b = 1.0 - saturate(dot(viewVec, Normal)); + output.ReflColor = float4(0.0, 0.0, 0.0, 1.0); + output.ReflColor.a = lerp(b*b*b*b*b, 1.0, fresnel)*shininess; + + //Light mainLight = lights[0]; + for(i = 0; i < 5; i++) + output.ReflColor.xyz += DoDirLightSpec(specLights[i], Normal, viewVec, specLights[i].direction.w)*specularity*lightStrength; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/extras/shaders/neoWorldIII.frag b/src/extras/shaders/neoWorldIII.frag new file mode 100644 index 0000000..d8bb715 --- /dev/null +++ b/src/extras/shaders/neoWorldIII.frag @@ -0,0 +1,25 @@ +uniform sampler2D tex0; +uniform sampler2D tex1; + +uniform vec4 u_lightMap; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN vec2 v_tex1; +FSIN float v_fog; + +void +main(void) +{ + vec4 t0 = texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + vec4 t1 = texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y)); + + vec4 color = t0*v_color*(1.0 + u_lightMap*(2.0*t1-1.0)); + color.a = v_color.a*t0.a*u_lightMap.a; + + color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog); + DoAlphaTest(color.a); + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/neoWorldIII_PS.hlsl b/src/extras/shaders/neoWorldIII_PS.hlsl new file mode 100644 index 0000000..8a3d5d8 --- /dev/null +++ b/src/extras/shaders/neoWorldIII_PS.hlsl @@ -0,0 +1,25 @@ +sampler2D Diffuse : register(s0); +sampler2D Light : register(s1); +float4 fogColor : register(c0); +float4 lm : register(c1); + +struct PS_INPUT +{ + float4 Color : COLOR0; + float3 Tex0 : TEXCOORD0; + float2 Tex1 : TEXCOORD1; +}; + +float4 +main(PS_INPUT IN) : COLOR +{ + float4 t0 = tex2D(Diffuse, IN.Tex0.xy); + float4 t1 = tex2D(Light, IN.Tex1); + + float4 col = t0*IN.Color*(1 + lm*(2*t1-1)); + col.a = IN.Color.a*t0.a*lm.a; + + col.rgb = lerp(fogColor.rgb, col.rgb, IN.Tex0.z); + + return col; +} diff --git a/src/extras/shaders/screenDroplet.frag b/src/extras/shaders/screenDroplet.frag new file mode 100644 index 0000000..84d30bd --- /dev/null +++ b/src/extras/shaders/screenDroplet.frag @@ -0,0 +1,18 @@ +uniform sampler2D tex0; +uniform sampler2D tex1; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN vec2 v_tex1; +FSIN float v_fog; + +void +main(void) +{ + vec4 color; + color = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + color *= texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y)); + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/screenDroplet_PS.hlsl b/src/extras/shaders/screenDroplet_PS.hlsl new file mode 100644 index 0000000..4d41da6 --- /dev/null +++ b/src/extras/shaders/screenDroplet_PS.hlsl @@ -0,0 +1,17 @@ +struct VS_out { + float4 Position : POSITION; + float2 TexCoord0 : TEXCOORD0; + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; +}; + +sampler2D tex0 : register(s0); +sampler2D tex1 : register(s1); + +float4 main(VS_out input) : COLOR +{ + float4 color = input.Color; + color *= tex2D(tex0, input.TexCoord0.xy); + color *= tex2D(tex1, input.TexCoord1.xy); + return color; +} diff --git a/src/extras/shaders/simple.frag b/src/extras/shaders/simple.frag new file mode 100644 index 0000000..c85bf08 --- /dev/null +++ b/src/extras/shaders/simple.frag @@ -0,0 +1,17 @@ +uniform sampler2D tex0; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN float v_fog; + +void +main(void) +{ + vec4 color; + color = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog); + DoAlphaTest(color.a); + + FRAGCOLOR(color); +} + diff --git a/src/extras/shaders/standardConstants.h b/src/extras/shaders/standardConstants.h new file mode 100644 index 0000000..088df7d --- /dev/null +++ b/src/extras/shaders/standardConstants.h @@ -0,0 +1,28 @@ +float4x4 combinedMat : register(c0); +float4x4 worldMat : register(c4); +float3x3 normalMat : register(c8); +float4 matCol : register(c12); +float4 surfProps : register(c13); +float4 fogData : register(c14); +float4 ambientLight : register(c15); + +#define surfAmbient (surfProps.x) +#define surfSpecular (surfProps.y) +#define surfDiffuse (surfProps.z) + +#define fogStart (fogData.x) +#define fogEnd (fogData.y) +#define fogRange (fogData.z) +#define fogDisable (fogData.w) + +#include "lighting.h" + +int numDirLights : register(i0); +int numPointLights : register(i1); +int numSpotLights : register(i2); +int4 firstLight : register(c16); +Light lights[8] : register(c17); + +#define firstDirLight (firstLight.x) +#define firstPointLight (firstLight.y) +#define firstSpotLight (firstLight.z) diff --git a/src/fakerw/fake.cpp b/src/fakerw/fake.cpp new file mode 100644 index 0000000..6dfebb3 --- /dev/null +++ b/src/fakerw/fake.cpp @@ -0,0 +1,1010 @@ +#define _CRT_SECURE_NO_WARNINGS +#define WITH_D3D // not WITHD3D, so it's librw define +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include "crossplatform.h" +#endif + +using namespace rw; + +RwUInt8 RwObjectGetType(const RwObject *obj) { return obj->type; } +RwFrame* rwObjectGetParent(const RwObject *obj) { return (RwFrame*)obj->parent; } + +void *RwMalloc(size_t size) { return engine->memfuncs.rwmalloc(size, 0); } +void *RwCalloc(size_t numObj, size_t sizeObj) { + void *mem = RwMalloc(numObj*sizeObj); + if(mem) + memset(mem, 0, numObj*sizeObj); + return mem; +} +void RwFree(void *mem) { engine->memfuncs.rwfree(mem); } + + +//RwReal RwV3dNormalize(RwV3d * out, const RwV3d * in); +RwReal RwV3dLength(const RwV3d * in) { return length(*in); } +//RwReal RwV2dLength(const RwV2d * in); +//RwReal RwV2dNormalize(RwV2d * out, const RwV2d * in); +//void RwV2dAssign(RwV2d * out, const RwV2d * ina); +//void RwV2dAdd(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +//void RwV2dLineNormal(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +//void RwV2dSub(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +//void RwV2dPerp(RwV2d * out, const RwV2d * in); +//void RwV2dScale(RwV2d * out, const RwV2d * in, RwReal scalar); +//RwReal RwV2dDotProduct(const RwV2d * ina, const RwV2d * inb); +//void RwV3dAssign(RwV3d * out, const RwV3d * ina); +void RwV3dAdd(RwV3d * out, const RwV3d * ina, const RwV3d * inb) { *out = add(*ina, *inb); } +void RwV3dSub(RwV3d * out, const RwV3d * ina, const RwV3d * inb) { *out = sub(*ina, *inb); } +void RwV3dScale(RwV3d * out, const RwV3d * in, RwReal scalar) { *out = scale(*in, scalar); } +void RwV3dIncrementScaled(RwV3d * out, const RwV3d * in, RwReal scalar) { *out = add(*out, scale(*in, scalar)); } +void RwV3dNegate(RwV3d * out, const RwV3d * in) { *out = neg(*in); } +RwReal RwV3dDotProduct(const RwV3d * ina, const RwV3d * inb) { return dot(*ina, *inb); } +//void RwV3dCrossProduct(RwV3d * out, const RwV3d * ina, const RwV3d * inb); +RwV3d *RwV3dTransformPoints(RwV3d * pointsOut, const RwV3d * pointsIn, RwInt32 numPoints, const RwMatrix * matrix) + { V3d::transformPoints(pointsOut, pointsIn, numPoints, matrix); return pointsOut; } +//RwV3d *RwV3dTransformVectors(RwV3d * vectorsOut, const RwV3d * vectorsIn, RwInt32 numPoints, const RwMatrix * matrix); + + + +RwBool RwMatrixDestroy(RwMatrix *mpMat) { mpMat->destroy(); return true; } +RwMatrix *RwMatrixCreate(void) { return Matrix::create(); } +void RwMatrixCopy(RwMatrix * dstMatrix, const RwMatrix * srcMatrix) { *dstMatrix = *srcMatrix; } +void RwMatrixSetIdentity(RwMatrix * matrix) { matrix->setIdentity(); } +RwMatrix *RwMatrixMultiply(RwMatrix * matrixOut, const RwMatrix * MatrixIn1, const RwMatrix * matrixIn2); +RwMatrix *RwMatrixTransform(RwMatrix * matrix, const RwMatrix * transform, RwOpCombineType combineOp) + { matrix->transform(transform, (rw::CombineOp)combineOp); return matrix; } +//RwMatrix *RwMatrixOrthoNormalize(RwMatrix * matrixOut, const RwMatrix * matrixIn); +RwMatrix *RwMatrixInvert(RwMatrix * matrixOut, const RwMatrix * matrixIn) { Matrix::invert(matrixOut, matrixIn); return matrixOut; } +RwMatrix *RwMatrixScale(RwMatrix * matrix, const RwV3d * scale, RwOpCombineType combineOp) + { matrix->scale(scale, (rw::CombineOp)combineOp); return matrix; } +RwMatrix *RwMatrixTranslate(RwMatrix * matrix, const RwV3d * translation, RwOpCombineType combineOp) + { matrix->translate(translation, (rw::CombineOp)combineOp); return matrix; } +RwMatrix *RwMatrixRotate(RwMatrix * matrix, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp) + { matrix->rotate(axis, angle, (rw::CombineOp)combineOp); return matrix; } +//RwMatrix *RwMatrixRotateOneMinusCosineSine(RwMatrix * matrix, const RwV3d * unitAxis, RwReal oneMinusCosine, RwReal sine, RwOpCombineType combineOp); +//const RwMatrix *RwMatrixQueryRotate(const RwMatrix * matrix, RwV3d * unitAxis, RwReal * angle, RwV3d * center); +RwV3d *RwMatrixGetRight(RwMatrix * matrix) { return &matrix->right; } +RwV3d *RwMatrixGetUp(RwMatrix * matrix) { return &matrix->up; } +RwV3d *RwMatrixGetAt(RwMatrix * matrix) { return &matrix->at; } +RwV3d *RwMatrixGetPos(RwMatrix * matrix) { return &matrix->pos; } +RwMatrix *RwMatrixUpdate(RwMatrix * matrix) { matrix->update(); return matrix; } +//RwMatrix *RwMatrixOptimize(RwMatrix * matrix, const RwMatrixTolerance *tolerance); + + + + +RwFrame *RwFrameForAllObjects(RwFrame * frame, RwObjectCallBack callBack, void *data) { + FORLIST(lnk, frame->objectList) + if(callBack(&ObjectWithFrame::fromFrame(lnk)->object, data) == nil) + break; + return frame; +} +RwFrame *RwFrameTranslate(RwFrame * frame, const RwV3d * v, RwOpCombineType combine) { frame->translate(v, (CombineOp)combine); return frame; } +RwFrame *RwFrameRotate(RwFrame * frame, const RwV3d * axis, RwReal angle, RwOpCombineType combine) { frame->rotate(axis, angle, (CombineOp)combine); return frame; } +RwFrame *RwFrameScale(RwFrame * frame, const RwV3d * v, RwOpCombineType combine) { frame->scale(v, (CombineOp)combine); return frame; } +RwFrame *RwFrameTransform(RwFrame * frame, const RwMatrix * m, RwOpCombineType combine) { frame->transform(m, (CombineOp)combine); return frame; } +//TODO: actually implement this! +RwFrame *RwFrameOrthoNormalize(RwFrame * frame) { return frame; } +RwFrame *RwFrameSetIdentity(RwFrame * frame) { frame->matrix.setIdentity(); frame->updateObjects(); return frame; } +//RwFrame *RwFrameCloneHierarchy(RwFrame * root); +//RwBool RwFrameDestroyHierarchy(RwFrame * frame); +RwFrame *RwFrameForAllChildren(RwFrame * frame, RwFrameCallBack callBack, void *data) + { return frame->forAllChildren(callBack, data); } +RwFrame *RwFrameRemoveChild(RwFrame * child) { child->removeChild(); return child; } +RwFrame *RwFrameAddChild(RwFrame * parent, RwFrame * child) { parent->addChild(child); return parent; } +RwFrame *RwFrameGetParent(const RwFrame * frame) { return frame->getParent(); } +//RwFrame *RwFrameGetRoot(const RwFrame * frame); +RwMatrix *RwFrameGetLTM(RwFrame * frame) { return frame->getLTM(); } +RwMatrix *RwFrameGetMatrix(RwFrame * frame) { return &frame->matrix; } +RwFrame *RwFrameUpdateObjects(RwFrame * frame) { frame->updateObjects(); return frame; } +RwFrame *RwFrameCreate(void) { return rw::Frame::create(); } +//RwBool RwFrameInit(RwFrame *frame); +//RwBool RwFrameDeInit(RwFrame *frame); +RwBool RwFrameDestroy(RwFrame * frame) { frame->destroy(); return true; } +//void _rwFrameInit(RwFrame *frame); +//void _rwFrameDeInit(RwFrame *frame); +//RwBool RwFrameDirty(const RwFrame * frame); +//RwInt32 RwFrameCount(RwFrame * frame); +//RwBool RwFrameSetStaticPluginsSize(RwInt32 size); +RwInt32 RwFrameRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) + { return Frame::registerPlugin(size, pluginID, constructCB, destructCB, (CopyConstructor)copyCB); } +//RwInt32 RwFrameGetPluginOffset(RwUInt32 pluginID); +//RwBool RwFrameValidatePlugins(const RwFrame * frame); +//RwFrame *_rwFrameCloneAndLinkClones(RwFrame * root); +//RwFrame *_rwFramePurgeClone(RwFrame *root); + +RwInt32 RwFrameRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) + { return Frame::registerPluginStream(pluginID, readCB, (StreamWrite)writeCB, (StreamGetSize)getSizeCB); } + + +rwFrameList *rwFrameListDeinitialize(rwFrameList *frameList) { + rwFree(frameList->frames); + frameList->frames = nil; + return frameList; +} +rwFrameList *rwFrameListStreamRead(RwStream *stream, rwFrameList *fl) { return fl->streamRead(stream); } + + + + +RwCamera *RwCameraBeginUpdate(RwCamera * camera) { camera->beginUpdate(); return camera; } +RwCamera *RwCameraEndUpdate(RwCamera * camera) { camera->endUpdate(); return camera; } +RwCamera *RwCameraClear(RwCamera * camera, RwRGBA * colour, RwInt32 clearMode) { camera->clear(colour, clearMode); return camera; } +// WARNING: ignored argument +RwCamera *RwCameraShowRaster(RwCamera * camera, void *pDev, RwUInt32 flags) { camera->showRaster(flags); return camera; } +RwBool RwCameraDestroy(RwCamera * camera) { camera->destroy(); return true; } +RwCamera *RwCameraCreate(void) { return rw::Camera::create(); } +RwCamera *RwCameraClone(RwCamera * camera) { return camera->clone(); } +RwCamera *RwCameraSetViewOffset(RwCamera *camera, const RwV2d *offset) { camera->setViewOffset(offset); return camera; } +RwCamera *RwCameraSetViewWindow(RwCamera *camera, const RwV2d *viewWindow) { camera->setViewWindow(viewWindow); return camera; } +RwCamera *RwCameraSetProjection(RwCamera *camera, RwCameraProjection projection) { camera->projection = projection; return camera; } +RwCamera *RwCameraSetNearClipPlane(RwCamera *camera, RwReal nearClip) { camera->setNearPlane(nearClip); return camera; } +RwCamera *RwCameraSetFarClipPlane(RwCamera *camera, RwReal farClip) { camera->setFarPlane(farClip); return camera; } +RwInt32 RwCameraRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwCameraGetPluginOffset(RwUInt32 pluginID); +RwBool RwCameraValidatePlugins(const RwCamera * camera); +RwFrustumTestResult RwCameraFrustumTestSphere(const RwCamera * camera, const RwSphere * sphere) { return (RwFrustumTestResult)camera->frustumTestSphere(sphere); } +const RwV2d *RwCameraGetViewOffset(const RwCamera *camera) { return &camera->viewOffset; } +RwCamera *RwCameraSetRaster(RwCamera *camera, RwRaster *raster) { camera->frameBuffer = raster; return camera; } +RwRaster *RwCameraGetRaster(const RwCamera *camera) { return camera->frameBuffer; } +RwCamera *RwCameraSetZRaster(RwCamera *camera, RwRaster *zRaster) { camera->zBuffer = zRaster; return camera; } +RwRaster *RwCameraGetZRaster(const RwCamera *camera) { return camera->zBuffer; } +RwReal RwCameraGetNearClipPlane(const RwCamera *camera) { return camera->nearPlane; } +RwReal RwCameraGetFarClipPlane(const RwCamera *camera) { return camera->farPlane; } +RwCamera *RwCameraSetFogDistance(RwCamera *camera, RwReal fogDistance) { camera->fogPlane = fogDistance; return camera; } +RwReal RwCameraGetFogDistance(const RwCamera *camera) { return camera->fogPlane; } +RwCamera *RwCameraGetCurrentCamera(void) { return rw::engine->currentCamera; } +RwCameraProjection RwCameraGetProjection(const RwCamera *camera); +const RwV2d *RwCameraGetViewWindow(const RwCamera *camera) { return &camera->viewWindow; } +RwMatrix *RwCameraGetViewMatrix(RwCamera *camera) { return &camera->viewMatrix; } +RwCamera *RwCameraSetFrame(RwCamera *camera, RwFrame *frame) { camera->setFrame(frame); return camera; } +RwFrame *RwCameraGetFrame(const RwCamera *camera) { return camera->getFrame(); } + + + + + +RwImage *RwImageCreate(RwInt32 width, RwInt32 height, RwInt32 depth) { return Image::create(width, height, depth); } +RwBool RwImageDestroy(RwImage * image) { image->destroy(); return true; } +RwImage *RwImageAllocatePixels(RwImage * image) { image->allocate(); return image; } +RwImage *RwImageFreePixels(RwImage * image) { image->free(); return image; } +RwImage *RwImageCopy(RwImage * destImage, const RwImage * sourceImage); +RwImage *RwImageResize(RwImage * image, RwInt32 width, RwInt32 height); +RwImage *RwImageApplyMask(RwImage * image, const RwImage * mask); +RwImage *RwImageMakeMask(RwImage * image); +RwImage *RwImageReadMaskedImage(const RwChar * imageName, const RwChar * maskname); +RwImage *RwImageRead(const RwChar * imageName); +RwImage *RwImageWrite(RwImage * image, const RwChar * imageName); +RwChar *RwImageGetPath(void); +const RwChar *RwImageSetPath(const RwChar * path) { Image::setSearchPath(path); return path; } +RwImage *RwImageSetStride(RwImage * image, RwInt32 stride) { image->stride = stride; return image; } +RwImage *RwImageSetPixels(RwImage * image, RwUInt8 * pixels) { image->pixels = pixels; return image; } +RwImage *RwImageSetPalette(RwImage * image, RwRGBA * palette) { image->palette = (uint8*)palette; return image; } +RwInt32 RwImageGetWidth(const RwImage * image) { return image->width; } +RwInt32 RwImageGetHeight(const RwImage * image) { return image->height; } +RwInt32 RwImageGetDepth(const RwImage * image) { return image->depth; } +RwInt32 RwImageGetStride(const RwImage * image) { return image->stride; } +RwUInt8 *RwImageGetPixels(const RwImage * image) { return image->pixels; } +RwRGBA *RwImageGetPalette(const RwImage * image) { return (RwRGBA*)image->palette; } +RwUInt32 RwRGBAToPixel(RwRGBA * rgbIn, RwInt32 rasterFormat); +RwRGBA *RwRGBASetFromPixel(RwRGBA * rgbOut, RwUInt32 pixelValue, RwInt32 rasterFormat); +RwBool RwImageSetGamma(RwReal gammaValue); +RwReal RwImageGetGamma(void); +RwImage *RwImageGammaCorrect(RwImage * image); +RwRGBA *RwRGBAGammaCorrect(RwRGBA * rgb); +RwInt32 RwImageRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwImageGetPluginOffset(RwUInt32 pluginID); +RwBool RwImageValidatePlugins(const RwImage * image); +//RwBool RwImageRegisterImageFormat(const RwChar * extension, RwImageCallBackRead imageRead, RwImageCallBackWrite imageWrite); +const RwChar *RwImageFindFileType(const RwChar * imageName); +RwInt32 RwImageStreamGetSize(const RwImage * image); +RwImage *RwImageStreamRead(RwStream * stream); +const RwImage *RwImageStreamWrite(const RwImage * image, RwStream * stream); + +RwImage *RwImageFindRasterFormat(RwImage *ipImage,RwInt32 nRasterType, RwInt32 *npWidth,RwInt32 *npHeight, RwInt32 *npDepth,RwInt32 *npFormat) +{ + return Raster::imageFindRasterFormat(ipImage, nRasterType, npWidth, npHeight, npDepth, npFormat) ? ipImage : nil; +} + + + + +RwRaster *RwRasterCreate(RwInt32 width, RwInt32 height, RwInt32 depth, RwInt32 flags) { return Raster::create(width, height, depth, flags); } +RwBool RwRasterDestroy(RwRaster * raster) { raster->destroy(); return true; } +RwInt32 RwRasterGetWidth(const RwRaster *raster) { return raster->width; } +RwInt32 RwRasterGetHeight(const RwRaster *raster) { return raster->height; } +RwInt32 RwRasterGetStride(const RwRaster *raster); +RwInt32 RwRasterGetDepth(const RwRaster *raster) { return raster->depth; } +RwInt32 RwRasterGetFormat(const RwRaster *raster); +RwInt32 RwRasterGetType(const RwRaster *raster); +RwRaster *RwRasterGetParent(const RwRaster *raster) { return raster->parent; } +RwRaster *RwRasterGetOffset(RwRaster *raster, RwInt16 *xOffset, RwInt16 *yOffset); +RwInt32 RwRasterGetNumLevels(RwRaster * raster); +RwRaster *RwRasterSubRaster(RwRaster * subRaster, RwRaster * raster, RwRect * rect); +RwRaster *RwRasterRenderFast(RwRaster * raster, RwInt32 x, RwInt32 y) { return raster->renderFast(x, y) ? raster : nil; } +RwRaster *RwRasterRender(RwRaster * raster, RwInt32 x, RwInt32 y); +RwRaster *RwRasterRenderScaled(RwRaster * raster, RwRect * rect); +RwRaster *RwRasterPushContext(RwRaster * raster) { return Raster::pushContext(raster); } +RwRaster *RwRasterPopContext(void) { return Raster::popContext(); } +RwRaster *RwRasterGetCurrentContext(void) { return Raster::getCurrentContext(); } +RwBool RwRasterClear(RwInt32 pixelValue); +RwBool RwRasterClearRect(RwRect * rpRect, RwInt32 pixelValue); +RwRaster *RwRasterShowRaster(RwRaster * raster, void *dev, RwUInt32 flags); +RwUInt8 *RwRasterLock(RwRaster * raster, RwUInt8 level, RwInt32 lockMode) { return raster->lock(level, lockMode); } +RwRaster *RwRasterUnlock(RwRaster * raster) { raster->unlock(0); return raster; } +RwUInt8 *RwRasterLockPalette(RwRaster * raster, RwInt32 lockMode); +RwRaster *RwRasterUnlockPalette(RwRaster * raster); +RwInt32 RwRasterRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwRasterGetPluginOffset(RwUInt32 pluginID); +RwBool RwRasterValidatePlugins(const RwRaster * raster); + +RwRaster *RwRasterSetFromImage(RwRaster *raster, RwImage *image) { return raster->setFromImage(image); } + + + + +RwTexture *RwTextureCreate(RwRaster * raster) { return Texture::create(raster); } +RwBool RwTextureDestroy(RwTexture * texture) { texture->destroy(); return true; } +RwTexture *RwTextureAddRef(RwTexture *texture) { texture->addRef(); return texture; } +// TODO +RwBool RwTextureSetMipmapping(RwBool enable) { return true; } +RwBool RwTextureGetMipmapping(void); +// TODO +RwBool RwTextureSetAutoMipmapping(RwBool enable) { return true; } +RwBool RwTextureGetAutoMipmapping(void); +RwBool RwTextureSetMipmapGenerationCallBack(RwTextureCallBackMipmapGeneration callback); +RwTextureCallBackMipmapGeneration RwTextureGetMipmapGenerationCallBack(void); +RwBool RwTextureSetMipmapNameCallBack(RwTextureCallBackMipmapName callback); +RwTextureCallBackMipmapName RwTextureGetMipmapNameCallBack(void); +RwBool RwTextureGenerateMipmapName(RwChar * name, RwChar * maskName, RwUInt8 mipLevel, RwInt32 format); +RwBool RwTextureRasterGenerateMipmaps(RwRaster * raster, RwImage * image); +RwTextureCallBackRead RwTextureGetReadCallBack(void); +RwBool RwTextureSetReadCallBack(RwTextureCallBackRead fpCallBack); +RwTexture *RwTextureSetName(RwTexture * texture, const RwChar * name) { strncpy(texture->name, name, 32); return texture; } +RwTexture *RwTextureSetMaskName(RwTexture * texture, const RwChar * maskName); +RwChar *RwTextureGetName(RwTexture *texture) { return texture->name; } +RwChar *RwTextureGetMaskName(RwTexture *texture); +RwTexture *RwTextureSetRaster(RwTexture * texture, RwRaster * raster) { texture->raster = raster; return texture; } +RwTexture *RwTextureRead(const RwChar * name, const RwChar * maskName) { return Texture::read(name, maskName); } +RwRaster *RwTextureGetRaster(const RwTexture *texture) { return texture->raster; } +RwInt32 RwTextureRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwTextureGetPluginOffset(RwUInt32 pluginID); +RwBool RwTextureValidatePlugins(const RwTexture * texture); + +RwTexDictionary *RwTextureGetDictionary(RwTexture *texture); +RwTexture *RwTextureSetFilterMode(RwTexture *texture, RwTextureFilterMode filtering) { texture->setFilter((Texture::FilterMode)filtering); return texture; } +RwTextureFilterMode RwTextureGetFilterMode(const RwTexture *texture); +RwTexture *RwTextureSetAddressing(RwTexture *texture, RwTextureAddressMode addressing) { + texture->setAddressU((Texture::Addressing)addressing); + texture->setAddressV((Texture::Addressing)addressing); + return texture; +} +RwTexture *RwTextureSetAddressingU(RwTexture *texture, RwTextureAddressMode addressing) { + texture->setAddressU((Texture::Addressing)addressing); + return texture; +} +RwTexture *RwTextureSetAddressingV(RwTexture *texture, RwTextureAddressMode addressing) { + texture->setAddressV((Texture::Addressing)addressing); + return texture; +} +RwTextureAddressMode RwTextureGetAddressing(const RwTexture *texture); +RwTextureAddressMode RwTextureGetAddressingU(const RwTexture *texture); +RwTextureAddressMode RwTextureGetAddressingV(const RwTexture *texture); + +// TODO +void _rwD3D8TexDictionaryEnableRasterFormatConversion(bool enable) { } + +// hack for reading native textures +RwBool rwNativeTextureHackRead(RwStream *stream, RwTexture **tex, RwInt32 size) +{ + *tex = Texture::streamReadNative(stream); +#ifdef LIBRW + (*tex)->raster = rw::Raster::convertTexToCurrentPlatform((*tex)->raster); +#endif + return *tex != nil; +} + + + + + +RwTexDictionary *RwTexDictionaryCreate(void) { return TexDictionary::create(); } +RwBool RwTexDictionaryDestroy(RwTexDictionary * dict) { dict->destroy(); return true; } +RwTexture *RwTexDictionaryAddTexture(RwTexDictionary * dict, RwTexture * texture) { dict->addFront(texture); return texture; } +//RwTexture *RwTexDictionaryRemoveTexture(RwTexture * texture); +RwTexture *RwTexDictionaryFindNamedTexture(RwTexDictionary * dict, const RwChar * name) { return dict->find(name); } +RwTexDictionary *RwTexDictionaryGetCurrent(void) { return TexDictionary::getCurrent(); } +RwTexDictionary *RwTexDictionarySetCurrent(RwTexDictionary * dict) { TexDictionary::setCurrent(dict); return dict; } +const RwTexDictionary *RwTexDictionaryForAllTextures(const RwTexDictionary * dict, RwTextureCallBack fpCallBack, void *pData) { + FORLIST(lnk, ((RwTexDictionary*)dict)->textures) + if(fpCallBack(Texture::fromDict(lnk), pData) == nil) + break; + return dict; +} +RwBool RwTexDictionaryForAllTexDictionaries(RwTexDictionaryCallBack fpCallBack, void *pData); +RwInt32 RwTexDictionaryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwTexDictionaryGetPluginOffset(RwUInt32 pluginID); +RwBool RwTexDictionaryValidatePlugins(const RwTexDictionary * dict); +RwUInt32 RwTexDictionaryStreamGetSize(const RwTexDictionary *texDict); +RwTexDictionary *RwTexDictionaryStreamRead(RwStream *stream); +const RwTexDictionary *RwTexDictionaryStreamWrite(const RwTexDictionary *texDict, RwStream *stream) { + ((RwTexDictionary*)texDict)->streamWrite(stream); + return texDict; +} + + + + + +RwStream *RwStreamOpen(RwStreamType type, RwStreamAccessType accessType, const void *pData) { + StreamFile *file; + StreamMemory *mem; + RwMemory *memargs; + const char *mode; + + switch(accessType){ + case rwSTREAMREAD: mode = "rb"; break; + case rwSTREAMWRITE: mode = "wb"; break; + case rwSTREAMAPPEND: mode = "ab"; break; + default: return nil; + } + + // oh god this is horrible. librw streams really need fixing + switch(type){ + case rwSTREAMFILENAME:{ + StreamFile fakefile; + file = rwNewT(StreamFile, 1, 0); + memcpy(file, &fakefile, sizeof(StreamFile)); +#ifndef _WIN32 + char *r = casepath((char*)pData); + if (r) { + if (file->open((char*)r, mode)) { + free(r); + return file; + } + free(r); + } else +#endif + { + if (file->open((char*)pData, mode)) + return file; + } + rwFree(file); + return nil; + } + case rwSTREAMMEMORY:{ + StreamMemory fakemem; + memargs = (RwMemory*)pData; + mem = rwNewT(StreamMemory, 1, 0); + memcpy(mem, &fakemem, sizeof(StreamMemory)); + if(mem->open(memargs->start, memargs->length)) + return mem; + rwFree(mem); + return nil; + } + default: + assert(0 && "unknown type"); + return nil; + } +} +RwBool RwStreamClose(RwStream * stream, void *pData) { stream->close(); rwFree(stream); return true; } +RwUInt32 RwStreamRead(RwStream * stream, void *buffer, RwUInt32 length) { return stream->read8(buffer, length); } +RwStream *RwStreamWrite(RwStream * stream, const void *buffer, RwUInt32 length) { stream->write8(buffer, length); return stream; } +RwStream *RwStreamSkip(RwStream * stream, RwUInt32 offset) { stream->seek(offset); return stream; } + +RwBool RwStreamFindChunk(RwStream *stream, RwUInt32 type, RwUInt32 *lengthOut, RwUInt32 *versionOut) + { return findChunk(stream, type, lengthOut, versionOut); } + + + +void RwIm2DVertexSetCameraX(RwIm2DVertex *vert, RwReal camx) { } +void RwIm2DVertexSetCameraY(RwIm2DVertex *vert, RwReal camy) { } +void RwIm2DVertexSetCameraZ(RwIm2DVertex *vert, RwReal camz) { vert->setCameraZ(camz); } +void RwIm2DVertexSetRecipCameraZ(RwIm2DVertex *vert, RwReal recipz) { vert->setRecipCameraZ(recipz); } +void RwIm2DVertexSetScreenX(RwIm2DVertex *vert, RwReal scrnx) { vert->setScreenX(scrnx); } +void RwIm2DVertexSetScreenY(RwIm2DVertex *vert, RwReal scrny) { vert->setScreenY(scrny); } +void RwIm2DVertexSetScreenZ(RwIm2DVertex *vert, RwReal scrnz) { vert->setScreenZ(scrnz); } +void RwIm2DVertexSetU(RwIm2DVertex *vert, RwReal texU, RwReal recipz) { vert->setU(texU, recipz); } +void RwIm2DVertexSetV(RwIm2DVertex *vert, RwReal texV, RwReal recipz) { vert->setV(texV, recipz); } +void RwIm2DVertexSetIntRGBA(RwIm2DVertex *vert, RwUInt8 red, RwUInt8 green, RwUInt8 blue, RwUInt8 alpha) { vert->setColor(red, green, blue, alpha); } + +RwReal RwIm2DGetNearScreenZ(void) { return im2d::GetNearZ(); } +RwReal RwIm2DGetFarScreenZ(void) { return im2d::GetFarZ(); } +RwBool RwIm2DRenderLine(RwIm2DVertex *vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2) + { im2d::RenderLine(vertices, numVertices, vert1, vert2); return true; } +RwBool RwIm2DRenderTriangle(RwIm2DVertex *vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2, RwInt32 vert3 ) + { im2d::RenderTriangle(vertices, numVertices, vert1, vert2, vert3); return true; } +RwBool RwIm2DRenderPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices) + { im2d::RenderPrimitive((PrimitiveType)primType, vertices, numVertices); return true; } +RwBool RwIm2DRenderIndexedPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices) + { im2d::RenderIndexedPrimitive((PrimitiveType)primType, vertices, numVertices, indices, numIndices); return true; } + + +void RwIm3DVertexSetPos(RwIm3DVertex *vert, RwReal x, RwReal y, RwReal z) { vert->setX(x); vert->setY(y); vert->setZ(z); } +void RwIm3DVertexSetU(RwIm3DVertex *vert, RwReal u) { vert->setU(u); } +void RwIm3DVertexSetV(RwIm3DVertex *vert, RwReal v) { vert->setV(v); } +void RwIm3DVertexSetRGBA(RwIm3DVertex *vert, RwUInt8 r, RwUInt8 g, RwUInt8 b, RwUInt8 a) { vert->setColor(r, g, b, a); } + +void *RwIm3DTransform(RwIm3DVertex *pVerts, RwUInt32 numVerts, RwMatrix *ltm, RwUInt32 flags) { im3d::Transform(pVerts, numVerts, ltm, flags); return pVerts; } +RwBool RwIm3DEnd(void) { im3d::End(); return true; } +RwBool RwIm3DRenderLine(RwInt32 vert1, RwInt32 vert2) { + RwImVertexIndex indices[2]; + indices[0] = vert1; + indices[1] = vert2; + im3d::RenderIndexedPrimitive((PrimitiveType)PRIMTYPELINELIST, indices, 2); + return true; +} +RwBool RwIm3DRenderTriangle(RwInt32 vert1, RwInt32 vert2, RwInt32 vert3); +RwBool RwIm3DRenderIndexedPrimitive(RwPrimitiveType primType, RwImVertexIndex *indices, RwInt32 numIndices) { im3d::RenderIndexedPrimitive((PrimitiveType)primType, indices, numIndices); return true; } +RwBool RwIm3DRenderPrimitive(RwPrimitiveType primType); + + + + + +RwBool RwRenderStateGet(RwRenderState state, void *value) +{ + uint32 *uival = (uint32*)value; + uint32 fog; + switch(state){ + case rwRENDERSTATETEXTURERASTER: *(void**)value = GetRenderStatePtr(TEXTURERASTER); return true; + case rwRENDERSTATETEXTUREADDRESS: *uival = GetRenderState(TEXTUREADDRESS); return true; + case rwRENDERSTATETEXTUREADDRESSU: *uival = GetRenderState(TEXTUREADDRESSU); return true; + case rwRENDERSTATETEXTUREADDRESSV: *uival = GetRenderState(TEXTUREADDRESSV); return true; + case rwRENDERSTATETEXTUREPERSPECTIVE: *uival = 1; return true; + case rwRENDERSTATEZTESTENABLE: *uival = GetRenderState(ZTESTENABLE); return true; + case rwRENDERSTATESHADEMODE: *uival = rwSHADEMODEGOURAUD; return true; + case rwRENDERSTATEZWRITEENABLE: *uival = GetRenderState(ZWRITEENABLE); return true; + case rwRENDERSTATETEXTUREFILTER: *uival = GetRenderState(TEXTUREFILTER); return true; + case rwRENDERSTATESRCBLEND: *uival = GetRenderState(SRCBLEND); return true; + case rwRENDERSTATEDESTBLEND: *uival = GetRenderState(DESTBLEND); return true; + case rwRENDERSTATEVERTEXALPHAENABLE: *uival = GetRenderState(VERTEXALPHA); return true; + case rwRENDERSTATEBORDERCOLOR: *uival = 0; return true; + case rwRENDERSTATEFOGENABLE: *uival = GetRenderState(FOGENABLE); return true; + case rwRENDERSTATEFOGCOLOR: + // have to swap R and B here + fog = GetRenderState(FOGCOLOR); + *uival = (fog>>16)&0xFF; + *uival |= (fog&0xFF)<<16; + *uival |= fog&0xFF00; + *uival |= fog&0xFF000000; + return true; + case rwRENDERSTATEFOGTYPE: *uival = rwFOGTYPELINEAR; return true; + case rwRENDERSTATEFOGDENSITY: *(float*)value = 1.0f; return true; + case rwRENDERSTATECULLMODE: *uival = GetRenderState(CULLMODE); return true; + + // all unsupported + case rwRENDERSTATEFOGTABLE: + case rwRENDERSTATEALPHAPRIMITIVEBUFFER: + + case rwRENDERSTATESTENCILENABLE: + case rwRENDERSTATESTENCILFAIL: + case rwRENDERSTATESTENCILZFAIL: + case rwRENDERSTATESTENCILPASS: + case rwRENDERSTATESTENCILFUNCTION: + case rwRENDERSTATESTENCILFUNCTIONREF: + case rwRENDERSTATESTENCILFUNCTIONMASK: + case rwRENDERSTATESTENCILFUNCTIONWRITEMASK: + default: + return false; + } +} +RwBool RwRenderStateSet(RwRenderState state, void *value) +{ + uint32 uival = (uintptr)value; + uint32 fog; + switch(state){ + case rwRENDERSTATETEXTURERASTER: SetRenderStatePtr(TEXTURERASTER, value); return true; + case rwRENDERSTATETEXTUREADDRESS: SetRenderState(TEXTUREADDRESS, uival); return true; + case rwRENDERSTATETEXTUREADDRESSU: SetRenderState(TEXTUREADDRESSU, uival); return true; + case rwRENDERSTATETEXTUREADDRESSV: SetRenderState(TEXTUREADDRESSV, uival); return true; + case rwRENDERSTATETEXTUREPERSPECTIVE: return true; + case rwRENDERSTATEZTESTENABLE: SetRenderState(ZTESTENABLE, uival); return true; + case rwRENDERSTATESHADEMODE: return true; + case rwRENDERSTATEZWRITEENABLE: SetRenderState(ZWRITEENABLE, uival); return true; + case rwRENDERSTATETEXTUREFILTER: SetRenderState(TEXTUREFILTER, uival); return true; + case rwRENDERSTATESRCBLEND: SetRenderState(SRCBLEND, uival); return true; + case rwRENDERSTATEDESTBLEND: SetRenderState(DESTBLEND, uival); return true; + case rwRENDERSTATEVERTEXALPHAENABLE: SetRenderState(VERTEXALPHA, uival); return true; + case rwRENDERSTATEBORDERCOLOR: return true; + case rwRENDERSTATEFOGENABLE: SetRenderState(FOGENABLE, uival); return true; + case rwRENDERSTATEFOGCOLOR: + // have to swap R and B here + fog = (uival>>16)&0xFF; + fog |= (uival&0xFF)<<16; + fog |= uival&0xFF00; + fog |= uival&0xFF000000; + SetRenderState(FOGCOLOR, fog); + return true; + case rwRENDERSTATEFOGTYPE: return true; + case rwRENDERSTATEFOGDENSITY: return true; + case rwRENDERSTATEFOGTABLE: return true; + case rwRENDERSTATEALPHAPRIMITIVEBUFFER: return true; + case rwRENDERSTATECULLMODE: SetRenderState(CULLMODE, uival); return true; + + // all unsupported + case rwRENDERSTATESTENCILENABLE: + case rwRENDERSTATESTENCILFAIL: + case rwRENDERSTATESTENCILZFAIL: + case rwRENDERSTATESTENCILPASS: + case rwRENDERSTATESTENCILFUNCTION: + case rwRENDERSTATESTENCILFUNCTIONREF: + case rwRENDERSTATESTENCILFUNCTIONMASK: + case rwRENDERSTATESTENCILFUNCTIONWRITEMASK: + default: + return true; + } +} + +static rw::MemoryFunctions gMemfuncs; +static void *(*real_malloc)(size_t size); +static void *(*real_realloc)(void *mem, size_t newSize); +static void *mallocWrap(size_t sz, uint32 hint) { if(sz == 0) return nil; return real_malloc(sz); } +static void *reallocWrap(void *p, size_t sz, uint32 hint) { return real_realloc(p, sz); } + + +// WARNING: unused parameters +RwBool RwEngineInit(RwMemoryFunctions *memFuncs, RwUInt32 initFlags, RwUInt32 resArenaSize) { + if(memFuncs){ + real_malloc = memFuncs->rwmalloc; + real_realloc = memFuncs->rwrealloc; + gMemfuncs.rwmalloc = mallocWrap; + gMemfuncs.rwrealloc = reallocWrap; + gMemfuncs.rwfree = memFuncs->rwfree; + Engine::init(&gMemfuncs); + }else{ + Engine::init(nil); + } + return true; +} +// TODO: this is platform dependent +RwBool RwEngineOpen(RwEngineOpenParams *initParams) { + static EngineOpenParams openParams; +#ifdef RW_D3D9 + openParams.window = (HWND)initParams->displayID; +#else + openParams = *(EngineOpenParams*)initParams->displayID; +#endif + return Engine::open(&openParams); +} +RwBool RwEngineStart(void) { + rw::d3d::isP8supported = false; + return Engine::start(); +} +RwBool RwEngineStop(void) { Engine::stop(); return true; } +RwBool RwEngineClose(void) { Engine::close(); return true; } +RwBool RwEngineTerm(void) { Engine::term(); return true; } +RwInt32 RwEngineRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor initCB, RwPluginObjectDestructor termCB); +RwInt32 RwEngineGetPluginOffset(RwUInt32 pluginID); +RwInt32 RwEngineGetNumSubSystems(void) { return Engine::getNumSubSystems(); } +RwSubSystemInfo *RwEngineGetSubSystemInfo(RwSubSystemInfo *subSystemInfo, RwInt32 subSystemIndex) + { return Engine::getSubSystemInfo(subSystemInfo, subSystemIndex); } +RwInt32 RwEngineGetCurrentSubSystem(void) { return Engine::getCurrentSubSystem(); } +RwBool RwEngineSetSubSystem(RwInt32 subSystemIndex) { return Engine::setSubSystem(subSystemIndex); } +RwInt32 RwEngineGetNumVideoModes(void) { return Engine::getNumVideoModes(); } +RwVideoMode *RwEngineGetVideoModeInfo(RwVideoMode *modeinfo, RwInt32 modeIndex) + { return Engine::getVideoModeInfo(modeinfo, modeIndex); } +RwInt32 RwEngineGetCurrentVideoMode(void) { return Engine::getCurrentVideoMode(); } +RwBool RwEngineSetVideoMode(RwInt32 modeIndex) { return Engine::setVideoMode(modeIndex); } +RwInt32 RwEngineGetTextureMemorySize(void); +RwInt32 RwEngineGetMaxTextureSize(void); + + + +// TODO +void RwD3D8EngineSetRefreshRate(RwUInt32 refreshRate) {} +RwBool RwD3D8DeviceSupportsDXTTexture(void) { return true; } + + +void RwD3D8EngineSetMultiSamplingLevels(RwUInt32 level) { Engine::setMultiSamplingLevels(level); } +RwUInt32 RwD3D8EngineGetMaxMultiSamplingLevels(void) { return Engine::getMaxMultiSamplingLevels(); } + + +RpMaterial *RpMaterialCreate(void) { return Material::create(); } +RwBool RpMaterialDestroy(RpMaterial *material) { material->destroy(); return true; } +//RpMaterial *RpMaterialClone(RpMaterial *material); +RpMaterial *RpMaterialSetTexture(RpMaterial *material, RwTexture *texture) { material->setTexture(texture); return material; } +//RpMaterial *RpMaterialAddRef(RpMaterial *material); +RwTexture *RpMaterialGetTexture(const RpMaterial *material) { return material->texture; } +RpMaterial *RpMaterialSetColor(RpMaterial *material, const RwRGBA *color) { material->color = *color; return material; } +const RwRGBA *RpMaterialGetColor(const RpMaterial *material) { return &material->color; } +RpMaterial *RpMaterialSetSurfaceProperties(RpMaterial *material, const RwSurfaceProperties *surfaceProperties); +const RwSurfaceProperties *RpMaterialGetSurfaceProperties(const RpMaterial *material) { return &material->surfaceProps; } +//RwInt32 RpMaterialRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +//RwInt32 RpMaterialRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +//RwInt32 RpMaterialSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +//RwInt32 RpMaterialGetPluginOffset(RwUInt32 pluginID); +//RwBool RpMaterialValidatePlugins(const RpMaterial *material); +//RwUInt32 RpMaterialStreamGetSize(const RpMaterial *material); +//RpMaterial *RpMaterialStreamRead(RwStream *stream); +//const RpMaterial *RpMaterialStreamWrite(const RpMaterial *material, RwStream *stream); +//RpMaterialChunkInfo *_rpMaterialChunkInfoRead(RwStream *stream, RpMaterialChunkInfo *materialChunkInfo, RwInt32 *bytesRead); + + + + + +RwReal RpLightGetRadius(const RpLight *light) { return light->radius; } +//const RwRGBAReal *RpLightGetColor(const RpLight *light); +RpLight *RpLightSetFrame(RpLight *light, RwFrame *frame) { light->setFrame(frame); return light; } +RwFrame *RpLightGetFrame(const RpLight *light) { return light->getFrame(); } +//RpLightType RpLightGetType(const RpLight *light); +RpLight *RpLightSetFlags(RpLight *light, RwUInt32 flags) { light->setFlags(flags); return light; } +//RwUInt32 RpLightGetFlags(const RpLight *light); +RpLight *RpLightCreate(RwInt32 type) { return rw::Light::create(type); } +RwBool RpLightDestroy(RpLight *light) { light->destroy(); return true; } +RpLight *RpLightSetRadius(RpLight *light, RwReal radius) { light->radius = radius; return light; } +RpLight *RpLightSetColor(RpLight *light, const RwRGBAReal *color) { light->setColor(color->red, color->green, color->blue); return light; } +//RwReal RpLightGetConeAngle(const RpLight *light); +//RpLight *RpLightSetConeAngle(RpLight * ight, RwReal angle); +//RwUInt32 RpLightStreamGetSize(const RpLight *light); +//RpLight *RpLightStreamRead(RwStream *stream); +//const RpLight *RpLightStreamWrite(const RpLight *light, RwStream *stream); +//RpLightChunkInfo *_rpLightChunkInfoRead(RwStream *stream, RpLightChunkInfo *lightChunkInfo, RwInt32 *bytesRead); +//RwInt32 RpLightRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +//RwInt32 RpLightRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +//RwInt32 RpLightSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +//RwInt32 RpLightGetPluginOffset(RwUInt32 pluginID); +//RwBool RpLightValidatePlugins(const RpLight * light); + + + + + +RpGeometry *RpGeometryCreate(RwInt32 numVert, RwInt32 numTriangles, RwUInt32 format) { return Geometry::create(numVert, numTriangles, format); } +RwBool RpGeometryDestroy(RpGeometry *geometry) { geometry->destroy(); return true; } +RpGeometry *_rpGeometryAddRef(RpGeometry *geometry); +RpGeometry *RpGeometryLock(RpGeometry *geometry, RwInt32 lockMode) { geometry->lock(lockMode); return geometry; } +RpGeometry *RpGeometryUnlock(RpGeometry *geometry) { geometry->unlock(); return geometry; } +RpGeometry *RpGeometryTransform(RpGeometry *geometry, const RwMatrix *matrix); +RpGeometry *RpGeometryCreateSpace(RwReal radius); +RpMorphTarget *RpMorphTargetSetBoundingSphere(RpMorphTarget *morphTarget, const RwSphere *boundingSphere) { morphTarget->boundingSphere = *boundingSphere; return morphTarget; } +RwSphere *RpMorphTargetGetBoundingSphere(RpMorphTarget *morphTarget) { return &morphTarget->boundingSphere; } +const RpMorphTarget *RpMorphTargetCalcBoundingSphere(const RpMorphTarget *morphTarget, RwSphere *boundingSphere) { *boundingSphere = morphTarget->calculateBoundingSphere(); return morphTarget; } +RwInt32 RpGeometryAddMorphTargets(RpGeometry *geometry, RwInt32 mtcount) { RwInt32 n = geometry->numMorphTargets; geometry->addMorphTargets(mtcount); return n; } +RwInt32 RpGeometryAddMorphTarget(RpGeometry *geometry) { return RpGeometryAddMorphTargets(geometry, 1); } +RpGeometry *RpGeometryRemoveMorphTarget(RpGeometry *geometry, RwInt32 morphTarget); +RwInt32 RpGeometryGetNumMorphTargets(const RpGeometry *geometry); +RpMorphTarget *RpGeometryGetMorphTarget(const RpGeometry *geometry, RwInt32 morphTarget) { return &geometry->morphTargets[morphTarget]; } +RwRGBA *RpGeometryGetPreLightColors(const RpGeometry *geometry) { return geometry->colors; } +RwTexCoords *RpGeometryGetVertexTexCoords(const RpGeometry *geometry, RwTextureCoordinateIndex uvIndex) { + if(uvIndex == rwNARWTEXTURECOORDINATEINDEX) + return nil; + return geometry->texCoords[uvIndex-rwTEXTURECOORDINATEINDEX0]; +} +RwInt32 RpGeometryGetNumTexCoordSets(const RpGeometry *geometry) { return geometry->numTexCoordSets; } +RwInt32 RpGeometryGetNumVertices (const RpGeometry *geometry) { return geometry->numVertices; } +RwV3d *RpMorphTargetGetVertices(const RpMorphTarget *morphTarget) { return morphTarget->vertices; } +RwV3d *RpMorphTargetGetVertexNormals(const RpMorphTarget *morphTarget) { return morphTarget->normals; } +RpTriangle *RpGeometryGetTriangles(const RpGeometry *geometry) { return geometry->triangles; } +RwInt32 RpGeometryGetNumTriangles(const RpGeometry *geometry) { return geometry->numTriangles; } +RpMaterial *RpGeometryGetMaterial(const RpGeometry *geometry, RwInt32 matNum) { return geometry->matList.materials[matNum]; } +const RpGeometry *RpGeometryTriangleSetVertexIndices(const RpGeometry *geometry, RpTriangle *triangle, RwUInt16 vert1, RwUInt16 vert2, RwUInt16 vert3) + { triangle->v[0] = vert1; triangle->v[1] = vert2; triangle->v[2] = vert3; return geometry; } +RpGeometry *RpGeometryTriangleSetMaterial(RpGeometry *geometry, RpTriangle *triangle, RpMaterial *material) { + int id = geometry->matList.findIndex(material); + if(id < 0) + id = geometry->matList.appendMaterial(material); + if(id < 0) + return nil; + triangle->matId = id; + return geometry; +} +const RpGeometry *RpGeometryTriangleGetVertexIndices(const RpGeometry *geometry, const RpTriangle *triangle, RwUInt16 *vert1, RwUInt16 *vert2, RwUInt16 *vert3); +RpMaterial *RpGeometryTriangleGetMaterial(const RpGeometry *geometry, const RpTriangle *triangle); +RwInt32 RpGeometryGetNumMaterials(const RpGeometry *geometry); +RpGeometry *RpGeometryForAllMaterials(RpGeometry *geometry, RpMaterialCallBack fpCallBack, void *pData) { + int i; + for(i = 0; i < geometry->matList.numMaterials; i++) + if(fpCallBack(geometry->matList.materials[i], pData) == nil) + break; + return geometry; +} +//const RpGeometry *RpGeometryForAllMeshes(const RpGeometry *geometry, RpMeshCallBack fpCallBack, void *pData); +RwInt32 RpGeometryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpGeometryRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpGeometrySetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpGeometryGetPluginOffset(RwUInt32 pluginID); +RwBool RpGeometryValidatePlugins(const RpGeometry *geometry); +RwUInt32 RpGeometryStreamGetSize(const RpGeometry *geometry); +const RpGeometry *RpGeometryStreamWrite(const RpGeometry *geometry, RwStream *stream); +RpGeometry *RpGeometryStreamRead(RwStream *stream) { return Geometry::streamRead(stream); } +//RpGeometryChunkInfo *_rpGeometryChunkInfoRead(RwStream *stream, RpGeometryChunkInfo *geometryChunkInfo, RwInt32 *bytesRead); +RwUInt32 RpGeometryGetFlags(const RpGeometry *geometry) { return geometry->flags; } +RpGeometry *RpGeometrySetFlags(RpGeometry *geometry, RwUInt32 flags) { geometry->flags = flags; return geometry; } +const RwSurfaceProperties *_rpGeometryGetSurfaceProperties(const RpGeometry *geometry); +RpGeometry *_rpGeometrySetSurfaceProperties(RpGeometry *geometry, const RwSurfaceProperties *surfaceProperties); + + + + + +RwFrame *RpClumpGetFrame(const RpClump * clump) { return clump->getFrame(); } +RpClump *RpClumpSetFrame(RpClump * clump, RwFrame * frame) { clump->setFrame(frame); return clump; } +RpClump *RpClumpForAllAtomics(RpClump * clump, RpAtomicCallBack callback, void *pData) { + FORLIST(lnk, clump->atomics) + if(callback(Atomic::fromClump(lnk), pData) == nil) + break; + return clump; +} +RpClump *RpClumpForAllLights(RpClump * clump, RpLightCallBack callback, void *pData); +RpClump *RpClumpForAllCameras(RpClump * clump, RwCameraCallBack callback, void *pData); +//RpClump *RpClumpCreateSpace(const RwV3d * position, RwReal radius); +RpClump *RpClumpRender(RpClump * clump) { clump->render(); return clump; } +RpClump *RpClumpRemoveAtomic(RpClump * clump, RpAtomic * atomic) { clump->removeAtomic(atomic); return clump; } +RpClump *RpClumpAddAtomic(RpClump * clump, RpAtomic * atomic) { clump->addAtomic(atomic); return clump; } +//RpClump *RpClumpRemoveLight(RpClump * clump, RpLight * light); +//RpClump *RpClumpAddLight(RpClump * clump, RpLight * light); +//RpClump *RpClumpRemoveCamera(RpClump * clump, RwCamera * camera); +//RpClump *RpClumpAddCamera(RpClump * clump, RwCamera * camera); +RwBool RpClumpDestroy(RpClump * clump) { clump->destroy(); return true; } +RpClump *RpClumpCreate(void) { return rw::Clump::create(); } +RpClump *RpClumpClone(RpClump * clump) { return clump->clone(); } +//RpClump *RpClumpSetCallBack(RpClump * clump, RpClumpCallBack callback); +//RpClumpCallBack RpClumpGetCallBack(const RpClump * clump); +RwInt32 RpClumpGetNumAtomics(RpClump * clump) { return clump->countAtomics(); } +//RwInt32 RpClumpGetNumLights(RpClump * clump); +//RwInt32 RpClumpGetNumCameras(RpClump * clump); +RpClump *RpClumpStreamRead(RwStream * stream) { return rw::Clump::streamRead(stream); } +//RpClump *RpClumpStreamWrite(RpClump * clump, RwStream * stream); +RwInt32 RpClumpRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) + { return Clump::registerPlugin(size, pluginID, constructCB, destructCB, (CopyConstructor)copyCB); } +RwInt32 RpClumpRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB) + { return Clump::registerPluginStream(pluginID, readCB, (StreamWrite)writeCB, (StreamGetSize)getSizeCB); } +//RwInt32 RpClumpSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +//RwInt32 RpClumpGetPluginOffset(RwUInt32 pluginID); +//RwBool RpClumpValidatePlugins(const RpClump * clump); + + + +RpAtomic *RpAtomicCreate(void) { return rw::Atomic::create(); } +RwBool RpAtomicDestroy(RpAtomic * atomic) { atomic->destroy(); return true; } +RpAtomic *RpAtomicClone(RpAtomic * atomic) { return atomic->clone(); } +RpAtomic *RpAtomicSetFrame(RpAtomic * atomic, RwFrame * frame) { atomic->setFrame(frame); return atomic; } +RpAtomic *RpAtomicSetGeometry(RpAtomic * atomic, RpGeometry * geometry, RwUInt32 flags) { atomic->setGeometry(geometry, flags); return atomic; } + +RwFrame *RpAtomicGetFrame(const RpAtomic * atomic) { return atomic->getFrame(); } +RpAtomic *RpAtomicSetFlags(RpAtomic * atomic, RwUInt32 flags) { atomic->setFlags(flags); return atomic; } +RwUInt32 RpAtomicGetFlags(const RpAtomic * atomic) { return atomic->getFlags(); } +RwSphere *RpAtomicGetBoundingSphere(RpAtomic * atomic) { return &atomic->boundingSphere; } +RpAtomic *RpAtomicRender(RpAtomic * atomic) { atomic->render(); return atomic; } +RpClump *RpAtomicGetClump(const RpAtomic * atomic) { return atomic->clump; } +//RpInterpolator *RpAtomicGetInterpolator(RpAtomic * atomic); +RpGeometry *RpAtomicGetGeometry(const RpAtomic * atomic) { return atomic->geometry; } +// WARNING: illegal cast +void RpAtomicSetRenderCallBack(RpAtomic * atomic, RpAtomicCallBackRender callback) { atomic->setRenderCB((Atomic::RenderCB)callback); } +RpAtomicCallBackRender RpAtomicGetRenderCallBack(const RpAtomic * atomic) { return (RpAtomicCallBackRender)atomic->renderCB; } +//RwBool RpAtomicInstance(RpAtomic *atomic); +//RwUInt32 RpAtomicStreamGetSize(RpAtomic * atomic); +//RpAtomic *RpAtomicStreamRead(RwStream * stream); +//RpAtomic *RpAtomicStreamWrite(RpAtomic * atomic, RwStream * stream); +RwInt32 RpAtomicRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB) + { return Atomic::registerPlugin(size, pluginID, constructCB, destructCB, (CopyConstructor)copyCB); } +//RwInt32 RpAtomicRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +//RwInt32 RpAtomicSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +//RwInt32 RpAtomicSetStreamRightsCallBack(RwUInt32 pluginID, RwPluginDataChunkRightsCallBack rightsCB); +//RwInt32 RpAtomicGetPluginOffset(RwUInt32 pluginID); +//RwBool RpAtomicValidatePlugins(const RpAtomic * atomic); + +RpAtomic *AtomicDefaultRenderCallBack(RpAtomic * atomic) { Atomic::defaultRenderCB(atomic); return atomic; } + + +// TODO: this is extremely simplified +RpWorld *RpWorldCreate(RwBBox * boundingBox) { return World::create(); } +RwBool RpWorldDestroy(RpWorld * world) { world->destroy(); return true; } + +RwBool RpWorldPluginAttach(void) { + registerMeshPlugin(); + registerNativeDataPlugin(); + registerAtomicRightsPlugin(); + registerMaterialRightsPlugin(); + + // not sure if this goes here + rw::xbox::registerVertexFormatPlugin(); + return true; +} + +RpWorld *RpWorldRemoveCamera(RpWorld *world, RwCamera *camera) { world->removeCamera(camera); return world; } +RpWorld *RpWorldAddCamera(RpWorld *world, RwCamera *camera) { world->addCamera(camera); return world; } +RpWorld *RwCameraGetWorld(const RwCamera *camera); +RpWorld *RpWorldRemoveAtomic(RpWorld *world, RpAtomic *atomic); +RpWorld *RpWorldAddAtomic(RpWorld *world, RpAtomic *atomic); +RpWorld *RpAtomicGetWorld(const RpAtomic *atomic); +RpWorld *RpWorldAddClump(RpWorld *world, RpClump *clump); +RpWorld *RpWorldRemoveClump(RpWorld *world, RpClump *clump); +RpWorld *RpClumpGetWorld(const RpClump *clump); +RpWorld *RpWorldAddLight(RpWorld *world, RpLight *light) { world->addLight(light); return world; } +RpWorld *RpWorldRemoveLight(RpWorld *world, RpLight *light) { world->removeLight(light); return world; } +RpWorld *RpLightGetWorld(const RpLight *light); +RwCamera *RwCameraForAllClumpsInFrustum(RwCamera *camera, void *data); +RwCamera *RwCameraForAllClumpsNotInFrustum(RwCamera *camera, RwInt32 numClumps, void *data); + + + + +RwBool RpMatFXPluginAttach( void ) { registerMatFXPlugin(); return true; } +RpAtomic *RpMatFXAtomicEnableEffects( RpAtomic *atomic ) { MatFX::enableEffects(atomic); return atomic; } +RpMatFXMaterialFlags RpMatFXMaterialGetEffects( const RpMaterial *material ){ return (RpMatFXMaterialFlags)MatFX::getEffects(material); } +RpMaterial *RpMatFXMaterialSetEffects( RpMaterial *material, RpMatFXMaterialFlags flags ) { MatFX::setEffects(material, (uint32)flags); return material; } +RpMaterial *RpMatFXMaterialSetupEnvMap( RpMaterial *material, RwTexture *texture, RwFrame *frame, RwBool useFrameBufferAlpha, RwReal coef ) { + MatFX *mfx = MatFX::get(material); + mfx->setEnvTexture(texture); + mfx->setEnvFrame(frame); + mfx->setEnvCoefficient(coef); + return material; +} +RpMaterial *RpMatFXMaterialSetEnvMapFrame( RpMaterial *material, RwFrame *frame ) +{ + MatFX *mfx = MatFX::get(material); + mfx->setEnvFrame(frame); + return material; +} +RpMaterial *RpMatFXMaterialSetEnvMapFrameBufferAlpha( RpMaterial *material, RwBool useFrameBufferAlpha ) +{ + MatFX *mfx = MatFX::get(material); + mfx->setEnvFBAlpha(useFrameBufferAlpha); + return material; +} +RpMaterial *RpMatFXMaterialSetEnvMapCoefficient( RpMaterial *material, RwReal coef ) +{ + MatFX *mfx = MatFX::get(material); + mfx->setEnvCoefficient(coef); + return material; +} +RwReal RpMatFXMaterialGetEnvMapCoefficient( const RpMaterial *material ) +{ + MatFX *mfx = MatFX::get(material); + return mfx->getEnvCoefficient(); +} + + + +RwBool RpHAnimPluginAttach(void) { + registerHAnimPlugin(); + return true; +} + +RwInt32 RpHAnimFrameGetID(RwFrame *frame) { return HAnimData::get(frame)->id; } + +RwInt32 RpHAnimIDGetIndex(RpHAnimHierarchy *hierarchy, RwInt32 ID) { return hierarchy->getIndex(ID); } + +RwBool RpHAnimFrameSetHierarchy(RwFrame *frame, RpHAnimHierarchy *hierarchy) { HAnimData::get(frame)->hierarchy = hierarchy; return true; } +RpHAnimHierarchy *RpHAnimFrameGetHierarchy(RwFrame *frame) { return HAnimHierarchy::get(frame); } + +RpHAnimHierarchy *RpHAnimHierarchySetFlags(RpHAnimHierarchy *hierarchy, RpHAnimHierarchyFlag flags) { hierarchy->flags = flags; return hierarchy; } + +RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy *hierarchy, RpHAnimAnimation *anim) { hierarchy->interpolator->setCurrentAnim(anim); return true; } +RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy *hierarchy, RwReal time) { hierarchy->interpolator->addTime(time); return true; } + +RwMatrix *RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy *hierarchy) { return hierarchy->matrices; } +RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy *hierarchy) { hierarchy->updateMatrices(); return true; } + +RpHAnimAnimation *RpHAnimAnimationCreate(RwInt32 typeID, RwInt32 numFrames, RwInt32 flags, RwReal duration) + { return Animation::create(AnimInterpolatorInfo::find(typeID), numFrames, flags, duration); } +RpHAnimAnimation *RpHAnimAnimationDestroy(RpHAnimAnimation *animation) { animation->destroy(); return animation; } +RpHAnimAnimation *RpHAnimAnimationStreamRead(RwStream *stream) { return Animation::streamRead(stream); } + + + + + + +RwBool RpSkinPluginAttach(void) { + registerSkinPlugin(); + return true; +} + +RwUInt32 RpSkinGetNumBones( RpSkin *skin ) { return skin->numBones; } +const RwMatrixWeights *RpSkinGetVertexBoneWeights( RpSkin *skin ) { return (RwMatrixWeights*)skin->weights; } +const RwUInt32 *RpSkinGetVertexBoneIndices( RpSkin *skin ) { return (RwUInt32*)skin->indices; } +const RwMatrix *RpSkinGetSkinToBoneMatrices( RpSkin *skin ) { return (const RwMatrix*)skin->inverseMatrices; } + +RpSkin *RpSkinGeometryGetSkin( RpGeometry *geometry ) { return Skin::get(geometry); } + +RpAtomic *RpSkinAtomicSetHAnimHierarchy( RpAtomic *atomic, RpHAnimHierarchy *hierarchy ) { Skin::setHierarchy(atomic, hierarchy); return atomic; } +RpHAnimHierarchy *RpSkinAtomicGetHAnimHierarchy( const RpAtomic *atomic ) { return Skin::getHierarchy(atomic); } + +RwImage * +RtBMPImageWrite(RwImage *image, const RwChar *imageName) +{ +#ifndef _WIN32 + char *r = casepath(imageName); + if (r) { + rw::writeBMP(image, r); + free(r); + } else { + rw::writeBMP(image, imageName); + } + +#else + rw::writeBMP(image, imageName); +#endif + return image; +} +RwImage * +RtBMPImageRead(const RwChar *imageName) +{ +#ifndef _WIN32 + RwImage *image; + char *r = casepath(imageName); + if (r) { + image = rw::readBMP(r); + free(r); + } else { + image = rw::readBMP(imageName); + } + return image; + +#else + return rw::readBMP(imageName); +#endif +} + + +RwImage * +RtPNGImageWrite(RwImage *image, const RwChar *imageName) +{ +#ifndef _WIN32 + char *r = casepath(imageName); + if (r) { + rw::writePNG(image, r); + free(r); + } else { + rw::writePNG(image, imageName); + } + +#else + rw::writePNG(image, imageName); +#endif + return image; +} +RwImage * +RtPNGImageRead(const RwChar *imageName) +{ +#ifndef _WIN32 + RwImage *image; + char *r = casepath(imageName); + if (r) { + image = rw::readPNG(r); + free(r); + } else { + image = rw::readPNG(imageName); + } + return image; + +#else + return rw::readPNG(imageName); +#endif +} + +#include "rtquat.h" + +RtQuat *RtQuatRotate(RtQuat * quat, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp) { return (RtQuat*)((rw::Quat*)quat)->rotate(axis, angle/180.0f*3.14159f, (CombineOp)combineOp); } +void RtQuatConvertToMatrix(const RtQuat * const qpQuat, RwMatrix * const mpMatrix) { mpMatrix->rotate(*(rw::Quat*)qpQuat, COMBINEREPLACE); } + + +#include "rtcharse.h" + +RwBool RtCharsetOpen(void) { return Charset::open(); } +void RtCharsetClose(void) { return Charset::close(); } +RtCharset *RtCharsetPrint(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y) { charSet->print(string, x, y, true); return charSet; } +RtCharset *RtCharsetPrintBuffered(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y, RwBool hideSpaces) { charSet->printBuffered(string, x, y, hideSpaces); return charSet; } +RwBool RtCharsetBufferFlush(void) { Charset::flushBuffer(); return true; } +RtCharset *RtCharsetSetColors(RtCharset * charSet, const RwRGBA * foreGround, const RwRGBA * backGround) { return charSet->setColors(foreGround, backGround); } +RtCharset *RtCharsetGetDesc(RtCharset * charset, RtCharsetDesc * desc) { *desc = charset->desc; return charset; } +RtCharset *RtCharsetCreate(const RwRGBA * foreGround, const RwRGBA * backGround) { return Charset::create(foreGround, backGround); } +RwBool RtCharsetDestroy(RtCharset * charSet) { charSet->destroy(); return true; } + + + +#include + +RwInt8 RpAnisotGetMaxSupportedMaxAnisotropy(void) { return rw::getMaxSupportedMaxAnisotropy(); } +RwTexture *RpAnisotTextureSetMaxAnisotropy(RwTexture *tex, RwInt8 val) { tex->setMaxAnisotropy(val); return tex; } +RwInt8 RpAnisotTextureGetMaxAnisotropy(RwTexture *tex) { return tex->getMaxAnisotropy(); } +RwBool RpAnisotPluginAttach(void) { rw::registerAnisotropyPlugin(); return true; } diff --git a/src/fakerw/rpanisot.h b/src/fakerw/rpanisot.h new file mode 100644 index 0000000..a886512 --- /dev/null +++ b/src/fakerw/rpanisot.h @@ -0,0 +1,6 @@ +#pragma once + +RwInt8 RpAnisotGetMaxSupportedMaxAnisotropy(void); +RwTexture *RpAnisotTextureSetMaxAnisotropy(RwTexture *tex, RwInt8 val); +RwInt8 RpAnisotTextureGetMaxAnisotropy(RwTexture *tex); +RwBool RpAnisotPluginAttach(void); diff --git a/src/fakerw/rphanim.h b/src/fakerw/rphanim.h new file mode 100644 index 0000000..6305980 --- /dev/null +++ b/src/fakerw/rphanim.h @@ -0,0 +1,72 @@ +#pragma once + +#include "rtquat.h" + +//struct RpHAnimHierarchy; +typedef rw::HAnimHierarchy RpHAnimHierarchy; +//struct RpHAnimAnimation; +typedef rw::Animation RpHAnimAnimation; + +#define rpHANIMSTDKEYFRAMETYPEID 0x1 + +// same as rw::HAnimKeyFrame, but we need RtQuat in this one +struct RpHAnimStdKeyFrame +{ + RpHAnimStdKeyFrame *prevFrame; + RwReal time; + RtQuat q; + RwV3d t; +}; +// same story, this one only exists in later RW versions +// but we need it for 64 bit builds because offset and size differs! +struct RpHAnimStdInterpFrame +{ + RpHAnimStdKeyFrame *keyFrame1; + RpHAnimStdKeyFrame *keyFrame2; + RtQuat q; + RwV3d t; +}; + +enum RpHAnimHierarchyFlag +{ + rpHANIMHIERARCHYSUBHIERARCHY = rw::HAnimHierarchy::SUBHIERARCHY, + rpHANIMHIERARCHYNOMATRICES = rw::HAnimHierarchy::NOMATRICES, + + rpHANIMHIERARCHYUPDATEMODELLINGMATRICES = rw::HAnimHierarchy::UPDATEMODELLINGMATRICES, + rpHANIMHIERARCHYUPDATELTMS = rw::HAnimHierarchy::UPDATELTMS, + rpHANIMHIERARCHYLOCALSPACEMATRICES = rw::HAnimHierarchy::LOCALSPACEMATRICES +}; + +#define rpHANIMPOPPARENTMATRIX rw::HAnimHierarchy::POP +#define rpHANIMPUSHPARENTMATRIX rw::HAnimHierarchy::PUSH + +RwBool RpHAnimPluginAttach(void); + +RwBool RpHAnimFrameSetID(RwFrame *frame, RwInt32 id); +RwInt32 RpHAnimFrameGetID(RwFrame *frame); + +RwInt32 RpHAnimIDGetIndex(RpHAnimHierarchy *hierarchy, RwInt32 ID); + +RwBool RpHAnimFrameSetHierarchy(RwFrame *frame, RpHAnimHierarchy *hierarchy); +RpHAnimHierarchy *RpHAnimFrameGetHierarchy(RwFrame *frame); + +RpHAnimHierarchy *RpHAnimHierarchySetFlags(RpHAnimHierarchy *hierarchy, RpHAnimHierarchyFlag flags); +RpHAnimHierarchyFlag RpHAnimHierarchyGetFlags(RpHAnimHierarchy *hierarchy); + +RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy *hierarchy, RpHAnimAnimation *anim); +RwBool RpHAnimHierarchySetCurrentAnimTime(RpHAnimHierarchy *hierarchy, RwReal time); +RwBool RpHAnimHierarchySubAnimTime(RpHAnimHierarchy *hierarchy, RwReal time); +RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy *hierarchy, RwReal time); + +RwMatrix *RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy *hierarchy); +RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy *hierarchy); + +#define rpHANIMHIERARCHYGETINTERPFRAME( hierarchy, nodeIndex ) \ + ( (void *)( ( (RwUInt8 *)&(hierarchy->interpolator[1]) + \ + ((nodeIndex) * \ + hierarchy->interpolator->currentInterpKeyFrameSize) ) ) ) + + +RpHAnimAnimation *RpHAnimAnimationCreate(RwInt32 typeID, RwInt32 numFrames, RwInt32 flags, RwReal duration); +RpHAnimAnimation *RpHAnimAnimationDestroy(RpHAnimAnimation *animation); +RpHAnimAnimation *RpHAnimAnimationStreamRead(RwStream *stream); diff --git a/src/fakerw/rpmatfx.h b/src/fakerw/rpmatfx.h new file mode 100644 index 0000000..87c8fb2 --- /dev/null +++ b/src/fakerw/rpmatfx.h @@ -0,0 +1,43 @@ +#pragma once + +enum RpMatFXMaterialFlags +{ + rpMATFXEFFECTNULL = rw::MatFX::NOTHING, + rpMATFXEFFECTBUMPMAP = rw::MatFX::BUMPMAP, + rpMATFXEFFECTENVMAP = rw::MatFX::ENVMAP, + rpMATFXEFFECTBUMPENVMAP = rw::MatFX::BUMPENVMAP, + rpMATFXEFFECTDUAL = rw::MatFX::DUAL, + + rpMATFXEFFECTMAX, + rpMATFXNUMEFFECTS = rpMATFXEFFECTMAX - 1, +}; + +RwBool RpMatFXPluginAttach( void ); +RpAtomic *RpMatFXAtomicEnableEffects( RpAtomic *atomic ); +RwBool RpMatFXAtomicQueryEffects( RpAtomic *atomic ); +//RpWorldSector *RpMatFXWorldSectorEnableEffects( RpWorldSector *worldSector ); +//RwBool RpMatFXWorldSectorQueryEffects( RpWorldSector *worldSector ); +RpMaterial *RpMatFXMaterialSetEffects( RpMaterial *material, RpMatFXMaterialFlags flags ); +RpMaterial *RpMatFXMaterialSetupBumpMap( RpMaterial *material, RwTexture *texture, RwFrame *frame, RwReal coef ); +RpMaterial *RpMatFXMaterialSetupEnvMap( RpMaterial *material, RwTexture *texture, RwFrame *frame, RwBool useFrameBufferAlpha, RwReal coef ); +RpMaterial *RpMatFXMaterialSetupDualTexture( RpMaterial *material, RwTexture *texture, RwBlendFunction srcBlendMode, RwBlendFunction dstBlendMode ); +RpMatFXMaterialFlags RpMatFXMaterialGetEffects( const RpMaterial *material ); +RpMaterial *RpMatFXMaterialSetBumpMapTexture( RpMaterial *material, RwTexture *texture ); +RpMaterial *RpMatFXMaterialSetBumpMapFrame( RpMaterial *material, RwFrame *frame ); +RpMaterial *RpMatFXMaterialSetBumpMapCoefficient( RpMaterial *material, RwReal coef ); +RwTexture *RpMatFXMaterialGetBumpMapTexture( const RpMaterial *material ); +RwTexture *RpMatFXMaterialGetBumpMapBumpedTexture( const RpMaterial *material ); +RwFrame *RpMatFXMaterialGetBumpMapFrame( const RpMaterial *material ); +RwReal RpMatFXMaterialGetBumpMapCoefficient( const RpMaterial *material ); +RpMaterial *RpMatFXMaterialSetEnvMapTexture( RpMaterial *material, RwTexture *texture ); +RpMaterial *RpMatFXMaterialSetEnvMapFrame( RpMaterial *material, RwFrame *frame ); +RpMaterial *RpMatFXMaterialSetEnvMapFrameBufferAlpha( RpMaterial *material, RwBool useFrameBufferAlpha ); +RpMaterial *RpMatFXMaterialSetEnvMapCoefficient( RpMaterial *material, RwReal coef ); +RwTexture *RpMatFXMaterialGetEnvMapTexture( const RpMaterial *material ); +RwFrame *RpMatFXMaterialGetEnvMapFrame( const RpMaterial *material ); +RwBool RpMatFXMaterialGetEnvMapFrameBufferAlpha( const RpMaterial *material ); +RwReal RpMatFXMaterialGetEnvMapCoefficient( const RpMaterial *material ); +RpMaterial *RpMatFXMaterialSetDualTexture( RpMaterial *material, RwTexture *texture ); +RpMaterial *RpMatFXMaterialSetDualBlendModes( RpMaterial *material, RwBlendFunction srcBlendMode, RwBlendFunction dstBlendMode ); +RwTexture *RpMatFXMaterialGetDualTexture( const RpMaterial *material ); +const RpMaterial *RpMatFXMaterialGetDualBlendModes( const RpMaterial *material, RwBlendFunction *srcBlendMode, RwBlendFunction *dstBlendMode ); diff --git a/src/fakerw/rpskin.h b/src/fakerw/rpskin.h new file mode 100644 index 0000000..1ffc9f2 --- /dev/null +++ b/src/fakerw/rpskin.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +//struct RpSkin; +typedef rw::Skin RpSkin; + +struct RwMatrixWeights +{ + RwReal w0; + RwReal w1; + RwReal w2; + RwReal w3; +}; + +RwBool RpSkinPluginAttach(void); + +RwUInt32 RpSkinGetNumBones( RpSkin *skin ); +const RwMatrixWeights *RpSkinGetVertexBoneWeights( RpSkin *skin ); +const RwUInt32 *RpSkinGetVertexBoneIndices( RpSkin *skin ); +const RwMatrix *RpSkinGetSkinToBoneMatrices( RpSkin *skin ); + +RpSkin *RpSkinGeometryGetSkin( RpGeometry *geometry ); + +RpAtomic *RpSkinAtomicSetHAnimHierarchy( RpAtomic *atomic, RpHAnimHierarchy *hierarchy ); +RpHAnimHierarchy *RpSkinAtomicGetHAnimHierarchy( const RpAtomic *atomic ); diff --git a/src/fakerw/rpworld.h b/src/fakerw/rpworld.h new file mode 100644 index 0000000..f10a375 --- /dev/null +++ b/src/fakerw/rpworld.h @@ -0,0 +1,336 @@ +#pragma once + +#define rpATOMIC rw::Atomic::ID +#define rpCLUMP rw::Clump::ID + +/* + *********************************************** + * + * RpMaterial + * + *********************************************** + */ + +//struct RpMaterial; +typedef rw::Material RpMaterial; + +typedef RpMaterial *(*RpMaterialCallBack)(RpMaterial *material, void *data); + +RpMaterial *RpMaterialCreate(void); +RwBool RpMaterialDestroy(RpMaterial *material); +RpMaterial *RpMaterialClone(RpMaterial *material); +RpMaterial *RpMaterialSetTexture(RpMaterial *material, RwTexture *texture); +RpMaterial *RpMaterialAddRef(RpMaterial *material); +RwTexture *RpMaterialGetTexture(const RpMaterial *material); +RpMaterial *RpMaterialSetColor(RpMaterial *material, const RwRGBA *color); +const RwRGBA *RpMaterialGetColor(const RpMaterial *material); +RpMaterial *RpMaterialSetSurfaceProperties(RpMaterial *material, const RwSurfaceProperties *surfaceProperties); +const RwSurfaceProperties *RpMaterialGetSurfaceProperties(const RpMaterial *material); +RwInt32 RpMaterialRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpMaterialRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpMaterialSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpMaterialGetPluginOffset(RwUInt32 pluginID); +RwBool RpMaterialValidatePlugins(const RpMaterial *material); +RwUInt32 RpMaterialStreamGetSize(const RpMaterial *material); +RpMaterial *RpMaterialStreamRead(RwStream *stream); +const RpMaterial *RpMaterialStreamWrite(const RpMaterial *material, RwStream *stream); +//RpMaterialChunkInfo *_rpMaterialChunkInfoRead(RwStream *stream, RpMaterialChunkInfo *materialChunkInfo, RwInt32 *bytesRead); + + +/* + *********************************************** + * + * RpLight + * + *********************************************** + */ + +//struct RpLight; +typedef rw::Light RpLight; + +enum RpLightType +{ + rpNALIGHTTYPE = 0, + rpLIGHTDIRECTIONAL, + rpLIGHTAMBIENT, + rpLIGHTPOINT = 0x80, + rpLIGHTSPOT, + rpLIGHTSPOTSOFT, +}; + +enum RpLightFlag +{ + rpLIGHTLIGHTATOMICS = 0x01, + rpLIGHTLIGHTWORLD = 0x02, +}; + +typedef RpLight *(*RpLightCallBack) (RpLight * light, void *data); + +RwReal RpLightGetRadius(const RpLight *light); +const RwRGBAReal *RpLightGetColor(const RpLight *light); +RpLight *RpLightSetFrame(RpLight *light, RwFrame *frame); +RwFrame *RpLightGetFrame(const RpLight *light); +RpLightType RpLightGetType(const RpLight *light); +RpLight *RpLightSetFlags(RpLight *light, RwUInt32 flags); +RwUInt32 RpLightGetFlags(const RpLight *light); +RpLight *RpLightCreate(RwInt32 type); +RwBool RpLightDestroy(RpLight *light); +RpLight *RpLightSetRadius(RpLight *light, RwReal radius); +RpLight *RpLightSetColor(RpLight *light, const RwRGBAReal *color); +RwReal RpLightGetConeAngle(const RpLight *light); +RpLight *RpLightSetConeAngle(RpLight * ight, RwReal angle); +RwUInt32 RpLightStreamGetSize(const RpLight *light); +RpLight *RpLightStreamRead(RwStream *stream); +const RpLight *RpLightStreamWrite(const RpLight *light, RwStream *stream); +//RpLightChunkInfo *_rpLightChunkInfoRead(RwStream *stream, RpLightChunkInfo *lightChunkInfo, RwInt32 *bytesRead); +RwInt32 RpLightRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpLightRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpLightSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpLightGetPluginOffset(RwUInt32 pluginID); +RwBool RpLightValidatePlugins(const RpLight * light); + +/* + *********************************************** + * + * RpGeometry + * + *********************************************** + */ + +typedef rw::Triangle RpTriangle; + +//struct RpGeometry; +typedef rw::Geometry RpGeometry; +//struct RpMorphTarget; +typedef rw::MorphTarget RpMorphTarget; + +enum RpGeometryFlag +{ + rpGEOMETRYTRISTRIP = rw::Geometry::TRISTRIP, + rpGEOMETRYPOSITIONS = rw::Geometry::POSITIONS, + rpGEOMETRYTEXTURED = rw::Geometry::TEXTURED, + rpGEOMETRYPRELIT = rw::Geometry::PRELIT, + rpGEOMETRYNORMALS = rw::Geometry::NORMALS, + rpGEOMETRYLIGHT = rw::Geometry::LIGHT, + rpGEOMETRYMODULATEMATERIALCOLOR = rw::Geometry::MODULATE, + rpGEOMETRYTEXTURED2 = rw::Geometry::TEXTURED2, + rpGEOMETRYNATIVE = rw::Geometry::NATIVE, + rpGEOMETRYNATIVEINSTANCE = rw::Geometry::NATIVEINSTANCE, + rpGEOMETRYFLAGSMASK = 0x000000FF, + rpGEOMETRYNATIVEFLAGSMASK = 0x0F000000, +}; + +enum RpGeometryLockMode +{ + rpGEOMETRYLOCKPOLYGONS = rw::Geometry::LOCKPOLYGONS, + rpGEOMETRYLOCKVERTICES = rw::Geometry::LOCKVERTICES, + rpGEOMETRYLOCKNORMALS = rw::Geometry::LOCKNORMALS, + rpGEOMETRYLOCKPRELIGHT = rw::Geometry::LOCKPRELIGHT, + rpGEOMETRYLOCKTEXCOORDS = rw::Geometry::LOCKTEXCOORDS, + rpGEOMETRYLOCKTEXCOORDS1 = rw::Geometry::LOCKTEXCOORDS1, + rpGEOMETRYLOCKTEXCOORDS2 = rw::Geometry::LOCKTEXCOORDS2, + rpGEOMETRYLOCKTEXCOORDS3 = rw::Geometry::LOCKTEXCOORDS3, + rpGEOMETRYLOCKTEXCOORDS4 = rw::Geometry::LOCKTEXCOORDS4, + rpGEOMETRYLOCKTEXCOORDS5 = rw::Geometry::LOCKTEXCOORDS5, + rpGEOMETRYLOCKTEXCOORDS6 = rw::Geometry::LOCKTEXCOORDS6, + rpGEOMETRYLOCKTEXCOORDS7 = rw::Geometry::LOCKTEXCOORDS7, + rpGEOMETRYLOCKTEXCOORDS8 = rw::Geometry::LOCKTEXCOORDS8, + rpGEOMETRYLOCKTEXCOORDSALL = rw::Geometry::LOCKTEXCOORDSALL, + rpGEOMETRYLOCKALL = rw::Geometry::LOCKALL +}; + +RpGeometry *RpGeometryCreate(RwInt32 numVert, RwInt32 numTriangles, RwUInt32 format); +RwBool RpGeometryDestroy(RpGeometry *geometry); +RpGeometry *_rpGeometryAddRef(RpGeometry *geometry); +RpGeometry *RpGeometryLock(RpGeometry *geometry, RwInt32 lockMode); +RpGeometry *RpGeometryUnlock(RpGeometry *geometry); +RpGeometry *RpGeometryTransform(RpGeometry *geometry, const RwMatrix *matrix); +RpGeometry *RpGeometryCreateSpace(RwReal radius); +RpMorphTarget *RpMorphTargetSetBoundingSphere(RpMorphTarget *morphTarget, const RwSphere *boundingSphere); +RwSphere *RpMorphTargetGetBoundingSphere(RpMorphTarget *morphTarget); +const RpMorphTarget *RpMorphTargetCalcBoundingSphere(const RpMorphTarget *morphTarget, RwSphere *boundingSphere); +RwInt32 RpGeometryAddMorphTargets(RpGeometry *geometry, RwInt32 mtcount); +RwInt32 RpGeometryAddMorphTarget(RpGeometry *geometry); +RpGeometry *RpGeometryRemoveMorphTarget(RpGeometry *geometry, RwInt32 morphTarget); +RwInt32 RpGeometryGetNumMorphTargets(const RpGeometry *geometry); +RpMorphTarget *RpGeometryGetMorphTarget(const RpGeometry *geometry, RwInt32 morphTarget); +RwRGBA *RpGeometryGetPreLightColors(const RpGeometry *geometry); +RwTexCoords *RpGeometryGetVertexTexCoords(const RpGeometry *geometry, RwTextureCoordinateIndex uvIndex); +RwInt32 RpGeometryGetNumTexCoordSets(const RpGeometry *geometry); +RwInt32 RpGeometryGetNumVertices (const RpGeometry *geometry); +RwV3d *RpMorphTargetGetVertices(const RpMorphTarget *morphTarget); +RwV3d *RpMorphTargetGetVertexNormals(const RpMorphTarget *morphTarget); +RpTriangle *RpGeometryGetTriangles(const RpGeometry *geometry); +RwInt32 RpGeometryGetNumTriangles(const RpGeometry *geometry); +RpMaterial *RpGeometryGetMaterial(const RpGeometry *geometry, RwInt32 matNum); +const RpGeometry *RpGeometryTriangleSetVertexIndices(const RpGeometry *geometry, RpTriangle *triangle, RwUInt16 vert1, RwUInt16 vert2, RwUInt16 vert3); +RpGeometry *RpGeometryTriangleSetMaterial(RpGeometry *geometry, RpTriangle *triangle, RpMaterial *material); +const RpGeometry *RpGeometryTriangleGetVertexIndices(const RpGeometry *geometry, const RpTriangle *triangle, RwUInt16 *vert1, RwUInt16 *vert2, RwUInt16 *vert3); +RpMaterial *RpGeometryTriangleGetMaterial(const RpGeometry *geometry, const RpTriangle *triangle); +RwInt32 RpGeometryGetNumMaterials(const RpGeometry *geometry); +RpGeometry *RpGeometryForAllMaterials(RpGeometry *geometry, RpMaterialCallBack fpCallBack, void *pData); +//const RpGeometry *RpGeometryForAllMeshes(const RpGeometry *geometry, RpMeshCallBack fpCallBack, void *pData); +RwInt32 RpGeometryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpGeometryRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpGeometrySetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpGeometryGetPluginOffset(RwUInt32 pluginID); +RwBool RpGeometryValidatePlugins(const RpGeometry *geometry); +RwUInt32 RpGeometryStreamGetSize(const RpGeometry *geometry); +const RpGeometry *RpGeometryStreamWrite(const RpGeometry *geometry, RwStream *stream); +RpGeometry *RpGeometryStreamRead(RwStream *stream); +//RpGeometryChunkInfo *_rpGeometryChunkInfoRead(RwStream *stream, RpGeometryChunkInfo *geometryChunkInfo, RwInt32 *bytesRead); +RwUInt32 RpGeometryGetFlags(const RpGeometry *geometry); +RpGeometry *RpGeometrySetFlags(RpGeometry *geometry, RwUInt32 flags); +const RwSurfaceProperties *_rpGeometryGetSurfaceProperties(const RpGeometry *geometry); +RpGeometry *_rpGeometrySetSurfaceProperties(RpGeometry *geometry, const RwSurfaceProperties *surfaceProperties); + + +/* + *********************************************** + * + * RpAtomic and RpClump + * + *********************************************** + */ + +//struct RpAtomic; +typedef rw::Atomic RpAtomic; + +enum RpAtomicFlag +{ + rpATOMICCOLLISIONTEST = 0x01, + rpATOMICRENDER = 0x04, +}; + +enum RpAtomicSetGeomFlag +{ + rpATOMICSAMEBOUNDINGSPHERE = 0x01, +}; + +typedef RpAtomic *(*RpAtomicCallBack) (RpAtomic * atomic, void *data); +typedef RpAtomic *(*RpAtomicCallBackRender) (RpAtomic * atomic); + + +//struct RpClump; +typedef rw::Clump RpClump; + +struct RpClumpChunkInfo +{ + RwInt32 numAtomics; + RwInt32 numLights; + RwInt32 numCameras; +}; + +typedef RpClump *(*RpClumpCallBack) (RpClump * clump, void *data); + + +RpAtomic *AtomicDefaultRenderCallBack(RpAtomic * atomic); +//void _rpAtomicResyncInterpolatedSphere(RpAtomic * atomic); +//const RwSphere *RpAtomicGetWorldBoundingSphere(RpAtomic * atomic); + +RwFrame *RpClumpGetFrame(const RpClump * clump); +RpClump *RpClumpSetFrame(RpClump * clump, RwFrame * frame); +RpClump *RpClumpForAllAtomics(RpClump * clump, RpAtomicCallBack callback, void *pData); +RpClump *RpClumpForAllLights(RpClump * clump, RpLightCallBack callback, void *pData); +RpClump *RpClumpForAllCameras(RpClump * clump, RwCameraCallBack callback, void *pData); +RpClump *RpClumpCreateSpace(const RwV3d * position, RwReal radius); +RpClump *RpClumpRender(RpClump * clump); +RpClump *RpClumpRemoveAtomic(RpClump * clump, RpAtomic * atomic); +RpClump *RpClumpAddAtomic(RpClump * clump, RpAtomic * atomic); +RpClump *RpClumpRemoveLight(RpClump * clump, RpLight * light); +RpClump *RpClumpAddLight(RpClump * clump, RpLight * light); +RpClump *RpClumpRemoveCamera(RpClump * clump, RwCamera * camera); +RpClump *RpClumpAddCamera(RpClump * clump, RwCamera * camera); +RwBool RpClumpDestroy(RpClump * clump); +RpClump *RpClumpCreate(void); +RpClump *RpClumpClone(RpClump * clump); +RpClump *RpClumpSetCallBack(RpClump * clump, RpClumpCallBack callback); +RpClumpCallBack RpClumpGetCallBack(const RpClump * clump); +RwInt32 RpClumpGetNumAtomics(RpClump * clump); +RwInt32 RpClumpGetNumLights(RpClump * clump); +RwInt32 RpClumpGetNumCameras(RpClump * clump); +RwUInt32 RpClumpStreamGetSize(RpClump * clump); +RpClump *RpClumpStreamRead(RwStream * stream); +RpClump *RpClumpStreamWrite(RpClump * clump, RwStream * stream); +RwInt32 RpClumpRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpClumpRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpClumpSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpClumpGetPluginOffset(RwUInt32 pluginID); +RwBool RpClumpValidatePlugins(const RpClump * clump); + +RpAtomic *RpAtomicCreate(void); +RwBool RpAtomicDestroy(RpAtomic * atomic); +RpAtomic *RpAtomicClone(RpAtomic * atomic); +RpAtomic *RpAtomicSetFrame(RpAtomic * atomic, RwFrame * frame); +RpAtomic *RpAtomicSetGeometry(RpAtomic * atomic, RpGeometry * geometry, RwUInt32 flags); + +RwFrame *RpAtomicGetFrame(const RpAtomic * atomic); +RpAtomic *RpAtomicSetFlags(RpAtomic * atomic, RwUInt32 flags); +RwUInt32 RpAtomicGetFlags(const RpAtomic * atomic); +RwSphere *RpAtomicGetBoundingSphere(RpAtomic * atomic); +RpAtomic *RpAtomicRender(RpAtomic * atomic); +RpClump *RpAtomicGetClump(const RpAtomic * atomic); +//RpInterpolator *RpAtomicGetInterpolator(RpAtomic * atomic); +RpGeometry *RpAtomicGetGeometry(const RpAtomic * atomic); +void RpAtomicSetRenderCallBack(RpAtomic * atomic, RpAtomicCallBackRender callback); +RpAtomicCallBackRender RpAtomicGetRenderCallBack(const RpAtomic * atomic); +RwBool RpAtomicInstance(RpAtomic *atomic); +RwUInt32 RpAtomicStreamGetSize(RpAtomic * atomic); +RpAtomic *RpAtomicStreamRead(RwStream * stream); +RpAtomic *RpAtomicStreamWrite(RpAtomic * atomic, RwStream * stream); +RwInt32 RpAtomicRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RpAtomicRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RpAtomicSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); +RwInt32 RpAtomicSetStreamRightsCallBack(RwUInt32 pluginID, RwPluginDataChunkRightsCallBack rightsCB); +RwInt32 RpAtomicGetPluginOffset(RwUInt32 pluginID); +RwBool RpAtomicValidatePlugins(const RpAtomic * atomic); + +//RwInt32 RpInterpolatorGetEndMorphTarget(const RpInterpolator * interpolator); +//RwInt32 RpInterpolatorGetStartMorphTarget(const RpInterpolator * interpolator); +//RwReal RpInterpolatorGetValue(const RpInterpolator * interpolator); +//RwReal RpInterpolatorGetScale(const RpInterpolator * interpolator); +//RpInterpolator *RpInterpolatorSetEndMorphTarget(RpInterpolator * interpolator, RwInt32 morphTarget, RpAtomic * atomic); +//RpInterpolator *RpInterpolatorSetStartMorphTarget(RpInterpolator * interpolator, RwInt32 morphTarget, RpAtomic * atomic); +//RpInterpolator *RpInterpolatorSetValue(RpInterpolator * interpolator, RwReal value, RpAtomic *atomic); +//RpInterpolator *RpInterpolatorSetScale(RpInterpolator * interpolator, RwReal scale, RpAtomic *atomic); + + +RpClump *RpLightGetClump(const RpLight *light); +RpClump *RwCameraGetClump(const RwCamera *camera); + +/* + *********************************************** + * + * RpWorld + * + *********************************************** + */ + +//struct RpWorld; +typedef rw::World RpWorld; + +RwBool RpWorldDestroy(RpWorld * world); +RpWorld *RpWorldCreate(RwBBox * boundingBox); + +RwBool RpWorldPluginAttach(void); + +RpWorld *RpWorldRemoveCamera(RpWorld *world, RwCamera *camera); +RpWorld *RpWorldAddCamera(RpWorld *world, RwCamera *camera); +RpWorld *RwCameraGetWorld(const RwCamera *camera); +RpWorld *RpWorldRemoveAtomic(RpWorld *world, RpAtomic *atomic); +RpWorld *RpWorldAddAtomic(RpWorld *world, RpAtomic *atomic); +RpWorld *RpAtomicGetWorld(const RpAtomic *atomic); +RpWorld *RpWorldAddClump(RpWorld *world, RpClump *clump); +RpWorld *RpWorldRemoveClump(RpWorld *world, RpClump *clump); +RpWorld *RpClumpGetWorld(const RpClump *clump); +RpWorld *RpWorldAddLight(RpWorld *world, RpLight *light); +RpWorld *RpWorldRemoveLight(RpWorld *world, RpLight *light); +RpWorld *RpLightGetWorld(const RpLight *light); +RwCamera *RwCameraForAllClumpsInFrustum(RwCamera *camera, void *data); +RwCamera *RwCameraForAllClumpsNotInFrustum(RwCamera *camera, RwInt32 numClumps, void *data); +//RwCamera *RwCameraForAllSectorsInFrustum(RwCamera *camera, RpWorldSectorCallBack callBack, void *pData); +//RpLight *RpLightForAllWorldSectors(RpLight *light, RpWorldSectorCallBack callback, void *data); +//RpAtomic *RpAtomicForAllWorldSectors(RpAtomic *atomic, RpWorldSectorCallBack callback, void *data); +//RpWorldSector *RpWorldSectorForAllAtomics(RpWorldSector *sector, RpAtomicCallBack callback, void *data); +//RpWorldSector *RpWorldSectorForAllCollisionAtomics(RpWorldSector *sector, RpAtomicCallBack callback, void *data); +//RpWorldSector *RpWorldSectorForAllLights(RpWorldSector *sector, RpLightCallBack callback, void *data); diff --git a/src/fakerw/rtbmp.h b/src/fakerw/rtbmp.h new file mode 100644 index 0000000..896d276 --- /dev/null +++ b/src/fakerw/rtbmp.h @@ -0,0 +1,4 @@ +#pragma once + +RwImage *RtBMPImageWrite(RwImage * image, const RwChar * imageName); +RwImage *RtBMPImageRead(const RwChar * imageName); diff --git a/src/fakerw/rtcharse.h b/src/fakerw/rtcharse.h new file mode 100644 index 0000000..10eb1f3 --- /dev/null +++ b/src/fakerw/rtcharse.h @@ -0,0 +1,14 @@ +#pragma once + +typedef rw::Charset RtCharset; +typedef rw::Charset::Desc RtCharsetDesc; + +RwBool RtCharsetOpen(void); +void RtCharsetClose(void); +RtCharset *RtCharsetPrint(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y); +RtCharset *RtCharsetPrintBuffered(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y, RwBool hideSpaces); +RwBool RtCharsetBufferFlush(void); +RtCharset *RtCharsetSetColors(RtCharset * charSet, const RwRGBA * foreGround, const RwRGBA * backGround); +RtCharset *RtCharsetGetDesc(RtCharset * charset, RtCharsetDesc * desc); +RtCharset *RtCharsetCreate(const RwRGBA * foreGround, const RwRGBA * backGround); +RwBool RtCharsetDestroy(RtCharset * charSet); diff --git a/src/fakerw/rtpng.h b/src/fakerw/rtpng.h new file mode 100644 index 0000000..80f2902 --- /dev/null +++ b/src/fakerw/rtpng.h @@ -0,0 +1,4 @@ +#pragma once + +RwImage *RtPNGImageWrite(RwImage * image, const RwChar * imageName); +RwImage *RtPNGImageRead(const RwChar * imageName); diff --git a/src/fakerw/rtquat.h b/src/fakerw/rtquat.h new file mode 100644 index 0000000..450342b --- /dev/null +++ b/src/fakerw/rtquat.h @@ -0,0 +1,15 @@ +#pragma once + +// Same layout as rw::Quat but with ugly imag,real separation which i don't want in librw +struct RtQuat +{ + rw::V3d imag; + rw::float32 real; +}; + +RwBool RtQuatConvertFromMatrix(RtQuat * const qpQuat, const RwMatrix * const mpMatrix); +RtQuat *RtQuatRotate(RtQuat * quat, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp); +const RtQuat *RtQuatQueryRotate(const RtQuat *quat, RwV3d * unitAxis, RwReal * angle); +RwV3d *RtQuatTransformVectors(RwV3d * vectorsOut, const RwV3d * vectorsIn, const RwInt32 numPoints, const RtQuat *quat); + +void RtQuatConvertToMatrix(const RtQuat * const qpQuat, RwMatrix * const mpMatrix); diff --git a/src/fakerw/rwcore.h b/src/fakerw/rwcore.h new file mode 100644 index 0000000..ab0a719 --- /dev/null +++ b/src/fakerw/rwcore.h @@ -0,0 +1,423 @@ +#pragma once + +#define RWCORE_H // needed by CVector + +#include + +#include + +/* + *********************************************** + * + * RwIm2D and RwIm3D + * + *********************************************** + */ + +typedef rw::RWDEVICE::Im2DVertex RwIm2DVertex; +typedef rw::RWDEVICE::Im3DVertex RwIm3DVertex; +typedef RwUInt16 RwImVertexIndex; + +enum RwIm3DTransformFlags +{ + rwIM3D_VERTEXUV = rw::im3d::VERTEXUV, + rwIM3D_ALLOPAQUE = rw::im3d::ALLOPAQUE, + rwIM3D_NOCLIP = rw::im3d::NOCLIP, + rwIM3D_VERTEXXYZ = rw::im3d::VERTEXXYZ, + rwIM3D_VERTEXRGBA = rw::im3d::VERTEXRGBA, +}; + +void RwIm2DVertexSetCameraX(RwIm2DVertex *vert, RwReal camx); +void RwIm2DVertexSetCameraY(RwIm2DVertex *vert, RwReal camy); +void RwIm2DVertexSetCameraZ(RwIm2DVertex *vert, RwReal camz); +void RwIm2DVertexSetRecipCameraZ(RwIm2DVertex *vert, RwReal recipz); +void RwIm2DVertexSetScreenX(RwIm2DVertex *vert, RwReal scrnx); +void RwIm2DVertexSetScreenY(RwIm2DVertex *vert, RwReal scrny); +void RwIm2DVertexSetScreenZ(RwIm2DVertex *vert, RwReal scrnz); +void RwIm2DVertexSetU(RwIm2DVertex *vert, RwReal texU, RwReal recipz); +void RwIm2DVertexSetV(RwIm2DVertex *vert, RwReal texV, RwReal recipz); +void RwIm2DVertexSetIntRGBA(RwIm2DVertex *vert, RwUInt8 red, RwUInt8 green, RwUInt8 blue, RwUInt8 alpha); + +RwReal RwIm2DGetNearScreenZ(void); +RwReal RwIm2DGetFarScreenZ(void); +RwBool RwIm2DRenderLine(RwIm2DVertex *vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2); +RwBool RwIm2DRenderTriangle(RwIm2DVertex *vertices, RwInt32 numVertices, RwInt32 vert1, RwInt32 vert2, RwInt32 vert3 ); +RwBool RwIm2DRenderPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices); +RwBool RwIm2DRenderIndexedPrimitive(RwPrimitiveType primType, RwIm2DVertex *vertices, RwInt32 numVertices, RwImVertexIndex *indices, RwInt32 numIndices); + + +void RwIm3DVertexSetPos(RwIm3DVertex *vert, RwReal x, RwReal y, RwReal z); +void RwIm3DVertexSetU(RwIm3DVertex *vert, RwReal u); +void RwIm3DVertexSetV(RwIm3DVertex *vert, RwReal v); +void RwIm3DVertexSetRGBA(RwIm3DVertex *vert, RwUInt8 r, RwUInt8 g, RwUInt8 b, RwUInt8 a); + +void *RwIm3DTransform(RwIm3DVertex *pVerts, RwUInt32 numVerts, RwMatrix *ltm, RwUInt32 flags); +RwBool RwIm3DEnd(void); +RwBool RwIm3DRenderLine(RwInt32 vert1, RwInt32 vert2); +RwBool RwIm3DRenderTriangle(RwInt32 vert1, RwInt32 vert2, RwInt32 vert3); +RwBool RwIm3DRenderIndexedPrimitive(RwPrimitiveType primType, RwImVertexIndex *indices, RwInt32 numIndices); +RwBool RwIm3DRenderPrimitive(RwPrimitiveType primType); + + +/* + *********************************************** + * + * RwRaster + * + *********************************************** + */ + +//struct RwRaster; +typedef rw::Raster RwRaster; + +enum RwRasterType +{ + rwRASTERTYPENORMAL = rw::Raster::NORMAL, + rwRASTERTYPEZBUFFER = rw::Raster::ZBUFFER, + rwRASTERTYPECAMERA = rw::Raster::CAMERA, + rwRASTERTYPETEXTURE = rw::Raster::TEXTURE, + rwRASTERTYPECAMERATEXTURE = rw::Raster::CAMERATEXTURE, + rwRASTERTYPEMASK = 0x07, + rwRASTERDONTALLOCATE = rw::Raster::DONTALLOCATE, +}; + +enum RwRasterFormat +{ + rwRASTERFORMATDEFAULT = rw::Raster::DEFAULT, + rwRASTERFORMAT1555 = rw::Raster::C1555, + rwRASTERFORMAT565 = rw::Raster::C565, + rwRASTERFORMAT4444 = rw::Raster::C4444, + rwRASTERFORMATLUM8 = rw::Raster::LUM8, + rwRASTERFORMAT8888 = rw::Raster::C8888, + rwRASTERFORMAT888 = rw::Raster::C888, + rwRASTERFORMAT16 = rw::Raster::D16, + rwRASTERFORMAT24 = rw::Raster::D24, + rwRASTERFORMAT32 = rw::Raster::D32, + rwRASTERFORMAT555 = rw::Raster::C555, + + rwRASTERFORMATAUTOMIPMAP = rw::Raster::AUTOMIPMAP, + rwRASTERFORMATPAL8 = rw::Raster::PAL8, + rwRASTERFORMATPAL4 = rw::Raster::PAL4, + rwRASTERFORMATMIPMAP = rw::Raster::MIPMAP, + + rwRASTERFORMATPIXELFORMATMASK = 0x0f00, + rwRASTERFORMATMASK = 0xff00 +}; + +enum RwRasterLockMode +{ + rwRASTERLOCKWRITE = rw::Raster::LOCKWRITE, + rwRASTERLOCKREAD = rw::Raster::LOCKREAD, + rwRASTERLOCKNOFETCH = rw::Raster::LOCKNOFETCH, + rwRASTERLOCKRAW = rw::Raster::LOCKRAW, +}; + +enum RwRasterFlipMode +{ + rwRASTERFLIPDONTWAIT = 0, + rwRASTERFLIPWAITVSYNC = 1, +}; + +RwRaster *RwRasterCreate(RwInt32 width, RwInt32 height, RwInt32 depth, RwInt32 flags); +RwBool RwRasterDestroy(RwRaster * raster); +RwInt32 RwRasterGetWidth(const RwRaster *raster); +RwInt32 RwRasterGetHeight(const RwRaster *raster); +RwInt32 RwRasterGetStride(const RwRaster *raster); +RwInt32 RwRasterGetDepth(const RwRaster *raster); +RwInt32 RwRasterGetFormat(const RwRaster *raster); +RwInt32 RwRasterGetType(const RwRaster *raster); +RwRaster *RwRasterGetParent(const RwRaster *raster); +RwRaster *RwRasterGetOffset(RwRaster *raster, RwInt16 *xOffset, RwInt16 *yOffset); +RwInt32 RwRasterGetNumLevels(RwRaster * raster); +RwRaster *RwRasterSubRaster(RwRaster * subRaster, RwRaster * raster, RwRect * rect); +RwRaster *RwRasterRenderFast(RwRaster * raster, RwInt32 x, RwInt32 y); +RwRaster *RwRasterRender(RwRaster * raster, RwInt32 x, RwInt32 y); +RwRaster *RwRasterRenderScaled(RwRaster * raster, RwRect * rect); +RwRaster *RwRasterPushContext(RwRaster * raster); +RwRaster *RwRasterPopContext(void); +RwRaster *RwRasterGetCurrentContext(void); +RwBool RwRasterClear(RwInt32 pixelValue); +RwBool RwRasterClearRect(RwRect * rpRect, RwInt32 pixelValue); +RwRaster *RwRasterShowRaster(RwRaster * raster, void *dev, RwUInt32 flags); +RwUInt8 *RwRasterLock(RwRaster * raster, RwUInt8 level, RwInt32 lockMode); +RwRaster *RwRasterUnlock(RwRaster * raster); +RwUInt8 *RwRasterLockPalette(RwRaster * raster, RwInt32 lockMode); +RwRaster *RwRasterUnlockPalette(RwRaster * raster); +RwInt32 RwRasterRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwRasterGetPluginOffset(RwUInt32 pluginID); +RwBool RwRasterValidatePlugins(const RwRaster * raster); + + +/* + *********************************************** + * + * RwImage + * + *********************************************** + */ + +//struct RwImage; +typedef rw::Image RwImage; + +RwImage *RwImageCreate(RwInt32 width, RwInt32 height, RwInt32 depth); +RwBool RwImageDestroy(RwImage * image); +RwImage *RwImageAllocatePixels(RwImage * image); +RwImage *RwImageFreePixels(RwImage * image); +RwImage *RwImageCopy(RwImage * destImage, const RwImage * sourceImage); +RwImage *RwImageResize(RwImage * image, RwInt32 width, RwInt32 height); +RwImage *RwImageApplyMask(RwImage * image, const RwImage * mask); +RwImage *RwImageMakeMask(RwImage * image); +RwImage *RwImageReadMaskedImage(const RwChar * imageName, const RwChar * maskname); +RwImage *RwImageRead(const RwChar * imageName); +RwImage *RwImageWrite(RwImage * image, const RwChar * imageName); +RwChar *RwImageGetPath(void); +const RwChar *RwImageSetPath(const RwChar * path); +RwImage *RwImageSetStride(RwImage * image, RwInt32 stride); +RwImage *RwImageSetPixels(RwImage * image, RwUInt8 * pixels); +RwImage *RwImageSetPalette(RwImage * image, RwRGBA * palette); +RwInt32 RwImageGetWidth(const RwImage * image); +RwInt32 RwImageGetHeight(const RwImage * image); +RwInt32 RwImageGetDepth(const RwImage * image); +RwInt32 RwImageGetStride(const RwImage * image); +RwUInt8 *RwImageGetPixels(const RwImage * image); +RwRGBA *RwImageGetPalette(const RwImage * image); +RwUInt32 RwRGBAToPixel(RwRGBA * rgbIn, RwInt32 rasterFormat); +RwRGBA *RwRGBASetFromPixel(RwRGBA * rgbOut, RwUInt32 pixelValue, RwInt32 rasterFormat); +RwBool RwImageSetGamma(RwReal gammaValue); +RwReal RwImageGetGamma(void); +RwImage *RwImageGammaCorrect(RwImage * image); +RwRGBA *RwRGBAGammaCorrect(RwRGBA * rgb); +RwInt32 RwImageRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwImageGetPluginOffset(RwUInt32 pluginID); +RwBool RwImageValidatePlugins(const RwImage * image); +//RwBool RwImageRegisterImageFormat(const RwChar * extension, RwImageCallBackRead imageRead, RwImageCallBackWrite imageWrite); +const RwChar *RwImageFindFileType(const RwChar * imageName); +RwInt32 RwImageStreamGetSize(const RwImage * image); +RwImage *RwImageStreamRead(RwStream * stream); +const RwImage *RwImageStreamWrite(const RwImage * image, RwStream * stream); + + +/* + *********************************************** + * + * RwTexture + * + *********************************************** + */ + +//struct RwTexture; +typedef rw::Texture RwTexture; +//struct RwTexDictionary; +typedef rw::TexDictionary RwTexDictionary; + +typedef RwTexture *(*RwTextureCallBackRead)(const RwChar *name, const RwChar *maskName); +typedef RwTexture *(*RwTextureCallBack)(RwTexture *texture, void *pData); +typedef RwTexDictionary *(*RwTexDictionaryCallBack)(RwTexDictionary *dict, void *data); +typedef RwRaster *(*RwTextureCallBackMipmapGeneration)(RwRaster * raster, RwImage * image); +typedef RwBool (*RwTextureCallBackMipmapName)(RwChar *name, RwChar *maskName, RwUInt8 mipLevel, RwInt32 format); + +RwTexture *RwTextureCreate(RwRaster * raster); +RwBool RwTextureDestroy(RwTexture * texture); +RwTexture *RwTextureAddRef(RwTexture *texture); +RwBool RwTextureSetMipmapping(RwBool enable); +RwBool RwTextureGetMipmapping(void); +RwBool RwTextureSetAutoMipmapping(RwBool enable); +RwBool RwTextureGetAutoMipmapping(void); +RwBool RwTextureSetMipmapGenerationCallBack(RwTextureCallBackMipmapGeneration callback); +RwTextureCallBackMipmapGeneration RwTextureGetMipmapGenerationCallBack(void); +RwBool RwTextureSetMipmapNameCallBack(RwTextureCallBackMipmapName callback); +RwTextureCallBackMipmapName RwTextureGetMipmapNameCallBack(void); +RwBool RwTextureGenerateMipmapName(RwChar * name, RwChar * maskName, RwUInt8 mipLevel, RwInt32 format); +RwBool RwTextureRasterGenerateMipmaps(RwRaster * raster, RwImage * image); +RwTextureCallBackRead RwTextureGetReadCallBack(void); +RwBool RwTextureSetReadCallBack(RwTextureCallBackRead fpCallBack); +RwTexture *RwTextureSetName(RwTexture * texture, const RwChar * name); +RwTexture *RwTextureSetMaskName(RwTexture * texture, const RwChar * maskName); +RwChar *RwTextureGetName(RwTexture *texture); +RwChar *RwTextureGetMaskName(RwTexture *texture); +RwTexture *RwTextureSetRaster(RwTexture * texture, RwRaster * raster); +RwTexture *RwTextureRead(const RwChar * name, const RwChar * maskName); +RwRaster *RwTextureGetRaster(const RwTexture *texture); +RwInt32 RwTextureRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwTextureGetPluginOffset(RwUInt32 pluginID); +RwBool RwTextureValidatePlugins(const RwTexture * texture); + +RwTexDictionary *RwTextureGetDictionary(RwTexture *texture); +RwTexture *RwTextureSetFilterMode(RwTexture *texture, RwTextureFilterMode filtering); +RwTextureFilterMode RwTextureGetFilterMode(const RwTexture *texture); +RwTexture *RwTextureSetAddressing(RwTexture *texture, RwTextureAddressMode addressing); +RwTexture *RwTextureSetAddressingU(RwTexture *texture, RwTextureAddressMode addressing); +RwTexture *RwTextureSetAddressingV(RwTexture *texture, RwTextureAddressMode addressing); +RwTextureAddressMode RwTextureGetAddressing(const RwTexture *texture); +RwTextureAddressMode RwTextureGetAddressingU(const RwTexture *texture); +RwTextureAddressMode RwTextureGetAddressingV(const RwTexture *texture); + +void _rwD3D8TexDictionaryEnableRasterFormatConversion(bool enable); + +// hack for reading native textures +RwBool rwNativeTextureHackRead(RwStream *stream, RwTexture **tex, RwInt32 size); + + +RwTexDictionary *RwTexDictionaryCreate(void); +RwBool RwTexDictionaryDestroy(RwTexDictionary * dict); +RwTexture *RwTexDictionaryAddTexture(RwTexDictionary * dict, RwTexture * texture); +RwTexture *RwTexDictionaryRemoveTexture(RwTexture * texture); +RwTexture *RwTexDictionaryFindNamedTexture(RwTexDictionary * dict, const RwChar * name); +RwTexDictionary *RwTexDictionaryGetCurrent(void); +RwTexDictionary *RwTexDictionarySetCurrent(RwTexDictionary * dict); +const RwTexDictionary *RwTexDictionaryForAllTextures(const RwTexDictionary * dict, RwTextureCallBack fpCallBack, void *pData); +RwBool RwTexDictionaryForAllTexDictionaries(RwTexDictionaryCallBack fpCallBack, void *pData); +RwInt32 RwTexDictionaryRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwTexDictionaryGetPluginOffset(RwUInt32 pluginID); +RwBool RwTexDictionaryValidatePlugins(const RwTexDictionary * dict); +RwUInt32 RwTexDictionaryStreamGetSize(const RwTexDictionary *texDict); +RwTexDictionary *RwTexDictionaryStreamRead(RwStream *stream); +const RwTexDictionary *RwTexDictionaryStreamWrite(const RwTexDictionary *texDict, RwStream *stream); + +/* RwImage/RwRaster */ + +RwImage *RwImageSetFromRaster(RwImage *image, RwRaster *raster); +RwRaster *RwRasterSetFromImage(RwRaster *raster, RwImage *image); +RwRGBA *RwRGBAGetRasterPixel(RwRGBA *rgbOut, RwRaster *raster, RwInt32 x, RwInt32 y); +RwRaster *RwRasterRead(const RwChar *filename); +RwRaster *RwRasterReadMaskedRaster(const RwChar *filename, const RwChar *maskname); +RwImage *RwImageFindRasterFormat(RwImage *ipImage,RwInt32 nRasterType, RwInt32 *npWidth,RwInt32 *npHeight, RwInt32 *npDepth,RwInt32 *npFormat); + + +/* + *********************************************** + * + * RwFrame + * + *********************************************** + */ + +//struct RwFrame; +typedef rw::Frame RwFrame; + +typedef RwFrame *(*RwFrameCallBack)(RwFrame *frame, void *data); + + +RwFrame *RwFrameForAllObjects(RwFrame * frame, RwObjectCallBack callBack, void *data); +RwFrame *RwFrameTranslate(RwFrame * frame, const RwV3d * v, RwOpCombineType combine); +RwFrame *RwFrameRotate(RwFrame * frame, const RwV3d * axis, RwReal angle, RwOpCombineType combine); +RwFrame *RwFrameScale(RwFrame * frame, const RwV3d * v, RwOpCombineType combine); +RwFrame *RwFrameTransform(RwFrame * frame, const RwMatrix * m, RwOpCombineType combine); +RwFrame *RwFrameOrthoNormalize(RwFrame * frame); +RwFrame *RwFrameSetIdentity(RwFrame * frame); +RwFrame *RwFrameCloneHierarchy(RwFrame * root); +RwBool RwFrameDestroyHierarchy(RwFrame * frame); +RwFrame *RwFrameForAllChildren(RwFrame * frame, RwFrameCallBack callBack, void *data); +RwFrame *RwFrameRemoveChild(RwFrame * child); +RwFrame *RwFrameAddChild(RwFrame * parent, RwFrame * child); +RwFrame *RwFrameGetParent(const RwFrame * frame); +RwFrame *RwFrameGetRoot(const RwFrame * frame); +RwMatrix *RwFrameGetLTM(RwFrame * frame); +RwMatrix *RwFrameGetMatrix(RwFrame * frame); +RwFrame *RwFrameUpdateObjects(RwFrame * frame); +RwFrame *RwFrameCreate(void); +RwBool RwFrameInit(RwFrame *frame); +RwBool RwFrameDeInit(RwFrame *frame); +RwBool RwFrameDestroy(RwFrame * frame); +void _rwFrameInit(RwFrame *frame); +void _rwFrameDeInit(RwFrame *frame); +RwBool RwFrameDirty(const RwFrame * frame); +RwInt32 RwFrameCount(RwFrame * frame); +RwBool RwFrameSetStaticPluginsSize(RwInt32 size); +RwInt32 RwFrameRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwFrameGetPluginOffset(RwUInt32 pluginID); +RwBool RwFrameValidatePlugins(const RwFrame * frame); +RwFrame *_rwFrameCloneAndLinkClones(RwFrame * root); +RwFrame *_rwFramePurgeClone(RwFrame *root); + +RwInt32 RwFrameRegisterPluginStream(RwUInt32 pluginID, RwPluginDataChunkReadCallBack readCB, RwPluginDataChunkWriteCallBack writeCB, RwPluginDataChunkGetSizeCallBack getSizeCB); +RwInt32 RwFrameSetStreamAlwaysCallBack(RwUInt32 pluginID, RwPluginDataChunkAlwaysCallBack alwaysCB); + +typedef rw::FrameList_ rwFrameList; +rwFrameList *rwFrameListInitialize(rwFrameList *frameList, RwFrame *frame); +RwBool rwFrameListFindFrame(const rwFrameList *frameList, const RwFrame *frame, RwInt32 *npIndex); +rwFrameList *rwFrameListDeinitialize(rwFrameList *frameList); +RwUInt32 rwFrameListStreamGetSize(const rwFrameList *frameList); +rwFrameList *rwFrameListStreamRead(RwStream *stream, rwFrameList *fl); +const rwFrameList *rwFrameListStreamWrite(const rwFrameList *frameList, RwStream *stream); + + +typedef rw::BBox RwBBox; + +/* + *********************************************** + * + * RwCamera + * + *********************************************** + */ + +//struct RwCamera; +typedef rw::Camera RwCamera; + +typedef RwCamera *(*RwCameraCallBack)(RwCamera *camera, void *data); + +enum RwCameraClearMode +{ + rwCAMERACLEARIMAGE = 0x1, + rwCAMERACLEARZ = 0x2, + rwCAMERACLEARSTENCIL = 0x4 +}; + +enum RwCameraProjection +{ + rwNACAMERAPROJECTION = 0, + rwPERSPECTIVE = 1, + rwPARALLEL = 2 +}; + +enum RwFrustumTestResult +{ + rwSPHEREOUTSIDE = 0, + rwSPHEREBOUNDARY = 1, + rwSPHEREINSIDE = 2 +}; + +RwCamera *RwCameraBeginUpdate(RwCamera * camera); +RwCamera *RwCameraEndUpdate(RwCamera * camera); +RwCamera *RwCameraClear(RwCamera * camera, RwRGBA * colour, RwInt32 clearMode); +RwCamera *RwCameraShowRaster(RwCamera * camera, void *pDev, RwUInt32 flags); +RwBool RwCameraDestroy(RwCamera * camera); +RwCamera *RwCameraCreate(void); +RwCamera *RwCameraClone(RwCamera * camera); +RwCamera *RwCameraSetViewOffset(RwCamera *camera, const RwV2d *offset); +RwCamera *RwCameraSetViewWindow(RwCamera *camera, const RwV2d *viewWindow); +RwCamera *RwCameraSetProjection(RwCamera *camera, RwCameraProjection projection); +RwCamera *RwCameraSetNearClipPlane(RwCamera *camera, RwReal nearClip); +RwCamera *RwCameraSetFarClipPlane(RwCamera *camera, RwReal farClip); +RwInt32 RwCameraRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor constructCB, RwPluginObjectDestructor destructCB, RwPluginObjectCopy copyCB); +RwInt32 RwCameraGetPluginOffset(RwUInt32 pluginID); +RwBool RwCameraValidatePlugins(const RwCamera * camera); +RwFrustumTestResult RwCameraFrustumTestSphere(const RwCamera * camera, const RwSphere * sphere); +const RwV2d *RwCameraGetViewOffset(const RwCamera *camera); +RwCamera *RwCameraSetRaster(RwCamera *camera, RwRaster *raster); +RwRaster *RwCameraGetRaster(const RwCamera *camera); +RwCamera *RwCameraSetZRaster(RwCamera *camera, RwRaster *zRaster); +RwRaster *RwCameraGetZRaster(const RwCamera *camera); +RwReal RwCameraGetNearClipPlane(const RwCamera *camera); +RwReal RwCameraGetFarClipPlane(const RwCamera *camera); +RwCamera *RwCameraSetFogDistance(RwCamera *camera, RwReal fogDistance); +RwReal RwCameraGetFogDistance(const RwCamera *camera); +RwCamera *RwCameraGetCurrentCamera(void); +RwCameraProjection RwCameraGetProjection(const RwCamera *camera); +const RwV2d *RwCameraGetViewWindow(const RwCamera *camera); +RwMatrix *RwCameraGetViewMatrix(RwCamera *camera); +RwCamera *RwCameraSetFrame(RwCamera *camera, RwFrame *frame); +RwFrame *RwCameraGetFrame(const RwCamera *camera); + + +/* + * + * D3D-engine specific stuff + * + */ + +void RwD3D8EngineSetRefreshRate(RwUInt32 refreshRate); +RwBool RwD3D8DeviceSupportsDXTTexture(void); +void RwD3D8EngineSetMultiSamplingLevels(RwUInt32 level); +RwUInt32 RwD3D8EngineGetMaxMultiSamplingLevels(void); diff --git a/src/fakerw/rwplcore.h b/src/fakerw/rwplcore.h new file mode 100644 index 0000000..69c921c --- /dev/null +++ b/src/fakerw/rwplcore.h @@ -0,0 +1,498 @@ +#pragma once + +typedef rw::int8 RwInt8; +typedef rw::int16 RwInt16; +typedef rw::int32 RwInt32; +typedef rw::uint8 RwUInt8; +typedef rw::uint16 RwUInt16; +typedef rw::uint32 RwUInt32; +typedef rw::float32 RwReal; + +typedef char RwChar; +typedef RwInt32 RwBool; + +#define __RWUNUSED__ + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE !FALSE +#endif + +// used for unicode +#define RWSTRING(x) x + +typedef rw::V2d RwV2d; + +typedef rw::V3d RwV3d; + +typedef rw::Rect RwRect; + +typedef rw::Sphere RwSphere; + +enum RwTextureCoordinateIndex +{ + rwNARWTEXTURECOORDINATEINDEX = 0, + rwTEXTURECOORDINATEINDEX0, + rwTEXTURECOORDINATEINDEX1, + rwTEXTURECOORDINATEINDEX2, + rwTEXTURECOORDINATEINDEX3, + rwTEXTURECOORDINATEINDEX4, + rwTEXTURECOORDINATEINDEX5, + rwTEXTURECOORDINATEINDEX6, + rwTEXTURECOORDINATEINDEX7, +}; + +typedef rw::TexCoords RwTexCoords; + +typedef rw::SurfaceProperties RwSurfaceProperties; + +#define RWRGBALONG(r,g,b,a) \ + ((RwUInt32) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))) + + +#define MAKECHUNKID(vendorID, chunkID) (((vendorID & 0xFFFFFF) << 8) | (chunkID & 0xFF)) + +enum RwCorePluginID +{ + rwID_NAOBJECT = 0x00, + rwID_STRUCT = 0x01, + rwID_STRING = 0x02, + rwID_EXTENSION = 0x03, + rwID_CAMERA = 0x05, + rwID_TEXTURE = 0x06, + rwID_MATERIAL = 0x07, + rwID_MATLIST = 0x08, + rwID_ATOMICSECT = 0x09, + rwID_PLANESECT = 0x0A, + rwID_WORLD = 0x0B, + rwID_SPLINE = 0x0C, + rwID_MATRIX = 0x0D, + rwID_FRAMELIST = 0x0E, + rwID_GEOMETRY = 0x0F, + rwID_CLUMP = 0x10, + rwID_LIGHT = 0x12, + rwID_UNICODESTRING = 0x13, + rwID_ATOMIC = 0x14, + rwID_TEXTURENATIVE = 0x15, + rwID_TEXDICTIONARY = 0x16, + rwID_ANIMDATABASE = 0x17, + rwID_IMAGE = 0x18, + rwID_SKINANIMATION = 0x19, + rwID_GEOMETRYLIST = 0x1A, + rwID_HANIMANIMATION = 0x1B, + rwID_TEAM = 0x1C, + rwID_CROWD = 0x1D, + rwID_DMORPHANIMATION = 0x1E, + rwID_RIGHTTORENDER = 0x1f, + rwID_MTEFFECTNATIVE = 0x20, + rwID_MTEFFECTDICT = 0x21, + rwID_TEAMDICTIONARY = 0x22, + rwID_PITEXDICTIONARY = 0x23, + rwID_TOC = 0x24, + rwID_PRTSTDGLOBALDATA = 0x25, + /* Insert before MAX and increment MAX */ + rwID_COREPLUGINIDMAX = 0x26, +}; + + +/* + *********************************************** + * + * RwObject + * + *********************************************** + */ + +//struct RwObject; +typedef rw::Object RwObject; +typedef rw::Frame RwFrame; + +typedef RwObject *(*RwObjectCallBack)(RwObject *object, void *data); + +RwUInt8 RwObjectGetType(const RwObject *obj); +RwFrame* rwObjectGetParent(const RwObject *obj); + +#define rwsprintf sprintf +#define rwvsprintf vsprintf +#define rwstrcpy strcpy +#define rwstrncpy strncpy +#define rwstrcat strcat +#define rwstrncat strncat +#define rwstrrchr strrchr +#define rwstrchr strchr +#define rwstrstr strstr +#define rwstrcmp strcmp +#define rwstricmp stricmp +#define rwstrlen strlen +#define rwstrupr strupr +#define rwstrlwr strlwr +#define rwstrtok strtok +#define rwsscanf sscanf + + +/* + *********************************************** + * + * Memory + * + *********************************************** + */ + +struct RwMemoryFunctions +{ + // NB: from RW 3.6 on the allocating functions take + // a hint parameter! + void *(*rwmalloc)(size_t size); + void (*rwfree)(void *mem); + void *(*rwrealloc)(void *mem, size_t newSize); + void *(*rwcalloc)(size_t numObj, size_t sizeObj); +}; + +void *RwMalloc(size_t size); +void RwFree(void *mem); +void *RwRealloc(void *mem, size_t newSize); +void *RwCalloc(size_t numObj, size_t sizeObj); + +/* + *********************************************** + * + * RwStream + * + *********************************************** + */ + +//struct RwStream; +typedef rw::Stream RwStream; + +struct RwMemory +{ + RwUInt8 *start; + RwUInt32 length; +}; + +enum RwStreamType +{ + rwNASTREAM = 0, + rwSTREAMFILE, + rwSTREAMFILENAME, + rwSTREAMMEMORY, + rwSTREAMCUSTOM +}; + +enum RwStreamAccessType +{ + rwNASTREAMACCESS = 0, + rwSTREAMREAD, + rwSTREAMWRITE, + rwSTREAMAPPEND +}; + +RwStream *RwStreamOpen(RwStreamType type, RwStreamAccessType accessType, const void *pData); +RwBool RwStreamClose(RwStream * stream, void *pData); +RwUInt32 RwStreamRead(RwStream * stream, void *buffer, RwUInt32 length); +RwStream *RwStreamWrite(RwStream * stream, const void *buffer, RwUInt32 length); +RwStream *RwStreamSkip(RwStream * stream, RwUInt32 offset); + + +/* + *********************************************** + * + * Plugin Registry + * + *********************************************** + */ + +#define RWPLUGINOFFSET(_type, _base, _offset) \ + ((_type *)((RwUInt8 *)(_base) + (_offset))) + +typedef RwStream *(*RwPluginDataChunkWriteCallBack)(RwStream *stream, RwInt32 binaryLength, const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef RwStream *(*RwPluginDataChunkReadCallBack)(RwStream *stream, RwInt32 binaryLength, void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef RwInt32(*RwPluginDataChunkGetSizeCallBack)(const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef RwBool(*RwPluginDataChunkAlwaysCallBack)(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef RwBool(*RwPluginDataChunkRightsCallBack)(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject, RwUInt32 extraData); +typedef void *(*RwPluginObjectConstructor)(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef void *(*RwPluginObjectCopy)(void *dstObject, const void *srcObject, RwInt32 offsetInObject, RwInt32 sizeInObject); +typedef void *(*RwPluginObjectDestructor)(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject); + +/* + *********************************************** + * + * RwMatrix + * + *********************************************** + */ + +typedef rw::Matrix RwMatrix; + +enum RwOpCombineType +{ + rwCOMBINEREPLACE = rw::COMBINEREPLACE, + rwCOMBINEPRECONCAT = rw::COMBINEPRECONCAT, + rwCOMBINEPOSTCONCAT = rw::COMBINEPOSTCONCAT +}; + +enum RwMatrixType +{ + rwMATRIXTYPENORMAL = rw::Matrix::TYPENORMAL, + rwMATRIXTYPEORTHOGANAL = rw::Matrix::TYPEORTHOGONAL, + rwMATRIXTYPEORTHONORMAL = rw::Matrix::TYPEORTHONORMAL, + rwMATRIXTYPEMASK = 0x00000003, +}; + +typedef rw::Matrix::Tolerance RwMatrixTolerance; + +RwBool RwMatrixDestroy(RwMatrix *mpMat); +RwMatrix *RwMatrixCreate(void); +void RwMatrixCopy(RwMatrix * dstMatrix, const RwMatrix * srcMatrix); +void RwMatrixSetIdentity(RwMatrix * matrix); +RwMatrix *RwMatrixMultiply(RwMatrix * matrixOut, const RwMatrix * MatrixIn1, const RwMatrix * matrixIn2); +RwMatrix *RwMatrixTransform(RwMatrix * matrix, const RwMatrix * transform, RwOpCombineType combineOp); +RwMatrix *RwMatrixOrthoNormalize(RwMatrix * matrixOut, const RwMatrix * matrixIn); +RwMatrix *RwMatrixInvert(RwMatrix * matrixOut, const RwMatrix * matrixIn); +RwMatrix *RwMatrixScale(RwMatrix * matrix, const RwV3d * scale, RwOpCombineType combineOp); +RwMatrix *RwMatrixTranslate(RwMatrix * matrix, const RwV3d * translation, RwOpCombineType combineOp); +RwMatrix *RwMatrixRotate(RwMatrix * matrix, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp); +RwMatrix *RwMatrixRotateOneMinusCosineSine(RwMatrix * matrix, const RwV3d * unitAxis, RwReal oneMinusCosine, RwReal sine, RwOpCombineType combineOp); +const RwMatrix *RwMatrixQueryRotate(const RwMatrix * matrix, RwV3d * unitAxis, RwReal * angle, RwV3d * center); +RwV3d *RwMatrixGetRight(RwMatrix * matrix); +RwV3d *RwMatrixGetUp(RwMatrix * matrix); +RwV3d *RwMatrixGetAt(RwMatrix * matrix); +RwV3d *RwMatrixGetPos(RwMatrix * matrix); +RwMatrix *RwMatrixUpdate(RwMatrix * matrix); +RwMatrix *RwMatrixOptimize(RwMatrix * matrix, const RwMatrixTolerance *tolerance); + +/* + *********************************************** + * + * RwRGBA + * + *********************************************** + */ + +typedef rw::RGBA RwRGBA; +typedef rw::RGBAf RwRGBAReal; + + +inline void RwRGBAAssign(RwRGBA *target, const RwRGBA *source) { *target = *source; } + + +RwReal RwV3dNormalize(RwV3d * out, const RwV3d * in); +RwReal RwV3dLength(const RwV3d * in); +RwReal RwV2dLength(const RwV2d * in); +RwReal RwV2dNormalize(RwV2d * out, const RwV2d * in); +void RwV2dAssign(RwV2d * out, const RwV2d * ina); +void RwV2dAdd(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +void RwV2dLineNormal(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +void RwV2dSub(RwV2d * out, const RwV2d * ina, const RwV2d * inb); +void RwV2dPerp(RwV2d * out, const RwV2d * in); +void RwV2dScale(RwV2d * out, const RwV2d * in, RwReal scalar); +RwReal RwV2dDotProduct(const RwV2d * ina, const RwV2d * inb); +void RwV3dAssign(RwV3d * out, const RwV3d * ina); +void RwV3dAdd(RwV3d * out, const RwV3d * ina, const RwV3d * inb); +void RwV3dSub(RwV3d * out, const RwV3d * ina, const RwV3d * inb); +void RwV3dScale(RwV3d * out, const RwV3d * in, RwReal scalar); +void RwV3dIncrementScaled(RwV3d * out, const RwV3d * in, RwReal scalar); +void RwV3dNegate(RwV3d * out, const RwV3d * in); +RwReal RwV3dDotProduct(const RwV3d * ina, const RwV3d * inb); +void RwV3dCrossProduct(RwV3d * out, const RwV3d * ina, const RwV3d * inb); +RwV3d *RwV3dTransformPoints(RwV3d * pointsOut, const RwV3d * pointsIn, RwInt32 numPoints, const RwMatrix * matrix); +RwV3d *RwV3dTransformVectors(RwV3d * vectorsOut, const RwV3d * vectorsIn, RwInt32 numPoints, const RwMatrix * matrix); + + +/* + *********************************************** + * + * Render States + * + *********************************************** + */ + +// not librw because we don't support all of them (yet?) - mapping in wrapper functions +enum RwRenderState +{ + rwRENDERSTATENARENDERSTATE = 0, + rwRENDERSTATETEXTURERASTER, + rwRENDERSTATETEXTUREADDRESS, + rwRENDERSTATETEXTUREADDRESSU, + rwRENDERSTATETEXTUREADDRESSV, + rwRENDERSTATETEXTUREPERSPECTIVE, + rwRENDERSTATEZTESTENABLE, + rwRENDERSTATESHADEMODE, + rwRENDERSTATEZWRITEENABLE, + rwRENDERSTATETEXTUREFILTER, + rwRENDERSTATESRCBLEND, + rwRENDERSTATEDESTBLEND, + rwRENDERSTATEVERTEXALPHAENABLE, + rwRENDERSTATEBORDERCOLOR, + rwRENDERSTATEFOGENABLE, + rwRENDERSTATEFOGCOLOR, + rwRENDERSTATEFOGTYPE, + rwRENDERSTATEFOGDENSITY, + rwRENDERSTATEFOGTABLE, + rwRENDERSTATEALPHAPRIMITIVEBUFFER, + rwRENDERSTATECULLMODE, + rwRENDERSTATESTENCILENABLE, + rwRENDERSTATESTENCILFAIL, + rwRENDERSTATESTENCILZFAIL, + rwRENDERSTATESTENCILPASS, + rwRENDERSTATESTENCILFUNCTION, + rwRENDERSTATESTENCILFUNCTIONREF, + rwRENDERSTATESTENCILFUNCTIONMASK, + rwRENDERSTATESTENCILFUNCTIONWRITEMASK +}; + +// not supported - we only do gouraud +enum RwShadeMode +{ + rwSHADEMODENASHADEMODE = 0, + rwSHADEMODEFLAT, + rwSHADEMODEGOURAUD +}; + +enum RwBlendFunction +{ + rwBLENDNABLEND = 0, + rwBLENDZERO = rw::BLENDZERO, + rwBLENDONE = rw::BLENDONE, + rwBLENDSRCCOLOR = rw::BLENDSRCCOLOR, + rwBLENDINVSRCCOLOR = rw::BLENDINVSRCCOLOR, + rwBLENDSRCALPHA = rw::BLENDSRCALPHA, + rwBLENDINVSRCALPHA = rw::BLENDINVSRCALPHA, + rwBLENDDESTALPHA = rw::BLENDDESTALPHA, + rwBLENDINVDESTALPHA = rw::BLENDINVDESTALPHA, + rwBLENDDESTCOLOR = rw::BLENDDESTCOLOR, + rwBLENDINVDESTCOLOR = rw::BLENDINVDESTCOLOR, + rwBLENDSRCALPHASAT = rw::BLENDSRCALPHASAT +}; + +// unsupported - we only need linear +enum RwFogType +{ + rwFOGTYPENAFOGTYPE = 0, + rwFOGTYPELINEAR, + rwFOGTYPEEXPONENTIAL, + rwFOGTYPEEXPONENTIAL2 +}; + +enum RwTextureFilterMode +{ + rwFILTERNAFILTERMODE = 0, + rwFILTERNEAREST = rw::Texture::NEAREST, + rwFILTERLINEAR = rw::Texture::LINEAR, + rwFILTERMIPNEAREST = rw::Texture::MIPNEAREST, + rwFILTERMIPLINEAR = rw::Texture::MIPLINEAR, + rwFILTERLINEARMIPNEAREST = rw::Texture::LINEARMIPNEAREST, + rwFILTERLINEARMIPLINEAR = rw::Texture::LINEARMIPLINEAR +}; + +enum RwTextureAddressMode +{ + rwTEXTUREADDRESSNATEXTUREADDRESS = 0, + rwTEXTUREADDRESSWRAP = rw::Texture::WRAP, + rwTEXTUREADDRESSMIRROR = rw::Texture::MIRROR, + rwTEXTUREADDRESSCLAMP = rw::Texture::CLAMP, + rwTEXTUREADDRESSBORDER = rw::Texture::BORDER +}; + +enum RwCullMode +{ + rwCULLMODENACULLMODE = 0, + rwCULLMODECULLNONE = rw::CULLNONE, + rwCULLMODECULLBACK = rw::CULLBACK, + rwCULLMODECULLFRONT = rw::CULLFRONT +}; + +enum RwPrimitiveType +{ + rwPRIMTYPENAPRIMTYPE = rw::PRIMTYPENONE, + rwPRIMTYPELINELIST = rw::PRIMTYPELINELIST, + rwPRIMTYPEPOLYLINE = rw::PRIMTYPEPOLYLINE, + rwPRIMTYPETRILIST = rw::PRIMTYPETRILIST, + rwPRIMTYPETRISTRIP = rw::PRIMTYPETRISTRIP, + rwPRIMTYPETRIFAN = rw::PRIMTYPETRIFAN, + rwPRIMTYPEPOINTLIST = rw::PRIMTYPEPOINTLIST +}; + + +RwBool RwRenderStateGet(RwRenderState state, void *value); +RwBool RwRenderStateSet(RwRenderState state, void *value); + + +/* + *********************************************** + * + * Engine + * + *********************************************** + */ + +struct RwEngineOpenParams +{ + void *displayID; +}; + +typedef rw::SubSystemInfo RwSubSystemInfo; + +enum RwVideoModeFlag +{ + rwVIDEOMODEEXCLUSIVE = rw::VIDEOMODEEXCLUSIVE, +/* + rwVIDEOMODEINTERLACE = 0x2, + rwVIDEOMODEFFINTERLACE = 0x4, + rwVIDEOMODEFSAA0 = 0x8, + rwVIDEOMODEFSAA1 = 0x10 +*/ +}; + +typedef rw::VideoMode RwVideoMode; + +#if 0 +struct RwFileFunctions +{ + rwFnFexist rwfexist; /**< Pointer to fexist function */ + rwFnFopen rwfopen; /**< Pointer to fopen function */ + rwFnFclose rwfclose; /**< Pointer to fclose function */ + rwFnFread rwfread; /**< Pointer to fread function */ + rwFnFwrite rwfwrite; /**< Pointer to fwrite function */ + rwFnFgets rwfgets; /**< Pointer to fgets function */ + rwFnFputs rwfputs; /**< Pointer to puts function */ + rwFnFeof rwfeof; /**< Pointer to feof function */ + rwFnFseek rwfseek; /**< Pointer to fseek function */ + rwFnFflush rwfflush; /**< Pointer to fflush function */ + rwFnFtell rwftell; /**< Pointer to ftell function */ +}; +RwFileFunctions *RwOsGetFileInterface(void); +#endif + +RwBool RwEngineInit(RwMemoryFunctions *memFuncs, RwUInt32 initFlags, RwUInt32 resArenaSize); +RwInt32 RwEngineRegisterPlugin(RwInt32 size, RwUInt32 pluginID, RwPluginObjectConstructor initCB, RwPluginObjectDestructor termCB); +RwInt32 RwEngineGetPluginOffset(RwUInt32 pluginID); +RwBool RwEngineOpen(RwEngineOpenParams *initParams); +RwBool RwEngineStart(void); +RwBool RwEngineStop(void); +RwBool RwEngineClose(void); +RwBool RwEngineTerm(void); +RwInt32 RwEngineGetNumSubSystems(void); +RwSubSystemInfo *RwEngineGetSubSystemInfo(RwSubSystemInfo *subSystemInfo, RwInt32 subSystemIndex); +RwInt32 RwEngineGetCurrentSubSystem(void); +RwBool RwEngineSetSubSystem(RwInt32 subSystemIndex); +RwInt32 RwEngineGetNumVideoModes(void); +RwVideoMode *RwEngineGetVideoModeInfo(RwVideoMode *modeinfo, RwInt32 modeIndex); +RwInt32 RwEngineGetCurrentVideoMode(void); +RwBool RwEngineSetVideoMode(RwInt32 modeIndex); +RwInt32 RwEngineGetTextureMemorySize(void); +RwInt32 RwEngineGetMaxTextureSize(void); + + +/* + *********************************************** + * + * Binary stream + * + *********************************************** + */ + +RwBool RwStreamFindChunk(RwStream *stream, RwUInt32 type, RwUInt32 *lengthOut, RwUInt32 *versionOut); diff --git a/src/math/Matrix.cpp b/src/math/Matrix.cpp new file mode 100644 index 0000000..b11e8a1 --- /dev/null +++ b/src/math/Matrix.cpp @@ -0,0 +1,544 @@ +#include "common.h" + +CMatrix::CMatrix(void) +{ + m_attachment = nil; + m_hasRwMatrix = false; +} + +CMatrix::CMatrix(CMatrix const &m) +{ + m_attachment = nil; + m_hasRwMatrix = false; + *this = m; +} + +CMatrix::CMatrix(RwMatrix *matrix, bool owner) +{ + m_attachment = nil; + Attach(matrix, owner); +} + +CMatrix::~CMatrix(void) +{ + if (m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); +} + +void +CMatrix::Attach(RwMatrix *matrix, bool owner) +{ +#ifdef FIX_BUGS + if (m_attachment && m_hasRwMatrix) +#else + if (m_hasRwMatrix && m_attachment) +#endif + RwMatrixDestroy(m_attachment); + m_attachment = matrix; + m_hasRwMatrix = owner; + Update(); +} + +void +CMatrix::AttachRW(RwMatrix *matrix, bool owner) +{ + if (m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); + m_attachment = matrix; + m_hasRwMatrix = owner; + UpdateRW(); +} + +void +CMatrix::Detach(void) +{ + if (m_hasRwMatrix && m_attachment) + RwMatrixDestroy(m_attachment); + m_attachment = nil; +} + +void +CMatrix::Update(void) +{ + GetRight() = m_attachment->right; + GetForward() = m_attachment->up; + GetUp() = m_attachment->at; + GetPosition() = m_attachment->pos; +} + +void +CMatrix::UpdateRW(void) +{ + if (m_attachment) { + m_attachment->right = GetRight(); + m_attachment->up = GetForward(); + m_attachment->at = GetUp(); + m_attachment->pos = GetPosition(); + RwMatrixUpdate(m_attachment); + } +} + +void +CMatrix::operator=(CMatrix const &rhs) +{ + memcpy(this, &rhs, sizeof(f)); + if (m_attachment) + UpdateRW(); +} + +void +CMatrix::CopyOnlyMatrix(const CMatrix &other) +{ + memcpy(this, &other, sizeof(f)); +} + +CMatrix & +CMatrix::operator+=(CMatrix const &rhs) +{ + GetRight() += rhs.GetRight(); + GetForward() += rhs.GetForward(); + GetUp() += rhs.GetUp(); + GetPosition() += rhs.GetPosition(); + return *this; +} + +void +CMatrix::SetUnity(void) +{ + rx = 1.0f; + ry = 0.0f; + rz = 0.0f; + fx = 0.0f; + fy = 1.0f; + fz = 0.0f; + ux = 0.0f; + uy = 0.0f; + uz = 1.0f; + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::ResetOrientation(void) +{ + rx = 1.0f; + ry = 0.0f; + rz = 0.0f; + fx = 0.0f; + fy = 1.0f; + fz = 0.0f; + ux = 0.0f; + uy = 0.0f; + uz = 1.0f; +} + +void +CMatrix::SetScale(float s) +{ + rx = s; + ry = 0.0f; + rz = 0.0f; + + fx = 0.0f; + fy = s; + fz = 0.0f; + + ux = 0.0f; + uy = 0.0f; + uz = s; + + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::SetTranslate(float x, float y, float z) +{ + rx = 1.0f; + ry = 0.0f; + rz = 0.0f; + + fx = 0.0f; + fy = 1.0f; + fz = 0.0f; + + ux = 0.0f; + uy = 0.0f; + uz = 1.0f; + + px = x; + py = y; + pz = z; +} + +void +CMatrix::SetRotateXOnly(float angle) +{ + float c = Cos(angle); + float s = Sin(angle); + + rx = 1.0f; + ry = 0.0f; + rz = 0.0f; + + fx = 0.0f; + fy = c; + fz = s; + + ux = 0.0f; + uy = -s; + uz = c; +} + +void +CMatrix::SetRotateYOnly(float angle) +{ + float c = Cos(angle); + float s = Sin(angle); + + rx = c; + ry = 0.0f; + rz = -s; + + fx = 0.0f; + fy = 1.0f; + fz = 0.0f; + + ux = s; + uy = 0.0f; + uz = c; +} + +void +CMatrix::SetRotateZOnly(float angle) +{ + float c = Cos(angle); + float s = Sin(angle); + + rx = c; + ry = s; + rz = 0.0f; + + fx = -s; + fy = c; + fz = 0.0f; + + ux = 0.0f; + uy = 0.0f; + uz = 1.0f; +} + +void +CMatrix::SetRotateX(float angle) +{ + SetRotateXOnly(angle); + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + + +void +CMatrix::SetRotateY(float angle) +{ + SetRotateYOnly(angle); + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::SetRotateZ(float angle) +{ + SetRotateZOnly(angle); + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::SetRotate(float xAngle, float yAngle, float zAngle) +{ + float cX = Cos(xAngle); + float sX = Sin(xAngle); + float cY = Cos(yAngle); + float sY = Sin(yAngle); + float cZ = Cos(zAngle); + float sZ = Sin(zAngle); + + rx = cZ * cY - (sZ * sX) * sY; + ry = (cZ * sX) * sY + sZ * cY; + rz = -cX * sY; + + fx = -sZ * cX; + fy = cZ * cX; + fz = sX; + + ux = (sZ * sX) * cY + cZ * sY; + uy = sZ * sY - (cZ * sX) * cY; + uz = cX * cY; + + px = 0.0f; + py = 0.0f; + pz = 0.0f; +} + +void +CMatrix::RotateX(float x) +{ + float c = Cos(x); + float s = Sin(x); + + float ry = this->ry; + float rz = this->rz; + float uy = this->fy; + float uz = this->fz; + float ay = this->uy; + float az = this->uz; + float py = this->py; + float pz = this->pz; + + this->ry = c * ry - s * rz; + this->rz = c * rz + s * ry; + this->fy = c * uy - s * uz; + this->fz = c * uz + s * uy; + this->uy = c * ay - s * az; + this->uz = c * az + s * ay; + this->py = c * py - s * pz; + this->pz = c * pz + s * py; +} + +void +CMatrix::RotateY(float y) +{ + float c = Cos(y); + float s = Sin(y); + + float rx = this->rx; + float rz = this->rz; + float ux = this->fx; + float uz = this->fz; + float ax = this->ux; + float az = this->uz; + float px = this->px; + float pz = this->pz; + + this->rx = c * rx + s * rz; + this->rz = c * rz - s * rx; + this->fx = c * ux + s * uz; + this->fz = c * uz - s * ux; + this->ux = c * ax + s * az; + this->uz = c * az - s * ax; + this->px = c * px + s * pz; + this->pz = c * pz - s * px; +} + +void +CMatrix::RotateZ(float z) +{ + float c = Cos(z); + float s = Sin(z); + + float ry = this->ry; + float rx = this->rx; + float uy = this->fy; + float ux = this->fx; + float ay = this->uy; + float ax = this->ux; + float py = this->py; + float px = this->px; + + this->rx = c * rx - s * ry; + this->ry = c * ry + s * rx; + this->fx = c * ux - s * uy; + this->fy = c * uy + s * ux; + this->ux = c * ax - s * ay; + this->uy = c * ay + s * ax; + this->px = c * px - s * py; + this->py = c * py + s * px; + +} + +void +CMatrix::Rotate(float x, float y, float z) +{ + float cX = Cos(x); + float sX = Sin(x); + float cY = Cos(y); + float sY = Sin(y); + float cZ = Cos(z); + float sZ = Sin(z); + + float rx = this->rx; + float ry = this->ry; + float rz = this->rz; + float ux = this->fx; + float uy = this->fy; + float uz = this->fz; + float ax = this->ux; + float ay = this->uy; + float az = this->uz; + float px = this->px; + float py = this->py; + float pz = this->pz; + + float x1 = cZ * cY - (sZ * sX) * sY; + float x2 = (cZ * sX) * sY + sZ * cY; + float x3 = -cX * sY; + float y1 = -sZ * cX; + float y2 = cZ * cX; + float y3 = sX; + float z1 = (sZ * sX) * cY + cZ * sY; + float z2 = sZ * sY - (cZ * sX) * cY; + float z3 = cX * cY; + + this->rx = x1 * rx + y1 * ry + z1 * rz; + this->ry = x2 * rx + y2 * ry + z2 * rz; + this->rz = x3 * rx + y3 * ry + z3 * rz; + this->fx = x1 * ux + y1 * uy + z1 * uz; + this->fy = x2 * ux + y2 * uy + z2 * uz; + this->fz = x3 * ux + y3 * uy + z3 * uz; + this->ux = x1 * ax + y1 * ay + z1 * az; + this->uy = x2 * ax + y2 * ay + z2 * az; + this->uz = x3 * ax + y3 * ay + z3 * az; + this->px = x1 * px + y1 * py + z1 * pz; + this->py = x2 * px + y2 * py + z2 * pz; + this->pz = x3 * px + y3 * py + z3 * pz; +} + +CMatrix & +CMatrix::operator*=(CMatrix const &rhs) +{ + // TODO: VU0 code + *this = *this * rhs; + return *this; +} + +void +CMatrix::Reorthogonalise(void) +{ + CVector &r = GetRight(); + CVector &f = GetForward(); + CVector &u = GetUp(); + u = CrossProduct(r, f); + u.Normalise(); + r = CrossProduct(f, u); + r.Normalise(); + f = CrossProduct(u, r); +} + +CMatrix +operator*(const CMatrix &m1, const CMatrix &m2) +{ + // TODO: VU0 code + CMatrix out; + out.rx = m1.rx * m2.rx + m1.fx * m2.ry + m1.ux * m2.rz; + out.ry = m1.ry * m2.rx + m1.fy * m2.ry + m1.uy * m2.rz; + out.rz = m1.rz * m2.rx + m1.fz * m2.ry + m1.uz * m2.rz; + out.fx = m1.rx * m2.fx + m1.fx * m2.fy + m1.ux * m2.fz; + out.fy = m1.ry * m2.fx + m1.fy * m2.fy + m1.uy * m2.fz; + out.fz = m1.rz * m2.fx + m1.fz * m2.fy + m1.uz * m2.fz; + out.ux = m1.rx * m2.ux + m1.fx * m2.uy + m1.ux * m2.uz; + out.uy = m1.ry * m2.ux + m1.fy * m2.uy + m1.uy * m2.uz; + out.uz = m1.rz * m2.ux + m1.fz * m2.uy + m1.uz * m2.uz; + out.px = m1.rx * m2.px + m1.fx * m2.py + m1.ux * m2.pz + m1.px; + out.py = m1.ry * m2.px + m1.fy * m2.py + m1.uy * m2.pz + m1.py; + out.pz = m1.rz * m2.px + m1.fz * m2.py + m1.uz * m2.pz + m1.pz; + return out; +} + +CMatrix & +Invert(const CMatrix &src, CMatrix &dst) +{ + // TODO: VU0 code + // GTA handles this as a raw 4x4 orthonormal matrix + // and trashes the RW flags, let's not do that + dst.f[3][0] = dst.f[3][1] = dst.f[3][2] = 0.0f; +#ifndef FIX_BUGS + dst.f[3][3] = src.f[3][3]; +#endif + + dst.f[0][0] = src.f[0][0]; + dst.f[0][1] = src.f[1][0]; + dst.f[0][2] = src.f[2][0]; +#ifndef FIX_BUGS + dst.f[0][3] = src.f[3][0]; +#endif + dst.f[1][0] = src.f[0][1]; + dst.f[1][1] = src.f[1][1]; + dst.f[1][2] = src.f[2][1]; +#ifndef FIX_BUGS + dst.f[1][3] = src.f[3][1]; +#endif + dst.f[2][0] = src.f[0][2]; + dst.f[2][1] = src.f[1][2]; + dst.f[2][2] = src.f[2][2]; +#ifndef FIX_BUGS + dst.f[2][3] = src.f[3][2]; +#endif + + dst.f[3][0] += dst.f[0][0] * src.f[3][0]; + dst.f[3][1] += dst.f[0][1] * src.f[3][0]; + dst.f[3][2] += dst.f[0][2] * src.f[3][0]; +#ifndef FIX_BUGS + dst.f[3][3] += dst.f[0][3] * src.f[3][0]; +#endif + + dst.f[3][0] += dst.f[1][0] * src.f[3][1]; + dst.f[3][1] += dst.f[1][1] * src.f[3][1]; + dst.f[3][2] += dst.f[1][2] * src.f[3][1]; +#ifndef FIX_BUGS + dst.f[3][3] += dst.f[1][3] * src.f[3][1]; +#endif + + dst.f[3][0] += dst.f[2][0] * src.f[3][2]; + dst.f[3][1] += dst.f[2][1] * src.f[3][2]; + dst.f[3][2] += dst.f[2][2] * src.f[3][2]; +#ifndef FIX_BUGS + dst.f[3][3] += dst.f[2][3] * src.f[3][2]; +#endif + + dst.f[3][0] = -dst.f[3][0]; + dst.f[3][1] = -dst.f[3][1]; + dst.f[3][2] = -dst.f[3][2]; +#ifndef FIX_BUGS + dst.f[3][3] = src.f[3][3] - dst.f[3][3]; +#endif + + return dst; +} + +CMatrix +Invert(const CMatrix &matrix) +{ + CMatrix inv; + return Invert(matrix, inv); +} + +void +CCompressedMatrixNotAligned::CompressFromFullMatrix(CMatrix &other) +{ + m_rightX = 127.0f * other.GetRight().x; + m_rightY = 127.0f * other.GetRight().y; + m_rightZ = 127.0f * other.GetRight().z; + m_upX = 127.0f * other.GetForward().x; + m_upY = 127.0f * other.GetForward().y; + m_upZ = 127.0f * other.GetForward().z; + m_vecPos = other.GetPosition(); +} + +void +CCompressedMatrixNotAligned::DecompressIntoFullMatrix(CMatrix &other) +{ + other.GetRight().x = m_rightX / 127.0f; + other.GetRight().y = m_rightY / 127.0f; + other.GetRight().z = m_rightZ / 127.0f; + other.GetForward().x = m_upX / 127.0f; + other.GetForward().y = m_upY / 127.0f; + other.GetForward().z = m_upZ / 127.0f; + other.GetUp() = CrossProduct(other.GetRight(), other.GetForward()); + other.GetPosition() = m_vecPos; + other.Reorthogonalise(); +} \ No newline at end of file diff --git a/src/math/Matrix.h b/src/math/Matrix.h new file mode 100644 index 0000000..6404b50 --- /dev/null +++ b/src/math/Matrix.h @@ -0,0 +1,131 @@ +#pragma once + +class CMatrix +{ +public: + union + { + float f[4][4]; + struct + { + float rx, ry, rz, rw; + float fx, fy, fz, fw; + float ux, uy, uz, uw; + float px, py, pz, pw; + }; + }; + + RwMatrix *m_attachment; + bool m_hasRwMatrix; // are we the owner? + + CMatrix(void); + CMatrix(CMatrix const &m); + CMatrix(RwMatrix *matrix, bool owner = false); + CMatrix(float scale){ + m_attachment = nil; + m_hasRwMatrix = false; + SetScale(scale); + } + ~CMatrix(void); + void Attach(RwMatrix *matrix, bool owner = false); + void AttachRW(RwMatrix *matrix, bool owner = false); + void Detach(void); + void Update(void); + void UpdateRW(void); + void operator=(CMatrix const &rhs); + CMatrix &operator+=(CMatrix const &rhs); + CMatrix &operator*=(CMatrix const &rhs); + + CVector &GetPosition(void) { return *(CVector*)&px; } + CVector &GetRight(void) { return *(CVector*)℞ } + CVector &GetForward(void) { return *(CVector*)&fx; } + CVector &GetUp(void) { return *(CVector*)&ux; } + + const CVector &GetPosition(void) const { return *(CVector*)&px; } + const CVector &GetRight(void) const { return *(CVector*)℞ } + const CVector &GetForward(void) const { return *(CVector*)&fx; } + const CVector &GetUp(void) const { return *(CVector*)&ux; } + + + void SetTranslate(float x, float y, float z); + void SetTranslate(const CVector &trans){ SetTranslate(trans.x, trans.y, trans.z); } + void Translate(float x, float y, float z){ + px += x; + py += y; + pz += z; + } + void Translate(const CVector &trans){ Translate(trans.x, trans.y, trans.z); } + + void SetScale(float s); + void Scale(float scale) + { + for (int i = 0; i < 3; i++) +#ifdef FIX_BUGS // BUGFIX from VC + for (int j = 0; j < 3; j++) +#else + for (int j = 0; j < 4; j++) +#endif + f[i][j] *= scale; + } + + + void SetRotateXOnly(float angle); + void SetRotateYOnly(float angle); + void SetRotateZOnly(float angle); + void SetRotateX(float angle); + void SetRotateY(float angle); + void SetRotateZ(float angle); + void SetRotate(float xAngle, float yAngle, float zAngle); + void Rotate(float x, float y, float z); + void RotateX(float x); + void RotateY(float y); + void RotateZ(float z); + + void Reorthogonalise(void); + void CopyOnlyMatrix(const CMatrix &other); + void SetUnity(void); + void ResetOrientation(void); + void SetTranslateOnly(float x, float y, float z) { + px = x; + py = y; + pz = z; + } + void SetTranslateOnly(const CVector& pos) { + SetTranslateOnly(pos.x, pos.y, pos.z); + } + void CheckIntegrity(){} +}; + + +CMatrix &Invert(const CMatrix &src, CMatrix &dst); +CMatrix Invert(const CMatrix &matrix); +CMatrix operator*(const CMatrix &m1, const CMatrix &m2); +inline CVector MultiplyInverse(const CMatrix &mat, const CVector &vec) +{ + CVector v(vec.x - mat.px, vec.y - mat.py, vec.z - mat.pz); + return CVector( + mat.rx * v.x + mat.ry * v.y + mat.rz * v.z, + mat.fx * v.x + mat.fy * v.y + mat.fz * v.z, + mat.ux * v.x + mat.uy * v.y + mat.uz * v.z); +} + + + +class CCompressedMatrixNotAligned +{ + CVector m_vecPos; + int8 m_rightX; + int8 m_rightY; + int8 m_rightZ; + int8 m_upX; + int8 m_upY; + int8 m_upZ; +public: + void CompressFromFullMatrix(CMatrix &other); + void DecompressIntoFullMatrix(CMatrix &other); +}; + +class CCompressedMatrix : public CCompressedMatrixNotAligned +{ + int _alignment; // no clue what would this align to +}; \ No newline at end of file diff --git a/src/math/Quaternion.cpp b/src/math/Quaternion.cpp new file mode 100644 index 0000000..b0e782e --- /dev/null +++ b/src/math/Quaternion.cpp @@ -0,0 +1,177 @@ +#include "common.h" +#include "Quaternion.h" + +void +CQuaternion::Normalise(void) +{ + float sq = MagnitudeSqr(); + if (sq == 0.0f) + w = 1.0f; + else { + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + z *= invsqrt; + w *= invsqrt; + } +} + +void +CQuaternion::Slerp(const CQuaternion &q1, const CQuaternion &q2, float theta, float invSin, float t) +{ + if (theta == 0.0f) + *this = q2; + else { + float w1, w2; + if (theta > PI / 2) { + theta = PI - theta; + w1 = Sin((1.0f - t) * theta) * invSin; + w2 = -Sin(t * theta) * invSin; + } else { + w1 = Sin((1.0f - t) * theta) * invSin; + w2 = Sin(t * theta) * invSin; + } + // TODO: VU0 code + *this = w1 * q1 + w2 * q2; + } +} + +void +CQuaternion::Multiply(const CQuaternion &q1, const CQuaternion &q2) +{ + x = (q2.z * q1.y) - (q1.z * q2.y) + (q1.x * q2.w) + (q2.x * q1.w); + y = (q2.x * q1.z) - (q1.x * q2.z) + (q1.y * q2.w) + (q2.y * q1.w); + z = (q2.y * q1.x) - (q1.y * q2.x) + (q1.z * q2.w) + (q2.z * q1.w); + w = (q2.w * q1.w) - (q2.x * q1.x) - (q2.y * q1.y) - (q2.z * q1.z); +} + +void +CQuaternion::Get(RwV3d *axis, float *angle) +{ + *angle = Acos(w); + float s = Sin(*angle); + + axis->x = x * (1.0f / s); + axis->y = y * (1.0f / s); + axis->z = z * (1.0f / s); +} + +void +CQuaternion::Set(RwV3d *axis, float angle) +{ + float halfCos = Cos(angle * 0.5f); + float halfSin = Sin(angle * 0.5f); + x = axis->x * halfSin; + y = axis->y * halfSin; + z = axis->z * halfSin; + w = halfCos; +} + +void +CQuaternion::Get(RwMatrix *matrix) +{ + float x2 = x + x; + float y2 = y + y; + float z2 = z + z; + + float x_2x = x * x2; + float x_2y = x * y2; + float x_2z = x * z2; + float y_2y = y * y2; + float y_2z = y * z2; + float z_2z = z * z2; + float w_2x = w * x2; + float w_2y = w * y2; + float w_2z = w * z2; + + matrix->right.x = 1.0f - (y_2y + z_2z); + matrix->up.x = x_2y - w_2z; + matrix->at.x = x_2z + w_2y; + matrix->right.y = x_2y + w_2z; + matrix->up.y = 1.0f - (x_2x + z_2z); + matrix->at.y = y_2z - w_2x; + matrix->right.z = x_2z - w_2y; + matrix->up.z = y_2z + w_2x; + matrix->at.z = 1.0f - (x_2x + y_2y); +} + +void +CQuaternion::Set(const RwMatrix &matrix) +{ + float f, s, m; + + f = matrix.up.y + matrix.right.x + matrix.at.z; + if (f >= 0.0f) { + s = Sqrt(f + 1.0f); + w = 0.5f * s; + m = 0.5f / s; + x = (matrix.up.z - matrix.at.y) * m; + y = (matrix.at.x - matrix.right.z) * m; + z = (matrix.right.y - matrix.up.x) * m; + return; + } + + f = matrix.right.x - matrix.up.y - matrix.at.z; + if (f >= 0.0f) { + s = Sqrt(f + 1.0f); + x = 0.5f * s; + m = 0.5f / s; + y = (matrix.up.x + matrix.right.y) * m; + z = (matrix.at.x + matrix.right.z) * m; + w = (matrix.up.z - matrix.at.y) * m; + return; + } + + f = matrix.up.y - matrix.right.x - matrix.at.z; + if (f >= 0.0f) { + s = Sqrt(f + 1.0f); + y = 0.5f * s; + m = 0.5f / s; + w = (matrix.at.x - matrix.right.z) * m; + x = (matrix.up.x - matrix.right.y) * m; + z = (matrix.at.y + matrix.up.z) * m; + return; + } + + f = matrix.at.z - (matrix.up.y + matrix.right.x); + s = Sqrt(f + 1.0f); + z = 0.5f * s; + m = 0.5f / s; + w = (matrix.right.y - matrix.up.x) * m; + x = (matrix.at.x + matrix.right.z) * m; + y = (matrix.at.y + matrix.up.z) * m; +} + +void +CQuaternion::Get(float *f1, float *f2, float *f3) +{ + RwMatrix matrix; + + Get(&matrix); + *f3 = Atan2(matrix.right.y, matrix.up.y); + if (*f3 < 0.0f) + *f3 += TWOPI; + float s = Sin(*f3); + float c = Cos(*f3); + *f1 = Atan2(-matrix.at.y, s * matrix.right.y + c * matrix.up.y); + if (*f1 < 0.0f) + *f1 += TWOPI; + *f2 = Atan2(-(matrix.right.z * c - matrix.up.z * s), matrix.right.x * c - matrix.up.x * s); + if (*f2 < 0.0f) + *f2 += TWOPI; +} + +void +CQuaternion::Set(float f1, float f2, float f3) +{ + float c1 = Cos(f1 * 0.5f); + float c2 = Cos(f2 * 0.5f); + float c3 = Cos(f3 * 0.5f); + float s1 = Sin(f1 * 0.5f); + float s2 = Sin(f2 * 0.5f); + float s3 = Sin(f3 * 0.5f); + x = ((c2 * c1) * s3) - ((s2 * s1) * c3); + y = ((s1 * c2) * c3) + ((s2 * c1) * s3); + z = ((s2 * c1) * c3) - ((s1 * c2) * s3); + w = ((c2 * c1) * c3) + ((s2 * s1) * s3); +} \ No newline at end of file diff --git a/src/math/Quaternion.h b/src/math/Quaternion.h new file mode 100644 index 0000000..47c94f7 --- /dev/null +++ b/src/math/Quaternion.h @@ -0,0 +1,95 @@ +#pragma once + +// TODO: actually implement this +class CQuaternion +{ +public: + float x, y, z, w; + CQuaternion(void) {} + CQuaternion(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} + + float Magnitude(void) const { return Sqrt(x*x + y*y + z*z + w*w); } + float MagnitudeSqr(void) const { return x*x + y*y + z*z + w*w; } + void Normalise(void); + void Multiply(const CQuaternion &q1, const CQuaternion &q2); + void Invert(void){ // Conjugate would have been a better name + x = -x; + y = -y; + z = -z; + } + + const CQuaternion &operator+=(CQuaternion const &right) { + x += right.x; + y += right.y; + z += right.z; + w += right.w; + return *this; + } + + const CQuaternion &operator-=(CQuaternion const &right) { + x -= right.x; + y -= right.y; + z -= right.z; + w -= right.w; + return *this; + } + + const CQuaternion &operator*=(float right) { + x *= right; + y *= right; + z *= right; + w *= right; + return *this; + } + + const CQuaternion &operator/=(float right) { + x /= right; + y /= right; + z /= right; + w /= right; + return *this; + } + + CQuaternion operator-() const { + return CQuaternion(-x, -y, -z, -w); + } + + void Slerp(const CQuaternion &q1, const CQuaternion &q2, float theta, float invSin, float t); + void Get(RwV3d *axis, float *angle); + void Set(RwV3d *axis, float angle); + void Get(RwMatrix *matrix); + void Set(const RwMatrix &matrix); + void Set(float f1, float f2, float f3); + void Get(float *f1, float *f2, float *f3); +}; + +inline float +DotProduct(const CQuaternion &q1, const CQuaternion &q2) +{ + return q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; +} + +inline CQuaternion operator+(const CQuaternion &left, const CQuaternion &right) +{ + return CQuaternion(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w); +} + +inline CQuaternion operator-(const CQuaternion &left, const CQuaternion &right) +{ + return CQuaternion(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w); +} + +inline CQuaternion operator*(const CQuaternion &left, float right) +{ + return CQuaternion(left.x * right, left.y * right, left.z * right, left.w * right); +} + +inline CQuaternion operator*(float left, const CQuaternion &right) +{ + return CQuaternion(left * right.x, left * right.y, left * right.z, left * right.w); +} + +inline CQuaternion operator/(const CQuaternion &left, float right) +{ + return CQuaternion(left.x / right, left.y / right, left.z / right, left.w / right); +} diff --git a/src/math/Rect.cpp b/src/math/Rect.cpp new file mode 100644 index 0000000..de6320a --- /dev/null +++ b/src/math/Rect.cpp @@ -0,0 +1,17 @@ +#include "common.h" + +CRect::CRect(void) +{ + left = 1000000.0f; + top = 1000000.0f; + right = -1000000.0f; + bottom = -1000000.0f; +} + +CRect::CRect(float l, float t, float r, float b) +{ + left = l; + top = t; + right = r; + bottom = b; +} \ No newline at end of file diff --git a/src/math/Rect.h b/src/math/Rect.h new file mode 100644 index 0000000..e9b2589 --- /dev/null +++ b/src/math/Rect.h @@ -0,0 +1,71 @@ +#pragma once + +class CRect +{ +public: + float left; // x min + float bottom; // y max + float right; // x max + float top; // y min + + CRect(void); + CRect(float l, float t, float r, float b); + void ContainPoint(CVector const &v){ + if(v.x < left) left = v.x; + if(v.x > right) right = v.x; + if(v.y < top) top = v.y; + if(v.y > bottom) bottom = v.y; + } + void ContainRect(const CRect &r){ + if(r.left < left) left = r.left; + if(r.right > right) right = r.right; + if(r.top < top) top = r.top; + if(r.bottom > bottom) bottom = r.bottom; + } + + bool IsPointInside(const CVector2D &p){ + return p.x >= left && + p.x <= right && + p.y >= top && + p.y <= bottom; + } + bool IsPointInside(const CVector2D &p, float extraRadius){ + return p.x >= left-extraRadius && + p.x <= right+extraRadius && + p.y >= top-extraRadius && + p.y <= bottom+extraRadius; + } + + void Translate(float x, float y){ + left += x; + right += x; + bottom += y; + top += y; + } + + void Grow(float r) { + left -= r; + right += r; + top -= r; + bottom += r; + } + + void Grow(float l, float r) + { + left -= l; + top -= l; + right += r; + bottom += r; + } + + void Grow(float l, float r, float t, float b) + { + left -= l; + top -= t; + right += r; + bottom += b; + } + + float GetWidth(void) { return right - left; } + float GetHeight(void) { return bottom - top; } +}; diff --git a/src/math/Vector.cpp b/src/math/Vector.cpp new file mode 100644 index 0000000..ee76e55 --- /dev/null +++ b/src/math/Vector.cpp @@ -0,0 +1,46 @@ +#include "common.h" + +void +CVector::Normalise(void) +{ + float sq = MagnitudeSqr(); + if (sq > 0.0f) { + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + z *= invsqrt; + } else + x = 1.0f; +} + +CVector +CrossProduct(const CVector &v1, const CVector &v2) +{ + return CVector(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); +} + +CVector +Multiply3x3(const CMatrix &mat, const CVector &vec) +{ + // TODO: VU0 code + return CVector(mat.rx * vec.x + mat.fx * vec.y + mat.ux * vec.z, + mat.ry * vec.x + mat.fy * vec.y + mat.uy * vec.z, + mat.rz * vec.x + mat.fz * vec.y + mat.uz * vec.z); +} + +CVector +Multiply3x3(const CVector &vec, const CMatrix &mat) +{ + return CVector(mat.rx * vec.x + mat.ry * vec.y + mat.rz * vec.z, + mat.fx * vec.x + mat.fy * vec.y + mat.fz * vec.z, + mat.ux * vec.x + mat.uy * vec.y + mat.uz * vec.z); +} + +CVector +operator*(const CMatrix &mat, const CVector &vec) +{ + // TODO: VU0 code + return CVector(mat.rx * vec.x + mat.fx * vec.y + mat.ux * vec.z + mat.px, + mat.ry * vec.x + mat.fy * vec.y + mat.uy * vec.z + mat.py, + mat.rz * vec.x + mat.fz * vec.y + mat.uz * vec.z + mat.pz); +} diff --git a/src/math/Vector.h b/src/math/Vector.h new file mode 100644 index 0000000..776bfcf --- /dev/null +++ b/src/math/Vector.h @@ -0,0 +1,129 @@ +#pragma once + +class CVector : public RwV3d +{ +public: + CVector(void) {} + CVector(float x, float y, float z) + { + this->x = x; + this->y = y; + this->z = z; + } + + CVector(const RwV3d &v) + { + x = v.x; + y = v.y; + z = v.z; + } + // (0,1,0) means no rotation. So get right vector and its atan + float Heading(void) const { return Atan2(-x, y); } + float Magnitude(void) const { return Sqrt(x*x + y*y + z*z); } + float MagnitudeSqr(void) const { return x*x + y*y + z*z; } + float Magnitude2D(void) const { return Sqrt(x*x + y*y); } + float MagnitudeSqr2D(void) const { return x*x + y*y; } + void Normalise(void); + + void Normalise2D(void) { + float sq = MagnitudeSqr2D(); + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + } + + const CVector &operator+=(CVector const &right) { + x += right.x; + y += right.y; + z += right.z; + return *this; + } + + const CVector &operator-=(CVector const &right) { + x -= right.x; + y -= right.y; + z -= right.z; + return *this; + } + + const CVector &operator*=(float right) { + x *= right; + y *= right; + z *= right; + return *this; + } + + const CVector &operator/=(float right) { + x /= right; + y /= right; + z /= right; + return *this; + } + + CVector operator-() const { + return CVector(-x, -y, -z); + } + + const bool operator==(CVector const &right) { + return x == right.x && y == right.y && z == right.z; + } + + const bool operator!=(CVector const &right) { + return x != right.x || y != right.y || z != right.z; + } + + bool IsZero(void) const { return x == 0.0f && y == 0.0f && z == 0.0f; } +}; + +inline CVector operator+(const CVector &left, const CVector &right) +{ + return CVector(left.x + right.x, left.y + right.y, left.z + right.z); +} + +inline CVector operator-(const CVector &left, const CVector &right) +{ + return CVector(left.x - right.x, left.y - right.y, left.z - right.z); +} + +inline CVector operator*(const CVector &left, float right) +{ + return CVector(left.x * right, left.y * right, left.z * right); +} + +inline CVector operator*(float left, const CVector &right) +{ + return CVector(left * right.x, left * right.y, left * right.z); +} + +inline CVector operator/(const CVector &left, float right) +{ + return CVector(left.x / right, left.y / right, left.z / right); +} + +inline float +DotProduct(const CVector &v1, const CVector &v2) +{ + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +} + +CVector CrossProduct(const CVector &v1, const CVector &v2); + +inline float +Distance(const CVector &v1, const CVector &v2) +{ + return (v2 - v1).Magnitude(); +} + +inline float +Distance2D(const CVector &v1, const CVector &v2) +{ + float x = v2.x - v1.x; + float y = v2.y - v1.y; + return Sqrt(x*x + y*y); +} + +class CMatrix; + +CVector Multiply3x3(const CMatrix &mat, const CVector &vec); +CVector Multiply3x3(const CVector &vec, const CMatrix &mat); +CVector operator*(const CMatrix &mat, const CVector &vec); \ No newline at end of file diff --git a/src/math/Vector2D.h b/src/math/Vector2D.h new file mode 100644 index 0000000..0235dbe --- /dev/null +++ b/src/math/Vector2D.h @@ -0,0 +1,109 @@ +#pragma once + +class CVector2D +{ +public: + float x, y; + CVector2D(void) {} + CVector2D(float x, float y) : x(x), y(y) {} + CVector2D(const CVector &v) : x(v.x), y(v.y) {} + float Heading(void) const { return Atan2(-x, y); } + float Magnitude(void) const { return Sqrt(x*x + y*y); } + float MagnitudeSqr(void) const { return x*x + y*y; } + + void Normalise(void) { + float sq = MagnitudeSqr(); + // assert(sq != 0.0f); // just be safe here + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + } + + void NormaliseSafe(void) { + float sq = MagnitudeSqr(); + if(sq > 0.0f){ + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + }else + x = 1.0f; + } + + const CVector2D &operator+=(CVector2D const &right) { + x += right.x; + y += right.y; + return *this; + } + + const CVector2D &operator-=(CVector2D const &right) { + x -= right.x; + y -= right.y; + return *this; + } + + const CVector2D &operator*=(float right) { + x *= right; + y *= right; + return *this; + } + + const CVector2D &operator/=(float right) { + x /= right; + y /= right; + return *this; + } + CVector2D operator-(const CVector2D &rhs) const { + return CVector2D(x-rhs.x, y-rhs.y); + } + CVector2D operator+(const CVector2D &rhs) const { + return CVector2D(x+rhs.x, y+rhs.y); + } + CVector2D operator/(float t) const { + return CVector2D(x/t, y/t); + } +}; + +inline float +DotProduct2D(const CVector2D &v1, const CVector2D &v2) +{ + return v1.x*v2.x + v1.y*v2.y; +} + +inline float +CrossProduct2D(const CVector2D &v1, const CVector2D &v2) +{ + return v1.x*v2.y - v1.y*v2.x; +} + +inline float +Distance2D(const CVector2D &v, float x, float y) +{ + return Sqrt((v.x-x)*(v.x-x) + (v.y-y)*(v.y-y)); +} + +inline float +DistanceSqr2D(const CVector2D &v, float x, float y) +{ + return (v.x-x)*(v.x-x) + (v.y-y)*(v.y-y); +} + +inline void +NormalizeXY(float &x, float &y) +{ + float l = Sqrt(x*x + y*y); + if(l != 0.0f){ + x /= l; + y /= l; + }else + x = 1.0f; +} + +inline CVector2D operator*(const CVector2D &left, float right) +{ + return CVector2D(left.x * right, left.y * right); +} + +inline CVector2D operator*(float left, const CVector2D &right) +{ + return CVector2D(left * right.x, left * right.y); +} diff --git a/src/math/VuVector.h b/src/math/VuVector.h new file mode 100644 index 0000000..4158409 --- /dev/null +++ b/src/math/VuVector.h @@ -0,0 +1,32 @@ +#pragma once + +class TYPEALIGN(16) CVuVector : public CVector +{ +public: + float w; + CVuVector(void) {} + CVuVector(float x, float y, float z) : CVector(x, y, z) {} + CVuVector(float x, float y, float z, float w) : CVector(x, y, z), w(w) {} + CVuVector(const CVector &v) : CVector(v.x, v.y, v.z) {} + CVuVector(const RwV3d &v) : CVector(v) {} +/* + void Normalise(void) { + float sq = MagnitudeSqr(); + // TODO: VU0 code + if(sq > 0.0f){ + float invsqrt = RecipSqrt(sq); + x *= invsqrt; + y *= invsqrt; + z *= invsqrt; + }else + x = 1.0f; + } +*/ + + // TODO: operator- +}; + +void TransformPoint(CVuVector &out, const CMatrix &mat, const CVuVector &in); +void TransformPoint(CVuVector &out, const CMatrix &mat, const RwV3d &in); +void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const RwV3d *in, int stride); +void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const CVuVector *in); diff --git a/src/math/math.cpp b/src/math/math.cpp new file mode 100644 index 0000000..8cb56da --- /dev/null +++ b/src/math/math.cpp @@ -0,0 +1,118 @@ +#include "common.h" + +#include "VuVector.h" + +// TODO: move more stuff into here + + +void TransformPoint(CVuVector &out, const CMatrix &mat, const CVuVector &in) +{ +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + lqc2 vf01,0x0(%2)\n\ + lqc2 vf02,0x0(%1)\n\ + lqc2 vf03,0x10(%1)\n\ + lqc2 vf04,0x20(%1)\n\ + lqc2 vf05,0x30(%1)\n\ + vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + sqc2 vf06,0x0(%0)\n\ + ": : "r" (&out) , "r" (&mat) ,"r" (&in): "memory"); +#else + out = mat * in; +#endif +} + +void TransformPoint(CVuVector &out, const CMatrix &mat, const RwV3d &in) +{ +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + ldr $8,0x0(%2)\n\ + ldl $8,0x7(%2)\n\ + lw $9,0x8(%2)\n\ + pcpyld $10,$9,$8\n\ + qmtc2 $10,vf01\n\ + lqc2 vf02,0x0(%1)\n\ + lqc2 vf03,0x10(%1)\n\ + lqc2 vf04,0x20(%1)\n\ + lqc2 vf05,0x30(%1)\n\ + vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + sqc2 vf06,0x0(%0)\n\ + ": : "r" (&out) , "r" (&mat) ,"r" (&in): "memory"); +#else + out = mat * in; +#endif +} + +void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const RwV3d *in, int stride) +{ +#ifdef GTA_PS3 + __asm__ __volatile__("\n\ + paddub $3,%4,$0\n\ + lqc2 vf02,0x0(%2)\n\ + lqc2 vf03,0x10(%2)\n\ + lqc2 vf04,0x20(%2)\n\ + lqc2 vf05,0x30(%2)\n\ + ldr $8,0x0(%3)\n\ + ldl $8,0x7(%3)\n\ + lw $9,0x8(%3)\n\ + pcpyld $10,$9,$8\n\ + qmtc2 $10,vf01\n\ + 1: vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + add %3,%3,$3\n\ + ldr $8,0x0(%3)\n\ + ldl $8,0x7(%3)\n\ + lw $9,0x8(%3)\n\ + pcpyld $10,$9,$8\n\ + qmtc2 $10,vf01\n\ + addi %1,%1,-1\n\ + addiu %0,%0,0x10\n\ + sqc2 vf06,-0x10(%0)\n\ + bnez %1,1b\n\ + ": : "r" (out) , "r" (n), "r" (&mat), "r" (in), "r" (stride): "memory"); +#else + while(n--){ + *out = mat * *in; + in = (RwV3d*)((uint8*)in + stride); + out++; + } +#endif +} + +void TransformPoints(CVuVector *out, int n, const CMatrix &mat, const CVuVector *in) +{ +#ifdef GTA_PS2 + __asm__ __volatile__("\n\ + lqc2 vf02,0x0(%2)\n\ + lqc2 vf03,0x10(%2)\n\ + lqc2 vf04,0x20(%2)\n\ + lqc2 vf05,0x30(%2)\n\ + lqc2 vf01,0x0(%3)\n\ + nop\n\ + 1: vmulax.xyz ACC, vf02,vf01\n\ + vmadday.xyz ACC, vf03,vf01\n\ + vmaddaz.xyz ACC, vf04,vf01\n\ + vmaddw.xyz vf06,vf05,vf00\n\ + lqc2 vf01,0x10(%3)\n\ + addiu %3,%3,0x10\n\ + addi %1,%1,-1\n\ + addiu %0,%0,0x10\n\ + sqc2 vf06,-0x10(%0)\n\ + bnez %1,1b\n\ + ": : "r" (out) , "r" (n), "r" (&mat) ,"r" (in): "memory"); +#else + while(n--){ + *out = mat * *in; + in++; + out++; + } +#endif +} diff --git a/src/math/maths.h b/src/math/maths.h new file mode 100644 index 0000000..6a22803 --- /dev/null +++ b/src/math/maths.h @@ -0,0 +1,19 @@ +#pragma once + +// wrapper around float versions of functions +// in gta they are in CMaths but that makes the code rather noisy + +inline float Sin(float x) { return sinf(x); } +inline float Asin(float x) { return asinf(x); } +inline float Cos(float x) { return cosf(x); } +inline float Acos(float x) { return acosf(x); } +inline float Tan(float x) { return tanf(x); } +inline float Atan(float x) { return atanf(x); } +inline float Atan2(float y, float x) { return atan2f(y, x); } +inline float Abs(float x) { return fabsf(x); } +inline float Sqrt(float x) { return sqrtf(x); } +inline float RecipSqrt(float x, float y) { return x/Sqrt(y); } +inline float RecipSqrt(float x) { return RecipSqrt(1.0f, x); } +inline float Pow(float x, float y) { return powf(x, y); } +inline float Floor(float x) { return floorf(x); } +inline float Ceil(float x) { return ceilf(x); } diff --git a/src/modelinfo/BaseModelInfo.cpp b/src/modelinfo/BaseModelInfo.cpp new file mode 100644 index 0000000..7137c60 --- /dev/null +++ b/src/modelinfo/BaseModelInfo.cpp @@ -0,0 +1,101 @@ +#include "common.h" + +#include "templates.h" +#include "TxdStore.h" +#include "2dEffect.h" +#include "BaseModelInfo.h" +#include "ColModel.h" + +CBaseModelInfo::CBaseModelInfo(ModelInfoType type) +{ + m_colModel = nil; + m_twodEffects = nil; + m_objectId = -1; + m_refCount = 0; + m_txdSlot = -1; + m_type = type; + m_num2dEffects = 0; + m_bOwnsColModel = false; +} + +void +CBaseModelInfo::Shutdown(void) +{ + DeleteCollisionModel(); + DeleteRwObject(); + m_twodEffects = nil; + m_num2dEffects = 0; + m_txdSlot = -1; +} + +void +CBaseModelInfo::DeleteCollisionModel(void) +{ + if(m_colModel && m_bOwnsColModel){ + if(m_colModel) + delete m_colModel; + m_colModel = nil; + } +} + +void +CBaseModelInfo::AddRef(void) +{ + m_refCount++; + AddTexDictionaryRef(); +} + +void +CBaseModelInfo::RemoveRef(void) +{ + m_refCount--; + RemoveTexDictionaryRef(); +} + +void +CBaseModelInfo::SetTexDictionary(const char *name) +{ + int slot = CTxdStore::FindTxdSlot(name); + if(slot == -1) + slot = CTxdStore::AddTxdSlot(name); + m_txdSlot = slot; +} + +void +CBaseModelInfo::AddTexDictionaryRef(void) +{ + CTxdStore::AddRef(m_txdSlot); +} + +void +CBaseModelInfo::RemoveTexDictionaryRef(void) +{ + CTxdStore::RemoveRef(m_txdSlot); +} + +void +CBaseModelInfo::Init2dEffects(void) +{ + m_twodEffects = nil; + m_num2dEffects = 0; +} + +void +CBaseModelInfo::Add2dEffect(C2dEffect *fx) +{ + if(m_twodEffects) + m_num2dEffects++; + else{ + m_twodEffects = fx; + m_num2dEffects = 1; + } +} + +C2dEffect* +CBaseModelInfo::Get2dEffect(int n) +{ + if(m_twodEffects) + return &m_twodEffects[n]; + else + return nil; +} diff --git a/src/modelinfo/BaseModelInfo.h b/src/modelinfo/BaseModelInfo.h new file mode 100644 index 0000000..f46cea8 --- /dev/null +++ b/src/modelinfo/BaseModelInfo.h @@ -0,0 +1,80 @@ +#pragma once + +struct CColModel; + +#define MAX_MODEL_NAME (24) + +enum ModelInfoType +{ + MITYPE_NA = 0, + MITYPE_SIMPLE = 1, + MITYPE_MLO = 2, + MITYPE_TIME = 3, + MITYPE_CLUMP = 4, + MITYPE_VEHICLE = 5, + MITYPE_PED = 6, + MITYPE_XTRACOMPS = 7, +}; + +class C2dEffect; + +class CBaseModelInfo +{ +protected: + char m_name[MAX_MODEL_NAME]; + CColModel *m_colModel; + C2dEffect *m_twodEffects; + int16 m_objectId; + uint16 m_refCount; + int16 m_txdSlot; + uint8 m_type; + uint8 m_num2dEffects; + bool m_bOwnsColModel; +#ifdef EXTRA_MODEL_FLAGS +public: + // from mobile + bool m_bIsDoubleSided; + bool m_bIsTree; + bool m_bCanBeIgnored; // for low-end devices + bool RenderDoubleSided(void) { return m_bIsDoubleSided || m_bIsTree; } +#endif + +public: + CBaseModelInfo(ModelInfoType type); + virtual ~CBaseModelInfo() {} + virtual void Shutdown(void); + virtual void DeleteRwObject(void) = 0; + virtual RwObject *CreateInstance(void) = 0; + virtual RwObject *CreateInstance(RwMatrix *) = 0; + virtual RwObject *GetRwObject(void) = 0; + + // one day it becomes virtual + uint8 GetModelType() const { return m_type; } + bool IsSimple(void) { return m_type == MITYPE_SIMPLE || m_type == MITYPE_TIME; } + bool IsClump(void) { return m_type == MITYPE_CLUMP || m_type == MITYPE_PED || m_type == MITYPE_VEHICLE || + m_type == MITYPE_MLO || m_type == MITYPE_XTRACOMPS; // unused but what the heck + } + char *GetModelName(void) { return m_name; } + void SetModelName(const char *name) { strncpy(m_name, name, MAX_MODEL_NAME); } + void SetColModel(CColModel *col, bool owns = false){ + m_colModel = col; m_bOwnsColModel = owns; } + CColModel *GetColModel(void) { return m_colModel; } + bool DoesOwnColModel(void) { return m_bOwnsColModel; } + void DeleteCollisionModel(void); + void ClearTexDictionary(void) { m_txdSlot = -1; } + int16 GetObjectID(void) { return m_objectId; } + void SetObjectID(int16 id) { m_objectId = id; } + int16 GetTxdSlot(void) { return m_txdSlot; } + void AddRef(void); + void RemoveRef(void); + void SetTexDictionary(const char *name); + void AddTexDictionaryRef(void); + void RemoveTexDictionaryRef(void); + void Init2dEffects(void); + void Add2dEffect(C2dEffect *fx); + C2dEffect *Get2dEffect(int n); + uint8 GetNum2dEffects() const { return m_num2dEffects; } + uint16 GetNumRefs() const { return m_refCount; } +}; + +VALIDATE_SIZE(CBaseModelInfo, 0x30); diff --git a/src/modelinfo/ClumpModelInfo.cpp b/src/modelinfo/ClumpModelInfo.cpp new file mode 100644 index 0000000..44a62af --- /dev/null +++ b/src/modelinfo/ClumpModelInfo.cpp @@ -0,0 +1,210 @@ +#include "common.h" + +#include "RwHelper.h" +#include "General.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "ModelInfo.h" +#include "ModelIndices.h" + +void +CClumpModelInfo::DeleteRwObject(void) +{ + if(m_clump){ + RpClumpDestroy(m_clump); + m_clump = nil; + RemoveTexDictionaryRef(); + } +} + +#ifdef PED_SKIN +static RpAtomic* +SetHierarchyForSkinAtomic(RpAtomic *atomic, void *data) +{ + RpSkinAtomicSetHAnimHierarchy(atomic, (RpHAnimHierarchy*)data); + return nil; +} +#endif + +RwObject* +CClumpModelInfo::CreateInstance(void) +{ + if(m_clump == nil) + return nil; + RpClump *clone = RpClumpClone(m_clump); +#ifdef PED_SKIN + if(IsClumpSkinned(clone)){ + RpHAnimHierarchy *hier; + RpHAnimAnimation *anim; + + hier = GetAnimHierarchyFromClump(clone); + assert(hier); + // This seems dangerous as only the first atomic will get a hierarchy + // can we guarantee this if hands and head are also in the clump? + RpClumpForAllAtomics(clone, SetHierarchyForSkinAtomic, hier); + anim = HAnimAnimationCreateForHierarchy(hier); + RpHAnimHierarchySetCurrentAnim(hier, anim); + RpHAnimHierarchySetFlags(hier, (RpHAnimHierarchyFlag)(rpHANIMHIERARCHYUPDATEMODELLINGMATRICES|rpHANIMHIERARCHYUPDATELTMS)); + // the rest is xbox only: + // RpSkinGetNumBones(RpSkinGeometryGetSkin(RpAtomicGetGeometry(IsClumpSkinned(clone)))); + RpHAnimHierarchyUpdateMatrices(hier); + } +#endif + return (RwObject*)clone; +} + +RwObject* +CClumpModelInfo::CreateInstance(RwMatrix *m) +{ + if(m_clump){ + RpClump *clump = (RpClump*)CreateInstance(); + *RwFrameGetMatrix(RpClumpGetFrame(clump)) = *m; + return (RwObject*)clump; + } + return nil; +} + +RpAtomic* +CClumpModelInfo::SetAtomicRendererCB(RpAtomic *atomic, void *data) +{ + CVisibilityPlugins::SetAtomicRenderCallback(atomic, (RpAtomicCallBackRender)data); + return atomic; +} + +void +CClumpModelInfo::SetClump(RpClump *clump) +{ + m_clump = clump; + CVisibilityPlugins::SetClumpModelInfo(m_clump, this); + AddTexDictionaryRef(); + RpClumpForAllAtomics(clump, SetAtomicRendererCB, nil); + +#ifdef PED_SKIN + if(IsClumpSkinned(clump)){ + int i; + RpHAnimHierarchy *hier; + RpAtomic *skinAtomic; + RpSkin *skin; + + // mobile: +// hier = nil; +// RwFrameForAllChildren(RpClumpGetFrame(clump), GetHierarchyFromChildNodesCB, &hier); +// assert(hier); +// RpClumpForAllAtomics(clump, SetHierarchyForSkinAtomic, hier); +// skinAtomic = GetFirstAtomic(clump); + + // xbox: + hier = GetAnimHierarchyFromClump(clump); + assert(hier); + RpSkinAtomicSetHAnimHierarchy(IsClumpSkinned(clump), hier); + skinAtomic = IsClumpSkinned(clump); + + assert(skinAtomic); + skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(skinAtomic)); + // ignore const + for(i = 0; i < RpGeometryGetNumVertices(RpAtomicGetGeometry(skinAtomic)); i++){ + RwMatrixWeights *weights = (RwMatrixWeights*)&RpSkinGetVertexBoneWeights(skin)[i]; + float sum = weights->w0 + weights->w1 + weights->w2 + weights->w3; + weights->w0 /= sum; + weights->w1 /= sum; + weights->w2 /= sum; + weights->w3 /= sum; + } + RpHAnimHierarchySetFlags(hier, (RpHAnimHierarchyFlag)(rpHANIMHIERARCHYUPDATEMODELLINGMATRICES|rpHANIMHIERARCHYUPDATELTMS)); + } + if(strcmp(GetModelName(), "playerh") == 0){ + // playerh is incompatible with the xbox player skin + // so check if player model is skinned and only apply skin to head if it isn't + CPedModelInfo *body = (CPedModelInfo*)CModelInfo::GetModelInfo(MI_PLAYER); + if(!(body->m_clump && IsClumpSkinned(body->m_clump))) + RpClumpForAllAtomics(clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); + } +#else + if(strcmp(GetModelName(), "playerh") == 0) + RpClumpForAllAtomics(clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); +#endif +} + +void +CClumpModelInfo::SetFrameIds(RwObjectNameIdAssocation *assocs) +{ + int32 i; + RwObjectNameAssociation objname; + + for(i = 0; assocs[i].name; i++) + if((assocs[i].flags & CLUMP_FLAG_NO_HIERID) == 0){ + objname.frame = nil; + objname.name = assocs[i].name; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), FindFrameFromNameWithoutIdCB, &objname); + if(objname.frame) + CVisibilityPlugins::SetFrameHierarchyId(objname.frame, assocs[i].hierId); + } +} + +RwFrame* +CClumpModelInfo::FindFrameFromIdCB(RwFrame *frame, void *data) +{ + RwObjectIdAssociation *assoc = (RwObjectIdAssociation*)data; + + if(CVisibilityPlugins::GetFrameHierarchyId(frame) == assoc->id){ + assoc->frame = frame; + return nil; + } + RwFrameForAllChildren(frame, FindFrameFromIdCB, assoc); + return assoc->frame ? nil : frame; +} + +RwFrame* +CClumpModelInfo::FindFrameFromNameCB(RwFrame *frame, void *data) +{ + RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data; + + if(!CGeneral::faststricmp(GetFrameNodeName(frame), assoc->name)){ + assoc->frame = frame; + return nil; + } + RwFrameForAllChildren(frame, FindFrameFromNameCB, assoc); + return assoc->frame ? nil : frame; +} + +RwFrame* +CClumpModelInfo::FindFrameFromNameWithoutIdCB(RwFrame *frame, void *data) +{ + RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data; + + if(CVisibilityPlugins::GetFrameHierarchyId(frame) == 0 && + !CGeneral::faststricmp(GetFrameNodeName(frame), assoc->name)){ + assoc->frame = frame; + return nil; + } + RwFrameForAllChildren(frame, FindFrameFromNameWithoutIdCB, assoc); + return assoc->frame ? nil : frame; +} + +RwFrame* +CClumpModelInfo::FillFrameArrayCB(RwFrame *frame, void *data) +{ + int32 id; + RwFrame **frames = (RwFrame**)data; + id = CVisibilityPlugins::GetFrameHierarchyId(frame); + if(id > 0) + frames[id] = frame; + RwFrameForAllChildren(frame, FillFrameArrayCB, data); + return frame; +} + +void +CClumpModelInfo::FillFrameArray(RpClump *clump, RwFrame **frames) +{ + RwFrameForAllChildren(RpClumpGetFrame(clump), FillFrameArrayCB, frames); +} + +RwFrame* +CClumpModelInfo::GetFrameFromId(RpClump *clump, int32 id) +{ + RwObjectIdAssociation assoc; + assoc.id = id; + assoc.frame = nil; + RwFrameForAllChildren(RpClumpGetFrame(clump), FindFrameFromIdCB, &assoc); + return assoc.frame; +} diff --git a/src/modelinfo/ClumpModelInfo.h b/src/modelinfo/ClumpModelInfo.h new file mode 100644 index 0000000..58b6de1 --- /dev/null +++ b/src/modelinfo/ClumpModelInfo.h @@ -0,0 +1,54 @@ +#pragma once + +#include "BaseModelInfo.h" + +struct RwObjectNameIdAssocation +{ + const char *name; + int32 hierId; + uint32 flags; +}; + +struct RwObjectNameAssociation +{ + const char *name; + RwFrame *frame; +}; + +struct RwObjectIdAssociation +{ + int32 id; + RwFrame *frame; +}; + +enum { + CLUMP_FLAG_NO_HIERID = 0x1, +}; + + +class CClumpModelInfo : public CBaseModelInfo +{ +public: + RpClump *m_clump; + + CClumpModelInfo(void) : CBaseModelInfo(MITYPE_CLUMP) {} + CClumpModelInfo(ModelInfoType id) : CBaseModelInfo(id) {} + ~CClumpModelInfo() {} + void DeleteRwObject(void); + RwObject *CreateInstance(void); + RwObject *CreateInstance(RwMatrix *); + RwObject *GetRwObject(void) { return (RwObject*)m_clump; } + + virtual void SetClump(RpClump *); + + static RpAtomic *SetAtomicRendererCB(RpAtomic *atomic, void *data); + void SetFrameIds(RwObjectNameIdAssocation *assocs); + static RwFrame *FindFrameFromNameCB(RwFrame *frame, void *data); + static RwFrame *FindFrameFromNameWithoutIdCB(RwFrame *frame, void *data); + static RwFrame *FindFrameFromIdCB(RwFrame *frame, void *data); + static void FillFrameArray(RpClump *clump, RwFrame **frames); + static RwFrame *FillFrameArrayCB(RwFrame *frame, void *data); + static RwFrame *GetFrameFromId(RpClump *clump, int32 id); +}; + +VALIDATE_SIZE(CClumpModelInfo, 0x34); diff --git a/src/modelinfo/MloModelInfo.cpp b/src/modelinfo/MloModelInfo.cpp new file mode 100644 index 0000000..7535e6c --- /dev/null +++ b/src/modelinfo/MloModelInfo.cpp @@ -0,0 +1,39 @@ +#include "common.h" + +#include "VisibilityPlugins.h" +#include "ModelInfo.h" + +void +CMloModelInfo::ConstructClump() +{ + m_clump = RpClumpCreate(); + RwFrame *mainFrame = RwFrameCreate(); + RwFrameSetIdentity(mainFrame); + RpClumpSetFrame(m_clump, mainFrame); + + for (int i = firstInstance; i < lastInstance; i++) { + int modelId = CModelInfo::GetMloInstanceStore().store[i].m_modelIndex; + RwMatrix *attMat = CModelInfo::GetMloInstanceStore().store[i].GetMatrix().m_attachment; + CSimpleModelInfo *minfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(modelId); + + if (minfo->m_atomics[0] != nil) { + RpAtomic *newAtomic = RpAtomicClone(minfo->m_atomics[0]); + RwFrame *newFrame = RwFrameCreate(); + if (newAtomic != nil && newFrame != nil) { + *RwFrameGetMatrix(newFrame) = *attMat; + RpAtomicSetFrame(newAtomic, newFrame); + RwFrameAddChild(mainFrame, newFrame); + RpClumpAddAtomic(m_clump, newAtomic); + } else { + debug("Failed to allocate memory while creating template MLO.\n"); + } + } + } + + if (RpClumpGetNumAtomics(m_clump) != 0) { + CVisibilityPlugins::SetClumpModelInfo(m_clump, this); + } else { + RpClumpDestroy(m_clump); + m_clump = nil; + } +} \ No newline at end of file diff --git a/src/modelinfo/MloModelInfo.h b/src/modelinfo/MloModelInfo.h new file mode 100644 index 0000000..b1ae329 --- /dev/null +++ b/src/modelinfo/MloModelInfo.h @@ -0,0 +1,14 @@ +#pragma once + +#include "ClumpModelInfo.h" + +class CMloModelInfo : public CClumpModelInfo +{ +public: + float drawDist; + int firstInstance; + int lastInstance; +public: + CMloModelInfo(void) : CClumpModelInfo(MITYPE_MLO) {} + void ConstructClump(); +}; \ No newline at end of file diff --git a/src/modelinfo/ModelIndices.cpp b/src/modelinfo/ModelIndices.cpp new file mode 100644 index 0000000..98c7fb3 --- /dev/null +++ b/src/modelinfo/ModelIndices.cpp @@ -0,0 +1,34 @@ +#include "common.h" + +#include "General.h" +#include "ModelIndices.h" + +#define X(name, var) int16 var = -1; + MODELINDICES +#undef X + +void +InitModelIndices(void) +{ +#define X(name, var) var = -1; + MODELINDICES +#undef X +} + +void +MatchModelString(const char *modelname, int16 id) +{ +#define X(name, var) \ + if(!CGeneral::faststrcmp(name, modelname)){ \ + var = id; \ + return; \ + } + MODELINDICES +#undef X +} + +void +TestModelIndices(void) +{ + ; +} diff --git a/src/modelinfo/ModelIndices.h b/src/modelinfo/ModelIndices.h new file mode 100644 index 0000000..c0f0192 --- /dev/null +++ b/src/modelinfo/ModelIndices.h @@ -0,0 +1,509 @@ +#pragma once + +#define MODELINDICES \ + X("fire_hydrant", MI_FIRE_HYDRANT) \ + X("bagelstnd02", MI_BAGELSTAND2) \ + X("fish01", MI_FISHSTALL01) \ + X("fishstall02", MI_FISHSTALL02) \ + X("fishstall03", MI_FISHSTALL03) \ + X("fishstall04", MI_FISHSTALL04) \ + X("taxisign", MI_TAXISIGN) \ + X("phonesign", MI_PHONESIGN) \ + X("noparkingsign1", MI_NOPARKINGSIGN1) \ + X("bussign1", MI_BUSSIGN1) \ + X("roadworkbarrier1", MI_ROADWORKBARRIER1) \ + X("dump1", MI_DUMP1) \ + X("trafficcone", MI_TRAFFICCONE) \ + X("newsstand1", MI_NEWSSTAND) \ + X("postbox1", MI_POSTBOX1) \ + X("bin1", MI_BIN) \ + X("wastebin", MI_WASTEBIN) \ + X("phonebooth1", MI_PHONEBOOTH1) \ + X("parkingmeter", MI_PARKINGMETER) \ + X("trafficlight1", MI_TRAFFICLIGHTS) \ + X("lamppost1", MI_SINGLESTREETLIGHTS1) \ + X("lamppost2", MI_SINGLESTREETLIGHTS2) \ + X("lamppost3", MI_SINGLESTREETLIGHTS3) \ + X("doublestreetlght1", MI_DOUBLESTREETLIGHTS) \ + X("rd_Road2A10", MI_ROADSFORROADBLOCKSSTART) \ + X("rd_Road1A30", MI_ROADSFORROADBLOCKSEND) \ + X("veg_tree1", MI_TREE1) \ + X("veg_tree3", MI_TREE2) \ + X("veg_treea1", MI_TREE3) \ + X("veg_treenew01", MI_TREE4) \ + X("veg_treenew05", MI_TREE5) \ + X("veg_treeb1", MI_TREE6) \ + X("veg_treenew10", MI_TREE7) \ + X("veg_treea3", MI_TREE8) \ + X("veg_treenew09", MI_TREE9) \ + X("veg_treenew08", MI_TREE10) \ + X("veg_treenew03", MI_TREE11) \ + X("veg_treenew16", MI_TREE12) \ + X("veg_treenew17", MI_TREE13) \ + X("veg_treenew06", MI_TREE14) \ + X("doc_crane_cab", MODELID_CRANE_1) \ + X("cranetopb", MODELID_CRANE_2) \ + X("cranetopa", MODELID_CRANE_3) \ + X("package1", MI_COLLECTABLE1) \ + X("Money", MI_MONEY) \ + X("barrel1", MI_CARMINE) \ + X("oddjgaragdoor", MI_GARAGEDOOR1) \ + X("bombdoor", MI_GARAGEDOOR2) \ + X("door_bombshop", MI_GARAGEDOOR3) \ + X("vheistlocdoor", MI_GARAGEDOOR4) \ + X("door2_garage", MI_GARAGEDOOR5) \ + X("ind_slidedoor", MI_GARAGEDOOR6) \ + X("bankjobdoor", MI_GARAGEDOOR7) \ + X("door_jmsgrage", MI_GARAGEDOOR9) \ + X("jamesgrge_kb", MI_GARAGEDOOR10) \ + X("door_sfehousegrge", MI_GARAGEDOOR11) \ + X("shedgaragedoor", MI_GARAGEDOOR12) \ + X("door4_garage", MI_GARAGEDOOR13) \ + X("door_col_compnd_01", MI_GARAGEDOOR14) \ + X("door_col_compnd_02", MI_GARAGEDOOR15) \ + X("door_col_compnd_03", MI_GARAGEDOOR16) \ + X("door_col_compnd_04", MI_GARAGEDOOR17) \ + X("door_col_compnd_05", MI_GARAGEDOOR18) \ + X("impex_door", MI_GARAGEDOOR19) \ + X("SalvGarage", MI_GARAGEDOOR20) \ + X("door3_garage", MI_GARAGEDOOR21) \ + X("leveldoor2", MI_GARAGEDOOR22) \ + X("double_garage_dr", MI_GARAGEDOOR23) \ + X("amcogaragedoor", MI_GARAGEDOOR24) \ + X("towergaragedoor1", MI_GARAGEDOOR25) \ + X("towergaragedoor2", MI_GARAGEDOOR26) \ + X("towergaragedoor3", MI_GARAGEDOOR27) \ + X("plysve_gragedoor", MI_GARAGEDOOR28) \ + X("impexpsubgrgdoor", MI_GARAGEDOOR29) \ + X("Sub_sprayshopdoor", MI_GARAGEDOOR30) \ + X("ind_plyrwoor", MI_GARAGEDOOR31) \ + X("8ballsuburbandoor", MI_GARAGEDOOR32) \ + X("barrel2", MI_NAUTICALMINE) \ + X("crushercrush", MI_CRUSHERBODY) \ + X("crushertop", MI_CRUSHERLID) \ + X("donkeymag", MI_DONKEYMAG) \ + X("bullion", MI_BULLION) \ + X("floatpackge1", MI_FLOATPACKAGE1) \ + X("briefcase", MI_BRIEFCASE) \ + X("chinabanner1", MI_CHINABANNER1) \ + X("chinabanner2", MI_CHINABANNER2) \ + X("chinabanner3", MI_CHINABANNER3) \ + X("chinabanner4", MI_CHINABANNER4) \ + X("iten_chinatown5", MI_CHINABANNER5) \ + X("iten_chinatown7", MI_CHINABANNER6) \ + X("iten_chinatown3", MI_CHINABANNER7) \ + X("iten_chinatown2", MI_CHINABANNER8) \ + X("iten_chinatown4", MI_CHINABANNER9) \ + X("iten_washline01", MI_CHINABANNER10) \ + X("iten_washline02", MI_CHINABANNER11) \ + X("iten_washline03", MI_CHINABANNER12) \ + X("chinalanterns", MI_CHINALANTERN) \ + X("glassfx1", MI_GLASS1) \ + X("glassfx2", MI_GLASS2) \ + X("glassfx3", MI_GLASS3) \ + X("glassfx4", MI_GLASS4) \ + X("glassfx55", MI_GLASS5) \ + X("glassfxsub1", MI_GLASS6) \ + X("glassfxsub2", MI_GLASS7) \ + X("glassfx_composh", MI_GLASS8) \ + X("bridge_liftsec", MI_BRIDGELIFT) \ + X("bridge_liftweight", MI_BRIDGEWEIGHT) \ + X("subbridge_lift", MI_BRIDGEROADSEGMENT) \ + X("barrel4", MI_EXPLODINGBARREL) \ + X("flagsitaly", MI_ITALYBANNER1) \ + X("adrenaline", MI_PICKUP_ADRENALINE) \ + X("bodyarmour", MI_PICKUP_BODYARMOUR) \ + X("info", MI_PICKUP_INFO) \ + X("health", MI_PICKUP_HEALTH) \ + X("bonus", MI_PICKUP_BONUS) \ + X("bribe", MI_PICKUP_BRIBE) \ + X("killfrenzy", MI_PICKUP_KILLFRENZY) \ + X("camerapickup", MI_PICKUP_CAMERA) \ + X("bollardlight", MI_BOLLARDLIGHT) \ + X("magnet", MI_MAGNET) \ + X("streetlamp1", MI_STREETLAMP1) \ + X("streetlamp2", MI_STREETLAMP2) \ + X("railtrax_lo4b", MI_RAILTRACKS) \ + X("bar_barrier10", MI_FENCE) \ + X("bar_barrier12", MI_FENCE2) \ + X("petrolpump", MI_PETROLPUMP) \ + X("bodycast", MI_BODYCAST) \ + X("backdoor", MI_BACKDOOR) \ + X("coffee", MI_COFFEE) \ + X("bouy", MI_BUOY) \ + X("parktable1", MI_PARKTABLE) \ + X("sbwy_tunl_start", MI_SUBWAY1) \ + X("sbwy_tunl_bit", MI_SUBWAY2) \ + X("sbwy_tunl_bend", MI_SUBWAY3) \ + X("sbwy_tunl_cstm6", MI_SUBWAY4) \ + X("sbwy_tunl_cstm7", MI_SUBWAY5) \ + X("sbwy_tunl_cstm8", MI_SUBWAY6) \ + X("sbwy_tunl_cstm10", MI_SUBWAY7) \ + X("sbwy_tunl_cstm9", MI_SUBWAY8) \ + X("sbwy_tunl_cstm11", MI_SUBWAY9) \ + X("sbwy_tunl_cstm1", MI_SUBWAY10) \ + X("sbwy_tunl_cstm2", MI_SUBWAY11) \ + X("sbwy_tunl_cstm4", MI_SUBWAY12) \ + X("sbwy_tunl_cstm3", MI_SUBWAY13) \ + X("sbwy_tunl_cstm5", MI_SUBWAY14) \ + X("subplatform_n2", MI_SUBWAY15) \ + X("suby_tunl_start", MI_SUBWAY16) \ + X("sbwy_tunl_start2", MI_SUBWAY17) \ + X("indy_tunl_start", MI_SUBWAY18) \ + X("indsubway03", MI_SUBPLATFORM_IND) \ + X("comerside_subway", MI_SUBPLATFORM_COMS) \ + X("subplatform", MI_SUBPLATFORM_COMS2) \ + X("subplatform_n", MI_SUBPLATFORM_COMN) \ + X("Otherside_subway", MI_SUBPLATFORM_SUB) \ + X("subplatform_sub", MI_SUBPLATFORM_SUB2) \ + X("files", MI_FILES) + +#define X(name, var) extern int16 var; + MODELINDICES +#undef X + +// and some hardcoded ones +// expand as needed +enum +{ + MI_PLAYER = 0, + MI_COP, + MI_SWAT, + MI_FBI, + MI_ARMY, + MI_MEDIC, + MI_FIREMAN, + MI_MALE01, + MI_TAXI_D, + MI_PIMP, + MI_GANG01, + MI_GANG02, + MI_GANG03, + MI_GANG04, + MI_GANG05, + MI_GANG06, + MI_GANG07, + MI_GANG08, + MI_GANG09, + MI_GANG10, + MI_GANG11, + MI_GANG12, + MI_GANG13, + MI_GANG14, + MI_CRIMINAL01, + MI_CRIMINAL02, + MI_SPECIAL01, + MI_SPECIAL02, + MI_SPECIAL03, + MI_SPECIAL04, + MI_MALE02, + MI_MALE03, + MI_FATMALE01, + MI_FATMALE02, + MI_FEMALE01, + MI_FEMALE02, + MI_FEMALE03, + MI_FATFEMALE01, + MI_FATFEMALE02, + MI_PROSTITUTE, + MI_PROSTITUTE2, + MI_P_MAN1, + MI_P_MAN2, + MI_P_WOM1, + MI_P_WOM2, + MI_CT_MAN1, + MI_CT_MAN2, + MI_CT_WOM1, + MI_CT_WOM2, + MI_LI_MAN1, + MI_LI_MAN2, + MI_LI_WOM1, + MI_LI_WOM2, + MI_DOCKER1, + MI_DOCKER2, + MI_SCUM_MAN, + MI_SCUM_WOM, + MI_WORKER1, + MI_WORKER2, + MI_B_MAN1, + MI_B_MAN2, + MI_B_MAN3, + MI_B_WOM1, + MI_B_WOM2, + MI_B_WOM3, + MI_MOD_MAN, + MI_MOD_WOM, + MI_ST_MAN, + MI_ST_WOM, + MI_FAN_MAN1, + MI_FAN_MAN2, + MI_FAN_WOM, + MI_HOS_MAN, + MI_HOS_WOM, + MI_CONST1, + MI_CONST2, + MI_SHOPPER1, + MI_SHOPPER2, + MI_SHOPPER3, + MI_STUD_MAN, + MI_STUD_WOM, + MI_CAS_MAN, + MI_CAS_WOM, + MI_BUSKER1, + MI_BUSKER2, + MI_BUSKER3, + MI_BUSKER4, + // three more peds possible + + MI_LAST_PED = 89, + MI_FIRST_VEHICLE, + + MI_LANDSTAL = MI_FIRST_VEHICLE, + MI_IDAHO, + MI_STINGER, + MI_LINERUN, + MI_PEREN, + MI_SENTINEL, + MI_PATRIOT, + MI_FIRETRUCK, + MI_TRASH, + MI_STRETCH, + MI_MANANA, + MI_INFERNUS, + MI_BLISTA, + MI_PONY, + MI_MULE, + MI_CHEETAH, + MI_AMBULAN, + MI_FBICAR, + MI_MOONBEAM, + MI_ESPERANT, + MI_TAXI, + MI_KURUMA, + MI_BOBCAT, + MI_MRWHOOP, + MI_BFINJECT, + MI_CORPSE, + MI_POLICE, + MI_ENFORCER, + MI_SECURICA, + MI_BANSHEE, + MI_PREDATOR, + MI_BUS, + MI_RHINO, + MI_BARRACKS, + MI_TRAIN, + MI_CHOPPER, + MI_DODO, + MI_COACH, + MI_CABBIE, + MI_STALLION, + MI_RUMPO, + MI_RCBANDIT, + MI_BELLYUP, + MI_MRWONGS, + MI_MAFIA, + MI_YARDIE, + MI_YAKUZA, + MI_DIABLOS, + MI_COLUMB , + MI_HOODS, + MI_AIRTRAIN, + MI_DEADDODO, + MI_SPEEDER, + MI_REEFER, + MI_PANLANT, + MI_FLATBED, + MI_YANKEE, + MI_ESCAPE, + MI_BORGNINE, + MI_TOYZ, + MI_GHOST, + + // leftovers on PC + MI_MIAMI_RCBARON = 154, + MI_MIAMI_RCRAIDER = 155, + MI_MIAMI_SPARROW = 159, + + MI_GRENADE = 170, + MI_AK47, + MI_BASEBALL_BAT, + MI_COLT, + MI_MOLOTOV, + MI_ROCKETLAUNCHER, + MI_SHOTGUN, + MI_SNIPER, + MI_UZI, + MI_MISSILE, + MI_M16, + MI_FLAMETHROWER, + MI_BOMB, + MI_FINGERS, + + MI_CUTOBJ01 = 185, + MI_CUTOBJ02, + MI_CUTOBJ03, + MI_CUTOBJ04, + MI_CUTOBJ05, + + MI_CAR_DOOR = 190, + MI_CAR_BUMPER, + MI_CAR_PANEL, + MI_CAR_BONNET, + MI_CAR_BOOT, + MI_CAR_WHEEL, + MI_BODYPARTA, + MI_BODYPARTB, + + MI_AIRTRAIN_VLO = 198, + MI_LOPOLYGUY, + + NUM_DEFAULT_MODELS +}; + +enum{ + NUM_OF_SPECIAL_CHARS = 4, + NUM_OF_CUTSCENE_OBJECTS = 5 +}; + +void InitModelIndices(void); +void MatchModelString(const char *name, int16 id); +void TestModelIndices(void); + +inline bool +IsGlass(int16 id) +{ + return id == MI_GLASS1 || + id == MI_GLASS2 || + id == MI_GLASS3 || + id == MI_GLASS4 || + id == MI_GLASS5 || + id == MI_GLASS6 || + id == MI_GLASS7 || + id == MI_GLASS8; +} + +inline bool +IsStreetLight(int16 id) +{ + return id == MI_TRAFFICLIGHTS || + id == MI_SINGLESTREETLIGHTS1 || + id == MI_SINGLESTREETLIGHTS2 || + id == MI_SINGLESTREETLIGHTS3 || + id == MI_DOUBLESTREETLIGHTS; +} + +inline bool +IsBodyPart(int16 id) +{ + return id == MI_BODYPARTA || id == MI_BODYPARTB; +} + +// This is bad and should perhaps not be used +inline bool +IsBoatModel(int16 id) +{ + return id == MI_PREDATOR || + id == MI_REEFER || + id == MI_SPEEDER || + id == MI_GHOST; +} + +inline bool +IsPedModel(int16 id) +{ + return id >= MI_PLAYER && id <= MI_LAST_PED; +} + +inline bool +IsTreeModel(int16 id) +{ + return id == MI_TREE1 || + id == MI_TREE2 || + id == MI_TREE3 || + id == MI_TREE4 || + id == MI_TREE5 || + id == MI_TREE6 || + id == MI_TREE7 || + id == MI_TREE8 || + id == MI_TREE9 || + id == MI_TREE10 || + id == MI_TREE11 || + id == MI_TREE12 || + id == MI_TREE13 || + id == MI_TREE14; +} + +inline bool +IsBannerModel(int16 id) +{ + return id == MI_CHINABANNER1 || + id == MI_CHINABANNER2 || + id == MI_CHINABANNER3 || + id == MI_CHINABANNER4 || + id == MI_CHINABANNER5 || + id == MI_CHINABANNER6 || + id == MI_CHINABANNER7 || + id == MI_CHINABANNER8 || + id == MI_CHINABANNER9 || + id == MI_CHINABANNER10 || + id == MI_CHINABANNER11 || + id == MI_CHINABANNER12 || + id == MI_ITALYBANNER1 || + id == MI_CHINALANTERN; +} +inline bool +IsPickupModel(int16 id) +{ + return id == MI_GRENADE || + id == MI_AK47 || + id == MI_BASEBALL_BAT || + id == MI_COLT || + id == MI_MOLOTOV || + id == MI_ROCKETLAUNCHER || + id == MI_SHOTGUN || + id == MI_SNIPER || + id == MI_UZI || + id == MI_M16 || + id == MI_FLAMETHROWER || + id == MI_PICKUP_ADRENALINE || + id == MI_PICKUP_BODYARMOUR || + id == MI_PICKUP_INFO || + id == MI_PICKUP_HEALTH || + id == MI_PICKUP_BONUS || + id == MI_PICKUP_BRIBE || + id == MI_PICKUP_KILLFRENZY || + id == MI_PICKUP_CAMERA; +} + +inline bool +IsPolicePedModel(int16 id) +{ + return id == MI_COP || + id == MI_SWAT || + id == MI_FBI || + id == MI_ARMY; +} + +inline bool +IsPoliceVehicleModel(int16 id) +{ + return id == MI_CHOPPER || + id == MI_PREDATOR || + id == MI_POLICE || + id == MI_ENFORCER; +} + +inline bool +IsExplosiveThingModel(int16 id) +{ + return id == MI_EXPLODINGBARREL || + id == MI_PETROLPUMP; +} + +inline bool +IsFence(int16 id) +{ + return id == MI_FENCE || + id == MI_FENCE2; +} \ No newline at end of file diff --git a/src/modelinfo/ModelInfo.cpp b/src/modelinfo/ModelInfo.cpp new file mode 100644 index 0000000..7aa5fc8 --- /dev/null +++ b/src/modelinfo/ModelInfo.cpp @@ -0,0 +1,254 @@ +#include "common.h" + +#include "General.h" +#include "TempColModels.h" +#include "ModelIndices.h" +#include "ModelInfo.h" +#include "Frontend.h" + +CBaseModelInfo *CModelInfo::ms_modelInfoPtrs[MODELINFOSIZE]; + +CStore CModelInfo::ms_simpleModelStore; +CStore CModelInfo::ms_mloModelStore; +CStore CModelInfo::ms_mloInstanceStore; +CStore CModelInfo::ms_timeModelStore; +CStore CModelInfo::ms_clumpModelStore; +CStore CModelInfo::ms_pedModelStore; +CStore CModelInfo::ms_vehicleModelStore; +CStore CModelInfo::ms_xtraCompsModelStore; +CStore CModelInfo::ms_2dEffectStore; + +void +CModelInfo::Initialise(void) +{ + int i; + CSimpleModelInfo *m; + + for(i = 0; i < MODELINFOSIZE; i++) + ms_modelInfoPtrs[i] = nil; + ms_2dEffectStore.Clear(); + ms_mloInstanceStore.Clear(); + ms_xtraCompsModelStore.Clear(); + ms_simpleModelStore.Clear(); + ms_timeModelStore.Clear(); + ms_mloModelStore.Clear(); + ms_clumpModelStore.Clear(); + ms_pedModelStore.Clear(); + ms_vehicleModelStore.Clear(); + + m = AddSimpleModel(MI_CAR_DOOR); + m->SetColModel(&CTempColModels::ms_colModelDoor1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_BUMPER); + m->SetColModel(&CTempColModels::ms_colModelBumper1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_PANEL); + m->SetColModel(&CTempColModels::ms_colModelPanel1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_BONNET); + m->SetColModel(&CTempColModels::ms_colModelBonnet1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_BOOT); + m->SetColModel(&CTempColModels::ms_colModelBoot1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_CAR_WHEEL); + m->SetColModel(&CTempColModels::ms_colModelWheel1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_BODYPARTA); + m->SetColModel(&CTempColModels::ms_colModelBodyPart1); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; + + m = AddSimpleModel(MI_BODYPARTB); + m->SetColModel(&CTempColModels::ms_colModelBodyPart2); + m->SetTexDictionary("generic"); + m->SetNumAtomics(1); + m->m_lodDistances[0] = 80.0f; +} + +void +CModelInfo::ShutDown(void) +{ + int i; + for(i = 0; i < ms_simpleModelStore.allocPtr; i++) + ms_simpleModelStore.store[i].Shutdown(); + for(i = 0; i < ms_mloInstanceStore.allocPtr; i++) + ms_mloInstanceStore.store[i].Shutdown(); + for(i = 0; i < ms_timeModelStore.allocPtr; i++) + ms_timeModelStore.store[i].Shutdown(); + for(i = 0; i < ms_clumpModelStore.allocPtr; i++) + ms_clumpModelStore.store[i].Shutdown(); + for(i = 0; i < ms_vehicleModelStore.allocPtr; i++) + ms_vehicleModelStore.store[i].Shutdown(); + for(i = 0; i < ms_pedModelStore.allocPtr; i++) + ms_pedModelStore.store[i].Shutdown(); + for(i = 0; i < ms_xtraCompsModelStore.allocPtr; i++) + ms_xtraCompsModelStore.store[i].Shutdown(); + for(i = 0; i < ms_mloInstanceStore.allocPtr; i++) + ms_mloInstanceStore.store[i].Shutdown(); + for(i = 0; i < ms_2dEffectStore.allocPtr; i++) + ms_2dEffectStore.store[i].Shutdown(); + + ms_2dEffectStore.Clear(); + ms_simpleModelStore.Clear(); + ms_mloInstanceStore.Clear(); + ms_mloModelStore.Clear(); + ms_xtraCompsModelStore.Clear(); + ms_timeModelStore.Clear(); + ms_pedModelStore.Clear(); + ms_clumpModelStore.Clear(); + ms_vehicleModelStore.Clear(); +} + +CSimpleModelInfo* +CModelInfo::AddSimpleModel(int id) +{ + CSimpleModelInfo *modelinfo; + modelinfo = CModelInfo::ms_simpleModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->Init(); + return modelinfo; +} + +CMloModelInfo * +CModelInfo::AddMloModel(int id) +{ + CMloModelInfo *modelinfo; + modelinfo = CModelInfo::ms_mloModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + modelinfo->firstInstance = 0; + modelinfo->lastInstance = 0; + return modelinfo; +} + +CTimeModelInfo* +CModelInfo::AddTimeModel(int id) +{ + CTimeModelInfo *modelinfo; + modelinfo = CModelInfo::ms_timeModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->Init(); + return modelinfo; +} + +CClumpModelInfo* +CModelInfo::AddClumpModel(int id) +{ + CClumpModelInfo *modelinfo; + modelinfo = CModelInfo::ms_clumpModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + return modelinfo; +} + +CPedModelInfo* +CModelInfo::AddPedModel(int id) +{ + CPedModelInfo *modelinfo; + modelinfo = CModelInfo::ms_pedModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + return modelinfo; +} + +CVehicleModelInfo* +CModelInfo::AddVehicleModel(int id) +{ + CVehicleModelInfo *modelinfo; + modelinfo = CModelInfo::ms_vehicleModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->m_clump = nil; + modelinfo->m_vehicleType = -1; + modelinfo->m_wheelId = -1; + modelinfo->m_materials1[0] = nil; + modelinfo->m_materials2[0] = nil; + modelinfo->m_bikeSteerAngle = 999.99f; + return modelinfo; +} + +CBaseModelInfo* +CModelInfo::GetModelInfo(const char *name, int *id) +{ + CBaseModelInfo *modelinfo; + for(int i = 0; i < MODELINFOSIZE; i++){ + modelinfo = CModelInfo::ms_modelInfoPtrs[i]; + if(modelinfo && !CGeneral::faststricmp(modelinfo->GetModelName(), name)){ + if(id) + *id = i; + return modelinfo; + } + } + return nil; +} + +bool +CModelInfo::IsBoatModel(int32 id) +{ + return GetModelInfo(id)->GetModelType() == MITYPE_VEHICLE && + ((CVehicleModelInfo*)GetModelInfo(id))->m_vehicleType == VEHICLE_TYPE_BOAT; +} + +bool +CModelInfo::IsBikeModel(int32 id) +{ + return GetModelInfo(id)->GetModelType() == MITYPE_VEHICLE && + ((CVehicleModelInfo*)GetModelInfo(id))->m_vehicleType == VEHICLE_TYPE_BIKE; +} + +void +CModelInfo::RemoveColModelsFromOtherLevels(eLevelName level) +{ + ISLAND_LOADING_IS(LOW) + { + int i; + CBaseModelInfo *mi; + CColModel *colmodel; + + for (i = 0; i < MODELINFOSIZE; i++) { + mi = GetModelInfo(i); + if (mi) { + colmodel = mi->GetColModel(); + if (colmodel && colmodel->level != LEVEL_GENERIC && colmodel->level != level) + colmodel->RemoveCollisionVolumes(); + } + } + } +} + +void +CModelInfo::ConstructMloClumps() +{ + for (int i = 0; i < ms_mloModelStore.allocPtr; i++) + ms_mloModelStore.store[i].ConstructClump(); +} + +void +CModelInfo::ReInit2dEffects() +{ + ms_2dEffectStore.Clear(); + + for (int i = 0; i < MODELINFOSIZE; i++) { + if (ms_modelInfoPtrs[i]) + ms_modelInfoPtrs[i]->Init2dEffects(); + } +} diff --git a/src/modelinfo/ModelInfo.h b/src/modelinfo/ModelInfo.h new file mode 100644 index 0000000..4d24e78 --- /dev/null +++ b/src/modelinfo/ModelInfo.h @@ -0,0 +1,55 @@ +#pragma once + +#include "2dEffect.h" +#include "BaseModelInfo.h" +#include "SimpleModelInfo.h" +#include "MloModelInfo.h" +#include "TimeModelInfo.h" +#include "ClumpModelInfo.h" +#include "PedModelInfo.h" +#include "VehicleModelInfo.h" +#include "XtraCompsModelInfo.h" +#include "Instance.h" +#include "Game.h" + +class CModelInfo +{ + static CBaseModelInfo *ms_modelInfoPtrs[MODELINFOSIZE]; + static CStore ms_simpleModelStore; + static CStore ms_mloModelStore; + static CStore ms_mloInstanceStore; + static CStore ms_timeModelStore; + static CStore ms_clumpModelStore; + static CStore ms_pedModelStore; + static CStore ms_vehicleModelStore; + static CStore ms_2dEffectStore; + static CStore ms_xtraCompsModelStore; + +public: + static void Initialise(void); + static void ShutDown(void); + + static CSimpleModelInfo *AddSimpleModel(int id); + static CMloModelInfo *AddMloModel(int id); + static CTimeModelInfo *AddTimeModel(int id); + static CClumpModelInfo *AddClumpModel(int id); + static CPedModelInfo *AddPedModel(int id); + static CVehicleModelInfo *AddVehicleModel(int id); + + static CStore &Get2dEffectStore(void) { return ms_2dEffectStore; } + static CStore &GetMloInstanceStore(void) { return ms_mloInstanceStore; } + + static CBaseModelInfo *GetModelInfo(const char *name, int *id); + static CBaseModelInfo *GetModelInfo(int id){ + return ms_modelInfoPtrs[id]; + } + static CColModel *GetColModel(int id){ + return ms_modelInfoPtrs[id]->GetColModel(); + } + + static bool IsBoatModel(int32 id); + static bool IsBikeModel(int32 id); + static void RemoveColModelsFromOtherLevels(eLevelName level); + static void ConstructMloClumps(); + static void ReInit2dEffects(); +}; diff --git a/src/modelinfo/PedModelInfo.cpp b/src/modelinfo/PedModelInfo.cpp new file mode 100644 index 0000000..2cce48f --- /dev/null +++ b/src/modelinfo/PedModelInfo.cpp @@ -0,0 +1,378 @@ +#include "common.h" + +#include "RwHelper.h" +#include "General.h" +#include "Bones.h" +#include "SurfaceTable.h" +#include "Ped.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "ModelInfo.h" +#include "custompipes.h" + +void +CPedModelInfo::DeleteRwObject(void) +{ + if(m_hitColModel) + delete m_hitColModel; + m_hitColModel = nil; +#ifdef PED_SKIN + RwFrame *frame; + if(m_head){ + frame = RpAtomicGetFrame(m_head); + RpAtomicDestroy(m_head); + RwFrameDestroy(frame); + m_head = nil; + } + if(m_lhand){ + frame = RpAtomicGetFrame(m_lhand); + RpAtomicDestroy(m_lhand); + RwFrameDestroy(frame); + m_lhand = nil; + } + if(m_rhand){ + frame = RpAtomicGetFrame(m_rhand); + RpAtomicDestroy(m_rhand); + RwFrameDestroy(frame); + m_rhand = nil; + } +#endif + CClumpModelInfo::DeleteRwObject(); // PC calls this first +} + +RwObjectNameIdAssocation CPedModelInfo::m_pPedIds[PED_NODE_MAX] = { + { "Smid", PED_MID, 0, }, // that is strange... + { "Shead", PED_HEAD, 0, }, + { "Supperarml", PED_UPPERARML, 0, }, + { "Supperarmr", PED_UPPERARMR, 0, }, + { "SLhand", PED_HANDL, 0, }, + { "SRhand", PED_HANDR, 0, }, + { "Supperlegl", PED_UPPERLEGL, 0, }, + { "Supperlegr", PED_UPPERLEGR, 0, }, + { "Sfootl", PED_FOOTL, 0, }, + { "Sfootr", PED_FOOTR, 0, }, + { "Slowerlegr", PED_LOWERLEGR, 0, }, + { nil, 0, 0, }, +}; + +#ifdef PED_SKIN +struct LimbCBarg +{ + CPedModelInfo *mi; + RpClump *clump; + int32 frameIDs[3]; +}; + +RpAtomic* +CPedModelInfo::findLimbsCb(RpAtomic *atomic, void *data) +{ + LimbCBarg *limbs = (LimbCBarg*)data; + RwFrame *frame = RpAtomicGetFrame(atomic); + const char *name = GetFrameNodeName(frame); + if(CGeneral::faststricmp(name, "Shead01") == 0){ + limbs->frameIDs[0] = RpHAnimFrameGetID(frame); + limbs->mi->m_head = atomic; + RpClumpRemoveAtomic(limbs->clump, atomic); + RwFrameRemoveChild(frame); + }else if(CGeneral::faststricmp(name, "SLhand01") == 0){ + limbs->frameIDs[1] = RpHAnimFrameGetID(frame); + limbs->mi->m_lhand = atomic; + RpClumpRemoveAtomic(limbs->clump, atomic); + RwFrameRemoveChild(frame); + }else if(CGeneral::faststricmp(name, "SRhand01") == 0){ + limbs->frameIDs[2] = RpHAnimFrameGetID(frame); + limbs->mi->m_rhand = atomic; + RpClumpRemoveAtomic(limbs->clump, atomic); + RwFrameRemoveChild(frame); + } + return atomic; +} +#endif + +void +CPedModelInfo::SetClump(RpClump *clump) +{ +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachRimPipe(clump); +#endif +#ifdef PED_SKIN + // CB has to be set here before atomics are detached from clump + if(strcmp(GetModelName(), "player") == 0) + RpClumpForAllAtomics(clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); + if(IsClumpSkinned(clump)){ + LimbCBarg limbs = { this, clump, { 0, 0, 0 } }; + RpClumpForAllAtomics(clump, findLimbsCb, &limbs); + } + CClumpModelInfo::SetClump(clump); + SetFrameIds(m_pPedIds); + if(m_hitColModel == nil && !IsClumpSkinned(clump)) + CreateHitColModel(); + // And again because CClumpModelInfo resets it + if(strcmp(GetModelName(), "player") == 0) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); + else if(IsClumpSkinned(clump)) + // skinned peds have no low detail version, so they don't have the right render Cb + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedCB); +#else + CClumpModelInfo::SetClump(clump); + SetFrameIds(m_pPedIds); + if(m_hitColModel == nil) + CreateHitColModel(); + if(strcmp(GetModelName(), "player") == 0) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); +#endif +} + +RpAtomic* +CountAtomicsCB(RpAtomic *atomic, void *data) +{ + (*(int32*)data)++; + return atomic; +} + +RpAtomic* +GetAtomicListCB(RpAtomic *atomic, void *data) +{ + **(RpAtomic***)data = atomic; + (*(RpAtomic***)data)++; + return atomic; +} + +RwFrame* +FindPedFrameFromNameCB(RwFrame *frame, void *data) +{ + RwObjectNameAssociation *assoc = (RwObjectNameAssociation*)data; + + if(CGeneral::faststricmp(GetFrameNodeName(frame)+1, assoc->name+1)){ + RwFrameForAllChildren(frame, FindPedFrameFromNameCB, assoc); + return assoc->frame ? nil : frame; + }else{ + assoc->frame = frame; + return nil; + } +} + +void +CPedModelInfo::SetLowDetailClump(RpClump *lodclump) +{ + RpAtomic *atomics[16]; + RpAtomic **pAtm; + int32 numAtm, numLodAtm; + int i; + RwObjectNameAssociation assoc; + + numAtm = 0; + numLodAtm = 0; + RpClumpForAllAtomics(m_clump, CountAtomicsCB, &numAtm); // actually unused + RpClumpForAllAtomics(lodclump, CountAtomicsCB, &numLodAtm); + + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedHiDetailCB); + RpClumpForAllAtomics(lodclump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedLowDetailCB); + + pAtm = atomics; + RpClumpForAllAtomics(lodclump, GetAtomicListCB, &pAtm); + + for(i = 0; i < numLodAtm; i++){ + assoc.name = GetFrameNodeName(RpAtomicGetFrame(atomics[i])); + assoc.frame = nil; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), FindPedFrameFromNameCB, &assoc); + if(assoc.frame){ + RpAtomicSetFrame(atomics[i], assoc.frame); + RpClumpRemoveAtomic(lodclump, atomics[i]); + RpClumpAddAtomic(m_clump, atomics[i]); + } + } +} + +struct ColNodeInfo +{ + Const char *name; + int pedNode; + int pieceType; + float x, z; + float radius; +}; + +#define NUMPEDINFONODES 8 +ColNodeInfo m_pColNodeInfos[NUMPEDINFONODES] = { + { nil, PED_HEAD, PEDPIECE_HEAD, 0.0f, 0.05f, 0.2f }, + { "Storso", 0, PEDPIECE_TORSO, 0.0f, 0.15f, 0.2f }, + { "Storso", 0, PEDPIECE_TORSO, 0.0f, -0.05f, 0.3f }, + { nil, PED_MID, PEDPIECE_MID, 0.0f, -0.07f, 0.3f }, + { nil, PED_UPPERARML, PEDPIECE_LEFTARM, 0.07f, -0.1f, 0.2f }, + { nil, PED_UPPERARMR, PEDPIECE_RIGHTARM, -0.07f, -0.1f, 0.2f }, + { "Slowerlegl", 0, PEDPIECE_LEFTLEG, 0.0f, 0.07f, 0.25f }, + { nil, PED_LOWERLEGR, PEDPIECE_RIGHTLEG, 0.0f, 0.07f, 0.25f }, +}; + +RwObject* +FindHeadRadiusCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + *(float*)data = RpAtomicGetBoundingSphere(atomic)->radius; + return nil; +} + +void +CPedModelInfo::CreateHitColModel(void) +{ + RwObjectNameAssociation nameAssoc; + RwObjectIdAssociation idAssoc; + RwFrame *nodeFrame; + CColModel *colmodel = new CColModel; + CColSphere *spheres = (CColSphere*)RwMalloc(NUMPEDINFONODES*sizeof(CColSphere)); + RwFrame *root = RpClumpGetFrame(m_clump); + RwMatrix *mat = RwMatrixCreate(); + for(int i = 0; i < NUMPEDINFONODES; i++){ + nodeFrame = nil; + if(m_pColNodeInfos[i].name){ + nameAssoc.name = m_pColNodeInfos[i].name; + nameAssoc.frame = nil; + RwFrameForAllChildren(root, FindFrameFromNameCB, &nameAssoc); + nodeFrame = nameAssoc.frame; + }else{ + idAssoc.id = m_pColNodeInfos[i].pedNode; + idAssoc.frame = nil; + RwFrameForAllChildren(root, FindFrameFromIdCB, &idAssoc); + nodeFrame = idAssoc.frame; + } + if(nodeFrame){ + float radius = m_pColNodeInfos[i].radius; + if(m_pColNodeInfos[i].pieceType == PEDPIECE_HEAD) + RwFrameForAllObjects(nodeFrame, FindHeadRadiusCB, &radius); + RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEREPLACE); + const char *name = GetFrameNodeName(nodeFrame); + for(nodeFrame = RwFrameGetParent(nodeFrame); + nodeFrame; + nodeFrame = RwFrameGetParent(nodeFrame)){ + name = GetFrameNodeName(nodeFrame); + RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEPOSTCONCAT); + if(RwFrameGetParent(nodeFrame) == root) + break; + } + spheres[i].center = mat->pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); + spheres[i].radius = radius; + spheres[i].surface = SURFACE_PED; + spheres[i].piece = m_pColNodeInfos[i].pieceType; + } + } + RwMatrixDestroy(mat); + colmodel->spheres = spheres; + colmodel->numSpheres = NUMPEDINFONODES; + colmodel->boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f), SURFACE_DEFAULT, 0); + colmodel->boundingBox.Set(CVector(-0.5f, -0.5f, -1.2f), CVector(0.5f, 0.5f, 1.2f), SURFACE_DEFAULT, 0); + colmodel->level = LEVEL_GENERIC; + m_hitColModel = colmodel; +} + +CColModel* +CPedModelInfo::AnimatePedColModel(CColModel* colmodel, RwFrame* frame) +{ + RwObjectNameAssociation nameAssoc; + RwObjectIdAssociation idAssoc; + RwMatrix* mat = RwMatrixCreate(); + CColSphere* spheres = colmodel->spheres; + + for (int i = 0; i < NUMPEDINFONODES; i++) { + RwFrame* f = nil; + if (m_pColNodeInfos[i].name) { + nameAssoc.name = m_pColNodeInfos[i].name; + nameAssoc.frame = nil; + RwFrameForAllChildren(frame, FindFrameFromNameCB, &nameAssoc); + f = nameAssoc.frame; + } + else { + idAssoc.id = m_pColNodeInfos[i].pedNode; + idAssoc.frame = nil; + RwFrameForAllChildren(frame, FindFrameFromIdCB, &idAssoc); + f = idAssoc.frame; + } + if (f) { + RwMatrixCopy(mat, RwFrameGetMatrix(f)); + + for (f = RwFrameGetParent(f); f; f = RwFrameGetParent(f)) { + RwMatrixTransform(mat, RwFrameGetMatrix(f), rwCOMBINEPOSTCONCAT); + if (RwFrameGetParent(f) == frame) + break; + } + + spheres[i].center = mat->pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); + } + } + + return colmodel; +} + +#ifdef PED_SKIN +void +CPedModelInfo::CreateHitColModelSkinned(RpClump *clump) +{ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump); + CColModel *colmodel = new CColModel; + CColSphere *spheres = (CColSphere*)RwMalloc(NUMPEDINFONODES*sizeof(CColSphere)); + RwFrame *root = RpClumpGetFrame(m_clump); + RwMatrix *invmat = RwMatrixCreate(); + RwMatrix *mat = RwMatrixCreate(); + RwMatrixInvert(invmat, RwFrameGetMatrix(RpClumpGetFrame(clump))); + + for(int i = 0; i < NUMPEDINFONODES; i++){ + *mat = *invmat; + // From LCS. Otherwise gives FPE +#ifdef FIX_BUGS + spheres[i].center = CVector(0.0f, 0.0f, 0.0f); +#else + int id = ConvertPedNode2BoneTag(m_pColNodeInfos[i].pedNode); // this is wrong, wtf R* ??? + int idx = RpHAnimIDGetIndex(hier, id); + + // This doesn't really work as the positions are not initialized yet + RwMatrixTransform(mat, &RpHAnimHierarchyGetMatrixArray(hier)[idx], rwCOMBINEPRECONCAT); + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + RwV3dTransformPoints(&pos, &pos, 1, mat); + + spheres[i].center = pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); +#endif + spheres[i].radius = m_pColNodeInfos[i].radius; + spheres[i].surface = SURFACE_PED; + spheres[i].piece = m_pColNodeInfos[i].pieceType; + } + RwMatrixDestroy(invmat); + RwMatrixDestroy(mat); + colmodel->spheres = spheres; + colmodel->numSpheres = NUMPEDINFONODES; + colmodel->boundingSphere.Set(2.0f, CVector(0.0f, 0.0f, 0.0f), SURFACE_DEFAULT, 0); + colmodel->boundingBox.Set(CVector(-0.5f, -0.5f, -1.2f), CVector(0.5f, 0.5f, 1.2f), SURFACE_DEFAULT, 0); + colmodel->level = LEVEL_GENERIC; + m_hitColModel = colmodel; +} + +CColModel* +CPedModelInfo::AnimatePedColModelSkinned(RpClump *clump) +{ + if(m_hitColModel == nil){ + CreateHitColModelSkinned(clump); + return m_hitColModel; + } + RwMatrix *invmat, *mat; + CColSphere *spheres = m_hitColModel->spheres; + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump); + invmat = RwMatrixCreate(); + mat = RwMatrixCreate(); + RwMatrixInvert(invmat, RwFrameGetMatrix(RpClumpGetFrame(clump))); + + for(int i = 0; i < NUMPEDINFONODES; i++){ + *mat = *invmat; + int id = ConvertPedNode2BoneTag(m_pColNodeInfos[i].pedNode); + int idx = RpHAnimIDGetIndex(hier, id); + + RwMatrixTransform(mat, &RpHAnimHierarchyGetMatrixArray(hier)[idx], rwCOMBINEPRECONCAT); + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + RwV3dTransformPoints(&pos, &pos, 1, mat); + + spheres[i].center = pos + CVector(m_pColNodeInfos[i].x, 0.0f, m_pColNodeInfos[i].z); + } + RwMatrixDestroy(invmat); + RwMatrixDestroy(mat); + return m_hitColModel; +} + +#endif diff --git a/src/modelinfo/PedModelInfo.h b/src/modelinfo/PedModelInfo.h new file mode 100644 index 0000000..26ab3c3 --- /dev/null +++ b/src/modelinfo/PedModelInfo.h @@ -0,0 +1,67 @@ +#pragma once + +#include "ClumpModelInfo.h" +#include "ColModel.h" +#include "PedType.h" + +enum PedNode { + PED_TORSO, + PED_MID, // Smid on PS2/PC, Storso on mobile/xbox + PED_HEAD, + PED_UPPERARML, + PED_UPPERARMR, + PED_HANDL, + PED_HANDR, + PED_UPPERLEGL, + PED_UPPERLEGR, + PED_FOOTL, + PED_FOOTR, + PED_LOWERLEGR, + PED_NODE_MAX// Not valid: PED_LOWERLEGL +}; + +class CPedModelInfo : public CClumpModelInfo +{ +public: + uint32 m_animGroup; + ePedType m_pedType; + ePedStats m_pedStatType; + uint32 m_carsCanDrive; + CColModel *m_hitColModel; +#ifdef PED_SKIN + RpAtomic *m_head; + RpAtomic *m_lhand; + RpAtomic *m_rhand; +#endif + + static RwObjectNameIdAssocation m_pPedIds[PED_NODE_MAX]; + + CPedModelInfo(void) : CClumpModelInfo(MITYPE_PED) { + m_hitColModel = nil; +#ifdef PED_SKIN + m_head = nil; + m_lhand = nil; + m_rhand = nil; +#endif + } + ~CPedModelInfo(void) { delete m_hitColModel; } + void DeleteRwObject(void); + void SetClump(RpClump *); + + void SetLowDetailClump(RpClump*); + void CreateHitColModel(void); + void CreateHitColModelSkinned(RpClump *clump); + CColModel *GetHitColModel(void) { return m_hitColModel; } + static CColModel *AnimatePedColModel(CColModel* colmodel, RwFrame* frame); + CColModel *AnimatePedColModelSkinned(RpClump *clump); + +#ifdef PED_SKIN + static RpAtomic *findLimbsCb(RpAtomic *atomic, void *data); + RpAtomic *getHead(void) { return m_head; } + RpAtomic *getLeftHand(void) { return m_lhand; } + RpAtomic *getRightHand(void) { return m_rhand; } +#endif +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CPedModelInfo, 0x48); +#endif \ No newline at end of file diff --git a/src/modelinfo/SimpleModelInfo.cpp b/src/modelinfo/SimpleModelInfo.cpp new file mode 100644 index 0000000..9fc0dd6 --- /dev/null +++ b/src/modelinfo/SimpleModelInfo.cpp @@ -0,0 +1,173 @@ +#include "common.h" + +#include "General.h" +#include "Camera.h" +#include "Renderer.h" +#include "ModelInfo.h" +#include "custompipes.h" + +void +CSimpleModelInfo::DeleteRwObject(void) +{ + int i; + RwFrame *f; + for(i = 0; i < m_numAtomics; i++) + if(m_atomics[i]){ + f = RpAtomicGetFrame(m_atomics[i]); + RpAtomicDestroy(m_atomics[i]); + RwFrameDestroy(f); + m_atomics[i] = nil; + RemoveTexDictionaryRef(); + } +} + +RwObject* +CSimpleModelInfo::CreateInstance(void) +{ + RpAtomic *atomic; + if(m_atomics[0] == nil) + return nil; + atomic = RpAtomicClone(m_atomics[0]); + RpAtomicSetFrame(atomic, RwFrameCreate()); + return (RwObject*)atomic; +} + +RwObject* +CSimpleModelInfo::CreateInstance(RwMatrix *matrix) +{ + RpAtomic *atomic; + RwFrame *frame; + + if(m_atomics[0] == nil) + return nil; + atomic = RpAtomicClone(m_atomics[0]); + frame = RwFrameCreate(); + *RwFrameGetMatrix(frame) = *matrix; + RpAtomicSetFrame(atomic, frame); + return (RwObject*)atomic; +} + +void +CSimpleModelInfo::Init(void) +{ + m_atomics[0] = nil; + m_atomics[1] = nil; + m_atomics[2] = nil; + m_numAtomics = 0; + m_firstDamaged = 0; + m_normalCull = 0; + m_isDamaged = 0; + m_isBigBuilding = 0; + m_noFade = 0; + m_drawLast = 0; + m_additive = 0; + m_isSubway = 0; + m_ignoreLight = 0; + m_noZwrite = 0; +} + +void +CSimpleModelInfo::SetAtomic(int n, RpAtomic *atomic) +{ + AddTexDictionaryRef(); + m_atomics[n] = atomic; + if(m_ignoreLight){ + RpGeometry *geo = RpAtomicGetGeometry(atomic); + RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) & ~rpGEOMETRYLIGHT); + } + +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachWorldPipe(atomic); +#endif +} + +void +CSimpleModelInfo::SetLodDistances(float *dist) +{ + m_lodDistances[0] = dist[0]; + m_lodDistances[1] = dist[1]; + m_lodDistances[2] = dist[2]; +} + +void +CSimpleModelInfo::IncreaseAlpha(void) +{ + if(m_alpha >= 0xEF) + m_alpha = 0xFF; + else + m_alpha += 0x10; +} + +float +CSimpleModelInfo::GetLodDistance(int i) +{ + return m_lodDistances[i] * TheCamera.LODDistMultiplier; +} + +float +CSimpleModelInfo::GetNearDistance(void) +{ + return m_lodDistances[2] * TheCamera.LODDistMultiplier; +} + +float +CSimpleModelInfo::GetLargestLodDistance(void) +{ + float d; + if(m_firstDamaged == 0 || m_isDamaged) + d = m_lodDistances[m_numAtomics-1]; + else + d = m_lodDistances[m_firstDamaged-1]; + return d * TheCamera.LODDistMultiplier; +} + +RpAtomic* +CSimpleModelInfo::GetAtomicFromDistance(float dist) +{ + int i; + i = 0; + if(m_isDamaged) + i = m_firstDamaged; + for(; i < m_numAtomics; i++) + if(dist < m_lodDistances[i] * TheCamera.LODDistMultiplier) + return m_atomics[i]; + return nil; +} + +void +CSimpleModelInfo::FindRelatedModel(void) +{ + int i; + CBaseModelInfo *mi; + for(i = 0; i < MODELINFOSIZE; i++){ + mi = CModelInfo::GetModelInfo(i); + if(mi && mi != this && + !CGeneral::faststrcmp(GetModelName()+3, mi->GetModelName()+3)){ + assert(mi->IsSimple()); + this->SetRelatedModel((CSimpleModelInfo*)mi); + return; + } + } +} + +void +CSimpleModelInfo::SetupBigBuilding(void) +{ + CSimpleModelInfo *related; + if(m_lodDistances[0] > LOD_DISTANCE && GetRelatedModel() == nil){ + m_isBigBuilding = 1; + FindRelatedModel(); + related = GetRelatedModel(); + if(related) + m_lodDistances[2] = related->GetLargestLodDistance()/TheCamera.LODDistMultiplier; + else +#ifdef FIX_BUGS + if(toupper(m_name[0]) == 'L' && toupper(m_name[1]) == 'O' && toupper(m_name[2]) == 'D') + m_lodDistances[2] = 100.0f; + else + m_lodDistances[2] = 0.0f; +#else + m_lodDistances[2] = 100.0f; +#endif + } +} diff --git a/src/modelinfo/SimpleModelInfo.h b/src/modelinfo/SimpleModelInfo.h new file mode 100644 index 0000000..94e55a2 --- /dev/null +++ b/src/modelinfo/SimpleModelInfo.h @@ -0,0 +1,65 @@ +#pragma once + +#include "BaseModelInfo.h" + +class CSimpleModelInfo : public CBaseModelInfo +{ +public: + // atomics[2] is often a pointer to the non-LOD modelinfo + RpAtomic *m_atomics[3]; + // m_lodDistances[2] holds the near distance for LODs + float m_lodDistances[3]; + uint8 m_numAtomics; + uint8 m_alpha; + /* // For reference, PS2 has: + uint8 m_firstDamaged; + uint8 m_normalCull : 1; + uint8 m_isDamaged : 1; + uint8 m_isBigBuilding : 1; + uint8 m_noFade : 1; + uint8 m_drawLast : 1; + uint8 m_additive : 1; + uint8 m_isSubway : 1; + uint8 m_ignoreLight : 1; + // m_noZwrite is missing because not needed + */ + uint16 m_firstDamaged : 2; // 0: no damage model + // 1: 1 and 2 are damage models + // 2: 2 is damage model + uint16 m_normalCull : 1; + uint16 m_isDamaged : 1; + uint16 m_isBigBuilding : 1; + uint16 m_noFade : 1; + uint16 m_drawLast : 1; + uint16 m_additive : 1; + uint16 m_isSubway : 1; + uint16 m_ignoreLight : 1; + uint16 m_noZwrite : 1; + + CSimpleModelInfo(void) : CBaseModelInfo(MITYPE_SIMPLE) {} + CSimpleModelInfo(ModelInfoType id) : CBaseModelInfo(id) {} + ~CSimpleModelInfo() {} + void DeleteRwObject(void); + RwObject *CreateInstance(void); + RwObject *CreateInstance(RwMatrix *); + RwObject *GetRwObject(void) { return (RwObject*)m_atomics[0]; } + + void Init(void); + void IncreaseAlpha(void); + void SetAtomic(int n, RpAtomic *atomic); + void SetLodDistances(float *dist); + float GetLodDistance(int i); + float GetNearDistance(void); + float GetLargestLodDistance(void); + RpAtomic *GetAtomicFromDistance(float dist); + void FindRelatedModel(void); + void SetupBigBuilding(void); + + void SetNumAtomics(int n) { m_numAtomics = n; } + CSimpleModelInfo *GetRelatedModel(void){ + return (CSimpleModelInfo*)m_atomics[2]; } + void SetRelatedModel(CSimpleModelInfo *m){ + m_atomics[2] = (RpAtomic*)m; } +}; + +VALIDATE_SIZE(CSimpleModelInfo, 0x4C); diff --git a/src/modelinfo/TimeModelInfo.cpp b/src/modelinfo/TimeModelInfo.cpp new file mode 100644 index 0000000..0db5fb7 --- /dev/null +++ b/src/modelinfo/TimeModelInfo.cpp @@ -0,0 +1,33 @@ +#include "common.h" + +#include "Camera.h" +#include "ModelInfo.h" +#include "General.h" + +CTimeModelInfo* +CTimeModelInfo::FindOtherTimeModel(void) +{ + char name[40]; + char *p; + int i; + + strcpy(name, GetModelName()); + // change _nt to _dy + if(p = strstr(name, "_nt")) + strncpy(p, "_dy", 4); + // change _dy to _nt + else if(p = strstr(name, "_dy")) + strncpy(p, "_nt", 4); + else + return nil; + + for(i = 0; i < MODELINFOSIZE; i++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(i); + if (mi && mi->GetModelType() == MITYPE_TIME && + !CGeneral::faststrncmp(name, mi->GetModelName(), MAX_MODEL_NAME)){ + m_otherTimeModelID = i; + return (CTimeModelInfo*)mi; + } + } + return nil; +} diff --git a/src/modelinfo/TimeModelInfo.h b/src/modelinfo/TimeModelInfo.h new file mode 100644 index 0000000..73b6ab2 --- /dev/null +++ b/src/modelinfo/TimeModelInfo.h @@ -0,0 +1,21 @@ +#pragma once + +#include "SimpleModelInfo.h" + +class CTimeModelInfo : public CSimpleModelInfo +{ + int32 m_timeOn; + int32 m_timeOff; + int32 m_otherTimeModelID; +public: + CTimeModelInfo(void) : CSimpleModelInfo(MITYPE_TIME) { m_otherTimeModelID = -1; } + + int32 GetTimeOn(void) { return m_timeOn; } + int32 GetTimeOff(void) { return m_timeOff; } + void SetTimes(int32 on, int32 off) { m_timeOn = on; m_timeOff = off; } + int32 GetOtherTimeModel(void) { return m_otherTimeModelID; } + void SetOtherTimeModel(int32 other) { m_otherTimeModelID = other; } + CTimeModelInfo *FindOtherTimeModel(void); +}; + +VALIDATE_SIZE(CTimeModelInfo, 0x58); diff --git a/src/modelinfo/VehicleModelInfo.cpp b/src/modelinfo/VehicleModelInfo.cpp new file mode 100644 index 0000000..685b6ef --- /dev/null +++ b/src/modelinfo/VehicleModelInfo.cpp @@ -0,0 +1,1121 @@ +#include "common.h" +#include + +#include "RwHelper.h" +#include "General.h" +#include "NodeName.h" +#include "TxdStore.h" +#include "Weather.h" +#include "HandlingMgr.h" +#include "VisibilityPlugins.h" +#include "FileMgr.h" +#include "World.h" +#include "Vehicle.h" +#include "Automobile.h" +#include "Boat.h" +#include "Train.h" +#include "Plane.h" +#include "Heli.h" +#include "Bike.h" +#include "ModelIndices.h" +#include "ModelInfo.h" +#include "custompipes.h" + +int8 CVehicleModelInfo::ms_compsToUse[2] = { -2, -2 }; +int8 CVehicleModelInfo::ms_compsUsed[2]; +RwTexture *CVehicleModelInfo::ms_pEnvironmentMaps[NUM_VEHICLE_ENVMAPS]; +RwRGBA CVehicleModelInfo::ms_vehicleColourTable[256]; +RwTexture *CVehicleModelInfo::ms_colourTextureTable[256]; + +RwTexture *gpWhiteTexture; +RwFrame *pMatFxIdentityFrame; + +enum { + VEHICLE_FLAG_COLLAPSE = 0x2, + VEHICLE_FLAG_ADD_WHEEL = 0x4, + VEHICLE_FLAG_POS = 0x8, + VEHICLE_FLAG_DOOR = 0x10, + VEHICLE_FLAG_LEFT = 0x20, + VEHICLE_FLAG_RIGHT = 0x40, + VEHICLE_FLAG_FRONT = 0x80, + VEHICLE_FLAG_REAR = 0x100, + VEHICLE_FLAG_COMP = 0x200, + VEHICLE_FLAG_DRAWLAST = 0x400, + VEHICLE_FLAG_WINDSCREEN = 0x800, + VEHICLE_FLAG_ANGLECULL = 0x1000, + VEHICLE_FLAG_REARDOOR = 0x2000, + VEHICLE_FLAG_FRONTDOOR = 0x4000, +}; + +RwObjectNameIdAssocation carIds[] = { + { "wheel_rf_dummy", CAR_WHEEL_RF, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_rm_dummy", CAR_WHEEL_RM, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_rb_dummy", CAR_WHEEL_RB, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_lf_dummy", CAR_WHEEL_LF, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_lm_dummy", CAR_WHEEL_LM, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL }, + { "wheel_lb_dummy", CAR_WHEEL_LB, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_ADD_WHEEL }, + { "bump_front_dummy", CAR_BUMP_FRONT, VEHICLE_FLAG_FRONT | VEHICLE_FLAG_COLLAPSE }, + { "bonnet_dummy", CAR_BONNET, VEHICLE_FLAG_COLLAPSE }, + { "wing_rf_dummy", CAR_WING_RF, VEHICLE_FLAG_COLLAPSE }, + { "wing_rr_dummy", CAR_WING_RR, VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_COLLAPSE }, + { "door_rf_dummy", CAR_DOOR_RF, VEHICLE_FLAG_FRONTDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "door_rr_dummy", CAR_DOOR_RR, VEHICLE_FLAG_REARDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_REAR | VEHICLE_FLAG_RIGHT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "wing_lf_dummy", CAR_WING_LF, VEHICLE_FLAG_COLLAPSE }, + { "wing_lr_dummy", CAR_WING_LR, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE }, + { "door_lf_dummy", CAR_DOOR_LF, VEHICLE_FLAG_FRONTDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_LEFT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "door_lr_dummy", CAR_DOOR_LR, VEHICLE_FLAG_REARDOOR | VEHICLE_FLAG_ANGLECULL | VEHICLE_FLAG_REAR | VEHICLE_FLAG_LEFT | VEHICLE_FLAG_DOOR | VEHICLE_FLAG_COLLAPSE }, + { "boot_dummy", CAR_BOOT, VEHICLE_FLAG_REAR | VEHICLE_FLAG_COLLAPSE }, + { "bump_rear_dummy", CAR_BUMP_REAR, VEHICLE_FLAG_REAR | VEHICLE_FLAG_COLLAPSE }, + { "windscreen_dummy", CAR_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_FRONT | VEHICLE_FLAG_COLLAPSE }, + + { "ped_frontseat", CAR_POS_FRONTSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_backseat", CAR_POS_BACKSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "headlights", CAR_POS_HEADLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "taillights", CAR_POS_TAILLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "exhaust", CAR_POS_EXHAUST, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "extra1", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra2", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra3", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra4", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra5", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra6", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation boatIds[] = { + { "boat_moving_hi", BOAT_MOVING, VEHICLE_FLAG_COLLAPSE }, + { "boat_rudder_hi", BOAT_RUDDER, VEHICLE_FLAG_COLLAPSE }, + { "windscreen", BOAT_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_COLLAPSE }, + { "ped_frontseat", BOAT_POS_FRONTSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation trainIds[] = { + { "door_lhs_dummy", TRAIN_DOOR_LHS, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE }, + { "door_rhs_dummy", TRAIN_DOOR_RHS, VEHICLE_FLAG_LEFT | VEHICLE_FLAG_COLLAPSE }, + { "light_front", TRAIN_POS_LIGHT_FRONT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "light_rear", TRAIN_POS_LIGHT_REAR, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_left_entry", TRAIN_POS_LEFT_ENTRY, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_mid_entry", TRAIN_POS_MID_ENTRY, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "ped_right_entry", TRAIN_POS_RIGHT_ENTRY, VEHICLE_FLAG_DOOR | VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation heliIds[] = { + { "chassis_dummy", HELI_CHASSIS, VEHICLE_FLAG_COLLAPSE }, + { "toprotor", HELI_TOPROTOR, 0 }, + { "backrotor", HELI_BACKROTOR, 0 }, + { "tail", HELI_TAIL, 0 }, + { "topknot", HELI_TOPKNOT, 0 }, + { "skid_left", HELI_SKID_LEFT, 0 }, + { "skid_right", HELI_SKID_RIGHT, 0 }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation planeIds[] = { + { "wheel_front_dummy", PLANE_WHEEL_FRONT, 0 }, + { "wheel_rear_dummy", PLANE_WHEEL_READ, 0 }, + { "light_tailplane", PLANE_POS_LIGHT_TAIL, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "light_left", PLANE_POS_LIGHT_LEFT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "light_right", PLANE_POS_LIGHT_RIGHT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation bikeIds[] = { + { "chassis_dummy", BIKE_CHASSIS, 0 }, + { "forks_front", BIKE_FORKS_FRONT, 0 }, + { "forks_rear", BIKE_FORKS_REAR, 0 }, + { "wheel_front", BIKE_WHEEL_FRONT, 0 }, + { "wheel_rear", BIKE_WHEEL_REAR, 0 }, + { "mudguard", BIKE_MUDGUARD, 0 }, + { "ped_frontseat", CAR_POS_FRONTSEAT, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "headlights", CAR_POS_HEADLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "taillights", CAR_POS_TAILLIGHTS, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "exhaust", CAR_POS_EXHAUST, VEHICLE_FLAG_POS | CLUMP_FLAG_NO_HIERID }, + { "extra1", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra2", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra3", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra4", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra5", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { "extra6", 0, VEHICLE_FLAG_DRAWLAST | VEHICLE_FLAG_COMP | CLUMP_FLAG_NO_HIERID }, + { nil, 0, 0 } +}; + +RwObjectNameIdAssocation *CVehicleModelInfo::ms_vehicleDescs[] = { + carIds, + boatIds, + trainIds, + heliIds, + planeIds, + bikeIds +}; + + +CVehicleModelInfo::CVehicleModelInfo(void) + : CClumpModelInfo(MITYPE_VEHICLE) +{ + int32 i; + for(i = 0; i < NUM_VEHICLE_POSITIONS; i++){ + m_positions[i].x = 0.0f; + m_positions[i].y = 0.0f; + m_positions[i].z = 0.0f; + } + m_numColours = 0; +} + +void +CVehicleModelInfo::DeleteRwObject(void) +{ + int32 i; + RwFrame *f; + + for(i = 0; i < m_numComps; i++){ + f = RpAtomicGetFrame(m_comps[i]); + RpAtomicDestroy(m_comps[i]); + RwFrameDestroy(f); + } + m_numComps = 0; + CClumpModelInfo::DeleteRwObject(); +} + +RwObject* +CVehicleModelInfo::CreateInstance(void) +{ + RpClump *clump; + RpAtomic *atomic; + RwFrame *clumpframe, *f; + int32 comp1, comp2; + + clump = (RpClump*)CClumpModelInfo::CreateInstance(); + if(m_numComps != 0){ + clumpframe = RpClumpGetFrame(clump); + + comp1 = ChooseComponent(); + if(comp1 != -1){ + atomic = RpAtomicClone(m_comps[comp1]); + f = RwFrameCreate(); + RwFrameTransform(f, + RwFrameGetMatrix(RpAtomicGetFrame(m_comps[comp1])), + rwCOMBINEREPLACE); + RpAtomicSetFrame(atomic, f); + RpClumpAddAtomic(clump, atomic); + RwFrameAddChild(clumpframe, f); + } + ms_compsUsed[0] = comp1; + + comp2 = ChooseSecondComponent(); + if(comp2 != -1){ + atomic = RpAtomicClone(m_comps[comp2]); + f = RwFrameCreate(); + RwFrameTransform(f, + RwFrameGetMatrix(RpAtomicGetFrame(m_comps[comp2])), + rwCOMBINEREPLACE); + RpAtomicSetFrame(atomic, f); + RpClumpAddAtomic(clump, atomic); + RwFrameAddChild(clumpframe, f); + } + ms_compsUsed[1] = comp2; + }else{ + ms_compsUsed[0] = -1; + ms_compsUsed[1] = -1; + } + return (RwObject*)clump; +} + +void +CVehicleModelInfo::SetClump(RpClump *clump) +{ + CClumpModelInfo::SetClump(clump); + SetAtomicRenderCallbacks(); + SetFrameIds(ms_vehicleDescs[m_vehicleType]); + PreprocessHierarchy(); + FindEditableMaterialList(); + m_envMap = nil; + SetEnvironmentMap(); +} + +RwFrame* +CVehicleModelInfo::CollapseFramesCB(RwFrame *frame, void *data) +{ + RwFrameForAllChildren(frame, CollapseFramesCB, data); + RwFrameForAllObjects(frame, MoveObjectsCB, data); + RwFrameDestroy(frame); + return frame; +} + +RwObject* +CVehicleModelInfo::MoveObjectsCB(RwObject *object, void *data) +{ + RpAtomicSetFrame((RpAtomic*)object, (RwFrame*)data); + return object; +} + +RpAtomic* +CVehicleModelInfo::HideDamagedAtomicCB(RpAtomic *atomic, void *data) +{ + if(strstr(GetFrameNodeName(RpAtomicGetFrame(atomic)), "_dam")){ + RpAtomicSetFlags(atomic, 0); + CVisibilityPlugins::SetAtomicFlag(atomic, ATOMIC_FLAG_DAM); + }else if(strstr(GetFrameNodeName(RpAtomicGetFrame(atomic)), "_ok")) + CVisibilityPlugins::SetAtomicFlag(atomic, ATOMIC_FLAG_OK); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::HideAllComponentsAtomicCB(RpAtomic *atomic, void *data) +{ + if(CVisibilityPlugins::GetAtomicId(atomic) & (uintptr)data) + RpAtomicSetFlags(atomic, 0); + else + RpAtomicSetFlags(atomic, rpATOMICRENDER); + return atomic; +} + +RpMaterial* +CVehicleModelInfo::HasAlphaMaterialCB(RpMaterial *material, void *data) +{ + if(RpMaterialGetColor(material)->alpha != 0xFF){ + *(bool*)data = true; + return nil; + } + return material; +} + + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB(RpAtomic *atomic, void *data) +{ + RpClump *clump; + char *name; + bool alpha; + + clump = (RpClump*)data; + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strstr(name, "_hi") || !CGeneral::faststrncmp(name, "extra", 5)) { + if(alpha || strncmp(name, "windscreen", 10) == 0) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB); + }else if(strstr(name, "_lo")){ + RpClumpRemoveAtomic(clump, atomic); + RpAtomicDestroy(atomic); + return atomic; // BUG: not done by gta + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_BigVehicle(RpAtomic *atomic, void *data) +{ + char *name; + bool alpha; + + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strstr(name, "_hi") || !CGeneral::faststrncmp(name, "extra", 5)) { + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle); + }else if(strstr(name, "_lo")){ + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle); + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Train(RpAtomic *atomic, void *data) +{ + char *name; + bool alpha; + + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strstr(name, "_hi")){ + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderTrainHiDetailAlphaCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderTrainHiDetailCB); + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Boat(RpAtomic *atomic, void *data) +{ + RpClump *clump; + char *name; + + clump = (RpClump*)data; + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + if(strcmp(name, "boat_hi") == 0 || !CGeneral::faststrncmp(name, "extra", 5)) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB_Boat); + else if(strstr(name, "_hi")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB); + else if(strstr(name, "_lo")){ + RpClumpRemoveAtomic(clump, atomic); + RpAtomicDestroy(atomic); + return atomic; // BUG: not done by gta + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Heli(RpAtomic *atomic, void *data) +{ + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + return atomic; +} + +void +CVehicleModelInfo::SetAtomicRenderCallbacks(void) +{ + switch(m_vehicleType){ + case VEHICLE_TYPE_TRAIN: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Train, nil); + break; + case VEHICLE_TYPE_HELI: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Heli, nil); + break; + case VEHICLE_TYPE_PLANE: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_BigVehicle, nil); + break; + case VEHICLE_TYPE_BOAT: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Boat, m_clump); + break; + default: + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, m_clump); + break; + } +} + +RwObject* +CVehicleModelInfo::SetAtomicFlagCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + CVisibilityPlugins::SetAtomicFlag(atomic, (uintptr)data); + return object; +} + +RwObject* +CVehicleModelInfo::ClearAtomicFlagCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + CVisibilityPlugins::ClearAtomicFlag(atomic, (uintptr)data); + return object; +} + +RwObject* +GetOkAndDamagedAtomicCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + if(CVisibilityPlugins::GetAtomicId(atomic) & ATOMIC_FLAG_OK) + ((RpAtomic**)data)[0] = atomic; + else if(CVisibilityPlugins::GetAtomicId(atomic) & ATOMIC_FLAG_DAM) + ((RpAtomic**)data)[1] = atomic; + return object; +} + +void +CVehicleModelInfo::PreprocessHierarchy(void) +{ + int32 i; + RwObjectNameIdAssocation *desc; + RwFrame *f; + RpAtomic *atomic; + RwV3d *rwvec; + + desc = ms_vehicleDescs[m_vehicleType]; + m_numDoors = 0; + m_numComps = 0; + + for(i = 0; desc[i].name; i++){ + RwObjectNameAssociation assoc; + + if((desc[i].flags & (VEHICLE_FLAG_COMP|VEHICLE_FLAG_POS)) == 0) + continue; + assoc.frame = nil; + assoc.name = desc[i].name; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), + FindFrameFromNameWithoutIdCB, &assoc); + if(assoc.frame == nil) + continue; + + if(desc[i].flags & VEHICLE_FLAG_DOOR) + m_numDoors++; + + if(desc[i].flags & VEHICLE_FLAG_POS){ + f = assoc.frame; + rwvec = &m_positions[desc[i].hierId]; + *rwvec = *RwMatrixGetPos(RwFrameGetMatrix(f)); + for(f = RwFrameGetParent(f); f; f = RwFrameGetParent(f)) + RwV3dTransformPoints(rwvec, rwvec, 1, RwFrameGetMatrix(f)); + RwFrameDestroy(assoc.frame); + }else{ + atomic = (RpAtomic*)GetFirstObject(assoc.frame); + RpClumpRemoveAtomic(m_clump, atomic); + RwFrameRemoveChild(assoc.frame); + SetVehicleComponentFlags(assoc.frame, desc[i].flags); + m_comps[m_numComps++] = atomic; + } + } + + for(i = 0; desc[i].name; i++){ + RwObjectIdAssociation assoc; + + if(desc[i].flags & (VEHICLE_FLAG_COMP|VEHICLE_FLAG_POS)) + continue; + assoc.frame = nil; + assoc.id = desc[i].hierId; + RwFrameForAllChildren(RpClumpGetFrame(m_clump), + FindFrameFromIdCB, &assoc); + if(assoc.frame == nil) + continue; + + if(desc[i].flags & VEHICLE_FLAG_DOOR) + m_numDoors++; + + if(desc[i].flags & VEHICLE_FLAG_COLLAPSE){ + RpAtomic *okdam[2] = { nil, nil }; + RwFrameForAllChildren(assoc.frame, CollapseFramesCB, assoc.frame); + RwFrameUpdateObjects(assoc.frame); + RwFrameForAllObjects(assoc.frame, GetOkAndDamagedAtomicCB, okdam); + if(okdam[0] && okdam[1]) + RpAtomicSetRenderCallBack(okdam[1], RpAtomicGetRenderCallBack(okdam[0])); + } + + SetVehicleComponentFlags(assoc.frame, desc[i].flags); + + if(desc[i].flags & VEHICLE_FLAG_ADD_WHEEL){ + if(m_wheelId == -1) + RwFrameDestroy(assoc.frame); + else{ + RwV3d scale; + atomic = (RpAtomic*)CModelInfo::GetModelInfo(m_wheelId)->CreateInstance(); + RwFrameDestroy(RpAtomicGetFrame(atomic)); + RpAtomicSetFrame(atomic, assoc.frame); + RpClumpAddAtomic(m_clump, atomic); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, + CVisibilityPlugins::RenderWheelAtomicCB); + scale.x = m_wheelScale; + scale.y = m_wheelScale; + scale.z = m_wheelScale; + RwFrameScale(assoc.frame, &scale, rwCOMBINEPRECONCAT); + } + } + } +} + +void +CVehicleModelInfo::SetVehicleComponentFlags(RwFrame *frame, uint32 flags) +{ + tHandlingData *handling; + + handling = mod_HandlingManager.GetHandlingData((tVehicleType)m_handlingId); + +#define SETFLAGS(f) RwFrameForAllObjects(frame, SetAtomicFlagCB, (void*)(f)) + + if(flags & VEHICLE_FLAG_WINDSCREEN){ + if(this == CModelInfo::GetModelInfo(MI_RHINO)) + return; + SETFLAGS(ATOMIC_FLAG_WINDSCREEN); + } + + if(flags & VEHICLE_FLAG_ANGLECULL) + SETFLAGS(ATOMIC_FLAG_ANGLECULL); + + if(flags & VEHICLE_FLAG_FRONT) + SETFLAGS(ATOMIC_FLAG_FRONT); + else if(flags & VEHICLE_FLAG_REAR && (handling->Flags & HANDLING_IS_VAN || (flags & (VEHICLE_FLAG_LEFT|VEHICLE_FLAG_RIGHT)) == 0)) + SETFLAGS(ATOMIC_FLAG_REAR); + else if(flags & VEHICLE_FLAG_LEFT) + SETFLAGS(ATOMIC_FLAG_LEFT); + else if(flags & VEHICLE_FLAG_RIGHT) + SETFLAGS(ATOMIC_FLAG_RIGHT); + + if(flags & VEHICLE_FLAG_REARDOOR) + SETFLAGS(ATOMIC_FLAG_REARDOOR); + else if(flags & VEHICLE_FLAG_FRONTDOOR) + SETFLAGS(ATOMIC_FLAG_FRONTDOOR); + + if(flags & VEHICLE_FLAG_DRAWLAST) + SETFLAGS(ATOMIC_FLAG_DRAWLAST); +} + +#define COMPRULE_RULE(comprule) (((comprule) >> 12) & 0xF) +#define COMPRULE_COMPS(comprule) ((comprule) & 0xFFF) +#define COMPRULE_COMPN(comps, n) (((comps) >> 4*(n)) & 0xF) +#define COMPRULE2_RULE(comprule) (((comprule) >> (12+16)) & 0xF) +#define COMPRULE2_COMPS(comprule) ((comprule >> 16) & 0xFFF) +#define COMPRULE2_COMPN(comps, n) (((comps >> 16) >> 4*(n)) & 0xF) + +bool +IsValidCompRule(int rule) +{ + if(rule == 2) + return CWeather::OldWeatherType == WEATHER_RAINY || + CWeather::NewWeatherType == WEATHER_RAINY; + return true; +} + +int32 +CountCompsInRule(int comps) +{ + int32 n; + for(n = 0; comps != 0; comps >>= 4) + if((comps & 0xF) != 0xF) + n++; + return n; +} + +int32 +ChooseComponent(int32 rule, int32 comps) +{ + int32 n; + switch(rule){ + // identical cases.... + case 1: + n = CGeneral::GetRandomNumberInRange(0, CountCompsInRule(comps)); + return COMPRULE_COMPN(comps, n); + case 2: + // only valid in rain + n = CGeneral::GetRandomNumberInRange(0, CountCompsInRule(comps)); + return COMPRULE_COMPN(comps, n); + } + return -1; +} + +int32 +GetListOfComponentsNotUsedByRules(uint32 comprules, int32 numComps, int32 *comps) +{ + int32 i, n; + int32 unused[6] = { 0, 1, 2, 3, 4, 5 }; + + // first comprule + if(COMPRULE_RULE(comprules) && IsValidCompRule(COMPRULE_RULE(comprules))) + for(i = 0; i < 3; i++){ + n = COMPRULE_COMPN(comprules, i); + if(n != 0xF) + unused[n] = 0xF; + } + // second comprule + comprules >>= 16; + if(COMPRULE_RULE(comprules) && IsValidCompRule(COMPRULE_RULE(comprules))) + for(i = 0; i < 3; i++){ + n = COMPRULE_COMPN(comprules, i); + if(n != 0xF) + unused[n] = 0xF; + } + + n = 0; + for(i = 0; i < numComps; i++) + if(unused[i] != 0xF) + comps[n++] = unused[i]; + return n; +} + +int32 wheelIds[] = { CAR_WHEEL_LF, CAR_WHEEL_LB, CAR_WHEEL_RF, CAR_WHEEL_RB }; + +void +CVehicleModelInfo::GetWheelPosn(int32 n, CVector &pos) +{ + RwMatrix *m = RwFrameGetMatrix(GetFrameFromId(m_clump, wheelIds[n])); + pos.x = RwMatrixGetPos(m)->x; + pos.y = RwMatrixGetPos(m)->y; + pos.z = RwMatrixGetPos(m)->z; +} + + +int32 +CVehicleModelInfo::ChooseComponent(void) +{ + int32 comp; + int32 comps[8]; + int32 n; + + comp = -1; + if(ms_compsToUse[0] == -2){ + if(COMPRULE_RULE(m_compRules) && IsValidCompRule(COMPRULE_RULE(m_compRules))) + comp = ::ChooseComponent(COMPRULE_RULE(m_compRules), COMPRULE_COMPS(m_compRules)); + else if(CGeneral::GetRandomNumberInRange(0, 3) < 2){ + n = GetListOfComponentsNotUsedByRules(m_compRules, m_numComps, comps); + if(n) + comp = comps[(int)CGeneral::GetRandomNumberInRange(0, n)]; + } + }else{ + comp = ms_compsToUse[0]; + ms_compsToUse[0] = -2; + } + return comp; +} + +int32 +CVehicleModelInfo::ChooseSecondComponent(void) +{ + int32 comp; + int32 comps[8]; + int32 n; + + comp = -1; + if(ms_compsToUse[1] == -2){ + if(COMPRULE2_RULE(m_compRules) && IsValidCompRule(COMPRULE2_RULE(m_compRules))) + comp = ::ChooseComponent(COMPRULE2_RULE(m_compRules), COMPRULE2_COMPS(m_compRules)); + else if(COMPRULE_RULE(m_compRules) && IsValidCompRule(COMPRULE_RULE(m_compRules)) && + CGeneral::GetRandomNumberInRange(0, 3) < 2){ + + n = GetListOfComponentsNotUsedByRules(m_compRules, m_numComps, comps); + if(n) + comp = comps[(int)CGeneral::GetRandomNumberInRange(0, n)]; + } + }else{ + comp = ms_compsToUse[1]; + ms_compsToUse[1] = -2; + } + return comp; +} + +struct editableMatCBData +{ + CVehicleModelInfo *vehicle; + int32 numMats1; + int32 numMats2; +}; + +RpMaterial* +CVehicleModelInfo::GetEditableMaterialListCB(RpMaterial *material, void *data) +{ + RwRGBA white = { 255, 255, 255, 255 }; + const RwRGBA *col; + editableMatCBData *cbdata; + + cbdata = (editableMatCBData*)data; + col = RpMaterialGetColor(material); + if(col->red == 0x3C && col->green == 0xFF && col->blue == 0){ + cbdata->vehicle->m_materials1[cbdata->numMats1++] = material; + RpMaterialSetColor(material, &white); + }else if(col->red == 0xFF && col->green == 0 && col->blue == 0xAF){ + cbdata->vehicle->m_materials2[cbdata->numMats2++] = material; + RpMaterialSetColor(material, &white); + } + return material; +} + +RpAtomic* +CVehicleModelInfo::GetEditableMaterialListCB(RpAtomic *atomic, void *data) +{ + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), GetEditableMaterialListCB, data); + return atomic; +} + +void +CVehicleModelInfo::FindEditableMaterialList(void) +{ + editableMatCBData cbdata; + int32 i; + + cbdata.vehicle = this; + cbdata.numMats1 = 0; + cbdata.numMats2 = 0; + RpClumpForAllAtomics(m_clump, GetEditableMaterialListCB, &cbdata); + for(i = 0; i < m_numComps; i++) + GetEditableMaterialListCB(m_comps[i], &cbdata); + m_materials1[cbdata.numMats1] = nil; + m_materials2[cbdata.numMats2] = nil; + m_currentColour1 = -1; + m_currentColour2 = -1; +} + +void +CVehicleModelInfo::SetVehicleColour(uint8 c1, uint8 c2) +{ + RwRGBA col, *colp; + RwTexture *coltex; + RpMaterial **matp; + + if(c1 != m_currentColour1){ + col = ms_vehicleColourTable[c1]; + coltex = ms_colourTextureTable[c1]; + for(matp = m_materials1; *matp; matp++){ + if(RpMaterialGetTexture(*matp) && RwTextureGetName(RpMaterialGetTexture(*matp))[0] != '@'){ + colp = (RwRGBA*)RpMaterialGetColor(*matp); // get rid of const + colp->red = col.red; + colp->green = col.green; + colp->blue = col.blue; + }else + RpMaterialSetTexture(*matp, coltex); + } + m_currentColour1 = c1; + } + + if(c2 != m_currentColour2){ + col = ms_vehicleColourTable[c2]; + coltex = ms_colourTextureTable[c2]; + for(matp = m_materials2; *matp; matp++){ + if(RpMaterialGetTexture(*matp) && RwTextureGetName(RpMaterialGetTexture(*matp))[0] != '@'){ + colp = (RwRGBA*)RpMaterialGetColor(*matp); // get rid of const + colp->red = col.red; + colp->green = col.green; + colp->blue = col.blue; + }else + RpMaterialSetTexture(*matp, coltex); + } + m_currentColour2 = c2; + } +} + +void +CVehicleModelInfo::ChooseVehicleColour(uint8 &col1, uint8 &col2) +{ + if(m_numColours == 0){ + col1 = 0; + col2 = 0; + }else{ + m_lastColorVariation = (m_lastColorVariation+1) % m_numColours; + col1 = m_colours1[m_lastColorVariation]; + col2 = m_colours2[m_lastColorVariation]; + if(m_numColours > 1){ + CVehicle *veh = FindPlayerVehicle(); + if(veh && CModelInfo::GetModelInfo(veh->GetModelIndex()) == this && + veh->m_currentColour1 == col1 && + veh->m_currentColour2 == col2){ + m_lastColorVariation = (m_lastColorVariation+1) % m_numColours; + col1 = m_colours1[m_lastColorVariation]; + col2 = m_colours2[m_lastColorVariation]; + } + } + } +} + +void +CVehicleModelInfo::AvoidSameVehicleColour(uint8 *col1, uint8 *col2) +{ + int i, n; + + if(m_numColours > 1) + for(i = 0; i < 8; i++){ + if(*col1 != m_lastColour1 || *col2 != m_lastColour2) + break; + n = CGeneral::GetRandomNumberInRange(0, m_numColours); + *col1 = m_colours1[n]; + *col2 = m_colours2[n]; + } + m_lastColour1 = *col1; + m_lastColour2 = *col2; +} + +RwTexture* +CreateCarColourTexture(uint8 r, uint8 g, uint8 b) +{ + RwImage *img; + RwRaster *ras; + RwTexture *tex; + RwUInt8 *pixels; + RwInt32 width, height, depth, format; + + img = RwImageCreate(2, 2, 32); + pixels = (RwUInt8*)RwMalloc(2*2*4); + pixels[0] = r; + pixels[1] = g; + pixels[2] = b; + pixels[3] = 0xFF; + pixels[4] = r; + pixels[5] = g; + pixels[6] = b; + pixels[7] = 0xFF; + pixels[8] = r; + pixels[9] = g; + pixels[10] = b; + pixels[11] = 0xFF; + pixels[12] = r; + pixels[13] = g; + pixels[14] = b; + pixels[15] = 0xFF; + RwImageSetPixels(img, pixels); + RwImageSetStride(img, 8); + RwImageSetPalette(img, nil); + RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &width, &height, &depth, &format); + ras = RwRasterCreate(width, height, depth, format); + RwRasterSetFromImage(ras, img); + RwImageDestroy(img); + RwFree(pixels); + tex = RwTextureCreate(ras); + RwTextureGetName(tex)[0] = '@'; + return tex; +} + +void +CVehicleModelInfo::LoadVehicleColours(void) +{ + int fd; + int i; + char line[1024]; + int start, end; + int section, numCols; + enum { + NONE, + COLOURS, + CARS + }; + int r, g, b; + char name[64]; + int colors[16]; + int n; + + CFileMgr::ChangeDir("\\DATA\\"); + fd = CFileMgr::OpenFile("CARCOLS.DAT", "r"); + CFileMgr::ChangeDir("\\"); + + for(i = 0; i < 256; i++) + ms_colourTextureTable[i] = nil; + + section = 0; + numCols = 0; + while(CFileMgr::ReadLine(fd, line, sizeof(line))){ + // find first valid character in line + for(start = 0; ; start++) + if(line[start] > ' ' || line[start] == '\0' || line[start] == '\n') + break; + // find end of line + for(end = start; ; end++){ + if(line[end] == '\0' || line[end] == '\n') + break; + if(line[end] == ',' || line[end] == '\r') + line[end] = ' '; + } + line[end] = '\0'; + + // empty line + if(line[start] == '#' || line[start] == '\0') + continue; + + if(section == NONE){ + if(line[start] == 'c' && line[start + 1] == 'o' && line[start + 2] == 'l') + section = COLOURS; + else if(line[start] == 'c' && line[start + 1] == 'a' && line[start + 2] == 'r') + section = CARS; + }else if(line[start] == 'e' && line[start + 1] == 'n' && line[start + 2] == 'd'){ + section = NONE; + }else if(section == COLOURS){ + sscanf(&line[start], // BUG: games doesn't add start + "%d %d %d", &r, &g, &b); + ms_vehicleColourTable[numCols].red = r; + ms_vehicleColourTable[numCols].green = g; + ms_vehicleColourTable[numCols].blue = b; + ms_vehicleColourTable[numCols].alpha = 0xFF; + ms_colourTextureTable[numCols] = CreateCarColourTexture(r, g, b); + numCols++; + }else if(section == CARS){ + n = sscanf(&line[start], // BUG: games doesn't add start + "%s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", + name, + &colors[0], &colors[1], + &colors[2], &colors[3], + &colors[4], &colors[5], + &colors[6], &colors[7], + &colors[8], &colors[9], + &colors[10], &colors[11], + &colors[12], &colors[13], + &colors[14], &colors[15]); + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(name, nil); + assert(mi); + mi->m_numColours = (n-1)/2; + for(i = 0; i < mi->m_numColours; i++){ + mi->m_colours1[i] = colors[i*2 + 0]; + mi->m_colours2[i] = colors[i*2 + 1]; + } + } + } + + CFileMgr::CloseFile(fd); +} + +void +CVehicleModelInfo::DeleteVehicleColourTextures(void) +{ + int i; + + for(i = 0; i < 256; i++){ + if(ms_colourTextureTable[i]){ + RwTextureDestroy(ms_colourTextureTable[i]); +#if GTA_VERSION >= GTA3_PC_11 + ms_colourTextureTable[i] = nil; +#endif + } + } +} + +RpMaterial* +CVehicleModelInfo::HasSpecularMaterialCB(RpMaterial *material, void *data) +{ + if(RpMaterialGetSurfaceProperties(material)->specular <= 0.0f) + return material; + *(bool*)data = true; + return nil; +} + +RpMaterial* +CVehicleModelInfo::SetEnvironmentMapCB(RpMaterial *material, void *data) +{ + float spec; + + spec = RpMaterialGetSurfaceProperties(material)->specular; + if(spec <= 0.0f) + RpMatFXMaterialSetEffects(material, rpMATFXEFFECTNULL); + else{ + if(RpMaterialGetTexture(material) == nil) + RpMaterialSetTexture(material, gpWhiteTexture); + RpMatFXMaterialSetEffects(material, rpMATFXEFFECTENVMAP); +#ifndef PS2_MATFX + spec *= 0.5f; // Tone down a bit for PC +#endif + RpMatFXMaterialSetupEnvMap(material, (RwTexture*)data, pMatFxIdentityFrame, false, spec); + } + return material; +} + +bool initialised; + +RpAtomic* +CVehicleModelInfo::SetEnvironmentMapCB(RpAtomic *atomic, void *data) +{ + bool hasSpec; + RpGeometry *geo; + + geo = RpAtomicGetGeometry(atomic); + hasSpec = 0; + RpGeometryForAllMaterials(geo, HasSpecularMaterialCB, &hasSpec); + if(hasSpec){ + RpGeometryForAllMaterials(geo, SetEnvironmentMapCB, data); + RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) | rpGEOMETRYMODULATEMATERIALCOLOR); + RpMatFXAtomicEnableEffects(atomic); +#ifdef GTA_PS2 + if(!initialised){ + SetupPS2ManagerLightingCallback(RpAtomicGetInstancePipeline(atomic)); + initialised = true; + } +#endif + } + return atomic; +} + +void +CVehicleModelInfo::SetEnvironmentMap(void) +{ + CSimpleModelInfo *wheelmi; + int32 i; + + if(pMatFxIdentityFrame == nil){ + pMatFxIdentityFrame = RwFrameCreate(); + RwMatrixSetIdentity(RwFrameGetMatrix(pMatFxIdentityFrame)); + RwFrameUpdateObjects(pMatFxIdentityFrame); + RwFrameGetLTM(pMatFxIdentityFrame); + } + + if(m_envMap != ms_pEnvironmentMaps[0]){ + m_envMap = ms_pEnvironmentMaps[0]; + RpClumpForAllAtomics(m_clump, SetEnvironmentMapCB, m_envMap); + if(m_wheelId != -1){ + wheelmi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_wheelId); + for(i = 0; i < wheelmi->m_numAtomics; i++) + SetEnvironmentMapCB(wheelmi->m_atomics[i], m_envMap); + } + } + +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachVehiclePipe(m_clump); +#endif +} + +void +CVehicleModelInfo::LoadEnvironmentMaps(void) +{ + const char *texnames[] = { + "reflection01", // only one used + "reflection02", + "reflection03", + "reflection04", + "reflection05", + "reflection06", + }; + int32 txdslot; + int32 i; + + txdslot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(txdslot); + for(i = 0; i < NUM_VEHICLE_ENVMAPS; i++){ + ms_pEnvironmentMaps[i] = RwTextureRead(texnames[i], nil); + RwTextureSetFilterMode(ms_pEnvironmentMaps[i], rwFILTERLINEAR); + } + if(gpWhiteTexture == nil){ + gpWhiteTexture = RwTextureRead("white", nil); + RwTextureGetName(gpWhiteTexture)[0] = '@'; + RwTextureSetFilterMode(gpWhiteTexture, rwFILTERLINEAR); + } + CTxdStore::PopCurrentTxd(); +} + +void +CVehicleModelInfo::ShutdownEnvironmentMaps(void) +{ + int32 i; + + // ignoring "initialised" as that's a PS2 thing only + RwTextureDestroy(gpWhiteTexture); + gpWhiteTexture = nil; + for(i = 0; i < NUM_VEHICLE_ENVMAPS; i++) + if(ms_pEnvironmentMaps[i]) + RwTextureDestroy(ms_pEnvironmentMaps[i]); + RwFrameDestroy(pMatFxIdentityFrame); + pMatFxIdentityFrame = nil; +} + +int +CVehicleModelInfo::GetMaximumNumberOfPassengersFromNumberOfDoors(int id) +{ + int n; + + switch(id){ + case MI_TRAIN: + n = 3; + break; + case MI_FIRETRUCK: + n = 2; + break; + default: + n = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(id))->m_numDoors; + } + + if(n == 0) + return id == MI_RCBANDIT ? 0 : 1; + + if(id == MI_COACH) + return 8; + + return n - 1; +} diff --git a/src/modelinfo/VehicleModelInfo.h b/src/modelinfo/VehicleModelInfo.h new file mode 100644 index 0000000..e6ba576 --- /dev/null +++ b/src/modelinfo/VehicleModelInfo.h @@ -0,0 +1,165 @@ +#pragma once + +#include "ClumpModelInfo.h" + +enum { + NUM_FIRST_MATERIALS = 26, + NUM_SECOND_MATERIALS = 26, + NUM_VEHICLE_COLOURS = 8, + NUM_VEHICLE_ENVMAPS = 1 +}; + +enum { + ATOMIC_FLAG_NONE = 0x0, + ATOMIC_FLAG_OK = 0x1, + ATOMIC_FLAG_DAM = 0x2, + ATOMIC_FLAG_LEFT = 0x4, + ATOMIC_FLAG_RIGHT = 0x8, + ATOMIC_FLAG_FRONT = 0x10, + ATOMIC_FLAG_REAR = 0x20, + ATOMIC_FLAG_DRAWLAST = 0x40, + ATOMIC_FLAG_WINDSCREEN = 0x80, + ATOMIC_FLAG_ANGLECULL = 0x100, + ATOMIC_FLAG_REARDOOR = 0x200, + ATOMIC_FLAG_FRONTDOOR = 0x400, + ATOMIC_FLAG_NOCULL = 0x800, +}; + +enum eVehicleType { + VEHICLE_TYPE_CAR, + VEHICLE_TYPE_BOAT, + VEHICLE_TYPE_TRAIN, + VEHICLE_TYPE_HELI, + VEHICLE_TYPE_PLANE, + VEHICLE_TYPE_BIKE, + NUM_VEHICLE_TYPES +}; + +enum eCarPositions +{ + CAR_POS_HEADLIGHTS, + CAR_POS_TAILLIGHTS, + CAR_POS_FRONTSEAT, + CAR_POS_BACKSEAT, + // these are unused so we don't know the actual values + CAR_POS_REVERSELIGHTS, + CAR_POS_BRAKELIGHTS, + CAR_POS_INDICATORS_FRONT, + CAR_POS_INDICATORS_BACK, + CAR_POS_STEERWHEEL, + // + CAR_POS_EXHAUST +}; + +enum eBoatPositions +{ + BOAT_POS_FRONTSEAT +}; + +enum eTrainPositions +{ + TRAIN_POS_LIGHT_FRONT, + TRAIN_POS_LIGHT_REAR, + TRAIN_POS_LEFT_ENTRY, + TRAIN_POS_MID_ENTRY, + TRAIN_POS_RIGHT_ENTRY +}; + +enum ePlanePositions +{ + PLANE_POS_LIGHT_LEFT, + PLANE_POS_LIGHT_RIGHT, + PLANE_POS_LIGHT_TAIL, +}; + +enum { + NUM_VEHICLE_POSITIONS = 10 +}; + +class CVehicleModelInfo : public CClumpModelInfo +{ +public: + uint8 m_lastColour1; + uint8 m_lastColour2; + char m_gameName[32]; + int32 m_vehicleType; + union { + int32 m_wheelId; + int32 m_planeLodId; + }; + float m_wheelScale; + int32 m_numDoors; + int32 m_handlingId; + int32 m_vehicleClass; + int32 m_level; + CVector m_positions[NUM_VEHICLE_POSITIONS]; + uint32 m_compRules; + float m_bikeSteerAngle; + RpMaterial *m_materials1[NUM_FIRST_MATERIALS]; + RpMaterial *m_materials2[NUM_SECOND_MATERIALS]; + uint8 m_colours1[NUM_VEHICLE_COLOURS]; + uint8 m_colours2[NUM_VEHICLE_COLOURS]; + uint8 m_numColours; + uint8 m_lastColorVariation; + uint8 m_currentColour1; + uint8 m_currentColour2; + RwTexture *m_envMap; + RpAtomic *m_comps[6]; + int32 m_numComps; + + static int8 ms_compsToUse[2]; + static int8 ms_compsUsed[2]; + static RwTexture *ms_pEnvironmentMaps[NUM_VEHICLE_ENVMAPS]; + static RwRGBA ms_vehicleColourTable[256]; + static RwTexture *ms_colourTextureTable[256]; + static RwObjectNameIdAssocation *ms_vehicleDescs[NUM_VEHICLE_TYPES]; + + CVehicleModelInfo(void); + void DeleteRwObject(void); + RwObject *CreateInstance(void); + void SetClump(RpClump *); + + static RwFrame *CollapseFramesCB(RwFrame *frame, void *data); + static RwObject *MoveObjectsCB(RwObject *object, void *data); + static RpAtomic *HideDamagedAtomicCB(RpAtomic *atomic, void *data); + static RpAtomic *HideAllComponentsAtomicCB(RpAtomic *atomic, void *data); + static RpMaterial *HasAlphaMaterialCB(RpMaterial *material, void *data); + + static RpAtomic *SetAtomicRendererCB(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_BigVehicle(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_Train(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_Boat(RpAtomic *atomic, void *data); + static RpAtomic *SetAtomicRendererCB_Heli(RpAtomic *atomic, void *data); + void SetAtomicRenderCallbacks(void); + + static RwObject *SetAtomicFlagCB(RwObject *object, void *data); + static RwObject *ClearAtomicFlagCB(RwObject *atomic, void *data); + void SetVehicleComponentFlags(RwFrame *frame, uint32 flags); + void PreprocessHierarchy(void); + void GetWheelPosn(int32 n, CVector &pos); + const CVector &GetFrontSeatPosn(void) { return m_vehicleType == VEHICLE_TYPE_BOAT ? m_positions[BOAT_POS_FRONTSEAT] : m_positions[CAR_POS_FRONTSEAT]; } + + int32 ChooseComponent(void); + int32 ChooseSecondComponent(void); + + static RpMaterial *GetEditableMaterialListCB(RpMaterial *material, void *data); + static RpAtomic *GetEditableMaterialListCB(RpAtomic *atomic, void *data); + void FindEditableMaterialList(void); + void SetVehicleColour(uint8 c1, uint8 c2); + void ChooseVehicleColour(uint8 &col1, uint8 &col2); + void AvoidSameVehicleColour(uint8 *col1, uint8 *col2); + static void LoadVehicleColours(void); + static void DeleteVehicleColourTextures(void); + + static RpAtomic *SetEnvironmentMapCB(RpAtomic *atomic, void *data); + static RpMaterial *SetEnvironmentMapCB(RpMaterial *material, void *data); + static RpMaterial *HasSpecularMaterialCB(RpMaterial *material, void *data); + void SetEnvironmentMap(void); + static void LoadEnvironmentMaps(void); + static void ShutdownEnvironmentMaps(void); + + static int GetMaximumNumberOfPassengersFromNumberOfDoors(int id); + static void SetComponentsToUse(int8 c1, int8 c2) { ms_compsToUse[0] = c1; ms_compsToUse[1] = c2; } +}; + +VALIDATE_SIZE(CVehicleModelInfo, 0x1F8); diff --git a/src/modelinfo/XtraCompsModelInfo.h b/src/modelinfo/XtraCompsModelInfo.h new file mode 100644 index 0000000..ab308a8 --- /dev/null +++ b/src/modelinfo/XtraCompsModelInfo.h @@ -0,0 +1,13 @@ +#pragma once + +#include "ClumpModelInfo.h" + +class CXtraCompsModelInfo : public CClumpModelInfo +{ + int field_34; +public: + CXtraCompsModelInfo(void) : CClumpModelInfo(MITYPE_XTRACOMPS) { field_34 = 0; } + void Shutdown(void) {}; + RwObject *CreateInstance(void) { return nil; } + void SetClump(RpClump*) {}; +}; \ No newline at end of file diff --git a/src/objects/CutsceneHead.cpp b/src/objects/CutsceneHead.cpp new file mode 100644 index 0000000..19b3a59 --- /dev/null +++ b/src/objects/CutsceneHead.cpp @@ -0,0 +1,230 @@ +#include "common.h" +#include + +#include "main.h" +#include "RwHelper.h" +#include "RpAnimBlend.h" +#include "AnimBlendClumpData.h" +#include "Bones.h" +#include "Directory.h" +#include "CutsceneMgr.h" +#include "Streaming.h" +#include "CutsceneHead.h" +#include "CdStream.h" + +#ifdef GTA_PS2_STUFF +// this is a total hack to switch between PC and PS2 code +static bool lastLoadedSKA; +#endif + +CCutsceneHead::CCutsceneHead(CObject *obj) +{ + RpAtomic *atm; + + assert(RwObjectGetType(obj->m_rwObject) == rpCLUMP); +#ifdef PED_SKIN + unk1 = 0; + bIsSkinned = false; + m_parentObject = (CCutsceneObject*)obj; + // Hide original head + if(IsClumpSkinned(obj->GetClump())){ + m_parentObject->SetRenderHead(false); + bIsSkinned = true; + }else +#endif + { + m_pHeadNode = RpAnimBlendClumpFindFrame((RpClump*)obj->m_rwObject, "Shead")->frame; + atm = (RpAtomic*)GetFirstObject(m_pHeadNode); + if(atm){ + assert(RwObjectGetType((RwObject*)atm) == rpATOMIC); + RpAtomicSetFlags(atm, RpAtomicGetFlags(atm) & ~rpATOMICRENDER); + } + } +} + +void +CCutsceneHead::CreateRwObject(void) +{ + RpAtomic *atm; + + CEntity::CreateRwObject(); + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + atm = GetFirstAtomic((RpClump*)m_rwObject); + RpSkinAtomicSetHAnimHierarchy(atm, RpHAnimFrameGetHierarchy(GetFirstChild(RpClumpGetFrame((RpClump*)m_rwObject)))); +} + +void +CCutsceneHead::DeleteRwObject(void) +{ + CEntity::DeleteRwObject(); +} + +void +CCutsceneHead::ProcessControl(void) +{ + RpAtomic *atm; + RpHAnimHierarchy *hier; + + // android/xbox calls is at the end + CPhysical::ProcessControl(); + +#ifdef PED_SKIN + if(bIsSkinned){ + UpdateRpHAnim(); + UpdateRwFrame(); + + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(m_parentObject->GetClump()); + int idx = RpHAnimIDGetIndex(hier, BONE_head); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + if(RwV3dLength(&mat->pos) > 100.0f){ + m_matrix.SetRotateY(PI/2); + m_matrix = CMatrix(mat) * m_matrix; + } + }else +#endif + { + m_matrix.SetRotateY(PI/2); + m_matrix = CMatrix(RwFrameGetLTM(m_pHeadNode)) * m_matrix; + } + + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + atm = GetFirstAtomic((RpClump*)m_rwObject); + hier = RpSkinAtomicGetHAnimHierarchy(atm); +#ifdef GTA_PS2_STUFF + // PS2 only plays anims in cutscene, PC always plays anims + if(!lastLoadedSKA || CCutsceneMgr::IsRunning()) +#endif + RpHAnimHierarchyAddAnimTime(hier, CTimer::GetTimeStepNonClippedInSeconds()); +} + +void +CCutsceneHead::Render(void) +{ + RpAtomic *atm; + +#ifdef PED_SKIN + if(bIsSkinned){ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(m_parentObject->GetClump()); + RpHAnimHierarchyUpdateMatrices(hier); + int idx = RpHAnimIDGetIndex(hier, BONE_head); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + if(RwV3dLength(&mat->pos) > 100.0f){ + m_matrix.SetRotateY(PI/2); + m_matrix = CMatrix(mat) * m_matrix; + } + // This is head...it has no limbs +#ifndef FIX_BUGS + RenderLimb(BONE_Lhand); + RenderLimb(BONE_Rhand); +#endif + }else +#endif + { + m_matrix.SetRotateY(PI/2); + m_matrix = CMatrix(RwFrameGetLTM(m_pHeadNode)) * m_matrix; + } + + UpdateRwFrame(); + + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + atm = GetFirstAtomic((RpClump*)m_rwObject); + RpHAnimHierarchyUpdateMatrices(RpSkinAtomicGetHAnimHierarchy(atm)); + + CObject::Render(); +} + +#ifdef PED_SKIN +void +CCutsceneHead::RenderLimb(int32 bone) +{ + // It's not clear what this is... + // modelinfo for this object is not a ped so it also doesn't have any limbs +#ifndef FIX_BUGS + RpAtomic *atomic; + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(m_parentObject->GetClump()); + int idx = RpHAnimIDGetIndex(hier, bone); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + CPedModelInfo *mi = (CPedModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + assert(mi->GetModelType() == MITYPE_PED); + switch(bone){ + case BONE_Lhand: + atomic = mi->getLeftHand(); + break; + case BONE_Rhand: + atomic = mi->getRightHand(); + break; + default: + return; + } + if(atomic){ + RwFrame *frame = RpAtomicGetFrame(atomic); + RwMatrixTransform(RwFrameGetMatrix(frame), &mats[idx], rwCOMBINEREPLACE); + RwFrameUpdateObjects(frame); + RpAtomicRender(atomic); + } +#endif +} +#endif + +void +CCutsceneHead::PlayAnimation(const char *animName) +{ + RpAtomic *atm; + RpHAnimHierarchy *hier; + RpHAnimAnimation *anim; + uint32 offset, size; + RwStream *stream; + +#ifdef GTA_PS2_STUFF + lastLoadedSKA = false; +#endif + + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + atm = GetFirstAtomic((RpClump*)m_rwObject); + hier = RpSkinAtomicGetHAnimHierarchy(atm); + + sprintf(gString, "%s.anm", animName); + + if(CCutsceneMgr::ms_pCutsceneDir->FindItem(gString, offset, size)){ + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "ANIM\\CUTS.IMG"); + assert(stream); + + CStreaming::MakeSpaceFor(size * CDSTREAM_SECTOR_SIZE); + CStreaming::ImGonnaUseStreamingMemory(); + + RwStreamSkip(stream, offset*2048); + if(RwStreamFindChunk(stream, rwID_HANIMANIMATION, nil, nil)){ + anim = RpHAnimAnimationStreamRead(stream); + RpHAnimHierarchySetCurrentAnim(hier, anim); + } + + CStreaming::IHaveUsedStreamingMemory(); + + RwStreamClose(stream, nil); + } +#ifdef GTA_PS2_STUFF +#ifdef LIBRW + else{ + sprintf(gString, "%s.ska", animName); + + if(CCutsceneMgr::ms_pCutsceneDir->FindItem(gString, offset, size)){ + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "ANIM\\CUTS.IMG"); + assert(stream); + + CStreaming::MakeSpaceFor(size * CDSTREAM_SECTOR_SIZE); + CStreaming::ImGonnaUseStreamingMemory(); + + RwStreamSkip(stream, offset*2048); + anim = rw::Animation::streamReadLegacy(stream); + RpHAnimHierarchySetCurrentAnim(hier, anim); + + CStreaming::IHaveUsedStreamingMemory(); + + RwStreamClose(stream, nil); + + lastLoadedSKA = true; + } + } +#endif +#endif +} diff --git a/src/objects/CutsceneHead.h b/src/objects/CutsceneHead.h new file mode 100644 index 0000000..c931eb0 --- /dev/null +++ b/src/objects/CutsceneHead.h @@ -0,0 +1,28 @@ +#pragma once + +#include "CutsceneObject.h" + +class CCutsceneHead : public CCutsceneObject +{ +public: + RwFrame *m_pHeadNode; +#ifdef PED_SKIN + int32 unk1; + CCutsceneObject *m_parentObject; + int32 unk2; + int32 bIsSkinned; +#endif + + CCutsceneHead(CObject *obj); + + void CreateRwObject(void); + void DeleteRwObject(void); + void ProcessControl(void); + void Render(void); + void RenderLimb(int32 bone); + + void PlayAnimation(const char *animName); +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CCutsceneHead, 0x19C); +#endif diff --git a/src/objects/CutsceneObject.cpp b/src/objects/CutsceneObject.cpp new file mode 100644 index 0000000..64e5780 --- /dev/null +++ b/src/objects/CutsceneObject.cpp @@ -0,0 +1,153 @@ +#include "common.h" + +#include "main.h" +#include "RwHelper.h" +#include "Lights.h" +#include "PointLights.h" +#include "RpAnimBlend.h" +#include "AnimBlendClumpData.h" +#include "Bones.h" +#include "Renderer.h" +#include "ModelIndices.h" +#include "Shadows.h" +#include "Timecycle.h" +#include "CutsceneObject.h" + +CCutsceneObject::CCutsceneObject(void) +{ + SetStatus(STATUS_SIMPLE); + bUsesCollision = false; + bStreamingDontDelete = true; + ObjectCreatedBy = CUTSCENE_OBJECT; + m_fMass = 1.0f; + m_fTurnMass = 1.0f; + +#ifdef PED_SKIN + bRenderHead = true; + bRenderRightHand = true; + bRenderLeftHand = true; +#endif +} + +void +CCutsceneObject::SetModelIndex(uint32 id) +{ + CEntity::SetModelIndex(id); + assert(RwObjectGetType(m_rwObject) == rpCLUMP); + RpAnimBlendClumpInit((RpClump*)m_rwObject); + (*RPANIMBLENDCLUMPDATA(m_rwObject))->velocity3d = &m_vecMoveSpeed; + (*RPANIMBLENDCLUMPDATA(m_rwObject))->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION_3D; +} + +void +CCutsceneObject::ProcessControl(void) +{ + CPhysical::ProcessControl(); + + if(CTimer::GetTimeStep() < 1/100.0f) + m_vecMoveSpeed *= 100.0f; + else + m_vecMoveSpeed *= 1.0f/CTimer::GetTimeStep(); + + ApplyMoveSpeed(); + +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())) + UpdateRpHAnim(); +#endif +} + +static RpMaterial* +MaterialSetAlpha(RpMaterial *material, void *data) +{ + ((RwRGBA*)RpMaterialGetColor(material))->alpha = (uint8)(uintptr)data; + return material; +} + +void +CCutsceneObject::PreRender(void) +{ + if(IsPedModel(GetModelIndex())){ + CShadows::StoreShadowForPedObject(this, + CTimeCycle::m_fShadowDisplacementX[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowDisplacementY[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowFrontX[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowFrontY[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowSideX[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowSideY[CTimeCycle::m_CurrentStoredValue]); + // For some reason xbox/android limbs are transparent here... + RpGeometry *geometry = RpAtomicGetGeometry(GetFirstAtomic(GetClump())); + RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geometry, MaterialSetAlpha, (void*)255); + } +} + +void +CCutsceneObject::Render(void) +{ +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + if(bRenderLeftHand) RenderLimb(BONE_Lhand); + if(bRenderRightHand) RenderLimb(BONE_Rhand); + if(bRenderHead) RenderLimb(BONE_head); + } +#endif + CObject::Render(); +} + +#ifdef PED_SKIN +void +CCutsceneObject::RenderLimb(int32 bone) +{ + RpAtomic *atomic; + CPedModelInfo *mi = (CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()); + switch(bone){ + case BONE_head: + atomic = mi->getHead(); + break; + case BONE_Lhand: + atomic = mi->getLeftHand(); + break; + case BONE_Rhand: + atomic = mi->getRightHand(); + break; + default: + return; + } + if(atomic){ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int idx = RpHAnimIDGetIndex(hier, bone); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwFrame *frame = RpAtomicGetFrame(atomic); + *RwFrameGetMatrix(frame) = *mat; + RwFrameUpdateObjects(frame); + RpAtomicRender(atomic); + } +} +#endif + +bool +CCutsceneObject::SetupLighting(void) +{ + ActivateDirectional(); + SetAmbientColoursForPedsCarsAndObjects(); + + if(bRenderScorched){ + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + }else{ + CVector coors = GetPosition(); + float lighting = CPointLights::GenerateLightsAffectingObject(&coors); + if(!bHasBlip && lighting != 1.0f){ + SetAmbientAndDirectionalColours(lighting); + return true; + } + } + + return false; +} + +void +CCutsceneObject::RemoveLighting(bool reset) +{ + CRenderer::RemoveVehiclePedLights(this, reset); +} diff --git a/src/objects/CutsceneObject.h b/src/objects/CutsceneObject.h new file mode 100644 index 0000000..407adcc --- /dev/null +++ b/src/objects/CutsceneObject.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Object.h" + +class CCutsceneObject : public CObject +{ +public: +#ifdef PED_SKIN + bool bRenderHead; + bool bRenderRightHand; + bool bRenderLeftHand; + + bool GetRenderHead(void) { return bRenderHead; } + bool GetRenderRightHand(void) { return bRenderRightHand; } + bool GetRenderLeftHand(void) { return bRenderLeftHand; } + void SetRenderHead(bool render) { bRenderHead = render; } + void SetRenderRightHand(bool render) { bRenderRightHand = render; } + void SetRenderLeftHand(bool render) { bRenderLeftHand = render; } +#endif + + CCutsceneObject(void); + + void SetModelIndex(uint32 id); + void ProcessControl(void); + void PreRender(void); + void Render(void); + void RenderLimb(int32 bone); + bool SetupLighting(void); + void RemoveLighting(bool reset); +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CCutsceneObject, 0x198); +#endif diff --git a/src/objects/DummyObject.cpp b/src/objects/DummyObject.cpp new file mode 100644 index 0000000..d580507 --- /dev/null +++ b/src/objects/DummyObject.cpp @@ -0,0 +1,13 @@ +#include "common.h" + +#include "DummyObject.h" +#include "Pools.h" + +CDummyObject::CDummyObject(CObject *obj) +{ + SetModelIndexNoCreate(obj->GetModelIndex()); + if(obj->m_rwObject) + AttachToRwObject(obj->m_rwObject); + obj->DetachFromRwObject(); + m_level = obj->m_level; +} diff --git a/src/objects/DummyObject.h b/src/objects/DummyObject.h new file mode 100644 index 0000000..d6f8833 --- /dev/null +++ b/src/objects/DummyObject.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Dummy.h" + +class CObject; + +class CDummyObject : public CDummy +{ +public: + CDummyObject(void) {} + CDummyObject(CObject *obj); +}; + +VALIDATE_SIZE(CDummyObject, 0x68); diff --git a/src/objects/Object.cpp b/src/objects/Object.cpp new file mode 100644 index 0000000..2a7de2c --- /dev/null +++ b/src/objects/Object.cpp @@ -0,0 +1,419 @@ +#include "common.h" + +#include "main.h" +#include "Lights.h" +#include "Pools.h" +#include "Radar.h" +#include "Object.h" +#include "DummyObject.h" +#include "Particle.h" +#include "General.h" +#include "ObjectData.h" +#include "World.h" +#include "Floater.h" +#include "soundlist.h" + +int16 CObject::nNoTempObjects; +int16 CObject::nBodyCastHealth = 1000; + +// Object pools tends to be full sometimes, let's free a temp. object in this case. +#ifdef FIX_BUGS +void *CObject::operator new(size_t sz) throw() { + CObject *obj = CPools::GetObjectPool()->New(); + if (!obj) { + CObjectPool *objectPool = CPools::GetObjectPool(); + for (int32 i = 0; i < objectPool->GetSize(); i++) { + CObject *existing = objectPool->GetSlot(i); + if (existing && existing->ObjectCreatedBy == TEMP_OBJECT) { + int32 handle = objectPool->GetIndex(existing); + CWorld::Remove(existing); + delete existing; + obj = objectPool->New(handle); + break; + } + } + } + return obj; +} +#else +void *CObject::operator new(size_t sz) throw() { return CPools::GetObjectPool()->New(); } +#endif +void *CObject::operator new(size_t sz, int handle) throw() { return CPools::GetObjectPool()->New(handle); }; + +void CObject::operator delete(void *p, size_t sz) throw() { CPools::GetObjectPool()->Delete((CObject*)p); } +void CObject::operator delete(void *p, int handle) throw() { CPools::GetObjectPool()->Delete((CObject*)p); } + +CObject::CObject(void) +{ + m_type = ENTITY_TYPE_OBJECT; + m_fUprootLimit = 0.0f; + m_nCollisionDamageEffect = 0; + m_nSpecialCollisionResponseCases = COLLRESPONSE_NONE; + m_bCameraToAvoidThisObject = false; + ObjectCreatedBy = UNKNOWN_OBJECT; + m_nEndOfLifeTime = 0; +// m_nRefModelIndex = -1; // duplicate +// bUseVehicleColours = false; // duplicate + m_colour2 = 0; + m_colour1 = m_colour2; + m_nBonusValue = 0; + bIsPickup = false; + bPickupObjWithMessage = false; + bOutOfStock = false; + bGlassCracked = false; + bGlassBroken = false; + bHasBeenDamaged = false; + m_nRefModelIndex = -1; + bUseVehicleColours = false; + m_pCurSurface = nil; + m_pCollidingEntity = nil; +} + +CObject::CObject(int32 mi, bool createRW) +{ + if (createRW) + SetModelIndex(mi); + else + SetModelIndexNoCreate(mi); + Init(); +} + +CObject::CObject(CDummyObject *dummy) +{ + SetModelIndexNoCreate(dummy->GetModelIndex()); + + if (dummy->m_rwObject) + AttachToRwObject(dummy->m_rwObject); + else + SetMatrix(dummy->GetMatrix()); + + m_objectMatrix = dummy->GetMatrix(); + dummy->DetachFromRwObject(); + Init(); + m_level = dummy->m_level; +} + +CObject::~CObject(void) +{ + CRadar::ClearBlipForEntity(BLIP_OBJECT, CPools::GetObjectPool()->GetIndex(this)); + + if(m_nRefModelIndex != -1) + CModelInfo::GetModelInfo(m_nRefModelIndex)->RemoveRef(); + + if(ObjectCreatedBy == TEMP_OBJECT && nNoTempObjects != 0) + nNoTempObjects--; +} + +void +CObject::ProcessControl(void) +{ + CVector point, impulse; + if (m_nCollisionDamageEffect) + ObjectDamage(m_fDamageImpulse); + CPhysical::ProcessControl(); + if (mod_Buoyancy.ProcessBuoyancy(this, m_fBuoyancy, &point, &impulse)) { + bIsInWater = true; + SetIsStatic(false); + ApplyMoveForce(impulse); + ApplyTurnForce(impulse, point); + float fTimeStep = Pow(0.97f, CTimer::GetTimeStep()); + m_vecMoveSpeed *= fTimeStep; + m_vecTurnSpeed *= fTimeStep; + } + if ((GetModelIndex() == MI_EXPLODINGBARREL || GetModelIndex() == MI_PETROLPUMP) && bHasBeenDamaged && bIsVisible + && (CGeneral::GetRandomNumber() & 0x1F) == 10) { + bExplosionProof = true; + bIsVisible = false; + bUsesCollision = false; + bAffectedByGravity = false; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } +} + +void +CObject::Teleport(CVector vecPos) +{ + CWorld::Remove(this); + GetMatrix().GetPosition() = vecPos; + GetMatrix().UpdateRW(); + UpdateRwFrame(); + CWorld::Add(this); +} + +void +CObject::Render(void) +{ + if(bDoNotRender) + return; + + if(m_nRefModelIndex != -1 && ObjectCreatedBy == TEMP_OBJECT && bUseVehicleColours){ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(m_nRefModelIndex); + assert(mi->GetModelType() == MITYPE_VEHICLE); + mi->SetVehicleColour(m_colour1, m_colour2); + } + + CEntity::Render(); +} + +bool +CObject::SetupLighting(void) +{ + DeActivateDirectional(); + SetAmbientColours(); + + if(bRenderScorched){ + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + return true; + } + return false; +} + +void +CObject::RemoveLighting(bool reset) +{ + if(reset) + WorldReplaceScorchedLightsWithNormal(Scene.world); +} + +void +CObject::ObjectDamage(float amount) +{ + if (!m_nCollisionDamageEffect || !bUsesCollision) + return; + static int8 nFrameGen = 0; + bool bBodyCastDamageEffect = false; + if (GetModelIndex() == MI_BODYCAST) { + if (amount > 50.0f) + nBodyCastHealth = (int16)(nBodyCastHealth - 0.5f * amount); + if (nBodyCastHealth < 0) + nBodyCastHealth = 0; + if (nBodyCastHealth < 200) + bBodyCastDamageEffect = true; + amount = 0.0f; + } + if ((amount * m_fCollisionDamageMultiplier > 150.0f || bBodyCastDamageEffect) && m_nCollisionDamageEffect) { + const CVector& vecPos = GetMatrix().GetPosition(); + const float fDirectionZ = 0.0002f * amount; + switch (m_nCollisionDamageEffect) + { + case DAMAGE_EFFECT_CHANGE_MODEL: + bRenderDamaged = true; + break; + case DAMAGE_EFFECT_SPLIT_MODEL: + break; + case DAMAGE_EFFECT_SMASH_COMPLETELY: + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + break; + case DAMAGE_EFFECT_CHANGE_THEN_SMASH: + if (!bRenderDamaged) { + bRenderDamaged = true; + } + else { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + } + break; + case DAMAGE_EFFECT_SMASH_CARDBOARD_COMPLETELY: { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + const RwRGBA color = { 96, 48, 0, 255 }; + for (int32 i = 0; i < 25; i++) { + CVector vecDir(CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f) + fDirectionZ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + float fRandom = CGeneral::GetRandomNumberInRange(0.01f, 1.0f); + RwRGBA randomColor = { uint8(color.red * fRandom), uint8(color.green * fRandom) , color.blue, color.alpha }; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, randomColor, nRotationSpeed, 0, currentFrame, 0); + } + PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_2, vecPos); + break; + } + case DAMAGE_EFFECT_SMASH_WOODENBOX_COMPLETELY: { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + const RwRGBA color = { 128, 128, 128, 255 }; + for (int32 i = 0; i < 45; i++) { + CVector vecDir(CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f) + fDirectionZ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + float fRandom = CGeneral::GetRandomNumberInRange(0.5f, 1.0f); + RwRGBA randomColor = { uint8(color.red * fRandom), uint8(color.green * fRandom), uint8(color.blue * fRandom), color.alpha }; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, randomColor, nRotationSpeed, 0, currentFrame, 0); + } + PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_1, vecPos); + break; + } + case DAMAGE_EFFECT_SMASH_TRAFFICCONE_COMPLETELY: { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + const RwRGBA color1 = { 200, 0, 0, 255 }; + const RwRGBA color2 = { 200, 200, 200, 255 }; + for (int32 i = 0; i < 10; i++) { + CVector vecDir(CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f) + fDirectionZ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + RwRGBA color = color2; + if (nFrameGen & 1) + color = color1; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, color, nRotationSpeed, 0, currentFrame, 0); + } + PlayOneShotScriptObject(SCRIPT_SOUND_TIRE_COLLISION, vecPos); + break; + } + case DAMAGE_EFFECT_SMASH_BARPOST_COMPLETELY: { + bIsVisible = false; + bUsesCollision = false; + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + const RwRGBA color1 = { 200, 0, 0, 255 }; + const RwRGBA color2 = { 200, 200, 200, 255 }; + for (int32 i = 0; i < 32; i++) { + CVector vecDir(CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f) + fDirectionZ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + RwRGBA color = color2; + if (nFrameGen & 1) + color = color1; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, color, nRotationSpeed, 0, currentFrame, 0); + } + PlayOneShotScriptObject(SCRIPT_SOUND_METAL_COLLISION, vecPos); + break; + } + } + } +} + +void +CObject::RefModelInfo(int32 modelId) +{ + m_nRefModelIndex = modelId; + CModelInfo::GetModelInfo(modelId)->AddRef(); +} + +void +CObject::Init(void) +{ + m_type = ENTITY_TYPE_OBJECT; + CObjectData::SetObjectData(GetModelIndex(), *this); + m_nEndOfLifeTime = 0; + ObjectCreatedBy = GAME_OBJECT; + SetIsStatic(true); + bIsPickup = false; + bPickupObjWithMessage = false; + bOutOfStock = false; + bGlassCracked = false; + bGlassBroken = false; + bHasBeenDamaged = false; + bUseVehicleColours = false; + m_nRefModelIndex = -1; + m_colour1 = 0; + m_colour2 = 0; + m_nBonusValue = 0; + m_pCollidingEntity = nil; + CColPoint point; + CEntity* outEntity = nil; + const CVector& vecPos = GetMatrix().GetPosition(); + if (CWorld::ProcessVerticalLine(vecPos, vecPos.z - 10.0f, point, outEntity, true, false, false, false, false, false, nil)) + m_pCurSurface = outEntity; + else + m_pCurSurface = nil; + if (GetModelIndex() == MI_BODYCAST) + nBodyCastHealth = 1000; + else if (GetModelIndex() == MI_BUOY) + bTouchingWater = true; +} + +bool +CObject::CanBeDeleted(void) +{ + switch (ObjectCreatedBy) { + case GAME_OBJECT: + return true; + case MISSION_OBJECT: + return false; + case TEMP_OBJECT: + return true; + case CUTSCENE_OBJECT: + return false; + default: + return true; + } +} + +void +CObject::DeleteAllMissionObjects() +{ + CObjectPool* objectPool = CPools::GetObjectPool(); + for (int32 i = 0; i < objectPool->GetSize(); i++) { + CObject* pObject = objectPool->GetSlot(i); + if (pObject && pObject->ObjectCreatedBy == MISSION_OBJECT) { + CWorld::Remove(pObject); + delete pObject; + } + } +} + +void +CObject::DeleteAllTempObjects() +{ + CObjectPool* objectPool = CPools::GetObjectPool(); + for (int32 i = 0; i < objectPool->GetSize(); i++) { + CObject* pObject = objectPool->GetSlot(i); + if (pObject && pObject->ObjectCreatedBy == TEMP_OBJECT) { + CWorld::Remove(pObject); + delete pObject; + } + } +} + +void +CObject::DeleteAllTempObjectsInArea(CVector point, float fRadius) +{ + CObjectPool *objectPool = CPools::GetObjectPool(); + for (int32 i = 0; i < objectPool->GetSize(); i++) { + CObject *pObject = objectPool->GetSlot(i); + if (pObject && pObject->ObjectCreatedBy == TEMP_OBJECT && (point - pObject->GetPosition()).MagnitudeSqr() < SQR(fRadius)) { + CWorld::Remove(pObject); + delete pObject; + } + } +} diff --git a/src/objects/Object.h b/src/objects/Object.h new file mode 100644 index 0000000..114a1a9 --- /dev/null +++ b/src/objects/Object.h @@ -0,0 +1,94 @@ +#pragma once + +#include "Physical.h" + +enum { + UNKNOWN_OBJECT = 0, + GAME_OBJECT = 1, + MISSION_OBJECT = 2, + TEMP_OBJECT = 3, + CUTSCENE_OBJECT = 4, +}; + +enum CollisionSpecialResponseCase +{ + COLLRESPONSE_NONE, + COLLRESPONSE_LAMPOST, + COLLRESPONSE_SMALLBOX, + COLLRESPONSE_BIGBOX, + COLLRESPONSE_FENCEPART, + COLLRESPONSE_UNKNOWN5 +}; + +enum CollisionDamageEffect +{ + DAMAGE_EFFECT_NONE, + DAMAGE_EFFECT_CHANGE_MODEL, + DAMAGE_EFFECT_SPLIT_MODEL, + DAMAGE_EFFECT_SMASH_COMPLETELY, + DAMAGE_EFFECT_CHANGE_THEN_SMASH, + + DAMAGE_EFFECT_SMASH_CARDBOARD_COMPLETELY = 50, + DAMAGE_EFFECT_SMASH_WOODENBOX_COMPLETELY = 60, + DAMAGE_EFFECT_SMASH_TRAFFICCONE_COMPLETELY = 70, + DAMAGE_EFFECT_SMASH_BARPOST_COMPLETELY = 80 +}; + +class CVehicle; +class CDummyObject; + +class CObject : public CPhysical +{ +public: + CMatrix m_objectMatrix; + float m_fUprootLimit; + int8 ObjectCreatedBy; + int8 bIsPickup : 1; + int8 bPickupObjWithMessage : 1; + int8 bOutOfStock : 1; + int8 bGlassCracked : 1; + int8 bGlassBroken : 1; + int8 bHasBeenDamaged : 1; + int8 bUseVehicleColours : 1; + int8 m_nBonusValue; + float m_fCollisionDamageMultiplier; + uint8 m_nCollisionDamageEffect; + uint8 m_nSpecialCollisionResponseCases; + bool m_bCameraToAvoidThisObject; + uint32 m_obj_unused1; + uint32 m_nEndOfLifeTime; + int16 m_nRefModelIndex; + CEntity *m_pCurSurface; + CEntity *m_pCollidingEntity; + int8 m_colour1, m_colour2; + + static int16 nNoTempObjects; + static int16 nBodyCastHealth; + + static void *operator new(size_t) throw(); + static void *operator new(size_t, int) throw(); + static void operator delete(void*, size_t) throw(); + static void operator delete(void*, int) throw(); + + CObject(void); + CObject(int32, bool); + CObject(CDummyObject*); + ~CObject(void); + + void ProcessControl(void); + void Teleport(CVector vecPos); + void Render(void); + bool SetupLighting(void); + void RemoveLighting(bool reset); + + void ObjectDamage(float amount); + void RefModelInfo(int32 modelId); + void Init(void); + bool CanBeDeleted(void); + + static void DeleteAllMissionObjects(); + static void DeleteAllTempObjects(); + static void DeleteAllTempObjectsInArea(CVector point, float fRadius); +}; + +VALIDATE_SIZE(CObject, 0x198); diff --git a/src/objects/ObjectData.cpp b/src/objects/ObjectData.cpp new file mode 100644 index 0000000..589cc3f --- /dev/null +++ b/src/objects/ObjectData.cpp @@ -0,0 +1,98 @@ +#include "common.h" + +#include "main.h" +#include "ModelInfo.h" +#include "Object.h" +#include "FileMgr.h" +#include "ObjectData.h" + +CObjectInfo CObjectData::ms_aObjectInfo[NUMOBJECTINFO]; + +// Another ugly file reader +void +CObjectData::Initialise(const char *filename) +{ + char *p, *lp; + char line[1024], name[256]; + int id; + float percentSubmerged; + int damageEffect, responseCase, camAvoid; + CBaseModelInfo *mi; + + CFileMgr::SetDir(""); + CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r"); + + id = 0; + p = (char*)work_buff; + while(*p != '*'){ + // skip over white space and comments + while(*p == ' ' || *p == '\n' || *p == '\r' || *p == ';') + if(*p == ';') + while(*p != '\n' && *p != '*') + p++; + else + p++; + + if(*p == '*') + break; + + // read one line + lp = line; + while(*p != '\n' && *p != '*'){ + *lp++ = *p == ',' ? ' ' : *p; + p++; + } + if(*p == '\n') + p++; + *lp = '\0'; // FIX: game wrote '\n' here + + assert(id < NUMOBJECTINFO); + sscanf(line, "%s %f %f %f %f %f %f %f %d %d %d", name, + &ms_aObjectInfo[id].m_fMass, + &ms_aObjectInfo[id].m_fTurnMass, + &ms_aObjectInfo[id].m_fAirResistance, + &ms_aObjectInfo[id].m_fElasticity, + &percentSubmerged, + &ms_aObjectInfo[id].m_fUprootLimit, + &ms_aObjectInfo[id].m_fCollisionDamageMultiplier, + &damageEffect, &responseCase, &camAvoid); + + ms_aObjectInfo[id].m_fBuoyancy = 100.0f/percentSubmerged * GRAVITY *ms_aObjectInfo[id].m_fMass; + ms_aObjectInfo[id].m_nCollisionDamageEffect = damageEffect; + ms_aObjectInfo[id].m_nSpecialCollisionResponseCases = responseCase; + ms_aObjectInfo[id].m_bCameraToAvoidThisObject = camAvoid; + + mi = CModelInfo::GetModelInfo(name, nil); + if(mi) + mi->SetObjectID(id++); + else + debug("CObjectData: Cannot find object %s\n", name); + } +} + +void +CObjectData::SetObjectData(int32 modelId, CObject &object) +{ + CObjectInfo *objinfo; + + if(CModelInfo::GetModelInfo(modelId)->GetObjectID() == -1) + return; + + objinfo = &ms_aObjectInfo[CModelInfo::GetModelInfo(modelId)->GetObjectID()]; + + object.m_fMass = objinfo->m_fMass; + object.m_fTurnMass = objinfo->m_fTurnMass; + object.m_fAirResistance = objinfo->m_fAirResistance; + object.m_fElasticity = objinfo->m_fElasticity; + object.m_fBuoyancy = objinfo->m_fBuoyancy; + object.m_fUprootLimit = objinfo->m_fUprootLimit; + object.m_fCollisionDamageMultiplier = objinfo->m_fCollisionDamageMultiplier; + object.m_nCollisionDamageEffect = objinfo->m_nCollisionDamageEffect; + object.m_nSpecialCollisionResponseCases = objinfo->m_nSpecialCollisionResponseCases; + object.m_bCameraToAvoidThisObject = objinfo->m_bCameraToAvoidThisObject; + if(object.m_fMass >= 99998.0f){ + object.bInfiniteMass = true; + object.bAffectedByGravity = false; + object.bExplosionProof = true; + } +} diff --git a/src/objects/ObjectData.h b/src/objects/ObjectData.h new file mode 100644 index 0000000..e25c1ae --- /dev/null +++ b/src/objects/ObjectData.h @@ -0,0 +1,27 @@ +#pragma once + +class CObject; + +class CObjectInfo +{ +public: + float m_fMass; + float m_fTurnMass; + float m_fAirResistance; + float m_fElasticity; + float m_fBuoyancy; + float m_fUprootLimit; + float m_fCollisionDamageMultiplier; + uint8 m_nCollisionDamageEffect; + uint8 m_nSpecialCollisionResponseCases; + bool m_bCameraToAvoidThisObject; +}; +VALIDATE_SIZE(CObjectInfo, 0x20); + +class CObjectData +{ + static CObjectInfo ms_aObjectInfo[NUMOBJECTINFO]; +public: + static void Initialise(const char *filename); + static void SetObjectData(int32 modelId, CObject &object); +}; diff --git a/src/objects/ParticleObject.cpp b/src/objects/ParticleObject.cpp new file mode 100644 index 0000000..5d480ec --- /dev/null +++ b/src/objects/ParticleObject.cpp @@ -0,0 +1,1365 @@ +#include "common.h" + +#include "ParticleObject.h" +#include "Timer.h" +#include "General.h" +#include "ParticleMgr.h" +#include "Particle.h" +#include "Camera.h" +#include "Game.h" +#include "DMAudio.h" +#include "screendroplets.h" + +#ifdef COMPATIBLE_SAVES +#define PARTICLE_OBJECT_SIZEOF 0x88 +#else +#define PARTICLE_OBJECT_SIZEOF sizeof(CParticleObject) +#endif + + +CParticleObject gPObjectArray[MAX_PARTICLEOBJECTS]; + +CParticleObject *CParticleObject::pCloseListHead; +CParticleObject *CParticleObject::pFarListHead; +CParticleObject *CParticleObject::pUnusedListHead; + +CAudioHydrant List[MAX_AUDIOHYDRANTS]; + +CAudioHydrant *CAudioHydrant::Get(int n) { return &List[n]; } + +bool +CAudioHydrant::Add(CParticleObject *particleobject) +{ + for ( int32 i = 0; i < MAX_AUDIOHYDRANTS; i++ ) + { + if ( List[i].AudioEntity == AEHANDLE_NONE ) + { + List[i].AudioEntity = DMAudio.CreateEntity(AUDIOTYPE_FIREHYDRANT, particleobject); + + if ( AEHANDLE_IS_FAILED(List[i].AudioEntity) ) + return false; + + DMAudio.SetEntityStatus(List[i].AudioEntity, TRUE); + + List[i].pParticleObject = particleobject; + + return true; + } + } + + return false; +} + +void +CAudioHydrant::Remove(CParticleObject *particleobject) +{ + for ( int32 i = 0; i < MAX_AUDIOHYDRANTS; i++ ) + { + if ( List[i].pParticleObject == particleobject ) + { + DMAudio.DestroyEntity(List[i].AudioEntity); + List[i].AudioEntity = AEHANDLE_NONE; + List[i].pParticleObject = NULL; + } + } +} + +CParticleObject::CParticleObject() : + CPlaceable(), + m_nFrameCounter(0), + m_nState(POBJECTSTATE_INITIALISED), + m_pNext(NULL), + m_pPrev(NULL), + m_nRemoveTimer(0) + +{ + ; +} + +CParticleObject::~CParticleObject() +{ + +} + +void +CParticleObject::Initialise() +{ + pCloseListHead = NULL; + pFarListHead = NULL; + + pUnusedListHead = &gPObjectArray[0]; + + for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ ) + { + if ( i == 0 ) + gPObjectArray[i].m_pPrev = NULL; + else + gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1]; + + if ( i == MAX_PARTICLEOBJECTS-1 ) + gPObjectArray[i].m_pNext = NULL; + else + gPObjectArray[i].m_pNext = &gPObjectArray[i + 1]; + + gPObjectArray[i].m_nState = POBJECTSTATE_FREE; + } +} + +CParticleObject * +CParticleObject::AddObject(uint16 type, CVector const &pos, uint8 remove) +{ + CRGBA color(0, 0, 0, 0); + CVector target(0.0f, 0.0f, 0.0f); + return AddObject(type, pos, target, 0.0f, 0, color, remove); +} + +CParticleObject * +CParticleObject::AddObject(uint16 type, CVector const &pos, float size, uint8 remove) +{ + CRGBA color(0, 0, 0, 0); + CVector target(0.0f, 0.0f, 0.0f); + return AddObject(type, pos, target, size, 0, color, remove); +} + +CParticleObject * +CParticleObject::AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint8 remove) +{ + CRGBA color(0, 0, 0, 0); + return AddObject(type, pos, target, size, 0, color, remove); +} + +CParticleObject * +CParticleObject::AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, RwRGBA const &color, uint8 remove) +{ + CParticleObject *pobj = pUnusedListHead; + + ASSERT(pobj != NULL); + + if ( pobj == NULL ) + { + printf("Error: No particle objects available!\n"); + return NULL; + } + + MoveToList(&pUnusedListHead, &pCloseListHead, pobj); + + pobj->m_nState = POBJECTSTATE_UPDATE_CLOSE; + pobj->m_Type = (eParticleObjectType)type; + + pobj->SetPosition(pos); + pobj->m_vecTarget = target; + + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_nFrameCounter = 0; + + pobj->m_bRemove = remove; + + pobj->m_pParticle = NULL; + + if ( lifeTime != 0 ) + pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds() + lifeTime; + else + pobj->m_nRemoveTimer = 0; + + if ( color.alpha != 0 ) + pobj->m_Color = color; + else + pobj->m_Color.alpha = 0; + + pobj->m_fSize = size; + pobj->m_fRandVal = 0.0f; + + if ( type <= POBJECT_CATALINAS_SHOTGUNFLASH ) + { + switch ( type ) + { + case POBJECT_PAVEMENT_STEAM: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 3; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 8; + break; + } + + case POBJECT_PAVEMENT_STEAM_SLOWMOTION: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY_SLOWMOTION; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 8; + break; + } + + case POBJECT_WALL_STEAM: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 3; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 8; + break; + } + + case POBJECT_WALL_STEAM_SLOWMOTION: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY_SLOWMOTION; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 8; + break; + } + + case POBJECT_DARK_SMOKE: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 3; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 8; + pobj->m_Color = CRGBA(16, 16, 16, 255); + break; + } + + case POBJECT_FIRE_HYDRANT: + { + pobj->m_ParticleType = PARTICLE_WATER_HYDRANT; + pobj->m_nNumEffectCycles = 4; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.3f); + pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds() + 5000; + CAudioHydrant::Add(pobj); + break; + } + + case POBJECT_CAR_WATER_SPLASH: + case POBJECT_PED_WATER_SPLASH: + { + pobj->m_ParticleType = PARTICLE_CAR_SPLASH; + pobj->m_nNumEffectCycles = 0; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 1; +#else + pobj->m_nSkipFrames = 3; +#endif + pobj->m_nCreationChance = 0; +#ifdef SCREEN_DROPLETS + ScreenDroplets::RegisterSplash(pobj); +#endif + break; + } + + case POBJECT_SPLASHES_AROUND: + { + pobj->m_ParticleType = PARTICLE_SPLASH; +#ifdef PC_PARTICLE + pobj->m_nNumEffectCycles = 15; +#else + pobj->m_nNumEffectCycles = 30; +#endif + pobj->m_nSkipFrames = 2; + pobj->m_nCreationChance = 0; + break; + } + + case POBJECT_SMALL_FIRE: + { + pobj->m_ParticleType = PARTICLE_FLAME; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 2; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 2; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f); + break; + } + + case POBJECT_BIG_FIRE: + { + pobj->m_ParticleType = PARTICLE_FLAME; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 2; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 4; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f); + break; + } + + case POBJECT_DRY_ICE: + { + pobj->m_ParticleType = PARTICLE_SMOKE; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f); + break; + } + + case POBJECT_DRY_ICE_SLOWMOTION: + { + pobj->m_ParticleType = PARTICLE_SMOKE_SLOWMOTION; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.0f); + break; + } + + case POBJECT_FIRE_TRAIL: + { + pobj->m_ParticleType = PARTICLE_EXPLOSION_MEDIUM; + pobj->m_nNumEffectCycles = 1; +#ifdef PC_PARTICLE + pobj->m_nSkipFrames = 3; +#else + pobj->m_nSkipFrames = 1; +#endif + pobj->m_nCreationChance = 2; + pobj->m_fRandVal = 0.01f; + break; + } + + case POBJECT_SMOKE_TRAIL: + { + pobj->m_ParticleType = PARTICLE_FIREBALL_SMOKE; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 2; + pobj->m_fRandVal = 0.02f; + break; + } + + case POBJECT_FIREBALL_AND_SMOKE: + { + pobj->m_ParticleType = PARTICLE_FLAME; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 2; + pobj->m_fRandVal = 0.1f; + break; + } + + case POBJECT_ROCKET_TRAIL: + { + pobj->m_ParticleType = PARTICLE_FLAME; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 2; + pobj->m_nCreationChance = 8; + pobj->m_fRandVal = 0.1f; + break; + } + + case POBJECT_EXPLOSION_ONCE: + { + pobj->m_ParticleType = PARTICLE_EXPLOSION_LARGE; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds(); + break; + } + + case POBJECT_CATALINAS_GUNFLASH: + case POBJECT_CATALINAS_SHOTGUNFLASH: + { + pobj->m_ParticleType = PARTICLE_GUNFLASH_NOANIM; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds(); + pobj->m_vecTarget.Normalise(); + break; + } + } + } + + return pobj; +} + +void +CParticleObject::RemoveObject(void) +{ + switch ( this->m_nState ) + { + case POBJECTSTATE_UPDATE_CLOSE: + { + MoveToList(&pCloseListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + break; + } + case POBJECTSTATE_UPDATE_FAR: + { + MoveToList(&pFarListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + break; + } + } +} + +void +CParticleObject::UpdateAll(void) +{ + { + CParticleObject *pobj = pCloseListHead; + CParticleObject *nextpobj; + if ( pobj != NULL ) + { + do + { + nextpobj = pobj->m_pNext; + pobj->UpdateClose(); + pobj = nextpobj; + } + while ( nextpobj != NULL ); + } + } + + { + int32 frame = CTimer::GetFrameCounter() & 31; + int32 counter = 0; + + CParticleObject *pobj = pFarListHead; + CParticleObject *nextpobj; + if ( pobj != NULL ) + { + do + { + nextpobj = pobj->m_pNext; + + if ( counter == frame ) + { + pobj->UpdateFar(); + frame += 32; + } + + counter++; + + pobj = nextpobj; + } + while ( nextpobj != NULL ); + } + } +} + +void CParticleObject::UpdateClose(void) +{ + if ( !CGame::playingIntro ) + { + if ( (this->GetPosition() - TheCamera.GetPosition()).MagnitudeSqr2D() > SQR(100.0f) ) + { + if ( this->m_bRemove ) + { + if ( this->m_Type == POBJECT_FIRE_HYDRANT ) + CAudioHydrant::Remove(this); + + MoveToList(&pCloseListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + } + else + { + MoveToList(&pCloseListHead, &pFarListHead, this); + this->m_nState = POBJECTSTATE_UPDATE_FAR; + } + + return; + } + } + + if ( ++this->m_nFrameCounter >= this->m_nSkipFrames ) + { + this->m_nFrameCounter = 0; + + int32 randVal; + if ( this->m_nCreationChance != 0 ) + randVal = CGeneral::GetRandomNumber() % this->m_nCreationChance; + + if ( this->m_nCreationChance == 0 + || randVal == 0 && this->m_nCreationChance < 0 + || randVal != 0 && this->m_nCreationChance > 0) + { + switch ( this->m_Type ) + { + case POBJECT_SMALL_FIRE: + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + CVector flamevel; + + flamevel.x = vel.x; + flamevel.y = vel.y; + flamevel.z = CGeneral::GetRandomNumberInRange(0.0125f*size, 0.1f*size); + + CParticle::AddParticle(PARTICLE_FLAME, pos, flamevel, NULL, size); + + + CVector possmoke = pos; + + possmoke.x += CGeneral::GetRandomNumberInRange(0.625f*-size, size*0.625f); + possmoke.y += CGeneral::GetRandomNumberInRange(0.625f*-size, size*0.625f); + possmoke.z += CGeneral::GetRandomNumberInRange(0.625f* size, size*2.5f); + + CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, possmoke, vel); + + break; + } + + case POBJECT_BIG_FIRE: + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + + float s = 0.7f*size; + + CVector flamevel; + + flamevel.x = vel.x; + flamevel.y = vel.y; + flamevel.z = CGeneral::GetRandomNumberInRange(0.0125f*s, 0.1f*s); + + float flamesize = 0.8f*size; + + CParticle::AddParticle(PARTICLE_FLAME, pos, flamevel, NULL, flamesize); + + + for ( int32 i = 0; i < 4; i++ ) + { + CVector smokepos = pos; + + smokepos.x += CGeneral::GetRandomNumberInRange(0.625f*-size, 0.625f*size); + smokepos.y += CGeneral::GetRandomNumberInRange(0.625f*-size, 0.625f*size); + smokepos.z += CGeneral::GetRandomNumberInRange(0.625f* size, 3.5f *size); + + CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, smokepos, vel); + } + + break; + } + + case POBJECT_FIREBALL_AND_SMOKE: + { + if ( this->m_pParticle == NULL ) + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + CVector expvel = 1.2f*vel; + float expsize = 1.2f*size; + this->m_pParticle = CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, expvel, NULL, expsize); + } + else + { + CVector pos = this->GetPosition(); // this->m_pParticle->m_vecPosition ? + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + CVector veloffset = 0.35f*vel; + CVector fireballvel = vel; + + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + fireballvel.x += CGeneral::GetRandomNumberInRange(-veloffset.x, veloffset.x); + fireballvel.y += CGeneral::GetRandomNumberInRange(-veloffset.y, veloffset.y); + fireballvel.z += CGeneral::GetRandomNumberInRange(-veloffset.z, veloffset.z); + + CParticle::AddParticle(PARTICLE_FIREBALL_SMOKE, pos, fireballvel, NULL, size); + } + } + + break; + } + + case POBJECT_ROCKET_TRAIL: + { + if ( this->m_pParticle == NULL ) + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + this->m_pParticle = CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, vel, NULL, size); + } + else + { + CVector pos = this->m_pParticle->m_vecPosition; + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + float fireballsize = size * 1.5f; + CVector fireballvel = vel * -0.8f; + + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CParticle::AddParticle(PARTICLE_FIREBALL_SMOKE, pos, fireballvel, NULL, fireballsize); + } + } + + break; + } + + case POBJECT_FIRE_TRAIL: + { + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CVector vel = this->m_vecTarget; + + if ( vel.x != 0.0f ) + vel.x += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + if ( vel.y != 0.0f ) + vel.y += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + if ( vel.z != 0.0f ) + vel.z += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), vel, NULL, this->m_fSize, + CGeneral::GetRandomNumberInRange(-6.0f, 6.0f)); + } + + break; + } + + case POBJECT_PED_WATER_SPLASH: + { +#ifdef PC_PARTICLE + CRGBA colorsmoke(255, 255, 255, 196); + + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + + for ( int32 i = 0; i < 3; i++ ) + { + int32 angle = 90 * i; + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos; + CVector splashvel; + + splashpos = pos + CVector(0.75f*fCos, 0.75f*fSin, 0.0f); + splashvel = vel + CVector(0.05f*fCos, 0.05f*fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f)); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color); + + + splashpos = pos + CVector(0.75f*fCos, 0.75f*-fSin, 0.0f); + splashvel = vel + CVector(0.05f*fCos, 0.05f*-fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f)); + + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color); + + + splashpos = pos + CVector(0.75f*-fCos, 0.75f*fSin, 0.0f); + splashvel = vel + CVector(0.05f*-fCos, 0.05f*fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f)); + + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color); + + + splashpos = pos + CVector(0.75f*-fCos, 0.75f*-fSin, 0.0f); + splashvel = vel + CVector(0.05f*-fCos, 0.05f*-fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f)); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color); + } + + for ( int32 i = 0; i < 1; i++ ) + { + int32 angle = 180 * (i + 1); + + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos; + CVector splashvel; + + splashpos = pos + CVector(0.5f*fCos, 0.5f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color); + + + splashpos = pos + CVector(0.5f*fCos, 0.5f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color); + + + splashpos = pos + CVector(0.5f*-fCos, 0.5f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color); + + + splashpos = pos + CVector(0.5f*-fCos, 0.5f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, + CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color); + } +#else + CVector pos; + CVector vel; + + for ( int32 i = -2; i < 2; i++ ) + { + pos = this->GetPosition(); + pos += CVector(-0.75f, 0.5f * float(i), 0.0f); + + vel = this->m_vecTarget; + vel.x += -1.5 * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + + pos = this->GetPosition(); + pos += CVector(0.75f, 0.5f * float(i), 0.0f); + + vel = this->m_vecTarget; + vel.x += 1.5f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + + pos = this->GetPosition(); + pos += CVector(0.5f * float(i), -0.75, 0.0f); + + vel = this->m_vecTarget; + vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += -1.5f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + + + pos = this->GetPosition(); + pos += CVector(0.5f * float(i), 0.75, 0.0f); + + vel = this->m_vecTarget; + vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += 1.5f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + } + + + for ( int32 i = 0; i < 4; i++ ) + { + pos = this->GetPosition(); + + pos.x += CGeneral::GetRandomNumberInRange(-1.5f, 1.5f); + pos.y += CGeneral::GetRandomNumberInRange(-1.5f, 1.5f); + pos.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + + vel = this->m_vecTarget; + CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color); + } +#endif + break; + } + + case POBJECT_CAR_WATER_SPLASH: + { +#ifdef PC_PARTICLE + CRGBA colorsmoke(255, 255, 255, 196); + + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + + float size = CGeneral::GetRandomNumberInRange(1.0f, 2.5f); + + for ( int32 i = 0; i < 3; i++ ) + { + int32 angle = 90 * i; + + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos; + CVector splashvel; + + splashpos = pos + CVector(2.0f*fCos, 2.0f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + + splashpos = pos + CVector(2.0f*fCos, 2.0f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(2.0f*-fCos, 2.0f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(2.0f*-fCos, 2.0f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + } + + for ( int32 i = 0; i < 1; i++ ) + { + int32 angle = 180 * (i + 1); + + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos; + CVector splashvel; + + + splashpos = pos + CVector(1.25f*fCos, 1.25f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(1.25f*fCos, 1.25f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(1.25f*-fCos, 1.25f*fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + + splashpos = pos + CVector(1.25f*-fCos, 1.25f*-fSin, 0.0f); + splashvel = vel; + splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fCos; + splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fSin; + splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color); + } +#else + CVector pos; + CVector vel; + + for ( int32 i = -3; i < 4; i++ ) + { + pos = this->GetPosition(); + pos += CVector(-1.5f, 0.5f * float(i), 0.0f); + + + vel = this->m_vecTarget; + vel.x += -3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + + + pos = this->GetPosition(); + pos += CVector(1.5f, 0.5f * float(i), 0.0f); + + vel = this->m_vecTarget; + vel.x += 3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + + + pos = this->GetPosition(); + pos += CVector(0.5f * float(i), -1.5f, 0.0f); + + vel = this->m_vecTarget; + vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += -3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + + + pos = this->GetPosition(); + pos += CVector(0.5f * float(i), 1.5f, 0.0f); + + + vel = this->m_vecTarget; + vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.y += 3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f); + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + } + + for ( int32 i = 0; i < 8; i++ ) + { + pos = this->GetPosition(); + pos.x += CGeneral::GetRandomNumberInRange(-3.0f, 3.0f); + pos.y += CGeneral::GetRandomNumberInRange(-3.0f, 3.0f); + + vel = this->m_vecTarget; + vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color); + } +#endif + break; + } + + case POBJECT_SPLASHES_AROUND: + { + CVector pos = this->GetPosition(); + float size = this->m_fSize; + + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CVector splashpos = pos; + + splashpos.x += CGeneral::GetRandomNumberInRange(-size, size); + splashpos.y += CGeneral::GetRandomNumberInRange(-size, size); + + if ( CGeneral::GetRandomNumber() & 1 ) + { + CParticle::AddParticle(PARTICLE_RAIN_SPLASH, splashpos, CVector(0.0f, 0.0f, 0.0f), + NULL, 0.1f, this->m_Color); + } + else + { + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, splashpos, CVector(0.0f, 0.0f, 0.0f), + NULL, 0.12f, this->m_Color); + } + } + + break; + } + + case POBJECT_CATALINAS_GUNFLASH: + { + CRGBA flashcolor(120, 120, 120, 255); + + CVector vel = this->m_vecTarget; + CVector pos = this->GetPosition(); + + float size = 1.0f; + if ( this->m_fSize != 0.0f ) + size = this->m_fSize; + + CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.12f*size, flashcolor); + + pos += size * (0.06f * vel); + CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.08f*size, flashcolor); + + pos += size * (0.04f * vel); + CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.04f*size, flashcolor); + + CVector smokepos = this->GetPosition(); + CVector smokevel = 0.1f * vel; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, smokepos, smokevel, NULL, 0.005f*size); + + break; + } + + case POBJECT_CATALINAS_SHOTGUNFLASH: + { + CRGBA flashcolor(120, 120, 120, 255); + + CVector vel = this->m_vecTarget; + + float size = 1.0f; + if ( this->m_fSize != 0.0f ) + size = this->m_fSize; + + CVector pos = this->GetPosition(); + + CVector velstep = size * (0.1f * vel); + CVector flashpos = pos; + + flashpos += velstep; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashpos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.0f, flashcolor); + + flashpos += velstep; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashpos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.15f*size, flashcolor); + + flashpos += velstep; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashpos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.2f*size, flashcolor); + + + CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.0f, flashcolor); + + CVector smokepos = this->GetPosition(); + CVector smokevel = 0.1f*vel; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, smokepos, smokevel, NULL, 0.1f*size); + + break; + } + + default: + { + if ( this->m_fRandVal != 0.0f ) + { + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CVector vel = this->m_vecTarget; + + if ( vel.x != 0.0f ) + vel.x += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + if ( vel.y != 0.0f ) + vel.y += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + if ( vel.z != 0.0f ) + vel.z += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal); + + CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), vel, NULL, + this->m_fSize, this->m_Color); + } + } + else + { + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), this->m_vecTarget, NULL, + this->m_fSize, this->m_Color); + } + } + + break; + } + } + } + } + + if ( this->m_nRemoveTimer != 0 && this->m_nRemoveTimer < CTimer::GetTimeInMilliseconds() ) + { + MoveToList(&pCloseListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + + if ( this->m_Type == POBJECT_FIRE_HYDRANT ) + CAudioHydrant::Remove(this); + } +} + +void +CParticleObject::UpdateFar(void) +{ + if ( this->m_nRemoveTimer != 0 && this->m_nRemoveTimer < CTimer::GetTimeInMilliseconds() ) + { + MoveToList(&pFarListHead, &pUnusedListHead, this); + this->m_nState = POBJECTSTATE_FREE; + + if ( this->m_Type == POBJECT_FIRE_HYDRANT ) + CAudioHydrant::Remove(this); + } + + CVector2D dist = this->GetPosition() - TheCamera.GetPosition(); + if ( dist.MagnitudeSqr() < SQR(100.0f)/*10000.0f*/ ) + { + MoveToList(&pFarListHead, &pCloseListHead, this); + this->m_nState = POBJECTSTATE_UPDATE_CLOSE; + } +} + +#ifdef COMPATIBLE_SAVES +static inline void +SaveOneParticle(CParticleObject *p, uint8 *&buffer) +{ +#define SkipBuf(buf, num) buf += num +#define ZeroBuf(buf, num) memset(buf, 0, num); SkipBuf(buf, num) +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipBuf(buf, sizeof(data)) + // CPlaceable + { + ZeroBuf(buffer, 4); + CopyToBuf(buffer, p->GetMatrix().f); + ZeroBuf(buffer, 4); + CopyToBuf(buffer, p->GetMatrix().m_hasRwMatrix); + ZeroBuf(buffer, 3); + } + + // CParticleObject + { + ZeroBuf(buffer, 4); + ZeroBuf(buffer, 4); + ZeroBuf(buffer, 4); + CopyToBuf(buffer, p->m_nRemoveTimer); + CopyToBuf(buffer, p->m_Type); + CopyToBuf(buffer, p->m_ParticleType); + CopyToBuf(buffer, p->m_nNumEffectCycles); + CopyToBuf(buffer, p->m_nSkipFrames); + CopyToBuf(buffer, p->m_nFrameCounter); + CopyToBuf(buffer, p->m_nState); + ZeroBuf(buffer, 2); + CopyToBuf(buffer, p->m_vecTarget); + CopyToBuf(buffer, p->m_fRandVal); + CopyToBuf(buffer, p->m_fSize); + CopyToBuf(buffer, p->m_Color); + CopyToBuf(buffer, p->m_bRemove); + CopyToBuf(buffer, p->m_nCreationChance); + ZeroBuf(buffer, 2); + } +#undef SkipBuf +#undef ZeroBuf +#undef CopyToBuf +} +#endif + +bool +CParticleObject::SaveParticle(uint8 *buffer, uint32 *length) +{ + ASSERT( buffer != NULL ); + ASSERT( length != NULL ); + + int32 numObjects = 0; + + for ( CParticleObject *p = pCloseListHead; p != NULL; p = p->m_pNext ) + ++numObjects; + + for ( CParticleObject *p = pFarListHead; p != NULL; p = p->m_pNext ) + ++numObjects; + + *(int32 *)buffer = numObjects; + buffer += sizeof(int32); + + int32 objectsLength = PARTICLE_OBJECT_SIZEOF * (numObjects + 1); + int32 dataLength = objectsLength + sizeof(int32); + + for ( CParticleObject *p = pCloseListHead; p != NULL; p = p->m_pNext ) + { +#ifdef COMPATIBLE_SAVES + SaveOneParticle(p, buffer); +#else +#ifdef THIS_IS_STUPID + *(CParticleObject*)buffer = *p; +#else + memcpy(buffer, p, sizeof(CParticleObject)); +#endif + buffer += sizeof(CParticleObject); +#endif + } + + for ( CParticleObject *p = pFarListHead; p != NULL; p = p->m_pNext ) + { +#ifdef COMPATIBLE_SAVES + SaveOneParticle(p, buffer); +#else +#ifdef THIS_IS_STUPID + *(CParticleObject*)buffer = *p; +#else + memcpy(buffer, p, sizeof(CParticleObject)); +#endif + buffer += sizeof(CParticleObject); +#endif + } + + *length = dataLength; + + return true; +} + +bool +CParticleObject::LoadParticle(uint8 *buffer, uint32 length) +{ + ASSERT( buffer != NULL ); + + RemoveAllParticleObjects(); + + int32 numObjects = *(int32 *)buffer; + buffer += sizeof(int32); + + if ( length != PARTICLE_OBJECT_SIZEOF * (numObjects + 1) + sizeof(int32) ) + return false; + + if ( numObjects == 0 ) + return true; + + + int32 i = 0; + while ( i < numObjects ) + { + CParticleObject *dst = pUnusedListHead; +#ifndef COMPATIBLE_SAVES + CParticleObject *src = (CParticleObject *)buffer; + buffer += sizeof(CParticleObject); +#endif + + if ( dst == NULL ) + return false; + + MoveToList(&pUnusedListHead, &pCloseListHead, dst); + +#ifndef COMPATIBLE_SAVES + dst->m_nState = POBJECTSTATE_UPDATE_CLOSE; + dst->m_Type = src->m_Type; + dst->m_ParticleType = src->m_ParticleType; + dst->SetPosition(src->GetPosition()); + dst->m_vecTarget = src->m_vecTarget; + dst->m_nFrameCounter = src->m_nFrameCounter; + dst->m_bRemove = src->m_bRemove; + dst->m_pParticle = NULL; + dst->m_nRemoveTimer = src->m_nRemoveTimer; + dst->m_Color = src->m_Color; + dst->m_fSize = src->m_fSize; + dst->m_fRandVal = src->m_fRandVal; + dst->m_nNumEffectCycles = src->m_nNumEffectCycles; + dst->m_nSkipFrames = src->m_nSkipFrames; + dst->m_nCreationChance = src->m_nCreationChance; +#else + dst->m_nState = POBJECTSTATE_UPDATE_CLOSE; + dst->m_pParticle = NULL; + +#define SkipBuf(buf, num) buf += num +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipBuf(buf, sizeof(data)) + // CPlaceable + { + SkipBuf(buffer, 4); + CMatrix matrix; + CopyFromBuf(buffer, matrix.f); + SkipBuf(buffer, 4); + CopyFromBuf(buffer, matrix.m_hasRwMatrix); + SkipBuf(buffer, 3); + dst->SetPosition(matrix.GetPosition()); + } + + // CParticleObject + { + SkipBuf(buffer, 4); + SkipBuf(buffer, 4); + SkipBuf(buffer, 4); + CopyFromBuf(buffer, dst->m_nRemoveTimer); + CopyFromBuf(buffer, dst->m_Type); + CopyFromBuf(buffer, dst->m_ParticleType); + CopyFromBuf(buffer, dst->m_nNumEffectCycles); + CopyFromBuf(buffer, dst->m_nSkipFrames); + CopyFromBuf(buffer, dst->m_nFrameCounter); + SkipBuf(buffer, 2); + SkipBuf(buffer, 2); + CopyFromBuf(buffer, dst->m_vecTarget); + CopyFromBuf(buffer, dst->m_fRandVal); + CopyFromBuf(buffer, dst->m_fSize); + CopyFromBuf(buffer, dst->m_Color); + CopyFromBuf(buffer, dst->m_bRemove); + CopyFromBuf(buffer, dst->m_nCreationChance); + SkipBuf(buffer, 2); + } +#undef CopyFromBuf +#undef SkipBuf +#endif + + i++; + } + + return true; +} + +void +CParticleObject::RemoveAllParticleObjects(void) +{ + pUnusedListHead = &gPObjectArray[0]; + + pCloseListHead = NULL; + pFarListHead = NULL; + + for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ ) + { + if ( i == 0 ) + gPObjectArray[i].m_pPrev = NULL; + else + gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1]; + + if ( i == MAX_PARTICLEOBJECTS-1 ) + gPObjectArray[i].m_pNext = NULL; + else + gPObjectArray[i].m_pNext = &gPObjectArray[i + 1]; + + gPObjectArray[i].m_nState = POBJECTSTATE_FREE; + } +} + +void +CParticleObject::MoveToList(CParticleObject **from, CParticleObject **to, CParticleObject *obj) +{ + ASSERT( from != NULL ); + ASSERT( to != NULL ); + ASSERT( obj != NULL ); + + if ( obj->m_pPrev == NULL ) + { + *from = obj->m_pNext; + if ( *from ) + (*from)->m_pPrev = NULL; + } + else + { + if ( obj->m_pNext == NULL ) + obj->m_pPrev->m_pNext = NULL; + else + { + obj->m_pNext->m_pPrev = obj->m_pPrev; + obj->m_pPrev->m_pNext = obj->m_pNext; + } + } + + obj->m_pNext = *to; + obj->m_pPrev = NULL; + *to = obj; + + if ( obj->m_pNext ) + obj->m_pNext->m_pPrev = obj; +} diff --git a/src/objects/ParticleObject.h b/src/objects/ParticleObject.h new file mode 100644 index 0000000..e4e7fcd --- /dev/null +++ b/src/objects/ParticleObject.h @@ -0,0 +1,108 @@ +#pragma once + +#include "AudioManager.h" +#include "ParticleType.h" +#include "Placeable.h" + +#define MAX_PARTICLEOBJECTS 100 +#define MAX_AUDIOHYDRANTS 8 + +enum eParticleObjectType +{ + POBJECT_PAVEMENT_STEAM, + POBJECT_PAVEMENT_STEAM_SLOWMOTION, + POBJECT_WALL_STEAM, + POBJECT_WALL_STEAM_SLOWMOTION, + POBJECT_DARK_SMOKE, + POBJECT_FIRE_HYDRANT, + POBJECT_CAR_WATER_SPLASH, + POBJECT_PED_WATER_SPLASH, + POBJECT_SPLASHES_AROUND, + POBJECT_SMALL_FIRE, + POBJECT_BIG_FIRE, + POBJECT_DRY_ICE, + POBJECT_DRY_ICE_SLOWMOTION, + POBJECT_FIRE_TRAIL, + POBJECT_SMOKE_TRAIL, + POBJECT_FIREBALL_AND_SMOKE, + POBJECT_ROCKET_TRAIL, + POBJECT_EXPLOSION_ONCE, + POBJECT_CATALINAS_GUNFLASH, + POBJECT_CATALINAS_SHOTGUNFLASH, +}; + +enum eParticleObjectState +{ + POBJECTSTATE_INITIALISED = 0, + POBJECTSTATE_UPDATE_CLOSE, + POBJECTSTATE_UPDATE_FAR, + POBJECTSTATE_FREE, +}; + +class CParticle; + +class CParticleObject : public CPlaceable +{ +public: + CParticleObject *m_pNext; + CParticleObject *m_pPrev; + CParticle *m_pParticle; + uint32 m_nRemoveTimer; + eParticleObjectType m_Type; + tParticleType m_ParticleType; + uint8 m_nNumEffectCycles; + uint8 m_nSkipFrames; + uint16 m_nFrameCounter; + uint16 m_nState; + CVector m_vecTarget; + float m_fRandVal; + float m_fSize; + CRGBA m_Color; + uint8 m_bRemove; + int8 m_nCreationChance; + + static CParticleObject *pCloseListHead; + static CParticleObject *pFarListHead; + static CParticleObject *pUnusedListHead; + + CParticleObject(); + ~CParticleObject(); + + static void Initialise(void); + + static CParticleObject *AddObject(uint16 type, CVector const &pos, uint8 remove); + static CParticleObject *AddObject(uint16 type, CVector const &pos, float size, uint8 remove); + static CParticleObject *AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint8 remove); + static CParticleObject *AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, RwRGBA const &color, uint8 remove); + + void RemoveObject(void); + + static void UpdateAll(void); + void UpdateClose(void); + void UpdateFar(void); + + static bool SaveParticle(uint8 *buffer, uint32 *length); + static bool LoadParticle(uint8 *buffer, uint32 length); + + static void RemoveAllParticleObjects(void); + static void MoveToList(CParticleObject **from, CParticleObject **to, CParticleObject *obj); +}; + +extern CParticleObject gPObjectArray[MAX_PARTICLEOBJECTS]; + +class CAudioHydrant +{ +public: + int32 AudioEntity; + CParticleObject *pParticleObject; + + CAudioHydrant() : + AudioEntity(AEHANDLE_NONE), + pParticleObject(NULL) + { } + + static bool Add (CParticleObject *particleobject); + static void Remove(CParticleObject *particleobject); + + static CAudioHydrant *Get(int n); // for neo screen droplets +}; \ No newline at end of file diff --git a/src/objects/Projectile.cpp b/src/objects/Projectile.cpp new file mode 100644 index 0000000..fe8b0c6 --- /dev/null +++ b/src/objects/Projectile.cpp @@ -0,0 +1,15 @@ +#include "common.h" + +#include "Projectile.h" + +CProjectile::CProjectile(int32 model) : CObject() +{ + m_fMass = 1.0f; + m_fTurnMass = 1.0f; + m_fAirResistance = 0.99999f; + m_fElasticity = 0.75f; + m_fBuoyancy = GRAVITY * m_fMass * 0.1f; + bExplosionProof = true; + SetModelIndex(model); + ObjectCreatedBy = MISSION_OBJECT; +} diff --git a/src/objects/Projectile.h b/src/objects/Projectile.h new file mode 100644 index 0000000..4b3eb4b --- /dev/null +++ b/src/objects/Projectile.h @@ -0,0 +1,9 @@ +#pragma once + +#include "Object.h" + +class CProjectile : public CObject +{ +public: + CProjectile(int32); +}; diff --git a/src/peds/CivilianPed.cpp b/src/peds/CivilianPed.cpp new file mode 100644 index 0000000..1c4f10f --- /dev/null +++ b/src/peds/CivilianPed.cpp @@ -0,0 +1,467 @@ +#include "common.h" + +#include "CivilianPed.h" +#include "Phones.h" +#include "General.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "World.h" +#include "Vehicle.h" +#include "SurfaceTable.h" + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +eCrimeType +EventTypeToCrimeType(eEventType event) +{ + eCrimeType crime; + switch (event) { + case EVENT_ASSAULT: crime = CRIME_HIT_PED; break; + case EVENT_RUN_REDLIGHT: crime = CRIME_RUN_REDLIGHT; break; + case EVENT_ASSAULT_POLICE: crime = CRIME_HIT_COP; break; + case EVENT_GUNSHOT: crime = CRIME_POSSESSION_GUN; break; + case EVENT_STEAL_CAR: crime = CRIME_STEAL_CAR; break; + case EVENT_HIT_AND_RUN: crime = CRIME_RUNOVER_PED; break; + case EVENT_HIT_AND_RUN_COP: crime = CRIME_RUNOVER_COP; break; + case EVENT_SHOOT_PED: crime = CRIME_SHOOT_PED; break; + case EVENT_SHOOT_COP: crime = CRIME_SHOOT_COP; break; + case EVENT_PED_SET_ON_FIRE: crime = CRIME_PED_BURNED; break; + case EVENT_COP_SET_ON_FIRE: crime = CRIME_COP_BURNED; break; + case EVENT_CAR_SET_ON_FIRE: crime = CRIME_VEHICLE_BURNED; break; + default: crime = CRIME_NONE; break; + } + return crime; +} +#endif + +CCivilianPed::CCivilianPed(ePedType pedtype, uint32 mi) : CPed(pedtype) +{ + SetModelIndex(mi); + for (int i = 0; i < ARRAY_SIZE(m_nearPeds); i++) { + m_nearPeds[i] = nil; + } +} + +void +CCivilianPed::CivilianAI(void) +{ + if (CTimer::GetTimeInMilliseconds() <= m_fleeTimer || m_objective != OBJECTIVE_NONE && !bRespondsToThreats + || !IsPedInControl()) { + + if (m_objective == OBJECTIVE_GUARD_SPOT) + return; + + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) { + if (m_pedInObjective && m_pedInObjective->IsPlayer()) + return; + } + if (CTimer::GetTimeInMilliseconds() <= m_lookTimer) + return; + + uint32 closestThreatFlag = ScanForThreats(); + if (closestThreatFlag == PED_FLAG_EXPLOSION) { + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + m_eventOrThreat.x, m_eventOrThreat.y, + GetPosition().x, GetPosition().y); + SetLookFlag(angleToFace, true); + SetLookTimer(500); + + } else if (closestThreatFlag == PED_FLAG_GUN) { + SetLookFlag(m_threatEntity, true); + SetLookTimer(500); + } + return; + } + uint32 closestThreatFlag = ScanForThreats(); + if (closestThreatFlag == PED_FLAG_GUN) { + if (!m_threatEntity || !m_threatEntity->IsPed()) + return; + + CPed *threatPed = (CPed*)m_threatEntity; + float threatDistSqr = (m_threatEntity->GetPosition() - GetPosition()).MagnitudeSqr2D(); + if (m_pedStats->m_fear <= m_pedStats->m_lawfulness) { + if (m_pedStats->m_temper <= m_pedStats->m_fear) { + if (!threatPed->IsPlayer() || !RunToReportCrime(CRIME_POSSESSION_GUN)) { + if (threatDistSqr < sq(10.0f)) { + Say(SOUND_PED_FLEE_SPRINT); + SetFindPathAndFlee(m_threatEntity, 10000); + } else { + SetFindPathAndFlee(m_threatEntity->GetPosition(), 5000, true); + } + } + } else if (m_objective != OBJECTIVE_NONE || GetWeapon()->IsTypeMelee()) { + SetFindPathAndFlee(m_threatEntity, 5000); + if (threatDistSqr < sq(20.0f)) { + SetMoveState(PEDMOVE_RUN); + Say(SOUND_PED_FLEE_SPRINT); + } else { + SetMoveState(PEDMOVE_WALK); + } + } else if (threatPed->IsPlayer() && FindPlayerPed()->m_pWanted->m_CurrentCops != 0) { + SetFindPathAndFlee(m_threatEntity, 5000); + if (threatDistSqr < sq(10.0f)) { + SetMoveState(PEDMOVE_RUN); + } else { + SetMoveState(PEDMOVE_WALK); + } + } else { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } + } else { + if (threatDistSqr < sq(10.0f)) { + Say(SOUND_PED_FLEE_SPRINT); + SetFindPathAndFlee(m_threatEntity, 10000); + SetMoveState(PEDMOVE_SPRINT); + } else { + Say(SOUND_PED_FLEE_SPRINT); + SetFindPathAndFlee(m_threatEntity, 5000); + SetMoveState(PEDMOVE_RUN); + } + } + SetLookFlag(m_threatEntity, false); + SetLookTimer(500); + } else if (closestThreatFlag == PED_FLAG_DEADPEDS) { + float eventDistSqr = (m_pEventEntity->GetPosition() - GetPosition()).MagnitudeSqr2D(); + if (IsGangMember() && m_nPedType == ((CPed*)m_pEventEntity)->m_nPedType) { + if (eventDistSqr < sq(5.0f)) { + SetFindPathAndFlee(m_pEventEntity, 2000); + SetMoveState(PEDMOVE_RUN); + } + } else if (IsGangMember() || eventDistSqr > sq(5.0f)) { + bool investigateDeadPed = true; + CEntity *killerOfDeadPed = ((CPed*)m_pEventEntity)->m_threatEntity; + if (killerOfDeadPed && killerOfDeadPed->IsPed()) { + CVector killerPos = killerOfDeadPed->GetPosition(); + CVector deadPedPos = m_pEventEntity->GetPosition(); + if (CVector2D(killerPos - deadPedPos).MagnitudeSqr() < sq(10.0f)) + investigateDeadPed = false; + } + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + int32 eventId = CheckForPlayerCrimes((CPed*)m_pEventEntity); + eCrimeType crime = (eventId == -1 ? CRIME_NONE : EventTypeToCrimeType(gaEvent[eventId].type)); + bool eligibleToReport = crime != CRIME_NONE && m_pedStats->m_fear <= m_pedStats->m_lawfulness && m_pedStats->m_temper <= m_pedStats->m_fear; + if (IsGangMember() || !eligibleToReport || !RunToReportCrime(crime)) +#endif + if (investigateDeadPed) + SetInvestigateEvent(EVENT_DEAD_PED, CVector2D(m_pEventEntity->GetPosition()), 1.0f, 20000, 0.0f); + + } else { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + int32 eventId = CheckForPlayerCrimes((CPed*)m_pEventEntity); + eCrimeType crime = (eventId == -1 ? CRIME_NONE : EventTypeToCrimeType(gaEvent[eventId].type)); + bool eligibleToReport = crime != CRIME_NONE && m_pedStats->m_fear <= m_pedStats->m_lawfulness && m_pedStats->m_temper <= m_pedStats->m_fear; + if(!eligibleToReport || !RunToReportCrime(crime)) +#endif + { + SetFindPathAndFlee(m_pEventEntity, 5000); + SetMoveState(PEDMOVE_RUN); + } + } + } else if (closestThreatFlag == PED_FLAG_EXPLOSION) { + CVector2D eventDistVec = m_eventOrThreat - GetPosition(); + float eventDistSqr = eventDistVec.MagnitudeSqr(); + if (eventDistSqr < sq(20.0f)) { + Say(SOUND_PED_FLEE_SPRINT); + SetFlee(m_eventOrThreat, 2000); + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + m_eventOrThreat.x, m_eventOrThreat.y, + GetPosition().x, GetPosition().y); + SetLookFlag(angleToFace, true); + SetLookTimer(500); + } else if (eventDistSqr < sq(40.0f)) { + if (bGonnaInvestigateEvent) { + if (CharCreatedBy != MISSION_CHAR && !IsGangMember()) + SetInvestigateEvent(EVENT_EXPLOSION, m_eventOrThreat, 6.0f, 30000, 0.0f); + + } else { + float eventHeading = CGeneral::GetRadianAngleBetweenPoints(eventDistVec.x, eventDistVec.y, 0.0f, 0.0f); + eventHeading = CGeneral::LimitRadianAngle(eventHeading); + if (eventHeading < 0.0f) + eventHeading = eventHeading + TWOPI; + + SetWanderPath(eventHeading / 8.0f); + } + } + } else { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + bool youShouldRunEventually = false; + bool dontGoToPhone = false; +#endif + if (m_threatEntity && m_threatEntity->IsPed()) { + CPed *threatPed = (CPed*)m_threatEntity; + if (m_pedStats->m_fear <= 100 - threatPed->m_pedStats->m_temper && threatPed->m_nPedType != PEDTYPE_COP) { + if (threatPed->GetWeapon(m_currentWeapon).IsTypeMelee() || !GetWeapon()->IsTypeMelee()) { + if (threatPed->IsPlayer() && FindPlayerPed()->m_pWanted->m_CurrentCops != 0) { + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + dontGoToPhone = true; +#endif + SetFindPathAndFlee(m_threatEntity, 10000); + } + } else { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + dontGoToPhone = true; +#endif + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } + } + } else { +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + youShouldRunEventually = true; +#else + SetFindPathAndFlee(m_threatEntity, 10000, true); +#endif + } + } + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (!dontGoToPhone) { + int32 eventId = CheckForPlayerCrimes(nil); + eCrimeType crime = (eventId == -1 ? CRIME_NONE : EventTypeToCrimeType(gaEvent[eventId].type)); + bool eligibleToReport = crime != CRIME_NONE && m_pedStats->m_fear <= m_pedStats->m_lawfulness; + + if ((!eligibleToReport || !RunToReportCrime(crime)) && youShouldRunEventually) { + SetFindPathAndFlee(m_threatEntity, 10000, true); + } + } +#endif + } +} + +void +CCivilianPed::ProcessControl(void) +{ + if (m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + CPed::ProcessControl(); + + if (bWasPostponed) + return; + + if (DyingOrDead()) + return; + + GetWeapon()->Update(m_audioEntityId); + switch (m_nPedState) { + case PED_WANDER_RANGE: + case PED_WANDER_PATH: + if (IsVisible()) + ScanForInterestingStuff(); + break; + case PED_SEEK_ENTITY: + if (!m_pSeekTarget) { + RestorePreviousState(); + break; + } + m_vecSeekPos = m_pSeekTarget->GetPosition(); + + // fall through + case PED_SEEK_POS: + if (Seek()) { + if ((m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA) && m_pNextPathNode) { + m_pNextPathNode = nil; +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + } else if (bRunningToPhone && m_objective < OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE) { + if (crimeReporters[m_phoneId] != this) { + RestorePreviousState(); + m_phoneId = -1; + bRunningToPhone = false; + } else { + m_facePhoneStart = true; + SetPedState(PED_FACE_PHONE); + } +#else + } else if (bRunningToPhone) { + if (gPhoneInfo.m_aPhones[m_phoneId].m_nState != PHONE_STATE_FREE) { + RestorePreviousState(); + m_phoneId = -1; + } else { + gPhoneInfo.m_aPhones[m_phoneId].m_nState = PHONE_STATE_REPORTING_CRIME; + SetPedState(PED_FACE_PHONE); + } +#endif + } else if (m_objective != OBJECTIVE_KILL_CHAR_ANY_MEANS && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + if (m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION) { + if (m_moved.Magnitude() == 0.0f) { + if (m_pedInObjective->m_nMoveState == PEDMOVE_STILL) + m_fRotationDest = m_pedInObjective->m_fRotationCur; + } + } else if (m_objective == OBJECTIVE_GOTO_CHAR_ON_FOOT + && m_pedInObjective && m_pedInObjective->m_nMoveState != PEDMOVE_STILL) { + SetMoveState(m_pedInObjective->m_nMoveState); + } else if (m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA) { + SetIdle(); + } else { + RestorePreviousState(); + } + } + } + break; + case PED_FACE_PHONE: + if (FacePhone()) + SetPedState(PED_MAKE_CALL); + break; + case PED_MAKE_CALL: + if (MakePhonecall()) + SetWanderPath(CGeneral::GetRandomNumber() & 7); + break; + case PED_MUG: + Mug(); + break; + case PED_SOLICIT: + Solicit(); + break; + case PED_UNKNOWN: + { + int pedsInSameState = 0; + Idle(); + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->m_nPedType == m_nPedType && nearPed->m_nPedState == PED_UNKNOWN) { + ++pedsInSameState; + } + } + if (pedsInSameState < 5) { + for (int j = 0; j < m_numNearPeds; ++j) { + CPed *nearPed = m_nearPeds[j]; + if (nearPed->m_nPedType == m_nPedType && nearPed->m_nPedState == PED_WANDER_PATH) { + nearPed->SetPedState(PED_UNKNOWN); + } + } + } + break; + } + case PED_DRIVING: + if (m_nPedType != PEDTYPE_PROSTITUTE) + break; + + if (CWorld::Players[CWorld::PlayerInFocus].m_pHooker != this) + break; + + if (CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime) { + if (m_nPedState == PED_DRIVING + && m_pMyVehicle->pDriver && m_pMyVehicle->pDriver->IsPlayer() && m_pMyVehicle->pDriver->m_nPedState == PED_DRIVING) { + CColPoint foundCol; + CEntity* foundEnt; + + CWorld::ProcessVerticalLine(m_pMyVehicle->GetPosition(), -100.0f, + foundCol, foundEnt, true, false, false, false, false, false, nil); + + if (m_pMyVehicle->m_vecMoveSpeed.MagnitudeSqr() < sq(0.01f) + && foundCol.surfaceB != SURFACE_DEFAULT && foundCol.surfaceB != SURFACE_TARMAC && foundCol.surfaceB != SURFACE_PAVEMENT) { + + if (m_pMyVehicle->CarHasRoof()) { + m_pMyVehicle->ApplyTurnForce(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(-0.8f, -1.2f) * m_fMass, + GetPosition().x - m_pMyVehicle->GetPosition().x, GetPosition().y - m_pMyVehicle->GetPosition().y, 0.0f); + + DMAudio.PlayOneShot(m_pMyVehicle->m_audioEntityId, SOUND_CAR_JERK, 0.0f); + + int playerSexFrequency = CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency; + if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney >= 10 && playerSexFrequency > 250) { + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + playerSexFrequency; + if (playerSexFrequency >= 350) { + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = Max(250, playerSexFrequency - 30); + } else { + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = Max(250, playerSexFrequency - 10); + } + + m_pMyVehicle->pDriver->m_fHealth = Min(125.0f, 1.0f + m_pMyVehicle->pDriver->m_fHealth); + if (CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency == 250) + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000; + } else { + bWanderPathAfterExitingCar = true; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } else { + bWanderPathAfterExitingCar = true; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil; + m_pMyVehicle->pDriver->m_fHealth = 125.0f; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } else { + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000; + int playerSexFrequency = CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency; + if (playerSexFrequency >= 1000 || playerSexFrequency <= 250) + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 1200; + else + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 250; + } + } else { + bWanderPathAfterExitingCar = true; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } + + if (CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime) { + int playerMoney = CWorld::Players[CWorld::PlayerInFocus].m_nMoney; + if (playerMoney <= 1) { + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 250; + } else { + CWorld::Players[CWorld::PlayerInFocus].m_nMoney = Max(0, playerMoney - 1); + } + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime = CTimer::GetTimeInMilliseconds() + 1000; + } + break; + default: + break; + } + if (IsPedInControl()) + CivilianAI(); + + if (CTimer::GetTimeInMilliseconds() > m_timerUnused) { + m_stateUnused = 0; + m_timerUnused = 0; + } + + if (m_moved.Magnitude() > 0.0f) + Avoid(); +} + +// It's "CPhoneInfo::ProcessNearestFreePhone" in PC IDB but that's not true, someone made it up. +bool +CPed::RunToReportCrime(eCrimeType crimeToReport) +{ +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (bRunningToPhone) { + if (!isPhoneAvailable(m_phoneId) && crimeReporters[m_phoneId] != this) { + crimeReporters[m_phoneId] = nil; + m_phoneId = -1; + bIsRunning = false; + ClearSeek(); // clears bRunningToPhone + return false; + } + + return true; + } +#else + // They changed true into false to make this function unusable. So running to phone actually starts but first frame after that cancels it. + if (m_nPedState == PED_SEEK_POS) + return false; +#endif + + CVector pos = GetPosition(); + int phoneId = gPhoneInfo.FindNearestFreePhone(&pos); + + if (phoneId == -1) + return false; + + CPhone *phone = &gPhoneInfo.m_aPhones[phoneId]; +#ifndef PEDS_REPORT_CRIMES_ON_PHONE + if (phone->m_nState != PHONE_STATE_FREE) + return false; +#else + crimeReporters[phoneId] = this; +#endif + + bRunningToPhone = true; + SetSeek(phone->m_pEntity->GetMatrix() * -phone->m_pEntity->GetForward(), 1.0f); // original: phone.m_vecPos, 0.3f + SetMoveState(PEDMOVE_RUN); + bIsRunning = true; // not there in original + m_phoneId = phoneId; + m_crimeToReportOnPhone = crimeToReport; + return true; +} \ No newline at end of file diff --git a/src/peds/CivilianPed.h b/src/peds/CivilianPed.h new file mode 100644 index 0000000..8418a99 --- /dev/null +++ b/src/peds/CivilianPed.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Ped.h" + +class CCivilianPed : public CPed +{ +public: + CCivilianPed(ePedType, uint32); + ~CCivilianPed(void) { } + + void CivilianAI(void); + void ProcessControl(void); +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CCivilianPed, 0x53C); +#endif diff --git a/src/peds/CopPed.cpp b/src/peds/CopPed.cpp new file mode 100644 index 0000000..44e3baf --- /dev/null +++ b/src/peds/CopPed.cpp @@ -0,0 +1,736 @@ +#include "common.h" + +#include "World.h" +#include "PlayerPed.h" +#include "CopPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "ModelIndices.h" +#include "Vehicle.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "General.h" +#include "ZoneCull.h" +#include "PathFind.h" +#include "RoadBlocks.h" +#include "CarCtrl.h" +#include "Renderer.h" +#include "Camera.h" + +CCopPed::CCopPed(eCopType copType) : CPed(PEDTYPE_COP) +{ + m_nCopType = copType; + switch (copType) { + case COP_STREET: + SetModelIndex(MI_COP); + GiveWeapon(WEAPONTYPE_COLT45, 1000); + m_currentWeapon = WEAPONTYPE_UNARMED; + m_fArmour = 0.0f; + m_wepSkills = 208; /* TODO: what is this? seems unused */ + m_wepAccuracy = 60; + break; + case COP_FBI: + SetModelIndex(MI_FBI); + GiveWeapon(WEAPONTYPE_COLT45, 1000); + GiveWeapon(WEAPONTYPE_AK47, 1000); + SetCurrentWeapon(WEAPONTYPE_AK47); + m_fArmour = 100.0f; + m_wepSkills = 176; /* TODO: what is this? seems unused */ + m_wepAccuracy = 76; + break; + case COP_SWAT: + SetModelIndex(MI_SWAT); + GiveWeapon(WEAPONTYPE_COLT45, 1000); + GiveWeapon(WEAPONTYPE_UZI, 1000); + SetCurrentWeapon(WEAPONTYPE_UZI); + m_fArmour = 50.0f; + m_wepSkills = 32; /* TODO: what is this? seems unused */ + m_wepAccuracy = 68; + break; + case COP_ARMY: + SetModelIndex(MI_ARMY); + GiveWeapon(WEAPONTYPE_COLT45, 1000); + GiveWeapon(WEAPONTYPE_M16, 1000); + GiveWeapon(WEAPONTYPE_GRENADE, 10); + SetCurrentWeapon(WEAPONTYPE_M16); + m_fArmour = 100.0f; + m_wepSkills = 32; /* TODO: what is this? seems unused */ + m_wepAccuracy = 84; + break; + default: + break; + } + m_bIsInPursuit = false; + field_1350 = 1; + m_bIsDisabledCop = false; + m_fAbseilPos = 0.0f; + m_attackTimer = 0; + m_bBeatingSuspect = false; + m_bStopAndShootDisabledZone = false; + m_bZoneDisabled = false; + field_1364 = -1; + SetWeaponLockOnTarget(nil); + + // VC also initializes in here, but as nil +#ifdef FIX_BUGS + m_nRoadblockNode = -1; +#endif +} + +CCopPed::~CCopPed() +{ + ClearPursuit(); +} + +// Parameter should always be CPlayerPed, but it seems they considered making civilians arrestable at some point +void +CCopPed::SetArrestPlayer(CPed *player) +{ + if (!IsPedInControl() || !player) + return; + + switch (m_nCopType) { + case COP_FBI: + Say(SOUND_PED_ARREST_FBI); + break; + case COP_SWAT: + Say(SOUND_PED_ARREST_SWAT); + break; + default: + Say(SOUND_PED_ARREST_COP); + break; + } + if (player->EnteringCar()) { + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) + return; + + // why? + player->bGonnaKillTheCarJacker = true; + + // Genius + FindPlayerPed()->m_bCanBeDamaged = false; + ((CPlayerPed*)player)->m_pArrestingCop = this; + this->RegisterReference((CEntity**) &((CPlayerPed*)player)->m_pArrestingCop); + + } else if (player->m_nPedState != PED_DIE && player->m_nPedState != PED_DEAD && player->m_nPedState != PED_ARRESTED) { + player->m_nLastPedState = player->m_nPedState; + player->SetPedState(PED_ARRESTED); + + FindPlayerPed()->m_bCanBeDamaged = false; + ((CPlayerPed*)player)->m_pArrestingCop = this; + this->RegisterReference((CEntity**) &((CPlayerPed*)player)->m_pArrestingCop); + } + + SetPedState(PED_ARREST_PLAYER); + SetObjective(OBJECTIVE_NONE); + m_prevObjective = OBJECTIVE_NONE; + bIsPointingGunAt = false; + m_pSeekTarget = player; + m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + SetCurrentWeapon(WEAPONTYPE_COLT45); + if (player->InVehicle()) { + player->m_pMyVehicle->m_nNumGettingIn = 0; + player->m_pMyVehicle->m_nGettingInFlags = 0; + player->m_pMyVehicle->bIsHandbrakeOn = true; + player->m_pMyVehicle->SetStatus(STATUS_PLAYER_DISABLED); + } + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) + SetCurrentWeapon(WEAPONTYPE_COLT45); +} + +void +CCopPed::ClearPursuit(void) +{ + CPlayerPed *player = FindPlayerPed(); + if (!player) + return; + + CWanted *wanted = player->m_pWanted; + int ourCopId = 0; + bool foundMyself = false; + int biggestCopId = 0; + if (!m_bIsInPursuit) + return; + + m_bIsInPursuit = false; + for (int i = 0; i < Max(wanted->m_MaxCops, wanted->m_CurrentCops); ++i) { + if (!foundMyself && wanted->m_pCops[i] == this) { + wanted->m_pCops[i] = nil; + --wanted->m_CurrentCops; + foundMyself = true; + ourCopId = i; + biggestCopId = i; + } else { + if (wanted->m_pCops[i]) + biggestCopId = i; + } + } + if (foundMyself && biggestCopId > ourCopId) { + wanted->m_pCops[ourCopId] = wanted->m_pCops[biggestCopId]; + wanted->m_pCops[biggestCopId] = nil; + } + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; + m_nLastPedState = PED_NONE; + bIsRunning = false; + bNotAllowedToDuck = false; + bKindaStayInSamePlace = false; + m_bStopAndShootDisabledZone = false; + m_bZoneDisabled = false; + ClearObjective(); + if (IsPedInControl()) { + if (!m_pMyVehicle || wanted->GetWantedLevel() != 0) { + if (m_pMyVehicle && (m_pMyVehicle->GetPosition() - GetPosition()).MagnitudeSqr() < sq(5.0f)) { + m_nLastPedState = PED_IDLE; + SetSeek((CEntity*)m_pMyVehicle, 2.5f); + } else { + m_nLastPedState = PED_WANDER_PATH; + SetFindPathAndFlee(FindPlayerPed()->GetPosition(), 10000, true); + } + } else { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } +} + +// TODO: I don't know why they needed that parameter. +void +CCopPed::SetPursuit(bool ignoreCopLimit) +{ + CWanted *wanted = FindPlayerPed()->m_pWanted; + if (m_bIsInPursuit || !IsPedInControl()) + return; + + if (wanted->m_CurrentCops < wanted->m_MaxCops || ignoreCopLimit) { + for (int i = 0; i < wanted->m_MaxCops; ++i) { + if (!wanted->m_pCops[i]) { + m_bIsInPursuit = true; + ++wanted->m_CurrentCops; + wanted->m_pCops[i] = this; + break; + } + } + if (m_bIsInPursuit) { + ClearObjective(); + m_prevObjective = OBJECTIVE_NONE; + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, FindPlayerPed()); + SetObjectiveTimer(0); + bNotAllowedToDuck = true; + bIsRunning = true; + m_bStopAndShootDisabledZone = false; + } + } +} + +void +CCopPed::ArrestPlayer(void) +{ + m_pVehicleAnim = nil; + CPed *suspect = (CPed*)m_pSeekTarget; + if (suspect) { + if (suspect->CanSetPedState()) + suspect->SetPedState(PED_ARRESTED); + + if (suspect->bInVehicle && m_pMyVehicle && suspect->m_pMyVehicle == m_pMyVehicle) { + + // BUG? I will never understand why they used LINE_UP_TO_CAR_2... + LineUpPedWithCar(LINE_UP_TO_CAR_2); + } + + if (suspect && (suspect->m_nPedState == PED_ARRESTED || suspect->DyingOrDead() || suspect->EnteringCar())) { + + CAnimBlendAssociation *arrestAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ARREST); + if (!arrestAssoc || arrestAssoc->blendDelta < 0.0f) + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ARREST, 4.0f); + + CVector suspMidPos; + suspect->m_pedIK.GetComponentPosition(suspMidPos, PED_MID); + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(suspMidPos.x, suspMidPos.y, + GetPosition().x, GetPosition().y); + + m_fRotationCur = m_fRotationDest; + SetOrientation(0.0f, 0.0f, m_fRotationCur); + } else { + ClearPursuit(); + } + } else { + ClearPursuit(); + } +} + +void +CCopPed::ScanForCrimes(void) +{ + CVehicle *playerVeh = FindPlayerVehicle(); + + // Look for car alarms + if (playerVeh && playerVeh->IsCar()) { + if (playerVeh->IsAlarmOn()) { + if ((playerVeh->GetPosition() - GetPosition()).MagnitudeSqr() < sq(20.0f)) + FindPlayerPed()->SetWantedLevelNoDrop(1); + } + } + + // Look for stolen cop cars + if (!m_bIsInPursuit) { + CPlayerPed *player = FindPlayerPed(); + if ((m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) + && player->m_pWanted->GetWantedLevel() == 0) { + + if (player->m_pMyVehicle +#ifdef FIX_BUGS + && m_pMyVehicle == player->m_pMyVehicle +#endif + && player->m_pMyVehicle->bIsLawEnforcer) + player->SetWantedLevelNoDrop(1); + } + } +} + +void +CCopPed::CopAI(void) +{ + CWanted *wanted = FindPlayerPed()->m_pWanted; + int wantedLevel = wanted->GetWantedLevel(); + CPhysical *playerOrHisVeh = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed(); + + if (wanted->m_bIgnoredByEveryone || wanted->m_bIgnoredByCops) { + if (m_nPedState != PED_ARREST_PLAYER) + ClearPursuit(); + + return; + } + if (CCullZones::NoPolice() && m_bIsInPursuit && !m_bIsDisabledCop) { + if (bHitSomethingLastFrame) { + m_bZoneDisabled = true; + m_bIsDisabledCop = true; +#ifdef FIX_BUGS + m_nRoadblockNode = -1; +#else + m_nRoadblockNode = 0; +#endif + bKindaStayInSamePlace = true; + bIsRunning = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + SetIdle(); + ClearObjective(); + ClearPursuit(); + m_prevObjective = OBJECTIVE_NONE; + m_nLastPedState = PED_NONE; + SetAttackTimer(0); + + // Safe distance for disabled zone? Or to just make game easier? + if (m_fDistanceToTarget > 15.0f) + m_bStopAndShootDisabledZone = true; + } + } else if (m_bZoneDisabled && !CCullZones::NoPolice()) { + m_bZoneDisabled = false; + m_bIsDisabledCop = false; + m_bStopAndShootDisabledZone = false; + bKindaStayInSamePlace = false; + bCrouchWhenShooting = false; + bDuckAndCover = false; + ClearPursuit(); + } + if (wantedLevel > 0) { + if (!m_bIsDisabledCop) { + if (!m_bIsInPursuit || wanted->m_CurrentCops > wanted->m_MaxCops) { + CCopPed *copFarthestToTarget = nil; + float copFarthestToTargetDist = m_fDistanceToTarget; + + int oldCopNum = wanted->m_CurrentCops; + int maxCops = wanted->m_MaxCops; + + for (int i = 0; i < Max(maxCops, oldCopNum); i++) { + CCopPed *cop = wanted->m_pCops[i]; + if (cop && cop->m_fDistanceToTarget > copFarthestToTargetDist) { + copFarthestToTargetDist = cop->m_fDistanceToTarget; + copFarthestToTarget = wanted->m_pCops[i]; + } + } + + if (m_bIsInPursuit) { + if (copFarthestToTarget && oldCopNum > maxCops) { + if (copFarthestToTarget == this && m_fDistanceToTarget > 10.0f) { + ClearPursuit(); + } else if(copFarthestToTargetDist > 10.0f) + copFarthestToTarget->ClearPursuit(); + } + } else { + if (oldCopNum < maxCops) { + SetPursuit(true); + } else { + if (m_fDistanceToTarget <= 10.0f || copFarthestToTarget && m_fDistanceToTarget < copFarthestToTargetDist) { + if (copFarthestToTarget && copFarthestToTargetDist > 10.0f) + copFarthestToTarget->ClearPursuit(); + + SetPursuit(true); + } + } + } + } else + SetPursuit(false); + + if (!m_bIsInPursuit) + return; + + if (wantedLevel > 1 && GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) + SetCurrentWeapon(WEAPONTYPE_COLT45); + else if (wantedLevel == 1 && GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && !FindPlayerPed()->m_pCurrentPhysSurface) { + // i.e. if player is on top of car, cop will still use colt45. + SetCurrentWeapon(WEAPONTYPE_UNARMED); + } + + if (FindPlayerVehicle()) { + if (m_bBeatingSuspect) { + --wanted->m_CopsBeatingSuspect; + m_bBeatingSuspect = false; + } + if (m_fDistanceToTarget * FindPlayerSpeed().Magnitude() > 4.0f) + ClearPursuit(); + } + return; + } + float weaponRange = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_fRange; + SetLookFlag(playerOrHisVeh, true); + TurnBody(); + SetCurrentWeapon(WEAPONTYPE_COLT45); + if (!bIsDucking) { + if (m_attackTimer >= CTimer::GetTimeInMilliseconds()) { + if (m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT && !m_bZoneDisabled) { + CVector targetDist = playerOrHisVeh->GetPosition() - GetPosition(); + if (m_fDistanceToTarget > 30.0f) { + CAnimBlendAssociation* crouchShootAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RBLOCK_SHOOT); + if (crouchShootAssoc) + crouchShootAssoc->blendDelta = -1000.0f; + + // Target is coming onto us + if (DotProduct(playerOrHisVeh->m_vecMoveSpeed, targetDist) > 0.0f) { + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bDuckAndCover = false; + SetPursuit(false); + SetObjective(OBJECTIVE_KILL_CHAR_ANY_MEANS, FindPlayerPed()); + } + } else if (m_fDistanceToTarget < 5.0f + && (!FindPlayerVehicle() || FindPlayerVehicle()->m_vecMoveSpeed.MagnitudeSqr() < sq(1.f/200.f))) { + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bDuckAndCover = false; + } else { + // VC checks for != nil compared to buggy behaviour of III. I check for != -1 here. +#ifdef VC_PED_PORTS + float dotProd; + if (m_nRoadblockNode != -1) { + CTreadable *roadBlockRoad = ThePaths.m_mapObjects[CRoadBlocks::RoadBlockObjects[m_nRoadblockNode]]; + dotProd = DotProduct2D(playerOrHisVeh->GetPosition() - roadBlockRoad->GetPosition(), GetPosition() - roadBlockRoad->GetPosition()); + } else + dotProd = -1.0f; + + if(dotProd >= 0.0f) { +#else + +#ifndef FIX_BUGS + float copRoadDotProd, targetRoadDotProd; +#else + float copRoadDotProd = 1.0f, targetRoadDotProd = 1.0f; + if (m_nRoadblockNode != -1) +#endif + { + CTreadable* roadBlockRoad = ThePaths.m_mapObjects[CRoadBlocks::RoadBlockObjects[m_nRoadblockNode]]; + CVector2D roadFwd = roadBlockRoad->GetForward(); + copRoadDotProd = DotProduct2D(GetPosition() - roadBlockRoad->GetPosition(), roadFwd); + targetRoadDotProd = DotProduct2D(playerOrHisVeh->GetPosition() - roadBlockRoad->GetPosition(), roadFwd); + } + // Roadblock may be towards road's fwd or opposite, so check both + if ((copRoadDotProd >= 0.0f || targetRoadDotProd >= 0.0f) + && (copRoadDotProd <= 0.0f || targetRoadDotProd <= 0.0f)) { +#endif + bIsPointingGunAt = true; + } else { + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + bIsDucking = false; + bDuckAndCover = false; + SetPursuit(false); + } + } + } + } else { + if (m_fDistanceToTarget < weaponRange) { + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CVector gunPos = weaponInfo->m_vecFireOffset; + TransformToNode(gunPos, PED_HANDR); + + CColPoint foundCol; + CEntity *foundEnt; + if (!CWorld::ProcessLineOfSight(gunPos, playerOrHisVeh->GetPosition(), foundCol, foundEnt, + false, true, false, false, true, false, false) + || foundEnt && foundEnt == playerOrHisVeh) { + SetWeaponLockOnTarget(playerOrHisVeh); + SetAttack(playerOrHisVeh); + SetShootTimer(CGeneral::GetRandomNumberInRange(500, 1000)); + } + SetAttackTimer(CGeneral::GetRandomNumberInRange(200, 300)); + } + SetMoveState(PEDMOVE_STILL); + } + } + } else { + if (!m_bIsDisabledCop || m_bZoneDisabled) { + if (m_nPedState != PED_AIM_GUN) { + if (m_bIsInPursuit) + ClearPursuit(); + + if (IsPedInControl()) { + // Entering the vehicle + if (m_pMyVehicle && !bInVehicle) { + if (m_pMyVehicle->IsLawEnforcementVehicle()) { + if (m_pMyVehicle->pDriver) { + if (m_pMyVehicle->pDriver->m_nPedType == PEDTYPE_COP) { + if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER) + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_pMyVehicle); + } else if (m_pMyVehicle->pDriver->IsPlayer()) { + FindPlayerPed()->SetWantedLevelNoDrop(1); + } + } else if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } else { + m_pMyVehicle = nil; + ClearObjective(); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + } + } +#ifdef VC_PED_PORTS + else { + if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && CharCreatedBy == RANDOM_CHAR) { + for (int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->CharCreatedBy == RANDOM_CHAR) { + if ((nearPed->m_nPedType == PEDTYPE_CRIMINAL || nearPed->IsGangMember()) + && nearPed->IsPedInControl()) { + + bool anotherCopChasesHim = false; + if (nearPed->m_nPedState == PED_FLEE_ENTITY) { + if (nearPed->m_fleeFrom && nearPed->m_fleeFrom->IsPed() && + ((CPed*)nearPed->m_fleeFrom)->m_nPedType == PEDTYPE_COP) { + anotherCopChasesHim = true; + } + } + if (!anotherCopChasesHim) { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, nearPed); + nearPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, this); + nearPed->bBeingChasedByPolice = true; + return; + } + } + } + } + } + } +#endif + } + } + } else { + if (m_bIsInPursuit && m_nPedState != PED_AIM_GUN) + ClearPursuit(); + + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + bIsDucking = false; + bDuckAndCover = false; + if (m_pMyVehicle) + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } +} + +void +CCopPed::ProcessControl(void) +{ + if (m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + CPed::ProcessControl(); + if (bWasPostponed) + return; + + if (m_nPedState == PED_DEAD) { + ClearPursuit(); + m_objective = OBJECTIVE_NONE; + return; + } + if (m_nPedState == PED_DIE) + return; + + if (m_nPedState == PED_ARREST_PLAYER) { + ArrestPlayer(); + return; + } + GetWeapon()->Update(m_audioEntityId); + if (m_moved.Magnitude() > 0.0f) + Avoid(); + + CPhysical *playerOrHisVeh = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed(); + CPlayerPed *player = FindPlayerPed(); + + m_fDistanceToTarget = (playerOrHisVeh->GetPosition() - GetPosition()).Magnitude(); + if (player->m_nPedState == PED_ARRESTED || player->DyingOrDead()) { + if (m_fDistanceToTarget < 5.0f) { + SetArrestPlayer(player); + return; + } + if (IsPedInControl()) + SetIdle(); + } + if (m_bIsInPursuit) { + if (player->m_nPedState != PED_ARRESTED && !player->DyingOrDead()) { + switch (m_nCopType) { + case COP_FBI: + Say(SOUND_PED_PURSUIT_FBI); + break; + case COP_SWAT: + Say(SOUND_PED_PURSUIT_SWAT); + break; + case COP_ARMY: + Say(SOUND_PED_PURSUIT_ARMY); + break; + default: + Say(SOUND_PED_PURSUIT_COP); + break; + } + } + } + + if (IsPedInControl()) { + CopAI(); + /* switch (m_nCopType) + { + case COP_FBI: + CopAI(); + break; + case COP_SWAT: + CopAI(); + break; + case COP_ARMY: + CopAI(); + break; + default: + CopAI(); + break; + } */ + } else if (InVehicle()) { + if (m_pMyVehicle->pDriver == this && m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE && + CanPedDriveOff() && m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE) { + + CCarCtrl::JoinCarWithRoadSystem(m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS; + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 17; + } + } + if (IsPedInControl() || m_nPedState == PED_DRIVING) + ScanForCrimes(); + + // They may have used goto to jump here in case of PED_ATTACK. + if (m_nPedState == PED_IDLE || m_nPedState == PED_ATTACK) { + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && + player && player->EnteringCar() && m_fDistanceToTarget < 1.3f) { + SetArrestPlayer(player); + } + } else { + if (m_nPedState == PED_SEEK_POS) { + if (player->m_nPedState == PED_ARRESTED) { + SetIdle(); + SetLookFlag(player, false); + SetLookTimer(1000); + RestorePreviousObjective(); + } else { + if (player->m_pMyVehicle && player->m_pMyVehicle->m_nNumGettingIn != 0) { + // This is 1.3f when arresting in car without seeking first (in above) +#if defined(VC_PED_PORTS) || defined(FIX_BUGS) + m_distanceToCountSeekDone = 1.3f; +#else + m_distanceToCountSeekDone = 2.0f; +#endif + } + + if (bDuckAndCover) { +#if GTA_VERSION < GTA3_PC_11 && !defined(VC_PED_PORTS) + if (!bNotAllowedToDuck && Seek()) { + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + SetPointGunAt(m_pedInObjective); + } +#endif + } else if (Seek()) { + CVehicle *playerVeh = FindPlayerVehicle(); + if (!playerVeh && player && player->EnteringCar()) { + SetArrestPlayer(player); + } else if (1.5f + GetPosition().z <= m_vecSeekPos.z || GetPosition().z - 0.3f >= m_vecSeekPos.z) { + SetMoveState(PEDMOVE_STILL); + } else if (playerVeh && playerVeh->CanPedEnterCar() && playerVeh->m_nNumGettingIn == 0) { + SetCarJack(playerVeh); + } + } + } + } else if (m_nPedState == PED_SEEK_ENTITY) { + if (!m_pSeekTarget) { + RestorePreviousState(); + } else { + m_vecSeekPos = m_pSeekTarget->GetPosition(); + if (Seek()) { + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && m_fDistanceToTarget < 2.5f && player) { + if (player->m_nPedState == PED_ARRESTED || player->m_nPedState == PED_ENTER_CAR || + (player->m_nPedState == PED_CARJACK && m_fDistanceToTarget < 1.3f)) { + SetArrestPlayer(player); + } else + RestorePreviousState(); + } else { + RestorePreviousState(); + } + + } + } + } + } + if (!m_bStopAndShootDisabledZone) + return; + + bool dontShoot = false; + if (GetIsOnScreen() && CRenderer::IsEntityCullZoneVisible(this)) { + if (((CTimer::GetFrameCounter() + m_randomSeed) & 0x1F) == 17) { + CEntity *foundBuilding = nil; + CColPoint foundCol; + CVector lookPos = GetPosition() + CVector(0.0f, 0.0f, 0.7f); + CVector camPos = TheCamera.GetGameCamPosition(); + CWorld::ProcessLineOfSight(camPos, lookPos, foundCol, foundBuilding, + true, false, false, false, false, false, false); + + // He's at least 15.0 far, in disabled zone, collided into somewhere (that's why m_bStopAndShootDisabledZone set), + // and now has building on front of him. He's stupid, we don't need him. + if (foundBuilding) { + FlagToDestroyWhenNextProcessed(); + dontShoot = true; + } + } + } else { + FlagToDestroyWhenNextProcessed(); + dontShoot = true; + } + + if (!dontShoot) { + bStopAndShoot = true; + bKindaStayInSamePlace = true; + bIsPointingGunAt = true; + SetAttack(m_pedInObjective); + } +} diff --git a/src/peds/CopPed.h b/src/peds/CopPed.h new file mode 100644 index 0000000..5346d9a --- /dev/null +++ b/src/peds/CopPed.h @@ -0,0 +1,41 @@ +#pragma once +#include "Ped.h" + +enum eCopType +{ + COP_STREET = 0, + COP_FBI = 1, + COP_SWAT = 2, + COP_ARMY = 3, +}; + +class CCopPed : public CPed +{ +public: + int16 m_nRoadblockNode; + float m_fDistanceToTarget; + bool m_bIsInPursuit; + bool m_bIsDisabledCop; + int8 field_1350; + bool m_bBeatingSuspect; + bool m_bStopAndShootDisabledZone; + bool m_bZoneDisabled; + float m_fAbseilPos; // VC leftover, unused + eCopType m_nCopType; + int8 field_1364; + + CCopPed(eCopType); + ~CCopPed(); + + void ClearPursuit(void); + void ProcessControl(void); + void SetArrestPlayer(CPed*); + void SetPursuit(bool); + void ArrestPlayer(void); + void ScanForCrimes(void); + void CopAI(void); +}; + +#ifndef PED_SKIN +VALIDATE_SIZE(CCopPed, 0x558); +#endif diff --git a/src/peds/DummyPed.h b/src/peds/DummyPed.h new file mode 100644 index 0000000..ea617c7 --- /dev/null +++ b/src/peds/DummyPed.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Dummy.h" + +// actually unused +class CDummyPed : CDummy +{ + int32 pedType; + int32 unknown; +}; + +VALIDATE_SIZE(CDummyPed, 0x70); diff --git a/src/peds/EmergencyPed.cpp b/src/peds/EmergencyPed.cpp new file mode 100644 index 0000000..6a5ca25 --- /dev/null +++ b/src/peds/EmergencyPed.cpp @@ -0,0 +1,417 @@ +#include "common.h" + +#include "EmergencyPed.h" +#include "DMAudio.h" +#include "ModelIndices.h" +#include "Vehicle.h" +#include "Fire.h" +#include "General.h" +#include "CarCtrl.h" +#include "Accident.h" + +CEmergencyPed::CEmergencyPed(uint32 type) : CPed(type) +{ + switch (type){ + case PEDTYPE_EMERGENCY: + SetModelIndex(MI_MEDIC); + m_pRevivedPed = nil; + field_1360 = 0; + break; + case PEDTYPE_FIREMAN: + SetModelIndex(MI_FIREMAN); + m_pRevivedPed = nil; + break; + default: + break; + } + m_nEmergencyPedState = EMERGENCY_PED_READY; + m_pAttendedAccident = nil; + m_bStartedToCPR = false; +} + +bool +CEmergencyPed::InRange(CPed *victim) +{ + if (!m_pMyVehicle) + return true; + + if ((m_pMyVehicle->GetPosition() - victim->GetPosition()).Magnitude() > 30.0f) + return false; + + return true; +} + +void +CEmergencyPed::ProcessControl(void) +{ + if (m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + CPed::ProcessControl(); + if (bWasPostponed) + return; + + if(!DyingOrDead()) { + GetWeapon()->Update(m_audioEntityId); + + if (IsPedInControl() && m_moved.Magnitude() > 0.0f) + Avoid(); + + switch (m_nPedState) { + case PED_SEEK_POS: + Seek(); + break; + case PED_SEEK_ENTITY: + if (m_pSeekTarget) { + m_vecSeekPos = m_pSeekTarget->GetPosition(); + Seek(); + } else { + ClearSeek(); + } + break; + default: + break; + } + + switch (m_nPedType) { + case PEDTYPE_EMERGENCY: + if (IsPedInControl() || m_nPedState == PED_DRIVING) + MedicAI(); + break; + case PEDTYPE_FIREMAN: + if (IsPedInControl()) + FiremanAI(); + break; + default: + return; + } + } +} + +// This function was buggy and incomplete in both III and VC, firemen had to be in 5.0m range of fire etc. etc. +// Copied some code from MedicAI to make it work. +void +CEmergencyPed::FiremanAI(void) +{ + float fireDist; + CFire *nearestFire; + + switch (m_nEmergencyPedState) { + case EMERGENCY_PED_READY: + nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist); + if (nearestFire) { + SetPedState(PED_NONE); + SetSeek(nearestFire->m_vecPos, 1.0f); + SetMoveState(PEDMOVE_RUN); + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + m_pAttendedFire = nearestFire; +#ifdef FIX_BUGS + bIsRunning = true; + ++nearestFire->m_nFiremenPuttingOut; +#endif + } + break; + case EMERGENCY_PED_DETERMINE_NEXT_STATE: + nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist); + if (nearestFire && nearestFire != m_pAttendedFire) { + SetPedState(PED_NONE); + SetSeek(nearestFire->m_vecPos, 1.0f); + SetMoveState(PEDMOVE_RUN); +#ifdef FIX_BUGS + bIsRunning = true; + if (m_pAttendedFire) { + --m_pAttendedFire->m_nFiremenPuttingOut; + } + ++nearestFire->m_nFiremenPuttingOut; + m_pAttendedFire = nearestFire; + } else if (!nearestFire) { +#else + m_pAttendedFire = nearestFire; + } else { +#endif + m_nEmergencyPedState = EMERGENCY_PED_STOP; + } + + // "Extinguish" the fire (Will overwrite the stop decision above if the attended and nearest fires are same) + if (fireDist < 5.0f) { + SetIdle(); + m_nEmergencyPedState = EMERGENCY_PED_STAND_STILL; + } + break; + case EMERGENCY_PED_STAND_STILL: + if (!m_pAttendedFire->m_bIsOngoing) + m_nEmergencyPedState = EMERGENCY_PED_STOP; + + // Leftover + // fireDist = 30.0f; + nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist); + if (nearestFire) { +#ifdef FIX_BUGS + if(nearestFire != m_pAttendedFire && (nearestFire->m_vecPos - GetPosition()).Magnitude() < 30.0f) +#endif + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + } + Say(SOUND_PED_EXTINGUISHING_FIRE); + break; + case EMERGENCY_PED_STOP: +#ifdef FIX_BUGS + bIsRunning = false; + if (m_pAttendedFire) +#endif + --m_pAttendedFire->m_nFiremenPuttingOut; + + SetPedState(PED_NONE); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + m_pAttendedFire = nil; + m_nEmergencyPedState = EMERGENCY_PED_READY; + SetMoveState(PEDMOVE_WALK); + break; + default: break; + } +} + +void +CEmergencyPed::MedicAI(void) +{ + float distToEmergency; + if (!bInVehicle && IsPedInControl()) { + ScanForThreats(); + if (m_threatEntity && m_threatEntity->IsPed() && ((CPed*)m_threatEntity)->IsPlayer()) { + if (((CPed*)m_threatEntity)->GetWeapon()->IsTypeMelee()) { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } else { + SetFlee(m_threatEntity, 6000); + Say(SOUND_PED_FLEE_SPRINT); + } + return; + } + } + + if (InVehicle()) { + if (m_pMyVehicle->IsCar() && m_objective != OBJECTIVE_LEAVE_CAR) { + if (gAccidentManager.FindNearestAccident(m_pMyVehicle->GetPosition(), &distToEmergency) + && distToEmergency < 25.0f && m_pMyVehicle->m_vecMoveSpeed.Magnitude() < 0.01f) { + + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + Say(SOUND_PED_LEAVE_VEHICLE); + } else if (m_pMyVehicle->pDriver == this && m_nPedState == PED_DRIVING + && m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE && !(CGeneral::GetRandomNumber() & 31)) { + + bool waitUntilMedicEntersCar = false; + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->m_nPedType == PEDTYPE_EMERGENCY) { + if ((nearPed->m_nPedState == PED_SEEK_CAR || nearPed->m_nPedState == PED_ENTER_CAR) + && nearPed->m_pMyVehicle == m_pMyVehicle) { + waitUntilMedicEntersCar = true; + break; + } + } + } + if (!waitUntilMedicEntersCar) { + CCarCtrl::JoinCarWithRoadSystem(m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + m_pMyVehicle->m_bSirenOrAlarm = false; + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 12; + m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_SLOW_DOWN_FOR_CARS; + if (m_pMyVehicle->bIsAmbulanceOnDuty) { + m_pMyVehicle->bIsAmbulanceOnDuty = false; + --CCarCtrl::NumAmbulancesOnDuty; + } + } + } + } + } + + CVector headPos, midPos; + CAccident *nearestAccident; + if (IsPedInControl()) { + switch (m_nEmergencyPedState) { + case EMERGENCY_PED_READY: + nearestAccident = gAccidentManager.FindNearestAccident(GetPosition(), &distToEmergency); + field_1360 = 0; + if (nearestAccident) { + m_pRevivedPed = nearestAccident->m_pVictim; + m_pRevivedPed->RegisterReference((CEntity**)&m_pRevivedPed); + m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID); + m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD); + SetSeek((headPos + midPos) * 0.5f, 1.0f); + SetObjective(OBJECTIVE_NONE); + bIsRunning = true; + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + m_pAttendedAccident = nearestAccident; + ++m_pAttendedAccident->m_nMedicsAttending; + } else { + if (m_pMyVehicle) { + if (!bInVehicle) { + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_pMyVehicle->pDriver || m_pMyVehicle->m_nGettingInFlags) { + + CPed* driver = m_pMyVehicle->pDriver; + if (driver && driver->m_nPedType != PEDTYPE_EMERGENCY && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, driver); + } else if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER + && m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER + && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_pMyVehicle); + } + } else { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } + } else if (m_nPedState != PED_WANDER_PATH) { + SetWanderPath(CGeneral::GetRandomNumber() & 7); + } + } + break; + case EMERGENCY_PED_DETERMINE_NEXT_STATE: + nearestAccident = gAccidentManager.FindNearestAccident(GetPosition(), &distToEmergency); + if (nearestAccident) { + if (nearestAccident != m_pAttendedAccident || m_nPedState != PED_SEEK_POS) { + m_pRevivedPed = nearestAccident->m_pVictim; + m_pRevivedPed->RegisterReference((CEntity**)&m_pRevivedPed); + if (!InRange(m_pRevivedPed)) { + m_nEmergencyPedState = EMERGENCY_PED_STOP; + break; + } + m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID); + m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD); + SetSeek((headPos + midPos) * 0.5f, nearestAccident->m_nMedicsPerformingCPR * 0.5f + 1.0f); + SetObjective(OBJECTIVE_NONE); + bIsRunning = true; + --m_pAttendedAccident->m_nMedicsAttending; + ++nearestAccident->m_nMedicsAttending; + m_pAttendedAccident = nearestAccident; + } + } else { + m_nEmergencyPedState = EMERGENCY_PED_STOP; + bIsRunning = false; + } + if (distToEmergency < 5.0f) { + if (m_pRevivedPed->m_pFire) { + bIsRunning = false; + SetMoveState(PEDMOVE_STILL); + } else if (distToEmergency < 4.5f) { + bIsRunning = false; + SetMoveState(PEDMOVE_WALK); + if (distToEmergency < 1.0f + || distToEmergency < 4.5f && m_pAttendedAccident->m_nMedicsPerformingCPR) { + m_nEmergencyPedState = EMERGENCY_PED_START_CPR; + } + } + } + break; + case EMERGENCY_PED_START_CPR: + if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f || m_pRevivedPed->bFadeOut) { + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + } else { + m_pRevivedPed->m_bloodyFootprintCountOrDeathTime = CTimer::GetTimeInMilliseconds(); + SetMoveState(PEDMOVE_STILL); + SetPedState(PED_CPR); + m_nLastPedState = PED_CPR; + SetLookFlag(m_pRevivedPed, 0); + SetLookTimer(500); + Say(SOUND_PED_HEALING); + if (m_pAttendedAccident->m_nMedicsPerformingCPR) { + SetIdle(); + m_nEmergencyPedState = EMERGENCY_PED_STAND_STILL; + } else { + m_nEmergencyPedState = EMERGENCY_PED_FACE_TO_PATIENT; + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_MEDIC_CPR, 4.0f); + bIsDucking = true; + } + SetLookTimer(2000); + ++m_pAttendedAccident->m_nMedicsPerformingCPR; + m_bStartedToCPR = true; + } + break; + case EMERGENCY_PED_FACE_TO_PATIENT: + if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + else { + m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID); + m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD); + midPos = (headPos + midPos) * 0.5f; + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + midPos.x, midPos.y, + GetPosition().x, GetPosition().y); + m_fRotationDest = CGeneral::LimitAngle(m_fRotationDest); + m_pLookTarget = m_pRevivedPed; + m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget); + TurnBody(); + + if (Abs(m_fRotationCur - m_fRotationDest) < DEGTORAD(45.0f)) + m_nEmergencyPedState = EMERGENCY_PED_PERFORM_CPR; + else + m_fRotationCur = (m_fRotationCur + m_fRotationDest) * 0.5f; + } + break; + case EMERGENCY_PED_PERFORM_CPR: + if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f) { + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + break; + } + m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID); + m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD); + midPos = (headPos + midPos) * 0.5f; + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + midPos.x, midPos.y, + GetPosition().x, GetPosition().y); + m_fRotationDest = CGeneral::LimitAngle(m_fRotationDest); + m_pLookTarget = m_pRevivedPed; + m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget); + TurnBody(); + if (CTimer::GetTimeInMilliseconds() <= m_lookTimer) { + SetMoveState(PEDMOVE_STILL); + break; + } + m_nEmergencyPedState = EMERGENCY_PED_STOP_CPR; + SetPedState(PED_NONE); + SetMoveState(PEDMOVE_WALK); + m_pVehicleAnim = nil; + if (!m_pRevivedPed->bBodyPartJustCameOff) { + m_pRevivedPed->m_fHealth = 100.0f; + m_pRevivedPed->SetPedState(PED_NONE); + m_pRevivedPed->m_nLastPedState = PED_WANDER_PATH; + m_pRevivedPed->SetGetUp(); + m_pRevivedPed->bUsesCollision = true; + m_pRevivedPed->SetMoveState(PEDMOVE_WALK); + m_pRevivedPed->RestartNonPartialAnims(); + m_pRevivedPed->bIsPedDieAnimPlaying = false; + m_pRevivedPed->bKnockedUpIntoAir = false; + m_pRevivedPed->m_pCollidingEntity = nil; + } + break; + case EMERGENCY_PED_STOP_CPR: + m_nEmergencyPedState = EMERGENCY_PED_STOP; + bIsDucking = true; + break; + case EMERGENCY_PED_STAND_STILL: + if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + else { + if (!m_pAttendedAccident->m_pVictim) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + if (!m_pAttendedAccident->m_nMedicsPerformingCPR) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + if (gAccidentManager.UnattendedAccidents()) + m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE; + } + break; + case EMERGENCY_PED_STOP: + m_bStartedToCPR = false; + SetPedState(PED_NONE); + if (m_pAttendedAccident) { + m_pAttendedAccident->m_pVictim = nil; + --m_pAttendedAccident->m_nMedicsAttending; + m_pAttendedAccident = nil; + } + SetWanderPath(CGeneral::GetRandomNumber() & 7); + m_pRevivedPed = nil; + m_nEmergencyPedState = EMERGENCY_PED_READY; + SetMoveState(PEDMOVE_WALK); + break; + default: break; + } + } +} diff --git a/src/peds/EmergencyPed.h b/src/peds/EmergencyPed.h new file mode 100644 index 0000000..390ba0b --- /dev/null +++ b/src/peds/EmergencyPed.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Ped.h" + +class CAccident; +class CFire; + +enum EmergencyPedState +{ + EMERGENCY_PED_READY = 0x0, + EMERGENCY_PED_DETERMINE_NEXT_STATE = 0x1, // you can set that anytime you want + EMERGENCY_PED_START_CPR = 0x2, + EMERGENCY_PED_FLAG_4 = 0x4, // unused + EMERGENCY_PED_FLAG_8 = 0x8, // unused + EMERGENCY_PED_FACE_TO_PATIENT = 0x10, // for CPR + EMERGENCY_PED_PERFORM_CPR = 0x20, + EMERGENCY_PED_STOP_CPR = 0x40, + EMERGENCY_PED_STAND_STILL = 0x80, // waiting colleagues for medics, "extinguishing" fire for firemen + EMERGENCY_PED_STOP = 0x100, +}; + +class CEmergencyPed : public CPed +{ +public: + CPed *m_pRevivedPed; + EmergencyPedState m_nEmergencyPedState; + CAccident *m_pAttendedAccident; + CFire *m_pAttendedFire; + bool m_bStartedToCPR; // set but unused + int32 field_1360; // set to 0 but unused + + CEmergencyPed(uint32); + ~CEmergencyPed() { } + bool InRange(CPed*); + void ProcessControl(void); + void FiremanAI(void); + void MedicAI(void); +}; +#ifndef PED_SKIN +VALIDATE_SIZE(CEmergencyPed, 0x554); +#endif diff --git a/src/peds/Gangs.cpp b/src/peds/Gangs.cpp new file mode 100644 index 0000000..be29379 --- /dev/null +++ b/src/peds/Gangs.cpp @@ -0,0 +1,78 @@ +#include "common.h" + +#include "ModelIndices.h" +#include "Gangs.h" +#include "Weapon.h" +#include "SaveBuf.h" + +CGangInfo CGangs::Gang[NUM_GANGS]; + +CGangInfo::CGangInfo() : + m_nVehicleMI(MI_BUS), + m_nPedModelOverride(-1), + m_Weapon1(WEAPONTYPE_UNARMED), + m_Weapon2(WEAPONTYPE_UNARMED) +{} + +void CGangs::Initialise(void) +{ + Gang[GANG_MAFIA].m_nVehicleMI = MI_MAFIA; + Gang[GANG_TRIAD].m_nVehicleMI = MI_BELLYUP; + Gang[GANG_DIABLOS].m_nVehicleMI = MI_DIABLOS; + Gang[GANG_YAKUZA].m_nVehicleMI = MI_YAKUZA; + Gang[GANG_YARDIE].m_nVehicleMI = MI_YARDIE; + Gang[GANG_COLUMB].m_nVehicleMI = MI_COLUMB; + Gang[GANG_HOODS].m_nVehicleMI = MI_HOODS; + Gang[GANG_7].m_nVehicleMI = -1; + Gang[GANG_8].m_nVehicleMI = -1; +#ifdef FIX_BUGS + for (int i = 0; i < NUM_GANGS; i++) + Gang[i].m_nPedModelOverride = -1; +#endif +} + +void CGangs::SetGangVehicleModel(int16 gang, int32 model) +{ + GetGangInfo(gang)->m_nVehicleMI = model; +} + +void CGangs::SetGangWeapons(int16 gang, int32 weapon1, int32 weapon2) +{ + CGangInfo *gi = GetGangInfo(gang); + gi->m_Weapon1 = weapon1; + gi->m_Weapon2 = weapon2; +} + +void CGangs::SetGangPedModelOverride(int16 gang, int8 ovrd) +{ + GetGangInfo(gang)->m_nPedModelOverride = ovrd; +} + +int8 CGangs::GetGangPedModelOverride(int16 gang) +{ + return GetGangInfo(gang)->m_nPedModelOverride; +} + +void CGangs::SaveAllGangData(uint8 *buf, uint32 *size) +{ +INITSAVEBUF + + *size = SAVE_HEADER_SIZE + sizeof(Gang); + WriteSaveHeader(buf, 'G','N','G','\0', *size - SAVE_HEADER_SIZE); + for (int i = 0; i < NUM_GANGS; i++) + WriteSaveBuf(buf, Gang[i]); + +VALIDATESAVEBUF(*size); +} + +void CGangs::LoadAllGangData(uint8 *buf, uint32 size) +{ + Initialise(); + +INITSAVEBUF + CheckSaveHeader(buf, 'G','N','G','\0', size - SAVE_HEADER_SIZE); + + for (int i = 0; i < NUM_GANGS; i++) + ReadSaveBuf(&Gang[i], buf); +VALIDATESAVEBUF(size); +} diff --git a/src/peds/Gangs.h b/src/peds/Gangs.h new file mode 100644 index 0000000..c8ea291 --- /dev/null +++ b/src/peds/Gangs.h @@ -0,0 +1,44 @@ +#pragma once + +struct CGangInfo +{ + int32 m_nVehicleMI; + int8 m_nPedModelOverride; + int32 m_Weapon1; + int32 m_Weapon2; + + CGangInfo(); +}; + +VALIDATE_SIZE(CGangInfo, 0x10); + +enum { + GANG_MAFIA = 0, + GANG_TRIAD, + GANG_DIABLOS, + GANG_YAKUZA, + GANG_YARDIE, + GANG_COLUMB, + GANG_HOODS, + GANG_7, + GANG_8, + NUM_GANGS +}; + +class CGangs +{ +public: + static void Initialise(void); + static void SetGangVehicleModel(int16, int32); + static void SetGangWeapons(int16, int32, int32); + static void SetGangPedModelOverride(int16, int8); + static int8 GetGangPedModelOverride(int16); + static void SaveAllGangData(uint8 *, uint32 *); + static void LoadAllGangData(uint8 *, uint32); + + static int32 GetGangVehicleModel(int16 gang) { return Gang[gang].m_nVehicleMI; } + static CGangInfo *GetGangInfo(int16 gang) { return &Gang[gang]; } + +private: + static CGangInfo Gang[NUM_GANGS]; +}; diff --git a/src/peds/Ped.cpp b/src/peds/Ped.cpp new file mode 100644 index 0000000..6b28dcb --- /dev/null +++ b/src/peds/Ped.cpp @@ -0,0 +1,8557 @@ +#include "common.h" + +#include "main.h" +#include "Pools.h" +#include "Particle.h" +#include "RpAnimBlend.h" +#include "Bones.h" +#include "Ped.h" +#include "AnimBlendAssociation.h" +#include "Fire.h" +#include "DMAudio.h" +#include "General.h" +#include "VisibilityPlugins.h" +#include "HandlingMgr.h" +#include "Replay.h" +#include "Radar.h" +#include "PedPlacement.h" +#include "Shadows.h" +#include "Weather.h" +#include "ZoneCull.h" +#include "Population.h" +#include "Pad.h" +#include "Phones.h" +#include "TrafficLights.h" +#include "CopPed.h" +#include "Script.h" +#include "CarCtrl.h" +#include "Garages.h" +#include "WaterLevel.h" +#include "Timecycle.h" +#include "ParticleObject.h" +#include "Floater.h" +#include "Range2D.h" +#include "Wanted.h" +#include "SaveBuf.h" + +CPed *gapTempPedList[50]; +uint16 gnNumTempPedList; + +static CColPoint aTempPedColPts[MAX_COLLISION_POINTS]; + + +uint16 CPed::nThreatReactionRangeMultiplier = 1; +uint16 CPed::nEnterCarRangeMultiplier = 1; + +bool CPed::bNastyLimbsCheat; +bool CPed::bPedCheat2; +bool CPed::bPedCheat3; +CVector2D CPed::ms_vec2DFleePosition; + +void *CPed::operator new(size_t sz) throw() { return CPools::GetPedPool()->New(); } +void *CPed::operator new(size_t sz, int handle) throw() { return CPools::GetPedPool()->New(handle); } +void CPed::operator delete(void *p, size_t sz) throw() { CPools::GetPedPool()->Delete((CPed*)p); } +void CPed::operator delete(void *p, int handle) throw() { CPools::GetPedPool()->Delete((CPed*)p); } + +#ifdef DEBUGMENU +bool CPed::bPopHeadsOnHeadshot = false; +#endif + +CPed::CPed(uint32 pedType) : m_pedIK(this) +{ + m_type = ENTITY_TYPE_PED; + bPedPhysics = true; + bUseCollisionRecords = true; + + m_vecAnimMoveDelta.x = 0.0f; + m_vecAnimMoveDelta.y = 0.0f; + m_fHealth = 100.0f; + m_fArmour = 0.0f; + m_nPedType = pedType; + m_lastSoundStart = 0; + m_soundStart = 0; + m_lastQueuedSound = SOUND_NO_SOUND; + m_queuedSound = SOUND_NO_SOUND; + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; +#ifdef FIX_BUGS + m_objectiveTimer = 0; +#endif + CharCreatedBy = RANDOM_CHAR; + m_leader = nil; + m_pedInObjective = nil; + m_carInObjective = nil; + bInVehicle = false; + m_pMyVehicle = nil; + m_pVehicleAnim = nil; + m_vecOffsetSeek.x = 0.0f; + m_vecOffsetSeek.y = 0.0f; + m_vecOffsetSeek.z = 0.0f; + m_pedFormation = FORMATION_UNDEFINED; + m_collidingThingTimer = 0; + m_nPedStateTimer = 0; + m_actionX = 0.0f; + m_actionY = 0.0f; + m_phoneTalkTimer = 0; + m_stateUnused = 0; + m_leaveCarTimer = 0; + m_getUpTimer = 0; + m_attackTimer = 0; + m_timerUnused = 0; + m_lookTimer = 0; + m_chatTimer = 0; + m_shootTimer = 0; + m_carJackTimer = 0; + m_duckAndCoverTimer = 0; + m_moved = CVector2D(0.0f, 0.0f); + m_fRotationCur = 0.0f; + m_headingRate = 15.0f; + m_fRotationDest = 0.0f; + m_vehDoor = CAR_DOOR_LF; + m_walkAroundType = 0; + m_pCurrentPhysSurface = nil; + m_vecOffsetFromPhysSurface = CVector(0.0f, 0.0f, 0.0f); + m_pSeekTarget = nil; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_wepSkills = 0; + m_distanceToCountSeekDone = 1.0f; + bRunningToPhone = false; + m_phoneId = -1; + m_lastAccident = 0; + m_fleeFrom = nil; + m_fleeFromPosX = 0; + m_fleeFromPosY = 0; + m_fleeTimer = 0; + m_vecSeekPosEx = CVector(0.0f, 0.0f, 0.0f); + m_distanceToCountSeekDoneEx = 0.0f; + m_nWaitState = WAITSTATE_FALSE; + m_nWaitTimer = 0; + m_pCollidingEntity = nil; + m_nPedState = PED_IDLE; + m_nLastPedState = PED_NONE; + m_nMoveState = PEDMOVE_STILL; +#ifdef FIX_BUGS + m_nPrevMoveState = PEDMOVE_NONE; +#endif + m_nStoredMoveState = PEDMOVE_NONE; + m_pFire = nil; + m_pPointGunAt = nil; + m_pLookTarget = nil; + m_fLookDirection = 0.0f; + m_pCurSurface = nil; + m_wanderRangeBounds = nil; + m_nPathNodes = 0; + m_nCurPathNode = 0; + m_nPathDir = 0; + m_pLastPathNode = nil; + m_pNextPathNode = nil; + m_routeLastPoint = -1; + m_routeStartPoint = 0; + m_routePointsPassed = 0; + m_routeType = 0; + m_bodyPartBleeding = -1; + + m_fMass = 70.0f; + m_fTurnMass = 100.0f; + m_fAirResistance = 0.4f / m_fMass; + m_fElasticity = 0.05f; + + bIsStanding = false; + bWasStanding = false; + bIsAttacking = false; + bIsPointingGunAt = false; + bIsLooking = false; + bKeepTryingToLook = false; + bIsRestoringLook = false; + bIsAimingGun = false; + + bIsRestoringGun = false; + bCanPointGunAtTarget = false; + bIsTalking = false; + bIsInTheAir = false; + bIsLanding = false; + bIsRunning = false; + bHitSomethingLastFrame = false; + bVehEnterDoorIsBlocked = false; + + bCanPedEnterSeekedCar = false; + bRespondsToThreats = true; + bRenderPedInCar = true; + bChangedSeat = false; + bUpdateAnimHeading = false; + bBodyPartJustCameOff = false; + bIsShooting = false; + bFindNewNodeAfterStateRestore = false; + + bGonnaInvestigateEvent = false; + bPedIsBleeding = false; + bStopAndShoot = false; + bIsPedDieAnimPlaying = false; + bUsePedNodeSeek = false; + bObjectiveCompleted = false; + bScriptObjectiveCompleted = false; + + bKindaStayInSamePlace = false; + bBeingChasedByPolice = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + bIsDucking = false; + bGetUpAnimStarted = false; + bDoBloodyFootprints = false; + bFleeAfterExitingCar = false; + + bWanderPathAfterExitingCar = false; + bIsLeader = false; + bDontDragMeOutCar = false; + m_ped_flagF8 = false; + bWillBeQuickJacked = false; + bCancelEnteringCar = false; + bObstacleShowedUpDuringKillObjective = false; + bDuckAndCover = false; + + bStillOnValidPoly = false; + bAllowMedicsToReviveMe = true; + bResetWalkAnims = false; + bStartWanderPathOnFoot = false; + bOnBoat = false; + bBusJacked = false; + bGonnaKillTheCarJacker = false; + bFadeOut = false; + + bKnockedUpIntoAir = false; + bHitSteepSlope = false; + bCullExtraFarAway = false; + bClearObjective = false; + bTryingToReachDryLand = false; + bCollidedWithMyVehicle = false; + bRichFromMugging = false; + bChrisCriminal = false; + + bShakeFist = false; + bNoCriticalHits = false; + bVehExitWillBeInstant = false; + bHasAlreadyBeenRecorded = false; + bFallenDown = false; +#ifdef KANGAROO_CHEAT + m_ped_flagI80 = false; +#endif +#ifdef VC_PED_PORTS + bSomeVCflag1 = false; +#endif + + if (CGeneral::GetRandomNumber() & 3) + bHasACamera = false; + else + bHasACamera = true; + + m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, this); + DMAudio.SetEntityStatus(m_audioEntityId, TRUE); + m_fearFlags = CPedType::GetThreats(m_nPedType); + m_threatEntity = nil; + m_eventOrThreat = CVector2D(0.0f, 0.0f); + m_pEventEntity = nil; + m_fAngleToEvent = 0.0f; + m_numNearPeds = 0; + + for (int i = 0; i < ARRAY_SIZE(m_nearPeds); i++) { + m_nearPeds[i] = nil; + if (i < ARRAY_SIZE(m_pPathNodesStates)) { + m_pPathNodesStates[i] = nil; + } + } + m_maxWeaponTypeAllowed = WEAPONTYPE_UNARMED; + m_currentWeapon = WEAPONTYPE_UNARMED; + m_storedWeapon = WEAPONTYPE_UNIDENTIFIED; + + for(int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) { + CWeapon &weapon = GetWeapon(i); + weapon.m_eWeaponType = WEAPONTYPE_UNARMED; + weapon.m_eWeaponState = WEAPONSTATE_READY; + weapon.m_nAmmoInClip = 0; + weapon.m_nAmmoTotal = 0; + weapon.m_nTimer = 0; + } + + m_curFightMove = FIGHTMOVE_NULL; + GiveWeapon(WEAPONTYPE_UNARMED, 0); + m_wepAccuracy = 60; + m_lastWepDam = -1; + m_collPoly.valid = false; + m_fCollisionSpeed = 0.0f; + m_wepModelID = -1; +#ifdef PED_SKIN + m_pWeaponModel = nil; +#endif + CPopulation::UpdatePedCount((ePedType)m_nPedType, false); +} + +CPed::~CPed(void) +{ + CWorld::Remove(this); + CRadar::ClearBlipForEntity(BLIP_CHAR, CPools::GetPedPool()->GetIndex(this)); + if (InVehicle()){ + uint8 door_flag = GetCarDoorFlag(m_vehDoor); + if (m_pMyVehicle->pDriver == this) + m_pMyVehicle->pDriver = nil; + else { + // FIX: Passenger counter now decreasing after removing ourself from vehicle. + m_pMyVehicle->RemovePassenger(this); + } + if (m_nPedState == PED_EXIT_CAR || m_nPedState == PED_DRAG_FROM_CAR) + m_pMyVehicle->m_nGettingOutFlags &= ~door_flag; + bInVehicle = false; + m_pMyVehicle = nil; + } else if (EnteringCar()) { + QuitEnteringCar(); + } + if (m_pFire) + m_pFire->Extinguish(); + CPopulation::UpdatePedCount((ePedType)m_nPedType, true); + DMAudio.DestroyEntity(m_audioEntityId); + + // Because of the nature of ped lists in GTA, it can sometimes be outdated. + // Remove ourself from nearPeds list of the Peds in our nearPeds list. +#ifdef FIX_BUGS + for(int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + assert(nearPed != nil); + if (!nearPed->IsPointerValid()) + continue; + + for(int j = 0; j < nearPed->m_numNearPeds;) { + assert(j == ARRAY_SIZE(m_nearPeds) - 1 || nearPed->m_nearPeds[j] || !nearPed->m_nearPeds[j+1]); // ensure nil comes after nil + + if (nearPed->m_nearPeds[j] == this) { + for (int k = j; k < ARRAY_SIZE(m_nearPeds) - 1; k++) { + nearPed->m_nearPeds[k] = nearPed->m_nearPeds[k + 1]; + nearPed->m_nearPeds[k + 1] = nil; + } + nearPed->m_nearPeds[ARRAY_SIZE(m_nearPeds) - 1] = nil; + nearPed->m_numNearPeds--; + } else + j++; + } + } +#endif +} + +void +CPed::Initialise(void) +{ + debug("Initialising CPed...\n"); + CPedType::Initialise(); + LoadFightData(); + SetAnimOffsetForEnterOrExitVehicle(); + debug("CPed ready\n"); +} + +void +CPed::SetModelIndex(uint32 mi) +{ + CEntity::SetModelIndex(mi); + RpAnimBlendClumpInit(GetClump()); + RpAnimBlendClumpFillFrameArray(GetClump(), m_pFrames); + CPedModelInfo *modelInfo = (CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()); + SetPedStats(modelInfo->m_pedStatType); + m_headingRate = m_pedStats->m_headingChangeRate; + m_animGroup = (AssocGroupId) modelInfo->m_animGroup; + CAnimManager::AddAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE); + + (*RPANIMBLENDCLUMPDATA(m_rwObject))->velocity2d = &m_vecAnimMoveDelta; + +#ifdef PED_SKIN + if(modelInfo->GetHitColModel() == nil) + modelInfo->CreateHitColModelSkinned(GetClump()); +#endif +} + +void +CPed::SetPedStats(ePedStats pedStat) +{ + m_pedStats = CPedStats::ms_apPedStats[pedStat]; +} + +void +CPed::BuildPedLists(void) +{ + if (((CTimer::GetFrameCounter() + m_randomSeed) % 16) == 0) { + CVector centre = CEntity::GetBoundCentre(); + CRect rect(centre.x - 20.0f, + centre.y - 20.0f, + centre.x + 20.0f, + centre.y + 20.0f); + int xstart = CWorld::GetSectorIndexX(rect.left); + int ystart = CWorld::GetSectorIndexY(rect.top); + int xend = CWorld::GetSectorIndexX(rect.right); + int yend = CWorld::GetSectorIndexY(rect.bottom); + gnNumTempPedList = 0; + + for(int y = ystart; y <= yend; y++) { + for(int x = xstart; x <= xend; x++) { + for (CPtrNode *pedPtrNode = CWorld::GetSector(x,y)->m_lists[ENTITYLIST_PEDS].first; pedPtrNode; pedPtrNode = pedPtrNode->next) { + CPed *ped = (CPed*)pedPtrNode->item; + if (ped != this && !ped->bInVehicle) { + float dist = (ped->GetPosition() - GetPosition()).Magnitude2D(); + if (nThreatReactionRangeMultiplier * 30.0f > dist) { +#ifdef FIX_BUGS + // If the gap ped list is full, sort it and truncate it + // before pushing more unsorted peds + if( gnNumTempPedList == ARRAY_SIZE(gapTempPedList) - 1 ) + { + gapTempPedList[gnNumTempPedList] = nil; + SortPeds(gapTempPedList, 0, gnNumTempPedList - 1); + gnNumTempPedList = ARRAY_SIZE(m_nearPeds); + } +#endif + + gapTempPedList[gnNumTempPedList] = ped; + gnNumTempPedList++; + // NOTE: We cannot absolutely fill the gap list, as the list is null-terminated before being passed to SortPeds + assert(gnNumTempPedList < ARRAY_SIZE(gapTempPedList)); + } + } + } + } + } + gapTempPedList[gnNumTempPedList] = nil; + SortPeds(gapTempPedList, 0, gnNumTempPedList - 1); + for (m_numNearPeds = 0; m_numNearPeds < ARRAY_SIZE(m_nearPeds); m_numNearPeds++) { + CPed *ped = gapTempPedList[m_numNearPeds]; + if (!ped) + break; + + m_nearPeds[m_numNearPeds] = ped; + } + for (int pedToClear = m_numNearPeds; pedToClear < ARRAY_SIZE(m_nearPeds); pedToClear++) + m_nearPeds[pedToClear] = nil; + } else { + for(int i = 0; i < ARRAY_SIZE(m_nearPeds); ) { + bool removePed = false; + if (m_nearPeds[i]) { + if (m_nearPeds[i]->IsPointerValid()) { + float distSqr = (GetPosition() - m_nearPeds[i]->GetPosition()).MagnitudeSqr2D(); + if (distSqr > 900.0f) + removePed = true; + } else + removePed = true; + } + + assert(i == ARRAY_SIZE(m_nearPeds) - 1 || m_nearPeds[i] || !m_nearPeds[i+1]); // ensure nil comes after nil + + if (removePed) { + // If we arrive here, the ped we're checking isn't "near", so we should remove it. + for (int j = i; j < ARRAY_SIZE(m_nearPeds) - 1; j++) { + m_nearPeds[j] = m_nearPeds[j + 1]; + m_nearPeds[j + 1] = nil; + } + m_nearPeds[ARRAY_SIZE(m_nearPeds) - 1] = nil; + m_numNearPeds--; + } else + i++; + } + } +} + +bool +CPed::OurPedCanSeeThisOne(CEntity *target) +{ + CColPoint colpoint; + CEntity *ent; + + CVector2D dist = CVector2D(target->GetPosition()) - CVector2D(GetPosition()); + + // Check if target is behind ped + if (DotProduct2D(dist, CVector2D(GetForward())) < 0.0f) + return false; + + // Check if target is too far away + if (dist.Magnitude() >= 40.0f) + return false; + + // Check line of sight from head + CVector headPos = this->GetPosition(); + headPos.z += 1.0f; + return !CWorld::ProcessLineOfSight(headPos, target->GetPosition(), colpoint, ent, true, false, false, false, false, false); +} + +// Some kind of binary sort +void +CPed::SortPeds(CPed **list, int min, int max) +{ + if (min >= max) + return; + + CVector leftDiff, rightDiff; + CVector middleDiff = GetPosition() - list[(max + min) / 2]->GetPosition(); + float middleDist = middleDiff.Magnitude(); + + int left = max; + int right = min; + while(right <= left){ + float rightDist, leftDist; + do { + rightDiff = GetPosition() - list[right]->GetPosition(); + rightDist = rightDiff.Magnitude(); + } while (middleDist > rightDist && ++right); + + do { + leftDiff = GetPosition() - list[left]->GetPosition(); + leftDist = leftDiff.Magnitude(); + } while (middleDist < leftDist && left--); + + if (right <= left) { + CPed *ped = list[right]; + list[right] = list[left]; + list[left] = ped; + right++; + left--; + } + } + SortPeds(list, min, left); + SortPeds(list, right, max); +} + +void +CPed::SetMoveState(eMoveState state) +{ + m_nMoveState = state; +} + +void +CPed::SetMoveAnim(void) +{ + if (m_nStoredMoveState == m_nMoveState || !IsPedInControl()) + return; + + if (m_nMoveState == PEDMOVE_NONE) { + m_nStoredMoveState = PEDMOVE_NONE; + return; + } + + AssocGroupId animGroupToUse; + if (m_leader && m_leader->IsPlayer()) + animGroupToUse = ASSOCGRP_PLAYER; + else + animGroupToUse = m_animGroup; + + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_BLOCK); + if (!animAssoc) { + CAnimBlendAssociation *fightIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + animAssoc = fightIdleAssoc; + if (fightIdleAssoc && m_nPedState == PED_FIGHT) + return; + + if (fightIdleAssoc) { + CAnimBlendAssociation *idleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + if (!idleAssoc || idleAssoc->blendDelta <= 0.0f) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_IDLE, 8.0f); + } + } + } + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + if (animAssoc) + if (m_nWaitState == WAITSTATE_STUCK || m_nWaitState == WAITSTATE_FINISH_FLEE) + return; + + if (animAssoc) { + CAnimBlendAssociation *idleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + if (!idleAssoc || idleAssoc->blendDelta <= 0.0f) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_IDLE, 4.0f); + } + } + } + if (!animAssoc) { + m_nStoredMoveState = m_nMoveState; + if (m_nMoveState == PEDMOVE_WALK || m_nMoveState == PEDMOVE_RUN || m_nMoveState == PEDMOVE_SPRINT) { + for (CAnimBlendAssociation *assoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_PARTIAL); + assoc; assoc = RpAnimBlendGetNextAssociation(assoc, ASSOC_PARTIAL)) { + + if (!(assoc->flags & ASSOC_FADEOUTWHENDONE)) { + assoc->blendDelta = -2.0f; + assoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + + ClearAimFlag(); + ClearLookFlag(); + } + + switch (m_nMoveState) { + case PEDMOVE_STILL: + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_IDLE, 4.0f); + break; + case PEDMOVE_WALK: + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_WALK, 1.0f); + break; + case PEDMOVE_RUN: + if (m_nPedState == PED_FLEE_ENTITY) { + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_RUN, 3.0f); + } else { + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_RUN, 1.0f); + } + break; + case PEDMOVE_SPRINT: + animAssoc = CAnimManager::BlendAnimation(GetClump(), animGroupToUse, ANIM_STD_RUNFAST, 1.0f); + break; + default: + break; + } + + if (animAssoc) { + if (m_leader) { + CAnimBlendAssociation *walkAssoc = RpAnimBlendClumpGetAssociation(m_leader->GetClump(), ANIM_STD_WALK); + if (!walkAssoc) + walkAssoc = RpAnimBlendClumpGetAssociation(m_leader->GetClump(), ANIM_STD_RUN); + + if (!walkAssoc) + walkAssoc = RpAnimBlendClumpGetAssociation(m_leader->GetClump(), ANIM_STD_RUNFAST); + + if (walkAssoc) { + animAssoc->speed = walkAssoc->speed; + } else { + if (CharCreatedBy == MISSION_CHAR) + animAssoc->speed = 1.0f; + else + animAssoc->speed = 1.2f - m_randomSeed * 0.4f / MYRAND_MAX; + + } + } else { + if (CharCreatedBy == MISSION_CHAR) + animAssoc->speed = 1.0f; + else + animAssoc->speed = 1.2f - m_randomSeed * 0.4f / MYRAND_MAX; + } + } + } +} + +void +CPed::StopNonPartialAnims(void) +{ + CAnimBlendAssociation *assoc; + + for (assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + if (!assoc->IsPartial()) + assoc->flags &= ~ASSOC_RUNNING; + } +} + +void +CPed::RestartNonPartialAnims(void) +{ + CAnimBlendAssociation *assoc; + + for (assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + if (!assoc->IsPartial()) + assoc->SetRun(); + } +} + +void +CPed::SetStoredState(void) +{ + if (m_nLastPedState != PED_NONE || !CanPedReturnToState()) + return; + + if (m_nPedState == PED_WANDER_PATH) { + bFindNewNodeAfterStateRestore = true; + if (m_nMoveState == PEDMOVE_NONE || m_nMoveState == PEDMOVE_STILL) + m_nMoveState = PEDMOVE_WALK; + } +#ifdef VC_PED_PORTS + if (m_nPedState != PED_IDLE) +#endif + { + m_nLastPedState = m_nPedState; + if (m_nMoveState >= m_nPrevMoveState) + m_nPrevMoveState = m_nMoveState; + } +} + +void +CPed::RestorePreviousState(void) +{ + if(!CanSetPedState() || m_nPedState == PED_FALL) + return; + + if (m_nPedState == PED_GETUP && !bGetUpAnimStarted) + return; + + if (InVehicle()) { + SetPedState(PED_DRIVING); + m_nLastPedState = PED_NONE; + } else { + if (m_nLastPedState == PED_NONE) { + if (!IsPlayer() && CharCreatedBy != MISSION_CHAR && m_objective == OBJECTIVE_NONE) { + if (SetWanderPath(CGeneral::GetRandomNumber() & 7) != 0) + return; + } + SetIdle(); + return; + } + + switch (m_nLastPedState) { + case PED_IDLE: + SetIdle(); + break; + case PED_WANDER_PATH: + SetPedState(PED_WANDER_PATH); + bIsRunning = false; + if (bFindNewNodeAfterStateRestore) { + if (m_pNextPathNode) { + CVector diff = m_pNextPathNode->GetPosition() - GetPosition(); + if (diff.MagnitudeSqr() < sq(7.0f)) { + SetMoveState(PEDMOVE_WALK); + break; + } + } + } + SetWanderPath(CGeneral::GetRandomNumber() & 7); + break; + default: + SetPedState(m_nLastPedState); + SetMoveState((eMoveState) m_nPrevMoveState); + break; + } + m_nLastPedState = PED_NONE; + } +} + +uint32 +CPed::ScanForThreats(void) +{ + int fearFlags = m_fearFlags; + CVector ourPos = GetPosition(); + float closestPedDist = 60.0f; + CVector2D explosionPos = GetPosition(); + if (fearFlags & PED_FLAG_EXPLOSION && CheckForExplosions(explosionPos)) { + m_eventOrThreat = explosionPos; + return PED_FLAG_EXPLOSION; + } + + CPed *shooter = nil; + if ((fearFlags & PED_FLAG_GUN) && (shooter = CheckForGunShots()) && (m_nPedType != shooter->m_nPedType || m_nPedType == PEDTYPE_CIVMALE || m_nPedType == PEDTYPE_CIVFEMALE)) { + if (!IsGangMember()) { + m_threatEntity = shooter; + m_threatEntity->RegisterReference((CEntity **) &m_threatEntity); + return PED_FLAG_GUN; + } + + if (CPedType::GetFlag(shooter->m_nPedType) & fearFlags) { + m_threatEntity = shooter; + m_threatEntity->RegisterReference((CEntity **) &m_threatEntity); + return CPedType::GetFlag(shooter->m_nPedType); + } + } + + CPed *deadPed; + if (fearFlags & PED_FLAG_DEADPEDS && CharCreatedBy != MISSION_CHAR + && (deadPed = CheckForDeadPeds()) != nil && (deadPed->GetPosition() - ourPos).MagnitudeSqr() < sq(20.0f) +#ifdef FIX_BUGS + && !deadPed->bIsInWater +#endif + ) { + m_pEventEntity = deadPed; + m_pEventEntity->RegisterReference((CEntity **) &m_pEventEntity); + return PED_FLAG_DEADPEDS; + } else { + uint32 flagsOfNearPed = 0; + + CPed *pedToFearFrom = nil; +#ifndef VC_PED_PORTS + for (int i = 0; i < m_numNearPeds; i++) { + if (CharCreatedBy != RANDOM_CHAR || m_nearPeds[i]->CharCreatedBy != MISSION_CHAR || m_nearPeds[i]->IsPlayer()) { + CPed *nearPed = m_nearPeds[i]; + + // BUG: WTF Rockstar?! Putting this here will result in returning the flags of farthest ped to us, since m_nearPeds is sorted by distance. + // Fixed at the bottom of the function. + flagsOfNearPed = CPedType::GetFlag(nearPed->m_nPedType); + + if (flagsOfNearPed & fearFlags) { + if (nearPed->m_fHealth > 0.0f && OurPedCanSeeThisOne(m_nearPeds[i])) { + // FIX: Taken from VC +#ifdef FIX_BUGS + float nearPedDistSqr = (nearPed->GetPosition() - ourPos).MagnitudeSqr2D(); +#else + float nearPedDistSqr = (CVector2D(ourPos) - explosionPos).MagnitudeSqr(); +#endif + if (sq(closestPedDist) > nearPedDistSqr) { + closestPedDist = Sqrt(nearPedDistSqr); + pedToFearFrom = m_nearPeds[i]; + } + } + } + } + } +#else + bool weSawOurEnemy = false; + bool weMaySeeOurEnemy = false; + float closestEnemyDist = 60.0f; + if ((CTimer::GetFrameCounter() + (uint8)m_randomSeed + 16) & 4) { + + for (int i = 0; i < m_numNearPeds; ++i) { + if (CharCreatedBy == RANDOM_CHAR && m_nearPeds[i]->CharCreatedBy == MISSION_CHAR && !m_nearPeds[i]->IsPlayer()) { + continue; + } + + // BUG: Explained at the same occurence of this bug above. Fixed at the bottom of the function. + flagsOfNearPed = CPedType::GetFlag(m_nearPeds[i]->m_nPedType); + + if (flagsOfNearPed & fearFlags) { + if (m_nearPeds[i]->m_fHealth > 0.0f) { + + // VC also has ability to include objects to line of sight check here (via last bit of flagsL) + if (OurPedCanSeeThisOne(m_nearPeds[i])) { + if (m_nearPeds[i]->m_nPedState == PED_ATTACK) { + if (m_nearPeds[i]->m_pedInObjective == this) { + + float enemyDistSqr = (m_nearPeds[i]->GetPosition() - ourPos).MagnitudeSqr2D(); + if (sq(closestEnemyDist) > enemyDistSqr) { + float enemyDist = Sqrt(enemyDistSqr); + weSawOurEnemy = true; + closestPedDist = enemyDist; + closestEnemyDist = enemyDist; + pedToFearFrom = m_nearPeds[i]; + } + } + } else { + float nearPedDistSqr = (m_nearPeds[i]->GetPosition() - ourPos).MagnitudeSqr2D(); + if (sq(closestPedDist) > nearPedDistSqr && !weSawOurEnemy) { + closestPedDist = Sqrt(nearPedDistSqr); + pedToFearFrom = m_nearPeds[i]; + } + } + } else if (!weSawOurEnemy) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->m_nPedState == PED_ATTACK) { + CColPoint foundCol; + CEntity *foundEnt; + + // We don't see him yet but he's behind a ped, vehicle or object + // VC also has ability to include objects to line of sight check here (via last bit of flagsL) + if (!CWorld::ProcessLineOfSight(ourPos, nearPed->GetPosition(), foundCol, foundEnt, + true, false, false, false, false, false, false)) { + + if (nearPed->m_pedInObjective == this) { + float enemyDistSqr = (m_nearPeds[i]->GetPosition() - ourPos).MagnitudeSqr2D(); + if (sq(closestEnemyDist) > enemyDistSqr) { + float enemyDist = Sqrt(enemyDistSqr); + weMaySeeOurEnemy = true; + closestPedDist = enemyDist; + closestEnemyDist = enemyDist; + pedToFearFrom = m_nearPeds[i]; + } + } else if (!nearPed->GetWeapon()->IsTypeMelee() && !weMaySeeOurEnemy) { + float nearPedDistSqr = (m_nearPeds[i]->GetPosition() - ourPos).MagnitudeSqr2D(); + if (sq(closestPedDist) > nearPedDistSqr) { + weMaySeeOurEnemy = true; + closestPedDist = Sqrt(nearPedDistSqr); + pedToFearFrom = m_nearPeds[i]; + } + } + } + } + } + } + } + } + } +#endif + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(ourPos, 20.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + CVehicle* foundVeh = nil; + for (int i = 0; i < lastVehicle; i++) { + CVehicle* nearVeh = (CVehicle*)vehicles[i]; + + CPed *driver = nearVeh->pDriver; + if (driver) { + + // BUG: Same bug as above. Fixed at the bottom of function. + flagsOfNearPed = CPedType::GetFlag(driver->m_nPedType); + if (flagsOfNearPed & fearFlags) { + if (driver->m_fHealth > 0.0f && OurPedCanSeeThisOne(nearVeh->pDriver)) { + // FIX: Taken from VC +#ifdef FIX_BUGS + float driverDistSqr = (driver->GetPosition() - ourPos).MagnitudeSqr2D(); +#else + float driverDistSqr = (CVector2D(ourPos) - explosionPos).MagnitudeSqr(); +#endif + if (sq(closestPedDist) > driverDistSqr) { + closestPedDist = Sqrt(driverDistSqr); + pedToFearFrom = nearVeh->pDriver; + } + } + } + } + } + m_threatEntity = pedToFearFrom; + if (m_threatEntity) + m_threatEntity->RegisterReference((CEntity **) &m_threatEntity); + +#ifdef FIX_BUGS + if (pedToFearFrom) + flagsOfNearPed = CPedType::GetFlag(((CPed*)m_threatEntity)->m_nPedType); + else + flagsOfNearPed = 0; +#endif + + return flagsOfNearPed; + } +} + +void +CPed::SetLookFlag(float direction, bool keepTryingToLook) +{ + if (m_lookTimer < CTimer::GetTimeInMilliseconds()) { + bIsLooking = true; + bIsRestoringLook = false; + m_pLookTarget = nil; + m_fLookDirection = direction; + m_lookTimer = 0; + bKeepTryingToLook = keepTryingToLook; + if (m_nPedState != PED_DRIVING) { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + } + } +} + +void +CPed::SetLookFlag(CEntity *target, bool keepTryingToLook) +{ + if (m_lookTimer < CTimer::GetTimeInMilliseconds()) { + bIsLooking = true; + bIsRestoringLook = false; + m_pLookTarget = target; + m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget); + m_fLookDirection = 999999.0f; + m_lookTimer = 0; + bKeepTryingToLook = keepTryingToLook; + if (m_nPedState != PED_DRIVING) { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + } + } +} + +void +CPed::ClearLookFlag(void) { + if (bIsLooking) { + bIsLooking = false; + bIsRestoringLook = true; + bShakeFist = false; + + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + if (IsPlayer()) + m_lookTimer = CTimer::GetTimeInMilliseconds() + 2000; + else + m_lookTimer = CTimer::GetTimeInMilliseconds() + 4000; + + if (m_nPedState == PED_LOOK_HEADING || m_nPedState == PED_LOOK_ENTITY) { + ClearLook(); + } + } +} + +void +FinishFuckUCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (animAssoc->animId == ANIM_STD_PARTIAL_FUCKU && ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) + ped->RemoveWeaponModel(0); +} + +void +CPed::MoveHeadToLook(void) +{ + CVector lookPos; + + if (m_lookTimer && CTimer::GetTimeInMilliseconds() > m_lookTimer) { + ClearLookFlag(); + } else if (m_nPedState == PED_DRIVING) { + m_pedIK.m_flags |= CPedIK::LOOKAROUND_HEAD_ONLY; + } + + if (m_pLookTarget) { + + if (!bShakeFist && GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) { + + CAnimBlendAssociation *fuckUAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_PARTIAL_FUCKU); + if (fuckUAssoc) { + + float animTime = fuckUAssoc->currentTime; + if (animTime > 4.0f / 30.0f && animTime - fuckUAssoc->timeStep > 4.0f / 30.0f) { + + bool lookingToCop = false; + if (m_pLookTarget->GetModelIndex() == MI_POLICE + || m_pLookTarget->IsPed() && ((CPed*)m_pLookTarget)->m_nPedType == PEDTYPE_COP) { + + lookingToCop = true; + } + + if (IsPlayer() && (m_pedStats->m_temper >= 52 || lookingToCop)) { + AddWeaponModel(MI_FINGERS); + ((CPlayerPed*)this)->AnnoyPlayerPed(true); + + } else if ((CGeneral::GetRandomNumber() & 3) == 0) { + AddWeaponModel(MI_FINGERS); + } + } + } + } + + if (m_pLookTarget->IsPed()) { + ((CPed*)m_pLookTarget)->m_pedIK.GetComponentPosition(lookPos, PED_MID); + } else { + lookPos = m_pLookTarget->GetPosition(); + } + + if (!m_pedIK.LookAtPosition(lookPos)) { + if (!bKeepTryingToLook) { + ClearLookFlag(); + } + return; + } + + if (!bShakeFist || bIsAimingGun || bIsRestoringGun) + return; + + if (m_lookTimer - CTimer::GetTimeInMilliseconds() >= 1000) + return; + + bool notRocketLauncher = false; + bool notTwoHanded = false; + AnimationId animToPlay = ANIM_STD_NUM; + + if (!GetWeapon()->IsType2Handed()) + notTwoHanded = true; + + if (notTwoHanded && GetWeapon()->m_eWeaponType != WEAPONTYPE_ROCKETLAUNCHER) + notRocketLauncher = true; + + if (IsPlayer() && notRocketLauncher) { + + if (m_pLookTarget->IsPed()) { + + if (m_pedStats->m_temper >= 49 && ((CPed*)m_pLookTarget)->m_nPedType != PEDTYPE_COP) { + + // FIX: Unreachable and meaningless condition +#ifndef FIX_BUGS + if (m_pedStats->m_temper < 47) +#endif + animToPlay = ANIM_STD_PARTIAL_PUNCH; + } else { + animToPlay = ANIM_STD_PARTIAL_FUCKU; + } + } else if (m_pedStats->m_temper > 49 || m_pLookTarget->GetModelIndex() == MI_POLICE) { + animToPlay = ANIM_STD_PARTIAL_FUCKU; + } + } else if (notRocketLauncher && (CGeneral::GetRandomNumber() & 1)) { + animToPlay = ANIM_STD_PARTIAL_FUCKU; + } + + if (animToPlay != ANIM_STD_NUM) { + CAnimBlendAssociation *newAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animToPlay, 4.0f); + + if (newAssoc) { + newAssoc->flags |= ASSOC_FADEOUTWHENDONE; + newAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (newAssoc->animId == ANIM_STD_PARTIAL_FUCKU) + newAssoc->SetDeleteCallback(FinishFuckUCB, this); + } + } + bShakeFist = false; + return; + } else if (999999.0f == m_fLookDirection) { + ClearLookFlag(); + } else if (!m_pedIK.LookInDirection(m_fLookDirection, 0.0f)) { + if (!bKeepTryingToLook) + ClearLookFlag(); + } +} + +void +CPed::RestoreHeadPosition(void) +{ + if (m_pedIK.RestoreLookAt()) { + bIsRestoringLook = false; + } +} + +void +CPed::SetAimFlag(float angle) +{ + bIsAimingGun = true; + bIsRestoringGun = false; + m_fLookDirection = angle; + m_lookTimer = 0; + m_pLookTarget = nil; + m_pSeekTarget = nil; + if (CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) + m_pedIK.m_flags |= CPedIK::AIMS_WITH_ARM; + else + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; +} + +void +CPed::SetAimFlag(CEntity *to) +{ + bIsAimingGun = true; + bIsRestoringGun = false; + m_pLookTarget = to; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + m_pSeekTarget = to; + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + m_lookTimer = 0; +} + +void +CPed::ClearAimFlag(void) +{ + if (bIsAimingGun) { + bIsAimingGun = false; + bIsRestoringGun = true; + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; +#if defined VC_PED_PORTS || defined FIX_BUGS + m_lookTimer = 0; +#endif + } + + if (IsPlayer()) { + ((CPlayerPed*)this)->m_fFPSMoveHeading = 0.0f; +#ifdef FREE_CAM + ((CPlayerPed*)this)->m_bFreeAimActive = false; +#endif + } +} + +void +CPed::AimGun(void) +{ + CVector vector; + + if (m_pSeekTarget) { + if (m_pSeekTarget->IsPed()) { + ((CPed*)m_pSeekTarget)->m_pedIK.GetComponentPosition(vector, PED_MID); + } else { + vector = m_pSeekTarget->GetPosition(); + } + Say(SOUND_PED_ATTACK); + + bCanPointGunAtTarget = m_pedIK.PointGunAtPosition(vector); + if (m_pLookTarget != m_pSeekTarget) { + SetLookFlag(m_pSeekTarget, true); + } + + } else { + if (IsPlayer()) { + bCanPointGunAtTarget = m_pedIK.PointGunInDirection(m_fLookDirection, ((CPlayerPed*)this)->m_fFPSMoveHeading); + } else { + bCanPointGunAtTarget = m_pedIK.PointGunInDirection(m_fLookDirection, 0.0f); + } + } +} + +void +CPed::RestoreGunPosition(void) +{ + if (bIsLooking) { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + bIsRestoringGun = false; + } else if (m_pedIK.RestoreGunPosn()) { + bIsRestoringGun = false; + } else { + if (IsPlayer()) + ((CPlayerPed*)this)->m_fFPSMoveHeading = 0.0f; + } +} + +void +CPed::ScanForInterestingStuff(void) +{ + if (!IsPedInControl()) + return; + + if (m_objective != OBJECTIVE_NONE) + return; + + if (CharCreatedBy == MISSION_CHAR) + return; + + LookForSexyPeds(); + LookForSexyCars(); + if (LookForInterestingNodes()) + return; + + if (m_nPedType == PEDTYPE_CRIMINAL && m_carJackTimer < CTimer::GetTimeInMilliseconds()) { + // Find a car to steal or a ped to mug if we haven't already decided to steal a car + if (CGeneral::GetRandomNumber() % 100 < 10) { + int mostExpensiveVehAround = -1; + int bestMonetaryValue = 0; + + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity *vehicles[8]; + CWorld::FindObjectsInRange(pos, 10.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle* veh = (CVehicle*)vehicles[i]; + + if (veh->VehicleCreatedBy != MISSION_VEHICLE) { + if (veh->m_vecMoveSpeed.Magnitude() <= 0.1f && veh->IsVehicleNormal() + && veh->IsCar() && bestMonetaryValue < veh->pHandling->nMonetaryValue) { + mostExpensiveVehAround = i; + bestMonetaryValue = veh->pHandling->nMonetaryValue; + } + } + } + if (bestMonetaryValue > 2000 && mostExpensiveVehAround != -1 && vehicles[mostExpensiveVehAround]) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, vehicles[mostExpensiveVehAround]); + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 5000; + return; + } + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 5000; + } else if (m_objective != OBJECTIVE_MUG_CHAR && !(CGeneral::GetRandomNumber() & 7)) { + CPed *charToMug = nil; + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + + if ((nearPed->GetPosition() - GetPosition()).MagnitudeSqr() > sq(7.0f)) + break; + + if ((nearPed->m_nPedType == PEDTYPE_CIVFEMALE || nearPed->m_nPedType == PEDTYPE_CIVMALE + || nearPed->m_nPedType == PEDTYPE_CRIMINAL || nearPed->m_nPedType == PEDTYPE_UNUSED1 + || nearPed->m_nPedType == PEDTYPE_PROSTITUTE) + && nearPed->CharCreatedBy != MISSION_CHAR + && nearPed->IsPedShootable() + && nearPed->m_objective != OBJECTIVE_MUG_CHAR) { + charToMug = nearPed; + break; + } + } + if (charToMug) + SetObjective(OBJECTIVE_MUG_CHAR, charToMug); + + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 5000; + } + } + + if (m_nPedState == PED_WANDER_PATH) { +#ifndef VC_PED_PORTS + if (CTimer::GetTimeInMilliseconds() > m_chatTimer) { + + // += 2 is weird + for (int i = 0; i < m_numNearPeds; i += 2) { + if (m_nearPeds[i]->m_nPedState == PED_WANDER_PATH && WillChat(m_nearPeds[i])) { + if (CGeneral::GetRandomNumberInRange(0, 100) >= 100) + m_chatTimer = CTimer::GetTimeInMilliseconds() + 30000; + else { + if ((GetPosition() - m_nearPeds[i]->GetPosition()).Magnitude() >= 1.8f) { + m_chatTimer = CTimer::GetTimeInMilliseconds() + 30000; + } else if (CanSeeEntity(m_nearPeds[i])) { + int time = CGeneral::GetRandomNumber() % 4000 + 10000; + SetChat(m_nearPeds[i], time); + m_nearPeds[i]->SetChat(this, time); + return; + } + } + } + } + } +#else + if (CGeneral::GetRandomNumberInRange(0.0f, 1.0f) < 0.5f) { + if (CTimer::GetTimeInMilliseconds() > m_chatTimer) { + for (int i = 0; i < m_numNearPeds; i ++) { + if (m_nearPeds[i] && m_nearPeds[i]->m_nPedState == PED_WANDER_PATH) { + if ((GetPosition() - m_nearPeds[i]->GetPosition()).Magnitude() < 1.8f + && CanSeeEntity(m_nearPeds[i]) + && m_nearPeds[i]->CanSeeEntity(this) + && WillChat(m_nearPeds[i])) { + + int time = CGeneral::GetRandomNumber() % 4000 + 10000; + SetChat(m_nearPeds[i], time); + m_nearPeds[i]->SetChat(this, time); + return; + } + } + } + } + } else { + m_chatTimer = CTimer::GetTimeInMilliseconds() + 200; + } +#endif + } + + // Parts below aren't there in VC, they're in somewhere else. + if (!CGame::noProstitutes && m_nPedType == PEDTYPE_PROSTITUTE && CharCreatedBy != MISSION_CHAR + && m_objectiveTimer < CTimer::GetTimeInMilliseconds() && !CTheScripts::IsPlayerOnAMission()) { + + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle* veh = (CVehicle*)vehicles[i]; + + if (veh->IsVehicleNormal()) { + if (veh->IsCar()) { + if ((GetPosition() - veh->GetPosition()).Magnitude() < 5.0f && veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, nil)) { + SetObjective(OBJECTIVE_SOLICIT_VEHICLE, veh); + Say(SOUND_PED_SOLICIT); + return; + } + } + } + } + } + if (m_nPedType == PEDTYPE_CIVMALE || m_nPedType == PEDTYPE_CIVFEMALE) { + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle* veh = (CVehicle*)vehicles[i]; + + if (veh->GetModelIndex() == MI_MRWHOOP) { + if (veh->GetStatus() != STATUS_ABANDONED && veh->GetStatus() != STATUS_WRECKED) { + if ((GetPosition() - veh->GetPosition()).Magnitude() < 5.0f) { + SetObjective(OBJECTIVE_BUY_ICE_CREAM, veh); + return; + } + } + } + } + } +} + +bool +CPed::WillChat(CPed *stranger) +{ + if (m_pNextPathNode && m_pLastPathNode) { + if (m_pNextPathNode != m_pLastPathNode && ThePaths.TestCrossesRoad(m_pNextPathNode, m_pLastPathNode)) { + return false; + } + } + if (m_nSurfaceTouched == SURFACE_TARMAC) + return false; + if (stranger == this) + return false; + if (m_nPedType == stranger->m_nPedType) + return true; + if (m_nPedType == PEDTYPE_CRIMINAL) + return false; + if ((IsGangMember() || stranger->IsGangMember()) && m_nPedType != stranger->m_nPedType) + return false; + return true; +} + +void +CPed::CalculateNewVelocity(void) +{ + if (IsPedInControl()) { + float headAmount = DEGTORAD(m_headingRate) * CTimer::GetTimeStep(); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + float limitedRotDest = CGeneral::LimitRadianAngle(m_fRotationDest); + + if (m_fRotationCur - PI > limitedRotDest) { + limitedRotDest += 2 * PI; + } else if(PI + m_fRotationCur < limitedRotDest) { + limitedRotDest -= 2 * PI; + } + +#ifdef FREE_CAM + if (!CCamera::bFreeCam || !TheCamera.Cams[0].Using3rdPersonMouseCam()) +#endif + if (IsPlayer() && m_nPedState == PED_ATTACK) + headAmount /= 4.0f; + + float neededTurn = limitedRotDest - m_fRotationCur; + if (neededTurn <= headAmount) { + if (neededTurn > (-headAmount)) + m_fRotationCur += neededTurn; + else + m_fRotationCur -= headAmount; + } else { + m_fRotationCur += headAmount; + } + } + + CVector2D forward(Sin(m_fRotationCur), Cos(m_fRotationCur)); + + m_moved.x = CrossProduct2D(m_vecAnimMoveDelta, forward); // (m_vecAnimMoveDelta.x * Cos(m_fRotationCur)) + -Sin(m_fRotationCur) * m_vecAnimMoveDelta.y; + m_moved.y = DotProduct2D(m_vecAnimMoveDelta, forward); // m_vecAnimMoveDelta.y* Cos(m_fRotationCur) + (m_vecAnimMoveDelta.x * Sin(m_fRotationCur)); + + if (CTimer::GetTimeStep() >= 0.01f) { + m_moved = m_moved * (1 / CTimer::GetTimeStep()); + } else { + m_moved = m_moved * (1 / 100.0f); + } + + if ((!TheCamera.Cams[TheCamera.ActiveCam].GetWeaponFirstPersonOn() && !TheCamera.Cams[0].Using3rdPersonMouseCam()) + || FindPlayerPed() != this || !CanStrafeOrMouseControl()) + return; + + float walkAngle = WorkOutHeadingForMovingFirstPerson(m_fRotationCur); + float pedSpeed = m_moved.Magnitude(); + float localWalkAngle = CGeneral::LimitRadianAngle(walkAngle - m_fRotationCur); + + if (localWalkAngle < -0.5f * PI) { + localWalkAngle += PI; + } else if (localWalkAngle > 0.5f * PI) { + localWalkAngle -= PI; + } + + // Interestingly this part is responsible for diagonal walking. + if (localWalkAngle > -DEGTORAD(50.0f) && localWalkAngle < DEGTORAD(50.0f)) { + TheCamera.Cams[TheCamera.ActiveCam].m_fPlayerVelocity = pedSpeed; + m_moved = CVector2D(-Sin(walkAngle), Cos(walkAngle)) * pedSpeed; + } + + CAnimBlendAssociation *idleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + CAnimBlendAssociation *fightAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); +#ifdef VC_PED_PORTS + if ((!idleAssoc || idleAssoc->blendAmount < 0.5f) && !fightAssoc && !bIsDucking) { +#else + if ((!idleAssoc || idleAssoc->blendAmount < 0.5f) && !fightAssoc) { +#endif + LimbOrientation newUpperLegs; + newUpperLegs.yaw = localWalkAngle; + + if (newUpperLegs.yaw < -DEGTORAD(100.0f)) { + newUpperLegs.yaw += PI; + } else if (newUpperLegs.yaw > DEGTORAD(100.0f)) { + newUpperLegs.yaw -= PI; + } + + if (newUpperLegs.yaw > -DEGTORAD(50.0f) && newUpperLegs.yaw < DEGTORAD(50.0f)) { +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ +/* + // this looks shit + newUpperLegs.pitch = 0.0f; + RwV3d axis = { -1.0f, 0.0f, 0.0f }; + RtQuatRotate(&m_pFrames[PED_UPPERLEGL]->hanimFrame->q, &axis, RADTODEG(newUpperLegs.yaw), rwCOMBINEPRECONCAT); + RtQuatRotate(&m_pFrames[PED_UPPERLEGR]->hanimFrame->q, &axis, RADTODEG(newUpperLegs.yaw), rwCOMBINEPRECONCAT); +*/ + newUpperLegs.pitch = 0.1f; + RwV3d Xaxis = { 1.0f, 0.0f, 0.0f }; + RwV3d Zaxis = { 0.0f, 0.0f, 1.0f }; + RtQuatRotate(&m_pFrames[PED_UPPERLEGL]->hanimFrame->q, &Zaxis, RADTODEG(newUpperLegs.pitch), rwCOMBINEPOSTCONCAT); + RtQuatRotate(&m_pFrames[PED_UPPERLEGL]->hanimFrame->q, &Xaxis, RADTODEG(newUpperLegs.yaw), rwCOMBINEPOSTCONCAT); + RtQuatRotate(&m_pFrames[PED_UPPERLEGR]->hanimFrame->q, &Zaxis, RADTODEG(newUpperLegs.pitch), rwCOMBINEPOSTCONCAT); + RtQuatRotate(&m_pFrames[PED_UPPERLEGR]->hanimFrame->q, &Xaxis, RADTODEG(newUpperLegs.yaw), rwCOMBINEPOSTCONCAT); + + bDontAcceptIKLookAts = true; + }else +#endif + { + newUpperLegs.pitch = 0.0f; + m_pedIK.RotateTorso(m_pFrames[PED_UPPERLEGL], &newUpperLegs, false); + m_pedIK.RotateTorso(m_pFrames[PED_UPPERLEGR], &newUpperLegs, false); + } + } + } +} + +float +CPed::WorkOutHeadingForMovingFirstPerson(float offset) +{ + if (!IsPlayer()) + return 0.0f; + + CPad *pad0 = CPad::GetPad(0); + float leftRight = pad0->GetPedWalkLeftRight(); + float upDown = pad0->GetPedWalkUpDown(); + float &angle = ((CPlayerPed*)this)->m_fWalkAngle; + + if (upDown != 0.0f) { + angle = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -leftRight, upDown); + } else { + if (leftRight < 0.0f) + angle = 0.5f * PI; + else if (leftRight > 0.0f) + angle = -0.5f * PI; + } + + return CGeneral::LimitRadianAngle(offset + angle); +} + +void +CPed::UpdatePosition(void) +{ + if (CReplay::IsPlayingBack() || !bIsStanding) + return; + + CVector2D velocityChange; + + SetHeading(m_fRotationCur); + if (m_pCurrentPhysSurface) { + CVector2D velocityOfSurface; + if (!IsPlayer() && m_pCurrentPhysSurface->IsVehicle() && ((CVehicle*)m_pCurrentPhysSurface)->IsBoat()) { + + // It seems R* didn't like m_vecOffsetFromPhysSurface for boats + CVector offsetToSurface = GetPosition() - m_pCurrentPhysSurface->GetPosition(); + offsetToSurface.z -= FEET_OFFSET; + + CVector surfaceMoveVelocity = m_pCurrentPhysSurface->m_vecMoveSpeed; + CVector surfaceTurnVelocity = CrossProduct(m_pCurrentPhysSurface->m_vecTurnSpeed, offsetToSurface); + + // Also we use that weird formula instead of friction if it's boat + float slideMult = -m_pCurrentPhysSurface->m_vecTurnSpeed.MagnitudeSqr(); + velocityOfSurface = slideMult * offsetToSurface * CTimer::GetTimeStep() + (surfaceTurnVelocity + surfaceMoveVelocity); + m_vecMoveSpeed.z = slideMult * offsetToSurface.z * CTimer::GetTimeStep() + (surfaceTurnVelocity.z + surfaceMoveVelocity.z); + } else { + velocityOfSurface = m_pCurrentPhysSurface->GetSpeed(m_vecOffsetFromPhysSurface); + } + // Reminder: m_moved is displacement from walking/running. + velocityChange = m_moved + velocityOfSurface - m_vecMoveSpeed; + m_fRotationCur += m_pCurrentPhysSurface->m_vecTurnSpeed.z * CTimer::GetTimeStep(); + m_fRotationDest += m_pCurrentPhysSurface->m_vecTurnSpeed.z * CTimer::GetTimeStep(); + } else if (m_nSurfaceTouched == SURFACE_STEEP_CLIFF && (m_vecDamageNormal.x != 0.0f || m_vecDamageNormal.y != 0.0f)) { + // Ped got damaged by steep slope + m_vecMoveSpeed = CVector(0.0f, 0.0f, -0.001f); + // some kind of + CVector2D reactionForce = m_vecDamageNormal; + reactionForce.Normalise(); + + velocityChange = 0.02f * reactionForce + m_moved; + + float reactionAndVelocityDotProd = DotProduct2D(reactionForce, velocityChange); + // they're in same direction + if (reactionAndVelocityDotProd < 0.0f) { + velocityChange -= reactionAndVelocityDotProd * reactionForce; + } + } else { + velocityChange = m_moved - m_vecMoveSpeed; + } + + // Take time step into account + if (m_pCurrentPhysSurface) { + float speedChange = velocityChange.Magnitude(); + float changeMult = speedChange; + if (m_nPedState == PED_DIE && m_pCurrentPhysSurface->IsVehicle()) { + changeMult = 0.002f * CTimer::GetTimeStep(); + } else if (!(m_pCurrentPhysSurface->IsVehicle() && ((CVehicle*)m_pCurrentPhysSurface)->IsBoat())) { + changeMult = 0.01f * CTimer::GetTimeStep(); + } + + if (speedChange > changeMult) { + velocityChange = velocityChange * (changeMult / speedChange); + } + } + m_vecMoveSpeed.x += velocityChange.x; + m_vecMoveSpeed.y += velocityChange.y; +} + +void +CPed::CalculateNewOrientation(void) +{ + if (CReplay::IsPlayingBack() || !IsPedInControl()) + return; + + SetHeading(m_fRotationCur); +} + +void +CPed::ClearAll(void) +{ + if (!IsPedInControl() && m_nPedState != PED_DEAD) + return; + + SetPedState(PED_NONE); + SetMoveState(PEDMOVE_NONE); + m_pSeekTarget = nil; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_fleeFromPosX = 0.0f; + m_fleeFromPosY = 0.0f; + m_fleeFrom = nil; + m_fleeTimer = 0; + bUsesCollision = true; +#ifdef VC_PED_PORTS + ClearPointGunAt(); +#else + ClearAimFlag(); + ClearLookFlag(); +#endif + bIsPointingGunAt = false; + bRenderPedInCar = true; + bKnockedUpIntoAir = false; + m_pCollidingEntity = nil; +} + +void +CPed::ProcessBuoyancy(void) +{ + static uint32 nGenerateRaindrops = 0; + static uint32 nGenerateWaterCircles = 0; + CRGBA color(((0.5f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed()) * 127.5f), + ((0.5f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue()) * 127.5f), + ((0.5f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen()) * 127.5f), + CGeneral::GetRandomNumberInRange(48.0f, 96.0f)); + + if (bInVehicle) + return; + + CVector buoyancyPoint; + CVector buoyancyImpulse; + +#ifndef VC_PED_PORTS + float buoyancyLevel = (m_nPedState == PED_DEAD ? 1.5f : 1.3f); +#else + float buoyancyLevel = (m_nPedState == PED_DEAD ? 1.8f : 1.1f); +#endif + + if (mod_Buoyancy.ProcessBuoyancy(this, GRAVITY * m_fMass * buoyancyLevel, &buoyancyPoint, &buoyancyImpulse)) { + bTouchingWater = true; + CEntity *entity; + CColPoint point; + if (CWorld::ProcessVerticalLine(GetPosition(), GetPosition().z - 3.0f, point, entity, false, true, false, false, false, false, nil) + && entity->IsVehicle() && ((CVehicle*)entity)->IsBoat()) { + bIsInWater = false; + return; + } + bIsInWater = true; + ApplyMoveForce(buoyancyImpulse); + if (!DyingOrDead()) { + if (bTryingToReachDryLand) { + if (buoyancyImpulse.z / m_fMass > GRAVITY * 0.4f * CTimer::GetTimeStep()) { + bTryingToReachDryLand = false; + CVector pos = GetPosition(); + if (PlacePedOnDryLand()) { + if (m_fHealth > 20.0f) + InflictDamage(nil, WEAPONTYPE_DROWNING, 15.0f, PEDPIECE_TORSO, false); + + if (bIsInTheAir) { + RpAnimBlendClumpSetBlendDeltas(GetClump(), ASSOC_PARTIAL, -1000.0f); + bIsInTheAir = false; + } + pos.z = pos.z - 0.8f; +#ifdef PC_PARTICLE + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, pos, CVector(0.0f, 0.0f, 0.0f), 0.0f, 50, color, true); +#else + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, pos, CVector(0.0f, 0.0f, 0.0f), 0.0f, 50, CRGBA(0, 0, 0, 0), true); +#endif + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + SetPedState(PED_IDLE); + return; + } + } + } + float speedMult = 0.0f; + if (buoyancyImpulse.z / m_fMass > GRAVITY * 0.75f * CTimer::GetTimeStep() + || mod_Buoyancy.m_waterlevel > GetPosition().z) { + speedMult = pow(0.9f, CTimer::GetTimeStep()); + m_vecMoveSpeed.x *= speedMult; + m_vecMoveSpeed.y *= speedMult; + m_vecMoveSpeed.z *= speedMult; + bIsStanding = false; + InflictDamage(nil, WEAPONTYPE_DROWNING, 3.0f * CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + } + if (buoyancyImpulse.z / m_fMass > GRAVITY * 0.25f * CTimer::GetTimeStep()) { + if (speedMult == 0.0f) { + speedMult = pow(0.9f, CTimer::GetTimeStep()); + } + m_vecMoveSpeed.x *= speedMult; + m_vecMoveSpeed.y *= speedMult; + if (m_vecMoveSpeed.z >= -0.1f) { + if (m_vecMoveSpeed.z < -0.04f) + m_vecMoveSpeed.z = -0.02f; + } else { + m_vecMoveSpeed.z = -0.01f; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLASH, 0.0f); +#ifdef PC_PARTICLE + CVector aBitForward = 2.2f * m_vecMoveSpeed + GetPosition(); + float level = 0.0f; + if (CWaterLevel::GetWaterLevel(aBitForward, &level, false)) + aBitForward.z = level; + + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, aBitForward, CVector(0.0f, 0.0f, 0.1f), 0.0f, 200, color, true); + nGenerateRaindrops = CTimer::GetTimeInMilliseconds() + 80; + nGenerateWaterCircles = CTimer::GetTimeInMilliseconds() + 100; +#else + CVector aBitForward = 1.6f * m_vecMoveSpeed + GetPosition(); + float level = 0.0f; + if (CWaterLevel::GetWaterLevel(aBitForward, &level, false)) + aBitForward.z = level + 0.5f; + + CVector vel = m_vecMoveSpeed * 0.1f; + vel.z = 0.18f; + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, aBitForward, vel, 0.0f, 350, CRGBA(0, 0, 0, 0), true); + nGenerateRaindrops = CTimer::GetTimeInMilliseconds() + 300; + nGenerateWaterCircles = CTimer::GetTimeInMilliseconds() + 60; +#endif + } + } + } else + return; + } else + bTouchingWater = false; + + if (nGenerateWaterCircles && CTimer::GetTimeInMilliseconds() >= nGenerateWaterCircles) { + CVector pos = GetPosition(); + float level = 0.0f; + if (CWaterLevel::GetWaterLevel(pos, &level, false)) + pos.z = level; + + if (pos.z != 0.0f) { + nGenerateWaterCircles = 0; + for(int i = 0; i < 4; i++) { +#ifdef PC_PARTICLE + pos.x += CGeneral::GetRandomNumberInRange(-0.75f, 0.75f); + pos.y += CGeneral::GetRandomNumberInRange(-0.75f, 0.75f); + CParticle::AddParticle(PARTICLE_RAIN_SPLASH_BIGGROW, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, color, 0, 0, 0, 0); +#else + pos.x += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f); + pos.y += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f); + CParticle::AddParticle(PARTICLE_RAIN_SPLASH_BIGGROW, pos+CVector(0.0f, 0.0f, 1.0f), CVector(0.0f, 0.0f, 0.0f)); +#endif + } + } + } + + if (nGenerateRaindrops && CTimer::GetTimeInMilliseconds() >= nGenerateRaindrops) { + CVector pos = GetPosition(); + float level = 0.0f; + if (CWaterLevel::GetWaterLevel(pos, &level, false)) + pos.z = level; + + if (pos.z >= 0.0f) { +#ifdef PC_PARTICLE + pos.z += 0.25f; +#else + pos.z += 0.5f; +#endif + nGenerateRaindrops = 0; +#ifdef PC_PARTICLE + CParticleObject::AddObject(POBJECT_SPLASHES_AROUND, pos, CVector(0.0f, 0.0f, 0.0f), 4.5f, 1500, CRGBA(0,0,0,0), true); +#else + CParticleObject::AddObject(POBJECT_SPLASHES_AROUND, pos, CVector(0.0f, 0.0f, 0.0f), 4.5f, 2500, CRGBA(0,0,0,0), true); +#endif + } + } +} + +void +CPed::ProcessControl(void) +{ + CColPoint foundCol; + CEntity *foundEnt = nil; + + if (m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + int alpha = CVisibilityPlugins::GetClumpAlpha(GetClump()); + if (!bFadeOut) { + if (alpha < 255) { + alpha += 16; + if (alpha > 255) + alpha = 255; + } + } else { + alpha -= 8; + if (alpha < 0) + alpha = 0; + } + + CVisibilityPlugins::SetClumpAlpha(GetClump(), alpha); + bIsShooting = false; + BuildPedLists(); + bIsInWater = false; + ProcessBuoyancy(); + + if (m_nPedState != PED_ARRESTED) { + if (m_nPedState == PED_DEAD) { + DeadPedMakesTyresBloody(); +#ifndef VC_PED_PORTS + if (CGame::nastyGame) { +#else + if (CGame::nastyGame && !bIsInWater) { +#endif + uint32 remainingBloodyFpTime = CTimer::GetTimeInMilliseconds() - m_bloodyFootprintCountOrDeathTime; + float timeDependentDist; + if (remainingBloodyFpTime >= 2000) { + if (remainingBloodyFpTime <= 7000) + timeDependentDist = (remainingBloodyFpTime - 2000) / 5000.0f * 0.75f; + else + timeDependentDist = 0.75f; + } else { + timeDependentDist = 0.0f; + } + + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (!nearPed->DyingOrDead()) { + CVector dist = nearPed->GetPosition() - GetPosition(); + if (dist.MagnitudeSqr() < sq(timeDependentDist)) { + nearPed->m_bloodyFootprintCountOrDeathTime = 200; + nearPed->bDoBloodyFootprints = true; + if (nearPed->IsPlayer()) { + if (!nearPed->bIsLooking && nearPed->m_nPedState != PED_ATTACK) { + int16 camMode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if (camMode != CCam::MODE_SNIPER + && camMode != CCam::MODE_ROCKETLAUNCHER + && camMode != CCam::MODE_M16_1STPERSON + && camMode != CCam::MODE_1STPERSON + && camMode != CCam::MODE_HELICANNON_1STPERSON + && !TheCamera.Cams[TheCamera.ActiveCam].GetWeaponFirstPersonOn()) { + + nearPed->SetLookFlag(this, true); + nearPed->SetLookTimer(500); + } + } + } + } + } + } + + if (remainingBloodyFpTime > 2000) { + CVector bloodPos = GetPosition(); + if (remainingBloodyFpTime - 2000 >= 5000) { + if (!m_deadBleeding) { + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, + 0.75f, 0.0f, 0.0f, -0.75f, 255, 255, 0, 0, 4.0f, 40000, 1.0f); + m_deadBleeding = true; + } + } else { + CShadows::StoreStaticShadow( + (uintptr)this + 17, SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, + (remainingBloodyFpTime - 2000) / 5000.0f * 0.75f, 0.0f, + 0.0f, (remainingBloodyFpTime - 2000) / 5000.0f * -0.75f, + 255, 255, 0, 0, 4.0f, 1.0f, 40.0f, false, 0.0f); + } + } + } + if (ServiceTalkingWhenDead()) + ServiceTalking(); + +#ifdef VC_PED_PORTS + if (bIsInWater) { + bIsStanding = false; + bWasStanding = false; + CPhysical::ProcessControl(); + } +#endif + return; + } + + bWasStanding = false; + if (bIsStanding) { + if (!CWorld::bForceProcessControl) { + if (m_pCurrentPhysSurface && m_pCurrentPhysSurface->bIsInSafePosition) { + bWasPostponed = true; + return; + } + } + } + + if (!IsPedInControl() || m_nWaitState != WAITSTATE_FALSE || 0.01f * CTimer::GetTimeStep() <= m_fDistanceTravelled + || (m_nStoredMoveState != PEDMOVE_WALK && m_nStoredMoveState != PEDMOVE_RUN && m_nStoredMoveState != PEDMOVE_SPRINT)) + m_panicCounter = 0; + else if (m_panicCounter < 50) + ++m_panicCounter; + + if (m_fHealth <= 1.0f && m_nPedState <= PED_STATES_NO_AI && !bIsInTheAir && !bIsLanding) + SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + + bCollidedWithMyVehicle = false; + + CEntity *collidingEnt = m_pDamageEntity; +#ifndef VC_PED_PORTS + if (!bUsesCollision || m_fDamageImpulse <= 0.0f || m_nPedState == PED_DIE || !collidingEnt) { +#else + if (!bUsesCollision || ((!collidingEnt || m_fDamageImpulse <= 0.0f) && (!IsPlayer() || !bIsStuck)) || m_nPedState == PED_DIE) { +#endif + bHitSomethingLastFrame = false; + if (m_nPedStateTimer <= 500 && bIsInTheAir) { + if (m_nPedStateTimer) + m_nPedStateTimer--; + } else if (m_nPedStateTimer < 1001) { + m_nPedStateTimer = 0; + } + } else { + if (m_panicCounter == 50 && IsPedInControl()) { + SetWaitState(WAITSTATE_STUCK, nil); + // Leftover + /* + if (m_nPedType < PEDTYPE_COP) { + + } else { + + } + */ +#ifndef VC_PED_PORTS + } else { +#else + } else if (collidingEnt) { +#endif + switch (collidingEnt->GetType()) + { + case ENTITY_TYPE_BUILDING: + case ENTITY_TYPE_OBJECT: + { + CBaseModelInfo *collidingModel = CModelInfo::GetModelInfo(collidingEnt->GetModelIndex()); + CColModel *collidingCol = collidingModel->GetColModel(); + if (collidingEnt->IsObject() && ((CObject*)collidingEnt)->m_nSpecialCollisionResponseCases != COLLRESPONSE_FENCEPART + || collidingCol->boundingBox.max.x < 3.0f + && collidingCol->boundingBox.max.y < 3.0f) { + + if (!IsPlayer()) { + SetDirectionToWalkAroundObject(collidingEnt); + break; + } + } + if (IsPlayer()) { + bHitSomethingLastFrame = true; + break; + } + + float angleToFaceWhenHit = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, + GetPosition().y, + m_vecDamageNormal.x + GetPosition().x, + m_vecDamageNormal.y + GetPosition().y); + + float neededTurn = Abs(m_fRotationCur - angleToFaceWhenHit); + + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + float oldDestRot = CGeneral::LimitRadianAngle(m_fRotationDest); + + if (m_pedInObjective && + (m_objective == OBJECTIVE_GOTO_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT)) { + + if (m_pedInObjective->IsPlayer() + && (neededTurn < DEGTORAD(20.0f) || m_panicCounter > 10)) { + if (CanPedJumpThis(collidingEnt)) { + SetJump(); + } else if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT) { + SetWaitState(WAITSTATE_LOOK_ABOUT, nil); + } else { + SetWaitState(WAITSTATE_PLAYANIM_TAXI, nil); + m_headingRate = 0.0f; + SetLookFlag(m_pedInObjective, true); + SetLookTimer(3000); + Say(SOUND_PED_TAXI_CALL); + } + } else { + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + } else { + if (m_nPedType != PEDTYPE_COP && neededTurn < DEGTORAD(15.0f) && m_nWaitState == WAITSTATE_FALSE) { + if ((m_nStoredMoveState == PEDMOVE_RUN || m_nStoredMoveState == PEDMOVE_SPRINT) && m_vecDamageNormal.z < 0.3f) { + CAnimBlendAssociation *runAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUN); + if (!runAssoc) + runAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNFAST); + + if (runAssoc && runAssoc->blendAmount > 0.9f && runAssoc->IsRunning()) { + SetWaitState(WAITSTATE_HITWALL, nil); + } + } + } + if (m_nPedState == PED_FLEE_POS) { + CVector2D fleePos = collidingEnt->GetPosition(); + uint32 oldFleeTimer = m_fleeTimer; + SetFlee(fleePos, 5000); + if (oldFleeTimer != m_fleeTimer) + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 500; + + } else { + if (m_nPedState == PED_FLEE_ENTITY && (neededTurn < DEGTORAD(25.0f) || m_panicCounter > 10)) { + m_collidingThingTimer = CTimer::GetTimeInMilliseconds() + 2500; + m_collidingEntityWhileFleeing = collidingEnt; + m_collidingEntityWhileFleeing->RegisterReference((CEntity **) &m_collidingEntityWhileFleeing); + + uint8 currentDir = Floor((PI + m_fRotationCur) / DEGTORAD(45.0f)); + uint8 nextDir; + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, currentDir, &nextDir); + + } else { + if (neededTurn < DEGTORAD(60.0f)) { + CVector posToHead = m_vecDamageNormal * 4.0f; + posToHead.z = 0.0f; + posToHead += GetPosition(); + int closestNodeId = ThePaths.FindNodeClosestToCoors(posToHead, PATH_PED, + 999999.9f, false, false); + float angleToFace; + + if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && m_objective != OBJECTIVE_KILL_CHAR_ANY_MEANS) { + if (m_nPedState != PED_SEEK_POS && m_nPedState != PED_SEEK_CAR) { + if (m_nPedState == PED_WANDER_PATH) { + m_pNextPathNode = &ThePaths.m_pathNodes[closestNodeId]; + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + m_pNextPathNode->GetX(), m_pNextPathNode->GetY(), + GetPosition().x, GetPosition().y); + } else { + if (ThePaths.m_pathNodes[closestNodeId].GetX() == 0.0f + || ThePaths.m_pathNodes[closestNodeId].GetY() == 0.0f) { + posToHead = (3.0f * m_vecDamageNormal) + GetPosition(); + posToHead.x += (CGeneral::GetRandomNumber() % 512) / 250.0f - 1.0f; + posToHead.y += (CGeneral::GetRandomNumber() % 512) / 250.0f - 1.0f; + } else { + posToHead.x = ThePaths.m_pathNodes[closestNodeId].GetX(); + posToHead.y = ThePaths.m_pathNodes[closestNodeId].GetY(); + } + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + posToHead.x, posToHead.y, + GetPosition().x, GetPosition().y); + + if (m_nPedState != PED_FOLLOW_PATH) + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 500; + } + } else { + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + ThePaths.m_pathNodes[closestNodeId].GetX(), + ThePaths.m_pathNodes[closestNodeId].GetY(), + GetPosition().x, + GetPosition().y); + + CVector2D distToNode = ThePaths.m_pathNodes[closestNodeId].GetPosition() - GetPosition(); + CVector2D distToSeekPos = m_vecSeekPos - GetPosition(); + + if (DotProduct2D(distToNode, distToSeekPos) < 0.0f) { + m_fRotationCur = m_fRotationDest; + break; + } + } + } else { + float angleToFaceAwayDamage = CGeneral::GetRadianAngleBetweenPoints( + m_vecDamageNormal.x, + m_vecDamageNormal.y, + 0.0f, + 0.0f); + + if (angleToFaceAwayDamage < m_fRotationCur) + angleToFaceAwayDamage += TWOPI; + + float neededTurn = angleToFaceAwayDamage - m_fRotationCur; + + if (neededTurn <= PI) { + angleToFace = 0.5f * neededTurn + m_fRotationCur; + m_fRotationCur += DEGTORAD(m_pedStats->m_headingChangeRate) * 2.0f; + } else { + angleToFace = m_fRotationCur - (TWOPI - neededTurn) * 0.5f; + m_fRotationCur -= DEGTORAD(m_pedStats->m_headingChangeRate) * 2.0f; + } + + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 200; + if (m_nPedType == PEDTYPE_COP) { + if (m_pedInObjective) { + float angleToLookCriminal = CGeneral::GetRadianAngleBetweenPoints( + m_pedInObjective->GetPosition().x, + m_pedInObjective->GetPosition().y, + GetPosition().x, + GetPosition().y); + + angleToLookCriminal = CGeneral::LimitRadianAngle(angleToLookCriminal); + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + + if (angleToLookCriminal < angleToFace) + angleToLookCriminal += TWOPI; + + float neededTurnToCriminal = angleToLookCriminal - angleToFace; + + if (neededTurnToCriminal > DEGTORAD(150.0f) && neededTurnToCriminal < DEGTORAD(210.0f)) { + ((CCopPed*)this)->m_bStopAndShootDisabledZone = true; + } + } + } + } + m_fRotationDest = CGeneral::LimitRadianAngle(angleToFace); + + if (m_fRotationCur - PI > m_fRotationDest) { + m_fRotationDest += TWOPI; + } else if (PI + m_fRotationCur < m_fRotationDest) { + m_fRotationDest -= TWOPI; + } + + if (oldDestRot == m_fRotationDest && CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 200; + m_fRotationDest += HALFPI; + } + } + } + } + } + + if (m_nPedState != PED_WANDER_PATH && m_nPedState != PED_FLEE_ENTITY) + m_pNextPathNode = nil; + + bHitSomethingLastFrame = true; + break; + } + case ENTITY_TYPE_VEHICLE: + { + CVehicle* collidingVeh = ((CVehicle*)collidingEnt); + float collidingVehSpeedSqr = collidingVeh->m_vecMoveSpeed.MagnitudeSqr(); + + if (collidingVeh == m_pMyVehicle) + bCollidedWithMyVehicle = true; +#ifdef VC_PED_PORTS + float oldHealth = m_fHealth; + bool playerSufferSound = false; + + if (collidingVehSpeedSqr <= 1.0f / 400.0f) { + if (IsPedInControl() + && (!IsPlayer() + || m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT + || m_objective == OBJECTIVE_RUN_TO_AREA + || m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER)) { + + if (collidingVeh != m_pCurrentPhysSurface || IsPlayer()) { + if (!bVehEnterDoorIsBlocked) { + if (collidingVeh->GetStatus() != STATUS_PLAYER || CharCreatedBy == MISSION_CHAR) { + + // VC calls SetDirectionToWalkAroundVehicle instead if ped is in PED_SEEK_CAR. + SetDirectionToWalkAroundObject(collidingVeh); + CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer = m_nPedStateTimer; + } else { + if (CTimer::GetTimeInMilliseconds() >= CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer + || m_nPedStateTimer >= CTimer::GetTimeInMilliseconds()) { + + // VC calls SetDirectionToWalkAroundVehicle instead if ped is in PED_SEEK_CAR. + SetDirectionToWalkAroundObject(collidingVeh); + CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer = m_nPedStateTimer; + + } else if (m_fleeFrom != collidingVeh) { + SetFlee(collidingVeh, 4000); + bUsePedNodeSeek = false; + SetMoveState(PEDMOVE_WALK); + } + } + } + } else { + float angleLeftToCompleteTurn = Abs(m_fRotationCur - m_fRotationDest); + if (angleLeftToCompleteTurn < 0.01f && CanPedJumpThis(collidingVeh)) { + SetJump(); + } + } + } else if (IsPlayer() && !bIsInTheAir) { + + if (IsPedInControl() && ((CPlayerPed*)this)->m_fMoveSpeed == 0.0f + && !bIsLooking && CTimer::GetTimeInMilliseconds() > m_lookTimer && collidingVeh->pDriver) { + + ((CPlayerPed*)this)->AnnoyPlayerPed(false); + SetLookFlag(collidingVeh, true); + SetLookTimer(1300); + + eWeaponType weaponType = GetWeapon()->m_eWeaponType; + if (weaponType == WEAPONTYPE_UNARMED + || weaponType == WEAPONTYPE_BASEBALLBAT + || weaponType == WEAPONTYPE_COLT45 + || weaponType == WEAPONTYPE_UZI) { + bShakeFist = true; + } + } else { + SetLookFlag(collidingVeh, true); + SetLookTimer(500); + } + } + } else { + float adjustedImpulse = m_fDamageImpulse; + if (IsPlayer()) { + if (bIsStanding) { + float forwardVecAndDamageDirDotProd = DotProduct(m_vecAnimMoveDelta.y * GetForward(), m_vecDamageNormal); + if (forwardVecAndDamageDirDotProd < 0.0f) { + adjustedImpulse = forwardVecAndDamageDirDotProd * m_fMass + m_fDamageImpulse; + if (adjustedImpulse < 0.0f) + adjustedImpulse = 0.0f; + } + } + } + if (m_fMass / 20.0f < adjustedImpulse) + DMAudio.PlayOneShot(collidingVeh->m_audioEntityId, SOUND_CAR_PED_COLLISION, adjustedImpulse); + + if (IsPlayer()) { + if (adjustedImpulse > 20.0f) + adjustedImpulse = 20.0f; + + if (adjustedImpulse > 5.0f) { + if (adjustedImpulse <= 13.0f) + playerSufferSound = true; + else + Say(SOUND_PED_DAMAGE); + } + + CColModel* collidingCol = CModelInfo::GetModelInfo(collidingVeh->m_modelIndex)->GetColModel(); + CVector colMinVec = collidingCol->boundingBox.min; + CVector colMaxVec = collidingCol->boundingBox.max; + + CVector vehColCenterDist = collidingVeh->GetMatrix() * ((colMinVec + colMaxVec) * 0.5f) - GetPosition(); + + // TLVC = To look vehicle center + + float angleToVehFront = collidingVeh->GetForward().Heading(); + float angleDiffFromLookingFrontTLVC = angleToVehFront - vehColCenterDist.Heading(); + angleDiffFromLookingFrontTLVC = CGeneral::LimitRadianAngle(angleDiffFromLookingFrontTLVC); + + // I don't know why do we use that + float vehTopRightHeading = Atan2(colMaxVec.x - colMinVec.x, colMaxVec.y - colMinVec.y); + + CVector vehDist = GetPosition() - collidingVeh->GetPosition(); + vehDist.Normalise(); + + float vehRightVecAndSpeedDotProd; + + if (Abs(angleDiffFromLookingFrontTLVC) >= vehTopRightHeading && Abs(angleDiffFromLookingFrontTLVC) < PI - vehTopRightHeading) { + if (angleDiffFromLookingFrontTLVC <= 0.0f) { + vehRightVecAndSpeedDotProd = DotProduct(collidingVeh->GetRight(), collidingVeh->m_vecMoveSpeed); + + // vehRightVecAndSpeedDotProd < 0.1f = Vehicle being overturned or spinning to it's right? + if (collidingVehSpeedSqr > 1.0f / 100.0f && vehRightVecAndSpeedDotProd < 0.1f) { + + // Car's right faces towards us and isn't coming directly to us + if (DotProduct(collidingVeh->GetRight(), GetForward()) < 0.0f + && DotProduct(vehDist, collidingVeh->m_vecMoveSpeed) > 0.0f) { + SetEvasiveStep(collidingVeh, 1); + } + } + } else { + vehRightVecAndSpeedDotProd = DotProduct(-1.0f * collidingVeh->GetRight(), collidingVeh->m_vecMoveSpeed); + + if (collidingVehSpeedSqr > 1.0f / 100.0f && vehRightVecAndSpeedDotProd < 0.1f) { + if (DotProduct(collidingVeh->GetRight(), GetForward()) > 0.0f + && DotProduct(vehDist, collidingVeh->m_vecMoveSpeed) > 0.0f) { + SetEvasiveStep(collidingVeh, 1); + } + } + } + } else { + vehRightVecAndSpeedDotProd = DotProduct(vehDist, collidingVeh->m_vecMoveSpeed); + } + + if (vehRightVecAndSpeedDotProd <= 0.1f) { + if (m_nPedState != PED_FIGHT) { + SetLookFlag(collidingVeh, true); + SetLookTimer(700); + } + } else { + bIsStanding = false; + CVector2D collidingEntMoveDir = -collidingVeh->m_vecMoveSpeed; + int dir = GetLocalDirection(collidingEntMoveDir); + SetFall(1000, (AnimationId)(dir + ANIM_STD_HIGHIMPACT_FRONT), false); + + float damage; + if (collidingVeh->m_modelIndex == MI_TRAIN) { + damage = 50.0f; + } else { + damage = 20.0f; + } + + InflictDamage(collidingVeh, WEAPONTYPE_RAMMEDBYCAR, damage, PEDPIECE_TORSO, dir); + Say(SOUND_PED_DAMAGE); + } + } else { + KillPedWithCar(collidingVeh, m_fDamageImpulse); + } + + /* VC specific + if (m_pCollidingEntity != collidingEnt) + bPushedAlongByCar = true; + */ + } + if (m_fHealth < oldHealth && playerSufferSound) + Say(SOUND_PED_HIT); +#else + if (collidingVehSpeedSqr <= 1.0f / 400.0f) { + if (!IsPedInControl() + || IsPlayer() + && m_objective != OBJECTIVE_GOTO_AREA_ON_FOOT + && m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER + && m_objective != OBJECTIVE_RUN_TO_AREA) { + + if (IsPlayer() && !bIsInTheAir) { + + if (IsPedInControl() + && ((CPlayerPed*)this)->m_fMoveSpeed == 0.0f + && !bIsLooking + && CTimer::GetTimeInMilliseconds() > m_lookTimer + && collidingVeh->pDriver) { + + ((CPlayerPed*)this)->AnnoyPlayerPed(false); + SetLookFlag(collidingVeh, true); + SetLookTimer(1300); + + eWeaponType weaponType = GetWeapon()->m_eWeaponType; + if (weaponType == WEAPONTYPE_UNARMED + || weaponType == WEAPONTYPE_BASEBALLBAT + || weaponType == WEAPONTYPE_COLT45 + || weaponType == WEAPONTYPE_UZI) { + bShakeFist = true; + } + } else { + SetLookFlag(collidingVeh, true); + SetLookTimer(500); + } + } + + } else if (!bVehEnterDoorIsBlocked) { + if (collidingVeh->GetStatus() != STATUS_PLAYER || CharCreatedBy == MISSION_CHAR) { + + SetDirectionToWalkAroundObject(collidingVeh); + + } else if (CTimer::GetTimeInMilliseconds() >= CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer + || m_nPedStateTimer >= CTimer::GetTimeInMilliseconds()) { + + SetDirectionToWalkAroundObject(collidingVeh); + CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer = m_nPedStateTimer; + + } else if (m_fleeFrom != collidingVeh) { + SetFlee(collidingVeh, 4000); + bUsePedNodeSeek = false; + SetMoveState(PEDMOVE_WALK); + } + } + } else { + DMAudio.PlayOneShot(collidingVeh->m_audioEntityId, SOUND_CAR_PED_COLLISION, m_fDamageImpulse); + if (IsPlayer()) { + CColModel *collidingCol = CModelInfo::GetColModel(collidingVeh->GetModelIndex()); + CVector colMinVec = collidingCol->boundingBox.min; + CVector colMaxVec = collidingCol->boundingBox.max; + + CVector vehColCenterDist = collidingVeh->GetMatrix() * ((colMinVec + colMaxVec) * 0.5f) - GetPosition(); + + // TLVC = To look vehicle center + + float angleToVehFront = collidingVeh->GetForward().Heading(); + float angleDiffFromLookingFrontTLVC = angleToVehFront - vehColCenterDist.Heading(); + angleDiffFromLookingFrontTLVC = CGeneral::LimitRadianAngle(angleDiffFromLookingFrontTLVC); + + // I don't know why do we use that + float vehTopRightHeading = Atan2(colMaxVec.x - colMinVec.x, colMaxVec.y - colMinVec.y); + + CVector vehDist = GetPosition() - collidingVeh->GetPosition(); + vehDist.Normalise(); + + float vehRightVecAndSpeedDotProd; + + if (Abs(angleDiffFromLookingFrontTLVC) >= vehTopRightHeading && Abs(angleDiffFromLookingFrontTLVC) < PI - vehTopRightHeading) { + if (angleDiffFromLookingFrontTLVC <= 0.0f) { + vehRightVecAndSpeedDotProd = DotProduct(collidingVeh->GetRight(), collidingVeh->m_vecMoveSpeed); + + // vehRightVecAndSpeedDotProd < 0.1f = Vehicle being overturned or spinning to it's right? + if (collidingVehSpeedSqr > 1.0f / 100.0f && vehRightVecAndSpeedDotProd < 0.1f) { + + // Car's right faces towards us and isn't coming directly to us + if (DotProduct(collidingVeh->GetRight(), GetForward()) < 0.0f + && DotProduct(vehDist, collidingVeh->m_vecMoveSpeed) > 0.0f) { + SetEvasiveStep(collidingVeh, 1); + } + } + } else { + vehRightVecAndSpeedDotProd = DotProduct(-1.0f * collidingVeh->GetRight(), collidingVeh->m_vecMoveSpeed); + + if (collidingVehSpeedSqr > 1.0f / 100.0f && vehRightVecAndSpeedDotProd < 0.1f) { + if (DotProduct(collidingVeh->GetRight(), GetForward()) > 0.0f + && DotProduct(vehDist, collidingVeh->m_vecMoveSpeed) > 0.0f) { + SetEvasiveStep(collidingVeh, 1); + } + } + } + } else { + vehRightVecAndSpeedDotProd = DotProduct(vehDist, collidingVeh->m_vecMoveSpeed); + } + + if (vehRightVecAndSpeedDotProd <= 0.1f) { + if (m_nPedState != PED_FIGHT) { + SetLookFlag(collidingVeh, true); + SetLookTimer(700); + } + } else { + bIsStanding = false; + CVector2D collidingEntMoveDir = -collidingVeh->m_vecMoveSpeed; + int dir = GetLocalDirection(collidingEntMoveDir); + SetFall(1000, (AnimationId)(dir + ANIM_STD_HIGHIMPACT_FRONT), false); + CPed *driver = collidingVeh->pDriver; + + float damage; + if (driver && driver->IsPlayer()) { + damage = vehRightVecAndSpeedDotProd * 1000.0f; + } else if (collidingVeh->GetModelIndex() == MI_TRAIN) { + damage = 50.0f; + } else { + damage = 20.0f; + } + + InflictDamage(collidingVeh, WEAPONTYPE_RAMMEDBYCAR, damage, PEDPIECE_TORSO, dir); + Say(SOUND_PED_DAMAGE); + } + } else { + KillPedWithCar(collidingVeh, m_fDamageImpulse); + } + } +#endif + break; + } + case ENTITY_TYPE_PED: + { + CollideWithPed((CPed*)collidingEnt); + if (((CPed*)collidingEnt)->IsPlayer()) { + CPlayerPed *player = ((CPlayerPed*)collidingEnt); + Say(SOUND_PED_CHAT); + if (m_nMoveState > PEDMOVE_STILL && player->IsPedInControl()) { + if (player->m_fMoveSpeed < 1.0f) { + if (!player->bIsLooking) { + if (CTimer::GetTimeInMilliseconds() > player->m_lookTimer) { + player->AnnoyPlayerPed(false); + player->SetLookFlag(this, true); + player->SetLookTimer(1300); + eWeaponType weapon = player->GetWeapon()->m_eWeaponType; + if (weapon == WEAPONTYPE_UNARMED + || weapon == WEAPONTYPE_BASEBALLBAT + || weapon == WEAPONTYPE_COLT45 + || weapon == WEAPONTYPE_UZI) { + player->bShakeFist = true; + } + } + } + } + } + } + break; + } + default: + break; + } + } + CVector forceDir; + if (!bIsInTheAir && m_nPedState != PED_JUMP +#ifdef VC_PED_PORTS + && m_fDamageImpulse > 0.0f +#endif + ) { + + forceDir = m_vecDamageNormal; + forceDir.z = 0.0f; + if (!bIsStanding) { + forceDir *= 4.0f; + } else { + forceDir *= 0.5f; + } + + ApplyMoveForce(forceDir); + } + if ((bIsInTheAir && !DyingOrDead()) +#ifdef VC_PED_PORTS + || (!bIsStanding && !bWasStanding && m_nPedState == PED_FALL) +#endif + ) { + if (m_nPedStateTimer > 0 && m_nPedStateTimer <= 1000) { + forceDir = GetPosition() - m_vecHitLastPos; + } else { + m_nPedStateTimer = 0; + m_vecHitLastPos = GetPosition(); + forceDir = CVector(0.0f, 0.0f, 0.0f); + } + + CVector offsetToCheck; + m_nPedStateTimer++; + + float adjustedTs = Max(CTimer::GetTimeStep(), 0.01f); + + CPad *pad0 = CPad::GetPad(0); + if ((m_nPedStateTimer <= 50.0f / (4.0f * adjustedTs) || m_nPedStateTimer * 0.01f <= forceDir.MagnitudeSqr()) + && (m_nCollisionRecords <= 1 || m_nPedStateTimer <= 50.0f / (2.0f * adjustedTs) || m_nPedStateTimer * 1.0f / 250.0f <= Abs(forceDir.z))) { + + if (m_nCollisionRecords == 1 && m_aCollisionRecords[0] != nil && m_aCollisionRecords[0]->IsBuilding() + && m_nPedStateTimer > 50.0f / (2.0f * adjustedTs) && m_nPedStateTimer * 1.0f / 250.0f > Abs(forceDir.z)) { + offsetToCheck.x = -forceDir.y; +#ifdef VC_PED_PORTS + offsetToCheck.z = 1.0f; +#else + offsetToCheck.z = 0.0f; +#endif + offsetToCheck.y = forceDir.x; + offsetToCheck.Normalise(); + + CVector posToCheck = GetPosition() + offsetToCheck; + + // These are either obstacle or ground to land, I don't know which one. + float obstacleForFlyingZ, obstacleForFlyingOtherDirZ; + CColPoint obstacleForFlying, obstacleForFlyingOtherDir; + + // Check is there any room for being knocked up in reverse direction of force + if (CWorld::ProcessVerticalLine(posToCheck, -20.0f, obstacleForFlying, foundEnt, true, false, false, false, false, false, nil)) { + obstacleForFlyingZ = obstacleForFlying.point.z; + } else { + obstacleForFlyingZ = 500.0f; + } + + posToCheck = GetPosition() - offsetToCheck; + + // Now check for direction of force this time + if (CWorld::ProcessVerticalLine(posToCheck, -20.0f, obstacleForFlyingOtherDir, foundEnt, true, false, false, false, false, false, nil)) { + obstacleForFlyingOtherDirZ = obstacleForFlyingOtherDir.point.z; + } else { + obstacleForFlyingOtherDirZ = 501.0f; + } +#ifdef VC_PED_PORTS + int16 flyDir = 0; + float feetZ = GetPosition().z - FEET_OFFSET; +#ifdef FIX_BUGS + if (obstacleForFlyingZ > feetZ && obstacleForFlyingZ < 500.0f) + flyDir = 1; + else if (obstacleForFlyingOtherDirZ > feetZ && obstacleForFlyingOtherDirZ < 501.0f) + flyDir = 2; +#else + if ((obstacleForFlyingZ > feetZ && obstacleForFlyingOtherDirZ < 500.0f) || (obstacleForFlyingZ > feetZ && obstacleForFlyingOtherDirZ > feetZ)) + flyDir = 1; + else if (obstacleForFlyingOtherDirZ > feetZ && obstacleForFlyingZ < 499.0f) + flyDir = 2; +#endif + + if (flyDir > 0 && !bSomeVCflag1) { + GetMatrix().SetTranslateOnly((flyDir == 2 ? obstacleForFlyingOtherDir.point : obstacleForFlying.point)); + GetMatrix().GetPosition().z += FEET_OFFSET; + GetMatrix().UpdateRW(); + SetLanding(); + bIsStanding = true; + } +#endif + if (obstacleForFlyingZ < obstacleForFlyingOtherDirZ) { + offsetToCheck *= -1.0f; + } + offsetToCheck.z = 1.0f; + forceDir = 4.0f * offsetToCheck; + forceDir.z = 4.0f; + ApplyMoveForce(forceDir); + + // What was that for?? It pushes player inside of collision sometimes and kills him. +#ifdef FIX_BUGS + if (!IsPlayer()) +#endif + GetMatrix().GetPosition() += 0.25f * offsetToCheck; + + m_fRotationCur = CGeneral::GetRadianAngleBetweenPoints(offsetToCheck.x, offsetToCheck.y, 0.0f, 0.0f); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + m_fRotationDest = m_fRotationCur; + SetHeading(m_fRotationCur); + + if (m_nPedState != PED_FALL && !bIsPedDieAnimPlaying) { + SetFall(1000, ANIM_STD_HIGHIMPACT_BACK, true); + } + bIsInTheAir = false; + } else if (m_vecDamageNormal.z > 0.4f) { +#ifndef VC_PED_PORTS + forceDir = m_vecDamageNormal; + forceDir.z = 0.0f; + forceDir.Normalise(); + ApplyMoveForce(2.0f * forceDir); +#else + if (m_nPedState == PED_JUMP) { + if (m_nWaitTimer <= 2000) { + if (m_nWaitTimer < 1000) + m_nWaitTimer += CTimer::GetTimeStepInMilliseconds(); + } else { + m_nWaitTimer = 0; + } + } + forceDir = m_vecDamageNormal; + forceDir.z = 0.0f; + forceDir.Normalise(); + if (m_nPedState != PED_JUMP || m_nWaitTimer >= 300) { + ApplyMoveForce(2.0f * forceDir); + } else { + ApplyMoveForce(-4.0f * forceDir); + } +#endif + } + } else if ((CTimer::GetFrameCounter() + m_randomSeed % 256 + 3) & 7) { + if (IsPlayer() && m_nPedState != PED_JUMP && pad0->JumpJustDown()) { + int16 padWalkX = pad0->GetPedWalkLeftRight(); + int16 padWalkY = pad0->GetPedWalkUpDown(); + if (Abs(padWalkX) > 0.0f || Abs(padWalkY) > 0.0f) { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -padWalkX, padWalkY); + m_fRotationDest -= TheCamera.Orientation; + m_fRotationDest = CGeneral::LimitRadianAngle(m_fRotationDest); + m_fRotationCur = m_fRotationDest; + SetHeading(m_fRotationCur); + } + SetJump(); + m_nPedStateTimer = 0; + m_vecHitLastPos = GetPosition(); + + // Why? forceDir is unused after this point. + forceDir = CVector(0.0f, 0.0f, 0.0f); + } else if (IsPlayer()) { + int16 padWalkX = pad0->GetPedWalkLeftRight(); + int16 padWalkY = pad0->GetPedWalkUpDown(); + if (Abs(padWalkX) > 0.0f || Abs(padWalkY) > 0.0f) { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -padWalkX, padWalkY); + m_fRotationDest -= TheCamera.Orientation; + m_fRotationDest = CGeneral::LimitRadianAngle(m_fRotationDest); + m_fRotationCur = m_fRotationDest; + SetHeading(m_fRotationCur); + } + CAnimBlendAssociation *jumpAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_JUMP_GLIDE); + + if (!jumpAssoc) + jumpAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_GLIDE); + + if (jumpAssoc) { + jumpAssoc->blendDelta = -3.0f; + jumpAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + if (m_nPedState == PED_JUMP) + m_nPedState = PED_IDLE; + } else { + CAnimBlendAssociation *jumpAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_JUMP_GLIDE); + + if (!jumpAssoc) + jumpAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_GLIDE); + + if (jumpAssoc) { + jumpAssoc->blendDelta = -3.0f; + jumpAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + } else { + offsetToCheck = GetPosition(); + offsetToCheck.z += 0.5f; + + if (CWorld::ProcessVerticalLine(offsetToCheck, GetPosition().z - FEET_OFFSET, foundCol, foundEnt, true, true, false, true, false, false, nil)) { +#ifdef VC_PED_PORTS + if (!bSomeVCflag1 || FEET_OFFSET + foundCol.point.z < GetPosition().z) { + GetMatrix().GetPosition().z = FEET_OFFSET + foundCol.point.z; + GetMatrix().UpdateRW(); + if (bSomeVCflag1) + bSomeVCflag1 = false; + } +#else + GetMatrix().GetPosition().z = FEET_OFFSET + foundCol.point.z; + GetMatrix().UpdateRW(); +#endif + SetLanding(); + bIsStanding = true; + } + } + } else if (m_nPedStateTimer < 1001) { + m_nPedStateTimer = 0; + } + } + + if (bIsDucking) + Duck(); + + if (bStartWanderPathOnFoot) { + if (IsPedInControl()) { + ClearAll(); + SetWanderPath(m_nPathDir); + bStartWanderPathOnFoot = false; + } else if (m_nPedState == PED_DRIVING) { + bWanderPathAfterExitingCar = true; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } + + if (!bIsStanding && m_vecMoveSpeed.z > 0.25f) { + float airResistance = Pow(0.95f, CTimer::GetTimeStep()); + + m_vecMoveSpeed *= airResistance; + } +#ifdef VC_PED_PORTS + if (IsPlayer() || !bIsStanding || m_vecMoveSpeed.x != 0.0f || m_vecMoveSpeed.y != 0.0f || m_vecMoveSpeed.z != 0.0f + || (m_nMoveState != PEDMOVE_NONE && m_nMoveState != PEDMOVE_STILL) + || m_vecAnimMoveDelta.x != 0.0f || m_vecAnimMoveDelta.y != 0.0f + || m_nPedState == PED_JUMP + || bIsInTheAir + || m_pCurrentPhysSurface) { + + CPhysical::ProcessControl(); + } else { + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + m_nCollisionRecords = 0; + bHasCollided = false; + m_nDamagePieceType = 0; + m_fDamageImpulse = 0.0f; + m_pDamageEntity = nil; + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + } +#else + CPhysical::ProcessControl(); +#endif + if (m_nPedState != PED_DIE || bIsPedDieAnimPlaying) { + if (m_nPedState != PED_DEAD) { + CalculateNewVelocity(); + CalculateNewOrientation(); + } + UpdatePosition(); + PlayFootSteps(); + if (IsPedInControl() && !bIsStanding && !m_pDamageEntity && CheckIfInTheAir()) { + SetInTheAir(); +#ifdef VC_PED_PORTS + bSomeVCflag1 = false; +#endif + } +#ifdef VC_PED_PORTS + if (bSomeVCflag1) { + CVector posToCheck = GetPosition(); + posToCheck.z += 0.9f; + if (!CWorld::TestSphereAgainstWorld(posToCheck, 0.2f, this, true, true, false, true, false, false)) + bSomeVCflag1 = false; + } +#endif + ProcessObjective(); + if (!bIsAimingGun) { + if (bIsRestoringGun) + RestoreGunPosition(); + } else { + AimGun(); + } + + if (bIsLooking) { + MoveHeadToLook(); + } else if (bIsRestoringLook) { + RestoreHeadPosition(); + } + + if (bIsInTheAir) + InTheAir(); + + if (bUpdateAnimHeading) { + if (m_nPedState != PED_GETUP && m_nPedState != PED_FALL) { + m_fRotationCur -= HALFPI; + m_fRotationDest = m_fRotationCur; + bUpdateAnimHeading = false; + } + } + + if (m_nWaitState != WAITSTATE_FALSE) + Wait(); + + if (m_nPedState != PED_IDLE) { + CAnimBlendAssociation *idleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_BIGGUN); + if(idleAssoc) { + idleAssoc->blendDelta = -8.0f; + idleAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + +#ifdef CANCELLABLE_CAR_ENTER + static bool cancelJack = false; + if (IsPlayer()) { + if (EnteringCar() && m_pVehicleAnim) { + CPad *pad = CPad::GetPad(0); + + if (!pad->ArePlayerControlsDisabled()) { + int vehAnim = m_pVehicleAnim->animId; + + int16 padWalkX = pad->GetPedWalkLeftRight(); + int16 padWalkY = pad->GetPedWalkUpDown(); + if (Abs(padWalkX) > 0.0f || Abs(padWalkY) > 0.0f) { + if (vehAnim == ANIM_STD_CAR_OPEN_DOOR_LHS || vehAnim == ANIM_STD_CAR_OPEN_DOOR_RHS || vehAnim == ANIM_STD_COACH_OPEN_LHS || vehAnim == ANIM_STD_COACH_OPEN_RHS || + vehAnim == ANIM_STD_VAN_OPEN_DOOR_REAR_LHS || vehAnim == ANIM_STD_VAN_OPEN_DOOR_REAR_RHS) { + + if (!m_pMyVehicle->pDriver) { + cancelJack = false; + bCancelEnteringCar = true; + } else + cancelJack = true; + } else if (vehAnim == ANIM_STD_QUICKJACK && m_pVehicleAnim->GetTimeLeft() > 0.75f) { + cancelJack = true; + } else if (vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LO_LHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LO_RHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_RHS) { + bCancelEnteringCar = true; + cancelJack = false; + } + } + if (cancelJack && vehAnim == ANIM_STD_QUICKJACK && m_pVehicleAnim->GetTimeLeft() > 0.75f && m_pVehicleAnim->GetTimeLeft() < 0.78f) { + cancelJack = false; + QuitEnteringCar(); + RestorePreviousObjective(); + } + if (cancelJack && (vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LO_LHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_LO_RHS || vehAnim == ANIM_STD_CAR_PULL_OUT_PED_RHS)) { + cancelJack = false; + bCancelEnteringCar = true; + } + } + } else + cancelJack = false; + } +#endif + + switch (m_nPedState) { + case PED_IDLE: + Idle(); + break; + case PED_LOOK_ENTITY: + case PED_LOOK_HEADING: + Look(); + break; + case PED_WANDER_RANGE: + WanderRange(); + CheckAroundForPossibleCollisions(); + break; + case PED_WANDER_PATH: + WanderPath(); + break; + case PED_ENTER_CAR: + case PED_CARJACK: + break; + case PED_FLEE_POS: + ms_vec2DFleePosition.x = m_fleeFromPosX; + ms_vec2DFleePosition.y = m_fleeFromPosY; + Flee(); + break; + case PED_FLEE_ENTITY: + if (!m_fleeFrom) { + SetIdle(); + break; + } + + if (CTimer::GetTimeInMilliseconds() <= m_nPedStateTimer) + break; + + ms_vec2DFleePosition = m_fleeFrom->GetPosition(); + Flee(); + break; + case PED_FOLLOW_PATH: + FollowPath(); + break; + case PED_PAUSE: + Pause(); + break; + case PED_ATTACK: + Attack(); + break; + case PED_FIGHT: + Fight(); + break; + case PED_CHAT: + Chat(); + break; + case PED_AIM_GUN: + if (m_pPointGunAt && m_pPointGunAt->IsPed() +#ifdef FIX_BUGS + && !GetWeapon()->IsTypeMelee() +#endif + && ((CPed*)m_pPointGunAt)->CanSeeEntity(this, CAN_SEE_ENTITY_ANGLE_THRESHOLD * 2)) { + ((CPed*)m_pPointGunAt)->ReactToPointGun(this); + } + PointGunAt(); + break; + case PED_SEEK_CAR: + SeekCar(); + break; + case PED_SEEK_IN_BOAT: + SeekBoatPosition(); + break; + case PED_INVESTIGATE: + InvestigateEvent(); + break; + case PED_ON_FIRE: + if (IsPlayer()) + break; + + if (CTimer::GetTimeInMilliseconds() <= m_fleeTimer) { + if (m_fleeFrom) { + ms_vec2DFleePosition = m_fleeFrom->GetPosition(); + } else { + ms_vec2DFleePosition.x = m_fleeFromPosX; + ms_vec2DFleePosition.y = m_fleeFromPosY; + } + Flee(); + } else { + if (m_pFire) + m_pFire->Extinguish(); + } + break; + case PED_FALL: + Fall(); + break; + case PED_GETUP: + SetGetUp(); + break; + case PED_ENTER_TRAIN: + EnterTrain(); + break; + case PED_EXIT_TRAIN: + ExitTrain(); + break; + case PED_DRIVING: + { + if (!m_pMyVehicle) { + bInVehicle = false; + FlagToDestroyWhenNextProcessed(); + return; + } + + if (m_pMyVehicle->pDriver != this || m_pMyVehicle->IsBoat()) { + LookForSexyPeds(); + LookForSexyCars(); + break; + } + + if (m_pMyVehicle->m_vehType == VEHICLE_TYPE_BIKE || !m_pMyVehicle->pDriver->IsPlayer()) { + break; + } + + CPad* pad = CPad::GetPad(0); + +#ifdef CAR_AIRBREAK + if (!pad->ArePlayerControlsDisabled()) { + if (pad->GetHorn()) { + float c = Cos(m_fRotationCur); + float s = Sin(m_fRotationCur); + m_pMyVehicle->GetRight() = CVector(1.0f, 0.0f, 0.0f); + m_pMyVehicle->GetForward() = CVector(0.0f, 1.0f, 0.0f); + m_pMyVehicle->GetUp() = CVector(0.0f, 0.0f, 1.0f); + if (pad->GetAccelerate()) { + m_pMyVehicle->ApplyMoveForce(GetForward() * 30.0f); + } else if (pad->GetBrake()) { + m_pMyVehicle->ApplyMoveForce(-GetForward() * 30.0f); + } else { + int16 lr = pad->GetSteeringLeftRight(); + if (lr < 0) { + //m_pMyVehicle->ApplyTurnForce(20.0f * -GetRight(), GetForward()); + m_pMyVehicle->ApplyMoveForce(-GetRight() * 30.0f); + } else if (lr > 0) { + m_pMyVehicle->ApplyMoveForce(GetRight() * 30.0f); + } else { + m_pMyVehicle->ApplyMoveForce(0.0f, 0.0f, 50.0f); + } + } + } + } +#endif + float steerAngle = m_pMyVehicle->m_fSteerAngle; + CAnimBlendAssociation *lDriveAssoc; + CAnimBlendAssociation *rDriveAssoc; + CAnimBlendAssociation *lbAssoc; + CAnimBlendAssociation *sitAssoc; + if (m_pMyVehicle->bLowVehicle) { + sitAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_LO); + + if (!sitAssoc || sitAssoc->blendAmount < 1.0f) { + break; + } + + lDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_LEFT_LO); + lbAssoc = nil; + rDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_RIGHT_LO); + } else { + sitAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT); + + if (!sitAssoc || sitAssoc->blendAmount < 1.0f) { + break; + } + + lDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_LEFT); + rDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_RIGHT); + lbAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_LOOKBEHIND); + + if (lbAssoc && + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON + && TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_LEFT) { + lbAssoc->blendDelta = -1000.0f; + } + } + + CAnimBlendAssociation *driveByAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + + if (!driveByAssoc) + driveByAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + + if (m_pMyVehicle->bLowVehicle || m_pMyVehicle->m_fGasPedal >= 0.0f || driveByAssoc) { + if (steerAngle == 0.0f || driveByAssoc) { + if (lDriveAssoc) + lDriveAssoc->blendAmount = 0.0f; + if (rDriveAssoc) + rDriveAssoc->blendAmount = 0.0f; + + } else if (steerAngle <= 0.0f) { + if (lDriveAssoc) + lDriveAssoc->blendAmount = 0.0f; + + if (rDriveAssoc) + rDriveAssoc->blendAmount = Clamp(steerAngle * -100.0f / 61.0f, 0.0f, 1.0f); + else if (m_pMyVehicle->bLowVehicle) + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVE_RIGHT_LO); + else + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVE_RIGHT); + + } else { + if (rDriveAssoc) + rDriveAssoc->blendAmount = 0.0f; + + if (lDriveAssoc) + lDriveAssoc->blendAmount = Clamp(steerAngle * 100.0f / 61.0f, 0.0f, 1.0f); + else if (m_pMyVehicle->bLowVehicle) + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVE_LEFT_LO); + else + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVE_LEFT); + } + + if (lbAssoc) + lbAssoc->blendDelta = -4.0f; + } else { + + if ((TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_1STPERSON + || TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking != LOOKING_LEFT) + && (!lbAssoc || lbAssoc->blendAmount < 1.0f)) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_LOOKBEHIND, 4.0f); + } + } + break; + } + case PED_DIE: + Die(); + break; + case PED_HANDS_UP: + if (m_pedStats->m_temper <= 50) { + if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HANDSCOWER)) { + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSCOWER); + Say(SOUND_PED_HANDS_COWER); + } + } else if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HANDSUP)) { + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSUP); + Say(SOUND_PED_HANDS_UP); + } + break; + default: break; + } + SetMoveAnim(); + if (bPedIsBleeding) { + if (CGame::nastyGame) { + if (!(CTimer::GetFrameCounter() & 3)) { + CVector cameraDist = GetPosition() - TheCamera.GetPosition(); + if (cameraDist.MagnitudeSqr() < sq(50.0f)) { + + float length = (CGeneral::GetRandomNumber() & 127) * 0.0015f + 0.15f; + CVector bloodPos( + ((CGeneral::GetRandomNumber() & 127) - 64) * 0.007f, + ((CGeneral::GetRandomNumber() & 127) - 64) * 0.007f, + 1.0f); + bloodPos += GetPosition(); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, length, 0.0f, + 0.0f, -length, 255, 255, 0, 0, 4.0f, (CGeneral::GetRandomNumber() & 4095) + 2000, 1.0f); + } + } + } + } + ServiceTalking(); + if (bInVehicle && !m_pMyVehicle) + bInVehicle = false; +#ifndef VC_PED_PORTS + m_pCurrentPhysSurface = nil; +#endif + } else { + if (bIsStanding && (!m_pCurrentPhysSurface || IsPlayer()) + || bIsInWater || !bUsesCollision) { + SetDead(); + } + m_pCurrentPhysSurface = nil; + } + } +} + +int32 +CPed::ProcessEntityCollision(CEntity *collidingEnt, CColPoint *collidingPoints) +{ + bool collidedWithBoat = false; + bool belowTorsoCollided = false; + float gravityEffect = -0.15f * CTimer::GetTimeStep(); + CColPoint intersectionPoint; + CColLine ourLine; + + CColModel *ourCol = CModelInfo::GetColModel(GetModelIndex()); + CColModel *hisCol = CModelInfo::GetColModel(collidingEnt->GetModelIndex()); + + if (!bUsesCollision) + return 0; + + if (collidingEnt->IsVehicle() && ((CVehicle*)collidingEnt)->IsBoat()) + collidedWithBoat = true; + + // ofc we're not vehicle + if (!m_bIsVehicleBeingShifted && !bSkipLineCol +#ifdef VC_PED_PORTS + && !collidingEnt->IsPed() +#endif + ) { + if (!bCollisionProcessed) { +#ifdef VC_PED_PORTS + m_pCurrentPhysSurface = nil; +#endif + if (bIsStanding) { + bIsStanding = false; + bWasStanding = true; + } + bCollisionProcessed = true; + m_fCollisionSpeed += m_vecMoveSpeed.Magnitude2D() * CTimer::GetTimeStep(); + bStillOnValidPoly = false; + if (IsPlayer() || m_fCollisionSpeed >= 1.0f + && (m_fCollisionSpeed >= 2.0f || m_nPedState != PED_WANDER_PATH)) { + m_collPoly.valid = false; + m_fCollisionSpeed = 0.0f; + bHitSteepSlope = false; + } else { + CVector pos = GetPosition(); + float potentialGroundZ = GetPosition().z - FEET_OFFSET; + if (bWasStanding) { + pos.z += -0.25f; + potentialGroundZ += gravityEffect; + } + if (CCollision::IsStoredPolyStillValidVerticalLine(pos, potentialGroundZ, intersectionPoint, &m_collPoly)) { + bStillOnValidPoly = true; +#ifdef VC_PED_PORTS + if(!bSomeVCflag1 || FEET_OFFSET + intersectionPoint.point.z < GetPosition().z) { + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; + if (bSomeVCflag1) + bSomeVCflag1 = false; + } +#else + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; +#endif + + m_vecMoveSpeed.z = 0.0f; + bIsStanding = true; + } else { + m_collPoly.valid = false; + m_fCollisionSpeed = 0.0f; + bHitSteepSlope = false; + } + } + } + + if (!bStillOnValidPoly) { + CVector potentialCenter = GetPosition(); + potentialCenter.z = GetPosition().z - 0.52f; + + // 0.52f should be a ped's approx. radius + float totalRadiusWhenCollided = collidingEnt->GetBoundRadius() + 0.52f - gravityEffect; + if (bWasStanding) { + if (collidedWithBoat) { + potentialCenter.z += 2.0f * gravityEffect; + totalRadiusWhenCollided += Abs(gravityEffect); + } else { + potentialCenter.z += gravityEffect; + } + } + if (sq(totalRadiusWhenCollided) > (potentialCenter - collidingEnt->GetBoundCentre()).MagnitudeSqr()) { + ourLine.p0 = GetPosition(); + ourLine.p1 = GetPosition(); + ourLine.p1.z = GetPosition().z - FEET_OFFSET; + if (bWasStanding) { + ourLine.p1.z = ourLine.p1.z + gravityEffect; + ourLine.p0.z = ourLine.p0.z + -0.25f; + } + float minDist = 1.0f; + belowTorsoCollided = CCollision::ProcessVerticalLine(ourLine, collidingEnt->GetMatrix(), *hisCol, + intersectionPoint, minDist, false, &m_collPoly); + + if (collidedWithBoat && bWasStanding && !belowTorsoCollided) { + ourLine.p0.z = ourLine.p1.z; + ourLine.p1.z = ourLine.p1.z + gravityEffect; + belowTorsoCollided = CCollision::ProcessVerticalLine(ourLine, collidingEnt->GetMatrix(), *hisCol, + intersectionPoint, minDist, false, &m_collPoly); + } + if (belowTorsoCollided) { +#ifndef VC_PED_PORTS + if (!collidingEnt->IsPed()) { +#endif + if (!bIsStanding + || FEET_OFFSET + intersectionPoint.point.z > GetPosition().z + || collidedWithBoat && 3.12f + intersectionPoint.point.z > GetPosition().z) { + + if (!collidingEnt->IsVehicle() && !collidingEnt->IsObject()) { + m_pCurSurface = collidingEnt; + collidingEnt->RegisterReference((CEntity**)&m_pCurSurface); + bTryingToReachDryLand = false; + bOnBoat = false; + } else { + m_pCurrentPhysSurface = (CPhysical*)collidingEnt; + collidingEnt->RegisterReference((CEntity**)&m_pCurrentPhysSurface); + m_vecOffsetFromPhysSurface = intersectionPoint.point - collidingEnt->GetPosition(); + m_pCurSurface = collidingEnt; + collidingEnt->RegisterReference((CEntity**)&m_pCurSurface); + m_collPoly.valid = false; + if (collidingEnt->IsVehicle() && ((CVehicle*)collidingEnt)->IsBoat()) { + bOnBoat = true; + } else { + bOnBoat = false; + } + } +#ifdef VC_PED_PORTS + if (!bSomeVCflag1 || FEET_OFFSET + intersectionPoint.point.z < GetPosition().z) { + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; + if (bSomeVCflag1) + bSomeVCflag1 = false; + } +#else + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; +#endif + m_nSurfaceTouched = intersectionPoint.surfaceB; + if (m_nSurfaceTouched == SURFACE_STEEP_CLIFF) { + bHitSteepSlope = true; + m_vecDamageNormal = intersectionPoint.normal; + } + } +#ifdef VC_PED_PORTS + float upperSpeedLimit = 0.33f; + float lowerSpeedLimit = -0.25f; + float speed = m_vecMoveSpeed.Magnitude2D(); + if (m_nPedState == PED_IDLE) { + upperSpeedLimit *= 2.0f; + lowerSpeedLimit *= 1.5f; + } + CAnimBlendAssociation *fallAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL); + if (!bWasStanding && ((speed > upperSpeedLimit /* ||!bPushedAlongByCar*/) || (m_vecMoveSpeed.z < lowerSpeedLimit)) + && m_pCollidingEntity != collidingEnt) { + + float damage = 100.0f * Max(speed - 0.25f, 0.0f); + float damage2 = damage; + if (m_vecMoveSpeed.z < -0.25f) + damage += (-0.25f - m_vecMoveSpeed.z) * 150.0f; + + uint8 dir = 2; // from backward + if (m_vecMoveSpeed.x > 0.01f || m_vecMoveSpeed.x < -0.01f || m_vecMoveSpeed.y > 0.01f || m_vecMoveSpeed.y < -0.01f) { + CVector2D offset = -m_vecMoveSpeed; + dir = GetLocalDirection(offset); + } + + InflictDamage(collidingEnt, WEAPONTYPE_FALL, damage, PEDPIECE_TORSO, dir); + if (IsPlayer() && damage2 > 5.0f) + Say(SOUND_PED_LAND); + + } else if (!bWasStanding && fallAnim && -0.016f * CTimer::GetTimeStep() > m_vecMoveSpeed.z) { + InflictDamage(collidingEnt, WEAPONTYPE_FALL, 15.0f, PEDPIECE_TORSO, 2); + } +#else + float speedSqr = 0.0f; + CAnimBlendAssociation *fallAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL); + if (!bWasStanding && (m_vecMoveSpeed.z < -0.25f || (speedSqr = m_vecMoveSpeed.MagnitudeSqr()) > sq(0.5f))) { + if (speedSqr == 0.0f) + speedSqr = sq(m_vecMoveSpeed.z); + + uint8 dir = 2; // from backward + if (m_vecMoveSpeed.x > 0.01f || m_vecMoveSpeed.x < -0.01f || m_vecMoveSpeed.y > 0.01f || m_vecMoveSpeed.y < -0.01f) { + CVector2D offset = -m_vecMoveSpeed; + dir = GetLocalDirection(offset); + } + InflictDamage(collidingEnt, WEAPONTYPE_FALL, 350.0f * sq(speedSqr), PEDPIECE_TORSO, dir); + + } else if (!bWasStanding && fallAnim && -0.016f * CTimer::GetTimeStep() > m_vecMoveSpeed.z) { + InflictDamage(collidingEnt, WEAPONTYPE_FALL, 15.0f, PEDPIECE_TORSO, 2); + } +#endif + m_vecMoveSpeed.z = 0.0f; + bIsStanding = true; +#ifndef VC_PED_PORTS + } else { + bOnBoat = false; + } +#endif + } else { + bOnBoat = false; + } + } + } + } + + int ourCollidedSpheres = CCollision::ProcessColModels(GetMatrix(), *ourCol, collidingEnt->GetMatrix(), *hisCol, collidingPoints, nil, nil); + if (ourCollidedSpheres > 0 || belowTorsoCollided) { + AddCollisionRecord(collidingEnt); + if (!collidingEnt->IsBuilding()) + ((CPhysical*)collidingEnt)->AddCollisionRecord(this); + + if (ourCollidedSpheres > 0 && (collidingEnt->IsBuilding() || collidingEnt->GetIsStatic())) { + bHasHitWall = true; + } + } + if (collidingEnt->IsBuilding() || collidingEnt->GetIsStatic()) { + + if (bWasStanding) { + CVector sphereNormal; + float normalLength; + for(int sphere = 0; sphere < ourCollidedSpheres; sphere++) { + sphereNormal = collidingPoints[sphere].normal; +#ifdef VC_PED_PORTS + if (sphereNormal.z >= -1.0f || !IsPlayer()) { +#endif + normalLength = sphereNormal.Magnitude2D(); + if (normalLength != 0.0f) { + sphereNormal.x = sphereNormal.x / normalLength; + sphereNormal.y = sphereNormal.y / normalLength; + } +#ifdef VC_PED_PORTS + } else { + float speed = m_vecMoveSpeed.Magnitude2D(); + sphereNormal.x = -m_vecMoveSpeed.x / Max(0.001f, speed); + sphereNormal.y = -m_vecMoveSpeed.y / Max(0.001f, speed); + GetMatrix().GetPosition().z -= 0.05f; + bSomeVCflag1 = true; + } +#endif + sphereNormal.Normalise(); + collidingPoints[sphere].normal = sphereNormal; + if (collidingPoints[sphere].surfaceB == SURFACE_STEEP_CLIFF) + bHitSteepSlope = true; + } + } + } + return ourCollidedSpheres; +} + +static void +particleProduceFootSplash(CPed *ped, CVector const &pos, float size, int times) +{ +#ifdef PC_PARTICLE + for (int i = 0; i < times; i++) { + CVector adjustedPos = pos; + adjustedPos.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + adjustedPos.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + + CVector direction = ped->GetForward() * -0.05f; + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, adjustedPos, direction, nil, size, CRGBA(32, 32, 32, 32), 0, 0, CGeneral::GetRandomNumber() & 1, 200); + } +#else + for ( int32 i = 0; i < times; i++ ) + { + CVector adjustedPos = pos; + adjustedPos.x += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + adjustedPos.y += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, adjustedPos, CVector(0.0f, 0.0f, 0.0f), nil, size, CRGBA(0, 0, 0, 0), 0, 0, CGeneral::GetRandomNumber() & 1, 200); + } +#endif +} + +static void +particleProduceFootDust(CPed *ped, CVector const &pos, float size, int times) +{ + switch (ped->m_nSurfaceTouched) + { + case SURFACE_TARMAC: + case SURFACE_GRAVEL: + case SURFACE_PAVEMENT: + case SURFACE_SAND: + for (int i = 0; i < times; ++i) { + CVector adjustedPos = pos; + adjustedPos.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + adjustedPos.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + CParticle::AddParticle(PARTICLE_PEDFOOT_DUST, adjustedPos, CVector(0.0f, 0.0f, 0.0f), nil, size, CRGBA(0, 0, 0, 0), 0, 0, 0, 0); + } + break; + default: + break; + } +} + +void +CPed::PlayFootSteps(void) +{ + if (bDoBloodyFootprints) { + if (m_bloodyFootprintCountOrDeathTime > 0 && m_bloodyFootprintCountOrDeathTime < 300) { + m_bloodyFootprintCountOrDeathTime--; + + if (m_bloodyFootprintCountOrDeathTime == 0) + bDoBloodyFootprints = false; + } + } + + if (!bIsStanding) + return; + + CAnimBlendAssociation *assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); + CAnimBlendAssociation *walkRunAssoc = nil; + float walkRunAssocBlend = 0.0f, idleAssocBlend = 0.0f; + + for (; assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + if (assoc->flags & ASSOC_WALK) { + walkRunAssoc = assoc; + walkRunAssocBlend += assoc->blendAmount; + } else if ((assoc->flags & ASSOC_NOWALK) == 0) { + idleAssocBlend += assoc->blendAmount; + } + } + +#ifdef GTA_PS2_STUFF + CAnimBlendAssociation *runStopAsoc = NULL; + + if ( IsPlayer() ) + { + runStopAsoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + + if ( runStopAsoc == NULL ) + runStopAsoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + } + + if ( runStopAsoc != NULL && runStopAsoc->blendAmount > 0.1f ) + { + { + CVector pos(0.0f, 0.0f, 0.0f); + TransformToNode(pos, PED_FOOTL); + + pos.z -= 0.1f; + pos += GetForward()*0.2f; + particleProduceFootDust(this, pos, 0.02f, 1); + } + + { + CVector pos(0.0f, 0.0f, 0.0f); + TransformToNode(pos, PED_FOOTR); + + pos.z -= 0.1f; + pos += GetForward()*0.2f; + particleProduceFootDust(this, pos, 0.02f, 1); + } + } +#endif + + + if (walkRunAssoc && walkRunAssocBlend > 0.5f && idleAssocBlend < 1.0f) { + float stepStart = 1 / 15.0f; + float stepEnd = walkRunAssoc->hierarchy->totalLength / 2.0f + stepStart; + float currentTime = walkRunAssoc->currentTime; + int stepPart = 0; + + if (currentTime >= stepStart && currentTime - walkRunAssoc->timeStep < stepStart) + stepPart = 1; + else if (currentTime >= stepEnd && currentTime - walkRunAssoc->timeStep < stepEnd) + stepPart = 2; + + if (stepPart != 0) { + DMAudio.PlayOneShot(m_audioEntityId, stepPart == 1 ? SOUND_STEP_START : SOUND_STEP_END, 1.0f); + CVector footPos(0.0f, 0.0f, 0.0f); + TransformToNode(footPos, stepPart == 1 ? PED_FOOTL : PED_FOOTR); + + CVector forward = GetForward(); + + footPos.z -= 0.1f; + footPos += 0.2f * forward; + + if (bDoBloodyFootprints) { + CVector2D top(forward * 0.26f); + CVector2D right(GetRight() * 0.14f); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &footPos, + top.x, top.y, + right.x, right.y, + 255, 255, 0, 0, 4.0f, 3000.0f, 1.0f); + + if (m_bloodyFootprintCountOrDeathTime <= 20) { + m_bloodyFootprintCountOrDeathTime = 0; + bDoBloodyFootprints = false; + } else { + m_bloodyFootprintCountOrDeathTime -= 20; + } + } + if (CWeather::Rain <= 0.1f || CCullZones::CamNoRain() || CCullZones::PlayerNoRain()) { + if(IsPlayer()) + particleProduceFootDust(this, footPos, 0.0f, 4); + } +#ifdef PC_PARTICLE + else if(stepPart == 2) +#else + else +#endif + { + particleProduceFootSplash(this, footPos, 0.15f, 4); + } + } + } + + if (m_nSurfaceTouched == SURFACE_WATER) { + float pedSpeed = CVector2D(m_vecMoveSpeed).Magnitude(); + if (pedSpeed > 0.03f && CTimer::GetFrameCounter() % 2 == 0 && pedSpeed > 0.13f) { +#ifdef PC_PARTICLE + float particleSize = pedSpeed * 2.0f; + + if (particleSize < 0.25f) + particleSize = 0.25f; + + if (particleSize > 0.75f) + particleSize = 0.75f; + + CVector particlePos = GetPosition() + GetForward() * 0.3f; + particlePos.z -= 1.2f; + + CVector particleDir = m_vecMoveSpeed * -0.75f; + + particleDir.z = CGeneral::GetRandomNumberInRange(0.01f, 0.03f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, particlePos, particleDir, nil, 0.8f * particleSize, CRGBA(155,155,185,128), 0, 0, 0, 0); + + particleDir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.05f); + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, particlePos, particleDir, nil, particleSize, CRGBA(255,255,255,255), 0, 0, 0, 0); +#else + CVector particlePos = (GetPosition() - 0.3f * GetUp()) + GetForward()*0.3f; + CVector particleDir = m_vecMoveSpeed * 0.45f; + particleDir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.05f); + CParticle::AddParticle(PARTICLE_PED_SPLASH, particlePos-CVector(0.0f, 0.0f, 1.2f), particleDir, nil, 0.0f, CRGBA(155, 185, 155, 255)); +#endif + } + } +} + +// Actually GetLocalDirectionTo(Turn/Look) +int +CPed::GetLocalDirection(const CVector2D &posOffset) +{ + int direction; + float angle; + + for (angle = posOffset.Heading() - m_fRotationCur + DEGTORAD(45.0f); angle < 0.0f; angle += TWOPI); + + for (direction = RADTODEG(angle)/90.0f; direction > 3; direction -= 4); + + // 0-forward, 1-left, 2-backward, 3-right. + return direction; +} + +#ifdef NEW_WALK_AROUND_ALGORITHM +CVector +LocalPosForWalkAround(CVector2D colMin, CVector2D colMax, int walkAround, uint32 enterDoorNode, bool itsVan) { + switch (walkAround) { + case 0: + if (enterDoorNode == CAR_DOOR_LF) + return CVector(colMin.x, colMax.y - 1.0f, 0.0f); + case 1: + return CVector(colMin.x, colMax.y, 0.0f); + case 2: + case 3: + if (walkAround == 3 && enterDoorNode == CAR_DOOR_RF) + return CVector(colMax.x, colMax.y - 1.0f, 0.0f); + + return CVector(colMax.x, colMax.y, 0.0f); + case 4: + if (enterDoorNode == CAR_DOOR_RR && !itsVan) + return CVector(colMax.x, colMin.y + 1.0f, 0.0f); + case 5: + return CVector(colMax.x, colMin.y, 0.0f); + case 6: + case 7: + if (walkAround == 7 && enterDoorNode == CAR_DOOR_LR && !itsVan) + return CVector(colMin.x, colMin.y + 1.0f, 0.0f); + + return CVector(colMin.x, colMin.y, 0.0f); + default: + return CVector(0.0f, 0.0f, 0.0f); + } +} + +bool +CanWeSeeTheCorner(CVector2D dist, CVector2D fwdOffset) +{ + // because fov isn't important if dist is more then 5 unit, we want shortest way + if (dist.Magnitude() > 5.0f) + return true; + + if (DotProduct2D(dist, fwdOffset) < 0.0f) + return false; + + return true; +} +#endif + +// This function looks completely same on VC. +void +CPed::SetDirectionToWalkAroundObject(CEntity *obj) +{ + float distLimitForTimer = 8.0f; + CColModel *objCol = CModelInfo::GetColModel(obj->GetModelIndex()); + CVector objColMin = objCol->boundingBox.min; + CVector objColMax = objCol->boundingBox.max; + CVector objColCenter = (objColMin + objColMax) / 2.0f; + CMatrix objMat(obj->GetMatrix()); + float dirToSet = obj->GetForward().Heading(); + bool goingToEnterCarAndItsVan = false; + bool goingToEnterCar = false; + bool objUpsideDown = false; + + float checkIntervalInDist = (objColMax.y - objColMin.y) * 0.1f; + float checkIntervalInTime; + + if (m_nMoveState == PEDMOVE_NONE || m_nMoveState == PEDMOVE_STILL) + return; + +#ifndef PEDS_REPORT_CRIMES_ON_PHONE + if (CharCreatedBy != MISSION_CHAR && obj->GetModelIndex() == MI_PHONEBOOTH1) { + bool isRunning = m_nMoveState == PEDMOVE_RUN || m_nMoveState == PEDMOVE_SPRINT; + SetFindPathAndFlee(obj, 5000, !isRunning); + return; + } +#endif + + CVector2D adjustedColMin(objColMin.x - 0.35f, objColMin.y - 0.35f); + CVector2D adjustedColMax(objColMax.x + 0.35f, objColMax.y + 0.35f); + + checkIntervalInDist = Max(checkIntervalInDist, 0.5f); + checkIntervalInDist = Min(checkIntervalInDist, (objColMax.z - objColMin.z) / 2.0f); + checkIntervalInDist = Min(checkIntervalInDist, (adjustedColMax.x - adjustedColMin.x) / 2.0f); + + if (objMat.GetUp().z < 0.0f) + objUpsideDown = true; + + if (obj->GetModelIndex() != MI_TRAFFICLIGHTS && obj->GetModelIndex() != MI_SINGLESTREETLIGHTS1 && obj->GetModelIndex() != MI_SINGLESTREETLIGHTS2) { + objColCenter = obj->GetMatrix() * objColCenter; + } else { + checkIntervalInDist = 0.4f; + if (objMat.GetUp().z <= 0.57f) { + + // Specific calculations for traffic lights, didn't get a bit. + adjustedColMin.x = 1.2f * (adjustedColMin.x < adjustedColMin.y ? adjustedColMin.x : adjustedColMin.y); + adjustedColMax.x = 1.2f * (adjustedColMax.x > adjustedColMax.y ? adjustedColMax.x : adjustedColMax.y); + adjustedColMin.y = 1.2f * objColMin.z; + adjustedColMax.y = 1.2f * objColMax.z; + dirToSet = objMat.GetUp().Heading(); + objMat.SetUnity(); + objMat.RotateZ(dirToSet); + objMat.GetPosition() += obj->GetPosition(); + objColCenter = obj->GetPosition(); + } else { + objColCenter.x = adjustedColMax.x - 0.25f; + objColCenter = obj->GetMatrix() * objColCenter; + distLimitForTimer = 0.75f; + } + objUpsideDown = false; + } + float oldRotDest = m_fRotationDest; +#ifndef NEW_WALK_AROUND_ALGORITHM + float angleToFaceObjCenter = (objColCenter - GetPosition()).Heading(); + float angleDiffBtwObjCenterAndForward = CGeneral::LimitRadianAngle(dirToSet - angleToFaceObjCenter); + float objTopRightHeading = Atan2(adjustedColMax.x - adjustedColMin.x, adjustedColMax.y - adjustedColMin.y); +#endif + + if (IsPlayer()) { + if (FindPlayerPed()->m_fMoveSpeed <= 0.0f) + checkIntervalInTime = 0.0f; + else + checkIntervalInTime = 2.0f / FindPlayerPed()->m_fMoveSpeed; + } else { + switch (m_nMoveState) { + case PEDMOVE_WALK: + checkIntervalInTime = 2.0f; + break; + case PEDMOVE_RUN: + checkIntervalInTime = 0.5f; + break; + case PEDMOVE_SPRINT: + checkIntervalInTime = 0.5f; + break; + default: + checkIntervalInTime = 0.0f; + break; + } + } + if (m_pSeekTarget == obj && obj->IsVehicle()) { + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER + || m_objective == OBJECTIVE_SOLICIT_VEHICLE) { + goingToEnterCar = true; + if (IsPlayer()) + checkIntervalInTime = 0.0f; + + if (((CVehicle*)obj)->bIsVan) + goingToEnterCarAndItsVan = true; + } + } + + int entityOnTopLeftOfObj = 0; + int entityOnBottomLeftOfObj = 0; + int entityOnTopRightOfObj = 0; + int entityOnBottomRightOfObj = 0; + + if (CTimer::GetTimeInMilliseconds() > m_collidingThingTimer || m_collidingEntityWhileFleeing != obj) { + bool collidingThingChanged = true; + CEntity *obstacle; + +#ifndef NEW_WALK_AROUND_ALGORITHM + if (!obj->IsVehicle() || objUpsideDown) { + collidingThingChanged = false; + } else { +#else + CVector cornerToGo = CVector(10.0f, 10.0f, 10.0f); + int dirToGo; + m_walkAroundType = 0; + int iWouldPreferGoingBack = 0; // 1:left 2:right +#endif + float adjustedCheckInterval = 0.7f * checkIntervalInDist; + CVector posToCheck; + + // Top left of obj + posToCheck.x = adjustedColMin.x + adjustedCheckInterval; + posToCheck.y = adjustedColMax.y - adjustedCheckInterval; + posToCheck.z = 0.0f; + posToCheck = objMat * posToCheck; + posToCheck.z += 0.6f; + obstacle = CWorld::TestSphereAgainstWorld(posToCheck, checkIntervalInDist, obj, + true, true, false, true, false, false); + if (obstacle) { + if (obstacle->IsBuilding()) { + entityOnTopLeftOfObj = 1; + } else if (obstacle->IsVehicle()) { + entityOnTopLeftOfObj = 2; + } else { + entityOnTopLeftOfObj = 3; + } + } +#ifdef NEW_WALK_AROUND_ALGORITHM + else { + CVector tl = obj->GetMatrix() * CVector(adjustedColMin.x, adjustedColMax.y, 0.0f) - GetPosition(); + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR)) { + cornerToGo = tl; + m_walkAroundType = 1; + + if (m_vehDoor == CAR_DOOR_LR) + iWouldPreferGoingBack = 1; + } else if(CanWeSeeTheCorner(tl, GetForward())){ + cornerToGo = tl; + dirToGo = GetLocalDirection(tl); + if (dirToGo == 1) + m_walkAroundType = 0; // ALL of the next turns will be right turn + else if (dirToGo == 3) + m_walkAroundType = 1; // ALL of the next turns will be left turn + } + } +#endif + + // Top right of obj + posToCheck.x = adjustedColMax.x - adjustedCheckInterval; + posToCheck.y = adjustedColMax.y - adjustedCheckInterval; + posToCheck.z = 0.0f; + posToCheck = objMat * posToCheck; + posToCheck.z += 0.6f; + obstacle = CWorld::TestSphereAgainstWorld(posToCheck, checkIntervalInDist, obj, + true, true, false, true, false, false); + if (obstacle) { + if (obstacle->IsBuilding()) { + entityOnTopRightOfObj = 1; + } else if (obstacle->IsVehicle()) { + entityOnTopRightOfObj = 2; + } else { + entityOnTopRightOfObj = 3; + } + } +#ifdef NEW_WALK_AROUND_ALGORITHM + else { + CVector tr = obj->GetMatrix() * CVector(adjustedColMax.x, adjustedColMax.y, 0.0f) - GetPosition(); + if (tr.Magnitude2D() < cornerToGo.Magnitude2D()) { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR)) { + cornerToGo = tr; + m_walkAroundType = 2; + + if (m_vehDoor == CAR_DOOR_RR) + iWouldPreferGoingBack = 2; + } else if (CanWeSeeTheCorner(tr, GetForward())) { + cornerToGo = tr; + dirToGo = GetLocalDirection(tr); + if (dirToGo == 1) + m_walkAroundType = 2; // ALL of the next turns will be right turn + else if (dirToGo == 3) + m_walkAroundType = 3; // ALL of the next turns will be left turn + } + } + } +#endif + + // Bottom right of obj + posToCheck.x = adjustedColMax.x - adjustedCheckInterval; + posToCheck.y = adjustedColMin.y + adjustedCheckInterval; + posToCheck.z = 0.0f; + posToCheck = objMat * posToCheck; + posToCheck.z += 0.6f; + obstacle = CWorld::TestSphereAgainstWorld(posToCheck, checkIntervalInDist, obj, + true, true, false, true, false, false); + if (obstacle) { + if (obstacle->IsBuilding()) { + entityOnBottomRightOfObj = 1; + } else if (obstacle->IsVehicle()) { + entityOnBottomRightOfObj = 2; + } else { + entityOnBottomRightOfObj = 3; + } + } +#ifdef NEW_WALK_AROUND_ALGORITHM + else { + CVector br = obj->GetMatrix() * CVector(adjustedColMax.x, adjustedColMin.y, 0.0f) - GetPosition(); + if (iWouldPreferGoingBack == 2) + m_walkAroundType = 4; + else if (br.Magnitude2D() < cornerToGo.Magnitude2D()) { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR)) { + cornerToGo = br; + m_walkAroundType = 5; + } else if (CanWeSeeTheCorner(br, GetForward())) { + cornerToGo = br; + dirToGo = GetLocalDirection(br); + if (dirToGo == 1) + m_walkAroundType = 4; // ALL of the next turns will be right turn + else if (dirToGo == 3) + m_walkAroundType = 5; // ALL of the next turns will be left turn + } + } + } +#endif + + // Bottom left of obj + posToCheck.x = adjustedColMin.x + adjustedCheckInterval; + posToCheck.y = adjustedColMin.y + adjustedCheckInterval; + posToCheck.z = 0.0f; + posToCheck = objMat * posToCheck; + posToCheck.z += 0.6f; + obstacle = CWorld::TestSphereAgainstWorld(posToCheck, checkIntervalInDist, obj, + true, true, false, true, false, false); + if (obstacle) { + if (obstacle->IsBuilding()) { + entityOnBottomLeftOfObj = 1; + } else if (obstacle->IsVehicle()) { + entityOnBottomLeftOfObj = 2; + } else { + entityOnBottomLeftOfObj = 3; + } + } +#ifdef NEW_WALK_AROUND_ALGORITHM + else { + CVector bl = obj->GetMatrix() * CVector(adjustedColMin.x, adjustedColMin.y, 0.0f) - GetPosition(); + if (iWouldPreferGoingBack == 1) + m_walkAroundType = 7; + else if (bl.Magnitude2D() < cornerToGo.Magnitude2D()) { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR)) { + cornerToGo = bl; + m_walkAroundType = 6; + } else if (CanWeSeeTheCorner(bl, GetForward())) { + cornerToGo = bl; + dirToGo = GetLocalDirection(bl); + if (dirToGo == 1) + m_walkAroundType = 6; // ALL of the next turns will be right turn + else if (dirToGo == 3) + m_walkAroundType = 7; // ALL of the next turns will be left turn + } + } + } +#else + } + + if (entityOnTopLeftOfObj && entityOnTopRightOfObj && entityOnBottomRightOfObj && entityOnBottomLeftOfObj) { + collidingThingChanged = false; + entityOnTopLeftOfObj = 0; + entityOnBottomLeftOfObj = 0; + entityOnTopRightOfObj = 0; + entityOnBottomRightOfObj = 0; + } + + if (!collidingThingChanged) { + m_walkAroundType = 0; + } else { + if (Abs(angleDiffBtwObjCenterAndForward) >= objTopRightHeading) { + if (PI - objTopRightHeading >= Abs(angleDiffBtwObjCenterAndForward)) { + if ((angleDiffBtwObjCenterAndForward <= 0.0f || objUpsideDown) && (angleDiffBtwObjCenterAndForward < 0.0f || !objUpsideDown)) { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR)) { + m_walkAroundType = 0; + } else { + if (CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) >= 0.0f) { + if (entityOnBottomRightOfObj == 1 || entityOnBottomRightOfObj && !entityOnTopLeftOfObj && !entityOnTopRightOfObj) { + m_walkAroundType = 1; + } else if (entityOnBottomLeftOfObj == 1 || entityOnBottomLeftOfObj && !entityOnTopLeftOfObj && !entityOnTopRightOfObj) { + m_walkAroundType = 1; + } + } else { + if (entityOnTopRightOfObj == 1 || entityOnTopRightOfObj && !entityOnBottomRightOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 4; + } else if (entityOnTopLeftOfObj == 1 || entityOnTopLeftOfObj && !entityOnBottomRightOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 4; + } + } + } + } else { + if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR)) { + m_walkAroundType = 0; + } else { + if (CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) <= 0.0f) { + if (entityOnBottomLeftOfObj == 1 || entityOnBottomLeftOfObj && !entityOnTopLeftOfObj && !entityOnTopRightOfObj) { + m_walkAroundType = 2; + } else if (entityOnBottomRightOfObj == 1 || entityOnBottomRightOfObj && !entityOnTopLeftOfObj && !entityOnTopRightOfObj) { + m_walkAroundType = 2; + } + } else { + if (entityOnTopLeftOfObj == 1 || entityOnTopLeftOfObj && !entityOnBottomRightOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 3; + } else if (entityOnTopRightOfObj == 1 || entityOnTopRightOfObj && !entityOnBottomRightOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 3; + } + } + } + } + } else if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) + || CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) < 0.0f) { + if (entityOnTopLeftOfObj == 1 || entityOnTopLeftOfObj && !entityOnTopRightOfObj && !entityOnBottomRightOfObj) { + m_walkAroundType = 3; + } + } else if (entityOnTopRightOfObj == 1 || entityOnTopRightOfObj && !entityOnTopLeftOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 4; + } + } else if (goingToEnterCar && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) + || CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) > 0.0f) { + if (entityOnBottomLeftOfObj == 1 || entityOnBottomLeftOfObj && !entityOnTopRightOfObj && !entityOnBottomRightOfObj) { + m_walkAroundType = 2; + } + } else if (entityOnBottomRightOfObj == 1 || entityOnBottomRightOfObj && !entityOnTopLeftOfObj && !entityOnBottomLeftOfObj) { + m_walkAroundType = 1; + } else { + m_walkAroundType = 0; + } + } +#endif + } + m_collidingEntityWhileFleeing = obj; + m_collidingEntityWhileFleeing->RegisterReference((CEntity**) &m_collidingEntityWhileFleeing); + + // TODO: This random may need to be changed. + m_collidingThingTimer = CTimer::GetTimeInMilliseconds() + 512 + CGeneral::GetRandomNumber(); + + CVector localPosToHead; + +#ifdef NEW_WALK_AROUND_ALGORITHM + int nextWalkAround = m_walkAroundType; + if (m_walkAroundType % 2 == 0) { + nextWalkAround += 2; + if (nextWalkAround > 6) + nextWalkAround = 0; + } else { + nextWalkAround -= 2; + if (nextWalkAround < 0) + nextWalkAround = 7; + } + + CVector nextPosToHead = objMat * LocalPosForWalkAround(adjustedColMin, adjustedColMax, nextWalkAround, goingToEnterCar ? m_vehDoor : 0, goingToEnterCarAndItsVan); + bool nextRouteIsClear = CWorld::GetIsLineOfSightClear(GetPosition(), nextPosToHead, true, true, true, true, true, true, false); + + if(nextRouteIsClear) + m_walkAroundType = nextWalkAround; + else { + CVector posToHead = objMat * LocalPosForWalkAround(adjustedColMin, adjustedColMax, m_walkAroundType, goingToEnterCar ? m_vehDoor : 0, goingToEnterCarAndItsVan); + bool currentRouteIsClear = CWorld::GetIsLineOfSightClear(GetPosition(), posToHead, + true, true, true, true, true, true, false); + + /* Either; + * - Some obstacle came in and it's impossible to reach current destination + * - We reached to the destination, but since next route is not clear, we're turning around us + */ + if (!currentRouteIsClear || + ((posToHead - GetPosition()).Magnitude2D() < 0.8f && + !CWorld::GetIsLineOfSightClear(GetPosition() + GetForward(), nextPosToHead, + true, true, true, true, true, true, false))) { + + // Change both target and direction (involves changing even/oddness) + if (m_walkAroundType % 2 == 0) { + m_walkAroundType -= 2; + if (m_walkAroundType < 0) + m_walkAroundType = 7; + else + m_walkAroundType += 1; + } else { + m_walkAroundType += 2; + if (m_walkAroundType > 7) + m_walkAroundType = 0; + else + m_walkAroundType -= 1; + } + } + } + + localPosToHead = LocalPosForWalkAround(adjustedColMin, adjustedColMax, m_walkAroundType, goingToEnterCar ? m_vehDoor : 0, goingToEnterCarAndItsVan); +#else + if (Abs(angleDiffBtwObjCenterAndForward) < objTopRightHeading) { + if (goingToEnterCar) { + if (goingToEnterCarAndItsVan) { + if (m_vehDoor == CAR_DOOR_LR || m_vehDoor == CAR_DOOR_RR) + return; + } + if (m_vehDoor != CAR_DOOR_LF && m_vehDoor != CAR_DOOR_LR && (!entityOnBottomRightOfObj || entityOnBottomLeftOfObj)) { + m_fRotationDest = CGeneral::LimitRadianAngle(dirToSet - HALFPI); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(HALFPI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } + } else { + if (m_walkAroundType != 1 && m_walkAroundType != 4 + && (m_walkAroundType || CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) <= 0.0f)) { + + m_fRotationDest = CGeneral::LimitRadianAngle(dirToSet - HALFPI); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(HALFPI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } + } + } else { + if (PI - objTopRightHeading >= Abs(angleDiffBtwObjCenterAndForward)) { + if (angleDiffBtwObjCenterAndForward <= 0.0f) { + if (!goingToEnterCar || !goingToEnterCarAndItsVan || m_vehDoor != CAR_DOOR_LR && m_vehDoor != CAR_DOOR_RR) { + if (goingToEnterCar) { + if (m_vehDoor == CAR_DOOR_RF || (m_vehDoor == CAR_DOOR_RR && !goingToEnterCarAndItsVan)) + return; + } + if (m_walkAroundType == 4 || m_walkAroundType == 3 + || !m_walkAroundType && CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) > 0.0f) { + + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } else { + m_fRotationDest = dirToSet; + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } + } else if (goingToEnterCar && goingToEnterCarAndItsVan && (m_vehDoor == CAR_DOOR_LR || m_vehDoor == CAR_DOOR_RR)) { + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } else { + if (goingToEnterCar) { + if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR && !goingToEnterCarAndItsVan) + return; + } + if (m_walkAroundType == 1 || m_walkAroundType == 2 + || !m_walkAroundType && CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) > 0.0f) { + + m_fRotationDest = dirToSet; + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMin.y; + } + } + } else { + if (goingToEnterCar && (!goingToEnterCarAndItsVan || m_vehDoor != CAR_DOOR_LR && m_vehDoor != CAR_DOOR_RR)) { + if (m_vehDoor != CAR_DOOR_LF && m_vehDoor != CAR_DOOR_LR && (!entityOnTopRightOfObj || entityOnTopLeftOfObj)) { + + m_fRotationDest = CGeneral::LimitRadianAngle(dirToSet - HALFPI); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(HALFPI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } + } else { + if (m_walkAroundType == 2 || m_walkAroundType == 3 + || !m_walkAroundType && CGeneral::LimitRadianAngle(m_fRotationDest - angleToFaceObjCenter) > 0.0f) { + + m_fRotationDest = CGeneral::LimitRadianAngle(dirToSet - HALFPI); + localPosToHead.x = adjustedColMax.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(HALFPI + dirToSet); + localPosToHead.x = adjustedColMin.x; + localPosToHead.z = 0.0f; + localPosToHead.y = adjustedColMax.y; + } + } + } + } +#endif + if (objUpsideDown) + localPosToHead.x = localPosToHead.x * -1.0f; + + localPosToHead = objMat * localPosToHead; + m_actionX = localPosToHead.x; + m_actionY = localPosToHead.y; + localPosToHead -= GetPosition(); + m_fRotationDest = CGeneral::LimitRadianAngle(localPosToHead.Heading()); + + if (m_fRotationDest != m_fRotationCur && bHitSomethingLastFrame) { + if (m_fRotationDest == oldRotDest) { + m_fRotationDest = oldRotDest; + } else { + m_fRotationDest = CGeneral::LimitRadianAngle(PI + dirToSet); + } + } + + float dist = localPosToHead.Magnitude2D(); + if (dist < 0.5f) + dist = 0.5f; + + if (dist > distLimitForTimer) + dist = distLimitForTimer; + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 280.0f * dist * checkIntervalInTime; +} + +bool +CPed::IsPedInControl(void) +{ + return m_nPedState <= PED_STATES_NO_AI + && !bIsInTheAir && !bIsLanding + && m_fHealth > 0.0f; +} + +bool +CPed::IsPedShootable(void) +{ + return m_nPedState <= PED_STATES_NO_ST; +} + +bool +CPed::UseGroundColModel(void) +{ + return m_nPedState == PED_FALL || + m_nPedState == PED_DIVE_AWAY || + m_nPedState == PED_DIE || + m_nPedState == PED_DEAD; +} + +bool +CPed::CanPedReturnToState(void) +{ + return m_nPedState <= PED_STATES_NO_AI && m_nPedState != PED_AIM_GUN && m_nPedState != PED_ATTACK && + m_nPedState != PED_FIGHT && m_nPedState != PED_STEP_AWAY && m_nPedState != PED_SNIPER_MODE && m_nPedState != PED_LOOK_ENTITY; +} + +bool +CPed::CanSetPedState(void) +{ + return !DyingOrDead() && m_nPedState != PED_ARRESTED && !EnteringCar() && m_nPedState != PED_STEAL_CAR; +} + +bool +CPed::CanStrafeOrMouseControl(void) +{ +#ifdef FREE_CAM + if (CCamera::bFreeCam) + return false; +#endif + return m_nPedState == PED_NONE || m_nPedState == PED_IDLE || m_nPedState == PED_FLEE_POS || m_nPedState == PED_FLEE_ENTITY || + m_nPedState == PED_ATTACK || m_nPedState == PED_FIGHT || m_nPedState == PED_AIM_GUN || m_nPedState == PED_JUMP; +} + +void +CPed::PedGetupCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + if (ped->m_nPedState == PED_GETUP) + RpAnimBlendClumpSetBlendDeltas(ped->GetClump(), ASSOC_PARTIAL, -1000.0f); + + ped->bFallenDown = false; + animAssoc->blendDelta = -1000.0f; + if (ped->m_nPedState == PED_GETUP) + ped->RestorePreviousState(); + + if (ped->m_nPedState != PED_FLEE_POS && ped->m_nPedState != PED_FLEE_ENTITY) + ped->SetMoveState(PEDMOVE_STILL); + else + ped->SetMoveState(PEDMOVE_RUN); + + ped->SetMoveAnim(); + ped->bGetUpAnimStarted = false; +} + +void +CPed::PedLandCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + animAssoc->blendDelta = -1000.0f; + ped->bIsLanding = false; + + if (ped->m_nPedState == PED_JUMP) + ped->RestorePreviousState(); +} + +void +CPed::PedStaggerCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + /* + CPed *ped = (CPed*)arg; + + if (ped->m_nPedState == PED_STAGGER) + // nothing + */ +} + +void +CPed::PedSetOutCarCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + + bool startedToRun = false; + ped->bUsesCollision = true; + ped->m_actionX = 0.0f; + ped->m_actionY = 0.0f; + ped->bVehExitWillBeInstant = false; + if (veh && veh->IsBoat()) + ped->ApplyMoveSpeed(); + + if (ped->m_objective == OBJECTIVE_LEAVE_CAR) + ped->RestorePreviousObjective(); +#ifdef VC_PED_PORTS + else if (ped->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) { + ped->m_fHealth = 0.0f; + ped->SetDie(ANIM_STD_HIT_FLOOR, 4.0f, 0.5f); + } +#endif + + ped->bInVehicle = false; + if (veh && veh->IsCar() && !veh->IsRoomForPedToLeaveCar(ped->m_vehDoor, nil)) { + ped->PositionPedOutOfCollision(); + } + + if (ped->m_nPedState == PED_EXIT_CAR) { + if (ped->m_nPedType == PEDTYPE_COP) + ped->SetIdle(); + else + ped->RestorePreviousState(); + + veh = ped->m_pMyVehicle; + if (ped->bFleeAfterExitingCar && veh) { + ped->bFleeAfterExitingCar = false; + ped->SetFlee(veh->GetPosition(), 12000); + ped->bUsePedNodeSeek = true; + ped->m_pNextPathNode = nil; + if (CGeneral::GetRandomNumber() & 1 || ped->m_pedStats->m_fear > 70) { + ped->SetMoveState(PEDMOVE_SPRINT); + ped->Say(SOUND_PED_FLEE_SPRINT); + } else { + ped->SetMoveState(PEDMOVE_RUN); + ped->Say(SOUND_PED_FLEE_RUN); + } + startedToRun = true; + + // This is not a good way to do this... + ped->m_nLastPedState = PED_WANDER_PATH; + + } else if (ped->bWanderPathAfterExitingCar) { + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + ped->bWanderPathAfterExitingCar = false; + if (ped->m_nPedType == PEDTYPE_PROSTITUTE) + ped->SetObjectiveTimer(30000); + ped->m_nLastPedState = PED_NONE; + + } else if (ped->bGonnaKillTheCarJacker) { + + // Kill objective is already given at this point. + ped->bGonnaKillTheCarJacker = false; + if (ped->m_pedInObjective) { + if (!(CGeneral::GetRandomNumber() & 1) + && ped->m_nPedType != PEDTYPE_COP + && (!ped->m_pedInObjective->IsPlayer() || !CTheScripts::IsPlayerOnAMission())) { + ped->ClearObjective(); + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, veh); + } + ped->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 1500; + } + int waitTime = 1500; + ped->SetWaitState(WAITSTATE_PLAYANIM_COWER, &waitTime); + ped->SetMoveState(PEDMOVE_RUN); + startedToRun = true; + } else if (ped->m_objective == OBJECTIVE_NONE && ped->CharCreatedBy != MISSION_CHAR && ped->m_nPedState == PED_IDLE && !ped->IsPlayer()) { + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + } + } +#ifdef VC_PED_PORTS + else { + ped->m_nPedState = PED_IDLE; + } +#endif + + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + ped->RestartNonPartialAnims(); + ped->m_pVehicleAnim = nil; + CVector posFromZ = ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&posFromZ); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + ped->SetPosition(posFromZ); + veh = ped->m_pMyVehicle; + if (veh) { + if (ped->m_nPedType == PEDTYPE_PROSTITUTE) { + if (veh->pDriver) { + if (veh->pDriver->IsPlayer() && ped->CharCreatedBy == RANDOM_CHAR) { + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime = 0; + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = 0; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil; + CWorld::Players[CWorld::PlayerInFocus].m_nMoney -= 100; + if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney < 0) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney = 0; + } + } + } + veh->m_nGettingOutFlags &= ~GetCarDoorFlag(ped->m_vehDoor); + if (veh->pDriver == ped) { + veh->RemoveDriver(); +#ifndef FIX_BUGS // RemoveDriver does it anyway + veh->SetStatus(STATUS_ABANDONED); +#endif + if (veh->m_nDoorLock == CARLOCK_LOCKED_INITIALLY) + veh->m_nDoorLock = CARLOCK_UNLOCKED; + if (ped->m_nPedType == PEDTYPE_COP && veh->IsLawEnforcementVehicle()) + veh->ChangeLawEnforcerState(false); + } else { + veh->RemovePassenger(ped); + } + + if (veh->bIsBus && !veh->IsUpsideDown() && !veh->IsOnItsSide()) { + float angleAfterExit; + if (ped->m_vehDoor == CAR_DOOR_LF) { + angleAfterExit = HALFPI + veh->GetForward().Heading(); + } else { + angleAfterExit = veh->GetForward().Heading() - HALFPI; + } + ped->SetHeading(angleAfterExit); + ped->m_fRotationDest = angleAfterExit; + ped->m_fRotationCur = angleAfterExit; + if (!ped->bBusJacked) + ped->SetMoveState(PEDMOVE_WALK); + } + if (CGarages::IsPointWithinAnyGarage(ped->GetPosition())) + veh->bLightsOn = false; + } + + if (ped->IsPlayer()) + AudioManager.PlayerJustLeftCar(); + + ped->ReplaceWeaponWhenExitingVehicle(); + + ped->bOnBoat = false; + if (ped->bBusJacked) { + ped->SetFall(1500, ANIM_STD_HIGHIMPACT_BACK, false); + ped->bBusJacked = false; + } + ped->m_nStoredMoveState = PEDMOVE_NONE; + if (!ped->IsPlayer()) { + // It's a shame... +#ifdef FIX_BUGS + int createdBy = ped->CharCreatedBy; +#else + int createdBy = !ped->CharCreatedBy; +#endif + + if (createdBy == MISSION_CHAR && !startedToRun) + ped->SetMoveState(PEDMOVE_WALK); + } +} + +void +CPed::PedSetDraggedOutCarCB(CAnimBlendAssociation *dragAssoc, void *arg) +{ + CAnimBlendAssociation *quickJackedAssoc; + CVehicle *vehicle; + CPed *ped = (CPed*)arg; + + quickJackedAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), ANIM_STD_QUICKJACKED); + if (ped->m_nPedState != PED_ARRESTED) { + ped->m_nLastPedState = PED_NONE; + if (dragAssoc) + dragAssoc->blendDelta = -1000.0f; + } + ped->RestartNonPartialAnims(); + ped->m_pVehicleAnim = nil; + ped->m_pSeekTarget = nil; + vehicle = ped->m_pMyVehicle; + + if (vehicle) { + vehicle->m_nGettingOutFlags &= ~GetCarDoorFlag(ped->m_vehDoor); + + if (vehicle->pDriver == ped) { + vehicle->RemoveDriver(); + if (vehicle->m_nDoorLock == CARLOCK_LOCKED_INITIALLY) + vehicle->m_nDoorLock = CARLOCK_UNLOCKED; + + if (ped->m_nPedType == PEDTYPE_COP && vehicle->IsLawEnforcementVehicle()) + vehicle->ChangeLawEnforcerState(false); + } else { + vehicle->RemovePassenger(ped); + } + } + ped->bInVehicle = false; + if (ped->IsPlayer()) + AudioManager.PlayerJustLeftCar(); + +#ifdef VC_PED_PORTS + if (ped->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) { + dragAssoc->SetDeleteCallback(PedSetDraggedOutCarPositionCB, ped); + ped->m_fHealth = 0.0f; + ped->SetDie(ANIM_STD_HIT_FLOOR, 1000.0f, 0.5f); + return; + } +#endif + + if (quickJackedAssoc) { + dragAssoc->SetDeleteCallback(PedSetQuickDraggedOutCarPositionCB, ped); + } else { + dragAssoc->SetDeleteCallback(PedSetDraggedOutCarPositionCB, ped); + if (ped->CanSetPedState()) + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_GET_UP, 1000.0f); + } + + ped->ReplaceWeaponWhenExitingVehicle(); + + ped->m_nStoredMoveState = PEDMOVE_NONE; + ped->bVehExitWillBeInstant = false; +} + +void +CPed::PedSetInCarCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + + // Pointless code + if (!veh) + return; + +#ifdef VC_PED_PORTS + // Situation of entering car as a driver while there is already a driver exiting atm. + CPed *driver = veh->pDriver; + if (driver && driver->m_nPedState == PED_DRIVING && !veh->bIsBus && driver->m_objective == OBJECTIVE_LEAVE_CAR + && (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || ped->m_nPedState == PED_CARJACK)) { + + if (!ped->IsPlayer() && (ped->CharCreatedBy != MISSION_CHAR || driver->IsPlayer())) { + ped->QuitEnteringCar(); + return; + } + if (driver->CharCreatedBy == MISSION_CHAR) { + PedSetOutCarCB(nil, veh->pDriver); + if (driver->m_pMyVehicle) { + driver->PositionPedOutOfCollision(); + } else { + driver->m_pMyVehicle = veh; + driver->PositionPedOutOfCollision(); + driver->m_pMyVehicle = nil; + } + veh->pDriver = nil; + } else { + driver->SetDead(); + driver->FlagToDestroyWhenNextProcessed(); + veh->pDriver = nil; + } + } +#endif + + if (!ped->IsNotInWreckedVehicle() || ped->DyingOrDead()) + return; + + ped->bInVehicle = true; + if (ped->m_nPedType == PEDTYPE_PROSTITUTE) { + if (veh->pDriver) { + if (veh->pDriver->IsPlayer() && ped->CharCreatedBy == RANDOM_CHAR) { + CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 1000; + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime = CTimer::GetTimeInMilliseconds() + 1000; + CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = (CCivilianPed*)ped; + } + } + } + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER +#if defined VC_PED_PORTS || defined FIX_BUGS + || ped->m_nPedState == PED_CARJACK +#endif + ) + veh->bIsBeingCarJacked = false; + + if (veh->m_nNumGettingIn) + --veh->m_nNumGettingIn; + + if (ped->IsPlayer() && ((CPlayerPed*)ped)->m_bAdrenalineActive) + ((CPlayerPed*)ped)->ClearAdrenaline(); + + if (veh->IsBoat()) { + if (ped->IsPlayer()) { +#if defined VC_PED_PORTS || defined FIX_BUGS + CCarCtrl::RegisterVehicleOfInterest(veh); +#endif + if (veh->GetStatus() == STATUS_SIMPLE) { + veh->m_vecMoveSpeed = CVector(0.0f, 0.0f, -0.00001f); + veh->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + } + veh->SetStatus(STATUS_PLAYER); + AudioManager.PlayerJustGotInCar(); + } + veh->SetDriver(ped); + if (!veh->bEngineOn) + veh->bEngineOn = true; + + ped->SetPedState(PED_DRIVING); + ped->StopNonPartialAnims(); + return; + } + + if (ped->m_pVehicleAnim) + ped->m_pVehicleAnim->blendDelta = -1000.0f; + + ped->bDoBloodyFootprints = false; + if (veh->m_nAlarmState == -1) + veh->m_nAlarmState = 15000; + + if (ped->IsPlayer()) { + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (veh->GetStatus() == STATUS_SIMPLE) { + veh->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + veh->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + } + veh->SetStatus(STATUS_PLAYER); + } + AudioManager.PlayerJustGotInCar(); + } else if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (veh->GetStatus() == STATUS_SIMPLE) { + veh->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + veh->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + } + veh->SetStatus(STATUS_PHYSICS); + } + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + for (int i = 0; i < veh->m_nNumMaxPassengers; ++i) { + CPed *passenger = veh->pPassengers[i]; + if (passenger && passenger->CharCreatedBy == RANDOM_CHAR) { + passenger->SetObjective(OBJECTIVE_LEAVE_CAR, veh); +#ifdef VC_PED_PORTS + passenger->m_leaveCarTimer = CTimer::GetTimeInMilliseconds(); +#endif + } + } + } + // This shouldn't happen at all. Passengers can't enter with PED_CARJACK. Even though they did, we shouldn't call AddPassenger in here and SetDriver in below. +#if !defined VC_PED_PORTS && !defined FIX_BUGS + else if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + if (ped->m_nPedState == PED_CARJACK) { + veh->AddPassenger(ped, 0); + ped->SetPedState(PED_DRIVING); + ped->RestorePreviousObjective(); + ped->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + } else if (veh->pDriver && ped->CharCreatedBy == RANDOM_CHAR) { + veh->AutoPilot.m_nCruiseSpeed = 17; + } + } +#endif + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || ped->m_nPedState == PED_CARJACK) { + veh->SetDriver(ped); + if (veh->VehicleCreatedBy == PARKED_VEHICLE) { + veh->VehicleCreatedBy = RANDOM_VEHICLE; + ++CCarCtrl::NumRandomCars; + --CCarCtrl::NumParkedCars; + } + if (veh->bIsAmbulanceOnDuty) { + veh->bIsAmbulanceOnDuty = false; + --CCarCtrl::NumAmbulancesOnDuty; + } + if (veh->bIsFireTruckOnDuty) { + veh->bIsFireTruckOnDuty = false; + --CCarCtrl::NumFiretrucksOnDuty; + } + if (ped->m_nPedType == PEDTYPE_COP && veh->IsLawEnforcementVehicle()) + veh->ChangeLawEnforcerState(true); + + if (!veh->bEngineOn) { + veh->bEngineOn = true; + DMAudio.PlayOneShot(ped->m_audioEntityId, SOUND_CAR_ENGINE_START, 1.0f); + } + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && ped->CharCreatedBy == RANDOM_CHAR + && ped != FindPlayerPed() && ped->m_nPedType != PEDTYPE_EMERGENCY) { + + CCarCtrl::JoinCarWithRoadSystem(veh); + veh->AutoPilot.m_nCarMission = MISSION_CRUISE; + veh->AutoPilot.m_nTempAction = TEMPACT_NONE; + veh->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + veh->AutoPilot.m_nCruiseSpeed = 25; + } + ped->SetPedState(PED_DRIVING); + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + + if (ped->m_prevObjective == OBJECTIVE_RUN_TO_AREA || ped->m_prevObjective == OBJECTIVE_GOTO_CHAR_ON_FOOT || ped->m_prevObjective == OBJECTIVE_KILL_CHAR_ON_FOOT) + ped->m_prevObjective = OBJECTIVE_NONE; + + ped->RestorePreviousObjective(); + } + + } else if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + if (veh->bIsBus) { + veh->AddPassenger(ped); + } else { + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: + veh->AddPassenger(ped, 0); + break; + case CAR_DOOR_RR: + veh->AddPassenger(ped, 2); + break; + case CAR_DOOR_LR: + veh->AddPassenger(ped, 1); + break; + default: + veh->AddPassenger(ped); + break; + } + } + ped->SetPedState(PED_DRIVING); + if (ped->m_prevObjective == OBJECTIVE_RUN_TO_AREA || ped->m_prevObjective == OBJECTIVE_GOTO_CHAR_ON_FOOT || ped->m_prevObjective == OBJECTIVE_KILL_CHAR_ON_FOOT) + ped->m_prevObjective = OBJECTIVE_NONE; + + ped->RestorePreviousObjective(); +#ifdef VC_PED_PORTS + if(veh->pDriver && ped->CharCreatedBy == RANDOM_CHAR) + veh->AutoPilot.m_nCruiseSpeed = 17; +#endif + } + + veh->m_nGettingInFlags &= ~GetCarDoorFlag(ped->m_vehDoor); + + if (veh->bIsBus && !veh->m_nGettingInFlags) + ((CAutomobile*)veh)->SetBusDoorTimer(1000, 1); + + switch (ped->m_objective) { + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_LEAVE_CAR: + case OBJECTIVE_FOLLOW_CAR_IN_CAR: + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + break; + default: + ped->SetObjective(OBJECTIVE_NONE); + } + + if (veh->pDriver == ped) { + if (veh->bLowVehicle) { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_LO, 100.0f); + } else { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT, 100.0f); + } + } else if (veh->bLowVehicle) { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_P_LO, 100.0f); + } else { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_P, 100.0f); + } + + ped->StopNonPartialAnims(); + if (veh->bIsBus) + ped->bRenderPedInCar = false; + + // FIX: RegisterVehicleOfInterest not just registers the vehicle, but also updates register time. So remove the IsThisVehicleInteresting check. +#ifndef FIX_BUGS + if (ped->IsPlayer() && !CCarCtrl::IsThisVehicleInteresting(veh) && veh->VehicleCreatedBy != MISSION_VEHICLE) { +#else + if (ped->IsPlayer() && veh->VehicleCreatedBy != MISSION_VEHICLE) { +#endif + CCarCtrl::RegisterVehicleOfInterest(veh); + + if (!veh->bHasBeenOwnedByPlayer && veh->VehicleCreatedBy != MISSION_VEHICLE) + CEventList::RegisterEvent(EVENT_STEAL_CAR, EVENT_ENTITY_VEHICLE, veh, ped, 1500); + + veh->bHasBeenOwnedByPlayer = true; + } + ped->bChangedSeat = true; +} + +bool +CPed::CanBeDeleted(void) +{ + if (bInVehicle) + return false; + + switch (CharCreatedBy) { + case RANDOM_CHAR: + return true; + case MISSION_CHAR: + return false; + default: + return true; + } +} + +void +CPed::AddWeaponModel(int id) +{ + RpAtomic *atm; + + if (id != -1) { +#ifdef PED_SKIN + if (IsClumpSkinned(GetClump())) { + if (m_pWeaponModel) + RemoveWeaponModel(-1); + + m_pWeaponModel = (RpAtomic*)CModelInfo::GetModelInfo(id)->CreateInstance(); + } else +#endif + { + atm = (RpAtomic*)CModelInfo::GetModelInfo(id)->CreateInstance(); + RwFrameDestroy(RpAtomicGetFrame(atm)); + RpAtomicSetFrame(atm, m_pFrames[PED_HANDR]->frame); + RpClumpAddAtomic(GetClump(), atm); + } + m_wepModelID = id; + } +} + +static RwObject* +RemoveAllModelCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + if (CVisibilityPlugins::GetAtomicModelInfo(atomic)) { + RpClumpRemoveAtomic(RpAtomicGetClump(atomic), atomic); + RpAtomicDestroy(atomic); + } + return object; +} + +void +CPed::RemoveWeaponModel(int modelId) +{ + // modelId is not used!! This function just removes the current weapon. +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + if(m_pWeaponModel){ + RwFrame *frm = RpAtomicGetFrame(m_pWeaponModel); + RpAtomicDestroy(m_pWeaponModel); + RwFrameDestroy(frm); + m_pWeaponModel = nil; + } + }else +#endif + RwFrameForAllObjects(m_pFrames[PED_HANDR]->frame,RemoveAllModelCB,nil); + m_wepModelID = -1; +} + +uint32 +CPed::GiveWeapon(eWeaponType weaponType, uint32 ammo) +{ + CWeapon &weapon = GetWeapon(weaponType); + + if (HasWeapon(weaponType)) { + if (weapon.m_nAmmoTotal + ammo > 99999) + weapon.m_nAmmoTotal = 99999; + else + weapon.m_nAmmoTotal += ammo; + + weapon.Reload(); + } else { + weapon.Initialise(weaponType, ammo); + // TODO: It seems game uses this as both weapon count and max WeaponType we have, which is ofcourse erroneous. + m_maxWeaponTypeAllowed++; + } + if (weapon.m_eWeaponState == WEAPONSTATE_OUT_OF_AMMO) + weapon.m_eWeaponState = WEAPONSTATE_READY; + + return weaponType; +} + +// Some kind of VC leftover I think +int +CPed::GetWeaponSlot(eWeaponType weaponType) +{ + if (HasWeapon(weaponType)) + return weaponType; + else + return -1; +} + +void +CPed::SetCurrentWeapon(uint32 weaponType) +{ + CWeaponInfo *weaponInfo; + if (HasWeapon(weaponType)) { + weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(weaponInfo->m_nModelId); + + m_currentWeapon = weaponType; + + weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + AddWeaponModel(weaponInfo->m_nModelId); + } +} + +void +CPed::GrantAmmo(eWeaponType weaponType, uint32 ammo) +{ + if (HasWeapon(weaponType)) { + GetWeapon(weaponType).m_nAmmoTotal += ammo; + } else { + GetWeapon(weaponType).Initialise(weaponType, ammo); + m_maxWeaponTypeAllowed++; + } +} + +void +CPed::SetAmmo(eWeaponType weaponType, uint32 ammo) +{ + if (HasWeapon(weaponType)) { + GetWeapon(weaponType).m_nAmmoTotal = ammo; + } else { + GetWeapon(weaponType).Initialise(weaponType, ammo); + m_maxWeaponTypeAllowed++; + } +} + +void +CPed::ClearWeapons(void) +{ + CWeaponInfo *currentWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(currentWeapon->m_nModelId); + + m_maxWeaponTypeAllowed = WEAPONTYPE_BASEBALLBAT; + m_currentWeapon = WEAPONTYPE_UNARMED; + + currentWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + AddWeaponModel(currentWeapon->m_nModelId); + for(int i = 0; i < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; i++) { + CWeapon &weapon = GetWeapon(i); + weapon.m_eWeaponType = WEAPONTYPE_UNARMED; + weapon.m_eWeaponState = WEAPONSTATE_READY; + weapon.m_nAmmoInClip = 0; + weapon.m_nAmmoTotal = 0; + weapon.m_nTimer = 0; + } +} + +void +CPed::PreRender(void) +{ + CShadows::StoreShadowForPed(this, + CTimeCycle::m_fShadowDisplacementX[CTimeCycle::m_CurrentStoredValue], CTimeCycle::m_fShadowDisplacementY[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowFrontX[CTimeCycle::m_CurrentStoredValue], CTimeCycle::m_fShadowFrontY[CTimeCycle::m_CurrentStoredValue], + CTimeCycle::m_fShadowSideX[CTimeCycle::m_CurrentStoredValue], CTimeCycle::m_fShadowSideY[CTimeCycle::m_CurrentStoredValue]); + +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + UpdateRpHAnim(); + + if(bBodyPartJustCameOff && m_bodyPartBleeding == PED_HEAD){ + // scale head to 0 if shot off + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_HEAD)); + RwMatrix *head = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwV3d zero = { 0.0f, 0.0f, 0.0f }; + RwMatrixScale(head, &zero, rwCOMBINEPRECONCAT); + } + } +#endif + + if (bBodyPartJustCameOff && bIsPedDieAnimPlaying && m_bodyPartBleeding != -1 && (CTimer::GetFrameCounter() & 7) > 3) { + CVector bloodDir(0.0f, 0.0f, 0.0f); + CVector bloodPos(0.0f, 0.0f, 0.0f); + + TransformToNode(bloodPos, m_bodyPartBleeding); + + switch (m_bodyPartBleeding) { + case PED_HEAD: + bloodDir = 0.1f * GetUp(); + break; + case PED_UPPERARML: + bloodDir = 0.04f * GetUp() - 0.04f * GetRight(); + break; + case PED_UPPERARMR: + bloodDir = 0.04f * GetUp() - 0.04f * GetRight(); + break; + case PED_UPPERLEGL: + bloodDir = 0.04f * GetUp() + 0.05f * GetForward(); + break; + case PED_UPPERLEGR: + bloodDir = 0.04f * GetUp() + 0.05f * GetForward(); + break; + default: + bloodDir = CVector(0.0f, 0.0f, 0.0f); + break; + } + + for(int i = 0; i < 4; i++) + CParticle::AddParticle(PARTICLE_BLOOD_SPURT, bloodPos, bloodDir, nil, 0.0f, 0, 0, 0, 0); + } + if (CWeather::Rain > 0.3f && TheCamera.SoundDistUp > 15.0f) { + if ((TheCamera.GetPosition() - GetPosition()).Magnitude() < 25.0f) { + bool doSplashUp = true; + CColModel *ourCol = CModelInfo::GetColModel(GetModelIndex()); + CVector speed = FindPlayerSpeed(); + + if (Abs(speed.x) <= 0.05f && Abs(speed.y) <= 0.05f) { + if (!OnGround() && m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT) { + if (!IsPedHeadAbovePos(0.3f) || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED)) { + doSplashUp = false; + } + } else + doSplashUp = false; + } else + doSplashUp = false; + + if (doSplashUp && ourCol->numSpheres > 0) { + for(int i = 0; i < ourCol->numSpheres; i++) { + CColSphere *sphere = &ourCol->spheres[i]; + CVector splashPos; + switch (sphere->piece) { + case PEDPIECE_LEFTARM: + case PEDPIECE_RIGHTARM: + case PEDPIECE_HEAD: + splashPos = GetMatrix() * ourCol->spheres[i].center; + splashPos.z += 0.7f * sphere->radius; + splashPos.x += CGeneral::GetRandomNumberInRange(-0.15f, 0.15f); + splashPos.y += CGeneral::GetRandomNumberInRange(-0.15f, 0.15f); + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, splashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, CGeneral::GetRandomNumber() & 1, 0); + break; + default: + break; + } + } + } + } + } +} + +void +CPed::Render(void) +{ + if (bInVehicle && m_nPedState != PED_EXIT_CAR && m_nPedState != PED_DRAG_FROM_CAR) { + if (!bRenderPedInCar) + return; + + float camDistSq = (TheCamera.GetPosition() - GetPosition()).MagnitudeSqr(); + if (camDistSq > SQR(25.0f * TheCamera.LODDistMultiplier)) + return; + } + + CEntity::Render(); + +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + renderLimb(PED_HEAD); + renderLimb(PED_HANDL); + renderLimb(PED_HANDR); + } + if(m_pWeaponModel && IsClumpSkinned(GetClump())){ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int idx = RpHAnimIDGetIndex(hier, m_pFrames[PED_HANDR]->nodeID); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwFrame *frame = RpAtomicGetFrame(m_pWeaponModel); + *RwFrameGetMatrix(frame) = *mat; + RwFrameUpdateObjects(frame); + RpAtomicRender(m_pWeaponModel); + } +#endif +} + +void +CPed::CheckAroundForPossibleCollisions(void) +{ + CVector ourCentre, objCentre; + CEntity *objects[8]; + int16 maxObject; + + if (CTimer::GetTimeInMilliseconds() <= m_nPedStateTimer) + return; + + GetBoundCentre(ourCentre); + + CWorld::FindObjectsInRange(ourCentre, 10.0f, true, &maxObject, 6, objects, false, true, false, true, false); + for (int i = 0; i < maxObject; i++) { + CEntity *object = objects[i]; + if (bRunningToPhone) { + if (gPhoneInfo.PhoneAtThisPosition(object->GetPosition())) + break; + } + object->GetBoundCentre(objCentre); + float radius = object->GetBoundRadius(); + if (radius > 4.5f || radius < 1.0f) + radius = 1.0f; + + // Developers gave up calculating Z diff. later according to asm. + float diff = CVector(ourCentre - objCentre).MagnitudeSqr2D(); + + if (sq(radius + 1.0f) > diff) + m_fRotationDest += DEGTORAD(22.5f); + } +} + +void +CPed::SetIdle(void) +{ + if (m_nPedState != PED_IDLE && m_nPedState != PED_MUG && m_nPedState != PED_FLEE_ENTITY) { +#ifdef VC_PED_PORTS + if (m_nPedState == PED_AIM_GUN) + ClearPointGunAt(); + + m_nLastPedState = PED_NONE; +#endif + SetPedState(PED_IDLE); + SetMoveState(PEDMOVE_STILL); + } + if (m_nWaitState == WAITSTATE_FALSE) { + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(2000, 4000); + } +} + +void +CPed::Idle(void) +{ + CVehicle *veh = m_pMyVehicle; + if (veh && veh->m_nGettingOutFlags && m_vehDoor) { + + if (veh->m_nGettingOutFlags & GetCarDoorFlag(m_vehDoor)) { + + if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + + CVector doorPos = GetPositionToOpenCarDoor(veh, m_vehDoor); + CVector doorDist = GetPosition() - doorPos; + + if (doorDist.MagnitudeSqr() < sq(0.5f)) { + SetMoveState(PEDMOVE_WALK); + return; + } + } + } + } + + CAnimBlendAssociation *armedIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_BIGGUN); + CAnimBlendAssociation *unarmedIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + int waitTime; + + if (m_nMoveState == PEDMOVE_STILL) { + + eWeaponType curWeapon = GetWeapon()->m_eWeaponType; + if (!armedIdleAssoc || + CTimer::GetTimeInMilliseconds() <= m_nWaitTimer && curWeapon != WEAPONTYPE_UNARMED && curWeapon != WEAPONTYPE_MOLOTOV && curWeapon != WEAPONTYPE_GRENADE) { + + if ((!GetWeapon()->IsType2Handed() || curWeapon == WEAPONTYPE_SHOTGUN) && curWeapon != WEAPONTYPE_BASEBALLBAT + || !unarmedIdleAssoc || unarmedIdleAssoc->blendAmount <= 0.95f || m_nWaitState != WAITSTATE_FALSE || CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) { + + m_moved = CVector2D(0.0f, 0.0f); + return; + } + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_BIGGUN, 3.0f); + waitTime = CGeneral::GetRandomNumberInRange(4000, 7500); + } else { + armedIdleAssoc->blendDelta = -2.0f; + armedIdleAssoc->flags |= ASSOC_DELETEFADEDOUT; + waitTime = CGeneral::GetRandomNumberInRange(3000, 8500); + } + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + waitTime; + } else { + if (armedIdleAssoc) { + armedIdleAssoc->blendDelta = -8.0f; + armedIdleAssoc->flags |= ASSOC_DELETEFADEDOUT; + m_nWaitTimer = 0; + } + if (!IsPlayer()) + SetMoveState(PEDMOVE_STILL); + } + m_moved = CVector2D(0.0f, 0.0f); +} + +void +CPed::ClearPause(void) +{ + RestorePreviousState(); +} + +void +CPed::Pause(void) +{ + m_moved = CVector2D(0.0f, 0.0f); + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer) + ClearPause(); +} + +void +CPed::SetFall(int extraTime, AnimationId animId, uint8 evenIfNotInControl) +{ + if (!IsPedInControl() && (!evenIfNotInControl || DyingOrDead())) + return; + + ClearLookFlag(); + ClearAimFlag(); + SetStoredState(); + SetPedState(PED_FALL); + CAnimBlendAssociation *fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), animId); + + if (fallAssoc) { + fallAssoc->SetCurrentTime(0.0f); + fallAssoc->blendAmount = 0.0f; + fallAssoc->blendDelta = 8.0f; + fallAssoc->SetRun(); + } else { + fallAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animId, 8.0f); + } + + if (extraTime == -1) { + m_getUpTimer = UINT32_MAX; + } else if (fallAssoc) { + if (IsPlayer()) { + m_getUpTimer = 1000.0f * fallAssoc->hierarchy->totalLength + + CTimer::GetTimeInMilliseconds() + + 500.0f; + } else { + m_getUpTimer = 1000.0f * fallAssoc->hierarchy->totalLength + + CTimer::GetTimeInMilliseconds() + + extraTime + + ((m_randomSeed + CTimer::GetFrameCounter()) % 1000); + } + } else { + m_getUpTimer = extraTime + + CTimer::GetTimeInMilliseconds() + + 1000 + + ((m_randomSeed + CTimer::GetFrameCounter()) % 1000); + } + bFallenDown = true; +} + +void +CPed::ClearFall(void) +{ + SetGetUp(); +} + +void +CPed::Fall(void) +{ + if (m_getUpTimer != UINT32_MAX && CTimer::GetTimeInMilliseconds() > m_getUpTimer +#ifdef VC_PED_PORTS + && bIsStanding +#endif + ) + ClearFall(); + + // VC plays animations ANIM_STD_FALL_ONBACK and ANIM_STD_FALL_ONFRONT in here, which doesn't exist in III. +} + +bool +CPed::CheckIfInTheAir(void) +{ + if (bInVehicle) + return false; + + CVector pos = GetPosition(); + CColPoint foundColPoint; + CEntity *foundEntity; + + float startZ = pos.z - 1.54f; + bool foundGround = CWorld::ProcessVerticalLine(pos, startZ, foundColPoint, foundEntity, true, true, false, true, false, false, nil); + if (!foundGround && m_nPedState != PED_JUMP) + { + pos.z -= FEET_OFFSET; + if (CWorld::TestSphereAgainstWorld(pos, 0.15f, this, true, false, false, false, false, false)) + foundGround = true; + } + return !foundGround; +} + +void +CPed::SetInTheAir(void) +{ + if (bIsInTheAir) + return; + + bIsInTheAir = true; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_GLIDE, 4.0f); + + if (m_nPedState == PED_ATTACK) { + ClearAttack(); + ClearPointGunAt(); + } else if (m_nPedState == PED_FIGHT) { + EndFight(ENDFIGHT_FAST); + } + +} + +void +CPed::InTheAir(void) +{ + CColPoint foundCol; + CEntity *foundEnt; + + CVector ourPos = GetPosition(); + CVector bitBelow = GetPosition(); + bitBelow.z -= 4.04f; + + if (m_vecMoveSpeed.z < 0.0f && !bIsPedDieAnimPlaying) { + if (!DyingOrDead()) { + if (CWorld::ProcessLineOfSight(ourPos, bitBelow, foundCol, foundEnt, true, true, false, true, false, false, false)) { + if (GetPosition().z - foundCol.point.z < 1.3f +#ifdef VC_PED_PORTS + || bIsStanding +#endif + ) + SetLanding(); + } else { + if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL)) { + if (m_vecMoveSpeed.z < -0.1f) + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL, 4.0f); + } + } + } + } +} + +void +CPed::SetLanding(void) +{ + if (DyingOrDead()) + return; + + CAnimBlendAssociation *fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL); + CAnimBlendAssociation *landAssoc; + + RpAnimBlendClumpSetBlendDeltas(GetClump(), ASSOC_PARTIAL, -1000.0f); + if (fallAssoc) { + landAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_COLLAPSE); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FALL_COLLAPSE, 1.0f); + + if (IsPlayer()) + Say(SOUND_PED_LAND); + + } else { + landAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_LAND); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FALL_LAND, 1.0f); + } + + landAssoc->SetFinishCallback(PedLandCB, this); + bIsInTheAir = false; + bIsLanding = true; +} + +void +CPed::SetGetUp(void) +{ + if (m_nPedState == PED_GETUP && bGetUpAnimStarted) + return; + + if (!CanSetPedState()) + return; + + if (m_fHealth >= 1.0f || IsPedHeadAbovePos(-0.3f)) { + if (bUpdateAnimHeading) { + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + m_fRotationCur -= HALFPI; + bUpdateAnimHeading = false; + } + if (m_nPedState != PED_GETUP) { + SetStoredState(); + SetPedState(PED_GETUP); + } + + CVehicle *collidingVeh = (CVehicle*)m_pCollidingEntity; + CVehicle *veh = (CVehicle*)CPedPlacement::IsPositionClearOfCars(&GetPosition()); + if (veh && veh->m_vehType != VEHICLE_TYPE_BIKE || + collidingVeh && collidingVeh->IsVehicle() && collidingVeh->m_vehType != VEHICLE_TYPE_BIKE + && ((uint8)(CTimer::GetFrameCounter() + m_randomSeed + 5) % 8 || + CCollision::ProcessColModels(GetMatrix(), *GetColModel(), collidingVeh->GetMatrix(), *collidingVeh->GetColModel(), + aTempPedColPts, nil, nil) > 0)) { + + bGetUpAnimStarted = false; + if (IsPlayer()) + InflictDamage(nil, WEAPONTYPE_RUNOVERBYCAR, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + else { + if (!CPad::GetPad(0)->ArePlayerControlsDisabled()) + return; + + InflictDamage(nil, WEAPONTYPE_RUNOVERBYCAR, 1000.0f, PEDPIECE_TORSO, 0); + } + return; + } + bGetUpAnimStarted = true; + m_pCollidingEntity = nil; + bKnockedUpIntoAir = false; + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNFAST); + if (animAssoc) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUN)) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_RUN, 8.0f); + } else { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 8.0f); + } + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GET_UP_FRONT, 1000.0f); + else + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GET_UP, 1000.0f); + + animAssoc->SetFinishCallback(PedGetupCB,this); + } else { + m_fHealth = 0.0f; + SetDie(ANIM_STD_NUM, 4.0f, 0.0f); + } +} + +void +CPed::Mug(void) +{ + if (m_pSeekTarget && m_pSeekTarget->IsPed()) { + + if (CTimer::GetTimeInMilliseconds() <= m_attackTimer - 2000) { + if ((m_pSeekTarget->GetPosition() - GetPosition()).Magnitude() > 3.0f) + m_wepSkills = 50; + + Say(SOUND_PED_MUGGING); + ((CPed*)m_pSeekTarget)->Say(SOUND_PED_ROBBED); + } else { + SetWanderPath(CGeneral::GetRandomNumber() & 7); + SetFlee(m_pSeekTarget, 20000); + } + + } else { + SetIdle(); + } +} + +void +CPed::SetLookTimer(int time) +{ + if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + m_lookTimer = CTimer::GetTimeInMilliseconds() + time; + } +} + +void +CPed::SetAttackTimer(uint32 time) +{ + if (CTimer::GetTimeInMilliseconds() > m_attackTimer) + m_attackTimer = Max(m_shootTimer, CTimer::GetTimeInMilliseconds()) + time; +} + +void +CPed::SetShootTimer(uint32 time) +{ + if (CTimer::GetTimeInMilliseconds() > m_shootTimer) { + m_shootTimer = CTimer::GetTimeInMilliseconds() + time; + } +} + +void +CPed::ClearLook(void) +{ + RestorePreviousState(); + ClearLookFlag(); +} + +void +CPed::Look(void) +{ + // UNUSED: This is a perfectly empty function. +} + +bool +CPed::TurnBody(void) +{ + bool turnDone = true; + + if (m_pLookTarget) + m_fLookDirection = CGeneral::GetRadianAngleBetweenPoints( + m_pLookTarget->GetPosition().x, + m_pLookTarget->GetPosition().y, + GetPosition().x, + GetPosition().y); + + float limitedLookDir = CGeneral::LimitRadianAngle(m_fLookDirection); + float currentRot = m_fRotationCur; + + if (currentRot - PI > limitedLookDir) + limitedLookDir += 2 * PI; + else if (PI + currentRot < limitedLookDir) + limitedLookDir -= 2 * PI; + + float neededTurn = currentRot - limitedLookDir; + m_fRotationDest = limitedLookDir; + + if (Abs(neededTurn) > 0.05f) { + turnDone = false; + currentRot -= neededTurn * 0.2f; + } + + m_fRotationCur = currentRot; + m_fLookDirection = limitedLookDir; + return turnDone; +} + +void +CPed::SetSeek(CVector pos, float distanceToCountDone) +{ + if (!IsPedInControl() + || (m_nPedState == PED_SEEK_POS && m_vecSeekPos.x == pos.x && m_vecSeekPos.y == pos.y)) + return; + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_M16 + || GetWeapon()->m_eWeaponType == WEAPONTYPE_AK47 + || GetWeapon()->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE + || GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER + || GetWeapon()->m_eWeaponType == WEAPONTYPE_SHOTGUN) { + ClearPointGunAt(); + } + + if (m_nPedState != PED_SEEK_POS) + SetStoredState(); + + SetPedState(PED_SEEK_POS); + m_distanceToCountSeekDone = distanceToCountDone; + m_vecSeekPos = pos; +} + +void +CPed::SetSeek(CEntity *seeking, float distanceToCountDone) +{ + if (!IsPedInControl()) + return; + + if (m_nPedState == PED_SEEK_ENTITY && m_pSeekTarget == seeking) + return; + + if (!seeking) + return; + + if (m_nPedState != PED_SEEK_ENTITY) + SetStoredState(); + + SetPedState(PED_SEEK_ENTITY); + m_distanceToCountSeekDone = distanceToCountDone; + m_pSeekTarget = seeking; + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + SetMoveState(PEDMOVE_STILL); +} + +void +CPed::ClearSeek(void) +{ + SetIdle(); + bRunningToPhone = false; +} + +bool +CPed::Seek(void) +{ + float distanceToCountItDone = m_distanceToCountSeekDone; + eMoveState nextMove = PEDMOVE_NONE; + + if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + + if (m_nPedState != PED_EXIT_TRAIN && m_nPedState != PED_ENTER_TRAIN && m_nPedState != PED_SEEK_IN_BOAT && + m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && m_objective != OBJECTIVE_SOLICIT_VEHICLE && !bDuckAndCover) { + + if ((!m_pedInObjective || !m_pedInObjective->bInVehicle) + && !((CTimer::GetFrameCounter() + (m_randomSeed % 256) + 17) & 7)) { + + CEntity *obstacle = CWorld::TestSphereAgainstWorld(m_vecSeekPos, 0.4f, nil, + false, true, false, false, false, false); + + if (obstacle) { + if (!obstacle->IsVehicle() || ((CVehicle*)obstacle)->m_vehType == VEHICLE_TYPE_CAR) { + distanceToCountItDone = 2.5f; + } else { + CVehicleModelInfo *vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(obstacle->GetModelIndex()); + float yLength = vehModel->GetColModel()->boundingBox.max.y + - vehModel->GetColModel()->boundingBox.min.y; + distanceToCountItDone = yLength * 0.55f; + } + } + } + } + } + + if (!m_pSeekTarget && m_nPedState == PED_SEEK_ENTITY) + ClearSeek(); + + float seekPosDist = (m_vecSeekPos - GetPosition()).Magnitude2D(); + if (seekPosDist < 2.0f || m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT) { + + if (m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION) { + + if (m_pedInObjective->m_nMoveState != PEDMOVE_STILL) + nextMove = m_pedInObjective->m_nMoveState; + } else + nextMove = PEDMOVE_WALK; + + } else if (m_objective != OBJECTIVE_FOLLOW_CHAR_IN_FORMATION) { + + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS || m_objective == OBJECTIVE_RUN_TO_AREA || bIsRunning) + nextMove = PEDMOVE_RUN; + else + nextMove = PEDMOVE_WALK; + + } else if (seekPosDist <= 2.0f) { + + if (m_pedInObjective->m_nMoveState != PEDMOVE_STILL) + nextMove = m_pedInObjective->m_nMoveState; + + } else { + nextMove = PEDMOVE_RUN; + } + + if (m_nPedState == PED_SEEK_ENTITY) { + if (m_pSeekTarget->IsPed()) { + if (((CPed*)m_pSeekTarget)->bInVehicle) + distanceToCountItDone += 2.0f; + } + } + + if (seekPosDist >= distanceToCountItDone) { + if (bIsRunning) + nextMove = PEDMOVE_RUN; + + if (CTimer::GetTimeInMilliseconds() <= m_nPedStateTimer) { + + if (m_actionX != 0.0f && m_actionY != 0.0f) { + + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_actionX, m_actionY, + GetPosition().x, GetPosition().y); + + float neededTurn = Abs(m_fRotationDest - m_fRotationCur); + + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + if (neededTurn > HALFPI) { + if (seekPosDist >= 1.0f) { + if (seekPosDist < 2.0f) { + if (bIsRunning) + nextMove = PEDMOVE_RUN; + else + nextMove = PEDMOVE_WALK; + } + } else { + nextMove = PEDMOVE_STILL; + } + } + + CVector2D moveDist(GetPosition().x - m_actionX, GetPosition().y - m_actionY); + if (moveDist.Magnitude() < 0.5f) { + m_nPedStateTimer = 0; + m_actionX = 0; + m_actionY = 0; + } + } + } else { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_vecSeekPos.x, m_vecSeekPos.y, + GetPosition().x, GetPosition().y); + + float neededTurn = Abs(m_fRotationDest - m_fRotationCur); + + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + if (neededTurn > HALFPI) { + if (seekPosDist >= 1.0 && neededTurn <= DEGTORAD(135.0f)) { + if (seekPosDist < 2.0f) + nextMove = PEDMOVE_WALK; + } else { + nextMove = PEDMOVE_STILL; + } + } + } + + if (((m_nPedState == PED_FLEE_POS || m_nPedState == PED_FLEE_ENTITY) && m_nMoveState < nextMove) + || (m_nPedState != PED_FLEE_POS && m_nPedState != PED_FLEE_ENTITY && m_objective != OBJECTIVE_GOTO_CHAR_ON_FOOT && m_nWaitState == WAITSTATE_FALSE)) { + + SetMoveState(nextMove); + } + + SetMoveAnim(); + return false; + } + + if ((m_objective != OBJECTIVE_FOLLOW_CHAR_IN_FORMATION || m_pedInObjective->m_nMoveState == PEDMOVE_STILL) && m_nMoveState != PEDMOVE_STILL) { + m_nPedStateTimer = 0; + m_actionX = 0; + m_actionY = 0; + } + + if (m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA || m_objective == OBJECTIVE_GOTO_AREA_ANY_MEANS) { + if (m_pNextPathNode) + m_pNextPathNode = nil; + else + bScriptObjectiveCompleted = true; + + bUsePedNodeSeek = true; + } + + if (SeekFollowingPath(nil)) + m_nCurPathNode++; + + return true; +} + +void +CPed::SetFlee(CVector2D const &from, int time) +{ + if (CTimer::GetTimeInMilliseconds() < m_nPedStateTimer || !IsPedInControl() || bKindaStayInSamePlace) + return; + + if (m_nPedState != PED_FLEE_ENTITY) { + SetStoredState(); + SetPedState(PED_FLEE_POS); + SetMoveState(PEDMOVE_RUN); + m_fleeFromPosX = from.x; + m_fleeFromPosY = from.y; + } + + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + m_fleeTimer = CTimer::GetTimeInMilliseconds() + time; + + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, GetPosition().y, + from.x, from.y); + + m_fRotationDest = CGeneral::LimitRadianAngle(angleToFace); + if (m_fRotationCur - PI > m_fRotationDest) { + m_fRotationDest += 2 * PI; + } else if (PI + m_fRotationCur < m_fRotationDest) { + m_fRotationDest -= 2 * PI; + } +} + +void +CPed::SetFlee(CEntity *fleeFrom, int time) +{ + if (!IsPedInControl() || bKindaStayInSamePlace || !fleeFrom) + return; + + SetStoredState(); + SetPedState(PED_FLEE_ENTITY); + bUsePedNodeSeek = true; + SetMoveState(PEDMOVE_RUN); + m_fleeFrom = fleeFrom; + m_fleeFrom->RegisterReference((CEntity **) &m_fleeFrom); + + if (time <= 0) + m_fleeTimer = 0; + else + m_fleeTimer = CTimer::GetTimeInMilliseconds() + time; + + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, GetPosition().y, + fleeFrom->GetPosition().x, fleeFrom->GetPosition().y); + + m_fRotationDest = CGeneral::LimitRadianAngle(angleToFace); + if (m_fRotationCur - PI > m_fRotationDest) { + m_fRotationDest += 2 * PI; + } else if (PI + m_fRotationCur < m_fRotationDest) { + m_fRotationDest -= 2 * PI; + } +} + +void +CPed::ClearFlee(void) +{ + RestorePreviousState(); + bUsePedNodeSeek = false; + m_chatTimer = 0; + m_fleeTimer = 0; +} + +void +CPed::Flee(void) +{ + if (CTimer::GetTimeInMilliseconds() > m_fleeTimer && m_fleeTimer) { + bool mayFinishFleeing = true; + if (m_nPedState == PED_FLEE_ENTITY) { + if ((CVector2D(GetPosition()) - ms_vec2DFleePosition).MagnitudeSqr() < sq(30.0f)) + mayFinishFleeing = false; + } + + if (mayFinishFleeing) { + eMoveState moveState = m_nMoveState; + ClearFlee(); + + if (m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE || m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS) + RestorePreviousObjective(); + + if ((m_nPedState == PED_IDLE || m_nPedState == PED_WANDER_PATH) && CGeneral::GetRandomNumber() & 1) { + SetWaitState(moveState <= PEDMOVE_WALK ? WAITSTATE_CROSS_ROAD_LOOK : WAITSTATE_FINISH_FLEE, nil); + } + return; + } + m_fleeTimer = CTimer::GetTimeInMilliseconds() + 5000; + } + + if (bUsePedNodeSeek) { + CPathNode *realLastNode = nil; + uint8 nextDirection = 0; + uint8 curDirectionShouldBe = 9; // means not defined yet + + if (m_nPedStateTimer < CTimer::GetTimeInMilliseconds() + && m_collidingThingTimer < CTimer::GetTimeInMilliseconds()) { + + if (m_pNextPathNode && CTimer::GetTimeInMilliseconds() > m_chatTimer) { + + curDirectionShouldBe = CGeneral::GetNodeHeadingFromVector(GetPosition().x - ms_vec2DFleePosition.x, GetPosition().y - ms_vec2DFleePosition.y); + if (m_nPathDir < curDirectionShouldBe) + m_nPathDir += 8; + + int dirDiff = m_nPathDir - curDirectionShouldBe; + if (dirDiff > 2 && dirDiff < 6) { + realLastNode = nil; + m_pLastPathNode = m_pNextPathNode; + m_pNextPathNode = nil; + } + } + + if (m_pNextPathNode) { + m_vecSeekPos = m_pNextPathNode->GetPosition(); + if (m_nMoveState == PEDMOVE_RUN) + bIsRunning = true; + + eMoveState moveState = m_nMoveState; + if (Seek()) { + realLastNode = m_pLastPathNode; + m_pLastPathNode = m_pNextPathNode; + m_pNextPathNode = nil; + } + bIsRunning = false; + SetMoveState(moveState); + } + } + + if (!m_pNextPathNode) { + if (curDirectionShouldBe == 9) { + curDirectionShouldBe = CGeneral::GetNodeHeadingFromVector(GetPosition().x - ms_vec2DFleePosition.x, GetPosition().y - ms_vec2DFleePosition.y); + } + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + curDirectionShouldBe, + &nextDirection); + + if (curDirectionShouldBe < nextDirection) + curDirectionShouldBe += 8; + + if (m_pNextPathNode && m_pNextPathNode != realLastNode && m_pNextPathNode != m_pLastPathNode && curDirectionShouldBe - nextDirection != 4) { + m_nPathDir = nextDirection; + m_chatTimer = CTimer::GetTimeInMilliseconds() + 2000; + } else { + bUsePedNodeSeek = false; + SetMoveState(PEDMOVE_RUN); + Flee(); + } + } + return; + } + + if ((m_nPedState == PED_FLEE_ENTITY || m_nPedState == PED_ON_FIRE) && m_nPedStateTimer < CTimer::GetTimeInMilliseconds()) { + + float angleToFleeFromPos = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, + GetPosition().y, + ms_vec2DFleePosition.x, + ms_vec2DFleePosition.y); + + m_fRotationDest = CGeneral::LimitRadianAngle(angleToFleeFromPos); + + if (m_fRotationCur - PI > m_fRotationDest) + m_fRotationDest += TWOPI; + else if (PI + m_fRotationCur < m_fRotationDest) + m_fRotationDest -= TWOPI; + } + + if (CTimer::GetTimeInMilliseconds() & 0x20) { + //CVector forwardPos = GetPosition(); + CMatrix forwardMat(GetMatrix()); + forwardMat.GetPosition() += Multiply3x3(forwardMat, CVector(0.0f, 4.0f, 0.0f)); + CVector forwardPos = forwardMat.GetPosition(); + + CEntity *foundEnt; + CColPoint foundCol; + bool found = CWorld::ProcessVerticalLine(forwardPos, forwardMat.GetPosition().z - 100.0f, foundCol, foundEnt, 1, 0, 0, 0, 1, 0, 0); + + if (!found || Abs(forwardPos.z - forwardMat.GetPosition().z) > 1.0f) { + m_fRotationDest += DEGTORAD(112.5f); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 2000; + } + } + + if (CTimer::GetTimeInMilliseconds() >= m_collidingThingTimer) + return; + + if (!m_collidingEntityWhileFleeing) + return; + + double collidingThingPriorityMult = (double)(m_collidingThingTimer - CTimer::GetTimeInMilliseconds()) * 2.0 / 2500; + + if (collidingThingPriorityMult <= 1.5) { + + double angleToFleeEntity = CGeneral::GetRadianAngleBetweenPoints( + GetPosition().x, + GetPosition().y, + m_collidingEntityWhileFleeing->GetPosition().x, + m_collidingEntityWhileFleeing->GetPosition().y); + angleToFleeEntity = CGeneral::LimitRadianAngle(angleToFleeEntity); + + double angleToFleeCollidingThing = CGeneral::GetRadianAngleBetweenPoints( + m_vecDamageNormal.x, + m_vecDamageNormal.y, + 0.0f, + 0.0f); + angleToFleeCollidingThing = CGeneral::LimitRadianAngle(angleToFleeCollidingThing); + + if (angleToFleeEntity - PI > angleToFleeCollidingThing) + angleToFleeCollidingThing += TWOPI; + else if (PI + angleToFleeEntity < angleToFleeCollidingThing) + angleToFleeCollidingThing -= TWOPI; + + if (collidingThingPriorityMult <= 1.0f) { + // Range [0.0, 1.0] + + float angleToFleeBoth = (angleToFleeCollidingThing + angleToFleeEntity) * 0.5f; + + if (m_fRotationDest - PI > angleToFleeBoth) + angleToFleeBoth += TWOPI; + else if (PI + m_fRotationDest < angleToFleeBoth) + angleToFleeBoth -= TWOPI; + + m_fRotationDest = (1.0f - collidingThingPriorityMult) * m_fRotationDest + collidingThingPriorityMult * angleToFleeBoth; + } else { + // Range (1.0, 1.5] + + double adjustedMult = (collidingThingPriorityMult - 1.0f) * 2.0f; + m_fRotationDest = angleToFleeEntity * (1.0 - adjustedMult) + adjustedMult * angleToFleeCollidingThing; + } + } else { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_vecDamageNormal.x, + m_vecDamageNormal.y, + 0.0f, + 0.0f); + m_fRotationDest = CGeneral::LimitRadianAngle(m_fRotationDest); + } + + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + + if (m_fRotationCur - PI > m_fRotationDest) + m_fRotationDest += TWOPI; + else if (PI + m_fRotationCur < m_fRotationDest) + m_fRotationDest -= TWOPI; + +} + +// "Wander range" state is unused in game, and you can't use it without SetWanderRange anyway +void +CPed::WanderRange(void) +{ + bool arrived = Seek(); + if (arrived) { + Idle(); + if ((m_randomSeed + 3 * CTimer::GetFrameCounter()) % 1000 > 997) { + CVector2D newCoords2D = m_wanderRangeBounds->GetRandomPointInRange(); + SetSeek(CVector(newCoords2D.x, newCoords2D.y, GetPosition().z), 2.5f); + } + } +} + +bool +CPed::SetWanderPath(int8 pathStateDest) +{ + uint8 nextPathState; + + if (IsPedInControl()) { + if (bKindaStayInSamePlace) { + SetIdle(); + return false; + } else { + m_nPathDir = pathStateDest; + if (pathStateDest == 0) + pathStateDest = CGeneral::GetRandomNumberInRange(1, 7); + + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + m_nPathDir, &nextPathState); + + // Circular loop until we find a node for current m_nPathDir + while (!m_pNextPathNode) { + m_nPathDir = (m_nPathDir+1) % 8; + + // We're at where we started and couldn't find any node + if (m_nPathDir == pathStateDest) { + ClearAll(); + SetIdle(); + return false; + } + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + m_nPathDir, &nextPathState); + } + + // We did it, save next path state and return true + m_nPathDir = nextPathState; + SetPedState(PED_WANDER_PATH); + SetMoveState(PEDMOVE_WALK); + bIsRunning = false; + return true; + } + } else { + m_nPathDir = pathStateDest; + bStartWanderPathOnFoot = true; + return false; + } +} + +void +CPed::WanderPath(void) +{ + if (!m_pNextPathNode) { + printf("THIS SHOULDN@T HAPPEN TOO OFTEN\n"); + SetIdle(); + return; + } + if (m_nWaitState == WAITSTATE_FALSE) { + if (m_nMoveState == PEDMOVE_STILL || m_nMoveState == PEDMOVE_NONE) + SetMoveState(PEDMOVE_WALK); + } + m_vecSeekPos = m_pNextPathNode->GetPosition(); + m_vecSeekPos.z += 1.0f; + + // Only returns true when ped is stuck(not stopped) I think, then we should assign new direction or wait state to him. + if (!Seek()) + return; + + CPathNode *previousLastNode = m_pLastPathNode; + uint8 randVal = (m_randomSeed + 3 * CTimer::GetFrameCounter()) % 100; + + // We don't prefer 180-degree turns in normal situations + uint8 dirWeWouldntPrefer = m_nPathDir; + if (dirWeWouldntPrefer <= 3) + dirWeWouldntPrefer += 4; + else + dirWeWouldntPrefer -= 4; + + CPathNode *nodeWeWouldntPrefer = nil; + uint8 dirToSet = 9; // means undefined + uint8 dirWeWouldntPrefer2 = 9; // means undefined + if (randVal <= 90) { + if (randVal > 80) { + m_nPathDir += 2; + m_nPathDir %= 8; + } + } else { + m_nPathDir -= 2; + if (m_nPathDir < 0) + m_nPathDir += 8; + } + + m_pLastPathNode = m_pNextPathNode; + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + m_nPathDir, &dirToSet); + + uint8 tryCount = 0; + + // NB: SetWanderPath checks for m_nPathDir == dirToStartWith, this one checks for tryCount > 7 + while (!m_pNextPathNode) { + tryCount++; + m_nPathDir = (m_nPathDir + 1) % 8; + + // We're at where we started and couldn't find any node + if (tryCount > 7) { + if (!nodeWeWouldntPrefer) { + ClearAll(); + SetIdle(); + // Probably this text carried over here after copy-pasting this loop from early version of SetWanderPath. + Error("Can't find valid path node, SetWanderPath, Ped.cpp"); + return; + } + m_pNextPathNode = nodeWeWouldntPrefer; + dirToSet = dirWeWouldntPrefer2; + } else { + ThePaths.FindNextNodeWandering(PATH_PED, GetPosition(), &m_pLastPathNode, &m_pNextPathNode, + m_nPathDir, &dirToSet); + if (m_pNextPathNode) { + if (dirToSet == dirWeWouldntPrefer) { + nodeWeWouldntPrefer = m_pNextPathNode; + dirWeWouldntPrefer2 = dirToSet; + m_pNextPathNode = nil; + } + } + } + } + + m_nPathDir = dirToSet; + if (m_pLastPathNode == m_pNextPathNode) { + m_pNextPathNode = previousLastNode; + SetWaitState(WAITSTATE_DOUBLEBACK, nil); + Say(SOUND_PED_WAIT_DOUBLEBACK); + } else if (ThePaths.TestForPedTrafficLight(m_pLastPathNode, m_pNextPathNode)) { + SetWaitState(WAITSTATE_TRAFFIC_LIGHTS, nil); + } else if (ThePaths.TestCrossesRoad(m_pLastPathNode, m_pNextPathNode)) { + SetWaitState(WAITSTATE_CROSS_ROAD, nil); + } else if (m_pNextPathNode == previousLastNode) { + SetWaitState(WAITSTATE_DOUBLEBACK, nil); + Say(SOUND_PED_WAIT_DOUBLEBACK); + } +} + +void +CPed::Avoid(void) +{ + CPed *nearestPed; + + if(m_pedStats->m_temper > m_pedStats->m_fear && m_pedStats->m_temper > 50) + return; + + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { + + if (m_nMoveState != PEDMOVE_NONE && m_nMoveState != PEDMOVE_STILL) { + nearestPed = m_nearPeds[0]; + + if (nearestPed && nearestPed->m_nPedState != PED_DEAD && nearestPed != m_pSeekTarget && nearestPed != m_pedInObjective) { + + // Check if this ped wants to avoid the nearest one + if (CPedType::GetAvoid(m_nPedType) & CPedType::GetFlag(nearestPed->m_nPedType)) { + + // Further codes checks whether the distance between us and ped will be equal or below 1.0, if we walk up to him by 1.25 meters. + // If so, we want to avoid it, so we turn our body 45 degree and look to somewhere else. + + // Game converts from radians to degress and back again here, doesn't make much sense + CVector2D forward(-Sin(m_fRotationCur), Cos(m_fRotationCur)); + forward.Normalise(); // this is kinda pointless + + // Move forward 1.25 meters + CVector2D testPosition = CVector2D(GetPosition()) + forward*1.25f; + + // Get distance to ped we want to avoid + CVector2D distToPed = CVector2D(nearestPed->GetPosition()) - testPosition; + + if (distToPed.Magnitude() <= 1.0f && OurPedCanSeeThisOne((CEntity*)nearestPed)) { + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + + 500 + (m_randomSeed + 3 * CTimer::GetFrameCounter()) + % 1000 / 5; + + m_fRotationDest += DEGTORAD(45.0f); + if (!bIsLooking) { + SetLookFlag(nearestPed, false); + SetLookTimer(CGeneral::GetRandomNumberInRange(500, 800)); + } + } + } + } + } + } +} + +bool +CPed::SeekFollowingPath(CVector *unused) +{ + return m_nCurPathNode <= m_nPathNodes && m_nPathNodes; +} + +bool +CPed::SetFollowPath(CVector dest) +{ + if (m_nPedState == PED_FOLLOW_PATH) + return false; + + if (FindPlayerPed() != this) + return false; + + if ((dest - GetPosition()).Magnitude() <= 2.0f) + return false; + + CVector pointPoses[7]; + int16 pointsFound; + CPedPath::CalcPedRoute(0, GetPosition(), dest, pointPoses, &pointsFound, 7); + for(int i = 0; i < pointsFound; i++) { + m_stPathNodeStates[i].x = pointPoses[i].x; + m_stPathNodeStates[i].y = pointPoses[i].y; + } + + m_nCurPathNode = 0; + m_nPathNodes = pointsFound; + if (m_nPathNodes < 1) + return false; + + SetStoredState(); + SetPedState(PED_FOLLOW_PATH); + SetMoveState(PEDMOVE_WALK); + return true; +} + +void +CPed::FollowPath(void) +{ + m_vecSeekPos.x = m_stPathNodeStates[m_nCurPathNode].x; + m_vecSeekPos.y = m_stPathNodeStates[m_nCurPathNode].y; + m_vecSeekPos.z = GetPosition().z; + + // Mysterious code +/* int v4 = 0; + int maxNodeIndex = m_nPathNodes - 1; + if (maxNodeIndex > 0) { + if (maxNodeIndex > 8) { + while (v4 < maxNodeIndex - 8) + v4 += 8; + } + + while (v4 < maxNodeIndex) + v4++; + + } +*/ + if (Seek()) { + m_nCurPathNode++; + if (m_nCurPathNode == m_nPathNodes) + RestorePreviousState(); + } +} + +void +CPed::SetEvasiveStep(CPhysical *reason, uint8 animType) +{ + AnimationId stepAnim; + + if (m_nPedState == PED_STEP_AWAY || !IsPedInControl() || ((IsPlayer() || !bRespondsToThreats) && animType == 0)) + return; + + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + reason->GetPosition().x, reason->GetPosition().y, + GetPosition().x, GetPosition().y); + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + float neededTurn = Abs(angleToFace - m_fRotationCur); + bool vehPressedHorn = false; + + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + if (reason->IsVehicle() && ((CVehicle*)reason)->IsCar()) { + CVehicle *veh = (CVehicle*)reason; + if (veh->m_nCarHornTimer != 0) { + vehPressedHorn = true; + if (!IsPlayer()) + animType = 1; + } + } + + if (neededTurn <= DEGTORAD(90.0f) || reason->GetModelIndex() == MI_RCBANDIT || vehPressedHorn || animType != 0) { + SetLookFlag(reason, true); + if ((CGeneral::GetRandomNumber() & 1) && reason->GetModelIndex() != MI_RCBANDIT && animType == 0) { + stepAnim = ANIM_STD_HAILTAXI; + } else { + + float dangerDirection = CGeneral::GetRadianAngleBetweenPoints( + reason->m_vecMoveSpeed.x, reason->m_vecMoveSpeed.y, + 0.0f, 0.0f); + + // Let's turn our back to the "reason" + angleToFace += PI; + + if (angleToFace > PI) + angleToFace -= TWOPI; + + // We don't want to run towards car's direction + float dangerZone = angleToFace - dangerDirection; + dangerZone = CGeneral::LimitRadianAngle(dangerZone); + + // So, add or subtract 90deg (jump to left/right) according to that + if (dangerZone > 0.0f) + angleToFace = dangerDirection - HALFPI; + else + angleToFace = dangerDirection + HALFPI; + + stepAnim = ANIM_STD_NUM; + if (animType == 0 || animType == 1) + stepAnim = ANIM_STD_EVADE_STEP; + else if (animType == 2) + stepAnim = ANIM_STD_HANDSCOWER; + } + if (!RpAnimBlendClumpGetAssociation(GetClump(), stepAnim)) { + CAnimBlendAssociation *stepAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, stepAnim, 8.0f); + stepAssoc->flags &= ~ASSOC_DELETEFADEDOUT; + stepAssoc->SetFinishCallback(PedEvadeCB, this); + + if (animType == 0) + Say(SOUND_PED_EVADE); + + m_fRotationCur = CGeneral::LimitRadianAngle(angleToFace); + ClearAimFlag(); + SetStoredState(); + SetPedState(PED_STEP_AWAY); + } + } +} + +void +CPed::SetEvasiveDive(CPhysical *reason, uint8 onlyRandomJump) +{ + if (!IsPedInControl() || !bRespondsToThreats) + return; + + CAnimBlendAssociation *animAssoc; + float angleToFace, neededTurn; + bool handsUp = false; + + angleToFace = m_fRotationCur; + CVehicle *veh = (CVehicle*) reason; + if (reason->IsVehicle() && veh->m_vehType == VEHICLE_TYPE_CAR && veh->m_nCarHornTimer != 0 && !IsPlayer()) { + onlyRandomJump = true; + } + + if (onlyRandomJump) { + if (reason) { + // Simple version of my bug fix below. Doesn't calculate "danger zone", selects jump direction randomly. + // Also doesn't include random hands up, sound etc. Only used on player ped and peds running from gun shots. + + float vehDirection = CGeneral::GetRadianAngleBetweenPoints( + veh->m_vecMoveSpeed.x, veh->m_vecMoveSpeed.y, + 0.0f, 0.0f); + angleToFace = (CGeneral::GetRandomNumber() & 1) * PI + (-0.5f*PI) + vehDirection; + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + } + } else { + if (IsPlayer()) { + ((CPlayerPed*)this)->m_nEvadeAmount = 5; + ((CPlayerPed*)this)->m_pEvadingFrom = reason; + reason->RegisterReference((CEntity**) &((CPlayerPed*)this)->m_pEvadingFrom); + return; + } + + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + reason->GetPosition().x, reason->GetPosition().y, + GetPosition().x, GetPosition().y); + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + + // FIX: Peds no more dive into cars. Taken from SetEvasiveStep, last if statement inverted +#ifdef FIX_BUGS + float vehDirection = CGeneral::GetRadianAngleBetweenPoints( + veh->m_vecMoveSpeed.x, veh->m_vecMoveSpeed.y, + 0.0f, 0.0f); + + // Let's turn our back to the "reason" + angleToFace += PI; + + if (angleToFace > PI) + angleToFace -= 2 * PI; + + // We don't want to dive towards car's direction + float dangerZone = angleToFace - vehDirection; + dangerZone = CGeneral::LimitRadianAngle(dangerZone); + + // So, add or subtract 90deg (jump to left/right) according to that + if (dangerZone > 0.0f) + angleToFace = 0.5f * PI + vehDirection; + else + angleToFace = vehDirection - 0.5f * PI; +#endif + + neededTurn = Abs(angleToFace - m_fRotationCur); + + if (neededTurn > PI) + neededTurn = 2 * PI - neededTurn; + + if (neededTurn <= 0.5f*PI) { + if (CGeneral::GetRandomNumber() & 1) + handsUp = true; + } else { + if (CGeneral::GetRandomNumber() & 7) + return; + } + Say(SOUND_PED_EVADE); + } + + if (handsUp || !IsPlayer() && m_pedStats->m_flags & STAT_NO_DIVE) { + m_fRotationCur = angleToFace; + ClearLookFlag(); + ClearAimFlag(); + SetLookFlag(reason, true); + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HANDSUP); + if (animAssoc) + return; + + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSUP, 8.0f); + animAssoc->flags &= ~ASSOC_DELETEFADEDOUT; + animAssoc->SetFinishCallback(PedEvadeCB, this); + SetStoredState(); + SetPedState(PED_STEP_AWAY); + } else { + m_fRotationCur = angleToFace; + ClearLookFlag(); + ClearAimFlag(); + SetStoredState(); + SetPedState(PED_DIVE_AWAY); + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_EVADE_DIVE, 8.0f); + animAssoc->SetFinishCallback(PedEvadeCB, this); + } + + if (reason->IsVehicle() && m_nPedType == PEDTYPE_COP) { + if (veh->pDriver && veh->pDriver->IsPlayer()) { + CWanted *wanted = FindPlayerPed()->m_pWanted; + wanted->RegisterCrime_Immediately(CRIME_RECKLESS_DRIVING, GetPosition(), (uintptr)this, false); + wanted->RegisterCrime_Immediately(CRIME_SPEEDING, GetPosition(), (uintptr)this, false); + } + } +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + else if (reason->IsVehicle()) { + if (veh->pDriver && veh->pDriver->IsPlayer()) { + CWanted* wanted = FindPlayerPed()->m_pWanted; + wanted->RegisterCrime(CRIME_RECKLESS_DRIVING, GetPosition(), (uintptr)this, false); + } + } +#endif +} + +void +CPed::PedEvadeCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + if (!animAssoc) { + ped->ClearLookFlag(); + if (ped->m_nPedState == PED_DIVE_AWAY || ped->m_nPedState == PED_STEP_AWAY) + ped->RestorePreviousState(); + + } else if (animAssoc->animId == ANIM_STD_EVADE_DIVE) { + ped->bUpdateAnimHeading = true; + ped->ClearLookFlag(); + if (ped->m_nPedState == PED_DIVE_AWAY) + { + ped->m_getUpTimer = CTimer::GetTimeInMilliseconds() + 1; + ped->SetPedState(PED_FALL); + } + animAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + + } else if (animAssoc->flags & ASSOC_FADEOUTWHENDONE) { + ped->ClearLookFlag(); + if (ped->m_nPedState == PED_DIVE_AWAY || ped->m_nPedState == PED_STEP_AWAY) + ped->RestorePreviousState(); + + } else if (ped->m_nPedState != PED_ARRESTED) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (animAssoc->blendDelta >= 0.0f) + animAssoc->blendDelta = -4.0f; + + ped->ClearLookFlag(); + if (ped->m_nPedState == PED_DIVE_AWAY || ped->m_nPedState == PED_STEP_AWAY) { + ped->RestorePreviousState(); + } + } +} + +void +CPed::SetDie(AnimationId animId, float delta, float speed) +{ + CPlayerPed *player = FindPlayerPed(); + if (player == this) { + if (!player->m_bCanBeDamaged) + return; + } + + m_threatEntity = nil; + if (DyingOrDead()) + return; + + if (m_nPedState == PED_FALL || m_nPedState == PED_GETUP) + delta *= 0.5f; + + SetStoredState(); + ClearAll(); + m_fHealth = 0.0f; + if (m_nPedState == PED_DRIVING) { + if (!IsPlayer()) + FlagToDestroyWhenNextProcessed(); + } else if (bInVehicle) { + if (m_pVehicleAnim) + m_pVehicleAnim->blendDelta = -1000.0f; + } else if (EnteringCar()) { + QuitEnteringCar(); + } + + SetPedState(PED_DIE); + if (animId == ANIM_STD_NUM) { + bIsPedDieAnimPlaying = false; + } else { + CAnimBlendAssociation *dieAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animId, delta); + if (speed > 0.0f) + dieAssoc->speed = speed; + + dieAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + if (dieAssoc->IsRunning()) { + dieAssoc->SetFinishCallback(FinishDieAnimCB, this); + bIsPedDieAnimPlaying = true; + } + } + + Say(SOUND_PED_DEATH); + if (m_nLastPedState == PED_ENTER_CAR || m_nLastPedState == PED_CARJACK) + QuitEnteringCar(); + if (!bInVehicle) + StopNonPartialAnims(); + + m_bloodyFootprintCountOrDeathTime = CTimer::GetTimeInMilliseconds(); +} + +void +CPed::FinishDieAnimCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (ped->bIsPedDieAnimPlaying) + ped->bIsPedDieAnimPlaying = false; +} + +void +CPed::SetDead(void) +{ + bUsesCollision = false; + + m_fHealth = 0.0f; + if (m_nPedState == PED_DRIVING) + bIsVisible = false; + + SetPedState(PED_DEAD); + m_pVehicleAnim = nil; + m_pCollidingEntity = nil; + + CWeaponInfo *weapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(weapon->m_nModelId); + + m_currentWeapon = WEAPONTYPE_UNARMED; + CEventList::RegisterEvent(EVENT_INJURED_PED, EVENT_ENTITY_PED, this, nil, 250); + if (this != FindPlayerPed()) { + CreateDeadPedWeaponPickups(); + CreateDeadPedMoney(); + } + + m_bloodyFootprintCountOrDeathTime = CTimer::GetTimeInMilliseconds(); + m_deadBleeding = false; + bDoBloodyFootprints = false; + bVehExitWillBeInstant = false; + CEventList::RegisterEvent(EVENT_DEAD_PED, EVENT_ENTITY_PED, this, nil, 1000); +} + +void +CPed::Die(void) +{ + // UNUSED: This is a perfectly empty function. +} + +void +CPed::SetChat(CEntity *chatWith, uint32 time) +{ + if(m_nPedState != PED_CHAT) + SetStoredState(); + + SetPedState(PED_CHAT); + SetMoveState(PEDMOVE_STILL); +#if defined VC_PED_PORTS || defined FIX_BUGS + m_lookTimer = 0; +#endif + SetLookFlag(chatWith, true); + m_chatTimer = CTimer::GetTimeInMilliseconds() + time; + m_lookTimer = CTimer::GetTimeInMilliseconds() + 3000; +} + +void +CPed::Chat(void) +{ + // We're already looking to our partner + if (bIsLooking && TurnBody()) + ClearLookFlag(); + + if (!m_pLookTarget || !m_pLookTarget->IsPed()) { + ClearChat(); + return; + } + + CPed *partner = (CPed*) m_pLookTarget; + + if (partner->m_nPedState != PED_CHAT) { + ClearChat(); + if (partner->m_pedInObjective) { + if (partner->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || + partner->m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE) + ReactToAttack(partner->m_pedInObjective); + } + return; + } + if (bIsTalking) { + if (CGeneral::GetRandomNumber() < 512) { + CAnimBlendAssociation *chatAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CHAT); + if (chatAssoc) { + chatAssoc->blendDelta = -4.0f; + chatAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + bIsTalking = false; + } else + Say(SOUND_PED_CHAT); + + } else { + + if (CGeneral::GetRandomNumber() < 20 && !RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_IDLE)) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_XPRESS_SCRATCH, 4.0f); + } + if (!bIsTalking && !RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_IDLE)) { + CAnimBlendAssociation *chatAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CHAT, 4.0f); + float chatTime = CGeneral::GetRandomNumberInRange(0.0f, 3.0f); + chatAssoc->SetCurrentTime(chatTime); + + bIsTalking = true; + Say(SOUND_PED_CHAT); + } + } + if (m_chatTimer && CTimer::GetTimeInMilliseconds() > m_chatTimer) { + ClearChat(); + m_chatTimer = CTimer::GetTimeInMilliseconds() + 30000; + } +} + +void +CPed::ClearChat(void) +{ + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CHAT); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + bIsTalking = false; + ClearLookFlag(); + RestorePreviousState(); +} + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +void +ReportPhonePickUpCB(CAnimBlendAssociation* assoc, void* arg) +{ + CPed* ped = (CPed*)arg; + ped->m_nMoveState = PEDMOVE_STILL; + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE, 8.0f); + + if (assoc->blendAmount > 0.5f && ped) { + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_TALK, 8.0f); + } +} + +void +ReportPhonePutDownCB(CAnimBlendAssociation* assoc, void* arg) +{ + assoc->flags |= ASSOC_DELETEFADEDOUT; + assoc->blendDelta = -1000.0f; + CPed* ped = (CPed*)arg; + + if (ped->m_phoneId != -1 && crimeReporters[ped->m_phoneId] == ped) { + crimeReporters[ped->m_phoneId] = nil; + gPhoneInfo.m_aPhones[ped->m_phoneId].m_nState = PHONE_STATE_FREE; + ped->m_phoneId = -1; + } + + if (assoc->blendAmount > 0.5f) + ped->bUpdateAnimHeading = true; + + ped->SetWanderPath(CGeneral::GetRandomNumber() & 7); +} +#endif + +bool +CPed::FacePhone(void) +{ + // This function was broken since it's left unused early in development. +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + float phoneDir = CGeneral::GetRadianAngleBetweenPoints( + gPhoneInfo.m_aPhones[m_phoneId].m_vecPos.x, gPhoneInfo.m_aPhones[m_phoneId].m_vecPos.y, + GetPosition().x, GetPosition().y); + + if (m_facePhoneStart) { + m_lookTimer = 0; + SetLookFlag(phoneDir, true); + m_lookTimer = CTimer::GetTimeInMilliseconds() + 3000; + m_facePhoneStart = false; + } + + if (bIsLooking && TurnBody()) { + ClearLookFlag(); + SetIdle(); + m_phoneTalkTimer = CTimer::GetTimeInMilliseconds() + 10000; + CAnimBlendAssociation* assoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_IN, 4.0f); + assoc->SetFinishCallback(ReportPhonePickUpCB, this); + return true; + } + + return false; +#else + float currentRot = RADTODEG(m_fRotationCur); + float phoneDir = CGeneral::GetRadianAngleBetweenPoints( + gPhoneInfo.m_aPhones[m_phoneId].m_vecPos.x, + gPhoneInfo.m_aPhones[m_phoneId].m_vecPos.y, + GetPosition().x, + GetPosition().y); + + SetLookFlag(phoneDir, false); + phoneDir = CGeneral::LimitAngle(phoneDir); + m_moved = CVector2D(0.0f, 0.0f); + + if (currentRot - 180.0f > phoneDir) + phoneDir += 2 * 180.0f; + else if (180.0f + currentRot < phoneDir) + phoneDir -= 2 * 180.0f; + + float neededTurn = currentRot - phoneDir; + + if (Abs(neededTurn) <= 0.75f) { + SetIdle(); + ClearLookFlag(); + m_phoneTalkTimer = CTimer::GetTimeInMilliseconds() + 10000; + return true; + } else { + m_fRotationCur = DEGTORAD(currentRot - neededTurn * 0.2f); + return false; + } +#endif +} +bool +CPed::MakePhonecall(void) +{ +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + if (!IsPlayer() && CTimer::GetTimeInMilliseconds() > m_phoneTalkTimer - 7000 && bRunningToPhone) { + + FindPlayerPed()->m_pWanted->RegisterCrime_Immediately(m_crimeToReportOnPhone, GetPosition(), + (m_crimeToReportOnPhone == CRIME_POSSESSION_GUN ? (uintptr)m_threatEntity : (uintptr)m_victimOfPlayerCrime), false); + + if (m_crimeToReportOnPhone != CRIME_POSSESSION_GUN) + FindPlayerPed()->m_pWanted->SetWantedLevelNoDrop(1); + + bRunningToPhone = false; + } +#endif + if (CTimer::GetTimeInMilliseconds() <= m_phoneTalkTimer) + return false; + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + CAnimBlendAssociation* talkAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_PHONE_TALK); + if (talkAssoc && talkAssoc->blendAmount > 0.5f) { + CAnimBlendAssociation* endAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_OUT, 8.0f); + endAssoc->flags &= ~ASSOC_DELETEFADEDOUT; + endAssoc->SetFinishCallback(ReportPhonePutDownCB, this); + } +#endif + SetIdle(); + + gPhoneInfo.m_aPhones[m_phoneId].m_nState = PHONE_STATE_FREE; +#ifndef PEDS_REPORT_CRIMES_ON_PHONE + m_phoneId = -1; +#endif + + // Because SetWanderPath is now done async in ReportPhonePutDownCB +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + return false; +#else + return true; +#endif +} + +void +CPed::Teleport(CVector pos) +{ + CWorld::Remove(this); + SetPosition(pos); + bIsStanding = false; + m_nPedStateTimer = 0; + m_actionX = 0.0f; + m_actionY = 0.0f; + m_pDamageEntity = nil; + CWorld::Add(this); +} + +void +CPed::SetSeekCar(CVehicle *car, uint32 doorNode) +{ + if (m_nPedState == PED_SEEK_CAR) + return; + +#ifdef VC_PED_PORTS + if (!CanSetPedState() || m_nPedState == PED_DRIVING) + return; +#endif + + SetStoredState(); + m_pSeekTarget = car; + m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + m_carInObjective = car; + m_carInObjective->RegisterReference((CEntity**) &m_carInObjective); + m_pMyVehicle = car; + m_pMyVehicle->RegisterReference((CEntity**) &m_pMyVehicle); + // m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + m_vehDoor = doorNode; + m_distanceToCountSeekDone = 0.5f; + SetPedState(PED_SEEK_CAR); + +} + +void +CPed::SeekCar(void) +{ + CVehicle *vehToSeek = m_carInObjective; + CVector dest(0.0f, 0.0f, 0.0f); + if (!vehToSeek) { + RestorePreviousState(); + return; + } + + if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + if (m_vehDoor && m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (IsRoomToBeCarJacked()) { + dest = GetPositionToOpenCarDoor(vehToSeek, m_vehDoor); + } else if (m_nPedType == PEDTYPE_COP) { + dest = GetPositionToOpenCarDoor(vehToSeek, CAR_DOOR_RF); + } else { + SetMoveState(PEDMOVE_STILL); + } + } else + GetNearestDoor(vehToSeek, dest); + } else { + if (m_carJackTimer > CTimer::GetTimeInMilliseconds()) { + SetMoveState(PEDMOVE_STILL); + return; + } + if (vehToSeek->GetModelIndex() == MI_COACH) { + GetNearestDoor(vehToSeek, dest); + } else { + if (vehToSeek->IsTrain()) { + if (vehToSeek->GetStatus() != STATUS_TRAIN_NOT_MOVING) { + RestorePreviousObjective(); + RestorePreviousState(); + return; + } + if (!GetNearestTrainDoor(vehToSeek, dest)) { + RestorePreviousObjective(); + RestorePreviousState(); + return; + } + } else { + if (!GetNearestPassengerDoor(vehToSeek, dest)) { + if (vehToSeek->m_nNumPassengers == vehToSeek->m_nNumMaxPassengers) { + RestorePreviousObjective(); + RestorePreviousState(); + } else { + SetMoveState(PEDMOVE_STILL); + } + bVehEnterDoorIsBlocked = true; + return; + } + bVehEnterDoorIsBlocked = false; + } + } + } + + if (dest.x == 0.0f && dest.y == 0.0f) { +#ifdef FIX_BUGS + if ((!IsPlayer() && CharCreatedBy != MISSION_CHAR) || vehToSeek->VehicleCreatedBy != MISSION_VEHICLE || vehToSeek->pDriver || !vehToSeek->CanPedOpenLocks(this)) { +#else + if ((!IsPlayer() && CharCreatedBy != MISSION_CHAR) || vehToSeek->VehicleCreatedBy != MISSION_VEHICLE || vehToSeek->pDriver) { +#endif + RestorePreviousState(); + if (IsPlayer()) { + ClearObjective(); + } else if (CharCreatedBy == RANDOM_CHAR) { + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 30000; + } + SetMoveState(PEDMOVE_STILL); + TheCamera.ClearPlayerWeaponMode(); + CCarCtrl::RemoveFromInterestingVehicleList(vehToSeek); + return; + } + dest = vehToSeek->GetPosition(); + if (bCollidedWithMyVehicle) { + WarpPedIntoCar(m_pMyVehicle); + return; + } + } + bool foundBetterPosToSeek = PossiblyFindBetterPosToSeekCar(&dest, vehToSeek); + m_vecSeekPos = dest; + float distToDestSqr = (m_vecSeekPos - GetPosition()).MagnitudeSqr(); +#ifndef VC_PED_PORTS + if (bIsRunning) + SetMoveState(PEDMOVE_RUN); +#else + if (bIsRunning || + vehToSeek->pDriver && distToDestSqr > sq(2.0f) && (Abs(vehToSeek->m_vecMoveSpeed.x) > 0.01f || Abs(vehToSeek->m_vecMoveSpeed.y) > 0.01f)) + SetMoveState(PEDMOVE_RUN); +#endif + else if (distToDestSqr < sq(2.0f)) + SetMoveState(PEDMOVE_WALK); + + if (distToDestSqr >= 1.0f) + bCanPedEnterSeekedCar = false; + else if (2.0f * vehToSeek->GetColModel()->boundingBox.max.x > distToDestSqr) + bCanPedEnterSeekedCar = true; + + if (vehToSeek->m_nGettingInFlags & GetCarDoorFlag(m_vehDoor)) + bVehEnterDoorIsBlocked = true; + else + bVehEnterDoorIsBlocked = false; + + // Arrived to the car + if (Seek()) { + if (!foundBetterPosToSeek) { + if (1.5f + GetPosition().z > dest.z && GetPosition().z - 0.5f < dest.z) { + if (vehToSeek->IsTrain()) { + SetEnterTrain(vehToSeek, m_vehDoor); + } else { + m_fRotationCur = m_fRotationDest; + if (!bVehEnterDoorIsBlocked) { + vehToSeek->SetIsStatic(false); + if (m_objective == OBJECTIVE_SOLICIT_VEHICLE) { + SetSolicit(1000); + } else if (m_objective == OBJECTIVE_BUY_ICE_CREAM) { + SetBuyIceCream(); + } else if (vehToSeek->m_nNumGettingIn < vehToSeek->m_nNumMaxPassengers + 1 + && vehToSeek->CanPedEnterCar()) { + + switch (vehToSeek->GetStatus()) { + case STATUS_PLAYER: + case STATUS_SIMPLE: + case STATUS_PHYSICS: + case STATUS_PLAYER_DISABLED: + if (!vehToSeek->bIsBus && (!m_leader || m_leader != vehToSeek->pDriver) && + (m_vehDoor == CAR_DOOR_LF && vehToSeek->pDriver || m_vehDoor == CAR_DOOR_RF && vehToSeek->pPassengers[0] || m_vehDoor == CAR_DOOR_LR && vehToSeek->pPassengers[1] || m_vehDoor == CAR_DOOR_RR && vehToSeek->pPassengers[2])) { + SetCarJack(vehToSeek); + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && m_vehDoor != CAR_DOOR_LF) + vehToSeek->pDriver->bFleeAfterExitingCar = true; + } else { + SetEnterCar(vehToSeek, m_vehDoor); + } + break; + case STATUS_ABANDONED: + if (m_vehDoor == CAR_DOOR_RF && vehToSeek->pPassengers[0]) { + if (vehToSeek->pPassengers[0]->bDontDragMeOutCar) { + if (IsPlayer()) + SetEnterCar(vehToSeek, m_vehDoor); + } else { + SetCarJack(vehToSeek); + } + } else { + SetEnterCar(vehToSeek, m_vehDoor); + } + break; + case STATUS_WRECKED: + SetIdle(); + break; + default: + return; + } + } else { + RestorePreviousState(); + } + } else { + SetMoveState(PEDMOVE_STILL); + } + } + } + } + } +} + +bool +CPed::CheckForExplosions(CVector2D &area) +{ + int event = 0; + if (CEventList::FindClosestEvent(EVENT_EXPLOSION, GetPosition(), &event)) { + area.x = gaEvent[event].posn.x; + area.y = gaEvent[event].posn.y; + CEntity *actualEntity = nil; + + switch (gaEvent[event].entityType) { + case EVENT_ENTITY_PED: + actualEntity = CPools::GetPed(gaEvent[event].entityRef); + break; + case EVENT_ENTITY_VEHICLE: + actualEntity = CPools::GetVehicle(gaEvent[event].entityRef); + break; + case EVENT_ENTITY_OBJECT: + actualEntity = CPools::GetObject(gaEvent[event].entityRef); + break; + default: + break; + } + + if (actualEntity) { + m_pEventEntity = actualEntity; + m_pEventEntity->RegisterReference((CEntity **) &m_pEventEntity); + bGonnaInvestigateEvent = true; + } else + bGonnaInvestigateEvent = false; + + CEventList::ClearEvent(event); + return true; + } else if (CEventList::FindClosestEvent(EVENT_FIRE, GetPosition(), &event)) { + area.x = gaEvent[event].posn.x; + area.y = gaEvent[event].posn.y; + CEventList::ClearEvent(event); + bGonnaInvestigateEvent = false; + return true; + } + + bGonnaInvestigateEvent = false; + return false; +} + +CPed * +CPed::CheckForGunShots(void) +{ + int event; + if (CEventList::FindClosestEvent(EVENT_GUNSHOT, GetPosition(), &event)) { + if (gaEvent[event].entityType == EVENT_ENTITY_PED) { + // Probably due to we don't want peds to go gunshot area? (same on VC) + bGonnaInvestigateEvent = false; + return CPools::GetPed(gaEvent[event].entityRef); + } + } + bGonnaInvestigateEvent = false; + return nil; +} + +CPed * +CPed::CheckForDeadPeds(void) +{ + int event; + if (CEventList::FindClosestEvent(EVENT_DEAD_PED, GetPosition(), &event)) { + int pedHandle = gaEvent[event].entityRef; + if (pedHandle && gaEvent[event].entityType == EVENT_ENTITY_PED) { + bGonnaInvestigateEvent = true; + return CPools::GetPed(pedHandle); + } + } + bGonnaInvestigateEvent = false; + return nil; +} + +bool +CPed::IsPlayer(void) const +{ + return m_nPedType == PEDTYPE_PLAYER1 || m_nPedType == PEDTYPE_PLAYER2 || + m_nPedType == PEDTYPE_PLAYER3 || m_nPedType == PEDTYPE_PLAYER4; +} + +bool +CPed::IsGangMember(void) const +{ + return m_nPedType >= PEDTYPE_GANG1 && m_nPedType <= PEDTYPE_GANG9; +} + +bool +CPed::IsPointerValid(void) +{ + int pedIndex = CPools::GetPedPool()->GetIndex(this) >> 8; + if (pedIndex < 0 || pedIndex >= NUMPEDS) + return false; + + if (m_entryInfoList.first || FindPlayerPed() == this) + return true; + + return false; +} + +void +CPed::SetPedPositionInCar(void) +{ + if (CReplay::IsPlayingBack()) + return; + + if (bChangedSeat) { + bool notYet = false; + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_GET_IN_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_GET_IN_LO_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_CLOSE_DOOR_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_CLOSE_DOOR_LO_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SHUFFLE_RHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SHUFFLE_LO_RHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_VAN_GET_IN_REAR_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_VAN_GET_IN_REAR_RHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_COACH_GET_IN_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_COACH_GET_IN_RHS)) { + notYet = true; + } + if (notYet) { + LineUpPedWithCar(LINE_UP_TO_CAR_START); + bChangedSeat = false; + return; + } + } + CVehicleModelInfo *vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(m_pMyVehicle->GetModelIndex()); + CMatrix newMat(m_pMyVehicle->GetMatrix()); + CVector seatPos; + if (m_pMyVehicle->pDriver == this) { + seatPos = vehModel->GetFrontSeatPosn(); + if (!m_pMyVehicle->IsBoat() && m_pMyVehicle->m_vehType != VEHICLE_TYPE_BIKE) + seatPos.x = -seatPos.x; + + } else if (m_pMyVehicle->pPassengers[0] == this) { + seatPos = vehModel->GetFrontSeatPosn(); + } else if (m_pMyVehicle->pPassengers[1] == this) { + seatPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + seatPos.x = -seatPos.x; + } else { + if (m_pMyVehicle->pPassengers[2] == this) { + seatPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + } else { + seatPos = vehModel->GetFrontSeatPosn(); + } + } + newMat.GetPosition() += Multiply3x3(newMat, seatPos); + // Already done below (SetTranslate(0.0f, 0.0f, 0.0f)) + // tempMat.SetUnity(); + + // Rear seats on vans don't face to front, so rotate them HALFPI. + if (m_pMyVehicle->bIsVan) { + CMatrix tempMat; + if (m_pMyVehicle->pPassengers[1] == this) { + m_fRotationCur = m_pMyVehicle->GetForward().Heading() - HALFPI; + tempMat.SetTranslate(0.0f, 0.0f, 0.0f); + tempMat.RotateZ(-HALFPI); + newMat = newMat * tempMat; + } else if (m_pMyVehicle->pPassengers[2] == this) { + m_fRotationCur = m_pMyVehicle->GetForward().Heading() + HALFPI; + tempMat.SetTranslate(0.0f, 0.0f, 0.0f); + tempMat.RotateZ(HALFPI); + newMat = newMat * tempMat; + } else { + m_fRotationCur = m_pMyVehicle->GetForward().Heading(); + } + } else { + m_fRotationCur = m_pMyVehicle->GetForward().Heading(); + } + SetMatrix(newMat); +} + +void +CPed::LookForSexyPeds(void) +{ + if ((!IsPedInControl() && m_nPedState != PED_DRIVING) + || m_lookTimer >= CTimer::GetTimeInMilliseconds() || +#ifdef FIX_BUGS + // gang members have these lines too + (!IsGangMember() && m_nPedType != PEDTYPE_CIVMALE) +#else + m_nPedType != PEDTYPE_CIVMALE +#endif + ) + return; + + for (int i = 0; i < m_numNearPeds; i++) { + if (CanSeeEntity(m_nearPeds[i])) { + if ((GetPosition() - m_nearPeds[i]->GetPosition()).Magnitude() < 10.0f) { + CPed *nearPed = m_nearPeds[i]; + if ((nearPed->m_pedStats->m_sexiness > m_pedStats->m_sexiness) +#ifdef FIX_BUGS + // react to prostitutes as well + && ((nearPed->m_nPedType == PEDTYPE_CIVFEMALE) || (nearPed->m_nPedType == PEDTYPE_PROSTITUTE))) { +#else + && nearPed->m_nPedType == PEDTYPE_CIVFEMALE) { +#endif + + SetLookFlag(nearPed, true); + m_lookTimer = CTimer::GetTimeInMilliseconds() + 4000; + Say(SOUND_PED_CHAT_SEXY); + return; + } + } + } + } + m_lookTimer = CTimer::GetTimeInMilliseconds() + 10000; +} + +void +CPed::LookForSexyCars(void) +{ + CEntity *vehicles[8]; + CVehicle *veh; + int foundVehId = 0; + int bestPriceYet = 0; + int16 lastVehicle; + + if (!IsPedInControl() && m_nPedState != PED_DRIVING) + return; + + if (m_lookTimer < CTimer::GetTimeInMilliseconds()) { + CWorld::FindObjectsInRange(GetPosition(), 10.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int vehId = 0; vehId < lastVehicle; vehId++) { + veh = (CVehicle*)vehicles[vehId]; + if (veh != m_pMyVehicle && bestPriceYet < veh->pHandling->nMonetaryValue) { + foundVehId = vehId; + bestPriceYet = veh->pHandling->nMonetaryValue; + } + } + if (lastVehicle > 0 && bestPriceYet > 40000) + SetLookFlag(vehicles[foundVehId], false); + + m_lookTimer = CTimer::GetTimeInMilliseconds() + 10000; + } +} + +bool +CPed::LookForInterestingNodes(void) +{ + CBaseModelInfo *model; + CPtrNode *ptrNode; + CVector effectDist; + C2dEffect *effect; + CMatrix *objMat; + + if ((CTimer::GetFrameCounter() + (m_randomSeed % 256)) & 7 || CTimer::GetTimeInMilliseconds() <= m_chatTimer) { + return false; + } + bool found = false; + uint8 randVal = CGeneral::GetRandomNumber() % 256; + + int minX = CWorld::GetSectorIndexX(GetPosition().x - CHECK_NEARBY_THINGS_MAX_DIST); + if (minX < 0) minX = 0; + int minY = CWorld::GetSectorIndexY(GetPosition().y - CHECK_NEARBY_THINGS_MAX_DIST); + if (minY < 0) minY = 0; + int maxX = CWorld::GetSectorIndexX(GetPosition().x + CHECK_NEARBY_THINGS_MAX_DIST); +#ifdef FIX_BUGS + if (maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; +#else + if (maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X; +#endif + + int maxY = CWorld::GetSectorIndexY(GetPosition().y + CHECK_NEARBY_THINGS_MAX_DIST); +#ifdef FIX_BUGS + if (maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; +#else + if (maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y; +#endif + + for (int curY = minY; curY <= maxY && !found; curY++) { + for (int curX = minX; curX <= maxX && !found; curX++) { + CSector *sector = CWorld::GetSector(curX, curY); + + for (ptrNode = sector->m_lists[ENTITYLIST_VEHICLES].first; ptrNode && !found; ptrNode = ptrNode->next) { + CVehicle *veh = (CVehicle*)ptrNode->item; + model = veh->GetModelInfo(); + if (model->GetNum2dEffects() != 0) { + for (int e = 0; e < model->GetNum2dEffects(); e++) { + effect = model->Get2dEffect(e); + if (effect->type == EFFECT_ATTRACTOR && effect->attractor.probability >= randVal) { + objMat = &veh->GetMatrix(); + CVector effectPos = veh->GetMatrix() * effect->pos; + effectDist = effectPos - GetPosition(); + if (effectDist.MagnitudeSqr() < sq(8.0f)) { + found = true; + break; + } + } + } + } + } + for (ptrNode = sector->m_lists[ENTITYLIST_OBJECTS].first; ptrNode && !found; ptrNode = ptrNode->next) { + CObject *obj = (CObject*)ptrNode->item; + model = CModelInfo::GetModelInfo(obj->GetModelIndex()); + if (model->GetNum2dEffects() != 0) { + for (int e = 0; e < model->GetNum2dEffects(); e++) { + effect = model->Get2dEffect(e); + if (effect->type == EFFECT_ATTRACTOR && effect->attractor.probability >= randVal) { + objMat = &obj->GetMatrix(); + CVector effectPos = obj->GetMatrix() * effect->pos; + effectDist = effectPos - GetPosition(); + if (effectDist.MagnitudeSqr() < sq(8.0f)) { + found = true; + break; + } + } + } + } + } + for (ptrNode = sector->m_lists[ENTITYLIST_BUILDINGS].first; ptrNode && !found; ptrNode = ptrNode->next) { + CBuilding *building = (CBuilding*)ptrNode->item; + model = CModelInfo::GetModelInfo(building->GetModelIndex()); + if (model->GetNum2dEffects() != 0) { + for (int e = 0; e < model->GetNum2dEffects(); e++) { + effect = model->Get2dEffect(e); + if (effect->type == EFFECT_ATTRACTOR && effect->attractor.probability >= randVal) { + objMat = &building->GetMatrix(); + CVector effectPos = building->GetMatrix() * effect->pos; + effectDist = effectPos - GetPosition(); + if (effectDist.MagnitudeSqr() < sq(8.0f)) { + found = true; + break; + } + } + } + } + } + for (ptrNode = sector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP].first; ptrNode && !found; ptrNode = ptrNode->next) { + CBuilding *building = (CBuilding*)ptrNode->item; + model = CModelInfo::GetModelInfo(building->GetModelIndex()); + if (model->GetNum2dEffects() != 0) { + for (int e = 0; e < model->GetNum2dEffects(); e++) { + effect = model->Get2dEffect(e); + if (effect->type == EFFECT_ATTRACTOR && effect->attractor.probability >= randVal) { + objMat = &building->GetMatrix(); + CVector effectPos = building->GetMatrix() * effect->pos; + effectDist = effectPos - GetPosition(); + if (effectDist.MagnitudeSqr() < sq(8.0f)) { + found = true; + break; + } + } + } + } + } + } + } + + if (!found) + return false; + + CVector effectFrontLocal = Multiply3x3(*objMat, effect->attractor.dir); + float angleToFace = CGeneral::GetRadianAngleBetweenPoints(effectFrontLocal.x, effectFrontLocal.y, 0.0f, 0.0f); + randVal = CGeneral::GetRandomNumber() % 256; + if (randVal <= m_randomSeed % 256) { + m_chatTimer = CTimer::GetTimeInMilliseconds() + 2000; + SetLookFlag(angleToFace, true); + SetLookTimer(1000); + return false; + } + + CVector2D effectPos = *objMat * effect->pos; + switch (effect->attractor.type) { + case ATTRACTORTYPE_ICECREAM: + SetInvestigateEvent(EVENT_ICECREAM, effectPos, 0.1f, 15000, angleToFace); + break; + case ATTRACTORTYPE_STARE: + SetInvestigateEvent(EVENT_SHOPSTALL, effectPos, 1.0f, + CGeneral::GetRandomNumberInRange(8000, 10 * effect->attractor.probability + 8500), + angleToFace); + break; + default: + return true; + } + return true; +} + +void +CPed::SetWaitState(eWaitState state, void *time) +{ + AnimationId waitAnim = ANIM_STD_NUM; + CAnimBlendAssociation *animAssoc; + + if (!IsPedInControl()) + return; + + if (state != m_nWaitState) + FinishedWaitCB(nil, this); + + switch (state) { + case WAITSTATE_TRAFFIC_LIGHTS: + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 500; + SetMoveState(PEDMOVE_STILL); + break; + case WAITSTATE_CROSS_ROAD: + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 1000; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_HBHB, 4.0f); + break; + case WAITSTATE_CROSS_ROAD_LOOK: + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROADCROSS, 8.0f); + + if (time) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(2000,5000); + + break; + case WAITSTATE_LOOK_PED: + case WAITSTATE_LOOK_SHOP: + case WAITSTATE_LOOK_ACCIDENT: + case WAITSTATE_FACEOFF_GANG: + break; + case WAITSTATE_DOUBLEBACK: + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 3500; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_HBHB, 4.0f); +#ifdef FIX_BUGS + animAssoc->SetFinishCallback(RestoreHeadingRateCB, this); +#endif + break; + case WAITSTATE_HITWALL: + m_headingRate = 2.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_WALL, 16.0f); + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->flags |= ASSOC_FADEOUTWHENDONE; + animAssoc->SetDeleteCallback(FinishedWaitCB, this); + + if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER && CharCreatedBy == RANDOM_CHAR && m_nPedState == PED_SEEK_CAR) { + ClearObjective(); + RestorePreviousState(); + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 30000; + } + break; + case WAITSTATE_TURN180: + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_TURN180, 4.0f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + animAssoc->SetDeleteCallback(RestoreHeadingRateCB, this); + break; + case WAITSTATE_SURPRISE: + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_WALL, 4.0f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + break; + case WAITSTATE_STUCK: + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 4.0f); +#ifdef FIX_BUGS + animAssoc->SetFinishCallback(RestoreHeadingRateCB, this); +#endif + + if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER && CharCreatedBy == RANDOM_CHAR && m_nPedState == PED_SEEK_CAR) { + ClearObjective(); + RestorePreviousState(); + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 30000; + } + break; + case WAITSTATE_LOOK_ABOUT: + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_HBHB, 4.0f); +#ifdef FIX_BUGS + animAssoc->SetFinishCallback(RestoreHeadingRateCB, this); +#endif + + break; + case WAITSTATE_PLAYANIM_COWER: + waitAnim = ANIM_STD_HANDSCOWER; + case WAITSTATE_PLAYANIM_HANDSUP: + if (waitAnim == ANIM_STD_NUM) + waitAnim = ANIM_STD_HANDSUP; + case WAITSTATE_PLAYANIM_HANDSCOWER: + if (waitAnim == ANIM_STD_NUM) + waitAnim = ANIM_STD_HANDSCOWER; + m_headingRate = 0.0f; + if (time) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 3000; + + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, waitAnim, 4.0f); + animAssoc->SetDeleteCallback(FinishedWaitCB, this); + break; + case WAITSTATE_PLAYANIM_DUCK: + waitAnim = ANIM_STD_DUCK_DOWN; + case WAITSTATE_PLAYANIM_TAXI: + if (waitAnim == ANIM_STD_NUM) + waitAnim = ANIM_STD_HAILTAXI; + case WAITSTATE_PLAYANIM_CHAT: + if (waitAnim == ANIM_STD_NUM) + waitAnim = ANIM_STD_CHAT; + if (time) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 3000; + + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, waitAnim, 4.0f); + animAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->SetDeleteCallback(FinishedWaitCB, this); + break; + case WAITSTATE_FINISH_FLEE: + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + m_headingRate = 0.0f; + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2500; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 4.0f); +#ifdef FIX_BUGS + animAssoc->SetFinishCallback(RestoreHeadingRateCB, this); +#endif + break; + default: + m_nWaitState = WAITSTATE_FALSE; + RestoreHeadingRate(); + return; + } + m_nWaitState = state; +} + +void +CPed::Wait(void) +{ + AnimationId mustHaveAnim = ANIM_STD_NUM; + CAnimBlendAssociation *animAssoc; + CPed *pedWeLook; + + if (DyingOrDead()) { + m_nWaitState = WAITSTATE_FALSE; + RestoreHeadingRate(); + return; + } + + switch (m_nWaitState) { + + case WAITSTATE_TRAFFIC_LIGHTS: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (CTrafficLights::LightForPeds() == PED_LIGHTS_WALK) { + m_nWaitState = WAITSTATE_FALSE; + SetMoveState(PEDMOVE_WALK); + } + } + break; + + case WAITSTATE_CROSS_ROAD: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (CGeneral::GetRandomNumber() & 1 || !m_nWaitTimer) + m_nWaitState = WAITSTATE_FALSE; + else + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, nil); + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_HBHB); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + break; + + case WAITSTATE_CROSS_ROAD_LOOK: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + m_nWaitState = WAITSTATE_FALSE; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + break; + + case WAITSTATE_DOUBLEBACK: + if (CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) { + uint32 timeLeft = m_nWaitTimer - CTimer::GetTimeInMilliseconds(); + if (timeLeft < 2500 && timeLeft > 2000) { + m_nWaitTimer -= 500; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_XPRESS_SCRATCH, 4.0f); + } + } else { + m_nWaitState = WAITSTATE_FALSE; + SetMoveState(PEDMOVE_WALK); + } + break; + + case WAITSTATE_HITWALL: + if (CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) { + if (m_collidingThingTimer > CTimer::GetTimeInMilliseconds()) { + m_collidingThingTimer = CTimer::GetTimeInMilliseconds() + 2500; + } + } else { + m_nWaitState = WAITSTATE_FALSE; + } + break; + + case WAITSTATE_TURN180: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + m_nWaitState = WAITSTATE_FALSE; + SetMoveState(PEDMOVE_WALK); + m_fRotationCur = m_fRotationCur + PI; + if (m_nPedState == PED_INVESTIGATE) + ClearInvestigateEvent(); + } + + if (m_collidingThingTimer > CTimer::GetTimeInMilliseconds()) { + m_collidingThingTimer = CTimer::GetTimeInMilliseconds() + 2500; + } + break; + + case WAITSTATE_SURPRISE: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HIT_WALL)) { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_XPRESS_SCRATCH, 4.0f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + } else { + m_nWaitState = WAITSTATE_FALSE; + } + } + break; + + case WAITSTATE_STUCK: + if (CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) + break; + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_TURN180); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_XPRESS_SCRATCH); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + + if (animAssoc) { + if (animAssoc->IsPartial()) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } else { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + } + + if (animAssoc->animId == ANIM_STD_TURN180) { + m_fRotationCur = CGeneral::LimitRadianAngle(PI + m_fRotationCur); + m_nWaitState = WAITSTATE_FALSE; + SetMoveState(PEDMOVE_WALK); + m_nStoredMoveState = PEDMOVE_NONE; + m_panicCounter = 0; + return; + } + } + + AnimationId animToPlay; + + switch (CGeneral::GetRandomNumber() & 3) { + case 0: + animToPlay = ANIM_STD_ROADCROSS; + break; + case 1: + animToPlay = ANIM_STD_IDLE_TIRED; + break; + case 2: + animToPlay = ANIM_STD_XPRESS_SCRATCH; + break; + case 3: + animToPlay = ANIM_STD_TURN180; + break; + default: + break; + } + + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animToPlay, 4.0f); + + if (animToPlay == ANIM_STD_TURN180) + animAssoc->SetFinishCallback(FinishedWaitCB, this); + + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(1500, 5000); + break; + + case WAITSTATE_LOOK_ABOUT: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + m_nWaitState = WAITSTATE_FALSE; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_HBHB); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + break; + + case WAITSTATE_PLAYANIM_HANDSUP: + mustHaveAnim = ANIM_STD_HANDSUP; + + case WAITSTATE_PLAYANIM_HANDSCOWER: + if (mustHaveAnim == ANIM_STD_NUM) + mustHaveAnim = ANIM_STD_HANDSCOWER; + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), mustHaveAnim); + pedWeLook = (CPed*) m_pLookTarget; + + if ((!m_pLookTarget || !m_pLookTarget->IsPed() || pedWeLook->m_pPointGunAt) + && m_nPedState != PED_FLEE_ENTITY + && m_nPedState != PED_ATTACK + && CTimer::GetTimeInMilliseconds() <= m_nWaitTimer + && animAssoc) { + + TurnBody(); + } else { + m_nWaitState = WAITSTATE_FALSE; + m_nWaitTimer = 0; + if (m_pLookTarget && m_pLookTarget->IsPed()) { + + if (m_nPedState != PED_FLEE_ENTITY && m_nPedState != PED_ATTACK) { + + if (m_pedStats->m_fear <= 100 - pedWeLook->m_pedStats->m_temper) { + + if (GetWeapon()->IsTypeMelee()) { +#ifdef VC_PED_PORTS + if(m_pedStats->m_flags & STAT_GUN_PANIC) { +#endif + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, m_pLookTarget); + if (m_nPedState == PED_FLEE_ENTITY || m_nPedState == PED_FLEE_POS) { + + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + } + if (m_nMoveState != PEDMOVE_RUN) + SetMoveState(PEDMOVE_WALK); + + if (m_nPedType != PEDTYPE_COP) { + ProcessObjective(); + SetMoveState(PEDMOVE_WALK); + } +#ifdef VC_PED_PORTS + } else { + SetObjective(OBJECTIVE_NONE); + SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + } +#endif + } else { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_pLookTarget); + SetObjectiveTimer(20000); + } + } else { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, m_pLookTarget); + if (m_nPedState == PED_FLEE_ENTITY || m_nPedState == PED_FLEE_POS) + { + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + } + SetMoveState(PEDMOVE_RUN); + Say(SOUND_PED_FLEE_RUN); + } + } + } + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), mustHaveAnim); + if (animAssoc) { + animAssoc->blendDelta = -4.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + break; + case WAITSTATE_PLAYANIM_COWER: + mustHaveAnim = ANIM_STD_HANDSCOWER; + + case WAITSTATE_PLAYANIM_DUCK: + if (mustHaveAnim == ANIM_STD_NUM) + mustHaveAnim = ANIM_STD_DUCK_DOWN; + + case WAITSTATE_PLAYANIM_TAXI: + if (mustHaveAnim == ANIM_STD_NUM) + mustHaveAnim = ANIM_STD_HAILTAXI; + + case WAITSTATE_PLAYANIM_CHAT: + if (mustHaveAnim == ANIM_STD_NUM) + mustHaveAnim = ANIM_STD_CHAT; + + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), mustHaveAnim); + if (animAssoc) { + animAssoc->blendDelta = -4.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + m_nWaitState = WAITSTATE_FALSE; + } +#ifdef VC_PED_PORTS + else if (m_nWaitState == WAITSTATE_PLAYANIM_TAXI) { + if (m_pedInObjective) { + if (m_objective == OBJECTIVE_GOTO_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT) { + + // VC also calls CleanUpOldReference here for old LookTarget. + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + } + } +#endif + break; + + case WAITSTATE_FINISH_FLEE: + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + if (animAssoc) { + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + int timer = 2000; + m_nWaitState = WAITSTATE_FALSE; + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &timer); + } + } else { + m_nWaitState = WAITSTATE_FALSE; + } + break; + default: + break; + } + + if(!m_nWaitState) + RestoreHeadingRate(); +} + +void +CPed::FinishedWaitCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + ped->m_nWaitTimer = 0; + ped->RestoreHeadingRate(); + ped->Wait(); +} + +void +CPed::RestoreHeadingRate(void) +{ + m_headingRate = m_pedStats->m_headingChangeRate; +} + +void +CPed::RestoreHeadingRateCB(CAnimBlendAssociation *assoc, void *arg) +{ + ((CPed*)arg)->m_headingRate = ((CPed*)arg)->m_pedStats->m_headingChangeRate; +} + +void +CPed::FlagToDestroyWhenNextProcessed(void) +{ + bRemoveFromWorld = true; + if (!InVehicle()) + return; + if (m_pMyVehicle->pDriver == this){ + m_pMyVehicle->pDriver = nil; + if (IsPlayer() && m_pMyVehicle->GetStatus() != STATUS_WRECKED) + m_pMyVehicle->SetStatus(STATUS_ABANDONED); + }else{ + m_pMyVehicle->RemovePassenger(this); + } + bInVehicle = false; + m_pMyVehicle = nil; + + if (CharCreatedBy == MISSION_CHAR) + SetPedState(PED_DEAD); + else + SetPedState(PED_NONE); + m_pVehicleAnim = nil; +} + +void +CPed::SetSolicit(uint32 time) +{ + if (m_nPedState == PED_SOLICIT || !IsPedInControl() || !m_carInObjective) + return; + + if (CharCreatedBy != MISSION_CHAR && m_carInObjective->m_nNumGettingIn == 0 + && CTimer::GetTimeInMilliseconds() < m_objectiveTimer) { + if (m_vehDoor == CAR_DOOR_LF) { + m_fRotationDest = m_carInObjective->GetForward().Heading() - HALFPI; + } else { + m_fRotationDest = m_carInObjective->GetForward().Heading() + HALFPI; + } + + if (Abs(m_fRotationDest - m_fRotationCur) < HALFPI) { + m_chatTimer = CTimer::GetTimeInMilliseconds() + time; + + if(!m_carInObjective->bIsVan && !m_carInObjective->bIsBus) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_HOOKERTALK, 4.0f); + + SetPedState(PED_SOLICIT); + } + } +} + +void +CPed::Solicit(void) +{ + if (m_chatTimer >= CTimer::GetTimeInMilliseconds() && m_carInObjective) { + CVector doorPos = GetPositionToOpenCarDoor(m_carInObjective, m_vehDoor, 0.0f); + SetMoveState(PEDMOVE_STILL); + + // Game uses GetAngleBetweenPoints and converts it to radian + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + doorPos.x, doorPos.y, + GetPosition().x, GetPosition().y); + + if (m_fRotationDest < 0.0f) { + m_fRotationDest = m_fRotationDest + TWOPI; + } else if (m_fRotationDest > TWOPI) { + m_fRotationDest = m_fRotationDest - TWOPI; + } + + if ((GetPosition() - doorPos).MagnitudeSqr() <= 1.0f) + return; + CAnimBlendAssociation *talkAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_HOOKERTALK); + if (talkAssoc) { + talkAssoc->blendDelta = -1000.0f; + talkAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + RestorePreviousState(); + RestorePreviousObjective(); + SetObjectiveTimer(10000); + } else if (!m_carInObjective) { + RestorePreviousState(); + RestorePreviousObjective(); + SetObjectiveTimer(10000); + } else if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney <= 100) { + m_carInObjective = nil; + } else { + m_pVehicleAnim = nil; + SetLeader(m_carInObjective->pDriver); + } +} + +void +CPed::SetBuyIceCream(void) +{ + if (m_nPedState == PED_BUY_ICECREAM || !IsPedInControl()) + return; + + if (!m_carInObjective) + return; + +#ifdef FIX_ICECREAM + + // Simulating BuyIceCream + CPed* driver = m_carInObjective->pDriver; + if (driver) { + SetPedState(PED_BUY_ICECREAM); + bFindNewNodeAfterStateRestore = true; + SetObjectiveTimer(8000); + SetChat(driver, 8000); + driver->SetChat(this, 8000); + return; + } +#endif + + // Side of the Ice Cream van + m_fRotationDest = m_carInObjective->GetForward().Heading() - HALFPI; + + if (Abs(m_fRotationDest - m_fRotationCur) < HALFPI) { + m_chatTimer = CTimer::GetTimeInMilliseconds() + 3000; + SetPedState(PED_BUY_ICECREAM); + } +} + +bool +CPed::PossiblyFindBetterPosToSeekCar(CVector *pos, CVehicle *veh) +{ + bool foundIt = false; + + CVector helperPos = GetPosition(); + helperPos.z = pos->z - 0.5f; + + CVector foundPos = *pos; + foundPos.z -= 0.5f; + + // If there is another car between target car and us. + if (CWorld::TestSphereAgainstWorld((foundPos + helperPos) / 2.0f, 0.25f, veh, false, true, false, false, false, false)) { + + CColModel *vehCol = veh->GetModelInfo()->GetColModel(); + CVector *colMin = &vehCol->boundingBox.min; + CVector *colMax = &vehCol->boundingBox.max; + + CVector leftRearPos = CVector(colMin->x - 0.5f, colMin->y - 0.5f, 0.0f); + CVector rightRearPos = CVector(0.5f + colMax->x, colMin->y - 0.5f, 0.0f); + CVector leftFrontPos = CVector(colMin->x - 0.5f, 0.5f + colMax->y, 0.0f); + CVector rightFrontPos = CVector(0.5f + colMax->x, 0.5f + colMax->y, 0.0f); + + leftRearPos = veh->GetMatrix() * leftRearPos; + rightRearPos = veh->GetMatrix() * rightRearPos; + leftFrontPos = veh->GetMatrix() * leftFrontPos; + rightFrontPos = veh->GetMatrix() * rightFrontPos; + + // Makes helperPos veh-ped distance vector. + helperPos -= veh->GetPosition(); + + // ?!? I think it's absurd to use this unless another function like SeekCar finds next pos. with it and we're trying to simulate it's behaviour. + // On every run it returns another pos. for ped, with same distance to the veh. + // Sequence of positions are not guaranteed, it depends on global pos. (So sometimes it returns positions to make ped draw circle, sometimes don't) + helperPos = veh->GetMatrix() * helperPos; + + float vehForwardHeading = veh->GetForward().Heading(); + + // I'm absolutely not sure about these namings. + // NTVF = needed turn if we're looking to vehicle front and wanna look to... + + float potentialLrHeading = Atan2(leftRearPos.x - helperPos.x, leftRearPos.y - helperPos.y); + float NTVF_LR = CGeneral::LimitRadianAngle(potentialLrHeading - vehForwardHeading); + + float potentialRrHeading = Atan2(rightRearPos.x - helperPos.x, rightRearPos.y - helperPos.y); + float NTVF_RR = CGeneral::LimitRadianAngle(potentialRrHeading - vehForwardHeading); + + float potentialLfHeading = Atan2(leftFrontPos.x - helperPos.x, leftFrontPos.y - helperPos.y); + float NTVF_LF = CGeneral::LimitRadianAngle(potentialLfHeading - vehForwardHeading); + + float potentialRfHeading = Atan2(rightFrontPos.x - helperPos.x, rightFrontPos.y - helperPos.y); + float NTVF_RF = CGeneral::LimitRadianAngle(potentialRfHeading - vehForwardHeading); + + bool canHeadToLr = NTVF_LR <= -PI || NTVF_LR >= -HALFPI; + + bool canHeadToRr = NTVF_RR <= HALFPI || NTVF_RR >= PI; + + bool canHeadToLf = NTVF_LF >= 0.0f || NTVF_LF <= -HALFPI; + + bool canHeadToRf = NTVF_RF <= 0.0f || NTVF_RF >= HALFPI; + + // Only order of conditions are different among enterTypes. + if (m_vehDoor == CAR_DOOR_RR) { + if (canHeadToRr) { + foundPos = rightRearPos; + foundIt = true; + } else if (canHeadToRf) { + foundPos = rightFrontPos; + foundIt = true; + } else if (canHeadToLr) { + foundPos = leftRearPos; + foundIt = true; + } else if (canHeadToLf) { + foundPos = leftFrontPos; + foundIt = true; + } + } else if(m_vehDoor == CAR_DOOR_RF) { + if (canHeadToRf) { + foundPos = rightFrontPos; + foundIt = true; + } else if (canHeadToRr) { + foundPos = rightRearPos; + foundIt = true; + } else if (canHeadToLf) { + foundPos = leftFrontPos; + foundIt = true; + } else if (canHeadToLr) { + foundPos = leftRearPos; + foundIt = true; + } + } else if (m_vehDoor == CAR_DOOR_LF) { + if (canHeadToLf) { + foundPos = leftFrontPos; + foundIt = true; + } else if (canHeadToLr) { + foundPos = leftRearPos; + foundIt = true; + } else if (canHeadToRf) { + foundPos = rightFrontPos; + foundIt = true; + } else if (canHeadToRr) { + foundPos = rightRearPos; + foundIt = true; + } + } else if (m_vehDoor == CAR_DOOR_LR) { + if (canHeadToLr) { + foundPos = leftRearPos; + foundIt = true; + } else if (canHeadToLf) { + foundPos = leftFrontPos; + foundIt = true; + } else if (canHeadToRr) { + foundPos = rightRearPos; + foundIt = true; + } else if (canHeadToRf) { + foundPos = rightFrontPos; + foundIt = true; + } + } + } + if (!foundIt) + return false; + + helperPos = GetPosition() - foundPos; + helperPos.z = 0.0f; + if (helperPos.MagnitudeSqr() <= sq(0.5f)) + return false; + + pos->x = foundPos.x; + pos->y = foundPos.y; + return true; +} + +void +CPed::SetLeader(CEntity *leader) +{ + m_leader = (CPed*)leader; + + if(m_leader) + m_leader->RegisterReference((CEntity **)&m_leader); +} + +#ifdef VC_PED_PORTS +bool +CPed::CanPedJumpThis(CEntity *unused, CVector *damageNormal) +{ + if (m_nSurfaceTouched == SURFACE_WATER) + return true; + + CVector pos = GetPosition(); + CVector forwardOffset = GetForward(); + if (damageNormal && damageNormal->z > 0.17f) { + if (damageNormal->z > 0.9f) + return false; + + CColModel *ourCol = CModelInfo::GetModelInfo(m_modelIndex)->GetColModel(); + pos.z = ourCol->spheres->center.z - ourCol->spheres->radius * damageNormal->z + pos.z; + pos.z = pos.z + 0.05f; + float collPower = damageNormal->Magnitude2D(); + if (damageNormal->z > 0.5f) { + CVector invDamageNormal(-damageNormal->x, -damageNormal->y, 0.0f); + invDamageNormal *= 1.0f / collPower; + CVector estimatedJumpDist = invDamageNormal + collPower * invDamageNormal * ourCol->spheres->radius; + forwardOffset = estimatedJumpDist * Min(2.0f / collPower, 4.0f); + } else { + forwardOffset += collPower * ourCol->spheres->radius * forwardOffset; + } + } else { + pos.z -= 0.15f; + } + + CVector forwardPos = pos + forwardOffset; + return CWorld::GetIsLineOfSightClear(pos, forwardPos, true, false, false, true, false, false, false); +} +#else +bool +CPed::CanPedJumpThis(CEntity *unused) +{ + CVector2D forward(-Sin(m_fRotationCur), Cos(m_fRotationCur)); + CVector pos = GetPosition(); + CVector forwardPos( + forward.x + pos.x, + forward.y + pos.y, + pos.z); + + return CWorld::GetIsLineOfSightClear(pos, forwardPos, true, false, false, true, false, false, false); +} +#endif + +void +CPed::SetJump(void) +{ + if (!bInVehicle && +#if defined VC_PED_PORTS || defined FIX_BUGS + m_nPedState != PED_JUMP && !RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_JUMP_LAUNCH) && +#endif + (m_nSurfaceTouched != SURFACE_STEEP_CLIFF || DotProduct(GetForward(), m_vecDamageNormal) >= 0.0f)) { + SetStoredState(); + SetPedState(PED_JUMP); + CAnimBlendAssociation *jumpAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_JUMP_LAUNCH, 8.0f); + jumpAssoc->SetFinishCallback(FinishLaunchCB, this); + m_fRotationDest = m_fRotationCur; + } +} + +void +CPed::FinishLaunchCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (ped->m_nPedState != PED_JUMP) + return; + + CVector forward(0.15f * ped->GetForward() + ped->GetPosition()); + forward.z += CModelInfo::GetColModel(ped->GetModelIndex())->spheres->center.z + 0.25f; + + CEntity *obstacle = CWorld::TestSphereAgainstWorld(forward, 0.25f, nil, true, true, false, true, false, false); + if (!obstacle) { + // Forward of forward + forward += 0.15f * ped->GetForward(); + forward.z += 0.15f; + obstacle = CWorld::TestSphereAgainstWorld(forward, 0.25f, nil, true, true, false, true, false, false); + } + + if (obstacle) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + + // ANIM_HIT_WALL in VC (which makes more sense) + CAnimBlendAssociation *handsCoverAssoc = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSCOWER, 8.0f); + handsCoverAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + handsCoverAssoc->SetFinishCallback(FinishHitHeadCB, ped); + ped->bIsLanding = true; + return; + } + + float velocityFromAnim = 0.1f; + CAnimBlendAssociation *sprintAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), ANIM_STD_RUNFAST); + + if (sprintAssoc) { + velocityFromAnim = 0.05f * sprintAssoc->blendAmount + 0.17f; + } else { + CAnimBlendAssociation *runAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), ANIM_STD_RUN); + if (runAssoc) { + velocityFromAnim = 0.07f * runAssoc->blendAmount + 0.1f; + } + } + + if (ped->IsPlayer() +#ifdef VC_PED_PORTS + || ped->m_pedInObjective && ped->m_pedInObjective->IsPlayer() +#endif + ) + ped->ApplyMoveForce(0.0f, 0.0f, 8.5f); + else + ped->ApplyMoveForce(0.0f, 0.0f, 4.5f); + + if (sq(velocityFromAnim) > ped->m_vecMoveSpeed.MagnitudeSqr2D() +#ifdef VC_PED_PORTS + || ped->m_pCurrentPhysSurface +#endif + ) { + +#ifdef FREE_CAM + if (TheCamera.Cams[0].Using3rdPersonMouseCam() && !CCamera::bFreeCam) { +#else + if (TheCamera.Cams[0].Using3rdPersonMouseCam()) { +#endif + float fpsAngle = ped->WorkOutHeadingForMovingFirstPerson(ped->m_fRotationCur); + ped->m_vecMoveSpeed.x = -velocityFromAnim * Sin(fpsAngle); + ped->m_vecMoveSpeed.y = velocityFromAnim * Cos(fpsAngle); + } else { + ped->m_vecMoveSpeed.x = -velocityFromAnim * Sin(ped->m_fRotationCur); + ped->m_vecMoveSpeed.y = velocityFromAnim * Cos(ped->m_fRotationCur); + } +#ifdef VC_PED_PORTS + if (ped->m_pCurrentPhysSurface) { + ped->m_vecMoveSpeed.x += ped->m_pCurrentPhysSurface->m_vecMoveSpeed.x; + ped->m_vecMoveSpeed.y += ped->m_pCurrentPhysSurface->m_vecMoveSpeed.y; + } +#endif + } + + ped->bIsStanding = false; + ped->bIsInTheAir = true; + animAssoc->blendDelta = -1000.0f; + CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_JUMP_GLIDE); + + if (ped->bDoBloodyFootprints) { + CVector bloodPos(0.0f, 0.0f, 0.0f); + ped->TransformToNode(bloodPos, PED_FOOTL); + + bloodPos.z -= 0.1f; + bloodPos += 0.2f * ped->GetForward(); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, + 0.26f * ped->GetForward().x, + 0.26f * ped->GetForward().y, + 0.14f * ped->GetRight().x, + 0.14f * ped->GetRight().y, + 255, 255, 0, 0, 4.0f, 3000, 1.0f); + + bloodPos = CVector(0.0f, 0.0f, 0.0f); + ped->TransformToNode(bloodPos, PED_FOOTR); + + bloodPos.z -= 0.1f; + bloodPos += 0.2f * ped->GetForward(); + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &bloodPos, + 0.26f * ped->GetForward().x, + 0.26f * ped->GetForward().y, + 0.14f * ped->GetRight().x, + 0.14f * ped->GetRight().y, + 255, 255, 0, 0, 4.0f, 3000, 1.0f); + + if (ped->m_bloodyFootprintCountOrDeathTime <= 40) { + ped->m_bloodyFootprintCountOrDeathTime = 0; + ped->bDoBloodyFootprints = false; + } else { + ped->m_bloodyFootprintCountOrDeathTime -= 40; + } + } +} + +void +CPed::FinishJumpCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + ped->bResetWalkAnims = true; + ped->bIsLanding = false; + + animAssoc->blendDelta = -1000.0f; +} + +void +CPed::FinishHitHeadCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (animAssoc) { + animAssoc->blendDelta = -4.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + + if (ped->m_nPedState == PED_JUMP) + ped->RestorePreviousState(); + + ped->bIsLanding = false; +} + +bool +CPed::CanPedDriveOff(void) +{ + if (m_nPedState != PED_DRIVING || m_lookTimer > CTimer::GetTimeInMilliseconds()) + return false; + + for (int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->m_nPedType == m_nPedType && nearPed->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER && nearPed->m_carInObjective == m_carInObjective) { + m_lookTimer = CTimer::GetTimeInMilliseconds() + 1000; + return false; + } + } + return true; +} + +// These categories are purely random, most of ped models have no correlation. So I don't think making an enum. +uint8 +CPed::GetPedRadioCategory(uint32 modelIndex) +{ + switch (modelIndex) { + case MI_MALE01: + case MI_FEMALE03: + case MI_PROSTITUTE2: + case MI_WORKER1: + case MI_MOD_MAN: + case MI_MOD_WOM: + case MI_ST_WOM: + case MI_FAN_WOM: + return 3; + case MI_TAXI_D: + case MI_PIMP: + case MI_MALE02: + case MI_FEMALE02: + case MI_FATFEMALE01: + case MI_FATFEMALE02: + case MI_DOCKER1: + case MI_WORKER2: + case MI_FAN_MAN2: + return 9; + case MI_GANG01: + case MI_GANG02: + case MI_SCUM_MAN: + case MI_SCUM_WOM: + case MI_HOS_WOM: + case MI_CONST1: + return 1; + case MI_GANG03: + case MI_GANG04: + case MI_GANG07: + case MI_GANG08: + case MI_CT_MAN2: + case MI_CT_WOM2: + case MI_B_MAN3: + case MI_SHOPPER3: + return 4; + case MI_GANG05: + case MI_GANG06: + case MI_GANG11: + case MI_GANG12: + case MI_CRIMINAL02: + case MI_B_WOM2: + case MI_ST_MAN: + case MI_HOS_MAN: + return 5; + case MI_FATMALE01: + case MI_LI_MAN2: + case MI_SHOPPER1: + case MI_CAS_MAN: + return 6; + case MI_PROSTITUTE: + case MI_P_WOM2: + case MI_LI_WOM2: + case MI_B_WOM3: + case MI_CAS_WOM: + return 2; + case MI_P_WOM1: + case MI_DOCKER2: + case MI_STUD_MAN: + return 7; + case MI_CT_MAN1: + case MI_CT_WOM1: + case MI_LI_MAN1: + case MI_LI_WOM1: + case MI_B_MAN1: + case MI_B_MAN2: + case MI_B_WOM1: + case MI_SHOPPER2: + case MI_STUD_WOM: + return 8; + default: + return 0; + } +} + +void +CPed::SetRadioStation(void) +{ + static const uint8 radiosPerRadioCategories[10][4] = { + {JAH_RADIO, RISE_FM, GAME_FM, MSX_FM}, + {HEAD_RADIO, DOUBLE_CLEF, LIPS_106, FLASHBACK}, + {RISE_FM, GAME_FM, MSX_FM, FLASHBACK}, + {HEAD_RADIO, RISE_FM, LIPS_106, MSX_FM}, + {HEAD_RADIO, RISE_FM, MSX_FM, FLASHBACK}, + {JAH_RADIO, RISE_FM, LIPS_106, FLASHBACK}, + {HEAD_RADIO, RISE_FM, LIPS_106, FLASHBACK}, + {HEAD_RADIO, JAH_RADIO, LIPS_106, FLASHBACK}, + {HEAD_RADIO, DOUBLE_CLEF, LIPS_106, FLASHBACK}, + {CHATTERBOX, HEAD_RADIO, LIPS_106, GAME_FM} + }; + uint8 orderInCat = 0; // BUG: this wasn't initialized + + if (IsPlayer() || !m_pMyVehicle || m_pMyVehicle->pDriver != this) + return; + + uint8 category = GetPedRadioCategory(GetModelIndex()); + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (CGeneral::GetRandomNumber() & 15) { + for (orderInCat = 0; orderInCat < 4; orderInCat++) { + if (m_pMyVehicle->m_nRadioStation == radiosPerRadioCategories[category][orderInCat]) + break; + } + } else { + m_pMyVehicle->m_nRadioStation = USERTRACK; + } + } else { + for (orderInCat = 0; orderInCat < 4; orderInCat++) { + if (m_pMyVehicle->m_nRadioStation == radiosPerRadioCategories[category][orderInCat]) + break; + } + } + if (orderInCat == 4) { + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (CGeneral::GetRandomNumber() & 15) + m_pMyVehicle->m_nRadioStation = radiosPerRadioCategories[category][CGeneral::GetRandomNumber() & 3]; + else + m_pMyVehicle->m_nRadioStation = USERTRACK; + } else { + m_pMyVehicle->m_nRadioStation = radiosPerRadioCategories[category][CGeneral::GetRandomNumber() & 3]; + } + } +} + +void +CPed::WarpPedIntoCar(CVehicle *car) +{ + bInVehicle = true; + m_pMyVehicle = car; + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_carInObjective = car; + m_carInObjective->RegisterReference((CEntity **) &m_carInObjective); + SetPedState(PED_DRIVING); + bUsesCollision = false; + bIsInTheAir = false; + bVehExitWillBeInstant = true; + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + car->SetDriver(this); + car->pDriver->RegisterReference((CEntity **) &car->pDriver); + + } else if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + for (int i = 0; i < 4; i++) { + if (!car->pPassengers[i]) { + car->pPassengers[i] = this; + car->pPassengers[i]->RegisterReference((CEntity **) &car->pPassengers[i]); + break; + } + } + } else + return; + + if (IsPlayer()) { + car->SetStatus(STATUS_PLAYER); + AudioManager.PlayerJustGotInCar(); + CCarCtrl::RegisterVehicleOfInterest(car); + } else { + car->SetStatus(STATUS_PHYSICS); + } + + CWorld::Remove(this); + SetPosition(car->GetPosition()); + CWorld::Add(this); + + if (car->bIsAmbulanceOnDuty) { + car->bIsAmbulanceOnDuty = false; + --CCarCtrl::NumAmbulancesOnDuty; + } + if (car->bIsFireTruckOnDuty) { + car->bIsFireTruckOnDuty = false; + --CCarCtrl::NumFiretrucksOnDuty; + } + if (!car->bEngineOn) { + car->bEngineOn = true; + DMAudio.PlayOneShot(car->m_audioEntityId, SOUND_CAR_ENGINE_START, 1.0f); + } + +#ifdef VC_PED_PORTS + RpAnimBlendClumpSetBlendDeltas(GetClump(), ASSOC_PARTIAL, -1000.0f); + + // VC uses AddInCarAnims but we don't have that + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, car->GetDriverAnim(), 100.0f); + RemoveWeaponWhenEnteringVehicle(); +#else + if (car->IsBoat()) { +#ifndef FIX_BUGS + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_BOAT_DRIVE, 100.0f); +#else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, car->GetDriverAnim(), 100.0f); +#endif + CWeaponInfo *ourWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(ourWeapon->m_nModelId); + } else { + // Because we can use Uzi for drive by + RemoveWeaponWhenEnteringVehicle(); + + if (car->bLowVehicle) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_LO, 100.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT, 100.0f); + } +#endif + + StopNonPartialAnims(); + if (car->bIsBus) + bRenderPedInCar = false; + + bChangedSeat = true; +} + + +#ifdef PEDS_REPORT_CRIMES_ON_PHONE +// returns event id, parameter is optional +int32 +CPed::CheckForPlayerCrimes(CPed *victim) +{ + int i; + float dist; + float mindist = 60.0f; + CPlayerPed *player = FindPlayerPed(); + int32 victimRef = (victim ? CPools::GetPedRef(victim) : 0); + int event = -1; + + for (i = 0; i < NUMEVENTS; i++) { + if (gaEvent[i].type == EVENT_NULL || gaEvent[i].type > EVENT_CAR_SET_ON_FIRE) + continue; + + // those are already handled in game, also DEAD_PED isn't registered alone, most of the time there is SHOOT_PED etc. + if (gaEvent[i].type == EVENT_DEAD_PED || gaEvent[i].type == EVENT_GUNSHOT || gaEvent[i].type == EVENT_EXPLOSION) + continue; + + if (victim && gaEvent[i].entityRef != victimRef) + continue; + + if (gaEvent[i].criminal != player) + continue; + + dist = (GetPosition() - gaEvent[i].posn).Magnitude(); + if (dist < mindist) { + mindist = dist; + event = i; + } + } + + if (event != -1) { + if (victim) { + m_victimOfPlayerCrime = victim; + } else { + switch (gaEvent[event].entityType) { + case EVENT_ENTITY_PED: + m_victimOfPlayerCrime = CPools::GetPed(gaEvent[event].entityRef); + break; + case EVENT_ENTITY_VEHICLE: + m_victimOfPlayerCrime = CPools::GetVehicle(gaEvent[event].entityRef); + break; + case EVENT_ENTITY_OBJECT: + m_victimOfPlayerCrime = CPools::GetObject(gaEvent[event].entityRef); + break; + default: + break; + } + } + } + + return event; +} +#endif + +#ifdef PED_SKIN +static RpMaterial* +SetLimbAlphaCB(RpMaterial *material, void *data) +{ + ((RwRGBA*)RpMaterialGetColor(material))->alpha = *(uint8*)data; + return material; +} + +void +CPed::renderLimb(int node) +{ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID); + RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + CPedModelInfo *mi = (CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()); + RpAtomic *atomic; + switch(node){ + case PED_HEAD: + atomic = mi->getHead(); + break; + case PED_HANDL: + atomic = mi->getLeftHand(); + break; + case PED_HANDR: + atomic = mi->getRightHand(); + break; + default: + return; + } + if(atomic == nil) + return; + + RwFrame *frame = RpAtomicGetFrame(atomic); + *RwFrameGetMatrix(frame) = *mat; + RwFrameUpdateObjects(frame); + int alpha = CVisibilityPlugins::GetClumpAlpha(GetClump()); + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), SetLimbAlphaCB, &alpha); + RpAtomicRender(atomic); +} +#endif + +#ifdef COMPATIBLE_SAVES +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +void +CPed::Save(uint8*& buf) +{ + ZeroSaveBuf(buf, 52); + CopyToBuf(buf, GetPosition().x); + CopyToBuf(buf, GetPosition().y); + CopyToBuf(buf, GetPosition().z); + ZeroSaveBuf(buf, 288); + CopyToBuf(buf, CharCreatedBy); + ZeroSaveBuf(buf, 351); + CopyToBuf(buf, m_fHealth); + CopyToBuf(buf, m_fArmour); + ZeroSaveBuf(buf, 148); + for (int i = 0; i < 13; i++) // has to be hardcoded + m_weapons[i].Save(buf); + ZeroSaveBuf(buf, 5); + CopyToBuf(buf, m_maxWeaponTypeAllowed); + ZeroSaveBuf(buf, 162); +} + +void +CPed::Load(uint8*& buf) +{ + SkipSaveBuf(buf, 52); + CopyFromBuf(buf, GetMatrix().GetPosition().x); + CopyFromBuf(buf, GetMatrix().GetPosition().y); + CopyFromBuf(buf, GetMatrix().GetPosition().z); + SkipSaveBuf(buf, 288); + CopyFromBuf(buf, CharCreatedBy); + SkipSaveBuf(buf, 351); + CopyFromBuf(buf, m_fHealth); + CopyFromBuf(buf, m_fArmour); + SkipSaveBuf(buf, 148); + for (int i = 0; i < 13; i++) // has to be hardcoded + m_weapons[i].Load(buf); + SkipSaveBuf(buf, 5); + CopyFromBuf(buf, m_maxWeaponTypeAllowed); + SkipSaveBuf(buf, 162); +} +#undef CopyFromBuf +#undef CopyToBuf +#endif diff --git a/src/peds/Ped.h b/src/peds/Ped.h new file mode 100644 index 0000000..9cd85ef --- /dev/null +++ b/src/peds/Ped.h @@ -0,0 +1,962 @@ +#pragma once + +#include "RwHelper.h" +#include "AnimManager.h" +#include "Crime.h" +#include "EventList.h" +#include "PedIK.h" +#include "PedType.h" +#include "Physical.h" +#include "Weapon.h" +#include "WeaponInfo.h" +#include "Collision.h" + +#define FEET_OFFSET 1.04f +#define CHECK_NEARBY_THINGS_MAX_DIST 15.0f +#define ENTER_CAR_MAX_DIST 30.0f +#define CAN_SEE_ENTITY_ANGLE_THRESHOLD DEGTORAD(60.0f) + +struct CPathNode; +class CAccident; +class CObject; +class CFire; +struct AnimBlendFrameData; +class CAnimBlendAssociation; + +struct PedAudioData +{ + int m_nFixedDelayTime; + int m_nOverrideFixedDelayTime; + int m_nOverrideMaxRandomDelayTime; + int m_nMaxRandomDelayTime; +}; + +enum eFormation +{ + FORMATION_UNDEFINED, + FORMATION_REAR, + FORMATION_REAR_LEFT, + FORMATION_REAR_RIGHT, + FORMATION_FRONT_LEFT, + FORMATION_FRONT_RIGHT, + FORMATION_LEFT, + FORMATION_RIGHT, + FORMATION_FRONT +}; + +enum FightState { + FIGHTSTATE_MOVE_FINISHED = -2, + FIGHTSTATE_JUST_ATTACKED, + FIGHTSTATE_NO_MOVE, + FIGHTSTATE_1 +}; + +enum +{ + ENDFIGHT_NORMAL, + ENDFIGHT_WITH_A_STEP, + ENDFIGHT_FAST +}; + +enum PedRouteType +{ + PEDROUTE_STOP_WHEN_DONE = 1, + PEDROUTE_GO_BACKWARD_WHEN_DONE, + PEDROUTE_GO_TO_START_WHEN_DONE +}; + +enum FightMoveHitLevel +{ + HITLEVEL_NULL, + HITLEVEL_GROUND, + HITLEVEL_LOW, + HITLEVEL_MEDIUM, + HITLEVEL_HIGH +}; + +struct FightMove +{ + AnimationId animId; + float startFireTime; + float endFireTime; + float comboFollowOnTime; + float strikeRadius; + uint8 hitLevel; // FightMoveHitLevel + uint8 damage; + uint8 flags; +}; +VALIDATE_SIZE(FightMove, 0x18); + +// TODO: This is eFightState on mobile. +enum PedFightMoves +{ + FIGHTMOVE_NULL, + // Attacker + FIGHTMOVE_STDPUNCH, + FIGHTMOVE_IDLE, + FIGHTMOVE_SHUFFLE_F, + FIGHTMOVE_KNEE, + FIGHTMOVE_HEADBUTT, + FIGHTMOVE_PUNCHJAB, + FIGHTMOVE_PUNCHHOOK, + FIGHTMOVE_KICK, + FIGHTMOVE_LONGKICK, + FIGHTMOVE_ROUNDHOUSE, + FIGHTMOVE_BODYBLOW, + FIGHTMOVE_GROUNDKICK, + // Opponent + FIGHTMOVE_HITFRONT, + FIGHTMOVE_HITBACK, + FIGHTMOVE_HITRIGHT, + FIGHTMOVE_HITLEFT, + FIGHTMOVE_HITBODY, + FIGHTMOVE_HITCHEST, + FIGHTMOVE_HITHEAD, + FIGHTMOVE_HITBIGSTEP, + FIGHTMOVE_HITONFLOOR, + FIGHTMOVE_HITBEHIND, + FIGHTMOVE_IDLE2NORM, + NUM_FIGHTMOVES +}; + +enum ePedPieceTypes +{ + PEDPIECE_TORSO, + PEDPIECE_MID, + PEDPIECE_LEFTARM, + PEDPIECE_RIGHTARM, + PEDPIECE_LEFTLEG, + PEDPIECE_RIGHTLEG, + PEDPIECE_HEAD, +}; + +enum eWaitState { + WAITSTATE_FALSE, + WAITSTATE_TRAFFIC_LIGHTS, + WAITSTATE_CROSS_ROAD, + WAITSTATE_CROSS_ROAD_LOOK, + WAITSTATE_LOOK_PED, + WAITSTATE_LOOK_SHOP, + WAITSTATE_LOOK_ACCIDENT, + WAITSTATE_FACEOFF_GANG, + WAITSTATE_DOUBLEBACK, + WAITSTATE_HITWALL, + WAITSTATE_TURN180, + WAITSTATE_SURPRISE, + WAITSTATE_STUCK, + WAITSTATE_LOOK_ABOUT, + WAITSTATE_PLAYANIM_DUCK, + WAITSTATE_PLAYANIM_COWER, + WAITSTATE_PLAYANIM_TAXI, + WAITSTATE_PLAYANIM_HANDSUP, + WAITSTATE_PLAYANIM_HANDSCOWER, + WAITSTATE_PLAYANIM_CHAT, + WAITSTATE_FINISH_FLEE +}; + +enum eObjective { + OBJECTIVE_NONE, + OBJECTIVE_WAIT_ON_FOOT, + OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE, + OBJECTIVE_GUARD_SPOT, + OBJECTIVE_GUARD_AREA, // not implemented + OBJECTIVE_WAIT_IN_CAR, + OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT, + OBJECTIVE_KILL_CHAR_ON_FOOT, + OBJECTIVE_KILL_CHAR_ANY_MEANS, + OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, + OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, + OBJECTIVE_GOTO_CHAR_ON_FOOT, + OBJECTIVE_FOLLOW_CHAR_IN_FORMATION, + OBJECTIVE_LEAVE_CAR, + OBJECTIVE_ENTER_CAR_AS_PASSENGER, + OBJECTIVE_ENTER_CAR_AS_DRIVER, + OBJECTIVE_FOLLOW_CAR_IN_CAR, // not implemented + OBJECTIVE_FIRE_AT_OBJECT_FROM_VEHICLE, // not implemented + OBJECTIVE_DESTROY_OBJECT, // not implemented + OBJECTIVE_DESTROY_CAR, + OBJECTIVE_GOTO_AREA_ANY_MEANS, + OBJECTIVE_GOTO_AREA_ON_FOOT, + OBJECTIVE_RUN_TO_AREA, + OBJECTIVE_GOTO_AREA_IN_CAR, // not implemented + OBJECTIVE_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, // not implemented + OBJECTIVE_GUARD_ATTACK, + OBJECTIVE_SET_LEADER, + OBJECTIVE_FOLLOW_ROUTE, + OBJECTIVE_SOLICIT_VEHICLE, + OBJECTIVE_HAIL_TAXI, + OBJECTIVE_CATCH_TRAIN, + OBJECTIVE_BUY_ICE_CREAM, + OBJECTIVE_STEAL_ANY_CAR, + OBJECTIVE_MUG_CHAR, + OBJECTIVE_FLEE_CAR, +#ifdef VC_PED_PORTS + OBJECTIVE_LEAVE_CAR_AND_DIE +#endif +}; + +enum { + RANDOM_CHAR = 1, + MISSION_CHAR, +}; + +enum PedLineUpPhase { + LINE_UP_TO_CAR_START, + LINE_UP_TO_CAR_END, + LINE_UP_TO_CAR_2 // Buggy. Used for cops arresting you from passenger door +}; + +enum PedOnGroundState { + NO_PED, + PED_IN_FRONT_OF_ATTACKER, + PED_ON_THE_FLOOR, + PED_DEAD_ON_THE_FLOOR +}; + +enum PointBlankNecessity { + NO_POINT_BLANK_PED, + POINT_BLANK_FOR_WANTED_PED, + POINT_BLANK_FOR_SOMEONE_ELSE +}; + +enum PedState +{ + PED_NONE, + PED_IDLE, + PED_LOOK_ENTITY, + PED_LOOK_HEADING, + PED_WANDER_RANGE, + PED_WANDER_PATH, + PED_SEEK_POS, + PED_SEEK_ENTITY, + PED_FLEE_POS, + PED_FLEE_ENTITY, + PED_PURSUE, + PED_FOLLOW_PATH, + PED_SNIPER_MODE, + PED_ROCKET_MODE, + PED_DUMMY, + PED_PAUSE, + PED_ATTACK, + PED_FIGHT, + PED_FACE_PHONE, + PED_MAKE_CALL, + PED_CHAT, + PED_MUG, + PED_AIM_GUN, + PED_AI_CONTROL, + PED_SEEK_CAR, + PED_SEEK_IN_BOAT, + PED_FOLLOW_ROUTE, + PED_CPR, + PED_SOLICIT, + PED_BUY_ICECREAM, + PED_INVESTIGATE, + PED_STEP_AWAY, + PED_ON_FIRE, + + PED_UNKNOWN, // Same with IDLE, but also infects up to 5 peds with same pedType and WANDER_PATH, so they become stone too. HANG_OUT in Fire_Head's idb + + PED_STATES_NO_AI, + + // One of these states isn't on PS2 - start + PED_JUMP, + PED_FALL, + PED_GETUP, + PED_STAGGER, + PED_DIVE_AWAY, + + PED_STATES_NO_ST, + PED_ENTER_TRAIN, + PED_EXIT_TRAIN, + PED_ARREST_PLAYER, + // One of these states isn't on PS2 - end + + PED_DRIVING, + PED_PASSENGER, + PED_TAXI_PASSENGER, + PED_OPEN_DOOR, + PED_DIE, + PED_DEAD, + PED_CARJACK, + PED_DRAG_FROM_CAR, + PED_ENTER_CAR, + PED_STEAL_CAR, + PED_EXIT_CAR, + PED_HANDS_UP, + PED_ARRESTED, +}; + +enum eMoveState { + PEDMOVE_NONE, + PEDMOVE_STILL, + PEDMOVE_WALK, + PEDMOVE_RUN, + PEDMOVE_SPRINT, +}; + +class CVehicle; + +class CPed : public CPhysical +{ +public: + // 0x128 + CStoredCollPoly m_collPoly; + float m_fCollisionSpeed; + + // cf. https://github.com/DK22Pac/plugin-sdk/blob/master/plugin_sa/game_sa/CPed.h from R* + uint32 bIsStanding : 1; + uint32 bWasStanding : 1; + uint32 bIsAttacking : 1; // doesn't reset after fist fight + uint32 bIsPointingGunAt : 1; + uint32 bIsLooking : 1; + uint32 bKeepTryingToLook : 1; // if we can't look somewhere due to unreachable angles + uint32 bIsRestoringLook : 1; + uint32 bIsAimingGun : 1; + + uint32 bIsRestoringGun : 1; + uint32 bCanPointGunAtTarget : 1; + uint32 bIsTalking : 1; + uint32 bIsInTheAir : 1; + uint32 bIsLanding : 1; + uint32 bIsRunning : 1; // on some conditions + uint32 bHitSomethingLastFrame : 1; + uint32 bVehEnterDoorIsBlocked : 1; // because someone else enters/exits from there + + uint32 bCanPedEnterSeekedCar : 1; + uint32 bRespondsToThreats : 1; + uint32 bRenderPedInCar : 1; + uint32 bChangedSeat : 1; + uint32 bUpdateAnimHeading : 1; + uint32 bBodyPartJustCameOff : 1; + uint32 bIsShooting : 1; + uint32 bFindNewNodeAfterStateRestore : 1; + + uint32 bHasACamera : 1; // does ped possess a camera to document accidents involves fire/explosion + uint32 bGonnaInvestigateEvent : 1; + uint32 bPedIsBleeding : 1; + uint32 bStopAndShoot : 1; // Ped cannot reach target to attack with fist, need to use gun + uint32 bIsPedDieAnimPlaying : 1; + uint32 bUsePedNodeSeek : 1; + uint32 bObjectiveCompleted : 1; + uint32 bScriptObjectiveCompleted : 1; + + uint32 bKindaStayInSamePlace : 1; + uint32 bBeingChasedByPolice : 1; // Unused VC leftover. Should've been set for criminal/gang members + uint32 bNotAllowedToDuck : 1; + uint32 bCrouchWhenShooting : 1; + uint32 bIsDucking : 1; + uint32 bGetUpAnimStarted : 1; + uint32 bDoBloodyFootprints : 1; + uint32 bFleeAfterExitingCar : 1; + + uint32 bWanderPathAfterExitingCar : 1; + uint32 bIsLeader : 1; + uint32 bDontDragMeOutCar : 1; // unfinished feature + uint32 m_ped_flagF8 : 1; + uint32 bWillBeQuickJacked : 1; + uint32 bCancelEnteringCar : 1; // after door is opened or couldn't be opened due to it's locked + uint32 bObstacleShowedUpDuringKillObjective : 1; + uint32 bDuckAndCover : 1; + + uint32 bStillOnValidPoly : 1; // set if the polygon the ped is on is still valid for collision + uint32 bAllowMedicsToReviveMe : 1; + uint32 bResetWalkAnims : 1; + uint32 bStartWanderPathOnFoot : 1; // exits the car if he's in it, reset after path found + uint32 bOnBoat : 1; // not just driver, may be just standing + uint32 bBusJacked : 1; + uint32 bGonnaKillTheCarJacker : 1; // only set when car is jacked from right door and when arrested by police + uint32 bFadeOut : 1; + + uint32 bKnockedUpIntoAir : 1; // has ped been knocked up into the air by a car collision + uint32 bHitSteepSlope : 1; // has ped collided/is standing on a steep slope (surface type) + uint32 bCullExtraFarAway : 1; // special ped only gets culled if it's extra far away (for roadblocks) + uint32 bClearObjective : 1; + uint32 bTryingToReachDryLand : 1; // has ped just exited boat and trying to get to dry land + uint32 bCollidedWithMyVehicle : 1; + uint32 bRichFromMugging : 1; // ped has lots of cash cause they've been mugging people + uint32 bChrisCriminal : 1; // Is a criminal as killed during Chris' police mission (should be counted as such) + + uint32 bShakeFist : 1; // test shake hand at look entity + uint32 bNoCriticalHits : 1; // if set, limbs won't came off + uint32 bVehExitWillBeInstant : 1; + uint32 bHasAlreadyBeenRecorded : 1; + uint32 bFallenDown : 1; +#ifdef VC_PED_PORTS + uint32 bSomeVCflag1 : 1; +#endif +#ifdef PED_SKIN + uint32 bDontAcceptIKLookAts : 1; // TODO: find uses of this +#endif + uint32 m_ped_flagI40 : 1; + uint32 m_ped_flagI80 : 1; // originally unused, KANGAROO_CHEAT define makes use of this as cheat toggle + + uint8 CharCreatedBy; + eObjective m_objective; + eObjective m_prevObjective; + CPed *m_pedInObjective; + CVehicle *m_carInObjective; + CVector m_nextRoutePointPos; + CPed *m_leader; + eFormation m_pedFormation; + uint32 m_fearFlags; + CEntity *m_threatEntity; + CVector2D m_eventOrThreat; + uint32 m_eventType; + CEntity* m_pEventEntity; + float m_fAngleToEvent; + AnimBlendFrameData *m_pFrames[PED_NODE_MAX]; +#ifdef PED_SKIN + // stored inside the clump with non-skin ped + RpAtomic *m_pWeaponModel; +#endif + AssocGroupId m_animGroup; + CAnimBlendAssociation *m_pVehicleAnim; + CVector2D m_vecAnimMoveDelta; + CVector m_vecOffsetSeek; + CPedIK m_pedIK; + float m_actionX; + float m_actionY; + uint32 m_nPedStateTimer; + PedState m_nPedState; + PedState m_nLastPedState; + eMoveState m_nMoveState; + int32 m_nStoredMoveState; + int32 m_nPrevMoveState; + eWaitState m_nWaitState; + uint32 m_nWaitTimer; + void *m_pPathNodesStates[8]; // unused, probably leftover from VC + CVector2D m_stPathNodeStates[10]; + uint16 m_nPathNodes; + int16 m_nCurPathNode; + int8 m_nPathDir; +public: + CPathNode *m_pLastPathNode; + CPathNode *m_pNextPathNode; + float m_fHealth; + float m_fArmour; + int16 m_routeLastPoint; + uint16 m_routeStartPoint; + int16 m_routePointsPassed; + int16 m_routeType; // See PedRouteType + int16 m_routePointsBeingPassed; + CVector2D m_moved; + float m_fRotationCur; + float m_fRotationDest; + float m_headingRate; + uint16 m_vehDoor; + int16 m_walkAroundType; + CPhysical *m_pCurrentPhysSurface; + CVector m_vecOffsetFromPhysSurface; + CEntity *m_pCurSurface; + CVector m_vecSeekPos; + CEntity *m_pSeekTarget; + CVehicle *m_pMyVehicle; + bool bInVehicle; + float m_distanceToCountSeekDone; + bool bRunningToPhone; + int16 m_phoneId; +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + bool m_facePhoneStart; + CEntity *m_victimOfPlayerCrime; +#endif + eCrimeType m_crimeToReportOnPhone; + uint32 m_phoneTalkTimer; + CAccident *m_lastAccident; + uint32 m_nPedType; + CPedStats *m_pedStats; + float m_fleeFromPosX; + float m_fleeFromPosY; + CEntity *m_fleeFrom; + uint32 m_fleeTimer; + CEntity* m_collidingEntityWhileFleeing; + uint32 m_collidingThingTimer; + CEntity *m_pCollidingEntity; + uint8 m_stateUnused; + uint32 m_timerUnused; + class CRange2D *m_wanderRangeBounds; + CWeapon m_weapons[WEAPONTYPE_TOTAL_INVENTORY_WEAPONS]; + eWeaponType m_storedWeapon; + uint8 m_currentWeapon; // eWeaponType + uint8 m_maxWeaponTypeAllowed; // eWeaponType + uint8 m_wepSkills; + uint8 m_wepAccuracy; + CEntity *m_pPointGunAt; + CVector m_vecHitLastPos; + uint32 m_curFightMove; + uint8 m_fightButtonPressure; + int8 m_fightState; + bool m_takeAStepAfterAttack; + CFire *m_pFire; + CEntity *m_pLookTarget; + float m_fLookDirection; + int32 m_wepModelID; + uint32 m_leaveCarTimer; + uint32 m_getUpTimer; + uint32 m_lookTimer; + uint32 m_chatTimer; + uint32 m_attackTimer; + uint32 m_shootTimer; // shooting is a part of attack + uint32 m_carJackTimer; + uint32 m_objectiveTimer; + uint32 m_duckTimer; + uint32 m_duckAndCoverTimer; + uint32 m_bloodyFootprintCountOrDeathTime; // Death time when bDoBloodyFootprints is false. Weird decision + uint8 m_panicCounter; + bool m_deadBleeding; + int8 m_bodyPartBleeding; // PedNode, but -1 if there isn't + CPed *m_nearPeds[10]; + uint16 m_numNearPeds; + int8 m_lastWepDam; + uint32 m_lastSoundStart; + uint32 m_soundStart; + uint16 m_lastQueuedSound; + uint16 m_queuedSound; + CVector m_vecSeekPosEx; // used for OBJECTIVE_GUARD_SPOT + float m_distanceToCountSeekDoneEx; // used for OBJECTIVE_GUARD_SPOT + + static void *operator new(size_t) throw(); + static void *operator new(size_t, int) throw(); + static void operator delete(void*, size_t) throw(); + static void operator delete(void*, int) throw(); + + CPed(uint32 pedType); + ~CPed(void); + + void SetModelIndex(uint32 mi); + void ProcessControl(void); + void Teleport(CVector); + void PreRender(void); + void Render(void); + bool SetupLighting(void); + void RemoveLighting(bool); + void FlagToDestroyWhenNextProcessed(void); + int32 ProcessEntityCollision(CEntity*, CColPoint*); + + virtual void SetMoveAnim(void); + + void AddWeaponModel(int id); + void AimGun(void); + void KillPedWithCar(CVehicle *veh, float impulse); + void Say(uint16 audio); + void SetLookFlag(CEntity *target, bool keepTryingToLook); + void SetLookFlag(float direction, bool keepTryingToLook); + void SetLookTimer(int time); + void SetDie(AnimationId anim, float arg1, float arg2); + void SetDead(void); + void ApplyHeadShot(eWeaponType weaponType, CVector pos, bool evenOnPlayer); + void RemoveBodyPart(PedNode nodeId, int8 direction); + bool OurPedCanSeeThisOne(CEntity *target); + void Avoid(void); + void Attack(void); + void ClearAimFlag(void); + void ClearLookFlag(void); + void RestorePreviousState(void); + void ClearAttack(void); + bool IsPedHeadAbovePos(float zOffset); + void RemoveWeaponModel(int modelId); + void SetCurrentWeapon(uint32 weaponType); + void Duck(void); + void ClearDuck(void); + void ClearPointGunAt(void); + void BeingDraggedFromCar(void); + void RestartNonPartialAnims(void); + void LineUpPedWithCar(PedLineUpPhase phase); + void SetPedPositionInCar(void); + void PlayFootSteps(void); + void QuitEnteringCar(void); + void BuildPedLists(void); + uint32 GiveWeapon(eWeaponType weaponType, uint32 ammo); + void CalculateNewOrientation(void); + float WorkOutHeadingForMovingFirstPerson(float); + void CalculateNewVelocity(void); + bool CanSeeEntity(CEntity*, float threshold = CAN_SEE_ENTITY_ANGLE_THRESHOLD); + void RestorePreviousObjective(void); + void SetIdle(void); +#ifdef _MSC_VER +#if _MSC_VER >= 1920 && _MSC_VER < 1929 + __declspec(noinline) // workaround for a compiler bug, hooray MS :P +#endif +#endif + void SetObjective(eObjective, void*); + void SetObjective(eObjective); + void SetObjective(eObjective, int16, int16); + void SetObjective(eObjective, CVector); + void SetObjective(eObjective, CVector, float); + void ClearChat(void); + void InformMyGangOfAttack(CEntity*); + void ReactToAttack(CEntity*); + void SetDuck(uint32); + void RegisterThreatWithGangPeds(CEntity*); + bool TurnBody(void); + void Chat(void); + void CheckAroundForPossibleCollisions(void); + void SetSeek(CVector, float); + void SetSeek(CEntity*, float); + bool MakePhonecall(void); + bool FacePhone(void); + CPed *CheckForDeadPeds(void); +#ifdef PEDS_REPORT_CRIMES_ON_PHONE + int32 CheckForPlayerCrimes(CPed *victim = nil); +#endif + bool CheckForExplosions(CVector2D &area); + CPed *CheckForGunShots(void); + uint8 CheckForPointBlankPeds(CPed*); + bool CheckIfInTheAir(void); + void ClearAll(void); + void SetPointGunAt(CEntity*); + bool Seek(void); + bool SetWanderPath(int8); + bool SetFollowPath(CVector); + void ClearAttackByRemovingAnim(void); + void SetStoredState(void); + void StopNonPartialAnims(void); + bool InflictDamage(CEntity*, eWeaponType, float, ePedPieceTypes, uint8); + void ClearFlee(void); + void ClearFall(void); + void SetGetUp(void); + void ClearInvestigateEvent(void); + void ClearLeader(void); + void ClearLook(void); + void ClearObjective(void); + void ClearPause(void); + void ClearSeek(void); + void ClearWeapons(void); + void RestoreGunPosition(void); + void RestoreHeadingRate(void); + void SetAimFlag(CEntity* to); + void SetAimFlag(float angle); + void SetAmmo(eWeaponType weaponType, uint32 ammo); + void SetEvasiveStep(CPhysical*, uint8); + void GrantAmmo(eWeaponType, uint32); + void SetEvasiveDive(CPhysical*, uint8); + void SetAttack(CEntity*); + void StartFightAttack(uint8); + void SetWaitState(eWaitState, void*); + bool FightStrike(CVector&); + int GetLocalDirection(const CVector2D &); + void StartFightDefend(uint8, uint8, uint8); + void PlayHitSound(CPed*); + void SetFall(int, AnimationId, uint8); + void SetFlee(CEntity*, int); + void SetFlee(CVector2D const &, int); + void RemoveInCarAnims(void); + void CollideWithPed(CPed*); + void SetDirectionToWalkAroundObject(CEntity*); + void CreateDeadPedMoney(void); + void CreateDeadPedWeaponPickups(void); + void SetAttackTimer(uint32); + void SetBeingDraggedFromCar(CVehicle*, uint32, bool); + void SetRadioStation(void); + void SetBuyIceCream(void); + void SetChat(CEntity*, uint32); + void DeadPedMakesTyresBloody(void); + void MakeTyresMuddySectorList(CPtrList&); + uint8 DoesLOSBulletHitPed(CColPoint &point); + bool DuckAndCover(void); + void EndFight(uint8); + void EnterCar(void); + uint8 GetNearestTrainPedPosition(CVehicle*, CVector&); + uint8 GetNearestTrainDoor(CVehicle*, CVector&); + void LineUpPedWithTrain(void); + void ExitCar(void); + void Fight(void); + bool FindBestCoordsFromNodes(CVector, CVector*); + void Wait(void); + void ProcessObjective(void); + bool SeekFollowingPath(CVector*); + void Flee(void); + void FollowPath(void); + CVector GetFormationPosition(void); + void GetNearestDoor(CVehicle*, CVector&); + bool GetNearestPassengerDoor(CVehicle*, CVector&); + int GetNextPointOnRoute(void); + uint8 GetPedRadioCategory(uint32); + int GetWeaponSlot(eWeaponType); + void GoToNearestDoor(CVehicle*); + bool HaveReachedNextPointOnRoute(float); + void Idle(void); + void InTheAir(void); + void SetLanding(void); + void InvestigateEvent(void); + bool IsPedDoingDriveByShooting(void); + bool IsRoomToBeCarJacked(void); + void SetInvestigateEvent(eEventType, CVector2D, float, uint16, float); + bool LookForInterestingNodes(void); + void LookForSexyCars(void); + void LookForSexyPeds(void); + void Mug(void); + void MoveHeadToLook(void); + void Pause(void); + void ProcessBuoyancy(void); + void ServiceTalking(void); + void SetJump(void); + void WanderPath(void); + void ReactToPointGun(CEntity*); + void SeekCar(void); + bool PositionPedOutOfCollision(void); + bool RunToReportCrime(eCrimeType); + bool PlacePedOnDryLand(void); + bool PossiblyFindBetterPosToSeekCar(CVector*, CVehicle*); + void UpdateFromLeader(void); + uint32 ScanForThreats(void); + void SetEnterCar(CVehicle*, uint32); + bool WarpPedToNearEntityOffScreen(CEntity*); + void SetExitCar(CVehicle*, uint32); + void SetFormation(eFormation); + bool WillChat(CPed*); + void SetEnterTrain(CVehicle*, uint32); + void SetEnterCar_AllClear(CVehicle*, uint32, uint32); + void SetSolicit(uint32 time); + void ScanForInterestingStuff(void); + void WarpPedIntoCar(CVehicle*); + void SetCarJack(CVehicle*); + bool WarpPedToNearLeaderOffScreen(void); + void Solicit(void); + void SetExitBoat(CVehicle*); + + // Static methods + static CVector GetLocalPositionToOpenCarDoor(CVehicle *veh, uint32 component, float offset); + static CVector GetPositionToOpenCarDoor(CVehicle *veh, uint32 component, float seatPosMult); + static CVector GetPositionToOpenCarDoor(CVehicle* veh, uint32 component); + static void Initialise(void); + static void SetAnimOffsetForEnterOrExitVehicle(void); + static void LoadFightData(void); + + // Callbacks + static void PedGetupCB(CAnimBlendAssociation *assoc, void *arg); + static void PedStaggerCB(CAnimBlendAssociation *assoc, void *arg); + static void PedEvadeCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishDieAnimCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishedWaitCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishLaunchCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishHitHeadCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimGetInCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimDoorOpenCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimPullPedOutCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimDoorCloseCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetInCarCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetOutCarCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimAlignCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetDraggedOutCarCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimStepOutCarCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetInTrainCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetOutTrainCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishedAttackCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishFightMoveCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimDoorCloseRollingCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishJumpCB(CAnimBlendAssociation *assoc, void *arg); + static void PedLandCB(CAnimBlendAssociation *assoc, void *arg); + static void RestoreHeadingRateCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetQuickDraggedOutCarPositionCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetDraggedOutCarPositionCB(CAnimBlendAssociation *assoc, void *arg); + + bool IsPlayer(void) const; + bool UseGroundColModel(void); + bool CanSetPedState(void); + bool IsPedInControl(void); + bool CanPedDriveOff(void); + bool CanBeDeleted(void); + bool CanStrafeOrMouseControl(void); + bool CanPedReturnToState(void); + void SetMoveState(eMoveState); + bool IsTemporaryObjective(eObjective objective); + void SetObjectiveTimer(int); + bool SelectGunIfArmed(void); + bool IsPointerValid(void); + void SortPeds(CPed**, int, int); + void ForceStoredObjective(eObjective); + void SetStoredObjective(void); + void SetLeader(CEntity* leader); + void SetPedStats(ePedStats); + bool IsGangMember(void) const; + void Die(void); + void EnterTrain(void); + void ExitTrain(void); + void Fall(void); + bool IsPedShootable(void); + void Look(void); + void SetInTheAir(void); + void RestoreHeadPosition(void); + void PointGunAt(void); + bool ServiceTalkingWhenDead(void); + void SetPedPositionInTrain(void); + void SetShootTimer(uint32); + void SetSeekCar(CVehicle*, uint32); + void SetSeekBoatPosition(CVehicle*); + void SetExitTrain(CVehicle*); + void WanderRange(void); + void SetFollowRoute(int16, int16); + void SeekBoatPosition(void); + void UpdatePosition(void); + CObject *SpawnFlyingComponent(int, int8); + void SetCarJack_AllClear(CVehicle*, uint32, uint32); +#ifdef VC_PED_PORTS + bool CanPedJumpThis(CEntity *unused, CVector *damageNormal = nil); +#else + bool CanPedJumpThis(CEntity*); +#endif + + bool HasWeapon(uint8 weaponType) { return m_weapons[weaponType].m_eWeaponType == weaponType; } + CWeapon &GetWeapon(uint8 weaponType) { return m_weapons[weaponType]; } + CWeapon *GetWeapon(void) { return &m_weapons[m_currentWeapon]; } + + PedState GetPedState(void) { return m_nPedState; } + void SetPedState(PedState state) { m_nPedState = state; } + bool Dead(void) { return m_nPedState == PED_DEAD; } + bool Dying(void) { return m_nPedState == PED_DIE; } + bool DyingOrDead(void) { return m_nPedState == PED_DIE || m_nPedState == PED_DEAD; } + bool OnGround(void) { return m_nPedState == PED_FALL || m_nPedState == PED_DIE || m_nPedState == PED_DEAD; } + bool OnGroundOrGettingUp(void) { return OnGround() || m_nPedState == PED_GETUP; } + + bool Driving(void) { return m_nPedState == PED_DRIVING; } + bool InVehicle(void) { return bInVehicle && m_pMyVehicle; } // True when ped is sitting/standing in vehicle, not in enter/exit state. + bool EnteringCar(void) { return m_nPedState == PED_ENTER_CAR || m_nPedState == PED_CARJACK; } + + // It was inlined in III but not in VC. + inline void + ReplaceWeaponWhenExitingVehicle(void) + { + eWeaponType weaponType = GetWeapon()->m_eWeaponType; + + // If it's Uzi, we may have stored weapon. Uzi is the only gun we can use in car. + if (IsPlayer() && weaponType == WEAPONTYPE_UZI) { + if (/*IsPlayer() && */ m_storedWeapon != WEAPONTYPE_UNIDENTIFIED) { + SetCurrentWeapon(m_storedWeapon); + m_storedWeapon = WEAPONTYPE_UNIDENTIFIED; + } + } else { + AddWeaponModel(CWeaponInfo::GetWeaponInfo(weaponType)->m_nModelId); + } + } + + // It was inlined in III but not in VC. + inline void + RemoveWeaponWhenEnteringVehicle(void) + { + if (IsPlayer() && HasWeapon(WEAPONTYPE_UZI) && GetWeapon(WEAPONTYPE_UZI).m_nAmmoTotal > 0) { + if (m_storedWeapon == WEAPONTYPE_UNIDENTIFIED) + m_storedWeapon = GetWeapon()->m_eWeaponType; + SetCurrentWeapon(WEAPONTYPE_UZI); + } else { + CWeaponInfo *ourWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(ourWeapon->m_nModelId); + } + } + bool IsNotInWreckedVehicle() + { + return m_pMyVehicle != nil && ((CEntity*)m_pMyVehicle)->GetStatus() != STATUS_WRECKED; + } + // My additions, because there were many, many instances of that. + inline void SetFindPathAndFlee(CEntity *fleeFrom, int time, bool walk = false) + { + SetFlee(fleeFrom, time); + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + if (walk) + SetMoveState(PEDMOVE_WALK); + } + + inline void SetFindPathAndFlee(CVector2D const &from, int time, bool walk = false) + { + SetFlee(from, time); + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + if (walk) + SetMoveState(PEDMOVE_WALK); + } + + inline void SetWeaponLockOnTarget(CEntity *target) + { + m_pPointGunAt = (CPed *)target; + if(target) + ((CEntity *)target)->RegisterReference(&m_pPointGunAt); + } + + // Using this to abstract nodes of skinned and non-skinned meshes + CVector GetNodePosition(int32 node) + { +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + // this is just stupid + //RwV3dTransformPoints(&pos, &pos, 1, &mats[idx]); + pos = mats[idx].pos; + return pos; + }else +#endif + { + RwMatrix mat; + CPedIK::GetWorldMatrix(m_pFrames[node]->frame, &mat); + return mat.pos; + } + } + void TransformToNode(CVector &pos, int32 node) + { +#ifdef PED_SKIN + if(IsClumpSkinned(GetClump())){ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + RwV3dTransformPoints(&pos, &pos, 1, &mats[idx]); + }else +#endif + { + RwFrame *frame; + for (frame = m_pFrames[node]->frame; frame; frame = RwFrameGetParent(frame)) + RwV3dTransformPoints(&pos, &pos, 1, RwFrameGetMatrix(frame)); + } + } + + // set by 0482:set_threat_reaction_range_multiplier opcode + static uint16 nThreatReactionRangeMultiplier; + + // set by 0481:set_enter_car_range_multiplier opcode + static uint16 nEnterCarRangeMultiplier; + + static bool bNastyLimbsCheat; + static bool bPedCheat2; + static bool bPedCheat3; + static CVector2D ms_vec2DFleePosition; + +#ifdef DEBUGMENU + static bool bPopHeadsOnHeadshot; +#endif + +#ifndef MASTER + // Mobile things + void DebugDrawPedDestination(CPed *, int, int); + void DebugDrawPedDesiredHeading(CPed *, int, int); + void DebugDrawCollisionRadius(float, float, float, float, int); + void DebugDrawVisionRange(CVector, float); + void DebugDrawVisionSimple(CVector, float); + void DebugDrawLook(); + void DebugDrawPedPsyche(); + void DebugDrawDebugLines(); + + static void SwitchDebugDisplay(void); + static int GetDebugDisplay(void); + + void DebugDrawLookAtPoints(); + void DebugRenderOnePedText(void); + void DebugRenderClosePedText(); +#endif + +#ifdef PED_SKIN + void renderLimb(int node); +#endif + +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif +}; + +void FinishFuckUCB(CAnimBlendAssociation *assoc, void *arg); + +#ifndef PED_SKIN +VALIDATE_SIZE(CPed, 0x53C); +#endif diff --git a/src/peds/PedAI.cpp b/src/peds/PedAI.cpp new file mode 100644 index 0000000..8bd6791 --- /dev/null +++ b/src/peds/PedAI.cpp @@ -0,0 +1,5404 @@ +#include "common.h" + +#include "main.h" +#include "Particle.h" +#include "RpAnimBlend.h" +#include "Ped.h" +#include "Wanted.h" +#include "AnimBlendAssociation.h" +#include "DMAudio.h" +#include "General.h" +#include "HandlingMgr.h" +#include "Replay.h" +#include "Camera.h" +#include "PedPlacement.h" +#include "ZoneCull.h" +#include "Pad.h" +#include "Pickups.h" +#include "Train.h" +#include "PedRoutes.h" +#include "CopPed.h" +#include "Script.h" +#include "CarCtrl.h" +#include "WaterLevel.h" +#include "CarAI.h" +#include "Zones.h" +#include "Cranes.h" + +CVector vecPedCarDoorAnimOffset; +CVector vecPedCarDoorLoAnimOffset; +CVector vecPedVanRearDoorAnimOffset; +CVector vecPedQuickDraggedOutCarAnimOffset; +CVector vecPedDraggedOutCarAnimOffset; +CVector vecPedTrainDoorAnimOffset; + +void +CPed::SetObjectiveTimer(int time) +{ + if (time == 0) { + m_objectiveTimer = 0; + } else if (CTimer::GetTimeInMilliseconds() > m_objectiveTimer) { + m_objectiveTimer = CTimer::GetTimeInMilliseconds() + time; + } +} + +void +CPed::SetStoredObjective(void) +{ + if (m_objective == m_prevObjective) + return; + + switch (m_objective) + { + case OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE: + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + case OBJECTIVE_LEAVE_CAR: + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + return; + default: + m_prevObjective = m_objective; + } +} + +void +CPed::ForceStoredObjective(eObjective objective) +{ + if (objective != OBJECTIVE_ENTER_CAR_AS_DRIVER && objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + m_prevObjective = m_objective; + return; + } + + switch (m_objective) + { + case OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE: + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + return; + default: + m_prevObjective = m_objective; + } +} + +bool +CPed::IsTemporaryObjective(eObjective objective) +{ + return objective == OBJECTIVE_LEAVE_CAR || objective == OBJECTIVE_SET_LEADER || +#ifdef VC_PED_PORTS + objective == OBJECTIVE_LEAVE_CAR_AND_DIE || +#endif + objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER; +} + +void +CPed::SetObjective(eObjective newObj) +{ + if (DyingOrDead()) + return; + + if (newObj == OBJECTIVE_NONE) { + if ((m_objective == OBJECTIVE_LEAVE_CAR || m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER +#ifdef VC_PED_PORTS + || m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) + && !IsPlayer() +#else + ) +#endif + && !IsPedInControl()) { + + bStartWanderPathOnFoot = true; + return; + } + // Unused code from assembly... + /* + else if(m_objective == OBJECTIVE_FLEE_CAR) { + + } else { + + } + */ + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; + } else if (m_prevObjective != newObj || m_prevObjective == OBJECTIVE_NONE) { + SetObjectiveTimer(0); + + if (m_objective == newObj) + return; + + if (IsTemporaryObjective(m_objective)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) + SetStoredObjective(); + + m_objective = newObj; + } + bObjectiveCompleted = false; + + switch (newObj) { + case OBJECTIVE_NONE: + m_prevObjective = OBJECTIVE_NONE; + break; + case OBJECTIVE_HAIL_TAXI: + m_nWaitTimer = 0; + SetIdle(); + SetMoveState(PEDMOVE_STILL); + break; + default: + break; + } + } +} + +void +CPed::SetObjective(eObjective newObj, void *entity) +{ + if (DyingOrDead()) + return; + + if (m_prevObjective == newObj) { + // Why? + if (m_prevObjective != OBJECTIVE_NONE) + return; + } + + if (entity == this) + return; + + SetObjectiveTimer(0); + if (m_objective == newObj) { + switch (newObj) { + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + case OBJECTIVE_GUARD_ATTACK: + if (m_pedInObjective == entity) + return; + + break; + case OBJECTIVE_LEAVE_CAR: + case OBJECTIVE_FLEE_CAR: +#ifdef VC_PED_PORTS + case OBJECTIVE_LEAVE_CAR_AND_DIE: +#endif + return; + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + case OBJECTIVE_DESTROY_CAR: + case OBJECTIVE_SOLICIT_VEHICLE: + case OBJECTIVE_BUY_ICE_CREAM: + if (m_carInObjective == entity) + return; + + break; + case OBJECTIVE_SET_LEADER: + if (m_leader == entity) + return; + + break; + default: + break; + } + } else { + if ((newObj == OBJECTIVE_LEAVE_CAR +#ifdef VC_PED_PORTS + || newObj == OBJECTIVE_LEAVE_CAR_AND_DIE +#endif + ) && !bInVehicle) + return; + } + +#ifdef VC_PED_PORTS + ClearPointGunAt(); +#endif + bObjectiveCompleted = false; + if (IsTemporaryObjective(m_objective) && !IsTemporaryObjective(newObj)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) { + if (IsTemporaryObjective(newObj)) + ForceStoredObjective(newObj); + else + SetStoredObjective(); + } + m_objective = newObj; + } + + switch (newObj) { + case OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT: + + // In this special case, entity parameter isn't CEntity, but int. + SetObjectiveTimer((uintptr)entity); + break; + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_MUG_CHAR: + m_pNextPathNode = nil; + bUsePedNodeSeek = false; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_pedInObjective = (CPed*)entity; + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + m_pLookTarget = (CEntity*)entity; + m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget); + break; + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_GUARD_ATTACK: + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_pedInObjective = (CPed*)entity; + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + break; + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + m_pedInObjective = (CPed*)entity; + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + m_pedFormation = FORMATION_REAR; + break; + case OBJECTIVE_LEAVE_CAR: +#ifdef VC_PED_PORTS + case OBJECTIVE_LEAVE_CAR_AND_DIE: +#endif + case OBJECTIVE_FLEE_CAR: + m_carInObjective = (CVehicle*)entity; + m_carInObjective->RegisterReference((CEntity **)&m_carInObjective); + if (m_carInObjective->bIsBus && m_leaveCarTimer == 0) { + for (int i = 0; i < m_carInObjective->m_nNumMaxPassengers; i++) { + if (m_carInObjective->pPassengers[i] == this) { + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 1200 * i; + break; + } + } + } + + break; + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + if (m_nMoveState == PEDMOVE_STILL) + SetMoveState(PEDMOVE_RUN); + + if (((CVehicle*)entity)->m_vehType == VEHICLE_TYPE_BOAT && !IsPlayer()) { + RestorePreviousObjective(); + break; + } + // fall through + case OBJECTIVE_DESTROY_CAR: + case OBJECTIVE_SOLICIT_VEHICLE: + case OBJECTIVE_BUY_ICE_CREAM: + m_carInObjective = (CVehicle*)entity; + m_carInObjective->RegisterReference((CEntity**)&m_carInObjective); + m_pSeekTarget = m_carInObjective; + m_pSeekTarget->RegisterReference((CEntity**)&m_pSeekTarget); + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + if (newObj == OBJECTIVE_SOLICIT_VEHICLE) { + m_objectiveTimer = CTimer::GetTimeInMilliseconds() + 10000; + } else if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER && CharCreatedBy == MISSION_CHAR && + (m_carInObjective->GetStatus() == STATUS_PLAYER_DISABLED || CPad::GetPad(CWorld::PlayerInFocus)->ArePlayerControlsDisabled())) { + SetObjectiveTimer(14000); + } else { + m_objectiveTimer = 0; + } + break; + case OBJECTIVE_SET_LEADER: + SetLeader((CEntity*)entity); + RestorePreviousObjective(); + break; + default: + break; + } +} + +void +CPed::SetObjective(eObjective newObj, CVector dest, float safeDist) +{ + if (DyingOrDead()) + return; + + if (m_prevObjective != OBJECTIVE_NONE && m_prevObjective == newObj) + return; + + SetObjectiveTimer(0); + if (m_objective == newObj) { + if (newObj == OBJECTIVE_GOTO_AREA_ANY_MEANS || newObj == OBJECTIVE_GOTO_AREA_ON_FOOT || newObj == OBJECTIVE_RUN_TO_AREA) { + if (m_nextRoutePointPos == dest && m_distanceToCountSeekDone == safeDist) + return; + } else if (newObj == OBJECTIVE_GUARD_SPOT) { + if (m_vecSeekPosEx == dest && m_distanceToCountSeekDoneEx == safeDist) + return; + } + } + +#ifdef VC_PED_PORTS + ClearPointGunAt(); +#endif + bObjectiveCompleted = false; + if (IsTemporaryObjective(m_objective)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) + SetStoredObjective(); + + m_objective = newObj; + } + + if (newObj == OBJECTIVE_GUARD_SPOT) { + m_vecSeekPosEx = dest; + m_distanceToCountSeekDoneEx = safeDist; + } else if (newObj == OBJECTIVE_GOTO_AREA_ANY_MEANS || newObj == OBJECTIVE_GOTO_AREA_ON_FOOT || newObj == OBJECTIVE_RUN_TO_AREA) { + m_pNextPathNode = nil; + m_nextRoutePointPos = dest; + m_vecSeekPos = m_nextRoutePointPos; + bUsePedNodeSeek = true; + } +} + +// Only used in 01E1: SET_CHAR_OBJ_FOLLOW_ROUTE opcode +// IDA fails very badly in here, puts a fake loop and ignores SetFollowRoute call... +void +CPed::SetObjective(eObjective newObj, int16 routePoint, int16 routeType) +{ + if (DyingOrDead()) + return; + + if (m_prevObjective == newObj && m_prevObjective != OBJECTIVE_NONE) + return; + + SetObjectiveTimer(0); + + if (m_objective == newObj && newObj == OBJECTIVE_FOLLOW_ROUTE && m_routeLastPoint == routePoint && m_routeType == routeType) + return; + + bObjectiveCompleted = false; + if (IsTemporaryObjective(m_objective)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) + SetStoredObjective(); + + m_objective = newObj; + } + + if (newObj == OBJECTIVE_FOLLOW_ROUTE) { + SetFollowRoute(routePoint, routeType); + } +} + +void +CPed::SetObjective(eObjective newObj, CVector dest) +{ + if (DyingOrDead()) + return; + + if (m_prevObjective != OBJECTIVE_NONE && m_prevObjective == newObj) + return; + + SetObjectiveTimer(0); + if (m_objective == newObj) { + if (newObj == OBJECTIVE_GOTO_AREA_ANY_MEANS || newObj == OBJECTIVE_GOTO_AREA_ON_FOOT || newObj == OBJECTIVE_RUN_TO_AREA) { + if (m_nextRoutePointPos == dest) + return; + } else if (newObj == OBJECTIVE_GUARD_SPOT) { + if (m_vecSeekPosEx == dest) + return; + } + } + +#ifdef VC_PED_PORTS + ClearPointGunAt(); +#endif + bObjectiveCompleted = false; + switch (newObj) { + case OBJECTIVE_GUARD_SPOT: + m_vecSeekPosEx = dest; + m_distanceToCountSeekDoneEx = 5.0f; + SetMoveState(PEDMOVE_STILL); + break; + case OBJECTIVE_GUARD_AREA: + case OBJECTIVE_WAIT_IN_CAR: + case OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT: + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + case OBJECTIVE_LEAVE_CAR: + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + case OBJECTIVE_FOLLOW_CAR_IN_CAR: + case OBJECTIVE_FIRE_AT_OBJECT_FROM_VEHICLE: + case OBJECTIVE_DESTROY_OBJECT: + case OBJECTIVE_DESTROY_CAR: + break; + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + bIsRunning = false; + m_pNextPathNode = nil; + m_nextRoutePointPos = dest; + m_vecSeekPos = m_nextRoutePointPos; + m_distanceToCountSeekDone = 0.5f; + bUsePedNodeSeek = true; + if (sq(m_distanceToCountSeekDone) > (m_nextRoutePointPos - GetPosition()).MagnitudeSqr2D()) + return; + break; + case OBJECTIVE_RUN_TO_AREA: + bIsRunning = true; + m_pNextPathNode = nil; + m_nextRoutePointPos = dest; + m_vecSeekPos = m_nextRoutePointPos; + m_distanceToCountSeekDone = 0.5f; + bUsePedNodeSeek = true; + if (sq(m_distanceToCountSeekDone) > (m_nextRoutePointPos - GetPosition()).MagnitudeSqr2D()) + return; + break; + default: break; + } + + if (IsTemporaryObjective(m_objective)) { + m_prevObjective = newObj; + } else { + if (m_objective != newObj) + SetStoredObjective(); + + m_objective = newObj; + } +} + +void +CPed::ClearObjective(void) +{ + if (IsPedInControl() || m_nPedState == PED_DRIVING) { + m_objective = OBJECTIVE_NONE; +#ifdef VC_PED_PORTS + m_pedInObjective = nil; + m_carInObjective = nil; +#endif + if (m_nPedState == PED_DRIVING && m_pMyVehicle) { + + if (m_pMyVehicle->pDriver != this) { +#if defined VC_PED_PORTS || defined FIX_BUGS + if(!IsPlayer()) +#endif + bWanderPathAfterExitingCar = true; + + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } +#ifdef VC_PED_PORTS + m_nLastPedState = PED_NONE; +#endif + } else { + SetIdle(); + SetMoveState(PEDMOVE_STILL); + } + } else { + bClearObjective = true; + } +} + +void +CPed::ClearLeader(void) +{ + if (!m_leader) + return; + + m_leader = nil; + if (IsPedInControl()) { + SetObjective(OBJECTIVE_NONE); + if (CharCreatedBy == MISSION_CHAR) { + SetIdle(); + } else { + SetWanderPath(CGeneral::GetRandomNumberInRange(0,8)); + } + } else if (m_objective != OBJECTIVE_NONE) { + bClearObjective = true; + } +} + +void +CPed::UpdateFromLeader(void) +{ + if (CTimer::GetTimeInMilliseconds() <= m_objectiveTimer) + return; + + if (!m_leader) + return; + + CVector leaderDist; + if (m_leader->InVehicle()) + leaderDist = m_leader->m_pMyVehicle->GetPosition() - GetPosition(); + else + leaderDist = m_leader->GetPosition() - GetPosition(); + + if (leaderDist.Magnitude() > 30.0f) { + if (IsPedInControl()) { + SetObjective(OBJECTIVE_NONE); + SetIdle(); + SetMoveState(PEDMOVE_STILL); + } + SetLeader(nil); + return; + } + + if (IsPedInControl()) { + if (m_nWaitState == WAITSTATE_PLAYANIM_TAXI) + WarpPedToNearLeaderOffScreen(); + + if (m_leader->m_nPedState == PED_DEAD) { + SetLeader(nil); + SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + return; + } + if (!m_leader->bInVehicle) { + if (m_leader->m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (bInVehicle) { + if (m_objective != OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT && m_objective != OBJECTIVE_LEAVE_CAR) + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + + return; + } + if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + RestorePreviousObjective(); + RestorePreviousState(); + } + } + if (m_nPedType == PEDTYPE_PROSTITUTE && CharCreatedBy == RANDOM_CHAR) { + SetLeader(nil); + return; + } + } + if (!bInVehicle && m_leader->bInVehicle && m_leader->m_nPedState == PED_DRIVING) { + if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (m_leader->m_pMyVehicle->m_nNumPassengers < m_leader->m_pMyVehicle->m_nNumMaxPassengers) + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_leader->m_pMyVehicle); + } + } else if (m_leader->m_objective == OBJECTIVE_NONE || (m_leader->IsPlayer() && m_leader->m_objective == OBJECTIVE_WAIT_ON_FOOT) + || m_objective == m_leader->m_objective) { + + if (m_leader->m_nPedState == PED_ATTACK) { + CEntity *lookTargetOfLeader = m_leader->m_pLookTarget; + if (lookTargetOfLeader && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT + && lookTargetOfLeader->IsPed() && lookTargetOfLeader != this) { + + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, lookTargetOfLeader); + SetObjectiveTimer(8000); + SetLookFlag(m_leader->m_pLookTarget, false); + SetLookTimer(500); + } + } else { + if (IsPedInControl() && m_nPedState != PED_ATTACK) { +#ifndef VC_PED_PORTS + SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, m_leader); + SetObjectiveTimer(0); +#else + if (m_leader->m_objective == OBJECTIVE_NONE && m_objective == OBJECTIVE_NONE + && m_leader->m_nPedState == PED_CHAT && m_nPedState == PED_CHAT) { + + SetObjective(OBJECTIVE_NONE); + } else { + SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, m_leader); + SetObjectiveTimer(0); + } +#endif + } + if (m_nPedState == PED_IDLE && m_leader->IsPlayer()) { + if (ScanForThreats() && m_threatEntity) { + m_pLookTarget = m_threatEntity; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + if (m_attackTimer < CTimer::GetTimeInMilliseconds() && !GetWeapon()->IsTypeMelee()) { + SetWeaponLockOnTarget(m_threatEntity); + SetAttack(m_threatEntity); + } + } + } + } + } else { + switch (m_leader->m_objective) { + case OBJECTIVE_WAIT_ON_FOOT: + case OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE: + case OBJECTIVE_WAIT_IN_CAR: + case OBJECTIVE_FOLLOW_ROUTE: + SetObjective(m_leader->m_objective); + m_objectiveTimer = m_leader->m_objectiveTimer; + break; + case OBJECTIVE_GUARD_SPOT: + SetObjective(OBJECTIVE_GUARD_SPOT, m_leader->m_vecSeekPosEx); + m_objectiveTimer = m_leader->m_objectiveTimer; + break; + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + if (m_leader->m_pedInObjective) { + SetObjective(m_leader->m_objective, m_leader->m_pedInObjective); + m_objectiveTimer = m_leader->m_objectiveTimer; + } + break; + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + if (m_leader->m_carInObjective) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_leader->m_carInObjective); + return; + } + break; + case OBJECTIVE_GUARD_ATTACK: + return; + case OBJECTIVE_HAIL_TAXI: + m_leader = nil; + SetObjective(OBJECTIVE_NONE); + break; + default: + SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT, m_leader); + SetObjectiveTimer(0); + break; + } + } + } else if (bInVehicle) { + if ((!m_leader->bInVehicle || m_leader->m_nPedState == PED_EXIT_CAR) && m_objective != OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT) { + + switch (m_leader->m_objective) { + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + if (m_pMyVehicle == m_leader->m_pMyVehicle || m_pMyVehicle == m_leader->m_carInObjective) + break; + + // fall through + default: + if (m_pMyVehicle && m_objective != OBJECTIVE_LEAVE_CAR) { +#ifdef VC_PED_PORTS + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 250; +#endif + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + + break; + } + } + } +} + +void +CPed::RestorePreviousObjective(void) +{ + if (m_objective == OBJECTIVE_NONE) + return; + + if (m_objective != OBJECTIVE_LEAVE_CAR && m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER && m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER +#if defined VC_PED_PORTS || defined FIX_BUGS + && m_nPedState != PED_CARJACK +#endif + ) + m_pedInObjective = nil; + + if (m_objective == OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT) { + m_objective = OBJECTIVE_NONE; + if (m_pMyVehicle) + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + + } else { + m_objective = m_prevObjective; + m_prevObjective = OBJECTIVE_NONE; + } + bObjectiveCompleted = false; +} + +void +CPed::ProcessObjective(void) +{ + if (bClearObjective && (IsPedInControl() || m_nPedState == PED_DRIVING)) { + ClearObjective(); + bClearObjective = false; + } + UpdateFromLeader(); + + CVector carOrOurPos; + CVector targetCarOrHisPos; + CVector distWithTarget; + + if (m_objective != OBJECTIVE_NONE && (IsPedInControl() || m_nPedState == PED_DRIVING)) { + if (bInVehicle) { + if (!m_pMyVehicle) { + bInVehicle = false; + return; + } + carOrOurPos = m_pMyVehicle->GetPosition(); + } else { + carOrOurPos = GetPosition(); + } + + if (m_pedInObjective) { + if (m_pedInObjective->InVehicle() && m_pedInObjective->m_nPedState != PED_DRAG_FROM_CAR) { + targetCarOrHisPos = m_pedInObjective->m_pMyVehicle->GetPosition(); + } else { + targetCarOrHisPos = m_pedInObjective->GetPosition(); + } + distWithTarget = targetCarOrHisPos - carOrOurPos; + } else if (m_carInObjective) { + targetCarOrHisPos = m_carInObjective->GetPosition(); + distWithTarget = targetCarOrHisPos - carOrOurPos; + } + + switch (m_objective) { + case OBJECTIVE_NONE: + case OBJECTIVE_GUARD_AREA: + case OBJECTIVE_FOLLOW_CAR_IN_CAR: + case OBJECTIVE_FIRE_AT_OBJECT_FROM_VEHICLE: + case OBJECTIVE_DESTROY_OBJECT: + case OBJECTIVE_GOTO_AREA_IN_CAR: + case OBJECTIVE_FOLLOW_CAR_ON_FOOT_WITH_OFFSET: + case OBJECTIVE_SET_LEADER: + break; + case OBJECTIVE_WAIT_ON_FOOT: + SetIdle(); + m_objective = OBJECTIVE_NONE; + SetMoveState(PEDMOVE_STILL); + break; + case OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE: + if (InVehicle()) { + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + bFleeAfterExitingCar = true; + } else if (m_nPedState != PED_FLEE_POS) { + CVector2D fleePos = GetPosition(); + SetFlee(fleePos, 10000); + bUsePedNodeSeek = true; + m_pNextPathNode = nil; + } + break; + case OBJECTIVE_GUARD_SPOT: + { + distWithTarget = m_vecSeekPosEx - GetPosition(); + if (m_pedInObjective) { + SetLookFlag(m_pedInObjective, true); + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + float distWithTargetSc = distWithTarget.Magnitude(); + if (2.0f * m_distanceToCountSeekDoneEx >= distWithTargetSc) { + if (m_pedInObjective) { + if (distWithTargetSc <= m_distanceToCountSeekDoneEx) + SetIdle(); + else + SetSeek(m_vecSeekPosEx, m_distanceToCountSeekDoneEx); + } else if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + int threatType = ScanForThreats(); + SetLookTimer(CGeneral::GetRandomNumberInRange(500, 1500)); + + // Second condition is pointless and isn't there in Mobile. + if (threatType == PED_FLAG_GUN || (threatType == PED_FLAG_EXPLOSION && m_threatEntity) || m_threatEntity) { + if (m_threatEntity->IsPed()) + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } + } + } else { + SetSeek(m_vecSeekPosEx, m_distanceToCountSeekDoneEx); + } + break; + } + case OBJECTIVE_WAIT_IN_CAR: + SetPedState(PED_DRIVING); + break; + case OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT: + SetPedState(PED_DRIVING); + break; + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + { + if (m_pedInObjective) { + if (m_pedInObjective->IsPlayer() && CharCreatedBy != MISSION_CHAR + && m_nPedType != PEDTYPE_COP && FindPlayerPed()->m_pWanted->m_CurrentCops != 0 + && !bKindaStayInSamePlace) { + + SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + break; + } + if (InVehicle()) { + if (distWithTarget.Magnitude() >= 20.0f + || m_pMyVehicle->m_vecMoveSpeed.MagnitudeSqr() >= sq(0.02f)) { + if (m_pMyVehicle->pDriver == this + && !m_pMyVehicle->m_nGettingInFlags) { + m_pMyVehicle->SetStatus(STATUS_PHYSICS); + m_pMyVehicle->AutoPilot.m_nPrevRouteNode = 0; + if (m_nPedType == PEDTYPE_COP) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = (FindPlayerPed()->m_pWanted->GetWantedLevel() * 0.1f + 0.6f) * (GAME_SPEED_TO_CARAI_SPEED * m_pMyVehicle->pHandling->Transmission.fMaxCruiseVelocity); + m_pMyVehicle->AutoPilot.m_nCarMission = CCarAI::FindPoliceCarMissionForWantedLevel(); + } else { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * m_pMyVehicle->pHandling->Transmission.fMaxCruiseVelocity * 0.8f; + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_RAMPLAYER_FARAWAY; + } + m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + } + } else { + bool targetHasVeh = m_pedInObjective->bInVehicle; + if (!targetHasVeh + || targetHasVeh && m_pedInObjective->m_pMyVehicle->CanPedExitCar()) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } + break; + } + if (distWithTarget.Magnitude() > 30.0f && !bKindaStayInSamePlace) { + if (m_pMyVehicle) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } else { + float closestVehDist = 60.0f; + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(GetPosition(), 25.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + CVehicle *foundVeh = nil; + for(int i = 0; i < lastVehicle; i++) { + CVehicle *nearVeh = (CVehicle*)vehicles[i]; + /* + Not used. + CVector vehSpeed = nearVeh->GetSpeed(); + CVector ourSpeed = GetSpeed(); + */ + CVector vehDistVec = nearVeh->GetPosition() - GetPosition(); + if (vehDistVec.Magnitude() < closestVehDist && m_pedInObjective->m_pMyVehicle != nearVeh + && nearVeh->CanPedOpenLocks(this)) { + + foundVeh = nearVeh; + closestVehDist = vehDistVec.Magnitude(); + } + } + m_pMyVehicle = foundVeh; + if (m_pMyVehicle) { + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } else if (!GetIsOnScreen()) { + CVector ourPos = GetPosition(); + int closestNode = ThePaths.FindNodeClosestToCoors(ourPos, PATH_CAR, 20.0f); + if (closestNode >= 0) { + int16 colliding; + CWorld::FindObjectsKindaColliding( + ThePaths.m_pathNodes[closestNode].GetPosition(), 10.0f, true, &colliding, 2, nil, false, true, true, false, false); + if (!colliding) { + CZoneInfo zoneInfo; + int chosenCarClass; + CTheZones::GetZoneInfoForTimeOfDay(&ourPos, &zoneInfo); + int chosenModel = CCarCtrl::ChooseModel(&zoneInfo, &ourPos, &chosenCarClass); + CAutomobile *newVeh = new CAutomobile(chosenModel, RANDOM_VEHICLE); + if (newVeh) { + newVeh->GetMatrix().GetPosition() = ThePaths.m_pathNodes[closestNode].GetPosition(); + newVeh->GetMatrix().GetPosition().z += 4.0f; + newVeh->SetHeading(DEGTORAD(200.0f)); + newVeh->SetStatus(STATUS_ABANDONED); + newVeh->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(newVeh); + m_pMyVehicle = newVeh; + if (m_pMyVehicle) { + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } + } + } + } + } + break; + } + } else { + ClearLookFlag(); + bObjectiveCompleted = true; + } + } + case OBJECTIVE_KILL_CHAR_ON_FOOT: + { + bool killPlayerInNoPoliceZone = false; + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && InVehicle()) { + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + break; + } + if (!m_pedInObjective || m_pedInObjective->DyingOrDead()) { + ClearLookFlag(); + bObjectiveCompleted = true; + SetMoveAnim(); + break; + } + if (m_pedInObjective->IsPlayer() && CCullZones::NoPolice()) + killPlayerInNoPoliceZone = true; + + if (!bNotAllowedToDuck || killPlayerInNoPoliceZone) { + if (m_nPedType == PEDTYPE_COP && !m_pedInObjective->GetWeapon()->IsTypeMelee() && !GetWeapon()->IsTypeMelee()) + bNotAllowedToDuck = true; + } else { + if (!m_pedInObjective->bInVehicle) { + if (m_pedInObjective->GetWeapon()->IsTypeMelee() || GetWeapon()->IsTypeMelee()) { + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + } else if (DuckAndCover()) { + break; + } + } else { + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + } + } + if (m_leaveCarTimer > CTimer::GetTimeInMilliseconds() && !bKindaStayInSamePlace) { + SetMoveState(PEDMOVE_STILL); + break; + } + if (m_pedInObjective->IsPlayer()) { + CPlayerPed *player = FindPlayerPed(); + if (m_nPedType == PEDTYPE_COP && player->m_pWanted->m_bIgnoredByCops + || player->m_pWanted->m_bIgnoredByEveryone + || m_pedInObjective->bIsInWater + || m_pedInObjective->m_nPedState == PED_ARRESTED) { + + if (m_nPedState != PED_ARREST_PLAYER) + SetIdle(); + + break; + } + } + CWeaponInfo *wepInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + float wepRange = wepInfo->m_fRange; + float maxDistToKeep; + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + maxDistToKeep = wepRange / 3.0f; + } else { + if (m_nPedState == PED_FIGHT) { + if (!IsPlayer() && !(m_pedStats->m_flags & STAT_CAN_KICK)) + wepRange = 2.0f; + } else { + wepRange = 1.3f; + } + maxDistToKeep = wepRange; + } + if (m_pedInObjective->m_getUpTimer > CTimer::GetTimeInMilliseconds() && maxDistToKeep < 2.5f) { + maxDistToKeep = 2.5f; + } + if (m_pedInObjective->IsPlayer() && m_nPedType != PEDTYPE_COP + && CharCreatedBy != MISSION_CHAR && FindPlayerPed()->m_pWanted->m_CurrentCops) { + SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + break; + } + if (m_pedInObjective->m_fHealth <= 0.0f) { + bObjectiveCompleted = true; + bScriptObjectiveCompleted = true; + SetMoveAnim(); + break; + } + float distWithTargetSc = distWithTarget.Magnitude(); + if (m_pedInObjective->bInVehicle && m_pedInObjective->m_nPedState != PED_DRAG_FROM_CAR) { + CVehicle *vehOfTarget = m_pedInObjective->m_pMyVehicle; + if (vehOfTarget->bIsInWater || vehOfTarget->GetStatus() == STATUS_PLAYER_DISABLED + || m_pedInObjective->IsPlayer() && CPad::GetPad(0)->ArePlayerControlsDisabled()) { + SetIdle(); + return; + } + SetLookFlag(vehOfTarget, false); + if (m_nPedState != PED_CARJACK) { + if (m_pedInObjective->m_nPedState != PED_ARRESTED) { + if (m_attackTimer < CTimer::GetTimeInMilliseconds() && wepInfo->m_eWeaponFire != WEAPON_FIRE_MELEE + && distWithTargetSc < wepRange && distWithTargetSc > 3.0f) { + + // I hope so + CVector ourHead = GetMatrix() * CVector(0.5f, 0.0f, 0.6f); + CVector maxShotPos = vehOfTarget->GetPosition() - ourHead; + maxShotPos *= wepInfo->m_fRange / maxShotPos.Magnitude(); + maxShotPos += ourHead; + + CColPoint foundCol; + CEntity *foundEnt; + + CWorld::bIncludeDeadPeds = true; + CWorld::ProcessLineOfSight(ourHead, maxShotPos, foundCol, foundEnt, true, true, true, true, false, true, false); + CWorld::bIncludeDeadPeds = false; + if (foundEnt == vehOfTarget) { + SetAttack(vehOfTarget); + SetWeaponLockOnTarget(vehOfTarget); + SetShootTimer(CGeneral::GetRandomNumberInRange(500, 2000)); + if (distWithTargetSc <= m_distanceToCountSeekDone) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(200, 500)); + SetMoveState(PEDMOVE_STILL); + } else { + SetAttackTimer(CGeneral::GetRandomNumberInRange(2000, 5000)); + } + } + } else if (m_nPedState != PED_ATTACK && !bKindaStayInSamePlace && !killPlayerInNoPoliceZone) { + if (vehOfTarget) { + if (m_nPedType == PEDTYPE_COP || vehOfTarget->bIsBus) { + GoToNearestDoor(vehOfTarget); + } else { + m_vehDoor = 0; + if (m_pedInObjective == vehOfTarget->pDriver || vehOfTarget->bIsBus) { + m_vehDoor = CAR_DOOR_LF; + } else if (m_pedInObjective == vehOfTarget->pPassengers[0]) { + m_vehDoor = CAR_DOOR_RF; + } else if (m_pedInObjective == vehOfTarget->pPassengers[1]) { + m_vehDoor = CAR_DOOR_LR; + } else if (m_pedInObjective == vehOfTarget->pPassengers[2]) { + m_vehDoor = CAR_DOOR_RR; + } + // Unused + // GetPositionToOpenCarDoor(vehOfTarget, m_vehDoor); + SetSeekCar(vehOfTarget, m_vehDoor); + SetMoveState(PEDMOVE_RUN); + } + } + } + } + } + SetMoveAnim(); + break; + } + if (m_nMoveState == PEDMOVE_STILL && IsPedInControl()) { + SetLookFlag(m_pedInObjective, false); + TurnBody(); + } + if (m_nPedType == PEDTYPE_COP && distWithTargetSc < 1.5f && m_pedInObjective->IsPlayer()) { + if (m_pedInObjective->m_getUpTimer > CTimer::GetTimeInMilliseconds() + || m_pedInObjective->m_nPedState == PED_DRAG_FROM_CAR) { + + ((CCopPed*)this)->SetArrestPlayer(m_pedInObjective); + return; + } + } + if (!bKindaStayInSamePlace && !bStopAndShoot && m_nPedState != PED_ATTACK && !killPlayerInNoPoliceZone) { + if (distWithTargetSc > wepRange + || m_pedInObjective->m_getUpTimer > CTimer::GetTimeInMilliseconds() + || m_pedInObjective->m_nPedState == PED_ARRESTED + || m_pedInObjective->EnteringCar() && distWithTargetSc < 3.0f + || distWithTargetSc > m_distanceToCountSeekDone && !CanSeeEntity(m_pedInObjective)) { + + if (m_pedInObjective->EnteringCar()) + maxDistToKeep = 2.0f; + + if (bUsePedNodeSeek) { + CVector bestCoords(0.0f, 0.0f, 0.0f); + m_vecSeekPos = m_pedInObjective->GetPosition(); + + if (!m_pNextPathNode) + FindBestCoordsFromNodes(m_vecSeekPos, &bestCoords); + + if (m_pNextPathNode) + m_vecSeekPos = m_pNextPathNode->GetPosition(); + + SetSeek(m_vecSeekPos, m_distanceToCountSeekDone); + } else { + SetSeek(m_pedInObjective, maxDistToKeep); + } + bCrouchWhenShooting = false; + if (m_pedInObjective->m_pCurrentPhysSurface && distWithTargetSc < 5.0f) { + if (wepRange <= 5.0f) { + if (m_pedInObjective->IsPlayer() + && FindPlayerPed()->m_bSpeedTimerFlag + && (IsGangMember() || m_nPedType == PEDTYPE_COP) + && GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) { + GiveWeapon(WEAPONTYPE_COLT45, 1000); + SetCurrentWeapon(WEAPONTYPE_COLT45); + } + } else { + bStopAndShoot = true; + } + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + break; + } + bStopAndShoot = false; + SetMoveAnim(); + break; + } + } + if (m_attackTimer < CTimer::GetTimeInMilliseconds() + && distWithTargetSc < wepRange && m_pedInObjective->m_nPedState != PED_GETUP && m_pedInObjective->m_nPedState != PED_DRAG_FROM_CAR) { + if (bIsDucking) { + CAnimBlendAssociation *duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (duckAnim) { + duckAnim->blendDelta = -2.0f; + break; + } + bIsDucking = false; + } else if (wepRange <= 5.0f) { + SetMoveState(PEDMOVE_STILL); + SetAttack(m_pedInObjective); + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_pedInObjective->GetPosition().x, m_pedInObjective->GetPosition().y, + GetPosition().x, GetPosition().y); + SetShootTimer(CGeneral::GetRandomNumberInRange(0.0f, 500.0f)); + SetAttackTimer(CGeneral::GetRandomNumberInRange(0.0f, 1500.0f)); + bObstacleShowedUpDuringKillObjective = false; + + } else { + CVector target; + CVector ourHead = GetMatrix() * CVector(0.5f, 0.0f, 0.6f); + if (m_pedInObjective->IsPed()) + m_pedInObjective->m_pedIK.GetComponentPosition(target, PED_MID); + else + target = m_pedInObjective->GetPosition(); + + target -= ourHead; + target *= wepInfo->m_fRange / target.Magnitude(); + target += ourHead; + + CColPoint foundCol; + CEntity *foundEnt = nil; + + CWorld::bIncludeDeadPeds = true; + CWorld::ProcessLineOfSight(ourHead, target, foundCol, foundEnt, true, true, true, true, false, true, false); + CWorld::bIncludeDeadPeds = false; + if (foundEnt == m_pedInObjective) { + SetAttack(m_pedInObjective); + SetWeaponLockOnTarget(m_pedInObjective); + SetShootTimer(CGeneral::GetRandomNumberInRange(500.0f, 2000.0f)); + + int time; + if (distWithTargetSc <= maxDistToKeep) + time = CGeneral::GetRandomNumberInRange(100.0f, 500.0f); + else + time = CGeneral::GetRandomNumberInRange(1500.0f, 3000.0f); + + SetAttackTimer(time); + bObstacleShowedUpDuringKillObjective = false; + + } else { + if (foundEnt) { + if (foundEnt->IsPed()) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(500.0f, 1000.0f)); + bObstacleShowedUpDuringKillObjective = false; + } else { + if (foundEnt->IsObject()) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(200.0f, 400.0f)); + bObstacleShowedUpDuringKillObjective = true; + } else if (foundEnt->IsVehicle()) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(400.0f, 600.0f)); + bObstacleShowedUpDuringKillObjective = true; + } else { + SetAttackTimer(CGeneral::GetRandomNumberInRange(700.0f, 1200.0f)); + bObstacleShowedUpDuringKillObjective = true; + } + } + + m_fleeFrom = foundEnt; + m_fleeFrom->RegisterReference((CEntity**) &m_fleeFrom); + } + SetPointGunAt(m_pedInObjective); + } + } + } else { + if (!m_pedInObjective->m_pCurrentPhysSurface) + bStopAndShoot = false; + + if (m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT) { + + // This is weird... + if (bNotAllowedToDuck && bKindaStayInSamePlace) { + if (!bIsDucking) { + CAnimBlendAssociation* duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!duckAnim || duckAnim->blendDelta < 0.0f) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_DOWN, 4.0f); + bIsDucking = true; + } + break; + } else { + CAnimBlendAssociation* duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!duckAnim || duckAnim->blendDelta < 0.0f) { + bIsDucking = false; + } else { + break; + } + } + } + if (bObstacleShowedUpDuringKillObjective) { + if (m_nPedType == PEDTYPE_COP) { + if (GetWeapon()->m_eWeaponType > WEAPONTYPE_COLT45 + || m_fleeFrom && m_fleeFrom->IsObject()) { + maxDistToKeep = 6.0f; + } else if (m_fleeFrom && m_fleeFrom->IsVehicle()) { + maxDistToKeep = 4.0f; + } else { + maxDistToKeep = 2.0f; + } + } else { + maxDistToKeep = 2.0f; + } + } + if (distWithTargetSc <= maxDistToKeep) { + SetMoveState(PEDMOVE_STILL); + bIsPointingGunAt = true; + if (m_nPedState != PED_AIM_GUN && !bDuckAndCover) { + m_attackTimer = CTimer::GetTimeInMilliseconds(); + SetIdle(); + } + } else { + if (m_nPedState != PED_SEEK_ENTITY && m_nPedState != PED_SEEK_POS + && !bStopAndShoot && !killPlayerInNoPoliceZone && !bKindaStayInSamePlace) { + Say(SOUND_PED_ATTACK); + SetSeek(m_pedInObjective, maxDistToKeep); + bIsRunning = true; + } + } + } + } + + if (distWithTargetSc < 2.5f && wepRange > 5.0f + && wepInfo->m_eWeaponFire != WEAPON_FIRE_MELEE) { + + SetAttack(m_pedInObjective); + if (m_attackTimer < CTimer::GetTimeInMilliseconds()) { + int time = CGeneral::GetRandomNumberInRange(500.0f, 1000.0f); + SetAttackTimer(time); + SetShootTimer(time - 500); + } + SetMoveState(PEDMOVE_STILL); + } + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(m_pedInObjective->GetPosition().x, m_pedInObjective->GetPosition().y, GetPosition().x, GetPosition().y); + + SetMoveAnim(); + break; + } + case OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE: + case OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS: + { + if (InVehicle()) { + if (m_nPedState == PED_DRIVING) + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } else if (m_nPedState != PED_FLEE_ENTITY) { + int time; + if (m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS) + time = 0; + else + time = 6000; + + SetFindPathAndFlee(m_pedInObjective, time); + } + break; + } + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + { + if (m_pedInObjective) { + float safeDistance = 2.0f; + if (m_pedInObjective->bInVehicle) + safeDistance = 3.0f; + + float distWithTargetSc = distWithTarget.Magnitude(); + if (m_nPedStateTimer < CTimer::GetTimeInMilliseconds()) { + if (distWithTargetSc <= safeDistance) { + bScriptObjectiveCompleted = true; + if (m_nPedState != PED_ATTACK) { + SetIdle(); + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + if (distWithTargetSc > 2.0f) + SetMoveState(m_pedInObjective->m_nMoveState); + else + SetMoveState(PEDMOVE_STILL); + } else { + SetSeek(m_pedInObjective, safeDistance); + if (distWithTargetSc >= 5.0f) { + if (m_leader && m_leader->m_nMoveState == PEDMOVE_SPRINT) + SetMoveState(PEDMOVE_SPRINT); + else + SetMoveState(PEDMOVE_RUN); + } else { + if (m_leader && m_leader->m_nMoveState != PEDMOVE_STILL + && m_leader->m_nMoveState != PEDMOVE_NONE) { + if (m_leader->IsPlayer()) { + if (distWithTargetSc >= 3.0f && FindPlayerPed()->m_fMoveSpeed >= 1.3f) + SetMoveState(PEDMOVE_RUN); + else + SetMoveState(PEDMOVE_WALK); + } else { + SetMoveState(m_leader->m_nMoveState); + } + } else if (distWithTargetSc <= 3.0f) { + SetMoveState(PEDMOVE_WALK); + } else { + SetMoveState(PEDMOVE_RUN); + } + } + } + } + } else { + SetObjective(OBJECTIVE_NONE); + } + break; + } + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + { + if (m_pedInObjective) { + CVector posToGo = GetFormationPosition(); + distWithTarget = posToGo - carOrOurPos; + SetSeek(posToGo, 1.0f); + if (distWithTarget.Magnitude() <= 3.0f) { + SetSeek(posToGo, 1.0f); + if (m_pedInObjective->m_nMoveState != PEDMOVE_STILL) + SetMoveState(m_pedInObjective->m_nMoveState); + } else { + SetSeek(posToGo, 1.0f); + SetMoveState(PEDMOVE_RUN); + } + } else { + SetObjective(OBJECTIVE_NONE); + } + break; + } + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + { + if (m_carInObjective) { + if (!bInVehicle && m_carInObjective->m_nNumPassengers >= m_carInObjective->m_nNumMaxPassengers) { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + + break; + } + + if (m_prevObjective == OBJECTIVE_HAIL_TAXI && !((CAutomobile*)m_carInObjective)->bTaxiLight) { + RestorePreviousObjective(); + ClearObjective(); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + bIsRunning = false; + break; + } + if (m_objectiveTimer && m_objectiveTimer < CTimer::GetTimeInMilliseconds()) { + if (!EnteringCar()) { + bool foundSeat = false; + if (m_carInObjective->pPassengers[0] || m_carInObjective->m_nGettingInFlags & CAR_DOOR_FLAG_RF) { + if (m_carInObjective->pPassengers[1] || m_carInObjective->m_nGettingInFlags & CAR_DOOR_FLAG_LR) { + if (m_carInObjective->pPassengers[2] || m_carInObjective->m_nGettingInFlags & CAR_DOOR_FLAG_RR) { + foundSeat = false; + } else { + m_vehDoor = CAR_DOOR_RR; + foundSeat = true; + } + } else { + m_vehDoor = CAR_DOOR_LR; + foundSeat = true; + } + } else { + m_vehDoor = CAR_DOOR_RF; + foundSeat = true; + } + for (int i = 2; i < m_carInObjective->m_nNumMaxPassengers; ++i) { + if (!m_carInObjective->pPassengers[i] && !(m_carInObjective->m_nGettingInFlags & CAR_DOOR_FLAG_RF)) { + m_vehDoor = CAR_DOOR_RF; + foundSeat = true; + } + } + if (foundSeat) { + SetPosition(GetPositionToOpenCarDoor(m_carInObjective, m_vehDoor)); + SetEnterCar(m_carInObjective, m_vehDoor); + } + } + m_objectiveTimer = 0; + } + } + // fall through + } + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + { + if (!m_carInObjective || bInVehicle) { +#ifdef VC_PED_PORTS + if (bInVehicle && m_pMyVehicle != m_carInObjective) { + SetExitCar(m_pMyVehicle, 0); + } else +#endif + { + bObjectiveCompleted = true; + bScriptObjectiveCompleted = true; + RestorePreviousState(); + } + } else { + if (m_leaveCarTimer > CTimer::GetTimeInMilliseconds()) { + SetMoveState(PEDMOVE_STILL); + break; + } + if (IsPedInControl()) { + if (m_prevObjective == OBJECTIVE_KILL_CHAR_ANY_MEANS) { + if (distWithTarget.Magnitude() < 20.0f) { + RestorePreviousObjective(); + RestorePreviousState(); + return; + } + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + if (m_carInObjective->pDriver +#ifdef VC_PED_PORTS + && !IsPlayer() +#endif + ) { + if (m_carInObjective->pDriver->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS && m_carInObjective->pDriver != m_pedInObjective) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_carInObjective); + m_carInObjective->bIsBeingCarJacked = false; + } + } + } + } else if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + if (m_carInObjective->pDriver +#ifdef VC_PED_PORTS + && (CharCreatedBy != MISSION_CHAR || m_carInObjective->pDriver->CharCreatedBy != RANDOM_CHAR) +#endif + ) { + if (m_carInObjective->pDriver->m_nPedType == m_nPedType) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_carInObjective); + m_carInObjective->bIsBeingCarJacked = false; + } + } + } + if (m_carInObjective->IsUpsideDown() && m_carInObjective->m_vehType != VEHICLE_TYPE_BIKE) { + RestorePreviousObjective(); + RestorePreviousState(); + return; + } + if (!m_carInObjective->IsBoat() || m_nPedState == PED_SEEK_IN_BOAT) { + if (m_nPedState != PED_SEEK_CAR) + SetSeekCar(m_carInObjective, 0); + } else { + SetSeekBoatPosition(m_carInObjective); + } + if (m_nMoveState == PEDMOVE_STILL && !bVehEnterDoorIsBlocked) + SetMoveState(PEDMOVE_RUN); + + if (m_carInObjective && m_carInObjective->m_fHealth > 0.0f) { + distWithTarget = m_carInObjective->GetPosition() - GetPosition(); + if (!bInVehicle) { + if (nEnterCarRangeMultiplier * ENTER_CAR_MAX_DIST < distWithTarget.Magnitude()) { + if (!m_carInObjective->pDriver && !m_carInObjective->GetIsOnScreen() && !GetIsOnScreen()) + WarpPedToNearEntityOffScreen(m_carInObjective); + + if (CharCreatedBy != MISSION_CHAR || m_prevObjective == OBJECTIVE_KILL_CHAR_ANY_MEANS +#ifdef VC_PED_PORTS + || IsPlayer() && !CPad::GetPad(0)->ArePlayerControlsDisabled() +#endif + ) { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + } else { + SetIdle(); + SetMoveState(PEDMOVE_STILL); + } + } + } + } else if (!bInVehicle) { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + } + } + } + break; + } + case OBJECTIVE_DESTROY_CAR: + { + if (!m_carInObjective) { + ClearLookFlag(); + bObjectiveCompleted = true; + break; + } + float distWithTargetSc = distWithTarget.Magnitude(); + CWeaponInfo *wepInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + float wepRange = wepInfo->m_fRange; + m_pLookTarget = m_carInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + + m_pSeekTarget = m_carInObjective; + m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + + TurnBody(); + if (m_carInObjective->m_fHealth <= 0.0f) { + ClearLookFlag(); + bScriptObjectiveCompleted = true; + break; + } + + if (m_attackTimer < CTimer::GetTimeInMilliseconds() && wepInfo->m_eWeaponFire != WEAPON_FIRE_MELEE + && distWithTargetSc < wepRange) { + + // I hope so + CVector ourHead = GetMatrix() * CVector(0.5f, 0.0f, 0.6f); + CVector maxShotPos = m_carInObjective->GetPosition() - ourHead; + maxShotPos *= wepInfo->m_fRange / maxShotPos.Magnitude(); + maxShotPos += ourHead; + + CColPoint foundCol; + CEntity *foundEnt; + + CWorld::bIncludeDeadPeds = true; + CWorld::ProcessLineOfSight(ourHead, maxShotPos, foundCol, foundEnt, true, true, true, true, false, true, false); + CWorld::bIncludeDeadPeds = false; + if (foundEnt == m_carInObjective) { + SetAttack(m_carInObjective); + SetWeaponLockOnTarget(m_carInObjective); + SetShootTimer(CGeneral::GetRandomNumberInRange(500, 2000)); + if (distWithTargetSc > 10.0f && !bKindaStayInSamePlace) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(2000, 5000)); + } else { + SetAttackTimer(CGeneral::GetRandomNumberInRange(50, 300)); + SetMoveState(PEDMOVE_STILL); + } + } + } else if (m_nPedState != PED_ATTACK && !bKindaStayInSamePlace) { + + float safeDistance; + if (wepRange <= 5.0f) + safeDistance = 3.0f; + else + safeDistance = wepRange * 0.25f; + + SetSeek(m_carInObjective, safeDistance); + SetMoveState(PEDMOVE_RUN); + } + SetLookFlag(m_carInObjective, false); + TurnBody(); + break; + } + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + { + distWithTarget = m_nextRoutePointPos - GetPosition(); + distWithTarget.z = 0.0f; + if (InVehicle()) { + CCarAI::GetCarToGoToCoors(m_pMyVehicle, &m_nextRoutePointPos); + CCarCtrl::RegisterVehicleOfInterest(m_pMyVehicle); + if (distWithTarget.MagnitudeSqr() < sq(20.0f)) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + ForceStoredObjective(OBJECTIVE_GOTO_AREA_ANY_MEANS); + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + break; + } + if (distWithTarget.Magnitude() > 30.0f) { + if (m_pMyVehicle) { + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + } else { + float closestVehDist = SQR(60.0f); + int16 lastVehicle; + CEntity* vehicles[8]; + CWorld::FindObjectsInRange(GetPosition(), 25.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + CVehicle* foundVeh = nil; + for (int i = 0; i < lastVehicle; i++) { + CVehicle* nearVeh = (CVehicle*)vehicles[i]; + /* + Not used. + CVector vehSpeed = nearVeh->GetSpeed(); + CVector ourSpeed = GetSpeed(); + */ + CVector vehDistVec = nearVeh->GetPosition() - GetPosition(); + if (vehDistVec.MagnitudeSqr() < closestVehDist + && m_pedInObjective->m_pMyVehicle != nearVeh) + { + foundVeh = nearVeh; + closestVehDist = vehDistVec.MagnitudeSqr(); + } + } + m_pMyVehicle = foundVeh; + if (m_pMyVehicle) { + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } + break; + } + // fall through + } + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + { + if ((m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA) + && InVehicle()) { + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } else { + distWithTarget = m_nextRoutePointPos - GetPosition(); + distWithTarget.z = 0.0f; + if (sq(m_distanceToCountSeekDone) >= distWithTarget.MagnitudeSqr()) { + bObjectiveCompleted = true; + bScriptObjectiveCompleted = true; + SetMoveState(PEDMOVE_STILL); + } else if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer || m_nPedState != PED_SEEK_POS) { + if (bUsePedNodeSeek) { + CVector bestCoords(0.0f, 0.0f, 0.0f); + m_vecSeekPos = m_nextRoutePointPos; + + if (!m_pNextPathNode) + FindBestCoordsFromNodes(m_vecSeekPos, &bestCoords); + + if (m_pNextPathNode) + m_vecSeekPos = m_pNextPathNode->GetPosition(); + } + SetSeek(m_vecSeekPos, m_distanceToCountSeekDone); + } + } + + break; + } + case OBJECTIVE_GUARD_ATTACK: + { + if (m_pedInObjective) { + SetLookFlag(m_pedInObjective, true); + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + m_lookTimer = m_attackTimer; + TurnBody(); + float distWithTargetSc = distWithTarget.Magnitude(); + if (distWithTargetSc >= 20.0f) { + RestorePreviousObjective(); + } else if (m_attackTimer < CTimer::GetTimeInMilliseconds()) { + if (m_nPedState != PED_SEEK_ENTITY && distWithTargetSc >= 2.0f) { + SetSeek(m_pedInObjective, 1.0f); + } else { + SetAttack(m_pedInObjective); + SetShootTimer(CGeneral::GetRandomNumberInRange(500.0f, 1500.0f)); + } + SetAttackTimer(1000); + } + } else { + RestorePreviousObjective(); + } + break; + } + case OBJECTIVE_FOLLOW_ROUTE: + if (HaveReachedNextPointOnRoute(1.0f)) { + int nextPoint = GetNextPointOnRoute(); + m_nextRoutePointPos = CRouteNode::GetPointPosition(nextPoint); + } else { + SetSeek(m_nextRoutePointPos, 0.8f); + } + break; + case OBJECTIVE_SOLICIT_VEHICLE: + if (m_carInObjective) { + if (m_objectiveTimer <= CTimer::GetTimeInMilliseconds()) { + if (!bInVehicle) { + SetObjective(OBJECTIVE_NONE); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + m_objectiveTimer = CTimer::GetTimeInMilliseconds() + 10000; + if (IsPedInControl()) + m_pMyVehicle = nil; + } + } else { + if (m_nPedState != PED_FLEE_ENTITY && m_nPedState != PED_SOLICIT) + SetSeekCar(m_carInObjective, 0); + } + } else { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + } + break; + case OBJECTIVE_HAIL_TAXI: + if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HAILTAXI) && CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + Say(SOUND_PED_TAXI_WAIT); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HAILTAXI, 4.0f); + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2000; + } + break; + case OBJECTIVE_CATCH_TRAIN: + { + if (m_carInObjective) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_carInObjective); + } else { + CVehicle* trainToEnter = nil; + float closestCarDist = CHECK_NEARBY_THINGS_MAX_DIST; + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity* vehicles[8]; + + CWorld::FindObjectsInRange(pos, 10.0f, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + for (int i = 0; i < lastVehicle; i++) { + CVehicle* nearVeh = (CVehicle*)vehicles[i]; + if (nearVeh->IsTrain()) { + CVector vehDistVec = GetPosition() - nearVeh->GetPosition(); + float vehDist = vehDistVec.Magnitude(); + if (vehDist < closestCarDist && m_pedInObjective->m_pMyVehicle != nearVeh) + { + trainToEnter = nearVeh; + closestCarDist = vehDist; + } + } + } + if (trainToEnter) { + m_carInObjective = trainToEnter; + m_carInObjective->RegisterReference((CEntity **) &m_carInObjective); + } + } + break; + } + case OBJECTIVE_BUY_ICE_CREAM: + if (m_carInObjective) { + if (m_nPedState != PED_FLEE_ENTITY && m_nPedState != PED_BUY_ICECREAM) + SetSeekCar(m_carInObjective, 0); + } else { + RestorePreviousObjective(); + RestorePreviousState(); + if (IsPedInControl()) + m_pMyVehicle = nil; + } + break; + case OBJECTIVE_STEAL_ANY_CAR: + { + if (bInVehicle) { + bScriptObjectiveCompleted = true; + RestorePreviousObjective(); + } else if (m_carJackTimer < CTimer::GetTimeInMilliseconds()) { + CVehicle *carToSteal = nil; + float closestCarDist = ENTER_CAR_MAX_DIST; + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity *vehicles[8]; + + // NB: This should've been ENTER_CAR_MAX_DIST actually, and is fixed in VC. + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + for(int i = 0; i < lastVehicle; i++) { + CVehicle *nearVeh = (CVehicle*)vehicles[i]; + if (nearVeh->VehicleCreatedBy != MISSION_VEHICLE) { + if (nearVeh->m_vecMoveSpeed.Magnitude() <= 0.1f) { + if (nearVeh->CanPedOpenLocks(this)) { + CVector vehDistVec = GetPosition() - nearVeh->GetPosition(); + float vehDist = vehDistVec.Magnitude(); + if (vehDist < closestCarDist) { + carToSteal = nearVeh; + closestCarDist = vehDist; + } + } + } + } + } + if (carToSteal) { + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, carToSteal); + m_carJackTimer = CTimer::GetTimeInMilliseconds() + 5000; + } else { + RestorePreviousObjective(); + RestorePreviousState(); + } + } + break; + } + case OBJECTIVE_MUG_CHAR: + { + if (m_pedInObjective) { + if (m_pedInObjective->IsPlayer() || m_pedInObjective->bInVehicle || m_pedInObjective->m_fHealth <= 0.0f) { + ClearObjective(); + return; + } + if (m_pedInObjective->m_nMoveState > PEDMOVE_WALK) { + ClearObjective(); + return; + } + if (m_pedInObjective->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && m_pedInObjective->m_pedInObjective == this) { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, m_pedInObjective); + SetMoveState(PEDMOVE_SPRINT); + return; + } + if (m_pedInObjective->m_nPedState == PED_FLEE_ENTITY && m_fleeFrom == this + || m_pedInObjective->m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE && m_pedInObjective->m_pedInObjective == this) { + ClearObjective(); + SetFindPathAndFlee(m_pedInObjective, 15000, true); + return; + } + float distWithTargetScSqr = distWithTarget.MagnitudeSqr(); + if (distWithTargetScSqr <= sq(10.0f)) { + if (distWithTargetScSqr <= sq(1.4f)) { + CAnimBlendAssociation *reloadAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_AK_RELOAD); + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + m_pedInObjective->GetPosition().x, m_pedInObjective->GetPosition().y, + GetPosition().x, GetPosition().y); + + if (reloadAssoc || !m_pedInObjective->IsPedShootable()) { + if (reloadAssoc && + (!reloadAssoc->IsRunning() || reloadAssoc->currentTime / reloadAssoc->hierarchy->totalLength > 0.8f)) { + CAnimBlendAssociation *punchAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PARTIAL_PUNCH, 8.0f); + punchAssoc->flags |= ASSOC_DELETEFADEDOUT; + punchAssoc->flags |= ASSOC_FADEOUTWHENDONE; + CVector2D offset(distWithTarget.x, distWithTarget.y); + int dir = m_pedInObjective->GetLocalDirection(offset); + m_pedInObjective->StartFightDefend(dir, HITLEVEL_HIGH, 5); + m_pedInObjective->ReactToAttack(this); + m_pedInObjective->Say(SOUND_PED_ROBBED); + Say(SOUND_PED_MUGGING); + bRichFromMugging = true; + + // VC FIX: ClearObjective() clears m_pedInObjective in VC (also same with VC_PED_PORTS), so get it before call + CPed *victim = m_pedInObjective; + ClearObjective(); + if (victim->m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT + || victim->m_pedInObjective != this) { + SetFindPathAndFlee(victim, 15000, true); + m_nLastPedState = PED_WANDER_PATH; + } else { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, victim); + SetMoveState(PEDMOVE_SPRINT); + m_nLastPedState = PED_WANDER_PATH; + } + } + } else { + eWeaponType weaponType = GetWeapon()->m_eWeaponType; + if (weaponType != WEAPONTYPE_UNARMED && weaponType != WEAPONTYPE_BASEBALLBAT) + SetCurrentWeapon(WEAPONTYPE_UNARMED); + + CAnimBlendAssociation *newReloadAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_AK_RELOAD, 8.0f); + newReloadAssoc->flags |= ASSOC_DELETEFADEDOUT; + newReloadAssoc->flags |= ASSOC_FADEOUTWHENDONE; + } + } else { + SetSeek(m_pedInObjective, 1.0f); + CAnimBlendAssociation *walkAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_WALK); + + if (walkAssoc) + walkAssoc->speed = 1.3f; + } + } else { + ClearObjective(); + SetWanderPath(CGeneral::GetRandomNumber() & 7); + } + } else { +#ifdef VC_PED_PORTS + m_objective = OBJECTIVE_NONE; +#endif + ClearObjective(); + } + break; + } + case OBJECTIVE_FLEE_CAR: + if (!bInVehicle && m_nPedState != PED_FLEE_ENTITY && m_pMyVehicle) { + RestorePreviousObjective(); + SetFlee(m_pMyVehicle, 6000); + break; + } + // fall through + case OBJECTIVE_LEAVE_CAR: + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer) { + if (InVehicle() +#ifdef VC_PED_PORTS + && (FindPlayerPed() != this || !CPad::GetPad(0)->GetAccelerate() + || bBusJacked) +#endif + ) { + if (m_nPedState != PED_EXIT_CAR && m_nPedState != PED_DRAG_FROM_CAR && m_nPedState != PED_EXIT_TRAIN + && (m_nPedType != PEDTYPE_COP +#ifdef VC_PED_PORTS + || m_pMyVehicle->IsBoat() +#endif + || m_pMyVehicle->m_vecMoveSpeed.MagnitudeSqr2D() < sq(0.005f))) { + if (m_pMyVehicle->IsTrain()) + SetExitTrain(m_pMyVehicle); +#ifdef VC_PED_PORTS + else if (m_pMyVehicle->IsBoat()) + SetExitBoat(m_pMyVehicle); +#endif + else + SetExitCar(m_pMyVehicle, 0); + } + } else { + RestorePreviousObjective(); + } + } + break; +#ifdef VC_PED_PORTS + case OBJECTIVE_LEAVE_CAR_AND_DIE: + { + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer) { + if (InVehicle()) { + if (m_nPedState != PED_EXIT_CAR && m_nPedState != PED_DRAG_FROM_CAR && m_nPedState != PED_EXIT_TRAIN) { + if (m_pMyVehicle->IsBoat()) + SetExitBoat(m_pMyVehicle); + else if (m_pMyVehicle->bIsBus) + SetExitCar(m_pMyVehicle, 0); + else { + eCarNodes doorNode = CAR_DOOR_LF; + if (m_pMyVehicle->pDriver != this) { + if (m_pMyVehicle->pPassengers[0] == this) { + doorNode = CAR_DOOR_RF; + } else if (m_pMyVehicle->pPassengers[1] == this) { + doorNode = CAR_DOOR_LR; + } else if (m_pMyVehicle->pPassengers[2] == this) { + doorNode = CAR_DOOR_RR; + } + } + SetBeingDraggedFromCar(m_pMyVehicle, doorNode, false); + } + } + } + } + break; + } +#endif + } + if (bObjectiveCompleted + || m_objectiveTimer > 0 && CTimer::GetTimeInMilliseconds() > m_objectiveTimer) { + RestorePreviousObjective(); + if (m_objectiveTimer > CTimer::GetTimeInMilliseconds() || !m_objectiveTimer) + m_objectiveTimer = CTimer::GetTimeInMilliseconds() - 1; + + if (CharCreatedBy != RANDOM_CHAR || bInVehicle) { + if (IsPedInControl()) + RestorePreviousState(); + } else { + SetWanderPath(CGeneral::GetRandomNumber() & 7); + } + ClearAimFlag(); + ClearLookFlag(); + } + } +} + +void +CPed::SetFollowRoute(int16 currentPoint, int16 routeType) +{ + m_routeLastPoint = currentPoint; + m_routeStartPoint = CRouteNode::GetRouteStart(currentPoint); + m_routePointsPassed = 0; + m_routeType = routeType; + m_routePointsBeingPassed = 1; + m_objective = OBJECTIVE_FOLLOW_ROUTE; + m_nextRoutePointPos = CRouteNode::GetPointPosition(GetNextPointOnRoute()); +} + +int +CPed::GetNextPointOnRoute(void) +{ + int16 nextPoint = m_routePointsBeingPassed + m_routePointsPassed + m_routeStartPoint; + + // Route is complete + if (nextPoint < 0 || nextPoint > NUMPEDROUTES || m_routeLastPoint != CRouteNode::GetRouteThisPointIsOn(nextPoint)) { + + switch (m_routeType) { + case PEDROUTE_STOP_WHEN_DONE: + nextPoint = -1; + break; + case PEDROUTE_GO_BACKWARD_WHEN_DONE: + m_routePointsBeingPassed = -m_routePointsBeingPassed; + nextPoint = m_routePointsBeingPassed + m_routePointsPassed + m_routeStartPoint; + break; + case PEDROUTE_GO_TO_START_WHEN_DONE: + m_routePointsPassed = -1; + nextPoint = m_routePointsBeingPassed + m_routePointsPassed + m_routeStartPoint; + break; + default: + break; + } + } + return nextPoint; +} + +bool +CPed::HaveReachedNextPointOnRoute(float distToCountReached) +{ + if ((m_nextRoutePointPos - GetPosition()).Magnitude2D() >= distToCountReached) + return false; + + m_routePointsPassed += m_routePointsBeingPassed; + return true; +} + +bool +CPed::CanSeeEntity(CEntity *entity, float threshold) +{ + float neededAngle = CGeneral::GetRadianAngleBetweenPoints( + entity->GetPosition().x, + entity->GetPosition().y, + GetPosition().x, + GetPosition().y); + + if (neededAngle < 0.0f) + neededAngle += TWOPI; + else if (neededAngle > TWOPI) + neededAngle -= TWOPI; + + float ourAngle = m_fRotationCur; + if (ourAngle < 0.0f) + ourAngle += TWOPI; + else if (ourAngle > TWOPI) + ourAngle -= TWOPI; + + float neededTurn = Abs(neededAngle - ourAngle); + + return neededTurn < threshold || TWOPI - threshold < neededTurn; +} + +// Only used while deciding which gun ped should switch to, if no ammo left. +bool +CPed::SelectGunIfArmed(void) +{ + for (int i = 0; i < m_maxWeaponTypeAllowed; i++) { + if (GetWeapon(i).m_nAmmoTotal > 0) { + eWeaponType weaponType = GetWeapon(i).m_eWeaponType; + if (weaponType == WEAPONTYPE_BASEBALLBAT || weaponType == WEAPONTYPE_COLT45 || weaponType == WEAPONTYPE_UZI || weaponType == WEAPONTYPE_SHOTGUN || + weaponType == WEAPONTYPE_M16 || weaponType == WEAPONTYPE_SNIPERRIFLE || weaponType == WEAPONTYPE_ROCKETLAUNCHER) { + SetCurrentWeapon(i); + return true; + } + } + } + SetCurrentWeapon(WEAPONTYPE_UNARMED); + return false; +} + +void +CPed::ReactToPointGun(CEntity *entWithGun) +{ + CPed *pedWithGun = (CPed*)entWithGun; + int waitTime; + + if (IsPlayer() || !IsPedInControl() || CharCreatedBy == MISSION_CHAR) + return; + + if (m_leader == pedWithGun) + return; + + if (m_nWaitState == WAITSTATE_PLAYANIM_HANDSUP || m_nWaitState == WAITSTATE_PLAYANIM_HANDSCOWER || + (GetPosition() - pedWithGun->GetPosition()).MagnitudeSqr2D() > 225.0f) + return; + + if (m_leader) { + if (FindPlayerPed() == m_leader) + return; + + ClearLeader(); + } + if (m_pedStats->m_flags & STAT_GUN_PANIC + && (m_nPedState != PED_ATTACK || GetWeapon()->IsTypeMelee()) + && m_nPedState != PED_FLEE_ENTITY && m_nPedState != PED_AIM_GUN) { + + waitTime = CGeneral::GetRandomNumberInRange(3000, 6000); + SetWaitState(WAITSTATE_PLAYANIM_HANDSCOWER, &waitTime); + Say(SOUND_PED_HANDS_COWER); + m_pLookTarget = pedWithGun; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + SetMoveState(PEDMOVE_NONE); + + } else if (m_nPedType != pedWithGun->m_nPedType) { + if (IsGangMember() || m_nPedType == PEDTYPE_EMERGENCY || m_nPedType == PEDTYPE_FIREMAN) { + RegisterThreatWithGangPeds(pedWithGun); + } + + if (m_nPedType == PEDTYPE_COP) { + if (pedWithGun->IsPlayer()) { + ((CPlayerPed*)pedWithGun)->m_pWanted->SetWantedLevelNoDrop(2); + if (bCrouchWhenShooting || bKindaStayInSamePlace) { + SetDuck(CGeneral::GetRandomNumberInRange(1000, 3000)); + return; + } + } + } + + if (m_nPedType != PEDTYPE_COP + && (m_nPedState != PED_ATTACK || GetWeapon()->IsTypeMelee()) + && (m_nPedState != PED_FLEE_ENTITY || pedWithGun->IsPlayer() && m_fleeFrom != pedWithGun) + && m_nPedState != PED_AIM_GUN && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + + waitTime = CGeneral::GetRandomNumberInRange(3000, 6000); + SetWaitState(WAITSTATE_PLAYANIM_HANDSUP, &waitTime); + Say(SOUND_PED_HANDS_UP); + m_pLookTarget = pedWithGun; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + SetMoveState(PEDMOVE_NONE); + if (m_nPedState == PED_FLEE_ENTITY) { + m_fleeFrom = pedWithGun; + m_fleeFrom->RegisterReference((CEntity **) &m_fleeFrom); + } + + if (FindPlayerPed() == pedWithGun && bRichFromMugging) { + int money = CGeneral::GetRandomNumberInRange(100, 300); + int pickupCount = money / 40 + 1; + int moneyPerPickup = money / pickupCount; + + for (int i = 0; i < pickupCount; i++) { + float pickupX = 1.5f * Sin((CGeneral::GetRandomNumber() % 256)/256.0f * TWOPI) + GetPosition().x; + float pickupY = 1.5f * Cos((CGeneral::GetRandomNumber() % 256)/256.0f * TWOPI) + GetPosition().y; + bool found = false; + float groundZ = CWorld::FindGroundZFor3DCoord(pickupX, pickupY, GetPosition().z, &found) + 0.5f; + if (found) { + CPickups::GenerateNewOne(CVector(pickupX, pickupY, groundZ), MI_MONEY, PICKUP_MONEY, moneyPerPickup + (CGeneral::GetRandomNumber() & 7)); + } + } + bRichFromMugging = false; + } + } + } +} + +void +CPed::ReactToAttack(CEntity *attacker) +{ + if (IsPlayer() && attacker->IsPed()) { + InformMyGangOfAttack(attacker); + SetLookFlag(attacker, true); + SetLookTimer(700); + return; + } + +#ifdef VC_PED_PORTS + if (m_nPedState == PED_DRIVING && InVehicle() + && (m_pMyVehicle->pDriver == this || m_pMyVehicle->pDriver && m_pMyVehicle->pDriver->m_nPedState == PED_DRIVING && m_pMyVehicle->pDriver->m_objective != OBJECTIVE_LEAVE_CAR_AND_DIE)) { + + if (m_pMyVehicle->VehicleCreatedBy == RANDOM_VEHICLE + && (m_pMyVehicle->GetStatus() == STATUS_SIMPLE || m_pMyVehicle->GetStatus() == STATUS_PHYSICS) + && m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE) { + + CCarCtrl::SwitchVehicleToRealPhysics(m_pMyVehicle); + m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * m_pMyVehicle->pHandling->Transmission.fMaxCruiseVelocity; + m_pMyVehicle->SetStatus(STATUS_PHYSICS); + } + } else +#endif + if (IsPedInControl() && (CharCreatedBy != MISSION_CHAR || bRespondsToThreats)) { + CPed *ourLeader = m_leader; + if (ourLeader != attacker && (!ourLeader || FindPlayerPed() != ourLeader) + && attacker->IsPed()) { + + CPed *attackerPed = (CPed*)attacker; + if (bNotAllowedToDuck) { + if (!attackerPed->GetWeapon()->IsTypeMelee()) { + m_duckAndCoverTimer = CTimer::GetTimeInMilliseconds(); + return; + } + } else if (bCrouchWhenShooting || bKindaStayInSamePlace) { + SetDuck(CGeneral::GetRandomNumberInRange(1000, 3000)); + return; + } + + if (m_pedStats->m_fear <= 100 - attackerPed->m_pedStats->m_temper) { + if (m_pedStats != attackerPed->m_pedStats) { + if (IsGangMember() || m_nPedType == PEDTYPE_EMERGENCY || m_nPedType == PEDTYPE_FIREMAN) { + RegisterThreatWithGangPeds(attackerPed); + } + if (!attackerPed->GetWeapon()->IsTypeMelee() && GetWeapon()->IsTypeMelee()) { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, attacker); + SetMoveState(PEDMOVE_RUN); + } else { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, attacker); + SetObjectiveTimer(20000); + } + } + } else { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, attackerPed); + SetMoveState(PEDMOVE_RUN); + if (attackerPed->GetWeapon()->IsTypeMelee()) + Say(SOUND_PED_FLEE_RUN); + } + } + } +} + +void +CPed::PedAnimAlignCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (!ped->IsNotInWreckedVehicle()) + return; + + if (!ped->EnteringCar()) { +#ifdef VC_PED_PORTS + if (ped->m_nPedState != PED_DRIVING) +#endif + ped->QuitEnteringCar(); + + return; + } + if (ped->m_fHealth == 0.0f) { + ped->QuitEnteringCar(); + return; + } + bool itsVan = !!veh->bIsVan; + bool itsBus = !!veh->bIsBus; +#ifdef FIX_BUGS + bool itsLow = !!veh->bLowVehicle; +#endif + eDoors enterDoor; + AnimationId enterAnim; + + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: + itsVan = false; + enterDoor = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_RR: + enterDoor = DOOR_REAR_RIGHT; + break; + case CAR_DOOR_LF: + itsVan = false; + enterDoor = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + enterDoor = DOOR_REAR_LEFT; + break; + default: + break; + } + + if (veh->IsDoorMissing(enterDoor) || veh->IsDoorFullyOpen(enterDoor)) { + + veh->AutoPilot.m_nCruiseSpeed = 0; + if (ped->m_nPedState == PED_CARJACK) { + ped->PedAnimDoorOpenCB(nil, ped); + return; + } + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) { + if (itsVan) { + enterAnim = ANIM_STD_VAN_GET_IN_REAR_RHS; + } else if (itsBus) { + enterAnim = ANIM_STD_COACH_GET_IN_RHS; +#ifdef FIX_BUGS + } else if (itsLow) { + enterAnim = ANIM_STD_CAR_GET_IN_LO_RHS; +#endif + } else { + enterAnim = ANIM_STD_CAR_GET_IN_RHS; + } + } else if (itsVan) { + enterAnim = ANIM_STD_VAN_GET_IN_REAR_LHS; + } else if (itsBus) { + enterAnim = ANIM_STD_COACH_GET_IN_LHS; +#ifdef FIX_BUGS + } else if (itsLow) { + enterAnim = ANIM_STD_CAR_GET_IN_LO_LHS; +#endif + } else { + enterAnim = ANIM_STD_CAR_GET_IN_LHS; + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, enterAnim); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + + } else if (veh->CanPedOpenLocks(ped)) { + + veh->AutoPilot.m_nCruiseSpeed = 0; + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) { + if (itsVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_VAN_OPEN_DOOR_REAR_RHS); + } else if (itsBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_COACH_OPEN_RHS); + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_OPEN_DOOR_RHS); + } + } else if (itsVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_VAN_OPEN_DOOR_REAR_LHS); + } else if (itsBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_COACH_OPEN_LHS); + } else { + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && veh->pDriver) { + + if (!veh->bLowVehicle + && veh->pDriver->CharCreatedBy != MISSION_CHAR + && veh->pDriver->m_nPedState == PED_DRIVING) { + + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_QUICKJACK); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + veh->pDriver->SetBeingDraggedFromCar(veh, ped->m_vehDoor, true); + + if (veh->pDriver->IsGangMember()) + veh->pDriver->RegisterThreatWithGangPeds(ped); + return; + } + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_OPEN_DOOR_LHS); + } + ped->m_pVehicleAnim->SetFinishCallback(PedAnimDoorOpenCB, ped); + + } else { + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CARDOOR_LOCKED_RHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CARDOOR_LOCKED_LHS); + + ped->bCancelEnteringCar = true; + ped->m_pVehicleAnim->SetFinishCallback(PedAnimDoorOpenCB, ped); + } +} + +void +CPed::PedAnimDoorOpenCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + CVehicle* veh = ped->m_pMyVehicle; + + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (!ped->IsNotInWreckedVehicle()) + return; + + if (!ped->EnteringCar()) { +#ifdef VC_PED_PORTS + if (ped->m_nPedState != PED_DRIVING) +#endif + ped->QuitEnteringCar(); + + return; + } + + eDoors door; + CPed *pedInSeat = nil; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: door = DOOR_FRONT_RIGHT; pedInSeat = veh->pPassengers[0]; break; + case CAR_DOOR_RR: door = DOOR_REAR_RIGHT; pedInSeat = veh->pPassengers[2]; break; + case CAR_DOOR_LF: door = DOOR_FRONT_LEFT; pedInSeat = veh->pDriver; break; + case CAR_DOOR_LR: door = DOOR_REAR_LEFT; pedInSeat = veh->pPassengers[1]; break; + default: assert(0); + } + + if (ped->m_fHealth == 0.0f || CPad::GetPad(0)->ArePlayerControlsDisabled() && pedInSeat && pedInSeat->IsPlayer()) { + ped->QuitEnteringCar(); + return; + } + + bool isVan = veh->bIsVan; + bool isBus = veh->bIsBus; + bool isLow = veh->bLowVehicle; + bool vehUpsideDown = veh->IsUpsideDown(); + if (ped->bCancelEnteringCar) { + if (ped->IsPlayer()) { + if (veh->pDriver) { + if (veh->pDriver->m_nPedType == PEDTYPE_COP) { + FindPlayerPed()->SetWantedLevelNoDrop(1); + } + } + } +#ifdef CANCELLABLE_CAR_ENTER + if (!veh->IsDoorMissing(door) && veh->CanPedOpenLocks(ped) && veh->IsCar()) { + ((CAutomobile*)veh)->Damage.SetDoorStatus(door, DOOR_STATUS_SWINGING); + } +#endif + ped->QuitEnteringCar(); + ped->RestorePreviousObjective(); + ped->bCancelEnteringCar = false; + return; + } + if (!veh->IsDoorMissing(door) && veh->IsCar()) { + ((CAutomobile*)veh)->Damage.SetDoorStatus(door, DOOR_STATUS_SWINGING); + } + + if (veh->m_vecMoveSpeed.Magnitude() > 0.2f) { + ped->QuitEnteringCar(); + if (ped->m_vehDoor != CAR_DOOR_LF && ped->m_vehDoor != CAR_DOOR_LR) + ped->SetFall(1000, ANIM_STD_HIGHIMPACT_LEFT, false); + else + ped->SetFall(1000, ANIM_STD_HIGHIMPACT_RIGHT, false); + + return; + } + veh->ProcessOpenDoor(ped->m_vehDoor, ANIM_STD_CAR_OPEN_DOOR_LHS, 1.0f); + + if (ped->m_vehDoor == CAR_DOOR_LF || ped->m_vehDoor == CAR_DOOR_RF) + isVan = false; + + if (ped->m_nPedState != PED_CARJACK || isBus) { + AnimationId animToPlay; + if (ped->m_vehDoor != CAR_DOOR_LF && ped->m_vehDoor != CAR_DOOR_LR) { + + if (isVan) { + animToPlay = ANIM_STD_VAN_GET_IN_REAR_RHS; + } else if (isBus) { + animToPlay = ANIM_STD_COACH_GET_IN_RHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_GET_IN_LO_RHS; + } else { + animToPlay = ANIM_STD_CAR_GET_IN_RHS; + } + } else if (isVan) { + animToPlay = ANIM_STD_VAN_GET_IN_REAR_LHS; + } else if (isBus) { + animToPlay = ANIM_STD_COACH_GET_IN_LHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_GET_IN_LO_LHS; + } else { + animToPlay = ANIM_STD_CAR_GET_IN_LHS; + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, animToPlay); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } else { + CPed *pedToDragOut = nil; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: pedToDragOut = veh->pPassengers[0]; break; + case CAR_DOOR_RR: pedToDragOut = veh->pPassengers[2]; break; + case CAR_DOOR_LF: pedToDragOut = veh->pDriver; break; + case CAR_DOOR_LR: pedToDragOut = veh->pPassengers[1]; break; + default: assert(0); + } + + if (vehUpsideDown) { + ped->QuitEnteringCar(); + if (ped->m_nPedType == PEDTYPE_COP) + ((CCopPed*)ped)->SetArrestPlayer(ped->m_pedInObjective); + } + + if (ped->m_vehDoor != CAR_DOOR_LF && ped->m_vehDoor != CAR_DOOR_LR) { + if (pedToDragOut && !pedToDragOut->bDontDragMeOutCar) { + if (pedToDragOut->m_nPedState != PED_DRIVING) { + ped->QuitEnteringCar(); + pedToDragOut = nil; + } else { + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_PULL_OUT_PED_LO_RHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_PULL_OUT_PED_RHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedAnimPullPedOutCB, ped); + } + } else if (ped->m_nPedType == PEDTYPE_COP) { + ped->QuitEnteringCar(); + if (ped->m_pedInObjective && ped->m_pedInObjective->m_nPedState == PED_DRIVING) { + veh->SetStatus(STATUS_PLAYER_DISABLED); + ((CCopPed*)ped)->SetArrestPlayer(ped->m_pedInObjective); + } else if (!veh->IsDoorMissing(DOOR_FRONT_RIGHT)) { + ((CAutomobile*)veh)->Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_SWINGING); + } + } else { + // BUG: Probably we will sit on top of the passenger if his m_ped_flagF4 is true. + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_LHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } + } else { + if (pedToDragOut) { + if (pedToDragOut->m_nPedState != PED_DRIVING || pedToDragOut->bDontDragMeOutCar) { + + // BUG: Player freezes in that condition due to its objective isn't restored. It's an unfinished feature, used in VC. + ped->QuitEnteringCar(); + pedToDragOut = nil; + } else { + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_PULL_OUT_PED_LO_LHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_PULL_OUT_PED_LHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedAnimPullPedOutCB, ped); + } + } else { + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_LHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } + } + + if (pedToDragOut) { + pedToDragOut->SetBeingDraggedFromCar(veh, ped->m_vehDoor, false); + if (pedToDragOut->IsGangMember()) + pedToDragOut->RegisterThreatWithGangPeds(ped); + } + } + + if (veh->pDriver && ped) { + veh->pDriver->SetLookFlag(ped, true); + veh->pDriver->SetLookTimer(1000); + } + return; +} + +void +CPed::PedAnimPullPedOutCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + CVehicle* veh = ped->m_pMyVehicle; + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (ped->EnteringCar()) { + if (!ped->IsNotInWreckedVehicle()) + return; + +#ifdef CANCELLABLE_CAR_ENTER + if (ped->bCancelEnteringCar) { + ped->QuitEnteringCar(); + ped->RestorePreviousObjective(); + ped->bCancelEnteringCar = false; + return; + } +#endif + + bool isLow = !!veh->bLowVehicle; + + int padNo; + if (ped->IsPlayer()) { + + // BUG? This will cause crash if m_nPedType is bigger then 1, there are only 2 pads + switch (ped->m_nPedType) { + case PEDTYPE_PLAYER1: + padNo = 0; + break; + case PEDTYPE_PLAYER2: + padNo = 1; + break; + case PEDTYPE_PLAYER3: + padNo = 2; + break; + case PEDTYPE_PLAYER4: + padNo = 3; + break; + } + CPad *pad = CPad::GetPad(padNo); + + if (!pad->ArePlayerControlsDisabled()) { + + if (pad->GetTarget() + || pad->NewState.LeftStickX + || pad->NewState.LeftStickY + || pad->NewState.DPadUp + || pad->NewState.DPadDown + || pad->NewState.DPadLeft + || pad->NewState.DPadRight) { + ped->QuitEnteringCar(); + ped->RestorePreviousObjective(); + return; + } + } + } + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + AnimationId animToPlay; + if (ped->m_vehDoor != CAR_DOOR_LF && ped->m_vehDoor != CAR_DOOR_LR) { + if (isLow) + animToPlay = ANIM_STD_CAR_GET_IN_LO_RHS; + else + animToPlay = ANIM_STD_CAR_GET_IN_RHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_GET_IN_LO_LHS; + } else { + animToPlay = ANIM_STD_CAR_GET_IN_LHS; + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, animToPlay); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } else { + ped->QuitEnteringCar(); + } + } else { + ped->QuitEnteringCar(); + } +} + +void +CPed::PedAnimGetInCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*) arg; + + CVehicle *veh = ped->m_pMyVehicle; + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (!ped->IsNotInWreckedVehicle() || ped->DyingOrDead()) + return; + + if (!ped->EnteringCar()) { +#ifdef VC_PED_PORTS + if(ped->m_nPedState != PED_DRIVING) +#endif + ped->QuitEnteringCar(); + return; + } + + if (ped->IsPlayer() && ped->bGonnaKillTheCarJacker && ((CPlayerPed*)ped)->m_pArrestingCop) { + PedSetInCarCB(nil, ped); + ped->m_nLastPedState = ped->m_nPedState; + ped->SetPedState(PED_ARRESTED); + ped->bGonnaKillTheCarJacker = false; + if (veh) { + veh->m_nNumGettingIn = 0; + veh->m_nGettingInFlags = 0; + veh->bIsHandbrakeOn = true; + veh->SetStatus(STATUS_PLAYER_DISABLED); + } + return; + } + if (ped->IsPlayer() && ped->m_vehDoor == CAR_DOOR_LF + && (Pads[0].GetAccelerate() >= 255.0f || Pads[0].GetBrake() >= 255.0f) + && veh->IsCar()) { + if (((CAutomobile*)veh)->Damage.GetDoorStatus(DOOR_FRONT_LEFT) != DOOR_STATUS_MISSING) + ((CAutomobile*)veh)->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_SWINGING); + + PedSetInCarCB(nil, ped); + return; + } + bool isVan = !!veh->bIsVan; + bool isBus = !!veh->bIsBus; + bool isLow = !!veh->bLowVehicle; + eDoors enterDoor; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: + isVan = false; + enterDoor = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_RR: + enterDoor = DOOR_REAR_RIGHT; + break; + case CAR_DOOR_LF: + isVan = false; + enterDoor = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + enterDoor = DOOR_REAR_LEFT; + break; + default: + break; + } + if (!veh->IsDoorMissing(enterDoor)) { + if (veh->IsCar()) + ((CAutomobile*)veh)->Damage.SetDoorStatus(enterDoor, DOOR_STATUS_SWINGING); + } + CPed *driver = veh->pDriver; + if (driver && (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || ped->m_nPedState == PED_CARJACK)) { + if (veh->bIsBus) { + driver->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + if (driver->IsPlayer()) { + veh->bIsHandbrakeOn = true; + veh->SetStatus(STATUS_PLAYER_DISABLED); + } + driver->bBusJacked = true; + veh->bIsBeingCarJacked = false; + PedSetInCarCB(nil, ped); + if (ped->m_nPedType == PEDTYPE_COP + || ped->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT + || ped->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) { + ped->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + } + ped->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 400; + return; + } + if (driver != ped && ped->m_vehDoor != CAR_DOOR_LF) { + if (!driver->IsPlayer()) { + driver->bUsePedNodeSeek = true; + driver->m_pLastPathNode = nil; + if (driver->m_pedStats->m_temper <= driver->m_pedStats->m_fear + || driver->CharCreatedBy == MISSION_CHAR + || veh->VehicleCreatedBy == MISSION_VEHICLE) { + driver->bFleeAfterExitingCar = true; + } else { + driver->bGonnaKillTheCarJacker = true; + veh->pDriver->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, ped); + + if (veh->pDriver->m_nPedType == PEDTYPE_COP && ped->IsPlayer()) { + FindPlayerPed()->SetWantedLevelNoDrop(1); + } + } + } + if ((ped->m_nPedType != PEDTYPE_EMERGENCY || veh->pDriver->m_nPedType != PEDTYPE_EMERGENCY) + && (ped->m_nPedType != PEDTYPE_COP || veh->pDriver->m_nPedType != PEDTYPE_COP)) { + veh->pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + veh->pDriver->Say(SOUND_PED_CAR_JACKED); +#ifdef VC_PED_PORTS + veh->pDriver->SetRadioStation(); +#endif + } else { + ped->m_objective = OBJECTIVE_ENTER_CAR_AS_PASSENGER; + } + } + } + if (veh->IsDoorMissing(enterDoor) || isBus) { + PedAnimDoorCloseCB(nil, ped); + } else { + AnimationId animToPlay; + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) { + if (isVan) { + animToPlay = ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_CLOSE_DOOR_LO_RHS; + } else { + animToPlay = ANIM_STD_CAR_CLOSE_DOOR_RHS; + } + } else if (isVan) { + animToPlay = ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS; + } else if (isLow) { + animToPlay = ANIM_STD_CAR_CLOSE_DOOR_LO_LHS; + } else { + animToPlay = ANIM_STD_CAR_CLOSE_DOOR_LHS; + } + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, animToPlay); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimDoorCloseCB, ped); + } +} + +void +CPed::PedAnimDoorCloseCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CAutomobile *veh = (CAutomobile*)(ped->m_pMyVehicle); + + if (!ped->IsNotInWreckedVehicle() || ped->DyingOrDead()) + return; + + if (ped->EnteringCar()) { + bool isLow = !!veh->bLowVehicle; + + if (!veh->bIsBus) + veh->ProcessOpenDoor(ped->m_vehDoor, ANIM_STD_CAR_CLOSE_DOOR_LHS, 1.0f); + + eDoors door; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: door = DOOR_FRONT_RIGHT; break; + case CAR_DOOR_RR: door = DOOR_REAR_RIGHT; break; + case CAR_DOOR_LF: door = DOOR_FRONT_LEFT; break; + case CAR_DOOR_LR: door = DOOR_REAR_LEFT; break; + default: assert(0); + } + + if (veh->Damage.GetDoorStatus(door) == DOOR_STATUS_SWINGING) + veh->Damage.SetDoorStatus(door, DOOR_STATUS_OK); + + if (door == DOOR_FRONT_LEFT || ped->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER || veh->bIsBus) { + PedSetInCarCB(nil, ped); + } else if (ped->m_vehDoor == CAR_DOOR_RF + && (veh->m_nGettingInFlags & CAR_DOOR_FLAG_LF || + (veh->pDriver != nil && + (veh->pDriver->m_objective != OBJECTIVE_LEAVE_CAR +#ifdef VC_PED_PORTS + && veh->pDriver->m_objective != OBJECTIVE_LEAVE_CAR_AND_DIE +#endif + || !veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, nil))))) { + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER +#if defined VC_PED_PORTS || defined FIX_BUGS + || ped->m_nPedState == PED_CARJACK +#endif + ) + veh->bIsBeingCarJacked = false; + + ped->m_objective = OBJECTIVE_ENTER_CAR_AS_PASSENGER; + PedSetInCarCB(nil, ped); + + ped->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + if (!ped->IsPlayer()) + ped->bFleeAfterExitingCar = true; + + ped->bUsePedNodeSeek = true; + ped->m_pNextPathNode = nil; + + } else { + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SHUFFLE_LO_RHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SHUFFLE_RHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedSetInCarCB, ped); + } + } else { +#ifdef VC_PED_PORTS + if (ped->m_nPedState != PED_DRIVING) +#endif + ped->QuitEnteringCar(); + } +} + +void +CPed::SetFormation(eFormation type) +{ + // FIX: Formations in GetFormationPosition were in range 1-8, whereas in here it's 0-7. + // To not change the behaviour, range in here tweaked by 1 with the use of enum. + + switch (m_pedFormation) { + case FORMATION_REAR: + case FORMATION_REAR_LEFT: + case FORMATION_REAR_RIGHT: + case FORMATION_FRONT_LEFT: + case FORMATION_FRONT_RIGHT: + case FORMATION_LEFT: + case FORMATION_RIGHT: + case FORMATION_FRONT: + break; + default: + Error("Unknown formation type, PedAI.cpp"); + break; + } + m_pedFormation = type; +} + +CVector +CPed::GetFormationPosition(void) +{ + if (m_pedInObjective->m_nPedState == PED_DEAD) { + if (!m_pedInObjective->m_pedInObjective) { + m_pedInObjective = nil; + return GetPosition(); + } + m_pedInObjective = m_pedInObjective->m_pedInObjective; + } + + CVector formationOffset; + switch (m_pedFormation) { + case FORMATION_REAR: + formationOffset = CVector(0.0f, -1.5f, 0.0f); + break; + case FORMATION_REAR_LEFT: + formationOffset = CVector(-1.5f, -1.5f, 0.0f); + break; + case FORMATION_REAR_RIGHT: + formationOffset = CVector(1.5f, -1.5f, 0.0f); + break; + case FORMATION_FRONT_LEFT: + formationOffset = CVector(-1.5f, 1.5f, 0.0f); + break; + case FORMATION_FRONT_RIGHT: + formationOffset = CVector(1.5f, 1.5f, 0.0f); + break; + case FORMATION_LEFT: + formationOffset = CVector(-1.5f, 0.0f, 0.0f); + break; + case FORMATION_RIGHT: + formationOffset = CVector(1.5f, 0.0f, 0.0f); + break; + case FORMATION_FRONT: + formationOffset = CVector(0.0f, 1.5f, 0.0f); + break; + default: + formationOffset = CVector(0.0f, 0.0f, 0.0f); + break; + } + return formationOffset + m_pedInObjective->GetPosition(); +} + +void +CPed::PedAnimStepOutCarCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + CVehicle* veh = ped->m_pMyVehicle; + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (!veh) { + PedSetOutCarCB(nil, ped); + return; + } +#ifdef VC_PED_PORTS + CVector posForZ = ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&posForZ); + if (ped->GetPosition().z - 0.5f > posForZ.z) { + PedSetOutCarCB(nil, ped); + return; + } +#endif + veh->m_nStaticFrames = 0; + veh->m_vecMoveSpeed += CVector(0.001f, 0.001f, 0.001f); + veh->m_vecTurnSpeed += CVector(0.001f, 0.001f, 0.001f); + if (!veh->bIsBus) + veh->ProcessOpenDoor(ped->m_vehDoor, ANIM_STD_GETOUT_LHS, 1.0f); + + /* + // Duplicate and only in PC for some reason + if (!veh) { + PedSetOutCarCB(nil, ped); + return; + } + */ + eDoors door; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: + door = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_RR: + door = DOOR_REAR_RIGHT; + break; + case CAR_DOOR_LF: + door = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + door = DOOR_REAR_LEFT; + break; + default: + break; + } + bool closeDoor = !veh->IsDoorMissing(door); + + int padNo; + if (ped->IsPlayer()) { + + // BUG? This will cause crash if m_nPedType is bigger then 1, there are only 2 pads + switch (ped->m_nPedType) { + case PEDTYPE_PLAYER1: + padNo = 0; + break; + case PEDTYPE_PLAYER2: + padNo = 1; + break; + case PEDTYPE_PLAYER3: + padNo = 2; + break; + case PEDTYPE_PLAYER4: + padNo = 3; + break; + } + CPad* pad = CPad::GetPad(padNo); + bool engineIsIntact = veh->IsCar() && ((CAutomobile*)veh)->Damage.GetEngineStatus() >= 225; + if (!pad->ArePlayerControlsDisabled() && veh->m_nDoorLock != CARLOCK_FORCE_SHUT_DOORS + && (pad->GetTarget() + || pad->NewState.LeftStickX + || pad->NewState.LeftStickY + || pad->NewState.DPadUp + || pad->NewState.DPadDown + || pad->NewState.DPadLeft + || pad->NewState.DPadRight) + || veh->bIsBus + || veh->m_pCarFire + || engineIsIntact) { + closeDoor = false; + } + } + +#ifdef VC_PED_PORTS + if (ped->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) + closeDoor = false; +#endif + + if (!closeDoor) { + if (!veh->IsDoorMissing(door) && !veh->bIsBus) { + ((CAutomobile*)veh)->Damage.SetDoorStatus(door, DOOR_STATUS_SWINGING); + } + PedSetOutCarCB(nil, ped); + return; + } + + if (ped->bFleeAfterExitingCar || ped->bGonnaKillTheCarJacker) { +#ifdef FIX_BUGS + if (!veh->IsDoorMissing(door)) + ((CAutomobile*)veh)->Damage.SetDoorStatus(door, DOOR_STATUS_SWINGING); + PedSetOutCarCB(nil, ped); + return; +#else + if (!veh->IsDoorMissing(door)) + ((CAutomobile*)veh)->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_SWINGING); +#endif + } else { + switch (door) { + case DOOR_FRONT_LEFT: + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_LHS); + break; + case DOOR_FRONT_RIGHT: + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_RHS); + break; + case DOOR_REAR_LEFT: + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_LHS); + break; + case DOOR_REAR_RIGHT: + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_RHS); + break; + default: + break; + } + } + + if (ped->m_pVehicleAnim) + ped->m_pVehicleAnim->SetFinishCallback(PedSetOutCarCB, ped); + return; +} + +void +CPed::LineUpPedWithCar(PedLineUpPhase phase) +{ + bool vehIsUpsideDown = false; + bool stillGettingInOut = false; + int vehAnim; + float seatPosMult = 0.0f; + float currentZ; + float adjustedTimeStep; + + if (CReplay::IsPlayingBack()) + return; + + if (!bChangedSeat && phase != LINE_UP_TO_CAR_2) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT)) { + SetPedPositionInCar(); + return; + } + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_LO)) { + SetPedPositionInCar(); + return; + } + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_P)) { + SetPedPositionInCar(); + return; + } + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_P_LO)) { + SetPedPositionInCar(); + return; + } + bChangedSeat = true; + } + if (phase == LINE_UP_TO_CAR_START) { + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } + CVehicle *veh = m_pMyVehicle; + + // Not quite right, IsUpsideDown func. checks for <= -0.9f. + if (veh->GetUp().z <= -0.8f) + vehIsUpsideDown = true; + + if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) { + if (vehIsUpsideDown) { + m_fRotationDest = -PI + veh->GetForward().Heading(); + } else if (veh->bIsBus) { + m_fRotationDest = 0.5f * PI + veh->GetForward().Heading(); + } else { + m_fRotationDest = veh->GetForward().Heading(); + } + } else if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) { + if (vehIsUpsideDown) { + m_fRotationDest = veh->GetForward().Heading(); + } else if (veh->bIsBus) { + m_fRotationDest = -0.5f * PI + veh->GetForward().Heading(); + } else { + m_fRotationDest = veh->GetForward().Heading(); + } + } else { + // I don't know will this part ever run(maybe boats?), but the game also handles that. I don't know is it intentional. + + if (vehIsUpsideDown) { + m_fRotationDest = veh->GetForward().Heading(); + } else if (veh->bIsBus) { + m_fRotationDest = 0.5f * PI + veh->GetForward().Heading(); + } else { + m_fRotationDest = veh->GetForward().Heading(); + } + } + + if (!bInVehicle) + seatPosMult = 1.0f; + +#ifdef VC_PED_PORTS + bool multExtractedFromAnim = false; + bool multExtractedFromAnimBus = false; + float zBlend; +#endif + if (m_pVehicleAnim) { + vehAnim = m_pVehicleAnim->animId; + + switch (vehAnim) { + case ANIM_STD_JACKEDCAR_RHS: + case ANIM_STD_JACKEDCAR_LO_RHS: + case ANIM_STD_JACKEDCAR_LHS: + case ANIM_STD_JACKEDCAR_LO_LHS: + case ANIM_STD_VAN_GET_IN_REAR_LHS: + case ANIM_STD_VAN_GET_IN_REAR_RHS: +#ifdef VC_PED_PORTS + multExtractedFromAnim = true; + zBlend = Max(m_pVehicleAnim->currentTime / m_pVehicleAnim->hierarchy->totalLength - 0.3f, 0.0f) / (1.0f - 0.3f); + // fall through +#endif + case ANIM_STD_QUICKJACKED: + case ANIM_STD_GETOUT_LHS: + case ANIM_STD_GETOUT_LO_LHS: + case ANIM_STD_GETOUT_RHS: + case ANIM_STD_GETOUT_LO_RHS: +#ifdef VC_PED_PORTS + if (!multExtractedFromAnim) { + multExtractedFromAnim = true; + zBlend = Max(m_pVehicleAnim->currentTime / m_pVehicleAnim->hierarchy->totalLength - 0.5f, 0.0f) / (1.0f - 0.5f); + } + // fall through +#endif + case ANIM_STD_CRAWLOUT_LHS: + case ANIM_STD_CRAWLOUT_RHS: + case ANIM_STD_VAN_GET_OUT_REAR_LHS: + case ANIM_STD_VAN_GET_OUT_REAR_RHS: + seatPosMult = m_pVehicleAnim->currentTime / m_pVehicleAnim->hierarchy->totalLength; + break; + case ANIM_STD_CAR_GET_IN_RHS: + case ANIM_STD_CAR_GET_IN_LHS: +#ifdef VC_PED_PORTS + if (veh && veh->IsCar() && veh->bIsBus) { + multExtractedFromAnimBus = true; + zBlend = Min(m_pVehicleAnim->currentTime / m_pVehicleAnim->hierarchy->totalLength, 0.5f) / 0.5f; + } + // fall through +#endif + case ANIM_STD_QUICKJACK: + case ANIM_STD_CAR_GET_IN_LO_LHS: + case ANIM_STD_CAR_GET_IN_LO_RHS: + case ANIM_STD_BOAT_DRIVE: + seatPosMult = m_pVehicleAnim->GetTimeLeft() / m_pVehicleAnim->hierarchy->totalLength; + break; + case ANIM_STD_CAR_CLOSE_DOOR_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_LO_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_RHS: + case ANIM_STD_CAR_CLOSE_DOOR_LO_RHS: + case ANIM_STD_CAR_SHUFFLE_RHS: + case ANIM_STD_CAR_SHUFFLE_LO_RHS: + seatPosMult = 0.0f; + break; + case ANIM_STD_CAR_CLOSE_LHS: + case ANIM_STD_CAR_CLOSE_RHS: + case ANIM_STD_COACH_OPEN_LHS: + case ANIM_STD_COACH_OPEN_RHS: + case ANIM_STD_COACH_GET_IN_LHS: + case ANIM_STD_COACH_GET_IN_RHS: + case ANIM_STD_COACH_GET_OUT_LHS: + seatPosMult = 1.0f; + break; + default: + break; + } + } + + CVector neededPos; + + if (phase == LINE_UP_TO_CAR_2) { + neededPos = GetPosition(); + } else { + neededPos = GetPositionToOpenCarDoor(veh, m_vehDoor, seatPosMult); + } + + CVector autoZPos = neededPos; + + if (veh->bIsInWater) { + if (veh->m_vehType == VEHICLE_TYPE_BOAT && veh->IsUpsideDown()) + autoZPos.z += 1.0f; + } else { + CPedPlacement::FindZCoorForPed(&autoZPos); + } + + if (phase == LINE_UP_TO_CAR_END || phase == LINE_UP_TO_CAR_2) { + neededPos.z = GetPosition().z; + + // Getting out + if (!veh->bIsBus || (veh->bIsBus && vehIsUpsideDown)) { + float nextZSpeed = m_vecMoveSpeed.z - GRAVITY * CTimer::GetTimeStep(); + + // If we're not in ground at next step, apply animation + if (neededPos.z + nextZSpeed >= autoZPos.z) { + m_vecMoveSpeed.z = nextZSpeed; + ApplyMoveSpeed(); + // Removing below line breaks the animation + neededPos.z = GetPosition().z; + } else { + neededPos.z = autoZPos.z; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } + } + } + + if (autoZPos.z > neededPos.z) { +#ifdef VC_PED_PORTS + if (multExtractedFromAnim) { + neededPos.z += (autoZPos.z - neededPos.z) * zBlend; + } else { +#endif + currentZ = GetPosition().z; + if (m_pVehicleAnim && vehAnim != ANIM_STD_VAN_GET_IN_REAR_LHS && vehAnim != ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS && vehAnim != ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS && vehAnim != ANIM_STD_VAN_GET_IN_REAR_RHS) { + neededPos.z = autoZPos.z; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } else if (neededPos.z < currentZ && m_pVehicleAnim && vehAnim != ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS && vehAnim != ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS) { + adjustedTimeStep = Max(m_pVehicleAnim->timeStep, 0.1f); + + // Smoothly change ped position + neededPos.z = currentZ - (currentZ - neededPos.z) / (m_pVehicleAnim->GetTimeLeft() / adjustedTimeStep); + } +#ifdef VC_PED_PORTS + } +#endif + } else { + // We may need to raise up the ped + if (phase == LINE_UP_TO_CAR_START) { + currentZ = GetPosition().z; + + if (neededPos.z > currentZ) { +#ifdef VC_PED_PORTS + if (multExtractedFromAnimBus) { + neededPos.z = (neededPos.z - currentZ) * zBlend + currentZ; + } else { +#endif + if (m_pVehicleAnim && + (vehAnim == ANIM_STD_CAR_GET_IN_RHS || vehAnim == ANIM_STD_CAR_GET_IN_LO_RHS || vehAnim == ANIM_STD_CAR_GET_IN_LHS || vehAnim == ANIM_STD_CAR_GET_IN_LO_LHS + || vehAnim == ANIM_STD_QUICKJACK || vehAnim == ANIM_STD_VAN_GET_IN_REAR_LHS || vehAnim == ANIM_STD_VAN_GET_IN_REAR_RHS)) { + adjustedTimeStep = Max(m_pVehicleAnim->timeStep, 0.1f); + + // Smoothly change ped position + neededPos.z = (neededPos.z - currentZ) / (m_pVehicleAnim->GetTimeLeft() / adjustedTimeStep) + currentZ; + } else if (EnteringCar()) { + neededPos.z = Max(currentZ, autoZPos.z); + } +#ifdef VC_PED_PORTS + } +#endif + } + } + } + + if (CTimer::GetTimeInMilliseconds() < m_nPedStateTimer) + stillGettingInOut = veh->m_vehType != VEHICLE_TYPE_BOAT || bOnBoat; + + if (!stillGettingInOut) { + m_fRotationCur = m_fRotationDest; + } else { + float limitedDest = CGeneral::LimitRadianAngle(m_fRotationDest); + float timeUntilStateChange = (m_nPedStateTimer - CTimer::GetTimeInMilliseconds())/600.0f; + + if (timeUntilStateChange <= 0.0f) { + m_vecOffsetSeek.x = 0.0f; + m_vecOffsetSeek.y = 0.0f; + } + m_vecOffsetSeek.z = 0.0f; + + neededPos -= timeUntilStateChange * m_vecOffsetSeek; + + if (PI + m_fRotationCur < limitedDest) { + limitedDest -= 2 * PI; + } else if (m_fRotationCur - PI > limitedDest) { + limitedDest += 2 * PI; + } + m_fRotationCur -= (m_fRotationCur - limitedDest) * (1.0f - timeUntilStateChange); + } + + if (seatPosMult > 0.2f || vehIsUpsideDown) { + SetPosition(neededPos); + SetHeading(m_fRotationCur); + } else { + CMatrix vehDoorMat(veh->GetMatrix()); + vehDoorMat.GetPosition() += Multiply3x3(vehDoorMat, GetLocalPositionToOpenCarDoor(veh, m_vehDoor, 0.0f)); + // VC couch anims are inverted, so they're fixing it here. + GetMatrix() = vehDoorMat; + } + +} + +void +CPed::SetCarJack(CVehicle* car) +{ + uint8 doorFlag; + eDoors door; + CPed *pedInSeat = nil; + + if (car->IsBoat()) + return; + + switch (m_vehDoor) { + case CAR_DOOR_RF: + doorFlag = CAR_DOOR_FLAG_RF; + door = DOOR_FRONT_RIGHT; + if (car->pPassengers[0]) { + pedInSeat = car->pPassengers[0]; + } else if (m_nPedType == PEDTYPE_COP) { + pedInSeat = car->pDriver; + } + break; + case CAR_DOOR_RR: + doorFlag = CAR_DOOR_FLAG_RR; + door = DOOR_REAR_RIGHT; + pedInSeat = car->pPassengers[2]; + break; + case CAR_DOOR_LF: + doorFlag = CAR_DOOR_FLAG_LF; + door = DOOR_FRONT_LEFT; + pedInSeat = car->pDriver; + break; + case CAR_DOOR_LR: + doorFlag = CAR_DOOR_FLAG_LR; + door = DOOR_REAR_LEFT; + pedInSeat = car->pPassengers[1]; + break; + default: + doorFlag = CAR_DOOR_FLAG_UNKNOWN; + break; + } + + if(car->bIsBus) + pedInSeat = car->pDriver; + + if (m_fHealth > 0.0f && (IsPlayer() || m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS || + (car->VehicleCreatedBy != MISSION_VEHICLE && car->GetModelIndex() != MI_DODO))) + if (pedInSeat && !pedInSeat->IsPedDoingDriveByShooting() && pedInSeat->m_nPedState == PED_DRIVING) + if (m_nPedState != PED_CARJACK && !m_pVehicleAnim) + if ((car->IsDoorReady(door) || car->IsDoorFullyOpen(door))) + if (!car->bIsBeingCarJacked && !(doorFlag & car->m_nGettingInFlags) && !(doorFlag & car->m_nGettingOutFlags)) + SetCarJack_AllClear(car, m_vehDoor, doorFlag); +} + +void +CPed::SetCarJack_AllClear(CVehicle *car, uint32 doorNode, uint32 doorFlag) +{ + RemoveWeaponWhenEnteringVehicle(); + if (m_nPedState != PED_SEEK_CAR) + SetStoredState(); + + m_pSeekTarget = car; + m_pSeekTarget->RegisterReference((CEntity**)&m_pSeekTarget); + SetPedState(PED_CARJACK); + car->bIsBeingCarJacked = true; + m_pMyVehicle = (CVehicle*)m_pSeekTarget; + m_pMyVehicle->RegisterReference((CEntity**)&m_pMyVehicle); + ((CVehicle*)m_pSeekTarget)->m_nNumGettingIn++; + + Say(m_nPedType == PEDTYPE_COP ? SOUND_PED_ARREST_COP : SOUND_PED_CAR_JACKING); + CVector carEnterPos; + carEnterPos = GetPositionToOpenCarDoor(car, m_vehDoor); + + car->m_nGettingInFlags |= doorFlag; + m_vecOffsetSeek = carEnterPos - GetPosition(); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 600; + float zDiff = Max(0.0f, carEnterPos.z - GetPosition().z); + bUsesCollision = false; + + if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, zDiff > 4.4f ? ANIM_STD_CAR_ALIGNHI_DOOR_LHS : ANIM_STD_CAR_ALIGN_DOOR_LHS, 4.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, zDiff > 4.4f ? ANIM_STD_CAR_ALIGNHI_DOOR_RHS : ANIM_STD_CAR_ALIGN_DOOR_RHS, 4.0f); + + m_pVehicleAnim->SetFinishCallback(PedAnimAlignCB, this); +} + +void +CPed::SetBeingDraggedFromCar(CVehicle *veh, uint32 vehEnterType, bool quickJack) +{ + if (m_nPedState == PED_DRAG_FROM_CAR) + return; + + bUsesCollision = false; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_nLastPedState = PED_IDLE; + SetMoveState(PEDMOVE_STILL); + m_pSeekTarget = veh; + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + m_vehDoor = vehEnterType; + if (m_vehDoor == CAR_DOOR_LF) { + if (veh->pDriver && veh->pDriver->IsPlayer()) + veh->SetStatus(STATUS_PLAYER_DISABLED); + else + veh->SetStatus(STATUS_ABANDONED); + } + RemoveInCarAnims(); + SetMoveState(PEDMOVE_NONE); + LineUpPedWithCar(LINE_UP_TO_CAR_START); + m_pVehicleAnim = nil; + SetPedState(PED_DRAG_FROM_CAR); + bChangedSeat = false; + bWillBeQuickJacked = quickJack; + + SetHeading(m_fRotationCur); + + Say(SOUND_PED_CAR_JACKED); + SetRadioStation(); + veh->m_nGettingOutFlags |= GetCarDoorFlag(m_vehDoor); +} + +void +CPed::BeingDraggedFromCar(void) +{ + CAnimBlendAssociation *animAssoc; + AnimationId enterAnim; + bool dontRunAnim = false; + PedLineUpPhase lineUpType; + + if (!m_pVehicleAnim) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 100.0f); + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT); + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_LO); + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_P); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_P_LO); + } + } + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) { + if (bWillBeQuickJacked) { + enterAnim = ANIM_STD_QUICKJACKED; + } else if (m_pMyVehicle->bLowVehicle) { + enterAnim = ANIM_STD_JACKEDCAR_LO_LHS; + } else { + enterAnim = ANIM_STD_JACKEDCAR_LHS; + } + } else if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) { + if (m_pMyVehicle->bLowVehicle) + enterAnim = ANIM_STD_JACKEDCAR_LO_RHS; + else + enterAnim = ANIM_STD_JACKEDCAR_RHS; + } else + dontRunAnim = true; + + + if (!dontRunAnim) + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, enterAnim); + + m_pVehicleAnim->SetFinishCallback(PedSetDraggedOutCarCB, this); + lineUpType = LINE_UP_TO_CAR_START; + } else if (m_pVehicleAnim->currentTime <= 1.4f) { + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + lineUpType = LINE_UP_TO_CAR_START; + } else { + lineUpType = LINE_UP_TO_CAR_2; + } + + LineUpPedWithCar(lineUpType); +#ifdef VC_PED_PORTS + if (m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) { + if (m_pMyVehicle) { + m_pMyVehicle->ProcessOpenDoor(m_vehDoor, ANIM_STD_NUM, m_pVehicleAnim->currentTime * 5.0f); + } + } +#endif +} + +void +CPed::SetEnterCar(CVehicle *car, uint32 unused) +{ + if (CCranes::IsThisCarBeingCarriedByAnyCrane(car)) { + RestorePreviousState(); + RestorePreviousObjective(); + } else { + uint8 doorFlag; + eDoors door; + switch (m_vehDoor) { + case CAR_DOOR_RF: + doorFlag = CAR_DOOR_FLAG_RF; + door = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_RR: + doorFlag = CAR_DOOR_FLAG_RR; + door = DOOR_REAR_RIGHT; + break; + case CAR_DOOR_LF: + doorFlag = CAR_DOOR_FLAG_LF; + door = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + doorFlag = CAR_DOOR_FLAG_LR; + door = DOOR_REAR_LEFT; + break; + default: + doorFlag = CAR_DOOR_FLAG_UNKNOWN; + break; + } + if (!IsPedInControl() || m_fHealth <= 0.0f + || doorFlag & car->m_nGettingInFlags || doorFlag & car->m_nGettingOutFlags + || car->bIsBeingCarJacked || m_pVehicleAnim + || doorFlag && !car->IsDoorReady(door) && !car->IsDoorFullyOpen(door)) + SetMoveState(PEDMOVE_STILL); + else + SetEnterCar_AllClear(car, m_vehDoor, doorFlag); + } +} + +void +CPed::SetEnterCar_AllClear(CVehicle *car, uint32 doorNode, uint32 doorFlag) +{ + float zDiff = 0.0f; + RemoveWeaponWhenEnteringVehicle(); + car->m_nGettingInFlags |= doorFlag; + bVehEnterDoorIsBlocked = false; + if (m_nPedState != PED_SEEK_CAR && m_nPedState != PED_SEEK_IN_BOAT) + SetStoredState(); + + m_pSeekTarget = car; + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + m_vehDoor = doorNode; + SetPedState(PED_ENTER_CAR); + if (m_vehDoor == CAR_DOOR_RF && m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && car->m_vehType != VEHICLE_TYPE_BIKE) { + car->bIsBeingCarJacked = true; + } + + m_pMyVehicle = (CVehicle*)m_pSeekTarget; + m_pMyVehicle->RegisterReference((CEntity**) &m_pMyVehicle); + ((CVehicle*)m_pSeekTarget)->m_nNumGettingIn++; + bUsesCollision = false; + CVector doorOpenPos = GetPositionToOpenCarDoor(car, m_vehDoor); + + // Because buses have stairs + if (!m_pMyVehicle->bIsBus) + zDiff = Max(0.0f, doorOpenPos.z - GetPosition().z); + + m_vecOffsetSeek = doorOpenPos - GetPosition(); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 600; + if (car->IsBoat()) { +#ifndef FIX_BUGS + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_BOAT_DRIVE, 100.0f); +#else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, car->GetDriverAnim(), 100.0f); +#endif + + // Otherwise boat enter key sometimes processed multiple times, so you enter/exit instantly +#if defined VC_PED_PORTS || defined FIX_BUGS + PedSetInCarCB(nil, this); + bVehExitWillBeInstant = true; +#else + m_pVehicleAnim->SetFinishCallback(PedSetInCarCB, this); +#endif + if (IsPlayer()) + CWaterLevel::AllocateBoatWakeArray(); + } else { + if (zDiff > 4.4f) { + if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_ALIGNHI_DOOR_RHS, 4.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_ALIGNHI_DOOR_LHS, 4.0f); + + } else { + if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_ALIGN_DOOR_RHS, 4.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_ALIGN_DOOR_LHS, 4.0f); + } + m_pVehicleAnim->SetFinishCallback(PedAnimAlignCB, this); + car->AutoPilot.m_nCruiseSpeed = 0; + } +} + +void +CPed::EnterCar(void) +{ + if (IsNotInWreckedVehicle() && m_fHealth > 0.0f) { + CVehicle *veh = (CVehicle*)m_pSeekTarget; + + // Not used. + // CVector posForDoor = GetPositionToOpenCarDoor(veh, m_vehDoor); + + if (veh->CanPedOpenLocks(this)) { + if (m_vehDoor && m_pVehicleAnim) { + veh->ProcessOpenDoor(m_vehDoor, m_pVehicleAnim->animId, m_pVehicleAnim->currentTime); + } + } + bIsInTheAir = false; + LineUpPedWithCar(LINE_UP_TO_CAR_START); + } else { + QuitEnteringCar(); + SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } +} + +void +CPed::QuitEnteringCar(void) +{ + CVehicle *veh = m_pMyVehicle; + if (m_pVehicleAnim) + m_pVehicleAnim->blendDelta = -1000.0f; + + RestartNonPartialAnims(); + + if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE)) + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 100.0f); + + if (veh) { + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_nPedState == PED_CARJACK) + veh->bIsBeingCarJacked = false; + + if (veh->m_nNumGettingIn != 0) + veh->m_nNumGettingIn--; + +#ifdef VC_PED_PORTS + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) + RestorePreviousObjective(); +#endif + + veh->m_nGettingInFlags &= ~GetCarDoorFlag(m_vehDoor); + } + + bUsesCollision = true; + + ReplaceWeaponWhenExitingVehicle(); + + if (DyingOrDead()) { + if (m_pVehicleAnim) { + m_pVehicleAnim->blendDelta = -4.0f; + m_pVehicleAnim->flags |= ASSOC_DELETEFADEDOUT; + m_pVehicleAnim->flags &= ~ASSOC_RUNNING; + } + } else + SetIdle(); + + m_pVehicleAnim = nil; + + if (veh) { +#ifdef VC_PED_PORTS + if (veh->AutoPilot.m_nCruiseSpeed == 0 && veh->VehicleCreatedBy == RANDOM_VEHICLE) +#else + if (veh->AutoPilot.m_nCruiseSpeed == 0) +#endif + veh->AutoPilot.m_nCruiseSpeed = 17; + } +} + +void +AddYardieDoorSmoke(CVehicle *veh, uint32 doorNode) +{ + eDoors door; + switch (doorNode) { + case CAR_DOOR_RF: + door = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_LF: + door = DOOR_FRONT_LEFT; + break; + default: + break; + } + + if (!veh->IsDoorMissing(door) && veh->IsComponentPresent(doorNode)) { + CVector pos; +#ifdef FIX_BUGS + veh->GetComponentWorldPosition(doorNode, pos); +#else + veh->GetComponentWorldPosition(CAR_DOOR_LF, pos); +#endif + CParticle::AddYardieDoorSmoke(pos, veh->GetMatrix()); + } +} + +// Seperate function in VC, more logical. Not sure is it inlined in III. +void +CPed::SetExitBoat(CVehicle *boat) +{ +#ifndef VC_PED_PORTS + SetPedState(PED_IDLE); + CVector firstPos = GetPosition(); + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 100.0f); + if (boat->GetModelIndex() == MI_SPEEDER && boat->IsUpsideDown()) { + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CRAWLOUT_LHS, 8.0f); + m_pVehicleAnim->SetFinishCallback(PedSetOutCarCB, this); + m_vehDoor = CAR_DOOR_RF; + SetPedState(PED_EXIT_CAR); + } else { + m_vehDoor = CAR_DOOR_RF; + PedSetOutCarCB(nil, this); + bIsStanding = true; + m_pCurSurface = boat; + m_pCurSurface->RegisterReference((CEntity**)&m_pCurSurface); + } + SetPosition(firstPos); + SetMoveState(PEDMOVE_STILL); + m_vecMoveSpeed = boat->m_vecMoveSpeed; + bTryingToReachDryLand = true; +#else + SetPedState(PED_IDLE); + CVector newPos = GetPosition(); + RemoveInCarAnims(); + CColModel* boatCol = boat->GetColModel(); + if (boat->IsUpsideDown()) { + newPos = { 0.0f, 0.0f, boatCol->boundingBox.min.z }; + newPos = boat->GetMatrix() * newPos; + newPos.z += 1.0f; + m_vehDoor = CAR_DOOR_RF; + PedSetOutCarCB(nil, this); + bIsStanding = true; + m_pCurSurface = boat; + m_pCurSurface->RegisterReference((CEntity**)&m_pCurSurface); + m_pCurrentPhysSurface = boat; + } else { +/* if (boat->m_modelIndex != MI_SKIMMER || boat->bIsInWater) { + if (boat->m_modelIndex == MI_SKIMMER) + newPos.z += 2.0f +*/ + m_vehDoor = CAR_DOOR_RF; + PedSetOutCarCB(nil, this); + bIsStanding = true; + m_pCurSurface = boat; + m_pCurSurface->RegisterReference((CEntity**)&m_pCurSurface); + m_pCurrentPhysSurface = boat; + CColPoint foundCol; + CEntity *foundEnt = nil; + if (CWorld::ProcessVerticalLine(newPos, newPos.z - 1.4f, foundCol, foundEnt, false, true, false, false, false, false, nil)) + newPos.z = FEET_OFFSET + foundCol.point.z; +/* // VC specific + } else { + m_vehDoor = CAR_DOOR_RF; + PedSetOutCarCB(nil, this); + bIsStanding = true; + SetMoveState(PEDMOVE_STILL); + bTryingToReachDryLand = true; + float upMult = 1.04f + boatCol->boundingBox.min.z; + float rightMult = 0.6f * boatCol->boundingBox.max.x; + newPos = upMult * boat->GetUp() + rightMult * boat->GetRight() + boat->GetPosition(); + GetPosition() = newPos; + if (m_pMyVehicle) { + PositionPedOutOfCollision(); + } else { + m_pMyVehicle = boat; + PositionPedOutOfCollision(); + m_pMyVehicle = nil; + } + return; + } +*/ } + SetPosition(newPos); + SetMoveState(PEDMOVE_STILL); + m_vecMoveSpeed = boat->m_vecMoveSpeed; +#endif + // Not there in VC. + CWaterLevel::FreeBoatWakeArray(); +} + +// wantedDoorNode = 0 means that func. will determine it +void +CPed::SetExitCar(CVehicle *veh, uint32 wantedDoorNode) +{ + uint32 optedDoorNode = wantedDoorNode; + bool teleportNeeded = false; + bool isLow = !!veh->bLowVehicle; + if (!veh->CanPedExitCar()) { + if (veh->pDriver && !veh->pDriver->IsPlayer()) { + veh->AutoPilot.m_nCruiseSpeed = 0; + veh->AutoPilot.m_nCarMission = MISSION_NONE; + } + return; + } + + if (m_nPedState == PED_EXIT_CAR || m_nPedState == PED_DRAG_FROM_CAR) + return; + + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + if (wantedDoorNode == 0) { + optedDoorNode = CAR_DOOR_LF; + if (!veh->bIsBus) { + if (veh->pDriver == this) { + optedDoorNode = CAR_DOOR_LF; + } else if (veh->pPassengers[0] == this) { + optedDoorNode = CAR_DOOR_RF; + } else if (veh->pPassengers[1] == this) { + optedDoorNode = CAR_DOOR_LR; + } else if (veh->pPassengers[2] == this) { + optedDoorNode = CAR_DOOR_RR; + } else { + for (int i = 3; i < veh->m_nNumMaxPassengers; ++i) { + if (veh->pPassengers[i] == this) { + if (i & 1) + optedDoorNode = CAR_DOOR_RR; + else + optedDoorNode = CAR_DOOR_LR; + + break; + } + } + } + } + } + bool someoneExitsFromOurExitDoor = false; + bool someoneEntersFromOurExitDoor = false; + switch (optedDoorNode) { + case CAR_DOOR_RF: + if (veh->m_nGettingInFlags & CAR_DOOR_FLAG_RF) + someoneEntersFromOurExitDoor = true; + if (veh->m_nGettingOutFlags & CAR_DOOR_FLAG_RF) + someoneExitsFromOurExitDoor = true; + break; + case CAR_DOOR_RR: + if (veh->m_nGettingInFlags & CAR_DOOR_FLAG_RR) + someoneEntersFromOurExitDoor = true; + if (veh->m_nGettingOutFlags & CAR_DOOR_FLAG_RR) + someoneExitsFromOurExitDoor = true; + break; + case CAR_DOOR_LF: + if (veh->m_nGettingInFlags & CAR_DOOR_FLAG_LF) + someoneEntersFromOurExitDoor = true; + if (veh->m_nGettingOutFlags & CAR_DOOR_FLAG_LF) + someoneExitsFromOurExitDoor = true; + break; + case CAR_DOOR_LR: + if (veh->m_nGettingInFlags & CAR_DOOR_FLAG_LR) + someoneEntersFromOurExitDoor = true; + if (veh->m_nGettingOutFlags & CAR_DOOR_FLAG_LR) + someoneExitsFromOurExitDoor = true; + break; + default: + break; + } + if (someoneEntersFromOurExitDoor && m_objective == OBJECTIVE_LEAVE_CAR) { + RestorePreviousObjective(); + return; + } + if (!someoneExitsFromOurExitDoor || m_nPedType == PEDTYPE_COP && veh->bIsBus) { + // Again, unused... + // CVector exitPos = GetPositionToOpenCarDoor(veh, optedDoorNode); + bool thereIsRoom = veh->IsRoomForPedToLeaveCar(optedDoorNode, nil); + if (veh->IsOnItsSide()) { + teleportNeeded = true; + } else if (!thereIsRoom) { + bool trySideSeat = false; + CPed *pedOnSideSeat = nil; + switch (optedDoorNode) { + case CAR_DOOR_RF: + if (veh->pDriver || veh->m_nGettingInFlags & CAR_DOOR_FLAG_LF) { + pedOnSideSeat = veh->pDriver; + trySideSeat = true; + } else + optedDoorNode = CAR_DOOR_LF; + + break; + case CAR_DOOR_RR: + if (veh->pPassengers[1] || veh->m_nGettingInFlags & CAR_DOOR_FLAG_LR) { + pedOnSideSeat = veh->pPassengers[1]; + trySideSeat = true; + } else + optedDoorNode = CAR_DOOR_LR; + + break; + case CAR_DOOR_LF: + if (veh->pPassengers[0] || veh->m_nGettingInFlags & CAR_DOOR_FLAG_RF) { + pedOnSideSeat = veh->pPassengers[0]; + trySideSeat = true; + } else + optedDoorNode = CAR_DOOR_RF; + + break; + case CAR_DOOR_LR: + if (veh->pPassengers[2] || veh->m_nGettingInFlags & CAR_DOOR_FLAG_RR) { + pedOnSideSeat = (CPed*)veh->pPassengers[2]; + trySideSeat = true; + } else + optedDoorNode = CAR_DOOR_RR; + + break; + default: + break; + } + if (trySideSeat) { + if (!pedOnSideSeat || !IsPlayer() && CharCreatedBy != MISSION_CHAR) + return; + + switch (optedDoorNode) { + case CAR_DOOR_RF: + optedDoorNode = CAR_DOOR_LF; + break; + case CAR_DOOR_RR: + optedDoorNode = CAR_DOOR_LR; + break; + case CAR_DOOR_LF: + optedDoorNode = CAR_DOOR_RF; + break; + case CAR_DOOR_LR: + optedDoorNode = CAR_DOOR_RR; + break; + default: + break; + } + } + // ... + // CVector exitPos = GetPositionToOpenCarDoor(veh, optedDoorNode); + if (!veh->IsRoomForPedToLeaveCar(optedDoorNode, nil)) { + if (!IsPlayer() && CharCreatedBy != MISSION_CHAR) + return; + + teleportNeeded = true; + } + } + if (m_nPedState == PED_FLEE_POS) { + m_nLastPedState = PED_FLEE_POS; + m_nPrevMoveState = PEDMOVE_RUN; + SetMoveState(PEDMOVE_SPRINT); + } else { + m_nLastPedState = PED_IDLE; + m_nPrevMoveState = PEDMOVE_STILL; + SetMoveState(PEDMOVE_STILL); + } + + ReplaceWeaponWhenExitingVehicle(); + bUsesCollision = false; + m_pSeekTarget = veh; + m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget); + m_vehDoor = optedDoorNode; + SetPedState(PED_EXIT_CAR); + if (m_pVehicleAnim && m_pVehicleAnim->flags & ASSOC_PARTIAL) + m_pVehicleAnim->blendDelta = -1000.0f; + SetMoveState(PEDMOVE_NONE); + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 100.0f); + RemoveInCarAnims(); + veh->AutoPilot.m_nCruiseSpeed = 0; + if (teleportNeeded) { + PedSetOutCarCB(nil, this); + + // This is same code with CPedPlacement::FindZCoorForPed, except we start from z + 1.5 and also check vehicles. + float zForPed; + float startZ = GetPosition().z - 100.0f; + float foundColZ = -100.0f; + float foundColZ2 = -100.0f; + CColPoint foundCol; + CEntity* foundEnt; + + CVector vec = GetPosition(); + vec.z += 1.5f; + + if (CWorld::ProcessVerticalLine(vec, startZ, foundCol, foundEnt, true, true, false, false, true, false, nil)) + foundColZ = foundCol.point.z; + + // Adjust coords and do a second test + vec.x += 0.1f; + vec.y += 0.1f; + + if (CWorld::ProcessVerticalLine(vec, startZ, foundCol, foundEnt, true, true, false, false, true, false, nil)) + foundColZ2 = foundCol.point.z; + + zForPed = Max(foundColZ, foundColZ2); + + if (zForPed > -99.0f) + GetMatrix().GetPosition().z = FEET_OFFSET + zForPed; + } else { + if (veh->GetUp().z > -0.8f) { + bool addDoorSmoke = false; + if (veh->GetModelIndex() == MI_YARDIE) + addDoorSmoke = true; + + switch (m_vehDoor) { + case CAR_DOOR_RF: + if (veh->bIsBus) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_COACH_GET_OUT_LHS); + } else { + if (isLow) + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LO_RHS); + else + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_RHS); + + if (addDoorSmoke) + AddYardieDoorSmoke(veh, CAR_DOOR_RF); + } + break; + case CAR_DOOR_RR: + if (veh->bIsVan) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_VAN_GET_OUT_REAR_RHS); + } else if (isLow) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LO_RHS); + } else { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_RHS); + } + break; + case CAR_DOOR_LF: + if (veh->bIsBus) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_COACH_GET_OUT_LHS); + } else { + if (isLow) + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LO_LHS); + else + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LHS); + + if (addDoorSmoke) + AddYardieDoorSmoke(veh, CAR_DOOR_LF); + } + break; + case CAR_DOOR_LR: + if (veh->bIsVan) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_VAN_GET_OUT_REAR_LHS); + } else if (isLow) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LO_LHS); + } else { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GETOUT_LHS); + } + break; + default: + break; + } + if (!bBusJacked) { + switch (m_vehDoor) { + case CAR_DOOR_RF: + veh->m_nGettingOutFlags |= CAR_DOOR_FLAG_RF; + break; + case CAR_DOOR_RR: + veh->m_nGettingOutFlags |= CAR_DOOR_FLAG_RR; + break; + case CAR_DOOR_LF: + veh->m_nGettingOutFlags |= CAR_DOOR_FLAG_LF; + break; + case CAR_DOOR_LR: + veh->m_nGettingOutFlags |= CAR_DOOR_FLAG_LR; + break; + default: + break; + } + } + m_pVehicleAnim->SetFinishCallback(PedAnimStepOutCarCB, this); + } else { + if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CRAWLOUT_RHS); + } else if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CRAWLOUT_LHS); + } + m_pVehicleAnim->SetFinishCallback(PedSetOutCarCB, this); + } + } + bChangedSeat = false; + if (veh->bIsBus) + bRenderPedInCar = true; + + SetRadioStation(); + if (veh->pDriver == this) { + if (IsPlayer()) + veh->SetStatus(STATUS_PLAYER_DISABLED); + else + veh->SetStatus(STATUS_ABANDONED); + } + } +} + +void +CPed::ExitCar(void) +{ + if (!m_pVehicleAnim) + return; + + AnimationId exitAnim = (AnimationId) m_pVehicleAnim->animId; + float animTime = m_pVehicleAnim->currentTime; + + m_pMyVehicle->ProcessOpenDoor(m_vehDoor, exitAnim, animTime); + + if (m_pSeekTarget) { + // Car is upside down + if (m_pMyVehicle->GetUp().z > -0.8f) { + if (exitAnim == ANIM_STD_CAR_CLOSE_RHS || exitAnim == ANIM_STD_CAR_CLOSE_LHS || animTime > 0.3f) + LineUpPedWithCar(LINE_UP_TO_CAR_END); + else + LineUpPedWithCar((m_pMyVehicle->GetModelIndex() == MI_DODO ? LINE_UP_TO_CAR_END : LINE_UP_TO_CAR_START)); + } else { + LineUpPedWithCar(LINE_UP_TO_CAR_END); + } + } + + // If there is someone in front of the door, make him fall while we exit. + if (m_nPedState == PED_EXIT_CAR) { + CPed *foundPed = nil; + for (int i = 0; i < m_numNearPeds; i++) { + if ((m_nearPeds[i]->GetPosition() - GetPosition()).MagnitudeSqr2D() < 0.04f) { + foundPed = m_nearPeds[i]; + break; + } + } + if (foundPed && animTime > 0.4f && foundPed->IsPedInControl()) + foundPed->SetFall(1000, ANIM_STD_HIGHIMPACT_FRONT, 1); + } +} + +// This function was mostly duplicate of GetLocalPositionToOpenCarDoor, so I've used it. +CVector +CPed::GetPositionToOpenCarDoor(CVehicle *veh, uint32 component) +{ + CVector localPos; + CVector vehDoorPos; + + localPos = GetLocalPositionToOpenCarDoor(veh, component, 1.0f); + vehDoorPos = Multiply3x3(veh->GetMatrix(), localPos) + veh->GetPosition(); + +/* + // Not used. + CVector localVehDoorOffset; + + if (veh->bIsVan && (component == VEHICLE_ENTER_REAR_LEFT || component == VEHICLE_ENTER_REAR_RIGHT)) { + localVehDoorOffset = vecPedVanRearDoorAnimOffset; + } else { + if (veh->bIsLow) { + localVehDoorOffset = vecPedCarDoorLoAnimOffset; + } else { + localVehDoorOffset = vecPedCarDoorAnimOffset; + } + } + + vehDoorPosWithoutOffset = Multiply3x3(veh->GetMatrix(), localPos + localVehDoorOffset) + veh->GetPosition(); +*/ + return vehDoorPos; +} + +void +CPed::GetNearestDoor(CVehicle *veh, CVector &posToOpen) +{ + CVector *enterOffset = nil; + if (m_vehDoor == CAR_DOOR_LF && veh->pDriver + || m_vehDoor == CAR_DOOR_RF && veh->pPassengers[0] + || m_vehDoor == CAR_DOOR_LR && veh->pPassengers[1] + || m_vehDoor == CAR_DOOR_RR && veh->pPassengers[2]) + { + enterOffset = &vecPedQuickDraggedOutCarAnimOffset; + } + + CVector lfPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_LF); + CVector rfPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_RF); + + // Left front door is closer + if ((lfPos - GetPosition()).MagnitudeSqr2D() < (rfPos - GetPosition()).MagnitudeSqr2D()) { + + if (veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, enterOffset)) { + m_vehDoor = CAR_DOOR_LF; + posToOpen = lfPos; + } else if (veh->IsRoomForPedToLeaveCar(CAR_DOOR_RF, enterOffset)) { + m_vehDoor = CAR_DOOR_RF; + posToOpen = rfPos; + } + } else { + + if (veh->IsRoomForPedToLeaveCar(CAR_DOOR_RF, enterOffset)) { + + CPed *rfPassenger = veh->pPassengers[0]; + if (rfPassenger && (rfPassenger->m_leader == this || rfPassenger->bDontDragMeOutCar || + veh->VehicleCreatedBy == MISSION_VEHICLE && m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) + && veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, enterOffset) + || (veh->m_nGettingInFlags & CAR_DOOR_FLAG_RF) && veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, enterOffset)) { + + m_vehDoor = CAR_DOOR_LF; + posToOpen = lfPos; + } else { + m_vehDoor = CAR_DOOR_RF; + posToOpen = rfPos; + } + } else if (veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, enterOffset)) { + m_vehDoor = CAR_DOOR_LF; + posToOpen = lfPos; + } + } +} + +bool +CPed::GetNearestPassengerDoor(CVehicle *veh, CVector &posToOpen) +{ + CVector rfPos, lrPos, rrPos; + bool canEnter = false; + + CVehicleModelInfo *vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(veh->GetModelIndex()); + + switch (veh->GetModelIndex()) { + case MI_BUS: + m_vehDoor = CAR_DOOR_RF; + posToOpen = GetPositionToOpenCarDoor(veh, CAR_DOOR_RF); + return true; + case MI_RHINO: + default: + break; + } + + CVector2D rfPosDist(999.0f, 999.0f); + CVector2D lrPosDist(999.0f, 999.0f); + CVector2D rrPosDist(999.0f, 999.0f); + + if (!veh->pPassengers[0] + && !(veh->m_nGettingInFlags & CAR_DOOR_FLAG_RF) + && veh->IsRoomForPedToLeaveCar(CAR_DOOR_RF, nil)) { + + rfPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_RF); + canEnter = true; + rfPosDist = rfPos - GetPosition(); + } + if (vehModel->m_numDoors == 4) { + if (!veh->pPassengers[1] + && !(veh->m_nGettingInFlags & CAR_DOOR_FLAG_LR) + && veh->IsRoomForPedToLeaveCar(CAR_DOOR_LR, nil)) { + lrPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_LR); + canEnter = true; + lrPosDist = lrPos - GetPosition(); + } + if (!veh->pPassengers[2] + && !(veh->m_nGettingInFlags & CAR_DOOR_FLAG_RR) + && veh->IsRoomForPedToLeaveCar(CAR_DOOR_RR, nil)) { + rrPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_RR); + canEnter = true; + rrPosDist = rrPos - GetPosition(); + } + + // When the door we should enter is blocked by some object. + if (!canEnter) + veh->ShufflePassengersToMakeSpace(); + } + + CVector2D nextToCompare = rfPosDist; + posToOpen = rfPos; + m_vehDoor = CAR_DOOR_RF; + if (lrPosDist.MagnitudeSqr() < nextToCompare.MagnitudeSqr()) { + m_vehDoor = CAR_DOOR_LR; + posToOpen = lrPos; + nextToCompare = lrPosDist; + } + + if (rrPosDist.MagnitudeSqr() < nextToCompare.MagnitudeSqr()) { + m_vehDoor = CAR_DOOR_RR; + posToOpen = rrPos; + } + return canEnter; +} + +void +CPed::GoToNearestDoor(CVehicle *veh) +{ + CVector posToOpen; + GetNearestDoor(veh, posToOpen); + SetSeek(posToOpen, 0.5f); + SetMoveState(PEDMOVE_RUN); +} + +void +CPed::SetAnimOffsetForEnterOrExitVehicle(void) +{ + // FIX: If there were no translations on enter anims, there were overflows all over this function. + + CAnimBlendHierarchy *enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_JACKEDCAR_LHS)->hierarchy; + CAnimBlendSequence *seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedDraggedOutCarAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LHS)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedCarDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedCarDoorAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_LHS)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedCarDoorLoAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedCarDoorLoAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_QUICKJACKED)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedQuickDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedQuickDraggedOutCarAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_VAN_GET_IN_REAR_LHS)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedVanRearDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedVanRearDoorAnimOffset = lastFrame->translation; + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_TRAIN_GETOUT)->hierarchy; + seq = enterAssoc->sequences; + CAnimManager::UncompressAnimation(enterAssoc); + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedTrainDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + KeyFrameTrans *lastFrame = (KeyFrameTrans*)seq->GetKeyFrame(seq->numFrames - 1); + vecPedTrainDoorAnimOffset = lastFrame->translation; + } + } +} + +void +CPed::PedSetQuickDraggedOutCarPositionCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + + CVector finalPos; + CVector draggedOutOffset; + + CMatrix pedMat(ped->GetMatrix()); + ped->bUsesCollision = true; + ped->RestartNonPartialAnims(); + draggedOutOffset = vecPedQuickDraggedOutCarAnimOffset; + if (ped->m_vehDoor == CAR_DOOR_RF || ped->m_vehDoor == CAR_DOOR_RR) + draggedOutOffset.x = -draggedOutOffset.x; + + finalPos = Multiply3x3(pedMat, draggedOutOffset) + ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&finalPos); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + ped->SetPosition(finalPos); + + if (veh) { + ped->m_fRotationDest = veh->GetForward().Heading() - HALFPI; + ped->m_fRotationCur = ped->m_fRotationDest; + ped->CalculateNewOrientation(); + + if (!veh->IsRoomForPedToLeaveCar(ped->m_vehDoor, &vecPedQuickDraggedOutCarAnimOffset)) + ped->PositionPedOutOfCollision(); + } + + if (!ped->CanSetPedState()) + return; + + ped->SetIdle(); + if (veh) { + if (ped->bFleeAfterExitingCar) { + ped->bFleeAfterExitingCar = false; + ped->SetFlee(veh->GetPosition(), 14000); + + } else if (ped->bWanderPathAfterExitingCar) { + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + ped->bWanderPathAfterExitingCar = false; + + } else if (ped->bGonnaKillTheCarJacker) { + ped->bGonnaKillTheCarJacker = false; + if (ped->m_pedInObjective && CGeneral::GetRandomNumber() & 1) { + if (ped->m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) + ped->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, ped->m_pedInObjective); + + } else { + CPed *driver = veh->pDriver; + if (!driver || driver == ped || driver->IsPlayer() && CTheScripts::IsPlayerOnAMission()) { + ped->SetFlee(veh->GetPosition(), 14000); + } else { + ped->ClearObjective(); + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, veh); + } + ped->bUsePedNodeSeek = true; + ped->m_pNextPathNode = nil; + ped->Say(SOUND_PED_FLEE_RUN); + } + } else if (ped->m_pedStats->m_temper > ped->m_pedStats->m_fear + && ped->CharCreatedBy != MISSION_CHAR && veh->VehicleCreatedBy != MISSION_VEHICLE + && veh->pDriver && veh->pDriver->IsPlayer() + && !CTheScripts::IsPlayerOnAMission()) { + +#ifndef VC_PED_PORTS + if (CGeneral::GetRandomNumber() < MYRAND_MAX / 2) { + ped->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, veh->pDriver); + } else +#endif + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, veh); + +#ifdef VC_PED_PORTS + } else if (ped->m_pedStats->m_temper > ped->m_pedStats->m_fear + && ped->CharCreatedBy != MISSION_CHAR && veh->VehicleCreatedBy != MISSION_VEHICLE + && !veh->pDriver && FindPlayerPed()->m_carInObjective == veh + && !CTheScripts::IsPlayerOnAMission()) { + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, veh); +#endif + } else { + ped->SetFindPathAndFlee(veh->GetPosition(), 10000); + if (CGeneral::GetRandomNumber() & 1 || ped->m_pedStats->m_fear > 70) { + ped->SetMoveState(PEDMOVE_SPRINT); + ped->Say(SOUND_PED_FLEE_SPRINT); + } else { + ped->Say(SOUND_PED_FLEE_RUN); + } + } + } + if (ped->m_nLastPedState == PED_IDLE) + ped->m_nLastPedState = PED_WANDER_PATH; +} + +void +CPed::PedSetDraggedOutCarPositionCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed *ped = (CPed*)arg; + + ped->bUsesCollision = true; + ped->RestartNonPartialAnims(); + bool itsRearDoor = false; + + if (ped->m_vehDoor == CAR_DOOR_RF || ped->m_vehDoor == CAR_DOOR_RR) + itsRearDoor = true; + + CMatrix pedMat(ped->GetMatrix()); + CVector posAfterBeingDragged = Multiply3x3(pedMat, (itsRearDoor ? -vecPedDraggedOutCarAnimOffset : vecPedDraggedOutCarAnimOffset)); + posAfterBeingDragged += ped->GetPosition(); +#ifndef VC_PED_PORTS + posAfterBeingDragged.z += 1.0f; +#endif + CPedPlacement::FindZCoorForPed(&posAfterBeingDragged); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + ped->SetPosition(posAfterBeingDragged); + + if (ped->m_pMyVehicle && !ped->m_pMyVehicle->IsRoomForPedToLeaveCar(ped->m_vehDoor, &vecPedDraggedOutCarAnimOffset)) { + ped->PositionPedOutOfCollision(); + } + + if (!ped->CanSetPedState()) + return; + + if (!ped->m_pMyVehicle) { + ped->SetIdle(); + ped->SetGetUp(); + return; + } + + CPed *driver = ped->m_pMyVehicle->pDriver; + + if (ped->IsPlayer()) { + ped->SetIdle(); + + } else if (ped->bFleeAfterExitingCar) { + ped->bFleeAfterExitingCar = false; + ped->SetFlee(ped->m_pMyVehicle->GetPosition(), 4000); + + } else if (ped->bWanderPathAfterExitingCar) { + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + ped->bWanderPathAfterExitingCar = false; + + } else if (ped->bGonnaKillTheCarJacker) { + // Kill objective is already set at this point. + + ped->bGonnaKillTheCarJacker = false; + if (!ped->m_pedInObjective || !(CGeneral::GetRandomNumber() & 1)) { + if (!driver || driver == ped || driver->IsPlayer() && CTheScripts::IsPlayerOnAMission()) { + ped->SetPedState(PED_NONE); + ped->m_nLastPedState = PED_NONE; + ped->SetFlee(ped->m_pMyVehicle->GetPosition(), 4000); + } else { + ped->ClearObjective(); + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, ped->m_pMyVehicle); + } + } + + } else if (ped->m_pedStats->m_temper > ped->m_pedStats->m_fear && ped->CharCreatedBy != MISSION_CHAR + && ped->m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE && driver + && driver->IsPlayer() && !CTheScripts::IsPlayerOnAMission()) { + +#ifndef VC_PED_PORTS + if (CGeneral::GetRandomNumber() & 1) + ped->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, driver); + else +#endif + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, ped->m_pMyVehicle); + + } else { +#ifdef VC_PED_PORTS + if (ped->m_pedStats->m_temper > ped->m_pedStats->m_fear && ped->CharCreatedBy != MISSION_CHAR + && ped->m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE && !driver + && FindPlayerPed()->m_carInObjective == ped->m_pMyVehicle && !CTheScripts::IsPlayerOnAMission()) + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, ped->m_pMyVehicle); + else +#endif + { + ped->SetPedState(PED_NONE); + ped->m_nLastPedState = PED_NONE; + ped->SetFindPathAndFlee(ped->m_pMyVehicle->GetPosition(), 10000); + } + } + ped->SetGetUp(); +} + +uint8 +CPed::GetNearestTrainDoor(CVehicle *train, CVector &doorPos) +{ + GetNearestTrainPedPosition(train, doorPos); +/* + // Not used. + CVehicleModelInfo* trainModel = (CVehicleModelInfo*)CModelInfo::GetModelInfo(train->m_modelIndex); + CMatrix trainMat = CMatrix(train->GetMatrix()); + + doorPos = trainModel->m_positions[m_vehDoor]; + doorPos.x -= 1.5f; + doorPos = Multiply3x3(trainMat, doorPos); + doorPos += train->GetPosition(); +*/ + return 1; +} + +uint8 +CPed::GetNearestTrainPedPosition(CVehicle *train, CVector &enterPos) +{ + CVector enterStepOffset; + CVehicleModelInfo *trainModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(train->GetModelIndex()); + CMatrix trainMat = CMatrix(train->GetMatrix()); + CVector leftEntryPos, rightEntryPos, midEntryPos; + float distLeftEntry, distRightEntry, distMidEntry; + + // enterStepOffset = vecPedCarDoorAnimOffset; + enterStepOffset = CVector(1.5f, 0.0f, 0.0f); + + if (train->pPassengers[TRAIN_POS_LEFT_ENTRY]) { + distLeftEntry = 999.0f; + } else { + leftEntryPos = trainModel->m_positions[TRAIN_POS_LEFT_ENTRY] - enterStepOffset; + leftEntryPos = Multiply3x3(trainMat, leftEntryPos); + leftEntryPos += train->GetPosition(); + distLeftEntry = (leftEntryPos - GetPosition()).Magnitude(); + } + + if (train->pPassengers[TRAIN_POS_MID_ENTRY]) { + distMidEntry = 999.0f; + } else { + midEntryPos = trainModel->m_positions[TRAIN_POS_MID_ENTRY] - enterStepOffset; + midEntryPos = Multiply3x3(trainMat, midEntryPos); + midEntryPos += train->GetPosition(); + distMidEntry = (midEntryPos - GetPosition()).Magnitude(); + } + + if (train->pPassengers[TRAIN_POS_RIGHT_ENTRY]) { + distRightEntry = 999.0f; + } else { + rightEntryPos = trainModel->m_positions[TRAIN_POS_RIGHT_ENTRY] - enterStepOffset; + rightEntryPos = Multiply3x3(trainMat, rightEntryPos); + rightEntryPos += train->GetPosition(); + distRightEntry = (rightEntryPos - GetPosition()).Magnitude(); + } + + if (distMidEntry < distLeftEntry) { + if (distMidEntry < distRightEntry) { + enterPos = midEntryPos; + m_vehDoor = TRAIN_POS_MID_ENTRY; + } else { + enterPos = rightEntryPos; + m_vehDoor = TRAIN_POS_RIGHT_ENTRY; + } + } else if (distRightEntry < distLeftEntry) { + enterPos = rightEntryPos; + m_vehDoor = TRAIN_POS_RIGHT_ENTRY; + } else { + enterPos = leftEntryPos; + m_vehDoor = TRAIN_POS_LEFT_ENTRY; + } + + return 1; +} + +void +CPed::PedSetInTrainCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed *ped = (CPed*)arg; + CTrain *veh = (CTrain*)ped->m_pMyVehicle; + + if (!veh) + return; + + ped->bInVehicle = true; + ped->SetPedState(PED_DRIVING); + ped->RestorePreviousObjective(); + ped->SetMoveState(PEDMOVE_STILL); + veh->AddPassenger(ped); +} + +void +CPed::SetEnterTrain(CVehicle *train, uint32 unused) +{ + if (m_nPedState == PED_ENTER_TRAIN || !((CTrain*)train)->Doors[0].IsFullyOpen()) + return; + + /* + // Not used + CVector enterPos; + GetNearestTrainPedPosition(train, enterPos); + */ + m_fRotationCur = train->GetForward().Heading() - HALFPI; + m_pMyVehicle = train; + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + + SetPedState(PED_ENTER_TRAIN); + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_TRAIN_GETIN, 4.0f); + m_pVehicleAnim->SetFinishCallback(PedSetInTrainCB, this); + bUsesCollision = false; + LineUpPedWithTrain(); + if (IsPlayer()) { + if (((CPlayerPed*)this)->m_bAdrenalineActive) + ((CPlayerPed*)this)->ClearAdrenaline(); + } +} + +void +CPed::EnterTrain(void) +{ + LineUpPedWithTrain(); +} + +void +CPed::SetPedPositionInTrain(void) +{ + LineUpPedWithTrain(); +} + +void +CPed::LineUpPedWithTrain(void) +{ + CVector lineUpPos; + CVehicleModelInfo *trainModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(m_pMyVehicle->GetModelIndex()); + CVector enterOffset(1.5f, 0.0f, -0.2f); + + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_fRotationCur = m_pMyVehicle->GetForward().Heading() - HALFPI; + m_fRotationDest = m_fRotationCur; + + if (!bInVehicle) { + GetNearestTrainDoor(m_pMyVehicle, lineUpPos); + lineUpPos.z += 0.2f; + } else { + if (m_pMyVehicle->pPassengers[TRAIN_POS_LEFT_ENTRY] == this) { + + lineUpPos = trainModel->m_positions[TRAIN_POS_LEFT_ENTRY] - enterOffset; + + } else if (m_pMyVehicle->pPassengers[TRAIN_POS_MID_ENTRY] == this) { + + lineUpPos = trainModel->m_positions[TRAIN_POS_MID_ENTRY] - enterOffset; + + } else if (m_pMyVehicle->pPassengers[TRAIN_POS_RIGHT_ENTRY] == this) { + + lineUpPos = trainModel->m_positions[TRAIN_POS_RIGHT_ENTRY] - enterOffset; + } + lineUpPos = Multiply3x3(m_pMyVehicle->GetMatrix(), lineUpPos); + lineUpPos += m_pMyVehicle->GetPosition(); + } + + if (m_pVehicleAnim) { + float percentageLeft = m_pVehicleAnim->GetTimeLeft() / m_pVehicleAnim->hierarchy->totalLength; + lineUpPos += (GetPosition() - lineUpPos) * percentageLeft; + } + + SetPosition(lineUpPos); + SetHeading(m_fRotationCur); +} + +void +CPed::SetExitTrain(CVehicle* train) +{ + if (m_nPedState == PED_EXIT_TRAIN || train->GetStatus() != STATUS_TRAIN_NOT_MOVING || !((CTrain*)train)->Doors[0].IsFullyOpen()) + return; + + /* + // Not used + CVector exitPos; + GetNearestTrainPedPosition(train, exitPos); + */ + SetPedState(PED_EXIT_TRAIN); + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_TRAIN_GETOUT, 4.0f); + m_pVehicleAnim->SetFinishCallback(PedSetOutTrainCB, this); + bUsesCollision = false; + LineUpPedWithTrain(); +} + +void +CPed::ExitTrain(void) +{ + LineUpPedWithTrain(); +} + +void +CPed::PedSetOutTrainCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + CVehicle *veh = ped->m_pMyVehicle; + + if (ped->m_pVehicleAnim) + ped->m_pVehicleAnim->blendDelta = -1000.0f; + + ped->bUsesCollision = true; + ped->m_pVehicleAnim = nil; + ped->bInVehicle = false; + ped->SetPedState(PED_IDLE); + ped->RestorePreviousObjective(); + ped->SetMoveState(PEDMOVE_STILL); + + CMatrix pedMat(ped->GetMatrix()); + ped->m_fRotationCur = HALFPI + veh->GetForward().Heading(); + ped->m_fRotationDest = ped->m_fRotationCur; + CVector posAfterExit = Multiply3x3(pedMat, vecPedTrainDoorAnimOffset); + posAfterExit += ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&posAfterExit); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + ped->SetPosition(posAfterExit); + ped->SetHeading(ped->m_fRotationCur); + veh->RemovePassenger(ped); +} + +void +CPed::RegisterThreatWithGangPeds(CEntity *attacker) +{ + CPed *attackerPed = nil; + if (attacker) { + if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && m_objective != OBJECTIVE_KILL_CHAR_ANY_MEANS) { + if (attacker->IsPed()) { + attackerPed = (CPed*)attacker; + } else { + if (!attacker->IsVehicle()) + return; + + attackerPed = ((CVehicle*)attacker)->pDriver; + if (!attackerPed) + return; + } + + if (attackerPed && (attackerPed->IsPlayer() || attackerPed->IsGangMember())) { + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->IsPointerValid()) { + if (nearPed != this && nearPed->m_nPedType == m_nPedType) + nearPed->m_fearFlags |= CPedType::GetFlag(attackerPed->m_nPedType); + } + } + } + } + } + + if (attackerPed && attackerPed->IsPlayer() && (attackerPed->m_nPedState == PED_CARJACK || attackerPed->bInVehicle)) { + if (!attackerPed->m_pMyVehicle || attackerPed->m_pMyVehicle->GetModelIndex() != MI_TOYZ) { + int16 lastVehicle; + CEntity *vehicles[8]; + CWorld::FindObjectsInRange(GetPosition(), ENTER_CAR_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + if (lastVehicle > 8) + lastVehicle = 8; + + for (int j = 0; j < lastVehicle; ++j) { + CVehicle *nearVeh = (CVehicle*) vehicles[j]; + + if (nearVeh->VehicleCreatedBy != MISSION_VEHICLE) { + CPed *nearVehDriver = nearVeh->pDriver; + + if (nearVehDriver && nearVehDriver != this && nearVehDriver->m_nPedType == m_nPedType) { + + if (nearVeh->IsVehicleNormal() && nearVeh->IsCar()) { + nearVeh->AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * nearVeh->pHandling->Transmission.fMaxCruiseVelocity * 0.8f; + nearVeh->AutoPilot.m_nCarMission = MISSION_RAMPLAYER_FARAWAY; + nearVeh->SetStatus(STATUS_PHYSICS); + nearVeh->AutoPilot.m_nTempAction = TEMPACT_NONE; + nearVeh->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + } + } + } + } + } + } +} + +// Some helper function which doesn't exist in og game. +inline void +SelectClosestNodeForSeek(CPed *ped, CPathNode *node, CVector2D closeDist, CVector2D farDist, CPathNode *closeNode, CPathNode *closeNode2, int runCount = 3) +{ + for (int i = 0; i < node->numLinks; i++) { + + CPathNode *testNode = &ThePaths.m_pathNodes[ThePaths.ConnectedNode(i + node->firstLink)]; + + if (testNode && testNode != closeNode && testNode != closeNode2) { + CVector2D posDiff(ped->m_vecSeekPos - testNode->GetPosition()); + float dist = posDiff.MagnitudeSqr(); + + if (farDist.MagnitudeSqr() > dist) { + + if (closeDist.MagnitudeSqr() <= dist) { + ped->m_pNextPathNode = closeNode; + closeDist = posDiff; + } else { + ped->m_pNextPathNode = (closeNode2 ? closeNode2 : testNode); + farDist = posDiff; + } + } + + if (--runCount > 0) + SelectClosestNodeForSeek(ped, testNode, closeDist, farDist, closeNode, (closeNode2 ? closeNode2 : testNode), runCount); + } + } +} + +bool +CPed::FindBestCoordsFromNodes(CVector unused, CVector *bestCoords) +{ + if (m_pNextPathNode || !bUsePedNodeSeek) + return false; + + CVector ourPos = GetPosition(); + + int closestNodeId = ThePaths.FindNodeClosestToCoors(GetPosition(), 1, 999999.9f); + + CVector seekObjPos = m_vecSeekPos; + seekObjPos.z += 1.0f; + + if (CWorld::GetIsLineOfSightClear(ourPos, seekObjPos, true, false, false, true, false, false, false)) + return false; + + m_pNextPathNode = nil; + + CVector2D seekPosDist (m_vecSeekPos - ourPos); + + CPathNode *closestNode = &ThePaths.m_pathNodes[closestNodeId]; + CVector2D closeDist(m_vecSeekPos - closestNode->GetPosition()); + + SelectClosestNodeForSeek(this, closestNode, closeDist, seekPosDist, closestNode, nil); + + // Above function decided that going to the next node is more logical than seeking the object. + if (m_pNextPathNode) { + + CVector pathToNextNode = m_pNextPathNode->GetPosition() - ourPos; + if (pathToNextNode.MagnitudeSqr2D() < seekPosDist.MagnitudeSqr()) { + *bestCoords = m_pNextPathNode->GetPosition(); + return true; + } + m_pNextPathNode = nil; + } + + return false; +} + +bool +CPed::DuckAndCover(void) +{ + if (!m_pedInObjective || CTimer::GetTimeInMilliseconds() <= m_duckAndCoverTimer) + return false; + + if (bKindaStayInSamePlace){ + + if (CTimer::GetTimeInMilliseconds() <= m_leaveCarTimer) { + if (!m_pLookTarget || m_pLookTarget != m_pedInObjective) { + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + } + if (!bIsAimingGun) + SetAimFlag(m_pedInObjective); + + } else { + bCrouchWhenShooting = false; + bKindaStayInSamePlace = false; + bIsDucking = false; + bDuckAndCover = false; + m_headingRate = 10.0f; + m_duckAndCoverTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(20000,30000); + if (m_pSeekTarget && m_pSeekTarget->IsVehicle()) + ((CVehicle*)m_pSeekTarget)->m_numPedsUseItAsCover--; + } + return false; + } + + bool justDucked = false; + CVehicle *foundVeh = nil; + float maxDist = 225.0f; + bIsDucking = false; + bCrouchWhenShooting = false; + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer) { + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity *vehicles[8]; + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle *veh = (CVehicle*) vehicles[i]; + if (veh->m_vecMoveSpeed.Magnitude() <= 0.02f + && !veh->bIsBus + && !veh->bIsVan + && !veh->bIsBig + && veh->m_numPedsUseItAsCover < 3) { + float dist = (GetPosition() - veh->GetPosition()).MagnitudeSqr(); + if (dist < maxDist) { + maxDist = dist; + foundVeh = veh; + } + } + } + if (foundVeh) { + // Unused. + // CVector lfWheelPos, rfWheelPos; + // foundVeh->GetComponentWorldPosition(CAR_WHEEL_RF, rfWheelPos); + // foundVeh->GetComponentWorldPosition(CAR_WHEEL_LF, lfWheelPos); + CVector rightSide, leftSide; + + // 3 persons can use the car as cover. Found the correct position for us. + if (foundVeh->m_numPedsUseItAsCover == 2) { + rightSide = CVector(1.5f, -0.5f, 0.0f); + leftSide = CVector(-1.5f, -0.5f, 0.0f); + } else if (foundVeh->m_numPedsUseItAsCover == 1) { + rightSide = CVector(1.5f, 0.5f, 0.0f); + leftSide = CVector(-1.5f, 0.5f, 0.0f); + } else if (foundVeh->m_numPedsUseItAsCover == 0) { + rightSide = CVector(1.5f, 0.0f, 0.0f); + leftSide = CVector(-1.5f, 0.0f, 0.0f); + } + + CMatrix vehMatrix(foundVeh->GetMatrix()); + CVector duckAtRightSide = Multiply3x3(vehMatrix, rightSide) + foundVeh->GetPosition(); + + CVector duckAtLeftSide = Multiply3x3(vehMatrix, leftSide) + foundVeh->GetPosition(); + + CVector distWithPedRightSide = m_pedInObjective->GetPosition() - duckAtRightSide; + CVector distWithPedLeftSide = m_pedInObjective->GetPosition() - duckAtLeftSide; + + CVector duckPos; + if (distWithPedRightSide.MagnitudeSqr() <= distWithPedLeftSide.MagnitudeSqr()) + duckPos = duckAtLeftSide; + else + duckPos = duckAtRightSide; + + if (CWorld::TestSphereAgainstWorld(duckPos, 0.5f, nil, true, true, true, false, false, false) + && CWorld::GetIsLineOfSightClear(GetPosition(), duckPos, 1, 0, 0, 1, 0, 0, 0)) { + SetSeek(duckPos, 1.0f); + m_headingRate = 15.0f; + bIsRunning = true; + bDuckAndCover = true; + justDucked = true; + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 500; + if (foundVeh->bIsLawEnforcer) + m_carInObjective = foundVeh; + + // BUG? Shouldn't we register the reference? + m_pSeekTarget = foundVeh; + ClearPointGunAt(); + } else { + m_duckAndCoverTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(10000, 15000); + bDuckAndCover = false; + } + } else { + bDuckAndCover = false; + } + } + + if (!justDucked && !bDuckAndCover) + return false; + + if (!Seek()) + return true; + + bKindaStayInSamePlace = true; + bDuckAndCover = false; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + if (m_pSeekTarget && m_pSeekTarget->IsVehicle()) + ((CVehicle*)m_pSeekTarget)->m_numPedsUseItAsCover++; + + SetIdle(); + SetMoveState(PEDMOVE_STILL); + SetMoveAnim(); + if (!m_pLookTarget || m_pLookTarget != m_pedInObjective) { + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + } + + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(3000, 6000); + return false; +} + +CVector +CPed::GetPositionToOpenCarDoor(CVehicle *veh, uint32 component, float offset) +{ + CVector doorPos; + CMatrix vehMat(veh->GetMatrix()); + + doorPos = Multiply3x3(vehMat, GetLocalPositionToOpenCarDoor(veh, component, offset)); + + return veh->GetPosition() + doorPos; +} + +CVector +CPed::GetLocalPositionToOpenCarDoor(CVehicle *veh, uint32 component, float seatPosMult) +{ + CVehicleModelInfo *vehModel; + CVector vehDoorPos; + CVector vehDoorOffset; + float seatOffset; + + vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(veh->GetModelIndex()); + if (veh->bIsVan && (component == CAR_DOOR_LR || component == CAR_DOOR_RR)) { + seatOffset = 0.0f; + vehDoorOffset = vecPedVanRearDoorAnimOffset; + } else { + seatOffset = veh->pHandling->fSeatOffsetDistance * seatPosMult; + if (veh->bLowVehicle) { + vehDoorOffset = vecPedCarDoorLoAnimOffset; + } else { + vehDoorOffset = vecPedCarDoorAnimOffset; + } + } + + switch (component) { + case CAR_DOOR_RF: + vehDoorPos = vehModel->GetFrontSeatPosn(); + vehDoorPos.x += seatOffset; + vehDoorOffset.x = -vehDoorOffset.x; + break; + + case CAR_DOOR_RR: + vehDoorPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + vehDoorPos.x += seatOffset; + vehDoorOffset.x = -vehDoorOffset.x; + break; + + case CAR_DOOR_LF: + vehDoorPos = vehModel->GetFrontSeatPosn(); + vehDoorPos.x = -(vehDoorPos.x + seatOffset); + break; + + case CAR_DOOR_LR: + vehDoorPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + vehDoorPos.x = -(vehDoorPos.x + seatOffset); + break; + + default: + vehDoorPos = vehModel->GetFrontSeatPosn(); + vehDoorOffset = CVector(0.0f, 0.0f, 0.0f); + } + return vehDoorPos - vehDoorOffset; +} + +void +CPed::SetDuck(uint32 time) +{ + if (bIsDucking || CTimer::GetTimeInMilliseconds() <= m_duckTimer) + return; + + if (bCrouchWhenShooting && (m_nPedState == PED_ATTACK || m_nPedState == PED_AIM_GUN)) { + CAnimBlendAssociation *duckAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_LOW); + if (!duckAssoc || duckAssoc->blendDelta < 0.0f) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_LOW, 4.0f); + bIsDucking = true; + m_duckTimer = CTimer::GetTimeInMilliseconds() + time; + } + } else { + CAnimBlendAssociation *duckAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!duckAssoc || duckAssoc->blendDelta < 0.0f) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_DOWN, 4.0f); + bIsDucking = true; + m_duckTimer = CTimer::GetTimeInMilliseconds() + time; + } + } +} + +void +CPed::Duck(void) +{ + if (CTimer::GetTimeInMilliseconds() > m_duckTimer) + ClearDuck(); +} + +void +CPed::ClearDuck(void) +{ + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_LOW); + + if (!animAssoc) { + bIsDucking = false; + return; + } + } + + if (!bCrouchWhenShooting) + return; + + if (m_nPedState != PED_ATTACK && m_nPedState != PED_AIM_GUN) + return; + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RBLOCK_SHOOT); + if (!animAssoc || animAssoc->blendDelta < 0.0f) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_RBLOCK_SHOOT, 4.0f); + } +} + +void +CPed::InformMyGangOfAttack(CEntity *attacker) +{ + CPed *attackerPed; + + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) + return; + + if (attacker->IsPed()) { + attackerPed = (CPed*)attacker; + } else { + if (!attacker->IsVehicle()) + return; + + attackerPed = ((CVehicle*)attacker)->pDriver; + if (!attackerPed) + return; + } + + if (attackerPed->m_nPedType == PEDTYPE_COP) + return; + + for (int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed && nearPed != this) { + CPed *leader = nearPed->m_leader; + if (leader && leader == this && nearPed->m_pedStats->m_fear < nearPed->m_pedStats->m_temper) + { + nearPed->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, attackerPed); + nearPed->SetObjectiveTimer(30000); + } + } + } +} + +void +CPed::PedAnimDoorCloseRollingCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + CAutomobile* veh = (CAutomobile*)(ped->m_pMyVehicle); + + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + if (veh->bLowVehicle) { + veh->ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS, 1.0f); + } else { + veh->ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, 1.0f); + } + + veh->m_nGettingOutFlags &= ~CAR_DOOR_FLAG_LF; + + if (veh->Damage.GetDoorStatus(DOOR_FRONT_LEFT) == DOOR_STATUS_SWINGING) + veh->Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_OK); +} + +void +CPed::SetSeekBoatPosition(CVehicle *boat) +{ + if (m_nPedState == PED_SEEK_IN_BOAT || boat->pDriver +#if defined VC_PED_PORTS || defined FIX_BUGS + || !IsPedInControl() +#endif + ) + return; + + SetStoredState(); + m_carInObjective = boat; + m_carInObjective->RegisterReference((CEntity **) &m_carInObjective); + m_pMyVehicle = boat; + m_pMyVehicle->RegisterReference((CEntity **) &m_pMyVehicle); + m_distanceToCountSeekDone = 0.5f; + SetPedState(PED_SEEK_IN_BOAT); +} + +void +CPed::SeekBoatPosition(void) +{ + if (m_carInObjective && !m_carInObjective->pDriver) { + CVehicleModelInfo *boatModel = m_carInObjective->GetModelInfo(); + + CVector enterOffset; + enterOffset = boatModel->GetFrontSeatPosn(); + enterOffset.x = 0.0f; + CMatrix boatMat(m_carInObjective->GetMatrix()); + CVector boatEnterPos = Multiply3x3(boatMat, enterOffset); + boatEnterPos += m_carInObjective->GetPosition(); + SetMoveState(PEDMOVE_WALK); + m_vecSeekPos = boatEnterPos; + if (Seek()) { + // We arrived to the boat + m_vehDoor = 0; + SetEnterCar(m_carInObjective, 0); + } + } else + RestorePreviousState(); +} + +bool +CPed::IsRoomToBeCarJacked(void) +{ + if (!m_pMyVehicle) + return false; + + CVector offset; + if (m_pMyVehicle->bLowVehicle || m_nPedType == PEDTYPE_COP) { + offset = vecPedDraggedOutCarAnimOffset; + } else { + offset = vecPedQuickDraggedOutCarAnimOffset; + } + + offset.z = 0.0f; + if (m_pMyVehicle->IsRoomForPedToLeaveCar(CAR_DOOR_LF, &offset)) { + return true; + } + + return false; +} + +void +CPed::RemoveInCarAnims(void) +{ + if (!IsPlayer()) + return; + + CAnimBlendAssociation *animAssoc; + + if (m_pMyVehicle && m_pMyVehicle->bLowVehicle) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_LEFT_LO); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_RIGHT_LO); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + } else { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_LEFT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVE_RIGHT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + } + +#ifdef VC_PED_PORTS + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_BOAT_DRIVE); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; +#endif + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_LOOKBEHIND); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; +} + +bool +CPed::PositionPedOutOfCollision(void) +{ + CVehicle *veh; + CVector posNearVeh; + CVector posSomewhereClose; + bool putNearVeh = false; + bool putSomewhereClose = false; + int smallestDistNearVeh = 999; + int smallestDistSomewhereClose = 999; + + if (!m_pMyVehicle) + return false; + + CVector vehPos = m_pMyVehicle->GetPosition(); + CVector potentialPos; + potentialPos.y = GetPosition().y - 3.5f; + potentialPos.z = GetPosition().z; + + for (int yTry = 0; yTry < 15; yTry++) { + potentialPos.x = GetPosition().x - 3.5f; + + for (int xTry = 0; xTry < 15; xTry++) { + CPedPlacement::FindZCoorForPed(&potentialPos); + CVector distVec = potentialPos - vehPos; + float dist = distVec.Magnitude(); + + // Makes close distances bigger for some reason. + float mult = (0.6f + dist) / dist; + CVector adjustedPotentialPos = distVec * mult + vehPos; + if (CWorld::GetIsLineOfSightClear(vehPos, adjustedPotentialPos, true, false, false, true, false, false, false) + && !CWorld::TestSphereAgainstWorld(potentialPos, 0.6f, this, true, false, false, true, false, false)) { + + float potentialChangeSqr = (potentialPos - GetPosition()).MagnitudeSqr(); + veh = (CVehicle*)CWorld::TestSphereAgainstWorld(potentialPos, 0.6f, this, false, true, false, false, false, false); + if (veh) { + if (potentialChangeSqr < smallestDistNearVeh) { + posNearVeh = potentialPos; + putNearVeh = true; + smallestDistNearVeh = potentialChangeSqr; + } + } else if (potentialChangeSqr < smallestDistSomewhereClose) { + smallestDistSomewhereClose = potentialChangeSqr; + posSomewhereClose = potentialPos; + putSomewhereClose = true; + } + } + potentialPos.x += 0.5f; + } + potentialPos.y += 0.5f; + } + + if (!putSomewhereClose && !putNearVeh) + return false; + + // We refrain from using posNearVeh, probably because of it may be top of the vehicle. + if (putSomewhereClose) { + SetPosition(posSomewhereClose); + } else { + CVector vehSize = veh->GetModelInfo()->GetColModel()->boundingBox.max; + posNearVeh.z += vehSize.z; + SetPosition(posNearVeh); + } + return true; +} + +bool +CPed::WarpPedToNearLeaderOffScreen(void) +{ + bool teleported = false; + if (GetIsOnScreen() || m_leaveCarTimer > CTimer::GetTimeInMilliseconds()) + return false; + + CVector warpToPos = m_leader->GetPosition(); + CVector distVec = warpToPos - GetPosition(); + float halfOfDist = distVec.Magnitude() * 0.5f; + CVector halfNormalizedDist = distVec / halfOfDist; + + CVector appropriatePos = GetPosition(); + CVector zCorrectedPos = appropriatePos; + int tryCount = Min(10, halfOfDist); + for (int i = 0; i < tryCount; ++i) { + appropriatePos += halfNormalizedDist; + CPedPlacement::FindZCoorForPed(&zCorrectedPos); + + if (Abs(zCorrectedPos.z - warpToPos.z) >= 3.0f && Abs(zCorrectedPos.z - appropriatePos.z) >= 3.0f) + continue; + + appropriatePos.z = zCorrectedPos.z; + if (!TheCamera.IsSphereVisible(appropriatePos, 0.6f) + && CWorld::GetIsLineOfSightClear(appropriatePos, warpToPos, true, true, false, true, false, false, false) + && !CWorld::TestSphereAgainstWorld(appropriatePos, 0.6f, this, true, true, false, true, false, false)) { + teleported = true; + Teleport(appropriatePos); + } + } + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 3000; + return teleported; +} + +bool +CPed::WarpPedToNearEntityOffScreen(CEntity *warpTo) +{ + bool teleported = false; + if (GetIsOnScreen() || m_leaveCarTimer > CTimer::GetTimeInMilliseconds()) + return false; + + CVector warpToPos = warpTo->GetPosition(); + CVector distVec = warpToPos - GetPosition(); + float halfOfDist = distVec.Magnitude() * 0.5f; + CVector halfNormalizedDist = distVec / halfOfDist; + + CVector appropriatePos = GetPosition(); + CVector zCorrectedPos = appropriatePos; + int tryCount = Min(10, halfOfDist); + for (int i = 0; i < tryCount; ++i) { + appropriatePos += halfNormalizedDist; + CPedPlacement::FindZCoorForPed(&zCorrectedPos); + + if (Abs(zCorrectedPos.z - warpToPos.z) >= 3.0f && Abs(zCorrectedPos.z - appropriatePos.z) >= 3.0f) + continue; + + appropriatePos.z = zCorrectedPos.z; + if (!TheCamera.IsSphereVisible(appropriatePos, 0.6f) + && CWorld::GetIsLineOfSightClear(appropriatePos, warpToPos, true, true, false, true, false, false, false) + && !CWorld::TestSphereAgainstWorld(appropriatePos, 0.6f, this, true, true, false, true, false, false)) { + teleported = true; + Teleport(appropriatePos); + } + } + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 3000; + return teleported; +} \ No newline at end of file diff --git a/src/peds/PedChat.cpp b/src/peds/PedChat.cpp new file mode 100644 index 0000000..907f575 --- /dev/null +++ b/src/peds/PedChat.cpp @@ -0,0 +1,152 @@ +#include "common.h" +#include "Camera.h" +#include "DMAudio.h" +#include "General.h" +#include "Ped.h" + +// Corresponds to ped sounds (from SOUND_PED_DEATH to SOUND_PED_TAXI_CALL) +PedAudioData CommentWaitTime[39] = { + {500, 800, 500, 2}, + {500, 800, 500, 2}, + {500, 800, 500, 2}, + {500, 800, 500, 2}, + {100, 2, 100, 2}, + {700, 500, 1000, 500}, + {700, 500, 1000, 500}, + {5000, 2000, 15000, 3000}, + {5000, 2000, 15000, 3000}, + {5000, 2000, 15000, 3000}, + {6000, 6000, 6000, 6000}, + {1000, 1000, 2000, 2000}, + {1000, 500, 2000, 1500}, + {1000, 500, 2000, 1500}, + {800, 200, 1000, 500}, + {800, 200, 1000, 500}, + {800, 400, 2000, 1000}, + {800, 400, 2000, 1000}, + {400, 300, 2000, 1000}, + {2000, 1000, 2500, 1500}, + {200, 200, 200, 200}, + {6000, 3000, 5000, 6000}, + {6000, 3000, 9000, 5000}, + {6000, 3000, 9000, 5000}, + {6000, 3000, 9000, 5000}, + {400, 300, 4000, 1000}, + {400, 300, 4000, 1000}, + {400, 300, 4000, 1000}, + {1000, 500, 3000, 1000}, + {1000, 500, 1000, 1000}, + {3000, 2000, 3000, 2000}, + {1000, 500, 3000, 6000}, + {1000, 500, 2000, 4000}, + {1000, 500, 2000, 5000}, + {1000, 500, 3000, 2000}, + {1600, 1000, 2000, 2000}, + {3000, 2000, 5000, 3000}, + {1000, 1000, 1000, 1000}, + {1000, 1000, 5000, 5000}, +}; + +bool +CPed::ServiceTalkingWhenDead(void) +{ + return m_queuedSound == SOUND_PED_DEATH; +} + +void +CPed::ServiceTalking(void) +{ + if (bBodyPartJustCameOff && m_bodyPartBleeding == PED_HEAD) + return; + + if (!CGeneral::faststricmp(CModelInfo::GetModelInfo(GetModelIndex())->GetModelName(), "bomber")) + m_queuedSound = SOUND_PED_BOMBER; + else if (m_nPedState == PED_ON_FIRE) + m_queuedSound = SOUND_PED_BURNING; + + if (m_queuedSound != SOUND_NO_SOUND) { + if (m_queuedSound == SOUND_PED_DEATH) + m_soundStart = CTimer::GetTimeInMilliseconds() - 1; + + if (CTimer::GetTimeInMilliseconds() > m_soundStart) { + DMAudio.PlayOneShot(m_audioEntityId, m_queuedSound, 1.0f); + m_lastSoundStart = CTimer::GetTimeInMilliseconds(); + m_soundStart = + CommentWaitTime[m_queuedSound - SOUND_PED_DEATH].m_nFixedDelayTime + + CTimer::GetTimeInMilliseconds() + + CGeneral::GetRandomNumberInRange(0, CommentWaitTime[m_queuedSound - SOUND_PED_DEATH].m_nOverrideFixedDelayTime); + m_lastQueuedSound = m_queuedSound; + m_queuedSound = SOUND_NO_SOUND; + } + } +} + +void +CPed::Say(uint16 audio) +{ + if (IsPlayer()) { + + // Ofc this part isn't in VC. + switch (audio) { + case SOUND_PED_DEATH: + audio = SOUND_PED_DAMAGE; + break; + case SOUND_PED_DAMAGE: + case SOUND_PED_HIT: + case SOUND_PED_LAND: + break; + case SOUND_PED_BULLET_HIT: + case SOUND_PED_CAR_JACKED: + case SOUND_PED_DEFEND: + audio = SOUND_PED_HIT; + break; + default: + return; + } + } else { + if (TheCamera.GetPosition().z + 3.0f < GetPosition().z) + return; + + if (TheCamera.m_CameraAverageSpeed > 1.65f) { +#ifdef VC_PED_PORTS + if (audio != SOUND_PED_DAMAGE && audio != SOUND_PED_HIT && audio != SOUND_PED_LAND) +#endif + return; + + } else if (TheCamera.m_CameraAverageSpeed > 1.25f) { + if (audio != SOUND_PED_DEATH && +#ifdef VC_PED_PORTS + audio != SOUND_PED_DAMAGE && audio != SOUND_PED_HIT && audio != SOUND_PED_LAND && +#endif + audio != SOUND_PED_TAXI_WAIT && audio != SOUND_PED_EVADE) + return; + + } else if (TheCamera.m_CameraAverageSpeed > 0.9f) { + switch (audio) { + case SOUND_PED_DEATH: +#ifdef VC_PED_PORTS + case SOUND_PED_DAMAGE: + case SOUND_PED_HIT: + case SOUND_PED_LAND: +#endif + case SOUND_PED_BURNING: + case SOUND_PED_FLEE_SPRINT: + case SOUND_PED_TAXI_WAIT: + case SOUND_PED_EVADE: + case SOUND_PED_ANNOYED_DRIVER: + break; + default: + return; + } + } + } + + if (audio < m_queuedSound) { + if (audio != m_lastQueuedSound || audio == SOUND_PED_DEATH + || CommentWaitTime[audio - SOUND_PED_DEATH].m_nOverrideMaxRandomDelayTime + + m_lastSoundStart + + (uint32) CGeneral::GetRandomNumberInRange(0, CommentWaitTime[audio - SOUND_PED_DEATH].m_nMaxRandomDelayTime) <= CTimer::GetTimeInMilliseconds()) { + m_queuedSound = audio; + } + } +} \ No newline at end of file diff --git a/src/peds/PedDebug.cpp b/src/peds/PedDebug.cpp new file mode 100644 index 0000000..1c22963 --- /dev/null +++ b/src/peds/PedDebug.cpp @@ -0,0 +1,310 @@ +#include "common.h" +#ifndef MASTER +#include "main.h" +#include "Camera.h" +#include "Font.h" +#include "Ped.h" +#include "Sprite.h" +#include "Text.h" + + +static char ObjectiveText[][28] = { + "No Obj", + "Wait on Foot", + "Flee on Foot Till Safe", + "Guard Spot", + "Guard Area", + "Wait in Car", + "Wait in Car then Getout", + "Kill Char on Foot", + "Kill Char Any Means", + "Flee Char on Foot Till Safe", + "Flee Char on Foot Always", + "GoTo Char on Foot", + "Follow Char in Formation", + "Leave Car", + "Enter Car as Passenger", + "Enter Car as Driver", + "Follow Car in Car", + "Fire at Obj from Vehicle", + "Destroy Obj", + "Destroy Car", + "GoTo Area Any Means", + "GoTo Area on Foot", + "Run to Area", + "GoTo Area in Car", + "Follow Car on Foot Woffset", + "Guard Attack", + "Set Leader", + "Follow Route", + "Solicit", + "Take Taxi", + "Catch Train", + "Buy IceCream", + "Steal Any Car", + "Mug Char", +#ifdef VC_PED_PORTS + "Leave Car and Die" +#endif +}; + +static char StateText[][18] = { + "None", + "Idle", + "Look Entity", + "Look Heading", + "Wander Range", + "Wander Path", + "Seek Pos", + "Seek Entity", + "Flee Pos", + "Flee Entity", + "Pursue", + "Follow Path", + "Sniper Mode", + "Rocket Mode", + "Dummy", + "Pause", + "Attack", + "Fight", + "Face Phone", + "Make Call", + "Chat", + "Mug", + "AimGun", + "AI Control", + "Seek Car", + "Seek InBoat", + "Follow Route", + "C.P.R.", + "Solicit", + "Buy IceCream", + "Investigate", + "Step away", + "On Fire", + "Unknown", + "STATES_NO_AI", + "Jump", + "Fall", + "GetUp", + "Stagger", + "Dive away", + "STATES_NO_ST", + "Enter Train", + "Exit Train", + "Arrest Plyr", + "Driving", + "Passenger", + "Taxi Passngr", + "Open Door", + "Die", + "Dead", + "CarJack", + "Drag fm Car", + "Enter Car", + "Steal Car", + "Exit Car", + "Hands Up", + "Arrested", +}; + +static char PersonalityTypeText[][18] = { + "Player", + "Cop", + "Medic", + "Fireman", + "Gang 1", + "Gang 2", + "Gang 3", + "Gang 4", + "Gang 5", + "Gang 6", + "Gang 7", + "Street Guy", + "Suit Guy", + "Sensible Guy", + "Geek Guy", + "Old Guy", + "Tough Guy", + "Street Girl", + "Suit Girl", + "Sensible Girl", + "Geek Girl", + "Old Girl", + "Tough Girl", + "Tramp Male", + "Tramp Female", + "Tourist", + "Prostitute", + "Criminal", + "Busker", + "Taxi Driver", + "Psycho", + "Steward", + "Sports Fan", + "Shopper", + "Old Shopper" +}; + +static char WaitStateText[][16] = { + "No Wait", + "Traffic Lights", + "Pause CrossRoad", + "Look CrossRoad", + "Look Ped", + "Look Shop", + "Look Accident", + "FaceOff Gang", + "Double Back", + "Hit Wall", + "Turn 180deg", + "Surprised", + "Ped Stuck", + "Look About", + "Play Duck", + "Play Cower", + "Play Taxi", + "Play HandsUp", + "Play HandsCower", + "Play Chat", + "Finish Flee", +}; + +void +CPed::DebugDrawPedDestination(CPed *, int, int) +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawPedDesiredHeading(CPed *, int, int) +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawCollisionRadius(float, float, float, float, int) +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawVisionRange(CVector a1, float) +{ + for (int i = a1.x - 90; i < a1.x + 89; i += 30) { +#ifndef FINAL + // TODO: something was here +#endif // !FINAL + } +} + +void +CPed::DebugDrawVisionSimple(CVector, float) +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawLook() +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawPedPsyche() +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +void +CPed::DebugDrawDebugLines() +{ +#ifndef FINAL + // TODO: something was here +#endif // !FINAL +} + +int nDisplayDebugInfo = 0; + +void +CPed::SwitchDebugDisplay(void) +{ + if (++nDisplayDebugInfo > 2) + nDisplayDebugInfo = 0; +} + +int +CPed::GetDebugDisplay(void) +{ + return nDisplayDebugInfo; +} + +void +CPed::DebugDrawLookAtPoints() +{ + // TODO: mobile code +} + +void +CPed::DebugRenderOnePedText(void) +{ + if ((GetPosition() - TheCamera.GetPosition()).MagnitudeSqr() < sq(30.0f)) { + float width, height; + RwV3d screenCoords; + CVector bitAbove = GetPosition(); + bitAbove.z += 2.0f; + if (CSprite::CalcScreenCoors(bitAbove, &screenCoords, &width, &height, true)) { + + float lineHeight = SCREEN_SCALE_Y(Min(height / 100.0f, 0.7f) * 22.0f); + + DefinedState(); + CFont::SetPropOn(); + CFont::SetBackgroundOn(); + + // Originally both of them were being divided by 60.0f. + float xScale = Min(width / 240.0f, 0.7f); + float yScale = Min(height / 80.0f, 0.7f); + + CFont::SetScale(SCREEN_SCALE_X(xScale), SCREEN_SCALE_Y(yScale)); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_WIDTH); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 0, 255)); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetFontStyle(0); + AsciiToUnicode(StateText[m_nPedState], gUString); + CFont::PrintString(screenCoords.x, screenCoords.y, gUString); + AsciiToUnicode(ObjectiveText[m_objective], gUString); + CFont::PrintString(screenCoords.x, screenCoords.y + lineHeight, gUString); + AsciiToUnicode(PersonalityTypeText[m_pedStats->m_type], gUString); + CFont::PrintString(screenCoords.x, screenCoords.y + 2 * lineHeight, gUString); + AsciiToUnicode(WaitStateText[m_nWaitState], gUString); + CFont::PrintString(screenCoords.x, screenCoords.y + 3 * lineHeight, gUString); + if (m_nPedState == PED_SEEK_POS || m_nPedState == PED_SEEK_ENTITY) { + sprintf(gString, "Safe distance to target: %.2f", m_distanceToCountSeekDone); + AsciiToUnicode(gString, gUString); + CFont::PrintString(screenCoords.x, screenCoords.y + 4 * lineHeight, gUString); + } + DefinedState(); + } + } +} + +void +CPed::DebugRenderClosePedText() +{ + // TODO: mobile code +} +#endif \ No newline at end of file diff --git a/src/peds/PedFight.cpp b/src/peds/PedFight.cpp new file mode 100644 index 0000000..03d5c75 --- /dev/null +++ b/src/peds/PedFight.cpp @@ -0,0 +1,3268 @@ +#include "common.h" + +#include "main.h" +#include "RpAnimBlend.h" +#include "AnimBlendClumpData.h" +#include "AnimBlendAssociation.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "Darkel.h" +#include "DMAudio.h" +#include "FileMgr.h" +#include "General.h" +#include "Object.h" +#include "Pad.h" +#include "Particle.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Stats.h" +#include "TempColModels.h" +#include "VisibilityPlugins.h" +#include "Vehicle.h" +#include "Automobile.h" +#include "WaterLevel.h" +#include "World.h" + +uint16 nPlayerInComboMove; + +RpClump *flyingClumpTemp; + +// This is beta fistfite.dat array. Not used anymore since they're being fetched from fistfite.dat. +FightMove tFightMoves[NUM_FIGHTMOVES] = { + {ANIM_STD_NUM, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_PUNCH, 0.2f, 8.0f / 30.0f, 0.0f, 0.3f, HITLEVEL_HIGH, 1, 0}, + {ANIM_STD_FIGHT_IDLE, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_FIGHT_SHUFFLE_F, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_FIGHT_KNEE, 4.0f / 30.0f, 0.2f, 0.0f, 0.6f, HITLEVEL_LOW, 2, 0}, + {ANIM_STD_FIGHT_HEAD, 4.0f / 30.0f, 0.2f, 0.0f, 0.7f, HITLEVEL_HIGH, 3, 0}, + {ANIM_STD_FIGHT_PUNCH, 4.0f / 30.0f, 7.0f / 30.0f, 10.0f / 30.0f, 0.4f, HITLEVEL_HIGH, 1, 0}, + {ANIM_STD_FIGHT_LHOOK, 8.0f / 30.0f, 10.0f / 30.0f, 0.0f, 0.4f, HITLEVEL_HIGH, 3, 0}, + {ANIM_STD_FIGHT_KICK, 8.0f / 30.0f, 10.0f / 30.0f, 0.0f, 0.5, HITLEVEL_MEDIUM, 2, 0}, + {ANIM_STD_FIGHT_LONGKICK, 8.0f / 30.0f, 10.0f / 30.0f, 0.0f, 0.5, HITLEVEL_MEDIUM, 4, 0}, + {ANIM_STD_FIGHT_ROUNDHOUSE, 8.0f / 30.0f, 10.0f / 30.0f, 0.0f, 0.6f, HITLEVEL_MEDIUM, 4, 0}, + {ANIM_STD_FIGHT_BODYBLOW, 5.0f / 30.0f, 7.0f / 30.0f, 0.0f, 0.35f, HITLEVEL_LOW, 2, 0}, + {ANIM_STD_KICKGROUND, 10.0f / 30.0f, 14.0f / 30.0f, 0.0f, 0.4f, HITLEVEL_GROUND, 1, 0}, + {ANIM_STD_HIT_FRONT, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_BACK, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_RIGHT, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_LEFT, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_BODYBLOW, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_CHEST, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_HEAD, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_WALK, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_FLOOR, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_HIT_BEHIND, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, + {ANIM_STD_FIGHT_2IDLE, 0.0f, 0.0f, 0.0f, 0.0f, HITLEVEL_NULL, 0, 0}, +}; + +static PedOnGroundState +CheckForPedsOnGroundToAttack(CPed *attacker, CPed **pedOnGround) +{ + PedOnGroundState stateToReturn; + float angleToFace; + CPed *currentPed = nil; + PedState currentPedState; + CPed *pedOnTheFloor = nil; + CPed *deadPed = nil; + CPed *pedBelow = nil; + bool foundDead = false; + bool foundOnTheFloor = false; + bool foundBelow = false; + float angleDiff; + float distance; + + if (!CGame::nastyGame) + return NO_PED; + + for (int currentPedId = 0; currentPedId < attacker->m_numNearPeds; currentPedId++) { + + currentPed = attacker->m_nearPeds[currentPedId]; + + CVector posDifference = currentPed->GetPosition() - attacker->GetPosition(); + distance = posDifference.Magnitude(); + + if (distance < 2.0f) { + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + currentPed->GetPosition().x, currentPed->GetPosition().y, + attacker->GetPosition().x, attacker->GetPosition().y); + + angleToFace = CGeneral::LimitRadianAngle(angleToFace); + attacker->m_fRotationCur = CGeneral::LimitRadianAngle(attacker->m_fRotationCur); + + angleDiff = Abs(angleToFace - attacker->m_fRotationCur); + + if (angleDiff > PI) + angleDiff = 2 * PI - angleDiff; + + currentPedState = currentPed->m_nPedState; + + if (currentPed->OnGroundOrGettingUp()) { + if (distance < 2.0f && angleDiff < DEGTORAD(65.0f)) { + if (currentPedState == PED_DEAD) { + foundDead = 1; + if (!deadPed) + deadPed = currentPed; + } else if (!currentPed->IsPedHeadAbovePos(-0.6f)) { + foundOnTheFloor = 1; + if (!pedOnTheFloor) + pedOnTheFloor = currentPed; + } + } + } else if ((distance < 0.8f && angleDiff < DEGTORAD(75.0f)) + || (distance < 1.3f && angleDiff < DEGTORAD(55.0f)) + || (distance < 1.7f && angleDiff < DEGTORAD(35.0f)) + || (distance < 2.0f && angleDiff < DEGTORAD(30.0f))) { + + // Either this condition or below one was probably returning 4 early in development. See Fight(). + foundBelow = 1; + pedBelow = currentPed; + break; + } else { + if (angleDiff < DEGTORAD(75.0f)) { + foundBelow = 1; + if (!pedBelow) + pedBelow = currentPed; + } + } + } + } + + if (foundOnTheFloor) { + currentPed = pedOnTheFloor; + stateToReturn = PED_ON_THE_FLOOR; + } else if (foundDead) { + currentPed = deadPed; + stateToReturn = PED_DEAD_ON_THE_FLOOR; + } else if (foundBelow) { + currentPed = pedBelow; + stateToReturn = PED_IN_FRONT_OF_ATTACKER; + } else { + currentPed = nil; + stateToReturn = NO_PED; + } + + if (pedOnGround) + *pedOnGround = currentPed; + + return stateToReturn; +} + +void +CPed::SetPointGunAt(CEntity *to) +{ + if (to) { + SetLookFlag(to, true); + SetAimFlag(to); +#ifdef VC_PED_PORTS + SetLookTimer(INT32_MAX); +#endif + } + + if (m_nPedState == PED_AIM_GUN || bIsDucking || m_nWaitState == WAITSTATE_PLAYANIM_DUCK) + return; + + if (m_nPedState != PED_ATTACK) + SetStoredState(); + + SetPedState(PED_AIM_GUN); + bIsPointingGunAt = true; + CWeaponInfo *curWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + SetMoveState(PEDMOVE_NONE); + + CAnimBlendAssociation *aimAssoc; + + if (bCrouchWhenShooting) + aimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), curWeapon->m_Anim2ToPlay); + else + aimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), curWeapon->m_AnimToPlay); + + if (!aimAssoc || aimAssoc->blendDelta < 0.0f) { + if (bCrouchWhenShooting) + aimAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_Anim2ToPlay, 4.0f); + else + aimAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_AnimToPlay); + + aimAssoc->blendAmount = 0.0f; + aimAssoc->blendDelta = 8.0f; + } + if (to) + Say(SOUND_PED_ATTACK); +} + +void +CPed::PointGunAt(void) +{ + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CAnimBlendAssociation *weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weaponInfo->m_AnimToPlay); + if (!weaponAssoc || weaponAssoc->blendDelta < 0.0f) + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weaponInfo->m_Anim2ToPlay); + + if (weaponAssoc && weaponAssoc->currentTime > weaponInfo->m_fAnimLoopStart) { + weaponAssoc->SetCurrentTime(weaponInfo->m_fAnimLoopStart); + weaponAssoc->flags &= ~ASSOC_RUNNING; + + if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) + m_pedIK.m_flags |= CPedIK::AIMS_WITH_ARM; + else + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; + } +} + +void +CPed::ClearPointGunAt(void) +{ + CAnimBlendAssociation *animAssoc; + CWeaponInfo *weaponInfo; + + ClearLookFlag(); + ClearAimFlag(); + bIsPointingGunAt = false; +#ifndef VC_PED_PORTS + if (m_nPedState == PED_AIM_GUN) { + RestorePreviousState(); +#else + if (m_nPedState == PED_AIM_GUN || m_nPedState == PED_ATTACK) { + SetPedState(PED_IDLE); + RestorePreviousState(); + } +#endif + weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weaponInfo->m_AnimToPlay); + if (!animAssoc || animAssoc->blendDelta < 0.0f) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weaponInfo->m_Anim2ToPlay); + } + if (animAssoc) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->blendDelta = -4.0f; + } +#ifndef VC_PED_PORTS + } +#endif +} + +void +CPed::SetAttack(CEntity *victim) +{ + CPed *victimPed = nil; + if (victim && victim->IsPed()) + victimPed = (CPed*)victim; + + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_BIGGUN); + if (animAssoc) { + animAssoc->blendDelta = -1000.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + + if (m_attackTimer > CTimer::GetTimeInMilliseconds() || m_nWaitState == WAITSTATE_SURPRISE) + return; + + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HGUN_RELOAD)) { + bIsAttacking = false; + return; + } + + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_AK_RELOAD)) { + if (!IsPlayer() || m_nPedState != PED_ATTACK || ((CPlayerPed*)this)->m_bHaveTargetSelected) + bIsAttacking = false; + else + bIsAttacking = true; + + return; + } + + CWeaponInfo *curWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + if (curWeapon->m_eWeaponFire == WEAPON_FIRE_INSTANT_HIT && !IsPlayer()) { + if (GetWeapon()->HitsGround(this, nil, victim)) + return; + } + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) { + if (IsPlayer() || + (m_nPedState != PED_FIGHT && m_nMoveState != PEDMOVE_NONE && m_nMoveState != PEDMOVE_STILL && !(m_pedStats->m_flags & STAT_SHOPPING_BAGS))) { + + if (m_nPedState != PED_ATTACK) { + SetPedState(PED_ATTACK); + bIsAttacking = false; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_AnimToPlay, 8.0f); + animAssoc->SetRun(); + if (animAssoc->currentTime == animAssoc->hierarchy->totalLength) + animAssoc->SetCurrentTime(0.0f); + + animAssoc->SetFinishCallback(FinishedAttackCB, this); + } + } else { + StartFightAttack(CGeneral::GetRandomNumber() % 256); + } + return; + } + + m_pSeekTarget = victim; + if (m_pSeekTarget) + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + + if (curWeapon->IsFlagSet(WEAPONFLAG_CANAIM)) { + CVector aimPos = GetRight() * 0.1f + GetForward() * 0.2f + GetPosition(); + CEntity *obstacle = CWorld::TestSphereAgainstWorld(aimPos, 0.2f, nil, true, false, false, true, false, false); + if (obstacle) + return; + + m_pLookTarget = victim; + if (victim) { + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + m_pSeekTarget->RegisterReference((CEntity **) &m_pSeekTarget); + } + + if (m_pLookTarget) { + SetAimFlag(m_pLookTarget); +#ifdef FREE_CAM + } else if (this != FindPlayerPed() || !((CPlayerPed*)this)->m_bFreeAimActive) { +#else + } else { +#endif + SetAimFlag(m_fRotationCur); + + if (FindPlayerPed() == this && TheCamera.Cams[0].Using3rdPersonMouseCam()) + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); + } + } +#ifdef FIX_BUGS + // fix aiming for flamethrower while using PC controls + else if (GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER && TheCamera.Cams[0].Using3rdPersonMouseCam() && this == FindPlayerPed()) + { + SetAimFlag(m_fRotationCur); + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); + } +#endif + if (m_nPedState == PED_ATTACK) { + bIsAttacking = true; + return; + } + + if (IsPlayer() || !victimPed || victimPed->IsPedInControl()) { + if (IsPlayer()) + CPad::GetPad(0)->ResetAverageWeapon(); + + uint8 pointBlankStatus; + if ((curWeapon->m_eWeaponFire == WEAPON_FIRE_INSTANT_HIT || GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER) + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON_RUNABOUT + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER_RUNABOUT + && (pointBlankStatus = CheckForPointBlankPeds(victimPed)) != NO_POINT_BLANK_PED) { + ClearAimFlag(); + + // This condition is pointless + if (pointBlankStatus == POINT_BLANK_FOR_WANTED_PED || !victimPed) + StartFightAttack(200); + } else { + if (!curWeapon->IsFlagSet(WEAPONFLAG_CANAIM)) + m_pSeekTarget = nil; + + if (m_nPedState != PED_AIM_GUN) + SetStoredState(); + + SetPedState(PED_ATTACK); + SetMoveState(PEDMOVE_NONE); + if (bCrouchWhenShooting) { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_RBLOCK_SHOOT, 4.0f); + } else { + float animDelta = 8.0f; + if (curWeapon->m_eWeaponFire == WEAPON_FIRE_MELEE) + animDelta = 1000.0f; + + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_BASEBALLBAT + || CheckForPedsOnGroundToAttack(this, nil) < PED_ON_THE_FLOOR) { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_AnimToPlay, animDelta); + } else { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, curWeapon->m_Anim2ToPlay, animDelta); + } + } + + animAssoc->SetRun(); + if (animAssoc->currentTime == animAssoc->hierarchy->totalLength) + animAssoc->SetCurrentTime(0.0f); + + animAssoc->SetFinishCallback(FinishedAttackCB, this); + } + return; + } + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT && victimPed->m_nPedState == PED_GETUP) + SetWaitState(WAITSTATE_SURPRISE, nil); + + SetLookFlag(victim, false); + SetLookTimer(100); +} + +void +CPed::ClearAttack(void) +{ + if (m_nPedState != PED_ATTACK || bIsDucking || m_nWaitState == WAITSTATE_PLAYANIM_DUCK) + return; + +#ifdef VC_PED_PORTS + // VC uses CCamera::Using1stPersonWeaponMode + if (FindPlayerPed() == this && (TheCamera.PlayerWeaponMode.Mode == CCam::MODE_SNIPER || + TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_ROCKETLAUNCHER)) { + SetPointGunAt(nil); + } else +#endif + if (bIsPointingGunAt) { + if (m_pLookTarget) + SetPointGunAt(m_pLookTarget); + else + ClearPointGunAt(); + } else if (m_objective != OBJECTIVE_NONE) { + SetIdle(); + } else { + RestorePreviousState(); + } +} + +void +CPed::ClearAttackByRemovingAnim(void) +{ + if (m_nPedState != PED_ATTACK || bIsDucking) + return; + + CWeaponInfo *weapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CAnimBlendAssociation *weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weapon->m_AnimToPlay); + if (!weaponAssoc) { + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), weapon->m_Anim2ToPlay); + + if (!weaponAssoc && weapon->IsFlagSet(WEAPONFLAG_THROW)) + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_THROW_UNDER); + + if (!weaponAssoc) { + ClearAttack(); + return; + } + } + weaponAssoc->blendDelta = -8.0f; + weaponAssoc->flags &= ~ASSOC_RUNNING; + weaponAssoc->flags |= ASSOC_DELETEFADEDOUT; + weaponAssoc->SetDeleteCallback(FinishedAttackCB, this); +} + +void +CPed::FinishedAttackCB(CAnimBlendAssociation *attackAssoc, void *arg) +{ + CWeaponInfo *currentWeapon; + CAnimBlendAssociation *newAnim; + CPed *ped = (CPed*)arg; + + if (attackAssoc) { + switch (attackAssoc->animId) { + case ANIM_STD_START_THROW: + // what?! + if ((!ped->IsPlayer() || ((CPlayerPed*)ped)->m_bHaveTargetSelected) && ped->IsPlayer()) { + attackAssoc->blendDelta = -1000.0f; + newAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_THROW_UNDER); + } else { + attackAssoc->blendDelta = -1000.0f; + newAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_WEAPON_THROW); + } + + newAnim->SetFinishCallback(FinishedAttackCB, ped); + return; + + case ANIM_STD_PARTIAL_PUNCH: + attackAssoc->blendDelta = -8.0f; + attackAssoc->flags |= ASSOC_DELETEFADEDOUT; + ped->ClearAttack(); + return; + + case ANIM_STD_WEAPON_THROW: + case ANIM_STD_THROW_UNDER: + if (ped->GetWeapon()->m_nAmmoTotal > 0) { + currentWeapon = CWeaponInfo::GetWeaponInfo(ped->GetWeapon()->m_eWeaponType); + ped->AddWeaponModel(currentWeapon->m_nModelId); + } + break; + default: + break; + } + } + + if (!ped->bIsAttacking) + ped->ClearAttack(); +} + +uint8 +CPed::CheckForPointBlankPeds(CPed *pedToVerify) +{ + float pbDistance = 1.1f; + if (GetWeapon()->IsType2Handed()) + pbDistance = 1.6f; + + for (int i = 0; i < m_numNearPeds; i++) { + CPed *nearPed = m_nearPeds[i]; + + if (!pedToVerify || pedToVerify == nearPed) { + + CVector diff = nearPed->GetPosition() - GetPosition(); + if (diff.Magnitude() < pbDistance) { + + float neededAngle = CGeneral::GetRadianAngleBetweenPoints( + nearPed->GetPosition().x, nearPed->GetPosition().y, + GetPosition().x, GetPosition().y); + neededAngle = CGeneral::LimitRadianAngle(neededAngle); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + + float neededTurn = Abs(neededAngle - m_fRotationCur); + + if (neededTurn > PI) + neededTurn = 2*PI - neededTurn; + + if (nearPed->OnGroundOrGettingUp() || nearPed->m_nPedState == PED_DIVE_AWAY) + return NO_POINT_BLANK_PED; + + if (neededTurn < CAN_SEE_ENTITY_ANGLE_THRESHOLD) { + if (pedToVerify == nearPed) + return POINT_BLANK_FOR_WANTED_PED; + else + return POINT_BLANK_FOR_SOMEONE_ELSE; + } + } + } + } + return NO_POINT_BLANK_PED; +} + +void +CPed::Attack(void) +{ + CAnimBlendAssociation *weaponAnimAssoc; + int32 weaponAnim; + float animStart; + float weaponAnimTime; + float animLoopEnd; + CWeaponInfo *ourWeapon; + bool attackShouldContinue; + AnimationId reloadAnim; + CAnimBlendAssociation *reloadAnimAssoc; + float delayBetweenAnimAndFire; + CVector firePos; + + ourWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ourWeapon->m_AnimToPlay); + attackShouldContinue = bIsAttacking; + reloadAnimAssoc = nil; + reloadAnim = ANIM_STD_NUM; + delayBetweenAnimAndFire = ourWeapon->m_fAnimFrameFire; + weaponAnim = ourWeapon->m_AnimToPlay; + + if (weaponAnim == ANIM_STD_WEAPON_HGUN_BODY) + reloadAnim = ANIM_STD_HGUN_RELOAD; + else if (weaponAnim == ANIM_STD_WEAPON_AK_BODY) + reloadAnim = ANIM_STD_AK_RELOAD; + + if (reloadAnim != ANIM_STD_NUM) + reloadAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), reloadAnim); + + if (bIsDucking) + return; + + if (reloadAnimAssoc) { + if (!IsPlayer() || ((CPlayerPed*)this)->m_bHaveTargetSelected) + ClearAttack(); + + return; + } + + if (CTimer::GetTimeInMilliseconds() < m_shootTimer) + attackShouldContinue = true; + + if (!weaponAnimAssoc) { + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ourWeapon->m_Anim2ToPlay); + delayBetweenAnimAndFire = ourWeapon->m_fAnim2FrameFire; + + // Long throw granade, molotov + if (!weaponAnimAssoc && ourWeapon->IsFlagSet(WEAPONFLAG_THROW)) { + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_THROW_UNDER); + delayBetweenAnimAndFire = 0.2f; + } + + if (!weaponAnimAssoc) { + if (attackShouldContinue) { + if (ourWeapon->m_eWeaponFire != WEAPON_FIRE_PROJECTILE || !IsPlayer() || ((CPlayerPed*)this)->m_bHaveTargetSelected) { + if (!CGame::nastyGame || ourWeapon->m_eWeaponFire != WEAPON_FIRE_MELEE || CheckForPedsOnGroundToAttack(this, nil) < PED_ON_THE_FLOOR) { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ourWeapon->m_AnimToPlay, 8.0f); + } + else { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ourWeapon->m_Anim2ToPlay, 8.0f); + } + + weaponAnimAssoc->SetFinishCallback(FinishedAttackCB, this); + weaponAnimAssoc->SetRun(); + + if (weaponAnimAssoc->currentTime == weaponAnimAssoc->hierarchy->totalLength) + weaponAnimAssoc->SetCurrentTime(0.0f); + + if (IsPlayer()) { + ((CPlayerPed*)this)->m_fAttackButtonCounter = 0.0f; + ((CPlayerPed*)this)->m_bHaveTargetSelected = false; + } + } + } else + FinishedAttackCB(nil, this); + + return; + } + } + + animStart = ourWeapon->m_fAnimLoopStart; + weaponAnimTime = weaponAnimAssoc->currentTime; + if (weaponAnimTime > animStart && weaponAnimTime - weaponAnimAssoc->timeStep <= animStart) { + if (ourWeapon->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) + m_pedIK.m_flags |= CPedIK::AIMS_WITH_ARM; + else + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; + } + + if (weaponAnimTime <= delayBetweenAnimAndFire || weaponAnimTime - weaponAnimAssoc->timeStep > delayBetweenAnimAndFire || !weaponAnimAssoc->IsRunning()) { + if (weaponAnimAssoc->speed < 1.0f) + weaponAnimAssoc->speed = 1.0f; + + } else { + firePos = ourWeapon->m_vecFireOffset; + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT) { + if (weaponAnimAssoc->animId == ourWeapon->m_Anim2ToPlay) + firePos.z = 0.7f * ourWeapon->m_fRadius - 1.0f; + + firePos = GetMatrix() * firePos; + } else if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + TransformToNode(firePos, weaponAnimAssoc->animId == ANIM_STD_KICKGROUND ? PED_FOOTR : PED_HANDR); + } else { + firePos = GetMatrix() * firePos; + } + + GetWeapon()->Fire(this, &firePos); + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_MOLOTOV || GetWeapon()->m_eWeaponType == WEAPONTYPE_GRENADE) { + RemoveWeaponModel(ourWeapon->m_nModelId); + } + if (!GetWeapon()->m_nAmmoTotal && ourWeapon->m_eWeaponFire != WEAPON_FIRE_MELEE && FindPlayerPed() != this) { + SelectGunIfArmed(); + } + + if (GetWeapon()->m_eWeaponState != WEAPONSTATE_MELEE_MADECONTACT) { + // If reloading just began, start the animation + // Last condition will always return true, even IDA hides it + if (GetWeapon()->m_eWeaponState == WEAPONSTATE_RELOADING && reloadAnim != ANIM_STD_NUM /* && !reloadAnimAssoc*/) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, reloadAnim, 8.0f); + ClearLookFlag(); + ClearAimFlag(); + bIsAttacking = false; + bIsPointingGunAt = false; + m_shootTimer = CTimer::GetTimeInMilliseconds(); + return; + } + } else { + if (weaponAnimAssoc->animId == ANIM_STD_WEAPON_BAT_V || weaponAnimAssoc->animId == ANIM_STD_WEAPON_BAT_H) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_BAT_ATTACK, 1.0f); + } else if (weaponAnimAssoc->animId == ANIM_STD_PARTIAL_PUNCH) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FIGHT_PUNCH_39, 0.0f); + } + + weaponAnimAssoc->speed = 0.5f; + + if (bIsAttacking || CTimer::GetTimeInMilliseconds() < m_shootTimer) { + weaponAnimAssoc->callbackType = 0; + } + } + + attackShouldContinue = false; + } + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_SHOTGUN) { + weaponAnimTime = weaponAnimAssoc->currentTime; + firePos = ourWeapon->m_vecFireOffset; + + if (weaponAnimTime > 1.0f && weaponAnimTime - weaponAnimAssoc->timeStep <= 1.0f && weaponAnimAssoc->IsRunning()) { + TransformToNode(firePos, PED_HANDR); + + CVector gunshellPos( + firePos.x - 0.6f * GetForward().x, + firePos.y - 0.6f * GetForward().y, + firePos.z - 0.15f * GetUp().z + ); + + CVector2D gunshellRot( + GetRight().x, + GetRight().y + ); + + gunshellRot.Normalise(); + GetWeapon()->AddGunshell(this, gunshellPos, gunshellRot, 0.025f); + } + } +#ifdef VC_PED_PORTS + if (IsPlayer()) { + if (CPad::GetPad(0)->GetSprint()) { + // animBreakout is a member of WeaponInfo in VC, so it's me that added the below line. + float animBreakOut = ((GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER || GetWeapon()->m_eWeaponType == WEAPONTYPE_UZI || GetWeapon()->m_eWeaponType == WEAPONTYPE_SHOTGUN) ? 25 / 30.0f : 99 / 30.0f); + if (!attackShouldContinue && weaponAnimAssoc->currentTime > animBreakOut) { + weaponAnimAssoc->blendDelta = -4.0f; + FinishedAttackCB(nil, this); + return; + } + } + } +#endif + animLoopEnd = ourWeapon->m_fAnimLoopEnd; + if (ourWeapon->m_eWeaponFire == WEAPON_FIRE_MELEE && weaponAnimAssoc->animId == ourWeapon->m_Anim2ToPlay) + animLoopEnd = 3.4f/6.0f; + + weaponAnimTime = weaponAnimAssoc->currentTime; + + // Anim loop end, either start the loop again or finish the attack + if (weaponAnimTime > animLoopEnd || !weaponAnimAssoc->IsRunning() && ourWeapon->m_eWeaponFire != WEAPON_FIRE_PROJECTILE) { + + if (weaponAnimTime - 2.0f * weaponAnimAssoc->timeStep <= animLoopEnd + && (bIsAttacking || CTimer::GetTimeInMilliseconds() < m_shootTimer) + && GetWeapon()->m_eWeaponState != WEAPONSTATE_RELOADING) { + + weaponAnim = weaponAnimAssoc->animId; + if (ourWeapon->m_eWeaponFire != WEAPON_FIRE_MELEE || CheckForPedsOnGroundToAttack(this, nil) < PED_ON_THE_FLOOR) { + if (weaponAnim != ourWeapon->m_Anim2ToPlay || weaponAnim == ANIM_STD_RBLOCK_SHOOT) { + weaponAnimAssoc->Start(ourWeapon->m_fAnimLoopStart); + } else { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ourWeapon->m_AnimToPlay, 8.0f); + } + } else { + if (weaponAnim == ourWeapon->m_Anim2ToPlay) + weaponAnimAssoc->SetCurrentTime(0.1f); + else + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ourWeapon->m_Anim2ToPlay, 8.0f); + } +#ifdef VC_PED_PORTS + } else if (IsPlayer() && m_pPointGunAt && bIsAimingGun && GetWeapon()->m_eWeaponState != WEAPONSTATE_RELOADING) { + weaponAnimAssoc->SetCurrentTime(ourWeapon->m_fAnimLoopEnd); + weaponAnimAssoc->flags &= ~ASSOC_RUNNING; + SetPointGunAt(m_pPointGunAt); +#endif +#ifdef FREE_CAM + } else if (IsPlayer() && ((CPlayerPed*)this)->m_bFreeAimActive && GetWeapon()->m_eWeaponState != WEAPONSTATE_RELOADING) { + float limitedCam = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + SetLookFlag(limitedCam, true); + SetAimFlag(limitedCam); + SetLookTimer(INT32_MAX); + SetPointGunAt(nil); + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); +#endif + } else { + ClearAimFlag(); + + // Echoes of bullets, at the end of the attack. (Bug: doesn't play while reloading) + if (weaponAnimAssoc->currentTime - weaponAnimAssoc->timeStep <= ourWeapon->m_fAnimLoopEnd) { + switch (GetWeapon()->m_eWeaponType) { + case WEAPONTYPE_UZI: + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_UZI_BULLET_ECHO, 0.0f); + break; + case WEAPONTYPE_AK47: + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_AK47_BULLET_ECHO, 0.0f); + break; + case WEAPONTYPE_M16: + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_M16_BULLET_ECHO, 0.0f); + break; + default: + break; + } + } + + // Fun fact: removing this part leds to reloading flamethrower + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER && weaponAnimAssoc->IsRunning()) { + weaponAnimAssoc->flags |= ASSOC_DELETEFADEDOUT; + weaponAnimAssoc->flags &= ~ASSOC_RUNNING; + weaponAnimAssoc->blendDelta = -4.0f; + } + } + } + if (weaponAnimAssoc->currentTime > delayBetweenAnimAndFire) + attackShouldContinue = false; + + bIsAttacking = attackShouldContinue; +} + +void +CPed::StartFightAttack(uint8 buttonPressure) +{ + if (!IsPedInControl() || m_attackTimer > CTimer::GetTimeInMilliseconds()) + return; + + if (m_nPedState == PED_FIGHT) { + m_fightButtonPressure = buttonPressure; + return; + } + + if (m_nPedState != PED_AIM_GUN) + SetStoredState(); + + if (m_nWaitState != WAITSTATE_FALSE) { + m_nWaitState = WAITSTATE_FALSE; + RestoreHeadingRate(); + } + + SetPedState(PED_FIGHT); + m_fightButtonPressure = 0; + RpAnimBlendClumpRemoveAssociations(GetClump(), ASSOC_REPEAT); + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_STARTWALK); + + if (animAssoc) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->blendDelta = -1000.0f; + } + + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP1); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + + if (animAssoc) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->blendDelta = -1000.0f; + RestoreHeadingRate(); + } + + SetMoveState(PEDMOVE_NONE); + m_nStoredMoveState = PEDMOVE_NONE; + + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_IDLE)->blendAmount = 1.0f; + + CPed *pedOnGround = nil; + if (IsPlayer() && CheckForPedsOnGroundToAttack(this, &pedOnGround) > PED_IN_FRONT_OF_ATTACKER) { + m_curFightMove = FIGHTMOVE_GROUNDKICK; + } else if (m_pedStats->m_flags & STAT_SHOPPING_BAGS) { + m_curFightMove = FIGHTMOVE_ROUNDHOUSE; + } else { + m_curFightMove = FIGHTMOVE_STDPUNCH; + } + + if (pedOnGround && IsPlayer()) { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + pedOnGround->GetPosition().x, pedOnGround->GetPosition().y, + GetPosition().x, GetPosition().y); + + m_fRotationDest = CGeneral::LimitRadianAngle(m_fRotationDest); + m_fRotationCur = m_fRotationDest; + m_lookTimer = 0; + SetLookFlag(pedOnGround, true); + SetLookTimer(1500); + } + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 4.0f); + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + m_fightState = FIGHTSTATE_NO_MOVE; + m_takeAStepAfterAttack = false; + bIsAttacking = true; + + if (IsPlayer()) + nPlayerInComboMove = 0; +} + +void +CPed::StartFightDefend(uint8 direction, uint8 hitLevel, uint8 unk) +{ + if (m_nPedState == PED_DEAD) { + if (CGame::nastyGame) { + if (hitLevel == HITLEVEL_GROUND) { + CAnimBlendAssociation *floorHitAssoc; + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) { + floorHitAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f); + } else { + floorHitAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[FIGHTMOVE_HITONFLOOR].animId, 8.0f); + } + if (floorHitAssoc) { + floorHitAssoc->SetCurrentTime(0.0f); + floorHitAssoc->SetRun(); + floorHitAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + } + } + if (CGame::nastyGame) { + CVector headPos = GetNodePosition(PED_HEAD); + for(int i = 0; i < 4; ++i) { + CVector bloodDir(0.0f, 0.0f, 0.1f); + CVector bloodPos = headPos - 0.2f * GetForward(); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, bloodDir, nil, 0.0f, 0, 0, 0, 0); + } + } + } + } else if (m_nPedState == PED_FALL) { + if (hitLevel == HITLEVEL_GROUND && !IsPedHeadAbovePos(-0.3f)) { + CAnimBlendAssociation *floorHitAssoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL) ? + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f) : + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR, 8.0f); + if (floorHitAssoc) { + floorHitAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + floorHitAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + } else if (IsPedInControl()) { + if ((IsPlayer() && m_nPedState != PED_FIGHT && ((CPlayerPed*)this)->m_fMoveSpeed > 1.0f) + || (!IsPlayer() && m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE)) { +#ifndef VC_PED_PORTS + if (hitLevel != HITLEVEL_HIGH && hitLevel != HITLEVEL_LOW || (IsPlayer() || CGeneral::GetRandomNumber() & 3) && CGeneral::GetRandomNumber() & 7) { + if (IsPlayer() || CGeneral::GetRandomNumber() & 3) { +#else + if (hitLevel != HITLEVEL_HIGH && hitLevel != HITLEVEL_LOW || (IsPlayer() || CGeneral::GetRandomNumber() & 1) && CGeneral::GetRandomNumber() & 7) { + if (IsPlayer() || CGeneral::GetRandomNumber() & 1) { +#endif + AnimationId shotAnim; + switch (direction) { + case 1: + shotAnim = ANIM_STD_HITBYGUN_LEFT; + break; + case 2: + shotAnim = ANIM_STD_HITBYGUN_BACK; + break; + case 3: + shotAnim = ANIM_STD_HITBYGUN_RIGHT; + break; + default: + shotAnim = ANIM_STD_HITBYGUN_FRONT; + break; + } + CAnimBlendAssociation *shotAssoc = RpAnimBlendClumpGetAssociation(GetClump(), shotAnim); + if (!shotAssoc || shotAssoc->blendDelta < 0.0f) + shotAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, shotAnim, 8.0f); + + shotAssoc->SetCurrentTime(0.0f); + shotAssoc->SetRun(); + shotAssoc->flags |= ASSOC_FADEOUTWHENDONE; + } else { + int time = CGeneral::GetRandomNumberInRange(1000, 3000); + SetWaitState(WAITSTATE_PLAYANIM_DUCK, &time); + } + } else { +#ifndef VC_PED_PORTS + switch (direction) { + case 1: + SetFall(500, ANIM_STD_HIGHIMPACT_LEFT, false); + break; + case 2: + SetFall(500, ANIM_STD_HIGHIMPACT_BACK, false); + break; + case 3: + SetFall(500, ANIM_STD_HIGHIMPACT_RIGHT, false); + break; + default: + SetFall(500, ANIM_STD_KO_SHOT_STOMACH, false); + break; + } +#else + bool fall = true; + AnimationId hitAnim; + switch (direction) { + case 1: + hitAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + if (CGeneral::GetRandomNumber() & 1) { + fall = false; + hitAnim = ANIM_STD_HIT_BACK; + } else { + hitAnim = ANIM_STD_HIGHIMPACT_BACK; + } + break; + case 3: + hitAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + if (hitLevel == HITLEVEL_LOW) { + hitAnim = ANIM_STD_KO_SHOT_STOMACH; + } else if (CGeneral::GetRandomNumber() & 1) { + fall = false; + hitAnim = ANIM_STD_HIT_WALK; + } else if (CGeneral::GetRandomNumber() & 1) { + fall = false; + hitAnim = ANIM_STD_HIT_HEAD; + } else { + hitAnim = ANIM_STD_KO_SHOT_FACE; + } + break; + } + if (fall) { + SetFall(500, hitAnim, false); + } else { + CAnimBlendAssociation *hitAssoc = RpAnimBlendClumpGetAssociation(GetClump(), hitAnim); + if (!hitAssoc || hitAssoc->blendDelta < 0.0f) + hitAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, hitAnim, 8.0f); + + hitAssoc->SetCurrentTime(0.0f); + hitAssoc->SetRun(); + hitAssoc->flags |= ASSOC_FADEOUTWHENDONE; + } +#endif + } + Say(SOUND_PED_DEFEND); + } else { + Say(SOUND_PED_DEFEND); + switch (hitLevel) { + case HITLEVEL_GROUND: + m_curFightMove = FIGHTMOVE_HITONFLOOR; + break; + case HITLEVEL_LOW: +#ifndef VC_PED_PORTS + if (direction == 2) { + SetFall(1000, ANIM_STD_HIGHIMPACT_BACK, false); + return; + } +#else + if (direction == 2 && (!IsPlayer() || ((CGeneral::GetRandomNumber() & 1) && m_fHealth < 30.0f))) { + SetFall(1000, ANIM_STD_HIGHIMPACT_BACK, false); + return; + } else if (direction != 2 && !IsPlayer() && (CGeneral::GetRandomNumber() & 1) && m_fHealth < 30.0f) { + SetFall(1000, ANIM_STD_KO_SHOT_STOMACH, false); + return; + } +#endif + m_curFightMove = FIGHTMOVE_HITBODY; + break; + case HITLEVEL_HIGH: + switch (direction) { + case 1: + m_curFightMove = FIGHTMOVE_HITLEFT; + break; + case 2: + m_curFightMove = FIGHTMOVE_HITBACK; + break; + case 3: + m_curFightMove = FIGHTMOVE_HITRIGHT; + break; + default: + if (unk <= 5) + m_curFightMove = FIGHTMOVE_HITHEAD; + else + m_curFightMove = FIGHTMOVE_HITBIGSTEP; + break; + } + break; + default: + switch (direction) { + case 1: + m_curFightMove = FIGHTMOVE_HITLEFT; + break; + case 2: + m_curFightMove = FIGHTMOVE_HITBACK; + break; + case 3: + m_curFightMove = FIGHTMOVE_HITRIGHT; + break; + default: + if (unk <= 5) + m_curFightMove = FIGHTMOVE_HITCHEST; + else + m_curFightMove = FIGHTMOVE_HITBIGSTEP; + break; + } + break; + } + if (m_nPedState == PED_GETUP && !IsPedHeadAbovePos(0.0f)) + m_curFightMove = FIGHTMOVE_HITONFLOOR; + + if (m_nPedState == PED_FIGHT) { + CAnimBlendAssociation *moveAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 8.0f); + moveAssoc->SetCurrentTime(0.0f); + moveAssoc->SetFinishCallback(FinishFightMoveCB, this); + if (IsPlayer()) + moveAssoc->speed = 1.3f; + + m_takeAStepAfterAttack = false; + m_fightButtonPressure = 0; + } else if (IsPlayer() && m_currentWeapon != WEAPONTYPE_UNARMED) { + CAnimBlendAssociation *moveAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 4.0f); + moveAssoc->SetCurrentTime(0.0f); + moveAssoc->speed = 1.3f; + } else { + if (m_nPedState != PED_AIM_GUN && m_nPedState != PED_ATTACK) + SetStoredState(); + + if (m_nWaitState != WAITSTATE_FALSE) { + m_nWaitState = WAITSTATE_FALSE; + RestoreHeadingRate(); + } + SetPedState(PED_FIGHT); + m_fightButtonPressure = 0; + RpAnimBlendClumpRemoveAssociations(GetClump(), ASSOC_REPEAT); + CAnimBlendAssociation *walkStartAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_STARTWALK); + if (walkStartAssoc) { + walkStartAssoc->flags |= ASSOC_DELETEFADEDOUT; + walkStartAssoc->blendDelta = -1000.0f; + } + CAnimBlendAssociation *walkStopAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP1); + if (!walkStopAssoc) + walkStopAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + if (walkStopAssoc) { + walkStopAssoc->flags |= ASSOC_DELETEFADEDOUT; + walkStopAssoc->blendDelta = -1000.0f; + RestoreHeadingRate(); + } + SetMoveState(PEDMOVE_NONE); + m_nStoredMoveState = PEDMOVE_NONE; + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_IDLE)->blendAmount = 1.0f; + CAnimBlendAssociation *moveAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 8.0f); + moveAssoc->SetFinishCallback(FinishFightMoveCB, this); + m_fightState = FIGHTSTATE_NO_MOVE; + m_takeAStepAfterAttack = false; + bIsAttacking = true; + } + } + } +} + +void +CPed::Fight(void) +{ + CAnimBlendAssociation *currentAssoc, *animAssoc; + bool hasShoppingBags, punchOnly, canKick, canKneeHead, canRoundhouse; + float angleToFace, nextAngle; + bool goForward = false; + int nextFightMove; + + switch (m_curFightMove) { + case FIGHTMOVE_NULL: + return; + case FIGHTMOVE_IDLE2NORM: + m_curFightMove = FIGHTMOVE_NULL; + RestorePreviousState(); + + // FIX: Uninitialized + currentAssoc = nil; + break; + case FIGHTMOVE_IDLE: + currentAssoc = nil; + break; + default: + currentAssoc = RpAnimBlendClumpGetAssociation(GetClump(), tFightMoves[m_curFightMove].animId); + break; + } + + if (!bIsAttacking && IsPlayer()) { + if (currentAssoc) { + currentAssoc->blendDelta = -1000.0f; + currentAssoc->flags |= ASSOC_DELETEFADEDOUT; + currentAssoc->flags &= ~ASSOC_RUNNING; + } + if (m_takeAStepAfterAttack) + EndFight(ENDFIGHT_WITH_A_STEP); + else + EndFight(ENDFIGHT_FAST); + + } else if (currentAssoc && m_fightState > FIGHTSTATE_MOVE_FINISHED) { + float animTime = currentAssoc->currentTime; + FightMove &curMove = tFightMoves[m_curFightMove]; + if (curMove.hitLevel != HITLEVEL_NULL && animTime > curMove.startFireTime && animTime <= curMove.endFireTime && m_fightState >= FIGHTSTATE_NO_MOVE) { + + CVector touchingNodePos(0.0f, 0.0f, 0.0f); + + switch (m_curFightMove) { + case FIGHTMOVE_STDPUNCH: + case FIGHTMOVE_PUNCHHOOK: + case FIGHTMOVE_BODYBLOW: + TransformToNode(touchingNodePos, PED_HANDR); + break; + case FIGHTMOVE_IDLE: + case FIGHTMOVE_SHUFFLE_F: + break; + case FIGHTMOVE_KNEE: + TransformToNode(touchingNodePos, PED_LOWERLEGR); + break; + case FIGHTMOVE_HEADBUTT: + TransformToNode(touchingNodePos, PED_HEAD); + break; + case FIGHTMOVE_PUNCHJAB: + TransformToNode(touchingNodePos, PED_HANDL); + break; + case FIGHTMOVE_KICK: + case FIGHTMOVE_LONGKICK: + case FIGHTMOVE_ROUNDHOUSE: + case FIGHTMOVE_GROUNDKICK: + TransformToNode(touchingNodePos, PED_FOOTR); + break; + } + + if (m_curFightMove == FIGHTMOVE_PUNCHJAB) { + touchingNodePos += 0.1f * GetForward(); + } else if (m_curFightMove == FIGHTMOVE_PUNCHHOOK) { + touchingNodePos += 0.22f * GetForward(); + } + FightStrike(touchingNodePos); + m_fightButtonPressure = 0; + return; + } + + if (curMove.hitLevel != HITLEVEL_NULL) { + if (animTime > curMove.endFireTime) { + if (IsPlayer()) + currentAssoc->speed = 1.0f; + else + currentAssoc->speed = 0.8f; + } + + if (IsPlayer() && !nPlayerInComboMove) { + if (curMove.comboFollowOnTime > 0.0f && m_fightButtonPressure != 0 && animTime > curMove.comboFollowOnTime) { + + // Notice that it increases fight move index, because we're in combo! + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[++m_curFightMove].animId, 8.0f); + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + animAssoc->SetCurrentTime(0.1f * animAssoc->hierarchy->totalLength); + m_fightButtonPressure = 0; + nPlayerInComboMove = 1; + } + } + } else { + if (curMove.startFireTime > 0.0f && m_curFightMove != FIGHTMOVE_SHUFFLE_F && animTime > curMove.startFireTime) { + if (IsPlayer()) + currentAssoc->speed = 1.3f; + else + currentAssoc->speed = 0.8f; + } + } + } else if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + EndFight(ENDFIGHT_FAST); + + } else if (m_fightButtonPressure != 0) { + bool canAffectMultiplePeople = true; + nextAngle = m_fRotationCur; + bool kickGround = false; + float angleForGroundKick = 0.0f; + CPed *pedOnGround = nil; + + Say(SOUND_PED_ATTACK); + + if (IsPlayer()) { + canRoundhouse = false; + punchOnly = false; + canKick = true; + nextFightMove = (m_fightButtonPressure > 190 ? FIGHTMOVE_HEADBUTT : FIGHTMOVE_KNEE); + hasShoppingBags = false; + canKneeHead = true; + nPlayerInComboMove = 0; + } else { + nextFightMove = (m_fightButtonPressure > 120 ? FIGHTMOVE_HEADBUTT : FIGHTMOVE_KNEE); + uint16 pedFeatures = m_pedStats->m_flags; + punchOnly = pedFeatures & STAT_PUNCH_ONLY; + canRoundhouse = pedFeatures & STAT_CAN_ROUNDHOUSE; + canKneeHead = pedFeatures & STAT_CAN_KNEE_HEAD; + canKick = pedFeatures & STAT_CAN_KICK; + hasShoppingBags = pedFeatures & STAT_SHOPPING_BAGS; + } + + // Attack isn't scripted, find the victim + if (IsPlayer() || !m_pedInObjective) { + + for (int i = 0; i < m_numNearPeds; i++) { + + CPed *nearPed = m_nearPeds[i]; + float nearPedDist = (nearPed->GetPosition() - GetPosition()).Magnitude(); + if (nearPedDist < 3.0f) { + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + nearPed->GetPosition().x, nearPed->GetPosition().y, + GetPosition().x, GetPosition().y); + + nextAngle = CGeneral::LimitRadianAngle(angleToFace); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + + float neededTurn = Abs(nextAngle - m_fRotationCur); + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + if (!nearPed->OnGroundOrGettingUp()) { + + if (nearPedDist < 0.8f && neededTurn < DEGTORAD(75.0f) && canKneeHead) { + canAffectMultiplePeople = false; + } else if (nearPedDist >= 1.3f || neededTurn >= DEGTORAD(55.0f) || hasShoppingBags) { + + if (nearPedDist < 1.7f + && neededTurn < DEGTORAD(35.0f) + && (canKick || hasShoppingBags)) { + + nextFightMove = FIGHTMOVE_KICK; + if (hasShoppingBags) { + nextFightMove = FIGHTMOVE_ROUNDHOUSE; + } else if (canRoundhouse && CGeneral::GetRandomNumber() & 1) { + nextFightMove = FIGHTMOVE_ROUNDHOUSE; + } + canAffectMultiplePeople = false; + } else if (nearPedDist < 2.0f && neededTurn < DEGTORAD(30.0f) && canKick) { + canAffectMultiplePeople = false; + nextFightMove = FIGHTMOVE_LONGKICK; + } else if (neededTurn < DEGTORAD(30.0f)) { + goForward = true; + } + } else { + nextFightMove += 2; // Makes it 6 or 7 + if (punchOnly) + nextFightMove = FIGHTMOVE_PUNCHJAB; + + canAffectMultiplePeople = false; + } + } else if (!CGame::nastyGame + || nearPedDist >= 1.3f + || neededTurn >= DEGTORAD(55.0f) + || punchOnly) { + + if (nearPedDist > 0.8f + && nearPedDist < 3.0f + && neededTurn < DEGTORAD(30.0f)) { + goForward = true; + } + + } else if (nearPed->m_nPedState != PED_DEAD || pedOnGround) { + if (!nearPed->IsPedHeadAbovePos(-0.3f)) { + canAffectMultiplePeople = false; + nextFightMove = FIGHTMOVE_GROUNDKICK; + } + + } else { + pedOnGround = nearPed; + kickGround = true; + angleForGroundKick = nextAngle; + } + } + + if (!canAffectMultiplePeople) { + m_fRotationDest = nextAngle; + if (IsPlayer()) { + m_fRotationCur = m_fRotationDest; + m_lookTimer = 0; + SetLookFlag(nearPed, true); + SetLookTimer(1500); + } + break; + } + } + } else { + // Because we're in a scripted fight with some particular ped. + canAffectMultiplePeople = false; + + float fightingPedDist = (m_pedInObjective->GetPosition() - GetPosition()).Magnitude(); + if (hasShoppingBags) { + if (fightingPedDist >= 1.7f) + nextFightMove = FIGHTMOVE_SHUFFLE_F; + else + nextFightMove = FIGHTMOVE_ROUNDHOUSE; + + } else if (punchOnly) { + if (fightingPedDist >= 1.3f) + nextFightMove = FIGHTMOVE_SHUFFLE_F; + else + nextFightMove = FIGHTMOVE_PUNCHJAB; + + } else if (fightingPedDist >= 3.0f) { + nextFightMove = FIGHTMOVE_STDPUNCH; + + } else { + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + m_pedInObjective->GetPosition().x, + m_pedInObjective->GetPosition().y, + GetPosition().x, + GetPosition().y); + + nextAngle = CGeneral::LimitRadianAngle(angleToFace); + m_fRotationDest = nextAngle; + m_fRotationCur = m_fRotationDest; + if (!m_pedInObjective->OnGroundOrGettingUp()) { + + if (fightingPedDist >= 0.8f || !canKneeHead) { + + if (fightingPedDist >= 1.3f) { + + if (fightingPedDist < 1.7f && canKick) { + nextFightMove = FIGHTMOVE_KICK; + if (canRoundhouse && CGeneral::GetRandomNumber() & 1) + nextFightMove = FIGHTMOVE_ROUNDHOUSE; + + } else if (fightingPedDist < 2.0f && canKick) { + nextFightMove += 5; // Makes it 9 or 10 + + } else { + nextFightMove = FIGHTMOVE_SHUFFLE_F; + + } + } else { + nextFightMove += 2; // Makes it 6 or 7 + } + } + } else if (!CGame::nastyGame + || fightingPedDist >= 1.3f + || m_pedInObjective->IsPlayer() + || m_pedInObjective->m_nPedState != PED_DEAD && m_pedInObjective->IsPedHeadAbovePos(-0.3f)) { + nextFightMove = FIGHTMOVE_IDLE; + } else { + nextFightMove = FIGHTMOVE_GROUNDKICK; + } + } + } + + if (canAffectMultiplePeople) { + if (kickGround && IsPlayer()) { + m_fRotationDest = angleForGroundKick; + nextFightMove = FIGHTMOVE_GROUNDKICK; + m_fRotationCur = m_fRotationDest; + m_lookTimer = 0; + SetLookFlag(pedOnGround, true); + SetLookTimer(1500); + } else if (goForward) { + nextFightMove = FIGHTMOVE_SHUFFLE_F; + } else { + nextFightMove = FIGHTMOVE_STDPUNCH; + } + } + + if (nextFightMove != FIGHTMOVE_IDLE) { + m_curFightMove = nextFightMove; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 4.0f); + + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + if (m_fightState == FIGHTSTATE_MOVE_FINISHED && animAssoc->currentTime != 0.0f) { + animAssoc->SetCurrentTime(0.0f); + animAssoc->SetRun(); + } + m_fightButtonPressure = 0; + } + m_fightState = FIGHTSTATE_NO_MOVE; + } else if (m_takeAStepAfterAttack && m_curFightMove != FIGHTMOVE_SHUFFLE_F +#ifndef FIX_BUGS + && CheckForPedsOnGroundToAttack(this, nil) == 4) { +#else + && CheckForPedsOnGroundToAttack(this, nil) == PED_IN_FRONT_OF_ATTACKER) { +#endif + m_curFightMove = FIGHTMOVE_SHUFFLE_F; + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), tFightMoves[m_curFightMove].animId); + + if (animAssoc) { + animAssoc->SetCurrentTime(0.0f); + animAssoc->blendDelta = 4.0f; + animAssoc->SetRun(); + } else { + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 32.0f); + } + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + m_fightState = FIGHTSTATE_NO_MOVE; + m_fightButtonPressure = 0; + m_takeAStepAfterAttack = false; + + } else if (m_takeAStepAfterAttack) { + EndFight(ENDFIGHT_FAST); + + } else if (m_curFightMove == FIGHTMOVE_IDLE) { + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + EndFight(ENDFIGHT_NORMAL); + } + + } else { + m_curFightMove = FIGHTMOVE_IDLE; + if (IsPlayer()) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 500; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2000; + } +} + +void +CPed::EndFight(uint8 endType) +{ + if (m_nPedState != PED_FIGHT) + return; + + m_curFightMove = FIGHTMOVE_NULL; + RestorePreviousState(); + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + if (animAssoc) + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + + switch (endType) { + case ENDFIGHT_NORMAL: + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 8.0f); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_2IDLE, 8.0f); + break; + case ENDFIGHT_WITH_A_STEP: + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 1.0f); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_STARTWALK, 8.0f); + break; + case ENDFIGHT_FAST: + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 8.0f); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_2IDLE, 8.0f)->speed = 2.0f; + break; + default: + break; + } + m_nWaitTimer = 0; +} + + +void +CPed::PlayHitSound(CPed *hitTo) +{ + // That was very complicated to reverse for me... + // First index is our fight move ID (from 1 to 12, total 12), second is the one of we fight with (from 13 to 22, total 10). + enum { + S33 = SOUND_FIGHT_PUNCH_33, + S34 = SOUND_FIGHT_KICK_34, + S35 = SOUND_FIGHT_HEADBUTT_35, + S36 = SOUND_FIGHT_PUNCH_36, + S37 = SOUND_FIGHT_PUNCH_37, + S38 = SOUND_FIGHT_CLOSE_PUNCH_38, + S39 = SOUND_FIGHT_PUNCH_39, + S40 = SOUND_FIGHT_PUNCH_OR_KICK_BELOW_40 , + S41 = SOUND_FIGHT_PUNCH_41, + S42 = SOUND_FIGHT_PUNCH_FROM_BEHIND_42, + S43 = SOUND_FIGHT_KNEE_OR_KICK_43, + S44 = SOUND_FIGHT_KICK_44, + NO_SND = SOUND_NO_SOUND + }; + uint16 hitSoundsByFightMoves[12][10] = { + {S39,S42,S43,S43,S39,S39,S39,S39,S39,S42}, + {NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND}, + {NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND,NO_SND}, + {S39,S39,S39,S39,S33,S43,S39,S39,S39,S39}, + {S39,S39,S39,S39,S35,S39,S38,S38,S39,S39}, + {S39,S39,S39,S39,S33,S39,S41,S36,S39,S39}, + {S39,S39,S39,S39,S37,S40,S38,S38,S39,S39}, + {S39,S39,S39,S39,S34,S43,S44,S37,S39,S39}, + {S39,S39,S39,S39,S34,S43,S44,S37,S39,S39}, + {S39,S39,S39,S39,S34,S43,S44,S37,S39,S40}, + {S39,S39,S39,S39,S33,S39,S41,S37,S39,S40}, + {S39,S39,S39,S39,S39,S39,S39,S39,S33,S33} + }; + + // This is why first dimension is between FightMove 1 and 12. + if (m_curFightMove == FIGHTMOVE_NULL || m_curFightMove >= FIGHTMOVE_HITFRONT) + return; + + uint16 soundId; + + // And this is why second dimension is between 13 and 22. + if (hitTo->m_curFightMove > FIGHTMOVE_GROUNDKICK && hitTo->m_curFightMove < FIGHTMOVE_IDLE2NORM) { + soundId = hitSoundsByFightMoves[m_curFightMove - FIGHTMOVE_STDPUNCH][hitTo->m_curFightMove - FIGHTMOVE_HITFRONT]; + + } else { + if (hitTo->m_nPedState == PED_DEAD || hitTo->UseGroundColModel()) { + soundId = hitSoundsByFightMoves[m_curFightMove - FIGHTMOVE_STDPUNCH][FIGHTMOVE_HITONFLOOR - FIGHTMOVE_HITFRONT]; + } else { + soundId = hitSoundsByFightMoves[m_curFightMove - FIGHTMOVE_STDPUNCH][FIGHTMOVE_HITFRONT - FIGHTMOVE_HITFRONT]; + } + } + + if (soundId != NO_SND) + DMAudio.PlayOneShot(m_audioEntityId, soundId, 0.0f); +} + +bool +CPed::FightStrike(CVector &touchedNodePos) +{ + CColModel *ourCol; + CVector attackDistance; + ePedPieceTypes closestPedPiece = PEDPIECE_TORSO; + float maxDistanceToBeBeaten; + CPed *nearPed; + int state = m_fightState; + bool pedFound = false; + + if (state == FIGHTSTATE_JUST_ATTACKED) + return false; + + // Pointless code + if (state > FIGHTSTATE_NO_MOVE) + attackDistance = touchedNodePos - m_vecHitLastPos; + + for (int i = 0; i < m_numNearPeds; i++) { + nearPed = m_nearPeds[i]; + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) + maxDistanceToBeBeaten = nearPed->GetBoundRadius() + tFightMoves[m_curFightMove].strikeRadius + 0.1f; + else + maxDistanceToBeBeaten = nearPed->GetBoundRadius() + tFightMoves[m_curFightMove].strikeRadius; + + if (nearPed->bUsesCollision || nearPed->m_nPedState == PED_DEAD) { + CVector nearPedCentre; + nearPed->GetBoundCentre(nearPedCentre); + CVector potentialAttackDistance = nearPedCentre - touchedNodePos; + + // He can beat us + if (sq(maxDistanceToBeBeaten) > potentialAttackDistance.MagnitudeSqr()) { + +#ifdef PED_SKIN + // Have to animate a skinned clump because the initial col model is useless + if(IsClumpSkinned(GetClump())) + ourCol = ((CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()))->AnimatePedColModelSkinned(GetClump()); + else +#endif + if (nearPed->OnGround() || !nearPed->IsPedHeadAbovePos(-0.3f)) { + ourCol = &CTempColModels::ms_colModelPedGroundHit; + } else { +#ifdef ANIMATE_PED_COL_MODEL + ourCol = CPedModelInfo::AnimatePedColModel(((CPedModelInfo *)CModelInfo::GetModelInfo(GetModelIndex()))->GetHitColModel(), + RpClumpGetFrame(GetClump())); +#else + ourCol = ((CPedModelInfo*)CModelInfo::GetModelInfo(m_modelIndex))->GetHitColModel(); +#endif + } + + for (int j = 0; j < ourCol->numSpheres; j++) { + attackDistance = nearPed->GetPosition() + ourCol->spheres[j].center; + attackDistance -= touchedNodePos; + CColSphere *ourPieces = ourCol->spheres; + float maxDistanceToBeat = ourPieces[j].radius + tFightMoves[m_curFightMove].strikeRadius; + + // We can beat him too + if (sq(maxDistanceToBeat) > attackDistance.MagnitudeSqr()) { + pedFound = true; + closestPedPiece = (ePedPieceTypes) ourPieces[j].piece; + break; + } + } + } + } + if (pedFound) + break; + } + + if (pedFound) { + if (nearPed->IsPlayer() && nearPed->m_nPedState == PED_GETUP) + return false; + + float oldVictimHealth = nearPed->m_fHealth; + CVector bloodPos = 0.5f * attackDistance + touchedNodePos; + int damageMult = tFightMoves[m_curFightMove].damage * ((CGeneral::GetRandomNumber() & 1) + 2) + 1; + + CVector2D diff (GetPosition() - nearPed->GetPosition()); + int direction = nearPed->GetLocalDirection(diff); + if (IsPlayer()) { + if (((CPlayerPed*)this)->m_bAdrenalineActive) + damageMult = 20; + } else { + damageMult *= m_pedStats->m_attackStrength; + } + + // Change direction if we used kick. + if (m_curFightMove == FIGHTMOVE_KICK) { + if (CGeneral::GetRandomNumber() & 1) { + direction++; + if (direction > 3) + direction -= 4; + } + } + nearPed->ReactToAttack(this); + + // Mostly unused. if > 5, ANIM_HIT_WALK will be run, that's it. + int unk2; + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && !nearPed->IsPlayer()) + unk2 = 101; + else + unk2 = damageMult; + + nearPed->StartFightDefend(direction, tFightMoves[m_curFightMove].hitLevel, unk2); + PlayHitSound(nearPed); + m_fightState = FIGHTSTATE_JUST_ATTACKED; + RpAnimBlendClumpGetAssociation(GetClump(), tFightMoves[m_curFightMove].animId)->speed = 0.6f; + if (!nearPed->DyingOrDead()) { + nearPed->InflictDamage(this, WEAPONTYPE_UNARMED, damageMult * 3.0f, closestPedPiece, direction); + } + + if (CGame::nastyGame + && tFightMoves[m_curFightMove].hitLevel > HITLEVEL_MEDIUM + && nearPed->m_nPedState == PED_DIE + && nearPed->GetIsOnScreen()) { + + // Just for blood particle. We will restore it below. + attackDistance /= (10.0f * attackDistance.Magnitude()); + for(int i=0; i<4; i++) { + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, attackDistance, nil, 0.0f, 0, 0, 0, 0); + } + } + if (!nearPed->OnGround()) { + float curVictimHealth = nearPed->m_fHealth; + if (curVictimHealth > 0.0f + && (curVictimHealth < 40.0f && oldVictimHealth > 40.0f && !nearPed->IsPlayer() + || nearPed->m_fHealth < 20.0f && oldVictimHealth > 20.0f + || GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && IsPlayer() + || nearPed->m_pedStats->m_flags & STAT_ONE_HIT_KNOCKDOWN)) { + + nearPed->SetFall(0, (AnimationId)(direction + ANIM_STD_HIGHIMPACT_FRONT), 0); + if (nearPed->m_nPedState == PED_FALL) + nearPed->bIsStanding = false; + } + } + if (nearPed->m_nPedState == PED_DIE || !nearPed->bIsStanding) { + attackDistance = nearPed->GetPosition() - GetPosition(); + attackDistance.Normalise(); + attackDistance.z = 1.0f; + nearPed->bIsStanding = false; + + float moveMult; + if (m_curFightMove == FIGHTMOVE_GROUNDKICK) { + moveMult = Min(damageMult * 0.6f, 4.0f); + } else { + if (nearPed->m_nPedState != PED_DIE || damageMult >= 20) { + moveMult = damageMult; + } else { + moveMult = Min(damageMult * 2.0f, 14.0f); + } + } + + nearPed->ApplyMoveForce(moveMult * 0.6f * attackDistance); + } + CEventList::RegisterEvent(nearPed->m_nPedType == PEDTYPE_COP ? EVENT_ASSAULT_POLICE : EVENT_ASSAULT, EVENT_ENTITY_PED, nearPed, this, 2000); + } + + if (m_fightState == FIGHTSTATE_NO_MOVE) + m_fightState = FIGHTSTATE_1; + + m_vecHitLastPos = touchedNodePos; + return false; +} + +void +CPed::FinishFightMoveCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (tFightMoves[ped->m_curFightMove].animId == animAssoc->animId) { + ped->m_fightState = FIGHTSTATE_MOVE_FINISHED; + animAssoc->blendDelta = -1000.0f; + } +} + +void +CPed::LoadFightData(void) +{ + float startFireTime, endFireTime, comboFollowOnTime, strikeRadius; + int damage, flags; + char line[256], moveName[32], animName[32], hitLevel; + int moveId = 0; + + CAnimBlendAssociation *animAssoc; + + size_t bp, buflen; + int lp, linelen; + + buflen = CFileMgr::LoadFile("DATA\\fistfite.dat", work_buff, sizeof(work_buff), "r"); + + for (bp = 0; bp < buflen; ) { + // read file line by line + for (linelen = 0; work_buff[bp] != '\n' && bp < buflen; bp++) { + line[linelen++] = work_buff[bp]; + } + bp++; + line[linelen] = '\0'; + + // skip white space + for (lp = 0; line[lp] <= ' ' && line[lp] != '\0'; lp++); + + if (line[lp] == '\0' || + line[lp] == '#') + continue; + + sscanf( + &line[lp], + "%s %f %f %f %f %c %s %d %d", + moveName, + &startFireTime, + &endFireTime, + &comboFollowOnTime, + &strikeRadius, + &hitLevel, + animName, + &damage, + &flags); + + if (strncmp(moveName, "ENDWEAPONDATA", 13) == 0) + return; + + tFightMoves[moveId].startFireTime = startFireTime / 30.0f; + tFightMoves[moveId].endFireTime = endFireTime / 30.0f; + tFightMoves[moveId].comboFollowOnTime = comboFollowOnTime / 30.0f; + tFightMoves[moveId].strikeRadius = strikeRadius; + tFightMoves[moveId].damage = damage; + tFightMoves[moveId].flags = flags; + + switch (hitLevel) { + case 'G': + tFightMoves[moveId].hitLevel = HITLEVEL_GROUND; + break; + case 'H': + tFightMoves[moveId].hitLevel = HITLEVEL_HIGH; + break; + case 'L': + tFightMoves[moveId].hitLevel = HITLEVEL_LOW; + break; + case 'M': + tFightMoves[moveId].hitLevel = HITLEVEL_MEDIUM; + break; + case 'N': + tFightMoves[moveId].hitLevel = HITLEVEL_NULL; + break; + default: + break; + } + + if (strcmp(animName, "null") != 0) { + animAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, animName); + tFightMoves[moveId].animId = (AnimationId)animAssoc->animId; + } else { + tFightMoves[moveId].animId = ANIM_STD_WALK; + } + moveId++; + } +} + +void +CPed::SetInvestigateEvent(eEventType event, CVector2D pos, float distanceToCountDone, uint16 time, float angle) +{ + if (!IsPedInControl() || CharCreatedBy == MISSION_CHAR) + return; + + SetStoredState(); + bFindNewNodeAfterStateRestore = false; + SetPedState(PED_INVESTIGATE); + m_chatTimer = CTimer::GetTimeInMilliseconds() + time; + m_eventType = event; + m_eventOrThreat = pos; + m_distanceToCountSeekDone = distanceToCountDone; + m_fAngleToEvent = angle; + + if (m_eventType >= EVENT_ICECREAM) + m_lookTimer = 0; + else + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSCOWER, 4.0f); + +} + +void +CPed::InvestigateEvent(void) +{ + CAnimBlendAssociation *animAssoc; + AnimationId animToPlay; + AssocGroupId animGroup; + + if (m_nWaitState == WAITSTATE_TURN180) + return; + + if (CTimer::GetTimeInMilliseconds() > m_chatTimer) { + + if (m_chatTimer) { + if (m_eventType < EVENT_ASSAULT_NASTYWEAPON) + SetWaitState(WAITSTATE_TURN180, nil); + + m_chatTimer = 0; + } else { + ClearInvestigateEvent(); + } + return; + } + + CVector2D vecDist = m_eventOrThreat - GetPosition(); + float distSqr = vecDist.MagnitudeSqr(); + if (sq(m_distanceToCountSeekDone) >= distSqr) { + + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(vecDist.x, vecDist.y, 0.0f, 0.0f); + SetMoveState(PEDMOVE_STILL); + + switch (m_eventType) { + case EVENT_DEAD_PED: + case EVENT_HIT_AND_RUN: + case EVENT_HIT_AND_RUN_COP: + + if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (m_pEventEntity) + SetLookFlag(m_pEventEntity, true); + + SetLookTimer(CGeneral::GetRandomNumberInRange(1500, 4000)); + + } else if (CGeneral::GetRandomNumber() & 3) { + ClearLookFlag(); + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROADCROSS, 4.0f); + + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + Say(SOUND_PED_CHAT_EVENT); + + } else { + ClearInvestigateEvent(); + } + } + break; + case EVENT_FIRE: + case EVENT_EXPLOSION: + + if (bHasACamera && CTimer::GetTimeInMilliseconds() > m_lookTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_CAM); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + + if (animAssoc && animAssoc->animId == ANIM_STD_IDLE_CAM) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + + } else if (CGeneral::GetRandomNumber() & 3) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_CAM, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(2500, 5000)); + Say(SOUND_PED_CHAT_EVENT); + + } else { + m_chatTimer = 0; + } + + } else if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_HBHB); + + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_XPRESS_SCRATCH); + + if (animAssoc && animAssoc->animId == ANIM_STD_IDLE) { + if (CGeneral::GetRandomNumber() & 1) + animToPlay = ANIM_STD_IDLE_HBHB; + else + animToPlay = ANIM_STD_XPRESS_SCRATCH; + + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animToPlay, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1500, 4000)); + + } else if (animAssoc && animAssoc->animId == ANIM_STD_IDLE_HBHB) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (CGeneral::GetRandomNumber() & 1) { + animToPlay = ANIM_STD_IDLE; + animGroup = m_animGroup; + } else { + animToPlay = ANIM_STD_XPRESS_SCRATCH; + animGroup = ASSOCGRP_STD; + } + + CAnimManager::BlendAnimation(GetClump(), animGroup, animToPlay, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + + } else { + if (CGeneral::GetRandomNumber() & 1) { + animToPlay = ANIM_STD_IDLE; + animGroup = m_animGroup; + } else { + animToPlay = ANIM_STD_IDLE_HBHB; + animGroup = ASSOCGRP_STD; + } + + CAnimManager::BlendAnimation(GetClump(), animGroup, animToPlay, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + } + Say(SOUND_PED_CHAT_EVENT); + } + break; + case EVENT_ICECREAM: + case EVENT_SHOPSTALL: + + m_fRotationDest = m_fAngleToEvent; + if (CTimer::GetTimeInMilliseconds() > m_lookTimer) { + + if (m_lookTimer) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (m_eventType == EVENT_ICECREAM) + animToPlay = ANIM_STD_CHAT; + else + animToPlay = ANIM_STD_XPRESS_SCRATCH; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, animToPlay,4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(2000, 5000)); + + } else { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CHAT); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + ClearInvestigateEvent(); + } else { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_XPRESS_SCRATCH); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + ClearInvestigateEvent(); + } + } + } else { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROADCROSS, 4.0f); + SetLookTimer(CGeneral::GetRandomNumberInRange(1000, 2500)); + } + } + break; + default: + return; + } + } else { + m_vecSeekPos.x = m_eventOrThreat.x; + m_vecSeekPos.y = m_eventOrThreat.y; + m_vecSeekPos.z = GetPosition().z; + Seek(); + + if (m_eventType < EVENT_ICECREAM) { + if (sq(5.0f + m_distanceToCountSeekDone) < distSqr) { + SetMoveState(PEDMOVE_RUN); + return; + } + } + if (m_eventType <= EVENT_EXPLOSION || m_eventType >= EVENT_SHOPSTALL) { + SetMoveState(PEDMOVE_WALK); + return; + } + if (distSqr > sq(1.2f)) { + SetMoveState(PEDMOVE_WALK); + return; + } + + for (int i = 0; i < m_numNearPeds; i++) { + if ((m_eventOrThreat - m_nearPeds[i]->GetPosition()).MagnitudeSqr() < sq(0.4f)) { + SetMoveState(PEDMOVE_STILL); + return; + } + } + + SetMoveState(PEDMOVE_WALK); + } +} + +void +CPed::ClearInvestigateEvent(void) +{ + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROADCROSS); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_XPRESS_SCRATCH); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_HBHB); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CHAT); + if (animAssoc) { + animAssoc->blendDelta = -8.0f; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + if (m_eventType > EVENT_EXPLOSION) + m_chatTimer = CTimer::GetTimeInMilliseconds() + 15000; + + bGonnaInvestigateEvent = false; + m_pEventEntity = nil; + ClearLookFlag(); + RestorePreviousState(); + if(m_nMoveState == PEDMOVE_NONE || m_nMoveState == PEDMOVE_STILL) + SetMoveState(PEDMOVE_WALK); +} + +bool +CPed::InflictDamage(CEntity *damagedBy, eWeaponType method, float damage, ePedPieceTypes pedPiece, uint8 direction) +{ + CPlayerPed *player = FindPlayerPed(); + float dieDelta = 4.0f; + float dieSpeed = 0.0f; + AnimationId dieAnim = ANIM_STD_KO_FRONT; + bool headShot = false; + bool willLinger = false; + int random; + + if (player == this) { + if (!player->m_bCanBeDamaged) + return false; + + player->AnnoyPlayerPed(false); + } + + if (DyingOrDead()) + return false; + + if (!bUsesCollision && method != WEAPONTYPE_DROWNING) + return false; + + if (bOnlyDamagedByPlayer && damagedBy != player && damagedBy != FindPlayerVehicle() && + method != WEAPONTYPE_DROWNING && method != WEAPONTYPE_EXPLOSION) + return false; + + float healthImpact; + if (IsPlayer()) + healthImpact = damage * 0.33f; + else + healthImpact = damage * m_pedStats->m_defendWeakness; + + bool detectDieAnim = true; + if (m_nPedState == PED_FALL || m_nPedState == PED_GETUP) { + if (!IsPedHeadAbovePos(-0.3f)) { + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) + dieAnim = ANIM_STD_HIT_FLOOR_FRONT; + else + dieAnim = ANIM_STD_HIT_FLOOR; + dieDelta *= 2.0f; + dieSpeed = 0.5f; + detectDieAnim = false; + } else if (m_nPedState == PED_FALL) { + dieAnim = ANIM_STD_NUM; + detectDieAnim = false; + } + } + if (detectDieAnim) { + switch (method) { + case WEAPONTYPE_UNARMED: + if (bMeleeProof) + return false; + + if (m_nPedState == PED_FALL) { + if (IsPedHeadAbovePos(-0.3f)) { + dieAnim = ANIM_STD_NUM; + } else { + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) + dieAnim = ANIM_STD_HIT_FLOOR_FRONT; + else + dieAnim = ANIM_STD_HIT_FLOOR; + dieDelta = dieDelta * 2.0f; + dieSpeed = 0.5f; + } + } else { + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + } + break; + case WEAPONTYPE_BASEBALLBAT: + if (bMeleeProof) + return false; + + if (m_nPedState == PED_FALL) { + if (IsPedHeadAbovePos(-0.3f)) { + dieAnim = ANIM_STD_NUM; + } else { + if (RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_FRONTAL)) + dieAnim = ANIM_STD_HIT_FLOOR_FRONT; + else + dieAnim = ANIM_STD_HIT_FLOOR; + dieDelta = dieDelta * 2.0f; + dieSpeed = 0.5f; + } + } else { + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + } + break; + case WEAPONTYPE_COLT45: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_AK47: + case WEAPONTYPE_M16: + case WEAPONTYPE_SNIPERRIFLE: + if (bBulletProof) + return false; + + bool dontRemoveLimb; + if (IsPlayer() || bNoCriticalHits) + dontRemoveLimb = true; + else { + switch (method) { + case WEAPONTYPE_SNIPERRIFLE: + dontRemoveLimb = false; + break; + case WEAPONTYPE_M16: + dontRemoveLimb = false; + break; + case WEAPONTYPE_SHOTGUN: + dontRemoveLimb = CGeneral::GetRandomNumber() & 7; + break; + default: + dontRemoveLimb = CGeneral::GetRandomNumber() & 15; + break; + } + } + + if (dontRemoveLimb) { + if (method == WEAPONTYPE_SHOTGUN) { + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + } else + dieAnim = ANIM_STD_KO_FRONT; + + willLinger = false; + } else { + switch (pedPiece) { + case PEDPIECE_TORSO: + willLinger = false; + dieAnim = ANIM_STD_KO_FRONT; + break; + case PEDPIECE_MID: + willLinger = false; + dieAnim = ANIM_STD_KO_SHOT_STOMACH; + break; + case PEDPIECE_LEFTARM: + dieAnim = ANIM_STD_KO_SHOT_ARM_L; + RemoveBodyPart(PED_UPPERARML, direction); + willLinger = true; + break; + case PEDPIECE_RIGHTARM: + dieAnim = ANIM_STD_KO_SHOT_ARM_R; + RemoveBodyPart(PED_UPPERARMR, direction); + willLinger = true; + break; + case PEDPIECE_LEFTLEG: + dieAnim = ANIM_STD_KO_SHOT_LEG_L; + RemoveBodyPart(PED_UPPERLEGL, direction); + willLinger = true; + break; + case PEDPIECE_RIGHTLEG: + dieAnim = ANIM_STD_KO_SHOT_LEG_R; + RemoveBodyPart(PED_UPPERLEGR, direction); + willLinger = true; + break; + case PEDPIECE_HEAD: + dieAnim = ANIM_STD_KO_SHOT_FACE; + RemoveBodyPart(PED_HEAD, direction); + headShot = true; + willLinger = true; + break; + default: + break; + } + } + break; + case WEAPONTYPE_ROCKETLAUNCHER: + case WEAPONTYPE_GRENADE: + case WEAPONTYPE_EXPLOSION: + if (bExplosionProof) + return false; + + if (CGame::nastyGame && !IsPlayer() && !bInVehicle && + 1.0f + healthImpact > m_fArmour + m_fHealth) { + + random = CGeneral::GetRandomNumber(); + if (random & 1) + RemoveBodyPart(PED_UPPERARML, direction); + if (random & 2) + RemoveBodyPart(PED_UPPERLEGR, direction); + if (random & 4) + RemoveBodyPart(PED_HEAD, direction); + if (random & 8) + RemoveBodyPart(PED_UPPERARMR, direction); + if (random & 0x10) + RemoveBodyPart(PED_UPPERLEGL, direction); + if (bBodyPartJustCameOff) + willLinger = true; + } + // fall through + case WEAPONTYPE_MOLOTOV: + if (bExplosionProof) + return false; + + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + break; + case WEAPONTYPE_FLAMETHROWER: + if (bFireProof) + return false; + + dieAnim = ANIM_STD_KO_FRONT; + break; + case WEAPONTYPE_RAMMEDBYCAR: + case WEAPONTYPE_RUNOVERBYCAR: + if (bCollisionProof) + return false; + + random = CGeneral::GetRandomNumber() & 3; + switch (random) { + case 0: + if ((pedPiece != PEDPIECE_LEFTARM || random <= 1) + && (pedPiece != PEDPIECE_MID || random != 1)) { + if (pedPiece == PEDPIECE_RIGHTARM && random > 1 + || pedPiece == PEDPIECE_MID && random == 2) + + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + else + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + } else + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + + break; + case 1: + if (m_nPedState == PED_DIVE_AWAY) + dieAnim = ANIM_STD_SPINFORWARD_LEFT; + else + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + if ((pedPiece != PEDPIECE_LEFTARM || random <= 1) + && (pedPiece != PEDPIECE_MID || random != 1)) { + if ((pedPiece != PEDPIECE_RIGHTARM || random <= 1) + && (pedPiece != PEDPIECE_MID || random != 2)) { + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + } else { + dieAnim = ANIM_STD_SPINFORWARD_RIGHT; + } + } else + dieAnim = ANIM_STD_SPINFORWARD_LEFT; + break; + case 3: + if (m_nPedState == PED_DIVE_AWAY) + dieAnim = ANIM_STD_SPINFORWARD_RIGHT; + else + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + if (damagedBy) { + CVehicle *vehicle = (CVehicle*)damagedBy; + if (method == WEAPONTYPE_RAMMEDBYCAR) { + float vehSpeed = vehicle->m_vecMoveSpeed.Magnitude(); + dieDelta = 8.0f * vehSpeed + 4.0f; + } else { + float vehSpeed = vehicle->m_vecMoveSpeed.Magnitude(); + dieDelta = 12.0f * vehSpeed + 4.0f; + dieSpeed = 16.0f * vehSpeed + 1.0f; + } + } + break; + case WEAPONTYPE_DROWNING: + dieAnim = ANIM_STD_DROWN; + break; + case WEAPONTYPE_FALL: + if (bCollisionProof) + return false; + + switch (direction) { + case 0: + dieAnim = ANIM_STD_HIGHIMPACT_FRONT; + break; + case 1: + dieAnim = ANIM_STD_HIGHIMPACT_LEFT; + break; + case 2: + dieAnim = ANIM_STD_HIGHIMPACT_BACK; + break; + case 3: + dieAnim = ANIM_STD_HIGHIMPACT_RIGHT; + break; + default: + break; + } + break; + default: + break; + } + } + + if (m_fArmour != 0.0f && method != WEAPONTYPE_DROWNING) { + if (player == this) + CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss = CTimer::GetTimeInMilliseconds(); + + if (healthImpact < m_fArmour) { + m_fArmour = m_fArmour - healthImpact; + healthImpact = 0.0f; + } else { + healthImpact = healthImpact - m_fArmour; + m_fArmour = 0.0f; + } + } + + if (healthImpact != 0.0f) { + if (player == this) + CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss = CTimer::GetTimeInMilliseconds(); + + m_lastWepDam = method; + } + + if (m_fHealth - healthImpact >= 1.0f && !willLinger) { + m_fHealth -= healthImpact; + return false; + } + + if (bInVehicle) { + if (method != WEAPONTYPE_DROWNING) { +#ifdef VC_PED_PORTS + if (m_pMyVehicle) { + if (m_pMyVehicle->IsCar() && m_pMyVehicle->pDriver == this) { + if (m_pMyVehicle->GetStatus() == STATUS_SIMPLE) { + m_pMyVehicle->SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(m_pMyVehicle); + } + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 0; + m_pMyVehicle->AutoPilot.m_nTempAction = TEMPACT_HANDBRAKESTRAIGHT; + m_pMyVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + } + if (m_pMyVehicle->CanPedExitCar()) { + SetObjective(OBJECTIVE_LEAVE_CAR_AND_DIE, m_pMyVehicle); + } else { + m_fHealth = 0.0f; + if (m_pMyVehicle && m_pMyVehicle->pDriver == this) { + SetRadioStation(); + m_pMyVehicle->SetStatus(STATUS_ABANDONED); + } + SetDie(dieAnim, dieDelta, dieSpeed); + /* + if (damagedBy == FindPlayerPed() && damagedBy != this) { + // PlayerInfo stuff + } + */ + } + for (int i = 0; i < ARRAY_SIZE(m_pMyVehicle->pPassengers); i++) { + CPed* passenger = m_pMyVehicle->pPassengers[i]; + if (passenger && passenger != this && damagedBy) + passenger->ReactToAttack(damagedBy); + } + + CPed *driverOfVeh = m_pMyVehicle->pDriver; + if (driverOfVeh && driverOfVeh != this && damagedBy) + driverOfVeh->ReactToAttack(damagedBy); + + if (damagedBy == FindPlayerPed() || damagedBy && damagedBy == FindPlayerVehicle()) { + CDarkel::RegisterKillByPlayer(this, method, headShot); + m_threatEntity = FindPlayerPed(); + } else { + CDarkel::RegisterKillNotByPlayer(this, method); + } + } +#endif + m_fHealth = 1.0f; + return false; + } + m_fHealth = 0.0f; + if (player == this) + m_pMyVehicle->SetStatus(STATUS_PLAYER_DISABLED); + + SetDie(ANIM_STD_NUM, 4.0f, 0.0f); + return true; + } else { + m_fHealth = 0.0f; + SetDie(dieAnim, dieDelta, dieSpeed); + + if (damagedBy == player || damagedBy && damagedBy == FindPlayerVehicle()) { + + // There are PlayerInfo stuff here in VC + CDarkel::RegisterKillByPlayer(this, method, headShot); + m_threatEntity = player; + } else { + CDarkel::RegisterKillNotByPlayer(this, method); + } + if (method == WEAPONTYPE_DROWNING) + bIsInTheAir = false; + + return true; + } +} + +static RwObject* +SetPedAtomicVisibilityCB(RwObject* object, void* data) +{ + if (data == nil) + RpAtomicSetFlags((RpAtomic*)object, 0); + return object; +} + +static RwFrame* +RecurseFrameChildrenVisibilityCB(RwFrame* frame, void* data) +{ + RwFrameForAllObjects(frame, SetPedAtomicVisibilityCB, data); + RwFrameForAllChildren(frame, RecurseFrameChildrenVisibilityCB, nil); + return frame; +} + +static RwObject* +CloneAtomicToFrameCB(RwObject *frame, void *data) +{ + RpAtomic *newAtomic = RpAtomicClone((RpAtomic*)frame); + RpAtomicSetFrame(newAtomic, (RwFrame*)data); + RpClumpAddAtomic(flyingClumpTemp, newAtomic); + CVisibilityPlugins::SetAtomicRenderCallback(newAtomic, nil); + return frame; +} + +static RwFrame* +RecurseFrameChildrenToCloneCB(RwFrame *frame, void *data) +{ + RwFrame *newFrame = RwFrameCreate(); + RwFrameAddChild((RwFrame*)data, newFrame); + RwFrameTransform(newFrame, RwFrameGetMatrix(frame), rwCOMBINEREPLACE); + RwFrameForAllObjects(frame, CloneAtomicToFrameCB, newFrame); + RwFrameForAllChildren(frame, RecurseFrameChildrenToCloneCB, newFrame); + return newFrame; +} + +void +CPed::RemoveBodyPart(PedNode nodeId, int8 direction) +{ + RwFrame *frame; + CVector pos; + + frame = m_pFrames[nodeId]->frame; + if (frame) { + if (CGame::nastyGame) { +#ifdef PED_SKIN + if(!IsClumpSkinned(GetClump())) +#endif + { +#ifdef DEBUGMENU + if (bPopHeadsOnHeadshot || nodeId != PED_HEAD) +#else + if (nodeId != PED_HEAD) +#endif + SpawnFlyingComponent(nodeId, direction); + + RecurseFrameChildrenVisibilityCB(frame, nil); + } + pos.x = 0.0f; + pos.y = 0.0f; + pos.z = 0.0f; + TransformToNode(pos, PED_HEAD); + + if (CEntity::GetIsOnScreen()) { + CParticle::AddParticle(PARTICLE_TEST, pos, + CVector(0.0f, 0.0f, 0.0f), + nil, 0.1f, 0, 0, 0, 0); + + for (int i = 0; i < 16; i++) { + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, + pos, + CVector(0.0f, 0.0f, 0.03f), + nil, 0.0f, 0, 0, 0, 0); + } + } + bBodyPartJustCameOff = true; + m_bodyPartBleeding = nodeId; + } + } else { + printf("Trying to remove ped component"); + } +} + +CObject* +CPed::SpawnFlyingComponent(int pedNode, int8 direction) +{ + if (CObject::nNoTempObjects >= NUMTEMPOBJECTS) + return nil; + +#ifdef PED_SKIN + assert(!IsClumpSkinned(GetClump())); +#endif + + CObject *obj = new CObject(); + if (!obj) + return nil; + + RwFrame *frame = RwFrameCreate(); + RpClump *clump = RpClumpCreate(); + RpClumpSetFrame(clump, frame); + RwMatrix *matrix = RwFrameGetLTM(m_pFrames[pedNode]->frame); + *RwFrameGetMatrix(frame) = *matrix; + + flyingClumpTemp = clump; + RwFrameForAllObjects(m_pFrames[pedNode]->frame, CloneAtomicToFrameCB, frame); + RwFrameForAllChildren(m_pFrames[pedNode]->frame, RecurseFrameChildrenToCloneCB, frame); + flyingClumpTemp = nil; + switch (pedNode) { + case PED_HEAD: + // So popping head would have wheel collision. They disabled it anyway + obj->SetModelIndexNoCreate(MI_CAR_WHEEL); + break; + case PED_UPPERARML: + case PED_UPPERARMR: + obj->SetModelIndexNoCreate(MI_BODYPARTB); + obj->SetCenterOfMass(0.25f, 0.0f, 0.0f); + break; + case PED_UPPERLEGL: + case PED_UPPERLEGR: + obj->SetModelIndexNoCreate(MI_BODYPARTA); + obj->SetCenterOfMass(0.4f, 0.0f, 0.0f); + break; + default: + break; + } + obj->RefModelInfo(GetModelIndex()); + obj->AttachToRwObject((RwObject*)clump); + obj->m_fMass = 15.0f; + obj->m_fTurnMass = 5.0f; + obj->m_fAirResistance = 0.99f; + obj->m_fElasticity = 0.03f; + obj->m_fBuoyancy = m_fMass*GRAVITY/0.75f; + obj->ObjectCreatedBy = TEMP_OBJECT; + obj->SetIsStatic(false); + obj->bIsPickup = false; + obj->m_nSpecialCollisionResponseCases = COLLRESPONSE_SMALLBOX; + + // life time - the more objects the are, the shorter this one will live + CObject::nNoTempObjects++; + if (CObject::nNoTempObjects > 20) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 12000; + else if (CObject::nNoTempObjects > 10) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 30000; + else + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 60000; + + CVector localForcePos, forceDir; + + if (direction == 2) { + obj->m_vecMoveSpeed = 0.03f * GetForward(); + obj->m_vecMoveSpeed.z = (CGeneral::GetRandomNumber() & 0x3F) * 0.001f; + obj->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + localForcePos = CVector(0.0f, 0.0f, 0.0f); + forceDir = GetForward(); + } else { + obj->m_vecMoveSpeed = -0.03f * GetForward(); + obj->m_vecMoveSpeed.z = (CGeneral::GetRandomNumber() & 0x3F) * 0.001f; + obj->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + localForcePos = CVector(0.0f, 0.0f, 0.0f); + forceDir = -GetForward(); + } + obj->ApplyTurnForce(forceDir, localForcePos); + CWorld::Add(obj); + + return obj; +} + +void +CPed::ApplyHeadShot(eWeaponType weaponType, CVector pos, bool evenOnPlayer) +{ + CVector pos2 = CVector( + pos.x, + pos.y, + pos.z + 0.1f + ); + + if (!IsPlayer() || evenOnPlayer) { + ++CStats::HeadsPopped; + + // BUG: This condition will always return true. Even fixing it won't work, because these states are unused. + // if (m_nPedState != PED_PASSENGER || m_nPedState != PED_TAXI_PASSENGER) { + SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + // } + + bBodyPartJustCameOff = true; + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 150; + + CParticle::AddParticle(PARTICLE_TEST, pos2, + CVector(0.0f, 0.0f, 0.0f), nil, 0.2f, 0, 0, 0, 0); + + if (CEntity::GetIsOnScreen()) { + for(int i=0; i < 32; i++) { + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, + pos2, CVector(0.0f, 0.0f, 0.03f), + nil, 0.0f, 0, 0, 0, 0); + } + + for (int i = 0; i < 16; i++) { + CParticle::AddParticle(PARTICLE_DEBRIS2, + pos2, + CVector(0.0f, 0.0f, 0.01f), + nil, 0.0f, 0, 0, 0, 0); + } + } + } +} + +uint8 +CPed::DoesLOSBulletHitPed(CColPoint &colPoint) +{ +#ifdef FIX_BUGS + return 1; +#else + uint8 retVal = 2; + + float headZ = GetNodePosition(PED_HEAD).z; + + if (m_nPedState == PED_FALL) + retVal = 1; + + float colZ = colPoint.point.z; + if (colZ < headZ) + retVal = 1; + + if (headZ + 0.2f <= colZ) + retVal = 0; + + return retVal; +#endif +} + +bool +CPed::IsPedHeadAbovePos(float zOffset) +{ + return zOffset + GetPosition().z < GetNodePosition(PED_HEAD).z; +} + +bool +CPed::PlacePedOnDryLand(void) +{ + float waterLevel = 0.0f; + CEntity *foundEnt = nil; + CColPoint foundCol; + float foundColZ; + + CWaterLevel::GetWaterLevelNoWaves(GetPosition().x, GetPosition().y, GetPosition().z, &waterLevel); + + CVector potentialGround = GetPosition(); + potentialGround.z = waterLevel; + + if (!CWorld::TestSphereAgainstWorld(potentialGround, 5.0f, nil, true, false, false, false, false, false)) + return false; + + CVector potentialGroundDist = gaTempSphereColPoints[0].point - GetPosition(); + potentialGroundDist.z = 0.0f; + potentialGroundDist.Normalise(); + + CVector posToCheck = 0.5f * potentialGroundDist + gaTempSphereColPoints[0].point; + posToCheck.z = 3.0f + waterLevel; + + if (CWorld::ProcessVerticalLine(posToCheck, waterLevel - 1.0f, foundCol, foundEnt, true, true, false, true, false, false, nil)) { + foundColZ = foundCol.point.z; + if (foundColZ >= waterLevel) { + posToCheck.z = 0.8f + foundColZ; + SetPosition(posToCheck); + bIsStanding = true; + bWasStanding = true; + return true; + } + } + + posToCheck = 5.0f * potentialGroundDist + GetPosition(); + posToCheck.z = 3.0f + waterLevel; + + if (!CWorld::ProcessVerticalLine(posToCheck, waterLevel - 1.0f, foundCol, foundEnt, true, true, false, true, false, false, nil)) + return false; + + foundColZ = foundCol.point.z; + if (foundColZ < waterLevel) + return false; + + posToCheck.z = 0.8f + foundColZ; + SetPosition(posToCheck); + bIsStanding = true; + bWasStanding = true; + return true; +} + +void +CPed::CollideWithPed(CPed *collideWith) +{ + CAnimBlendAssociation *animAssoc; + AnimationId animToPlay; + + bool weAreMissionChar = CharCreatedBy == MISSION_CHAR; + bool heIsMissionChar = collideWith->CharCreatedBy == MISSION_CHAR; + CVector posDiff = collideWith->GetPosition() - GetPosition(); + int waitTime = 0; + + if (weAreMissionChar || !collideWith->IsPlayer() || collideWith->m_nPedState != PED_MAKE_CALL) { + bool weDontLookToHim = DotProduct(posDiff, GetForward()) > 0.0f; + bool heLooksToUs = DotProduct(posDiff, collideWith->GetForward()) < 0.0f; + + if (m_nMoveState != PEDMOVE_NONE && m_nMoveState != PEDMOVE_STILL) { + + if ((!IsPlayer() || ((CPlayerPed*)this)->m_fMoveSpeed <= 1.8f) + && (IsPlayer() || heIsMissionChar && weAreMissionChar || m_nMoveState != PEDMOVE_RUN && m_nMoveState != PEDMOVE_SPRINT +#ifdef VC_PED_PORTS + || m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION && m_pedInObjective == collideWith + || collideWith->m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION && collideWith->m_pedInObjective == this +#endif + )) { + + if (m_objective != OBJECTIVE_FOLLOW_CHAR_IN_FORMATION && m_objective != OBJECTIVE_GOTO_CHAR_ON_FOOT) { + + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { + + if (heIsMissionChar || !weAreMissionChar && collideWith->m_nMoveState != PEDMOVE_STILL) { + + if (weAreMissionChar && (m_nPedState == PED_SEEK_POS || m_nPedState == PED_SEEK_ENTITY)) { + + if (collideWith->m_nMoveState != PEDMOVE_STILL + && (!collideWith->IsPlayer() || collideWith->IsPlayer() && CPad::GetPad(0)->ArePlayerControlsDisabled())) { + float seekPosDist = (GetPosition() - m_vecSeekPos).MagnitudeSqr2D(); + float heAndSeekPosDist = (collideWith->GetPosition() - m_vecSeekPos).MagnitudeSqr2D(); + + if (seekPosDist <= heAndSeekPosDist) { + waitTime = 1000; + collideWith->SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &waitTime); + collideWith->m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + waitTime; + } else { + waitTime = 500; + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &waitTime); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + waitTime; + } + } else if (collideWith->m_nMoveState == PEDMOVE_STILL) { + SetDirectionToWalkAroundObject(collideWith); + } + } else { + if (weAreMissionChar || m_pedStats->m_fear <= 100 - collideWith->m_pedStats->m_temper + || (collideWith->IsPlayer() || collideWith->m_nMoveState == PEDMOVE_NONE || collideWith->m_nMoveState == PEDMOVE_STILL) && + (!collideWith->IsPlayer() || ((CPlayerPed*)collideWith)->m_fMoveSpeed <= 1.0f)) { + SetDirectionToWalkAroundObject(collideWith); + if (!weAreMissionChar) + Say(SOUND_PED_CHAT); + } else { + SetEvasiveStep(collideWith, 2); + } + } + } else { + if (m_pedStats->m_temper <= m_pedStats->m_fear + || GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED + || weAreMissionChar + || collideWith->m_nPedType == PEDTYPE_CIVFEMALE + || collideWith->m_nPedType == m_nPedType + || collideWith->GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + SetDirectionToWalkAroundObject(collideWith); + Say(SOUND_PED_CHAT); + } else { + TurnBody(); + SetAttack(collideWith); +#ifdef VC_PED_PORTS + m_fRotationCur = 0.3f + m_fRotationCur; + m_fRotationDest = m_fRotationCur; +#endif + } + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(250, 450); + } + } + } else { +#ifdef VC_PED_PORTS + if (m_pedInObjective && (collideWith == m_pedInObjective || collideWith->m_pedInObjective == m_pedInObjective) && CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { +#else + if (m_pedInObjective && collideWith == m_pedInObjective && CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { +#endif + if (heLooksToUs) { + SetEvasiveStep(collideWith, 1); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 3000; + } + } else if (weDontLookToHim && IsPedInControl()) { + + if (m_pedStats != collideWith->m_pedStats) { + + if (collideWith->m_pedStats->m_fear <= 100 - m_pedStats->m_temper +#ifdef VC_PED_PORTS + || collideWith->IsPlayer() || CTimer::GetTimeInMilliseconds() < m_nPedStateTimer +#endif + ) { + + if (collideWith->IsPlayer()) { + // He's on our right side + if (DotProduct(posDiff,GetRight()) <= 0.0f) + m_fRotationCur -= m_headingRate; + else + m_fRotationCur += m_headingRate; + } else { + // He's on our right side + if (DotProduct(posDiff, collideWith->GetRight()) <= 0.0f) + collideWith->m_fRotationCur -= collideWith->m_headingRate; + else + collideWith->m_fRotationCur += collideWith->m_headingRate; + } + } else { + SetLookFlag(collideWith, false); + TurnBody(); + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PARTIAL_PUNCH, 8.0f); + animAssoc->flags |= ASSOC_FADEOUTWHENDONE; +#ifdef VC_PED_PORTS + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 2000; +#endif + if (!heIsMissionChar) { + CVector2D posDiff2D(posDiff); + int direction = collideWith->GetLocalDirection(posDiff2D); + collideWith->StartFightDefend(direction, HITLEVEL_HIGH, 5); + } + } + } + } + } + } else if (collideWith->m_pedStats->m_defendWeakness <= 1.5f || heIsMissionChar +#ifdef VC_PED_PORTS + || m_pedStats->m_defendWeakness <= collideWith->m_pedStats->m_defendWeakness +#endif + ) { + // He looks us and we're not at his right side + if (heLooksToUs && DotProduct(posDiff,collideWith->GetRight()) > 0.0f) { + CVector moveForce = GetRight(); + moveForce.z += 0.1f; + ApplyMoveForce(moveForce); + if (collideWith->m_nMoveState != PEDMOVE_RUN && collideWith->m_nMoveState != PEDMOVE_SPRINT) + animToPlay = ANIM_STD_HIT_LEFT; + else + animToPlay = ANIM_STD_HITBYGUN_LEFT; + } else if (heLooksToUs) { + CVector moveForce = GetRight() * -1.0f; + moveForce.z += 0.1f; + ApplyMoveForce(moveForce); + if (collideWith->m_nMoveState != PEDMOVE_RUN && collideWith->m_nMoveState != PEDMOVE_SPRINT) + animToPlay = ANIM_STD_HIT_RIGHT; + else + animToPlay = ANIM_STD_HITBYGUN_RIGHT; + } else { + if (collideWith->m_nMoveState != PEDMOVE_RUN && collideWith->m_nMoveState != PEDMOVE_SPRINT) + animToPlay = ANIM_STD_HIT_BACK; + else + animToPlay = ANIM_STD_HITBYGUN_BACK; + } + + if (collideWith->IsPedInControl() && CTimer::GetTimeInMilliseconds() > collideWith->m_nPedStateTimer) { + animAssoc = CAnimManager::BlendAnimation(collideWith->GetClump(), ASSOCGRP_STD, animToPlay, 8.0f); + animAssoc->flags |= ASSOC_FADEOUTWHENDONE; + collideWith->m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 1000; + if (m_nPedState == PED_ATTACK) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FIGHT_PUNCH_39, 0.0f); + } + } else { + // We're at his right side + if (DotProduct(posDiff, collideWith->GetRight()) <= 0.0f) { + CVector moveForce = GetRight() * -1.0f; + moveForce.z += 0.1f; + ApplyMoveForce(moveForce); + if (heLooksToUs) + animToPlay = ANIM_STD_HIGHIMPACT_RIGHT; + else + animToPlay = ANIM_STD_SPINFORWARD_RIGHT; + } else { + CVector moveForce = GetRight(); + moveForce.z += 0.1f; + ApplyMoveForce(moveForce); + if (heLooksToUs) + animToPlay = ANIM_STD_HIGHIMPACT_LEFT; + else + animToPlay = ANIM_STD_SPINFORWARD_LEFT; + } + + if (m_nPedState == PED_ATTACK && collideWith->IsPedInControl()) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FIGHT_PUNCH_39, 0.0f); + + collideWith->SetFall(3000, animToPlay, 0); + } + } else { + if (!IsPedInControl()) + return; + + if (collideWith->m_nMoveState == PEDMOVE_NONE || collideWith->m_nMoveState == PEDMOVE_STILL) + return; + + if (m_nPedType != collideWith->m_nPedType || m_nPedType == PEDTYPE_CIVMALE || m_nPedType == PEDTYPE_CIVFEMALE) { + + if (!weAreMissionChar && heLooksToUs && m_pedStats->m_fear > 100 - collideWith->m_pedStats->m_temper) { + + if (CGeneral::GetRandomNumber() & 1 && CTimer::GetTimeInMilliseconds() < m_nPedStateTimer){ + SetEvasiveStep(collideWith, 2); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 3000; + } else if (collideWith->m_nMoveState > PEDMOVE_WALK) { + waitTime = 2000; + SetWaitState(WAITSTATE_PLAYANIM_DUCK, &waitTime); + } + } + } else if (heLooksToUs + && collideWith->m_nPedState != PED_STEP_AWAY + && m_nPedState != PED_STEP_AWAY + && CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { + + SetEvasiveStep(collideWith, 1); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 3000; + } + } + + if (IsPlayer()) { + SetLookFlag(collideWith, true); + SetLookTimer(800); + } + } else { + bool isRunning = m_nMoveState == PEDMOVE_RUN || m_nMoveState == PEDMOVE_SPRINT; + SetFindPathAndFlee(collideWith, 5000, !isRunning); + } +} + +void +CPed::KillPedWithCar(CVehicle *car, float impulse) +{ + CVehicleModelInfo *vehModel; + CColModel *vehColModel; + uint8 damageDir; + PedNode nodeToDamage; + eWeaponType killMethod; + + if (m_nPedState == PED_FALL || m_nPedState == PED_DIE) { + if (!this->m_pCollidingEntity || car->GetStatus() == STATUS_PLAYER) + this->m_pCollidingEntity = car; + return; + } + + if (m_nPedState == PED_DEAD) + return; + + if (m_pCurSurface) { + if (m_pCurSurface->IsVehicle() && (((CVehicle*)m_pCurSurface)->m_vehType == VEHICLE_TYPE_BOAT || IsPlayer())) + return; + } + + CVector distVec = GetPosition() - car->GetPosition(); + + if ((impulse > 12.0f || car->GetModelIndex() == MI_TRAIN) && !IsPlayer()) { + nodeToDamage = PED_TORSO; + killMethod = WEAPONTYPE_RAMMEDBYCAR; + uint8 randVal = CGeneral::GetRandomNumber() & 3; + + if (car == FindPlayerVehicle()) { + float carSpeed = car->m_vecMoveSpeed.Magnitude(); + uint8 shakeFreq; + if (100.0f * carSpeed * 2000.0f / car->m_fMass + 80.0f <= 250.0f) { + shakeFreq = 100.0f * carSpeed * 2000.0f / car->m_fMass + 80.0f; + } else { + shakeFreq = 250.0f; + } + CPad::GetPad(0)->StartShake(40000 / shakeFreq, shakeFreq); + } + bIsStanding = false; + damageDir = GetLocalDirection(-m_vecMoveSpeed); + vehModel = (CVehicleModelInfo *)CModelInfo::GetModelInfo(car->GetModelIndex()); + vehColModel = vehModel->GetColModel(); + float carRightAndDistDotProd = DotProduct(distVec, car->GetRight()); + + if (car->GetModelIndex() == MI_TRAIN) { + killMethod = WEAPONTYPE_RUNOVERBYCAR; + nodeToDamage = PED_HEAD; + m_vecMoveSpeed = 0.9f * car->m_vecMoveSpeed; + m_vecMoveSpeed.z = 0.0f; + if (damageDir == 1 || damageDir == 3) + damageDir = 2; + if (CGame::nastyGame) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLATTER, 0.0f); + + // Car doesn't look to us + } else if (DotProduct(car->m_vecMoveSpeed, car->GetForward()) >= 0.0f){ + + if (0.99f * vehColModel->boundingBox.max.x < Abs(carRightAndDistDotProd)) { + + // We're at the right of the car + if (carRightAndDistDotProd <= 0.0f) + nodeToDamage = PED_UPPERARML; + else + nodeToDamage = PED_UPPERARMR; + + if (Abs(DotProduct(distVec, car->GetForward())) < 0.85f * vehColModel->boundingBox.max.y) { + killMethod = WEAPONTYPE_RUNOVERBYCAR; + m_vecMoveSpeed = 0.9f * car->m_vecMoveSpeed; + m_vecMoveSpeed.z = 0.0f; + if (damageDir == 1 || damageDir == 3) + damageDir = 2; + if (CGame::nastyGame) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLATTER, 0.0f); + + } + } else { + float carFrontAndDistDotProd = DotProduct(distVec, car->GetForward()); + + // carFrontAndDistDotProd <= 0.0 car looks to us + if ((carFrontAndDistDotProd <= 0.1 || randVal == 1) && randVal != 0) { + killMethod = WEAPONTYPE_RUNOVERBYCAR; + nodeToDamage = PED_HEAD; + m_vecMoveSpeed = 0.9f * car->m_vecMoveSpeed; + m_vecMoveSpeed.z = 0.0f; + if (damageDir == 1 || damageDir == 3) + damageDir = 2; + + if (CGame::nastyGame) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLATTER, 0.0f); + + } else { + nodeToDamage = PED_MID; + float vehColMaxY = vehColModel->boundingBox.max.y; + float vehColMinY = vehColModel->boundingBox.min.y; + float vehColMaxZ = vehColModel->boundingBox.max.z; + float carFrontZ = car->GetForward().z; + float carHighestZ, carLength; + + if (carFrontZ < -0.2f) { + // Highest point of car's back + carHighestZ = (car->GetMatrix() * CVector(0.0f, vehColMinY, vehColMaxZ)).z; + carLength = vehColMaxY - vehColMinY; + + } else if (carFrontZ > 0.1f) { + // Highest point of car's front + carHighestZ = (car->GetMatrix() * CVector(0.0f, vehColMaxY, vehColMaxZ)).z; + float highestZDist = carHighestZ - GetPosition().z; + if (highestZDist > 0.0f) { + GetMatrix().GetPosition().z += 0.5f * highestZDist; + carHighestZ += highestZDist * 0.25f; + } + carLength = vehColMaxY; + + } else { + // Highest point of car's front + carHighestZ = (car->GetMatrix() * CVector(0.0f, vehColMaxY, vehColMaxZ)).z; + carLength = vehColMaxY; + } + + float pedJumpSpeedToReachHighestZ = (carHighestZ - GetPosition().z) / (carLength / car->m_vecMoveSpeed.Magnitude()); + + // TODO: What are we doing down here? + float unknown = ((CGeneral::GetRandomNumber() % 256) * 0.002 + 1.5) * pedJumpSpeedToReachHighestZ; + + // After this point, distVec isn't distVec anymore. + distVec = car->m_vecMoveSpeed; + distVec.Normalise(); + distVec *= 0.2 * unknown; + + if (damageDir != 1 && damageDir != 3) + distVec.z += unknown; + else + distVec.z += 1.5f * unknown; + + m_vecMoveSpeed = distVec; + damageDir += 2; + if (damageDir > 3) + damageDir = damageDir - 4; + + if (car->m_vehType == VEHICLE_TYPE_CAR) { + CObject *bonnet = ((CAutomobile*)car)->RemoveBonnetInPedCollision(); + + if (bonnet) { + if (CGeneral::GetRandomNumber() & 1) { + bonnet->m_vecMoveSpeed += Multiply3x3(car->GetMatrix(), CVector(0.1f, 0.0f, 0.5f)); + } else { + bonnet->m_vecMoveSpeed += Multiply3x3(car->GetMatrix(), CVector(-0.1f, 0.0f, 0.5f)); + } + CVector forceDir = car->GetUp() * 10.0f; + bonnet->ApplyTurnForce(forceDir, car->GetForward()); + } + } + } + } + } + + if (car->pDriver) { + CEventList::RegisterEvent((m_nPedType == PEDTYPE_COP ? EVENT_HIT_AND_RUN_COP : EVENT_HIT_AND_RUN), EVENT_ENTITY_PED, this, car->pDriver, 1000); + } + + ePedPieceTypes pieceToDamage; + switch (nodeToDamage) { + case PED_HEAD: + pieceToDamage = PEDPIECE_HEAD; + break; + case PED_UPPERARML: + pieceToDamage = PEDPIECE_LEFTARM; + break; + case PED_UPPERARMR: + pieceToDamage = PEDPIECE_RIGHTARM; + break; + default: + pieceToDamage = PEDPIECE_MID; + break; + } + InflictDamage(car, killMethod, 1000.0f, pieceToDamage, damageDir); + + if (DyingOrDead() + && bIsPedDieAnimPlaying && !m_pCollidingEntity) { + m_pCollidingEntity = car; + } + if (nodeToDamage == PED_MID) + bKnockedUpIntoAir = true; + else + bKnockedUpIntoAir = false; + + distVec.Normalise(); + +#ifdef VC_PED_PORTS + distVec *= Min(car->m_fMass / 1400.0f, 1.0f); +#endif + car->ApplyMoveForce(distVec * -100.0f); + Say(SOUND_PED_DEFEND); + + } else if (m_vecDamageNormal.z < -0.8f && impulse > 3.0f + || impulse > 6.0f && (!IsPlayer() || impulse > 10.0f)) { + + bIsStanding = false; + uint8 fallDirection = GetLocalDirection(-car->m_vecMoveSpeed); + float damage; + if (IsPlayer() && car->GetModelIndex() == MI_TRAIN) + damage = 150.0f; + else + damage = 30.0f; + + InflictDamage(car, WEAPONTYPE_RAMMEDBYCAR, damage, PEDPIECE_TORSO, fallDirection); + SetFall(1000, (AnimationId)(fallDirection + ANIM_STD_HIGHIMPACT_FRONT), true); + + if (OnGround() && !m_pCollidingEntity && + (!IsPlayer() || bHasHitWall || car->GetModelIndex() == MI_TRAIN || m_vecDamageNormal.z < -0.8f)) { + + m_pCollidingEntity = car; + } + + bKnockedUpIntoAir = false; + if (car->GetModelIndex() != MI_TRAIN && !bHasHitWall) { + m_vecMoveSpeed = car->m_vecMoveSpeed * 0.75f; + } + m_vecMoveSpeed.z = 0.0f; + distVec.Normalise(); +#ifdef VC_PED_PORTS + distVec *= Min(car->m_fMass / 1400.0f, 1.0f); +#endif + car->ApplyMoveForce(distVec * -60.0f); + Say(SOUND_PED_DEFEND); + } + +#ifdef VC_PED_PORTS + // Killing gang members with car wasn't triggering a fight, until now... Taken from VC. + if (IsGangMember()) { + CPed *driver = car->pDriver; + if (driver && driver->IsPlayer() +#ifdef FIX_BUGS + && (CharCreatedBy != MISSION_CHAR || bRespondsToThreats) && (!m_leader || m_leader != driver) +#endif + ) { + RegisterThreatWithGangPeds(driver); + } + } +#endif +} \ No newline at end of file diff --git a/src/peds/PedIK.cpp b/src/peds/PedIK.cpp new file mode 100644 index 0000000..8358a19 --- /dev/null +++ b/src/peds/PedIK.cpp @@ -0,0 +1,560 @@ +#include "common.h" + +#include "Bones.h" +#include "Camera.h" +#include "PedIK.h" +#include "Ped.h" +#include "General.h" +#include "RwHelper.h" + +LimbMovementInfo CPedIK::ms_torsoInfo = { DEGTORAD(50.0f), DEGTORAD(-50.0f), DEGTORAD(15.0f), DEGTORAD(45.0f), DEGTORAD(-45.0f), DEGTORAD(7.0f) }; +LimbMovementInfo CPedIK::ms_headInfo = { DEGTORAD(90.0f), DEGTORAD(-90.0f), DEGTORAD(10.0f), DEGTORAD(45.0f), DEGTORAD(-45.0f), DEGTORAD(5.0f) }; +LimbMovementInfo CPedIK::ms_headRestoreInfo = { DEGTORAD(90.0f), DEGTORAD(-90.0f), DEGTORAD(10.0f), DEGTORAD(45.0f), DEGTORAD(-45.0f), DEGTORAD(5.0f) }; +LimbMovementInfo CPedIK::ms_upperArmInfo = { DEGTORAD(20.0f), DEGTORAD(-100.0f), DEGTORAD(20.0f), DEGTORAD(70.0f), DEGTORAD(-70.0f), DEGTORAD(10.0f) }; +LimbMovementInfo CPedIK::ms_lowerArmInfo = { DEGTORAD(80.0f), DEGTORAD(0.0f), DEGTORAD(20.0f), DEGTORAD(90.0f), DEGTORAD(-90.0f), DEGTORAD(5.0f) }; + +const RwV3d XaxisIK = { 1.0f, 0.0f, 0.0f}; +const RwV3d YaxisIK = { 0.0f, 1.0f, 0.0f}; +const RwV3d ZaxisIK = { 0.0f, 0.0f, 1.0f}; + +CPedIK::CPedIK(CPed *ped) : m_ped(ped) +{ + assert(ped != nil); + m_flags = 0; + m_headOrient.yaw = 0.0f; + m_headOrient.pitch = 0.0f; + m_torsoOrient.yaw = 0.0f; + m_torsoOrient.pitch = 0.0f; + m_upperArmOrient.yaw = 0.0f; + m_upperArmOrient.pitch = 0.0f; + m_lowerArmOrient.yaw = 0.0f; + m_lowerArmOrient.pitch = 0.0f; +} + +#ifdef PED_SKIN +inline RwMatrix* +GetBoneMatrix(CPed *ped, int32 bone) +{ + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(ped->GetClump()); + int idx = RpHAnimIDGetIndex(hier, bone); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + return &mats[idx]; +} +inline RwMatrix* +GetComponentMatrix(CPed *ped, int32 node) +{ + return GetBoneMatrix(ped, ped->m_pFrames[node]->nodeID); +} +#endif + +void +CPedIK::RotateTorso(AnimBlendFrameData *node, LimbOrientation *limb, bool changeRoll) +{ +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + RtQuat *q = &node->hanimFrame->q; +#ifndef FIX_BUGS + // this is what the game does (also VC), but it does not look great + RtQuatRotate(q, &XaxisIK, RADTODEG(limb->yaw), rwCOMBINEPRECONCAT); + RtQuatRotate(q, &ZaxisIK, RADTODEG(limb->pitch), rwCOMBINEPRECONCAT); // pitch +#else + // copied the code from the non-skinned case + // this seems to work ok + + // We can't get the parent matrix of an hanim frame but + // this function is always called with PED_MID, so we know the parent frame. + // Trouble is that PED_MID is "Smid" on PS2/PC but BONE_torso on mobile/xbox... + // Assuming BONE_torso, the parent is BONE_mid, so let's use that: + RwMatrix *mat = GetBoneMatrix(m_ped, BONE_mid); + + RwV3d vec1, vec2; + vec1.x = mat->right.z; + vec1.y = mat->up.z; + vec1.z = mat->at.z; + float c = Cos(m_ped->m_fRotationCur); + float s = Sin(m_ped->m_fRotationCur); + vec2.x = -(c*mat->right.x + s*mat->right.y); + vec2.y = -(c*mat->up.x + s*mat->up.y); + vec2.z = -(c*mat->at.x + s*mat->at.y); + + // Not sure what exactly to do here + RtQuatRotate(q, &vec1, RADTODEG(limb->yaw), rwCOMBINEPRECONCAT); + RtQuatRotate(q, &vec2, RADTODEG(limb->pitch), rwCOMBINEPRECONCAT); +#endif + m_ped->bDontAcceptIKLookAts = true; + }else +#endif + { + RwFrame *f = node->frame; + RwMatrix *mat = GetWorldMatrix(RwFrameGetParent(f), RwMatrixCreate()); + + RwV3d upVector = { mat->right.z, mat->up.z, mat->at.z }; + RwV3d rightVector; + RwV3d pos = RwFrameGetMatrix(f)->pos; + + // rotation == 0 -> looking in y direction + // left? vector + float c = Cos(m_ped->m_fRotationCur); + float s = Sin(m_ped->m_fRotationCur); + rightVector.x = -(c*mat->right.x + s*mat->right.y); + rightVector.y = -(c*mat->up.x + s*mat->up.y); + rightVector.z = -(c*mat->at.x + s*mat->at.y); + + if(changeRoll){ + // Used when aiming only involves over the legs.(canAimWithArm) + // Automatically changes roll(forward rotation) axis of the parts above upper legs while moving, based on position of upper legs. + // Not noticeable in normal conditions... + + RwV3d forwardVector; + CVector inversedForward = CrossProduct(CVector(0.0f, 0.0f, 1.0f), mat->up); + inversedForward.Normalise(); + float dotProduct = DotProduct(mat->at, inversedForward); + if(dotProduct > 1.0f) dotProduct = 1.0f; + if(dotProduct < -1.0f) dotProduct = -1.0f; + float alpha = Acos(dotProduct); + + if(mat->at.z < 0.0f) + alpha = -alpha; + + forwardVector.x = s * mat->right.x - c * mat->right.y; + forwardVector.y = s * mat->up.x - c * mat->up.y; + forwardVector.z = s * mat->at.x - c * mat->at.y; + + float curYaw, curPitch; + ExtractYawAndPitchWorld(mat, &curYaw, &curPitch); + RwMatrixRotate(RwFrameGetMatrix(f), &rightVector, RADTODEG(limb->pitch), rwCOMBINEPOSTCONCAT); + RwMatrixRotate(RwFrameGetMatrix(f), &upVector, RADTODEG(limb->yaw - (curYaw - m_ped->m_fRotationCur)), rwCOMBINEPOSTCONCAT); + RwMatrixRotate(RwFrameGetMatrix(f), &forwardVector, RADTODEG(alpha), rwCOMBINEPOSTCONCAT); + }else{ + // pitch + RwMatrixRotate(RwFrameGetMatrix(f), &rightVector, RADTODEG(limb->pitch), rwCOMBINEPOSTCONCAT); + // yaw + RwMatrixRotate(RwFrameGetMatrix(f), &upVector, RADTODEG(limb->yaw), rwCOMBINEPOSTCONCAT); + } + RwFrameGetMatrix(f)->pos = pos; + RwMatrixDestroy(mat); + } +} + +void +CPedIK::GetComponentPosition(RwV3d &pos, uint32 node) +{ + RwFrame *f; + RwMatrix *mat; + +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + pos.x = 0.0f; + pos.y = 0.0f; + pos.z = 0.0f; + mat = GetComponentMatrix(m_ped, node); + // could just copy the position out of the matrix... + RwV3dTransformPoints(&pos, &pos, 1, mat); + }else +#endif + { + f = m_ped->m_pFrames[node]->frame; + mat = RwFrameGetMatrix(f); + pos = mat->pos; + + for (f = RwFrameGetParent(f); f; f = RwFrameGetParent(f)) + RwV3dTransformPoints(&pos, &pos, 1, RwFrameGetMatrix(f)); + } +} + +RwMatrix* +CPedIK::GetWorldMatrix(RwFrame *source, RwMatrix *destination) +{ + RwFrame *i; + + *destination = *RwFrameGetMatrix(source); + + for (i = RwFrameGetParent(source); i; i = RwFrameGetParent(i)) + RwMatrixTransform(destination, RwFrameGetMatrix(i), rwCOMBINEPOSTCONCAT); + + return destination; +} + +LimbMoveStatus +CPedIK::MoveLimb(LimbOrientation &limb, float targetYaw, float targetPitch, LimbMovementInfo &moveInfo) +{ + LimbMoveStatus result = ONE_ANGLE_COULDNT_BE_SET_EXACTLY; + + // yaw + + if (limb.yaw > targetYaw) { + limb.yaw -= moveInfo.yawD; + } else if (limb.yaw < targetYaw) { + limb.yaw += moveInfo.yawD; + } + + if (Abs(limb.yaw - targetYaw) < moveInfo.yawD) { + limb.yaw = targetYaw; + result = ANGLES_SET_EXACTLY; + } + + if (limb.yaw > moveInfo.maxYaw || limb.yaw < moveInfo.minYaw) { + limb.yaw = Clamp(limb.yaw, moveInfo.minYaw, moveInfo.maxYaw); + result = ANGLES_SET_TO_MAX; + } + + // pitch + + if (limb.pitch > targetPitch) { + limb.pitch -= moveInfo.pitchD; + } else if (limb.pitch < targetPitch) { + limb.pitch += moveInfo.pitchD; + } + + if (Abs(limb.pitch - targetPitch) < moveInfo.pitchD) + limb.pitch = targetPitch; + else + result = ONE_ANGLE_COULDNT_BE_SET_EXACTLY; + + if (limb.pitch > moveInfo.maxPitch || limb.pitch < moveInfo.minPitch) { + limb.pitch = Clamp(limb.pitch, moveInfo.minPitch, moveInfo.maxPitch); + result = ANGLES_SET_TO_MAX; + } + return result; +} + +bool +CPedIK::RestoreGunPosn(void) +{ + LimbMoveStatus limbStatus = MoveLimb(m_torsoOrient, 0.0f, 0.0f, ms_torsoInfo); + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + return limbStatus == ANGLES_SET_EXACTLY; +} + +#ifdef PED_SKIN +void +CPedIK::RotateHead(void) +{ + RtQuat *q = &m_ped->m_pFrames[PED_HEAD]->hanimFrame->q; + RtQuatRotate(q, &XaxisIK, RADTODEG(m_headOrient.yaw), rwCOMBINEREPLACE); + RtQuatRotate(q, &ZaxisIK, RADTODEG(m_headOrient.pitch), rwCOMBINEPOSTCONCAT); + m_ped->bDontAcceptIKLookAts = true; +} +#endif + +bool +CPedIK::LookInDirection(float targetYaw, float targetPitch) +{ + bool success = true; + float yaw, pitch; +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + if (!(m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION)) { + m_ped->m_pFrames[PED_HEAD]->flag |= AnimBlendFrameData::IGNORE_ROTATION; + ExtractYawAndPitchLocalSkinned(m_ped->m_pFrames[PED_HEAD], &m_headOrient.yaw, &m_headOrient.pitch); + } + + // parent of head is torso + RwMatrix worldMat = *GetBoneMatrix(m_ped, BONE_torso); + ExtractYawAndPitchWorld(&worldMat, &yaw, &pitch); + + LimbMoveStatus headStatus = MoveLimb(m_headOrient, CGeneral::LimitRadianAngle(targetYaw - yaw), + CGeneral::LimitRadianAngle(DEGTORAD(10.0f)), ms_headInfo); + if (headStatus == ANGLES_SET_TO_MAX) + success = false; + + if (headStatus != ANGLES_SET_EXACTLY){ + if (!(m_flags & LOOKAROUND_HEAD_ONLY)){ + if (MoveLimb(m_torsoOrient, CGeneral::LimitRadianAngle(targetYaw), targetPitch, ms_torsoInfo)) + success = true; + }else{ + RotateHead(); + return success; + } + } + + if (!(m_flags & LOOKAROUND_HEAD_ONLY)) + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + RotateHead(); + }else +#endif + { + RwFrame *frame = m_ped->m_pFrames[PED_HEAD]->frame; + RwMatrix *frameMat = RwFrameGetMatrix(frame); + + if (!(m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION)) { + m_ped->m_pFrames[PED_HEAD]->flag |= AnimBlendFrameData::IGNORE_ROTATION; + ExtractYawAndPitchLocal(frameMat, &m_headOrient.yaw, &m_headOrient.pitch); + } + + RwMatrix *worldMat = RwMatrixCreate(); + worldMat = GetWorldMatrix(RwFrameGetParent(frame), worldMat); + + ExtractYawAndPitchWorld(worldMat, &yaw, &pitch); + RwMatrixDestroy(worldMat); + + yaw += m_torsoOrient.yaw; + float neededYawTurn = CGeneral::LimitRadianAngle(targetYaw - yaw); + pitch *= Cos(neededYawTurn); + + float neededPitchTurn = CGeneral::LimitRadianAngle(targetPitch - pitch); + LimbMoveStatus headStatus = MoveLimb(m_headOrient, neededYawTurn, neededPitchTurn, ms_headInfo); + if (headStatus == ANGLES_SET_TO_MAX) + success = false; + + if (headStatus != ANGLES_SET_EXACTLY && !(m_flags & LOOKAROUND_HEAD_ONLY)) { + float remainingTurn = CGeneral::LimitRadianAngle(targetYaw - m_ped->m_fRotationCur); + if (MoveLimb(m_torsoOrient, remainingTurn, targetPitch, ms_torsoInfo)) + success = true; + } + CMatrix nextFrame = CMatrix(frameMat); + CVector framePos = nextFrame.GetPosition(); + + nextFrame.SetRotateZ(m_headOrient.pitch); + nextFrame.RotateX(m_headOrient.yaw); + nextFrame.GetPosition() += framePos; + nextFrame.UpdateRW(); + + if (!(m_flags & LOOKAROUND_HEAD_ONLY)) + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + + } + return success; +} + +bool +CPedIK::LookAtPosition(CVector const &pos) +{ + float yawToFace = CGeneral::GetRadianAngleBetweenPoints( + pos.x, pos.y, + m_ped->GetPosition().x, m_ped->GetPosition().y); + + float pitchToFace = CGeneral::GetRadianAngleBetweenPoints( + pos.z, (m_ped->GetPosition() - pos).Magnitude2D(), + m_ped->GetPosition().z, 0.0f); + + return LookInDirection(yawToFace, pitchToFace); +} + +bool +CPedIK::PointGunInDirection(float targetYaw, float targetPitch) +{ + bool result = true; + bool armPointedToGun = false; + float angle = CGeneral::LimitRadianAngle(targetYaw - m_ped->m_fRotationCur); + m_flags &= (~GUN_POINTED_SUCCESSFULLY); + m_flags |= LOOKAROUND_HEAD_ONLY; + if (m_flags & AIMS_WITH_ARM) { + armPointedToGun = PointGunInDirectionUsingArm(angle, targetPitch); + angle = CGeneral::LimitRadianAngle(angle - m_upperArmOrient.yaw); + } + if (armPointedToGun) { + if (m_flags & AIMS_WITH_ARM && m_torsoOrient.yaw * m_upperArmOrient.yaw < 0.0f) + MoveLimb(m_torsoOrient, 0.0f, m_torsoOrient.pitch, ms_torsoInfo); + } else { + // Unused code + RwMatrix *matrix; + float yaw, pitch; +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + matrix = RwMatrixCreate(); + *matrix = *GetComponentMatrix(m_ped, PED_UPPERARMR); + ExtractYawAndPitchWorld(matrix, &yaw, &pitch); + RwMatrixDestroy(matrix); + }else +#endif + { + matrix = GetWorldMatrix(RwFrameGetParent(m_ped->m_pFrames[PED_UPPERARMR]->frame), RwMatrixCreate()); + ExtractYawAndPitchWorld(matrix, &yaw, &pitch); + RwMatrixDestroy(matrix); + } + // + + LimbMoveStatus status = MoveLimb(m_torsoOrient, angle, targetPitch, ms_torsoInfo); + if (status == ANGLES_SET_TO_MAX) + result = false; + else if (status == ANGLES_SET_EXACTLY) + m_flags |= GUN_POINTED_SUCCESSFULLY; + } + if (TheCamera.Cams[TheCamera.ActiveCam].Using3rdPersonMouseCam() && m_flags & AIMS_WITH_ARM) + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, true); + else + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + return result; +} + +bool +CPedIK::PointGunInDirectionUsingArm(float targetYaw, float targetPitch) +{ + bool result = false; + + RwV3d upVector; // only for non-skinned + RwMatrix *matrix; + float yaw, pitch; +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + matrix = RwMatrixCreate(); + *matrix = *GetComponentMatrix(m_ped, PED_UPPERARMR); + ExtractYawAndPitchWorld(matrix, &yaw, &pitch); + RwMatrixDestroy(matrix); + }else +#endif + { + RwFrame *frame = m_ped->m_pFrames[PED_UPPERARMR]->frame; + matrix = GetWorldMatrix(RwFrameGetParent(frame), RwMatrixCreate()); + + // with PED_SKIN this is actually done below (with a memory leak) + upVector.x = matrix->right.z; + upVector.y = matrix->up.z; + upVector.z = matrix->at.z; + + ExtractYawAndPitchWorld(matrix, &yaw, &pitch); + RwMatrixDestroy(matrix); + } + + RwV3d rightVector = { 0.0f, 0.0f, 1.0f }; + RwV3d forwardVector = { 1.0f, 0.0f, 0.0f }; + + float uaYaw, uaPitch; +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + uaYaw = targetYaw; + uaPitch = targetPitch + DEGTORAD(10.0f); + }else +#endif + { + uaYaw = targetYaw - m_torsoOrient.yaw - DEGTORAD(15.0f); + uaPitch = CGeneral::LimitRadianAngle(targetPitch - pitch); + } + LimbMoveStatus uaStatus = MoveLimb(m_upperArmOrient, uaYaw, uaPitch, ms_upperArmInfo); + if (uaStatus == ANGLES_SET_EXACTLY) { + m_flags |= GUN_POINTED_SUCCESSFULLY; + result = true; + } + +#ifdef PED_SKIN + // this code is completely missing on xbox & android, but we can keep it with the check + // TODO? implement it for skinned geometry? + if(!IsClumpSkinned(m_ped->GetClump())) +#endif + if (uaStatus == ANGLES_SET_TO_MAX) { + float laYaw = uaYaw - m_upperArmOrient.yaw; + + LimbMoveStatus laStatus; + if (laYaw > 0.0f) + laStatus = MoveLimb(m_lowerArmOrient, laYaw, -DEGTORAD(45.0f), ms_lowerArmInfo); + else + laStatus = MoveLimb(m_lowerArmOrient, laYaw, 0.0f, ms_lowerArmInfo); + + if (laStatus == ANGLES_SET_EXACTLY) { + m_flags |= GUN_POINTED_SUCCESSFULLY; + result = true; + } + RwFrame *child = GetFirstChild(m_ped->m_pFrames[PED_UPPERARMR]->frame); + RwV3d pos = RwFrameGetMatrix(child)->pos; + RwMatrixRotate(RwFrameGetMatrix(child), &forwardVector, RADTODEG(m_lowerArmOrient.pitch), rwCOMBINEPOSTCONCAT); + RwMatrixRotate(RwFrameGetMatrix(child), &rightVector, RADTODEG(-m_lowerArmOrient.yaw), rwCOMBINEPOSTCONCAT); + RwFrameGetMatrix(child)->pos = pos; + } + +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + RtQuat *q = &m_ped->m_pFrames[PED_UPPERARMR]->hanimFrame->q; + RtQuatRotate(q, &XaxisIK, RADTODEG(m_upperArmOrient.yaw), rwCOMBINEPOSTCONCAT); + RtQuatRotate(q, &ZaxisIK, RADTODEG(m_upperArmOrient.pitch), rwCOMBINEPOSTCONCAT); + m_ped->bDontAcceptIKLookAts = true; + }else +#endif + { + RwFrame *frame = m_ped->m_pFrames[PED_UPPERARMR]->frame; + // with PED_SKIN we're also getting upVector here + RwV3d pos = RwFrameGetMatrix(frame)->pos; + RwMatrixRotate(RwFrameGetMatrix(frame), &rightVector, RADTODEG(m_upperArmOrient.pitch), rwCOMBINEPOSTCONCAT); + RwMatrixRotate(RwFrameGetMatrix(frame), &upVector, RADTODEG(m_upperArmOrient.yaw), rwCOMBINEPOSTCONCAT); + RwFrameGetMatrix(frame)->pos = pos; + } + return result; +} + +bool +CPedIK::PointGunAtPosition(CVector const& position) +{ + return PointGunInDirection( + CGeneral::GetRadianAngleBetweenPoints(position.x, position.y, m_ped->GetPosition().x, m_ped->GetPosition().y), + CGeneral::GetRadianAngleBetweenPoints(position.z, Distance2D(m_ped->GetPosition(), position.x, position.y), + m_ped->GetPosition().z, + 0.0f)); +} + +bool +CPedIK::RestoreLookAt(void) +{ + bool result = false; + float yaw, pitch; + +#ifdef PED_SKIN + if(IsClumpSkinned(m_ped->GetClump())){ + if (m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION) { + m_ped->m_pFrames[PED_HEAD]->flag &= (~AnimBlendFrameData::IGNORE_ROTATION); + } else { + ExtractYawAndPitchLocalSkinned(m_ped->m_pFrames[PED_HEAD], &yaw, &pitch); + if (MoveLimb(m_headOrient, yaw, pitch, ms_headRestoreInfo) == ANGLES_SET_EXACTLY) + result = true; + } + RotateHead(); + }else +#endif + { + RwMatrix *mat = RwFrameGetMatrix(m_ped->m_pFrames[PED_HEAD]->frame); + if (m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION) { + m_ped->m_pFrames[PED_HEAD]->flag &= (~AnimBlendFrameData::IGNORE_ROTATION); + } else { + ExtractYawAndPitchLocal(mat, &yaw, &pitch); + if (MoveLimb(m_headOrient, yaw, pitch, ms_headRestoreInfo) == ANGLES_SET_EXACTLY) + result = true; + } + + CMatrix matrix(mat); + CVector pos = matrix.GetPosition(); + matrix.SetRotateZ(m_headOrient.pitch); + matrix.RotateX(m_headOrient.yaw); + matrix.Translate(pos); + matrix.UpdateRW(); + } + if (!(m_flags & LOOKAROUND_HEAD_ONLY)){ + MoveLimb(m_torsoOrient, 0.0f, 0.0f, ms_torsoInfo); + if (!(m_flags & LOOKAROUND_HEAD_ONLY)) + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + } + return result; +} + +void +CPedIK::ExtractYawAndPitchWorld(RwMatrix *mat, float *yaw, float *pitch) +{ + float f = Clamp(DotProduct(mat->up, CVector(0.0f, 1.0f, 0.0f)), -1.0f, 1.0f); + *yaw = Acos(f); + if (mat->up.x > 0.0f) *yaw = -*yaw; + + f = Clamp(DotProduct(mat->right, CVector(0.0f, 0.0f, 1.0f)), -1.0f, 1.0f); + *pitch = Acos(f); + if (mat->up.z > 0.0f) *pitch = -*pitch; +} + +void +CPedIK::ExtractYawAndPitchLocal(RwMatrix *mat, float *yaw, float *pitch) +{ + float f = Clamp(DotProduct(mat->at, CVector(0.0f, 0.0f, 1.0f)), -1.0f, 1.0f); + *yaw = Acos(f); + if (mat->at.y > 0.0f) *yaw = -*yaw; + + f = Clamp(DotProduct(mat->right, CVector(1.0f, 0.0f, 0.0f)), -1.0f, 1.0f); + *pitch = Acos(f); + if (mat->up.x > 0.0f) *pitch = -*pitch; +} + +#ifdef PED_SKIN +void +CPedIK::ExtractYawAndPitchLocalSkinned(AnimBlendFrameData *node, float *yaw, float *pitch) +{ + RwMatrix *mat = RwMatrixCreate(); + RtQuatConvertToMatrix(&node->hanimFrame->q, mat); + ExtractYawAndPitchLocal(mat, yaw, pitch); + RwMatrixDestroy(mat); +} +#endif diff --git a/src/peds/PedIK.h b/src/peds/PedIK.h new file mode 100644 index 0000000..1543fa3 --- /dev/null +++ b/src/peds/PedIK.h @@ -0,0 +1,68 @@ +#pragma once +#include "common.h" +#include "AnimBlendClumpData.h" + +struct LimbOrientation +{ + float yaw; + float pitch; +}; + +struct LimbMovementInfo { + float maxYaw; + float minYaw; + float yawD; + float maxPitch; + float minPitch; + float pitchD; +}; + +enum LimbMoveStatus { + ANGLES_SET_TO_MAX, // because given angles were unreachable + ONE_ANGLE_COULDNT_BE_SET_EXACTLY, // because it can't be reached in a jiffy + ANGLES_SET_EXACTLY +}; + +class CPed; + +class CPedIK +{ +public: + enum { + GUN_POINTED_SUCCESSFULLY = 1, + LOOKAROUND_HEAD_ONLY = 2, + AIMS_WITH_ARM = 4, + }; + + CPed *Const m_ped; + LimbOrientation m_headOrient; + LimbOrientation m_torsoOrient; + LimbOrientation m_upperArmOrient; + LimbOrientation m_lowerArmOrient; + int32 m_flags; + + static LimbMovementInfo ms_torsoInfo; + static LimbMovementInfo ms_headInfo; + static LimbMovementInfo ms_headRestoreInfo; + static LimbMovementInfo ms_upperArmInfo; + static LimbMovementInfo ms_lowerArmInfo; + + CPedIK(CPed *ped); + bool PointGunInDirection(float targetYaw, float targetPitch); + bool PointGunInDirectionUsingArm(float targetYaw, float targetPitch); + bool PointGunAtPosition(CVector const& position); + void GetComponentPosition(RwV3d &pos, uint32 node); + static RwMatrix *GetWorldMatrix(RwFrame *source, RwMatrix *destination); + void RotateTorso(AnimBlendFrameData* animBlend, LimbOrientation* limb, bool changeRoll); + void ExtractYawAndPitchLocal(RwMatrix *mat, float *yaw, float *pitch); + void ExtractYawAndPitchLocalSkinned(AnimBlendFrameData *node, float *yaw, float *pitch); + void ExtractYawAndPitchWorld(RwMatrix *mat, float *yaw, float *pitch); + LimbMoveStatus MoveLimb(LimbOrientation &limb, float targetYaw, float targetPitch, LimbMovementInfo &moveInfo); + bool RestoreGunPosn(void); + void RotateHead(void); + bool LookInDirection(float targetYaw, float targetPitch); + bool LookAtPosition(CVector const& pos); + bool RestoreLookAt(void); +}; + +VALIDATE_SIZE(CPedIK, 0x28); diff --git a/src/peds/PedPlacement.cpp b/src/peds/PedPlacement.cpp new file mode 100644 index 0000000..2d4a92f --- /dev/null +++ b/src/peds/PedPlacement.cpp @@ -0,0 +1,51 @@ +#include "common.h" + +#include "Ped.h" +#include "PedPlacement.h" +#include "World.h" + +void +CPedPlacement::FindZCoorForPed(CVector* pos) +{ + float zForPed; + float startZ = pos->z - 100.0f; + float foundColZ = -100.0f; + float foundColZ2 = -100.0f; + CColPoint foundCol; + CEntity* foundEnt; + + CVector vec( + pos->x, + pos->y, + pos->z + 1.0f + ); + + if (CWorld::ProcessVerticalLine(vec, startZ, foundCol, foundEnt, true, false, false, false, true, false, nil)) + foundColZ = foundCol.point.z; + + // Adjust coords and do a second test + vec.x += 0.1f; + vec.y += 0.1f; + + if (CWorld::ProcessVerticalLine(vec, startZ, foundCol, foundEnt, true, false, false, false, true, false, nil)) + foundColZ2 = foundCol.point.z; + + zForPed = Max(foundColZ, foundColZ2); + + if (zForPed > -99.0f) + pos->z = FEET_OFFSET + zForPed; +} + +CEntity* +CPedPlacement::IsPositionClearOfCars(Const CVector *pos) +{ + return CWorld::TestSphereAgainstWorld(*pos, 0.25f, nil, true, true, false, false, false, false); +} + +bool +CPedPlacement::IsPositionClearForPed(CVector* pos) +{ + int16 count; + CWorld::FindObjectsKindaColliding(*pos, 0.75f, true, &count, 2, nil, false, true, true, false, false); + return count == 0; +} diff --git a/src/peds/PedPlacement.h b/src/peds/PedPlacement.h new file mode 100644 index 0000000..b51e2aa --- /dev/null +++ b/src/peds/PedPlacement.h @@ -0,0 +1,8 @@ +#pragma once + +class CPedPlacement { +public: + static void FindZCoorForPed(CVector* pos); + static CEntity* IsPositionClearOfCars(Const CVector*); + static bool IsPositionClearForPed(CVector*); +}; \ No newline at end of file diff --git a/src/peds/PedRoutes.cpp b/src/peds/PedRoutes.cpp new file mode 100644 index 0000000..3ff080e --- /dev/null +++ b/src/peds/PedRoutes.cpp @@ -0,0 +1,79 @@ +#include "common.h" + +#include "main.h" +#include "PedRoutes.h" + +CRouteNode gaRoutes[NUMPEDROUTES]; + +void +CRouteNode::Initialise() +{ + for (int i = 0; i < NUMPEDROUTES; i++) { + gaRoutes[i].m_route = -1; + gaRoutes[i].m_pos = CVector(0.0f, 0.0f, 0.0f); + } +} + +int16 +CRouteNode::GetRouteThisPointIsOn(int16 point) +{ + return gaRoutes[point].m_route; +} + +// Actually GetFirstPointOfRoute +int16 +CRouteNode::GetRouteStart(int16 route) +{ + for (int i = 0; i < NUMPEDROUTES; i++) { + if (route == gaRoutes[i].m_route) + return i; + } + return -1; +} + +CVector +CRouteNode::GetPointPosition(int16 point) +{ + return gaRoutes[point].m_pos; +} + +void +CRouteNode::AddRoutePoint(int16 route, CVector pos) +{ + uint16 point; + for (point = 0; point < NUMPEDROUTES; point++) { + if (gaRoutes[point].m_route == -1) + break; + } +#ifdef FIX_BUGS + if (point == NUMPEDROUTES) + return; +#endif + gaRoutes[point].m_route = route; + gaRoutes[point].m_pos = pos; +} + +void +CRouteNode::RemoveRoute(int16 route) +{ + uint16 first_point, last_point, i; + for (first_point = 0; first_point < NUMPEDROUTES; first_point++) { + if (gaRoutes[first_point].m_route == route) + break; + } + if (first_point == NUMPEDROUTES) + return; + for (last_point = first_point; last_point < NUMPEDROUTES; last_point++) + if (gaRoutes[last_point].m_route != route) + break; + uint16 diff = last_point - first_point; +#ifdef FIX_BUGS + for (i = first_point; i < NUMPEDROUTES - diff; i++) + gaRoutes[i] = gaRoutes[i + diff]; +#else + for (i = 0; i < diff; i++) + gaRoutes[first_point + i] = gaRoutes[last_point + i]; +#endif + for (i = NUMPEDROUTES - diff; i < NUMPEDROUTES; i++) + gaRoutes[i].m_route = -1; +} diff --git a/src/peds/PedRoutes.h b/src/peds/PedRoutes.h new file mode 100644 index 0000000..c478e38 --- /dev/null +++ b/src/peds/PedRoutes.h @@ -0,0 +1,15 @@ +#pragma once + +class CRouteNode +{ +public: + int16 m_route; + CVector m_pos; + + static int16 GetRouteThisPointIsOn(int16); + static CVector GetPointPosition(int16); + static int16 GetRouteStart(int16); + static void AddRoutePoint(int16, CVector); + static void RemoveRoute(int16); + static void Initialise(void); +}; diff --git a/src/peds/PedType.cpp b/src/peds/PedType.cpp new file mode 100644 index 0000000..dcd4c71 --- /dev/null +++ b/src/peds/PedType.cpp @@ -0,0 +1,317 @@ +#include "common.h" + +#include "General.h" +#include "FileMgr.h" +#include "PedType.h" +#include "SaveBuf.h" + +CPedType *CPedType::ms_apPedType[NUM_PEDTYPES]; +CPedStats *CPedStats::ms_apPedStats[NUM_PEDSTATS]; + +void +CPedType::Initialise(void) +{ + int i; + + debug("Initialising CPedType...\n"); + for(i = 0; i < NUM_PEDTYPES; i++){ + ms_apPedType[i] = new CPedType; + ms_apPedType[i]->m_flag = PED_FLAG_PLAYER1; + ms_apPedType[i]->unknown1 = 0.0f; + ms_apPedType[i]->unknown2 = 0.0f; + // unknown3 not initialized + ms_apPedType[i]->unknown4 = 0.0f; + ms_apPedType[i]->unknown5 = 0.0f; + ms_apPedType[i]->m_threats = 0; + ms_apPedType[i]->m_avoid = 0; + } + debug("Loading ped data...\n"); + LoadPedData(); + debug("CPedType ready\n"); +} + +void +CPedType::Shutdown(void) +{ + int i; + debug("Shutting down CPedType...\n"); + for(i = 0; i < NUM_PEDTYPES; i++) + delete ms_apPedType[i]; + debug("CPedType shut down\n"); +} + +void +CPedType::LoadPedData(void) +{ + char *buf; + char line[256]; + char word[32]; + ssize_t bp, buflen; + int lp, linelen; + int type; + uint32 flags; + float f1, f2, f3, f4, f5; + + type = NUM_PEDTYPES; + buf = new char[16 * 1024]; + + CFileMgr::SetDir("DATA"); + buflen = CFileMgr::LoadFile("PED.DAT", (uint8*)buf, 16 * 1024, "r"); + CFileMgr::SetDir(""); + + for(bp = 0; bp < buflen; ){ + // read file line by line + for(linelen = 0; buf[bp] != '\n' && bp < buflen; bp++){ + if(buf[bp] == '\r' || buf[bp] == ',' || buf[bp] == '\t') + line[linelen++] = ' '; + else + line[linelen++] = buf[bp]; + } + bp++; + line[linelen] = '\0'; + + // skip white space + for(lp = 0; line[lp] <= ' '; lp++); + + if(lp >= linelen || // FIX: game uses == here, but this is safer if we have empty lines + line[lp] == '#') + continue; + + // Game uses just "line" here since sscanf already trims whitespace, but this is safer + sscanf(&line[lp], "%s", word); + + if(strcmp(word, "Threat") == 0){ + flags = 0; + lp += 7; + while(sscanf(&line[lp], "%s", word) == 1 && lp <= linelen){ + flags |= FindPedFlag(word); + // skip word + while(line[lp] != ' ' && line[lp] != '\n' && line[lp] != '\0') + lp++; + // skip white space + while(line[lp] == ' ') + lp++; + } + ms_apPedType[type]->m_threats = flags; + }else if(strcmp(word, "Avoid") == 0){ + flags = 0; + lp += 6; + while(sscanf(&line[lp], "%s", word) == 1 && lp <= linelen){ + flags |= FindPedFlag(word); + // skip word + while(line[lp] != ' ' && line[lp] != '\n' && line[lp] != '\0') + lp++; + // skip white space + while(line[lp] == ' ') + lp++; + } + ms_apPedType[type]->m_avoid = flags; + }else{ + sscanf(line, "%s %f %f %f %f %f", word, &f1, &f2, &f3, &f4, &f5); + type = FindPedType(word); + ms_apPedType[type]->m_flag = FindPedFlag(word); + // unknown values + ms_apPedType[type]->unknown1 = f1 / 50.0f; + ms_apPedType[type]->unknown2 = f2 / 50.0f; + ms_apPedType[type]->unknown3 = f3 / 50.0f; + ms_apPedType[type]->unknown4 = f4; + ms_apPedType[type]->unknown5 = f5; + + } + } + + delete[] buf; +} + +ePedType +CPedType::FindPedType(char *type) +{ + if(strcmp(type, "PLAYER1") == 0) return PEDTYPE_PLAYER1; + if(strcmp(type, "PLAYER2") == 0) return PEDTYPE_PLAYER2; + if(strcmp(type, "PLAYER3") == 0) return PEDTYPE_PLAYER3; + if(strcmp(type, "PLAYER4") == 0) return PEDTYPE_PLAYER4; + if(strcmp(type, "CIVMALE") == 0) return PEDTYPE_CIVMALE; + if(strcmp(type, "CIVFEMALE") == 0) return PEDTYPE_CIVFEMALE; + if(strcmp(type, "COP") == 0) return PEDTYPE_COP; + if(strcmp(type, "GANG1") == 0) return PEDTYPE_GANG1; + if(strcmp(type, "GANG2") == 0) return PEDTYPE_GANG2; + if(strcmp(type, "GANG3") == 0) return PEDTYPE_GANG3; + if(strcmp(type, "GANG4") == 0) return PEDTYPE_GANG4; + if(strcmp(type, "GANG5") == 0) return PEDTYPE_GANG5; + if(strcmp(type, "GANG6") == 0) return PEDTYPE_GANG6; + if(strcmp(type, "GANG7") == 0) return PEDTYPE_GANG7; + if(strcmp(type, "GANG8") == 0) return PEDTYPE_GANG8; + if(strcmp(type, "GANG9") == 0) return PEDTYPE_GANG9; + if(strcmp(type, "EMERGENCY") == 0) return PEDTYPE_EMERGENCY; + if(strcmp(type, "FIREMAN") == 0) return PEDTYPE_FIREMAN; + if(strcmp(type, "CRIMINAL") == 0) return PEDTYPE_CRIMINAL; + if(strcmp(type, "SPECIAL") == 0) return PEDTYPE_SPECIAL; + if(strcmp(type, "PROSTITUTE") == 0) return PEDTYPE_PROSTITUTE; + Error("Unknown ped type, Pedtype.cpp"); + return NUM_PEDTYPES; +} + +uint32 +CPedType::FindPedFlag(char *type) +{ + if(strcmp(type, "PLAYER1") == 0) return PED_FLAG_PLAYER1; + if(strcmp(type, "PLAYER2") == 0) return PED_FLAG_PLAYER2; + if(strcmp(type, "PLAYER3") == 0) return PED_FLAG_PLAYER3; + if(strcmp(type, "PLAYER4") == 0) return PED_FLAG_PLAYER4; + if(strcmp(type, "CIVMALE") == 0) return PED_FLAG_CIVMALE; + if(strcmp(type, "CIVFEMALE") == 0) return PED_FLAG_CIVFEMALE; + if(strcmp(type, "COP") == 0) return PED_FLAG_COP; + if(strcmp(type, "GANG1") == 0) return PED_FLAG_GANG1; + if(strcmp(type, "GANG2") == 0) return PED_FLAG_GANG2; + if(strcmp(type, "GANG3") == 0) return PED_FLAG_GANG3; + if(strcmp(type, "GANG4") == 0) return PED_FLAG_GANG4; + if(strcmp(type, "GANG5") == 0) return PED_FLAG_GANG5; + if(strcmp(type, "GANG6") == 0) return PED_FLAG_GANG6; + if(strcmp(type, "GANG7") == 0) return PED_FLAG_GANG7; + if(strcmp(type, "GANG8") == 0) return PED_FLAG_GANG8; + if(strcmp(type, "GANG9") == 0) return PED_FLAG_GANG9; + if(strcmp(type, "EMERGENCY") == 0) return PED_FLAG_EMERGENCY; + if(strcmp(type, "FIREMAN") == 0) return PED_FLAG_FIREMAN; + if(strcmp(type, "CRIMINAL") == 0) return PED_FLAG_CRIMINAL; + if(strcmp(type, "SPECIAL") == 0) return PED_FLAG_SPECIAL; + if(strcmp(type, "GUN") == 0) return PED_FLAG_GUN; + if(strcmp(type, "COP_CAR") == 0) return PED_FLAG_COP_CAR; + if(strcmp(type, "FAST_CAR") == 0) return PED_FLAG_FAST_CAR; + if(strcmp(type, "EXPLOSION") == 0) return PED_FLAG_EXPLOSION; + if(strcmp(type, "PROSTITUTE") == 0) return PED_FLAG_PROSTITUTE; + if(strcmp(type, "DEADPEDS") == 0) return PED_FLAG_DEADPEDS; + return 0; +} + +void +CPedType::Save(uint8 *buf, uint32 *size) +{ + *size = sizeof(CPedType) * NUM_PEDTYPES + SAVE_HEADER_SIZE; +INITSAVEBUF + WriteSaveHeader(buf, 'P','T','P','\0', *size - SAVE_HEADER_SIZE); + for(int i = 0; i < NUM_PEDTYPES; i++) + WriteSaveBuf(buf, *ms_apPedType[i]); +VALIDATESAVEBUF(*size) +} + +void +CPedType::Load(uint8 *buf, uint32 size) +{ +INITSAVEBUF + // original: SkipSaveBuf(buf, SAVE_HEADER_SIZE); + CheckSaveHeader(buf, 'P', 'T', 'P', '\0', size - SAVE_HEADER_SIZE); + + for(int i = 0; i < NUM_PEDTYPES; i++) + ReadSaveBuf(ms_apPedType[i], buf); +VALIDATESAVEBUF(size) +} + +void +CPedStats::Initialise(void) +{ + int i; + + debug("Initialising CPedStats...\n"); + for(i = 0; i < NUM_PEDSTATS; i++){ + ms_apPedStats[i] = new CPedStats; + ms_apPedStats[i]->m_type = PEDSTAT_PLAYER; + ms_apPedStats[i]->m_name[8] = 'R'; // WHAT? + ms_apPedStats[i]->m_fleeDistance = 20.0f; + ms_apPedStats[i]->m_headingChangeRate = 15.0f; + ms_apPedStats[i]->m_fear = 50; + ms_apPedStats[i]->m_temper = 50; + ms_apPedStats[i]->m_lawfulness = 50; + ms_apPedStats[i]->m_sexiness = 50; + ms_apPedStats[i]->m_attackStrength = 1.0f; + ms_apPedStats[i]->m_defendWeakness = 1.0f; + ms_apPedStats[i]->m_flags = 0; + } + debug("Loading pedstats data...\n"); + CPedStats::LoadPedStats(); + debug("CPedStats ready\n"); +} + +void +CPedStats::Shutdown(void) +{ + int i; + debug("Shutting down CPedStats...\n"); + for(i = 0; i < NUM_PEDSTATS; i++) + delete ms_apPedStats[i]; + debug("CPedStats shut down\n"); +} + +void +CPedStats::LoadPedStats(void) +{ + char *buf; + char line[256]; + char name[32]; + ssize_t bp, buflen; + int lp, linelen; + int type; + float fleeDist, headingChangeRate, attackStrength, defendWeakness; + int fear, temper, lawfullness, sexiness, flags; + + type = 0; + buf = new char[16 * 1024]; + + CFileMgr::SetDir("DATA"); + buflen = CFileMgr::LoadFile("PEDSTATS.DAT", (uint8*)buf, 16 * 1024, "r"); + CFileMgr::SetDir(""); + + for(bp = 0; bp < buflen; ){ + // read file line by line + for(linelen = 0; buf[bp] != '\n' && bp < buflen; bp++){ + if(buf[bp] == '\r' || buf[bp] == ',' || buf[bp] == '\t') + line[linelen++] = ' '; + else + line[linelen++] = buf[bp]; + } + bp++; + line[linelen] = '\0'; + + // skip white space + for(lp = 0; line[lp] <= ' '; lp++); + + if(lp >= linelen || // FIX: game uses == here, but this is safer if we have empty lines + line[lp] == '#') + continue; + + sscanf(&line[lp], "%s %f %f %d %d %d %d %f %f %d", + name, + &fleeDist, + &headingChangeRate, + &fear, + &temper, + &lawfullness, + &sexiness, + &attackStrength, + &defendWeakness, + &flags); + ms_apPedStats[type]->m_type = (ePedStats)type; + strncpy(ms_apPedStats[type]->m_name, name, 24); // FIX: game uses strcpy + ms_apPedStats[type]->m_fleeDistance = fleeDist; + ms_apPedStats[type]->m_headingChangeRate = headingChangeRate; + ms_apPedStats[type]->m_fear = fear; + ms_apPedStats[type]->m_temper = temper; + ms_apPedStats[type]->m_lawfulness = lawfullness; + ms_apPedStats[type]->m_sexiness = sexiness; + ms_apPedStats[type]->m_attackStrength = attackStrength; + ms_apPedStats[type]->m_defendWeakness = defendWeakness; + ms_apPedStats[type]->m_flags = flags; + type++; + } + + delete[] buf; +} + +ePedStats +CPedStats::GetPedStatType(char *name) +{ + for(uint16 type = 0; type < NUM_PEDSTATS; type++) + if(!CGeneral::faststrcmp(ms_apPedStats[type]->m_name, name)) + return (ePedStats) type; + + return NUM_PEDSTATS; +} diff --git a/src/peds/PedType.h b/src/peds/PedType.h new file mode 100644 index 0000000..3e23c24 --- /dev/null +++ b/src/peds/PedType.h @@ -0,0 +1,173 @@ +#pragma once + +// Index into the PedType array +enum ePedType +{ + PEDTYPE_PLAYER1, + PEDTYPE_PLAYER2, + PEDTYPE_PLAYER3, + PEDTYPE_PLAYER4, + PEDTYPE_CIVMALE, + PEDTYPE_CIVFEMALE, + PEDTYPE_COP, + PEDTYPE_GANG1, + PEDTYPE_GANG2, + PEDTYPE_GANG3, + PEDTYPE_GANG4, + PEDTYPE_GANG5, + PEDTYPE_GANG6, + PEDTYPE_GANG7, + PEDTYPE_GANG8, + PEDTYPE_GANG9, + PEDTYPE_EMERGENCY, + PEDTYPE_FIREMAN, + PEDTYPE_CRIMINAL, + PEDTYPE_UNUSED1, + PEDTYPE_PROSTITUTE, + PEDTYPE_SPECIAL, + PEDTYPE_UNUSED2, + + NUM_PEDTYPES +}; + +enum +{ + PED_FLAG_PLAYER1 = 1 << 0, + PED_FLAG_PLAYER2 = 1 << 1, + PED_FLAG_PLAYER3 = 1 << 2, + PED_FLAG_PLAYER4 = 1 << 3, + PED_FLAG_CIVMALE = 1 << 4, + PED_FLAG_CIVFEMALE = 1 << 5, + PED_FLAG_COP = 1 << 6, + PED_FLAG_GANG1 = 1 << 7, + PED_FLAG_GANG2 = 1 << 8, + PED_FLAG_GANG3 = 1 << 9, + PED_FLAG_GANG4 = 1 << 10, + PED_FLAG_GANG5 = 1 << 11, + PED_FLAG_GANG6 = 1 << 12, + PED_FLAG_GANG7 = 1 << 13, + PED_FLAG_GANG8 = 1 << 14, + PED_FLAG_GANG9 = 1 << 15, + PED_FLAG_EMERGENCY = 1 << 16, + PED_FLAG_PROSTITUTE = 1 << 17, + PED_FLAG_CRIMINAL = 1 << 18, + PED_FLAG_SPECIAL = 1 << 19, + PED_FLAG_GUN = 1 << 20, + PED_FLAG_COP_CAR = 1 << 21, + PED_FLAG_FAST_CAR = 1 << 22, + PED_FLAG_EXPLOSION = 1 << 23, + PED_FLAG_FIREMAN = 1 << 24, + PED_FLAG_DEADPEDS = 1 << 25, +}; + +class CPedType +{ + uint32 m_flag; + float unknown1; + float unknown2; + float unknown3; + float unknown4; + float unknown5; + uint32 m_threats; + uint32 m_avoid; + + static CPedType *ms_apPedType[NUM_PEDTYPES]; +public: + + static void Initialise(void); + static void Shutdown(void); + static void LoadPedData(void); + static ePedType FindPedType(char *type); + static uint32 FindPedFlag(char *type); + static void Save(uint8 *buf, uint32 *size); + static void Load(uint8 *buf, uint32 size); + + static uint32 GetFlag(int type) { return ms_apPedType[type]->m_flag; } + static uint32 GetAvoid(int type) { return ms_apPedType[type]->m_avoid; } + static uint32 GetThreats(int type) { return ms_apPedType[type]->m_threats; } + static void SetThreats(int type, uint32 threat) { ms_apPedType[type]->m_threats = threat; } + static void AddThreat(int type, int threat) { ms_apPedType[type]->m_threats |= threat; } + static void RemoveThreat(int type, int threat) { ms_apPedType[type]->m_threats &= ~threat; } + static bool IsThreat(int type, int threat) { return ms_apPedType[type]->m_threats & threat; } +}; + +VALIDATE_SIZE(CPedType, 0x20); + +enum ePedStats +{ + PEDSTAT_PLAYER, + PEDSTAT_COP, + PEDSTAT_MEDIC, + PEDSTAT_FIREMAN, + PEDSTAT_GANG1, + PEDSTAT_GANG2, + PEDSTAT_GANG3, + PEDSTAT_GANG4, + PEDSTAT_GANG5, + PEDSTAT_GANG6, + PEDSTAT_GANG7, + PEDSTAT_STREET_GUY, + PEDSTAT_SUIT_GUY, + PEDSTAT_SENSIBLE_GUY, + PEDSTAT_GEEK_GUY, + PEDSTAT_OLD_GUY, + PEDSTAT_TOUGH_GUY, + PEDSTAT_STREET_GIRL, + PEDSTAT_SUIT_GIRL, + PEDSTAT_SENSIBLE_GIRL, + PEDSTAT_GEEK_GIRL, + PEDSTAT_OLD_GIRL, + PEDSTAT_TOUGH_GIRL, + PEDSTAT_TRAMP_MALE, + PEDSTAT_TRAMP_FEMALE, + PEDSTAT_TOURIST, + PEDSTAT_PROSTITUTE, + PEDSTAT_CRIMINAL, + PEDSTAT_BUSKER, + PEDSTAT_TAXIDRIVER, + PEDSTAT_PSYCHO, + PEDSTAT_STEWARD, + PEDSTAT_SPORTSFAN, + PEDSTAT_SHOPPER, + PEDSTAT_OLDSHOPPER, + + NUM_PEDSTATS +}; + +// flags +enum +{ + STAT_PUNCH_ONLY = 1, + STAT_CAN_KNEE_HEAD = 2, + STAT_CAN_KICK = 4, + STAT_CAN_ROUNDHOUSE = 8, + STAT_NO_DIVE = 0x10, + STAT_ONE_HIT_KNOCKDOWN = 0x20, + STAT_SHOPPING_BAGS = 0x40, + STAT_GUN_PANIC = 0x80 +}; + +class CPedStats +{ +public: + ePedStats m_type; + char m_name[24]; + float m_fleeDistance; + float m_headingChangeRate; + int8 m_fear; + int8 m_temper; + int8 m_lawfulness; + int8 m_sexiness; + float m_attackStrength; + float m_defendWeakness; + int16 m_flags; + + static CPedStats *ms_apPedStats[NUM_PEDSTATS]; + + static void Initialise(void); + static void Shutdown(void); + static void LoadPedStats(void); + static ePedStats GetPedStatType(char *name); +}; + +VALIDATE_SIZE(CPedStats, 0x34); \ No newline at end of file diff --git a/src/peds/PlayerPed.cpp b/src/peds/PlayerPed.cpp new file mode 100644 index 0000000..ef87796 --- /dev/null +++ b/src/peds/PlayerPed.cpp @@ -0,0 +1,1581 @@ +#include "common.h" + +#include "RwHelper.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "Fire.h" +#include "DMAudio.h" +#include "Pad.h" +#include "Camera.h" +#include "WeaponEffects.h" +#include "ModelIndices.h" +#include "World.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "General.h" +#include "Pools.h" +#include "Darkel.h" +#include "CarCtrl.h" +#include "SaveBuf.h" + +#define PAD_MOVE_TO_GAME_WORLD_MOVE 60.0f + +#ifdef VC_PED_PORTS +bool CPlayerPed::bDontAllowWeaponChange; +#endif + +const uint32 CPlayerPed::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1520; +#else + sizeof(CPlayerPed); +#endif + +CPlayerPed::~CPlayerPed() +{ + delete m_pWanted; +} + +CPlayerPed::CPlayerPed(void) : CPed(PEDTYPE_PLAYER1) +{ + m_fMoveSpeed = 0.0f; + SetModelIndex(MI_PLAYER); +#ifdef FIX_BUGS + m_fCurrentStamina = m_fMaxStamina = 150.0f; +#endif + SetInitialState(); + + m_pWanted = new CWanted(); + m_pWanted->Initialise(); + m_pArrestingCop = nil; + m_currentWeapon = WEAPONTYPE_UNARMED; + m_nSelectedWepSlot = WEAPONTYPE_UNARMED; + m_nSpeedTimer = 0; + m_bSpeedTimerFlag = false; + SetWeaponLockOnTarget(nil); + SetPedState(PED_IDLE); +#ifndef FIX_BUGS + m_fCurrentStamina = m_fMaxStamina = 150.0f; +#endif + m_fStaminaProgress = 0.0f; + m_nEvadeAmount = 0; + field_1367 = 0; + m_nHitAnimDelayTimer = 0; + m_fAttackButtonCounter = 0.0f; + m_bHaveTargetSelected = false; + m_bHasLockOnTarget = false; + m_bCanBeDamaged = true; + m_fWalkAngle = 0.0f; + m_fFPSMoveHeading = 0.0f; + m_nTargettableObjects[0] = m_nTargettableObjects[1] = m_nTargettableObjects[2] = m_nTargettableObjects[3] = -1; + field_1413 = 0; + for (int i = 0; i < 6; i++) { + m_vecSafePos[i] = CVector(0.0f, 0.0f, 0.0f); + m_pPedAtSafePos[i] = nil; + } +} + +void CPlayerPed::ClearWeaponTarget() +{ + if (m_nPedType == PEDTYPE_PLAYER1) { + SetWeaponLockOnTarget(nil); + TheCamera.ClearPlayerWeaponMode(); + CWeaponEffects::ClearCrossHair(); + } + ClearPointGunAt(); +} + +void +CPlayerPed::SetWantedLevel(int32 level) +{ + m_pWanted->SetWantedLevel(level); +} + +void +CPlayerPed::SetWantedLevelNoDrop(int32 level) +{ + m_pWanted->SetWantedLevelNoDrop(level); +} + +void +CPlayerPed::MakeObjectTargettable(int32 handle) +{ + for (int i = 0; i < ARRAY_SIZE(m_nTargettableObjects); i++) { + if ( +#ifdef FIX_BUGS + m_nTargettableObjects[i] == -1 || +#endif + CPools::GetObjectPool()->GetAt(m_nTargettableObjects[i]) == nil) { + m_nTargettableObjects[i] = handle; + return; + } + } +} + +// I don't know the actual purpose of parameter +void +CPlayerPed::AnnoyPlayerPed(bool annoyedByPassingEntity) +{ + if (m_pedStats->m_temper < 52) { + m_pedStats->m_temper++; + } else if (annoyedByPassingEntity && m_pedStats->m_temper < 55) { + m_pedStats->m_temper++; + } else if (annoyedByPassingEntity) { + m_pedStats->m_temper = 46; + } +} + +void +CPlayerPed::ClearAdrenaline(void) +{ + if (m_bAdrenalineActive && m_nAdrenalineTime != 0) { + m_nAdrenalineTime = 0; + CTimer::SetTimeScale(1.0f); + } +} + +CPlayerInfo * +CPlayerPed::GetPlayerInfoForThisPlayerPed() +{ + if (CWorld::Players[0].m_pPed == this) + return &CWorld::Players[0]; + + return nil; +} + +void +CPlayerPed::SetupPlayerPed(int32 index) +{ + CPlayerPed *player = new CPlayerPed(); + CWorld::Players[index].m_pPed = player; +#ifdef FIX_BUGS + player->RegisterReference((CEntity**)&CWorld::Players[index].m_pPed); +#endif + + player->SetOrientation(0.0f, 0.0f, 0.0f); + + CWorld::Add(player); + player->m_wepAccuracy = 100; +} + +void +CPlayerPed::DeactivatePlayerPed(int32 index) +{ + CWorld::Remove(CWorld::Players[index].m_pPed); +} + +void +CPlayerPed::ReactivatePlayerPed(int32 index) +{ + CWorld::Add(CWorld::Players[index].m_pPed); +} + +void +CPlayerPed::UseSprintEnergy(void) +{ + if (m_fCurrentStamina > -150.0f && !CWorld::Players[CWorld::PlayerInFocus].m_bInfiniteSprint + && !m_bAdrenalineActive) { + m_fCurrentStamina = m_fCurrentStamina - CTimer::GetTimeStep(); + m_fStaminaProgress = m_fStaminaProgress + CTimer::GetTimeStep(); + } + + if (m_fStaminaProgress >= 500.0f) { + m_fStaminaProgress = 0; + if (m_fMaxStamina < 1000.0f) + m_fMaxStamina += 10.0f; + } +} + +void +CPlayerPed::MakeChangesForNewWeapon(int8 weapon) +{ + if (m_nPedState == PED_SNIPER_MODE) { + RestorePreviousState(); + TheCamera.ClearPlayerWeaponMode(); + } + SetCurrentWeapon(weapon); + + GetWeapon()->m_nAmmoInClip = Min(GetWeapon()->m_nAmmoTotal, CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_nAmountofAmmunition); + + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM)) + ClearWeaponTarget(); + + CAnimBlendAssociation *weaponAnim = RpAnimBlendClumpGetAssociation(GetClump(), CWeaponInfo::GetWeaponInfo(WEAPONTYPE_SNIPERRIFLE)->m_AnimToPlay); + if (weaponAnim) { + weaponAnim->SetRun(); + weaponAnim->flags |= ASSOC_FADEOUTWHENDONE; + } + TheCamera.ClearPlayerWeaponMode(); +} + +void +CPlayerPed::ReApplyMoveAnims(void) +{ + static AnimationId moveAnims[] = { ANIM_STD_WALK, ANIM_STD_RUN, ANIM_STD_RUNFAST, ANIM_STD_IDLE, ANIM_STD_STARTWALK }; + + for(int i = 0; i < ARRAY_SIZE(moveAnims); i++) { + CAnimBlendAssociation *curMoveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), moveAnims[i]); + if (curMoveAssoc) { + if (CGeneral::faststrcmp(CAnimManager::GetAnimAssociation(m_animGroup, moveAnims[i])->hierarchy->name, curMoveAssoc->hierarchy->name)) { + CAnimBlendAssociation *newMoveAssoc = CAnimManager::AddAnimation(GetClump(), m_animGroup, moveAnims[i]); + newMoveAssoc->blendDelta = curMoveAssoc->blendDelta; + newMoveAssoc->blendAmount = curMoveAssoc->blendAmount; + curMoveAssoc->blendDelta = -1000.0f; + curMoveAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + } + } +} + +void +CPlayerPed::SetInitialState(void) +{ + m_bAdrenalineActive = false; + m_nAdrenalineTime = 0; + CTimer::SetTimeScale(1.0f); + m_pSeekTarget = nil; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_fleeFromPosX = 0.0f; + m_fleeFromPosY = 0.0f; + m_fleeFrom = nil; + m_fleeTimer = 0; + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; + bUsesCollision = true; + ClearAimFlag(); + ClearLookFlag(); + bIsPointingGunAt = false; + bRenderPedInCar = true; + if (m_pFire) + m_pFire->Extinguish(); + RpAnimBlendClumpRemoveAllAssociations(GetClump()); + SetPedState(PED_IDLE); + SetMoveState(PEDMOVE_STILL); + m_nLastPedState = PED_NONE; + m_animGroup = ASSOCGRP_PLAYER; + m_fMoveSpeed = 0.0f; + m_nSelectedWepSlot = WEAPONTYPE_UNARMED; + m_nEvadeAmount = 0; + m_pEvadingFrom = nil; + bIsPedDieAnimPlaying = false; + SetRealMoveAnim(); + m_bCanBeDamaged = true; + m_pedStats->m_temper = 50; + m_fWalkAngle = 0.0f; +} + +void +CPlayerPed::SetRealMoveAnim(void) +{ + CAnimBlendAssociation *curWalkAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_WALK); + CAnimBlendAssociation *curRunAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUN); + CAnimBlendAssociation *curSprintAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNFAST); + CAnimBlendAssociation *curWalkStartAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_STARTWALK); + CAnimBlendAssociation *curIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE); + CAnimBlendAssociation *curRunStopAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP1); + CAnimBlendAssociation *curRunStopRAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + if (bResetWalkAnims) { + if (curWalkAssoc) + curWalkAssoc->SetCurrentTime(0.0f); + if (curRunAssoc) + curRunAssoc->SetCurrentTime(0.0f); + if (curSprintAssoc) + curSprintAssoc->SetCurrentTime(0.0f); + bResetWalkAnims = false; + } + + if (!curIdleAssoc) + curIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + if (!curIdleAssoc) + curIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + + if (!((curRunStopAssoc && curRunStopAssoc->IsRunning()) || (curRunStopRAssoc && curRunStopRAssoc->IsRunning()))) { + + if (curRunStopAssoc && curRunStopAssoc->blendDelta >= 0.0f || curRunStopRAssoc && curRunStopRAssoc->blendDelta >= 0.0f) { + if (curRunStopAssoc) { + curRunStopAssoc->flags |= ASSOC_DELETEFADEDOUT; + curRunStopAssoc->blendAmount = 1.0f; + curRunStopAssoc->blendDelta = -8.0f; + } else if (curRunStopRAssoc) { + curRunStopRAssoc->flags |= ASSOC_DELETEFADEDOUT; + curRunStopRAssoc->blendAmount = 1.0f; + curRunStopRAssoc->blendDelta = -8.0f; + } + + RestoreHeadingRate(); + if (!curIdleAssoc) { + if (m_fCurrentStamina < 0.0f && !CWorld::TestSphereAgainstWorld(GetPosition(), 0.5f, + nil, true, false, false, false, false, false)) { + curIdleAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 8.0f); + + } else { + curIdleAssoc = CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 8.0f); + } + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(2500, 4000); + } + curIdleAssoc->blendAmount = 0.0f; + curIdleAssoc->blendDelta = 8.0f; + + } else if (m_fMoveSpeed == 0.0f && !curSprintAssoc) { + if (!curIdleAssoc) { + if (m_fCurrentStamina < 0.0f && !CWorld::TestSphereAgainstWorld(GetPosition(), 0.5f, + nil, true, false, false, false, false, false)) { + curIdleAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 4.0f); + + } else { + curIdleAssoc = CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + } + + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(2500, 4000); + } + + if (m_fCurrentStamina > 0.0f && curIdleAssoc->animId == ANIM_STD_IDLE_TIRED) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + + } else if (m_nPedState != PED_FIGHT) { + if (m_fCurrentStamina < 0.0f && curIdleAssoc->animId != ANIM_STD_IDLE_TIRED + && !CWorld::TestSphereAgainstWorld(GetPosition(), 0.5f, nil, true, false, false, false, false, false)) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE_TIRED, 4.0f); + + } else if (curIdleAssoc->animId != ANIM_STD_IDLE) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 4.0f); + } + } + m_nMoveState = PEDMOVE_STILL; + + } else { + if (curIdleAssoc) { + if (curWalkStartAssoc) { + curWalkStartAssoc->blendAmount = 1.0f; + curWalkStartAssoc->blendDelta = 0.0f; + } else { + curWalkStartAssoc = CAnimManager::AddAnimation(GetClump(), m_animGroup, ANIM_STD_STARTWALK); + } + if (curWalkAssoc) + curWalkAssoc->SetCurrentTime(0.0f); + if (curRunAssoc) + curRunAssoc->SetCurrentTime(0.0f); + + delete curIdleAssoc; + delete RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + delete RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + delete curSprintAssoc; + + curSprintAssoc = nil; + m_nMoveState = PEDMOVE_WALK; + } + if (curRunStopAssoc) { + delete curRunStopAssoc; + RestoreHeadingRate(); + } + if (curRunStopRAssoc) { + delete curRunStopRAssoc; + RestoreHeadingRate(); + } + if (!curWalkAssoc) { + curWalkAssoc = CAnimManager::AddAnimation(GetClump(), m_animGroup, ANIM_STD_WALK); + curWalkAssoc->blendAmount = 0.0f; + } + if (!curRunAssoc) { + curRunAssoc = CAnimManager::AddAnimation(GetClump(), m_animGroup, ANIM_STD_RUN); + curRunAssoc->blendAmount = 0.0f; + } + if (curWalkStartAssoc && !(curWalkStartAssoc->IsRunning())) { + delete curWalkStartAssoc; + curWalkStartAssoc = nil; + curWalkAssoc->SetRun(); + curRunAssoc->SetRun(); + } + if (m_nMoveState == PEDMOVE_SPRINT) { + if (m_fCurrentStamina < 0.0f && (m_fCurrentStamina <= -150.0f || !curSprintAssoc || curSprintAssoc->blendDelta < 0.0f)) + m_nMoveState = PEDMOVE_STILL; + + if (curWalkStartAssoc) + m_nMoveState = PEDMOVE_STILL; + } + + if (curSprintAssoc && (m_nMoveState != PEDMOVE_SPRINT || m_fMoveSpeed < 0.4f)) { + // Stop sprinting in various conditions + if (curSprintAssoc->blendAmount == 0.0f) { + curSprintAssoc->blendDelta = -1000.0f; + curSprintAssoc->flags |= ASSOC_DELETEFADEDOUT; + + } else if (curSprintAssoc->blendDelta >= 0.0f || curSprintAssoc->blendAmount >= 0.8f) { + if (m_fMoveSpeed < 0.4f) { + AnimationId runStopAnim; + if (curSprintAssoc->currentTime / curSprintAssoc->hierarchy->totalLength < 0.5) // double + runStopAnim = ANIM_STD_RUNSTOP1; + else + runStopAnim = ANIM_STD_RUNSTOP2; + CAnimBlendAssociation* newRunStopAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, runStopAnim); + newRunStopAssoc->blendAmount = 1.0f; + newRunStopAssoc->SetDeleteCallback(RestoreHeadingRateCB, this); + m_headingRate = 0.0f; + curSprintAssoc->flags |= ASSOC_DELETEFADEDOUT; + curSprintAssoc->blendDelta = -1000.0f; + curWalkAssoc->flags &= ~ASSOC_RUNNING; + curWalkAssoc->blendAmount = 0.0f; + curWalkAssoc->blendDelta = 0.0f; + curRunAssoc->flags &= ~ASSOC_RUNNING; + curRunAssoc->blendAmount = 0.0f; + curRunAssoc->blendDelta = 0.0f; + + } else if (curSprintAssoc->blendDelta >= 0.0f) { + // Stop sprinting when tired + curSprintAssoc->flags |= ASSOC_DELETEFADEDOUT; + curSprintAssoc->blendDelta = -1.0f; + curRunAssoc->blendDelta = 1.0f; + } + } else if (m_fMoveSpeed < 1.0f) { + curSprintAssoc->blendDelta = -8.0f; + curRunAssoc->blendDelta = 8.0f; + } + + } else if (curWalkStartAssoc) { + // Walk start and walk/run shouldn't run at the same time + curWalkAssoc->flags &= ~ASSOC_RUNNING; + curRunAssoc->flags &= ~ASSOC_RUNNING; + curWalkAssoc->blendAmount = 0.0f; + curRunAssoc->blendAmount = 0.0f; + + } else if (m_nMoveState == PEDMOVE_SPRINT) { + if (curSprintAssoc) { + // We have anim, do it + if (curSprintAssoc->blendDelta < 0.0f) { + curSprintAssoc->blendDelta = 2.0f; + curRunAssoc->blendDelta = -2.0f; + } + } else { + // Transition between run-sprint + curWalkAssoc->blendAmount = 0.0f; + curRunAssoc->blendAmount = 1.0f; + curSprintAssoc = CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_RUNFAST, 2.0f); + } + UseSprintEnergy(); + } else { + if (m_fMoveSpeed < 1.0f) { + curWalkAssoc->blendAmount = 1.0f; + curRunAssoc->blendAmount = 0.0f; + m_nMoveState = PEDMOVE_WALK; + } else if (m_fMoveSpeed < 2.0f) { + curWalkAssoc->blendAmount = 2.0f - m_fMoveSpeed; + curRunAssoc->blendAmount = m_fMoveSpeed - 1.0f; + m_nMoveState = PEDMOVE_RUN; + } else { + curWalkAssoc->blendAmount = 0.0f; + curRunAssoc->blendAmount = 1.0f; + m_nMoveState = PEDMOVE_RUN; + } + } + } + } + if (m_bAdrenalineActive) { + if (CTimer::GetTimeInMilliseconds() > m_nAdrenalineTime) { + m_bAdrenalineActive = false; + CTimer::SetTimeScale(1.0f); + if (curWalkStartAssoc) + curWalkStartAssoc->speed = 1.0f; + if (curWalkAssoc) + curWalkAssoc->speed = 1.0f; + if (curRunAssoc) + curRunAssoc->speed = 1.0f; + if (curSprintAssoc) + curSprintAssoc->speed = 1.0f; + } else { + CTimer::SetTimeScale(1.0f / 3); + if (curWalkStartAssoc) + curWalkStartAssoc->speed = 2.0f; + if (curWalkAssoc) + curWalkAssoc->speed = 2.0f; + if (curRunAssoc) + curRunAssoc->speed = 2.0f; + if (curSprintAssoc) + curSprintAssoc->speed = 2.0f; + } + } +} + +void +CPlayerPed::RestoreSprintEnergy(float restoreSpeed) +{ + if (m_fCurrentStamina < m_fMaxStamina) + m_fCurrentStamina += restoreSpeed * CTimer::GetTimeStep() * 0.5f; +} + +bool +CPlayerPed::DoWeaponSmoothSpray(void) +{ + if (m_nPedState == PED_ATTACK && !m_pPointGunAt) { + eWeaponType weapon = GetWeapon()->m_eWeaponType; +#ifdef FREE_CAM + if(CCamera::bFreeCam && TheCamera.Cams[0].Using3rdPersonMouseCam() && (weapon == WEAPONTYPE_COLT45 || weapon == WEAPONTYPE_UZI)) + return false; +#endif + if (weapon == WEAPONTYPE_FLAMETHROWER || weapon == WEAPONTYPE_COLT45 || weapon == WEAPONTYPE_UZI || weapon == WEAPONTYPE_SHOTGUN || + weapon == WEAPONTYPE_AK47 || weapon == WEAPONTYPE_M16 || weapon == WEAPONTYPE_HELICANNON) + return true; + } + return false; +} + +void +CPlayerPed::DoStuffToGoOnFire(void) +{ + if (m_nPedState == PED_SNIPER_MODE) + TheCamera.ClearPlayerWeaponMode(); +} + +bool +CPlayerPed::DoesTargetHaveToBeBroken(CVector target, CWeapon *weaponUsed) +{ + CVector distVec = target - GetPosition(); + + if (distVec.Magnitude() > CWeaponInfo::GetWeaponInfo(weaponUsed->m_eWeaponType)->m_fRange) + return true; + + if (weaponUsed->m_eWeaponType != WEAPONTYPE_SHOTGUN && weaponUsed->m_eWeaponType != WEAPONTYPE_AK47) + return false; + + distVec.Normalise(); + + if (DotProduct(distVec,GetForward()) < 0.4f) + return true; + + return false; +} + +// Cancels landing anim while running & jumping? I think +void +CPlayerPed::RunningLand(CPad *padUsed) +{ + CAnimBlendAssociation *landAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_LAND); + if (landAssoc && landAssoc->currentTime == 0.0f && m_fMoveSpeed > 1.5f + && padUsed && (padUsed->GetPedWalkLeftRight() != 0.0f || padUsed->GetPedWalkUpDown() != 0.0f)) { + + landAssoc->blendDelta = -1000.0f; + landAssoc->flags |= ASSOC_DELETEFADEDOUT; + + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_JUMP_LAND)->SetFinishCallback(FinishJumpCB, this); + + if (m_nPedState == PED_JUMP) + RestorePreviousState(); + } +} + +bool +CPlayerPed::IsThisPedAttackingPlayer(CPed *suspect) +{ + if (suspect->m_pPointGunAt == this) + return true; + + switch (suspect->m_objective) { + case OBJECTIVE_KILL_CHAR_ON_FOOT: + case OBJECTIVE_KILL_CHAR_ANY_MEANS: + if (suspect->m_pedInObjective == this) + return true; + + break; + default: + break; + } + return false; +} + +void +CPlayerPed::PlayerControlSniper(CPad *padUsed) +{ + ProcessWeaponSwitch(padUsed); + TheCamera.PlayerExhaustion = (1.0f - (m_fCurrentStamina - -150.0f) / 300.0f) * 0.9f + 0.1f; + + if (!padUsed->GetTarget()) { + RestorePreviousState(); + TheCamera.ClearPlayerWeaponMode(); + } + + if (padUsed->WeaponJustDown()) { + CVector firePos(0.0f, 0.0f, 0.6f); + firePos = GetMatrix() * firePos; + GetWeapon()->Fire(this, &firePos); + } + GetWeapon()->Update(m_audioEntityId); +} + +// I think R* also used goto in here. +void +CPlayerPed::ProcessWeaponSwitch(CPad *padUsed) +{ + if (CDarkel::FrenzyOnGoing()) + goto switchDetectDone; + +#ifdef VC_PED_PORTS + if (padUsed->CycleWeaponRightJustDown() && !m_pPointGunAt && !bDontAllowWeaponChange) { +#else + if (padUsed->CycleWeaponRightJustDown() && !m_pPointGunAt) { +#endif + + if (TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON_RUNABOUT + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER_RUNABOUT + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_ROCKETLAUNCHER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_ROCKETLAUNCHER_RUNABOUT) { + + for (m_nSelectedWepSlot = m_currentWeapon + 1; m_nSelectedWepSlot < WEAPONTYPE_TOTAL_INVENTORY_WEAPONS; ++m_nSelectedWepSlot) { + if (HasWeapon(m_nSelectedWepSlot) && GetWeapon(m_nSelectedWepSlot).HasWeaponAmmoToBeUsed()) { + goto switchDetectDone; + } + } + m_nSelectedWepSlot = WEAPONTYPE_UNARMED; + } +#ifdef VC_PED_PORTS + } else if (padUsed->CycleWeaponLeftJustDown() && !m_pPointGunAt && !bDontAllowWeaponChange) { +#else + } else if (padUsed->CycleWeaponLeftJustDown() && !m_pPointGunAt) { +#endif + if (TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_ROCKETLAUNCHER) { + + for (m_nSelectedWepSlot = m_currentWeapon - 1; ; --m_nSelectedWepSlot) { + if (m_nSelectedWepSlot < WEAPONTYPE_UNARMED) + m_nSelectedWepSlot = WEAPONTYPE_DETONATOR; + + if (HasWeapon(m_nSelectedWepSlot) && GetWeapon(m_nSelectedWepSlot).HasWeaponAmmoToBeUsed()) { + goto switchDetectDone; + } + } + } + // Out of ammo, switch to another weapon + } else if (CWeaponInfo::GetWeaponInfo((eWeaponType)m_currentWeapon)->m_eWeaponFire != WEAPON_FIRE_MELEE) { + if (GetWeapon(m_currentWeapon).m_nAmmoTotal <= 0) { + if (TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON + || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_SNIPER + || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_ROCKETLAUNCHER) + return; + + for (m_nSelectedWepSlot = m_currentWeapon - 1; m_nSelectedWepSlot >= 0; --m_nSelectedWepSlot) { + if (m_nSelectedWepSlot == WEAPONTYPE_BASEBALLBAT && HasWeapon(WEAPONTYPE_BASEBALLBAT) + || GetWeapon(m_nSelectedWepSlot).m_nAmmoTotal > 0 && m_nSelectedWepSlot != WEAPONTYPE_MOLOTOV && m_nSelectedWepSlot != WEAPONTYPE_GRENADE) { + goto switchDetectDone; + } + } + m_nSelectedWepSlot = WEAPONTYPE_UNARMED; + } + } + +switchDetectDone: + if (m_nSelectedWepSlot != m_currentWeapon) { + if (m_nPedState != PED_ATTACK && m_nPedState != PED_AIM_GUN && m_nPedState != PED_FIGHT) + MakeChangesForNewWeapon(m_nSelectedWepSlot); + } +} + +void +CPlayerPed::PlayerControlM16(CPad *padUsed) +{ + ProcessWeaponSwitch(padUsed); + TheCamera.PlayerExhaustion = (1.0f - (m_fCurrentStamina - -150.0f) / 300.0f) * 0.9f + 0.1f; + + if (!padUsed->GetTarget()) { + RestorePreviousState(); + TheCamera.ClearPlayerWeaponMode(); + } + + if (padUsed->GetWeapon()) { + CVector firePos(0.0f, 0.0f, 0.6f); + firePos = GetMatrix() * firePos; + GetWeapon()->Fire(this, &firePos); + } + GetWeapon()->Update(m_audioEntityId); +} + +void +CPlayerPed::PlayerControlFighter(CPad *padUsed) +{ + float leftRight = padUsed->GetPedWalkLeftRight(); + float upDown = padUsed->GetPedWalkUpDown(); + float padMove = CVector2D(leftRight, upDown).Magnitude(); + + if (padMove > 0.0f) { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -leftRight, upDown) - TheCamera.Orientation; + m_takeAStepAfterAttack = padMove > (2 * PAD_MOVE_TO_GAME_WORLD_MOVE); + if (padUsed->GetSprint() && padMove > (1 * PAD_MOVE_TO_GAME_WORLD_MOVE)) + bIsAttacking = false; + } + + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY) && padUsed->JumpJustDown()) { + if (m_nEvadeAmount != 0 && m_pEvadingFrom) { + SetEvasiveDive((CPhysical*)m_pEvadingFrom, 1); + m_nEvadeAmount = 0; + m_pEvadingFrom = nil; + } else { + SetJump(); + } + } +} + +void +CPlayerPed::PlayerControl1stPersonRunAround(CPad *padUsed) +{ + float leftRight = padUsed->GetPedWalkLeftRight(); + float upDown = padUsed->GetPedWalkUpDown(); + float padMove = CVector2D(leftRight, upDown).Magnitude(); + float padMoveInGameUnit = padMove / PAD_MOVE_TO_GAME_WORLD_MOVE; + if (padMoveInGameUnit > 0.0f) { + m_fRotationDest = CGeneral::LimitRadianAngle(TheCamera.Orientation); + m_fMoveSpeed = Min(padMoveInGameUnit, 0.07f * CTimer::GetTimeStep() + m_fMoveSpeed); + } else { + m_fMoveSpeed = 0.0f; + } + + if (m_nPedState == PED_JUMP) { + if (bIsInTheAir) { + if (bUsesCollision && !bHitSteepSlope && (!bHitSomethingLastFrame || m_vecDamageNormal.z > 0.6f) + && m_fDistanceTravelled < CTimer::GetTimeStepInSeconds() && m_vecMoveSpeed.MagnitudeSqr() < 0.01f) { + + float angleSin = Sin(m_fRotationCur); // originally sin(DEGTORAD(RADTODEG(m_fRotationCur))) o_O + float angleCos = Cos(m_fRotationCur); + ApplyMoveForce(-angleSin * 3.0f, 3.0f * angleCos, 0.05f); + } + } else if (bIsLanding) { + m_fMoveSpeed = 0.0f; + } + } + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY) && padUsed->GetSprint()) { + m_nMoveState = PEDMOVE_SPRINT; + } + if (m_nPedState != PED_FIGHT) + SetRealMoveAnim(); + + if (!bIsInTheAir && !(CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY)) + && padUsed->JumpJustDown() && m_nPedState != PED_JUMP) { + ClearAttack(); + ClearWeaponTarget(); + if (m_nEvadeAmount != 0 && m_pEvadingFrom) { + SetEvasiveDive((CPhysical*)m_pEvadingFrom, 1); + m_nEvadeAmount = 0; + m_pEvadingFrom = nil; + } else { + SetJump(); + } + } +} + +void +CPlayerPed::KeepAreaAroundPlayerClear(void) +{ + BuildPedLists(); + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->CharCreatedBy == RANDOM_CHAR && !nearPed->DyingOrDead()) { + if (nearPed->GetIsOnScreen()) { + if (nearPed->m_objective == OBJECTIVE_NONE) { + nearPed->SetFindPathAndFlee(this, 5000, true); + } else { + if (nearPed->EnteringCar()) + nearPed->QuitEnteringCar(); + + nearPed->ClearObjective(); + } + } else { + nearPed->FlagToDestroyWhenNextProcessed(); + } + } + } + CVector playerPos = (InVehicle() ? m_pMyVehicle->GetPosition() : GetPosition()); + + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity *vehicles[8]; + CWorld::FindObjectsInRange(pos, CHECK_NEARBY_THINGS_MAX_DIST, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + + for (int i = 0; i < lastVehicle; i++) { + CVehicle *veh = (CVehicle*)vehicles[i]; + if (veh->VehicleCreatedBy != MISSION_VEHICLE) { + if (veh->GetStatus() != STATUS_PLAYER && veh->GetStatus() != STATUS_PLAYER_DISABLED) { + if ((veh->GetPosition() - playerPos).MagnitudeSqr() > 25.0f) { + veh->AutoPilot.m_nTempAction = TEMPACT_WAIT; + veh->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 5000; + } else { + if (DotProduct2D(playerPos - veh->GetPosition(), veh->GetForward()) > 0.0f) + veh->AutoPilot.m_nTempAction = TEMPACT_REVERSE; + else + veh->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD; + + veh->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2000; + } + CCarCtrl::PossiblyRemoveVehicle(veh); + } + } + } +} + +void +CPlayerPed::EvaluateNeighbouringTarget(CEntity *candidate, CEntity **targetPtr, float *lastCloseness, float distLimit, float angleOffset, bool lookToLeft) +{ + CVector distVec = candidate->GetPosition() - GetPosition(); + if (distVec.Magnitude2D() <= distLimit) { + if (!DoesTargetHaveToBeBroken(candidate->GetPosition(), GetWeapon())) { +#ifdef VC_PED_PORTS + float angleBetweenUs = CGeneral::GetATanOfXY(candidate->GetPosition().x - TheCamera.GetPosition().x, + candidate->GetPosition().y - TheCamera.GetPosition().y); +#else + float angleBetweenUs = CGeneral::GetATanOfXY(distVec.x, distVec.y); +#endif + angleBetweenUs = CGeneral::LimitAngle(angleBetweenUs - angleOffset); + float closeness; + if (lookToLeft) { + closeness = angleBetweenUs > 0.0f ? -Abs(angleBetweenUs) : -100000.0f; + } else { + closeness = angleBetweenUs > 0.0f ? -100000.0f : -Abs(angleBetweenUs); + } + + if (closeness > *lastCloseness) { + *targetPtr = candidate; + *lastCloseness = closeness; + } + } + } +} + +void +CPlayerPed::EvaluateTarget(CEntity *candidate, CEntity **targetPtr, float *lastCloseness, float distLimit, float angleOffset, bool priority) +{ + CVector distVec = candidate->GetPosition() - GetPosition(); + float dist = distVec.Magnitude2D(); + if (dist <= distLimit) { + if (!DoesTargetHaveToBeBroken(candidate->GetPosition(), GetWeapon())) { + float angleBetweenUs = CGeneral::GetATanOfXY(distVec.x, distVec.y); + angleBetweenUs = CGeneral::LimitAngle(angleBetweenUs - angleOffset); + + float closeness = -dist - 5.0f * Abs(angleBetweenUs); + if (priority) { + closeness += 5.0f; + } + + if (closeness > *lastCloseness) { + *targetPtr = candidate; + *lastCloseness = closeness; + } + } + } +} + +bool +CPlayerPed::FindNextWeaponLockOnTarget(CEntity *previousTarget, bool lookToLeft) +{ + CEntity *nextTarget = nil; + float weaponRange = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_fRange; + // nextTarget = nil; // duplicate + float lastCloseness = -10000.0f; + // CGeneral::GetATanOfXY(GetForward().x, GetForward().y); // unused +#ifdef VC_PED_PORTS + CVector distVec = previousTarget->GetPosition() - TheCamera.GetPosition(); +#else + CVector distVec = previousTarget->GetPosition() - GetPosition(); +#endif + float referenceBeta = CGeneral::GetATanOfXY(distVec.x, distVec.y); + + for (int h = CPools::GetPedPool()->GetSize() - 1; h >= 0; h--) { + CPed *pedToCheck = CPools::GetPedPool()->GetSlot(h); + if (pedToCheck) { + if (pedToCheck != FindPlayerPed() && pedToCheck != previousTarget) { + if (!pedToCheck->DyingOrDead() && !pedToCheck->bInVehicle + && pedToCheck->m_leader != FindPlayerPed() && OurPedCanSeeThisOne(pedToCheck)) { + + EvaluateNeighbouringTarget(pedToCheck, &nextTarget, &lastCloseness, + weaponRange, referenceBeta, lookToLeft); + } + } + } + } + for (int i = 0; i < ARRAY_SIZE(m_nTargettableObjects); i++) { + CObject *obj = CPools::GetObjectPool()->GetAt(m_nTargettableObjects[i]); + if (obj) + EvaluateNeighbouringTarget(obj, &nextTarget, &lastCloseness, weaponRange, referenceBeta, lookToLeft); + } + if (!nextTarget) + return false; + + SetWeaponLockOnTarget(nextTarget); +#ifdef VC_PED_PORTS + bDontAllowWeaponChange = true; +#endif + SetPointGunAt(nextTarget); + return true; +} + +bool +CPlayerPed::FindWeaponLockOnTarget(void) +{ + CEntity *nextTarget = nil; + float weaponRange = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_fRange; + + if (m_pPointGunAt) { + CVector distVec = m_pPointGunAt->GetPosition() - GetPosition(); + if (distVec.Magnitude2D() > weaponRange) { + SetWeaponLockOnTarget(nil); + return false; + } else { + return true; + } + } + + // nextTarget = nil; // duplicate + float lastCloseness = -10000.0f; + float referenceBeta = CGeneral::GetATanOfXY(GetForward().x, GetForward().y); + for (int h = CPools::GetPedPool()->GetSize() - 1; h >= 0; h--) { + CPed *pedToCheck = CPools::GetPedPool()->GetSlot(h); + if (pedToCheck) { + if (pedToCheck != FindPlayerPed()) { + if (!pedToCheck->DyingOrDead() && !pedToCheck->bInVehicle + && pedToCheck->m_leader != FindPlayerPed() && OurPedCanSeeThisOne(pedToCheck)) { + + EvaluateTarget(pedToCheck, &nextTarget, &lastCloseness, + weaponRange, referenceBeta, IsThisPedAttackingPlayer(pedToCheck)); + } + } + } + } + for (int i = 0; i < ARRAY_SIZE(m_nTargettableObjects); i++) { + CObject *obj = CPools::GetObjectPool()->GetAt(m_nTargettableObjects[i]); + if (obj) + EvaluateTarget(obj, &nextTarget, &lastCloseness, weaponRange, referenceBeta, false); + } + if (!nextTarget) + return false; + + SetWeaponLockOnTarget(nextTarget); +#ifdef VC_PED_PORTS + bDontAllowWeaponChange = true; +#endif + SetPointGunAt(nextTarget); + return true; +} + +void +CPlayerPed::ProcessAnimGroups(void) +{ + AssocGroupId groupToSet; + +#ifdef PC_PLAYER_CONTROLS + if ((m_fWalkAngle <= -DEGTORAD(50.0f) || m_fWalkAngle >= DEGTORAD(50.0f)) + && TheCamera.Cams[TheCamera.ActiveCam].Using3rdPersonMouseCam() + && CanStrafeOrMouseControl()) { + + if (m_fWalkAngle >= -DEGTORAD(130.0f) && m_fWalkAngle <= DEGTORAD(130.0f)) { + if (m_fWalkAngle > 0.0f) { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + groupToSet = ASSOCGRP_ROCKETLEFT; + else + groupToSet = ASSOCGRP_PLAYERLEFT; + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + groupToSet = ASSOCGRP_ROCKETRIGHT; + else + groupToSet = ASSOCGRP_PLAYERRIGHT; + } + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + groupToSet = ASSOCGRP_ROCKETBACK; + else + groupToSet = ASSOCGRP_PLAYERBACK; + } + } else +#endif + { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) { + groupToSet = ASSOCGRP_PLAYERROCKET; + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT) { + groupToSet = ASSOCGRP_PLAYERBBBAT; + } else if (GetWeapon()->m_eWeaponType != WEAPONTYPE_COLT45 && GetWeapon()->m_eWeaponType != WEAPONTYPE_UZI) { + if (!GetWeapon()->IsType2Handed()) { + groupToSet = ASSOCGRP_PLAYER; + } else { + groupToSet = ASSOCGRP_PLAYER2ARMED; + } + } else { + groupToSet = ASSOCGRP_PLAYER1ARMED; + } + } + } + + if (m_animGroup != groupToSet) { + m_animGroup = groupToSet; + ReApplyMoveAnims(); + } +} + +void +CPlayerPed::ProcessPlayerWeapon(CPad *padUsed) +{ + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + if (m_bHasLockOnTarget && !m_pPointGunAt) { + TheCamera.ClearPlayerWeaponMode(); + CWeaponEffects::ClearCrossHair(); + ClearPointGunAt(); + } + if (!m_pFire) { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER || + GetWeapon()->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE || GetWeapon()->m_eWeaponType == WEAPONTYPE_M16) { + if (padUsed->TargetJustDown()) { + SetStoredState(); + SetPedState(PED_SNIPER_MODE); +#ifdef FREE_CAM + if (CCamera::bFreeCam && TheCamera.Cams[0].Using3rdPersonMouseCam()) { + m_fRotationCur = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + SetHeading(m_fRotationCur); + } +#endif + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_ROCKETLAUNCHER, 0, 0); + else if (GetWeapon()->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE) + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_SNIPER, 0, 0); + else + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_M16_1STPERSON, 0, 0); + + m_fMoveSpeed = 0.0f; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_IDLE, 1000.0f); + } + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER || GetWeapon()->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE + || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON) + return; + } + } + + if (padUsed->GetWeapon() && m_nMoveState != PEDMOVE_SPRINT) { + if (m_nSelectedWepSlot == m_currentWeapon) { + if (m_pPointGunAt) { +#ifdef FREE_CAM + if (CCamera::bFreeCam && weaponInfo->m_eWeaponFire == WEAPON_FIRE_MELEE && m_fMoveSpeed < 1.0f) + StartFightAttack(padUsed->GetWeapon()); + else +#endif + SetAttack(m_pPointGunAt); + } else if (m_currentWeapon != WEAPONTYPE_UNARMED) { + if (m_nPedState == PED_ATTACK) { + if (padUsed->WeaponJustDown()) { + m_bHaveTargetSelected = true; + } else if (!m_bHaveTargetSelected) { + m_fAttackButtonCounter += CTimer::GetTimeStepNonClipped(); + } + } else { + m_fAttackButtonCounter = 0.0f; + m_bHaveTargetSelected = false; + } + SetAttack(nil); + } else if (padUsed->WeaponJustDown()) { + if (m_fMoveSpeed < 1.0f) + StartFightAttack(padUsed->GetWeapon()); + else + SetAttack(nil); + } + } + } else { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + if (m_nPedState == PED_ATTACK) { + m_bHaveTargetSelected = true; + bIsAttacking = false; + } + } + +#ifdef FREE_CAM + static int8 changedHeadingRate = 0; + static int8 pointedGun = 0; + if (changedHeadingRate == 2) changedHeadingRate = 1; + if (pointedGun == 2) pointedGun = 1; + + // Rotate player/arm when shooting. We don't have auto-rotation anymore + if (CCamera::m_bUseMouse3rdPerson && CCamera::bFreeCam && + m_nSelectedWepSlot == m_currentWeapon && m_nMoveState != PEDMOVE_SPRINT) { + + // Weapons except throwable and melee ones + if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM) || weaponInfo->IsFlagSet(WEAPONFLAG_1ST_PERSON) || weaponInfo->IsFlagSet(WEAPONFLAG_EXPANDS)) { + if ((padUsed->GetTarget() && weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) || padUsed->GetWeapon()) { + float limitedCam = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + + m_cachedCamSource = TheCamera.Cams[TheCamera.ActiveCam].Source; + m_cachedCamFront = TheCamera.Cams[TheCamera.ActiveCam].Front; + m_cachedCamUp = TheCamera.Cams[TheCamera.ActiveCam].Up; + + // On this one we can rotate arm. + if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) { + pointedGun = 2; + m_bFreeAimActive = true; + SetLookFlag(limitedCam, true); + SetAimFlag(limitedCam); +#ifdef VC_PED_PORTS + SetLookTimer(INT32_MAX); // removing this makes head move for real, but I experinced some bugs. +#endif + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); + if (m_nPedState != PED_ATTACK && m_nPedState != PED_AIM_GUN) { + // This is a seperate ped state just for pointing gun. Used for target button + SetPointGunAt(nil); + } + } else { + m_fRotationDest = limitedCam; + changedHeadingRate = 2; + m_headingRate = 12.5f; + + // Anim. fix for shotgun, ak47 and m16 (we must finish rot. it quickly) + if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM) && padUsed->WeaponJustDown()) { + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + float limitedRotDest = m_fRotationDest; + + if (m_fRotationCur - PI > m_fRotationDest) { + limitedRotDest += 2 * PI; + } else if (PI + m_fRotationCur < m_fRotationDest) { + limitedRotDest -= 2 * PI; + } + + m_fRotationCur += (limitedRotDest - m_fRotationCur) / 2; + } + } + } + } + } + if (changedHeadingRate == 1) { + changedHeadingRate = 0; + RestoreHeadingRate(); + } + if (pointedGun == 1) { + if (m_nPedState == PED_ATTACK) { + if (!padUsed->GetWeapon() && (m_pedIK.m_flags & CPedIK::GUN_POINTED_SUCCESSFULLY) == 0) { + float limitedCam = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + + SetAimFlag(limitedCam); + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); + m_bFreeAimActive = true; + } + } else { + pointedGun = 0; + ClearPointGunAt(); + } + } +#endif + + if (padUsed->GetTarget() && m_nSelectedWepSlot == m_currentWeapon && m_nMoveState != PEDMOVE_SPRINT) { + if (m_pPointGunAt) { + // what?? + if (!m_pPointGunAt +#ifdef FREE_CAM + || (!CCamera::bFreeCam && CCamera::m_bUseMouse3rdPerson) +#else + || CCamera::m_bUseMouse3rdPerson +#endif + || m_pPointGunAt->IsPed() && ((CPed*)m_pPointGunAt)->bInVehicle) { + ClearWeaponTarget(); + return; + } + if (CPlayerPed::DoesTargetHaveToBeBroken(m_pPointGunAt->GetPosition(), GetWeapon())) { + ClearWeaponTarget(); + return; + } + if (m_pPointGunAt) { + if (padUsed->ShiftTargetLeftJustDown()) + FindNextWeaponLockOnTarget(m_pPointGunAt, true); + if (padUsed->ShiftTargetRightJustDown()) + FindNextWeaponLockOnTarget(m_pPointGunAt, false); + } + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_SYPHON, 0, 0); + TheCamera.UpdateAimingCoors(m_pPointGunAt->GetPosition()); + } +#ifdef FREE_CAM + else if ((CCamera::bFreeCam && weaponInfo->m_eWeaponFire == WEAPON_FIRE_MELEE) || (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM) && !CCamera::m_bUseMouse3rdPerson)) { +#else + else if (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM) && !CCamera::m_bUseMouse3rdPerson) { +#endif + if (padUsed->TargetJustDown()) + FindWeaponLockOnTarget(); + } + } else if (m_pPointGunAt) { + ClearWeaponTarget(); + } + + if (m_pPointGunAt) { +#ifndef VC_PED_PORTS + CVector markPos = m_pPointGunAt->GetPosition(); +#else + CVector markPos; + if (m_pPointGunAt->IsPed()) { + ((CPed*)m_pPointGunAt)->m_pedIK.GetComponentPosition(markPos, PED_MID); + } else { + markPos = m_pPointGunAt->GetPosition(); + } +#endif + if (bCanPointGunAtTarget) { + CWeaponEffects::MarkTarget(markPos, 64, 0, 0, 255, 0.8f); + } else { + CWeaponEffects::MarkTarget(markPos, 64, 32, 0, 255, 0.8f); + } + } + m_bHasLockOnTarget = m_pPointGunAt != nil; +} + +void +CPlayerPed::PlayerControlZelda(CPad *padUsed) +{ + bool doSmoothSpray = DoWeaponSmoothSpray(); + float camOrientation = TheCamera.Orientation; + float leftRight = padUsed->GetPedWalkLeftRight(); + float upDown = padUsed->GetPedWalkUpDown(); + float padMoveInGameUnit; + bool smoothSprayWithoutMove = false; + + if (doSmoothSpray && upDown > 0.0f) { + padMoveInGameUnit = 0.0f; + smoothSprayWithoutMove = true; + } else { + padMoveInGameUnit = CVector2D(leftRight, upDown).Magnitude() / PAD_MOVE_TO_GAME_WORLD_MOVE; + } + +#ifdef FREE_CAM + if(CCamera::bFreeCam && TheCamera.Cams[0].Using3rdPersonMouseCam() && doSmoothSpray) { + padMoveInGameUnit = 0.0f; + smoothSprayWithoutMove = false; + } +#endif + + if (padMoveInGameUnit > 0.0f || smoothSprayWithoutMove) { + float padHeading = CGeneral::GetRadianAngleBetweenPoints(0.0f, 0.0f, -leftRight, upDown); + float neededTurn = CGeneral::LimitRadianAngle(padHeading - camOrientation); + if (doSmoothSpray) { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER || GetWeapon()->m_eWeaponType == WEAPONTYPE_COLT45 + || GetWeapon()->m_eWeaponType == WEAPONTYPE_UZI) + m_fRotationDest = m_fRotationCur - leftRight / 128.0f * (PI / 80.0f) * CTimer::GetTimeStep(); + else + m_fRotationDest = m_fRotationCur - leftRight / 128.0f * (PI / 128.0f) * CTimer::GetTimeStep(); + } else { + m_fRotationDest = neededTurn; + } + + float maxAcc = 0.07f * CTimer::GetTimeStep(); + m_fMoveSpeed = Min(padMoveInGameUnit, m_fMoveSpeed + maxAcc); + + } else { + m_fMoveSpeed = 0.0f; + } + + if (m_nPedState == PED_JUMP) { + if (bIsInTheAir) { + if (bUsesCollision && !bHitSteepSlope && (!bHitSomethingLastFrame || m_vecDamageNormal.z > 0.6f) + && m_fDistanceTravelled < CTimer::GetTimeStepInSeconds() && m_vecMoveSpeed.MagnitudeSqr() < 0.01f) { + + float angleSin = Sin(m_fRotationCur); // originally sin(DEGTORAD(RADTODEG(m_fRotationCur))) o_O + float angleCos = Cos(m_fRotationCur); + ApplyMoveForce(-angleSin * 3.0f, 3.0f * angleCos, 0.05f); + } + } else if (bIsLanding) { + m_fMoveSpeed = 0.0f; + } + } + + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY) && padUsed->GetSprint()) { + m_nMoveState = PEDMOVE_SPRINT; + } + if (m_nPedState != PED_FIGHT) + SetRealMoveAnim(); + + if (!bIsInTheAir && !(CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY)) + && padUsed->JumpJustDown() && m_nPedState != PED_JUMP) { + ClearAttack(); + ClearWeaponTarget(); + if (m_nEvadeAmount != 0 && m_pEvadingFrom) { + SetEvasiveDive((CPhysical*)m_pEvadingFrom, 1); + m_nEvadeAmount = 0; + m_pEvadingFrom = nil; + } else { + SetJump(); + } + } +} + +void +CPlayerPed::ProcessControl(void) +{ + if (m_nEvadeAmount != 0) + --m_nEvadeAmount; + + if (m_nEvadeAmount == 0) + m_pEvadingFrom = nil; + + if (m_pCurrentPhysSurface && m_pCurrentPhysSurface->IsVehicle() && ((CVehicle*)m_pCurrentPhysSurface)->IsBoat()) { + bTryingToReachDryLand = true; + } else if (!(((uint8)CTimer::GetFrameCounter() + m_randomSeed) & 0xF)) { + CVehicle *nearVeh = (CVehicle*)CWorld::TestSphereAgainstWorld(GetPosition(), 7.0f, nil, + false, true, false, false, false, false); + if (nearVeh && nearVeh->IsBoat()) + bTryingToReachDryLand = true; + else + bTryingToReachDryLand = false; + } + CPed::ProcessControl(); + if (bWasPostponed) + return; + + CPad *padUsed = CPad::GetPad(0); + m_pWanted->Update(); + CEntity::PruneReferences(); + + if (m_nMoveState != PEDMOVE_RUN && m_nMoveState != PEDMOVE_SPRINT) + RestoreSprintEnergy(1.0f); + else if (m_nMoveState == PEDMOVE_RUN) + RestoreSprintEnergy(0.3f); + + if (m_nPedState == PED_DEAD) { + ClearWeaponTarget(); + return; + } + if (m_nPedState == PED_DIE) { + ClearWeaponTarget(); + if (CTimer::GetTimeInMilliseconds() > m_bloodyFootprintCountOrDeathTime + 4000) + SetDead(); + return; + } + if (m_nPedState == PED_DRIVING && m_objective != OBJECTIVE_LEAVE_CAR) { + if (m_pMyVehicle->IsCar() && ((CAutomobile*)m_pMyVehicle)->Damage.GetDoorStatus(DOOR_FRONT_LEFT) == DOOR_STATUS_SWINGING) { + CAnimBlendAssociation *rollDoorAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS); + + if (m_pMyVehicle->m_nGettingOutFlags & CAR_DOOR_FLAG_LF || rollDoorAssoc || (rollDoorAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS))) { + if (rollDoorAssoc) + m_pMyVehicle->ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, rollDoorAssoc->currentTime); + + } else { + // These comparisons are wrong, they return uint16 + if (padUsed && (padUsed->GetAccelerate() != 0.0f || padUsed->GetSteeringLeftRight() != 0.0f || padUsed->GetBrake() != 0.0f)) { + if (rollDoorAssoc) + m_pMyVehicle->ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS, rollDoorAssoc->currentTime); + + } else { + m_pMyVehicle->m_nGettingOutFlags |= CAR_DOOR_FLAG_LF; + if (m_pMyVehicle->bLowVehicle) + rollDoorAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS); + else + rollDoorAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS); + + rollDoorAssoc->SetFinishCallback(PedAnimDoorCloseRollingCB, this); + } + } + } + return; + } + if (m_objective == OBJECTIVE_NONE) + m_nMoveState = PEDMOVE_STILL; + if (bIsLanding) + RunningLand(padUsed); + if (padUsed && padUsed->WeaponJustDown() && m_nPedState != PED_SNIPER_MODE) { + + // ...Really? + eWeaponType playerWeapon = FindPlayerPed()->GetWeapon()->m_eWeaponType; + if (playerWeapon == WEAPONTYPE_SNIPERRIFLE) { + DMAudio.PlayFrontEndSound(SOUND_WEAPON_SNIPER_SHOT_NO_ZOOM, 0); + } else if (playerWeapon == WEAPONTYPE_ROCKETLAUNCHER) { + DMAudio.PlayFrontEndSound(SOUND_WEAPON_ROCKET_SHOT_NO_ZOOM, 0); + } + } + + switch (m_nPedState) { + case PED_NONE: + case PED_IDLE: + case PED_FLEE_POS: + case PED_FLEE_ENTITY: + case PED_ATTACK: + case PED_FIGHT: + case PED_AIM_GUN: + if (!RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_BLOCK)) { + if (TheCamera.Cams[0].Using3rdPersonMouseCam() +#ifdef FREE_CAM + && !CCamera::bFreeCam +#endif + ) { + if (padUsed) + PlayerControl1stPersonRunAround(padUsed); + + } else if (m_nPedState == PED_FIGHT) { + if (padUsed) + PlayerControlFighter(padUsed); + + } else if (padUsed) { + PlayerControlZelda(padUsed); + } + } + if (IsPedInControl() && padUsed) + ProcessPlayerWeapon(padUsed); + break; + case PED_SEEK_ENTITY: + m_vecSeekPos = m_pSeekTarget->GetPosition(); + + // fall through + case PED_SEEK_POS: + switch (m_nMoveState) { + case PEDMOVE_WALK: + m_fMoveSpeed = 1.0f; + break; + case PEDMOVE_RUN: + m_fMoveSpeed = 1.8f; + break; + case PEDMOVE_SPRINT: + m_fMoveSpeed = 2.5f; + break; + default: + m_fMoveSpeed = 0.0f; + break; + } + SetRealMoveAnim(); + if (Seek()) { + RestorePreviousState(); + SetMoveState(PEDMOVE_STILL); + } + break; + case PED_SNIPER_MODE: + if (FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_M16) { + if (padUsed) + PlayerControlM16(padUsed); + + } else if (padUsed) { + PlayerControlSniper(padUsed); + } + break; + case PED_SEEK_CAR: + case PED_SEEK_IN_BOAT: + if (bVehEnterDoorIsBlocked || bKindaStayInSamePlace) { + m_fMoveSpeed = 0.0f; + } else { + m_fMoveSpeed = Min(2.0f, 2.0f * (m_vecSeekPos - GetPosition()).Magnitude2D()); + } + if (padUsed && !padUsed->ArePlayerControlsDisabled()) { + if (padUsed->GetTarget() || padUsed->GetLeftStickXJustDown() || padUsed->GetLeftStickYJustDown() || + padUsed->GetDPadUpJustDown() || padUsed->GetDPadDownJustDown() || padUsed->GetDPadLeftJustDown() || + padUsed->GetDPadRightJustDown()) { + + RestorePreviousState(); + if (m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER || m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + RestorePreviousObjective(); + } + } + } + if (padUsed && padUsed->GetSprint()) + m_nMoveState = PEDMOVE_SPRINT; + SetRealMoveAnim(); + break; + case PED_JUMP: + if (padUsed) + PlayerControlZelda(padUsed); + if (bIsLanding) + break; + + // This has been added later it seems + return; + case PED_FALL: + case PED_GETUP: + case PED_ENTER_TRAIN: + case PED_EXIT_TRAIN: + case PED_CARJACK: + case PED_DRAG_FROM_CAR: + case PED_ENTER_CAR: + case PED_STEAL_CAR: + case PED_EXIT_CAR: + ClearWeaponTarget(); + break; + case PED_ARRESTED: + if (m_nLastPedState == PED_DRAG_FROM_CAR && m_pVehicleAnim) + BeingDraggedFromCar(); + break; + default: + break; + } + if (padUsed && IsPedShootable()) { + ProcessWeaponSwitch(padUsed); + GetWeapon()->Update(m_audioEntityId); + } + ProcessAnimGroups(); + if (padUsed) { + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FOLLOWPED + && TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_BEHIND) { + + m_lookTimer = 0; + float camAngle = CGeneral::LimitRadianAngle(TheCamera.Cams[TheCamera.ActiveCam].Front.Heading()); + float angleBetweenPlayerAndCam = Abs(camAngle - m_fRotationCur); + if (m_nPedState != PED_ATTACK && angleBetweenPlayerAndCam > DEGTORAD(30.0f) && angleBetweenPlayerAndCam < DEGTORAD(330.0f)) { + + if (angleBetweenPlayerAndCam > DEGTORAD(150.0f) && angleBetweenPlayerAndCam < DEGTORAD(210.0f)) { + float rightTurnAngle = CGeneral::LimitRadianAngle(m_fRotationCur - DEGTORAD(150.0f)); + float leftTurnAngle = CGeneral::LimitRadianAngle(DEGTORAD(150.0f) + m_fRotationCur); + if (m_fLookDirection == 999999.0f) + camAngle = rightTurnAngle; + else if (Abs(rightTurnAngle - m_fLookDirection) < Abs(leftTurnAngle - m_fLookDirection)) + camAngle = rightTurnAngle; + else + camAngle = leftTurnAngle; + } + SetLookFlag(camAngle, true); + SetLookTimer(CTimer::GetTimeStepInMilliseconds() * 5.0f); + } else { + ClearLookFlag(); + } + } + } + if (m_nMoveState == PEDMOVE_SPRINT && bIsLooking) { + ClearLookFlag(); + SetLookTimer(250); + } + + if (m_vecMoveSpeed.Magnitude2D() < 0.1f) { + if (m_nSpeedTimer) { + if (CTimer::GetTimeInMilliseconds() > m_nSpeedTimer) + m_bSpeedTimerFlag = true; + } else { + m_nSpeedTimer = CTimer::GetTimeInMilliseconds() + 500; + } + } else { + m_nSpeedTimer = 0; + m_bSpeedTimerFlag = false; + } + +#ifdef VC_PED_PORTS + if (bDontAllowWeaponChange && FindPlayerPed() == this) { + if (!CPad::GetPad(0)->GetTarget()) + bDontAllowWeaponChange = false; + } +#endif + +#ifdef PED_SKIN + if (!bIsVisible && IsClumpSkinned(GetClump())) + UpdateRpHAnim(); +#endif +} + +#ifdef COMPATIBLE_SAVES +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +void +CPlayerPed::Save(uint8*& buf) +{ + CPed::Save(buf); + ZeroSaveBuf(buf, 16); + CopyToBuf(buf, m_fMaxStamina); + ZeroSaveBuf(buf, 28); + CopyToBuf(buf, m_nTargettableObjects[0]); + CopyToBuf(buf, m_nTargettableObjects[1]); + CopyToBuf(buf, m_nTargettableObjects[2]); + CopyToBuf(buf, m_nTargettableObjects[3]); + ZeroSaveBuf(buf, 116); +} + +void +CPlayerPed::Load(uint8*& buf) +{ + CPed::Load(buf); + SkipSaveBuf(buf, 16); + CopyFromBuf(buf, m_fMaxStamina); + SkipSaveBuf(buf, 28); + CopyFromBuf(buf, m_nTargettableObjects[0]); + CopyFromBuf(buf, m_nTargettableObjects[1]); + CopyFromBuf(buf, m_nTargettableObjects[2]); + CopyFromBuf(buf, m_nTargettableObjects[3]); + SkipSaveBuf(buf, 116); +} +#undef CopyFromBuf +#undef CopyToBuf +#endif diff --git a/src/peds/PlayerPed.h b/src/peds/PlayerPed.h new file mode 100644 index 0000000..2e9f798 --- /dev/null +++ b/src/peds/PlayerPed.h @@ -0,0 +1,98 @@ +#pragma once + +#include "Ped.h" + +class CPad; +class CCopPed; +class CWanted; + +class CPlayerPed : public CPed +{ +public: + CWanted *m_pWanted; + CCopPed *m_pArrestingCop; + float m_fMoveSpeed; + float m_fCurrentStamina; + float m_fMaxStamina; + float m_fStaminaProgress; + int8 m_nSelectedWepSlot; // eWeaponType + bool m_bSpeedTimerFlag; + uint8 m_nEvadeAmount; + int8 field_1367; + uint32 m_nSpeedTimer; + uint32 m_nHitAnimDelayTimer; + float m_fAttackButtonCounter; + bool m_bHaveTargetSelected; // may have better name + CEntity *m_pEvadingFrom; // is this CPhysical? + int32 m_nTargettableObjects[4]; + bool m_bAdrenalineActive; + bool m_bHasLockOnTarget; + uint32 m_nAdrenalineTime; + bool m_bCanBeDamaged; + int8 field_1413; + CVector m_vecSafePos[6]; // safe places from the player, for example behind a tree + CPed *m_pPedAtSafePos[6]; + float m_fWalkAngle; + float m_fFPSMoveHeading; +#ifdef FREE_CAM + bool m_bFreeAimActive; + CVector m_cachedCamSource; + CVector m_cachedCamFront; + CVector m_cachedCamUp; +#endif +#ifdef VC_PED_PORTS + static bool bDontAllowWeaponChange; +#endif + + CPlayerPed(); + ~CPlayerPed(); + void SetMoveAnim() { }; + + void ReApplyMoveAnims(void); + void ClearWeaponTarget(void); + void SetWantedLevel(int32 level); + void SetWantedLevelNoDrop(int32 level); + void KeepAreaAroundPlayerClear(void); + void AnnoyPlayerPed(bool); + void MakeChangesForNewWeapon(int8); + void SetInitialState(void); + void ProcessControl(void); + void ClearAdrenaline(void); + void UseSprintEnergy(void); + class CPlayerInfo *GetPlayerInfoForThisPlayerPed(); + void SetRealMoveAnim(void); + void RestoreSprintEnergy(float); + bool DoWeaponSmoothSpray(void); + void DoStuffToGoOnFire(void); + bool DoesTargetHaveToBeBroken(CVector, CWeapon*); + void RunningLand(CPad*); + bool IsThisPedAttackingPlayer(CPed*); + void PlayerControlSniper(CPad*); + void PlayerControlM16(CPad*); + void PlayerControlFighter(CPad*); + void ProcessWeaponSwitch(CPad*); + void MakeObjectTargettable(int32); + void PlayerControl1stPersonRunAround(CPad *padUsed); + void EvaluateNeighbouringTarget(CEntity*, CEntity**, float*, float, float, bool); + void EvaluateTarget(CEntity*, CEntity**, float*, float, float, bool); + bool FindNextWeaponLockOnTarget(CEntity*, bool); + bool FindWeaponLockOnTarget(void); + void ProcessAnimGroups(void); + void ProcessPlayerWeapon(CPad*); + void PlayerControlZelda(CPad*); + + static void SetupPlayerPed(int32); + static void DeactivatePlayerPed(int32); + static void ReactivatePlayerPed(int32); + +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + + static const uint32 nSaveStructSize; +}; + +#ifndef PED_SKIN +VALIDATE_SIZE(CPlayerPed, 0x5F0); +#endif diff --git a/src/peds/Population.cpp b/src/peds/Population.cpp new file mode 100644 index 0000000..5c80702 --- /dev/null +++ b/src/peds/Population.cpp @@ -0,0 +1,1174 @@ +#include "common.h" + +#include "Game.h" +#include "General.h" +#include "World.h" +#include "Population.h" +#include "CopPed.h" +#include "Wanted.h" +#include "FileMgr.h" +#include "Gangs.h" +#include "ModelIndices.h" +#include "Zones.h" +#include "CivilianPed.h" +#include "EmergencyPed.h" +#include "Replay.h" +#include "Camera.h" +#include "CutsceneMgr.h" +#include "CarCtrl.h" +#include "IniFile.h" +#include "VisibilityPlugins.h" +#include "PedPlacement.h" +#include "DummyObject.h" +#include "Script.h" +#include "Shadows.h" +#include "Bike.h" + +#define MIN_CREATION_DIST 40.0f // not for start of the game (look at the GeneratePedsAtStartOfGame) +#define CREATION_RANGE 10.0f // added over the MIN_CREATION_DIST. +#define OFFSCREEN_CREATION_MULT 0.5f +#define PED_REMOVE_DIST (MIN_CREATION_DIST + CREATION_RANGE + 1.0f) +#define PED_REMOVE_DIST_SPECIAL (MIN_CREATION_DIST + CREATION_RANGE + 15.0f) // for peds with bCullExtraFarAway flag + +// Transition areas between zones +const RegenerationPoint aSafeZones[] = { + LEVEL_INDUSTRIAL, LEVEL_COMMERCIAL, 400.0f, 814.0f, -954.0f, -903.0f, 30.0f, 100.0f, + 790.0f, -917.0f, 39.0f, 775.0f, -921.0f, 39.0f, 424.0f, -942.0f, 38.0f, 439.0f, -938.0f, 38.0f, + LEVEL_INDUSTRIAL, LEVEL_COMMERCIAL, 555.0f, 711.0f, 118.0f, 186.0f, -30.0f, -10.0f, + CVector(698.0f, 182.0f, -20.0f), CVector(681.0f, 178.0f, -20.0f), CVector(586.0f, 144.0f, -20.0f), CVector(577.0f, 135.0f, -20.0f), + LEVEL_INDUSTRIAL, LEVEL_COMMERCIAL, 626.0f, 744.0f, -124.0f, -87.0f, -20.0f, -6.0f, + CVector(736.0f, -117.0f, -13.0f), CVector(730.0f, -115.0f, -13.0f), CVector(635.0f, -93.0f, -12.5f), CVector(650.0f, -89.0f, -12.5f), + LEVEL_INDUSTRIAL, LEVEL_COMMERCIAL, 645.0f, 734.0f, -780.0f, -750.0f, -25.0f, -6.0f, + CVector(729.0f, -764.0f, -18.0f), CVector(720.0f, -769.0f, -17.0f), CVector(652.0f, -774.0f, -10.5f), CVector(659.0f, -770.0f, -10.5f), + LEVEL_COMMERCIAL, LEVEL_SUBURBAN, -532.0f, -136.0f, -668.0f, -599.0f, 34.0f, 60.0f, + CVector(-172.0f, -619.0f, 44.0f), CVector(-183.0f, -623.0f, 44.0f), CVector(-511.0f, -645.0f, 41.0f), CVector(-493.0f, -639.0f, 41.5f), + LEVEL_COMMERCIAL, LEVEL_SUBURBAN, -325.0f, -175.0f, 27.0f, 75.0f, -30.0f, -10.0f, + CVector(-185.0f, 40.8f, -20.5f), CVector(-202.0f, 37.0f, -20.5f), CVector(-315.0f, 65.5f, -20.5f), CVector(-306.0f, 62.4f, -20.5f), + LEVEL_COMMERCIAL, LEVEL_SUBURBAN, -410.0f, -310.0f, -1055.0f, -1030.0f, -20.0f, -6.0f, + CVector(-321.0f, -1043.0f, -13.2f), CVector(-328.0f, -1045.0f, -13.2f), CVector(-398.0f, -1044.0f, -13.5f), CVector(-390.0f, -1040.5f, -13.5f), + LEVEL_COMMERCIAL, LEVEL_SUBURBAN, -425.0f, -280.0f, -471.0f, -447.0f, -20.0f, -5.0f, + CVector(-292.0f, -457.0f, -11.6f), CVector(-310.0f, -461.0f, -11.6f), CVector(-413.0f, -461.0f, -11.5f), CVector(-399.0f, -457.0f, -11.3f) +}; + +PedGroup CPopulation::ms_pPedGroups[NUMPEDGROUPS]; +bool CPopulation::ms_bGivePedsWeapons; +int32 CPopulation::m_AllRandomPedsThisType = -1; +float CPopulation::PedDensityMultiplier = 1.0f; +uint32 CPopulation::ms_nTotalMissionPeds; +int32 CPopulation::MaxNumberOfPedsInUse = DEFAULT_MAX_NUMBER_OF_PEDS; +uint32 CPopulation::ms_nNumCivMale; +uint32 CPopulation::ms_nNumCivFemale; +uint32 CPopulation::ms_nNumCop; +bool CPopulation::bZoneChangeHasHappened; +uint32 CPopulation::ms_nNumEmergency; +int8 CPopulation::m_CountDownToPedsAtStart; +uint32 CPopulation::ms_nNumGang1; +uint32 CPopulation::ms_nNumGang2; +uint32 CPopulation::ms_nTotalPeds; +uint32 CPopulation::ms_nNumGang3; +uint32 CPopulation::ms_nTotalGangPeds; +uint32 CPopulation::ms_nNumGang4; +uint32 CPopulation::ms_nTotalCivPeds; +uint32 CPopulation::ms_nNumGang5; +uint32 CPopulation::ms_nNumDummy; +uint32 CPopulation::ms_nNumGang6; +uint32 CPopulation::ms_nNumGang9; +uint32 CPopulation::ms_nNumGang7; +uint32 CPopulation::ms_nNumGang8; +CVector CPopulation::RegenerationPoint_a; +CVector CPopulation::RegenerationPoint_b; +CVector CPopulation::RegenerationFront; + +void +CPopulation::Initialise() +{ + debug("Initialising CPopulation...\n"); + + ms_nNumCivMale = 0; + ms_nNumCivFemale = 0; + ms_nNumCop = 0; + ms_nNumEmergency = 0; + ms_nNumGang1 = 0; + ms_nNumGang2 = 0; + ms_nNumGang3 = 0; + ms_nNumGang4 = 0; + ms_nNumGang5 = 0; + ms_nNumGang6 = 0; + ms_nNumGang7 = 0; + ms_nNumGang8 = 0; + ms_nNumGang9 = 0; + ms_nNumDummy = 0; + + m_AllRandomPedsThisType = -1; + PedDensityMultiplier = 1.0f; + bZoneChangeHasHappened = false; + m_CountDownToPedsAtStart = 2; + + ms_nTotalMissionPeds = 0; + ms_nTotalPeds = 0; + ms_nTotalGangPeds = 0; + ms_nTotalCivPeds = 0; + + LoadPedGroups(); + DealWithZoneChange(LEVEL_COMMERCIAL, LEVEL_INDUSTRIAL, true); + + debug("CPopulation ready\n"); +} + +void +CPopulation::RemovePed(CPed *ent) +{ + CWorld::Remove((CEntity*)ent); + delete ent; +} + +int32 +CPopulation::ChooseCivilianOccupation(int32 group) +{ + return ms_pPedGroups[group].models[CGeneral::GetRandomNumberInRange(0, NUMMODELSPERPEDGROUP)]; +} + +// returns eCopType +int32 +CPopulation::ChoosePolicePedOccupation() +{ + CGeneral::GetRandomNumber(); + return COP_STREET; +} + +void +CPopulation::LoadPedGroups() +{ + int fd; + char line[1024]; + int nextPedGroup = 0; + // char unused[16]; // non-existence of that in mobile kinda verifies that + char modelName[256]; + + CFileMgr::ChangeDir("\\DATA\\"); + fd = CFileMgr::OpenFile("PEDGRP.DAT", "r"); + CFileMgr::ChangeDir("\\"); + while (CFileMgr::ReadLine(fd, line, sizeof(line))) { + int end; + // find end of line + for (end = 0; ; end++) { + if (line[end] == '\n') + break; + if (line[end] == ',' || line[end] == '\r') + line[end] = ' '; + } + line[end] = '\0'; + int cursor = 0; + int i; + for (i = 0; i < NUMMODELSPERPEDGROUP; i++) { + while (line[cursor] <= ' ' && line[cursor] != '\0') + ++cursor; + + if (line[cursor] == '#') + break; + + // find next whitespace + int nextWhitespace; + for (nextWhitespace = cursor; line[nextWhitespace] > ' '; ++nextWhitespace) + ; + + if (cursor == nextWhitespace) + break; + + // read until next whitespace + strncpy(modelName, &line[cursor], nextWhitespace - cursor); + modelName[nextWhitespace - cursor] = '\0'; + CModelInfo::GetModelInfo(modelName, &ms_pPedGroups[nextPedGroup].models[i]); + cursor = nextWhitespace; + } + if (i == NUMMODELSPERPEDGROUP) + nextPedGroup++; + } + CFileMgr::CloseFile(fd); +} + +void +CPopulation::UpdatePedCount(ePedType pedType, bool decrease) +{ + if (decrease) { + switch (pedType) { + case PEDTYPE_PLAYER1: + case PEDTYPE_PLAYER2: + case PEDTYPE_PLAYER3: + case PEDTYPE_PLAYER4: + case PEDTYPE_UNUSED1: + case PEDTYPE_SPECIAL: + return; + case PEDTYPE_CIVMALE: + --ms_nNumCivMale; + break; + case PEDTYPE_CIVFEMALE: + --ms_nNumCivFemale; + break; + case PEDTYPE_COP: + --ms_nNumCop; + break; + case PEDTYPE_GANG1: + --ms_nNumGang1; + break; + case PEDTYPE_GANG2: + --ms_nNumGang2; + break; + case PEDTYPE_GANG3: + --ms_nNumGang3; + break; + case PEDTYPE_GANG4: + --ms_nNumGang4; + break; + case PEDTYPE_GANG5: + --ms_nNumGang5; + break; + case PEDTYPE_GANG6: + --ms_nNumGang6; + break; + case PEDTYPE_GANG7: + --ms_nNumGang7; + break; + case PEDTYPE_GANG8: + --ms_nNumGang8; + break; + case PEDTYPE_GANG9: + --ms_nNumGang9; + break; + case PEDTYPE_EMERGENCY: + case PEDTYPE_FIREMAN: + --ms_nNumEmergency; + break; + case PEDTYPE_CRIMINAL: + --ms_nNumCivMale; + break; + case PEDTYPE_PROSTITUTE: + --ms_nNumCivFemale; + break; + case PEDTYPE_UNUSED2: + --ms_nNumDummy; + break; + default: + Error("Unknown ped type, UpdatePedCount, Population.cpp"); + break; + } + } else { + switch (pedType) { + case PEDTYPE_PLAYER1: + case PEDTYPE_PLAYER2: + case PEDTYPE_PLAYER3: + case PEDTYPE_PLAYER4: + case PEDTYPE_UNUSED1: + case PEDTYPE_SPECIAL: + return; + case PEDTYPE_CIVMALE: + ++ms_nNumCivMale; + break; + case PEDTYPE_CIVFEMALE: + ++ms_nNumCivFemale; + break; + case PEDTYPE_COP: + ++ms_nNumCop; + break; + case PEDTYPE_GANG1: + ++ms_nNumGang1; + break; + case PEDTYPE_GANG2: + ++ms_nNumGang2; + break; + case PEDTYPE_GANG3: + ++ms_nNumGang3; + break; + case PEDTYPE_GANG4: + ++ms_nNumGang4; + break; + case PEDTYPE_GANG5: + ++ms_nNumGang5; + break; + case PEDTYPE_GANG6: + ++ms_nNumGang6; + break; + case PEDTYPE_GANG7: + ++ms_nNumGang7; + break; + case PEDTYPE_GANG8: + ++ms_nNumGang8; + break; + case PEDTYPE_GANG9: + ++ms_nNumGang9; + break; + case PEDTYPE_EMERGENCY: + case PEDTYPE_FIREMAN: + ++ms_nNumEmergency; + break; + case PEDTYPE_CRIMINAL: + ++ms_nNumCivMale; + break; + case PEDTYPE_PROSTITUTE: + ++ms_nNumCivFemale; + break; + case PEDTYPE_UNUSED2: + ++ms_nNumDummy; + break; + default: + Error("Unknown ped type, UpdatePedCount, Population.cpp"); + break; + } + } +} + +int +CPopulation::ChooseGangOccupation(int gangId) +{ + int8 modelOverride = CGangs::GetGangPedModelOverride(gangId); + + // All gangs have 2 models + int firstGangModel = 2 * gangId + MI_GANG01; + + // GetRandomNumberInRange never returns max. value + if (modelOverride == -1) + return CGeneral::GetRandomNumberInRange(firstGangModel, firstGangModel + 2); + + if (modelOverride != 0) + return firstGangModel + 1; + else + return firstGangModel; +} + +void +CPopulation::DealWithZoneChange(eLevelName oldLevel, eLevelName newLevel, bool forceIndustrialZone) +{ + bZoneChangeHasHappened = true; + + CVector findSafeZoneAround; + int safeZone; + + if (forceIndustrialZone) { + // Commercial to industrial transition area on Callahan Bridge + findSafeZoneAround.x = 690.0f; + findSafeZoneAround.y = -920.0f; + findSafeZoneAround.z = 42.0f; + } else { + findSafeZoneAround = FindPlayerCoors(); + } + eLevelName level; + FindCollisionZoneForCoors(&findSafeZoneAround, &safeZone, &level); + + // We aren't in a "safe zone", find closest one + if (safeZone < 0) + FindClosestZoneForCoors(&findSafeZoneAround, &safeZone, oldLevel, newLevel); + + // No, there should be one! + if (safeZone < 0) { + if (newLevel == LEVEL_INDUSTRIAL) { + safeZone = 0; + } else if (newLevel == LEVEL_SUBURBAN) { + safeZone = 4; + } + } + + if (aSafeZones[safeZone].srcLevel == newLevel) { + CPopulation::RegenerationPoint_a = aSafeZones[safeZone].srcPosA; + CPopulation::RegenerationPoint_b = aSafeZones[safeZone].srcPosB; + CPopulation::RegenerationFront = aSafeZones[safeZone].destPosA - aSafeZones[safeZone].srcPosA; + RegenerationFront.Normalise(); + } else if (aSafeZones[safeZone].destLevel == newLevel) { + CPopulation::RegenerationPoint_a = aSafeZones[safeZone].destPosA; + CPopulation::RegenerationPoint_b = aSafeZones[safeZone].destPosB; + CPopulation::RegenerationFront = aSafeZones[safeZone].srcPosA - aSafeZones[safeZone].destPosA; + RegenerationFront.Normalise(); + } +} + +void +CPopulation::FindCollisionZoneForCoors(CVector *coors, int *safeZoneOut, eLevelName *levelOut) +{ + *safeZoneOut = -1; + for (int i = 0; i < ARRAY_SIZE(aSafeZones); i++) { + if (coors->x > aSafeZones[i].x1 && coors->x < aSafeZones[i].x2) { + if (coors->y > aSafeZones[i].y1 && coors->y < aSafeZones[i].y2) { + if (coors->z > aSafeZones[i].z1 && coors->z < aSafeZones[i].z2) + *safeZoneOut = i; + } + } + } + // Then it's transition area + if (*safeZoneOut >= 0) + *levelOut = LEVEL_GENERIC; + else + *levelOut = CTheZones::GetLevelFromPosition(coors); +} + +void +CPopulation::FindClosestZoneForCoors(CVector *coors, int *safeZoneOut, eLevelName level1, eLevelName level2) +{ + float minDist = 10000000.0f; + int closestSafeZone = -1; + for (int i = 0; i < ARRAY_SIZE(aSafeZones); i++) { + if ((level1 == aSafeZones[i].srcLevel || level1 == aSafeZones[i].destLevel) && (level2 == aSafeZones[i].srcLevel || level2 == aSafeZones[i].destLevel)) { + CVector2D safeZoneDistVec(coors->x - (aSafeZones[i].x1 + aSafeZones[i].x2) * 0.5f, coors->y - (aSafeZones[i].y1 + aSafeZones[i].y2) * 0.5f); + float safeZoneDist = safeZoneDistVec.Magnitude(); + if (safeZoneDist < minDist) { + minDist = safeZoneDist; + closestSafeZone = i; + } + } + } + *safeZoneOut = closestSafeZone; +} + +void +CPopulation::Update() +{ + if (!CReplay::IsPlayingBack()) { + ManagePopulation(); + MoveCarsAndPedsOutOfAbandonedZones(); + if (m_CountDownToPedsAtStart != 0) { + if (--m_CountDownToPedsAtStart == 0) + GeneratePedsAtStartOfGame(); + } else { + ms_nTotalCivPeds = ms_nNumCivFemale + ms_nNumCivMale; + ms_nTotalGangPeds = ms_nNumGang9 + ms_nNumGang8 + ms_nNumGang7 + + ms_nNumGang6 + ms_nNumGang5 + ms_nNumGang4 + ms_nNumGang3 + + ms_nNumGang2 + ms_nNumGang1; + ms_nTotalPeds = ms_nNumDummy + ms_nNumEmergency + ms_nNumCop + + ms_nTotalGangPeds + ms_nNumCivFemale + ms_nNumCivMale; + if (!CCutsceneMgr::IsRunning()) { + float pcdm = PedCreationDistMultiplier(); + AddToPopulation(pcdm * (MIN_CREATION_DIST * TheCamera.GenerationDistMultiplier), + pcdm * ((MIN_CREATION_DIST + CREATION_RANGE) * TheCamera.GenerationDistMultiplier), + pcdm * (MIN_CREATION_DIST + CREATION_RANGE) * OFFSCREEN_CREATION_MULT - CREATION_RANGE, + pcdm * (MIN_CREATION_DIST + CREATION_RANGE) * OFFSCREEN_CREATION_MULT); + } + } + } +} + +void +CPopulation::GeneratePedsAtStartOfGame() +{ + for (int i = 0; i < 50; i++) { + ms_nTotalCivPeds = ms_nNumCivFemale + ms_nNumCivMale; + ms_nTotalGangPeds = ms_nNumGang9 + ms_nNumGang8 + ms_nNumGang7 + + ms_nNumGang6 + ms_nNumGang5 + ms_nNumGang4 + + ms_nNumGang3 + ms_nNumGang2 + ms_nNumGang1; + ms_nTotalPeds = ms_nNumDummy + ms_nNumEmergency + ms_nNumCop + + ms_nTotalGangPeds + ms_nNumCivFemale + ms_nNumCivMale; + + // Min dist is 10.0f only for start of the game (naturally) + AddToPopulation(10.0f, PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE), + 10.0f, PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE)); + } +} + +bool +CPopulation::IsPointInSafeZone(CVector *coors) +{ + for (int i = 0; i < ARRAY_SIZE(aSafeZones); i++) { + if (coors->x > aSafeZones[i].x1 && coors->x < aSafeZones[i].x2) { + if (coors->y > aSafeZones[i].y1 && coors->y < aSafeZones[i].y2) { + if (coors->z > aSafeZones[i].z1 && coors->z < aSafeZones[i].z2) + return true; + } + } + } + return false; +} + +// More speed = wider area to spawn peds +float +CPopulation::PedCreationDistMultiplier() +{ + CVehicle *veh = FindPlayerVehicle(); + if (!veh) + return 1.0f; + + float vehSpeed = veh->m_vecMoveSpeed.Magnitude2D(); + return Clamp(vehSpeed - 0.1f + 1.0f, 1.0f, 1.5f); +} + +CPed* +CPopulation::AddPed(ePedType pedType, uint32 miOrCopType, CVector const &coors) +{ + switch (pedType) { + case PEDTYPE_CIVMALE: + case PEDTYPE_CIVFEMALE: + { + CCivilianPed *ped = new CCivilianPed(pedType, miOrCopType); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + if (ms_bGivePedsWeapons) { + eWeaponType weapon = (eWeaponType)CGeneral::GetRandomNumberInRange(WEAPONTYPE_UNARMED, WEAPONTYPE_DETONATOR); + if (weapon != WEAPONTYPE_UNARMED) { + ped->SetCurrentWeapon(ped->GiveWeapon(weapon, 25001)); + } + } + return ped; + } + case PEDTYPE_COP: + { + CCopPed *ped = new CCopPed((eCopType)miOrCopType); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + return ped; + } + case PEDTYPE_GANG1: + case PEDTYPE_GANG2: + case PEDTYPE_GANG3: + case PEDTYPE_GANG4: + case PEDTYPE_GANG5: + case PEDTYPE_GANG6: + case PEDTYPE_GANG7: + case PEDTYPE_GANG8: + case PEDTYPE_GANG9: + { + CCivilianPed *ped = new CCivilianPed(pedType, miOrCopType); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + + uint32 weapon; + if (CGeneral::GetRandomNumberInRange(0, 100) >= 50) + weapon = ped->GiveWeapon((eWeaponType)CGangs::GetGangInfo(pedType - PEDTYPE_GANG1)->m_Weapon2, 25001); + else + weapon = ped->GiveWeapon((eWeaponType)CGangs::GetGangInfo(pedType - PEDTYPE_GANG1)->m_Weapon1, 25001); + ped->SetCurrentWeapon(weapon); + return ped; + } + case PEDTYPE_EMERGENCY: + { + CEmergencyPed *ped = new CEmergencyPed(PEDTYPE_EMERGENCY); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + return ped; + } + case PEDTYPE_FIREMAN: + { + CEmergencyPed *ped = new CEmergencyPed(PEDTYPE_FIREMAN); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + return ped; + } + case PEDTYPE_CRIMINAL: + case PEDTYPE_PROSTITUTE: + { + CCivilianPed *ped = new CCivilianPed(pedType, miOrCopType); + ped->SetPosition(coors); + ped->SetOrientation(0.0f, 0.0f, 0.0f); + CWorld::Add(ped); + return ped; + } + default: + Error("Unknown ped type, AddPed, Population.cpp"); + return nil; + } +} + +void +CPopulation::AddToPopulation(float minDist, float maxDist, float minDistOffScreen, float maxDistOffScreen) +{ + uint32 pedTypeToAdd; + int32 modelToAdd; + int pedAmount; + + CZoneInfo zoneInfo; + CPed *gangLeader = nil; + bool addCop = false; + CPlayerInfo *playerInfo = &CWorld::Players[CWorld::PlayerInFocus]; + CVector playerCentreOfWorld = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + CTheZones::GetZoneInfoForTimeOfDay(&playerCentreOfWorld, &zoneInfo); + CWanted *wantedInfo = playerInfo->m_pPed->m_pWanted; + if (wantedInfo->GetWantedLevel() > 2) { + if (ms_nNumCop < wantedInfo->m_MaxCops && !playerInfo->m_pPed->bInVehicle + && (CCarCtrl::NumLawEnforcerCars >= wantedInfo->m_MaximumLawEnforcerVehicles + || CCarCtrl::NumRandomCars >= playerInfo->m_nTrafficMultiplier * CCarCtrl::CarDensityMultiplier + || CCarCtrl::NumFiretrucksOnDuty + CCarCtrl::NumAmbulancesOnDuty + CCarCtrl::NumParkedCars + + CCarCtrl::NumMissionCars + CCarCtrl::NumLawEnforcerCars + CCarCtrl::NumRandomCars >= CCarCtrl::MaxNumberOfCarsInUse)) { + addCop = true; + minDist = PedCreationDistMultiplier() * MIN_CREATION_DIST; + maxDist = PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE); + } + } + // Yeah, float + float maxPossiblePedsForArea = (zoneInfo.pedDensity + zoneInfo.carDensity) * playerInfo->m_fRoadDensity * PedDensityMultiplier * CIniFile::PedNumberMultiplier; + maxPossiblePedsForArea = Min(maxPossiblePedsForArea, MaxNumberOfPedsInUse); + + if (ms_nTotalPeds < maxPossiblePedsForArea || addCop) { + int decisionThreshold = CGeneral::GetRandomNumberInRange(0, 1000); + if (decisionThreshold < zoneInfo.copDensity || addCop) { + pedTypeToAdd = PEDTYPE_COP; + modelToAdd = ChoosePolicePedOccupation(); + } else { + int16 density = zoneInfo.copDensity; + for (int i = 0; i < NUM_GANGS; i++) { + density += zoneInfo.gangDensity[i]; + if (decisionThreshold < density) { + pedTypeToAdd = PEDTYPE_GANG1 + i; + break; + } + + if (i == NUM_GANGS - 1) { + modelToAdd = ChooseCivilianOccupation(zoneInfo.pedGroup); + if (modelToAdd == -1) + return; + pedTypeToAdd = ((CPedModelInfo*)CModelInfo::GetModelInfo(modelToAdd))->m_pedType; + } + + } + } + if (!addCop && m_AllRandomPedsThisType > PEDTYPE_PLAYER1) + pedTypeToAdd = m_AllRandomPedsThisType; + + if (pedTypeToAdd >= PEDTYPE_GANG1 && pedTypeToAdd <= PEDTYPE_GANG9) { + int randVal = CGeneral::GetRandomNumber() % 100; + if (randVal < 50) + return; + + if (randVal < 57) { + pedAmount = 1; + } else if (randVal >= 74) { + if (randVal >= 85) + pedAmount = 4; + else + pedAmount = 3; + } else { + pedAmount = 2; + } + } else + pedAmount = 1; + + CVector generatedCoors; + int node1, node2; + float randomPos; + bool foundCoors = !!ThePaths.GeneratePedCreationCoors(playerCentreOfWorld.x, playerCentreOfWorld.y, minDist, maxDist, minDistOffScreen, maxDistOffScreen, + &generatedCoors, &node1, &node2, &randomPos, nil); + + if (!foundCoors) + return; + + for (int i = 0; i < pedAmount; ++i) { + if (pedTypeToAdd >= PEDTYPE_GANG1 && pedTypeToAdd <= PEDTYPE_GANG9) + modelToAdd = ChooseGangOccupation(pedTypeToAdd - PEDTYPE_GANG1); + + if (pedTypeToAdd == PEDTYPE_COP) { + // Unused code, ChoosePolicePedOccupation returns COP_STREET. Spawning FBI/SWAT/Army done in somewhere else. + if (modelToAdd == COP_STREET) { + if (!CModelInfo::GetModelInfo(MI_COP)->GetRwObject()) + return; + + } else if (modelToAdd == COP_FBI) { + if (!CModelInfo::GetModelInfo(MI_FBI)->GetRwObject()) + return; + + } else if (modelToAdd == COP_SWAT) { + if (!CModelInfo::GetModelInfo(MI_SWAT)->GetRwObject()) + return; + + } else if (modelToAdd == COP_ARMY && !CModelInfo::GetModelInfo(MI_ARMY)->GetRwObject()) { + return; + } + } else if (!CModelInfo::GetModelInfo(modelToAdd)->GetRwObject()) { + return; + } + generatedCoors.z += 0.7f; + + // What? How can this not be met? + if (i < pedAmount) { + //rand() + if (gangLeader) { + // Align gang members in formation. (btw i can't be 0 in here) + float offsetMin = i * 0.75f; + float offsetMax = (i + 1.0f) * 0.75f - offsetMin; + float xOffset = CGeneral::GetRandomNumberInRange(offsetMin, offsetMin + offsetMax); + float yOffset = CGeneral::GetRandomNumberInRange(offsetMin, offsetMin + offsetMax); + if (CGeneral::GetRandomNumber() & 1) + xOffset = -xOffset; + if (CGeneral::GetRandomNumber() & 1) + yOffset = -yOffset; + generatedCoors.x = xOffset + gangLeader->GetPosition().x; + generatedCoors.y = yOffset + gangLeader->GetPosition().y; + } + } + if (!CPedPlacement::IsPositionClearForPed(&generatedCoors)) + break; + + // Why no love for last gang member?! + if (i + 1 < pedAmount) { + bool foundGround; + float groundZ = CWorld::FindGroundZFor3DCoord(generatedCoors.x, generatedCoors.y, 2.0f + generatedCoors.z, &foundGround) + 0.7f; + if (!foundGround) + return; + + generatedCoors.z = Max(generatedCoors.z, groundZ); + } + bool farEnoughToAdd = true; + if (TheCamera.IsSphereVisible(generatedCoors, 2.0f)) { + if (PedCreationDistMultiplier() * MIN_CREATION_DIST > (generatedCoors - playerCentreOfWorld).Magnitude2D()) + farEnoughToAdd = false; + } + if (!farEnoughToAdd) + break; + CPed *newPed = AddPed((ePedType)pedTypeToAdd, modelToAdd, generatedCoors); + newPed->SetWanderPath(CGeneral::GetRandomNumberInRange(0, 8)); + + if (i != 0) { + // Gang member + newPed->SetLeader(gangLeader); +#if !defined(FIX_BUGS) && GTA_VERSION >= GTA3_PC_10 + // seems to be a miami leftover (this code is not on PS2) but gang peds end up just being frozen + newPed->SetPedState(PED_UNKNOWN); + gangLeader->SetPedState(PED_UNKNOWN); + newPed->m_fRotationCur = CGeneral::GetRadianAngleBetweenPoints( + gangLeader->GetPosition().x, gangLeader->GetPosition().y, + newPed->GetPosition().x, newPed->GetPosition().y); + newPed->m_fRotationDest = newPed->m_fRotationCur; +#endif + } else { + gangLeader = newPed; + } + CVisibilityPlugins::SetClumpAlpha(newPed->GetClump(), 0); + /* + // Pointless, this is already a for loop + if (i + 1 > pedAmount) + break; + if (pedAmount <= 1) + break; */ + } + } +} + +CPed* +CPopulation::AddPedInCar(CVehicle* car) +{ + int defaultModel = MI_MALE01; + bool imSureThatModelIsLoaded = true; + CVector coors = FindPlayerCoors(); + CZoneInfo zoneInfo; + int pedType; + + // May be eCopType, model index or non-sense(for medic), AddPed knows that by looking to ped type. + int preferredModel; + + CTheZones::GetZoneInfoForTimeOfDay(&coors, &zoneInfo); + switch (car->GetModelIndex()) { + case MI_FIRETRUCK: + preferredModel = 0; + pedType = PEDTYPE_FIREMAN; + break; + case MI_AMBULAN: + preferredModel = 0; + pedType = PEDTYPE_EMERGENCY; + break; + case MI_FBICAR: + preferredModel = COP_FBI; + pedType = PEDTYPE_COP; + break; + case MI_POLICE: + preferredModel = COP_STREET; + pedType = PEDTYPE_COP; + break; + case MI_ENFORCER: + preferredModel = COP_SWAT; + pedType = PEDTYPE_COP; + break; + case MI_RHINO: + case MI_BARRACKS: + preferredModel = COP_ARMY; + pedType = PEDTYPE_COP; + break; + case MI_TAXI: + case MI_CABBIE: + case MI_BORGNINE: + if (CGeneral::GetRandomTrueFalse()) { + pedType = PEDTYPE_CIVMALE; + preferredModel = MI_TAXI_D; + break; + } + defaultModel = MI_TAXI_D; + + // fall through + default: + int gangOfPed = 0; + imSureThatModelIsLoaded = false; + + while (gangOfPed < NUM_GANGS && CGangs::GetGangInfo(gangOfPed)->m_nVehicleMI != car->GetModelIndex()) + gangOfPed++; + + if (gangOfPed < NUM_GANGS) { + pedType = gangOfPed + PEDTYPE_GANG1; + preferredModel = ChooseGangOccupation(gangOfPed); + } else if (gangOfPed == NUM_GANGS) { + CVehicleModelInfo *carModelInfo = ((CVehicleModelInfo *)CModelInfo::GetModelInfo(car->GetModelIndex())); + int i = 15; + for(; i >= 0; i--) { + // Should return random model each time + preferredModel = ChooseCivilianOccupation(zoneInfo.pedGroup); + if (preferredModel == -1) + preferredModel = defaultModel; + + if (((CPedModelInfo*)CModelInfo::GetModelInfo(preferredModel))->m_carsCanDrive & (1 << carModelInfo->m_vehicleClass)) + break; + } + if (i == -1) + preferredModel = defaultModel; + + pedType = ((CPedModelInfo*)CModelInfo::GetModelInfo(preferredModel))->m_pedType; + } + break; + } + if (!imSureThatModelIsLoaded && !((CPedModelInfo*)CModelInfo::GetModelInfo(preferredModel))->GetRwObject()) { + preferredModel = defaultModel; + pedType = ((CPedModelInfo*)CModelInfo::GetModelInfo(defaultModel))->m_pedType; + } + + CPed *newPed = CPopulation::AddPed((ePedType)pedType, preferredModel, car->GetPosition()); + newPed->bUsesCollision = false; + + // what?? + if (pedType != PEDTYPE_COP) { + newPed->SetCurrentWeapon(WEAPONTYPE_COLT45); + newPed->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(newPed->GetWeapon()->m_eWeaponType)->m_nModelId); + } + + // Miami leftover + if (car->m_vehType == VEHICLE_TYPE_BIKE) { + newPed->m_pVehicleAnim = CAnimManager::BlendAnimation(newPed->GetClump(), ASSOCGRP_STD, ((CBike*)car)->m_bikeSitAnimation, 100.0f); + } else + + // FIX: Make peds comfortable while driving car/boat +#ifdef FIX_BUGS + { + newPed->m_pVehicleAnim = CAnimManager::BlendAnimation(newPed->GetClump(), ASSOCGRP_STD, car->GetDriverAnim(), 100.0f); + } +#else + { + newPed->m_pVehicleAnim = CAnimManager::BlendAnimation(newPed->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT, 100.0f); + } +#endif + + newPed->StopNonPartialAnims(); + return newPed; +} + +void +CPopulation::MoveCarsAndPedsOutOfAbandonedZones() +{ + eLevelName level; + int zone; + int frame = CTimer::GetFrameCounter() & 7; + if (frame == 1) { + int movedVehicleCount = 0; + int poolSize = CPools::GetVehiclePool()->GetSize(); + for (int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + + CVehicle* veh = CPools::GetVehiclePool()->GetSlot(poolIndex); + if (veh && veh->m_nZoneLevel == LEVEL_GENERIC && veh->IsCar()) { + + if(veh->GetStatus() != STATUS_ABANDONED && veh->GetStatus() != STATUS_WRECKED && veh->GetStatus() != STATUS_PLAYER && + veh->GetStatus() != STATUS_PLAYER_REMOTE) { + + CVector vehPos(veh->GetPosition()); + CPopulation::FindCollisionZoneForCoors(&vehPos, &zone, &level); + + // Level 0 is transition zones, and we don't wanna touch cars on transition zones. + if (level != LEVEL_GENERIC && level != CCollision::ms_collisionInMemory && vehPos.z > -4.0f) { + if (veh->bIsLocked || !veh->CanBeDeleted()) { + switch (movedVehicleCount & 3) { + case 0: + veh->SetPosition(RegenerationPoint_a); + break; + case 1: + veh->SetPosition(RegenerationPoint_b); + break; + case 2: + veh->SetPosition(RegenerationPoint_a.x, RegenerationPoint_b.y, RegenerationPoint_a.z); + break; + case 3: + veh->SetPosition(RegenerationPoint_b.x, RegenerationPoint_a.y, RegenerationPoint_a.z); + break; + default: + break; + } + veh->GetMatrix().GetPosition().z += (movedVehicleCount / 4) * 7.0f; + veh->GetMatrix().GetForward() = RegenerationFront; + ((CAutomobile*)veh)->PlaceOnRoadProperly(); + CCarCtrl::JoinCarWithRoadSystem(veh); + CTheScripts::ClearSpaceForMissionEntity(veh->GetPosition(), veh); + ++movedVehicleCount; + } else { + CWorld::Remove(veh); + delete veh; + } + } + } + } + } + } else if (frame == 5) { + int poolSize = CPools::GetPedPool()->GetSize(); + for (int poolIndex = poolSize - 1; poolIndex >= 0; poolIndex--) { + + CPed *ped = CPools::GetPedPool()->GetSlot(poolIndex); + if (ped && ped->m_nZoneLevel == LEVEL_GENERIC && !ped->bInVehicle) { + + CVector pedPos(ped->GetPosition()); + CPopulation::FindCollisionZoneForCoors(&pedPos, &zone, &level); + + // Level 0 is transition zones, and we don't wanna touch peds on transition zones. + if (level != LEVEL_GENERIC && level != CCollision::ms_collisionInMemory && pedPos.z > -4.0f) { + if (ped->CanBeDeleted()) { + CWorld::Remove(ped); + delete ped; + } else if (ped->m_nPedType != PEDTYPE_PLAYER1 && ped->m_nPedType != PEDTYPE_PLAYER2) { + ped->SetPosition(RegenerationPoint_a); + + bool foundGround; + float groundZ = CWorld::FindGroundZFor3DCoord(ped->GetPosition().x, ped->GetPosition().y, + ped->GetPosition().z + 2.0f, &foundGround); + + if (foundGround) { + ped->GetMatrix().GetPosition().z = 1.0f + groundZ; + //ped->GetPosition().z += 0.0f; + CTheScripts::ClearSpaceForMissionEntity(ped->GetPosition(), ped); + } + } + } + } + } + } +} + +void +CPopulation::ConvertAllObjectsToDummyObjects() +{ + uint32 i = CPools::GetObjectPool()->GetSize(); + while(i--) { + CObject *obj = CPools::GetObjectPool()->GetSlot(i); + if (obj) { + if (obj->CanBeDeleted()) + ConvertToDummyObject(obj); + } + } +} + +void +CPopulation::ConvertToRealObject(CDummyObject *dummy) +{ + if (!TestSafeForRealObject(dummy)) + return; + + CObject *obj = new CObject(dummy); + if (!obj) + return; + + CWorld::Remove(dummy); + delete dummy; + CWorld::Add(obj); + + if (IsGlass(obj->GetModelIndex())) { + obj->bIsVisible = false; + } else if (obj->GetModelIndex() == MI_BUOY) { + obj->SetIsStatic(false); + obj->m_vecMoveSpeed = CVector(0.0f, 0.0f, -0.001f); + obj->bTouchingWater = true; + obj->AddToMovingList(); + } +} + +void +CPopulation::ConvertToDummyObject(CObject *obj) +{ + CDummyObject *dummy = new CDummyObject(obj); + + dummy->GetMatrix() = obj->m_objectMatrix; + dummy->GetMatrix().UpdateRW(); + dummy->UpdateRwFrame(); + + if (IsGlass(obj->GetModelIndex())) + dummy->bIsVisible = false; + + CWorld::Remove(obj); + delete obj; + CWorld::Add(dummy); +} + +bool +CPopulation::TestRoomForDummyObject(CObject *obj) +{ + int16 collidingObjs; + CWorld::FindObjectsKindaColliding(obj->m_objectMatrix.GetPosition(), CModelInfo::GetColModel(obj->GetModelIndex())->boundingSphere.radius, + false, &collidingObjs, 2, nil, false, true, true, false, false); + + return collidingObjs == 0; +} + +bool +CPopulation::TestSafeForRealObject(CDummyObject *dummy) +{ + CPtrNode *ptrNode; + CColModel *dummyCol = dummy->GetColModel(); + + float radius = dummyCol->boundingSphere.radius; + int minX = CWorld::GetSectorIndexX(dummy->GetPosition().x - radius); + if (minX < 0) minX = 0; + int minY = CWorld::GetSectorIndexY(dummy->GetPosition().y - radius); + if (minY < 0) minY = 0; + int maxX = CWorld::GetSectorIndexX(dummy->GetPosition().x + radius); +#ifdef FIX_BUGS + if (maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; +#else + if (maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X; +#endif + + int maxY = CWorld::GetSectorIndexY(dummy->GetPosition().y + radius); +#ifdef FIX_BUGS + if (maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; +#else + if (maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y; +#endif + + float colRadius = dummy->GetBoundRadius(); + CVUVECTOR colCentre; + dummy->GetBoundCentre(colCentre); + + static CColPoint aTempColPoints[MAX_COLLISION_POINTS]; + + for (int curY = minY; curY <= maxY; curY++) { + for (int curX = minX; curX <= maxX; curX++) { + CSector *sector = CWorld::GetSector(curX, curY); + + for (ptrNode = sector->m_lists[ENTITYLIST_VEHICLES].first; ptrNode; ptrNode = ptrNode->next) { + CVehicle *veh = (CVehicle*)ptrNode->item; + if (veh->m_scanCode != CWorld::GetCurrentScanCode()) { + if (veh->GetIsTouching(colCentre, colRadius)) { + veh->m_scanCode = CWorld::GetCurrentScanCode(); + if (CCollision::ProcessColModels(dummy->GetMatrix(), *dummyCol, veh->GetMatrix(), *veh->GetColModel(), aTempColPoints, nil, nil) > 0) + return false; + } + } + } + + for (ptrNode = sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP].first; ptrNode; ptrNode = ptrNode->next) { + CVehicle *veh = (CVehicle*)ptrNode->item; + if (veh->m_scanCode != CWorld::GetCurrentScanCode()) { + if (veh->GetIsTouching(colCentre, colRadius)) { + veh->m_scanCode = CWorld::GetCurrentScanCode(); + if (CCollision::ProcessColModels(dummy->GetMatrix(), *dummyCol, veh->GetMatrix(), *veh->GetColModel(), aTempColPoints, nil, nil) > 0) + return false; + } + } + } + } + } + return true; +} + +void +CPopulation::ManagePopulation(void) +{ + int frameMod32 = CTimer::GetFrameCounter() & 31; + CVector playerPos = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + + // Why this code is here?! Delete temporary objects when they got too far, and convert others to "dummy" objects. (like lamp posts) + int objectPoolSize = CPools::GetObjectPool()->GetSize(); + for (int i = objectPoolSize * frameMod32 / 32; i < objectPoolSize * (frameMod32 + 1) / 32; i++) { + CObject *obj = CPools::GetObjectPool()->GetSlot(i); + if (obj && obj->CanBeDeleted()) { + if ((obj->GetPosition() - playerPos).Magnitude() <= 80.0f || + (obj->m_objectMatrix.GetPosition() - playerPos).Magnitude() <= 80.0f) { + if (obj->ObjectCreatedBy == TEMP_OBJECT && CTimer::GetTimeInMilliseconds() > obj->m_nEndOfLifeTime) { + CWorld::Remove(obj); + delete obj; + } + } else { + if (obj->ObjectCreatedBy == TEMP_OBJECT) { + CWorld::Remove(obj); + delete obj; + } else if (obj->ObjectCreatedBy != CUTSCENE_OBJECT && TestRoomForDummyObject(obj)) { + ConvertToDummyObject(obj); + } + } + } + } + + // Convert them back to real objects. Dummy objects don't have collisions, so they need to be converted. + int dummyPoolSize = CPools::GetDummyPool()->GetSize(); + for (int i = dummyPoolSize * frameMod32 / 32; i < dummyPoolSize * (frameMod32 + 1) / 32; i++) { + CDummy *dummy = CPools::GetDummyPool()->GetSlot(i); + if (dummy) { + if ((dummy->GetPosition() - playerPos).Magnitude() < 80.0f) + ConvertToRealObject((CDummyObject*)dummy); + } + } + + int pedPoolSize = CPools::GetPedPool()->GetSize(); +#ifndef SQUEEZE_PERFORMANCE + for (int poolIndex = pedPoolSize-1; poolIndex >= 0; poolIndex--) { +#else + for (int poolIndex = (pedPoolSize * (frameMod32 + 1) / 32) - 1; poolIndex >= pedPoolSize * frameMod32 / 32; poolIndex--) { +#endif + CPed *ped = CPools::GetPedPool()->GetSlot(poolIndex); + + if (ped && !ped->IsPlayer() && ped->CanBeDeleted() && !ped->bInVehicle) { + if (ped->m_nPedState == PED_DEAD && CTimer::GetTimeInMilliseconds() - ped->m_bloodyFootprintCountOrDeathTime > 60000) + ped->bFadeOut = true; + + if (ped->bFadeOut && CVisibilityPlugins::GetClumpAlpha(ped->GetClump()) == 0) { + RemovePed(ped); + continue; + } + + float dist = (ped->GetPosition() - playerPos).Magnitude2D(); + + bool pedIsFarAway = false; + if (PedCreationDistMultiplier() * (PED_REMOVE_DIST_SPECIAL * TheCamera.GenerationDistMultiplier) < dist + || (!ped->bCullExtraFarAway && PedCreationDistMultiplier() * PED_REMOVE_DIST * TheCamera.GenerationDistMultiplier < dist) +#ifndef EXTENDED_OFFSCREEN_DESPAWN_RANGE + || (PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE) * OFFSCREEN_CREATION_MULT < dist + && !ped->GetIsOnScreen() + && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_SNIPER + && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_SNIPER_RUNABOUT + && !TheCamera.Cams[TheCamera.ActiveCam].LookingLeft + && !TheCamera.Cams[TheCamera.ActiveCam].LookingRight + && !TheCamera.Cams[TheCamera.ActiveCam].LookingBehind) +#endif + ) + pedIsFarAway = true; + + if (!pedIsFarAway) + continue; + + if (ped->m_nPedState == PED_DEAD && !ped->bFadeOut) { + CVector pedPos = ped->GetPosition(); + + float randAngle = (uint8) CGeneral::GetRandomNumber() * (3.14f / 128.0f); // Not PI, 3.14 + switch (CGeneral::GetRandomNumber() % 3) { + case 0: + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpOutline1Tex, &pedPos, + 0.9f * Cos(randAngle), 0.9f * Sin(randAngle), 0.9f * Sin(randAngle), -0.9f * Cos(randAngle), + 255, 255, 255, 255, 4.0f, 40000, 1.0f); + break; + case 1: + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpOutline2Tex, &pedPos, + 0.9f * Cos(randAngle), 0.9f * Sin(randAngle), 0.9f * Sin(randAngle), -0.9f * Cos(randAngle), + 255, 255, 255, 255, 4.0f, 40000, 1.0f); + break; + case 2: + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpOutline3Tex, &pedPos, + 0.9f * Cos(randAngle), 0.9f * Sin(randAngle), 0.9f * Sin(randAngle), -0.9f * Cos(randAngle), + 255, 255, 255, 255, 4.0f, 40000, 1.0f); + break; + default: + break; + } + } + if (ped->GetIsOnScreen()) + ped->bFadeOut = true; + else + RemovePed(ped); + } + } +} diff --git a/src/peds/Population.h b/src/peds/Population.h new file mode 100644 index 0000000..61f0bdb --- /dev/null +++ b/src/peds/Population.h @@ -0,0 +1,89 @@ +#pragma once + +#include "Game.h" +#include "PedType.h" + +class CPed; +class CVehicle; +class CDummyObject; +class CObject; + +struct PedGroup +{ + int32 models[NUMMODELSPERPEDGROUP]; +}; + +// Don't know the original name +struct RegenerationPoint +{ + eLevelName srcLevel; // this and below one may need to be exchanged + eLevelName destLevel; + float x1; + float x2; + float y1; + float y2; + float z1; + float z2; + RwV3d destPosA; + RwV3d destPosB; + RwV3d srcPosA; + RwV3d srcPosB; +}; + +class CPopulation +{ +public: + static PedGroup ms_pPedGroups[NUMPEDGROUPS]; + static bool ms_bGivePedsWeapons; + static int32 m_AllRandomPedsThisType; + static float PedDensityMultiplier; + static uint32 ms_nTotalMissionPeds; + static int32 MaxNumberOfPedsInUse; + static uint32 ms_nNumCivMale; + static uint32 ms_nNumCivFemale; + static uint32 ms_nNumCop; + static bool bZoneChangeHasHappened; + static uint32 ms_nNumEmergency; + static int8 m_CountDownToPedsAtStart; + static uint32 ms_nNumGang1; + static uint32 ms_nNumGang2; + static uint32 ms_nTotalPeds; + static uint32 ms_nNumGang3; + static uint32 ms_nTotalGangPeds; + static uint32 ms_nNumGang4; + static uint32 ms_nTotalCivPeds; + static uint32 ms_nNumGang5; + static uint32 ms_nNumDummy; + static uint32 ms_nNumGang6; + static uint32 ms_nNumGang9; + static uint32 ms_nNumGang7; + static uint32 ms_nNumGang8; + static CVector RegenerationPoint_a; + static CVector RegenerationPoint_b; + static CVector RegenerationFront; + + static void Initialise(); + static void Update(void); + static void LoadPedGroups(); + static void UpdatePedCount(ePedType, bool); + static void DealWithZoneChange(eLevelName oldLevel, eLevelName newLevel, bool); + static CPed *AddPedInCar(CVehicle *car); + static bool IsPointInSafeZone(CVector *coors); + static void RemovePed(CPed *ent); + static int32 ChooseCivilianOccupation(int32); + static int32 ChoosePolicePedOccupation(); + static int32 ChooseGangOccupation(int); + static void FindCollisionZoneForCoors(CVector*, int*, eLevelName*); + static void FindClosestZoneForCoors(CVector*, int*, eLevelName, eLevelName); + static void GeneratePedsAtStartOfGame(); + static float PedCreationDistMultiplier(); + static CPed *AddPed(ePedType pedType, uint32 mi, CVector const &coors); + static void AddToPopulation(float, float, float, float); + static void ManagePopulation(void); + static void MoveCarsAndPedsOutOfAbandonedZones(void); + static void ConvertToRealObject(CDummyObject*); + static void ConvertToDummyObject(CObject*); + static void ConvertAllObjectsToDummyObjects(void); + static bool TestRoomForDummyObject(CObject*); + static bool TestSafeForRealObject(CDummyObject*); +}; diff --git a/src/renderer/2dEffect.h b/src/renderer/2dEffect.h new file mode 100644 index 0000000..a8013b3 --- /dev/null +++ b/src/renderer/2dEffect.h @@ -0,0 +1,93 @@ +#pragma once + +enum { + EFFECT_LIGHT, + EFFECT_PARTICLE, + EFFECT_ATTRACTOR +}; + +enum { + LIGHT_ON, + LIGHT_ON_NIGHT, + LIGHT_FLICKER, + LIGHT_FLICKER_NIGHT, + LIGHT_FLASH1, + LIGHT_FLASH1_NIGHT, + LIGHT_FLASH2, + LIGHT_FLASH2_NIGHT, + LIGHT_FLASH3, + LIGHT_FLASH3_NIGHT, + LIGHT_RANDOM_FLICKER, + LIGHT_RANDOM_FLICKER_NIGHT, + LIGHT_SPECIAL, + LIGHT_BRIDGE_FLASH1, + LIGHT_BRIDGE_FLASH2, +}; + +enum { + ATTRACTORTYPE_ICECREAM, + ATTRACTORTYPE_STARE +}; + +enum { + LIGHTFLAG_LOSCHECK = 1, + // same order as CPointLights flags, must start at 2 + LIGHTFLAG_FOG_NORMAL = 2, // can have light and fog + LIGHTFLAG_FOG_ALWAYS = 4, // fog only + LIGHTFLAG_FOG = (LIGHTFLAG_FOG_NORMAL|LIGHTFLAG_FOG_ALWAYS) +}; + +class C2dEffect +{ +public: + struct Light { + float dist; + float range; // of pointlight + float size; + float shadowSize; + uint8 lightType; // LIGHT_ + uint8 roadReflection; + uint8 flareType; + uint8 shadowIntensity; + uint8 flags; // LIGHTFLAG_ + RwTexture *corona; + RwTexture *shadow; + }; + struct Particle { + int particleType; + CVector dir; + float scale; + }; + struct Attractor { + CVector dir; + int8 type; + uint8 probability; + }; + + CVector pos; + CRGBA col; + uint8 type; + union { + Light light; + Particle particle; + Attractor attractor; + }; + + C2dEffect(void) {} + void Shutdown(void){ + if(type == EFFECT_LIGHT){ + if(light.corona) + RwTextureDestroy(light.corona); +#if GTA_VERSION >= GTA3_PC_11 + light.corona = nil; +#endif + if(light.shadow) + RwTextureDestroy(light.shadow); +#if GTA_VERSION >= GTA3_PC_11 + light.shadow = nil; +#endif + } + } +}; + +VALIDATE_SIZE(C2dEffect, 0x34); diff --git a/src/renderer/Antennas.cpp b/src/renderer/Antennas.cpp new file mode 100644 index 0000000..5e30aca --- /dev/null +++ b/src/renderer/Antennas.cpp @@ -0,0 +1,129 @@ +#include "common.h" + +#include "main.h" +#include "Antennas.h" + +CAntenna CAntennas::aAntennas[NUMANTENNAS]; + +void +CAntennas::Init(void) +{ + int i; + for(i = 0; i < NUMANTENNAS; i++){ + aAntennas[i].active = false; + aAntennas[i].updatedLastFrame = false; + } +} + +// Free antennas that aren't used anymore +void +CAntennas::Update(void) +{ + int i; + + for(i = 0; i < NUMANTENNAS; i++){ + if(aAntennas[i].active && !aAntennas[i].updatedLastFrame) + aAntennas[i].active = false; + aAntennas[i].updatedLastFrame = false; + } +} + +// Add a new one or update an old one +void +CAntennas::RegisterOne(uint32 id, CVector dir, CVector position, float length) +{ + int i, j; + + for(i = 0; i < NUMANTENNAS; i++) + if(aAntennas[i].active && aAntennas[i].id == id) + break; + + if(i >= NUMANTENNAS){ + // not found, register new one + + // find empty slot + for(i = 0; i < NUMANTENNAS; i++) + if(!aAntennas[i].active) + break; + + // there is space + if(i < NUMANTENNAS){ + aAntennas[i].active = true; + aAntennas[i].updatedLastFrame = true; + aAntennas[i].id = id; + aAntennas[i].segmentLength = length/6.0f; + for(j = 0; j < 6; j++){ + aAntennas[i].pos[j] = position + dir*j*aAntennas[i].segmentLength; + aAntennas[i].speed[j] = CVector(0.0f, 0.0f, 0.0f); + } + } + }else{ + // found, update + aAntennas[i].Update(dir, position); + aAntennas[i].updatedLastFrame = true; + } +} + +static RwIm3DVertex vertexbufferA[2]; + +void +CAntennas::Render(void) +{ + int i, j; + + PUSH_RENDERGROUP("CAntennas::Render"); + for(i = 0; i < NUMANTENNAS; i++){ + if(!aAntennas[i].active) + continue; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + for(j = 0; j < 5; j++){ + RwIm3DVertexSetRGBA(&vertexbufferA[0], 200, 200, 200, 100); + RwIm3DVertexSetPos(&vertexbufferA[0], + aAntennas[i].pos[j].x, + aAntennas[i].pos[j].y, + aAntennas[i].pos[j].z); + RwIm3DVertexSetRGBA(&vertexbufferA[1], 200, 200, 200, 100); + RwIm3DVertexSetPos(&vertexbufferA[1], + aAntennas[i].pos[j+1].x, + aAntennas[i].pos[j+1].y, + aAntennas[i].pos[j+1].z); + + // LittleTest(); + if(RwIm3DTransform(vertexbufferA, 2, nil, 0)){ + RwIm3DRenderLine(0, 1); + RwIm3DEnd(); + } + } + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + + POP_RENDERGROUP(); +} + +void +CAntenna::Update(CVector dir, CVector basepos) +{ + int i; + + pos[0] = basepos; + pos[1] = basepos + dir*segmentLength; + + for(i = 2; i < 6; i++){ + CVector basedir = pos[i-1] - pos[i-2]; + CVector newdir = pos[i] - pos[i-1] + // drag along + dir*0.1f + // also drag up a bit for stiffness + speed[i]; // and keep moving + newdir.Normalise(); + newdir *= segmentLength; + CVector newpos = pos[i-1] + (basedir + newdir)/2.0f; + speed[i] = (newpos - pos[i])*0.9f; + pos[i] = newpos; + } +} diff --git a/src/renderer/Antennas.h b/src/renderer/Antennas.h new file mode 100644 index 0000000..47cb1da --- /dev/null +++ b/src/renderer/Antennas.h @@ -0,0 +1,25 @@ +#pragma once + +class CAntenna +{ +public: + bool active; + bool updatedLastFrame; + uint32 id; + float segmentLength; + CVector pos[6]; + CVector speed[6]; + + void Update(CVector dir, CVector pos); +}; + +class CAntennas +{ + // no need to use game's array + static CAntenna aAntennas[NUMANTENNAS]; +public: + static void Init(void); + static void Update(void); + static void RegisterOne(uint32 id, CVector dir, CVector position, float length); + static void Render(void); +}; diff --git a/src/renderer/Clouds.cpp b/src/renderer/Clouds.cpp new file mode 100644 index 0000000..957844a --- /dev/null +++ b/src/renderer/Clouds.cpp @@ -0,0 +1,466 @@ +#include "common.h" + +#include "main.h" +#include "Sprite.h" +#include "Sprite2d.h" +#include "General.h" +#include "Coronas.h" +#include "Camera.h" +#include "TxdStore.h" +#include "Weather.h" +#include "Clock.h" +#include "Timer.h" +#include "Timecycle.h" +#include "Renderer.h" +#include "Clouds.h" + +#define SMALLSTRIPHEIGHT 4.0f +#define HORIZSTRIPHEIGHT 48.0f + +RwTexture *gpCloudTex[5]; + +float CClouds::CloudRotation; +uint32 CClouds::IndividualRotation; + +float CClouds::ms_cameraRoll; +float CClouds::ms_horizonZ; +CRGBA CClouds::ms_colourTop; +CRGBA CClouds::ms_colourBottom; + +void +CClouds::Init(void) +{ + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle")); + gpCloudTex[0] = RwTextureRead("cloud1", nil); + gpCloudTex[1] = RwTextureRead("cloud2", nil); + gpCloudTex[2] = RwTextureRead("cloud3", nil); + gpCloudTex[3] = RwTextureRead("cloudhilit", nil); + gpCloudTex[4] = RwTextureRead("cloudmasked", nil); + CTxdStore::PopCurrentTxd(); + CloudRotation = 0.0f; +} + +void +CClouds::Shutdown(void) +{ + RwTextureDestroy(gpCloudTex[0]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[0] = nil; +#endif + RwTextureDestroy(gpCloudTex[1]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[1] = nil; +#endif + RwTextureDestroy(gpCloudTex[2]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[2] = nil; +#endif + RwTextureDestroy(gpCloudTex[3]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[3] = nil; +#endif + RwTextureDestroy(gpCloudTex[4]); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex[4] = nil; +#endif +} + +void +CClouds::Update(void) +{ + float s = Sin(TheCamera.Orientation - 0.85f); +#ifdef FIX_BUGS + CloudRotation += CWeather::Wind*s*0.0025f*CTimer::GetTimeStepFix(); + IndividualRotation += (CWeather::Wind*CTimer::GetTimeStep() + 0.3f*CTimer::GetTimeStepFix()) * 60.0f; +#else + CloudRotation += CWeather::Wind*s*0.0025f; + IndividualRotation += (CWeather::Wind*CTimer::GetTimeStep() + 0.3f) * 60.0f; +#endif +} + +float StarCoorsX[9] = { 0.0f, 0.05f, 0.12f, 0.5f, 0.8f, 0.6f, 0.27f, 0.55f, 0.75f }; +float StarCoorsY[9] = { 0.0f, 0.45f, 0.9f, 1.0f, 0.85f, 0.52f, 0.48f, 0.35f, 0.2f }; +float StarSizes[9] = { 1.0f, 1.4f, 0.9f, 1.0f, 0.6f, 1.5f, 1.3f, 1.0f, 0.8f }; + +float LowCloudsX[12] = { 1.0f, 0.7f, 0.0f, -0.7f, -1.0f, -0.7f, 0.0f, 0.7f, 0.8f, -0.8f, 0.4f, -0.4f }; +float LowCloudsY[12] = { 0.0f, -0.7f, -1.0f, -0.7f, 0.0f, 0.7f, 1.0f, 0.7f, 0.4f, 0.4f, -0.8f, -0.8f }; +float LowCloudsZ[12] = { 0.0f, 1.0f, 0.5f, 0.0f, 1.0f, 0.3f, 0.9f, 0.4f, 1.3f, 1.4f, 1.2f, 1.7f }; + +float CoorsOffsetX[37] = { + 0.0f, 60.0f, 72.0f, 48.0f, 21.0f, 12.0f, + 9.0f, -3.0f, -8.4f, -18.0f, -15.0f, -36.0f, + -40.0f, -48.0f, -60.0f, -24.0f, 100.0f, 100.0f, + 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, + 100.0f, 100.0f, -30.0f, -20.0f, 10.0f, 30.0f, + 0.0f, -100.0f, -100.0f, -100.0f, -100.0f, -100.0f, -100.0f +}; +float CoorsOffsetY[37] = { + 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, + 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, 100.0f, + 100.0f, 100.0f, 100.0f, 100.0f, -30.0f, 10.0f, + -25.0f, -5.0f, 28.0f, -10.0f, 10.0f, 0.0f, + 15.0f, 40.0f, -100.0f, -100.0f, -100.0f, -100.0f, + -100.0f, -40.0f, -20.0f, 0.0f, 10.0f, 30.0f, 35.0f +}; +float CoorsOffsetZ[37] = { + 2.0f, 1.0f, 0.0f, 0.3f, 0.7f, 1.4f, + 1.7f, 0.24f, 0.7f, 1.3f, 1.6f, 1.0f, + 1.2f, 0.3f, 0.7f, 1.4f, 0.0f, 0.1f, + 0.5f, 0.4f, 0.55f, 0.75f, 1.0f, 1.4f, + 1.7f, 2.0f, 2.0f, 2.3f, 1.9f, 2.4f, + 2.0f, 2.0f, 1.5f, 1.2f, 1.7f, 1.5f, 2.1f +}; + +uint8 BowRed[6] = { 30, 30, 30, 10, 0, 15 }; +uint8 BowGreen[6] = { 0, 15, 30, 30, 0, 0 }; +uint8 BowBlue[6] = { 0, 0, 0, 10, 30, 30 }; + +void +CClouds::Render(void) +{ + int i; + float szx, szy; + RwV3d screenpos; + RwV3d worldpos; + + PUSH_RENDERGROUP("CClouds::Render"); + + CCoronas::SunBlockedByClouds = false; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + CSprite::InitSpriteBuffer(); + + int minute = CClock::GetHours()*60 + CClock::GetMinutes(); + RwV3d campos = TheCamera.GetPosition(); + + // Moon + int moonfadeout = Abs(minute - 180); // fully visible at 3AM + if(moonfadeout < 180){ // fade in/out 3 hours + float coverage = Max(CWeather::Foggyness, CWeather::CloudCoverage); + int brightness = (1.0f - coverage) * (180 - moonfadeout); + RwV3d pos = { 0.0f, -100.0f, 15.0f }; + RwV3dAdd(&worldpos, &campos, &pos); + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[2])); + if(CCoronas::bSmallMoon){ + szx *= 4.0f; + szy *= 4.0f; + }else{ + szx *= 10.0f; + szy *= 10.0f; + } + CSprite::RenderOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + szx, szy, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255); + } + } + + // The R* logo + int starintens = 0; + if(CClock::GetHours() < 22 && CClock::GetHours() > 5) + starintens = 0; + else if(CClock::GetHours() > 22 || CClock::GetHours() < 5) + starintens = 255; + else if(CClock::GetHours() == 22) + starintens = 255 * CClock::GetMinutes()/60.0f; + else if(CClock::GetHours() == 5) + starintens = 255 * (60 - CClock::GetMinutes())/60.0f; + if(starintens != 0){ + float coverage = Max(CWeather::Foggyness, CWeather::CloudCoverage); + int brightness = (1.0f - coverage) * starintens; + + // R + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + for(i = 0; i < 11; i++){ + RwV3d pos = { 100.0f, 0.0f, 10.0f }; + if(i >= 9) pos.x = -pos.x; + RwV3dAdd(&worldpos, &campos, &pos); + worldpos.y -= 90.0f*StarCoorsX[i%9]; + worldpos.z += 80.0f*StarCoorsY[i%9]; + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + float sz = 0.8f*StarSizes[i%9]; + CSprite::RenderBufferedOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + szx*sz, szy*sz, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255); + } + } + CSprite::FlushSpriteBuffer(); + + // * + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + RwV3d pos = { 100.0f, 0.0f, 10.0f }; + RwV3dAdd(&worldpos, &campos, &pos); + worldpos.y -= 90.0f; + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + brightness *= (CGeneral::GetRandomNumber()&127) / 640.0f + 0.5f; + CSprite::RenderOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + szx*5.0f, szy*5.0f, brightness, brightness, brightness, 255, 1.0f/screenpos.z, 255); + } + } + + // Low clouds + float lowcloudintensity = 1.0f - Max(CWeather::Foggyness, CWeather::CloudCoverage); + int r = CTimeCycle::GetLowCloudsRed() * lowcloudintensity; + int g = CTimeCycle::GetLowCloudsGreen() * lowcloudintensity; + int b = CTimeCycle::GetLowCloudsBlue() * lowcloudintensity; + for(int cloudtype = 0; cloudtype < 3; cloudtype++){ + for(i = cloudtype; i < 12; i += 3){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCloudTex[cloudtype])); + RwV3d pos = { 800.0f*LowCloudsX[i], 800.0f*LowCloudsY[i], 60.0f*LowCloudsZ[i] }; + worldpos.x = campos.x + pos.x; + worldpos.y = campos.y + pos.y; + worldpos.z = 40.0f + pos.z; + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)) + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(screenpos.x, screenpos.y, screenpos.z, + szx*320.0f, szy*40.0f, r, g, b, 255, 1.0f/screenpos.z, ms_cameraRoll, 255); + } + CSprite::FlushSpriteBuffer(); + } + + // Fluffy clouds + float rot_sin = Sin(CloudRotation); + float rot_cos = Cos(CloudRotation); + int fluffyalpha = 160 * (1.0f - CWeather::Foggyness); + if(fluffyalpha != 0){ + static bool bCloudOnScreen[37]; + float hilight; + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCloudTex[4])); + for(i = 0; i < 37; i++){ + RwV3d pos = { 2.0f*CoorsOffsetX[i], 2.0f*CoorsOffsetY[i], 40.0f*CoorsOffsetZ[i] + 40.0f }; + worldpos.x = pos.x*rot_cos + pos.y*rot_sin + campos.x; + worldpos.y = pos.x*rot_sin - pos.y*rot_cos + campos.y; + worldpos.z = pos.z; + + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + float sundist = Sqrt(sq(screenpos.x-CCoronas::SunScreenX) + sq(screenpos.y-CCoronas::SunScreenY)); + int tr = CTimeCycle::GetFluffyCloudsTopRed(); + int tg = CTimeCycle::GetFluffyCloudsTopGreen(); + int tb = CTimeCycle::GetFluffyCloudsTopBlue(); + int br = CTimeCycle::GetFluffyCloudsBottomRed(); + int bg = CTimeCycle::GetFluffyCloudsBottomGreen(); + int bb = CTimeCycle::GetFluffyCloudsBottomBlue(); + if(sundist < SCREEN_WIDTH/2){ + hilight = (1.0f - Max(CWeather::Foggyness, CWeather::CloudCoverage)) * (1.0f - sundist/(SCREEN_WIDTH/2)); + tr = tr*(1.0f-hilight) + 255*hilight; + tg = tg*(1.0f-hilight) + 190*hilight; + tb = tb*(1.0f-hilight) + 190*hilight; + br = br*(1.0f-hilight) + 255*hilight; + bg = bg*(1.0f-hilight) + 190*hilight; + bb = bb*(1.0f-hilight) + 190*hilight; + if(sundist < SCREEN_WIDTH/10) + CCoronas::SunBlockedByClouds = true; + }else + hilight = 0.0f; + CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(screenpos.x, screenpos.y, screenpos.z, + szx*55.0f, szy*55.0f, + tr, tg, tb, br, bg, bb, 0.0f, -1.0f, + 1.0f/screenpos.z, + (uint16)IndividualRotation/65336.0f * 6.28f + ms_cameraRoll, + fluffyalpha); + bCloudOnScreen[i] = true; + }else + bCloudOnScreen[i] = false; + } + CSprite::FlushSpriteBuffer(); + + // Highlights + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCloudTex[3])); + + for(i = 0; i < 37; i++){ + RwV3d pos = { 2.0f*CoorsOffsetX[i], 2.0f*CoorsOffsetY[i], 40.0f*CoorsOffsetZ[i] + 40.0f }; + worldpos.x = pos.x*rot_cos + pos.y*rot_sin + campos.x; + worldpos.y = pos.x*rot_sin - pos.y*rot_cos + campos.y; + worldpos.z = pos.z; + if(bCloudOnScreen[i] && CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)){ + // BUG: this is stupid....would have to do this for each cloud individually + if(hilight > 0.0f){ + CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(screenpos.x, screenpos.y, screenpos.z, + szx*30.0f, szy*30.0f, + 200*hilight, 0, 0, 255, 1.0f/screenpos.z, + 1.7f - CGeneral::GetATanOfXY(screenpos.x-CCoronas::SunScreenX, screenpos.y-CCoronas::SunScreenY) + CClouds::ms_cameraRoll, 255); + } + } + } + CSprite::FlushSpriteBuffer(); + } + + // Rainbow + if(CWeather::Rainbow != 0.0f){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + for(i = 0; i < 6; i++){ + RwV3d pos = { i*1.5f, 100.0f, 5.0f }; + RwV3dAdd(&worldpos, &campos, &pos); + if(CSprite::CalcScreenCoors(worldpos, &screenpos, &szx, &szy, false)) + CSprite::RenderBufferedOneXLUSprite(screenpos.x, screenpos.y, screenpos.z, + 2.0f*szx, 50.0*szy, + BowRed[i]*CWeather::Rainbow, BowGreen[i]*CWeather::Rainbow, BowBlue[i]*CWeather::Rainbow, + 255, 1.0f/screenpos.z, 255); + + } + CSprite::FlushSpriteBuffer(); + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + POP_RENDERGROUP(); +} + +bool +UseDarkBackground(void) +{ + return TheCamera.GetForward().z < -0.9f || gbShowCollisionPolys; +} + +void +CClouds::RenderBackground(int16 topred, int16 topgreen, int16 topblue, + int16 botred, int16 botgreen, int16 botblue, int16 alpha) +{ + PUSH_RENDERGROUP("CClouds::RenderBackground"); + + CVector left = TheCamera.GetRight(); + float c = left.Magnitude2D(); + if(c > 1.0f) + c = 1.0f; + ms_cameraRoll = Acos(c); + if(left.z < 0.0f) + ms_cameraRoll = -ms_cameraRoll; + + if(UseDarkBackground()){ + ms_colourTop.r = 50; + ms_colourTop.g = 50; + ms_colourTop.b = 50; + ms_colourTop.a = 255; + if(gbShowCollisionPolys){ + if(CTimer::GetFrameCounter() & 1){ + ms_colourTop.r = 0; + ms_colourTop.g = 0; + ms_colourTop.b = 0; + }else{ + ms_colourTop.r = 255; + ms_colourTop.g = 255; + ms_colourTop.b = 255; + } + } + ms_colourBottom = ms_colourTop; + CRect r(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + CSprite2d::DrawRect(r, ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + }else{ + ms_horizonZ = CSprite::CalcHorizonCoors(); + + // Draw top/bottom gradient + float gradheight = SCREEN_HEIGHT/2.0f; + float topedge = ms_horizonZ - gradheight; + float botpos, toppos; + if(ms_horizonZ > 0.0f && topedge < SCREEN_HEIGHT){ + ms_colourTop.r = topred; + ms_colourTop.g = topgreen; + ms_colourTop.b = topblue; + ms_colourTop.a = alpha; + ms_colourBottom.r = botred; + ms_colourBottom.g = botgreen; + ms_colourBottom.b = botblue; + ms_colourBottom.a = alpha; + + if(ms_horizonZ < SCREEN_HEIGHT) + botpos = ms_horizonZ; + else{ + float f = (ms_horizonZ - SCREEN_HEIGHT)/gradheight; + ms_colourBottom.r = topred*f + (1.0f-f)*botred; + ms_colourBottom.g = topgreen*f + (1.0f-f)*botgreen; + ms_colourBottom.b = topblue*f + (1.0f-f)*botblue; + botpos = SCREEN_HEIGHT; + } + if(topedge >= 0.0f) + toppos = topedge; + else{ + float f = (0.0f - topedge)/gradheight; + ms_colourTop.r = botred*f + (1.0f-f)*topred; + ms_colourTop.g = botgreen*f + (1.0f-f)*topgreen; + ms_colourTop.b = botblue*f + (1.0f-f)*topblue; + toppos = 0.0f; + } + CSprite2d::DrawRect(CRect(0, toppos, SCREEN_WIDTH, botpos), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + } + + // draw the small stripe (whatever it's supposed to be) + if(ms_horizonZ > -SMALLSTRIPHEIGHT && ms_horizonZ < SCREEN_HEIGHT){ + // Same colour as fog + ms_colourTop.r = (topred + 2 * botred) / 3; + ms_colourTop.g = (topgreen + 2 * botgreen) / 3; + ms_colourTop.b = (topblue + 2 * botblue) / 3; + CSprite2d::DrawRect(CRect(0, ms_horizonZ, SCREEN_WIDTH, ms_horizonZ+SMALLSTRIPHEIGHT), + ms_colourTop, ms_colourTop, ms_colourTop, ms_colourTop); + } + + // Only top + if(topedge > 0.0f){ + ms_colourTop.r = topred; + ms_colourTop.g = topgreen; + ms_colourTop.b = topblue; + ms_colourTop.a = alpha; + ms_colourBottom.r = topred; + ms_colourBottom.g = topgreen; + ms_colourBottom.b = topblue; + ms_colourBottom.a = alpha; + + botpos = Min(SCREEN_HEIGHT, topedge); + CSprite2d::DrawRect(CRect(0, 0, SCREEN_WIDTH, botpos), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + } + + // Set both to fog colour for RenderHorizon + ms_colourTop.r = (topred + 2 * botred) / 3; + ms_colourTop.g = (topgreen + 2 * botgreen) / 3; + ms_colourTop.b = (topblue + 2 * botblue) / 3; + ms_colourBottom.r = (topred + 2 * botred) / 3; + ms_colourBottom.g = (topgreen + 2 * botgreen) / 3; + ms_colourBottom.b = (topblue + 2 * botblue) / 3; + } + + POP_RENDERGROUP(); +} + +void +CClouds::RenderHorizon(void) +{ + if(UseDarkBackground()) + return; + + ms_colourBottom.a = 230; + ms_colourTop.a = 80; + + if(ms_horizonZ > SCREEN_HEIGHT) + return; + + PUSH_RENDERGROUP("CClouds::RenderHorizon"); + + float z1 = Min(ms_horizonZ + SMALLSTRIPHEIGHT, SCREEN_HEIGHT); + CSprite2d::DrawRectXLU(CRect(0, ms_horizonZ, SCREEN_WIDTH, z1), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + + // This is just weird + float a = SCREEN_HEIGHT/400.0f * HORIZSTRIPHEIGHT + + SCREEN_HEIGHT/300.0f * Max(TheCamera.GetPosition().z, 0.0f); + float b = TheCamera.GetUp().z < 0.0f ? + SCREEN_HEIGHT : + SCREEN_HEIGHT * Abs(TheCamera.GetRight().z); + float z2 = z1 + (a + b)*TheCamera.LODDistMultiplier; + z2 = Min(z2, SCREEN_HEIGHT); + CSprite2d::DrawRect(CRect(0, z1, SCREEN_WIDTH, z2), + ms_colourBottom, ms_colourBottom, ms_colourTop, ms_colourTop); + + POP_RENDERGROUP(); +} diff --git a/src/renderer/Clouds.h b/src/renderer/Clouds.h new file mode 100644 index 0000000..4d8cd2c --- /dev/null +++ b/src/renderer/Clouds.h @@ -0,0 +1,21 @@ +#pragma once + +class CClouds +{ +public: + static float CloudRotation; + static uint32 IndividualRotation; + + static float ms_cameraRoll; + static float ms_horizonZ; + static CRGBA ms_colourTop; + static CRGBA ms_colourBottom; + + static void Init(void); + static void Shutdown(void); + static void Update(void); + static void Render(void); + static void RenderBackground(int16 topred, int16 topgreen, int16 topblue, + int16 botred, int16 botgreen, int16 botblue, int16 alpha); + static void RenderHorizon(void); +}; diff --git a/src/renderer/Console.cpp b/src/renderer/Console.cpp new file mode 100644 index 0000000..8ea5b7a --- /dev/null +++ b/src/renderer/Console.cpp @@ -0,0 +1,96 @@ +#include "common.h" +#include + +#include "Console.h" +#include "Font.h" +#include "Timer.h" + +#define CONSOLE_X_POS (30.0f) +#define CONSOLE_Y_POS (10.0f) +#define CONSOLE_LINE_HEIGHT (12.0f) + +CConsole TheConsole; + +void +CConsole::AddLine(char *s, uint8 r, uint8 g, uint8 b) +{ + char tempstr[MAX_STR_LEN+1]; + + while (strlen(s) > MAX_STR_LEN) { + strncpy(tempstr, s, MAX_STR_LEN); + tempstr[MAX_STR_LEN-1] = '\0'; + s += MAX_STR_LEN - 1; + AddOneLine(tempstr, r, g, b); + } + AddOneLine(s, r, g, b); +} + +void +CConsole::AddOneLine(char *s, uint8 r, uint8 g, uint8 b) +{ + int32 StrIndex = (m_nLineCount + m_nCurrentLine) % MAX_LINES; + + for (int32 i = 0; i < MAX_STR_LEN; i++) { + Buffers[StrIndex][i] = s[i]; + if (s[i] == '\0') break; + } + + uint8 _strNum1 = m_nLineCount; + if (_strNum1 < MAX_LINES) + _strNum1++; + + m_aTimer[StrIndex] = CTimer::GetTimeInMilliseconds(); + Buffers[StrIndex][MAX_STR_LEN-1] = '\0'; + m_aRed[StrIndex] = r; + m_aGreen[StrIndex] = g; + m_aBlue[StrIndex] = b; + + if (_strNum1 >= MAX_LINES) + m_nCurrentLine = (m_nCurrentLine + 1) % MAX_LINES; + else + m_nLineCount = _strNum1; + +} + +void +CConsole::Display() +{ + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(0.6f, 0.6f); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); +#ifndef FIX_BUGS + CFont::SetPropOff(); // not sure why this is here anyway +#endif + CFont::SetWrapx(RsGlobal.width); + + while (m_nLineCount != 0 && CTimer::GetTimeInMilliseconds() - m_aTimer[m_nCurrentLine] > 20000) { + m_nLineCount--; + m_nCurrentLine = (m_nCurrentLine + 1) % MAX_LINES; + } + + for (int16 i = 0; i < m_nLineCount; i++) { + int16 line = (i + m_nCurrentLine) % MAX_LINES; + CFont::SetColor(CRGBA(0, 0, 0, 200)); + CFont::PrintString(CONSOLE_X_POS + 1.0f, CONSOLE_Y_POS + 1.0f + i * CONSOLE_LINE_HEIGHT, Buffers[line]); + CFont::SetColor(CRGBA(m_aRed[line], m_aGreen[line], m_aBlue[line], 200)); + CFont::PrintString(CONSOLE_X_POS, CONSOLE_Y_POS + i * CONSOLE_LINE_HEIGHT, Buffers[line]); + } +} + +void +cprintf(char* format, ...) +{ + char s[256]; + va_list vl1, vl2; + + va_start(vl1, format); + va_copy(vl2, vl1); + vsprintf(s, format, vl1); + TheConsole.AddLine(s, 255, 255, 128); +} diff --git a/src/renderer/Console.h b/src/renderer/Console.h new file mode 100644 index 0000000..9f22236 --- /dev/null +++ b/src/renderer/Console.h @@ -0,0 +1,27 @@ +#pragma once + +class CConsole +{ + enum + { + MAX_LINES = 8, // BUG? only shows 7 + MAX_STR_LEN = 40, + }; + + uint8 m_nLineCount; + uint8 m_nCurrentLine; + wchar Buffers[MAX_LINES][MAX_STR_LEN]; + uint32 m_aTimer[MAX_LINES]; + uint8 m_aRed[MAX_LINES]; + uint8 m_aGreen[MAX_LINES]; + uint8 m_aBlue[MAX_LINES]; +public: + void AddLine(char *s, uint8 r, uint8 g, uint8 b); + void AddOneLine(char *s, uint8 r, uint8 g, uint8 b); + void Display(); + void Init() { m_nCurrentLine = 0; m_nLineCount = 0; } +}; + +extern CConsole TheConsole; + +void cprintf(char*, ...); \ No newline at end of file diff --git a/src/renderer/Coronas.cpp b/src/renderer/Coronas.cpp new file mode 100644 index 0000000..e9f9e66 --- /dev/null +++ b/src/renderer/Coronas.cpp @@ -0,0 +1,779 @@ +#include "common.h" + +#include "main.h" +#include "General.h" +#include "Entity.h" +#include "TxdStore.h" +#include "Camera.h" +#include "Sprite.h" +#include "Timer.h" +#include "World.h" +#include "Weather.h" +#include "Collision.h" +#include "Timecycle.h" +#include "Coronas.h" +#include "PointLights.h" +#include "Shadows.h" +#include "Clock.h" +#include "Bridge.h" + +struct FlareDef +{ + float position; + float size; + int16 red; + int16 green; + int16 blue; + int16 alpha; + int16 texture; +}; + +FlareDef SunFlareDef[] = { + { -0.5f, 15.0f, 50, 50, 0, 200, 1 }, + { -1.0f, 10.0f, 50, 20, 0, 200, 2 }, + { -1.5f, 15.0f, 50, 0, 0, 200, 3 }, + { -2.5f, 25.0f, 50, 0, 0, 200, 1 }, + { 0.5f, 12.5f, 40, 40, 25, 200, 1 }, + { 0.05f, 20.0f, 30, 22, 9, 200, 2 }, + { 1.3f, 7.5f, 50, 30, 9, 200, 3 }, + { 0.0f, 0.0f, 255, 255, 255, 255, 0 } +}; + +FlareDef HeadLightsFlareDef[] = { + { -0.5f, 15.5, 70, 70, 70, 200, 1 }, + { -1.0f, 10.0, 70, 70, 70, 200, 2 }, + { -1.5f, 5.5f, 50, 50, 50, 200, 3 }, + { 0.5f, 12.0f, 50, 50, 50, 200, 1 }, + { 0.05f, 20.0f, 40, 40, 40, 200, 2 }, + { 1.3f, 8.0f, 60, 60, 60, 200, 3 }, + { -2.0f, 12.0f, 50, 50, 50, 200, 1 }, + { -2.3f, 15.0f, 40, 40, 40, 200, 2 }, + { -3.0f, 16.0f, 40, 40, 40, 200, 3 }, + { 0.0f, 0.0f, 255, 255, 255, 255, 0 } +}; + + +RwTexture *gpCoronaTexture[9] = { nil, nil, nil, nil, nil, nil, nil, nil, nil }; + +float CCoronas::LightsMult = 1.0f; +float CCoronas::SunScreenX; +float CCoronas::SunScreenY; +bool CCoronas::bSmallMoon; +bool CCoronas::SunBlockedByClouds; +int CCoronas::bChangeBrightnessImmediately; + +CRegisteredCorona CCoronas::aCoronas[NUMCORONAS]; + +const char aCoronaSpriteNames[][32] = { + "coronastar", + "corona", + "coronamoon", + "coronareflect", + "coronaheadlightline", + "coronahex", + "coronacircle", + "coronaringa", + "streek" +}; + +void +CCoronas::Init(void) +{ + int i; + + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle")); + + for(i = 0; i < 9; i++) + if(gpCoronaTexture[i] == nil) + gpCoronaTexture[i] = RwTextureRead(aCoronaSpriteNames[i], nil); + + CTxdStore::PopCurrentTxd(); + + for(i = 0; i < NUMCORONAS; i++) + aCoronas[i].id = 0; +} + +void +CCoronas::Shutdown(void) +{ + int i; + for(i = 0; i < 9; i++) + if(gpCoronaTexture[i]){ + RwTextureDestroy(gpCoronaTexture[i]); + gpCoronaTexture[i] = nil; + } +} + +void +CCoronas::Update(void) +{ + int i; + static int LastCamLook = 0; + + LightsMult = Min(LightsMult + 0.03f * CTimer::GetTimeStep(), 1.0f); + + int CamLook = 0; + if(TheCamera.Cams[TheCamera.ActiveCam].LookingLeft) CamLook |= 1; + if(TheCamera.Cams[TheCamera.ActiveCam].LookingRight) CamLook |= 2; + if(TheCamera.Cams[TheCamera.ActiveCam].LookingBehind) CamLook |= 4; + // BUG? + if(TheCamera.GetLookDirection() == LOOKING_BEHIND) CamLook |= 8; + + if(LastCamLook != CamLook) + bChangeBrightnessImmediately = 3; + else + bChangeBrightnessImmediately = Max(bChangeBrightnessImmediately-1, 0); + LastCamLook = CamLook; + + for(i = 0; i < NUMCORONAS; i++) + if(aCoronas[i].id != 0) + aCoronas[i].Update(); +} + +void +CCoronas::RegisterCorona(uint32 id, uint8 red, uint8 green, uint8 blue, uint8 alpha, + const CVector &coors, float size, float drawDist, RwTexture *tex, + int8 flareType, uint8 reflection, uint8 LOScheck, uint8 drawStreak, float someAngle) +{ + int i; + + if(sq(drawDist) < (TheCamera.GetPosition() - coors).MagnitudeSqr2D()) + return; + + for(i = 0; i < NUMCORONAS; i++) + if(aCoronas[i].id == id) + break; + + if(i == NUMCORONAS){ + // add a new one + + // find empty slot + for(i = 0; i < NUMCORONAS; i++) + if(aCoronas[i].id == 0) + break; + if(i == NUMCORONAS) + return; // no space + + aCoronas[i].fadeAlpha = 0; + aCoronas[i].offScreen = true; + aCoronas[i].firstUpdate = true; + aCoronas[i].renderReflection = false; + aCoronas[i].lastLOScheck = 0; + aCoronas[i].sightClear = false; + aCoronas[i].hasValue[0] = false; + aCoronas[i].hasValue[1] = false; + aCoronas[i].hasValue[2] = false; + aCoronas[i].hasValue[3] = false; + aCoronas[i].hasValue[4] = false; + aCoronas[i].hasValue[5] = false; + + }else{ + // use existing one + + if(aCoronas[i].fadeAlpha == 0 && alpha == 0){ + // unregister + aCoronas[i].id = 0; + return; + } + } + + aCoronas[i].id = id; + aCoronas[i].red = red; + aCoronas[i].green = green; + aCoronas[i].blue = blue; + aCoronas[i].alpha = alpha; + aCoronas[i].coors = coors; + aCoronas[i].size = size; + aCoronas[i].someAngle = someAngle; + aCoronas[i].registeredThisFrame = true; + aCoronas[i].drawDist = drawDist; + aCoronas[i].texture = tex; + aCoronas[i].flareType = flareType; + aCoronas[i].reflection = reflection; + aCoronas[i].LOScheck = LOScheck; + aCoronas[i].drawStreak = drawStreak; +} + +void +CCoronas::RegisterCorona(uint32 id, uint8 red, uint8 green, uint8 blue, uint8 alpha, + const CVector &coors, float size, float drawDist, uint8 type, + int8 flareType, uint8 reflection, uint8 LOScheck, uint8 drawStreak, float someAngle) +{ + RegisterCorona(id, red, green, blue, alpha, coors, size, drawDist, + gpCoronaTexture[type], flareType, reflection, LOScheck, drawStreak, someAngle); +} + +void +CCoronas::UpdateCoronaCoors(uint32 id, const CVector &coors, float drawDist, float someAngle) +{ + int i; + + if(sq(drawDist) < (TheCamera.GetPosition() - coors).MagnitudeSqr2D()) + return; + + for(i = 0; i < NUMCORONAS; i++) + if(aCoronas[i].id == id) + break; + + if(i == NUMCORONAS) + return; + + if(aCoronas[i].fadeAlpha == 0) + aCoronas[i].id = 0; // faded out, remove + else{ + aCoronas[i].coors = coors; + aCoronas[i].someAngle = someAngle; + } +} + +static RwIm2DVertex vertexbufferX[2]; + +void +CCoronas::Render(void) +{ + int i, j; + int screenw, screenh; + + PUSH_RENDERGROUP("CCoronas::Render"); + + screenw = RwRasterGetWidth(RwCameraGetRaster(Scene.camera)); + screenh = RwRasterGetHeight(RwCameraGetRaster(Scene.camera)); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + + for(i = 0; i < NUMCORONAS; i++){ + for(j = 5; j > 0; j--){ + aCoronas[i].prevX[j] = aCoronas[i].prevX[j-1]; + aCoronas[i].prevY[j] = aCoronas[i].prevY[j-1]; + aCoronas[i].prevRed[j] = aCoronas[i].prevRed[j-1]; + aCoronas[i].prevGreen[j] = aCoronas[i].prevGreen[j-1]; + aCoronas[i].prevBlue[j] = aCoronas[i].prevBlue[j-1]; + aCoronas[i].hasValue[j] = aCoronas[i].hasValue[j-1]; + } + aCoronas[i].hasValue[0] = false; + + if(aCoronas[i].id == 0 || + aCoronas[i].fadeAlpha == 0 && aCoronas[i].alpha == 0) + continue; + + CVector spriteCoors; + float spritew, spriteh; + if(!CSprite::CalcScreenCoors(aCoronas[i].coors, &spriteCoors, &spritew, &spriteh, true)){ + aCoronas[i].offScreen = true; + aCoronas[i].sightClear = false; + }else{ + aCoronas[i].offScreen = false; + + if(spriteCoors.x < 0.0f || spriteCoors.y < 0.0f || + spriteCoors.x > screenw || spriteCoors.y > screenh){ + aCoronas[i].offScreen = true; + aCoronas[i].sightClear = false; + }else{ + if(CTimer::GetTimeInMilliseconds() > aCoronas[i].lastLOScheck + 2000){ + aCoronas[i].lastLOScheck = CTimer::GetTimeInMilliseconds(); + aCoronas[i].sightClear = CWorld::GetIsLineOfSightClear( + aCoronas[i].coors, TheCamera.Cams[TheCamera.ActiveCam].Source, + true, true, false, false, false, true, false); + } + + // add new streak point + if(aCoronas[i].sightClear){ + aCoronas[i].prevX[0] = spriteCoors.x; + aCoronas[i].prevY[0] = spriteCoors.y; + aCoronas[i].prevRed[0] = aCoronas[i].red; + aCoronas[i].prevGreen[0] = aCoronas[i].green; + aCoronas[i].prevBlue[0] = aCoronas[i].blue; + aCoronas[i].hasValue[0] = true; + } + + // if distance too big, break streak + if(aCoronas[i].hasValue[1]){ + if(Abs(aCoronas[i].prevX[0] - aCoronas[i].prevX[1]) > 50.0f || + Abs(aCoronas[i].prevY[0] - aCoronas[i].prevY[1]) > 50.0f) + aCoronas[i].hasValue[0] = false; + } + } + + + if(aCoronas[i].fadeAlpha && spriteCoors.z < aCoronas[i].drawDist){ + float recipz = 1.0f/spriteCoors.z; + float fadeDistance = aCoronas[i].drawDist / 2.0f; + float distanceFade = spriteCoors.z < fadeDistance ? 1.0f : 1.0f - (spriteCoors.z - fadeDistance)/fadeDistance; + int totalFade = aCoronas[i].fadeAlpha * distanceFade; + + if(aCoronas[i].LOScheck) + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + else + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + // render corona itself + if(aCoronas[i].texture){ + float fogscale = CWeather::Foggyness*Min(spriteCoors.z, 40.0f)/40.0f + 1.0f; + if(CCoronas::aCoronas[i].id == SUN_CORE) + spriteCoors.z = 0.95f * RwCameraGetFarClipPlane(Scene.camera); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(aCoronas[i].texture)); + spriteCoors.z -= 1.5f; + + if(aCoronas[i].texture == gpCoronaTexture[8]){ + // what's this? + float f = 1.0f - aCoronas[i].someAngle*2.0f/PI; + float wscale = 6.0f*sq(sq(sq(f))) + 0.5f; + float hscale = 0.35f - (wscale - 0.5f) * 0.06f; + hscale = Max(hscale, 0.15f); + + CSprite::RenderOneXLUSprite(spriteCoors.x, spriteCoors.y, spriteCoors.z, + spritew * aCoronas[i].size * wscale, + spriteh * aCoronas[i].size * fogscale * hscale, + CCoronas::aCoronas[i].red / fogscale, + CCoronas::aCoronas[i].green / fogscale, + CCoronas::aCoronas[i].blue / fogscale, + totalFade, + recipz, + 255); + }else{ + CSprite::RenderOneXLUSprite_Rotate_Aspect( + spriteCoors.x, spriteCoors.y, spriteCoors.z, + spritew * aCoronas[i].size * fogscale, + spriteh * aCoronas[i].size * fogscale, + CCoronas::aCoronas[i].red / fogscale, + CCoronas::aCoronas[i].green / fogscale, + CCoronas::aCoronas[i].blue / fogscale, + totalFade, + recipz, + 20.0f * recipz, + 255); + } + } + + // render flares + if(aCoronas[i].flareType != FLARE_NONE){ + FlareDef *flare; + + switch(aCoronas[i].flareType){ + case FLARE_SUN: flare = SunFlareDef; break; + case FLARE_HEADLIGHTS: flare = HeadLightsFlareDef; break; + default: assert(0); + } + + for(; flare->texture; flare++){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[flare->texture + 4])); + CSprite::RenderOneXLUSprite( + (spriteCoors.x - (screenw/2)) * flare->position + (screenw/2), + (spriteCoors.y - (screenh/2)) * flare->position + (screenh/2), + spriteCoors.z, + 4.0f*flare->size * spritew/spriteh, + 4.0f*flare->size, + (flare->red * aCoronas[i].red)>>8, + (flare->green * aCoronas[i].green)>>8, + (flare->blue * aCoronas[i].blue)>>8, + (totalFade * flare->alpha)>>8, + recipz, 255); + } + } + } + } + } + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + // streaks + for(i = 0; i < NUMCORONAS; i++){ + if(aCoronas[i].id == 0 || !aCoronas[i].drawStreak) + continue; + + for(j = 0; j < 5; j++){ + if(!aCoronas[i].hasValue[j] || !aCoronas[i].hasValue[j+1]) + continue; + + int alpha1 = (float)(6 - j) / 6 * 128; + int alpha2 = (float)(6 - (j+1)) / 6 * 128; + + RwIm2DVertexSetScreenX(&vertexbufferX[0], aCoronas[i].prevX[j]); + RwIm2DVertexSetScreenY(&vertexbufferX[0], aCoronas[i].prevY[j]); + RwIm2DVertexSetIntRGBA(&vertexbufferX[0], aCoronas[i].prevRed[j] * alpha1 / 256, aCoronas[i].prevGreen[j] * alpha1 / 256, aCoronas[i].prevBlue[j] * alpha1 / 256, 255); + RwIm2DVertexSetScreenX(&vertexbufferX[1], aCoronas[i].prevX[j+1]); + RwIm2DVertexSetScreenY(&vertexbufferX[1], aCoronas[i].prevY[j+1]); + RwIm2DVertexSetIntRGBA(&vertexbufferX[1], aCoronas[i].prevRed[j+1] * alpha2 / 256, aCoronas[i].prevGreen[j+1] * alpha2 / 256, aCoronas[i].prevBlue[j+1] * alpha2 / 256, 255); + +#ifdef FIX_BUGS + RwIm2DVertexSetScreenZ(&vertexbufferX[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&vertexbufferX[0], RwCameraGetNearClipPlane(Scene.camera)); + RwIm2DVertexSetRecipCameraZ(&vertexbufferX[0], 1.0f/RwCameraGetNearClipPlane(Scene.camera)); + RwIm2DVertexSetScreenZ(&vertexbufferX[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&vertexbufferX[1], RwCameraGetNearClipPlane(Scene.camera)); + RwIm2DVertexSetRecipCameraZ(&vertexbufferX[1], 1.0f/RwCameraGetNearClipPlane(Scene.camera)); +#endif + + RwIm2DRenderLine(vertexbufferX, 2, 0, 1); + } + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + + POP_RENDERGROUP(); +} + +void +CCoronas::RenderReflections(void) +{ + int i; + CColPoint point; + CEntity *entity; + + if(CWeather::WetRoads > 0.0f){ + PUSH_RENDERGROUP("CCoronas::RenderReflections"); + +#ifdef FIX_BUGS + CSprite::InitSpriteBuffer(); +#endif + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[3])); + + for(i = 0; i < NUMCORONAS; i++){ + if(aCoronas[i].id == 0 || + aCoronas[i].fadeAlpha == 0 && aCoronas[i].alpha == 0 || + aCoronas[i].reflection == 0) + continue; + + // check if we want a reflection on this corona + if(aCoronas[i].renderReflection){ + if(((CTimer::GetFrameCounter() + i) & 0xF) == 0 && + CWorld::ProcessVerticalLine(aCoronas[i].coors, -1000.0f, point, entity, true, false, false, false, true, false, nil)) + aCoronas[i].heightAboveRoad = aCoronas[i].coors.z - point.point.z; + }else{ + if(CWorld::ProcessVerticalLine(aCoronas[i].coors, -1000.0f, point, entity, true, false, false, false, true, false, nil)){ + aCoronas[i].heightAboveRoad = aCoronas[i].coors.z - point.point.z; + aCoronas[i].renderReflection = true; + } + } + + // Don't draw if reflection is too high + if(aCoronas[i].renderReflection && aCoronas[i].heightAboveRoad < 20.0f){ + // don't draw if camera is below road + if(CCoronas::aCoronas[i].coors.z - aCoronas[i].heightAboveRoad > TheCamera.GetPosition().z) + continue; + + CVector coors = aCoronas[i].coors; + coors.z -= 2.0f*aCoronas[i].heightAboveRoad; + + CVector spriteCoors; + float spritew, spriteh; + if(CSprite::CalcScreenCoors(coors, &spriteCoors, &spritew, &spriteh, true)){ + float drawDist = 0.75f * aCoronas[i].drawDist; + drawDist = Min(drawDist, 55.0f); + if(spriteCoors.z < drawDist){ + float fadeDistance = drawDist / 2.0f; + float distanceFade = spriteCoors.z < fadeDistance ? 1.0f : 1.0f - (spriteCoors.z - fadeDistance)/fadeDistance; + distanceFade = Clamp(distanceFade, 0.0f, 1.0f); + float recipz = 1.0f/RwCameraGetNearClipPlane(Scene.camera); + float heightFade = (20.0f - aCoronas[i].heightAboveRoad)/20.0f; + int intensity = distanceFade*heightFade * 230.0 * CWeather::WetRoads; + + CSprite::RenderBufferedOneXLUSprite( +#ifdef FIX_BUGS + spriteCoors.x, spriteCoors.y, spriteCoors.z, +#else + spriteCoors.x, spriteCoors.y, RwIm2DGetNearScreenZ(), +#endif + spritew * aCoronas[i].size * 0.75f, + spriteh * aCoronas[i].size * 2.0f, + (intensity * CCoronas::aCoronas[i].red)>>8, + (intensity * CCoronas::aCoronas[i].green)>>8, + (intensity * CCoronas::aCoronas[i].blue)>>8, + 255, + recipz, + 255); + } + } + } + } + CSprite::FlushSpriteBuffer(); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + POP_RENDERGROUP(); + }else{ + for(i = 0; i < NUMCORONAS; i++) + aCoronas[i].renderReflection = false; + } +} + +void +CCoronas::DoSunAndMoon(void) +{ + // yeah, moon is done somewhere else.... + + CVector sunCoors = CTimeCycle::GetSunDirection(); + sunCoors *= 150.0f; + sunCoors += TheCamera.GetPosition(); + + if(CTimeCycle::GetSunDirection().z > -0.2f){ + float size = ((CGeneral::GetRandomNumber()&0xFF) * 0.005f + 10.0f) * CTimeCycle::GetSunSize(); + RegisterCorona(SUN_CORE, + CTimeCycle::GetSunCoreRed(), CTimeCycle::GetSunCoreGreen(), CTimeCycle::GetSunCoreBlue(), + 255, sunCoors, size, + 999999.88f, TYPE_STAR, FLARE_NONE, REFLECTION_OFF, LOSCHECK_OFF, STREAK_OFF, 0.0f); + + if(CTimeCycle::GetSunDirection().z > 0.0f) + RegisterCorona(SUN_CORONA, + CTimeCycle::GetSunCoronaRed(), CTimeCycle::GetSunCoronaGreen(), CTimeCycle::GetSunCoronaBlue(), + 255, sunCoors, 25.0f * CTimeCycle::GetSunSize(), + 999999.88f, TYPE_STAR, FLARE_SUN, REFLECTION_OFF, LOSCHECK_ON, STREAK_OFF, 0.0f); + } + + CVector spriteCoors; + float spritew, spriteh; + if(CSprite::CalcScreenCoors(sunCoors, &spriteCoors, &spritew, &spriteh, true)){ + SunScreenX = spriteCoors.x; + SunScreenY = spriteCoors.y; + }else{ + SunScreenX = 1000000.0f; + SunScreenY = 1000000.0f; + } +} + +void +CRegisteredCorona::Update(void) +{ + if(!registeredThisFrame) + alpha = 0; + + if(LOScheck && + (CCoronas::SunBlockedByClouds && id == CCoronas::SUN_CORONA || + !CWorld::GetIsLineOfSightClear(coors, TheCamera.GetPosition(), true, false, false, false, false, false))){ + // Corona is blocked, fade out + fadeAlpha = Max(fadeAlpha - 15.0f*CTimer::GetTimeStep(), 0.0f); + }else if(offScreen){ + // Same when off screen + fadeAlpha = Max(fadeAlpha - 15.0f*CTimer::GetTimeStep(), 0.0f); + }else{ + // Visible + if(alpha > fadeAlpha){ + // fade in + fadeAlpha = Min(fadeAlpha + 15.0f*CTimer::GetTimeStep(), alpha); + if(CCoronas::bChangeBrightnessImmediately) + fadeAlpha = alpha; + }else if(alpha < fadeAlpha){ + // too visible, decrease alpha but not below alpha + fadeAlpha = Max(fadeAlpha - 15.0f*CTimer::GetTimeStep(), alpha); + } + + // darken scene when the sun is visible + if(id == CCoronas::SUN_CORONA) + CCoronas::LightsMult = Max(CCoronas::LightsMult - CTimer::GetTimeStep()*0.06f, 0.6f); + } + + // remove if invisible + if(fadeAlpha == 0 && !firstUpdate) + id = 0; + firstUpdate = false; + registeredThisFrame = false; +} + +void +CEntity::ProcessLightsForEntity(void) +{ + int i, n; + C2dEffect *effect; + CVector pos; + bool lightOn, lightFlickering; + uint32 flashTimer1, flashTimer2, flashTimer3; + + if(bRenderDamaged || !bIsVisible || GetUp().z < 0.96f) + return; + + flashTimer1 = 0; + flashTimer2 = 0; + flashTimer3 = 0; + + n = CModelInfo::GetModelInfo(GetModelIndex())->GetNum2dEffects(); + for(i = 0; i < n; i++, flashTimer1 += 0x80, flashTimer2 += 0x100, flashTimer3 += 0x200){ + effect = CModelInfo::GetModelInfo(GetModelIndex())->Get2dEffect(i); + + if(effect->type != EFFECT_LIGHT) + continue; + + pos = GetMatrix() * effect->pos; + + lightOn = false; + lightFlickering = false; + switch(effect->light.lightType){ + case LIGHT_ON: + lightOn = true; + break; + case LIGHT_ON_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7) + lightOn = true; + break; + case LIGHT_FLICKER: + if((CTimer::GetTimeInMilliseconds() ^ m_randomSeed) & 0x60) + lightOn = true; + else + lightFlickering = true; + if((CTimer::GetTimeInMilliseconds()>>11 ^ m_randomSeed) & 3) + lightOn = true; + break; + case LIGHT_FLICKER_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7 || CWeather::WetRoads > 0.5f){ + if((CTimer::GetTimeInMilliseconds() ^ m_randomSeed) & 0x60) + lightOn = true; + else + lightFlickering = true; + if((CTimer::GetTimeInMilliseconds()>>11 ^ m_randomSeed) & 3) + lightOn = true; + } + break; + case LIGHT_FLASH1: + if((CTimer::GetTimeInMilliseconds() + flashTimer1) & 0x200) + lightOn = true; + break; + case LIGHT_FLASH1_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7) + if((CTimer::GetTimeInMilliseconds() + flashTimer1) & 0x200) + lightOn = true; + break; + case LIGHT_FLASH2: + if((CTimer::GetTimeInMilliseconds() + flashTimer2) & 0x400) + lightOn = true; + break; + case LIGHT_FLASH2_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7) + if((CTimer::GetTimeInMilliseconds() + flashTimer2) & 0x400) + lightOn = true; + break; + case LIGHT_FLASH3: + if((CTimer::GetTimeInMilliseconds() + flashTimer3) & 0x800) + lightOn = true; + break; + case LIGHT_FLASH3_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7) + if((CTimer::GetTimeInMilliseconds() + flashTimer3) & 0x800) + lightOn = true; + break; + case LIGHT_RANDOM_FLICKER: + if(m_randomSeed > 16) + lightOn = true; + else{ + if((CTimer::GetTimeInMilliseconds() ^ m_randomSeed*8) & 0x60) + lightOn = true; + else + lightFlickering = true; + if((CTimer::GetTimeInMilliseconds()>>11 ^ m_randomSeed*8) & 3) + lightOn = true; + } + break; + case LIGHT_RANDOM_FLICKER_NIGHT: + if(CClock::GetHours() > 18 || CClock::GetHours() < 7){ + if(m_randomSeed > 16) + lightOn = true; + else{ + if((CTimer::GetTimeInMilliseconds() ^ m_randomSeed*8) & 0x60) + lightOn = true; + else + lightFlickering = true; + if((CTimer::GetTimeInMilliseconds()>>11 ^ m_randomSeed*8) & 3) + lightOn = true; + } + } + break; + case LIGHT_BRIDGE_FLASH1: + if(CBridge::ShouldLightsBeFlashing() && CTimer::GetTimeInMilliseconds() & 0x200) + lightOn = true; + break; + case LIGHT_BRIDGE_FLASH2: + if(CBridge::ShouldLightsBeFlashing() && (CTimer::GetTimeInMilliseconds() & 0x1FF) < 60) + lightOn = true; + break; + } + + // Corona + if(lightOn) + CCoronas::RegisterCorona((uintptr)this + i, + effect->col.r, effect->col.g, effect->col.b, 255, + pos, effect->light.size, effect->light.dist, + effect->light.corona, effect->light.flareType, effect->light.roadReflection, + effect->light.flags&LIGHTFLAG_LOSCHECK, CCoronas::STREAK_OFF, 0.0f); + else if(lightFlickering) + CCoronas::RegisterCorona((uintptr)this + i, + 0, 0, 0, 255, + pos, effect->light.size, effect->light.dist, + effect->light.corona, effect->light.flareType, effect->light.roadReflection, + effect->light.flags&LIGHTFLAG_LOSCHECK, CCoronas::STREAK_OFF, 0.0f); + + // Pointlight + if(effect->light.flags & LIGHTFLAG_FOG_ALWAYS){ + CPointLights::AddLight(CPointLights::LIGHT_FOGONLY_ALWAYS, + pos, CVector(0.0f, 0.0f, 0.0f), + effect->light.range, + effect->col.r/255.0f, effect->col.g/255.0f, effect->col.b/255.0f, + CPointLights::FOG_ALWAYS, true); + }else if(effect->light.flags & LIGHTFLAG_FOG_NORMAL && lightOn && effect->light.range == 0.0f){ + CPointLights::AddLight(CPointLights::LIGHT_FOGONLY, + pos, CVector(0.0f, 0.0f, 0.0f), + effect->light.range, + effect->col.r/255.0f, effect->col.g/255.0f, effect->col.b/255.0f, + CPointLights::FOG_NORMAL, true); + }else if(lightOn && effect->light.range != 0.0f){ + if(effect->col.r == 0 && effect->col.g == 0 && effect->col.b == 0){ + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos, CVector(0.0f, 0.0f, 0.0f), + effect->light.range, + 0.0f, 0.0f, 0.0f, + CPointLights::FOG_NONE, true); + }else{ + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos, CVector(0.0f, 0.0f, 0.0f), + effect->light.range, + effect->col.r*CTimeCycle::GetSpriteBrightness()/255.0f, + effect->col.g*CTimeCycle::GetSpriteBrightness()/255.0f, + effect->col.b*CTimeCycle::GetSpriteBrightness()/255.0f, + // half-useless because LIGHTFLAG_FOG_ALWAYS can't be on + (effect->light.flags & LIGHTFLAG_FOG) >> 1, + true); + } + } + + // Light shadow + if(effect->light.shadowSize != 0.0f){ + if(lightOn){ + CShadows::StoreStaticShadow((uintptr)this + i, SHADOWTYPE_ADDITIVE, + effect->light.shadow, &pos, + effect->light.shadowSize, 0.0f, + 0.0f, -effect->light.shadowSize, + 128, + effect->col.r*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, + effect->col.g*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, + effect->col.b*CTimeCycle::GetSpriteBrightness()*effect->light.shadowIntensity/255.0f, + 15.0f, 1.0f, 40.0f, false, 0.0f); + }else if(lightFlickering){ + CShadows::StoreStaticShadow((uintptr)this + i, SHADOWTYPE_ADDITIVE, + effect->light.shadow, &pos, + effect->light.shadowSize, 0.0f, + 0.0f, -effect->light.shadowSize, + 0, 0.0f, 0.0f, 0.0f, + 15.0f, 1.0f, 40.0f, false, 0.0f); + } + } + } +} diff --git a/src/renderer/Coronas.h b/src/renderer/Coronas.h new file mode 100644 index 0000000..46eb431 --- /dev/null +++ b/src/renderer/Coronas.h @@ -0,0 +1,101 @@ +#pragma once + +extern RwTexture *gpCoronaTexture[9]; + +struct CRegisteredCorona +{ + uint32 id; + uint32 lastLOScheck; + RwTexture *texture; + uint8 red; + uint8 green; + uint8 blue; + uint8 alpha; // alpha when fully visible + uint8 fadeAlpha; // actual value used for rendering, faded + CVector coors; + float size; + float someAngle; + bool registeredThisFrame; + float drawDist; + int8 flareType; + int8 reflection; + + uint8 LOScheck : 1; + uint8 offScreen : 1; + uint8 firstUpdate : 1; + uint8 drawStreak : 1; + uint8 sightClear : 1; + + bool renderReflection; + float heightAboveRoad; + + float prevX[6]; + float prevY[6]; + uint8 prevRed[6]; + uint8 prevGreen[6]; + uint8 prevBlue[6]; + bool hasValue[6]; + + void Update(void); +}; + +VALIDATE_SIZE(CRegisteredCorona, 0x80); + +class CCoronas +{ + static CRegisteredCorona aCoronas[NUMCORONAS]; +public: + enum { + SUN_CORE = 1, + SUN_CORONA + }; + enum { + TYPE_STAR, + TYPE_NORMAL, + TYPE_MOON, + TYPE_REFLECT, + TYPE_HEADLIGHT, + TYPE_HEX, + TYPE_CIRCLE, + TYPE_RING, + TYPE_STREAK, + }; + enum { + FLARE_NONE, + FLARE_SUN, + FLARE_HEADLIGHTS + }; + enum { + REFLECTION_OFF, + REFLECTION_ON, + }; + enum { + LOSCHECK_OFF, + LOSCHECK_ON, + }; + enum { + STREAK_OFF, + STREAK_ON, + }; + + static float LightsMult; + static float SunScreenY; + static float SunScreenX; + static bool bSmallMoon; + static bool SunBlockedByClouds; + static int bChangeBrightnessImmediately; + + static void Init(void); + static void Shutdown(void); + static void Update(void); + static void RegisterCorona(uint32 id, uint8 red, uint8 green, uint8 blue, uint8 alpha, + const CVector &coors, float size, float drawDist, RwTexture *tex, + int8 flareType, uint8 reflection, uint8 LOScheck, uint8 drawStreak, float someAngle); + static void RegisterCorona(uint32 id, uint8 red, uint8 green, uint8 blue, uint8 alpha, + const CVector &coors, float size, float drawDist, uint8 type, + int8 flareType, uint8 reflection, uint8 LOScheck, uint8 drawStreak, float someAngle); + static void UpdateCoronaCoors(uint32 id, const CVector &coors, float drawDist, float someAngle); + static void Render(void); + static void RenderReflections(void); + static void DoSunAndMoon(void); +}; diff --git a/src/renderer/Credits.cpp b/src/renderer/Credits.cpp new file mode 100644 index 0000000..6058179 --- /dev/null +++ b/src/renderer/Credits.cpp @@ -0,0 +1,518 @@ +#include "common.h" + +#include "Timer.h" +#include "Font.h" +#include "Frontend.h" +#include "RwHelper.h" +#include "Camera.h" +#include "Text.h" +#include "Credits.h" + +bool CCredits::bCreditsGoing; +uint32 CCredits::CreditsStartTime; + +void +CCredits::Init(void) +{ + Stop(); +} + +void +CCredits::Start(void) +{ + bCreditsGoing = true; + CreditsStartTime = CTimer::GetTimeInMilliseconds(); +} + +void +CCredits::Stop(void) +{ + bCreditsGoing = false; +} + +void +CCredits::PrintCreditSpace(float space, uint32 &line) +{ + line += space * 25.0f; +} + +void +CCredits::PrintCreditText(float scaleX, float scaleY, wchar *text, uint32 &lineoffset, float scrolloffset) +{ +#ifdef FIX_BUGS + float start = DEFAULT_SCREEN_HEIGHT + 50.0f; +#else + float start = SCREEN_HEIGHT + 50.0f; +#endif + float y = lineoffset + start - scrolloffset; + if(y > -50.0f && y < start){ +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(scaleX), SCREEN_SCALE_Y(scaleY)); + CFont::PrintString(SCREEN_WIDTH/2.0f, SCREEN_SCALE_Y(y), (uint16*)text); +#else + CFont::SetScale(scaleX, scaleY); + CFont::PrintString(SCREEN_WIDTH/2.0f, y, (uint16*)text); +#endif + } + lineoffset += scaleY*25.0f; +} + +void +CCredits::Render(void) +{ + uint32 lineoffset; + float scrolloffset; + + if(!bCreditsGoing || FrontEndMenuManager.m_bMenuActive) + return; + + DefinedState(); + lineoffset = 0; + scrolloffset = (CTimer::GetTimeInMilliseconds() - CreditsStartTime) / 24.0f; + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); +#ifdef FIX_BUGS + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); +#else + CFont::SetCentreSize(SCREEN_WIDTH - 20); +#endif + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(220, 220, 220, 220)); + CFont::SetFontStyle(FONT_HEADING); + + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED002"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED003"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED004"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED005"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED006"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED007"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED008"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED009"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED010"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED011"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED012"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED013"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED014"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED015"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED016"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED017"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED018"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED019"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED020"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED021"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED022"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED245"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED023"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED024"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED025"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED026"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED027"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED028"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED257"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED029"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED030"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED031"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED032"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED033"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED244"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED034"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED035"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED247"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED036"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED037"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED038"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED039"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED040"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED041"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED042"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED043"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED044"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED045"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED046"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED047"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED048"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED049"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED050"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRD050A"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED051"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED052"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED053"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED054"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED055"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED056"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED248"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED249"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED250"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED251"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED252"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED253"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED057"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED058"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED059"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED254"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED255"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED060"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED061"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED062"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED063"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED064"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED065"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED066"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED067"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED068"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED069"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED070"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED071"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED072"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED073"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED074"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED075"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED076"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED077"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED078"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED079"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED080"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED081"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED082"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED083"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED084"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED242"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED259"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED260"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED261"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED262"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED085"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED086"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED087"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED088"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED089"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED090"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED091"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED094"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED095"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED096"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED097"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED098"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED099"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED263"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED264"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED265"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED267"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED270"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED266"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED100"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED101"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED102"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED103"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED104"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED105"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED106"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED268"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED269"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED107"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED108"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED109"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED110"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED111"), lineoffset, scrolloffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED112"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED113"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED114"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED115"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED116"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED117"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED118"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED119"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED120"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED121"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED122"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED123"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED124"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED125"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED126"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED127"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED128"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED129"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED130"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED131"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED132"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED133"), lineoffset, scrolloffset); + if(CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED134"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED135"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED136"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD136A"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED137"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD137A"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED138"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD138A"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD138B"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED139"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.7f, 1.0f, TheText.Get("CRED140"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140A"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140B"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140C"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140D"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD140E"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED141"), lineoffset, scrolloffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED142"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED143"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.0f, 1.0f, TheText.Get("CRED144"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED145"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED146"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED147"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED148"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED149"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED150"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED151"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED152"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED153"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED154"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED155"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED156"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED157"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED158"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED159"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED160"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED161"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED162"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED163"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED164"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED165"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED166"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED167"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED168"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED169"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED170"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED171"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED172"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED173"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED174"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED175"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED176"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED177"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED178"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED179"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED180"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED181"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED182"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED183"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED184"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED185"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED186"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED187"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED188"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED189"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED190"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED191"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED192"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED193"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED194"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED195"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED196"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED197"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED198"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED199"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED200"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED201"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED202"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED203"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED204"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED205"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED206"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED207"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED208"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED209"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED210"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED211"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED212"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED213"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED214"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED215"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED216"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED241"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED217"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED218"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD218A"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRD218B"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED219"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED220"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED221"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED222"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED223"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED224"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED225"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED226"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED227"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED228"), lineoffset, scrolloffset); + PrintCreditText(1.7f, 1.7f, TheText.Get("CRED229"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditText(1.4f, 0.82f, TheText.Get("CRED230"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED231"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED232"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED233"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED234"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED235"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED236"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED237"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED238"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED239"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED240"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("LITTLE"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("NICK"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED243"), lineoffset, scrolloffset); + PrintCreditText(1.4f, 1.4f, TheText.Get("CRED244"), lineoffset, scrolloffset); + PrintCreditSpace(2.0f, lineoffset); + PrintCreditSpace(2.0f, lineoffset); + + + CFont::DrawFonts(); + if(TheCamera.m_WideScreenOn) + TheCamera.DrawBordersForWideScreen(); + +#ifdef FIX_BUGS + if(lineoffset + DEFAULT_SCREEN_HEIGHT - scrolloffset < -10.0f) +#else + if(lineoffset + SCREEN_HEIGHT - scrolloffset < -10.0f) +#endif + { + bCreditsGoing = false; + } +} + +bool CCredits::AreCreditsDone(void) +{ + return !bCreditsGoing; +} diff --git a/src/renderer/Credits.h b/src/renderer/Credits.h new file mode 100644 index 0000000..e049ce7 --- /dev/null +++ b/src/renderer/Credits.h @@ -0,0 +1,15 @@ +#pragma once + +class CCredits +{ + static bool bCreditsGoing; + static uint32 CreditsStartTime; +public: + static void Init(void); + static void Start(void); + static void Stop(void); + static bool AreCreditsDone(void); + static void Render(void); + static void PrintCreditSpace(float space, uint32 &line); + static void PrintCreditText(float scaleX, float scaleY, wchar *text, uint32 &lineoffset, float scrolloffset); +}; diff --git a/src/renderer/Draw.cpp b/src/renderer/Draw.cpp new file mode 100644 index 0000000..f702f18 --- /dev/null +++ b/src/renderer/Draw.cpp @@ -0,0 +1,95 @@ +#include "common.h" + +#include "Draw.h" +#include "Frontend.h" +#include "Camera.h" +#include "CutsceneMgr.h" + +#ifdef ASPECT_RATIO_SCALE +float CDraw::ms_fAspectRatio = DEFAULT_ASPECT_RATIO; +float CDraw::ms_fScaledFOV = 45.0f; +#endif + +float CDraw::ms_fNearClipZ; +float CDraw::ms_fFarClipZ; +float CDraw::ms_fFOV = 45.0f; +float CDraw::ms_fLODDistance; + +uint8 CDraw::FadeValue; +uint8 CDraw::FadeRed; +uint8 CDraw::FadeGreen; +uint8 CDraw::FadeBlue; + +#ifdef PROPER_SCALING +bool CDraw::ms_bProperScaling = true; +#endif +#ifdef FIX_RADAR +bool CDraw::ms_bFixRadar = true; +#endif +#ifdef FIX_SPRITES +bool CDraw::ms_bFixSprites = true; +#endif + +float +CDraw::FindAspectRatio(void) +{ +#ifndef ASPECT_RATIO_SCALE + if(FrontEndMenuManager.m_PrefsUseWideScreen) + return 16.0f/9.0f; + else + return 4.0f/3.0f; +#else + switch (FrontEndMenuManager.m_PrefsUseWideScreen) { + case AR_AUTO: + return SCREEN_WIDTH / SCREEN_HEIGHT; + default: + case AR_4_3: + return 4.0f / 3.0f; + case AR_5_4: + return 5.0f / 4.0f; + case AR_16_10: + return 16.0f / 10.0f; + case AR_16_9: + return 16.0f / 9.0f; + case AR_21_9: + return 21.0f / 9.0f; + }; +#endif +} + +#ifdef ASPECT_RATIO_SCALE +// convert a 4:3 hFOV to vFOV, +// then convert that vFOV to hFOV for our aspect ratio, +// i.e. HOR+ +float +CDraw::ConvertFOV(float hfov) +{ + // => tan(hFOV/2) = tan(vFOV/2)*aspectRatio + // => tan(vFOV/2) = tan(hFOV/2)/aspectRatio + float ar1 = DEFAULT_ASPECT_RATIO; + float ar2 = GetAspectRatio(); + hfov = DEGTORAD(hfov); + float vfov = Atan(tan(hfov/2) / ar1) *2; + hfov = Atan(tan(vfov/2) * ar2) *2; + return RADTODEG(hfov); +} +#endif + +void +CDraw::SetFOV(float fov) +{ +#ifdef ASPECT_RATIO_SCALE + if (!CCutsceneMgr::IsRunning()) + ms_fScaledFOV = ConvertFOV(fov); + else + ms_fScaledFOV = fov; +#endif + ms_fFOV = fov; +} + +#ifdef PROPER_SCALING +float CDraw::ScaleY(float y) +{ + return ms_bProperScaling ? y : y * ((float)DEFAULT_SCREEN_HEIGHT/SCREEN_HEIGHT_NTSC); +} +#endif \ No newline at end of file diff --git a/src/renderer/Draw.h b/src/renderer/Draw.h new file mode 100644 index 0000000..8727e0e --- /dev/null +++ b/src/renderer/Draw.h @@ -0,0 +1,73 @@ +#pragma once + +enum eAspectRatio +{ + // Make sure these work the same as FrontEndMenuManager.m_PrefsUseWideScreen + // without widescreen support + AR_AUTO, + AR_4_3, + AR_5_4, + AR_16_10, + AR_16_9, + AR_21_9, + + AR_MAX, +}; + +class CDraw +{ +private: + static float ms_fNearClipZ; + static float ms_fFarClipZ; + static float ms_fFOV; +#ifdef ASPECT_RATIO_SCALE + // we use this variable to scale a lot of 2D elements + // so better cache it + static float ms_fAspectRatio; + // similar thing for 3D rendering + static float ms_fScaledFOV; +#endif +public: + static float ms_fLODDistance; // set but unused? + + static uint8 FadeValue; + static uint8 FadeRed; + static uint8 FadeGreen; + static uint8 FadeBlue; + +#ifdef PROPER_SCALING + static bool ms_bProperScaling; +#endif +#ifdef FIX_RADAR + static bool ms_bFixRadar; +#endif +#ifdef FIX_SPRITES + static bool ms_bFixSprites; +#endif + + static void SetNearClipZ(float nearclip) { ms_fNearClipZ = nearclip; } + static float GetNearClipZ(void) { return ms_fNearClipZ; } + static void SetFarClipZ(float farclip) { ms_fFarClipZ = farclip; } + static float GetFarClipZ(void) { return ms_fFarClipZ; } + + static void SetFOV(float fov); + static float GetFOV(void) { return ms_fFOV; } +#ifdef ASPECT_RATIO_SCALE + static float GetScaledFOV(void) { return ms_fScaledFOV; } +#else + static float GetScaledFOV(void) { return ms_fFOV; } +#endif + + static float FindAspectRatio(void); +#ifdef ASPECT_RATIO_SCALE + static float ConvertFOV(float fov); + static float GetAspectRatio(void) { return ms_fAspectRatio; } + static void SetAspectRatio(float ratio) { ms_fAspectRatio = ratio; } +#else + static float GetAspectRatio(void) { return FindAspectRatio(); } +#endif + +#ifdef PROPER_SCALING + static float ScaleY(float y); +#endif +}; diff --git a/src/renderer/Fluff.cpp b/src/renderer/Fluff.cpp new file mode 100644 index 0000000..c4cfe7f --- /dev/null +++ b/src/renderer/Fluff.cpp @@ -0,0 +1,870 @@ +#include "common.h" +#include "main.h" + +#include "Entity.h" +#include "Fluff.h" +#include "Camera.h" +#include "Sprite.h" +#include "Coronas.h" +#include "General.h" +#include "Timer.h" +#include "Clock.h" +#include "Weather.h" +#include "Stats.h" +#include "maths.h" +#include "Frontend.h" + +uint8 ScrollCharSet[59][5] = { + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // ' ' + { 0x00, 0x00, 0x1D, 0x00, 0x00 }, // '!' + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // '"' + { 0x0A, 0x1F, 0x0A, 0x1F, 0x0A }, // '#' + { 0x00, 0x09, 0x1F, 0x12, 0x00 }, // '$' + { 0x18, 0x18, 0x00, 0x03, 0x03 }, // '%' + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // '&' + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // ''' + { 0x01, 0x02, 0x04, 0x08, 0x10 }, // '(' + { 0x00, 0x00, 0x18, 0x00, 0x00 }, // ')' + { 0x15, 0x04, 0x1F, 0x04, 0x15 }, // '*' + { 0x00, 0x04, 0x0E, 0x04, 0x00 }, // '+' + { 0x00, 0x00, 0x03, 0x00, 0x00 }, // ',' + { 0x00, 0x04, 0x04, 0x04, 0x00 }, // '-' + { 0x00, 0x00, 0x01, 0x00, 0x00 }, // '.' + { 0x00, 0x00, 0x00, 0x00, 0x00 }, // '/' + { 0x0E, 0x11, 0x11, 0x11, 0x0E }, // '0' + { 0x01, 0x09, 0x1F, 0x01, 0x01 }, // '1' + { 0x03, 0x15, 0x15, 0x15, 0x09 }, // '2' + { 0x11, 0x11, 0x15, 0x15, 0x0A }, // '3' + { 0x02, 0x06, 0x0A, 0x1F, 0x02 }, // '4' + { 0x1D, 0x15, 0x15, 0x15, 0x12 }, // '5' + { 0x0E, 0x15, 0x15, 0x15, 0x12 }, // '6' + { 0x18, 0x10, 0x13, 0x14, 0x18 }, // '7' + { 0x0A, 0x15, 0x15, 0x15, 0x0A }, // '8' + { 0x08, 0x15, 0x15, 0x15, 0x0E }, // '9' + { 0x00, 0x00, 0x0A, 0x00, 0x00 }, // ':' + { 0x18, 0x18, 0x00, 0x03, 0x03 }, // ';' + { 0x04, 0x08, 0x1F, 0x08, 0x04 }, // '<' + { 0x00, 0x0A, 0x0A, 0x0A, 0x00 }, // '=' + { 0x04, 0x02, 0x1F, 0x02, 0x04 }, // '>' + { 0x10, 0x10, 0x15, 0x14, 0x1D }, // '?' + { 0x00, 0x1C, 0x14, 0x1C, 0x00 }, // '@' + { 0x0F, 0x12, 0x12, 0x12, 0x0F }, // 'A' + { 0x1F, 0x15, 0x15, 0x15, 0x0A }, // 'B' + { 0x0E, 0x11, 0x11, 0x11, 0x0A }, // 'C' + { 0x1F, 0x11, 0x11, 0x11, 0x0E }, // 'D' + { 0x1F, 0x15, 0x15, 0x11, 0x11 }, // 'E' + { 0x1F, 0x14, 0x14, 0x10, 0x10 }, // 'F' + { 0x0E, 0x11, 0x15, 0x15, 0x06 }, // 'G' + { 0x1F, 0x04, 0x04, 0x04, 0x1F }, // 'H' + { 0x11, 0x11, 0x1F, 0x11, 0x11 }, // 'I' + { 0x02, 0x01, 0x01, 0x01, 0x1E }, // 'J' + { 0x1F, 0x04, 0x0C, 0x12, 0x01 }, // 'K' + { 0x1F, 0x01, 0x01, 0x01, 0x01 }, // 'L' + { 0x1F, 0x08, 0x06, 0x08, 0x1F }, // 'M' + { 0x1F, 0x08, 0x04, 0x02, 0x1F }, // 'N' + { 0x0E, 0x11, 0x11, 0x11, 0x0E }, // 'O' + { 0x1F, 0x12, 0x12, 0x12, 0x0C }, // 'P' + { 0x0C, 0x12, 0x12, 0x13, 0x0D }, // 'Q' + { 0x1F, 0x14, 0x14, 0x16, 0x09 }, // 'R' + { 0x09, 0x15, 0x15, 0x15, 0x02 }, // 'S' + { 0x10, 0x10, 0x1F, 0x10, 0x10 }, // 'T' + { 0x1E, 0x01, 0x01, 0x01, 0x1E }, // 'U' + { 0x1C, 0x02, 0x01, 0x02, 0x1C }, // 'V' + { 0x1E, 0x01, 0x06, 0x01, 0x1E }, // 'W' + { 0x11, 0x0A, 0x04, 0x0A, 0x11 }, // 'X' + { 0x18, 0x04, 0x03, 0x04, 0x18 }, // 'Y' + { 0x11, 0x13, 0x15, 0x19, 0x11 } // 'Z' +}; + +// ---------- CMovingThings ---------- +enum eScrollBarTypes +{ + SCROLL_BUSINESS, + SCROLL_TRAFFIC, + SCROLL_ENTERTAINMENT, + SCROLL_AIRPORT_DOORS, + SCROLL_AIRPORT_FRONT, + SCROLL_STORE, + SCROLL_USED_CARS +}; + +CScrollBar aScrollBars[11]; +CTowerClock aTowerClocks[2]; +CDigitalClock aDigitalClocks[3]; + +CMovingThing CMovingThings::StartCloseList; +CMovingThing CMovingThings::EndCloseList; +int16 CMovingThings::Num; +CMovingThing CMovingThings::aMovingThings[NUMMOVINGTHINGS]; + +void CMovingThings::Init() +{ + StartCloseList.m_pNext = &CMovingThings::EndCloseList; + StartCloseList.m_pPrev = nil; + EndCloseList.m_pNext = nil; + EndCloseList.m_pPrev = &CMovingThings::StartCloseList; + Num = 0; + + // Initialize scroll bars + aScrollBars[0].Init(CVector( 228.3f, -669.0f, 39.0f ), SCROLL_BUSINESS, 0.0f, 0.5f, 0.5f, 255, 128, 0, 0.3f); + aScrollBars[1].Init(CVector( 772.0f, 164.0f, -9.5f ), SCROLL_TRAFFIC, 0.0f, 0.5f, 0.25f, 128, 255, 0, 0.3f); + aScrollBars[2].Init(CVector(-1089.61f, -584.224f, 13.246f), SCROLL_AIRPORT_DOORS, 0.0f, -0.1706f, 0.107f, 255, 0, 0, 0.11f); + aScrollBars[3].Init(CVector(-1089.61f, -602.04602f, 13.246f), SCROLL_AIRPORT_DOORS, 0.0f, -0.1706f, 0.107f, 0, 255, 0, 0.11f); + aScrollBars[4].Init(CVector(-1089.61f, -619.81702f, 13.246f), SCROLL_AIRPORT_DOORS, 0.0f, -0.1706f, 0.107f, 255, 128, 0, 0.11f); + aScrollBars[5].Init(CVector(-754.578f, -633.50897f, 18.411f), SCROLL_AIRPORT_FRONT, 0.0f, 0.591f, 0.52f, 100, 100, 255, 0.3f); + aScrollBars[6].Init(CVector( -754.578f, -586.672f, 18.411f), SCROLL_AIRPORT_FRONT, 0.0f, 0.591f, 0.52f, 100, 100, 255, 0.3f); + aScrollBars[7].Init(CVector( 85.473f, -1069.512f, 30.5f ), SCROLL_STORE, 0.625f, -0.3125f, 0.727f, 100, 100, 255, 0.5f); + aScrollBars[8].Init(CVector( 74.823f, -1086.879f, 31.495f), SCROLL_ENTERTAINMENT, -0.2083f, 0.1041f, 0.5f, 255, 255, 128, 0.3f); + aScrollBars[9].Init(CVector( -36.459f, -1031.2371f, 32.534f), SCROLL_ENTERTAINMENT, -0.1442f, 0.0721f, 0.229f, 150, 255, 50, 0.3f); + aScrollBars[10].Init(CVector( 1208.0f, -62.208f, 19.157f), SCROLL_USED_CARS, 0.0642f, -0.20365f, 0.229f, 255, 128, 0, 0.3f); + + // Initialize tower clocks + aTowerClocks[0].Init(CVector(59.4f, -1081.3f, 54.15f), -1.0f, 0.0f, 0, 0, 0, 80.0f, 2.0f); + aTowerClocks[1].Init(CVector(55.4f, -1083.6f, 54.15f), 0.0f, -1.0f, 0, 0, 0, 80.0f, 2.0f); + + // Initialize digital clocks + CVector2D sz(3.7f, 2.144f); + sz.Normalise(); + aDigitalClocks[0].Init( + CVector(54.485f - sz.x * 0.05f + sz.y * 0.3f, -1081.679f - sz.y * 0.05f - sz.x * 0.3f, 32.803f), + sz.y, -sz.x, 255, 0, 0, 100.0f, 0.8f + ); + aDigitalClocks[1].Init( + CVector(60.564f + sz.x * 0.05f - sz.y * 0.3f, -1083.089f + sz.y * 0.05f + sz.x * 0.3f, 32.803f), + -sz.y, sz.x, 0, 0, 255, 100.0f, 0.8f + ); + aDigitalClocks[2].Init( + CVector(58.145f - sz.y * 0.05f - sz.x * 0.3f, -1079.268f + sz.x * 0.05f - sz.y * 0.3f, 32.803f), + -sz.x, -sz.y, 0, 255, 0, 100.0f, 0.8f + ); +} + +void CMovingThings::Shutdown() +{ + int i; + for (i = 0; i < ARRAY_SIZE(aScrollBars); ++i) + aScrollBars[i].SetVisibility(false); + for (i = 0; i < ARRAY_SIZE(aTowerClocks); ++i) + aTowerClocks[i].SetVisibility(false); + for (i = 0; i < ARRAY_SIZE(aDigitalClocks); ++i) + aDigitalClocks[i].SetVisibility(false); +} + +void CMovingThings::Update() +{ + int16 i; +#ifndef SQUEEZE_PERFORMANCE + const int TIME_SPAN = 64; // frames to process all aMovingThings + + int block = CTimer::GetFrameCounter() % TIME_SPAN; + + for (i = (block * NUMMOVINGTHINGS) / TIME_SPAN; i < ((block + 1) * NUMMOVINGTHINGS) / TIME_SPAN; i++) { + if (aMovingThings[i].m_nHidden == 1) + aMovingThings[i].Update(); + } + + for (i = 0; i < CMovingThings::Num; i++) { + if (aMovingThings[i].m_nHidden == 0) + aMovingThings[i].Update(); + } +#endif + + for (i = 0; i < ARRAY_SIZE(aScrollBars); ++i) + { + if (aScrollBars[i].IsVisible() || (CTimer::GetFrameCounter() + i) % 8 == 0) + aScrollBars[i].Update(); + } + for (i = 0; i < ARRAY_SIZE(aTowerClocks); ++i) + { + if (aTowerClocks[i].IsVisible() || (CTimer::GetFrameCounter() + i) % 8 == 0) + aTowerClocks[i].Update(); + } + for (i = 0; i < ARRAY_SIZE(aDigitalClocks); ++i) + { + if (aDigitalClocks[i].IsVisible() || (CTimer::GetFrameCounter() + i) % 8 == 0) + aDigitalClocks[i].Update(); + } +} + +void CMovingThings::Render() +{ + int i; + PUSH_RENDERGROUP("CMovingThings::Render"); + for (i = 0; i < ARRAY_SIZE(aScrollBars); ++i) + { + if (aScrollBars[i].IsVisible()) + aScrollBars[i].Render(); + } + for (i = 0; i < ARRAY_SIZE(aTowerClocks); ++i) + { + if (aTowerClocks[i].IsVisible()) + aTowerClocks[i].Render(); + } + for (i = 0; i < ARRAY_SIZE(aDigitalClocks); ++i) + { + if (aDigitalClocks[i].IsVisible()) + aDigitalClocks[i].Render(); + } + POP_RENDERGROUP(); +} + +// ---------- CMovingThing ---------- +void CMovingThing::Update() +{ + m_pEntity->GetMatrix().UpdateRW(); + m_pEntity->UpdateRwFrame(); + + if (SQR(m_pEntity->GetPosition().x - TheCamera.GetPosition().x) + SQR(m_pEntity->GetPosition().y - TheCamera.GetPosition().y) < 40000.0f) { + if (m_nHidden == 1) { + AddToList(&CMovingThings::StartCloseList); + m_nHidden = 0; + } + } else { + if (m_nHidden == 0) { + RemoveFromList(); + m_nHidden = 1; + } + } +} + +void CMovingThing::AddToList(CMovingThing *pThing) +{ + m_pNext = pThing->m_pNext; + m_pPrev = pThing; + pThing->m_pNext = this; + m_pNext->m_pPrev = this; +} + +void CMovingThing::RemoveFromList() +{ + m_pNext->m_pPrev = m_pPrev; + m_pPrev->m_pNext = m_pNext; +} + +int16 CMovingThing::SizeList() +{ + CMovingThing *next = m_pNext; + int16 count = 0; + + while (next != nil) { + next = next->m_pNext; + count++; + } + + return count; +} + +// ---------- Find message functions ---------- +const char* FindTunnelMessage() +{ + if (CStats::CommercialPassed) + return "LIBERTY TUNNEL HAS BEEN OPENED TO ALL TRAFFIC . . . "; + + if (CStats::IndustrialPassed) + return "FIRST PHASE LIBERTY TUNNEL HAS BEEN COMPLETED . . . "; + + return "FIRST PHASE LIBERTY TUNNEL ABOUT TO BE COMPLETED . . . "; +} + +const char* FindBridgeMessage() +{ + if (CStats::CommercialPassed) + return "STAUNTON LIFT BRIDGE IS OPERATIONAL AGAIN "; + + if (CStats::IndustrialPassed) + return "LONG DELAYS BEHIND US AS CALLAHAN BRIDGE IS FIXED . . . STAUNTON LIFT BRIDGE STUCK OPEN "; + + return "CHAOS AS CALLAHAN BRIDGE IS UNDER REPAIR. . . "; +} + +char String_Time[] = "THE TIME IS 12:34 "; +const char* FindTimeMessage() +{ + String_Time[12] = '0' + CClock::GetHours() / 10; + String_Time[13] = '0' + CClock::GetHours() % 10; + String_Time[15] = '0' + CClock::GetMinutes() / 10; + String_Time[16] = '0' + CClock::GetMinutes() % 10; + return String_Time; +} + +char String_DigitalClock[] = "12:34"; +const char* FindDigitalClockMessage() +{ + if (((CTimer::GetTimeInMilliseconds() >> 10) & 7) < 6) + { + String_DigitalClock[0] = '0' + CClock::GetHours() / 10; + String_DigitalClock[1] = '0' + CClock::GetHours() % 10; + String_DigitalClock[2] = CTimer::GetTimeInMilliseconds() & 0x200 ? ':' : ' '; + String_DigitalClock[3] = '0' + CClock::GetMinutes() / 10; + String_DigitalClock[4] = '0' + CClock::GetMinutes() % 10; + } + else + { + // they didn't use rad2deg here because of 3.14 + int temperature = 13.0f - 6.0f * Cos((CClock::GetMinutes() + 60.0f * CClock::GetHours()) / (4.0f * 180.0f / 3.14f) - 1.0f); + String_DigitalClock[0] = '0' + temperature / 10; + if (String_DigitalClock[0] == '0') + String_DigitalClock[0] = ' '; + String_DigitalClock[1] = '0' + temperature % 10; + String_DigitalClock[2] = ' '; + String_DigitalClock[3] = '@'; + String_DigitalClock[4] = 'C'; + } + return String_DigitalClock; +} + +// ---------- CScrollBar ---------- +void CScrollBar::Init(CVector position, uint8 type, float sizeX, float sizeY, float sizeZ, uint8 red, uint8 green, uint8 blue, float scale) +{ + for (int i = 0; i < ARRAY_SIZE(m_MessageBar); ++i) + m_MessageBar[i] = 0; + + m_pMessage = ". "; + m_MessageCurrentChar = 0; + m_MessageLength = 2; + + m_Counter = 0; + m_bVisible = false; + m_Position = position; + m_Type = type; + m_Size.x = sizeX; + m_Size.y = sizeY; + m_Size.z = sizeZ; + m_uRed = red; + m_uGreen = green; + m_uBlue = blue; + m_fScale = scale; +} + +void CScrollBar::Update() +{ + float distanceFromCamera = (TheCamera.GetPosition() - m_Position).Magnitude(); + if (distanceFromCamera > 100.0f) + { + m_bVisible = false; + return; + } + + m_bVisible = true; + + if (distanceFromCamera < 75.0f) + m_fIntensity = 1.0f; + else + m_fIntensity = 1.0f - 4.0f * (distanceFromCamera - 75.0f) / 100.0f; + + m_Counter = (m_Counter + 1) % 8; + + // if message is fully printed, load up the next one + if (m_Counter == 0 && ++m_MessageCurrentChar >= m_MessageLength) + { + const char* previousMessage = m_pMessage; + switch (m_Type) + { + case SCROLL_BUSINESS: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 7) + { + case 0: + m_pMessage = "SHARES UYE<10% DWD<20% NDWE>22% . . . "; + break; + case 1: + m_pMessage = "CRIME WAVE HITS LIBERTY CITY . . . "; + break; + case 2: + m_pMessage = "SHARES OBR>29% MADD<76% LEZ<11% ADAMSKI>53% AAG>110%. . . "; + break; + case 3: + m_pMessage = FindTunnelMessage(); + break; + case 4: + m_pMessage = FindBridgeMessage(); + break; + case 5: + m_pMessage = FindTimeMessage(); + break; + case 6: + if (CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH || CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_GERMAN) + m_pMessage = FindTimeMessage(); + else + m_pMessage = "WWW.GRANDTHEFTAUTO3.COM "; + break; + } + } + break; + case SCROLL_TRAFFIC: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 8) + { + case 0: + m_pMessage = "DRIVE CAREFULLY . . . "; + break; + case 1: + m_pMessage = "RECENT WAVE OF CARJACKINGS. KEEP YOUR DOORS LOCKED !!! "; + break; + case 2: + m_pMessage = "CHECK YOUR SPEED . . . "; + break; + case 3: + m_pMessage = "KEEP YOUR EYES ON THE ROAD AND NOT ON THIS SIGN "; + break; + case 4: + if (CWeather::Foggyness > 0.5f) + m_pMessage = "POOR VISIBILITY ! "; + else if (CWeather::WetRoads > 0.5f) + m_pMessage = "ROADS ARE SLIPPERY ! "; + else + m_pMessage = "ENJOY YOUR TRIP "; + break; + case 5: + m_pMessage = FindTunnelMessage(); + break; + case 6: + m_pMessage = FindBridgeMessage(); + break; + case 7: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + case SCROLL_ENTERTAINMENT: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 12) + { + case 0: + m_pMessage = " )69TH STREET) STILL HOLDS TOP POSITION THIS MONTH AT THE BOX-OFFICE WITH )MY FAIR LADYBOY) JUST CREEPING UP BEHIND. "; + break; + case 1: + m_pMessage = " TALKING OF )FANNIE). THERE IS STILL TIME TO CATCH THIS LOVELY FAMILY MUSICAL, ABOUT THE ORPHAN WHO IS SO EASILY TAKEN IN BY ANY MAN WITH LOADS OF MONEY. "; + break; + case 2: + m_pMessage = " DO NOT MISS )GTA3, THE MUSICAL) . . . "; + break; + case 3: + m_pMessage = + " STILL RUNNING ARE )RATS) AND )GUYS AND DOGS), BETWEEN THEN THEY SHOULD HAVE THE LEGS TO LAST TILL THE AND OF THE YEAR. . . " + " ALSO FOR FOUR LEGGED FANS, THE STAGE VERSION OF THE GRITTY REALISTIC )SATERDAY NIGHT BEAVER) OPENED LAST WEEKEND," + " AND I FOR ONE CERTAINLY ENJOYED THAT. "; + break; + case 4: + m_pMessage = + " NOW SHOWING STATE-WIDE, ARNOLD STEELONE, HOLLYWOODS BEST LIVING SPECIAL EFFECT, APPEARS AGAIN AS A HALF_MAN," + " HALF ANDROID IN THE HALF-BAKED ROMP, )TOP DOWN CITY). AN HOMAGE TO HIS EARLIER TWO MULTI_MILLION MAKING MOVIES," + " IN WHICH HE PLAYED TWO-DEE, AN OUT OF CONTROL MONSTER, INTENT ON CORRUPTING CIVILISATION! "; + break; + case 5: + m_pMessage = + " ALSO APPEARING THIS WEEK )HALF-COCKED) SEES CHUCK SCHWARTZ UP TO HIS USUAL NONSENSE AS HE TAKES ON HALF OF LIBERTY CITY" + " IN AN ATTEMPT TO SAVE HIS CROSS-DRESSING LADY-BOY SIDEKICK, )MISS PING-PONG), FROM A GANG OF RUTHLESS COSMETIC SURGEONS. "; + break; + case 6: + m_pMessage = + " STILL SHOWING: )SOLDIERS OF MISFORTUNE), ATTROCIOUS ACTING WHICH SEES BOYZ 2 GIRLZ) TRANSITION FROM THE CHARTS TO THE BIG SCREEN," + " AT LEAST THEY ALL DIE AT THE END. . . "; + break; + case 7: + m_pMessage = + " )BADFELLAS) IS STILL GOING STRONG WITH CROWDS ALMOST BEING PUSHED INTO CINEMAS TO SEE THIS ONE." + " ANOTHER ONE WORTH LOOKING INTO IS )THE TUNNEL). "; + break; + case 8: + m_pMessage = FindTunnelMessage(); + break; + case 9: + m_pMessage = FindBridgeMessage(); + break; + case 10: + m_pMessage = FindTimeMessage(); + break; + case 11: + m_pMessage = "WWW.ROCKSTARGAMES.COM "; + break; + } + } + break; + case SCROLL_AIRPORT_DOORS: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 4) + { + case 0: + m_pMessage = "WELCOME TO LIBERTY CITY . . . "; + break; + case 1: + m_pMessage = "PLEASE HAVE YOUR PASSPORT READY . . . "; + break; + case 2: + m_pMessage = "PLACE KEYS, FIREARMS, CHANGE AND OTHER METAL OBJECTS ON THE TRAY PLEASE . . . "; + break; + case 3: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + case SCROLL_AIRPORT_FRONT: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 4) + { + case 0: + m_pMessage = "WELCOME TO FRANCIS INTERNATIONAL AIRPORT . . . "; + break; + case 1: + m_pMessage = "PLEASE DO NOT LEAVE LUGGAGE UNATTENDED . . . "; + break; + case 2: + m_pMessage = "FOLLOW 1 FOR LONG AND SHORT TERM PARKING "; + break; + case 3: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + case SCROLL_STORE: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 10) + { + case 0: + m_pMessage = "WWW.ROCKSTARGAMES.COM "; + break; + case 1: + m_pMessage = "GTA3 OUT NOW . . . "; + break; + case 2: + m_pMessage = "OUR STUFF IS CHEAP CHEAP CHEAP "; + break; + case 3: + m_pMessage = "BUY 12 CDS GET ONE FREE . . . "; + break; + case 4: + m_pMessage = "APPEARING IN SHOP SOON, )THE BLOODY CHOPPERS), WITH THEIR NEW ALBUM, )IS THAT MY DAUGHTER?) "; + break; + case 5: + m_pMessage = "THIS MONTH IS OUR CRAZY CLEAROUT MONTH, EVERYTHING MUST GO, CDS, DVDS, STAFF, EVEN OUR CARPETS! "; + break; + case 6: + m_pMessage = + "OUT THIS WEEK: THE THEME TUNE TO )BOYS TO GIRLS) FIRST MOVIE )SOLDIERS OF MISFORTUNE), " + "THE SINGLE )LET ME IN YOU)RE BODY-BAG) IS TAKEN FROM THE SOUNDTRACK ALBUM, )BOOT CAMP BOYS). " + "ALSO INCLUDES THE SMASH SINGLE, )PRAY IT GOES OK). "; + break; + case 7: + m_pMessage = + "ALBUMS OUT THIS WEEK: MARYDANCING, )MUTHA O) CHRIST), FEATURING THE SINGLE )WASH HIM OFF), " + "ALSO CRAIG GRAYS) DEBUT, )FADE AWAY), INCLUDES THE SINGLE OF THE SAME NAME. . . "; + break; + case 8: + m_pMessage = + "ON THE FILM FRONT, A NELY COMPILED COMPILATION OF ARNOLD STEELONES GREATEST MOVIES ON DVD. " + "THE PACK INCLUDES THE EARLY )BY-CEP), THE CULT CLASSIC )FUTURE ANNHILATOR), AND THE HILARIOUS CROSS-DRESSING COMEDY )SISTERS). " + "ONE FOR ALL THE FAMILY. . . "; + break; + case 9: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + case SCROLL_USED_CARS: + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 11) + { + case 0: + m_pMessage = "QUICK, TAKE A LOOK AT OUR CURRENT STOCK )CAUSE THESE AUTOS ARE MOVIN) FAST . . . "; + break; + case 1: + m_pMessage = "THAT)S RIGHT, HERE AT )CAPITAL AUTO SALES) OUR VEHICLES ARE SO GOOD THAT THEY PRACTICALLY DRIVE THEMSELVES OFF OUR LOT . . . "; + break; + case 2: + m_pMessage = "EASY CREDIT ON ALL CARS . . . "; + break; + case 3: + m_pMessage = "FEEL LIKE A STUD IN ONE OF OUR STALLIONS OR TEST-DRIVE OUR BANSHEE, IT)S A REAL STEAL!!! "; + break; + case 4: + m_pMessage = "TRY OUR HARDY PERENNIAL, IT)LL LAST YOU THE WHOLE YEAR. OUR BOBCATS AIN)T NO PUSSIES EITHER!!! "; + break; + case 5: + m_pMessage = "IF IT)S A GUARANTEE YOU'RE AFTER, GO SOMEWHERE ELSE, )CAPITAL) CARS ARE THAT GOOD THEY DON)T NEED GUARANTEES!!! "; + break; + case 6: + m_pMessage = "TOP DOLLAR OFFERED FOR YOUR OLD WHEELS, NOT YOUR CAR, JUST IT)S WHEELS. . . "; + break; + case 7: + m_pMessage = "THAT)S RIGHT WE)RE CAR SILLY. TEST DRIVE ANY CAR, YOU WON)T WANT TO BRING IT BACK!!! "; + break; + case 8: + m_pMessage = "FREE FLUFFY DICE WITH ALL PURCHASES. . ."; + break; + case 9: + if (CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH || CMenuManager::m_PrefsLanguage == CMenuManager::LANGUAGE_GERMAN) + m_pMessage = "QUICK, TAKE A LOOK AT OUR CURRENT STOCK )CAUSE THESE AUTOS ARE MOVIN) FAST . . . "; + else + m_pMessage = "HTTP:((ROCKSTARGAMES.COM(GRANDTHEFTAUTO3(CAPITALAUTOS "; + break; + case 10: + m_pMessage = FindTimeMessage(); + break; + } + } + break; + } + + m_MessageLength = (uint32)strlen(m_pMessage); + m_MessageCurrentChar = 0; + } + + // Scroll + for (int i = 0; i < ARRAY_SIZE(m_MessageBar)-1; i++) + m_MessageBar[i] = m_MessageBar[i + 1]; + m_MessageBar[ARRAY_SIZE(m_MessageBar)-1] = m_Counter < 5 ? ScrollCharSet[m_pMessage[m_MessageCurrentChar] - ' '][m_Counter] : 0; + + // Introduce some random displaying glitches; signs aren't supposed to be perfect :P + switch (CGeneral::GetRandomNumber() & 0xFF) + { + case 0x0D: m_MessageBar[ARRAY_SIZE(m_MessageBar)-1] = 0; break; + case 0xE3: m_MessageBar[ARRAY_SIZE(m_MessageBar)-1] = 0xE3; break; + case 0x64: m_MessageBar[ARRAY_SIZE(m_MessageBar)-1] = ~m_MessageBar[ARRAY_SIZE(m_MessageBar)-1]; break; + } +} + +void CScrollBar::Render() +{ + if (!TheCamera.IsSphereVisible(m_Position, 2.0f * 20.0f * (ABS(m_Size.x) + ABS(m_Size.y)))) + return; + + CSprite::InitSpriteBuffer(); + + // Calculate intensity of colours + uint8 r = m_fIntensity * m_uRed; + uint8 g = m_fIntensity * m_uGreen; + uint8 b = m_fIntensity * m_uBlue; + + // Set render states + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + CVector coronaCoord, screenCoord; + float screenW, screenH; + for (int i = 1; i < ARRAY_SIZE(m_MessageBar); ++i) + { + for (int j = 0; j < 5; ++j) + { + coronaCoord.x = m_Position.x + m_Size.x * i; + coronaCoord.y = m_Position.y + m_Size.y * i; + coronaCoord.z = m_Position.z + m_Size.z * j; + + // Render main coronas + if (m_MessageBar[i] & (1 << j)) + { + if (CSprite::CalcScreenCoors(coronaCoord, &screenCoord, &screenW, &screenH, true)) + { + CSprite::RenderBufferedOneXLUSprite( + screenCoord.x, screenCoord.y, screenCoord.z, + screenW * m_fScale, screenH * m_fScale, + r, g, b, + 255, 1.0f / screenCoord.z, 255); + } + } + // Render smaller and faded coronas for a trailing effect + else if (m_MessageBar[i - 1] & (1 << j)) + { + if (CSprite::CalcScreenCoors(coronaCoord, &screenCoord, &screenW, &screenH, true)) + { + CSprite::RenderBufferedOneXLUSprite( + screenCoord.x, screenCoord.y, screenCoord.z, + screenW * m_fScale * 0.8f, + screenH * m_fScale * 0.8f, + r / 2, + g / 2, + b / 2, + 255, 1.0f / screenCoord.z, 255); + } + } + } + } + + CSprite::FlushSpriteBuffer(); +} + +// ---------- CTowerClock ---------- +void CTowerClock::Init(CVector position, float sizeX, float sizeY, uint8 red, uint8 green, uint8 blue, float drawDistance, float scale) +{ + m_bVisible = false; + m_Position = position; + m_Size.x = sizeX; + m_Size.y = sizeY; + m_Size.z = 0.0f; + m_uRed = red; + m_uGreen = green; + m_uBlue = blue; + m_fDrawDistance = drawDistance; + m_fScale = scale; +} + +void CTowerClock::Update() +{ + float distanceFromCamera = (TheCamera.GetPosition() - m_Position).Magnitude(); + if (distanceFromCamera < m_fDrawDistance) + { + m_bVisible = true; + if (distanceFromCamera < 0.75f * m_fDrawDistance) + m_fIntensity = 1.0f; + else + m_fIntensity = 1.0f - (distanceFromCamera - 0.75f * m_fDrawDistance) * 4.0f / m_fDrawDistance; + } + else + m_bVisible = false; +} + +RwIm3DVertex TempV[4]; +void CTowerClock::Render() +{ + if (TheCamera.IsSphereVisible(m_Position, m_fScale)) + { + // Calculate angle for each clock index + float angleHour = 2.0f * (float)PI * (CClock::GetMinutes() + 60.0f * CClock::GetHours()) / 720.0f; + float angleMinute = 2.0f * (float)PI * (CClock::GetSeconds() + 60.0f * CClock::GetMinutes()) / 3600.0f; + + // Prepare render states + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + // Set vertices colors + RwIm3DVertexSetRGBA(&TempV[0], m_uRed, m_uGreen, m_uBlue, (uint8)(m_fIntensity * 255.0f)); + RwIm3DVertexSetRGBA(&TempV[1], m_uRed, m_uGreen, m_uBlue, (uint8)(m_fIntensity * 255.0f)); + RwIm3DVertexSetRGBA(&TempV[2], m_uRed, m_uGreen, m_uBlue, (uint8)(m_fIntensity * 255.0f)); + RwIm3DVertexSetRGBA(&TempV[3], m_uRed, m_uGreen, m_uBlue, (uint8)(m_fIntensity * 255.0f)); + + // Set vertices position + RwIm3DVertexSetPos(&TempV[0], m_Position.x, m_Position.y, m_Position.z); + RwIm3DVertexSetPos( + &TempV[1], + m_Position.x + Sin(angleMinute) * m_fScale * m_Size.x, + m_Position.y + Sin(angleMinute) * m_fScale * m_Size.y, + m_Position.z + Cos(angleMinute) * m_fScale + ); + RwIm3DVertexSetPos(&TempV[2], m_Position.x, m_Position.y, m_Position.z); + RwIm3DVertexSetPos( + &TempV[3], + m_Position.x + Sin(angleHour) * 0.75f * m_fScale * m_Size.x, + m_Position.y + Sin(angleHour) * 0.75f * m_fScale * m_Size.y, + m_Position.z + Cos(angleHour) * 0.75f * m_fScale + ); + + LittleTest(); + + // Draw lines + if (RwIm3DTransform(TempV, 4, nil, 0)) + { + RwIm3DRenderLine(0, 1); + RwIm3DRenderLine(2, 3); + RwIm3DEnd(); + } + } +} + +// ---------- CDigitalClock ---------- +void CDigitalClock::Init(CVector position, float sizeX, float sizeY, uint8 red, uint8 green, uint8 blue, float drawDistance, float scale) +{ + m_bVisible = false; + m_Position = position; + m_Size.x = sizeX; + m_Size.y = sizeY; + m_Size.z = 0.0f; + m_uRed = red; + m_uGreen = green; + m_uBlue = blue; + m_fDrawDistance = drawDistance; + m_fScale = scale; +} + +void CDigitalClock::Update() +{ + float distanceFromCamera = (TheCamera.GetPosition() - m_Position).Magnitude(); + if (distanceFromCamera < m_fDrawDistance) + { + m_bVisible = true; + if (distanceFromCamera < 0.75f * m_fDrawDistance) + m_fIntensity = 1.0f; + else + m_fIntensity = 1.0f - (distanceFromCamera - 0.75f * m_fDrawDistance) * 4.0f / m_fDrawDistance; + } + else + m_bVisible = false; +} + +void CDigitalClock::Render() +{ + if (TheCamera.IsSphereVisible(m_Position, 5.0f * m_fScale)) + { + CSprite::InitSpriteBuffer(); + + // Simulate flicker + float currentIntensity = m_fIntensity * CGeneral::GetRandomNumberInRange(0x300, 0x400) / 1024.0f; + + uint8 r = currentIntensity * m_uRed; + uint8 g = currentIntensity * m_uGreen; + uint8 b = currentIntensity * m_uBlue; + + // Set render states + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpCoronaTexture[0])); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + const char* clockMessage = FindDigitalClockMessage(); + + CVector coronaCoord, screenCoord; + float screenW, screenH; + for (int c = 0; c < 5; ++c) // for each char to be displayed + { + for (int i = 0; i < 5; ++i) // for each column of coronas + { + for (int j = 0; j < 5; ++j) // for each row of coronas + { + if (ScrollCharSet[clockMessage[c] - ' '][i] & (1 << j)) + { + coronaCoord.x = m_Position.x + (8 * c + i) * m_Size.x * m_fScale / 8.0f; + coronaCoord.y = m_Position.y + (8 * c + i) * m_Size.y * m_fScale / 8.0f; + coronaCoord.z = m_Position.z + j * m_fScale / 8.0f; + + if (CSprite::CalcScreenCoors(coronaCoord, &screenCoord, &screenW, &screenH, true)) + { + CSprite::RenderBufferedOneXLUSprite( + screenCoord.x, screenCoord.y, screenCoord.z, + screenW * m_fScale * 0.12f, + screenW * m_fScale * 0.12f, + r, g, b, + 255, + 1.0f / screenCoord.z, + 255); + } + } + } + } + } + + CSprite::FlushSpriteBuffer(); + } +} diff --git a/src/renderer/Fluff.h b/src/renderer/Fluff.h new file mode 100644 index 0000000..fe3ab25 --- /dev/null +++ b/src/renderer/Fluff.h @@ -0,0 +1,106 @@ +#pragma once +#include "common.h" +#include "Vector.h" + +class CMovingThing +{ +public: + CMovingThing *m_pNext; + CMovingThing *m_pPrev; + int16 m_nType; + int16 m_nHidden; + CVector m_vecPosn; + CEntity* m_pEntity; + + void Update(); + void AddToList(CMovingThing *pThing); + void RemoveFromList(); + int16 SizeList(); +}; + +#define NUMMOVINGTHINGS 128 + +class CMovingThings +{ +public: + static CMovingThing StartCloseList; + static CMovingThing EndCloseList; + static int16 Num; + static CMovingThing aMovingThings[NUMMOVINGTHINGS]; + + static void Init(); + static void Shutdown(); + static void Update(); + static void Render(); +}; + +class CScrollBar +{ +private: + uint8 m_Counter; + const char* m_pMessage; + CVector m_Position; + uint32 m_MessageCurrentChar; + uint32 m_MessageLength; + CVector m_Size; + float m_fIntensity; + uint8 m_MessageBar[40]; + uint8 m_Type; + bool m_bVisible; + uint8 m_uRed; + uint8 m_uGreen; + uint8 m_uBlue; + float m_fScale; + +public: + void SetVisibility(bool visible) { m_bVisible = visible; } + bool IsVisible() { return m_bVisible; } + + void Init(CVector, uint8, float, float, float, uint8, uint8, uint8, float); + void Update(); + void Render(); +}; + +class CTowerClock +{ +private: + CVector m_Position; + CVector m_Size; + float m_fDrawDistance; + float m_fScale; + uint8 m_uRed; + uint8 m_uGreen; + uint8 m_uBlue; + bool m_bVisible; + float m_fIntensity; + +public: + void SetVisibility(bool visible) { m_bVisible = visible; } + bool IsVisible() { return m_bVisible; } + + void Init(CVector, float, float, uint8, uint8, uint8, float, float); + void Update(); + void Render(); +}; + +class CDigitalClock +{ +private: + CVector m_Position; + CVector m_Size; + float m_fDrawDistance; + float m_fScale; + uint8 m_uRed; + uint8 m_uGreen; + uint8 m_uBlue; + bool m_bVisible; + float m_fIntensity; + +public: + void SetVisibility(bool visible) { m_bVisible = visible; } + bool IsVisible() { return m_bVisible; } + + void Init(CVector, float, float, uint8, uint8, uint8, float, float); + void Update(); + void Render(); +}; \ No newline at end of file diff --git a/src/renderer/Font.cpp b/src/renderer/Font.cpp new file mode 100644 index 0000000..6a9944e --- /dev/null +++ b/src/renderer/Font.cpp @@ -0,0 +1,1628 @@ +#include "common.h" + +#include "Sprite2d.h" +#include "TxdStore.h" +#include "Font.h" +#ifdef BUTTON_ICONS +#include "FileMgr.h" +#endif + +void +AsciiToUnicode(const char *src, wchar *dst) +{ + while((*dst++ = (unsigned char)*src++) != '\0'); +} + +void +UnicodeStrcat(wchar *dst, wchar *append) +{ + UnicodeStrcpy(&dst[UnicodeStrlen(dst)], append); +} + +void +UnicodeStrcpy(wchar *dst, const wchar *src) +{ + while((*dst++ = *src++) != '\0'); +} + +int +UnicodeStrlen(const wchar *str) +{ + int len; + for(len = 0; *str != '\0'; len++, str++); + return len; +} + +CFontDetails CFont::Details; +bool16 CFont::NewLine; +CSprite2d CFont::Sprite[MAX_FONTS]; + +#ifdef MORE_LANGUAGES +uint8 CFont::LanguageSet = FONT_LANGSET_EFIGS; +int32 CFont::Slot = -1; +#define JAP_TERMINATION (0x8000 | '~') + +int16 CFont::Size[LANGSET_MAX][MAX_FONTS][193] = { + { +#else +int16 CFont::Size[MAX_FONTS][193] = { +#endif + +#if !defined(GTA_PS2) || defined(FIX_BUGS) + { + 13, 12, 31, 35, 23, 35, 31, 9, 14, 15, 25, 30, 11, 17, 13, 31, + 23, 16, 22, 21, 24, 23, 23, 20, 23, 22, 10, 35, 26, 26, 26, 26, + 30, 26, 24, 23, 24, 22, 21, 24, 26, 10, 20, 26, 22, 29, 26, 25, + 23, 25, 24, 24, 22, 25, 24, 29, 29, 23, 25, 37, 22, 37, 35, 37, + 35, 21, 22, 21, 21, 22, 13, 22, 21, 10, 16, 22, 11, 32, 21, 21, + 23, 22, 16, 20, 14, 21, 20, 30, 25, 21, 21, 33, 33, 33, 33, 35, + 27, 27, 27, 27, 32, 24, 23, 23, 23, 23, 11, 11, 11, 11, 26, 26, + 26, 26, 26, 26, 26, 25, 26, 21, 21, 21, 21, 32, 23, 22, 22, 22, + 22, 11, 11, 11, 11, 22, 22, 22, 22, 22, 22, 22, 22, 26, 21, 24, + 12, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 18, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 20 + }, + + { + 13, 9, 21, 35, 23, 35, 35, 11, 35, 35, 25, 35, 11, 17, 13, 33, + 28, 14, 22, 21, 24, 23, 23, 21, 23, 22, 10, 35, 13, 35, 13, 33, + 5, 25, 22, 23, 24, 21, 21, 24, 24, 9, 20, 24, 21, 27, 25, 25, + 22, 25, 23, 20, 23, 23, 23, 31, 23, 23, 23, 37, 33, 37, 35, 37, + 35, 21, 19, 19, 21, 19, 17, 21, 21, 8, 17, 18, 14, 24, 21, 21, + 20, 22, 19, 20, 20, 19, 20, 26, 21, 20, 21, 33, 33, 33, 33, 35, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 16 + }, + + { + 15, 14, 16, 25, 19, 26, 22, 11, 18, 18, 27, 26, 13, 19, 9, 27, + 19, 18, 19, 19, 22, 19, 20, 18, 19, 20, 12, 32, 15, 32, 15, 35, + 15, 19, 19, 19, 19, 19, 16, 19, 20, 9, 19, 20, 14, 29, 19, 20, + 19, 19, 19, 19, 21, 19, 20, 32, 20, 19, 19, 33, 31, 39, 37, 39, + 37, 21, 21, 21, 23, 21, 19, 23, 23, 10, 19, 20, 16, 26, 23, 23, + 20, 20, 20, 22, 21, 22, 22, 26, 22, 22, 23, 35, 35, 35, 35, 37, + 19, 19, 19, 19, 29, 19, 19, 19, 19, 19, 9, 9, 9, 9, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 30, 19, 19, 19, 19, + 19, 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 23, 35, + 12, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19 + } +#else // #if defined(GTA_PS2) && !defined(FIX_BUGS) + { + 13, 12, 31, 35, 23, 35, 31, 9, 14, 15, 25, 30, 11, 17, 13, 31, + 23, 16, 22, 21, 24, 23, 23, 20, 23, 22, 10, 35, 26, 26, 26, 26, + 30, 26, 24, 23, 24, 22, 21, 24, 26, 10, 20, 26, 22, 29, 26, 25, + 24, 25, 24, 24, 22, 25, 24, 29, 29, 23, 25, 37, 22, 37, 35, 37, + 35, 21, 22, 21, 21, 22, 13, 22, 21, 10, 16, 22, 11, 32, 21, 21, + 23, 22, 16, 20, 14, 21, 20, 30, 25, 21, 21, 33, 33, 33, 33, 35, + 27, 27, 27, 27, 32, 24, 23, 23, 23, 23, 11, 11, 11, 11, 26, 26, + 26, 26, 26, 26, 26, 25, 26, 21, 21, 21, 21, 32, 23, 22, 22, 22, + 22, 11, 11, 11, 11, 22, 22, 22, 22, 22, 22, 22, 22, 26, 21, 24, + 12, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 18, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 20 + }, + + { + 13, 9, 21, 35, 23, 35, 35, 11, 35, 35, 25, 35, 11, 17, 13, 33, + 28, 14, 22, 21, 24, 23, 23, 21, 23, 22, 10, 35, 13, 35, 13, 33, + 5, 25, 22, 23, 24, 21, 21, 24, 24, 9, 20, 24, 21, 27, 25, 25, + 22, 25, 23, 20, 23, 23, 23, 31, 23, 23, 23, 37, 33, 37, 35, 37, + 35, 21, 19, 19, 21, 19, 17, 21, 21, 8, 17, 18, 14, 24, 21, 21, + 20, 22, 19, 20, 20, 19, 20, 26, 21, 20, 21, 33, 33, 33, 33, 35, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 16 + }, + + { + 15, 14, 16, 25, 19, 26, 22, 11, 18, 18, 27, 26, 13, 19, 9, 27, + 19, 18, 19, 19, 21, 19, 20, 18, 19, 20, 12, 32, 15, 32, 15, 35, + 15, 19, 19, 19, 19, 19, 16, 19, 20, 9, 19, 20, 14, 29, 19, 19, + 19, 19, 19, 19, 21, 19, 20, 32, 20, 19, 19, 33, 31, 39, 37, 39, + 37, 21, 21, 21, 23, 21, 19, 23, 23, 10, 19, 20, 16, 26, 23, 23, + 20, 20, 20, 22, 21, 22, 22, 26, 22, 22, 23, 35, 35, 35, 35, 37, + 19, 19, 19, 19, 29, 19, 19, 19, 19, 19, 9, 9, 9, 9, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 30, 19, 19, 19, 19, 19, + 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 23, 35, 12, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19 + } +#endif + +#ifdef MORE_LANGUAGES + }, + { + { 13, 12, 31, 35, 23, 35, 31, 9, 14, 15, 25, 30, 11, 17, + 13, 31, 23, 16, 22, 21, 24, 23, 23, 20, 23, 22, 10, + 35, 26, 26, 26, 26, 30, 26, 24, 23, 24, 22, 21, 24, + 26, 10, 20, 26, 22, 29, 26, 25, 23, 25, 24, 24, 22, + 25, 24, 29, 29, 23, 25, 37, 22, 37, 35, 37, 35, 21, + 22, 21, 21, 22, 13, 22, 21, 10, 16, 22, 11, 32, 21, + 21, 23, 22, 16, 20, 14, 21, 20, 30, 25, 21, 21, 13, + 33, 13, 13, 13, 24, 22, 22, 19, 26, 21, 30, 20, 23, + 23, 21, 24, 26, 23, 22, 23, 21, 22, 20, 20, 26, 25, + 24, 22, 31, 32, 23, 30, 22, 22, 32, 23, 19, 18, 18, + 15, 22, 19, 27, 19, 20, 20, 18, 22, 24, 20, 19, 19, + 20, 19, 16, 19, 28, 20, 20, 18, 26, 27, 19, 26, 18, + 19, 27, 19, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 18, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 20 }, + { 13, 9, 21, 35, 23, 35, 35, 11, 35, 35, 25, 35, 11, + 17, 13, 33, 28, 14, 22, 21, 24, 23, 23, 21, 23, 22, + 10, 35, 13, 35, 13, 33, 5, 25, 22, 23, 24, 21, 21, 24, + 24, 9, 20, 24, 21, 27, 25, 25, 22, 25, 23, 20, 23, 23, + 23, 31, 23, 23, 23, 37, 33, 37, 35, 37, 35, 21, 19, + 19, 21, 19, 17, 21, 21, 8, 17, 18, 14, 24, 21, 21, 20, + 22, 19, 20, 20, 19, 20, 26, 21, 20, 21, 33, 33, 33, + 33, 35, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 16, }, + { 15, 14, 16, 25, 19, + 26, 22, 11, 18, 18, 27, 26, 13, 19, 9, 27, 19, 18, 19, + 19, 22, 19, 20, 18, 19, 20, 12, 32, 15, 32, 15, 35, + 15, 19, 19, 19, 19, 19, 16, 19, 20, 9, 19, 20, 14, 29, + 19, 20, 19, 19, 19, 19, 21, 19, 20, 32, 20, 19, 19, + 33, 31, 39, 37, 39, 37, 21, 21, 21, 23, 21, 19, 23, 23, 10, 19, 20, 16, 26, 23, + 21, 21, 20, 20, 22, 21, 22, 22, 26, 22, 22, 23, 35, + 35, 35, 35, 37, 19, 19, 19, 19, 19, 19, 29, 19, 19, + 19, 20, 22, 31, 19, 19, 19, 19, 19, 29, 19, 29, 19, + 21, 19, 30, 31, 21, 29, 19, 19, 29, 19, 21, 23, 32, + 21, 21, 30, 31, 22, 21, 32, 33, 23, 32, 21, 21, 32, + 21, 19, 19, 30, 31, 22, 22, 21, 32, 33, 23, 32, 21, + 21, 32, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 11, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19 }, + }, + + { + { + 13, 12, 31, 35, 23, 35, 31, 9, 14, 15, 25, 30, 11, 17, 13, 31, + 23, 16, 22, 21, 24, 23, 23, 20, 23, 22, 10, 35, 26, 26, 26, 26, + 30, 26, 24, 23, 24, 22, 21, 24, 26, 10, 20, 26, 22, 29, 26, 25, + 23, 25, 24, 24, 22, 25, 24, 29, 29, 23, 25, 37, 22, 37, 35, 37, + 35, 21, 22, 21, 21, 22, 13, 22, 21, 10, 16, 22, 11, 32, 21, 21, + 23, 22, 16, 20, 14, 21, 20, 30, 25, 21, 21, 33, 33, 33, 33, 35, + 27, 27, 27, 27, 32, 24, 23, 23, 23, 23, 11, 11, 11, 11, 26, 26, + 26, 26, 26, 26, 26, 25, 26, 21, 21, 21, 21, 32, 23, 22, 22, 22, + 22, 11, 11, 11, 11, 22, 22, 22, 22, 22, 22, 22, 22, 26, 21, 24, + 12, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 18, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 20 + }, + + { + 13, 9, 21, 35, 23, 35, 35, 11, 35, 35, 25, 35, 11, 17, 13, 33, + 28, 14, 22, 21, 24, 23, 23, 21, 23, 22, 10, 35, 13, 35, 13, 33, + 5, 25, 22, 23, 24, 21, 21, 24, 24, 9, 20, 24, 21, 27, 25, 25, + 22, 25, 23, 20, 23, 23, 23, 31, 23, 23, 23, 37, 33, 37, 35, 37, + 35, 21, 19, 19, 21, 19, 17, 21, 21, 8, 17, 18, 14, 24, 21, 21, + 20, 22, 19, 20, 20, 19, 20, 26, 21, 20, 21, 33, 33, 33, 33, 35, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 16 + }, + + { + 15, 14, 16, 25, 19, 26, 22, 11, 18, 18, 27, 26, 13, 19, 9, 27, + 19, 18, 19, 19, 22, 19, 20, 18, 19, 20, 12, 32, 15, 32, 15, 35, + 15, 19, 19, 19, 19, 19, 16, 19, 20, 9, 19, 20, 14, 29, 19, 20, + 19, 19, 19, 19, 21, 19, 20, 32, 20, 19, 19, 33, 31, 39, 37, 39, + 37, 21, 21, 21, 23, 21, 19, 23, 23, 10, 19, 20, 16, 26, 23, 23, + 20, 20, 20, 22, 21, 22, 22, 26, 22, 22, 23, 35, 35, 35, 35, 37, + 19, 19, 19, 19, 29, 19, 19, 19, 19, 19, 9, 9, 9, 9, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 30, 19, 19, 19, 19, + 19, 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 23, 35, + 12, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19 + } + } +#endif +}; + +#ifdef MORE_LANGUAGES +int16 Size_jp[] = { + 15, 14, 16, 20, 19, 26, 22, 11, 18, 18, 27, 26, 13, //; 0 + 19, 20, 27, 19, 15, 19, 19, 21, 19, 20, 18, 19, 15, //; 13 + 13, 28, 15, 32, 15, 35, 15, 19, 19, 19, 19, 17, 16, //; 26 + 19, 20, 15, 19, 20, 14, 17, 19, 19, 19, 19, 19, 19, //; 39 + 19, 19, 20, 25, 20, 19, 19, 33, 31, 39, 37, 39, 37, //; 52 + 21, 21, 21, 19, 17, 15, 23, 21, 15, 19, 20, 16, 19, //; 65 + 19, 19, 20, 20, 17, 22, 19, 22, 22, 19, 22, 22, 23, //; 78 + 35, 35, 35, 35, 37, 19, 19, 19, 19, 29, 19, 19, 19, //; 91 + 19, 19, 9, 9, 9, 9, 19, 19, 19, 19, 19, 19, 19, 19, //; 104 + 19, 19, 19, 19, 19, 30, 19, 19, 19, 19, 19, 10, 10, //; 118 + 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 23, 35, //; 131 + 12, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, //; 144 + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, //; 157 + 19, 19, 19, 11, 19, 19, 19, 19, 19, 19, 19, 19, 19, //; 170 + 19, 19, 19, 19, 19, 19, 19, 19, 19, 21 +}; +#endif + +wchar foreign_table[128] = { + 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, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 177, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 175, + 128, 129, 130, 0, 131, 0, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 0, 173, 142, 143, 144, 0, 145, 0, 0, 146, 147, 148, 149, 0, 0, 150, + 151, 152, 153, 0, 154, 0, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 0, 174, 165, 166, 167, 0, 168, 0, 0, 169, 170, 171, 172, 0, 0, 0, +}; + +#ifdef BUTTON_ICONS +CSprite2d CFont::ButtonSprite[MAX_BUTTON_ICONS]; +int CFont::PS2Symbol = BUTTON_NONE; +int CFont::ButtonsSlot = -1; +#endif // BUTTON_ICONS + +void +CFont::Initialise(void) +{ + int slot; + + slot = CTxdStore::AddTxdSlot("fonts"); +#ifdef MORE_LANGUAGES + Slot = slot; + switch (LanguageSet) + { + case FONT_LANGSET_EFIGS: + default: + CTxdStore::LoadTxd(slot, "MODELS/FONTS.TXD"); + break; + case FONT_LANGSET_POLISH: + CTxdStore::LoadTxd(slot, "MODELS/FONTS_P.TXD"); + break; + case FONT_LANGSET_RUSSIAN: + CTxdStore::LoadTxd(slot, "MODELS/FONTS_R.TXD"); + break; + case FONT_LANGSET_JAPANESE: + CTxdStore::LoadTxd(slot, "MODELS/FONTS_J.TXD"); + break; + } +#else + CTxdStore::LoadTxd(slot, "MODELS/FONTS.TXD"); +#endif + CTxdStore::AddRef(slot); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(slot); + Sprite[0].SetTexture("font2", "font2_mask"); +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + Sprite[1].SetTexture("FONTJAP", "FONTJAP_mask"); + Sprite[3].SetTexture("FONTJAP", "FONTJAP_mask"); + } + else +#endif // MORE_LANGUAGES + Sprite[1].SetTexture("pager", "pager_mask"); + Sprite[2].SetTexture("font1", "font1_mask"); + SetScale(1.0f, 1.0f); + SetSlantRefPoint(SCREEN_WIDTH, 0.0f); + SetSlant(0.0f); + SetColor(CRGBA(255, 255, 255, 0)); + SetJustifyOff(); + SetCentreOff(); +#ifdef FIX_BUGS + SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + SetCentreSize(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); +#else + SetWrapx(DEFAULT_SCREEN_WIDTH); + SetCentreSize(DEFAULT_SCREEN_WIDTH); +#endif + SetBackgroundOff(); + SetBackgroundColor(CRGBA(128, 128, 128, 128)); + SetBackGroundOnlyTextOff(); + SetPropOn(); + SetFontStyle(FONT_BANK); + SetRightJustifyWrap(0.0f); + SetAlphaFade(255.0f); + SetDropShadowPosition(0); + CTxdStore::PopCurrentTxd(); + +#if !defined(GAMEPAD_MENU) && defined(BUTTON_ICONS) + // loaded in CMenuManager with GAMEPAD_MENU defined + LoadButtons("MODELS/X360BTNS.TXD"); +#endif +} + +#ifdef BUTTON_ICONS +void +CFont::LoadButtons(const char* txdPath) +{ + if (int file = CFileMgr::OpenFile(txdPath)) { + CFileMgr::CloseFile(file); + if (ButtonsSlot == -1) + ButtonsSlot = CTxdStore::AddTxdSlot("buttons"); + else { + for (int i = 0; i < MAX_BUTTON_ICONS; i++) + ButtonSprite[i].Delete(); + CTxdStore::RemoveTxd(ButtonsSlot); + } + CTxdStore::LoadTxd(ButtonsSlot, txdPath); + CTxdStore::AddRef(ButtonsSlot); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(ButtonsSlot); +#if 0 // unused + ButtonSprite[BUTTON_UP].SetTexture("up"); + ButtonSprite[BUTTON_DOWN].SetTexture("down"); + ButtonSprite[BUTTON_LEFT].SetTexture("left"); + ButtonSprite[BUTTON_RIGHT].SetTexture("right"); +#endif + ButtonSprite[BUTTON_CROSS].SetTexture("cross"); + ButtonSprite[BUTTON_CIRCLE].SetTexture("circle"); + ButtonSprite[BUTTON_SQUARE].SetTexture("square"); + ButtonSprite[BUTTON_TRIANGLE].SetTexture("triangle"); + ButtonSprite[BUTTON_L1].SetTexture("l1"); + ButtonSprite[BUTTON_L2].SetTexture("l2"); + ButtonSprite[BUTTON_L3].SetTexture("l3"); + ButtonSprite[BUTTON_R1].SetTexture("r1"); + ButtonSprite[BUTTON_R2].SetTexture("r2"); + ButtonSprite[BUTTON_R3].SetTexture("r3"); + CTxdStore::PopCurrentTxd(); + } + else { + if (ButtonsSlot != -1) { + for (int i = 0; i < MAX_BUTTON_ICONS; i++) + ButtonSprite[i].Delete(); + CTxdStore::RemoveTxdSlot(ButtonsSlot); + ButtonsSlot = -1; + } + } +} +#endif // BUTTON_ICONS + +#ifdef MORE_LANGUAGES +void +CFont::ReloadFonts(uint8 set) +{ + if (Slot != -1 && LanguageSet != set) { + Sprite[0].Delete(); + Sprite[1].Delete(); + Sprite[2].Delete(); + if (IsJapanese()) + Sprite[3].Delete(); + CTxdStore::PushCurrentTxd(); + CTxdStore::RemoveTxd(Slot); + switch (set) + { + case FONT_LANGSET_EFIGS: + default: + CTxdStore::LoadTxd(Slot, "MODELS/FONTS.TXD"); + break; + case FONT_LANGSET_POLISH: + CTxdStore::LoadTxd(Slot, "MODELS/FONTS_P.TXD"); + break; + case FONT_LANGSET_RUSSIAN: + CTxdStore::LoadTxd(Slot, "MODELS/FONTS_R.TXD"); + break; + case FONT_LANGSET_JAPANESE: + CTxdStore::LoadTxd(Slot, "MODELS/FONTS_J.TXD"); + break; + } + CTxdStore::SetCurrentTxd(Slot); + Sprite[0].SetTexture("font2", "font2_mask"); + if (set == FONT_LANGSET_JAPANESE) { + Sprite[1].SetTexture("FONTJAP", "FONTJAP_mask"); + Sprite[3].SetTexture("FONTJAP", "FONTJAP_mask"); + } + else + Sprite[1].SetTexture("pager", "pager_mask"); + Sprite[2].SetTexture("font1", "font1_mask"); + CTxdStore::PopCurrentTxd(); + } + LanguageSet = set; +} +#endif + +void +CFont::Shutdown(void) +{ +#ifdef BUTTON_ICONS + if (ButtonsSlot != -1) { + for (int i = 0; i < MAX_BUTTON_ICONS; i++) + ButtonSprite[i].Delete(); + CTxdStore::RemoveTxdSlot(ButtonsSlot); + ButtonsSlot = -1; + } +#endif + Sprite[0].Delete(); + Sprite[1].Delete(); + Sprite[2].Delete(); +#ifdef MORE_LANGUAGES + if (IsJapanese()) + Sprite[3].Delete(); + CTxdStore::RemoveTxdSlot(Slot); + Slot = -1; +#else + CTxdStore::RemoveTxdSlot(CTxdStore::FindTxdSlot("fonts")); +#endif +} + +void +CFont::InitPerFrame(void) +{ + Details.bank = CSprite2d::GetBank(30, Sprite[0].m_pTexture); + CSprite2d::GetBank(15, Sprite[1].m_pTexture); + CSprite2d::GetBank(15, Sprite[2].m_pTexture); +#ifdef MORE_LANGUAGES + if (IsJapanese()) + CSprite2d::GetBank(15, Sprite[3].m_pTexture); +#endif + SetDropShadowPosition(0); + NewLine = false; +#ifdef BUTTON_ICONS + PS2Symbol = BUTTON_NONE; +#endif +} + +#ifdef BUTTON_ICONS +void +CFont::DrawButton(float x, float y) +{ + if (x <= 0.0f || x > SCREEN_WIDTH || y <= 0.0f || y > SCREEN_HEIGHT) + return; + + if (PS2Symbol != BUTTON_NONE) { + CRect rect; + rect.left = x; + rect.top = Details.scaleY + Details.scaleY + y; + rect.right = Details.scaleY * 17.0f + x; + rect.bottom = Details.scaleY * 19.0f + y; + + int vertexAlphaState; + RwRenderStateGet(rwRENDERSTATEVERTEXALPHAENABLE, &vertexAlphaState); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + ButtonSprite[PS2Symbol].Draw(rect, CRGBA(255, 255, 255, Details.color.a)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)vertexAlphaState); + } +} +#endif + +void +CFont::PrintChar(float x, float y, wchar c) +{ + if(x <= 0.0f || x > SCREEN_WIDTH || +#ifdef FIX_BUGS + y <= 0.0f || y > SCREEN_HEIGHT) +#else + y <= 0.0f || y > SCREEN_WIDTH) +#endif + return; + + float w = GetCharacterWidth(c) / 32.0f; + float xoff = c % 16; + float yoff = c / 16; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) { + w = 21.0f; + xoff = (float)(c % 48); + yoff = c / 48; + } +#endif + + if(Details.style == FONT_BANK || Details.style == FONT_HEADING){ + if(Details.dropShadowPosition != 0){ + CSprite2d::AddSpriteToBank( +#ifdef FIX_BUGS + Details.bank + Details.style, +#else + Details.style, // BUG: game doesn't add bank +#endif +#ifdef FIX_BUGS + CRect(x + SCREEN_SCALE_X(Details.dropShadowPosition), + y + SCREEN_SCALE_Y(Details.dropShadowPosition), + x + SCREEN_SCALE_X(Details.dropShadowPosition) + 32.0f * Details.scaleX * 1.0f, + y + SCREEN_SCALE_Y(Details.dropShadowPosition) + 40.0f * Details.scaleY * 0.5f), +#else + CRect(x + Details.dropShadowPosition, + y + Details.dropShadowPosition, + x + Details.dropShadowPosition + 32.0f * Details.scaleX * 1.0f, + y + Details.dropShadowPosition + 40.0f * Details.scaleY * 0.5f), +#endif + Details.dropColor, + xoff/16.0f, yoff/12.8f, + (xoff+1.0f)/16.0f - 0.001f, yoff/12.8f, + xoff/16.0f, (yoff+1.0f)/12.8f, + (xoff+1.0f)/16.0f - 0.001f, (yoff+1.0f)/12.8f - 0.0001f); + } + CSprite2d::AddSpriteToBank( +#ifdef FIX_BUGS + Details.bank + Details.style, +#else + Details.style, // BUG: game doesn't add bank +#endif + CRect(x, y, + x + 32.0f * Details.scaleX * 1.0f, + y + 40.0f * Details.scaleY * 0.5f), + Details.color, + xoff/16.0f, yoff/12.8f, + (xoff+1.0f)/16.0f - 0.001f, yoff/12.8f, + xoff/16.0f, (yoff+1.0f)/12.8f - 0.002f, + (xoff+1.0f)/16.0f - 0.001f, (yoff+1.0f)/12.8f - 0.002f); +#ifdef MORE_LANGUAGES + }else if (IsJapaneseFont()) { + if (Details.dropShadowPosition != 0) { + CSprite2d::AddSpriteToBank( +#ifdef FIX_BUGS + Details.bank + Details.style, +#else + Details.style, // BUG: game doesn't add bank +#endif +#ifdef FIX_BUGS + CRect(x + SCREEN_SCALE_X(Details.dropShadowPosition), + y + SCREEN_SCALE_Y(Details.dropShadowPosition), + x + SCREEN_SCALE_X(Details.dropShadowPosition) + 32.0f * Details.scaleX * 1.0f, + y + SCREEN_SCALE_Y(Details.dropShadowPosition) + 40.0f * Details.scaleY / 2.75f), +#else + CRect(x + Details.dropShadowPosition, + y + Details.dropShadowPosition, + x + Details.dropShadowPosition + 32.0f * Details.scaleX * 1.0f, + y + Details.dropShadowPosition + 40.0f * Details.scaleY / 2.75f), +#endif + Details.dropColor, + xoff * w / 1024.0f, yoff / 25.6f, + xoff * w / 1024.0f + (1.0f / 48.0f) - 0.001f, yoff / 25.6f, + xoff * w / 1024.0f, (yoff + 1.0f) / 25.6f, + xoff * w / 1024.0f + (1.0f / 48.0f) - 0.001f, (yoff + 1.0f) / 25.6f - 0.0001f); + } + CSprite2d::AddSpriteToBank(Details.bank + Details.style, // BUG: game doesn't add bank + CRect(x, y, + x + 32.0f * Details.scaleX * 1.0f, + y + 40.0f * Details.scaleY / 2.75f), + Details.color, + xoff * w / 1024.0f, yoff / 25.6f, + xoff * w / 1024.0f + (1.0f / 48.0f) - 0.001f, yoff / 25.6f, + xoff * w / 1024.0f, (yoff + 1.0f) / 25.6f - 0.002f, + xoff * w / 1024.0f + (1.0f / 48.0f) - 0.001f, (yoff + 1.0f) / 25.6f - 0.0001f); +#endif + }else + { + CSprite2d::AddSpriteToBank( +#ifdef FIX_BUGS + Details.bank + Details.style, +#else + Details.style, // BUG: game doesn't add bank +#endif + CRect(x, y, + x + 32.0f * Details.scaleX * w, + y + 32.0f * Details.scaleY * 0.5f), + Details.color, + xoff/16.0f, yoff/16.0f, + (xoff+w)/16.0f, yoff/16.0f, + xoff/16.0f, (yoff+1.0f)/16.0f, + (xoff+w)/16.0f - 0.0001f, (yoff+1.0f)/16.0f - 0.0001f); + } +} + +#ifdef MORE_LANGUAGES +bool CFont::IsJapanesePunctuation(wchar *str) +{ + return (*str == 0xE7 || *str == 0x124 || *str == 0x126 || *str == 0x128 || *str == 0x104 || *str == ',' || *str == '>' || *str == '!' || *str == 0x99 || *str == '?' || *str == ':'); +} + +bool CFont::IsAnsiCharacter(wchar *s) +{ + if (*s >= 'A' && *s <= 'Z') + return true; + if (*s >= 'a' && *s <= 'z') + return true; + if (*s >= '0' && *s <= ':') + return true; + if (*s == '(' || *s == ')') + return true; + if (*s == 'D' || *s == '$') + return true; + return false; +} +#endif + +void +CFont::PrintString(float xstart, float ystart, wchar *s) +{ + CRect rect; + int numSpaces; + float lineLength; + float x, y; + bool first; + wchar *start, *t; + + if(*s == '*') + return; + + if(Details.background){ + GetNumberLines(xstart, ystart, s); // BUG: result not used + GetTextRect(&rect, xstart, ystart, s); + CSprite2d::DrawRect(rect, Details.backgroundColor); + } + + lineLength = 0.0f; + numSpaces = 0; + first = true; + if(Details.centre || Details.rightJustify) + x = 0.0f; + else + x = xstart; + y = ystart; + start = s; + + // This is super ugly, I blame R* + for(;;){ + for(;;){ + for(;;){ + if(*s == '\0') + return; + float xend = Details.centre ? Details.centreSize : + Details.rightJustify ? xstart - Details.rightJustifyWrap : + Details.wrapX; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + xend -= SCREEN_SCALE_X(21.0f * 2.0f); +#endif + if(x + GetStringWidth(s) > xend && !first){ +#ifdef MORE_LANGUAGES + if (IsJapanese() && IsJapanesePunctuation(s)) + s--; +#endif + // flush line + float spaceWidth = !Details.justify || Details.centre ? 0.0f : + (Details.wrapX - lineLength) / numSpaces; + float xleft = Details.centre ? xstart - x/2 : + Details.rightJustify ? xstart - x : + xstart; +#ifdef MORE_LANGUAGES + PrintString(xleft, y, start, s, spaceWidth, xstart); +#else + PrintString(xleft, y, start, s, spaceWidth); +#endif + // reset things + lineLength = 0.0f; + numSpaces = 0; + first = true; + if(Details.centre || Details.rightJustify) + x = 0.0f; + else + x = xstart; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + y += 32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY; + else +#endif + y += 32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY; + start = s; + }else + break; + } + // advance by one word + t = GetNextSpace(s); + if(t[0] == '\0' || + t[0] == ' ' && t[1] == '\0') + break; + if(!first) + numSpaces++; + first = false; + x += GetStringWidth(s) + GetCharacterSize(*t - ' '); +#ifdef MORE_LANGUAGES + if (IsJapaneseFont() && IsAnsiCharacter(s)) + x += 21.0f; +#endif + lineLength = x; + s = t+1; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont() && !*s) { + x += GetStringWidth(s); + if (IsAnsiCharacter(s)) + x += 21.0f; + float xleft = Details.centre ? xstart - x / 2 : + Details.rightJustify ? xstart - x : + xstart; + if (PrintString(xleft, y, start, s, 0.0f, xstart)) + { + start = s; + if (!Details.centre && !Details.rightJustify) + x = xstart; + else + x = 0.0f; + + y += 32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY; + numSpaces = 0; + first = true; + lineLength = 0.0f; + } + } +#endif + } + // print rest + if(t[0] == ' ' && t[1] == '\0') + t[0] = '\0'; + x += GetStringWidth(s); + s = t; + float xleft = Details.centre ? xstart - x/2 : + Details.rightJustify ? xstart - x : + xstart; +#ifdef MORE_LANGUAGES + if (PrintString(xleft, y, start, s, 0.0f, xstart) && IsJapaneseFont()) { + start = s; + if (!Details.centre && !Details.rightJustify) + x = xstart; + else + x = 0.0f; + y += 32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY; + numSpaces = 0; + first = true; + lineLength = 0.0f; + } +#else + PrintString(xleft, y, start, s, 0.0f); +#endif + } +} + +int +CFont::GetNumberLines(float xstart, float ystart, wchar *s) +{ + int n; + float x, y; + wchar *t; + n = 0; + +#ifdef MORE_LANGUAGES + bool bSomeJapBool = false; + + if (IsJapanese()) { + t = s; + wchar unused; + while (*t) { + if (*t == JAP_TERMINATION || *t == '~') + t = ParseToken(t, &unused, true); + if (NewLine) { + n++; + NewLine = false; + bSomeJapBool = true; + } + t++; + } + } + + if (bSomeJapBool) n--; +#endif + + if(Details.centre || Details.rightJustify) + x = 0.0f; + else + x = xstart; + y = ystart; + + while(*s){ +#ifdef FIX_BUGS + float f = Details.centre ? Details.centreSize : + Details.rightJustify ? xstart - Details.rightJustifyWrap : + Details.wrapX; +#else + float f = (Details.centre ? Details.centreSize : Details.wrapX); +#endif + +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + f -= SCREEN_SCALE_X(21.0f * 2.0f); +#endif + + if(x + GetStringWidth(s) > f){ +#ifdef MORE_LANGUAGES + if (IsJapanese()) + { + if (IsJapanesePunctuation(s)) + s--; + } +#endif + // reached end of line + if(Details.centre || Details.rightJustify) + x = 0.0f; + else + x = xstart; + n++; + // Why even? +#ifdef MORE_LANGUAGES + if (IsJapanese()) + y += 32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY; + else +#endif + y += 32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY; + }else{ + // still space in current line + t = GetNextSpace(s); + if(*t == '\0'){ + // end of string + x += GetStringWidth(s); +#ifdef MORE_LANGUAGES + if (IsJapanese() && IsAnsiCharacter(s)) + x += 21.0f; +#endif + n++; + s = t; + }else{ + x += GetStringWidth(s); +#ifdef MORE_LANGUAGES + if (IsJapanese() && IsAnsiCharacter(s)) + x += 21.0f; +#endif + s = t+1; + x += GetCharacterSize(*t - ' '); +#ifdef MORE_LANGUAGES + if (IsJapanese() && !*s) + n++; +#endif + } + } + } + + return n; +} + +void +CFont::GetTextRect(CRect *rect, float xstart, float ystart, wchar *s) +{ + int numLines; + float x, y; + int16 maxlength; + wchar *t; + + maxlength = 0; + numLines = 0; + +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + numLines = GetNumberLines(xstart, ystart, s); + }else{ +#endif + +#ifdef FIX_BUGS + if(Details.centre || Details.rightJustify) +#else + if(Details.centre) +#endif + x = 0.0f; + else + x = xstart; + y = ystart; + +#ifdef FIX_BUGS + float xEnd = Details.centre ? Details.centreSize : + Details.rightJustify ? xstart - Details.rightJustifyWrap : + Details.wrapX; +#else + float xEnd = (Details.centre ? Details.centreSize : Details.wrapX); +#endif + while(*s){ + if(x + GetStringWidth(s) > xEnd){ + // reached end of line + if(x > maxlength) + maxlength = x; +#ifdef FIX_BUGS + if(Details.centre || Details.rightJustify) +#else + if(Details.centre) +#endif + x = 0.0f; + else + x = xstart; + numLines++; + y += 32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY; + }else{ + // still space in current line + t = GetNextSpace(s); + if(*t == '\0'){ + // end of string + x += GetStringWidth(s); + if(x > maxlength) + maxlength = x; + numLines++; + s = t; + }else{ + x += GetStringWidth(s); + x += GetCharacterSize(*t - ' '); + s = t+1; + } + } + } +#ifdef MORE_LANGUAGES + } +#endif + + if(Details.centre){ + if(Details.backgroundOnlyText){ + rect->left = xstart - maxlength/2 - 4.0f; + rect->right = xstart + maxlength/2 + 4.0f; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) { + rect->bottom = (32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY) * numLines + ystart + (4.0f / 2.75f); + rect->top = ystart - (4.0f / 2.75f); + } else { +#endif + rect->bottom = (32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY) * numLines + ystart + 2.0f; + rect->top = ystart - 2.0f; +#ifdef MORE_LANGUAGES + } +#endif + }else{ + rect->left = xstart - Details.centreSize*0.5f - 4.0f; + rect->right = xstart + Details.centreSize*0.5f + 4.0f; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) { + rect->bottom = (32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY) * numLines + ystart + (4.0f / 2.75f); + rect->top = ystart - (4.0f / 2.75f); + } else { +#endif + rect->bottom = (32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY) * numLines + ystart + 2.0f; + rect->top = ystart - 2.0f; +#ifdef MORE_LANGUAGES + } +#endif + } + }else{ + rect->left = xstart - 4.0f; + rect->right = Details.wrapX; + // WTF? + rect->bottom = ystart - 4.0f + 4.0f; +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + rect->top = (32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY) * numLines + ystart + 2.0f + (4.0f / 2.75f); + else +#endif + rect->top = (32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY) * numLines + ystart + 2.0f + 2.0f; + } +} + +#ifdef MORE_LANGUAGES +bool +CFont::PrintString(float x, float y, wchar *start, wchar *&end, float spwidth, float japX) +{ + wchar *s, c, unused; + + if (IsJapanese()) { + float jx = 0.0f; + for (s = start; s < end; s++) { + if (*s == JAP_TERMINATION || *s == '~') + s = ParseToken(s, &unused, true); + if (NewLine) { + NewLine = false; + break; + } + jx += GetCharacterSize(*s - ' '); + } + s = start; + if (Details.centre) + x = japX - jx / 2.0f; + else if (Details.rightJustify) + x = japX - jx; + } + + for (s = start; s < end; s++) { + if (*s == '~' || (IsJapanese() && *s == JAP_TERMINATION)) + s = ParseToken(s, &unused); + if (NewLine && IsJapanese()) { + NewLine = false; + end = s; + return true; + } + c = *s - ' '; + if (Details.slant != 0.0f && !IsJapanese()) + y = (Details.slantRefX - x) * Details.slant + Details.slantRefY; + +#ifdef BUTTON_ICONS + if (PS2Symbol != BUTTON_NONE) { + DrawButton(x, y); + x += Details.scaleY * 17.0f; + PS2Symbol = BUTTON_NONE; + } +#endif + + PrintChar(x, y, c); + x += GetCharacterSize(c); + if (c == 0 && (!NewLine || !IsJapanese())) // space + x += spwidth; + } + return false; +} +#else +void +CFont::PrintString(float x, float y, wchar *start, wchar *end, float spwidth) +{ + wchar *s, c, unused; + + for(s = start; s < end; s++){ + if(*s == '~') + s = ParseToken(s, &unused); + c = *s - ' '; + if(Details.slant != 0.0f) + y = (Details.slantRefX - x)*Details.slant + Details.slantRefY; + PrintChar(x, y, c); + x += GetCharacterSize(c); + if(c == 0) // space + x += spwidth; + } +} +#endif + +void +CFont::PrintStringFromBottom(float x, float y, wchar *str) +{ +#ifdef MORE_LANGUAGES + if (IsJapaneseFont()) + y -= (32.0f * CFont::Details.scaleY / 2.75f + 2.0f * CFont::Details.scaleY) * GetNumberLines(x, y, str); + else +#endif + y -= (32.0f * CFont::Details.scaleY * 0.5f + 2.0f * CFont::Details.scaleY) * GetNumberLines(x, y, str); + PrintString(x, y, str); +} + +#ifdef XBOX_SUBTITLES +void +CFont::PrintOutlinedString(float x, float y, wchar *str, float outlineStrength, bool fromBottom, CRGBA outlineColor) +{ + CRGBA textColor = Details.color; + SetColor(outlineColor); + CVector2D offsets[] = { {1.f, 1.f}, {1.f, -1.f}, {-1.f, 1.f}, {-1.f, -1.f} }; + for(int i = 0; i < ARRAY_SIZE(offsets); i++){ + if (fromBottom) + PrintStringFromBottom(x + SCREEN_SCALE_X(offsets[i].x * outlineStrength), y + SCREEN_SCALE_Y(offsets[i].y * outlineStrength), str); + else + PrintString(x + SCREEN_SCALE_X(offsets[i].x * outlineStrength), y + SCREEN_SCALE_Y(offsets[i].y * outlineStrength), str); + } + SetColor(textColor); + + if (fromBottom) + PrintStringFromBottom(x, y, str); + else + PrintString(x, y, str); +} +#endif + +float +CFont::GetCharacterWidth(wchar c) +{ +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + if (!Details.proportional) + return Size[0][Details.style][192]; + if (c <= 94 || Details.style == FONT_HEADING || Details.style == FONT_BANK) { + switch (Details.style) + { + case FONT_JAPANESE: + return Size_jp[c]; + default: + return Size[0][Details.style][c]; + } + } + if (c < 254 && Details.style == FONT_PAGER) + return 29.4f; + + switch (Details.style) + { + case FONT_JAPANESE: + return 29.4f; + case FONT_BANK: + return 10.0f; + case FONT_PAGER: + return 31.5f; + default: + return Size[0][Details.style][c]; + } + } + + else if (Details.proportional) + return Size[LanguageSet][Details.style][c]; + else + return Size[LanguageSet][Details.style][192]; +#else + if (Details.proportional) + return Size[Details.style][c]; + else + return Size[Details.style][192]; +#endif // MORE_LANGUAGES +} + +float +CFont::GetCharacterSize(wchar c) +{ +#ifdef MORE_LANGUAGES + + if (IsJapanese()) + { + if (!Details.proportional) + return Size[0][Details.style][192] * Details.scaleX; + if (c <= 94 || Details.style == FONT_HEADING || Details.style == FONT_BANK) { + switch (Details.style) + { + case FONT_JAPANESE: + return Size_jp[c] * Details.scaleX; + default: + return Size[0][Details.style][c] * Details.scaleX; + } + } + if (c < 254 && (Details.style == FONT_PAGER)) + return 29.4f * Details.scaleX; + + switch (Details.style) + { + case FONT_JAPANESE: + return 29.4f * Details.scaleX; + case FONT_BANK: + return 10.0f * Details.scaleX; + case FONT_PAGER: + return 31.5f * Details.scaleX; + default: + return Size[0][Details.style][c] * Details.scaleX; + } + } + else if(Details.proportional) + return Size[LanguageSet][Details.style][c] * Details.scaleX; + else + return Size[LanguageSet][Details.style][192] * Details.scaleX; +#else + if (Details.proportional) + return Size[Details.style][c] * Details.scaleX; + else + return Size[Details.style][192] * Details.scaleX; +#endif // MORE_LANGUAGES +} + +float +CFont::GetStringWidth(wchar *s, bool spaces) +{ + float w; + + w = 0.0f; +#ifdef MORE_LANGUAGES + if (IsJapanese()) + { + do + { + if ((*s != ' ' || spaces) && *s != '\0') { + do { + while (*s == '~' || *s == JAP_TERMINATION) { + s++; +#ifdef BUTTON_ICONS + switch (*s) { +#if 0 // unused + case 'U': + case 'D': + case '<': + case '>': +#endif + case 'X': + case 'O': + case 'Q': + case 'T': + case 'K': + case 'M': + case 'A': + case 'J': + case 'V': + case 'C': + w += 17.0f * Details.scaleY; + break; + default: + break; + } +#endif + while (!(*s == '~' || *s == JAP_TERMINATION)) s++; + s++; + } + w += GetCharacterSize(*s - ' '); + ++s; + } while (*s == '~' || *s == JAP_TERMINATION); + } + } while (IsAnsiCharacter(s)); + } else +#endif + { + for (; (*s != ' ' || spaces) && *s != '\0'; s++) { + if (*s == '~') { + s++; +#ifdef BUTTON_ICONS + switch (*s) { +#if 0 // unused + case 'U': + case 'D': + case '<': + case '>': +#endif + case 'X': + case 'O': + case 'Q': + case 'T': + case 'K': + case 'M': + case 'A': + case 'J': + case 'V': + case 'C': + w += 17.0f * Details.scaleY; + break; + default: + break; + } +#endif + while (*s != '~') s++; +#ifndef FIX_BUGS + s++; + if (*s == ' ' && !spaces) + break; + } +#else + } else +#endif + w += GetCharacterSize(*s - ' '); + } + } + return w; +} + +#ifdef MORE_LANGUAGES +float +CFont::GetStringWidth_Jap(wchar* s) +{ + float w; + + w = 0.0f; + for (; *s != '\0';) { + do { + while (*s == '~' || *s == JAP_TERMINATION) { + s++; + while (!(*s == '~' || *s == JAP_TERMINATION)) s++; + s++; + } + w += GetCharacterSize(*s - ' '); + ++s; + } while (*s == '~' || *s == JAP_TERMINATION); + } + return w; +} +#endif + +wchar* +CFont::GetNextSpace(wchar *s) +{ +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + do + { + if (*s != ' ' && *s != '\0') { + do { + while (*s == '~' || *s == JAP_TERMINATION) { + s++; + while (!(*s == '~' || *s == JAP_TERMINATION)) s++; + s++; + } + ++s; + } while (*s == '~' || *s == JAP_TERMINATION); + } + } while (IsAnsiCharacter(s)); + } else +#endif + { + for(; *s != ' ' && *s != '\0'; s++) + if(*s == '~'){ + s++; + while(*s != '~') s++; +#ifndef FIX_BUGS + s++; + if(*s == ' ') + break; +#endif + } + } + return s; +} + +#ifdef MORE_LANGUAGES +wchar* +CFont::ParseToken(wchar *s, wchar* ss, bool japShit) +{ + s++; + if ((Details.color.r || Details.color.g || Details.color.b) && !japShit) { + wchar c = *s; + if (IsJapanese()) + c &= 0x7FFF; + switch (c) { + case 'N': + case 'n': + NewLine = true; + break; + case 'b': SetColor(CRGBA(128, 167, 243, 255)); break; + case 'g': SetColor(CRGBA(95, 160, 106, 255)); break; + case 'h': SetColor(CRGBA(225, 225, 225, 255)); break; + case 'l': SetColor(CRGBA(0, 0, 0, 255)); break; + case 'p': SetColor(CRGBA(168, 110, 252, 255)); break; + case 'r': SetColor(CRGBA(113, 43, 73, 255)); break; + case 'w': SetColor(CRGBA(175, 175, 175, 255)); break; + case 'y': SetColor(CRGBA(210, 196, 106, 255)); break; +#ifdef BUTTON_ICONS +#if 0 // unused + case 'U': PS2Symbol = BUTTON_UP; break; + case 'D': PS2Symbol = BUTTON_DOWN; break; + case '<': PS2Symbol = BUTTON_LEFT; break; + case '>': PS2Symbol = BUTTON_RIGHT; break; +#endif + case 'X': PS2Symbol = BUTTON_CROSS; break; + case 'O': PS2Symbol = BUTTON_CIRCLE; break; + case 'Q': PS2Symbol = BUTTON_SQUARE; break; + case 'T': PS2Symbol = BUTTON_TRIANGLE; break; + case 'K': PS2Symbol = BUTTON_L1; break; + case 'M': PS2Symbol = BUTTON_L2; break; + case 'A': PS2Symbol = BUTTON_L3; break; + case 'J': PS2Symbol = BUTTON_R1; break; + case 'V': PS2Symbol = BUTTON_R2; break; + case 'C': PS2Symbol = BUTTON_R3; break; +#endif + } + } else if (IsJapanese()) { + if ((*s & 0x7FFF) == 'N' || (*s & 0x7FFF) == 'n') + NewLine = true; + } + while ((!IsJapanese() || (*s != JAP_TERMINATION)) && *s != '~') s++; +#ifdef FIX_BUGS + if (*(++s) == '~') + s = ParseToken(s, ss, japShit); + return s; +#else + return s + 1; +#endif +} +#else +wchar* +CFont::ParseToken(wchar *s, wchar*) +{ + s++; + if(Details.color.r || Details.color.g || Details.color.b) + switch(*s){ + case 'N': + case 'n': + NewLine = true; + break; + case 'b': SetColor(CRGBA(128, 167, 243, 255)); break; + case 'g': SetColor(CRGBA(95, 160, 106, 255)); break; + case 'h': SetColor(CRGBA(225, 225, 225, 255)); break; + case 'l': SetColor(CRGBA(0, 0, 0, 255)); break; + case 'p': SetColor(CRGBA(168, 110, 252, 255)); break; + case 'r': SetColor(CRGBA(113, 43, 73, 255)); break; + case 'w': SetColor(CRGBA(175, 175, 175, 255)); break; + case 'y': SetColor(CRGBA(210, 196, 106, 255)); break; +#ifdef BUTTON_ICONS +#if 0 // unused + case 'U': PS2Symbol = BUTTON_UP; break; + case 'D': PS2Symbol = BUTTON_DOWN; break; + case '<': PS2Symbol = BUTTON_LEFT; break; + case '>': PS2Symbol = BUTTON_RIGHT; break; +#endif + case 'X': PS2Symbol = BUTTON_CROSS; break; + case 'O': PS2Symbol = BUTTON_CIRCLE; break; + case 'Q': PS2Symbol = BUTTON_SQUARE; break; + case 'T': PS2Symbol = BUTTON_TRIANGLE; break; + case 'K': PS2Symbol = BUTTON_L1; break; + case 'M': PS2Symbol = BUTTON_L2; break; + case 'A': PS2Symbol = BUTTON_L3; break; + case 'J': PS2Symbol = BUTTON_R1; break; + case 'V': PS2Symbol = BUTTON_R2; break; + case 'C': PS2Symbol = BUTTON_R3; break; +#endif + } + while(*s != '~') s++; + return s+1; +} +#endif + +void +CFont::DrawFonts(void) +{ + CSprite2d::DrawBank(Details.bank); + CSprite2d::DrawBank(Details.bank+1); + CSprite2d::DrawBank(Details.bank+2); +#ifdef MORE_LANGUAGES + if (IsJapanese()) + CSprite2d::DrawBank(Details.bank+3); +#endif +} + + +void +CFont::SetScale(float x, float y) +{ +#ifdef MORE_LANGUAGES + /*if (IsJapanese()) { + x *= 1.35f; + y *= 1.25f; + }*/ +#endif + Details.scaleX = x; + Details.scaleY = y; +} + +void +CFont::SetSlantRefPoint(float x, float y) +{ + Details.slantRefX = x; + Details.slantRefY = y; +} + +void +CFont::SetSlant(float s) +{ + Details.slant = s; +} + +void +CFont::SetColor(CRGBA col) +{ + Details.color = col; + if (Details.alphaFade < 255.0f) + Details.color.a *= Details.alphaFade / 255.0f; +} + +void +CFont::SetJustifyOn(void) +{ + Details.justify = true; + Details.centre = false; + Details.rightJustify = false; +} + +void +CFont::SetJustifyOff(void) +{ + Details.justify = false; + Details.rightJustify = false; +} + +void +CFont::SetCentreOn(void) +{ + Details.centre = true; + Details.justify = false; + Details.rightJustify = false; +} + +void +CFont::SetCentreOff(void) +{ + Details.centre = false; +} + +void +CFont::SetWrapx(float x) +{ + Details.wrapX = x; +} + +void +CFont::SetCentreSize(float s) +{ + Details.centreSize = s; +} + +void +CFont::SetBackgroundOn(void) +{ + Details.background = true; +} + +void +CFont::SetBackgroundOff(void) +{ + Details.background = false; +} + +void +CFont::SetBackgroundColor(CRGBA col) +{ + Details.backgroundColor = col; +} + +void +CFont::SetBackGroundOnlyTextOn(void) +{ + Details.backgroundOnlyText = true; +} + +void +CFont::SetBackGroundOnlyTextOff(void) +{ + Details.backgroundOnlyText = false; +} + +void +CFont::SetRightJustifyOn(void) +{ + Details.rightJustify = true; + Details.justify = false; + Details.centre = false; +} + +void +CFont::SetRightJustifyOff(void) +{ + Details.rightJustify = false; + Details.justify = false; + Details.centre = false; +} + +void +CFont::SetPropOn(void) +{ + Details.proportional = true; +} + +void +CFont::SetPropOff(void) +{ + Details.proportional = false; +} + +void +CFont::SetFontStyle(int16 style) +{ + Details.style = style; +} + +void +CFont::SetRightJustifyWrap(float wrap) +{ + Details.rightJustifyWrap = wrap; +} + +void +CFont::SetAlphaFade(float fade) +{ + Details.alphaFade = fade; +} + +void +CFont::SetDropColor(CRGBA col) +{ + Details.dropColor = col; + if (Details.alphaFade < 255.0f) + Details.dropColor.a *= Details.alphaFade / 255.0f; +} + +void +CFont::SetDropShadowPosition(int16 pos) +{ + Details.dropShadowPosition = pos; +} + +wchar +CFont::character_code(uint8 c) +{ + if(c < 128) + return c; + return foreign_table[c-128]; +} \ No newline at end of file diff --git a/src/renderer/Font.h b/src/renderer/Font.h new file mode 100644 index 0000000..9316ed3 --- /dev/null +++ b/src/renderer/Font.h @@ -0,0 +1,182 @@ +#pragma once + +#include "Sprite2d.h" + +void AsciiToUnicode(const char *src, wchar *dst); +void UnicodeStrcpy(wchar *dst, const wchar *src); +void UnicodeStrcat(wchar *dst, wchar *append); +int UnicodeStrlen(const wchar *str); + +struct CFontDetails +{ + CRGBA color; + float scaleX; + float scaleY; + float slant; + float slantRefX; + float slantRefY; + bool8 justify; + bool8 centre; + bool8 rightJustify; + bool8 background; + bool8 backgroundOnlyText; + bool8 proportional; + float alphaFade; + CRGBA backgroundColor; + float wrapX; + float centreSize; + float rightJustifyWrap; + int16 style; + int32 bank; + int16 dropShadowPosition; + CRGBA dropColor; +}; + +class CSprite2d; + +enum { + FONT_BANK, + FONT_PAGER, + FONT_HEADING, +#ifdef MORE_LANGUAGES + FONT_JAPANESE, +#endif + MAX_FONTS +}; + +enum { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT, +}; + +#ifdef MORE_LANGUAGES +enum +{ + FONT_LANGSET_EFIGS, + FONT_LANGSET_RUSSIAN, + FONT_LANGSET_POLISH, + FONT_LANGSET_JAPANESE, + LANGSET_MAX +}; + +#define FONT_LOCALE(style) (CFont::IsJapanese() ? FONT_JAPANESE : style) +#else +#define FONT_LOCALE(style) (style) +#endif + +#ifdef BUTTON_ICONS +enum +{ + BUTTON_NONE = -1, +#if 0 // unused + BUTTON_UP, + BUTTON_DOWN, + BUTTON_LEFT, + BUTTON_RIGHT, +#endif + BUTTON_CROSS, + BUTTON_CIRCLE, + BUTTON_SQUARE, + BUTTON_TRIANGLE, + BUTTON_L1, + BUTTON_L2, + BUTTON_L3, + BUTTON_R1, + BUTTON_R2, + BUTTON_R3, + MAX_BUTTON_ICONS +}; +#endif // BUTTON_ICONS + + +class CFont +{ +#ifdef MORE_LANGUAGES + static int16 Size[LANGSET_MAX][MAX_FONTS][193]; + static uint8 LanguageSet; + static int32 Slot; +#else + static int16 Size[MAX_FONTS][193]; +#endif + static bool16 NewLine; +public: + static CSprite2d Sprite[MAX_FONTS]; + static CFontDetails Details; + +#ifdef BUTTON_ICONS + static int32 ButtonsSlot; + static CSprite2d ButtonSprite[MAX_BUTTON_ICONS]; + static int PS2Symbol; + + static void LoadButtons(const char *txdPath); + static void DrawButton(float x, float y); +#endif // BUTTON_ICONS + + + static void Initialise(void); + static void Shutdown(void); + static void InitPerFrame(void); + static void PrintChar(float x, float y, wchar c); + static void PrintString(float x, float y, wchar *s); + static void PrintStringFromBottom(float x, float y, wchar *str); +#ifdef XBOX_SUBTITLES + static void PrintOutlinedString(float x, float y, wchar *str, float outlineStrength, bool fromBottom, CRGBA outlineColor); +#endif + static int GetNumberLines(float xstart, float ystart, wchar *s); + static void GetTextRect(CRect *rect, float xstart, float ystart, wchar *s); +#ifdef MORE_LANGUAGES + static bool PrintString(float x, float y, wchar *start, wchar* &end, float spwidth, float japX); +#else + static void PrintString(float x, float y, wchar *start, wchar *end, float spwidth); +#endif + static float GetCharacterWidth(wchar c); + static float GetCharacterSize(wchar c); + static float GetStringWidth(wchar *s, bool spaces = false); +#ifdef MORE_LANGUAGES + static float GetStringWidth_Jap(wchar* s); +#endif + static uint16 *GetNextSpace(wchar *s); +#ifdef MORE_LANGUAGES + static uint16 *ParseToken(wchar *s, wchar*, bool japShit = false); +#else + static uint16 *ParseToken(wchar *s, wchar*); +#endif + static void DrawFonts(void); + static uint16 character_code(uint8 c); + + static void SetScale(float x, float y); + static void SetSlantRefPoint(float x, float y); + static void SetSlant(float s); + static void SetJustifyOn(void); + static void SetJustifyOff(void); + static void SetRightJustifyOn(void); + static void SetRightJustifyOff(void); + static void SetCentreOn(void); + static void SetCentreOff(void); + static void SetWrapx(float x); + static void SetCentreSize(float s); + static void SetBackgroundOn(void); + static void SetBackgroundOff(void); + static void SetBackGroundOnlyTextOn(void); + static void SetBackGroundOnlyTextOff(void); + static void SetPropOn(void); + static void SetPropOff(void); + static void SetFontStyle(int16 style); + static void SetRightJustifyWrap(float wrap); + static void SetAlphaFade(float fade); + static void SetDropShadowPosition(int16 pos); + static void SetBackgroundColor(CRGBA col); + static void SetColor(CRGBA col); + static void SetDropColor(CRGBA col); + +#ifdef MORE_LANGUAGES + static void ReloadFonts(uint8 set); + + // japanese stuff + static bool IsAnsiCharacter(wchar* s); + static bool IsJapanesePunctuation(wchar* str); + static bool IsJapanese() { return LanguageSet == FONT_LANGSET_JAPANESE; } + static bool IsJapaneseFont() { return IsJapanese() && (Details.style == FONT_JAPANESE || Details.style == FONT_PAGER); } +#endif +}; diff --git a/src/renderer/Glass.cpp b/src/renderer/Glass.cpp new file mode 100644 index 0000000..cc45648 --- /dev/null +++ b/src/renderer/Glass.cpp @@ -0,0 +1,719 @@ +#include "common.h" + +#include "Glass.h" +#include "Timer.h" +#include "Object.h" +#include "General.h" +#include "AudioScriptObject.h" +#include "World.h" +#include "Timecycle.h" +#include "Particle.h" +#include "Camera.h" +#include "RenderBuffer.h" +#include "Shadows.h" +#include "ModelIndices.h" +#include "main.h" +#include "soundlist.h" + + +uint32 CGlass::NumGlassEntities; +CEntity *CGlass::apEntitiesToBeRendered[NUM_GLASSENTITIES]; +CFallingGlassPane CGlass::aGlassPanes[NUM_GLASSPANES]; + + +CVector2D CentersWithTriangle[NUM_GLASSTRIANGLES]; +const CVector2D CoorsWithTriangle[NUM_GLASSTRIANGLES][3] = +{ + { + CVector2D(0.0f, 0.0f), + CVector2D(0.0f, 1.0f), + CVector2D(0.4f, 0.5f) + }, + + { + CVector2D(0.0f, 1.0f), + CVector2D(1.0f, 1.0f), + CVector2D(0.4f, 0.5f) + }, + + { + CVector2D(0.0f, 0.0f), + CVector2D(0.4f, 0.5f), + CVector2D(0.7f, 0.0f) + }, + + { + CVector2D(0.7f, 0.0f), + CVector2D(0.4f, 0.5f), + CVector2D(1.0f, 1.0f) + }, + + { + CVector2D(0.7f, 0.0f), + CVector2D(1.0f, 1.0f), + CVector2D(1.0f, 0.0f) + } +}; + +#define TEMPBUFFERVERTHILIGHTOFFSET 0 +#define TEMPBUFFERINDEXHILIGHTOFFSET 0 +#define TEMPBUFFERVERTHILIGHTSIZE 128 +#define TEMPBUFFERINDEXHILIGHTSIZE 512 + +#define TEMPBUFFERVERTSHATTEREDOFFSET TEMPBUFFERVERTHILIGHTSIZE +#define TEMPBUFFERINDEXSHATTEREDOFFSET TEMPBUFFERINDEXHILIGHTSIZE +#define TEMPBUFFERVERTSHATTEREDSIZE 192 +#define TEMPBUFFERINDEXSHATTEREDSIZE 768 + +#define TEMPBUFFERVERTREFLECTIONOFFSET TEMPBUFFERVERTSHATTEREDSIZE +#define TEMPBUFFERINDEXREFLECTIONOFFSET TEMPBUFFERINDEXSHATTEREDSIZE +#define TEMPBUFFERVERTREFLECTIONSIZE 256 +#define TEMPBUFFERINDEXREFLECTIONSIZE 1024 + +int32 TempBufferIndicesStoredHiLight = 0; +int32 TempBufferVerticesStoredHiLight = 0; +int32 TempBufferIndicesStoredShattered = 0; +int32 TempBufferVerticesStoredShattered = 0; +int32 TempBufferIndicesStoredReflection = 0; +int32 TempBufferVerticesStoredReflection = 0; + +void +CFallingGlassPane::Update(void) +{ + if ( CTimer::GetTimeInMilliseconds() >= m_nTimer ) + { + // Apply MoveSpeed + GetPosition() += m_vecMoveSpeed * CTimer::GetTimeStep(); + + // Apply Gravity + m_vecMoveSpeed.z -= 0.02f * CTimer::GetTimeStep(); + + // Apply TurnSpeed + GetRight() += CrossProduct(m_vecTurn, GetRight()); + GetForward() += CrossProduct(m_vecTurn, GetForward()); + GetUp() += CrossProduct(m_vecTurn, GetUp()); + + if ( GetPosition().z < m_fGroundZ ) + { + CVector pos; + CVector dir; + + m_bActive = false; + + pos = CVector(GetPosition().x, GetPosition().y, m_fGroundZ); + + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_LIGHT_BREAK, pos); + + RwRGBA color = { 255, 255, 255, 255 }; + + static int32 nFrameGen = 0; + + for ( int32 i = 0; i < 4; i++ ) + { + dir.x = CGeneral::GetRandomNumberInRange(-0.35f, 0.35f); + dir.y = CGeneral::GetRandomNumberInRange(-0.35f, 0.35f); + dir.z = CGeneral::GetRandomNumberInRange(0.05f, 0.20f); + + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, + pos, + dir, + nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.2f), + color, + CGeneral::GetRandomNumberInRange(-40, 40), + 0, + ++nFrameGen & 3, + 500); + } + } + } +} + +void +CFallingGlassPane::Render(void) +{ + float distToCamera = (TheCamera.GetPosition() - GetPosition()).Magnitude(); + + CVector fwdNorm = GetForward(); + fwdNorm.Normalise(); + uint8 alpha = CGlass::CalcAlphaWithNormal(&fwdNorm); + +#ifdef FIX_BUGS + uint16 time = Clamp(CTimer::GetTimeInMilliseconds() > m_nTimer ? CTimer::GetTimeInMilliseconds() - m_nTimer : 0u, 0u, 500u); +#else + uint16 time = Clamp(CTimer::GetTimeInMilliseconds() - m_nTimer, 0, 500); +#endif + + uint8 color = int32( float(alpha) * (float(time) / 500) ); + + if ( TempBufferIndicesStoredHiLight >= TEMPBUFFERINDEXHILIGHTSIZE-7 || TempBufferVerticesStoredHiLight >= TEMPBUFFERVERTHILIGHTSIZE-4 ) + CGlass::RenderHiLightPolys(); + + // HiLight Polys + + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 0], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 1], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 2], color, color, color, color); + + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 0], 0.5f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 0], 0.5f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 1], 0.5f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 1], 0.6f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 2], 0.6f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 2], 0.6f); + + ASSERT(m_nTriIndex < NUM_GLASSTRIANGLES); + + CVector2D p0 = CoorsWithTriangle[m_nTriIndex][0] - CentersWithTriangle[m_nTriIndex]; + CVector2D p1 = CoorsWithTriangle[m_nTriIndex][1] - CentersWithTriangle[m_nTriIndex]; + CVector2D p2 = CoorsWithTriangle[m_nTriIndex][2] - CentersWithTriangle[m_nTriIndex]; + CVector v0 = *this * CVector(p0.x, 0.0f, p0.y); + CVector v1 = *this * CVector(p1.x, 0.0f, p1.y); + CVector v2 = *this * CVector(p2.x, 0.0f, p2.y); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 0], v0.x, v0.y, v0.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 1], v1.x, v1.y, v1.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredHiLight + 2], v2.x, v2.y, v2.z); + + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 0] = TempBufferVerticesStoredHiLight + 0; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 1] = TempBufferVerticesStoredHiLight + 1; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 2] = TempBufferVerticesStoredHiLight + 2; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 3] = TempBufferVerticesStoredHiLight + 0; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 4] = TempBufferVerticesStoredHiLight + 2; + TempBufferRenderIndexList[TempBufferIndicesStoredHiLight + 5] = TempBufferVerticesStoredHiLight + 1; + + TempBufferVerticesStoredHiLight += 3; + TempBufferIndicesStoredHiLight += 6; + + if ( m_bShattered ) + { + if ( TempBufferIndicesStoredShattered >= TEMPBUFFERINDEXSHATTEREDSIZE-7 || TempBufferVerticesStoredShattered >= TEMPBUFFERVERTSHATTEREDSIZE-4 ) + CGlass::RenderShatteredPolys(); + + uint8 shatteredColor = 255; + if ( distToCamera > 30.0f ) + shatteredColor = int32((1.0f - (distToCamera - 30.0f) * 4.0f / 40.0f) * 255); + + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], shatteredColor, shatteredColor, shatteredColor, shatteredColor); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], shatteredColor, shatteredColor, shatteredColor, shatteredColor); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], shatteredColor, shatteredColor, shatteredColor, shatteredColor); + + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], 4.0f * CoorsWithTriangle[m_nTriIndex][0].x * m_fStep); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], 4.0f * CoorsWithTriangle[m_nTriIndex][0].y * m_fStep); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], 4.0f * CoorsWithTriangle[m_nTriIndex][1].x * m_fStep); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], 4.0f * CoorsWithTriangle[m_nTriIndex][1].y * m_fStep); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], 4.0f * CoorsWithTriangle[m_nTriIndex][2].x * m_fStep); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], 4.0f * CoorsWithTriangle[m_nTriIndex][2].y * m_fStep); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], v0.x, v0.y, v0.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], v1.x, v1.y, v1.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], v2.x, v2.y, v2.z); + + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 0] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 0; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 1] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 1; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 2] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 2; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 3] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 0; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 4] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 2; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 5] = TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET + 1; + + TempBufferIndicesStoredShattered += 6; + TempBufferVerticesStoredShattered += 3; + } +} + +void +CGlass::Init(void) +{ + for ( int32 i = 0; i < NUM_GLASSPANES; i++ ) + aGlassPanes[i].m_bActive = false; + + for ( int32 i = 0; i < NUM_GLASSTRIANGLES; i++ ) + CentersWithTriangle[i] = (CoorsWithTriangle[i][0] + CoorsWithTriangle[i][1] + CoorsWithTriangle[i][2]) / 3; +} + +void +CGlass::Update(void) +{ + for ( int32 i = 0; i < NUM_GLASSPANES; i++ ) + { + if ( aGlassPanes[i].m_bActive ) + aGlassPanes[i].Update(); + } +} + +void +CGlass::Render(void) +{ + TempBufferVerticesStoredHiLight = 0; + TempBufferIndicesStoredHiLight = 0; + + TempBufferVerticesStoredShattered = TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferIndicesStoredShattered = TEMPBUFFERINDEXSHATTEREDOFFSET; + + TempBufferVerticesStoredReflection = TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferIndicesStoredReflection = TEMPBUFFERINDEXREFLECTIONOFFSET; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGCOLOR, (void *)RWRGBALONG(CTimeCycle::GetFogRed(), CTimeCycle::GetFogGreen(), CTimeCycle::GetFogBlue(), 255)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + + PUSH_RENDERGROUP("CGlass::Render"); + + for ( int32 i = 0; i < NUM_GLASSPANES; i++ ) + { + if ( aGlassPanes[i].m_bActive ) + aGlassPanes[i].Render(); + } + + for ( uint32 i = 0; i < NumGlassEntities; i++ ) + RenderEntityInGlass(apEntitiesToBeRendered[i]); + + POP_RENDERGROUP(); + + NumGlassEntities = 0; + + RenderHiLightPolys(); + RenderShatteredPolys(); + RenderReflectionPolys(); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); +} + +CFallingGlassPane * +CGlass::FindFreePane(void) +{ + for ( int32 i = 0; i < NUM_GLASSPANES; i++ ) + { + if ( !aGlassPanes[i].m_bActive ) + return &aGlassPanes[i]; + } + + return nil; +} + +void +CGlass::GeneratePanesForWindow(uint32 type, CVector pos, CVector up, CVector right, CVector speed, CVector point, + float moveSpeed, bool cracked, bool explosion) +{ + float upLen = up.Magnitude(); + float rightLen = right.Magnitude(); + + float upSteps = upLen + 0.75f; + if ( upSteps < 1.0f ) upSteps = 1.0f; + + float rightSteps = rightLen + 0.75f; + if ( rightSteps < 1.0f ) rightSteps = 1.0f; + + uint32 ysteps = (uint32)upSteps; + if ( ysteps > 3 ) ysteps = 3; + + uint32 xsteps = (uint32)rightSteps; + if ( xsteps > 3 ) xsteps = 3; + + if ( explosion ) + { + if ( ysteps > 1 ) ysteps = 1; + if ( xsteps > 1 ) xsteps = 1; + } + + float upScl = upLen / float(ysteps); + float rightScl = rightLen / float(xsteps); + + bool bZFound; + float groundZ = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &bZFound); + if ( !bZFound ) groundZ = pos.z - 2.0f; + + for ( uint32 y = 0; y < ysteps; y++ ) + { + for ( uint32 x = 0; x < xsteps; x++ ) + { + float stepy = float(y) * upLen / float(ysteps); + float stepx = float(x) * rightLen / float(xsteps); + + for ( int32 i = 0; i < NUM_GLASSTRIANGLES; i++ ) + { + CFallingGlassPane *pane = FindFreePane(); + if ( pane ) + { + pane->m_nTriIndex = i; + + pane->GetRight() = (right * rightScl) / rightLen; +#ifdef FIX_BUGS + pane->GetUp() = (up * upScl) / upLen; +#else + pane->GetUp() = (up * upScl) / rightLen; // copypaste bug +#endif + CVector fwd = CrossProduct(pane->GetRight(), pane->GetUp()); + fwd.Normalise(); + + pane->GetForward() = fwd; + + pane->GetPosition() = right / rightLen * (rightScl * CentersWithTriangle[i].x + stepx) + + up / upLen * (upScl * CentersWithTriangle[i].y + stepy) + + pos; + + pane->m_vecMoveSpeed.x = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0015f + speed.x; + pane->m_vecMoveSpeed.y = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0015f + speed.y; + pane->m_vecMoveSpeed.z = 0.0f + speed.z; + + if ( moveSpeed != 0.0f ) + { + CVector dist = pane->GetPosition() - point; + dist.Normalise(); + + pane->m_vecMoveSpeed += moveSpeed * dist; + } + + pane->m_vecTurn.x = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.002f; + pane->m_vecTurn.y = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.002f; + pane->m_vecTurn.z = float((CGeneral::GetRandomNumber() & 127) - 64) * 0.002f; + + switch ( type ) + { + case 0: + pane->m_nTimer = CTimer::GetTimeInMilliseconds(); + break; + case 1: + float dist = (pane->GetPosition() - point).Magnitude(); + pane->m_nTimer = uint32(dist*100 + CTimer::GetTimeInMilliseconds()); + break; + } + + pane->m_fGroundZ = groundZ; + pane->m_bShattered = cracked; + pane->m_fStep = upLen / float(ysteps); + pane->m_bActive = true; + } + } + } + } +} + +void +CGlass::AskForObjectToBeRenderedInGlass(CEntity *entity) +{ +#ifdef FIX_BUGS + if ( NumGlassEntities < NUM_GLASSENTITIES ) +#else + if ( NumGlassEntities < NUM_GLASSENTITIES-1 ) +#endif + { + apEntitiesToBeRendered[NumGlassEntities++] = entity; + } +} + +void +CGlass::RenderEntityInGlass(CEntity *entity) +{ + ASSERT(entity!=nil); + CObject *object = (CObject *)entity; + + if ( object->bGlassBroken ) + return; + + float distToCamera = (TheCamera.GetPosition() - object->GetPosition()).Magnitude(); + + if ( distToCamera > 40.0f ) + return; + + CVector fwdNorm = object->GetForward(); + fwdNorm.Normalise(); + uint8 alpha = CalcAlphaWithNormal(&fwdNorm); + + CColModel *col = object->GetColModel(); + ASSERT(col!=nil); + if ( col->numTriangles >= 2 ) + { + CVector a = object->GetMatrix() * col->vertices[0].Get(); + CVector b = object->GetMatrix() * col->vertices[1].Get(); + CVector c = object->GetMatrix() * col->vertices[2].Get(); + CVector d = object->GetMatrix() * col->vertices[3].Get(); + + if ( object->bGlassCracked ) + { + uint8 color = 255; + if ( distToCamera > 30.0f ) + color = int32((1.0f - (distToCamera - 30.0f) * 4.0f / 40.0f) * 255); + + if ( TempBufferIndicesStoredShattered >= TEMPBUFFERINDEXSHATTEREDSIZE-13 || TempBufferVerticesStoredShattered >= TEMPBUFFERVERTSHATTEREDSIZE-5 ) + RenderShatteredPolys(); + + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 3], color, color, color, color); + + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], 0.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], 0.0f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], 16.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], 0.0f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], 0.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], 16.0f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 3], 16.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 3], 16.0f); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 0], a.x, a.y, a.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 1], b.x, b.y, b.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 2], c.x, c.y, c.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredShattered + 3], d.x, d.y, d.z); + + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 0] = col->triangles[0].a + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 1] = col->triangles[0].b + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 2] = col->triangles[0].c + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 3] = col->triangles[1].a + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 4] = col->triangles[1].b + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 5] = col->triangles[1].c + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 6] = col->triangles[0].a + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 7] = col->triangles[0].c + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 8] = col->triangles[0].b + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 9] = col->triangles[1].a + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 10] = col->triangles[1].c + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredShattered + 11] = col->triangles[1].b + TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET; + + TempBufferIndicesStoredShattered += 12; + TempBufferVerticesStoredShattered += 4; + } + + if ( TempBufferIndicesStoredReflection >= TEMPBUFFERINDEXREFLECTIONSIZE-13 || TempBufferVerticesStoredReflection >= TEMPBUFFERVERTREFLECTIONSIZE-5 ) + RenderReflectionPolys(); + + uint8 color = 100; + if ( distToCamera > 30.0f ) + color = int32((1.0f - (distToCamera - 30.0f) * 4.0f / 40.0f) * 100); + + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 0], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 1], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 2], color, color, color, color); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 3], color, color, color, color); + + float FwdAngle = CGeneral::GetATanOfXY(TheCamera.GetForward().x, TheCamera.GetForward().y); + float v = 2.0f * TheCamera.GetForward().z * 0.2f; + float u = float(object->m_randomSeed & 15) * 0.02f + (FwdAngle / TWOPI); + + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 0], u); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 0], v); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 1], u+0.2f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 1], v); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 2], u); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 2], v+0.2f); + RwIm3DVertexSetU (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 3], u+0.2f); + RwIm3DVertexSetV (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 3], v+0.2f); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 0], a.x, a.y, a.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 1], b.x, b.y, b.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 2], c.x, c.y, c.z); + RwIm3DVertexSetPos (&TempBufferRenderVertices[TempBufferVerticesStoredReflection + 3], d.x, d.y, d.z); + + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 0] = col->triangles[0].a + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 1] = col->triangles[0].b + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 2] = col->triangles[0].c + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 3] = col->triangles[1].a + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 4] = col->triangles[1].b + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 5] = col->triangles[1].c + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 6] = col->triangles[0].a + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 7] = col->triangles[0].c + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 8] = col->triangles[0].b + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 9] = col->triangles[1].a + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 10] = col->triangles[1].c + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + TempBufferRenderIndexList[TempBufferIndicesStoredReflection + 11] = col->triangles[1].b + TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET; + + TempBufferIndicesStoredReflection += 12; + TempBufferVerticesStoredReflection += 4; + } +} + +int32 +CGlass::CalcAlphaWithNormal(CVector *normal) +{ + ASSERT(normal!=nil); + + float fwdDir = 2.0f * DotProduct(*normal, TheCamera.GetForward()); + float fwdDot = DotProduct(TheCamera.GetForward()-fwdDir*(*normal), CVector(0.57f, 0.57f, -0.57f)); + return int32(lerp(fwdDot*fwdDot*fwdDot*fwdDot*fwdDot*fwdDot, 20.0f, 255.0f)); +} + +void +CGlass::RenderHiLightPolys(void) +{ + if ( TempBufferVerticesStoredHiLight != TEMPBUFFERVERTHILIGHTOFFSET ) + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)RwTextureGetRaster(gpShadowExplosionTex)); + + LittleTest(); + + if ( RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStoredHiLight, nil, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStoredHiLight); + RwIm3DEnd(); + } + + TempBufferVerticesStoredHiLight = TEMPBUFFERVERTHILIGHTOFFSET; + TempBufferIndicesStoredHiLight = TEMPBUFFERINDEXHILIGHTOFFSET; + } +} + +void +CGlass::RenderShatteredPolys(void) +{ + if ( TempBufferVerticesStoredShattered != TEMPBUFFERVERTSHATTEREDOFFSET ) + { + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)RwTextureGetRaster(gpCrackedGlassTex)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + LittleTest(); + + if ( RwIm3DTransform(&TempBufferRenderVertices[TEMPBUFFERVERTSHATTEREDOFFSET], TempBufferVerticesStoredShattered - TEMPBUFFERVERTSHATTEREDOFFSET, nil, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, &TempBufferRenderIndexList[TEMPBUFFERINDEXSHATTEREDOFFSET], TempBufferIndicesStoredShattered - TEMPBUFFERINDEXSHATTEREDOFFSET); + RwIm3DEnd(); + } + + TempBufferIndicesStoredShattered = TEMPBUFFERINDEXSHATTEREDOFFSET; + TempBufferVerticesStoredShattered = TEMPBUFFERVERTSHATTEREDOFFSET; + } +} + +void +CGlass::RenderReflectionPolys(void) +{ + if ( TempBufferVerticesStoredReflection != TEMPBUFFERVERTREFLECTIONOFFSET ) + { + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)RwTextureGetRaster(gpShadowHeadLightsTex)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + LittleTest(); + + if ( RwIm3DTransform(&TempBufferRenderVertices[TEMPBUFFERVERTREFLECTIONOFFSET], TempBufferVerticesStoredReflection - TEMPBUFFERVERTREFLECTIONOFFSET, nil, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, &TempBufferRenderIndexList[TEMPBUFFERINDEXREFLECTIONOFFSET], TempBufferIndicesStoredReflection - TEMPBUFFERINDEXREFLECTIONOFFSET); + RwIm3DEnd(); + } + + TempBufferIndicesStoredReflection = TEMPBUFFERINDEXREFLECTIONOFFSET; + TempBufferVerticesStoredReflection = TEMPBUFFERVERTREFLECTIONOFFSET; + } +} + +void +CGlass::WindowRespondsToCollision(CEntity *entity, float amount, CVector speed, CVector point, bool explosion) +{ + ASSERT(entity!=nil); + + CObject *object = (CObject *)entity; + + if ( object->bGlassBroken ) + return; + + object->bGlassCracked = true; + + CColModel *col = object->GetColModel(); + ASSERT(col!=nil); + + CVector a = object->GetMatrix() * col->vertices[0].Get(); + CVector b = object->GetMatrix() * col->vertices[1].Get(); + CVector c = object->GetMatrix() * col->vertices[2].Get(); + CVector d = object->GetMatrix() * col->vertices[3].Get(); + + float minx = Min(Min(a.x, b.x), Min(c.x, d.x)); + float maxx = Max(Max(a.x, b.x), Max(c.x, d.x)); + float miny = Min(Min(a.y, b.y), Min(c.y, d.y)); + float maxy = Max(Max(a.y, b.y), Max(c.y, d.y)); + float minz = Min(Min(a.z, b.z), Min(c.z, d.z)); + float maxz = Max(Max(a.z, b.z), Max(c.z, d.z)); + + + if ( amount > 300.0f ) + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_BREAK_L, object->GetPosition()); + + GeneratePanesForWindow(0, + CVector(minx, miny, minz), + CVector(0.0f, 0.0f, maxz-minz), + CVector(maxx-minx, maxy-miny, 0.0f), + speed, point, 0.1f, !!object->bGlassCracked, explosion); + } + else + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_BREAK_S, object->GetPosition()); + + GeneratePanesForWindow(1, + CVector(minx, miny, minz), + CVector(0.0f, 0.0f, maxz-minz), + CVector(maxx-minx, maxy-miny, 0.0f), + speed, point, 0.1f, !!object->bGlassCracked, explosion); + } + + object->bGlassBroken = true; + object->GetMatrix().GetPosition().z = -100.0f; +} + +void +CGlass::WindowRespondsToSoftCollision(CEntity *entity, float amount) +{ + ASSERT(entity!=nil); + + CObject *object = (CObject *)entity; + + if ( amount > 50.0f && !object->bGlassCracked ) + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_CRACK, object->GetPosition()); + object->bGlassCracked = true; + } +} + +void +CGlass::WasGlassHitByBullet(CEntity *entity, CVector point) +{ + ASSERT(entity!=nil); + + CObject *object = (CObject *)entity; + + if ( IsGlass(object->GetModelIndex()) ) + { + if ( !object->bGlassCracked ) + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_CRACK, object->GetPosition()); + object->bGlassCracked = true; + } + else + { + if ( (CGeneral::GetRandomNumber() & 3) == 2 ) + WindowRespondsToCollision(object, 0.0f, CVector(0.0f, 0.0f, 0.0f), point, false); + } + } +} + +void +CGlass::WindowRespondsToExplosion(CEntity *entity, CVector point) +{ + ASSERT(entity!=nil); + + CObject *object = (CObject *)entity; + + CVector distToGlass = object->GetPosition() - point; + + float fDistToGlass = distToGlass.Magnitude(); + + if ( fDistToGlass < 10.0f ) + { + distToGlass *= (0.3f / fDistToGlass); // normalise + WindowRespondsToCollision(object, 10000.0f, distToGlass, object->GetPosition(), true); + } + else + { + if ( fDistToGlass < 30.0f ) + object->bGlassCracked = true; + } +} diff --git a/src/renderer/Glass.h b/src/renderer/Glass.h new file mode 100644 index 0000000..51c5aae --- /dev/null +++ b/src/renderer/Glass.h @@ -0,0 +1,52 @@ +#pragma once + +class CEntity; + +class CFallingGlassPane : public CMatrix +{ +public: + CVector m_vecMoveSpeed; + CVector m_vecTurn; + uint32 m_nTimer; + float m_fGroundZ; + float m_fStep; + uint8 m_nTriIndex; + bool m_bActive; + bool m_bShattered; + + CFallingGlassPane() { } + ~CFallingGlassPane() { } + + void Update(void); + void Render(void); +}; + +VALIDATE_SIZE(CFallingGlassPane, 0x70); + +enum +{ + NUM_GLASSTRIANGLES = 5, +}; + +class CGlass +{ + static uint32 NumGlassEntities; + static CEntity *apEntitiesToBeRendered[NUM_GLASSENTITIES]; + static CFallingGlassPane aGlassPanes[NUM_GLASSPANES]; +public: + static void Init(void); + static void Update(void); + static void Render(void); + static CFallingGlassPane *FindFreePane(void); + static void GeneratePanesForWindow(uint32 type, CVector pos, CVector up, CVector right, CVector speed, CVector point, float moveSpeed, bool cracked, bool explosion); + static void AskForObjectToBeRenderedInGlass(CEntity *entity); + static void RenderEntityInGlass(CEntity *entity); + static int32 CalcAlphaWithNormal(CVector *normal); + static void RenderHiLightPolys(void); + static void RenderShatteredPolys(void); + static void RenderReflectionPolys(void); + static void WindowRespondsToCollision(CEntity *entity, float amount, CVector speed, CVector point, bool explosion); + static void WindowRespondsToSoftCollision(CEntity *entity, float amount); + static void WasGlassHitByBullet(CEntity *entity, CVector point); + static void WindowRespondsToExplosion(CEntity *entity, CVector point); +}; \ No newline at end of file diff --git a/src/renderer/Hud.cpp b/src/renderer/Hud.cpp new file mode 100644 index 0000000..bba8c52 --- /dev/null +++ b/src/renderer/Hud.cpp @@ -0,0 +1,1713 @@ +#include "common.h" + +#include "Camera.h" +#include "DMAudio.h" +#include "Clock.h" +#include "Darkel.h" +#include "Hud.h" +#include "Messages.h" +#include "Frontend.h" +#include "Font.h" +#include "Pad.h" +#include "Radar.h" +#include "Replay.h" +#include "Wanted.h" +#include "Sprite.h" +#include "Sprite2d.h" +#include "Text.h" +#include "Timer.h" +#include "Script.h" +#include "TxdStore.h" +#include "User.h" +#include "World.h" + +#ifdef PS2_HUD +#define MONEY_X 100.0f +#define WEAPON_X 91.0f +#define AMMO_X 59.0f +#define HEALTH_X 100.0f +#define STARS_X 49.0f +#define ZONE_Y 61.0f +#define VEHICLE_Y 81.0f +#define CLOCK_X 101.0f +#define SUBS_Y 83.0f +#define WASTEDBUSTED_Y 122.0f +#define BIGMESSAGE_Y 80.0f +#else +#define MONEY_X 110.0f +#define WEAPON_X 99.0f +#define AMMO_X 66.0f +#define HEALTH_X 110.0f +#define STARS_X 60.0f +#define ZONE_Y 30.0f +#define VEHICLE_Y 55.0f +#define CLOCK_X 111.0f +#define SUBS_Y 68.0f +#define WASTEDBUSTED_Y 82.0f +#define BIGMESSAGE_Y 84.0f +#endif + +#ifdef FIX_BUGS +#define TIMER_RIGHT_OFFSET 34.0f // Taken from VC frenzy timer +#define BIGMESSAGE_Y_OFFSET 18.0f +#else +#define TIMER_RIGHT_OFFSET 27.0f +#define BIGMESSAGE_Y_OFFSET 20.0f +#endif + +#if defined(PS2_HUD) && !defined(FIX_BUGS) + #define SCREEN_SCALE_X_PC(a) (a) + #define SCREEN_SCALE_Y_PC(a) (a) + #define SCALE_AND_CENTER_X_PC(a) (a) +#else + #define SCREEN_SCALE_X_PC(a) SCREEN_SCALE_X(a) + #define SCREEN_SCALE_Y_PC(a) SCREEN_SCALE_Y(a) + #define SCALE_AND_CENTER_X_PC(a) SCALE_AND_CENTER_X(a) +#endif + +#if defined(FIX_BUGS) + #define SCREEN_SCALE_X_FIX(a) SCREEN_SCALE_X(a) + #define SCREEN_SCALE_Y_FIX(a) SCREEN_SCALE_Y(a) + #define SCALE_AND_CENTER_X_FIX(a) SCALE_AND_CENTER_X(a) +#else + #define SCREEN_SCALE_X_FIX(a) (a) + #define SCREEN_SCALE_Y_FIX(a) (a) + #define SCALE_AND_CENTER_X_FIX(a) (a) +#endif + +#ifdef FIX_BUGS +#define FRAMECOUNTER CTimer::GetLogicalFrameCounter() +#else +#define FRAMECOUNTER CTimer::GetFrameCounter() +#endif + +// Game has colors inlined in code. +// For easier modification we collect them here: +CRGBA MONEY_COLOR(89, 115, 150, 255); +CRGBA AMMO_COLOR(0, 0, 0, 255); +CRGBA HEALTH_COLOR(186, 101, 50, 255); +CRGBA ARMOUR_COLOR(124, 140, 95, 255); +CRGBA WANTED_COLOR(193, 164, 120, 255); +CRGBA ZONE_COLOR(152, 154, 82, 255); +CRGBA VEHICLE_COLOR(194, 165, 120, 255); +CRGBA CLOCK_COLOR(194, 165, 120, 255); +CRGBA TIMER_COLOR(186, 101, 50, 255); +CRGBA COUNTER_COLOR(0, 106, 164, 255); +CRGBA PAGER_COLOR(32, 162, 66, 205); +CRGBA RADARDISC_COLOR(0, 0, 0, 255); +CRGBA BIGMESSAGE_COLOR(85, 119, 133, 255); +CRGBA WASTEDBUSTED_COLOR(170, 123, 87, 255); +CRGBA ODDJOB_COLOR(89, 115, 150, 255); +CRGBA ODDJOB2_COLOR(156, 91, 40, 255); +CRGBA MISSIONTITLE_COLOR(220, 172, 2, 255); + + +int16 CHud::m_ItemToFlash; +CSprite2d CHud::Sprites[NUM_HUD_SPRITES]; +wchar *CHud::m_pZoneName; +wchar *CHud::m_pLastZoneName; +wchar *CHud::m_ZoneToPrint; +wchar CHud::m_Message[256]; +wchar CHud::m_BigMessage[6][128]; +wchar LastBigMessage[6][128]; +wchar CHud::m_PagerMessage[256]; +uint32 CHud::m_ZoneNameTimer; +int32 CHud::m_ZoneFadeTimer; +uint32 CHud::m_ZoneState; +wchar CHud::m_HelpMessage[HELP_MSG_LENGTH]; +wchar CHud::m_LastHelpMessage[HELP_MSG_LENGTH]; +wchar CHud::m_HelpMessageToPrint[HELP_MSG_LENGTH]; +uint32 CHud::m_HelpMessageTimer; +int32 CHud::m_HelpMessageFadeTimer; +uint32 CHud::m_HelpMessageState; +bool CHud::m_HelpMessageQuick; +float CHud::m_HelpMessageDisplayTime; +int32 CHud::SpriteBrightness; +bool CHud::m_Wants_To_Draw_Hud; +bool CHud::m_Wants_To_Draw_3dMarkers; +wchar *CHud::m_pVehicleName; +wchar *CHud::m_pLastVehicleName; +uint32 CHud::m_VehicleNameTimer; +int32 CHud::m_VehicleFadeTimer; +uint32 CHud::m_VehicleState; +wchar *CHud::m_pVehicleNameToPrint; + +// These aren't really in CHud +float BigMessageInUse[6]; +float BigMessageX[6]; +float BigMessageAlpha[6]; +int16 PagerOn; +int16 PagerTimer; +float PagerXOffset; +int16 PagerSoundPlayed; +int16 OddJob2On; +uint16 OddJob2Timer; +float OddJob2XOffset; +float OddJob2OffTimer; +bool CounterOnLastFrame; +uint16 CounterFlashTimer; +bool TimerOnLastFrame; +uint16 TimerFlashTimer; + +RwTexture *gpSniperSightTex; +RwTexture *gpRocketSightTex; + +struct +{ + const char *name; + const char *mask; +} WeaponFilenames[] = { + {"fist", "fistm"}, + {"bat", "batm"}, + {"pistol", "pistolm" }, + {"uzi", "uzim"}, + {"shotgun", "shotgunm"}, + {"ak47", "ak47m"}, + {"m16", "m16m"}, + {"sniper", "sniperm"}, + {"rocket", "rocketm"}, + {"flame", "flamem"}, + {"molotov", "molotovm"}, + {"grenade", "grenadem"}, + {"detonator", "detonator_mask"}, + {"", ""}, + {"", ""}, + {"radardisc", "radardisc"}, + {"pager", "pagerm"}, + {"", ""}, + {"", ""}, + {"bleeder", ""}, + {"sitesniper", "sitesniperm"}, + {"siteM16", "siteM16m"}, + {"siterocket", "siterocket"} +}; + +void CHud::Initialise() +{ + m_Wants_To_Draw_Hud = true; + m_Wants_To_Draw_3dMarkers = true; + + int HudTXD = CTxdStore::AddTxdSlot("hud"); + CTxdStore::LoadTxd(HudTXD, "MODELS/HUD.TXD"); + CTxdStore::AddRef(HudTXD); + CTxdStore::PopCurrentTxd(); + CTxdStore::SetCurrentTxd(HudTXD); + + for (int i = 0; i < NUM_HUD_SPRITES; i++) { + Sprites[i].SetTexture(WeaponFilenames[i].name, WeaponFilenames[i].mask); + } + + GetRidOfAllHudMessages(); + + if (gpSniperSightTex == nil) + gpSniperSightTex = RwTextureRead("sitesniper", nil); + if (gpRocketSightTex == nil) + gpRocketSightTex = RwTextureRead("siterocket", nil); + + CounterOnLastFrame = false; + m_ItemToFlash = ITEM_NONE; + OddJob2Timer = 0; + OddJob2OffTimer = 0.0f; + OddJob2On = 0; + OddJob2XOffset = 0.0f; + CounterFlashTimer = 0; + TimerOnLastFrame = false; + TimerFlashTimer = 0; + SpriteBrightness = 0; + PagerOn = 0; + PagerTimer = 0; + PagerSoundPlayed = 0; + PagerXOffset = 150.0f; + + CTxdStore::PopCurrentTxd(); +} + +void CHud::Shutdown() +{ + for (int i = 0; i < NUM_HUD_SPRITES; ++i) { + Sprites[i].Delete(); + } + + RwTextureDestroy(gpSniperSightTex); + gpSniperSightTex = nil; + + RwTextureDestroy(gpRocketSightTex); + gpRocketSightTex = nil; + + int HudTXD = CTxdStore::FindTxdSlot("hud"); + CTxdStore::RemoveTxdSlot(HudTXD); +} + +void CHud::ReInitialise() { + m_Wants_To_Draw_Hud = true; + m_Wants_To_Draw_3dMarkers = true; + + GetRidOfAllHudMessages(); + + CounterOnLastFrame = false; + m_ItemToFlash = ITEM_NONE; + OddJob2Timer = 0; + OddJob2OffTimer = 0.0f; + OddJob2On = 0; + OddJob2XOffset = 0.0f; + CounterFlashTimer = 0; + TimerOnLastFrame = false; + TimerFlashTimer = 0; + SpriteBrightness = 0; + PagerOn = 0; + PagerTimer = 0; + PagerSoundPlayed = 0; + PagerXOffset = 150.0f; +} + +void CHud::GetRidOfAllHudMessages() +{ + m_ZoneState = 0; + m_pLastZoneName = nil; + m_ZoneNameTimer = 0; + m_pZoneName = nil; + + for (int i = 0; i < HELP_MSG_LENGTH; i++) { + m_HelpMessage[i] = 0; + m_LastHelpMessage[i] = 0; + m_HelpMessageToPrint[i] = 0; + } + + m_HelpMessageTimer = 0; + m_HelpMessageFadeTimer = 0; + m_HelpMessageState = 0; + m_HelpMessageQuick = 0; + m_HelpMessageDisplayTime = 1.0f; + m_pVehicleName = nil; + m_pLastVehicleName = nil; + m_pVehicleNameToPrint = nil; + m_VehicleNameTimer = 0; + m_VehicleFadeTimer = 0; + m_VehicleState = 0; + + for (int i = 0; i < ARRAY_SIZE(m_Message); i++) + m_Message[i] = 0; + + for (int i = 0; i < 6; i++) { + BigMessageInUse[i] = 0.0f; + + for (int j = 0; j < 128; j++) + m_BigMessage[i][j] = 0; + } +} + +void CHud::SetZoneName(wchar *name) +{ + m_pZoneName = name; +} + +void CHud::SetHelpMessage(wchar *message, bool quick) +{ + if (!CReplay::IsPlayingBack()) { + CMessages::WideStringCopy(m_HelpMessage, message, HELP_MSG_LENGTH); + CMessages::InsertPlayerControlKeysInString(m_HelpMessage); + + for (int i = 0; i < HELP_MSG_LENGTH; i++) { + m_LastHelpMessage[i] = 0; + } + + m_HelpMessageState = 0; + m_HelpMessageQuick = quick; + } +} + +void CHud::SetVehicleName(wchar *name) +{ + m_pVehicleName = name; +} + +void CHud::Draw() +{ + // disable hud via second controller + if (CPad::GetPad(1)->GetStartJustDown()) + m_Wants_To_Draw_Hud = !m_Wants_To_Draw_Hud; + +#ifdef GTA_PC + if (CReplay::IsPlayingBack()) + return; +#endif + + if (m_Wants_To_Draw_Hud && !TheCamera.m_WideScreenOn) { + bool DrawCrossHair = false; +#ifdef GTA_PC + bool DrawCrossHairPC = false; +#endif + + int32 WeaponType = FindPlayerPed()->m_weapons[FindPlayerPed()->m_currentWeapon].m_eWeaponType; + int32 Mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + + if (Mode == CCam::MODE_SNIPER || Mode == CCam::MODE_ROCKETLAUNCHER || Mode == CCam::MODE_M16_1STPERSON +#ifdef GTA_PC + || Mode == CCam::MODE_HELICANNON_1STPERSON +#endif + ) + { + DrawCrossHair = true; + } + +#ifdef GTA_PC + if (Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || Mode == CCam::MODE_SNIPER_RUNABOUT) + DrawCrossHairPC = true; + + /* + Draw Crosshairs + */ + if (TheCamera.Cams[TheCamera.ActiveCam].Using3rdPersonMouseCam() && + (!CPad::GetPad(0)->GetLookBehindForPed() || TheCamera.m_bPlayerIsInGarage) || Mode == CCam::MODE_1STPERSON_RUNABOUT) { + if (FindPlayerPed() && !FindPlayerPed()->EnteringCar()) { + if ((WeaponType >= WEAPONTYPE_COLT45 && WeaponType <= WEAPONTYPE_M16) || WeaponType == WEAPONTYPE_FLAMETHROWER) + DrawCrossHairPC = true; + } + } +#endif + + if ( DrawCrossHair +#ifdef GTA_PC + || DrawCrossHairPC +#endif + ) + { + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)rwFILTERLINEAR); + + SpriteBrightness = Min(SpriteBrightness+1, 30); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + + float fStep = Sin((CTimer::GetTimeInMilliseconds() & 1023)/1024.0f * 6.28f); + float fMultBright = SpriteBrightness / 30.0f * (0.25f * fStep + 0.75f); + CRect rect; +#ifdef GTA_PC + if (DrawCrossHairPC && TheCamera.Cams[TheCamera.ActiveCam].Using3rdPersonMouseCam()) { + float f3rdX = SCREEN_WIDTH * TheCamera.m_f3rdPersonCHairMultX; + float f3rdY = SCREEN_HEIGHT * TheCamera.m_f3rdPersonCHairMultY; +#ifdef ASPECT_RATIO_SCALE + f3rdY -= SCREEN_SCALE_Y(2.0f); +#endif + if (FindPlayerPed() && WeaponType == WEAPONTYPE_M16) { + rect.left = f3rdX - SCREEN_SCALE_X(32.0f * 0.6f); + rect.top = f3rdY - SCREEN_SCALE_Y(32.0f * 0.6f); + rect.right = f3rdX + SCREEN_SCALE_X(32.0f * 0.6f); + rect.bottom = f3rdY + SCREEN_SCALE_Y(32.0f * 0.6f); + + Sprites[HUD_SITEM16].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f); + } + else { + rect.left = f3rdX - SCREEN_SCALE_X(32.0f * 0.4f); + rect.top = f3rdY - SCREEN_SCALE_Y(32.0f * 0.4f); + rect.right = f3rdX + SCREEN_SCALE_X(32.0f * 0.4f); + rect.bottom = f3rdY + SCREEN_SCALE_Y(32.0f * 0.4f); + + Sprites[HUD_SITEM16].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f); + } + } + else +#endif + { + if (Mode == CCam::MODE_M16_1STPERSON +#ifdef GTA_PC + || Mode == CCam::MODE_M16_1STPERSON_RUNABOUT + || Mode == CCam::MODE_HELICANNON_1STPERSON +#endif + ) + { + rect.left = (SCREEN_WIDTH / 2) - SCREEN_SCALE_X(32.0f); + rect.top = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(32.0f); + rect.right = (SCREEN_WIDTH / 2) + SCREEN_SCALE_X(32.0f); + rect.bottom = (SCREEN_HEIGHT / 2) + SCREEN_SCALE_Y(32.0f); + Sprites[HUD_SITEM16].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f); + } +#ifdef GTA_PC + else if (Mode == CCam::MODE_1STPERSON_RUNABOUT) { + rect.left = (SCREEN_WIDTH / 2) - SCREEN_SCALE_X(32.0f * 0.7f); + rect.top = (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(32.0f * 0.7f); + rect.right = (SCREEN_WIDTH / 2) + SCREEN_SCALE_X(32.0f * 0.7f); + rect.bottom = (SCREEN_HEIGHT / 2) + SCREEN_SCALE_Y(32.0f * 0.7f); + + Sprites[HUD_SITEM16].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f); + } +#endif + else if (Mode == CCam::MODE_ROCKETLAUNCHER +#ifdef GTA_PC + || Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT +#endif + ) + { + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpRocketSightTex)); + CSprite::RenderOneXLUSprite(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 1.0f, SCREEN_SCALE_X_PC(40.0f), SCREEN_SCALE_Y_PC(40.0f), (100.0f * fMultBright), (200.0f * fMultBright), (100.0f * fMultBright), 255, 1.0f, 255); + } + else { + // Sniper + rect.left = SCREEN_WIDTH/2 - SCREEN_SCALE_X(210.0f); + rect.top = SCREEN_HEIGHT/2 - SCREEN_SCALE_Y(210.0f); + rect.right = SCREEN_WIDTH/2; + rect.bottom = SCREEN_HEIGHT/2; + Sprites[HUD_SITESNIPER].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.01f, 0.01f, 1.0f, 0.0f, 0.01f, 1.0f, 1.0f, 1.0f); + + rect.left = SCREEN_WIDTH/2; + rect.top = SCREEN_HEIGHT/2 - SCREEN_SCALE_Y(210.0f); + rect.right = SCREEN_WIDTH/2 + SCREEN_SCALE_X(210.0f); + rect.bottom = SCREEN_HEIGHT/2; + Sprites[HUD_SITESNIPER].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.99f, 0.0f, 0.01f, 0.01f, 0.99f, 1.0f, 0.01f, 1.0f); + + rect.left = SCREEN_WIDTH/2 - SCREEN_SCALE_X(210.0f); + rect.top = SCREEN_HEIGHT/2; + rect.right = SCREEN_WIDTH/2; + rect.bottom = SCREEN_HEIGHT/2 + SCREEN_SCALE_Y(210.0f); + Sprites[HUD_SITESNIPER].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.01f, 0.99f, 1.0f, 0.99f, 0.01f, 0.01f, 1.0f, 0.01f); + + rect.left = SCREEN_WIDTH/2; + rect.top = SCREEN_HEIGHT/2; + rect.right = SCREEN_WIDTH/2 + SCREEN_SCALE_X(210.0f); + rect.bottom = SCREEN_HEIGHT/2 + SCREEN_SCALE_Y(210.0f); + Sprites[HUD_SITESNIPER].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.99f, 0.99f, 0.01f, 0.99f, 0.99f, 0.01f, 0.01f, 0.01f); + } + } + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + } + else { + SpriteBrightness = 0; + } + + /* + DrawMoneyCounter + */ + wchar sPrint[16]; + wchar sPrintIcon[16]; + char sTemp[16]; + + sprintf(sTemp, "$%08d", CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney); + AsciiToUnicode(sTemp, sPrint); + + CFont::SetPropOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetPropOff(); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(MONEY_X) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(43.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrint); + CFont::SetColor(MONEY_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(MONEY_X), SCREEN_SCALE_Y(43.0f), sPrint); + + /* + DrawAmmo + */ + int32 AmmoAmount = CWeaponInfo::GetWeaponInfo(FindPlayerPed()->GetWeapon()->m_eWeaponType)->m_nAmountofAmmunition; + int32 AmmoInClip = FindPlayerPed()->m_weapons[FindPlayerPed()->m_currentWeapon].m_nAmmoInClip; + int32 TotalAmmo = FindPlayerPed()->m_weapons[FindPlayerPed()->m_currentWeapon].m_nAmmoTotal; + int32 Ammo, Clip; + + if (AmmoAmount <= 1 || AmmoAmount >= 1000) + sprintf(sTemp, "%d", TotalAmmo); + else { + if (WeaponType == WEAPONTYPE_FLAMETHROWER) { + Clip = AmmoInClip / 10; + + Ammo = Min((TotalAmmo - AmmoInClip) / 10, 9999); + } + else { + Clip = AmmoInClip; + + Ammo = Min(TotalAmmo - AmmoInClip, 9999); + } + + sprintf(sTemp, "%d-%d", Ammo, Clip); + } + + AsciiToUnicode(sTemp, sPrint); + + /* + DrawWeaponIcon + */ + Sprites[WeaponType].Draw( + CRect( + SCREEN_SCALE_FROM_RIGHT(WEAPON_X), + SCREEN_SCALE_Y(27.0f), + SCREEN_SCALE_FROM_RIGHT(WEAPON_X)+SCREEN_SCALE_X(64.0f), + SCREEN_SCALE_Y(27.0f)+SCREEN_SCALE_Y(64.0f)), + CRGBA(255, 255, 255, 255), + 0.015f, + 0.015f, + 1.0f, + 0.0f, + 0.015f, + 1.0f, + 1.0f, + 1.0f); + + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.4f), SCREEN_SCALE_Y(0.6f)); + CFont::SetJustifyOff(); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_WIDTH); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_BANK); + + if (!CDarkel::FrenzyOnGoing() && WeaponType != WEAPONTYPE_UNARMED && WeaponType != WEAPONTYPE_BASEBALLBAT) { + CFont::SetColor(AMMO_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(AMMO_X), SCREEN_SCALE_Y(73.0f), sPrint); + } + + /* + DrawHealth + */ + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetJustifyOff(); + CFont::SetCentreOff(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetRightJustifyOn(); + CFont::SetPropOff(); + CFont::SetFontStyle(FONT_HEADING); + + if (m_ItemToFlash == ITEM_HEALTH && FRAMECOUNTER & 8 + || m_ItemToFlash != ITEM_HEALTH + || FindPlayerPed()->m_fHealth < 10 + && FRAMECOUNTER & 8) { + if (FindPlayerPed()->m_fHealth >= 10 + || FindPlayerPed()->m_fHealth < 10 && FRAMECOUNTER & 8) { + + AsciiToUnicode("{", sPrintIcon); +#ifdef FIX_BUGS + sprintf(sTemp, "%03d", int32(FindPlayerPed()->m_fHealth + 0.5f)); +#else + sprintf(sTemp, "%03d", (int32)FindPlayerPed()->m_fHealth); +#endif + AsciiToUnicode(sTemp, sPrint); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(HEALTH_X) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(65.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrint); + + if (!CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss || CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss + 2000 || FRAMECOUNTER & 4) + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(HEALTH_X) + SCREEN_SCALE_X_FIX(2.0f) - SCREEN_SCALE_X(56.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(65.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrintIcon); + + CFont::SetColor(HEALTH_COLOR); + + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(HEALTH_X), SCREEN_SCALE_Y(65.0f), sPrint); + + if (!CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss || CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss + 2000 || FRAMECOUNTER & 4) + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(HEALTH_X) + SCREEN_SCALE_X_FIX(2.0f) - SCREEN_SCALE_X(56.0f), SCREEN_SCALE_Y(65.0f), sPrintIcon); + } + } + + /* + DrawArmour + */ + if (m_ItemToFlash == ITEM_ARMOUR && FRAMECOUNTER & 8 || m_ItemToFlash != ITEM_ARMOUR) { + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + if (FindPlayerPed()->m_fArmour > 1.0f) { + AsciiToUnicode("[", sPrintIcon); +#ifdef FIX_BUGS + sprintf(sTemp, "%03d", int32(FindPlayerPed()->m_fArmour + 0.5f)); +#else + sprintf(sTemp, "%03d", (int32)FindPlayerPed()->m_fArmour); +#endif + AsciiToUnicode(sTemp, sPrint); + + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(182.0f) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(65.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrint); + + if (!CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss || CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss + 2000 || FRAMECOUNTER & 4) + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(182.0f) + SCREEN_SCALE_X_FIX(2.0f) - SCREEN_SCALE_X(54.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(65.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrintIcon); + + CFont::SetColor(ARMOUR_COLOR); + + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(182.0f), SCREEN_SCALE_Y(65.0f), sPrint); + + if (!CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss || CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastArmourLoss + 2000 || FRAMECOUNTER & 1) { + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(182.0f) - SCREEN_SCALE_X(54.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(65.0f), sPrintIcon); + } + } + } + + /* + DrawWantedLevel + */ + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetJustifyOff(); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_HEADING); + + AsciiToUnicode("]", sPrintIcon); + + float fStarsX = SCREEN_SCALE_FROM_RIGHT(STARS_X); + + for (int i = 0; i < 6; i++) { + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(fStarsX + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(87.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrintIcon); + + if (FindPlayerPed()->m_pWanted->GetWantedLevel() > i + && (CTimer::GetTimeInMilliseconds() > FindPlayerPed()->m_pWanted->m_nLastWantedLevelChange + + 2000 || FRAMECOUNTER & 4)) { + + CFont::SetColor(WANTED_COLOR); + CFont::PrintString(fStarsX, SCREEN_SCALE_Y(87.0f), sPrintIcon); + } + + fStarsX -= SCREEN_SCALE_X(23.0f); + } + + /* + DrawZoneName + */ + if (m_pZoneName) { + float fZoneAlpha = 255.0f; + + if (m_pZoneName != m_pLastZoneName) { + switch (m_ZoneState) { + case 0: + m_ZoneState = 2; + m_ZoneToPrint = m_pZoneName; + m_ZoneNameTimer = 0; + m_ZoneFadeTimer = 0; + break; + case 1: + case 2: + case 3: + case 4: + m_ZoneNameTimer = 5; + m_ZoneState = 4; + break; + default: + break; + } + m_pLastZoneName = m_pZoneName; + } + + if (m_ZoneState) { + switch (m_ZoneState) { + case 1: + m_ZoneFadeTimer = 1000; + if (m_ZoneNameTimer > 10000) { + m_ZoneFadeTimer = 1000; + m_ZoneState = 3; + } + fZoneAlpha = 255.0f; + break; + case 2: + m_ZoneFadeTimer += CTimer::GetTimeStepInMilliseconds(); + if (m_ZoneFadeTimer > 1000) { + m_ZoneState = 1; + m_ZoneFadeTimer = 1000; + } + fZoneAlpha = m_ZoneFadeTimer / 1000.0f * 255.0f; + break; + case 3: + m_ZoneFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_ZoneFadeTimer < 0) { + m_ZoneState = 0; + m_ZoneFadeTimer = 0; + } + fZoneAlpha = m_ZoneFadeTimer / 1000.0f * 255.0f; + break; + case 4: + m_ZoneFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_ZoneFadeTimer < 0) { + m_ZoneFadeTimer = 0; + m_ZoneToPrint = m_pLastZoneName; + m_ZoneState = 2; + } + fZoneAlpha = m_ZoneFadeTimer / 1000.0f * 255.0f; + break; + default: + break; + + } + +#ifndef HUD_ENHANCEMENTS + if (!m_Message[0]) +#else + if (!m_Message[0] && !m_BigMessage[2][0]) // Hide zone name if wasted/busted text is displaying +#endif + { + m_ZoneNameTimer += CTimer::GetTimeStepInMilliseconds(); + CFont::SetJustifyOff(); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + CFont::SetScale(SCREEN_SCALE_X(1.2f * 0.8f), SCREEN_SCALE_Y(1.2f)); + else + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.2f)); + + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetColor(CRGBA(0, 0, 0, fZoneAlpha)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(32.0f) + SCREEN_SCALE_X_FIX(1.0f), SCREEN_SCALE_FROM_BOTTOM(ZONE_Y) + SCREEN_SCALE_Y_FIX(1.0f), m_ZoneToPrint); + CFont::SetColor(CRGBA(ZONE_COLOR.r, ZONE_COLOR.g, ZONE_COLOR.b, fZoneAlpha)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(32.0f), SCREEN_SCALE_FROM_BOTTOM(ZONE_Y), m_ZoneToPrint); + } + } + } + + /* + DrawVehicleName + */ + if (m_pVehicleName) { + float fVehicleAlpha = 0.0f; + + if (m_pVehicleName != m_pLastVehicleName) { + switch (m_VehicleState) { + case 0: + m_VehicleState = 2; + m_pVehicleNameToPrint = m_pVehicleName; + m_VehicleNameTimer = 0; + m_VehicleFadeTimer = 0; + break; + case 1: + case 2: + case 3: + case 4: + m_VehicleNameTimer = 0; + m_VehicleState = 4; + break; + default: + break; + } + m_pLastVehicleName = m_pVehicleName; + } + + if (m_VehicleState) { + switch (m_VehicleState) { + case 1: + if (m_VehicleNameTimer > 10000) { + m_VehicleFadeTimer = 1000; + m_VehicleState = 3; + } + fVehicleAlpha = 255.0f; + break; + case 2: + m_VehicleFadeTimer += CTimer::GetTimeStepInMilliseconds(); + if (m_VehicleFadeTimer > 1000) { + m_VehicleState = 1; + m_VehicleFadeTimer = 1000; + } + fVehicleAlpha = m_VehicleFadeTimer / 1000.0f * 255.0f; + break; + case 3: + m_VehicleFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_VehicleFadeTimer < 0) { + m_VehicleState = 0; + m_VehicleFadeTimer = 0; + } + fVehicleAlpha = m_VehicleFadeTimer / 1000.0f * 255.0f; + break; + case 4: + m_VehicleFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_VehicleFadeTimer < 0) { + m_VehicleFadeTimer = 0; + m_pVehicleNameToPrint = m_pLastVehicleName; + m_VehicleNameTimer = 0; + m_VehicleState = 2; + } + fVehicleAlpha = m_VehicleFadeTimer / 1000.0f * 255.0f; + break; + default: + break; + } + +#ifndef HUD_ENHANCEMENTS + if (!m_Message[0]) +#else + if (!m_Message[0] && !m_BigMessage[2][0]) // Hide vehicle name if wasted/busted text is displaying +#endif + { + m_VehicleNameTimer += CTimer::GetTimeStepInMilliseconds(); + CFont::SetJustifyOff(); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + + if (FrontEndMenuManager.m_PrefsLanguage != CMenuManager::LANGUAGE_ITALIAN && FrontEndMenuManager.m_PrefsLanguage != CMenuManager::LANGUAGE_SPANISH) + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.2f)); + else + CFont::SetScale(SCREEN_SCALE_X(1.2f * 0.85f), SCREEN_SCALE_Y(1.2f)); + + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetColor(CRGBA(0, 0, 0, fVehicleAlpha)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(32.0f) + SCREEN_SCALE_X_FIX(1.0f), SCREEN_SCALE_FROM_BOTTOM(VEHICLE_Y) + SCREEN_SCALE_Y_FIX(1.0f), m_pVehicleNameToPrint); + CFont::SetColor(CRGBA(VEHICLE_COLOR.r, VEHICLE_COLOR.g, VEHICLE_COLOR.b, fVehicleAlpha)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(32.0f), SCREEN_SCALE_FROM_BOTTOM(VEHICLE_Y), m_pVehicleNameToPrint); + } + } + } + else { + m_pLastVehicleName = nil; + m_VehicleState = 0; + m_VehicleFadeTimer = 0; + m_VehicleNameTimer = 0; + } + + /* + DrawClock + */ + CFont::SetJustifyOff(); + CFont::SetCentreOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetPropOff(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + + sprintf(sTemp, "%02d:%02d", CClock::GetHours(), CClock::GetMinutes()); + AsciiToUnicode(sTemp, sPrint); + + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(CLOCK_X) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(22.0f) + SCREEN_SCALE_Y_FIX(2.0f), sPrint); + CFont::SetColor(CLOCK_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(CLOCK_X), SCREEN_SCALE_Y(22.0f), sPrint); + + /* + DrawOnScreenTimer + */ + wchar sTimer[16]; + + if (!CUserDisplay::OnscnTimer.m_sEntries[0].m_bTimerProcessed) + TimerOnLastFrame = false; + if (!CUserDisplay::OnscnTimer.m_sEntries[0].m_bCounterProcessed) + CounterOnLastFrame = false; + + if (CUserDisplay::OnscnTimer.m_bProcessed) { + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_bTimerProcessed) { + if (!TimerOnLastFrame) + TimerFlashTimer = 1; + + TimerOnLastFrame = true; + + if (TimerFlashTimer) { + if (++TimerFlashTimer > 50) + TimerFlashTimer = 0; + } + + if (FRAMECOUNTER & 4 || !TimerFlashTimer) { + AsciiToUnicode(CUserDisplay::OnscnTimer.m_sEntries[0].m_bTimerBuffer, sTimer); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetPropOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(110.0f) + SCREEN_SCALE_Y_FIX(2.0f), sTimer); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetColor(TIMER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET), SCREEN_SCALE_Y(110.0f), sTimer); + + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_aTimerText[0]) { + CFont::SetPropOn(); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::SetScale(SCREEN_SCALE_X(0.8f * 0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(80.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(110.0f) + SCREEN_SCALE_Y_FIX(2.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sEntries[0].m_aTimerText)); + CFont::SetColor(TIMER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(80.0f), SCREEN_SCALE_Y(110.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sEntries[0].m_aTimerText)); + } + } + } + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_bCounterProcessed) { + if (!CounterOnLastFrame) + CounterFlashTimer = 1; + + CounterOnLastFrame = true; + + if (CounterFlashTimer) { + if (++CounterFlashTimer > 50) + CounterFlashTimer = 0; + } + + if (FRAMECOUNTER & 4 || !CounterFlashTimer) { + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_nType == COUNTER_DISPLAY_NUMBER) { + AsciiToUnicode(CUserDisplay::OnscnTimer.m_sEntries[0].m_bCounterBuffer, sTimer); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetColor(CRGBA(244, 20, 20, 255)); + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetPropOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y_FIX(2.0f), sTimer); + CFont::SetColor(COUNTER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET), SCREEN_SCALE_Y(132.0f), sTimer); + } else { + int counter = atoi(CUserDisplay::OnscnTimer.m_sEntries[0].m_bCounterBuffer); +#ifdef FIX_BUGS + counter = Min(counter, 100); +#endif + CSprite2d::DrawRect + ( + CRect + ( + SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(100.0f) / 2 + SCREEN_SCALE_X_FIX(4.0f), + SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y(8.0f), + SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) + SCREEN_SCALE_X_FIX(4.0f), + SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y_PC(11.0f) + SCREEN_SCALE_Y(8.0f) + ), + CRGBA(0, 106, 164, 80) + ); + + CSprite2d::DrawRect + ( + CRect + ( + SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(100.0f) / 2 + SCREEN_SCALE_X_FIX(4.0f), + SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y(8.0f), + SCREEN_SCALE_X_PC((float)counter) / 2.0f + SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(100.0f) / 2.0f + SCREEN_SCALE_X_FIX(4.0f), + SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y_PC(11.0f) + SCREEN_SCALE_Y(8.0f) + ), + CRGBA(0, 106, 164, 255) + ); + } + + if (CUserDisplay::OnscnTimer.m_sEntries[0].m_aCounterText[0]) { + CFont::SetPropOn(); + CFont::SetScale(SCREEN_SCALE_X(0.8f), SCREEN_SCALE_Y(1.35f)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(61.0f) + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y_FIX(2.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sEntries[0].m_aCounterText)); + CFont::SetColor(COUNTER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(TIMER_RIGHT_OFFSET) - SCREEN_SCALE_X(61.0f), SCREEN_SCALE_Y(132.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sEntries[0].m_aCounterText)); + } + } + } + } + + ///////////////////////////////// + /* + DrawPager + */ + if (!m_PagerMessage[0] && PagerOn == 1) { + PagerSoundPlayed = false; + PagerOn = 2; + } + if (m_PagerMessage[0] || PagerOn == 2) { + if (!PagerOn) { + PagerOn = 1; + PagerXOffset = 150.0f; + } + if (PagerOn == 1) { + if (PagerXOffset > 0.0f) { + float fStep = PagerXOffset * 0.1f; + if (fStep > 10.0f) + fStep = 10.0f; + PagerXOffset -= fStep * CTimer::GetTimeStep(); + } + if (!PagerSoundPlayed) { + DMAudio.PlayFrontEndSound(SOUND_PAGER, 0); + PagerSoundPlayed = 1; + } + } + else if (PagerOn == 2) { + float fStep = PagerXOffset * 0.1f; + if (fStep < 2.0f) + fStep = 2.0f; + PagerXOffset += fStep; + if (PagerXOffset > 150.0f) { + PagerXOffset = 150.0f; + PagerOn = 0; + } + } + Sprites[HUD_PAGER].Draw(CRect(SCREEN_SCALE_X(26.0f) - SCREEN_SCALE_X_FIX(PagerXOffset), SCREEN_SCALE_Y(27.0f), SCREEN_SCALE_X(160.0f) + SCREEN_SCALE_X(26.0f) - SCREEN_SCALE_X_FIX(PagerXOffset), SCREEN_SCALE_Y(80.0f) + SCREEN_SCALE_Y(27.0f)), CRGBA(255, 255, 255, 255)); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.84f), SCREEN_SCALE_Y(1.0f)); + CFont::SetColor(PAGER_COLOR); + CFont::SetRightJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetCentreOff(); + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetJustifyOff(); + CFont::SetPropOff(); + CFont::SetFontStyle(FONT_PAGER); + CFont::PrintString(SCREEN_SCALE_X(52.0f) - SCREEN_SCALE_X_FIX(PagerXOffset), SCREEN_SCALE_Y(54.0f), m_PagerMessage); + } + + /* + DrawRadar + */ + if (m_ItemToFlash == ITEM_RADAR && FRAMECOUNTER & 8 || m_ItemToFlash != ITEM_RADAR) { + CRadar::DrawMap(); + CRect rect(0.0f, 0.0f, SCREEN_SCALE_X(RADAR_WIDTH), SCREEN_SCALE_Y(RADAR_HEIGHT)); + rect.Translate(SCREEN_SCALE_X_FIX(RADAR_LEFT), SCREEN_SCALE_FROM_BOTTOM(RADAR_BOTTOM + RADAR_HEIGHT)); + +#ifdef PS2_HUD + #ifdef FIX_BUGS + rect.Grow(SCREEN_SCALE_X(2.0f), SCREEN_SCALE_X(4.0f), SCREEN_SCALE_Y(2.0f), SCREEN_SCALE_Y(4.0f)); + #else + rect.Grow(2.0f, 4.0f); + #endif +#else + #ifdef FIX_BUGS + rect.Grow(SCREEN_SCALE_X(4.0f), SCREEN_SCALE_X(4.0f), SCREEN_SCALE_Y(4.0f), SCREEN_SCALE_Y(4.0f)); + #else + rect.Grow(4.0f); + #endif +#endif + Sprites[HUD_RADARDISC].Draw(rect, RADARDISC_COLOR); + CRadar::DrawBlips(); + } + } + + /* + Draw3dMarkers + */ + if (m_Wants_To_Draw_3dMarkers && !TheCamera.m_WideScreenOn && !m_BigMessage[0][0] && !m_BigMessage[2][0]) { + CRadar::Draw3dMarkers(); + } + + /* + DrawScriptText + */ + if (!CTimer::GetIsUserPaused()) { + for (int i = 0; i < ARRAY_SIZE(CTheScripts::IntroTextLines); i++) { + if (CTheScripts::IntroTextLines[i].m_Text[0] && CTheScripts::IntroTextLines[i].m_bTextBeforeFade) { + CFont::SetScale(SCREEN_SCALE_X_PC(CTheScripts::IntroTextLines[i].m_fScaleX), SCREEN_SCALE_Y_PC(CTheScripts::IntroTextLines[i].m_fScaleY) +#if !defined(PS2_HUD) || defined(FIX_BUGS) + * 0.5f +#endif + ); + + CFont::SetColor(CTheScripts::IntroTextLines[i].m_sColor); + + if (CTheScripts::IntroTextLines[i].m_bJustify) + CFont::SetJustifyOn(); + else + CFont::SetJustifyOff(); + + if (CTheScripts::IntroTextLines[i].m_bRightJustify) + CFont::SetRightJustifyOn(); + else + CFont::SetRightJustifyOff(); + + if (CTheScripts::IntroTextLines[i].m_bCentered) + CFont::SetCentreOn(); + else + CFont::SetCentreOff(); + + CFont::SetWrapx(SCALE_AND_CENTER_X_PC(CTheScripts::IntroTextLines[i].m_fWrapX)); + + CFont::SetCentreSize(SCREEN_SCALE_X_PC(CTheScripts::IntroTextLines[i].m_fCenterSize)); + + if (CTheScripts::IntroTextLines[i].m_bBackground) + CFont::SetBackgroundOn(); + else + CFont::SetBackgroundOff(); + + CFont::SetBackgroundColor(CTheScripts::IntroTextLines[i].m_sBackgroundColor); + + if (CTheScripts::IntroTextLines[i].m_bBackgroundOnly) + CFont::SetBackGroundOnlyTextOn(); + else + CFont::SetBackGroundOnlyTextOff(); + + if (CTheScripts::IntroTextLines[i].m_bTextProportional) + CFont::SetPropOn(); + else + CFont::SetPropOff(); + + CFont::SetFontStyle(FONT_LOCALE(CTheScripts::IntroTextLines[i].m_nFont)); + +#if defined(PS2_HUD) && !defined(FIX_BUGS) + CFont::PrintString(CTheScripts::IntroTextLines[i].m_fAtX, CTheScripts::IntroTextLines[i].m_fAtY, CTheScripts::IntroTextLines[i].m_Text); +#else + CFont::PrintString(SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH - CTheScripts::IntroTextLines[i].m_fAtX), SCREEN_SCALE_Y(DEFAULT_SCREEN_HEIGHT - CTheScripts::IntroTextLines[i].m_fAtY), CTheScripts::IntroTextLines[i].m_Text); +#endif + } + } + for (int i = 0; i < ARRAY_SIZE(CTheScripts::IntroRectangles); i++) { + intro_script_rectangle &IntroRect = CTheScripts::IntroRectangles[i]; + + // Yeah, top and bottom changed place. R* vision + if (IntroRect.m_bIsUsed && IntroRect.m_bBeforeFade) { + if (IntroRect.m_nTextureId >= 0) { + CRect rect ( + IntroRect.m_sRect.left, + IntroRect.m_sRect.bottom, + IntroRect.m_sRect.right, + IntroRect.m_sRect.top ); + + CTheScripts::ScriptSprites[IntroRect.m_nTextureId].Draw(rect, IntroRect.m_sColor); + } + else { + CRect rect ( + IntroRect.m_sRect.left, + IntroRect.m_sRect.bottom, + IntroRect.m_sRect.right, + IntroRect.m_sRect.top ); + + CSprite2d::DrawRect(rect, IntroRect.m_sColor); + } + } + } + + /* + DrawSubtitles + */ + if (m_Message[0] && !m_BigMessage[2][0] && (FrontEndMenuManager.m_PrefsShowSubtitles == 1 || !TheCamera.m_WideScreenOn)) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetBackgroundColor(CRGBA(0, 0, 0, 128)); + CFont::SetScale(SCREEN_SCALE_X_PC(0.48f), SCREEN_SCALE_Y_PC(1.12f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + +#ifdef XBOX_SUBTITLES + float radarBulge = SCREEN_SCALE_X(45.0f) + SCREEN_SCALE_X(16.0f); + float rectWidth = SCREEN_WIDTH - SCREEN_SCALE_X(45.0f) - SCREEN_SCALE_X(16.0f) - radarBulge; + CFont::SetCentreSize(rectWidth); + CFont::SetColor(CRGBA(180, 180, 180, 255)); + + CFont::PrintOutlinedString(rectWidth / 2.0f + radarBulge, SCREEN_SCALE_Y(4.0f) + SCREEN_SCALE_FROM_BOTTOM(48.0f) - SCREEN_SCALE_Y(1), m_Message, + 2.0f, true, CRGBA(0, 0, 0, 255)); +#else + float radarBulge = SCREEN_SCALE_X(40.0f) + SCREEN_SCALE_X(8.0f); + float rectWidth = SCREEN_SCALE_FROM_RIGHT(50.0f) - SCREEN_SCALE_X(8.0f) - radarBulge; + + CFont::SetCentreSize(rectWidth); + + const int16 shadow = 1; + CFont::SetDropShadowPosition(shadow); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetColor(CRGBA(235, 235, 235, 255)); + + // I'm not sure shadow substaction was intentional here, might be a leftover if CFont::PrintString was used for a shadow draw call + CFont::PrintString(rectWidth / 2.0f + radarBulge - SCREEN_SCALE_X_FIX(shadow), SCREEN_SCALE_Y_PC(4.0f) + SCREEN_SCALE_FROM_BOTTOM(SUBS_Y) - SCREEN_SCALE_Y_FIX(shadow), m_Message); + CFont::SetDropShadowPosition(0); +#endif // #ifdef XBOX_SUBTITLES + } + + /* + DrawBigMessage + */ + // MissionCompleteFailedText + if (m_BigMessage[0][0]) { + if (BigMessageInUse[0] != 0.0f) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetBackGroundOnlyTextOff(); + + if (CGame::frenchGame || CGame::germanGame) + CFont::SetScale(SCREEN_SCALE_X_PC(1.8f), SCREEN_SCALE_Y_PC(1.8f)); + else + CFont::SetScale(SCREEN_SCALE_X_PC(1.8f), SCREEN_SCALE_Y_PC(1.8f)); + + CFont::SetPropOn(); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 25)); + CFont::SetColor(CRGBA(255, 255, 0, 255)); + CFont::SetFontStyle(FONT_HEADING); + + // Appearently sliding text in here was abandoned very early, since this text is centered now. +#ifdef FIX_BUGS + if (BigMessageX[0] >= SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH-20)) +#else + if (BigMessageX[0] >= SCREEN_WIDTH-20) +#endif + { + BigMessageInUse[0] += CTimer::GetTimeStep(); + + if (BigMessageInUse[0] >= 120.0f) { + BigMessageInUse[0] = 120.0f; + BigMessageAlpha[0] -= (CTimer::GetTimeStepInMilliseconds() * 0.3f); + } + + if (BigMessageAlpha[0] <= 0.0f) { + m_BigMessage[0][0] = 0; + BigMessageAlpha[0] = 0.0f; + } + } + else { + BigMessageX[0] += SCREEN_SCALE_X_FIX(CTimer::GetTimeStepInMilliseconds() * 0.3f); + BigMessageAlpha[0] += (CTimer::GetTimeStepInMilliseconds() * 0.3f); + + if (BigMessageAlpha[0] > 255.0f) + BigMessageAlpha[0] = 255.0f; + } + CFont::SetColor(CRGBA(0, 0, 0, BigMessageAlpha[0])); + +#if defined(PS2_HUD) && !defined(FIX_BUGS) // yeah, that's right. ps2 uses y=ScaleX(a) + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X_FIX(2.0f), (SCREEN_WIDTH / 2) - SCREEN_SCALE_X(120.0f) + SCREEN_SCALE_Y_FIX(2.0f), m_BigMessage[0]); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X_FIX(2.0f), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y_OFFSET) + SCREEN_SCALE_Y_FIX(2.0f), m_BigMessage[0]); +#endif + CFont::SetColor(CRGBA(BIGMESSAGE_COLOR.r, BIGMESSAGE_COLOR.g, BIGMESSAGE_COLOR.b, BigMessageAlpha[0])); +#if defined(PS2_HUD) && !defined(FIX_BUGS) // same + CFont::PrintString(SCREEN_WIDTH / 2, (SCREEN_WIDTH / 2) - SCREEN_SCALE_X(120.0f), m_BigMessage[0]); +#else + CFont::PrintString(SCREEN_WIDTH / 2, (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(18.0f), m_BigMessage[0]); +#endif + } + else { + BigMessageAlpha[0] = 0.0f; + BigMessageX[0] = SCALE_AND_CENTER_X_FIX(-60.0f); + BigMessageInUse[0] = 1.0f; + } + } + else { + BigMessageInUse[0] = 0.0f; + } + + // WastedBustedText + if (m_BigMessage[2][0]) { + if (BigMessageInUse[2] != 0.0f) { + BigMessageAlpha[2] += (CTimer::GetTimeStepInMilliseconds() * 0.4f); + + if (BigMessageAlpha[2] > 255.0f) + BigMessageAlpha[2] = 255.0f; + + CFont::SetBackgroundOff(); + + if (CGame::frenchGame || CGame::germanGame) + CFont::SetScale(SCREEN_SCALE_X_PC(1.4f), SCREEN_SCALE_Y_PC(1.4f)); + else + CFont::SetScale(SCREEN_SCALE_X_PC(2.0f), SCREEN_SCALE_Y_PC(2.0f)); + + CFont::SetPropOn(); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + + CFont::SetColor(CRGBA(0, 0, 0, BigMessageAlpha[2]*0.75f)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f) + SCREEN_SCALE_X_FIX(4.0f), SCREEN_SCALE_FROM_BOTTOM(WASTEDBUSTED_Y) + SCREEN_SCALE_Y(4.0f), m_BigMessage[2]); + CFont::SetColor(CRGBA(WASTEDBUSTED_COLOR.r, WASTEDBUSTED_COLOR.g, WASTEDBUSTED_COLOR.b, BigMessageAlpha[2])); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(WASTEDBUSTED_Y), m_BigMessage[2]); + } + else { + BigMessageAlpha[2] = 0.0f; + BigMessageInUse[2] = 1.0f; + } + } + else { + BigMessageInUse[2] = 0.0f; + } + } +} + +void CHud::DrawAfterFade() +{ + if (CTimer::GetIsUserPaused() || CReplay::IsPlayingBack()) + return; + + if (m_HelpMessage[0]) { + if (!CMessages::WideStringCompare(m_HelpMessage, m_LastHelpMessage, HELP_MSG_LENGTH)) { + switch (m_HelpMessageState) { + case 0: + m_HelpMessageFadeTimer = 0; + m_HelpMessageState = 2; + m_HelpMessageTimer = 0; + CMessages::WideStringCopy(m_HelpMessageToPrint, m_HelpMessage, HELP_MSG_LENGTH); + m_HelpMessageDisplayTime = CMessages::GetWideStringLength(m_HelpMessage) / 20.0f + 3.0f; + + if (TheCamera.m_ScreenReductionPercentage == 0.0f) + DMAudio.PlayFrontEndSound(SOUND_HUD, 0); + break; + case 1: + case 2: + case 3: + case 4: + m_HelpMessageTimer = 5; + m_HelpMessageState = 4; + break; + default: + break; + } + CMessages::WideStringCopy(m_LastHelpMessage, m_HelpMessage, HELP_MSG_LENGTH); + } + + float fAlpha = 225.0f; + + if (m_HelpMessageState != 0) { + switch (m_HelpMessageState) { + case 1: + fAlpha = 225.0f; + m_HelpMessageFadeTimer = 600; + if (m_HelpMessageTimer > m_HelpMessageDisplayTime * 1000.0f || m_HelpMessageQuick && m_HelpMessageTimer > 1500.0f) { + m_HelpMessageFadeTimer = 600; + m_HelpMessageState = 3; + } + break; + case 2: + m_HelpMessageFadeTimer += 2 * CTimer::GetTimeStepInMilliseconds(); + if (m_HelpMessageFadeTimer > 0) { + m_HelpMessageState = 1; + m_HelpMessageFadeTimer = 0; + } + fAlpha = m_HelpMessageFadeTimer / 1000.0f * 225.0f; + break; + case 3: + m_HelpMessageFadeTimer -= 2 * CTimer::GetTimeStepInMilliseconds(); + if (m_HelpMessageFadeTimer < 0) { + m_HelpMessageState = 0; + m_HelpMessageFadeTimer = 0; + } + fAlpha = m_HelpMessageFadeTimer / 1000.0f * 225.0f; + break; + case 4: + m_HelpMessageFadeTimer -= 2 * CTimer::GetTimeStepInMilliseconds(); + if (m_HelpMessageFadeTimer < 0) { + m_HelpMessageState = 2; + m_HelpMessageFadeTimer = 0; + CMessages::WideStringCopy(m_HelpMessageToPrint, m_LastHelpMessage, HELP_MSG_LENGTH); + } + fAlpha = m_HelpMessageFadeTimer / 1000.0f * 225.0f; + break; + default: + break; + } + + m_HelpMessageTimer += CTimer::GetTimeStepInMilliseconds(); + + CFont::SetAlphaFade(fAlpha); + CFont::SetCentreOff(); + CFont::SetPropOn(); + + if (CGame::germanGame) + CFont::SetScale(SCREEN_SCALE_X(0.52f * 0.85f), SCREEN_SCALE_Y(1.1f * 0.85f)); +#ifdef MORE_LANGUAGES + else if (CFont::IsJapanese()) + CFont::SetScale(SCREEN_SCALE_X(0.52f) * 1.35f, SCREEN_SCALE_Y(1.1f) * 1.25f); +#endif + else + CFont::SetScale(SCREEN_SCALE_X(0.52f), SCREEN_SCALE_Y(1.1f)); + + CFont::SetColor(CRGBA(175, 175, 175, 255)); + CFont::SetJustifyOff(); +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + CFont::SetWrapx(SCREEN_SCALE_X(229.0f) + SCREEN_SCALE_X(26.0f) - SCREEN_SCALE_X_FIX(4.0f)); + else +#endif + CFont::SetWrapx(SCREEN_SCALE_X(200.0f) + SCREEN_SCALE_X(26.0f) - SCREEN_SCALE_X_FIX(4.0f)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetBackgroundOn(); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetBackgroundColor(CRGBA(0, 0, 0, fAlpha * 0.9f)); + CFont::SetColor(CRGBA(175, 175, 175, 255)); + CFont::PrintString(SCREEN_SCALE_X(26.0f), SCREEN_SCALE_Y(28.0f) + SCREEN_SCALE_Y_FIX((150.0f - PagerXOffset) * 0.6f), m_HelpMessageToPrint); + CFont::SetAlphaFade(255.0f); + } + } + + for (int i = 0; i < ARRAY_SIZE(CTheScripts::IntroTextLines); i++) { + intro_text_line &line = CTheScripts::IntroTextLines[i]; + if (line.m_Text[0] != '\0' && !line.m_bTextBeforeFade) { + + CFont::SetScale(SCREEN_SCALE_X_PC(line.m_fScaleX), SCREEN_SCALE_Y_PC(line.m_fScaleY) +#if !defined(PS2_HUD) || defined(FIX_BUGS) + / 2 +#endif + ); + CFont::SetColor(line.m_sColor); + if (line.m_bJustify) + CFont::SetJustifyOn(); + else + CFont::SetJustifyOff(); + + if (line.m_bRightJustify) + CFont::SetRightJustifyOn(); + else + CFont::SetRightJustifyOff(); + + if (line.m_bCentered) + CFont::SetCentreOn(); + else + CFont::SetCentreOff(); + + CFont::SetWrapx(SCALE_AND_CENTER_X_PC(line.m_fWrapX)); + CFont::SetCentreSize(SCREEN_SCALE_X_PC(line.m_fCenterSize)); + + if (line.m_bBackground) + CFont::SetBackgroundOn(); + else + CFont::SetBackgroundOff(); + + CFont::SetBackgroundColor(line.m_sBackgroundColor); + if (line.m_bBackgroundOnly) + CFont::SetBackGroundOnlyTextOn(); + else + CFont::SetBackGroundOnlyTextOff(); + + if (line.m_bTextProportional) + CFont::SetPropOn(); + else + CFont::SetPropOff(); + + CFont::SetFontStyle(line.m_nFont); +#if defined(PS2_HUD) && !defined(FIX_BUGS) + CFont::PrintString(line.m_fAtX, line.m_fAtY, line.m_Text); +#else + CFont::PrintString(SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH - line.m_fAtX), SCREEN_SCALE_Y(DEFAULT_SCREEN_HEIGHT - line.m_fAtY), line.m_Text); +#endif + } + } + for (int i = 0; i < ARRAY_SIZE(CTheScripts::IntroRectangles); i++) { + intro_script_rectangle &rectangle = CTheScripts::IntroRectangles[i]; + if (rectangle.m_bIsUsed && !rectangle.m_bBeforeFade) { + + // Yeah, top and bottom changed place. R* vision + if (rectangle.m_nTextureId >= 0) { + CTheScripts::ScriptSprites[rectangle.m_nTextureId].Draw(CRect(rectangle.m_sRect.left, rectangle.m_sRect.bottom, + rectangle.m_sRect.right, rectangle.m_sRect.top), rectangle.m_sColor); + } else { + CSprite2d::DrawRect(CRect(rectangle.m_sRect.left, rectangle.m_sRect.bottom, + rectangle.m_sRect.right, rectangle.m_sRect.top), rectangle.m_sColor); + } + } + } + + /* + DrawBigMessage2 + */ + // Oddjob + if (m_BigMessage[3][0]) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X_PC(1.2f), SCREEN_SCALE_Y_PC(1.5f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 40)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString((SCREEN_WIDTH / 2) + SCREEN_SCALE_X_FIX(2.0f), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y) + SCREEN_SCALE_Y_FIX(2.0f), m_BigMessage[3]); + CFont::SetColor(ODDJOB_COLOR); + CFont::PrintString((SCREEN_WIDTH / 2), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y), m_BigMessage[3]); + } + + if (!m_BigMessage[1][0] && m_BigMessage[4][0]) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X_PC(1.2f), SCREEN_SCALE_Y_PC(1.5f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + CFont::PrintString((SCREEN_WIDTH / 2) - SCREEN_SCALE_X_FIX(2.0f), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y) - SCREEN_SCALE_Y_FIX(2.0f), m_BigMessage[4]); + CFont::SetColor(ODDJOB_COLOR); + CFont::PrintString((SCREEN_WIDTH / 2), (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(BIGMESSAGE_Y), m_BigMessage[4]); + } + + // Oddjob result + if (OddJob2OffTimer > 0) + OddJob2OffTimer -= CTimer::GetTimeStepInMilliseconds(); + + float fStep; + if (m_BigMessage[5][0] && OddJob2OffTimer <= 0.0f) { + switch (OddJob2On) { + case 0: + OddJob2On = 1; + OddJob2XOffset = 380.0f; + break; + case 1: + if (OddJob2XOffset <= 2.0f) { + OddJob2Timer = 0; + OddJob2On = 2; + } + else { + fStep = Min(40.0f, OddJob2XOffset / 6.0f); + OddJob2XOffset = OddJob2XOffset - fStep; + } + break; + case 2: + OddJob2Timer += CTimer::GetTimeStepInMilliseconds(); + if (OddJob2Timer > 1500) { + OddJob2On = 3; + } + break; + case 3: + fStep = Max(30.0f, OddJob2XOffset / 5.0f); + + OddJob2XOffset = OddJob2XOffset - fStep; + + if (OddJob2XOffset < -380.0f) { + OddJob2OffTimer = 5000.0f; + OddJob2On = 0; + } + break; + default: + break; + } + + if (!m_BigMessage[1][0]) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(1.0f), SCREEN_SCALE_Y(1.2f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + // Not bug, we just want these kind of texts to be wrapped at the center. +#ifdef ASPECT_RATIO_SCALE + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH - 20.0f)); +#else + CFont::SetCentreSize(SCREEN_SCALE_FROM_RIGHT(20.0f)); +#endif + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::SetFontStyle(FONT_LOCALE(FONT_BANK)); + +#ifdef BETA_SLIDING_TEXT + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X_PC(2.0f) - SCREEN_SCALE_X(OddJob2XOffset), SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(20.0f) + SCREEN_SCALE_Y_PC(2.0f), m_BigMessage[5]); + CFont::SetColor(ODDJOB2_COLOR); + CFont::PrintString(SCREEN_WIDTH / 2 - SCREEN_SCALE_X(OddJob2XOffset), SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(20.0f), m_BigMessage[5]); +#else + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X_PC(2.0f), SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(20.0f) + SCREEN_SCALE_Y_PC(2.0f), m_BigMessage[5]); + CFont::SetColor(ODDJOB2_COLOR); + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(20.0f), m_BigMessage[5]); +#endif + } + } + + /* + DrawMissionTitle + */ + if (m_BigMessage[1][0]) { + if (BigMessageInUse[1] != 0.0f) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + + if (CGame::frenchGame || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + CFont::SetScale(SCREEN_SCALE_X_PC(0.884f), SCREEN_SCALE_Y_PC(1.36f)); + else + CFont::SetScale(SCREEN_SCALE_X_PC(1.04f), SCREEN_SCALE_Y_PC(1.6f)); + + CFont::SetPropOn(); +#ifdef FIX_BUGS + CFont::SetRightJustifyWrap(SCREEN_SCALE_FROM_RIGHT(DEFAULT_SCREEN_WIDTH + 500.0f)); +#else + CFont::SetRightJustifyWrap(-500.0f); +#endif + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + + if (BigMessageX[1] >= SCREEN_WIDTH - SCREEN_SCALE_X_FIX(20.0f)) + { + BigMessageInUse[1] += CTimer::GetTimeStep(); + + if (BigMessageInUse[1] >= 120.0f) { + BigMessageInUse[1] = 120.0f; + BigMessageAlpha[1] -= (CTimer::GetTimeStepInMilliseconds() * 0.3f); + } + if (BigMessageAlpha[1] <= 0) { + m_BigMessage[1][0] = 0; + BigMessageAlpha[1] = 0.0f; + } + } else { + BigMessageX[1] += SCREEN_SCALE_X_FIX(CTimer::GetTimeStepInMilliseconds() * 0.3f); + BigMessageAlpha[1] += (CTimer::GetTimeStepInMilliseconds() * 0.3f); + + if (BigMessageAlpha[1] > 255.0f) + BigMessageAlpha[1] = 255.0f; + } + + CFont::SetColor(CRGBA(40, 40, 40, BigMessageAlpha[1])); +#ifdef BETA_SLIDING_TEXT + CFont::PrintString(SCREEN_SCALE_X(2.0f) + BigMessageX[1], SCREEN_SCALE_FROM_BOTTOM(120.0f) + SCREEN_SCALE_Y_PC(2.0f), m_BigMessage[1]); + CFont::SetColor(CRGBA(MISSIONTITLE_COLOR.r, MISSIONTITLE_COLOR.g, MISSIONTITLE_COLOR.b, BigMessageAlpha[1])); + CFont::PrintString(BigMessageX[1], SCREEN_SCALE_FROM_BOTTOM(120.0f), m_BigMessage[1]); +#else + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f) + SCREEN_SCALE_X_FIX(2.0f), SCREEN_SCALE_FROM_BOTTOM(120.0f) + SCREEN_SCALE_Y_PC(2.0f), m_BigMessage[1]); + CFont::SetColor(CRGBA(MISSIONTITLE_COLOR.r, MISSIONTITLE_COLOR.g, MISSIONTITLE_COLOR.b, BigMessageAlpha[1])); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(20.0f), SCREEN_SCALE_FROM_BOTTOM(120.0f), m_BigMessage[1]); +#endif + } + else { + BigMessageAlpha[1] = 0.0f; +#ifdef FIX_BUGS + BigMessageX[1] = SCREEN_SCALE_FROM_RIGHT(DEFAULT_SCREEN_WIDTH + 60.0f); +#else + BigMessageX[1] = -60.0f; +#endif + BigMessageInUse[1] = 1.0f; + } + } + else { + BigMessageInUse[1] = 0.0f; + } +} + +void CHud::SetMessage(wchar *message) +{ + int i = 0; + for (i = 0; i < ARRAY_SIZE(m_Message); i++) { + if (message[i] == 0) + break; + + m_Message[i] = message[i]; + } + m_Message[i] = 0; +} + +void CHud::SetBigMessage(wchar *message, uint16 style) +{ + int i = 0; + + if (style == 5) { + for (i = 0; i < 128; i++) { + if (message[i] == 0) + break; + + if (message[i] != LastBigMessage[5][i]) { + OddJob2On = 0; + OddJob2OffTimer = 0.0f; + } + + m_BigMessage[5][i] = message[i]; + LastBigMessage[5][i] = message[i]; + } + } else { + for (i = 0; i < 128; i++) { + if (message[i] == 0) + break; + m_BigMessage[style][i] = message[i]; + } + } + LastBigMessage[style][i] = 0; + m_BigMessage[style][i] = 0; +#ifndef FIX_BUGS + m_BigMessage[style][i] = 0; +#endif +} + +void CHud::SetPagerMessage(wchar *message) +{ + int i = 0; + for (i = 0; i < ARRAY_SIZE(m_PagerMessage); i++) { + if (message[i] == 0) + break; + + m_PagerMessage[i] = message[i]; + } + m_PagerMessage[i] = 0; +} \ No newline at end of file diff --git a/src/renderer/Hud.h b/src/renderer/Hud.h new file mode 100644 index 0000000..adfdf1f --- /dev/null +++ b/src/renderer/Hud.h @@ -0,0 +1,81 @@ +#pragma once +#include "Sprite2d.h" + +#define HELP_MSG_LENGTH 256 + +enum eItems +{ + ITEM_NONE = -1, + ITEM_ARMOUR = 3, + ITEM_HEALTH = 4, + ITEM_RADAR = 8 +}; + +enum eSprites +{ + HUD_FIST, + HUD_BAT, + HUD_PISTOL, + HUD_UZI, + HUD_SHOTGUN, + HUD_AK47, + HUD_M16, + HUD_SNIPER, + HUD_ROCKET, + HUD_FLAME, + HUD_MOLOTOV, + HUD_GRENADE, + HUD_DETONATOR, + HUD_RADARDISC = 15, + HUD_PAGER = 16, + HUD_SITESNIPER = 20, + HUD_SITEM16, + HUD_SITEROCKET, + NUM_HUD_SPRITES, +}; + +class CHud +{ +public: + static int16 m_ItemToFlash; + static CSprite2d Sprites[NUM_HUD_SPRITES]; + static wchar *m_pZoneName; + static wchar *m_pLastZoneName; + static wchar *m_ZoneToPrint; + static wchar m_Message[256]; + static wchar m_BigMessage[6][128]; + static wchar m_PagerMessage[256]; + static uint32 m_ZoneNameTimer; + static int32 m_ZoneFadeTimer; + static uint32 m_ZoneState; + static wchar m_HelpMessage[HELP_MSG_LENGTH]; + static wchar m_LastHelpMessage[HELP_MSG_LENGTH]; + static wchar m_HelpMessageToPrint[HELP_MSG_LENGTH]; + static uint32 m_HelpMessageTimer; + static int32 m_HelpMessageFadeTimer; + static uint32 m_HelpMessageState; + static bool m_HelpMessageQuick; + static float m_HelpMessageDisplayTime; + static int32 SpriteBrightness; + static bool m_Wants_To_Draw_Hud; + static bool m_Wants_To_Draw_3dMarkers; + static wchar *m_pVehicleName; + static wchar *m_pLastVehicleName; + static uint32 m_VehicleNameTimer; + static int32 m_VehicleFadeTimer; + static uint32 m_VehicleState; + static wchar *m_pVehicleNameToPrint; +public: + static void Initialise(); + static void Shutdown(); + static void ReInitialise(); + static void GetRidOfAllHudMessages(); + static void SetZoneName(wchar *name); + static void SetHelpMessage(wchar *message, bool quick); + static void SetVehicleName(wchar *name); + static void Draw(); + static void DrawAfterFade(); + static void SetMessage(wchar *message); + static void SetBigMessage(wchar *message, uint16 style); + static void SetPagerMessage(wchar *message); +}; diff --git a/src/renderer/Instance.cpp b/src/renderer/Instance.cpp new file mode 100644 index 0000000..be6d73d --- /dev/null +++ b/src/renderer/Instance.cpp @@ -0,0 +1,9 @@ +#include "common.h" + +#include "Instance.h" + +void +CInstance::Shutdown() +{ + GetMatrix().Detach(); +} diff --git a/src/renderer/Instance.h b/src/renderer/Instance.h new file mode 100644 index 0000000..693cfdf --- /dev/null +++ b/src/renderer/Instance.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Placeable.h" + +// unused + +class CInstance : public CPlaceable +{ +public: + int m_modelIndex; +public: + ~CInstance() { } + void Shutdown(); +}; diff --git a/src/renderer/Lines.cpp b/src/renderer/Lines.cpp new file mode 100644 index 0000000..b5c8514 --- /dev/null +++ b/src/renderer/Lines.cpp @@ -0,0 +1,74 @@ +#include "common.h" + +#include "main.h" +#include "Lines.h" + +// This is super inefficient, why split the line into segments at all? +void +CLines::RenderLineWithClipping(float x1, float y1, float z1, float x2, float y2, float z2, uint32 c1, uint32 c2) +{ + static RwIm3DVertex v[2]; +#ifdef THIS_IS_STUPID + int i; + float f1, f2; + float len = sqrt(sq(x1-x2) + sq(y1-y2) + sq(z1-z2)); + int numsegs = len/1.5f + 1.0f; + + RwRGBA col1; + col1.red = c1>>24; + col1.green = c1>>16; + col1.blue = c1>>8; + col1.alpha = c1; + RwRGBA col2; + col2.red = c2>>24; + col2.green = c2>>16; + col2.blue = c2>>8; + col2.alpha = c2; + + float dx = x2 - x1; + float dy = y2 - y1; + float dz = z2 - z1; + for(i = 0; i < numsegs; i++){ + f1 = (float)i/numsegs; + f2 = (float)(i+1)/numsegs; + + RwIm3DVertexSetRGBA(&v[0], (int)(col1.red + (col2.red-col1.red)*f1), + (int)(col1.green + (col2.green-col1.green)*f1), + (int)(col1.blue + (col2.blue-col1.blue)*f1), + (int)(col1.alpha + (col2.alpha-col1.alpha)*f1)); + RwIm3DVertexSetRGBA(&v[1], (int)(col1.red + (col2.red-col1.red)*f2), + (int)(col1.green + (col2.green-col1.green)*f2), + (int)(col1.blue + (col2.blue-col1.blue)*f2), + (int)(col1.alpha + (col2.alpha-col1.alpha)*f2)); + RwIm3DVertexSetPos(&v[0], x1 + dx*f1, y1 + dy*f1, z1 + dz*f1); + RwIm3DVertexSetPos(&v[1], x1 + dx*f2, y1 + dy*f2, z1 + dz*f2); + + LittleTest(); + if(RwIm3DTransform(v, 2, nil, 0)){ + RwIm3DRenderLine(0, 1); + RwIm3DEnd(); + } + } +#else + RwRGBA col1; + col1.red = c1>>24; + col1.green = c1>>16; + col1.blue = c1>>8; + col1.alpha = c1; + RwRGBA col2; + col2.red = c2>>24; + col2.green = c2>>16; + col2.blue = c2>>8; + col2.alpha = c2; + + RwIm3DVertexSetRGBA(&v[0], col1.red, col1.green, col1.blue, col1.alpha); + RwIm3DVertexSetRGBA(&v[1], col2.red, col2.green, col2.blue, col2.alpha); + RwIm3DVertexSetPos(&v[0], x1, y1, z1); + RwIm3DVertexSetPos(&v[1], x2, y2, z2); + LittleTest(); + if(RwIm3DTransform(v, 2, nil, 0)){ + RwIm3DRenderLine(0, 1); + RwIm3DEnd(); + } +#endif +} diff --git a/src/renderer/Lines.h b/src/renderer/Lines.h new file mode 100644 index 0000000..f2694fc --- /dev/null +++ b/src/renderer/Lines.h @@ -0,0 +1,7 @@ +#pragma once + +class CLines +{ +public: + static void RenderLineWithClipping(float x1, float y1, float z1, float x2, float y2, float z2, uint32 c1, uint32 c2); +}; diff --git a/src/renderer/MBlur.cpp b/src/renderer/MBlur.cpp new file mode 100644 index 0000000..8e5fba2 --- /dev/null +++ b/src/renderer/MBlur.cpp @@ -0,0 +1,325 @@ +#ifndef LIBRW +#define WITHD3D +#endif +#include "common.h" +#ifndef LIBRW +#include +#endif + +#include "main.h" +#include "RwHelper.h" +#include "Camera.h" +#include "MBlur.h" +#include "postfx.h" + +// Originally taken from RW example 'mblur' + +RwRaster *CMBlur::pFrontBuffer; +bool CMBlur::ms_bJustInitialised; +bool CMBlur::ms_bScaledBlur; +bool CMBlur::BlurOn; + +static RwIm2DVertex Vertex[4]; +static RwImVertexIndex Index[6] = { 0, 1, 2, 0, 2, 3 }; + +#ifndef LIBRW +extern "C" D3DCAPS8 _RwD3D8DeviceCaps; +#endif +RwBool +CMBlur::MotionBlurOpen(RwCamera *cam) +{ +#ifdef EXTENDED_COLOURFILTER + CPostFX::Open(cam); + return TRUE; +#else +#ifdef GTA_PS2 + RwRect rect = {0, 0, 0, 0}; + + if (pFrontBuffer) + return TRUE; + + BlurOn = true; + + rect.w = RwRasterGetWidth(RwCameraGetRaster(cam)); + rect.h = RwRasterGetHeight(RwCameraGetRaster(cam)); + + pFrontBuffer = RwRasterCreate(0, 0, 0, rwRASTERDONTALLOCATE|rwRASTERTYPECAMERATEXTURE); + if (!pFrontBuffer) + { + printf("Error creating raster\n"); + return FALSE; + } + + RwRaster *raster = RwRasterSubRaster(pFrontBuffer, RwCameraGetRaster(cam), &rect); + if (!raster) + { + RwRasterDestroy(pFrontBuffer); + pFrontBuffer = NULL; + printf("Error subrastering\n"); + return FALSE; + } + + CreateImmediateModeData(cam, &rect); +#else + RwRect rect = { 0, 0, 0, 0 }; + + if(pFrontBuffer) + MotionBlurClose(); + +#ifndef LIBRW + extern void _GetVideoMemInfo(LPDWORD total, LPDWORD avaible); + DWORD total, avaible; + + _GetVideoMemInfo(&total, &avaible); + debug("Available video memory %d\n", avaible); +#endif + + if(BlurOn) + { + uint32 width = Pow(2.0f, int32(log2(RwRasterGetWidth (RwCameraGetRaster(cam))))+1); + uint32 height = Pow(2.0f, int32(log2(RwRasterGetHeight(RwCameraGetRaster(cam))))+1); + uint32 depth = RwRasterGetDepth(RwCameraGetRaster(cam)); + +#ifndef LIBRW + extern DWORD _dwMemTotalVideo; + if ( _RwD3D8DeviceCaps.MaxTextureWidth >= width && _RwD3D8DeviceCaps.MaxTextureHeight >= height ) + { + total = _dwMemTotalVideo - 3 * + ( RwRasterGetDepth(RwCameraGetRaster(cam)) + * RwRasterGetHeight(RwCameraGetRaster(cam)) + * RwRasterGetWidth(RwCameraGetRaster(cam)) / 8 ); + BlurOn = total >= height*width*(depth/8) + (12*1024*1024) /*12 MB*/; + } + else + BlurOn = false; +#endif + + if ( BlurOn ) + { + ms_bScaledBlur = false; + rect.w = width; + rect.h = height; + + pFrontBuffer = RwRasterCreate(rect.w, rect.h, depth, rwRASTERTYPECAMERATEXTURE); + if ( !pFrontBuffer ) + { + debug("MBlurOpen can't create raster."); + BlurOn = false; + rect.w = RwRasterGetWidth(RwCameraGetRaster(cam)); + rect.h = RwRasterGetHeight(RwCameraGetRaster(cam)); + } + else + ms_bJustInitialised = true; + } + else + { + rect.w = RwRasterGetWidth(RwCameraGetRaster(cam)); + rect.h = RwRasterGetHeight(RwCameraGetRaster(cam)); + } + +#ifndef LIBRW + _GetVideoMemInfo(&total, &avaible); + debug("Available video memory %d\n", avaible); +#endif + CreateImmediateModeData(cam, &rect); + } + else + { + rect.w = RwRasterGetWidth(RwCameraGetRaster(cam)); + rect.h = RwRasterGetHeight(RwCameraGetRaster(cam)); + CreateImmediateModeData(cam, &rect); + } + + return TRUE; +#endif +#endif +} + +RwBool +CMBlur::MotionBlurClose(void) +{ +#ifdef EXTENDED_COLOURFILTER + CPostFX::Close(); +#else + if(pFrontBuffer){ + RwRasterDestroy(pFrontBuffer); + pFrontBuffer = nil; + + return TRUE; + } +#endif + return FALSE; +} + +void +CMBlur::CreateImmediateModeData(RwCamera *cam, RwRect *rect) +{ + float zero, xmax, ymax; + + if(RwRasterGetDepth(RwCameraGetRaster(cam)) == 16){ + zero = HALFPX; + xmax = rect->w + HALFPX; + ymax = rect->h + HALFPX; + }else{ + zero = -HALFPX; + xmax = rect->w - HALFPX; + ymax = rect->h - HALFPX; + } + + RwIm2DVertexSetScreenX(&Vertex[0], zero); + RwIm2DVertexSetScreenY(&Vertex[0], zero); + RwIm2DVertexSetScreenZ(&Vertex[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[0], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[0], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[1], zero); + RwIm2DVertexSetScreenY(&Vertex[1], ymax); + RwIm2DVertexSetScreenZ(&Vertex[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[1], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[1], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[1], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[2], xmax); + RwIm2DVertexSetScreenY(&Vertex[2], ymax); + RwIm2DVertexSetScreenZ(&Vertex[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[2], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[2], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX(&Vertex[3], xmax); + RwIm2DVertexSetScreenY(&Vertex[3], zero); + RwIm2DVertexSetScreenZ(&Vertex[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&Vertex[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&Vertex[3], 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetU(&Vertex[3], 1.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetV(&Vertex[3], 0.0f, 1.0f/RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, 255); +} + +void +CMBlur::MotionBlurRender(RwCamera *cam, uint32 red, uint32 green, uint32 blue, uint32 blur, int32 type, uint32 bluralpha) +{ +#ifdef EXTENDED_COLOURFILTER + CPostFX::Render(cam, red, green, blue, blur, type, bluralpha); +#else + PUSH_RENDERGROUP("CMBlur::MotionBlurRender"); + RwRGBA color = { (RwUInt8)red, (RwUInt8)green, (RwUInt8)blue, (RwUInt8)blur }; +#ifdef GTA_PS2 + if( pFrontBuffer ) + OverlayRender(cam, pFrontBuffer, color, type, bluralpha); +#else + if(BlurOn){ + if(pFrontBuffer){ + if(ms_bJustInitialised) + ms_bJustInitialised = false; + else + OverlayRender(cam, pFrontBuffer, color, type, bluralpha); + } + RwRasterPushContext(pFrontBuffer); + RwRasterRenderFast(RwCameraGetRaster(cam), 0, 0); + RwRasterPopContext(); + }else{ + OverlayRender(cam, nil, color, type, bluralpha); + } +#endif + POP_RENDERGROUP(); +#endif +} + +void +CMBlur::OverlayRender(RwCamera *cam, RwRaster *raster, RwRGBA color, int32 type, int32 bluralpha) +{ + int r, g, b, a; + + r = color.red; + g = color.green; + b = color.blue; + a = color.alpha; + + DefinedState(); + + switch(type) + { + case MOTION_BLUR_SECURITY_CAM: + r = 0; + g = 255; + b = 0; + a = 128; + break; + case MOTION_BLUR_INTRO: + r = 100; + g = 220; + b = 230; + a = 158; + break; + case MOTION_BLUR_INTRO2: + r = 80; + g = 255; + b = 230; + a = 138; + break; + case MOTION_BLUR_INTRO3: + r = 255; + g = 60; + b = 60; + a = 200; + break; + case MOTION_BLUR_INTRO4: + r = 255; + g = 180; + b = 180; + a = 128; + break; + } + + if(!BlurOn){ + r = Min(r*0.6f, 255.0f); + g = Min(g*0.6f, 255.0f); + b = Min(b*0.6f, 255.0f); + if(type != MOTION_BLUR_SNIPER) + a = Min(a*0.6f, 255.0f); + // game clamps to 255 here, but why? + } + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, a); + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, BlurOn ? raster : nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); + + a = bluralpha/2; + if(a < 30) + a = 30; + + if(BlurOn && a != 0){ // the second condition should always be true + RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, a); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); + } + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +} diff --git a/src/renderer/MBlur.h b/src/renderer/MBlur.h new file mode 100644 index 0000000..e2e5d38 --- /dev/null +++ b/src/renderer/MBlur.h @@ -0,0 +1,17 @@ +#pragma once + +class CMBlur +{ +public: + static RwRaster *pFrontBuffer; + static bool ms_bJustInitialised; + static bool ms_bScaledBlur; + static bool BlurOn; + +public: + static RwBool MotionBlurOpen(RwCamera *cam); + static RwBool MotionBlurClose(void); + static void CreateImmediateModeData(RwCamera *cam, RwRect *rect); + static void MotionBlurRender(RwCamera *cam, uint32 red, uint32 green, uint32 blue, uint32 blur, int32 type, uint32 bluralpha); + static void OverlayRender(RwCamera *cam, RwRaster *raster, RwRGBA color, int32 type, int32 bluralpha); +}; diff --git a/src/renderer/Particle.cpp b/src/renderer/Particle.cpp new file mode 100644 index 0000000..76ddde5 --- /dev/null +++ b/src/renderer/Particle.cpp @@ -0,0 +1,1902 @@ +#include "common.h" + +#include "main.h" +#include "General.h" +#include "Timer.h" +#include "TxdStore.h" +#include "Entity.h" +#include "Sprite.h" +#include "Camera.h" +#include "Collision.h" +#include "World.h" +#include "Shadows.h" +#include "AudioScriptObject.h" +#include "ParticleObject.h" +#include "Particle.h" +#include "soundlist.h" +#include "debugmenu.h" + + +#define MAX_PARTICLES_ON_SCREEN (1000) + + +//(5) +#define MAX_SMOKE_FILES ARRAY_SIZE(SmokeFiles) + +//(5) +#define MAX_SMOKE2_FILES ARRAY_SIZE(Smoke2Files) +//(5) +#define MAX_RUBBER_FILES ARRAY_SIZE(RubberFiles) +//(5) +#define MAX_RAINSPLASH_FILES ARRAY_SIZE(RainSplashFiles) +//(3) +#define MAX_WATERSPRAY_FILES ARRAY_SIZE(WatersprayFiles) +//(6) +#define MAX_EXPLOSIONMEDIUM_FILES ARRAY_SIZE(ExplosionMediumFiles) +//(4) +#define MAX_GUNFLASH_FILES ARRAY_SIZE(GunFlashFiles) +//(2) +#define MAX_RAINSPLASHUP_FILES ARRAY_SIZE(RainSplashupFiles) +//(4) +#define MAX_BIRDFRONT_FILES ARRAY_SIZE(BirdfrontFiles) +//(4) +#define MAX_CARDEBRIS_FILES ARRAY_SIZE(CardebrisFiles) +//(4) +#define MAX_CARSPLASH_FILES ARRAY_SIZE(CarsplashFiles) + +//(4) +#define MAX_RAINDROP_FILES ARRAY_SIZE(RaindropFiles) + + + +const char SmokeFiles[][6+1] = +{ + "smoke1", + "smoke2", + "smoke3", + "smoke4", + "smoke5" +}; + + +const char Smoke2Files[][9+1] = +{ + "smokeII_1", + "smokeII_2", + "smokeII_3", + "smokeII_4", + "smokeII_5" +}; + +const char RubberFiles[][7+1] = +{ + "rubber1", + "rubber2", + "rubber3", + "rubber4", + "rubber5" +}; + +const char RainSplashFiles[][7+1] = +{ + "splash1", + "splash2", + "splash3", + "splash4", + "splash5" +}; + +const char WatersprayFiles[][11+1] = +{ + "waterspray1", + "waterspray2", + "waterspray3" +}; + +const char ExplosionMediumFiles[][7+1] = +{ + "explo01", + "explo02", + "explo03", + "explo04", + "explo05", + "explo06" +}; + +const char GunFlashFiles[][9+1] = +{ + "gunflash1", + "gunflash2", + "gunflash3", + "gunflash4" +}; + +const char RaindropFiles[][9+1] = +{ + "raindrop1", + "raindrop2", + "raindrop3", + "raindrop4" +}; + +const char RainSplashupFiles[][10+1] = +{ + "splash_up1", + "splash_up2" +}; + +const char BirdfrontFiles[][8+1] = +{ + "birdf_01", + "birdf_02", + "birdf_03", + "birdf_04" +}; + +const char CardebrisFiles[][12+1] = +{ + "cardebris_01", + "cardebris_02", + "cardebris_03", + "cardebris_04" +}; + +const char CarsplashFiles[][12+1] = +{ + "carsplash_01", + "carsplash_02", + "carsplash_03", + "carsplash_04" +}; + +CParticle gParticleArray[MAX_PARTICLES_ON_SCREEN]; + +RwTexture *gpSmokeTex[MAX_SMOKE_FILES]; +RwTexture *gpSmoke2Tex[MAX_SMOKE2_FILES]; +RwTexture *gpRubberTex[MAX_RUBBER_FILES]; +RwTexture *gpRainSplashTex[MAX_RAINSPLASH_FILES]; +RwTexture *gpWatersprayTex[MAX_WATERSPRAY_FILES]; +RwTexture *gpExplosionMediumTex[MAX_EXPLOSIONMEDIUM_FILES]; +RwTexture *gpGunFlashTex[MAX_GUNFLASH_FILES]; +RwTexture *gpRainSplashupTex[MAX_RAINSPLASHUP_FILES]; +RwTexture *gpBirdfrontTex[MAX_BIRDFRONT_FILES]; +RwTexture *gpCarDebrisTex[MAX_CARDEBRIS_FILES]; +RwTexture *gpCarSplashTex[MAX_CARSPLASH_FILES]; + +RwTexture *gpFlame1Tex; +RwTexture *gpFlame5Tex; +RwTexture *gpRainDropSmallTex; +RwTexture *gpBloodTex; +RwTexture *gpLeafTex; +RwTexture *gpCloudTex1; // unused +RwTexture *gpCloudTex4; +RwTexture *gpBloodSmallTex; +RwTexture *gpGungeTex; +RwTexture *gpCollisionSmokeTex; +RwTexture *gpBulletHitTex; +RwTexture *gpGunShellTex; +RwTexture *gpWakeOldTex; +RwTexture *gpPointlightTex; + +RwRaster *gpSmokeRaster[MAX_SMOKE_FILES]; +RwRaster *gpSmoke2Raster[MAX_SMOKE2_FILES]; +RwRaster *gpRubberRaster[MAX_RUBBER_FILES]; +RwRaster *gpRainSplashRaster[MAX_RAINSPLASH_FILES]; +RwRaster *gpWatersprayRaster[MAX_WATERSPRAY_FILES]; +RwRaster *gpExplosionMediumRaster[MAX_EXPLOSIONMEDIUM_FILES]; +RwRaster *gpGunFlashRaster[MAX_GUNFLASH_FILES]; +RwRaster *gpRainSplashupRaster[MAX_RAINSPLASHUP_FILES]; +RwRaster *gpBirdfrontRaster[MAX_BIRDFRONT_FILES]; +RwRaster *gpCarDebrisRaster[MAX_CARDEBRIS_FILES]; +RwRaster *gpCarSplashRaster[MAX_CARSPLASH_FILES]; + +RwRaster *gpFlame1Raster; +RwRaster *gpFlame5Raster; +RwRaster *gpRainDropSmallRaster; +RwRaster *gpBloodRaster; +RwRaster *gpLeafRaster; +RwRaster *gpCloudRaster1; // unused +RwRaster *gpCloudRaster4; +RwRaster *gpBloodSmallRaster; +RwRaster *gpGungeRaster; +RwRaster *gpCollisionSmokeRaster; +RwRaster *gpBulletHitRaster; +RwRaster *gpGunShellRaster; +RwRaster *gpWakeOldRaster; + + +RwRaster *gpPointlightRaster; // CPointLights::RenderFogEffect + +RwTexture *gpRainDropTex[MAX_RAINDROP_FILES]; // CWeather::RenderRainStreaks + + +RwRaster *gpRainDropRaster[MAX_RAINDROP_FILES]; + +float CParticle::ms_afRandTable[CParticle::RAND_TABLE_SIZE]; + + +CParticle *CParticle::m_pUnusedListHead; + + +float CParticle::m_SinTable[CParticle::SIN_COS_TABLE_SIZE]; +float CParticle::m_CosTable[CParticle::SIN_COS_TABLE_SIZE]; + +int32 Randomizer; + +int32 nParticleCreationInterval = 1; +float fParticleScaleLimit = 0.5f; + +#ifdef DEBUGMENU +SETTWEAKPATH("Particle"); +TWEAKINT32(nParticleCreationInterval, 0, 5, 1); +TWEAKFLOAT(fParticleScaleLimit, 0.0f, 1.0f, 0.1f); +TWEAKFUNC(CParticle::ReloadConfig); +#endif + +void CParticle::ReloadConfig() +{ + debug("Initialising CParticleMgr..."); + + mod_ParticleSystemManager.Initialise(); + + debug("Initialising CParticle..."); + + m_pUnusedListHead = gParticleArray; + + for ( int32 i = 0; i < MAX_PARTICLES_ON_SCREEN; i++ ) + { + if ( i == MAX_PARTICLES_ON_SCREEN - 1 ) + gParticleArray[i].m_pNext = nil; + else + gParticleArray[i].m_pNext = &gParticleArray[i + 1]; + + gParticleArray[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + + gParticleArray[i].m_vecVelocity = CVector(0.0f, 0.0f, 0.0f); + + gParticleArray[i].m_nTimeWhenWillBeDestroyed = 0; + + gParticleArray[i].m_nTimeWhenColorWillBeChanged = 0; + + gParticleArray[i].m_fSize = 0.2f; + + gParticleArray[i].m_fExpansionRate = 0.0f; + + gParticleArray[i].m_nColorIntensity = 255; + + gParticleArray[i].m_nFadeToBlackTimer = 0; + + gParticleArray[i].m_nAlpha = 255; + + gParticleArray[i].m_nFadeAlphaTimer = 0; + + gParticleArray[i].m_nCurrentZRotation = 0; + + gParticleArray[i].m_nZRotationTimer = 0; + + gParticleArray[i].m_fCurrentZRadius = 0.0f; + + gParticleArray[i].m_nZRadiusTimer = 0; + + gParticleArray[i].m_nCurrentFrame = 0; + + gParticleArray[i].m_nAnimationSpeedTimer = 0; + + gParticleArray[i].m_nRotation = 0; + + gParticleArray[i].m_nRotationStep = 0; + } +} + +void CParticle::Initialise() +{ + ReloadConfig(); + + CParticleObject::Initialise(); + + float randVal = -1.0f; + for ( int32 i = 0; i < RAND_TABLE_SIZE; i++ ) + { + ms_afRandTable[i] = randVal; + randVal += 0.1f; + } + + for ( int32 i = 0; i < SIN_COS_TABLE_SIZE; i++ ) + { + float angle = DEGTORAD(float(i) * float(360.0f / SIN_COS_TABLE_SIZE)); + + m_SinTable[i] = ::Sin(angle); + m_CosTable[i] = ::Cos(angle); + } + + int32 slot = CTxdStore::FindTxdSlot("particle"); + + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(slot); + + for ( int32 i = 0; i < MAX_SMOKE_FILES; i++ ) + { + gpSmokeTex[i] = RwTextureRead(SmokeFiles[i], nil); + gpSmokeRaster[i] = RwTextureGetRaster(gpSmokeTex[i]); + } + + for ( int32 i = 0; i < MAX_SMOKE2_FILES; i++ ) + { + gpSmoke2Tex[i] = RwTextureRead(Smoke2Files[i], nil); + gpSmoke2Raster[i] = RwTextureGetRaster(gpSmoke2Tex[i]); + } + + for ( int32 i = 0; i < MAX_RUBBER_FILES; i++ ) + { + gpRubberTex[i] = RwTextureRead(RubberFiles[i], nil); + gpRubberRaster[i] = RwTextureGetRaster(gpRubberTex[i]); + } + + for ( int32 i = 0; i < MAX_RAINSPLASH_FILES; i++ ) + { + gpRainSplashTex[i] = RwTextureRead(RainSplashFiles[i], nil); + gpRainSplashRaster[i] = RwTextureGetRaster(gpRainSplashTex[i]); + } + + for ( int32 i = 0; i < MAX_WATERSPRAY_FILES; i++ ) + { + gpWatersprayTex[i] = RwTextureRead(WatersprayFiles[i], nil); + gpWatersprayRaster[i] = RwTextureGetRaster(gpWatersprayTex[i]); + } + + for ( int32 i = 0; i < MAX_EXPLOSIONMEDIUM_FILES; i++ ) + { + gpExplosionMediumTex[i] = RwTextureRead(ExplosionMediumFiles[i], nil); + gpExplosionMediumRaster[i] = RwTextureGetRaster(gpExplosionMediumTex[i]); + } + + for ( int32 i = 0; i < MAX_GUNFLASH_FILES; i++ ) + { + gpGunFlashTex[i] = RwTextureRead(GunFlashFiles[i], NULL); + gpGunFlashRaster[i] = RwTextureGetRaster(gpGunFlashTex[i]); + } + + for ( int32 i = 0; i < MAX_RAINDROP_FILES; i++ ) + { + gpRainDropTex[i] = RwTextureRead(RaindropFiles[i], nil); + gpRainDropRaster[i] = RwTextureGetRaster(gpRainDropTex[i]); + } + + for ( int32 i = 0; i < MAX_RAINSPLASHUP_FILES; i++ ) + { + gpRainSplashupTex[i] = RwTextureRead(RainSplashupFiles[i], nil); + gpRainSplashupRaster[i] = RwTextureGetRaster(gpRainSplashupTex[i]); + } + + for ( int32 i = 0; i < MAX_BIRDFRONT_FILES; i++ ) + { + gpBirdfrontTex[i] = RwTextureRead(BirdfrontFiles[i], NULL); + gpBirdfrontRaster[i] = RwTextureGetRaster(gpBirdfrontTex[i]); + } + + for ( int32 i = 0; i < MAX_CARDEBRIS_FILES; i++ ) + { + gpCarDebrisTex[i] = RwTextureRead(CardebrisFiles[i], nil); + gpCarDebrisRaster[i] = RwTextureGetRaster(gpCarDebrisTex[i]); + } + + for ( int32 i = 0; i < MAX_CARSPLASH_FILES; i++ ) + { + gpCarSplashTex[i] = RwTextureRead(CarsplashFiles[i], nil); + gpCarSplashRaster[i] = RwTextureGetRaster(gpCarSplashTex[i]); + } + + gpFlame1Tex = RwTextureRead("flame1", NULL); + gpFlame1Raster = RwTextureGetRaster(gpFlame1Tex); + + gpFlame5Tex = RwTextureRead("flame5", nil); + +//#ifdef FIX_BUGS +#if 0 + gpFlame5Raster = RwTextureGetRaster(gpFlame5Tex); +#else + // this seems to have become more of a design choice + gpFlame5Raster = RwTextureGetRaster(gpFlame1Tex); // copy-paste bug ? +#endif + + gpRainDropSmallTex = RwTextureRead("rainsmall", nil); + gpRainDropSmallRaster = RwTextureGetRaster(gpRainDropSmallTex); + + gpBloodTex = RwTextureRead("blood", nil); + gpBloodRaster = RwTextureGetRaster(gpBloodTex); + + gpLeafTex = RwTextureRead("gameleaf01_64", nil); + gpLeafRaster = RwTextureGetRaster(gpLeafTex); + + gpCloudTex1 = RwTextureRead("cloud3", nil); + gpCloudRaster1 = RwTextureGetRaster(gpCloudTex1); + + gpCloudTex4 = RwTextureRead("cloudmasked", nil); + gpCloudRaster4 = RwTextureGetRaster(gpCloudTex4); + + gpBloodSmallTex = RwTextureRead("bloodsplat2", nil); + gpBloodSmallRaster = RwTextureGetRaster(gpBloodSmallTex); + + gpGungeTex = RwTextureRead("gunge", nil); + gpGungeRaster = RwTextureGetRaster(gpGungeTex); + + gpCollisionSmokeTex = RwTextureRead("collisionsmoke", nil); + gpCollisionSmokeRaster = RwTextureGetRaster(gpCollisionSmokeTex); + + gpBulletHitTex = RwTextureRead("bullethitsmoke", nil); + gpBulletHitRaster = RwTextureGetRaster(gpBulletHitTex); + + gpGunShellTex = RwTextureRead("gunshell", nil); + gpGunShellRaster = RwTextureGetRaster(gpGunShellTex); + + gpWakeOldTex = RwTextureRead("wake_old", nil); + gpWakeOldRaster = RwTextureGetRaster(gpWakeOldTex); + + gpPointlightTex = RwTextureRead("pointlight", nil); + gpPointlightRaster = RwTextureGetRaster(gpPointlightTex); + + CTxdStore::PopCurrentTxd(); + + for ( int32 i = 0; i < MAX_PARTICLES; i++ ) + { + tParticleSystemData *entry = &mod_ParticleSystemManager.m_aParticles[i]; + + switch ( i ) + { + case PARTICLE_BLOOD: + entry->m_ppRaster = &gpBloodRaster; + break; + + case PARTICLE_BLOOD_SMALL: + case PARTICLE_BLOOD_SPURT: + entry->m_ppRaster = &gpBloodSmallRaster; + break; + + case PARTICLE_DEBRIS2: + entry->m_ppRaster = &gpGungeRaster; + break; + + case PARTICLE_GUNFLASH: + case PARTICLE_GUNFLASH_NOANIM: + entry->m_ppRaster = gpGunFlashRaster; + break; + + case PARTICLE_GUNSMOKE: + case PARTICLE_SPLASH: + entry->m_ppRaster = nil; + break; + + case PARTICLE_FLAME: + case PARTICLE_CARFLAME: + entry->m_ppRaster = &gpFlame1Raster; + break; + + case PARTICLE_FIREBALL: + entry->m_ppRaster = &gpFlame5Raster; + break; + + case PARTICLE_RAIN_SPLASH: + case PARTICLE_RAIN_SPLASH_BIGGROW: + entry->m_ppRaster = gpRainSplashRaster; + break; + + case PARTICLE_RAIN_SPLASHUP: + entry->m_ppRaster = gpRainSplashupRaster; + break; + + case PARTICLE_WATERSPRAY: + entry->m_ppRaster = gpWatersprayRaster; + break; + + case PARTICLE_SHARD: + case PARTICLE_RAINDROP: + case PARTICLE_RAINDROP_2D: + entry->m_ppRaster = gpRainDropRaster; + break; + + case PARTICLE_EXPLOSION_MEDIUM: + case PARTICLE_EXPLOSION_LARGE: + case PARTICLE_EXPLOSION_MFAST: + case PARTICLE_EXPLOSION_LFAST: + entry->m_ppRaster = gpExplosionMediumRaster; + break; + + case PARTICLE_BOAT_WAKE: + entry->m_ppRaster = &gpWakeOldRaster; + break; + + case PARTICLE_CAR_SPLASH: + case PARTICLE_WATER_HYDRANT: + case PARTICLE_PED_SPLASH: + entry->m_ppRaster = gpCarSplashRaster; + break; + + case PARTICLE_SPARK: + case PARTICLE_SPARK_SMALL: + case PARTICLE_RAINDROP_SMALL: + case PARTICLE_HELI_ATTACK: + entry->m_ppRaster = &gpRainDropSmallRaster; + break; + + case PARTICLE_DEBRIS: + case PARTICLE_TREE_LEAVES: + entry->m_ppRaster = &gpLeafRaster; + break; + + case PARTICLE_CAR_DEBRIS: + case PARTICLE_HELI_DEBRIS: + entry->m_ppRaster = gpCarDebrisRaster; + break; + + case PARTICLE_WHEEL_DIRT: + case PARTICLE_STEAM2: + case PARTICLE_STEAM_NY: + case PARTICLE_STEAM_NY_SLOWMOTION: + case PARTICLE_ENGINE_STEAM: + case PARTICLE_BOAT_THRUSTJET: + case PARTICLE_PEDFOOT_DUST: + case PARTICLE_EXHAUST_FUMES: + entry->m_ppRaster = gpSmoke2Raster; + break; + + case PARTICLE_GUNSMOKE2: + case PARTICLE_RUBBER_SMOKE: + entry->m_ppRaster = gpRubberRaster; + break; + + case PARTICLE_CARCOLLISION_DUST: + case PARTICLE_BURNINGRUBBER_SMOKE: + entry->m_ppRaster = &gpCollisionSmokeRaster; + break; + + case PARTICLE_WHEEL_WATER: + case PARTICLE_WATER: + case PARTICLE_SMOKE: + case PARTICLE_SMOKE_SLOWMOTION: + case PARTICLE_GARAGEPAINT_SPRAY: + case PARTICLE_STEAM: + case PARTICLE_BOAT_SPLASH: + case PARTICLE_WATER_CANNON: + case PARTICLE_EXTINGUISH_STEAM: + case PARTICLE_HELI_DUST: + case PARTICLE_PAINT_SMOKE: + case PARTICLE_BULLETHIT_SMOKE: + entry->m_ppRaster = gpSmokeRaster; + break; + + case PARTICLE_GUNSHELL_FIRST: + case PARTICLE_GUNSHELL: + case PARTICLE_GUNSHELL_BUMP1: + case PARTICLE_GUNSHELL_BUMP2: + entry->m_ppRaster = &gpGunShellRaster; + break; + + case PARTICLE_ENGINE_SMOKE: + case PARTICLE_ENGINE_SMOKE2: + case PARTICLE_CARFLAME_SMOKE: + case PARTICLE_FIREBALL_SMOKE: + case PARTICLE_TEST: + entry->m_ppRaster = &gpCloudRaster4; + break; + + case PARTICLE_BIRD_FRONT: + entry->m_ppRaster = gpBirdfrontRaster; + break; + } + } + + debug("CParticle ready"); +} + +void +CEntity::AddSteamsFromGround(CVector *unused) +{ + int i, n; + C2dEffect *effect; + CVector pos; + + n = CModelInfo::GetModelInfo(GetModelIndex())->GetNum2dEffects(); + for(i = 0; i < n; i++){ + effect = CModelInfo::GetModelInfo(GetModelIndex())->Get2dEffect(i); + if(effect->type != EFFECT_PARTICLE) + continue; + + pos = GetMatrix() * effect->pos; + switch(effect->particle.particleType){ + case 0: + CParticleObject::AddObject(POBJECT_PAVEMENT_STEAM, pos, effect->particle.dir, effect->particle.scale, false); + break; + case 1: + CParticleObject::AddObject(POBJECT_WALL_STEAM, pos, effect->particle.dir, effect->particle.scale, false); + break; + case 2: + CParticleObject::AddObject(POBJECT_DRY_ICE, pos, effect->particle.scale, false); + break; + case 3: + CParticleObject::AddObject(POBJECT_SMALL_FIRE, pos, effect->particle.dir, effect->particle.scale, false); + break; + case 4: + CParticleObject::AddObject(POBJECT_DARK_SMOKE, pos, effect->particle.dir, effect->particle.scale, false); + break; + } + } +} + +void CParticle::Shutdown() +{ + debug("Shutting down CParticle..."); + + for ( int32 i = 0; i < MAX_SMOKE_FILES; i++ ) + { + RwTextureDestroy(gpSmokeTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpSmokeTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_SMOKE2_FILES; i++ ) + { + RwTextureDestroy(gpSmoke2Tex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpSmoke2Tex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_RUBBER_FILES; i++ ) + { + RwTextureDestroy(gpRubberTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubberTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_RAINSPLASH_FILES; i++ ) + { + RwTextureDestroy(gpRainSplashTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpRainSplashTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_WATERSPRAY_FILES; i++ ) + { + RwTextureDestroy(gpWatersprayTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpWatersprayTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_EXPLOSIONMEDIUM_FILES; i++ ) + { + RwTextureDestroy(gpExplosionMediumTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpExplosionMediumTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_GUNFLASH_FILES; i++ ) + { + RwTextureDestroy(gpGunFlashTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpGunFlashTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_RAINDROP_FILES; i++ ) + { + RwTextureDestroy(gpRainDropTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpRainDropTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_RAINSPLASHUP_FILES; i++ ) + { + RwTextureDestroy(gpRainSplashupTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpRainSplashupTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_BIRDFRONT_FILES; i++ ) + { + RwTextureDestroy(gpBirdfrontTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpBirdfrontTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_CARDEBRIS_FILES; i++ ) + { + RwTextureDestroy(gpCarDebrisTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpCarDebrisTex[i] = nil; +#endif + } + + for ( int32 i = 0; i < MAX_CARSPLASH_FILES; i++ ) + { + RwTextureDestroy(gpCarSplashTex[i]); +#if GTA_VERSION >= GTA3_PC_11 + gpCarSplashTex[i] = nil; +#endif + } + + RwTextureDestroy(gpFlame1Tex); +#if GTA_VERSION >= GTA3_PC_11 + gpFlame1Tex = nil; +#endif + + RwTextureDestroy(gpFlame5Tex); +#if GTA_VERSION >= GTA3_PC_11 + gpFlame5Tex = nil; +#endif + + RwTextureDestroy(gpRainDropSmallTex); +#if GTA_VERSION >= GTA3_PC_11 + gpRainDropSmallTex = nil; +#endif + + RwTextureDestroy(gpBloodTex); +#if GTA_VERSION >= GTA3_PC_11 + gpBloodTex = nil; +#endif + + RwTextureDestroy(gpLeafTex); +#if GTA_VERSION >= GTA3_PC_11 + gpLeafTex = nil; +#endif + + RwTextureDestroy(gpCloudTex1); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex1 = nil; +#endif + + RwTextureDestroy(gpCloudTex4); +#if GTA_VERSION >= GTA3_PC_11 + gpCloudTex4 = nil; +#endif + + RwTextureDestroy(gpBloodSmallTex); +#if GTA_VERSION >= GTA3_PC_11 + gpBloodSmallTex = nil; +#endif + + RwTextureDestroy(gpGungeTex); +#if GTA_VERSION >= GTA3_PC_11 + gpGungeTex = nil; +#endif + + RwTextureDestroy(gpCollisionSmokeTex); +#if GTA_VERSION >= GTA3_PC_11 + gpCollisionSmokeTex = nil; +#endif + + RwTextureDestroy(gpBulletHitTex); +#if GTA_VERSION >= GTA3_PC_11 + gpBulletHitTex = nil; +#endif + + RwTextureDestroy(gpGunShellTex); +#if GTA_VERSION >= GTA3_PC_11 + gpGunShellTex = nil; +#endif + + RwTextureDestroy(gpWakeOldTex); +#if GTA_VERSION >= GTA3_PC_11 + gpWakeOldTex = nil; +#endif + + RwTextureDestroy(gpPointlightTex); +#if GTA_VERSION >= GTA3_PC_11 + gpPointlightTex = nil; +#endif + + int32 slot; + + slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::RemoveTxdSlot(slot); + + debug("CParticle shut down"); +} + +CParticle *CParticle::AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity, float fSize, int32 nRotationSpeed, int32 nRotation, int32 nCurFrame, int32 nLifeSpan) +{ + CRGBA color(0, 0, 0, 0); + return AddParticle(type, vecPos, vecDir, pEntity, fSize, color, nRotationSpeed, nRotation, nCurFrame, nLifeSpan); +} + +CParticle *CParticle::AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity, float fSize, RwRGBA const &color, int32 nRotationSpeed, int32 nRotation, int32 nCurFrame, int32 nLifeSpan) +{ + if ( CTimer::GetIsPaused() ) + return NULL; + +#ifdef PC_PARTICLE + if ( ( type == PARTICLE_ENGINE_SMOKE + || type == PARTICLE_ENGINE_SMOKE2 + || type == PARTICLE_ENGINE_STEAM + || type == PARTICLE_CARFLAME_SMOKE + || type == PARTICLE_RUBBER_SMOKE + || type == PARTICLE_BURNINGRUBBER_SMOKE + || type == PARTICLE_EXHAUST_FUMES + || type == PARTICLE_CARCOLLISION_DUST ) + && nParticleCreationInterval & CTimer::GetFrameCounter() ) + { + return nil; + } +#endif + + CParticle *pParticle = m_pUnusedListHead; + + if ( pParticle == nil ) + return nil; + + tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[type]; + + if ( psystem->m_fCreateRange != 0.0f && psystem->m_fCreateRange < ( TheCamera.GetPosition() - vecPos ).MagnitudeSqr() ) + return nil; + + + pParticle->m_fSize = psystem->m_fDefaultInitialRadius; + pParticle->m_fExpansionRate = psystem->m_fExpansionRate; + + if ( nLifeSpan != 0 ) + pParticle->m_nTimeWhenWillBeDestroyed = CTimer::GetTimeInMilliseconds() + nLifeSpan; + else + pParticle->m_nTimeWhenWillBeDestroyed = CTimer::GetTimeInMilliseconds() + psystem->m_nLifeSpan; + + pParticle->m_nColorIntensity = psystem->m_nFadeToBlackInitialIntensity; + pParticle->m_nAlpha = psystem->m_nFadeAlphaInitialIntensity; + pParticle->m_nCurrentZRotation = psystem->m_nZRotationInitialAngle; + pParticle->m_fCurrentZRadius = psystem->m_fInitialZRadius; + + if ( nCurFrame != 0 ) + pParticle->m_nCurrentFrame = nCurFrame; + else + pParticle->m_nCurrentFrame = psystem->m_nStartAnimationFrame; + + pParticle->m_nFadeToBlackTimer = 0; + pParticle->m_nFadeAlphaTimer = 0; + pParticle->m_nZRotationTimer = 0; + pParticle->m_nZRadiusTimer = 0; + pParticle->m_nAnimationSpeedTimer = 0; + pParticle->m_fZGround = 0.0f; + pParticle->m_vecPosition = vecPos; + pParticle->m_vecVelocity = vecDir; + pParticle->m_vecParticleMovementOffset = CVector(0.0f, 0.0f, 0.0f); + pParticle->m_nTimeWhenColorWillBeChanged = 0; + + if ( color.alpha != 0 ) + RwRGBAAssign(&pParticle->m_Color, &color); + else + { + RwRGBAAssign(&pParticle->m_Color, &psystem->m_RenderColouring); + + if ( psystem->m_ColorFadeTime != 0 ) + pParticle->m_nTimeWhenColorWillBeChanged = CTimer::GetTimeInMilliseconds() + psystem->m_ColorFadeTime; + + if ( psystem->m_InitialColorVariation != 0 ) + { + int32 ColorVariation = CGeneral::GetRandomNumberInRange(-psystem->m_InitialColorVariation, psystem->m_InitialColorVariation); + //Float ColorVariation = CGeneral::GetRandomNumberInRange((float)-psystem->m_InitialColorVariation, (float)psystem->m_InitialColorVariation); + + pParticle->m_Color.red = Clamp(pParticle->m_Color.red + + PERCENT(pParticle->m_Color.red, ColorVariation), + 0, 255); + + pParticle->m_Color.green = Clamp(pParticle->m_Color.green + + PERCENT(pParticle->m_Color.green, ColorVariation), + 0, 255); + + pParticle->m_Color.blue = Clamp(pParticle->m_Color.blue + + PERCENT(pParticle->m_Color.blue, ColorVariation), + 0, 255); + } + } + + pParticle->m_nRotation = nRotation; + +// PC only + if ( pParticle->m_nRotation >= 360 ) + pParticle->m_nRotation -= 360; + else if ( pParticle->m_nRotation < 0 ) + pParticle->m_nRotation += 360; + + if ( nRotationSpeed != 0 ) + pParticle->m_nRotationStep = nRotationSpeed; + else + pParticle->m_nRotationStep = psystem->m_nRotationSpeed; + + if ( CGeneral::GetRandomNumber() & 1 ) + pParticle->m_nRotationStep = -pParticle->m_nRotationStep; + + pParticle->m_vecScreenPosition.x = 0.0f; // bug ? + + if ( psystem->m_fPositionRandomError != 0.0f ) + { + pParticle->m_vecPosition.x += psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + pParticle->m_vecPosition.y += psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + + if ( psystem->Flags & RAND_VERT_V ) + pParticle->m_vecPosition.z += psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + } + + if ( psystem->m_fVelocityRandomError != 0.0f ) + { + pParticle->m_vecVelocity.x += psystem->m_fVelocityRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + pParticle->m_vecVelocity.y += psystem->m_fVelocityRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + + if ( psystem->Flags & RAND_VERT_V ) + pParticle->m_vecVelocity.z += psystem->m_fVelocityRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + } + + if ( psystem->m_fExpansionRateError != 0.0f ) + pParticle->m_fExpansionRate += psystem->m_fExpansionRateError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE] + psystem->m_fExpansionRateError; + + if ( psystem->m_nRotationRateError != 0 ) + pParticle->m_nRotationStep += CGeneral::GetRandomNumberInRange(-psystem->m_nRotationRateError, psystem->m_nRotationRateError); + + if ( psystem->m_nLifeSpanErrorShape != 0 ) + { + float randVal = ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + if ( randVal > 0.0f ) + pParticle->m_nTimeWhenWillBeDestroyed += int32(float(psystem->m_nLifeSpan) * randVal * float(psystem->m_nLifeSpanErrorShape)); + else + pParticle->m_nTimeWhenWillBeDestroyed += int32(float(psystem->m_nLifeSpan) * randVal / float(psystem->m_nLifeSpanErrorShape)); + } + + if ( psystem->Flags & ZCHECK_FIRST ) + { + static bool bValidGroundFound = false; + static CVector LastTestCoors; + static float LastTestGroundZ; + + if ( bValidGroundFound + && vecPos.x == LastTestCoors.x + && vecPos.y == LastTestCoors.y + && vecPos.z == LastTestCoors.z ) + { + pParticle->m_fZGround = LastTestGroundZ; + } + else + { + bValidGroundFound = false; + + CColPoint point; + CEntity *entity; + + if ( !CWorld::ProcessVerticalLine( + pParticle->m_vecPosition + CVector(0.0f, 0.0f, 0.5f), + -100.0f, point, entity, true, true, false, false, true, false, nil) ) + { + return nil; + } + + if ( point.point.z >= pParticle->m_vecPosition.z ) + return nil; + + pParticle->m_fZGround = point.point.z; + bValidGroundFound = true; + LastTestCoors = vecPos; + LastTestGroundZ = point.point.z; + } + } + + if ( psystem->Flags & ZCHECK_BUMP ) + { + static float Z_Ground = 0.0f; + + if ( psystem->Flags & ZCHECK_BUMP_FIRST ) + { + bool bZFound = false; + + Z_Ground = CWorld::FindGroundZFor3DCoord(vecPos.x, vecPos.y, vecPos.z, (bool *)&bZFound); + + if ( bZFound == false ) + return nil; + + pParticle->m_fZGround = Z_Ground; + } + + pParticle->m_fZGround = Z_Ground; + } + + switch ( type ) + { + case PARTICLE_DEBRIS: + pParticle->m_vecVelocity.z *= CGeneral::GetRandomNumberInRange(0.5f, 3.0f); + break; + + case PARTICLE_EXPLOSION_MEDIUM: + pParticle->m_nColorIntensity -= 30 * (CGeneral::GetRandomNumber() & 1); // mb "+= -30 * rand" here ? + pParticle->m_nAnimationSpeedTimer = CGeneral::GetRandomNumber() & 7; + pParticle->m_fSize = CGeneral::GetRandomNumberInRange(0.3f, 0.8f); + pParticle->m_vecPosition.z -= CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + break; + + case PARTICLE_EXPLOSION_LARGE: + pParticle->m_nColorIntensity -= 30 * (CGeneral::GetRandomNumber() & 1); // mb "+= -30 * rand" here ? + pParticle->m_nAnimationSpeedTimer = CGeneral::GetRandomNumber() & 7; + pParticle->m_fSize = CGeneral::GetRandomNumberInRange(0.8f, 1.4f); + pParticle->m_vecPosition.z -= CGeneral::GetRandomNumberInRange(-0.3f, 0.3f); + break; + + case PARTICLE_WATER_HYDRANT: + pParticle->m_vecPosition.z += 20.0f * psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE]; + break; + default: break; + } + + if ( fSize != 0.0f ) + pParticle->m_fSize = fSize; + + m_pUnusedListHead = pParticle->m_pNext; + + pParticle->m_pNext = psystem->m_pParticles; + + psystem->m_pParticles = pParticle; + + return pParticle; +} + +void CParticle::Update() +{ + if ( CTimer::GetIsPaused() ) + return; + + CRGBA color(0, 0, 0, 0); + + float fFricDeccel50 = pow(0.50f, CTimer::GetTimeStep()); + float fFricDeccel80 = pow(0.80f, CTimer::GetTimeStep()); + float fFricDeccel90 = pow(0.90f, CTimer::GetTimeStep()); + float fFricDeccel95 = pow(0.95f, CTimer::GetTimeStep()); + float fFricDeccel96 = pow(0.96f, CTimer::GetTimeStep()); + float fFricDeccel99 = pow(0.99f, CTimer::GetTimeStep()); + + CParticleObject::UpdateAll(); + + for ( int32 i = 0; i < MAX_PARTICLES; i++ ) + { + tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[i]; + CParticle *particle = psystem->m_pParticles; + CParticle *prevParticle = nil; + bool bRemoveParticle; + + if ( particle == nil ) + continue; + + for ( ; particle != nil; _Next(particle, prevParticle, psystem, bRemoveParticle) ) + { + bRemoveParticle = false; + + CVector moveStep = particle->m_vecPosition + ( particle->m_vecVelocity * CTimer::GetTimeStep() ); + + if ( CTimer::GetTimeInMilliseconds() > particle->m_nTimeWhenWillBeDestroyed || particle->m_nAlpha == 0 ) + { + bRemoveParticle = true; + continue; + } + + if ( particle->m_nTimeWhenColorWillBeChanged != 0 ) + { + if ( particle->m_nTimeWhenColorWillBeChanged > CTimer::GetTimeInMilliseconds() ) + { + float colorMul = 1.0f - float(particle->m_nTimeWhenColorWillBeChanged - CTimer::GetTimeInMilliseconds()) / float(psystem->m_ColorFadeTime); + + particle->m_Color.red = Clamp( + psystem->m_RenderColouring.red + int32(float(psystem->m_FadeDestinationColor.red - psystem->m_RenderColouring.red) * colorMul), + 0, 255); + + particle->m_Color.green = Clamp( + psystem->m_RenderColouring.green + int32(float(psystem->m_FadeDestinationColor.green - psystem->m_RenderColouring.green) * colorMul), + 0, 255); + + particle->m_Color.blue = Clamp( + psystem->m_RenderColouring.blue + int32(float(psystem->m_FadeDestinationColor.blue - psystem->m_RenderColouring.blue) * colorMul), + 0, 255); + } + else + RwRGBAAssign(&particle->m_Color, &psystem->m_FadeDestinationColor); + } + + if ( psystem->Flags & CLIPOUT2D ) + { + if ( particle->m_vecPosition.x < -10.0f || particle->m_vecPosition.x > SCREEN_WIDTH + 10.0f + || particle->m_vecPosition.y < -10.0f || particle->m_vecPosition.y > SCREEN_HEIGHT + 10.0f ) + { + bRemoveParticle = true; + continue; + } + } + + float size = particle->m_fSize + particle->m_fExpansionRate; + + if ( size < 0.0f ) + { + bRemoveParticle = true; + continue; + } + + particle->m_fSize = size; + + switch ( psystem->m_nFrictionDecceleration ) + { + case 50: + particle->m_vecVelocity *= fFricDeccel50; + break; + + case 80: + particle->m_vecVelocity *= fFricDeccel80; + break; + + case 90: + particle->m_vecVelocity *= fFricDeccel90; + break; + + case 95: + particle->m_vecVelocity *= fFricDeccel95; + break; + + case 96: + particle->m_vecVelocity *= fFricDeccel96; + break; + + case 99: + particle->m_vecVelocity *= fFricDeccel99; + break; + } + + if ( psystem->m_fGravitationalAcceleration > 0.0f ) + { + if ( -50.0f * psystem->m_fGravitationalAcceleration < particle->m_vecVelocity.z ) + particle->m_vecVelocity.z -= psystem->m_fGravitationalAcceleration * CTimer::GetTimeStep(); + + if ( psystem->Flags & ZCHECK_FIRST ) + { + if ( particle->m_vecPosition.z < particle->m_fZGround ) + { + switch ( psystem->m_Type ) + { + case PARTICLE_RAINDROP: + case PARTICLE_RAINDROP_SMALL: + { + bRemoveParticle = true; + + if ( CGeneral::GetRandomNumber() & 1 ) + { + AddParticle(PARTICLE_RAIN_SPLASH, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0); + } + else + { + AddParticle(PARTICLE_RAIN_SPLASHUP, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0); + } + + continue; + } + break; + + case PARTICLE_WHEEL_WATER: + { + bRemoveParticle = true; + + int32 randVal = CGeneral::GetRandomNumber(); + + if ( randVal & 1 ) + { + if ( (randVal % 5) == 0 ) + { + AddParticle(PARTICLE_RAIN_SPLASH, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0); + } + else + { + AddParticle(PARTICLE_RAIN_SPLASHUP, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0); + } + + } + continue; + } + break; + + case PARTICLE_BLOOD: + case PARTICLE_BLOOD_SMALL: + { + bRemoveParticle = true; + + CVector vecPosn = particle->m_vecPosition; + vecPosn.z += 1.0f; + + Randomizer++; + int32 randVal = int32(Randomizer & 7); + + if ( randVal == 5 ) + { + CShadows::AddPermanentShadow(1, gpBloodPoolTex, &vecPosn, + 0.1f, 0.0f, 0.0f, -0.1f, + 255, + 255, 0, 0, + 4.0f, (CGeneral::GetRandomNumber() & 4095) + 2000, 1.0f); + } + else if ( randVal == 2 ) + { + CShadows::AddPermanentShadow(1, gpBloodPoolTex, &vecPosn, + 0.2f, 0.0f, 0.0f, -0.2f, + 255, + 255, 0, 0, + 4.0f, (CGeneral::GetRandomNumber() & 4095) + 8000, 1.0f); + } + continue; + } + break; + default: break; + } + } + } + else if ( psystem->Flags & ZCHECK_STEP ) + { + CColPoint point; + CEntity *entity; + + if ( CWorld::ProcessVerticalLine(particle->m_vecPosition, moveStep.z, point, entity, + true, true, false, false, true, false, nil) ) + { + if ( moveStep.z <= point.point.z ) + { + moveStep.z = point.point.z; + if ( psystem->m_Type == PARTICLE_DEBRIS2 ) + { + particle->m_vecVelocity.x *= 0.8f; + particle->m_vecVelocity.y *= 0.8f; + particle->m_vecVelocity.z *= -0.4f; + if ( particle->m_vecVelocity.z < 0.005f ) + particle->m_vecVelocity.z = 0.0f; + } + } + } + } + else if ( psystem->Flags & ZCHECK_BUMP ) + { + if ( particle->m_vecPosition.z < particle->m_fZGround ) + { + switch ( psystem->m_Type ) + { + case PARTICLE_GUNSHELL_FIRST: + case PARTICLE_GUNSHELL: + { + bRemoveParticle = true; + + AddParticle(PARTICLE_GUNSHELL_BUMP1, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector + ( + CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), + CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), + CGeneral::GetRandomNumberInRange(0.05f, 0.1f) + ), + nil, + particle->m_fSize, color, particle->m_nRotationStep, 0, 0, 0); + + PlayOneShotScriptObject(SCRIPT_SOUND_GUNSHELL_DROP, particle->m_vecPosition); + } + break; + + case PARTICLE_GUNSHELL_BUMP1: + { + bRemoveParticle = true; + + AddParticle(PARTICLE_GUNSHELL_BUMP2, + CVector + ( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + 0.05f + particle->m_fZGround + ), + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.03f, 0.06f)), + nil, + particle->m_fSize, color, 0, 0, 0, 0); + + PlayOneShotScriptObject(SCRIPT_SOUND_GUNSHELL_DROP_SOFT, particle->m_vecPosition); + } + break; + + case PARTICLE_GUNSHELL_BUMP2: + { + bRemoveParticle = true; + continue; + } + break; + default: break; + } + } + } + } + else + { + if ( psystem->m_fGravitationalAcceleration < 0.0f ) + { + if ( -5.0f * psystem->m_fGravitationalAcceleration > particle->m_vecVelocity.z ) + particle->m_vecVelocity.z -= psystem->m_fGravitationalAcceleration * CTimer::GetTimeStep(); + } + else + { + if ( psystem->Flags & ZCHECK_STEP ) + { + CColPoint point; + CEntity *entity; + + if ( CWorld::ProcessVerticalLine(particle->m_vecPosition, moveStep.z, point, entity, + true, false, false, false, true, false, nil) ) + { + if ( moveStep.z <= point.point.z ) + { + moveStep.z = point.point.z; + if ( psystem->m_Type == PARTICLE_HELI_ATTACK ) + { + bRemoveParticle = true; + AddParticle(PARTICLE_STEAM, moveStep, CVector(0.0f, 0.0f, 0.05f), nil, 0.2f, 0, 0, 0, 0); + continue; + } + } + } + } + } + } + + if ( psystem->m_nFadeToBlackAmount != 0 ) + { + if ( particle->m_nFadeToBlackTimer >= psystem->m_nFadeToBlackTime ) + { + particle->m_nFadeToBlackTimer = 0; + + particle->m_nColorIntensity = Clamp(particle->m_nColorIntensity - psystem->m_nFadeToBlackAmount, + 0, 255); + } + else + ++particle->m_nFadeToBlackTimer; + } + + if ( psystem->m_nFadeAlphaAmount != 0 ) + { + if ( particle->m_nFadeAlphaTimer >= psystem->m_nFadeAlphaTime ) + { + particle->m_nFadeAlphaTimer = 0; + + particle->m_nAlpha = Clamp(particle->m_nAlpha - psystem->m_nFadeAlphaAmount, + 0, 255); +#ifdef PC_PARTICLE + if ( particle->m_nAlpha == 0 ) + { + bRemoveParticle = true; + continue; + } +#endif + } + else + ++particle->m_nFadeAlphaTimer; + } + + if ( psystem->m_nZRotationAngleChangeAmount != 0 ) + { + if ( particle->m_nZRotationTimer >= psystem->m_nZRotationChangeTime ) + { + particle->m_nZRotationTimer = 0; + particle->m_nCurrentZRotation += psystem->m_nZRotationAngleChangeAmount; + } + else + ++particle->m_nZRotationTimer; + } + + if ( psystem->m_fZRadiusChangeAmount != 0.0f ) + { + if ( particle->m_nZRadiusTimer >= psystem->m_nZRadiusChangeTime ) + { + particle->m_nZRadiusTimer = 0; + particle->m_fCurrentZRadius += psystem->m_fZRadiusChangeAmount; + } + else + ++particle->m_nZRadiusTimer; + } + + if ( psystem->m_nAnimationSpeed != 0 ) + { + if ( particle->m_nAnimationSpeedTimer > psystem->m_nAnimationSpeed ) + { + particle->m_nAnimationSpeedTimer = 0; + + if ( ++particle->m_nCurrentFrame > psystem->m_nFinalAnimationFrame ) + { + if ( psystem->Flags & CYCLE_ANIM ) + particle->m_nCurrentFrame = psystem->m_nStartAnimationFrame; + else + --particle->m_nCurrentFrame; + } + } + else + ++particle->m_nAnimationSpeedTimer; + } + + if ( particle->m_nRotationStep != 0 ) + { + particle->m_nRotation += particle->m_nRotationStep; + + if ( particle->m_nRotation >= 360 ) + particle->m_nRotation -= 360; + else if ( particle->m_nRotation < 0 ) + particle->m_nRotation += 360; + } + + if ( particle->m_fCurrentZRadius != 0.0f ) + { + int32 nRot = particle->m_nCurrentZRotation % (SIN_COS_TABLE_SIZE - 1); + + float fX = (Cos(nRot) - Sin(nRot)) * particle->m_fCurrentZRadius; + + float fY = (Sin(nRot) + Cos(nRot)) * particle->m_fCurrentZRadius; + + moveStep -= particle->m_vecParticleMovementOffset; + + moveStep += CVector(fX, fY, 0.0f); + + particle->m_vecParticleMovementOffset = CVector(fX, fY, 0.0f); + } + + particle->m_vecPosition = moveStep; + } + } +} + +void CParticle::Render() +{ + PUSH_RENDERGROUP("CParticle::Render"); + + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void *)rwTEXTUREADDRESSWRAP); + RwRenderStateSet(rwRENDERSTATETEXTUREPERSPECTIVE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + CSprite::InitSpriteBuffer2D(); + + uint32 flags = DRAW_OPAQUE; + + RwRaster *prevFrame = nil; + + for ( int32 i = 0; i < MAX_PARTICLES; i++ ) + { + tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[i]; +#ifdef PC_PARTICLE + bool particleBanned = false; +#endif + CParticle *particle = psystem->m_pParticles; + + RwRaster **frames = psystem->m_ppRaster; +#ifdef PC_PARTICLE + tParticleType type = psystem->m_Type; + + if ( type == PARTICLE_ENGINE_SMOKE + || type == PARTICLE_ENGINE_SMOKE2 + || type == PARTICLE_ENGINE_STEAM + || type == PARTICLE_CARFLAME_SMOKE + || type == PARTICLE_RUBBER_SMOKE + || type == PARTICLE_BURNINGRUBBER_SMOKE + || type == PARTICLE_EXHAUST_FUMES + || type == PARTICLE_CARCOLLISION_DUST ) + { + particleBanned = true; + } +#endif + + if ( particle ) + { + if ( (flags & DRAW_OPAQUE) != (psystem->Flags & DRAW_OPAQUE) + || (flags & DRAW_DARK) != (psystem->Flags & DRAW_DARK) ) + { + CSprite::FlushSpriteBuffer(); + + if ( psystem->Flags & DRAW_OPAQUE ) + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + } + else + { + if ( psystem->Flags & DRAW_DARK ) + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + else + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + } + + flags = psystem->Flags; + } + + if ( frames != nil ) + { + RwRaster *curFrame = *frames; + if ( curFrame != prevFrame ) + { + CSprite::FlushSpriteBuffer(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)curFrame); + prevFrame = curFrame; + } + } + } + + while ( particle != nil ) + { + bool canDraw = true; +#ifdef PC_PARTICLE + + if ( particle->m_nAlpha == 0 ) + canDraw = false; +#endif + if ( canDraw && psystem->m_nFinalAnimationFrame != 0 && frames != nil ) + { + RwRaster *curFrame = frames[particle->m_nCurrentFrame]; + if ( prevFrame != curFrame ) + { + CSprite::FlushSpriteBuffer(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)curFrame); + prevFrame = curFrame; + } + } + + if ( canDraw && psystem->Flags & DRAWTOP2D ) + { + if ( particle->m_nRotation != 0 ) + { + CSprite::RenderBufferedOneXLUSprite2D_Rotate_Dimension( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + particle->m_fSize * 63.0f, + particle->m_fSize * 63.0f, + particle->m_Color, + particle->m_nColorIntensity, + (float)particle->m_nRotation, //DEGTORAD((float)particle->m_nRotation) ps2 + particle->m_nAlpha); + } + else + { + CSprite::RenderBufferedOneXLUSprite2D( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + particle->m_fSize * 63.0f, + particle->m_fSize * 63.0f, + particle->m_Color, + particle->m_nColorIntensity, + particle->m_nAlpha); + } + + canDraw = false; + } + + if ( canDraw ) + { + CVector coors; + float w; + float h; + + if ( CSprite::CalcScreenCoors(particle->m_vecPosition, &coors, &w, &h, true) ) + { +#ifdef PC_PARTICLE + if ( (!particleBanned || SCREEN_WIDTH * fParticleScaleLimit >= w) + && SCREEN_HEIGHT * fParticleScaleLimit >= h ) +#endif + { + if ( particle->m_nRotation != 0 ) + { + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(coors.x, coors.y, coors.z, + particle->m_fSize * w, particle->m_fSize * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + float(particle->m_nRotation), // DEGTORAD((float)particle->m_nRotation) ps2 + particle->m_nAlpha); + } + else if ( psystem->Flags & SCREEN_TRAIL ) + { + float fRotation; + float fTrailLength; + + if ( particle->m_vecScreenPosition.x == 0.0f ) + { + fTrailLength = 0.0f; + fRotation = 0.0f; + } + else + { + CVector2D vecDist + ( + coors.x - particle->m_vecScreenPosition.x, + coors.y - particle->m_vecScreenPosition.y + ); + + float fDist = vecDist.Magnitude(); + + fTrailLength = fDist; + + float fRot = Asin(vecDist.x / fDist); + + fRotation = fRot; + + if ( vecDist.y < 0.0f ) + fRotation = -1.0f * fRot + DEGTORAD(180.0f); + + fRotation = RADTODEG(fRotation); + + if ( fRotation < 0.0f ) + fRotation += 360.0f; + + float fSpeed = particle->m_vecVelocity.Magnitude(); + + float fNewTrailLength = fSpeed * CTimer::GetTimeStep() * w * 2.0f; + + if ( fDist > fNewTrailLength ) + fTrailLength = fNewTrailLength; + } + + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(coors.x, coors.y, coors.z, + particle->m_fSize * w, + particle->m_fSize * h + fTrailLength * psystem->m_fTrailLengthMultiplier, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + fRotation, + particle->m_nAlpha); + + particle->m_vecScreenPosition = coors; + } + else if ( psystem->Flags & SPEED_TRAIL ) + { + CVector vecPrevPos = particle->m_vecPosition - particle->m_vecVelocity; + float fRotation; + float fTrailLength; + + if ( CSprite::CalcScreenCoors(vecPrevPos, &particle->m_vecScreenPosition, &fTrailLength, &fRotation, true) ) + { + CVector2D vecDist + ( + coors.x - particle->m_vecScreenPosition.x, + coors.y - particle->m_vecScreenPosition.y + ); + + float fDist = vecDist.Magnitude(); + + fTrailLength = fDist; + + float fRot = Asin(vecDist.x / fDist); + + fRotation = fRot; + + if ( vecDist.y < 0.0f ) + fRotation = -1.0f * fRot + DEGTORAD(180.0f); + + fRotation = RADTODEG(fRotation); + + if ( fRotation < 0.0f ) + fRotation += 360.0f; + } + else + { + fRotation = 0.0f; + fTrailLength = 0.0f; + } + + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(coors.x, coors.y, coors.z, + particle->m_fSize * w, + particle->m_fSize * h + fTrailLength * psystem->m_fTrailLengthMultiplier, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + fRotation, + particle->m_nAlpha); + } + else if ( psystem->Flags & VERT_TRAIL ) + { + float fTrailLength = fabsf(particle->m_vecVelocity.z * 10.0f); + + CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z, + particle->m_fSize * w, + (particle->m_fSize + fTrailLength * psystem->m_fTrailLengthMultiplier) * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + particle->m_nAlpha); + } + else if ( i == PARTICLE_RAINDROP_SMALL ) + { + CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z, + particle->m_fSize * w * 0.05f, + particle->m_fSize * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + particle->m_nAlpha); + } + else if ( i == PARTICLE_BOAT_WAKE ) + { + CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z, + particle->m_fSize * w, + psystem->m_fDefaultInitialRadius * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + particle->m_nAlpha); + } + else + { + CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z, + particle->m_fSize * w, + particle->m_fSize * h, + particle->m_Color.red, + particle->m_Color.green, + particle->m_Color.blue, + particle->m_nColorIntensity, + 1.0f / coors.z, + particle->m_nAlpha); + } + } + } + } + + particle = particle->m_pNext; + } + + CSprite::FlushSpriteBuffer(); + + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + POP_RENDERGROUP(); +} + +void CParticle::RemovePSystem(tParticleType type) +{ + tParticleSystemData *psystemdata = &mod_ParticleSystemManager.m_aParticles[type]; + + for ( CParticle *particle = psystemdata->m_pParticles; particle; particle = psystemdata->m_pParticles ) + RemoveParticle(particle, nil, psystemdata); +} + +void CParticle::RemoveParticle(CParticle *pParticle, CParticle *pPrevParticle, tParticleSystemData *pPSystemData) +{ + if ( pPrevParticle ) + pPrevParticle->m_pNext = pParticle->m_pNext; + else + pPSystemData->m_pParticles = pParticle->m_pNext; + + pParticle->m_pNext = m_pUnusedListHead; + m_pUnusedListHead = pParticle; +} + +void CParticle::AddJetExplosion(CVector const &vecPos, float fPower, float fSize) +{ + CRGBA color(240, 240, 240, 255); + + if ( fPower < 1.0f ) + fPower = 1.0f; + + CVector vecRandOffset + ( + CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), + CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), + CGeneral::GetRandomNumberInRange(0.1f, 0.3f) + ); + + vecRandOffset *= 2.0f; + + CVector vecStepPos = vecPos; + + for ( int32 i = 0; i < int32(fPower * 4.0f); i++ ) + { + AddParticle(PARTICLE_EXPLOSION_MFAST, + vecStepPos, + CVector + ( + CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), + CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), + CGeneral::GetRandomNumberInRange(-0.02f, 0.0f) + ), + nil, + fSize, color, 0, 0, 0, 0); + + AddParticle(PARTICLE_EXPLOSION_MFAST, + vecStepPos, + CVector + ( + CGeneral::GetRandomNumberInRange(-0.04f, 0.04f), + CGeneral::GetRandomNumberInRange(-0.04f, 0.04f), + CGeneral::GetRandomNumberInRange(0.0f, 0.07f) + ), + nil, + fSize, color, 0, 0, 0, 0); + + AddParticle(PARTICLE_EXPLOSION_MFAST, + vecStepPos, + CVector + ( + CGeneral::GetRandomNumberInRange(-0.04f, 0.04f), + CGeneral::GetRandomNumberInRange(-0.04f, 0.04f), + CGeneral::GetRandomNumberInRange(0.0f, 0.07f) + ), + nil, + fSize, color, 0, 0, 0, 0); + + vecStepPos += vecRandOffset; + } +} + +void CParticle::AddYardieDoorSmoke(CVector const &vecPos, CMatrix const &matMatrix) +{ + CRGBA color(0, 0, 0, 0); + + CMatrix invMat(Invert(matMatrix)); + + CVector vecBasePos = matMatrix * (invMat * vecPos + CVector(0.0f, -1.0f, 0.5f)); + + for ( int32 i = 0; i < 5; i++ ) + { + CVector pos = vecBasePos; + + pos.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f); + pos.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f); + + AddParticle(PARTICLE_CARCOLLISION_DUST, + pos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.3f, color, 0, 0, 0, 0); + } +} diff --git a/src/renderer/Particle.h b/src/renderer/Particle.h new file mode 100644 index 0000000..7f02e31 --- /dev/null +++ b/src/renderer/Particle.h @@ -0,0 +1,94 @@ +#pragma once +#include "ParticleMgr.h" + + +class CEntity; + +class CParticle +{ +public: + enum + { + RAND_TABLE_SIZE = 20, + SIN_COS_TABLE_SIZE = 1024 + }; + + CVector m_vecPosition; + CVector m_vecVelocity; + CVector m_vecScreenPosition; + uint32 m_nTimeWhenWillBeDestroyed; + uint32 m_nTimeWhenColorWillBeChanged; + float m_fZGround; + CVector m_vecParticleMovementOffset; + int16 m_nCurrentZRotation; + uint16 m_nZRotationTimer; + float m_fCurrentZRadius; + uint16 m_nZRadiusTimer; + float m_fSize; + float m_fExpansionRate; + uint16 m_nFadeToBlackTimer; + uint16 m_nFadeAlphaTimer; + uint8 m_nColorIntensity; + uint8 m_nAlpha; + uint16 m_nCurrentFrame; + int16 m_nAnimationSpeedTimer; + int16 m_nRotationStep; + int16 m_nRotation; + RwRGBA m_Color; + CParticle *m_pNext; + + CParticle() + { + ; + } + + ~CParticle() + { + ; + } + + static float ms_afRandTable[RAND_TABLE_SIZE]; + static CParticle *m_pUnusedListHead; + + static float m_SinTable[SIN_COS_TABLE_SIZE]; + static float m_CosTable[SIN_COS_TABLE_SIZE]; + + static float Sin(int32 value) { return m_SinTable[value]; } + static float Cos(int32 value) { return m_CosTable[value]; } + + static void ReloadConfig(); + static void Initialise(); + static void Shutdown(); + + static CParticle *AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity = nil, float fSize = 0.0f, int32 nRotationSpeed = 0, int32 nRotation = 0, int32 nCurFrame = 0, int32 nLifeSpan = 0); + static CParticle *AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity, float fSize, RwRGBA const &color, int32 nRotationSpeed = 0, int32 nRotation = 0, int32 nCurFrame = 0, int32 nLifeSpan = 0); + + static void Update(); + static void Render(); + + static void RemovePSystem(tParticleType type); + static void RemoveParticle(CParticle *pParticle, CParticle *pPrevParticle, tParticleSystemData *pPSystemData); + + static void _Next(CParticle *&pParticle, CParticle *&pPrevParticle, tParticleSystemData *pPSystemData, bool bRemoveParticle) + { + if ( bRemoveParticle ) + { + RemoveParticle(pParticle, pPrevParticle, pPSystemData); + + if ( pPrevParticle ) + pParticle = pPrevParticle->m_pNext; + else + pParticle = pPSystemData->m_pParticles; + } + else + { + pPrevParticle = pParticle; + pParticle = pParticle->m_pNext; + } + } + + static void AddJetExplosion(CVector const &vecPos, float fPower, float fSize); + static void AddYardieDoorSmoke(CVector const &vecPos, CMatrix const &matMatrix); +}; + +VALIDATE_SIZE(CParticle, 0x68); diff --git a/src/renderer/ParticleMgr.cpp b/src/renderer/ParticleMgr.cpp new file mode 100644 index 0000000..3387d47 --- /dev/null +++ b/src/renderer/ParticleMgr.cpp @@ -0,0 +1,243 @@ +#include "common.h" + +#include "main.h" +#include "FileMgr.h" +#include "ParticleMgr.h" + +cParticleSystemMgr mod_ParticleSystemManager; + +const char *ParticleFilename = "PARTICLE.CFG"; + +cParticleSystemMgr::cParticleSystemMgr() +{ + memset(this, 0, sizeof(*this)); +} + +void cParticleSystemMgr::Initialise() +{ + LoadParticleData(); + + for ( int32 i = 0; i < MAX_PARTICLES; i++ ) + m_aParticles[i].m_pParticles = nil; +} + +void cParticleSystemMgr::LoadParticleData() +{ + CFileMgr::SetDir("DATA"); + CFileMgr::LoadFile(ParticleFilename, work_buff, ARRAY_SIZE(work_buff), "r"); + CFileMgr::SetDir(""); + + tParticleSystemData *entry = nil; + int32 type = PARTICLE_FIRST; + + char *lineStart = (char *)work_buff; + char *lineEnd = lineStart + 1; + + char line[500]; + char delims[4]; + + while ( true ) + { + ASSERT(lineStart != nil); + ASSERT(lineEnd != nil); + + while ( *lineEnd != '\n' ) + ++lineEnd; + + int32 lineLength = lineEnd - lineStart; + + ASSERT(lineLength < 500); + + strncpy(line, lineStart, lineLength); + + line[lineLength] = '\0'; + + if ( !strcmp(line, ";the end") ) + break; + + if ( *line != ';' ) + { + int32 param = CFG_PARAM_FIRST; + + strcpy(delims, " \t"); + + char *value = strtok(line, delims); + + ASSERT(value != nil); + + do + { + switch ( param ) + { + case CFG_PARAM_PARTICLE_TYPE_NAME: + ASSERT(type < MAX_PARTICLES); + entry = &m_aParticles[type]; + ASSERT(entry != nil); + entry->m_Type = (tParticleType)type++; + strcpy(entry->m_aName, value); + break; + + case CFG_PARAM_RENDER_COLOURING_R: + entry->m_RenderColouring.red = atoi(value); + break; + + case CFG_PARAM_RENDER_COLOURING_G: + entry->m_RenderColouring.green = atoi(value); + break; + + case CFG_PARAM_RENDER_COLOURING_B: + entry->m_RenderColouring.blue = atoi(value); + break; + + case CFG_PARAM_INITIAL_COLOR_VARIATION: + entry->m_InitialColorVariation = Min(atoi(value), 100); + break; + + case CFG_PARAM_FADE_DESTINATION_COLOR_R: + entry->m_FadeDestinationColor.red = atoi(value); + break; + + case CFG_PARAM_FADE_DESTINATION_COLOR_G: + entry->m_FadeDestinationColor.green = atoi(value); + break; + + case CFG_PARAM_FADE_DESTINATION_COLOR_B: + entry->m_FadeDestinationColor.blue = atoi(value); + break; + + case CFG_PARAM_COLOR_FADE_TIME: + entry->m_ColorFadeTime = atoi(value); + break; + + case CFG_PARAM_DEFAULT_INITIAL_RADIUS: + entry->m_fDefaultInitialRadius = atof(value); + break; + + case CFG_PARAM_EXPANSION_RATE: + entry->m_fExpansionRate = atof(value); + break; + + case CFG_PARAM_INITIAL_INTENSITY: + entry->m_nFadeToBlackInitialIntensity = atoi(value); + break; + + case CFG_PARAM_FADE_TIME: + entry->m_nFadeToBlackTime = atoi(value); + break; + + case CFG_PARAM_FADE_AMOUNT: + entry->m_nFadeToBlackAmount = atoi(value); + break; + + case CFG_PARAM_INITIAL_ALPHA_INTENSITY: + entry->m_nFadeAlphaInitialIntensity = atoi(value); + break; + + case CFG_PARAM_FADE_ALPHA_TIME: + entry->m_nFadeAlphaTime = atoi(value); + break; + + case CFG_PARAM_FADE_ALPHA_AMOUNT: + entry->m_nFadeAlphaAmount = atoi(value); + break; + + case CFG_PARAM_INITIAL_ANGLE: + entry->m_nZRotationInitialAngle = atoi(value); + break; + + case CFG_PARAM_CHANGE_TIME: + entry->m_nZRotationChangeTime = atoi(value); + break; + + case CFG_PARAM_ANGLE_CHANGE_AMOUNT: + entry->m_nZRotationAngleChangeAmount = atoi(value); + break; + + case CFG_PARAM_INITIAL_Z_RADIUS: + entry->m_fInitialZRadius = atof(value); + break; + + case CFG_PARAM_Z_RADIUS_CHANGE_TIME: + entry->m_nZRadiusChangeTime = atoi(value); + break; + + case CFG_PARAM_Z_RADIUS_CHANGE_AMOUNT: + entry->m_fZRadiusChangeAmount = atof(value); + break; + + case CFG_PARAM_ANIMATION_SPEED: + entry->m_nAnimationSpeed = atoi(value); + break; + + case CFG_PARAM_START_ANIMATION_FRAME: + entry->m_nStartAnimationFrame = atoi(value); + break; + + case CFG_PARAM_FINAL_ANIMATION_FRAME: + entry->m_nFinalAnimationFrame = atoi(value); + break; + + case CFG_PARAM_ROTATION_SPEED: + entry->m_nRotationSpeed = atoi(value); + break; + + case CFG_PARAM_GRAVITATIONAL_ACCELERATION: + entry->m_fGravitationalAcceleration = atof(value); + break; + + case CFG_PARAM_FRICTION_DECCELERATION: + entry->m_nFrictionDecceleration = atoi(value); + break; + + case CFG_PARAM_LIFE_SPAN: + entry->m_nLifeSpan = atoi(value); + break; + + case CFG_PARAM_POSITION_RANDOM_ERROR: + entry->m_fPositionRandomError = atof(value); + break; + + case CFG_PARAM_VELOCITY_RANDOM_ERROR: + entry->m_fVelocityRandomError = atof(value); + break; + + case CFG_PARAM_EXPANSION_RATE_ERROR: + entry->m_fExpansionRateError = atof(value); + break; + + case CFG_PARAM_ROTATION_RATE_ERROR: + entry->m_nRotationRateError = atoi(value); + break; + + case CFG_PARAM_LIFE_SPAN_ERROR_SHAPE: + entry->m_nLifeSpanErrorShape = atoi(value); + break; + + case CFG_PARAM_TRAIL_LENGTH_MULTIPLIER: + entry->m_fTrailLengthMultiplier = atof(value); + break; + + case CFG_PARAM_PARTICLE_CREATE_RANGE: + entry->m_fCreateRange = SQR(atof(value)); + break; + + case CFG_PARAM_FLAGS: + entry->Flags = atoi(value); + break; + } + + value = strtok(nil, delims); + + param++; + + if ( param > CFG_PARAM_LAST ) + param = CFG_PARAM_FIRST; + + } while ( value != nil ); + } + + lineEnd++; + lineStart = lineEnd; + lineEnd++; + } +} diff --git a/src/renderer/ParticleMgr.h b/src/renderer/ParticleMgr.h new file mode 100644 index 0000000..0100bb6 --- /dev/null +++ b/src/renderer/ParticleMgr.h @@ -0,0 +1,130 @@ +#pragma once + +#include "ParticleType.h" + +class CParticle; + +enum +{ + ZCHECK_FIRST = BIT(0), + ZCHECK_STEP = BIT(1), + DRAW_OPAQUE = BIT(2), + SCREEN_TRAIL = BIT(3), + SPEED_TRAIL = BIT(4), + RAND_VERT_V = BIT(5), + CYCLE_ANIM = BIT(6), + DRAW_DARK = BIT(7), + VERT_TRAIL = BIT(8), + _FLAG9 = BIT(9), // unused + DRAWTOP2D = BIT(10), + CLIPOUT2D = BIT(11), + ZCHECK_BUMP = BIT(12), + ZCHECK_BUMP_FIRST = BIT(13) +}; + + +struct tParticleSystemData +{ + tParticleType m_Type; + char m_aName[20]; + float m_fCreateRange; + float m_fDefaultInitialRadius; + float m_fExpansionRate; + uint16 m_nZRotationInitialAngle; + int16 m_nZRotationAngleChangeAmount; + uint16 m_nZRotationChangeTime; + uint16 m_nZRadiusChangeTime; + float m_fInitialZRadius; + float m_fZRadiusChangeAmount; + uint16 m_nFadeToBlackTime; + int16 m_nFadeToBlackAmount; + uint8 m_nFadeToBlackInitialIntensity; + uint8 m_nFadeAlphaInitialIntensity; + uint16 m_nFadeAlphaTime; + int16 m_nFadeAlphaAmount; + uint16 m_nStartAnimationFrame; + uint16 m_nFinalAnimationFrame; + uint16 m_nAnimationSpeed; + uint16 m_nRotationSpeed; + float m_fGravitationalAcceleration; + int32 m_nFrictionDecceleration; + int32 m_nLifeSpan; + float m_fPositionRandomError; + float m_fVelocityRandomError; + float m_fExpansionRateError; + int32 m_nRotationRateError; + uint32 m_nLifeSpanErrorShape; + float m_fTrailLengthMultiplier; + uint32 Flags; + RwRGBA m_RenderColouring; + uint8 m_InitialColorVariation; + RwRGBA m_FadeDestinationColor; + uint32 m_ColorFadeTime; + + RwRaster **m_ppRaster; + CParticle *m_pParticles; +}; + +VALIDATE_SIZE(tParticleSystemData, 0x88); + +class cParticleSystemMgr +{ + enum + { + CFG_PARAM_PARTICLE_TYPE_NAME = 0, + CFG_PARAM_RENDER_COLOURING_R, + CFG_PARAM_RENDER_COLOURING_G, + CFG_PARAM_RENDER_COLOURING_B, + CFG_PARAM_INITIAL_COLOR_VARIATION, + CFG_PARAM_FADE_DESTINATION_COLOR_R, + CFG_PARAM_FADE_DESTINATION_COLOR_G, + CFG_PARAM_FADE_DESTINATION_COLOR_B, + CFG_PARAM_COLOR_FADE_TIME, + CFG_PARAM_DEFAULT_INITIAL_RADIUS, + CFG_PARAM_EXPANSION_RATE, + CFG_PARAM_INITIAL_INTENSITY, + CFG_PARAM_FADE_TIME, + CFG_PARAM_FADE_AMOUNT, + CFG_PARAM_INITIAL_ALPHA_INTENSITY, + CFG_PARAM_FADE_ALPHA_TIME, + CFG_PARAM_FADE_ALPHA_AMOUNT, + CFG_PARAM_INITIAL_ANGLE, + CFG_PARAM_CHANGE_TIME, + CFG_PARAM_ANGLE_CHANGE_AMOUNT, + CFG_PARAM_INITIAL_Z_RADIUS, + CFG_PARAM_Z_RADIUS_CHANGE_TIME, + CFG_PARAM_Z_RADIUS_CHANGE_AMOUNT, + CFG_PARAM_ANIMATION_SPEED, + CFG_PARAM_START_ANIMATION_FRAME, + CFG_PARAM_FINAL_ANIMATION_FRAME, + CFG_PARAM_ROTATION_SPEED, + CFG_PARAM_GRAVITATIONAL_ACCELERATION, + CFG_PARAM_FRICTION_DECCELERATION, + CFG_PARAM_LIFE_SPAN, + CFG_PARAM_POSITION_RANDOM_ERROR, + CFG_PARAM_VELOCITY_RANDOM_ERROR, + CFG_PARAM_EXPANSION_RATE_ERROR, + CFG_PARAM_ROTATION_RATE_ERROR, + CFG_PARAM_LIFE_SPAN_ERROR_SHAPE, + CFG_PARAM_TRAIL_LENGTH_MULTIPLIER, + CFG_PARAM_PARTICLE_CREATE_RANGE, + CFG_PARAM_FLAGS, + + MAX_CFG_PARAMS, + CFG_PARAM_FIRST = CFG_PARAM_PARTICLE_TYPE_NAME, + CFG_PARAM_LAST = CFG_PARAM_FLAGS + }; + +public: + tParticleSystemData m_aParticles[MAX_PARTICLES]; + + cParticleSystemMgr(); + + void Initialise(); + void LoadParticleData(); + void RangeCheck(tParticleSystemData *pData) { } +}; + +VALIDATE_SIZE(cParticleSystemMgr, 0x2420); + +extern cParticleSystemMgr mod_ParticleSystemManager; diff --git a/src/renderer/ParticleType.h b/src/renderer/ParticleType.h new file mode 100644 index 0000000..8d352c4 --- /dev/null +++ b/src/renderer/ParticleType.h @@ -0,0 +1,77 @@ +#pragma once + +enum tParticleType +{ + PARTICLE_SPARK = 0, + PARTICLE_SPARK_SMALL, + PARTICLE_WHEEL_DIRT, + PARTICLE_WHEEL_WATER, + PARTICLE_BLOOD, + PARTICLE_BLOOD_SMALL, + PARTICLE_BLOOD_SPURT, + PARTICLE_DEBRIS, + PARTICLE_DEBRIS2, + PARTICLE_WATER, + PARTICLE_FLAME, + PARTICLE_FIREBALL, + PARTICLE_GUNFLASH, + PARTICLE_GUNFLASH_NOANIM, + PARTICLE_GUNSMOKE, + PARTICLE_GUNSMOKE2, + PARTICLE_SMOKE, + PARTICLE_SMOKE_SLOWMOTION, + PARTICLE_GARAGEPAINT_SPRAY, + PARTICLE_SHARD, + PARTICLE_SPLASH, + PARTICLE_CARFLAME, + PARTICLE_STEAM, + PARTICLE_STEAM2, + PARTICLE_STEAM_NY, + PARTICLE_STEAM_NY_SLOWMOTION, + PARTICLE_ENGINE_STEAM, + PARTICLE_RAINDROP, + PARTICLE_RAINDROP_SMALL, + PARTICLE_RAIN_SPLASH, + PARTICLE_RAIN_SPLASH_BIGGROW, + PARTICLE_RAIN_SPLASHUP, + PARTICLE_WATERSPRAY, + PARTICLE_EXPLOSION_MEDIUM, + PARTICLE_EXPLOSION_LARGE, + PARTICLE_EXPLOSION_MFAST, + PARTICLE_EXPLOSION_LFAST, + PARTICLE_CAR_SPLASH, + PARTICLE_BOAT_SPLASH, + PARTICLE_BOAT_THRUSTJET, + PARTICLE_BOAT_WAKE, + PARTICLE_WATER_HYDRANT, + PARTICLE_WATER_CANNON, + PARTICLE_EXTINGUISH_STEAM, + PARTICLE_PED_SPLASH, + PARTICLE_PEDFOOT_DUST, + PARTICLE_HELI_DUST, + PARTICLE_HELI_ATTACK, + PARTICLE_ENGINE_SMOKE, + PARTICLE_ENGINE_SMOKE2, + PARTICLE_CARFLAME_SMOKE, + PARTICLE_FIREBALL_SMOKE, + PARTICLE_PAINT_SMOKE, + PARTICLE_TREE_LEAVES, + PARTICLE_CARCOLLISION_DUST, + PARTICLE_CAR_DEBRIS, + PARTICLE_HELI_DEBRIS, + PARTICLE_EXHAUST_FUMES, + PARTICLE_RUBBER_SMOKE, + PARTICLE_BURNINGRUBBER_SMOKE, + PARTICLE_BULLETHIT_SMOKE, + PARTICLE_GUNSHELL_FIRST, + PARTICLE_GUNSHELL, + PARTICLE_GUNSHELL_BUMP1, + PARTICLE_GUNSHELL_BUMP2, + PARTICLE_TEST, + PARTICLE_BIRD_FRONT, + PARTICLE_RAINDROP_2D, + + MAX_PARTICLES, + PARTICLE_FIRST = PARTICLE_SPARK, + PARTICLE_LAST = PARTICLE_RAINDROP_2D +}; \ No newline at end of file diff --git a/src/renderer/PlayerSkin.cpp b/src/renderer/PlayerSkin.cpp new file mode 100644 index 0000000..f0fae45 --- /dev/null +++ b/src/renderer/PlayerSkin.cpp @@ -0,0 +1,166 @@ +#include "common.h" + +#include "main.h" +#include "PlayerSkin.h" +#include "TxdStore.h" +#include "rtbmp.h" +#include "ClumpModelInfo.h" +#include "VisibilityPlugins.h" +#include "World.h" +#include "PlayerInfo.h" +#include "CdStream.h" +#include "FileMgr.h" +#include "Directory.h" +#include "RwHelper.h" +#include "Timer.h" +#include "Lights.h" +#include "MemoryMgr.h" + +RpClump *gpPlayerClump; +float gOldFov; + +int CPlayerSkin::m_txdSlot; + +void +FindPlayerDff(uint32 &offset, uint32 &size) +{ + int file; + CDirectory::DirectoryInfo info; + + file = CFileMgr::OpenFile("models\\gta3.dir", "rb"); + + do { + if (!CFileMgr::Read(file, (char*)&info, sizeof(CDirectory::DirectoryInfo))) + return; + } while (strcasecmp("player.dff", info.name) != 0); + + offset = info.offset; + size = info.size; +} + +void +LoadPlayerDff(void) +{ + RwStream *stream; + RwMemory mem; + uint32 offset, size; + uint8 *buffer; + bool streamWasAdded = false; + + if (CdStreamGetNumImages() == 0) { + CdStreamAddImage("models\\gta3.img"); + streamWasAdded = true; + } + + FindPlayerDff(offset, size); + buffer = (uint8*)RwMallocAlign(size << 11, 2048); + CdStreamRead(0, buffer, offset, size); + CdStreamSync(0); + + mem.start = buffer; + mem.length = size << 11; + stream = RwStreamOpen(rwSTREAMMEMORY, rwSTREAMREAD, &mem); + + if (RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)) + gpPlayerClump = RpClumpStreamRead(stream); + + RwStreamClose(stream, &mem); + RwFreeAlign(buffer); + + if (streamWasAdded) + CdStreamRemoveImages(); +} + +void +CPlayerSkin::Initialise(void) +{ + m_txdSlot = CTxdStore::AddTxdSlot("skin"); + CTxdStore::Create(m_txdSlot); + CTxdStore::AddRef(m_txdSlot); +} + +void +CPlayerSkin::Shutdown(void) +{ + CTxdStore::RemoveTxdSlot(m_txdSlot); +} + +RwTexture * +CPlayerSkin::GetSkinTexture(const char *texName) +{ + RwTexture *tex; + RwRaster *raster; + int32 width, height, depth, format; + + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(m_txdSlot); + tex = RwTextureRead(texName, NULL); + CTxdStore::PopCurrentTxd(); + if (tex != nil) return tex; + + if (strcmp(DEFAULT_SKIN_NAME, texName) == 0) + sprintf(gString, "models\\generic\\player.bmp"); + else + sprintf(gString, "skins\\%s.bmp", texName); + + if (RwImage *image = RtBMPImageRead(gString)) { + RwImageFindRasterFormat(image, rwRASTERTYPETEXTURE, &width, &height, &depth, &format); + raster = RwRasterCreate(width, height, depth, format); + RwRasterSetFromImage(raster, image); + + tex = RwTextureCreate(raster); + RwTextureSetName(tex, texName); +#ifdef FIX_BUGS + RwTextureSetFilterMode(tex, rwFILTERLINEAR); // filtering bugfix from VC +#endif + RwTexDictionaryAddTexture(CTxdStore::GetSlot(m_txdSlot)->texDict, tex); + + RwImageDestroy(image); + } + return tex; +} + +void +CPlayerSkin::BeginFrontendSkinEdit(void) +{ + LoadPlayerDff(); + RpClumpForAllAtomics(gpPlayerClump, CClumpModelInfo::SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); + CWorld::Players[0].LoadPlayerSkin(); + gOldFov = CDraw::GetFOV(); + CDraw::SetFOV(30.0f); +} + +void +CPlayerSkin::EndFrontendSkinEdit(void) +{ + RpClumpDestroy(gpPlayerClump); + gpPlayerClump = NULL; + CDraw::SetFOV(gOldFov); +} + +void +CPlayerSkin::RenderFrontendSkinEdit(void) +{ + static float rotation = 0.0f; + RwRGBAReal AmbientColor = { 0.65f, 0.65f, 0.65f, 1.0f }; + const RwV3d pos = { 1.35f, 0.35f, 7.725f }; + const RwV3d axis1 = { 1.0f, 0.0f, 0.0f }; + const RwV3d axis2 = { 0.0f, 0.0f, 1.0f }; + static uint32 LastFlash = 0; + + RwFrame *frame = RpClumpGetFrame(gpPlayerClump); + + if (CTimer::GetTimeInMillisecondsPauseMode() - LastFlash > 7) { + rotation += 2.0f; + if (rotation > 360.0f) + rotation -= 360.0f; + LastFlash = CTimer::GetTimeInMillisecondsPauseMode(); + } + RwFrameTransform(frame, RwFrameGetMatrix(RwCameraGetFrame(Scene.camera)), rwCOMBINEREPLACE); + RwFrameTranslate(frame, &pos, rwCOMBINEPRECONCAT); + RwFrameRotate(frame, &axis1, -90.0f, rwCOMBINEPRECONCAT); + RwFrameRotate(frame, &axis2, rotation, rwCOMBINEPRECONCAT); + RwFrameUpdateObjects(frame); + SetAmbientColours(&AmbientColor); + RpClumpRender(gpPlayerClump); +} diff --git a/src/renderer/PlayerSkin.h b/src/renderer/PlayerSkin.h new file mode 100644 index 0000000..e0214ce --- /dev/null +++ b/src/renderer/PlayerSkin.h @@ -0,0 +1,15 @@ +#pragma once + +#define DEFAULT_SKIN_NAME "$$\"\"" + +class CPlayerSkin +{ + static int m_txdSlot; +public: + static void Initialise(); + static void Shutdown(); + static RwTexture *GetSkinTexture(const char *texName); + static void BeginFrontendSkinEdit(); + static void EndFrontendSkinEdit(); + static void RenderFrontendSkinEdit(); +}; \ No newline at end of file diff --git a/src/renderer/PointLights.cpp b/src/renderer/PointLights.cpp new file mode 100644 index 0000000..84ac4ab --- /dev/null +++ b/src/renderer/PointLights.cpp @@ -0,0 +1,289 @@ +#include "common.h" + +#include "main.h" +#include "Lights.h" +#include "Camera.h" +#include "Weather.h" +#include "World.h" +#include "Collision.h" +#include "Sprite.h" +#include "Timer.h" +#include "PointLights.h" + +int16 CPointLights::NumLights; +CRegisteredPointLight CPointLights::aLights[NUMPOINTLIGHTS]; + +void +CPointLights::InitPerFrame(void) +{ + NumLights = 0; +} + +#define MAX_DIST 22.0f + +void +CPointLights::AddLight(uint8 type, CVector coors, CVector dir, float radius, float red, float green, float blue, uint8 fogType, bool castExtraShadows) +{ + CVector dist; + float distance; + + // The check is done in some weird way in the game + // we're doing it a bit better here + if(NumLights >= NUMPOINTLIGHTS) + return; + + dist = coors - TheCamera.GetPosition(); + if(Abs(dist.x) < MAX_DIST && Abs(dist.y) < MAX_DIST){ + distance = dist.Magnitude(); + if(distance < MAX_DIST){ + aLights[NumLights].type = type; + aLights[NumLights].fogType = fogType; + aLights[NumLights].coors = coors; + aLights[NumLights].dir = dir; + aLights[NumLights].radius = radius; + aLights[NumLights].castExtraShadows = castExtraShadows; + if(distance < MAX_DIST*0.75f){ + aLights[NumLights].red = red; + aLights[NumLights].green = green; + aLights[NumLights].blue = blue; + }else{ + float fade = 1.0f - (distance/MAX_DIST - 0.75f)*4.0f; + aLights[NumLights].red = red * fade; + aLights[NumLights].green = green * fade; + aLights[NumLights].blue = blue * fade; + } + NumLights++; + } + } +} + +float +CPointLights::GenerateLightsAffectingObject(Const CVector *objCoors) +{ + int i; + float ret; + CVector dist; + float radius, distance; + + ret = 1.0f; + for(i = 0; i < NumLights; i++){ + if(aLights[i].type == LIGHT_FOGONLY || aLights[i].type == LIGHT_FOGONLY_ALWAYS) + continue; + + // same weird distance calculation. simplified here + dist = aLights[i].coors - *objCoors; + radius = aLights[i].radius; + if(Abs(dist.x) < radius && + Abs(dist.y) < radius && + Abs(dist.z) < radius){ + + distance = dist.Magnitude(); + if(distance < radius){ + + float distNorm = distance/radius; + if(aLights[i].type == LIGHT_DARKEN){ + // darken the object the closer it is + ret *= distNorm; + }else{ + float intensity; + // distance fade + if(distNorm < 0.5f) + intensity = 1.0f; + else + intensity = 1.0f - (distNorm - 0.5f)/(1.0f - 0.5f); + + if(distance != 0.0f){ + CVector dir = dist / distance; + + if(aLights[i].type == LIGHT_DIRECTIONAL){ + float dot = -DotProduct(dir, aLights[i].dir); + intensity *= Max((dot-0.5f)*2.0f, 0.0f); + } + + if(intensity > 0.0f) + AddAnExtraDirectionalLight(Scene.world, + dir.x, dir.y, dir.z, + aLights[i].red*intensity, aLights[i].green*intensity, aLights[i].blue*intensity); + } + } + } + } + } + + return ret; +} + +extern RwRaster *gpPointlightRaster; + +void +CPointLights::RemoveLightsAffectingObject(void) +{ + RemoveExtraDirectionalLights(Scene.world); +} + +// for directional fog +#define FOG_AREA_LENGTH 12.0f +#define FOG_AREA_WIDTH 5.0f +// for pointlight fog +#define FOG_AREA_RADIUS 9.0f + +float FogSizes[8] = { 1.3f, 2.0f, 1.7f, 2.0f, 1.4f, 2.1f, 1.5f, 2.3f }; + +void +CPointLights::RenderFogEffect(void) +{ + int i; + float fogginess; + CColPoint point; + CEntity *entity; + float xmin, ymin; + float xmax, ymax; + int16 xi, yi; + CVector spriteCoors; + float spritew, spriteh; + + PUSH_RENDERGROUP("CPointLights::RenderFogEffect"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpPointlightRaster); + + for(i = 0; i < NumLights; i++){ + if(aLights[i].fogType != FOG_NORMAL && aLights[i].fogType != FOG_ALWAYS) + continue; + + fogginess = aLights[i].fogType == FOG_NORMAL ? CWeather::Foggyness : 1.0f; + if(fogginess == 0.0f) + continue; + + if(aLights[i].type == LIGHT_DIRECTIONAL){ + + // TODO: test this. haven't found directional fog so far + + float coors2X = aLights[i].coors.x + FOG_AREA_LENGTH*aLights[i].dir.x; + float coors2Y = aLights[i].coors.y + FOG_AREA_LENGTH*aLights[i].dir.y; + + if(coors2X < aLights[i].coors.x){ + xmin = coors2X; + xmax = aLights[i].coors.x; + }else{ + xmax = coors2X; + xmin = aLights[i].coors.x; + } + if(coors2Y < aLights[i].coors.y){ + ymin = coors2Y; + ymax = aLights[i].coors.y; + }else{ + ymax = coors2Y; + ymin = aLights[i].coors.y; + } + + xmin -= 5.0f; + ymin -= 5.0f; + xmax += 5.0f; + ymax += 5.0f; + + for(xi = (int16)xmin - (int16)xmin % 4; xi <= (int16)xmax + 4; xi += 4){ + for(yi = (int16)ymin - (int16)ymin % 4; yi <= (int16)ymax + 4; yi += 4){ + // Some kind of pseudo random number? + int r = (xi ^ yi)>>2 & 0xF; + if((r & 1) == 0) + continue; + + // Check if fog effect is close enough to directional line in x and y + float dx = xi - aLights[i].coors.x; + float dy = yi - aLights[i].coors.y; + float dot = dx*aLights[i].dir.x + dy*aLights[i].dir.y; + float distsq = sq(dx) + sq(dy); + float linedistsq = distsq - sq(dot); + if(dot > 0.0f && dot < FOG_AREA_LENGTH && linedistsq < sq(FOG_AREA_WIDTH)){ + CVector fogcoors(xi, yi, aLights[i].coors.z + 10.0f); + if(CWorld::ProcessVerticalLine(fogcoors, fogcoors.z - 20.0f, + point, entity, true, false, false, false, true, false, nil)){ + // Now same check again in xyz + fogcoors.z = point.point.z + 1.3f; + // actually we don't have to recalculate x and y, but the game does it that way + dx = xi - aLights[i].coors.x; + dy = yi - aLights[i].coors.y; + float dz = fogcoors.z - aLights[i].coors.z; + dot = dx*aLights[i].dir.x + dy*aLights[i].dir.y + dz*aLights[i].dir.z; + distsq = sq(dx) + sq(dy) + sq(dz); + linedistsq = distsq - sq(dot); + if(dot > 0.0f && dot < FOG_AREA_LENGTH && linedistsq < sq(FOG_AREA_WIDTH)){ + float intensity = 158.0f * fogginess; + // more intensity the smaller the angle + intensity *= dot/Sqrt(distsq); + // more intensity the closer to light source + intensity *= 1.0f - sq(dot/FOG_AREA_LENGTH); + // more intensity the closer to line + intensity *= 1.0f - sq(Sqrt(linedistsq) / FOG_AREA_WIDTH); + + if(CSprite::CalcScreenCoors(fogcoors, &spriteCoors, &spritew, &spriteh, true)){ + float rotation = (CTimer::GetTimeInMilliseconds()&0x1FFF) * 2*3.14f / 0x2000; + float size = FogSizes[r>>1]; + CSprite::RenderOneXLUSprite_Rotate_Aspect(spriteCoors.x, spriteCoors.y, spriteCoors.z, + spritew * size, spriteh * size, + aLights[i].red * intensity, aLights[i].green * intensity, aLights[i].blue * intensity, + intensity, 1/spriteCoors.z, rotation, 255); + } + } + } + } + } + } + + }else if(aLights[i].type == LIGHT_POINT || aLights[i].type == LIGHT_FOGONLY || aLights[i].type == LIGHT_FOGONLY_ALWAYS){ + if(CWorld::ProcessVerticalLine(aLights[i].coors, aLights[i].coors.z - 20.0f, + point, entity, true, false, false, false, true, false, nil)){ + + xmin = aLights[i].coors.x - FOG_AREA_RADIUS; + ymin = aLights[i].coors.y - FOG_AREA_RADIUS; + xmax = aLights[i].coors.x + FOG_AREA_RADIUS; + ymax = aLights[i].coors.y + FOG_AREA_RADIUS; + + for(xi = (int16)xmin - (int16)xmin % 2; xi <= (int16)xmax + 2; xi += 2){ + for(yi = (int16)ymin - (int16)ymin % 2; yi <= (int16)ymax + 2; yi += 2){ + // Some kind of pseudo random number? + int r = (xi ^ yi)>>1 & 0xF; + if((r & 1) == 0) + continue; + + float dx = xi - aLights[i].coors.x; + float dy = yi - aLights[i].coors.y; + float lightdist = Sqrt(sq(dx) + sq(dy)); + if(lightdist < FOG_AREA_RADIUS){ + dx = xi - TheCamera.GetPosition().x; + dy = yi - TheCamera.GetPosition().y; + float camdist = Sqrt(sq(dx) + sq(dy)); + if(camdist < MAX_DIST){ + float intensity; + // distance fade + if(camdist < MAX_DIST/2) + intensity = 1.0f; + else + intensity = 1.0f - (camdist - MAX_DIST/2) / (MAX_DIST/2); + intensity *= 132.0f * fogginess; + // more intensity the closer to light source + intensity *= 1.0f - sq(lightdist / FOG_AREA_RADIUS); + + CVector fogcoors(xi, yi, point.point.z + 1.6f); + if(CSprite::CalcScreenCoors(fogcoors, &spriteCoors, &spritew, &spriteh, true)){ + float rotation = (CTimer::GetTimeInMilliseconds()&0x3FFF) * 2*3.14f / 0x4000; + float size = FogSizes[r>>1]; + CSprite::RenderOneXLUSprite_Rotate_Aspect(spriteCoors.x, spriteCoors.y, spriteCoors.z, + spritew * size, spriteh * size, + aLights[i].red * intensity, aLights[i].green * intensity, aLights[i].blue * intensity, + intensity, 1/spriteCoors.z, rotation, 255); + } + } + } + } + } + } + } + } + + POP_RENDERGROUP(); +} diff --git a/src/renderer/PointLights.h b/src/renderer/PointLights.h new file mode 100644 index 0000000..9e94328 --- /dev/null +++ b/src/renderer/PointLights.h @@ -0,0 +1,45 @@ +#pragma once + +class CRegisteredPointLight +{ +public: + CVector coors; + CVector dir; + float radius; + float red; + float green; + float blue; + int8 type; + int8 fogType; + bool castExtraShadows; +}; +VALIDATE_SIZE(CRegisteredPointLight, 0x2C); + +class CPointLights +{ +public: + static int16 NumLights; + static CRegisteredPointLight aLights[NUMPOINTLIGHTS]; + + enum { + LIGHT_POINT, + LIGHT_DIRECTIONAL, + LIGHT_DARKEN, // no effects at all + // these have only fog, otherwise no difference? + // only used by CEntity::ProcessLightsForEntity it seems + // and there used together with fog type + LIGHT_FOGONLY_ALWAYS, + LIGHT_FOGONLY, + }; + enum { + FOG_NONE, + FOG_NORMAL, // taken from Foggyness + FOG_ALWAYS + }; + + static void InitPerFrame(void); + static void AddLight(uint8 type, CVector coors, CVector dir, float radius, float red, float green, float blue, uint8 fogType, bool castExtraShadows); + static float GenerateLightsAffectingObject(Const CVector *objCoors); + static void RemoveLightsAffectingObject(void); + static void RenderFogEffect(void); +}; diff --git a/src/renderer/RenderBuffer.cpp b/src/renderer/RenderBuffer.cpp new file mode 100644 index 0000000..6120dfe --- /dev/null +++ b/src/renderer/RenderBuffer.cpp @@ -0,0 +1,52 @@ +#include "common.h" + +#include "RenderBuffer.h" + +int32 TempBufferVerticesStored; +int32 TempBufferIndicesStored; + +RwIm3DVertex TempBufferRenderVertices[TEMPBUFFERVERTSIZE]; +RwImVertexIndex TempBufferRenderIndexList[TEMPBUFFERINDEXSIZE]; + +int RenderBuffer::VerticesToBeStored; +int RenderBuffer::IndicesToBeStored; + +void +RenderBuffer::ClearRenderBuffer(void) +{ + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; +} + +void +RenderBuffer::StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart) +{ + if(TempBufferIndicesStored + numIndices >= TEMPBUFFERINDEXSIZE) + RenderStuffInBuffer(); + if(TempBufferVerticesStored + numVertices >= TEMPBUFFERVERTSIZE) + RenderStuffInBuffer(); + *indexStart = &TempBufferRenderIndexList[TempBufferIndicesStored]; + *vertexStart = &TempBufferRenderVertices[TempBufferVerticesStored]; + IndicesToBeStored = numIndices; + VerticesToBeStored = numVertices; +} + +void +RenderBuffer::StopStoring(void) +{ + int i; + for(i = TempBufferIndicesStored; i < TempBufferIndicesStored+IndicesToBeStored; i++) + TempBufferRenderIndexList[i] += TempBufferVerticesStored; + TempBufferIndicesStored += IndicesToBeStored; + TempBufferVerticesStored += VerticesToBeStored; +} + +void +RenderBuffer::RenderStuffInBuffer(void) +{ + if(TempBufferVerticesStored && RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + ClearRenderBuffer(); +} diff --git a/src/renderer/RenderBuffer.h b/src/renderer/RenderBuffer.h new file mode 100644 index 0000000..485d24e --- /dev/null +++ b/src/renderer/RenderBuffer.h @@ -0,0 +1,18 @@ +class RenderBuffer +{ +public: + static int VerticesToBeStored; + static int IndicesToBeStored; + static void ClearRenderBuffer(void); + static void StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart); + static void StopStoring(void); + static void RenderStuffInBuffer(void); +}; + +#define TEMPBUFFERVERTSIZE 256 +#define TEMPBUFFERINDEXSIZE 1024 + +extern int32 TempBufferVerticesStored; +extern int32 TempBufferIndicesStored; +extern RwIm3DVertex TempBufferRenderVertices[TEMPBUFFERVERTSIZE]; +extern RwImVertexIndex TempBufferRenderIndexList[TEMPBUFFERINDEXSIZE]; \ No newline at end of file diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp new file mode 100644 index 0000000..334f395 --- /dev/null +++ b/src/renderer/Renderer.cpp @@ -0,0 +1,1841 @@ +#define WITHD3D +#include "common.h" + +#include "main.h" +#include "Lights.h" +#include "ModelInfo.h" +#include "Treadable.h" +#include "Ped.h" +#include "Vehicle.h" +#include "Boat.h" +#include "Heli.h" +#include "Object.h" +#include "PathFind.h" +#include "Collision.h" +#include "VisibilityPlugins.h" +#include "Clock.h" +#include "World.h" +#include "Camera.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "Shadows.h" +#include "PointLights.h" +#include "Renderer.h" +#include "Frontend.h" +#include "custompipes.h" +#include "Debug.h" + +bool gbShowPedRoadGroups; +bool gbShowCarRoadGroups; +bool gbShowCollisionPolys; +bool gbShowCollisionLines; +bool gbShowCullZoneDebugStuff; +bool gbDisableZoneCull; // not original +bool gbBigWhiteDebugLightSwitchedOn; + +bool gbDontRenderBuildings; +bool gbDontRenderBigBuildings; +bool gbDontRenderPeds; +bool gbDontRenderObjects; +bool gbDontRenderVehicles; + +int32 EntitiesRendered; +int32 EntitiesNotRendered; +int32 RenderedBigBuildings; +int32 RenderedBuildings; +int32 RenderedCars; +int32 RenderedPeds; +int32 RenderedObjects; +int32 RenderedDummies; +int32 TestedBigBuildings; +int32 TestedBuildings; +int32 TestedCars; +int32 TestedPeds; +int32 TestedObjects; +int32 TestedDummies; + +// unused +int16 TestCloseThings; +int16 TestBigThings; + +struct EntityInfo +{ + CEntity *ent; + float sort; +}; + +CLinkList gSortedVehiclesAndPeds; + +int32 CRenderer::ms_nNoOfVisibleEntities; +CEntity *CRenderer::ms_aVisibleEntityPtrs[NUMVISIBLEENTITIES]; +CEntity *CRenderer::ms_aInVisibleEntityPtrs[NUMINVISIBLEENTITIES]; +int32 CRenderer::ms_nNoOfInVisibleEntities; +#ifdef NEW_RENDERER +int32 CRenderer::ms_nNoOfVisibleVehicles; +CEntity *CRenderer::ms_aVisibleVehiclePtrs[NUMVISIBLEENTITIES]; +int32 CRenderer::ms_nNoOfVisibleBuildings; +CEntity *CRenderer::ms_aVisibleBuildingPtrs[NUMVISIBLEENTITIES]; + +CLinkList gSortedBuildings; +#endif + +CVector CRenderer::ms_vecCameraPosition; +CVehicle *CRenderer::m_pFirstPersonVehicle; +bool CRenderer::m_loadingPriority; +float CRenderer::ms_lodDistScale = 1.2f; + +// unused +BlockedRange CRenderer::aBlockedRanges[16]; +BlockedRange *CRenderer::pFullBlockedRanges; +BlockedRange *CRenderer::pEmptyBlockedRanges; + +void +CRenderer::Init(void) +{ + gSortedVehiclesAndPeds.Init(40); + SortBIGBuildings(); +#ifdef NEW_RENDERER + gSortedBuildings.Init(NUMVISIBLEENTITIES); +#endif +} + +void +CRenderer::Shutdown(void) +{ + gSortedVehiclesAndPeds.Shutdown(); +#ifdef NEW_RENDERER + gSortedBuildings.Shutdown(); +#endif +} + +void +CRenderer::PreRender(void) +{ + int i; + CLink *node; + + for(i = 0; i < ms_nNoOfVisibleEntities; i++) + ms_aVisibleEntityPtrs[i]->PreRender(); + +#ifdef NEW_RENDERER + if(gbNewRenderer){ + for(i = 0; i < ms_nNoOfVisibleVehicles; i++) + ms_aVisibleVehiclePtrs[i]->PreRender(); + // How is this done with cWorldStream? + //for(i = 0; i < ms_nNoOfVisibleBuildings; i++) + // ms_aVisibleBuildingPtrs[i]->PreRender(); + for(CLink *node = gSortedBuildings.head.next; + node != &gSortedBuildings.tail; + node = node->next) + ((CEntity*)node->item.ent)->PreRender(); + for(node = CVisibilityPlugins::m_alphaBuildingList.head.next; + node != &CVisibilityPlugins::m_alphaBuildingList.tail; + node = node->next) + ((CEntity*)node->item.entity)->PreRender(); + } +#endif + + for (i = 0; i < ms_nNoOfInVisibleEntities; i++) { +#ifdef SQUEEZE_PERFORMANCE + if (ms_aInVisibleEntityPtrs[i]->IsVehicle() && ((CVehicle*)ms_aInVisibleEntityPtrs[i])->IsHeli()) +#endif + ms_aInVisibleEntityPtrs[i]->PreRender(); + } + + for(node = CVisibilityPlugins::m_alphaEntityList.head.next; + node != &CVisibilityPlugins::m_alphaEntityList.tail; + node = node->next) + ((CEntity*)node->item.entity)->PreRender(); + + CHeli::SpecialHeliPreRender(); + CShadows::RenderExtraPlayerShadows(); +} + +void +CRenderer::RenderOneRoad(CEntity *e) +{ +#ifndef MASTER + if(gbDontRenderBuildings) + return; + if(gbShowCollisionPolys) + CCollision::DrawColModel_Coloured(e->GetMatrix(), *CModelInfo::GetColModel(e->GetModelIndex()), e->GetModelIndex()); + else +#endif + { +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachGlossPipe(e->GetAtomic()); +#endif + PUSH_RENDERGROUP(CModelInfo::GetModelInfo(e->GetModelIndex())->GetModelName()); + +#ifdef EXTRA_MODEL_FLAGS + if(!e->IsBuilding() || CModelInfo::GetModelInfo(e->GetModelIndex())->RenderDoubleSided()){ + BACKFACE_CULLING_OFF; + e->Render(); + BACKFACE_CULLING_ON; + }else +#endif + e->Render(); + + POP_RENDERGROUP(); + } +} + +void +CRenderer::RenderOneNonRoad(CEntity *e) +{ + CPed *ped; + CVehicle *veh; + int i; + bool resetLights; + +#ifndef MASTER + if(gbShowCollisionPolys){ + if(!e->IsVehicle()){ + CCollision::DrawColModel_Coloured(e->GetMatrix(), *CModelInfo::GetColModel(e->GetModelIndex()), e->GetModelIndex()); + return; + } + }else if(e->IsBuilding()){ + if(e->bIsBIGBuilding){ + if(gbDontRenderBigBuildings) + return; + }else{ + if(gbDontRenderBuildings) + return; + } + }else +#endif + if(e->IsPed()){ +#ifndef MASTER + if(gbDontRenderPeds) + return; +#endif + ped = (CPed*)e; + if(ped->m_nPedState == PED_DRIVING) + return; + } +#ifndef MASTER + else if(e->IsObject() || e->IsDummy()){ + if(gbDontRenderObjects) + return; + }else if(e->IsVehicle()){ + // re3 addition + if(gbDontRenderVehicles) + return; + } +#endif + + PUSH_RENDERGROUP(CModelInfo::GetModelInfo(e->GetModelIndex())->GetModelName()); + + resetLights = e->SetupLighting(); + + if(e->IsVehicle()) + CVisibilityPlugins::InitAlphaAtomicList(); + + // Render Peds in vehicle before vehicle itself + if(e->IsVehicle()){ + veh = (CVehicle*)e; + if(veh->pDriver && veh->pDriver->m_nPedState == PED_DRIVING) + veh->pDriver->Render(); + for(i = 0; i < 8; i++) + if(veh->pPassengers[i] && veh->pPassengers[i]->m_nPedState == PED_DRIVING) + veh->pPassengers[i]->Render(); + BACKFACE_CULLING_OFF; + } +#ifdef EXTRA_MODEL_FLAGS + if(!e->IsBuilding() || CModelInfo::GetModelInfo(e->GetModelIndex())->RenderDoubleSided()){ + BACKFACE_CULLING_OFF; + e->Render(); + BACKFACE_CULLING_ON; + }else +#endif + e->Render(); + + if(e->IsVehicle()){ + BACKFACE_CULLING_OFF; + e->bImBeingRendered = true; + CVisibilityPlugins::RenderAlphaAtomics(); + e->bImBeingRendered = false; + BACKFACE_CULLING_ON; + } + + e->RemoveLighting(resetLights); + + POP_RENDERGROUP(); +} + +void +CRenderer::RenderFirstPersonVehicle(void) +{ + if(m_pFirstPersonVehicle == nil) + return; + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RenderOneNonRoad(m_pFirstPersonVehicle); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); +} + +inline bool IsRoad(CEntity *e) { return e->IsBuilding() && ((CBuilding*)e)->GetIsATreadable(); } + +void +CRenderer::RenderRoads(void) +{ + int i; + CTreadable *t; + + PUSH_RENDERGROUP("CRenderer::RenderRoads"); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + BACKFACE_CULLING_ON; + DeActivateDirectional(); + SetAmbientColours(); + + for(i = 0; i < ms_nNoOfVisibleEntities; i++){ + t = (CTreadable*)ms_aVisibleEntityPtrs[i]; + if(IsRoad(t)){ +#ifndef MASTER + if(gbShowCarRoadGroups || gbShowPedRoadGroups){ + int ind = 0; + if(gbShowCarRoadGroups) + ind += ThePaths.m_pathNodes[t->m_nodeIndices[PATH_CAR][0]].group; + if(gbShowPedRoadGroups) + ind += ThePaths.m_pathNodes[t->m_nodeIndices[PATH_PED][0]].group; + SetAmbientColoursToIndicateRoadGroup(ind); + } +#endif + RenderOneRoad(t); +#ifndef MASTER + if(gbShowCarRoadGroups || gbShowPedRoadGroups) + ReSetAmbientAndDirectionalColours(); +#endif + } + } + POP_RENDERGROUP(); +} + +void +CRenderer::RenderEverythingBarRoads(void) +{ + int i; + CEntity *e; + CVector dist; + EntityInfo ei; + + PUSH_RENDERGROUP("CRenderer::RenderEverythingBarRoads"); + BACKFACE_CULLING_ON; + gSortedVehiclesAndPeds.Clear(); + + for(i = 0; i < ms_nNoOfVisibleEntities; i++){ + e = ms_aVisibleEntityPtrs[i]; + + if(IsRoad(e)) + continue; + +#ifdef EXTENDED_PIPELINES + if(CustomPipes::bRenderingEnvMap && (e->IsPed() || e->IsVehicle())) + continue; +#endif + + if(e->IsVehicle() || + e->IsPed() && CVisibilityPlugins::GetClumpAlpha((RpClump*)e->m_rwObject) != 255){ + if(e->IsVehicle() && ((CVehicle*)e)->IsBoat()){ + ei.ent = e; + dist = ms_vecCameraPosition - e->GetPosition(); + ei.sort = dist.MagnitudeSqr(); + gSortedVehiclesAndPeds.InsertSorted(ei); + }else{ + dist = ms_vecCameraPosition - e->GetPosition(); + if(!CVisibilityPlugins::InsertEntityIntoSortedList(e, dist.Magnitude())){ + printf("Ran out of space in alpha entity list"); + RenderOneNonRoad(e); + } + } + }else + RenderOneNonRoad(e); + } + POP_RENDERGROUP(); +} + +void +CRenderer::RenderVehiclesButNotBoats(void) +{ + // This function doesn't do anything + // because only boats are inserted into the list + CLink *node; + + for(node = gSortedVehiclesAndPeds.tail.prev; + node != &gSortedVehiclesAndPeds.head; + node = node->prev){ + // only boats in this list + CVehicle *v = (CVehicle*)node->item.ent; + if(!v->IsBoat()) + RenderOneNonRoad(v); + } +} + +void +CRenderer::RenderBoats(void) +{ + CLink *node; + + PUSH_RENDERGROUP("CRenderer::RenderBoats"); + BACKFACE_CULLING_ON; + + for(node = gSortedVehiclesAndPeds.tail.prev; + node != &gSortedVehiclesAndPeds.head; + node = node->prev){ + // only boats in this list + CVehicle *v = (CVehicle*)node->item.ent; + if(v->IsBoat()) + RenderOneNonRoad(v); + } + POP_RENDERGROUP(); +} + +#ifdef NEW_RENDERER +#ifndef LIBRW +#error "Need librw for EXTENDED_PIPELINES" +#endif +#include "WaterLevel.h" + +enum { + // blend passes + PASS_NOZ, // no z-write + PASS_ADD, // additive + PASS_BLEND // normal blend +}; + +static RwRGBAReal black; + +static void +SetStencilState(int state) +{ + switch(state){ + // disable stencil + case 0: + rw::SetRenderState(rw::STENCILENABLE, FALSE); + break; + // test against stencil + case 1: + rw::SetRenderState(rw::STENCILENABLE, TRUE); + rw::SetRenderState(rw::STENCILFUNCTION, rw::STENCILNOTEQUAL); + rw::SetRenderState(rw::STENCILPASS, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILFAIL, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILZFAIL, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILFUNCTIONMASK, 0xFF); + rw::SetRenderState(rw::STENCILFUNCTIONREF, 0xFF); + break; + // write to stencil + case 2: + rw::SetRenderState(rw::STENCILENABLE, TRUE); + rw::SetRenderState(rw::STENCILFUNCTION, rw::STENCILALWAYS); + rw::SetRenderState(rw::STENCILPASS, rw::STENCILREPLACE); + rw::SetRenderState(rw::STENCILFUNCTIONREF, 0xFF); + break; + } +} + +void +CRenderer::RenderOneBuilding(CEntity *ent, float camdist) +{ + if(ent->m_rwObject == nil) + return; + + ent->bImBeingRendered = true; // TODO: this seems wrong, but do we even need it? + + assert(RwObjectGetType(ent->m_rwObject) == rpATOMIC); + RpAtomic *atomic = (RpAtomic*)ent->m_rwObject; + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->GetModelIndex()); + +#ifdef EXTRA_MODEL_FLAGS + bool resetCull = false; + if(!ent->IsBuilding() || mi->RenderDoubleSided()){ + resetCull = true; + BACKFACE_CULLING_OFF; + } +#endif + + int pass = PASS_BLEND; + if(mi->m_additive) // very questionable + pass = PASS_ADD; + if(mi->m_noZwrite) + pass = PASS_NOZ; + + if(ent->bDistanceFade){ + RpAtomic *lodatm; + float fadefactor; + uint32 alpha; + + lodatm = mi->GetAtomicFromDistance(camdist - FADE_DISTANCE); + fadefactor = (mi->GetLargestLodDistance() - (camdist - FADE_DISTANCE))/FADE_DISTANCE; + if(fadefactor > 1.0f) + fadefactor = 1.0f; + alpha = mi->m_alpha * fadefactor; + + if(alpha == 255) + WorldRender::AtomicFirstPass(atomic, pass); + else{ + // not quite sure what this is about, do we have to do that? + RpGeometry *geo = RpAtomicGetGeometry(lodatm); + if(geo != RpAtomicGetGeometry(atomic)) + RpAtomicSetGeometry(atomic, geo, rpATOMICSAMEBOUNDINGSPHERE); + WorldRender::AtomicFullyTransparent(atomic, pass, alpha); + } + }else + WorldRender::AtomicFirstPass(atomic, pass); + +#ifdef EXTRA_MODEL_FLAGS + if(resetCull) + BACKFACE_CULLING_ON; +#endif + + ent->bImBeingRendered = false; // TODO: this seems wrong, but do we even need it? +} + +void +CRenderer::RenderWorld(int pass) +{ + int i; + CEntity *e; + CLink *node; + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + BACKFACE_CULLING_ON; + DeActivateDirectional(); + SetAmbientColours(); + + // Temporary...have to figure out sorting better + switch(pass){ + case 0: + // Roads + PUSH_RENDERGROUP("CRenderer::RenderWorld - Roads"); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); +/* + for(i = 0; i < ms_nNoOfVisibleBuildings; i++){ + e = ms_aVisibleBuildingPtrs[i]; + if(e->bIsBIGBuilding || IsRoad(e)) + RenderOneBuilding(e); + } +*/ + for(CLink *node = gSortedBuildings.tail.prev; + node != &gSortedBuildings.head; + node = node->prev){ + e = node->item.ent; + if(e->bIsBIGBuilding || IsRoad(e)) + RenderOneBuilding(e); + } + for(node = CVisibilityPlugins::m_alphaBuildingList.tail.prev; + node != &CVisibilityPlugins::m_alphaBuildingList.head; + node = node->prev){ + e = node->item.entity; + if(e->bIsBIGBuilding || IsRoad(e)) + RenderOneBuilding(e, node->item.sort); + } + + // KLUDGE for road puddles which have to be rendered at road-time + // only very temporary, there are more rendering issues + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + WorldRender::RenderBlendPass(PASS_BLEND); + WorldRender::numBlendInsts[PASS_BLEND] = 0; + POP_RENDERGROUP(); + break; + case 1: + // Opaque + PUSH_RENDERGROUP("CRenderer::RenderWorld - Opaque"); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); +/* + for(i = 0; i < ms_nNoOfVisibleBuildings; i++){ + e = ms_aVisibleBuildingPtrs[i]; + if(!(e->bIsBIGBuilding || IsRoad(e))) + RenderOneBuilding(e); + } +*/ + for(CLink *node = gSortedBuildings.tail.prev; + node != &gSortedBuildings.head; + node = node->prev){ + e = node->item.ent; + if(!(e->bIsBIGBuilding || IsRoad(e))) + RenderOneBuilding(e); + } + for(node = CVisibilityPlugins::m_alphaBuildingList.tail.prev; + node != &CVisibilityPlugins::m_alphaBuildingList.head; + node = node->prev){ + e = node->item.entity; + if(!(e->bIsBIGBuilding || IsRoad(e))) + RenderOneBuilding(e, node->item.sort); + } + // Now we have iterated through all visible buildings (unsorted and sorted) + // and the transparency list is done. + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + WorldRender::RenderBlendPass(PASS_NOZ); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + POP_RENDERGROUP(); + break; + case 2: + // Transparent + PUSH_RENDERGROUP("CRenderer::RenderWorld - Transparent"); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + WorldRender::RenderBlendPass(PASS_ADD); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + WorldRender::RenderBlendPass(PASS_BLEND); + POP_RENDERGROUP(); + break; + } +} + +void +CRenderer::RenderPeds(void) +{ + int i; + CEntity *e; + + PUSH_RENDERGROUP("CRenderer::RenderPeds"); + for(i = 0; i < ms_nNoOfVisibleVehicles; i++){ + e = ms_aVisibleVehiclePtrs[i]; + if(e->IsPed()) + RenderOneNonRoad(e); + } + POP_RENDERGROUP(); +} + +void +CRenderer::RenderVehicles(void) +{ + int i; + CEntity *e; + EntityInfo ei; + CLink *node; + + PUSH_RENDERGROUP("CRenderer::RenderVehicles"); + // not the real thing + for(i = 0; i < ms_nNoOfVisibleVehicles; i++){ + e = ms_aVisibleVehiclePtrs[i]; + if(!e->IsVehicle()) + continue; +// if(PutIntoSortedVehicleList((CVehicle*)e)) +// continue; // boats handled elsewhere + ei.ent = e; + ei.sort = (ms_vecCameraPosition - e->GetPosition()).MagnitudeSqr(); + gSortedVehiclesAndPeds.InsertSorted(ei); + } + + for(node = gSortedVehiclesAndPeds.tail.prev; + node != &gSortedVehiclesAndPeds.head; + node = node->prev) + RenderOneNonRoad(node->item.ent); + POP_RENDERGROUP(); +} + +void +CRenderer::RenderWater(void) +{ + int i; + CEntity *e; + + PUSH_RENDERGROUP("CRenderer::RenderWater"); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + SetStencilState(2); + + for(i = 0; i < ms_nNoOfVisibleVehicles; i++){ + e = ms_aVisibleVehiclePtrs[i]; + if(e->IsVehicle() && ((CVehicle*)e)->IsBoat()) + ((CBoat*)e)->RenderWaterOutPolys(); + } + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + SetStencilState(1); + + CWaterLevel::RenderWater(); + + SetStencilState(0); + POP_RENDERGROUP(); +} + +void +CRenderer::ClearForFrame(void) +{ + ms_nNoOfVisibleEntities = 0; + ms_nNoOfVisibleVehicles = 0; + ms_nNoOfVisibleBuildings = 0; + ms_nNoOfInVisibleEntities = 0; + gSortedVehiclesAndPeds.Clear(); + gSortedBuildings.Clear(); + + WorldRender::numBlendInsts[PASS_NOZ] = 0; + WorldRender::numBlendInsts[PASS_ADD] = 0; + WorldRender::numBlendInsts[PASS_BLEND] = 0; +} +#endif + +void +CRenderer::RenderFadingInEntities(void) +{ + PUSH_RENDERGROUP("CRenderer::RenderFadingInEntities"); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + BACKFACE_CULLING_ON; + DeActivateDirectional(); + SetAmbientColours(); + CVisibilityPlugins::RenderFadingEntities(); + POP_RENDERGROUP(); +} + +void +CRenderer::RenderCollisionLines(void) +{ + int i; + + // game doesn't draw fading in entities + // this should probably be fixed + for(i = 0; i < ms_nNoOfVisibleEntities; i++){ + CEntity *e = ms_aVisibleEntityPtrs[i]; + if(Abs(e->GetPosition().x - ms_vecCameraPosition.x) < 100.0f && + Abs(e->GetPosition().y - ms_vecCameraPosition.y) < 100.0f) + CCollision::DrawColModel(e->GetMatrix(), *e->GetColModel()); + } +} + +// unused +void +CRenderer::RenderBlockBuildingLines(void) +{ + for(BlockedRange *br = pFullBlockedRanges; br; br = br->next) + printf("Blocked: %f %f\n", br->a, br->b); +} + +enum Visbility +{ + VIS_INVISIBLE, + VIS_VISIBLE, + VIS_OFFSCREEN, + VIS_STREAMME +}; + +// Time Objects can be time culled if +// other == -1 || CModelInfo::GetModelInfo(other)->GetRwObject() +// i.e. we have to draw even at the wrong time if +// other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject() == nil + +#define OTHERUNAVAILABLE (other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject() == nil) +#define CANTIMECULL (!OTHERUNAVAILABLE) + +int32 +CRenderer::SetupEntityVisibility(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->m_modelIndex); + CTimeModelInfo *ti; + int32 other; + float dist; + + bool request = true; + if (mi->GetModelType() == MITYPE_TIME) { + ti = (CTimeModelInfo*)mi; + other = ti->GetOtherTimeModel(); + if(CClock::GetIsTimeInRange(ti->GetTimeOn(), ti->GetTimeOff())){ + // don't fade in, or between time objects + if(CANTIMECULL) + ti->m_alpha = 255; + }else{ + // Hide if possible + if(CANTIMECULL) + return VIS_INVISIBLE; + // can't cull, so we'll try to draw this one, but don't request + // it since what we really want is the other one. + request = false; + } + }else{ + if (mi->GetModelType() != MITYPE_SIMPLE) { + if(FindPlayerVehicle() == ent && + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON){ + // Player's vehicle in first person mode + if(TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_FORWARD || + ent->GetModelIndex() == MI_RHINO || + ent->GetModelIndex() == MI_COACH || + TheCamera.m_bInATunnelAndABigVehicle){ + ent->bNoBrightHeadLights = true; + }else{ + m_pFirstPersonVehicle = (CVehicle*)ent; + ent->bNoBrightHeadLights = false; + } + return VIS_OFFSCREEN; + } + // All sorts of Clumps + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + if(!ent->GetIsOnScreen()) + return VIS_OFFSCREEN; + if(ent->bDrawLast){ + dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = false; + return VIS_INVISIBLE; + } + return VIS_VISIBLE; + } + if(ent->IsObject() && + ((CObject*)ent)->ObjectCreatedBy == TEMP_OBJECT){ + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + return ent->GetIsOnScreen() ? VIS_VISIBLE : VIS_OFFSCREEN; + } + } + + // Simple ModelInfo + + dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + + // This can only happen with multi-atomic models (e.g. railtracks) + // but why do we bump up the distance? can only be fading... + if(LOD_DISTANCE + STREAM_DISTANCE < dist && dist < mi->GetLargestLodDistance()) + dist = mi->GetLargestLodDistance(); + + if(ent->IsObject() && ent->bRenderDamaged) + mi->m_isDamaged = true; + + RpAtomic *a = mi->GetAtomicFromDistance(dist); + if(a){ + mi->m_isDamaged = false; + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + // Make sure our atomic uses the right geometry and not + // that of an atomic for another draw distance. + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + mi->IncreaseAlpha(); + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + + if(!ent->GetIsOnScreen()){ + mi->m_alpha = 255; + return VIS_OFFSCREEN; + } + + if(mi->m_alpha != 255){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = true; + return VIS_INVISIBLE; + } + + if(mi->m_drawLast || ent->bDrawLast){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = false; + return VIS_INVISIBLE; + } + return VIS_VISIBLE; + } + + // Object is not loaded, figure out what to do + + if(mi->m_noFade){ + mi->m_isDamaged = false; + // request model + if(dist - STREAM_DISTANCE < mi->GetLargestLodDistance() && request) + return VIS_STREAMME; + return VIS_INVISIBLE; + } + + // We might be fading + + a = mi->GetAtomicFromDistance(dist - FADE_DISTANCE); + mi->m_isDamaged = false; + if(a == nil){ + // request model + if(dist - FADE_DISTANCE - STREAM_DISTANCE < mi->GetLargestLodDistance() && request) + return VIS_STREAMME; + return VIS_INVISIBLE; + } + + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + mi->IncreaseAlpha(); + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + + if(!ent->GetIsOnScreen()){ + mi->m_alpha = 255; + return VIS_OFFSCREEN; + }else{ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = true; + return VIS_OFFSCREEN; // Why this? + } +} + +int32 +CRenderer::SetupBigBuildingVisibility(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo *)CModelInfo::GetModelInfo(ent->GetModelIndex()); + CTimeModelInfo *ti; + int32 other; + + if (mi->GetModelType() == MITYPE_TIME) { + ti = (CTimeModelInfo*)mi; + other = ti->GetOtherTimeModel(); + // Hide objects not in time range if possible + if(CANTIMECULL) + if(!CClock::GetIsTimeInRange(ti->GetTimeOn(), ti->GetTimeOff())) + return VIS_INVISIBLE; + // Draw like normal + } else if (mi->GetModelType() == MITYPE_VEHICLE) + return ent->IsVisible() ? VIS_VISIBLE : VIS_INVISIBLE; + + float dist = (ms_vecCameraPosition-ent->GetPosition()).Magnitude(); + CSimpleModelInfo *nonLOD = mi->GetRelatedModel(); + + // Find out whether to draw below near distance. + // This is only the case if there is a non-LOD which is either not + // loaded or not completely faded in yet. + if(dist < mi->GetNearDistance() && dist < LOD_DISTANCE + STREAM_DISTANCE){ + // No non-LOD or non-LOD is completely visible. + if(nonLOD == nil || + nonLOD->GetRwObject() && nonLOD->m_alpha == 255) + return VIS_INVISIBLE; + + // But if it is a time object, we'd rather draw the wrong + // non-LOD than the right LOD. + if (nonLOD->GetModelType() == MITYPE_TIME) { + ti = (CTimeModelInfo*)nonLOD; + other = ti->GetOtherTimeModel(); + if(other != -1 && CModelInfo::GetModelInfo(other)->GetRwObject()) + return VIS_INVISIBLE; + } + } + + RpAtomic *a = mi->GetAtomicFromDistance(dist); + if(a){ + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + + // Make sure our atomic uses the right geometry and not + // that of an atomic for another draw distance. + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + if (!ent->IsVisible() || !ent->GetIsOnScreenComplex()) + return VIS_INVISIBLE; + if(mi->m_drawLast){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = false; + return VIS_INVISIBLE; + } + return VIS_VISIBLE; + } + + if(mi->m_noFade){ + ent->DeleteRwObject(); + return VIS_INVISIBLE; + } + + + // get faded atomic + a = mi->GetAtomicFromDistance(dist - FADE_DISTANCE); + if(a == nil){ + ent->DeleteRwObject(); + return VIS_INVISIBLE; + } + + // Fade... + if(ent->m_rwObject == nil) + ent->CreateRwObject(); + assert(ent->m_rwObject); + RpAtomic *rwobj = (RpAtomic*)ent->m_rwObject; + if(RpAtomicGetGeometry(a) != RpAtomicGetGeometry(rwobj)) + RpAtomicSetGeometry(rwobj, RpAtomicGetGeometry(a), rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + if (ent->IsVisible() && ent->GetIsOnScreenComplex()) + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + return VIS_INVISIBLE; +} + +void +CRenderer::ConstructRenderList(void) +{ +#ifdef NEW_RENDERER + if(!gbNewRenderer) +#endif +{ + ms_nNoOfVisibleEntities = 0; + ms_nNoOfInVisibleEntities = 0; +} + ms_vecCameraPosition = TheCamera.GetPosition(); + + // unused + pFullBlockedRanges = nil; + pEmptyBlockedRanges = aBlockedRanges; + for(int i = 0; i < 16; i++){ + aBlockedRanges[i].prev = &aBlockedRanges[i-1]; + aBlockedRanges[i].next = &aBlockedRanges[i+1]; + } + aBlockedRanges[0].prev = nil; + aBlockedRanges[15].next = nil; + + // unused + TestCloseThings = 0; + TestBigThings = 0; + + ScanWorld(); +} + +void +LimitFrustumVector(CVector &vec1, const CVector &vec2, float l) +{ + float f; + f = (l - vec2.z) / (vec1.z - vec2.z); + vec1.x = f*(vec1.x - vec2.x) + vec2.x; + vec1.y = f*(vec1.y - vec2.y) + vec2.y; + vec1.z = f*(vec1.z - vec2.z) + vec2.z; +} + +enum Corners +{ + CORNER_CAM = 0, + CORNER_FAR_TOPLEFT, + CORNER_FAR_TOPRIGHT, + CORNER_FAR_BOTRIGHT, + CORNER_FAR_BOTLEFT, + CORNER_LOD_LEFT, + CORNER_LOD_RIGHT, + CORNER_PRIO_LEFT, + CORNER_PRIO_RIGHT, +}; + +void +CRenderer::ScanWorld(void) +{ + float f = RwCameraGetFarClipPlane(TheCamera.m_pRwCamera); + RwV2d vw = *RwCameraGetViewWindow(TheCamera.m_pRwCamera); + CVector vectors[9]; + RwMatrix *cammatrix; + RwV2d poly[3]; + +#ifndef MASTER + // missing in game but has to be done somewhere + EntitiesRendered = 0; + EntitiesNotRendered = 0; + RenderedBigBuildings = 0; + RenderedBuildings = 0; + RenderedCars = 0; + RenderedPeds = 0; + RenderedObjects = 0; + RenderedDummies = 0; + TestedBigBuildings = 0; + TestedBuildings = 0; + TestedCars = 0; + TestedPeds = 0; + TestedObjects = 0; + TestedDummies = 0; +#endif + + memset(vectors, 0, sizeof(vectors)); + vectors[CORNER_FAR_TOPLEFT].x = -vw.x * f; + vectors[CORNER_FAR_TOPLEFT].y = vw.y * f; + vectors[CORNER_FAR_TOPLEFT].z = f; + vectors[CORNER_FAR_TOPRIGHT].x = vw.x * f; + vectors[CORNER_FAR_TOPRIGHT].y = vw.y * f; + vectors[CORNER_FAR_TOPRIGHT].z = f; + vectors[CORNER_FAR_BOTRIGHT].x = vw.x * f; + vectors[CORNER_FAR_BOTRIGHT].y = -vw.y * f; + vectors[CORNER_FAR_BOTRIGHT].z = f; + vectors[CORNER_FAR_BOTLEFT].x = -vw.x * f; + vectors[CORNER_FAR_BOTLEFT].y = -vw.y * f; + vectors[CORNER_FAR_BOTLEFT].z = f; + + cammatrix = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + + m_pFirstPersonVehicle = nil; + CVisibilityPlugins::InitAlphaEntityList(); + CWorld::AdvanceCurrentScanCode(); + + if(cammatrix->at.z > 0.0f){ + // looking up, bottom corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_BOTLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_BOTRIGHT] * LOD_DISTANCE/f; + }else{ + // looking down, top corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_TOPLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_TOPRIGHT] * LOD_DISTANCE/f; + } + vectors[CORNER_PRIO_LEFT].x = vectors[CORNER_LOD_LEFT].x * 0.2f; + vectors[CORNER_PRIO_LEFT].y = vectors[CORNER_LOD_LEFT].y * 0.2f; + vectors[CORNER_PRIO_LEFT].z = vectors[CORNER_LOD_LEFT].z; + vectors[CORNER_PRIO_RIGHT].x = vectors[CORNER_LOD_RIGHT].x * 0.2f; + vectors[CORNER_PRIO_RIGHT].y = vectors[CORNER_LOD_RIGHT].y * 0.2f; + vectors[CORNER_PRIO_RIGHT].z = vectors[CORNER_LOD_RIGHT].z; + RwV3dTransformPoints(vectors, vectors, 9, cammatrix); + + m_loadingPriority = false; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || +#ifdef FIX_BUGS + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_GTACLASSIC || +#endif + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CRect rect; + int x1, x2, y1, y2; + LimitFrustumVector(vectors[CORNER_FAR_TOPLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPLEFT]); + LimitFrustumVector(vectors[CORNER_FAR_TOPRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTLEFT]); + x1 = CWorld::GetSectorIndexX(rect.left); + if(x1 < 0) x1 = 0; + x2 = CWorld::GetSectorIndexX(rect.right); + if(x2 >= NUMSECTORS_X-1) x2 = NUMSECTORS_X-1; + y1 = CWorld::GetSectorIndexY(rect.top); + if(y1 < 0) y1 = 0; + y2 = CWorld::GetSectorIndexY(rect.bottom); + if(y2 >= NUMSECTORS_Y-1) y2 = NUMSECTORS_Y-1; + for(; x1 <= x2; x1++) + for(int y = y1; y <= y2; y++) + ScanSectorList(CWorld::GetSector(x1, y)->m_lists); + }else{ + CVehicle *train = FindPlayerTrain(); + if(train && train->GetPosition().z < 0.0f){ + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList_Subway); + }else{ + if(f > LOD_DISTANCE){ + // priority + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_PRIO_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_PRIO_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_PRIO_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_PRIO_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList_Priority); + + // below LOD + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList); + }else{ + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_FAR_TOPLEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_FAR_TOPLEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_FAR_TOPRIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_FAR_TOPRIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList); + } +#ifdef NO_ISLAND_LOADING + if (CMenuManager::m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_HIGH) { + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_INDUSTRIAL)); + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_COMMERCIAL)); + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_SUBURBAN)); + } else +#endif + { + #ifdef FIX_BUGS + if (CCollision::ms_collisionInMemory != LEVEL_GENERIC) + #endif + ScanBigBuildingList(CWorld::GetBigBuildingList(CCollision::ms_collisionInMemory)); + } + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_GENERIC)); + } + } + +#ifndef MASTER + if(gbShowCullZoneDebugStuff){ + sprintf(gString, "Rejected: %d/%d.", EntitiesNotRendered, EntitiesNotRendered + EntitiesRendered); + CDebug::PrintAt(gString, 10, 10); + sprintf(gString, "Tested:BBuild:%d Build:%d Peds:%d Cars:%d Obj:%d Dummies:%d", + TestedBigBuildings, TestedBuildings, TestedPeds, TestedCars, TestedObjects, TestedDummies); + CDebug::PrintAt(gString, 10, 11); + sprintf(gString, "Rendered:BBuild:%d Build:%d Peds:%d Cars:%d Obj:%d Dummies:%d", + RenderedBigBuildings, RenderedBuildings, RenderedPeds, RenderedCars, RenderedObjects, RenderedDummies); + CDebug::PrintAt(gString, 10, 12); + } +#endif +} + +void +CRenderer::RequestObjectsInFrustum(void) +{ + float f = RwCameraGetFarClipPlane(TheCamera.m_pRwCamera); + RwV2d vw = *RwCameraGetViewWindow(TheCamera.m_pRwCamera); + CVector vectors[9]; + RwMatrix *cammatrix; + RwV2d poly[3]; + + memset(vectors, 0, sizeof(vectors)); + vectors[CORNER_FAR_TOPLEFT].x = -vw.x * f; + vectors[CORNER_FAR_TOPLEFT].y = vw.y * f; + vectors[CORNER_FAR_TOPLEFT].z = f; + vectors[CORNER_FAR_TOPRIGHT].x = vw.x * f; + vectors[CORNER_FAR_TOPRIGHT].y = vw.y * f; + vectors[CORNER_FAR_TOPRIGHT].z = f; + vectors[CORNER_FAR_BOTRIGHT].x = vw.x * f; + vectors[CORNER_FAR_BOTRIGHT].y = -vw.y * f; + vectors[CORNER_FAR_BOTRIGHT].z = f; + vectors[CORNER_FAR_BOTLEFT].x = -vw.x * f; + vectors[CORNER_FAR_BOTLEFT].y = -vw.y * f; + vectors[CORNER_FAR_BOTLEFT].z = f; + + cammatrix = RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)); + + CWorld::AdvanceCurrentScanCode(); + + if(cammatrix->at.z > 0.0f){ + // looking up, bottom corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_BOTLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_BOTRIGHT] * LOD_DISTANCE/f; + }else{ + // looking down, top corners are further away + vectors[CORNER_LOD_LEFT] = vectors[CORNER_FAR_TOPLEFT] * LOD_DISTANCE/f; + vectors[CORNER_LOD_RIGHT] = vectors[CORNER_FAR_TOPRIGHT] * LOD_DISTANCE/f; + } + vectors[CORNER_PRIO_LEFT].x = vectors[CORNER_LOD_LEFT].x * 0.2f; + vectors[CORNER_PRIO_LEFT].y = vectors[CORNER_LOD_LEFT].y * 0.2f; + vectors[CORNER_PRIO_LEFT].z = vectors[CORNER_LOD_LEFT].z; + vectors[CORNER_PRIO_RIGHT].x = vectors[CORNER_LOD_RIGHT].x * 0.2f; + vectors[CORNER_PRIO_RIGHT].y = vectors[CORNER_LOD_RIGHT].y * 0.2f; + vectors[CORNER_PRIO_RIGHT].z = vectors[CORNER_LOD_RIGHT].z; + RwV3dTransformPoints(vectors, vectors, 9, cammatrix); + + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || +#ifdef FIX_BUGS + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_GTACLASSIC || +#endif + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED){ + CRect rect; + int x1, x2, y1, y2; + LimitFrustumVector(vectors[CORNER_FAR_TOPLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPLEFT]); + LimitFrustumVector(vectors[CORNER_FAR_TOPRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_TOPRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTRIGHT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTRIGHT]); + LimitFrustumVector(vectors[CORNER_FAR_BOTLEFT], vectors[CORNER_CAM], -100.0f); + rect.ContainPoint(vectors[CORNER_FAR_BOTLEFT]); + x1 = CWorld::GetSectorIndexX(rect.left); + if(x1 < 0) x1 = 0; + x2 = CWorld::GetSectorIndexX(rect.right); + if(x2 >= NUMSECTORS_X-1) x2 = NUMSECTORS_X-1; + y1 = CWorld::GetSectorIndexY(rect.top); + if(y1 < 0) y1 = 0; + y2 = CWorld::GetSectorIndexY(rect.bottom); + if(y2 >= NUMSECTORS_Y-1) y2 = NUMSECTORS_Y-1; + for(; x1 <= x2; x1++) + for(int y = y1; y <= y2; y++) + ScanSectorList_RequestModels(CWorld::GetSector(x1, y)->m_lists); + }else{ + poly[0].x = CWorld::GetSectorX(vectors[CORNER_CAM].x); + poly[0].y = CWorld::GetSectorY(vectors[CORNER_CAM].y); + poly[1].x = CWorld::GetSectorX(vectors[CORNER_LOD_LEFT].x); + poly[1].y = CWorld::GetSectorY(vectors[CORNER_LOD_LEFT].y); + poly[2].x = CWorld::GetSectorX(vectors[CORNER_LOD_RIGHT].x); + poly[2].y = CWorld::GetSectorY(vectors[CORNER_LOD_RIGHT].y); + ScanSectorPoly(poly, 3, ScanSectorList_RequestModels); + } +} + +bool +CEntity::SetupLighting(void) +{ + DeActivateDirectional(); + SetAmbientColours(); + return false; +} + +void +CEntity::RemoveLighting(bool) +{ +} + +bool +CPed::SetupLighting(void) +{ + ActivateDirectional(); + SetAmbientColoursForPedsCarsAndObjects(); + +#ifndef MASTER + // Originally this was being called through iteration of Sectors, but putting it here is better. + if (GetDebugDisplay() != 0 && !IsPlayer()) + DebugRenderOnePedText(); +#endif + + if (bRenderScorched) { + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + } else { + // Note that this lightMult is only affected by LIGHT_DARKEN. If there's no LIGHT_DARKEN, it will be 1.0. + float lightMult = CPointLights::GenerateLightsAffectingObject(&GetPosition()); + if (!bHasBlip && lightMult != 1.0f) { + SetAmbientAndDirectionalColours(lightMult); + return true; + } + } + return false; +} + +void +CPed::RemoveLighting(bool reset) +{ + CRenderer::RemoveVehiclePedLights(this, reset); +} + +float +CalcNewDelta(RwV2d *a, RwV2d *b) +{ + return (b->x - a->x) / (b->y - a->y); +} + +#ifdef FIX_BUGS +#define TOINT(x) ((int)Floor(x)) +#else +#define TOINT(x) ((int)(x)) +#endif + +void +CRenderer::ScanSectorPoly(RwV2d *poly, int32 numVertices, void (*scanfunc)(CPtrList *)) +{ + float miny, maxy; + int y, yend; + int x, xstart, xend; + int i; + int a1, a2, b1, b2; + float deltaA, deltaB; + float xA, xB; + + miny = poly[0].y; + maxy = poly[0].y; + a2 = 0; + xstart = 9999; + xend = -9999; + + for(i = 1; i < numVertices; i++){ + if(poly[i].y > maxy) + maxy = poly[i].y; + if(poly[i].y < miny){ + miny = poly[i].y; + a2 = i; + } + } + y = TOINT(miny); + yend = TOINT(maxy); + + // Go left in poly to find first edge b + b2 = a2; + for(i = 0; i < numVertices; i++){ + b1 = b2--; + if(b2 < 0) b2 = numVertices-1; + if(poly[b1].x < xstart) + xstart = TOINT(poly[b1].x); + if(TOINT(poly[b1].y) != TOINT(poly[b2].y)) + break; + } + // Go right to find first edge a + for(i = 0; i < numVertices; i++){ + a1 = a2++; + if(a2 == numVertices) a2 = 0; + if(poly[a1].x > xend) + xend = TOINT(poly[a1].x); + if(TOINT(poly[a1].y) != TOINT(poly[a2].y)) + break; + } + + // prestep x1 and x2 to next integer y + deltaA = CalcNewDelta(&poly[a1], &poly[a2]); + xA = deltaA * (Ceil(poly[a1].y) - poly[a1].y) + poly[a1].x; + deltaB = CalcNewDelta(&poly[b1], &poly[b2]); + xB = deltaB * (Ceil(poly[b1].y) - poly[b1].y) + poly[b1].x; + + if(y != yend){ + if(deltaB < 0.0f && TOINT(xB) < xstart) + xstart = TOINT(xB); + if(deltaA >= 0.0f && TOINT(xA) > xend) + xend = TOINT(xA); + } + + while(y <= yend && y < NUMSECTORS_Y){ + // scan one x-line + if(y >= 0 && xstart < NUMSECTORS_X) + for(x = xstart; x <= xend && x != NUMSECTORS_X; x++) + if(x >= 0) + scanfunc(CWorld::GetSector(x, y)->m_lists); + + // advance one scan line + y++; + xA += deltaA; + xB += deltaB; + + // update left side + if(y == TOINT(poly[b2].y)){ + // reached end of edge + if(y == yend){ + if(deltaB < 0.0f){ + do{ + xstart = TOINT(poly[b2--].x); + if(b2 < 0) b2 = numVertices-1; + }while(xstart > TOINT(poly[b2].x)); + }else + xstart = TOINT(xB - deltaB); + }else{ + // switch edges + if(deltaB < 0.0f) + xstart = TOINT(poly[b2].x); + else + xstart = TOINT(xB - deltaB); + do{ + b1 = b2--; + if(b2 < 0) b2 = numVertices-1; + if(TOINT(poly[b1].x) < xstart) + xstart = TOINT(poly[b1].x); + }while(y == TOINT(poly[b2].y)); + deltaB = CalcNewDelta(&poly[b1], &poly[b2]); + xB = deltaB * (Ceil(poly[b1].y) - poly[b1].y) + poly[b1].x; + if(deltaB < 0.0f && TOINT(xB) < xstart) + xstart = TOINT(xB); + } + }else{ + if(deltaB < 0.0f) + xstart = TOINT(xB); + else + xstart = TOINT(xB - deltaB); + } + + // update right side + if(y == TOINT(poly[a2].y)){ + // reached end of edge + if(y == yend){ + if(deltaA < 0.0f) + xend = TOINT(xA - deltaA); + else{ + do{ + xend = TOINT(poly[a2++].x); + if(a2 == numVertices) a2 = 0; + }while(xend < TOINT(poly[a2].x)); + } + }else{ + // switch edges + if(deltaA < 0.0f) + xend = TOINT(xA - deltaA); + else + xend = TOINT(poly[a2].x); + do{ + a1 = a2++; + if(a2 == numVertices) a2 = 0; + if(TOINT(poly[a1].x) > xend) + xend = TOINT(poly[a1].x); + }while(y == TOINT(poly[a2].y)); + deltaA = CalcNewDelta(&poly[a1], &poly[a2]); + xA = deltaA * (Ceil(poly[a1].y) - poly[a1].y) + poly[a1].x; + if(deltaA >= 0.0f && TOINT(xA) > xend) + xend = TOINT(xA); + } + }else{ + if(deltaA < 0.0f) + xend = TOINT(xA - deltaA); + else + xend = TOINT(xA); + } + } +} + +void +CRenderer::InsertEntityIntoList(CEntity *ent) +{ +#ifdef FIX_BUGS + if (!ent->m_rwObject) return; +#endif + +#ifdef NEW_RENDERER + // TODO: there are more flags being checked here + if(gbNewRenderer && (ent->IsVehicle() || ent->IsPed())) + ms_aVisibleVehiclePtrs[ms_nNoOfVisibleVehicles++] = ent; + else if(gbNewRenderer && ent->IsBuilding()){ + EntityInfo info; + info.ent = ent; + info.sort = -(ent->GetPosition() - ms_vecCameraPosition).MagnitudeSqr(); + gSortedBuildings.InsertSorted(info); +// ms_aVisibleBuildingPtrs[ms_nNoOfVisibleBuildings++] = ent; + }else +#endif + ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent; +} + +void +CRenderer::ScanBigBuildingList(CPtrList &list) +{ + CPtrNode *node; + CEntity *ent; + + for(node = list.first; node; node = node->next){ + ent = (CEntity*)node->item; +#ifndef MASTER + // all missing from game actually + TestedBigBuildings++; +#endif + if(!ent->bZoneCulled || gbDisableZoneCull){ + if(SetupBigBuildingVisibility(ent) == VIS_VISIBLE) + InsertEntityIntoList(ent); +#ifndef MASTER + EntitiesRendered++; + RenderedBigBuildings++; + }else{ + EntitiesNotRendered++; +#endif + } + } +} + +void +CRenderer::ScanSectorList(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + float dx, dy; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + + if(IsEntityCullZoneVisible(ent)){ + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + break; + case VIS_INVISIBLE: + if(!IsGlass(ent->GetModelIndex())) + break; + // fall through + case VIS_OFFSCREEN: + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -65.0f && dx < 65.0f && + dy > -65.0f && dy < 65.0f && + ms_nNoOfInVisibleEntities < NUMINVISIBLEENTITIES - 1) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + case VIS_STREAMME: + if(!CStreaming::ms_disableStreaming) + if(!m_loadingPriority || CStreaming::ms_numModelsRequested < 10) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + break; + } +#ifndef MASTER + EntitiesRendered++; + switch(ent->GetType()){ + case ENTITY_TYPE_BUILDING: + if(ent->bIsBIGBuilding) + RenderedBigBuildings++; + else + RenderedBuildings++; + break; + case ENTITY_TYPE_VEHICLE: + RenderedCars++; + break; + case ENTITY_TYPE_PED: + RenderedPeds++; + break; + case ENTITY_TYPE_OBJECT: + RenderedObjects++; + break; + case ENTITY_TYPE_DUMMY: + RenderedDummies++; + break; + } +#endif + }else if(IsRoad(ent) && !CStreaming::ms_disableStreaming){ + if(SetupEntityVisibility(ent) == VIS_STREAMME) + if(!m_loadingPriority || CStreaming::ms_numModelsRequested < 10) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + }else{ +#ifndef MASTER + EntitiesNotRendered++; +#endif + } + } + } +} + +void +CRenderer::ScanSectorList_Priority(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + float dx, dy; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + + if(IsEntityCullZoneVisible(ent)){ + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + break; + case VIS_INVISIBLE: + if(!IsGlass(ent->GetModelIndex())) + break; + // fall through + case VIS_OFFSCREEN: + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -65.0f && dx < 65.0f && + dy > -65.0f && dy < 65.0f && + ms_nNoOfInVisibleEntities < NUMINVISIBLEENTITIES - 1) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + case VIS_STREAMME: + if(!CStreaming::ms_disableStreaming){ + CStreaming::RequestModel(ent->GetModelIndex(), 0); + if(CStreaming::ms_aInfoForModel[ent->GetModelIndex()].m_loadState != STREAMSTATE_LOADED) + m_loadingPriority = true; + } + break; + } +#ifndef MASTER + // actually missing in game + EntitiesRendered++; + switch(ent->GetType()){ + case ENTITY_TYPE_BUILDING: + if(ent->bIsBIGBuilding) + RenderedBigBuildings++; + else + RenderedBuildings++; + break; + case ENTITY_TYPE_VEHICLE: + RenderedCars++; + break; + case ENTITY_TYPE_PED: + RenderedPeds++; + break; + case ENTITY_TYPE_OBJECT: + RenderedObjects++; + break; + case ENTITY_TYPE_DUMMY: + RenderedDummies++; + break; + } +#endif + }else if(IsRoad(ent) && !CStreaming::ms_disableStreaming){ + if(SetupEntityVisibility(ent) == VIS_STREAMME) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + }else{ +#ifndef MASTER + // actually missing in game + EntitiesNotRendered++; +#endif + } + } + } +} + +void +CRenderer::ScanSectorList_Subway(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + float dx, dy; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + break; + case VIS_OFFSCREEN: + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -65.0f && dx < 65.0f && + dy > -65.0f && dy < 65.0f && + ms_nNoOfInVisibleEntities < NUMINVISIBLEENTITIES - 1) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + } + } + } +} + +void +CRenderer::ScanSectorList_RequestModels(CPtrList *lists) +{ + CPtrNode *node; + CPtrList *list; + CEntity *ent; + int i; + + for(i = 0; i < NUMSECTORENTITYLISTS; i++){ + list = &lists[i]; + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->m_scanCode == CWorld::GetCurrentScanCode()) + continue; // already seen + ent->m_scanCode = CWorld::GetCurrentScanCode(); + if(IsEntityCullZoneVisible(ent)) + if(ShouldModelBeStreamed(ent)) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + } + } +} + +// Put big buildings in front +// This seems pointless because the sector lists shouldn't have big buildings in the first place +void +CRenderer::SortBIGBuildings(void) +{ + int x, y; + for(y = 0; y < NUMSECTORS_Y; y++) + for(x = 0; x < NUMSECTORS_X; x++){ + SortBIGBuildingsForSectorList(&CWorld::GetSector(x, y)->m_lists[ENTITYLIST_BUILDINGS]); + SortBIGBuildingsForSectorList(&CWorld::GetSector(x, y)->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + } +} + +void +CRenderer::SortBIGBuildingsForSectorList(CPtrList *list) +{ + CPtrNode *node; + CEntity *ent; + + for(node = list->first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->bIsBIGBuilding){ + list->RemoveNode(node); + list->InsertNode(node); + } + } +} + +bool +CRenderer::ShouldModelBeStreamed(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo *)CModelInfo::GetModelInfo(ent->GetModelIndex()); + float dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + if(mi->m_noFade) + return dist - STREAM_DISTANCE < mi->GetLargestLodDistance(); + else + return dist - FADE_DISTANCE - STREAM_DISTANCE < mi->GetLargestLodDistance(); +} + +bool +CRenderer::IsEntityCullZoneVisible(CEntity *ent) +{ + CPed *ped; + CObject *obj; + + if(gbDisableZoneCull) return true; + +#ifndef MASTER + switch(ent->GetType()){ + case ENTITY_TYPE_BUILDING: + if(ent->bIsBIGBuilding) + TestedBigBuildings++; + else + TestedBuildings++; + break; + case ENTITY_TYPE_VEHICLE: + TestedCars++; + break; + case ENTITY_TYPE_PED: + TestedPeds++; + break; + case ENTITY_TYPE_OBJECT: + TestedObjects++; + break; + case ENTITY_TYPE_DUMMY: + TestedDummies++; + break; + } +#endif + if(ent->bZoneCulled) + return false; + + + switch(ent->GetType()){ + case ENTITY_TYPE_VEHICLE: + return IsVehicleCullZoneVisible(ent); + case ENTITY_TYPE_PED: + ped = (CPed*)ent; + if (ped->bInVehicle) { + if (ped->m_pMyVehicle) + return IsVehicleCullZoneVisible(ped->m_pMyVehicle); + else + return true; + } + return !(ped->m_pCurSurface && ped->m_pCurSurface->bZoneCulled2); + case ENTITY_TYPE_OBJECT: + obj = (CObject*)ent; + if(!obj->GetIsStatic()) + return true; + return !(obj->m_pCurSurface && obj->m_pCurSurface->bZoneCulled2); + default: break; + } + return true; +} + +bool +CRenderer::IsVehicleCullZoneVisible(CEntity *ent) +{ + CVehicle *v = (CVehicle*)ent; + switch(v->GetStatus()) { + case STATUS_SIMPLE: + case STATUS_PHYSICS: + case STATUS_ABANDONED: + case STATUS_WRECKED: + return !(v->m_pCurGroundEntity && v->m_pCurGroundEntity->bZoneCulled2); + default: break; + } + return true; +} + +void +CRenderer::RemoveVehiclePedLights(CEntity *ent, bool reset) +{ + if(ent->bRenderScorched){ + WorldReplaceScorchedLightsWithNormal(Scene.world); + return; + } + CPointLights::RemoveLightsAffectingObject(); + if(reset) + ReSetAmbientAndDirectionalColours(); +} diff --git a/src/renderer/Renderer.h b/src/renderer/Renderer.h new file mode 100644 index 0000000..0322939 --- /dev/null +++ b/src/renderer/Renderer.h @@ -0,0 +1,119 @@ +#pragma once + +class CEntity; + +#ifdef FIX_BUGS +#define LOD_DISTANCE (300.0f*TheCamera.LODDistMultiplier) +#else +#define LOD_DISTANCE 300.0f +#endif +#define FADE_DISTANCE 20.0f +#define STREAM_DISTANCE 30.0f + +#ifdef EXTRA_MODEL_FLAGS +#define BACKFACE_CULLING_ON SetCullMode(rwCULLMODECULLBACK) +#define BACKFACE_CULLING_OFF SetCullMode(rwCULLMODECULLNONE) +#else +#define BACKFACE_CULLING_ON +#define BACKFACE_CULLING_OFF +#endif + +extern bool gbShowPedRoadGroups; +extern bool gbShowCarRoadGroups; +extern bool gbShowCollisionPolys; +extern bool gbShowCollisionLines; +extern bool gbShowCullZoneDebugStuff; +extern bool gbDisableZoneCull; // not original +extern bool gbBigWhiteDebugLightSwitchedOn; + +extern bool gbDontRenderBuildings; +extern bool gbDontRenderBigBuildings; +extern bool gbDontRenderPeds; +extern bool gbDontRenderObjects; +extern bool gbDontRenderVehicles; + +class CVehicle; +class CPtrList; + +// unused +struct BlockedRange +{ + float a, b; // unknown + BlockedRange *prev, *next; +}; + +class CRenderer +{ + static int32 ms_nNoOfVisibleEntities; + static CEntity *ms_aVisibleEntityPtrs[NUMVISIBLEENTITIES]; + static int32 ms_nNoOfInVisibleEntities; + static CEntity *ms_aInVisibleEntityPtrs[NUMINVISIBLEENTITIES]; +#ifdef NEW_RENDERER + static int32 ms_nNoOfVisibleVehicles; + static CEntity *ms_aVisibleVehiclePtrs[NUMVISIBLEENTITIES]; + // for cWorldStream emulation + static int32 ms_nNoOfVisibleBuildings; + static CEntity *ms_aVisibleBuildingPtrs[NUMVISIBLEENTITIES]; +#endif + + static CVector ms_vecCameraPosition; + static CVehicle *m_pFirstPersonVehicle; + + // unused + static BlockedRange aBlockedRanges[16]; + static BlockedRange *pFullBlockedRanges; + static BlockedRange *pEmptyBlockedRanges; +public: + static float ms_lodDistScale; + static bool m_loadingPriority; + + static void Init(void); + static void Shutdown(void); + static void PreRender(void); + + static void RenderRoads(void); + static void RenderFadingInEntities(void); + static void RenderEverythingBarRoads(void); + static void RenderVehiclesButNotBoats(void); + static void RenderBoats(void); + static void RenderOneRoad(CEntity *); + static void RenderOneNonRoad(CEntity *); + static void RenderFirstPersonVehicle(void); + + static void RenderCollisionLines(void); + // unused + static void RenderBlockBuildingLines(void); + + static int32 SetupEntityVisibility(CEntity *ent); + static int32 SetupBigBuildingVisibility(CEntity *ent); + + static void ConstructRenderList(void); + static void ScanWorld(void); + static void RequestObjectsInFrustum(void); + static void ScanSectorPoly(RwV2d *poly, int32 numVertices, void (*scanfunc)(CPtrList *)); + static void ScanBigBuildingList(CPtrList &list); + static void ScanSectorList(CPtrList *lists); + static void ScanSectorList_Priority(CPtrList *lists); + static void ScanSectorList_Subway(CPtrList *lists); + static void ScanSectorList_RequestModels(CPtrList *lists); + + static void SortBIGBuildings(void); + static void SortBIGBuildingsForSectorList(CPtrList *list); + + static bool ShouldModelBeStreamed(CEntity *ent); + static bool IsEntityCullZoneVisible(CEntity *ent); + static bool IsVehicleCullZoneVisible(CEntity *ent); + + static void RemoveVehiclePedLights(CEntity *ent, bool reset); + + +#ifdef NEW_RENDERER + static void ClearForFrame(void); + static void RenderPeds(void); + static void RenderVehicles(void); // also renders peds in LCS + static void RenderOneBuilding(CEntity *ent, float camdist = 0.0f); + static void RenderWorld(int pass); // like cWorldStream::Render(int) + static void RenderWater(void); // keep-out polys and water +#endif + static void InsertEntityIntoList(CEntity *ent); +}; diff --git a/src/renderer/Rubbish.cpp b/src/renderer/Rubbish.cpp new file mode 100644 index 0000000..8da6b02 --- /dev/null +++ b/src/renderer/Rubbish.cpp @@ -0,0 +1,436 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "Timer.h" +#include "Weather.h" +#include "Camera.h" +#include "World.h" +#include "Vehicle.h" +#include "ZoneCull.h" +#include "TxdStore.h" +#include "RenderBuffer.h" +#include "Rubbish.h" + +#define RUBBISH_MAX_DIST (18.0f) +#define RUBBISH_FADE_DIST (16.5f) + +RwTexture *gpRubbishTexture[4]; +RwImVertexIndex RubbishIndexList[6]; +RwImVertexIndex RubbishIndexList2[6]; // unused +RwIm3DVertex RubbishVertices[4]; +bool CRubbish::bRubbishInvisible; +int CRubbish::RubbishVisibility; +COneSheet CRubbish::aSheets[NUM_RUBBISH_SHEETS]; +COneSheet CRubbish::StartEmptyList; +COneSheet CRubbish::EndEmptyList; +COneSheet CRubbish::StartStaticsList; +COneSheet CRubbish::EndStaticsList; +COneSheet CRubbish::StartMoversList; +COneSheet CRubbish::EndMoversList; + + +void +COneSheet::AddToList(COneSheet *list) +{ + this->m_next = list->m_next; + this->m_prev = list; + list->m_next = this; + this->m_next->m_prev = this; +} + +void +COneSheet::RemoveFromList(void) +{ + m_next->m_prev = m_prev; + m_prev->m_next = m_next; +} + + +void +CRubbish::Render(void) +{ + int type; + + PUSH_RENDERGROUP("CRubbish::Render"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + + for(type = 0; type < 4; type++){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpRubbishTexture[type])); + + TempBufferIndicesStored = 0; + TempBufferVerticesStored = 0; + + COneSheet *sheet; + for(sheet = &aSheets[type*NUM_RUBBISH_SHEETS / 4]; + sheet < &aSheets[(type+1)*NUM_RUBBISH_SHEETS / 4]; + sheet++){ + if(sheet->m_state == 0) + continue; + + uint32 alpha = 128; + CVector pos; + if(sheet->m_state == 1){ + pos = sheet->m_basePos; + if(!sheet->m_isVisible) + alpha = 0; + }else{ + pos = sheet->m_animatedPos; + // Not fully visible during animation, calculate current alpha + if(!sheet->m_isVisible || !sheet->m_targetIsVisible){ + float t = (float)(CTimer::GetTimeInMilliseconds() - sheet->m_moveStart)/sheet->m_moveDuration; + float f1 = sheet->m_isVisible ? 1.0f-t : 0.0f; + float f2 = sheet->m_targetIsVisible ? t : 0.0f; + alpha = 128 * (f1+f2); + } + } + + float camDist = (pos - TheCamera.GetPosition()).Magnitude2D(); + if(camDist < RUBBISH_MAX_DIST){ + if(camDist >= RUBBISH_FADE_DIST) + alpha -= alpha*(camDist-RUBBISH_FADE_DIST)/(RUBBISH_MAX_DIST-RUBBISH_FADE_DIST); + alpha = (RubbishVisibility*alpha)/256; + + float vx = Sin(sheet->m_angle) * 0.4f; + float vy = Cos(sheet->m_angle) * 0.4f; + + int v = TempBufferVerticesStored; + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+0], pos.x + vx, pos.y + vy, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+0], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+1], pos.x - vy, pos.y + vx, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+1], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+2], pos.x + vy, pos.y - vx, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+2], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+3], pos.x - vx, pos.y - vy, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+3], 255, 255, 255, alpha); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+0], 0.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+0], 0.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+1], 1.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+1], 0.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+2], 0.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+2], 1.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+3], 1.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+3], 1.0f); + + int i = TempBufferIndicesStored; + TempBufferRenderIndexList[i+0] = RubbishIndexList[0] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+1] = RubbishIndexList[1] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+2] = RubbishIndexList[2] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+3] = RubbishIndexList[3] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+4] = RubbishIndexList[4] + TempBufferVerticesStored; + TempBufferRenderIndexList[i+5] = RubbishIndexList[5] + TempBufferVerticesStored; + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; + } + } + + if(TempBufferIndicesStored != 0){ + LittleTest(); + if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + } + } + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + + POP_RENDERGROUP(); +} + +void +CRubbish::StirUp(CVehicle *veh) +{ + if((CTimer::GetFrameCounter() ^ (veh->m_randomSeed&3)) == 0) + return; + + if(Abs(veh->GetPosition().x - TheCamera.GetPosition().x) < 20.0f && + Abs(veh->GetPosition().y - TheCamera.GetPosition().y) < 20.0f) + if(Abs(veh->GetMoveSpeed().x) > 0.05f || Abs(veh->GetMoveSpeed().y) > 0.05f){ + float speed = veh->GetMoveSpeed().Magnitude2D(); + if(speed > 0.05f){ + bool movingForward = DotProduct2D(veh->GetMoveSpeed(), veh->GetForward()) > 0.0f; + COneSheet *sheet = StartStaticsList.m_next; + CVector2D size = veh->GetColModel()->boundingBox.max; + + // Check all static sheets + while(sheet != &EndStaticsList){ + COneSheet *next = sheet->m_next; + CVector2D carToSheet = sheet->m_basePos - veh->GetPosition(); + float distFwd = DotProduct2D(carToSheet, veh->GetForward()); + + // sheet has to be a bit behind car + if(movingForward && distFwd < -0.5f*size.y && distFwd > -1.5f*size.y || + !movingForward && distFwd > 0.5f*size.y && distFwd < 1.5f*size.y){ + float distSide = Abs(DotProduct2D(carToSheet, veh->GetRight())); + if(distSide < 1.5*size.x){ + // Check with higher speed for sheet directly behind car + float speedToCheck = distSide < size.x ? speed : speed*0.5f; + if(speedToCheck > 0.05f){ + sheet->m_state = 2; + if(speedToCheck > 0.15f) + sheet->m_animationType = 2; + else + sheet->m_animationType = 1; + sheet->m_moveDuration = 2000; + sheet->m_xDist = veh->GetMoveSpeed().x; + sheet->m_yDist = veh->GetMoveSpeed().y; + float dist = Sqrt(SQR(sheet->m_xDist)+SQR(sheet->m_yDist)); + sheet->m_xDist *= 25.0f*speed/dist; + sheet->m_yDist *= 25.0f*speed/dist; + sheet->m_animHeight = 3.0f*speed; + sheet->m_moveStart = CTimer::GetTimeInMilliseconds(); + float tx = sheet->m_basePos.x + sheet->m_xDist; + float ty = sheet->m_basePos.y + sheet->m_yDist; + float tz = sheet->m_basePos.z + 3.0f; + sheet->m_targetZ = CWorld::FindGroundZFor3DCoord(tx, ty, tz, nil) + 0.1f; + sheet->RemoveFromList(); + sheet->AddToList(&StartMoversList); + } + } + } + + sheet = next; + } + } + } +} + +static float aAnimations[3][34] = { + { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, + + // Normal move + { 0.0f, 0.05f, 0.12f, 0.25f, 0.42f, 0.57f, 0.68f, 0.8f, 0.86f, 0.9f, 0.93f, 0.95f, 0.96f, 0.97f, 0.98f, 0.99f, 1.0f, // XY movemnt + 0.15f, 0.35f, 0.6f, 0.9f, 1.2f, 1.25f, 1.3f, 1.2f, 1.1f, 0.95f, 0.8f, 0.6f, 0.45f, 0.3f, 0.2f, 0.1f, 0 }, // Z movement + + // Stirred up by fast vehicle + { 0.0f, 0.05f, 0.12f, 0.25f, 0.42f, 0.57f, 0.68f, 0.8f, 0.95f, 1.1f, 1.15f, 1.18f, 1.15f, 1.1f, 1.05f, 1.03f, 1.0f, + 0.15f, 0.35f, 0.6f, 0.9f, 1.2f, 1.25f, 1.3f, 1.2f, 1.1f, 0.95f, 0.8f, 0.6f, 0.45f, 0.3f, 0.2f, 0.1f, 0 } +}; + +void +CRubbish::Update(void) +{ + bool foundGround; + + // FRAMETIME + if(bRubbishInvisible) + RubbishVisibility = Max(RubbishVisibility-5, 0); + else + RubbishVisibility = Min(RubbishVisibility+5, 255); + + // Spawn a new sheet + COneSheet *sheet = StartEmptyList.m_next; + if(sheet != &EndEmptyList){ + float spawnDist; + float spawnAngle; + + spawnDist = (CGeneral::GetRandomNumber()&0xFF)/256.0f + RUBBISH_MAX_DIST; + uint8 r = CGeneral::GetRandomNumber(); + if(r&1) + spawnAngle = (CGeneral::GetRandomNumber()&0xFF)/256.0f * 6.28f; + else + spawnAngle = (r-128)/160.0f + TheCamera.Orientation; + sheet->m_basePos.x = TheCamera.GetPosition().x + spawnDist*Sin(spawnAngle); + sheet->m_basePos.y = TheCamera.GetPosition().y + spawnDist*Cos(spawnAngle); + sheet->m_basePos.z = CWorld::FindGroundZFor3DCoord(sheet->m_basePos.x, sheet->m_basePos.y, TheCamera.GetPosition().z, &foundGround) + 0.1f; + if(foundGround){ + // Found ground, so add to statics list + sheet->m_angle = (CGeneral::GetRandomNumber()&0xFF)/256.0f * 6.28f; + sheet->m_state = 1; + if(CCullZones::FindAttributesForCoors(sheet->m_basePos, nil) & ATTRZONE_NORAIN) + sheet->m_isVisible = false; + else + sheet->m_isVisible = true; + sheet->RemoveFromList(); + sheet->AddToList(&StartStaticsList); + } + } + + // Process animation + sheet = StartMoversList.m_next; + while(sheet != &EndMoversList){ + uint32 currentTime = CTimer::GetTimeInMilliseconds() - sheet->m_moveStart; + if(currentTime < sheet->m_moveDuration){ + // Animation + int step = 16 * currentTime / sheet->m_moveDuration; // 16 steps in animation + int stepTime = sheet->m_moveDuration/16; // time in each step + float s = (float)(currentTime - stepTime*step) / stepTime; // position on step + float t = (float)currentTime / sheet->m_moveDuration; // position on total animation + // factors for xy and z-movment + float fxy = aAnimations[sheet->m_animationType][step]*(1.0f-s) + aAnimations[sheet->m_animationType][step+1]*s; + float fz = aAnimations[sheet->m_animationType][step+17]*(1.0f-s) + aAnimations[sheet->m_animationType][step+1+17]*s; + sheet->m_animatedPos.x = sheet->m_basePos.x + fxy*sheet->m_xDist; + sheet->m_animatedPos.y = sheet->m_basePos.y + fxy*sheet->m_yDist; + sheet->m_animatedPos.z = (1.0f-t)*sheet->m_basePos.z + t*sheet->m_targetZ + fz*sheet->m_animHeight; + sheet->m_angle += CTimer::GetTimeStep()*0.04f; + if(sheet->m_angle > 6.28f) + sheet->m_angle -= 6.28f; + sheet = sheet->m_next; + }else{ + // End of animation, back into statics list + sheet->m_basePos.x += sheet->m_xDist; + sheet->m_basePos.y += sheet->m_yDist; + sheet->m_basePos.z = sheet->m_targetZ; + sheet->m_state = 1; + sheet->m_isVisible = sheet->m_targetIsVisible; + + COneSheet *next = sheet->m_next; + sheet->RemoveFromList(); + sheet->AddToList(&StartStaticsList); + sheet = next; + } + } + + // Stir up a sheet by wind + // FRAMETIME + int freq; + if(CWeather::Wind < 0.1f) + freq = 31; + else if(CWeather::Wind < 0.4f) + freq = 7; + else if(CWeather::Wind < 0.7f) + freq = 1; + else + freq = 0; + if((CTimer::GetFrameCounter() & freq) == 0){ + // Pick a random sheet and set animation state if static + int i = CGeneral::GetRandomNumber() % NUM_RUBBISH_SHEETS; + if(aSheets[i].m_state == 1){ + aSheets[i].m_moveStart = CTimer::GetTimeInMilliseconds(); + aSheets[i].m_moveDuration = CWeather::Wind*1500.0f + 1000.0f; + aSheets[i].m_animHeight = 0.2f; + aSheets[i].m_xDist = 3.0f*CWeather::Wind; + aSheets[i].m_yDist = 3.0f*CWeather::Wind; + // Check if target position is ok + float tx = aSheets[i].m_basePos.x + aSheets[i].m_xDist; + float ty = aSheets[i].m_basePos.y + aSheets[i].m_yDist; + float tz = aSheets[i].m_basePos.z + 3.0f; + aSheets[i].m_targetZ = CWorld::FindGroundZFor3DCoord(tx, ty, tz, &foundGround) + 0.1f; + if(CCullZones::FindAttributesForCoors(CVector(tx, ty, aSheets[i].m_targetZ), nil) & ATTRZONE_NORAIN) + aSheets[i].m_targetIsVisible = false; + else + aSheets[i].m_targetIsVisible = true; + if(foundGround){ + // start animation + aSheets[i].m_state = 2; + aSheets[i].m_animationType = 1; + aSheets[i].RemoveFromList(); + aSheets[i].AddToList(&StartMoversList); + } + } + } + + // Remove sheets that are too far away + int i = (CTimer::GetFrameCounter()%(NUM_RUBBISH_SHEETS/4))*4; + int last = ((CTimer::GetFrameCounter()%(NUM_RUBBISH_SHEETS/4)) + 1)*4; + for(; i < last; i++){ + if(aSheets[i].m_state == 1 && + (aSheets[i].m_basePos - TheCamera.GetPosition()).MagnitudeSqr2D() > SQR(RUBBISH_MAX_DIST+1.0f)){ + aSheets[i].m_state = 0; + aSheets[i].RemoveFromList(); + aSheets[i].AddToList(&StartEmptyList); + } + } +} + +void +CRubbish::SetVisibility(bool visible) +{ + bRubbishInvisible = !visible; +} + +void +CRubbish::Init(void) +{ + int i; + for(i = 0; i < NUM_RUBBISH_SHEETS; i++){ + aSheets[i].m_state = 0; + if(i < NUM_RUBBISH_SHEETS-1) + aSheets[i].m_next = &aSheets[i+1]; + else + aSheets[i].m_next = &EndEmptyList; + if(i > 0) + aSheets[i].m_prev = &aSheets[i-1]; + else + aSheets[i].m_prev = &StartEmptyList; + } + + StartEmptyList.m_next = &aSheets[0]; + StartEmptyList.m_prev = nil; + EndEmptyList.m_next = nil; + EndEmptyList.m_prev = &aSheets[NUM_RUBBISH_SHEETS-1]; + + StartStaticsList.m_next = &EndStaticsList; + StartStaticsList.m_prev = nil; + EndStaticsList.m_next = nil; + EndStaticsList.m_prev = &StartStaticsList; + + StartMoversList.m_next = &EndMoversList; + StartMoversList.m_prev = nil; + EndMoversList.m_next = nil; + EndMoversList.m_prev = &StartMoversList; + + // unused + RwIm3DVertexSetU(&RubbishVertices[0], 0.0f); + RwIm3DVertexSetV(&RubbishVertices[0], 0.0f); + RwIm3DVertexSetU(&RubbishVertices[1], 1.0f); + RwIm3DVertexSetV(&RubbishVertices[1], 0.0f); + RwIm3DVertexSetU(&RubbishVertices[2], 0.0f); + RwIm3DVertexSetV(&RubbishVertices[2], 1.0f); + RwIm3DVertexSetU(&RubbishVertices[3], 1.0f); + RwIm3DVertexSetV(&RubbishVertices[3], 1.0f); + + // unused + RubbishIndexList2[0] = 0; + RubbishIndexList2[1] = 2; + RubbishIndexList2[2] = 1; + RubbishIndexList2[3] = 1; + RubbishIndexList2[4] = 2; + RubbishIndexList2[5] = 3; + + RubbishIndexList[0] = 0; + RubbishIndexList[1] = 1; + RubbishIndexList[2] = 2; + RubbishIndexList[3] = 1; + RubbishIndexList[4] = 3; + RubbishIndexList[5] = 2; + + CTxdStore::PushCurrentTxd(); + int slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + gpRubbishTexture[0] = RwTextureRead("gameleaf01_64", nil); + gpRubbishTexture[1] = RwTextureRead("gameleaf02_64", nil); + gpRubbishTexture[2] = RwTextureRead("newspaper01_64", nil); + gpRubbishTexture[3] = RwTextureRead("newspaper02_64", nil); + CTxdStore::PopCurrentTxd(); + RubbishVisibility = 255; + bRubbishInvisible = false; +} + +void +CRubbish::Shutdown(void) +{ + RwTextureDestroy(gpRubbishTexture[0]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubbishTexture[0] = nil; +#endif + RwTextureDestroy(gpRubbishTexture[1]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubbishTexture[1] = nil; +#endif + RwTextureDestroy(gpRubbishTexture[2]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubbishTexture[2] = nil; +#endif + RwTextureDestroy(gpRubbishTexture[3]); +#if GTA_VERSION >= GTA3_PC_11 + gpRubbishTexture[3] = nil; +#endif +} diff --git a/src/renderer/Rubbish.h b/src/renderer/Rubbish.h new file mode 100644 index 0000000..37f895f --- /dev/null +++ b/src/renderer/Rubbish.h @@ -0,0 +1,55 @@ +#pragma once + +class CVehicle; + +enum { + // NB: not all values are allowed, check the code +#ifdef SQUEEZE_PERFORMANCE + NUM_RUBBISH_SHEETS = 32 +#else + NUM_RUBBISH_SHEETS = 64 +#endif +}; + +class COneSheet +{ +public: + CVector m_basePos; + CVector m_animatedPos; + float m_targetZ; + int8 m_state; + int8 m_animationType; + uint32 m_moveStart; + uint32 m_moveDuration; + float m_animHeight; + float m_xDist; + float m_yDist; + float m_angle; + bool m_isVisible; + bool m_targetIsVisible; + COneSheet *m_next; + COneSheet *m_prev; + + void AddToList(COneSheet *list); + void RemoveFromList(void); +}; + +class CRubbish +{ + static bool bRubbishInvisible; + static int RubbishVisibility; + static COneSheet aSheets[NUM_RUBBISH_SHEETS]; + static COneSheet StartEmptyList; + static COneSheet EndEmptyList; + static COneSheet StartStaticsList; + static COneSheet EndStaticsList; + static COneSheet StartMoversList; + static COneSheet EndMoversList; +public: + static void Render(void); + static void StirUp(CVehicle *veh); // CAutomobile on PS2 + static void Update(void); + static void SetVisibility(bool visible); + static void Init(void); + static void Shutdown(void); +}; diff --git a/src/renderer/Shadows.cpp b/src/renderer/Shadows.cpp new file mode 100644 index 0000000..3884d3b --- /dev/null +++ b/src/renderer/Shadows.cpp @@ -0,0 +1,1785 @@ +#include "common.h" + +#include "main.h" +#include "TxdStore.h" +#include "Timer.h" +#include "Camera.h" +#include "Timecycle.h" +#include "CutsceneMgr.h" +#include "Automobile.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "World.h" +#include "Weather.h" +#include "ModelIndices.h" +#include "RenderBuffer.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif +#include "PointLights.h" +#include "SpecialFX.h" +#include "Shadows.h" + +#ifdef DEBUGMENU +//SETTWEAKPATH("Shadows"); +//TWEAKBOOL(gbPrintShite); +#endif + +RwImVertexIndex ShadowIndexList[24]; + +RwTexture *gpShadowCarTex; +RwTexture *gpShadowPedTex; +RwTexture *gpShadowHeliTex; +RwTexture *gpShadowExplosionTex; +RwTexture *gpShadowHeadLightsTex; +RwTexture *gpOutline1Tex; +RwTexture *gpOutline2Tex; +RwTexture *gpOutline3Tex; +RwTexture *gpBloodPoolTex; +RwTexture *gpReflectionTex; +RwTexture *gpGoalMarkerTex; +RwTexture *gpWalkDontTex; +RwTexture *gpCrackedGlassTex; +RwTexture *gpPostShadowTex; +RwTexture *gpGoalTex; + +int16 CShadows::ShadowsStoredToBeRendered; +CStoredShadow CShadows::asShadowsStored [MAX_STOREDSHADOWS]; +CPolyBunch CShadows::aPolyBunches [MAX_POLYBUNCHES]; +CStaticShadow CShadows::aStaticShadows [MAX_STATICSHADOWS]; +CPolyBunch *CShadows::pEmptyBunchList; +CPermanentShadow CShadows::aPermanentShadows[MAX_PERMAMENTSHADOWS]; + + +void +CShadows::Init(void) +{ + CTxdStore::PushCurrentTxd(); + + int32 slut = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slut); + + gpShadowCarTex = RwTextureRead("shad_car", NULL); + gpShadowPedTex = RwTextureRead("shad_ped", NULL); + gpShadowHeliTex = RwTextureRead("shad_heli", NULL); + gpShadowExplosionTex = RwTextureRead("shad_exp", NULL); + gpShadowHeadLightsTex = RwTextureRead("headlight", NULL); + gpOutline1Tex = RwTextureRead("outline_64", NULL); + gpOutline2Tex = RwTextureRead("outline2_64", NULL); + gpOutline3Tex = RwTextureRead("outline3_64", NULL); + gpBloodPoolTex = RwTextureRead("bloodpool_64", NULL); + gpReflectionTex = RwTextureRead("reflection01", NULL); + gpGoalMarkerTex = RwTextureRead("goal", NULL); + gpWalkDontTex = RwTextureRead("walk_dont", NULL); + gpCrackedGlassTex = RwTextureRead("wincrack_32", NULL); + gpPostShadowTex = RwTextureRead("lamp_shad_64", NULL); + + CTxdStore::PopCurrentTxd(); + + ASSERT(gpShadowCarTex != NULL); + ASSERT(gpShadowPedTex != NULL); + ASSERT(gpShadowHeliTex != NULL); + ASSERT(gpShadowExplosionTex != NULL); + ASSERT(gpShadowHeadLightsTex != NULL); + ASSERT(gpOutline1Tex != NULL); + ASSERT(gpOutline2Tex != NULL); + ASSERT(gpOutline3Tex != NULL); + ASSERT(gpBloodPoolTex != NULL); + ASSERT(gpReflectionTex != NULL); + ASSERT(gpGoalMarkerTex != NULL); + ASSERT(gpWalkDontTex != NULL); + ASSERT(gpCrackedGlassTex != NULL); + ASSERT(gpPostShadowTex != NULL); + + + ShadowIndexList[0] = 0; + ShadowIndexList[1] = 2; + ShadowIndexList[2] = 1; + + ShadowIndexList[3] = 0; + ShadowIndexList[4] = 3; + ShadowIndexList[5] = 2; + + ShadowIndexList[6] = 0; + ShadowIndexList[7] = 4; + ShadowIndexList[8] = 3; + + ShadowIndexList[9] = 0; + ShadowIndexList[10] = 5; + ShadowIndexList[11] = 4; + + ShadowIndexList[12] = 0; + ShadowIndexList[13] = 6; + ShadowIndexList[14] = 5; + + ShadowIndexList[15] = 0; + ShadowIndexList[16] = 7; + ShadowIndexList[17] = 6; + + ShadowIndexList[18] = 0; + ShadowIndexList[19] = 8; + ShadowIndexList[20] = 7; + + ShadowIndexList[21] = 0; + ShadowIndexList[22] = 9; + ShadowIndexList[23] = 8; + + + for ( int32 i = 0; i < MAX_STATICSHADOWS; i++ ) + { + aStaticShadows[i].m_nId = 0; + aStaticShadows[i].m_pPolyBunch = NULL; + } + + pEmptyBunchList = &aPolyBunches[0]; + + for ( int32 i = 0; i < MAX_POLYBUNCHES; i++ ) + { + if ( i == MAX_POLYBUNCHES - 1 ) + aPolyBunches[i].m_pNext = NULL; + else + aPolyBunches[i].m_pNext = &aPolyBunches[i + 1]; + } + + for ( int32 i = 0; i < MAX_PERMAMENTSHADOWS; i++ ) + { + aPermanentShadows[i].m_nType = SHADOWTYPE_NONE; + } +} + +void +CShadows::Shutdown(void) +{ + ASSERT(gpShadowCarTex != NULL); + ASSERT(gpShadowPedTex != NULL); + ASSERT(gpShadowHeliTex != NULL); + ASSERT(gpShadowExplosionTex != NULL); + ASSERT(gpShadowHeadLightsTex != NULL); + ASSERT(gpOutline1Tex != NULL); + ASSERT(gpOutline2Tex != NULL); + ASSERT(gpOutline3Tex != NULL); + ASSERT(gpBloodPoolTex != NULL); + ASSERT(gpReflectionTex != NULL); + ASSERT(gpGoalMarkerTex != NULL); + ASSERT(gpWalkDontTex != NULL); + ASSERT(gpCrackedGlassTex != NULL); + ASSERT(gpPostShadowTex != NULL); + + RwTextureDestroy(gpShadowCarTex); + RwTextureDestroy(gpShadowPedTex); + RwTextureDestroy(gpShadowHeliTex); + RwTextureDestroy(gpShadowExplosionTex); + RwTextureDestroy(gpShadowHeadLightsTex); + RwTextureDestroy(gpOutline1Tex); + RwTextureDestroy(gpOutline2Tex); + RwTextureDestroy(gpOutline3Tex); + RwTextureDestroy(gpBloodPoolTex); + RwTextureDestroy(gpReflectionTex); + RwTextureDestroy(gpGoalMarkerTex); + RwTextureDestroy(gpWalkDontTex); + RwTextureDestroy(gpCrackedGlassTex); + RwTextureDestroy(gpPostShadowTex); +} + +void +CShadows::AddPermanentShadow(uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, uint32 nTime, float fScale) +{ + ASSERT(pTexture != NULL); + ASSERT(pPosn != NULL); + + + // find free slot + int32 nSlot = 0; + while ( nSlot < MAX_PERMAMENTSHADOWS && aPermanentShadows[nSlot].m_nType != SHADOWTYPE_NONE ) + nSlot++; + + if ( nSlot < MAX_PERMAMENTSHADOWS ) + { + aPermanentShadows[nSlot].m_nType = ShadowType; + aPermanentShadows[nSlot].m_pTexture = pTexture; + aPermanentShadows[nSlot].m_vecPos = *pPosn; + aPermanentShadows[nSlot].m_vecFront.x = fFrontX; + aPermanentShadows[nSlot].m_vecFront.y = fFrontY; + aPermanentShadows[nSlot].m_vecSide.x = fSideX; + aPermanentShadows[nSlot].m_vecSide.y = fSideY; + aPermanentShadows[nSlot].m_nIntensity = nIntensity; + aPermanentShadows[nSlot].m_nRed = nRed; + aPermanentShadows[nSlot].m_nGreen = nGreen; + aPermanentShadows[nSlot].m_nBlue = nBlue; + aPermanentShadows[nSlot].m_fZDistance = fZDistance; + aPermanentShadows[nSlot].m_nLifeTime = nTime; + aPermanentShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + } +} + +void +CShadows::StoreStaticShadow(uint32 nID, uint8 ShadowType, RwTexture *pTexture, Const CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, float fScale, float fDrawDistance, bool bTempShadow, float fUpDistance) +{ + ASSERT(pPosn != NULL); + + float fDistToCamSqr = (*pPosn - TheCamera.GetPosition()).MagnitudeSqr2D(); + + if ( SQR(fDrawDistance) > fDistToCamSqr) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + if ( fDistToCam >= (fDrawDistance*(1.0f-(1.0f/4.0f))) ) + { + //fDistToCam == 0 -> 4 + //fDistToCam == fDrawDistance -> 0 + float fMult = 1.0f - (4.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f-(1.0f/4.0f)))); + + nIntensity = (int32)(nIntensity * fMult); + nRed = (int32)(nRed * fMult); + nGreen = (int32)(nGreen * fMult); + nBlue = (int32)(nBlue * fMult); + } + + int32 nSlot; + + nSlot = 0; + while ( nSlot < MAX_STATICSHADOWS && !(nID == aStaticShadows[nSlot].m_nId && aStaticShadows[nSlot].m_pPolyBunch != NULL) ) + nSlot++; + + if ( nSlot < MAX_STATICSHADOWS ) + { + if ( Abs(pPosn->x - aStaticShadows[nSlot].m_vecPosn.x) < fUpDistance + && Abs(pPosn->y - aStaticShadows[nSlot].m_vecPosn.y) < fUpDistance ) + { + aStaticShadows[nSlot].m_bJustCreated = true; + aStaticShadows[nSlot].m_nType = ShadowType; + aStaticShadows[nSlot].m_pTexture = pTexture; + aStaticShadows[nSlot].m_nIntensity = nIntensity; + aStaticShadows[nSlot].m_nRed = nRed; + aStaticShadows[nSlot].m_nGreen = nGreen; + aStaticShadows[nSlot].m_nBlue = nBlue; + aStaticShadows[nSlot].m_fZDistance = fZDistance; + aStaticShadows[nSlot].m_fScale = fScale; + aStaticShadows[nSlot].m_bTemp = bTempShadow; + aStaticShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + } + else if ( Abs(pPosn->x - aStaticShadows[nSlot].m_vecPosn.x) < 0.05f + && Abs(pPosn->y - aStaticShadows[nSlot].m_vecPosn.y) < 0.05f + && Abs(pPosn->z - aStaticShadows[nSlot].m_vecPosn.z) < 2.0f + + && fFrontX == aStaticShadows[nSlot].m_vecFront.x + && fFrontY == aStaticShadows[nSlot].m_vecFront.y + && fSideX == aStaticShadows[nSlot].m_vecSide.x + && fSideY == aStaticShadows[nSlot].m_vecSide.y ) + { + aStaticShadows[nSlot].m_bJustCreated = true; + aStaticShadows[nSlot].m_nType = ShadowType; + aStaticShadows[nSlot].m_pTexture = pTexture; + aStaticShadows[nSlot].m_nIntensity = nIntensity; + aStaticShadows[nSlot].m_nRed = nRed; + aStaticShadows[nSlot].m_nGreen = nGreen; + aStaticShadows[nSlot].m_nBlue = nBlue; + aStaticShadows[nSlot].m_fZDistance = fZDistance; + aStaticShadows[nSlot].m_fScale = fScale; + aStaticShadows[nSlot].m_bTemp = bTempShadow; + aStaticShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + } + else + { + aStaticShadows[nSlot].Free(); + + aStaticShadows[nSlot].m_nId = nID; + aStaticShadows[nSlot].m_nType = ShadowType; + aStaticShadows[nSlot].m_pTexture = pTexture; + aStaticShadows[nSlot].m_nIntensity = nIntensity; + aStaticShadows[nSlot].m_nRed = nRed; + aStaticShadows[nSlot].m_nGreen = nGreen; + aStaticShadows[nSlot].m_nBlue = nBlue; + aStaticShadows[nSlot].m_fZDistance = fZDistance; + aStaticShadows[nSlot].m_fScale = fScale; + aStaticShadows[nSlot].m_vecPosn = *pPosn; + aStaticShadows[nSlot].m_vecFront.x = fFrontX; + aStaticShadows[nSlot].m_vecFront.y = fFrontY; + aStaticShadows[nSlot].m_vecSide.x = fSideX; + aStaticShadows[nSlot].m_vecSide.y = fSideY; + aStaticShadows[nSlot].m_bJustCreated = true; + aStaticShadows[nSlot].m_bTemp = bTempShadow; + aStaticShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + + GeneratePolysForStaticShadow(nSlot); + } + } + else + { + nSlot = 0; + while ( nSlot < MAX_STATICSHADOWS && aStaticShadows[nSlot].m_pPolyBunch != NULL ) + nSlot++; + + if ( nSlot != MAX_STATICSHADOWS ) + { + aStaticShadows[nSlot].m_nId = nID; + aStaticShadows[nSlot].m_nType = ShadowType; + aStaticShadows[nSlot].m_pTexture = pTexture; + aStaticShadows[nSlot].m_nIntensity = nIntensity; + aStaticShadows[nSlot].m_nRed = nRed; + aStaticShadows[nSlot].m_nGreen = nGreen; + aStaticShadows[nSlot].m_nBlue = nBlue; + aStaticShadows[nSlot].m_fZDistance = fZDistance; + aStaticShadows[nSlot].m_fScale = fScale; + aStaticShadows[nSlot].m_vecPosn = *pPosn; + aStaticShadows[nSlot].m_vecFront.x = fFrontX; + aStaticShadows[nSlot].m_vecFront.y = fFrontY; + aStaticShadows[nSlot].m_vecSide.x = fSideX; + aStaticShadows[nSlot].m_vecSide.y = fSideY; + aStaticShadows[nSlot].m_bJustCreated = true; + aStaticShadows[nSlot].m_bTemp = bTempShadow; + aStaticShadows[nSlot].m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + + GeneratePolysForStaticShadow(nSlot); + } + } + } +} + +void +CShadows::StoreShadowToBeRendered(uint8 ShadowTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue) +{ + ASSERT(pPosn != NULL); + + switch ( ShadowTexture ) + { + case SHADOWTEX_NONE: + { + break; + } + + case SHADOWTEX_CAR: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_PED: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowPedTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_EXPLOSION: + { + StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_HELI: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowHeliTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_HEADLIGHTS: + { + StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowHeadLightsTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + + case SHADOWTEX_BLOOD: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpBloodPoolTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f); + + break; + } + } + + //ASSERT(false); +} + +void +CShadows::StoreShadowToBeRendered(uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, bool bDrawOnWater, float fScale) +{ + ASSERT(pTexture != NULL); + ASSERT(pPosn != NULL); + + if ( ShadowsStoredToBeRendered < MAX_STOREDSHADOWS ) + { + asShadowsStored[ShadowsStoredToBeRendered].m_ShadowType = ShadowType; + asShadowsStored[ShadowsStoredToBeRendered].m_pTexture = pTexture; + asShadowsStored[ShadowsStoredToBeRendered].m_vecPos = *pPosn; + asShadowsStored[ShadowsStoredToBeRendered].m_vecFront.x = fFrontX; + asShadowsStored[ShadowsStoredToBeRendered].m_vecFront.y = fFrontY; + asShadowsStored[ShadowsStoredToBeRendered].m_vecSide.x = fSideX; + asShadowsStored[ShadowsStoredToBeRendered].m_vecSide.y = fSideY; + asShadowsStored[ShadowsStoredToBeRendered].m_nIntensity = nIntensity; + asShadowsStored[ShadowsStoredToBeRendered].m_nRed = nRed; + asShadowsStored[ShadowsStoredToBeRendered].m_nGreen = nGreen; + asShadowsStored[ShadowsStoredToBeRendered].m_nBlue = nBlue; + asShadowsStored[ShadowsStoredToBeRendered].m_fZDistance = fZDistance; + asShadowsStored[ShadowsStoredToBeRendered].m_nFlags.bDrawOnWater = bDrawOnWater; + asShadowsStored[ShadowsStoredToBeRendered].m_fScale = fScale; + + ShadowsStoredToBeRendered++; + } +} + +void +CShadows::StoreShadowForCar(CAutomobile *pCar) +{ + ASSERT(pCar != NULL); + + if ( CTimeCycle::GetShadowStrength() != 0 ) + { + CVector CarPos = pCar->GetPosition(); + float fDistToCamSqr = (CarPos - TheCamera.GetPosition()).MagnitudeSqr2D(); + + if ( CCutsceneMgr::IsRunning() ) + fDistToCamSqr /= SQR(TheCamera.LODDistMultiplier) * 4.0f; + + float fDrawDistance = 18.0f; + + if ( fDistToCamSqr < SQR(fDrawDistance) ) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + //fDistToCam == 0 -> 4 + //fDistToCam == fDrawDistance -> 0 + float fMult = 1.0f - (4.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f-(1.0f/4.0f))) ); + + int32 nColorStrength; + + if ( fDistToCam >= (fDrawDistance*(1.0f-(1.0f/4.0f))) ) + nColorStrength = (int32)(CTimeCycle::GetShadowStrength() * fMult); + else + nColorStrength = CTimeCycle::GetShadowStrength(); + + float fVehicleHeight = pCar->GetColModel()->boundingBox.GetSize().y; + float fVehicleWidth = pCar->GetColModel()->boundingBox.GetSize().x; + + if ( pCar->GetModelIndex() == MI_DODO ) + { + fVehicleHeight *= 0.9f; + fVehicleWidth *= 0.4f; + } + + CarPos.x -= pCar->GetForward().x * ((fVehicleHeight / 2) - pCar->GetColModel()->boundingBox.max.y); + CarPos.y -= pCar->GetForward().y * ((fVehicleHeight / 2) - pCar->GetColModel()->boundingBox.max.y); + + if ( pCar->GetUp().z > 0.0f ) + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, &CarPos, + pCar->GetForward().x * (fVehicleHeight / 2), + pCar->GetForward().y * (fVehicleHeight / 2), + pCar->GetRight().x * (fVehicleWidth / 2), + pCar->GetRight().y * (fVehicleWidth / 2), + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.5f, false, 1.0f); + } + else + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, &CarPos, + pCar->GetForward().x * (fVehicleHeight / 2), + pCar->GetForward().y * (fVehicleHeight / 2), + -pCar->GetRight().x * (fVehicleWidth / 2), + -pCar->GetRight().y * (fVehicleWidth / 2), + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.5f, false, 1.0f); + } + } + } +} + +void +CShadows::StoreCarLightShadow(CAutomobile *pCar, int32 nID, RwTexture *pTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fMaxViewAngle) +{ + ASSERT(pCar != NULL); + ASSERT(pPosn != NULL); + + float fDistToCamSqr = (*pPosn - TheCamera.GetPosition()).MagnitudeSqr2D(); + + bool bSpecialCam = TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN + || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED + || CCutsceneMgr::IsRunning(); + + float fDrawDistance = 27.0f; + + if ( fDistToCamSqr < SQR(fDrawDistance) || bSpecialCam ) + { + if ( bSpecialCam || DotProduct2D(CVector2D(TheCamera.CamFrontXNorm, TheCamera.CamFrontYNorm), + *pPosn - TheCamera.GetPosition() ) > -fMaxViewAngle ) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + if ( fDistToCam >= (fDrawDistance*(1.0f-(1.0f/4.0f))) && !bSpecialCam ) // BUG? must be 3.0? + { + //fDistToCam == 0 -> 3 + //fDistToCam == fDrawDistance -> 0 + float fMult = 1.0f - (3.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f-(1.0f/3.0f))) ); + + nRed = (int32)(nRed * fMult); + nGreen = (int32)(nGreen * fMult); + nBlue = (int32)(nBlue * fMult); + } + + StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, pTexture, pPosn, + fFrontX, fFrontY, + fSideX, fSideY, + 128, nRed, nGreen, nBlue, + 6.0f, false, 1.0f); + + } + } +} + +void +CShadows::StoreShadowForPed(CPed *pPed, float fDisplacementX, float fDisplacementY, + float fFrontX, float fFrontY, float fSideX, float fSideY) +{ + ASSERT(pPed != NULL); + + if ( pPed->bIsVisible ) + { + if ( !(pPed->bInVehicle && pPed->m_nPedState != PED_DRAG_FROM_CAR && pPed->m_nPedState != PED_EXIT_CAR) ) + { + if ( CTimeCycle::GetShadowStrength() != 0 ) + StoreShadowForPedObject(pPed, + fDisplacementX, fDisplacementY, + fFrontX, fFrontY, + fSideX, fSideY); + } + } +} + +void +CShadows::StoreShadowForPedObject(CEntity *pPedObject, float fDisplacementX, float fDisplacementY, + float fFrontX, float fFrontY, float fSideX, float fSideY) +{ + ASSERT(pPedObject != NULL); + + CVector PedPos = pPedObject->GetPosition(); + + float fDistToCamSqr = (PedPos - TheCamera.GetPosition()).MagnitudeSqr2D(); + + float fDrawDistance = 26.0f; + + if ( fDistToCamSqr < SQR(fDrawDistance*0.5f)/*?*/ ) + { + if ( pPedObject == FindPlayerPed() || TheCamera.IsSphereVisible(PedPos, 2.0f) != false ) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + //fDistToCam == 0 -> 2 + //fDistToCam == fDrawDistance -> -2 + float fMult = 1.0f - (4.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f/4.0f))); // BUG ? negative + int32 nColorStrength; + + if ( fDistToCam >= (fDrawDistance*(1.0f/4.0f)) ) // BUG ? negative + nColorStrength = (int32)(CTimeCycle::GetShadowStrength() * fMult); + else + nColorStrength = CTimeCycle::GetShadowStrength(); + + PedPos.x += fDisplacementX; + PedPos.y += fDisplacementY; + + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowPedTex, &PedPos, + fFrontX, fFrontY, + fSideX, fSideY, + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.0f, false, 1.0f); + } + } +} + +void +CShadows::StoreShadowForTree(CEntity *pTree) +{ + ASSERT(pTree != NULL); +} + +void +CShadows::StoreShadowForPole(CEntity *pPole, float fOffsetX, float fOffsetY, float fOffsetZ, + float fPoleHeight, float fPoleWidth, uint32 nID) +{ + ASSERT(pPole != NULL); + + if ( CTimeCycle::GetShadowStrength() != 0 ) + { + if ( pPole->GetUp().z < 0.5f ) + return; + + CVector PolePos = pPole->GetPosition(); + + PolePos.x += fOffsetX * pPole->GetRight().x + fOffsetY * pPole->GetForward().x; + PolePos.y += fOffsetX * pPole->GetRight().y + fOffsetY * pPole->GetForward().y; + PolePos.z += fOffsetZ; + + PolePos.x += -CTimeCycle::GetSunDirection().x * (fPoleHeight / 2); + PolePos.y += -CTimeCycle::GetSunDirection().y * (fPoleHeight / 2); + + StoreStaticShadow((uintptr)pPole + nID + _TODOCONST(51), SHADOWTYPE_DARK, gpPostShadowTex, &PolePos, + -CTimeCycle::GetSunDirection().x * (fPoleHeight / 2), + -CTimeCycle::GetSunDirection().y * (fPoleHeight / 2), + CTimeCycle::GetShadowSideX() * fPoleWidth, + CTimeCycle::GetShadowSideY() * fPoleWidth, + 2 * (int32)((pPole->GetUp().z - 0.5f) * CTimeCycle::GetShadowStrength() * 2.0f) / 3, + 0, 0, 0, + 15.0f, 1.0f, 40.0f, false, 0.0f); + } +} + +void +CShadows::SetRenderModeForShadowType(uint8 ShadowType) +{ + switch ( ShadowType ) + { + case SHADOWTYPE_DARK: + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + break; + } + + case SHADOWTYPE_ADDITIVE: + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + break; + } + + case SHADOWTYPE_INVCOLOR: + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCCOLOR); + break; + } + } +} + +void +CShadows::RenderStoredShadows(void) +{ + PUSH_RENDERGROUP("CShadows::RenderStoredShadows"); + + RenderBuffer::ClearRenderBuffer(); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + + for ( int32 i = 0; i < ShadowsStoredToBeRendered; i++ ) + asShadowsStored[i].m_nFlags.bRendered = false; + + for ( int32 i = 0; i < ShadowsStoredToBeRendered; i++ ) + { + if ( !asShadowsStored[i].m_nFlags.bRendered ) + { + SetRenderModeForShadowType(asShadowsStored[i].m_ShadowType); + + ASSERT(asShadowsStored[i].m_pTexture != NULL); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(asShadowsStored[i].m_pTexture)); + + for ( int32 j = i; j < ShadowsStoredToBeRendered; j++ ) + { + if ( asShadowsStored[i].m_ShadowType == asShadowsStored[j].m_ShadowType + && asShadowsStored[i].m_pTexture == asShadowsStored[j].m_pTexture ) + { + float fWidth = Abs(asShadowsStored[j].m_vecFront.x) + Abs(asShadowsStored[j].m_vecSide.x); + float fHeight = Abs(asShadowsStored[j].m_vecFront.y) + Abs(asShadowsStored[j].m_vecSide.y); + + CVector shadowPos = asShadowsStored[j].m_vecPos; + + float fStartX = shadowPos.x - fWidth; + float fEndX = shadowPos.x + fWidth; + float fStartY = shadowPos.y - fHeight; + float fEndY = shadowPos.y + fHeight; + + int32 nStartX = Max(CWorld::GetSectorIndexX(fStartX), 0); + int32 nStartY = Max(CWorld::GetSectorIndexY(fStartY), 0); + int32 nEndX = Min(CWorld::GetSectorIndexX(fEndX), NUMSECTORS_X-1); + int32 nEndY = Min(CWorld::GetSectorIndexY(fEndY), NUMSECTORS_Y-1); + + CWorld::AdvanceCurrentScanCode(); + + for ( int32 y = nStartY; y <= nEndY; y++ ) + { + for ( int32 x = nStartX; x <= nEndX; x++ ) + { + CSector *pCurSector = CWorld::GetSector(x, y); + + ASSERT(pCurSector != NULL); + + CastShadowSectorList(pCurSector->m_lists[ENTITYLIST_BUILDINGS], + fStartX, fStartY, + fEndX, fEndY, + &shadowPos, + asShadowsStored[j].m_vecFront.x, + asShadowsStored[j].m_vecFront.y, + asShadowsStored[j].m_vecSide.x, + asShadowsStored[j].m_vecSide.y, + asShadowsStored[j].m_nIntensity, + asShadowsStored[j].m_nRed, + asShadowsStored[j].m_nGreen, + asShadowsStored[j].m_nBlue, + asShadowsStored[j].m_fZDistance, + asShadowsStored[j].m_fScale, + NULL); + + CastShadowSectorList(pCurSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], + fStartX, fStartY, + fEndX, fEndY, + &shadowPos, + asShadowsStored[j].m_vecFront.x, + asShadowsStored[j].m_vecFront.y, + asShadowsStored[j].m_vecSide.x, + asShadowsStored[j].m_vecSide.y, + asShadowsStored[j].m_nIntensity, + asShadowsStored[j].m_nRed, + asShadowsStored[j].m_nGreen, + asShadowsStored[j].m_nBlue, + asShadowsStored[j].m_fZDistance, + asShadowsStored[j].m_fScale, + NULL); + } + } + + asShadowsStored[j].m_nFlags.bRendered = true; + } + } + + RenderBuffer::RenderStuffInBuffer(); + } + + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + + ShadowsStoredToBeRendered = 0; + + POP_RENDERGROUP(); +} + +void +CShadows::RenderStaticShadows(void) +{ + PUSH_RENDERGROUP("CShadows::RenderStaticShadows"); + + RenderBuffer::ClearRenderBuffer(); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); + + SetAlphaTest(0); + + for ( int32 i = 0; i < MAX_STATICSHADOWS; i++ ) + aStaticShadows[i].m_bRendered = false; + + for ( int32 i = 0; i < MAX_STATICSHADOWS; i++ ) + { + if ( aStaticShadows[i].m_pPolyBunch && !aStaticShadows[i].m_bRendered ) + { + SetRenderModeForShadowType(aStaticShadows[i].m_nType); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(aStaticShadows[i].m_pTexture)); + + // optimization trick, render all shadows with same renderstate and texture + for ( int32 j = i; j < MAX_STATICSHADOWS; j++ ) + { + if ( aStaticShadows[j].m_pPolyBunch != NULL + && aStaticShadows[i].m_nType == aStaticShadows[j].m_nType + && aStaticShadows[i].m_pTexture == aStaticShadows[j].m_pTexture ) + { + for ( CPolyBunch *bunch = aStaticShadows[j].m_pPolyBunch; bunch != NULL; bunch = bunch->m_pNext ) + { + RwImVertexIndex *pIndexes; + RwIm3DVertex *pVerts; + + RenderBuffer::StartStoring(3 * (bunch->m_nNumVerts - 2), bunch->m_nNumVerts, &pIndexes, &pVerts); + + ASSERT(pIndexes != NULL); + ASSERT(pVerts != NULL); + + for ( int32 k = 0; k < bunch->m_nNumVerts; k++ ) + { + RwIm3DVertexSetRGBA(&pVerts[k], + aStaticShadows[j].m_nRed, + aStaticShadows[j].m_nGreen, + aStaticShadows[j].m_nBlue, + (int32)(aStaticShadows[j].m_nIntensity * (1.0f - CWeather::Foggyness * 0.5f))); + + RwIm3DVertexSetU (&pVerts[k], bunch->m_aU[k] / 200.0f); + RwIm3DVertexSetV (&pVerts[k], bunch->m_aV[k] / 200.0f); + RwIm3DVertexSetPos(&pVerts[k], bunch->m_aVerts[k].x, bunch->m_aVerts[k].y, bunch->m_aVerts[k].z + 0.03f); + } + + for ( int32 k = 0; k < 3 * (bunch->m_nNumVerts - 2); k++ ) + pIndexes[k] = ShadowIndexList[k]; + + RenderBuffer::StopStoring(); + } + + aStaticShadows[j].m_bRendered = true; + } + } + + RenderBuffer::RenderStuffInBuffer(); + } + } + RestoreAlphaTest(); + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + + POP_RENDERGROUP(); +} + +void +CShadows::GeneratePolysForStaticShadow(int16 nStaticShadowID) +{ + float fWidth = Abs(aStaticShadows[nStaticShadowID].m_vecFront.x) + Abs(aStaticShadows[nStaticShadowID].m_vecSide.x); + float fHeight = Abs(aStaticShadows[nStaticShadowID].m_vecFront.y) + Abs(aStaticShadows[nStaticShadowID].m_vecSide.y); + + CVector shadowPos = aStaticShadows[nStaticShadowID].m_vecPosn; + + float fStartX = shadowPos.x - fWidth; + float fEndX = shadowPos.x + fWidth; + float fStartY = shadowPos.y - fHeight; + float fEndY = shadowPos.y + fHeight; + + int32 nStartX = Max(CWorld::GetSectorIndexX(fStartX), 0); + int32 nStartY = Max(CWorld::GetSectorIndexY(fStartY), 0); + int32 nEndX = Min(CWorld::GetSectorIndexX(fEndX), NUMSECTORS_X-1); + int32 nEndY = Min(CWorld::GetSectorIndexY(fEndY), NUMSECTORS_Y-1); + + CWorld::AdvanceCurrentScanCode(); + + for ( int32 y = nStartY; y <= nEndY; y++ ) + { + for ( int32 x = nStartX; x <= nEndX; x++ ) + { + CSector *pCurSector = CWorld::GetSector(x, y); + + ASSERT(pCurSector != NULL); + + CastShadowSectorList(pCurSector->m_lists[ENTITYLIST_BUILDINGS], + fStartX, fStartY, + fEndX, fEndY, + &shadowPos, + aStaticShadows[nStaticShadowID].m_vecFront.x, + aStaticShadows[nStaticShadowID].m_vecFront.y, + aStaticShadows[nStaticShadowID].m_vecSide.x, + aStaticShadows[nStaticShadowID].m_vecSide.y, + 0, 0, 0, 0, + aStaticShadows[nStaticShadowID].m_fZDistance, + aStaticShadows[nStaticShadowID].m_fScale, + &aStaticShadows[nStaticShadowID].m_pPolyBunch); + + CastShadowSectorList(pCurSector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], + fStartX, fStartY, + fEndX, fEndY, + &shadowPos, + aStaticShadows[nStaticShadowID].m_vecFront.x, + aStaticShadows[nStaticShadowID].m_vecFront.y, + aStaticShadows[nStaticShadowID].m_vecSide.x, + aStaticShadows[nStaticShadowID].m_vecSide.y, + 0, 0, 0, 0, + aStaticShadows[nStaticShadowID].m_fZDistance, + aStaticShadows[nStaticShadowID].m_fScale, + &aStaticShadows[nStaticShadowID].m_pPolyBunch); + } + } +} + +void +CShadows::CastShadowSectorList(CPtrList &PtrList, float fStartX, float fStartY, float fEndX, float fEndY, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, float fScale, CPolyBunch **ppPolyBunch) +{ + ASSERT(pPosn != NULL); + + CPtrNode *pNode = PtrList.first; + + CRect Bound; + + while ( pNode != NULL ) + { + CEntity *pEntity = (CEntity *)pNode->item; + uint16 nScanCode = pEntity->m_scanCode; + pNode = pNode->next; + + ASSERT( pEntity != NULL ); + + if ( nScanCode != CWorld::GetCurrentScanCode() ) + { + if ( pEntity->bUsesCollision && pEntity->IsBuilding() ) + { + pEntity->m_scanCode = CWorld::GetCurrentScanCode(); + + Bound = pEntity->GetBoundRect(); + + if ( fStartX < Bound.right + && fEndX > Bound.left + && fStartY < Bound.bottom + && fEndY > Bound.top ) + { + if ( pPosn->z - fZDistance < pEntity->GetPosition().z + pEntity->GetColModel()->boundingBox.max.z + && pEntity->GetPosition().z + pEntity->GetColModel()->boundingBox.min.z < pPosn->z ) + { + CastShadowEntity(pEntity, + fStartX, fStartY, + fEndX, fEndY, + pPosn, + fFrontX, fFrontY, + fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + fZDistance, fScale, ppPolyBunch); + } + } + } + } + } +} + +void +CShadows::CastShadowEntity(CEntity *pEntity, float fStartX, float fStartY, float fEndX, float fEndY, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, float fScale, CPolyBunch **ppPolyBunch) +{ + ASSERT(pEntity != NULL); + ASSERT(pPosn != NULL); + + static CVector List [20]; + static CVector Texture[20]; + static CVector Points [4]; + + CColModel *pCol = pEntity->GetColModel(); + ASSERT(pCol != NULL); + +#ifndef MASTER + if ( gbPrintShite ) + printf("MI:%d Triangles:%d Coors:%f %f BBoxXY:%f %f\n", + pEntity->GetModelIndex(), + pCol->numTriangles, + pEntity->GetPosition().x, + pEntity->GetPosition().y, + pCol->boundingBox.GetSize().x, + pCol->boundingBox.GetSize().y); +#endif + + CCollision::CalculateTrianglePlanes(pCol); + + float fFrontRight = DotProduct2D(CVector2D(fFrontX, fFrontY), pEntity->GetRight()); + float fFrontForward = DotProduct2D(CVector2D(fFrontX, fFrontY), pEntity->GetForward()); + float fSideRight = DotProduct2D(CVector2D(fSideX, fSideY), pEntity->GetRight()); + float fSideForward = DotProduct2D(CVector2D(fSideX, fSideY), pEntity->GetForward()); + float fLengthRight = DotProduct2D(*pPosn - pEntity->GetPosition(), pEntity->GetRight()); + float fLengthForward = DotProduct2D(*pPosn - pEntity->GetPosition(), pEntity->GetForward()); + + Points[0].x = (fLengthRight + fFrontRight ) - fSideRight; + Points[0].y = (fLengthForward + fFrontForward) - fSideForward; + + Points[1].x = fSideRight + (fLengthRight + fFrontRight); + Points[1].y = fSideForward + (fLengthForward + fFrontForward); + + Points[2].x = fSideRight + (fLengthRight - fFrontRight); + Points[2].y = fSideForward + (fLengthForward - fFrontForward); + + Points[3].x = (fLengthRight - fFrontRight) - fSideRight; + Points[3].y = (fLengthForward - fFrontForward) - fSideForward; + + float MinX = Min(Min(Points[0].x, Points[1].x), Min(Points[2].x, Points[3].x)); + float MaxX = Max(Max(Points[0].x, Points[1].x), Max(Points[2].x, Points[3].x)); + + float MinY = Min(Min(Points[0].y, Points[1].y), Min(Points[2].y, Points[3].y)); + float MaxY = Max(Max(Points[0].y, Points[1].y), Max(Points[2].y, Points[3].y)); + + float MaxZ = pPosn->z - pEntity->GetPosition().z; + float MinZ = MaxZ - fZDistance; + + for ( int32 i = 0; i < pCol->numTriangles; i++ ) + { + CColTrianglePlane *pColTriPlanes = pCol->trianglePlanes; + ASSERT(pColTriPlanes != NULL); + + CVector normal; + pColTriPlanes[i].GetNormal(normal); + if ( Abs(normal.z) > 0.1f ) + { + CColTriangle *pColTri = pCol->triangles; + ASSERT(pColTri != NULL); + + CVector PointA, PointB, PointC; + + pCol->GetTrianglePoint(PointA, pColTri[i].a); + pCol->GetTrianglePoint(PointB, pColTri[i].b); + pCol->GetTrianglePoint(PointC, pColTri[i].c); + + if ( (PointA.x > MinX || PointB.x > MinX || PointC.x > MinX) + && (PointA.x < MaxX || PointB.x < MaxX || PointC.x < MaxX) + && (PointA.y > MinY || PointB.y > MinY || PointC.y > MinY) + && (PointA.y < MaxY || PointB.y < MaxY || PointC.y < MaxY) + && (PointA.z < MaxZ || PointB.z < MaxZ || PointC.z < MaxZ) + && (PointA.z > MinZ || PointB.z > MinZ || PointC.z > MinZ) ) + + { + List[0].x = Points[0].x; + List[0].y = Points[0].y; + + List[1].x = Points[1].x; + List[1].y = Points[1].y; + + List[2].x = Points[2].x; + List[2].y = Points[2].y; + + List[3].x = Points[3].x; + List[3].y = Points[3].y; + + Texture[0].x = 0.0f; + Texture[0].y = 0.0f; + + Texture[1].x = 1.0f; + Texture[1].y = 0.0f; + + Texture[2].x = 1.0f; + Texture[2].y = 1.0f; + + Texture[3].x = 0.0f; + Texture[3].y = 1.0f; + + + CVector2D start; + CVector2D dist; + + int32 numVerts1 = 0; + int16 vertType1 = 0; + { + for ( int32 j = 0; j < 4; j++ ) + { + start = PointA; + dist = PointB - PointA; + + int32 in = j; + + float cp = CrossProduct2D(CVector2D(List[in]) - start, dist); + + if ( cp > 0.0f ) + { + switch ( vertType1 ) + { + case 0: + { + int32 out = numVerts1++ + 10; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 1: + { + int32 out = numVerts1++ + 10; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 2: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out1 = numVerts1++ + 10; + int32 out2 = numVerts1++ + 10; + + Texture[out1].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out1].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out1].x = Compl*List[in-1].x + Scale*List[in].x; + List[out1].y = Compl*List[in-1].y + Scale*List[in].y; + + Texture[out2].x = Texture[in].x; + Texture[out2].y = Texture[in].y; + List[out2].x = List[in].x; + List[out2].y = List[in].y; + + break; + } + } + + vertType1 = 1; + } + else + { + switch ( vertType1 ) + { + case 1: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out = numVerts1++ + 10; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out].x = Compl*List[in-1].x + Scale*List[in].x; + List[out].y = Compl*List[in-1].y + Scale*List[in].y; + + break; + } + } + + vertType1 = 2; + } + } + + float cp1 = CrossProduct2D(CVector2D(List[0]) - start, dist); + if ( cp1 > 0.0f && vertType1 == 2 || cp1 <= 0.0f && vertType1 == 1 ) + { + float cp2 = CrossProduct2D(CVector2D(List[3]) - start, dist); + + float Scale = Abs(cp2) / (Abs(cp2) + Abs(cp1)); + float Compl = 1.0f - Scale; + + int32 out = numVerts1++ + 10; + + Texture[out].x = Compl*Texture[3].x + Scale*Texture[0].x; + Texture[out].y = Compl*Texture[3].y + Scale*Texture[0].y; + List[out].x = Compl*List[3].x + Scale*List[0].x; + List[out].y = Compl*List[3].y + Scale*List[0].y; + } + } + + int32 numVerts2 = 0; + int16 vertType2 = 0; + { + for ( int32 j = 0; j < numVerts1; j++ ) + { + start = PointB; + dist = PointC - PointB; + + int32 in = j + 10; + float cp = CrossProduct2D(CVector2D(List[in]) - start, dist); + + if ( cp > 0.0f ) + { + switch ( vertType2 ) + { + case 0: + { + int32 out = numVerts2++; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 1: + { + int32 out = numVerts2++; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 2: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out1 = numVerts2++; + int32 out2 = numVerts2++; + + Texture[out1].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out1].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out1].x = Compl*List[in-1].x + Scale*List[in].x; + List[out1].y = Compl*List[in-1].y + Scale*List[in].y; + + Texture[out2].x = Texture[in].x; + Texture[out2].y = Texture[in].y; + List[out2].x = List[in].x; + List[out2].y = List[in].y; + + break; + } + } + + vertType2 = 1; + } + else + { + switch ( vertType2 ) + { + case 1: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out = numVerts2++; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out].x = Compl*List[in-1].x + Scale*List[in].x; + List[out].y = Compl*List[in-1].y + Scale*List[in].y; + + break; + } + } + + vertType2 = 2; + } + } + + float cp1 = CrossProduct2D(CVector2D(List[10]) - start, dist); + if ( cp1 > 0.0f && vertType2 == 2 || cp1 <= 0.0f && vertType2 == 1 ) + { + int32 in = numVerts1 + 10; + + float cp2 = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(cp2) / (Abs(cp2) + Abs(cp1)); + float Compl = 1.0f - Scale; + + int32 out = numVerts2++; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[10].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[10].y; + List[out].x = Compl*List[in-1].x + Scale*List[10].x; + List[out].y = Compl*List[in-1].y + Scale*List[10].y; + } + } + + int32 numVerts3 = 0; + int16 vertType3 = 0; + { + for ( int32 j = 0; j < numVerts2; j++ ) + { + start = PointC; + dist = PointA - PointC; + + int32 in = j; + float cp = CrossProduct2D(CVector2D(List[in]) - start, dist); + + if ( cp > 0.0f ) + { + switch ( vertType3 ) + { + case 0: + { + int32 out = numVerts3++ + 10; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 1: + { + int32 out = numVerts3++ + 10; + + Texture[out].x = Texture[in].x; + Texture[out].y = Texture[in].y; + List[out].x = List[in].x; + List[out].y = List[in].y; + + break; + } + + case 2: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out1 = numVerts3++ + 10; + int32 out2 = numVerts3++ + 10; + + Texture[out1].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out1].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out1].x = Compl*List[in-1].x + Scale*List[in].x; + List[out1].y = Compl*List[in-1].y + Scale*List[in].y; + + Texture[out2].x = Texture[in].x; + Texture[out2].y = Texture[in].y; + List[out2].x = List[in].x; + List[out2].y = List[in].y; + + break; + } + } + + vertType3 = 1; + } + else + { + switch ( vertType3 ) + { + case 1: + { + float prevcp = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(prevcp) / (Abs(prevcp) + Abs(cp)); + float Compl = 1.0f - Scale; + + int32 out = numVerts3++ + 10; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[in].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[in].y; + List[out].x = Compl*List[in-1].x + Scale*List[in].x; + List[out].y = Compl*List[in-1].y + Scale*List[in].y; + + break; + } + } + + vertType3 = 2; + } + } + + float cp1 = CrossProduct2D(CVector2D(List[0]) - start, dist); + if ( cp1 > 0.0f && vertType3 == 2 || cp1 <= 0.0f && vertType3 == 1 ) + { + int32 in = numVerts2; + + float cp2 = CrossProduct2D(CVector2D(List[in-1]) - start, dist); + + float Scale = Abs(cp2) / (Abs(cp2) + Abs(cp1)); + float Compl = 1.0f - Scale; + + int32 out = numVerts3++ + 10; + + Texture[out].x = Compl*Texture[in-1].x + Scale*Texture[0].x; + Texture[out].y = Compl*Texture[in-1].y + Scale*Texture[0].y; + List[out].x = Compl*List[in-1].x + Scale*List[0].x; + List[out].y = Compl*List[in-1].y + Scale*List[0].y; + } + } + + if ( numVerts3 >= 3 ) + { + CVector norm; + + pColTriPlanes[i].GetNormal(norm); + + float dot = DotProduct(norm, PointA); + + for ( int32 j = 0; j < numVerts3; j++ ) + { + int32 idx = j + 10; + + List[idx].z = -(DotProduct2D(norm, List[idx]) - dot) / norm.z; + } + + for ( int32 j = 0; j < numVerts3; j++ ) + { + int32 idx = j + 10; + + CVector p = List[idx]; + + List[idx].x = p.y * pEntity->GetForward().x + p.x * pEntity->GetRight().x + pEntity->GetPosition().x; + List[idx].y = p.y * pEntity->GetForward().y + p.x * pEntity->GetRight().y + pEntity->GetPosition().y; + List[idx].z = p.z + pEntity->GetPosition().z; + } + + + if ( ppPolyBunch != NULL ) + { + if ( pEmptyBunchList != NULL ) + { + CPolyBunch *pBunch = pEmptyBunchList; + ASSERT(pBunch != NULL); + pEmptyBunchList = pEmptyBunchList->m_pNext; + pBunch->m_pNext = *ppPolyBunch; + *ppPolyBunch = pBunch; + + pBunch->m_nNumVerts = numVerts3; + + for ( int32 j = 0; j < numVerts3; j++ ) + { + int32 in = j + 10; + + pBunch->m_aVerts[j] = List[in]; + + pBunch->m_aU[j] = (int32)(Texture[in].x * 200.0f); + pBunch->m_aV[j] = (int32)(Texture[in].y * 200.0f); + } + } + } + else + { + RwImVertexIndex *pIndexes; + RwIm3DVertex *pVerts; + + RenderBuffer::StartStoring(3 * (numVerts3 - 2), numVerts3, &pIndexes, &pVerts); + + ASSERT(pIndexes != NULL); + ASSERT(pVerts != NULL); + + + for ( int32 j = 0; j < numVerts3; j++ ) + { + int32 in = j + 10; + + RwIm3DVertexSetRGBA(&pVerts[j], nRed, nGreen, nBlue, nIntensity); + RwIm3DVertexSetU (&pVerts[j], Texture[in].x*fScale); + RwIm3DVertexSetV (&pVerts[j], Texture[in].y*fScale); + RwIm3DVertexSetPos (&pVerts[j], List[in].x, List[in].y, List[in].z + 0.03f); + } + + for ( int32 j = 0; j < 3*(numVerts3 - 2); j++ ) + pIndexes[j] = ShadowIndexList[j]; + + RenderBuffer::StopStoring(); + } + } + } + } + } +} + +void +CShadows::UpdateStaticShadows(void) +{ + for ( int32 i = 0; i < MAX_STATICSHADOWS; i++ ) + { + if ( aStaticShadows[i].m_pPolyBunch != NULL && !aStaticShadows[i].m_bJustCreated + && (!aStaticShadows[i].m_bTemp || CTimer::GetTimeInMilliseconds() > aStaticShadows[i].m_nTimeCreated + 5000) ) + { + aStaticShadows[i].Free(); + } + + aStaticShadows[i].m_bJustCreated = false; + } +} + +void +CShadows::UpdatePermanentShadows(void) +{ + for ( int32 i = 0; i < MAX_PERMAMENTSHADOWS; i++ ) + { + if ( aPermanentShadows[i].m_nType != SHADOWTYPE_NONE ) + { + uint32 timePassed = CTimer::GetTimeInMilliseconds() - aPermanentShadows[i].m_nTimeCreated; + + if ( timePassed >= aPermanentShadows[i].m_nLifeTime ) + aPermanentShadows[i].m_nType = SHADOWTYPE_NONE; + else + { + if ( timePassed >= (aPermanentShadows[i].m_nLifeTime * 3 / 4) ) + { + // timePassed == 0 -> 4 + // timePassed == aPermanentShadows[i].m_nLifeTime -> 0 + float fMult = 1.0f - float(timePassed - (aPermanentShadows[i].m_nLifeTime * 3 / 4)) / (aPermanentShadows[i].m_nLifeTime / 4); + + StoreStaticShadow((uintptr)&aPermanentShadows[i], + aPermanentShadows[i].m_nType, + aPermanentShadows[i].m_pTexture, + &aPermanentShadows[i].m_vecPos, + aPermanentShadows[i].m_vecFront.x, + aPermanentShadows[i].m_vecFront.y, + aPermanentShadows[i].m_vecSide.x, + aPermanentShadows[i].m_vecSide.y, + (int32)(aPermanentShadows[i].m_nIntensity * fMult), + (int32)(aPermanentShadows[i].m_nRed * fMult), + (int32)(aPermanentShadows[i].m_nGreen * fMult), + (int32)(aPermanentShadows[i].m_nBlue * fMult), + aPermanentShadows[i].m_fZDistance, + 1.0f, 40.0f, false, 0.0f); + } + else + { + StoreStaticShadow((uintptr)&aPermanentShadows[i], + aPermanentShadows[i].m_nType, + aPermanentShadows[i].m_pTexture, + &aPermanentShadows[i].m_vecPos, + aPermanentShadows[i].m_vecFront.x, + aPermanentShadows[i].m_vecFront.y, + aPermanentShadows[i].m_vecSide.x, + aPermanentShadows[i].m_vecSide.y, + aPermanentShadows[i].m_nIntensity, + aPermanentShadows[i].m_nRed, + aPermanentShadows[i].m_nGreen, + aPermanentShadows[i].m_nBlue, + aPermanentShadows[i].m_fZDistance, + 1.0f, 40.0f, false, 0.0f); + } + } + } + } +} + +void +CStaticShadow::Free(void) +{ + if ( m_pPolyBunch != NULL ) + { + CPolyBunch *pFree = CShadows::pEmptyBunchList; + CShadows::pEmptyBunchList = m_pPolyBunch; + + CPolyBunch *pUsed = m_pPolyBunch; + while (pUsed->m_pNext != NULL) + pUsed = pUsed->m_pNext; + + pUsed->m_pNext = pFree; + } + + m_pPolyBunch = NULL; + + m_nId = 0; +} + +void +CShadows::CalcPedShadowValues(CVector vecLightDir, + float *pfFrontX, float *pfFrontY, + float *pfSideX, float *pfSideY, + float *pfDisplacementX, float *pfDisplacementY) +{ + ASSERT(pfFrontX != nil); + ASSERT(pfFrontY != nil); + ASSERT(pfSideX != nil); + ASSERT(pfSideY != nil); + ASSERT(pfDisplacementX != nil); + ASSERT(pfDisplacementY != nil); + + *pfFrontX = -vecLightDir.x; + *pfFrontY = -vecLightDir.y; + + float fDist = Sqrt(*pfFrontY * *pfFrontY + *pfFrontX * *pfFrontX); + float fMult = (fDist + 1.0f) / fDist; + + *pfFrontX *= fMult; + *pfFrontY *= fMult; + + *pfSideX = -vecLightDir.y / fDist; + *pfSideY = vecLightDir.x / fDist; + + *pfDisplacementX = -vecLightDir.x; + *pfDisplacementY = -vecLightDir.y; + + *pfFrontX /= 2; + *pfFrontY /= 2; + + *pfSideX /= 2; + *pfSideY /= 2; + + *pfDisplacementX /= 2; + *pfDisplacementY /= 2; + +} + +void +CShadows::RenderExtraPlayerShadows(void) +{ +#ifdef FIX_BUGS + if (CReplay::IsPlayingBack()) + return; +#endif + if ( CTimeCycle::GetLightShadowStrength() != 0 ) + { + CVehicle *pCar = FindPlayerVehicle(); + + if ( pCar == NULL ) + { + for ( int32 i = 0; i < CPointLights::NumLights; i++ ) + { + if ( 0.0f != CPointLights::aLights[i].red + || 0.0f != CPointLights::aLights[i].green + || 0.0f != CPointLights::aLights[i].blue ) + { + if ( CPointLights::aLights[i].castExtraShadows ) + { + CVector vecLight = CPointLights::aLights[i].coors - FindPlayerCoors(); + float fLightDist = vecLight.Magnitude(); + float fRadius = CPointLights::aLights[i].radius; + + if ( fLightDist < fRadius ) + { + // fLightDist == fRadius -> 2.0f + // fLightDist == 0 -> 0.0f + float fMult = (1.0f - (2.0f * fLightDist - fRadius) / fRadius); + + int32 nColorStrength; + if ( fLightDist < fRadius*0.5f ) + nColorStrength = (5*CTimeCycle::GetLightShadowStrength()/8); + else + nColorStrength = int32((5*CTimeCycle::GetLightShadowStrength()/8) * fMult); + + float fInv = 1.0f / fLightDist; + vecLight.x *= fInv; + vecLight.y *= fInv; + vecLight.z *= fInv; + + float fFrontX, fFrontY, fSideX, fSideY, fDisplacementX, fDisplacementY; + + CalcPedShadowValues(vecLight, + &fFrontX, &fFrontY, + &fSideX, &fSideY, + &fDisplacementX, &fDisplacementY); + + CVector shadowPos = FindPlayerCoors(); + + shadowPos.x += fDisplacementX; + shadowPos.y += fDisplacementY; + + + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowPedTex, &shadowPos, + fFrontX, fFrontY, + fSideX, fSideY, + nColorStrength, 0, 0, 0, + 4.0f, false, 1.0f); + } + } + } + } + } + else + { + if ( pCar->GetModelIndex() != MI_RCBANDIT ) + { + for ( int32 i = 0; i < CPointLights::NumLights; i++ ) + { + if ( CPointLights::aLights[i].type == CPointLights::LIGHT_POINT + && CPointLights::aLights[i].castExtraShadows + &&(0.0f != CPointLights::aLights[i].red + || 0.0f != CPointLights::aLights[i].green + || 0.0f != CPointLights::aLights[i].blue) ) + { + CVector vecLight = CPointLights::aLights[i].coors - FindPlayerCoors(); + float fLightDist = vecLight.Magnitude(); + float fRadius = CPointLights::aLights[i].radius; + + if ( fLightDist < fRadius ) + { + // fLightDist == 0 -> 2.0f + // fLightDist == fRadius -> 0.0f + float fMult = (1.0f - (2.0f * fLightDist - fRadius) / fRadius); + + int32 nColorStrength; + if ( fLightDist < fRadius*0.5f ) + nColorStrength = (5*CTimeCycle::GetLightShadowStrength()/8); + else + nColorStrength = int32((5*CTimeCycle::GetLightShadowStrength()/8) * fMult); + + float fInv = 1.0f / fLightDist; + vecLight.x *= fInv; + vecLight.y *= fInv; + vecLight.z *= fInv; + + CVector shadowPos = pCar->GetPosition(); + + shadowPos.x -= vecLight.x * 1.2f; + shadowPos.y -= vecLight.y * 1.2f; + + float fVehicleWidth = pCar->GetColModel()->boundingBox.GetSize().x; + float fVehicleHeight = pCar->GetColModel()->boundingBox.GetSize().y; + + shadowPos.x -= ((fVehicleHeight/2) - pCar->GetColModel()->boundingBox.max.y) + * pCar->GetForward().x; + + shadowPos.y -= ((fVehicleHeight/2) - pCar->GetColModel()->boundingBox.max.y) + * pCar->GetForward().y; + + if ( pCar->GetUp().z > 0.0f ) + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, &shadowPos, + pCar->GetForward().x * (fVehicleHeight/2), + pCar->GetForward().y * (fVehicleHeight/2), + pCar->GetRight().x * (fVehicleWidth/3), + pCar->GetRight().y * (fVehicleWidth/3), + nColorStrength, 0, 0, 0, + 4.5f, false, 1.0f); + } + else + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowCarTex, &shadowPos, + pCar->GetForward().x * (fVehicleHeight/2), + pCar->GetForward().y * (fVehicleHeight/2), + -pCar->GetRight().x * (fVehicleWidth/2), + -pCar->GetRight().y * (fVehicleWidth/2), + nColorStrength, 0, 0, 0, + 4.5f, false, 1.0f); + } + } + } + } + } + } + } +} + +void +CShadows::TidyUpShadows(void) +{ + for ( int32 i = 0; i < MAX_PERMAMENTSHADOWS; i++ ) + aPermanentShadows[i].m_nType = SHADOWTYPE_NONE; +} + +void +CShadows::RenderIndicatorShadow(uint32 nID, uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity) +{ + ASSERT(pPosn != NULL); + + C3dMarkers::PlaceMarkerSet(nID, MARKERTYPE_CYLINDER, *pPosn, Max(fFrontX, -fSideY), + 0, 128, 255, 128, + 2048, 0.2f, 0); +} diff --git a/src/renderer/Shadows.h b/src/renderer/Shadows.h new file mode 100644 index 0000000..8c909df --- /dev/null +++ b/src/renderer/Shadows.h @@ -0,0 +1,180 @@ +#pragma once + +#define MAX_STOREDSHADOWS 48 +#define MAX_POLYBUNCHES 300 +#define MAX_STATICSHADOWS 64 +#define MAX_PERMAMENTSHADOWS 48 + + +class CEntity; + +enum eShadowType +{ + SHADOWTYPE_NONE = 0, + SHADOWTYPE_DARK, + SHADOWTYPE_ADDITIVE, + SHADOWTYPE_INVCOLOR +}; + +enum eShadowTextureType +{ + SHADOWTEX_NONE = 0, + SHADOWTEX_CAR, + SHADOWTEX_PED, + SHADOWTEX_EXPLOSION, + SHADOWTEX_HELI, + SHADOWTEX_HEADLIGHTS, + SHADOWTEX_BLOOD +}; + +class CStoredShadow +{ +public: + CVector m_vecPos; + CVector2D m_vecFront; + CVector2D m_vecSide; + float m_fZDistance; + float m_fScale; + int16 m_nIntensity; + uint8 m_ShadowType; + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + struct + { + uint8 bDrawOnWater : 1; + uint8 bRendered : 1; + //uint8 bDrawOnBuildings : 1; + } m_nFlags; + RwTexture *m_pTexture; + + CStoredShadow() + { } +}; + +VALIDATE_SIZE(CStoredShadow, 0x30); + +class CPolyBunch +{ +public: + int16 m_nNumVerts; + CVector m_aVerts[7]; + uint8 m_aU[7]; + uint8 m_aV[7]; + CPolyBunch *m_pNext; + + CPolyBunch() + { } +}; + +VALIDATE_SIZE(CPolyBunch, 0x6C); + +class CStaticShadow +{ +public: + uint32 m_nId; + CPolyBunch *m_pPolyBunch; + uint32 m_nTimeCreated; + CVector m_vecPosn; + CVector2D m_vecFront; + CVector2D m_vecSide; + float m_fZDistance; + float m_fScale; + uint8 m_nType; + int16 m_nIntensity; // unsigned ? + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + bool m_bJustCreated; + bool m_bRendered; + bool m_bTemp; + RwTexture *m_pTexture; + + CStaticShadow() + { } + + void Free(); +}; + +VALIDATE_SIZE(CStaticShadow, 0x40); + +class CPermanentShadow +{ +public: + CVector m_vecPos; + CVector2D m_vecFront; + CVector2D m_vecSide; + float m_fZDistance; + float m_fScale; + int16 m_nIntensity; + uint8 m_nType; // eShadowType + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + uint32 m_nTimeCreated; + uint32 m_nLifeTime; + RwTexture *m_pTexture; + + CPermanentShadow() + { } +}; + +VALIDATE_SIZE(CPermanentShadow, 0x38); + +class CPtrList; +class CAutomobile; +class CPed; + +class CShadows +{ +public: + static int16 ShadowsStoredToBeRendered; + static CStoredShadow asShadowsStored [MAX_STOREDSHADOWS]; + static CPolyBunch aPolyBunches [MAX_POLYBUNCHES]; + static CStaticShadow aStaticShadows [MAX_STATICSHADOWS]; + static CPolyBunch *pEmptyBunchList; + static CPermanentShadow aPermanentShadows[MAX_PERMAMENTSHADOWS]; + + static void Init (void); + static void Shutdown (void); + static void AddPermanentShadow ( uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, uint32 nTime, float fScale); + static void StoreStaticShadow (uint32 nID, uint8 ShadowType, RwTexture *pTexture, Const CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, float fScale, float fDrawDistance, bool bTempShadow, float fUpDistance); + static void StoreShadowToBeRendered ( uint8 ShadowType, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue); + static void StoreShadowToBeRendered ( uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, bool bDrawOnWater, float fScale); + static void StoreShadowForCar (CAutomobile *pCar); + static void StoreCarLightShadow (CAutomobile *pCar, int32 nID, RwTexture *pTexture, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, uint8 nRed, uint8 nGreen, uint8 nBlue, float fMaxViewAngle); + static void StoreShadowForPed (CPed *pPed, float fDisplacementX, float fDisplacementY, float fFrontX, float fFrontY, float fSideX, float fSideY); + static void StoreShadowForPedObject (CEntity *pPedObject, float fDisplacementX, float fDisplacementY, float fFrontX, float fFrontY, float fSideX, float fSideY); + static void StoreShadowForTree (CEntity *pTree); + static void StoreShadowForPole (CEntity *pPole, float fOffsetX, float fOffsetY, float fOffsetZ, float fPoleHeight, float fPoleWidth, uint32 nID); + static void SetRenderModeForShadowType (uint8 ShadowType); + static void RenderStoredShadows (void); + static void RenderStaticShadows (void); + static void GeneratePolysForStaticShadow (int16 nStaticShadowID); + static void CastShadowSectorList (CPtrList &PtrList, float fStartX, float fStartY, float fEndX, float fEndY, + CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, float fScale, CPolyBunch **ppPolyBunch); + static void CastShadowEntity (CEntity *pEntity, float fStartX, float fStartY, float fEndX, float fEndY, + CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, float fScale, CPolyBunch **ppPolyBunch); + static void UpdateStaticShadows (void); + static void UpdatePermanentShadows (void); + static void CalcPedShadowValues (CVector vecLightDir, float *pfFrontX, float *pfFrontY, float *pfSideX, float *pfSideY, float *pfDisplacementX, float *pfDisplacementY); + static void RenderExtraPlayerShadows (void); + static void TidyUpShadows (void); + static void RenderIndicatorShadow (uint32 nID, uint8 ShadowType, RwTexture *pTexture, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity); +}; + +extern RwTexture *gpShadowCarTex; +extern RwTexture *gpShadowPedTex; +extern RwTexture *gpShadowHeliTex; +extern RwTexture *gpShadowExplosionTex; +extern RwTexture *gpShadowHeadLightsTex; +extern RwTexture *gpOutline1Tex; +extern RwTexture *gpOutline2Tex; +extern RwTexture *gpOutline3Tex; +extern RwTexture *gpBloodPoolTex; +extern RwTexture *gpReflectionTex; +extern RwTexture *gpGoalMarkerTex; +extern RwTexture *gpWalkDontTex; +extern RwTexture *gpCrackedGlassTex; +extern RwTexture *gpPostShadowTex; +extern RwTexture *gpGoalTex; diff --git a/src/renderer/Skidmarks.cpp b/src/renderer/Skidmarks.cpp new file mode 100644 index 0000000..4c662a7 --- /dev/null +++ b/src/renderer/Skidmarks.cpp @@ -0,0 +1,262 @@ +#include "common.h" + +#include "main.h" +#include "TxdStore.h" +#include "Timer.h" +#include "Replay.h" +#include "Skidmarks.h" + +CSkidmark CSkidmarks::aSkidmarks[NUMSKIDMARKS]; + +RwImVertexIndex SkidmarkIndexList[SKIDMARK_LENGTH * 6]; +RwIm3DVertex SkidmarkVertices[SKIDMARK_LENGTH * 2]; +RwTexture *gpSkidTex; +RwTexture *gpSkidBloodTex; +RwTexture *gpSkidMudTex; + +void +CSkidmarks::Init(void) +{ + int i, ix, slot; + CTxdStore::PushCurrentTxd(); + slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + gpSkidTex = RwTextureRead("particleskid", nil); + gpSkidBloodTex = RwTextureRead("particleskidblood", nil); + gpSkidMudTex = RwTextureRead("particleskidmud", nil); + CTxdStore::PopCurrentTxd(); + + for(i = 0; i < NUMSKIDMARKS; i++){ + aSkidmarks[i].m_state = 0; + aSkidmarks[i].m_wasUpdated = false; + } + + ix = 0; + for(i = 0; i < SKIDMARK_LENGTH; i++){ + SkidmarkIndexList[i*6+0] = ix+0; + SkidmarkIndexList[i*6+1] = ix+2; + SkidmarkIndexList[i*6+2] = ix+1; + SkidmarkIndexList[i*6+3] = ix+1; + SkidmarkIndexList[i*6+4] = ix+2; + SkidmarkIndexList[i*6+5] = ix+3; + ix += 2; + } + + for(i = 0; i < SKIDMARK_LENGTH; i++){ + RwIm3DVertexSetU(&SkidmarkVertices[i*2 + 0], 0.0f); + RwIm3DVertexSetV(&SkidmarkVertices[i*2 + 0], i*5.01f); + RwIm3DVertexSetU(&SkidmarkVertices[i*2 + 1], 1.0f); + RwIm3DVertexSetV(&SkidmarkVertices[i*2 + 1], i*5.01f); + } +} + +void +CSkidmarks::Shutdown(void) +{ + RwTextureDestroy(gpSkidTex); +#if GTA_VERSION >= GTA3_PC_11 + gpSkidTex = nil; +#endif + RwTextureDestroy(gpSkidBloodTex); +#if GTA_VERSION >= GTA3_PC_11 + gpSkidBloodTex = nil; +#endif + RwTextureDestroy(gpSkidMudTex); +#if GTA_VERSION >= GTA3_PC_11 + gpSkidMudTex = nil; +#endif +} + +void +CSkidmarks::Clear(void) +{ + int i; + for(i = 0; i < NUMSKIDMARKS; i++){ + aSkidmarks[i].m_state = 0; + aSkidmarks[i].m_wasUpdated = false; + } +} + +void +CSkidmarks::Update(void) +{ + int i; + uint32 t1 = CTimer::GetTimeInMilliseconds() + 2500; + uint32 t2 = CTimer::GetTimeInMilliseconds() + 5000; + uint32 t3 = CTimer::GetTimeInMilliseconds() + 10000; + uint32 t4 = CTimer::GetTimeInMilliseconds() + 20000; + for(i = 0; i < NUMSKIDMARKS; i++){ + switch(aSkidmarks[i].m_state){ + case 1: + if(!aSkidmarks[i].m_wasUpdated){ + // Didn't continue this one last time, so finish it and set fade times + aSkidmarks[i].m_state = 2; + if(aSkidmarks[i].m_last < 4){ + aSkidmarks[i].m_fadeStart = t1; + aSkidmarks[i].m_fadeEnd = t2; + }else if(aSkidmarks[i].m_last < 9){ + aSkidmarks[i].m_fadeStart = t2; + aSkidmarks[i].m_fadeEnd = t3; + }else{ + aSkidmarks[i].m_fadeStart = t3; + aSkidmarks[i].m_fadeEnd = t4; + } + } + break; + case 2: + if(CTimer::GetTimeInMilliseconds() > aSkidmarks[i].m_fadeEnd) + aSkidmarks[i].m_state = 0; + break; + } + aSkidmarks[i].m_wasUpdated = false; + } +} + +void +CSkidmarks::Render(void) +{ + int i, j; + RwTexture *lastTex = nil; + + PUSH_RENDERGROUP("CSkidmarks::Render"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + for(i = 0; i < NUMSKIDMARKS; i++){ + if(aSkidmarks[i].m_state == 0 || aSkidmarks[i].m_last < 1) + continue; + + if(aSkidmarks[i].m_isBloody){ + if(lastTex != gpSkidBloodTex){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpSkidBloodTex)); + lastTex = gpSkidBloodTex; + } + }else if(aSkidmarks[i].m_isMuddy){ + if(lastTex != gpSkidMudTex){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpSkidMudTex)); + lastTex = gpSkidMudTex; + } + }else{ + if(lastTex != gpSkidTex){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpSkidTex)); + lastTex = gpSkidTex; + } + } + + uint32 fade, alpha; + if(aSkidmarks[i].m_state == 1 || CTimer::GetTimeInMilliseconds() < aSkidmarks[i].m_fadeStart) + fade = 255; + else + fade = 255*(aSkidmarks[i].m_fadeEnd - CTimer::GetTimeInMilliseconds()) / (aSkidmarks[i].m_fadeEnd - aSkidmarks[i].m_fadeStart); + + for(j = 0; j <= aSkidmarks[i].m_last; j++){ + alpha = 128; + if(j == 0 || j == aSkidmarks[i].m_last && aSkidmarks[i].m_state == 2) + alpha = 0; + alpha = alpha*fade/256; + + CVector p1 = aSkidmarks[i].m_pos[j] + aSkidmarks[i].m_side[j]; + CVector p2 = aSkidmarks[i].m_pos[j] - aSkidmarks[i].m_side[j]; + RwIm3DVertexSetRGBA(&SkidmarkVertices[j*2+0], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&SkidmarkVertices[j*2+0], p1.x, p1.y, p1.z+0.1f); + RwIm3DVertexSetRGBA(&SkidmarkVertices[j*2+1], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&SkidmarkVertices[j*2+1], p2.x, p2.y, p2.z+0.1f); + } + + LittleTest(); + if(RwIm3DTransform(SkidmarkVertices, 2*(aSkidmarks[i].m_last+1), nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, SkidmarkIndexList, 6*aSkidmarks[i].m_last); + RwIm3DEnd(); + } + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + + POP_RENDERGROUP(); +} + +void +CSkidmarks::RegisterOne(uintptr id, CVector pos, float fwdX, float fwdY, bool *isMuddy, bool *isBloody) +{ + int i; + CVector2D fwd(fwdX, fwdY); + + if(CReplay::IsPlayingBack()) + return; + + // Find a skidmark to continue + for(i = 0; i < NUMSKIDMARKS; i++) + if(aSkidmarks[i].m_state == 1 && aSkidmarks[i].m_id == id) + break; + + if(i < NUMSKIDMARKS){ + // Continue this one + + if(aSkidmarks[i].m_isBloody != *isBloody){ + // Blood-status changed, end this one + aSkidmarks[i].m_state = 2; + aSkidmarks[i].m_fadeStart = CTimer::GetTimeInMilliseconds() + 10000; + aSkidmarks[i].m_fadeEnd = CTimer::GetTimeInMilliseconds() + 20000; + return; + } + + aSkidmarks[i].m_wasUpdated = true; + + if(CTimer::GetTimeInMilliseconds() - aSkidmarks[i].m_lastUpdate <= 100){ + // Last update was recently, just change last coords + aSkidmarks[i].m_pos[aSkidmarks[i].m_last] = pos; + return; + } + aSkidmarks[i].m_lastUpdate = CTimer::GetTimeInMilliseconds(); + + if(aSkidmarks[i].m_last >= SKIDMARK_LENGTH-1){ + // No space to continue, end it + aSkidmarks[i].m_state = 2; + aSkidmarks[i].m_fadeStart = CTimer::GetTimeInMilliseconds() + 10000; + aSkidmarks[i].m_fadeEnd = CTimer::GetTimeInMilliseconds() + 20000; + *isBloody = false; // stpo blood marks at end + return; + } + aSkidmarks[i].m_last++; + + aSkidmarks[i].m_pos[aSkidmarks[i].m_last] = pos; + + CVector2D right(aSkidmarks[i].m_pos[aSkidmarks[i].m_last].y - aSkidmarks[i].m_pos[aSkidmarks[i].m_last - 1].y, + aSkidmarks[i].m_pos[aSkidmarks[i].m_last - 1].x - aSkidmarks[i].m_pos[aSkidmarks[i].m_last].x); + + right.NormaliseSafe(); + fwd.NormaliseSafe(); + float turn = DotProduct2D(fwd, right); + turn = Abs(turn) + 1.0f; + aSkidmarks[i].m_side[aSkidmarks[i].m_last] = CVector(right.x, right.y, 0.0f) * turn * 0.125f; + if(aSkidmarks[i].m_last == 1) + aSkidmarks[i].m_side[0] = aSkidmarks[i].m_side[1]; + + if(aSkidmarks[i].m_last > 8) + *isBloody = false; // stop blood marks after 8 + return; + } + + // Start a new one + for(i = 0; i < NUMSKIDMARKS; i++) + if(aSkidmarks[i].m_state == 0) + break; + if(i < NUMSKIDMARKS){ + // Found a free slot + aSkidmarks[i].m_state = 1; + aSkidmarks[i].m_id = id; + aSkidmarks[i].m_pos[0] = pos; + aSkidmarks[i].m_side[0] = CVector(0.0f, 0.0f, 0.0f); + aSkidmarks[i].m_wasUpdated = true; + aSkidmarks[i].m_last = 0; + aSkidmarks[i].m_lastUpdate = CTimer::GetTimeInMilliseconds() - 1000; + aSkidmarks[i].m_isBloody = *isBloody; + aSkidmarks[i].m_isMuddy = *isMuddy; + }else + *isBloody = false; // stop blood marks if no space +} diff --git a/src/renderer/Skidmarks.h b/src/renderer/Skidmarks.h new file mode 100644 index 0000000..c061782 --- /dev/null +++ b/src/renderer/Skidmarks.h @@ -0,0 +1,32 @@ +#pragma once + +enum { SKIDMARK_LENGTH = 16 }; + +class CSkidmark +{ +public: + uint8 m_state; + bool m_wasUpdated; + bool m_isBloody; + bool m_isMuddy; + uintptr m_id; + int16 m_last; + uint32 m_lastUpdate; + uint32 m_fadeStart; + uint32 m_fadeEnd; + CVector m_pos[SKIDMARK_LENGTH]; + CVector m_side[SKIDMARK_LENGTH]; +}; + +class CSkidmarks +{ + static CSkidmark aSkidmarks[NUMSKIDMARKS]; +public: + + static void Init(void); + static void Shutdown(void); + static void Clear(void); + static void Update(void); + static void Render(void); + static void RegisterOne(uintptr id, CVector pos, float fwdX, float fwdY, bool *isMuddy, bool *isBloody); +}; diff --git a/src/renderer/SpecialFX.cpp b/src/renderer/SpecialFX.cpp new file mode 100644 index 0000000..6d96d21 --- /dev/null +++ b/src/renderer/SpecialFX.cpp @@ -0,0 +1,1194 @@ +#include "common.h" + +#include "SpecialFX.h" +#include "RenderBuffer.h" +#include "Timer.h" +#include "Sprite.h" +#include "Font.h" +#include "Text.h" +#include "TxdStore.h" +#include "FileMgr.h" +#include "FileLoader.h" +#include "Timecycle.h" +#include "Lights.h" +#include "ModelIndices.h" +#include "VisibilityPlugins.h" +#include "World.h" +#include "PlayerPed.h" +#include "Particle.h" +#include "Shadows.h" +#include "General.h" +#include "Camera.h" +#include "Shadows.h" +#include "main.h" + +RwIm3DVertex StreakVertices[4]; +RwImVertexIndex StreakIndexList[12]; + +RwIm3DVertex TraceVertices[6]; +RwImVertexIndex TraceIndexList[12]; + + +void +CSpecialFX::Init(void) +{ + CBulletTraces::Init(); + + RwIm3DVertexSetU(&StreakVertices[0], 0.0f); + RwIm3DVertexSetV(&StreakVertices[0], 0.0f); + RwIm3DVertexSetU(&StreakVertices[1], 1.0f); + RwIm3DVertexSetV(&StreakVertices[1], 0.0f); + RwIm3DVertexSetU(&StreakVertices[2], 0.0f); + RwIm3DVertexSetV(&StreakVertices[2], 0.0f); + RwIm3DVertexSetU(&StreakVertices[3], 1.0f); + RwIm3DVertexSetV(&StreakVertices[3], 0.0f); + + StreakIndexList[0] = 0; + StreakIndexList[1] = 1; + StreakIndexList[2] = 2; + StreakIndexList[3] = 1; + StreakIndexList[4] = 3; + StreakIndexList[5] = 2; + StreakIndexList[6] = 0; + StreakIndexList[7] = 2; + StreakIndexList[8] = 1; + StreakIndexList[9] = 1; + StreakIndexList[10] = 2; + StreakIndexList[11] = 3; + + RwIm3DVertexSetRGBA(&TraceVertices[0], 20, 20, 20, 255); + RwIm3DVertexSetRGBA(&TraceVertices[1], 20, 20, 20, 255); + RwIm3DVertexSetRGBA(&TraceVertices[2], 70, 70, 70, 255); + RwIm3DVertexSetRGBA(&TraceVertices[3], 70, 70, 70, 255); + RwIm3DVertexSetRGBA(&TraceVertices[4], 10, 10, 10, 255); + RwIm3DVertexSetRGBA(&TraceVertices[5], 10, 10, 10, 255); + RwIm3DVertexSetU(&TraceVertices[0], 0.0); + RwIm3DVertexSetV(&TraceVertices[0], 0.0); + RwIm3DVertexSetU(&TraceVertices[1], 1.0); + RwIm3DVertexSetV(&TraceVertices[1], 0.0); + RwIm3DVertexSetU(&TraceVertices[2], 0.0); + RwIm3DVertexSetV(&TraceVertices[2], 0.5); + RwIm3DVertexSetU(&TraceVertices[3], 1.0); + RwIm3DVertexSetV(&TraceVertices[3], 0.5); + RwIm3DVertexSetU(&TraceVertices[4], 0.0); + RwIm3DVertexSetV(&TraceVertices[4], 1.0); + RwIm3DVertexSetU(&TraceVertices[5], 1.0); + RwIm3DVertexSetV(&TraceVertices[5], 1.0); + + TraceIndexList[0] = 0; + TraceIndexList[1] = 2; + TraceIndexList[2] = 1; + TraceIndexList[3] = 1; + TraceIndexList[4] = 2; + TraceIndexList[5] = 3; + TraceIndexList[6] = 2; + TraceIndexList[7] = 4; + TraceIndexList[8] = 3; + TraceIndexList[9] = 3; + TraceIndexList[10] = 4; + TraceIndexList[11] = 5; + + CMotionBlurStreaks::Init(); + CBrightLights::Init(); + CShinyTexts::Init(); + CMoneyMessages::Init(); + C3dMarkers::Init(); +} + +RwObject* +LookForBatCB(RwObject *object, void *data) +{ + static CMatrix MatLTM; + + if(CVisibilityPlugins::GetAtomicModelInfo((RpAtomic*)object) == (CSimpleModelInfo*)data){ + MatLTM = CMatrix(RwFrameGetLTM(RpAtomicGetFrame((RpAtomic*)object))); + CVector p1 = MatLTM * CVector(0.02f, 0.05f, 0.07f); + CVector p2 = MatLTM * CVector(0.246f, 0.0325f, 0.796f); + CMotionBlurStreaks::RegisterStreak((uintptr)object, 100, 100, 100, p1, p2); + } + return nil; +} + +void +CSpecialFX::Update(void) +{ + CMotionBlurStreaks::Update(); + CBulletTraces::Update(); + + if(FindPlayerPed() && + FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT && + FindPlayerPed()->GetWeapon()->m_eWeaponState == WEAPONSTATE_FIRING){ +#ifdef PED_SKIN + if(IsClumpSkinned(FindPlayerPed()->GetClump())){ + LookForBatCB((RwObject*)FindPlayerPed()->m_pWeaponModel, CModelInfo::GetModelInfo(MI_BASEBALL_BAT)); + }else +#endif + RwFrameForAllObjects(FindPlayerPed()->m_pFrames[PED_HANDR]->frame, LookForBatCB, CModelInfo::GetModelInfo(MI_BASEBALL_BAT)); + } +} + +void +CSpecialFX::Shutdown(void) +{ + C3dMarkers::Shutdown(); +} + +void +CSpecialFX::Render(void) +{ + PUSH_RENDERGROUP("CSpecialFX::Render"); + CMotionBlurStreaks::Render(); + CBulletTraces::Render(); + CBrightLights::Render(); + CShinyTexts::Render(); + CMoneyMessages::Render(); +#ifdef NEW_RENDERER + if(!(gbNewRenderer && FredIsInFirstPersonCam())) +#endif + C3dMarkers::Render(); + POP_RENDERGROUP(); +} + +CRegisteredMotionBlurStreak CMotionBlurStreaks::aStreaks[NUMMBLURSTREAKS]; + +void +CRegisteredMotionBlurStreak::Update(void) +{ + int i; + bool wasUpdated; + bool lastWasUpdated = false; + for(i = 2; i > 0; i--){ + m_pos1[i] = m_pos1[i-1]; + m_pos2[i] = m_pos2[i-1]; + m_isValid[i] = m_isValid[i-1]; + wasUpdated = true; + if(!lastWasUpdated && !m_isValid[i]) + wasUpdated = false; + lastWasUpdated = wasUpdated; + } + m_isValid[0] = false; + if(!wasUpdated) + m_id = 0; +} + +void +CRegisteredMotionBlurStreak::Render(void) +{ + int i; + int a1, a2; + for(i = 0; i < 2; i++) + if(m_isValid[i] && m_isValid[i+1]){ + a1 = (255/3)*(3-i)/3; + RwIm3DVertexSetRGBA(&StreakVertices[0], m_red, m_green, m_blue, a1); + RwIm3DVertexSetRGBA(&StreakVertices[1], m_red, m_green, m_blue, a1); + a2 = (255/3)*(3-(i+1))/3; + RwIm3DVertexSetRGBA(&StreakVertices[2], m_red, m_green, m_blue, a2); + RwIm3DVertexSetRGBA(&StreakVertices[3], m_red, m_green, m_blue, a2); + RwIm3DVertexSetPos(&StreakVertices[0], m_pos1[i].x, m_pos1[i].y, m_pos1[i].z); + RwIm3DVertexSetPos(&StreakVertices[1], m_pos2[i].x, m_pos2[i].y, m_pos2[i].z); + RwIm3DVertexSetPos(&StreakVertices[2], m_pos1[i+1].x, m_pos1[i+1].y, m_pos1[i+1].z); + RwIm3DVertexSetPos(&StreakVertices[3], m_pos2[i+1].x, m_pos2[i+1].y, m_pos2[i+1].z); + LittleTest(); + if(RwIm3DTransform(StreakVertices, 4, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, StreakIndexList, 12); + RwIm3DEnd(); + } + } +} + +void +CMotionBlurStreaks::Init(void) +{ + int i; + for(i = 0; i < NUMMBLURSTREAKS; i++) + aStreaks[i].m_id = 0; +} + +void +CMotionBlurStreaks::Update(void) +{ + int i; + for(i = 0; i < NUMMBLURSTREAKS; i++) + if(aStreaks[i].m_id != 0) + aStreaks[i].Update(); +} + +void +CMotionBlurStreaks::RegisterStreak(uintptr id, uint8 r, uint8 g, uint8 b, CVector p1, CVector p2) +{ + int i; + for(i = 0; i < NUMMBLURSTREAKS; i++){ + if(aStreaks[i].m_id == id){ + // Found a streak from last frame, update + aStreaks[i].m_red = r; + aStreaks[i].m_green = g; + aStreaks[i].m_blue = b; + aStreaks[i].m_pos1[0] = p1; + aStreaks[i].m_pos2[0] = p2; + aStreaks[i].m_isValid[0] = true; + return; + } + } + // Find free slot + for(i = 0; aStreaks[i].m_id != 0; i++) + if(i == NUMMBLURSTREAKS-1) + return; + // Create a new streak + aStreaks[i].m_id = id; + aStreaks[i].m_red = r; + aStreaks[i].m_green = g; + aStreaks[i].m_blue = b; + aStreaks[i].m_pos1[0] = p1; + aStreaks[i].m_pos2[0] = p2; + aStreaks[i].m_isValid[0] = true; + aStreaks[i].m_isValid[1] = false; + aStreaks[i].m_isValid[2] = false; +} + +void +CMotionBlurStreaks::Render(void) +{ + bool setRenderStates = false; + int i; + for(i = 0; i < NUMMBLURSTREAKS; i++) + if(aStreaks[i].m_id != 0){ + if(!setRenderStates){ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGCOLOR, + (void*)RWRGBALONG(CTimeCycle::GetFogRed(), CTimeCycle::GetFogGreen(), CTimeCycle::GetFogBlue(), 255)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void*)FALSE); + setRenderStates = true; + } + aStreaks[i].Render(); + } + if(setRenderStates){ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); + } +} + + +CBulletTrace CBulletTraces::aTraces[NUMBULLETTRACES]; + +void CBulletTraces::Init(void) +{ + for (int i = 0; i < NUMBULLETTRACES; i++) + aTraces[i].m_bInUse = false; +} + +void CBulletTraces::AddTrace(CVector* vecStart, CVector* vecTarget) +{ + int index; + for (index = 0; index < NUMBULLETTRACES; index++) { + if (!aTraces[index].m_bInUse) + break; + } + if (index == NUMBULLETTRACES) + return; + aTraces[index].m_vecCurrentPos = *vecStart; + aTraces[index].m_vecTargetPos = *vecTarget; + aTraces[index].m_bInUse = true; + aTraces[index].m_framesInUse = 0; + aTraces[index].m_lifeTime = 25 + CGeneral::GetRandomNumber() % 32; +} + +void CBulletTraces::Render(void) +{ + for (int i = 0; i < NUMBULLETTRACES; i++) { + if (!aTraces[i].m_bInUse) + continue; + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); +#ifdef FIX_BUGS + // Raster has no transparent pixels so it relies on the raster format having alpha + // to turn on blending. librw image conversion might get rid of it right now so let's + // just force it on. + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); +#endif + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpShadowExplosionTex)); + CVector inf = aTraces[i].m_vecCurrentPos; + CVector sup = aTraces[i].m_vecTargetPos; + CVector center = (inf + sup) / 2; + CVector width = CrossProduct(TheCamera.GetForward(), (sup - inf)); + width.Normalise(); + width /= 20; + uint8 intensity = aTraces[i].m_lifeTime; + for (int i = 0; i < ARRAY_SIZE(TraceVertices); i++) + RwIm3DVertexSetRGBA(&TraceVertices[i], intensity, intensity, intensity, 0xFF); + RwIm3DVertexSetPos(&TraceVertices[0], inf.x + width.x, inf.y + width.y, inf.z + width.z); + RwIm3DVertexSetPos(&TraceVertices[1], inf.x - width.x, inf.y - width.y, inf.z - width.z); + RwIm3DVertexSetPos(&TraceVertices[2], center.x + width.x, center.y + width.y, center.z + width.z); + RwIm3DVertexSetPos(&TraceVertices[3], center.x - width.x, center.y - width.y, center.z - width.z); + RwIm3DVertexSetPos(&TraceVertices[4], sup.x + width.x, sup.y + width.y, sup.z + width.z); + RwIm3DVertexSetPos(&TraceVertices[5], sup.x - width.x, sup.y - width.y, sup.z - width.z); + LittleTest(); + if (RwIm3DTransform(TraceVertices, ARRAY_SIZE(TraceVertices), nil, rwIM3D_VERTEXUV)) { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TraceIndexList, ARRAY_SIZE(TraceIndexList)); + RwIm3DEnd(); + } + } + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +} + +void CBulletTraces::Update(void) +{ + for (int i = 0; i < NUMBULLETTRACES; i++) { + if (aTraces[i].m_bInUse) + aTraces[i].Update(); + } +} + +void CBulletTrace::Update(void) +{ + if (m_framesInUse == 0) { + m_framesInUse++; + return; + } + if (m_framesInUse > 60) { + m_bInUse = false; + return; + } + CVector diff = m_vecCurrentPos - m_vecTargetPos; + float remaining = diff.Magnitude(); + if (remaining > 0.8f) + m_vecCurrentPos = m_vecTargetPos + (remaining - 0.8f) / remaining * diff; + else + m_bInUse = false; + if (--m_lifeTime == 0) + m_bInUse = false; + m_framesInUse++; +} + +RpAtomic * +MarkerAtomicCB(RpAtomic *atomic, void *data) +{ + *(RpAtomic**)data = atomic; + return atomic; +} + +bool +C3dMarker::AddMarker(uint32 identifier, uint16 type, float fSize, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate) +{ + m_nIdentifier = identifier; + + m_Matrix.SetUnity(); + + RpAtomic *origAtomic; + origAtomic = nil; + RpClumpForAllAtomics(C3dMarkers::m_pRpClumpArray[type], MarkerAtomicCB, &origAtomic); + + RpAtomic *atomic = RpAtomicClone(origAtomic); + RwFrame *frame = RwFrameCreate(); + RpAtomicSetFrame(atomic, frame); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + + RpGeometry *geometry = RpAtomicGetGeometry(atomic); + RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR); + + m_pAtomic = atomic; + m_Matrix.Attach(RwFrameGetMatrix(RpAtomicGetFrame(m_pAtomic))); + m_pMaterial = RpGeometryGetMaterial(geometry, 0); + m_fSize = fSize; + m_fStdSize = m_fSize; + m_Color.red = r; + m_Color.green = g; + m_Color.blue = b; + m_Color.alpha = a; + m_nPulsePeriod = pulsePeriod; + m_fPulseFraction = pulseFraction; + m_nRotateRate = rotateRate; + m_nStartTime = CTimer::GetTimeInMilliseconds(); + m_nType = type; + return m_pAtomic != nil; +} + +void +C3dMarker::DeleteMarkerObject() +{ + RwFrame *frame; + + m_nIdentifier = 0; + m_nStartTime = 0; + m_bIsUsed = false; + m_nType = MARKERTYPE_INVALID; + + frame = RpAtomicGetFrame(m_pAtomic); + RpAtomicDestroy(m_pAtomic); + RwFrameDestroy(frame); + m_pAtomic = nil; +} + +void +C3dMarker::Render() +{ + if (m_pAtomic == nil) return; + + RpMaterialSetColor(m_pMaterial, &m_Color); + + m_Matrix.UpdateRW(); + + CMatrix matrix; + matrix.Attach(m_Matrix.m_attachment); + matrix.Scale(m_fSize); + matrix.UpdateRW(); + + RwFrameUpdateObjects(RpAtomicGetFrame(m_pAtomic)); + SetBrightMarkerColours(m_fBrightness); + if (m_nType != MARKERTYPE_ARROW) + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RpAtomicRender(m_pAtomic); + if (m_nType != MARKERTYPE_ARROW) + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + ReSetAmbientAndDirectionalColours(); +} + +C3dMarker C3dMarkers::m_aMarkerArray[NUM3DMARKERS]; +int32 C3dMarkers::NumActiveMarkers; +RpClump* C3dMarkers::m_pRpClumpArray[NUMMARKERTYPES]; + +void +C3dMarkers::Init() +{ + for (int i = 0; i < NUM3DMARKERS; i++) { + m_aMarkerArray[i].m_pAtomic = nil; + m_aMarkerArray[i].m_nType = MARKERTYPE_INVALID; + m_aMarkerArray[i].m_bIsUsed = false; + m_aMarkerArray[i].m_nIdentifier = 0; + m_aMarkerArray[i].m_Color.red = 255; + m_aMarkerArray[i].m_Color.green = 255; + m_aMarkerArray[i].m_Color.blue = 255; + m_aMarkerArray[i].m_Color.alpha = 255; + m_aMarkerArray[i].m_nPulsePeriod = 1024; + m_aMarkerArray[i].m_nRotateRate = 5; + m_aMarkerArray[i].m_nStartTime = 0; + m_aMarkerArray[i].m_fPulseFraction = 0.25f; + m_aMarkerArray[i].m_fStdSize = 1.0f; + m_aMarkerArray[i].m_fSize = 1.0f; + m_aMarkerArray[i].m_fBrightness = 1.0f; + m_aMarkerArray[i].m_fCameraRange = 0.0f; + } + NumActiveMarkers = 0; + int txdSlot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(txdSlot); + CFileMgr::ChangeDir("\\"); + m_pRpClumpArray[MARKERTYPE_ARROW] = CFileLoader::LoadAtomicFile2Return("models/generic/arrow.dff"); + m_pRpClumpArray[MARKERTYPE_CYLINDER] = CFileLoader::LoadAtomicFile2Return("models/generic/zonecylb.dff"); + CTxdStore::PopCurrentTxd(); +} + +void +C3dMarkers::Shutdown() +{ + for (int i = 0; i < NUM3DMARKERS; i++) { + if (m_aMarkerArray[i].m_pAtomic != nil) + m_aMarkerArray[i].DeleteMarkerObject(); + } + + for (int i = 0; i < NUMMARKERTYPES; i++) { + if (m_pRpClumpArray[i] != nil) + RpClumpDestroy(m_pRpClumpArray[i]); + } +} + +void +C3dMarkers::Render() +{ + NumActiveMarkers = 0; + ActivateDirectional(); + for (int i = 0; i < NUM3DMARKERS; i++) { + if (m_aMarkerArray[i].m_bIsUsed) { + if (m_aMarkerArray[i].m_fCameraRange < 120.0f) + m_aMarkerArray[i].Render(); + NumActiveMarkers++; + m_aMarkerArray[i].m_bIsUsed = false; + } else if (m_aMarkerArray[i].m_pAtomic != nil) { + m_aMarkerArray[i].DeleteMarkerObject(); + } + } +} + +C3dMarker * +C3dMarkers::PlaceMarker(uint32 identifier, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate) +{ + C3dMarker *pMarker; + + pMarker = nil; + float dist = Sqrt((pos.x - FindPlayerCentreOfWorld(0).x) * (pos.x - FindPlayerCentreOfWorld(0).x) + (pos.y - FindPlayerCentreOfWorld(0).y) * (pos.y - FindPlayerCentreOfWorld(0).y)); + + if (type != MARKERTYPE_ARROW && type != MARKERTYPE_CYLINDER) return nil; + + for (int i = 0; i < NUM3DMARKERS; i++) { + if (!m_aMarkerArray[i].m_bIsUsed && m_aMarkerArray[i].m_nIdentifier == identifier) { + pMarker = &m_aMarkerArray[i]; + break; + } + } + + if (pMarker == nil) { + for (int i = 0; i < NUM3DMARKERS; i++) { + if (m_aMarkerArray[i].m_nType == MARKERTYPE_INVALID) { + pMarker = &m_aMarkerArray[i]; + break; + } + } + } + + if (pMarker == nil && type == MARKERTYPE_ARROW) { + for (int i = 0; i < NUM3DMARKERS; i++) { + if (dist < m_aMarkerArray[i].m_fCameraRange && m_aMarkerArray[i].m_nType == MARKERTYPE_ARROW && (pMarker == nil || m_aMarkerArray[i].m_fCameraRange > pMarker->m_fCameraRange)) { + pMarker = &m_aMarkerArray[i]; + break; + } + } + + if (pMarker != nil) + pMarker->m_nType = MARKERTYPE_INVALID; + } + + if (pMarker == nil) return pMarker; + + pMarker->m_fCameraRange = dist; + if (pMarker->m_nIdentifier == identifier && pMarker->m_nType == type) { + if (type == MARKERTYPE_ARROW) { + if (dist < 25.0f) { + if (dist > 5.0f) + pMarker->m_fStdSize = size - (25.0f - dist) * (0.3f * size) / 20.0f; + else + pMarker->m_fStdSize = size - 0.3f * size; + } else { + pMarker->m_fStdSize = size; + } + } else if (type == MARKERTYPE_CYLINDER) { + if (dist < size + 12.0f) { + if (dist > size + 1.0f) + pMarker->m_Color.alpha = (1.0f - (size + 12.0f - dist) * 0.7f / 11.0f) * (float)a; + else + pMarker->m_Color.alpha = (float)a * 0.3f; + } else { + pMarker->m_Color.alpha = a; + } + } + float someSin = Sin(TWOPI * (float)((pMarker->m_nPulsePeriod - 1) & (CTimer::GetTimeInMilliseconds() - pMarker->m_nStartTime)) / (float)pMarker->m_nPulsePeriod); + pMarker->m_fSize = pMarker->m_fStdSize - pulseFraction * pMarker->m_fStdSize * someSin; + + if (type == MARKERTYPE_ARROW) { + pos.z += 0.25f * pMarker->m_fStdSize * someSin; + } else if (type == MARKERTYPE_0) { + if (someSin > 0.0f) + pMarker->m_Color.alpha = (float)a * 0.7f * someSin + a; + else + pMarker->m_Color.alpha = (float)a * 0.4f * someSin + a; + } + if (pMarker->m_nRotateRate) { + CVector pos = pMarker->m_Matrix.GetPosition(); + pMarker->m_Matrix.RotateZ(DEGTORAD(pMarker->m_nRotateRate * CTimer::GetTimeStep())); + pMarker->m_Matrix.GetPosition() = pos; + } + if (type == MARKERTYPE_ARROW) + pMarker->m_Matrix.GetPosition() = pos; + pMarker->m_bIsUsed = true; + return pMarker; + } + + if (pMarker->m_nIdentifier != 0) + pMarker->DeleteMarkerObject(); + + pMarker->AddMarker(identifier, type, size, r, g, b, a, pulsePeriod, pulseFraction, rotateRate); + if (type == MARKERTYPE_CYLINDER || type == MARKERTYPE_0 || type == MARKERTYPE_2) { + float z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 1.0f, nil); + if (z != 0.0f) + pos.z = z - 0.05f * size; + } + pMarker->m_Matrix.SetTranslate(pos.x, pos.y, pos.z); + if (type == MARKERTYPE_2) { + pMarker->m_Matrix.RotateX(PI); + pMarker->m_Matrix.GetPosition() = pos; + } + pMarker->m_Matrix.UpdateRW(); + if (type == MARKERTYPE_ARROW) { + if (dist < 25.0f) { + if (dist > 5.0f) + pMarker->m_fStdSize = size - (25.0f - dist) * (0.3f * size) / 20.0f; + else + pMarker->m_fStdSize = size - 0.3f * size; + } else { + pMarker->m_fStdSize = size; + } + } else if (type == MARKERTYPE_CYLINDER) { + if (dist < size + 12.0f) { + if (dist > size + 1.0f) + pMarker->m_Color.alpha = (1.0f - (size + 12.0f - dist) * 0.7f / 11.0f) * (float)a; + else + pMarker->m_Color.alpha = (float)a * 0.3f; + } else { + pMarker->m_Color.alpha = a; + } + } + pMarker->m_bIsUsed = true; + return pMarker; +} + +void +C3dMarkers::PlaceMarkerSet(uint32 id, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate) +{ + PlaceMarker(id, type, pos, size, r, g, b, a, pulsePeriod, pulseFraction, 1); + PlaceMarker(id, type, pos, size * 0.93f, r, g, b, a, pulsePeriod, pulseFraction, 2); + PlaceMarker(id, type, pos, size * 0.86f, r, g, b, a, pulsePeriod, pulseFraction, -1); +} + + +void +C3dMarkers::Update() +{ +} + + +#define BRIGHTLIGHTS_MAX_DIST (60.0f) // invisible beyond this +#define BRIGHTLIGHTS_FADE_DIST (45.0f) // strongest between these two +#define CARLIGHTS_MAX_DIST (30.0f) +#define CARLIGHTS_FADE_DIST (15.0f) // 31 for close lights + +int CBrightLights::NumBrightLights; +CBrightLight CBrightLights::aBrightLights[NUMBRIGHTLIGHTS]; + +void +CBrightLights::Init(void) +{ + NumBrightLights = 0; +} + +void +CBrightLights::RegisterOne(CVector pos, CVector up, CVector side, CVector front, + uint8 type, uint8 red, uint8 green, uint8 blue) +{ + if(NumBrightLights >= NUMBRIGHTLIGHTS) + return; + + aBrightLights[NumBrightLights].m_camDist = (pos - TheCamera.GetPosition()).Magnitude(); + if(aBrightLights[NumBrightLights].m_camDist > BRIGHTLIGHTS_MAX_DIST) + return; + + aBrightLights[NumBrightLights].m_pos = pos; + aBrightLights[NumBrightLights].m_up = up; + aBrightLights[NumBrightLights].m_side = side; + aBrightLights[NumBrightLights].m_front = front; + aBrightLights[NumBrightLights].m_type = type; + aBrightLights[NumBrightLights].m_red = red; + aBrightLights[NumBrightLights].m_green = green; + aBrightLights[NumBrightLights].m_blue = blue; + + NumBrightLights++; +} + +static float TrafficLightsSide[6] = { -0.09f, 0.09f, 0.162f, 0.09f, -0.09f, -0.162f }; +static float TrafficLightsUp[6] = { 0.162f, 0.162f, 0.0f, -0.162f, -0.162f, 0.0f }; +static float LongCarHeadLightsSide[8] = { -0.2f, 0.2f, -0.2f, 0.2f, -0.2f, 0.2f, -0.2f, 0.2f }; +static float LongCarHeadLightsFront[8] = { 0.1f, 0.1f, -0.1f, -0.1f, 0.1f, 0.1f, -0.1f, -0.1f }; +static float LongCarHeadLightsUp[8] = { 0.1f, 0.1f, 0.1f, 0.1f, -0.1f, -0.1f, -0.1f, -0.1f }; +static float SmallCarHeadLightsSide[8] = { -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f }; +static float SmallCarHeadLightsFront[8] = { 0.08f, 0.08f, -0.08f, -0.08f, 0.08f, 0.08f, -0.08f, -0.08f }; +static float SmallCarHeadLightsUp[8] = { 0.08f, 0.08f, 0.08f, 0.08f, -0.08f, -0.08f, -0.08f, -0.08f }; +static float BigCarHeadLightsSide[8] = { -0.15f, 0.15f, -0.15f, 0.15f, -0.15f, 0.15f, -0.15f, 0.15f }; +static float BigCarHeadLightsFront[8] = { 0.15f, 0.15f, -0.15f, -0.15f, 0.15f, 0.15f, -0.15f, -0.15f }; +static float BigCarHeadLightsUp[8] = { 0.15f, 0.15f, 0.15f, 0.15f, -0.15f, -0.15f, -0.15f, -0.15f }; +static float TallCarHeadLightsSide[8] = { -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f, -0.08f, 0.08f }; +static float TallCarHeadLightsFront[8] = { 0.08f, 0.08f, -0.08f, -0.08f, 0.08f, 0.08f, -0.08f, -0.08f }; +static float TallCarHeadLightsUp[8] = { 0.2f, 0.2f, 0.2f, 0.2f, -0.2f, -0.2f, -0.2f, -0.2f }; +static float SirenLightsSide[6] = { -0.04f, 0.04f, 0.06f, 0.04f, -0.04f, -0.06f }; +static float SirenLightsUp[6] = { 0.06f, 0.06f, 0.0f, -0.06f, -0.06f, 0.0f }; +static RwImVertexIndex TrafficLightIndices[4*3] = { 0, 1, 5, 1, 2, 3, 1, 3, 4, 1, 4, 5 }; +static RwImVertexIndex CubeIndices[12*3] = { + 0, 2, 1, 1, 2, 3, 3, 5, 1, 3, 7, 5, + 2, 7, 3, 2, 6, 7, 4, 0, 1, 4, 1, 5, + 6, 0, 4, 6, 2, 0, 6, 5, 7, 6, 4, 5 +}; + +void +CBrightLights::Render(void) +{ + int i, j; + CVector pos; + + if(NumBrightLights == 0) + return; + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + + for(i = 0; i < NumBrightLights; i++){ + if(TempBufferIndicesStored > TEMPBUFFERINDEXSIZE-40 || TempBufferVerticesStored > TEMPBUFFERVERTSIZE-40) + RenderOutGeometryBuffer(); + + int r, g, b, a; + float flicker = (CGeneral::GetRandomNumber()&0xFF) * 0.2f; + switch(aBrightLights[i].m_type){ + case BRIGHTLIGHT_TRAFFIC_GREEN: + r = flicker; g = 255; b = flicker; + break; + case BRIGHTLIGHT_TRAFFIC_YELLOW: + r = 255; g = 128; b = flicker; + break; + case BRIGHTLIGHT_TRAFFIC_RED: + r = 255; g = flicker; b = flicker; + break; + + case BRIGHTLIGHT_FRONT_LONG: + case BRIGHTLIGHT_FRONT_SMALL: + case BRIGHTLIGHT_FRONT_BIG: + case BRIGHTLIGHT_FRONT_TALL: + r = 255; g = 255; b = 255; + break; + + case BRIGHTLIGHT_REAR_LONG: + case BRIGHTLIGHT_REAR_SMALL: + case BRIGHTLIGHT_REAR_BIG: + case BRIGHTLIGHT_REAR_TALL: + r = 255; g = flicker; b = flicker; + break; + + case BRIGHTLIGHT_SIREN: + r = aBrightLights[i].m_red; + g = aBrightLights[i].m_green; + b = aBrightLights[i].m_blue; + break; + } + + if(aBrightLights[i].m_camDist < BRIGHTLIGHTS_FADE_DIST) + a = 255; + else + a = 255*(1.0f - (aBrightLights[i].m_camDist-BRIGHTLIGHTS_FADE_DIST)/(BRIGHTLIGHTS_MAX_DIST-BRIGHTLIGHTS_FADE_DIST)); + // fade car lights down to 31 as they come near + if(aBrightLights[i].m_type >= BRIGHTLIGHT_FRONT_LONG && aBrightLights[i].m_type <= BRIGHTLIGHT_REAR_TALL){ + if(aBrightLights[i].m_camDist < CARLIGHTS_FADE_DIST) + a = 31; + else if(aBrightLights[i].m_camDist < CARLIGHTS_MAX_DIST) + a = 31 + (255-31)*((aBrightLights[i].m_camDist-CARLIGHTS_FADE_DIST)/(CARLIGHTS_MAX_DIST-CARLIGHTS_FADE_DIST)); + } + + switch(aBrightLights[i].m_type){ + case BRIGHTLIGHT_TRAFFIC_GREEN: + case BRIGHTLIGHT_TRAFFIC_YELLOW: + case BRIGHTLIGHT_TRAFFIC_RED: + for(j = 0; j < 6; j++){ + pos = TrafficLightsSide[j]*aBrightLights[i].m_side + + TrafficLightsUp[j]*aBrightLights[i].m_up + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 4*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = TrafficLightIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 6; + TempBufferIndicesStored += 4*3; + break; + + case BRIGHTLIGHT_FRONT_LONG: + case BRIGHTLIGHT_REAR_LONG: + for(j = 0; j < 8; j++){ + pos = LongCarHeadLightsSide[j]*aBrightLights[i].m_side + + LongCarHeadLightsUp[j]*aBrightLights[i].m_up + + LongCarHeadLightsFront[j]*aBrightLights[i].m_front + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 12*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = CubeIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 8; + TempBufferIndicesStored += 12*3; + break; + + case BRIGHTLIGHT_FRONT_SMALL: + case BRIGHTLIGHT_REAR_SMALL: + for(j = 0; j < 8; j++){ + pos = SmallCarHeadLightsSide[j]*aBrightLights[i].m_side + + SmallCarHeadLightsUp[j]*aBrightLights[i].m_up + + SmallCarHeadLightsFront[j]*aBrightLights[i].m_front + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 12*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = CubeIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 8; + TempBufferIndicesStored += 12*3; + break; + + case BRIGHTLIGHT_FRONT_BIG: + case BRIGHTLIGHT_REAR_BIG: + for (j = 0; j < 8; j++) { + pos = BigCarHeadLightsSide[j] * aBrightLights[i].m_side + + BigCarHeadLightsUp[j] * aBrightLights[i].m_up + + BigCarHeadLightsFront[j] * aBrightLights[i].m_front + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + j], pos.x, pos.y, pos.z); + } + for (j = 0; j < 12 * 3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored + j] = CubeIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 8; + TempBufferIndicesStored += 12 * 3; + break; + + case BRIGHTLIGHT_FRONT_TALL: + case BRIGHTLIGHT_REAR_TALL: + for(j = 0; j < 8; j++){ + pos = TallCarHeadLightsSide[j]*aBrightLights[i].m_side + + TallCarHeadLightsUp[j]*aBrightLights[i].m_up + + TallCarHeadLightsFront[j]*aBrightLights[i].m_front + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 12*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = CubeIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 8; + TempBufferIndicesStored += 12*3; + break; + + case BRIGHTLIGHT_SIREN: + for(j = 0; j < 6; j++){ + pos = SirenLightsSide[j] * TheCamera.GetRight() + + SirenLightsUp[j] * TheCamera.GetUp() + + aBrightLights[i].m_pos; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+j], r, g, b, a); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+j], pos.x, pos.y, pos.z); + } + for(j = 0; j < 4*3; j++) + TempBufferRenderIndexList[TempBufferIndicesStored+j] = TrafficLightIndices[j] + TempBufferVerticesStored; + TempBufferVerticesStored += 6; + TempBufferIndicesStored += 4*3; + break; + + } + } + + RenderOutGeometryBuffer(); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + NumBrightLights = 0; +} + +void +CBrightLights::RenderOutGeometryBuffer(void) +{ + if(TempBufferIndicesStored != 0){ + LittleTest(); + if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + } +} + +int CShinyTexts::NumShinyTexts; +CShinyText CShinyTexts::aShinyTexts[NUMSHINYTEXTS]; + +void +CShinyTexts::Init(void) +{ + NumShinyTexts = 0; +} + +void +CShinyTexts::RegisterOne(CVector p0, CVector p1, CVector p2, CVector p3, + float u0, float v0, float u1, float v1, float u2, float v2, float u3, float v3, + uint8 type, uint8 red, uint8 green, uint8 blue, float maxDist) +{ + if(NumShinyTexts >= NUMSHINYTEXTS) + return; + + aShinyTexts[NumShinyTexts].m_camDist = (p0 - TheCamera.GetPosition()).Magnitude(); + if(aShinyTexts[NumShinyTexts].m_camDist > maxDist) + return; + aShinyTexts[NumShinyTexts].m_verts[0] = p0; + aShinyTexts[NumShinyTexts].m_verts[1] = p1; + aShinyTexts[NumShinyTexts].m_verts[2] = p2; + aShinyTexts[NumShinyTexts].m_verts[3] = p3; + aShinyTexts[NumShinyTexts].m_texCoords[0].x = u0; + aShinyTexts[NumShinyTexts].m_texCoords[0].y = v0; + aShinyTexts[NumShinyTexts].m_texCoords[1].x = u1; + aShinyTexts[NumShinyTexts].m_texCoords[1].y = v1; + aShinyTexts[NumShinyTexts].m_texCoords[2].x = u2; + aShinyTexts[NumShinyTexts].m_texCoords[2].y = v2; + aShinyTexts[NumShinyTexts].m_texCoords[3].x = u3; + aShinyTexts[NumShinyTexts].m_texCoords[3].y = v3; + aShinyTexts[NumShinyTexts].m_type = type; + aShinyTexts[NumShinyTexts].m_red = red; + aShinyTexts[NumShinyTexts].m_green = green; + aShinyTexts[NumShinyTexts].m_blue = blue; + // Fade out at half the max dist + float halfDist = maxDist*0.5f; + if(aShinyTexts[NumShinyTexts].m_camDist > halfDist){ + float f = 1.0f - (aShinyTexts[NumShinyTexts].m_camDist - halfDist)/halfDist; + aShinyTexts[NumShinyTexts].m_red *= f; + aShinyTexts[NumShinyTexts].m_green *= f; + aShinyTexts[NumShinyTexts].m_blue *= f; + } + + NumShinyTexts++; +} + +void +CShinyTexts::Render(void) +{ + int i, ix, v; + RwTexture *lastTex = nil; + + if(NumShinyTexts == 0) + return; + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + + for(i = 0; i < NumShinyTexts; i++){ + if(TempBufferIndicesStored > TEMPBUFFERINDEXSIZE-64 || TempBufferVerticesStored > TEMPBUFFERVERTSIZE-62) + RenderOutGeometryBuffer(); + + uint8 r = aShinyTexts[i].m_red; + uint8 g = aShinyTexts[i].m_green; + uint8 b = aShinyTexts[i].m_blue; + + switch(aShinyTexts[i].m_type){ + case SHINYTEXT_WALK: + if(lastTex != gpWalkDontTex){ + RenderOutGeometryBuffer(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpWalkDontTex)); + lastTex = gpWalkDontTex; + } + quad: + v = TempBufferVerticesStored; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+0], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+0], aShinyTexts[i].m_verts[0].x, aShinyTexts[i].m_verts[0].y, aShinyTexts[i].m_verts[0].z); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+0], aShinyTexts[i].m_texCoords[0].x); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+0], aShinyTexts[i].m_texCoords[0].y); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+1], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+1], aShinyTexts[i].m_verts[1].x, aShinyTexts[i].m_verts[1].y, aShinyTexts[i].m_verts[1].z); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+1], aShinyTexts[i].m_texCoords[1].x); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+1], aShinyTexts[i].m_texCoords[1].y); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+2], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+2], aShinyTexts[i].m_verts[2].x, aShinyTexts[i].m_verts[2].y, aShinyTexts[i].m_verts[2].z); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+2], aShinyTexts[i].m_texCoords[2].x); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+2], aShinyTexts[i].m_texCoords[2].y); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+3], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+3], aShinyTexts[i].m_verts[3].x, aShinyTexts[i].m_verts[3].y, aShinyTexts[i].m_verts[3].z); + RwIm3DVertexSetU(&TempBufferRenderVertices[v+3], aShinyTexts[i].m_texCoords[3].x); + RwIm3DVertexSetV(&TempBufferRenderVertices[v+3], aShinyTexts[i].m_texCoords[3].y); + ix = TempBufferIndicesStored; + TempBufferRenderIndexList[ix+0] = 0 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+1] = 1 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+2] = 2 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+3] = 2 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+4] = 1 + TempBufferVerticesStored; + TempBufferRenderIndexList[ix+5] = 3 + TempBufferVerticesStored; + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; + break; + + case SHINYTEXT_FLAT: + if(lastTex != nil){ + RenderOutGeometryBuffer(); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + lastTex = nil; + } + goto quad; + } + } + + RenderOutGeometryBuffer(); + NumShinyTexts = 0; + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +} + +void +CShinyTexts::RenderOutGeometryBuffer(void) +{ + if(TempBufferIndicesStored != 0){ + LittleTest(); + if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + } +} + + + +#define MONEY_MESSAGE_LIFETIME_MS 2000 + +CMoneyMessage CMoneyMessages::aMoneyMessages[NUMMONEYMESSAGES]; + +void +CMoneyMessage::Render() +{ + const float MAX_SCALE = 4.0f; + uint32 nLifeTime = CTimer::GetTimeInMilliseconds() - m_nTimeRegistered; + if (nLifeTime >= MONEY_MESSAGE_LIFETIME_MS) m_nTimeRegistered = 0; + else { + float fLifeTime = (float)nLifeTime / MONEY_MESSAGE_LIFETIME_MS; + RwV3d vecOut; + float fDistX, fDistY; + if (CSprite::CalcScreenCoors(m_vecPosition + CVector(0.0f, 0.0f, fLifeTime), &vecOut, &fDistX, &fDistY, true)) { + fDistX *= (0.7f * fLifeTime + 2.0f) * m_fSize; + fDistY *= (0.7f * fLifeTime + 2.0f) * m_fSize; + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + + float fScaleY = Min(fDistY / 100.0f, MAX_SCALE); + float fScaleX = Min(fDistX / 100.0f, MAX_SCALE); + +#ifdef FIX_BUGS + CFont::SetScale(SCREEN_SCALE_X(fScaleX), SCREEN_SCALE_Y(fScaleY)); +#else + CFont::SetScale(fScaleX, fScaleY); +#endif + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_WIDTH); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(m_Colour.r, m_Colour.g, m_Colour.b, (255.0f - 255.0f * fLifeTime) * m_fOpacity)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString(vecOut.x, vecOut.y, m_aText); + } + } +} + +void +CMoneyMessages::Init() +{ + for (int32 i = 0; i < NUMMONEYMESSAGES; i++) + aMoneyMessages[i].m_nTimeRegistered = 0; +} + +void +CMoneyMessages::Render() +{ + for (int32 i = 0; i < NUMMONEYMESSAGES; i++) { + if (aMoneyMessages[i].m_nTimeRegistered != 0) + aMoneyMessages[i].Render(); + } +} + +void +CMoneyMessages::RegisterOne(CVector vecPos, const char *pText, uint8 bRed, uint8 bGreen, uint8 bBlue, float fSize, float fOpacity) +{ + uint32 i; +#ifdef FIX_BUGS + for(i = 0; i < NUMMONEYMESSAGES && aMoneyMessages[i].m_nTimeRegistered != 0; i++); +#else + for(i = 0; aMoneyMessages[i].m_nTimeRegistered != 0 && i < NUMMONEYMESSAGES; i++); +#endif + + if(i < NUMMONEYMESSAGES) { + // Add data of this money message to the array + AsciiToUnicode(pText, aMoneyMessages[i].m_aText); + + aMoneyMessages[i].m_nTimeRegistered = CTimer::GetTimeInMilliseconds(); + aMoneyMessages[i].m_vecPosition = vecPos; + aMoneyMessages[i].m_Colour.red = bRed; + aMoneyMessages[i].m_Colour.green = bGreen; + aMoneyMessages[i].m_Colour.blue = bBlue; + aMoneyMessages[i].m_fSize = fSize; + aMoneyMessages[i].m_fOpacity = fOpacity; + } +} + +CRGBA FoamColour(255, 255, 255, 255); +uint32 CSpecialParticleStuff::BoatFromStart; + +void +CSpecialParticleStuff::CreateFoamAroundObject(CMatrix* pMatrix, float innerFw, float innerRg, float innerUp, int32 particles) +{ + float outerFw = innerFw + 5.0f; + float outerRg = innerRg + 5.0f; + float outerUp = innerUp + 5.0f; + for (int attempts = 0; particles > 0 && attempts < 1000; attempts++) { + CVector pos; + int rnd = CGeneral::GetRandomNumber(); + pos.x = (int8)(rnd - 128) * innerFw / 110.0f; + pos.y = (int8)((rnd >> 8) - 128) * innerFw / 110.0f; + pos.z = 0.0f; + if (DotProduct2D(pos, TheCamera.GetForward()) >= 0) + continue; + // was there any point in adding it here? + pos += pMatrix->GetPosition(); + pos.z = 2.0f; + float fw = Abs(DotProduct(pMatrix->GetForward(), pos - pMatrix->GetPosition())); + if (fw >= outerFw) + continue; + float rg = Abs(DotProduct(pMatrix->GetRight(), pos - pMatrix->GetPosition())); + if (rg >= outerRg) + continue; + float up = Abs(DotProduct(pMatrix->GetUp(), pos - pMatrix->GetPosition())); + if (up >= outerUp) + continue; + if (fw > innerFw || rg > innerRg || up > innerUp) { + CParticle::AddParticle(PARTICLE_STEAM2, pos, CVector(0.0f, 0.0f, 0.0f), nil, 4.0f, FoamColour, 1, 0, 0, 0); + particles--; + } + } +} + +void +CSpecialParticleStuff::StartBoatFoamAnimation() +{ + BoatFromStart = CTimer::GetTimeInMilliseconds(); +} + +void +CSpecialParticleStuff::UpdateBoatFoamAnimation(CMatrix* pMatrix) +{ + static int32 FrameInAnimation = 0; + static float X, Y, Z, dX, dY, dZ; + CreateFoamAroundObject(pMatrix, 107.0f, 24.1f, 30.5f, 2); + uint32 prev = CTimer::GetPreviousTimeInMilliseconds(); + uint32 cur = CTimer::GetTimeInMilliseconds(); + if (FrameInAnimation != 0) { + X += dX; + Y += dY; + Z += dZ; + CVector pos = *pMatrix * CVector(X, Y, Z); + CParticle::AddParticle(PARTICLE_STEAM_NY, pos, CVector(0.0f, 0.0f, 0.0f), + nil, FrameInAnimation * 0.5f + 2.0f, FoamColour, 1, 0, 0, 0); + if (++FrameInAnimation > 15) + FrameInAnimation = 0; + } + if ((cur & 0x3FF) < (prev & 0x3FF)) { + FrameInAnimation = 1; + int rnd = CGeneral::GetRandomNumber(); + X = (int8)(rnd - 128) * 0.2f; + Y = (int8)((rnd >> 8) - 128) * 0.2f; + Z = 10.0f; + rnd = CGeneral::GetRandomNumber(); + dX = (int8)(rnd - 128) * 0.02f; + dY = (int8)((rnd >> 8) - 128) * 0.02f; + dZ = 2.0f; + } +} diff --git a/src/renderer/SpecialFX.h b/src/renderer/SpecialFX.h new file mode 100644 index 0000000..2d9f18b --- /dev/null +++ b/src/renderer/SpecialFX.h @@ -0,0 +1,224 @@ +#pragma once + +class CSpecialFX +{ +public: + static void Render(void); + static void Update(void); + static void Init(void); + static void Shutdown(void); +}; + +class CRegisteredMotionBlurStreak +{ +public: + uintptr m_id; + uint8 m_red; + uint8 m_green; + uint8 m_blue; + CVector m_pos1[3]; + CVector m_pos2[3]; + bool m_isValid[3]; + + void Update(void); + void Render(void); +}; + +class CMotionBlurStreaks +{ + static CRegisteredMotionBlurStreak aStreaks[NUMMBLURSTREAKS]; +public: + static void Init(void); + static void Update(void); + static void RegisterStreak(uintptr id, uint8 r, uint8 g, uint8 b, CVector p1, CVector p2); + static void Render(void); +}; + +struct CBulletTrace +{ + CVector m_vecCurrentPos; + CVector m_vecTargetPos; + bool m_bInUse; + uint8 m_framesInUse; + uint8 m_lifeTime; + + void Update(void); +}; + +class CBulletTraces +{ +public: + static CBulletTrace aTraces[NUMBULLETTRACES]; + + static void Init(void); + static void AddTrace(CVector*, CVector*); + static void Render(void); + static void Update(void); +}; + +enum +{ + MARKERTYPE_0 = 0, + MARKERTYPE_ARROW, + MARKERTYPE_2, + MARKERTYPE_3, + MARKERTYPE_CYLINDER, + NUMMARKERTYPES, + + MARKERTYPE_INVALID = 0x101 +}; + + +class C3dMarker +{ +public: + CMatrix m_Matrix; + RpAtomic *m_pAtomic; + RpMaterial *m_pMaterial; + uint16 m_nType; + bool m_bIsUsed; + uint32 m_nIdentifier; + RwRGBA m_Color; + uint16 m_nPulsePeriod; + int16 m_nRotateRate; + uint32 m_nStartTime; + float m_fPulseFraction; + float m_fStdSize; + float m_fSize; + float m_fBrightness; + float m_fCameraRange; + + bool AddMarker(uint32 identifier, uint16 type, float fSize, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate); + void DeleteMarkerObject(); + void Render(); +}; + +class C3dMarkers +{ +public: + static void Init(); + static void Shutdown(); + static C3dMarker *PlaceMarker(uint32 id, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate); + static void PlaceMarkerSet(uint32 id, uint16 type, CVector &pos, float size, uint8 r, uint8 g, uint8 b, uint8 a, uint16 pulsePeriod, float pulseFraction, int16 rotateRate); + static void Render(); + static void Update(); + + static C3dMarker m_aMarkerArray[NUM3DMARKERS]; + static int32 NumActiveMarkers; + static RpClump* m_pRpClumpArray[NUMMARKERTYPES]; +}; + +enum +{ + BRIGHTLIGHT_INVALID, + BRIGHTLIGHT_TRAFFIC_GREEN, + BRIGHTLIGHT_TRAFFIC_YELLOW, + BRIGHTLIGHT_TRAFFIC_RED, + + // white + BRIGHTLIGHT_FRONT_LONG, + BRIGHTLIGHT_FRONT_SMALL, + BRIGHTLIGHT_FRONT_BIG, + BRIGHTLIGHT_FRONT_TALL, + + // red + BRIGHTLIGHT_REAR_LONG, + BRIGHTLIGHT_REAR_SMALL, + BRIGHTLIGHT_REAR_BIG, + BRIGHTLIGHT_REAR_TALL, + + BRIGHTLIGHT_SIREN, // unused + + BRIGHTLIGHT_FRONT = BRIGHTLIGHT_FRONT_LONG, + BRIGHTLIGHT_REAR = BRIGHTLIGHT_REAR_LONG, +}; + +class CBrightLight +{ +public: + CVector m_pos; + CVector m_up; + CVector m_side; + CVector m_front; + float m_camDist; + uint8 m_type; + uint8 m_red; + uint8 m_green; + uint8 m_blue; +}; + +class CBrightLights +{ + static int NumBrightLights; + static CBrightLight aBrightLights[NUMBRIGHTLIGHTS]; +public: + static void Init(void); + static void RegisterOne(CVector pos, CVector up, CVector side, CVector front, + uint8 type, uint8 red = 0, uint8 green = 0, uint8 blue = 0); + static void Render(void); + static void RenderOutGeometryBuffer(void); +}; + + +enum +{ + SHINYTEXT_WALK = 1, + SHINYTEXT_FLAT +}; + +class CShinyText +{ +public: + CVector m_verts[4]; + CVector2D m_texCoords[4]; + float m_camDist; + uint8 m_type; + uint8 m_red; + uint8 m_green; + uint8 m_blue; +}; + +class CShinyTexts +{ + static int NumShinyTexts; + static CShinyText aShinyTexts[NUMSHINYTEXTS]; +public: + static void Init(void); + static void RegisterOne(CVector p0, CVector p1, CVector p2, CVector p3, + float u0, float v0, float u1, float v1, float u2, float v2, float u3, float v3, + uint8 type, uint8 red, uint8 green, uint8 blue, float maxDist); + static void Render(void); + static void RenderOutGeometryBuffer(void); +}; + +class CMoneyMessage +{ + friend class CMoneyMessages; + + uint32 m_nTimeRegistered; + CVector m_vecPosition; + wchar m_aText[16]; + CRGBA m_Colour; + float m_fSize; + float m_fOpacity; +public: + void Render(); +}; + +class CMoneyMessages +{ + static CMoneyMessage aMoneyMessages[NUMMONEYMESSAGES]; +public: + static void Init(); + static void Render(); + static void RegisterOne(CVector vecPos, const char *pText, uint8 bRed, uint8 bGreen, uint8 bBlue, float fSize, float fOpacity); +}; + +class CSpecialParticleStuff +{ + static uint32 BoatFromStart; +public: + static void CreateFoamAroundObject(CMatrix*, float, float, float, int32); + static void StartBoatFoamAnimation(); + static void UpdateBoatFoamAnimation(CMatrix*); +}; diff --git a/src/renderer/Sprite.cpp b/src/renderer/Sprite.cpp new file mode 100644 index 0000000..3fef073 --- /dev/null +++ b/src/renderer/Sprite.cpp @@ -0,0 +1,603 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "Camera.h" +#include "Sprite.h" + +#ifdef ASPECT_RATIO_SCALE +#include "Frontend.h" +#endif + +float CSprite::m_f2DNearScreenZ; +float CSprite::m_f2DFarScreenZ; +float CSprite::m_fRecipNearClipPlane; +int32 CSprite::m_bFlushSpriteBufferSwitchZTest; + +float +CSprite::CalcHorizonCoors(void) +{ + CVector p = TheCamera.GetPosition() + CVector(TheCamera.CamFrontXNorm, TheCamera.CamFrontYNorm, 0.0f)*3000.0f; + p.z = 0.0f; + p = TheCamera.m_viewMatrix * p; + return p.y * SCREEN_HEIGHT / p.z; +} + +bool +CSprite::CalcScreenCoors(const RwV3d &in, RwV3d *out, float *outw, float *outh, bool farclip) +{ + CVector viewvec = TheCamera.m_viewMatrix * in; + *out = viewvec; + if(out->z <= CDraw::GetNearClipZ() + 1.0f) return false; + if(out->z >= CDraw::GetFarClipZ() && farclip) return false; + float recip = 1.0f/out->z; + out->x *= SCREEN_WIDTH * recip; + out->y *= SCREEN_HEIGHT * recip; + const float fov = DefaultFOV; + // this is used to scale correctly if you zoom in with sniper rifle + float fovScale = fov / CDraw::GetFOV(); + +#ifdef FIX_SPRITES + *outw = CDraw::ms_bFixSprites ? (fovScale * recip * SCREEN_HEIGHT) : (fovScale * SCREEN_SCALE_AR(recip) * SCREEN_WIDTH); +#else + *outw = fovScale * SCREEN_SCALE_AR(recip) * SCREEN_WIDTH; +#endif + *outh = fovScale * recip * SCREEN_HEIGHT; + + return true; +} + +#define SPRITEBUFFERSIZE 64 +static int32 nSpriteBufferIndex; +static RwIm2DVertex SpriteBufferVerts[SPRITEBUFFERSIZE*6]; +static RwIm2DVertex verts[4]; + +void +CSprite::InitSpriteBuffer(void) +{ + m_f2DNearScreenZ = RwIm2DGetNearScreenZ(); + m_f2DFarScreenZ = RwIm2DGetFarScreenZ(); +} + +void +CSprite::InitSpriteBuffer2D(void) +{ + m_fRecipNearClipPlane = 1.0f / RwCameraGetNearClipPlane(Scene.camera); + InitSpriteBuffer(); +} + +void +CSprite::FlushSpriteBuffer(void) +{ + if(nSpriteBufferIndex > 0){ + if(m_bFlushSpriteBufferSwitchZTest){ + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, SpriteBufferVerts, nSpriteBufferIndex*6); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + }else + RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, SpriteBufferVerts, nSpriteBufferIndex*6); + nSpriteBufferIndex = 0; + } +} + +void +CSprite::RenderOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a) +{ + static short indices[] = { 0, 1, 2, 3 }; + // 0---3 + // | | + // 1---2 + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x-w; us[0] = 0.0f; + xs[1] = x-w; us[1] = 0.0f; + xs[2] = x+w; us[2] = 1.0f; + xs[3] = x+w; us[3] = 1.0f; + + ys[0] = y-h; vs[0] = 0.0f; + ys[1] = y+h; vs[1] = 1.0f; + ys[2] = y+h; vs[2] = 1.0f; + ys[3] = y-h; vs[3] = 0.0f; + + // clip + for(i = 0; i < 4; i++){ + if(xs[i] < 0.0f){ + us[i] = -xs[i] / (2.0f*w); + xs[i] = 0.0f; + } + if(xs[i] > SCREEN_WIDTH){ + us[i] = 1.0f - (xs[i]-SCREEN_WIDTH) / (2.0f*w); + xs[i] = SCREEN_WIDTH; + } + if(ys[i] < 0.0f){ + vs[i] = -ys[i] / (2.0f*h); + ys[i] = 0.0f; + } + if(ys[i] > SCREEN_HEIGHT){ + vs[i] = 1.0f - (ys[i]-SCREEN_HEIGHT) / (2.0f*h); + ys[i] = SCREEN_HEIGHT; + } + } + + // (DrawZ - DrawNear)/(DrawFar - DrawNear) = (SpriteZ-SpriteNear)/(SpriteFar-SpriteNear) + // So to calculate SpriteZ: + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + for(i = 0; i < 4; i++){ + RwIm2DVertexSetScreenX(&verts[i], xs[i]); + RwIm2DVertexSetScreenY(&verts[i], ys[i]); + RwIm2DVertexSetScreenZ(&verts[i], screenz); + RwIm2DVertexSetCameraZ(&verts[i], z); + RwIm2DVertexSetRecipCameraZ(&verts[i], recipz); + RwIm2DVertexSetIntRGBA(&verts[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&verts[i], us[i], recipz); + RwIm2DVertexSetV(&verts[i], vs[i], recipz); + } + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, verts, 4); +} + +void +CSprite::RenderOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a) +{ + float c = Cos(rotation); + float s = Sin(rotation); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + // Fade out when too near + // why not in buffered version? + if(z < 3.0f){ + if(z < 1.5f) + return; + int f = (z - 1.5f)/1.5f * 255; + r = f*r >> 8; + g = f*g >> 8; + b = f*b >> 8; + intens = f*intens >> 8; + } + + xs[0] = x + w*(-c-s); us[0] = 0.0f; + xs[1] = x + w*(-c+s); us[1] = 0.0f; + xs[2] = x + w*(+c+s); us[2] = 1.0f; + xs[3] = x + w*(+c-s); us[3] = 1.0f; + + ys[0] = y + h*(-c+s); vs[0] = 0.0f; + ys[1] = y + h*(+c+s); vs[1] = 1.0f; + ys[2] = y + h*(+c-s); vs[2] = 1.0f; + ys[3] = y + h*(-c-s); vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > SCREEN_WIDTH && xs[1] > SCREEN_WIDTH && + xs[2] > SCREEN_WIDTH && xs[3] > SCREEN_WIDTH) return; + if(ys[0] > SCREEN_HEIGHT && ys[1] > SCREEN_HEIGHT && + ys[2] > SCREEN_HEIGHT && ys[3] > SCREEN_HEIGHT) return; + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + for(i = 0; i < 4; i++){ + RwIm2DVertexSetScreenX(&verts[i], xs[i]); + RwIm2DVertexSetScreenY(&verts[i], ys[i]); + RwIm2DVertexSetScreenZ(&verts[i], screenz); + RwIm2DVertexSetCameraZ(&verts[i], z); + RwIm2DVertexSetRecipCameraZ(&verts[i], recipz); + RwIm2DVertexSetIntRGBA(&verts[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&verts[i], us[i], recipz); + RwIm2DVertexSetV(&verts[i], vs[i], recipz); + } + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, verts, 4); +} + +void +CSprite::RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + + // 0---3 + // | | + // 1---2 + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x-w; us[0] = 0.0f; + xs[1] = x-w; us[1] = 0.0f; + xs[2] = x+w; us[2] = 1.0f; + xs[3] = x+w; us[3] = 1.0f; + + ys[0] = y-h; vs[0] = 0.0f; + ys[1] = y+h; vs[1] = 1.0f; + ys[2] = y+h; vs[2] = 1.0f; + ys[3] = y-h; vs[3] = 0.0f; + + // clip + for(i = 0; i < 4; i++){ + if(xs[i] < 0.0f){ + us[i] = -xs[i] / (2.0f*w); + xs[i] = 0.0f; + } + if(xs[i] > SCREEN_WIDTH){ + us[i] = 1.0f - (xs[i]-SCREEN_WIDTH) / (2.0f*w); + xs[i] = SCREEN_WIDTH; + } + if(ys[i] < 0.0f){ + vs[i] = -ys[i] / (2.0f*h); + ys[i] = 0.0f; + } + if(ys[i] > SCREEN_HEIGHT){ + vs[i] = 1.0f - (ys[i]-SCREEN_HEIGHT) / (2.0f*h); + ys[i] = SCREEN_HEIGHT; + } + } + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + // TODO: replace with lookup + float c = Cos(DEGTORAD(rotation)); + float s = Sin(DEGTORAD(rotation)); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x - c*w - s*h; us[0] = 0.0f; + xs[1] = x - c*w + s*h; us[1] = 0.0f; + xs[2] = x + c*w + s*h; us[2] = 1.0f; + xs[3] = x + c*w - s*h; us[3] = 1.0f; + + ys[0] = y - c*h + s*w; vs[0] = 0.0f; + ys[1] = y + c*h + s*w; vs[1] = 1.0f; + ys[2] = y + c*h - s*w; vs[2] = 1.0f; + ys[3] = y - c*h - s*w; vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > SCREEN_WIDTH && xs[1] > SCREEN_WIDTH && + xs[2] > SCREEN_WIDTH && xs[3] > SCREEN_WIDTH) return; + if(ys[0] > SCREEN_HEIGHT && ys[1] > SCREEN_HEIGHT && + ys[2] > SCREEN_HEIGHT && ys[3] > SCREEN_HEIGHT) return; + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float rotation, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + float c = Cos(rotation); + float s = Sin(rotation); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + int i; + + xs[0] = x + w*(-c-s); us[0] = 0.0f; + xs[1] = x + w*(-c+s); us[1] = 0.0f; + xs[2] = x + w*(+c+s); us[2] = 1.0f; + xs[3] = x + w*(+c-s); us[3] = 1.0f; + + ys[0] = y + h*(-c+s); vs[0] = 0.0f; + ys[1] = y + h*(+c+s); vs[1] = 1.0f; + ys[2] = y + h*(+c-s); vs[2] = 1.0f; + ys[3] = y + h*(-c-s); vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > SCREEN_WIDTH && xs[1] > SCREEN_WIDTH && + xs[2] > SCREEN_WIDTH && xs[3] > SCREEN_WIDTH) return; + if(ys[0] > SCREEN_HEIGHT && ys[1] > SCREEN_HEIGHT && + ys[2] > SCREEN_HEIGHT && ys[3] > SCREEN_HEIGHT) return; + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], r*intens>>8, g*intens>>8, b*intens>>8, a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(float x, float y, float z, float w, float h, uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2, float cx, float cy, float recipz, float rotation, uint8 a) +{ + m_bFlushSpriteBufferSwitchZTest = 0; + float c = Cos(rotation); + float s = Sin(rotation); + + float xs[4]; + float ys[4]; + float us[4]; + float vs[4]; + float cf[4]; + int i; + + xs[0] = x + w*(-c-s); us[0] = 0.0f; + xs[1] = x + w*(-c+s); us[1] = 0.0f; + xs[2] = x + w*(+c+s); us[2] = 1.0f; + xs[3] = x + w*(+c-s); us[3] = 1.0f; + + ys[0] = y + h*(-c+s); vs[0] = 0.0f; + ys[1] = y + h*(+c+s); vs[1] = 1.0f; + ys[2] = y + h*(+c-s); vs[2] = 1.0f; + ys[3] = y + h*(-c-s); vs[3] = 0.0f; + + // No clipping, just culling + if(xs[0] < 0.0f && xs[1] < 0.0f && xs[2] < 0.0f && xs[3] < 0.0f) return; + if(ys[0] < 0.0f && ys[1] < 0.0f && ys[2] < 0.0f && ys[3] < 0.0f) return; + if(xs[0] > SCREEN_WIDTH && xs[1] > SCREEN_WIDTH && + xs[2] > SCREEN_WIDTH && xs[3] > SCREEN_WIDTH) return; + if(ys[0] > SCREEN_HEIGHT && ys[1] > SCREEN_HEIGHT && + ys[2] > SCREEN_HEIGHT && ys[3] > SCREEN_HEIGHT) return; + + // Colour factors, cx/y is the direction in which colours change from rgb1 to rgb2 + cf[0] = (cx*(-c-s) + cy*(-c+s))*0.5f + 0.5f; + cf[0] = Clamp(cf[0], 0.0f, 1.0f); + cf[1] = (cx*(-c+s) + cy*( c+s))*0.5f + 0.5f; + cf[1] = Clamp(cf[1], 0.0f, 1.0f); + cf[2] = (cx*( c+s) + cy*( c-s))*0.5f + 0.5f; + cf[2] = Clamp(cf[2], 0.0f, 1.0f); + cf[3] = (cx*( c-s) + cy*(-c-s))*0.5f + 0.5f; + cf[3] = Clamp(cf[3], 0.0f, 1.0f); + + float screenz = m_f2DNearScreenZ + + (z-CDraw::GetNearClipZ())*(m_f2DFarScreenZ-m_f2DNearScreenZ)*CDraw::GetFarClipZ() / + ((CDraw::GetFarClipZ()-CDraw::GetNearClipZ())*z); + + RwIm2DVertex *vert = &SpriteBufferVerts[nSpriteBufferIndex*6]; + static int indices[6] = { 0, 1, 2, 3, 0, 2 }; + for(i = 0; i < 6; i++){ + RwIm2DVertexSetScreenX(&vert[i], xs[indices[i]]); + RwIm2DVertexSetScreenY(&vert[i], ys[indices[i]]); + RwIm2DVertexSetScreenZ(&vert[i], screenz); + RwIm2DVertexSetCameraZ(&vert[i], z); + RwIm2DVertexSetRecipCameraZ(&vert[i], recipz); + RwIm2DVertexSetIntRGBA(&vert[i], + r1*cf[indices[i]] + r2*(1.0f - cf[indices[i]]), + g1*cf[indices[i]] + g2*(1.0f - cf[indices[i]]), + b1*cf[indices[i]] + b2*(1.0f - cf[indices[i]]), + a); + RwIm2DVertexSetU(&vert[i], us[indices[i]], recipz); + RwIm2DVertexSetV(&vert[i], vs[indices[i]], recipz); + } + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::Set6Vertices2D(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + float screenz, recipz; + float z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + screenz = m_f2DNearScreenZ; + recipz = m_fRecipNearClipPlane; + + RwIm2DVertexSetScreenX(&verts[0], r.left); + RwIm2DVertexSetScreenY(&verts[0], r.top); + RwIm2DVertexSetScreenZ(&verts[0], screenz); + RwIm2DVertexSetCameraZ(&verts[0], z); + RwIm2DVertexSetRecipCameraZ(&verts[0], recipz); + RwIm2DVertexSetIntRGBA(&verts[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[0], 0.0f, recipz); + RwIm2DVertexSetV(&verts[0], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[1], r.right); + RwIm2DVertexSetScreenY(&verts[1], r.top); + RwIm2DVertexSetScreenZ(&verts[1], screenz); + RwIm2DVertexSetCameraZ(&verts[1], z); + RwIm2DVertexSetRecipCameraZ(&verts[1], recipz); + RwIm2DVertexSetIntRGBA(&verts[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&verts[1], 1.0f, recipz); + RwIm2DVertexSetV(&verts[1], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[2], r.right); + RwIm2DVertexSetScreenY(&verts[2], r.bottom); + RwIm2DVertexSetScreenZ(&verts[2], screenz); + RwIm2DVertexSetCameraZ(&verts[2], z); + RwIm2DVertexSetRecipCameraZ(&verts[2], recipz); + RwIm2DVertexSetIntRGBA(&verts[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[2], 1.0f, recipz); + RwIm2DVertexSetV(&verts[2], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[3], r.left); + RwIm2DVertexSetScreenY(&verts[3], r.bottom); + RwIm2DVertexSetScreenZ(&verts[3], screenz); + RwIm2DVertexSetCameraZ(&verts[3], z); + RwIm2DVertexSetRecipCameraZ(&verts[3], recipz); + RwIm2DVertexSetIntRGBA(&verts[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&verts[3], 0.0f, recipz); + RwIm2DVertexSetV(&verts[3], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[4], r.left); + RwIm2DVertexSetScreenY(&verts[4], r.top); + RwIm2DVertexSetScreenZ(&verts[4], screenz); + RwIm2DVertexSetCameraZ(&verts[4], z); + RwIm2DVertexSetRecipCameraZ(&verts[4], recipz); + RwIm2DVertexSetIntRGBA(&verts[4], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[4], 0.0f, recipz); + RwIm2DVertexSetV(&verts[4], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[5], r.right); + RwIm2DVertexSetScreenY(&verts[5], r.bottom); + RwIm2DVertexSetScreenZ(&verts[5], screenz); + RwIm2DVertexSetCameraZ(&verts[5], z); + RwIm2DVertexSetRecipCameraZ(&verts[5], recipz); + RwIm2DVertexSetIntRGBA(&verts[5], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[5], 1.0f, recipz); + RwIm2DVertexSetV(&verts[5], 1.0f, recipz); +} + +void +CSprite::Set6Vertices2D(RwIm2DVertex *verts, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + float screenz, recipz; + float z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + screenz = m_f2DNearScreenZ; + recipz = m_fRecipNearClipPlane; + + RwIm2DVertexSetScreenX(&verts[0], x3); + RwIm2DVertexSetScreenY(&verts[0], y3); + RwIm2DVertexSetScreenZ(&verts[0], screenz); + RwIm2DVertexSetCameraZ(&verts[0], z); + RwIm2DVertexSetRecipCameraZ(&verts[0], recipz); + RwIm2DVertexSetIntRGBA(&verts[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[0], 0.0f, recipz); + RwIm2DVertexSetV(&verts[0], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[1], x4); + RwIm2DVertexSetScreenY(&verts[1], y4); + RwIm2DVertexSetScreenZ(&verts[1], screenz); + RwIm2DVertexSetCameraZ(&verts[1], z); + RwIm2DVertexSetRecipCameraZ(&verts[1], recipz); + RwIm2DVertexSetIntRGBA(&verts[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&verts[1], 1.0f, recipz); + RwIm2DVertexSetV(&verts[1], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[2], x2); + RwIm2DVertexSetScreenY(&verts[2], y2); + RwIm2DVertexSetScreenZ(&verts[2], screenz); + RwIm2DVertexSetCameraZ(&verts[2], z); + RwIm2DVertexSetRecipCameraZ(&verts[2], recipz); + RwIm2DVertexSetIntRGBA(&verts[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[2], 1.0f, recipz); + RwIm2DVertexSetV(&verts[2], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[3], x1); + RwIm2DVertexSetScreenY(&verts[3], y1); + RwIm2DVertexSetScreenZ(&verts[3], screenz); + RwIm2DVertexSetCameraZ(&verts[3], z); + RwIm2DVertexSetRecipCameraZ(&verts[3], recipz); + RwIm2DVertexSetIntRGBA(&verts[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&verts[3], 0.0f, recipz); + RwIm2DVertexSetV(&verts[3], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[4], x3); + RwIm2DVertexSetScreenY(&verts[4], y3); + RwIm2DVertexSetScreenZ(&verts[4], screenz); + RwIm2DVertexSetCameraZ(&verts[4], z); + RwIm2DVertexSetRecipCameraZ(&verts[4], recipz); + RwIm2DVertexSetIntRGBA(&verts[4], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[4], 0.0f, recipz); + RwIm2DVertexSetV(&verts[4], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&verts[5], x2); + RwIm2DVertexSetScreenY(&verts[5], y2); + RwIm2DVertexSetScreenZ(&verts[5], screenz); + RwIm2DVertexSetCameraZ(&verts[5], z); + RwIm2DVertexSetRecipCameraZ(&verts[5], recipz); + RwIm2DVertexSetIntRGBA(&verts[5], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[5], 1.0f, recipz); + RwIm2DVertexSetV(&verts[5], 1.0f, recipz); +} + +void +CSprite::RenderBufferedOneXLUSprite2D(float x, float y, float w, float h, const RwRGBA &colour, int16 intens, uint8 alpha) +{ + m_bFlushSpriteBufferSwitchZTest = 1; + CRGBA col(intens * colour.red >> 8, intens * colour.green >> 8, intens * colour.blue >> 8, alpha); + CRect rect(x - w, y - h, x + h, y + h); + Set6Vertices2D(&SpriteBufferVerts[6 * nSpriteBufferIndex], rect, col, col, col, col); + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} + +void +CSprite::RenderBufferedOneXLUSprite2D_Rotate_Dimension(float x, float y, float w, float h, const RwRGBA &colour, int16 intens, float rotation, uint8 alpha) +{ + m_bFlushSpriteBufferSwitchZTest = 1; + CRGBA col(intens * colour.red >> 8, intens * colour.green >> 8, intens * colour.blue >> 8, alpha); + float c = Cos(DEGTORAD(rotation)); + float s = Sin(DEGTORAD(rotation)); + + Set6Vertices2D(&SpriteBufferVerts[6 * nSpriteBufferIndex], + x + c*w - s*h, + y - c*h - s*w, + x + c*w + s*h, + y + c*h - s*w, + x - c*w - s*h, + y - c*h + s*w, + x - c*w + s*h, + y + c*h + s*w, + col, col, col, col); + nSpriteBufferIndex++; + if(nSpriteBufferIndex >= SPRITEBUFFERSIZE) + FlushSpriteBuffer(); +} diff --git a/src/renderer/Sprite.h b/src/renderer/Sprite.h new file mode 100644 index 0000000..ec4c1d1 --- /dev/null +++ b/src/renderer/Sprite.h @@ -0,0 +1,28 @@ +#pragma once + +class CSprite +{ + static float m_f2DNearScreenZ; + static float m_f2DFarScreenZ; + static float m_fRecipNearClipPlane; + static int32 m_bFlushSpriteBufferSwitchZTest; +public: + static float CalcHorizonCoors(void); + static bool CalcScreenCoors(const RwV3d &in, RwV3d *out, float *outw, float *outh, bool farclip); + static void InitSpriteBuffer(void); + static void InitSpriteBuffer2D(void); + static void FlushSpriteBuffer(void); + static void RenderOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a); + static void RenderOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a); + static void RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, uint8 a); + static void RenderBufferedOneXLUSprite_Rotate_Dimension(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a); + static void RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intens, float recipz, float roll, uint8 a); + // cx/y is the direction in which the colour changes + static void RenderBufferedOneXLUSprite_Rotate_2Colours(float x, float y, float z, float w, float h, uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2, float cx, float cy, float recipz, float rotation, uint8 a); + static void Set6Vertices2D(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void Set6Vertices2D(RwIm2DVertex *verts, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void RenderBufferedOneXLUSprite2D(float x, float y, float w, float h, const RwRGBA &colour, int16 intens, uint8 alpha); + static void RenderBufferedOneXLUSprite2D_Rotate_Dimension(float x, float y, float w, float h, const RwRGBA &colour, int16 intens, float rotation, uint8 alpha); + +}; diff --git a/src/renderer/Sprite2d.cpp b/src/renderer/Sprite2d.cpp new file mode 100644 index 0000000..5962251 --- /dev/null +++ b/src/renderer/Sprite2d.cpp @@ -0,0 +1,490 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "Camera.h" +#include "Sprite2d.h" +#include "Font.h" + +RwIm2DVertex CSprite2d::maVertices[8]; +float CSprite2d::RecipNearClip; +int32 CSprite2d::mCurrentBank; +RwTexture *CSprite2d::mpBankTextures[10]; +int32 CSprite2d::mCurrentSprite[10]; +int32 CSprite2d::mBankStart[10]; +RwIm2DVertex CSprite2d::maBankVertices[500]; + +void +CSprite2d::SetRecipNearClip(void) +{ + RecipNearClip = 1.0f / RwCameraGetNearClipPlane(Scene.camera); +} + +void +CSprite2d::InitPerFrame(void) +{ + int i; + + mCurrentBank = 0; + for(i = 0; i < 10; i++) + mCurrentSprite[i] = 0; +#ifndef SQUEEZE_PERFORMANCE + for(i = 0; i < 10; i++) + mpBankTextures[i] = nil; +#endif +} + +int32 +CSprite2d::GetBank(int32 n, RwTexture *tex) +{ +#ifndef SQUEEZE_PERFORMANCE + mpBankTextures[mCurrentBank] = tex; +#endif + mCurrentSprite[mCurrentBank] = 0; + mBankStart[mCurrentBank+1] = mBankStart[mCurrentBank] + n; + return mCurrentBank++; +} + +void +CSprite2d::AddSpriteToBank(int32 bank, const CRect &rect, const CRGBA &col, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + SetVertices(&maBankVertices[6 * (mCurrentSprite[bank] + mBankStart[bank])], + rect, col, col, col, col, + u0, v0, u1, v1, u2, v2, u3, v3); + mCurrentSprite[bank]++; + if(mCurrentSprite[bank] + mBankStart[bank] >= mBankStart[bank+1]){ + DrawBank(bank); + mCurrentSprite[bank] = 0; + } +} + +void +CSprite2d::DrawBank(int32 bank) +{ + if(mCurrentSprite[bank] == 0) + return; +#ifndef SQUEEZE_PERFORMANCE + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, + mpBankTextures[bank] ? RwTextureGetRaster(mpBankTextures[bank]) : nil); +#else + CFont::Sprite[bank].SetRenderState(); +#endif + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwIm2DRenderPrimitive(rwPRIMTYPETRILIST, &maBankVertices[6*mBankStart[bank]], 6*mCurrentSprite[bank]); + mCurrentSprite[bank] = 0; + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); +} + + +void +CSprite2d::Delete(void) +{ + if(m_pTexture){ + RwTextureDestroy(m_pTexture); + m_pTexture = nil; + } +} + +void +CSprite2d::SetTexture(const char *name) +{ + Delete(); + if(name) + m_pTexture = RwTextureRead(name, nil); +} + +void +CSprite2d::SetTexture(const char *name, const char *mask) +{ + Delete(); + if(name) + m_pTexture = RwTextureRead(name, mask); +} + +void +CSprite2d::SetAddressing(RwTextureAddressMode addr) +{ + if(m_pTexture) + RwTextureSetAddressing(m_pTexture, addr); +} + +void +CSprite2d::SetRenderState(void) +{ + if(m_pTexture) + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(m_pTexture)); + else + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); +} + +void +CSprite2d::Draw(float x, float y, float w, float h, const CRGBA &col) +{ + SetVertices(CRect(x, y, x + w, y + h), col, col, col, col, 0); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + +void +CSprite2d::Draw(const CRect &rect, const CRGBA &col) +{ + SetVertices(rect, col, col, col, col, 0); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + +void +CSprite2d::Draw(const CRect &rect, const CRGBA &col, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + SetVertices(rect, col, col, col, col, u0, v0, u1, v1, u3, v3, u2, v2); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + +void +CSprite2d::Draw(const CRect &rect, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + SetVertices(rect, c0, c1, c2, c3, 0); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + +void +CSprite2d::Draw(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, const CRGBA &col) +{ + SetVertices(x1, y1, x2, y2, x3, y3, x4, y4, col, col, col, col); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + + +// Arguments: +// 2---3 +// | | +// 0---1 +void +CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, uint32 far) +{ + float screenz, z, recipz; + + if(far){ + screenz = RwIm2DGetFarScreenZ(); + z = RwCameraGetFarClipPlane(Scene.camera); + }else{ + screenz = RwIm2DGetNearScreenZ(); + z = 1.0f/RecipNearClip; + } + recipz = 1.0f/z; + float offset = 1.0f/1024.0f; + + // This is what we draw: + // 0---1 + // | / | + // 3---2 + RwIm2DVertexSetScreenX(&maVertices[0], r.left); + RwIm2DVertexSetScreenY(&maVertices[0], r.top); + RwIm2DVertexSetScreenZ(&maVertices[0], screenz); + RwIm2DVertexSetCameraZ(&maVertices[0], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], 0.0f+offset, recipz); + RwIm2DVertexSetV(&maVertices[0], 0.0f+offset, recipz); + + RwIm2DVertexSetScreenX(&maVertices[1], r.right); + RwIm2DVertexSetScreenY(&maVertices[1], r.top); + RwIm2DVertexSetScreenZ(&maVertices[1], screenz); + RwIm2DVertexSetCameraZ(&maVertices[1], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], 1.0f+offset, recipz); + RwIm2DVertexSetV(&maVertices[1], 0.0f+offset, recipz); + + RwIm2DVertexSetScreenX(&maVertices[2], r.right); + RwIm2DVertexSetScreenY(&maVertices[2], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[2], screenz); + RwIm2DVertexSetCameraZ(&maVertices[2], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], 1.0f+offset, recipz); + RwIm2DVertexSetV(&maVertices[2], 1.0f+offset, recipz); + + RwIm2DVertexSetScreenX(&maVertices[3], r.left); + RwIm2DVertexSetScreenY(&maVertices[3], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[3], screenz); + RwIm2DVertexSetCameraZ(&maVertices[3], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], 0.0f+offset, recipz); + RwIm2DVertexSetV(&maVertices[3], 1.0f+offset, recipz); +} + +void +CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + float screenz, z, recipz; + + screenz = RwIm2DGetNearScreenZ(); + z = 1.0f/RecipNearClip; + recipz = 1.0f/z; + + // This is what we draw: + // 0---1 + // | / | + // 3---2 + RwIm2DVertexSetScreenX(&maVertices[0], r.left); + RwIm2DVertexSetScreenY(&maVertices[0], r.top); + RwIm2DVertexSetScreenZ(&maVertices[0], screenz); + RwIm2DVertexSetCameraZ(&maVertices[0], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], u0, recipz); + RwIm2DVertexSetV(&maVertices[0], v0, recipz); + + RwIm2DVertexSetScreenX(&maVertices[1], r.right); + RwIm2DVertexSetScreenY(&maVertices[1], r.top); + RwIm2DVertexSetScreenZ(&maVertices[1], screenz); + RwIm2DVertexSetCameraZ(&maVertices[1], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], u1, recipz); + RwIm2DVertexSetV(&maVertices[1], v1, recipz); + + RwIm2DVertexSetScreenX(&maVertices[2], r.right); + RwIm2DVertexSetScreenY(&maVertices[2], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[2], screenz); + RwIm2DVertexSetCameraZ(&maVertices[2], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], u2, recipz); + RwIm2DVertexSetV(&maVertices[2], v2, recipz); + + RwIm2DVertexSetScreenX(&maVertices[3], r.left); + RwIm2DVertexSetScreenY(&maVertices[3], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[3], screenz); + RwIm2DVertexSetCameraZ(&maVertices[3], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], u3, recipz); + RwIm2DVertexSetV(&maVertices[3], v3, recipz); +} + +void +CSprite2d::SetVertices(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + float screenz, recipz; + float z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + screenz = RwIm2DGetNearScreenZ(); + recipz = RecipNearClip; + + RwIm2DVertexSetScreenX(&maVertices[0], x3); + RwIm2DVertexSetScreenY(&maVertices[0], y3); + RwIm2DVertexSetScreenZ(&maVertices[0], screenz); + RwIm2DVertexSetCameraZ(&maVertices[0], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], 0.0f, recipz); + RwIm2DVertexSetV(&maVertices[0], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&maVertices[1], x4); + RwIm2DVertexSetScreenY(&maVertices[1], y4); + RwIm2DVertexSetScreenZ(&maVertices[1], screenz); + RwIm2DVertexSetCameraZ(&maVertices[1], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], 1.0f, recipz); + RwIm2DVertexSetV(&maVertices[1], 0.0f, recipz); + + RwIm2DVertexSetScreenX(&maVertices[2], x2); + RwIm2DVertexSetScreenY(&maVertices[2], y2); + RwIm2DVertexSetScreenZ(&maVertices[2], screenz); + RwIm2DVertexSetCameraZ(&maVertices[2], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], 1.0f, recipz); + RwIm2DVertexSetV(&maVertices[2], 1.0f, recipz); + + RwIm2DVertexSetScreenX(&maVertices[3], x1); + RwIm2DVertexSetScreenY(&maVertices[3], y1); + RwIm2DVertexSetScreenZ(&maVertices[3], screenz); + RwIm2DVertexSetCameraZ(&maVertices[3], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], 0.0f, recipz); + RwIm2DVertexSetV(&maVertices[3], 1.0f, recipz); +} + +void +CSprite2d::SetVertices(int n, float *positions, float *uvs, const CRGBA &col) +{ + int i; + float screenz, recipz, z; + + screenz = RwIm2DGetNearScreenZ(); + recipz = RecipNearClip; + z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + + for(i = 0; i < n; i++){ + RwIm2DVertexSetScreenX(&maVertices[i], positions[i*2 + 0]); + RwIm2DVertexSetScreenY(&maVertices[i], positions[i*2 + 1]); + RwIm2DVertexSetScreenZ(&maVertices[i], screenz + 0.0001f); + RwIm2DVertexSetCameraZ(&maVertices[i], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[i], recipz); + RwIm2DVertexSetIntRGBA(&maVertices[i], col.r, col.g, col.b, col.a); + RwIm2DVertexSetU(&maVertices[i], uvs[i*2 + 0], recipz); + RwIm2DVertexSetV(&maVertices[i], uvs[i*2 + 1], recipz); + } +} + +void +CSprite2d::SetMaskVertices(int n, float *positions) +{ + int i; + float screenz, recipz, z; + + screenz = RwIm2DGetNearScreenZ(); + recipz = RecipNearClip; + z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + for(i = 0; i < n; i++){ + RwIm2DVertexSetScreenX(&maVertices[i], positions[i*2 + 0]); + RwIm2DVertexSetScreenY(&maVertices[i], positions[i*2 + 1]); + RwIm2DVertexSetScreenZ(&maVertices[i], screenz); + RwIm2DVertexSetCameraZ(&maVertices[i], z); + RwIm2DVertexSetRecipCameraZ(&maVertices[i], recipz); +#if !defined(GTA_PS2_STUFF) && defined(RWLIBS) + RwIm2DVertexSetIntRGBA(&maVertices[i], 0, 0, 0, 0); +#else + RwIm2DVertexSetIntRGBA(&maVertices[i], 255, 255, 255, 255); +#endif + } +} + +void +CSprite2d::SetVertices(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + float screenz, recipz, z; + + screenz = RwIm2DGetNearScreenZ(); + recipz = RecipNearClip; + z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + + RwIm2DVertexSetScreenX(&verts[0], r.left); + RwIm2DVertexSetScreenY(&verts[0], r.top); + RwIm2DVertexSetScreenZ(&verts[0], screenz); + RwIm2DVertexSetCameraZ(&verts[0], z); + RwIm2DVertexSetRecipCameraZ(&verts[0], recipz); + RwIm2DVertexSetIntRGBA(&verts[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[0], u0, recipz); + RwIm2DVertexSetV(&verts[0], v0, recipz); + + RwIm2DVertexSetScreenX(&verts[1], r.left); + RwIm2DVertexSetScreenY(&verts[1], r.bottom); + RwIm2DVertexSetScreenZ(&verts[1], screenz); + RwIm2DVertexSetCameraZ(&verts[1], z); + RwIm2DVertexSetRecipCameraZ(&verts[1], recipz); + RwIm2DVertexSetIntRGBA(&verts[1], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&verts[1], u2, recipz); + RwIm2DVertexSetV(&verts[1], v2, recipz); + + RwIm2DVertexSetScreenX(&verts[2], r.right); + RwIm2DVertexSetScreenY(&verts[2], r.bottom); + RwIm2DVertexSetScreenZ(&verts[2], screenz); + RwIm2DVertexSetCameraZ(&verts[2], z); + RwIm2DVertexSetRecipCameraZ(&verts[2], recipz); + RwIm2DVertexSetIntRGBA(&verts[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[2], u3, recipz); + RwIm2DVertexSetV(&verts[2], v3, recipz); + + RwIm2DVertexSetScreenX(&verts[3], r.left); + RwIm2DVertexSetScreenY(&verts[3], r.top); + RwIm2DVertexSetScreenZ(&verts[3], screenz); + RwIm2DVertexSetCameraZ(&verts[3], z); + RwIm2DVertexSetRecipCameraZ(&verts[3], recipz); + RwIm2DVertexSetIntRGBA(&verts[3], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[3], u0, recipz); + RwIm2DVertexSetV(&verts[3], v0, recipz); + + RwIm2DVertexSetScreenX(&verts[4], r.right); + RwIm2DVertexSetScreenY(&verts[4], r.bottom); + RwIm2DVertexSetScreenZ(&verts[4], screenz); + RwIm2DVertexSetCameraZ(&verts[4], z); + RwIm2DVertexSetRecipCameraZ(&verts[4], recipz); + RwIm2DVertexSetIntRGBA(&verts[4], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[4], u3, recipz); + RwIm2DVertexSetV(&verts[4], v3, recipz); + + RwIm2DVertexSetScreenX(&verts[5], r.right); + RwIm2DVertexSetScreenY(&verts[5], r.top); + RwIm2DVertexSetScreenZ(&verts[5], screenz); + RwIm2DVertexSetCameraZ(&verts[5], z); + RwIm2DVertexSetRecipCameraZ(&verts[5], recipz); + RwIm2DVertexSetIntRGBA(&verts[5], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&verts[5], u1, recipz); + RwIm2DVertexSetV(&verts[5], v1, recipz); + +} + +void +CSprite2d::DrawRect(const CRect &r, const CRGBA &col) +{ + SetVertices(r, col, col, col, col, false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(col.a != 255)); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD); +} + +void +CSprite2d::DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + SetVertices(r, c0, c1, c2, c3, false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); +} + +void +CSprite2d::DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3) +{ + SetVertices(r, c0, c1, c2, c3, false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); +} + +void CSprite2d::Draw2DPolygon(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, const CRGBA &color) +{ + SetVertices(x1, y1, x2, y2, x3, y3, x4, y4, color, color, color, color); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, 0); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEFLAT); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(color.a != 255)); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD); +} diff --git a/src/renderer/Sprite2d.h b/src/renderer/Sprite2d.h new file mode 100644 index 0000000..0e12d44 --- /dev/null +++ b/src/renderer/Sprite2d.h @@ -0,0 +1,53 @@ +#pragma once + +class CSprite2d +{ + static float RecipNearClip; + static int32 mCurrentBank; + static RwTexture *mpBankTextures[10]; + static int32 mCurrentSprite[10]; + static int32 mBankStart[10]; + static RwIm2DVertex maBankVertices[500]; + static RwIm2DVertex maVertices[8]; +public: + RwTexture *m_pTexture; + + static void SetRecipNearClip(void); + static void InitPerFrame(void); + static int32 GetBank(int32 n, RwTexture *tex); + static void AddSpriteToBank(int32 bank, const CRect &rect, const CRGBA &col, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); + static void DrawBank(int32 bank); + + CSprite2d(void) : m_pTexture(nil) {}; + ~CSprite2d(void) { Delete(); }; + void Delete(void); + void SetRenderState(void); + void SetTexture(const char *name); + void SetTexture(const char *name, const char *mask); + void SetAddressing(RwTextureAddressMode addr); + void Draw(float x, float y, float w, float h, const CRGBA &col); + void Draw(const CRect &rect, const CRGBA &col); + void Draw(const CRect &rect, const CRGBA &col, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); + void Draw(const CRect &rect, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + void Draw(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, const CRGBA &col); + + static void SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, uint32 far); + static void SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); + static void SetVertices(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void SetVertices(int n, float *positions, float *uvs, const CRGBA &col); + static void SetMaskVertices(int n, float *positions); + static void SetVertices(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3, + float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2); + + static void DrawRect(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + static void DrawRect(const CRect &r, const CRGBA &col); + static void DrawRectXLU(const CRect &r, const CRGBA &c0, const CRGBA &c1, const CRGBA &c2, const CRGBA &c3); + + static void Draw2DPolygon(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, const CRGBA &color); + + static RwIm2DVertex* GetVertices() { return maVertices; }; +}; diff --git a/src/renderer/TexList.cpp b/src/renderer/TexList.cpp new file mode 100644 index 0000000..1689837 --- /dev/null +++ b/src/renderer/TexList.cpp @@ -0,0 +1,41 @@ +#include "common.h" +#include "TexList.h" +#include "rtbmp.h" +#include "FileMgr.h" + +bool CTexList::ms_nTexUsed[MAX_TEXUSED]; + +void +CTexList::Initialise() +{} + +void +CTexList::Shutdown() +{} + +RwTexture * +CTexList::SetTexture(int32 slot, char *name) +{ + return nil; +} + +int32 +CTexList::GetFirstFreeTexture() +{ + for (int32 i = 0; i < MAX_TEXUSED; i++) + if (!ms_nTexUsed[i]) + return i; + return -1; +} + +RwTexture * +CTexList::LoadFileNameTexture(char *name) +{ + return SetTexture(GetFirstFreeTexture(), name); +} + +void +CTexList::LoadGlobalTextureList() +{ + CFileMgr::SetDir("TEXTURES"); +} \ No newline at end of file diff --git a/src/renderer/TexList.h b/src/renderer/TexList.h new file mode 100644 index 0000000..7e04221 --- /dev/null +++ b/src/renderer/TexList.h @@ -0,0 +1,14 @@ +#pragma once + +class CTexList +{ + enum { MAX_TEXUSED = 400, }; + static bool ms_nTexUsed[MAX_TEXUSED]; +public: + static void Initialise(); + static void Shutdown(); + static RwTexture *SetTexture(int32 slot, char *name); + static int32 GetFirstFreeTexture(); + static RwTexture *LoadFileNameTexture(char *name); + static void LoadGlobalTextureList(); +}; \ No newline at end of file diff --git a/src/renderer/Timecycle.cpp b/src/renderer/Timecycle.cpp new file mode 100644 index 0000000..0d94dbd --- /dev/null +++ b/src/renderer/Timecycle.cpp @@ -0,0 +1,317 @@ +#include "common.h" + +#include "main.h" +#include "Clock.h" +#include "Weather.h" +#include "Camera.h" +#include "Shadows.h" +#include "ZoneCull.h" +#include "CutsceneMgr.h" +#include "FileMgr.h" +#include "Timecycle.h" + +int32 CTimeCycle::m_nAmbientRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nAmbientGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nAmbientBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nDirectionalRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nDirectionalGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nDirectionalBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyTopRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyTopGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyTopBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyBottomRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyBottomGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSkyBottomBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoreRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoreGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoreBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoronaRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoronaGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nSunCoronaBlue[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fSunSize[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fSpriteSize[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fSpriteBrightness[NUMHOURS][NUMWEATHERS]; +int16 CTimeCycle::m_nShadowStrength[NUMHOURS][NUMWEATHERS]; +int16 CTimeCycle::m_nLightShadowStrength[NUMHOURS][NUMWEATHERS]; +int16 CTimeCycle::m_nTreeShadowStrength[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fFogStart[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fFarClip[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fLightsOnGroundBrightness[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nLowCloudsRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nLowCloudsGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nLowCloudsBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsTopRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsTopGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsTopBlue[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsBottomRed[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsBottomGreen[NUMHOURS][NUMWEATHERS]; +int32 CTimeCycle::m_nFluffyCloudsBottomBlue[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fBlurRed[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fBlurGreen[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fBlurBlue[NUMHOURS][NUMWEATHERS]; +float CTimeCycle::m_fBlurAlpha[NUMHOURS][NUMWEATHERS]; + +float CTimeCycle::m_fCurrentAmbientRed; +float CTimeCycle::m_fCurrentAmbientGreen; +float CTimeCycle::m_fCurrentAmbientBlue; +float CTimeCycle::m_fCurrentDirectionalRed; +float CTimeCycle::m_fCurrentDirectionalGreen; +float CTimeCycle::m_fCurrentDirectionalBlue; +int32 CTimeCycle::m_nCurrentSkyTopRed; +int32 CTimeCycle::m_nCurrentSkyTopGreen; +int32 CTimeCycle::m_nCurrentSkyTopBlue; +int32 CTimeCycle::m_nCurrentSkyBottomRed; +int32 CTimeCycle::m_nCurrentSkyBottomGreen; +int32 CTimeCycle::m_nCurrentSkyBottomBlue; +int32 CTimeCycle::m_nCurrentSunCoreRed; +int32 CTimeCycle::m_nCurrentSunCoreGreen; +int32 CTimeCycle::m_nCurrentSunCoreBlue; +int32 CTimeCycle::m_nCurrentSunCoronaRed; +int32 CTimeCycle::m_nCurrentSunCoronaGreen; +int32 CTimeCycle::m_nCurrentSunCoronaBlue; +float CTimeCycle::m_fCurrentSunSize; +float CTimeCycle::m_fCurrentSpriteSize; +float CTimeCycle::m_fCurrentSpriteBrightness; +int32 CTimeCycle::m_nCurrentShadowStrength; +int32 CTimeCycle::m_nCurrentLightShadowStrength; +int32 CTimeCycle::m_nCurrentTreeShadowStrength; +float CTimeCycle::m_fCurrentFogStart; +float CTimeCycle::m_fCurrentFarClip; +float CTimeCycle::m_fCurrentLightsOnGroundBrightness; +int32 CTimeCycle::m_nCurrentLowCloudsRed; +int32 CTimeCycle::m_nCurrentLowCloudsGreen; +int32 CTimeCycle::m_nCurrentLowCloudsBlue; +int32 CTimeCycle::m_nCurrentFluffyCloudsTopRed; +int32 CTimeCycle::m_nCurrentFluffyCloudsTopGreen; +int32 CTimeCycle::m_nCurrentFluffyCloudsTopBlue; +int32 CTimeCycle::m_nCurrentFluffyCloudsBottomRed; +int32 CTimeCycle::m_nCurrentFluffyCloudsBottomGreen; +int32 CTimeCycle::m_nCurrentFluffyCloudsBottomBlue; +float CTimeCycle::m_fCurrentBlurRed; +float CTimeCycle::m_fCurrentBlurGreen; +float CTimeCycle::m_fCurrentBlurBlue; +float CTimeCycle::m_fCurrentBlurAlpha; +int32 CTimeCycle::m_nCurrentFogColourRed; +int32 CTimeCycle::m_nCurrentFogColourGreen; +int32 CTimeCycle::m_nCurrentFogColourBlue; + +int32 CTimeCycle::m_FogReduction; + +int32 CTimeCycle::m_CurrentStoredValue; +CVector CTimeCycle::m_VectorToSun[16]; +float CTimeCycle::m_fShadowFrontX[16]; +float CTimeCycle::m_fShadowFrontY[16]; +float CTimeCycle::m_fShadowSideX[16]; +float CTimeCycle::m_fShadowSideY[16]; +float CTimeCycle::m_fShadowDisplacementX[16]; +float CTimeCycle::m_fShadowDisplacementY[16]; + + +void +CTimeCycle::Initialise(void) +{ + int w, h; + int li, bi; + char line[1040]; + + int ambR, ambG, ambB; + int dirR, dirG, dirB; + int skyTopR, skyTopG, skyTopB; + int skyBotR, skyBotG, skyBotB; + int sunCoreR, sunCoreG, sunCoreB; + int sunCoronaR, sunCoronaG, sunCoronaB; + float sunSz, sprSz, sprBght; + int shad, lightShad, treeShad; + float farClp, fogSt, lightGnd; + int cloudR, cloudG, cloudB; + int fluffyTopR, fluffyTopG, fluffyTopB; + int fluffyBotR, fluffyBotG, fluffyBotB; + float blurR, blurG, blurB, blurA; + + debug("Intialising CTimeCycle...\n"); + + CFileMgr::SetDir("DATA"); + CFileMgr::LoadFile("TIMECYC.DAT", work_buff, sizeof(work_buff), "rb"); + CFileMgr::SetDir(""); + + line[0] = '\0'; + bi = 0; + for(w = 0; w < NUMWEATHERS; w++) + for(h = 0; h < NUMHOURS; h++){ + li = 0; + while(work_buff[bi] == '/'){ + while(work_buff[bi] != '\n') + bi++; + bi++; + } + while(work_buff[bi] != '\n') + line[li++] = work_buff[bi++]; + line[li] = '\0'; + bi++; + + sscanf(line, "%d %d %d %d %d %d %d %d %d %d %d %d " + "%d %d %d %d %d %d %f %f %f %d %d %d %f %f %f " + "%d %d %d %d %d %d %d %d %d %f %f %f %f", + &ambR, &ambG, &ambB, + &dirR, &dirG, &dirB, + &skyTopR, &skyTopG, &skyTopB, + &skyBotR, &skyBotG, &skyBotB, + &sunCoreR, &sunCoreG, &sunCoreB, + &sunCoronaR, &sunCoronaG, &sunCoronaB, + &sunSz, &sprSz, &sprBght, + &shad, &lightShad, &treeShad, + &farClp, &fogSt, &lightGnd, + &cloudR, &cloudG, &cloudB, + &fluffyTopR, &fluffyTopG, &fluffyTopB, + &fluffyBotR, &fluffyBotG, &fluffyBotB, + &blurR, &blurG, &blurB, &blurA); + + m_nAmbientRed[h][w] = ambR; + m_nAmbientGreen[h][w] = ambG; + m_nAmbientBlue[h][w] = ambB; + m_nDirectionalRed[h][w] = dirR; + m_nDirectionalGreen[h][w] = dirG; + m_nDirectionalBlue[h][w] = dirB; + m_nSkyTopRed[h][w] = skyTopR; + m_nSkyTopGreen[h][w] = skyTopG; + m_nSkyTopBlue[h][w] = skyTopB; + m_nSkyBottomRed[h][w] = skyBotR; + m_nSkyBottomGreen[h][w] = skyBotG; + m_nSkyBottomBlue[h][w] = skyBotB; + m_nSunCoreRed[h][w] = sunCoreR; + m_nSunCoreGreen[h][w] = sunCoreG; + m_nSunCoreBlue[h][w] = sunCoreB; + m_nSunCoronaRed[h][w] = sunCoronaR; + m_nSunCoronaGreen[h][w] = sunCoronaG; + m_nSunCoronaBlue[h][w] = sunCoronaB; + m_fSunSize[h][w] = sunSz; + m_fSpriteSize[h][w] = sprSz; + m_fSpriteBrightness[h][w] = sprBght; + m_nShadowStrength[h][w] = shad; + m_nLightShadowStrength[h][w] = lightShad; + m_nTreeShadowStrength[h][w] = treeShad; + m_fFarClip[h][w] = farClp; + m_fFogStart[h][w] = fogSt; + m_fLightsOnGroundBrightness[h][w] = lightGnd; + m_nLowCloudsRed[h][w] = cloudR; + m_nLowCloudsGreen[h][w] = cloudG; + m_nLowCloudsBlue[h][w] = cloudB; + m_nFluffyCloudsTopRed[h][w] = fluffyTopR; + m_nFluffyCloudsTopGreen[h][w] = fluffyTopG; + m_nFluffyCloudsTopBlue[h][w] = fluffyTopB; + m_nFluffyCloudsBottomRed[h][w] = fluffyBotR; + m_nFluffyCloudsBottomGreen[h][w] = fluffyBotG; + m_nFluffyCloudsBottomBlue[h][w] = fluffyBotB; + m_fBlurRed[h][w] = blurR; + m_fBlurGreen[h][w] = blurG; + m_fBlurBlue[h][w] = blurB; + m_fBlurAlpha[h][w] = blurA; + } + + m_FogReduction = 0; + + debug("CTimeCycle ready\n"); +} + +void +CTimeCycle::Update(void) +{ + int h1 = CClock::GetHours(); + int h2 = (h1+1)%24; + int w1 = CWeather::OldWeatherType; + int w2 = CWeather::NewWeatherType; + float timeInterp = CClock::GetMinutes()/60.0f; + // coefficients for a bilinear interpolation + float c0 = (1.0f-timeInterp) * (1.0f-CWeather::InterpolationValue); + float c1 = timeInterp * (1.0f-CWeather::InterpolationValue); + float c2 = (1.0f-timeInterp) * CWeather::InterpolationValue; + float c3 = timeInterp * CWeather::InterpolationValue; + +#define INTERP(v) v[h1][w1]*c0 + v[h2][w1]*c1 + v[h1][w2]*c2 + v[h2][w2]*c3 + + m_nCurrentSkyTopRed = INTERP(m_nSkyTopRed); + m_nCurrentSkyTopGreen = INTERP(m_nSkyTopGreen); + m_nCurrentSkyTopBlue = INTERP(m_nSkyTopBlue); + + m_nCurrentSkyBottomRed = INTERP(m_nSkyBottomRed); + m_nCurrentSkyBottomGreen = INTERP(m_nSkyBottomGreen); + m_nCurrentSkyBottomBlue = INTERP(m_nSkyBottomBlue); + + m_fCurrentAmbientRed = INTERP(m_nAmbientRed); + m_fCurrentAmbientGreen = INTERP(m_nAmbientGreen); + m_fCurrentAmbientBlue = INTERP(m_nAmbientBlue); + m_fCurrentAmbientRed /= 255.0f; + m_fCurrentAmbientGreen /= 255.0f; + m_fCurrentAmbientBlue /= 255.0f; + + m_fCurrentDirectionalRed = INTERP(m_nDirectionalRed); + m_fCurrentDirectionalGreen = INTERP(m_nDirectionalGreen); + m_fCurrentDirectionalBlue = INTERP(m_nDirectionalBlue); + m_fCurrentDirectionalRed /= 255.0f; + m_fCurrentDirectionalGreen /= 255.0f; + m_fCurrentDirectionalBlue /= 255.0f; + + m_nCurrentSunCoreRed = INTERP(m_nSunCoreRed); + m_nCurrentSunCoreGreen = INTERP(m_nSunCoreGreen); + m_nCurrentSunCoreBlue = INTERP(m_nSunCoreBlue); + + m_nCurrentSunCoronaRed = INTERP(m_nSunCoronaRed); + m_nCurrentSunCoronaGreen = INTERP(m_nSunCoronaGreen); + m_nCurrentSunCoronaBlue = INTERP(m_nSunCoronaBlue); + + m_fCurrentSunSize = INTERP(m_fSunSize); + m_fCurrentSpriteSize = INTERP(m_fSpriteSize); + m_fCurrentSpriteBrightness = INTERP(m_fSpriteBrightness); + m_nCurrentShadowStrength = INTERP(m_nShadowStrength); + m_nCurrentLightShadowStrength = INTERP(m_nLightShadowStrength); + m_nCurrentTreeShadowStrength = INTERP(m_nTreeShadowStrength); + m_fCurrentFarClip = INTERP(m_fFarClip); + m_fCurrentFogStart = INTERP(m_fFogStart); + m_fCurrentLightsOnGroundBrightness = INTERP(m_fLightsOnGroundBrightness); + + m_nCurrentLowCloudsRed = INTERP(m_nLowCloudsRed); + m_nCurrentLowCloudsGreen = INTERP(m_nLowCloudsGreen); + m_nCurrentLowCloudsBlue = INTERP(m_nLowCloudsBlue); + + m_nCurrentFluffyCloudsTopRed = INTERP(m_nFluffyCloudsTopRed); + m_nCurrentFluffyCloudsTopGreen = INTERP(m_nFluffyCloudsTopGreen); + m_nCurrentFluffyCloudsTopBlue = INTERP(m_nFluffyCloudsTopBlue); + + m_nCurrentFluffyCloudsBottomRed = INTERP(m_nFluffyCloudsBottomRed); + m_nCurrentFluffyCloudsBottomGreen = INTERP(m_nFluffyCloudsBottomGreen); + m_nCurrentFluffyCloudsBottomBlue = INTERP(m_nFluffyCloudsBottomBlue); + + m_fCurrentBlurRed = INTERP(m_fBlurRed); + m_fCurrentBlurGreen = INTERP(m_fBlurGreen); + m_fCurrentBlurBlue = INTERP(m_fBlurBlue); + m_fCurrentBlurAlpha = INTERP(m_fBlurAlpha); + + if(TheCamera.m_BlurType == MOTION_BLUR_NONE || TheCamera.m_BlurType == MOTION_BLUR_LIGHT_SCENE) + TheCamera.SetMotionBlur(m_fCurrentBlurRed, m_fCurrentBlurGreen, m_fCurrentBlurBlue, m_fCurrentBlurAlpha, MOTION_BLUR_LIGHT_SCENE); + + if(m_FogReduction != 0) + m_fCurrentFarClip = Max(m_fCurrentFarClip, m_FogReduction/64.0f * 650.0f); + m_nCurrentFogColourRed = (m_nCurrentSkyTopRed + 2*m_nCurrentSkyBottomRed) / 3; + m_nCurrentFogColourGreen = (m_nCurrentSkyTopGreen + 2*m_nCurrentSkyBottomGreen) / 3; + m_nCurrentFogColourBlue = (m_nCurrentSkyTopBlue + 2*m_nCurrentSkyBottomBlue) / 3; + + m_CurrentStoredValue = (m_CurrentStoredValue+1)&0xF; + + float sunAngle = 2*PI*(CClock::GetMinutes() + CClock::GetHours()*60)/(24*60); + CVector &sunPos = GetSunDirection(); + sunPos.x = Sin(sunAngle); + sunPos.y = 1.0f; + sunPos.z = 0.2f - Cos(sunAngle); + sunPos.Normalise(); + + CShadows::CalcPedShadowValues(sunPos, + &m_fShadowFrontX[m_CurrentStoredValue], &m_fShadowFrontY[m_CurrentStoredValue], + &m_fShadowSideX[m_CurrentStoredValue], &m_fShadowSideY[m_CurrentStoredValue], + &m_fShadowDisplacementX[m_CurrentStoredValue], &m_fShadowDisplacementY[m_CurrentStoredValue]); + + if(TheCamera.GetForward().z < -0.9f || + !CWeather::bScriptsForceRain && (CCullZones::PlayerNoRain() || CCullZones::CamNoRain() || CCutsceneMgr::IsRunning())) + m_FogReduction = Min(m_FogReduction+1, 64); + else + m_FogReduction = Max(m_FogReduction-1, 0); +} diff --git a/src/renderer/Timecycle.h b/src/renderer/Timecycle.h new file mode 100644 index 0000000..d5d7b67 --- /dev/null +++ b/src/renderer/Timecycle.h @@ -0,0 +1,152 @@ +#pragma once + +class CTimeCycle +{ + static int32 m_nAmbientRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nAmbientGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nAmbientBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nDirectionalRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nDirectionalGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nDirectionalBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyTopRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyTopGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyTopBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyBottomRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyBottomGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nSkyBottomBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoreRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoreGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoreBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoronaRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoronaGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nSunCoronaBlue[NUMHOURS][NUMWEATHERS]; + static float m_fSunSize[NUMHOURS][NUMWEATHERS]; + static float m_fSpriteSize[NUMHOURS][NUMWEATHERS]; + static float m_fSpriteBrightness[NUMHOURS][NUMWEATHERS]; + static int16 m_nShadowStrength[NUMHOURS][NUMWEATHERS]; + static int16 m_nLightShadowStrength[NUMHOURS][NUMWEATHERS]; + static int16 m_nTreeShadowStrength[NUMHOURS][NUMWEATHERS]; + static float m_fFogStart[NUMHOURS][NUMWEATHERS]; + static float m_fFarClip[NUMHOURS][NUMWEATHERS]; + static float m_fLightsOnGroundBrightness[NUMHOURS][NUMWEATHERS]; + static int32 m_nLowCloudsRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nLowCloudsGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nLowCloudsBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsTopRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsTopGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsTopBlue[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsBottomRed[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsBottomGreen[NUMHOURS][NUMWEATHERS]; + static int32 m_nFluffyCloudsBottomBlue[NUMHOURS][NUMWEATHERS]; + static float m_fBlurRed[NUMHOURS][NUMWEATHERS]; + static float m_fBlurGreen[NUMHOURS][NUMWEATHERS]; + static float m_fBlurBlue[NUMHOURS][NUMWEATHERS]; + static float m_fBlurAlpha[NUMHOURS][NUMWEATHERS]; + + static float m_fCurrentAmbientRed; + static float m_fCurrentAmbientGreen; + static float m_fCurrentAmbientBlue; + static float m_fCurrentDirectionalRed; + static float m_fCurrentDirectionalGreen; + static float m_fCurrentDirectionalBlue; + static int32 m_nCurrentSkyTopRed; + static int32 m_nCurrentSkyTopGreen; + static int32 m_nCurrentSkyTopBlue; + static int32 m_nCurrentSkyBottomRed; + static int32 m_nCurrentSkyBottomGreen; + static int32 m_nCurrentSkyBottomBlue; + static int32 m_nCurrentSunCoreRed; + static int32 m_nCurrentSunCoreGreen; + static int32 m_nCurrentSunCoreBlue; + static int32 m_nCurrentSunCoronaRed; + static int32 m_nCurrentSunCoronaGreen; + static int32 m_nCurrentSunCoronaBlue; + static float m_fCurrentSunSize; + static float m_fCurrentSpriteSize; + static float m_fCurrentSpriteBrightness; + static int32 m_nCurrentShadowStrength; + static int32 m_nCurrentLightShadowStrength; + static int32 m_nCurrentTreeShadowStrength; + static float m_fCurrentFogStart; + static float m_fCurrentFarClip; + static float m_fCurrentLightsOnGroundBrightness; + static int32 m_nCurrentLowCloudsRed; + static int32 m_nCurrentLowCloudsGreen; + static int32 m_nCurrentLowCloudsBlue; + static int32 m_nCurrentFluffyCloudsTopRed; + static int32 m_nCurrentFluffyCloudsTopGreen; + static int32 m_nCurrentFluffyCloudsTopBlue; + static int32 m_nCurrentFluffyCloudsBottomRed; + static int32 m_nCurrentFluffyCloudsBottomGreen; + static int32 m_nCurrentFluffyCloudsBottomBlue; + static float m_fCurrentBlurRed; + static float m_fCurrentBlurGreen; + static float m_fCurrentBlurBlue; + static float m_fCurrentBlurAlpha; + static int32 m_nCurrentFogColourRed; + static int32 m_nCurrentFogColourGreen; + static int32 m_nCurrentFogColourBlue; + + static int32 m_FogReduction; + +public: + static int32 m_CurrentStoredValue; + static CVector m_VectorToSun[16]; + static float m_fShadowFrontX[16]; + static float m_fShadowFrontY[16]; + static float m_fShadowSideX[16]; + static float m_fShadowSideY[16]; + static float m_fShadowDisplacementX[16]; + static float m_fShadowDisplacementY[16]; + + static float GetAmbientRed(void) { return m_fCurrentAmbientRed; } + static float GetAmbientGreen(void) { return m_fCurrentAmbientGreen; } + static float GetAmbientBlue(void) { return m_fCurrentAmbientBlue; } + static float GetDirectionalRed(void) { return m_fCurrentDirectionalRed; } + static float GetDirectionalGreen(void) { return m_fCurrentDirectionalGreen; } + static float GetDirectionalBlue(void) { return m_fCurrentDirectionalBlue; } + static int32 GetSkyTopRed(void) { return m_nCurrentSkyTopRed; } + static int32 GetSkyTopGreen(void) { return m_nCurrentSkyTopGreen; } + static int32 GetSkyTopBlue(void) { return m_nCurrentSkyTopBlue; } + static int32 GetSkyBottomRed(void) { return m_nCurrentSkyBottomRed; } + static int32 GetSkyBottomGreen(void) { return m_nCurrentSkyBottomGreen; } + static int32 GetSkyBottomBlue(void) { return m_nCurrentSkyBottomBlue; } + static int32 GetSunCoreRed(void) { return m_nCurrentSunCoreRed; } + static int32 GetSunCoreGreen(void) { return m_nCurrentSunCoreGreen; } + static int32 GetSunCoreBlue(void) { return m_nCurrentSunCoreBlue; } + static int32 GetSunCoronaRed(void) { return m_nCurrentSunCoronaRed; } + static int32 GetSunCoronaGreen(void) { return m_nCurrentSunCoronaGreen; } + static int32 GetSunCoronaBlue(void) { return m_nCurrentSunCoronaBlue; } + static float GetSunSize(void) { return m_fCurrentSunSize; } + static float GetSpriteBrightness(void) { return m_fCurrentSpriteBrightness; } + static float GetSpriteSize(void) { return m_fCurrentSpriteSize; } + static int32 GetShadowStrength(void) { return m_nCurrentShadowStrength; } + static int32 GetLightShadowStrength(void) { return m_nCurrentLightShadowStrength; } + static float GetLightOnGroundBrightness(void) { return m_fCurrentLightsOnGroundBrightness; } + static float GetFarClip(void) { return m_fCurrentFarClip; } + static float GetFogStart(void) { return m_fCurrentFogStart; } + + static int32 GetLowCloudsRed(void) { return m_nCurrentLowCloudsRed; } + static int32 GetLowCloudsGreen(void) { return m_nCurrentLowCloudsGreen; } + static int32 GetLowCloudsBlue(void) { return m_nCurrentLowCloudsBlue; } + static int32 GetFluffyCloudsTopRed(void) { return m_nCurrentFluffyCloudsTopRed; } + static int32 GetFluffyCloudsTopGreen(void) { return m_nCurrentFluffyCloudsTopGreen; } + static int32 GetFluffyCloudsTopBlue(void) { return m_nCurrentFluffyCloudsTopBlue; } + static int32 GetFluffyCloudsBottomRed(void) { return m_nCurrentFluffyCloudsBottomRed; } + static int32 GetFluffyCloudsBottomGreen(void) { return m_nCurrentFluffyCloudsBottomGreen; } + static int32 GetFluffyCloudsBottomBlue(void) { return m_nCurrentFluffyCloudsBottomBlue; } + static int32 GetFogRed(void) { return m_nCurrentFogColourRed; } + static int32 GetFogGreen(void) { return m_nCurrentFogColourGreen; } + static int32 GetFogBlue(void) { return m_nCurrentFogColourBlue; } + static int32 GetFogReduction(void) { return m_FogReduction; } + + static void Initialise(void); + static void Update(void); + static CVector &GetSunDirection(void) { return m_VectorToSun[m_CurrentStoredValue]; } + static float GetShadowFrontX(void) { return m_fShadowFrontX[m_CurrentStoredValue]; } + static float GetShadowFrontY(void) { return m_fShadowFrontY[m_CurrentStoredValue]; } + static float GetShadowSideX(void) { return m_fShadowSideX[m_CurrentStoredValue]; } + static float GetShadowSideY(void) { return m_fShadowSideY[m_CurrentStoredValue]; } + static float GetShadowDisplacementX(void) { return m_fShadowDisplacementX[m_CurrentStoredValue]; } + static float GetShadowDisplacementY(void) { return m_fShadowDisplacementY[m_CurrentStoredValue]; } +}; diff --git a/src/renderer/WaterCannon.cpp b/src/renderer/WaterCannon.cpp new file mode 100644 index 0000000..08898be --- /dev/null +++ b/src/renderer/WaterCannon.cpp @@ -0,0 +1,307 @@ +#include "common.h" + +#include "WaterCannon.h" +#include "Vector.h" +#include "General.h" +#include "main.h" +#include "Timer.h" +#include "Pools.h" +#include "Ped.h" +#include "AnimManager.h" +#include "Fire.h" +#include "WaterLevel.h" +#include "Camera.h" + +#define WATERCANNONVERTS 4 +#define WATERCANNONINDEXES 12 + +RwIm3DVertex WaterCannonVertices[WATERCANNONVERTS]; +RwImVertexIndex WaterCannonIndexList[WATERCANNONINDEXES]; + +CWaterCannon CWaterCannons::aCannons[NUM_WATERCANNONS]; + +void CWaterCannon::Init(void) +{ + m_nId = 0; + m_nCur = 0; + m_nTimeCreated = CTimer::GetTimeInMilliseconds(); + + for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ ) + m_abUsed[i] = false; + + RwIm3DVertexSetU(&WaterCannonVertices[0], 0.0f); + RwIm3DVertexSetV(&WaterCannonVertices[0], 0.0f); + + RwIm3DVertexSetU(&WaterCannonVertices[1], 1.0f); + RwIm3DVertexSetV(&WaterCannonVertices[1], 0.0f); + + RwIm3DVertexSetU(&WaterCannonVertices[2], 0.0f); + RwIm3DVertexSetV(&WaterCannonVertices[2], 0.0f); + + RwIm3DVertexSetU(&WaterCannonVertices[3], 1.0f); + RwIm3DVertexSetV(&WaterCannonVertices[3], 0.0f); + + WaterCannonIndexList[0] = 0; + WaterCannonIndexList[1] = 1; + WaterCannonIndexList[2] = 2; + + WaterCannonIndexList[3] = 1; + WaterCannonIndexList[4] = 3; + WaterCannonIndexList[5] = 2; + + WaterCannonIndexList[6] = 0; + WaterCannonIndexList[7] = 2; + WaterCannonIndexList[8] = 1; + + WaterCannonIndexList[9] = 1; + WaterCannonIndexList[10] = 2; + WaterCannonIndexList[11] = 3; +} + +void CWaterCannon::Update_OncePerFrame(int16 index) +{ + ASSERT(index < NUM_WATERCANNONS); + + if (CTimer::GetTimeInMilliseconds() > m_nTimeCreated + WATERCANNON_LIFETIME ) + { + m_nCur = (m_nCur + 1) % NUM_SEGMENTPOINTS; + m_abUsed[m_nCur] = false; + } + + for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ ) + { + if ( m_abUsed[i] ) + { + m_avecVelocity[i].z += -WATERCANNON_GRAVITY * CTimer::GetTimeStep(); + m_avecPos[i] += m_avecVelocity[i] * CTimer::GetTimeStep(); + } + } + + int32 extinguishingPoint = CGeneral::GetRandomNumber() & (NUM_SEGMENTPOINTS - 1); + if ( m_abUsed[extinguishingPoint] ) + gFireManager.ExtinguishPoint(m_avecPos[extinguishingPoint], 3.0f); + + if ( ((index + CTimer::GetFrameCounter()) & 3) == 0 ) + PushPeds(); + + // free if unused + + int32 i = 0; + while ( 1 ) + { + if ( m_abUsed[i] ) + break; + + if ( ++i >= NUM_SEGMENTPOINTS ) + { + m_nId = 0; + return; + } + } +} + +void CWaterCannon::Update_NewInput(CVector *pos, CVector *dir) +{ + ASSERT(pos != NULL); + ASSERT(dir != NULL); + + m_avecPos[m_nCur] = *pos; + m_avecVelocity[m_nCur] = *dir; + m_abUsed[m_nCur] = true; +} + +void CWaterCannon::Render(void) +{ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpWaterRaster); + + float v = float(CGeneral::GetRandomNumber() & 255) / 256; + + RwIm3DVertexSetV(&WaterCannonVertices[0], v); + RwIm3DVertexSetV(&WaterCannonVertices[1], v); + RwIm3DVertexSetV(&WaterCannonVertices[2], v); + RwIm3DVertexSetV(&WaterCannonVertices[3], v); + + int16 pointA = m_nCur % NUM_SEGMENTPOINTS; + + int16 pointB = pointA - 1; + if ( pointB < 0 ) + pointB += NUM_SEGMENTPOINTS; + + bool bInit = false; + CVector norm; + + for ( int32 i = 0; i < NUM_SEGMENTPOINTS - 1; i++ ) + { + if ( m_abUsed[pointA] && m_abUsed[pointB] ) + { + if ( !bInit ) + { + CVector cp = CrossProduct(m_avecPos[pointB] - m_avecPos[pointA], TheCamera.GetForward()); + norm = cp * (0.05f / cp.Magnitude()); + bInit = true; + } + + float dist = float(i*i*i) / 300.0f + 1.0f; + float brightness = float(i) / NUM_SEGMENTPOINTS; + + int32 color = (int32)((1.0f - brightness*brightness) * 255.0f); + CVector offset = dist * norm; + + RwIm3DVertexSetRGBA(&WaterCannonVertices[0], color, color, color, color); + RwIm3DVertexSetPos (&WaterCannonVertices[0], m_avecPos[pointA].x - offset.x, m_avecPos[pointA].y - offset.y, m_avecPos[pointA].z - offset.z); + + RwIm3DVertexSetRGBA(&WaterCannonVertices[1], color, color, color, color); + RwIm3DVertexSetPos (&WaterCannonVertices[1], m_avecPos[pointA].x + offset.x, m_avecPos[pointA].y + offset.y, m_avecPos[pointA].z + offset.z); + + RwIm3DVertexSetRGBA(&WaterCannonVertices[2], color, color, color, color); + RwIm3DVertexSetPos (&WaterCannonVertices[2], m_avecPos[pointB].x - offset.x, m_avecPos[pointB].y - offset.y, m_avecPos[pointB].z - offset.z); + + RwIm3DVertexSetRGBA(&WaterCannonVertices[3], color, color, color, color); + RwIm3DVertexSetPos (&WaterCannonVertices[3], m_avecPos[pointB].x + offset.x, m_avecPos[pointB].y + offset.y, m_avecPos[pointB].z + offset.z); + + LittleTest(); + + if ( RwIm3DTransform(WaterCannonVertices, WATERCANNONVERTS, NULL, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, WaterCannonIndexList, WATERCANNONINDEXES); + RwIm3DEnd(); + } + } + + pointA = pointB--; + if ( pointB < 0 ) + pointB += NUM_SEGMENTPOINTS; + } + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE); +} + +void CWaterCannon::PushPeds(void) +{ + float minx = 10000.0f; + float maxx = -10000.0f; + float miny = 10000.0f; + float maxy = -10000.0f; + float minz = 10000.0f; + float maxz = -10000.0f; + + for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ ) + { + if ( m_abUsed[i] ) + { + minx = Min(minx, m_avecPos[i].x); + maxx = Max(maxx, m_avecPos[i].x); + + miny = Min(miny, m_avecPos[i].y); + maxy = Max(maxy, m_avecPos[i].y); + + minz = Min(minz, m_avecPos[i].z); + maxz = Max(maxz, m_avecPos[i].z); + } + } + + for ( int32 i = CPools::GetPedPool()->GetSize() - 1; i >= 0; i--) + { + CPed *ped = CPools::GetPedPool()->GetSlot(i); + if ( ped ) + { + if ( ped->GetPosition().x > minx && ped->GetPosition().x < maxx + && ped->GetPosition().y > miny && ped->GetPosition().y < maxy + && ped->GetPosition().z > minz && ped->GetPosition().z < maxz ) + { + for ( int32 j = 0; j < NUM_SEGMENTPOINTS; j++ ) + { + if ( m_abUsed[j] ) + { + CVector dist = m_avecPos[j] - ped->GetPosition(); + + if ( dist.MagnitudeSqr() < 5.0f ) + { + int32 localDir = ped->GetLocalDirection(CVector2D(1.0f, 0.0f)); + + ped->bIsStanding = false; + + ped->ApplyMoveForce(0.0f, 0.0f, 2.0f * CTimer::GetTimeStep()); + + ped->m_vecMoveSpeed.x = (0.6f * m_avecVelocity[j].x + ped->m_vecMoveSpeed.x) * 0.5f; + ped->m_vecMoveSpeed.y = (0.6f * m_avecVelocity[j].y + ped->m_vecMoveSpeed.y) * 0.5f; + + ped->SetFall(2000, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), 0); + + CFire *fire = ped->m_pFire; + if ( fire ) + fire->Extinguish(); + + j = NUM_SEGMENTPOINTS; + } + } + } + } + } + } +} + +void CWaterCannons::Init(void) +{ + for ( int32 i = 0; i < NUM_WATERCANNONS; i++ ) + aCannons[i].Init(); +} + +void CWaterCannons::UpdateOne(uint32 id, CVector *pos, CVector *dir) +{ + ASSERT(pos != NULL); + ASSERT(dir != NULL); + + // find the one by id + { + int32 n = 0; + while ( n < NUM_WATERCANNONS && id != aCannons[n].m_nId ) + n++; + + if ( n < NUM_WATERCANNONS ) + { + aCannons[n].Update_NewInput(pos, dir); + return; + } + } + + // if no luck then find a free one + { + int32 n = 0; + while ( n < NUM_WATERCANNONS && 0 != aCannons[n].m_nId ) + n++; + + if ( n < NUM_WATERCANNONS ) + { + aCannons[n].Init(); + aCannons[n].m_nId = id; + aCannons[n].Update_NewInput(pos, dir); + return; + } + } +} + +void CWaterCannons::Update(void) +{ + for ( int32 i = 0; i < NUM_WATERCANNONS; i++ ) + { + if ( aCannons[i].m_nId != 0 ) + aCannons[i].Update_OncePerFrame(i); + } +} + +void CWaterCannons::Render(void) +{ + PUSH_RENDERGROUP("CWaterCannons::Render"); + for ( int32 i = 0; i < NUM_WATERCANNONS; i++ ) + { + if ( aCannons[i].m_nId != 0 ) + aCannons[i].Render(); + } + POP_RENDERGROUP(); +} diff --git a/src/renderer/WaterCannon.h b/src/renderer/WaterCannon.h new file mode 100644 index 0000000..a37bdd1 --- /dev/null +++ b/src/renderer/WaterCannon.h @@ -0,0 +1,39 @@ +#pragma once + +#define WATERCANNON_GRAVITY (0.009f) +#define WATERCANNON_LIFETIME (150) + +class CWaterCannon +{ +public: + enum + { + NUM_SEGMENTPOINTS = 16, + }; + + int32 m_nId; + int16 m_nCur; + uint32 m_nTimeCreated; + CVector m_avecPos[NUM_SEGMENTPOINTS]; + CVector m_avecVelocity[NUM_SEGMENTPOINTS]; + bool m_abUsed[NUM_SEGMENTPOINTS]; + + void Init(void); + void Update_OncePerFrame(int16 index); + void Update_NewInput(CVector *pos, CVector *dir); + void Render(void); + void PushPeds(void); +}; + +VALIDATE_SIZE(CWaterCannon, 412); + +class CWaterCannons +{ +public: + static CWaterCannon aCannons[NUM_WATERCANNONS]; + + static void Init(void); + static void UpdateOne(uint32 id, CVector *pos, CVector *dir); + static void Update(); + static void Render(void); +}; \ No newline at end of file diff --git a/src/renderer/WaterLevel.cpp b/src/renderer/WaterLevel.cpp new file mode 100644 index 0000000..7001c0c --- /dev/null +++ b/src/renderer/WaterLevel.cpp @@ -0,0 +1,1554 @@ +#include "common.h" +#include "main.h" +#include "FileMgr.h" +#include "FileLoader.h" +#include "TxdStore.h" +#include "Timer.h" +#include "Weather.h" +#include "Camera.h" +#include "Vehicle.h" +#include "Boat.h" +#include "World.h" +#include "General.h" +#include "Timecycle.h" +#include "ZoneCull.h" +#include "Clock.h" +#include "Particle.h" +#include "ParticleMgr.h" +#include "RwHelper.h" +#include "Streaming.h" +#include "CdStream.h" +#include "Pad.h" +#include "RenderBuffer.h" +#include +#include "WaterLevel.h" +#include "MemoryHeap.h" + + +float TEXTURE_ADDU; +float TEXTURE_ADDV; + +int32 CWaterLevel::ms_nNoOfWaterLevels; +float CWaterLevel::ms_aWaterZs[48]; +CRect CWaterLevel::ms_aWaterRects[48]; +int8 CWaterLevel::aWaterBlockList[MAX_LARGE_SECTORS][MAX_LARGE_SECTORS]; +int8 CWaterLevel::aWaterFineBlockList[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS]; +bool CWaterLevel::WavesCalculatedThisFrame; +RpAtomic *CWaterLevel::ms_pWavyAtomic; +RpGeometry *CWaterLevel::apGeomArray[8]; +int16 CWaterLevel::nGeomUsed; +//"Custom" Don't Render Water Toggle +bool gbDontRenderWater; + +//RwTexture *gpWaterTex; +//RwRaster *gpWaterRaster; + +RwTexture *gpWaterTex; +RwRaster *gpWaterRaster; + + +const float fAdd1 = 180.0f; +const float fAdd2 = 80.0f; +const float fRedMult = 0.6f; +const float fGreenMult = 1.0f; +const float fBlueMult = 1.4f; + + +void +CWaterLevel::Initialise(Const char *pWaterDat) +{ + ms_nNoOfWaterLevels = 0; + +#ifdef MASTER + int32 hFile = -1; + + do + { + hFile = CFileMgr::OpenFile("DATA\\waterpro.dat", "rb"); + } + while ( hFile < 0 ); +#else + int32 hFile = CFileMgr::OpenFile("DATA\\waterpro.dat", "rb"); +#endif + + if (hFile > 0) + { + CFileMgr::Read(hFile, (char *)&ms_nNoOfWaterLevels, sizeof(ms_nNoOfWaterLevels)); + CFileMgr::Read(hFile, (char *)ms_aWaterZs, sizeof(ms_aWaterZs)); + CFileMgr::Read(hFile, (char *)ms_aWaterRects, sizeof(ms_aWaterRects)); + CFileMgr::Read(hFile, (char *)aWaterBlockList, sizeof(aWaterBlockList)); + CFileMgr::Read(hFile, (char *)aWaterFineBlockList, sizeof(aWaterFineBlockList)); + + CFileMgr::CloseFile(hFile); + } +#ifndef MASTER + else + { + printf("Init waterlevels\n"); + + CFileMgr::SetDir(""); + hFile = CFileMgr::OpenFile(pWaterDat, "r"); + + char *line; + + while ((line = CFileLoader::LoadLine(hFile))) + { +#ifdef FIX_BUGS + if (*line && *line != ';' && !strstr(line, "* ;end of file")) +#else + if (*line && *line != ';') +#endif + { + float z, l, b, r, t; + sscanf(line, "%f %f %f %f %f", &z, &l, &b, &r, &t); + AddWaterLevel(l, b, r, t, z); + } + } + + CFileMgr::CloseFile(hFile); + + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + aWaterFineBlockList[x][y] = NO_WATER; + } + } + + // rasterize water rects read from file + for (int32 i = 0; i < ms_nNoOfWaterLevels; i++) + { + int32 l = WATER_HUGE_X(ms_aWaterRects[i].left); + int32 r = WATER_HUGE_X(ms_aWaterRects[i].right) + 1.0f; + int32 t = WATER_HUGE_Y(ms_aWaterRects[i].top); + int32 b = WATER_HUGE_Y(ms_aWaterRects[i].bottom) + 1.0f; + +#ifdef FIX_BUGS + // water.dat has rects that go out of bounds + // which causes memory corruption + l = Clamp(l, 0, MAX_SMALL_SECTORS - 1); + r = Clamp(r, 0, MAX_SMALL_SECTORS - 1); + t = Clamp(t, 0, MAX_SMALL_SECTORS - 1); + b = Clamp(b, 0, MAX_SMALL_SECTORS - 1); +#endif + + for (int32 x = l; x <= r; x++) + { + for (int32 y = t; y <= b; y++) + { + aWaterFineBlockList[x][y] = i; + } + } + } + + // remove tiles that are obscured by land + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + float worldX = WATER_START_X + x * SMALL_SECTOR_SIZE; + + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + if (aWaterFineBlockList[x][y] >= 0) + { + float worldY = WATER_START_Y + y * SMALL_SECTOR_SIZE; + + int32 i; + for (i = 0; i <= 8; i++) + { + for (int32 j = 0; j <= 8; j++) + { + CVector worldPos = CVector(worldX + i * (SMALL_SECTOR_SIZE / 8), worldY + j * (SMALL_SECTOR_SIZE / 8), ms_aWaterZs[aWaterFineBlockList[x][y]]); + + if ((worldPos.x > WORLD_MIN_X && worldPos.x < WORLD_MAX_X) && (worldPos.y > WORLD_MIN_Y && worldPos.y < WORLD_MAX_Y) && + (!WaterLevelAccordingToRectangles(worldPos.x, worldPos.y) || TestVisibilityForFineWaterBlocks(worldPos))) + continue; + + // at least one point in the tile wasn't blocked, so don't remove water + i = 1000; + break; + } + } + + if (i < 1000) + aWaterFineBlockList[x][y] = NO_WATER; + } + } + } + + RemoveIsolatedWater(); + + // calculate coarse tiles from fine tiles + for (int32 x = 0; x < MAX_LARGE_SECTORS; x++) + { + for (int32 y = 0; y < MAX_LARGE_SECTORS; y++) + { + if (aWaterFineBlockList[x * 2][y * 2] >= 0) + { + aWaterBlockList[x][y] = aWaterFineBlockList[x * 2][y * 2]; + } + else if (aWaterFineBlockList[x * 2 + 1][y * 2] >= 0) + { + aWaterBlockList[x][y] = aWaterFineBlockList[x * 2 + 1][y * 2]; + } + else if (aWaterFineBlockList[x * 2][y * 2 + 1] >= 0) + { + aWaterBlockList[x][y] = aWaterFineBlockList[x * 2][y * 2 + 1]; + } + else if (aWaterFineBlockList[x * 2 + 1][y * 2 + 1] >= 0) + { + aWaterBlockList[x][y] = aWaterFineBlockList[x * 2 + 1][y * 2 + 1]; + } + else + { + aWaterBlockList[x][y] = NO_WATER; + } + } + } + + hFile = CFileMgr::OpenFileForWriting("data\\waterpro.dat"); + + if (hFile > 0) + { + CFileMgr::Write(hFile, (char *)&ms_nNoOfWaterLevels, sizeof(ms_nNoOfWaterLevels)); + CFileMgr::Write(hFile, (char *)ms_aWaterZs, sizeof(ms_aWaterZs)); + CFileMgr::Write(hFile, (char *)ms_aWaterRects, sizeof(ms_aWaterRects)); + CFileMgr::Write(hFile, (char *)aWaterBlockList, sizeof(aWaterBlockList)); + CFileMgr::Write(hFile, (char *)aWaterFineBlockList, sizeof(aWaterFineBlockList)); + + CFileMgr::CloseFile(hFile); + } + } +#endif + + CTxdStore::PushCurrentTxd(); + + int32 slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + + if ( gpWaterTex == nil ) + gpWaterTex = RwTextureRead("water_old", nil); + gpWaterRaster = RwTextureGetRaster(gpWaterTex); + + CTxdStore::PopCurrentTxd(); + + CreateWavyAtomic(); + FreeBoatWakeArray(); + + printf("Done Initing waterlevels\n"); +} + +void +CWaterLevel::Shutdown() +{ + FreeBoatWakeArray(); + DestroyWavyAtomic(); + + if ( gpWaterTex != nil ) + { + RwTextureDestroy(gpWaterTex); + gpWaterTex = nil; + } +} + +void +CWaterLevel::CreateWavyAtomic() +{ + RpGeometry *wavyGeometry; + RpMaterial *wavyMaterial; + RpTriangle *wavyTriangles; + RpMorphTarget *wavyMorphTarget; + RwSphere boundingSphere; + RwV3d *wavyVert; + + RwFrame *wavyFrame; + + { + wavyGeometry = RpGeometryCreate(9*9, 8*8*2, rpGEOMETRYTRISTRIP + |rpGEOMETRYTEXTURED + |rpGEOMETRYPRELIT + |rpGEOMETRYMODULATEMATERIALCOLOR); + + ASSERT(wavyGeometry != nil); + + } + + { + wavyMaterial = RpMaterialCreate(); + + ASSERT(wavyMaterial != nil); + ASSERT(gpWaterTex != nil); + + RpMaterialSetTexture(wavyMaterial, gpWaterTex); + } + + { + wavyTriangles = RpGeometryGetTriangles(wavyGeometry); + + ASSERT(wavyTriangles != nil); + /* + [B] [C] + *********** + * * * + * * * + * * * + * * * + *********** + [A] [D] + */ + + for ( int32 i = 0; i < 8; i++ ) + { + for ( int32 j = 0; j < 8; j++ ) + { + RpGeometryTriangleSetVertexIndices(wavyGeometry, + &wavyTriangles[2 * 8*i + 2*j + 0], /*A*/9*i+j+0, /*B*/9*i+j+1, /*C*/9*i+j+9+1); + + RpGeometryTriangleSetVertexIndices(wavyGeometry, + &wavyTriangles[2 * 8*i + 2*j + 1], /*A*/9*i+j+0, /*C*/9*i+j+9+1, /*D*/9*i+j+9 ); + + RpGeometryTriangleSetMaterial(wavyGeometry, &wavyTriangles[2 * 8*i + 2*j + 0], wavyMaterial); + RpGeometryTriangleSetMaterial(wavyGeometry, &wavyTriangles[2 * 8*i + 2*j + 1], wavyMaterial); + } + } + } + + + { + wavyMorphTarget = RpGeometryGetMorphTarget(wavyGeometry, 0); + ASSERT(wavyMorphTarget != nil); + wavyVert = RpMorphTargetGetVertices(wavyMorphTarget); + ASSERT(wavyVert != nil); + + for ( int32 i = 0; i < 9; i++ ) + { + for ( int32 j = 0; j < 9; j++ ) + { + wavyVert[9*i+j].x = (float)i * 4.0f; + wavyVert[9*i+j].y = (float)j * 4.0f; + wavyVert[9*i+j].z = 0.0f; + } + } + + RpMorphTargetCalcBoundingSphere(wavyMorphTarget, &boundingSphere); + RpMorphTargetSetBoundingSphere(wavyMorphTarget, &boundingSphere); + RpGeometryUnlock(wavyGeometry); + } + + + { + wavyFrame = RwFrameCreate(); + ASSERT( wavyFrame != nil ); + + ms_pWavyAtomic = RpAtomicCreate(); + ASSERT( ms_pWavyAtomic != nil ); + + RpAtomicSetGeometry(ms_pWavyAtomic, wavyGeometry, 0); + RpAtomicSetFrame(ms_pWavyAtomic, wavyFrame); + RpMaterialDestroy(wavyMaterial); + RpGeometryDestroy(wavyGeometry); + } +} + +void +CWaterLevel::DestroyWavyAtomic() +{ + RwFrame *frame; + + frame = RpAtomicGetFrame(ms_pWavyAtomic); + + RpAtomicDestroy(ms_pWavyAtomic); + + RwFrameDestroy(frame); +} + +#ifndef MASTER +void +CWaterLevel::AddWaterLevel(float fXLeft, float fYBottom, float fXRight, float fYTop, float fLevel) +{ + ms_aWaterRects[ms_nNoOfWaterLevels] = CRect(fXLeft, fYBottom, fXRight, fYTop); + ms_aWaterZs[ms_nNoOfWaterLevels] = fLevel; + ms_nNoOfWaterLevels++; +} + +bool +CWaterLevel::WaterLevelAccordingToRectangles(float fX, float fY, float *pfOutLevel) +{ + if (ms_nNoOfWaterLevels <= 0) return false; + + for (int32 i = 0; i < ms_nNoOfWaterLevels; i++) + { + if (fX >= ms_aWaterRects[i].left && fX <= ms_aWaterRects[i].right + && fY >= ms_aWaterRects[i].top && fY <= ms_aWaterRects[i].bottom) + { + if (pfOutLevel) *pfOutLevel = ms_aWaterZs[i]; + + return true; + } + } + + return false; +} + +bool +CWaterLevel::TestVisibilityForFineWaterBlocks(const CVector &worldPos) +{ + static CVector2D tab[] = + { + { 50.0f, 50.0f }, + { -50.0f, 50.0f }, + { -50.0f, -50.0f }, + { 50.0f, -50.0f }, + { 50.0f, 0.0f }, + { -50.0f, 0.0f }, + { 0.0f, -50.0f }, + { 0.0f, 50.0f }, + }; + + CEntity *entity; + CColPoint col; + CVector lineStart, lineEnd; + + lineStart = worldPos; + + if (!CWorld::ProcessVerticalLine(lineStart, lineStart.z + 100.0f, col, entity, true, false, false, false, true, false, nil)) + { + lineStart.x += 0.4f; + lineStart.y += 0.4f; + + if (!CWorld::ProcessVerticalLine(lineStart, lineStart.z + 100.0f, col, entity, true, false, false, false, true, false, nil)) + { + return false; + } + } + + for (int32 i = 0; i < ARRAY_SIZE(tab); i++) + { + lineStart = worldPos; + lineEnd = worldPos; + + lineEnd.x += tab[i].x; + lineEnd.y += tab[i].y; + lineEnd.z += 100.0f; + + if ((lineEnd.x > WORLD_MIN_X && lineEnd.x < WORLD_MAX_X) && (lineEnd.y > WORLD_MIN_Y && lineEnd.y < WORLD_MAX_Y)) + { + if (!CWorld::ProcessLineOfSight(lineStart, lineEnd, col, entity, true, false, false, false, true, false)) + { + lineStart.x += 0.4f; + lineStart.y += 0.4f; + lineEnd.x += 0.4f; + lineEnd.y += 0.4f; + + if (!CWorld::ProcessLineOfSight(lineStart, lineEnd, col, entity, true, false, false, false, true, false)) + { + return false; + } + } + } + } + + return true; +} + +void +CWaterLevel::RemoveIsolatedWater() +{ + bool (*isConnected)[MAX_SMALL_SECTORS] = new bool[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS]; + + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + isConnected[x][y] = false; + } + } + + isConnected[0][0] = true; + bool keepGoing; + + do + { + keepGoing = false; + + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + if (aWaterFineBlockList[x][y] < 0 || isConnected[x][y]) + continue; + + if (x > 0 && isConnected[x - 1][y]) + { + isConnected[x][y] = true; + keepGoing = true; + } + + if (y > 0 && isConnected[x][y - 1]) + { + isConnected[x][y] = true; + keepGoing = true; + } + + if (x + 1 < MAX_SMALL_SECTORS && isConnected[x + 1][y]) + { + isConnected[x][y] = true; + keepGoing = true; + } + + if (y + 1 < MAX_SMALL_SECTORS && isConnected[x][y + 1]) + { + isConnected[x][y] = true; + keepGoing = true; + } + } + } + } + while (keepGoing); + + int32 numRemoved = 0; + + for (int32 x = 0; x < MAX_SMALL_SECTORS; x++) + { + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + if (aWaterFineBlockList[x][y] >= 0 && !isConnected[x][y] && ms_aWaterZs[aWaterFineBlockList[x][y]] == 0.0f) + { + numRemoved++; + aWaterFineBlockList[x][y] = NO_WATER; + } + } + } + + printf("Removed %d isolated patches of water\n", numRemoved); + + delete[] isConnected; +} +#endif + +bool +CWaterLevel::GetWaterLevel(float fX, float fY, float fZ, float *pfOutLevel, bool bDontCheckZ) +{ + int32 x = WATER_HUGE_X(fX); + int32 y = WATER_HUGE_Y(fY); + + ASSERT( x >= 0 && x < HUGE_SECTOR_SIZE ); + ASSERT( y >= 0 && y < HUGE_SECTOR_SIZE ); + + int8 nBlock = aWaterFineBlockList[x][y]; + + if ( nBlock == NO_WATER ) + return false; + + ASSERT( pfOutLevel != nil ); + *pfOutLevel = ms_aWaterZs[nBlock]; + + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + + float fWave = Sin + ( + /*( WATER_UNSIGN_Y(fY) - float(y) * MAX_HUGE_SECTORS + WATER_UNSIGN_X(fX) - float(x) * MAX_HUGE_SECTORS )*/ // VC + (float)( ((int32)fX & (MAX_HUGE_SECTORS-1)) + ((int32)fY & (MAX_HUGE_SECTORS-1)) ) + * (TWOPI / MAX_HUGE_SECTORS ) + fAngle + ); + + float fWindFactor = CWeather::Wind * 0.7f + 0.3f; + + *pfOutLevel += fWave * fWindFactor; + + if ( bDontCheckZ == false && (*pfOutLevel - fZ) > 3.0f ) + { + *pfOutLevel = 0.0f; + return false; + } + + return true; +} + +bool +CWaterLevel::GetWaterLevelNoWaves(float fX, float fY, float fZ, float *pfOutLevel) +{ + int32 x = WATER_HUGE_X(fX); + int32 y = WATER_HUGE_Y(fY); + + ASSERT( x >= 0 && x < HUGE_SECTOR_SIZE ); + ASSERT( y >= 0 && y < HUGE_SECTOR_SIZE ); + + int8 nBlock = aWaterFineBlockList[x][y]; + + if ( nBlock == NO_WATER ) + return false; + + ASSERT( pfOutLevel != nil ); + *pfOutLevel = ms_aWaterZs[nBlock]; + + return true; +} + +inline float +_GetWaterDrawDist() +{ + // if z less then 15.0f return 1200.0f + if ( TheCamera.GetPosition().z < 15.0f ) + return 1200.0f; + + // if z greater then 60.0f return 2000.0f; + if ( TheCamera.GetPosition().z > 60.0f ) + return 2000.0f; + + return (TheCamera.GetPosition().z + -15.0f) * 800.0f / 45.0f + 1200.0f; +} + +inline float +_GetWavyDrawDist() +{ + if ( FindPlayerVehicle() && FindPlayerVehicle()->IsBoat() ) + return 120.0f; + else + return 70.0f; +} + +inline void +_GetCamBounds(bool *bUseCamStartY, bool *bUseCamEndY, bool *bUseCamStartX, bool *bUseCamEndX) +{ + if ( TheCamera.GetForward().z > -0.8f ) + { + if ( Abs(TheCamera.GetForward().x) > Abs(TheCamera.GetForward().y) ) + { + if ( TheCamera.GetForward().x > 0.0f ) + *bUseCamStartX = true; + else + *bUseCamEndX = true; + } + else + { + if ( TheCamera.GetForward().y > 0.0f ) + *bUseCamStartY = true; + else + *bUseCamEndY = true; + } + } +} + +inline float +SectorRadius(float fSize) +{ + return Sqrt(Pow(fSize, 2) + Pow(fSize, 2)); +} + +void +CWaterLevel::RenderWater() +{ +//"Custom" Don't Render Water Toggle +#ifndef MASTER + if (gbDontRenderWater) + return; +#endif + PUSH_RENDERGROUP("CWaterLevel::RenderWater"); + bool bUseCamEndX = false; + bool bUseCamStartY = false; + + bool bUseCamStartX = false; + bool bUseCamEndY = false; + + float fWavySectorMaxRenderDist = _GetWavyDrawDist(); + float fWavySectorMaxRenderDistSqr = SQR(fWavySectorMaxRenderDist); + + _GetCamBounds(&bUseCamStartY, &bUseCamEndY, &bUseCamStartX, &bUseCamEndX); + + float fHugeSectorMaxRenderDist = _GetWaterDrawDist(); + float fHugeSectorMaxRenderDistSqr = SQR(fHugeSectorMaxRenderDist); + + float windAddUV = CWeather::Wind * 0.0015f + 0.0005f; + + + if ( !CTimer::GetIsPaused() ) + { +#ifdef FIX_BUGS + TEXTURE_ADDU += (CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV) * CTimer::GetTimeStepFix(); + TEXTURE_ADDV += (CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV) * CTimer::GetTimeStepFix(); +#else + TEXTURE_ADDU += CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV; + TEXTURE_ADDV += CGeneral::GetRandomNumberInRange(-0.0005f, 0.0005f) + windAddUV; +#endif + } + + if ( TEXTURE_ADDU >= 1.0f ) + TEXTURE_ADDU = 0.0f; + if ( TEXTURE_ADDV >= 1.0f ) + TEXTURE_ADDV = 0.0f; + + WavesCalculatedThisFrame = false; + + RwRGBA color = { 0, 0, 0, 255 }; + + color.red = uint32((CTimeCycle::GetDirectionalRed() * 0.5f + CTimeCycle::GetAmbientRed() ) * 255.0f); + color.green = uint32((CTimeCycle::GetDirectionalGreen() * 0.5f + CTimeCycle::GetAmbientGreen()) * 255.0f); + color.blue = uint32((CTimeCycle::GetDirectionalBlue() * 0.5f + CTimeCycle::GetAmbientBlue() ) * 255.0f); + + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpWaterRaster); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDZERO); + + CVector2D camPos + ( + TheCamera.GetPosition().x, + TheCamera.GetPosition().y + ); + + int32 nStartX = WATER_TO_HUGE_SECTOR_X(camPos.x - fHugeSectorMaxRenderDist); + int32 nEndX = WATER_TO_HUGE_SECTOR_X(camPos.x + fHugeSectorMaxRenderDist) + 1; + int32 nStartY = WATER_TO_HUGE_SECTOR_Y(camPos.y - fHugeSectorMaxRenderDist); + int32 nEndY = WATER_TO_HUGE_SECTOR_Y(camPos.y + fHugeSectorMaxRenderDist) + 1; + + if ( bUseCamStartX ) + nStartX = WATER_TO_HUGE_SECTOR_X(camPos.x); + if ( bUseCamEndX ) + nEndX = WATER_TO_HUGE_SECTOR_X(camPos.x); + if ( bUseCamStartY ) + nStartY = WATER_TO_HUGE_SECTOR_Y(camPos.y); + if ( bUseCamEndY ) + nEndY = WATER_TO_HUGE_SECTOR_Y(camPos.y); + + nStartX = Clamp(nStartX, 0, MAX_HUGE_SECTORS - 1); + nEndX = Clamp(nEndX, 0, MAX_HUGE_SECTORS - 1); + nStartY = Clamp(nStartY, 0, MAX_HUGE_SECTORS - 1); + nEndY = Clamp(nEndY, 0, MAX_HUGE_SECTORS - 1); + + for ( int32 x = nStartX; x <= nEndX; x++ ) + { + for ( int32 y = nStartY; y <= nEndY; y++ ) + { + if ( aWaterBlockList[2*x+0][2*y+0] >= 0 + || aWaterBlockList[2*x+1][2*y+0] >= 0 + || aWaterBlockList[2*x+0][2*y+1] >= 0 + || aWaterBlockList[2*x+1][2*y+1] >= 0 ) + { + float fX = WATER_FROM_HUGE_SECTOR_X(x); + float fY = WATER_FROM_HUGE_SECTOR_Y(y); + + CVector2D vecHugeSectorCentre + ( + fX + HUGE_SECTOR_SIZE/2, + fY + HUGE_SECTOR_SIZE/2 + ); + + float fHugeSectorDistToCamSqr = (camPos - vecHugeSectorCentre).MagnitudeSqr(); + + if ( fHugeSectorMaxRenderDistSqr > fHugeSectorDistToCamSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecHugeSectorCentre.x, vecHugeSectorCentre.y, 0.0f), SectorRadius(HUGE_SECTOR_SIZE)) ) + { + if ( fHugeSectorDistToCamSqr >= SQR(500.0f) /*fHugeSectorNearDist*/ ) + { + float fZ; + + if ( aWaterBlockList[2*x+0][2*y+0] >= 0 ) + fZ = ms_aWaterZs[ aWaterBlockList[2*x+0][2*y+0] ]; + + if ( aWaterBlockList[2*x+1][2*y+0] >= 0 ) + fZ = ms_aWaterZs[ aWaterBlockList[2*x+1][2*y+0] ]; + + if ( aWaterBlockList[2*x+0][2*y+1] >= 0 ) + fZ = ms_aWaterZs[ aWaterBlockList[2*x+0][2*y+1] ]; + + if ( aWaterBlockList[2*x+1][2*y+1] >= 0 ) + fZ = ms_aWaterZs[ aWaterBlockList[2*x+1][2*y+1] ]; + + RenderOneFlatHugeWaterPoly(fX, fY, fZ, color); + } + else + { + for ( int32 x2 = 2*x; x2 <= 2*x+1; x2++ ) + { + for ( int32 y2 = 2*y; y2 <= 2*y+1; y2++ ) + { + if ( aWaterBlockList[x2][y2] >= 0 ) + { + float fLargeX = WATER_FROM_LARGE_SECTOR_X(x2); + float fLargeY = WATER_FROM_LARGE_SECTOR_Y(y2); + + CVector2D vecLargeSectorCentre + ( + fLargeX + LARGE_SECTOR_SIZE/2, + fLargeY + LARGE_SECTOR_SIZE/2 + ); + + float fLargeSectorDistToCamSqr = (camPos - vecLargeSectorCentre).MagnitudeSqr(); + + if ( fLargeSectorDistToCamSqr < fHugeSectorMaxRenderDistSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecLargeSectorCentre.x, vecLargeSectorCentre.y, 0.0f), SectorRadius(LARGE_SECTOR_SIZE)) ) //90.879997f, + { + // Render four small(32x32) sectors, or one large(64x64). + + // + // [N] + // --------- + // |0x1|1x1| + // [W] --------- [E] + // |0x0|1x0| + // --------- + // [S] + // + + if ( fLargeSectorDistToCamSqr < SQR(176.0f) ) + { + float fZ; + + // WS + if ( aWaterFineBlockList[2*x2+0][2*y2+0] >= 0 ) + { + float fSmallX = fLargeX; + float fSmallY = fLargeY; + + CVector2D vecSmallSectorCentre + ( + fSmallX + SMALL_SECTOR_SIZE/2, + fSmallY + SMALL_SECTOR_SIZE/2 + ); + + float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr(); + fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+0][2*y2+0] ]; + + if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr ) + RenderOneWavySector(fSmallX, fSmallY, fZ, color); + else + RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color); + } + + // SE + if ( aWaterFineBlockList[2*x2+1][2*y2+0] >= 0 ) + { + float fSmallX = fLargeX + (LARGE_SECTOR_SIZE/2); + float fSmallY = fLargeY; + + CVector2D vecSmallSectorCentre + ( + fSmallX + SMALL_SECTOR_SIZE/2, + fSmallY + SMALL_SECTOR_SIZE/2 + ); + + float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr(); + fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+1][2*y2+0] ]; + + if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr ) + RenderOneWavySector(fSmallX, fSmallY, fZ, color); + else + RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color); + } + + // WN + if ( aWaterFineBlockList[2*x2+0][2*y2+1] >= 0 ) + { + float fSmallX = fLargeX; + float fSmallY = fLargeY + (LARGE_SECTOR_SIZE/2); + + CVector2D vecSmallSectorCentre + ( + fSmallX + SMALL_SECTOR_SIZE/2, + fSmallY + SMALL_SECTOR_SIZE/2 + ); + + float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr(); + fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+0][2*y2+1] ]; + + if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr ) + RenderOneWavySector(fSmallX, fSmallY, fZ, color); + else + RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color); + } + + //NE + if ( aWaterFineBlockList[2*x2+1][2*y2+1] >= 0 ) + { + float fSmallX = fLargeX + (LARGE_SECTOR_SIZE/2); + float fSmallY = fLargeY + (LARGE_SECTOR_SIZE/2); + + CVector2D vecSmallSectorCentre + ( + fSmallX + SMALL_SECTOR_SIZE/2, + fSmallY + SMALL_SECTOR_SIZE/2 + ); + + float fSmallSectorDistToCamSqr = (camPos - vecSmallSectorCentre).MagnitudeSqr(); + fZ = ms_aWaterZs[ aWaterFineBlockList[2*x2+1][2*y2+1] ]; + + if ( fSmallSectorDistToCamSqr < fWavySectorMaxRenderDistSqr ) + RenderOneWavySector(fSmallX, fSmallY, fZ, color); + else + RenderOneFlatSmallWaterPoly(fSmallX, fSmallY, fZ, color); + } + } + else + { + float fZ; + + fZ = ms_aWaterZs[ aWaterBlockList[x2][y2] ]; + + RenderOneFlatLargeWaterPoly(fLargeX, fLargeY, fZ, color); + } + } // if ( TheCamera.IsSphereVisible + } // if ( fLargeSectorDistToCamSqr < fHugeSectorMaxRenderDistSqr ) + } // if ( aWaterBlockList[x2][y2] >= 0 ) + } // for ( int32 y2 = 2*y; y2 <= 2*y+1; y2++ ) + } // for ( int32 x2 = 2*x; x2 <= 2*x+1; x2++ ) + // + + } + } + } + } + } + } + + /* + ----------- ---------------------- ---------------------- + | [N] | | [ EndY ] | | [ top ] | + | | | | | | + |[W] [0] [E]| |[StartX] [] [ EndX ]| |[ left ] [] [ right]| + | | | | | | + | [S] | | [StartY] | | [bottom] | + ----------- ---------------------- ---------------------- + + + [S] [StartY] [bottom] + [N] [EndY] [top] + [W] [StartX] [left] + [E] [EndX] [right] + + [S] -> [N] && [W] -> [E] + bottom -> top && left -> right + */ + + if ( !bUseCamStartY ) + { + for ( int32 x = 0; x < 26; x++ ) + { + for ( int32 y = 0; y < 5; y++ ) + { + float fX = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + float fY = WATER_SIGN_Y(float(y) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + + CVector2D vecExtraHugeSectorCentre + ( + fX + EXTRAHUGE_SECTOR_SIZE/2, + fY + EXTRAHUGE_SECTOR_SIZE/2 + ); + + float fCamDistToSector = (vecExtraHugeSectorCentre - camPos).Magnitude(); + + if ( fCamDistToSector < fHugeSectorMaxRenderDistSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecExtraHugeSectorCentre.x, vecExtraHugeSectorCentre.y, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE)) ) + { + RenderOneFlatExtraHugeWaterPoly( + vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2, + vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2, + 0.0f, + color); + } + } + } + } + } + + for ( int32 y = 5; y < 21; y++ ) + { + for ( int32 x = 0; x < 5; x++ ) + { + float fX = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + float fX2 = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + float fY = WATER_SIGN_Y(float(y) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + + if ( !bUseCamStartX ) + { + CVector2D vecExtraHugeSectorCentre + ( + fX + EXTRAHUGE_SECTOR_SIZE/2, + fY + EXTRAHUGE_SECTOR_SIZE/2 + ); + + float fCamDistToSector = (vecExtraHugeSectorCentre - camPos).Magnitude(); + + if ( fCamDistToSector < fHugeSectorMaxRenderDistSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecExtraHugeSectorCentre.x, vecExtraHugeSectorCentre.y, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE)) ) + { + RenderOneFlatExtraHugeWaterPoly( + vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2, + vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2, + 0.0f, + color); + } + } + } + + if ( !bUseCamEndX ) + { + CVector2D vecExtraHugeSectorCentre + ( + -(fX2 + EXTRAHUGE_SECTOR_SIZE/2), + fY + EXTRAHUGE_SECTOR_SIZE/2 + ); + + float fCamDistToSector = (vecExtraHugeSectorCentre - camPos).Magnitude(); + + if ( fCamDistToSector < fHugeSectorMaxRenderDistSqr ) + { + if ( TheCamera.IsSphereVisible(CVector(vecExtraHugeSectorCentre.x, vecExtraHugeSectorCentre.y, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE)) ) + { + RenderOneFlatExtraHugeWaterPoly( + vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2, + vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2, + 0.0f, + color); + } + } + } + } + } + + RenderAndEmptyRenderBuffer(); + + CVector cur_pos = TheCamera.GetPosition(); + + if ( !CCullZones::CamNoRain() + && !CCullZones::PlayerNoRain() + && CWeather::NewWeatherType == WEATHER_SUNNY + && CClock::GetHours() > 6 && CClock::GetHours() < 20 + && WavesCalculatedThisFrame) + { + static CVector prev_pos(0.0f, 0.0f, 0.0f); + static CVector prev_front(0.0f, 0.0f, 0.0f); + static int32 timecounter; + + if ( Abs(prev_pos.x - cur_pos.x) + Abs(prev_pos.y - cur_pos.y) + Abs(prev_pos.z - cur_pos.z) > 1.5f ) + { + prev_pos = cur_pos; + timecounter = CTimer::GetTimeInMilliseconds(); + } + else if ( CTimer::GetTimeInMilliseconds() - timecounter > 5000 ) + { + static int32 birdgenTime = 0; + + if ( CTimer::GetTimeInMilliseconds() - birdgenTime > 1000 ) + { + birdgenTime = CTimer::GetTimeInMilliseconds(); + + CVector vecPos = cur_pos; + + float fAngle = CGeneral::GetRandomNumberInRange(90.0f, 150.0f); + + int32 nRot = CGeneral::GetRandomNumber() % CParticle::SIN_COS_TABLE_SIZE-1; + + float fCos = CParticle::Cos(nRot); + float fSin = CParticle::Sin(nRot); + + vecPos.x += (fCos - fSin) * fAngle; + vecPos.y += (fSin + fCos) * fAngle; + vecPos.z += CGeneral::GetRandomNumberInRange(10.0f, 30.0f); + + CVector vecDir(CGeneral::GetRandomNumberInRange(-1.0f, 1.0f), + CGeneral::GetRandomNumberInRange(-1.0f, 1.0f), + 0.0f); + + CParticle::AddParticle(PARTICLE_BIRD_FRONT, vecPos, vecDir); + } + } + } + + DefinedState(); + + POP_RENDERGROUP(); +} + +void +CWaterLevel::RenderOneFlatSmallWaterPoly(float fX, float fY, float fZ, RwRGBA const &color) +{ + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + SMALL_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 1.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + SMALL_SECTOR_SIZE, fY + SMALL_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 1.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 1.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + SMALL_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 1.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, color.alpha); + + + int32 iidx = TempBufferIndicesStored; + + TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2; + + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; +} + +void +CWaterLevel::RenderOneFlatLargeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color) +{ + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + LARGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 2.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + LARGE_SECTOR_SIZE, fY + LARGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 2.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 2.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, color.alpha); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + LARGE_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 2.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, color.alpha); + + + int32 iidx = TempBufferIndicesStored; + + TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2; + + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; +} + +void +CWaterLevel::RenderOneFlatHugeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color) +{ + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + HUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 4.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + HUGE_SECTOR_SIZE, fY + HUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 4.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 4.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + HUGE_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 4.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, 255); + + + int32 iidx = TempBufferIndicesStored; + + TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2; + + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; +} + +void +CWaterLevel::RenderOneFlatExtraHugeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color) +{ + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + EXTRAHUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 8.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + EXTRAHUGE_SECTOR_SIZE, fY + EXTRAHUGE_SECTOR_SIZE, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 8.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 8.0f); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + EXTRAHUGE_SECTOR_SIZE, fY, fZ - WATER_Z_OFFSET); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 8.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, 255); + + + int32 iidx = TempBufferIndicesStored; + + TempBufferRenderIndexList[iidx + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[iidx + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[iidx + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[iidx + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[iidx + 5] = TempBufferVerticesStored + 2; + + TempBufferVerticesStored += 4; + TempBufferIndicesStored += 6; +} + +void +CWaterLevel::RenderOneWavySector(float fX, float fY, float fZ, RwRGBA const &color, bool bUnk) +{ + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + + if ( !WavesCalculatedThisFrame ) + { + nGeomUsed = 0; + + WavesCalculatedThisFrame = true; + + CBoat::FillBoatList(); + + ASSERT( ms_pWavyAtomic != nil ); + + RpGeometry *geometry = RpAtomicGetGeometry(ms_pWavyAtomic); + + ASSERT( geometry != nil ); + + RwRGBA *wavyPreLights = RpGeometryGetPreLightColors(geometry); + RwTexCoords *wavyTexCoords = RpGeometryGetVertexTexCoords(geometry, rwTEXTURECOORDINATEINDEX0); + RwV3d *wavyVertices = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(geometry, 0)); + + ASSERT( wavyPreLights != nil ); + ASSERT( wavyTexCoords != nil ); + ASSERT( wavyVertices != nil ); + + RpGeometryLock(geometry, rpGEOMETRYLOCKVERTICES + | rpGEOMETRYLOCKPRELIGHT + | rpGEOMETRYLOCKTEXCOORDS); + + for ( int32 i = 0; i < 9; i++ ) + { + for ( int32 j = 0; j < 9; j++ ) + { + wavyTexCoords[9*i+j].u = float(i) / 8 + TEXTURE_ADDV; + wavyTexCoords[9*i+j].v = float(j) / 8 + TEXTURE_ADDU; + RwRGBAAssign(&wavyPreLights[9*i+j], &color); + + wavyVertices[9*i+j].z = ( CWeather::Wind * 0.7f + 0.3f ) + * ( Sin(float(i + j) * DEGTORAD(45.0f) + fAngle) ) + + ( CWeather::Wind * 0.2f * Sin(float(j - i) * PI + (2.0f * fAngle)) ); + } + } + + RpGeometryUnlock(geometry); + } + + static CBoat *apBoatList[4] = { nil }; + + if ( apGeomArray[0] + && nGeomUsed < MAX_BOAT_WAKES + && CBoat::IsSectorAffectedByWake( + CVector2D(fX + (SMALL_SECTOR_SIZE / 2), fY + (SMALL_SECTOR_SIZE / 2)), + SMALL_SECTOR_SIZE / 2, + apBoatList) ) + { + float fWakeColor = fAdd1 - Max(255.0f - float(color.blue + color.red + color.green) / 3, fAdd2); + + RpGeometry *wavyGeometry = RpAtomicGetGeometry(ms_pWavyAtomic); + RpGeometry *geom = apGeomArray[nGeomUsed++]; + + ASSERT( wavyGeometry != nil ); + ASSERT( geom != nil ); + + RpAtomic *atomic = RpAtomicCreate(); + ASSERT( atomic != nil ); + + RpAtomicSetGeometry(atomic, geom, 0); + + RwFrame *frame = RwFrameCreate(); + ASSERT( frame != nil ); + + RwMatrixCopy(RwFrameGetMatrix(frame), RwFrameGetMatrix(RpAtomicGetFrame(ms_pWavyAtomic))); + RpAtomicSetFrame(atomic, frame); + + RwTexCoords *geomTexCoords = RpGeometryGetVertexTexCoords(geom, rwTEXTURECOORDINATEINDEX0); + RwTexCoords *wavyTexCoord = RpGeometryGetVertexTexCoords(wavyGeometry, rwTEXTURECOORDINATEINDEX0); + RwRGBA *geomPreLights = RpGeometryGetPreLightColors(geom); + RwV3d *geomVertices = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(geom, 0)); + RwV3d *wavyVertices = RpMorphTargetGetVertices(RpGeometryGetMorphTarget(wavyGeometry, 0)); + + ASSERT( geomTexCoords != nil ); + ASSERT( wavyTexCoord != nil ); + ASSERT( geomPreLights != nil ); + ASSERT( geomVertices != nil ); + ASSERT( wavyVertices != nil ); + + RpGeometryLock(geom, rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKPRELIGHT | rpGEOMETRYLOCKTEXCOORDS); + + for ( int32 i = 0; i < 9; i++ ) + { + for ( int32 j = 0; j < 9; j++ ) + { + geomTexCoords[9*i+j] = wavyTexCoord[9*i+j]; + + float fVertexX = (float)i * 4.0f + fX; + float fVertexY = (float)j * 4.0f + fY; + + float fDistMult = 0.0f; + + for ( int32 k = 0; k < 4; k++ ) + { + if ( apBoatList[k] != nil ) + fDistMult += CBoat::IsVertexAffectedByWake(CVector(fVertexX, fVertexY, 0.0f), apBoatList[k]); + } + + if ( fDistMult > 0.0f ) + { + RwRGBA wakeColor; + + RwRGBAAssign(&wakeColor, &color); + + wakeColor.red = Min(color.red + int32(fWakeColor * fRedMult * fDistMult), 255); + wakeColor.green = Min(color.green + int32(fWakeColor * fGreenMult * fDistMult), 255); + wakeColor.blue = Min(color.blue + int32(fWakeColor * fBlueMult * fDistMult), 255); + + RwRGBAAssign(&geomPreLights[9*i+j], &wakeColor); + + } + else + RwRGBAAssign(&geomPreLights[9*i+j], &color); + + + geomVertices[9*i+j].z = wavyVertices[9*i+j].z; + } + } + + RpGeometryUnlock(geom); + + + RwV3d pos = {0.0f, 0.0f, 0.0f}; + + pos.x = fX; + pos.z = fZ; + pos.y = fY; + + RwFrameTranslate(RpAtomicGetFrame(atomic), &pos, rwCOMBINEREPLACE); + + RpAtomicRender(atomic); + + RpAtomicDestroy(atomic); + RwFrameDestroy(frame); + } + else + { + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + + pos.x = fX; + pos.y = fY; + pos.z = fZ; + + ASSERT( ms_pWavyAtomic != nil ); + + RwFrameTranslate(RpAtomicGetFrame(ms_pWavyAtomic), &pos, rwCOMBINEREPLACE); + + RpAtomicRender(ms_pWavyAtomic); + } +} + +float +CWaterLevel::CalcDistanceToWater(float fX, float fY) +{ + const float fSectorMaxRenderDist = 75.0f; + + int32 nStartX = WATER_TO_SMALL_SECTOR_X(fX - fSectorMaxRenderDist) - 1; + int32 nEndX = WATER_TO_SMALL_SECTOR_X(fX + fSectorMaxRenderDist) + 1; + int32 nStartY = WATER_TO_SMALL_SECTOR_Y(fY - fSectorMaxRenderDist) - 1; + int32 nEndY = WATER_TO_SMALL_SECTOR_Y(fY + fSectorMaxRenderDist) + 1; + + nStartX = Clamp(nStartX, 0, MAX_SMALL_SECTORS - 1); + nEndX = Clamp(nEndX, 0, MAX_SMALL_SECTORS - 1); + nStartY = Clamp(nStartY, 0, MAX_SMALL_SECTORS - 1); + nEndY = Clamp(nEndY, 0, MAX_SMALL_SECTORS - 1); + + float fDistSqr = 1.0e10f; + + for ( int32 x = nStartX; x <= nEndX; x++ ) + { + for ( int32 y = nStartY; y <= nEndY; y++ ) + { + if ( aWaterFineBlockList[x][y] >= 0 ) + { + float fSectorX = WATER_FROM_SMALL_SECTOR_X(x); + float fSectorY = WATER_FROM_SMALL_SECTOR_Y(y); + + CVector2D vecDist + ( + fSectorX + SMALL_SECTOR_SIZE - fX, + fSectorY + SMALL_SECTOR_SIZE - fY + ); + + fDistSqr = Min(vecDist.MagnitudeSqr(), fDistSqr); + } + } + } + + return Clamp(Sqrt(fDistSqr) - 23.0f, 0.0f, fSectorMaxRenderDist); +} + +void +CWaterLevel::RenderAndEmptyRenderBuffer() +{ + if ( TempBufferVerticesStored ) + { + LittleTest(); + + if ( RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV) ) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + } + + TempBufferIndicesStored = 0; + TempBufferVerticesStored = 0; +} + +void +CWaterLevel::AllocateBoatWakeArray() +{ + CStreaming::MakeSpaceFor(14 * CDSTREAM_SECTOR_SIZE); + + PUSH_MEMID(MEMID_STREAM); + + ASSERT(ms_pWavyAtomic != nil ); + + RpGeometry *wavyGeometry = RpAtomicGetGeometry(ms_pWavyAtomic); + ASSERT(wavyGeometry != nil ); + RpMorphTarget *wavyMorphTarget = RpGeometryGetMorphTarget(wavyGeometry, 0); + RpMaterial *wavyMaterial = RpGeometryGetMaterial(wavyGeometry, 0); + + ASSERT(wavyMorphTarget != nil ); + ASSERT(wavyMaterial != nil ); + + for ( int32 geom = 0; geom < MAX_BOAT_WAKES; geom++ ) + { + if ( apGeomArray[geom] == nil ) + { + apGeomArray[geom] = RpGeometryCreate(9*9, 8*8*2, rpGEOMETRYTRISTRIP + | rpGEOMETRYPRELIT + | rpGEOMETRYMODULATEMATERIALCOLOR + | rpGEOMETRYTEXTURED); + ASSERT(apGeomArray[geom] != nil); + + RpTriangle *geomTriangles = RpGeometryGetTriangles(apGeomArray[geom]); + + ASSERT( geomTriangles != nil ); + + for ( int32 i = 0; i < 8; i++ ) + { + for ( int32 j = 0; j < 8; j++ ) + { + + /* + [B] [C] + *********** + * * * + * * * + * * * + * * * + *********** + [A] [D] + */ + + + RpGeometryTriangleSetVertexIndices(apGeomArray[geom], + &geomTriangles[2 * 8*i + 2*j + 0], /*A*/i*9+j+0, /*B*/i*9+j+1, /*C*/i*9+j+9+1); + + RpGeometryTriangleSetVertexIndices(apGeomArray[geom], + &geomTriangles[2 * 8*i + 2*j + 1], /*A*/i*9+j+0, /*C*/i*9+j+9+1, /*D*/i*9+j+9 ); + + RpGeometryTriangleSetMaterial(apGeomArray[geom], &geomTriangles[2 * 8*i + 2*j + 0], wavyMaterial); + + RpGeometryTriangleSetMaterial(apGeomArray[geom], &geomTriangles[2 * 8*i + 2*j + 1], wavyMaterial); + } + } + + RpMorphTarget *geomMorphTarget = RpGeometryGetMorphTarget(apGeomArray[geom], 0); + RwV3d *geomVertices = RpMorphTargetGetVertices(geomMorphTarget); + + ASSERT( geomMorphTarget != nil ); + ASSERT( geomVertices != nil ); + + for ( int32 i = 0; i < 9; i++ ) + { + for ( int32 j = 0; j < 9; j++ ) + { + geomVertices[9*i+j].x = (float)i * 4.0f; + geomVertices[9*i+j].y = (float)j * 4.0f; + geomVertices[9*i+j].z = 0.0f; + } + } + + RpMorphTargetSetBoundingSphere(geomMorphTarget, RpMorphTargetGetBoundingSphere(wavyMorphTarget)); + RpGeometryUnlock(apGeomArray[geom]); + } + } + + POP_MEMID(); +} + +void +CWaterLevel::FreeBoatWakeArray() +{ + for ( int32 i = 0; i < MAX_BOAT_WAKES; i++ ) + { + if ( apGeomArray[i] != nil ) + { + RpGeometryDestroy(apGeomArray[i]); + apGeomArray[i] = nil; + } + } + + nGeomUsed = 0; +} diff --git a/src/renderer/WaterLevel.h b/src/renderer/WaterLevel.h new file mode 100644 index 0000000..b797f25 --- /dev/null +++ b/src/renderer/WaterLevel.h @@ -0,0 +1,103 @@ +#pragma once + +#define WATER_Z_OFFSET (1.5f) + +#define NO_WATER -128 + +#define MAX_SMALL_SECTORS 128 +#define MAX_LARGE_SECTORS 64 +#define MAX_HUGE_SECTORS 32 +#define MAX_EXTRAHUGE_SECTORS 16 + +#define SMALL_SECTOR_SIZE 32 +#define LARGE_SECTOR_SIZE 64 +#define HUGE_SECTOR_SIZE 128 +#define EXTRAHUGE_SECTOR_SIZE 256 + +#define WATER_START_X -2048.0f +#define WATER_END_X 2048.0f + +#define WATER_START_Y -2048.0f +#define WATER_END_Y 2048.0f + +#define WATER_WIDTH ((WATER_END_X - WATER_START_X)) +#define WATER_HEIGHT ((WATER_END_Y - WATER_START_Y)) + +#define WATER_UNSIGN_X(x) ( (x) + (WATER_WIDTH /2) ) +#define WATER_UNSIGN_Y(y) ( (y) + (WATER_HEIGHT/2) ) +#define WATER_SIGN_X(x) ( (x) - (WATER_WIDTH /2) ) +#define WATER_SIGN_Y(y) ( (y) - (WATER_HEIGHT/2) ) + +// 32 +#define WATER_SMALL_X(x) ( WATER_UNSIGN_X(x) / MAX_SMALL_SECTORS ) +#define WATER_SMALL_Y(y) ( WATER_UNSIGN_Y(y) / MAX_SMALL_SECTORS ) +#define WATER_FROM_SMALL_SECTOR_X(x) ( ((x) - (MAX_SMALL_SECTORS/2) ) * SMALL_SECTOR_SIZE ) +#define WATER_FROM_SMALL_SECTOR_Y(y) ( ((y) - (MAX_SMALL_SECTORS/2) ) * SMALL_SECTOR_SIZE ) +#define WATER_TO_SMALL_SECTOR_X(x) ( WATER_UNSIGN_X(x) / SMALL_SECTOR_SIZE ) +#define WATER_TO_SMALL_SECTOR_Y(y) ( WATER_UNSIGN_Y(y) / SMALL_SECTOR_SIZE ) + +// 64 +#define WATER_LARGE_X(x) ( WATER_UNSIGN_X(x) / MAX_LARGE_SECTORS ) +#define WATER_LARGE_Y(y) ( WATER_UNSIGN_Y(y) / MAX_LARGE_SECTORS ) +#define WATER_FROM_LARGE_SECTOR_X(x) ( ((x) - (MAX_LARGE_SECTORS/2) ) * LARGE_SECTOR_SIZE ) +#define WATER_FROM_LARGE_SECTOR_Y(y) ( ((y) - (MAX_LARGE_SECTORS/2) ) * LARGE_SECTOR_SIZE ) +#define WATER_TO_LARGE_SECTOR_X(x) ( WATER_UNSIGN_X(x) / LARGE_SECTOR_SIZE ) +#define WATER_TO_LARGE_SECTOR_Y(y) ( WATER_UNSIGN_Y(y) / LARGE_SECTOR_SIZE ) + +// 128 +#define WATER_HUGE_X(x) ( WATER_UNSIGN_X(x) / MAX_HUGE_SECTORS ) +#define WATER_HUGE_Y(y) ( WATER_UNSIGN_Y(y) / MAX_HUGE_SECTORS ) +#define WATER_FROM_HUGE_SECTOR_X(x) ( ((x) - (MAX_HUGE_SECTORS/2) ) * HUGE_SECTOR_SIZE ) +#define WATER_FROM_HUGE_SECTOR_Y(y) ( ((y) - (MAX_HUGE_SECTORS/2) ) * HUGE_SECTOR_SIZE ) +#define WATER_TO_HUGE_SECTOR_X(x) ( WATER_UNSIGN_X(x) / HUGE_SECTOR_SIZE ) +#define WATER_TO_HUGE_SECTOR_Y(y) ( WATER_UNSIGN_Y(y) / HUGE_SECTOR_SIZE ) + +// 256 +#define WATER_EXTRAHUGE_X(x) ( WATER_UNSIGN_X(x) / MAX_EXTRAHUGE_SECTORS ) +#define WATER_EXTRAHUGE_Y(y) ( WATER_UNSIGN_Y(y) / MAX_EXTRAHUGE_SECTORS ) +#define WATER_FROM_EXTRAHUGE_SECTOR_X(x) ( ((x) - (MAX_EXTRAHUGE_SECTORS/2)) * EXTRAHUGE_SECTOR_SIZE ) +#define WATER_FROM_EXTRAHUGE_SECTOR_Y(y) ( ((y) - (MAX_EXTRAHUGE_SECTORS/2)) * EXTRAHUGE_SECTOR_SIZE ) +#define WATER_TO_EXTRAHUGE_SECTOR_X(x) ( WATER_UNSIGN_X(x) / EXTRAHUGE_SECTOR_SIZE ) +#define WATER_TO_EXTRAHUGE_SECTOR_Y(y) ( WATER_UNSIGN_Y(y) / EXTRAHUGE_SECTOR_SIZE ) + + +#define MAX_BOAT_WAKES 8 + +extern RwRaster* gpWaterRaster; +extern bool gbDontRenderWater; + +class CWaterLevel +{ + static int32 ms_nNoOfWaterLevels; + static float ms_aWaterZs[48]; + static CRect ms_aWaterRects[48]; + static int8 aWaterBlockList[MAX_LARGE_SECTORS][MAX_LARGE_SECTORS]; + static int8 aWaterFineBlockList[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS]; + static bool WavesCalculatedThisFrame; + static RpAtomic *ms_pWavyAtomic; + static RpGeometry *apGeomArray[MAX_BOAT_WAKES]; + static int16 nGeomUsed; + +public: + static void Initialise(Const char *pWaterDat); // out of class in III PC and later because of SecuROM + static void Shutdown(); + static void CreateWavyAtomic(); + static void DestroyWavyAtomic(); + static void AddWaterLevel(float fXLeft, float fYBottom, float fXRight, float fYTop, float fLevel); + static bool WaterLevelAccordingToRectangles(float fX, float fY, float *pfOutLevel = nil); + static bool TestVisibilityForFineWaterBlocks(const CVector &worldPos); + static void RemoveIsolatedWater(); + static bool GetWaterLevel(float fX, float fY, float fZ, float *pfOutLevel, bool bDontCheckZ); + static bool GetWaterLevel(CVector coors, float *pfOutLevel, bool bDontCheckZ) { return GetWaterLevel(coors.x, coors.y, coors.z, pfOutLevel, bDontCheckZ); } + static bool GetWaterLevelNoWaves(float fX, float fY, float fZ, float *pfOutLevel); + static void RenderWater(); + static void RenderOneFlatSmallWaterPoly (float fX, float fY, float fZ, RwRGBA const &color); + static void RenderOneFlatLargeWaterPoly (float fX, float fY, float fZ, RwRGBA const &color); + static void RenderOneFlatHugeWaterPoly (float fX, float fY, float fZ, RwRGBA const &color); + static void RenderOneFlatExtraHugeWaterPoly(float fX, float fY, float fZ, RwRGBA const &color); + static void RenderOneWavySector (float fX, float fY, float fZ, RwRGBA const &color, bool bUnk = false); + static float CalcDistanceToWater(float fX, float fY); + static void RenderAndEmptyRenderBuffer(); + static void AllocateBoatWakeArray(); + static void FreeBoatWakeArray(); +}; diff --git a/src/renderer/Weather.cpp b/src/renderer/Weather.cpp new file mode 100644 index 0000000..e57d57d --- /dev/null +++ b/src/renderer/Weather.cpp @@ -0,0 +1,552 @@ +#include "common.h" + +#include "Weather.h" + +#include "Camera.h" +#include "Clock.h" +#include "CutsceneMgr.h" +#include "DMAudio.h" +#include "General.h" +#include "Pad.h" +#include "Particle.h" +#include "RenderBuffer.h" +#include "Stats.h" +#include "Shadows.h" +#include "Timecycle.h" +#include "Timer.h" +#include "Vehicle.h" +#include "World.h" +#include "ZoneCull.h" + +int32 CWeather::SoundHandle = -1; + +int32 CWeather::WeatherTypeInList; +int16 CWeather::OldWeatherType; +int16 CWeather::NewWeatherType; +int16 CWeather::ForcedWeatherType; + +bool CWeather::LightningFlash; +bool CWeather::LightningBurst; +uint32 CWeather::LightningStart; +uint32 CWeather::LightningFlashLastChange; +uint32 CWeather::WhenToPlayLightningSound; +uint32 CWeather::LightningDuration; + +float CWeather::Foggyness; +float CWeather::CloudCoverage; +float CWeather::Wind; +float CWeather::Rain; +float CWeather::InterpolationValue; +float CWeather::WetRoads; +float CWeather::Rainbow; + +bool CWeather::bScriptsForceRain; +bool CWeather::Stored_StateStored; + +float CWeather::Stored_InterpolationValue; +int16 CWeather::Stored_OldWeatherType; +int16 CWeather::Stored_NewWeatherType; +float CWeather::Stored_Rain; + +tRainStreak Streaks[NUM_RAIN_STREAKS]; + +const int16 WeatherTypesList[] = { + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_CLOUDY, WEATHER_CLOUDY, WEATHER_RAINY, WEATHER_RAINY, + WEATHER_CLOUDY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_CLOUDY, WEATHER_FOGGY, WEATHER_FOGGY, WEATHER_CLOUDY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_CLOUDY, WEATHER_CLOUDY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_CLOUDY, WEATHER_CLOUDY, WEATHER_RAINY, WEATHER_RAINY, + WEATHER_CLOUDY, WEATHER_RAINY, WEATHER_CLOUDY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_FOGGY, WEATHER_FOGGY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_RAINY, WEATHER_CLOUDY, +}; + +const float Windyness[] = { + 0.0f, // WEATHER_SUNNY + 0.7f, // WEATHER_CLOUDY + 1.0f, // WEATHER_RAINY + 0.5f // WEATHER_FOGGY +}; + +#define MIN_TIME_BETWEEN_LIGHTNING_FLASH_CHANGES (50) + +#define RAIN_CHANGE_SPEED (0.003f) + +#define DROPLETS_LEFT_OFFSET (10.0f) +#define DROPLETS_RIGHT_OFFSET (10.0f) +#define DROPLETS_TOP_OFFSET (10.0f) +#define DROPLETS_BOTTOM_OFFSET (10.0f) + +#define STREAK_U (10.0f) +#define STREAK_V (18.0f) +#define LARGE_STREAK_COEFFICIENT (1.23f) +#define STREAK_MIN_DISTANCE (8.0f) +#define STREAK_MAX_DISTANCE (16.0f) + +#define SPLASH_CHECK_RADIUS (7.0f) +#define SPLASH_OFFSET_RADIUS (2.0f) + +#define STREAK_LIFETIME (4.0f) +#define STREAK_INTEROLATION_TIME (0.3f) + +#define RAIN_COLOUR_R (200) +#define RAIN_COLOUR_G (200) +#define RAIN_COLOUR_B (256) +#define RAIN_ALPHA (255) + +void CWeather::Init(void) +{ + NewWeatherType = WEATHER_SUNNY; + bScriptsForceRain = false; + OldWeatherType = WEATHER_CLOUDY; + Stored_StateStored = false; + InterpolationValue = 0.0f; + WhenToPlayLightningSound = 0; + WeatherTypeInList = 0; + ForcedWeatherType = WEATHER_RANDOM; + SoundHandle = DMAudio.CreateEntity(AUDIOTYPE_WEATHER, (void*)1); + if (SoundHandle >= 0) + DMAudio.SetEntityStatus(SoundHandle, TRUE); +} + +void CWeather::Update(void) +{ + float fNewInterpolation = CClock::GetMinutes() * 1.0f / 60; + if (fNewInterpolation < InterpolationValue) { + // new hour + OldWeatherType = NewWeatherType; + if (ForcedWeatherType >= 0) + NewWeatherType = ForcedWeatherType; + else { + WeatherTypeInList = (WeatherTypeInList + 1) % ARRAY_SIZE(WeatherTypesList); + NewWeatherType = WeatherTypesList[WeatherTypeInList]; +#ifdef FIX_BUGS + } + if (NewWeatherType == WEATHER_RAINY) + CStats::mmRain += CGeneral::GetRandomNumber() & 7; +#else + if (NewWeatherType == WEATHER_RAINY) + CStats::mmRain += CGeneral::GetRandomNumber() & 7; + } +#endif + } + InterpolationValue = fNewInterpolation; + if (CPad::GetPad(1)->GetRightShockJustDown()) { + NewWeatherType = (NewWeatherType + 1) % WEATHER_TOTAL; + OldWeatherType = NewWeatherType; + } + + // Lightning + if (NewWeatherType != WEATHER_RAINY || OldWeatherType != WEATHER_RAINY) { + LightningFlash = false; + LightningBurst = false; + } + else{ + if (LightningBurst) { + if ((CGeneral::GetRandomNumber() & 255) >= 32) { + // 0.875 probability + if (CTimer::GetTimeInMilliseconds() - LightningFlashLastChange > MIN_TIME_BETWEEN_LIGHTNING_FLASH_CHANGES) { + bool bOldLightningFlash = LightningFlash; + LightningFlash = CGeneral::GetRandomTrueFalse(); + if (LightningFlash != bOldLightningFlash) + LightningFlashLastChange = CTimer::GetTimeInMilliseconds(); + } + } + else { + // 0.125 probability + LightningBurst = false; + LightningDuration = Min(CTimer::GetFrameCounter() - LightningStart, 20); + LightningFlash = false; + WhenToPlayLightningSound = CTimer::GetTimeInMilliseconds() + 150 * (20 - LightningDuration); + } + } + else { + if (CGeneral::GetRandomNumber() >= 200) { + // lower probability on PC due to randomness bug + LightningFlash = false; + } + else { + LightningBurst = true; + LightningStart = CTimer::GetFrameCounter(); + LightningFlashLastChange = CTimer::GetTimeInMilliseconds(); + LightningFlash = true; + } + } + } + if (WhenToPlayLightningSound && CTimer::GetTimeInMilliseconds() > WhenToPlayLightningSound) { + DMAudio.PlayOneShot(SoundHandle, SOUND_LIGHTNING, LightningDuration); + CPad::GetPad(0)->StartShake(40 * LightningDuration + 100, 2 * LightningDuration + 80); + WhenToPlayLightningSound = 0; + } + + // Wet roads + if (OldWeatherType == WEATHER_RAINY) { + if (NewWeatherType == WEATHER_RAINY) + WetRoads = 1.0f; + else + WetRoads = 1.0f - InterpolationValue; + } + else { + if (NewWeatherType == WEATHER_RAINY) + WetRoads = InterpolationValue; + else + WetRoads = 0.0f; + } + + // Rain +#ifndef VC_RAIN_NERF + float fNewRain; + if (NewWeatherType == WEATHER_RAINY) { + // if raining for >1 hour, values: 0, 0.33, 0.66, 0.99, switching every ~16.5s + fNewRain = ((uint16)CTimer::GetTimeInMilliseconds() >> 14) * 0.33f; + if (OldWeatherType != WEATHER_RAINY) { + if (InterpolationValue < 0.4f) + // if rain has just started (<24 minutes), always 0.5 + fNewRain = 0.5f; + else + // if rain is ongoing for >24 minutes, values: 0.25, 0.5, 0.75, 1.0, switching every ~16.5s + fNewRain = 0.25f + ((uint16)CTimer::GetTimeInMilliseconds() >> 14) * 0.25f; + } + } + else + fNewRain = 0.0f; + if (Rain != fNewRain) { // ok to use comparasion + if (Rain < fNewRain) + Rain = Min(fNewRain, Rain + RAIN_CHANGE_SPEED * CTimer::GetTimeStep()); + else + Rain = Max(fNewRain, Rain - RAIN_CHANGE_SPEED * CTimer::GetTimeStep()); + } +#else + float fNewRain; + if (NewWeatherType == WEATHER_RAINY) { + // if raining for >1 hour, values: 0, 0.33, switching every ~16.5s + fNewRain = (((uint16)CTimer::GetTimeInMilliseconds() >> 14) & 1) * 0.33f; + if (OldWeatherType != WEATHER_RAINY) { + if (InterpolationValue < 0.4f) + // if rain has just started (<24 minutes), always 0.5 + fNewRain = 0.5f; + else + // if rain is ongoing for >24 minutes, values: 0.25, 0.5, switching every ~16.5s + fNewRain = 0.25f + (((uint16)CTimer::GetTimeInMilliseconds() >> 14) & 1) * 0.25f; + } + fNewRain = Max(fNewRain, 0.5f); + } + else + fNewRain = 0.0f; + Rain = fNewRain; +#endif + + // Clouds + if (OldWeatherType != WEATHER_SUNNY) + CloudCoverage = 1.0f - InterpolationValue; + else + CloudCoverage = 0.0f; + if (NewWeatherType != WEATHER_SUNNY) + CloudCoverage += InterpolationValue; + + // Fog + if (OldWeatherType == WEATHER_FOGGY) + Foggyness = 1.0f - InterpolationValue; + else + Foggyness = 0.0f; + if (NewWeatherType == WEATHER_FOGGY) + Foggyness += InterpolationValue; + if (OldWeatherType == WEATHER_RAINY && NewWeatherType == WEATHER_SUNNY && InterpolationValue < 0.5f && CClock::GetHours() > 6 && CClock::GetHours() < 21) + Rainbow = 1.0f - 4.0f * Abs(InterpolationValue - 0.25f) / 4.0f; + else + Rainbow = 0.0f; + Wind = InterpolationValue * Windyness[NewWeatherType] + (1.0f - InterpolationValue) * Windyness[OldWeatherType]; + AddRain(); +} + +void CWeather::ForceWeather(int16 weather) +{ + ForcedWeatherType = weather; +} + +void CWeather::ForceWeatherNow(int16 weather) +{ + OldWeatherType = weather; + NewWeatherType = weather; + ForcedWeatherType = weather; +} + +void CWeather::ReleaseWeather() +{ + ForcedWeatherType = -1; +} + +void CWeather::AddRain() +{ + if (CCullZones::CamNoRain() || CCullZones::PlayerNoRain()) + return; + if (TheCamera.GetLookingLRBFirstPerson()) { + CVehicle* pVehicle = FindPlayerVehicle(); + if (pVehicle && pVehicle->CarHasRoof()) { + CParticle::RemovePSystem(PARTICLE_RAINDROP_2D); + return; + } + } + if (Rain <= 0.1f) + return; + static RwRGBA colour; + float screen_width = SCREEN_WIDTH; + float screen_height = SCREEN_HEIGHT; + int cur_frame = (int)(3 * Rain) & 3; + int num_drops = (int)(2 * Rain) + 2; + static int STATIC_RAIN_ANGLE = -45; + static int count = 1500; + static int add_angle = 1; + if (--count == 0) { + count = 1; + if (add_angle) { + STATIC_RAIN_ANGLE += 12; + if (STATIC_RAIN_ANGLE > 45) { + count = 1500; + add_angle = !add_angle; + } + } + else { + STATIC_RAIN_ANGLE -= 12; + if (STATIC_RAIN_ANGLE < -45) { + count = 1500; + add_angle = !add_angle; + } + } + } + float rain_angle = DEGTORAD(STATIC_RAIN_ANGLE + ((STATIC_RAIN_ANGLE < 0) ? 360 : 0)); + float sin_angle = Sin(rain_angle); + float cos_angle = Cos(rain_angle); + float base_x = 0.0f * cos_angle - 1.0f * sin_angle; + float base_y = 1.0f * cos_angle + 0.0f * sin_angle; + CVector xpos(0.0f, 0.0f, 0.0f); + for (int i = 0; i < 2 * num_drops; i++) { + CVector dir; + dir.x = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_x) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.y = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_y) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.z = 0; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, xpos, dir, nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f), + colour, 0, rain_angle + CGeneral::GetRandomNumberInRange(-10, 10), cur_frame); + xpos.x += screen_width / (2 * num_drops); + xpos.x += CGeneral::GetRandomNumberInRange(-25.0f, 25.0f); + } + CVector ypos(0.0f, 0.0f, 0.0f); + for (int i = 0; i < num_drops; i++) { + CVector dir; + dir.x = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_x) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.y = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_y) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.z = 0; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, ypos, dir, nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f), + colour, 0, rain_angle + CGeneral::GetRandomNumberInRange(-10, 10), cur_frame); + ypos.y += screen_width / num_drops; + ypos.y += CGeneral::GetRandomNumberInRange(-25.0f, 25.0f); + } + CVector ypos2(0.0f, 0.0f, 0.0f); + for (int i = 0; i < num_drops; i++) { + CVector dir; + dir.x = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_x) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.y = (CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) + base_y) * CGeneral::GetRandomNumberInRange(10.0f, 25.0f); + dir.z = 0; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, ypos2, dir, nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f), + colour, 0, rain_angle + CGeneral::GetRandomNumberInRange(-10, 10), cur_frame); + ypos2.y += screen_width / num_drops; + ypos2.y += CGeneral::GetRandomNumberInRange(-25.0f, 25.0f); + } + for (int i = 0; i < num_drops; i++) { + CVector pos; + pos.x = CGeneral::GetRandomNumberInRange(DROPLETS_LEFT_OFFSET, screen_width - DROPLETS_RIGHT_OFFSET); + pos.y = CGeneral::GetRandomNumberInRange(DROPLETS_TOP_OFFSET, screen_height - DROPLETS_TOP_OFFSET); + pos.z = 0.0f; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, pos, CVector(0.0f, 0.0f, 0.0f), nil, CGeneral::GetRandomNumberInRange(0.5f, 0.9f), + colour, CGeneral::GetRandomNumberInRange(-10, 10), 360 - rain_angle + CGeneral::GetRandomNumberInRange(-30, 30), cur_frame, 50); + } + int num_splash_attempts = (int)(3 * Rain) + 1; + int num_splashes = (int)(3 * Rain) + 4; + CVector splash_points[4]; + splash_points[0] = CVector(-RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) * + RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude(); + splash_points[1] = CVector(RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) * + RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude(); + splash_points[2] = 4.0f * CVector(-RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) * + RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude(); + splash_points[3] = 4.0f * CVector(RwCameraGetViewWindow(TheCamera.m_pRwCamera)->x, RwCameraGetViewWindow(TheCamera.m_pRwCamera)->y, 1.0f) * + RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) / (RwCameraGetFarClipPlane(TheCamera.m_pRwCamera) * *(CVector2D*)RwCameraGetViewWindow(TheCamera.m_pRwCamera)).Magnitude(); + RwV3dTransformPoints(splash_points, splash_points, 4, RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera))); + CVector fp = (splash_points[0] + splash_points[1] + splash_points[2] + splash_points[3]) / 4; + for (int i = 0; i < num_splash_attempts; i++) { + CColPoint point; + CEntity* entity; + CVector np = fp + CVector(CGeneral::GetRandomNumberInRange(-SPLASH_CHECK_RADIUS, SPLASH_CHECK_RADIUS), CGeneral::GetRandomNumberInRange(-SPLASH_CHECK_RADIUS, SPLASH_CHECK_RADIUS), 0.0f); + if (CWorld::ProcessVerticalLine(np + CVector(0.0f, 0.0f, 40.0f), -40.0f, point, entity, true, false, false, false, true, false, nil)) { + for (int j = 0; j < num_splashes; j++) + CParticle::AddParticle((CGeneral::GetRandomTrueFalse() ? PARTICLE_RAIN_SPLASH : PARTICLE_RAIN_SPLASHUP), + CVector( + np.x + CGeneral::GetRandomNumberInRange(-SPLASH_OFFSET_RADIUS, SPLASH_OFFSET_RADIUS), + np.y + CGeneral::GetRandomNumberInRange(-SPLASH_OFFSET_RADIUS, SPLASH_OFFSET_RADIUS), + point.point.z + 0.1f), + CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, colour); + } + } +} + +void RenderOneRainStreak(CVector pos, CVector unused, int intensity, bool scale, float distance) +{ + static float RandomTex; + static float RandomTexX; + static float RandomTexY; + TempBufferRenderIndexList[TempBufferIndicesStored + 0] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[TempBufferIndicesStored + 1] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[TempBufferIndicesStored + 2] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[TempBufferIndicesStored + 3] = TempBufferVerticesStored + 0; + TempBufferRenderIndexList[TempBufferIndicesStored + 4] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[TempBufferIndicesStored + 5] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[TempBufferIndicesStored + 6] = TempBufferVerticesStored + 1; + TempBufferRenderIndexList[TempBufferIndicesStored + 7] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[TempBufferIndicesStored + 8] = TempBufferVerticesStored + 4; + TempBufferRenderIndexList[TempBufferIndicesStored + 9] = TempBufferVerticesStored + 2; + TempBufferRenderIndexList[TempBufferIndicesStored + 10] = TempBufferVerticesStored + 3; + TempBufferRenderIndexList[TempBufferIndicesStored + 11] = TempBufferVerticesStored + 4; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 0], 0, 0, 0, 0); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 0], pos.x + 11.0f * TheCamera.GetUp().x, pos.y + 11.0f * TheCamera.GetUp().y, pos.z + 11.0f * TheCamera.GetUp().z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 1], 0, 0, 0, 0); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 1], pos.x - 9.0f * TheCamera.GetRight().x, pos.y - 9.0f * TheCamera.GetRight().y, pos.z - 9.0f * TheCamera.GetRight().z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 2], RAIN_COLOUR_R * intensity / 256, RAIN_COLOUR_G * intensity / 256, RAIN_COLOUR_B * intensity / 256, RAIN_ALPHA); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 2], pos.x, pos.y, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 3], 0, 0, 0, 0); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 3], pos.x + 9.0f * TheCamera.GetRight().x, pos.y + 9.0f * TheCamera.GetRight().y, pos.z + 9.0f * TheCamera.GetRight().z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored + 4], 0, 0, 0, 0); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored + 4], pos.x - 11.0f * TheCamera.GetUp().x, pos.y - 11.0f * TheCamera.GetUp().y, pos.z - 11.0f * TheCamera.GetUp().z); + float u = STREAK_U; + float v = STREAK_V; + if (scale) { + u *= LARGE_STREAK_COEFFICIENT; + v *= LARGE_STREAK_COEFFICIENT; + } + float distance_coefficient; + if (distance < STREAK_MIN_DISTANCE) + distance_coefficient = 1.0f; + else if (distance > STREAK_MAX_DISTANCE) + distance_coefficient = 0.5f; + else + distance_coefficient = 1.0f - 0.5f * (distance - STREAK_MIN_DISTANCE) / (STREAK_MAX_DISTANCE - STREAK_MIN_DISTANCE); + u *= distance_coefficient; + v *= distance_coefficient; + if (!CTimer::GetIsPaused()) { + RandomTex = ((CGeneral::GetRandomNumber() & 255) - 128) * 0.01f; + RandomTexX = (CGeneral::GetRandomNumber() & 127) * 0.01f; + RandomTexY = (CGeneral::GetRandomNumber() & 127) * 0.01f; + } + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 0], 0.5f * u - RandomTex + RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 0], -v * 0.5f + RandomTexY); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 1], RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 1], RandomTexY); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 2], 0.5f * u + RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 2], RandomTexY); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 3], u + RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 3], RandomTexY); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored + 4], 0.5f * u + RandomTex + RandomTexX); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored + 5], 0.5f * v + RandomTexY); + TempBufferIndicesStored += 12; + TempBufferVerticesStored += 5; +} + +void CWeather::RenderRainStreaks(void) +{ + if (CTimer::GetIsCodePaused()) + return; + int base_intensity = (64.0f - CTimeCycle::GetFogReduction()) / 64.0f * int(255 * Rain); + if (base_intensity == 0) + return; + TempBufferIndicesStored = 0; + TempBufferVerticesStored = 0; + for (int i = 0; i < NUM_RAIN_STREAKS; i++) { + if (Streaks[i].timer) { + float secondsElapsed = (CTimer::GetTimeInMilliseconds() - Streaks[i].timer) / 1024.0f; + if (secondsElapsed > STREAK_LIFETIME) + Streaks[i].timer = 0; + else{ + int intensity; + if (secondsElapsed < STREAK_INTEROLATION_TIME) + intensity = base_intensity * 0.5f * secondsElapsed / STREAK_INTEROLATION_TIME; + else if (secondsElapsed > (STREAK_LIFETIME - STREAK_INTEROLATION_TIME)) + intensity = (STREAK_LIFETIME - secondsElapsed) * 0.5f * base_intensity / STREAK_INTEROLATION_TIME; + else + intensity = base_intensity * 0.5f; + CVector dir = Streaks[i].direction; + dir.Normalise(); + CVector pos = Streaks[i].position + secondsElapsed * Streaks[i].direction; + RenderOneRainStreak(pos, dir, intensity, false, (pos - TheCamera.GetPosition()).Magnitude()); +#ifndef FIX_BUGS // remove useless code + if (secondsElapsed > 1.0f && secondsElapsed < STREAK_LIFETIME - 1.0f) { + CGeneral::GetRandomNumber(), CGeneral::GetRandomNumber(); + } +#endif + } + } + else if ((CGeneral::GetRandomNumber() & 0xF00) == 0){ + // 1/16 probability + Streaks[i].direction = CVector(4.0f, 4.0f, -4.0f); + Streaks[i].position = 6.0f * TheCamera.GetForward() + TheCamera.GetPosition() + CVector(-1.8f * Streaks[i].direction.x, -1.8f * Streaks[i].direction.y, 8.0f); + if (!CCutsceneMgr::IsRunning()) { + Streaks[i].position.x += 2.0f * FindPlayerSpeed().x * 60.0f; + Streaks[i].position.y += 2.0f * FindPlayerSpeed().y * 60.0f; + } + else + Streaks[i].position += (TheCamera.GetPosition() - TheCamera.m_RealPreviousCameraPosition) * 20.0f; + Streaks[i].position.x += ((CGeneral::GetRandomNumber() & 255) - 128) * 0.08f; + Streaks[i].position.y += ((CGeneral::GetRandomNumber() & 255) - 128) * 0.08f; + Streaks[i].timer = CTimer::GetTimeInMilliseconds(); + } + } + if (TempBufferIndicesStored){ + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEFOGTYPE, (void*)rwFOGTYPELINEAR); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpRainDropTex[3])); + if (RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, 1)) + { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + } + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; +} + +void CWeather::StoreWeatherState() +{ + Stored_StateStored = true; + Stored_InterpolationValue = InterpolationValue; + Stored_Rain = Rain; + Stored_NewWeatherType = NewWeatherType; + Stored_OldWeatherType = OldWeatherType; +} + +void CWeather::RestoreWeatherState() +{ +#ifdef FIX_BUGS // it's not used anyway though + Stored_StateStored = false; +#endif + InterpolationValue = Stored_InterpolationValue; + Rain = Stored_Rain; + NewWeatherType = Stored_NewWeatherType; + OldWeatherType = Stored_OldWeatherType; +} diff --git a/src/renderer/Weather.h b/src/renderer/Weather.h new file mode 100644 index 0000000..9c67031 --- /dev/null +++ b/src/renderer/Weather.h @@ -0,0 +1,71 @@ +enum { + WEATHER_SUNNY, + WEATHER_CLOUDY, + WEATHER_RAINY, + WEATHER_FOGGY +}; + +class CWeather +{ +public: + enum { + WEATHER_RANDOM = -1, + WEATHER_SUNNY = 0, + WEATHER_CLOUDY = 1, + WEATHER_RAINY = 2, + WEATHER_FOGGY = 3, + WEATHER_TOTAL = 4 + }; + static int32 SoundHandle; + + static int32 WeatherTypeInList; + static int16 OldWeatherType; + static int16 NewWeatherType; + static int16 ForcedWeatherType; + + static bool LightningFlash; + static bool LightningBurst; + static uint32 LightningStart; + static uint32 LightningFlashLastChange; + static uint32 WhenToPlayLightningSound; + static uint32 LightningDuration; + + static float Foggyness; + static float CloudCoverage; + static float Wind; + static float Rain; + static float InterpolationValue; + static float WetRoads; + static float Rainbow; + + static bool bScriptsForceRain; + static bool Stored_StateStored; + static float Stored_InterpolationValue; + static int16 Stored_OldWeatherType; + static int16 Stored_NewWeatherType; + static float Stored_Rain; + + static void RenderRainStreaks(void); + static void Update(void); + static void Init(void); + + static void ReleaseWeather(); + static void ForceWeather(int16); + static void ForceWeatherNow(int16); + static void StoreWeatherState(); + static void RestoreWeatherState(); + static void AddRain(); +}; + +enum { + NUM_RAIN_STREAKS = 35 +}; + +struct tRainStreak +{ + CVector position; + CVector direction; + uint32 timer; +}; + +extern RwTexture* gpRainDropTex[4]; \ No newline at end of file diff --git a/src/rw/ClumpRead.cpp b/src/rw/ClumpRead.cpp new file mode 100644 index 0000000..5f50f52 --- /dev/null +++ b/src/rw/ClumpRead.cpp @@ -0,0 +1,224 @@ +#include "common.h" + + +struct rpGeometryList +{ + RpGeometry **geometries; + int32 numGeoms; +}; + +struct rpAtomicBinary +{ + RwInt32 frameIndex; + RwInt32 geomIndex; + RwInt32 flags; + RwInt32 unused; +}; + +static int32 numberGeometrys; +static int32 streamPosition; +static rpGeometryList gGeomList; +static rwFrameList gFrameList; +static RpClumpChunkInfo gClumpInfo; + +rpGeometryList* +GeometryListStreamRead1(RwStream *stream, rpGeometryList *geomlist) +{ + int i; + RwUInt32 size, version; + RwInt32 numGeoms; + + numberGeometrys = 0; + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return nil; + assert(size == 4); + if(RwStreamRead(stream, &numGeoms, 4) != 4) + return nil; + + numberGeometrys = numGeoms/2; + geomlist->numGeoms = numGeoms; + if(geomlist->numGeoms > 0){ + geomlist->geometries = (RpGeometry**)RwMalloc(geomlist->numGeoms * sizeof(RpGeometry*)); + if(geomlist->geometries == nil) + return nil; + memset(geomlist->geometries, 0, geomlist->numGeoms * sizeof(RpGeometry*)); + }else + geomlist->geometries = nil; + + for(i = 0; i < numberGeometrys; i++){ + if(!RwStreamFindChunk(stream, rwID_GEOMETRY, nil, &version)) + return nil; + geomlist->geometries[i] = RpGeometryStreamRead(stream); + if(geomlist->geometries[i] == nil) + return nil; + } + + return geomlist; +} + +rpGeometryList* +GeometryListStreamRead2(RwStream *stream, rpGeometryList *geomlist) +{ + int i; + RwUInt32 version; + + for(i = numberGeometrys; i < geomlist->numGeoms; i++){ + if(!RwStreamFindChunk(stream, rwID_GEOMETRY, nil, &version)) + return nil; + geomlist->geometries[i] = RpGeometryStreamRead(stream); + if(geomlist->geometries[i] == nil) + return nil; + } + + return geomlist; +} + +void +GeometryListDeinitialize(rpGeometryList *geomlist) +{ + int i; + + for(i = 0; i < geomlist->numGeoms; i++) + if(geomlist->geometries[i]) + RpGeometryDestroy(geomlist->geometries[i]); + + if(geomlist->numGeoms){ + RwFree(geomlist->geometries); + geomlist->numGeoms = 0; + } +} + +RpAtomic* +ClumpAtomicStreamRead(RwStream *stream, rwFrameList *frmList, rpGeometryList *geomList) +{ + RwUInt32 size, version; + rpAtomicBinary a; + RpAtomic *atomic; + + numberGeometrys = 0; + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return nil; + assert(size <= sizeof(rpAtomicBinary)); + if(RwStreamRead(stream, &a, size) != size) + return nil; + + atomic = RpAtomicCreate(); + if(atomic == nil) + return nil; + + RpAtomicSetFlags(atomic, a.flags); + + if(frmList->numFrames){ + assert(a.frameIndex < frmList->numFrames); + RpAtomicSetFrame(atomic, frmList->frames[a.frameIndex]); + } + + if(geomList->numGeoms){ + assert(a.geomIndex < geomList->numGeoms); + RpAtomicSetGeometry(atomic, geomList->geometries[a.geomIndex], 0); + }else{ + RpGeometry *geom; + if(!RwStreamFindChunk(stream, rwID_GEOMETRY, nil, &version)){ + RpAtomicDestroy(atomic); + return nil; + } + geom = RpGeometryStreamRead(stream); + if(geom == nil){ + RpAtomicDestroy(atomic); + return nil; + } + RpAtomicSetGeometry(atomic, geom, 0); + RpGeometryDestroy(geom); + } + + return atomic; +} + +bool +RpClumpGtaStreamRead1(RwStream *stream) +{ + RwUInt32 size, version; + + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return false; + if(version >= 0x33000){ + assert(size == 12); + if(RwStreamRead(stream, &gClumpInfo, 12) != 12) + return false; + }else{ + assert(size == 4); + if(RwStreamRead(stream, &gClumpInfo, 4) != 4) + return false; + } + + if(!RwStreamFindChunk(stream, rwID_FRAMELIST, nil, &version)) + return false; + if(rwFrameListStreamRead(stream, &gFrameList) == nil) + return false; + + if(!RwStreamFindChunk(stream, rwID_GEOMETRYLIST, nil, &version)){ + rwFrameListDeinitialize(&gFrameList); + return false; + } + if(GeometryListStreamRead1(stream, &gGeomList) == nil){ + rwFrameListDeinitialize(&gFrameList); + return false; + } + streamPosition = STREAMPOS(stream); + return true; +} + +RpClump* +RpClumpGtaStreamRead2(RwStream *stream) +{ + int i; + RwUInt32 version; + RpAtomic *atomic; + RpClump *clump; + + clump = RpClumpCreate(); + if(clump == nil) + return nil; + + RwStreamSkip(stream, streamPosition - STREAMPOS(stream)); + + if(GeometryListStreamRead2(stream, &gGeomList) == nil){ + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + RpClumpDestroy(clump); + return nil; + } + + RpClumpSetFrame(clump, gFrameList.frames[0]); + + for(i = 0; i < gClumpInfo.numAtomics; i++){ + if(!RwStreamFindChunk(stream, rwID_ATOMIC, nil, &version)){ + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + RpClumpDestroy(clump); + return nil; + } + + atomic = ClumpAtomicStreamRead(stream, &gFrameList, &gGeomList); + if(atomic == nil){ + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + RpClumpDestroy(clump); + return nil; + } + + RpClumpAddAtomic(clump, atomic); + } + + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + return clump; +} + +void +RpClumpGtaCancelStream(void) +{ + GeometryListDeinitialize(&gGeomList); + rwFrameListDeinitialize(&gFrameList); + gFrameList.numFrames = 0; +} diff --git a/src/rw/Lights.cpp b/src/rw/Lights.cpp new file mode 100644 index 0000000..b3cef6d --- /dev/null +++ b/src/rw/Lights.cpp @@ -0,0 +1,355 @@ +#include "common.h" +#include +#include + +#include "Lights.h" +#include "Timer.h" +#include "Timecycle.h" +#include "Coronas.h" +#include "Weather.h" +#include "ZoneCull.h" +#include "Frontend.h" + +RpLight *pAmbient; +RpLight *pDirect; +RpLight *pExtraDirectionals[4] = { nil }; +int LightStrengths[4]; +int NumExtraDirLightsInWorld; + +RwRGBAReal AmbientLightColourForFrame; +RwRGBAReal AmbientLightColourForFrame_PedsCarsAndObjects; +RwRGBAReal DirectionalLightColourForFrame; + +RwRGBAReal AmbientLightColour; +RwRGBAReal DirectionalLightColour; + +void +SetLightsWithTimeOfDayColour(RpWorld *) +{ + CVector vec1, vec2, vecsun; + RwMatrix mat; + + if(pAmbient){ + AmbientLightColourForFrame.red = CTimeCycle::GetAmbientRed() * CCoronas::LightsMult; + AmbientLightColourForFrame.green = CTimeCycle::GetAmbientGreen() * CCoronas::LightsMult; + AmbientLightColourForFrame.blue = CTimeCycle::GetAmbientBlue() * CCoronas::LightsMult; + if(CWeather::LightningFlash && !CCullZones::CamNoRain()){ + AmbientLightColourForFrame.red = 1.0f; + AmbientLightColourForFrame.green = 1.0f; + AmbientLightColourForFrame.blue = 1.0f; + } + AmbientLightColourForFrame_PedsCarsAndObjects.red = Min(1.0f, AmbientLightColourForFrame.red*1.3f); + AmbientLightColourForFrame_PedsCarsAndObjects.green = Min(1.0f, AmbientLightColourForFrame.green*1.3f); + AmbientLightColourForFrame_PedsCarsAndObjects.blue = Min(1.0f, AmbientLightColourForFrame.blue*1.3f); + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); + } + + if(pDirect){ + DirectionalLightColourForFrame.red = CTimeCycle::GetDirectionalRed() * CCoronas::LightsMult; + DirectionalLightColourForFrame.green = CTimeCycle::GetDirectionalGreen() * CCoronas::LightsMult; + DirectionalLightColourForFrame.blue = CTimeCycle::GetDirectionalBlue() * CCoronas::LightsMult; + RpLightSetColor(pDirect, &DirectionalLightColourForFrame); + + vecsun = CTimeCycle::m_VectorToSun[CTimeCycle::m_CurrentStoredValue]; + vec1 = CVector(0.0f, 0.0f, 1.0f); + vec2 = CrossProduct(vec1, vecsun); + vec2.Normalise(); + vec1 = CrossProduct(vec2, vecsun); + mat.at.x = -vecsun.x; + mat.at.y = -vecsun.y; + mat.at.z = -vecsun.z; + mat.right.x = vec1.x; + mat.right.y = vec1.y; + mat.right.z = vec1.z; + mat.up.x = vec2.x; + mat.up.y = vec2.y; + mat.up.z = vec2.z; + RwFrameTransform(RpLightGetFrame(pDirect), &mat, rwCOMBINEREPLACE); + } + + if(CMenuManager::m_PrefsBrightness > 256){ + float f1 = 2.0f * (CMenuManager::m_PrefsBrightness/256.0f - 1.0f) * 0.6f + 1.0f; + float f2 = 3.0f * (CMenuManager::m_PrefsBrightness/256.0f - 1.0f) * 0.6f + 1.0f; + + AmbientLightColourForFrame.red = Min(1.0f, AmbientLightColourForFrame.red * f2); + AmbientLightColourForFrame.green = Min(1.0f, AmbientLightColourForFrame.green * f2); + AmbientLightColourForFrame.blue = Min(1.0f, AmbientLightColourForFrame.blue * f2); + AmbientLightColourForFrame_PedsCarsAndObjects.red = Min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.red * f1); + AmbientLightColourForFrame_PedsCarsAndObjects.green = Min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.green * f1); + AmbientLightColourForFrame_PedsCarsAndObjects.blue = Min(1.0f, AmbientLightColourForFrame_PedsCarsAndObjects.blue * f1); +#ifdef FIX_BUGS + DirectionalLightColourForFrame.red = Min(1.0f, DirectionalLightColourForFrame.red * f1); + DirectionalLightColourForFrame.green = Min(1.0f, DirectionalLightColourForFrame.green * f1); + DirectionalLightColourForFrame.blue = Min(1.0f, DirectionalLightColourForFrame.blue * f1); +#else + DirectionalLightColourForFrame.red = Min(1.0f, AmbientLightColourForFrame.red * f1); + DirectionalLightColourForFrame.green = Min(1.0f, AmbientLightColourForFrame.green * f1); + DirectionalLightColourForFrame.blue = Min(1.0f, AmbientLightColourForFrame.blue * f1); +#endif + } +} + +RpWorld* +LightsCreate(RpWorld *world) +{ + int i; + RwRGBAReal color; + RwFrame *frame; + + if(world == nil) + return nil; + + pAmbient = RpLightCreate(rpLIGHTAMBIENT); + RpLightSetFlags(pAmbient, rpLIGHTLIGHTATOMICS); + color.red = 0.25f; + color.green = 0.25f; + color.blue = 0.2f; + RpLightSetColor(pAmbient, &color); + + pDirect = RpLightCreate(rpLIGHTDIRECTIONAL); + RpLightSetFlags(pDirect, rpLIGHTLIGHTATOMICS); + color.red = 1.0f; + color.green = 0.85f; + color.blue = 0.45f; + RpLightSetColor(pDirect, &color); + RpLightSetRadius(pDirect, 2.0f); + frame = RwFrameCreate(); + RpLightSetFrame(pDirect, frame); + RwV3d axis = { 1.0f, 1.0f, 0.0f }; + RwFrameRotate(frame, &axis, 160.0f, rwCOMBINEPRECONCAT); + + RpWorldAddLight(world, pAmbient); + RpWorldAddLight(world, pDirect); + + for(i = 0; i < NUMEXTRADIRECTIONALS; i++){ + pExtraDirectionals[i] = RpLightCreate(rpLIGHTDIRECTIONAL); + RpLightSetFlags(pExtraDirectionals[i], 0); + color.red = 1.0f; + color.green = 0.5f; + color.blue = 0.0f; + RpLightSetColor(pExtraDirectionals[i], &color); + RpLightSetRadius(pExtraDirectionals[i], 2.0f); + frame = RwFrameCreate(); + RpLightSetFrame(pExtraDirectionals[i], frame); + RpWorldAddLight(world, pExtraDirectionals[i]); + } + + return world; +} + +void +LightsDestroy(RpWorld *world) +{ + int i; + + if(world == nil) + return; + + if(pAmbient){ + RpWorldRemoveLight(world, pAmbient); + RpLightDestroy(pAmbient); + pAmbient = nil; + } + + if(pDirect){ + RpWorldRemoveLight(world, pDirect); + RwFrameDestroy(RpLightGetFrame(pDirect)); + RpLightDestroy(pDirect); + pDirect = nil; + } + + for(i = 0; i < NUMEXTRADIRECTIONALS; i++) + if(pExtraDirectionals[i]){ + RpWorldRemoveLight(world, pExtraDirectionals[i]); + RwFrameDestroy(RpLightGetFrame(pExtraDirectionals[i])); + RpLightDestroy(pExtraDirectionals[i]); + pExtraDirectionals[i] = nil; + } +} + +void +WorldReplaceNormalLightsWithScorched(RpWorld *world, float l) +{ + RwRGBAReal color; + color.red = l; + color.green = l; + color.blue = l; + RpLightSetColor(pAmbient, &color); + RpLightSetFlags(pDirect, 0); +} + +void +WorldReplaceScorchedLightsWithNormal(RpWorld *world) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); + RpLightSetFlags(pDirect, rpLIGHTLIGHTATOMICS); +} + +void +AddAnExtraDirectionalLight(RpWorld *world, float dirx, float diry, float dirz, float red, float green, float blue) +{ + float strength; + int weakest; + int i, n; + RwRGBAReal color; + RwV3d *dir; + + strength = Max(Max(red, green), blue); + n = -1; + if(NumExtraDirLightsInWorld < NUMEXTRADIRECTIONALS) + n = NumExtraDirLightsInWorld; + else{ + weakest = strength; + for(i = 0; i < NUMEXTRADIRECTIONALS; i++) + if(LightStrengths[i] < weakest){ + weakest = LightStrengths[i]; + n = i; + } + } + + if(n < 0) + return; + + color.red = red; + color.green = green; + color.blue = blue; + RpLightSetColor(pExtraDirectionals[n], &color); + dir = RwMatrixGetAt(RwFrameGetMatrix(RpLightGetFrame(pExtraDirectionals[n]))); + dir->x = -dirx; + dir->y = -diry; + dir->z = -dirz; + RwMatrixUpdate(RwFrameGetMatrix(RpLightGetFrame(pExtraDirectionals[n]))); + RwFrameUpdateObjects(RpLightGetFrame(pExtraDirectionals[n])); + RpLightSetFlags(pExtraDirectionals[n], rpLIGHTLIGHTATOMICS); + LightStrengths[n] = strength; + NumExtraDirLightsInWorld = Min(NumExtraDirLightsInWorld+1, NUMEXTRADIRECTIONALS); +} + +void +RemoveExtraDirectionalLights(RpWorld *world) +{ + int i; + for(i = 0; i < NumExtraDirLightsInWorld; i++) + RpLightSetFlags(pExtraDirectionals[i], 0); + NumExtraDirLightsInWorld = 0; +} + +void +SetAmbientAndDirectionalColours(float f) +{ + AmbientLightColour.red = AmbientLightColourForFrame.red * f; + AmbientLightColour.green = AmbientLightColourForFrame.green * f; + AmbientLightColour.blue = AmbientLightColourForFrame.blue * f; + + DirectionalLightColour.red = DirectionalLightColourForFrame.red * f; + DirectionalLightColour.green = DirectionalLightColourForFrame.green * f; + DirectionalLightColour.blue = DirectionalLightColourForFrame.blue * f; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); +} + +// unused +void +SetFlashyColours(float f) +{ + if(CTimer::GetTimeInMilliseconds() & 0x100){ + AmbientLightColour.red = 1.0f; + AmbientLightColour.green = 1.0f; + AmbientLightColour.blue = 1.0f; + + DirectionalLightColour.red = DirectionalLightColourForFrame.red; + DirectionalLightColour.green = DirectionalLightColourForFrame.green; + DirectionalLightColour.blue = DirectionalLightColourForFrame.blue; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); + }else{ + SetAmbientAndDirectionalColours(f * 0.75f); + } +} + +// unused +void +SetFlashyColours_Mild(float f) +{ + if(CTimer::GetTimeInMilliseconds() & 0x100){ + AmbientLightColour.red = 0.65f; + AmbientLightColour.green = 0.65f; + AmbientLightColour.blue = 0.65f; + + DirectionalLightColour.red = DirectionalLightColourForFrame.red; + DirectionalLightColour.green = DirectionalLightColourForFrame.green; + DirectionalLightColour.blue = DirectionalLightColourForFrame.blue; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); + }else{ + SetAmbientAndDirectionalColours(f * 0.9f); + } +} + +void +SetBrightMarkerColours(float f) +{ + AmbientLightColour.red = 0.6f; + AmbientLightColour.green = 0.6f; + AmbientLightColour.blue = 0.6f; + + DirectionalLightColour.red = (1.0f - DirectionalLightColourForFrame.red) * 0.4f + DirectionalLightColourForFrame.red; + DirectionalLightColour.green = (1.0f - DirectionalLightColourForFrame.green) * 0.4f + DirectionalLightColourForFrame.green; + DirectionalLightColour.blue = (1.0f - DirectionalLightColourForFrame.blue) * 0.4f + DirectionalLightColourForFrame.blue; + + RpLightSetColor(pAmbient, &AmbientLightColour); + RpLightSetColor(pDirect, &DirectionalLightColour); +} + +void +ReSetAmbientAndDirectionalColours(void) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); + RpLightSetColor(pDirect, &DirectionalLightColourForFrame); +} + +void +DeActivateDirectional(void) +{ + RpLightSetFlags(pDirect, 0); +} + +void +ActivateDirectional(void) +{ + RpLightSetFlags(pDirect, rpLIGHTLIGHTATOMICS); +} + +void +SetAmbientColours(void) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame); +} + +void +SetAmbientColoursForPedsCarsAndObjects(void) +{ + RpLightSetColor(pAmbient, &AmbientLightColourForFrame_PedsCarsAndObjects); +} + +uint8 IndicateR[] = { 0, 255, 0, 0, 255, 255, 0 }; +uint8 IndicateG[] = { 0, 0, 255, 0, 255, 0, 255 }; +uint8 IndicateB[] = { 0, 0, 0, 255, 0, 255, 255 }; + +void +SetAmbientColoursToIndicateRoadGroup(int i) +{ + AmbientLightColour.red = IndicateR[i%7]/255.0f; + AmbientLightColour.green = IndicateG[i%7]/255.0f; + AmbientLightColour.blue = IndicateB[i%7]/255.0f; + RpLightSetColor(pAmbient, &AmbientLightColour); +} + +void +SetAmbientColours(RwRGBAReal *color) +{ + RpLightSetColor(pAmbient, color); +} diff --git a/src/rw/Lights.h b/src/rw/Lights.h new file mode 100644 index 0000000..5057f1d --- /dev/null +++ b/src/rw/Lights.h @@ -0,0 +1,26 @@ +#pragma once + +extern RpLight *pAmbient; +extern RpLight *pDirect; +extern RpLight *pExtraDirectionals[4]; +extern int LightStrengths[4]; +extern int NumExtraDirLightsInWorld; + +void SetLightsWithTimeOfDayColour(RpWorld *); +RpWorld *LightsCreate(RpWorld *world); +void LightsDestroy(RpWorld *world); +void WorldReplaceNormalLightsWithScorched(RpWorld *world, float l); +void WorldReplaceScorchedLightsWithNormal(RpWorld *world); +void AddAnExtraDirectionalLight(RpWorld *world, float dirx, float diry, float dirz, float red, float green, float blue); +void RemoveExtraDirectionalLights(RpWorld *world); +void SetAmbientAndDirectionalColours(float f); +void SetFlashyColours(float f); +void SetFlashyColours_Mild(float f); +void SetBrightMarkerColours(float f); +void ReSetAmbientAndDirectionalColours(void); +void DeActivateDirectional(void); +void ActivateDirectional(void); +void SetAmbientColours(void); +void SetAmbientColoursForPedsCarsAndObjects(void); +void SetAmbientColoursToIndicateRoadGroup(int i); +void SetAmbientColours(RwRGBAReal *color); diff --git a/src/rw/MemoryHeap.cpp b/src/rw/MemoryHeap.cpp new file mode 100644 index 0000000..469262d --- /dev/null +++ b/src/rw/MemoryHeap.cpp @@ -0,0 +1,497 @@ +#include "common.h" +#include "main.h" +#include "FileMgr.h" +#include "Timer.h" +#include "ModelInfo.h" +#include "Streaming.h" +#include "FileLoader.h" +#include "MemoryHeap.h" + +#ifdef USE_CUSTOM_ALLOCATOR + +//#define MEMORYHEAP_ASSERT(cond) { if (!(cond)) { printf("ASSERT File:%s Line:%d\n", __FILE__, __LINE__); exit(1); } } +//#define MEMORYHEAP_ASSERT_MESSAGE(cond, message) { if (!(cond)) { printf("ASSERT File:%s Line:%d:\n\t%s\n", __FILE__, __LINE__, message); exit(1); } } + +#define MEMORYHEAP_ASSERT(cond) assert(cond) +#define MEMORYHEAP_ASSERT_MESSAGE(cond, message) assert(cond) + +// registered pointers that we keep track of +void **gPtrList[4000]; +int32 numPtrs; +int32 gPosnInList; +// indices into the ptr list in here are free +CStack m_ptrListIndexStack; +// how much memory we've moved +uint32 memMoved; + +CMemoryHeap gMainHeap; + +void +CMemoryHeap::Init(uint32 total) +{ + MEMORYHEAP_ASSERT((total != 0xF) != 0); + + m_totalMemUsed = 0; + m_memUsed = nil; + m_currentMemID = MEMID_FREE; + m_blocksUsed = nil; + m_totalBlocksUsed = 0; + m_unkMemId = -1; + + uint8 *mem = (uint8*)malloc(total); + assert(((uintptr)mem & 0xF) == 0); + m_start = (HeapBlockDesc*)mem; + m_end = (HeapBlockDesc*)(mem + total - sizeof(HeapBlockDesc)); + m_start->m_memId = MEMID_FREE; + m_start->m_size = total - 2*sizeof(HeapBlockDesc); + m_end->m_memId = MEMID_GAME; + m_end->m_size = 0; + + m_freeList.m_last.m_size = INT_MAX; + m_freeList.Init(); + m_freeList.Insert(m_start); + + // TODO: figure out what these are and use sizeof + m_fixedSize[0].Init(0x10); + m_fixedSize[1].Init(0x20); + m_fixedSize[2].Init(0xE0); + m_fixedSize[3].Init(0x60); + m_fixedSize[4].Init(0x1C0); + m_fixedSize[5].Init(0x50); + + m_currentMemID = MEMID_FREE; // disable registration + m_memUsed = (uint32*)Malloc(NUM_MEMIDS * sizeof(uint32)); + m_blocksUsed = (uint32*)Malloc(NUM_MEMIDS * sizeof(uint32)); + RegisterMalloc(GetDescFromHeapPointer(m_memUsed)); + RegisterMalloc(GetDescFromHeapPointer(m_blocksUsed)); + + m_currentMemID = MEMID_GAME; + for(int i = 0; i < NUM_MEMIDS; i++){ + m_memUsed[i] = 0; + m_blocksUsed[i] = 0; + } +} + +void +CMemoryHeap::RegisterMalloc(HeapBlockDesc *block) +{ + block->m_memId = m_currentMemID; + if(m_currentMemID == MEMID_FREE) + return; + m_totalMemUsed += block->m_size + sizeof(HeapBlockDesc); + m_memUsed[m_currentMemID] += block->m_size + sizeof(HeapBlockDesc); + m_blocksUsed[m_currentMemID]++; + m_totalBlocksUsed++; +} + +void +CMemoryHeap::RegisterFree(HeapBlockDesc *block) +{ + if(block->m_memId == MEMID_FREE) + return; + m_totalMemUsed -= block->m_size + sizeof(HeapBlockDesc); + m_memUsed[block->m_memId] -= block->m_size + sizeof(HeapBlockDesc); + m_blocksUsed[block->m_memId]--; + m_totalBlocksUsed--; +} + +void* +CMemoryHeap::Malloc(uint32 size) +{ + static int recursion = 0; + + // weird way to round up + if((size & 0xF) != 0) + size = (size&~0xF) + 0x10; + + recursion++; + + // See if we can allocate from one of the fixed-size lists + for(int i = 0; i < NUM_FIXED_MEMBLOCKS; i++){ + CommonSize *list = &m_fixedSize[i]; + if(m_fixedSize[i].m_size == size){ + HeapBlockDesc *block = list->Malloc(); + if(block){ + RegisterMalloc(block); + recursion--; + return block->GetDataPointer(); + } + break; + } + } + + // now try the normal free list + HeapBlockDesc *next; + for(HeapBlockDesc *block = m_freeList.m_first.m_next; + block != &m_freeList.m_last; + block = next){ + MEMORYHEAP_ASSERT(block->m_memId == MEMID_FREE); + MEMORYHEAP_ASSERT_MESSAGE(block >= m_start && block <= m_end, "Block outside of memory"); + + // make sure block has maximum size + uint32 initialsize = block->m_size; + uint32 blocksize = CombineFreeBlocks(block); +#ifdef FIX_BUGS + // has to be done here because block can be moved + next = block->m_next; +#endif + if(initialsize != blocksize){ + block->RemoveHeapFreeBlock(); + HeapBlockDesc *pos = block->m_prev->FindSmallestFreeBlock(block->m_size); + block->InsertHeapFreeBlock(pos->m_prev); + } + if(block->m_size >= size){ + // got space to allocate from! + block->RemoveHeapFreeBlock(); + FillInBlockData(block, block->GetNextConsecutive(), size); + recursion--; + return block->GetDataPointer(); + } +#ifndef FIX_BUGS + next = block->m_next; +#endif + } + + // oh no, we're losing, try to free some stuff + static bool removeCollision = false; + static bool removeIslands = false; + static bool removeBigBuildings = false; + size_t initialMemoryUsed = CStreaming::ms_memoryUsed; + CStreaming::MakeSpaceFor(0xCFE800 - CStreaming::ms_memoryUsed); + if (recursion > 10) + CGame::TidyUpMemory(true, false); + else if (recursion > 6) + CGame::TidyUpMemory(false, true); + if (initialMemoryUsed == CStreaming::ms_memoryUsed && recursion > 11) { + if (!removeCollision && !CGame::playingIntro) { + CModelInfo::RemoveColModelsFromOtherLevels(LEVEL_GENERIC); + removeCollision = true; + } + else if (!removeIslands && !CGame::playingIntro) { + CStreaming::RemoveIslandsNotUsed(LEVEL_INDUSTRIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_COMMERCIAL); + CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN); + removeIslands = true; + } + else if (!removeBigBuildings) { + CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL); + CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL); + CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN); + } + else { + LoadingScreen("NO MORE MEMORY", nil, nil); + LoadingScreen("NO MORE MEMORY", nil, nil); + } + CGame::TidyUpMemory(true, false); + } + void *mem = Malloc(size); + if (removeCollision) { + CTimer::Stop(); + // TODO: different on PS2 + CFileLoader::LoadCollisionFromDatFile(CCollision::ms_collisionInMemory); + removeCollision = false; + CTimer::Update(); + } + if (removeBigBuildings || removeIslands) { + CTimer::Stop(); + if (!CGame::playingIntro) + CStreaming::RequestBigBuildings(CGame::currLevel); + CStreaming::LoadAllRequestedModels(true); + removeBigBuildings = false; + removeIslands = false; + CTimer::Update(); + } + recursion--; + return mem; +} + +void* +CMemoryHeap::Realloc(void *ptr, uint32 size) +{ + if(ptr == nil) + return Malloc(size); + + // weird way to round up + if((size & 0xF) != 0) + size = (size&~0xF) + 0x10; + + HeapBlockDesc *block = GetDescFromHeapPointer(ptr); + +#ifdef FIX_BUGS + // better handling of size < block->m_size + if(size == 0){ + Free(ptr); + return nil; + } + if(block->m_size >= size){ + // shrink allocated block + RegisterFree(block); + PushMemId(block->m_memId); + FillInBlockData(block, block->GetNextConsecutive(), size); + PopMemId(); + return ptr; + } +#else + // not growing. just returning here is a bit cheap though + if(block->m_size >= size) + return ptr; +#endif + + // have to grow allocated block + HeapBlockDesc *next = block->GetNextConsecutive(); + MEMORYHEAP_ASSERT_MESSAGE(next >= m_start && next <= m_end, "Block outside of memory"); + if(next->m_memId == MEMID_FREE){ + // try to grow the current block + // make sure the next free block has maximum size + uint32 freespace = CombineFreeBlocks(next); + HeapBlockDesc *end = next->GetNextConsecutive(); + MEMORYHEAP_ASSERT_MESSAGE(end >= m_start && end <= m_end, "Block outside of memory"); + // why the sizeof here? + if(block->m_size + next->m_size + sizeof(HeapBlockDesc) >= size){ + // enough space to grow + next->RemoveHeapFreeBlock(); + RegisterFree(block); + PushMemId(block->m_memId); + FillInBlockData(block, next->GetNextConsecutive(), size); + PopMemId(); + return ptr; + } + } + + // can't grow the existing block, have to get a new one and copy + PushMemId(block->m_memId); + void *dst = Malloc(size); + PopMemId(); + memcpy(dst, ptr, block->m_size); + Free(ptr); + return dst; +} + +void +CMemoryHeap::Free(void *ptr) +{ + HeapBlockDesc *block = GetDescFromHeapPointer(ptr); + MEMORYHEAP_ASSERT_MESSAGE(block->m_memId != MEMID_FREE, "MemoryHeap corrupt"); + MEMORYHEAP_ASSERT(m_unkMemId == -1 || m_unkMemId == block->m_memId); + + RegisterFree(block); + block->m_memId = MEMID_FREE; + CombineFreeBlocks(block); + FreeBlock(block); + if(block->m_ptrListIndex != -1){ + int32 idx = block->m_ptrListIndex; + gPtrList[idx] = nil; + m_ptrListIndexStack.push(idx); + } + block->m_ptrListIndex = -1; +} + +// allocate 'size' bytes from 'block' +void +CMemoryHeap::FillInBlockData(HeapBlockDesc *block, HeapBlockDesc *end, uint32 size) +{ + block->m_size = size; + block->m_ptrListIndex = -1; + HeapBlockDesc *remainder = block->GetNextConsecutive(); + MEMORYHEAP_ASSERT(remainder <= end); + + if(remainder < end-1){ + RegisterMalloc(block); + + // can fit another block in the remaining space + remainder->m_size = GetSizeBetweenBlocks(remainder, end); + remainder->m_memId = MEMID_FREE; + MEMORYHEAP_ASSERT(remainder->m_size != 0); + FreeBlock(remainder); + }else{ + // fully allocate this one + if(remainder < end) + // no gaps allowed + block->m_size = GetSizeBetweenBlocks(block, end); + RegisterMalloc(block); + } +} + +// Make sure free block has no other free blocks after it +uint32 +CMemoryHeap::CombineFreeBlocks(HeapBlockDesc *block) +{ + HeapBlockDesc *next = block->GetNextConsecutive(); + if(next->m_memId != MEMID_FREE) + return block->m_size; + // get rid of free blocks after this one and adjust size + for(; next->m_memId == MEMID_FREE; next = next->GetNextConsecutive()) + next->RemoveHeapFreeBlock(); + block->m_size = GetSizeBetweenBlocks(block, next); + return block->m_size; +} + +// Try to move all registered memory blocks into more optimal location +void +CMemoryHeap::TidyHeap(void) +{ + for(int i = 0; i < numPtrs; i++){ + if(gPtrList[i] == nil || *gPtrList[i] == nil) + continue; + HeapBlockDesc *newblock = WhereShouldMemoryMove(*gPtrList[i]); + if(newblock) + *gPtrList[i] = MoveHeapBlock(newblock, GetDescFromHeapPointer(*gPtrList[i])); + } +} + +// +void +CMemoryHeap::RegisterMemPointer(void *ptr) +{ + HeapBlockDesc *block = GetDescFromHeapPointer(*(void**)ptr); + + if(block->m_ptrListIndex != -1) + return; // already registered + + int index; + if(m_ptrListIndexStack.sp > 0){ + // re-use a previously free'd index + index = m_ptrListIndexStack.pop(); + }else{ + // have to find a new index + index = gPosnInList; + + void **pp = gPtrList[index]; + // we're replacing an old pointer here?? + if(pp && *pp && *pp != (void*)0xDDDDDDDD) + GetDescFromHeapPointer(*pp)->m_ptrListIndex = -1; + + gPosnInList++; + if(gPosnInList == 4000) + gPosnInList = 0; + if(numPtrs < 4000) + numPtrs++; + } + gPtrList[index] = (void**)ptr; + block->m_ptrListIndex = index; +} + +void* +CMemoryHeap::MoveMemory(void *ptr) +{ + HeapBlockDesc *newblock = WhereShouldMemoryMove(ptr); + if(newblock) + return MoveHeapBlock(newblock, GetDescFromHeapPointer(ptr)); + else + return ptr; +} + +HeapBlockDesc* +CMemoryHeap::WhereShouldMemoryMove(void *ptr) +{ + HeapBlockDesc *block = GetDescFromHeapPointer(ptr); + MEMORYHEAP_ASSERT(block->m_memId != MEMID_FREE); + + HeapBlockDesc *next = block->GetNextConsecutive(); + if(next->m_memId != MEMID_FREE) + return nil; + + // we want to move the block into another block + // such that the free space between this and the next block can be minimized + HeapBlockDesc *newblock = m_freeList.m_first.FindSmallestFreeBlock(block->m_size); + // size of free space wouldn't decrease, so return + if(newblock->m_size >= block->m_size + next->m_size) + return nil; + // size of free space wouldn't decrease enough + if(newblock->m_size >= 16 + 1.125f*block->m_size) // what are 16 and 1.125 here? sizeof(HeapBlockDesc)? + return nil; + return newblock; +} + +void* +CMemoryHeap::MoveHeapBlock(HeapBlockDesc *dst, HeapBlockDesc *src) +{ + PushMemId(src->m_memId); + dst->RemoveHeapFreeBlock(); + FillInBlockData(dst, dst->GetNextConsecutive(), src->m_size); + PopMemId(); + memcpy(dst->GetDataPointer(), src->GetDataPointer(), src->m_size); + memMoved += src->m_size; + dst->m_ptrListIndex = src->m_ptrListIndex; + src->m_ptrListIndex = -1; + Free(src->GetDataPointer()); + return dst->GetDataPointer(); +} + +uint32 +CMemoryHeap::GetMemoryUsed(int32 id) +{ + return m_memUsed[id]; +} + +uint32 +CMemoryHeap::GetBlocksUsed(int32 id) +{ + return m_blocksUsed[id]; +} + +void +CMemoryHeap::PopMemId(void) +{ + assert(m_idStack.sp > 0); + m_currentMemID = m_idStack.pop(); + assert(m_currentMemID != MEMID_FREE); +} + +void +CMemoryHeap::PushMemId(int32 id) +{ + MEMORYHEAP_ASSERT(id != MEMID_FREE); + assert(m_idStack.sp < 16); + m_idStack.push(m_currentMemID); + m_currentMemID = id; +} + +void +CMemoryHeap::ParseHeap(void) +{ + char tmp[16]; + int fd = CFileMgr::OpenFileForWriting("heap.txt"); + CTimer::Stop(); + + // CMemoryHeap::IntegrityCheck(); + + uint32 addrQW = 0; + for(HeapBlockDesc *block = m_start; block < m_end; block = block->GetNextConsecutive()){ + char chr = '*'; // free + if(block->m_memId != MEMID_FREE) + chr = block->m_memId-1 + 'A'; + int numQW = block->m_size>>4; + + if((addrQW & 0x3F) == 0){ + sprintf(tmp, "\n%5dK:", addrQW>>6); + CFileMgr::Write(fd, tmp, 8); + } + CFileMgr::Write(fd, "#", 1); // the descriptor, has to be 16 bytes!!!! + addrQW++; + + while(numQW--){ + if((addrQW & 0x3F) == 0){ + sprintf(tmp, "\n%5dK:", addrQW>>6); + CFileMgr::Write(fd, tmp, 8); + } + CFileMgr::Write(fd, &chr, 1); + addrQW++; + } + } + + CTimer::Update(); + CFileMgr::CloseFile(fd); +} + + +void +CommonSize::Init(uint32 size) +{ + m_freeList.Init(); + m_size = size; + m_failed = 0; + m_remaining = 0; +} + +#endif diff --git a/src/rw/MemoryHeap.h b/src/rw/MemoryHeap.h new file mode 100644 index 0000000..cd8cf22 --- /dev/null +++ b/src/rw/MemoryHeap.h @@ -0,0 +1,202 @@ +#pragma once + +// some windows shit +#ifdef MoveMemory +#undef MoveMemory +#endif + +#ifdef USE_CUSTOM_ALLOCATOR +#define PUSH_MEMID(id) gMainHeap.PushMemId(id) +#define POP_MEMID() gMainHeap.PopMemId() +#define REGISTER_MEMPTR(ptr) gMainHeap.RegisterMemPointer(ptr) +#else +#define PUSH_MEMID(id) +#define POP_MEMID() +#define REGISTER_MEMPTR(ptr) +#endif + +enum { + MEMID_FREE, + MEMID_GAME = 1, // "Game" + MEMID_WORLD = 2, // "World" + MEMID_ANIMATION = 3, // "Animation" + MEMID_POOLS = 4, // "Pools" + MEMID_DEF_MODELS = 5, // "Default Models" + MEMID_STREAM = 6, // "Streaming" + MEMID_STREAM_MODELS = 7, // "Streamed Models" (instance) + MEMID_STREAM_TEXUTRES = 8, // "Streamed Textures" + MEMID_TEXTURES = 9, // "Textures" + MEMID_COLLISION = 10, // "Collision" + MEMID_RENDERLIST = 11, // ? + MEMID_GAME_PROCESS = 12, // "Game Process" + MEMID_SCRIPT = 13, // "Script" + MEMID_CARS = 14, // "Cars" + MEMID_RENDER = 15, // "Render" + MEMID_FRONTEND = 17, // ? + + NUM_MEMIDS, + + NUM_FIXED_MEMBLOCKS = 6 +}; + +template +class CStack +{ +public: + T values[N]; + uint32 sp; + + CStack() : sp(0) {} + void push(const T& val) { values[sp++] = val; } + T& pop() { return values[--sp]; } +}; + + +struct HeapBlockDesc +{ + uint32 m_size; + int16 m_memId; + int16 m_ptrListIndex; + HeapBlockDesc *m_next; + HeapBlockDesc *m_prev; + + HeapBlockDesc *GetNextConsecutive(void) + { + return (HeapBlockDesc*)((uintptr)this + sizeof(HeapBlockDesc) + m_size); + } + + void *GetDataPointer(void) + { + return (void*)((uintptr)this + sizeof(HeapBlockDesc)); + } + + void RemoveHeapFreeBlock(void) + { + m_next->m_prev = m_prev; + m_prev->m_next = m_next; + } + + // after node + void InsertHeapFreeBlock(HeapBlockDesc *node) + { + m_next = node->m_next; + node->m_next->m_prev = this; + m_prev = node; + node->m_next = this; + } + + HeapBlockDesc *FindSmallestFreeBlock(uint32 size) + { + HeapBlockDesc *b; + for(b = m_next; b->m_size < size; b = b->m_next); + return b; + } +}; + +#ifdef USE_CUSTOM_ALLOCATOR +// TODO: figure something out for 64 bit pointers +static_assert(sizeof(HeapBlockDesc) == 0x10, "HeapBlockDesc must have 0x10 size otherwise most of assumptions don't make sense"); +#endif + +struct HeapBlockList +{ + HeapBlockDesc m_first; + HeapBlockDesc m_last; + + void Init(void) + { + m_first.m_next = &m_last; + m_last.m_prev = &m_first; + } + + void Insert(HeapBlockDesc *node) + { + node->InsertHeapFreeBlock(&m_first); + } +}; + +struct CommonSize +{ + HeapBlockList m_freeList; + uint32 m_size; + uint32 m_failed; + uint32 m_remaining; + + void Init(uint32 size); + void Free(HeapBlockDesc *node) + { + m_freeList.Insert(node); + m_remaining++; + } + HeapBlockDesc *Malloc(void) + { + if(m_freeList.m_first.m_next == &m_freeList.m_last){ + m_failed++; + return nil; + } + HeapBlockDesc *block = m_freeList.m_first.m_next; + m_remaining--; + block->RemoveHeapFreeBlock(); + block->m_ptrListIndex = -1; + return block; + } +}; + +class CMemoryHeap +{ +public: + HeapBlockDesc *m_start; + HeapBlockDesc *m_end; + HeapBlockList m_freeList; + CommonSize m_fixedSize[NUM_FIXED_MEMBLOCKS]; + uint32 m_totalMemUsed; + CStack m_idStack; + uint32 m_currentMemID; + uint32 *m_memUsed; + uint32 m_totalBlocksUsed; + uint32 *m_blocksUsed; + uint32 m_unkMemId; + + CMemoryHeap(void) : m_start(nil) {} + void Init(uint32 total); + void RegisterMalloc(HeapBlockDesc *block); + void RegisterFree(HeapBlockDesc *block); + void *Malloc(uint32 size); + void *Realloc(void *ptr, uint32 size); + void Free(void *ptr); + void FillInBlockData(HeapBlockDesc *block, HeapBlockDesc *end, uint32 size); + uint32 CombineFreeBlocks(HeapBlockDesc *block); + void *MoveMemory(void *ptr); + HeapBlockDesc *WhereShouldMemoryMove(void *ptr); + void *MoveHeapBlock(HeapBlockDesc *dst, HeapBlockDesc *src); + void PopMemId(void); + void PushMemId(int32 id); + void RegisterMemPointer(void *ptr); + void TidyHeap(void); + uint32 GetMemoryUsed(int32 id); + uint32 GetBlocksUsed(int32 id); + int32 GetLargestFreeBlock(void) { return m_freeList.m_last.m_prev->m_size; } + + void ParseHeap(void); + + HeapBlockDesc *GetDescFromHeapPointer(void *block) + { + return (HeapBlockDesc*)((uintptr)block - sizeof(HeapBlockDesc)); + } + uint32 GetSizeBetweenBlocks(HeapBlockDesc *first, HeapBlockDesc *second) + { + return (uintptr)second - (uintptr)first - sizeof(HeapBlockDesc); + } + void FreeBlock(HeapBlockDesc *block){ + for(int i = 0; i < NUM_FIXED_MEMBLOCKS; i++){ + if(m_fixedSize[i].m_size == block->m_size){ + m_fixedSize[i].Free(block); + return; + } + } + HeapBlockDesc *b = m_freeList.m_first.FindSmallestFreeBlock(block->m_size); + block->InsertHeapFreeBlock(b->m_prev); + } +}; + +extern CMemoryHeap gMainHeap; diff --git a/src/rw/MemoryMgr.cpp b/src/rw/MemoryMgr.cpp new file mode 100644 index 0000000..b9cff04 --- /dev/null +++ b/src/rw/MemoryMgr.cpp @@ -0,0 +1,130 @@ +#include "common.h" +#include "MemoryHeap.h" +#include "MemoryMgr.h" + + +uint8 *pMemoryTop; + +void +InitMemoryMgr(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR +#ifdef GTA_PS2 +#error "finish this" +#else + // randomly allocate 128mb + gMainHeap.Init(128*1024*1024); +#endif +#endif +} + + +RwMemoryFunctions memFuncs = { + MemoryMgrMalloc, + MemoryMgrFree, + MemoryMgrRealloc, + MemoryMgrCalloc +}; + +#ifdef USE_CUSTOM_ALLOCATOR +// game seems to be using heap directly here, but this is nicer +void *operator new(size_t sz) throw() { return MemoryMgrMalloc(sz); } +void *operator new[](size_t sz) throw() { return MemoryMgrMalloc(sz); } +void operator delete(void *ptr) throw() { MemoryMgrFree(ptr); } +void operator delete[](void *ptr) throw() { MemoryMgrFree(ptr); } +#endif + +void* +MemoryMgrMalloc(size_t size) +{ +#ifdef USE_CUSTOM_ALLOCATOR + void *mem = gMainHeap.Malloc(size); +#else + void *mem = malloc(size); +#endif + if((uint8*)mem + size > pMemoryTop) + pMemoryTop = (uint8*)mem + size ; + return mem; +} + +void* +MemoryMgrRealloc(void *ptr, size_t size) +{ +#ifdef USE_CUSTOM_ALLOCATOR + void *mem = gMainHeap.Realloc(ptr, size); +#else + void *mem = realloc(ptr, size); +#endif + if((uint8*)mem + size > pMemoryTop) + pMemoryTop = (uint8*)mem + size ; + return mem; +} + +void* +MemoryMgrCalloc(size_t num, size_t size) +{ +#ifdef USE_CUSTOM_ALLOCATOR + void *mem = gMainHeap.Malloc(num*size); +#else + void *mem = calloc(num, size); +#endif + if((uint8*)mem + size > pMemoryTop) + pMemoryTop = (uint8*)mem + size ; +#ifdef FIX_BUGS + memset(mem, 0, num*size); +#endif + return mem; +} + +void +MemoryMgrFree(void *ptr) +{ +#ifdef USE_CUSTOM_ALLOCATOR +#ifdef FIX_BUGS + // i don't suppose this is handled by RW? + if(ptr == nil) return; +#endif + gMainHeap.Free(ptr); +#else + free(ptr); +#endif +} + +void * +RwMallocAlign(RwUInt32 size, RwUInt32 align) +{ +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + uintptr ptralign = align-1; + void *mem = (void *)MemoryMgrMalloc(size + sizeof(uintptr) + ptralign); + + ASSERT(mem != nil); + + void *addr = (void *)((((uintptr)mem) + sizeof(uintptr) + ptralign) & ~ptralign); + + ASSERT(addr != nil); +#else + void *mem = (void *)MemoryMgrMalloc(size + align); + + ASSERT(mem != nil); + + void *addr = (void *)((((uintptr)mem) + align) & ~(align - 1)); + + ASSERT(addr != nil); +#endif + + *(((void **)addr) - 1) = mem; + + return addr; +} + +void +RwFreeAlign(void *mem) +{ + ASSERT(mem != nil); + + void *addr = *(((void **)mem) - 1); + + ASSERT(addr != nil); + + MemoryMgrFree(addr); +} diff --git a/src/rw/MemoryMgr.h b/src/rw/MemoryMgr.h new file mode 100644 index 0000000..e296280 --- /dev/null +++ b/src/rw/MemoryMgr.h @@ -0,0 +1,12 @@ +#pragma once + +extern RwMemoryFunctions memFuncs; +void InitMemoryMgr(void); + +void *MemoryMgrMalloc(size_t size); +void *MemoryMgrRealloc(void *ptr, size_t size); +void *MemoryMgrCalloc(size_t num, size_t size); +void MemoryMgrFree(void *ptr); + +void *RwMallocAlign(RwUInt32 size, RwUInt32 align); +void RwFreeAlign(void *mem); diff --git a/src/rw/NodeName.cpp b/src/rw/NodeName.cpp new file mode 100644 index 0000000..a7185e4 --- /dev/null +++ b/src/rw/NodeName.cpp @@ -0,0 +1,77 @@ +#include "common.h" + +#include "NodeName.h" + +static int32 gPluginOffset; + +enum +{ + ID_NODENAME = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0xFE), +}; + +#define NODENAMEEXT(o) (RWPLUGINOFFSET(char, o, gPluginOffset)) + +void* +NodeNameConstructor(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + if(gPluginOffset > 0) + NODENAMEEXT(object)[0] = '\0'; + return object; +} + +void* +NodeNameDestructor(void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + return object; +} + +void* +NodeNameCopy(void *dstObject, const void *srcObject, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + strncpy(NODENAMEEXT(dstObject), NODENAMEEXT(srcObject), 23); + return nil; +} + +RwStream* +NodeNameStreamRead(RwStream *stream, RwInt32 binaryLength, void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + RwStreamRead(stream, NODENAMEEXT(object), binaryLength); + NODENAMEEXT(object)[binaryLength] = '\0'; + return stream; +} + +RwStream* +NodeNameStreamWrite(RwStream *stream, RwInt32 binaryLength, const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + RwStreamWrite(stream, NODENAMEEXT(object), binaryLength); + return stream; +} + +RwInt32 +NodeNameStreamGetSize(const void *object, RwInt32 offsetInObject, RwInt32 sizeInObject) +{ + char *name = NODENAMEEXT(object); // can't be nil + return name ? (RwInt32)rwstrlen(name) : 0; +} + +bool +NodeNamePluginAttach(void) +{ + gPluginOffset = RwFrameRegisterPlugin(24, ID_NODENAME, + NodeNameConstructor, + NodeNameDestructor, + NodeNameCopy); + RwFrameRegisterPluginStream(ID_NODENAME, + NodeNameStreamRead, + NodeNameStreamWrite, + NodeNameStreamGetSize); + return gPluginOffset != -1; +} + +char* +GetFrameNodeName(RwFrame *frame) +{ + if(gPluginOffset < 0) + return nil; + return NODENAMEEXT(frame); +} diff --git a/src/rw/NodeName.h b/src/rw/NodeName.h new file mode 100644 index 0000000..1a3e057 --- /dev/null +++ b/src/rw/NodeName.h @@ -0,0 +1,4 @@ +#pragma once + +bool NodeNamePluginAttach(void); +char *GetFrameNodeName(RwFrame *frame); diff --git a/src/rw/RwHelper.cpp b/src/rw/RwHelper.cpp new file mode 100644 index 0000000..3559372 --- /dev/null +++ b/src/rw/RwHelper.cpp @@ -0,0 +1,735 @@ +#define WITHD3D +#include "common.h" +#include + +#include "RwHelper.h" +#include "Timecycle.h" +#include "skeleton.h" +#include "Debug.h" +#include "MBlur.h" +#if !defined(FINAL) || defined(DEBUGMENU) +#include "rtcharse.h" +#endif +#ifndef FINAL +RtCharset *debugCharset; +bool bDebugRenderGroups; +#endif + +#ifdef PS2_ALPHA_TEST +bool gPS2alphaTest = true; +#else +bool gPS2alphaTest = false; +#endif +bool gBackfaceCulling = true; + +#if !defined(FINAL) || defined(DEBUGMENU) +static bool charsetOpen; +void OpenCharsetSafe() +{ + if(!charsetOpen) + RtCharsetOpen(); + charsetOpen = true; +} +#endif + +void CreateDebugFont() +{ +#ifndef FINAL + RwRGBA color = { 255, 255, 128, 255 }; + RwRGBA colorbg = { 0, 0, 0, 0 }; + OpenCharsetSafe(); + debugCharset = RtCharsetCreate(&color, &colorbg); +#endif +} + +void DestroyDebugFont() +{ +#ifndef FINAL + RtCharsetDestroy(debugCharset); + RtCharsetClose(); + charsetOpen = false; +#endif +} + +void ObrsPrintfString(const char *str, short x, short y) +{ +#ifndef FINAL + RtCharsetPrintBuffered(debugCharset, str, x*8, y*16, true); +#endif +} + +void FlushObrsPrintfs() +{ +#ifndef FINAL + RtCharsetBufferFlush(); +#endif +} + +void +DefinedState(void) +{ + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSWRAP); + RwRenderStateSet(rwRENDERSTATETEXTUREPERSPECTIVE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEALPHAPRIMITIVEBUFFER, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEBORDERCOLOR, (void*)RWRGBALONG(0, 0, 0, 255)); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEFOGCOLOR, + (void*)RWRGBALONG(CTimeCycle::GetFogRed(), CTimeCycle::GetFogGreen(), CTimeCycle::GetFogBlue(), 255)); + RwRenderStateSet(rwRENDERSTATEFOGTYPE, (void*)rwFOGTYPELINEAR); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + +#ifdef LIBRW + rw::SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAGREATEREQUAL); + + rw::SetRenderState(rw::GSALPHATEST, gPS2alphaTest); +#else + // D3D stuff + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); +#endif + SetAlphaRef(2); +} + +void +SetAlphaRef(int ref) +{ +#ifdef LIBRW + rw::SetRenderState(rw::ALPHATESTREF, ref+1); +#else + RwD3D8SetRenderState(D3DRS_ALPHAREF, ref); +#endif +} + +void +SetCullMode(uint32 mode) +{ + if(gBackfaceCulling) + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)mode); + else + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); +} + +#ifndef FINAL +void +PushRendergroup(const char *name) +{ + if(!bDebugRenderGroups) + return; +#if defined(RW_OPENGL) + if(GLAD_GL_KHR_debug) + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, name); +#elif defined(RW_D3D9) + static WCHAR tmp[256]; + MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, name, -1, tmp, sizeof(tmp)); + D3DPERF_BeginEvent(0xFFFFFFFF, tmp); +#endif +} + +void +PopRendergroup(void) +{ + if(!bDebugRenderGroups) + return; +#if defined(RW_OPENGL) + if(GLAD_GL_KHR_debug) + glPopDebugGroup(); +#elif defined(RW_D3D9) + D3DPERF_EndEvent(); +#endif +} +#endif + +RwFrame* +GetFirstFrameCallback(RwFrame *child, void *data) +{ + *(RwFrame**)data = child; + return nil; +} + +RwFrame* +GetFirstChild(RwFrame *frame) +{ + RwFrame *child; + + child = nil; + RwFrameForAllChildren(frame, GetFirstFrameCallback, &child); + return child; +} + +RwObject* +GetFirstObjectCallback(RwObject *object, void *data) +{ + *(RwObject**)data = object; + return nil; +} + +RwObject* +GetFirstObject(RwFrame *frame) +{ + RwObject *obj; + + obj = nil; + RwFrameForAllObjects(frame, GetFirstObjectCallback, &obj); + return obj; +} + +RpAtomic* +GetFirstAtomicCallback(RpAtomic *atm, void *data) +{ + *(RpAtomic**)data = atm; + return nil; +} + +RpAtomic* +GetFirstAtomic(RpClump *clump) +{ + RpAtomic *atm; + + atm = nil; + RpClumpForAllAtomics(clump, GetFirstAtomicCallback, &atm); + return atm; +} + +RwTexture* +GetFirstTextureCallback(RwTexture *tex, void *data) +{ + *(RwTexture**)data = tex; + return nil; +} + +RwTexture* +GetFirstTexture(RwTexDictionary *txd) +{ + RwTexture *tex; + + tex = nil; + RwTexDictionaryForAllTextures(txd, GetFirstTextureCallback, &tex); + return tex; +} + +#ifdef PED_SKIN +static RpAtomic* +isSkinnedCb(RpAtomic *atomic, void *data) +{ + RpAtomic **pAtomic = (RpAtomic**)data; + if(*pAtomic) + return nil; // already found one + if(RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic))) + *pAtomic = atomic; // we could just return nil here directly... + return atomic; +} + +RpAtomic* +IsClumpSkinned(RpClump *clump) +{ + RpAtomic *atomic = nil; + RpClumpForAllAtomics(clump, isSkinnedCb, &atomic); + return atomic; +} + +static RpAtomic* +GetAnimHierarchyCallback(RpAtomic *atomic, void *data) +{ + *(RpHAnimHierarchy**)data = RpSkinAtomicGetHAnimHierarchy(atomic); + return nil; +} + +RpHAnimHierarchy* +GetAnimHierarchyFromSkinClump(RpClump *clump) +{ + RpHAnimHierarchy *hier = nil; + RpClumpForAllAtomics(clump, GetAnimHierarchyCallback, &hier); + return hier; +} + +static RwFrame* +GetAnimHierarchyFromClumpCB(RwFrame *frame, void *data) +{ + RpHAnimHierarchy *hier = RpHAnimFrameGetHierarchy(frame); + if(hier){ + *(RpHAnimHierarchy**)data = hier; + return nil; + } + RwFrameForAllChildren(frame, GetAnimHierarchyFromClumpCB, data); + return frame; +} + +RpHAnimHierarchy* +GetAnimHierarchyFromClump(RpClump *clump) +{ + RpHAnimHierarchy *hier = nil; + RwFrameForAllChildren(RpClumpGetFrame(clump), GetAnimHierarchyFromClumpCB, &hier); + return hier; +} + +RwFrame* +GetHierarchyFromChildNodesCB(RwFrame *frame, void *data) +{ + RpHAnimHierarchy **pHier = (RpHAnimHierarchy**)data; + RpHAnimHierarchy *hier = RpHAnimFrameGetHierarchy(frame); + if(hier == nil) + RwFrameForAllChildren(frame, GetHierarchyFromChildNodesCB, &hier); + *pHier = hier; + return nil; +} + +void +SkinGetBonePositionsToTable(RpClump *clump, RwV3d *boneTable) +{ + int i, parent; + RpAtomic *atomic; + RpSkin *skin; + RpHAnimHierarchy *hier; + int numBones; + RwMatrix m, invmat; + int stack[32]; + int sp; + + if(boneTable == nil) + return; + +// atomic = GetFirstAtomic(clump); // mobile, also VC + atomic = IsClumpSkinned(clump); // xbox, seems safer + assert(atomic); + skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic)); + assert(skin); + hier = GetAnimHierarchyFromSkinClump(clump); + assert(hier); + boneTable[0].x = 0.0f; + boneTable[0].y = 0.0f; + boneTable[0].z = 0.0f; + numBones = RpSkinGetNumBones(skin); + parent = 0; + sp = 0; +#ifdef FIX_BUGS + stack[0] = 0; // i think this is ok +#endif + for(i = 1; i < numBones; i++){ + RwMatrixCopy(&m, &RpSkinGetSkinToBoneMatrices(skin)[i]); + RwMatrixInvert(&invmat, &m); + const RwMatrix *x = RpSkinGetSkinToBoneMatrices(skin); + RwV3dTransformPoints(&boneTable[i], &invmat.pos, 1, &x[parent]); + if(HIERNODEINFO(hier)[i].flags & rpHANIMPUSHPARENTMATRIX) + stack[++sp] = parent; + if(HIERNODEINFO(hier)[i].flags & rpHANIMPOPPARENTMATRIX) + parent = stack[sp--]; + else + parent = i; + + //assert(parent >= 0 && parent < numBones); + } +} + +RpHAnimAnimation* +HAnimAnimationCreateForHierarchy(RpHAnimHierarchy *hier) +{ + int i; +#if defined FIX_BUGS || defined LIBRW + int numNodes = hier->numNodes*2; // you're supposed to have at least two KFs per node +#else + int numNodes = hier->numNodes; +#endif + RpHAnimAnimation *anim = RpHAnimAnimationCreate(rpHANIMSTDKEYFRAMETYPEID, numNodes, 0, 0.0f); + if(anim == nil) + return nil; + RpHAnimStdKeyFrame *frame; + for(i = 0; i < numNodes; i++){ + frame = (RpHAnimStdKeyFrame*)HANIMFRAME(anim, i); // games uses struct size here, not safe + frame->q.real = 1.0f; + frame->q.imag.x = frame->q.imag.y = frame->q.imag.z = 0.0f; + frame->t.x = frame->t.y = frame->t.z = 0.0f; +#if defined FIX_BUGS || defined LIBRW + // times are subtracted and divided giving NaNs + // so they can't both be 0 + frame->time = i/hier->numNodes; +#else + frame->time = 0.0f; +#endif + frame->prevFrame = nil; + } + return anim; +} + +void +RenderSkeleton(RpHAnimHierarchy *hier) +{ + int i; + int sp; + int stack[32]; + int par; + CVector p1, p2; + int numNodes = hier->numNodes; + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + p1 = mats[0].pos; + + par = 0; + sp = 0; + stack[sp++] = par; + for(i = 1; i < numNodes; i++){ + p1 = mats[par].pos; + p2 = mats[i].pos; + CDebug::AddLine(p1, p2, 0xFFFFFFFF, 0xFFFFFFFF); + if(HIERNODEINFO(hier)[i].flags & rpHANIMPUSHPARENTMATRIX) + stack[sp++] = par; + par = i; + if(HIERNODEINFO(hier)[i].flags & rpHANIMPOPPARENTMATRIX) + par = stack[--sp]; + } +} +#endif + +void +CameraSize(RwCamera * camera, RwRect * rect, + RwReal viewWindow, RwReal aspectRatio) +{ + if (camera) + { + RwVideoMode videoMode; + RwRect r; + RwRect origSize = { 0, 0, 0, 0 }; // FIX just to make the compier happy + RwV2d vw; + + RwEngineGetVideoModeInfo(&videoMode, + RwEngineGetCurrentVideoMode()); + + origSize.w = RwRasterGetWidth(RwCameraGetRaster(camera)); + origSize.h = RwRasterGetHeight(RwCameraGetRaster(camera)); + + if (!rect) + { + if (videoMode.flags & rwVIDEOMODEEXCLUSIVE) + { + /* For full screen applications, resizing the camera just doesn't + * make sense, use the video mode size. + */ + + r.x = r.y = 0; + r.w = videoMode.width; + r.h = videoMode.height; + rect = &r; + } + else + { + /* + rect not specified - reuse current values + */ + r.w = RwRasterGetWidth(RwCameraGetRaster(camera)); + r.h = RwRasterGetHeight(RwCameraGetRaster(camera)); + r.x = r.y = 0; + rect = &r; + } + } + + if (( origSize.w != rect->w ) || ( origSize.h != rect->h )) + { + RwRaster *raster; + RwRaster *zRaster; + + // BUG: game just changes camera raster's sizes, but this is a hack +#if defined FIX_BUGS || defined LIBRW + /* + * Destroy rasters... + */ + + raster = RwCameraGetRaster(camera); + if( raster ) + { + RwRasterDestroy(raster); + camera->frameBuffer = nil; + } + + zRaster = RwCameraGetZRaster(camera); + if( zRaster ) + { + RwRasterDestroy(zRaster); + camera->zBuffer = nil; + } + + /* + * Create new rasters... + */ + + raster = RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPECAMERA); + zRaster = RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPEZBUFFER); + + if( raster && zRaster ) + { + RwCameraSetRaster(camera, raster); + RwCameraSetZRaster(camera, zRaster); + } + else + { + if( raster ) + { + RwRasterDestroy(raster); + } + + if( zRaster ) + { + RwRasterDestroy(zRaster); + } + + rect->x = origSize.x; + rect->y = origSize.y; + rect->w = origSize.w; + rect->h = origSize.h; + + /* + * Use default values... + */ + raster = + RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPECAMERA); + + zRaster = + RwRasterCreate(rect->w, rect->h, 0, rwRASTERTYPEZBUFFER); + + RwCameraSetRaster(camera, raster); + RwCameraSetZRaster(camera, zRaster); + } +#else + raster = RwCameraGetRaster(camera); + zRaster = RwCameraGetZRaster(camera); + + raster->width = zRaster->width = rect->w; + raster->height = zRaster->height = rect->h; +#endif +#ifdef FIX_BUGS + if(CMBlur::BlurOn){ + CMBlur::MotionBlurClose(); + CMBlur::MotionBlurOpen(camera); + } +#endif + } + + /* Figure out the view window */ + if (videoMode.flags & rwVIDEOMODEEXCLUSIVE) + { + /* derive ratio from aspect ratio */ + vw.x = viewWindow; + vw.y = viewWindow / aspectRatio; + } + else + { + /* derive from pixel ratios */ + if (rect->w > rect->h) + { + vw.x = viewWindow; + vw.y = (rect->h * viewWindow) / rect->w; + } + else + { + vw.x = (rect->w * viewWindow) / rect->h; + vw.y = viewWindow; + } + } + + RwCameraSetViewWindow(camera, &vw); + + RsGlobal.width = rect->w; + RsGlobal.height = rect->h; + } + + return; +} + +void +CameraDestroy(RwCamera *camera) +{ + RwRaster *raster, *tmpRaster; + RwFrame *frame; + + if (camera) + { + frame = RwCameraGetFrame(camera); + if (frame) + { + RwFrameDestroy(frame); + } + + raster = RwCameraGetRaster(camera); + if (raster) + { + tmpRaster = RwRasterGetParent(raster); + + RwRasterDestroy(raster); + + if ((tmpRaster != nil) && (tmpRaster != raster)) + { + RwRasterDestroy(tmpRaster); + } + } + + raster = RwCameraGetZRaster(camera); + if (raster) + { + tmpRaster = RwRasterGetParent(raster); + + RwRasterDestroy(raster); + + if ((tmpRaster != nil) && (tmpRaster != raster)) + { + RwRasterDestroy(tmpRaster); + } + } + + RwCameraDestroy(camera); + } + + return; +} + +RwCamera * +CameraCreate(RwInt32 width, RwInt32 height, RwBool zBuffer) +{ + RwCamera *camera; + + camera = RwCameraCreate(); + + if (camera) + { + RwCameraSetFrame(camera, RwFrameCreate()); + RwCameraSetRaster(camera, + RwRasterCreate(0, 0, 0, rwRASTERTYPECAMERA)); + + if (zBuffer) + { + RwCameraSetZRaster(camera, + RwRasterCreate(0, 0, 0, + rwRASTERTYPEZBUFFER)); + } + + /* now check that everything is valid */ + if (RwCameraGetFrame(camera) && + RwCameraGetRaster(camera) && + RwRasterGetParent(RwCameraGetRaster(camera)) && + (!zBuffer || (RwCameraGetZRaster(camera) && + RwRasterGetParent(RwCameraGetZRaster + (camera))))) + { + /* everything OK */ + return (camera); + } + } + + /* if we're here then an error must have occurred so clean up */ + + CameraDestroy(camera); + return (nil); +} + +#ifdef LIBRW +#include +#include "VehicleModelInfo.h" + +int32 +findPlatform(rw::Atomic *a) +{ + rw::Geometry *g = a->geometry; + if(g->instData) + return g->instData->platform; + return 0; +} + +// in CVehicleModelInfo in VC +static RpMaterial* +GetMatFXEffectMaterialCB(RpMaterial *material, void *data) +{ + if(RpMatFXMaterialGetEffects(material) == rpMATFXEFFECTNULL) + return material; + *(int*)data = RpMatFXMaterialGetEffects(material); + return nil; +} + +// Game doesn't read atomic extensions so we never get any other than the default pipe, +// but we need it for uninstancing +void +attachPipe(rw::Atomic *atomic) +{ + if(RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic))) + atomic->pipeline = rw::skinGlobals.pipelines[rw::platform]; + else{ + int fx = rpMATFXEFFECTNULL; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), GetMatFXEffectMaterialCB, &fx); + if(fx != rpMATFXEFFECTNULL) + RpMatFXAtomicEnableEffects(atomic); + } +} + +// Attach pipes for the platform we have native data for so we can uninstance +void +switchPipes(rw::Atomic *a, int32 platform) +{ + if(a->pipeline && a->pipeline->platform != platform){ + uint32 plgid = a->pipeline->pluginID; + switch(plgid){ + // assume default pipe won't be attached explicitly + case rw::ID_SKIN: + a->pipeline = rw::skinGlobals.pipelines[platform]; + break; + case rw::ID_MATFX: + a->pipeline = rw::matFXGlobals.pipelines[platform]; + break; + } + } +} + +RpAtomic* +ConvertPlatformAtomic(RpAtomic *atomic, void *data) +{ + int32 driver = rw::platform; + int32 platform = findPlatform(atomic); + if(platform != 0 && platform != driver){ + attachPipe(atomic); // kludge + rw::ObjPipeline *origPipe = atomic->pipeline; + rw::platform = platform; + switchPipes(atomic, rw::platform); + if(atomic->geometry->flags & rw::Geometry::NATIVE) + atomic->uninstance(); + // no ADC in this game + //rw::ps2::unconvertADC(atomic->geometry); + rw::platform = driver; + atomic->pipeline = origPipe; + } + return atomic; +} +#endif + +#if defined(FIX_BUGS) && defined(GTA_PC) +RwUInt32 saved_alphafunc, saved_alpharef; + +void +SetAlphaTest(RwUInt32 alpharef) +{ +#ifdef LIBRW + saved_alphafunc = rw::GetRenderState(rw::ALPHATESTFUNC); + saved_alpharef = rw::GetRenderState(rw::ALPHATESTREF); + + rw::SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAGREATEREQUAL); + rw::SetRenderState(rw::ALPHATESTREF, 0); +#else + RwD3D8GetRenderState(D3DRS_ALPHAFUNC, &saved_alphafunc); + RwD3D8GetRenderState(D3DRS_ALPHAREF, &saved_alpharef); + + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL); + RwD3D8SetRenderState(D3DRS_ALPHAREF, alpharef); +#endif +} + +void +RestoreAlphaTest() +{ +#ifdef LIBRW + rw::SetRenderState(rw::ALPHATESTFUNC, saved_alphafunc); + rw::SetRenderState(rw::ALPHATESTREF, saved_alpharef); +#else + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, saved_alphafunc); + RwD3D8SetRenderState(D3DRS_ALPHAREF, saved_alpharef); +#endif +} +#endif diff --git a/src/rw/RwHelper.h b/src/rw/RwHelper.h new file mode 100644 index 0000000..0e04aec --- /dev/null +++ b/src/rw/RwHelper.h @@ -0,0 +1,63 @@ +#pragma once + +extern bool bDebugRenderGroups; +extern bool gPS2alphaTest; + +void OpenCharsetSafe(); +void CreateDebugFont(); +void DestroyDebugFont(); +void ObrsPrintfString(const char *str, short x, short y); +void FlushObrsPrintfs(); +void DefinedState(void); +void SetAlphaRef(int ref); +void SetCullMode(uint32 mode); +RwFrame *GetFirstChild(RwFrame *frame); +RwObject *GetFirstObject(RwFrame *frame); +RpAtomic *GetFirstAtomic(RpClump *clump); +RwTexture *GetFirstTexture(RwTexDictionary *txd); + +#ifdef PED_SKIN +RpAtomic *IsClumpSkinned(RpClump *clump); +RpHAnimHierarchy *GetAnimHierarchyFromSkinClump(RpClump *clump); // get from atomic +RpHAnimHierarchy *GetAnimHierarchyFromClump(RpClump *clump); // get from frame +RwFrame *GetHierarchyFromChildNodesCB(RwFrame *frame, void *data); +void SkinGetBonePositionsToTable(RpClump *clump, RwV3d *boneTable); +RpHAnimAnimation *HAnimAnimationCreateForHierarchy(RpHAnimHierarchy *hier); +RpAtomic *AtomicRemoveAnimFromSkinCB(RpAtomic *atomic, void *data); +void RenderSkeleton(RpHAnimHierarchy *hier); +#endif + +RwTexDictionary *RwTexDictionaryGtaStreamRead(RwStream *stream); +RwTexDictionary *RwTexDictionaryGtaStreamRead1(RwStream *stream); +RwTexDictionary *RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict); +void ReadVideoCardCapsFile(uint32&, uint32&, uint32&, uint32&); +bool CheckVideoCardCaps(void); +void WriteVideoCardCapsFile(void); +void ConvertingTexturesScreen(uint32, uint32, const char*); +void DealWithTxdWriteError(uint32, uint32, const char*); +bool CreateTxdImageForVideoCard(); + +bool RpClumpGtaStreamRead1(RwStream *stream); +RpClump *RpClumpGtaStreamRead2(RwStream *stream); +void RpClumpGtaCancelStream(void); + +void CameraSize(RwCamera *camera, + RwRect *rect, + RwReal viewWindow, + RwReal aspectRatio); +void CameraDestroy(RwCamera *camera); +RwCamera *CameraCreate(RwInt32 width, + RwInt32 height, + RwBool zBuffer); + + + +RpAtomic *ConvertPlatformAtomic(RpAtomic *atomic, void *data); + +#if defined(FIX_BUGS) && defined (GTA_PC) +void SetAlphaTest(RwUInt32 alpharef); +void RestoreAlphaTest(); +#else +#define SetAlphaTest(a) (0) +#define RestoreAlphaTest() (0) +#endif \ No newline at end of file diff --git a/src/rw/RwMatFX.cpp b/src/rw/RwMatFX.cpp new file mode 100644 index 0000000..c8384b0 --- /dev/null +++ b/src/rw/RwMatFX.cpp @@ -0,0 +1,312 @@ +#ifndef LIBRW + +#define WITHD3D +#include "common.h" +#include "rpmatfx.h" + +struct MatFXNothing { int pad[5]; int effect; }; + +struct MatFXBump +{ + RwFrame *bumpFrame; + RwTexture *bumpedTex; + RwTexture *bumpTex; + float negBumpCoefficient; + int pad; + int effect; +}; + +struct MatFXEnv +{ + RwFrame *envFrame; + RwTexture *envTex; + float envCoeff; + int envFBalpha; + int pad; + int effect; +}; + +struct MatFXDual +{ + RwTexture *dualTex; + RwInt32 srcBlend; + RwInt32 dstBlend; +}; + + +struct MatFX +{ + union { + MatFXNothing n; + MatFXBump b; + MatFXEnv e; + MatFXDual d; + } fx[2]; + int effects; +}; + +extern "C" { + extern int MatFXMaterialDataOffset; + extern int MatFXAtomicDataOffset; + + void _rpMatFXD3D8AtomicMatFXEnvRender(RxD3D8InstanceData* inst, int flags, int sel, RwTexture* texture, RwTexture* envMap); + void _rpMatFXD3D8AtomicMatFXRenderBlack(RxD3D8InstanceData *inst); + void _rpMatFXD3D8AtomicMatFXBumpMapRender(RxD3D8InstanceData *inst, int flags, RwTexture *texture, RwTexture *bumpMap, RwTexture *envMap); + void _rpMatFXD3D8AtomicMatFXDualPassRender(RxD3D8InstanceData *inst, int flags, RwTexture *texture, RwTexture *dualTexture); +} + + +#ifdef PS2_MATFX + +void +_rpMatFXD3D8AtomicMatFXDefaultRender(RxD3D8InstanceData *inst, int flags, RwTexture *texture) +{ + if(flags & (rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2) && texture) + RwD3D8SetTexture(texture, 0); + else + RwD3D8SetTexture(nil, 0); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(inst->vertexAlpha || inst->material->color.alpha != 0xFF)); + RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, inst->vertexAlpha != 0); + RwD3D8SetPixelShader(0); + RwD3D8SetVertexShader(inst->vertexShader); + RwD3D8SetStreamSource(0, inst->vertexBuffer, inst->stride); + + if(inst->indexBuffer){ + RwD3D8SetIndices(inst->indexBuffer, inst->baseIndex); + RwD3D8DrawIndexedPrimitive(inst->primType, 0, inst->numVertices, 0, inst->numIndices); + }else + RwD3D8DrawPrimitive(inst->primType, inst->baseIndex, inst->numVertices); +} + +// map [-1; -1] -> [0; 1], flip V +static RwMatrix scalenormal = { + { 0.5f, 0.0f, 0.0f }, 0, + { 0.0f, -0.5f, 0.0f }, 0, + { 0.0f, 0.0f, 1.0f }, 0, + { 0.5f, 0.5f, 0.0f }, 0, + +}; + +// flipped U for PS2 +static RwMatrix scalenormal_flipU = { + { -0.5f, 0.0f, 0.0f }, 0, + { 0.0f, -0.5f, 0.0f }, 0, + { 0.0f, 0.0f, 1.0f }, 0, + { 0.5f, 0.5f, 0.0f }, 0, + +}; + +void +ApplyEnvMapTextureMatrix(RwTexture *tex, int n, RwFrame *frame) +{ + RwD3D8SetTexture(tex, n); + RwD3D8SetTextureStageState(n, D3DRS_ALPHAREF, 2); + RwD3D8SetTextureStageState(n, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL); + if(frame){ + RwMatrix *envframemat = RwMatrixCreate(); + RwMatrix *tmpmat = RwMatrixCreate(); + RwMatrix *envmat = RwMatrixCreate(); + + RwMatrixInvert(envframemat, RwFrameGetLTM(frame)); + // PS2 + // can this be simplified? + *tmpmat = *RwFrameGetLTM(RwCameraGetFrame((RwCamera*)RWSRCGLOBAL(curCamera))); + RwV3dNegate(&tmpmat->right, &tmpmat->right); + tmpmat->flags = 0; + tmpmat->pos.x = 0.0f; + tmpmat->pos.y = 0.0f; + tmpmat->pos.z = 0.0f; + RwMatrixMultiply(envmat, tmpmat, envframemat); + *tmpmat = *envmat; + // important because envframemat can have a translation that we don't like + tmpmat->pos.x = 0.0f; + tmpmat->pos.y = 0.0f; + tmpmat->pos.z = 0.0f; + // for some reason we flip in U as well + RwMatrixMultiply(envmat, tmpmat, &scalenormal_flipU); + + RwD3D8SetTransform(D3DTS_TEXTURE0+n, envmat); + + RwMatrixDestroy(envmat); + RwMatrixDestroy(tmpmat); + RwMatrixDestroy(envframemat); + }else + RwD3D8SetTransform(D3DTS_TEXTURE0+n, &scalenormal); +} + +void +_rpMatFXD3D8AtomicMatFXEnvRender_ps2(RxD3D8InstanceData *inst, int flags, int sel, RwTexture *texture, RwTexture *envMap) +{ + MatFX *matfx = *RWPLUGINOFFSET(MatFX*, inst->material, MatFXMaterialDataOffset); + MatFXEnv *env = &matfx->fx[sel].e; + + uint8 intens = (uint8)(env->envCoeff*255.0f); + + if(intens == 0 || envMap == nil){ + if(sel == 0) + _rpMatFXD3D8AtomicMatFXDefaultRender(inst, flags, texture); + return; + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(inst->vertexAlpha || inst->material->color.alpha != 0xFF)); + if(flags & (rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2) && texture) + RwD3D8SetTexture(texture, 0); + else + RwD3D8SetTexture(nil, 0); + RwD3D8SetPixelShader(0); + RwD3D8SetVertexShader(inst->vertexShader); + RwD3D8SetStreamSource(0, inst->vertexBuffer, inst->stride); + RwD3D8SetIndices(inst->indexBuffer, inst->baseIndex); + if(inst->indexBuffer) + RwD3D8DrawIndexedPrimitive(inst->primType, 0, inst->numVertices, 0, inst->numIndices); + else + RwD3D8DrawPrimitive(inst->primType, inst->baseIndex, inst->numVertices); + + // Effect pass + + ApplyEnvMapTextureMatrix(envMap, 0, env->envFrame); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwUInt32 src, dst, lighting, zwrite, fog, fogcol; + RwRenderStateGet(rwRENDERSTATESRCBLEND, &src); + RwRenderStateGet(rwRENDERSTATEDESTBLEND, &dst); + + // This is of course not using framebuffer alpha, + // but if the diffuse texture had no alpha, the result should actually be rather the same + if(env->envFBalpha) + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + else + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting); + RwD3D8GetRenderState(D3DRS_ZWRITEENABLE, &zwrite); + RwD3D8GetRenderState(D3DRS_FOGENABLE, &fog); + RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, FALSE); + if(fog){ + RwD3D8GetRenderState(D3DRS_FOGCOLOR, &fogcol); + RwD3D8SetRenderState(D3DRS_FOGCOLOR, 0); + } + + D3DCOLOR texfactor = D3DCOLOR_RGBA(intens, intens, intens, intens); + RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, texfactor); + RwD3D8SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE); + RwD3D8SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + RwD3D8SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TFACTOR); + // alpha unused + //RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + //RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + //RwD3D8SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); + + if(inst->indexBuffer) + RwD3D8DrawIndexedPrimitive(inst->primType, 0, inst->numVertices, 0, inst->numIndices); + else + RwD3D8DrawPrimitive(inst->primType, inst->baseIndex, inst->numVertices); + + // Reset states + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)src); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)dst); + RwD3D8SetRenderState(D3DRS_LIGHTING, lighting); + RwD3D8SetRenderState(D3DRS_ZWRITEENABLE, zwrite); + if(fog) + RwD3D8SetRenderState(D3DRS_FOGCOLOR, fogcol); + RwD3D8SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + RwD3D8SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + RwD3D8SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, 0); + RwD3D8SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0); +} + +void +_rwD3D8EnableClippingIfNeeded(void *object, RwUInt8 type) +{ + int clip; + if (type == rpATOMIC) + clip = !RwD3D8CameraIsSphereFullyInsideFrustum(RwCameraGetCurrentCameraMacro(), RpAtomicGetWorldBoundingSphere((RpAtomic *)object)); + else + clip = !RwD3D8CameraIsBBoxFullyInsideFrustum(RwCameraGetCurrentCameraMacro(), &((RpWorldSector *)object)->tightBoundingBox); + RwD3D8SetRenderState(D3DRS_CLIPPING, clip); +} + +void +_rwD3D8AtomicMatFXRenderCallback(RwResEntry *repEntry, void *object, RwUInt8 type, RwUInt32 flags) +{ + RwBool lighting; + RwBool forceBlack; + RxD3D8ResEntryHeader *header; + RxD3D8InstanceData *inst; + RwInt32 i; + + if (flags & rpGEOMETRYPRELIT) { + RwD3D8SetRenderState(D3DRS_COLORVERTEX, 1); + RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1); + } else { + RwD3D8SetRenderState(D3DRS_COLORVERTEX, 0); + RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + } + + _rwD3D8EnableClippingIfNeeded(object, type); + + RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting); + if (lighting || flags & rpGEOMETRYPRELIT) { + forceBlack = FALSE; + } else { + forceBlack = TRUE; + RwD3D8SetTexture(nil, 0); + RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, D3DCOLOR_RGBA(0, 0, 0, 255)); + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); + } + + header = (RxD3D8ResEntryHeader *)(repEntry + 1); + inst = (RxD3D8InstanceData *)(header + 1); + for (i = 0; i < header->numMeshes; i++) { + if (forceBlack) + _rpMatFXD3D8AtomicMatFXRenderBlack(inst); + else { + if (lighting) + RwD3D8SetSurfaceProperties(&inst->material->color, &inst->material->surfaceProps, flags & rpGEOMETRYMODULATEMATERIALCOLOR); + MatFX *matfx = *RWPLUGINOFFSET(MatFX *, inst->material, MatFXMaterialDataOffset); + int effect = matfx ? matfx->effects : rpMATFXEFFECTNULL; + switch (effect) { + case rpMATFXEFFECTNULL: + default: + _rpMatFXD3D8AtomicMatFXDefaultRender(inst, flags, inst->material->texture); + break; + case rpMATFXEFFECTBUMPMAP: + _rpMatFXD3D8AtomicMatFXBumpMapRender(inst, flags, inst->material->texture, matfx->fx[0].b.bumpedTex, nil); + break; + case rpMATFXEFFECTENVMAP: + { + // TODO: matfx switch in the settings + //_rpMatFXD3D8AtomicMatFXEnvRender(inst, flags, 0, inst->material->texture, matfx->fx[0].e.envTex); + _rpMatFXD3D8AtomicMatFXEnvRender_ps2(inst, flags, 0, inst->material->texture, matfx->fx[0].e.envTex); + break; + } + case rpMATFXEFFECTBUMPENVMAP: + _rpMatFXD3D8AtomicMatFXBumpMapRender(inst, flags, inst->material->texture, matfx->fx[0].b.bumpedTex, matfx->fx[1].e.envTex); + break; + case rpMATFXEFFECTDUAL: + _rpMatFXD3D8AtomicMatFXDualPassRender(inst, flags, inst->material->texture, matfx->fx[0].d.dualTex); + break; + } + } + inst++; + } + + if (forceBlack) { + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + } +} + +void +ReplaceMatFxCallback() +{ + RxD3D8AllInOneSetRenderCallBack( + RxPipelineFindNodeByName(RpMatFXGetD3D8Pipeline(rpMATFXD3D8ATOMICPIPELINE), RxNodeDefinitionGetD3D8AtomicAllInOne()->name, nil, nil), + _rwD3D8AtomicMatFXRenderCallback); + +} +#endif // PS2_MATFX + +#endif // !LIBRW diff --git a/src/rw/RwPS2AlphaTest.cpp b/src/rw/RwPS2AlphaTest.cpp new file mode 100644 index 0000000..c0d6835 --- /dev/null +++ b/src/rw/RwPS2AlphaTest.cpp @@ -0,0 +1,247 @@ +#ifndef LIBRW + +#define WITHD3D +#include "common.h" +#ifdef PS2_ALPHA_TEST +#include "rwcore.h" + +extern "C" { +RwBool _rwD3D8RenderStateIsVertexAlphaEnable(void); +RwBool _rwD3D8RenderStateVertexAlphaEnable(RwBool enable); +RwRaster *_rwD3D8RWGetRasterStage(RwUInt32 stage); +} + +extern bool gPS2alphaTest; + +void +_rxD3D8DualPassRenderCallback(RwResEntry *repEntry, void *object, RwUInt8 type, RwUInt32 flags) +{ + RxD3D8ResEntryHeader *resEntryHeader; + RxD3D8InstanceData *instancedData; + RwInt32 numMeshes; + RwBool lighting; + RwBool vertexAlphaBlend; + RwBool forceBlack; + RwUInt32 ditherEnable; + RwUInt32 shadeMode; + void *lastVertexBuffer; + + /* Get lighting state */ + RwD3D8GetRenderState(D3DRS_LIGHTING, &lighting); + + forceBlack = FALSE; + + if (lighting) { + if (flags & rxGEOMETRY_PRELIT) { + /* Emmisive color from the vertex colors */ + RwD3D8SetRenderState(D3DRS_COLORVERTEX, TRUE); + RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1); + } else { + /* Emmisive color from material, set to black in the submit node */ + RwD3D8SetRenderState(D3DRS_COLORVERTEX, FALSE); + RwD3D8SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + } + } else { + if ((flags & rxGEOMETRY_PRELIT) == 0) { + forceBlack = TRUE; + + RwD3D8GetRenderState(D3DRS_DITHERENABLE, &ditherEnable); + RwD3D8GetRenderState(D3DRS_SHADEMODE, &shadeMode); + + RwD3D8SetRenderState(D3DRS_TEXTUREFACTOR, 0xff000000); + RwD3D8SetRenderState(D3DRS_DITHERENABLE, FALSE); + RwD3D8SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT); + } + } + + /* Enable clipping */ + if (type == rpATOMIC) { + RpAtomic *atomic; + RwCamera *cam; + + atomic = (RpAtomic *)object; + + cam = RwCameraGetCurrentCamera(); + // RWASSERT(cam); + + if (RwD3D8CameraIsSphereFullyInsideFrustum(cam, RpAtomicGetWorldBoundingSphere(atomic))) { + RwD3D8SetRenderState(D3DRS_CLIPPING, FALSE); + } else { + RwD3D8SetRenderState(D3DRS_CLIPPING, TRUE); + } + } else { + RpWorldSector *worldSector; + RwCamera *cam; + + worldSector = (RpWorldSector *)object; + + cam = RwCameraGetCurrentCamera(); + // RWASSERT(cam); + + if (RwD3D8CameraIsBBoxFullyInsideFrustum(cam, RpWorldSectorGetTightBBox(worldSector))) { + RwD3D8SetRenderState(D3DRS_CLIPPING, FALSE); + } else { + RwD3D8SetRenderState(D3DRS_CLIPPING, TRUE); + } + } + + /* Set texture to NULL if hasn't any texture flags */ + if ((flags & (rxGEOMETRY_TEXTURED | rpGEOMETRYTEXTURED2)) == 0) { + RwD3D8SetTexture(NULL, 0); + + if (forceBlack) { + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); + + RwD3D8SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + } + } + + /* Get vertex alpha Blend state */ + vertexAlphaBlend = _rwD3D8RenderStateIsVertexAlphaEnable(); + + /* Set Last vertex buffer to force the call */ + lastVertexBuffer = (void *)0xffffffff; + + /* Get the instanced data */ + resEntryHeader = (RxD3D8ResEntryHeader *)(repEntry + 1); + instancedData = (RxD3D8InstanceData *)(resEntryHeader + 1); + + /* + * Data shared between meshes + */ + + /* + * Set the Default Pixel shader + */ + RwD3D8SetPixelShader(0); + + /* + * Vertex shader + */ + RwD3D8SetVertexShader(instancedData->vertexShader); + + /* Get the number of meshes */ + numMeshes = resEntryHeader->numMeshes; + while (numMeshes--) { + // RWASSERT(instancedData->material != NULL); + + if ((flags & (rxGEOMETRY_TEXTURED | rpGEOMETRYTEXTURED2))) { + RwD3D8SetTexture(instancedData->material->texture, 0); + + if (forceBlack) { + /* Only change the colorop, we need to use the texture alpha channel */ + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); + } + } + + if (instancedData->vertexAlpha || (0xFF != instancedData->material->color.alpha)) { + if (!vertexAlphaBlend) { + vertexAlphaBlend = TRUE; + + _rwD3D8RenderStateVertexAlphaEnable(TRUE); + } + } else { + if (vertexAlphaBlend) { + vertexAlphaBlend = FALSE; + + _rwD3D8RenderStateVertexAlphaEnable(FALSE); + } + } + + if (lighting) { + if (instancedData->vertexAlpha) { + RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1); + } else { + RwD3D8SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL); + } + + RwD3D8SetSurfaceProperties(&instancedData->material->color, &instancedData->material->surfaceProps, (flags & rxGEOMETRY_MODULATE)); + } + + /* + * Render + */ + + /* Set the stream source */ + if (lastVertexBuffer != instancedData->vertexBuffer) { + RwD3D8SetStreamSource(0, instancedData->vertexBuffer, instancedData->stride); + + lastVertexBuffer = instancedData->vertexBuffer; + } + if (!gPS2alphaTest) { + /* Set the Index buffer */ + if (instancedData->indexBuffer != NULL) { + RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex); + + /* Draw the indexed primitive */ + RwD3D8DrawIndexedPrimitive((D3DPRIMITIVETYPE)instancedData->primType, 0, instancedData->numVertices, 0, instancedData->numIndices); + } else { + RwD3D8DrawPrimitive((D3DPRIMITIVETYPE)instancedData->primType, instancedData->baseIndex, instancedData->numVertices); + } + } else { + RwD3D8SetIndices(instancedData->indexBuffer, instancedData->baseIndex); + + int hasAlpha, alphafunc, alpharef, zwrite; + RwD3D8GetRenderState(D3DRS_ALPHABLENDENABLE, &hasAlpha); + RwD3D8GetRenderState(D3DRS_ZWRITEENABLE, &zwrite); + if (hasAlpha && zwrite) { + RwD3D8GetRenderState(D3DRS_ALPHAFUNC, &alphafunc); + RwD3D8GetRenderState(D3DRS_ALPHAREF, &alpharef); + + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL); + RwD3D8SetRenderState(D3DRS_ALPHAREF, 128); + + if (instancedData->indexBuffer) + RwD3D8DrawIndexedPrimitive(instancedData->primType, 0, instancedData->numVertices, 0, instancedData->numIndices); + else + RwD3D8DrawPrimitive(instancedData->primType, instancedData->baseIndex, instancedData->numVertices); + + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_LESS); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + + if (instancedData->indexBuffer) + RwD3D8DrawIndexedPrimitive(instancedData->primType, 0, instancedData->numVertices, 0, instancedData->numIndices); + else + RwD3D8DrawPrimitive(instancedData->primType, instancedData->baseIndex, instancedData->numVertices); + + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, alphafunc); + RwD3D8SetRenderState(D3DRS_ALPHAREF, alpharef); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + } else { + if (instancedData->indexBuffer) + RwD3D8DrawIndexedPrimitive(instancedData->primType, 0, instancedData->numVertices, 0, instancedData->numIndices); + else + RwD3D8DrawPrimitive(instancedData->primType, instancedData->baseIndex, instancedData->numVertices); + } + } + + /* Move onto the next instancedData */ + instancedData++; + } + + if (forceBlack) { + RwD3D8SetRenderState(D3DRS_DITHERENABLE, ditherEnable); + RwD3D8SetRenderState(D3DRS_SHADEMODE, shadeMode); + + if (_rwD3D8RWGetRasterStage(0)) { + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + } else { + RwD3D8SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + RwD3D8SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + } + } +} + +void +ReplaceAtomicPipeCallback() +{ + RxD3D8AllInOneSetRenderCallBack(RxPipelineFindNodeByName(RXPIPELINEGLOBAL(platformAtomicPipeline), RxNodeDefinitionGetD3D8AtomicAllInOne()->name, nil, nil), + _rxD3D8DualPassRenderCallback); +} + +#endif // PS2_ALPHA_TEST + +#endif // !LIBRW \ No newline at end of file diff --git a/src/rw/TexRead.cpp b/src/rw/TexRead.cpp new file mode 100644 index 0000000..98e7d18 --- /dev/null +++ b/src/rw/TexRead.cpp @@ -0,0 +1,459 @@ +#pragma warning( push ) +#pragma warning( disable : 4005) +#pragma warning( pop ) +#define FORCE_PC_SCALING +#include "common.h" +#ifdef ANISOTROPIC_FILTERING +#include "rpanisot.h" +#endif +#include "crossplatform.h" +#include "platform.h" + +#include "Timer.h" +#ifdef GTA_PC +#include "FileMgr.h" +#include "Pad.h" +#include "main.h" +#include "Directory.h" +#include "Streaming.h" +#include "TxdStore.h" +#include "CdStream.h" +#include "Font.h" +#include "Sprite2d.h" +#include "Text.h" +#include "RwHelper.h" +#include "Frontend.h" +#endif //GTA_PC + +float texLoadTime; +int32 texNumLoaded; + +#ifdef LIBRW +#define READNATIVE(stream, tex, size) rwNativeTextureHackRead(stream, tex, size) +#else +#define READNATIVE(stream, tex, size) RWSRCGLOBAL(stdFunc[rwSTANDARDNATIVETEXTUREREAD](stream, tex, size)) +#endif + +RwTexture* +RwTextureGtaStreamRead(RwStream *stream) +{ + RwUInt32 size, version; + RwTexture *tex; + + if(!RwStreamFindChunk(stream, rwID_TEXTURENATIVE, &size, &version)) + return nil; + + float preloadTime = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); + + if(!READNATIVE(stream, &tex, size)) + return nil; + + if (gGameState == GS_INIT_PLAYING_GAME) { + texLoadTime = (texNumLoaded * texLoadTime + (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond() - preloadTime) / (float)(texNumLoaded+1); + texNumLoaded++; + } + +#ifdef ANISOTROPIC_FILTERING + if(tex && RpAnisotGetMaxSupportedMaxAnisotropy() > 1) // BUG? this was RpAnisotTextureGetMaxAnisotropy, but that doesn't make much sense + RpAnisotTextureSetMaxAnisotropy(tex, RpAnisotGetMaxSupportedMaxAnisotropy()); +#endif + + return tex; +} + +RwTexture* +destroyTexture(RwTexture *texture, void *data) +{ + RwTextureDestroy(texture); + return texture; +} + +RwTexDictionary* +RwTexDictionaryGtaStreamRead(RwStream *stream) +{ + RwUInt32 size, version; + RwInt32 numTextures; + RwTexDictionary *texDict; + RwTexture *tex; + + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return nil; + if(RwStreamRead(stream, &numTextures, size) != size) + return nil; + + texDict = RwTexDictionaryCreate(); + if(texDict == nil) + return nil; + + while(numTextures--){ + tex = RwTextureGtaStreamRead(stream); + if(tex == nil){ + RwTexDictionaryForAllTextures(texDict, destroyTexture, nil); + RwTexDictionaryDestroy(texDict); + return nil; + } + RwTexDictionaryAddTexture(texDict, tex); + } + + return texDict; +} + +static int32 numberTextures = -1; +static int32 streamPosition; + +RwTexDictionary* +RwTexDictionaryGtaStreamRead1(RwStream *stream) +{ + RwUInt32 size, version; + RwInt32 numTextures; + RwTexDictionary *texDict; + RwTexture *tex; + + numberTextures = 0; + if(!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version)) + return nil; + assert(size == 4); + if(RwStreamRead(stream, &numTextures, size) != size) + return nil; + + texDict = RwTexDictionaryCreate(); + if(texDict == nil) + return nil; + + numberTextures = numTextures/2; + + while(numTextures > numberTextures){ + numTextures--; + + tex = RwTextureGtaStreamRead(stream); + if(tex == nil){ + RwTexDictionaryForAllTextures(texDict, destroyTexture, nil); + RwTexDictionaryDestroy(texDict); + return nil; + } + RwTexDictionaryAddTexture(texDict, tex); + } + + numberTextures = numTextures; + streamPosition = STREAMPOS(stream); + + return texDict; +} + +RwTexDictionary* +RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict) +{ + RwTexture *tex; + + RwStreamSkip(stream, streamPosition - STREAMPOS(stream)); + + while(numberTextures--){ + tex = RwTextureGtaStreamRead(stream); + if(tex == nil){ + RwTexDictionaryForAllTextures(texDict, destroyTexture, nil); + RwTexDictionaryDestroy(texDict); + return nil; + } + RwTexDictionaryAddTexture(texDict, tex); + } + + return texDict; +} + +#ifdef GTA_PC + +#ifdef LIBRW + +#define CAPSVERSION 0 + +struct GPUcaps +{ + uint32 version; // so we can force regeneration easily + uint32 platform; + uint32 subplatform; + uint32 dxtSupport; +}; + +static void +GetGPUcaps(GPUcaps *caps) +{ + caps->version = CAPSVERSION; + caps->platform = rw::platform; + caps->subplatform = 0; + caps->dxtSupport = 0; + // TODO: more later +#ifdef RW_GL3 + caps->subplatform = rw::gl3::gl3Caps.gles; + caps->dxtSupport = rw::gl3::gl3Caps.dxtSupported; +#endif +#ifdef RW_D3D9 + caps->dxtSupport = 1; // TODO, probably +#endif +} + +void +ReadVideoCardCapsFile(GPUcaps *caps) +{ + memset(caps, 0, sizeof(GPUcaps)); + + int32 file = CFileMgr::OpenFile("DATA\\CAPS.DAT", "rb"); + if (file != 0) { + CFileMgr::Read(file, (char*)&caps->version, 4); + CFileMgr::Read(file, (char*)&caps->platform, 4); + CFileMgr::Read(file, (char*)&caps->subplatform, 4); + CFileMgr::Read(file, (char*)&caps->dxtSupport, 4); + CFileMgr::CloseFile(file); + } +} + +bool +CheckVideoCardCaps(void) +{ + GPUcaps caps, fcaps; + GetGPUcaps(&caps); + ReadVideoCardCapsFile(&fcaps); + return caps.version != fcaps.version || + caps.platform != fcaps.platform || + caps.subplatform != fcaps.subplatform || + caps.dxtSupport != fcaps.dxtSupport; +} + +void +WriteVideoCardCapsFile(void) +{ + GPUcaps caps; + GetGPUcaps(&caps); + int32 file = CFileMgr::OpenFile("DATA\\CAPS.DAT", "wb"); + if (file != 0) { + CFileMgr::Write(file, (char*)&caps.version, 4); + CFileMgr::Write(file, (char*)&caps.platform, 4); + CFileMgr::Write(file, (char*)&caps.subplatform, 4); + CFileMgr::Write(file, (char*)&caps.dxtSupport, 4); + CFileMgr::CloseFile(file); + } +} + +#else +extern "C" RwInt32 _rwD3D8FindCorrectRasterFormat(RwRasterType type, RwInt32 flags); +void +ReadVideoCardCapsFile(uint32 &cap32, uint32 &cap24, uint32 &cap16, uint32 &cap8) +{ + cap32 = UINT32_MAX; + cap24 = UINT32_MAX; + cap16 = UINT32_MAX; + cap8 = UINT32_MAX; + + int32 file = CFileMgr::OpenFile("DATA\\CAPS.DAT", "rb"); + if (file != 0) { + CFileMgr::Read(file, (char*)&cap32, 4); + CFileMgr::Read(file, (char*)&cap24, 4); + CFileMgr::Read(file, (char*)&cap16, 4); + CFileMgr::Read(file, (char*)&cap8, 4); + CFileMgr::CloseFile(file); + } +} + +bool +CheckVideoCardCaps(void) +{ + uint32 cap32 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT8888); + uint32 cap24 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT888); + uint32 cap16 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT1555); + uint32 cap8 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMATPAL8 | rwRASTERFORMAT8888); + uint32 fcap32, fcap24, fcap16, fcap8; + ReadVideoCardCapsFile(fcap32, fcap24, fcap16, fcap8); + return cap32 != fcap32 || cap24 != fcap24 || cap16 != fcap16 || cap8 != fcap8; +} + +void +WriteVideoCardCapsFile(void) +{ + uint32 cap32 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT8888); + uint32 cap24 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT888); + uint32 cap16 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMAT1555); + uint32 cap8 = _rwD3D8FindCorrectRasterFormat(rwRASTERTYPETEXTURE, rwRASTERFORMATPAL8 | rwRASTERFORMAT8888); + int32 file = CFileMgr::OpenFile("DATA\\CAPS.DAT", "wb"); + if (file != 0) { + CFileMgr::Write(file, (char*)&cap32, 4); + CFileMgr::Write(file, (char*)&cap24, 4); + CFileMgr::Write(file, (char*)&cap16, 4); + CFileMgr::Write(file, (char*)&cap8, 4); + CFileMgr::CloseFile(file); + } +} +#endif + +void +ConvertingTexturesScreen(uint32 num, uint32 count, const char *text) +{ + HandleExit(); + + CSprite2d *splash = LoadSplash(nil); + if (!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) + return; + + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + + CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(200.0f), SCREEN_SCALE_Y(240.0f), SCREEN_SCALE_FROM_RIGHT(200.0f), SCREEN_SCALE_Y(248.0f)), CRGBA(64, 64, 64, 255)); + CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(200.0f), SCREEN_SCALE_Y(240.0f), (SCREEN_SCALE_FROM_RIGHT(200.0f) - SCREEN_SCALE_X(200.0f)) * ((float)num / (float)count) + SCREEN_SCALE_X(200.0f), SCREEN_SCALE_Y(248.0f)), CRGBA(255, 217, 106, 255)); + CSprite2d::DrawRect(CRect(SCREEN_SCALE_X(120.0f), SCREEN_SCALE_Y(150.0f), SCREEN_SCALE_FROM_RIGHT(120.0f), SCREEN_HEIGHT - SCREEN_SCALE_Y(220.0f)), CRGBA(50, 50, 50, 210)); + + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetScale(SCREEN_SCALE_X(0.45f), SCREEN_SCALE_Y(0.7f)); + CFont::SetCentreOff(); + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(170.0f)); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 217, 106, 255)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::PrintString(SCREEN_SCALE_X(170.0f), SCREEN_SCALE_Y(160.0f), TheText.Get(text)); + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +DealWithTxdWriteError(uint32 num, uint32 count, const char *text) +{ + while (!RsGlobal.quit) { + ConvertingTexturesScreen(num, count, text); + CPad::UpdatePads(); + if (CPad::GetPad(0)->GetEscapeJustDown()) + break; + } + RsGlobal.quit = false; + LoadingScreen(nil, nil, nil); + RsGlobal.quit = true; +} + +#ifdef LIBRW +#define STREAMTELL(str) str->tell() +#else +#define STREAMTELL(str) filesys->rwftell((str)->Type.file.fpFile) +#endif + +bool +CreateTxdImageForVideoCard() +{ + uint8 *buf = new uint8[CDSTREAM_SECTOR_SIZE]; + CDirectory *pDir = new CDirectory(TXDSTORESIZE); + CDirectory::DirectoryInfo dirInfo; + + CStreaming::FlushRequestList(); + +#ifndef LIBRW + RwFileFunctions *filesys = RwOsGetFileInterface(); +#endif + + RwStream *img = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, "models\\txd.img"); + if (img == nil) { + // original code does otherwise and it leaks + delete []buf; + delete pDir; + + if (_dwOperatingSystemVersion == OS_WINNT || _dwOperatingSystemVersion == OS_WIN2000 || _dwOperatingSystemVersion == OS_WINXP) + DealWithTxdWriteError(0, TXDSTORESIZE, "CVT_CRT"); + + return false; + } + +#ifdef RW_GL3 + // so we can read back DXT with GLES + // only works for textures that are not yet loaded + // so let's hope that is the case for all + rw::gl3::needToReadBackTextures = true; +#endif + +#ifdef DISABLE_VSYNC_ON_TEXTURE_CONVERSION + // let's disable vsync and frame limiter to speed up texture conversion + // (actually we probably don't need to disable frame limiter in here, but let's do it just in case =P) + int8 vsyncState = CMenuManager::m_PrefsVsync; + int8 frameLimiterState = CMenuManager::m_PrefsFrameLimiter; + CMenuManager::m_PrefsVsync = 0; + CMenuManager::m_PrefsFrameLimiter = 0; +#endif + + int32 i; + for (i = 0; i < TXDSTORESIZE; i++) { + ConvertingTexturesScreen(i, TXDSTORESIZE, "CVT_MSG"); + + if (CTxdStore::GetSlot(i) != nil && CStreaming::IsObjectInCdImage(i + STREAM_OFFSET_TXD)) { +#ifdef FIX_BUGS + if(strcmp(CTxdStore::GetTxdName(i), "generic") == 0) + continue; +#endif + + CStreaming::RequestTxd(i, STREAMFLAGS_KEEP_IN_MEMORY); + CStreaming::RequestModelStream(0); + CStreaming::FlushChannels(); + + char filename[64]; + sprintf(filename, "%s.txd", CTxdStore::GetTxdName(i)); + + if (CTxdStore::GetSlot(i)->texDict) { + + int32 pos = STREAMTELL(img); + + if (RwTexDictionaryStreamWrite(CTxdStore::GetSlot(i)->texDict, img) == nil) { + DealWithTxdWriteError(i, TXDSTORESIZE, "CVT_ERR"); + RwStreamClose(img, nil); + delete []buf; + delete pDir; + CStreaming::RemoveTxd(i); +#ifdef RW_GL3 + rw::gl3::needToReadBackTextures = false; +#endif + return false; + } + + int32 size = STREAMTELL(img) - pos; + int32 num = size % CDSTREAM_SECTOR_SIZE; + + size /= CDSTREAM_SECTOR_SIZE; + if (num != 0) { + size++; + num = CDSTREAM_SECTOR_SIZE - num; + RwStreamWrite(img, buf, num); + } + + dirInfo.offset = pos / CDSTREAM_SECTOR_SIZE; + dirInfo.size = size; + strncpy(dirInfo.name, filename, sizeof(dirInfo.name)); + pDir->AddItem(dirInfo); + CStreaming::RemoveTxd(i); + } + CStreaming::FlushRequestList(); + } + } + +#ifdef DISABLE_VSYNC_ON_TEXTURE_CONVERSION + // restore vsync and frame limiter states + CMenuManager::m_PrefsVsync = vsyncState; + CMenuManager::m_PrefsFrameLimiter = frameLimiterState; +#endif + + RwStreamClose(img, nil); + delete []buf; + +#ifdef RW_GL3 + rw::gl3::needToReadBackTextures = false; +#endif + + if (!pDir->WriteDirFile("models\\txd.dir")) { + DealWithTxdWriteError(i, TXDSTORESIZE, "CVT_ERR"); + delete pDir; + return false; + } + + delete pDir; + + WriteVideoCardCapsFile(); + return true; +} +#endif // GTA_PC diff --git a/src/rw/TexturePools.cpp b/src/rw/TexturePools.cpp new file mode 100644 index 0000000..c2ba6cf --- /dev/null +++ b/src/rw/TexturePools.cpp @@ -0,0 +1,221 @@ +#ifndef LIBRW + +#include +#define WITHD3D +#include "common.h" +#include "TexturePools.h" + +// TODO: this needs to be integrated into RW + +extern "C" LPDIRECT3DDEVICE8 _RwD3DDevice; + +CTexturePool aTexturePools[12]; +CPaletteList PaletteList; +int numTexturePools; +int MaxPaletteIndex; +bool bUsePaletteIndex = true; + + +void +CTexturePool::Create(D3DFORMAT _Format, int _size, uint32 mipmapLevels, int32 numTextures) +{ + Format = _Format; + size = _size; + levels = mipmapLevels; + pTextures = new IDirect3DTexture8 *[numTextures]; + texturesMax = numTextures; + texturesNum = 0; + texturesUsed = 0; +} + +void +CTexturePool::Release() +{ + int i = 0; + while (i < texturesNum) { + pTextures[i]->Release(); + i++; + } + + delete[] pTextures; + + pTextures = nil; + texturesNum = 0; + texturesUsed = 0; +} + +IDirect3DTexture8 * +CTexturePool::FindTexture() +{ + if (texturesNum == 0) + return nil; + texturesUsed--; + return pTextures[--texturesNum]; +} + +bool +CTexturePool::AddTexture(IDirect3DTexture8 *texture) +{ + ++texturesUsed; + if (texturesNum >= texturesMax) + return false; + pTextures[texturesNum] = texture; + ++texturesNum; + return true; +} + +void +CTexturePool::Resize(int numTextures) +{ + if (numTextures == texturesMax) + return; + + IDirect3DTexture8 **newTextures = new IDirect3DTexture8 *[numTextures]; + + for (int i = 0; i < texturesNum && i < numTextures; i++) + newTextures[i] = pTextures[i]; + + if (numTextures < texturesNum) { + for (int i = numTextures; i < texturesNum; i++) + pTextures[i]->Release(); + } + delete[] pTextures; + pTextures = newTextures; + texturesMax = numTextures; +} + +void +CPaletteList::Alloc(int max) +{ + Data = new int[max]; + Max = max; + Num = 0; +} + +void +CPaletteList::Free() +{ + delete[] Data; + Data = nil; + Num = 0; +} + +int +CPaletteList::Find() +{ + if (Num == 0) + return -1; + return Data[--Num]; +} + +void +CPaletteList::Add(int item) +{ + if (Num < Max) + Data[Num++] = item; + else { + Resize(2 * Max); + Add(item); + } +} + +void +CPaletteList::Resize(int max) +{ + if (max == Max) + return; + + int *newData = new int[4 * max]; + for (int i = 0; i < Num && i < max; i++) + newData[i] = Data[i]; + delete[] Data; + Data = newData; + Max = max; +} + +HRESULT +CreateTexture(int width, int height, int levels, D3DFORMAT Format, IDirect3DTexture8 **texture) +{ + if (width == height) { + for (int i = 0; i < numTexturePools; i++) { + if (width != aTexturePools[i].GetSize() && levels == aTexturePools[i].levels && Format == aTexturePools[i].Format) + *texture = aTexturePools[i].FindTexture(); + } + } + if (*texture) + return D3D_OK; + else + return _RwD3DDevice->CreateTexture(width, height, levels, 0, Format, D3DPOOL_MANAGED, texture); +} + +void +ReleaseTexture(IDirect3DTexture8 *texture) +{ + int levels = 1; + if (texture->GetLevelCount() > 1) + levels = 0; + + D3DSURFACE_DESC SURFACE_DESC; + + texture->GetLevelDesc(0, &SURFACE_DESC); + + if (SURFACE_DESC.Width == SURFACE_DESC.Height) { + for (int i = 0; i < numTexturePools; i++) { + if (SURFACE_DESC.Width == aTexturePools[i].GetSize() && SURFACE_DESC.Format == aTexturePools[i].Format && levels == aTexturePools[i].levels) { + if (!aTexturePools[i].AddTexture(texture)) { + if (aTexturePools[i].texturesUsed > 3 * aTexturePools[i].texturesMax / 2) { + aTexturePools[i].Resize(2 * aTexturePools[i].texturesMax); + aTexturePools[i].texturesUsed--; + aTexturePools[i].AddTexture(texture); + } else { + texture->Release(); + } + } + return; + } + } + } + if (numTexturePools < 12 && bUsePaletteIndex && levels != 0 && SURFACE_DESC.Width == SURFACE_DESC.Height && + (SURFACE_DESC.Width == 64 || SURFACE_DESC.Width == 128 || SURFACE_DESC.Width == 256)) { + aTexturePools[numTexturePools].Create(SURFACE_DESC.Format, SURFACE_DESC.Width, 1, 16); + aTexturePools[numTexturePools].AddTexture(texture); + numTexturePools++; + } else + texture->Release(); +} + +int +FindAvailablePaletteIndex() +{ + int index = PaletteList.Find(); + if (index == -1) + index = MaxPaletteIndex++; + return index; +} + +void +AddAvailablePaletteIndex(int index) +{ + if (bUsePaletteIndex) + PaletteList.Add(index); +} + +void +_TexturePoolsInitialise() +{ + PaletteList.Alloc(100); + MaxPaletteIndex = 0; +} + +void +_TexturePoolsShutdown() +{ + for (int i = 0; i < numTexturePools; i++) + aTexturePools[i].Release(); + + numTexturePools = 0; + bUsePaletteIndex = false; + PaletteList.Free(); +} + +#endif // !LIBRW \ No newline at end of file diff --git a/src/rw/TexturePools.h b/src/rw/TexturePools.h new file mode 100644 index 0000000..7518743 --- /dev/null +++ b/src/rw/TexturePools.h @@ -0,0 +1,42 @@ +#pragma once + +class CTexturePool +{ +public: + D3DFORMAT Format; + int size; + uint32 levels; + int32 texturesMax; + int32 texturesUsed; + int32 texturesNum; + IDirect3DTexture8 **pTextures; + +public: + CTexturePool() {} + void Create(D3DFORMAT _Format, int size, uint32 mipmapLevels, int32 numTextures); + void Release(); + IDirect3DTexture8 *FindTexture(); + bool AddTexture(IDirect3DTexture8 *texture); + void Resize(int numTextures); +#ifdef FIX_BUGS + int GetSize() { return size; } +#else + float GetSize() { return size; } +#endif +}; + +class CPaletteList +{ + int Max; + int Num; + int *Data; +public: + void Alloc(int max); + void Free(); + int Find(); + void Add(int item); + void Resize(int max); +}; + +void _TexturePoolsInitialise(); +void _TexturePoolsShutdown(); \ No newline at end of file diff --git a/src/rw/TxdStore.cpp b/src/rw/TxdStore.cpp new file mode 100644 index 0000000..a9e2972 --- /dev/null +++ b/src/rw/TxdStore.cpp @@ -0,0 +1,183 @@ +#include "common.h" + +#include "templates.h" +#include "General.h" +#include "Streaming.h" +#include "RwHelper.h" +#include "TxdStore.h" + +CPool *CTxdStore::ms_pTxdPool; +RwTexDictionary *CTxdStore::ms_pStoredTxd; + +void +CTxdStore::Initialise(void) +{ + if(ms_pTxdPool == nil) + ms_pTxdPool = new CPool(TXDSTORESIZE); +} + +void +CTxdStore::Shutdown(void) +{ + if(ms_pTxdPool) + delete ms_pTxdPool; +} + +void +CTxdStore::GameShutdown(void) +{ + int i; + + for(i = 0; i < TXDSTORESIZE; i++){ + TxdDef *def = GetSlot(i); + if(def && GetNumRefs(i) == 0) + RemoveTxdSlot(i); + } +} + +int +CTxdStore::AddTxdSlot(const char *name) +{ + TxdDef *def = ms_pTxdPool->New(); + assert(def); + def->texDict = nil; + def->refCount = 0; + strcpy(def->name, name); + return ms_pTxdPool->GetJustIndex(def); +} + +void +CTxdStore::RemoveTxdSlot(int slot) +{ + TxdDef *def = GetSlot(slot); + if(def->texDict) + RwTexDictionaryDestroy(def->texDict); + ms_pTxdPool->Delete(def); +} + +int +CTxdStore::FindTxdSlot(const char *name) +{ + int size = ms_pTxdPool->GetSize(); + for(int i = 0; i < size; i++){ + TxdDef *def = GetSlot(i); + if(def && !CGeneral::faststricmp(def->name, name)) + return i; + } + return -1; +} + +char* +CTxdStore::GetTxdName(int slot) +{ + return GetSlot(slot)->name; +} + +void +CTxdStore::PushCurrentTxd(void) +{ + ms_pStoredTxd = RwTexDictionaryGetCurrent(); +} + +void +CTxdStore::PopCurrentTxd(void) +{ + RwTexDictionarySetCurrent(ms_pStoredTxd); + ms_pStoredTxd = nil; +} + +void +CTxdStore::SetCurrentTxd(int slot) +{ + RwTexDictionarySetCurrent(GetSlot(slot)->texDict); +} + +void +CTxdStore::Create(int slot) +{ + GetSlot(slot)->texDict = RwTexDictionaryCreate(); +} + +int +CTxdStore::GetNumRefs(int slot) +{ + return GetSlot(slot)->refCount; +} + +void +CTxdStore::AddRef(int slot) +{ + GetSlot(slot)->refCount++; +} + +void +CTxdStore::RemoveRef(int slot) +{ + if(--GetSlot(slot)->refCount <= 0) + CStreaming::RemoveTxd(slot); +} + +void +CTxdStore::RemoveRefWithoutDelete(int slot) +{ + GetSlot(slot)->refCount--; +} + +bool +CTxdStore::LoadTxd(int slot, RwStream *stream) +{ + TxdDef *def = GetSlot(slot); + + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)){ + def->texDict = RwTexDictionaryGtaStreamRead(stream); + return def->texDict != nil; + } + printf("Failed to load TXD\n"); + return false; +} + +bool +CTxdStore::LoadTxd(int slot, const char *filename) +{ + RwStream *stream; + bool ret; + + ret = false; + _rwD3D8TexDictionaryEnableRasterFormatConversion(true); + do + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + while(stream == nil); + ret = LoadTxd(slot, stream); + RwStreamClose(stream, nil); + return ret; +} + +bool +CTxdStore::StartLoadTxd(int slot, RwStream *stream) +{ + TxdDef *def = GetSlot(slot); + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, nil, nil)){ + def->texDict = RwTexDictionaryGtaStreamRead1(stream); + return def->texDict != nil; + }else{ + printf("Failed to load TXD\n"); + return false; + } +} + +bool +CTxdStore::FinishLoadTxd(int slot, RwStream *stream) +{ + TxdDef *def = GetSlot(slot); + def->texDict = RwTexDictionaryGtaStreamRead2(stream, def->texDict); + return def->texDict != nil; +} + +void +CTxdStore::RemoveTxd(int slot) +{ + TxdDef *def = GetSlot(slot); + if(def->texDict) + RwTexDictionaryDestroy(def->texDict); + def->texDict = nil; +} diff --git a/src/rw/TxdStore.h b/src/rw/TxdStore.h new file mode 100644 index 0000000..937fd1b --- /dev/null +++ b/src/rw/TxdStore.h @@ -0,0 +1,44 @@ +#pragma once + +#include "templates.h" + +struct TxdDef { + RwTexDictionary *texDict; + int refCount; + char name[20]; +}; + +class CTxdStore +{ + static CPool *ms_pTxdPool; + static RwTexDictionary *ms_pStoredTxd; +public: + static void Initialise(void); + static void Shutdown(void); + static void GameShutdown(void); + static int AddTxdSlot(const char *name); + static void RemoveTxdSlot(int slot); + static int FindTxdSlot(const char *name); + static char *GetTxdName(int slot); + static void PushCurrentTxd(void); + static void PopCurrentTxd(void); + static void SetCurrentTxd(int slot); + static void Create(int slot); + static int GetNumRefs(int slot); + static void AddRef(int slot); + static void RemoveRef(int slot); + static void RemoveRefWithoutDelete(int slot); + static bool LoadTxd(int slot, RwStream *stream); + static bool LoadTxd(int slot, const char *filename); + static bool StartLoadTxd(int slot, RwStream *stream); + static bool FinishLoadTxd(int slot, RwStream *stream); + static void RemoveTxd(int slot); + + static TxdDef *GetSlot(int slot) { + assert(slot >= 0); + assert(ms_pTxdPool); + assert(slot < ms_pTxdPool->GetSize()); + return ms_pTxdPool->GetSlot(slot); + } + static bool isTxdLoaded(int slot); +}; diff --git a/src/rw/VisibilityPlugins.cpp b/src/rw/VisibilityPlugins.cpp new file mode 100644 index 0000000..e6d4641 --- /dev/null +++ b/src/rw/VisibilityPlugins.cpp @@ -0,0 +1,1059 @@ +#include "common.h" + +#include "RwHelper.h" +#include "templates.h" +#include "main.h" +#include "Entity.h" +#include "ModelInfo.h" +#include "Lights.h" +#include "Renderer.h" +#include "Camera.h" +#include "VisibilityPlugins.h" +#include "World.h" +#include "custompipes.h" +#include "MemoryHeap.h" + +CLinkList CVisibilityPlugins::m_alphaList; +CLinkList CVisibilityPlugins::m_alphaEntityList; +#ifdef NEW_RENDERER +CLinkList CVisibilityPlugins::m_alphaBuildingList; +#endif + +int32 CVisibilityPlugins::ms_atomicPluginOffset = -1; +int32 CVisibilityPlugins::ms_framePluginOffset = -1; +int32 CVisibilityPlugins::ms_clumpPluginOffset = -1; + +RwCamera *CVisibilityPlugins::ms_pCamera; +RwV3d *CVisibilityPlugins::ms_pCameraPosn; +float CVisibilityPlugins::ms_cullCompsDist; +float CVisibilityPlugins::ms_vehicleLod0Dist; +float CVisibilityPlugins::ms_vehicleLod1Dist; +float CVisibilityPlugins::ms_vehicleFadeDist; +float CVisibilityPlugins::ms_bigVehicleLod0Dist; +float CVisibilityPlugins::ms_bigVehicleLod1Dist; +float CVisibilityPlugins::ms_pedLod0Dist; +float CVisibilityPlugins::ms_pedLod1Dist; +float CVisibilityPlugins::ms_pedFadeDist; + +#ifdef GTA_PS2 // maybe something else? +// if wanted, delete the original geometry data after rendering +// and only keep the instanced data +bool +rpDefaultGeometryInstance(RpGeometry *geo, void *atomic, int del) +{ +#if THIS_IS_COMPATIBLE_WITH_GTA3_RW31 + if(RpGeometryGetNumMorphTargets(geo) != 1) + return false; + + // this needs R*'s modification that geometry data is + // allocated separately from the geometry itself + geo->instanceFlags = rpGEOMETRYINSTANCE; + AtomicDefaultRenderCallBack((RpAtomic*)atomic); + + if(!del) + return true; + + // New mesh without indices + RpMeshHeader *newheader = _rpMeshHeaderCreate(sizeof(RpMesh)*geo->mesh->numMeshes + sizeof(RpMeshHeader)); + newheader->numMeshes = geo->mesh->numMeshes; + newheader->serialNum = 1; + newheader->totalIndicesInMesh = 0; + newheader->firstMeshOffset = 0; + RpMesh *oldmesh = (RpMesh*)(geo->mesh+1); + RpMesh *newmesh = (RpMesh*)(newheader+1); + for(int i = 0; i < geo->mesh->numMeshes; i++){ + newmesh[i].indices = nil; + newmesh[i].numIndices = 0; + newmesh[i].material = oldmesh[i].material; + } + + geo->refCount++; + RpGeometryLock(geo, rpGEOMETRYLOCKPOLYGONS | rpGEOMETRYLOCKVERTICES | + rpGEOMETRYLOCKNORMALS | rpGEOMETRYLOCKPRELIGHT | + rpGEOMETRYLOCKTEXCOORDS1 | rpGEOMETRYLOCKTEXCOORDS2); + + // vertices and normals + RpMorphTarget *mt = RpGeometryGetMorphTarget(geo, 0); + if(mt->verts){ + RwFree(mt->verts); + mt->verts = nil; + mt->normals = nil; + } + geo->numVertices = 0; + + // triangles + for(int i = 0; i < RpGeometryGetNumTriangles(geo); i++){ + if(RpGeometryGetTriangles(geo)->matIndex == -1) + continue; + RpMaterialDestroy(_rpMaterialListGetMaterial(&geo->matList, RpGeometryGetTriangles(geo)->matIndex)); + } + if(RpGeometryGetTriangles(geo)){ + RwFree(RpGeometryGetTriangles(geo)); + geo->triangles = nil; + geo->numTriangles = 0; + } + + // tex coords + if(RpGeometryGetVertexTexCoords(geo, 1)){ + RwFree(RpGeometryGetVertexTexCoords(geo, 1)); + geo->texCoords[1] = nil; + } + if(RpGeometryGetVertexTexCoords(geo, 0)){ + RwFree(RpGeometryGetVertexTexCoords(geo, 0)); + geo->texCoords[0] = nil; + } + + // vertex colors + if(RpGeometryGetPreLightColors(geo)){ + RwFree(RpGeometryGetPreLightColors(geo)); + geo->preLitLum = nil; + } + + RpGeometryUnlock(geo); + + geo->instanceFlags = rpGEOMETRYPERSISTENT; + // BUG? don't we have to free the old mesh? + geo->mesh = newheader; + geo->refCount--; +#else + // We can do something for librw here actually, maybe later + AtomicDefaultRenderCallBack((RpAtomic*)atomic); +#endif + + return true; +} + +RpAtomic* +PreInstanceRenderCB(RpAtomic *atomic) +{ + RpGeometry *geo = RpAtomicGetGeometry(atomic); + if(RpGeometryGetTriangles(geo)){ + PUSH_MEMID(MEMID_STREAM_MODELS); + rpDefaultGeometryInstance(geo, atomic, 1); + POP_MEMID(); + }else + AtomicDefaultRenderCallBack(atomic); + return atomic; +} +#define RENDERCALLBACK PreInstanceRenderCB +#else +RpAtomic* +DefaultRenderCB_pushid(RpAtomic *atomic) +{ + PUSH_MEMID(MEMID_STREAM_MODELS); + AtomicDefaultRenderCallBack(atomic); + POP_MEMID(); + return atomic; +} +#define RENDERCALLBACK DefaultRenderCB_pushid +#endif + +void +CVisibilityPlugins::Initialise(void) +{ + m_alphaList.Init(NUMALPHALIST); + m_alphaList.head.item.sort = 0.0f; + m_alphaList.tail.item.sort = 100000000.0f; +#ifdef ASPECT_RATIO_SCALE + // default 150 is not enough for bigger FOVs + m_alphaEntityList.Init(NUMALPHAENTITYLIST * 3); +#else + m_alphaEntityList.Init(NUMALPHAENTITYLIST); +#endif // ASPECT_RATIO_SCALE + m_alphaEntityList.head.item.sort = 0.0f; + m_alphaEntityList.tail.item.sort = 100000000.0f; + +#ifdef NEW_RENDERER + m_alphaBuildingList.Init(NUMALPHAENTITYLIST); + m_alphaBuildingList.head.item.sort = 0.0f; + m_alphaBuildingList.tail.item.sort = 100000000.0f; +#endif +} + +void +CVisibilityPlugins::Shutdown(void) +{ + m_alphaList.Shutdown(); + m_alphaEntityList.Shutdown(); +#ifdef NEW_RENDERER + m_alphaBuildingList.Shutdown(); +#endif +} + +void +CVisibilityPlugins::InitAlphaEntityList(void) +{ + m_alphaEntityList.Clear(); +#ifdef NEW_RENDERER + m_alphaBuildingList.Clear(); +#endif +} + +bool +CVisibilityPlugins::InsertEntityIntoSortedList(CEntity *e, float dist) +{ +#ifdef FIX_BUGS + if (!e->m_rwObject) return true; +#endif + + AlphaObjectInfo item; + item.entity = e; + item.sort = dist; +#ifdef NEW_RENDERER + if(gbNewRenderer && e->IsBuilding()) + return !!m_alphaBuildingList.InsertSorted(item); +#endif + bool ret = !!m_alphaEntityList.InsertSorted(item); +// if(!ret) +// printf("list full %d\n", m_alphaEntityList.Count()); + return ret; +} + +void +CVisibilityPlugins::InitAlphaAtomicList(void) +{ + m_alphaList.Clear(); +} + +bool +CVisibilityPlugins::InsertAtomicIntoSortedList(RpAtomic *a, float dist) +{ + AlphaObjectInfo item; + item.atomic = a; + item.sort = dist; + bool ret = !!m_alphaList.InsertSorted(item); +// if(!ret) +// printf("list full %d\n", m_alphaList.Count()); + return ret; +} + +// can't increase this yet unfortunately... +// probably have to fix fading for this so material alpha isn't overwritten +#define VEHICLE_LODDIST_MULTIPLIER (TheCamera.GenerationDistMultiplier) + +void +CVisibilityPlugins::SetRenderWareCamera(RwCamera *camera) +{ + ms_pCamera = camera; + ms_pCameraPosn = RwMatrixGetPos(RwFrameGetMatrix(RwCameraGetFrame(camera))); + + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) + ms_cullCompsDist = 1000000.0f; + else + ms_cullCompsDist = sq(TheCamera.LODDistMultiplier * 20.0f); + + ms_vehicleLod0Dist = sq(70.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_vehicleLod1Dist = sq(90.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_vehicleFadeDist = sq(100.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_bigVehicleLod0Dist = sq(60.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_bigVehicleLod1Dist = sq(150.0f * VEHICLE_LODDIST_MULTIPLIER); + ms_pedLod0Dist = sq(25.0f * TheCamera.LODDistMultiplier); + ms_pedLod1Dist = sq(60.0f * TheCamera.LODDistMultiplier); + ms_pedFadeDist = sq(70.0f * TheCamera.LODDistMultiplier); +} + +RpMaterial* +SetAlphaCB(RpMaterial *material, void *data) +{ + ((RwRGBA*)RpMaterialGetColor(material))->alpha = (uint8)(uintptr)data; + return material; +} + +RpMaterial* +SetTextureCB(RpMaterial *material, void *data) +{ + RpMaterialSetTexture(material, (RwTexture*)data); + return material; +} + +void +CVisibilityPlugins::RenderAlphaAtomics(void) +{ + CLink *node; + for(node = m_alphaList.tail.prev; + node != &m_alphaList.head; + node = node->prev) + RENDERCALLBACK(node->item.atomic); +} + +void +CVisibilityPlugins::RenderFadingEntities(void) +{ + CLink *node; + CSimpleModelInfo *mi; + for(node = m_alphaEntityList.tail.prev; + node != &m_alphaEntityList.head; + node = node->prev){ + CEntity *e = node->item.entity; + if(e->m_rwObject == nil) + continue; +#ifdef EXTENDED_PIPELINES + if(CustomPipes::bRenderingEnvMap && (e->IsPed() || e->IsVehicle())) + continue; +#endif + mi = (CSimpleModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex()); + +#ifdef FIX_BUGS + if(mi->GetModelType() == MITYPE_SIMPLE && mi->m_noZwrite) +#else + if(mi->m_noZwrite) +#endif + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); +#ifdef EXTRA_MODEL_FLAGS + else if(mi->m_bIsTree) + SetAlphaRef(128); + if(!e->IsBuilding() || mi->RenderDoubleSided()) + BACKFACE_CULLING_OFF; +#endif + + if(e->bDistanceFade){ + DeActivateDirectional(); + SetAmbientColours(); + e->bImBeingRendered = true; + PUSH_RENDERGROUP(mi->GetModelName()); + RenderFadingAtomic((RpAtomic*)e->m_rwObject, node->item.sort); + POP_RENDERGROUP(); + e->bImBeingRendered = false; + }else + CRenderer::RenderOneNonRoad(e); + +#ifdef EXTRA_MODEL_FLAGS + if(mi->m_bIsTree) + SetAlphaRef(2); + BACKFACE_CULLING_ON; +#endif +#ifdef FIX_BUGS + if(mi->GetModelType() == MITYPE_SIMPLE && mi->m_noZwrite) +#else + if(mi->m_noZwrite) +#endif + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + } +} + +RpAtomic* +CVisibilityPlugins::RenderWheelAtomicCB(RpAtomic *atomic) +{ + RpAtomic *lodatm; + RwMatrix *m; + RwV3d view; + float len; + CSimpleModelInfo *mi; + + mi = GetAtomicModelInfo(atomic); + m = RwFrameGetLTM(RpAtomicGetFrame(atomic)); + RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn); + len = RwV3dLength(&view); +#ifdef FIX_BUGS + // from VC + lodatm = mi->GetAtomicFromDistance(len * TheCamera.LODDistMultiplier / VEHICLE_LODDIST_MULTIPLIER); +#else + lodatm = mi->GetAtomicFromDistance(len); +#endif + if(lodatm){ + if(RpAtomicGetGeometry(lodatm) != RpAtomicGetGeometry(atomic)) + RpAtomicSetGeometry(atomic, RpAtomicGetGeometry(lodatm), rpATOMICSAMEBOUNDINGSPHERE); + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderObjNormalAtomic(RpAtomic *atomic) +{ + RwMatrix *m; + RwV3d view; + float len; + + m = RwFrameGetLTM(RpAtomicGetFrame(atomic)); + RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn); + len = RwV3dLength(&view); + if(RwV3dDotProduct(&view, RwMatrixGetUp(m)) < -0.3f*len && len > 8.0f) + return atomic; + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderAlphaAtomic(RpAtomic *atomic, int alpha) +{ + RpGeometry *geo; + uint32 flags; + + geo = RpAtomicGetGeometry(atomic); + flags = RpGeometryGetFlags(geo); + RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha); + RENDERCALLBACK(atomic); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255); + RpGeometrySetFlags(geo, flags); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderFadingAtomic(RpAtomic *atomic, float camdist) +{ + RpAtomic *lodatm; + float fadefactor; + uint32 alpha; + CSimpleModelInfo *mi; + + mi = GetAtomicModelInfo(atomic); + lodatm = mi->GetAtomicFromDistance(camdist - FADE_DISTANCE); + if(mi->m_additive){ + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RENDERCALLBACK(atomic); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + }else{ + fadefactor = (mi->GetLargestLodDistance() - (camdist - FADE_DISTANCE))/FADE_DISTANCE; + if(fadefactor > 1.0f) + fadefactor = 1.0f; + alpha = mi->m_alpha * fadefactor; + if(alpha == 255) + RENDERCALLBACK(atomic); + else{ + RpGeometry *geo = RpAtomicGetGeometry(lodatm); + uint32 flags = RpGeometryGetFlags(geo); + RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha); + if(geo != RpAtomicGetGeometry(atomic)) + RpAtomicSetGeometry(atomic, geo, rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?) + RENDERCALLBACK(atomic); + RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255); + RpGeometrySetFlags(geo, flags); + } + } + return atomic; +} + + + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_vehicleLod0Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailAlphaCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_vehicleLod0Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + + if(flags & ATOMIC_FLAG_DRAWLAST){ + // sort before clump + if(!InsertAtomicIntoSortedList(atomic, distsq - 0.0001f)) + RENDERCALLBACK(atomic); + }else{ + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + RENDERCALLBACK(atomic); + } + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod0Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod0Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + return atomic; + + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB_Boat(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod1Dist) + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq >= ms_bigVehicleLod0Dist && + distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq >= ms_bigVehicleLod0Dist && + distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + return atomic; + + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleReallyLowDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist >= ms_vehicleLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; + +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq >= ms_bigVehicleLod1Dist) + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderTrainHiDetailCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderTrainHiDetailAlphaCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float distsq, dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + distsq = GetDistanceSquaredFromCamera(clumpframe); + if(distsq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0) + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot)) + return atomic; + + if(flags & ATOMIC_FLAG_DRAWLAST){ + if(!InsertAtomicIntoSortedList(atomic, distsq)) + RENDERCALLBACK(atomic); + }else{ + if(!InsertAtomicIntoSortedList(atomic, distsq + dot)) + RENDERCALLBACK(atomic); + } + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderPlayerCB(RpAtomic *atomic) +{ + if(CWorld::Players[0].m_pSkinTexture) + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), SetTextureCB, CWorld::Players[0].m_pSkinTexture); + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderPedLowDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist >= ms_pedLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderPedHiDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist < ms_pedLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +// This is needed for peds with only one clump, i.e. skinned models +// strangely even the xbox version has no such thing +RpAtomic* +CVisibilityPlugins::RenderPedCB(RpAtomic *atomic) +{ + int32 alpha; + RwV3d cam2atm; + + RwV3dSub(&cam2atm, &RwFrameGetLTM(RpAtomicGetFrame(atomic))->pos, ms_pCameraPosn); + if(RwV3dDotProduct(&cam2atm, &cam2atm) < ms_pedLod1Dist){ + alpha = GetClumpAlpha(RpAtomicGetClump(atomic)); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +float +CVisibilityPlugins::GetDistanceSquaredFromCamera(RwFrame *frame) +{ + RwMatrix *m; + RwV3d dist; + m = RwFrameGetLTM(frame); + RwV3dSub(&dist, RwMatrixGetPos(m), ms_pCameraPosn); + return RwV3dDotProduct(&dist, &dist); +} + +float +CVisibilityPlugins::GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags) +{ + RwV3d dist; + float dot, dotdoor; + + // Vehicle forward is the y axis (RwMatrix.up) + // Vehicle right is the x axis (RwMatrix.right) + + RwV3dSub(&dist, RwMatrixGetPos(atomicMat), ms_pCameraPosn); + // forward/backward facing + if(flags & (ATOMIC_FLAG_FRONT | ATOMIC_FLAG_REAR)) + dot = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat)); + // left/right facing + else if(flags & (ATOMIC_FLAG_LEFT | ATOMIC_FLAG_RIGHT)) + dot = RwV3dDotProduct(&dist, RwMatrixGetRight(clumpMat)); + else + dot = 0.0f; + if(flags & (ATOMIC_FLAG_LEFT | ATOMIC_FLAG_REAR)) + dot = -dot; + + if(flags & (ATOMIC_FLAG_REARDOOR | ATOMIC_FLAG_FRONTDOOR)){ + if(flags & ATOMIC_FLAG_REARDOOR) + dotdoor = -RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat)); + else if(flags & ATOMIC_FLAG_FRONTDOOR) + dotdoor = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat)); + else + dotdoor = 0.0f; + + if(dot < 0.0f && dotdoor < 0.0f) + dot += dotdoor; + if(dot > 0.0f && dotdoor > 0.0f) + dot += dotdoor; + } + + return dot; +} + +/* These are all unused */ + +bool +CVisibilityPlugins::DefaultVisibilityCB(RpClump *clump) +{ + return true; +} + +bool +CVisibilityPlugins::FrustumSphereCB(RpClump *clump) +{ + RwSphere sphere; + RwFrame *frame = RpClumpGetFrame(clump); + + CClumpModelInfo *modelInfo = (CClumpModelInfo*)GetFrameHierarchyId(frame); + sphere.radius = modelInfo->GetColModel()->boundingSphere.radius; + sphere.center.x = modelInfo->GetColModel()->boundingSphere.center.x; + sphere.center.y = modelInfo->GetColModel()->boundingSphere.center.y; + sphere.center.z = modelInfo->GetColModel()->boundingSphere.center.z; + RwV3dTransformPoints(&sphere.center, &sphere.center, 1, RwFrameGetLTM(frame)); + return RwCameraFrustumTestSphere(ms_pCamera, &sphere) != rwSPHEREOUTSIDE; +} + +bool +CVisibilityPlugins::MloVisibilityCB(RpClump *clump) +{ + RwFrame *frame = RpClumpGetFrame(clump); + CMloModelInfo *modelInfo = (CMloModelInfo*)GetFrameHierarchyId(frame); + if (SQR(modelInfo->drawDist) < GetDistanceSquaredFromCamera(frame)) + return false; + return CVisibilityPlugins::FrustumSphereCB(clump); +} + +bool +CVisibilityPlugins::VehicleVisibilityCB(RpClump *clump) +{ + RwFrame *frame = RpClumpGetFrame(clump); + if (ms_vehicleLod1Dist < GetDistanceSquaredFromCamera(frame)) + return false; + return FrustumSphereCB(clump); +} + +bool +CVisibilityPlugins::VehicleVisibilityCB_BigVehicle(RpClump *clump) +{ + return FrustumSphereCB(clump); +} + + + + +// +// RW Plugins +// + +enum +{ + ID_VISIBILITYATOMIC = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x00), + ID_VISIBILITYCLUMP = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x01), + ID_VISIBILITYFRAME = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x02), +}; + +bool +CVisibilityPlugins::PluginAttach(void) +{ + ms_atomicPluginOffset = RpAtomicRegisterPlugin(sizeof(AtomicExt), + ID_VISIBILITYATOMIC, + AtomicConstructor, AtomicDestructor, AtomicCopyConstructor); + + ms_framePluginOffset = RwFrameRegisterPlugin(sizeof(FrameExt), + ID_VISIBILITYFRAME, + FrameConstructor, FrameDestructor, FrameCopyConstructor); + + ms_clumpPluginOffset = RpClumpRegisterPlugin(sizeof(ClumpExt), + ID_VISIBILITYCLUMP, + ClumpConstructor, ClumpDestructor, ClumpCopyConstructor); + +#if GTA_VERSION <= GTA3_PS2_160 + Initialise(); +#endif + + return ms_atomicPluginOffset != -1 && ms_clumpPluginOffset != -1; +} + +#define ATOMICEXT(o) (RWPLUGINOFFSET(AtomicExt, o, ms_atomicPluginOffset)) +#define FRAMEEXT(o) (RWPLUGINOFFSET(FrameExt, o, ms_framePluginOffset)) +#define CLUMPEXT(o) (RWPLUGINOFFSET(ClumpExt, o, ms_clumpPluginOffset)) + +// +// Atomic +// + +void* +CVisibilityPlugins::AtomicConstructor(void *object, int32, int32) +{ + ATOMICEXT(object)->modelInfo = nil; + return object; +} + +void* +CVisibilityPlugins::AtomicDestructor(void *object, int32, int32) +{ + return object; +} + +void* +CVisibilityPlugins::AtomicCopyConstructor(void *dst, const void *src, int32, int32) +{ + *ATOMICEXT(dst) = *ATOMICEXT(src); + return dst; +} + +void +CVisibilityPlugins::SetAtomicModelInfo(RpAtomic *atomic, + CSimpleModelInfo *modelInfo) +{ + AtomicExt *ext = ATOMICEXT(atomic); + ext->modelInfo = modelInfo; + switch (modelInfo->GetModelType()) { + case MITYPE_SIMPLE: + case MITYPE_TIME: + if(modelInfo->m_normalCull) + SetAtomicRenderCallback(atomic, RenderObjNormalAtomic); + default: break; + } +} + +CSimpleModelInfo* +CVisibilityPlugins::GetAtomicModelInfo(RpAtomic *atomic) +{ + return ATOMICEXT(atomic)->modelInfo; +} + +void +CVisibilityPlugins::SetAtomicFlag(RpAtomic *atomic, int f) +{ + ATOMICEXT(atomic)->flags |= f; +} + +void +CVisibilityPlugins::ClearAtomicFlag(RpAtomic *atomic, int f) +{ + ATOMICEXT(atomic)->flags &= ~f; +} + +void +CVisibilityPlugins::SetAtomicId(RpAtomic *atomic, int id) +{ + ATOMICEXT(atomic)->flags = id; +} + +int +CVisibilityPlugins::GetAtomicId(RpAtomic *atomic) +{ + return ATOMICEXT(atomic)->flags; +} + +void +CVisibilityPlugins::SetAtomicRenderCallback(RpAtomic *atomic, RpAtomicCallBackRender cb) +{ + if(cb == nil) + cb = RENDERCALLBACK; + RpAtomicSetRenderCallBack(atomic, cb); +} + +// +// Frame +// + +void* +CVisibilityPlugins::FrameConstructor(void *object, int32, int32) +{ + FRAMEEXT(object)->id = 0; + return object; +} + +void* +CVisibilityPlugins::FrameDestructor(void *object, int32, int32) +{ + return object; +} + +void* +CVisibilityPlugins::FrameCopyConstructor(void *dst, const void *src, int32, int32) +{ + *FRAMEEXT(dst) = *FRAMEEXT(src); + return dst; +} + +void +CVisibilityPlugins::SetFrameHierarchyId(RwFrame *frame, intptr id) +{ + FRAMEEXT(frame)->id = id; +} + +intptr +CVisibilityPlugins::GetFrameHierarchyId(RwFrame *frame) +{ + return FRAMEEXT(frame)->id; +} + + +// +// Clump +// + +void* +CVisibilityPlugins::ClumpConstructor(void *object, int32, int32) +{ + ClumpExt *ext = CLUMPEXT(object); + ext->visibilityCB = DefaultVisibilityCB; + ext->alpha = 0xFF; + return object; +} + +void* +CVisibilityPlugins::ClumpDestructor(void *object, int32, int32) +{ + return object; +} + +void* +CVisibilityPlugins::ClumpCopyConstructor(void *dst, const void *src, int32, int32) +{ + CLUMPEXT(dst)->visibilityCB = CLUMPEXT(src)->visibilityCB; + return dst; +} + +void +CVisibilityPlugins::SetClumpModelInfo(RpClump *clump, CClumpModelInfo *modelInfo) +{ + CVehicleModelInfo *vmi; + SetFrameHierarchyId(RpClumpGetFrame(clump), (intptr)modelInfo); + + // Unused + switch (modelInfo->GetModelType()) { + case MITYPE_MLO: + CLUMPEXT(clump)->visibilityCB = MloVisibilityCB; + break; + case MITYPE_VEHICLE: + vmi = (CVehicleModelInfo*)modelInfo; + if(vmi->m_vehicleType == VEHICLE_TYPE_TRAIN || + vmi->m_vehicleType == VEHICLE_TYPE_HELI || + vmi->m_vehicleType == VEHICLE_TYPE_PLANE) + CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB_BigVehicle; + else + CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB; + break; + default: break; + } +} + +CClumpModelInfo* +CVisibilityPlugins::GetClumpModelInfo(RpClump *clump) +{ + return (CClumpModelInfo*)GetFrameHierarchyId(RpClumpGetFrame(clump)); +} + +void +CVisibilityPlugins::SetClumpAlpha(RpClump *clump, int alpha) +{ + CLUMPEXT(clump)->alpha = alpha; +} + +int +CVisibilityPlugins::GetClumpAlpha(RpClump *clump) +{ + return CLUMPEXT(clump)->alpha; +} + +bool +CVisibilityPlugins::IsClumpVisible(RpClump *clump) +{ + return CLUMPEXT(clump)->visibilityCB(clump); +} diff --git a/src/rw/VisibilityPlugins.h b/src/rw/VisibilityPlugins.h new file mode 100644 index 0000000..f97fd58 --- /dev/null +++ b/src/rw/VisibilityPlugins.h @@ -0,0 +1,141 @@ +#pragma once + +#include "templates.h" + +class CEntity; +class CSimpleModelInfo; +class CClumpModelInfo; + +typedef bool (*ClumpVisibilityCB)(RpClump*); + +class CVisibilityPlugins +{ +public: + struct AlphaObjectInfo + { + union { + CEntity *entity; + RpAtomic *atomic; + }; + float sort; + }; + + static CLinkList m_alphaList; + static CLinkList m_alphaEntityList; +#ifdef NEW_RENDERER + static CLinkList m_alphaBuildingList; +#endif + static RwCamera *ms_pCamera; + static RwV3d *ms_pCameraPosn; + static float ms_cullCompsDist; + static float ms_vehicleLod0Dist; + static float ms_vehicleLod1Dist; + static float ms_vehicleFadeDist; + static float ms_bigVehicleLod0Dist; + static float ms_bigVehicleLod1Dist; + static float ms_pedLod0Dist; + static float ms_pedLod1Dist; + static float ms_pedFadeDist; + + static void Initialise(void); + static void Shutdown(void); + static void InitAlphaEntityList(void); + static bool InsertEntityIntoSortedList(CEntity *e, float dist); + static void InitAlphaAtomicList(void); + static bool InsertAtomicIntoSortedList(RpAtomic *a, float dist); + + static void SetRenderWareCamera(RwCamera *camera); + + static RpAtomic *RenderWheelAtomicCB(RpAtomic *atomic); + static RpAtomic *RenderObjNormalAtomic(RpAtomic *atomic); + static RpAtomic *RenderAlphaAtomic(RpAtomic *atomic, int alpha); + static RpAtomic *RenderFadingAtomic(RpAtomic *atm, float dist); + + static RpAtomic *RenderVehicleHiDetailCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailAlphaCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleHiDetailCB_Boat(RpAtomic *atomic); + static RpAtomic *RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderVehicleReallyLowDetailCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic); + static RpAtomic *RenderTrainHiDetailCB(RpAtomic *atomic); + static RpAtomic *RenderTrainHiDetailAlphaCB(RpAtomic *atomic); + + static RpAtomic *RenderPlayerCB(RpAtomic *atomic); + static RpAtomic *RenderPedLowDetailCB(RpAtomic *atomic); + static RpAtomic *RenderPedHiDetailCB(RpAtomic *atomic); + static RpAtomic *RenderPedCB(RpAtomic *atomic); // for skinned models with only one clump + + static void RenderAlphaAtomics(void); + static void RenderFadingEntities(void); + + // All actually unused + static bool DefaultVisibilityCB(RpClump *clump); + static bool FrustumSphereCB(RpClump *clump); + static bool MloVisibilityCB(RpClump *clump); + static bool VehicleVisibilityCB(RpClump *clump); + static bool VehicleVisibilityCB_BigVehicle(RpClump *clump); + + static float GetDistanceSquaredFromCamera(RwFrame *frame); + static float GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags); + + // + // RW Plugins + // + + union AtomicExt + { + CSimpleModelInfo *modelInfo; // used by SimpleModelInfo + int flags; // used by ClumpModelInfo + }; + static void SetAtomicModelInfo(RpAtomic*, CSimpleModelInfo*); + static CSimpleModelInfo *GetAtomicModelInfo(RpAtomic *atomic); + static void SetAtomicFlag(RpAtomic*, int); + static void ClearAtomicFlag(RpAtomic*, int); + static void SetAtomicId(RpAtomic *atomic, int); + static int GetAtomicId(RpAtomic *atomic); + static void SetAtomicRenderCallback(RpAtomic*, RpAtomicCallBackRender); + + static void *AtomicConstructor(void *object, int32 offset, int32 len); + static void *AtomicDestructor(void *object, int32 offset, int32 len); + static void *AtomicCopyConstructor(void *dst, const void *src, + int32 offset, int32 len); + static int32 ms_atomicPluginOffset; + + struct FrameExt + { + // BUG: this is abused to hold a pointer by SetClumpModelInfo + intptr id; + }; + static void SetFrameHierarchyId(RwFrame *frame, intptr id); + static intptr GetFrameHierarchyId(RwFrame *frame); + + static void *FrameConstructor(void *object, int32 offset, int32 len); + static void *FrameDestructor(void *object, int32 offset, int32 len); + static void *FrameCopyConstructor(void *dst, const void *src, + int32 offset, int32 len); + static int32 ms_framePluginOffset; + + struct ClumpExt + { + ClumpVisibilityCB visibilityCB; + int alpha; + }; + static void SetClumpModelInfo(RpClump*, CClumpModelInfo*); + static CClumpModelInfo *GetClumpModelInfo(RpClump*); + static void SetClumpAlpha(RpClump*, int); + static int GetClumpAlpha(RpClump*); + static bool IsClumpVisible(RpClump*); + + static void *ClumpConstructor(void *object, int32 offset, int32 len); + static void *ClumpDestructor(void *object, int32 offset, int32 len); + static void *ClumpCopyConstructor(void *dst, const void *src, + int32 offset, int32 len); + static int32 ms_clumpPluginOffset; + + static bool PluginAttach(void); +}; + +RpMaterial *SetAlphaCB(RpMaterial *material, void *data); diff --git a/src/save/Date.cpp b/src/save/Date.cpp new file mode 100644 index 0000000..ca75bb5 --- /dev/null +++ b/src/save/Date.cpp @@ -0,0 +1,91 @@ +#include "common.h" +#include "Date.h" + +CDate::CDate() +{ + m_nYear = 0; + m_nSecond = 0; + m_nMinute = 0; + m_nHour = 0; + m_nDay = 0; + m_nMonth = 0; +} + +bool +CDate::operator>(const CDate &right) +{ + if (m_nYear > right.m_nYear) + return true; + if (m_nYear != right.m_nYear) + return false; + + if (m_nMonth > right.m_nMonth) + return true; + if (m_nMonth != right.m_nMonth) + return false; + + if (m_nDay > right.m_nDay) + return true; + if (m_nDay != right.m_nDay) + return false; + + if (m_nHour > right.m_nHour) + return true; + if (m_nHour != right.m_nHour) + return false; + + if (m_nMinute > right.m_nMinute) + return true; + if (m_nMinute != right.m_nMinute) + return false; + return m_nSecond > right.m_nSecond; +} + +bool +CDate::operator<(const CDate &right) +{ + if (m_nYear < right.m_nYear) + return true; + if (m_nYear != right.m_nYear) + return false; + + if (m_nMonth < right.m_nMonth) + return true; + if (m_nMonth != right.m_nMonth) + return false; + + if (m_nDay < right.m_nDay) + return true; + if (m_nDay != right.m_nDay) + return false; + + if (m_nHour < right.m_nHour) + return true; + if (m_nHour != right.m_nHour) + return false; + + if (m_nMinute < right.m_nMinute) + return true; + if (m_nMinute != right.m_nMinute) + return false; + return m_nSecond < right.m_nSecond; +} + +bool +CDate::operator==(const CDate &right) +{ + if (m_nYear != right.m_nYear || m_nMonth != right.m_nMonth || m_nDay != right.m_nDay || m_nHour != right.m_nHour || m_nMinute != right.m_nMinute) + return false; + return m_nSecond == right.m_nSecond; +} + +void +CDate::PopulateDateFields(int8 &second, int8 &minute, int8 &hour, int8 &day, int8 &month, int16 year) +{ + m_nSecond = second; + m_nMinute = minute; + m_nHour = hour; + m_nDay = day; + m_nMonth = month; + m_nYear = year; +} \ No newline at end of file diff --git a/src/save/Date.h b/src/save/Date.h new file mode 100644 index 0000000..15646c2 --- /dev/null +++ b/src/save/Date.h @@ -0,0 +1,18 @@ +#pragma once + +class CDate +{ +public: + int m_nSecond; + int m_nMinute; + int m_nHour; + int m_nDay; + int m_nMonth; + int m_nYear; + + CDate(); + bool operator>(const CDate &right); + bool operator<(const CDate &right); + bool operator==(const CDate &right); + void PopulateDateFields(int8 &second, int8 &minute, int8 &hour, int8 &day, int8 &month, int16 year); +}; \ No newline at end of file diff --git a/src/save/GenericGameStorage.cpp b/src/save/GenericGameStorage.cpp new file mode 100644 index 0000000..e4ee454 --- /dev/null +++ b/src/save/GenericGameStorage.cpp @@ -0,0 +1,1174 @@ +#define WITHWINDOWS +#include "common.h" +#include "crossplatform.h" +#include "main.h" + +#include "DMAudio.h" +#include "AudioScriptObject.h" +#include "Camera.h" +#include "CarGen.h" +#include "Cranes.h" +#include "Clock.h" +#include "Date.h" +#include "FileMgr.h" +#include "Font.h" +#include "Frontend.h" +#include "GameLogic.h" +#include "Gangs.h" +#include "Garages.h" +#include "GenericGameStorage.h" +#include "Pad.h" +#include "Particle.h" +#include "ParticleObject.h" +#include "PathFind.h" +#include "PCSave.h" +#include "Phones.h" +#include "Pickups.h" +#include "PlayerPed.h" +#include "ProjectileInfo.h" +#include "Pools.h" +#include "Radar.h" +#include "Restart.h" +#include "Script.h" +#include "Stats.h" +#include "Streaming.h" +#include "Timer.h" +#include "TimeStep.h" +#include "Weather.h" +#include "World.h" +#include "Zones.h" + +#define BLOCK_COUNT 20 +#define SIZE_OF_SIMPLEVARS 0xBC + +const uint32 SIZE_OF_ONE_GAME_IN_BYTES = 201729; + +#ifdef MISSION_REPLAY +int8 IsQuickSave; +const int PAUSE_SAVE_SLOT = SLOT_COUNT; +#endif + +char DefaultPCSaveFileName[260]; +char ValidSaveName[260]; +char LoadFileName[256]; +wchar SlotFileName[SLOT_COUNT][260]; +wchar SlotSaveDate[SLOT_COUNT][70]; +int CheckSum; +eLevelName m_LevelToLoad; +char SaveFileNameJustSaved[260]; +int Slots[SLOT_COUNT+1]; +CDate CompileDateAndTime; + +bool b_FoundRecentSavedGameWantToLoad; +bool JustLoadedDontFadeInYet; +bool StillToFadeOut; +uint32 TimeStartedCountingForFade; +uint32 TimeToStayFadedBeforeFadeOut = 1750; + +#define ReadDataFromBufferPointer(buf, to) memcpy(&to, buf, sizeof(to)); buf += align4bytes(sizeof(to)); +#define WriteDataToBufferPointer(buf, from) memcpy(buf, &from, sizeof(from)); buf += align4bytes(sizeof(from)); + +#define LoadSaveDataBlock()\ +do {\ + if (!ReadDataFromFile(file, (uint8 *) &size, 4))\ + return false;\ + size = align4bytes(size);\ + if (!ReadDataFromFile(file, work_buff, size))\ + return false;\ + buf = work_buff;\ +} while (0) + +#define ReadDataFromBlock(msg,load_func)\ +do {\ + debug(msg);\ + ReadDataFromBufferPointer(buf, size);\ + load_func(buf, size);\ + size = align4bytes(size);\ + buf += size;\ +} while (0) + +#define WriteSaveDataBlock(save_func)\ +do {\ + buf = work_buff;\ + reserved = 0;\ + MakeSpaceForSizeInBufferPointer(presize, buf, postsize);\ + save_func(buf, &size);\ + CopySizeAndPreparePointer(presize, buf, postsize, reserved, size);\ + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff))\ + return false;\ + totalSize += buf - work_buff;\ +} while (0) + +bool +GenericSave(int file) +{ + uint8 *buf, *presize, *postsize; + uint32 size; + uint32 reserved; + + uint32 totalSize; + + wchar *lastMissionPassed; + wchar suffix[6]; + wchar saveName[24]; + SYSTEMTIME saveTime; + CPad *currPad; + + CheckSum = 0; + buf = work_buff; + reserved = 0; + + // Save simple vars + lastMissionPassed = TheText.Get(CStats::LastMissionPassedName); + if (lastMissionPassed[0] != '\0') { + AsciiToUnicode("...'", suffix); +#ifdef FIX_BUGS + // fix buffer overflow + int len = UnicodeStrlen(lastMissionPassed); + if (len > ARRAY_SIZE(saveName)-1) + len = ARRAY_SIZE(saveName)-1; + memcpy(saveName, lastMissionPassed, sizeof(wchar) * len); +#else + TextCopy(saveName, lastMissionPassed); + int len = UnicodeStrlen(saveName); +#endif + saveName[len] = '\0'; + if (len > ARRAY_SIZE(saveName)-2) + TextCopy(&saveName[ARRAY_SIZE(saveName)-ARRAY_SIZE(suffix)], suffix); + saveName[ARRAY_SIZE(saveName)-1] = '\0'; + } + WriteDataToBufferPointer(buf, saveName); + GetLocalTime(&saveTime); + WriteDataToBufferPointer(buf, saveTime); +#ifdef MISSION_REPLAY + int32 data = IsQuickSave << 24 | SIZE_OF_ONE_GAME_IN_BYTES; + WriteDataToBufferPointer(buf, data); +#else + WriteDataToBufferPointer(buf, SIZE_OF_ONE_GAME_IN_BYTES); +#endif + WriteDataToBufferPointer(buf, CGame::currLevel); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().x); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().y); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().z); + WriteDataToBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute); + WriteDataToBufferPointer(buf, CClock::ms_nLastClockTick); + WriteDataToBufferPointer(buf, CClock::ms_nGameClockHours); + WriteDataToBufferPointer(buf, CClock::ms_nGameClockMinutes); + currPad = CPad::GetPad(0); + WriteDataToBufferPointer(buf, currPad->Mode); + WriteDataToBufferPointer(buf, CTimer::m_snTimeInMilliseconds); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeScale); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeStep); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeStepNonClipped); + WriteDataToBufferPointer(buf, CTimer::m_FrameCounter); + WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeStep); + WriteDataToBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate); + WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeScale); + WriteDataToBufferPointer(buf, CWeather::OldWeatherType); + WriteDataToBufferPointer(buf, CWeather::NewWeatherType); + WriteDataToBufferPointer(buf, CWeather::ForcedWeatherType); + WriteDataToBufferPointer(buf, CWeather::InterpolationValue); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nSecond); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMinute); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nHour); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nDay); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMonth); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nYear); + WriteDataToBufferPointer(buf, CWeather::WeatherTypeInList); +#ifdef COMPATIBLE_SAVES + // converted to float for compatibility with original format + // TODO: maybe remove this? not really gonna break anything vital + float f = TheCamera.CarZoomIndicator; + WriteDataToBufferPointer(buf, f); + f = TheCamera.PedZoomIndicator; + WriteDataToBufferPointer(buf, f); +#else + WriteDataToBufferPointer(buf, TheCamera.CarZoomIndicator); + WriteDataToBufferPointer(buf, TheCamera.PedZoomIndicator); +#endif + assert(buf - work_buff == SIZE_OF_SIMPLEVARS); + + // Save scripts, block is nested within the same block as simple vars for some reason + presize = buf; + buf += 4; + postsize = buf; + CTheScripts::SaveAllScripts(buf, &size); + CopySizeAndPreparePointer(presize, buf, postsize, reserved, size); + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff)) + return false; + + totalSize = buf - work_buff; + + // Save the rest + WriteSaveDataBlock(CPools::SavePedPool); + WriteSaveDataBlock(CGarages::Save); + WriteSaveDataBlock(CPools::SaveVehiclePool); + WriteSaveDataBlock(CPools::SaveObjectPool); + WriteSaveDataBlock(ThePaths.Save); + WriteSaveDataBlock(CCranes::Save); + WriteSaveDataBlock(CPickups::Save); + WriteSaveDataBlock(gPhoneInfo.Save); + WriteSaveDataBlock(CRestart::SaveAllRestartPoints); + WriteSaveDataBlock(CRadar::SaveAllRadarBlips); + WriteSaveDataBlock(CTheZones::SaveAllZones); + WriteSaveDataBlock(CGangs::SaveAllGangData); + WriteSaveDataBlock(CTheCarGenerators::SaveAllCarGenerators); + WriteSaveDataBlock(CParticleObject::SaveParticle); + WriteSaveDataBlock(cAudioScriptObject::SaveAllAudioScriptObjects); + WriteSaveDataBlock(CWorld::Players[CWorld::PlayerInFocus].SavePlayerInfo); + WriteSaveDataBlock(CStats::SaveStats); + WriteSaveDataBlock(CStreaming::MemoryCardSave); + WriteSaveDataBlock(CPedType::Save); + + // sure just write garbage data repeatedly ... +#ifndef THIS_IS_STUPID + memset(work_buff, 0, sizeof(work_buff)); +#endif + + // Write padding + for (int i = 0; i < 4; i++) { + size = align4bytes(SIZE_OF_ONE_GAME_IN_BYTES - totalSize - 4); + if (size > sizeof(work_buff)) + size = sizeof(work_buff); + if (size > 4) { + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, size)) + return false; + totalSize += size; + } + } + + // Write checksum and close + CFileMgr::Write(file, (const char *) &CheckSum, sizeof(CheckSum)); + if (CFileMgr::GetErrorReadWrite(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; + + return false; + } + + return true; +} + +bool +GenericLoad() +{ + uint8 *buf; + int32 file; + uint32 size; +#ifdef MISSION_REPLAY + int8 qs; +#endif + + int32 saveSize; + CPad *currPad; + + // Load SimpleVars and Scripts + CheckSum = 0; + CDate dummy; // unused + CPad::ResetCheats(); + if (!ReadInSizeofSaveFileBuffer(file, size)) + return false; + size = align4bytes(size); + ReadDataFromFile(file, work_buff, size); + buf = (work_buff + 0x40); + ReadDataFromBufferPointer(buf, saveSize); +#ifdef MISSION_REPLAY // a hack to keep compatibility but get new data from save + qs = saveSize >> 24; +#endif + ReadDataFromBufferPointer(buf, CGame::currLevel); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().x); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().y); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().z); + ReadDataFromBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute); + ReadDataFromBufferPointer(buf, CClock::ms_nLastClockTick); + ReadDataFromBufferPointer(buf, CClock::ms_nGameClockHours); + ReadDataFromBufferPointer(buf, CClock::ms_nGameClockMinutes); + currPad = CPad::GetPad(0); + ReadDataFromBufferPointer(buf, currPad->Mode); + ReadDataFromBufferPointer(buf, CTimer::m_snTimeInMilliseconds); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeScale); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStep); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStepNonClipped); + ReadDataFromBufferPointer(buf, CTimer::m_FrameCounter); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeStep); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeScale); + ReadDataFromBufferPointer(buf, CWeather::OldWeatherType); + ReadDataFromBufferPointer(buf, CWeather::NewWeatherType); + ReadDataFromBufferPointer(buf, CWeather::ForcedWeatherType); + ReadDataFromBufferPointer(buf, CWeather::InterpolationValue); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nSecond); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nMinute); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nHour); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nDay); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nMonth); + ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nYear); + ReadDataFromBufferPointer(buf, CWeather::WeatherTypeInList); +#ifdef COMPATIBLE_SAVES + // converted to float for compatibility with original format + // TODO: maybe remove this? not really gonna break anything vital + float f; + ReadDataFromBufferPointer(buf, f); + TheCamera.CarZoomIndicator = f; + ReadDataFromBufferPointer(buf, f); + TheCamera.PedZoomIndicator = f; +#else + ReadDataFromBufferPointer(buf, TheCamera.CarZoomIndicator); + ReadDataFromBufferPointer(buf, TheCamera.PedZoomIndicator); +#endif + assert(buf - work_buff == SIZE_OF_SIMPLEVARS); +#ifdef MISSION_REPLAY + WaitForSave = 0; + if (FrontEndMenuManager.m_nCurrSaveSlot == PAUSE_SAVE_SLOT && qs == 3) + WaitForMissionActivate = CTimer::GetTimeInMilliseconds() + 2000; +#endif + ReadDataFromBlock("Loading Scripts \n", CTheScripts::LoadAllScripts); + + // Load the rest + LoadSaveDataBlock(); + ReadDataFromBlock("Loading PedPool \n", CPools::LoadPedPool); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Garages \n", CGarages::Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Vehicles \n", CPools::LoadVehiclePool); + LoadSaveDataBlock(); + CProjectileInfo::RemoveAllProjectiles(); + CObject::DeleteAllTempObjects(); + ReadDataFromBlock("Loading Objects \n", CPools::LoadObjectPool); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Paths \n", ThePaths.Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Cranes \n", CCranes::Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Pickups \n", CPickups::Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Phoneinfo \n", gPhoneInfo.Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Restart \n", CRestart::LoadAllRestartPoints); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Radar Blips \n", CRadar::LoadAllRadarBlips); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Zones \n", CTheZones::LoadAllZones); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Gang Data \n", CGangs::LoadAllGangData); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Car Generators \n", CTheCarGenerators::LoadAllCarGenerators); + CParticle::ReloadConfig(); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Particles \n", CParticleObject::LoadParticle); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading AudioScript Objects \n", cAudioScriptObject::LoadAllAudioScriptObjects); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Player Info \n", CWorld::Players[CWorld::PlayerInFocus].LoadPlayerInfo); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Stats \n", CStats::LoadStats); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Streaming Stuff \n", CStreaming::MemoryCardLoad); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading PedType Stuff \n", CPedType::Load); + + DMAudio.SetMusicMasterVolume(CMenuManager::m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(CMenuManager::m_PrefsSfxVolume); + if (!CloseFile(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } + + DoGameSpecificStuffAfterSucessLoad(); + debug("Game successfully loaded \n"); + return true; +} + +bool +ReadInSizeofSaveFileBuffer(int32 &file, uint32 &size) +{ + file = CFileMgr::OpenFile(LoadFileName, "rb"); + if (file == 0) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; + return false; + } + CFileMgr::Read(file, (const char*)&size, sizeof(size)); + if (CFileMgr::GetErrorReadWrite(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } + return true; +} + +bool +ReadDataFromFile(int32 file, uint8 *buf, uint32 size) +{ + if (file == 0) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; + return false; + } + size_t read_size = CFileMgr::Read(file, (const char*)buf, size); + if (CFileMgr::GetErrorReadWrite(file) || read_size != size) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } + return true; +} + +bool +CloseFile(int32 file) +{ + return CFileMgr::CloseFile(file) == 0; +} + +void +DoGameSpecificStuffAfterSucessLoad() +{ + StillToFadeOut = true; + JustLoadedDontFadeInYet = true; + CTheScripts::Process(); +} + +bool +CheckSlotDataValid(int32 slot) +{ + PcSaveHelper.nErrorCode = SAVESTATUS_SUCCESSFUL; + if (CheckDataNotCorrupt(slot, LoadFileName)) { + CStreaming::DeleteAllRwObjects(); + return true; + } + + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_DATA_INVALID; + return false; +} + +void +MakeSpaceForSizeInBufferPointer(uint8 *&presize, uint8 *&buf, uint8 *&postsize) +{ + presize = buf; + buf += sizeof(uint32); + postsize = buf; +} + +void +CopySizeAndPreparePointer(uint8 *&buf, uint8 *&postbuf, uint8 *&postbuf2, uint32 &unused, uint32 &size) +{ + memcpy(buf, &size, sizeof(size)); + size = align4bytes(size); + postbuf2 += size; + postbuf = postbuf2; +} + +void +DoGameSpecificStuffBeforeSave() +{ + CGameLogic::PassTime(360); + CPlayerPed *ped = FindPlayerPed(); + ped->m_fCurrentStamina = ped->m_fMaxStamina; + CGame::TidyUpMemory(true, false); +} + + +void +MakeValidSaveName(int32 slot) +{ + ValidSaveName[0] = '\0'; + sprintf(ValidSaveName, "%s%i", DefaultPCSaveFileName, slot + 1); + strncat(ValidSaveName, ".b", 5); +} + +wchar * +GetSavedGameDateAndTime(int32 slot) +{ + return SlotSaveDate[slot]; +} + +wchar * +GetNameOfSavedGame(int32 slot) +{ + return SlotFileName[slot]; +} + +bool +CheckDataNotCorrupt(int32 slot, char *name) +{ +#ifdef FIX_BUGS + char filename[MAX_PATH]; +#else + char filename[100]; +#endif + + int32 blocknum = 0; + eLevelName level = LEVEL_GENERIC; + CheckSum = 0; + uint32 bytes_processed = 0; + sprintf(filename, "%s%i%s", DefaultPCSaveFileName, slot + 1, ".b"); + int file = CFileMgr::OpenFile(filename, "rb"); + if (file == 0) + return false; + strcpy(name, filename); + while (SIZE_OF_ONE_GAME_IN_BYTES - sizeof(uint32) > bytes_processed && blocknum < 40) { + int32 blocksize; + if (!ReadDataFromFile(file, (uint8*)&blocksize, sizeof(blocksize))) { + CloseFile(file); + return false; + } + if (blocksize > align4bytes(sizeof(work_buff))) + blocksize = sizeof(work_buff) - sizeof(uint32); + if (!ReadDataFromFile(file, work_buff, align4bytes(blocksize))) { + CloseFile(file); + return false; + } + + CheckSum += ((uint8*)&blocksize)[0]; + CheckSum += ((uint8*)&blocksize)[1]; + CheckSum += ((uint8*)&blocksize)[2]; + CheckSum += ((uint8*)&blocksize)[3]; + uint8 *_work_buf = work_buff; + for (int i = 0; i < align4bytes(blocksize); i++) { + CheckSum += *_work_buf++; + bytes_processed++; + } + + if (blocknum == 0) + memcpy(&level, work_buff+4, sizeof(level)); + blocknum++; + } + int32 _checkSum; + if (ReadDataFromFile(file, (uint8*)&_checkSum, sizeof(_checkSum))) { + if (CloseFile(file)) { + if (CheckSum == _checkSum) { + m_LevelToLoad = level; + return true; + } + return false; + } + return false; + } + + CloseFile(file); + return false; +} + +bool +RestoreForStartLoad() +{ + uint8 buf[999]; + + int file = CFileMgr::OpenFile(LoadFileName, "rb"); + if (file == 0) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; + return false; + } + ReadDataFromFile(file, buf, sizeof(buf)); + if (CFileMgr::GetErrorReadWrite(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } else { + uint8 *_buf = buf + sizeof(int32) + sizeof(wchar[24]) + sizeof(SYSTEMTIME) + sizeof(SIZE_OF_ONE_GAME_IN_BYTES); + ReadDataFromBufferPointer(_buf, CGame::currLevel); + ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().x); + ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().y); + ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().z); + ISLAND_LOADING_IS(LOW) + { + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + } + CCollision::SortOutCollisionAfterLoad(); + ISLAND_LOADING_IS(LOW) + { + CStreaming::RequestBigBuildings(CGame::currLevel); + CStreaming::LoadAllRequestedModels(false); + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); + CGame::TidyUpMemory(true, false); + } + if (CloseFile(file)) { + return true; + } else { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + return false; + } + } +} + +int +align4bytes(int32 size) +{ + return (size + 3) & 0xFFFFFFFC; +} + +#ifdef FIX_INCOMPATIBLE_SAVES +#define LoadSaveDataBlockNoCheck(buf, file, size) \ +do { \ + CFileMgr::Read(file, (const char *)&size, sizeof(size)); \ + size = align4bytes(size); \ + CFileMgr::Read(file, (const char *)work_buff, size); \ + buf = work_buff; \ +} while(0) + +#define WriteSavaDataBlockNoFunc(buf, file, size) \ +do { \ + if (!PcSaveHelper.PcClassSaveRoutine(file, buf, size)) \ + goto fail; \ + totalSize += size; \ +} while(0) + +#define FixSaveDataBlock(fix_func, file, size) \ +do { \ + ReadDataFromBufferPointer(buf, size); \ + memset(work_buff2, 0, sizeof(work_buff2)); \ + buf2 = work_buff2; \ + reserved = 0; \ + MakeSpaceForSizeInBufferPointer(presize, buf2, postsize); \ + fix_func(save_type, buf, buf2, &size); \ + CopySizeAndPreparePointer(presize, buf2, postsize, reserved, size); \ + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff2, buf2 - work_buff2)) \ + goto fail; \ + totalSize += buf2 - work_buff2; \ +} while(0) + +#define ReadDataFromBufferPointerWithSize(buf, to, size) memcpy(&to, buf, size); buf += align4bytes(size) + +#define ReadBuf(buf, to) memcpy(&to, buf, sizeof(to)); buf += sizeof(to) +#define WriteBuf(buf, from) memcpy(buf, &from, sizeof(from)); buf += sizeof(from) +#define CopyBuf(from, to, size) memcpy(to, from, size); to += (size); from += (size) +#define CopyPtr(from, to) memcpy(to, from, 4); to += 4; from += 8 +#define SkipBuf(buf, size) buf += (size) +#define SkipBoth(from, to, size) to += (size); from += (size) +#define SkipPtr(from, to) to += 4; from += 8 + +// unfortunately we need a 2nd buffer of the same size to store the fixed output ... +static uint8 work_buff2[sizeof(work_buff)]; + +enum +{ + SAVE_TYPE_NONE = 0, + SAVE_TYPE_32_BIT = 1, + SAVE_TYPE_64_BIT = 2, + SAVE_TYPE_MSVC = 4, + SAVE_TYPE_GCC = 8, +}; + +uint8 +GetSaveType(char *savename) +{ + uint8 save_type = SAVE_TYPE_NONE; + int file = CFileMgr::OpenFile(savename, "rb"); + + uint32 size; + CFileMgr::Read(file, (const char *)&size, sizeof(size)); + + uint8 *buf = work_buff; + CFileMgr::Read(file, (const char *)work_buff, size); // simple vars + scripts + + LoadSaveDataBlockNoCheck(buf, file, size); // ped pool + + LoadSaveDataBlockNoCheck(buf, file, size); // garages + ReadDataFromBufferPointer(buf, size); + + // store for later after we know how much data we need to skip + ReadDataFromBufferPointerWithSize(buf, work_buff2, size); + + LoadSaveDataBlockNoCheck(buf, file, size); // vehicle pool + LoadSaveDataBlockNoCheck(buf, file, size); // object pool + LoadSaveDataBlockNoCheck(buf, file, size); // paths + + LoadSaveDataBlockNoCheck(buf, file, size); // cranes + + CFileMgr::CloseFile(file); + + ReadDataFromBufferPointer(buf, size); + + if (size == 1032) + save_type |= SAVE_TYPE_32_BIT; + else if (size == 1160) + save_type |= SAVE_TYPE_64_BIT; + else + assert(0); // this should never happen + + buf = work_buff2; + + buf += 760; // skip everything before the first garage + buf += save_type & SAVE_TYPE_32_BIT ? 28 : 40; // skip first garage up to m_fX1 + + // now the values we want to verify + float fX1, fX2, fY1, fY2, fZ1, fZ2; + + ReadBuf(buf, fX1); + ReadBuf(buf, fX2); + ReadBuf(buf, fY1); + ReadBuf(buf, fY2); + ReadBuf(buf, fZ1); + ReadBuf(buf, fZ2); + + if (fX1 == CRUSHER_GARAGE_X1 && fX2 == CRUSHER_GARAGE_X2 && + fY1 == CRUSHER_GARAGE_Y1 && fY2 == CRUSHER_GARAGE_Y2 && + fZ1 == CRUSHER_GARAGE_Z1 && fZ2 == CRUSHER_GARAGE_Z2) + save_type |= SAVE_TYPE_MSVC; + else + save_type |= SAVE_TYPE_GCC; + + return save_type; +} + +static void +FixGarages(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + // hardcoded: 5484 + // x86 msvc: 5240 + // x86 gcc: 5040 + // amd64 msvc: 5880 + // amd64 gcc: 5808 + + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read; + uint32 written = 5240; + + if (save_type & SAVE_TYPE_32_BIT && save_type & SAVE_TYPE_GCC) + read = 5040; + else if (save_type & SAVE_TYPE_64_BIT && save_type & SAVE_TYPE_GCC) + read = 5808; + else + read = 5880; + + uint32 ptrsize = save_type & SAVE_TYPE_32_BIT ? 4 : 8; + + CopyBuf(buf, buf2, 4 * 6); + CopyBuf(buf, buf2, 4 * TOTAL_COLLECTCARS_GARAGES); + CopyBuf(buf, buf2, 4); + + if (save_type & SAVE_TYPE_GCC) + { + for (int32 i = 0; i < NUM_GARAGE_STORED_CARS; i++) + { +#define FixStoredCar(buf, buf2) \ +do { \ + CopyBuf(buf, buf2, 4 + sizeof(CVector) + sizeof(CVector)); \ + uint8 nFlags8; \ + ReadBuf(buf, nFlags8); \ + int32 nFlags32 = nFlags8; \ + WriteBuf(buf2, nFlags32); \ + CopyBuf(buf, buf2, 1 * 6); \ + SkipBuf(buf, 1); \ + SkipBuf(buf2, 2); \ +} while(0) + + FixStoredCar(buf, buf2); + FixStoredCar(buf, buf2); + FixStoredCar(buf, buf2); + +#undef FixStoredCar + } + } + else + { + CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS); + CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS); + CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS); + } + + for (int32 i = 0; i < NUM_GARAGES; i++) + { + // skip the last 5 garages in 64bit builds without FIX_GARAGE_SIZE since they weren't actually saved and are unused + if (save_type & SAVE_TYPE_64_BIT && *size == 5484 && i >= NUM_GARAGES - 5) + { + SkipBuf(buf, 160); // sizeof(CGarage) on x64 + SkipBuf(buf2, 140); // sizeof(CGarage) on x86 + } + else + { + CopyBuf(buf, buf2, 1 * 6); + SkipBoth(buf, buf2, 2); + CopyBuf(buf, buf2, 4); + SkipBuf(buf, ptrsize - 4); // write 4 bytes padding if 8 byte pointer, if not, write 0 + SkipBuf(buf, ptrsize * 2); + SkipBuf(buf2, 4 * 2); + CopyBuf(buf, buf2, 1 * 7); + SkipBoth(buf, buf2, 1); + CopyBuf(buf, buf2, 4 * 15 + 1); + SkipBoth(buf, buf2, 3); + SkipBuf(buf, ptrsize * 2); + SkipBuf(buf2, 4 * 2); + + if (save_type & SAVE_TYPE_GCC) + SkipBuf(buf, save_type & SAVE_TYPE_64_BIT ? 36 + 4 : 36); // sizeof(CStoredCar) on gcc 64/32 before fix + else + SkipBuf(buf, sizeof(CStoredCar)); + + SkipBuf(buf2, sizeof(CStoredCar)); + } + } + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + +#ifdef FIX_GARAGE_SIZE + *size = (6 * sizeof(uint32) + TOTAL_COLLECTCARS_GARAGES * sizeof(*CGarages::CarTypesCollected) + sizeof(uint32) + 3 * NUM_GARAGE_STORED_CARS * sizeof(CStoredCar) + NUM_GARAGES * sizeof(CGarage)); +#else + *size = 5484; +#endif +} + +static void +FixCranes(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = 2 * sizeof(uint32) + 0x480; // sizeof(aCranes) + uint32 written = 2 * sizeof(uint32) + 0x400; // see CRANES_SAVE_SIZE + + CopyBuf(buf, buf2, 4 + 4); + + for (int32 i = 0; i < NUM_CRANES; i++) + { + CopyPtr(buf, buf2); + CopyPtr(buf, buf2); + CopyBuf(buf, buf2, 15 * 4 + sizeof(CVector) * 3 + sizeof(CVector2D)); + CopyPtr(buf, buf2); + CopyBuf(buf, buf2, 4 + 7 * 1); + SkipBuf(buf, 5); + SkipBuf(buf2, 1); + } + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixPickups(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = 0x3480 + sizeof(uint16) + sizeof(uint16) + sizeof(int32) * NUMCOLLECTEDPICKUPS; // sizeof(aPickUps) + uint32 written = 0x24C0 + sizeof(uint16) + sizeof(uint16) + sizeof(int32) * NUMCOLLECTEDPICKUPS; // see PICKUPS_SAVE_SIZE + + for (int32 i = 0; i < NUMPICKUPS; i++) + { + CopyBuf(buf, buf2, 1 + 1 + 2); + SkipBuf(buf, 4); + CopyPtr(buf, buf2); + CopyBuf(buf, buf2, 4 + 2 + 2 + sizeof(CVector)); + SkipBuf(buf, 4); + } + + CopyBuf(buf, buf2, 2); + SkipBoth(buf, buf2, 2); + + CopyBuf(buf, buf2, NUMCOLLECTEDPICKUPS * 4); + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixPhoneInfo(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = 0x1138; // sizeof(CPhoneInfo) + uint32 written = 0xA30; // see PHONEINFO_SAVE_SIZE + + CopyBuf(buf, buf2, 4 + 4); + + for (int32 i = 0; i < NUMPHONES; i++) + { + CopyBuf(buf, buf2, sizeof(CVector)); + SkipBuf(buf, 4); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + CopyBuf(buf, buf2, 4); + SkipBuf(buf, 4); + CopyPtr(buf, buf2); + CopyBuf(buf, buf2, 4 + 1); + SkipBoth(buf, buf2, 3); + } + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixZones(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = 11300; // see SaveAllZones + uint32 written = 10100; // see SaveAllZones + + CopyBuf(buf, buf2, 1 * 4); + + SkipBuf(buf, 4); + uint32 hdr_size = 10100 - (1 * 4 + 4); // see SaveAllZones + WriteBuf(buf2, hdr_size); + + CopyBuf(buf, buf2, 4 * 2 + 2); + SkipBoth(buf, buf2, 2); + +#define FixOneZone(buf, buf2) \ +do { \ + CopyBuf(buf, buf2, 8 + 8 * 4 + 2 * 2); \ + SkipBuf(buf, 4); \ + CopyPtr(buf, buf2); \ + CopyPtr(buf, buf2); \ + CopyPtr(buf, buf2); \ +} while(0) + + for (int32 i = 0; i < NUMZONES; i++) + FixOneZone(buf, buf2); + + CopyBuf(buf, buf2, sizeof(CZoneInfo) * NUMZONES * 2); + CopyBuf(buf, buf2, 2 + 2); + + for (int32 i = 0; i < NUMMAPZONES; i++) + FixOneZone(buf, buf2); + + CopyBuf(buf, buf2, 2 * NUMAUDIOZONES); + CopyBuf(buf, buf2, 2 + 2); + +#undef FixOneZone + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixParticles(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + + int32 numObjects; + ReadBuf(buf, numObjects); + WriteBuf(buf2, numObjects); + + uint32 read = 0xA0 * (numObjects + 1) + 4; // sizeof(CParticleObject) + uint32 written = 0x88 * (numObjects + 1) + 4; // see PARTICLE_OBJECT_SIZEOF + + for (int32 i = 0; i < numObjects; i++) + { + // CPlaceable + SkipPtr(buf, buf2); + CopyBuf(buf, buf2, 4 * 4 * 4); + SkipPtr(buf, buf2); + CopyBuf(buf, buf2, 1); + SkipBuf(buf, 7); + SkipBuf(buf2, 3); + + // CParticleObject + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + SkipPtr(buf, buf2); + CopyBuf(buf, buf2, 4 * 3 + 2 * 1 + 2 * 2); + SkipBoth(buf, buf2, 2); + CopyBuf(buf, buf2, sizeof(CVector) + 2 * 4 + sizeof(CRGBA) + 2 * 1); + SkipBoth(buf, buf2, 2); + } + + SkipBuf(buf, 0xA0); // sizeof(CParticleObject) + SkipBuf(buf2, 0x88); // see PARTICLE_OBJECT_SIZEOF + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +bool +FixSave(int32 slot, uint8 save_type) +{ + if (save_type & SAVE_TYPE_32_BIT && save_type & SAVE_TYPE_MSVC) + return true; + + bool success = false; + + uint8 *buf, *presize, *postsize, *buf2; + uint32 size; + uint32 reserved; + + uint32 totalSize; + + char savename[MAX_PATH]; + char savename_bak[MAX_PATH]; + + sprintf(savename, "%s%i%s", DefaultPCSaveFileName, slot + 1, ".b"); + sprintf(savename_bak, "%s%i%s.%lld.bak", DefaultPCSaveFileName, slot + 1, ".b", time(nil)); + + assert(caserename(savename, savename_bak) == 0); + + int file_in = CFileMgr::OpenFile(savename_bak, "rb"); + int file_out = CFileMgr::OpenFileForWriting(savename); + + CheckSum = 0; + totalSize = 0; + + CFileMgr::Read(file_in, (const char *)&size, sizeof(size)); + size = align4bytes(size); + + buf = work_buff; + CFileMgr::Read(file_in, (const char *)work_buff, size); // simple vars + scripts + + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // ped pool + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // garages + FixSaveDataBlock(FixGarages, file_out, size); // garages need to be fixed in either case + + LoadSaveDataBlockNoCheck(buf, file_in, size); // vehicle pool + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // object pool + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // paths + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // cranes + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixCranes, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // pickups + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixPickups, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // phoneinfo + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixPhoneInfo, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // restart + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // radar blips + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // zones + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixZones, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // gang data + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // car generators + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // particles + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixParticles, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // audio script objects + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // player info + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // stats + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // streaming + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // ped type + WriteSavaDataBlockNoFunc(buf, file_out, size); + + memset(work_buff, 0, sizeof(work_buff)); + + for (int i = 0; i < 4; i++) { + size = align4bytes(SIZE_OF_ONE_GAME_IN_BYTES - totalSize - 4); + if (size > sizeof(work_buff)) + size = sizeof(work_buff); + if (size > 4) { + if (!PcSaveHelper.PcClassSaveRoutine(file_out, work_buff, size)) + goto fail; + totalSize += size; + } + } + + if (!CFileMgr::Write(file_out, (const char *)&CheckSum, sizeof(CheckSum))) + goto fail; + + success = true; + +fail:; + CFileMgr::CloseFile(file_in); + CFileMgr::CloseFile(file_out); + + return success; +} + +#undef LoadSaveDataBlockNoCheck +#undef WriteSavaDataBlockNoFunc +#undef FixSaveDataBlock +#undef ReadDataFromBufferPointerWithSize +#undef ReadBuf +#undef WriteBuf +#undef CopyBuf +#undef CopyPtr +#undef SkipBuf +#undef SkipBoth +#undef SkipPtr +#endif + +#ifdef MISSION_REPLAY + +void DisplaySaveResult(int unk, char* name) +{} + +bool SaveGameForPause(int type) +{ + if (AllowMissionReplay != MISSION_RETRY_STAGE_NORMAL) + return false; + if (type != SAVE_TYPE_QUICKSAVE_FOR_MISSION_REPLAY && WaitForSave > CTimer::GetTimeInMilliseconds()) + return false; + WaitForSave = 0; + if (gGameState != GS_PLAYING_GAME || CTheScripts::IsPlayerOnAMission() || CStats::LastMissionPassedName[0] == '\0') { + DisplaySaveResult(3, CStats::LastMissionPassedName); + return false; + } + IsQuickSave = type; + MissionStartTime = 0; + int res = PcSaveHelper.SaveSlot(PAUSE_SAVE_SLOT); + PcSaveHelper.PopulateSlotInfo(); + IsQuickSave = 0; + DisplaySaveResult(res, CStats::LastMissionPassedName); + return true; +} +#endif diff --git a/src/save/GenericGameStorage.h b/src/save/GenericGameStorage.h new file mode 100644 index 0000000..6a5b04f --- /dev/null +++ b/src/save/GenericGameStorage.h @@ -0,0 +1,63 @@ +#pragma once + +#include "Game.h" +#include "PCSave.h" + +#define SLOT_COUNT (8) + +bool GenericSave(int file); +bool GenericLoad(); +bool ReadInSizeofSaveFileBuffer(int32 &file, uint32 &size); +bool ReadDataFromFile(int32 file, uint8 *buf, uint32 size); +bool CloseFile(int32 file); +void DoGameSpecificStuffAfterSucessLoad(); +bool CheckSlotDataValid(int32 slot); +void MakeSpaceForSizeInBufferPointer(uint8 *&presize, uint8 *&buf, uint8 *&postsize); +void CopySizeAndPreparePointer(uint8 *&buf, uint8 *&postbuf, uint8 *&postbuf2, uint32 &unused, uint32 &size); +void DoGameSpecificStuffBeforeSave(); +void MakeValidSaveName(int32 slot); +wchar *GetSavedGameDateAndTime(int32 slot); +wchar *GetNameOfSavedGame(int32 slot); +bool CheckDataNotCorrupt(int32 slot, char *name); +bool RestoreForStartLoad(); +int align4bytes(int32 size); + +#ifdef FIX_INCOMPATIBLE_SAVES +uint8 GetSaveType(char *savename); +bool FixSave(int32 slot, uint8 save_type); +#endif + +extern class CDate CompileDateAndTime; + +extern char DefaultPCSaveFileName[260]; +extern char ValidSaveName[260]; +extern char LoadFileName[256]; +extern wchar SlotFileName[SLOT_COUNT][260]; +extern wchar SlotSaveDate[SLOT_COUNT][70]; +extern int CheckSum; +extern enum eLevelName m_LevelToLoad; +extern int Slots[SLOT_COUNT+1]; + +extern bool b_FoundRecentSavedGameWantToLoad; +extern bool JustLoadedDontFadeInYet; +extern bool StillToFadeOut; +extern uint32 TimeStartedCountingForFade; +extern uint32 TimeToStayFadedBeforeFadeOut; + +extern char SaveFileNameJustSaved[260]; // 8F2570 + +const char TopLineEmptyFile[] = "THIS FILE IS NOT VALID YET"; + +#ifdef MISSION_REPLAY +extern int8 IsQuickSave; // originally int + +bool SaveGameForPause(int); + +enum { + SAVE_TYPE_NORMAL, + SAVE_TYPE_QUICKSAVE, + SAVE_TYPE_2, + SAVE_TYPE_QUICKSAVE_FOR_MISSION_REPLAY +}; + +#endif diff --git a/src/save/MemoryCard.cpp b/src/save/MemoryCard.cpp new file mode 100644 index 0000000..d6e95d3 --- /dev/null +++ b/src/save/MemoryCard.cpp @@ -0,0 +1,3084 @@ +#define WITHWINDOWS +#include "common.h" +#ifdef PS2_MENU +#include "crossplatform.h" +#include "MemoryCard.h" +#include "main.h" +#include "DMAudio.h" +#include "AudioScriptObject.h" +#include "Camera.h" +#include "CarGen.h" +#include "Cranes.h" +#include "Clock.h" +#include "MBlur.h" +#include "Date.h" +#include "Font.h" +#include "FileMgr.h" +#include "Game.h" +#include "GameLogic.h" +#include "Gangs.h" +#include "Garages.h" +#include "GenericGameStorage.h" +#include "Pad.h" +#include "Particle.h" +#include "ParticleObject.h" +#include "PathFind.h" +#include "PCSave.h" +#include "Phones.h" +#include "Pickups.h" +#include "PlayerPed.h" +#include "ProjectileInfo.h" +#include "Pools.h" +#include "Radar.h" +#include "Restart.h" +#include "Script.h" +#include "Stats.h" +#include "Streaming.h" +#include "Sprite2d.h" +#include "Timer.h" +#include "TimeStep.h" +#include "Weather.h" +#include "World.h" +#include "Zones.h" +#include "Frontend_PS2.h" + +CMemoryCard TheMemoryCard; + +char icon_one[16] = "slime1.ico"; +char icon_two[16] = "slime2.ico"; +char icon_three[16] = "slime3.ico"; +char HostFileLocationOfIcons[64] = "icons\\"; +char TheGameRootDirectory[64] = "/BESLES-50330GTA30000"; + +#define ReadDataFromBufferPointer(buf, to) memcpy(&to, buf, sizeof(to)); buf += align4bytes(sizeof(to)); +#define WriteDataToBufferPointer(buf, from) memcpy(buf, &from, sizeof(from)); buf += align4bytes(sizeof(from)); + +static int +align4bytes(int32 size) +{ + return (size + 3) & 0xFFFFFFFC; +} + +unsigned short ascii_table[3][2] = +{ + { 0x824F, 0x30 }, /* 0-9 */ + { 0x8260, 0x41 }, /* A-Z */ + { 0x8281, 0x61 }, /* a-z */ +}; + +unsigned short ascii_special[33][2] = +{ + {0x8140, 0x20}, /* " " */ + {0x8149, 0x21}, /* "!" */ + {0x8168, 0x22}, /* """ */ + {0x8194, 0x23}, /* "#" */ + {0x8190, 0x24}, /* "$" */ + {0x8193, 0x25}, /* "%" */ + {0x8195, 0x26}, /* "&" */ + {0x8166, 0x27}, /* "'" */ + {0x8169, 0x28}, /* "(" */ + {0x816A, 0x29}, /* ")" */ + {0x8196, 0x2A}, /* "*" */ + {0x817B, 0x2B}, /* "+" */ + {0x8143, 0x2C}, /* "," */ + {0x817C, 0x2D}, /* "-" */ + {0x8144, 0x2E}, /* "." */ + {0x815E, 0x2F}, /* "/" */ + {0x8146, 0x3A}, /* ":" */ + {0x8147, 0x3B}, /* ";" */ + {0x8171, 0x3C}, /* "<" */ + {0x8181, 0x3D}, /* "=" */ + {0x8172, 0x3E}, /* ">" */ + {0x8148, 0x3F}, /* "?" */ + {0x8197, 0x40}, /* "@" */ + {0x816D, 0x5B}, /* "[" */ + {0x818F, 0x5C}, /* "\" */ + {0x816E, 0x5D}, /* "]" */ + {0x814F, 0x5E}, /* "^" */ + {0x8151, 0x5F}, /* "_" */ + {0x8165, 0x60}, /* "`" */ + {0x816F, 0x7B}, /* "{" */ + {0x8162, 0x7C}, /* "|" */ + {0x8170, 0x7D}, /* "}" */ + {0x8150, 0x7E}, /* "~" */ +}; + +unsigned short +Ascii2Sjis(unsigned char ascii_code) +{ + unsigned short sjis_code = 0; + unsigned char stmp; + unsigned char stmp2 = 0; + + if ((ascii_code >= 0x20) && (ascii_code <= 0x2f)) + stmp2 = 1; + else + if ((ascii_code >= 0x30) && (ascii_code <= 0x39)) + stmp = 0; + else + if ((ascii_code >= 0x3a) && (ascii_code <= 0x40)) + stmp2 = 11; + else + if ((ascii_code >= 0x41) && (ascii_code <= 0x5a)) + stmp = 1; + else + if ((ascii_code >= 0x5b) && (ascii_code <= 0x60)) + stmp2 = 37; + else + if ((ascii_code >= 0x61) && (ascii_code <= 0x7a)) + stmp = 2; + else + if ((ascii_code >= 0x7b) && (ascii_code <= 0x7e)) + stmp2 = 63; + else { + printf("bad ASCII code 0x%x\n", ascii_code); + return(0); + } + + if (stmp2) + sjis_code = ascii_special[ascii_code - 0x20 - (stmp2 - 1)][0]; + else + sjis_code = ascii_table[stmp][0] + ascii_code - ascii_table[stmp][1]; + + return(sjis_code); +} + +#if defined(GTA_PC) + +extern "C" +{ + extern void HandleExit(); +} + +char CardCurDir[MAX_CARDS][260] = { "", "" }; +char PCCardsPath[260]; +char PCCardDir[MAX_CARDS][12] = { "memcard1", "memcard2" }; + +const char* _psGetUserFilesFolder(); +void _psCreateFolder(LPCSTR path); + +void +PCMCInit() +{ + sprintf(PCCardsPath, "%s", _psGetUserFilesFolder()); + + char path[512]; + + sprintf(path, "%s\\%s", PCCardsPath, PCCardDir[CARD_ONE]); + _psCreateFolder(path); + + sprintf(path, "%s\\%s", PCCardsPath, PCCardDir[CARD_TWO]); + _psCreateFolder(path); +} +#endif + +CMemoryCardInfo::CMemoryCardInfo(void) +{ + type = 0; + free = 0; + format = 0; + + for ( int32 i = 0; i < sizeof(dir); i++ ) + dir[i] = '\0'; + + strncpy(dir, TheGameRootDirectory, sizeof(dir) - 1); +} + +int32 +CMemoryCard::Init(void) +{ +#if defined(PS2) + if ( sceMcInit() == sceMcIniSucceed ) + { + printf("Memory card initialsed\n"); + return RES_SUCCESS; + } + + printf("Memory Card not being initialised\n"); + return RES_FAILED; +#else + PCMCInit(); + printf("Memory card initialsed\n"); + return RES_SUCCESS; +#endif +} + +CMemoryCard::CMemoryCard(void) +{ + _unk0 = 0; + CurrentCard = CARD_ONE; + Cards[CARD_ONE].port = 0; + Cards[CARD_TWO].port = 1; + + for ( int32 i = 0; i < sizeof(_unkName3); i++ ) + _unkName3[i] = '\0'; + + m_bWantToLoad = false; + _bunk2 = false; + _bunk7 = false; + JustLoadedDontFadeInYet = false; + StillToFadeOut = false; + TimeStartedCountingForFade = 0; + TimeToStayFadedBeforeFadeOut = 1750; + b_FoundRecentSavedGameWantToLoad = false; + + char date[64]; + char time[64]; + char day[8]; + char month[8]; + char year[8]; + char hour[8]; + char minute[8]; + char second[8]; + + strncpy(date, "Oct 7 2001", 62); + strncpy(time, "15:48:32", 62); + + strncpy(month, date, 3); + month[3] = '\0'; + + strncpy(day, &date[4], 2); + day[2] = '\0'; + + strncpy(year, &date[7], 4); + year[4] = '\0'; + + strncpy(hour, time, 2); + hour[2] = '\0'; + + strncpy(minute, &time[3], 2); + minute[2] = '\0'; + + strncpy(second, &time[6], 2); + second[2] = '\0'; + + + #define _CMP(m) strncmp(month, m, sizeof(m)-1) + + if ( !_CMP("Jan") ) CompileDateAndTime.m_nMonth = 1; + else + if ( !_CMP("Feb") ) CompileDateAndTime.m_nMonth = 2; + else + if ( !_CMP("Mar") ) CompileDateAndTime.m_nMonth = 3; + else + if ( !_CMP("Apr") ) CompileDateAndTime.m_nMonth = 4; + else + if ( !_CMP("May") ) CompileDateAndTime.m_nMonth = 5; + else + if ( !_CMP("Jun") ) CompileDateAndTime.m_nMonth = 6; + else + if ( !_CMP("Jul") ) CompileDateAndTime.m_nMonth = 7; + else + if ( !_CMP("Aug") ) CompileDateAndTime.m_nMonth = 8; + else + if ( !_CMP("Oct") ) CompileDateAndTime.m_nMonth = 9; // BUG: oct and sep is swapped here + else + if ( !_CMP("Sep") ) CompileDateAndTime.m_nMonth = 10; + else + if ( !_CMP("Nov") ) CompileDateAndTime.m_nMonth = 11; + else + if ( !_CMP("Dec") ) CompileDateAndTime.m_nMonth = 12; + + #undef _CMP + + CompileDateAndTime.m_nDay = atoi(day); + CompileDateAndTime.m_nYear = atoi(year); + CompileDateAndTime.m_nHour = atoi(hour); + CompileDateAndTime.m_nMinute = atoi(minute); + CompileDateAndTime.m_nSecond = atoi(second); +} + +int32 +CMemoryCard::RestoreForStartLoad(void) +{ + uint8 buf[30]; + + int32 file = OpenMemCardFileForReading(CurrentCard, LoadFileName); + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + ReadFromMemCard(file, buf, sizeof(buf) - 1); + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + uint8 *pBuf = buf + sizeof(uint32) + sizeof(uint32); + ReadDataFromBufferPointer(pBuf, CGame::currLevel); + ReadDataFromBufferPointer(pBuf, TheCamera.GetMatrix().GetPosition().x); + ReadDataFromBufferPointer(pBuf, TheCamera.GetMatrix().GetPosition().y); + ReadDataFromBufferPointer(pBuf, TheCamera.GetMatrix().GetPosition().z); + + if ( CGame::currLevel != LEVEL_INDUSTRIAL ) + CStreaming::RemoveBigBuildings(LEVEL_INDUSTRIAL); + + if ( CGame::currLevel != LEVEL_COMMERCIAL ) + CStreaming::RemoveBigBuildings(LEVEL_COMMERCIAL); + + if ( CGame::currLevel != LEVEL_SUBURBAN ) + CStreaming::RemoveBigBuildings(LEVEL_SUBURBAN); + + CStreaming::RemoveIslandsNotUsed(CGame::currLevel); + CCollision::SortOutCollisionAfterLoad(); + CStreaming::RequestBigBuildings(CGame::currLevel); + CStreaming::LoadAllRequestedModels(false); + CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel); + CGame::TidyUpMemory(true, false); + + CloseMemCardFile(file); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + return RES_SUCCESS; +} + +int32 +CMemoryCard::LoadSavedGame(void) +{ + CheckSum = 0; + CDate date; + + int32 saveSize = 0; + uint32 size = 0; + + int32 oldLang = CMenuManager::m_PrefsLanguage; + + CPad::ResetCheats(); + + ChangeDirectory(CurrentCard, Cards[CurrentCard].dir); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + int32 file = OpenMemCardFileForReading(CurrentCard, LoadFileName); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + + #define LoadSaveDataBlock()\ + do {\ + ReadFromMemCard(file, &size, sizeof(size)); \ + if ( nError != NO_ERR_SUCCESS ) return RES_FAILED; \ + size = align4bytes(size); \ + ReadFromMemCard(file, work_buff, size); \ + if ( nError != NO_ERR_SUCCESS ) return RES_FAILED; \ + buf = work_buff; \ + } while (0) + + uint8 *buf; + + LoadSaveDataBlock(); + + ReadDataFromBufferPointer(buf, saveSize); + ReadDataFromBufferPointer(buf, CGame::currLevel); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().x); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().y); + ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().z); + ReadDataFromBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute); + ReadDataFromBufferPointer(buf, CClock::ms_nLastClockTick); + ReadDataFromBufferPointer(buf, CClock::ms_nGameClockHours); + ReadDataFromBufferPointer(buf, CClock::ms_nGameClockMinutes); + ReadDataFromBufferPointer(buf, CPad::GetPad(0)->Mode); + ReadDataFromBufferPointer(buf, CTimer::m_snTimeInMilliseconds); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeScale); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStep); + ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStepNonClipped); + ReadDataFromBufferPointer(buf, CTimer::m_FrameCounter); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeStep); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate); + ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeScale); + ReadDataFromBufferPointer(buf, CWeather::OldWeatherType); + ReadDataFromBufferPointer(buf, CWeather::NewWeatherType); + ReadDataFromBufferPointer(buf, CWeather::ForcedWeatherType); + ReadDataFromBufferPointer(buf, CWeather::InterpolationValue); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsMusicVolume); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsSfxVolume); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsControllerConfig); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsUseVibration); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsStereoMono); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsRadioStation); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsBrightness); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsShowTrails); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsShowSubtitles); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsLanguage); + ReadDataFromBufferPointer(buf, CMenuManager::m_PrefsUseWideScreen); + ReadDataFromBufferPointer(buf, CPad::GetPad(0)->Mode); +#ifdef PS2 + ReadDataFromBufferPointer(buf, BlurOn); +#else + ReadDataFromBufferPointer(buf, CMBlur::BlurOn); +#endif + ReadDataFromBufferPointer(buf, date.m_nSecond); + ReadDataFromBufferPointer(buf, date.m_nMinute); + ReadDataFromBufferPointer(buf, date.m_nHour); + ReadDataFromBufferPointer(buf, date.m_nDay); + ReadDataFromBufferPointer(buf, date.m_nMonth); + ReadDataFromBufferPointer(buf, date.m_nYear); + ReadDataFromBufferPointer(buf, CWeather::WeatherTypeInList); + ReadDataFromBufferPointer(buf, TheCamera.CarZoomIndicator); + ReadDataFromBufferPointer(buf, TheCamera.PedZoomIndicator); + + if ( date > CompileDateAndTime ) + ; + else + if ( date < CompileDateAndTime ) + ; + + #define ReadDataFromBlock(load_func)\ + do {\ + ReadDataFromBufferPointer(buf, size);\ + load_func(buf, size);\ + size = align4bytes(size);\ + buf += size;\ + } while (0) + + + + printf("Loading Scripts \n"); + ReadDataFromBlock(CTheScripts::LoadAllScripts); + + printf("Loading PedPool \n"); + ReadDataFromBlock(CPools::LoadPedPool); + + printf("Loading Garages \n"); + ReadDataFromBlock(CGarages::Load); + + printf("Loading Vehicles \n"); + ReadDataFromBlock(CPools::LoadVehiclePool); + + LoadSaveDataBlock(); + + CProjectileInfo::RemoveAllProjectiles(); + CObject::DeleteAllTempObjects(); + + printf("Loading Objects \n"); + ReadDataFromBlock(CPools::LoadObjectPool); + + printf("Loading Paths \n"); + ReadDataFromBlock(ThePaths.Load); + + printf("Loading Cranes \n"); + ReadDataFromBlock(CCranes::Load); + + LoadSaveDataBlock(); + + printf("Loading Pickups \n"); + ReadDataFromBlock(CPickups::Load); + + printf("Loading Phoneinfo \n"); + ReadDataFromBlock(gPhoneInfo.Load); + + printf("Loading Restart \n"); + ReadDataFromBlock(CRestart::LoadAllRestartPoints); + + printf("Loading Radar Blips \n"); + ReadDataFromBlock(CRadar::LoadAllRadarBlips); + + printf("Loading Zones \n"); + ReadDataFromBlock(CTheZones::LoadAllZones); + + printf("Loading Gang Data \n"); + ReadDataFromBlock(CGangs::LoadAllGangData); + + printf("Loading Car Generators \n"); + ReadDataFromBlock(CTheCarGenerators::LoadAllCarGenerators); + + printf("Loading Particles \n"); + ReadDataFromBlock(CParticleObject::LoadParticle); + + printf("Loading AudioScript Objects \n"); + ReadDataFromBlock(cAudioScriptObject::LoadAllAudioScriptObjects); + + printf("Loading Player Info \n"); + ReadDataFromBlock(CWorld::Players[CWorld::PlayerInFocus].LoadPlayerInfo); + + printf("Loading Stats \n"); + ReadDataFromBlock(CStats::LoadStats); + + printf("Loading Streaming Stuff \n"); + ReadDataFromBlock(CStreaming::MemoryCardLoad); + + printf("Loading PedType Stuff \n"); + ReadDataFromBlock(CPedType::Load); + + #undef LoadSaveDataBlock + #undef ReadDataFromBlock + + FrontEndMenuManager.SetSoundLevelsForMusicMenu(); + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + + CloseMemCardFile(file); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + if ( oldLang != CMenuManager::m_PrefsLanguage ) + { + TheText.Unload(); + TheText.Load(); + } + + JustLoadedDontFadeInYet = true; + StillToFadeOut = true; + + CTheScripts::Process(); + + printf("Game sucessfully loaded \n"); + + return RES_SUCCESS; +} + +int32 +CMemoryCard::CheckCardInserted(int32 cardID) +{ +#if defined(PS2) + int cmd = sceMcFuncNoCardInfo; + int type = sceMcTypeNoCard; + + CTimer::Stop(); + + while ( sceMcGetInfo(Cards[cardID].port, 0, &type, 0, 0) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( type == sceMcTypePS2 ) + { + if ( result == sceMcResChangedCard || result == sceMcResSucceed ) + { + nError = NO_ERR_SUCCESS; + return nError; + } + else if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + } + + printf("Memory card %i not present\n", cardID); + + nError = ERR_NONE; + return nError; +#else + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::PopulateCardFlags(int32 cardID, bool bSlotFlag, bool bTypeFlag, bool bFreeFlag, bool bFormatFlag) +{ +#if defined(PS2) + int cmd = sceMcFuncNoCardInfo; + int type = sceMcTypeNoCard; + int free = 0; + int format = 0; + + CTimer::Stop(); + + Cards[cardID].type = 0; + Cards[cardID].free = 0; + Cards[cardID].format = 0; + + while ( sceMcGetInfo(Cards[cardID].port, 0, &type, &free, &format) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( type == sceMcTypePS2 ) + { + if ( result == sceMcResChangedCard || result == sceMcResSucceed ) + { + if ( bSlotFlag ) + Cards[cardID].slot = 0; + + //if ( bTypeFlag ) + Cards[cardID].type = type; + + if ( bFreeFlag ) + Cards[cardID].free = free; + + if ( bFormatFlag ) + Cards[cardID].format = format; + + printf("Memory card %i present\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + else if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + } + + printf("Memory card %i not present\n", cardID); + + nError = ERR_NONE; + return nError; +#else + CTimer::Stop(); + + Cards[cardID].type = 0; + Cards[cardID].free = 0; + Cards[cardID].format = 0; + + if ( bSlotFlag ) + Cards[cardID].slot = 0; + + //if ( bTypeFlag ) + Cards[cardID].type = 0; + + if ( bFreeFlag ) + Cards[cardID].free = 1024 * 1024 * 4; + + if ( bFormatFlag ) + Cards[cardID].format = 0; + + printf("Memory card %i present\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::FormatCard(int32 cardID) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoFormat; + + int32 r = CheckCardInserted(cardID); + if ( r == NO_ERR_SUCCESS ) + { + while ( sceMcFormat(Cards[cardID].port, 0) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result < sceMcResSucceed ) + { + printf("Memory card %i could not be formatted\n", cardID); + + nError = ERR_FORMATFAILED; + return nError; + } + + printf("Memory card %i present and formatted\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + + return r; +#else + printf("Memory card %i present and formatted\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::PopulateFileTable(int32 cardID) +{ + CTimer::Stop(); + +#if defined (PS2) + int cmd = sceMcFuncNoGetDir; + + ClearFileTableBuffer(cardID); + + while ( sceMcGetDir(Cards[cardID].port, 0, "*", 0, ARRAY_SIZE(Cards[cardID].table), Cards[cardID].table) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + printf("Memory card %i present PopulateFileTables function successfull \n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i PopulateFileTables function successfull. MemoryCard not Formatted \n", cardID); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i PopulateFileTables function unsuccessfull. Path does not exist \n", cardID); + + nError = ERR_FILETABLENOENTRY; + return nError; + } + + printf("Memory card %i not Present\n", cardID); + + nError = ERR_NONE; + return nError; +#else + ClearFileTableBuffer(cardID); + + char path[512]; + sprintf(path, "%s\\%s\\%s\\*", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID]); + + memset(Cards[cardID].table, 0, sizeof(Cards[cardID].table)); + WIN32_FIND_DATA fd; HANDLE hFind; int32 num = 0; + if ( (hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE ) + { + printf("Memory card %i not Present\n", cardID); + nError = ERR_NONE; + return nError; + } + do + { + SYSTEMTIME st; + FileTimeToSystemTime(&fd.ftCreationTime, &st); + Cards[cardID].table[num]._Create.Sec = st.wSecond;Cards[cardID].table[num]._Create.Min = st.wMinute;Cards[cardID].table[num]._Create.Hour = st.wHour;Cards[cardID].table[num]._Create.Day = st.wDay;Cards[cardID].table[num]._Create.Month = st.wMonth;Cards[cardID].table[num]._Create.Year = st.wYear; + FileTimeToSystemTime(&fd.ftLastWriteTime, &st); + Cards[cardID].table[num]._Modify.Sec = st.wSecond;Cards[cardID].table[num]._Modify.Min = st.wMinute;Cards[cardID].table[num]._Modify.Hour = st.wHour;Cards[cardID].table[num]._Modify.Day = st.wDay;Cards[cardID].table[num]._Modify.Month = st.wMonth;Cards[cardID].table[num]._Modify.Year = st.wYear; + Cards[cardID].table[num].FileSizeByte = fd.nFileSizeLow; + strncpy((char *)Cards[cardID].table[num].EntryName, fd.cFileName, sizeof(Cards[cardID].table[num].EntryName) - 1); + num++; + } while( FindNextFile(hFind, &fd) && num < ARRAY_SIZE(Cards[cardID].table) ); + FindClose(hFind); + + //todo errors + + printf("Memory card %i present PopulateFileTables function successfull \n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::CreateRootDirectory(int32 cardID) +{ + CTimer::Stop(); +#if defined(PS2) + int cmd = sceMcFuncNoMkdir; + + while ( sceMcMkdir(Cards[cardID].port, 0, Cards[cardID].dir) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResSucceed ) + { + printf("Memory card %i present. RootDirectory Created\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i RootDirectory not created card unformatted\n", cardID); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResFullDevice ) + { + printf("Memory card %i RootDirectory not created due to insufficient memory card capacity\n", cardID); + + nError = ERR_DIRFULLDEVICE; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i RootDirectory not created due to problem with pathname\n", cardID); + + nError = ERR_DIRBADENTRY; + return nError; + } + + printf("Memory card %i not present so RootDirectory not created \n", cardID); + + nError = ERR_NONE; + return nError; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], Cards[cardID].dir); + _psCreateFolder(path); + + printf("Memory card %i present. RootDirectory Created\n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::ChangeDirectory(int32 cardID, char *dir) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoChDir; + + while ( sceMcChdir(Cards[cardID].port, 0, dir, 0) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResSucceed ) + { + printf("Memory Card %i. Changed to the directory %s \n", cardID, dir); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i. Couldn't change to the directory %s. MemoryCard not Formatted \n", cardID, dir); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i Couldn't change to the directory %s. Path does not exist \n", cardID, dir); + + nError = ERR_DIRNOENTRY; + return nError; + } + + printf("Memory card %i not Present. So could not change to directory %s.\n", cardID, dir); + + nError = ERR_NONE; + return nError; +#else + + if ( !strcmp(dir, "/" ) ) + { + strncpy(CardCurDir[cardID], dir, sizeof(CardCurDir[cardID]) - 1); + printf("Memory Card %i. Changed to the directory %s \n", cardID, dir); + nError = NO_ERR_SUCCESS; + return nError; + } + + char path[512]; + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], dir); + + WIN32_FIND_DATA fd; HANDLE hFind; + if ( (hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE ) + { + printf("Memory card %i Couldn't change to the directory %s. Path does not exist \n", cardID, dir); + + nError = ERR_DIRNOENTRY; + return nError; + } + + FindClose(hFind); + + strncpy(CardCurDir[cardID], dir, sizeof(CardCurDir[cardID]) - 1); + printf("Memory Card %i. Changed to the directory %s \n", cardID, dir); + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::CreateIconFiles(int32 cardID, char *icon_one, char *icon_two, char *icon_three) +{ +#if defined(PS2) + sceMcIconSys icon; + static sceVu0IVECTOR bgcolor[4] = { + { 0x80, 0, 0, 0 }, + { 0, 0x80, 0, 0 }, + { 0, 0, 0x80, 0 }, + { 0x80, 0x80, 0x80, 0 }, + }; + static sceVu0FVECTOR lightdir[3] = { + { 0.5, 0.5, 0.5, 0.0 }, + { 0.0,-0.4,-0.1, 0.0 }, + {-0.5,-0.5, 0.5, 0.0 }, + }; + static sceVu0FVECTOR lightcol[3] = { + { 0.48, 0.48, 0.03, 0.00 }, + { 0.50, 0.33, 0.20, 0.00 }, + { 0.14, 0.14, 0.38, 0.00 }, + }; + static sceVu0FVECTOR ambient = { 0.50, 0.50, 0.50, 0.00 }; + char head[8] = "PS2D"; + char title[8] = "GTA3"; + + memset(&icon, 0, sizeof(icon)); + + memcpy(icon.BgColor, bgcolor, sizeof(bgcolor)); + memcpy(icon.LightDir, lightdir, sizeof(lightdir)); + memcpy(icon.LightColor, lightcol, sizeof(lightcol)); + memcpy(icon.Ambient, ambient, sizeof(ambient)); + + icon.OffsLF = 24; + icon.TransRate = 0x60; + + unsigned short *titleName = (unsigned short *)icon.TitleName; + + uint32 titlec = 0; + while ( titlec < strlen(title) ) + { + unsigned short sjis = Ascii2Sjis(title[titlec]); + titleName[titlec] = (sjis << 8) | (sjis >> 8); + titlec++; + } + + titleName[titlec] = L'\0'; + + char icon1[80]; + char icon2[80]; + char icon3[80]; + + strncpy(icon1, icon_one, sizeof(icon1) - 1); + strncpy(icon2, icon_two, sizeof(icon2) - 1); + strncpy(icon3, icon_three, sizeof(icon3) - 1); + + strncpy((char *)icon.FnameView, icon1, sizeof(icon.FnameView) - 1); + strncpy((char *)icon.FnameCopy, icon2, sizeof(icon.FnameCopy) - 1); + strncpy((char *)icon.FnameDel, icon3, sizeof(icon.FnameDel) - 1); + strncpy((char *)icon.Head, head, sizeof(icon.Head)); + + int32 iconFile = CreateMemCardFileReadWrite(Cards[cardID].port, "icon.sys"); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + WritetoMemCard(iconFile, &icon, sizeof(icon)); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + CloseMemCardFile(iconFile); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + if ( LoadIconFiles(Cards[cardID].port, icon_one, icon_two, icon_three) == RES_SUCCESS ) + { + printf("All Icon files Created and loaded. \n"); + return RES_SUCCESS; + } + + printf("Could not load all the icon files \n"); + + return RES_FAILED; +#else + return RES_SUCCESS; +#endif +} + +int32 +CMemoryCard::LoadIconFiles(int32 cardID, char *icon_one, char *icon_two, char *icon_three) +{ +#if defined(PS2) + const uint32 size = 50968; + uint8 *data = new uint8[size]; + + char icon1_path[80]; + char icon2_path[80]; + char icon3_path[80]; + char icon1[32]; + char icon2[32]; + char icon3[32]; + + strncpy(icon1, icon_one, sizeof(icon1) - 1); + strncpy(icon2, icon_two, sizeof(icon2) - 1); + strncpy(icon3, icon_three, sizeof(icon3) - 1); + + int hostlen = strlen(HostFileLocationOfIcons); + + strncpy(icon1_path, HostFileLocationOfIcons, sizeof(icon1_path) - 1); + strncpy(icon2_path, HostFileLocationOfIcons, sizeof(icon2_path) - 1); + strncpy(icon3_path, HostFileLocationOfIcons, sizeof(icon3_path) - 1); + + strncpy(icon1_path+hostlen, icon_one, sizeof(icon1_path) - 1 - hostlen); + strncpy(icon2_path+hostlen, icon_two, sizeof(icon2_path) - 1 - hostlen); + strncpy(icon3_path+hostlen, icon_three, sizeof(icon3_path) - 1 - hostlen); + + // ico1 copy + int32 ico1file = CFileMgr::OpenFile(icon1_path); + CFileMgr::Read(ico1file, (char *)data, size); + CFileMgr::CloseFile(ico1file); + + int32 ico1mc = CreateMemCardFileReadWrite(Cards[cardID].port, icon1); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + WritetoMemCard(ico1mc, data, size); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + CloseMemCardFile(ico1mc); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + // ico2 copy + int32 ico2file = CFileMgr::OpenFile(icon2_path); + CFileMgr::Read(ico2file, (char *)data, size); + CFileMgr::CloseFile(ico2file); + + int32 ico2mc = CreateMemCardFileReadWrite(Cards[cardID].port, icon2); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + WritetoMemCard(ico2mc, data, size); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + CloseMemCardFile(ico2mc); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + // ico3 copy + int32 ico3file = CFileMgr::OpenFile(icon3_path); + CFileMgr::Read(ico3file, (char *)data, size); + CFileMgr::CloseFile(ico3file); + + int32 ico3mc = CreateMemCardFileReadWrite(Cards[cardID].port, icon3); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + WritetoMemCard(ico3mc, data, size); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + CloseMemCardFile(ico3mc); + + if ( nError != NO_ERR_SUCCESS ) + { + delete [] data; + return RES_FAILED; + } + + delete [] data; + + return RES_SUCCESS; +#else + return RES_SUCCESS; +#endif +} + +int32 +CMemoryCard::CloseMemCardFile(int32 file) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoClose; + + while ( sceMcClose(file) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResSucceed ) + { + printf("File %i closed\n", file); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory Card is Unformatted"); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory Card File Handle %i has not been opened", file); + + nError = ERR_OPENNOENTRY; + return nError; + } + + nError = ERR_NONE; + return nError; +#else + CFileMgr::CloseFile(file); + printf("File %i closed\n", file); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::CreateMemCardFileReadWrite(int32 cardID, char *filename) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoOpen; + + char buff[255]; + + strncpy(buff, filename, sizeof(buff)); + + while ( sceMcOpen(Cards[cardID].port, 0, buff, SCE_RDWR|SCE_CREAT) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + printf("%s File Created for MemoryCard. Its File handle is %i. \n", buff, result); + + nError = NO_ERR_SUCCESS; + return result; + } + + if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResFullDevice ) + { + nError = ERR_FILEFULLDEVICE; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + nError = ERR_FILENOPATHENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + nError = ERR_FILEDENIED; + return nError; + } + + if ( result == sceMcResUpLimitHandle ) + { + nError = ERR_FILEUPLIMIT; + return nError; + } + + printf("File %s not created on memory card.\n", buff); + + return ERR_NONE; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID], filename); + int32 file = CFileMgr::OpenFile(path, "wb+"); + if (file == 0) + { + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], filename); + file = CFileMgr::OpenFile(path, "wb+"); + } + + if ( file ) + { + printf("%s File Created for MemoryCard. Its File handle is %i. \n", filename, file); + nError = NO_ERR_SUCCESS; + return file; + } + + printf("File %s not created on memory card.\n", path); + + nError = ERR_NONE; + return 0; +#endif +} + +int32 +CMemoryCard::OpenMemCardFileForReading(int32 cardID, char *filename) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoOpen; + + char buff[255]; + + strncpy(buff, filename, sizeof(buff)); + + while ( sceMcOpen(Cards[cardID].port, 0, buff, SCE_RDONLY) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + printf("%s File Created for MemoryCard. Its File handle is %i. \n", buff, result); + + nError = NO_ERR_SUCCESS; + return result; + } + + if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResFullDevice ) + { + nError = ERR_FILEFULLDEVICE; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + nError = ERR_FILENOPATHENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + nError = ERR_FILEDENIED; + return nError; + } + + if ( result == sceMcResUpLimitHandle ) + { + nError = ERR_FILEUPLIMIT; + return nError; + } + + printf("File %s not created on memory card.\n", buff); + nError = ERR_NONE; + return nError; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID], filename); + int32 file = CFileMgr::OpenFile(path, "rb"); + if (file == 0) + { + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], filename); + file = CFileMgr::OpenFile(path, "rb"); + } + + if ( file ) + { + printf("%s File Created for MemoryCard. Its File handle is %i. \n", filename, file); + nError = NO_ERR_SUCCESS; + return file; + } + + printf("File %s not created on memory card.\n", path); + nError = ERR_NONE; + return 0; +#endif +} + +int32 +CMemoryCard::ReadFromMemCard(int32 file, void *buff, int32 size) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoRead; + + while ( sceMcRead(file, buff, size) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + if ( size >= result ) + { + printf("%i Bytes Read for Filehandle %i \n", result, file); + + nError = NO_ERR_SUCCESS; + return result; + } + } + + if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + nError = ERR_READNOENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + nError = ERR_READDENIED; + return nError; + } + + printf("No Bytes Read for Filehandle %i \n", file); + + nError = ERR_NONE; + return result; +#else + int32 s = CFileMgr::Read(file, (const char *)buff, size); + if ( s == size ) + { + printf("%i Bytes Read for Filehandle %i \n", s, file); + + nError = NO_ERR_SUCCESS; + return s; + } + + printf("No Bytes Read for Filehandle %i \n", file); + + nError = ERR_NONE; + return s; +#endif +} + +int32 +CMemoryCard::DeleteMemoryCardFile(int32 cardID, char *filename) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoDelete; + + while ( sceMcDelete(Cards[cardID].port, 0, filename) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResSucceed ) + { + printf("Memory Card %i, %s NO_ERR_SUCCESSfully deleted", cardID, filename); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory Card %i, %s not deleted as memory Card is unformatted", cardID, filename); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory Card %i, %s attempt made to delete non existing file", cardID, filename); + + nError = ERR_DELETENOENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + printf("Memory Card %i, %s not deleted as file is in use or write protected", cardID, filename); + + nError = ERR_DELETEDENIED; + return nError; + } + + if ( result == sceMcResNotEmpty ) + { + printf("Memory Card %i, %s not deleted. Entries remain in subdirectory", cardID, filename); + + nError = ERR_DELETEFAILED; + return nError; + } + + nError = ERR_NONE; + return nError; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID], filename); + int32 file = CFileMgr::OpenFile(path, "rb"); + if (file == 0) + { + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], filename); + file = CFileMgr::OpenFile(path, "rb"); + } + + if ( file ) + { + CFileMgr::CloseFile(file); + + DeleteFile(path); + + printf("Memory Card %i, %s NO_ERR_SUCCESSfully deleted", cardID, filename); + + nError = NO_ERR_SUCCESS; + return nError; + } + + printf("Memory Card %i, %s attempt made to delete non existing file", cardID, filename); + nError = ERR_DELETENOENTRY; + + //nError = ERR_NONE; + return nError; + +#endif +} + +void +CMemoryCard::PopulateErrorMessage() +{ + switch ( nError ) + { + case ERR_WRITEFULLDEVICE: + case ERR_DIRFULLDEVICE: + pErrorMsg = TheText.Get("SLONDR"); break; // Insufficient space to save. Please insert a Memory Card (PS2) with at least 500KB of free space available into MEMORY CARD slot 1. + case ERR_FORMATFAILED: + pErrorMsg = TheText.Get("SLONFM"); break; // Error formatting Memory Card (PS2) in MEMORY CARD slot 1. + case ERR_SAVEFAILED: + pErrorMsg = TheText.Get("SLNSP"); break; // Insufficient space to save. Please insert a Memory Card (PS2) with at least 200KB of free space available into MEMORY CARD slot 1. + case ERR_DELETEDENIED: + case ERR_NOFORMAT: + pErrorMsg = TheText.Get("SLONNF"); break; // Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + case ERR_NONE: + pErrorMsg = TheText.Get("SLONNO"); break; // No Memory Card (PS2) in MEMORY CARD slot 1. + } +} + +int32 +CMemoryCard::WritetoMemCard(int32 file, void *buff, int32 size) +{ +#if defined(PS2) + int cmd = sceMcFuncNoWrite; + int result = sceMcResSucceed; + int result1 = sceMcResSucceed; + + CTimer::Stop(); + + while ( sceMcWrite(file, buff, size) != sceMcResSucceed ) + ; + + sceMcSync(0, &cmd, &result); + + if ( result == sceMcResNoFormat ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResFullDevice ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_WRITEFULLDEVICE; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_WRITENOENTRY; + return nError; + } + + if ( result == sceMcResDeniedPermit ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_WRITEDENIED; + return nError; + } + + if ( result == sceMcResFailReplace ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_WRITEFAILED; + return nError; + } + + if ( result <= -10 ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_NONE; + return nError; + } + + cmd = sceMcFuncNoFlush; + + while ( sceMcFlush(file) != sceMcResSucceed ) + ; + + sceMcSync(0, &cmd, &result1); + + if ( result1 == sceMcResNoFormat ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result1 == sceMcResNoEntry ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_FLUSHNOENTRY; + return nError; + } + + if ( result1 <= -10 ) + { + printf("No Bytes written for Filehandle %i \n", file); + + nError = ERR_NONE; + return nError; + } + + if ( result > 0 && result1 == sceMcResSucceed ) + { + printf("%i Bytes written for Filehandle %i \n", result, file); + } + else if ( result == sceMcResSucceed && result1 == sceMcResSucceed ) + { + printf("Filehandle %i was flushed\n", file); + } + + nError = NO_ERR_SUCCESS; + return nError; +#else + CTimer::Stop(); + + int32 s = CFileMgr::Write(file, (const char *)buff, size); + if ( s == size ) + { + printf("%i Bytes written for Filehandle %i \n", s, file); + nError = NO_ERR_SUCCESS; + return s; + } + + nError = ERR_NONE; + return s; +#endif +} + +static inline void +MakeSpaceForSizeInBufferPointer(uint8 *&presize, uint8 *&buf, uint8 *&postsize) +{ + presize = buf; + buf += sizeof(uint32); + postsize = buf; +} + +static inline void +CopySizeAndPreparePointer(uint8 *&buf, uint8 *&postbuf, uint8 *&postbuf2, uint32 &unused, uint32 &size) +{ + memcpy(buf, &size, sizeof(size)); + size = align4bytes(size); + postbuf2 += size; + postbuf = postbuf2; +} + +bool +CMemoryCard::SaveGame(void) +{ + uint32 saveSize = 0; + uint32 totalSize = 0; + + CurrentCard = CARD_ONE; + + CheckSum = 0; + + CGameLogic::PassTime(360); + CPlayerPed *ped = FindPlayerPed(); + ped->m_fCurrentStamina = ped->m_fMaxStamina; + CGame::TidyUpMemory(true, false); + + saveSize = SAVE_FILE_SIZE; + int32 minfree = 198; + + PopulateCardFlags(CurrentCard, false, false, true, false); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + minfree += GetClusterAmountForFileCreation(CurrentCard); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( Cards[CurrentCard].free < 200 ) + { + CTimer::Update(); + uint32 startTime = CTimer::GetTimeInMillisecondsPauseMode(); + + while ( CTimer::GetTimeInMillisecondsPauseMode()-startTime < 1250 ) + { + for ( int32 i = 0; i < 1000; i++ ) + powf(3.33f, 3.444f); + + CTimer::Update(); + } + + nError = ERR_SAVEFAILED; + return false; + } + + if ( Cards[CurrentCard].free < minfree ) + { + CTimer::Update(); + uint32 startTime = CTimer::GetTimeInMillisecondsPauseMode(); + + while ( CTimer::GetTimeInMillisecondsPauseMode()-startTime < 1250 ) + { + for ( int32 i = 0; i < 1000; i++ ) + powf(3.33f, 3.444f); + + CTimer::Update(); + } + + nError = ERR_SAVEFAILED; + return false; + } + + uint32 size; + uint8 *buf = work_buff; + uint32 reserved = 0; + + int32 file = CreateMemCardFileReadWrite(CurrentCard, ValidSaveName); + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(SaveFileNameJustSaved) - 1); + return false; + } + + WriteDataToBufferPointer(buf, saveSize); + WriteDataToBufferPointer(buf, CGame::currLevel); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().x); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().y); + WriteDataToBufferPointer(buf, TheCamera.GetPosition().z); + WriteDataToBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute); + WriteDataToBufferPointer(buf, CClock::ms_nLastClockTick); + WriteDataToBufferPointer(buf, CClock::ms_nGameClockHours); + WriteDataToBufferPointer(buf, CClock::ms_nGameClockMinutes); + WriteDataToBufferPointer(buf, CPad::GetPad(0)->Mode); + WriteDataToBufferPointer(buf, CTimer::m_snTimeInMilliseconds); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeScale); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeStep); + WriteDataToBufferPointer(buf, CTimer::ms_fTimeStepNonClipped); + WriteDataToBufferPointer(buf, CTimer::m_FrameCounter); + WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeStep); + WriteDataToBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate); + WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeScale); + WriteDataToBufferPointer(buf, CWeather::OldWeatherType); + WriteDataToBufferPointer(buf, CWeather::NewWeatherType); + WriteDataToBufferPointer(buf, CWeather::ForcedWeatherType); + WriteDataToBufferPointer(buf, CWeather::InterpolationValue); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsMusicVolume); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsSfxVolume); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsControllerConfig); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsUseVibration); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsStereoMono); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsRadioStation); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsBrightness); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsShowTrails); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsShowSubtitles); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsLanguage); + WriteDataToBufferPointer(buf, CMenuManager::m_PrefsUseWideScreen); + WriteDataToBufferPointer(buf, CPad::GetPad(0)->Mode); +#ifdef PS2 + WriteDataToBufferPointer(buf, BlurOn); +#else + WriteDataToBufferPointer(buf, CMBlur::BlurOn); +#endif + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nSecond); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMinute); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nHour); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nDay); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMonth); + WriteDataToBufferPointer(buf, CompileDateAndTime.m_nYear); + WriteDataToBufferPointer(buf, CWeather::WeatherTypeInList); + WriteDataToBufferPointer(buf, TheCamera.CarZoomIndicator); + WriteDataToBufferPointer(buf, TheCamera.PedZoomIndicator); + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(SaveFileNameJustSaved) - 1); + return false; + } + + uint8 *presize; + uint8 *postsize; + + #define WriteSaveDataBlock(save_func)\ + do {\ + MakeSpaceForSizeInBufferPointer(presize, buf, postsize);\ + save_func(buf, &size);\ + CopySizeAndPreparePointer(presize, buf, postsize, reserved, size);\ + } while (0) + + WriteSaveDataBlock(CTheScripts::SaveAllScripts); + printf("Script Save Size %d, \n", size); + + WriteSaveDataBlock(CPools::SavePedPool); + printf("PedPool Save Size %d, \n", size); + + WriteSaveDataBlock(CGarages::Save); + printf("Garage Save Size %d, \n", size); + + WriteSaveDataBlock(CPools::SaveVehiclePool); + printf("Vehicle Save Size %d, \n", size); + + DoClassSaveRoutine(file, work_buff, buf - work_buff); + totalSize += buf - work_buff; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + buf = work_buff; + reserved = 0; + + WriteSaveDataBlock(CPools::SaveObjectPool); + printf("Object Save Size %d, \n", size); + + WriteSaveDataBlock(ThePaths.Save); + printf("The Paths Save Size %d, \n", size); + + WriteSaveDataBlock(CCranes::Save); + printf("Cranes Save Size %d, \n", size); + + DoClassSaveRoutine(file, work_buff, buf - work_buff); + totalSize += buf - work_buff; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + buf = work_buff; + reserved = 0; + + WriteSaveDataBlock(CPickups::Save); + printf("Pick Ups Save Size %d, \n", size); + + WriteSaveDataBlock(gPhoneInfo.Save); + printf("Phones Save Size %d, \n", size); + + WriteSaveDataBlock(CRestart::SaveAllRestartPoints); + printf("RestartPoints Save Size %d, \n", size); + + WriteSaveDataBlock(CRadar::SaveAllRadarBlips); + printf("Radar Save Size %d, \n", size); + + WriteSaveDataBlock(CTheZones::SaveAllZones); + printf("Save Size %d, \n", size); + + WriteSaveDataBlock(CGangs::SaveAllGangData); + printf("Gangs Save Size %d, \n", size); + + WriteSaveDataBlock(CTheCarGenerators::SaveAllCarGenerators); + printf("Car Gens Save Size %d, \n", size); + + WriteSaveDataBlock(CParticleObject::SaveParticle); + printf("Particles Save Size %d, \n", size); + + WriteSaveDataBlock(cAudioScriptObject::SaveAllAudioScriptObjects); + printf("Audio Script Save Size %d, \n", size); + + WriteSaveDataBlock(CWorld::Players[CWorld::PlayerInFocus].SavePlayerInfo); + printf("Player Info Save Size %d, \n", size); + + WriteSaveDataBlock(CStats::SaveStats); + printf("Stats Save Size %d, \n", size); + + WriteSaveDataBlock(CStreaming::MemoryCardSave); + printf("Streaming Save Size %d, \n", size); + + WriteSaveDataBlock(CPedType::Save); + printf("PedType Save Size %d, \n", size); + + DoClassSaveRoutine(file, work_buff, buf - work_buff); + totalSize += buf - work_buff; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + buf = work_buff; + reserved = 0; + + for (int32 i = 0; i < 3; i++) + { + size = align4bytes(saveSize - totalSize - 4); + if (size > sizeof(work_buff)) + size = sizeof(work_buff); + if (size > 4) { + DoClassSaveRoutine(file, work_buff, size); + totalSize += size; + } + } + + WritetoMemCard(file, &CheckSum, sizeof(CheckSum)); + + CloseMemCardFile(file); + + #undef WriteSaveDataBlock + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(SaveFileNameJustSaved) - 1); + DoHackRoundSTUPIDSonyDateTimeStuff(CARD_ONE, ValidSaveName); + return false; + } + + DoHackRoundSTUPIDSonyDateTimeStuff(CARD_ONE, ValidSaveName); + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(SaveFileNameJustSaved) - 1); + return true; +} + +bool +CMemoryCard::DoHackRoundSTUPIDSonyDateTimeStuff(int32 port, char *filename) +{ +#if defined(PS2) + int cmd = sceMcFuncNoFileInfo; + int result = sceMcResSucceed; + + sceCdCLOCK rtc; + sceCdReadClock(&rtc); + + sceScfGetLocalTimefromRTC(&rtc); + + #define ROUNDHACK(a) ( ((a) & 15) + ( ( ( ((a) >> 4) << 2 ) + ((a) >> 4) ) << 1 ) ) + + sceMcTblGetDir info; + + info._Create.Sec = ROUNDHACK(rtc.second); + info._Create.Min = ROUNDHACK(rtc.minute); + info._Create.Hour = ROUNDHACK(rtc.hour); + info._Create.Day = ROUNDHACK(rtc.day); + info._Create.Month = ROUNDHACK(rtc.month); + info._Create.Year = ROUNDHACK(rtc.year) + 2000; + + #undef ROUNDHACK + + while ( sceMcSetFileInfo(port, 0, filename, (char *)&info, sceMcFileInfoCreate) != sceMcResSucceed ) + ; + + sceMcSync(0, &cmd, &result); + + return sceMcResSucceed >= result; +#else + return true; +#endif +} + +int32 +CMemoryCard::LookForRootDirectory(int32 cardID) +{ + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoGetDir; + + while ( sceMcGetDir(Cards[cardID].port, 0, Cards[cardID].dir, 0, ARRAY_SIZE(Cards[cardID].table), Cards[cardID].table) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result == 0 ) + { + nError = NO_ERR_SUCCESS; + return ERR_NOROOTDIR; + } + + if ( result > sceMcResSucceed ) + { + printf("Memory card %i present PopulateFileTables function NO_ERR_SUCCESSfull \n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i PopulateFileTables function unNO_ERR_SUCCESSfull. MemoryCard not Formatted \n", cardID); + + nError = ERR_NOFORMAT; + return nError; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i PopulateFileTables function unNO_ERR_SUCCESSfull. Path does not exist \n", cardID); + + nError = ERR_FILETABLENOENTRY; + return nError; + } + + printf("Memory card %i not Present\n", cardID); + + nError = ERR_NONE; + return nError; +#else + char path[512]; + sprintf(path, "%s\\%s\\%s", PCCardsPath, PCCardDir[cardID], Cards[cardID].dir); + + memset(Cards[cardID].table, 0, sizeof(Cards[cardID].table)); + WIN32_FIND_DATA fd; HANDLE hFind; int32 num = 0; + if ( (hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE ) + { + nError = NO_ERR_SUCCESS; + return ERR_NOROOTDIR; + } + do + { + SYSTEMTIME st; + FileTimeToSystemTime(&fd.ftCreationTime, &st); + Cards[cardID].table[num]._Create.Sec = st.wSecond;Cards[cardID].table[num]._Create.Min = st.wMinute;Cards[cardID].table[num]._Create.Hour = st.wHour;Cards[cardID].table[num]._Create.Day = st.wDay;Cards[cardID].table[num]._Create.Month = st.wMonth;Cards[cardID].table[num]._Create.Year = st.wYear; + FileTimeToSystemTime(&fd.ftLastWriteTime, &st); + Cards[cardID].table[num]._Modify.Sec = st.wSecond;Cards[cardID].table[num]._Modify.Min = st.wMinute;Cards[cardID].table[num]._Modify.Hour = st.wHour;Cards[cardID].table[num]._Modify.Day = st.wDay;Cards[cardID].table[num]._Modify.Month = st.wMonth;Cards[cardID].table[num]._Modify.Year = st.wYear; + Cards[cardID].table[num].FileSizeByte = fd.nFileSizeLow; + strncpy((char *)Cards[cardID].table[num].EntryName, fd.cFileName, sizeof(Cards[cardID].table[num].EntryName) - 1); + num++; + } while( FindNextFile(hFind, &fd) && num < ARRAY_SIZE(Cards[cardID].table) ); + FindClose(hFind); + + if ( num == 0 ) + { + nError = NO_ERR_SUCCESS; + return ERR_NOROOTDIR; + } + + //todo errors + + printf("Memory card %i present PopulateFileTables function NO_ERR_SUCCESSfull \n", cardID); + + nError = NO_ERR_SUCCESS; + return nError; +#endif +} + +int32 +CMemoryCard::FillFirstFileWithGuff(int32 cardID) +{ + CTimer::Stop(); + + char buff[80]; + strncpy(buff, Cards[cardID].dir+1, sizeof(buff) - 1); + + int32 file = CreateMemCardFileReadWrite(Cards[cardID].port, buff); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + const int32 kBlockSize = GUFF_FILE_SIZE / 3; + + work_buff[kBlockSize-1] = 5; + WritetoMemCard(file, work_buff, kBlockSize); + WritetoMemCard(file, work_buff, kBlockSize); + WritetoMemCard(file, work_buff, kBlockSize); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + CloseMemCardFile(file); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + return RES_SUCCESS; +} + +bool +CMemoryCard::FindMostRecentFileName(int32 cardID, char *filename) +{ + CDate date1, date2; + + CTimer::Stop(); + +#if defined(PS2) + int cmd = sceMcFuncNoGetDir; + + ClearFileTableBuffer(cardID); + + while ( sceMcGetDir(Cards[cardID].port, 0, "*", 0, ARRAY_SIZE(Cards[cardID].table), Cards[cardID].table) != sceMcResSucceed ) + ; + + int result; + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + printf("Memory card %i present PopulateFileTables function NO_ERR_SUCCESSfull \n", cardID); + nError = NO_ERR_SUCCESS; + + for ( int32 entry = 7; entry < ARRAY_SIZE(Cards[CARD_ONE].table); entry++ ) + { + bool found = false; + + if ( Cards[CARD_ONE].table[entry]._Modify.Sec != 0 + || Cards[CARD_ONE].table[entry]._Modify.Min != 0 + || Cards[CARD_ONE].table[entry]._Modify.Hour != 0 + || Cards[CARD_ONE].table[entry]._Modify.Day != 0 + || Cards[CARD_ONE].table[entry]._Modify.Month != 0 + || Cards[CARD_ONE].table[entry]._Modify.Year != 0 ) + { + date1.m_nSecond = Cards[CARD_ONE].table[entry]._Modify.Sec; + date1.m_nMinute = Cards[CARD_ONE].table[entry]._Modify.Min; + date1.m_nHour = Cards[CARD_ONE].table[entry]._Modify.Hour; + date1.m_nDay = Cards[CARD_ONE].table[entry]._Modify.Day; + date1.m_nMonth = Cards[CARD_ONE].table[entry]._Modify.Month; + date1.m_nYear = Cards[CARD_ONE].table[entry]._Modify.Year; + + if ( Cards[CARD_ONE].table[entry].FileSizeByte != 0 + && Cards[CARD_ONE].table[entry].AttrFile & sceMcFileAttrClosed + && Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + found = true; + } + } + else + if ( Cards[CARD_ONE].table[entry]._Create.Sec != 0 + || Cards[CARD_ONE].table[entry]._Create.Min != 0 + || Cards[CARD_ONE].table[entry]._Create.Hour != 0 + || Cards[CARD_ONE].table[entry]._Create.Day != 0 + || Cards[CARD_ONE].table[entry]._Create.Month != 0 + || Cards[CARD_ONE].table[entry]._Create.Year != 0 ) + { + date1.m_nSecond = Cards[CARD_ONE].table[entry]._Create.Sec; + date1.m_nMinute = Cards[CARD_ONE].table[entry]._Create.Min; + date1.m_nHour = Cards[CARD_ONE].table[entry]._Create.Hour; + date1.m_nDay = Cards[CARD_ONE].table[entry]._Create.Day; + date1.m_nMonth = Cards[CARD_ONE].table[entry]._Create.Month; + date1.m_nYear = Cards[CARD_ONE].table[entry]._Create.Year; + + if ( Cards[CARD_ONE].table[entry].FileSizeByte != 0 + && Cards[CARD_ONE].table[entry].AttrFile & sceMcFileAttrClosed + && Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + found = true; + } + } + + if ( found ) + { + int32 d; + if ( date1 > date2 ) d = 1; + else if ( date1 < date2 ) d = 2; + else d = 0; + + if ( d == 1 ) + { + char *entryname = (char *)Cards[CARD_ONE].table[entry].EntryName; + + date2 = date1; + strncpy(filename, entryname, 28); + } + else + { + int32 d; + if ( date1 > date2 ) d = 1; + else if ( date1 < date2 ) d = 2; + else d = 0; + + if ( d == 0 ) + { + char *entryname = (char *)Cards[CARD_ONE].table[entry].EntryName; + date2 = date1; + strncpy(filename, entryname, 28); + } + } + } + } + + if ( date2.m_nSecond != 0 + || date2.m_nMinute != 0 + || date2.m_nHour != 0 + || date2.m_nDay != 0 + || date2.m_nMonth != 0 + || date2.m_nYear != 0 ) + { + return true; + } + + return false; + } + + if ( result == sceMcResNoFormat ) + { + printf("Memory card %i PopulateFileTables function unNO_ERR_SUCCESSfull. MemoryCard not Formatted \n", cardID); + nError = ERR_NOFORMAT; + return false; + } + + if ( result == sceMcResNoEntry ) + { + printf("Memory card %i PopulateFileTables function unNO_ERR_SUCCESSfull. Path does not exist \n", cardID); + nError = ERR_FILETABLENOENTRY; + return false; + } + + printf("Memory card %i not Present\n", cardID); + nError = ERR_NONE; + return false; +#else + ClearFileTableBuffer(cardID); + + char path[512]; + sprintf(path, "%s\\%s\\%s\\*", PCCardsPath, PCCardDir[cardID], CardCurDir[cardID]); + + memset(Cards[cardID].table, 0, sizeof(Cards[cardID].table)); + WIN32_FIND_DATA fd; HANDLE hFind; int32 num = 0; + if ( (hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE ) + { + printf("Memory card %i not Present\n", cardID); + nError = ERR_NONE; + return nError; + } + do + { + SYSTEMTIME st; + FileTimeToSystemTime(&fd.ftCreationTime, &st); + Cards[cardID].table[num]._Create.Sec = st.wSecond;Cards[cardID].table[num]._Create.Min = st.wMinute;Cards[cardID].table[num]._Create.Hour = st.wHour;Cards[cardID].table[num]._Create.Day = st.wDay;Cards[cardID].table[num]._Create.Month = st.wMonth;Cards[cardID].table[num]._Create.Year = st.wYear; + FileTimeToSystemTime(&fd.ftLastWriteTime, &st); + Cards[cardID].table[num]._Modify.Sec = st.wSecond;Cards[cardID].table[num]._Modify.Min = st.wMinute;Cards[cardID].table[num]._Modify.Hour = st.wHour;Cards[cardID].table[num]._Modify.Day = st.wDay;Cards[cardID].table[num]._Modify.Month = st.wMonth;Cards[cardID].table[num]._Modify.Year = st.wYear; + Cards[cardID].table[num].FileSizeByte = fd.nFileSizeLow; + strncpy((char *)Cards[cardID].table[num].EntryName, fd.cFileName, sizeof(Cards[cardID].table[num].EntryName) - 1); + num++; + } while( FindNextFile(hFind, &fd) && num < ARRAY_SIZE(Cards[cardID].table) ); + FindClose(hFind); + + if ( num > 0 ) + { + printf("Memory card %i present PopulateFileTables function NO_ERR_SUCCESSfull \n", cardID); + nError = NO_ERR_SUCCESS; + + for ( int32 entry = 0; entry < ARRAY_SIZE(Cards[CARD_ONE].table); entry++ ) + { + bool found = false; + + if ( Cards[CARD_ONE].table[entry]._Modify.Sec != 0 + || Cards[CARD_ONE].table[entry]._Modify.Min != 0 + || Cards[CARD_ONE].table[entry]._Modify.Hour != 0 + || Cards[CARD_ONE].table[entry]._Modify.Day != 0 + || Cards[CARD_ONE].table[entry]._Modify.Month != 0 + || Cards[CARD_ONE].table[entry]._Modify.Year != 0 ) + { + date1.m_nSecond = Cards[CARD_ONE].table[entry]._Modify.Sec; + date1.m_nMinute = Cards[CARD_ONE].table[entry]._Modify.Min; + date1.m_nHour = Cards[CARD_ONE].table[entry]._Modify.Hour; + date1.m_nDay = Cards[CARD_ONE].table[entry]._Modify.Day; + date1.m_nMonth = Cards[CARD_ONE].table[entry]._Modify.Month; + date1.m_nYear = Cards[CARD_ONE].table[entry]._Modify.Year; + + if ( Cards[CARD_ONE].table[entry].FileSizeByte != 0 + && Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + found = true; + } + } + else + if ( Cards[CARD_ONE].table[entry]._Create.Sec != 0 + || Cards[CARD_ONE].table[entry]._Create.Min != 0 + || Cards[CARD_ONE].table[entry]._Create.Hour != 0 + || Cards[CARD_ONE].table[entry]._Create.Day != 0 + || Cards[CARD_ONE].table[entry]._Create.Month != 0 + || Cards[CARD_ONE].table[entry]._Create.Year != 0 ) + { + date1.m_nSecond = Cards[CARD_ONE].table[entry]._Create.Sec; + date1.m_nMinute = Cards[CARD_ONE].table[entry]._Create.Min; + date1.m_nHour = Cards[CARD_ONE].table[entry]._Create.Hour; + date1.m_nDay = Cards[CARD_ONE].table[entry]._Create.Day; + date1.m_nMonth = Cards[CARD_ONE].table[entry]._Create.Month; + date1.m_nYear = Cards[CARD_ONE].table[entry]._Create.Year; + + if ( Cards[CARD_ONE].table[entry].FileSizeByte != 0 + && Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + found = true; + } + } + + if ( found ) + { + int32 d; + if ( date1 > date2 ) d = 1; + else if ( date1 < date2 ) d = 2; + else d = 0; + + if ( d == 1 ) + { + char *entryname = (char *)Cards[CARD_ONE].table[entry].EntryName; + + date2 = date1; + strncpy(filename, entryname, 28); + } + else + { + int32 d; + if ( date1 > date2 ) d = 1; + else if ( date1 < date2 ) d = 2; + else d = 0; + + if ( d == 0 ) + { + char *entryname = (char *)Cards[CARD_ONE].table[entry].EntryName; + date2 = date1; + strncpy(filename, entryname, 28); + } + } + } + } + + if ( date2.m_nSecond != 0 + || date2.m_nMinute != 0 + || date2.m_nHour != 0 + || date2.m_nDay != 0 + || date2.m_nMonth != 0 + || date2.m_nYear != 0 ) + { + return true; + } + + return false; + } + + //todo errors + + nError = ERR_NONE; + return false; +#endif +} + +void +CMemoryCard::ClearFileTableBuffer(int32 cardID) +{ + for ( int32 i = 0; i < ARRAY_SIZE(Cards[cardID].table); i++ ) + { + Cards[cardID].table[i].FileSizeByte = 0; + strncpy((char *)Cards[cardID].table[i].EntryName, " ", sizeof(Cards[cardID].table[i].EntryName) - 1); + } +} + +int32 +CMemoryCard::GetClusterAmountForFileCreation(int32 port) +{ +#if defined(PS2) + int cmd = sceMcFuncNoEntSpace; + int result = 0; + + CTimer::Stop(); + + while ( sceMcGetEntSpace(port, 0, TheGameRootDirectory) != sceMcResSucceed ) + ; + + sceMcSync(0, &cmd, &result); + + if ( result >= sceMcResSucceed ) + { + nError = NO_ERR_SUCCESS; + return result; + } + + if ( result == sceMcResNoFormat ) + { + nError = ERR_NOFORMAT; + return nError; + } + + nError = ERR_NONE; + return nError; +#else + CTimer::Stop(); + nError = NO_ERR_SUCCESS; + return 0; +#endif +} + +bool +CMemoryCard::DeleteEverythingInGameRoot(int32 cardID) +{ + CTimer::Stop(); + + ChangeDirectory(CurrentCard, Cards[CurrentCard].dir); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + PopulateFileTable(cardID); + + for ( int32 i = ARRAY_SIZE(Cards[cardID].table) - 1; i >= 0; i--) + DeleteMemoryCardFile(cardID, (char *)Cards[cardID].table[i].EntryName); + + ChangeDirectory(CurrentCard, "/"); + + DeleteMemoryCardFile(cardID, Cards[CurrentCard].dir); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + return true; +} + +int32 +CMemoryCard::CheckDataNotCorrupt(char *filename) +{ + CheckSum = 0; + + int32 lang = 0; + int32 level = 0; + + LastBlockSize = 0; + + char buf[100*4]; + + for ( int32 i = 0; i < sizeof(buf); i++ ) + buf[i] = '\0'; + + strncpy(buf, Cards[CurrentCard].dir, sizeof(buf) - 1); + strncat(buf, "/", sizeof(buf) - 1); + strcat (buf, filename); + + ChangeDirectory(CurrentCard, Cards[CurrentCard].dir); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + int32 file = OpenMemCardFileForReading(CurrentCard, buf); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + int32 bytes_processed = 0; + int32 blocknum = 0; + int32 lastblocksize; + + while ( SAVE_FILE_SIZE - sizeof(int32) > bytes_processed && blocknum < 8 ) + { + int32 size; + + ReadFromMemCard(file, &size, sizeof(size)); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + lastblocksize = ReadFromMemCard(file, work_buff, align4bytes(size)); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + uint8 sizebuff[4]; + memcpy(sizebuff, &size, sizeof(size)); + + for ( int32 i = 0; i < ARRAY_SIZE(sizebuff); i++ ) + CheckSum += sizebuff[i]; + + uint8 *pWork_buf = work_buff; + for ( int32 i = 0; i < lastblocksize; i++ ) + { + CheckSum += *pWork_buf++; + bytes_processed++; + } + + if ( blocknum == 0 ) + { + uint8 *pBuf = work_buff + sizeof(uint32); + ReadDataFromBufferPointer(pBuf, level); + pBuf += sizeof(uint32) * 29; + ReadDataFromBufferPointer(pBuf, lang); + } + + blocknum++; + } + + int32 checkSum; + ReadFromMemCard(file, &checkSum, sizeof(checkSum)); + CloseMemCardFile(file); + + if ( nError != NO_ERR_SUCCESS ) + return RES_FAILED; + + if ( CheckSum == checkSum ) + { + m_LevelToLoad = level; + m_LanguageToLoad = lang; + LastBlockSize = lastblocksize; + + return RES_SUCCESS; + } + + nError = ERR_DATACORRUPTED; + return RES_FAILED; +} + +int32 +CMemoryCard::GetLanguageToLoad(void) +{ + return m_LanguageToLoad; +} + +int32 +CMemoryCard::GetLevelToLoad(void) +{ + return m_LevelToLoad; +} + +bool +CMemoryCard::CreateGameDirectoryFromScratch(int32 cardID) +{ + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + + int32 err = RES_SUCCESS; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( Cards[CurrentCard].free < 500 ) + { + CTimer::Update(); + uint32 startTime = CTimer::GetTimeInMillisecondsPauseMode(); + + while ( CTimer::GetTimeInMillisecondsPauseMode()-startTime < 1250 ) + { + for ( int32 i = 0; i < 1000; i++ ) + powf(3.33f, 3.444f); + + CTimer::Update(); + } + + nError = ERR_DIRFULLDEVICE; + return false; + } + + TheMemoryCard.ChangeDirectory(CARD_ONE, "/"); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + int32 r = LookForRootDirectory(CARD_ONE); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( r == ERR_NOROOTDIR ) + { + CreateRootDirectory(CARD_ONE); + + if ( nError != NO_ERR_SUCCESS ) + DeleteEverythingInGameRoot(CARD_ONE); + } + + ChangeDirectory(CARD_ONE, Cards[CARD_ONE].dir); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + PopulateFileTable(CARD_ONE); + + if ( nError != NO_ERR_SUCCESS ) + return false; + +#if defined(PS2) + bool entryExist; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(CARD_ONE) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[CARD_ONE].table); i++ ) + { + if ( !strcmp("icon.sys", (char *)TheMemoryCard.Cards[CARD_ONE].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + err = RES_FAILED; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(CARD_ONE) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[CARD_ONE].table); i++ ) + { + if ( !strcmp(icon_one, (char *)TheMemoryCard.Cards[CARD_ONE].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + err = RES_FAILED; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(CARD_ONE) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[CARD_ONE].table); i++ ) + { + if ( !strcmp(icon_two, (char *)TheMemoryCard.Cards[CARD_ONE].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + err = RES_FAILED; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(CARD_ONE) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[CARD_ONE].table); i++ ) + { + if ( !strcmp(icon_three, (char *)TheMemoryCard.Cards[CARD_ONE].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + err = RES_FAILED; + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( err != RES_SUCCESS ) + { + int32 icon = CreateIconFiles(CARD_ONE, icon_one, icon_two, icon_three); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( icon != RES_SUCCESS ) + DeleteEverythingInGameRoot(CARD_ONE); + } +#endif + + int32 guff = FillFirstFileWithGuff(CARD_ONE); + + if ( nError != NO_ERR_SUCCESS ) + return false; + + if ( guff == RES_SUCCESS ) + { + printf("Game Default directory present"); + return true; + } + + DeleteEverythingInGameRoot(CARD_ONE); + + return false; +} + +bool +CMemoryCard::CheckGameDirectoryThere(int32 cardID) +{ + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + + if ( TheMemoryCard.nError != NO_ERR_SUCCESS ) + return false; + + TheMemoryCard.ChangeDirectory(cardID, Cards[CARD_ONE].dir); + + if ( TheMemoryCard.nError != NO_ERR_SUCCESS ) + return false; + + PopulateFileTable(cardID); + + if ( TheMemoryCard.nError != NO_ERR_SUCCESS ) + return false; + + + bool entryExist; + +#if defined(PS2) + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp("icon.sys", (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp(icon_one, (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp(icon_two, (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp(icon_three, (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; +#endif + + char buff[80]; + + strncpy(buff, Cards[cardID].dir+1, sizeof(buff) - 1); + + + entryExist = false; + if ( TheMemoryCard.PopulateFileTable(cardID) == NO_ERR_SUCCESS ) + { + for ( int32 i = 0; i < ARRAY_SIZE(TheMemoryCard.Cards[cardID].table); i++ ) + { + if ( !strcmp(buff, (char *)TheMemoryCard.Cards[cardID].table[i].EntryName) ) + { + entryExist = true; + break; + } + } + } + + if ( !entryExist ) + return false; + + printf("Game directory present"); + + return true; +} + +void +CMemoryCard::PopulateSlotInfo(int32 cardID) +{ + CTimer::Stop(); + + for ( int32 i = 0; i < MAX_SLOTS; i++ ) + { + Slots[i] = SLOT_NOTPRESENT; + + for ( int32 j = 0; j < ARRAY_SIZE(SlotFileName[i]); j++ ) + SlotFileName[i][j] = L'\0'; + + for ( int32 j = 0; j < ARRAY_SIZE(SlotSaveDate[i]); j++ ) + SlotSaveDate[i][j] = L'\0'; + + UnicodeStrcpy(SlotSaveDate[i], TheText.Get("DEFDT")); + } + + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + + if ( nError != NO_ERR_SUCCESS ) + return; + + TheMemoryCard.ChangeDirectory(cardID, TheMemoryCard.Cards[CARD_ONE].dir); + + if ( nError != NO_ERR_SUCCESS && nError != ERR_DIRNOENTRY ) + return; + + PopulateFileTable(cardID); + + if ( nError != NO_ERR_SUCCESS && nError != ERR_FILETABLENOENTRY ) + return; + + for ( int32 slot = 0; slot < MAX_SLOTS; slot++ ) + { +#if defined(PS2) + for ( int32 entry = 7; entry < ARRAY_SIZE(Cards[cardID].table); entry++ ) +#else + for ( int32 entry = 0; entry < ARRAY_SIZE(Cards[cardID].table); entry++ ) +#endif + { + if ( TheMemoryCard.Cards[CARD_ONE].table[entry].FileSizeByte != 0 ) + { + char slotnum[30]; + char slotname[30]; + char slotdate[30]; + + if ( +#if defined(PS2) + TheMemoryCard.Cards[CARD_ONE].table[entry].AttrFile & sceMcFileAttrClosed && +#endif + TheMemoryCard.Cards[CARD_ONE].table[entry].FileSizeByte >= SAVE_FILE_SIZE ) + { + char *entryname = (char *)Cards[cardID].table[entry].EntryName; + + bool bFound = false; +#if defined(PS2) + for ( int32 i = 7; i < ARRAY_SIZE(Cards[cardID].table) && !bFound; i++ ) +#else + for ( int32 i = 0; i < ARRAY_SIZE(Cards[cardID].table) && !bFound; i++ ) +#endif + { + sprintf(slotnum, "%i ", slot+1); + + for ( int32 j = 0; j < sizeof(slotname); j++ ) + slotname[j] = '\0'; + + strncat(slotname, slotnum, sizeof(slotnum)-1); + + if ( !strncmp(slotname, entryname, 1) ) + { + bFound = true; + + Slots[slot] = SLOT_PRESENT; + AsciiToUnicode(entryname, SlotFileName[slot]); + + int32 sec = Cards[CARD_ONE].table[entry]._Create.Sec; + int32 month = Cards[CARD_ONE].table[entry]._Create.Month; + int32 year = Cards[CARD_ONE].table[entry]._Create.Year; + int32 min = Cards[CARD_ONE].table[entry]._Create.Min; + int32 hour = Cards[CARD_ONE].table[entry]._Create.Hour; + int32 day = Cards[CARD_ONE].table[entry]._Create.Day; + + for ( int32 j = 0; j < ARRAY_SIZE(SlotSaveDate[slot]); j++ ) + SlotSaveDate[slot][j] = L'\0'; + + for ( int32 j = 0; j < ARRAY_SIZE(slotdate); j++ ) + slotdate[j] = '\0'; + + char *monthstr; + switch ( month ) + { + case 1: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("JAN")); break; + case 2: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("FEB")); break; + case 3: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("MAR")); break; + case 4: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("APR")); break; + case 5: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("MAY")); break; + case 6: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("JUN")); break; + case 7: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("JUL")); break; + case 8: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("AUG")); break; + case 9: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("SEP")); break; + case 10: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("OCT")); break; + case 11: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("NOV")); break; + case 12: monthstr = UnicodeToAsciiForMemoryCard(TheText.Get("DEC")); break; + } + + sprintf(slotdate, "%02d %s %04d %02d:%02d:%02d", day, monthstr, year, hour, min, sec); + AsciiToUnicode(slotdate, SlotSaveDate[slot]); + } + } + } + else + { + char *entryname = (char *)Cards[cardID].table[entry].EntryName; + + bool bFound = false; +#if defined(PS2) + for ( int32 i = 7; i < ARRAY_SIZE(Cards[cardID].table) && !bFound; i++ ) // again ... +#else + for ( int32 i = 0; i < ARRAY_SIZE(Cards[cardID].table) && !bFound; i++ ) // again ... +#endif + { + sprintf(slotnum, "%i ", slot+1); + + for ( int32 j = 0; j < sizeof(slotname); j++ ) + slotname[j] = '\0'; + + strncat(slotname, slotnum, sizeof(slotnum)-1); + + if ( !strncmp(slotname, entryname, 1) ) + { + bFound = true; + + Slots[slot] = SLOT_CORRUPTED; + AsciiToUnicode(entryname, SlotFileName[slot]); + } + } + } + } + } + } + + nError = NO_ERR_SUCCESS; + return; +} + +int32 +CMemoryCard::GetInfoOnSpecificSlot(int32 slotID) +{ + return Slots[slotID]; +} + +wchar * +CMemoryCard::GetDateAndTimeOfSavedGame(int32 slotID) +{ + return SlotSaveDate[slotID]; +} + +int32 +CMemoryCard::CheckCardStateAtGameStartUp(int32 cardID) +{ + CheckCardInserted(cardID); + if ( nError == ERR_NOFORMAT ) + return MCSTATE_OK; + if ( nError == ERR_NONE ) + return MCSTATE_NOCARD; + + if ( !CheckGameDirectoryThere(cardID) ) + { + if ( nError == ERR_NONE ) + return MCSTATE_NOCARD; + + DeleteEverythingInGameRoot(cardID); + if ( nError == ERR_NONE ) + return MCSTATE_NOCARD; + + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + if ( nError == ERR_NONE ) + return MCSTATE_NOCARD; + + if ( Cards[CurrentCard].free < 500 ) + return MCSTATE_NEED_500KB; + + return MCSTATE_OK; + } + + TheMemoryCard.CheckCardInserted(CARD_ONE); + + if ( nError == NO_ERR_SUCCESS ) + { + if ( TheMemoryCard.ChangeDirectory(CARD_ONE, Cards[CARD_ONE].dir) != ERR_NONE ) + { + if ( TheMemoryCard.FindMostRecentFileName(CARD_ONE, MostRecentFile) == true ) + { + if ( TheMemoryCard.CheckDataNotCorrupt(MostRecentFile) == RES_FAILED ) + { + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + if ( Cards[CurrentCard].free < 200 ) + return MCSTATE_NEED_200KB; + } + } + else + { + TheMemoryCard.PopulateCardFlags(cardID, false, false, true, true); + if ( Cards[CurrentCard].free < 200 ) + return MCSTATE_NEED_200KB; + } + } + } + + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) != NO_ERR_SUCCESS ) + return MCSTATE_NOCARD; + + return MCSTATE_OK; +} + +void +CMemoryCard::SaveSlot(int32 slotID) +{ + bool bSave = true; + + for ( int32 j = 0; j < sizeof(ValidSaveName); j++ ) + ValidSaveName[j] = '\0'; + + char buff[100]; + + sprintf(buff, "%i ", slotID+1); + strncat(ValidSaveName, buff, sizeof(ValidSaveName) - 1); + + if ( CStats::LastMissionPassedName[0] != '\0' ) + { + char mission[100]; + + strcpy(mission, UnicodeToAsciiForMemoryCard(TheText.Get(CStats::LastMissionPassedName))); + +#ifdef FIX_BUGS + strncat(ValidSaveName, mission, sizeof(ValidSaveName)-1); +#else + strncat(ValidSaveName, mission, 21); + strncat(ValidSaveName, "...", strlen("...")); +#endif + } + + if ( !CheckGameDirectoryThere(CARD_ONE) ) + { + DeleteEverythingInGameRoot(CARD_ONE); + bSave = CreateGameDirectoryFromScratch(CARD_ONE); + } + + if ( bSave ) + { + if ( Slots[slotID] == SLOT_PRESENT ) + { + TheMemoryCard.ChangeDirectory(CARD_ONE, Cards[CurrentCard].dir); + if ( nError == NO_ERR_SUCCESS ) + TheMemoryCard.DeleteMemoryCardFile(CARD_ONE, UnicodeToAsciiForMemoryCard(SlotFileName[slotID])); + } + + SaveGame(); + } + + CTimer::Stop(); + CStreaming::FlushRequestList(); + CStreaming::DeleteRwObjectsAfterDeath(FindPlayerPed()->GetPosition()); + CStreaming::RemoveUnusedModelsInLoadedList(); + CGame::DrasticTidyUpMemory(false); + CTimer::Update(); +} + +void +CMemoryCard::DeleteSlot(int32 slotID) +{ + TheMemoryCard.ChangeDirectory(CARD_ONE, Cards[CurrentCard].dir); + + if ( nError == NO_ERR_SUCCESS ) + TheMemoryCard.DeleteMemoryCardFile(CARD_ONE, UnicodeToAsciiForMemoryCard(SlotFileName[slotID])); +} + +void +CMemoryCard::LoadSlotToBuffer(int32 slotID) +{ + CStreaming::DeleteAllRwObjects(); + + strcpy(LoadFileName, UnicodeToAsciiForMemoryCard(SlotFileName[slotID])); + + TheMemoryCard.ChangeDirectory(CARD_ONE, Cards[CurrentCard].dir); + + if ( nError == NO_ERR_SUCCESS ) + TheMemoryCard.CheckDataNotCorrupt(LoadFileName); +} + +wchar * +CMemoryCard::GetNameOfSavedGame(int32 slotID) +{ + return SlotFileName[slotID]; +} + +int32 +CMemoryCard::DoClassSaveRoutine(int32 file, uint8 *data, uint32 size) +{ + WritetoMemCard(file, &size, sizeof(size)); + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return ERR_NONE; + } + + WritetoMemCard(file, data, align4bytes(size)); + + uint8 sizebuff[4]; + memcpy(sizebuff, &size, sizeof(size)); + + for ( int32 i = 0; i < ARRAY_SIZE(sizebuff); i++ ) + CheckSum += sizebuff[i]; + + for ( int32 i = 0; i < align4bytes(size); i++ ) + CheckSum += *data++; + + if ( nError != NO_ERR_SUCCESS ) + { + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return ERR_NONE; + } + + return nError; +} + +#endif diff --git a/src/save/MemoryCard.h b/src/save/MemoryCard.h new file mode 100644 index 0000000..bae605f --- /dev/null +++ b/src/save/MemoryCard.h @@ -0,0 +1,197 @@ +#pragma once +#include "common.h" +#ifdef PS2_MENU +#include "Date.h" + +#if defined(PS2) +#include +#include +#include +#endif + +enum +{ + CARD_ONE = 0, + CARD_TWO, + MAX_CARDS, +}; + +class CMemoryCardInfo +{ +public: + int port; + int slot; + int type; + int free; + int format; + char dir[40]; +#if defined(PS2) + sceMcTblGetDir table[15]; +#else + struct + { + typedef struct {unsigned char Sec,Min,Hour; unsigned char Day,Month; unsigned short Year;} _time; + _time _Create; + _time _Modify; + unsigned int FileSizeByte; + unsigned short AttrFile; + unsigned char EntryName[32]; + }table[15]; +#endif + CMemoryCardInfo(void); +}; + + +#define GUFF_FILE_SIZE 147096 +#define SAVE_FILE_SIZE 201729 + +class CMemoryCard +{ +public: + enum + { + MAX_SLOTS = 8, + }; + + enum MCSTATE + { + MCSTATE_OK = 0, + MCSTATE_NEED_500KB, + MCSTATE_NEED_200KB, + MCSTATE_NOCARD, + }; + + enum SLOTINFO + { + SLOT_PRESENT = 0, + SLOT_NOTPRESENT, + SLOT_CORRUPTED, + }; + + int _unk0; + int _unk1; + bool m_bWantToLoad; + bool JustLoadedDontFadeInYet; + bool StillToFadeOut; + bool b_FoundRecentSavedGameWantToLoad; + uint32 TimeStartedCountingForFade; + uint32 TimeToStayFadedBeforeFadeOut; + uint32 LastBlockSize; + bool _bunk2; + char ValidSaveName [30]; + char MostRecentFile [30]; + char _unkName3 [30]; + char SaveFileNameJustSaved[30]; + char _pad0[3]; + wchar *pErrorMsg; + char _unk4[32]; + bool _bunk5; + bool _bunk6; + bool _bunk7; + bool _bunk8; + int nError; + wchar _unk9[30]; + char LoadFileName[30]; + char _pad1[2]; + CDate CompileDateAndTime; + int m_LanguageToLoad; + int m_LevelToLoad; + int CurrentCard; + CMemoryCardInfo Cards [MAX_CARDS]; + int Slots [MAX_SLOTS]; + wchar SlotFileName[MAX_SLOTS][30]; + wchar SlotSaveDate[MAX_SLOTS][30]; + char _unk10[32]; + + enum + { + ERR_NONE = 0, + ERR_NOFORMAT = 1, + ERR_DIRNOENTRY = 2, + ERR_OPENNOENTRY = 3, + ERR_DELETENOENTRY = 4, + ERR_DELETEDENIED = 5, + ERR_DELETEFAILED = 6, + ERR_WRITEFULLDEVICE = 7, + ERR_WRITENOENTRY = 8, + ERR_WRITEDENIED = 9, + ERR_FLUSHNOENTRY, + ERR_WRITEFAILED, + ERR_FORMATFAILED = 12, + ERR_FILETABLENOENTRY = 13, + ERR_DIRFULLDEVICE = 14, + ERR_DIRBADENTRY = 15, + ERR_FILEFULLDEVICE = 16, + ERR_FILENOPATHENTRY = 17, + ERR_FILEDENIED = 18, + ERR_FILEUPLIMIT = 19, + ERR_READNOENTRY = 20, + ERR_READDENIED = 21, + ERR_LOADFAILED = 22, // unused + ERR_SAVEFAILED = 23, + ERR_DATACORRUPTED = 24, + ERR_NOROOTDIR = 25, + NO_ERR_SUCCESS = 26, + }; + + enum + { + RES_SUCCESS = 1, + RES_FAILED = -1, + }; + + int32 GetError() + { + return nError; + } + + wchar *GetErrorMessage() + { + return pErrorMsg; + } + + int32 Init(void); + CMemoryCard(void); + int32 RestoreForStartLoad(void); + int32 LoadSavedGame(void); + int32 CheckCardInserted(int32 cardID); + int32 PopulateCardFlags(int32 cardID, bool bSlotFlag, bool bTypeFlag, bool bFreeFlag, bool bFormatFlag); + int32 FormatCard(int32 cardID); + int32 PopulateFileTable(int32 cardID); + int32 CreateRootDirectory(int32 cardID); + int32 ChangeDirectory(int32 cardID, char *dir); + int32 CreateIconFiles(int32 cardID, char *icon_one, char *icon_two, char *icon_three); + int32 LoadIconFiles(int32 cardID, char *icon_one, char *icon_two, char *icon_three); + int32 CloseMemCardFile(int32 file); + int32 CreateMemCardFileReadWrite(int32 cardID, char *filename); + int32 OpenMemCardFileForReading(int32 cardID, char *filename); + int32 ReadFromMemCard(int32 file, void *buff, int32 size); + int32 DeleteMemoryCardFile(int32 cardID, char *filename); + void PopulateErrorMessage(); + int32 WritetoMemCard(int32 file, void *buff, int32 size); + bool SaveGame(void); + bool DoHackRoundSTUPIDSonyDateTimeStuff(int32 port, char *filename); + int32 LookForRootDirectory(int32 cardID); + int32 FillFirstFileWithGuff(int32 cardID); + bool FindMostRecentFileName(int32 cardID, char *filename); + void ClearFileTableBuffer(int32 cardID); + int32 GetClusterAmountForFileCreation(int32 port); + bool DeleteEverythingInGameRoot(int32 cardID); + int32 CheckDataNotCorrupt(char *filename); + int32 GetLanguageToLoad(void); + int32 GetLevelToLoad(void); + bool CreateGameDirectoryFromScratch(int32 cardID); + bool CheckGameDirectoryThere(int32 cardID); + void PopulateSlotInfo(int32 cardID); + int32 GetInfoOnSpecificSlot(int32 slotID); + wchar *GetDateAndTimeOfSavedGame(int32 slotID); + int32 CheckCardStateAtGameStartUp(int32 cardID); + void SaveSlot(int32 slotID); + void DeleteSlot(int32 slotID); + void LoadSlotToBuffer(int32 slotID); + wchar *GetNameOfSavedGame(int32 slotID); + int32 DoClassSaveRoutine(int32 file, uint8 *data, uint32 size); +}; + +extern CMemoryCard TheMemoryCard; +#endif \ No newline at end of file diff --git a/src/save/PCSave.cpp b/src/save/PCSave.cpp new file mode 100644 index 0000000..0c228a6 --- /dev/null +++ b/src/save/PCSave.cpp @@ -0,0 +1,166 @@ +#define WITHWINDOWS +#include "common.h" +#include "crossplatform.h" + +#include "FileMgr.h" +#include "Font.h" +#ifdef MORE_LANGUAGES +#include "Game.h" +#endif +#include "GenericGameStorage.h" +#include "Messages.h" +#include "PCSave.h" +#include "Text.h" + +const char* _psGetUserFilesFolder(); + +C_PcSave PcSaveHelper; + +void +C_PcSave::SetSaveDirectory(const char *path) +{ + sprintf(DefaultPCSaveFileName, "%s\\%s", path, "GTA3sf"); +} + +bool +C_PcSave::DeleteSlot(int32 slot) +{ +#ifdef FIX_BUGS + char FileName[MAX_PATH]; +#else + char FileName[200]; +#endif + + PcSaveHelper.nErrorCode = SAVESTATUS_SUCCESSFUL; + sprintf(FileName, "%s%i.b", DefaultPCSaveFileName, slot + 1); + DeleteFile(FileName); + SlotSaveDate[slot][0] = '\0'; + return true; +} + +bool +C_PcSave::SaveSlot(int32 slot) +{ + MakeValidSaveName(slot); + PcSaveHelper.nErrorCode = SAVESTATUS_SUCCESSFUL; + _psGetUserFilesFolder(); + int file = CFileMgr::OpenFile(ValidSaveName, "wb"); + if (file != 0) { +#ifdef MISSION_REPLAY + if (!IsQuickSave) +#endif + DoGameSpecificStuffBeforeSave(); + if (GenericSave(file)) { + if (!!CFileMgr::CloseFile(file)) + nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; + return true; + } + + return false; + } + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CREATE; + return false; +} + +bool +C_PcSave::PcClassSaveRoutine(int32 file, uint8 *data, uint32 size) +{ + CFileMgr::Write(file, (const char*)&size, sizeof(size)); + if (CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + + CFileMgr::Write(file, (const char*)data, align4bytes(size)); + CheckSum += (uint8) size; + CheckSum += (uint8) (size >> 8); + CheckSum += (uint8) (size >> 16); + CheckSum += (uint8) (size >> 24); + for (int i = 0; i < align4bytes(size); i++) { + CheckSum += *data++; + } + if (CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + + return true; +} + +void +C_PcSave::PopulateSlotInfo() +{ + for (int i = 0; i < SLOT_COUNT; i++) { + Slots[i + 1] = SLOT_EMPTY; + SlotFileName[i][0] = '\0'; + SlotSaveDate[i][0] = '\0'; + } + for (int i = 0; i < SLOT_COUNT; i++) { +#ifdef FIX_BUGS + char savename[MAX_PATH]; +#else + char savename[52]; +#endif + struct { + int size; + wchar FileName[24]; + SYSTEMTIME SaveDateTime; + } header; + sprintf(savename, "%s%i%s", DefaultPCSaveFileName, i + 1, ".b"); + int file = CFileMgr::OpenFile(savename, "rb"); + if (file != 0) { + CFileMgr::Read(file, (char*)&header, sizeof(header)); + if (strncmp((char*)&header, TopLineEmptyFile, sizeof(TopLineEmptyFile)-1) != 0) { + Slots[i + 1] = SLOT_OK; + memcpy(SlotFileName[i], &header.FileName, sizeof(header.FileName)); + + SlotFileName[i][24] = '\0'; + } + CFileMgr::CloseFile(file); + } + if (Slots[i + 1] == SLOT_OK) { + if (CheckDataNotCorrupt(i, savename)) { +#ifdef FIX_INCOMPATIBLE_SAVES + if (!FixSave(i, GetSaveType(savename))) { + CMessages::InsertNumberInString(TheText.Get("FEC_SLC"), i + 1, -1, -1, -1, -1, -1, SlotFileName[i]); + Slots[i + 1] = SLOT_CORRUPTED; + continue; + } +#endif + SYSTEMTIME st; + memcpy(&st, &header.SaveDateTime, sizeof(SYSTEMTIME)); + const char *month; + switch (st.wMonth) + { + case 1: month = "JAN"; break; + case 2: month = "FEB"; break; + case 3: month = "MAR"; break; + case 4: month = "APR"; break; + case 5: month = "MAY"; break; + case 6: month = "JUN"; break; + case 7: month = "JUL"; break; + case 8: month = "AUG"; break; + case 9: month = "SEP"; break; + case 10: month = "OCT"; break; + case 11: month = "NOV"; break; + case 12: month = "DEC"; break; + default: assert(0); + } + char date[70]; +#ifdef MORE_LANGUAGES + if (CGame::japaneseGame) + sprintf(date, "%02d %02d %04d %02d:%02d:%02d", st.wDay, st.wMonth, st.wYear, st.wHour, st.wMinute, st.wSecond); + else +#endif // MORE_LANGUAGES + sprintf(date, "%02d %s %04d %02d:%02d:%02d", st.wDay, UnicodeToAsciiForSaveLoad(TheText.Get(month)), st.wYear, st.wHour, st.wMinute, st.wSecond); + AsciiToUnicode(date, SlotSaveDate[i]); + + } else { + CMessages::InsertNumberInString(TheText.Get("FEC_SLC"), i + 1, -1, -1, -1, -1, -1, SlotFileName[i]); + Slots[i + 1] = SLOT_CORRUPTED; + } + } + } +} diff --git a/src/save/PCSave.h b/src/save/PCSave.h new file mode 100644 index 0000000..83471b5 --- /dev/null +++ b/src/save/PCSave.h @@ -0,0 +1,40 @@ +#pragma once + +enum eSaveStatus +{ + SAVESTATUS_SUCCESSFUL = 0, + SAVESTATUS_ERR_SAVE_CREATE, + SAVESTATUS_ERR_SAVE_WRITE, + SAVESTATUS_ERR_SAVE_CLOSE, + SAVESTATUS_ERR_LOAD_OPEN, + SAVESTATUS_ERR_LOAD_READ, + SAVESTATUS_ERR_LOAD_CLOSE, + SAVESTATUS_ERR_DATA_INVALID, + + // unused + SAVESTATUS_DELETEFAILED8, + SAVESTATUS_DELETEFAILED9, + SAVESTATUS_DELETEFAILED10, +}; + +enum +{ + SLOT_OK = 0, + SLOT_EMPTY, + SLOT_CORRUPTED +}; + +class C_PcSave +{ +public: + eSaveStatus nErrorCode; + + C_PcSave() : nErrorCode(SAVESTATUS_SUCCESSFUL) {} + void PopulateSlotInfo(); + bool DeleteSlot(int32 slot); + bool SaveSlot(int32 slot); + bool PcClassSaveRoutine(int32 file, uint8 *data, uint32 size); + static void SetSaveDirectory(const char *path); +}; + +extern C_PcSave PcSaveHelper; diff --git a/src/save/SaveBuf.h b/src/save/SaveBuf.h new file mode 100644 index 0000000..aad2e1a --- /dev/null +++ b/src/save/SaveBuf.h @@ -0,0 +1,73 @@ +#pragma once + +#ifdef VALIDATE_SAVE_SIZE +extern int32 _saveBufCount; +#define INITSAVEBUF _saveBufCount = 0; +#define VALIDATESAVEBUF(b) assert(_saveBufCount == b); +#else +#define INITSAVEBUF +#define VALIDATESAVEBUF(b) +#endif + +inline void +SkipSaveBuf(uint8 *&buf, int32 skip) +{ + buf += skip; +#ifdef VALIDATE_SAVE_SIZE + _saveBufCount += skip; +#endif +} + +template +inline void +ReadSaveBuf(T* out, uint8 *&buf) +{ + *out = *(T *)buf; + SkipSaveBuf(buf, sizeof(T)); +} + +template +inline T * +WriteSaveBuf(uint8 *&buf, const T &value) +{ + T *p = (T *)buf; + *p = value; + SkipSaveBuf(buf, sizeof(T)); + return p; +} + +#ifdef COMPATIBLE_SAVES +inline void +ZeroSaveBuf(uint8 *&buf, uint32 length) +{ + memset(buf, 0, length); + SkipSaveBuf(buf, length); +} +#endif + +#define SAVE_HEADER_SIZE (4 * sizeof(char) + sizeof(uint32)) + +#define WriteSaveHeader(buf, a, b, c, d, size) \ + WriteSaveBuf(buf, a); \ + WriteSaveBuf(buf, b); \ + WriteSaveBuf(buf, c); \ + WriteSaveBuf(buf, d); \ + WriteSaveBuf(buf, (uint32)(size)); + +#ifdef VALIDATE_SAVE_SIZE +#define CheckSaveHeader(buf, a, b, c, d, size) do { \ + char _c; uint32 _size;\ + ReadSaveBuf(&_c, buf);\ + assert(_c == a);\ + ReadSaveBuf(&_c, buf);\ + assert(_c == b);\ + ReadSaveBuf(&_c, buf);\ + assert(_c == c);\ + ReadSaveBuf(&_c, buf);\ + assert(_c == d);\ + ReadSaveBuf(&_size, buf);\ + assert(_size == size);\ + } while(0) +#else +#define CheckSaveHeader(buf, a, b, c, d, size) SkipSaveBuf(buf, 8); +#endif \ No newline at end of file diff --git a/src/skel/crossplatform.cpp b/src/skel/crossplatform.cpp new file mode 100644 index 0000000..e69c22e --- /dev/null +++ b/src/skel/crossplatform.cpp @@ -0,0 +1,425 @@ +#include "common.h" +#include "crossplatform.h" + +// Codes compatible with Windows and Linux +#ifndef _WIN32 + +// For internal use +// wMilliseconds is not needed +void tmToSystemTime(const tm *tm, SYSTEMTIME *out) { + out->wYear = tm->tm_year + 1900; + out->wMonth = tm->tm_mon + 1; + out->wDayOfWeek = tm->tm_wday; + out->wDay = tm->tm_mday; + out->wHour = tm->tm_hour; + out->wMinute = tm->tm_min; + out->wSecond = tm->tm_sec; +} + +void GetLocalTime_CP(SYSTEMTIME *out) { + time_t timestamp = time(nil); + tm *localTm = localtime(×tamp); + tmToSystemTime(localTm, out); +} +#endif + +// Compatible with Linux/POSIX and MinGW on Windows +#ifndef _WIN32 +HANDLE FindFirstFile(const char* pathname, WIN32_FIND_DATA* firstfile) { + char pathCopy[MAX_PATH]; + strcpy(pathCopy, pathname); + + char *folder = strtok(pathCopy, "*"); + char *extension = strtok(NULL, "*"); + + // because I remember like strtok might not return NULL for last delimiter + if (extension && extension - folder == strlen(pathname)) + extension = nil; + + // Case-sensitivity and backslashes... + // Will be freed at the bottom + char *realFolder = casepath(folder); + if (realFolder) { + folder = realFolder; + } + + strncpy(firstfile->folder, folder, sizeof(firstfile->folder)); + + if (extension) + strncpy(firstfile->extension, extension, sizeof(firstfile->extension)); + else + firstfile->extension[0] = '\0'; + + if (realFolder) + free(realFolder); + + HANDLE d; + if ((d = (HANDLE)opendir(firstfile->folder)) == NULL || !FindNextFile(d, firstfile)) + return NULL; + + return d; +} + +bool FindNextFile(HANDLE d, WIN32_FIND_DATA* finddata) { + dirent *file; + static struct stat fileStats; + static char path[PATH_MAX], relativepath[NAME_MAX + sizeof(finddata->folder) + 1]; + int extensionLen = strlen(finddata->extension); + while ((file = readdir((DIR*)d)) != NULL) { + + // We only want "DT_REG"ular Files, but reportedly some FS and OSes gives DT_UNKNOWN as type. + if ((file->d_type == DT_UNKNOWN || file->d_type == DT_REG || file->d_type == DT_LNK) && + (extensionLen == 0 || strncasecmp(&file->d_name[strlen(file->d_name) - extensionLen], finddata->extension, extensionLen) == 0)) { + + sprintf(relativepath, "%s/%s", finddata->folder, file->d_name); + realpath(relativepath, path); + stat(path, &fileStats); + strncpy(finddata->cFileName, file->d_name, sizeof(finddata->cFileName)); + finddata->ftLastWriteTime = fileStats.st_mtime; + return true; + } + } + return false; +} + +void GetDateFormat(int unused1, int unused2, SYSTEMTIME* in, int unused3, char* out, int size) { + tm linuxTime; + linuxTime.tm_year = in->wYear - 1900; + linuxTime.tm_mon = in->wMonth - 1; + linuxTime.tm_wday = in->wDayOfWeek; + linuxTime.tm_mday = in->wDay; + linuxTime.tm_hour = in->wHour; + linuxTime.tm_min = in->wMinute; + linuxTime.tm_sec = in->wSecond; + strftime(out, size, nl_langinfo(D_FMT), &linuxTime); +} + +void FileTimeToSystemTime(time_t* writeTime, SYSTEMTIME* out) { + tm *ptm = gmtime(writeTime); + tmToSystemTime(ptm, out); +} +#endif + +// Because wchar length differs between platforms. +wchar* +AllocUnicode(const char* src) +{ + wchar *dst = (wchar*)malloc(strlen(src)*2 + 2); + wchar *i = dst; + while((*i++ = (unsigned char)*src++) != '\0'); + return dst; +} + +// Funcs/features from Windows that we need on other platforms +#ifndef _WIN32 +char *strupr(char *s) { + char* tmp = s; + + for (;*tmp;++tmp) { + *tmp = toupper((unsigned char) *tmp); + } + + return s; +} +char *strlwr(char *s) { + char* tmp = s; + + for (;*tmp;++tmp) { + *tmp = tolower((unsigned char) *tmp); + } + + return s; +} + +char *trim(char *s) { + char *ptr; + if (!s) + return NULL; // handle NULL string + if (!*s) + return s; // handle empty string + for (ptr = s + strlen(s) - 1; (ptr >= s) && isspace(*ptr); --ptr); + ptr[1] = '\0'; + return s; +} + +FILE* _fcaseopen(char const* filename, char const* mode) +{ + FILE* result; + char* real = casepath(filename); + if (!real) + result = fopen(filename, mode); + else { + result = fopen(real, mode); + free(real); + } + return result; +} + +int _caserename(const char *old_filename, const char *new_filename) +{ + int result; + char *real_old = casepath(old_filename); + char *real_new = casepath(new_filename); + + // hack so we don't even try to rename it to new_filename if it already exists + if (!real_new) { + free(real_old); + return -1; + } + + if (!real_old) + result = rename(old_filename, real_new); + else + result = rename(real_old, real_new); + + free(real_old); + free(real_new); + + return result; +} + +// Case-insensitivity on linux (from https://github.com/OneSadCookie/fcaseopen) +// Returned string should freed manually (if exists) +char* casepath(char const* path, bool checkPathFirst) +{ + if (checkPathFirst && access(path, F_OK) != -1) { + // File path is correct + return nil; + } + + size_t l = strlen(path); + char* p = (char*)alloca(l + 1); + char* out = (char*)malloc(l + 3); // for extra ./ + strcpy(p, path); + + // my addon: linux doesn't handle filenames with spaces at the end nicely + p = trim(p); + + size_t rl = 0; + + DIR* d; + char* c; + + #if defined(__SWITCH__) || defined(PSP2) + if( (c = strstr(p, ":/")) != NULL) // scheme used by some environments, eg. switch, vita + { + size_t deviceNameOffset = c - p + 3; + char* deviceNamePath = (char*)alloca(deviceNameOffset + 1); + strlcpy(deviceNamePath, p, deviceNameOffset); + deviceNamePath[deviceNameOffset] = 0; + d = opendir(deviceNamePath); + p = c + 1; + } + else + #endif + if (p[0] == '/' || p[0] == '\\') + { + d = opendir("/"); + } + else + { + d = opendir("."); + out[0] = '.'; + out[1] = 0; + rl = 1; + } + + bool cantProceed = false; // just convert slashes in what's left in string, don't correct case of letters(because we can't) + bool mayBeTrailingSlash = false; + + while (c = strsep(&p, "/\\")) + { + // May be trailing slash(allow), slash at the start(avoid), or multiple slashes(avoid) + if (*c == '\0') + { + mayBeTrailingSlash = true; + continue; + } else { + mayBeTrailingSlash = false; + } + + out[rl] = '/'; + rl += 1; + out[rl] = 0; + + if (cantProceed) + { + strcpy(out + rl, c); + rl += strlen(c); + continue; + } + + struct dirent* e; + while (e = readdir(d)) + { + if (strcasecmp(c, e->d_name) == 0) + { + strcpy(out + rl, e->d_name); + int reportedLen = (int)strlen(e->d_name); + rl += reportedLen; + assert(reportedLen == strlen(c) && "casepath: This is not good at all"); + + closedir(d); + d = opendir(out); + + // Either it wasn't a folder, or permission error, I/O error etc. + if (!d) { + cantProceed = true; + } + + break; + } + } + + if (!e) + { + printf("casepath couldn't find dir/file \"%s\", full path was %s\n", c, path); + // No match, add original name and continue converting further slashes. + strcpy(out + rl, c); + rl += strlen(c); + cantProceed = true; + } + } + + if (d) closedir(d); + if (mayBeTrailingSlash) { + out[rl] = '/'; rl += 1; + out[rl] = '\0'; + } + + if (rl > l + 2) { + printf("\n\ncasepath: Corrected path length is longer then original+2:\n\tOriginal: %s (%zu chars)\n\tCorrected: %s (%zu chars)\n\n", path, l, out, rl); + } + return out; +} +#endif + +#ifdef __SWITCH__ +/* Taken from glibc */ +char *realpath(const char *name, char *resolved) +{ + char *rpath, *dest = NULL; + const char *start, *end, *rpath_limit; + long int path_max; + + /* As per Single Unix Specification V2 we must return an error if + either parameter is a null pointer. We extend this to allow + the RESOLVED parameter to be NULL in case the we are expected to + allocate the room for the return value. */ + if (!name) + return NULL; + + /* As per Single Unix Specification V2 we must return an error if + the name argument points to an empty string. */ + if (name[0] == '\0') + return NULL; + +#ifdef PATH_MAX + path_max = PATH_MAX; +#else + path_max = pathconf(name, _PC_PATH_MAX); + if (path_max <= 0) + path_max = 1024; +#endif + + if (!resolved) + { + rpath = (char*)malloc(path_max); + if (!rpath) + return NULL; + } + else + rpath = resolved; + rpath_limit = rpath + path_max; + + if (name[0] != '/') + { + if (!getcwd(rpath, path_max)) + { + rpath[0] = '\0'; + goto error; + } + dest = (char*)memchr(rpath, '\0', path_max); + } + else + { + rpath[0] = '/'; + dest = rpath + 1; + } + + for (start = end = name; *start; start = end) + { + /* Skip sequence of multiple path-separators. */ + while (*start == '/') + ++start; + + /* Find end of path component. */ + for (end = start; *end && *end != '/'; ++end) + /* Nothing. */; + + if (end - start == 0) + break; + else if (end - start == 1 && start[0] == '.') + /* nothing */; + else if (end - start == 2 && start[0] == '.' && start[1] == '.') + { + /* Back up to previous component, ignore if at root already. */ + if (dest > rpath + 1) + while ((--dest)[-1] != '/') + ; + } + else + { + size_t new_size; + + if (dest[-1] != '/') + *dest++ = '/'; + + if (dest + (end - start) >= rpath_limit) + { + ptrdiff_t dest_offset = dest - rpath; + char *new_rpath; + + if (resolved) + { + if (dest > rpath + 1) + dest--; + *dest = '\0'; + goto error; + } + new_size = rpath_limit - rpath; + if (end - start + 1 > path_max) + new_size += end - start + 1; + else + new_size += path_max; + new_rpath = (char *)realloc(rpath, new_size); + if (!new_rpath) + goto error; + rpath = new_rpath; + rpath_limit = rpath + new_size; + + dest = rpath + dest_offset; + } + + dest = (char*)memcpy(dest, start, end - start); + *dest = '\0'; + } + } + if (dest > rpath + 1 && dest[-1] == '/') + --dest; + *dest = '\0'; + + return rpath; + +error: + if (!resolved) + free(rpath); + return NULL; +} + +ssize_t readlink (const char * __path, char * __buf, size_t __buflen) +{ + errno = ENOSYS; + return -1; +} +#endif diff --git a/src/skel/crossplatform.h b/src/skel/crossplatform.h new file mode 100644 index 0000000..67bb428 --- /dev/null +++ b/src/skel/crossplatform.h @@ -0,0 +1,184 @@ +#include +#include + +// This is the common include for platform/renderer specific skeletons(glfw.cpp, win.cpp etc.) and using cross platform things (like Windows directories wrapper, platform specific global arrays etc.) +// Functions that's different on glfw and win but have same signature, should be located on platform.h. + +enum eWinVersion +{ + OS_WIN95 = 0, + OS_WIN98, + OS_WINNT, + OS_WIN2000, + OS_WINXP, +}; + +#ifdef _WIN32 + +// As long as WITHWINDOWS isn't defined / isn't included, we only need type definitions so let's include . +// NOTE: It's perfectly fine to include here, but it can increase build size and time in *some* conditions, and maybe substantially in future if we'll use crossplatform.h more. +#ifndef _INC_WINDOWS + #ifndef __MWERKS__ + #include + #else + #include + #endif +#endif +#if defined RW_D3D9 || defined RWLIBS +#include "win.h" +#endif +extern DWORD _dwOperatingSystemVersion; +#define fcaseopen fopen +#define caserename rename +#else +char *strupr(char *str); +char *strlwr(char *str); + +enum { + LANG_OTHER, + LANG_GERMAN, + LANG_FRENCH, + LANG_ENGLISH, + LANG_ITALIAN, + LANG_SPANISH, +}; + +enum { + SUBLANG_OTHER, + SUBLANG_ENGLISH_AUS +}; + +extern long _dwOperatingSystemVersion; +char *casepath(char const *path, bool checkPathFirst = true); +FILE *_fcaseopen(char const *filename, char const *mode); +#define fcaseopen _fcaseopen +int _caserename(const char *old_filename, const char *new_filename); +#define caserename _caserename +#endif + +#ifdef RW_GL3 +typedef struct +{ + GLFWwindow* window; + RwBool fullScreen; + RwV2d lastMousePos; + double mouseWheel; // glfw doesn't cache it + bool cursorIsInWindow; + RwInt8 joy1id; + RwInt8 joy2id; +} +psGlobalType; + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +void CapturePad(RwInt32 padID); +void joysChangeCB(int jid, int event); +#endif + +#ifdef DETECT_JOYSTICK_MENU +extern char gSelectedJoystickName[128]; +#endif + +enum eGameState +{ + GS_START_UP = 0, + GS_INIT_LOGO_MPEG, + GS_LOGO_MPEG, + GS_INIT_INTRO_MPEG, + GS_INTRO_MPEG, + GS_INIT_ONCE, + GS_INIT_FRONTEND, + GS_FRONTEND, + GS_INIT_PLAYING_GAME, + GS_PLAYING_GAME, +}; +extern RwUInt32 gGameState; + +RwBool IsForegroundApp(); + +#ifndef MAX_PATH + #if !defined _WIN32 || defined __MINGW32__ + #define MAX_PATH PATH_MAX + #else + #define MAX_PATH 260 + #endif +#endif + +// Codes compatible with Windows and Linux +#ifndef _WIN32 +#define DeleteFile unlink + +// Needed for save games +struct SYSTEMTIME { + RwUInt16 wYear; + RwUInt16 wMonth; + RwUInt16 wDayOfWeek; + RwUInt16 wDay; + RwUInt16 wHour; + RwUInt16 wMinute; + RwUInt16 wSecond; + RwUInt16 wMilliseconds; +}; + +void GetLocalTime_CP(SYSTEMTIME* out); +#define GetLocalTime GetLocalTime_CP +#define OutputDebugString(s) re3_debug("[DBG-2]: %s\n",s) +#endif + +// Compatible with Linux/POSIX and MinGW on Windows +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include + +typedef void* HANDLE; +#define INVALID_HANDLE_VALUE NULL +#define FindClose(h) \ + do { \ + if (h != nil) \ + closedir((DIR*)h); \ + } while(0) + +#define LOCALE_USER_DEFAULT 0 +#define DATE_SHORTDATE 0 + +struct WIN32_FIND_DATA { + char extension[32]; // for searching + char folder[MAX_PATH]; // for searching + char cFileName[256]; // because tSkinInfo has it 256 + time_t ftLastWriteTime; +}; + +HANDLE FindFirstFile(const char*, WIN32_FIND_DATA*); +bool FindNextFile(HANDLE, WIN32_FIND_DATA*); +void FileTimeToSystemTime(time_t*, SYSTEMTIME*); +void GetDateFormat(int, int, SYSTEMTIME*, int, char*, int); +#endif + +#ifdef __SWITCH__ + +// tweak glfw values for switch to match expected pc bindings +#ifdef GLFW_GAMEPAD_BUTTON_A + #undef GLFW_GAMEPAD_BUTTON_A +#endif +#define GLFW_GAMEPAD_BUTTON_A 1 + +#ifdef GLFW_GAMEPAD_BUTTON_B + #undef GLFW_GAMEPAD_BUTTON_B +#endif +#define GLFW_GAMEPAD_BUTTON_B 0 + +#ifdef GLFW_GAMEPAD_BUTTON_X + #undef GLFW_GAMEPAD_BUTTON_X +#endif +#define GLFW_GAMEPAD_BUTTON_X 3 + +#ifdef GLFW_GAMEPAD_BUTTON_Y + #undef GLFW_GAMEPAD_BUTTON_Y +#endif +#define GLFW_GAMEPAD_BUTTON_Y 2 + +#endif diff --git a/src/skel/events.cpp b/src/skel/events.cpp new file mode 100644 index 0000000..8744781 --- /dev/null +++ b/src/skel/events.cpp @@ -0,0 +1,831 @@ +#include "common.h" +#include "Pad.h" +#include "ControllerConfig.h" +#include "Frontend.h" +#include "Camera.h" + +#include "rwcore.h" +#include "skeleton.h" +#include "events.h" + + +/* + ***************************************************************************** + */ +static RsEventStatus +HandleKeyDown(RsKeyStatus *keyStatus) +{ + CPad *pad0 = CPad::GetPad(0); + CPad *pad1 = CPad::GetPad(1); + + RwInt32 c = keyStatus->keyCharCode; + + if ( c != rsNULL ) + { + switch (c) + { + case rsESC: + { + CPad::TempKeyState.ESC = 255; + break; + } + + case rsINS: + { + CPad::TempKeyState.INS = 255; + break; + } + + case rsDEL: + { + CPad::TempKeyState.DEL = 255; + break; + } + + case rsHOME: + { + CPad::TempKeyState.HOME = 255; + break; + } + + case rsEND: + { + CPad::TempKeyState.END = 255; + break; + } + + case rsPGUP: + { + CPad::TempKeyState.PGUP = 255; + break; + } + + case rsPGDN: + { + CPad::TempKeyState.PGDN = 255; + break; + } + + case rsUP: + { + CPad::TempKeyState.UP = 255; + break; + } + + case rsDOWN: + { + CPad::TempKeyState.DOWN = 255; + break; + } + + case rsLEFT: + { + CPad::TempKeyState.LEFT = 255; + break; + } + + case rsRIGHT: + { + CPad::TempKeyState.RIGHT = 255; + break; + } + + case rsNUMLOCK: + { + CPad::TempKeyState.NUMLOCK = 255; + break; + } + + case rsPADDEL: + { + CPad::TempKeyState.DECIMAL = 255; + break; + } + + case rsPADEND: + { + CPad::TempKeyState.NUM1 = 255; + break; + } + + case rsPADDOWN: + { + CPad::TempKeyState.NUM2 = 255; + break; + } + + case rsPADPGDN: + { + CPad::TempKeyState.NUM3 = 255; + break; + } + + case rsPADLEFT: + { + CPad::TempKeyState.NUM4 = 255; + break; + } + + case rsPAD5: + { + CPad::TempKeyState.NUM5 = 255; + break; + } + + case rsPADRIGHT: + { + CPad::TempKeyState.NUM6 = 255; + break; + } + + case rsPADHOME: + { + CPad::TempKeyState.NUM7 = 255; + break; + } + + case rsPADUP: + { + CPad::TempKeyState.NUM8 = 255; + break; + } + + case rsPADPGUP: + { + CPad::TempKeyState.NUM9 = 255; + break; + } + + case rsPADINS: + { + CPad::TempKeyState.NUM0 = 255; + break; + } + + case rsDIVIDE: + { + CPad::TempKeyState.DIV = 255; + break; + } + + case rsTIMES: + { + CPad::TempKeyState.MUL = 255; + break; + } + + case rsMINUS: + { + CPad::TempKeyState.SUB = 255; + break; + } + + case rsPADENTER: + { + CPad::TempKeyState.ENTER = 255; + break; + } + + case rsPLUS: + { + CPad::TempKeyState.ADD = 255; + break; + } + + case rsENTER: + { + CPad::TempKeyState.EXTENTER = 255; + break; + } + + case rsSCROLL: + { + CPad::TempKeyState.SCROLLLOCK = 255; + break; + } + + case rsPAUSE: + { + CPad::TempKeyState.PAUSE = 255; + break; + } + + case rsBACKSP: + { + CPad::TempKeyState.BACKSP = 255; + break; + } + + case rsTAB: + { + CPad::TempKeyState.TAB = 255; + break; + } + + case rsCAPSLK: + { + CPad::TempKeyState.CAPSLOCK = 255; + break; + } + + case rsLSHIFT: + { + CPad::TempKeyState.LSHIFT = 255; + break; + } + + case rsSHIFT: + { + CPad::TempKeyState.SHIFT = 255; + break; + } + + case rsRSHIFT: + { + CPad::TempKeyState.RSHIFT = 255; + break; + } + + case rsLCTRL: + { + CPad::TempKeyState.LCTRL = 255; + break; + } + + case rsRCTRL: + { + CPad::TempKeyState.RCTRL = 255; + break; + } + + case rsLALT: + { + CPad::TempKeyState.LALT = 255; + break; + } + + case rsRALT: + { + CPad::TempKeyState.RALT = 255; + break; + } + + + case rsLWIN: + { + CPad::TempKeyState.LWIN = 255; + break; + } + + case rsRWIN: + { + CPad::TempKeyState.RWIN = 255; + break; + } + + case rsAPPS: + { + CPad::TempKeyState.APPS = 255; + break; + } + + case rsF1: + case rsF2: + case rsF3: + case rsF4: + case rsF5: + case rsF6: + case rsF7: + case rsF8: + case rsF9: + case rsF10: + case rsF11: + case rsF12: + { + CPad::TempKeyState.F[c - rsF1] = 255; + break; + } + + default: + { + if ( c < 255 ) + { + CPad::TempKeyState.VK_KEYS[c] = 255; + pad0->AddToPCCheatString(c); + } + break; + } + } + + if ( CPad::m_bMapPadOneToPadTwo ) + { + if ( c == 'D' ) pad1->PCTempKeyState.LeftStickX = 128; + if ( c == 'A' ) pad1->PCTempKeyState.LeftStickX = -128; + if ( c == 'W' ) pad1->PCTempKeyState.LeftStickY = 128; + if ( c == 'S' ) pad1->PCTempKeyState.LeftStickY = -128; + if ( c == 'J' ) pad1->PCTempKeyState.RightStickX = 128; + if ( c == 'G' ) pad1->PCTempKeyState.RightStickX = -128; + if ( c == 'Y' ) pad1->PCTempKeyState.RightStickY = 128; + if ( c == 'H' ) pad1->PCTempKeyState.RightStickY = -128; + if ( c == 'Z' ) pad1->PCTempKeyState.LeftShoulder1 = 255; + if ( c == 'X' ) pad1->PCTempKeyState.LeftShoulder2 = 255; + if ( c == 'C' ) pad1->PCTempKeyState.RightShoulder1 = 255; + if ( c == 'V' ) pad1->PCTempKeyState.RightShoulder2 = 255; + if ( c == 'O' ) pad1->PCTempKeyState.DPadUp = 255; + if ( c == 'L' ) pad1->PCTempKeyState.DPadDown = 255; + if ( c == 'K' ) pad1->PCTempKeyState.DPadLeft = 255; + if ( c == ';' ) pad1->PCTempKeyState.DPadRight = 255; + if ( c == 'B' ) pad1->PCTempKeyState.Start = 255; + if ( c == 'N' ) pad1->PCTempKeyState.Select = 255; + if ( c == 'M' ) pad1->PCTempKeyState.Square = 255; + if ( c == ',' ) pad1->PCTempKeyState.Triangle = 255; + if ( c == '.' ) pad1->PCTempKeyState.Cross = 255; + if ( c == '/' ) pad1->PCTempKeyState.Circle = 255; + if ( c == rsRSHIFT ) pad1->PCTempKeyState.LeftShock = 255; + if ( c == rsRCTRL ) pad1->PCTempKeyState.RightShock = 255; + } + } + + return rsEVENTPROCESSED; +} + + +static RsEventStatus +HandleKeyUp(RsKeyStatus *keyStatus) +{ + CPad *pad0 = CPad::GetPad(0); + CPad *pad1 = CPad::GetPad(1); + + RwInt32 c = keyStatus->keyCharCode; + + if ( c != rsNULL ) + { + switch (c) + { + case rsESC: + { + CPad::TempKeyState.ESC = 0; + break; + } + + case rsINS: + { + CPad::TempKeyState.INS = 0; + break; + } + + case rsDEL: + { + CPad::TempKeyState.DEL = 0; + break; + } + + case rsHOME: + { + CPad::TempKeyState.HOME = 0; + break; + } + + case rsEND: + { + CPad::TempKeyState.END = 0; + break; + } + + case rsPGUP: + { + CPad::TempKeyState.PGUP = 0; + break; + } + + case rsPGDN: + { + CPad::TempKeyState.PGDN = 0; + break; + } + + case rsUP: + { + CPad::TempKeyState.UP = 0; + break; + } + + case rsDOWN: + { + CPad::TempKeyState.DOWN = 0; + break; + } + + case rsLEFT: + { + CPad::TempKeyState.LEFT = 0; + break; + } + + case rsRIGHT: + { + CPad::TempKeyState.RIGHT = 0; + break; + } + + case rsNUMLOCK: + { + CPad::TempKeyState.NUMLOCK = 0; + break; + } + + case rsPADDEL: + { + CPad::TempKeyState.DECIMAL = 0; + break; + } + + case rsPADEND: + { + CPad::TempKeyState.NUM1 = 0; + break; + } + + case rsPADDOWN: + { + CPad::TempKeyState.NUM2 = 0; + break; + } + + case rsPADPGDN: + { + CPad::TempKeyState.NUM3 = 0; + break; + } + + case rsPADLEFT: + { + CPad::TempKeyState.NUM4 = 0; + break; + } + + case rsPAD5: + { + CPad::TempKeyState.NUM5 = 0; + break; + } + + case rsPADRIGHT: + { + CPad::TempKeyState.NUM6 = 0; + break; + } + + case rsPADHOME: + { + CPad::TempKeyState.NUM7 = 0; + break; + } + + case rsPADUP: + { + CPad::TempKeyState.NUM8 = 0; + break; + } + + case rsPADPGUP: + { + CPad::TempKeyState.NUM9 = 0; + break; + } + + case rsPADINS: + { + CPad::TempKeyState.NUM0 = 0; + break; + } + + case rsDIVIDE: + { + CPad::TempKeyState.DIV = 0; + break; + } + + case rsTIMES: + { + CPad::TempKeyState.MUL = 0; + break; + } + + case rsMINUS: + { + CPad::TempKeyState.SUB = 0; + break; + } + + case rsPADENTER: + { + CPad::TempKeyState.ENTER = 0; + break; + } + + case rsPLUS: + { + CPad::TempKeyState.ADD = 0; + break; + } + + case rsENTER: + { + CPad::TempKeyState.EXTENTER = 0; + break; + } + + case rsSCROLL: + { + CPad::TempKeyState.SCROLLLOCK = 0; + break; + } + + case rsPAUSE: + { + CPad::TempKeyState.PAUSE = 0; + break; + } + + case rsBACKSP: + { + CPad::TempKeyState.BACKSP = 0; + break; + } + + case rsTAB: + { + CPad::TempKeyState.TAB = 0; + break; + } + + case rsCAPSLK: + { + CPad::TempKeyState.CAPSLOCK = 0; + break; + } + + case rsLSHIFT: + { + CPad::TempKeyState.LSHIFT = 0; + break; + } + + case rsSHIFT: + { + CPad::TempKeyState.SHIFT = 0; + break; + } + + case rsRSHIFT: + { + CPad::TempKeyState.RSHIFT = 0; + break; + } + + case rsLCTRL: + { + CPad::TempKeyState.LCTRL = 0; + break; + } + + case rsRCTRL: + { + CPad::TempKeyState.RCTRL = 0; + break; + } + + case rsLALT: + { + CPad::TempKeyState.LALT = 0; + break; + } + + case rsRALT: + { + CPad::TempKeyState.RALT = 0; + break; + } + + + case rsLWIN: + { + CPad::TempKeyState.LWIN = 0; + break; + } + + case rsRWIN: + { + CPad::TempKeyState.RWIN = 0; + break; + } + + case rsAPPS: + { + CPad::TempKeyState.APPS = 0; + break; + } + + case rsF1: + case rsF2: + case rsF3: + case rsF4: + case rsF5: + case rsF6: + case rsF7: + case rsF8: + case rsF9: + case rsF10: + case rsF11: + case rsF12: + { + CPad::TempKeyState.F[c - rsF1] = 0; + break; + } + + default: + { + if ( c < 255 ) + { + CPad::TempKeyState.VK_KEYS[c] = 0; + } + break; + } + } + + if ( CPad::m_bMapPadOneToPadTwo ) + { + if ( c == 'D' ) pad1->PCTempKeyState.LeftStickX = 0; + if ( c == 'A' ) pad1->PCTempKeyState.LeftStickX = 0; + if ( c == 'W' ) pad1->PCTempKeyState.LeftStickY = 0; + if ( c == 'S' ) pad1->PCTempKeyState.LeftStickY = 0; + if ( c == 'J' ) pad1->PCTempKeyState.RightStickX = 0; + if ( c == 'G' ) pad1->PCTempKeyState.RightStickX = 0; + if ( c == 'Y' ) pad1->PCTempKeyState.RightStickY = 0; + if ( c == 'H' ) pad1->PCTempKeyState.RightStickY = 0; + if ( c == 'Z' ) pad1->PCTempKeyState.LeftShoulder1 = 0; + if ( c == 'X' ) pad1->PCTempKeyState.LeftShoulder2 = 0; + if ( c == 'C' ) pad1->PCTempKeyState.RightShoulder1 = 0; + if ( c == 'V' ) pad1->PCTempKeyState.RightShoulder2 = 0; + if ( c == 'O' ) pad1->PCTempKeyState.DPadUp = 0; + if ( c == 'L' ) pad1->PCTempKeyState.DPadDown = 0; + if ( c == 'K' ) pad1->PCTempKeyState.DPadLeft = 0; + if ( c == ';' ) pad1->PCTempKeyState.DPadRight = 0; + if ( c == 'B' ) pad1->PCTempKeyState.Start = 0; + if ( c == 'N' ) pad1->PCTempKeyState.Select = 0; + if ( c == 'M' ) pad1->PCTempKeyState.Square = 0; + if ( c == ',' ) pad1->PCTempKeyState.Triangle = 0; + if ( c == '.' ) pad1->PCTempKeyState.Cross = 0; + if ( c == '/' ) pad1->PCTempKeyState.Circle = 0; + if ( c == rsRSHIFT ) pad1->PCTempKeyState.LeftShock = 0; + if ( c == rsRCTRL ) pad1->PCTempKeyState.RightShock = 0; + } + } + + return rsEVENTPROCESSED; +} + + +/* + ***************************************************************************** + */ +static RsEventStatus +KeyboardHandler(RsEvent event, void *param) +{ + /* + * ...then the application events, if necessary... + */ + switch( event ) + { + case rsKEYDOWN: + { + return HandleKeyDown((RsKeyStatus *)param); + } + + case rsKEYUP: + { + return HandleKeyUp((RsKeyStatus *)param); + } + + default: + { + return rsEVENTNOTPROCESSED; + } + } +} + +/* + ***************************************************************************** + */ +static RsEventStatus +HandlePadButtonDown(RsPadButtonStatus *padButtonStatus) +{ + bool bPadTwo = false; + int32 padNumber = padButtonStatus->padID; + + CPad *pad = CPad::GetPad(padNumber); + + if ( CPad::m_bMapPadOneToPadTwo ) + padNumber = 1; + + if ( padNumber == 1 ) + bPadTwo = true; + + ControlsManager.UpdateJoyButtonState(padNumber); + + for ( int32 i = 0; i < _TODOCONST(16); i++ ) + { + RsPadButtons btn = rsPADNULL; + if ( ControlsManager.m_aButtonStates[i] == TRUE ) + btn = (RsPadButtons)(i + 1); + + if ( FrontEndMenuManager.m_bMenuActive || bPadTwo ) + ControlsManager.UpdateJoyInConfigMenus_ButtonDown(btn, padNumber); + else + ControlsManager.AffectControllerStateOn_ButtonDown(btn, JOYSTICK); + } + + return rsEVENTPROCESSED; +} + + +/* + ***************************************************************************** + */ +static RsEventStatus +HandlePadButtonUp(RsPadButtonStatus *padButtonStatus) +{ + bool bPadTwo = false; + int32 padNumber = padButtonStatus->padID; + + CPad *pad = CPad::GetPad(padNumber); + + if ( CPad::m_bMapPadOneToPadTwo ) + padNumber = 1; + + if ( padNumber == 1 ) + bPadTwo = true; + + bool bCam = false; + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if ( mode == CCam::MODE_FLYBY || mode == CCam::MODE_FIXED ) + bCam = true; + + ControlsManager.UpdateJoyButtonState(padNumber); + + for ( int32 i = 1; i < _TODOCONST(16); i++ ) + { + RsPadButtons btn = rsPADNULL; + if ( ControlsManager.m_aButtonStates[i] == FALSE ) + btn = (RsPadButtons)(i + 1); // bug ?, cycle begins from 1(not zero), 1+1==2==rsPADBUTTON2, so we skip rsPADBUTTON1, right ? + + if ( FrontEndMenuManager.m_bMenuActive || bPadTwo || bCam ) + ControlsManager.UpdateJoyInConfigMenus_ButtonUp(btn, padNumber); + else + ControlsManager.AffectControllerStateOn_ButtonUp(btn, JOYSTICK); + } + + return rsEVENTPROCESSED; +} + +/* + ***************************************************************************** + */ +static RsEventStatus +PadHandler(RsEvent event, void *param) +{ + switch( event ) + { + case rsPADBUTTONDOWN: + { + return HandlePadButtonDown((RsPadButtonStatus *)param); + } + + case rsPADBUTTONUP: + { + return HandlePadButtonUp((RsPadButtonStatus *)param); + } + + default: + { + return rsEVENTNOTPROCESSED; + } + } +} + + +/* + ***************************************************************************** + */ +RwBool +AttachInputDevices(void) +{ +#ifndef IGNORE_MOUSE_KEYBOARD + RsInputDeviceAttach(rsKEYBOARD, KeyboardHandler); +#endif + + RsInputDeviceAttach(rsPAD, PadHandler); + + return TRUE; +} diff --git a/src/skel/events.h b/src/skel/events.h new file mode 100644 index 0000000..ef812eb --- /dev/null +++ b/src/skel/events.h @@ -0,0 +1,7 @@ +#ifndef EVENTS_H +#define EVENTS_H + +#include +#include "skeleton.h" + +#endif /* EVENTS_H */ diff --git a/src/skel/glfw/glfw.cpp b/src/skel/glfw/glfw.cpp new file mode 100644 index 0000000..92a5a93 --- /dev/null +++ b/src/skel/glfw/glfw.cpp @@ -0,0 +1,2572 @@ +#if defined RW_GL3 && !defined LIBRW_SDL2 + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include + +DWORD _dwOperatingSystemVersion; +#include "resource.h" +#else +long _dwOperatingSystemVersion; +#ifndef __SWITCH__ +#ifndef __APPLE__ +#include +#else +#include +#include +#endif +#endif +#include +#include +#include +#include +#endif + +#include "common.h" +#if (defined(_MSC_VER)) +#include +#endif /* (defined(_MSC_VER)) */ +#include +#include "rwcore.h" +#include "skeleton.h" +#include "platform.h" +#include "crossplatform.h" + +#include "main.h" +#include "FileMgr.h" +#include "Text.h" +#include "Pad.h" +#include "Timer.h" +#include "DMAudio.h" +#include "ControllerConfig.h" +#include "Frontend.h" +#include "Game.h" +#include "PCSave.h" +#include "MemoryCard.h" +#include "Sprite2d.h" +#include "AnimViewer.h" +#include "Font.h" +#include "MemoryMgr.h" + +// This is defined on project-level, via premake5 or cmake +#ifdef GET_KEYBOARD_INPUT_FROM_X11 +#include +#include +#define GLFW_EXPOSE_NATIVE_X11 +#include +#endif + +#ifdef _WIN32 +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#endif + +#define MAX_SUBSYSTEMS (16) + +rw::EngineOpenParams openParams; + +static RwBool ForegroundApp = TRUE; +static RwBool WindowIconified = FALSE; +static RwBool WindowFocused = TRUE; + +static RwBool RwInitialised = FALSE; + +static RwSubSystemInfo GsubSysInfo[MAX_SUBSYSTEMS]; +static RwInt32 GnumSubSystems = 0; +static RwInt32 GcurSel = 0, GcurSelVM = 0; + +static RwBool useDefault; + +// What is that for anyway? +#ifndef IMPROVED_VIDEOMODE +static RwBool defaultFullscreenRes = TRUE; +#else +static RwBool defaultFullscreenRes = FALSE; +static RwInt32 bestWndMode = -1; +#endif + +static psGlobalType PsGlobal; + + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +size_t _dwMemAvailPhys; +RwUInt32 gGameState; + +#ifdef DETECT_JOYSTICK_MENU +char gSelectedJoystickName[128] = ""; +#endif + +/* + ***************************************************************************** + */ +void _psCreateFolder(const char *path) +{ +#ifdef _WIN32 + HANDLE hfle = CreateFile(path, GENERIC_READ, + FILE_SHARE_READ, + nil, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, + nil); + + if ( hfle == INVALID_HANDLE_VALUE ) + CreateDirectory(path, nil); + else + CloseHandle(hfle); +#else + struct stat info; + char fullpath[PATH_MAX]; + realpath(path, fullpath); + + if (lstat(fullpath, &info) != 0) { + if (errno == ENOENT || (errno != EACCES && !S_ISDIR(info.st_mode))) { + mkdir(fullpath, 0755); + } + } +#endif +} + +/* + ***************************************************************************** + */ +const char *_psGetUserFilesFolder() +{ +#if defined USE_MY_DOCUMENTS && defined _WIN32 + HKEY hKey = NULL; + + static CHAR szUserFiles[256]; + + if ( RegOpenKeyEx(HKEY_CURRENT_USER, + REGSTR_PATH_SPECIAL_FOLDERS, + REG_OPTION_RESERVED, + KEY_READ, + &hKey) == ERROR_SUCCESS ) + { + DWORD KeyType; + DWORD KeycbData = sizeof(szUserFiles); + if ( RegQueryValueEx(hKey, + "Personal", + NULL, + &KeyType, + (LPBYTE)szUserFiles, + &KeycbData) == ERROR_SUCCESS ) + { + RegCloseKey(hKey); + strcat(szUserFiles, "\\GTA3 User Files"); + _psCreateFolder(szUserFiles); + return szUserFiles; + } + + RegCloseKey(hKey); + } + + strcpy(szUserFiles, "data"); + return szUserFiles; +#else + static char szUserFiles[256]; + strcpy(szUserFiles, "userfiles"); + _psCreateFolder(szUserFiles); + return szUserFiles; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psCameraBeginUpdate(RwCamera *camera) +{ + if ( !RwCameraBeginUpdate(Scene.camera) ) + { + ForegroundApp = FALSE; + RsEventHandler(rsACTIVATE, (void *)FALSE); + return FALSE; + } + + return TRUE; +} + +/* + ***************************************************************************** + */ +void +psCameraShowRaster(RwCamera *camera) +{ + if (CMenuManager::m_PrefsVsync) + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPWAITVSYNC); + else + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPDONTWAIT); + + return; +} + +/* + ***************************************************************************** + */ +RwImage * +psGrabScreen(RwCamera *pCamera) +{ +#ifndef LIBRW + RwRaster *pRaster = RwCameraGetRaster(pCamera); + if (RwImage *pImage = RwImageCreate(pRaster->width, pRaster->height, 32)) { + RwImageAllocatePixels(pImage); + RwImageSetFromRaster(pImage, pRaster); + return pImage; + } +#else + rw::Image *image = RwCameraGetRaster(pCamera)->toImage(); + image->removeMask(); + if(image) + return image; +#endif + return nil; +} + +/* + ***************************************************************************** + */ +#ifdef _WIN32 +#pragma comment( lib, "Winmm.lib" ) // Needed for time +RwUInt32 +psTimer(void) +{ + RwUInt32 time; + + TIMECAPS TimeCaps; + + timeGetDevCaps(&TimeCaps, sizeof(TIMECAPS)); + + timeBeginPeriod(TimeCaps.wPeriodMin); + + time = (RwUInt32) timeGetTime(); + + timeEndPeriod(TimeCaps.wPeriodMin); + + return time; +} +#else +double +psTimer(void) +{ + struct timespec start; +#if defined(CLOCK_MONOTONIC_RAW) + clock_gettime(CLOCK_MONOTONIC_RAW, &start); +#elif defined(CLOCK_MONOTONIC_FAST) + clock_gettime(CLOCK_MONOTONIC_FAST, &start); +#else + clock_gettime(CLOCK_MONOTONIC, &start); +#endif + return start.tv_sec * 1000.0 + start.tv_nsec/1000000.0; +} +#endif + + +/* + ***************************************************************************** + */ +void +psMouseSetPos(RwV2d *pos) +{ + glfwSetCursorPos(PSGLOBAL(window), pos->x, pos->y); + + PSGLOBAL(lastMousePos.x) = (RwInt32)pos->x; + + PSGLOBAL(lastMousePos.y) = (RwInt32)pos->y; + + return; +} + +/* + ***************************************************************************** + */ +RwMemoryFunctions* +psGetMemoryFunctions(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + return &memFuncs; +#else + return nil; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psInstallFileSystem(void) +{ + return (TRUE); +} + + +/* + ***************************************************************************** + */ +RwBool +psNativeTextureSupport(void) +{ + return true; +} + +/* + ***************************************************************************** + */ +#ifdef UNDER_CE +#define CMDSTR LPWSTR +#else +#define CMDSTR LPSTR +#endif + +/* + ***************************************************************************** + */ + +#ifdef __SWITCH__ + +static HidVibrationValue SwitchVibrationValues[2]; +static HidVibrationDeviceHandle SwitchVibrationDeviceHandles[2][2]; +static HidVibrationDeviceHandle SwitchVibrationDeviceGC; + +static PadState SwitchPad; + +static Result HidInitializationResult[2]; +static Result HidInitializationGCResult; + +static void _psInitializeVibration() +{ + HidInitializationResult[0] = hidInitializeVibrationDevices(SwitchVibrationDeviceHandles[0], 2, HidNpadIdType_Handheld, HidNpadStyleTag_NpadHandheld); + if(R_FAILED(HidInitializationResult[0])) { + printf("Failed to initialize VibrationDevice for Handheld Mode\n"); + } + HidInitializationResult[1] = hidInitializeVibrationDevices(SwitchVibrationDeviceHandles[1], 2, HidNpadIdType_No1, HidNpadStyleSet_NpadFullCtrl); + if(R_FAILED(HidInitializationResult[1])) { + printf("Failed to initialize VibrationDevice for Detached Mode\n"); + } + HidInitializationGCResult = hidInitializeVibrationDevices(&SwitchVibrationDeviceGC, 1, HidNpadIdType_No1, HidNpadStyleTag_NpadGc); + if(R_FAILED(HidInitializationResult[1])) { + printf("Failed to initialize VibrationDevice for GC Mode\n"); + } + + SwitchVibrationValues[0].freq_low = 160.0f; + SwitchVibrationValues[0].freq_high = 320.0f; + + padConfigureInput(1, HidNpadStyleSet_NpadFullCtrl); + padInitializeDefault(&SwitchPad); +} + +static void _psHandleVibration() +{ + padUpdate(&SwitchPad); + + uint8 target_device = padIsHandheld(&SwitchPad) ? 0 : 1; + + if(R_SUCCEEDED(HidInitializationResult[target_device])) { + CPad* pad = CPad::GetPad(0); + + // value conversion based on SDL2 switch port + SwitchVibrationValues[0].amp_high = SwitchVibrationValues[0].amp_low = pad->ShakeFreq == 0 ? 0.0f : 320.0f; + SwitchVibrationValues[0].freq_low = pad->ShakeFreq == 0.0 ? 160.0f : (float)pad->ShakeFreq * 1.26f; + SwitchVibrationValues[0].freq_high = pad->ShakeFreq == 0.0 ? 320.0f : (float)pad->ShakeFreq * 1.26f; + + if (pad->ShakeDur < CTimer::GetTimeStepInMilliseconds()) + pad->ShakeDur = 0; + else + pad->ShakeDur -= CTimer::GetTimeStepInMilliseconds(); + if (pad->ShakeDur == 0) pad->ShakeFreq = 0; + + + if(target_device == 1 && R_SUCCEEDED(HidInitializationGCResult)) { + // gamecube rumble + hidSendVibrationGcErmCommand(SwitchVibrationDeviceGC, pad->ShakeFreq > 0 ? HidVibrationGcErmCommand_Start : HidVibrationGcErmCommand_Stop); + } + + memcpy(&SwitchVibrationValues[1], &SwitchVibrationValues[0], sizeof(HidVibrationValue)); + hidSendVibrationValues(SwitchVibrationDeviceHandles[target_device], SwitchVibrationValues, 2); + } +} +#else +static void _psInitializeVibration() {} +static void _psHandleVibration() {} +#endif + +/* + ***************************************************************************** + */ +RwBool +psInitialize(void) +{ + PsGlobal.lastMousePos.x = PsGlobal.lastMousePos.y = 0.0f; + + RsGlobal.ps = &PsGlobal; + + PsGlobal.fullScreen = FALSE; + PsGlobal.cursorIsInWindow = FALSE; + WindowFocused = TRUE; + WindowIconified = FALSE; + + PsGlobal.joy1id = -1; + PsGlobal.joy2id = -1; + + CFileMgr::Initialise(); + +#ifdef PS2_MENU + CPad::Initialise(); + CPad::GetPad(0)->Mode = 0; + + CGame::frenchGame = false; + CGame::germanGame = false; + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + +#ifndef _WIN32 + // Mandatory for Linux(Unix? Posix?) to set lang. to environment lang. + setlocale(LC_ALL, ""); + + char *systemLang, *keyboardLang; + + systemLang = setlocale (LC_ALL, NULL); + keyboardLang = setlocale (LC_CTYPE, NULL); + + short lang; + lang = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH : + !strncmp(systemLang, "de_",3) ? LANG_GERMAN : + !strncmp(systemLang, "en_",3) ? LANG_ENGLISH : + !strncmp(systemLang, "it_",3) ? LANG_ITALIAN : + !strncmp(systemLang, "es_",3) ? LANG_SPANISH : + LANG_OTHER; +#else + WORD lang = PRIMARYLANGID(GetSystemDefaultLCID()); +#endif + + if ( lang == LANG_ITALIAN ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + else if ( lang == LANG_SPANISH ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + else if ( lang == LANG_GERMAN ) + { + CGame::germanGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + } + else if ( lang == LANG_FRENCH ) + { + CGame::frenchGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + } + else + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + + TheMemoryCard.Init(); +#else + C_PcSave::SetSaveDirectory(_psGetUserFilesFolder()); + + InitialiseLanguage(); + +#if GTA_VERSION < GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + _psInitializeVibration(); + + gGameState = GS_START_UP; + TRACE("gGameState = GS_START_UP"); +#ifdef _WIN32 + OSVERSIONINFO verInfo; + verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + GetVersionEx(&verInfo); + + _dwOperatingSystemVersion = OS_WIN95; + + if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + if ( verInfo.dwMajorVersion == 4 ) + { + debug("Operating System is WinNT\n"); + _dwOperatingSystemVersion = OS_WINNT; + } + else if ( verInfo.dwMajorVersion == 5 ) + { + debug("Operating System is Win2000\n"); + _dwOperatingSystemVersion = OS_WIN2000; + } + else if ( verInfo.dwMajorVersion > 5 ) + { + debug("Operating System is WinXP or greater\n"); + _dwOperatingSystemVersion = OS_WINXP; + } + } + else if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) + { + if ( verInfo.dwMajorVersion > 4 || verInfo.dwMajorVersion == 4 && verInfo.dwMinorVersion != 0 ) + { + debug("Operating System is Win98\n"); + _dwOperatingSystemVersion = OS_WIN98; + } + else + { + debug("Operating System is Win95\n"); + _dwOperatingSystemVersion = OS_WIN95; + } + } +#else + _dwOperatingSystemVersion = OS_WINXP; // To fool other classes +#endif + + +#ifndef PS2_MENU + +#if GTA_VERSION >= GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + +#ifdef _WIN32 + MEMORYSTATUS memstats; + GlobalMemoryStatus(&memstats); + + _dwMemAvailPhys = memstats.dwAvailPhys; + + debug("Physical memory size %u\n", memstats.dwTotalPhys); + debug("Available physical memory %u\n", memstats.dwAvailPhys); +#elif defined (__APPLE__) + uint64_t size = 0; + uint64_t page_size = 0; + size_t uint64_len = sizeof(uint64_t); + size_t ull_len = sizeof(unsigned long long); + sysctl((int[]){CTL_HW, HW_PAGESIZE}, 2, &page_size, &ull_len, NULL, 0); + sysctl((int[]){CTL_HW, HW_MEMSIZE}, 2, &size, &uint64_len, NULL, 0); + vm_statistics_data_t vm_stat; + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vm_stat, &count); + _dwMemAvailPhys = (uint64_t)(vm_stat.free_count * page_size); + debug("Physical memory size %llu\n", _dwMemAvailPhys); + debug("Available physical memory %llu\n", size); +#elif defined (__SWITCH__) + svcGetInfo(&_dwMemAvailPhys, InfoType_UsedMemorySize, CUR_PROCESS_HANDLE, 0); + debug("Physical memory size %llu\n", _dwMemAvailPhys); +#else + struct sysinfo systemInfo; + sysinfo(&systemInfo); + _dwMemAvailPhys = systemInfo.freeram; + debug("Physical memory size %u\n", systemInfo.totalram); + debug("Available physical memory %u\n", systemInfo.freeram); +#endif + + TheText.Unload(); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +void +psTerminate(void) +{ + return; +} + +/* + ***************************************************************************** + */ +static RwChar **_VMList; + +RwInt32 _psGetNumVideModes() +{ + return RwEngineGetNumVideoModes(); +} + +/* + ***************************************************************************** + */ +RwBool _psFreeVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + numModes = _psGetNumVideModes(); + + if ( _VMList == nil ) + return TRUE; + + for ( i = 0; i < numModes; i++ ) + { + RwFree(_VMList[i]); + } + + RwFree(_VMList); + + _VMList = nil; + + return TRUE; +} + +/* + ***************************************************************************** + */ +RwChar **_psGetVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + if ( _VMList != nil ) + { + return _VMList; + } + + numModes = RwEngineGetNumVideoModes(); + + _VMList = (RwChar **)RwCalloc(numModes, sizeof(RwChar*)); + + for ( i = 0; i < numModes; i++ ) + { + RwVideoMode vm; + + RwEngineGetVideoModeInfo(&vm, i); + + if ( vm.flags & rwVIDEOMODEEXCLUSIVE ) + { + _VMList[i] = (RwChar*)RwCalloc(100, sizeof(RwChar)); + rwsprintf(_VMList[i],"%d X %d X %d", vm.width, vm.height, vm.depth); + } + else + _VMList[i] = nil; + } + + return _VMList; +} + +/* + ***************************************************************************** + */ +void _psSelectScreenVM(RwInt32 videoMode) +{ + RwTexDictionarySetCurrent( nil ); + + FrontEndMenuManager.UnloadTextures(); + + if (!_psSetVideoMode(RwEngineGetCurrentSubSystem(), videoMode)) + { + RsGlobal.quit = TRUE; + + printf("ERROR: Failed to select new screen resolution\n"); + } + else + FrontEndMenuManager.LoadAllTextures(); +} + +/* + ***************************************************************************** + */ + +RwBool IsForegroundApp() +{ + return !!ForegroundApp; +} +/* +UINT GetBestRefreshRate(UINT width, UINT height, UINT depth) +{ + LPDIRECT3D8 d3d = Direct3DCreate8(D3D_SDK_VERSION); + + ASSERT(d3d != nil); + + UINT refreshRate = INT_MAX; + D3DFORMAT format; + + if ( depth == 32 ) + format = D3DFMT_X8R8G8B8; + else if ( depth == 24 ) + format = D3DFMT_R8G8B8; + else + format = D3DFMT_R5G6B5; + + UINT modeCount = d3d->GetAdapterModeCount(GcurSel); + + for ( UINT i = 0; i < modeCount; i++ ) + { + D3DDISPLAYMODE mode; + + d3d->EnumAdapterModes(GcurSel, i, &mode); + + if ( mode.Width == width && mode.Height == height && mode.Format == format ) + { + if ( mode.RefreshRate == 0 ) + return 0; + + if ( mode.RefreshRate < refreshRate && mode.RefreshRate >= 60 ) + refreshRate = mode.RefreshRate; + } + } + +#ifdef FIX_BUGS + d3d->Release(); +#endif + + if ( refreshRate == -1 ) + return -1; + + return refreshRate; +} +*/ +/* + ***************************************************************************** + */ +RwBool +psSelectDevice() +{ + RwVideoMode vm; + RwInt32 subSysNum; + RwInt32 AutoRenderer = 0; + + + RwBool modeFound = FALSE; + + if ( !useDefault ) + { + GnumSubSystems = RwEngineGetNumSubSystems(); + if ( !GnumSubSystems ) + { + return FALSE; + } + + /* Just to be sure ... */ + GnumSubSystems = (GnumSubSystems > MAX_SUBSYSTEMS) ? MAX_SUBSYSTEMS : GnumSubSystems; + + /* Get the names of all the sub systems */ + for (subSysNum = 0; subSysNum < GnumSubSystems; subSysNum++) + { + RwEngineGetSubSystemInfo(&GsubSysInfo[subSysNum], subSysNum); + } + + /* Get the default selection */ + GcurSel = RwEngineGetCurrentSubSystem(); +#ifdef IMPROVED_VIDEOMODE + if(FrontEndMenuManager.m_nPrefsSubsystem < GnumSubSystems) + GcurSel = FrontEndMenuManager.m_nPrefsSubsystem; +#endif + } + + /* Set the driver to use the correct sub system */ + if (!RwEngineSetSubSystem(GcurSel)) + { + return FALSE; + } + +#ifdef IMPROVED_VIDEOMODE + FrontEndMenuManager.m_nPrefsSubsystem = GcurSel; +#endif + +#ifndef IMPROVED_VIDEOMODE + if ( !useDefault ) + { + if ( _psGetVideoModeList()[FrontEndMenuManager.m_nDisplayVideoMode] && FrontEndMenuManager.m_nDisplayVideoMode ) + { + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + GcurSelVM = FrontEndMenuManager.m_nDisplayVideoMode; + } + else + { +#ifdef DEFAULT_NATIVE_RESOLUTION + // get the native video mode + HDC hDevice = GetDC(NULL); + int w = GetDeviceCaps(hDevice, HORZRES); + int h = GetDeviceCaps(hDevice, VERTRES); + int d = GetDeviceCaps(hDevice, BITSPIXEL); +#else + const int w = 640; + const int h = 480; + const int d = 16; +#endif + while ( !modeFound && GcurSelVM < RwEngineGetNumVideoModes() ) + { + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + if ( defaultFullscreenRes && vm.width != w + || vm.height != h + || vm.depth != d + || !(vm.flags & rwVIDEOMODEEXCLUSIVE) ) + ++GcurSelVM; + else + modeFound = TRUE; + } + + if ( !modeFound ) + { +#ifdef DEFAULT_NATIVE_RESOLUTION + GcurSelVM = 1; +#else + printf("WARNING: Cannot find 640x480 video mode, selecting device cancelled\n"); + return FALSE; +#endif + } + } + } +#else + if ( !useDefault ) + { + if(FrontEndMenuManager.m_nPrefsWidth == 0 || + FrontEndMenuManager.m_nPrefsHeight == 0 || + FrontEndMenuManager.m_nPrefsDepth == 0){ + // Defaults if nothing specified + const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + FrontEndMenuManager.m_nPrefsWidth = mode->width; + FrontEndMenuManager.m_nPrefsHeight = mode->height; + FrontEndMenuManager.m_nPrefsDepth = 32; + FrontEndMenuManager.m_nPrefsWindowed = 0; + } + + // Find the videomode that best fits what we got from the settings file + RwInt32 bestFsMode = -1; + RwInt32 bestWidth = -1; + RwInt32 bestHeight = -1; + RwInt32 bestDepth = -1; + for(GcurSelVM = 0; GcurSelVM < RwEngineGetNumVideoModes(); GcurSelVM++){ + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + if (!(vm.flags & rwVIDEOMODEEXCLUSIVE)){ + bestWndMode = GcurSelVM; + } else { + // try the largest one that isn't larger than what we wanted + if(vm.width >= bestWidth && vm.width <= FrontEndMenuManager.m_nPrefsWidth && + vm.height >= bestHeight && vm.height <= FrontEndMenuManager.m_nPrefsHeight && + vm.depth >= bestDepth && vm.depth <= FrontEndMenuManager.m_nPrefsDepth){ + bestWidth = vm.width; + bestHeight = vm.height; + bestDepth = vm.depth; + bestFsMode = GcurSelVM; + } + } + } + + if(bestFsMode < 0){ + printf("WARNING: Cannot find desired video mode, selecting device cancelled\n"); + return FALSE; + } + GcurSelVM = bestFsMode; + + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + + FrontEndMenuManager.m_nSelectedScreenMode = FrontEndMenuManager.m_nPrefsWindowed; + } +#endif + + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + +#ifdef IMPROVED_VIDEOMODE + if (FrontEndMenuManager.m_nPrefsWindowed) + GcurSelVM = bestWndMode; + + // Now GcurSelVM is 0 but vm has sizes(and fullscreen flag) of the video mode we want, that's why we changed the rwVIDEOMODEEXCLUSIVE conditions below + FrontEndMenuManager.m_nPrefsWidth = vm.width; + FrontEndMenuManager.m_nPrefsHeight = vm.height; + FrontEndMenuManager.m_nPrefsDepth = vm.depth; +#endif + +#ifndef PS2_MENU + FrontEndMenuManager.m_nCurrOption = 0; +#endif + + /* Set up the video mode and set the apps window + * dimensions to match */ + if (!RwEngineSetVideoMode(GcurSelVM)) + { + return FALSE; + } + /* + TODO + if (vm.flags & rwVIDEOMODEEXCLUSIVE) + { + debug("%dx%dx%d", vm.width, vm.height, vm.depth); + + UINT refresh = GetBestRefreshRate(vm.width, vm.height, vm.depth); + + if ( refresh != (UINT)-1 ) + { + debug("refresh %d", refresh); + RwD3D8EngineSetRefreshRate((RwUInt32)refresh); + } + } + */ +#ifndef IMPROVED_VIDEOMODE + if (vm.flags & rwVIDEOMODEEXCLUSIVE) + { + RsGlobal.maximumWidth = vm.width; + RsGlobal.maximumHeight = vm.height; + RsGlobal.width = vm.width; + RsGlobal.height = vm.height; + + PSGLOBAL(fullScreen) = TRUE; + } +#else + RsGlobal.maximumWidth = FrontEndMenuManager.m_nPrefsWidth; + RsGlobal.maximumHeight = FrontEndMenuManager.m_nPrefsHeight; + RsGlobal.width = FrontEndMenuManager.m_nPrefsWidth; + RsGlobal.height = FrontEndMenuManager.m_nPrefsHeight; + + PSGLOBAL(fullScreen) = !FrontEndMenuManager.m_nPrefsWindowed; +#endif + +#ifdef MULTISAMPLING + RwD3D8EngineSetMultiSamplingLevels(1 << FrontEndMenuManager.m_nPrefsMSAALevel); +#endif + return TRUE; +} + +#ifndef GET_KEYBOARD_INPUT_FROM_X11 +void keypressCB(GLFWwindow* window, int key, int scancode, int action, int mods); +#endif +void resizeCB(GLFWwindow* window, int width, int height); +void scrollCB(GLFWwindow* window, double xoffset, double yoffset); +void cursorCB(GLFWwindow* window, double xpos, double ypos); +void cursorEnterCB(GLFWwindow* window, int entered); +void windowFocusCB(GLFWwindow* window, int focused); +void windowIconifyCB(GLFWwindow* window, int iconified); +void joysChangeCB(int jid, int event); + +bool IsThisJoystickBlacklisted(int i) +{ +#ifndef DETECT_JOYSTICK_MENU + return false; +#else + if (glfwJoystickIsGamepad(i)) + return false; + + const char* joyname = glfwGetJoystickName(i); + + if (gSelectedJoystickName[0] != '\0' && + strncmp(joyname, gSelectedJoystickName, strlen(gSelectedJoystickName)) == 0) + return false; + + return true; +#endif +} + +void _InputInitialiseJoys() +{ + PSGLOBAL(joy1id) = -1; + PSGLOBAL(joy2id) = -1; + + // Load our gamepad mappings. +#define SDL_GAMEPAD_DB_PATH "gamecontrollerdb.txt" + FILE *f = fopen(SDL_GAMEPAD_DB_PATH, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + size_t fsize = ftell(f); + fseek(f, 0, SEEK_SET); + + char *db = (char*)malloc(fsize + 1); + if (fread(db, 1, fsize, f) == fsize) { + db[fsize] = '\0'; + + if (glfwUpdateGamepadMappings(db) == GLFW_FALSE) + Error("glfwUpdateGamepadMappings didn't succeed, check " SDL_GAMEPAD_DB_PATH ".\n"); + } else + Error("fread on " SDL_GAMEPAD_DB_PATH " wasn't successful.\n"); + + free(db); + fclose(f); + } else + printf("You don't seem to have copied " SDL_GAMEPAD_DB_PATH " file from re3/gamefiles to GTA3 directory. Some gamepads may not be recognized.\n"); + +#undef SDL_GAMEPAD_DB_PATH + + // But always overwrite it with the one in SDL_GAMECONTROLLERCONFIG. + char const* EnvControlConfig = getenv("SDL_GAMECONTROLLERCONFIG"); + if (EnvControlConfig != nil) { + glfwUpdateGamepadMappings(EnvControlConfig); + } + + for (int i = 0; i <= GLFW_JOYSTICK_LAST; i++) { + if (glfwJoystickPresent(i) && !IsThisJoystickBlacklisted(i)) { + if (PSGLOBAL(joy1id) == -1) + PSGLOBAL(joy1id) = i; + else if (PSGLOBAL(joy2id) == -1) + PSGLOBAL(joy2id) = i; + else + break; + } + } + + if (PSGLOBAL(joy1id) != -1) { + int count; + glfwGetJoystickButtons(PSGLOBAL(joy1id), &count); +#ifdef DETECT_JOYSTICK_MENU + strcpy(gSelectedJoystickName, glfwGetJoystickName(PSGLOBAL(joy1id))); +#endif + ControlsManager.InitDefaultControlConfigJoyPad(count); + } +} + +long _InputInitialiseMouse() +{ + glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + return 0; +} + +void psPostRWinit(void) +{ + RwVideoMode vm; + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + glfwSetFramebufferSizeCallback(PSGLOBAL(window), resizeCB); +#ifndef IGNORE_MOUSE_KEYBOARD +#ifndef GET_KEYBOARD_INPUT_FROM_X11 + glfwSetKeyCallback(PSGLOBAL(window), keypressCB); +#endif + glfwSetScrollCallback(PSGLOBAL(window), scrollCB); + glfwSetCursorPosCallback(PSGLOBAL(window), cursorCB); + glfwSetCursorEnterCallback(PSGLOBAL(window), cursorEnterCB); +#endif + glfwSetWindowIconifyCallback(PSGLOBAL(window), windowIconifyCB); + glfwSetWindowFocusCallback(PSGLOBAL(window), windowFocusCB); + glfwSetJoystickCallback(joysChangeCB); + + _InputInitialiseJoys(); + _InputInitialiseMouse(); + + if(!(vm.flags & rwVIDEOMODEEXCLUSIVE)) + glfwSetWindowSize(PSGLOBAL(window), RsGlobal.maximumWidth, RsGlobal.maximumHeight); + + // Make sure all keys are released + CPad::GetPad(0)->Clear(true); + CPad::GetPad(1)->Clear(true); +} + +/* + ***************************************************************************** + */ +RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode) +{ + RwInitialised = FALSE; + + RsEventHandler(rsRWTERMINATE, nil); + + GcurSel = subSystem; + GcurSelVM = videoMode; + + useDefault = TRUE; + + if ( RsEventHandler(rsRWINITIALIZE, &openParams) == rsEVENTERROR ) + return FALSE; + + RwInitialised = TRUE; + useDefault = FALSE; + + RwRect r; + + r.x = 0; + r.y = 0; + r.w = RsGlobal.maximumWidth; + r.h = RsGlobal.maximumHeight; + + RsEventHandler(rsCAMERASIZE, &r); + + psPostRWinit(); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +static RwChar ** +CommandLineToArgv(RwChar *cmdLine, RwInt32 *argCount) +{ + RwInt32 numArgs = 0; + RwBool inArg, inString; + RwInt32 i, len; + RwChar *res, *str, **aptr; + + len = strlen(cmdLine); + + /* + * Count the number of arguments... + */ + inString = FALSE; + inArg = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if( inArg ) + { + inArg = FALSE; + + numArgs++; + } + } + else if( !inArg ) + { + inArg = TRUE; + } + } + + /* + * Allocate memory for result... + */ + res = (RwChar *)malloc(sizeof(RwChar *) * numArgs + len + 1); + str = res + sizeof(RwChar *) * numArgs; + aptr = (RwChar **)res; + + strcpy(str, cmdLine); + + /* + * Walk through cmdLine again this time setting pointer to each arg... + */ + inArg = FALSE; + inString = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if( inArg ) + { + if( str[i-1] == '"' ) + { + str[i-1] = '\0'; + } + else + { + str[i] = '\0'; + } + + inArg = FALSE; + } + } + else if( !inArg && cmdLine[i] != '"' ) + { + inArg = TRUE; + + *aptr++ = &str[i]; + } + } + + *argCount = numArgs; + + return (RwChar **)res; +} + +/* + ***************************************************************************** + */ +void InitialiseLanguage() +{ +#ifndef _WIN32 + // Mandatory for Linux(Unix? Posix?) to set lang. to environment lang. + setlocale(LC_ALL, ""); + + char *systemLang, *keyboardLang; + + systemLang = setlocale (LC_ALL, NULL); + keyboardLang = setlocale (LC_CTYPE, NULL); + + short primUserLCID, primSystemLCID; + primUserLCID = primSystemLCID = !strncmp(systemLang, "fr_",3) ? LANG_FRENCH : + !strncmp(systemLang, "de_",3) ? LANG_GERMAN : + !strncmp(systemLang, "en_",3) ? LANG_ENGLISH : + !strncmp(systemLang, "it_",3) ? LANG_ITALIAN : + !strncmp(systemLang, "es_",3) ? LANG_SPANISH : + LANG_OTHER; + + short primLayout = !strncmp(keyboardLang, "fr_",3) ? LANG_FRENCH : (!strncmp(keyboardLang, "de_",3) ? LANG_GERMAN : LANG_ENGLISH); + + short subUserLCID, subSystemLCID; + subUserLCID = subSystemLCID = !strncmp(systemLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER; + short subLayout = !strncmp(keyboardLang, "en_AU",5) ? SUBLANG_ENGLISH_AUS : SUBLANG_OTHER; + +#else + WORD primUserLCID = PRIMARYLANGID(GetSystemDefaultLCID()); + WORD primSystemLCID = PRIMARYLANGID(GetUserDefaultLCID()); + WORD primLayout = PRIMARYLANGID((DWORD)GetKeyboardLayout(0)); + + WORD subUserLCID = SUBLANGID(GetSystemDefaultLCID()); + WORD subSystemLCID = SUBLANGID(GetUserDefaultLCID()); + WORD subLayout = SUBLANGID((DWORD)GetKeyboardLayout(0)); +#endif + if ( primUserLCID == LANG_GERMAN + || primSystemLCID == LANG_GERMAN + || primLayout == LANG_GERMAN ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::germanGame = true; + } + + if ( primUserLCID == LANG_FRENCH + || primSystemLCID == LANG_FRENCH + || primLayout == LANG_FRENCH ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::frenchGame = true; + } + + if ( subUserLCID == SUBLANG_ENGLISH_AUS + || subSystemLCID == SUBLANG_ENGLISH_AUS + || subLayout == SUBLANG_ENGLISH_AUS ) + CGame::noProstitutes = true; + +#ifdef NASTY_GAME + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + CGame::noProstitutes = false; +#endif + + int32 lang; + + switch ( primSystemLCID ) + { + case LANG_GERMAN: + { + lang = LANG_GERMAN; + break; + } + case LANG_FRENCH: + { + lang = LANG_FRENCH; + break; + } + case LANG_SPANISH: + { + lang = LANG_SPANISH; + break; + } + case LANG_ITALIAN: + { + lang = LANG_ITALIAN; + break; + } + default: + { + lang = ( subSystemLCID == SUBLANG_ENGLISH_AUS ) ? -99 : LANG_ENGLISH; + break; + } + } + + CMenuManager::OS_Language = primUserLCID; + + switch ( lang ) + { + case LANG_GERMAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + break; + } + case LANG_SPANISH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + break; + } + case LANG_FRENCH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + break; + } + case LANG_ITALIAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + break; + } + default: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + break; + } + } + +#ifndef _WIN32 + // TODO this is needed for strcasecmp to work correctly across all languages, but can these cause other problems?? + setlocale(LC_CTYPE, "C"); + setlocale(LC_COLLATE, "C"); + setlocale(LC_NUMERIC, "C"); +#endif + + TheText.Unload(); + TheText.Load(); +} + +/* + ***************************************************************************** + */ + +void HandleExit() +{ +#ifdef _WIN32 + MSG message; + while ( PeekMessage(&message, nil, 0U, 0U, PM_REMOVE|PM_NOYIELD) ) + { + if( message.message == WM_QUIT ) + { + RsGlobal.quit = TRUE; + } + else + { + TranslateMessage(&message); + DispatchMessage(&message); + } + } +#else + // We now handle terminate message always, why handle on some cases? + return; +#endif +} + +#ifndef _WIN32 +void terminateHandler(int sig, siginfo_t *info, void *ucontext) { + RsGlobal.quit = TRUE; +} + +#ifdef FLUSHABLE_STREAMING +void dummyHandler(int sig){ + // Don't kill the app pls +} +#endif +#endif + +void resizeCB(GLFWwindow* window, int width, int height) { + /* + * Handle event to ensure window contents are displayed during re-size + * as this can be disabled by the user, then if there is not enough + * memory things don't work. + */ + /* redraw window */ + + if (RwInitialised && gGameState == GS_PLAYING_GAME) + { + RsEventHandler(rsIDLE, (void *)TRUE); + } + + if (RwInitialised && height > 0 && width > 0) { + RwRect r; + + // TODO fix artifacts of resizing with mouse + RsGlobal.maximumHeight = height; + RsGlobal.maximumWidth = width; + + r.x = 0; + r.y = 0; + r.w = width; + r.h = height; + + RsEventHandler(rsCAMERASIZE, &r); + } +// glfwSetWindowPos(window, 0, 0); +} + +void scrollCB(GLFWwindow* window, double xoffset, double yoffset) { + PSGLOBAL(mouseWheel) = yoffset; +} + +bool lshiftStatus = false; +bool rshiftStatus = false; + +#ifndef GET_KEYBOARD_INPUT_FROM_X11 +int keymap[GLFW_KEY_LAST + 1]; + +static void +initkeymap(void) +{ + int i; + for (i = 0; i < GLFW_KEY_LAST + 1; i++) + keymap[i] = rsNULL; + + keymap[GLFW_KEY_SPACE] = ' '; + keymap[GLFW_KEY_APOSTROPHE] = '\''; + keymap[GLFW_KEY_COMMA] = ','; + keymap[GLFW_KEY_MINUS] = '-'; + keymap[GLFW_KEY_PERIOD] = '.'; + keymap[GLFW_KEY_SLASH] = '/'; + keymap[GLFW_KEY_0] = '0'; + keymap[GLFW_KEY_1] = '1'; + keymap[GLFW_KEY_2] = '2'; + keymap[GLFW_KEY_3] = '3'; + keymap[GLFW_KEY_4] = '4'; + keymap[GLFW_KEY_5] = '5'; + keymap[GLFW_KEY_6] = '6'; + keymap[GLFW_KEY_7] = '7'; + keymap[GLFW_KEY_8] = '8'; + keymap[GLFW_KEY_9] = '9'; + keymap[GLFW_KEY_SEMICOLON] = ';'; + keymap[GLFW_KEY_EQUAL] = '='; + keymap[GLFW_KEY_A] = 'A'; + keymap[GLFW_KEY_B] = 'B'; + keymap[GLFW_KEY_C] = 'C'; + keymap[GLFW_KEY_D] = 'D'; + keymap[GLFW_KEY_E] = 'E'; + keymap[GLFW_KEY_F] = 'F'; + keymap[GLFW_KEY_G] = 'G'; + keymap[GLFW_KEY_H] = 'H'; + keymap[GLFW_KEY_I] = 'I'; + keymap[GLFW_KEY_J] = 'J'; + keymap[GLFW_KEY_K] = 'K'; + keymap[GLFW_KEY_L] = 'L'; + keymap[GLFW_KEY_M] = 'M'; + keymap[GLFW_KEY_N] = 'N'; + keymap[GLFW_KEY_O] = 'O'; + keymap[GLFW_KEY_P] = 'P'; + keymap[GLFW_KEY_Q] = 'Q'; + keymap[GLFW_KEY_R] = 'R'; + keymap[GLFW_KEY_S] = 'S'; + keymap[GLFW_KEY_T] = 'T'; + keymap[GLFW_KEY_U] = 'U'; + keymap[GLFW_KEY_V] = 'V'; + keymap[GLFW_KEY_W] = 'W'; + keymap[GLFW_KEY_X] = 'X'; + keymap[GLFW_KEY_Y] = 'Y'; + keymap[GLFW_KEY_Z] = 'Z'; + keymap[GLFW_KEY_LEFT_BRACKET] = '['; + keymap[GLFW_KEY_BACKSLASH] = '\\'; + keymap[GLFW_KEY_RIGHT_BRACKET] = ']'; + keymap[GLFW_KEY_GRAVE_ACCENT] = '`'; + keymap[GLFW_KEY_ESCAPE] = rsESC; + keymap[GLFW_KEY_ENTER] = rsENTER; + keymap[GLFW_KEY_TAB] = rsTAB; + keymap[GLFW_KEY_BACKSPACE] = rsBACKSP; + keymap[GLFW_KEY_INSERT] = rsINS; + keymap[GLFW_KEY_DELETE] = rsDEL; + keymap[GLFW_KEY_RIGHT] = rsRIGHT; + keymap[GLFW_KEY_LEFT] = rsLEFT; + keymap[GLFW_KEY_DOWN] = rsDOWN; + keymap[GLFW_KEY_UP] = rsUP; + keymap[GLFW_KEY_PAGE_UP] = rsPGUP; + keymap[GLFW_KEY_PAGE_DOWN] = rsPGDN; + keymap[GLFW_KEY_HOME] = rsHOME; + keymap[GLFW_KEY_END] = rsEND; + keymap[GLFW_KEY_CAPS_LOCK] = rsCAPSLK; + keymap[GLFW_KEY_SCROLL_LOCK] = rsSCROLL; + keymap[GLFW_KEY_NUM_LOCK] = rsNUMLOCK; + keymap[GLFW_KEY_PRINT_SCREEN] = rsNULL; + keymap[GLFW_KEY_PAUSE] = rsPAUSE; + + keymap[GLFW_KEY_F1] = rsF1; + keymap[GLFW_KEY_F2] = rsF2; + keymap[GLFW_KEY_F3] = rsF3; + keymap[GLFW_KEY_F4] = rsF4; + keymap[GLFW_KEY_F5] = rsF5; + keymap[GLFW_KEY_F6] = rsF6; + keymap[GLFW_KEY_F7] = rsF7; + keymap[GLFW_KEY_F8] = rsF8; + keymap[GLFW_KEY_F9] = rsF9; + keymap[GLFW_KEY_F10] = rsF10; + keymap[GLFW_KEY_F11] = rsF11; + keymap[GLFW_KEY_F12] = rsF12; + keymap[GLFW_KEY_F13] = rsNULL; + keymap[GLFW_KEY_F14] = rsNULL; + keymap[GLFW_KEY_F15] = rsNULL; + keymap[GLFW_KEY_F16] = rsNULL; + keymap[GLFW_KEY_F17] = rsNULL; + keymap[GLFW_KEY_F18] = rsNULL; + keymap[GLFW_KEY_F19] = rsNULL; + keymap[GLFW_KEY_F20] = rsNULL; + keymap[GLFW_KEY_F21] = rsNULL; + keymap[GLFW_KEY_F22] = rsNULL; + keymap[GLFW_KEY_F23] = rsNULL; + keymap[GLFW_KEY_F24] = rsNULL; + keymap[GLFW_KEY_F25] = rsNULL; + keymap[GLFW_KEY_KP_0] = rsPADINS; + keymap[GLFW_KEY_KP_1] = rsPADEND; + keymap[GLFW_KEY_KP_2] = rsPADDOWN; + keymap[GLFW_KEY_KP_3] = rsPADPGDN; + keymap[GLFW_KEY_KP_4] = rsPADLEFT; + keymap[GLFW_KEY_KP_5] = rsPAD5; + keymap[GLFW_KEY_KP_6] = rsPADRIGHT; + keymap[GLFW_KEY_KP_7] = rsPADHOME; + keymap[GLFW_KEY_KP_8] = rsPADUP; + keymap[GLFW_KEY_KP_9] = rsPADPGUP; + keymap[GLFW_KEY_KP_DECIMAL] = rsPADDEL; + keymap[GLFW_KEY_KP_DIVIDE] = rsDIVIDE; + keymap[GLFW_KEY_KP_MULTIPLY] = rsTIMES; + keymap[GLFW_KEY_KP_SUBTRACT] = rsMINUS; + keymap[GLFW_KEY_KP_ADD] = rsPLUS; + keymap[GLFW_KEY_KP_ENTER] = rsPADENTER; + keymap[GLFW_KEY_KP_EQUAL] = rsNULL; + keymap[GLFW_KEY_LEFT_SHIFT] = rsLSHIFT; + keymap[GLFW_KEY_LEFT_CONTROL] = rsLCTRL; + keymap[GLFW_KEY_LEFT_ALT] = rsLALT; + keymap[GLFW_KEY_LEFT_SUPER] = rsLWIN; + keymap[GLFW_KEY_RIGHT_SHIFT] = rsRSHIFT; + keymap[GLFW_KEY_RIGHT_CONTROL] = rsRCTRL; + keymap[GLFW_KEY_RIGHT_ALT] = rsRALT; + keymap[GLFW_KEY_RIGHT_SUPER] = rsRWIN; + keymap[GLFW_KEY_MENU] = rsNULL; +} + +void +keypressCB(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + if (key >= 0 && key <= GLFW_KEY_LAST && action != GLFW_REPEAT) { + RsKeyCodes ks = (RsKeyCodes)keymap[key]; + + if (key == GLFW_KEY_LEFT_SHIFT) + lshiftStatus = action != GLFW_RELEASE; + + if (key == GLFW_KEY_RIGHT_SHIFT) + rshiftStatus = action != GLFW_RELEASE; + + if (action == GLFW_RELEASE) RsKeyboardEventHandler(rsKEYUP, &ks); + else if (action == GLFW_PRESS) RsKeyboardEventHandler(rsKEYDOWN, &ks); + } +} + +#else + +uint32 keymap[512]; // 256 ascii + 256 KeySyms between 0xff00 - 0xffff +bool keyStates[512]; +uint32 keyCodeToKeymapIndex[256]; // cache for physical keys + +#define KEY_MAP_OFFSET (0xff00 - 256) +static void +initkeymap(void) +{ + Display *display = glfwGetX11Display(); + int i; + + for (i = 0; i < ARRAY_SIZE(keymap); i++) + keymap[i] = rsNULL; + + // You can add new ASCII mappings to here freely (but beware that if right hand side of assignment isn't supported on CFont, it'll be blank/won't work on binding screen) + // Right hand side of assigments should always be uppercase counterpart of character + keymap[XK_space] = ' '; + keymap[XK_apostrophe] = '\''; + keymap[XK_ampersand] = '&'; + keymap[XK_percent] = '%'; + keymap[XK_dollar] = '$'; + keymap[XK_comma] = ','; + keymap[XK_minus] = '-'; + keymap[XK_period] = '.'; + keymap[XK_slash] = '/'; + keymap[XK_question] = '?'; + keymap[XK_exclam] = '!'; + keymap[XK_quotedbl] = '"'; + keymap[XK_colon] = ':'; + keymap[XK_semicolon] = ';'; + keymap[XK_equal] = '='; + keymap[XK_bracketleft] = '['; + keymap[XK_backslash] = '\\'; + keymap[XK_bracketright] = ']'; + keymap[XK_grave] = '`'; + keymap[XK_0] = '0'; + keymap[XK_1] = '1'; + keymap[XK_2] = '2'; + keymap[XK_3] = '3'; + keymap[XK_4] = '4'; + keymap[XK_5] = '5'; + keymap[XK_6] = '6'; + keymap[XK_7] = '7'; + keymap[XK_8] = '8'; + keymap[XK_9] = '9'; + keymap[XK_a] = 'A'; + keymap[XK_b] = 'B'; + keymap[XK_c] = 'C'; + keymap[XK_d] = 'D'; + keymap[XK_e] = 'E'; + keymap[XK_f] = 'F'; + keymap[XK_g] = 'G'; + keymap[XK_h] = 'H'; + keymap[XK_i] = 'I'; + keymap[XK_I] = 'I'; // Turkish I problem + keymap[XK_j] = 'J'; + keymap[XK_k] = 'K'; + keymap[XK_l] = 'L'; + keymap[XK_m] = 'M'; + keymap[XK_n] = 'N'; + keymap[XK_o] = 'O'; + keymap[XK_p] = 'P'; + keymap[XK_q] = 'Q'; + keymap[XK_r] = 'R'; + keymap[XK_s] = 'S'; + keymap[XK_t] = 'T'; + keymap[XK_u] = 'U'; + keymap[XK_v] = 'V'; + keymap[XK_w] = 'W'; + keymap[XK_x] = 'X'; + keymap[XK_y] = 'Y'; + keymap[XK_z] = 'Z'; + + // Some of regional but ASCII characters that GTA supports + keymap[XK_agrave] = 0x00c0; + keymap[XK_aacute] = 0x00c1; + keymap[XK_acircumflex] = 0x00c2; + keymap[XK_adiaeresis] = 0x00c4; + + keymap[XK_ae] = 0x00c6; + + keymap[XK_egrave] = 0x00c8; + keymap[XK_eacute] = 0x00c9; + keymap[XK_ecircumflex] = 0x00ca; + keymap[XK_ediaeresis] = 0x00cb; + + keymap[XK_igrave] = 0x00cc; + keymap[XK_iacute] = 0x00cd; + keymap[XK_icircumflex] = 0x00ce; + keymap[XK_idiaeresis] = 0x00cf; + + keymap[XK_ccedilla] = 0x00c7; + keymap[XK_odiaeresis] = 0x00d6; + keymap[XK_udiaeresis] = 0x00dc; + + // These are 0xff00 - 0xffff range of KeySym's, and subtracting KEY_MAP_OFFSET is needed + keymap[XK_Escape - KEY_MAP_OFFSET] = rsESC; + keymap[XK_Return - KEY_MAP_OFFSET] = rsENTER; + keymap[XK_Tab - KEY_MAP_OFFSET] = rsTAB; + keymap[XK_BackSpace - KEY_MAP_OFFSET] = rsBACKSP; + keymap[XK_Insert - KEY_MAP_OFFSET] = rsINS; + keymap[XK_Delete - KEY_MAP_OFFSET] = rsDEL; + keymap[XK_Right - KEY_MAP_OFFSET] = rsRIGHT; + keymap[XK_Left - KEY_MAP_OFFSET] = rsLEFT; + keymap[XK_Down - KEY_MAP_OFFSET] = rsDOWN; + keymap[XK_Up - KEY_MAP_OFFSET] = rsUP; + keymap[XK_Page_Up - KEY_MAP_OFFSET] = rsPGUP; + keymap[XK_Page_Down - KEY_MAP_OFFSET] = rsPGDN; + keymap[XK_Home - KEY_MAP_OFFSET] = rsHOME; + keymap[XK_End - KEY_MAP_OFFSET] = rsEND; + keymap[XK_Caps_Lock - KEY_MAP_OFFSET] = rsCAPSLK; + keymap[XK_Scroll_Lock - KEY_MAP_OFFSET] = rsSCROLL; + keymap[XK_Num_Lock - KEY_MAP_OFFSET] = rsNUMLOCK; + keymap[XK_Pause - KEY_MAP_OFFSET] = rsPAUSE; + + keymap[XK_F1 - KEY_MAP_OFFSET] = rsF1; + keymap[XK_F2 - KEY_MAP_OFFSET] = rsF2; + keymap[XK_F3 - KEY_MAP_OFFSET] = rsF3; + keymap[XK_F4 - KEY_MAP_OFFSET] = rsF4; + keymap[XK_F5 - KEY_MAP_OFFSET] = rsF5; + keymap[XK_F6 - KEY_MAP_OFFSET] = rsF6; + keymap[XK_F7 - KEY_MAP_OFFSET] = rsF7; + keymap[XK_F8 - KEY_MAP_OFFSET] = rsF8; + keymap[XK_F9 - KEY_MAP_OFFSET] = rsF9; + keymap[XK_F10 - KEY_MAP_OFFSET] = rsF10; + keymap[XK_F11 - KEY_MAP_OFFSET] = rsF11; + keymap[XK_F12 - KEY_MAP_OFFSET] = rsF12; + keymap[XK_F13 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F14 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F15 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F16 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F17 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F18 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F19 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F20 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F21 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F22 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F23 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F24 - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_F25 - KEY_MAP_OFFSET] = rsNULL; + + keymap[XK_KP_0 - KEY_MAP_OFFSET] = rsPADINS; + keymap[XK_KP_1 - KEY_MAP_OFFSET] = rsPADEND; + keymap[XK_KP_2 - KEY_MAP_OFFSET] = rsPADDOWN; + keymap[XK_KP_3 - KEY_MAP_OFFSET] = rsPADPGDN; + keymap[XK_KP_4 - KEY_MAP_OFFSET] = rsPADLEFT; + keymap[XK_KP_5 - KEY_MAP_OFFSET] = rsPAD5; + keymap[XK_KP_6 - KEY_MAP_OFFSET] = rsPADRIGHT; + keymap[XK_KP_7 - KEY_MAP_OFFSET] = rsPADHOME; + keymap[XK_KP_8 - KEY_MAP_OFFSET] = rsPADUP; + keymap[XK_KP_9 - KEY_MAP_OFFSET] = rsPADPGUP; + keymap[XK_KP_Insert - KEY_MAP_OFFSET] = rsPADINS; + keymap[XK_KP_End - KEY_MAP_OFFSET] = rsPADEND; + keymap[XK_KP_Down - KEY_MAP_OFFSET] = rsPADDOWN; + keymap[XK_KP_Page_Down - KEY_MAP_OFFSET] = rsPADPGDN; + keymap[XK_KP_Left - KEY_MAP_OFFSET] = rsPADLEFT; + keymap[XK_KP_Begin - KEY_MAP_OFFSET] = rsPAD5; + keymap[XK_KP_Right - KEY_MAP_OFFSET] = rsPADRIGHT; + keymap[XK_KP_Home - KEY_MAP_OFFSET] = rsPADHOME; + keymap[XK_KP_Up - KEY_MAP_OFFSET] = rsPADUP; + keymap[XK_KP_Page_Up - KEY_MAP_OFFSET] = rsPADPGUP; + + keymap[XK_KP_Decimal - KEY_MAP_OFFSET] = rsPADDEL; + keymap[XK_KP_Divide - KEY_MAP_OFFSET] = rsDIVIDE; + keymap[XK_KP_Multiply - KEY_MAP_OFFSET] = rsTIMES; + keymap[XK_KP_Subtract - KEY_MAP_OFFSET] = rsMINUS; + keymap[XK_KP_Add - KEY_MAP_OFFSET] = rsPLUS; + keymap[XK_KP_Enter - KEY_MAP_OFFSET] = rsPADENTER; + keymap[XK_KP_Equal - KEY_MAP_OFFSET] = rsNULL; + keymap[XK_Shift_L - KEY_MAP_OFFSET] = rsLSHIFT; + keymap[XK_Control_L - KEY_MAP_OFFSET] = rsLCTRL; + keymap[XK_Alt_L - KEY_MAP_OFFSET] = rsLALT; + keymap[XK_Super_L - KEY_MAP_OFFSET] = rsLWIN; + keymap[XK_Shift_R - KEY_MAP_OFFSET] = rsRSHIFT; + keymap[XK_Control_R - KEY_MAP_OFFSET] = rsRCTRL; + keymap[XK_Alt_R - KEY_MAP_OFFSET] = rsRALT; + keymap[XK_Super_R - KEY_MAP_OFFSET] = rsRWIN; + keymap[XK_Menu - KEY_MAP_OFFSET] = rsNULL; + + // Cache the key codes' key symbol equivelants, otherwise we will have to do it on each frame + // KeyCode is always in [0,255], and represents a physical key + + int min_keycode, max_keycode, keysyms_per_keycode; + KeySym *keymap, *origkeymap; + + char *keyboardLang = setlocale (LC_CTYPE, NULL); + setlocale(LC_CTYPE, ""); + + XDisplayKeycodes(display, &min_keycode, &max_keycode); + origkeymap = XGetKeyboardMapping(display, min_keycode, (max_keycode - min_keycode + 1), &keysyms_per_keycode); + keymap = origkeymap; + for (int i = min_keycode; i <= max_keycode; i++) { + int j, lastKeysym; + + lastKeysym = keysyms_per_keycode - 1; + while ((lastKeysym >= 0) && (keymap[lastKeysym] == NoSymbol)) + lastKeysym--; + + for (j = 0; j <= lastKeysym; j++) { + KeySym ks = keymap[j]; + + if (ks == NoSymbol) + continue; + + if (ks < 256) { + keyCodeToKeymapIndex[i] = ks; + break; + } else if (ks >= 0xff00 && ks < 0xffff) { + keyCodeToKeymapIndex[i] = ks - KEY_MAP_OFFSET; + break; + } + } + keymap += keysyms_per_keycode; + } + XFree(origkeymap); + + setlocale(LC_CTYPE, keyboardLang); +} +#undef KEY_MAP_OFFSET + +void checkKeyPresses() +{ + Display *display = glfwGetX11Display(); + char keys[32]; + XQueryKeymap(display, keys); + for (int i = 0; i < sizeof(keys); i++) { + for (int j = 0; j < 8; j++) { + KeyCode keycode = 8 * i + j; + uint32 keymapIndex = keyCodeToKeymapIndex[keycode]; + if (keymapIndex != 0) { + int rsCode = keymap[keymapIndex]; + if (rsCode == rsNULL) + continue; + + bool pressed = WindowFocused && !!(keys[i] & (1 << j)); + + // idk why R* does that + if (rsCode == rsLSHIFT) + lshiftStatus = pressed; + else if (rsCode == rsRSHIFT) + rshiftStatus = pressed; + + if (keyStates[keymapIndex] != pressed) { + if (pressed) { + RsKeyboardEventHandler(rsKEYDOWN, &rsCode); + } else { + RsKeyboardEventHandler(rsKEYUP, &rsCode); + } + } + + keyStates[keymapIndex] = pressed; + } + } + } + +} +#endif + +// R* calls that in ControllerConfig, idk why +void +_InputTranslateShiftKeyUpDown(RsKeyCodes *rs) { + RsKeyboardEventHandler(lshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsLSHIFT)); + RsKeyboardEventHandler(rshiftStatus ? rsKEYDOWN : rsKEYUP, &(*rs = rsRSHIFT)); +} + +// TODO this only works in frontend(and luckily only frontend use this). Fun fact: if I get pos manually in game, glfw reports that it's > 32000 +void +cursorCB(GLFWwindow* window, double xpos, double ypos) { + if (!FrontEndMenuManager.m_bMenuActive) + return; + + int winw, winh; + glfwGetWindowSize(PSGLOBAL(window), &winw, &winh); + FrontEndMenuManager.m_nMouseTempPosX = xpos * (RsGlobal.maximumWidth / winw); + FrontEndMenuManager.m_nMouseTempPosY = ypos * (RsGlobal.maximumHeight / winh); +} + +void +cursorEnterCB(GLFWwindow* window, int entered) { + PSGLOBAL(cursorIsInWindow) = !!entered; +} + +void +windowFocusCB(GLFWwindow* window, int focused) { + WindowFocused = !!focused; +} + +void +windowIconifyCB(GLFWwindow* window, int iconified) { + WindowIconified = !!iconified; +} + +/* + ***************************************************************************** + */ +#ifdef _WIN32 +int PASCAL +WinMain(HINSTANCE instance, + HINSTANCE prevInstance __RWUNUSED__, + CMDSTR cmdLine, + int cmdShow) +{ + + RwInt32 argc; + RwChar** argv; + SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, SPIF_SENDCHANGE); + +#ifndef MASTER + if (strstr(cmdLine, "-console")) + { + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } +#endif + +#else +int +main(int argc, char *argv[]) +{ +#endif + RwV2d pos; + RwInt32 i; + +#ifdef USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + +#if !defined(_WIN32) && !defined(__SWITCH__) + struct sigaction act; + act.sa_sigaction = terminateHandler; + act.sa_flags = SA_SIGINFO; + sigaction(SIGTERM, &act, NULL); +#ifdef FLUSHABLE_STREAMING + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_handler = dummyHandler; + sa.sa_flags = 0; + sigaction(SIGUSR1, &sa, NULL); +#endif +#endif + + /* + * Initialize the platform independent data. + * This will in turn initialize the platform specific data... + */ + if( RsEventHandler(rsINITIALIZE, nil) == rsEVENTERROR ) + { + return FALSE; + } + +#ifdef _WIN32 + /* + * Get proper command line params, cmdLine passed to us does not + * work properly under all circumstances... + */ + cmdLine = GetCommandLine(); + + /* + * Parse command line into standard (argv, argc) parameters... + */ + argv = CommandLineToArgv(cmdLine, &argc); + + + /* + * Parse command line parameters (except program name) one at + * a time BEFORE RenderWare initialization... + */ +#endif + for(i=1; iGetLeftMouseJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetEnterJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetCharJustDown(' ')) +// ++gGameState; +// else if (CPad::GetPad(0)->GetAltJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetTabJustDown()) +// ++gGameState; + + break; + } + + case GS_INIT_INTRO_MPEG: + { +//#ifndef NO_MOVIES +// CloseClip(); +// CoUninitialize(); +//#endif +// +// if (CMenuManager::OS_Language == LANG_FRENCH || CMenuManager::OS_Language == LANG_GERMAN) +// PlayMovieInWindow(cmdShow, "movies\\GTAtitlesGER.mpg"); +// else +// PlayMovieInWindow(cmdShow, "movies\\GTAtitles.mpg"); + + gGameState = GS_INTRO_MPEG; + TRACE("gGameState = GS_INTRO_MPEG;"); + break; + } + + case GS_INTRO_MPEG: + { +// CPad::UpdatePads(); +// +// if (startupDeactivate || ControlsManager.GetJoyButtonJustDown() != 0) + ++gGameState; +// else if (CPad::GetPad(0)->GetLeftMouseJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetEnterJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetCharJustDown(' ')) +// ++gGameState; +// else if (CPad::GetPad(0)->GetAltJustDown()) +// ++gGameState; +// else if (CPad::GetPad(0)->GetTabJustDown()) +// ++gGameState; + + break; + } + + case GS_INIT_ONCE: + { + //CoUninitialize(); + +#ifdef PS2_MENU + extern char version_name[64]; + if ( CGame::frenchGame || CGame::germanGame ) + LoadingScreen(NULL, version_name, "loadsc24"); + else + LoadingScreen(NULL, version_name, "loadsc0"); + + printf("Into TheGame!!!\n"); +#else + LoadingScreen(nil, nil, "loadsc0"); +#endif + if ( !CGame::InitialiseOnceAfterRW() ) + RsGlobal.quit = TRUE; + +#ifdef PS2_MENU + gGameState = GS_INIT_PLAYING_GAME; +#else + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); +#endif + break; + } + +#ifndef PS2_MENU + case GS_INIT_FRONTEND: + { + LoadingScreen(nil, nil, "loadsc0"); + + FrontEndMenuManager.m_bGameNotLoaded = true; + + CMenuManager::m_bStartUpFrontEndRequested = true; + + if ( defaultFullscreenRes ) + { + defaultFullscreenRes = FALSE; + FrontEndMenuManager.m_nPrefsVideoMode = GcurSelVM; + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + } + + gGameState = GS_FRONTEND; + TRACE("gGameState = GS_FRONTEND;"); + break; + } + + case GS_FRONTEND: + { + if(!WindowIconified) + RsEventHandler(rsFRONTENDIDLE, nil); + +#ifdef PS2_MENU + if ( !FrontEndMenuManager.m_bMenuActive || TheMemoryCard.m_bWantToLoad ) +#else + if ( !FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bWantToLoad ) +#endif + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + +#ifdef PS2_MENU + if (TheMemoryCard.m_bWantToLoad ) +#else + if ( FrontEndMenuManager.m_bWantToLoad ) +#endif + { + InitialiseGame(); + FrontEndMenuManager.m_bGameNotLoaded = false; + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + } + break; + } +#endif + + case GS_INIT_PLAYING_GAME: + { +#ifdef PS2_MENU + CGame::Initialise("DATA\\GTA3.DAT"); + + //LoadingScreen("Starting Game", NULL, GetRandomSplashScreen()); + + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) == CMemoryCard::NO_ERR_SUCCESS + && TheMemoryCard.ChangeDirectory(CARD_ONE, TheMemoryCard.Cards[CARD_ONE].dir) + && TheMemoryCard.FindMostRecentFileName(CARD_ONE, TheMemoryCard.MostRecentFile) == true + && TheMemoryCard.CheckDataNotCorrupt(TheMemoryCard.MostRecentFile)) + { + strcpy(TheMemoryCard.LoadFileName, TheMemoryCard.MostRecentFile); + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = true; + + if (CMenuManager::m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad()) + { + CMenuManager::m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad(); + TheText.Unload(); + TheText.Load(); + } + + CGame::currLevel = (eLevelName)TheMemoryCard.GetLevelToLoad(); + } +#else + InitialiseGame(); + + FrontEndMenuManager.m_bGameNotLoaded = false; +#endif + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + break; + } + + case GS_PLAYING_GAME: + { + float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); + if ( RwInitialised ) + { + if (!CMenuManager::m_PrefsFrameLimiter || (1000.0f / (float)RsGlobal.maxFPS) < ms) + RsEventHandler(rsIDLE, (void *)TRUE); + } + break; + } + } + } + else + { + if ( RwCameraBeginUpdate(Scene.camera) ) + { + RwCameraEndUpdate(Scene.camera); + ForegroundApp = TRUE; + RsEventHandler(rsACTIVATE, (void *)TRUE); + } + + } + } + + + /* + * About to shut down - block resize events again... + */ + RwInitialised = FALSE; + + FrontEndMenuManager.UnloadTextures(); +#ifdef PS2_MENU + if ( !(FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad)) + break; +#else + if ( !FrontEndMenuManager.m_bWantToRestart ) + break; +#endif + + CPad::ResetCheats(); + CPad::StopPadsShaking(); + + DMAudio.ChangeMusicMode(MUSICMODE_DISABLE); + +#ifdef PS2_MENU + CGame::ShutDownForRestart(); +#endif + + CTimer::Stop(); + +#ifdef PS2_MENU + if (FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + if (TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + FrontEndMenuManager.m_bWantToRestart = true; + TheMemoryCard.m_bWantToLoad = true; + } + + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + FrontEndMenuManager.m_bWantToRestart = false; + + continue; + } + + CGame::ShutDown(); + CTimer::Stop(); + + break; +#else + if ( FrontEndMenuManager.m_bWantToLoad ) + { + CGame::ShutDownForRestart(); + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + FrontEndMenuManager.m_bWantToLoad = false; + } + else + { +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + CTimer::Stop(); + + if ( FrontEndMenuManager.m_bFirstTime == true ) + { + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); + } + else + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + } + + FrontEndMenuManager.m_bFirstTime = false; + FrontEndMenuManager.m_bWantToRestart = false; +#endif + } + + +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + DMAudio.Terminate(); + + _psFreeVideoModeList(); + + + /* + * Tidy up the 3D (RenderWare) components of the application... + */ + RsEventHandler(rsRWTERMINATE, nil); + + /* + * Free the platform dependent data... + */ + RsEventHandler(rsTERMINATE, nil); + +#ifdef _WIN32 + /* + * Free the argv strings... + */ + free(argv); + + SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &SavedStickyKeys, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETPOWEROFFACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETLOWPOWERACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SetErrorMode(0); +#endif + + return 0; +} + +/* + ***************************************************************************** + */ + +RwV2d leftStickPos; +RwV2d rightStickPos; + +void CapturePad(RwInt32 padID) +{ + int8 glfwPad = -1; + + if( padID == 0 ) + glfwPad = PSGLOBAL(joy1id); + else if( padID == 1) + glfwPad = PSGLOBAL(joy2id); + else + assert("invalid padID"); + + if ( glfwPad == -1 ) + return; + + int numButtons, numAxes; + const uint8 *buttons = glfwGetJoystickButtons(glfwPad, &numButtons); + const float *axes = glfwGetJoystickAxes(glfwPad, &numAxes); + GLFWgamepadstate gamepadState; + + if (ControlsManager.m_bFirstCapture == false) { + memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState)); + } else { + // In case connected gamepad doesn't have L-R trigger axes. + ControlsManager.m_NewState.mappedButtons[15] = ControlsManager.m_NewState.mappedButtons[16] = 0; + } + + ControlsManager.m_NewState.buttons = (uint8*)buttons; + ControlsManager.m_NewState.numButtons = numButtons; + ControlsManager.m_NewState.id = glfwPad; + ControlsManager.m_NewState.isGamepad = glfwGetGamepadState(glfwPad, &gamepadState); + if (ControlsManager.m_NewState.isGamepad) { + memcpy(&ControlsManager.m_NewState.mappedButtons, gamepadState.buttons, sizeof(gamepadState.buttons)); + float lt = gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER], rt = gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER]; + + // glfw returns 0.0 for non-existent axises(which is bullocks) so we treat it as deadzone, and keep value of previous frame. + // otherwise if this axis is present, -1 = released, 1 = pressed + if (lt != 0.0f) + ControlsManager.m_NewState.mappedButtons[15] = lt > -0.8f; + + if (rt != 0.0f) + ControlsManager.m_NewState.mappedButtons[16] = rt > -0.8f; + } + // TODO? L2-R2 axes(not buttons-that's fine) on joysticks that don't have SDL gamepad mapping AREN'T handled, and I think it's impossible to do without mapping. + + if (ControlsManager.m_bFirstCapture == true) { + memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(ControlsManager.m_NewState)); + + ControlsManager.m_bFirstCapture = false; + } + + RsPadButtonStatus bs; + bs.padID = padID; + + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + + // Gamepad axes are guaranteed to return 0.0f if that particular gamepad doesn't have that axis. + // And that's really good for sticks, because gamepads return 0.0 for them when sticks are in released state. + if ( glfwPad != -1 ) { + leftStickPos.x = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_X] : numAxes >= 1 ? axes[0] : 0.0f; + leftStickPos.y = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y] : numAxes >= 2 ? axes[1] : 0.0f; + + rightStickPos.x = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X] : numAxes >= 3 ? axes[2] : 0.0f; + rightStickPos.y = ControlsManager.m_NewState.isGamepad ? gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y] : numAxes >= 4 ? axes[3] : 0.0f; + } + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + RsPadEventHandler(rsPADBUTTONDOWN, (void *)&bs); + } + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + CPad *pad = CPad::GetPad(bs.padID); + + if ( Abs(leftStickPos.x) > 0.3f ) + pad->PCTempJoyState.LeftStickX = (int32)(leftStickPos.x * 128.0f); + + if ( Abs(leftStickPos.y) > 0.3f ) + pad->PCTempJoyState.LeftStickY = (int32)(leftStickPos.y * 128.0f); + + if ( Abs(rightStickPos.x) > 0.3f ) + pad->PCTempJoyState.RightStickX = (int32)(rightStickPos.x * 128.0f); + + if ( Abs(rightStickPos.y) > 0.3f ) + pad->PCTempJoyState.RightStickY = (int32)(rightStickPos.y * 128.0f); + } + + _psHandleVibration(); + + return; +} + +void joysChangeCB(int jid, int event) +{ + if (event == GLFW_CONNECTED && !IsThisJoystickBlacklisted(jid)) { + if (PSGLOBAL(joy1id) == -1) { + PSGLOBAL(joy1id) = jid; +#ifdef DETECT_JOYSTICK_MENU + strcpy(gSelectedJoystickName, glfwGetJoystickName(jid)); +#endif + // This is behind LOAD_INI_SETTINGS, because otherwise the Init call below will destroy/overwrite your bindings. +#ifdef LOAD_INI_SETTINGS + int count; + glfwGetJoystickButtons(PSGLOBAL(joy1id), &count); + ControlsManager.InitDefaultControlConfigJoyPad(count); +#endif + } else if (PSGLOBAL(joy2id) == -1) + PSGLOBAL(joy2id) = jid; + + } else if (event == GLFW_DISCONNECTED) { + if (PSGLOBAL(joy1id) == jid) { + PSGLOBAL(joy1id) = -1; + } else if (PSGLOBAL(joy2id) == jid) + PSGLOBAL(joy2id) = -1; + } +} + +#if (defined(_MSC_VER)) +int strcasecmp(const char* str1, const char* str2) +{ + return _strcmpi(str1, str2); +} +#endif +#endif diff --git a/src/skel/platform.h b/src/skel/platform.h new file mode 100644 index 0000000..c9a8a11 --- /dev/null +++ b/src/skel/platform.h @@ -0,0 +1,59 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +// Functions that's different on glfw/win etc. but have same signature (but if a function only used in win.cpp you can keep in win.h) + +#include "rwcore.h" +#include "skeleton.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#ifdef _WIN32 +extern RwUInt32 psTimer(void); +#else +extern double psTimer(void); +#endif + +extern RwBool psInitialize(void); +extern void psTerminate(void); + +extern void psCameraShowRaster(RwCamera *camera); +extern RwBool psCameraBeginUpdate(RwCamera *camera); +extern RwImage *psGrabScreen(RwCamera *camera); + +extern void psMouseSetPos(RwV2d *pos); + +extern RwBool psSelectDevice(); + +extern RwMemoryFunctions *psGetMemoryFunctions(void); + +/* install the platform specific file system */ +extern RwBool psInstallFileSystem(void); + + +/* Handle native texture support */ +extern RwBool psNativeTextureSupport(void); + +extern void _InputTranslateShiftKeyUpDown(RsKeyCodes* rs); +extern long _InputInitialiseMouse(); // returns HRESULT on Windows actually +extern void _InputInitialiseJoys(); + +extern void HandleExit(); + +extern void _psSelectScreenVM(RwInt32 videoMode); + +extern void InitialiseLanguage(); + +extern RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode); + +extern RwChar** _psGetVideoModeList(); + +extern RwInt32 _psGetNumVideModes(); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PLATFORM_H */ diff --git a/src/skel/skeleton.cpp b/src/skel/skeleton.cpp new file mode 100644 index 0000000..7889056 --- /dev/null +++ b/src/skel/skeleton.cpp @@ -0,0 +1,432 @@ +#include "common.h" + + +#include +#include +#include +#include + +#include "rwcore.h" + +#include "skeleton.h" +#include "platform.h" +#include "main.h" +#include "MemoryHeap.h" + +static RwBool DefaultVideoMode = TRUE; + +RsGlobalType RsGlobal; + +#ifdef _WIN32 +RwUInt32 +#else +double +#endif +RsTimer(void) +{ + return psTimer(); +} + + +/* + ***************************************************************************** + */ +void +RsCameraShowRaster(RwCamera * camera) +{ + psCameraShowRaster(camera); + + return; +} + +/* + ***************************************************************************** + */ +RwBool +RsCameraBeginUpdate(RwCamera * camera) +{ + return psCameraBeginUpdate(camera); +} + +/* + ***************************************************************************** + */ +RwImage* +RsGrabScreen(RwCamera *camera) +{ + return psGrabScreen(camera); +} + +/* + ***************************************************************************** + */ +RwBool +RsRegisterImageLoader(void) +{ + return TRUE; +} + +/* + ***************************************************************************** + */ +static RwBool +RsSetDebug(void) +{ + return TRUE; +} + +/* + ***************************************************************************** + */ +void +RsMouseSetPos(RwV2d * pos) +{ + psMouseSetPos(pos); + + return; +} + +/* + ***************************************************************************** + */ +RwBool +RsSelectDevice(void) +{ + return psSelectDevice(); +} + +/* + ***************************************************************************** + */ +RwBool +RsInputDeviceAttach(RsInputDeviceType inputDevice, + RsInputEventHandler inputEventHandler) +{ + switch (inputDevice) + { + case rsKEYBOARD: + { + RsGlobal.keyboard.inputEventHandler = inputEventHandler; + RsGlobal.keyboard.used = TRUE; + break; + } + case rsMOUSE: + { + RsGlobal.mouse.inputEventHandler = inputEventHandler; + RsGlobal.mouse.used = TRUE; + break; + } + case rsPAD: + { + RsGlobal.pad.inputEventHandler = inputEventHandler; + RsGlobal.pad.used = TRUE; + break; + } + default: + { + return FALSE; + } + } + + return TRUE; +} + + +/* + ***************************************************************************** + */ +static RwBool +rsCommandLine(RwChar *arg) +{ + RsEventHandler(rsFILELOAD, arg); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +static RwBool +rsPreInitCommandLine(RwChar *arg) +{ + if( !strcmp(arg, RWSTRING("-vms")) ) + { + DefaultVideoMode = FALSE; + + return TRUE; + } +#ifndef MASTER + if (!strcmp(arg, RWSTRING("-animviewer"))) + { + gbModelViewer = TRUE; + + return TRUE; + } +#endif + return FALSE; +} + +/* + ***************************************************************************** + */ +RsEventStatus +RsKeyboardEventHandler(RsEvent event, void *param) +{ + if (RsGlobal.keyboard.used) + { + return RsGlobal.keyboard.inputEventHandler(event, param); + } + + return rsEVENTNOTPROCESSED; +} + +/* + ***************************************************************************** + */ +RsEventStatus +RsPadEventHandler(RsEvent event, void *param) +{ + if (RsGlobal.pad.used) + { + return RsGlobal.pad.inputEventHandler(event, param); + } + + return rsEVENTNOTPROCESSED; +} + +/* + ***************************************************************************** + */ +RsEventStatus +RsEventHandler(RsEvent event, void *param) +{ + RsEventStatus result; + RsEventStatus es; + + /* + * Give the application an opportunity to override any events... + */ + es = AppEventHandler(event, param); + + /* + * We never allow the app to replace the quit behaviour, + * only to intercept... + */ + if (event == rsQUITAPP) + { + /* + * Set the flag which causes the event loop to exit... + */ + RsGlobal.quit = TRUE; + } + + if (es == rsEVENTNOTPROCESSED) + { + switch (event) + { + case rsSELECTDEVICE: + result = + (RsSelectDevice()? rsEVENTPROCESSED : rsEVENTERROR); + break; + + case rsCOMMANDLINE: + result = (rsCommandLine((RwChar *) param) ? + rsEVENTPROCESSED : rsEVENTERROR); + break; + case rsPREINITCOMMANDLINE: + result = (rsPreInitCommandLine((RwChar *) param) ? + rsEVENTPROCESSED : rsEVENTERROR); + break; + case rsINITDEBUG: + result = + (RsSetDebug()? rsEVENTPROCESSED : rsEVENTERROR); + break; + + case rsREGISTERIMAGELOADER: + result = (RsRegisterImageLoader()? + rsEVENTPROCESSED : rsEVENTERROR); + break; + + case rsRWTERMINATE: + RsRwTerminate(); + result = (rsEVENTPROCESSED); + break; + + case rsRWINITIALIZE: + result = (RsRwInitialize(param) ? + rsEVENTPROCESSED : rsEVENTERROR); + break; + + case rsTERMINATE: + RsTerminate(); + result = (rsEVENTPROCESSED); + break; + + case rsINITIALIZE: + result = + (RsInitialize()? rsEVENTPROCESSED : rsEVENTERROR); + break; + + default: + result = (es); + break; + + } + } + else + { + result = (es); + } + + return result; +} + +/* + ***************************************************************************** + */ +void +RsRwTerminate(void) +{ + /* Close RenderWare */ + + RwEngineStop(); + RwEngineClose(); + RwEngineTerm(); + + return; +} + +/* + ***************************************************************************** + */ +RwBool +RsRwInitialize(void *displayID) +{ + RwEngineOpenParams openParams; + + PUSH_MEMID(MEMID_RENDER); // NB: not popped on failed return + + /* + * Start RenderWare... + */ + + if (!RwEngineInit(psGetMemoryFunctions(), 0, rsRESOURCESDEFAULTARENASIZE)) + { + return (FALSE); + } + + /* + * Install any platform specific file systems... + */ + psInstallFileSystem(); + + /* + * Initialize debug message handling... + */ + RsEventHandler(rsINITDEBUG, nil); + + /* + * Attach all plugins... + */ + if (RsEventHandler(rsPLUGINATTACH, nil) == rsEVENTERROR) + { + return (FALSE); + } + + /* + * Attach input devices... + */ + if (RsEventHandler(rsINPUTDEVICEATTACH, nil) == rsEVENTERROR) + { + return (FALSE); + } + + openParams.displayID = displayID; + + if (!RwEngineOpen(&openParams)) + { + RwEngineTerm(); + return (FALSE); + } + + if (RsEventHandler(rsSELECTDEVICE, displayID) == rsEVENTERROR) + { + RwEngineClose(); + RwEngineTerm(); + return (FALSE); + } + + if (!RwEngineStart()) + { + RwEngineClose(); + RwEngineTerm(); + return (FALSE); + } + + /* + * Register loaders for an image with a particular file extension... + */ + RsEventHandler(rsREGISTERIMAGELOADER, nil); + + psNativeTextureSupport(); + + RwTextureSetMipmapping(FALSE); + RwTextureSetAutoMipmapping(FALSE); + + POP_MEMID(); + + return TRUE; +} + +/* + ***************************************************************************** + */ +void +RsTerminate(void) +{ + psTerminate(); + + return; +} + +/* + ***************************************************************************** + */ +RwBool +RsInitialize(void) +{ + /* + * Initialize Platform independent data... + */ + RwBool result; + + RsGlobal.appName = RWSTRING("GTA3"); + RsGlobal.maximumWidth = DEFAULT_SCREEN_WIDTH; + RsGlobal.maximumHeight = DEFAULT_SCREEN_HEIGHT; + RsGlobal.width = DEFAULT_SCREEN_WIDTH; + RsGlobal.height = DEFAULT_SCREEN_HEIGHT; + + RsGlobal.maxFPS = 30; + + RsGlobal.quit = FALSE; + + /* setup the keyboard */ + RsGlobal.keyboard.inputDeviceType = rsKEYBOARD; + RsGlobal.keyboard.inputEventHandler = nil; + RsGlobal.keyboard.used = FALSE; + + /* setup the mouse */ + RsGlobal.mouse.inputDeviceType = rsMOUSE; + RsGlobal.mouse.inputEventHandler = nil; + RsGlobal.mouse.used = FALSE; + + /* setup the pad */ + RsGlobal.pad.inputDeviceType = rsPAD; + RsGlobal.pad.inputEventHandler = nil; + RsGlobal.pad.used = FALSE; + + result = psInitialize(); + + return result; +} diff --git a/src/skel/skeleton.h b/src/skel/skeleton.h new file mode 100644 index 0000000..380b6c0 --- /dev/null +++ b/src/skel/skeleton.h @@ -0,0 +1,290 @@ +#ifndef SKELETON_H +#define SKELETON_H + +#include "rwcore.h" + +/* Default arena size depending on platform. */ +#define rsRESOURCESDEFAULTARENASIZE (1 << 20) + +#if (!defined(RsSprintf)) +#define RsSprintf rwsprintf +#endif /* (!defined(RsSprintf)) */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#if (!defined(RSASSERT)) +#define RSASSERT(_condition) /* No-op */ +#endif /* (!defined(RSASSERT)) */ + +#define RSASSERTISTYPE(_f, _t) \ + RSASSERT( (!(_f)) || ((((const RwObject *)(_f))->type)==(_t)) ) + +enum RsInputDeviceType +{ + rsKEYBOARD, + rsMOUSE, + rsPAD +}; +typedef enum RsInputDeviceType RsInputDeviceType; + +enum RsEventStatus +{ + rsEVENTERROR, + rsEVENTPROCESSED, + rsEVENTNOTPROCESSED +}; +typedef enum RsEventStatus RsEventStatus; + +enum RsEvent +{ + rsCAMERASIZE, + rsCOMMANDLINE, + rsFILELOAD, + rsINITDEBUG, + rsINPUTDEVICEATTACH, + rsLEFTBUTTONDOWN, + rsLEFTBUTTONUP, + rsMOUSEMOVE, + rsMOUSEWHEELMOVE, + rsPLUGINATTACH, + rsREGISTERIMAGELOADER, + rsRIGHTBUTTONDOWN, + rsRIGHTBUTTONUP, + _rs_13, + _rs_14, + _rs_15, + _rs_16, + _rs_17, + _rs_18, + _rs_19, + _rs_20, + rsRWINITIALIZE, + rsRWTERMINATE, + rsSELECTDEVICE, + rsINITIALIZE, + rsTERMINATE, + rsIDLE, + rsFRONTENDIDLE, + rsKEYDOWN, + rsKEYUP, + rsQUITAPP, + rsPADBUTTONDOWN, + rsPADBUTTONUP, + rsPADANALOGUELEFT, + rsPADANALOGUELEFTRESET, + rsPADANALOGUERIGHT, + rsPADANALOGUERIGHTRESET, + rsPREINITCOMMANDLINE, + rsACTIVATE, +}; + +typedef enum RsEvent RsEvent; + +typedef RsEventStatus (*RsInputEventHandler)(RsEvent event, void *param); + +typedef struct RsInputDevice RsInputDevice; +struct RsInputDevice +{ + RsInputDeviceType inputDeviceType; + RwBool used; + RsInputEventHandler inputEventHandler; +}; + +typedef struct RsGlobalType RsGlobalType; +struct RsGlobalType +{ + const RwChar *appName; + RwInt32 width; + RwInt32 height; + RwInt32 maximumWidth; + RwInt32 maximumHeight; + RwInt32 maxFPS; + RwBool quit; + + void *ps; /* platform specific data */ + + RsInputDevice keyboard; + RsInputDevice mouse; + RsInputDevice pad; +}; + +enum RsKeyCodes +{ + rsESC = 1000, + + rsF1 = 1001, + rsF2 = 1002, + rsF3 = 1003, + rsF4 = 1004, + rsF5 = 1005, + rsF6 = 1006, + rsF7 = 1007, + rsF8 = 1008, + rsF9 = 1009, + rsF10 = 1010, + rsF11 = 1011, + rsF12 = 1012, + + rsINS = 1013, + rsDEL = 1014, + rsHOME = 1015, + rsEND = 1016, + rsPGUP = 1017, + rsPGDN = 1018, + + rsUP = 1019, + rsDOWN = 1020, + rsLEFT = 1021, + rsRIGHT = 1022, + + rsDIVIDE = 1023, + rsTIMES = 1024, + rsPLUS = 1025, + rsMINUS = 1026, + rsPADDEL = 1027, + rsPADEND = 1028, + rsPADDOWN = 1029, + rsPADPGDN = 1030, + rsPADLEFT = 1031, + rsPAD5 = 1032, + rsNUMLOCK = 1033, + rsPADRIGHT = 1034, + rsPADHOME = 1035, + rsPADUP = 1036, + rsPADPGUP = 1037, + rsPADINS = 1038, + rsPADENTER = 1039, + + rsSCROLL = 1040, + rsPAUSE = 1041, + + rsBACKSP = 1042, + rsTAB = 1043, + rsCAPSLK = 1044, + rsENTER = 1045, + rsLSHIFT = 1046, + rsRSHIFT = 1047, + rsSHIFT = 1048, + rsLCTRL = 1049, + rsRCTRL = 1050, + rsLALT = 1051, + rsRALT = 1052, + rsLWIN = 1053, + rsRWIN = 1054, + rsAPPS = 1055, + + rsNULL = 1056, + + rsMOUSELEFTBUTTON = 1, + rsMOUSMIDDLEBUTTON = 2, + rsMOUSERIGHTBUTTON = 3, + rsMOUSEWHEELUPBUTTON = 4, + rsMOUSEWHEELDOWNBUTTON = 5, + rsMOUSEX1BUTTON = 6, + rsMOUSEX2BUTTON = 7, +}; +typedef enum RsKeyCodes RsKeyCodes; + +typedef struct RsKeyStatus RsKeyStatus; +struct RsKeyStatus +{ + RwInt32 keyCharCode; +}; + +typedef struct RsPadButtonStatus RsPadButtonStatus; +struct RsPadButtonStatus +{ + RwInt32 padID; +}; + +enum RsPadButtons +{ + rsPADNULL = 0, + + rsPADBUTTON1 = 1, + rsPADBUTTON2 = 2, + rsPADBUTTON3 = 3, + rsPADBUTTON4 = 4, + + rsPADBUTTON5 = 5, + rsPADBUTTON6 = 6, + rsPADBUTTON7 = 7, + rsPADBUTTON8 = 8, + + rsPADSELECT = 9, + + rsPADBUTTONA1 = 10, + rsPADBUTTONA2 = 11, + + rsPADSTART = 12, + + rsPADDPADUP = 13, + rsPADDPADRIGHT = 14, + rsPADDPADDOWN = 15, + rsPADDPADLEFT = 16, +}; +typedef enum RsPadButtons RsPadButtons; + + +extern RsGlobalType RsGlobal; + +extern RsEventStatus AppEventHandler(RsEvent event, void *param); +extern RwBool AttachInputDevices(void); + +extern RsEventStatus RsEventHandler(RsEvent event, void *param); +extern RsEventStatus RsKeyboardEventHandler(RsEvent event, void *param); +extern RsEventStatus RsPadEventHandler(RsEvent event, void *param); + +extern RwBool +RsInitialize(void); + +extern RwBool +RsRegisterImageLoader(void); + +extern RwBool +RsRwInitialize(void *param); + +extern RwBool +RsSelectDevice(void); + +extern RwBool +RsInputDeviceAttach(RsInputDeviceType inputDevice, + RsInputEventHandler inputEventHandler); + +#ifdef _WIN32 +extern RwUInt32 +#else +extern double +#endif +RsTimer(void); + +extern void +RsCameraShowRaster(RwCamera *camera); + +extern RwBool +RsCameraBeginUpdate(RwCamera *camera); + +//TODO +//extern void +//RsMouseSetVisibility(RwBool visible); + +extern RwImage* +RsGrabScreen(RwCamera *camera); + +extern void +RsMouseSetPos(RwV2d *pos); + +extern void +RsRwTerminate(void); + +extern void +RsTerminate(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SKELETON_H */ diff --git a/src/skel/win/gta3.ico b/src/skel/win/gta3.ico new file mode 100644 index 0000000..d0a4771 Binary files /dev/null and b/src/skel/win/gta3.ico differ diff --git a/src/skel/win/resource.h b/src/skel/win/resource.h new file mode 100644 index 0000000..84dffb9 --- /dev/null +++ b/src/skel/win/resource.h @@ -0,0 +1,21 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by dungeon.rc +// +#define IDD_DIALOG1 104 +#define IDC_DEVICESEL 1000 +#define IDC_VIDMODE 1001 +#define IDEXIT 1002 +#define IDC_SELECTDEVICE 1005 + +#define IDI_MAIN_ICON 1042 +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/skel/win/win.cpp b/src/skel/win/win.cpp new file mode 100644 index 0000000..95ac28a --- /dev/null +++ b/src/skel/win/win.cpp @@ -0,0 +1,3432 @@ +#if defined RW_D3D9 || defined RWLIBS || defined __MWERKS__ + +#define _WIN32_WINDOWS 0x0500 +#define WINVER 0x0500 + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#pragma warning( push ) +#pragma warning( disable : 4005) + +#ifdef __MWERKS__ +#define MAPVK_VK_TO_CHAR (2) // this is missing from codewarrior win32 headers - but it gets used ... how? +#endif + +#include +#include +#pragma warning( pop ) + +#define WM_GRAPHNOTIFY WM_USER+13 + +#ifndef USE_D3D9 +#pragma comment( lib, "d3d8.lib" ) +#endif +#pragma comment( lib, "ddraw.lib" ) +#pragma comment( lib, "Winmm.lib" ) +#pragma comment( lib, "dxguid.lib" ) +#pragma comment( lib, "strmiids.lib" ) +#pragma comment( lib, "dinput8.lib" ) + +#define WITHD3D +#define WITHDINPUT +#include "common.h" +#if (defined(_MSC_VER)) +#include +#endif /* (defined(_MSC_VER)) */ +#include +#include "rwcore.h" +#include "resource.h" +#include "skeleton.h" +#include "platform.h" +#include "crossplatform.h" + +#define MAX_SUBSYSTEMS (16) + + +static RwBool ForegroundApp = TRUE; + +static RwBool RwInitialised = FALSE; + +static RwSubSystemInfo GsubSysInfo[MAX_SUBSYSTEMS]; +static RwInt32 GnumSubSystems = 0; +static RwInt32 GcurSel = 0, GcurSelVM = 0; + +static RwBool startupDeactivate; + +static RwBool useDefault; + +/* Class name for the MS Window's window class. */ + +static const RwChar *AppClassName = RWSTRING("Grand theft auto 3"); + +static psGlobalType PsGlobal; + + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +#undef MAKEPOINTS +#define MAKEPOINTS(l) (*((POINTS /*FAR*/ *)&(l))) + +#define SAFE_RELEASE(x) { if (x) x->Release(); x = NULL; } +#define JIF(x) if (FAILED(hr=(x))) \ + {debug(TEXT("FAILED(hr=0x%x) in ") TEXT(#x) TEXT("\n"), hr); return;} + +#include "main.h" +#include "FileMgr.h" +#include "Text.h" +#include "Pad.h" +#include "Timer.h" +#include "DMAudio.h" +#include "ControllerConfig.h" +#include "Frontend.h" +#include "Game.h" +#include "PCSave.h" +#include "AnimViewer.h" +#include "MemoryMgr.h" + +#ifdef PS2_MENU +#include "MemoryCard.h" +#include "Font.h" +#endif + +VALIDATE_SIZE(psGlobalType, 0x28); + +// DirectShow interfaces +IGraphBuilder *pGB = nil; +IMediaControl *pMC = nil; +IMediaEventEx *pME = nil; +IVideoWindow *pVW = nil; +IMediaSeeking *pMS = nil; + +DWORD dwDXVersion; +SIZE_T _dwMemTotalPhys; +size_t _dwMemAvailPhys; +SIZE_T _dwMemTotalVirtual; +SIZE_T _dwMemAvailVirtual; +DWORD _dwMemTotalVideo; +DWORD _dwMemAvailVideo; +DWORD _dwOperatingSystemVersion; + +RwUInt32 gGameState; +CJoySticks AllValidWinJoys; + +#ifdef DETECT_JOYSTICK_MENU +char gSelectedJoystickName[128] = ""; +#endif + +// What is that for anyway? +#ifndef IMPROVED_VIDEOMODE +static RwBool defaultFullscreenRes = TRUE; +#else +static RwBool defaultFullscreenRes = FALSE; +static RwInt32 bestWndMode = -1; +#endif + +CJoySticks::CJoySticks() +{ + for (int i = 0; i < MAX_JOYSTICKS; i++) + { + ClearJoyInfo(i); + } +} + +void CJoySticks::ClearJoyInfo(int joyID) +{ + m_aJoys[joyID].m_State = JOYPAD_UNUSED; + m_aJoys[joyID].m_bInitialised = false; + m_aJoys[joyID].m_bHasAxisZ = false; + m_aJoys[joyID].m_bHasAxisR = false; +} + + + +/* + ***************************************************************************** + */ +void _psCreateFolder(LPCSTR path) +{ + HANDLE hfle = CreateFile(path, GENERIC_READ, + FILE_SHARE_READ, + nil, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL, + nil); + + if ( hfle == INVALID_HANDLE_VALUE ) + CreateDirectory(path, nil); + else + CloseHandle(hfle); +} + +/* + ***************************************************************************** + */ +const char *_psGetUserFilesFolder() +{ +#ifdef USE_MY_DOCUMENTS + HKEY hKey = NULL; + + static CHAR szUserFiles[256]; + + if ( RegOpenKeyEx(HKEY_CURRENT_USER, + REGSTR_PATH_SPECIAL_FOLDERS, + REG_OPTION_RESERVED, + KEY_READ, + &hKey) == ERROR_SUCCESS ) + { + DWORD KeyType; + DWORD KeycbData = sizeof(szUserFiles); + if ( RegQueryValueEx(hKey, + "Personal", + NULL, + &KeyType, + (LPBYTE)szUserFiles, + &KeycbData) == ERROR_SUCCESS ) + { + RegCloseKey(hKey); + strcat(szUserFiles, "\\GTA3 User Files"); + _psCreateFolder(szUserFiles); + return szUserFiles; + } + + RegCloseKey(hKey); + } + + strcpy(szUserFiles, "data"); + return szUserFiles; +#else + static CHAR szUserFiles[256]; + strcpy(szUserFiles, "userfiles"); + _psCreateFolder(szUserFiles); + return szUserFiles; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psCameraBeginUpdate(RwCamera *camera) +{ + if ( !RwCameraBeginUpdate(Scene.camera) ) + { + ForegroundApp = FALSE; + RsEventHandler(rsACTIVATE, (void *)FALSE); + return FALSE; + } + + return TRUE; +} + +/* + ***************************************************************************** + */ +void +psCameraShowRaster(RwCamera *camera) +{ + if (CMenuManager::m_PrefsVsync) + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPWAITVSYNC); + else + RwCameraShowRaster(camera, PSGLOBAL(window), rwRASTERFLIPDONTWAIT); + + return; +} + + +/* + ***************************************************************************** + */ +RwImage * +psGrabScreen(RwCamera *pCamera) +{ +#ifndef LIBRW + RwRaster *pRaster = RwCameraGetRaster(pCamera); + if (RwImage *pImage = RwImageCreate(pRaster->width, pRaster->height, 32)) { + RwImageAllocatePixels(pImage); + RwImageSetFromRaster(pImage, pRaster); + return pImage; + } +#else + rw::Image *image = RwCameraGetRaster(pCamera)->toImage(); + image->removeMask(); + if(image) + return image; +#endif + return nil; +} + +/* + ***************************************************************************** + */ +RwUInt32 +psTimer(void) +{ + RwUInt32 time; + + TIMECAPS TimeCaps; + + timeGetDevCaps(&TimeCaps, sizeof(TIMECAPS)); + + timeBeginPeriod(TimeCaps.wPeriodMin); + + time = (RwUInt32) timeGetTime(); + + timeEndPeriod(TimeCaps.wPeriodMin); + + return time; +} + +/* + ***************************************************************************** + */ +void +psMouseSetPos(RwV2d *pos) +{ + POINT point; + + point.x = (RwInt32) pos->x; + point.y = (RwInt32) pos->y; + + ClientToScreen(PSGLOBAL(window), &point); + + SetCursorPos(point.x, point.y); + + PSGLOBAL(lastMousePos.x) = (RwInt32)pos->x; + + PSGLOBAL(lastMousePos.y) = (RwInt32)pos->y; + + return; +} + +/* + ***************************************************************************** + */ +RwMemoryFunctions* +psGetMemoryFunctions(void) +{ +#ifdef USE_CUSTOM_ALLOCATOR + return &memFuncs; +#else + return nil; +#endif +} + +/* + ***************************************************************************** + */ +RwBool +psInstallFileSystem(void) +{ + return (TRUE); +} + + +/* + ***************************************************************************** + */ +RwBool +psNativeTextureSupport(void) +{ + return RwD3D8DeviceSupportsDXTTexture(); +} + +/* + ***************************************************************************** + */ +static HWND +InitInstance(HANDLE instance) +{ + /* + * Perform any necessary initialization for this instance of the + * application. + * + * Create the MS Window's window instance for this application. The + * initial window size is given by the defined camera size. The window + * is not given a title as we set it during Init3D() with information + * about the version of RenderWare being used. + */ + + RECT rect; + + rect.left = rect.top = 0; + rect.right = RsGlobal.maximumWidth; + rect.bottom = RsGlobal.maximumHeight; + + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); + + return CreateWindow(AppClassName, RsGlobal.appName, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + rect.right - rect.left, rect.bottom - rect.top, + (HWND)nil, (HMENU)nil, (HINSTANCE)instance, nil); +} + +void _GetVideoMemInfo(LPDWORD total, LPDWORD avaible) +{ + HRESULT hr; + LPDIRECTDRAW7 pDD7; + + hr = DirectDrawCreateEx(nil, (VOID**)&pDD7, IID_IDirectDraw7, nil); + + if ( FAILED(hr) ) + return; + + DDSCAPS2 caps; + + ZeroMemory(&caps, sizeof(DDSCAPS2)); + caps.dwCaps = DDSCAPS_VIDEOMEMORY; + + pDD7->GetAvailableVidMem(&caps, total, avaible); + + pDD7->Release(); +} + +/* + ***************************************************************************** + */ +typedef HRESULT(WINAPI * DIRECTDRAWCREATEEX)( GUID*, VOID**, REFIID, IUnknown* ); + + +//----------------------------------------------------------------------------- +// Name: GetDXVersion() +// Desc: This function returns the DirectX version number as follows: +// 0x0000 = No DirectX installed +// 0x0700 = At least DirectX 7 installed. +// 0x0800 = At least DirectX 8 installed. +// +// Please note that this code is intended as a general guideline. Your +// app will probably be able to simply query for functionality (via +// QueryInterface) for one or two components. +// +// Please also note: +// "if( dwDXVersion != 0x500 ) return FALSE;" is VERY BAD. +// "if( dwDXVersion < 0x500 ) return FALSE;" is MUCH BETTER. +// to ensure your app will run on future releases of DirectX. +//----------------------------------------------------------------------------- +DWORD GetDXVersion() +{ + DIRECTDRAWCREATEEX DirectDrawCreateEx = NULL; + HINSTANCE hDDrawDLL = nil; + HINSTANCE hD3D8DLL = nil; + HINSTANCE hDPNHPASTDLL = NULL; + DWORD dwDXVersion = 0; + //HRESULT hr; + + // First see if DDRAW.DLL even exists. + hDDrawDLL = LoadLibrary( "DDRAW.DLL" ); + if( hDDrawDLL == nil ) + { + dwDXVersion = 0; + OutputDebugString( "Couldn't LoadLibrary DDraw\r\n" ); + return dwDXVersion; + } + + + //------------------------------------------------------------------------- + // DirectX 7.0 Checks + //------------------------------------------------------------------------- + + // Check for DirectX 7 by creating a DDraw7 object + LPDIRECTDRAW7 pDD7; + DirectDrawCreateEx = (DIRECTDRAWCREATEEX)GetProcAddress( hDDrawDLL, + "DirectDrawCreateEx" ); + if( nil == DirectDrawCreateEx ) + { + FreeLibrary( hDDrawDLL ); + OutputDebugString( "Couldn't GetProcAddress DirectDrawCreateEx\r\n" ); + return dwDXVersion; + } + + if( FAILED( DirectDrawCreateEx( nil, (VOID**)&pDD7, IID_IDirectDraw7, + nil ) ) ) + { + FreeLibrary( hDDrawDLL ); + OutputDebugString( "Couldn't DirectDrawCreateEx\r\n" ); + return dwDXVersion; + } + + // DDraw7 was created successfully. We must be at least DX7.0 + dwDXVersion = 0x700; + pDD7->Release(); + +#ifdef USE_D3D9 + HINSTANCE hD3D9DLL = LoadLibrary("D3D9.DLL"); + if (hD3D9DLL != nil) { + FreeLibrary(hDDrawDLL); + FreeLibrary(hD3D9DLL); + + dwDXVersion = 0x900; + return dwDXVersion; + } +#endif + + //------------------------------------------------------------------------- + // DirectX 8.0 Checks + //------------------------------------------------------------------------- + + // Simply see if D3D8.dll exists. + hD3D8DLL = LoadLibrary( "D3D8.DLL" ); + if( hD3D8DLL == nil ) + { + FreeLibrary( hDDrawDLL ); + OutputDebugString( "Couldn't LoadLibrary D3D8.DLL\r\n" ); + return dwDXVersion; + } + + // D3D8.dll exists. We must be at least DX8.0 + dwDXVersion = 0x800; + + + //------------------------------------------------------------------------- + // DirectX 8.1 Checks + //------------------------------------------------------------------------- + + // Simply see if dpnhpast.dll exists. + hDPNHPASTDLL = LoadLibrary( "dpnhpast.dll" ); + if( hDPNHPASTDLL == nil ) + { + FreeLibrary( hDPNHPASTDLL ); + OutputDebugString( "Couldn't LoadLibrary dpnhpast.dll\r\n" ); + return dwDXVersion; + } + + // dpnhpast.dll exists. We must be at least DX8.1 + dwDXVersion = 0x801; + + + //------------------------------------------------------------------------- + // End of checking for versions of DirectX + //------------------------------------------------------------------------- + + // Close open libraries and return + FreeLibrary( hDDrawDLL ); + FreeLibrary( hD3D8DLL ); + + return dwDXVersion; +} + +/* + ***************************************************************************** + */ +#ifndef _WIN64 +static char cpuvendor[16] = "UnknownVendr"; +__declspec(naked) const char * _psGetCpuVendr() +{ + __asm + { + push ebx + xor eax, eax + cpuid + mov dword ptr [cpuvendor+0], ebx + mov dword ptr [cpuvendor+4], edx + mov dword ptr [cpuvendor+8], ecx + mov eax, offset cpuvendor + pop ebx + retn + } +} + +/* + ***************************************************************************** + */ +__declspec(naked) RwUInt32 _psGetCpuFeatures() +{ + __asm + { + mov eax, 1 + cpuid + mov eax, edx + retn + } +} + +/* + ***************************************************************************** + */ +__declspec(naked) RwUInt32 _psGetCpuFeaturesEx() +{ + __asm + { + mov eax, 80000000h + cpuid + + cmp eax, 80000000h + jbe short _NOEX + + mov eax, 80000001h + cpuid + + mov eax, edx + jmp short _RETEX + +_NOEX: + xor eax, eax + mov eax, eax + +_RETEX: + retn + } +} + +void _psPrintCpuInfo() +{ + RwUInt32 features = _psGetCpuFeatures(); + RwUInt32 FeaturesEx = _psGetCpuFeaturesEx(); + + debug("Running on a %s", _psGetCpuVendr()); + + if ( features & 0x800000 ) + debug("with MMX"); + if ( features & 0x2000000 ) + debug("with SSE"); + if ( FeaturesEx & 0x80000000 ) + debug("with 3DNow"); +} +#endif + +/* + ***************************************************************************** + */ +#ifdef UNDER_CE +#define CMDSTR LPWSTR +#else +#define CMDSTR LPSTR +#endif + +/* + ***************************************************************************** + */ +RwBool +psInitialize(void) +{ + PsGlobal.lastMousePos.x = PsGlobal.lastMousePos.y = 0.0f; + + RsGlobal.ps = &PsGlobal; + + PsGlobal.fullScreen = FALSE; + + PsGlobal.dinterface = nil; + PsGlobal.mouse = nil; + PsGlobal.joy1 = nil; + PsGlobal.joy2 = nil; + + CFileMgr::Initialise(); + +#ifdef PS2_MENU + CPad::Initialise(); + CPad::GetPad(0)->Mode = 0; + + CGame::frenchGame = false; + CGame::germanGame = false; + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + + WORD lang = PRIMARYLANGID(GetSystemDefaultLCID()); + if ( lang == LANG_ITALIAN ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + else if ( lang == LANG_SPANISH ) + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + else if ( lang == LANG_GERMAN ) + { + CGame::germanGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + } + else if ( lang == LANG_FRENCH ) + { + CGame::frenchGame = true; + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + } + else + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + + FrontEndMenuManager.InitialiseMenuContentsAfterLoadingGame(); + + TheMemoryCard.Init(); +#else + C_PcSave::SetSaveDirectory(_psGetUserFilesFolder()); + + InitialiseLanguage(); +#if GTA_VERSION < GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + gGameState = GS_START_UP; + TRACE("gGameState = GS_START_UP"); +#ifndef _WIN64 + _psPrintCpuInfo(); +#endif + OSVERSIONINFO verInfo; + verInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + GetVersionEx(&verInfo); + + _dwOperatingSystemVersion = OS_WIN95; + + if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + if ( verInfo.dwMajorVersion == 4 ) + { + debug("Operating System is WinNT\n"); + _dwOperatingSystemVersion = OS_WINNT; + } + else if ( verInfo.dwMajorVersion == 5 ) + { + debug("Operating System is Win2000\n"); + _dwOperatingSystemVersion = OS_WIN2000; + } + else if ( verInfo.dwMajorVersion > 5 ) + { + debug("Operating System is WinXP or greater\n"); + _dwOperatingSystemVersion = OS_WINXP; + } + } + else if ( verInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) + { + if ( verInfo.dwMajorVersion > 4 || verInfo.dwMajorVersion == 4 && verInfo.dwMinorVersion != 0 ) + { + debug("Operating System is Win98\n"); + _dwOperatingSystemVersion = OS_WIN98; + } + else + { + debug("Operating System is Win95\n"); + _dwOperatingSystemVersion = OS_WIN95; + } + } + +#ifndef PS2_MENU + +#if GTA_VERSION >= GTA3_PC_11 + FrontEndMenuManager.LoadSettings(); +#endif + +#endif + + dwDXVersion = GetDXVersion(); + debug("DirectX version 0x%x\n", dwDXVersion); + + if ( _dwOperatingSystemVersion == OS_WIN95 ) + { + MessageBoxW(nil, + (LPCWSTR)TheText.Get("WIN_95"), // Grand Theft Auto III cannot run on Windows 95 + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto III + MB_OK); + + return FALSE; + } + + if ( dwDXVersion < 0x801 ) + { + MessageBoxW(nil, + (LPCWSTR)TheText.Get("WIN_DX"), // Grand Theft Auto III requires at least DirectX version 8.1 + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto III + MB_OK); + + return FALSE; + } + + MEMORYSTATUS memstats; + GlobalMemoryStatus(&memstats); + + _dwMemTotalPhys = memstats.dwTotalPhys; + _dwMemAvailPhys = memstats.dwAvailPhys; + _dwMemTotalVirtual = memstats.dwTotalVirtual; + _dwMemAvailVirtual = memstats.dwAvailVirtual; + + _GetVideoMemInfo(&_dwMemTotalVideo, &_dwMemAvailVideo); +#ifdef FIX_BUGS + debug("Physical memory size %lu\n", _dwMemTotalPhys); + debug("Available physical memory %lu\n", _dwMemAvailPhys); + debug("Video memory size %lu\n", _dwMemTotalVideo); + debug("Available video memory %lu\n", _dwMemAvailVideo); +#else + debug("Physical memory size %d\n", _dwMemTotalPhys); + debug("Available physical memory %d\n", _dwMemAvailPhys); + debug("Video memory size %d\n", _dwMemTotalVideo); + debug("Available video memory %d\n", _dwMemAvailVideo); +#endif + + if ( _dwMemAvailVideo < (12 * 1024 * 1024) /*12 MB*/ ) + { + MessageBoxW(nil, + (LPCWSTR)TheText.Get("WIN_VDM"), // Grand Theft Auto III requires at least 12MB of available video memory + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto III + MB_OK); + + return FALSE; + } + + TheText.Unload(); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +void +psTerminate(void) +{ + return; +} + +/* + ***************************************************************************** + */ +static RwChar **_VMList; + +RwInt32 _psGetNumVideModes() +{ + return RwEngineGetNumVideoModes(); +} + +/* + ***************************************************************************** + */ +RwBool _psFreeVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + numModes = _psGetNumVideModes(); + + if ( _VMList == nil ) + return TRUE; + + for ( i = 0; i < numModes; i++ ) + { + RwFree(_VMList[i]); + } + + RwFree(_VMList); + + _VMList = nil; + + return TRUE; +} + +/* + ***************************************************************************** + */ +RwChar **_psGetVideoModeList() +{ + RwInt32 numModes; + RwInt32 i; + + if ( _VMList != nil ) + { + return _VMList; + } + + numModes = RwEngineGetNumVideoModes(); + + _VMList = (RwChar **)RwCalloc(numModes, sizeof(RwChar*)); + + for ( i = 0; i < numModes; i++ ) + { + RwVideoMode vm; + + RwEngineGetVideoModeInfo(&vm, i); + + if ( vm.flags & rwVIDEOMODEEXCLUSIVE ) + { + if ( vm.width >= 640 + && vm.height >= 480 + && (vm.width == 640 + && vm.height == 480) + || !(vm.flags & rwVIDEOMODEEXCLUSIVE) + || (_dwMemTotalVideo - vm.depth * vm.height * vm.width / 8) > (12 * 1024 * 1024)/*12 MB*/ ) + { + _VMList[i] = (RwChar*)RwCalloc(100, sizeof(RwChar)); + rwsprintf(_VMList[i],"%lu X %lu X %lu", vm.width, vm.height, vm.depth); + } + else + _VMList[i] = nil; + } + else + _VMList[i] = nil; + } + + return _VMList; +} + +/* + ***************************************************************************** + */ +void _psSelectScreenVM(RwInt32 videoMode) +{ + RwTexDictionarySetCurrent( nil ); + + FrontEndMenuManager.UnloadTextures(); + + if ( !_psSetVideoMode(RwEngineGetCurrentSubSystem(), videoMode) ) + { + RsGlobal.quit = TRUE; + + ShowWindow(PSGLOBAL(window), SW_HIDE); + + MessageBoxW(nil, + (LPCWSTR)TheText.Get("WIN_RSZ"), // Failed to select new screen resolution + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto III + MB_OK); + } + else + FrontEndMenuManager.LoadAllTextures(); +} + +/* + ***************************************************************************** + */ +void WaitForState(FILTER_STATE State) +{ + HRESULT hr; + + ASSERT(pMC != nil); + + // Make sure we have switched to the required state + LONG lfs; + do + { + hr = pMC->GetState(10, &lfs); + } while (State != lfs); +} + +/* + ***************************************************************************** + */ +void HandleGraphEvent(void) +{ + LONG evCode; + LONG_PTR evParam1, evParam2; + HRESULT hr=S_OK; + + ASSERT(pME != nil); + + // Process all queued events + while (SUCCEEDED(pME->GetEvent(&evCode, &evParam1, &evParam2, 0))) + { + // Free memory associated with callback, since we're not using it + hr = pME->FreeEventParams(evCode, evParam1, evParam2); + + // If this is the end of the clip, reset to beginning + if (EC_COMPLETE == evCode) + { + switch (gGameState) + { + case GS_LOGO_MPEG: + { + gGameState = GS_INIT_INTRO_MPEG; + TRACE("gGameState = GS_INIT_INTRO_MPEG"); + break; + } + case GS_INTRO_MPEG: + { + gGameState = GS_INIT_ONCE; + TRACE("gGameState = GS_INIT_ONCE"); + break; + } + default: + { + break; + } + } + + pME->SetNotifyWindow((OAHWND)NULL, 0, 0); + } + } +} + +/* + ***************************************************************************** + */ + +LRESULT CALLBACK +MainWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) +{ + POINTS points; + static BOOL noMemory = FALSE; + + + switch( message ) + { + case WM_SETCURSOR: + { + ShowCursor(FALSE); + + SetCursor(nil); + + break; // is this correct ? + } + + case WM_SIZE: + { + RwRect r; + + r.x = 0; + r.y = 0; + r.w = LOWORD(lParam); + r.h = HIWORD(lParam); + + if (RwInitialised && r.h > 0 && r.w > 0) + { + RsEventHandler(rsCAMERASIZE, &r); + + if (r.w != LOWORD(lParam) && r.h != HIWORD(lParam)) + { + WINDOWPLACEMENT wp; + + /* failed to create window of required size */ + noMemory = TRUE; + + /* stop re-sizing */ + ReleaseCapture(); + + /* handle maximised window */ + GetWindowPlacement(window, &wp); + if (wp.showCmd == SW_SHOWMAXIMIZED) + { + SendMessage(window, WM_WINDOWPOSCHANGED, 0, 0); + } + } + else + { + noMemory = FALSE; + } + + } + + return 0L; + } + + case WM_SIZING: + { + /* + * Handle event to ensure window contents are displayed during re-size + * as this can be disabled by the user, then if there is not enough + * memory things don't work. + */ + RECT *newPos = (LPRECT) lParam; + RECT rect; + + /* redraw window */ + + if (RwInitialised && gGameState == GS_PLAYING_GAME) + { + RsEventHandler(rsIDLE, (void *)TRUE); + } + + /* Manually resize window */ + rect.left = rect.top = 0; + rect.bottom = newPos->bottom - newPos->top; + rect.right = newPos->right - newPos->left; + + SetWindowPos(window, HWND_TOP, rect.left, rect.top, + (rect.right - rect.left), + (rect.bottom - rect.top), SWP_NOMOVE); + + return 0L; + } + + case WM_LBUTTONDOWN: + { + SetCapture(window); + + return 0L; + } + + case WM_RBUTTONDOWN: + { + SetCapture(window); + + return 0L; + } + + case WM_MBUTTONDOWN: + { + SetCapture(window); + + return 0L; + } + + case WM_MOUSEWHEEL: + { + return 0L; + } + + case WM_MOUSEMOVE: + { + points = MAKEPOINTS(lParam); + + FrontEndMenuManager.m_nMouseTempPosX = points.x; + FrontEndMenuManager.m_nMouseTempPosY = points.y; + + return 0L; + } + + case WM_LBUTTONUP: + { + ReleaseCapture(); + + return 0L; + } + + case WM_RBUTTONUP: + { + ReleaseCapture(); + + return 0L; + } + + case WM_MBUTTONUP: + { + ReleaseCapture(); + + return 0L; + } + + case WM_KEYDOWN: + { + RsKeyCodes ks; + + if ( _InputTranslateKey(&ks, lParam, wParam) ) + RsKeyboardEventHandler(rsKEYDOWN, &ks); + + if ( wParam == VK_SHIFT ) + _InputTranslateShiftKeyUpDown(&ks); +#ifdef FIX_BUGS + break; +#else + return 0L; +#endif + } + + case WM_KEYUP: + { + RsKeyCodes ks; + + if ( _InputTranslateKey(&ks, lParam, wParam) ) + RsKeyboardEventHandler(rsKEYUP, &ks); + + if ( wParam == VK_SHIFT ) + _InputTranslateShiftKeyUpDown(&ks); + +#ifdef FIX_BUGS + break; +#else + return 0L; +#endif + } + + case WM_SYSKEYDOWN: + { + RsKeyCodes ks; + + if ( _InputTranslateKey(&ks, lParam, wParam) ) + RsKeyboardEventHandler(rsKEYDOWN, &ks); + + if ( wParam == VK_SHIFT ) + _InputTranslateShiftKeyUpDown(&ks); + +#ifdef FIX_BUGS + break; +#else + return 0L; +#endif + } + + case WM_SYSKEYUP: + { + RsKeyCodes ks; + + if ( _InputTranslateKey(&ks, lParam, wParam) ) + RsKeyboardEventHandler(rsKEYUP, &ks); + + if ( wParam == VK_SHIFT ) + _InputTranslateShiftKeyUpDown(&ks); + +#ifdef FIX_BUGS + break; +#else + return 0L; +#endif + } + + case WM_ACTIVATEAPP: + { + switch ( gGameState ) + { + case GS_LOGO_MPEG: + case GS_INTRO_MPEG: + { + ASSERT(pMC != nil); + + LONG state; + pMC->GetState(10, &state); + + if ( !(BOOL)wParam ) // losing activation + { + if ( state == State_Running && pMC != nil ) + { + HRESULT hr = pMC->Pause(); + + if (hr == S_FALSE) + OutputDebugString("Failed to pause the MPEG"); + else + WaitForState(State_Paused); + } + } + else + { + CenterVideo(); + + if ( state != State_Running && pMC != nil ) + { + HRESULT hr = pMC->Run(); + + if ( hr == S_FALSE ) + OutputDebugString("Failed to run the MPEG"); + else + { + WaitForState(State_Running); + SetFocus(PSGLOBAL(window)); + } + } + } + + break; + } + + case GS_START_UP: + { + if ( !(BOOL)wParam && PSGLOBAL(fullScreen) ) // losing activation + startupDeactivate = TRUE; + + break; + } + } + + CPad::GetPad(0)->Clear(false); + CPad::GetPad(1)->Clear(false); + + return 0L; + } + + case WM_TIMER: + { + return 0L; + } + + case WM_GRAPHNOTIFY: + { + if (gGameState == GS_INTRO_MPEG || gGameState == GS_LOGO_MPEG) + HandleGraphEvent(); + + break; + } + + case WM_CLOSE: + case WM_DESTROY: + { + /* + * Quit message handling. + */ + ClipCursor(nil); + + _InputShutdown(); + + PostQuitMessage(0); + + return 0L; + } + + case WM_DEVICECHANGE: + { + if( wParam == DBT_DEVICEREMOVECOMPLETE ) + { + PDEV_BROADCAST_HDR pDev = (PDEV_BROADCAST_HDR)lParam; + + if (pDev->dbch_devicetype != DBT_DEVTYP_VOLUME) + break; + + if ( DMAudio.IsAudioInitialised() ) + { + PDEV_BROADCAST_VOLUME pVol = (PDEV_BROADCAST_VOLUME)pDev; + if ( pVol->dbcv_flags & DBTF_MEDIA ) + { + char c = DMAudio.GetCDAudioDriveLetter(); + + if ( c >= 'A' && pVol->dbcv_unitmask & (1 << (c - 'A')) ) + { + OutputDebugString("About to check CD drive..."); + + while ( true ) + { + FrontEndMenuManager.WaitForUserCD(); + + if ( !FrontEndMenuManager.m_bQuitGameNoCD ) + { + if ( DMAudio.CheckForAnAudioFileOnCD() ) + { + OutputDebugString("GTA3 Audio CD has been inserted"); + break; + } + } + else + { + OutputDebugString("Exiting game as Audio CD was not inserted"); + break; + } + } + } + } + } + } + + break; + } + + } + + /* + * Let Windows handle all other messages. + */ + return DefWindowProc(window, message, wParam, lParam); +} + + +/* + ***************************************************************************** + */ +static BOOL +InitApplication(HANDLE instance) +{ + /* + * Perform any necessary MS Windows application initialization. Basically, + * this means registering the window class for this application. + */ + + WNDCLASS windowClass; + + windowClass.style = CS_BYTEALIGNWINDOW; + windowClass.lpfnWndProc = (WNDPROC)MainWndProc; + windowClass.cbClsExtra = 0; + windowClass.cbWndExtra = 0; + windowClass.hInstance = (HINSTANCE)instance; +#ifdef FIX_BUGS + windowClass.hIcon = LoadIcon((HINSTANCE)instance, MAKEINTRESOURCE(IDI_MAIN_ICON)); +#else + windowClass.hIcon = nil; +#endif + windowClass.hCursor = LoadCursor(nil, IDC_ARROW); + windowClass.hbrBackground = nil; + windowClass.lpszMenuName = NULL; + windowClass.lpszClassName = AppClassName; + + return RegisterClass(&windowClass); +} + + +/* + ***************************************************************************** + */ + +RwBool IsForegroundApp() +{ + return !!ForegroundApp; +} + +UINT GetBestRefreshRate(UINT width, UINT height, UINT depth) +{ +#ifdef USE_D3D9 + LPDIRECT3D9 d3d = Direct3DCreate9(D3D_SDK_VERSION); +#else + LPDIRECT3D8 d3d = Direct3DCreate8(D3D_SDK_VERSION); +#endif + ASSERT(d3d != nil); + + UINT refreshRate = INT_MAX; + D3DFORMAT format; + + if ( depth == 32 ) + format = D3DFMT_X8R8G8B8; + else if ( depth == 24 ) + format = D3DFMT_R8G8B8; + else + format = D3DFMT_R5G6B5; + +#ifdef USE_D3D9 + UINT modeCount = d3d->GetAdapterModeCount(GcurSel, format); +#else + UINT modeCount = d3d->GetAdapterModeCount(GcurSel); +#endif + + for ( UINT i = 0; i < modeCount; i++ ) + { + D3DDISPLAYMODE mode; + +#ifdef USE_D3D9 + d3d->EnumAdapterModes(GcurSel, format, i, &mode); +#else + d3d->EnumAdapterModes(GcurSel, i, &mode); +#endif + if ( mode.Width == width && mode.Height == height && mode.Format == format ) + { + if ( mode.RefreshRate == 0 ) { + // From VC +#ifdef FIX_BUGS + d3d->Release(); +#endif + return 0; + } + + if ( mode.RefreshRate < refreshRate && mode.RefreshRate >= 60 ) + refreshRate = mode.RefreshRate; + } + } + + // From VC +#ifdef FIX_BUGS + d3d->Release(); +#endif + + if ( refreshRate == -1 ) + return -1; + + return refreshRate; +} + +/* + ***************************************************************************** + */ +RwBool +psSelectDevice() +{ + RwVideoMode vm; + RwInt32 subSysNum; + RwInt32 AutoRenderer = 0; + + + RwBool modeFound = FALSE; + + if ( !useDefault ) + { + GnumSubSystems = RwEngineGetNumSubSystems(); + if ( !GnumSubSystems ) + { + return FALSE; + } + + /* Just to be sure ... */ + GnumSubSystems = (GnumSubSystems > MAX_SUBSYSTEMS) ? MAX_SUBSYSTEMS : GnumSubSystems; + + /* Get the names of all the sub systems */ + for (subSysNum = 0; subSysNum < GnumSubSystems; subSysNum++) + { + RwEngineGetSubSystemInfo(&GsubSysInfo[subSysNum], subSysNum); + } + + /* Get the default selection */ + GcurSel = RwEngineGetCurrentSubSystem(); +#ifdef IMPROVED_VIDEOMODE + if(FrontEndMenuManager.m_nPrefsSubsystem < GnumSubSystems) + GcurSel = FrontEndMenuManager.m_nPrefsSubsystem; +#endif + } + + /* Set the driver to use the correct sub system */ + if (!RwEngineSetSubSystem(GcurSel)) + { + return FALSE; + } + +#ifdef IMPROVED_VIDEOMODE + FrontEndMenuManager.m_nPrefsSubsystem = GcurSel; +#endif + +#ifndef IMPROVED_VIDEOMODE + if ( !useDefault ) + { + if ( _psGetVideoModeList()[FrontEndMenuManager.m_nDisplayVideoMode] && FrontEndMenuManager.m_nDisplayVideoMode ) + { + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + GcurSelVM = FrontEndMenuManager.m_nDisplayVideoMode; + } + else + { +#ifdef DEFAULT_NATIVE_RESOLUTION + // get the native video mode + HDC hDevice = GetDC(NULL); + int w = GetDeviceCaps(hDevice, HORZRES); + int h = GetDeviceCaps(hDevice, VERTRES); + int d = GetDeviceCaps(hDevice, BITSPIXEL); +#else + const int w = 640; + const int h = 480; + const int d = 16; +#endif + while ( !modeFound && GcurSelVM < RwEngineGetNumVideoModes() ) + { + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + if ( defaultFullscreenRes && vm.width != w + || vm.height != h + || vm.depth != d + || !(vm.flags & rwVIDEOMODEEXCLUSIVE) ) + ++GcurSelVM; + else + modeFound = TRUE; + } + + if ( !modeFound ) + { +#ifdef DEFAULT_NATIVE_RESOLUTION + GcurSelVM = 1; +#else + MessageBox(nil, "Cannot find 640x480 video mode", "GTA3", MB_OK); + return FALSE; +#endif + } + } + } +#else + if ( !useDefault ) + { + if(FrontEndMenuManager.m_nPrefsWidth == 0 || + FrontEndMenuManager.m_nPrefsHeight == 0 || + FrontEndMenuManager.m_nPrefsDepth == 0){ + // Defaults if nothing specified + FrontEndMenuManager.m_nPrefsWidth = GetSystemMetrics(SM_CXSCREEN); + FrontEndMenuManager.m_nPrefsHeight = GetSystemMetrics(SM_CYSCREEN); + FrontEndMenuManager.m_nPrefsDepth = 32; + FrontEndMenuManager.m_nPrefsWindowed = 0; + } + + // Find the videomode that best fits what we got from the settings file + RwInt32 bestFsMode = -1; + RwInt32 bestWidth = -1; + RwInt32 bestHeight = -1; + RwInt32 bestDepth = -1; + for (GcurSelVM = 0; GcurSelVM < RwEngineGetNumVideoModes(); GcurSelVM++) { + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + if (!(vm.flags & rwVIDEOMODEEXCLUSIVE)) { + bestWndMode = GcurSelVM; + } else { + // try the largest one that isn't larger than what we wanted + if (vm.width >= bestWidth && vm.width <= FrontEndMenuManager.m_nPrefsWidth && + vm.height >= bestHeight && vm.height <= FrontEndMenuManager.m_nPrefsHeight && + vm.depth >= bestDepth && vm.depth <= FrontEndMenuManager.m_nPrefsDepth){ + bestWidth = vm.width; + bestHeight = vm.height; + bestDepth = vm.depth; + bestFsMode = GcurSelVM; + } + } + } + + if(bestFsMode < 0){ + MessageBox(nil, "Cannot find desired video mode", "GTA3", MB_OK); + return FALSE; + } + GcurSelVM = bestFsMode; + + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode; + + FrontEndMenuManager.m_nSelectedScreenMode = FrontEndMenuManager.m_nPrefsWindowed; + } +#endif + + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + +#ifdef IMPROVED_VIDEOMODE + if (FrontEndMenuManager.m_nPrefsWindowed) + GcurSelVM = bestWndMode; + + // Now GcurSelVM is 0 but vm has sizes(and fullscreen flag) of the video mode we want, that's why we changed the rwVIDEOMODEEXCLUSIVE conditions below + FrontEndMenuManager.m_nPrefsWidth = vm.width; + FrontEndMenuManager.m_nPrefsHeight = vm.height; + FrontEndMenuManager.m_nPrefsDepth = vm.depth; +#endif + +#ifndef PS2_MENU + FrontEndMenuManager.m_nCurrOption = 0; +#endif + + /* Set up the video mode and set the apps window + * dimensions to match */ + if (!RwEngineSetVideoMode(GcurSelVM)) + { + return FALSE; + } + +#ifdef IMPROVED_VIDEOMODE + if (!FrontEndMenuManager.m_nPrefsWindowed) +#else + if (vm.flags & rwVIDEOMODEEXCLUSIVE) +#endif + { + debug("%dx%dx%d", vm.width, vm.height, vm.depth); + + UINT refresh = GetBestRefreshRate(vm.width, vm.height, vm.depth); + + if ( refresh != (UINT)-1 ) + { + debug("refresh %d", refresh); + RwD3D8EngineSetRefreshRate((RwUInt32)refresh); + } + } + +#ifdef IMPROVED_VIDEOMODE + if (!FrontEndMenuManager.m_nPrefsWindowed) +#else + if (vm.flags & rwVIDEOMODEEXCLUSIVE) +#endif + { + RsGlobal.maximumWidth = vm.width; + RsGlobal.maximumHeight = vm.height; + RsGlobal.width = vm.width; + RsGlobal.height = vm.height; + + PSGLOBAL(fullScreen) = TRUE; + +#ifdef IMPROVED_VIDEOMODE + SetWindowLong(PSGLOBAL(window), GWL_STYLE, WS_POPUP); + SetWindowPos(PSGLOBAL(window), nil, 0, 0, 0, 0, + SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER| + SWP_FRAMECHANGED); + }else{ + RECT rect; + rect.left = rect.top = 0; + rect.right = FrontEndMenuManager.m_nPrefsWidth; + rect.bottom = FrontEndMenuManager.m_nPrefsHeight; + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); + + // center it + int spaceX = GetSystemMetrics(SM_CXSCREEN) - (rect.right-rect.left); + int spaceY = GetSystemMetrics(SM_CYSCREEN) - (rect.bottom-rect.top); + + SetWindowLong(PSGLOBAL(window), GWL_STYLE, WS_VISIBLE | WS_OVERLAPPEDWINDOW); + SetWindowPos(PSGLOBAL(window), HWND_NOTOPMOST, spaceX/2, spaceY/2, + (rect.right - rect.left), + (rect.bottom - rect.top), 0); + + // Have to get actual size because the window perhaps didn't fit + GetClientRect(PSGLOBAL(window), &rect); + RsGlobal.maximumWidth = rect.right; + RsGlobal.maximumHeight = rect.bottom; + RsGlobal.width = rect.right; + RsGlobal.height = rect.bottom; + + PSGLOBAL(fullScreen) = FALSE; +#endif + } +#ifdef MULTISAMPLING + RwD3D8EngineSetMultiSamplingLevels(1 << FrontEndMenuManager.m_nPrefsMSAALevel); +#endif + return TRUE; +} + +/* + ***************************************************************************** + */ +RwBool _psSetVideoMode(RwInt32 subSystem, RwInt32 videoMode) +{ + RwInitialised = FALSE; + + RsEventHandler(rsRWTERMINATE, nil); + + GcurSel = subSystem; + GcurSelVM = videoMode; + + useDefault = TRUE; + + if ( RsEventHandler(rsRWINITIALIZE, PSGLOBAL(window)) == rsEVENTERROR ) + return FALSE; + + RwInitialised = TRUE; + useDefault = FALSE; + + RwRect r; + + r.x = 0; + r.y = 0; + r.w = RsGlobal.maximumWidth; + r.h = RsGlobal.maximumHeight; + + RsEventHandler(rsCAMERASIZE, &r); + + return TRUE; +} + + +/* + ***************************************************************************** + */ +static RwChar ** +CommandLineToArgv(RwChar *cmdLine, RwInt32 *argCount) +{ + RwInt32 numArgs = 0; + RwBool inArg, inString; + RwInt32 i, len; + RwChar *res, *str, **aptr; + + len = (int)strlen(cmdLine); + + /* + * Count the number of arguments... + */ + inString = FALSE; + inArg = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if( inArg ) + { + inArg = FALSE; + + numArgs++; + } + } + else if( !inArg ) + { + inArg = TRUE; + } + } + + /* + * Allocate memory for result... + */ + res = (RwChar *)malloc(sizeof(RwChar *) * numArgs + len + 1); + str = res + sizeof(RwChar *) * numArgs; + aptr = (RwChar **)res; + + strcpy(str, cmdLine); + + /* + * Walk through cmdLine again this time setting pointer to each arg... + */ + inArg = FALSE; + inString = FALSE; + + for(i=0; i<=len; i++) + { + if( cmdLine[i] == '"' ) + { + inString = !inString; + } + + if( (cmdLine[i] <= ' ' && !inString) || i == len ) + { + if( inArg ) + { + if( str[i-1] == '"' ) + { + str[i-1] = '\0'; + } + else + { + str[i] = '\0'; + } + + inArg = FALSE; + } + } + else if( !inArg && cmdLine[i] != '"' ) + { + inArg = TRUE; + + *aptr++ = &str[i]; + } + } + + *argCount = numArgs; + + return (RwChar **)res; +} + +/* + ***************************************************************************** + */ +void InitialiseLanguage() +{ + WORD primUserLCID = PRIMARYLANGID(GetSystemDefaultLCID()); + WORD primSystemLCID = PRIMARYLANGID(GetUserDefaultLCID()); + WORD primLayout = PRIMARYLANGID((DWORD_PTR)GetKeyboardLayout(0)); + + WORD subUserLCID = SUBLANGID(GetSystemDefaultLCID()); + WORD subSystemLCID = SUBLANGID(GetUserDefaultLCID()); + WORD subLayout = SUBLANGID((DWORD_PTR)GetKeyboardLayout(0)); + + if ( primUserLCID == LANG_GERMAN + || primSystemLCID == LANG_GERMAN + || primLayout == LANG_GERMAN ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::germanGame = true; + } + + if ( primUserLCID == LANG_FRENCH + || primSystemLCID == LANG_FRENCH + || primLayout == LANG_FRENCH ) + { + CGame::nastyGame = false; + CMenuManager::m_PrefsAllowNastyGame = false; + CGame::frenchGame = true; + } + + if ( subUserLCID == SUBLANG_ENGLISH_AUS + || subSystemLCID == SUBLANG_ENGLISH_AUS + || subLayout == SUBLANG_ENGLISH_AUS ) + CGame::noProstitutes = true; + +#ifdef NASTY_GAME + CGame::nastyGame = true; + CMenuManager::m_PrefsAllowNastyGame = true; + CGame::noProstitutes = false; +#endif + + int32 lang; + + switch ( primSystemLCID ) + { + case LANG_GERMAN: + { + lang = LANG_GERMAN; + break; + } + case LANG_FRENCH: + { + lang = LANG_FRENCH; + break; + } + case LANG_SPANISH: + { + lang = LANG_SPANISH; + break; + } + case LANG_ITALIAN: + { + lang = LANG_ITALIAN; + break; + } + default: + { + lang = ( subSystemLCID == SUBLANG_ENGLISH_AUS ) ? -99 : LANG_ENGLISH; + break; + } + } + + CMenuManager::OS_Language = primUserLCID; + + switch ( lang ) + { + case LANG_GERMAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + break; + } + case LANG_SPANISH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + break; + } + case LANG_FRENCH: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + break; + } + case LANG_ITALIAN: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + break; + } + default: + { + CMenuManager::m_PrefsLanguage = CMenuManager::LANGUAGE_AMERICAN; + break; + } + } + + TheText.Unload(); + TheText.Load(); +} + +/* + ***************************************************************************** + */ +void CenterVideo(void) +{ + HRESULT hr = S_OK; + RECT rect; + + ASSERT(pVW != nil); + + GetClientRect(PSGLOBAL(window), &rect); + + JIF(pVW->SetWindowPosition(rect.left, rect.top, rect.right, rect.bottom)); + + JIF(pVW->put_MessageDrain((OAHWND) PSGLOBAL(window))); + + SetFocus(PSGLOBAL(window)); +} + +/* + ***************************************************************************** + */ +void PlayMovieInWindow(int cmdShow, const char* szFile) +{ + WCHAR wFileName[256]; + HRESULT hr; + + // Clear open dialog remnants before calling RenderFile() + UpdateWindow(PSGLOBAL(window)); + + // Convert filename to wide character string + MultiByteToWideChar(CP_ACP, 0, szFile, -1, wFileName, sizeof(wFileName) - 1); + + // Initialize COM +#ifdef FIX_BUGS // will also return S_FALSE if it has already been inited in the same thread + CoInitialize(nil); +#else + JIF(CoInitialize(nil)); +#endif + + // Get the interface for DirectShow's GraphBuilder + JIF(CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC, + IID_IGraphBuilder, (void **)&pGB)); + + if(SUCCEEDED(hr)) + { + // Have the graph builder construct its the appropriate graph automatically + JIF(pGB->RenderFile(&wFileName[0], nil)); + + // QueryInterface for DirectShow interfaces + JIF(pGB->QueryInterface(IID_IMediaControl, (void **)&pMC)); + JIF(pGB->QueryInterface(IID_IMediaEventEx, (void **)&pME)); + JIF(pGB->QueryInterface(IID_IMediaSeeking, (void **)&pMS)); + + // Query for video interfaces, which may not be relevant for audio files + JIF(pGB->QueryInterface(IID_IVideoWindow, (void **)&pVW)); + + JIF(pVW->put_Owner((OAHWND) PSGLOBAL(window))); + JIF(pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN)); + + // Have the graph signal event via window callbacks for performance + JIF(pME->SetNotifyWindow((OAHWND)PSGLOBAL(window), WM_GRAPHNOTIFY, 0)); + + CenterVideo(); + + // Run the graph to play the media file + JIF(pMC->Run()); + + SetFocus(PSGLOBAL(window)); + } + + ASSERT(pGB != nil); + ASSERT(pVW != nil); + ASSERT(pME != nil); + ASSERT(pMC != nil); + + if(FAILED(hr)) + CloseClip(); +} + +/* + ***************************************************************************** + */ +void CloseInterfaces(void) +{ + // Release and zero DirectShow interfaces + SAFE_RELEASE(pME); + SAFE_RELEASE(pMS); + SAFE_RELEASE(pMC); + SAFE_RELEASE(pVW); + SAFE_RELEASE(pGB); +} + +/* + ***************************************************************************** + */ +void CloseClip(void) +{ + HRESULT hr; + + // Stop playback + if(pMC) + hr = pMC->Stop(); + + // Free DirectShow interfaces + CloseInterfaces(); +} + +/* + ***************************************************************************** + */ +void HandleExit() +{ + MSG message; + while ( PeekMessage(&message, nil, 0U, 0U, PM_REMOVE|PM_NOYIELD) ) + { + if( message.message == WM_QUIT ) + { + RsGlobal.quit = TRUE; + } + else + { + TranslateMessage(&message); + DispatchMessage(&message); + } + } +} + +/* + ***************************************************************************** + */ +int PASCAL +WinMain(HINSTANCE instance, + HINSTANCE prevInstance __RWUNUSED__, + CMDSTR cmdLine, + int cmdShow) +{ + MSG message; + RwV2d pos; + RwInt32 argc, i; + RwChar **argv; + SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, SPIF_SENDCHANGE); + +#ifndef MASTER + if (strstr(cmdLine, "-console")) + { + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } +#endif + +#ifdef USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + + /* + * Initialize the platform independent data. + * This will in turn initialize the platform specific data... + */ + if( RsEventHandler(rsINITIALIZE, nil) == rsEVENTERROR ) + { + return FALSE; + } + + /* + * Register the window class... + */ + if( !InitApplication(instance) ) + { + return FALSE; + } + + /* + * Get proper command line params, cmdLine passed to us does not + * work properly under all circumstances... + */ + cmdLine = GetCommandLine(); + + /* + * Parse command line into standard (argv, argc) parameters... + */ + argv = CommandLineToArgv(cmdLine, &argc); + + + /* + * Parse command line parameters (except program name) one at + * a time BEFORE RenderWare initialization... + */ + for(i=1; iGetLeftMouseJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetEnterJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetCharJustDown(' ') ) + ++gGameState; + else if ( CPad::GetPad(0)->GetAltJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetTabJustDown() ) + ++gGameState; + + break; + } + + case GS_INIT_INTRO_MPEG: + { +#ifdef NO_MOVIES + if (!gbNoMovies) +#endif + CloseClip(); +#ifndef FIX_BUGS + CoUninitialize(); +#endif + + if ( CMenuManager::OS_Language == LANG_FRENCH || CMenuManager::OS_Language == LANG_GERMAN ) + PlayMovieInWindow(cmdShow, "movies\\GTAtitlesGER.mpg"); + else + PlayMovieInWindow(cmdShow, "movies\\GTAtitles.mpg"); + + gGameState = GS_INTRO_MPEG; + TRACE("gGameState = GS_INTRO_MPEG;"); + break; + } + + case GS_INTRO_MPEG: + { + CPad::UpdatePads(); + + if ( startupDeactivate || ControlsManager.GetJoyButtonJustDown() != 0 ) + ++gGameState; + else if ( CPad::GetPad(0)->GetLeftMouseJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetEnterJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetCharJustDown(' ') ) + ++gGameState; + else if ( CPad::GetPad(0)->GetAltJustDown() ) + ++gGameState; + else if ( CPad::GetPad(0)->GetTabJustDown() ) + ++gGameState; + + break; + } + + case GS_INIT_ONCE: + { +#ifdef NO_MOVIES + if (!gbNoMovies) +#endif + CloseClip(); +#ifndef FIX_BUGS + CoUninitialize(); +#endif + +#ifdef FIX_BUGS + // draw one frame because otherwise we'll end up looking at black screen for a while if vsync is on + RsCameraShowRaster(Scene.camera); +#endif + +#ifdef PS2_MENU + extern char version_name[64]; + if ( CGame::frenchGame || CGame::germanGame ) + LoadingScreen(NULL, version_name, "loadsc24"); + else + LoadingScreen(NULL, version_name, "loadsc0"); + + printf("Into TheGame!!!\n"); +#else + LoadingScreen(nil, nil, "loadsc0"); +#endif + if ( !CGame::InitialiseOnceAfterRW() ) + RsGlobal.quit = TRUE; + +#ifdef PS2_MENU + gGameState = GS_INIT_PLAYING_GAME; +#else + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); +#endif + break; + } + +#ifndef PS2_MENU + case GS_INIT_FRONTEND: + { + LoadingScreen(nil, nil, "loadsc0"); + + FrontEndMenuManager.m_bGameNotLoaded = true; + + CMenuManager::m_bStartUpFrontEndRequested = true; + + if ( defaultFullscreenRes ) + { + defaultFullscreenRes = FALSE; + FrontEndMenuManager.m_nPrefsVideoMode = GcurSelVM; + FrontEndMenuManager.m_nDisplayVideoMode = GcurSelVM; + } + + gGameState = GS_FRONTEND; + TRACE("gGameState = GS_FRONTEND;"); + break; + } + + case GS_FRONTEND: + { + GetWindowPlacement(PSGLOBAL(window), &wp); + + if (wp.showCmd != SW_SHOWMINIMIZED) + RsEventHandler(rsFRONTENDIDLE, nil); + +#ifdef PS2_MENU + if ( !FrontEndMenuManager.m_bMenuActive || TheMemoryCard.m_bWantToLoad ) +#else + if ( !FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bWantToLoad ) +#endif + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + +#ifdef PS2_MENU + if (TheMemoryCard.m_bWantToLoad ) +#else + if ( FrontEndMenuManager.m_bWantToLoad ) +#endif + { + InitialiseGame(); + FrontEndMenuManager.m_bGameNotLoaded = false; + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + } + break; + } +#endif + + case GS_INIT_PLAYING_GAME: + { +#ifdef PS2_MENU + CGame::Initialise("DATA\\GTA3.DAT"); + + //LoadingScreen("Starting Game", NULL, GetRandomSplashScreen()); + + if ( TheMemoryCard.CheckCardInserted(CARD_ONE) == CMemoryCard::NO_ERR_SUCCESS + && TheMemoryCard.ChangeDirectory(CARD_ONE, TheMemoryCard.Cards[CARD_ONE].dir) + && TheMemoryCard.FindMostRecentFileName(CARD_ONE, TheMemoryCard.MostRecentFile) == true + && TheMemoryCard.CheckDataNotCorrupt(TheMemoryCard.MostRecentFile)) + { + strcpy(TheMemoryCard.LoadFileName, TheMemoryCard.MostRecentFile); + TheMemoryCard.b_FoundRecentSavedGameWantToLoad = true; + + if (CMenuManager::m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad()) + { + CMenuManager::m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad(); + TheText.Unload(); + TheText.Load(); + } + + CGame::currLevel = (eLevelName)TheMemoryCard.GetLevelToLoad(); + } +#else + InitialiseGame(); + + FrontEndMenuManager.m_bGameNotLoaded = false; +#endif + gGameState = GS_PLAYING_GAME; + TRACE("gGameState = GS_PLAYING_GAME;"); + break; + } + + case GS_PLAYING_GAME: + { + float ms = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); + if ( RwInitialised ) + { + if (!CMenuManager::m_PrefsFrameLimiter || (1000.0f / (float)RsGlobal.maxFPS) < ms) + RsEventHandler(rsIDLE, (void *)TRUE); + } + break; + } + } + } + else + { + if ( RwCameraBeginUpdate(Scene.camera) ) + { + RwCameraEndUpdate(Scene.camera); + ForegroundApp = TRUE; + RsEventHandler(rsACTIVATE, (void *)TRUE); + } + + WaitMessage(); + } + } + + + /* + * About to shut down - block resize events again... + */ + RwInitialised = FALSE; + + FrontEndMenuManager.UnloadTextures(); +#ifdef PS2_MENU + if ( !(FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad)) + break; +#else + if ( !FrontEndMenuManager.m_bWantToRestart ) + break; +#endif + + CPad::ResetCheats(); + CPad::StopPadsShaking(); + + DMAudio.ChangeMusicMode(MUSICMODE_DISABLE); + +#ifdef PS2_MENU + CGame::ShutDownForRestart(); +#endif + CTimer::Stop(); + +#ifdef PS2_MENU + if (FrontEndMenuManager.m_bWantToRestart || TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + if (TheMemoryCard.b_FoundRecentSavedGameWantToLoad) + { + FrontEndMenuManager.m_bWantToRestart = true; + TheMemoryCard.m_bWantToLoad = true; + } + + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + FrontEndMenuManager.m_bWantToRestart = false; + + continue; + } + + CGame::ShutDown(); + CTimer::Stop(); + + break; +#else + if ( FrontEndMenuManager.m_bWantToLoad ) + { + CGame::ShutDownForRestart(); + CGame::InitialiseWhenRestarting(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + LoadSplash(GetLevelSplashScreen(CGame::currLevel)); + FrontEndMenuManager.m_bWantToLoad = false; + } + else + { +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + CTimer::Stop(); + + if ( FrontEndMenuManager.m_bFirstTime == true ) + { + gGameState = GS_INIT_FRONTEND; + TRACE("gGameState = GS_INIT_FRONTEND;"); + } + else + { + gGameState = GS_INIT_PLAYING_GAME; + TRACE("gGameState = GS_INIT_PLAYING_GAME;"); + } + } + + FrontEndMenuManager.m_bFirstTime = false; + FrontEndMenuManager.m_bWantToRestart = false; +#endif + } + + +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif + if ( gGameState == GS_PLAYING_GAME ) + CGame::ShutDown(); + + DMAudio.Terminate(); + + _psFreeVideoModeList(); + + + /* + * Tidy up the 3D (RenderWare) components of the application... + */ + RsEventHandler(rsRWTERMINATE, nil); + + /* + * Kill the window... + */ + DestroyWindow(PSGLOBAL(window)); + + /* + * Free the platform dependent data... + */ + RsEventHandler(rsTERMINATE, nil); + + /* + * Free the argv strings... + */ + free(argv); + + ShowCursor(TRUE); + + SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &SavedStickyKeys, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETPOWEROFFACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETLOWPOWERACTIVE, TRUE, nil, SPIF_SENDCHANGE); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, nil, SPIF_SENDCHANGE); + + SetErrorMode(0); + + return message.wParam; +} + +/* + ***************************************************************************** + */ + +#define DEVICE_AXIS_MIN -2000 +#define DEVICE_AXIS_MAX 2000 + + +HRESULT _InputInitialise() +{ + HRESULT hr; + + // Create a DInput object + if( FAILED( hr = DirectInput8Create( GetModuleHandle(nil), DIRECTINPUT_VERSION, + IID_IDirectInput8, (VOID**)&PSGLOBAL(dinterface), nil ) ) ) + return hr; + + return S_OK; +} + +HRESULT _InputInitialiseMouse() +{ + HRESULT hr; + + // Obtain an interface to the system mouse device. + if( FAILED( hr = PSGLOBAL(dinterface)->CreateDevice( GUID_SysMouse, &PSGLOBAL(mouse), nil ) ) ) + return hr; + + // Set the data format to "mouse format" - a predefined data format + // + // A data format specifies which controls on a device we + // are interested in, and how they should be reported. + // + // This tells DirectInput that we will be passing a + // DIMOUSESTATE2 structure to IDirectInputDevice::GetDeviceState. + if( FAILED( hr = PSGLOBAL(mouse)->SetDataFormat( &c_dfDIMouse2 ) ) ) + return hr; + + if( FAILED( hr = PSGLOBAL(mouse)->SetCooperativeLevel( PSGLOBAL(window), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ) ) ) + return hr; + + // Acquire the newly created device + PSGLOBAL(mouse)->Acquire(); + + return S_OK; +} + +RwV2d leftStickPos; +RwV2d rightStickPos; + +HRESULT CapturePad(RwInt32 padID) +{ + HRESULT hr; + DIJOYSTATE2 js; + LPDIRECTINPUTDEVICE8 *pPad = nil; + + if( padID == 0 ) + pPad = &PSGLOBAL(joy1); + else if( padID == 1) + pPad = &PSGLOBAL(joy2); + else + assert("invalid padID"); + + if ( nil == (*pPad) ) + return S_OK; + + // Poll the device to read the current state + hr = (*pPad)->Poll(); + + if( FAILED(hr) ) + { + // DInput is telling us that the input stream has been + // interrupted. We aren't tracking any state between polls, so + // we don't have any special reset that needs to be done. We + // just re-acquire and try again. + hr = (*pPad)->Acquire(); + while( hr == DIERR_INPUTLOST ) + hr = (*pPad)->Acquire(); + + // hr may be DIERR_OTHERAPPHASPRIO or other errors. This + // may occur when the app is minimized or in the process of + // switching, so just try again later + + if( FAILED(hr) ) + return hr; + + hr = (*pPad)->Poll(); + if( FAILED(hr) ) + return hr; + } + + // Get the input's device state + if( FAILED( hr = (*pPad)->GetDeviceState( sizeof(DIJOYSTATE2), &js ) ) ) + return hr; // The device should have been acquired during the Poll() + + if ( ControlsManager.m_bFirstCapture == true ) + { + memcpy(&ControlsManager.m_OldState, &js, sizeof(DIJOYSTATE2)); + memcpy(&ControlsManager.m_NewState, &js, sizeof(DIJOYSTATE2)); + + ControlsManager.m_bFirstCapture = false; + } + else + { + memcpy(&ControlsManager.m_OldState, &ControlsManager.m_NewState, sizeof(DIJOYSTATE2)); + memcpy(&ControlsManager.m_NewState, &js, sizeof(DIJOYSTATE2)); + } + + RsPadButtonStatus bs; + bs.padID = padID; + + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + + bool deviceAvailable = (*pPad) != nil; + + if ( deviceAvailable ) + { + leftStickPos.x = (float)js.lX / (float)((DEVICE_AXIS_MAX - DEVICE_AXIS_MIN) / 2); + leftStickPos.y = (float)js.lY / (float)((DEVICE_AXIS_MAX - DEVICE_AXIS_MIN) / 2); + + if (LOWORD(js.rgdwPOV[0]) != 0xFFFF) + { + float angle = DEGTORAD((float)js.rgdwPOV[0] / 100.0f); + + leftStickPos.x = Sin(angle); + leftStickPos.y = -Cos(angle); + } + + if ( AllValidWinJoys.m_aJoys[bs.padID].m_bHasAxisR && AllValidWinJoys.m_aJoys[bs.padID].m_bHasAxisZ ) + { + rightStickPos.x = (float)js.lZ / (float)((DEVICE_AXIS_MAX - DEVICE_AXIS_MIN) / 2); + rightStickPos.y = (float)js.lRz / (float)((DEVICE_AXIS_MAX - DEVICE_AXIS_MIN) / 2); + } + } + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + RsPadEventHandler(rsPADBUTTONUP, (void *)&bs); + RsPadEventHandler(rsPADBUTTONDOWN, (void *)&bs); + } + + { + if (CPad::m_bMapPadOneToPadTwo) + bs.padID = 1; + + CPad *pad = CPad::GetPad(bs.padID); + + if ( Abs(leftStickPos.x) > 0.3f ) + pad->PCTempJoyState.LeftStickX = (int32)(leftStickPos.x * 128.0f); + + if ( Abs(leftStickPos.y) > 0.3f ) + pad->PCTempJoyState.LeftStickY = (int32)(leftStickPos.y * 128.0f); + + if ( Abs(rightStickPos.x) > 0.3f ) + pad->PCTempJoyState.RightStickX = (int32)(rightStickPos.x * 128.0f); + + if ( Abs(rightStickPos.y) > 0.3f ) + pad->PCTempJoyState.RightStickY = (int32)(rightStickPos.y * 128.0f); + } + + return S_OK; +} + +void _InputInitialiseJoys() +{ + DIPROPDWORD prop; + DIDEVCAPS devCaps; + + for ( int32 i = 0; i < _TODOCONST(2); i++ ) + AllValidWinJoys.ClearJoyInfo(i); + + _InputAddJoys(); + + if ( PSGLOBAL(joy1) != nil ) + { + devCaps.dwSize = sizeof(DIDEVCAPS); + PSGLOBAL(joy1)->GetCapabilities(&devCaps); + + prop.diph.dwSize = sizeof(DIPROPDWORD); + prop.diph.dwHeaderSize = sizeof(DIPROPHEADER); + prop.diph.dwObj = 0; + prop.diph.dwHow = 0; + + PSGLOBAL(joy1)->GetProperty(DIPROP_VIDPID, (LPDIPROPHEADER)&prop); + AllValidWinJoys.m_aJoys[0].m_nVendorID = LOWORD(prop.dwData); + AllValidWinJoys.m_aJoys[0].m_nProductID = HIWORD(prop.dwData); + AllValidWinJoys.m_aJoys[0].m_bInitialised = true; + + ControlsManager.InitDefaultControlConfigJoyPad(devCaps.dwButtons); + } + + if ( PSGLOBAL(joy2) != nil ) + { + PSGLOBAL(joy2)->GetProperty(DIPROP_VIDPID, (LPDIPROPHEADER)&prop); + AllValidWinJoys.m_aJoys[1].m_nVendorID = LOWORD(prop.dwData); + AllValidWinJoys.m_aJoys[1].m_nProductID = HIWORD(prop.dwData); + AllValidWinJoys.m_aJoys[1].m_bInitialised = true; + } +} + +void _InputAddJoyStick(LPDIRECTINPUTDEVICE8 lpDevice, INT num) +{ + DIDEVICEOBJECTINSTANCE objInst; + + objInst.dwSize = sizeof( DIDEVICEOBJECTINSTANCE ); + + DIPROPRANGE range; + range.diph.dwSize = sizeof(DIPROPRANGE); + range.diph.dwHeaderSize = sizeof(DIPROPHEADER); + range.lMin = DEVICE_AXIS_MIN; + range.lMax = DEVICE_AXIS_MAX; + range.diph.dwHow = DIPH_BYOFFSET; + + // get the info about the object from the device + + range.diph.dwObj = DIJOFS_X; + if ( lpDevice != nil ) + { + if ( SUCCEEDED( lpDevice->GetObjectInfo( &objInst, DIJOFS_X, DIPH_BYOFFSET ) ) ) + { + if( FAILED( lpDevice->SetProperty( DIPROP_RANGE, (LPCDIPROPHEADER)&range ) ) ) + return; + else + ; + } + } + + range.diph.dwObj = DIJOFS_Y; + if ( lpDevice != nil ) + { + if ( SUCCEEDED( lpDevice->GetObjectInfo( &objInst, DIJOFS_Y, DIPH_BYOFFSET ) ) ) + { + if( FAILED( lpDevice->SetProperty( DIPROP_RANGE, (LPCDIPROPHEADER)&range ) ) ) + return; + else + ; + } + } + + range.diph.dwObj = DIJOFS_Z; + if ( lpDevice != nil ) + { + if ( SUCCEEDED( lpDevice->GetObjectInfo( &objInst, DIJOFS_Z, DIPH_BYOFFSET ) ) ) + { + if( FAILED( lpDevice->SetProperty( DIPROP_RANGE, (LPCDIPROPHEADER)&range ) ) ) + return; + else + AllValidWinJoys.m_aJoys[num].m_bHasAxisZ = true; // z rightStickPos.x + } + } + + range.diph.dwObj = DIJOFS_RZ; + if ( lpDevice != nil ) + { + if ( SUCCEEDED( lpDevice->GetObjectInfo( &objInst, DIJOFS_RZ, DIPH_BYOFFSET ) ) ) + { + if( FAILED( lpDevice->SetProperty( DIPROP_RANGE, (LPCDIPROPHEADER)&range ) ) ) + return; + else + AllValidWinJoys.m_aJoys[num].m_bHasAxisR = true; // r rightStickPos.y + } + } +} + +HRESULT _InputAddJoys() +{ + HRESULT hr; + + hr = PSGLOBAL(dinterface)->EnumDevices(DI8DEVCLASS_GAMECTRL, _InputEnumDevicesCallback, nil, DIEDFL_ATTACHEDONLY ); + + if( FAILED(hr) ) + return hr; + + if ( PSGLOBAL(joy1) == nil ) + return S_FALSE; + + _InputAddJoyStick(PSGLOBAL(joy1), 0); + + if ( PSGLOBAL(joy2) == nil ) + return S_OK; // we have one device already so return OK and ignore second + + _InputAddJoyStick(PSGLOBAL(joy2), 1); + + return S_OK; +} + +HRESULT _InputGetMouseState(DIMOUSESTATE2 *state) +{ + HRESULT hr; + + if ( PSGLOBAL(mouse) == nil ) + return S_FALSE; + + // Get the input's device state, and put the state in dims + ZeroMemory( state, sizeof(DIMOUSESTATE2) ); + + hr = PSGLOBAL(mouse)->GetDeviceState( sizeof(DIMOUSESTATE2), state ); + + if( FAILED(hr) ) + { + // DirectInput may be telling us that the input stream has been + // interrupted. We aren't tracking any state between polls, so + // we don't have any special reset that needs to be done. + // We just re-acquire and try again. + + // If input is lost then acquire and keep trying + hr = PSGLOBAL(mouse)->Acquire(); + while( hr == DIERR_INPUTLOST ) + hr = PSGLOBAL(mouse)->Acquire(); + + ZeroMemory( state, sizeof(DIMOUSESTATE2) ); + hr = PSGLOBAL(mouse)->GetDeviceState( sizeof(DIMOUSESTATE2), state ); + + return hr; + } + + return S_OK; +} + +void _InputShutdown() +{ + SAFE_RELEASE(PSGLOBAL(dinterface)); +} + +BOOL CALLBACK _InputEnumDevicesCallback( const DIDEVICEINSTANCE* pdidInstance, VOID* pContext ) +{ + HRESULT hr; + + static INT Count = 0; + + LPDIRECTINPUTDEVICE8 *pJoystick = nil; + + if ( Count == 0 ) + pJoystick = &PSGLOBAL(joy1); + else if ( Count == 1 ) + pJoystick = &PSGLOBAL(joy2); + else + assert("too many pads"); + + // Obtain an interface to the enumerated joystick. + hr = PSGLOBAL(dinterface)->CreateDevice( pdidInstance->guidInstance, pJoystick, nil ); + + // If it failed, then we can't use this joystick. (Maybe the user unplugged + // it while we were in the middle of enumerating it.) + if( hr != S_OK ) + return DIENUM_CONTINUE; + + hr = (*pJoystick)->SetDataFormat( &c_dfDIJoystick2 ); + if( hr != S_OK ) + { + (*pJoystick)->Release(); + return DIENUM_CONTINUE; + } + + ++Count; + + hr = (*pJoystick)->SetCooperativeLevel( PSGLOBAL(window), DISCL_NONEXCLUSIVE|DISCL_FOREGROUND ); + if( hr != S_OK ) + { + (*pJoystick)->Release(); +#ifdef FIX_BUGS + // BUG: enum will be called with Count == 2, which will write to a null pointer + // So decrement count again since we're not using this pad + --Count; +#endif + return DIENUM_CONTINUE; + } + + // Stop enumeration. Note: we're just taking the first two joysticks we get. You + // could store all the enumerated joysticks and let the user pick. + if ( Count == 2 ) + return DIENUM_STOP; + + return DIENUM_CONTINUE; +} + +BOOL _InputTranslateKey(RsKeyCodes *rs, UINT flag, UINT key) +{ + *rs = rsNULL; + + switch ( key ) + { + case VK_SHIFT: + { + if ( _dwOperatingSystemVersion == OS_WIN98 ) + *rs = rsSHIFT; + break; + } + + case VK_RETURN: + { + if ( _InputIsExtended(flag) ) + *rs = rsPADENTER; + else + *rs = rsENTER; + break; + } + + case VK_CONTROL: + { + if ( _InputIsExtended(flag) ) + *rs = rsRCTRL; + else + *rs = rsLCTRL; + break; + } + + case VK_MENU: + { + if ( _InputIsExtended(flag) ) + *rs = rsRALT; + else + *rs = rsLALT; + break; + } + + case VK_APPS: + { + *rs = rsAPPS; + break; + } + + case VK_PAUSE: + { + *rs = rsPAUSE; + break; + } + + case VK_CAPITAL: + { + *rs = rsCAPSLK; + break; + } + + case VK_ESCAPE: + { + *rs = rsESC; + break; + } + + case VK_PRIOR: + { + if ( _InputIsExtended(flag) ) + *rs = rsPGUP; + else + *rs = rsPADPGUP; + break; + } + + case VK_NEXT: + { + if ( _InputIsExtended(flag) ) + *rs = rsPGDN; + else + *rs = rsPADPGDN; + break; + } + + case VK_END: + { + if ( _InputIsExtended(flag) ) + *rs = rsEND; + else + *rs = rsPADEND; + break; + } + + case VK_HOME: + { + if ( _InputIsExtended(flag) ) + *rs = rsHOME; + else + *rs = rsPADHOME; + break; + } + + case VK_LEFT: + { + if ( _InputIsExtended(flag) ) + *rs = rsLEFT; + else + *rs = rsPADLEFT; + break; + } + + case VK_UP: + { + if ( _InputIsExtended(flag) ) + *rs = rsUP; + else + *rs = rsPADUP; + break; + } + + case VK_RIGHT: + { + if ( _InputIsExtended(flag) ) + *rs = rsRIGHT; + else + *rs = rsPADRIGHT; + break; + } + + case VK_DOWN: + { + if ( _InputIsExtended(flag) ) + *rs = rsDOWN; + else + *rs = rsPADDOWN; + break; + } + + case VK_INSERT: + { + if ( _InputIsExtended(flag) ) + *rs = rsINS; + else + *rs = rsPADINS; + break; + } + + case VK_DELETE: + { + if ( _InputIsExtended(flag) ) + *rs = rsDEL; + else + *rs = rsPADDEL; + break; + } + + case VK_LWIN: + { + *rs = rsLWIN; + break; + } + + case VK_RWIN: + { + *rs = rsRWIN; + break; + } + + case VK_NUMPAD0: + { + *rs = rsPADINS; + break; + } + + case VK_NUMPAD1: + { + *rs = rsPADEND; + break; + } + + case VK_NUMPAD2: + { + *rs = rsPADDOWN; + break; + } + + case VK_NUMPAD3: + { + *rs = rsPADPGDN; + break; + } + + case VK_NUMPAD4: + { + *rs = rsPADLEFT; + break; + } + + case VK_NUMPAD5: + { + *rs = rsPAD5; + break; + } + + case VK_NUMPAD6: + { + *rs = rsPADRIGHT; + break; + } + + case VK_NUMPAD7: + { + *rs = rsPADHOME; + break; + } + + case VK_NUMPAD8: + { + *rs = rsPADUP; + break; + } + + case VK_NUMPAD9: + { + *rs = rsPADPGUP; + break; + } + + case VK_MULTIPLY: + { + *rs = rsTIMES; + break; + } + + case VK_DIVIDE: + { + *rs = rsDIVIDE; + break; + } + + case VK_ADD: + { + *rs = rsPLUS; + break; + } + + case VK_SUBTRACT: + { + *rs = rsMINUS; + break; + } + + case VK_DECIMAL: + { + *rs = rsPADDEL; + break; + } + + case VK_F1: + { + *rs = rsF1; + break; + } + + case VK_F2: + { + *rs = rsF2; + break; + } + + case VK_F3: + { + *rs = rsF3; + break; + } + + case VK_F4: + { + *rs = rsF4; + break; + } + + case VK_F5: + { + *rs = rsF5; + break; + } + + case VK_F6: + { + *rs = rsF6; + break; + } + + case VK_F7: + { + *rs = rsF7; + break; + } + + case VK_F8: + { + *rs = rsF8; + break; + } + + case VK_F9: + { + *rs = rsF9; + break; + } + + case VK_F10: + { + *rs = rsF10; + break; + } + + case VK_F11: + { + *rs = rsF11; + break; + } + + case VK_F12: + { + *rs = rsF12; + break; + } + + case VK_NUMLOCK: + { + *rs = rsNUMLOCK; + break; + } + + case VK_SCROLL: + { + *rs = rsSCROLL; + break; + } + + case VK_BACK: + { + *rs = rsBACKSP; + break; + } + + case VK_TAB: + { + *rs = rsTAB; + break; + } + + default: + { + UINT vkey = MapVirtualKey(key, MAPVK_VK_TO_CHAR) & 0xFFFF; + if ( vkey < 255 ) + *rs = (RsKeyCodes)vkey; + break; + } + } + + return *rs != rsNULL; +} + +void _InputTranslateShiftKeyUpDown(RsKeyCodes *rs) +{ + if ( _dwOperatingSystemVersion != OS_WIN98 ) + { + if ( _InputTranslateShiftKey(rs, VK_LSHIFT, TRUE) ) + RsKeyboardEventHandler(rsKEYDOWN, rs); + if ( _InputTranslateShiftKey(rs, VK_RSHIFT, TRUE) ) + RsKeyboardEventHandler(rsKEYDOWN, rs); + if ( _InputTranslateShiftKey(rs, VK_LSHIFT, FALSE) ) + RsKeyboardEventHandler(rsKEYUP, rs); + if ( _InputTranslateShiftKey(rs, VK_RSHIFT, FALSE) ) + RsKeyboardEventHandler(rsKEYUP, rs); + } +} + +BOOL _InputTranslateShiftKey(RsKeyCodes *rs, UINT key, BOOLEAN bDown) +{ + *rs = rsNULL; + switch ( key ) + { + case VK_LSHIFT: + { + if ( bDown == (GetKeyState(VK_LSHIFT) & 0x8000) >> 15 ) + *rs = rsLSHIFT; + break; + } + + case VK_RSHIFT: + { + if ( bDown == (GetKeyState(VK_RSHIFT) & 0x8000) >> 15 ) + *rs = rsRSHIFT; + break; + } + + default: + { + return *rs != rsNULL; + } + } + + return TRUE; +} + +BOOL _InputIsExtended(INT flag) +{ + return (flag & 0x1000000) != 0; +} + +#if (defined(_MSC_VER)) +int strcasecmp(const char *str1, const char *str2) +{ + return _strcmpi(str1, str2); +} +#endif +#endif diff --git a/src/skel/win/win.h b/src/skel/win/win.h new file mode 100644 index 0000000..be84089 --- /dev/null +++ b/src/skel/win/win.h @@ -0,0 +1,91 @@ + +// DON'T include directly. crossplatform.h includes this if you're using D3D9 backend(win.cpp). + +#if (!defined(_PLATFORM_WIN_H)) +#define _PLATFORM_WIN_H + +#if (!defined(RSREGSETBREAKALLOC)) +#define RSREGSETBREAKALLOC(_name) /* No op */ +#endif /* (!defined(RSREGSETBREAKALLOC)) */ + +#ifdef __DINPUT_INCLUDED__ +/* platform specfic global data */ +typedef struct +{ + HWND window; + HINSTANCE instance; + RwBool fullScreen; + RwV2d lastMousePos; + + DWORD field_14; + + LPDIRECTINPUT8 dinterface; + LPDIRECTINPUTDEVICE8 mouse; + LPDIRECTINPUTDEVICE8 joy1; + LPDIRECTINPUTDEVICE8 joy2; +} +psGlobalType; + +#define PSGLOBAL(var) (((psGlobalType *)(RsGlobal.ps))->var) + +enum eJoypads +{ + JOYSTICK1 = 0, + JOYSTICK2, + MAX_JOYSTICKS +}; + +enum eJoypadState +{ + JOYPAD_UNUSED, + JOYPAD_ATTACHED, +}; + +struct tJoy +{ + eJoypadState m_State; + bool m_bInitialised; + bool m_bHasAxisZ; + bool m_bHasAxisR; + int m_nVendorID; + int m_nProductID; +}; + +class CJoySticks +{ +public: + tJoy m_aJoys[MAX_JOYSTICKS]; + + CJoySticks(); + void ClearJoyInfo(int joyID); +}; + +extern CJoySticks AllValidWinJoys; +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#ifdef __DINPUT_INCLUDED__ +HRESULT _InputInitialise(); +HRESULT CapturePad(RwInt32 padID); +void _InputAddJoyStick(LPDIRECTINPUTDEVICE8 lpDevice, INT num); +HRESULT _InputAddJoys(); +HRESULT _InputGetMouseState(DIMOUSESTATE2 *state); +void _InputShutdown(); +BOOL CALLBACK _InputEnumDevicesCallback( const DIDEVICEINSTANCE* pdidInstance, VOID* pContext ); +BOOL _InputTranslateKey(RsKeyCodes *rs, UINT flag, UINT key); +BOOL _InputTranslateShiftKey(RsKeyCodes *rs, UINT key, BOOLEAN bDown); +BOOL _InputIsExtended(INT flag); +#endif + +void CenterVideo(void); +void CloseClip(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* (!defined(_PLATFORM_WIN_H)) */ diff --git a/src/skel/win/win.rc b/src/skel/win/win.rc new file mode 100644 index 0000000..379c473 --- /dev/null +++ b/src/skel/win/win.rc @@ -0,0 +1,47 @@ +#include "resource.h" + +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +//#if !defined(__GNU_C__) +//#include "afxres.h" +//#else +#include "winresrc.h" +//#endif /* !defined(__GNU_C__) */ + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 186, 90 +STYLE DS_MODALFRAME | DS_CENTER | DS_CENTERMOUSE | WS_POPUP | WS_CAPTION | + WS_SYSMENU +CAPTION "Device Selection" +FONT 8, "MS Sans Serif" +BEGIN + COMBOBOX IDC_DEVICESEL,7,25,172,33,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + COMBOBOX IDC_VIDMODE,7,46,172,74,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + DEFPUSHBUTTON "EXIT",IDEXIT,103,69,52,14 + DEFPUSHBUTTON "OK",IDOK,28,69,50,14 + LTEXT "Please select the Device To Use:",IDC_SELECTDEVICE,7,7, + 137,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_MAIN_ICON ICON DISCARDABLE "gta3.ico" + +///////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/src/text/Messages.cpp b/src/text/Messages.cpp new file mode 100644 index 0000000..b68f918 --- /dev/null +++ b/src/text/Messages.cpp @@ -0,0 +1,815 @@ +#include "common.h" + +#include "Messages.h" +#include "RwHelper.h" +#include "Hud.h" +#include "User.h" +#include "Timer.h" +#include "Text.h" + +#include "ControllerConfig.h" + +#include "Font.h" + +tMessage CMessages::BriefMessages[NUMBRIEFMESSAGES]; +tPreviousBrief CMessages::PreviousBriefs[NUMPREVIOUSBRIEFS]; +tBigMessage CMessages::BIGMessages[NUMBIGMESSAGES]; +char CMessages::PreviousMissionTitle[16]; // unused + +void +CMessages::Init() +{ + ClearMessages(); + + for (int32 i = 0; i < NUMPREVIOUSBRIEFS; i++) { + PreviousBriefs[i].m_pText = nil; + PreviousBriefs[i].m_pString = nil; + } +} + +uint16 +CMessages::GetWideStringLength(wchar *src) +{ + uint16 length = 0; + while (*(src++)) length++; + return length; +} + +void +CMessages::WideStringCopy(wchar *dst, wchar *src, uint16 size) +{ + int32 i = 0; + if (src) { + while (i < size - 1) { + if (!src[i]) break; + dst[i] = src[i]; + i++; + } + } else { + while (i < size - 1) + dst[i++] = '\0'; + } + dst[i] = '\0'; +} + +wchar FixupChar(wchar c) +{ +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + return c & 0x7fff; +#endif + return c; +} + +bool +CMessages::WideStringCompare(wchar *str1, wchar *str2, uint16 size) +{ + uint16 len1 = GetWideStringLength(str1); + uint16 len2 = GetWideStringLength(str2); + if (len1 != len2 && (len1 < size || len2 < size)) + return false; + + for (int32 i = 0; i < size && FixupChar(str1[i]) != '\0'; i++) { + if (FixupChar(str1[i]) != FixupChar(str2[i])) + return false; + } + return true; +} + +void +CMessages::Process() +{ + for (int32 style = 0; style < 6; style++) { + if (BIGMessages[style].m_Stack[0].m_pText != nil && CTimer::GetTimeInMilliseconds() > BIGMessages[style].m_Stack[0].m_nTime + BIGMessages[style].m_Stack[0].m_nStartTime) { + BIGMessages[style].m_Stack[0].m_pText = nil; + + int32 i = 0; + while (i < 3) { + if (BIGMessages[style].m_Stack[i + 1].m_pText == nil) break; + BIGMessages[style].m_Stack[i] = BIGMessages[style].m_Stack[i + 1]; + i++; + } + + BIGMessages[style].m_Stack[i].m_pText = nil; + BIGMessages[style].m_Stack[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + } + } + + if (BriefMessages[0].m_pText != nil && CTimer::GetTimeInMilliseconds() > BriefMessages[0].m_nTime + BriefMessages[0].m_nStartTime) { + BriefMessages[0].m_pText = nil; + int32 i; + for (i = 0; i < NUMBRIEFMESSAGES-1 && BriefMessages[i + 1].m_pText != nil; i++) { + BriefMessages[i] = BriefMessages[i + 1]; + } + CMessages::BriefMessages[i].m_pText = nil; + CMessages::BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + if (BriefMessages[0].m_pText != nil) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); + } +} + +void +CMessages::Display() +{ + wchar outstr[256]; + + DefinedState(); + + for (int32 i = 0; i < NUMBIGMESSAGES; i++) { + InsertNumberInString( + BIGMessages[i].m_Stack[0].m_pText, + BIGMessages[i].m_Stack[0].m_nNumber[0], + BIGMessages[i].m_Stack[0].m_nNumber[1], + BIGMessages[i].m_Stack[0].m_nNumber[2], + BIGMessages[i].m_Stack[0].m_nNumber[3], + BIGMessages[i].m_Stack[0].m_nNumber[4], + BIGMessages[i].m_Stack[0].m_nNumber[5], + outstr); + InsertStringInString(outstr, BIGMessages[i].m_Stack[0].m_pString); + InsertPlayerControlKeysInString(outstr); + CHud::SetBigMessage(outstr, i); + } + + InsertNumberInString( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + outstr); + InsertStringInString(outstr, BriefMessages[0].m_pString); + InsertPlayerControlKeysInString(outstr); + CHud::SetMessage(outstr); +} + +void +CMessages::AddMessage(wchar *msg, uint32 time, uint16 flag) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + int32 i = 0; + while (i < NUMBRIEFMESSAGES && BriefMessages[i].m_pText != nil) + i++; + if (i >= NUMBRIEFMESSAGES) return; + + BriefMessages[i].m_pText = msg; + BriefMessages[i].m_nFlag = flag; + BriefMessages[i].m_nTime = time; + BriefMessages[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[i].m_nNumber[0] = -1; + BriefMessages[i].m_nNumber[1] = -1; + BriefMessages[i].m_nNumber[2] = -1; + BriefMessages[i].m_nNumber[3] = -1; + BriefMessages[i].m_nNumber[4] = -1; + BriefMessages[i].m_nNumber[5] = -1; + BriefMessages[i].m_pString = nil; + if (i == 0) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); +} + +void +CMessages::AddMessageJumpQ(wchar *msg, uint32 time, uint16 flag) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BriefMessages[0].m_pText = msg; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = -1; + BriefMessages[0].m_nNumber[1] = -1; + BriefMessages[0].m_nNumber[2] = -1; + BriefMessages[0].m_nNumber[3] = -1; + BriefMessages[0].m_nNumber[4] = -1; + BriefMessages[0].m_nNumber[5] = -1; + BriefMessages[0].m_pString = nil; + AddToPreviousBriefArray(msg, -1, -1, -1, -1, -1, -1, 0); +} + +void +CMessages::AddMessageSoon(wchar *msg, uint32 time, uint16 flag) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + if (BriefMessages[0].m_pText != nil) { + for (int i = NUMBRIEFMESSAGES-1; i > 1; i--) + BriefMessages[i] = BriefMessages[i-1]; + + BriefMessages[1].m_pText = msg; + BriefMessages[1].m_nFlag = flag; + BriefMessages[1].m_nTime = time; + BriefMessages[1].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[1].m_nNumber[0] = -1; + BriefMessages[1].m_nNumber[1] = -1; + BriefMessages[1].m_nNumber[2] = -1; + BriefMessages[1].m_nNumber[3] = -1; + BriefMessages[1].m_nNumber[4] = -1; + BriefMessages[1].m_nNumber[5] = -1; + BriefMessages[1].m_pString = nil; + }else{ + BriefMessages[0].m_pText = msg; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = -1; + BriefMessages[0].m_nNumber[1] = -1; + BriefMessages[0].m_nNumber[2] = -1; + BriefMessages[0].m_nNumber[3] = -1; + BriefMessages[0].m_nNumber[4] = -1; + BriefMessages[0].m_nNumber[5] = -1; + BriefMessages[0].m_pString = nil; + AddToPreviousBriefArray(msg, -1, -1, -1, -1, -1, -1, nil); + } +} + +void +CMessages::ClearMessages() +{ + for (int32 i = 0; i < NUMBIGMESSAGES; i++) { + for (int32 j = 0; j < 4; j++) { + BIGMessages[i].m_Stack[j].m_pText = nil; + BIGMessages[i].m_Stack[j].m_pString = nil; + } + } + ClearSmallMessagesOnly(); +} + +void +CMessages::ClearSmallMessagesOnly() +{ + for (int32 i = 0; i < NUMBRIEFMESSAGES; i++) { + BriefMessages[i].m_pText = nil; + BriefMessages[i].m_pString = nil; + } +} + +void +CMessages::AddBigMessage(wchar *msg, uint32 time, uint16 style) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BIGMessages[style].m_Stack[0].m_pText = msg; + BIGMessages[style].m_Stack[0].m_nFlag = 0; + BIGMessages[style].m_Stack[0].m_nTime = time; + BIGMessages[style].m_Stack[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BIGMessages[style].m_Stack[0].m_nNumber[0] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[1] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[2] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[3] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[4] = -1; + BIGMessages[style].m_Stack[0].m_nNumber[5] = -1; + BIGMessages[style].m_Stack[0].m_pString = nil; +} +void +CMessages::AddBigMessageQ(wchar *msg, uint32 time, uint16 style) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, msg, 256); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + int32 i = 0; + while (i < 4 && BIGMessages[style].m_Stack[i].m_pText != nil) + i++; + + if (i >= 4) return; + + BIGMessages[style].m_Stack[i].m_pText = msg; + BIGMessages[style].m_Stack[i].m_nFlag = 0; + BIGMessages[style].m_Stack[i].m_nTime = time; + BIGMessages[style].m_Stack[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BIGMessages[style].m_Stack[i].m_nNumber[0] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[1] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[2] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[3] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[4] = -1; + BIGMessages[style].m_Stack[i].m_nNumber[5] = -1; + BIGMessages[style].m_Stack[i].m_pString = nil; +} + +void +CMessages::AddToPreviousBriefArray(wchar *text, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *string) +{ + int32 i = 0; + for (i = 0; i < NUMPREVIOUSBRIEFS && PreviousBriefs[i].m_pText != nil; i++) { + if (PreviousBriefs[i].m_nNumber[0] == n1 + && PreviousBriefs[i].m_nNumber[1] == n2 + && PreviousBriefs[i].m_nNumber[2] == n3 + && PreviousBriefs[i].m_nNumber[3] == n4 + && PreviousBriefs[i].m_nNumber[4] == n5 + && PreviousBriefs[i].m_nNumber[5] == n6 + && PreviousBriefs[i].m_pText == text + && PreviousBriefs[i].m_pString == string) + return; + } + + if (i != 0) { + if (i == NUMPREVIOUSBRIEFS) i -= 2; + else i--; + + while (i >= 0) { + PreviousBriefs[i + 1] = PreviousBriefs[i]; + i--; + } + } + PreviousBriefs[0].m_pText = text; + PreviousBriefs[0].m_nNumber[0] = n1; + PreviousBriefs[0].m_nNumber[1] = n2; + PreviousBriefs[0].m_nNumber[2] = n3; + PreviousBriefs[0].m_nNumber[3] = n4; + PreviousBriefs[0].m_nNumber[4] = n5; + PreviousBriefs[0].m_nNumber[5] = n6; + PreviousBriefs[0].m_pString = string; +} + +void +CMessages::InsertNumberInString(wchar *str, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *outstr) +{ + char numStr[10]; + wchar wNumStr[10]; + + if (str == nil) { + *outstr = '\0'; + return; + } + + sprintf(numStr, "%d", n1); + size_t outLen = strlen(numStr); + AsciiToUnicode(numStr, wNumStr); + if (str[0] == 0) { + *outstr = '\0'; + return; + } + + int32 size = GetWideStringLength(str); + + int32 i = 0; + + for (int32 c = 0; c < size;) { +#ifdef MORE_LANGUAGES + if ((CFont::IsJapanese() && str[c] == (0x8000 | '~') && str[c + 1] == (0x8000 | '1') && str[c + 2] == (0x8000 | '~')) || + (!CFont::IsJapanese() && str[c] == '~' && str[c + 1] == '1' && str[c + 2] == '~')) { +#else + if (str[c] == '~' && str[c + 1] == '1' && str[c + 2] == '~') { +#endif + c += 3; + for (int j = 0; j < outLen; ) + *(outstr++) = wNumStr[j++]; + + i++; + switch (i) { + case 1: sprintf(numStr, "%d", n2); break; + case 2: sprintf(numStr, "%d", n3); break; + case 3: sprintf(numStr, "%d", n4); break; + case 4: sprintf(numStr, "%d", n5); break; + case 5: sprintf(numStr, "%d", n6); break; + } + outLen = strlen(numStr); + AsciiToUnicode(numStr, wNumStr); + } else { + *(outstr++) = str[c++]; + } + } + *outstr = '\0'; +} + +void +CMessages::InsertStringInString(wchar *str1, wchar *str2) +{ + wchar tempstr[256]; + + if (!str1 || !str2) return; + + int32 str1_size = GetWideStringLength(str1); + int32 str2_size = GetWideStringLength(str2); + int32 total_size = str1_size + str2_size; + + wchar *_str1 = str1; + uint16 i; + for (i = 0; i < total_size; ) { +#ifdef MORE_LANGUAGES + if ((CFont::IsJapanese() && *_str1 == (0x8000 | '~') && *(_str1 + 1) == (0x8000 | 'a') && *(_str1 + 2) == (0x8000 | '~')) + || (*_str1 == '~' && *(_str1 + 1) == 'a' && *(_str1 + 2) == '~')) + { +#else + if (*_str1 == '~' && *(_str1 + 1) == 'a' && *(_str1 + 2) == '~') { +#endif + _str1 += 3; + for (int j = 0; j < str2_size; j++) { + tempstr[i++] = str2[j]; + } + } else { + tempstr[i++] = *(_str1++); + } + } + tempstr[i] = '\0'; + + for (i = 0; i < total_size; i++) + str1[i] = tempstr[i]; + + while (i < 256) + str1[i++] = '\0'; +} + +void +CMessages::InsertPlayerControlKeysInString(wchar *str) +{ + uint16 i; + wchar outstr[256]; + wchar keybuf[256]; + + if (!str) return; + uint16 strSize = GetWideStringLength(str); + memset(keybuf, 0, 256*sizeof(wchar)); + + wchar *_outstr = outstr; + for (i = 0; i < strSize;) { +#ifdef MORE_LANGUAGES + if ((CFont::IsJapanese() && str[i] == (0x8000 | '~') && str[i + 1] == (0x8000 | 'k') && str[i + 2] == (0x8000 | '~')) || + (!CFont::IsJapanese() && str[i] == '~' && str[i + 1] == 'k' && str[i + 2] == '~')) { +#else + if (str[i] == '~' && str[i + 1] == 'k' && str[i + 2] == '~') { +#endif + i += 4; + bool done = false; + for (int32 cont = 0; cont < MAX_CONTROLLERACTIONS && !done; cont++) { + uint16 contSize = GetWideStringLength(ControlsManager.m_aActionNames[cont]); + if (contSize != 0) { + if (WideStringCompare(&str[i], ControlsManager.m_aActionNames[cont], contSize)) { + done = true; + ControlsManager.GetWideStringOfCommandKeys(cont, keybuf, 256); + uint16 keybuf_size = GetWideStringLength(keybuf); + for (uint16 j = 0; j < keybuf_size; j++) { + *(_outstr++) = keybuf[j]; + keybuf[j] = '\0'; + } + i += contSize + 1; + } + } + } + } else { + *(_outstr++) = str[i++]; + } + } + *_outstr = '\0'; + + for (i = 0; i < GetWideStringLength(outstr); i++) + str[i] = outstr[i]; + + while (i < 256) + str[i++] = '\0'; +} + +void +CMessages::AddMessageWithNumber(wchar *str, uint32 time, uint16 flag, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + uint16 i = 0; + while (i < NUMBRIEFMESSAGES && BriefMessages[i].m_pText != nil) + i++; + + if (i >= NUMBRIEFMESSAGES) return; + + BriefMessages[i].m_pText = str; + BriefMessages[i].m_nFlag = flag; + BriefMessages[i].m_nTime = time; + BriefMessages[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[i].m_nNumber[0] = n1; + BriefMessages[i].m_nNumber[1] = n2; + BriefMessages[i].m_nNumber[2] = n3; + BriefMessages[i].m_nNumber[3] = n4; + BriefMessages[i].m_nNumber[4] = n5; + BriefMessages[i].m_nNumber[5] = n6; + BriefMessages[i].m_pString = nil; + if (i == 0) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); +} + +void +CMessages::AddMessageJumpQWithNumber(wchar *str, uint32 time, uint16 flag, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BriefMessages[0].m_pText = str; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = n1; + BriefMessages[0].m_nNumber[1] = n2; + BriefMessages[0].m_nNumber[2] = n3; + BriefMessages[0].m_nNumber[3] = n4; + BriefMessages[0].m_nNumber[4] = n5; + BriefMessages[0].m_nNumber[5] = n6; + BriefMessages[0].m_pString = nil; + AddToPreviousBriefArray(str, n1, n2, n3, n4, n5, n6, nil); +} + +void +CMessages::AddMessageSoonWithNumber(wchar *str, uint32 time, uint16 flag, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + if (BriefMessages[0].m_pText != nil) { + for (int32 i = NUMBRIEFMESSAGES-1; i > 1; i--) + BriefMessages[i] = BriefMessages[i-1]; + + BriefMessages[1].m_pText = str; + BriefMessages[1].m_nFlag = flag; + BriefMessages[1].m_nTime = time; + BriefMessages[1].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[1].m_nNumber[0] = n1; + BriefMessages[1].m_nNumber[1] = n2; + BriefMessages[1].m_nNumber[2] = n3; + BriefMessages[1].m_nNumber[3] = n4; + BriefMessages[1].m_nNumber[4] = n5; + BriefMessages[1].m_nNumber[5] = n6; + BriefMessages[1].m_pString = nil; + } else { + BriefMessages[0].m_pText = str; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = n1; + BriefMessages[0].m_nNumber[1] = n2; + BriefMessages[0].m_nNumber[2] = n3; + BriefMessages[0].m_nNumber[3] = n4; + BriefMessages[0].m_nNumber[4] = n5; + BriefMessages[0].m_nNumber[5] = n6; + BriefMessages[0].m_pString = nil; + AddToPreviousBriefArray(str, n1, n2, n3, n4, n5, n6, nil); + } +} + +void +CMessages::AddBigMessageWithNumber(wchar *str, uint32 time, uint16 style, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BIGMessages[style].m_Stack[0].m_pText = str; + BIGMessages[style].m_Stack[0].m_nFlag = 0; + BIGMessages[style].m_Stack[0].m_nTime = time; + BIGMessages[style].m_Stack[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BIGMessages[style].m_Stack[0].m_nNumber[0] = n1; + BIGMessages[style].m_Stack[0].m_nNumber[1] = n2; + BIGMessages[style].m_Stack[0].m_nNumber[2] = n3; + BIGMessages[style].m_Stack[0].m_nNumber[3] = n4; + BIGMessages[style].m_Stack[0].m_nNumber[4] = n5; + BIGMessages[style].m_Stack[0].m_nNumber[5] = n6; + BIGMessages[style].m_Stack[0].m_pString = nil; +} + +void +CMessages::AddBigMessageWithNumberQ(wchar *str, uint32 time, uint16 style, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6) +{ + wchar outstr[512]; // unused + InsertNumberInString(str, n1, n2, n3, n4, n5, n6, outstr); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + int32 i = 0; + + while (i < 4 && BIGMessages[style].m_Stack[i].m_pText != nil) + i++; + + if (i >= 4) return; + + BIGMessages[style].m_Stack[i].m_pText = str; + BIGMessages[style].m_Stack[i].m_nFlag = 0; + BIGMessages[style].m_Stack[i].m_nTime = time; + BIGMessages[style].m_Stack[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BIGMessages[style].m_Stack[i].m_nNumber[0] = n1; + BIGMessages[style].m_Stack[i].m_nNumber[1] = n2; + BIGMessages[style].m_Stack[i].m_nNumber[2] = n3; + BIGMessages[style].m_Stack[i].m_nNumber[3] = n4; + BIGMessages[style].m_Stack[i].m_nNumber[4] = n5; + BIGMessages[style].m_Stack[i].m_nNumber[5] = n6; + BIGMessages[style].m_Stack[i].m_pString = nil; +} + +void +CMessages::AddMessageWithString(wchar *text, uint32 time, uint16 flag, wchar *str) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, text, 256); + InsertStringInString(outstr, str); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + int32 i = 0; + while (i < NUMBRIEFMESSAGES && BriefMessages[i].m_pText != nil) + i++; + + if (i >= NUMBRIEFMESSAGES) return; + + BriefMessages[i].m_pText = text; + BriefMessages[i].m_nFlag = flag; + BriefMessages[i].m_nTime = time; + BriefMessages[i].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[i].m_nNumber[0] = -1; + BriefMessages[i].m_nNumber[1] = -1; + BriefMessages[i].m_nNumber[2] = -1; + BriefMessages[i].m_nNumber[3] = -1; + BriefMessages[i].m_nNumber[4] = -1; + BriefMessages[i].m_nNumber[5] = -1; + BriefMessages[i].m_pString = str; + if (i == 0) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); +} + +void +CMessages::AddMessageJumpQWithString(wchar *text, uint32 time, uint16 flag, wchar *str) +{ + wchar outstr[512]; // unused + WideStringCopy(outstr, text, 256); + InsertStringInString(outstr, str); + InsertPlayerControlKeysInString(outstr); + GetWideStringLength(outstr); + + BriefMessages[0].m_pText = text; + BriefMessages[0].m_nFlag = flag; + BriefMessages[0].m_nTime = time; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + BriefMessages[0].m_nNumber[0] = -1; + BriefMessages[0].m_nNumber[1] = -1; + BriefMessages[0].m_nNumber[2] = -1; + BriefMessages[0].m_nNumber[3] = -1; + BriefMessages[0].m_nNumber[4] = -1; + BriefMessages[0].m_nNumber[5] = -1; + BriefMessages[0].m_pString = str; + AddToPreviousBriefArray(text, -1, -1, -1, -1, -1, -1, str); +} + +inline bool +FastWideStringComparison(wchar *str1, wchar *str2) +{ + while (*str1 == *str2) { + ++str1; + ++str2; + if (!*str1 && !*str2) return true; + } + return false; +} + +void +CMessages::ClearThisPrint(wchar *str) +{ + bool equal; + + do { + equal = false; + uint16 i; + for (i = 0; i < NUMBRIEFMESSAGES && BriefMessages[i].m_pText != nil; i++) { + equal = FastWideStringComparison(str, BriefMessages[i].m_pText); + + if (equal) break; + } + + if (equal) { + if (i != 0) { + BriefMessages[i].m_pText = nil; + for (; i < NUMBRIEFMESSAGES-1 && BriefMessages[i+1].m_pText != nil; i++) { + BriefMessages[i] = BriefMessages[i + 1]; + } + BriefMessages[i].m_pText = nil; + } else { + BriefMessages[0].m_pText = nil; + for (; i < NUMBRIEFMESSAGES-1 && BriefMessages[i+1].m_pText != nil; i++) { + BriefMessages[i] = BriefMessages[i + 1]; + } + BriefMessages[i].m_pText = nil; + BriefMessages[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + if (BriefMessages[0].m_pText != nil) + AddToPreviousBriefArray( + BriefMessages[0].m_pText, + BriefMessages[0].m_nNumber[0], + BriefMessages[0].m_nNumber[1], + BriefMessages[0].m_nNumber[2], + BriefMessages[0].m_nNumber[3], + BriefMessages[0].m_nNumber[4], + BriefMessages[0].m_nNumber[5], + BriefMessages[0].m_pString); + } + } + } while (equal); +} + +void +CMessages::ClearThisBigPrint(wchar *str) +{ + bool equal; + + do { + uint16 i = 0; + equal = false; + uint16 style = 0; + while (style < NUMBIGMESSAGES) + { + if (i >= 4) + break; + + if (CMessages::BIGMessages[style].m_Stack[i].m_pText == nil || equal) + break; + + equal = FastWideStringComparison(str, BIGMessages[style].m_Stack[i].m_pText); + + if (!equal && ++i == 4) { + i = 0; + style++; + } + } + if (equal) { + if (i != 0) { + BIGMessages[style].m_Stack[i].m_pText = nil; + while (i < 3) { + if (BIGMessages[style].m_Stack[i + 1].m_pText == nil) + break; + BIGMessages[style].m_Stack[i] = BIGMessages[style].m_Stack[i + 1]; + i++; + } + BIGMessages[style].m_Stack[i].m_pText = nil; + } else { + BIGMessages[style].m_Stack[0].m_pText = nil; + i = 0; + while (i < 3) { + if (BIGMessages[style].m_Stack[i + 1].m_pText == nil) + break; + BIGMessages[style].m_Stack[i] = BIGMessages[style].m_Stack[i + 1]; + i++; + } + BIGMessages[style].m_Stack[i].m_pText = nil; + BIGMessages[style].m_Stack[0].m_nStartTime = CTimer::GetTimeInMilliseconds(); + } + } + } while (equal); +} + +void +CMessages::ClearAllMessagesDisplayedByGame() +{ + ClearMessages(); + for (int32 i = 0; i < NUMPREVIOUSBRIEFS; i++) { + PreviousBriefs[i].m_pText = nil; + PreviousBriefs[i].m_pString = nil; + } + CHud::GetRidOfAllHudMessages(); + CUserDisplay::Pager.ClearMessages(); +} diff --git a/src/text/Messages.h b/src/text/Messages.h new file mode 100644 index 0000000..e8ba1bf --- /dev/null +++ b/src/text/Messages.h @@ -0,0 +1,69 @@ +#pragma once + +struct tMessage +{ + wchar *m_pText; + uint16 m_nFlag; + uint32 m_nTime; + uint32 m_nStartTime; + int32 m_nNumber[6]; + wchar *m_pString; +}; + +struct tBigMessage +{ + tMessage m_Stack[4]; +}; + +struct tPreviousBrief +{ + wchar *m_pText; + int32 m_nNumber[6]; + wchar *m_pString; +}; + +#define NUMBRIEFMESSAGES 8 +#define NUMBIGMESSAGES 6 +#define NUMPREVIOUSBRIEFS 5 + +class CMessages +{ +public: + static tMessage BriefMessages[NUMBRIEFMESSAGES]; + static tBigMessage BIGMessages[NUMBIGMESSAGES]; + static tPreviousBrief PreviousBriefs[NUMPREVIOUSBRIEFS]; + static char PreviousMissionTitle[16]; // unused +public: + static void Init(void); + static uint16 GetWideStringLength(wchar *src); + static void WideStringCopy(wchar *dst, wchar *src, uint16 size); + static bool WideStringCompare(wchar *str1, wchar *str2, uint16 size); + static void Process(void); + static void Display(void); + static void AddMessage(wchar *key, uint32 time, uint16 pos); + static void AddMessageJumpQ(wchar *key, uint32 time, uint16 pos); + static void AddMessageSoon(wchar *key, uint32 time, uint16 pos); + static void ClearMessages(void); + static void ClearSmallMessagesOnly(void); + static void AddBigMessage(wchar *key, uint32 time, uint16 pos); + static void AddBigMessageQ(wchar *key, uint32 time, uint16 pos); + static void AddToPreviousBriefArray(wchar *text, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *string); + static void InsertNumberInString(wchar *src, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, wchar *dst); + static void InsertStringInString(wchar *str1, wchar *str2); + static void InsertPlayerControlKeysInString(wchar *src); + static void AddMessageWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddMessageJumpQWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddMessageSoonWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddBigMessageWithNumber(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddBigMessageWithNumberQ(wchar *key, uint32 time, uint16 pos, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6); + static void AddMessageWithString(wchar *text, uint32 time, uint16 flag, wchar *str); + static void AddMessageJumpQWithString(wchar *text, uint32 time, uint16 flag, wchar *str); + static void ClearThisPrint(wchar *str); + static void ClearThisBigPrint(wchar *str); + static void ClearAllMessagesDisplayedByGame(void); + + // unused or cut + //static void AddMessageSoonWithString(wchar*, uint32, uint16, wchar*); + //static void CutString(int16, char*, char**); + //static void PrintString(char*, int16, int16, int16); +}; diff --git a/src/text/Pager.cpp b/src/text/Pager.cpp new file mode 100644 index 0000000..609c686 --- /dev/null +++ b/src/text/Pager.cpp @@ -0,0 +1,184 @@ +#include "common.h" + +#include "Pager.h" +#include "Timer.h" +#include "Messages.h" +#include "Hud.h" +#include "Camera.h" + +void +CPager::Init() +{ + ClearMessages(); + m_nNumDisplayLetters = 8; +} + +void +CPager::Process() +{ + if (m_messages[0].m_pText != nil && m_messages[0].m_nCurrentPosition >= (int32)m_messages[0].m_nStringLength) { + m_messages[0].m_pText = nil; + uint16 i = 0; + while (i < NUMPAGERMESSAGES-1) { + if (m_messages[i + 1].m_pText == nil) break; + m_messages[i] = m_messages[i + 1]; + i++; + } + m_messages[i].m_pText = nil; + if (m_messages[0].m_pText != nil) + CMessages::AddToPreviousBriefArray( + m_messages[0].m_pText, + m_messages[0].m_nNumber[0], + m_messages[0].m_nNumber[1], + m_messages[0].m_nNumber[2], + m_messages[0].m_nNumber[3], + m_messages[0].m_nNumber[4], + m_messages[0].m_nNumber[5], + 0); + } + Display(); + if (m_messages[0].m_pText != nil) { + if (TheCamera.m_WideScreenOn || !CHud::m_Wants_To_Draw_Hud || CHud::m_BigMessage[0][0] || CHud::m_BigMessage[2][0]) { + RestartCurrentMessage(); + } else { + if (CTimer::GetTimeInMilliseconds() > m_messages[0].m_nTimeToChangePosition) { + m_messages[0].m_nCurrentPosition++; + m_messages[0].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + m_messages[0].m_nSpeedMs; + } + } + } +} + +void +CPager::Display() +{ + wchar outstr1[256]; + wchar outstr2[260]; + + wchar *pText = m_messages[0].m_pText; + uint16 i = 0; + if (pText != nil) { + CMessages::InsertNumberInString( + pText, + m_messages[0].m_nNumber[0], + m_messages[0].m_nNumber[1], + m_messages[0].m_nNumber[2], + m_messages[0].m_nNumber[3], + m_messages[0].m_nNumber[4], + m_messages[0].m_nNumber[5], + outstr1); + for (; i < m_nNumDisplayLetters; i++) { + int pos = m_messages[0].m_nCurrentPosition + i; + if (pos >= 0) { + if (!outstr1[pos]) break; + + outstr2[i] = outstr1[pos]; + } else { + outstr2[i] = ' '; + } + } + } + outstr2[i] = '\0'; + CHud::SetPagerMessage(outstr2); +} + +void +CPager::AddMessage(wchar *str, uint16 speed, uint16 priority, uint16 a5) +{ + uint16 size = CMessages::GetWideStringLength(str); + for (int32 i = 0; i < NUMPAGERMESSAGES; i++) { + if (m_messages[i].m_pText) { + if (m_messages[i].m_nPriority >= priority) + continue; + + for (int j = NUMPAGERMESSAGES-1; j > i; j--) + m_messages[j] = m_messages[j-1]; + + } + m_messages[i].m_pText = str; + m_messages[i].m_nSpeedMs = speed; + m_messages[i].m_nPriority = priority; + m_messages[i].unused = a5; + m_messages[i].m_nCurrentPosition = -(m_nNumDisplayLetters + 10); + m_messages[i].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + speed; + m_messages[i].m_nStringLength = size; + m_messages[i].m_nNumber[0] = -1; + m_messages[i].m_nNumber[1] = -1; + m_messages[i].m_nNumber[2] = -1; + m_messages[i].m_nNumber[3] = -1; + m_messages[i].m_nNumber[4] = -1; + m_messages[i].m_nNumber[5] = -1; + + if (i == 0) + CMessages::AddToPreviousBriefArray( + m_messages[0].m_pText, + m_messages[0].m_nNumber[0], + m_messages[0].m_nNumber[1], + m_messages[0].m_nNumber[2], + m_messages[0].m_nNumber[3], + m_messages[0].m_nNumber[4], + m_messages[0].m_nNumber[5], + nil); + return; + } +} + +void +CPager::AddMessageWithNumber(wchar *str, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, uint16 speed, uint16 priority, uint16 a11) +{ + wchar nstr[520]; + + CMessages::InsertNumberInString(str, n1, n2, n3, n4, n5, n6, nstr); + uint16 size = CMessages::GetWideStringLength(nstr); + for (int32 i = 0; i < NUMPAGERMESSAGES; i++) { + if (m_messages[i].m_pText) { + if (m_messages[i].m_nPriority >= priority) + continue; + + for (int j = NUMPAGERMESSAGES-1; j > i; j--) + m_messages[j] = m_messages[j - 1]; + + } + m_messages[i].m_pText = str; + m_messages[i].m_nSpeedMs = speed; + m_messages[i].m_nPriority = priority; + m_messages[i].unused = a11; + m_messages[i].m_nCurrentPosition = -(m_nNumDisplayLetters + 10); + m_messages[i].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + speed; + m_messages[i].m_nStringLength = size; + m_messages[i].m_nNumber[0] = n1; + m_messages[i].m_nNumber[1] = n2; + m_messages[i].m_nNumber[2] = n3; + m_messages[i].m_nNumber[3] = n4; + m_messages[i].m_nNumber[4] = n5; + m_messages[i].m_nNumber[5] = n6; + + if (i == 0) + CMessages::AddToPreviousBriefArray( + m_messages[0].m_pText, + m_messages[0].m_nNumber[0], + m_messages[0].m_nNumber[1], + m_messages[0].m_nNumber[2], + m_messages[0].m_nNumber[3], + m_messages[0].m_nNumber[4], + m_messages[0].m_nNumber[5], + nil); + return; + } +} + +void +CPager::ClearMessages() +{ + for (int32 i = 0; i < NUMPAGERMESSAGES; i++) + m_messages[i].m_pText = nil; +} + +void +CPager::RestartCurrentMessage() +{ + if (m_messages[0].m_pText != nil) { + m_messages[0].m_nCurrentPosition = -(m_nNumDisplayLetters + 10); + m_messages[0].m_nTimeToChangePosition = CTimer::GetTimeInMilliseconds() + m_messages[0].m_nSpeedMs; + } +} \ No newline at end of file diff --git a/src/text/Pager.h b/src/text/Pager.h new file mode 100644 index 0000000..1630797 --- /dev/null +++ b/src/text/Pager.h @@ -0,0 +1,28 @@ +#pragma once + +struct PagerMessage { + wchar *m_pText; + uint16 m_nSpeedMs; + int16 m_nCurrentPosition; + uint16 m_nStringLength; + uint16 m_nPriority; + uint32 m_nTimeToChangePosition; + int16 unused; // but still set in SCM. importance? ringtone? + int32 m_nNumber[6]; +}; + +#define NUMPAGERMESSAGES 8 + +class CPager +{ + int16 m_nNumDisplayLetters; + PagerMessage m_messages[NUMPAGERMESSAGES]; +public: + void Init(); + void Process(); + void Display(); + void AddMessage(wchar*, uint16, uint16, uint16); + void AddMessageWithNumber(wchar *str, int32 n1, int32 n2, int32 n3, int32 n4, int32 n5, int32 n6, uint16 speed, uint16 priority, uint16 a11); + void ClearMessages(); + void RestartCurrentMessage(); +}; \ No newline at end of file diff --git a/src/text/Text.cpp b/src/text/Text.cpp new file mode 100644 index 0000000..08ab0e1 --- /dev/null +++ b/src/text/Text.cpp @@ -0,0 +1,331 @@ +#include "common.h" + +#include "FileMgr.h" +#ifdef MORE_LANGUAGES +#include "Game.h" +#endif +#include "Frontend.h" +#include "Messages.h" +#include "Text.h" + +wchar WideErrorString[25]; + +CText TheText; + +CText::CText(void) +{ + encoding = 'e'; + memset(WideErrorString, 0, sizeof(WideErrorString)); +} + +void +CText::Load(void) +{ + uint8 *filedata; + char filename[32], type[4]; + ssize_t offset, length; + size_t sectlen; + + Unload(); + filedata = new uint8[0x40000]; + + CFileMgr::SetDir("TEXT"); + switch(CMenuManager::m_PrefsLanguage){ + case CMenuManager::LANGUAGE_AMERICAN: + sprintf(filename, "AMERICAN.GXT"); + break; + case CMenuManager::LANGUAGE_FRENCH: + sprintf(filename, "FRENCH.GXT"); + break; + case CMenuManager::LANGUAGE_GERMAN: + sprintf(filename, "GERMAN.GXT"); + break; + case CMenuManager::LANGUAGE_ITALIAN: + sprintf(filename, "ITALIAN.GXT"); + break; + case CMenuManager::LANGUAGE_SPANISH: + sprintf(filename, "SPANISH.GXT"); + break; +#ifdef MORE_LANGUAGES + case CMenuManager::LANGUAGE_POLISH: + sprintf(filename, "POLISH.GXT"); + break; + case CMenuManager::LANGUAGE_RUSSIAN: + sprintf(filename, "RUSSIAN.GXT"); + break; + case CMenuManager::LANGUAGE_JAPANESE: + sprintf(filename, "JAPANESE.GXT"); + break; +#endif + } + + length = CFileMgr::LoadFile(filename, filedata, 0x40000, "rb"); + CFileMgr::SetDir(""); + + offset = 0; + while(offset < length){ + type[0] = filedata[offset++]; + type[1] = filedata[offset++]; + type[2] = filedata[offset++]; + type[3] = filedata[offset++]; + sectlen = (int)filedata[offset+3]<<24 | (int)filedata[offset+2]<<16 | + (int)filedata[offset+1]<<8 | (int)filedata[offset+0]; + offset += 4; + if(sectlen != 0){ + if(strncmp(type, "TKEY", 4) == 0) + keyArray.Load(sectlen, filedata, &offset); + else if(strncmp(type, "TDAT", 4) == 0) + data.Load(sectlen, filedata, &offset); + else + offset += sectlen; + } + } + + keyArray.Update(data.chars); + + delete[] filedata; +} + +void +CText::Unload(void) +{ + CMessages::ClearAllMessagesDisplayedByGame(); + data.Unload(); + keyArray.Unload(); +} + +wchar* +CText::Get(const char *key) +{ +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + return keyArray.Search(key, data.chars); +#else + return keyArray.Search(key); +#endif +} + +wchar UpperCaseTable[128] = { + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, + 149, 173, 173, 175, 176, 177, 178, 179, 180, 181, 182, + 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, + 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, + 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, + 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, + 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, + 249, 250, 251, 252, 253, 254, 255 +}; + +wchar FrenchUpperCaseTable[128] = { + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 65, 65, 65, 65, 132, 133, 69, 69, 69, 69, 73, 73, + 73, 73, 79, 79, 79, 79, 85, 85, 85, 85, 173, 173, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, + 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, + 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, + 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, + 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, + 253, 254, 255 +}; + +wchar +CText::GetUpperCase(wchar c) +{ + switch (encoding) + { + case 'e': + if (c >= 'a' && c <= 'z') + return c - 32; + break; + case 'f': + if (c >= 'a' && c <= 'z') + return c - 32; + + if (c >= 128 && c <= 255) + return FrenchUpperCaseTable[c-128]; + break; + case 'g': + case 'i': + case 's': + if (c >= 'a' && c <= 'z') + return c - 32; + + if (c >= 128 && c <= 255) + return UpperCaseTable[c-128]; + break; + default: + break; + } + return c; +} + +void +CText::UpperCase(wchar *s) +{ + while(*s){ + *s = GetUpperCase(*s); + s++; + } +} + + +void +CKeyArray::Load(size_t length, uint8 *data, ssize_t *offset) +{ + size_t i; + uint8 *rawbytes; + + // You can make numEntries size_t if you want to exceed 32-bit boundaries, everything else should be ready. + numEntries = (int)(length / sizeof(CKeyEntry)); + entries = new CKeyEntry[numEntries]; + rawbytes = (uint8*)entries; + + for(i = 0; i < length; i++) + rawbytes[i] = data[(*offset)++]; +} + +void +CKeyArray::Unload(void) +{ + delete[] entries; + entries = nil; + numEntries = 0; +} + +void +CKeyArray::Update(wchar *chars) +{ +#if !defined(FIX_BUGS) && !defined(FIX_BUGS_64) + int i; + for(i = 0; i < numEntries; i++) + entries[i].value = (wchar*)((uint8*)chars + (uintptr)entries[i].value); +#endif +} + +CKeyEntry* +CKeyArray::BinarySearch(const char *key, CKeyEntry *entries, int16 low, int16 high) +{ + int mid; + int diff; + + if(low > high) + return nil; + + mid = (low + high)/2; + diff = strcmp(key, entries[mid].key); + if(diff == 0) + return &entries[mid]; + if(diff < 0) + return BinarySearch(key, entries, low, mid-1); + if(diff > 0) + return BinarySearch(key, entries, mid+1, high); + return nil; +} + +wchar* +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) +CKeyArray::Search(const char *key, wchar *data) +#else +CKeyArray::Search(const char *key) +#endif +{ + CKeyEntry *found; + char errstr[25]; + int i; + +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + found = BinarySearch(key, entries, 0, numEntries-1); + if(found) + return (wchar*)((uint8*)data + found->valueOffset); +#else + found = BinarySearch(key, entries, 0, numEntries-1); + if(found) + return found->value; +#endif + sprintf(errstr, "%s missing", key); + for(i = 0; i < 25; i++) + WideErrorString[i] = errstr[i]; + return WideErrorString; +} + + +void +CData::Load(size_t length, uint8 *data, ssize_t *offset) +{ + size_t i; + uint8 *rawbytes; + + // You can make numChars size_t if you want to exceed 32-bit boundaries, everything else should be ready. + numChars = (int)(length / sizeof(wchar)); + chars = new wchar[numChars]; + rawbytes = (uint8*)chars; + + for(i = 0; i < length; i++) + rawbytes[i] = data[(*offset)++]; +} + +void +CData::Unload(void) +{ + delete[] chars; + chars = nil; + numChars = 0; +} + +char* +UnicodeToAscii(wchar *src) +{ + static char aStr[256]; + int len; + for(len = 0; *src != '\0' && len < 256-1; len++, src++) +#ifdef MORE_LANGUAGES + if(*src < 128 || ((CGame::russianGame || CGame::japaneseGame) && *src < 256)) +#else + if(*src < 128) +#endif + aStr[len] = *src; + else + aStr[len] = '#'; + aStr[len] = '\0'; + return aStr; +} + +char* +UnicodeToAsciiForSaveLoad(wchar *src) +{ + static char aStr[256]; + int len; + for(len = 0; *src != '\0' && len < 256; len++, src++) + if(*src < 256) + aStr[len] = *src; + else + aStr[len] = '#'; + aStr[len] = '\0'; + return aStr; +} + +char* +UnicodeToAsciiForMemoryCard(wchar *src) +{ + static char aStr[256]; + int len; + for(len = 0; *src != '\0' && len < 256; len++, src++) + if(*src < 256) + aStr[len] = *src; + else + aStr[len] = '#'; + aStr[len] = '\0'; + return aStr; +} + +void +TextCopy(wchar *dst, const wchar *src) +{ + while((*dst++ = *src++) != '\0'); +} diff --git a/src/text/Text.h b/src/text/Text.h new file mode 100644 index 0000000..ab6d180 --- /dev/null +++ b/src/text/Text.h @@ -0,0 +1,66 @@ +#pragma once + +char *UnicodeToAscii(wchar *src); +char *UnicodeToAsciiForSaveLoad(wchar *src); +char *UnicodeToAsciiForMemoryCard(wchar *src); +void TextCopy(wchar *dst, const wchar *src); + +struct CKeyEntry +{ +#if defined(FIX_BUGS) || defined(FIX_BUGS_64) + uint32 valueOffset; +#else + wchar *value; +#endif + char key[8]; +}; + +// If this fails, CKeyArray::Load will have to be fixed +VALIDATE_SIZE(CKeyEntry, 12); + +class CKeyArray +{ +public: + CKeyEntry *entries; + int numEntries; // You can make this size_t if you want to exceed 32-bit boundaries, everything else should be ready. + + CKeyArray(void) : entries(nil), numEntries(0) {} + ~CKeyArray(void) { Unload(); } + void Load(size_t length, uint8 *data, ssize_t *offset); + void Unload(void); + void Update(wchar *chars); + CKeyEntry *BinarySearch(const char *key, CKeyEntry *entries, int16 low, int16 high); +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + wchar *Search(const char *key, wchar *data); +#else + wchar *Search(const char *key); +#endif +}; + +class CData +{ +public: + wchar *chars; + int numChars; // You can make this size_t if you want to exceed 32-bit boundaries, everything else should be ready. + + CData(void) : chars(nil), numChars(0) {} + ~CData(void) { Unload(); } + void Load(size_t length, uint8 *data, ssize_t *offset); + void Unload(void); +}; + +class CText +{ + CKeyArray keyArray; + CData data; + char encoding; +public: + CText(void); + void Load(void); + void Unload(void); + wchar *Get(const char *key); + wchar GetUpperCase(wchar c); + void UpperCase(wchar *s); +}; + +extern CText TheText; diff --git a/src/vehicles/Automobile.cpp b/src/vehicles/Automobile.cpp new file mode 100644 index 0000000..417dd14 --- /dev/null +++ b/src/vehicles/Automobile.cpp @@ -0,0 +1,4735 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "RwHelper.h" +#include "Pad.h" +#include "ModelIndices.h" +#include "VisibilityPlugins.h" +#include "DMAudio.h" +#include "Clock.h" +#include "Timecycle.h" +#include "ZoneCull.h" +#include "Camera.h" +#include "Darkel.h" +#include "Rubbish.h" +#include "Fire.h" +#include "Explosion.h" +#include "Particle.h" +#include "ParticleObject.h" +#include "Antennas.h" +#include "Skidmarks.h" +#include "Shadows.h" +#include "PointLights.h" +#include "Coronas.h" +#include "SpecialFX.h" +#include "WaterCannon.h" +#include "WaterLevel.h" +#include "Floater.h" +#include "World.h" +#include "SurfaceTable.h" +#include "Weather.h" +#include "HandlingMgr.h" +#include "Record.h" +#include "Remote.h" +#include "Population.h" +#include "CarCtrl.h" +#include "CarAI.h" +#include "Garages.h" +#include "PathFind.h" +#include "AnimManager.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Object.h" +#include "Automobile.h" +#include "Wanted.h" +#include "SaveBuf.h" + +bool bAllCarCheat; // unused + +RwObject *GetCurrentAtomicObjectCB(RwObject *object, void *data); + +bool CAutomobile::m_sAllTaxiLights; + +const uint32 CAutomobile::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1448; +#else + sizeof(CAutomobile); +#endif + +CAutomobile::CAutomobile(int32 id, uint8 CreatedBy) + : CVehicle(CreatedBy) +{ + int i; + + m_vehType = VEHICLE_TYPE_CAR; + + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + m_fFireBlowUpTimer = 0.0f; + m_auto_unk1 = 0; + bTaxiLight = m_sAllTaxiLights; + bFixedColour = false; + bBigWheels = false; + bWaterTight = false; + + SetModelIndex(id); + + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + + m_auto_unused1 = 20.0f; + m_auto_unused2 = 0; + + mi->ChooseVehicleColour(m_currentColour1, m_currentColour2); + + bIsVan = !!(pHandling->Flags & HANDLING_IS_VAN); + bIsBig = !!(pHandling->Flags & HANDLING_IS_BIG); + bIsBus = !!(pHandling->Flags & HANDLING_IS_BUS); + bLowVehicle = !!(pHandling->Flags & HANDLING_IS_LOW); + + // Doors + if(bIsBus){ + Doors[DOOR_FRONT_LEFT].Init(-HALFPI, 0.0f, 0, 2); + Doors[DOOR_FRONT_RIGHT].Init(0.0f, HALFPI, 1, 2); + }else{ + Doors[DOOR_FRONT_LEFT].Init(-PI*0.4f, 0.0f, 0, 2); + Doors[DOOR_FRONT_RIGHT].Init(0.0f, PI*0.4f, 1, 2); + } + if(bIsVan){ + Doors[DOOR_REAR_LEFT].Init(-HALFPI, 0.0f, 1, 2); + Doors[DOOR_REAR_RIGHT].Init(0.0f, HALFPI, 0, 2); + }else{ + Doors[DOOR_REAR_LEFT].Init(-PI*0.4f, 0.0f, 0, 2); + Doors[DOOR_REAR_RIGHT].Init(0.0f, PI*0.4f, 1, 2); + } + if(pHandling->Flags & HANDLING_REV_BONNET) + Doors[DOOR_BONNET].Init(-PI*0.3f, 0.0f, 1, 0); + else + Doors[DOOR_BONNET].Init(0.0f, PI*0.3f, 1, 0); + if(pHandling->Flags & HANDLING_HANGING_BOOT) + Doors[DOOR_BOOT].Init(-PI*0.4f, 0.0f, 0, 0); + else if(pHandling->Flags & HANDLING_TAILGATE_BOOT) + Doors[DOOR_BOOT].Init(0.0, HALFPI, 1, 0); + else + Doors[DOOR_BOOT].Init(-PI*0.3f, 0.0f, 1, 0); + if(pHandling->Flags & HANDLING_NO_DOORS){ + Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_REAR_LEFT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_REAR_RIGHT, DOOR_STATUS_MISSING); + } + + for(i = 0; i < 6; i++) + m_randomValues[i] = CGeneral::GetRandomNumberInRange(-0.15f, 0.15f); + + m_fMass = pHandling->fMass; + m_fTurnMass = pHandling->fTurnMass; + m_vecCentreOfMass = pHandling->CentreOfMass; + m_fAirResistance = pHandling->Dimension.x*pHandling->Dimension.z/m_fMass; + m_fElasticity = 0.05f; + m_fBuoyancy = pHandling->fBuoyancy; + + m_nBusDoorTimerEnd = 0; + m_nBusDoorTimerStart = 0; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_fBrakePedal = 0.0f; + m_pSetOnFireEntity = nil; + m_fGasPedalAudio = 0.0f; + bNotDamagedUpsideDown = false; + bMoreResistantToDamage = false; + m_fVelocityChangeForAudio = 0.0f; + m_hydraulicState = 0; + + for(i = 0; i < 4; i++){ + m_aGroundPhysical[i] = nil; + m_aGroundOffset[i] = CVector(0.0f, 0.0f, 0.0f); + m_aSuspensionSpringRatioPrev[i] = m_aSuspensionSpringRatio[i] = 1.0f; + m_aWheelTimer[i] = 0.0f; + m_aWheelRotation[i] = 0.0f; + m_aWheelSpeed[i] = 0.0f; + m_aWheelState[i] = WHEEL_STATE_NORMAL; + m_aWheelSkidmarkMuddy[i] = false; + m_aWheelSkidmarkBloody[i] = false; + } + + m_nWheelsOnGround = 0; + m_nDriveWheelsOnGround = 0; + m_nDriveWheelsOnGroundPrev = 0; + m_fHeightAboveRoad = 0.0f; + m_fTraction = 1.0f; + + CColModel *colModel = mi->GetColModel(); + if(colModel->lines == nil){ + colModel->lines = (CColLine*)RwMalloc(4*sizeof(CColLine)); + colModel->numLines = 4; + } + + SetupSuspensionLines(); + + SetStatus(STATUS_SIMPLE); + bUseCollisionRecords = true; + + m_nNumPassengers = 0; + + m_bombType = CARBOMB_NONE; + bDriverLastFrame = false; + m_pBombRigger = nil; + + if(m_nDoorLock == CARLOCK_UNLOCKED && + (id == MI_POLICE || id == MI_ENFORCER || id == MI_RHINO)) + m_nDoorLock = CARLOCK_LOCKED_INITIALLY; + + m_fCarGunLR = 0.0f; + m_fCarGunUD = 0.05f; + m_fPropellerRotation = 0.0f; + m_weaponDoorTimerLeft = 0.0f; + m_weaponDoorTimerRight = m_weaponDoorTimerLeft; + + if(GetModelIndex() == MI_DODO){ + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0); + CMatrix mat1; + mat1.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + CMatrix mat2(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + mat1.GetPosition() += CVector(mat2.GetPosition().x + 0.1f, 0.0f, mat2.GetPosition().z); + mat1.UpdateRW(); + }else if(GetModelIndex() == MI_MIAMI_SPARROW || GetModelIndex() == MI_MIAMI_RCRAIDER){ + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0); + }else if(GetModelIndex() == MI_RHINO){ + bExplosionProof = true; + bBulletProof = true; + } +} + + +void +CAutomobile::SetModelIndex(uint32 id) +{ + CVehicle::SetModelIndex(id); + SetupModelNodes(); +} + +CVector vecDAMAGE_ENGINE_POS_SMALL(-0.1f, -0.1f, 0.0f); +CVector vecDAMAGE_ENGINE_POS_BIG(-0.5f, -0.3f, 0.0f); + +#pragma optimize("", off) // that's what R* did + +void +CAutomobile::ProcessControl(void) +{ + int i; + float wheelRot; + CColModel *colModel; + float brake = 0.0f; + + if(bUsingSpecialColModel) + colModel = &CWorld::Players[CWorld::PlayerInFocus].m_ColModel; + else + colModel = GetColModel(); + bWarnedPeds = false; + + // skip if the collision isn't for the current level + if(colModel->level > LEVEL_GENERIC && colModel->level != CCollision::ms_collisionInMemory) + return; + + // Improve grip of vehicles in certain cases + bool strongGrip1 = false; + bool strongGrip2 = false; + if(FindPlayerVehicle() && this != FindPlayerVehicle() && FindPlayerPed()->m_pWanted->GetWantedLevel() > 3 && + (AutoPilot.m_nCarMission == MISSION_RAMPLAYER_FARAWAY || AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE || + AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_FARAWAY || AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_CLOSE) && + FindPlayerSpeed().Magnitude() > 0.3f){ + + strongGrip1 = true; + if(FindPlayerSpeed().Magnitude() > 0.4f && + m_vecMoveSpeed.Magnitude() < 0.3f) + strongGrip2 = true; + else if((GetPosition() - FindPlayerCoors()).Magnitude() > 50.0f) + strongGrip2 = true; + } + + if(bIsBus) + ProcessAutoBusDoors(); + + ProcessCarAlarm(); + + // Scan if this car sees the player committing any crimes + if(GetStatus() != STATUS_ABANDONED && GetStatus() != STATUS_WRECKED && + GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && GetStatus() != STATUS_PLAYER_DISABLED){ + switch(GetModelIndex()) + case MI_FBICAR: + case MI_POLICE: + case MI_ENFORCER: + case MI_SECURICA: + case MI_RHINO: + case MI_BARRACKS: + ScanForCrimes(); + } + + // Process driver + if(pDriver){ + if(!bDriverLastFrame && m_bombType == CARBOMB_ONIGNITIONACTIVE){ + // If someone enters the car and there is a bomb, detonate + m_nBombTimer = 1000; + m_pBlowUpEntity = m_pBombRigger; + if(m_pBlowUpEntity) + m_pBlowUpEntity->RegisterReference((CEntity**)&m_pBlowUpEntity); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_BOMB_TICK, 1.0f); + } + bDriverLastFrame = true; + + if(IsUpsideDown() && CanPedEnterCar()){ + if(!pDriver->IsPlayer() && + !(pDriver->m_leader && pDriver->m_leader->bInVehicle) && + pDriver->CharCreatedBy != MISSION_CHAR) + pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, this); + } + }else + bDriverLastFrame = false; + + // Process passengers + if(m_nNumPassengers != 0 && IsUpsideDown() && CanPedEnterCar()){ + for(i = 0; i < m_nNumMaxPassengers; i++) + if(pPassengers[i]) + if(!pPassengers[i]->IsPlayer() && + !(pPassengers[i]->m_leader && pPassengers[i]->m_leader->bInVehicle) && + pPassengers[i]->CharCreatedBy != MISSION_CHAR) + pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this); + } + + CRubbish::StirUp(this); + + // blend in clump + int clumpAlpha = CVisibilityPlugins::GetClumpAlpha((RpClump*)m_rwObject); + if(bFadeOut){ + clumpAlpha -= 8; + if(clumpAlpha < 0) + clumpAlpha = 0; + }else if(clumpAlpha < 255){ + clumpAlpha += 16; + if(clumpAlpha > 255) + clumpAlpha = 255; + } + CVisibilityPlugins::SetClumpAlpha((RpClump*)m_rwObject, clumpAlpha); + + AutoPilot.m_bSlowedDownBecauseOfCars = false; + AutoPilot.m_bSlowedDownBecauseOfPeds = false; + + // Set Center of Mass to make car more stable + if(strongGrip1 || bCheat3) + m_vecCentreOfMass.z = 0.3f*m_aSuspensionSpringLength[0] + -1.0f*m_fHeightAboveRoad; + else if(pHandling->Flags & HANDLING_NONPLAYER_STABILISER && GetStatus() == STATUS_PHYSICS) + m_vecCentreOfMass.z = pHandling->CentreOfMass.z - 0.2f*pHandling->Dimension.z; + else + m_vecCentreOfMass.z = pHandling->CentreOfMass.z; + + // Process depending on status + + bool playerRemote = false; + switch(GetStatus()){ + case STATUS_PLAYER_REMOTE: +#ifdef FIX_BUGS + if (CPad::GetPad(0)->CarGunJustDown()) { +#else + if (CPad::GetPad(0)->WeaponJustDown()) { +#endif + BlowUpCar(FindPlayerPed()); + CRemote::TakeRemoteControlledCarFromPlayer(); + } + + if(GetModelIndex() == MI_RCBANDIT){ + CVector pos = GetPosition(); + // FindPlayerCoors unused + if(RcbanditCheckHitWheels() || bIsInWater || CPopulation::IsPointInSafeZone(&pos)){ + if(CPopulation::IsPointInSafeZone(&pos)) + CGarages::TriggerMessage("HM2_5", -1, 5000, -1); + CRemote::TakeRemoteControlledCarFromPlayer(); + BlowUpCar(FindPlayerPed()); + } + } + + if(CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle == this) + playerRemote = true; + // fall through + case STATUS_PLAYER: + if(playerRemote || + pDriver && pDriver->GetPedState() != PED_EXIT_CAR && pDriver->GetPedState() != PED_DRAG_FROM_CAR){ + // process control input if controlled by player + if(playerRemote || pDriver->m_nPedType == PEDTYPE_PLAYER1) + ProcessControlInputs(0); + + PruneReferences(); + + if(GetStatus() == STATUS_PLAYER && !CRecordDataForChase::IsRecording()) + DoDriveByShootings(); + } + break; + + case STATUS_SIMPLE: + CCarAI::UpdateCarAI(this); + CPhysical::ProcessControl(); + CCarCtrl::UpdateCarOnRails(this); + + m_nWheelsOnGround = 4; + m_nDriveWheelsOnGroundPrev = m_nDriveWheelsOnGround; + m_nDriveWheelsOnGround = 4; + + pHandling->Transmission.CalculateGearForSimpleCar(AutoPilot.m_fMaxTrafficSpeed/50.0f, m_nCurrentGear); + + wheelRot = ProcessWheelRotation(WHEEL_STATE_NORMAL, GetForward(), m_vecMoveSpeed, 0.35f); + for(i = 0; i < 4; i++) + m_aWheelRotation[i] += wheelRot; + + PlayHornIfNecessary(); + ReduceHornCounter(); + bVehicleColProcessed = false; + // that's all we do for simple vehicles + return; + + case STATUS_PHYSICS: + CCarAI::UpdateCarAI(this); + CCarCtrl::SteerAICarWithPhysics(this); + PlayHornIfNecessary(); + break; + + case STATUS_ABANDONED: + m_fBrakePedal = 0.2f; + bIsHandbrakeOn = false; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_nCarHornTimer = 0; + break; + + case STATUS_WRECKED: + m_fBrakePedal = 0.05f; + bIsHandbrakeOn = true; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_nCarHornTimer = 0; + break; + + case STATUS_PLAYER_DISABLED: + m_fBrakePedal = 1.0f; + bIsHandbrakeOn = true; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_nCarHornTimer = 0; + break; + default: break; + } + + // what's going on here? + if(GetPosition().z < -0.6f && + Abs(m_vecMoveSpeed.x) < 0.05f && + Abs(m_vecMoveSpeed.y) < 0.05f) + m_vecTurnSpeed *= Pow(0.95f, CTimer::GetTimeStep()); + + // Skip physics if object is found to have been static recently + bool skipPhysics = false; + if(!bIsStuck && (GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED)){ + bool makeStatic = false; + float moveSpeedLimit, turnSpeedLimit, distanceLimit; + + if(!bVehicleColProcessed && + m_vecMoveSpeed.IsZero() && + // BUG? m_aSuspensionSpringRatioPrev[3] is checked twice in the game. also, why 3? + m_aSuspensionSpringRatioPrev[3] != 1.0f) + makeStatic = true; + + if(GetStatus() == STATUS_WRECKED){ + moveSpeedLimit = 0.006f; + turnSpeedLimit = 0.0015f; + distanceLimit = 0.015f; + }else{ + moveSpeedLimit = 0.003f; + turnSpeedLimit = 0.0009f; + distanceLimit = 0.005f; + } + + m_vecMoveSpeedAvg = (m_vecMoveSpeedAvg + m_vecMoveSpeed)/2.0f; + m_vecTurnSpeedAvg = (m_vecTurnSpeedAvg + m_vecTurnSpeed)/2.0f; + + if(m_vecMoveSpeedAvg.MagnitudeSqr() <= sq(moveSpeedLimit*CTimer::GetTimeStep()) && + m_vecTurnSpeedAvg.MagnitudeSqr() <= sq(turnSpeedLimit*CTimer::GetTimeStep()) && + m_fDistanceTravelled < distanceLimit || + makeStatic){ + m_nStaticFrames++; + + if(m_nStaticFrames > 10 || makeStatic) + if(!CCarCtrl::MapCouldMoveInThisArea(GetPosition().x, GetPosition().y)){ + if(!makeStatic || m_nStaticFrames > 10) + m_nStaticFrames = 10; + + skipPhysics = true; + + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + } + }else + m_nStaticFrames = 0; + } + + // Postpone + for(i = 0; i < 4; i++) + if(m_aGroundPhysical[i] && !CWorld::bForceProcessControl && m_aGroundPhysical[i]->bIsInSafePosition){ + bWasPostponed = true; + return; + } + + VehicleDamage(0.0f, 0); + + // special control + switch(GetModelIndex()){ + case MI_FIRETRUCK: + FireTruckControl(); + break; + case MI_RHINO: + TankControl(); + BlowUpCarsInPath(); + break; + case MI_YARDIE: + // beta also had esperanto here it seems + HydraulicControl(); + break; + default: + if(CVehicle::bCheat3){ + // Make vehicle jump when horn is sounded + if(GetStatus() == STATUS_PLAYER && m_vecMoveSpeed.MagnitudeSqr() > sq(0.2f) && + // BUG: game checks [0] four times, instead of all wheels + m_aSuspensionSpringRatio[0] < 1.0f && + CPad::GetPad(0)->HornJustDown()){ + + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP, 1.0f); + + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, + m_aWheelColPoints[0].point + 0.5f*GetUp(), + 1.3f*m_vecMoveSpeed, nil, 2.5f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, + m_aWheelColPoints[0].point + 0.5f*GetUp(), + 1.2f*m_vecMoveSpeed, nil, 2.0f); + + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, + m_aWheelColPoints[2].point + 0.5f*GetUp(), + 1.3f*m_vecMoveSpeed, nil, 2.5f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, + m_aWheelColPoints[2].point + 0.5f*GetUp(), + 1.2f*m_vecMoveSpeed, nil, 2.0f); + + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, + m_aWheelColPoints[0].point + 0.5f*GetUp() - GetForward(), + 1.3f*m_vecMoveSpeed, nil, 2.5f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, + m_aWheelColPoints[0].point + 0.5f*GetUp() - GetForward(), + 1.2f*m_vecMoveSpeed, nil, 2.0f); + + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, + m_aWheelColPoints[2].point + 0.5f*GetUp() - GetForward(), + 1.3f*m_vecMoveSpeed, nil, 2.5f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, + m_aWheelColPoints[2].point + 0.5f*GetUp() - GetForward(), + 1.2f*m_vecMoveSpeed, nil, 2.0f); + + ApplyMoveForce(CVector(0.0f, 0.0f, 1.0f)*m_fMass*0.4f); + ApplyTurnForce(GetUp()*m_fMass*0.035f, GetForward()*1.0f); + } + } + break; + } + + if(skipPhysics){ + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + m_nCollisionRecords = 0; + bHasCollided = false; + bVehicleColProcessed = false; + m_nDamagePieceType = 0; + m_fDamageImpulse = 0.0f; + m_pDamageEntity = nil; + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + }else{ + + // This has to be done if ProcessEntityCollision wasn't called + if(!bVehicleColProcessed){ + CMatrix mat(GetMatrix()); + bIsStuck = false; + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + m_fDistanceTravelled = 0.0f; + m_bIsVehicleBeingShifted = false; + bSkipLineCol = false; + ApplyMoveSpeed(); + ApplyTurnSpeed(); + for(i = 0; CheckCollision() && i < 5; i++){ + GetMatrix() = mat; + ApplyMoveSpeed(); + ApplyTurnSpeed(); + } + bIsInSafePosition = true; + bIsStuck = false; + } + + CPhysical::ProcessControl(); + + ProcessBuoyancy(); + + // Rescale spring ratios, i.e. subtract wheel radius + for(i = 0; i < 4; i++){ + // wheel radius in relation to suspension line + float wheelRadius = 1.0f - m_aSuspensionSpringLength[i]/m_aSuspensionLineLength[i]; + // rescale such that 0.0 is fully compressed and 1.0 is fully extended + m_aSuspensionSpringRatio[i] = (m_aSuspensionSpringRatio[i]-wheelRadius)/(1.0f-wheelRadius); + } + + float fwdSpeed = Abs(DotProduct(m_vecMoveSpeed, GetForward())); + CVector contactPoints[4]; // relative to model + CVector contactSpeeds[4]; // speed at contact points + CVector springDirections[4]; // normalized, in world space + + for(i = 0; i < 4; i++){ + // Set spring under certain circumstances + if(Damage.GetWheelStatus(i) == WHEEL_STATUS_MISSING) + m_aSuspensionSpringRatio[i] = 1.0f; + else if(Damage.GetWheelStatus(i) == WHEEL_STATUS_BURST){ + // wheel more bumpy the faster we are + if(CGeneral::GetRandomNumberInRange(0, (uint16)(40*fwdSpeed) + 98) < 100){ + m_aSuspensionSpringRatio[i] += 0.3f*(m_aSuspensionLineLength[i]-m_aSuspensionSpringLength[i])/m_aSuspensionSpringLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + } + + // get points and directions if spring is compressed + if(m_aSuspensionSpringRatio[i] < 1.0f){ + contactPoints[i] = m_aWheelColPoints[i].point - GetPosition(); + springDirections[i] = Multiply3x3(GetMatrix(), colModel->lines[i].p1 - colModel->lines[i].p0); + springDirections[i].Normalise(); + } + } + + // Make springs push up vehicle + for(i = 0; i < 4; i++){ + if(m_aSuspensionSpringRatio[i] < 1.0f){ + float bias = pHandling->fSuspensionBias; + if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT) + bias = 1.0f - bias; + + ApplySpringCollision(pHandling->fSuspensionForceLevel, + springDirections[i], contactPoints[i], + m_aSuspensionSpringRatio[i], bias); + m_aWheelSkidmarkMuddy[i] = + m_aWheelColPoints[i].surfaceB == SURFACE_GRASS || + m_aWheelColPoints[i].surfaceB == SURFACE_MUD_DRY || + m_aWheelColPoints[i].surfaceB == SURFACE_SAND; + }else{ + contactPoints[i] = Multiply3x3(GetMatrix(), colModel->lines[i].p1); + } + } + + // Get speed at contact points + for(i = 0; i < 4; i++){ + contactSpeeds[i] = GetSpeed(contactPoints[i]); + if(m_aGroundPhysical[i]){ + // subtract movement of physical we're standing on + contactSpeeds[i] -= m_aGroundPhysical[i]->GetSpeed(m_aGroundOffset[i]); +#ifndef FIX_BUGS + // this shouldn't be reset because we still need it below + m_aGroundPhysical[i] = nil; +#endif + } + } + + // dampen springs + for(i = 0; i < 4; i++) + if(m_aSuspensionSpringRatio[i] < 1.0f) + ApplySpringDampening(pHandling->fSuspensionDampingLevel, + springDirections[i], contactPoints[i], contactSpeeds[i]); + + // Get speed at contact points again + for(i = 0; i < 4; i++){ + contactSpeeds[i] = GetSpeed(contactPoints[i]); + if(m_aGroundPhysical[i]){ + // subtract movement of physical we're standing on + contactSpeeds[i] -= m_aGroundPhysical[i]->GetSpeed(m_aGroundOffset[i]); + m_aGroundPhysical[i] = nil; + } + } + + + bool gripCheat = true; + fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward()); + if(!strongGrip1 && !CVehicle::bCheat3) + gripCheat = false; + float acceleration = pHandling->Transmission.CalculateDriveAcceleration(m_fGasPedal, m_nCurrentGear, m_fChangeGearTime, fwdSpeed, gripCheat); + acceleration /= m_fForceMultiplier; + + // unused + if(GetModelIndex() == MI_MIAMI_RCBARON || + GetModelIndex() == MI_MIAMI_RCRAIDER || + GetModelIndex() == MI_MIAMI_SPARROW) + acceleration = 0.0f; + + brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep(); + bool neutralHandling = !!(pHandling->Flags & HANDLING_NEUTRALHANDLING); + float brakeBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fBrakeBias; + float brakeBiasRear = neutralHandling ? 1.0f : 2.0f*(1.0f-pHandling->fBrakeBias); + float tractionBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fTractionBias; + float tractionBiasRear = neutralHandling ? 1.0f : 2.0f-tractionBiasFront; + + // Count how many wheels are touching the ground + + m_nWheelsOnGround = 0; + m_nDriveWheelsOnGroundPrev = m_nDriveWheelsOnGround; + m_nDriveWheelsOnGround = 0; + + for(i = 0; i < 4; i++){ + if(m_aSuspensionSpringRatio[i] < 1.0f) + m_aWheelTimer[i] = 4.0f; + else + m_aWheelTimer[i] = Max(m_aWheelTimer[i]-CTimer::GetTimeStep(), 0.0f); + + if(m_aWheelTimer[i] > 0.0f){ + m_nWheelsOnGround++; + switch(pHandling->Transmission.nDriveType){ + case '4': + m_nDriveWheelsOnGround++; + break; + case 'F': + if(i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT) + m_nDriveWheelsOnGround++; + break; + case 'R': + if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT) + m_nDriveWheelsOnGround++; + break; + } + } + } + + float traction; + if(GetStatus() == STATUS_PHYSICS) + traction = 0.004f * m_fTraction; + else + traction = 0.004f; + traction *= pHandling->fTractionMultiplier / 4.0f; + traction /= m_fForceMultiplier; + if(CVehicle::bCheat3) + traction *= 4.0f; + + if(FindPlayerVehicle() && FindPlayerVehicle() == this){ + if(CPad::GetPad(0)->CarGunJustDown()){ + if(m_bombType == CARBOMB_TIMED){ + m_bombType = CARBOMB_TIMEDACTIVE; + m_nBombTimer = 7000; + m_pBlowUpEntity = FindPlayerPed(); + CGarages::TriggerMessage("GA_12", -1, 3000, -1); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_BOMB_TIMED_ACTIVATED, 1.0f); + }else if(m_bombType == CARBOMB_ONIGNITION){ + m_bombType = CARBOMB_ONIGNITIONACTIVE; + CGarages::TriggerMessage("GA_12", -1, 3000, -1); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_BOMB_ONIGNITION_ACTIVATED, 1.0f); + } + } + }else if(strongGrip1 || CVehicle::bCheat3){ + traction *= 1.2f; + acceleration *= 1.4f; + if(strongGrip2 || CVehicle::bCheat3){ + traction *= 1.3f; + acceleration *= 1.4f; + } + } + + static float fThrust; + static tWheelState WheelState[4]; + + // Process front wheels on ground + + if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f || m_aWheelTimer[CARWHEEL_FRONT_RIGHT] > 0.0f){ + float s = Sin(m_fSteerAngle); + float c = Cos(m_fSteerAngle); + CVector wheelFwd = Multiply3x3(GetMatrix(), CVector(-s, c, 0.0f)); + CVector wheelRight = Multiply3x3(GetMatrix(), CVector(c, s, 0.0f)); + + if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_FRONT_LEFT])*traction; + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceB); + WheelState[CARWHEEL_FRONT_LEFT] = m_aWheelState[CARWHEEL_FRONT_LEFT]; + + if(Damage.GetWheelStatus(CARWHEEL_FRONT_LEFT) == WHEEL_STATUS_BURST) + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_FRONT_LEFT], contactPoints[CARWHEEL_FRONT_LEFT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront*Damage.m_fWheelDamageEffect, + CARWHEEL_FRONT_LEFT, + &m_aWheelSpeed[CARWHEEL_FRONT_LEFT], + &WheelState[CARWHEEL_FRONT_LEFT], + WHEEL_STATUS_BURST); + else + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_FRONT_LEFT], contactPoints[CARWHEEL_FRONT_LEFT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront, + CARWHEEL_FRONT_LEFT, + &m_aWheelSpeed[CARWHEEL_FRONT_LEFT], + &WheelState[CARWHEEL_FRONT_LEFT], + WHEEL_STATUS_OK); + } + + if(m_aWheelTimer[CARWHEEL_FRONT_RIGHT] > 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_FRONT_RIGHT])*traction; + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceB); + WheelState[CARWHEEL_FRONT_RIGHT] = m_aWheelState[CARWHEEL_FRONT_RIGHT]; + + if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST) + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_FRONT_RIGHT], contactPoints[CARWHEEL_FRONT_RIGHT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront*Damage.m_fWheelDamageEffect, + CARWHEEL_FRONT_RIGHT, + &m_aWheelSpeed[CARWHEEL_FRONT_RIGHT], + &WheelState[CARWHEEL_FRONT_RIGHT], + WHEEL_STATUS_BURST); + else + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_FRONT_RIGHT], contactPoints[CARWHEEL_FRONT_RIGHT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront, + CARWHEEL_FRONT_RIGHT, + &m_aWheelSpeed[CARWHEEL_FRONT_RIGHT], + &WheelState[CARWHEEL_FRONT_RIGHT], + WHEEL_STATUS_OK); + } + } + + // Process front wheels off ground + + if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] <= 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){ + if(acceleration > 0.0f){ + if(m_aWheelSpeed[CARWHEEL_FRONT_LEFT] < 2.0f) + m_aWheelSpeed[CARWHEEL_FRONT_LEFT] -= 0.2f; + }else{ + if(m_aWheelSpeed[CARWHEEL_FRONT_LEFT] > -2.0f) + m_aWheelSpeed[CARWHEEL_FRONT_LEFT] += 0.1f; + } + }else{ + m_aWheelSpeed[CARWHEEL_FRONT_LEFT] *= 0.95f; + } + m_aWheelRotation[CARWHEEL_FRONT_LEFT] += m_aWheelSpeed[CARWHEEL_FRONT_LEFT]; + } + if(m_aWheelTimer[CARWHEEL_FRONT_RIGHT] <= 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){ + if(acceleration > 0.0f){ + if(m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] < 2.0f) + m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] -= 0.2f; + }else{ + if(m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] > -2.0f) + m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] += 0.1f; + } + }else{ + m_aWheelSpeed[CARWHEEL_FRONT_RIGHT] *= 0.95f; + } + m_aWheelRotation[CARWHEEL_FRONT_RIGHT] += m_aWheelSpeed[CARWHEEL_FRONT_RIGHT]; + } + + // Process rear wheels + + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f || m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f){ + CVector wheelFwd = GetForward(); + CVector wheelRight = GetRight(); + +#ifdef FIX_BUGS + // Not sure if this is needed, but brake usually has timestep as a factor + if(bIsHandbrakeOn) + brake = 20000.0f * CTimer::GetTimeStepFix(); +#else + if(bIsHandbrakeOn) + brake = 20000.0f; +#endif + + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_REAR_LEFT])*traction; + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB); + WheelState[CARWHEEL_REAR_LEFT] = m_aWheelState[CARWHEEL_REAR_LEFT]; + + if(Damage.GetWheelStatus(CARWHEEL_REAR_LEFT) == WHEEL_STATUS_BURST) + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_REAR_LEFT], contactPoints[CARWHEEL_REAR_LEFT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasRear, + adhesion*tractionBiasRear*Damage.m_fWheelDamageEffect, + CARWHEEL_REAR_LEFT, + &m_aWheelSpeed[CARWHEEL_REAR_LEFT], + &WheelState[CARWHEEL_REAR_LEFT], + WHEEL_STATUS_BURST); + else + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_REAR_LEFT], contactPoints[CARWHEEL_REAR_LEFT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasRear, + adhesion*tractionBiasRear, + CARWHEEL_REAR_LEFT, + &m_aWheelSpeed[CARWHEEL_REAR_LEFT], + &WheelState[CARWHEEL_REAR_LEFT], + WHEEL_STATUS_OK); + } + + if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_REAR_RIGHT])*traction; + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB); + WheelState[CARWHEEL_REAR_RIGHT] = m_aWheelState[CARWHEEL_REAR_RIGHT]; + + if(Damage.GetWheelStatus(CARWHEEL_REAR_RIGHT) == WHEEL_STATUS_BURST) + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_REAR_RIGHT], contactPoints[CARWHEEL_REAR_RIGHT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasRear, + adhesion*tractionBiasRear*Damage.m_fWheelDamageEffect, + CARWHEEL_REAR_RIGHT, + &m_aWheelSpeed[CARWHEEL_REAR_RIGHT], + &WheelState[CARWHEEL_REAR_RIGHT], + WHEEL_STATUS_BURST); + else + ProcessWheel(wheelFwd, wheelRight, + contactSpeeds[CARWHEEL_REAR_RIGHT], contactPoints[CARWHEEL_REAR_RIGHT], + m_nWheelsOnGround, fThrust, + brake*brakeBiasRear, + adhesion*tractionBiasRear, + CARWHEEL_REAR_RIGHT, + &m_aWheelSpeed[CARWHEEL_REAR_RIGHT], + &WheelState[CARWHEEL_REAR_RIGHT], + WHEEL_STATUS_OK); + } + } + + // Process rear wheels off ground + + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] <= 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){ + if(acceleration > 0.0f){ + if(m_aWheelSpeed[CARWHEEL_REAR_LEFT] < 2.0f) + m_aWheelSpeed[CARWHEEL_REAR_LEFT] -= 0.2f; + }else{ + if(m_aWheelSpeed[CARWHEEL_REAR_LEFT] > -2.0f) + m_aWheelSpeed[CARWHEEL_REAR_LEFT] += 0.1f; + } + }else{ + m_aWheelSpeed[CARWHEEL_REAR_LEFT] *= 0.95f; + } + m_aWheelRotation[CARWHEEL_REAR_LEFT] += m_aWheelSpeed[CARWHEEL_REAR_LEFT]; + } + if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] <= 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier) && acceleration != 0.0f){ + if(acceleration > 0.0f){ + if(m_aWheelSpeed[CARWHEEL_REAR_RIGHT] < 2.0f) + m_aWheelSpeed[CARWHEEL_REAR_RIGHT] -= 0.2f; + }else{ + if(m_aWheelSpeed[CARWHEEL_REAR_RIGHT] > -2.0f) + m_aWheelSpeed[CARWHEEL_REAR_RIGHT] += 0.1f; + } + }else{ + m_aWheelSpeed[CARWHEEL_REAR_RIGHT] *= 0.95f; + } + m_aWheelRotation[CARWHEEL_REAR_RIGHT] += m_aWheelSpeed[CARWHEEL_REAR_RIGHT]; + } + + for(i = 0; i < 4; i++){ + float wheelPos = colModel->lines[i].p0.z; + if(m_aSuspensionSpringRatio[i] > 0.0f) + wheelPos -= m_aSuspensionSpringRatio[i]*m_aSuspensionSpringLength[i]; + m_aWheelPosition[i] += (wheelPos - m_aWheelPosition[i])*0.75f; + } + for(i = 0; i < 4; i++) + m_aWheelState[i] = WheelState[i]; + + // Process horn + + if(GetStatus() != STATUS_PLAYER){ + ReduceHornCounter(); + }else{ + if(GetModelIndex() == MI_MRWHOOP){ + if(Pads[0].bHornHistory[Pads[0].iCurrHornHistory] && + !Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+CPad::HORNHISTORY_SIZE-1) % CPad::HORNHISTORY_SIZE]){ + m_bSirenOrAlarm = !m_bSirenOrAlarm; + printf("m_bSirenOrAlarm toggled to %d\n", m_bSirenOrAlarm); + } + }else if(UsesSiren(GetModelIndex())){ + if(Pads[0].bHornHistory[Pads[0].iCurrHornHistory]){ + if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+CPad::HORNHISTORY_SIZE-1) % CPad::HORNHISTORY_SIZE] && + Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+CPad::HORNHISTORY_SIZE-2) % CPad::HORNHISTORY_SIZE]) + m_nCarHornTimer = 1; + else + m_nCarHornTimer = 0; + }else if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+CPad::HORNHISTORY_SIZE-1) % CPad::HORNHISTORY_SIZE] && + !Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+1) % CPad::HORNHISTORY_SIZE]){ + m_nCarHornTimer = 0; + m_bSirenOrAlarm = !m_bSirenOrAlarm; + }else + m_nCarHornTimer = 0; + }else if(GetModelIndex() != MI_YARDIE && !CVehicle::bCheat3){ + if(Pads[0].GetHorn()) + m_nCarHornTimer = 1; + else + m_nCarHornTimer = 0; + } + } + + // Flying + + if(GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && GetStatus() != STATUS_PHYSICS){ + if(GetModelIndex() == MI_MIAMI_RCRAIDER || GetModelIndex() == MI_MIAMI_SPARROW) + m_aWheelSpeed[0] = Max(m_aWheelSpeed[0]-0.0005f, 0.0f); + }else if((GetModelIndex() == MI_DODO || CVehicle::bAllDodosCheat) && + m_vecMoveSpeed.Magnitude() > 0.0f && CTimer::GetTimeStep() > 0.0f){ +#ifdef ALT_DODO_CHEAT + if (bAltDodoCheat) + FlyingControl(FLIGHT_MODEL_SEAPLANE); + else +#endif + FlyingControl(FLIGHT_MODEL_DODO); + }else if(GetModelIndex() == MI_MIAMI_RCBARON){ + FlyingControl(FLIGHT_MODEL_RCPLANE); + }else if(GetModelIndex() == MI_MIAMI_RCRAIDER || GetModelIndex() == MI_MIAMI_SPARROW || bAllCarCheat){ +#ifdef ALLCARSHELI_CHEAT + if (bAllCarCheat) + FlyingControl(FLIGHT_MODEL_HELI); + else +#endif + { + if (CPad::GetPad(0)->GetCircleJustDown()) + m_aWheelSpeed[0] = Max(m_aWheelSpeed[0] - 0.03f, 0.0f); + if (m_aWheelSpeed[0] < 0.22f) + m_aWheelSpeed[0] += 0.0001f; + if (m_aWheelSpeed[0] > 0.15f) + FlyingControl(FLIGHT_MODEL_HELI); + } + } + } + + + + // Process car on fire + // A similar calculation of damagePos is done elsewhere for smoke + + uint8 engineStatus = Damage.GetEngineStatus(); + CVector damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_positions[CAR_POS_HEADLIGHTS]; + + switch(Damage.GetDoorStatus(DOOR_BONNET)){ + case DOOR_STATUS_OK: + case DOOR_STATUS_SMASHED: + // Bonnet is still there, smoke comes out at the edge + damagePos += vecDAMAGE_ENGINE_POS_SMALL; + break; + case DOOR_STATUS_SWINGING: + case DOOR_STATUS_MISSING: + // Bonnet is gone, smoke comes out at the engine + damagePos += vecDAMAGE_ENGINE_POS_BIG; + break; + } + + // move fire forward if in first person + if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson()) + if(m_fHealth < 250.0f && GetStatus() != STATUS_WRECKED){ + if(GetModelIndex() == MI_FIRETRUCK) + damagePos += CVector(0.0f, 3.0f, -0.2f); + else + damagePos += CVector(0.0f, 1.2f, -0.8f); + } + + damagePos = GetMatrix()*damagePos; + damagePos.z += 0.15f; + + if(m_fHealth < 250.0f && GetStatus() != STATUS_WRECKED){ + // Car is on fire + + CParticle::AddParticle(PARTICLE_CARFLAME, damagePos, + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.01125f, 0.09f)), + nil, 0.9f); + + CVector coors = damagePos; + coors.x += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f), + coors.y += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f), + coors.z += CGeneral::GetRandomNumberInRange(0.5625f, 2.25f); + CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, coors, CVector(0.0f, 0.0f, 0.0f)); + + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, CVector(0.0f, 0.0f, 0.0f), nil, 0.5f); + + // Blow up car after 5 seconds + m_fFireBlowUpTimer += CTimer::GetTimeStepInMilliseconds(); + if(m_fFireBlowUpTimer > 5000.0f){ + CWorld::Players[CWorld::PlayerInFocus].AwardMoneyForExplosion(this); + BlowUpCar(m_pSetOnFireEntity); + } + }else + m_fFireBlowUpTimer = 0.0f; + + // Decrease car health if engine is damaged badly + if(engineStatus > ENGINE_STATUS_ON_FIRE && m_fHealth > 250.0f) + m_fHealth -= 2.0f; + + ProcessDelayedExplosion(); + + + if(m_bSirenOrAlarm && (CTimer::GetFrameCounter()&7) == 5 && + UsesSiren(GetModelIndex()) && GetModelIndex() != MI_MRWHOOP) + CCarAI::MakeWayForCarWithSiren(this); + + + // Find out how much to shake the pad depending on suspension and ground surface + + float suspShake = 0.0f; + float surfShake = 0.0f; + for(i = 0; i < 4; i++){ + float suspChange = m_aSuspensionSpringRatioPrev[i] - m_aSuspensionSpringRatio[i]; + if(suspChange > 0.3f){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP, suspChange); + if(suspChange > suspShake) + suspShake = suspChange; + } + + uint8 surf = m_aWheelColPoints[i].surfaceB; + if(surf == SURFACE_GRAVEL || surf == SURFACE_WATER || surf == SURFACE_HEDGE){ + if(surfShake < 0.2f) + surfShake = 0.3f; + }else if(surf == SURFACE_MUD_DRY || surf == SURFACE_SAND){ + if(surfShake < 0.1f) + surfShake = 0.2f; + }else if(surf == SURFACE_GRASS){ + if(surfShake < 0.05f) + surfShake = 0.1f; + } + + m_aSuspensionSpringRatioPrev[i] = m_aSuspensionSpringRatio[i]; + m_aSuspensionSpringRatio[i] = 1.0f; + } + + // Shake pad + + if((suspShake > 0.0f || surfShake > 0.0f) && GetStatus() == STATUS_PLAYER){ + float speed = m_vecMoveSpeed.MagnitudeSqr(); + if(speed > sq(0.1f)){ + speed = Sqrt(speed); + if(suspShake > 0.0f){ + uint8 freq = Min(200.0f*suspShake*speed*2000.0f/m_fMass + 100.0f, 250.0f); + CPad::GetPad(0)->StartShake(20000.0f*CTimer::GetTimeStep()/freq, freq); + }else{ + uint8 freq = Min(200.0f*surfShake*speed*2000.0f/m_fMass + 40.0f, 150.0f); + CPad::GetPad(0)->StartShake(5000.0f*CTimer::GetTimeStep()/freq, freq); + } + } + } + + bVehicleColProcessed = false; + + if(!bWarnedPeds) + CCarCtrl::ScanForPedDanger(this); + + + // Turn around at the edge of the world + // TODO: make the numbers defines + + float heading; + if(GetPosition().x > 1900.0f){ + if(m_vecMoveSpeed.x > 0.0f) + m_vecMoveSpeed.x *= -1.0f; + heading = GetForward().Heading(); + if(heading > 0.0f) // going west + SetHeading(-heading); + }else if(GetPosition().x < -1900.0f){ + if(m_vecMoveSpeed.x < 0.0f) + m_vecMoveSpeed.x *= -1.0f; + heading = GetForward().Heading(); + if(heading < 0.0f) // going east + SetHeading(-heading); + } + if(GetPosition().y > 1900.0f){ + if(m_vecMoveSpeed.y > 0.0f) + m_vecMoveSpeed.y *= -1.0f; + heading = GetForward().Heading(); + if(heading < HALFPI && heading > 0.0f) + SetHeading(PI-heading); + else if(heading > -HALFPI && heading < 0.0f) + SetHeading(-PI-heading); + }else if(GetPosition().y < -1900.0f){ + if(m_vecMoveSpeed.y < 0.0f) + m_vecMoveSpeed.y *= -1.0f; + heading = GetForward().Heading(); + if(heading > HALFPI) + SetHeading(PI-heading); + else if(heading < -HALFPI) + SetHeading(-PI-heading); + } + + if(bInfiniteMass){ + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + }else if(!skipPhysics && + (m_fGasPedal == 0.0f && brake == 0.0f || GetStatus() == STATUS_WRECKED)){ + if(Abs(m_vecMoveSpeed.x) < 0.005f && + Abs(m_vecMoveSpeed.y) < 0.005f && + Abs(m_vecMoveSpeed.z) < 0.005f){ + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed.z = 0.0f; + } + } +} + +#pragma optimize("", on) + +void +CAutomobile::Teleport(CVector pos) +{ + CWorld::Remove(this); + + SetPosition(pos); + SetOrientation(0.0f, 0.0f, 0.0f); + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + + ResetSuspension(); + + CWorld::Add(this); +} + +void +CAutomobile::PreRender(void) +{ + int i, j, n; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + if(GetModelIndex() == MI_RCBANDIT){ + CVector pos = GetMatrix() * CVector(0.218f, -0.444f, 0.391f); + CAntennas::RegisterOne((uintptr)this, GetUp(), pos, 1.0f); + } + + float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward())*180.0f; + + + // Wheel particles + + if(GetModelIndex() == MI_DODO){ + ; // nothing + }else if(GetModelIndex() == MI_RCBANDIT){ + for(i = 0; i < 4; i++){ + // Game has same code three times here + switch(m_aWheelState[i]){ + case WHEEL_STATE_SPINNING: + case WHEEL_STATE_SKIDDING: + case WHEEL_STATE_FIXED: + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.05f), + CVector(0.0f, 0.0f, 0.0f), nil, 0.1f); + break; + default: break; + } + } + }else{ + if(GetStatus() == STATUS_SIMPLE){ + CMatrix mat; + CVector pos; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB])); + pos = mat.GetPosition(); + pos.z = 1.5f*m_aWheelPosition[CARWHEEL_REAR_RIGHT]; + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point = GetMatrix() * pos; + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB = SURFACE_DEFAULT; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB])); + pos = mat.GetPosition(); + pos.z = 1.5f*m_aWheelPosition[CARWHEEL_REAR_LEFT]; + m_aWheelColPoints[CARWHEEL_REAR_LEFT].point = GetMatrix() * pos; + m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB = SURFACE_DEFAULT; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos = mat.GetPosition(); + pos.z = 1.5f*m_aWheelPosition[CARWHEEL_FRONT_RIGHT]; + m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].point = GetMatrix() * pos; + m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceB = SURFACE_DEFAULT; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + pos = mat.GetPosition(); + pos.z = 1.5f*m_aWheelPosition[CARWHEEL_FRONT_LEFT]; + m_aWheelColPoints[CARWHEEL_FRONT_LEFT].point = GetMatrix() * pos; + m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceB = SURFACE_DEFAULT; + } + + int drawParticles = Abs(fwdSpeed) < 90.0f; + if(GetStatus() == STATUS_SIMPLE || GetStatus() == STATUS_PHYSICS || + GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PLAYER_PLAYBACKFROMBUFFER){ + bool rearSkidding = false; + if(m_aWheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SKIDDING || + m_aWheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SKIDDING) + rearSkidding = true; + + for(i = 0; i < 4; i++){ + switch(m_aWheelState[i]){ + case WHEEL_STATE_SPINNING: + if(AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles)){ + CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.0f)); + + CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.05f)); + } + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[i] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[i], &m_aWheelSkidmarkBloody[i]); + break; + + case WHEEL_STATE_SKIDDING: + if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT || rearSkidding){ + // same as below + + AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[i] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[i], &m_aWheelSkidmarkBloody[i]); + } + break; + + case WHEEL_STATE_FIXED: + AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[i].point + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[i] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[i], &m_aWheelSkidmarkBloody[i]); + break; + + default: + if(Abs(fwdSpeed) > 5.0f) + AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles); + if(m_aWheelSkidmarkBloody[i] && m_aWheelTimer[i] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[i], &m_aWheelSkidmarkBloody[i]); + } + } + } + } + + if(m_aCarNodes[CAR_WHEEL_RM]){ + // assume middle wheels are two units before rear ones + CVector offset = GetForward()*2.0f; + + switch(m_aWheelState[CARWHEEL_REAR_LEFT]){ + // Game has same code three times here + case WHEEL_STATE_SPINNING: + case WHEEL_STATE_SKIDDING: + case WHEEL_STATE_FIXED: + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[CARWHEEL_REAR_LEFT].point + CVector(0.0f, 0.0f, 0.25f) + offset, + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + 5, + m_aWheelColPoints[CARWHEEL_REAR_LEFT].point + offset, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[CARWHEEL_REAR_LEFT], &m_aWheelSkidmarkBloody[CARWHEEL_REAR_LEFT]); + break; + default: break; + } + + switch(m_aWheelState[CARWHEEL_REAR_RIGHT]){ + // Game has same code three times here + case WHEEL_STATE_SPINNING: + case WHEEL_STATE_SKIDDING: + case WHEEL_STATE_FIXED: + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point + CVector(0.0f, 0.0f, 0.25f) + offset, + CVector(0.0f, 0.0f, 0.0f)); + + if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + 6, + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point + offset, + GetForward().x, GetForward().y, + &m_aWheelSkidmarkMuddy[CARWHEEL_REAR_RIGHT], &m_aWheelSkidmarkBloody[CARWHEEL_REAR_RIGHT]); + break; + default: break; + } + } + + + // Rain on roof + if(!CCullZones::CamNoRain() && !CCullZones::PlayerNoRain() && + Abs(fwdSpeed) < 20.0f && CWeather::Rain > 0.02f){ + CColModel *colModel = GetColModel(); + + for(i = 0; i < colModel->numTriangles; i++){ + CVector p1, p2, p3, c; + + colModel->GetTrianglePoint(p1, colModel->triangles[i].a); + p1 = GetMatrix() * p1; + colModel->GetTrianglePoint(p2, colModel->triangles[i].b); + p2 = GetMatrix() * p2; + colModel->GetTrianglePoint(p3, colModel->triangles[i].c); + p3 = GetMatrix() * p3; + c = (p1 + p2 + p3)/3.0f; + + n = 6.0f*CWeather::Rain; + for(j = 0; j <= n; j++) + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, + c + CVector(CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), CGeneral::GetRandomNumberInRange(-0.4f, 0.4f), 0.0f), + CVector(0.0f, 0.0f, 0.0f), + nil, 0.0f, 0, 0, CGeneral::GetRandomNumber() & 1); + } + } + + AddDamagedVehicleParticles(); + + // Exhaust smoke + if(bEngineOn && fwdSpeed < 90.0f){ + CVector exhaustPos = mi->m_positions[CAR_POS_EXHAUST]; + CVector pos1, pos2, dir; + + if(exhaustPos != CVector(0.0f, 0.0f, 0.0f)){ + dir.z = 0.0f; + if(fwdSpeed < 10.0f){ + CVector steerFwd(-Sin(m_fSteerAngle), Cos(m_fSteerAngle), 0.0f); + steerFwd = Multiply3x3(GetMatrix(), steerFwd); + float r = CGeneral::GetRandomNumberInRange(-0.06f, -0.03f); + dir.x = steerFwd.x * r; + dir.y = steerFwd.y * r; + }else{ + dir.x = m_vecMoveSpeed.x; + dir.y = m_vecMoveSpeed.y; + } + + bool dblExhaust = false; + pos1 = GetMatrix() * exhaustPos; + if(pHandling->Flags & HANDLING_DBL_EXHAUST){ + dblExhaust = true; + pos2 = exhaustPos; + pos2.x = -pos2.x; + pos2 = GetMatrix() * pos2; + } + + n = 4.0f*m_fGasPedal; + if(dblExhaust) + for(i = 0; i <= n; i++){ + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir); + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos2, dir); + } + else + for(i = 0; i <= n; i++) + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir); + } + } + + + // Siren and taxi lights + switch(GetModelIndex()){ + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_POLICE: + case MI_ENFORCER: + if(m_bSirenOrAlarm){ + CVector pos1, pos2; + uint8 r1, g1, b1; + uint8 r2, g2, b2; + uint8 r, g, b; + + switch(GetModelIndex()){ + case MI_FIRETRUCK: + pos1 = CVector(1.1f, 1.7f, 2.0f); + pos2 = CVector(-1.1f, 1.7f, 2.0f); + r1 = 255; g1 = 0; b1 = 0; + r2 = 255; g2 = 255; b2 = 0; + break; + case MI_AMBULAN: + pos1 = CVector(1.1f, 0.9f, 1.6f); + pos2 = CVector(-1.1f, 0.9f, 1.6f); + r1 = 255; g1 = 0; b1 = 0; + r2 = 255; g2 = 255; b2 = 255; + break; + case MI_POLICE: + pos1 = CVector(0.7f, -0.4f, 1.0f); + pos2 = CVector(-0.7f, -0.4f, 1.0f); + r1 = 255; g1 = 0; b1 = 0; + r2 = 0; g2 = 0; b2 = 255; + break; + case MI_ENFORCER: + pos1 = CVector(1.1f, 0.8f, 1.2f); + pos2 = CVector(-1.1f, 0.8f, 1.2f); + r1 = 255; g1 = 0; b1 = 0; + r2 = 0; g2 = 0; b2 = 255; + break; + } + + uint32 t = CTimer::GetTimeInMilliseconds() & 0x3FF; // 1023 + if(t < 512){ + r = r1/6; + g = g1/6; + b = b1/6; + }else{ + r = r2/6; + g = g2/6; + b = b2/6; + } + + t = CTimer::GetTimeInMilliseconds() & 0x1FF; // 511 + if(t < 100){ + float f = t/100.0f; + r *= f; + g *= f; + b *= f; + }else if(t > (512-100)){ + float f = (512-t)/100.0f; + r *= f; + g *= f; + b *= f; + } + + CVector pos = GetPosition(); + float angle = (CTimer::GetTimeInMilliseconds() & 0x3FF)*TWOPI/0x3FF; + float s = 8.0f*Sin(angle); + float c = 8.0f*Cos(angle); + CShadows::StoreCarLightShadow(this, (uintptr)this + 21, gpShadowHeadLightsTex, + &pos, c, s, s, -c, r, g, b, 8.0f); + + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos + GetUp()*2.0f, CVector(0.0f, 0.0f, 0.0f), 12.0f, + r*0.02f, g*0.02f, b*0.02f, CPointLights::FOG_NONE, true); + + pos1 = GetMatrix() * pos1; + pos2 = GetMatrix() * pos2; + + for(i = 0; i < 4; i++){ + uint8 sirenTimer = ((CTimer::GetTimeInMilliseconds() + (i<<6))>>8) & 3; + pos = (pos1*i + pos2*(3.0f-i))/3.0f; + + switch(sirenTimer){ + case 0: + CCoronas::RegisterCorona((uintptr)this + 21 + i, + r1, g1, b1, 255, + pos, 0.4f, 50.0f, + CCoronas::TYPE_STAR, + i == 1 ? CCoronas::FLARE_HEADLIGHTS : CCoronas::FLARE_NONE, + CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + break; + case 2: + CCoronas::RegisterCorona((uintptr)this + 21 + i, + r2, g2, b2, 255, + pos, 0.4f, 50.0f, + CCoronas::TYPE_STAR, + CCoronas::FLARE_NONE, + CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + break; + default: + CCoronas::UpdateCoronaCoors((uintptr)this + 21 + i, pos, 50.0f, 0.0f); + break; + } + } + } + break; + + case MI_FBICAR: + if(m_bSirenOrAlarm){ + CVector pos = GetMatrix() * CVector(0.4f, 0.6f, 0.3f); + if(CTimer::GetTimeInMilliseconds() & 0x100 && + DotProduct(GetForward(), GetPosition() - TheCamera.GetPosition()) < 0.0f) + CCoronas::RegisterCorona((uintptr)this + 21, + 0, 0, 255, 255, + pos, 0.4f, 50.0f, + CCoronas::TYPE_STAR, + CCoronas::FLARE_NONE, + CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::UpdateCoronaCoors((uintptr)this + 21, pos, 50.0f, 0.0f); + } + break; + + case MI_TAXI: + case MI_CABBIE: + case MI_BORGNINE: + if(bTaxiLight){ + CVector pos = GetPosition() + GetUp()*0.95f; + CCoronas::RegisterCorona((uintptr)this + 21, + 128, 128, 0, 255, + pos, 0.8f, 50.0f, + CCoronas::TYPE_NORMAL, + CCoronas::FLARE_NONE, + CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos, CVector(0.0f, 0.0f, 0.0f), 10.0f, + 1.0f, 1.0f, 0.5f, CPointLights::FOG_NONE, true); + } + break; + } + + if(GetModelIndex() != MI_RCBANDIT && GetModelIndex() != MI_DODO && + GetModelIndex() != MI_RHINO) { + // Process lights + + // Turn lights on/off + bool shouldLightsBeOn = + CClock::GetHours() > 20 || + CClock::GetHours() > 19 && CClock::GetMinutes() > (m_randomSeed & 0x3F) || + CClock::GetHours() < 7 || + CClock::GetHours() < 8 && CClock::GetMinutes() < (m_randomSeed & 0x3F) || + m_randomSeed/50000.0f < CWeather::Foggyness || + m_randomSeed/50000.0f < CWeather::WetRoads; + if(shouldLightsBeOn != bLightsOn && GetStatus() != STATUS_WRECKED){ + if(GetStatus() == STATUS_ABANDONED){ + // Turn off lights on abandoned vehicles only when we they're far away + if(bLightsOn && + Abs(TheCamera.GetPosition().x - GetPosition().x) + Abs(TheCamera.GetPosition().y - GetPosition().y) > 100.0f) + bLightsOn = false; + }else + bLightsOn = shouldLightsBeOn; + } + + // Actually render the lights + bool alarmOn = false; + bool alarmOff = false; + if(IsAlarmOn()){ + if(CTimer::GetTimeInMilliseconds() & 0x100) + alarmOn = true; + else + alarmOff = true; + } + if(bEngineOn && bLightsOn || alarmOn || alarmOff){ + CVector lookVector = GetPosition() - TheCamera.GetPosition(); + float camDist = lookVector.Magnitude(); + if(camDist != 0.0f) + lookVector *= 1.0f/camDist; + else + lookVector = CVector(1.0f, 0.0f, 0.0f); + + // 1.0 if directly behind car, -1.0 if in front + // BUG on PC: Abs of DotProduct is taken + float behindness = DotProduct(lookVector, GetForward()); + behindness = Clamp(behindness, -1.0f, 1.0f); // shouldn't be necessary + // 0.0 if behind car, PI if in front + // Abs not necessary + float angle = Abs(Acos(behindness)); + + // Headlights + + CVector headLightPos = mi->m_positions[CAR_POS_HEADLIGHTS]; + CVector lightR = GetMatrix() * headLightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*headLightPos.x; + + // Headlight coronas + if(behindness < 0.0f){ + // In front of car + float intensity = -0.5f*behindness + 0.3f; + float size = 1.0f - behindness; + + if(behindness < -0.97f && camDist < 30.0f){ + // Directly in front and not too far away + if(pHandling->Flags & HANDLING_HALOGEN_LIGHTS){ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 6, 150, 150, 195, 255, + lightL, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 7, 150, 150, 195, 255, + lightR, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + }else{ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 6, 160, 160, 140, 255, + lightL, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 7, 160, 160, 140, 255, + lightR, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + } + } + + if(alarmOff){ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this, 0, 0, 0, 0, + lightL, size, 0.0f, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 1, 0, 0, 0, 0, + lightR, size, 0.0f, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + if(pHandling->Flags & HANDLING_HALOGEN_LIGHTS){ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this, 190*intensity, 190*intensity, 255*intensity, 255, + lightL, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 1, 190*intensity, 190*intensity, 255*intensity, 255, + lightR, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this, 210*intensity, 210*intensity, 195*intensity, 255, + lightL, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 1, 210*intensity, 210*intensity, 195*intensity, 255, + lightR, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + } + } + }else{ + // Behind car + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this, lightL, 50.0f*TheCamera.LODDistMultiplier, angle); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 1, lightR, 50.0f*TheCamera.LODDistMultiplier, angle); + } + + // bright lights + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK && !bNoBrightHeadLights) + CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT); + if(Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK && !bNoBrightHeadLights) + CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT); + + // Taillights + + CVector tailLightPos = mi->m_positions[CAR_POS_TAILLIGHTS]; + lightR = GetMatrix() * tailLightPos; + lightL = lightR; + lightL -= GetRight()*2.0f*tailLightPos.x; + + // Taillight coronas + if(behindness > 0.0f){ + // Behind car + float intensity = (behindness + 1.0f)*0.4f; + float size = (behindness + 1.0f)*0.5f; + + if(m_fGasPedal < 0.0f){ + // reversing + intensity += 0.4f; + size += 0.3f; + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 128*intensity, 128*intensity, 128*intensity, 255, + lightL, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 128*intensity, 128*intensity, 128*intensity, 255, + lightR, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + if(m_fBrakePedal > 0.0f){ + intensity += 0.4f; + size += 0.3f; + } + + if(alarmOff){ + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 0, 0, 0, 0, + lightL, size, 0.0f, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 0, 0, 0, 0, + lightR, size, 0.0f, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 128*intensity, 0, 0, 255, + lightL, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 128*intensity, 0, 0, 255, + lightR, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + } + } + }else{ + // In front of car + // missing LODDistMultiplier probably a BUG + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 14, lightL, 50.0f, angle); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 15, lightR, 50.0f, angle); + } + + // bright lights + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR); + + // Light shadows + if(!alarmOff){ + CVector pos = GetPosition(); + CVector2D fwd(GetForward()); + fwd.Normalise(); + float f = headLightPos.y + 6.0f; + pos += CVector(f*fwd.x, f*fwd.y, 2.0f); + + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK || + Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CShadows::StoreCarLightShadow(this, (uintptr)this + 22, gpShadowHeadLightsTex, &pos, + 7.0f*fwd.x, 7.0f*fwd.y, 7.0f*fwd.y, -7.0f*fwd.x, 45, 45, 45, 7.0f); + + f = (tailLightPos.y - 2.5f) - (headLightPos.y + 6.0f); + pos += CVector(f*fwd.x, f*fwd.y, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK || + Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CShadows::StoreCarLightShadow(this, (uintptr)this + 25, gpShadowExplosionTex, &pos, + 3.0f, 0.0f, 0.0f, -3.0f, 35, 0, 0, 4.0f); + } + + if(this == FindPlayerVehicle() && !alarmOff){ + if(Damage.GetLightStatus(VEHLIGHT_FRONT_LEFT) == LIGHT_STATUS_OK || + Damage.GetLightStatus(VEHLIGHT_FRONT_RIGHT) == LIGHT_STATUS_OK) + CPointLights::AddLight(CPointLights::LIGHT_DIRECTIONAL, GetPosition(), GetForward(), + 20.0f, 1.0f, 1.0f, 1.0f, + FindPlayerVehicle()->m_vecMoveSpeed.MagnitudeSqr2D() < sq(0.45f) ? CPointLights::FOG_NORMAL : CPointLights::FOG_NONE, + false); + CVector pos = GetPosition() - 4.0f*GetForward(); + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK || + Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) { + if(m_fBrakePedal > 0.0f) + CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f), + 10.0f, 1.0f, 0.0f, 0.0f, + CPointLights::FOG_NONE, false); + else + CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f), + 7.0f, 0.6f, 0.0f, 0.0f, + CPointLights::FOG_NONE, false); + } + } + }else if(GetStatus() != STATUS_ABANDONED && GetStatus() != STATUS_WRECKED){ + // Lights off + + CVector lightPos = mi->m_positions[CAR_POS_TAILLIGHTS]; + CVector lightR = GetMatrix() * lightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*lightPos.x; + + if(m_fBrakePedal > 0.0f || m_fGasPedal < 0.0f){ + CVector lookVector = GetPosition() - TheCamera.GetPosition(); + lookVector.Normalise(); + float behindness = DotProduct(lookVector, GetForward()); + if(behindness > 0.0f){ + if(m_fGasPedal < 0.0f){ + // reversing + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 120, 120, 120, 255, + lightL, 1.2f, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 120, 120, 120, 255, + lightR, 1.2f, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_FRONT); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_FRONT); + }else{ + // braking + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 14, 120, 0, 0, 255, + lightL, 1.2f, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::RegisterCorona((uintptr)this + 15, 120, 0, 0, 255, + lightR, 1.2f, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightL, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CBrightLights::RegisterOne(lightR, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR); + } + }else{ + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 14, lightL, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 15, lightR, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + } + }else{ + if(Damage.GetLightStatus(VEHLIGHT_REAR_LEFT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 14, lightL, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + if(Damage.GetLightStatus(VEHLIGHT_REAR_RIGHT) == LIGHT_STATUS_OK) + CCoronas::UpdateCoronaCoors((uintptr)this + 15, lightR, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + } + } + // end of lights + } + + CShadows::StoreShadowForCar(this); +} + +void +CAutomobile::Render(void) +{ + int i; + CMatrix mat; + CVector pos; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + if(GetModelIndex() == MI_RHINO && m_aCarNodes[CAR_BONNET]){ + // Rotate Rhino turret + CMatrix m; + CVector p; + m.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BONNET])); + p = m.GetPosition(); + m.SetRotateZ(m_fCarGunLR); + m.Translate(p); + m.UpdateRW(); + } + + CVector contactPoints[4]; // relative to model + CVector contactSpeeds[4]; // speed at contact points + CVector frontWheelFwd = Multiply3x3(GetMatrix(), CVector(-Sin(m_fSteerAngle), Cos(m_fSteerAngle), 0.0f)); + CVector rearWheelFwd = GetForward(); + for(i = 0; i < 4; i++){ + if (m_aWheelTimer[i] > 0.0f) { + contactPoints[i] = m_aWheelColPoints[i].point - GetPosition(); + contactSpeeds[i] = GetSpeed(contactPoints[i]); + if (i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT) + m_aWheelSpeed[i] = ProcessWheelRotation(m_aWheelState[i], frontWheelFwd, contactSpeeds[i], 0.5f*mi->m_wheelScale); + else + m_aWheelSpeed[i] = ProcessWheelRotation(m_aWheelState[i], rearWheelFwd, contactSpeeds[i], 0.5f*mi->m_wheelScale); + m_aWheelRotation[i] += m_aWheelSpeed[i]; + } + } + + // Rear right wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_REAR_RIGHT]; + if(Damage.GetWheelStatus(CARWHEEL_REAR_RIGHT) == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[CARWHEEL_REAR_RIGHT], 0.0f, 0.3f*Sin(m_aWheelRotation[CARWHEEL_REAR_RIGHT])); + else + mat.SetRotateX(m_aWheelRotation[CARWHEEL_REAR_RIGHT]); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB])); + + // Rear left wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_REAR_LEFT]; + if(Damage.GetWheelStatus(CARWHEEL_REAR_LEFT) == WHEEL_STATUS_BURST) + mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI+0.3f*Sin(-m_aWheelRotation[CARWHEEL_REAR_LEFT])); + else + mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB])); + + // Mid right wheel + if(m_aCarNodes[CAR_WHEEL_RM]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RM])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_REAR_RIGHT]; + if(Damage.GetWheelStatus(CARWHEEL_REAR_RIGHT) == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[CARWHEEL_REAR_RIGHT], 0.0f, 0.3f*Sin(m_aWheelRotation[CARWHEEL_REAR_RIGHT])); + else + mat.SetRotateX(m_aWheelRotation[CARWHEEL_REAR_RIGHT]); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RM])); + } + + // Mid left wheel + if(m_aCarNodes[CAR_WHEEL_LM]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LM])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_REAR_LEFT]; + if(Damage.GetWheelStatus(CARWHEEL_REAR_LEFT) == WHEEL_STATUS_BURST) + mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI+0.3f*Sin(-m_aWheelRotation[CARWHEEL_REAR_LEFT])); + else + mat.SetRotate(-m_aWheelRotation[CARWHEEL_REAR_LEFT], 0.0f, PI); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LM])); + } + + if(GetModelIndex() == MI_DODO){ + // Front wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_RIGHT]; + if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle+0.3f*Sin(m_aWheelRotation[CARWHEEL_FRONT_RIGHT])); + else + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF])); + + // Rotate propeller + if(m_aCarNodes[CAR_WINDSCREEN]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WINDSCREEN])); + pos = mat.GetPosition(); + mat.SetRotateY(m_fPropellerRotation); + mat.Translate(pos); + mat.UpdateRW(); + + m_fPropellerRotation += m_fGasPedal != 0.0f ? TWOPI/13.0f : TWOPI/26.0f; + if(m_fPropellerRotation > TWOPI) + m_fPropellerRotation -= TWOPI; + } + + // Rudder + if(Damage.GetDoorStatus(DOOR_BOOT) != DOOR_STATUS_MISSING && m_aCarNodes[CAR_BOOT]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BOOT])); + pos = mat.GetPosition(); + mat.SetRotate(0.0f, 0.0f, -m_fSteerAngle); + mat.Rotate(0.0f, Sin(m_fSteerAngle)*DEGTORAD(22.0f), 0.0f); + mat.Translate(pos); + mat.UpdateRW(); + } + + ProcessSwingingDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT); + ProcessSwingingDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + }else if(GetModelIndex() == MI_RHINO){ + // Front right wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_RIGHT]; + // no damaged wheels or steering + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, 0.0f); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF])); + + // Front left wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_LEFT]; + // no damaged wheels or steering + mat.SetRotate(-m_aWheelRotation[CARWHEEL_FRONT_LEFT], 0.0f, PI); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF])); + }else{ + // Front right wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_RIGHT]; + if(Damage.GetWheelStatus(CARWHEEL_FRONT_RIGHT) == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle+0.3f*Sin(m_aWheelRotation[CARWHEEL_FRONT_RIGHT])); + else + mat.SetRotate(m_aWheelRotation[CARWHEEL_FRONT_RIGHT], 0.0f, m_fSteerAngle); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF])); + + // Front left wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + pos.x = mat.GetPosition().x; + pos.y = mat.GetPosition().y; + pos.z = m_aWheelPosition[CARWHEEL_FRONT_LEFT]; + if(Damage.GetWheelStatus(CARWHEEL_FRONT_LEFT) == WHEEL_STATUS_BURST) + mat.SetRotate(-m_aWheelRotation[CARWHEEL_FRONT_LEFT], 0.0f, PI+m_fSteerAngle+0.3f*Sin(-m_aWheelRotation[CARWHEEL_FRONT_LEFT])); + else + mat.SetRotate(-m_aWheelRotation[CARWHEEL_FRONT_LEFT], 0.0f, PI+m_fSteerAngle); + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + if(CVehicle::bWheelsOnlyCheat) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF])); + + ProcessSwingingDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT); + ProcessSwingingDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + ProcessSwingingDoor(CAR_DOOR_LR, DOOR_REAR_LEFT); + ProcessSwingingDoor(CAR_DOOR_RR, DOOR_REAR_RIGHT); + ProcessSwingingDoor(CAR_BONNET, DOOR_BONNET); + ProcessSwingingDoor(CAR_BOOT, DOOR_BOOT); + + mi->SetVehicleColour(m_currentColour1, m_currentColour2); + } + + if(!CVehicle::bWheelsOnlyCheat) + CEntity::Render(); +} + +int32 +CAutomobile::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints) +{ + int i; + CColModel *colModel; + + if(GetStatus() != STATUS_SIMPLE) + bVehicleColProcessed = true; + + if(bUsingSpecialColModel) + colModel = &CWorld::Players[CWorld::PlayerInFocus].m_ColModel; + else + colModel = GetColModel(); + + int numWheelCollisions = 0; + float prevRatios[4] = { 0.0f, 0.0f, 0.0f, 0.0f}; + for(i = 0; i < 4; i++) + prevRatios[i] = m_aSuspensionSpringRatio[i]; + + int numCollisions = CCollision::ProcessColModels(GetMatrix(), *colModel, + ent->GetMatrix(), *ent->GetColModel(), + colpoints, + m_aWheelColPoints, m_aSuspensionSpringRatio); + + // m_aSuspensionSpringRatio are now set to the point where the tyre touches ground. + // In ProcessControl these will be re-normalized to ignore the tyre radius. + + if(m_bIsVehicleBeingShifted || bSkipLineCol || + GetModelIndex() == MI_DODO && (ent->IsPed() || ent->IsVehicle())){ + // don't do line collision + for(i = 0; i < 4; i++) + m_aSuspensionSpringRatio[i] = prevRatios[i]; + }else{ + for(i = 0; i < 4; i++) + if(m_aSuspensionSpringRatio[i] < 1.0f && m_aSuspensionSpringRatio[i] < prevRatios[i]){ + numWheelCollisions++; + + // wheel is touching a physical + if(ent->IsVehicle() || ent->IsObject()){ + CPhysical *phys = (CPhysical*)ent; + + m_aGroundPhysical[i] = phys; + phys->RegisterReference((CEntity**)&m_aGroundPhysical[i]); + m_aGroundOffset[i] = m_aWheelColPoints[i].point - phys->GetPosition(); + + if(phys->GetModelIndex() == MI_BODYCAST && GetStatus() == STATUS_PLAYER){ + // damage body cast + float speed = m_vecMoveSpeed.MagnitudeSqr(); + if(speed > 0.1f){ + CObject::nBodyCastHealth -= 0.1f*m_fMass*speed; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_PED_BODYCAST_HIT, 0.0f); + } + + // move body cast + if(phys->GetIsStatic()){ + phys->SetIsStatic(false); + phys->m_nStaticFrames = 0; + phys->ApplyMoveForce(m_vecMoveSpeed / Sqrt(speed)); + phys->AddToMovingList(); + } + } + } + + m_nSurfaceTouched = m_aWheelColPoints[i].surfaceB; + if(ent->IsBuilding()) + m_pCurGroundEntity = ent; + } + } + + if(numCollisions > 0 || numWheelCollisions > 0){ + AddCollisionRecord(ent); + if(!ent->IsBuilding()) + ((CPhysical*)ent)->AddCollisionRecord(this); + + if(numCollisions > 0) + if(ent->IsBuilding() || + ent->IsObject() && ((CPhysical*)ent)->bInfiniteMass) + bHasHitWall = true; + } + + return numCollisions; +} + +static int16 nLastControlInput; +static float fMouseCentreRange = 0.35f; +static float fMouseSteerSens = -0.0035f; +static float fMouseCentreMult = 0.975f; + +void +CAutomobile::ProcessControlInputs(uint8 pad) +{ + float speed = DotProduct(m_vecMoveSpeed, GetForward()); + + if(CPad::GetPad(pad)->GetExitVehicle()) + bIsHandbrakeOn = true; + else + bIsHandbrakeOn = !!CPad::GetPad(pad)->GetHandBrake(); + + // Steer left/right + if(CCamera::m_bUseMouse3rdPerson && !CVehicle::m_bDisableMouseSteering){ + if(CPad::GetPad(pad)->GetMouseX() != 0.0f){ + m_fSteerInput += fMouseSteerSens*CPad::GetPad(pad)->GetMouseX(); + nLastControlInput = 2; + if(Abs(m_fSteerInput) < fMouseCentreRange) + m_fSteerInput *= Pow(fMouseCentreMult, CTimer::GetTimeStep()); + }else if(CPad::GetPad(pad)->GetSteeringLeftRight() || nLastControlInput != 2){ + // mouse hasn't move, steer with pad like below + m_fSteerInput += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerInput)* + 0.2f*CTimer::GetTimeStep(); + nLastControlInput = 0; + } + }else{ + m_fSteerInput += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteerInput)* + 0.2f*CTimer::GetTimeStep(); + nLastControlInput = 0; + } + m_fSteerInput = Clamp(m_fSteerInput, -1.0f, 1.0f); + + // Accelerate/Brake + float acceleration = (CPad::GetPad(pad)->GetAccelerate() - CPad::GetPad(pad)->GetBrake())/255.0f; + if(GetModelIndex() == MI_DODO && acceleration < 0.0f) + acceleration *= 0.3f; + if(Abs(speed) < 0.01f){ + // standing still, go into direction we want + m_fGasPedal = acceleration; + m_fBrakePedal = 0.0f; + }else{ +#if 1 + // simpler than the code below + if(speed * acceleration < 0.0f){ + // if opposite directions, have to brake first + m_fGasPedal = 0.0f; + m_fBrakePedal = Abs(acceleration); + }else{ + // accelerating in same direction we were already going + m_fGasPedal = acceleration; + m_fBrakePedal = 0.0f; + } +#else + if(speed < 0.0f){ + // moving backwards currently + if(acceleration < 0.0f){ + // still go backwards + m_fGasPedal = acceleration; + m_fBrakePedal = 0.0f; + }else{ + // want to go forwards, so brake + m_fGasPedal = 0.0f; + m_fBrakePedal = acceleration; + } + }else{ + // moving forwards currently + if(acceleration < 0.0f){ + // want to go backwards, so brake + m_fGasPedal = 0.0f; + m_fBrakePedal = -acceleration; + }else{ + // still go forwards + m_fGasPedal = acceleration; + m_fBrakePedal = 0.0f; + } + } +#endif + } + + // Actually turn wheels + static float fValue; // why static? + if(m_fSteerInput < 0.0f) + fValue = -sq(m_fSteerInput); + else + fValue = sq(m_fSteerInput); + m_fSteerAngle = DEGTORAD(pHandling->fSteeringLock) * fValue; + + if(bComedyControls){ +#if 0 // old comedy controls from PS2 - same as bike's + if(((CTimer::GetTimeInMilliseconds() >> 10) & 0xF) < 12) + m_fGasPedal = 1.0f; + if((((CTimer::GetTimeInMilliseconds() >> 10)+6) & 0xF) < 12) + m_fBrakePedal = 0.0f; + bIsHandbrakeOn = false; + if(CTimer::GetTimeInMilliseconds() & 0x800) + m_fSteerAngle += 0.08f; + else + m_fSteerAngle -= 0.03f; +#else + int rnd = CGeneral::GetRandomNumber() % 10; + switch(m_comedyControlState){ + case 0: + if(rnd < 2) + m_comedyControlState = 1; + else if(rnd < 4) + m_comedyControlState = 2; + break; + case 1: + m_fSteerAngle += 0.05f; + if(rnd < 2) + m_comedyControlState = 0; + break; + case 2: + m_fSteerAngle -= 0.05f; + if(rnd < 2) + m_comedyControlState = 0; + break; + } + }else{ + m_comedyControlState = 0; +#endif + } + + // Brake if player isn't in control + // BUG: game always uses pad 0 here + if(CPad::GetPad(pad)->ArePlayerControlsDisabled()){ + m_fBrakePedal = 1.0f; + bIsHandbrakeOn = true; + m_fGasPedal = 0.0f; + + FindPlayerPed()->KeepAreaAroundPlayerClear(); + + // slow down car immediately + speed = m_vecMoveSpeed.Magnitude(); + if(speed > 0.28f) + m_vecMoveSpeed *= 0.28f/speed; + } +} + +void +CAutomobile::FireTruckControl(void) +{ + if(this == FindPlayerVehicle()){ +#ifdef FIX_BUGS + if (!CPad::GetPad(0)->GetCarGunFired()) +#else + if (!CPad::GetPad(0)->GetWeapon()) +#endif // FIX_BUGS + return; +#ifdef FREE_CAM + if (!CCamera::bFreeCam) +#endif + { + m_fCarGunLR += CPad::GetPad(0)->GetCarGunLeftRight() * 0.00025f * CTimer::GetTimeStep(); + m_fCarGunUD += CPad::GetPad(0)->GetCarGunUpDown() * 0.0001f * CTimer::GetTimeStep(); + } + m_fCarGunUD = Clamp(m_fCarGunUD, 0.05f, 0.3f); + + + CVector cannonPos(0.0f, 1.5f, 1.9f); + cannonPos = GetMatrix() * cannonPos; + CVector cannonDir( + Sin(m_fCarGunLR) * Cos(m_fCarGunUD), + Cos(m_fCarGunLR) * Cos(m_fCarGunUD), + Sin(m_fCarGunUD)); + cannonDir = Multiply3x3(GetMatrix(), cannonDir); + cannonDir.z += (CGeneral::GetRandomNumber()&0xF)/1000.0f; + CWaterCannons::UpdateOne((uintptr)this, &cannonPos, &cannonDir); + }else if(GetStatus() == STATUS_PHYSICS){ + CFire *fire = gFireManager.FindFurthestFire_NeverMindFireMen(GetPosition(), 10.0f, 35.0f); + if(fire == nil) + return; + + // Target cannon onto fire + float targetAngle = CGeneral::GetATanOfXY(fire->m_vecPos.x-GetPosition().x, fire->m_vecPos.y-GetPosition().y); + float fwdAngle = CGeneral::GetATanOfXY(GetForward().x, GetForward().y); + float targetCannonAngle = fwdAngle - targetAngle; + float angleDelta = CTimer::GetTimeStep()*0.01f; + float cannonDelta = targetCannonAngle - m_fCarGunLR; + while(cannonDelta < PI) cannonDelta += TWOPI; + while(cannonDelta > PI) cannonDelta -= TWOPI; + if(Abs(cannonDelta) < angleDelta) + m_fCarGunLR = targetCannonAngle; + else if(cannonDelta > 0.0f) + m_fCarGunLR += angleDelta; + else + m_fCarGunLR -= angleDelta; + + // Go up and down a bit + float upDown = Sin((float)(CTimer::GetTimeInMilliseconds() & 0xFFF)/0x1000 * TWOPI); + m_fCarGunUD = 0.2f + 0.2f*upDown; + + // Spray water every once in a while + if((CTimer::GetTimeInMilliseconds()>>10) & 3){ + CVector cannonPos(0.0f, 0.0f, 2.2f); // different position than player's firetruck! + cannonPos = GetMatrix() * cannonPos; + CVector cannonDir( + Sin(m_fCarGunLR) * Cos(m_fCarGunUD), + Cos(m_fCarGunLR) * Cos(m_fCarGunUD), + Sin(m_fCarGunUD)); + cannonDir = Multiply3x3(GetMatrix(), cannonDir); + cannonDir.z += (CGeneral::GetRandomNumber()&0xF)/1000.0f; + CWaterCannons::UpdateOne((uintptr)this, &cannonPos, &cannonDir); + } + } +} + +void +CAutomobile::TankControl(void) +{ + int i; + + // These coords are 1 unit higher then they should be relative to model center + CVector turrentBase(0.0f, -1.394f, 2.296f); + CVector gunEnd(0.0f, 1.813f, 2.979f); + CVector baseToEnd = gunEnd - turrentBase; + + if(this != FindPlayerVehicle()) + return; + if(CWorld::Players[CWorld::PlayerInFocus].m_WBState != WBSTATE_PLAYING) + return; + + // Rotate turret + float prevAngle = m_fCarGunLR; +#ifdef FREE_CAM + if(!CCamera::bFreeCam) +#endif + m_fCarGunLR -= CPad::GetPad(0)->GetCarGunLeftRight() * 0.00015f * CTimer::GetTimeStep(); + + if(m_fCarGunLR < 0.0f) + m_fCarGunLR += TWOPI; + if(m_fCarGunLR > TWOPI) + m_fCarGunLR -= TWOPI; + if(m_fCarGunLR != prevAngle) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_TANK_TURRET_ROTATE, Abs(m_fCarGunLR - prevAngle)); + + // Shoot + if(CPad::GetPad(0)->CarGunJustDown() && + CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nTimeTankShotGun + 800){ + CWorld::Players[CWorld::PlayerInFocus].m_nTimeTankShotGun = CTimer::GetTimeInMilliseconds(); + + // more like -sin(angle), cos(angle), i.e. rotated (0,1,0) + CVector turretDir = CVector(Sin(-m_fCarGunLR), Cos(-m_fCarGunLR), 0.0f); + turretDir = Multiply3x3(GetMatrix(), turretDir); + + float c = Cos(m_fCarGunLR); + float s = Sin(m_fCarGunLR); + CVector rotatedEnd( + c*baseToEnd.x - s*baseToEnd.y, + s*baseToEnd.x + c*baseToEnd.y, + baseToEnd.z - 1.0f); // correct offset here + rotatedEnd += turrentBase; + + CVector point1 = GetMatrix() * rotatedEnd; + CVector point2 = point1 + 60.0f*turretDir; + m_vecMoveSpeed -= 0.06f*turretDir; + m_vecMoveSpeed.z += 0.05f; + + CWeapon::DoTankDoomAiming(FindPlayerVehicle(), FindPlayerPed(), &point1, &point2); + CColPoint colpoint; + CEntity *entity = nil; + CWorld::ProcessLineOfSight(point1, point2, colpoint, entity, true, true, true, true, true, true, false); + if(entity) + point2 = colpoint.point - 0.04f*(colpoint.point - point1); + + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_TANK_GRENADE, point2, 0); + + // Add particles on the way to the explosion; + float shotDist = (point2 - point1).Magnitude(); + int n = shotDist/4.0f; + RwRGBA black = { 0, 0, 0, 0 }; + for(i = 0; i < n; i++){ + float f = (float)i/n; + CParticle::AddParticle(PARTICLE_HELI_DUST, + point1 + f*(point2 - point1), + CVector(0.0f, 0.0f, 0.0f), + nil, 0.1f, black); + } + + // More particles + CVector shotDir = point2 - point1; + shotDir.Normalise(); + for(i = 0; i < 15; i++){ + float f = i/15.0f; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, point1, + shotDir*CGeneral::GetRandomNumberInRange(0.3f, 1.0f)*f, + nil, CGeneral::GetRandomNumberInRange(0.5f, 1.5f)*f, black); + } + + // And some gun flashes near the gun + CVector flashPos = point1; + CVector nullDir(0.0f, 0.0f, 0.0f); + int lifeSpan = 250; + if(m_vecMoveSpeed.Magnitude() > 0.08f){ + lifeSpan = 125; + flashPos.x += 5.0f*m_vecMoveSpeed.x; + flashPos.y += 5.0f*m_vecMoveSpeed.y; + } + CParticle::AddParticle(PARTICLE_GUNFLASH, flashPos, nullDir, nil, 0.4f, black, 0, 0, 0, lifeSpan); + flashPos += 0.3f*shotDir; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashPos, nullDir, nil, 0.2f, black, 0, 0, 0, lifeSpan); + flashPos += 0.1f*shotDir; + CParticle::AddParticle(PARTICLE_GUNFLASH, flashPos, nullDir, nil, 0.15f, black, 0, 0, 0, lifeSpan); + } + + // Actually update turret node + if(m_aCarNodes[CAR_WINDSCREEN]){ + CMatrix mat; + CVector pos; + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WINDSCREEN])); + pos = mat.GetPosition(); + mat.SetRotateZ(m_fCarGunLR); + mat.Translate(pos); + mat.UpdateRW(); + } +} + +#define HYDRAULIC_UPPER_EXT (-0.12f) +#define HYDRAULIC_LOWER_EXT (0.14f) + +void +CAutomobile::HydraulicControl(void) +{ + int i; + float wheelPositions[4]; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + CColModel *normalColModel = mi->GetColModel(); + float wheelRadius = 0.5f*mi->m_wheelScale; + CPlayerInfo *playerInfo = &CWorld::Players[CWorld::PlayerInFocus]; + CColModel *specialColModel = &playerInfo->m_ColModel; + + if(GetStatus() != STATUS_PLAYER){ + // reset hydraulics for non-player cars + + if(!bUsingSpecialColModel) + return; + if(specialColModel != nil) // this is always true + for(i = 0; i < 4; i++) + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + for(i = 0; i < 4; i++){ + m_aSuspensionSpringLength[i] = pHandling->fSuspensionUpperLimit - pHandling->fSuspensionLowerLimit; + m_aSuspensionLineLength[i] = normalColModel->lines[i].p0.z - normalColModel->lines[i].p1.z; + m_aSuspensionSpringRatio[i] = (normalColModel->lines[i].p0.z - wheelPositions[i]) / m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + + if(m_hydraulicState == 0) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f); + else if(m_hydraulicState >= 100) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + + if(playerInfo->m_pVehicleEx == this) + playerInfo->m_pVehicleEx = nil; + bUsingSpecialColModel = false; + m_hydraulicState = 0; + return; + } + + // Player car + + float normalUpperLimit = pHandling->fSuspensionUpperLimit; + float normalLowerLimit = pHandling->fSuspensionLowerLimit; + float normalSpringLength = normalUpperLimit - normalLowerLimit; + float extendedUpperLimit = normalUpperLimit - 0.2f; + float extendedLowerLimit = normalLowerLimit - 0.2f; + float extendedSpringLength = extendedUpperLimit - extendedLowerLimit; + + if(!bUsingSpecialColModel){ + // Init special col model + + if(playerInfo->m_pVehicleEx && playerInfo->m_pVehicleEx == this) + playerInfo->m_pVehicleEx->bUsingSpecialColModel = false; + playerInfo->m_pVehicleEx = this; + playerInfo->m_ColModel = *normalColModel; + bUsingSpecialColModel = true; + specialColModel = &playerInfo->m_ColModel; + + if(m_fVelocityChangeForAudio > 0.1f) + m_hydraulicState = 20; + else{ + m_hydraulicState = 0; + normalUpperLimit += HYDRAULIC_UPPER_EXT; + normalSpringLength = normalUpperLimit - (normalLowerLimit+HYDRAULIC_LOWER_EXT); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + } + + // Setup suspension + float normalLineLength = normalSpringLength + wheelRadius; + CVector pos; + for(i = 0; i < 4; i++){ + wheelPositions[i] = normalColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += normalUpperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= normalLineLength; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = normalSpringLength; + m_aSuspensionLineLength[i] = normalLineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + } + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + + // Adjust col model + mi->GetWheelPosn(0, pos); + float minz = pos.z + extendedLowerLimit - wheelRadius; + if(minz < specialColModel->boundingBox.min.z) + specialColModel->boundingBox.min.z = minz; + float radius = Max(specialColModel->boundingBox.min.Magnitude(), specialColModel->boundingBox.max.Magnitude()); + if(specialColModel->boundingSphere.radius < radius) + specialColModel->boundingSphere.radius = radius; + return; + } + + if(playerInfo->m_WBState != WBSTATE_PLAYING) + return; + + bool setPrevRatio = false; + if(m_hydraulicState < 20 && m_fVelocityChangeForAudio > 0.2f){ + if(m_hydraulicState == 0){ + m_hydraulicState = 20; + for(i = 0; i < 4; i++) + m_aWheelPosition[i] -= 0.06f; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f); + setPrevRatio = true; + }else{ + m_hydraulicState++; + } + }else if(m_hydraulicState != 0){ // must always be true + if(m_hydraulicState < 21 && m_fVelocityChangeForAudio < 0.1f){ + m_hydraulicState--; + if(m_hydraulicState == 0) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + } + } + + if(CPad::GetPad(0)->HornJustDown()){ + // Switch between normal and extended + + if(m_hydraulicState < 100) + m_hydraulicState = 100; + else{ + if(m_fVelocityChangeForAudio > 0.1f) + m_hydraulicState = 20; + else + m_hydraulicState = 0; + } + + if(m_hydraulicState < 100){ + if(m_hydraulicState == 0){ + normalUpperLimit += HYDRAULIC_UPPER_EXT; + normalLowerLimit += HYDRAULIC_LOWER_EXT; + normalSpringLength = normalUpperLimit - normalLowerLimit; + } + + // Reset suspension to normal + float normalLineLength = normalSpringLength + wheelRadius; + CVector pos; + for(i = 0; i < 4; i++){ + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += normalUpperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= normalLineLength; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = normalSpringLength; + m_aSuspensionLineLength[i] = normalLineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + } + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + }else{ + // Reset suspension to extended + float extendedLineLength = extendedSpringLength + wheelRadius; + CVector pos; + for(i = 0; i < 4; i++){ + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += extendedUpperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= extendedLineLength; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = extendedSpringLength; + m_aSuspensionLineLength[i] = extendedLineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + + setPrevRatio = true; + m_aWheelPosition[i] -= 0.05f; + } + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + } + }else{ + float suspChange[4]; + float maxDelta = 0.0f; + float rear = CPad::GetPad(0)->GetCarGunUpDown()/128.0f; + float front = -rear; + float right = CPad::GetPad(0)->GetCarGunLeftRight()/128.0f; + float left = -right; + suspChange[CARWHEEL_FRONT_LEFT] = Max(front+left, 0.0f); + suspChange[CARWHEEL_REAR_LEFT] = Max(rear+left, 0.0f); + suspChange[CARWHEEL_FRONT_RIGHT] = Max(front+right, 0.0f); + suspChange[CARWHEEL_REAR_RIGHT] = Max(rear+right, 0.0f); + + if(m_hydraulicState < 100){ + // Lowered, move wheels up + + if(m_hydraulicState == 0){ + normalUpperLimit += HYDRAULIC_UPPER_EXT; + normalLowerLimit += HYDRAULIC_LOWER_EXT; + normalSpringLength = normalUpperLimit - normalLowerLimit; + } + + // Set suspension + CVector pos; + for(i = 0; i < 4; i++){ + if(suspChange[i] > 1.0f) + suspChange[i] = 1.0f; + + float oldZ = specialColModel->lines[i].p1.z; + float upperLimit = suspChange[i]*(extendedUpperLimit-normalUpperLimit) + normalUpperLimit; + float springLength = suspChange[i]*(extendedSpringLength-normalSpringLength) + normalSpringLength; + float lineLength = springLength + wheelRadius; + + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += upperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= lineLength; + if(Abs(pos.z - specialColModel->lines[i].p1.z) > Abs(maxDelta)) + maxDelta = pos.z - specialColModel->lines[i].p1.z; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = springLength; + m_aSuspensionLineLength[i] = lineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + m_aWheelPosition[i] -= (oldZ - specialColModel->lines[i].p1.z)*0.3f; + } + } + }else{ + if(m_hydraulicState < 104){ + m_hydraulicState++; + for(i = 0; i < 4; i++) + m_aWheelPosition[i] -= 0.1f; + } + + if(m_fVelocityChangeForAudio < 0.1f){ + normalUpperLimit += HYDRAULIC_UPPER_EXT; + normalLowerLimit += HYDRAULIC_LOWER_EXT; + normalSpringLength = normalUpperLimit - normalLowerLimit; + } + + // Set suspension + CVector pos; + for(i = 0; i < 4; i++){ + if(suspChange[i] > 1.0f) + suspChange[i] = 1.0f; + + float upperLimit = suspChange[i]*(normalUpperLimit-extendedUpperLimit) + extendedUpperLimit; + float springLength = suspChange[i]*(normalSpringLength-extendedSpringLength) + extendedSpringLength; + float lineLength = springLength + wheelRadius; + + wheelPositions[i] = specialColModel->lines[i].p0.z - m_aSuspensionSpringRatio[i]*m_aSuspensionLineLength[i]; + mi->GetWheelPosn(i, pos); + pos.z += upperLimit; + specialColModel->lines[i].p0 = pos; + pos.z -= lineLength; + if(Abs(pos.z - specialColModel->lines[i].p1.z) > Abs(maxDelta)) + maxDelta = pos.z - specialColModel->lines[i].p1.z; + specialColModel->lines[i].p1 = pos; + m_aSuspensionSpringLength[i] = springLength; + m_aSuspensionLineLength[i] = lineLength; + + if(m_aSuspensionSpringRatio[i] < 1.0f){ + m_aSuspensionSpringRatio[i] = (specialColModel->lines[i].p0.z - wheelPositions[i])/m_aSuspensionLineLength[i]; + if(m_aSuspensionSpringRatio[i] > 1.0f) + m_aSuspensionSpringRatio[i] = 1.0f; + } + } + } + + float limitDiff = extendedLowerLimit - normalLowerLimit; + if(limitDiff != 0.0f && Abs(maxDelta/limitDiff) > 0.01f){ + float f = (maxDelta + limitDiff)/2.0f/limitDiff; + f = Clamp(f, 0.0f, 1.0f); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_3, f); + if(f < 0.4f || f > 0.6f) + setPrevRatio = true; + if(f < 0.25f) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_2, 0.0f); + else if(f > 0.75f) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_HYDRAULIC_1, 0.0f); + } + } + + if(setPrevRatio) + for(i = 0; i < 4; i++){ + // wheel radius in relation to suspension line + float wheelRadius = 1.0f - m_aSuspensionSpringLength[i]/m_aSuspensionLineLength[i]; + m_aSuspensionSpringRatioPrev[i] = (m_aSuspensionSpringRatio[i]-wheelRadius)/(1.0f-wheelRadius); + } +} + +void +CAutomobile::ProcessBuoyancy(void) +{ + int i; + CVector impulse, point; + + if(mod_Buoyancy.ProcessBuoyancy(this, m_fBuoyancy, &point, &impulse)){ + bTouchingWater = true; + ApplyMoveForce(impulse); + ApplyTurnForce(impulse, point); + + CVector initialSpeed = m_vecMoveSpeed; + float timeStep = Max(CTimer::GetTimeStep(), 0.01f); + float impulseRatio = impulse.z / (GRAVITY * m_fMass * timeStep); + float waterResistance = Pow(1.0f - 0.05f*impulseRatio, CTimer::GetTimeStep()); + m_vecMoveSpeed *= waterResistance; + m_vecTurnSpeed *= waterResistance; + + if(impulseRatio > 0.5f){ + bIsInWater = true; + if(m_vecMoveSpeed.z < -0.1f) + m_vecMoveSpeed.z = -0.1f; + + if(pDriver){ + pDriver->bIsInWater = true; + if(pDriver->IsPlayer() || !bWaterTight) + pDriver->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + } + for(i = 0; i < m_nNumMaxPassengers; i++) + if(pPassengers[i]){ + pPassengers[i]->bIsInWater = true; + if(pPassengers[i]->IsPlayer() || !bWaterTight) + pPassengers[i]->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + } + }else + bIsInWater = false; + + static uint32 nGenerateRaindrops = 0; + static uint32 nGenerateWaterCircles = 0; + + if(initialSpeed.z < -0.3f && impulse.z > 0.3f){ +#if defined(PC_PARTICLE) || defined (PS2_ALTERNATIVE_CARSPLASH) + RwRGBA color; + color.red = (0.5f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed())*0.45f*255; + color.green = (0.5f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen())*0.45f*255; + color.blue = (0.5f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue())*0.45f*255; + color.alpha = CGeneral::GetRandomNumberInRange(0, 32) + 128; + CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, GetPosition(), + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.15f, 0.3f)), + 0.0f, 75, color, true); +#else + CVector pos = (initialSpeed * 2.0f) + (GetPosition() + point); + + for ( int32 i = 0; i < 360; i += 4 ) + { + float fSin = Sin(float(i)); + float fCos = Cos(float(i)); + + CVector dir(fSin*0.01f, fCos*0.01f, CGeneral::GetRandomNumberInRange(0.25f, 0.45f)); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, + pos + CVector(fSin*4.5f, fCos*4.5f, 0.0f), + dir, NULL, 0.0f, CRGBA(225, 225, 255, 180)); + + for ( int32 j = 0; j < 3; j++ ) + { + float fMul = 1.5f * float(j + 1); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, + pos + CVector(fSin * fMul, fCos * fMul, 0.0f), + dir, NULL, 0.0f, CRGBA(225, 225, 255, 180)); + } + } +#endif + + nGenerateRaindrops = CTimer::GetTimeInMilliseconds() + 300; + nGenerateWaterCircles = CTimer::GetTimeInMilliseconds() + 60; + if(m_vecMoveSpeed.z < -0.2f) + m_vecMoveSpeed.z = -0.2f; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WATER_FALL, 0.0f); + } + + if(nGenerateWaterCircles > 0 && nGenerateWaterCircles <= CTimer::GetTimeInMilliseconds()){ + CVector pos = GetPosition(); + float waterLevel = 0.0f; + if(CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &waterLevel, false)) + pos.z = waterLevel; + static RwRGBA black; + if(pos.z != 0.0f){ + nGenerateWaterCircles = 0; + pos.z += 1.0f; + for(i = 0; i < 4; i++){ + CVector p = pos; + p.x += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f); + p.y += CGeneral::GetRandomNumberInRange(-2.5f, 2.5f); + CParticle::AddParticle(PARTICLE_RAIN_SPLASH_BIGGROW, + p, CVector(0.0f, 0.0f, 0.0f), + nil, 0.0f, black, 0, 0, 0, 0); + } + } + } + + if(nGenerateRaindrops > 0 && nGenerateRaindrops <= CTimer::GetTimeInMilliseconds()){ + CVector pos = GetPosition(); + float waterLevel = 0.0f; + if(CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &waterLevel, false)) + pos.z = waterLevel; + static RwRGBA black; + if(pos.z >= 0.0f){ + nGenerateRaindrops = 0; + pos.z += 0.5f; + CParticleObject::AddObject(POBJECT_SPLASHES_AROUND, + pos, CVector(0.0f, 0.0f, 0.0f), 6.5f, 2500, black, true); + } + } + }else{ + bIsInWater = false; + bTouchingWater = false; + + static RwRGBA splashCol = {155, 155, 185, 196}; + static RwRGBA smokeCol = {255, 255, 255, 255}; + + for(i = 0; i < 4; i++){ + if(m_aSuspensionSpringRatio[i] < 1.0f && m_aWheelColPoints[i].surfaceB == SURFACE_WATER){ + CVector pos = m_aWheelColPoints[i].point + 0.3f*GetUp() - GetPosition(); + CVector vSpeed = GetSpeed(pos); + vSpeed.z = 0.0f; +#ifdef GTA_PS2_STUFF + // ps2 puddle physics + CVector moveForce = CTimer::GetTimeStep() * (m_fMass * (vSpeed * -0.003f)); + ApplyMoveForce(moveForce.x, moveForce.y, moveForce.z); +#endif + float fSpeed = vSpeed.MagnitudeSqr(); +#ifdef PC_PARTICLE + if(fSpeed > sq(0.05f)){ + fSpeed = Sqrt(fSpeed); + + float size = Min((fSpeed < 0.15f ? 0.25f : 0.75f)*fSpeed, 0.6f); + CVector right = 0.2f*fSpeed*GetRight() + 0.2f*vSpeed; + + CParticle::AddParticle(PARTICLE_PED_SPLASH, + pos + GetPosition(), -0.5f*right, + nil, size, splashCol, + CGeneral::GetRandomNumberInRange(0.0f, 10.0f), + CGeneral::GetRandomNumberInRange(0.0f, 90.0f), 1, 0); + + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + pos + GetPosition(), -0.6f*right, + nil, size, smokeCol, 0, 0, 0, 0); + + if((CTimer::GetFrameCounter() & 0xF) == 0) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, 2000.0f*fSpeed); + } +#else + if ( ( (CTimer::GetFrameCounter() + i) & 3 ) == 0 ) + { + if(fSpeed > sq(0.05f)) + { + fSpeed = Sqrt(fSpeed); + CRGBA color(155, 185, 155, 255); + float boxY = GetColModel()->boundingBox.max.y; + CVector right = 0.5f * GetRight(); + + if ( i == 2 ) + { + CParticle::AddParticle(PARTICLE_PED_SPLASH, + GetPosition() + (boxY * GetForward()) + right, + 0.75f*m_vecMoveSpeed, NULL, 0.0f, color); + + } + else if ( i == 0 ) + { + CParticle::AddParticle(PARTICLE_PED_SPLASH, + GetPosition() + (boxY * GetForward()) - right, + 0.75f*m_vecMoveSpeed, NULL, 0.0f, color); + } + + if((CTimer::GetFrameCounter() & 0xF) == 0) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, 2000.0f*fSpeed); + } + } +#endif + } + } + } +} + +void +CAutomobile::DoDriveByShootings(void) +{ + CAnimBlendAssociation *anim; + CWeapon *weapon = pDriver->GetWeapon(); + if(weapon->m_eWeaponType != WEAPONTYPE_UZI) + return; + + weapon->Update(pDriver->m_audioEntityId); + + bool lookingLeft = false; + bool lookingRight = false; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN){ + if(CPad::GetPad(0)->GetLookLeft()) + lookingLeft = true; + if(CPad::GetPad(0)->GetLookRight()) + lookingRight = true; + }else{ + if(TheCamera.Cams[TheCamera.ActiveCam].LookingLeft) + lookingLeft = true; + if(TheCamera.Cams[TheCamera.ActiveCam].LookingRight) + lookingRight = true; + } + + if(lookingLeft || lookingRight){ + if(lookingLeft){ + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if(anim == nil || anim->blendDelta < 0.0f) + CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVEBY_LEFT); + else + anim->SetRun(); + }else if(pDriver->m_pMyVehicle->pPassengers[0] == nil || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON){ + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if(anim == nil || anim->blendDelta < 0.0f) + CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVEBY_RIGHT); + else + anim->SetRun(); + } + + if(CPad::GetPad(0)->GetCarGunFired() && CTimer::GetTimeInMilliseconds() > weapon->m_nTimer){ + weapon->FireFromCar(this, lookingLeft); + weapon->m_nTimer = CTimer::GetTimeInMilliseconds() + 70; + } + }else{ + weapon->Reload(); + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT); + if(anim) + anim->blendDelta = -1000.0f; + } + + // TODO: what is this? + if(!lookingLeft && m_weaponDoorTimerLeft > 0.0f){ + m_weaponDoorTimerLeft = Max(m_weaponDoorTimerLeft - CTimer::GetTimeStep()*0.1f, 0.0f); + ProcessOpenDoor(CAR_DOOR_LF, ANIM_STD_NUM, m_weaponDoorTimerLeft); + } + if(!lookingRight && m_weaponDoorTimerRight > 0.0f){ + m_weaponDoorTimerRight = Max(m_weaponDoorTimerRight - CTimer::GetTimeStep()*0.1f, 0.0f); + ProcessOpenDoor(CAR_DOOR_RF, ANIM_STD_NUM, m_weaponDoorTimerRight); + } +} + +int32 +CAutomobile::RcbanditCheckHitWheels(void) +{ + int x, xmin, xmax; + int y, ymin, ymax; + + xmin = CWorld::GetSectorIndexX(GetPosition().x - 2.0f); + if(xmin < 0) xmin = 0; + xmax = CWorld::GetSectorIndexX(GetPosition().x + 2.0f); + if(xmax > NUMSECTORS_X-1) xmax = NUMSECTORS_X-1; + ymin = CWorld::GetSectorIndexY(GetPosition().y - 2.0f); + if(ymin < 0) ymin = 0; + ymax = CWorld::GetSectorIndexY(GetPosition().y + 2.0f); + if(ymax > NUMSECTORS_Y-1) ymax = NUMSECTORS_X-1; + + CWorld::AdvanceCurrentScanCode(); + + for(y = ymin; y <= ymax; y++) + for(x = xmin; x <= xmax; x++){ + CSector *s = CWorld::GetSector(x, y); + if(RcbanditCheck1CarWheels(s->m_lists[ENTITYLIST_VEHICLES]) || + RcbanditCheck1CarWheels(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP])) + return 1; + } + return 0; +} + +int32 +CAutomobile::RcbanditCheck1CarWheels(CPtrList &list) +{ + static CMatrix matW2B; + int i; + CPtrNode *node; + CAutomobile *car; + CColModel *colModel = GetColModel(); + CVehicleModelInfo *mi; + + for(node = list.first; node; node = node->next){ + car = (CAutomobile*)node->item; + if(this != car && car->IsCar() && car->m_scanCode != CWorld::GetCurrentScanCode()){ + car->m_scanCode = CWorld::GetCurrentScanCode(); + + if(Abs(this->GetPosition().x - car->GetPosition().x) < 10.0f && + Abs(this->GetPosition().y - car->GetPosition().y) < 10.0f){ + mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(car->GetModelIndex()); + + for(i = 0; i < 4; i++){ + if(car->m_aSuspensionSpringRatioPrev[i] < 1.0f || car->GetStatus() == STATUS_SIMPLE){ + CVector wheelPos; + CColSphere sph; + mi->GetWheelPosn(i, wheelPos); + matW2B = Invert(GetMatrix()); + sph.center = matW2B * (car->GetMatrix() * wheelPos); + sph.radius = mi->m_wheelScale*0.25f; + if(CCollision::TestSphereBox(sph, colModel->boundingBox)) + return 1; + } + } + } + } + } + return 0; +} + +void +CAutomobile::PlaceOnRoadProperly(void) +{ + CColPoint point; + CEntity *entity; + CColModel *colModel = GetColModel(); + float lenFwd, lenBack; + float frontZ, rearZ; + + lenFwd = colModel->boundingBox.max.y; + lenBack = -colModel->boundingBox.min.y; + + CVector front(GetPosition().x + GetForward().x*lenFwd, + GetPosition().y + GetForward().y*lenFwd, + GetPosition().z + 5.0f); + if(CWorld::ProcessVerticalLine(front, GetPosition().z - 5.0f, point, entity, + true, false, false, false, false, false, nil)){ + frontZ = point.point.z; + m_pCurGroundEntity = entity; + }else{ + frontZ = m_fMapObjectHeightAhead; + } + + CVector rear(GetPosition().x - GetForward().x*lenBack, + GetPosition().y - GetForward().y*lenBack, + GetPosition().z + 5.0f); + if(CWorld::ProcessVerticalLine(rear, GetPosition().z - 5.0f, point, entity, + true, false, false, false, false, false, nil)){ + rearZ = point.point.z; + m_pCurGroundEntity = entity; + }else{ + rearZ = m_fMapObjectHeightBehind; + } + + float len = lenFwd + lenBack; + float angle = Atan((frontZ - rearZ)/len); + float c = Cos(angle); + float s = Sin(angle); + + GetMatrix().GetRight() = CVector((front.y - rear.y) / len, -(front.x - rear.x) / len, 0.0f); + GetMatrix().GetForward() = CVector(-c * GetRight().y, c * GetRight().x, s); + GetMatrix().GetUp() = CrossProduct(GetRight(), GetForward()); + GetMatrix().GetPosition() = CVector((front.x + rear.x) / 2.0f, (front.y + rear.y) / 2.0f, (frontZ + rearZ) / 2.0f + GetHeightAboveRoad()); +} + +void +CAutomobile::VehicleDamage(float impulse, uint16 damagedPiece) +{ + int i; + float damageMultiplier = 0.2f; + bool doubleMoney = false; + + if(impulse == 0.0f){ + impulse = m_fDamageImpulse; + damagedPiece = m_nDamagePieceType; + damageMultiplier = 1.0f; + } + + CVector pos(0.0f, 0.0f, 0.0f); + + if(!bCanBeDamaged) + return; + + // damage flipped over car + if(GetUp().z < 0.0f && this != FindPlayerVehicle()){ + if(bNotDamagedUpsideDown || GetStatus() == STATUS_PLAYER_REMOTE || bIsInWater) + return; + m_fHealth -= 4.0f*CTimer::GetTimeStep(); + } + + if(impulse > 25.0f && GetStatus() != STATUS_WRECKED){ + if(bIsLawEnforcer && + FindPlayerVehicle() && FindPlayerVehicle() == m_pDamageEntity && + GetStatus() != STATUS_ABANDONED && + FindPlayerVehicle()->m_vecMoveSpeed.Magnitude() >= m_vecMoveSpeed.Magnitude() && + FindPlayerVehicle()->m_vecMoveSpeed.Magnitude() > 0.1f) + FindPlayerPed()->SetWantedLevelNoDrop(1); + + if(GetStatus() == STATUS_PLAYER && impulse > 50.0f){ + uint8 freq = Min(0.4f*impulse*2000.0f/m_fMass + 100.0f, 250.0f); + CPad::GetPad(0)->StartShake(40000/freq, freq); + } + + if(bOnlyDamagedByPlayer){ + if(m_pDamageEntity != FindPlayerPed() && + m_pDamageEntity != FindPlayerVehicle()) + return; + } + + if(bCollisionProof) + return; + + if(m_pDamageEntity){ + if(m_pDamageEntity->IsBuilding() && + DotProduct(m_vecDamageNormal, GetUp()) > 0.6f) + return; + } + + int oldLightStatus[4]; + for(i = 0; i < 4; i++) + oldLightStatus[i] = Damage.GetLightStatus((eLights)i); + + if(GetUp().z > 0.0f || m_vecMoveSpeed.MagnitudeSqr() > 0.1f){ + float impulseMult = bMoreResistantToDamage ? 0.5f : 4.0f; + + switch(damagedPiece){ + case CAR_PIECE_BUMP_FRONT: + GetComponentWorldPosition(CAR_BUMP_FRONT, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_BUMPER_FRONT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT); + doubleMoney = true; + } + if(m_aCarNodes[CAR_BONNET] && Damage.GetPanelStatus(VEHBUMPER_FRONT) == PANEL_STATUS_MISSING){ + case CAR_PIECE_BONNET: + GetComponentWorldPosition(CAR_BONNET, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(GetModelIndex() != MI_DODO) + if(Damage.ApplyDamage(COMPONENT_DOOR_BONNET, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_BONNET, DOOR_BONNET); + doubleMoney = true; + } + } + break; + + case CAR_PIECE_BUMP_REAR: + GetComponentWorldPosition(CAR_BUMP_REAR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_BUMPER_REAR, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR); + doubleMoney = true; + } + if(m_aCarNodes[CAR_BOOT] && Damage.GetPanelStatus(VEHBUMPER_REAR) == PANEL_STATUS_MISSING){ + case CAR_PIECE_BOOT: + GetComponentWorldPosition(CAR_BOOT, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_DOOR_BOOT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_BOOT, DOOR_BOOT); + doubleMoney = true; + } + } + break; + + case CAR_PIECE_DOOR_LF: + GetComponentWorldPosition(CAR_DOOR_LF, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if((m_nDoorLock == CARLOCK_NOT_USED || m_nDoorLock == CARLOCK_UNLOCKED) && + Damage.ApplyDamage(COMPONENT_DOOR_FRONT_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT); + doubleMoney = true; + } + break; + case CAR_PIECE_DOOR_RF: + GetComponentWorldPosition(CAR_DOOR_RF, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if((m_nDoorLock == CARLOCK_NOT_USED || m_nDoorLock == CARLOCK_UNLOCKED) && + Damage.ApplyDamage(COMPONENT_DOOR_FRONT_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + doubleMoney = true; + } + break; + case CAR_PIECE_DOOR_LR: + GetComponentWorldPosition(CAR_DOOR_LR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if((m_nDoorLock == CARLOCK_NOT_USED || m_nDoorLock == CARLOCK_UNLOCKED) && + Damage.ApplyDamage(COMPONENT_DOOR_REAR_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT); + doubleMoney = true; + } + break; + case CAR_PIECE_DOOR_RR: + GetComponentWorldPosition(CAR_DOOR_RR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if((m_nDoorLock == CARLOCK_NOT_USED || m_nDoorLock == CARLOCK_UNLOCKED) && + Damage.ApplyDamage(COMPONENT_DOOR_REAR_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT); + doubleMoney = true; + } + break; + + case CAR_PIECE_WING_LF: + GetComponentWorldPosition(CAR_WING_LF, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_PANEL_FRONT_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT); + doubleMoney = true; + } + break; + case CAR_PIECE_WING_RF: + GetComponentWorldPosition(CAR_WING_RF, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_PANEL_FRONT_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT); + doubleMoney = true; + } + break; + case CAR_PIECE_WING_LR: + GetComponentWorldPosition(CAR_WING_LR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_PANEL_REAR_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT); + doubleMoney = true; + } + break; + case CAR_PIECE_WING_RR: + GetComponentWorldPosition(CAR_WING_RR, pos); + dmgDrawCarCollidingParticles(pos, impulse); + if(Damage.ApplyDamage(COMPONENT_PANEL_REAR_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT); + doubleMoney = true; + } + break; + + case CAR_PIECE_WHEEL_LF: + case CAR_PIECE_WHEEL_LR: + case CAR_PIECE_WHEEL_RF: + case CAR_PIECE_WHEEL_RR: + break; + + case CAR_PIECE_WINDSCREEN: + if(Damage.ApplyDamage(COMPONENT_PANEL_WINDSCREEN, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)){ + uint8 oldStatus = Damage.GetPanelStatus(VEHPANEL_WINDSCREEN); + SetPanelDamage(CAR_WINDSCREEN, VEHPANEL_WINDSCREEN); + if(oldStatus != Damage.GetPanelStatus(VEHPANEL_WINDSCREEN)){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_WINDSHIELD_CRACK, 0.0f); + doubleMoney = true; + } + } + break; + } + + if(m_pDamageEntity && m_pDamageEntity == FindPlayerVehicle() && impulse > 10.0f){ + int money = (doubleMoney ? 2 : 1) * impulse*pHandling->nMonetaryValue/1000000.0f; + money = Min(money, 40); + if(money > 2){ + sprintf(gString, "$%d", money); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += money; + } + } + } + + float damage = (impulse-25.0f)*pHandling->fCollisionDamageMultiplier*0.6f*damageMultiplier; + + if(GetModelIndex() == MI_SECURICA && m_pDamageEntity && m_pDamageEntity->GetStatus() == STATUS_PLAYER) + damage *= 7.0f; + + if(damage > 0.0f){ + int oldHealth = m_fHealth; + if(this == FindPlayerVehicle()){ + m_fHealth -= bTakeLessDamage ? damage/6.0f : damage/2.0f; + }else{ + if(damage > 35.0f && pDriver) + pDriver->Say(SOUND_PED_ANNOYED_DRIVER); + m_fHealth -= bTakeLessDamage ? damage/12.0f : damage/4.0f; + } + if(m_fHealth <= 0.0f && oldHealth > 0) + m_fHealth = 1.0f; + } + + // play sound if a light broke + for(i = 0; i < 4; i++) + if(oldLightStatus[i] != 1 && Damage.GetLightStatus((eLights)i) == 1){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_LIGHT_BREAK, i); // BUG? i? + break; + } + } + + if(m_fHealth < 250.0f){ + // Car is on fire + if(Damage.GetEngineStatus() < ENGINE_STATUS_ON_FIRE){ + // Set engine on fire and remember who did this + Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE); + m_fFireBlowUpTimer = 0.0f; + m_pSetOnFireEntity = m_pDamageEntity; + if(m_pSetOnFireEntity) + m_pSetOnFireEntity->RegisterReference(&m_pSetOnFireEntity); + } + }else{ + if(GetModelIndex() == MI_BFINJECT){ + if(m_fHealth < 400.0f) + Damage.SetEngineStatus(200); + else if(m_fHealth < 600.0f) + Damage.SetEngineStatus(100); + } + } +} + +void +CAutomobile::dmgDrawCarCollidingParticles(const CVector &pos, float amount) +{ + int i, n; + + if(!GetIsOnScreen()) + return; + + // FindPlayerSpeed() unused + + n = (int)amount/20; + + for(i = 0; i < ((n+4)&0x1F); i++) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, pos, + CVector(CGeneral::GetRandomNumberInRange(-0.1f, 0.1f), + CGeneral::GetRandomNumberInRange(-0.1f, 0.1f), + 0.006f)); + + for(i = 0; i < n+2; i++) + CParticle::AddParticle(PARTICLE_CARCOLLISION_DUST, + CVector(CGeneral::GetRandomNumberInRange(-1.2f, 1.2f) + pos.x, + CGeneral::GetRandomNumberInRange(-1.2f, 1.2f) + pos.y, + pos.z), + CVector(0.0f, 0.0f, 0.0f), nil, 0.5f); + + n = (int)amount/50 + 1; + for(i = 0; i < n; i++) + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, pos, + CVector(CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), + CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), + CGeneral::GetRandomNumberInRange(0.1f, 0.25f)), + nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.08f), + CVehicleModelInfo::ms_vehicleColourTable[m_currentColour1], + CGeneral::GetRandomNumberInRange(-40, 40), + 0, + CGeneral::GetRandomNumberInRange(0, 4)); +} + +void +CAutomobile::AddDamagedVehicleParticles(void) +{ + if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson()) + return; + + uint8 engineStatus = Damage.GetEngineStatus(); + if(engineStatus < ENGINE_STATUS_STEAM1) + return; + + float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward()) * 180.0f; + CVector direction = 0.5f*m_vecMoveSpeed; + CVector damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_positions[CAR_POS_HEADLIGHTS]; + + switch(Damage.GetDoorStatus(DOOR_BONNET)){ + case DOOR_STATUS_OK: + case DOOR_STATUS_SMASHED: + // Bonnet is still there, smoke comes out at the edge + damagePos += vecDAMAGE_ENGINE_POS_SMALL; + break; + case DOOR_STATUS_SWINGING: + case DOOR_STATUS_MISSING: + // Bonnet is gone, smoke comes out at the engine + damagePos += vecDAMAGE_ENGINE_POS_BIG; + break; + } + + if(GetModelIndex() == MI_BFINJECT) + damagePos = CVector(0.3f, -1.5f, -0.1f); + + damagePos = GetMatrix()*damagePos; + damagePos.z += 0.15f; + + if(engineStatus < ENGINE_STATUS_STEAM2){ + if(fwdSpeed < 90.0f){ + direction.z += 0.05f; + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction, nil, 0.1f); + } + }else if(engineStatus < ENGINE_STATUS_SMOKE){ + if(fwdSpeed < 90.0f) + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction, nil, 0.0f); + }else if(engineStatus < ENGINE_STATUS_ON_FIRE){ + if(fwdSpeed < 90.0f){ + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction, nil, 0.0f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, 0.3f*direction, nil, 0.0f); + } + }else if(m_fHealth > 250.0f){ + if(fwdSpeed < 90.0f) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, 0.2f*direction, nil, 0.0f); + } +} + +int32 +CAutomobile::AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed) +{ + int i; + CVector dir; + static RwRGBA grassCol = { 8, 24, 8, 255 }; + static RwRGBA gravelCol = { 64, 64, 64, 255 }; + static RwRGBA mudCol = { 64, 32, 16, 255 }; + static RwRGBA waterCol = { 48, 48, 64, 0 }; + + if(!belowEffectSpeed) + return 0; + + switch(colpoint->surfaceB){ + case SURFACE_GRASS: + dir.x = -0.05f*m_vecMoveSpeed.x; + dir.y = -0.05f*m_vecMoveSpeed.y; + for(i = 0; i < 4; i++){ + dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.1f), grassCol); + } + return 0; + case SURFACE_GRAVEL: + dir.x = -0.05f*m_vecMoveSpeed.x; + dir.y = -0.05f*m_vecMoveSpeed.y; + for(i = 0; i < 4; i++){ + dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.06f), gravelCol); + } + return 1; + case SURFACE_MUD_DRY: + dir.x = -0.05f*m_vecMoveSpeed.x; + dir.y = -0.05f*m_vecMoveSpeed.y; + for(i = 0; i < 4; i++){ + dir.z = CGeneral::GetRandomNumberInRange(0.03f, 0.06f); + CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil, + CGeneral::GetRandomNumberInRange(0.02f, 0.06f), mudCol); + } + return 0; + default: + if ( CWeather::WetRoads > 0.01f +#ifdef PC_PARTICLE + && CTimer::GetFrameCounter() & 1 +#endif + ) + { + CParticle::AddParticle( +#if defined(FIX_BUGS) && !defined(PC_PARTICLE) // looks wrong on PC particles + PARTICLE_WHEEL_WATER, +#else + PARTICLE_WATERSPRAY, +#endif + colpoint->point + CVector(0.0f, 0.0f, 0.25f+0.25f), +#ifdef PC_PARTICLE + CVector(0.0f, 0.0f, 1.0f), +#else + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.005f, 0.04f)), +#endif + nil, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), waterCol); + return 0; + } + + return 1; + } +} + +void +CAutomobile::GetComponentWorldPosition(int32 component, CVector &pos) +{ + if(m_aCarNodes[component] == nil){ + printf("CarNode missing: %d %d\n", GetModelIndex(), component); + return; + } + RwMatrix *ltm = RwFrameGetLTM(m_aCarNodes[component]); + pos = *RwMatrixGetPos(ltm); +} + +bool +CAutomobile::IsComponentPresent(int32 comp) +{ + return m_aCarNodes[comp] != nil; +} + +void +CAutomobile::SetComponentRotation(int32 component, CVector rotation) +{ + CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component])); + CVector pos = mat.GetPosition(); + // BUG: all these set the whole matrix + mat.SetRotateX(DEGTORAD(rotation.x)); + mat.SetRotateY(DEGTORAD(rotation.y)); + mat.SetRotateZ(DEGTORAD(rotation.z)); + mat.Translate(pos); + mat.UpdateRW(); +} + +void +CAutomobile::OpenDoor(int32 component, eDoors door, float openRatio) +{ + CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component])); + CVector pos = mat.GetPosition(); + float axes[3] = { 0.0f, 0.0f, 0.0f }; + float wasClosed = false; + + if(Doors[door].IsClosed()){ + // enable angle cull for closed doors + RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::ClearAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL); + wasClosed = true; + } + + Doors[door].Open(openRatio); + + if(wasClosed && Doors[door].RetAngleWhenClosed() != Doors[door].m_fAngle){ + // door opened + HideAllComps(); + // turn off angle cull for swinging door + RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::SetAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_DOOR_OPEN_BONNET + door, 0.0f); + } + + if(!wasClosed && openRatio == 0.0f){ + // door closed + if(Damage.GetDoorStatus(door) == DOOR_STATUS_SWINGING) + Damage.SetDoorStatus(door, DOOR_STATUS_OK); // huh? + ShowAllComps(); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_DOOR_CLOSE_BONNET + door, 0.0f); + } + + axes[Doors[door].m_nAxis] = Doors[door].m_fAngle; + mat.SetRotate(axes[0], axes[1], axes[2]); + mat.Translate(pos); + mat.UpdateRW(); +} + +inline void ProcessDoorOpenAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float end) +{ + if(time > start && time < end){ + float ratio = (time - start)/(end - start); + if(car->Doors[door].GetAngleOpenRatio() < ratio) + car->OpenDoor(component, door, ratio); + }else if(time > end){ + car->OpenDoor(component, door, 1.0f); + } +} + +inline void ProcessDoorCloseAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float end) +{ + if(time > start && time < end){ + float ratio = 1.0f - (time - start)/(end - start); + if(car->Doors[door].GetAngleOpenRatio() > ratio) + car->OpenDoor(component, door, ratio); + }else if(time > end){ + car->OpenDoor(component, door, 0.0f); + } +} + +inline void ProcessDoorOpenCloseAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float mid, float end) +{ + if(time > start && time < mid){ + // open + float ratio = (time - start)/(mid - start); + if(car->Doors[door].GetAngleOpenRatio() < ratio) + car->OpenDoor(component, door, ratio); + }else if(time > mid && time < end){ + // close + float ratio = 1.0f - (time - mid)/(end - mid); + if(car->Doors[door].GetAngleOpenRatio() > ratio) + car->OpenDoor(component, door, ratio); + }else if(time > end){ + car->OpenDoor(component, door, 0.0f); + } +} +void +CAutomobile::ProcessOpenDoor(uint32 component, uint32 anim, float time) +{ + eDoors door; + + switch(component){ + case CAR_DOOR_RF: door = DOOR_FRONT_RIGHT; break; + case CAR_DOOR_RR: door = DOOR_REAR_RIGHT; break; + case CAR_DOOR_LF: door = DOOR_FRONT_LEFT; break; + case CAR_DOOR_LR: door = DOOR_REAR_LEFT; break; + default: assert(0); + } + + if(IsDoorMissing(door)) + return; + + switch(anim){ + case ANIM_STD_QUICKJACK: + case ANIM_STD_CAR_OPEN_DOOR_LHS: + case ANIM_STD_CAR_OPEN_DOOR_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.66f, 0.8f); + break; + case ANIM_STD_CAR_CLOSE_DOOR_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_LO_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_RHS: + case ANIM_STD_CAR_CLOSE_DOOR_LO_RHS: + ProcessDoorCloseAnimation(this, component, door, time, 0.2f, 0.63f); + break; + case ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LHS: + case ANIM_STD_CAR_CLOSE_DOOR_ROLLING_LO_LHS: + ProcessDoorOpenCloseAnimation(this, component, door, time, 0.1f, 0.6f, 0.95f); + break; + case ANIM_STD_GETOUT_LHS: + case ANIM_STD_GETOUT_LO_LHS: + case ANIM_STD_GETOUT_RHS: + case ANIM_STD_GETOUT_LO_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.06f, 0.43f); + break; + case ANIM_STD_CAR_CLOSE_LHS: + case ANIM_STD_CAR_CLOSE_RHS: + ProcessDoorCloseAnimation(this, component, door, time, 0.1f, 0.23f); + break; + case ANIM_STD_CAR_PULL_OUT_PED_RHS: + case ANIM_STD_CAR_PULL_OUT_PED_LO_RHS: + OpenDoor(component, door, 1.0f); + break; + case ANIM_STD_COACH_OPEN_LHS: + case ANIM_STD_COACH_OPEN_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.66f, 0.8f); + break; + case ANIM_STD_COACH_GET_OUT_LHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.0f, 0.3f); + break; + case ANIM_STD_VAN_OPEN_DOOR_REAR_LHS: + case ANIM_STD_VAN_OPEN_DOOR_REAR_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.37f, 0.55f); + break; + case ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS: + case ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS: + ProcessDoorCloseAnimation(this, component, door, time, 0.5f, 0.8f); + break; + case ANIM_STD_VAN_GET_OUT_REAR_LHS: + case ANIM_STD_VAN_GET_OUT_REAR_RHS: + ProcessDoorOpenAnimation(this, component, door, time, 0.5f, 0.6f); + break; + case ANIM_STD_NUM: + OpenDoor(component, door, time); + break; + } +} + +bool +CAutomobile::IsDoorReady(eDoors door) +{ + if(Doors[door].IsClosed() || IsDoorMissing(door)) + return true; + int doorflag = 0; + switch(door){ + case DOOR_FRONT_LEFT: doorflag = CAR_DOOR_FLAG_LF; break; + case DOOR_FRONT_RIGHT: doorflag = CAR_DOOR_FLAG_RF; break; + case DOOR_REAR_LEFT: doorflag = CAR_DOOR_FLAG_LR; break; + case DOOR_REAR_RIGHT: doorflag = CAR_DOOR_FLAG_RR; break; + default: break; + } + return (doorflag & m_nGettingInFlags) == 0; +} + +bool +CAutomobile::IsDoorFullyOpen(eDoors door) +{ + return Doors[door].IsFullyOpen() || IsDoorMissing(door); +} + +bool +CAutomobile::IsDoorClosed(eDoors door) +{ + return !!Doors[door].IsClosed(); +} + +bool +CAutomobile::IsDoorMissing(eDoors door) +{ + return Damage.GetDoorStatus(door) == DOOR_STATUS_MISSING; +} + +void +CAutomobile::RemoveRefsToVehicle(CEntity *ent) +{ + int i; + for(i = 0; i < 4; i++) + if(m_aGroundPhysical[i] == ent) + m_aGroundPhysical[i] = nil; +} + +void +CAutomobile::BlowUpCar(CEntity *culprit) +{ + int i; + RpAtomic *atomic; + + if(!bCanBeDamaged) + return; + + // explosion pushes vehicle up + m_vecMoveSpeed.z += 0.13f; + SetStatus(STATUS_WRECKED); + bRenderScorched = true; + m_nTimeOfDeath = CTimer::GetTimeInMilliseconds(); + Damage.FuckCarCompletely(); + + if(GetModelIndex() != MI_RCBANDIT){ + SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT); + SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR); + SetDoorDamage(CAR_BONNET, DOOR_BONNET); + SetDoorDamage(CAR_BOOT, DOOR_BOOT); + SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT); + SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT); + SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT); + SpawnFlyingComponent(CAR_WHEEL_LF, COMPGROUP_WHEEL); + atomic = nil; + RwFrameForAllObjects(m_aCarNodes[CAR_WHEEL_LF], GetCurrentAtomicObjectCB, &atomic); + if(atomic) + RpAtomicSetFlags(atomic, 0); + } + + m_fHealth = 0.0f; + m_nBombTimer = 0; + m_bombType = CARBOMB_NONE; + + TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z); + + // kill driver and passengers + if(pDriver){ + CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION); + if(pDriver->GetPedState() == PED_DRIVING){ + pDriver->SetDead(); + if(!pDriver->IsPlayer()) + pDriver->FlagToDestroyWhenNextProcessed(); + }else + pDriver->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + for(i = 0; i < m_nNumMaxPassengers; i++){ + if(pPassengers[i]){ + CDarkel::RegisterKillByPlayer(pPassengers[i], WEAPONTYPE_EXPLOSION); + if(pPassengers[i]->GetPedState() == PED_DRIVING){ + pPassengers[i]->SetDead(); + if(!pPassengers[i]->IsPlayer()) + pPassengers[i]->FlagToDestroyWhenNextProcessed(); + }else + pPassengers[i]->SetDie(ANIM_STD_KO_FRONT, 4.0f, 0.0f); + } + } + + bEngineOn = false; + bLightsOn = false; + m_bSirenOrAlarm = false; + bTaxiLight = false; + if(bIsAmbulanceOnDuty){ + bIsAmbulanceOnDuty = false; + CCarCtrl::NumAmbulancesOnDuty--; + } + if(bIsFireTruckOnDuty){ + bIsFireTruckOnDuty = false; + CCarCtrl::NumFiretrucksOnDuty--; + } + ChangeLawEnforcerState(false); + + gFireManager.StartFire(this, culprit, 0.8f, true); + CDarkel::RegisterCarBlownUpByPlayer(this); + if(GetModelIndex() == MI_RCBANDIT) + CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR_QUICK, GetPosition(), 0); + else + CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR, GetPosition(), 0); +} + +bool +CAutomobile::SetUpWheelColModel(CColModel *colModel) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + CColModel *vehColModel = mi->GetColModel(); + + colModel->boundingSphere = vehColModel->boundingSphere; + colModel->boundingBox = vehColModel->boundingBox; + + CMatrix mat; + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + colModel->spheres[0].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LF); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB])); + colModel->spheres[1].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + colModel->spheres[2].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RF); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB])); + colModel->spheres[3].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RR); + + if(m_aCarNodes[CAR_WHEEL_LM] != nil && m_aCarNodes[CAR_WHEEL_RM] != nil){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LM])); + colModel->spheres[4].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RM])); + colModel->spheres[5].Set(mi->m_wheelScale, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RR); + colModel->numSpheres = 6; + }else + colModel->numSpheres = 4; + + return true; +} + +float fBurstForceMult = 0.03f; + +// this isn't used in III yet +void +CAutomobile::BurstTyre(uint8 wheel) +{ + switch(wheel){ + case CAR_PIECE_WHEEL_LF: wheel = CARWHEEL_FRONT_LEFT; break; + case CAR_PIECE_WHEEL_RF: wheel = CARWHEEL_FRONT_RIGHT; break; + case CAR_PIECE_WHEEL_LR: wheel = CARWHEEL_REAR_LEFT; break; + case CAR_PIECE_WHEEL_RR: wheel = CARWHEEL_REAR_RIGHT; break; + } + + int status = Damage.GetWheelStatus(wheel); + if(status == WHEEL_STATUS_OK){ + Damage.SetWheelStatus(wheel, WHEEL_STATUS_BURST); + + if(GetStatus() == STATUS_SIMPLE){ + SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(this); + } + + ApplyMoveForce(GetRight() * m_fMass * CGeneral::GetRandomNumberInRange(-fBurstForceMult, fBurstForceMult)); + ApplyTurnForce(GetRight() * m_fTurnMass * CGeneral::GetRandomNumberInRange(-fBurstForceMult, fBurstForceMult), GetForward()); + } +} + +bool +CAutomobile::IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset) +{ + CColPoint colpoint; + CEntity *ent; + colpoint.point = CVector(0.0f, 0.0f, 0.0f); + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + CVector seatPos; + switch(component){ + case CAR_DOOR_RF: + seatPos = mi->GetFrontSeatPosn(); + break; + case CAR_DOOR_LF: + seatPos = mi->GetFrontSeatPosn(); + seatPos.x = -seatPos.x; + break; + case CAR_DOOR_RR: + seatPos = mi->m_positions[CAR_POS_BACKSEAT]; + break; + case CAR_DOOR_LR: + seatPos = mi->m_positions[CAR_POS_BACKSEAT]; + seatPos.x = -seatPos.x; + break; + } + seatPos = GetMatrix() * seatPos; + + CVector doorPos = CPed::GetPositionToOpenCarDoor(this, component); + if(doorOffset){ + CVector off = *doorOffset; + if(component == CAR_DOOR_RF || component == CAR_DOOR_RR) + off.x = -off.x; + doorPos += Multiply3x3(GetMatrix(), off); + } + + if(GetUp().z < 0.0f){ + seatPos.z += 0.5f; + doorPos.z += 0.5f; + } + + CVector dist = doorPos - seatPos; + + // Removing that makes this func. return false for van doors. + doorPos.z += 0.5f; + float length = dist.Magnitude(); + CVector pedPos = seatPos + dist*((length+0.6f)/length); + + if(!CWorld::GetIsLineOfSightClear(seatPos, pedPos, true, false, false, true, false, false)) + return false; + if(CWorld::TestSphereAgainstWorld(doorPos, 0.6f, this, true, true, false, true, false, false)) + return false; + if(CWorld::ProcessVerticalLine(doorPos, 1000.0f, colpoint, ent, true, false, false, true, false, false, nil)) + if(colpoint.point.z > doorPos.z && colpoint.point.z < doorPos.z + 0.6f) + return false; + float upperZ = colpoint.point.z; + if(!CWorld::ProcessVerticalLine(doorPos, -1000.0f, colpoint, ent, true, false, false, true, false, false, nil)) + return false; + if(upperZ != 0.0f && upperZ < colpoint.point.z) + return false; + return true; +} + +float +CAutomobile::GetHeightAboveRoad(void) +{ + return m_fHeightAboveRoad; +} + +void +CAutomobile::PlayCarHorn(void) +{ + uint32 r; + + if(m_nCarHornTimer != 0) + return; + + r = CGeneral::GetRandomNumber() & 7; + if(r < 2){ + m_nCarHornTimer = 45; + }else if(r < 4){ + if(pDriver) + pDriver->Say(SOUND_PED_ANNOYED_DRIVER); + m_nCarHornTimer = 45; + }else{ + if(pDriver) + pDriver->Say(SOUND_PED_ANNOYED_DRIVER); + } +} + +void +CAutomobile::PlayHornIfNecessary(void) +{ + if(AutoPilot.m_bSlowedDownBecauseOfPeds || + AutoPilot.m_bSlowedDownBecauseOfCars) + if(!HasCarStoppedBecauseOfLight()) + PlayCarHorn(); +} + + +void +CAutomobile::ResetSuspension(void) +{ + int i; + for(i = 0; i < 4; i++){ + m_aSuspensionSpringRatio[i] = 1.0f; + m_aWheelTimer[i] = 0.0f; + m_aWheelRotation[i] = 0.0f; + m_aWheelState[i] = WHEEL_STATE_NORMAL; + } +} + +void +CAutomobile::SetupSuspensionLines(void) +{ + int i; + CVector posn; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + CColModel *colModel = mi->GetColModel(); + + // Each suspension line starts at the uppermost wheel position + // and extends down to the lowermost point on the tyre + for(i = 0; i < 4; i++){ + mi->GetWheelPosn(i, posn); + m_aWheelPosition[i] = posn.z; + + // uppermost wheel position + posn.z += pHandling->fSuspensionUpperLimit; + colModel->lines[i].p0 = posn; + + // lowermost wheel position + posn.z += pHandling->fSuspensionLowerLimit - pHandling->fSuspensionUpperLimit; + // lowest point on tyre + posn.z -= mi->m_wheelScale*0.5f; + colModel->lines[i].p1 = posn; + + // this is length of the spring at rest + m_aSuspensionSpringLength[i] = pHandling->fSuspensionUpperLimit - pHandling->fSuspensionLowerLimit; + m_aSuspensionLineLength[i] = colModel->lines[i].p0.z - colModel->lines[i].p1.z; + } + + // Compress spring somewhat to get normal height on road + m_fHeightAboveRoad = -(colModel->lines[0].p0.z + (colModel->lines[0].p1.z - colModel->lines[0].p0.z)* + (1.0f - 1.0f/(8.0f*pHandling->fSuspensionForceLevel))); + for(i = 0; i < 4; i++) + m_aWheelPosition[i] = mi->m_wheelScale*0.5f - m_fHeightAboveRoad; + + // adjust col model to include suspension lines + if(colModel->boundingBox.min.z > colModel->lines[0].p1.z) + colModel->boundingBox.min.z = colModel->lines[0].p1.z; + float radius = Max(colModel->boundingBox.min.Magnitude(), colModel->boundingBox.max.Magnitude()); + if(colModel->boundingSphere.radius < radius) + colModel->boundingSphere.radius = radius; + + if(GetModelIndex() == MI_RCBANDIT){ + colModel->boundingSphere.radius = 2.0f; + for(i = 0; i < colModel->numSpheres; i++) + colModel->spheres[i].radius = 0.3f; + } +} + +// called on police cars +void +CAutomobile::ScanForCrimes(void) +{ + if(FindPlayerVehicle() && FindPlayerVehicle()->IsCar()) + if(FindPlayerVehicle()->IsAlarmOn()) + // if player's alarm is on, increase wanted level + if((FindPlayerVehicle()->GetPosition() - GetPosition()).MagnitudeSqr() < sq(20.0f)) + CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetWantedLevelNoDrop(1); +} + +void +CAutomobile::BlowUpCarsInPath(void) +{ + int i; + + if(m_vecMoveSpeed.Magnitude() > 0.1f) + for(i = 0; i < m_nCollisionRecords; i++) + if(m_aCollisionRecords[i] && + m_aCollisionRecords[i]->IsVehicle() && + m_aCollisionRecords[i]->GetModelIndex() != MI_RHINO && + !m_aCollisionRecords[i]->bRenderScorched) + ((CVehicle*)m_aCollisionRecords[i])->BlowUpCar(this); +} + +bool +CAutomobile::HasCarStoppedBecauseOfLight(void) +{ + int i; + + if(GetStatus() != STATUS_SIMPLE && GetStatus() != STATUS_PHYSICS) + return false; + + if(AutoPilot.m_nCurrentRouteNode && AutoPilot.m_nNextRouteNode){ + CPathNode *curnode = &ThePaths.m_pathNodes[AutoPilot.m_nCurrentRouteNode]; + for(i = 0; i < curnode->numLinks; i++) + if(ThePaths.ConnectedNode(curnode->firstLink + i) == AutoPilot.m_nNextRouteNode) + break; + if(i < curnode->numLinks && + ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3) + return true; + } + + if(AutoPilot.m_nCurrentRouteNode && AutoPilot.m_nPrevRouteNode){ + CPathNode *curnode = &ThePaths.m_pathNodes[AutoPilot.m_nCurrentRouteNode]; + for(i = 0; i < curnode->numLinks; i++) + if(ThePaths.ConnectedNode(curnode->firstLink + i) == AutoPilot.m_nPrevRouteNode) + break; + if(i < curnode->numLinks && + ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3) + return true; + } + + return false; +} + +void +CPed::DeadPedMakesTyresBloody(void) +{ + int minX = CWorld::GetSectorIndexX(GetPosition().x - 2.0f); + if (minX < 0) minX = 0; + int minY = CWorld::GetSectorIndexY(GetPosition().y - 2.0f); + if (minY < 0) minY = 0; + int maxX = CWorld::GetSectorIndexX(GetPosition().x + 2.0f); + if (maxX > NUMSECTORS_X-1) maxX = NUMSECTORS_X-1; + int maxY = CWorld::GetSectorIndexY(GetPosition().y + 2.0f); + if (maxY > NUMSECTORS_Y-1) maxY = NUMSECTORS_Y-1; + + CWorld::AdvanceCurrentScanCode(); + + for (int curY = minY; curY <= maxY; curY++) { + for (int curX = minX; curX <= maxX; curX++) { + CSector *sector = CWorld::GetSector(curX, curY); + MakeTyresMuddySectorList(sector->m_lists[ENTITYLIST_VEHICLES]); + MakeTyresMuddySectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP]); + } + } +} + +void +CPed::MakeTyresMuddySectorList(CPtrList &list) +{ + for (CPtrNode *node = list.first; node; node = node->next) { + CVehicle *veh = (CVehicle*)node->item; + if (veh->IsCar() && veh->m_scanCode != CWorld::GetCurrentScanCode()) { + veh->m_scanCode = CWorld::GetCurrentScanCode(); + + if (Abs(GetPosition().x - veh->GetPosition().x) < 10.0f) { + + if (Abs(GetPosition().y - veh->GetPosition().y) < 10.0f + && veh->m_vecMoveSpeed.MagnitudeSqr2D() > 0.05f) { + + for(int wheel = 0; wheel < 4; wheel++) { + + if (!((CAutomobile*)veh)->m_aWheelSkidmarkBloody[wheel] + && ((CAutomobile*)veh)->m_aSuspensionSpringRatio[wheel] < 1.0f) { + + CColModel *vehCol = veh->GetModelInfo()->GetColModel(); + CVector approxWheelOffset; + switch (wheel) { + case 0: + approxWheelOffset = CVector(-vehCol->boundingBox.max.x, vehCol->boundingBox.max.y, 0.0f); + break; + case 1: + approxWheelOffset = CVector(-vehCol->boundingBox.max.x, vehCol->boundingBox.min.y, 0.0f); + break; + case 2: + approxWheelOffset = CVector(vehCol->boundingBox.max.x, vehCol->boundingBox.max.y, 0.0f); + break; + case 3: + approxWheelOffset = CVector(vehCol->boundingBox.max.x, vehCol->boundingBox.min.y, 0.0f); + break; + default: + break; + } + + // I hope so + CVector wheelPos = veh->GetMatrix() * approxWheelOffset; + if (Abs(wheelPos.z - GetPosition().z) < 2.0f) { + + if ((wheelPos - GetPosition()).MagnitudeSqr2D() < 1.0f) { + if (CGame::nastyGame) { + ((CAutomobile*)veh)->m_aWheelSkidmarkBloody[wheel] = true; + DMAudio.PlayOneShot(veh->m_audioEntityId, SOUND_SPLATTER, 0.0f); + } + veh->ApplyMoveForce(CVector(0.0f, 0.0f, 50.0f)); + + CVector vehAndWheelDist = wheelPos - veh->GetPosition(); + veh->ApplyTurnForce(CVector(0.0f, 0.0f, 50.0f), vehAndWheelDist); + + if (veh == FindPlayerVehicle()) { + CPad::GetPad(0)->StartShake(300, 70); + } + } + } + } + } + } + } + } + } +} + +void +CAutomobile::SetBusDoorTimer(uint32 timer, uint8 type) +{ + if(timer < 1000) + timer = 1000; + if(type == 0) + // open and close + m_nBusDoorTimerStart = CTimer::GetTimeInMilliseconds(); + else + // only close + m_nBusDoorTimerStart = CTimer::GetTimeInMilliseconds() - 500; + m_nBusDoorTimerEnd = m_nBusDoorTimerStart + timer; +} + +void +CAutomobile::ProcessAutoBusDoors(void) +{ + if(CTimer::GetTimeInMilliseconds() < m_nBusDoorTimerEnd){ + if(m_nBusDoorTimerEnd != 0 && CTimer::GetTimeInMilliseconds() > m_nBusDoorTimerEnd-500){ + // close door + if(!IsDoorMissing(DOOR_FRONT_LEFT) && (m_nGettingInFlags & CAR_DOOR_FLAG_LF) == 0){ + if(IsDoorClosed(DOOR_FRONT_LEFT)){ + m_nBusDoorTimerEnd = CTimer::GetTimeInMilliseconds(); + OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f); + }else{ + OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, + 1.0f - (CTimer::GetTimeInMilliseconds() - (m_nBusDoorTimerEnd-500))/500.0f); + } + } + + if(!IsDoorMissing(DOOR_FRONT_RIGHT) && (m_nGettingInFlags & CAR_DOOR_FLAG_RF) == 0){ + if(IsDoorClosed(DOOR_FRONT_RIGHT)){ + m_nBusDoorTimerEnd = CTimer::GetTimeInMilliseconds(); + OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f); + }else{ + OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, + 1.0f - (CTimer::GetTimeInMilliseconds() - (m_nBusDoorTimerEnd-500))/500.0f); + } + } + } + }else{ + // ended + if(m_nBusDoorTimerStart){ + if(!IsDoorMissing(DOOR_FRONT_LEFT) && (m_nGettingInFlags & CAR_DOOR_FLAG_LF) == 0) + OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f); + if(!IsDoorMissing(DOOR_FRONT_RIGHT) && (m_nGettingInFlags & CAR_DOOR_FLAG_RF) == 0) + OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f); + m_nBusDoorTimerStart = 0; + m_nBusDoorTimerEnd = 0; + } + } +} + +void +CAutomobile::ProcessSwingingDoor(int32 component, eDoors door) +{ + if(Damage.GetDoorStatus(door) != DOOR_STATUS_SWINGING) + return; + + CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component])); + CVector pos = mat.GetPosition(); + float axes[3] = { 0.0f, 0.0f, 0.0f }; + + Doors[door].Process(this); + axes[Doors[door].m_nAxis] = Doors[door].m_fAngle; + mat.SetRotate(axes[0], axes[1], axes[2]); + mat.Translate(pos); + mat.UpdateRW(); +} + +void +CAutomobile::Fix(void) +{ + int component; + + Damage.ResetDamageStatus(); + + if(pHandling->Flags & HANDLING_NO_DOORS){ + Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_REAR_LEFT, DOOR_STATUS_MISSING); + Damage.SetDoorStatus(DOOR_REAR_RIGHT, DOOR_STATUS_MISSING); + } + + bIsDamaged = false; + RpClumpForAllAtomics((RpClump*)m_rwObject, CVehicleModelInfo::HideAllComponentsAtomicCB, (void*)ATOMIC_FLAG_DAM); + + for(component = CAR_BUMP_FRONT; component < NUM_CAR_NODES; component++){ + if(m_aCarNodes[component]){ + CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component])); + mat.SetTranslate(mat.GetPosition()); + mat.UpdateRW(); + } + } +} + +void +CAutomobile::SetupDamageAfterLoad(void) +{ + if(m_aCarNodes[CAR_BUMP_FRONT]) + SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT); + if(m_aCarNodes[CAR_BONNET]) + SetDoorDamage(CAR_BONNET, DOOR_BONNET); + if(m_aCarNodes[CAR_BUMP_REAR]) + SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR); + if(m_aCarNodes[CAR_BOOT]) + SetDoorDamage(CAR_BOOT, DOOR_BOOT); + if(m_aCarNodes[CAR_DOOR_LF]) + SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT); + if(m_aCarNodes[CAR_DOOR_RF]) + SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + if(m_aCarNodes[CAR_DOOR_LR]) + SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT); + if(m_aCarNodes[CAR_DOOR_RR]) + SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT); + if(m_aCarNodes[CAR_WING_LF]) + SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT); + if(m_aCarNodes[CAR_WING_RF]) + SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT); + if(m_aCarNodes[CAR_WING_LR]) + SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT); + if(m_aCarNodes[CAR_WING_RR]) + SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT); +} + +RwObject* +GetCurrentAtomicObjectCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + if(RpAtomicGetFlags(atomic) & rpATOMICRENDER) + *(RpAtomic**)data = atomic; + return object; +} + +static CColPoint aTempPedColPts[MAX_COLLISION_POINTS]; + +CObject* +CAutomobile::SpawnFlyingComponent(int32 component, uint32 type) +{ + RpAtomic *atomic; + RwFrame *frame; + RwMatrix *matrix; + CObject *obj; + + if(CObject::nNoTempObjects >= NUMTEMPOBJECTS) + return nil; + + atomic = nil; + RwFrameForAllObjects(m_aCarNodes[component], GetCurrentAtomicObjectCB, &atomic); + if(atomic == nil) + return nil; + + obj = new CObject(); + if(obj == nil) + return nil; + + if(component == CAR_WINDSCREEN){ + obj->SetModelIndexNoCreate(MI_CAR_BONNET); + }else switch(type){ + case COMPGROUP_BUMPER: + obj->SetModelIndexNoCreate(MI_CAR_BUMPER); + break; + case COMPGROUP_WHEEL: + obj->SetModelIndexNoCreate(MI_CAR_WHEEL); + break; + case COMPGROUP_DOOR: + obj->SetModelIndexNoCreate(MI_CAR_DOOR); + obj->SetCenterOfMass(0.0f, -0.5f, 0.0f); + break; + case COMPGROUP_BONNET: + obj->SetModelIndexNoCreate(MI_CAR_BONNET); + obj->SetCenterOfMass(0.0f, 0.4f, 0.0f); + break; + case COMPGROUP_BOOT: + obj->SetModelIndexNoCreate(MI_CAR_BOOT); + obj->SetCenterOfMass(0.0f, -0.3f, 0.0f); + break; + case COMPGROUP_PANEL: + default: + obj->SetModelIndexNoCreate(MI_CAR_PANEL); + break; + } + + // object needs base model + obj->RefModelInfo(GetModelIndex()); + + // create new atomic + matrix = RwFrameGetLTM(m_aCarNodes[component]); + frame = RwFrameCreate(); + atomic = RpAtomicClone(atomic); + *RwFrameGetMatrix(frame) = *matrix; + RpAtomicSetFrame(atomic, frame); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + obj->AttachToRwObject((RwObject*)atomic); + + // init object + obj->m_fMass = 10.0f; + obj->m_fTurnMass = 25.0f; + obj->m_fAirResistance = 0.97f; + obj->m_fElasticity = 0.1f; + obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f; + obj->ObjectCreatedBy = TEMP_OBJECT; + obj->SetIsStatic(false); + obj->bIsPickup = false; + obj->bUseVehicleColours = true; + obj->m_colour1 = m_currentColour1; + obj->m_colour2 = m_currentColour2; + + // life time - the more objects the are, the shorter this one will live + CObject::nNoTempObjects++; + if(CObject::nNoTempObjects > 20) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000/5.0f; + else if(CObject::nNoTempObjects > 10) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000/2.0f; + else + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000; + + obj->m_vecMoveSpeed = m_vecMoveSpeed; + if(obj->m_vecMoveSpeed.z > 0.0f){ + obj->m_vecMoveSpeed.z *= 1.5f; + }else if(GetUp().z > 0.0f && + (component == COMPGROUP_BONNET || component == COMPGROUP_BOOT || component == CAR_WINDSCREEN)){ + obj->m_vecMoveSpeed.z *= -1.5f; + obj->m_vecMoveSpeed.z += 0.04f; + }else{ + obj->m_vecMoveSpeed.z *= 0.25f; + } + obj->m_vecMoveSpeed.x *= 0.75f; + obj->m_vecMoveSpeed.y *= 0.75f; + + obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f; + + // push component away from car + CVector dist = obj->GetPosition() - GetPosition(); + dist.Normalise(); + if(component == COMPGROUP_BONNET || component == COMPGROUP_BOOT || component == CAR_WINDSCREEN){ + // push these up some + dist += GetUp(); + if(GetUp().z > 0.0f){ + // simulate fast upward movement if going fast + float speed = CVector2D(m_vecMoveSpeed).Magnitude(); + obj->GetMatrix().Translate(GetUp()*speed); + } + } + obj->ApplyMoveForce(dist); + + if(type == COMPGROUP_WHEEL){ + obj->m_fTurnMass = 5.0f; + obj->m_vecTurnSpeed.x = 0.5f; + obj->m_fAirResistance = 0.99f; + } + + if(CCollision::ProcessColModels(obj->GetMatrix(), *obj->GetColModel(), + this->GetMatrix(), *this->GetColModel(), + aTempPedColPts, nil, nil) > 0) + obj->m_pCollidingEntity = this; + + if(bRenderScorched) + obj->bRenderScorched = true; + + CWorld::Add(obj); + + return obj; +} + +CObject* +CAutomobile::RemoveBonnetInPedCollision(void) +{ + CObject *obj; + + if(Damage.GetDoorStatus(DOOR_BONNET) == DOOR_STATUS_SWINGING && + Doors[DOOR_BONNET].RetAngleWhenOpen()*0.4f < Doors[DOOR_BONNET].m_fAngle){ +#ifdef FIX_BUGS + obj = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_BONNET); +#else + obj = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_DOOR); +#endif + // make both doors invisible on car + SetComponentVisibility(m_aCarNodes[CAR_BONNET], ATOMIC_FLAG_NONE); + Damage.SetDoorStatus(DOOR_BONNET, DOOR_STATUS_MISSING); + return obj; + } + return nil; +} + +void +CAutomobile::SetPanelDamage(int32 component, ePanels panel, bool noFlyingComponents) +{ + int status = Damage.GetPanelStatus(panel); + if(m_aCarNodes[component] == nil) + return; + if(status == PANEL_STATUS_SMASHED1){ + // show damaged part + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM); + }else if(status == PANEL_STATUS_MISSING){ + if(!noFlyingComponents) + SpawnFlyingComponent(component, COMPGROUP_PANEL); + // hide both + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_NONE); + } +} + +void +CAutomobile::SetBumperDamage(int32 component, ePanels panel, bool noFlyingComponents) +{ + int status = Damage.GetPanelStatus(panel); + if(m_aCarNodes[component] == nil){ + printf("Trying to damage component %d of %s\n", + component, CModelInfo::GetModelInfo(GetModelIndex())->GetModelName()); + return; + } + if(status == PANEL_STATUS_SMASHED1){ + // show damaged part + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM); + }else if(status == PANEL_STATUS_MISSING){ + if(!noFlyingComponents) + SpawnFlyingComponent(component, COMPGROUP_BUMPER); + // hide both + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_NONE); + } +} + +void +CAutomobile::SetDoorDamage(int32 component, eDoors door, bool noFlyingComponents) +{ + int status = Damage.GetDoorStatus(door); + if(m_aCarNodes[component] == nil){ + printf("Trying to damage component %d of %s\n", + component, CModelInfo::GetModelInfo(GetModelIndex())->GetModelName()); + return; + } + + if(door == DOOR_BOOT && status == DOOR_STATUS_SWINGING && pHandling->Flags & HANDLING_NOSWING_BOOT){ + Damage.SetDoorStatus(DOOR_BOOT, DOOR_STATUS_MISSING); + status = DOOR_STATUS_MISSING; + } + + switch(status){ + case DOOR_STATUS_SMASHED: + // show damaged part + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM); + break; + case DOOR_STATUS_SWINGING: + // turn off angle cull for swinging doors + RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::SetAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL); + break; + case DOOR_STATUS_MISSING: + if(!noFlyingComponents){ + if(door == DOOR_BONNET) + SpawnFlyingComponent(component, COMPGROUP_BONNET); + else if(door == DOOR_BOOT) + SpawnFlyingComponent(component, COMPGROUP_BOOT); + else + SpawnFlyingComponent(component, COMPGROUP_DOOR); + } + // hide both + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_NONE); + break; + } +} + + +static RwObject* +SetVehicleAtomicVisibilityCB(RwObject *object, void *data) +{ + uint32 flags = (uint32)(uintptr)data; + RpAtomic *atomic = (RpAtomic*)object; + if((CVisibilityPlugins::GetAtomicId(atomic) & (ATOMIC_FLAG_OK|ATOMIC_FLAG_DAM)) == flags) + RpAtomicSetFlags(atomic, rpATOMICRENDER); + else + RpAtomicSetFlags(atomic, 0); + return object; +} + +void +CAutomobile::SetComponentVisibility(RwFrame *frame, uint32 flags) +{ + HideAllComps(); + bIsDamaged = true; + RwFrameForAllObjects(frame, SetVehicleAtomicVisibilityCB, (void*)flags); +} + +void +CAutomobile::SetupModelNodes(void) +{ + int i; + for(i = 0; i < NUM_CAR_NODES; i++) + m_aCarNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aCarNodes); +} + +void +CAutomobile::SetTaxiLight(bool light) +{ + bTaxiLight = light; +} + +bool +CAutomobile::GetAllWheelsOffGround(void) +{ + return m_nDriveWheelsOnGround == 0; +} + +void +CAutomobile::HideAllComps(void) +{ + // empty +} + +void +CAutomobile::ShowAllComps(void) +{ + // empty +} + +void +CAutomobile::ReduceHornCounter(void) +{ + if(m_nCarHornTimer != 0) + m_nCarHornTimer--; +} + +void +CAutomobile::SetAllTaxiLights(bool set) +{ + m_sAllTaxiLights = set; +} + +#ifdef COMPATIBLE_SAVES +void +CAutomobile::Save(uint8*& buf) +{ + CVehicle::Save(buf); + WriteSaveBuf(buf, Damage); + ZeroSaveBuf(buf, 800 - sizeof(CDamageManager)); +} + +void +CAutomobile::Load(uint8*& buf) +{ + CVehicle::Load(buf); + ReadSaveBuf(&Damage, buf); + SkipSaveBuf(buf, 800 - sizeof(CDamageManager)); + SetupDamageAfterLoad(); +} +#endif diff --git a/src/vehicles/Automobile.h b/src/vehicles/Automobile.h new file mode 100644 index 0000000..a5bee22 --- /dev/null +++ b/src/vehicles/Automobile.h @@ -0,0 +1,204 @@ +#pragma once + +#include "Vehicle.h" +#include "DamageManager.h" +#include "Door.h" + +class CObject; + +enum eCarNodes +{ + CAR_WHEEL_RF = 1, + CAR_WHEEL_RM, + CAR_WHEEL_RB, + CAR_WHEEL_LF, + CAR_WHEEL_LM, + CAR_WHEEL_LB, + CAR_BUMP_FRONT, + CAR_BUMP_REAR, + CAR_WING_RF, + CAR_WING_RR, + CAR_DOOR_RF, + CAR_DOOR_RR, + CAR_WING_LF, + CAR_WING_LR, + CAR_DOOR_LF, + CAR_DOOR_LR, + CAR_BONNET, + CAR_BOOT, + CAR_WINDSCREEN, + NUM_CAR_NODES, +}; + +enum { + CARWHEEL_FRONT_LEFT, + CARWHEEL_REAR_LEFT, + CARWHEEL_FRONT_RIGHT, + CARWHEEL_REAR_RIGHT +}; + +enum eBombType +{ + CARBOMB_NONE, + CARBOMB_TIMED, + CARBOMB_ONIGNITION, + CARBOMB_REMOTE, + CARBOMB_TIMEDACTIVE, + CARBOMB_ONIGNITIONACTIVE, +}; + +enum { + CAR_DOOR_FLAG_UNKNOWN = 0x0, + CAR_DOOR_FLAG_LF = 0x1, + CAR_DOOR_FLAG_LR = 0x2, + CAR_DOOR_FLAG_RF = 0x4, + CAR_DOOR_FLAG_RR = 0x8 +}; + +class CAutomobile : public CVehicle +{ +public: + // 0x288 + CDamageManager Damage; + CDoor Doors[6]; + RwFrame *m_aCarNodes[NUM_CAR_NODES]; + CColPoint m_aWheelColPoints[4]; + float m_aSuspensionSpringRatio[4]; + float m_aSuspensionSpringRatioPrev[4]; + float m_aWheelTimer[4]; // set to 4.0 when wheel is touching ground, then decremented + float m_auto_unused1; + bool m_aWheelSkidmarkMuddy[4]; + bool m_aWheelSkidmarkBloody[4]; + float m_aWheelRotation[4]; + float m_aWheelPosition[4]; + float m_aWheelSpeed[4]; + uint8 m_auto_unused2; + uint8 m_bombType : 3; + uint8 bTaxiLight : 1; + uint8 bDriverLastFrame : 1; // for bombs + uint8 bFixedColour : 1; + uint8 bBigWheels : 1; + uint8 bWaterTight : 1; // no damage for non-player peds + uint8 bNotDamagedUpsideDown : 1; + uint8 bMoreResistantToDamage : 1; + CEntity *m_pBombRigger; + int16 m_auto_unk1; + uint16 m_hydraulicState; + uint32 m_nBusDoorTimerEnd; + uint32 m_nBusDoorTimerStart; + float m_aSuspensionSpringLength[4]; + float m_aSuspensionLineLength[4]; + float m_fHeightAboveRoad; + float m_fTraction; + float m_fVelocityChangeForAudio; + float m_randomValues[6]; // used for what? + float m_fFireBlowUpTimer; + CPhysical *m_aGroundPhysical[4]; // physicals touching wheels + CVector m_aGroundOffset[4]; // from ground object to colpoint + CEntity *m_pSetOnFireEntity; + float m_weaponDoorTimerLeft; // still don't know what exactly this is + float m_weaponDoorTimerRight; + float m_fCarGunLR; + float m_fCarGunUD; + float m_fPropellerRotation; + uint8 stuff4[4]; + uint8 m_nWheelsOnGround; + uint8 m_nDriveWheelsOnGround; + uint8 m_nDriveWheelsOnGroundPrev; + float m_fGasPedalAudio; + tWheelState m_aWheelState[4]; + + static bool m_sAllTaxiLights; + + CAutomobile(int32 id, uint8 CreatedBy); + + // from CEntity + void SetModelIndex(uint32 id); + void ProcessControl(void); + void Teleport(CVector v); + void PreRender(void); + void Render(void); + + // from CPhysical + int32 ProcessEntityCollision(CEntity *ent, CColPoint *colpoints); + + // from CVehicle + void ProcessControlInputs(uint8); + void GetComponentWorldPosition(int32 component, CVector &pos); + bool IsComponentPresent(int32 component); + void SetComponentRotation(int32 component, CVector rotation); + void OpenDoor(int32 component, eDoors door, float openRatio); + void ProcessOpenDoor(uint32, uint32, float); + bool IsDoorReady(eDoors door); + bool IsDoorFullyOpen(eDoors door); + bool IsDoorClosed(eDoors door); + bool IsDoorMissing(eDoors door); + void RemoveRefsToVehicle(CEntity *ent); + void BlowUpCar(CEntity *ent); + bool SetUpWheelColModel(CColModel *colModel); + void BurstTyre(uint8 tyre); + bool IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset); + float GetHeightAboveRoad(void); + void PlayCarHorn(void); + + void FireTruckControl(void); + void TankControl(void); + void HydraulicControl(void); + void VehicleDamage(float impulse, uint16 damagedPiece); + void ProcessBuoyancy(void); + void DoDriveByShootings(void); + int32 RcbanditCheckHitWheels(void); + int32 RcbanditCheck1CarWheels(CPtrList &list); + void PlaceOnRoadProperly(void); + void dmgDrawCarCollidingParticles(const CVector &pos, float amount); + void AddDamagedVehicleParticles(void); + int32 AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed); + void PlayHornIfNecessary(void); + void ResetSuspension(void); + void SetupSuspensionLines(void); + void ScanForCrimes(void); + void BlowUpCarsInPath(void); + bool HasCarStoppedBecauseOfLight(void); + void SetBusDoorTimer(uint32 timer, uint8 type); + void ProcessAutoBusDoors(void); + void ProcessSwingingDoor(int32 component, eDoors door); + void SetupDamageAfterLoad(void); + CObject *SpawnFlyingComponent(int32 component, uint32 type); + CObject *RemoveBonnetInPedCollision(void); + void SetPanelDamage(int32 component, ePanels panel, bool noFlyingComponents = false); + void SetBumperDamage(int32 component, ePanels panel, bool noFlyingComponents = false); + void SetDoorDamage(int32 component, eDoors door, bool noFlyingComponents = false); + + void Fix(void); + void SetComponentVisibility(RwFrame *frame, uint32 flags); + void SetupModelNodes(void); + void SetTaxiLight(bool light); + bool GetAllWheelsOffGround(void); + void HideAllComps(void); + void ShowAllComps(void); + void ReduceHornCounter(void); +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + static const uint32 nSaveStructSize; + + static void SetAllTaxiLights(bool set); +}; + +VALIDATE_SIZE(CAutomobile, 0x5A8); + +inline uint8 GetCarDoorFlag(int32 carnode) { + switch (carnode) { + case CAR_DOOR_LF: + return CAR_DOOR_FLAG_LF; + case CAR_DOOR_LR: + return CAR_DOOR_FLAG_LR; + case CAR_DOOR_RF: + return CAR_DOOR_FLAG_RF; + case CAR_DOOR_RR: + return CAR_DOOR_FLAG_RR; + default: + return CAR_DOOR_FLAG_UNKNOWN; + } +} diff --git a/src/vehicles/Bike.h b/src/vehicles/Bike.h new file mode 100644 index 0000000..85ff211 --- /dev/null +++ b/src/vehicles/Bike.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Vehicle.h" + +// some miami bike leftovers + +enum eBikeNodes { + BIKE_NODE_NONE, + BIKE_CHASSIS, + BIKE_FORKS_FRONT, + BIKE_FORKS_REAR, + BIKE_WHEEL_FRONT, + BIKE_WHEEL_REAR, + BIKE_MUDGUARD, + BIKE_HANDLEBARS, + BIKE_NUM_NODES +}; + +class CBike : public CVehicle +{ +public: + RwFrame *m_aBikeNodes[BIKE_NUM_NODES]; // assuming + uint8 unk1[96]; + AnimationId m_bikeSitAnimation; + uint8 unk2[180]; + float m_aSuspensionSpringRatio[4]; + + /* copied from VC, one of the floats here is gone, assuming m_bike_unused1 */ + float m_aSuspensionSpringRatioPrev[4]; + float m_aWheelTimer[4]; + //float m_bike_unused1; + int m_aWheelSkidmarkType[2]; + bool m_aWheelSkidmarkBloody[2]; + bool m_aWheelSkidmarkUnk[2]; + float m_aWheelRotation[2]; + float m_aWheelSpeed[2]; + float m_aWheelPosition[2]; + float m_aWheelBasePosition[2]; + float m_aSuspensionSpringLength[4]; + float m_aSuspensionLineLength[4]; + float m_fHeightAboveRoad; + /**/ + + float m_fTraction; +}; \ No newline at end of file diff --git a/src/vehicles/Boat.cpp b/src/vehicles/Boat.cpp new file mode 100644 index 0000000..65cdd8c --- /dev/null +++ b/src/vehicles/Boat.cpp @@ -0,0 +1,952 @@ +#include "common.h" + +#include "main.h" +#include "General.h" +#include "Timecycle.h" +#include "HandlingMgr.h" +#include "CarCtrl.h" +#include "RwHelper.h" +#include "ModelIndices.h" +#include "VisibilityPlugins.h" +#include "DMAudio.h" +#include "Camera.h" +#include "Darkel.h" +#include "Explosion.h" +#include "Particle.h" +#include "WaterLevel.h" +#include "Floater.h" +#include "World.h" +#include "Pools.h" +#include "Pad.h" +#include "Boat.h" +#include "SaveBuf.h" + +#define INVALID_ORIENTATION (-9999.99f) + +float MAX_WAKE_LENGTH = 50.0f; +float MIN_WAKE_INTERVAL = 1.0f; +float WAKE_LIFETIME = 400.0f; + +float fShapeLength = 0.4f; +float fShapeTime = 0.05f; +float fRangeMult = 0.75f; +float fTimeMult = 1.0f/WAKE_LIFETIME; + +CBoat *CBoat::apFrameWakeGeneratingBoats[4]; + +const uint32 CBoat::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1156; +#else + sizeof(CBoat); +#endif + +CBoat::CBoat(int mi, uint8 owner) : CVehicle(owner) +{ + CVehicleModelInfo *minfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(mi); + m_vehType = VEHICLE_TYPE_BOAT; + m_fAccelerate = 0.0f; + m_fBrake = 0.0f; + m_fSteeringLeftRight = 0.0f; + m_nPadID = 0; + m_fMovingRotation = 0.0f; + SetModelIndex(mi); + + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)minfo->m_handlingId); + minfo->ChooseVehicleColour(m_currentColour1, m_currentColour2); + + m_fMass = pHandling->fMass; + m_fTurnMass = pHandling->fTurnMass / 2.0f; + m_vecCentreOfMass = pHandling->CentreOfMass; + m_fAirResistance = pHandling->Dimension.x * pHandling->Dimension.z / m_fMass; + m_fElasticity = 0.1f; + m_fBuoyancy = pHandling->fBuoyancy; + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + m_fBrakePedal = 0.0f; + + m_fThrustZ = 0.25f; + m_fThrustY = 0.35f; + m_vecMoveRes = CVector(0.7f, 0.998f, 0.999f); + m_vecTurnRes = CVector(0.85f, 0.96f, 0.96f); + m_boat_unused3 = false; + + m_fVolumeUnderWater = 7.0f; + m_fPrevVolumeUnderWater = 7.0f; + m_vecBuoyancePoint = CVector(0.0f, 0.0f, 0.0f); + + m_nDeltaVolumeUnderWater = 0; + bBoatInWater = true; + bPropellerInWater = true; + + bIsInWater = true; + + m_boat_unused2 = 0; + m_bIsAnchored = true; + m_fOrientation = INVALID_ORIENTATION; + bTouchingWater = true; + m_fDamage = 0.0f; + m_pSetOnFireEntity = nil; + m_nNumWakePoints = 0; + + for (int16 i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++) + m_afWakePointLifeTime[i] = 0.0f; + + m_nAmmoInClip = 20; +} + +void +CBoat::SetModelIndex(uint32 id) +{ + CEntity::SetModelIndex(id); + SetupModelNodes(); +} + +void +CBoat::GetComponentWorldPosition(int32 component, CVector &pos) +{ + pos = *RwMatrixGetPos(RwFrameGetLTM(m_aBoatNodes[component])); +} + +void +CBoat::ProcessControl(void) +{ + if(m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory) + return; + + bool onLand = m_fDamageImpulse > 0.0f && m_vecDamageNormal.z > 0.1f; + + PruneWakeTrail(); + + int r, g, b; + RwRGBA splashColor, jetColor; + r = 114.75f*(CTimeCycle::GetAmbientRed() + 0.5f*CTimeCycle::GetDirectionalRed()); + g = 114.75f*(CTimeCycle::GetAmbientGreen() + 0.5f*CTimeCycle::GetDirectionalGreen()); + b = 114.75f*(CTimeCycle::GetAmbientBlue() + 0.5f*CTimeCycle::GetDirectionalBlue()); + r = Clamp(r, 0, 255); + g = Clamp(g, 0, 255); + b = Clamp(b, 0, 255); + splashColor.red = r; + splashColor.green = g; + splashColor.blue = b; + splashColor.alpha = CGeneral::GetRandomNumberInRange(128, 150); + + r = 242.25f*(CTimeCycle::GetAmbientRed() + 0.5f*CTimeCycle::GetDirectionalRed()); + g = 242.25f*(CTimeCycle::GetAmbientGreen() + 0.5f*CTimeCycle::GetDirectionalGreen()); + b = 242.25f*(CTimeCycle::GetAmbientBlue() + 0.5f*CTimeCycle::GetDirectionalBlue()); + r = Clamp(r, 0, 255); + g = Clamp(g, 0, 255); + b = Clamp(b, 0, 255); + jetColor.red = r; + jetColor.green = g; + jetColor.blue = b; + jetColor.alpha = CGeneral::GetRandomNumberInRange(96, 128); + + CGeneral::GetRandomNumber(); // unused + + ProcessCarAlarm(); + + switch(GetStatus()){ + case STATUS_PLAYER: + m_bIsAnchored = false; + m_fOrientation = INVALID_ORIENTATION; + ProcessControlInputs(0); + if(GetModelIndex() == MI_PREDATOR) + DoFixedMachineGuns(); + break; + case STATUS_SIMPLE: + m_bIsAnchored = false; + m_fOrientation = INVALID_ORIENTATION; + CPhysical::ProcessControl(); + bBoatInWater = true; + bPropellerInWater = true; + bIsInWater = true; + return; + case STATUS_PHYSICS: + m_bIsAnchored = false; + m_fOrientation = INVALID_ORIENTATION; + CCarCtrl::SteerAIBoatWithPhysics(this); + break; + case STATUS_ABANDONED: + case STATUS_WRECKED: + bBoatInWater = true; + bPropellerInWater = true; + bIsInWater = true; + m_fSteerAngle = 0.0; + bIsHandbrakeOn = false; + m_fBrakePedal = 0.5f; + m_fGasPedal = 0.0f; + if((GetPosition() - CWorld::Players[CWorld::PlayerInFocus].GetPos()).Magnitude() > 150.0f){ + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + return; + } + break; + default: break; + } + + float collisionDamage = pHandling->fCollisionDamageMultiplier * m_fDamageImpulse; +#ifdef FIX_BUGS + if (collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED && m_fHealth >= 150.0f && !bCollisionProof) { +#else + if(collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED && m_fHealth >= 150.0f){ +#endif + float prevHealth = m_fHealth; + if(this == FindPlayerVehicle()){ + if(bTakeLessDamage) + m_fHealth -= (collisionDamage-25.0f)/6.0f; + else + m_fHealth -= (collisionDamage-25.0f)/2.0f; + }else{ + if(collisionDamage > 60.0f && pDriver) + pDriver->Say(SOUND_PED_ANNOYED_DRIVER); + if(bTakeLessDamage) + m_fHealth -= (collisionDamage-25.0f)/12.0f; + else + m_fHealth -= (collisionDamage-25.0f)/4.0f; + } + + if(m_fHealth <= 0.0f && prevHealth > 0.0f){ + m_fHealth = 1.0f; + m_pSetOnFireEntity = m_pDamageEntity; + } + } + + // Damage particles + if(m_fHealth <= 600.0f && GetStatus() != STATUS_WRECKED && + Abs(GetPosition().x - TheCamera.GetPosition().x) < 200.0f && + Abs(GetPosition().y - TheCamera.GetPosition().y) < 200.0f){ + float speedSq = m_vecMoveSpeed.MagnitudeSqr(); + CVector smokeDir = 0.8f*m_vecMoveSpeed; + CVector smokePos; + switch(GetModelIndex()){ + case MI_SPEEDER: + smokePos = CVector(0.4f, -2.4f, 0.8f); + smokeDir += 0.05f*GetRight(); + smokeDir.z += 0.2f*m_vecMoveSpeed.z; + break; + case MI_REEFER: + smokePos = CVector(2.0f, -1.0f, 0.5f); + smokeDir += 0.07f*GetRight(); + break; + case MI_PREDATOR: + default: + smokePos = CVector(-1.5f, -0.5f, 1.2f); + smokeDir += -0.08f*GetRight(); + break; + } + + smokePos = GetMatrix() * smokePos; + + // On fire + if(m_fHealth < 150.0f){ + CParticle::AddParticle(PARTICLE_CARFLAME, smokePos, + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(2.25f/200.0f, 0.09f)), + nil, 0.9f); + CVector smokePos2 = smokePos; + smokePos2.x += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f); + smokePos2.y += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f); + smokePos2.z += CGeneral::GetRandomNumberInRange(2.25f/4.0f, 2.25f); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, smokePos2, CVector(0.0f, 0.0f, 0.0f)); + + m_fDamage += CTimer::GetTimeStepInMilliseconds(); + if(m_fDamage > 5000.0f) + BlowUpCar(m_pSetOnFireEntity); + } + + if(speedSq < 0.25f && (CTimer::GetFrameCounter() + m_randomSeed) & 1) + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, smokePos, smokeDir); + if(speedSq < 0.25f && m_fHealth <= 350.0f) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, smokePos, 1.25f*smokeDir); + } + + CPhysical::ProcessControl(); + + CVector buoyanceImpulse(0.0f, 0.0f, 0.0f); + CVector buoyancePoint(0.0f, 0.0f, 0.0f); + if(mod_Buoyancy.ProcessBuoyancy(this, pHandling->fBuoyancy, &buoyancePoint, &buoyanceImpulse)){ + // Process boat in water + if(0.1f * m_fMass * GRAVITY*CTimer::GetTimeStep() < buoyanceImpulse.z){ + bBoatInWater = true; + bIsInWater = true; + }else{ + bBoatInWater = false; + bIsInWater = false; + } + + m_fVolumeUnderWater = mod_Buoyancy.m_volumeUnderWater; + m_vecBuoyancePoint = buoyancePoint; + ApplyMoveForce(buoyanceImpulse); + if(!onLand) + ApplyTurnForce(buoyanceImpulse, buoyancePoint); + + if(!onLand && bBoatInWater && GetUp().z > 0.0f){ + float impulse; + if(m_fGasPedal > 0.05f) + impulse = m_vecMoveSpeed.MagnitudeSqr()*pHandling->fSuspensionForceLevel*buoyanceImpulse.z*CTimer::GetTimeStep()*0.5f*m_fGasPedal; + else + impulse = 0.0f; + impulse = Min(impulse, GRAVITY*pHandling->fSuspensionDampingLevel*m_fMass*CTimer::GetTimeStep()); + ApplyMoveForce(impulse*GetUp()); + ApplyTurnForce(impulse*GetUp(), buoyancePoint - pHandling->fSuspensionBias*GetForward()); + } + + // Handle boat moving forward + if(Abs(m_fGasPedal) > 0.05f || m_vecMoveSpeed.Magnitude2D() > 0.01f){ + if(bBoatInWater) + AddWakePoint(GetPosition()); + + float steerFactor = 1.0f - DotProduct(m_vecMoveSpeed, GetForward()); + if (GetModelIndex() == MI_GHOST) + steerFactor = 1.0f - DotProduct(m_vecMoveSpeed, GetForward())*0.3f; + if(steerFactor < 0.0f) steerFactor = 0.0f; + + CVector propeller(0.0f, -pHandling->Dimension.y*m_fThrustY, -pHandling->Dimension.z*m_fThrustZ); + propeller = Multiply3x3(GetMatrix(), propeller); + CVector propellerWorld = GetPosition() + propeller; + + float steerSin = Sin(-m_fSteerAngle * steerFactor); + float steerCos = Cos(-m_fSteerAngle * steerFactor); + float waterLevel; + CWaterLevel::GetWaterLevel(propellerWorld, &waterLevel, true); + if(propellerWorld.z-0.5f < waterLevel){ + float propellerDepth = waterLevel - (propellerWorld.z - 0.5f); + if(propellerDepth > 1.0f) + propellerDepth = 1.0f; + else + propellerDepth = SQR(propellerDepth); + bPropellerInWater = true; + + if(Abs(m_fGasPedal) > 0.05f){ + CVector forceDir = Multiply3x3(GetMatrix(), CVector(-steerSin, steerCos, -Abs(m_fSteerAngle))); + CVector force = propellerDepth * m_fGasPedal * 40.0f * pHandling->Transmission.fEngineAcceleration * pHandling->fMass * forceDir; + if(force.z > 0.2f) + force.z = SQR(1.2f - force.z) + 0.2f; + if(onLand){ + if(m_fGasPedal < 0.0f){ + force.x *= 5.0f; + force.y *= 5.0f; + } + if(force.z < 0.0f) + force.z = 0.0f; + ApplyMoveForce(force * CTimer::GetTimeStep()); + }else{ + ApplyMoveForce(force * CTimer::GetTimeStep()); + ApplyTurnForce(force * CTimer::GetTimeStep(), propeller - pHandling->fTractionBias*GetUp()); + float rightForce = DotProduct(GetRight(), force); + ApplyTurnForce(-rightForce*GetRight() * CTimer::GetTimeStep(), GetUp()); + } + + // Spray some particles + CVector jetDir = -0.04f * force; + if(m_fGasPedal > 0.0f){ + if(GetStatus() == STATUS_PLAYER){ + bool cameraHack = TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || + TheCamera.WhoIsInControlOfTheCamera == CAMCONTROL_OBBE; + CVector sternPos = GetColModel()->boundingBox.min; + sternPos.x = 0.0f; + sternPos.z = 0.0f; + sternPos = Multiply3x3(GetMatrix(), sternPos); + + CVector jetPos = GetPosition() + sternPos; + if(cameraHack) + jetPos.z = 1.0f; + else + jetPos.z = 0.0f; + +#ifdef PC_PARTICLE + CVector wakePos = GetPosition() + sternPos; + wakePos.z -= 0.65f; +#else + CVector wakePos = GetPosition() + sternPos; + wakePos.z = -0.3f; +#endif + + CVector wakeDir = 0.75f * jetDir; + + CParticle::AddParticle(PARTICLE_BOAT_THRUSTJET, jetPos, jetDir, nil, 0.0f, jetColor); +#ifdef PC_PARTICLE + CParticle::AddParticle(PARTICLE_CAR_SPLASH, jetPos, 0.25f * jetDir, nil, 1.0f, splashColor, + CGeneral::GetRandomNumberInRange(0, 30), + CGeneral::GetRandomNumberInRange(0, 90), 3); +#endif + if(!cameraHack) + CParticle::AddParticle(PARTICLE_BOAT_WAKE, wakePos, wakeDir, nil, 0.0f, jetColor); + }else if((CTimer::GetFrameCounter() + m_randomSeed) & 1){ +#ifdef PC_PARTICLE + jetDir.z = 0.018f; + jetDir.x *= 0.01f; + jetDir.y *= 0.01f; + propellerWorld.z += 1.5f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 1.5f, jetColor); +#else + jetDir.z = 0.018f; + jetDir.x *= 0.03f; + jetDir.y *= 0.03f; + propellerWorld.z += 1.0f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 0.0f, jetColor); +#endif + +#ifdef PC_PARTICLE + CParticle::AddParticle(PARTICLE_CAR_SPLASH, propellerWorld, 0.1f * jetDir, nil, 0.5f, splashColor, + CGeneral::GetRandomNumberInRange(0, 30), + CGeneral::GetRandomNumberInRange(0, 90), 3); +#endif + } + } + }else if(!onLand){ + float force = 50.0f*DotProduct(m_vecMoveSpeed, GetForward()); + force = Min(force, 10.0f); + CVector propellerForce = propellerDepth * Multiply3x3(GetMatrix(), force*CVector(-steerSin, 0.0f, 0.0f)); + ApplyMoveForce(propellerForce * CTimer::GetTimeStep()*0.5f); + ApplyTurnForce(propellerForce * CTimer::GetTimeStep()*0.5f, propeller); + } + }else + bPropellerInWater = false; + } + + // Slow down or push down boat as it approaches the world limits + m_vecMoveSpeed.x = Min(m_vecMoveSpeed.x, -(GetPosition().x - 1900.0f)*0.01f); // east + m_vecMoveSpeed.x = Max(m_vecMoveSpeed.x, -(GetPosition().x - -1515.0f)*0.01f); // west + m_vecMoveSpeed.y = Min(m_vecMoveSpeed.y, -(GetPosition().y - 600.0f)*0.01f); // north + m_vecMoveSpeed.y = Max(m_vecMoveSpeed.y, -(GetPosition().y - -1900.0f)*0.01f); // south + + if(!onLand && bBoatInWater) + ApplyWaterResistance(); + + // No idea what exactly is going on here besides drag in YZ + float fx = Pow(m_vecTurnRes.x, CTimer::GetTimeStep()); + float fy = Pow(m_vecTurnRes.y, CTimer::GetTimeStep()); + float fz = Pow(m_vecTurnRes.z, CTimer::GetTimeStep()); + m_vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); // invert - to local space + // TODO: figure this out + float magic = 1.0f/(1000.0f * SQR(m_vecTurnSpeed.x) + 1.0f) * fx; + m_vecTurnSpeed.y *= fy; + m_vecTurnSpeed.z *= fz; + float forceUp = (magic - 1.0f) * m_vecTurnSpeed.x * m_fTurnMass; + m_vecTurnSpeed = Multiply3x3(GetMatrix(), m_vecTurnSpeed); // back to world + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + ApplyTurnForce(CVector(0.0f, 0.0f, forceUp), com + GetForward()); + + m_nDeltaVolumeUnderWater = (m_fVolumeUnderWater-m_fPrevVolumeUnderWater)*10000; + + // Falling into water + if(!onLand && bBoatInWater && GetUp().z > 0.0f && m_nDeltaVolumeUnderWater > 200){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, m_nDeltaVolumeUnderWater); + + float speedUp = m_vecMoveSpeed.MagnitudeSqr() * m_nDeltaVolumeUnderWater * 0.0004f; + if(speedUp + m_vecMoveSpeed.z > pHandling->fBrakeDeceleration) + speedUp = pHandling->fBrakeDeceleration - m_vecMoveSpeed.z; + if(speedUp < 0.0f) speedUp = 0.0f; + float speedFwd = DotProduct(m_vecMoveSpeed, GetForward()); + speedFwd *= -m_nDeltaVolumeUnderWater * 0.01f * pHandling->fTractionLoss; + CVector speed = speedFwd*GetForward() + CVector(0.0f, 0.0f, speedUp); + CVector splashImpulse = speed * m_fMass; + ApplyMoveForce(splashImpulse); + ApplyTurnForce(splashImpulse, buoyancePoint); + } + + // Spray particles on sides of boat +#ifdef PC_PARTICLE + if(m_nDeltaVolumeUnderWater > 75) +#else + if(m_nDeltaVolumeUnderWater > 120) +#endif + { + float speed = m_vecMoveSpeed.Magnitude(); + float splash1Size = speed; + float splash2Size = float(m_nDeltaVolumeUnderWater) * 0.005f * 0.2f; + float front = 0.9f * GetColModel()->boundingBox.max.y; + if(splash1Size > 0.75f) splash1Size = 0.75f; + + CVector dir, pos; + + // right +#ifdef PC_PARTICLE + dir = -0.5f*m_vecMoveSpeed; + dir.z += 0.1f*speed; + dir += 0.5f*GetRight()*speed; + pos = front*GetForward() + 0.5f*GetRight() + GetPosition() + m_vecBuoyancePoint; + CWaterLevel::GetWaterLevel(pos, &pos.z, true); +#else + dir = 0.3f*m_vecMoveSpeed; + dir.z += 0.05f*speed; + dir += 0.5f*GetRight()*speed; + pos = (GetPosition() + m_vecBuoyancePoint) + (1.5f*GetRight()); +#endif + +#ifdef PC_PARTICLE + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, 0.75f * dir, nil, splash1Size, splashColor, + CGeneral::GetRandomNumberInRange(0, 30), + CGeneral::GetRandomNumberInRange(0, 90), 1); + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size, jetColor); +#else + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size); +#endif + + + // left +#ifdef PC_PARTICLE + dir = -0.5f*m_vecMoveSpeed; + dir.z += 0.1f*speed; + dir -= 0.5f*GetRight()*speed; + pos = front*GetForward() - 0.5f*GetRight() + GetPosition() + m_vecBuoyancePoint; + CWaterLevel::GetWaterLevel(pos, &pos.z, true); +#else + dir = 0.3f*m_vecMoveSpeed; + dir.z += 0.05f*speed; + dir -= 0.5f*GetRight()*speed; + pos = (GetPosition() + m_vecBuoyancePoint) - (1.5f*GetRight()); +#endif + +#ifdef PC_PARTICLE + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, 0.75f * dir, nil, splash1Size, splashColor, + CGeneral::GetRandomNumberInRange(0, 30), + CGeneral::GetRandomNumberInRange(0, 90), 1); + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size, jetColor); +#else + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size); +#endif + } + + m_fPrevVolumeUnderWater = m_fVolumeUnderWater; + }else{ + bBoatInWater = false; + bIsInWater = false; + } + + if(m_bIsAnchored){ + m_vecMoveSpeed.x = 0.0f; + m_vecMoveSpeed.y = 0.0f; + + if(m_fOrientation == INVALID_ORIENTATION){ + m_fOrientation = GetForward().Heading(); + }else{ + // is this some inlined CPlaceable method? + CVector pos = GetPosition(); + GetMatrix().RotateZ(m_fOrientation - GetForward().Heading()); + GetMatrix().SetTranslateOnly(pos); + } + } + + ProcessDelayedExplosion(); +} + +void +CBoat::ProcessControlInputs(uint8 pad) +{ + m_nPadID = pad; + if(m_nPadID > 3) + m_nPadID = 3; + + m_fBrake += (CPad::GetPad(pad)->GetBrake()/255.0f - m_fBrake)*0.1f; + m_fBrake = Clamp(m_fBrake, 0.0f, 1.0f); + + if(m_fBrake < 0.05f){ + m_fBrake = 0.0f; + m_fAccelerate += (CPad::GetPad(pad)->GetAccelerate()/255.0f - m_fAccelerate)*0.1f; + m_fAccelerate = Clamp(m_fAccelerate, 0.0f, 1.0f); + }else + m_fAccelerate = -m_fBrake*0.2f; + + m_fSteeringLeftRight += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteeringLeftRight)*0.2f; + m_fSteeringLeftRight = Clamp(m_fSteeringLeftRight, -1.0f, 1.0f); + + float steeringSq = m_fSteeringLeftRight < 0.0f ? -SQR(m_fSteeringLeftRight) : SQR(m_fSteeringLeftRight); + m_fSteerAngle = pHandling->fSteeringLock * DEGTORAD(steeringSq); + m_fGasPedal = m_fAccelerate; +} + +void +CBoat::ApplyWaterResistance(void) +{ + float fwdSpeed = DotProduct(GetMoveSpeed(), GetForward()); + // TODO: figure out how this works + float resistance = 0.001f * SQR(m_fVolumeUnderWater) * m_fMass; + float magic = (SQR(fwdSpeed) + 0.05f) * resistance + 1.0f; + magic = Abs(magic); + float fx = Pow(m_vecMoveRes.x/magic, 0.5f*CTimer::GetTimeStep()); + float fy = Pow(m_vecMoveRes.y/magic, 0.5f*CTimer::GetTimeStep()); + float fz = Pow(m_vecMoveRes.z/magic, 0.5f*CTimer::GetTimeStep()); + + m_vecMoveSpeed = Multiply3x3(m_vecMoveSpeed, GetMatrix()); // invert - to local space + m_vecMoveSpeed.x *= fx; + m_vecMoveSpeed.y *= fy; + m_vecMoveSpeed.z *= fz; + float force = (fy - 1.0f) * m_vecMoveSpeed.y * m_fMass; + m_vecMoveSpeed = Multiply3x3(GetMatrix(), m_vecMoveSpeed); // back to world + + ApplyTurnForce(force*GetForward(), -GetUp()); + + if(m_vecMoveSpeed.z > 0.0f) + m_vecMoveSpeed.z *= fz; + else + m_vecMoveSpeed.z *= (1.0f - fz)*0.5f + fz; +} + +RwObject* +GetBoatAtomicObjectCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + if(RpAtomicGetFlags(atomic) & rpATOMICRENDER) + *(RpAtomic**)data = atomic; + return object; + + +} + +void +CBoat::BlowUpCar(CEntity *culprit) +{ + RpAtomic *atomic; + RwFrame *frame; + RwMatrix *matrix; + CObject *obj; + + if(!bCanBeDamaged) + return; + + // explosion pushes vehicle up + m_vecMoveSpeed.z += 0.13f; + SetStatus(STATUS_WRECKED); + bRenderScorched = true; + + m_fHealth = 0.0; + m_nBombTimer = 0; + TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z); + + if(this == FindPlayerVehicle()) + FindPlayerPed()->m_fHealth = 0.0f; // kill player + if(pDriver){ + CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION); + pDriver->SetDead(); + pDriver->FlagToDestroyWhenNextProcessed(); + } + + bEngineOn = false; + bLightsOn = false; + ChangeLawEnforcerState(false); + + CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR, GetPosition(), 0); + if(m_aBoatNodes[BOAT_MOVING] == nil) + return; + + // much like CAutomobile::SpawnFlyingComponent from here on + + atomic = nil; + RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic); + if(atomic == nil) + return; + + obj = new CObject(); + if(obj == nil) + return; + + obj->SetModelIndexNoCreate(MI_CAR_WHEEL); + + // object needs base model + obj->RefModelInfo(GetModelIndex()); + + // create new atomic + matrix = RwFrameGetLTM(m_aBoatNodes[BOAT_MOVING]); + frame = RwFrameCreate(); + atomic = RpAtomicClone(atomic); + *RwFrameGetMatrix(frame) = *matrix; + RpAtomicSetFrame(atomic, frame); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + obj->AttachToRwObject((RwObject*)atomic); + + // init object + obj->m_fMass = 10.0f; + obj->m_fTurnMass = 25.0f; + obj->m_fAirResistance = 0.99f; + obj->m_fElasticity = 0.1f; + obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f; + obj->ObjectCreatedBy = TEMP_OBJECT; + obj->SetIsStatic(false); + obj->bIsPickup = false; + + // life time + CObject::nNoTempObjects++; + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000; + + obj->m_vecMoveSpeed = m_vecMoveSpeed; + if(GetUp().z > 0.0f) + obj->m_vecMoveSpeed.z = 0.3f; + else + obj->m_vecMoveSpeed.z = 0.0f; + + obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f; + obj->m_vecTurnSpeed.x = 0.5f; + + // push component away from boat + CVector dist = obj->GetPosition() - GetPosition(); + dist.Normalise(); + if(GetUp().z > 0.0f) + dist += GetUp(); + obj->GetMatrix().GetPosition() += dist; + + CWorld::Add(obj); + + atomic = nil; + RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic); + if(atomic) + RpAtomicSetFlags(atomic, 0); +} + +RwIm3DVertex KeepWaterOutVertices[4]; +RwImVertexIndex KeepWaterOutIndices[6]; + +void +CBoat::Render() +{ + CMatrix matrix; + + if (m_aBoatNodes[BOAT_MOVING] != nil) { + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING])); + + CVector pos = matrix.GetPosition(); + matrix.SetRotateZ(m_fMovingRotation); + matrix.Translate(pos); + + matrix.UpdateRW(); + if (CVehicle::bWheelsOnlyCheat) { + RpAtomicRender((RpAtomic*)GetFirstObject(m_aBoatNodes[BOAT_MOVING])); + } + } + m_fMovingRotation += 0.05f; + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->SetVehicleColour(m_currentColour1, m_currentColour2); + if (!CVehicle::bWheelsOnlyCheat) + CEntity::Render(); +#ifdef NEW_RENDERER + if(!gbNewRenderer) +#endif + RenderWaterOutPolys(); // not separate function in III +} + +void +CBoat::RenderWaterOutPolys(void) +{ + KeepWaterOutIndices[0] = 0; + KeepWaterOutIndices[1] = 2; + KeepWaterOutIndices[2] = 1; + KeepWaterOutIndices[3] = 1; + KeepWaterOutIndices[4] = 2; + KeepWaterOutIndices[5] = 3; + RwIm3DVertexSetRGBA(&KeepWaterOutVertices[0], 255, 255, 255, 255); + RwIm3DVertexSetRGBA(&KeepWaterOutVertices[1], 255, 255, 255, 255); + RwIm3DVertexSetRGBA(&KeepWaterOutVertices[2], 255, 255, 255, 255); + RwIm3DVertexSetRGBA(&KeepWaterOutVertices[3], 255, 255, 255, 255); + switch (GetModelIndex()) { + case MI_SPEEDER: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.15f, 3.61f, 1.03f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.15f, 3.61f, 1.03f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.15f, 0.06f, 1.03f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.15f, 0.06f, 1.03f); + break; + case MI_REEFER: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.9f, 2.83f, 1.0f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.9f, 2.83f, 1.0f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.66f, -4.48f, 0.83f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.66f, -4.48f, 0.83f); + break; + case MI_PREDATOR: + default: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.45f, 1.9f, 0.96f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.45f, 1.9f, 0.96f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.45f, -3.75f, 0.96f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.45f, -3.75f, 0.96f); + break; + } + KeepWaterOutVertices[0].u = 0.0f; + KeepWaterOutVertices[0].v = 0.0f; + KeepWaterOutVertices[1].u = 1.0f; + KeepWaterOutVertices[1].v = 0.0f; + KeepWaterOutVertices[2].u = 0.0f; + KeepWaterOutVertices[2].v = 1.0f; + KeepWaterOutVertices[3].u = 1.0f; + KeepWaterOutVertices[3].v = 1.0f; +#ifdef NEW_RENDERER + if(!gbNewRenderer) +#endif +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpWaterRaster); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); +} + if (!CVehicle::bWheelsOnlyCheat && RwIm3DTransform(KeepWaterOutVertices, 4, GetMatrix().m_attachment, rwIM3D_VERTEXUV)) { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, KeepWaterOutIndices, 6); + RwIm3DEnd(); + } +#ifdef NEW_RENDERER + if(!gbNewRenderer) +#endif +{ + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +} +} + +void +CBoat::Teleport(CVector v) +{ + CWorld::Remove(this); + SetPosition(v); + SetOrientation(0.0f, 0.0f, 0.0f); + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + CWorld::Add(this); +} + +bool +CBoat::IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats) +{ + uint8 numVerts = 0; + + if ( apFrameWakeGeneratingBoats[0] == NULL ) + return false; + + for ( int32 i = 0; i < 4; i++ ) + { + CBoat *pBoat = apFrameWakeGeneratingBoats[i]; + if ( !pBoat ) + break; + + for ( int j = 0; j < pBoat->m_nNumWakePoints; j++ ) + { + float fDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[j]) * fShapeTime + float(j) * fShapeLength + fSize; + + if ( Abs(pBoat->m_avec2dWakePoints[j].x - sector.x) < fDist + && Abs(pBoat->m_avec2dWakePoints[i].y - sector.y) < fDist ) + { + apBoats[numVerts] = pBoat; + numVerts = 1; // += ? + break; + } + } + } + + return numVerts != 0; +} + +float +CBoat::IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat) +{ + for ( int i = 0; i < pBoat->m_nNumWakePoints; i++ ) + { + float fMaxDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fShapeTime + float(i) * fShapeLength; + + CVector2D vecDist = pBoat->m_avec2dWakePoints[i] - CVector2D(vecVertex); + + float fDist = vecDist.MagnitudeSqr(); + + if ( fDist < SQR(fMaxDist) ) + return 1.0f - Min(fRangeMult * Sqrt(fDist / SQR(fMaxDist)) + (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fTimeMult, 1.0f); + } + + return 0.0f; +} + +void +CBoat::SetupModelNodes() +{ + int i; + for(i = 0; i < ARRAY_SIZE(m_aBoatNodes); i++) + m_aBoatNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aBoatNodes); +} + +void +CBoat::FillBoatList() +{ + int16 frameId = 0; + + apFrameWakeGeneratingBoats[0] = nil; + apFrameWakeGeneratingBoats[1] = nil; + apFrameWakeGeneratingBoats[2] = nil; + apFrameWakeGeneratingBoats[3] = nil; + + for (int i = CPools::GetVehiclePool()->GetSize() - 1; i >= 0; i--) { + CBoat *boat = (CBoat *)(CPools::GetVehiclePool()->GetSlot(i)); + if (boat && boat->m_vehType == VEHICLE_TYPE_BOAT) { + int16 nNumWakePoints = boat->m_nNumWakePoints; + if (nNumWakePoints != 0) { + if (frameId >= ARRAY_SIZE(apFrameWakeGeneratingBoats)) { + int16 frameId2 = -1; + for (int16 j = 0; j < ARRAY_SIZE(apFrameWakeGeneratingBoats); j++) { + if (apFrameWakeGeneratingBoats[j]->m_nNumWakePoints < nNumWakePoints) { + frameId2 = j; + nNumWakePoints = apFrameWakeGeneratingBoats[j]->m_nNumWakePoints; + } + } + + if (frameId2 != -1) + apFrameWakeGeneratingBoats[frameId2] = boat; + } else { + apFrameWakeGeneratingBoats[frameId++] = boat; + } + } + } + } +} + +void +CBoat::PruneWakeTrail(void) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++){ + if(m_afWakePointLifeTime[i] <= 0.0f) + break; + if(m_afWakePointLifeTime[i] <= CTimer::GetTimeStep()){ + m_afWakePointLifeTime[i] = 0.0f; + break; + } + m_afWakePointLifeTime[i] -= CTimer::GetTimeStep(); + } + m_nNumWakePoints = i; +} + +void +CBoat::AddWakePoint(CVector point) +{ + int i; + if(m_afWakePointLifeTime[0] > 0.0f){ + if((CVector2D(GetPosition()) - m_avec2dWakePoints[0]).MagnitudeSqr() < SQR(1.0f)){ + for(i = Min(m_nNumWakePoints, ARRAY_SIZE(m_afWakePointLifeTime)-1); i != 0; i--){ + m_avec2dWakePoints[i] = m_avec2dWakePoints[i-1]; + m_afWakePointLifeTime[i] = m_afWakePointLifeTime[i-1]; + } + m_avec2dWakePoints[0] = point; + m_afWakePointLifeTime[0] = 400.0f; + if(m_nNumWakePoints < ARRAY_SIZE(m_afWakePointLifeTime)) + m_nNumWakePoints++; + } + }else{ + m_avec2dWakePoints[0] = point; + m_afWakePointLifeTime[0] = 400.0f; + m_nNumWakePoints = 1; + } +} + +#ifdef COMPATIBLE_SAVES +void +CBoat::Save(uint8*& buf) +{ + CVehicle::Save(buf); + ZeroSaveBuf(buf, 1156 - 648); +} + +void +CBoat::Load(uint8*& buf) +{ + CVehicle::Load(buf); + SkipSaveBuf(buf, 1156 - 648); +} +#endif diff --git a/src/vehicles/Boat.h b/src/vehicles/Boat.h new file mode 100644 index 0000000..157b485 --- /dev/null +++ b/src/vehicles/Boat.h @@ -0,0 +1,81 @@ +#pragma once + +#include "Vehicle.h" + +enum eBoatNodes +{ + BOAT_MOVING = 1, + BOAT_RUDDER, + BOAT_WINDSCREEN, + NUM_BOAT_NODES +}; + +class CBoat : public CVehicle +{ +public: + // 0x288 + float m_fThrustZ; + float m_fThrustY; + CVector m_vecMoveRes; + CVector m_vecTurnRes; + float m_fMovingRotation; + int32 m_boat_unused1; + RwFrame *m_aBoatNodes[NUM_BOAT_NODES]; + uint8 bBoatInWater : 1; + uint8 bPropellerInWater : 1; + bool m_bIsAnchored; + float m_fOrientation; + int32 m_boat_unused2; + float m_fDamage; + CEntity *m_pSetOnFireEntity; + bool m_boat_unused3; + float m_fAccelerate; + float m_fBrake; + float m_fSteeringLeftRight; + uint8 m_nPadID; + int32 m_boat_unused4; + float m_fVolumeUnderWater; + CVector m_vecBuoyancePoint; + float m_fPrevVolumeUnderWater; + int16 m_nDeltaVolumeUnderWater; + uint16 m_nNumWakePoints; + CVector2D m_avec2dWakePoints[32]; + float m_afWakePointLifeTime[32]; + + CBoat(int, uint8); + + virtual void SetModelIndex(uint32 id); + virtual void ProcessControl(); + virtual void Teleport(CVector v); + virtual void PreRender(void) {}; + virtual void Render(void); + virtual void ProcessControlInputs(uint8); + virtual void GetComponentWorldPosition(int32 component, CVector &pos); + virtual bool IsComponentPresent(int32 component) { return true; } + virtual void BlowUpCar(CEntity *ent); + + void RenderWaterOutPolys(void); + void ApplyWaterResistance(void); + void SetupModelNodes(); + void PruneWakeTrail(void); + void AddWakePoint(CVector point); + + static CBoat *apFrameWakeGeneratingBoats[4]; + + static bool IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats); + static float IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat); + static void FillBoatList(void); + +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + static const uint32 nSaveStructSize; + +}; + +VALIDATE_SIZE(CBoat, 0x484); + +extern float MAX_WAKE_LENGTH; +extern float MIN_WAKE_INTERVAL; +extern float WAKE_LIFETIME; \ No newline at end of file diff --git a/src/vehicles/CarGen.cpp b/src/vehicles/CarGen.cpp new file mode 100644 index 0000000..ebb767d --- /dev/null +++ b/src/vehicles/CarGen.cpp @@ -0,0 +1,271 @@ +#include "common.h" + +#include "CarGen.h" + +#include "Automobile.h" +#include "Boat.h" +#include "Camera.h" +#include "CarCtrl.h" +#include "CutsceneMgr.h" +#include "General.h" +#include "Pools.h" +#include "Streaming.h" +#include "Timer.h" +#include "Vehicle.h" +#include "World.h" +#include "SaveBuf.h" + +uint8 CTheCarGenerators::ProcessCounter; +uint32 CTheCarGenerators::NumOfCarGenerators; +CCarGenerator CTheCarGenerators::CarGeneratorArray[NUM_CARGENS]; +uint8 CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter; +uint32 CTheCarGenerators::CurrentActiveCount; + +void CCarGenerator::SwitchOff() +{ +#ifdef FIX_BUGS + if (m_nUsesRemaining != 0) +#endif + { + m_nUsesRemaining = 0; + --CTheCarGenerators::CurrentActiveCount; + } +} + +void CCarGenerator::SwitchOn() +{ + m_nUsesRemaining = UINT16_MAX; + m_nTimer = CalcNextGen(); + ++CTheCarGenerators::CurrentActiveCount; +} + +uint32 CCarGenerator::CalcNextGen() +{ + return CTimer::GetTimeInMilliseconds() + 4; +} + +void CCarGenerator::DoInternalProcessing() +{ + if (CheckForBlockage()) { + m_nTimer += 4; + if (m_nUsesRemaining == 0) + --CTheCarGenerators::CurrentActiveCount; + return; + } + if (CCarCtrl::NumParkedCars >= 10) + return; + CStreaming::RequestModel(m_nModelIndex, STREAMFLAGS_DEPENDENCY); + if (!CStreaming::HasModelLoaded(m_nModelIndex)) + return; + if (CModelInfo::IsBoatModel(m_nModelIndex)){ + CBoat* pBoat = new CBoat(m_nModelIndex, PARKED_VEHICLE); + pBoat->SetIsStatic(false); + pBoat->bEngineOn = false; + CVector pos = m_vecPos; + if (pos.z <= -100.0f) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pos.z += pBoat->GetDistanceFromCentreOfMassToBaseOfModel(); + pBoat->SetPosition(pos); + pBoat->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle)); + pBoat->SetStatus(STATUS_ABANDONED); + pBoat->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(pBoat); + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm) + pBoat->m_nAlarmState = -1; + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock) + pBoat->m_nDoorLock = CARLOCK_LOCKED; + if (m_nColor1 != -1 && m_nColor2){ + pBoat->m_currentColour1 = m_nColor1; + pBoat->m_currentColour2 = m_nColor2; + } + m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pBoat); + }else{ + bool groundFound; + CVector pos = m_vecPos; + if (pos.z > -100.0f){ + pos.z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &groundFound); + }else{ + groundFound = false; + CColPoint cp; + CEntity* pEntity; + groundFound = CWorld::ProcessVerticalLine(CVector(pos.x, pos.y, 1000.0f), -1000.0f, + cp, pEntity, true, false, false, false, false, false, nil); + if (groundFound) + pos.z = cp.point.z; + } + if (!groundFound) { + debug("CCarGenerator::DoInternalProcessing - can't find ground z for new car x = %f y = %f \n", m_vecPos.x, m_vecPos.y); + }else{ + CAutomobile* pCar; + + // So game crashes if it's bike :D + if (((CVehicleModelInfo*)CModelInfo::GetModelInfo(m_nModelIndex))->m_vehicleType != VEHICLE_TYPE_BIKE) + pCar = new CAutomobile(m_nModelIndex, PARKED_VEHICLE); + + pCar->SetIsStatic(false); + pCar->bEngineOn = false; + pos.z += pCar->GetDistanceFromCentreOfMassToBaseOfModel(); + pCar->SetPosition(pos); + pCar->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle)); + pCar->SetStatus(STATUS_ABANDONED); + pCar->bLightsOn = false; + pCar->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(pCar); + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm) + pCar->m_nAlarmState = -1; + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock) + pCar->m_nDoorLock = CARLOCK_LOCKED; + if (m_nColor1 != -1 && m_nColor2) { + pCar->m_currentColour1 = m_nColor1; + pCar->m_currentColour2 = m_nColor2; + } + m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pCar); + } + } +#ifdef FIX_BUGS + if (m_nUsesRemaining < UINT16_MAX) + --m_nUsesRemaining; +#else + if (m_nUsesRemaining < ~0) + --m_nUsesRemaining; +#endif + m_nTimer = CalcNextGen(); + if (m_nUsesRemaining == 0) + --CTheCarGenerators::CurrentActiveCount; +} + +void CCarGenerator::Process() +{ + if (m_nVehicleHandle == -1 && + (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter || CTimer::GetTimeInMilliseconds() >= m_nTimer) && + m_nUsesRemaining != 0 && CheckIfWithinRangeOfAnyPlayer()) + DoInternalProcessing(); + if (m_nVehicleHandle == -1) + return; + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(m_nVehicleHandle); + if (!pVehicle){ + m_nVehicleHandle = -1; + return; + } + if (pVehicle->GetStatus() != STATUS_PLAYER) + return; + m_nTimer += 60000; + m_nVehicleHandle = -1; + m_bIsBlocking = true; + pVehicle->bExtendedRange = false; +} + +void CCarGenerator::Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay) +{ + CMatrix m1, m2, m3; /* Unused but present on stack, so I'll leave them. */ + m_vecPos = CVector(x, y, z); + m_fAngle = angle; + m_nModelIndex = mi; + m_nColor1 = color1; + m_nColor2 = color2; + m_bForceSpawn = force; + m_nAlarm = alarm; + m_nDoorlock = lock; + m_nMinDelay = min_delay; + m_nMaxDelay = max_delay; + m_nVehicleHandle = -1; + m_nTimer = CTimer::GetTimeInMilliseconds() + 1; + m_nUsesRemaining = 0; + m_bIsBlocking = false; + m_vecInf = CModelInfo::GetColModel(m_nModelIndex)->boundingBox.min; + m_vecSup = CModelInfo::GetColModel(m_nModelIndex)->boundingBox.max; + m_fSize = Max(m_vecInf.Magnitude(), m_vecSup.Magnitude()); +} + +bool CCarGenerator::CheckForBlockage() +{ + int16 entities; + CWorld::FindObjectsKindaColliding(CVector(m_vecPos), m_fSize, 1, &entities, 2, nil, false, true, true, false, false); + return entities > 0; +} + +bool CCarGenerator::CheckIfWithinRangeOfAnyPlayer() +{ + CVector2D direction = FindPlayerCentreOfWorld(CWorld::PlayerInFocus) - m_vecPos; + float distance = direction.Magnitude(); + float farclip = 120.0f * TheCamera.GenerationDistMultiplier; + float nearclip = farclip - 20.0f; + if (distance >= farclip){ + if (m_bIsBlocking) + m_bIsBlocking = false; + return false; + } + if (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter) + return true; + if (m_bIsBlocking) + return false; + if (distance < nearclip) + return false; + return DotProduct2D(direction, FindPlayerSpeed()) <= 0; +} + +void CTheCarGenerators::Process() +{ + if (FindPlayerTrain() || CCutsceneMgr::IsCutsceneProcessing()) + return; + if (++CTheCarGenerators::ProcessCounter == 4) + CTheCarGenerators::ProcessCounter = 0; + for (uint32 i = ProcessCounter; i < NumOfCarGenerators; i += 4) + CTheCarGenerators::CarGeneratorArray[i].Process(); + if (GenerateEvenIfPlayerIsCloseCounter) + GenerateEvenIfPlayerIsCloseCounter--; +} + +int32 CTheCarGenerators::CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay) +{ + CarGeneratorArray[NumOfCarGenerators].Setup(x, y, z, angle, mi, color1, color2, force, alarm, lock, min_delay, max_delay); + return NumOfCarGenerators++; +} + +void CTheCarGenerators::Init() +{ + GenerateEvenIfPlayerIsCloseCounter = 0; + NumOfCarGenerators = 0; + ProcessCounter = 0; + CurrentActiveCount = 0; +} + +void CTheCarGenerators::SaveAllCarGenerators(uint8 *buffer, uint32 *size) +{ + const uint32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16); + *size = sizeof(int) + nGeneralDataSize + sizeof(uint32) + sizeof(CarGeneratorArray) + SAVE_HEADER_SIZE; +INITSAVEBUF + WriteSaveHeader(buffer, 'C','G','N','\0', *size - SAVE_HEADER_SIZE); + + WriteSaveBuf(buffer, nGeneralDataSize); + WriteSaveBuf(buffer, NumOfCarGenerators); + WriteSaveBuf(buffer, CurrentActiveCount); + WriteSaveBuf(buffer, ProcessCounter); + WriteSaveBuf(buffer, GenerateEvenIfPlayerIsCloseCounter); + WriteSaveBuf(buffer, (int16)0); // alignment + WriteSaveBuf(buffer, (uint32)sizeof(CarGeneratorArray)); + for (int i = 0; i < NUM_CARGENS; i++) + WriteSaveBuf(buffer, CarGeneratorArray[i]); +VALIDATESAVEBUF(*size) +} + +void CTheCarGenerators::LoadAllCarGenerators(uint8* buffer, uint32 size) +{ + const int32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16); + Init(); +INITSAVEBUF + CheckSaveHeader(buffer, 'C','G','N','\0', size - SAVE_HEADER_SIZE); + uint32 tmp; + ReadSaveBuf(&tmp, buffer); + assert(tmp == nGeneralDataSize); + ReadSaveBuf(&NumOfCarGenerators, buffer); + ReadSaveBuf(&CurrentActiveCount, buffer); + ReadSaveBuf(&ProcessCounter, buffer); + ReadSaveBuf(&GenerateEvenIfPlayerIsCloseCounter, buffer); + SkipSaveBuf(buffer, 2); + ReadSaveBuf(&tmp, buffer); + assert(tmp == sizeof(CarGeneratorArray)); + for (int i = 0; i < NUM_CARGENS; i++) + ReadSaveBuf(&CarGeneratorArray[i], buffer); +VALIDATESAVEBUF(size) +} diff --git a/src/vehicles/CarGen.h b/src/vehicles/CarGen.h new file mode 100644 index 0000000..9d64531 --- /dev/null +++ b/src/vehicles/CarGen.h @@ -0,0 +1,54 @@ +#pragma once +#include "common.h" +#include "config.h" + +enum { + CARGEN_MAXACTUALLIMIT = 100 +}; + +class CCarGenerator +{ + int32 m_nModelIndex; + CVector m_vecPos; + float m_fAngle; + int16 m_nColor1; + int16 m_nColor2; + uint8 m_bForceSpawn; + uint8 m_nAlarm; + uint8 m_nDoorlock; + int16 m_nMinDelay; + int16 m_nMaxDelay; + uint32 m_nTimer; + int32 m_nVehicleHandle; + uint16 m_nUsesRemaining; + bool m_bIsBlocking; + CVector m_vecInf; + CVector m_vecSup; + float m_fSize; +public: + void SwitchOff(); + void SwitchOn(); + uint32 CalcNextGen(); + void DoInternalProcessing(); + void Process(); + void Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay); + bool CheckForBlockage(); + bool CheckIfWithinRangeOfAnyPlayer(); + void SetUsesRemaining(uint16 uses) { m_nUsesRemaining = uses; } +}; + +class CTheCarGenerators +{ +public: + static uint8 ProcessCounter; + static uint32 NumOfCarGenerators; + static CCarGenerator CarGeneratorArray[NUM_CARGENS]; + static uint8 GenerateEvenIfPlayerIsCloseCounter; + static uint32 CurrentActiveCount; + + static void Process(); + static int32 CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay); + static void Init(); + static void SaveAllCarGenerators(uint8 *, uint32 *); + static void LoadAllCarGenerators(uint8 *, uint32); +}; diff --git a/src/vehicles/Cranes.cpp b/src/vehicles/Cranes.cpp new file mode 100644 index 0000000..177d8af --- /dev/null +++ b/src/vehicles/Cranes.cpp @@ -0,0 +1,759 @@ +#include "common.h" + +#include "Cranes.h" + +#include "Camera.h" +#include "DMAudio.h" +#include "Garages.h" +#include "General.h" +#include "Entity.h" +#include "ModelIndices.h" +#include "Replay.h" +#include "Object.h" +#include "World.h" +#include "SaveBuf.h" + +#define MAX_DISTANCE_TO_FIND_CRANE (10.0f) +#define CRANE_UPDATE_RADIUS (300.0f) +#define CRANE_MOVEMENT_PROCESSING_RADIUS (150.0f) +#define CRUSHER_Z (-0.951f) +#define MILITARY_Z (10.7862f) +#define DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE (5.0f) +#define DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT (0.5f) +#define CAR_REWARD_MILITARY_CRANE (1500) +#define CAR_MOVING_SPEED_THRESHOLD (0.01f) +#define CRANE_SLOWDOWN_MULTIPLIER (0.3f) + +#define OSCILLATION_SPEED (0.002f) +#define CAR_ROTATION_SPEED (0.0035f) +#define CRANE_MOVEMENT_SPEED (0.001f) +#define HOOK_ANGLE_MOVEMENT_SPEED (0.004f) +#define HOOK_OFFSET_MOVEMENT_SPEED (0.1f) +#define HOOK_HEIGHT_MOVEMENT_SPEED (0.06f) + +#define MESSAGE_SHOW_DURATION (4000) + +#define MAX_DISTANCE (99999.9f) +#define MIN_VALID_POSITION (-10000.0f) +#define DEFAULT_OFFSET (20.0f) + +#ifdef COMPATIBLE_SAVES +#define CRANES_SAVE_SIZE 0x400 +#else +#define CRANES_SAVE_SIZE sizeof(aCranes) +#endif + +uint32 TimerForCamInterpolation; + +uint32 CCranes::CarsCollectedMilitaryCrane; +int32 CCranes::NumCranes; +CCrane CCranes::aCranes[NUM_CRANES]; + +void CCranes::InitCranes(void) +{ + CarsCollectedMilitaryCrane = 0; + NumCranes = 0; + for (int i = 0; i < NUMSECTORS_X; i++) { + for (int j = 0; j < NUMSECTORS_Y; j++) { + for (CPtrNode* pNode = CWorld::GetSector(i, j)->m_lists[ENTITYLIST_BUILDINGS].first; pNode; pNode = pNode->next) { + CEntity* pEntity = (CEntity*)pNode->item; + if (MODELID_CRANE_1 == pEntity->GetModelIndex() || + MODELID_CRANE_2 == pEntity->GetModelIndex() || + MODELID_CRANE_3 == pEntity->GetModelIndex()) + AddThisOneCrane(pEntity); + } + } + } + for (CPtrNode* pNode = CWorld::GetBigBuildingList(LEVEL_INDUSTRIAL).first; pNode; pNode = pNode->next) { + CEntity* pEntity = (CEntity*)pNode->item; + if (MODELID_CRANE_1 == pEntity->GetModelIndex() || + MODELID_CRANE_2 == pEntity->GetModelIndex() || + MODELID_CRANE_3 == pEntity->GetModelIndex()) + AddThisOneCrane(pEntity); + } +} + +void CCranes::AddThisOneCrane(CEntity* pEntity) +{ + pEntity->GetMatrix().ResetOrientation(); + if (NumCranes >= NUM_CRANES) + return; + CCrane* pCrane = &aCranes[NumCranes]; + pCrane->Init(); + pCrane->m_pCraneEntity = (CBuilding*)pEntity; + pCrane->m_nCraneStatus = CCrane::NONE; + pCrane->m_fHookAngle = NumCranes; // lol wtf + while (pCrane->m_fHookAngle > TWOPI) + pCrane->m_fHookAngle -= TWOPI; + pCrane->m_fHookOffset = DEFAULT_OFFSET; + pCrane->m_fHookHeight = DEFAULT_OFFSET; + pCrane->m_nTimeForNextCheck = 0; + pCrane->m_nCraneState = CCrane::IDLE; + pCrane->m_bWasMilitaryCrane = false; + pCrane->m_nAudioEntity = DMAudio.CreateEntity(AUDIOTYPE_CRANE, &aCranes[NumCranes]); + if (pCrane->m_nAudioEntity >= 0) + DMAudio.SetEntityStatus(pCrane->m_nAudioEntity, TRUE); + pCrane->m_bIsTop = (MODELID_CRANE_1 != pEntity->GetModelIndex()); + // Is this used to avoid military crane? + if (pCrane->m_bIsTop || pEntity->GetPosition().y > 0.0f) { + CObject* pHook = new CObject(MI_MAGNET, false); + pHook->ObjectCreatedBy = MISSION_OBJECT; + pHook->bUsesCollision = false; + pHook->bExplosionProof = true; + pHook->bAffectedByGravity = false; + pCrane->m_pHook = pHook; + pCrane->CalcHookCoordinates(&pCrane->m_vecHookCurPos.x, &pCrane->m_vecHookCurPos.y, &pCrane->m_vecHookCurPos.z); + pCrane->SetHookMatrix(); + } + else + pCrane->m_pHook = nil; + NumCranes++; +} + +void CCranes::ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY) +{ + float fMinDistance = MAX_DISTANCE; + float X = fPosX, Y = fPosY; + if (X <= MIN_VALID_POSITION || Y <= MIN_VALID_POSITION) { + X = fDropOffX; + Y = fDropOffY; + } + int index = 0; + for (int i = 0; i < NumCranes; i++) { + float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude(); + if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) { + fMinDistance = distance; + index = i; + } + } +#ifdef FIX_BUGS // classic + if (fMinDistance == MAX_DISTANCE) + return; +#endif + CCrane* pCrane = &aCranes[index]; + pCrane->m_fPickupX1 = fInfX; + pCrane->m_fPickupX2 = fSupX; + pCrane->m_fPickupY1 = fInfY; + pCrane->m_fPickupY2 = fSupY; + pCrane->m_vecDropoffTarget.x = fDropOffX; + pCrane->m_vecDropoffTarget.y = fDropOffY; + pCrane->m_vecDropoffTarget.z = fDropOffZ; + pCrane->m_nCraneStatus = CCrane::ACTIVATED; + pCrane->m_pVehiclePickedUp = nil; + pCrane->m_nVehiclesCollected = 0; + pCrane->m_fDropoffHeading = fHeading; + pCrane->m_bIsCrusher = bIsCrusher; + pCrane->m_bIsMilitaryCrane = bIsMilitary; + bool military = true; + if (!bIsMilitary && !pCrane->m_bWasMilitaryCrane) + military = false; + pCrane->m_bWasMilitaryCrane = military; + pCrane->m_nTimeForNextCheck = 0; + pCrane->m_nCraneState = CCrane::IDLE; + float Z; + if (bIsCrusher) + Z = CRUSHER_Z; + else if (bIsMilitary) + Z = MILITARY_Z; + else + Z = CWorld::FindGroundZForCoord((fInfX + fSupX) / 2, (fInfY + fSupY) / 2); + pCrane->FindParametersForTarget((fInfX + fSupX) / 2, (fInfY + fSupY) / 2, Z, &pCrane->m_fPickupAngle, &pCrane->m_fPickupDistance, &pCrane->m_fPickupHeight); + pCrane->FindParametersForTarget(fDropOffX, fDropOffY, fDropOffZ, &pCrane->m_fDropoffAngle, &pCrane->m_fDropoffDistance, &pCrane->m_fDropoffHeight); +} + +void CCranes::DeActivateCrane(float X, float Y) +{ + float fMinDistance = MAX_DISTANCE; + int index = 0; + for (int i = 0; i < NumCranes; i++) { + float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude(); + if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) { + fMinDistance = distance; + index = i; + } + } +#ifdef FIX_BUGS // classic + if (fMinDistance == MAX_DISTANCE) + return; +#endif + aCranes[index].m_nCraneStatus = CCrane::DEACTIVATED; + aCranes[index].m_nCraneState = CCrane::IDLE; +} + +bool CCranes::IsThisCarPickedUp(float X, float Y, CVehicle* pVehicle) +{ + int index = 0; + bool result = false; + for (int i = 0; i < NumCranes; i++) { + float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude(); + if (distance < MAX_DISTANCE_TO_FIND_CRANE && aCranes[i].m_pVehiclePickedUp == pVehicle) { + if (aCranes[i].m_nCraneState == CCrane::LIFTING_TARGET || aCranes[i].m_nCraneState == CCrane::ROTATING_TARGET) + result = true; + } + } + return result; +} + +void CCranes::UpdateCranes(void) +{ + for (int i = 0; i < NumCranes; i++) { + if (aCranes[i].m_bIsTop || aCranes[i].m_bIsCrusher || + (TheCamera.GetPosition().x + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().x && + TheCamera.GetPosition().x - CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().x && + TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().y && + TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().y)) + aCranes[i].Update(); + } +} + +void CCrane::Update(void) +{ + if (CReplay::IsPlayingBack()) + return; + if (((m_nCraneStatus == ACTIVATED || m_nCraneStatus == DEACTIVATED) && + Abs(TheCamera.GetGameCamPosition().x - m_pCraneEntity->GetPosition().x) < CRANE_MOVEMENT_PROCESSING_RADIUS && + Abs(TheCamera.GetGameCamPosition().y - m_pCraneEntity->GetPosition().y) < CRANE_MOVEMENT_PROCESSING_RADIUS) || + m_nCraneState != IDLE) { + switch (m_nCraneState) { + case IDLE: + if (GoTowardsTarget(m_fPickupAngle, m_fPickupDistance, GetHeightToPickup()) && + CTimer::GetTimeInMilliseconds() > m_nTimeForNextCheck) { + CWorld::AdvanceCurrentScanCode(); +#ifdef FIX_BUGS + int xstart = Max(0, CWorld::GetSectorIndexX(m_fPickupX1)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(m_fPickupX2)); + int ystart = Max(0, CWorld::GetSectorIndexY(m_fPickupY1)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(m_fPickupY2)); +#else + int xstart = CWorld::GetSectorIndexX(m_fPickupX1); + int xend = CWorld::GetSectorIndexX(m_fPickupX2); + int ystart = CWorld::GetSectorIndexY(m_fPickupY1); + int yend = CWorld::GetSectorIndexY(m_fPickupY1); +#endif + assert(xstart <= xend); + assert(ystart <= yend); + for (int i = xstart; i <= xend; i++) { + for (int j = ystart; j <= yend; j++) { + FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES]); + FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES_OVERLAP]); + } + } + } + break; + case GOING_TOWARDS_TARGET: + if (m_pVehiclePickedUp){ + if (m_pVehiclePickedUp->GetPosition().x < m_fPickupX1 || + m_pVehiclePickedUp->GetPosition().x > m_fPickupX2 || + m_pVehiclePickedUp->GetPosition().y < m_fPickupY1 || + m_pVehiclePickedUp->GetPosition().y > m_fPickupY2 || + m_pVehiclePickedUp->pDriver || + Abs(m_pVehiclePickedUp->GetMoveSpeed().x) > CAR_MOVING_SPEED_THRESHOLD || + Abs(m_pVehiclePickedUp->GetMoveSpeed().y) > CAR_MOVING_SPEED_THRESHOLD || + Abs(m_pVehiclePickedUp->GetMoveSpeed().z) > CAR_MOVING_SPEED_THRESHOLD || + (FindPlayerPed()->GetPedState() == PED_ENTER_CAR +#ifdef FIX_BUGS + || FindPlayerPed()->GetPedState() == PED_CARJACK +#endif + ) && FindPlayerPed()->m_pSeekTarget == m_pVehiclePickedUp) { + m_pVehiclePickedUp = nil; + m_nCraneState = IDLE; + } + else { + float fAngle, fOffset, fHeight; + FindParametersForTarget( + m_pVehiclePickedUp->GetPosition().x, + m_pVehiclePickedUp->GetPosition().y, + m_pVehiclePickedUp->GetPosition().z + m_pVehiclePickedUp->GetColModel()->boundingBox.max.z, + &fAngle, &fOffset, &fHeight); + if (GoTowardsTarget(fAngle, fOffset, fHeight)) { + CVector distance = m_pVehiclePickedUp->GetPosition() - m_vecHookCurPos; + distance.z += m_pVehiclePickedUp->GetColModel()->boundingBox.max.z; + if (distance.MagnitudeSqr() < SQR(DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT)) { + m_nCraneState = GOING_TOWARDS_TARGET_ONLY_HEIGHT; + m_vecHookVelocity *= 0.4f; + m_pVehiclePickedUp->bLightsOn = false; + m_pVehiclePickedUp->bUsesCollision = false; + if (m_bIsCrusher) + m_pVehiclePickedUp->bCollisionProof = true; + DMAudio.PlayOneShot(m_nAudioEntity, SOUND_CRANE_PICKUP, 0.0f); + } + } + } + } + else + m_nCraneState = IDLE; + break; + case LIFTING_TARGET: + RotateCarriedCarProperly(); + if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoff(), CRANE_SLOWDOWN_MULTIPLIER)) + m_nCraneState = ROTATING_TARGET; + if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) { + m_pVehiclePickedUp = nil; + m_nCraneState = IDLE; + } + break; + case GOING_TOWARDS_TARGET_ONLY_HEIGHT: + RotateCarriedCarProperly(); + if (GoTowardsHeightTarget(GetHeightToPickupHeight(), CRANE_SLOWDOWN_MULTIPLIER)) + m_nCraneState = LIFTING_TARGET; + TimerForCamInterpolation = CTimer::GetTimeInMilliseconds(); + if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) { + m_pVehiclePickedUp = nil; + m_nCraneState = IDLE; + } + break; + case ROTATING_TARGET: + { + bool bRotateFinished = RotateCarriedCarProperly(); + bool bMovementFinished = GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, m_fDropoffHeight, 0.3f); + if (bMovementFinished && bRotateFinished) { + float fDistanceFromPlayer = m_pVehiclePickedUp ? ((CVector2D)FindPlayerCoors() - (CVector2D)m_pVehiclePickedUp->GetPosition()).Magnitude() : 0.0f; + if (fDistanceFromPlayer > DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE || !m_bWasMilitaryCrane) { + m_nCraneState = DROPPING_TARGET; + if (m_pVehiclePickedUp) { + m_pVehiclePickedUp->bUsesCollision = true; + m_pVehiclePickedUp->m_nStaticFrames = 0; + ++m_nVehiclesCollected; + if (m_bIsMilitaryCrane) { + CCranes::RegisterCarForMilitaryCrane(m_pVehiclePickedUp->GetModelIndex()); + if (!CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()) { + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += CAR_REWARD_MILITARY_CRANE; + CGarages::TriggerMessage("GA_10", CAR_REWARD_MILITARY_CRANE, MESSAGE_SHOW_DURATION, -1); + } + CWorld::Remove(m_pVehiclePickedUp); + delete m_pVehiclePickedUp; + } + } + m_pVehiclePickedUp = nil; + } + } + break; + } + case DROPPING_TARGET: + if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoffHeight(), CRANE_SLOWDOWN_MULTIPLIER)) { + m_nCraneState = IDLE; + m_nTimeForNextCheck = CTimer::GetTimeInMilliseconds() + 10000; + } + break; + default: + break; + } + CVector vecHook; + CalcHookCoordinates(&vecHook.x, &vecHook.y, &vecHook.z); + m_vecHookVelocity += ((CVector2D)vecHook - (CVector2D)m_vecHookCurPos) * CTimer::GetTimeStep() * CRANE_MOVEMENT_SPEED; + m_vecHookVelocity *= Pow(0.98f, CTimer::GetTimeStep()); + m_vecHookCurPos.x += m_vecHookVelocity.x * CTimer::GetTimeStep(); + m_vecHookCurPos.y += m_vecHookVelocity.y * CTimer::GetTimeStep(); + m_vecHookCurPos.z = vecHook.z; + switch (m_nCraneState) { + case LIFTING_TARGET: + case GOING_TOWARDS_TARGET_ONLY_HEIGHT: + case ROTATING_TARGET: + if (m_pVehiclePickedUp) { + m_pVehiclePickedUp->SetPosition(m_vecHookCurPos.x, m_vecHookCurPos.y, m_vecHookCurPos.z - m_pVehiclePickedUp->GetColModel()->boundingBox.max.z); + m_pVehiclePickedUp->SetMoveSpeed(0.0f, 0.0f, 0.0f); + CVector up(vecHook.x - m_vecHookCurPos.x, vecHook.y - m_vecHookCurPos.y, 20.0f); + up.Normalise(); + m_pVehiclePickedUp->GetRight() = CrossProduct(m_pVehiclePickedUp->GetForward(), up); + m_pVehiclePickedUp->GetForward() = CrossProduct(up, m_pVehiclePickedUp->GetRight()); + m_pVehiclePickedUp->GetUp() = up; + } + break; + default: + break; + } + } + else { + int16 rnd = (m_pCraneEntity->m_randomSeed + (CTimer::GetTimeInMilliseconds() >> 11)) & 0xF; + // 16 options, lasting 2048 ms each + // a bit awkward: why there are 4 periods for -= and 6 for +=? is it a bug? + if (rnd < 4) { + m_fHookAngle -= OSCILLATION_SPEED * CTimer::GetTimeStep(); + if (m_fHookAngle < 0.0f) + m_fHookAngle += TWOPI; + } + else if (rnd > 5 && rnd < 12) { + m_fHookAngle += OSCILLATION_SPEED * CTimer::GetTimeStep(); + if (m_fHookAngle > TWOPI) + m_fHookAngle -= TWOPI; + } + CalcHookCoordinates(&m_vecHookCurPos.x, &m_vecHookCurPos.y, &m_vecHookCurPos.z); + m_vecHookVelocity.x = m_vecHookVelocity.y = 0.0f; + } + float fCos = Cos(m_fHookAngle); + float fSin = Sin(m_fHookAngle); + m_pCraneEntity->GetRight().x = fCos; + m_pCraneEntity->GetForward().y = fCos; + m_pCraneEntity->GetRight().y = fSin; + m_pCraneEntity->GetForward().x = -fSin; + m_pCraneEntity->GetMatrix().UpdateRW(); + m_pCraneEntity->UpdateRwFrame(); + SetHookMatrix(); +} + +bool CCrane::RotateCarriedCarProperly() +{ + if (m_fDropoffHeading <= 0.0f) + return true; + if (!m_pVehiclePickedUp) + return true; + float fAngleDelta = m_fDropoffHeading - CGeneral::GetATanOfXY(m_pVehiclePickedUp->GetForward().x, m_pVehiclePickedUp->GetForward().y); + while (fAngleDelta < -HALFPI) + fAngleDelta += PI; + while (fAngleDelta > HALFPI) + fAngleDelta -= PI; + float fDeltaThisFrame = CAR_ROTATION_SPEED * CTimer::GetTimeStep(); + if (Abs(fAngleDelta) <= fDeltaThisFrame) // no rotation is actually applied? + return true; + m_pVehiclePickedUp->GetMatrix().RotateZ(fAngleDelta < 0 ? -fDeltaThisFrame : fDeltaThisFrame); + return false; +} + +void CCrane::FindCarInSectorList(CPtrList* pList) +{ + CPtrNode* node; + for (node = pList->first; node; node = node->next) { + CVehicle* pVehicle = (CVehicle*)node->item; + if (pVehicle->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + pVehicle->m_scanCode = CWorld::GetCurrentScanCode(); + if (pVehicle->GetPosition().x < m_fPickupX1 || pVehicle->GetPosition().x > m_fPickupX2 || + pVehicle->GetPosition().y < m_fPickupY1 || pVehicle->GetPosition().y > m_fPickupY2) + continue; + if (pVehicle->pDriver) + continue; + if (Abs(pVehicle->GetMoveSpeed().x) >= CAR_MOVING_SPEED_THRESHOLD || + Abs(pVehicle->GetMoveSpeed().y) >= CAR_MOVING_SPEED_THRESHOLD || + Abs(pVehicle->GetMoveSpeed().z) >= CAR_MOVING_SPEED_THRESHOLD) + continue; + if (!pVehicle->IsCar() || pVehicle->GetStatus() == STATUS_WRECKED || pVehicle->m_fHealth < 250.0f) + continue; + if (!DoesCranePickUpThisCarType(pVehicle->GetModelIndex()) || + m_bIsMilitaryCrane && CCranes::DoesMilitaryCraneHaveThisOneAlready(pVehicle->GetModelIndex())) { + if (!pVehicle->bCraneMessageDone) { + pVehicle->bCraneMessageDone = true; + if (!m_bIsMilitaryCrane) + CGarages::TriggerMessage("CR_1", -1, MESSAGE_SHOW_DURATION, -1); // Crane cannot lift this vehicle. + else if (DoesCranePickUpThisCarType(pVehicle->GetModelIndex())) + CGarages::TriggerMessage("GA_20", -1, MESSAGE_SHOW_DURATION, -1); // We got more of these than we can shift. Sorry man, no deal. + else + CGarages::TriggerMessage("GA_19", -1, MESSAGE_SHOW_DURATION, -1); // We're not interested in that model. + } + } + else { + m_pVehiclePickedUp = pVehicle; + pVehicle->RegisterReference((CEntity**)&m_pVehiclePickedUp); + m_nCraneState = GOING_TOWARDS_TARGET; + } + } +} + +bool CCrane::DoesCranePickUpThisCarType(uint32 mi) +{ + if (m_bIsCrusher) { + return mi != MI_FIRETRUCK && + mi != MI_TRASH && +#ifdef FIX_BUGS + mi != MI_COACH && +#else + mi != MI_BLISTA && +#endif + mi != MI_SECURICA && + mi != MI_BUS && + mi != MI_DODO && + mi != MI_RHINO; + } + if (m_bIsMilitaryCrane) { + return mi == MI_FIRETRUCK || + mi == MI_AMBULAN || + mi == MI_ENFORCER || + mi == MI_FBICAR || + mi == MI_RHINO || + mi == MI_BARRACKS || + mi == MI_POLICE; + } + return true; +} + +bool CCranes::DoesMilitaryCraneHaveThisOneAlready(uint32 mi) +{ + switch (mi) { + case MI_FIRETRUCK: return (CarsCollectedMilitaryCrane & 1); + case MI_AMBULAN: return (CarsCollectedMilitaryCrane & 2); + case MI_ENFORCER: return (CarsCollectedMilitaryCrane & 4); + case MI_FBICAR: return (CarsCollectedMilitaryCrane & 8); + case MI_RHINO: return (CarsCollectedMilitaryCrane & 0x10); + case MI_BARRACKS: return (CarsCollectedMilitaryCrane & 0x20); + case MI_POLICE: return (CarsCollectedMilitaryCrane & 0x40); + default: break; + } + return false; +} + +void CCranes::RegisterCarForMilitaryCrane(uint32 mi) +{ + switch (mi) { + case MI_FIRETRUCK: CarsCollectedMilitaryCrane |= 1; break; + case MI_AMBULAN: CarsCollectedMilitaryCrane |= 2; break; + case MI_ENFORCER: CarsCollectedMilitaryCrane |= 4; break; + case MI_FBICAR: CarsCollectedMilitaryCrane |= 8; break; + case MI_RHINO: CarsCollectedMilitaryCrane |= 0x10; break; + case MI_BARRACKS: CarsCollectedMilitaryCrane |= 0x20; break; + case MI_POLICE: CarsCollectedMilitaryCrane |= 0x40; break; + default: break; + } +} + +bool CCranes::HaveAllCarsBeenCollectedByMilitaryCrane() +{ + return (CarsCollectedMilitaryCrane & 0x7F) == 0x7F; +} + +bool CCrane::GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier) +{ + bool bAngleMovementFinished, bOffsetMovementFinished, bHeightMovementFinished; + float fHookAngleDelta = fAngleToTarget - m_fHookAngle; + while (fHookAngleDelta > PI) + fHookAngleDelta -= TWOPI; + while (fHookAngleDelta < -PI) + fHookAngleDelta += TWOPI; + float fHookAngleChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_ANGLE_MOVEMENT_SPEED; + if (Abs(fHookAngleDelta) < fHookAngleChangeThisFrame) { + m_fHookAngle = fAngleToTarget; + bAngleMovementFinished = true; + } else { + if (fHookAngleDelta < 0.0f) { + m_fHookAngle -= fHookAngleChangeThisFrame; + if (m_fHookAngle < 0.0f) + m_fHookAngle += TWOPI; + } + else { + m_fHookAngle += fHookAngleChangeThisFrame; + if (m_fHookAngle > TWOPI) + m_fHookAngle -= TWOPI; + } + bAngleMovementFinished = false; + } + float fHookOffsetDelta = fDistanceToTarget - m_fHookOffset; + float fHookOffsetChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_OFFSET_MOVEMENT_SPEED; + if (Abs(fHookOffsetDelta) < fHookOffsetChangeThisFrame) { + m_fHookOffset = fDistanceToTarget; + bOffsetMovementFinished = true; + } else { + if (fHookOffsetDelta < 0.0f) + m_fHookOffset -= fHookOffsetChangeThisFrame; + else + m_fHookOffset += fHookOffsetChangeThisFrame; + bOffsetMovementFinished = false; + } + float fHookHeightDelta = fTargetHeight - m_fHookHeight; + float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED; + if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) { + m_fHookHeight = fTargetHeight; + bHeightMovementFinished = true; + } else { + if (fHookHeightDelta < 0.0f) + m_fHookHeight -= fHookHeightChangeThisFrame; + else + m_fHookHeight += fHookHeightChangeThisFrame; + bHeightMovementFinished = false; + } + return bAngleMovementFinished && bOffsetMovementFinished && bHeightMovementFinished; +} + +bool CCrane::GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier) +{ + bool bHeightMovementFinished; + float fHookHeightDelta = fTargetHeight - m_fHookHeight; + float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED; + if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) { + m_fHookHeight = fTargetHeight; + bHeightMovementFinished = true; + } else { + if (fHookHeightDelta < 0.0f) + m_fHookHeight -= fHookHeightChangeThisFrame; + else + m_fHookHeight += fHookHeightChangeThisFrame; + bHeightMovementFinished = false; + } + return bHeightMovementFinished; +} + +void CCrane::FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight) +{ + *pAngle = CGeneral::GetATanOfXY(X - m_pCraneEntity->GetPosition().x, Y - m_pCraneEntity->GetPosition().y); + *pDistance = ((CVector2D(X, Y) - (CVector2D)m_pCraneEntity->GetPosition())).Magnitude(); + *pHeight = Z; +} + +void CCrane::CalcHookCoordinates(float* pX, float* pY, float* pZ) +{ + *pX = Cos(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().x; + *pY = Sin(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().y; + *pZ = m_fHookHeight; +} + +void CCrane::SetHookMatrix() +{ + if (m_pHook == nil) + return; + m_pHook->SetPosition(m_vecHookCurPos); + CVector up(m_vecHookInitPos.x - m_vecHookCurPos.x, m_vecHookInitPos.y - m_vecHookCurPos.y, 20.0f); + up.Normalise(); + m_pHook->GetRight() = CrossProduct(CVector(0.0f, 1.0f, 0.0f), up); + m_pHook->GetForward() = CrossProduct(up, m_pHook->GetRight()); + m_pHook->GetUp() = up; + m_pHook->SetOrientation(0.0f, 0.0f, -HALFPI); + m_pHook->GetMatrix().UpdateRW(); + m_pHook->UpdateRwFrame(); + CWorld::Remove(m_pHook); + CWorld::Add(m_pHook); +} + +bool CCranes::IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle) +{ + for (int i = 0; i < NumCranes; i++) { + if (pVehicle == aCranes[i].m_pVehiclePickedUp) { + switch (aCranes[i].m_nCraneState) { + case CCrane::GOING_TOWARDS_TARGET_ONLY_HEIGHT: + case CCrane::LIFTING_TARGET: + case CCrane::ROTATING_TARGET: + return true; + default: + break; + } + } + } + return false; +} + +bool CCranes::IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle) +{ + for (int i = 0; i < NumCranes; i++) { + if (pVehicle == aCranes[i].m_pVehiclePickedUp) + return true; + } + return false; +} + +void CCranes::Save(uint8* buf, uint32* size) +{ + INITSAVEBUF + + *size = 2 * sizeof(uint32) + CRANES_SAVE_SIZE; + WriteSaveBuf(buf, NumCranes); + WriteSaveBuf(buf, CarsCollectedMilitaryCrane); + for (int i = 0; i < NUM_CRANES; i++) { +#ifdef COMPATIBLE_SAVES + int32 tmp = aCranes[i].m_pCraneEntity != nil ? CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(aCranes[i].m_pCraneEntity) + 1 : 0; + WriteSaveBuf(buf, tmp); + tmp = aCranes[i].m_pHook != nil ? CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(aCranes[i].m_pHook) + 1 : 0; + WriteSaveBuf(buf, tmp); + WriteSaveBuf(buf, aCranes[i].m_nAudioEntity); + WriteSaveBuf(buf, aCranes[i].m_fPickupX1); + WriteSaveBuf(buf, aCranes[i].m_fPickupX2); + WriteSaveBuf(buf, aCranes[i].m_fPickupY1); + WriteSaveBuf(buf, aCranes[i].m_fPickupY2); + WriteSaveBuf(buf, aCranes[i].m_vecDropoffTarget); + WriteSaveBuf(buf, aCranes[i].m_fDropoffHeading); + WriteSaveBuf(buf, aCranes[i].m_fPickupAngle); + WriteSaveBuf(buf, aCranes[i].m_fDropoffAngle); + WriteSaveBuf(buf, aCranes[i].m_fPickupDistance); + WriteSaveBuf(buf, aCranes[i].m_fDropoffDistance); + WriteSaveBuf(buf, aCranes[i].m_fPickupHeight); + WriteSaveBuf(buf, aCranes[i].m_fDropoffHeight); + WriteSaveBuf(buf, aCranes[i].m_fHookAngle); + WriteSaveBuf(buf, aCranes[i].m_fHookOffset); + WriteSaveBuf(buf, aCranes[i].m_fHookHeight); + WriteSaveBuf(buf, aCranes[i].m_vecHookInitPos); + WriteSaveBuf(buf, aCranes[i].m_vecHookCurPos); + WriteSaveBuf(buf, aCranes[i].m_vecHookVelocity); + tmp = aCranes[i].m_pVehiclePickedUp != nil ? CPools::GetVehiclePool()->GetJustIndex_NoFreeAssert(aCranes[i].m_pVehiclePickedUp) + 1 : 0; + WriteSaveBuf(buf, tmp); + WriteSaveBuf(buf, aCranes[i].m_nTimeForNextCheck); + WriteSaveBuf(buf, aCranes[i].m_nCraneStatus); + WriteSaveBuf(buf, aCranes[i].m_nCraneState); + WriteSaveBuf(buf, aCranes[i].m_nVehiclesCollected); + WriteSaveBuf(buf, aCranes[i].m_bIsCrusher); + WriteSaveBuf(buf, aCranes[i].m_bIsMilitaryCrane); + WriteSaveBuf(buf, aCranes[i].m_bWasMilitaryCrane); + WriteSaveBuf(buf, aCranes[i].m_bIsTop); + ZeroSaveBuf(buf, 1); +#else + CCrane *pCrane = WriteSaveBuf(buf, aCranes[i]); + if (pCrane->m_pCraneEntity != nil) + pCrane->m_pCraneEntity = (CBuilding*)(CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(pCrane->m_pCraneEntity) + 1); + if (pCrane->m_pHook != nil) + pCrane->m_pHook = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(pCrane->m_pHook) + 1); + if (pCrane->m_pVehiclePickedUp != nil) + pCrane->m_pVehiclePickedUp = (CVehicle*)(CPools::GetVehiclePool()->GetJustIndex_NoFreeAssert(pCrane->m_pVehiclePickedUp) + 1); +#endif + } + + VALIDATESAVEBUF(*size); +} + +void CCranes::Load(uint8* buf, uint32 size) +{ + INITSAVEBUF + + ReadSaveBuf(&NumCranes, buf); + ReadSaveBuf(&CarsCollectedMilitaryCrane, buf); + for (int i = 0; i < NUM_CRANES; i++) { +#ifdef COMPATIBLE_SAVES + int32 tmp; + ReadSaveBuf(&tmp, buf); + aCranes[i].m_pCraneEntity = tmp != 0 ? CPools::GetBuildingPool()->GetSlot(tmp - 1) : nil; + ReadSaveBuf(&tmp, buf); + aCranes[i].m_pHook = tmp != 0 ? CPools::GetObjectPool()->GetSlot(tmp - 1) : nil; + ReadSaveBuf(&aCranes[i].m_nAudioEntity, buf); + ReadSaveBuf(&aCranes[i].m_fPickupX1, buf); + ReadSaveBuf(&aCranes[i].m_fPickupX2, buf); + ReadSaveBuf(&aCranes[i].m_fPickupY1, buf); + ReadSaveBuf(&aCranes[i].m_fPickupY2, buf); + ReadSaveBuf(&aCranes[i].m_vecDropoffTarget, buf); + ReadSaveBuf(&aCranes[i].m_fDropoffHeading, buf); + ReadSaveBuf(&aCranes[i].m_fPickupAngle, buf); + ReadSaveBuf(&aCranes[i].m_fDropoffAngle, buf); + ReadSaveBuf(&aCranes[i].m_fPickupDistance, buf); + ReadSaveBuf(&aCranes[i].m_fDropoffDistance, buf); + ReadSaveBuf(&aCranes[i].m_fPickupHeight, buf); + ReadSaveBuf(&aCranes[i].m_fDropoffHeight, buf); + ReadSaveBuf(&aCranes[i].m_fHookAngle, buf); + ReadSaveBuf(&aCranes[i].m_fHookOffset, buf); + ReadSaveBuf(&aCranes[i].m_fHookHeight, buf); + ReadSaveBuf(&aCranes[i].m_vecHookInitPos, buf); + ReadSaveBuf(&aCranes[i].m_vecHookCurPos, buf); + ReadSaveBuf(&aCranes[i].m_vecHookVelocity, buf); + ReadSaveBuf(&tmp, buf); + aCranes[i].m_pVehiclePickedUp = tmp != 0 ? CPools::GetVehiclePool()->GetSlot(tmp - 1) : nil; + ReadSaveBuf(&aCranes[i].m_nTimeForNextCheck, buf); + ReadSaveBuf(&aCranes[i].m_nCraneStatus, buf); + ReadSaveBuf(&aCranes[i].m_nCraneState, buf); + ReadSaveBuf(&aCranes[i].m_nVehiclesCollected, buf); + ReadSaveBuf(&aCranes[i].m_bIsCrusher, buf); + ReadSaveBuf(&aCranes[i].m_bIsMilitaryCrane, buf); + ReadSaveBuf(&aCranes[i].m_bWasMilitaryCrane, buf); + ReadSaveBuf(&aCranes[i].m_bIsTop, buf); + SkipSaveBuf(buf, 1); +#else + ReadSaveBuf(&aCranes[i], buf); + } + for (int i = 0; i < NUM_CRANES; i++) { + CCrane *pCrane = &aCranes[i]; + if (pCrane->m_pCraneEntity != nil) + pCrane->m_pCraneEntity = CPools::GetBuildingPool()->GetSlot((uintptr)pCrane->m_pCraneEntity - 1); + if (pCrane->m_pHook != nil) + pCrane->m_pHook = CPools::GetObjectPool()->GetSlot((uintptr)pCrane->m_pHook - 1); + if (pCrane->m_pVehiclePickedUp != nil) + pCrane->m_pVehiclePickedUp = CPools::GetVehiclePool()->GetSlot((uintptr)pCrane->m_pVehiclePickedUp - 1); +#endif + } + for (int i = 0; i < NUM_CRANES; i++) { + aCranes[i].m_nAudioEntity = DMAudio.CreateEntity(AUDIOTYPE_CRANE, &aCranes[i]); + if (aCranes[i].m_nAudioEntity != 0) + DMAudio.SetEntityStatus(aCranes[i].m_nAudioEntity, TRUE); + } + + VALIDATESAVEBUF(size); +} diff --git a/src/vehicles/Cranes.h b/src/vehicles/Cranes.h new file mode 100644 index 0000000..e917810 --- /dev/null +++ b/src/vehicles/Cranes.h @@ -0,0 +1,97 @@ +#pragma once +#include "common.h" + +#include "World.h" + +class CVehicle; +class CEntity; +class CObject; +class CBuilding; + +class CCrane +{ +public: + enum CraneState { + IDLE = 0, + GOING_TOWARDS_TARGET = 1, + LIFTING_TARGET = 2, + GOING_TOWARDS_TARGET_ONLY_HEIGHT = 3, + ROTATING_TARGET = 4, + DROPPING_TARGET = 5 + }; + enum CraneStatus { + NONE = 0, + ACTIVATED = 1, + DEACTIVATED = 2 + }; + CBuilding *m_pCraneEntity; + CObject *m_pHook; + int32 m_nAudioEntity; + float m_fPickupX1; + float m_fPickupX2; + float m_fPickupY1; + float m_fPickupY2; + CVector m_vecDropoffTarget; + float m_fDropoffHeading; + float m_fPickupAngle; + float m_fDropoffAngle; + float m_fPickupDistance; + float m_fDropoffDistance; + float m_fPickupHeight; + float m_fDropoffHeight; + float m_fHookAngle; + float m_fHookOffset; + float m_fHookHeight; + CVector m_vecHookInitPos; + CVector m_vecHookCurPos; + CVector2D m_vecHookVelocity; + CVehicle *m_pVehiclePickedUp; + uint32 m_nTimeForNextCheck; + uint8 m_nCraneStatus; + uint8 m_nCraneState; + uint8 m_nVehiclesCollected; + bool m_bIsCrusher; + bool m_bIsMilitaryCrane; + bool m_bWasMilitaryCrane; + bool m_bIsTop; + + void Init(void) { memset(this, 0, sizeof(*this)); } + void Update(void); + bool RotateCarriedCarProperly(void); + void FindCarInSectorList(CPtrList* pList); + bool DoesCranePickUpThisCarType(uint32 mi); + bool GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier = 1.0f); + bool GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier = 1.0f); + void FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight); + void CalcHookCoordinates(float* pX, float* pY, float* pZ); + void SetHookMatrix(void); + + float GetHeightToPickup() { return 4.0f + m_fPickupHeight + (m_bIsCrusher ? 4.5f : 0.0f); }; + float GetHeightToDropoff() { return m_bIsCrusher ? (2.0f + m_fDropoffHeight + 3.0f) : (2.0f + m_fDropoffHeight); } + float GetHeightToPickupHeight() { return m_fPickupHeight + (m_bIsCrusher ? 7.0f : 4.0f); } + float GetHeightToDropoffHeight() { return m_fDropoffHeight + (m_bIsCrusher ? 7.0f : 2.0f); } +}; + +VALIDATE_SIZE(CCrane, 128); + +class CCranes +{ +public: + static void InitCranes(void); + static void AddThisOneCrane(CEntity* pCraneEntity); + static void ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY); + static void DeActivateCrane(float fX, float fY); + static bool IsThisCarPickedUp(float fX, float fY, CVehicle* pVehicle); + static void UpdateCranes(void); + static bool DoesMilitaryCraneHaveThisOneAlready(uint32 mi); + static void RegisterCarForMilitaryCrane(uint32 mi); + static bool HaveAllCarsBeenCollectedByMilitaryCrane(void); + static bool IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle); + static bool IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle); + static void Save(uint8* buf, uint32* size); + static void Load(uint8* buf, uint32 size); // out of class in III PC and later because of SecuROM + + static uint32 CarsCollectedMilitaryCrane; + static int32 NumCranes; + static CCrane aCranes[NUM_CRANES]; +}; diff --git a/src/vehicles/DamageManager.cpp b/src/vehicles/DamageManager.cpp new file mode 100644 index 0000000..56034de --- /dev/null +++ b/src/vehicles/DamageManager.cpp @@ -0,0 +1,229 @@ +#include "common.h" + +#include "General.h" +#include "Vehicle.h" +#include "DamageManager.h" + + +float G_aComponentDamage[] = { 2.5f, 1.25f, 3.2f, 1.4f, 2.5f, 2.8f, 0.5f }; + +CDamageManager::CDamageManager(void) +{ + ResetDamageStatus(); + m_fWheelDamageEffect = 0.75f; + field_18 = 1; +} + +void +CDamageManager::ResetDamageStatus(void) +{ + memset(this, 0, sizeof(*this)); +} + +void +CDamageManager::FuckCarCompletely(void) +{ + int i; + + m_wheelStatus[0] = WHEEL_STATUS_MISSING; + // wheels 1-3 not reset? + + for(i = 0; i < ARRAY_SIZE(m_doorStatus); i++) + m_doorStatus[i] = DOOR_STATUS_MISSING; + + for(i = 0; i < 3; i++){ +#ifdef FIX_BUGS + ProgressPanelDamage(VEHBUMPER_FRONT); + ProgressPanelDamage(VEHBUMPER_REAR); +#else + // this can't be right + ProgressPanelDamage(COMPONENT_BUMPER_FRONT); + ProgressPanelDamage(COMPONENT_BUMPER_REAR); +#endif + } + // Why set to no damage? +#ifndef FIX_BUGS + m_lightStatus = 0; + m_panelStatus = 0; +#endif + SetEngineStatus(250); +} + +bool +CDamageManager::ApplyDamage(tComponent component, float damage, float unused) +{ + tComponentGroup group; + uint8 subComp; + + GetComponentGroup(component, &group, &subComp); + damage *= G_aComponentDamage[group]; + if(damage > 150.0f){ + switch(group){ + case COMPGROUP_WHEEL: + ProgressWheelDamage(subComp); + break; + case COMPGROUP_DOOR: + case COMPGROUP_BOOT: + ProgressDoorDamage(subComp); + break; + case COMPGROUP_BONNET: + if(damage > 220.0f) + ProgressEngineDamage(); + ProgressDoorDamage(subComp); + break; + case COMPGROUP_PANEL: + // so windscreen is a light? + SetLightStatus((eLights)subComp, 1); + // fall through + case COMPGROUP_BUMPER: + if(damage > 220.0f && + (component == COMPONENT_PANEL_FRONT_LEFT || + component == COMPONENT_PANEL_FRONT_RIGHT || + component == COMPONENT_PANEL_WINDSCREEN)) + ProgressEngineDamage(); + ProgressPanelDamage(subComp); + break; + default: break; + } + return true; + } + return false; +} + +bool +CDamageManager::GetComponentGroup(tComponent component, tComponentGroup *componentGroup, uint8 *subComp) +{ + *subComp = -2; // ?? + + // This is done very strangely in the game, maybe an optimized switch? + if(component >= COMPONENT_PANEL_FRONT_LEFT){ + if(component >= COMPONENT_BUMPER_FRONT) + *componentGroup = COMPGROUP_BUMPER; + else + *componentGroup = COMPGROUP_PANEL; + *subComp = component - COMPONENT_PANEL_FRONT_LEFT; + return true; + }else if(component >= COMPONENT_DOOR_BONNET){ + if(component == COMPONENT_DOOR_BONNET) + *componentGroup = COMPGROUP_BONNET; + else if(component == COMPONENT_DOOR_BOOT) + *componentGroup = COMPGROUP_BOOT; + else + *componentGroup = COMPGROUP_DOOR; + *subComp = component - COMPONENT_DOOR_BONNET; + return true; + }else if(component >= COMPONENT_WHEEL_FRONT_LEFT){ + *componentGroup = COMPGROUP_WHEEL; + *subComp = component - COMPONENT_WHEEL_FRONT_LEFT; + return true; + }else if(component >= COMPONENT_DEFAULT){ + *componentGroup = COMPGROUP_DEFAULT; + *subComp = COMPONENT_DEFAULT; + return true; + }else + return false; +} + +void +CDamageManager::SetDoorStatus(int32 door, uint32 status) +{ + m_doorStatus[door] = status; +} + +int32 +CDamageManager::GetDoorStatus(int32 door) +{ + return m_doorStatus[door]; +} + +bool +CDamageManager::ProgressDoorDamage(uint8 door) +{ + int status = GetDoorStatus(door); + if(status == PANEL_STATUS_MISSING) + return false; + SetDoorStatus(door, status+1); + return true; +} + +void +CDamageManager::SetPanelStatus(int32 panel, uint32 status) +{ + m_panelStatus = dpb(status, panel*4, 4, m_panelStatus); +} + +int32 +CDamageManager::GetPanelStatus(int32 panel) +{ + return ldb(panel*4, 4, m_panelStatus); +} + +bool +CDamageManager::ProgressPanelDamage(uint8 panel) +{ + int status = GetPanelStatus(panel); + if(status == DOOR_STATUS_MISSING) + return false; + SetPanelStatus(panel, status+1); + return true; +} + +void +CDamageManager::SetLightStatus(eLights light, uint32 status) +{ + m_lightStatus = dpb(status, light*2, 2, m_lightStatus); +} + +int32 +CDamageManager::GetLightStatus(eLights light) +{ + return ldb(light*2, 2, m_lightStatus); +} + +void +CDamageManager::SetWheelStatus(int32 wheel, uint32 status) +{ + m_wheelStatus[wheel] = status; +} + +int32 +CDamageManager::GetWheelStatus(int32 wheel) +{ + return m_wheelStatus[wheel]; +} + +bool +CDamageManager::ProgressWheelDamage(uint8 wheel) +{ + int status = GetWheelStatus(wheel); + if(status == WHEEL_STATUS_MISSING) + return false; + SetWheelStatus(wheel, status+1); + return true; +} + +void +CDamageManager::SetEngineStatus(uint32 status) +{ + if(status > 250) + m_engineStatus = 250; + else + m_engineStatus = status; +} + +int32 +CDamageManager::GetEngineStatus(void) +{ + return m_engineStatus; +} + +bool +CDamageManager::ProgressEngineDamage(void) +{ + int status = GetEngineStatus(); + int newstatus = status + 32 + (CGeneral::GetRandomNumber() & 0x1F); + if(status < ENGINE_STATUS_ON_FIRE && newstatus > ENGINE_STATUS_ON_FIRE-1) + newstatus = ENGINE_STATUS_ON_FIRE-1; + SetEngineStatus(newstatus); + return true; +} diff --git a/src/vehicles/DamageManager.h b/src/vehicles/DamageManager.h new file mode 100644 index 0000000..312006e --- /dev/null +++ b/src/vehicles/DamageManager.h @@ -0,0 +1,115 @@ +#pragma once + +#include "common.h" + +// TODO: move some of this into Vehicle.h + +enum eEngineStatus +{ + ENGINE_STATUS_STEAM1 = 100, + ENGINE_STATUS_STEAM2 = 150, + ENGINE_STATUS_SMOKE = 200, + ENGINE_STATUS_ON_FIRE = 225 +}; + +enum eDoorStatus +{ + DOOR_STATUS_OK, + DOOR_STATUS_SMASHED, + DOOR_STATUS_SWINGING, + DOOR_STATUS_MISSING +}; + +enum ePanelStatus +{ + PANEL_STATUS_OK, + PANEL_STATUS_SMASHED1, + PANEL_STATUS_SMASHED2, + PANEL_STATUS_MISSING, +}; + +enum eWheelStatus +{ + WHEEL_STATUS_OK, + WHEEL_STATUS_BURST, + WHEEL_STATUS_MISSING +}; + +enum eLightStatus +{ + LIGHT_STATUS_OK, + LIGHT_STATUS_BROKEN +}; + +enum tComponent +{ + COMPONENT_DEFAULT, + COMPONENT_WHEEL_FRONT_LEFT, + COMPONENT_WHEEL_FRONT_RIGHT, + COMPONENT_WHEEL_REAR_LEFT, + COMPONENT_WHEEL_REAR_RIGHT, + COMPONENT_DOOR_BONNET, + COMPONENT_DOOR_BOOT, + COMPONENT_DOOR_FRONT_LEFT, + COMPONENT_DOOR_FRONT_RIGHT, + COMPONENT_DOOR_REAR_LEFT, + COMPONENT_DOOR_REAR_RIGHT, + COMPONENT_PANEL_FRONT_LEFT, + COMPONENT_PANEL_FRONT_RIGHT, + COMPONENT_PANEL_REAR_LEFT, + COMPONENT_PANEL_REAR_RIGHT, + COMPONENT_PANEL_WINDSCREEN, + COMPONENT_BUMPER_FRONT, + COMPONENT_BUMPER_REAR, +}; + +enum tComponentGroup +{ + COMPGROUP_BUMPER, + COMPGROUP_WHEEL, + COMPGROUP_DOOR, + COMPGROUP_BONNET, + COMPGROUP_BOOT, + COMPGROUP_PANEL, + COMPGROUP_DEFAULT, +}; + +enum eLights; + +class CDamageManager +{ +public: + + float m_fWheelDamageEffect; + uint8 m_engineStatus; + uint8 m_wheelStatus[4]; + uint8 m_doorStatus[6]; + uint32 m_lightStatus; + uint32 m_panelStatus; + uint8 field_18; + + CDamageManager(void); + + void ResetDamageStatus(void); + void FuckCarCompletely(void); + bool ApplyDamage(tComponent component, float damage, float unused); + bool GetComponentGroup(tComponent component, tComponentGroup *componentGroup, uint8 *foo); + + void SetDoorStatus(int32 door, uint32 status); + int32 GetDoorStatus(int32 door); + bool ProgressDoorDamage(uint8 door); + void SetPanelStatus(int32 panel, uint32 status); + int32 GetPanelStatus(int32 panel); + bool ProgressPanelDamage(uint8 panel); + // needed for CReplay + static int32 GetPanelStatus(uint32 panelstatus, int32 panel) { return ldb(panel*4, 4, panelstatus); } + void SetLightStatus(eLights light, uint32 status); + int32 GetLightStatus(eLights light); + void SetWheelStatus(int32 wheel, uint32 status); + int32 GetWheelStatus(int32 wheel); + bool ProgressWheelDamage(uint8 wheel); + void SetEngineStatus(uint32 status); + int32 GetEngineStatus(void); + bool ProgressEngineDamage(void); +}; +VALIDATE_SIZE(CDamageManager, 0x1C); diff --git a/src/vehicles/Door.cpp b/src/vehicles/Door.cpp new file mode 100644 index 0000000..1b3f9e8 --- /dev/null +++ b/src/vehicles/Door.cpp @@ -0,0 +1,170 @@ +#include "common.h" + +#include "Vehicle.h" +#include "Door.h" + +CDoor::CDoor(void) +{ + memset(this, 0, sizeof(*this)); +} + +void +CDoor::Open(float ratio) +{ + float open; + + m_fPrevAngle = m_fAngle; + open = RetAngleWhenOpen(); + if(ratio < 1.0f){ + m_fAngle = open*ratio; + if(m_fAngle == 0.0f) + m_fAngVel = 0.0f; + }else{ + m_nDoorState = DOORST_OPEN; + m_fAngle = open; + } +} + +void +CDoor::Process(CVehicle *vehicle) +{ + static CVector vecOffset(1.0f, 0.0f, 0.0f); + CVector speed = vehicle->GetSpeed(vecOffset); + CVector vecSpeedDiff = speed - m_vecSpeed; + vecSpeedDiff = Multiply3x3(vecSpeedDiff, vehicle->GetMatrix()); + + // air resistance + float fSpeedDiff = 0.0f; // uninitialized in game + switch(m_nAxis){ + case 0: // x-axis + if(m_nDirn) + fSpeedDiff = vecSpeedDiff.y + vecSpeedDiff.z; + else + fSpeedDiff = -(vecSpeedDiff.y + vecSpeedDiff.z); + break; + + // we don't support y axis apparently? + + case 2: // z-axis + if(m_nDirn) + fSpeedDiff = -(vecSpeedDiff.y + vecSpeedDiff.x); + else + fSpeedDiff = vecSpeedDiff.y - vecSpeedDiff.x; + break; + } + fSpeedDiff = Clamp(fSpeedDiff, -0.2f, 0.2f); + if(Abs(fSpeedDiff) > 0.002f) + m_fAngVel += fSpeedDiff; + m_fAngVel *= 0.945f; + m_fAngVel = Clamp(m_fAngVel, -0.3f, 0.3f); + + m_fAngle += m_fAngVel; + m_nDoorState = DOORST_SWINGING; + if(m_fAngle > m_fMaxAngle){ + m_fAngle = m_fMaxAngle; + m_fAngVel *= -0.8f; + m_nDoorState = DOORST_OPEN; + } + if(m_fAngle < m_fMinAngle){ + m_fAngle = m_fMinAngle; + m_fAngVel *= -0.8f; + m_nDoorState = DOORST_CLOSED; + } + m_vecSpeed = speed; +} + +float +CDoor::RetAngleWhenClosed(void) +{ + if(Abs(m_fMaxAngle) < Abs(m_fMinAngle)) + return m_fMaxAngle; + else + return m_fMinAngle; +} + +float +CDoor::RetAngleWhenOpen(void) +{ + if(Abs(m_fMaxAngle) < Abs(m_fMinAngle)) + return m_fMinAngle; + else + return m_fMaxAngle; +} + +float +CDoor::GetAngleOpenRatio(void) +{ + float open = RetAngleWhenOpen(); + if(open == 0.0f) + return 0.0f; + return m_fAngle/open; +} + +bool +CDoor::IsFullyOpen(void) +{ + // why -0.5? that's around 28 deg less than fully open + if(Abs(m_fAngle) < Abs(RetAngleWhenOpen()) - 0.5f) + return false; + return true; +} + +bool +CDoor::IsClosed(void) +{ + return m_fAngle == RetAngleWhenClosed(); +} + + +CTrainDoor::CTrainDoor(void) +{ + memset(this, 0, sizeof(*this)); +} + +void +CTrainDoor::Open(float ratio) +{ + float open; + + m_fPrevPosn = m_fPosn; + open = RetTranslationWhenOpen(); + if(ratio < 1.0f){ + m_fPosn = open*ratio; + }else{ + m_nDoorState = DOORST_OPEN; + m_fPosn = open; + } +} + +float +CTrainDoor::RetTranslationWhenClosed(void) +{ + if(Abs(m_fClosedPosn) < Abs(m_fOpenPosn)) + return m_fClosedPosn; + else + return m_fOpenPosn; +} + +float +CTrainDoor::RetTranslationWhenOpen(void) +{ + if(Abs(m_fClosedPosn) < Abs(m_fOpenPosn)) + return m_fOpenPosn; + else + return m_fClosedPosn; +} + +bool +CTrainDoor::IsFullyOpen(void) +{ + // 0.5f again... + if(Abs(m_fPosn) < Abs(RetTranslationWhenOpen()) - 0.5f) + return false; + return true; +} + +bool +CTrainDoor::IsClosed(void) +{ + return m_fPosn == RetTranslationWhenClosed(); +} diff --git a/src/vehicles/Door.h b/src/vehicles/Door.h new file mode 100644 index 0000000..567d326 --- /dev/null +++ b/src/vehicles/Door.h @@ -0,0 +1,69 @@ +#pragma once + +class CVehicle; + +enum eDoorState +{ + DOORST_SWINGING, + // actually wrong though, + // OPEN is really MAX_ANGLE and CLOSED is MIN_ANGLE + DOORST_OPEN, + DOORST_CLOSED +}; + +class CDoor +{ +public: + float m_fMaxAngle; + float m_fMinAngle; + // direction of rotation for air resistance + int8 m_nDirn; + // axis in which this door rotates + int8 m_nAxis; + int8 m_nDoorState; + float m_fAngle; + float m_fPrevAngle; + float m_fAngVel; + CVector m_vecSpeed; + + CDoor(void); + void Init(float minAngle, float maxAngle, int8 dir, int8 axis) { + m_fMinAngle = minAngle; + m_fMaxAngle = maxAngle; + m_nDirn = dir; + m_nAxis = axis; + } + void Open(float ratio); + void Process(CVehicle *veh); + float RetAngleWhenClosed(void); // dead + float RetAngleWhenOpen(void); + float GetAngleOpenRatio(void); + bool IsFullyOpen(void); + bool IsClosed(void); // dead +}; + +class CTrainDoor +{ +public: + float m_fClosedPosn; + float m_fOpenPosn; + int8 m_nDirn; + int8 m_nDoorState; // same enum as above? + int8 m_nAxis; + float m_fPosn; + float m_fPrevPosn; + int field_14; // unused? + + CTrainDoor(void); + void Init(float open, float closed, int8 dir, int8 axis) { + m_fOpenPosn = open; + m_fClosedPosn = closed; + m_nDirn = dir; + m_nAxis = axis; + } + bool IsClosed(void); + bool IsFullyOpen(void); + float RetTranslationWhenClosed(void); + float RetTranslationWhenOpen(void); + void Open(float ratio); +}; diff --git a/src/vehicles/Floater.cpp b/src/vehicles/Floater.cpp new file mode 100644 index 0000000..4331090 --- /dev/null +++ b/src/vehicles/Floater.cpp @@ -0,0 +1,185 @@ +#include "common.h" + +#include "Timer.h" +#include "WaterLevel.h" +#include "ModelIndices.h" +#include "Physical.h" +#include "Vehicle.h" +#include "Floater.h" + +cBuoyancy mod_Buoyancy; + +float fVolMultiplier = 1.0f; +// amount of boat volume in bounding box +// 1.0-volume is the empty space in the bbox +float fBoatVolumeDistribution[9] = { + // rear + 0.75f, 0.9f, 0.75f, + 0.95f, 1.0f, 0.95f, + 0.3f, 0.7f, 0.3f + // bow +}; + +bool +cBuoyancy::ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVector *impulse) +{ + m_numSteps = 2.0f; + + if(!CWaterLevel::GetWaterLevel(phys->GetPosition(), &m_waterlevel, phys->bTouchingWater)) + return false; + m_matrix = phys->GetMatrix(); + + PreCalcSetup(phys, buoyancy); + SimpleCalcBuoyancy(); + float f = CalcBuoyancyForce(phys, point, impulse); + if(m_isBoat) + return true; + return f != 0.0f; +} + +void +cBuoyancy::PreCalcSetup(CPhysical *phys, float buoyancy) +{ + CColModel *colModel; + + m_isBoat = phys->IsVehicle() && ((CVehicle*)phys)->IsBoat(); + colModel = phys->GetColModel(); + m_dimMin = colModel->boundingBox.min; + m_dimMax = colModel->boundingBox.max; + + if(m_isBoat){ + if(phys->GetModelIndex() == MI_PREDATOR){ + m_dimMax.y *= 0.9f; + m_dimMin.y *= 0.9f; + }else if(phys->GetModelIndex() == MI_SPEEDER){ + m_dimMax.y *= 1.1f; + m_dimMin.y *= 0.9f; + }else if(phys->GetModelIndex() == MI_REEFER){ + m_dimMin.y *= 0.9f; + }else{ + m_dimMax.y *= 0.9f; + m_dimMin.y *= 0.9f; + } + } + + m_step = (m_dimMax - m_dimMin)/m_numSteps; + + if(m_step.z > m_step.x && m_step.z > m_step.y){ + m_stepRatio.x = m_step.x/m_step.z; + m_stepRatio.y = m_step.y/m_step.z; + m_stepRatio.z = 1.0f; + }else if(m_step.y > m_step.x && m_step.y > m_step.z){ + m_stepRatio.x = m_step.x/m_step.y; + m_stepRatio.y = 1.0f; + m_stepRatio.z = m_step.z/m_step.y; + }else{ + m_stepRatio.x = 1.0f; + m_stepRatio.y = m_step.y/m_step.x; + m_stepRatio.z = m_step.z/m_step.x; + } + + m_haveVolume = false; + m_numPartialVolumes = 1.0f; + m_volumeUnderWater = 0.0f; + m_impulsePoint = CVector(0.0f, 0.0f, 0.0f); + m_position = phys->GetPosition(); + m_positionZ = CVector(0.0f, 0.0f, m_position.z); + m_buoyancy = buoyancy; + m_waterlevel += m_waterLevelInc; +} + +void +cBuoyancy::SimpleCalcBuoyancy(void) +{ + float x, y; + int ix, i; + tWaterLevel waterPosition; + + // Floater is divided into 3x3 parts. Process and sum each of them + ix = 0; + for(x = m_dimMin.x; x <= m_dimMax.x; x += m_step.x){ + i = ix; + for(y = m_dimMin.y; y <= m_dimMax.y; y += m_step.y){ + CVector waterLevel(x, y, 0.0f); + FindWaterLevel(m_positionZ, &waterLevel, &waterPosition); + fVolMultiplier = m_isBoat ? fBoatVolumeDistribution[i] : 1.0f; + if(waterPosition != FLOATER_ABOVE_WATER) + SimpleSumBuoyancyData(waterLevel, waterPosition); + i += 3; + } + ix++; + } + + m_volumeUnderWater /= (m_dimMax.z - m_dimMin.z)*sq(m_numSteps+1.0f); +} + +float +cBuoyancy::SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition) +{ + static float fThisVolume; + static CVector AverageOfWaterLevel; + static float fFraction; + static float fRemainingSlice; + + float submerged = Abs(waterLevel.z - m_dimMin.z); + // subtract empty space from submerged volume + fThisVolume = submerged - (1.0f - fVolMultiplier); + if(fThisVolume < 0.0f) + return 0.0f; + + if(m_isBoat){ + fThisVolume *= fVolMultiplier; + if(fThisVolume < 0.5f) + fThisVolume = 2.0f*sq(fThisVolume); + if(fThisVolume < 1.0f) + fThisVolume = sq(fThisVolume); + fThisVolume = sq(fThisVolume); + } + + m_volumeUnderWater += fThisVolume; + + AverageOfWaterLevel.x = waterLevel.x * m_stepRatio.x; + AverageOfWaterLevel.y = waterLevel.y * m_stepRatio.y; + AverageOfWaterLevel.z = (waterLevel.z+m_dimMin.z)/2.0f * m_stepRatio.z; + + if(m_flipAverage) + AverageOfWaterLevel = -AverageOfWaterLevel; + + fFraction = 1.0f/m_numPartialVolumes; + fRemainingSlice = 1.0f - fFraction; + m_impulsePoint = m_impulsePoint*fRemainingSlice + AverageOfWaterLevel*fThisVolume*fFraction; + m_numPartialVolumes += 1.0f; + m_haveVolume = true; + return fThisVolume; +} + +void +cBuoyancy::FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition) +{ + *waterPosition = FLOATER_IN_WATER; + // waterLevel is a local x,y point + // m_position is the global position of our floater + // zpos is the global z coordinate of our floater + CVector xWaterLevel = Multiply3x3(m_matrix, *waterLevel); + CWaterLevel::GetWaterLevel(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y, m_position.z, + &waterLevel->z, true); + waterLevel->z -= xWaterLevel.z + zpos.z; // make local + if(waterLevel->z > m_dimMax.z){ + waterLevel->z = m_dimMax.z; + *waterPosition = FLOATER_UNDER_WATER; + }else if(waterLevel->z < m_dimMin.z){ + waterLevel->z = m_dimMin.z; + *waterPosition = FLOATER_ABOVE_WATER; + } +} + +bool +cBuoyancy::CalcBuoyancyForce(CPhysical *phys, CVector *point, CVector *impulse) +{ + if(!m_haveVolume) + return false; + + *point = Multiply3x3(m_matrix, m_impulsePoint); + *impulse = CVector(0.0f, 0.0f, m_volumeUnderWater*m_buoyancy*CTimer::GetTimeStep()); + return true; +} diff --git a/src/vehicles/Floater.h b/src/vehicles/Floater.h new file mode 100644 index 0000000..1cfb46f --- /dev/null +++ b/src/vehicles/Floater.h @@ -0,0 +1,45 @@ +#pragma once + +class CPhysical; + +enum tWaterLevel +{ + FLOATER_ABOVE_WATER, + FLOATER_IN_WATER, + FLOATER_UNDER_WATER, +}; + +class cBuoyancy +{ +public: + CVector m_position; + CMatrix m_matrix; + int m_field_54; + CVector m_positionZ; + float m_waterlevel; + float m_waterLevelInc; + float m_buoyancy; + CVector m_dimMax; + CVector m_dimMin; + float m_numPartialVolumes; + int m_field_8C; + int m_field_90; + int m_field_94; + bool m_haveVolume; + CVector m_step; + CVector m_stepRatio; + float m_numSteps; + bool m_flipAverage; + char m_field_B9; + bool m_isBoat; + float m_volumeUnderWater; + CVector m_impulsePoint; + + bool ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVector *impulse); + void PreCalcSetup(CPhysical *phys, float buoyancy); + void SimpleCalcBuoyancy(void); + float SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition); + void FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition); + bool CalcBuoyancyForce(CPhysical *phys, CVector *impulse, CVector *point); +}; +extern cBuoyancy mod_Buoyancy; diff --git a/src/vehicles/HandlingMgr.cpp b/src/vehicles/HandlingMgr.cpp new file mode 100644 index 0000000..00aaa68 --- /dev/null +++ b/src/vehicles/HandlingMgr.cpp @@ -0,0 +1,270 @@ +#include "common.h" + +#include "main.h" +#include "FileMgr.h" +#include "Physical.h" +#include "HandlingMgr.h" + +cHandlingDataMgr mod_HandlingManager; + +const char *HandlingFilename = "HANDLING.CFG"; + +const char VehicleNames[NUMHANDLINGS][14] = { + "LANDSTAL", + "IDAHO", + "STINGER", + "LINERUN", + "PEREN", + "SENTINEL", + "PATRIOT", + "FIRETRUK", + "TRASH", + "STRETCH", + "MANANA", + "INFERNUS", + "BLISTA", + "PONY", + "MULE", + "CHEETAH", + "AMBULAN", + "FBICAR", + "MOONBEAM", + "ESPERANT", + "TAXI", + "KURUMA", + "BOBCAT", + "MRWHOOP", + "BFINJECT", + "POLICE", + "ENFORCER", + "SECURICA", + "BANSHEE", + "PREDATOR", + "BUS", + "RHINO", + "BARRACKS", + "TRAIN", + "HELI", + "DODO", + "COACH", + "CABBIE", + "STALLION", + "RUMPO", + "RCBANDIT", + "BELLYUP", + "MRWONGS", + "MAFIA", + "YARDIE", + "YAKUZA", + "DIABLOS", + "COLUMB", + "HOODS", + "AIRTRAIN", + "DEADDODO", + "SPEEDER", + "REEFER", + "PANLANT", + "FLATBED", + "YANKEE", + "BORGNINE" +}; + +cHandlingDataMgr::cHandlingDataMgr(void) +{ + memset(this, 0, sizeof(*this)); +} + +void +cHandlingDataMgr::Initialise(void) +{ + LoadHandlingData(); + field_0 = 0.1f; + fWheelFriction = 0.9f; + field_8 = 1.0f; + field_C = 0.8f; + field_10 = 0.98f; +} + +void +cHandlingDataMgr::LoadHandlingData(void) +{ + char *start, *end; + char line[201]; // weird value + char delim[4]; // not sure + char *word; + int field, handlingId; + int keepGoing; + tHandlingData *handling; + + CFileMgr::SetDir("DATA"); + CFileMgr::LoadFile(HandlingFilename, work_buff, sizeof(work_buff), "r"); + CFileMgr::SetDir(""); + + start = (char*)work_buff; + end = start+1; + handling = nil; + keepGoing = 1; + + while(keepGoing){ + // find end of line + while(*end != '\n') end++; + + // get line + strncpy(line, start, end - start); + line[end - start] = '\0'; + start = end+1; + end = start+1; + + // yeah, this is kinda crappy + if(strcmp(line, ";the end") == 0) + keepGoing = 0; + else if(line[0] != ';'){ + field = 0; + strcpy(delim, " \t"); + // FIX: game seems to use a do-while loop here + for(word = strtok(line, delim); word; word = strtok(nil, delim)){ + switch(field){ + case 0: + handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS); + assert(handlingId >= 0 && handlingId < NUMHANDLINGS); + handling = &HandlingData[handlingId]; + handling->nIdentifier = (tVehicleType)handlingId; + break; + case 1: handling->fMass = strtod(word, nil); break; + case 2: handling->Dimension.x = strtod(word, nil); break; + case 3: handling->Dimension.y = strtod(word, nil); break; + case 4: handling->Dimension.z = strtod(word, nil); break; + case 5: handling->CentreOfMass.x = strtod(word, nil); break; + case 6: handling->CentreOfMass.y = strtod(word, nil); break; + case 7: handling->CentreOfMass.z = strtod(word, nil); break; + case 8: handling->nPercentSubmerged = atoi(word); break; + case 9: handling->fTractionMultiplier = strtod(word, nil); break; + case 10: handling->fTractionLoss = strtod(word, nil); break; + case 11: handling->fTractionBias = strtod(word, nil); break; + case 12: handling->Transmission.nNumberOfGears = atoi(word); break; + case 13: handling->Transmission.fMaxVelocity = strtod(word, nil); break; + case 14: handling->Transmission.fEngineAcceleration = strtod(word, nil) * 0.4; break; + case 15: handling->Transmission.nDriveType = word[0]; break; + case 16: handling->Transmission.nEngineType = word[0]; break; + case 17: handling->fBrakeDeceleration = strtod(word, nil); break; + case 18: handling->fBrakeBias = strtod(word, nil); break; + case 19: handling->bABS = !!atoi(word); break; + case 20: handling->fSteeringLock = strtod(word, nil); break; + case 21: handling->fSuspensionForceLevel = strtod(word, nil); break; + case 22: handling->fSuspensionDampingLevel = strtod(word, nil); break; + case 23: handling->fSeatOffsetDistance = strtod(word, nil); break; + case 24: handling->fCollisionDamageMultiplier = strtod(word, nil); break; + case 25: handling->nMonetaryValue = atoi(word); break; + case 26: handling->fSuspensionUpperLimit = strtod(word, nil); break; + case 27: handling->fSuspensionLowerLimit = strtod(word, nil); break; + case 28: handling->fSuspensionBias = strtod(word, nil); break; + case 29: + sscanf(word, "%x", &handling->Flags); + handling->Transmission.Flags = handling->Flags; + break; + case 30: handling->FrontLights = atoi(word); break; + case 31: handling->RearLights = atoi(word); break; + } + field++; + } + ConvertDataToGameUnits(handling); + } + } +} + +int +cHandlingDataMgr::FindExactWord(const char *word, const char *words, int wordLen, int numWords) +{ + int i; + + for(i = 0; i < numWords; i++){ + // BUG: the game does something really stupid here, it's fixed here + if(strncmp(word, words, wordLen) == 0) + return i; + words += wordLen; + } + return numWords; +} + + +void +cHandlingDataMgr::ConvertDataToGameUnits(tHandlingData *handling) +{ + // acceleration is in ms^-2, but we need mf^-2 where f is one frame time (50fps) + float velocity, a, b; + + handling->Transmission.fEngineAcceleration *= 1.0f/(50.0f*50.0f); + handling->Transmission.fMaxVelocity *= 1000.0f/(60.0f*60.0f * 50.0f); + handling->fBrakeDeceleration *= 1.0f/(50.0f*50.0f); + handling->fTurnMass = (sq(handling->Dimension.x) + sq(handling->Dimension.y)) * handling->fMass / 12.0f; + if(handling->fTurnMass < 10.0f) + handling->fTurnMass *= 5.0f; + handling->fInvMass = 1.0f/handling->fMass; + handling->fBuoyancy = 100.0f/handling->nPercentSubmerged * GRAVITY*handling->fMass; + + // Don't quite understand this. What seems to be going on is that + // we calculate a drag (air resistance) deceleration for a given velocity and + // find the intersection between that and the max engine acceleration. + // at that point the car cannot accelerate any further and we've found the max velocity. + a = 0.0f; + b = 100.0f; + velocity = handling->Transmission.fMaxVelocity; + while(a < b && velocity > 0.0f){ + velocity -= 0.01f; + // what's the 1/6? + a = handling->Transmission.fEngineAcceleration/6.0f; + // no density or drag coefficient here... + float a_drag = 0.5f*SQR(velocity) * handling->Dimension.x*handling->Dimension.z / handling->fMass; + // can't make sense of this... maybe v - v/(drag + 1) ? but that doesn't make so much sense either + b = -velocity * (1.0f/(a_drag + 1.0f) - 1.0f); + } + + if(handling->nIdentifier == HANDLING_RCBANDIT){ + handling->Transmission.fMaxCruiseVelocity = handling->Transmission.fMaxVelocity; + }else{ + handling->Transmission.fMaxCruiseVelocity = velocity; + handling->Transmission.fMaxVelocity = velocity * 1.2f; + } + handling->Transmission.fMaxReverseVelocity = -0.2f; + + if(handling->Transmission.nDriveType == '4') + handling->Transmission.fEngineAcceleration /= 4.0f; + else + handling->Transmission.fEngineAcceleration /= 2.0f; + + handling->Transmission.InitGearRatios(); +} + +int32 +cHandlingDataMgr::GetHandlingId(const char *name) +{ + int i; + for(i = 0; i < NUMHANDLINGS; i++) + if(strncmp(VehicleNames[i], name, 14) == 0) + break; + return i; +} + +void +cHandlingDataMgr::ConvertDataToWorldUnits(tHandlingData *handling) +{ + // TODO: mobile code +} + +void +cHandlingDataMgr::RangeCheck(tHandlingData *handling) +{ + // TODO: mobile code +} + +void +cHandlingDataMgr::ModifyHandlingValue(CVehicle *, const tVehicleType &, const tField &, const bool &) +{ + // TODO: mobile code +} + +void +cHandlingDataMgr::DisplayHandlingData(CVehicle *, tHandlingData *, uint8, bool) +{ + // TODO: mobile code +} \ No newline at end of file diff --git a/src/vehicles/HandlingMgr.h b/src/vehicles/HandlingMgr.h new file mode 100644 index 0000000..9848bb7 --- /dev/null +++ b/src/vehicles/HandlingMgr.h @@ -0,0 +1,156 @@ +#pragma once + +#include "Transmission.h" + +enum tVehicleType +{ + HANDLING_LANDSTAL, + HANDLING_IDAHO, + HANDLING_STINGER, + HANDLING_LINERUN, + HANDLING_PEREN, + HANDLING_SENTINEL, + HANDLING_PATRIOT, + HANDLING_FIRETRUK, + HANDLING_TRASH, + HANDLING_STRETCH, + HANDLING_MANANA, + HANDLING_INFERNUS, + HANDLING_BLISTA, + HANDLING_PONY, + HANDLING_MULE, + HANDLING_CHEETAH, + HANDLING_AMBULAN, + HANDLING_FBICAR, + HANDLING_MOONBEAM, + HANDLING_ESPERANT, + HANDLING_TAXI, + HANDLING_KURUMA, + HANDLING_BOBCAT, + HANDLING_MRWHOOP, + HANDLING_BFINJECT, + HANDLING_POLICE, + HANDLING_ENFORCER, + HANDLING_SECURICA, + HANDLING_BANSHEE, + HANDLING_PREDATOR, + HANDLING_BUS, + HANDLING_RHINO, + HANDLING_BARRACKS, + HANDLING_TRAIN, + HANDLING_HELI, + HANDLING_DODO, + HANDLING_COACH, + HANDLING_CABBIE, + HANDLING_STALLION, + HANDLING_RUMPO, + HANDLING_RCBANDIT, + HANDLING_BELLYUP, + HANDLING_MRWONGS, + HANDLING_MAFIA, + HANDLING_YARDIE, + HANDLING_YAKUZA, + HANDLING_DIABLOS, + HANDLING_COLUMB, + HANDLING_HOODS, + HANDLING_AIRTRAIN, + HANDLING_DEADDODO, + HANDLING_SPEEDER, + HANDLING_REEFER, + HANDLING_PANLANT, + HANDLING_FLATBED, + HANDLING_YANKEE, + HANDLING_BORGNINE, + + NUMHANDLINGS +}; + +enum tField // most likely a handling field enum, never used so :shrug: +{ + +}; + +enum +{ + HANDLING_1G_BOOST = 1, + HANDLING_2G_BOOST = 2, + HANDLING_REV_BONNET = 4, + HANDLING_HANGING_BOOT = 8, + HANDLING_NO_DOORS = 0x10, + HANDLING_IS_VAN = 0x20, + HANDLING_IS_BUS = 0x40, + HANDLING_IS_LOW = 0x80, + HANDLING_DBL_EXHAUST = 0x100, + HANDLING_TAILGATE_BOOT = 0x200, + HANDLING_NOSWING_BOOT = 0x400, + HANDLING_NONPLAYER_STABILISER = 0x800, + HANDLING_NEUTRALHANDLING = 0x1000, + HANDLING_HAS_NO_ROOF = 0x2000, + HANDLING_IS_BIG = 0x4000, + HANDLING_HALOGEN_LIGHTS = 0x8000, +}; + +struct tHandlingData +{ + tVehicleType nIdentifier; + float fMass; + float fInvMass; + float fTurnMass; + CVector Dimension; + CVector CentreOfMass; + int8 nPercentSubmerged; + float fBuoyancy; + float fTractionMultiplier; + cTransmission Transmission; + float fBrakeDeceleration; + float fBrakeBias; + int8 bABS; + float fSteeringLock; + float fTractionLoss; + float fTractionBias; + float fUnused; + float fSuspensionForceLevel; + float fSuspensionDampingLevel; + float fSuspensionUpperLimit; + float fSuspensionLowerLimit; + float fSuspensionBias; + float fCollisionDamageMultiplier; + uint32 Flags; + float fSeatOffsetDistance; + int32 nMonetaryValue; + int8 FrontLights; + int8 RearLights; +}; +VALIDATE_SIZE(tHandlingData, 0xD8); + +class CVehicle; + +class cHandlingDataMgr +{ + float field_0; // unused it seems +public: + float fWheelFriction; // wheel related +private: + float field_8; // + float field_C; // unused it seems + float field_10; // + tHandlingData HandlingData[NUMHANDLINGS]; + uint32 field_302C; // unused it seems + +public: + cHandlingDataMgr(void); + void Initialise(void); + void LoadHandlingData(void); + int FindExactWord(const char *word, const char *words, int wordLen, int numWords); + void ConvertDataToWorldUnits(tHandlingData *handling); + void ConvertDataToGameUnits(tHandlingData *handling); + void RangeCheck(tHandlingData *handling); + void ModifyHandlingValue(CVehicle *, const tVehicleType &, const tField &, const bool &); + void DisplayHandlingData(CVehicle *, tHandlingData *, uint8, bool); + int32 GetHandlingId(const char *name); + tHandlingData *GetHandlingData(tVehicleType id) { return &HandlingData[id]; } + bool HasRearWheelDrive(tVehicleType id) { return HandlingData[id].Transmission.nDriveType != 'F'; } + bool HasFrontWheelDrive(tVehicleType id) { return HandlingData[id].Transmission.nDriveType != 'R'; } +}; +VALIDATE_SIZE(cHandlingDataMgr, 0x3030); +extern cHandlingDataMgr mod_HandlingManager; diff --git a/src/vehicles/Heli.cpp b/src/vehicles/Heli.cpp new file mode 100644 index 0000000..6e302e0 --- /dev/null +++ b/src/vehicles/Heli.cpp @@ -0,0 +1,1070 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "Darkel.h" +#include "Stats.h" +#include "SurfaceTable.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "Camera.h" +#include "VisibilityPlugins.h" +#include "ZoneCull.h" +#include "Particle.h" +#include "Shadows.h" +#include "Coronas.h" +#include "Explosion.h" +#include "Timecycle.h" +#include "TempColModels.h" +#include "World.h" +#include "WaterLevel.h" +#include "PlayerPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "Object.h" +#include "HandlingMgr.h" +#include "Heli.h" +#ifdef FIX_BUGS +#include "Replay.h" +#endif + +enum +{ + HELI_STATUS_HOVER, + HELI_STATUS_CHASE_PLAYER, + HELI_STATUS_FLY_AWAY, + HELI_STATUS_SHOT_DOWN, + HELI_STATUS_HOVER2, +}; + +CHeli *CHeli::pHelis[NUM_HELIS]; +int16 CHeli::NumRandomHelis; +uint32 CHeli::TestForNewRandomHelisTimer; +int16 CHeli::NumScriptHelis; // unused +bool CHeli::CatalinaHeliOn; +bool CHeli::CatalinaHasBeenShotDown; +bool CHeli::ScriptHeliOn; + +CHeli::CHeli(int32 id, uint8 CreatedBy) + : CVehicle(CreatedBy) +{ + int i; + + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + m_vehType = VEHICLE_TYPE_HELI; + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + SetModelIndex(id); + m_heliStatus = HELI_STATUS_HOVER; + m_pathState = 0; + + m_fMass = 100000000.0f; + m_fTurnMass = 100000000.0f; + m_fAirResistance = 0.9994f; + m_fElasticity = 0.05f; + + m_nHeliId = 0; + m_fRotorRotation = 0.0f; + m_nBulletDamage = 0; + m_fAngularSpeed = 0.0f; + m_fRotation = 0.0f; + m_nSearchLightTimer = CTimer::GetTimeInMilliseconds(); + for(i = 0; i < 6; i++){ + m_aSearchLightHistoryX[i] = 0.0f; + m_aSearchLightHistoryY[i] = 0.0f; + } + + for(i = 0; i < 8; i++) + m_fHeliDustZ[i] = -50.0f; + + m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds(); + SetStatus(STATUS_HELI); + m_bTestRight = true; + m_fTargetOffset = 0.0f; + m_fSearchLightX = m_fSearchLightY = 0.0f; + + // BUG: not in game but gets initialized to CDCDCDCD in debug + m_nLastShotTime = 0; +} + +void +CHeli::SetModelIndex(uint32 id) +{ + int i; + + CVehicle::SetModelIndex(id); + for(i = 0; i < NUM_HELI_NODES; i++) + m_aHeliNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aHeliNodes); +} + +static float CatalinaTargetX[7] = { -478.0, -677.0, -907.0, -1095.0, -1152.0, -1161.0, -1161.0 }; +static float CatalinaTargetY[7] = { 227.0, 206.0, 210.0, 242.0, 278.0, 341.0, 341.0 }; +static float CatalinaTargetZ[7] = { 77.0, 66.0, 60.0, 53.0, 51.0, 46.0, 30.0 }; +static float DamPathX[6] = { -1191.0, -1176.0, -1128.0, -1072.0, -1007.0, -971.0 }; +static float DamPathY[6] = { 350.0, 388.0, 429.0, 447.0, 449.0, 416.0 }; +static float DamPathZ[6] = { 42.0, 37.0, 28.0, 28.0, 31.0, 33.0 }; +static float ShortPathX[4] = { -974.0, -1036.0, -1112.0, -1173.0 }; +static float ShortPathY[4] = { 340.0, 312.0, 317.0, 294.0 }; +static float ShortPathZ[4] = { 41.0, 38.0, 32.0, 39.0 }; +static float LongPathX[7] = { -934.0, -905.0, -906.0, -1063.0, -1204.0, -1233.0, -1207.0 }; +static float LongPathY[7] = { 371.0, 362.0, 488.0, 548.0, 451.0, 346.0, 308.0 }; +static float LongPathZ[7] = { 57.0, 90.0, 105.0, 100.0, 81.0, 79.0, 70.0 }; + +static int PathPoint; + +void +CHeli::ProcessControl(void) +{ + int i; + + if(gbModelViewer) + return; + + // Find target + CVector target(0.0f, 0.0f, 0.0f); + CVector2D vTargetDist; + if(m_heliType == HELI_TYPE_CATALINA && m_heliStatus != HELI_STATUS_SHOT_DOWN){ + switch(m_pathState){ + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + target.x = CatalinaTargetX[m_pathState]; + target.y = CatalinaTargetY[m_pathState]; + target.z = CatalinaTargetZ[m_pathState]; + if((target - GetPosition()).Magnitude() < 9.0f) + m_pathState++; + break; + case 6: + target.x = CatalinaTargetX[m_pathState]; + target.y = CatalinaTargetY[m_pathState]; + target.z = CatalinaTargetZ[m_pathState]; + if(GetPosition().z > 31.55f) + break; + m_pathState = 7; + GetMatrix().GetPosition().z = 31.55f; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + break; + case 7: + GetMatrix().GetPosition().z = 31.55f; + target = GetPosition(); + break; + + + // Take off + case 8: + target.x = GetPosition().x; + target.y = GetPosition().y; + target.z = 74.0f; + if(GetPosition().z < 40.0f) + break; + PathPoint = 2; + m_pathState = 9; + break; + // Circle around dam + case 9: + target.x = DamPathX[PathPoint]; + target.y = DamPathY[PathPoint]; + target.z = DamPathZ[PathPoint]; + if((target - GetPosition()).Magnitude() < 9.0f){ + PathPoint++; + if(PathPoint >= 6){ + m_pathState = 10; + PathPoint = 0; + } + } + break; + case 10: + target.x = ShortPathX[PathPoint]; + target.y = ShortPathY[PathPoint]; + target.z = ShortPathZ[PathPoint]; + if((target - GetPosition()).Magnitude() < 9.0f){ + PathPoint++; + if(PathPoint >= 3){ + m_pathState = 9; + PathPoint = 1; + } + } + break; + // how do we get here? + case 11: + target.x = LongPathX[PathPoint]; + target.y = LongPathY[PathPoint]; + target.z = LongPathZ[PathPoint]; + if((target - GetPosition()).Magnitude() < 9.0f){ + PathPoint++; + if(PathPoint >= 7){ + m_pathState = 9; + PathPoint = 0; + } + } + break; + + + // Fly away + case 12: + target.x = GetPosition().x; + target.y = GetPosition().y; + target.z = 200.0f; + break; + } + + vTargetDist = target - GetPosition(); + m_fTargetZ = target.z; + if(m_pathState == 6){ + GetMatrix().GetPosition().x = GetMatrix().GetPosition().x*0.99f + target.x*0.01f; + GetMatrix().GetPosition().y = GetMatrix().GetPosition().y*0.99f + target.y*0.01f; + } + }else{ + vTargetDist = FindPlayerCoors() - GetPosition(); + m_fTargetZ = FindPlayerCoors().z; + + // Heli flies away to (0, 0) + if(m_heliStatus == HELI_STATUS_FLY_AWAY && GetPosition().z > 20.0f){ + vTargetDist.x = 0.0f - GetPosition().x; + vTargetDist.y = 0.0f - GetPosition().y; + } + + float groundZ; + switch(m_heliStatus){ + case HELI_STATUS_HOVER: + groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil); + m_fTargetZ = Max(groundZ, m_fTargetZ) + 8.0f; + break; + case HELI_STATUS_SHOT_DOWN: + groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil); + m_fTargetZ = Max(groundZ, m_fTargetZ) + 8.0f + m_fTargetOffset; + break; + case HELI_STATUS_HOVER2: + groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil); + m_fTargetZ = Max(groundZ, m_fTargetZ) + 8.0f + m_fTargetOffset; + break; + default: + groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, 1000.0f, nil); + m_fTargetZ = Max(groundZ, m_fTargetZ) + 12.0f; + break; + } + + // Move up if too low + if(GetPosition().z - 2.0f < groundZ && m_heliStatus != HELI_STATUS_SHOT_DOWN) + m_vecMoveSpeed.z += CTimer::GetTimeStep()*0.01f; + m_vecMoveSpeed.z = Clamp(m_vecMoveSpeed.z, -0.3f, 0.3f); + } + + float fTargetDist = vTargetDist.Magnitude(); + + switch(m_heliStatus){ + case HELI_STATUS_HOVER: + case HELI_STATUS_HOVER2:{ + float targetHeight; + if(m_heliType == HELI_TYPE_CATALINA) + targetHeight = 8.0f; + else + targetHeight = 40.0f - m_nHeliId*10.0f; + if(fTargetDist > targetHeight) + m_heliStatus = HELI_STATUS_CHASE_PLAYER; + } +#ifdef FIX_BUGS + break; +#endif + case HELI_STATUS_CHASE_PLAYER:{ + float targetHeight; + if(m_heliType == HELI_TYPE_CATALINA) + targetHeight = 4.0f; + else + targetHeight = 30.0f - m_nHeliId*7.5f; + if(fTargetDist < 1.0f || + fTargetDist < targetHeight && CWorld::GetIsLineOfSightClear(GetPosition(), FindPlayerCoors(), true, false, false, false, false, false)) + m_heliStatus = HELI_STATUS_HOVER; + } + } + + // Find xy speed + float speed; + if(fTargetDist > 100.0f) + speed = 1.0f; + else if(fTargetDist > 75.0f) + speed = 0.7f; + else + speed = 0.4f; + if(m_heliStatus == HELI_STATUS_HOVER || m_heliStatus == HELI_STATUS_HOVER2 || m_heliStatus == HELI_STATUS_SHOT_DOWN) + speed = 0.0f; + + if(fTargetDist != 0.0f) + vTargetDist /= fTargetDist; + else + vTargetDist.x = 1.0f; + CVector2D targetSpeed = vTargetDist * speed; + + if(m_heliStatus == HELI_STATUS_HOVER2 || m_heliStatus == HELI_STATUS_SHOT_DOWN){ + bool force = !!((CTimer::GetFrameCounter() + m_randomSeed) & 8); + if(m_bTestRight){ + if(force || CWorld::TestSphereAgainstWorld(GetPosition() + 4.0f*GetRight(), 2.0f, this, true, false, false, false, false, false) == nil){ + if(m_heliStatus == HELI_STATUS_SHOT_DOWN){ + m_fTargetOffset -= CTimer::GetTimeStep()*0.05f; + targetSpeed.x -= -vTargetDist.x*0.15f; + targetSpeed.y -= vTargetDist.y*0.15f; + }else{ + targetSpeed.x -= -vTargetDist.x*0.05f; + targetSpeed.y -= vTargetDist.y*0.05f; + } + }else{ + m_bTestRight = false; + if(m_heliStatus == HELI_STATUS_HOVER2) + m_fTargetOffset += 5.0f; + else + m_fTargetOffset -= 5.0f; + } + }else{ + if(force || CWorld::TestSphereAgainstWorld(GetPosition() - 4.0f*GetRight(), 2.0f, this, true, false, false, false, false, false) == nil){ + if(m_heliStatus == HELI_STATUS_SHOT_DOWN){ + m_fTargetOffset -= CTimer::GetTimeStep()*0.05f; + targetSpeed.x += -vTargetDist.x*0.15f; + targetSpeed.y += vTargetDist.y*0.15f; + }else{ + targetSpeed.x += -vTargetDist.x*0.05f; + targetSpeed.y += vTargetDist.y*0.05f; + } + }else{ + m_bTestRight = true; + if(m_heliStatus == HELI_STATUS_HOVER2) + m_fTargetOffset += 5.0f; + else + m_fTargetOffset -= 5.0f; + } + } + + if(m_fTargetOffset > 30.0f) + m_fTargetOffset = 30.0f; + + if(m_heliStatus == HELI_STATUS_SHOT_DOWN && force){ + if(CWorld::TestSphereAgainstWorld(GetPosition() + 1.5f*GetForward(), 2.0f, this, true, false, false, false, false, false) || + CWorld::TestSphereAgainstWorld(GetPosition() - 1.5f*GetForward(), 2.0f, this, true, false, false, false, false, false)) + m_nExplosionTimer = CTimer::GetPreviousTimeInMilliseconds(); + } + }else + if(m_fTargetOffset >= 2.0f) + m_fTargetOffset -= 2.0f; + + if(m_heliType == HELI_TYPE_CATALINA) + if(m_pathState == 9 || m_pathState == 11 || m_pathState == 10){ + float f = Pow(0.997f, CTimer::GetTimeStep()); + m_vecMoveSpeed.x *= f; + m_vecMoveSpeed.y *= f; + } + + CVector2D speedDir = targetSpeed - m_vecMoveSpeed; + float speedDiff = speedDir.Magnitude(); + if(speedDiff != 0.0f) + speedDir /= speedDiff; + else + speedDir.x = 1.0f; + float speedInc = CTimer::GetTimeStep()*0.002f; + if(speedDiff < speedInc){ + m_vecMoveSpeed.x = targetSpeed.x; + m_vecMoveSpeed.y = targetSpeed.y; + }else{ + m_vecMoveSpeed.x += speedDir.x*speedInc; + m_vecMoveSpeed.y += speedDir.y*speedInc; + } + GetMatrix().GetPosition().x += m_vecMoveSpeed.x*CTimer::GetTimeStep(); + GetMatrix().GetPosition().y += m_vecMoveSpeed.y*CTimer::GetTimeStep(); + + // Find z target + if(m_heliStatus == HELI_STATUS_FLY_AWAY) + m_fTargetZ = 1000.0f; + if((CTimer::GetTimeInMilliseconds() + 800*m_nHeliId) & 0x800) + m_fTargetZ += 2.0f; + m_fTargetZ += m_nHeliId*5.0f; + + // Find z speed + float targetSpeedZ = (m_fTargetZ - GetPosition().z)*0.01f; + float speedDiffZ = targetSpeedZ - m_vecMoveSpeed.z; + float speedIncZ = CTimer::GetTimeStep()*0.001f; + if(m_heliStatus == HELI_STATUS_FLY_AWAY) + speedIncZ *= 1.5f; + if(Abs(speedDiffZ) < speedIncZ) + m_vecMoveSpeed.z = targetSpeedZ; + else if(speedDiffZ < 0.0f) + m_vecMoveSpeed.z -= speedIncZ; + else + m_vecMoveSpeed.z += speedIncZ*1.5f; + GetMatrix().GetPosition().z += m_vecMoveSpeed.z*CTimer::GetTimeStep(); + + // Find angular speed + float targetAngularSpeed; + m_fAngularSpeed *= Pow(0.995f, CTimer::GetTimeStep()); + if(fTargetDist < 8.0f) + targetAngularSpeed = 0.0f; + else{ + float rotationDiff = CGeneral::GetATanOfXY(vTargetDist.x, vTargetDist.y) - m_fRotation; + while(rotationDiff < -3.14f) rotationDiff += 6.28f; + while(rotationDiff > 3.14f) rotationDiff -= 6.28f; + if(Abs(rotationDiff) > 0.4f){ + if(rotationDiff < 0.0f) + targetAngularSpeed = -0.2f; + else + targetAngularSpeed = 0.2f; + }else + targetAngularSpeed = 0.0f; + } + float angularSpeedDiff = targetAngularSpeed - m_fAngularSpeed; + float angularSpeedInc = CTimer::GetTimeStep()*0.0001f; + if(Abs(angularSpeedDiff) < angularSpeedInc) + m_fAngularSpeed = targetAngularSpeed; + else if(angularSpeedDiff < 0.0f) + m_fAngularSpeed -= angularSpeedInc; + else + m_fAngularSpeed += angularSpeedInc; + m_fRotation += m_fAngularSpeed * CTimer::GetTimeStep(); + + // Set matrix + CVector up(3.0f*m_vecMoveSpeed.x, 3.0f*m_vecMoveSpeed.y, 1.0f); + up.Normalise(); + CVector fwd(-Cos(m_fRotation), -Sin(m_fRotation), 0.0f); // not really forward + CVector right = CrossProduct(up, fwd); + fwd = CrossProduct(up, right); + GetRight() = right; + GetForward() = fwd; + GetUp() = up; + + // Search light and shooting + if(m_heliStatus == HELI_STATUS_FLY_AWAY || m_heliType == HELI_TYPE_CATALINA || CCullZones::PlayerNoRain()) + m_fSearchLightIntensity = 0.0f; + else { + // Update search light history once every 1000ms + int timeDiff = CTimer::GetTimeInMilliseconds() - m_nSearchLightTimer; + while (timeDiff > 1000) { + for (i = 5; i > 0; i--) { + m_aSearchLightHistoryX[i] = m_aSearchLightHistoryX[i - 1]; + m_aSearchLightHistoryY[i] = m_aSearchLightHistoryY[i - 1]; + } + m_aSearchLightHistoryX[0] = FindPlayerCoors().x + FindPlayerSpeed().x * 50.0f * (m_nHeliId + 2); + m_aSearchLightHistoryY[0] = FindPlayerCoors().y + FindPlayerSpeed().y * 50.0f * (m_nHeliId + 2); + + timeDiff -= 1000; + m_nSearchLightTimer += 1000; + } + assert(timeDiff <= 1000); + float f1 = timeDiff / 1000.0f; + float f2 = 1.0f - f1; + m_fSearchLightX = m_aSearchLightHistoryX[m_nHeliId + 2] * f2 + m_aSearchLightHistoryX[m_nHeliId + 2 - 1] * f1; + m_fSearchLightY = m_aSearchLightHistoryY[m_nHeliId + 2] * f2 + m_aSearchLightHistoryY[m_nHeliId + 2 - 1] * f1; + + float searchLightDist = (CVector2D(m_fSearchLightX, m_fSearchLightY) - GetPosition()).Magnitude(); + if (searchLightDist > 60.0f) + m_fSearchLightIntensity = 0.0f; + else if (searchLightDist < 40.0f) + m_fSearchLightIntensity = 1.0f; + else + m_fSearchLightIntensity = 1.0f - (40.0f - searchLightDist) / (60.0f-40.0f); + + if (m_fSearchLightIntensity < 0.9f || sq(FindPlayerCoors().x - m_fSearchLightX) + sq(FindPlayerCoors().y - m_fSearchLightY) > sq(7.0f)) + m_nShootTimer = CTimer::GetTimeInMilliseconds(); + else if (CTimer::GetTimeInMilliseconds() > m_nPoliceShoutTimer) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_PED_HELI_PLAYER_FOUND, 0.0f); + m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds() + 4500 + (CGeneral::GetRandomNumber() & 0xFFF); + } +#ifdef FIX_BUGS + if (!CReplay::IsPlayingBack()) +#endif + { + // Shoot + int shootTimeout; + if (m_heliType == HELI_TYPE_RANDOM) { + switch (FindPlayerPed()->m_pWanted->GetWantedLevel()) { + case 0: + case 1: + case 2: shootTimeout = 999999; break; + case 3: shootTimeout = 10000; break; + case 4: shootTimeout = 5000; break; + case 5: shootTimeout = 3500; break; + case 6: shootTimeout = 2000; break; + } + if (CCullZones::NoPolice()) + shootTimeout /= 2; + } + else + shootTimeout = 1500; + + if (FindPlayerPed()->m_pWanted->IsIgnored()) + m_nShootTimer = CTimer::GetTimeInMilliseconds(); + else { + // Check if line of sight is clear + if (CTimer::GetTimeInMilliseconds() > m_nShootTimer + shootTimeout && + CTimer::GetPreviousTimeInMilliseconds() <= m_nShootTimer + shootTimeout) { + if (CWorld::GetIsLineOfSightClear(GetPosition(), FindPlayerCoors(), true, false, false, false, false, false)) { + if (m_heliStatus == HELI_STATUS_HOVER2) + m_heliStatus = HELI_STATUS_HOVER; + } + else { + m_nShootTimer = CTimer::GetTimeInMilliseconds(); + if (m_heliStatus == HELI_STATUS_HOVER) + m_heliStatus = HELI_STATUS_HOVER2; + } + } + + // Shoot! + if (CTimer::GetTimeInMilliseconds() > m_nShootTimer + shootTimeout && + CTimer::GetTimeInMilliseconds() > m_nLastShotTime + 200) { + CVector shotTarget = FindPlayerCoors(); + // some inaccuracy + shotTarget.x += ((CGeneral::GetRandomNumber() & 0xFF) - 128) / 50.0f; + shotTarget.y += ((CGeneral::GetRandomNumber() & 0xFF) - 128) / 50.0f; + CVector direction = FindPlayerCoors() - GetPosition(); + direction.Normalise(); + shotTarget += 3.0f * direction; + CVector shotSource = GetPosition(); + shotSource += 3.0f * direction; + FireOneInstantHitRound(&shotSource, &shotTarget, 20); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + m_nLastShotTime = CTimer::GetTimeInMilliseconds(); + } + } + } + } + + // Drop Catalina's bombs + if(m_heliType == HELI_TYPE_CATALINA && m_pathState > 8 && (CTimer::GetTimeInMilliseconds()>>9) != (CTimer::GetPreviousTimeInMilliseconds()>>9)){ + CVector bombPos = GetPosition() - 60.0f*m_vecMoveSpeed; + if(sq(FindPlayerCoors().x-bombPos.x) + sq(FindPlayerCoors().y-bombPos.y) < sq(35.0f)){ + bool found; + float groundZ = CWorld::FindGroundZFor3DCoord(bombPos.x, bombPos.y, bombPos.z, &found); + float waterZ; + if(!CWaterLevel::GetWaterLevelNoWaves(bombPos.x, bombPos.y, bombPos.z, &waterZ)) + waterZ = 0.0f; + if(groundZ > waterZ){ + bombPos.z = groundZ + 2.0f; + CExplosion::AddExplosion(nil, this, EXPLOSION_HELI_BOMB, bombPos, 0); + }else{ + bombPos.z = waterZ; + CVector dir; + for(i = 0; i < 16; i++){ + dir.x = ((CGeneral::GetRandomNumber()&0xFF)-127)*0.001f; + dir.y = ((CGeneral::GetRandomNumber()&0xFF)-127)*0.001f; + dir.z = 0.5f; + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, bombPos, dir, nil, 0.2f); + } + } + } + } + + RemoveAndAdd(); + bIsInSafePosition = true; + GetMatrix().UpdateRW(); + UpdateRwFrame(); +} + +void +CHeli::PreRender(void) +{ + float angle; + uint8 i; + CColPoint point; + CEntity *entity; + uint8 r, g, b; + float testLowZ = FindPlayerCoors().z - 10.0f; + float radius = (GetPosition().z - FindPlayerCoors().z - 10.0f - 1.0f) * 0.3f + 10.0f; + int frm = CTimer::GetFrameCounter() & 7; + + i = 0; + for(angle = 0.0f; angle < TWOPI; angle += TWOPI/32){ + CVector pos(radius*Cos(angle), radius*Sin(angle), 0.0f); + CVector dir = CVector(pos.x, pos.y, 1.0f)*0.01f; + pos += GetPosition(); + + if(CWorld::ProcessVerticalLine(pos, testLowZ, point, entity, true, false, false, false, true, false, nil)) + m_fHeliDustZ[frm] = point.point.z; + else + m_fHeliDustZ[frm] = -101.0f; + + switch(point.surfaceB){ + default: + case SURFACE_TARMAC: + r = 10; + g = 10; + b = 10; + break; + case SURFACE_GRASS: + r = 10; + g = 6; + b = 3; + break; + case SURFACE_GRAVEL: + r = 10; + g = 8; + b = 7; + break; + case SURFACE_MUD_DRY: + r = 10; + g = 6; + b = 3; + break; + } + RwRGBA col = { r, g, b, 32 }; +#ifdef FIX_BUGS + pos.z = m_fHeliDustZ[frm]; +#else + // What the hell is the point of this? + pos.z = m_fHeliDustZ[(i - (i&3))/4]; // advance every 4 iterations, why not just /4? +#endif + if(pos.z > -200.0f && GetPosition().z - pos.z < 20.0f) + CParticle::AddParticle(PARTICLE_HELI_DUST, pos, dir, nil, 0.0f, col); + i++; + } +} + +void +CHeli::Render(void) +{ + CMatrix mat; + CVector pos; + + mat.Attach(RwFrameGetMatrix(m_aHeliNodes[HELI_TOPROTOR])); + pos = mat.GetPosition(); + mat.SetRotateZ(m_fRotorRotation); + mat.Translate(pos); + mat.UpdateRW(); + + m_fRotorRotation += 3.14f/6.5f; + if(m_fRotorRotation > 6.28f) + m_fRotorRotation -= 6.28f; + + mat.Attach(RwFrameGetMatrix(m_aHeliNodes[HELI_BACKROTOR])); + pos = mat.GetPosition(); + mat.SetRotateX(m_fRotorRotation); + mat.Translate(pos); + mat.UpdateRW(); + + CEntity::Render(); +} + +void +CHeli::PreRenderAlways(void) +{ + CVector shadowPos(m_fSearchLightX, m_fSearchLightY, GetPosition().z); + if(m_fSearchLightIntensity > 0.0f){ + CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &shadowPos, + 6.0f, 0.0f, 0.0f, -6.0f, + 80*m_fSearchLightIntensity, 80*m_fSearchLightIntensity, 80*m_fSearchLightIntensity, 80*m_fSearchLightIntensity, + 50.0f, true, 1.0f); + + CVector front = GetMatrix() * CVector(0.0f, 7.0f, 0.0f); + CVector toPlayer = FindPlayerCoors() - front; + toPlayer.Normalise(); + float intensity = m_fSearchLightIntensity*sq(CTimeCycle::GetSpriteBrightness()); + if(DotProduct(toPlayer, TheCamera.GetForward()) < -0.8f) + CCoronas::RegisterCorona((uintptr)this, 255*intensity, 255*intensity, 255*intensity, 255, + front, 10.0f, 60.0f, CCoronas::TYPE_STAR, + CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::RegisterCorona((uintptr)this, 200*intensity, 200*intensity, 200*intensity, 255, + front, 8.0f, 60.0f, CCoronas::TYPE_STAR, + CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } + + CVector back = GetMatrix() * CVector(0.0f, -9.0f, 0.0f); + if(CTimer::GetTimeInMilliseconds() & 0x100) + CCoronas::RegisterCorona((uintptr)this + 2, 255, 0, 0, 255, + back, 1.0f, 60.0f, CCoronas::TYPE_STAR, + CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::RegisterCorona((uintptr)this + 2, 0, 0, 0, 255, + back, 1.0f, 60.0f, CCoronas::TYPE_STAR, + CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); +} + +RwObject* +GetHeliAtomicObjectCB(RwObject *object, void *data) +{ + RpAtomic *atomic = (RpAtomic*)object; + assert(RwObjectGetType(object) == rpATOMIC); + if(RpAtomicGetFlags(atomic) & rpATOMICRENDER) + *(RpAtomic**)data = atomic; + return object; +} + +CObject* +CHeli::SpawnFlyingComponent(int32 component) +{ + RpAtomic *atomic; + RwFrame *frame; + RwMatrix *matrix; + CObject *obj; + + if(m_aHeliNodes[component] == nil) + return nil; + + atomic = nil; + RwFrameForAllObjects(m_aHeliNodes[component], GetHeliAtomicObjectCB, &atomic); + if(atomic == nil) + return nil; + + obj = new CObject; + if(obj == nil) + return nil; + + obj->SetModelIndexNoCreate(MI_CAR_WHEEL); + // object needs base model + obj->RefModelInfo(GetModelIndex()); + + // create new atomic + matrix = RwFrameGetLTM(m_aHeliNodes[component]); + frame = RwFrameCreate(); + atomic = RpAtomicClone(atomic); + *RwFrameGetMatrix(frame) = *matrix; + RpAtomicSetFrame(atomic, frame); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + obj->AttachToRwObject((RwObject*)atomic); + + // init object + obj->m_fMass = 10.0f; + obj->m_fTurnMass = 25.0f; + obj->m_fAirResistance = 0.99f; + obj->m_fElasticity = 0.1f; + obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f; + obj->ObjectCreatedBy = TEMP_OBJECT; + obj->SetIsStatic(false); + obj->bIsPickup = false; + + // life time + CObject::nNoTempObjects++; + if(component == HELI_TOPROTOR) + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 1000; + else + obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 3000; + + obj->m_vecMoveSpeed = m_vecMoveSpeed; + if(obj->m_vecMoveSpeed.z > 0.0f) + obj->m_vecMoveSpeed.z = 0.3f; + else + obj->m_vecMoveSpeed.z = 0.0f; + + obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f; + + if(component == HELI_BACKROTOR) + obj->m_vecTurnSpeed.x = 0.5f; + else if(component == HELI_TOPROTOR || component == HELI_TOPKNOT) + obj->m_vecTurnSpeed.z = 0.5f; + else + obj->m_vecTurnSpeed.y = 0.5f; + + obj->bRenderScorched = true; + + CWorld::Add(obj); + + atomic = nil; + RwFrameForAllObjects(m_aHeliNodes[component], GetHeliAtomicObjectCB, &atomic); + if(atomic) + RpAtomicSetFlags(atomic, 0); + + return obj; +} + + + +void +CHeli::InitHelis(void) +{ + int i; + + NumRandomHelis = 0; + TestForNewRandomHelisTimer = 0; + NumScriptHelis = 0; + CatalinaHeliOn = false; + ScriptHeliOn = false; + for(i = 0; i < NUM_HELIS; i++) + pHelis[i] = nil; + +#if GTA_VERSION >= GTA3_PS2_160 + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_ESCAPE))->SetColModel(&CTempColModels::ms_colModelPed1); + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_CHOPPER))->SetColModel(&CTempColModels::ms_colModelPed1); +#endif +} + +CHeli* +CHeli::GenerateHeli(bool catalina) +{ + CHeli *heli; + CVector heliPos; + int i; + +#if GTA_VERSION < GTA3_PS2_160 + if(catalina) + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_ESCAPE))->SetColModel(&CTempColModels::ms_colModelPed1); + else + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_CHOPPER))->SetColModel(&CTempColModels::ms_colModelPed1); +#endif + + if(catalina) + heli = new CHeli(MI_ESCAPE, PERMANENT_VEHICLE); + else + heli = new CHeli(MI_CHOPPER, PERMANENT_VEHICLE); + + if(catalina) + heliPos = CVector(-224.0f, 201.0f, 83.0f); + else{ + heliPos = FindPlayerCoors(); + float angle = (float)(CGeneral::GetRandomNumber() & 0xFF)/0x100 * 6.28f; + heliPos.x += 250.0f*Sin(angle); + heliPos.y += 250.0f*Cos(angle); + if(heliPos.x < -2000.0f || heliPos.x > 2000.0f || heliPos.y < -2000.0f || heliPos.y > 2000.0f){ + heliPos = FindPlayerCoors(); + heliPos.x -= 250.0f*Sin(angle); + heliPos.y -= 250.0f*Cos(angle); + } + heliPos.z += 50.0f; + } + heli->GetMatrix().SetTranslate(heliPos); + if(catalina) + heli->GetMatrix().SetRotateZOnly(DEGTORAD(270.0f)); // game actually uses 3.14 here + + heli->SetStatus(STATUS_ABANDONED); + heli->bIsLocked = true; + + int id = -1; + bool found = false; + while(!found){ + id++; + found = true; + for(i = 0; i < 4; i++) + if(pHelis[i] && pHelis[i]->m_nHeliId == id) + found = false; + } + heli->m_nHeliId = id; + + CWorld::Add(heli); + + return heli; +} + +void +CHeli::UpdateHelis(void) +{ + int i, j; + + // Spawn new police helis + int numHelisRequired = +#ifdef FIX_BUGS + CReplay::IsPlayingBack() ? 0 : +#endif + FindPlayerPed()->m_pWanted->NumOfHelisRequired(); + if(CStreaming::HasModelLoaded(MI_CHOPPER) && CTimer::GetTimeInMilliseconds() > TestForNewRandomHelisTimer){ + // Spawn a police heli + TestForNewRandomHelisTimer = CTimer::GetTimeInMilliseconds() + 15000; + if(NumRandomHelis < numHelisRequired){ + NumRandomHelis++; + CHeli *heli = GenerateHeli(false); + heli->m_heliType = HELI_TYPE_RANDOM; + if(pHelis[HELI_RANDOM0] == nil) + pHelis[HELI_RANDOM0] = heli; + else if(pHelis[HELI_RANDOM1] == nil) + pHelis[HELI_RANDOM1] = heli; + else + assert(0 && "too many helis"); + } + } + + // Handle script heli + if(ScriptHeliOn){ + if(CStreaming::HasModelLoaded(MI_CHOPPER) && pHelis[HELI_SCRIPT] == nil){ + pHelis[HELI_SCRIPT] = GenerateHeli(false); + pHelis[HELI_SCRIPT]->m_heliType = HELI_TYPE_SCRIPT; + }else + CStreaming::RequestModel(MI_CHOPPER, 0); + }else{ + if(pHelis[HELI_SCRIPT]) + pHelis[HELI_SCRIPT]->m_heliStatus = HELI_STATUS_FLY_AWAY; + } + + // Handle Catalina's heli + if(CatalinaHeliOn){ + if(CStreaming::HasModelLoaded(MI_ESCAPE) && pHelis[HELI_CATALINA] == nil){ + pHelis[HELI_CATALINA] = GenerateHeli(true); + pHelis[HELI_CATALINA]->m_heliType = HELI_TYPE_CATALINA; + }else + CStreaming::RequestModel(MI_ESCAPE, STREAMFLAGS_DONT_REMOVE); + }else{ + if(pHelis[HELI_CATALINA]) + pHelis[HELI_CATALINA]->m_heliStatus = HELI_STATUS_FLY_AWAY; + } + + // Delete helis that we no longer need + for(i = 0; i < NUM_HELIS; i++) + if(pHelis[i] && pHelis[i]->m_heliStatus == HELI_STATUS_FLY_AWAY && pHelis[i]->GetPosition().z > 150.0f){ + CWorld::Remove(pHelis[i]); + delete pHelis[i]; + pHelis[i] = nil; + if(i != HELI_SCRIPT && i != HELI_CATALINA) + NumRandomHelis--; + } + + // Handle explosions + for(i = 0; i < NUM_HELIS; i++){ + if(pHelis[i] && pHelis[i]->m_heliStatus == HELI_STATUS_SHOT_DOWN && CTimer::GetTimeInMilliseconds() > pHelis[i]->m_nExplosionTimer){ + // Second part of explosion + static int nFrameGen; + CRGBA colors[8]; + + TheCamera.CamShake(0.7f, pHelis[i]->GetPosition().x, pHelis[i]->GetPosition().y, pHelis[i]->GetPosition().z); + + colors[0] = CRGBA(0, 0, 0, 255); + colors[1] = CRGBA(224, 230, 238, 255); + colors[2] = CRGBA(0, 0, 0, 255); + colors[3] = CRGBA(0, 0, 0, 255); + colors[4] = CRGBA(66, 162, 252, 255); + colors[5] = CRGBA(0, 0, 0, 255); + colors[6] = CRGBA(0, 0, 0, 255); + colors[7] = CRGBA(0, 0, 0, 255); + + CVector pos = pHelis[i]->GetPosition(); + CVector dir; + for(j = 0; j < 40; j++){ + dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f); + int rotSpeed = CGeneral::GetRandomNumberInRange(10, 30); + if(CGeneral::GetRandomNumber() & 1) + rotSpeed = -rotSpeed; + int f = ++nFrameGen & 3; + CParticle::AddParticle(PARTICLE_HELI_DEBRIS, pos, dir, + nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f), + colors[nFrameGen], rotSpeed, 0, f, 0); + } + + CExplosion::AddExplosion(nil, nil, EXPLOSION_HELI, pos, 0); + + pHelis[i]->SpawnFlyingComponent(HELI_SKID_LEFT); + pHelis[i]->SpawnFlyingComponent(HELI_SKID_RIGHT); + pHelis[i]->SpawnFlyingComponent(HELI_TOPROTOR); + + CDarkel::RegisterCarBlownUpByPlayer(pHelis[i]); + CWorld::Remove(pHelis[i]); + delete pHelis[i]; + pHelis[i] = nil; + if(i != HELI_SCRIPT && i != HELI_CATALINA) + NumRandomHelis--; + if(i == HELI_CATALINA) + CatalinaHasBeenShotDown = true; + + CStats::HelisDestroyed++; + CStats::PeopleKilledByPlayer += 2; + CStats::PedsKilledOfThisType[PEDTYPE_COP] += 2; + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 250; + pos = CWorld::Players[CWorld::PlayerInFocus].m_pPed->GetPosition(); + CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->RegisterCrime_Immediately(CRIME_SHOOT_HELI, + pos, i + 19843, false); + + TestForNewRandomHelisTimer = CTimer::GetTimeInMilliseconds() + 50000; + }else if(pHelis[i] && pHelis[i]->m_heliStatus == HELI_STATUS_SHOT_DOWN && CTimer::GetTimeInMilliseconds()+7000 > pHelis[i]->m_nExplosionTimer){ + // First part of explosion + if(CTimer::GetPreviousTimeInMilliseconds()+7000 < pHelis[i]->m_nExplosionTimer){ + pHelis[i]->SpawnFlyingComponent(HELI_BACKROTOR); + pHelis[i]->SpawnFlyingComponent(HELI_TAIL); + pHelis[i]->m_fAngularSpeed *= -2.5f; + pHelis[i]->bRenderScorched = true; + + TheCamera.CamShake(0.4f, pHelis[i]->GetPosition().x, pHelis[i]->GetPosition().y, pHelis[i]->GetPosition().z); + + CVector pos = pHelis[i]->GetPosition() - 2.5f*pHelis[i]->GetForward(); + CExplosion::AddExplosion(nil, nil, EXPLOSION_HELI, pos, 0); + }else + pHelis[i]->m_fAngularSpeed *= 1.03f; + } + } + + // Find police helis to remove + for(i = 0; i < 2; i++) + if(pHelis[i] && pHelis[i]->m_heliStatus != HELI_STATUS_FLY_AWAY){ + if(numHelisRequired > 0) + numHelisRequired--; + else + pHelis[i]->m_heliStatus = HELI_STATUS_FLY_AWAY; + } + + // Remove all helis if in a tunnel or under water + if(FindPlayerCoors().z < - 2.0f) + for(i = 0; i < NUM_HELIS; i++) + if(pHelis[i] && pHelis[i]->m_heliStatus != HELI_STATUS_SHOT_DOWN) + pHelis[i]->m_heliStatus = HELI_STATUS_FLY_AWAY; +} + +void +CHeli::SpecialHeliPreRender(void) +{ + int i; + for(i = 0; i < NUM_HELIS; i++) + if(pHelis[i]) + pHelis[i]->PreRenderAlways(); +} + +bool +CHeli::TestRocketCollision(CVector *rocketPos) +{ + int i; + bool hit = false; + + for(i = 0; i < NUM_HELIS; i++){ + if(pHelis[i] && !pHelis[i]->bExplosionProof && (*rocketPos - pHelis[i]->GetPosition()).MagnitudeSqr() < sq(8.0f)){ + pHelis[i]->m_fAngularSpeed = CGeneral::GetRandomTrueFalse() ? 0.05f : -0.05f; + pHelis[i]->m_heliStatus = HELI_STATUS_SHOT_DOWN; + pHelis[i]->m_nExplosionTimer = CTimer::GetTimeInMilliseconds() + 10000; + hit = true; + } + } + return hit; +} + +bool +CHeli::TestBulletCollision(CVector *line0, CVector *line1, CVector *bulletPos, int32 damage) +{ + int i; + bool hit = false; + + for(i = 0; i < NUM_HELIS; i++) + if(pHelis[i] && !pHelis[i]->bBulletProof && CCollision::DistToLine(line0, line1, &pHelis[i]->GetPosition()) < 5.0f){ + // Find bullet position + float distToHeli = (pHelis[i]->GetPosition() - *line0).Magnitude(); + CVector line = (*line1 - *line0); + float lineLength = line.Magnitude(); + *bulletPos = *line0 + line*Max(1.0f, distToHeli-5.0f)/lineLength; + + pHelis[i]->m_nBulletDamage += damage; + + if(pHelis[i]->m_heliType == HELI_CATALINA && pHelis[i]->m_nBulletDamage > 400 || + pHelis[i]->m_heliType != HELI_CATALINA && pHelis[i]->m_nBulletDamage > 700){ + pHelis[i]->m_fAngularSpeed = CGeneral::GetRandomTrueFalse() ? 0.05f : -0.05f; + pHelis[i]->m_heliStatus = HELI_STATUS_SHOT_DOWN; + pHelis[i]->m_nExplosionTimer = CTimer::GetTimeInMilliseconds() + 10000; + } + + hit = true; + } + return hit; +} + +void CHeli::StartCatalinaFlyBy(void) +{ + CatalinaHeliOn = true; + CatalinaHasBeenShotDown = false; +} + +void +CHeli::RemoveCatalinaHeli(void) +{ + CatalinaHeliOn = false; + if(pHelis[HELI_CATALINA]){ + CWorld::Remove(pHelis[HELI_CATALINA]); + delete pHelis[HELI_CATALINA]; + pHelis[HELI_CATALINA] = nil; + } +} + +CHeli *CHeli::FindPointerToCatalinasHeli(void) { return pHelis[HELI_CATALINA]; } +void CHeli::CatalinaTakeOff(void) { pHelis[HELI_CATALINA]->m_pathState = 8; } +void CHeli::MakeCatalinaHeliFlyAway(void) { pHelis[HELI_CATALINA]->m_pathState = 12; } +bool CHeli::HasCatalinaBeenShotDown(void) { return CatalinaHasBeenShotDown; } + +void CHeli::ActivateHeli(bool activate) { ScriptHeliOn = activate; } diff --git a/src/vehicles/Heli.h b/src/vehicles/Heli.h new file mode 100644 index 0000000..5fef799 --- /dev/null +++ b/src/vehicles/Heli.h @@ -0,0 +1,101 @@ +#pragma once + +#include "Vehicle.h" + +class CObject; + +enum eHeliNodes +{ + HELI_CHASSIS = 1, + HELI_TOPROTOR, + HELI_BACKROTOR, + HELI_TAIL, + HELI_TOPKNOT, + HELI_SKID_LEFT, + HELI_SKID_RIGHT, + NUM_HELI_NODES +}; + +enum +{ + HELI_RANDOM0, + HELI_RANDOM1, + HELI_SCRIPT, + HELI_CATALINA, + NUM_HELIS +}; + +enum +{ + HELI_TYPE_RANDOM, + HELI_TYPE_SCRIPT, + HELI_TYPE_CATALINA, +}; + + +class CHeli : public CVehicle +{ +public: + // 0x288 + RwFrame *m_aHeliNodes[NUM_HELI_NODES]; + int8 m_heliStatus; + float m_fSearchLightX; + float m_fSearchLightY; + uint32 m_nExplosionTimer; + float m_fRotation; + float m_fAngularSpeed; + float m_fTargetZ; + float m_fSearchLightIntensity; + int8 m_nHeliId; + int8 m_heliType; + int8 m_pathState; + float m_aSearchLightHistoryX[6]; + float m_aSearchLightHistoryY[6]; + uint32 m_nSearchLightTimer; + uint32 m_nShootTimer; + uint32 m_nLastShotTime; + uint32 m_nBulletDamage; + float m_fRotorRotation; + float m_fHeliDustZ[8]; + uint32 m_nPoliceShoutTimer; + float m_fTargetOffset; + bool m_bTestRight; + + static CHeli *pHelis[NUM_HELIS]; + static int16 NumRandomHelis; + static uint32 TestForNewRandomHelisTimer; + static int16 NumScriptHelis; // unused + static bool CatalinaHeliOn; + static bool CatalinaHasBeenShotDown; + static bool ScriptHeliOn; + + CHeli(int32 id, uint8 CreatedBy); + + // from CEntity + void SetModelIndex(uint32 id); + void ProcessControl(void); + void PreRender(void); + void Render(void); + + void PreRenderAlways(void); + CObject *SpawnFlyingComponent(int32 component); + + static void InitHelis(void); + static CHeli *GenerateHeli(bool catalina); // out of class in III PC and later because of SecuROM + static void UpdateHelis(void); + static void SpecialHeliPreRender(void); + static bool TestRocketCollision(CVector *coors); + static bool TestBulletCollision(CVector *line0, CVector *line1, CVector *bulletPos, int32 damage); + + static void StartCatalinaFlyBy(void); // out of class in III PC and later because of SecuROM + static void RemoveCatalinaHeli(void); + static CHeli *FindPointerToCatalinasHeli(void); + static void CatalinaTakeOff(void); + static void MakeCatalinaHeliFlyAway(void); + static bool HasCatalinaBeenShotDown(void); + + static void ActivateHeli(bool activate); +}; + +VALIDATE_SIZE(CHeli, 0x33C); + diff --git a/src/vehicles/Plane.cpp b/src/vehicles/Plane.cpp new file mode 100644 index 0000000..8ea03bf --- /dev/null +++ b/src/vehicles/Plane.cpp @@ -0,0 +1,975 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "ModelIndices.h" +#include "FileMgr.h" +#include "Streaming.h" +#include "Replay.h" +#include "Camera.h" +#include "DMAudio.h" +#include "Wanted.h" +#include "Coronas.h" +#include "Particle.h" +#include "Explosion.h" +#include "World.h" +#include "HandlingMgr.h" +#include "Plane.h" +#include "MemoryHeap.h" + +CPlaneNode *pPathNodes; +CPlaneNode *pPath2Nodes; +CPlaneNode *pPath3Nodes; +CPlaneNode *pPath4Nodes; +int32 NumPathNodes; +int32 NumPath2Nodes; +int32 NumPath3Nodes; +int32 NumPath4Nodes; +float TotalLengthOfFlightPath; +float TotalLengthOfFlightPath2; +float TotalLengthOfFlightPath3; +float TotalLengthOfFlightPath4; +float TotalDurationOfFlightPath; +float TotalDurationOfFlightPath2; +float TotalDurationOfFlightPath3; +float TotalDurationOfFlightPath4; +float LandingPoint; +float TakeOffPoint; +CPlaneInterpolationLine aPlaneLineBits[6]; + +float PlanePathPosition[3]; +float OldPlanePathPosition[3]; +float PlanePathSpeed[3]; +float PlanePath2Position[3]; +float PlanePath3Position; +float PlanePath4Position; +float PlanePath2Speed[3]; +float PlanePath3Speed; +float PlanePath4Speed; + + +enum +{ + CESNA_STATUS_NONE, // doesn't even exist + CESNA_STATUS_FLYING, + CESNA_STATUS_DESTROYED, + CESNA_STATUS_LANDED, +}; + +int32 CesnaMissionStatus; +int32 CesnaMissionStartTime; +CPlane *pDrugRunCesna; +int32 DropOffCesnaMissionStatus; +int32 DropOffCesnaMissionStartTime; +CPlane *pDropOffCesna; + + +CPlane::CPlane(int32 id, uint8 CreatedBy) + : CVehicle(CreatedBy) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + m_vehType = VEHICLE_TYPE_PLANE; + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + SetModelIndex(id); + + m_fMass = 100000000.0f; + m_fTurnMass = 100000000.0f; + m_fAirResistance = 0.9994f; + m_fElasticity = 0.05f; + + bUsesCollision = false; + m_bHasBeenHit = false; + m_bIsDrugRunCesna = false; + m_bIsDropOffCesna = false; + + SetStatus(STATUS_PLANE); + bIsBIGBuilding = true; + m_level = LEVEL_GENERIC; + +#ifdef FIX_BUGS + m_isFarAway = false; +#endif +} + +CPlane::~CPlane() +{ + DeleteRwObject(); +} + +void +CPlane::SetModelIndex(uint32 id) +{ + CVehicle::SetModelIndex(id); +} + +void +CPlane::DeleteRwObject(void) +{ + if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){ + GetMatrix().Detach(); + if(RwObjectGetType(m_rwObject) == rpATOMIC){ // useless check + RwFrame *f = RpAtomicGetFrame((RpAtomic*)m_rwObject); + RpAtomicDestroy((RpAtomic*)m_rwObject); + RwFrameDestroy(f); + } + m_rwObject = nil; + } + CEntity::DeleteRwObject(); +} + +// There's a LOT of copy and paste in here. Maybe this could be refactored somehow +void +CPlane::ProcessControl(void) +{ + int i; + CVector pos; + + // Explosion + if(m_bHasBeenHit){ + // BUG: since this is all based on frames, you can skip the explosion processing when you go into the menu + if(GetModelIndex() == MI_AIRTRAIN){ + int frm = CTimer::GetFrameCounter() - m_nFrameWhenHit; + if(frm == 20){ + static int nFrameGen; + CRGBA colors[8]; + + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), 0); + + colors[0] = CRGBA(0, 0, 0, 255); + colors[1] = CRGBA(224, 230, 238, 255); + colors[2] = CRGBA(224, 230, 238, 255); + colors[3] = CRGBA(0, 0, 0, 255); + colors[4] = CRGBA(224, 230, 238, 255); + colors[5] = CRGBA(0, 0, 0, 255); + colors[6] = CRGBA(0, 0, 0, 255); + colors[7] = CRGBA(224, 230, 238, 255); + + CVector dir; + for(i = 0; i < 40; i++){ + dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f); + int rotSpeed = CGeneral::GetRandomNumberInRange(10, 30); + if(CGeneral::GetRandomNumber() & 1) + rotSpeed = -rotSpeed; + int f = ++nFrameGen & 3; + CParticle::AddParticle(PARTICLE_HELI_DEBRIS, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), dir, + nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f), + colors[nFrameGen&7], rotSpeed, 0, f, 0); + } + } + if(frm >= 40 && frm <= 80 && frm & 1){ + if(frm & 1){ + pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f; + pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f; + pos.y = frm - 40; + pos = GetMatrix() * pos; + }else{ + pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f; + pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f; + pos.y = 40 - frm; + pos = GetMatrix() * pos; + } + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, pos, 0); + } + if(frm == 60) + bRenderScorched = true; + if(frm == 82){ + TheCamera.SetFadeColour(255, 255, 255); + TheCamera.Fade(0.0f, FADE_OUT); + TheCamera.ProcessFade(); + TheCamera.Fade(1.0f, FADE_IN); + FlagToDestroyWhenNextProcessed(); + } + }else{ + int frm = CTimer::GetFrameCounter() - m_nFrameWhenHit; + if(frm == 20){ + static int nFrameGen; + CRGBA colors[8]; + + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), 0); + + colors[0] = CRGBA(0, 0, 0, 255); + colors[1] = CRGBA(224, 230, 238, 255); + colors[2] = CRGBA(224, 230, 238, 255); + colors[3] = CRGBA(0, 0, 0, 255); + colors[4] = CRGBA(252, 66, 66, 255); + colors[5] = CRGBA(0, 0, 0, 255); + colors[6] = CRGBA(0, 0, 0, 255); + colors[7] = CRGBA(252, 66, 66, 255); + + CVector dir; + for(i = 0; i < 40; i++){ + dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f); + dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f); + int rotSpeed = CGeneral::GetRandomNumberInRange(30.0f, 20.0f); + if(CGeneral::GetRandomNumber() & 1) + rotSpeed = -rotSpeed; + int f = ++nFrameGen & 3; + CParticle::AddParticle(PARTICLE_HELI_DEBRIS, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), dir, + nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f), + colors[nFrameGen&7], rotSpeed, 0, f, 0); + } + } + if(frm >= 40 && frm <= 60 && frm & 1){ + if(frm & 1){ + pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f; + pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f; + pos.y = (frm - 40)*0.3f; + pos = GetMatrix() * pos; + }else{ + pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f; + pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f; + pos.y = (40 - frm)*0.3f; + pos = GetMatrix() * pos; + } + CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, pos, 0); + } + if(frm == 30) + bRenderScorched = true; + if(frm == 62){ + TheCamera.SetFadeColour(200, 200, 200); + TheCamera.Fade(0.0f, FADE_OUT); + TheCamera.ProcessFade(); + TheCamera.Fade(1.0f, FADE_IN); + if(m_bIsDrugRunCesna){ + CesnaMissionStatus = CESNA_STATUS_DESTROYED; + pDrugRunCesna = nil; + } + if(m_bIsDropOffCesna){ + DropOffCesnaMissionStatus = CESNA_STATUS_DESTROYED; + pDropOffCesna = nil; + } + FlagToDestroyWhenNextProcessed(); + } + } + } + + // Update plane position and speed + if(GetModelIndex() == MI_AIRTRAIN || !m_isFarAway || ((CTimer::GetFrameCounter() + m_randomSeed) & 7) == 0){ + if(GetModelIndex() == MI_AIRTRAIN){ + float pathPositionRear = PlanePathPosition[m_nPlaneId] - 30.0f; + if(pathPositionRear < 0.0f) + pathPositionRear += TotalLengthOfFlightPath; + float pathPosition = pathPositionRear + 30.0f; + + float pitch = 0.0f; + float distSinceTakeOff = pathPosition - TakeOffPoint; + if(distSinceTakeOff <= 0.0f && distSinceTakeOff > -70.0f){ + // shortly before take off + pitch = 1.0f - distSinceTakeOff/-70.0f; + }else if(distSinceTakeOff >= 0.0f && distSinceTakeOff < 100.0f){ + // shortly after take off + pitch = 1.0f - distSinceTakeOff/100.0f; + } + + float distSinceLanding = pathPosition - LandingPoint; + if(distSinceLanding <= 0.0f && distSinceLanding > -200.0f){ + // shortly before landing + pitch = 1.0f - distSinceLanding/-200.0f; + }else if(distSinceLanding >= 0.0f && distSinceLanding < 70.0f){ + // shortly after landing + pitch = 1.0f - distSinceLanding/70.0f; + } + + + // Advance current node to appropriate position + float pos1, pos2; + int nextTrackNode = m_nCurPathNode + 1; + pos1 = pPathNodes[m_nCurPathNode].t; + if(nextTrackNode < NumPathNodes) + pos2 = pPathNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = TotalLengthOfFlightPath; + } + while(pathPositionRear < pos1 || pathPositionRear > pos2){ + m_nCurPathNode = (m_nCurPathNode+1) % NumPathNodes; + nextTrackNode = m_nCurPathNode + 1; + pos1 = pPathNodes[m_nCurPathNode].t; + if(nextTrackNode < NumPathNodes) + pos2 = pPathNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = TotalLengthOfFlightPath; + } + } + bool bothOnGround = pPathNodes[m_nCurPathNode].bOnGround && pPathNodes[nextTrackNode].bOnGround; + if(PlanePathPosition[m_nPlaneId] >= LandingPoint && OldPlanePathPosition[m_nPlaneId] < LandingPoint) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_PLANE_ON_GROUND, 0.0f); + float dist = pPathNodes[nextTrackNode].t - pPathNodes[m_nCurPathNode].t; + if(dist < 0.0f) + dist += TotalLengthOfFlightPath; + float f = (pathPositionRear - pPathNodes[m_nCurPathNode].t)/dist; + CVector posRear = (1.0f - f)*pPathNodes[m_nCurPathNode].p + f*pPathNodes[nextTrackNode].p; + + // Same for the front + float pathPositionFront = pathPositionRear + 60.0f; + if(pathPositionFront > TotalLengthOfFlightPath) + pathPositionFront -= TotalLengthOfFlightPath; + int curPathNodeFront = m_nCurPathNode; + int nextPathNodeFront = curPathNodeFront + 1; + pos1 = pPathNodes[curPathNodeFront].t; + if(nextPathNodeFront < NumPathNodes) + pos2 = pPathNodes[nextPathNodeFront].t; + else{ + nextPathNodeFront = 0; + pos2 = TotalLengthOfFlightPath; + } + while(pathPositionFront < pos1 || pathPositionFront > pos2){ + curPathNodeFront = (curPathNodeFront+1) % NumPathNodes; + nextPathNodeFront = curPathNodeFront + 1; + pos1 = pPathNodes[curPathNodeFront].t; + if(nextPathNodeFront < NumPathNodes) + pos2 = pPathNodes[nextPathNodeFront].t; + else{ + nextPathNodeFront = 0; + pos2 = TotalLengthOfFlightPath; + } + } + dist = pPathNodes[nextPathNodeFront].t - pPathNodes[curPathNodeFront].t; + if(dist < 0.0f) + dist += TotalLengthOfFlightPath; + f = (pathPositionFront - pPathNodes[curPathNodeFront].t)/dist; + CVector posFront = (1.0f - f)*pPathNodes[curPathNodeFront].p + f*pPathNodes[nextPathNodeFront].p; + + // And for another point 60 units in front of the plane, used to calculate roll + float pathPositionFront2 = pathPositionFront + 60.0f; + if(pathPositionFront2 > TotalLengthOfFlightPath) + pathPositionFront2 -= TotalLengthOfFlightPath; + int curPathNodeFront2 = m_nCurPathNode; + int nextPathNodeFront2 = curPathNodeFront2 + 1; + pos1 = pPathNodes[curPathNodeFront2].t; + if(nextPathNodeFront2 < NumPathNodes) + pos2 = pPathNodes[nextPathNodeFront2].t; + else{ + nextPathNodeFront2 = 0; + pos2 = TotalLengthOfFlightPath; + } + while(pathPositionFront2 < pos1 || pathPositionFront2 > pos2){ + curPathNodeFront2 = (curPathNodeFront2+1) % NumPathNodes; + nextPathNodeFront2 = curPathNodeFront2 + 1; + pos1 = pPathNodes[curPathNodeFront2].t; + if(nextPathNodeFront2 < NumPathNodes) + pos2 = pPathNodes[nextPathNodeFront2].t; + else{ + nextPathNodeFront2 = 0; + pos2 = TotalLengthOfFlightPath; + } + } + dist = pPathNodes[nextPathNodeFront2].t - pPathNodes[curPathNodeFront2].t; + if(dist < 0.0f) + dist += TotalLengthOfFlightPath; + f = (pathPositionFront2 - pPathNodes[curPathNodeFront2].t)/dist; + CVector posFront2 = (1.0f - f)*pPathNodes[curPathNodeFront2].p + f*pPathNodes[nextPathNodeFront2].p; + + // Now set matrix + GetMatrix().SetTranslateOnly((posRear + posFront) / 2.0f); + GetMatrix().GetPosition().z += 4.3f; + CVector fwd = posFront - posRear; + fwd.Normalise(); + if(pitch != 0.0f){ + fwd.z += 0.4f*pitch; + fwd.Normalise(); + } + CVector fwd2 = posFront2 - posRear; + fwd2.Normalise(); + CVector roll = CrossProduct(fwd, fwd2); + CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f)); + if(!bothOnGround) + right.z += 3.0f*roll.z; + right.Normalise(); + CVector up = CrossProduct(right, fwd); + GetMatrix().GetRight() = right; + GetMatrix().GetUp() = up; + GetMatrix().GetForward() = fwd; + + // Set speed + m_vecMoveSpeed = fwd*PlanePathSpeed[m_nPlaneId]/60.0f; + m_fSpeed = PlanePathSpeed[m_nPlaneId]/60.0f; + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + + m_isFarAway = !((posFront - TheCamera.GetPosition()).MagnitudeSqr2D() < sq(300.0f)); + }else{ + float planePathPosition; + float totalLengthOfFlightPath; + CPlaneNode *pathNodes; + float planePathSpeed; + int numPathNodes; + + if(m_bIsDrugRunCesna){ + planePathPosition = PlanePath3Position; + totalLengthOfFlightPath = TotalLengthOfFlightPath3; + pathNodes = pPath3Nodes; + planePathSpeed = PlanePath3Speed; + numPathNodes = NumPath3Nodes; + if(CesnaMissionStatus == CESNA_STATUS_LANDED){ + pDrugRunCesna = nil; + FlagToDestroyWhenNextProcessed(); + } + }else if(m_bIsDropOffCesna){ + planePathPosition = PlanePath4Position; + totalLengthOfFlightPath = TotalLengthOfFlightPath4; + pathNodes = pPath4Nodes; + planePathSpeed = PlanePath4Speed; + numPathNodes = NumPath4Nodes; + if(DropOffCesnaMissionStatus == CESNA_STATUS_LANDED){ + pDropOffCesna = nil; + FlagToDestroyWhenNextProcessed(); + } + }else{ + planePathPosition = PlanePath2Position[m_nPlaneId]; + totalLengthOfFlightPath = TotalLengthOfFlightPath2; + pathNodes = pPath2Nodes; + planePathSpeed = PlanePath2Speed[m_nPlaneId]; + numPathNodes = NumPath2Nodes; + } + + // Advance current node to appropriate position + float pathPositionRear = planePathPosition - 10.0f; + if(pathPositionRear < 0.0f) + pathPositionRear += totalLengthOfFlightPath; + float pos1, pos2; + int nextTrackNode = m_nCurPathNode + 1; + pos1 = pathNodes[m_nCurPathNode].t; + if(nextTrackNode < numPathNodes) + pos2 = pathNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = totalLengthOfFlightPath; + } + while(pathPositionRear < pos1 || pathPositionRear > pos2){ + m_nCurPathNode = (m_nCurPathNode+1) % numPathNodes; + nextTrackNode = m_nCurPathNode + 1; + pos1 = pathNodes[m_nCurPathNode].t; + if(nextTrackNode < numPathNodes) + pos2 = pathNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = totalLengthOfFlightPath; + } + } + float dist = pathNodes[nextTrackNode].t - pathNodes[m_nCurPathNode].t; + if(dist < 0.0f) + dist += totalLengthOfFlightPath; + float f = (pathPositionRear - pathNodes[m_nCurPathNode].t)/dist; + CVector posRear = (1.0f - f)*pathNodes[m_nCurPathNode].p + f*pathNodes[nextTrackNode].p; + + // Same for the front + float pathPositionFront = pathPositionRear + 20.0f; + if(pathPositionFront > totalLengthOfFlightPath) + pathPositionFront -= totalLengthOfFlightPath; + int curPathNodeFront = m_nCurPathNode; + int nextPathNodeFront = curPathNodeFront + 1; + pos1 = pathNodes[curPathNodeFront].t; + if(nextPathNodeFront < numPathNodes) + pos2 = pathNodes[nextPathNodeFront].t; + else{ + nextPathNodeFront = 0; + pos2 = totalLengthOfFlightPath; + } + while(pathPositionFront < pos1 || pathPositionFront > pos2){ + curPathNodeFront = (curPathNodeFront+1) % numPathNodes; + nextPathNodeFront = curPathNodeFront + 1; + pos1 = pathNodes[curPathNodeFront].t; + if(nextPathNodeFront < numPathNodes) + pos2 = pathNodes[nextPathNodeFront].t; + else{ + nextPathNodeFront = 0; + pos2 = totalLengthOfFlightPath; + } + } + dist = pathNodes[nextPathNodeFront].t - pathNodes[curPathNodeFront].t; + if(dist < 0.0f) + dist += totalLengthOfFlightPath; + f = (pathPositionFront - pathNodes[curPathNodeFront].t)/dist; + CVector posFront = (1.0f - f)*pathNodes[curPathNodeFront].p + f*pathNodes[nextPathNodeFront].p; + + // And for another point 30 units in front of the plane, used to calculate roll + float pathPositionFront2 = pathPositionFront + 30.0f; + if(pathPositionFront2 > totalLengthOfFlightPath) + pathPositionFront2 -= totalLengthOfFlightPath; + int curPathNodeFront2 = m_nCurPathNode; + int nextPathNodeFront2 = curPathNodeFront2 + 1; + pos1 = pathNodes[curPathNodeFront2].t; + if(nextPathNodeFront2 < numPathNodes) + pos2 = pathNodes[nextPathNodeFront2].t; + else{ + nextPathNodeFront2 = 0; + pos2 = totalLengthOfFlightPath; + } + while(pathPositionFront2 < pos1 || pathPositionFront2 > pos2){ + curPathNodeFront2 = (curPathNodeFront2+1) % numPathNodes; + nextPathNodeFront2 = curPathNodeFront2 + 1; + pos1 = pathNodes[curPathNodeFront2].t; + if(nextPathNodeFront2 < numPathNodes) + pos2 = pathNodes[nextPathNodeFront2].t; + else{ + nextPathNodeFront2 = 0; + pos2 = totalLengthOfFlightPath; + } + } + dist = pathNodes[nextPathNodeFront2].t - pathNodes[curPathNodeFront2].t; + if(dist < 0.0f) + dist += totalLengthOfFlightPath; + f = (pathPositionFront2 - pathNodes[curPathNodeFront2].t)/dist; + CVector posFront2 = (1.0f - f)*pathNodes[curPathNodeFront2].p + f*pathNodes[nextPathNodeFront2].p; + + // Now set matrix + GetMatrix().SetTranslateOnly((posRear + posFront) / 2.0f); + GetMatrix().GetPosition().z += 1.0f; + CVector fwd = posFront - posRear; + fwd.Normalise(); + CVector fwd2 = posFront2 - posRear; + fwd2.Normalise(); + CVector roll = CrossProduct(fwd, fwd2); + CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f)); + right.z += 3.0f*roll.z; + right.Normalise(); + CVector up = CrossProduct(right, fwd); + GetMatrix().GetRight() = right; + GetMatrix().GetUp() = up; + GetMatrix().GetForward() = fwd; + + // Set speed + m_vecMoveSpeed = fwd*planePathSpeed/60.0f; + m_fSpeed = planePathSpeed/60.0f; + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + + m_isFarAway = !((posFront - TheCamera.GetPosition()).MagnitudeSqr2D() < sq(300.0f)); + } + } + + bIsInSafePosition = true; + GetMatrix().UpdateRW(); + UpdateRwFrame(); + + // Handle streaming and such + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + if(m_isFarAway){ + // Switch to LOD model + if(m_rwObject && RwObjectGetType(m_rwObject) == rpCLUMP){ + DeleteRwObject(); + if(mi->m_planeLodId != -1){ + PUSH_MEMID(MEMID_WORLD); + m_rwObject = CModelInfo::GetModelInfo(mi->m_planeLodId)->CreateInstance(); + POP_MEMID(); + if(m_rwObject) + GetMatrix().AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic*)m_rwObject))); + } + } + }else if(CStreaming::HasModelLoaded(GetModelIndex())){ + if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){ + // Get rid of LOD model + GetMatrix().Detach(); + if(m_rwObject){ // useless check + if(RwObjectGetType(m_rwObject) == rpATOMIC){ // useless check + RwFrame *f = RpAtomicGetFrame((RpAtomic*)m_rwObject); + RpAtomicDestroy((RpAtomic*)m_rwObject); + RwFrameDestroy(f); + } + m_rwObject = nil; + } + } + // Set high detail model + if(m_rwObject == nil){ + int id = GetModelIndex(); + m_modelIndex = -1; + SetModelIndex(id); + } + }else{ + CStreaming::RequestModel(GetModelIndex(), STREAMFLAGS_DEPENDENCY); + } +} + +void +CPlane::PreRender(void) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + CVector lookVector = GetPosition() - TheCamera.GetPosition(); + float camDist = lookVector.Magnitude(); + if(camDist != 0.0f) + lookVector *= 1.0f/camDist; + else + lookVector = CVector(1.0f, 0.0f, 0.0f); + float behindness = DotProduct(lookVector, GetForward()); + + // Wing lights + if(behindness < 0.0f){ + // in front of plane + CVector lightPos = mi->m_positions[PLANE_POS_LIGHT_RIGHT]; + CVector lightR = GetMatrix() * lightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*lightPos.x; + + float intensity = -0.6f*behindness + 0.4f; + float size = 1.0f - behindness; + + if(behindness < -0.9f && camDist < 50.0f){ + // directly in front + CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255, + lightL, size, 240.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255, + lightR, size, 240.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + }else{ + CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255, + lightL, size, 240.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255, + lightR, size, 240.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + } + } + + // Tail light + if(CTimer::GetTimeInMilliseconds() & 0x200){ + CVector pos = GetMatrix() * mi->m_positions[PLANE_POS_LIGHT_TAIL]; + + CCoronas::RegisterCorona((uintptr)this + 12, 255, 0, 0, 255, + pos, 1.0f, 120.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + } +} + +void +CPlane::Render(void) +{ + CEntity::Render(); +} + +#define CRUISE_SPEED (50.0f) +#define TAXI_SPEED (5.0f) + +void +CPlane::InitPlanes(void) +{ + int i; + + CesnaMissionStatus = CESNA_STATUS_NONE; + + // Jumbo + if(pPathNodes == nil){ + pPathNodes = LoadPath("data\\paths\\flight.dat", NumPathNodes, TotalLengthOfFlightPath, true); + + // Figure out which nodes are on ground + CColPoint colpoint; + CEntity *entity; + for(i = 0; i < NumPathNodes; i++){ + if(CWorld::ProcessVerticalLine(pPathNodes[i].p, 1000.0f, colpoint, entity, true, false, false, false, true, false, nil)){ + pPathNodes[i].p.z = colpoint.point.z; + pPathNodes[i].bOnGround = true; + }else + pPathNodes[i].bOnGround = false; + } + + // Find lading and takeoff points + LandingPoint = -1.0f; + TakeOffPoint = -1.0f; + bool lastOnGround = pPathNodes[NumPathNodes-1].bOnGround; + for(i = 0; i < NumPathNodes; i++){ + if(pPathNodes[i].bOnGround && !lastOnGround) + LandingPoint = pPathNodes[i].t; + else if(!pPathNodes[i].bOnGround && lastOnGround) + TakeOffPoint = pPathNodes[i].t; + lastOnGround = pPathNodes[i].bOnGround; + } + + // Animation + float time = 0.0f; + float position = 0.0f; + // Start on ground with slow speed + aPlaneLineBits[0].type = 1; + aPlaneLineBits[0].time = time; + aPlaneLineBits[0].position = position; + aPlaneLineBits[0].speed = TAXI_SPEED; + aPlaneLineBits[0].acceleration = 0.0f; + float dist = (TakeOffPoint-600.0f) - position; + time += dist/TAXI_SPEED; + position += dist; + + // Accelerate to take off + aPlaneLineBits[1].type = 2; + aPlaneLineBits[1].time = time; + aPlaneLineBits[1].position = position; + aPlaneLineBits[1].speed = TAXI_SPEED; + aPlaneLineBits[1].acceleration = 618.75f/600.0f; + time += 600.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f); + position += 600.0f; + + // Fly at cruise speed + aPlaneLineBits[2].type = 1; + aPlaneLineBits[2].time = time; + aPlaneLineBits[2].position = position; + aPlaneLineBits[2].speed = CRUISE_SPEED; + aPlaneLineBits[2].acceleration = 0.0f; + dist = LandingPoint - TakeOffPoint; + time += dist/CRUISE_SPEED; + position += dist; + + // Brake after landing + aPlaneLineBits[3].type = 2; + aPlaneLineBits[3].time = time; + aPlaneLineBits[3].position = position; + aPlaneLineBits[3].speed = CRUISE_SPEED; + aPlaneLineBits[3].acceleration = -618.75f/600.0f; + time += 600.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f); + position += 600.0f; + + // Taxi + aPlaneLineBits[4].type = 1; + aPlaneLineBits[4].time = time; + aPlaneLineBits[4].position = position; + aPlaneLineBits[4].speed = TAXI_SPEED; + aPlaneLineBits[4].acceleration = 0.0f; + time += (TotalLengthOfFlightPath - position)/TAXI_SPEED; + + // end + aPlaneLineBits[5].time = time; + TotalDurationOfFlightPath = time; + } + + // Dodo + if(pPath2Nodes == nil){ + pPath2Nodes = LoadPath("data\\paths\\flight2.dat", NumPath2Nodes, TotalLengthOfFlightPath2, true); + TotalDurationOfFlightPath2 = TotalLengthOfFlightPath2/CRUISE_SPEED; + } + + // Mission Cesna + if(pPath3Nodes == nil){ + pPath3Nodes = LoadPath("data\\paths\\flight3.dat", NumPath3Nodes, TotalLengthOfFlightPath3, false); + TotalDurationOfFlightPath3 = TotalLengthOfFlightPath3/CRUISE_SPEED; + } + + // Mission Cesna + if(pPath4Nodes == nil){ + pPath4Nodes = LoadPath("data\\paths\\flight4.dat", NumPath4Nodes, TotalLengthOfFlightPath4, false); + TotalDurationOfFlightPath4 = TotalLengthOfFlightPath4/CRUISE_SPEED; + } + + CStreaming::LoadAllRequestedModels(false); + CStreaming::RequestModel(MI_AIRTRAIN, 0); + CStreaming::LoadAllRequestedModels(false); + + for(i = 0; i < 3; i++){ + CPlane *plane = new CPlane(MI_AIRTRAIN, PERMANENT_VEHICLE); + plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + plane->SetStatus(STATUS_ABANDONED); + plane->bIsLocked = true; + plane->m_nPlaneId = i; + plane->m_nCurPathNode = 0; + CWorld::Add(plane); + } + + + CStreaming::RequestModel(MI_DEADDODO, 0); + CStreaming::LoadAllRequestedModels(false); + + for(i = 0; i < 3; i++){ + CPlane *plane = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE); + plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + plane->SetStatus(STATUS_ABANDONED); + plane->bIsLocked = true; + plane->m_nPlaneId = i; + plane->m_nCurPathNode = 0; + CWorld::Add(plane); + } +} + +void +CPlane::Shutdown(void) +{ + delete[] pPathNodes; + delete[] pPath2Nodes; + delete[] pPath3Nodes; + delete[] pPath4Nodes; + pPathNodes = nil; + pPath2Nodes = nil; + pPath3Nodes = nil; + pPath4Nodes = nil; +} + +CPlaneNode* +CPlane::LoadPath(char const *filename, int32 &numNodes, float &totalLength, bool loop) +{ + int bp, lp; + int i; + + CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r"); + *gString = '\0'; + for(bp = 0, lp = 0; work_buff[bp] != '\n'; bp++, lp++) + gString[lp] = work_buff[bp]; + bp++; + gString[lp] = '\0'; + sscanf(gString, "%d", &numNodes); + CPlaneNode *nodes = new CPlaneNode[numNodes]; + + for(i = 0; i < numNodes; i++){ + *gString = '\0'; + for(lp = 0; work_buff[bp] != '\n'; bp++, lp++) + gString[lp] = work_buff[bp]; + bp++; + // BUG: game doesn't terminate string + gString[lp] = '\0'; + sscanf(gString, "%f %f %f", &nodes[i].p.x, &nodes[i].p.y, &nodes[i].p.z); + } + + // Calculate length of segments and path + totalLength = 0.0f; + for(i = 0; i < numNodes; i++){ + nodes[i].t = totalLength; + float l = (nodes[(i+1) % numNodes].p - nodes[i].p).Magnitude2D(); + if(!loop && i == numNodes-1) + l = 0.0f; + totalLength += l; + } + + return nodes; +} + +void +CPlane::UpdatePlanes(void) +{ + int i, j; + uint32 time; + float t, deltaT; + + if(CReplay::IsPlayingBack()) + return; + + // Jumbo jets + time = CTimer::GetTimeInMilliseconds(); + for(i = 0; i < 3; i++){ + t = TotalDurationOfFlightPath * (float)(time & 0x7FFFF)/0x80000; + // find current frame + for(j = 0; t > aPlaneLineBits[j+1].time; j++); + + OldPlanePathPosition[i] = PlanePathPosition[i]; + deltaT = t - aPlaneLineBits[j].time; + switch(aPlaneLineBits[j].type){ + case 0: // standing still + PlanePathPosition[i] = aPlaneLineBits[j].position; + PlanePathSpeed[i] = 0.0f; + break; + case 1: // moving with constant speed + PlanePathPosition[i] = aPlaneLineBits[j].position + aPlaneLineBits[j].speed*deltaT; + PlanePathSpeed[i] = (TotalDurationOfFlightPath*1000.0f/0x80000) * aPlaneLineBits[j].speed; + break; + case 2: // accelerating/braking + PlanePathPosition[i] = aPlaneLineBits[j].position + (aPlaneLineBits[j].speed + aPlaneLineBits[j].acceleration*deltaT)*deltaT; + PlanePathSpeed[i] = (TotalDurationOfFlightPath*1000.0f/0x80000)*aPlaneLineBits[j].speed + 2.0f*aPlaneLineBits[j].acceleration*deltaT; + break; + } + + // time offset for each plane + time += 0x80000/3; + } + + time = CTimer::GetTimeInMilliseconds(); + + t = TotalDurationOfFlightPath2/0x80000; + PlanePath2Position[0] = CRUISE_SPEED * (time & 0x7FFFF)*t; + PlanePath2Position[1] = CRUISE_SPEED * ((time + 0x80000/3) & 0x7FFFF)*t; + PlanePath2Position[2] = CRUISE_SPEED * ((time + 0x80000/3*2) & 0x7FFFF)*t; + PlanePath2Speed[0] = CRUISE_SPEED*t; + PlanePath2Speed[1] = CRUISE_SPEED*t; + PlanePath2Speed[2] = CRUISE_SPEED*t; + + if(CesnaMissionStatus == CESNA_STATUS_FLYING){ + PlanePath3Speed = CRUISE_SPEED*TotalDurationOfFlightPath3/0x20000; + PlanePath3Position = PlanePath3Speed * ((time - CesnaMissionStartTime) & 0x1FFFF); + if(time - CesnaMissionStartTime >= 128072) + CesnaMissionStatus = CESNA_STATUS_LANDED; + } + + if(DropOffCesnaMissionStatus == CESNA_STATUS_FLYING){ + PlanePath4Speed = CRUISE_SPEED*TotalDurationOfFlightPath4/0x80000; + PlanePath4Position = PlanePath4Speed * ((time - DropOffCesnaMissionStartTime) & 0x7FFFF); + if(time - DropOffCesnaMissionStartTime >= 521288) + DropOffCesnaMissionStatus = CESNA_STATUS_LANDED; + } +} + +bool +CPlane::TestRocketCollision(CVector *rocketPos) +{ + int i; + + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + CPlane *plane = (CPlane*)CPools::GetVehiclePool()->GetSlot(i); + if(plane && +#ifdef EXPLODING_AIRTRAIN + (plane->GetModelIndex() == MI_AIRTRAIN || plane->GetModelIndex() == MI_DEADDODO) && +#else + plane->GetModelIndex() != MI_AIRTRAIN && plane->GetModelIndex() == MI_DEADDODO && // strange check +#endif + !plane->m_bHasBeenHit && (*rocketPos - plane->GetPosition()).Magnitude() < 25.0f){ + plane->m_nFrameWhenHit = CTimer::GetFrameCounter(); + plane->m_bHasBeenHit = true; + CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->RegisterCrime_Immediately(CRIME_DESTROYED_CESSNA, + plane->GetPosition(), i+1983, false); + return true; + } + } + return false; +} + +// BUG: not in CPlane in the game +void +CPlane::CreateIncomingCesna(void) +{ + if(CesnaMissionStatus == CESNA_STATUS_FLYING){ + CWorld::Remove(pDrugRunCesna); + delete pDrugRunCesna; + pDrugRunCesna = nil; + } + pDrugRunCesna = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE); + pDrugRunCesna->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + pDrugRunCesna->SetStatus(STATUS_ABANDONED); + pDrugRunCesna->bIsLocked = true; + pDrugRunCesna->m_nPlaneId = 0; + pDrugRunCesna->m_nCurPathNode = 0; + pDrugRunCesna->m_bIsDrugRunCesna = true; + CWorld::Add(pDrugRunCesna); + + CesnaMissionStatus = CESNA_STATUS_FLYING; + CesnaMissionStartTime = CTimer::GetTimeInMilliseconds(); + printf("CPlane::CreateIncomingCesna(void)\n"); +} + +void +CPlane::CreateDropOffCesna(void) +{ + if(DropOffCesnaMissionStatus == CESNA_STATUS_FLYING){ + CWorld::Remove(pDropOffCesna); + delete pDropOffCesna; + pDropOffCesna = nil; + } + pDropOffCesna = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE); + pDropOffCesna->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + pDropOffCesna->SetStatus(STATUS_ABANDONED); + pDropOffCesna->bIsLocked = true; + pDropOffCesna->m_nPlaneId = 0; + pDropOffCesna->m_nCurPathNode = 0; + pDropOffCesna->m_bIsDropOffCesna = true; + CWorld::Add(pDropOffCesna); + + DropOffCesnaMissionStatus = CESNA_STATUS_FLYING; + DropOffCesnaMissionStartTime = CTimer::GetTimeInMilliseconds(); + printf("CPlane::CreateDropOffCesna(void)\n"); +} + +const CVector CPlane::FindDrugPlaneCoordinates(void) { return pDrugRunCesna->GetPosition(); } +const CVector CPlane::FindDropOffCesnaCoordinates(void) { return pDropOffCesna->GetPosition(); } +bool CPlane::HasCesnaLanded(void) { return CesnaMissionStatus == CESNA_STATUS_LANDED; } +bool CPlane::HasCesnaBeenDestroyed(void) { return CesnaMissionStatus == CESNA_STATUS_DESTROYED; } +bool CPlane::HasDropOffCesnaBeenShotDown(void) { return DropOffCesnaMissionStatus == CESNA_STATUS_DESTROYED; } diff --git a/src/vehicles/Plane.h b/src/vehicles/Plane.h new file mode 100644 index 0000000..783c53b --- /dev/null +++ b/src/vehicles/Plane.h @@ -0,0 +1,71 @@ +#pragma once + +#include "Vehicle.h" + +enum ePlaneNodes +{ + PLANE_WHEEL_FRONT = 2, + PLANE_WHEEL_READ, + NUM_PLANE_NODES +}; + +struct CPlaneNode +{ + CVector p; // position + float t; // xy-distance from start on path + bool bOnGround; // i.e. not flying +}; + +struct CPlaneInterpolationLine +{ + uint8 type; + float time; // when does this keyframe start + // initial values at start of frame + float position; + float speed; + float acceleration; +}; + +class CPlane : public CVehicle +{ +public: + // 0x288 + int16 m_nPlaneId; + int16 m_isFarAway; + int16 m_nCurPathNode; + float m_fSpeed; + uint32 m_nFrameWhenHit; + bool m_bHasBeenHit; + bool m_bIsDrugRunCesna; + bool m_bIsDropOffCesna; + + CPlane(int32 id, uint8 CreatedBy); + ~CPlane(void); + + // from CEntity + void SetModelIndex(uint32 id); + void DeleteRwObject(void); + void ProcessControl(void); + void PreRender(void); + void Render(void); + void FlagToDestroyWhenNextProcessed() { bRemoveFromWorld = true; } + + static void InitPlanes(void); + static void Shutdown(void); + static CPlaneNode *LoadPath(char const *filename, int32 &numNodes, float &totalLength, bool loop); + static void UpdatePlanes(void); + static bool TestRocketCollision(CVector *rocketPos); + static void CreateIncomingCesna(void); + static void CreateDropOffCesna(void); + static const CVector FindDrugPlaneCoordinates(void); + static const CVector FindDropOffCesnaCoordinates(void); + static bool HasCesnaLanded(void); + static bool HasCesnaBeenDestroyed(void); + static bool HasDropOffCesnaBeenShotDown(void); +}; + +VALIDATE_SIZE(CPlane, 0x29C); + +extern float LandingPoint; +extern float TakeOffPoint; +extern float PlanePathPosition[3]; diff --git a/src/vehicles/Train.cpp b/src/vehicles/Train.cpp new file mode 100644 index 0000000..be546c7 --- /dev/null +++ b/src/vehicles/Train.cpp @@ -0,0 +1,738 @@ +#include "common.h" +#include "main.h" + +#include "Timer.h" +#include "ModelIndices.h" +#include "FileMgr.h" +#include "Streaming.h" +#include "Pad.h" +#include "Camera.h" +#include "Coronas.h" +#include "World.h" +#include "Ped.h" +#include "DMAudio.h" +#include "HandlingMgr.h" +#include "Train.h" +#include "AudioScriptObject.h" + +static CTrainNode* pTrackNodes; +static int16 NumTrackNodes; +static float StationDist[3] = { 873.0f, 1522.0f, 2481.0f }; +static float TotalLengthOfTrack; +static float TotalDurationOfTrack; +static CTrainInterpolationLine aLineBits[17]; +static float EngineTrackPosition[2]; +static float EngineTrackSpeed[2]; + +static CTrainNode* pTrackNodes_S; +static int16 NumTrackNodes_S; +static float StationDist_S[4] = { 55.0f, 1388.0f, 2337.0f, 3989.0f }; +static float TotalLengthOfTrack_S; +static float TotalDurationOfTrack_S; +static CTrainInterpolationLine aLineBits_S[18]; +static float EngineTrackPosition_S[4]; +static float EngineTrackSpeed_S[4]; + +CVector CTrain::aStationCoors[3]; +CVector CTrain::aStationCoors_S[4]; + +static bool bTrainArrivalAnnounced[3] = {false, false, false}; + +CTrain::CTrain(int32 id, uint8 CreatedBy) + : CVehicle(CreatedBy) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + m_vehType = VEHICLE_TYPE_TRAIN; + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + SetModelIndex(id); + + Doors[0].Init(0.8f, 0.0f, 1, 0); + Doors[1].Init(-0.8f, 0.0f, 0, 0); + + m_fMass = 100000000.0f; + m_fTurnMass = 100000000.0f; + m_fAirResistance = 0.9994f; + m_fElasticity = 0.05f; + + m_bProcessDoor = true; + m_bTrainStopping = false; + m_nTrackId = TRACK_ELTRAIN; + m_nNumMaxPassengers = 5; + m_nDoorTimer = CTimer::GetTimeInMilliseconds(); + m_nDoorState = TRAIN_DOOR_CLOSED; + + bUsesCollision = true; + SetStatus(STATUS_TRAIN_MOVING); + +#ifdef FIX_BUGS + m_isFarAway = true; +#endif +} + +void +CTrain::SetModelIndex(uint32 id) +{ + int i; + + CVehicle::SetModelIndex(id); + for(i = 0; i < NUM_TRAIN_NODES; i++) + m_aTrainNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aTrainNodes); +} + +void +CTrain::ProcessControl(void) +{ + if(gbModelViewer || m_isFarAway && (CTimer::GetFrameCounter() + m_nWagonId) & 0xF) + return; + + CTrainNode *trackNodes; + int16 numTrackNodes; + float totalLengthOfTrack; + float *engineTrackPosition; + float *engineTrackSpeed; + + if(m_nTrackId == TRACK_SUBWAY){ + trackNodes = pTrackNodes_S; + numTrackNodes = NumTrackNodes_S; + totalLengthOfTrack = TotalLengthOfTrack_S; + engineTrackPosition = EngineTrackPosition_S; + engineTrackSpeed = EngineTrackSpeed_S; + }else{ + trackNodes = pTrackNodes; + numTrackNodes = NumTrackNodes; + totalLengthOfTrack = TotalLengthOfTrack; + engineTrackPosition = EngineTrackPosition; + engineTrackSpeed = EngineTrackSpeed; + } + + float trackPositionRear = engineTrackPosition[m_nWagonGroup] - m_fWagonPosition; + if(trackPositionRear < 0.0f) + trackPositionRear += totalLengthOfTrack; + + // Advance current node to appropriate position + float pos1, pos2; + int nextTrackNode = m_nCurTrackNode + 1; + pos1 = trackNodes[m_nCurTrackNode].t; + if(nextTrackNode < numTrackNodes) + pos2 = trackNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = totalLengthOfTrack; + } + while(trackPositionRear < pos1 || trackPositionRear > pos2){ + m_nCurTrackNode = (m_nCurTrackNode+1) % numTrackNodes; + nextTrackNode = m_nCurTrackNode + 1; + pos1 = trackNodes[m_nCurTrackNode].t; + if(nextTrackNode < numTrackNodes) + pos2 = trackNodes[nextTrackNode].t; + else{ + nextTrackNode = 0; + pos2 = totalLengthOfTrack; + } + } + float dist = trackNodes[nextTrackNode].t - trackNodes[m_nCurTrackNode].t; + if(dist < 0.0f) + dist += totalLengthOfTrack; + float f = (trackPositionRear - trackNodes[m_nCurTrackNode].t)/dist; + CVector posRear = (1.0f - f)*trackNodes[m_nCurTrackNode].p + f*trackNodes[nextTrackNode].p; + + // Now same again for the front + float trackPositionFront = trackPositionRear + 20.0f; + if(trackPositionFront > totalLengthOfTrack) + trackPositionFront -= totalLengthOfTrack; + int curTrackNodeFront = m_nCurTrackNode; + int nextTrackNodeFront = curTrackNodeFront + 1; + pos1 = trackNodes[curTrackNodeFront].t; + if(nextTrackNodeFront < numTrackNodes) + pos2 = trackNodes[nextTrackNodeFront].t; + else{ + nextTrackNodeFront = 0; + pos2 = totalLengthOfTrack; + } + while(trackPositionFront < pos1 || trackPositionFront > pos2){ + curTrackNodeFront = (curTrackNodeFront+1) % numTrackNodes; + nextTrackNodeFront = curTrackNodeFront + 1; + pos1 = trackNodes[curTrackNodeFront].t; + if(nextTrackNodeFront < numTrackNodes) + pos2 = trackNodes[nextTrackNodeFront].t; + else{ + nextTrackNodeFront = 0; + pos2 = totalLengthOfTrack; + } + } + dist = trackNodes[nextTrackNodeFront].t - trackNodes[curTrackNodeFront].t; + if(dist < 0.0f) + dist += totalLengthOfTrack; + f = (trackPositionFront - trackNodes[curTrackNodeFront].t)/dist; + CVector posFront = (1.0f - f)*trackNodes[curTrackNodeFront].p + f*trackNodes[nextTrackNodeFront].p; + + // Now set matrix + SetPosition((posRear + posFront)/2.0f); + CVector fwd = posFront - posRear; + fwd.Normalise(); + CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f)); + right.Normalise(); + CVector up = CrossProduct(right, fwd); + GetRight() = right; + GetUp() = up; + GetForward() = fwd; + + // Set speed + m_vecMoveSpeed = fwd*engineTrackSpeed[m_nWagonGroup]/60.0f; + m_fSpeed = engineTrackSpeed[m_nWagonGroup]/60.0f; + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + + if(engineTrackSpeed[m_nWagonGroup] > 0.001f){ + SetStatus(STATUS_TRAIN_MOVING); + m_bTrainStopping = false; + m_bProcessDoor = true; + }else{ + SetStatus(STATUS_TRAIN_NOT_MOVING); + m_bTrainStopping = true; + } + + m_isFarAway = !((posFront - TheCamera.GetPosition()).Magnitude2D() < sq(250.0f)); + + if(m_fWagonPosition == 20.0f && m_fSpeed > 0.0001f) + if(Abs(TheCamera.GetPosition().z - GetPosition().z) < 15.0f) + CPad::GetPad(0)->StartShake_Train(GetPosition().x, GetPosition().y); + + if(m_bProcessDoor) + switch(m_nDoorState){ + case TRAIN_DOOR_CLOSED: + if(m_bTrainStopping){ + m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 1000; + m_nDoorState = TRAIN_DOOR_OPENING; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_TRAIN_DOOR_CLOSE, 0.0f); + } + break; + + case TRAIN_DOOR_OPENING: + if(CTimer::GetTimeInMilliseconds() < m_nDoorTimer){ + OpenTrainDoor(1.0f - (m_nDoorTimer - CTimer::GetTimeInMilliseconds())/1000.0f); + }else{ + OpenTrainDoor(1.0f); + m_nDoorState = TRAIN_DOOR_OPEN; + } + break; + + case TRAIN_DOOR_OPEN: + if(!m_bTrainStopping){ + m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 1000; + m_nDoorState = TRAIN_DOOR_CLOSING; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_TRAIN_DOOR_OPEN, 0.0f); + } + break; + + case TRAIN_DOOR_CLOSING: + if(CTimer::GetTimeInMilliseconds() < m_nDoorTimer){ + OpenTrainDoor((m_nDoorTimer - CTimer::GetTimeInMilliseconds())/1000.0f); + }else{ + OpenTrainDoor(0.0f); + m_nDoorState = TRAIN_DOOR_CLOSED; + m_bProcessDoor = false; + } + break; + } + + GetMatrix().UpdateRW(); + UpdateRwFrame(); + RemoveAndAdd(); + + bIsStuck = false; + bIsInSafePosition = true; + bWasPostponed = false; + + // request/remove model + if(m_isFarAway){ + if(m_rwObject) + DeleteRwObject(); + }else if(CStreaming::HasModelLoaded(MI_TRAIN)){ + if(m_rwObject == nil){ + m_modelIndex = -1; + SetModelIndex(MI_TRAIN); + } + }else{ + if(FindPlayerCoors().z * GetPosition().z >= 0.0f) + CStreaming::RequestModel(MI_TRAIN, STREAMFLAGS_DEPENDENCY); + } + + // Hit stuff + if(m_bIsFirstWagon && GetStatus()== STATUS_TRAIN_MOVING){ + CVector front = GetPosition() + GetForward()*GetColModel()->boundingBox.max.y + m_vecMoveSpeed*CTimer::GetTimeStep(); + + int x, xmin, xmax; + int y, ymin, ymax; + + xmin = CWorld::GetSectorIndexX(front.x - 3.0f); + if(xmin < 0) xmin = 0; + xmax = CWorld::GetSectorIndexX(front.x + 3.0f); + if(xmax > NUMSECTORS_X-1) xmax = NUMSECTORS_X-1; + ymin = CWorld::GetSectorIndexY(front.y - 3.0f); + if(ymin < 0) ymin = 0; + ymax = CWorld::GetSectorIndexY(front.y + 3.0f); + if(ymax > NUMSECTORS_Y-1) ymax = NUMSECTORS_X-1; + + CWorld::AdvanceCurrentScanCode(); + + for(y = ymin; y <= ymax; y++) + for(x = xmin; x <= xmax; x++){ + CSector *s = CWorld::GetSector(x, y); + TrainHitStuff(s->m_lists[ENTITYLIST_VEHICLES]); + TrainHitStuff(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]); + TrainHitStuff(s->m_lists[ENTITYLIST_PEDS]); + TrainHitStuff(s->m_lists[ENTITYLIST_PEDS_OVERLAP]); + } + } +} + +void +CTrain::PreRender(void) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + if(m_bIsFirstWagon){ + CVector lookVector = GetPosition() - TheCamera.GetPosition(); + float camDist = lookVector.Magnitude(); + if(camDist != 0.0f) + lookVector *= 1.0f/camDist; + else + lookVector = CVector(1.0f, 0.0f, 0.0f); + float behindness = DotProduct(lookVector, GetForward()); + + if(behindness < 0.0f){ + // In front of train + CVector lightPos = mi->m_positions[TRAIN_POS_LIGHT_FRONT]; + CVector lightR = GetMatrix() * lightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*lightPos.x; + + float intensity = -0.4f*behindness + 0.2f; + float size = 1.0f - behindness; + + if(behindness < -0.9f && camDist < 35.0f){ + // directly in front + CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255, + lightL, size, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255, + lightR, size, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + }else{ + CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255, + lightL, size, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255, + lightR, size, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + } + } + } + + if(m_bIsLastWagon){ + CVector lightPos = mi->m_positions[TRAIN_POS_LIGHT_REAR]; + CVector lightR = GetMatrix() * lightPos; + CVector lightL = lightR; + lightL -= GetRight()*2.0f*lightPos.x; + + CCoronas::RegisterCorona((uintptr)this + 12, 255, 0, 0, 255, + lightL, 1.0f, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CCoronas::RegisterCorona((uintptr)this + 13, 255, 0, 0, 255, + lightR, 1.0f, 80.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + } +} + +void +CTrain::Render(void) +{ + CEntity::Render(); +} + +void +CTrain::TrainHitStuff(CPtrList &list) +{ + CPtrNode *node; + CPhysical *phys; + + for(node = list.first; node; node = node->next){ + phys = (CPhysical*)node->item; + if(phys != this && Abs(this->GetPosition().z - phys->GetPosition().z) < 1.5f) + phys->bHitByTrain = true; + } +} + +void +CTrain::AddPassenger(CPed *ped) +{ + int i = ped->m_vehDoor; + if((i == TRAIN_POS_LEFT_ENTRY || i == TRAIN_POS_MID_ENTRY || i == TRAIN_POS_RIGHT_ENTRY) && pPassengers[i] == nil){ + pPassengers[i] = ped; + m_nNumPassengers++; + }else{ + for(i = 0; i < 6; i++) + if(pPassengers[i] == nil){ + pPassengers[i] = ped; + m_nNumPassengers++; + return; + } + } +} + +void +CTrain::OpenTrainDoor(float ratio) +{ + if(m_rwObject == nil) + return; + + CMatrix doorL(RwFrameGetMatrix(m_aTrainNodes[TRAIN_DOOR_LHS])); + CMatrix doorR(RwFrameGetMatrix(m_aTrainNodes[TRAIN_DOOR_RHS])); + CVector posL = doorL.GetPosition(); + CVector posR = doorR.GetPosition(); + + bool isClosed = Doors[0].IsClosed(); // useless + + Doors[0].Open(ratio); + Doors[1].Open(ratio); + + if(isClosed) + Doors[0].RetTranslationWhenClosed(); // useless + + posL.y = Doors[0].m_fPosn; + posR.y = Doors[1].m_fPosn; + + doorL.SetTranslate(posL); + doorR.SetTranslate(posR); + + doorL.UpdateRW(); + doorR.UpdateRW(); +} + + + +void +CTrain::InitTrains(void) +{ + int i, j; + CTrain *train; + + // El train + if(pTrackNodes == nil) + ReadAndInterpretTrackFile("data\\paths\\tracks.dat", &pTrackNodes, &NumTrackNodes, 3, StationDist, + &TotalLengthOfTrack, &TotalDurationOfTrack, aLineBits, false); + // Subway + if(pTrackNodes_S == nil) + ReadAndInterpretTrackFile("data\\paths\\tracks2.dat", &pTrackNodes_S, &NumTrackNodes_S, 4, StationDist_S, + &TotalLengthOfTrack_S, &TotalDurationOfTrack_S, aLineBits_S, true); + + int trainId; + CStreaming::LoadAllRequestedModels(false); + if(CModelInfo::GetModelInfo("train", &trainId)) + CStreaming::RequestModel(trainId, 0); + CStreaming::LoadAllRequestedModels(false); + + // El-Train wagons + float wagonPositions[] = { 0.0f, 20.0f, 40.0f, 0.0f, 20.0f }; + int8 firstWagon[] = { 1, 0, 0, 1, 0 }; + int8 lastWagon[] = { 0, 0, 1, 0, 1 }; + int16 wagonGroup[] = { 0, 0, 0, 1, 1 }; + for(i = 0; i < 5; i++){ + train = new CTrain(MI_TRAIN, PERMANENT_VEHICLE); + train->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + train->SetStatus(STATUS_ABANDONED); + train->bIsLocked = true; + train->m_fWagonPosition = wagonPositions[i]; + train->m_bIsFirstWagon = firstWagon[i]; + train->m_bIsLastWagon = lastWagon[i]; + train->m_nWagonGroup = wagonGroup[i]; + train->m_nWagonId = i; + train->m_nCurTrackNode = 0; + CWorld::Add(train); + } + + // Subway wagons + float wagonPositions_S[] = { 0.0f, 20.0f, 0.0f, 20.0f, 0.0f, 20.0f, 0.0f, 20.0f }; + int8 firstWagon_S[] = { 1, 0, 1, 0, 1, 0, 1, 0 }; + int8 lastWagon_S[] = { 0, 1, 0, 1, 0, 1, 0, 1 }; + int16 wagonGroup_S[] = { 0, 0, 1, 1, 2, 2, 3, 3 }; + for(i = 0; i < 8; i++){ + train = new CTrain(MI_TRAIN, PERMANENT_VEHICLE); + train->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f); + train->SetStatus(STATUS_ABANDONED); + train->bIsLocked = true; + train->m_fWagonPosition = wagonPositions_S[i]; + train->m_bIsFirstWagon = firstWagon_S[i]; + train->m_bIsLastWagon = lastWagon_S[i]; + train->m_nWagonGroup = wagonGroup_S[i]; + train->m_nWagonId = i; + train->m_nCurTrackNode = 0; + train->m_nTrackId = TRACK_SUBWAY; + CWorld::Add(train); + } + + // This code is actually useless, it seems it was used for announcements once + for(i = 0; i < 3; i++){ + for(j = 0; pTrackNodes[j].t < StationDist[i]; j++); + aStationCoors[i] = pTrackNodes[j].p; + } + for(i = 0; i < 4; i++){ + for(j = 0; pTrackNodes_S[j].t < StationDist_S[i]; j++); + aStationCoors_S[i] = pTrackNodes_S[j].p; + } +} + +void +CTrain::Shutdown(void) +{ + delete[] pTrackNodes; + delete[] pTrackNodes_S; + pTrackNodes = nil; + pTrackNodes_S = nil; +} + +void +CTrain::ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int16 *numNodes, int32 numStations, float *stationDists, + float *totalLength, float *totalDuration, CTrainInterpolationLine *interpLines, bool rightRail) +{ + bool readingFile = false; + int bp, lp; + int i, tmp; + + if(*nodes == nil){ + readingFile = true; + + CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r"); + *gString = '\0'; + for(bp = 0, lp = 0; work_buff[bp] != '\n'; bp++, lp++) + gString[lp] = work_buff[bp]; + bp++; + // BUG: game doesn't terminate string and uses numNodes in sscanf directly + gString[lp] = '\0'; + sscanf(gString, "%d", &tmp); + *numNodes = tmp; + *nodes = new CTrainNode[*numNodes]; + + for(i = 0; i < *numNodes; i++){ + *gString = '\0'; + for(lp = 0; work_buff[bp] != '\n'; bp++, lp++) + gString[lp] = work_buff[bp]; + bp++; + // BUG: game doesn't terminate string + gString[lp] = '\0'; + sscanf(gString, "%f %f %f", &(*nodes)[i].p.x, &(*nodes)[i].p.y, &(*nodes)[i].p.z); + } + + // Coordinates are of one of the rails, but we want the center + float toCenter = rightRail ? 0.9f : -0.9f; + CVector fwd; + for(i = 0; i < *numNodes; i++){ + if(i == *numNodes-1) + fwd = (*nodes)[0].p - (*nodes)[i].p; + else + fwd = (*nodes)[i+1].p - (*nodes)[i].p; + CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f)); + right.Normalise(); + (*nodes)[i].p -= right*toCenter; + } + } + + // Calculate length of segments and track + float t = 0.0f; + for(i = 0; i < *numNodes; i++){ + (*nodes)[i].t = t; + t += ((*nodes)[(i+1) % (*numNodes)].p - (*nodes)[i].p).Magnitude2D(); + } + *totalLength = t; + + // Find correct z values + if(readingFile){ + CColPoint colpoint; + CEntity *entity; + for(i = 0; i < *numNodes; i++){ + CVector p = (*nodes)[i].p; + p.z += 1.0f; + if(CWorld::ProcessVerticalLine(p, p.z-0.5f, colpoint, entity, true, false, false, false, true, false, nil)) + (*nodes)[i].p.z = colpoint.point.z; + (*nodes)[i].p.z += 0.2f; + } + } + + // Create animation for stopping at stations + // TODO: figure out magic numbers? + float position = 0.0f; + float time = 0.0f; + int j = 0; + for(i = 0; i < numStations; i++){ + // Start at full speed + interpLines[j].type = 1; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 15.0f; + interpLines[j].acceleration = 0.0f; + j++; + // distance to next keyframe + float dist = (stationDists[i]-40.0f) - position; + time += dist/15.0f; + position += dist; + + // Now slow down 40 units before stop + interpLines[j].type = 2; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 15.0f; + interpLines[j].acceleration = -45.0f/32.0f; + j++; + time += 80.0f/15.0f; + position += 40.0f; // at station + + // stopping + interpLines[j].type = 0; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 0.0f; + interpLines[j].acceleration = 0.0f; + j++; + time += 25.0f; + + // accelerate again + interpLines[j].type = 2; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 0.0f; + interpLines[j].acceleration = 45.0f/32.0f; + j++; + time += 80.0f/15.0f; + position += 40.0f; // after station + } + // last keyframe + interpLines[j].type = 1; + interpLines[j].time = time; + interpLines[j].position = position; + interpLines[j].speed = 15.0f; + interpLines[j].acceleration = 0.0f; + j++; + *totalDuration = time + (*totalLength - position)/15.0f; + + // end + interpLines[j].time = *totalDuration; +} + +void +PlayAnnouncement(uint8 sound, uint8 station) +{ + // this was gone in a PC version but inlined on PS2 + cAudioScriptObject *obj = new cAudioScriptObject; + obj->AudioId = sound; + obj->Posn = CTrain::aStationCoors[station]; + obj->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(obj); +} + +void +ProcessTrainAnnouncements(void) +{ + for (int i = 0; i < ARRAY_SIZE(StationDist); i++) { + for (int j = 0; j < ARRAY_SIZE(EngineTrackPosition); j++) { + if (!bTrainArrivalAnnounced[i]) { + float preDist = StationDist[i] - 100.0f; + if (preDist < 0.0f) + preDist += TotalLengthOfTrack; + if (EngineTrackPosition[j] > preDist && EngineTrackPosition[j] < StationDist[i]) { + bTrainArrivalAnnounced[i] = true; + PlayAnnouncement(SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_1, i); + break; + } + } else { + float postDist = StationDist[i] + 10.0f; +#ifdef FIX_BUGS + if (postDist > TotalLengthOfTrack) + postDist -= TotalLengthOfTrack; +#else + if (postDist < 0.0f) // does this even make sense here? + postDist += TotalLengthOfTrack; +#endif + if (EngineTrackPosition[j] > StationDist[i] && EngineTrackPosition[j] < postDist) { + bTrainArrivalAnnounced[i] = false; + PlayAnnouncement(SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_2, i); + break; + } + } + } + } +} + +void +CTrain::UpdateTrains(void) +{ + int i, j; + uint32 time; + float t, deltaT; + + if(TheCamera.GetPosition().x > 200.0f && TheCamera.GetPosition().x < 1600.0f && + TheCamera.GetPosition().y > -1000.0f && TheCamera.GetPosition().y < 500.0f){ + // Update El-Train + + time = CTimer::GetTimeInMilliseconds(); + for(i = 0; i < 2; i++){ + t = TotalDurationOfTrack * (float)(time & 0x1FFFF)/0x20000; + // find current frame + for(j = 0; t > aLineBits[j+1].time; j++); + + deltaT = t - aLineBits[j].time; + switch(aLineBits[j].type){ + case 0: // standing still + EngineTrackPosition[i] = aLineBits[j].position; + EngineTrackSpeed[i] = 0.0f; + break; + case 1: // moving with constant speed + EngineTrackPosition[i] = aLineBits[j].position + aLineBits[j].speed*deltaT; + EngineTrackSpeed[i] = (TotalDurationOfTrack*1000.0f/0x20000) * aLineBits[j].speed; + break; + case 2: // accelerating/braking + EngineTrackPosition[i] = aLineBits[j].position + (aLineBits[j].speed + aLineBits[j].acceleration*deltaT)*deltaT; + EngineTrackSpeed[i] = (TotalDurationOfTrack*1000.0f/0x20000)*aLineBits[j].speed + 2.0f*aLineBits[j].acceleration*deltaT; + break; + } + + // time offset for each train + time += 0x20000/2; + } + + ProcessTrainAnnouncements(); + } + + // Update Subway + time = CTimer::GetTimeInMilliseconds(); + for(i = 0; i < 4; i++){ + t = TotalDurationOfTrack_S * (float)(time & 0x3FFFF)/0x40000; + // find current frame + for(j = 0; t > aLineBits_S[j+1].time; j++); + + deltaT = t - aLineBits_S[j].time; + switch(aLineBits_S[j].type){ + case 0: // standing still + EngineTrackPosition_S[i] = aLineBits_S[j].position; + EngineTrackSpeed_S[i] = 0.0f; + break; + case 1: // moving with constant speed + EngineTrackPosition_S[i] = aLineBits_S[j].position + aLineBits_S[j].speed*deltaT; + EngineTrackSpeed_S[i] = (TotalDurationOfTrack*1000.0f/0x40000) * aLineBits_S[j].speed; + break; + case 2: // accelerating/braking + EngineTrackPosition_S[i] = aLineBits_S[j].position + (aLineBits_S[j].speed + aLineBits_S[j].acceleration*deltaT)*deltaT; + EngineTrackSpeed_S[i] = (TotalDurationOfTrack*1000.0f/0x40000)*aLineBits_S[j].speed + 2.0f*aLineBits_S[j].acceleration*deltaT; + break; + } + + // time offset for each train + time += 0x40000/4; + } +} diff --git a/src/vehicles/Train.h b/src/vehicles/Train.h new file mode 100644 index 0000000..3446eeb --- /dev/null +++ b/src/vehicles/Train.h @@ -0,0 +1,86 @@ +#pragma once + +#include "Vehicle.h" +#include "Door.h" + +enum +{ + TRACK_ELTRAIN, + TRACK_SUBWAY +}; + +enum +{ + TRAIN_DOOR_CLOSED, + TRAIN_DOOR_OPENING, + TRAIN_DOOR_OPEN, + TRAIN_DOOR_CLOSING +}; + +enum eTrainNodes +{ + TRAIN_DOOR_LHS = 1, + TRAIN_DOOR_RHS, + NUM_TRAIN_NODES +}; + +struct CTrainNode +{ + CVector p; // position + float t; // xy-distance from start on track +}; + +struct CTrainInterpolationLine +{ + uint8 type; + float time; // when does this keyframe start + // initial values at start of frame + float position; + float speed; + float acceleration; +}; + +class CTrain : public CVehicle +{ +public: + // 0x288 + float m_fWagonPosition; + int16 m_nWagonId; + int16 m_isFarAway; // don't update so often? + int16 m_nCurTrackNode; + int16 m_nWagonGroup; + float m_fSpeed; + bool m_bProcessDoor; + bool m_bTrainStopping; + bool m_bIsFirstWagon; + bool m_bIsLastWagon; + uint8 m_nTrackId; // or m_bUsesSubwayTracks? + uint32 m_nDoorTimer; + int16 m_nDoorState; + CTrainDoor Doors[2]; + RwFrame *m_aTrainNodes[NUM_TRAIN_NODES]; + + // unused + static CVector aStationCoors[3]; + static CVector aStationCoors_S[4]; + + CTrain(int32 id, uint8 CreatedBy); + + // from CEntity + void SetModelIndex(uint32 id); + void ProcessControl(void); + void PreRender(void); + void Render(void); + + void AddPassenger(CPed *ped); + void OpenTrainDoor(float ratio); + void TrainHitStuff(CPtrList &list); + + static void InitTrains(void); + static void Shutdown(void); + static void ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int16 *numNodes, int32 numStations, float *stationDists, + float *totalLength, float *totalDuration, CTrainInterpolationLine *interpLines, bool rightRail); + static void UpdateTrains(void); +}; + +VALIDATE_SIZE(CTrain, 0x2E4); diff --git a/src/vehicles/Transmission.cpp b/src/vehicles/Transmission.cpp new file mode 100644 index 0000000..109847a --- /dev/null +++ b/src/vehicles/Transmission.cpp @@ -0,0 +1,138 @@ +#include "common.h" + +#include "Timer.h" +#include "HandlingMgr.h" +#include "Transmission.h" + +void +cTransmission::InitGearRatios(void) +{ + static tGear *pGearRatio0 = nil; + static tGear *pGearRatio1 = nil; + int i; + float velocityDiff; + + memset(Gears, 0, sizeof(Gears)); + + for(i = 1; i <= nNumberOfGears; i++){ + pGearRatio0 = &Gears[i-1]; + pGearRatio1 = &Gears[i]; + + pGearRatio1->fMaxVelocity = (float)i / nNumberOfGears * fMaxVelocity; + + velocityDiff = pGearRatio1->fMaxVelocity - pGearRatio0->fMaxVelocity; + + if(i >= nNumberOfGears){ + pGearRatio1->fShiftUpVelocity = fMaxVelocity; + }else{ + Gears[i+1].fShiftDownVelocity = velocityDiff*0.42f + pGearRatio0->fMaxVelocity; + pGearRatio1->fShiftUpVelocity = velocityDiff*0.6667f + pGearRatio0->fMaxVelocity; + } + } + + // Reverse gear + Gears[0].fMaxVelocity = fMaxReverseVelocity; + Gears[0].fShiftUpVelocity = -0.01f; + Gears[0].fShiftDownVelocity = fMaxReverseVelocity; + + Gears[1].fShiftDownVelocity = -0.01f; +} + +void +cTransmission::CalculateGearForSimpleCar(float speed, uint8 &gear) +{ + static tGear *pGearRatio; + + pGearRatio = &Gears[gear]; + fCurVelocity = speed; + if(speed > pGearRatio->fShiftUpVelocity) + gear++; + else if(speed < pGearRatio->fShiftDownVelocity){ + if(gear - 1 < 0) + gear = 0; + else + gear--; + } +} + +float +cTransmission::CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat) +{ + static float fAcceleration = 0.0f; + static float fVelocity; + static float fCheat; + static tGear *pGearRatio; + + fVelocity = velocity; + if(fVelocity < fMaxReverseVelocity){ + fVelocity = fMaxReverseVelocity; + return 0.0f; + } + if(fVelocity > fMaxVelocity){ + fVelocity = fMaxVelocity; + return 0.0f; + } + fCurVelocity = fVelocity; + + assert(gear <= nNumberOfGears); + + pGearRatio = &Gears[gear]; + if(fVelocity > pGearRatio->fShiftUpVelocity){ + if(gear != 0 || gasPedal > 0.0f){ + gear++; + time = 0.0f; + return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false); + } + }else if(fVelocity < pGearRatio->fShiftDownVelocity && gear != 0){ + if(gear != 1 || gasPedal < 0.0f){ + gear--; + time = 0.0f; + return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false); + } + } + + if(time > 0.0f){ + // changing gears currently, can't accelerate + fAcceleration = 0.0f; + time -= CTimer::GetTimeStepInSeconds(); + }else{ + float speedMul, accelMul; + + if(gear < 1){ + // going reverse + accelMul = (Flags & HANDLING_2G_BOOST) ? 2.0f : 1.0f; + speedMul = -1.0f; + }else if(nNumberOfGears == 1){ + accelMul = 1.0f; + speedMul = 1.0f; + }else{ + // BUG or not? this is 1.0 normally but 0.0 in the highest gear + float f = 1.0f - (gear-1)/(nNumberOfGears-1); + speedMul = 3.0f*sq(f) + 1.0f; + // This is pretty ugly, could be written more clearly + if(Flags & HANDLING_2G_BOOST){ + if(gear == 1) + accelMul = (Flags & HANDLING_1G_BOOST) ? 3.0f : 2.0f; + else if(gear == 2) + accelMul = 1.3f; + else + accelMul = 1.0f; + }else if(Flags & HANDLING_1G_BOOST && gear == 1){ + accelMul = 3.0f; + }else + accelMul = 1.0f; + } + + if(cheat) + fCheat = 1.2f; + else + fCheat = 1.0f; + float targetVelocity = Gears[gear].fMaxVelocity*speedMul*fCheat; + float accel = (targetVelocity - fVelocity) * (fEngineAcceleration*accelMul) / Abs(targetVelocity); + if(Abs(fVelocity) < Abs(Gears[gear].fMaxVelocity*fCheat)) + fAcceleration = gasPedal * accel * CTimer::GetTimeStep(); + else + fAcceleration = 0.0f; + } + return fAcceleration; +} diff --git a/src/vehicles/Transmission.h b/src/vehicles/Transmission.h new file mode 100644 index 0000000..a3d1551 --- /dev/null +++ b/src/vehicles/Transmission.h @@ -0,0 +1,28 @@ +#pragma once + +struct tGear +{ + float fMaxVelocity; + float fShiftUpVelocity; + float fShiftDownVelocity; +}; + +class cTransmission +{ +public: + // Gear 0 is reverse, 1-5 are forward + tGear Gears[6]; + char nDriveType; + char nEngineType; + int8 nNumberOfGears; + uint8 Flags; + float fEngineAcceleration; + float fMaxVelocity; + float fMaxCruiseVelocity; + float fMaxReverseVelocity; + float fCurVelocity; + + void InitGearRatios(void); + void CalculateGearForSimpleCar(float speed, uint8 &gear); + float CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat); +}; diff --git a/src/vehicles/Vehicle.cpp b/src/vehicles/Vehicle.cpp new file mode 100644 index 0000000..451f3a3 --- /dev/null +++ b/src/vehicles/Vehicle.cpp @@ -0,0 +1,1385 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "Timer.h" +#include "Pad.h" +#include "Vehicle.h" +#include "Pools.h" +#include "HandlingMgr.h" +#include "CarCtrl.h" +#include "Population.h" +#include "ModelIndices.h" +#include "World.h" +#include "Lights.h" +#include "PointLights.h" +#include "Renderer.h" +#include "DMAudio.h" +#include "Radar.h" +#include "Fire.h" +#include "Darkel.h" +#include "SaveBuf.h" + +bool CVehicle::bWheelsOnlyCheat; +bool CVehicle::bAllDodosCheat; +bool CVehicle::bCheat3; +bool CVehicle::bCheat4; +bool CVehicle::bCheat5; +#ifdef ALT_DODO_CHEAT +bool CVehicle::bAltDodoCheat; +#endif +bool CVehicle::m_bDisableMouseSteering = true; + +void *CVehicle::operator new(size_t sz) throw() { return CPools::GetVehiclePool()->New(); } +void *CVehicle::operator new(size_t sz, int handle) throw() { return CPools::GetVehiclePool()->New(handle); } +void CVehicle::operator delete(void *p, size_t sz) throw() { CPools::GetVehiclePool()->Delete((CVehicle*)p); } +void CVehicle::operator delete(void *p, int handle) throw() { CPools::GetVehiclePool()->Delete((CVehicle*)p); } + +#ifdef FIX_BUGS +// I think they meant that +#define DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE (MYRAND_MAX * 35 / 100) +#define DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE (MYRAND_MAX * 70 / 100) +#else +#define DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE (35000) +#define DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE (70000) +#endif +#define DAMAGE_HEALTH_TO_FLEE_ALWAYS (200.0f) +#define DAMAGE_HEALTH_TO_CATCH_FIRE (250.0f) + + +CVehicle::CVehicle(uint8 CreatedBy) +{ + int i; + + m_nCurrentGear = 1; + m_fChangeGearTime = 0.0f; + m_fSteerInput = 0.0f; + m_type = ENTITY_TYPE_VEHICLE; + VehicleCreatedBy = CreatedBy; + bIsLocked = false; + bIsLawEnforcer = false; + bIsAmbulanceOnDuty = false; + bIsFireTruckOnDuty = false; +#ifdef FIX_BUGS + bIsHandbrakeOn = false; +#endif + CCarCtrl::UpdateCarCount(this, false); + m_fHealth = 1000.0f; + bEngineOn = true; + bFreebies = true; + pDriver = nil; + m_nNumPassengers = 0; + m_nNumGettingIn = 0; + m_nGettingInFlags = 0; + m_nGettingOutFlags = 0; + m_nNumMaxPassengers = ARRAY_SIZE(pPassengers); + for(i = 0; i < m_nNumMaxPassengers; i++) + pPassengers[i] = nil; + m_nBombTimer = 0; + m_pBlowUpEntity = nil; + m_nPacManPickupsCarried = 0; + bComedyControls = false; + bCraneMessageDone = false; + bExtendedRange = false; + bTakeLessDamage = false; + bIsDamaged = false; + bFadeOut = false; + bIsBeingCarJacked = false; + m_nTimeOfDeath = 0; + m_pCarFire = nil; + bHasBeenOwnedByPlayer = false; + bCreateRoadBlockPeds = false; + bCanBeDamaged = true; + bUsingSpecialColModel = false; + bOccupantsHaveBeenGenerated = false; + bGunSwitchedOff = false; + m_nGunFiringTime = 0; + m_nTimeBlocked = 0; + bLightsOn = false; + bVehicleColProcessed = false; + m_numPedsUseItAsCover = 0; + bIsCarParkVehicle = false; + bHasAlreadyBeenRecorded = false; + m_bSirenOrAlarm = false; + m_nCarHornTimer = 0; + m_nCarHornPattern = 0; + m_nAlarmState = 0; + m_nDoorLock = CARLOCK_UNLOCKED; + m_nLastWeaponDamage = -1; + m_fMapObjectHeightAhead = m_fMapObjectHeightBehind = 0.0f; + m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, this); + if(m_audioEntityId >= 0) + DMAudio.SetEntityStatus(m_audioEntityId, TRUE); + m_nRadioStation = CGeneral::GetRandomNumber() % USERTRACK; + m_pCurGroundEntity = nil; + m_bRainAudioCounter = 0; + m_bRainSamplesCounter = 0; + m_comedyControlState = 0; + m_aCollPolys[0].valid = false; + m_aCollPolys[1].valid = false; + AutoPilot.m_nCarMission = MISSION_NONE; + AutoPilot.m_nTempAction = TEMPACT_NONE; + AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds(); + AutoPilot.m_bStayInCurrentLevel = false; + AutoPilot.m_bIgnorePathfinding = false; +} + +CVehicle::~CVehicle() +{ + m_nAlarmState = 0; + if (m_audioEntityId >= 0){ + DMAudio.DestroyEntity(m_audioEntityId); + m_audioEntityId = -5; + } + CRadar::ClearBlipForEntity(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(this)); + if (pDriver) + pDriver->FlagToDestroyWhenNextProcessed(); + for (int i = 0; i < m_nNumMaxPassengers; i++){ + if (pPassengers[i]) + pPassengers[i]->FlagToDestroyWhenNextProcessed(); + } + if (m_pCarFire) + m_pCarFire->Extinguish(); + CCarCtrl::UpdateCarCount(this, true); + if (bIsAmbulanceOnDuty){ + CCarCtrl::NumAmbulancesOnDuty--; + bIsAmbulanceOnDuty = false; + } + if (bIsFireTruckOnDuty){ + CCarCtrl::NumFiretrucksOnDuty--; + bIsFireTruckOnDuty = false; + } +} + +void +CVehicle::SetModelIndex(uint32 id) +{ + CEntity::SetModelIndex(id); + m_aExtras[0] = CVehicleModelInfo::ms_compsUsed[0]; + m_aExtras[1] = CVehicleModelInfo::ms_compsUsed[1]; + m_nNumMaxPassengers = CVehicleModelInfo::GetMaximumNumberOfPassengersFromNumberOfDoors(id); +} + +bool +CVehicle::SetupLighting(void) +{ + ActivateDirectional(); + SetAmbientColoursForPedsCarsAndObjects(); + + if(bRenderScorched){ + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + }else{ + CVector coors = GetPosition(); + float lighting = CPointLights::GenerateLightsAffectingObject(&coors); + if(!bHasBlip && lighting != 1.0f){ + SetAmbientAndDirectionalColours(lighting); + return true; + } + } + + return false; +} + +void +CVehicle::RemoveLighting(bool reset) +{ + CRenderer::RemoveVehiclePedLights(this, reset); +} + +float +CVehicle::GetHeightAboveRoad(void) +{ + return -1.0f * GetColModel()->boundingBox.min.z; +} + +const float fRCPropFallOff = 3.0f; +const float fRCAeroThrust = 0.003f; +const float fRCSideSlipMult = 0.1f; +const float fRCRudderMult = 0.2f; +const float fRCYawMult = -0.01f; +const float fRCRollMult = 0.02f; +const float fRCRollStabilise = -0.08f; +const float fRCPitchMult = 0.005f; +const float fRCTailMult = 0.3f; +const float fRCFormLiftMult = 0.02f; +const float fRCAttackLiftMult = 0.25f; +const CVector vecRCAeroResistance(0.998f, 0.998f, 0.9f); + +const float fSeaPropFallOff = 2.3f; +const float fSeaThrust = 0.002f; +const float fSeaSideSlipMult = 0.1f; +const float fSeaRudderMult = 0.01f; +const float fSeaYawMult = -0.0003f; +const float fSeaRollMult = 0.0015f; +const float fSeaRollStabilise = -0.01f; +const float fSeaPitchMult = 0.0002f; +const float fSeaTailMult = 0.01f; +const float fSeaFormLiftMult = 0.012f; +const float fSeaAttackLiftMult = 0.1f; +const CVector vecSeaAeroResistance(0.995f, 0.995f, 0.85f); + +const float fSpeedResistanceY = 500.0f; +const float fSpeedResistanceZ = 500.0f; + +const CVector vecHeliMoveRes(0.995f, 0.995f, 0.99f); +const CVector vecRCHeliMoveRes(0.99f, 0.99f, 0.99f); +const float fThrustVar = 0.3f; +const float fRotorFallOff = 0.75f; +const float fStabiliseVar = 0.015f; +const float fPitchBrake = 10.0f; +const float fPitchVar = 0.006f; +const float fRollVar = 0.006f; +const float fYawVar = -0.001f; +const CVector vecHeliResistance(0.81f, 0.85f, 0.99f); +const CVector vecRCHeliResistance(0.92f, 0.92f, 0.998f); +const float fSpinSpeedRes = 20.0f; + +void +CVehicle::FlyingControl(eFlightModel flightModel) +{ + switch(flightModel){ + case FLIGHT_MODEL_DODO: + { + // This seems pretty magic + + // Move Left/Right + float moveSpeed = m_vecMoveSpeed.Magnitude(); + float sideSpeed = DotProduct(m_vecMoveSpeed, GetRight()); + float sideImpulse = -1.0f * sideSpeed / moveSpeed; + float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward()); + float magic = m_vecMoveSpeed.MagnitudeSqr() * sq(fwdSpeed); + float turnImpulse = (sideImpulse*0.003f + m_fSteerAngle*0.001f) * + magic*m_fTurnMass*CTimer::GetTimeStep(); + ApplyTurnForce(turnImpulse*GetRight(), -4.0f*GetForward()); + + float impulse = sideImpulse*0.2f * + magic*m_fMass*CTimer::GetTimeStep(); + ApplyMoveForce(impulse*GetRight()); + ApplyTurnForce(impulse*GetRight(), 2.0f*GetUp()); + + + // Move Up/Down + moveSpeed = m_vecMoveSpeed.Magnitude(); + float upSpeed = DotProduct(m_vecMoveSpeed, GetUp()); + float upImpulse = -1.0f * upSpeed / moveSpeed; + turnImpulse = (upImpulse*0.002f + -CPad::GetPad(0)->GetSteeringUpDown()/128.0f*0.001f) * + magic*m_fTurnMass*CTimer::GetTimeStep(); + ApplyTurnForce(turnImpulse*GetUp(), -4.0f*GetForward()); + + impulse = (upImpulse*3.5f + 0.5f)*0.05f * + magic*m_fMass*CTimer::GetTimeStep(); + if(GRAVITY*m_fMass*CTimer::GetTimeStep() < impulse && + GetPosition().z > 100.0f) + impulse = 0.9f*GRAVITY*m_fMass*CTimer::GetTimeStep(); + CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + ApplyMoveForce(impulse*GetUp()); + ApplyTurnForce(impulse*GetUp(), 2.0f*GetUp() + com); + + + m_vecTurnSpeed.y *= Pow(0.9f, CTimer::GetTimeStep()); + moveSpeed = m_vecMoveSpeed.MagnitudeSqr(); + if(moveSpeed > SQR(1.5f)) + m_vecMoveSpeed *= 1.5f/Sqrt(moveSpeed); + + float turnSpeed = m_vecTurnSpeed.MagnitudeSqr(); + if(turnSpeed > SQR(0.2f)) + m_vecTurnSpeed *= 0.2f/Sqrt(turnSpeed); + break; + } + + case FLIGHT_MODEL_RCPLANE: + case FLIGHT_MODEL_SEAPLANE: + { + // thrust + float fThrust = flightModel == FLIGHT_MODEL_RCPLANE ? fRCAeroThrust : fSeaThrust; + float fThrustFallOff = flightModel == FLIGHT_MODEL_RCPLANE ? fRCPropFallOff : fSeaPropFallOff; + + float fForwSpeed = DotProduct(GetMoveSpeed(), GetForward()); + CVector vecTail = GetColModel()->boundingBox.min.y * GetForward(); + float fPedalState = (CPad::GetPad(0)->GetAccelerate() - CPad::GetPad(0)->GetBrake()) / 255.0f; + if (fForwSpeed > 0.1f || (flightModel == FLIGHT_MODEL_RCPLANE && fForwSpeed > 0.02f)) + fPedalState += 1.0f; + else if (fForwSpeed > 0.0f && fPedalState < 0.0f) + fPedalState = 0.0f; + float fThrustAccel = (fPedalState - fThrustFallOff * fForwSpeed) * fThrust; + + ApplyMoveForce(fThrustAccel * GetForward() * m_fMass * CTimer::GetTimeStep()); + + // left/right + float fSideSpeed = -DotProduct(GetMoveSpeed(), GetRight()); + float fSteerLR = CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f; + float fSideSlipAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fSideSlipAccel = Abs(fSideSpeed) * fSideSpeed * fRCSideSlipMult; + else + fSideSlipAccel = Abs(fSideSpeed) * fSideSpeed * fSeaSideSlipMult; + ApplyMoveForce(m_fMass * GetRight() * fSideSlipAccel * CTimer::GetTimeStep()); + + float fYaw = -DotProduct(GetSpeed(vecTail), GetRight()); + float fYawAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fYawAccel = fRCRudderMult * fYaw * Abs(fYaw) + fRCYawMult * fSteerLR * fForwSpeed; + else + fYawAccel = fSeaRudderMult * fYaw * Abs(fYaw) + fSeaYawMult * fSteerLR * fForwSpeed; + ApplyTurnForce(fYawAccel * GetRight() * m_fTurnMass * CTimer::GetTimeStep(), vecTail); + + float fRollAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) { + float fDirectionMultiplier = CPad::GetPad(0)->GetLookRight(); + if (CPad::GetPad(0)->GetLookLeft()) + fDirectionMultiplier = -1; + fRollAccel = (0.5f * fDirectionMultiplier + fSteerLR) * fRCRollMult; + } + else + fRollAccel = fSteerLR * fSeaRollMult; + ApplyTurnForce(GetRight() * fRollAccel * fForwSpeed * m_fTurnMass * CTimer::GetTimeStep(), GetUp()); + + CVector vecFRight = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f)); + CVector vecStabilise = (GetUp().z > 0.0f) ? vecFRight : -vecFRight; + float fStabiliseDirection = (GetRight().z > 0.0f) ? -1.0f : 1.0f; + float fStabiliseSpeed; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fStabiliseSpeed = fRCRollStabilise * fStabiliseDirection * (1.0f - DotProduct(GetRight(), vecStabilise)) * (1.0f - Abs(GetForward().z)); + else + fStabiliseSpeed = fSeaRollStabilise * fStabiliseDirection * (1.0f - DotProduct(GetRight(), vecStabilise)) * (1.0f - Abs(GetForward().z)); + ApplyTurnForce(fStabiliseSpeed * m_fTurnMass * GetRight(), GetUp()); // no CTimer::GetTimeStep(), is it right? VC doesn't have it too + + // up/down + float fTail = -DotProduct(GetSpeed(vecTail), GetUp()); + float fSteerUD = -CPad::GetPad(0)->GetSteeringUpDown() / 128.0f; + float fPitchAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fPitchAccel = fRCTailMult * fTail * Abs(fTail) + fRCPitchMult * fSteerUD * fForwSpeed; + else + fPitchAccel = fSeaTailMult * fTail * Abs(fTail) + fSeaPitchMult * fSteerUD * fForwSpeed; + ApplyTurnForce(fPitchAccel * m_fTurnMass * GetUp() * CTimer::GetTimeStep(), vecTail); + + float fLift = DotProduct(GetMoveSpeed(), GetUp()) / Max(0.01f, GetMoveSpeed().Magnitude()); //accel*angle + float fLiftAccel; + if (flightModel == FLIGHT_MODEL_RCPLANE) + fLiftAccel = (fRCFormLiftMult - fRCAttackLiftMult * fLift) * SQR(fForwSpeed); + else + fLiftAccel = (fSeaFormLiftMult - fSeaAttackLiftMult * fLift) * SQR(fForwSpeed); + float fLiftImpulse = fLiftAccel * m_fMass * CTimer::GetTimeStep(); + if (GRAVITY * CTimer::GetTimeStep() * m_fMass < fLiftImpulse) { + if (flightModel == FLIGHT_MODEL_RCPLANE && GetPosition().z > 50.0f) + fLiftImpulse = CTimer::GetTimeStep() * 0.9f*GRAVITY * m_fMass; + else if (flightModel == FLIGHT_MODEL_SEAPLANE && GetPosition().z > 80.0f) + fLiftImpulse = CTimer::GetTimeStep() * 0.9f*GRAVITY * m_fMass; + } + ApplyMoveForce(fLiftImpulse * GetUp()); + + CVector vecResistance; + if (flightModel == FLIGHT_MODEL_RCPLANE) + vecResistance = vecRCAeroResistance; + else + vecResistance = vecSeaAeroResistance; + float rX = Pow(vecResistance.x, CTimer::GetTimeStep()); + float rY = Pow(vecResistance.y, CTimer::GetTimeStep()); + float rZ = Pow(vecResistance.z, CTimer::GetTimeStep()); + CVector vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); + vecTurnSpeed.x *= rX; + float fResistance = vecTurnSpeed.y * (1.0f / (fSpeedResistanceY * SQR(vecTurnSpeed.y) + 1.0f)) * rY - vecTurnSpeed.y; + vecTurnSpeed.z *= rZ; + m_vecTurnSpeed = Multiply3x3(GetMatrix(), vecTurnSpeed); + ApplyTurnForce(-GetUp() * fResistance * m_fTurnMass, GetRight() + Multiply3x3(GetMatrix(), m_vecCentreOfMass)); + break; + } + case FLIGHT_MODEL_HELI: + { + CVector vecMoveResistance; + if (GetModelIndex() == MI_MIAMI_SPARROW) + vecMoveResistance = vecHeliMoveRes; + else + vecMoveResistance = vecRCHeliMoveRes; + float rmX = Pow(vecMoveResistance.x, CTimer::GetTimeStep()); + float rmY = Pow(vecMoveResistance.y, CTimer::GetTimeStep()); + float rmZ = Pow(vecMoveResistance.z, CTimer::GetTimeStep()); + m_vecMoveSpeed.x *= rmX; + m_vecMoveSpeed.y *= rmY; + m_vecMoveSpeed.z *= rmZ; + if (GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE) + return; + float fThrust; + if (bCheat5) + fThrust = CPad::GetPad(0)->GetSteeringUpDown() * fThrustVar / 128.0f + 0.95f; + else + fThrust = fThrustVar * (CPad::GetPad(0)->GetAccelerate() - 2 * CPad::GetPad(0)->GetBrake()) / 255.0f + 0.95f; + fThrust -= fRotorFallOff * DotProduct(m_vecMoveSpeed, GetUp()); +#if GTA_VERSION >= GTA3_PC_11 + if (fThrust > 0.9f && GetPosition().z > 80.0f) + fThrust = 0.9f; +#endif + ApplyMoveForce(GRAVITY * GetUp() * fThrust * m_fMass * CTimer::GetTimeStep()); + + if (GetUp().z > 0.0f) + ApplyTurnForce(-CVector(GetUp().x, GetUp().y, 0.0f) * fStabiliseVar * m_fTurnMass * CTimer::GetTimeStep(), GetUp()); + + float fRoll, fPitch, fYaw; + if (bCheat5) { + fPitch = CPad::GetPad(0)->GetCarGunUpDown() / 128.0f; + fRoll = -CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f; + fYaw = CPad::GetPad(0)->GetCarGunLeftRight() / 128.0f; + } + else { + fPitch = CPad::GetPad(0)->GetSteeringUpDown() / 128.0f; + fRoll = CPad::GetPad(0)->GetLookLeft(); + if (CPad::GetPad(0)->GetLookRight()) + fRoll = -1.0f; + fYaw = CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f; + } + if (CPad::GetPad(0)->GetHorn()) { + fYaw = 0.0f; + fPitch = Clamp(10.0f * DotProduct(m_vecMoveSpeed, GetForward()), -200.0f, 1.3f); + fRoll = Clamp(10.0f * DotProduct(m_vecMoveSpeed, GetRight()), -200.0f, 1.3f); + } + ApplyTurnForce(fPitch * GetUp() * fPitchVar * m_fTurnMass * CTimer::GetTimeStep(), GetForward()); + ApplyTurnForce(fRoll * GetUp() * fRollVar * m_fTurnMass * CTimer::GetTimeStep(), GetRight()); + ApplyTurnForce(fYaw * GetForward() * fYawVar * m_fTurnMass * CTimer::GetTimeStep(), GetRight()); + + CVector vecResistance; + if (GetModelIndex() == MI_MIAMI_SPARROW) + vecResistance = vecHeliResistance; + else + vecResistance = vecRCHeliResistance; + float rX = Pow(vecResistance.x, CTimer::GetTimeStep()); + float rY = Pow(vecResistance.y, CTimer::GetTimeStep()); + float rZ = Pow(vecResistance.z, CTimer::GetTimeStep()); + CVector vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); + float fResistanceMultiplier = Pow(1.0f / (fSpinSpeedRes * SQR(vecTurnSpeed.z) + 1.0f) * rZ, CTimer::GetTimeStep()); + float fResistance = vecTurnSpeed.z * fResistanceMultiplier - vecTurnSpeed.z; + vecTurnSpeed.x *= rX; + vecTurnSpeed.y *= rY; + vecTurnSpeed.z *= fResistanceMultiplier; + m_vecTurnSpeed = Multiply3x3(GetMatrix(), vecTurnSpeed); + ApplyTurnForce(-GetRight() * fResistance * m_fTurnMass, GetForward() + Multiply3x3(GetMatrix(), m_vecCentreOfMass)); + break; + } + } +} + +float fBurstSpeedMax = 0.3f; +float fBurstTyreMod = 0.1f; + +void +CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, + int32 wheelsOnGround, float thrust, float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, uint16 wheelStatus) +{ + // BUG: using statics here is probably a bad idea + static bool bAlreadySkidding = false; // this is never reset + static bool bBraking; + static bool bDriving; + +#ifdef FIX_SIGNIFICANT_BUGS + bAlreadySkidding = false; +#endif + + // how much force we want to apply in these axes + float fwd = 0.0f; + float right = 0.0f; + + bBraking = brake != 0.0f; + if(bBraking) + thrust = 0.0f; + bDriving = thrust != 0.0f; + + float contactSpeedFwd = DotProduct(wheelContactSpeed, wheelFwd); + float contactSpeedRight = DotProduct(wheelContactSpeed, wheelRight); + + if(*wheelState != WHEEL_STATE_NORMAL) + bAlreadySkidding = true; + *wheelState = WHEEL_STATE_NORMAL; + + adhesion *= CTimer::GetTimeStep(); + if(bAlreadySkidding) + adhesion *= pHandling->fTractionLoss; + + // moving sideways + if(contactSpeedRight != 0.0f){ + // exert opposing force + right = -contactSpeedRight/wheelsOnGround; + // BUG? + // contactSpeedRight is independent of framerate but right has timestep as a factor + // so we probably have to fix this + // fixing this causes jittery cars at 15fps, and causes the car to move backwards slowly at 18fps + // at 19fps, the effects are gone ... + //right *= CTimer::GetTimeStepFix(); + + if(wheelStatus == WHEEL_STATUS_BURST){ + float fwdspeed = Min(contactSpeedFwd, fBurstSpeedMax); + right += fwdspeed * CGeneral::GetRandomNumberInRange(-fBurstTyreMod, fBurstTyreMod); + } + } + + if(bDriving){ + fwd = thrust; + + // limit sideways force (why?) + if(right > 0.0f){ + if(right > adhesion) + right = adhesion; + }else{ + if(right < -adhesion) + right = -adhesion; + } + }else if(contactSpeedFwd != 0.0f){ + fwd = -contactSpeedFwd/wheelsOnGround; +#ifdef FIX_BUGS + // contactSpeedFwd is independent of framerate but fwd has timestep as a factor + // so we probably have to fix this + // better get rid of it here too + //fwd *= CTimer::GetTimeStepFix(); +#endif + + if(!bBraking){ + if(m_fGasPedal < 0.01f){ + if(GetModelIndex() == MI_RCBANDIT) + brake = 0.2f * mod_HandlingManager.fWheelFriction / pHandling->fMass; + else + brake = mod_HandlingManager.fWheelFriction / pHandling->fMass; +#ifdef FIX_BUGS + brake *= CTimer::GetTimeStepFix(); +#endif + } + } + + if(brake > adhesion){ + if(Abs(contactSpeedFwd) > 0.005f) + *wheelState = WHEEL_STATE_FIXED; + }else { + if(fwd > 0.0f){ + if(fwd > brake) + fwd = brake; + }else{ + if(fwd < -brake) + fwd = -brake; + } + } + } + + float speedSq = sq(right) + sq(fwd); + if(sq(adhesion) < speedSq){ + if(*wheelState != WHEEL_STATE_FIXED){ + if(bDriving && contactSpeedFwd < 0.2f) + *wheelState = WHEEL_STATE_SPINNING; + else + *wheelState = WHEEL_STATE_SKIDDING; + } + + float l = Sqrt(speedSq); + float tractionLoss = bAlreadySkidding ? 1.0f : pHandling->fTractionLoss; + right *= adhesion * tractionLoss / l; + fwd *= adhesion * tractionLoss / l; + } + + if(fwd != 0.0f || right != 0.0f){ + CVector direction = fwd*wheelFwd + right*wheelRight; + float speed = direction.Magnitude(); + direction.Normalise(); + + float impulse = speed*m_fMass; + float turnImpulse = speed*GetMass(wheelContactPoint, direction); + + ApplyMoveForce(impulse * direction); + ApplyTurnForce(turnImpulse * direction, wheelContactPoint); + } +} + +void +CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, int32 wheelsOnGround, float thrust, + float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, eBikeWheelSpecial special, uint16 wheelStatus) +{ + // TODO: mobile code +} + +float +CVehicle::ProcessWheelRotation(tWheelState state, const CVector &fwd, const CVector &speed, float radius) +{ + float angularVelocity; + switch(state){ + case WHEEL_STATE_SPINNING: + angularVelocity = -1.1f; // constant speed forward + break; + case WHEEL_STATE_FIXED: + angularVelocity = 0.0f; // not moving + break; + default: + angularVelocity = -DotProduct(fwd, speed) / radius; // forward speed + break; + } + return angularVelocity * CTimer::GetTimeStep(); +} + +void +CVehicle::InflictDamage(CEntity* damagedBy, eWeaponType weaponType, float damage) +{ + if (!bCanBeDamaged) + return; + if (bOnlyDamagedByPlayer && (damagedBy != FindPlayerPed() && damagedBy != FindPlayerVehicle())) + return; + bool bFrightensDriver = false; + switch (weaponType) { + case WEAPONTYPE_UNARMED: + case WEAPONTYPE_BASEBALLBAT: + if (bMeleeProof) + return; + break; + case WEAPONTYPE_COLT45: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_AK47: + case WEAPONTYPE_M16: + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_TOTAL_INVENTORY_WEAPONS: + case WEAPONTYPE_UZI_DRIVEBY: + if (bBulletProof) + return; + bFrightensDriver = true; + break; + case WEAPONTYPE_ROCKETLAUNCHER: + case WEAPONTYPE_MOLOTOV: + case WEAPONTYPE_GRENADE: + case WEAPONTYPE_EXPLOSION: + if (bExplosionProof) + return; + bFrightensDriver = true; + break; + case WEAPONTYPE_FLAMETHROWER: + if (bFireProof) + return; + break; + case WEAPONTYPE_RAMMEDBYCAR: + if (bCollisionProof) + return; + break; + default: + break; + } + if (m_fHealth > 0.0f) { + if (VehicleCreatedBy == RANDOM_VEHICLE && pDriver && + (GetStatus() == STATUS_SIMPLE || GetStatus() == STATUS_PHYSICS) && + AutoPilot.m_nCarMission == MISSION_CRUISE) { + if (m_randomSeed < DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE) { + CCarCtrl::SwitchVehicleToRealPhysics(this); + AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * pHandling->Transmission.fMaxCruiseVelocity; + SetStatus(STATUS_PHYSICS); + } + } + m_nLastWeaponDamage = weaponType; + float oldHealth = m_fHealth; + if (m_fHealth > damage) { + m_fHealth -= damage; + if (VehicleCreatedBy == RANDOM_VEHICLE && + (m_fHealth < DAMAGE_HEALTH_TO_FLEE_ALWAYS || + bFrightensDriver && m_randomSeed > DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE)) { + switch (GetStatus()) { + case STATUS_SIMPLE: + case STATUS_PHYSICS: + if (pDriver) { + SetStatus(STATUS_ABANDONED); + pDriver->bFleeAfterExitingCar = true; + pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, this); + } + for (int i = 0; i < m_nNumMaxPassengers; i++) { + if (pPassengers[i]) { + pPassengers[i]->bFleeAfterExitingCar = true; + pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this); + } + } + break; + default: + break; + } + } + if (oldHealth >= DAMAGE_HEALTH_TO_CATCH_FIRE && m_fHealth < DAMAGE_HEALTH_TO_CATCH_FIRE) { + if (IsCar()) { + CAutomobile* pThisCar = (CAutomobile*)this; + pThisCar->Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE); + pThisCar->m_pSetOnFireEntity = damagedBy; + if (damagedBy) + damagedBy->RegisterReference((CEntity**)&pThisCar->m_pSetOnFireEntity); + } + } + } + else { + m_fHealth = 0.0f; + if (weaponType == WEAPONTYPE_EXPLOSION) { + // between 1000 and 3047. Also not very nice: can't be saved by respray or cheat + m_nBombTimer = 1000 + CGeneral::GetRandomNumber() & 0x7FF; + m_pBlowUpEntity = damagedBy; + if (damagedBy) + damagedBy->RegisterReference((CEntity**)&m_pBlowUpEntity); + } + else + BlowUpCar(damagedBy); + } + } +#ifdef FIX_BUGS // removing dumb case when shooting police car in player's own garage gives wanted level + if (GetModelIndex() == MI_POLICE && damagedBy == FindPlayerPed() && damagedBy != nil && !bHasBeenOwnedByPlayer) +#else + if (GetModelIndex() == MI_POLICE && damagedBy == FindPlayerPed()) +#endif + FindPlayerPed()->SetWantedLevelNoDrop(1); +} + +void +CVehicle::DoFixedMachineGuns(void) +{ + if(CPad::GetPad(0)->GetCarGunFired() && !bGunSwitchedOff){ + if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 150){ + CVector source, target; + float dx, dy, len; + + dx = GetForward().x; + dy = GetForward().y; + len = Sqrt(SQR(dx) + SQR(dy)); + if(len < 0.1f) len = 0.1f; + dx /= len; + dy /= len; + + m_nGunFiringTime = CTimer::GetTimeInMilliseconds(); + + source = GetMatrix() * CVector(2.0f, 2.5f, 1.0f); + target = source + CVector(dx, dy, 0.0f)*60.0f; + target += CVector( + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f, + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f, + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.02f); + CWeapon::DoTankDoomAiming(this, pDriver, &source, &target); + FireOneInstantHitRound(&source, &target, 15); + + source = GetMatrix() * CVector(-2.0f, 2.5f, 1.0f); + target = source + CVector(dx, dy, 0.0f)*60.0f; + target += CVector( + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f, + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f, + ((CGeneral::GetRandomNumber()&0xFF)-128) * 0.02f); + CWeapon::DoTankDoomAiming(this, pDriver, &source, &target); + FireOneInstantHitRound(&source, &target, 15); + + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + + m_nAmmoInClip--; + if(m_nAmmoInClip == 0){ + m_nAmmoInClip = 20; + m_nGunFiringTime = CTimer::GetTimeInMilliseconds() + 1400; + } + } + }else{ + if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 1400) + m_nAmmoInClip = 20; + } +} + +void +CVehicle::ExtinguishCarFire(void) +{ + m_fHealth = Max(m_fHealth, 300.0f); + if(m_pCarFire) + m_pCarFire->Extinguish(); + if(IsCar()){ + CAutomobile *car = (CAutomobile*)this; + if(car->Damage.GetEngineStatus() >= ENGINE_STATUS_ON_FIRE) + car->Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE-10); + car->m_fFireBlowUpTimer = 0.0f; + } +} + +bool +CVehicle::ShufflePassengersToMakeSpace(void) +{ + if (m_nNumPassengers >= m_nNumMaxPassengers) + return false; + if (pPassengers[1] && + !(m_nGettingInFlags & CAR_DOOR_FLAG_LR) && + IsRoomForPedToLeaveCar(CAR_DOOR_LR, nil)) { + if (!pPassengers[2] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RR)) { + pPassengers[2] = pPassengers[1]; + pPassengers[1] = nil; + pPassengers[2]->m_vehDoor = CAR_DOOR_RR; + return true; + } + if (!pPassengers[0] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RF)) { + pPassengers[0] = pPassengers[1]; + pPassengers[1] = nil; + pPassengers[0]->m_vehDoor = CAR_DOOR_RF; + return true; + } + return false; + } + if (pPassengers[2] && + !(m_nGettingInFlags & CAR_DOOR_FLAG_RR) && + IsRoomForPedToLeaveCar(CAR_DOOR_RR, nil)) { + if (!pPassengers[1] && !(m_nGettingInFlags & CAR_DOOR_FLAG_LR)) { + pPassengers[1] = pPassengers[2]; + pPassengers[2] = nil; + pPassengers[1]->m_vehDoor = CAR_DOOR_LR; + return true; + } + if (!pPassengers[0] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RF)) { + pPassengers[0] = pPassengers[2]; + pPassengers[2] = nil; + pPassengers[0]->m_vehDoor = CAR_DOOR_RF; + return true; + } + return false; + } + if (pPassengers[0] && + !(m_nGettingInFlags & CAR_DOOR_FLAG_RF) && + IsRoomForPedToLeaveCar(CAR_DOOR_RF, nil)) { + if (!pPassengers[1] && !(m_nGettingInFlags & CAR_DOOR_FLAG_LR)) { + pPassengers[1] = pPassengers[0]; + pPassengers[0] = nil; + pPassengers[1]->m_vehDoor = CAR_DOOR_LR; + return true; + } + if (!pPassengers[2] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RR)) { + pPassengers[2] = pPassengers[0]; + pPassengers[0] = nil; + pPassengers[2]->m_vehDoor = CAR_DOOR_RR; + return true; + } + return false; + } + return false; +} + +void +CVehicle::ProcessDelayedExplosion(void) +{ + if(m_nBombTimer == 0) + return; + + int tick = CTimer::GetTimeStep()/60.0f*1000.0f; + int16 prev = m_nBombTimer; + if(tick > m_nBombTimer) + m_nBombTimer = 0; + else + m_nBombTimer -= tick; + + if(IsCar() && ((CAutomobile*)this)->m_bombType == CARBOMB_TIMEDACTIVE && (m_nBombTimer & 0xFE00) != (prev & 0xFE00)) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_BOMB_TICK, 0.0f); + + if (m_nBombTimer == 0){ + if(FindPlayerVehicle() != this && m_pBlowUpEntity == FindPlayerPed()) + CWorld::Players[CWorld::PlayerInFocus].AwardMoneyForExplosion(this); + BlowUpCar(m_pBlowUpEntity); + } +} + +bool +CVehicle::IsLawEnforcementVehicle(void) +{ + switch(GetModelIndex()){ + case MI_FBICAR: + case MI_POLICE: + case MI_ENFORCER: + case MI_PREDATOR: + case MI_RHINO: + case MI_BARRACKS: + return true; + default: + return false; + } +} + +bool +CVehicle::UsesSiren(uint32 id) +{ + switch(id){ + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_FBICAR: + case MI_MRWHOOP: + case MI_POLICE: + case MI_ENFORCER: + case MI_PREDATOR: + return true; + default: + return false; + } +} + +bool +CVehicle::IsVehicleNormal(void) +{ + if (!pDriver || m_nNumPassengers != 0 || GetStatus() == STATUS_WRECKED) + return false; + switch (GetModelIndex()){ + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_TAXI: + case MI_POLICE: + case MI_ENFORCER: + case MI_BUS: + case MI_RHINO: + case MI_BARRACKS: + case MI_DODO: + case MI_COACH: + case MI_CABBIE: + case MI_RCBANDIT: + case MI_BORGNINE: + return false; + default: + return true; + } +} + +bool +CVehicle::CarHasRoof(void) +{ + if((pHandling->Flags & HANDLING_HAS_NO_ROOF) == 0) + return true; + if(m_aExtras[0] && m_aExtras[1]) + return false; + return true; +} + +bool +CVehicle::IsUpsideDown(void) +{ + if(GetUp().z > -0.9f) + return false; + return true; +} + +bool +CVehicle::IsOnItsSide(void) +{ + if(GetRight().z < 0.8f && GetRight().z > -0.8f) + return false; + return true; +} + +bool +CVehicle::CanBeDeleted(void) +{ + int i; + + if(m_nNumGettingIn || m_nGettingOutFlags) + return false; + + if(pDriver){ + // This looks like it was inlined + if(pDriver->CharCreatedBy == MISSION_CHAR) + return false; + if(pDriver->GetPedState() != PED_DRIVING && + pDriver->GetPedState() != PED_DEAD) + return false; + } + + for(i = 0; i < ARRAY_SIZE(pPassengers); i++){ + // Same check as above + if(pPassengers[i]){ + if(pPassengers[i]->CharCreatedBy == MISSION_CHAR) + return false; + if(pPassengers[i]->GetPedState() != PED_DRIVING && + pPassengers[i]->GetPedState() != PED_DEAD) + return false; + } + // and then again... probably because something was inlined + if(pPassengers[i]){ + if(pPassengers[i]->GetPedState() != PED_DRIVING && + pPassengers[i]->GetPedState() != PED_DEAD) + return false; + } + } + + switch(VehicleCreatedBy){ + case RANDOM_VEHICLE: return true; + case MISSION_VEHICLE: return false; + case PARKED_VEHICLE: return true; + case PERMANENT_VEHICLE: return false; + } + return true; +} + +bool +CVehicle::CanPedOpenLocks(CPed *ped) +{ + if(m_nDoorLock == CARLOCK_LOCKED || + m_nDoorLock == CARLOCK_LOCKED_INITIALLY || + m_nDoorLock == CARLOCK_LOCKED_PLAYER_INSIDE) + return false; + if(ped->IsPlayer() && m_nDoorLock == CARLOCK_LOCKOUT_PLAYER_ONLY) + return false; + return true; +} + +bool +CVehicle::CanPedEnterCar(void) +{ + // can't enter when car is on side + if(GetUp().z > 0.1f || GetUp().z < -0.1f){ + // also when car is moving too fast + if(m_vecMoveSpeed.MagnitudeSqr() > sq(0.2f)) + return false; + if(m_vecTurnSpeed.MagnitudeSqr() > sq(0.2f)) + return false; + return true; + } + return false; +} + +bool +CVehicle::CanPedExitCar(void) +{ + CVector up = GetUp(); + if(up.z > 0.1f || up.z < -0.1f){ +#ifdef VC_PED_PORTS + if (IsBoat()) + return true; +#endif + // can't exit when car is moving too fast + if(m_vecMoveSpeed.MagnitudeSqr() > 0.005f) + return false; + // if car is slow enough, check turn speed + if(Abs(m_vecTurnSpeed.x) > 0.01f || + Abs(m_vecTurnSpeed.y) > 0.01f || + Abs(m_vecTurnSpeed.z) > 0.01f) + return false; + return true; + }else{ + // What is this? just > replaced by >= ?? + + // can't exit when car is moving too fast + if(m_vecMoveSpeed.MagnitudeSqr() >= 0.005f) + return false; + // if car is slow enough, check turn speed + if(Abs(m_vecTurnSpeed.x) >= 0.01f || + Abs(m_vecTurnSpeed.y) >= 0.01f || + Abs(m_vecTurnSpeed.z) >= 0.01f) + return false; + return true; + } +} + +void +CVehicle::ChangeLawEnforcerState(uint8 enable) +{ + if (enable) { + if (!bIsLawEnforcer) { + bIsLawEnforcer = true; + CCarCtrl::NumLawEnforcerCars++; + } + } else { + if (bIsLawEnforcer) { + bIsLawEnforcer = false; + CCarCtrl::NumLawEnforcerCars--; + } + } +} + +CPed* +CVehicle::SetUpDriver(void) +{ + if(pDriver) + return pDriver; + if(VehicleCreatedBy != RANDOM_VEHICLE) + return nil; + + pDriver = CPopulation::AddPedInCar(this); + pDriver->m_pMyVehicle = this; + pDriver->m_pMyVehicle->RegisterReference((CEntity**)&pDriver->m_pMyVehicle); + pDriver->bInVehicle = true; + pDriver->SetPedState(PED_DRIVING); + if(bIsBus) + pDriver->bRenderPedInCar = false; + return pDriver; +} + +CPed* +CVehicle::SetupPassenger(int n) +{ + if(pPassengers[n]) + return pPassengers[n]; + + pPassengers[n] = CPopulation::AddPedInCar(this); + pPassengers[n]->m_pMyVehicle = this; + pPassengers[n]->m_pMyVehicle->RegisterReference((CEntity**)&pPassengers[n]->m_pMyVehicle); + pPassengers[n]->bInVehicle = true; + pPassengers[n]->SetPedState(PED_DRIVING); + if(bIsBus) + pPassengers[n]->bRenderPedInCar = false; + ++m_nNumPassengers; + return pPassengers[n]; +} + +void +CVehicle::SetDriver(CPed *driver) +{ + pDriver = driver; + pDriver->RegisterReference((CEntity**)&pDriver); + + if(bFreebies && driver == FindPlayerPed()){ + if(GetModelIndex() == MI_AMBULAN) + FindPlayerPed()->m_fHealth = Min(FindPlayerPed()->m_fHealth + 20.0f, 100.0f); + else if(GetModelIndex() == MI_TAXI) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 25; + else if(GetModelIndex() == MI_POLICE) + driver->GiveWeapon(WEAPONTYPE_SHOTGUN, 5); + else if(GetModelIndex() == MI_ENFORCER) + driver->m_fArmour = Max(driver->m_fArmour, 100.0f); + else if(GetModelIndex() == MI_CABBIE || GetModelIndex() == MI_BORGNINE) + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 25; + bFreebies = false; + } + + ApplyTurnForce(0.0f, 0.0f, -0.2f*driver->m_fMass, + driver->GetPosition().x - GetPosition().x, + driver->GetPosition().y - GetPosition().y, + 0.0f); +} + +bool +CVehicle::AddPassenger(CPed *passenger) +{ + int i; + + ApplyTurnForce(0.0f, 0.0f, -0.2f*passenger->m_fMass, + passenger->GetPosition().x - GetPosition().x, + passenger->GetPosition().y - GetPosition().y, + 0.0f); + + for(i = 0; i < m_nNumMaxPassengers; i++) + if(pPassengers[i] == nil){ + pPassengers[i] = passenger; + m_nNumPassengers++; + return true; + } + return false; +} + +bool +CVehicle::AddPassenger(CPed *passenger, uint8 n) +{ + if(bIsBus) + return AddPassenger(passenger); + + ApplyTurnForce(0.0f, 0.0f, -0.2f*passenger->m_fMass, + passenger->GetPosition().x - GetPosition().x, + passenger->GetPosition().y - GetPosition().y, + 0.0f); + + if(n < m_nNumMaxPassengers && pPassengers[n] == nil){ + pPassengers[n] = passenger; + m_nNumPassengers++; + return true; + } + return false; +} + +void +CVehicle::RemoveDriver(void) +{ +#ifdef FIX_BUGS + if (GetStatus() != STATUS_WRECKED) +#endif + SetStatus(STATUS_ABANDONED); + pDriver = nil; +} + +void +CVehicle::RemovePassenger(CPed *p) +{ + if (IsTrain()){ + for (int i = 0; i < ARRAY_SIZE(pPassengers); i++){ + if (pPassengers[i] == p) { + pPassengers[i] = nil; + m_nNumPassengers--; + return; + } + } + return; + } + for (int i = 0; i < m_nNumMaxPassengers; i++){ + if (pPassengers[i] == p){ + pPassengers[i] = nil; + m_nNumPassengers--; + return; + } + } +} + +void +CVehicle::ProcessCarAlarm(void) +{ + uint32 step; + + if(m_nAlarmState == 0 || m_nAlarmState == -1) + return; + + step = CTimer::GetTimeStepInMilliseconds(); + if((uint16)m_nAlarmState < step) + m_nAlarmState = 0; + else + m_nAlarmState -= step; +} + +bool +CVehicle::IsSphereTouchingVehicle(float sx, float sy, float sz, float radius) +{ + float x, y, z; + // sphere relative to vehicle + CVector sph = CVector(sx, sy, sz) - GetPosition(); + CColModel *colmodel = GetColModel(); + + x = DotProduct(sph, GetRight()); + if(colmodel->boundingBox.min.x - radius > x || + colmodel->boundingBox.max.x + radius < x) + return false; + y = DotProduct(sph, GetForward()); + if(colmodel->boundingBox.min.y - radius > y || + colmodel->boundingBox.max.y + radius < y) + return false; + z = DotProduct(sph, GetUp()); + if(colmodel->boundingBox.min.z - radius > z || + colmodel->boundingBox.max.z + radius < z) + return false; + + return true; +} + +void +DestroyVehicleAndDriverAndPassengers(CVehicle* pVehicle) +{ + if (pVehicle->pDriver) { + CDarkel::RegisterKillByPlayer(pVehicle->pDriver, WEAPONTYPE_UNIDENTIFIED); + pVehicle->pDriver->FlagToDestroyWhenNextProcessed(); + } + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) { + if (pVehicle->pPassengers[i]) { + CDarkel::RegisterKillByPlayer(pVehicle->pPassengers[i], WEAPONTYPE_UNIDENTIFIED); + pVehicle->pPassengers[i]->FlagToDestroyWhenNextProcessed(); + } + } + CWorld::Remove(pVehicle); + delete pVehicle; +} + +#ifdef COMPATIBLE_SAVES +void +CVehicle::Save(uint8*& buf) +{ + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, GetRight().x); + WriteSaveBuf(buf, GetRight().y); + WriteSaveBuf(buf, GetRight().z); + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, GetForward().x); + WriteSaveBuf(buf, GetForward().y); + WriteSaveBuf(buf, GetForward().z); + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, GetUp().x); + WriteSaveBuf(buf, GetUp().y); + WriteSaveBuf(buf, GetUp().z); + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, GetPosition().x); + WriteSaveBuf(buf, GetPosition().y); + WriteSaveBuf(buf, GetPosition().z); + ZeroSaveBuf(buf, 16); + SaveEntityFlags(buf); + ZeroSaveBuf(buf, 212); + AutoPilot.Save(buf); + WriteSaveBuf(buf, m_currentColour1); + WriteSaveBuf(buf, m_currentColour2); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, m_nAlarmState); + ZeroSaveBuf(buf, 43); + WriteSaveBuf(buf, m_nNumMaxPassengers); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, field_1D0[0]); + WriteSaveBuf(buf, field_1D0[1]); + WriteSaveBuf(buf, field_1D0[2]); + WriteSaveBuf(buf, field_1D0[3]); + ZeroSaveBuf(buf, 8); + WriteSaveBuf(buf, m_fSteerAngle); + WriteSaveBuf(buf, m_fGasPedal); + WriteSaveBuf(buf, m_fBrakePedal); + WriteSaveBuf(buf, VehicleCreatedBy); + uint8 flags = 0; + if (bIsLawEnforcer) flags |= BIT(0); + if (bIsLocked) flags |= BIT(3); + if (bEngineOn) flags |= BIT(4); + if (bIsHandbrakeOn) flags |= BIT(5); + if (bLightsOn) flags |= BIT(6); + if (bFreebies) flags |= BIT(7); + WriteSaveBuf(buf, flags); + ZeroSaveBuf(buf, 10); + WriteSaveBuf(buf, m_fHealth); + WriteSaveBuf(buf, m_nCurrentGear); + ZeroSaveBuf(buf, 3); + WriteSaveBuf(buf, m_fChangeGearTime); + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, m_nTimeOfDeath); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, m_nBombTimer); + ZeroSaveBuf(buf, 12); + WriteSaveBuf(buf, m_nDoorLock); + ZeroSaveBuf(buf, 96); +} + +void +CVehicle::Load(uint8*& buf) +{ + CMatrix tmp; + SkipSaveBuf(buf, 4); + ReadSaveBuf(&tmp.GetRight().x, buf); + ReadSaveBuf(&tmp.GetRight().y, buf); + ReadSaveBuf(&tmp.GetRight().z, buf); + SkipSaveBuf(buf, 4); + ReadSaveBuf(&tmp.GetForward().x, buf); + ReadSaveBuf(&tmp.GetForward().y, buf); + ReadSaveBuf(&tmp.GetForward().z, buf); + SkipSaveBuf(buf, 4); + ReadSaveBuf(&tmp.GetUp().x, buf); + ReadSaveBuf(&tmp.GetUp().y, buf); + ReadSaveBuf(&tmp.GetUp().z, buf); + SkipSaveBuf(buf, 4); + ReadSaveBuf(&tmp.GetPosition().x, buf); + ReadSaveBuf(&tmp.GetPosition().y, buf); + ReadSaveBuf(&tmp.GetPosition().z, buf); + m_matrix = tmp; + SkipSaveBuf(buf, 16); + LoadEntityFlags(buf); + SkipSaveBuf(buf, 212); + AutoPilot.Load(buf); + ReadSaveBuf(&m_currentColour1, buf); + ReadSaveBuf(&m_currentColour2, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&m_nAlarmState, buf); + SkipSaveBuf(buf, 43); + ReadSaveBuf(&m_nNumMaxPassengers, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&field_1D0[0], buf); + ReadSaveBuf(&field_1D0[1], buf); + ReadSaveBuf(&field_1D0[2], buf); + ReadSaveBuf(&field_1D0[3], buf); + SkipSaveBuf(buf, 8); + ReadSaveBuf(&m_fSteerAngle, buf); + ReadSaveBuf(&m_fGasPedal, buf); + ReadSaveBuf(&m_fBrakePedal, buf); + ReadSaveBuf(&VehicleCreatedBy, buf); + uint8 flags; + ReadSaveBuf(&flags, buf); + bIsLawEnforcer = !!(flags & BIT(0)); + bIsLocked = !!(flags & BIT(3)); + bEngineOn = !!(flags & BIT(4)); + bIsHandbrakeOn = !!(flags & BIT(5)); + bLightsOn = !!(flags & BIT(6)); + bFreebies = !!(flags & BIT(7)); + SkipSaveBuf(buf, 10); + ReadSaveBuf(&m_fHealth, buf); + ReadSaveBuf(&m_nCurrentGear, buf); + SkipSaveBuf(buf, 3); + ReadSaveBuf(&m_fChangeGearTime, buf); + SkipSaveBuf(buf, 4); + ReadSaveBuf(&m_nTimeOfDeath, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&m_nBombTimer, buf); + SkipSaveBuf(buf, 12); + ReadSaveBuf(&m_nDoorLock, buf); + SkipSaveBuf(buf, 96); +} +#endif diff --git a/src/vehicles/Vehicle.h b/src/vehicles/Vehicle.h new file mode 100644 index 0000000..ab60ee2 --- /dev/null +++ b/src/vehicles/Vehicle.h @@ -0,0 +1,293 @@ +#pragma once + +#include "Physical.h" +#include "AutoPilot.h" +#include "ModelIndices.h" +#include "AnimationId.h" +#include "WeaponType.h" +#include "Collision.h" + +class CPed; +class CFire; +struct tHandlingData; + +enum { + RANDOM_VEHICLE = 1, + MISSION_VEHICLE = 2, + PARKED_VEHICLE = 3, + PERMANENT_VEHICLE = 4, +}; + +enum eCarLock { + CARLOCK_NOT_USED, + CARLOCK_UNLOCKED, + CARLOCK_LOCKED, + CARLOCK_LOCKOUT_PLAYER_ONLY, + CARLOCK_LOCKED_PLAYER_INSIDE, + CARLOCK_LOCKED_INITIALLY, + CARLOCK_FORCE_SHUT_DOORS +}; + +enum eDoors +{ + DOOR_BONNET = 0, + DOOR_BOOT, + DOOR_FRONT_LEFT, + DOOR_FRONT_RIGHT, + DOOR_REAR_LEFT, + DOOR_REAR_RIGHT +}; + +enum ePanels +{ + VEHPANEL_FRONT_LEFT, + VEHPANEL_FRONT_RIGHT, + VEHPANEL_REAR_LEFT, + VEHPANEL_REAR_RIGHT, + VEHPANEL_WINDSCREEN, + VEHBUMPER_FRONT, + VEHBUMPER_REAR, +}; + +enum eLights +{ + VEHLIGHT_FRONT_LEFT, + VEHLIGHT_FRONT_RIGHT, + VEHLIGHT_REAR_LEFT, + VEHLIGHT_REAR_RIGHT, +}; + +enum +{ + CAR_PIECE_BONNET = 1, + CAR_PIECE_BOOT, + CAR_PIECE_BUMP_FRONT, + CAR_PIECE_BUMP_REAR, + CAR_PIECE_DOOR_LF, + CAR_PIECE_DOOR_RF, + CAR_PIECE_DOOR_LR, + CAR_PIECE_DOOR_RR, + CAR_PIECE_WING_LF, + CAR_PIECE_WING_RF, + CAR_PIECE_WING_LR, + CAR_PIECE_WING_RR, + CAR_PIECE_WHEEL_LF, + CAR_PIECE_WHEEL_RF, + CAR_PIECE_WHEEL_LR, + CAR_PIECE_WHEEL_RR, + CAR_PIECE_WINDSCREEN, +}; + +enum tWheelState +{ + WHEEL_STATE_NORMAL, // standing still or rolling normally + WHEEL_STATE_SPINNING, // rotating but not moving + WHEEL_STATE_SKIDDING, + WHEEL_STATE_FIXED, // not rotating +}; + +enum eFlightModel +{ + FLIGHT_MODEL_DODO, + // not used in III + FLIGHT_MODEL_RCPLANE, + FLIGHT_MODEL_HELI, + FLIGHT_MODEL_SEAPLANE +}; + +// TODO: what is this even? +enum eBikeWheelSpecial { + BIKE_WHEELSPEC_0, // both wheels on ground + BIKE_WHEELSPEC_1, // rear wheel on ground + BIKE_WHEELSPEC_2, // only front wheel on ground + BIKE_WHEELSPEC_3, // can't happen +}; + + +class CVehicle : public CPhysical +{ +public: + // 0x128 + tHandlingData *pHandling; + CAutoPilot AutoPilot; + uint8 m_currentColour1; + uint8 m_currentColour2; + int8 m_aExtras[2]; + int16 m_nAlarmState; + int16 m_nMissionValue; + CPed *pDriver; + CPed *pPassengers[8]; + uint8 m_nNumPassengers; + int8 m_nNumGettingIn; + int8 m_nGettingInFlags; + int8 m_nGettingOutFlags; + uint8 m_nNumMaxPassengers; + float field_1D0[4]; + CEntity *m_pCurGroundEntity; + CFire *m_pCarFire; + float m_fSteerAngle; + float m_fGasPedal; + float m_fBrakePedal; + uint8 VehicleCreatedBy; + + // cf. https://github.com/DK22Pac/plugin-sdk/blob/master/plugin_sa/game_sa/CVehicle.h from R* + uint8 bIsLawEnforcer: 1; // Is this guy chasing the player at the moment + uint8 bIsAmbulanceOnDuty: 1; // Ambulance trying to get to an accident + uint8 bIsFireTruckOnDuty: 1; // Firetruck trying to get to a fire + uint8 bIsLocked: 1; // Is this guy locked by the script (cannot be removed) + uint8 bEngineOn: 1; // For sound purposes. Parked cars have their engines switched off (so do destroyed cars) + uint8 bIsHandbrakeOn: 1; // How's the handbrake doing ? + uint8 bLightsOn: 1; // Are the lights switched on ? + uint8 bFreebies: 1; // Any freebies left in this vehicle ? + + uint8 bIsVan: 1; // Is this vehicle a van (doors at back of vehicle) + uint8 bIsBus: 1; // Is this vehicle a bus + uint8 bIsBig: 1; // Is this vehicle a bus + uint8 bLowVehicle: 1; // Need this for sporty type cars to use low getting-in/out anims + uint8 bComedyControls : 1; // Will make the car hard to control (hopefully in a funny way) + uint8 bWarnedPeds : 1; // Has scan and warn peds of danger been processed? + uint8 bCraneMessageDone : 1; // A crane message has been printed for this car allready + uint8 bExtendedRange : 1; // This vehicle needs to be a bit further away to get deleted + + uint8 bTakeLessDamage : 1; // This vehicle is stronger (takes about 1/4 of damage) + uint8 bIsDamaged : 1; // This vehicle has been damaged and is displaying all its components + uint8 bHasBeenOwnedByPlayer : 1;// To work out whether stealing it is a crime + uint8 bFadeOut : 1; // Fade vehicle out + uint8 bIsBeingCarJacked : 1; // Fade vehicle out + uint8 bCreateRoadBlockPeds : 1; // If this vehicle gets close enough we will create peds (coppers or gang members) round it + uint8 bCanBeDamaged : 1; // Set to FALSE during cut scenes to avoid explosions + uint8 bUsingSpecialColModel : 1;// Is player vehicle using special collision model, stored in player strucure + + uint8 bOccupantsHaveBeenGenerated : 1; // Is true if the occupants have already been generated. (Shouldn't happen again) + uint8 bGunSwitchedOff : 1; // Level designers can use this to switch off guns on boats + uint8 bVehicleColProcessed : 1;// Has ProcessEntityCollision been processed for this car? + uint8 bIsCarParkVehicle : 1; // Car has been created using the special CAR_PARK script command + uint8 bHasAlreadyBeenRecorded : 1; // Used for replays + + int8 m_numPedsUseItAsCover; + uint8 m_nAmmoInClip; // Used to make the guns on boat do a reload (20 by default) + int8 m_nPacManPickupsCarried; + uint8 m_nRoadblockType; + int16 m_nRoadblockNode; + float m_fHealth; // 1000.0f = full health. 250.0f = fire. 0 -> explode + uint8 m_nCurrentGear; + float m_fChangeGearTime; + uint32 m_nGunFiringTime; // last time when gun on vehicle was fired (used on boats) + uint32 m_nTimeOfDeath; + uint16 m_nTimeBlocked; + int16 m_nBombTimer; // goes down with each frame + CEntity *m_pBlowUpEntity; + float m_fMapObjectHeightAhead; // front Z? + float m_fMapObjectHeightBehind; // rear Z? + eCarLock m_nDoorLock; + int8 m_nLastWeaponDamage; // see eWeaponType, -1 if no damage + uint8 m_nRadioStation; + uint8 m_bRainAudioCounter; + uint8 m_bRainSamplesCounter; + uint8 m_nCarHornTimer; + uint8 m_nCarHornPattern; // last horn? + bool m_bSirenOrAlarm; + int8 m_comedyControlState; + CStoredCollPoly m_aCollPolys[2]; // poly which is under front/rear part of car + float m_fSteerInput; + eVehicleType m_vehType; + + static void *operator new(size_t) throw(); + static void *operator new(size_t sz, int slot) throw(); + static void operator delete(void*, size_t) throw(); + static void operator delete(void*, int) throw(); + + CVehicle(void) {} // FAKE + CVehicle(uint8 CreatedBy); + ~CVehicle(void); + // from CEntity + void SetModelIndex(uint32 id); + bool SetupLighting(void); + void RemoveLighting(bool); + void FlagToDestroyWhenNextProcessed(void) {} + + virtual void ProcessControlInputs(uint8) {} + virtual void GetComponentWorldPosition(int32 component, CVector &pos) {} + virtual bool IsComponentPresent(int32 component) { return false; } + virtual void SetComponentRotation(int32 component, CVector rotation) {} + virtual void OpenDoor(int32, eDoors door, float) {} + virtual void ProcessOpenDoor(uint32, uint32, float) {} + virtual bool IsDoorReady(eDoors door) { return false; } + virtual bool IsDoorFullyOpen(eDoors door) { return false; } + virtual bool IsDoorClosed(eDoors door) { return false; } + virtual bool IsDoorMissing(eDoors door) { return false; } + virtual void RemoveRefsToVehicle(CEntity *ent) {} + virtual void BlowUpCar(CEntity *ent) {} + virtual bool SetUpWheelColModel(CColModel *colModel) { return false; } + virtual void BurstTyre(uint8 tyre) {} + virtual bool IsRoomForPedToLeaveCar(uint32 component, CVector *forcedDoorPos) { return false;} + virtual float GetHeightAboveRoad(void); + virtual void PlayCarHorn(void) {} +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + + bool IsCar(void) { return m_vehType == VEHICLE_TYPE_CAR; } + bool IsBoat(void) { return m_vehType == VEHICLE_TYPE_BOAT; } + bool IsTrain(void) { return m_vehType == VEHICLE_TYPE_TRAIN; } + bool IsHeli(void) { return m_vehType == VEHICLE_TYPE_HELI; } + bool IsPlane(void) { return m_vehType == VEHICLE_TYPE_PLANE; } + bool IsBike(void) { return m_vehType == VEHICLE_TYPE_BIKE; } + + void FlyingControl(eFlightModel flightModel); + void ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, + int32 wheelsOnGround, float thrust, float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, uint16 wheelStatus); + void ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, int32 wheelsOnGround, float thrust, + float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, eBikeWheelSpecial special, uint16 wheelStatus); + void ExtinguishCarFire(void); + void ProcessDelayedExplosion(void); + float ProcessWheelRotation(tWheelState state, const CVector &fwd, const CVector &speed, float radius); + bool IsLawEnforcementVehicle(void); + void ChangeLawEnforcerState(uint8 enable); + bool UsesSiren(uint32 id); + bool IsVehicleNormal(void); + bool CarHasRoof(void); + bool IsUpsideDown(void); + bool IsOnItsSide(void); + bool CanBeDeleted(void); + bool CanPedOpenLocks(CPed *ped); + bool CanPedEnterCar(void); + bool CanPedExitCar(void); + // do these two actually return something? + CPed *SetUpDriver(void); + CPed *SetupPassenger(int n); + void SetDriver(CPed *driver); + bool AddPassenger(CPed *passenger); + bool AddPassenger(CPed *passenger, uint8 n); + void RemovePassenger(CPed *passenger); + void RemoveDriver(void); + void ProcessCarAlarm(void); + bool IsSphereTouchingVehicle(float sx, float sy, float sz, float radius); + bool ShufflePassengersToMakeSpace(void); + void InflictDamage(CEntity *damagedBy, eWeaponType weaponType, float damage); + void DoFixedMachineGuns(void); + +#ifdef FIX_BUGS + bool IsAlarmOn(void) { return m_nAlarmState != 0 && m_nAlarmState != -1 && GetStatus() != STATUS_WRECKED; } +#else + bool IsAlarmOn(void) { return m_nAlarmState != 0 && m_nAlarmState != -1; } +#endif + CVehicleModelInfo* GetModelInfo() { return (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); } + bool IsTaxi(void) { return GetModelIndex() == MI_TAXI || GetModelIndex() == MI_CABBIE || GetModelIndex() == MI_BORGNINE; } + AnimationId GetDriverAnim(void) { return IsCar() && bLowVehicle ? ANIM_STD_CAR_SIT_LO : (IsBoat() && GetModelIndex() != MI_SPEEDER ? ANIM_STD_BOAT_DRIVE : ANIM_STD_CAR_SIT); } + + static bool bWheelsOnlyCheat; + static bool bAllDodosCheat; + static bool bCheat3; + static bool bCheat4; + static bool bCheat5; +#ifdef ALT_DODO_CHEAT + static bool bAltDodoCheat; +#endif + static bool m_bDisableMouseSteering; +}; + +VALIDATE_SIZE(CVehicle, 0x288); + +void DestroyVehicleAndDriverAndPassengers(CVehicle* pVehicle); diff --git a/src/weapons/BulletInfo.cpp b/src/weapons/BulletInfo.cpp new file mode 100644 index 0000000..bfe27e1 --- /dev/null +++ b/src/weapons/BulletInfo.cpp @@ -0,0 +1,306 @@ +#include "common.h" + +#include "BulletInfo.h" + +#include "AnimBlendAssociation.h" +#include "DMAudio.h" +#include "AudioScriptObject.h" +#ifdef FIX_BUGS +#include "Collision.h" +#endif +#include "RpAnimBlend.h" +#include "Entity.h" +#include "EventList.h" +#include "Fire.h" +#include "Glass.h" +#include "Particle.h" +#include "Ped.h" +#include "Object.h" +#include "Stats.h" +#include "Timer.h" +#include "Vehicle.h" +#include "Weapon.h" +#include "WeaponInfo.h" +#include "World.h" +#include "SurfaceTable.h" + +#ifdef SQUEEZE_PERFORMANCE +uint32 bulletInfoInUse; +#endif + +#define BULLET_LIFETIME (1000) +#define NUM_PED_BLOOD_PARTICLES (8) +#define BLOOD_PARTICLE_OFFSET (CVector(0.0f, 0.0f, 0.0f)) +#define NUM_VEHICLE_SPARKS (16) +#define NUM_OTHER_SPARKS (8) +#define BULLET_HIT_FORCE (7.5f) +#define MAP_BORDER (1960.0f) + +CBulletInfo gaBulletInfo[CBulletInfo::NUM_BULLETS]; +bool bPlayerSniperBullet; +CVector PlayerSniperBulletStart; +CVector PlayerSniperBulletEnd; + +void CBulletInfo::Initialise(void) +{ + debug("Initialising CBulletInfo...\n"); + for (int i = 0; i < NUM_BULLETS; i++) { + gaBulletInfo[i].m_bInUse = false; + gaBulletInfo[i].m_eWeaponType = WEAPONTYPE_COLT45; + gaBulletInfo[i].m_fTimer = 0.0f; + gaBulletInfo[i].m_pSource = nil; + } + debug("CBulletInfo ready\n"); +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse = 0; +#endif +} + +void CBulletInfo::Shutdown(void) +{ + debug("Shutting down CBulletInfo...\n"); + debug("CBulletInfo shut down\n"); +} + +bool CBulletInfo::AddBullet(CEntity* pSource, eWeaponType type, CVector vecPosition, CVector vecSpeed) +{ + int i; + for (i = 0; i < NUM_BULLETS; i++) { + if (!gaBulletInfo[i].m_bInUse) + break; + } + if (i == NUM_BULLETS) + return false; + gaBulletInfo[i].m_pSource = pSource; + gaBulletInfo[i].m_eWeaponType = type; + gaBulletInfo[i].m_nDamage = CWeaponInfo::GetWeaponInfo(type)->m_nDamage; + gaBulletInfo[i].m_vecPosition = vecPosition; + gaBulletInfo[i].m_vecSpeed = vecSpeed; + gaBulletInfo[i].m_fTimer = CTimer::GetTimeInMilliseconds() + BULLET_LIFETIME; + gaBulletInfo[i].m_bInUse = true; + +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse++; +#endif + return true; +} + +void CBulletInfo::Update(void) +{ +#ifdef SQUEEZE_PERFORMANCE + if (bulletInfoInUse == 0) + return; +#endif + bool bAddSound = true; + bPlayerSniperBullet = false; + for (int i = 0; i < NUM_BULLETS; i++) { + CBulletInfo* pBullet = &gaBulletInfo[i]; + if (pBullet->m_pSource && pBullet->m_pSource->IsPed() && !((CPed*)pBullet->m_pSource)->IsPointerValid()) + pBullet->m_pSource = nil; + if (!pBullet->m_bInUse) + continue; + if (CTimer::GetTimeInMilliseconds() > pBullet->m_fTimer) { + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + } + CVector vecOldPos = pBullet->m_vecPosition; + CVector vecNewPos = pBullet->m_vecPosition + pBullet->m_vecSpeed * CTimer::GetTimeStep() * 0.5f; + CWorld::bIncludeCarTyres = true; + CWorld::bIncludeDeadPeds = true; + CWorld::pIgnoreEntity = pBullet->m_pSource; + CColPoint point; + CEntity* pHitEntity; + if (CWorld::ProcessLineOfSight(vecOldPos, vecNewPos, point, pHitEntity, true, true, true, true, true, true)) { + if (pBullet->m_pSource && (pHitEntity->IsPed() || pHitEntity->IsVehicle())) + CStats::InstantHitsHitByPlayer++; + if (pHitEntity->IsPed()) { + CPed* pPed = (CPed*)pHitEntity; + if (!pPed->DyingOrDead() && pPed != pBullet->m_pSource) { + if (pPed->DoesLOSBulletHitPed(point)) { + if (pPed->IsPedInControl() && !pPed->bIsDucking) { + pPed->ClearAttackByRemovingAnim(); + CAnimBlendAssociation* pAnim = CAnimManager::AddAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HITBYGUN_FRONT); + pAnim->SetBlend(0.0f, 8.0f); + } + pPed->InflictDamage(pBullet->m_pSource, pBullet->m_eWeaponType, pBullet->m_nDamage, (ePedPieceTypes)point.pieceB, pPed->GetLocalDirection(pPed->GetPosition() - point.point)); + CEventList::RegisterEvent(pPed->m_nPedType == PEDTYPE_COP ? EVENT_SHOOT_COP : EVENT_SHOOT_PED, EVENT_ENTITY_PED, pPed, (CPed*)pBullet->m_pSource, 1000); + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + vecNewPos = point.point; + } + else { + bAddSound = false; + } + } + if (CGame::nastyGame) { + CVector vecParticleDirection = (point.point - pPed->GetPosition()) * 0.01f; + vecParticleDirection.z = 0.01f; + if (pPed->GetIsOnScreen()) { + for (int j = 0; j < NUM_PED_BLOOD_PARTICLES; j++) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point.point + BLOOD_PARTICLE_OFFSET, vecParticleDirection); + } + if (pPed->GetPedState() == PED_DEAD) { + CAnimBlendAssociation* pAnim; + if (RpAnimBlendClumpGetFirstAssociation(pPed->GetClump(), ASSOC_FRONTAL)) + pAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f); + else + pAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR, 8.0f); + if (pAnim) { + pAnim->SetCurrentTime(0.0f); + pAnim->flags |= ASSOC_RUNNING; + pAnim->flags &= ~ASSOC_FADEOUTWHENDONE; + } + } + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + vecNewPos = point.point; + } + } + else if (pHitEntity->IsVehicle()) { + CVehicle* pVehicle = (CVehicle*)pHitEntity; + pVehicle->InflictDamage(pBullet->m_pSource, pBullet->m_eWeaponType, pBullet->m_nDamage); + if (pBullet->m_eWeaponType == WEAPONTYPE_FLAMETHROWER) // huh? + gFireManager.StartFire(pVehicle, pBullet->m_pSource, 0.8f, true); + else { + for (int j = 0; j < NUM_VEHICLE_SPARKS; j++) + CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal / 20); + } +#ifdef FIX_BUGS + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + vecNewPos = point.point; +#endif + } + else { + for (int j = 0; j < NUM_OTHER_SPARKS; j++) + CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal / 20); + if (pHitEntity->IsObject()) { + CObject* pObject = (CObject*)pHitEntity; + if (!pObject->bInfiniteMass) { + if (pObject->GetIsStatic() && pObject->m_fUprootLimit <= 0.0f) { + pObject->SetIsStatic(false); + pObject->AddToMovingList(); + } + if (!pObject->GetIsStatic()) + pObject->ApplyMoveForce(-BULLET_HIT_FORCE * point.normal); + } + } +#ifdef FIX_BUGS + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + vecNewPos = point.point; +#endif + } + if (pBullet->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE && bAddSound) { + cAudioScriptObject* pAudio; + switch (pHitEntity->GetType()) { + case ENTITY_TYPE_BUILDING: + pAudio = new cAudioScriptObject(); + pAudio->Posn = pHitEntity->GetPosition(); + pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_1; + pAudio->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(pAudio); + break; + case ENTITY_TYPE_OBJECT: + pAudio = new cAudioScriptObject(); + pAudio->Posn = pHitEntity->GetPosition(); + pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_2; + pAudio->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(pAudio); + break; + case ENTITY_TYPE_DUMMY: + pAudio = new cAudioScriptObject(); + pAudio->Posn = pHitEntity->GetPosition(); + pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_3; + pAudio->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(pAudio); + break; + case ENTITY_TYPE_PED: + DMAudio.PlayOneShot(((CPed*)pHitEntity)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)pHitEntity)->Say(SOUND_PED_BULLET_HIT); + break; + case ENTITY_TYPE_VEHICLE: + DMAudio.PlayOneShot(((CVehicle*)pHitEntity)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + default: break; + } + } + CGlass::WasGlassHitByBullet(pHitEntity, point.point); + CWeapon::BlowUpExplosiveThings(pHitEntity); + } + CWorld::pIgnoreEntity = nil; + CWorld::bIncludeDeadPeds = false; + CWorld::bIncludeCarTyres = false; + if (pBullet->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE) { + bPlayerSniperBullet = true; + PlayerSniperBulletStart = pBullet->m_vecPosition; + PlayerSniperBulletEnd = vecNewPos; + } + pBullet->m_vecPosition = vecNewPos; + if (pBullet->m_vecPosition.x < -MAP_BORDER || pBullet->m_vecPosition.x > MAP_BORDER || + pBullet->m_vecPosition.y < -MAP_BORDER || pBullet->m_vecPosition.y > MAP_BORDER) { + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + } + } +} + +bool CBulletInfo::TestForSniperBullet(float x1, float x2, float y1, float y2, float z1, float z2) +{ + if (!bPlayerSniperBullet) + return false; +#ifdef FIX_BUGS // original code is not going work anyway... + CColLine line(PlayerSniperBulletStart, PlayerSniperBulletEnd); + CColBox box; + box.Set(CVector(x1, y1, z1), CVector(x2, y2, z2), SURFACE_DEFAULT, 0); + return CCollision::TestLineBox(line, box); +#else + float minP = 0.0f; + float maxP = 1.0f; + float minX = Min(PlayerSniperBulletStart.x, PlayerSniperBulletEnd.x); + float maxX = Max(PlayerSniperBulletStart.x, PlayerSniperBulletEnd.x); + if (minX < x2 || maxX > x1) { + if (minX < x1) + minP = Min(minP, (x1 - minX) / (maxX - minX)); + if (maxX > x2) + maxP = Max(maxP, (maxX - x2) / (maxX - minX)); + } + else + return false; + float minY = Min(PlayerSniperBulletStart.y, PlayerSniperBulletEnd.y); + float maxY = Max(PlayerSniperBulletStart.y, PlayerSniperBulletEnd.y); + if (minY < y2 || maxY > y1) { + if (minY < y1) + minP = Min(minP, (y1 - minY) / (maxY - minY)); + if (maxY > y2) + maxP = Max(maxP, (maxY - y2) / (maxY - minY)); + } +#ifdef FIX_BUGS + else + return false; +#endif + float minZ = Min(PlayerSniperBulletStart.z, PlayerSniperBulletEnd.z); + float maxZ = Max(PlayerSniperBulletStart.z, PlayerSniperBulletEnd.z); + if (minZ < z2 || maxZ > z1) { + if (minZ < z1) + minP = Min(minP, (z1 - minZ) / (maxZ - minZ)); + if (maxZ > z2) + maxP = Max(maxP, (maxZ - z2) / (maxZ - minZ)); + } + else + return false; + return minP <= maxP; +#endif +} diff --git a/src/weapons/BulletInfo.h b/src/weapons/BulletInfo.h new file mode 100644 index 0000000..cf1dd27 --- /dev/null +++ b/src/weapons/BulletInfo.h @@ -0,0 +1,25 @@ +#pragma once + +#include "WeaponType.h" + +class CEntity; + +class CBulletInfo +{ + eWeaponType m_eWeaponType; + CEntity* m_pSource; + float m_fTimer; // big mistake + bool m_bInUse; + CVector m_vecPosition; + CVector m_vecSpeed; + int16 m_nDamage; +public: + enum { + NUM_BULLETS = 100 + }; + static void Initialise(void); + static void Shutdown(void); + static bool AddBullet(CEntity* pSource, eWeaponType type, CVector vecPosition, CVector vecSpeed); + static void Update(void); + static bool TestForSniperBullet(float x1, float x2, float y1, float y2, float z1, float z2); +}; \ No newline at end of file diff --git a/src/weapons/Explosion.cpp b/src/weapons/Explosion.cpp new file mode 100644 index 0000000..f79c027 --- /dev/null +++ b/src/weapons/Explosion.cpp @@ -0,0 +1,465 @@ +#include "common.h" + +#include "Automobile.h" +#include "Bike.h" +#include "Camera.h" +#include "Coronas.h" +#include "DMAudio.h" +#include "Entity.h" +#include "EventList.h" +#include "Explosion.h" +#include "General.h" +#include "Fire.h" +#include "Pad.h" +#include "Particle.h" +#include "PointLights.h" +#include "Shadows.h" +#include "Timer.h" +#include "Vehicle.h" +#include "WaterLevel.h" +#include "World.h" + +CExplosion gaExplosion[NUM_EXPLOSIONS]; + +// these two were not initialised in original code, I'm really not sure what were they meant to be +RwRGBA colMedExpl = { 0, 0, 0, 0 }; +RwRGBA colUpdate = { 0, 0, 0, 0 }; + +int AudioHandle = AEHANDLE_NONE; + +void +CExplosion::Initialise() +{ + debug("Initialising CExplosion...\n"); + for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + gaExplosion[i].m_ExplosionType = EXPLOSION_GRENADE; + gaExplosion[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f); + gaExplosion[i].m_fRadius = 1.0f; + gaExplosion[i].m_fPropagationRate = 0.0f; + gaExplosion[i].m_fZshift = 0.0f; + gaExplosion[i].m_pCreatorEntity = nil; + gaExplosion[i].m_pVictimEntity = nil; + gaExplosion[i].m_fStopTime = 0.0f; + gaExplosion[i].m_nIteration = 0; + gaExplosion[i].m_fStartTime = 0.0f; + gaExplosion[i].m_bIsBoat = false; + } + AudioHandle = DMAudio.CreateEntity(AUDIOTYPE_EXPLOSION, (void*)1); + if (AudioHandle >= 0) + DMAudio.SetEntityStatus(AudioHandle, TRUE); + debug("CExplosion ready\n"); +} + +void +CExplosion::Shutdown() +{ + debug("Shutting down CExplosion...\n"); + if (AudioHandle >= 0) { + DMAudio.DestroyEntity(AudioHandle); + AudioHandle = AEHANDLE_NONE; + } + debug("CExplosion shut down\n"); +} + +int8 +CExplosion::GetExplosionActiveCounter(uint8 id) +{ + return gaExplosion[id].m_nActiveCounter; +} + +void +CExplosion::ResetExplosionActiveCounter(uint8 id) +{ + gaExplosion[id].m_nActiveCounter = 0; +} + +uint8 +CExplosion::GetExplosionType(uint8 id) +{ + return gaExplosion[id].m_ExplosionType; +} + +CVector * +CExplosion::GetExplosionPosition(uint8 id) +{ + return &gaExplosion[id].m_vecPosition; +} + +bool +CExplosion::AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32 lifetime) +{ + CVector pPosn; + CVector posGround; + + RwRGBA colorMedium = colMedExpl; + bool bDontExplode = false; + const RwRGBA color = { 160, 160, 160, 255 }; + pPosn = pos; + pPosn.z += 5.0f; +#ifdef FIX_BUGS + CShadows::AddPermanentShadow(SHADOWTEX_CAR, gpShadowHeliTex, &pPosn, 8.0f, 0.0f, 0.0f, -8.0f, 200, 0, 0, 0, 10.0f, 30000, 1.0f); +#else + // last two arguments are swapped resulting in no shadow + CShadows::AddPermanentShadow(SHADOWTEX_CAR, gpShadowHeliTex, &pPosn, 8.0f, 0.0f, 0.0f, -8.0f, 200, 0, 0, 0, 10.0f, 1, 30000.0f); +#endif + + int n = 0; +#ifdef FIX_BUGS + while (n < ARRAY_SIZE(gaExplosion) && gaExplosion[n].m_nIteration != 0) +#else + // array overrun is UB + while (gaExplosion[n].m_nIteration != 0 && n < ARRAY_SIZE(gaExplosion)) +#endif + n++; + if (n == ARRAY_SIZE(gaExplosion)) + return false; + + CExplosion &explosion = gaExplosion[n]; + explosion.m_ExplosionType = type; + explosion.m_vecPosition = pos; + explosion.m_fRadius = 1.0f; + explosion.m_fZshift = 0.0f; + explosion.m_pCreatorEntity = culprit; + if (culprit != nil) + culprit->RegisterReference(&explosion.m_pCreatorEntity); + explosion.m_pVictimEntity = explodingEntity; + if (explodingEntity != nil) + explodingEntity->RegisterReference(&explosion.m_pVictimEntity); + explosion.m_nIteration = 1; + explosion.m_nActiveCounter = 1; + explosion.m_bIsBoat = false; + explosion.m_nParticlesExpireTime = lifetime != 0 ? CTimer::GetTimeInMilliseconds() + lifetime : 0; + switch (type) + { + case EXPLOSION_GRENADE: + explosion.m_fRadius = 9.0f; + explosion.m_fPower = 300.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + posGround.z = CWorld::FindGroundZFor3DCoord(posGround.x, posGround.y, posGround.z + 3.0f, nil); + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + if (Distance(explosion.m_vecPosition, TheCamera.GetPosition()) < 40.0f) + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color); + break; + case EXPLOSION_MOLOTOV: + { + explosion.m_fRadius = 6.0f; + explosion.m_fPower = 0.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 3000; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + bool found; + posGround.z = CWorld::FindGroundZFor3DCoord(posGround.x, posGround.y, posGround.z + 3.0f, &found); + if (found) { + float waterLevel; + if (CWaterLevel::GetWaterLevelNoWaves(posGround.x, posGround.y, posGround.z, &waterLevel) + && posGround.z < waterLevel + && waterLevel - 6.0f < posGround.z) // some subway/tunnels check? + bDontExplode = true; + else + gFireManager.StartFire(posGround, 1.8f, false); + } + else + bDontExplode = true; + break; + } + case EXPLOSION_ROCKET: + explosion.m_fRadius = 10.0f; + explosion.m_fPower = 300.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + CEventList::RegisterEvent(EVENT_EXPLOSION, pos, 250); + if (Distance(explosion.m_vecPosition, TheCamera.GetPosition()) < 40.0f) + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color); + break; + case EXPLOSION_CAR: + case EXPLOSION_CAR_QUICK: + explosion.m_fRadius = 9.0f; + explosion.m_fPower = 300.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 4250; + explosion.m_fPropagationRate = 0.5f; + explosion.m_fStartTime = CTimer::GetTimeInMilliseconds(); + if (explosion.m_pVictimEntity != nil) { + if (explosion.m_pVictimEntity->IsVehicle() && ((CVehicle*)explosion.m_pVictimEntity)->IsBoat()) + explosion.m_bIsBoat = true; + CEventList::RegisterEvent(EVENT_EXPLOSION, EVENT_ENTITY_VEHICLE, explosion.m_pVictimEntity, nil, 1000); + } else + CEventList::RegisterEvent(EVENT_EXPLOSION, pos, 1000); + + if (explosion.m_pVictimEntity != nil && !explosion.m_bIsBoat) { + int rn = (CGeneral::GetRandomNumber() & 1) + 2; + for (int i = 0; i < rn; i++) { + CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, explosion.m_pVictimEntity->GetPosition(), CVector(0.0f, 0.0f, 0.0f), nil, 3.5f, colMedExpl); + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_pVictimEntity->GetPosition(), CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color); + } + CVehicle *veh = (CVehicle*)explosion.m_pVictimEntity; + int32 component = CAR_WING_LR; + + // miami leftover + if (veh->IsBike()) + component = BIKE_FORKS_REAR; + + if (veh->IsComponentPresent(component)) { + CVector componentPos; + veh->GetComponentWorldPosition(component, componentPos); + rn = (CGeneral::GetRandomNumber() & 1) + 1; + for (int i = 0; i < rn; i++) + CParticle::AddJetExplosion(componentPos, 1.4f, 0.0f); + } + } + break; + case EXPLOSION_HELI: + explosion.m_fRadius = 6.0f; + explosion.m_fPower = 300.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + explosion.m_fStartTime = CTimer::GetTimeInMilliseconds(); + for (int i = 0; i < 10; i++) { + CVector randpos; + uint8 x, y, z; + + x = CGeneral::GetRandomNumber(); + y = CGeneral::GetRandomNumber(); + z = CGeneral::GetRandomNumber(); + randpos = pos + CVector(x - 128, y - 128, z - 128) / 20.0f; + + CParticle::AddParticle(PARTICLE_EXPLOSION_MFAST, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 2.5f, color); + + x = CGeneral::GetRandomNumber(); + y = CGeneral::GetRandomNumber(); + z = CGeneral::GetRandomNumber(); + randpos = pos + CVector(x - 128, y - 128, z - 128) / 20.0f; + + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 5.0f, color); + + x = CGeneral::GetRandomNumber(); + y = CGeneral::GetRandomNumber(); + z = CGeneral::GetRandomNumber(); + randpos = pos + CVector(x - 128, y - 128, z - 128) / 20.0f; + + CParticle::AddJetExplosion(randpos, 1.4f, 3.0f); + } + CEventList::RegisterEvent(EVENT_EXPLOSION, pos, 1000); + break; + case EXPLOSION_MINE: + explosion.m_fRadius = 10.0f; + explosion.m_fPower = 150.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + //posGround.z = + CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + break; + case EXPLOSION_BARREL: + explosion.m_fRadius = 7.0f; + explosion.m_fPower = 150.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + for (int i = 0; i < 6; i++) { + CVector randpos; + uint8 x, y, z; + + x = CGeneral::GetRandomNumber(); + y = CGeneral::GetRandomNumber(); + z = CGeneral::GetRandomNumber(); + randpos = CVector(x - 128, y - 128, z - 128); + + randpos.x /= 50.0f; + randpos.y /= 50.0f; + randpos.z /= 25.0f; + randpos += pos; + CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, colorMedium); + } + posGround = pos; + //posGround.z = + CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + break; + case EXPLOSION_TANK_GRENADE: + explosion.m_fRadius = 10.0f; + explosion.m_fPower = 150.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + //posGround.z = + CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + break; + case EXPLOSION_HELI_BOMB: + explosion.m_fRadius = 8.0f; + explosion.m_fPower = 50.0f; + explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750; + explosion.m_fPropagationRate = 0.5f; + posGround = pos; + //posGround.z = + CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused + CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250); + break; + } + if (bDontExplode) { + explosion.m_nIteration = 0; + return false; + } + + if (explosion.m_fPower != 0.0f && explosion.m_nParticlesExpireTime == 0) + CWorld::TriggerExplosion(pos, explosion.m_fRadius, explosion.m_fPower, culprit, (type == EXPLOSION_ROCKET || type == EXPLOSION_CAR_QUICK || type == EXPLOSION_MINE || type == EXPLOSION_BARREL || type == EXPLOSION_TANK_GRENADE || type == EXPLOSION_HELI_BOMB)); + + TheCamera.CamShake(0.6f, pos.x, pos.y, pos.z); + CPad::GetPad(0)->StartShake_Distance(300, 128, pos.x, pos.y, pos.z); + return true; +} + +void +CExplosion::Update() +{ + RwRGBA color = colUpdate; + for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + CExplosion &explosion = gaExplosion[i]; + if (explosion.m_nIteration == 0) continue; + + if (explosion.m_nParticlesExpireTime != 0) { + if (CTimer::GetTimeInMilliseconds() > explosion.m_nParticlesExpireTime) { + explosion.m_nParticlesExpireTime = 0; + if (explosion.m_fPower != 0.0f) + CWorld::TriggerExplosion(explosion.m_vecPosition, explosion.m_fRadius, explosion.m_fPower, explosion.m_pCreatorEntity, (explosion.m_ExplosionType == EXPLOSION_ROCKET || explosion.m_ExplosionType == EXPLOSION_CAR_QUICK || explosion.m_ExplosionType == EXPLOSION_MINE || explosion.m_ExplosionType == EXPLOSION_BARREL || explosion.m_ExplosionType == EXPLOSION_TANK_GRENADE || explosion.m_ExplosionType == EXPLOSION_HELI_BOMB)); + } + } else { + explosion.m_fRadius += explosion.m_fPropagationRate * CTimer::GetTimeStep(); + int32 someTime = explosion.m_fStopTime - CTimer::GetTimeInMilliseconds(); + switch (explosion.m_ExplosionType) + { + case EXPLOSION_GRENADE: + case EXPLOSION_ROCKET: + case EXPLOSION_HELI: + case EXPLOSION_MINE: + case EXPLOSION_BARREL: + if (CTimer::GetFrameCounter() & 1) { + CPointLights::AddLight(CPointLights::LIGHT_POINT, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), 20.0f, 1.0f, 1.0f, 0.5f, CPointLights::FOG_NONE, true); + CCoronas::RegisterCorona((uintptr)&explosion, 255, 255, 200, 255, explosion.m_vecPosition, 8.0f, 120.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } else + CCoronas::RegisterCorona((uintptr)&explosion, 128, 128, 100, 255, explosion.m_vecPosition, 8.0f, 120.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + CCoronas::RegisterCorona((uintptr)&explosion + 1, 30, 30, 25, 255, explosion.m_vecPosition, explosion.m_fRadius, 120.0f, gpCoronaTexture[7], CCoronas::TYPE_STAR, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + break; + case EXPLOSION_MOLOTOV: + CWorld::SetPedsOnFire(explosion.m_vecPosition.x, explosion.m_vecPosition.y, explosion.m_vecPosition.z, 6.0f, explosion.m_pCreatorEntity); + CWorld::SetCarsOnFire(explosion.m_vecPosition.x, explosion.m_vecPosition.y, explosion.m_vecPosition.z, 6.0f, explosion.m_pCreatorEntity); + if (explosion.m_nIteration < 10) { + if (explosion.m_nIteration == 1) { + CVector point1 = explosion.m_vecPosition; + point1.z += 5.0f; + CColPoint colPoint; + CEntity *pEntity; + CWorld::ProcessVerticalLine(point1, -1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil); + explosion.m_fZshift = colPoint.point.z; + } + float ff = ((float)explosion.m_nIteration * 0.55f); + for (int i = 0; i < 5 * ff; i++) { + float angle = CGeneral::GetRandomNumber() / 256.0f * 6.28f; + + CVector pos = explosion.m_vecPosition; + pos.x += ff * Sin(angle); + pos.y += ff * Cos(angle); + pos.z += 5.0f; // what is the point of this? + + pos.z = explosion.m_fZshift + 0.5f; + CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, color, CGeneral::GetRandomNumberInRange(-3.0f, 3.0f), CGeneral::GetRandomNumberInRange(-180.0f, 180.0f)); + } + } + break; + case EXPLOSION_CAR: + case EXPLOSION_CAR_QUICK: + if (someTime >= 3500) { + if (explosion.m_pVictimEntity != nil && !explosion.m_bIsBoat) { + if ((CGeneral::GetRandomNumber() & 0xF) == 0) { + CVehicle *veh = (CVehicle*)explosion.m_pVictimEntity; + uint8 component = CAR_WING_LR; + + // miami leftover + if (veh->IsBike()) + component = BIKE_FORKS_REAR; + + if (veh->IsComponentPresent(component)) { + CVector componentPos; + veh->GetComponentWorldPosition(component, componentPos); + CParticle::AddJetExplosion(componentPos, 1.5f, 0.0f); + } + } + if (CTimer::GetTimeInMilliseconds() > explosion.m_fStartTime) { + explosion.m_fStartTime = CTimer::GetTimeInMilliseconds() + 125 + (CGeneral::GetRandomNumber() & 0x7F); + CVector pos = explosion.m_pVictimEntity->GetPosition(); + for (int i = 0; i < (CGeneral::GetRandomNumber() & 1) + 1; i++) { + CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, CVector(0.0f, 0.0f, 0.0f), nil, 3.5f, color); + CParticle::AddParticle(PARTICLE_EXPLOSION_LARGE, pos, CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color); + } + } + } + if (CTimer::GetFrameCounter() & 1) { + CPointLights::AddLight(CPointLights::LIGHT_POINT, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), 15.0f, 1.0f, 0.0f, 0.0f, CPointLights::FOG_NONE, true); + CCoronas::RegisterCorona((uintptr)&explosion, 200, 100, 0, 255, explosion.m_vecPosition, 6.0f, 80.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } else + CCoronas::RegisterCorona((uintptr)&explosion, 128, 0, 0, 255, explosion.m_vecPosition, 8.0f, 80.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + + CCoronas::RegisterCorona((uintptr)&explosion + 1, 30, 15, 0, 255, explosion.m_vecPosition, explosion.m_fRadius, 80.0f, gpCoronaTexture[7], CCoronas::TYPE_STAR, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } else if (explosion.m_nIteration & 1) { + if (explosion.m_pVictimEntity != nil) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, explosion.m_pVictimEntity->GetPosition(), CVector(0.0f, 0.0f, 0.0f), nil, CGeneral::GetRandomNumberInRange(0.5f, 0.8f), color); + CVector pos = explosion.m_vecPosition; + pos.z += 1.0f; + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, pos, CVector(0.0f, 0.0f, 0.11f), nil, CGeneral::GetRandomNumberInRange(0.5f, 2.0f), color); + } + break; + case EXPLOSION_TANK_GRENADE: + case EXPLOSION_HELI_BOMB: + if (explosion.m_nIteration < 5) { + float ff = ((float)explosion.m_nIteration * 0.65f); + for (int i = 0; i < 10 * ff; i++) { + uint8 x = CGeneral::GetRandomNumber(), y = CGeneral::GetRandomNumber(), z = CGeneral::GetRandomNumber(); + CVector pos(x - 128, y - 128, (z % 128) + 1); + + pos.Normalise(); + pos *= ff / 5.0f; + pos += explosion.m_vecPosition; + pos.z += 0.5f; + CParticle::AddParticle(PARTICLE_EXPLOSION_LARGE, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, color, CGeneral::GetRandomNumberInRange(-3.0f, 3.0f), CGeneral::GetRandomNumberInRange(-180.0f, 180.0f)); + } + } + break; + } + if (someTime > 0) + explosion.m_nIteration++; + else + explosion.m_nIteration = 0; + } + } +} + +bool +CExplosion::TestForExplosionInArea(eExplosionType type, float x1, float x2, float y1, float y2, float z1, float z2) +{ + for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + if (gaExplosion[i].m_nIteration != 0) { + if (type == gaExplosion[i].m_ExplosionType) { + if (gaExplosion[i].m_vecPosition.x >= x1 && gaExplosion[i].m_vecPosition.x <= x2) { + if (gaExplosion[i].m_vecPosition.y >= y1 && gaExplosion[i].m_vecPosition.y <= y2) { + if (gaExplosion[i].m_vecPosition.z >= z1 && gaExplosion[i].m_vecPosition.z <= z2) + return true; + } + } + } + } + } + return false; +} + +void +CExplosion::RemoveAllExplosionsInArea(CVector pos, float radius) +{ + for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) { + if (gaExplosion[i].m_nIteration != 0) { + if ((pos - gaExplosion[i].m_vecPosition).MagnitudeSqr() < SQR(radius)) + gaExplosion[i].m_nIteration = 0; + } + } +} \ No newline at end of file diff --git a/src/weapons/Explosion.h b/src/weapons/Explosion.h new file mode 100644 index 0000000..bf54328 --- /dev/null +++ b/src/weapons/Explosion.h @@ -0,0 +1,49 @@ +#pragma once + +class CEntity; +class CVector; + +enum eExplosionType +{ + EXPLOSION_GRENADE, + EXPLOSION_MOLOTOV, + EXPLOSION_ROCKET, + EXPLOSION_CAR, + EXPLOSION_CAR_QUICK, + EXPLOSION_HELI, + EXPLOSION_MINE, + EXPLOSION_BARREL, + EXPLOSION_TANK_GRENADE, + EXPLOSION_HELI_BOMB +}; + +class CExplosion +{ + eExplosionType m_ExplosionType; + CVector m_vecPosition; + float m_fRadius; + float m_fPropagationRate; + CEntity *m_pCreatorEntity; + CEntity *m_pVictimEntity; + float m_fStopTime; + uint8 m_nIteration; + uint8 m_nActiveCounter; + float m_fStartTime; + uint32 m_nParticlesExpireTime; + float m_fPower; + bool m_bIsBoat; + float m_fZshift; +public: + static void Initialise(); + static void Shutdown(); + static int8 GetExplosionActiveCounter(uint8 id); + static void ResetExplosionActiveCounter(uint8 id); + static uint8 GetExplosionType(uint8 id); + static CVector *GetExplosionPosition(uint8 id); + static bool AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32 lifetime); + static void Update(); + static bool TestForExplosionInArea(eExplosionType type, float x1, float x2, float y1, float y2, float z1, float z2); + static void RemoveAllExplosionsInArea(CVector pos, float radius); +}; + +extern CExplosion gaExplosion[NUM_EXPLOSIONS]; \ No newline at end of file diff --git a/src/weapons/ProjectileInfo.cpp b/src/weapons/ProjectileInfo.cpp new file mode 100644 index 0000000..da00b87 --- /dev/null +++ b/src/weapons/ProjectileInfo.cpp @@ -0,0 +1,342 @@ +#include "common.h" + +#include "Camera.h" +#include "General.h" +#include "Heli.h" +#include "ModelIndices.h" +#include "Particle.h" +#include "Ped.h" +#include "Plane.h" +#include "ProjectileInfo.h" +#include "Projectile.h" +#include "Explosion.h" +#include "Weapon.h" +#include "World.h" + +#ifdef SQUEEZE_PERFORMANCE +uint32 projectileInUse; +#endif + +CProjectileInfo gaProjectileInfo[NUM_PROJECTILES]; +CProjectile *CProjectileInfo::ms_apProjectile[NUM_PROJECTILES]; + +void +CProjectileInfo::Initialise() +{ + debug("Initialising CProjectileInfo...\n"); + + for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) { + ms_apProjectile[i] = nil; + gaProjectileInfo[i].m_eWeaponType = WEAPONTYPE_GRENADE; + gaProjectileInfo[i].m_pSource = nil; + gaProjectileInfo[i].m_nExplosionTime = 0; + gaProjectileInfo[i].m_bInUse = false; + } + + debug("CProjectileInfo ready\n"); + +#ifdef SQUEEZE_PERFORMANCE + projectileInUse = 0; +#endif +} + +void +CProjectileInfo::Shutdown() +{ + debug("Shutting down CProjectileInfo...\n"); + debug("CProjectileInfo shut down\n"); +} + +CProjectileInfo* +CProjectileInfo::GetProjectileInfo(int32 id) +{ + return &gaProjectileInfo[id]; +} + +bool +CProjectileInfo::AddProjectile(CEntity *entity, eWeaponType weapon, CVector pos, float speed) +{ + int8 SpecialCollisionResponseCase = COLLRESPONSE_NONE; + bool gravity = true; + CMatrix matrix; + float elasticity = 0.75f; + CPed* ped = (CPed*)entity; + int time; + CVector velocity; + + switch (weapon) + { + case WEAPONTYPE_ROCKETLAUNCHER: + { + float vy = 1.25f; + time = CTimer::GetTimeInMilliseconds() + 1400; + if (ped->IsPlayer()) { + matrix.GetForward() = TheCamera.Cams[TheCamera.ActiveCam].Front; + matrix.GetUp() = TheCamera.Cams[TheCamera.ActiveCam].Up; + matrix.GetRight() = CrossProduct(TheCamera.Cams[TheCamera.ActiveCam].Up, TheCamera.Cams[TheCamera.ActiveCam].Front); + matrix.GetPosition() = pos; + } else if (ped->m_pSeekTarget != nil) { + float ry = CGeneral::GetRadianAngleBetweenPoints(1.0f, ped->m_pSeekTarget->GetPosition().z, 1.0f, pos.z); + float rz = Atan2(-ped->GetForward().x, ped->GetForward().y); + vy = 0.35f * speed + 0.15f; + matrix.SetTranslate(0.0f, 1.0f, 1.0f); + matrix.Rotate(0.0f, ry, rz); + matrix.GetPosition() += pos; + } else { + matrix = ped->GetMatrix(); + } + velocity = Multiply3x3(matrix, CVector(0.0f, vy, 0.0f)); + gravity = false; + break; + } + case WEAPONTYPE_FLAMETHROWER: + Error("Undefined projectile type, AddProjectile, ProjectileInfo.cpp"); + break; + case WEAPONTYPE_MOLOTOV: + { + time = CTimer::GetTimeInMilliseconds() + 2000; + float scale = 0.22f * speed + 0.15f; + if (scale < 0.2f) + scale = 0.2f; + float angle = Atan2(-ped->GetForward().x, ped->GetForward().y); + matrix.SetTranslate(0.0f, 0.0f, 0.0f); + matrix.RotateZ(angle); + matrix.GetPosition() += pos; + velocity.x = -1.0f * scale * Sin(angle); + velocity.y = scale * Cos(angle); + velocity.z = (0.2f * speed + 0.4f) * scale; + break; + } + case WEAPONTYPE_GRENADE: + { + time = CTimer::GetTimeInMilliseconds() + 2000; + float scale = 0.0f; + if (speed != 0.0f) + scale = 0.22f * speed + 0.15f; + float angle = Atan2(-ped->GetForward().x, ped->GetForward().y); + matrix.SetTranslate(0.0f, 0.0f, 0.0f); + matrix.RotateZ(angle); + matrix.GetPosition() += pos; + SpecialCollisionResponseCase = COLLRESPONSE_UNKNOWN5; + velocity.x = -1.0f * scale * Sin(angle); + velocity.y = scale * Cos(angle); + velocity.z = (0.4f * speed + 0.4f) * scale; + elasticity = 0.5f; + break; + } + default: break; + } + + int i = 0; +#ifdef FIX_BUGS + while (i < ARRAY_SIZE(gaProjectileInfo) && gaProjectileInfo[i].m_bInUse) i++; +#else + // array overrun is UB + while (gaProjectileInfo[i].m_bInUse && i < ARRAY_SIZE(gaProjectileInfo)) i++; +#endif + if (i == ARRAY_SIZE(gaProjectileInfo)) + return false; + + switch (weapon) + { + case WEAPONTYPE_ROCKETLAUNCHER: + ms_apProjectile[i] = new CProjectile(MI_MISSILE); + break; + case WEAPONTYPE_FLAMETHROWER: + break; + case WEAPONTYPE_MOLOTOV: + ms_apProjectile[i] = new CProjectile(MI_MOLOTOV); + break; + case WEAPONTYPE_GRENADE: + ms_apProjectile[i] = new CProjectile(MI_GRENADE); + break; + default: break; + } + + if (ms_apProjectile[i] == nil) + return false; + + gaProjectileInfo[i].m_eWeaponType = weapon; + gaProjectileInfo[i].m_pSource = ped; + ms_apProjectile[i]->GetMatrix() = matrix; + ms_apProjectile[i]->SetMoveSpeed(velocity); + ms_apProjectile[i]->bAffectedByGravity = gravity; + + gaProjectileInfo[i].m_nExplosionTime = time; + ms_apProjectile[i]->m_fElasticity = elasticity; + ms_apProjectile[i]->m_nSpecialCollisionResponseCases = SpecialCollisionResponseCase; + +#ifdef SQUEEZE_PERFORMANCE + projectileInUse++; +#endif + + gaProjectileInfo[i].m_bInUse = true; + CWorld::Add(ms_apProjectile[i]); + + gaProjectileInfo[i].m_vecPos = ms_apProjectile[i]->GetPosition(); + return true; +} + +void +CProjectileInfo::RemoveProjectile(CProjectileInfo *info, CProjectile *projectile) +{ + RemoveNotAdd(info->m_pSource, info->m_eWeaponType, projectile->GetPosition()); +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + info->m_bInUse = false; + CWorld::Remove(projectile); + delete projectile; +} + +void +CProjectileInfo::RemoveNotAdd(CEntity *entity, eWeaponType weaponType, CVector pos) +{ + switch (weaponType) + { + case WEAPONTYPE_GRENADE: + CExplosion::AddExplosion(nil, entity, EXPLOSION_GRENADE, pos, 0); + break; + case WEAPONTYPE_MOLOTOV: + CExplosion::AddExplosion(nil, entity, EXPLOSION_MOLOTOV, pos, 0); + break; + case WEAPONTYPE_ROCKETLAUNCHER: + CExplosion::AddExplosion(nil, entity, EXPLOSION_ROCKET, pos, 0); + break; + default: break; + } +} + +void +CProjectileInfo::Update() +{ +#ifdef SQUEEZE_PERFORMANCE + if (projectileInUse == 0) + return; +#endif + + for (int i = 0; i < ARRAY_SIZE(gaProjectileInfo); i++) { + if (!gaProjectileInfo[i].m_bInUse) continue; + + CPed *ped = (CPed*)gaProjectileInfo[i].m_pSource; + if (ped != nil && ped->IsPed() && !ped->IsPointerValid()) + gaProjectileInfo[i].m_pSource = nil; + + if (ms_apProjectile[i] == nil) { +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + gaProjectileInfo[i].m_bInUse = false; + continue; + } + + if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) { + CParticle::AddParticle(PARTICLE_SMOKE, ms_apProjectile[i]->GetPosition(), CVector(0.0f, 0.0f, 0.0f)); + } + + if (CTimer::GetTimeInMilliseconds() <= gaProjectileInfo[i].m_nExplosionTime) { + if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) { + CVector pos = ms_apProjectile[i]->GetPosition(); + CWorld::pIgnoreEntity = ms_apProjectile[i]; + if (ms_apProjectile[i]->bHasCollided + || !CWorld::GetIsLineOfSightClear(gaProjectileInfo[i].m_vecPos, pos, true, true, true, true, false, false) + || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER && (CHeli::TestRocketCollision(&pos) || CPlane::TestRocketCollision(&pos))) { + RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]); + } + CWorld::pIgnoreEntity = nil; + } else if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_MOLOTOV) { + CVector pos = ms_apProjectile[i]->GetPosition(); + CWorld::pIgnoreEntity = ms_apProjectile[i]; + + if (gaProjectileInfo[i].m_pSource == nil + || ((gaProjectileInfo[i].m_vecPos - gaProjectileInfo[i].m_pSource->GetPosition()).MagnitudeSqr() >= 2.0f)) + { + if (ms_apProjectile[i]->bHasCollided + || !CWorld::GetIsLineOfSightClear(gaProjectileInfo[i].m_vecPos, pos, true, true, true, true, false, false) + || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER && (CHeli::TestRocketCollision(&pos) || CPlane::TestRocketCollision(&pos))) { + RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]); + } + } + CWorld::pIgnoreEntity = nil; + } + } else { + RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]); + } + + gaProjectileInfo[i].m_vecPos = ms_apProjectile[i]->GetPosition(); + } +} + +bool +CProjectileInfo::IsProjectileInRange(float x1, float x2, float y1, float y2, float z1, float z2, bool remove) +{ + bool result = false; + for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) { + if (gaProjectileInfo[i].m_bInUse) { + if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_MOLOTOV || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_GRENADE) { + const CVector &pos = ms_apProjectile[i]->GetPosition(); + if (pos.x >= x1 && pos.x <= x2 && pos.y >= y1 && pos.y <= y2 && pos.z >= z1 && pos.z <= z2) { + result = true; + if (remove) { +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + gaProjectileInfo[i].m_bInUse = false; + CWorld::Remove(ms_apProjectile[i]); + delete ms_apProjectile[i]; + } + } + } + } + } + return result; +} + +void +CProjectileInfo::RemoveAllProjectiles() +{ +#ifdef SQUEEZE_PERFORMANCE + if (projectileInUse == 0) + return; +#endif + + for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) { + if (gaProjectileInfo[i].m_bInUse) { +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + gaProjectileInfo[i].m_bInUse = false; + CWorld::Remove(ms_apProjectile[i]); + delete ms_apProjectile[i]; + } + } +} + +bool +CProjectileInfo::RemoveIfThisIsAProjectile(CObject *object) +{ +#ifdef SQUEEZE_PERFORMANCE + if (projectileInUse == 0) + return false; +#endif + + int i = 0; + while (ms_apProjectile[i++] != object) { + if (i >= ARRAY_SIZE(ms_apProjectile)) + return false; + } + +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + gaProjectileInfo[i].m_bInUse = false; + CWorld::Remove(ms_apProjectile[i]); + delete ms_apProjectile[i]; + ms_apProjectile[i] = nil; + return true; +} diff --git a/src/weapons/ProjectileInfo.h b/src/weapons/ProjectileInfo.h new file mode 100644 index 0000000..3d8074c --- /dev/null +++ b/src/weapons/ProjectileInfo.h @@ -0,0 +1,33 @@ +#pragma once + +#include "WeaponType.h" + +class CEntity; +class CObject; +class CProjectile; + +class CProjectileInfo +{ +public: + eWeaponType m_eWeaponType; + CEntity *m_pSource; + uint32 m_nExplosionTime; + bool m_bInUse; + CVector m_vecPos; + +public: + static CProjectileInfo *GetProjectileInfo(int32 id); + static CProjectile *ms_apProjectile[NUM_PROJECTILES]; + + static void Initialise(); + static void Shutdown(); + static bool AddProjectile(CEntity *ped, eWeaponType weapon, CVector pos, float speed); + static void RemoveProjectile(CProjectileInfo *info, CProjectile *projectile); + static void RemoveNotAdd(CEntity *entity, eWeaponType weaponType, CVector pos); + static bool RemoveIfThisIsAProjectile(CObject *pObject); + static void RemoveAllProjectiles(); + static void Update(); + static bool IsProjectileInRange(float x1, float x2, float y1, float y2, float z1, float z2, bool remove); +}; + +extern CProjectileInfo gaProjectileInfo[NUM_PROJECTILES]; \ No newline at end of file diff --git a/src/weapons/ShotInfo.cpp b/src/weapons/ShotInfo.cpp new file mode 100644 index 0000000..e604093 --- /dev/null +++ b/src/weapons/ShotInfo.cpp @@ -0,0 +1,149 @@ +#include "common.h" + +#include "ShotInfo.h" +#include "Entity.h" +#include "Weapon.h" +#include "World.h" +#include "WeaponInfo.h" +#include "General.h" +#include "Timer.h" +#include "Ped.h" +#include "Fire.h" + +CShotInfo gaShotInfo[NUMSHOTINFOS]; +float CShotInfo::ms_afRandTable[20]; + +#ifdef SQUEEZE_PERFORMANCE +uint32 shotInfoInUse; +#endif + +/* + Used for flamethrower. I don't know why it's name is CShotInfo. + Has no relation with any visual, just calculates the area fire affects + (including spreading and slowing of fire) and make entities burn/flee. +*/ + +void +CShotInfo::Initialise() +{ + debug("Initialising CShotInfo...\n"); + for(int i=0; im_fRadius; + + if (weaponInfo->m_fSpread != 0.0f) { + gaShotInfo[slot].m_areaAffected.x += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] * weaponInfo->m_fSpread; + gaShotInfo[slot].m_areaAffected.y += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] * weaponInfo->m_fSpread; + gaShotInfo[slot].m_areaAffected.z += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)]; + } + gaShotInfo[slot].m_areaAffected.Normalise(); + if (weaponInfo->IsFlagSet(WEAPONFLAG_RAND_SPEED)) + gaShotInfo[slot].m_areaAffected *= CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] + weaponInfo->m_fSpeed; + else + gaShotInfo[slot].m_areaAffected *= weaponInfo->m_fSpeed; + + gaShotInfo[slot].m_sourceEntity = sourceEntity; + gaShotInfo[slot].m_timeout = CTimer::GetTimeInMilliseconds() + weaponInfo->m_fLifespan; + + return true; +} + +void +CShotInfo::Shutdown() +{ + debug("Shutting down CShotInfo...\n"); + debug("CShotInfo shut down\n"); +} + +void +CShotInfo::Update() +{ +#ifdef SQUEEZE_PERFORMANCE + if (shotInfoInUse == 0) + return; +#endif + for (int slot = 0; slot < ARRAY_SIZE(gaShotInfo); slot++) { + CShotInfo &shot = gaShotInfo[slot]; + if (shot.m_sourceEntity && shot.m_sourceEntity->IsPed() && !((CPed*)shot.m_sourceEntity)->IsPointerValid()) + shot.m_sourceEntity = nil; + + if (!shot.m_inUse) + continue; + + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(shot.m_weapon); + if (CTimer::GetTimeInMilliseconds() > shot.m_timeout) { +#ifdef SQUEEZE_PERFORMANCE + shotInfoInUse--; +#endif + shot.m_inUse = false; + } + + if (weaponInfo->IsFlagSet(WEAPONFLAG_SLOWS_DOWN)) + shot.m_areaAffected *= pow(0.96, CTimer::GetTimeStep()); // FRAMERATE + + if (weaponInfo->IsFlagSet(WEAPONFLAG_EXPANDS)) + shot.m_radius += 0.075f * CTimer::GetTimeStep(); + + shot.m_startPos += CTimer::GetTimeStep() * shot.m_areaAffected; + if (shot.m_sourceEntity) { + assert(shot.m_sourceEntity->IsPed()); + CPed *ped = (CPed*) shot.m_sourceEntity; + float radius = Max(1.0f, shot.m_radius); + + for (int i = 0; i < ped->m_numNearPeds; ++i) { + CPed *nearPed = ped->m_nearPeds[i]; + if (nearPed->IsPointerValid()) { + if (nearPed->IsPedInControl() && (nearPed->GetPosition() - shot.m_startPos).MagnitudeSqr() < radius && !nearPed->bFireProof) { + + if (!nearPed->IsPlayer()) { + nearPed->SetFindPathAndFlee(shot.m_sourceEntity, 10000); + nearPed->SetMoveState(PEDMOVE_SPRINT); + } + gFireManager.StartFire(nearPed, shot.m_sourceEntity, 0.8f, true); + } + } + } + } + if (!((CTimer::GetFrameCounter() + slot) & 3)) + CWorld::SetCarsOnFire(shot.m_startPos.x, shot.m_startPos.y, shot.m_startPos.z, 4.0f, shot.m_sourceEntity); + } +} \ No newline at end of file diff --git a/src/weapons/ShotInfo.h b/src/weapons/ShotInfo.h new file mode 100644 index 0000000..db6158c --- /dev/null +++ b/src/weapons/ShotInfo.h @@ -0,0 +1,24 @@ +#pragma once + +#include "WeaponType.h" + +class CEntity; + +class CShotInfo +{ +public: + eWeaponType m_weapon; + CVector m_startPos; + CVector m_areaAffected; + float m_radius; + CEntity *m_sourceEntity; + float m_timeout; + bool m_inUse; + + static float ms_afRandTable[20]; + + static void Initialise(void); + static bool AddShot(CEntity*, eWeaponType, CVector, CVector); + static void Shutdown(void); + static void Update(void); +}; diff --git a/src/weapons/Weapon.cpp b/src/weapons/Weapon.cpp new file mode 100644 index 0000000..d6af820 --- /dev/null +++ b/src/weapons/Weapon.cpp @@ -0,0 +1,2415 @@ +#include "common.h" + +#include "Weapon.h" +#include "AnimBlendAssociation.h" +#include "AudioManager.h" +#include "BulletInfo.h" +#include "Camera.h" +#include "Coronas.h" +#include "DMAudio.h" +#include "Explosion.h" +#include "General.h" +#include "Glass.h" +#include "Heli.h" +#include "ModelIndices.h" +#include "Object.h" +#include "Pad.h" +#include "Particle.h" +#include "Ped.h" +#include "PointLights.h" +#include "Pools.h" +#include "ProjectileInfo.h" +#include "RpAnimBlend.h" +#include "ShotInfo.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "TempColModels.h" +#include "Timer.h" +#include "Automobile.h" +#include "Boat.h" +#include "WaterLevel.h" +#include "WeaponInfo.h" +#include "World.h" +#include "SaveBuf.h" + +uint16 gReloadSampleTime[WEAPONTYPE_LAST_WEAPONTYPE] = +{ + 0, // UNARMED + 0, // BASEBALLBAT + 250, // COLT45 + 400, // UZI + 650, // SHOTGUN + 300, // AK47 + 300, // M16 + 423, // SNIPERRIFLE + 400, // ROCKETLAUNCHER + 0, // FLAMETHROWER + 0, // MOLOTOV + 0, // GRENADE + 0, // DETONATOR + 0 // HELICANNON +}; + +#ifdef FREE_CAM +static bool +Find3rdPersonCamTargetVectorFromCachedVectors(float dist, CVector pos, CVector& source, CVector& target, CVector camSource, CVector camFront, CVector camUp) +{ + if (CPad::GetPad(0)->GetLookBehindForPed()) { + source = pos; + target = dist * FindPlayerPed()->GetForward() + source; + return false; + } else { + float angleX = DEGTORAD((TheCamera.m_f3rdPersonCHairMultX - 0.5f) * 1.8f * 0.5f * TheCamera.Cams[TheCamera.ActiveCam].FOV * CDraw::GetAspectRatio()); + float angleY = DEGTORAD((0.5f - TheCamera.m_f3rdPersonCHairMultY) * 1.8f * 0.5f * TheCamera.Cams[TheCamera.ActiveCam].FOV); + source = camSource; + target = camFront; + target += camUp * Tan(angleY); + target += CrossProduct(camFront, camUp) * Tan(angleX); + target.Normalise(); + source += DotProduct(pos - source, target) * target; + target = dist * target + source; + return true; + } +} +#endif + +CWeaponInfo * +CWeapon::GetInfo() +{ + CWeaponInfo *info = CWeaponInfo::GetWeaponInfo(m_eWeaponType); + ASSERT(info!=nil); + return info; +} + +void +CWeapon::InitialiseWeapons(void) +{ + CWeaponInfo::Initialise(); + CShotInfo::Initialise(); + CExplosion::Initialise(); + CProjectileInfo::Initialise(); + CBulletInfo::Initialise(); +} + +void +CWeapon::ShutdownWeapons(void) +{ + CWeaponInfo::Shutdown(); + CShotInfo::Shutdown(); + CExplosion::Shutdown(); + CProjectileInfo::Shutdown(); + CBulletInfo::Shutdown(); +} + +void +CWeapon::UpdateWeapons(void) +{ + CShotInfo::Update(); + CExplosion::Update(); + CProjectileInfo::Update(); + CBulletInfo::Update(); +} + +void +CWeapon::Initialise(eWeaponType type, int32 ammo) +{ + m_eWeaponType = type; + m_eWeaponState = WEAPONSTATE_READY; + if (ammo > 99999) + m_nAmmoTotal = 99999; + else + m_nAmmoTotal = ammo; + m_nAmmoInClip = 0; + Reload(); + m_nTimer = 0; +} + +bool +CWeapon::Fire(CEntity *shooter, CVector *fireSource) +{ + ASSERT(shooter!=nil); + + CVector fireOffset(0.0f, 0.0f, 0.6f); + CVector *source = fireSource; + + if (!fireSource) + { + fireOffset = shooter->GetMatrix() * fireOffset; +#ifdef FIX_BUGS + static CVector tmp; + tmp = fireOffset; + source = &tmp; +#else + source = &fireOffset; +#endif + + } + if ( m_bAddRotOffset ) + { + float heading = RADTODEG(shooter->GetForward().Heading()); + float angle = DEGTORAD(heading); + (*source).x += -Sin(angle) * 0.15f; + (*source).y += Cos(angle) * 0.15f; + } + + if ( m_eWeaponState != WEAPONSTATE_READY && m_eWeaponState != WEAPONSTATE_FIRING ) + return false; + + bool fired; + + if ( GetInfo()->m_eWeaponFire != WEAPON_FIRE_MELEE ) + { + if ( m_nAmmoInClip <= 0 ) + return false; + + switch ( m_eWeaponType ) + { + case WEAPONTYPE_SHOTGUN: + { + fired = FireShotgun(shooter, source); + + break; + } + + case WEAPONTYPE_COLT45: + case WEAPONTYPE_UZI: + case WEAPONTYPE_AK47: + { + fired = FireInstantHit(shooter, source); + + break; + } + + case WEAPONTYPE_SNIPERRIFLE: + { + fired = FireSniper(shooter); + + break; + } + + case WEAPONTYPE_M16: + { + if ( TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON && shooter == FindPlayerPed() ) + fired = FireM16_1stPerson(shooter); + else + fired = FireInstantHit(shooter, source); + + break; + } + + case WEAPONTYPE_ROCKETLAUNCHER: + { + if ( shooter->IsPed() && ((CPed*)shooter)->m_pSeekTarget != nil ) + { + float distToTarget = (shooter->GetPosition() - ((CPed*)shooter)->m_pSeekTarget->GetPosition()).Magnitude(); + + if ( distToTarget > 8.0f || ((CPed*)shooter)->IsPlayer() ) + fired = FireProjectile(shooter, source, 0.0f); + else + fired = false; + } + else + fired = FireProjectile(shooter, source, 0.0f); + + break; + } + + case WEAPONTYPE_MOLOTOV: + case WEAPONTYPE_GRENADE: + { + if ( shooter == FindPlayerPed() ) + { + fired = FireProjectile(shooter, source, ((CPlayerPed*)shooter)->m_fAttackButtonCounter*0.0375f); + if ( m_eWeaponType == WEAPONTYPE_GRENADE ) + CStats::KgsOfExplosivesUsed++; + } + else if ( shooter->IsPed() && ((CPed*)shooter)->m_pSeekTarget != nil ) + { + float distToTarget = (shooter->GetPosition() - ((CPed*)shooter)->m_pSeekTarget->GetPosition()).Magnitude(); + float power = Clamp((distToTarget-10.0f)*0.02f, 0.2f, 1.0f); + + fired = FireProjectile(shooter, source, power); + } + else + fired = FireProjectile(shooter, source, 0.3f); + + break; + } + + case WEAPONTYPE_FLAMETHROWER: + { + fired = FireAreaEffect(shooter, source); + + break; + } + + case WEAPONTYPE_DETONATOR: + { + CWorld::UseDetonator(shooter); + m_nAmmoTotal = 1; + m_nAmmoInClip = m_nAmmoTotal; + fired = true; + + break; + } + + case WEAPONTYPE_HELICANNON: + { + if ( (TheCamera.PlayerWeaponMode.Mode == CCam::MODE_HELICANNON_1STPERSON || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON ) + && shooter == FindPlayerPed() ) + { + fired = FireM16_1stPerson(shooter); + } + else + fired = FireInstantHit(shooter, source); + + break; + } + + default: + { + debug("Unknown weapon type, Weapon.cpp"); + break; + } + } + + if (fired) + { + bool isPlayer = false; + + if (shooter->IsPed()) + { + CPed* shooterPed = (CPed*)shooter; + + shooterPed->bIsShooting = true; + + if (shooterPed->IsPlayer()) + isPlayer = true; + + DMAudio.PlayOneShot(shooterPed->m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + } + + if (m_nAmmoInClip > 0) m_nAmmoInClip--; + if (m_nAmmoTotal > 0 && (m_nAmmoTotal < 25000 || isPlayer)) m_nAmmoTotal--; + + if (m_eWeaponState == WEAPONSTATE_READY && m_eWeaponType == WEAPONTYPE_FLAMETHROWER) + DMAudio.PlayOneShot(((CPhysical*)shooter)->m_audioEntityId, SOUND_WEAPON_FLAMETHROWER_FIRE, 0.0f); + + m_eWeaponState = WEAPONSTATE_FIRING; + + if (m_nAmmoInClip == 0) + { + if (m_nAmmoTotal == 0) + return true; + + m_eWeaponState = WEAPONSTATE_RELOADING; + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nReload; + + if (shooter == FindPlayerPed()) + { + if (CWorld::Players[CWorld::PlayerInFocus].m_bFastReload) + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nReload / 4; + } + + return true; + } + + m_nTimer = CTimer::GetTimeInMilliseconds() + 1000; + if (shooter == FindPlayerPed()) + CStats::RoundsFiredByPlayer++; + } + } + else + { + if ( m_eWeaponState != WEAPONSTATE_FIRING ) + { + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nReload; + m_eWeaponState = WEAPONSTATE_FIRING; + } + + FireMelee(shooter, *source); + } + + if ( m_eWeaponType == WEAPONTYPE_UNARMED || m_eWeaponType == WEAPONTYPE_BASEBALLBAT ) + return true; + else + return fired; +} + +bool +CWeapon::FireFromCar(CAutomobile *shooter, bool left) +{ + ASSERT(shooter!=nil); + + if ( m_eWeaponState != WEAPONSTATE_READY && m_eWeaponState != WEAPONSTATE_FIRING ) + return false; + + if ( m_nAmmoInClip <= 0 ) + return false; + + if ( FireInstantHitFromCar(shooter, left) ) + { + DMAudio.PlayOneShot(shooter->m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + + if ( m_nAmmoInClip > 0 ) m_nAmmoInClip--; + if ( m_nAmmoTotal < 25000 && m_nAmmoTotal > 0 ) m_nAmmoTotal--; + + m_eWeaponState = WEAPONSTATE_FIRING; + + if ( m_nAmmoInClip == 0 ) + { + if ( m_nAmmoTotal == 0 ) + return true; + + m_eWeaponState = WEAPONSTATE_RELOADING; + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nReload; + + return true; + } + + m_nTimer = CTimer::GetTimeInMilliseconds() + 1000; + if ( shooter == FindPlayerVehicle() ) + CStats::RoundsFiredByPlayer++; + } + + return true; +} + +bool +CWeapon::FireMelee(CEntity *shooter, CVector &fireSource) +{ + ASSERT(shooter!=nil); + + CWeaponInfo *info = GetInfo(); + + bool anim2Playing = false; + if ( RpAnimBlendClumpGetAssociation(shooter->GetClump(), info->m_Anim2ToPlay) ) + anim2Playing = true; + + ASSERT(shooter->IsPed()); + + CPed *shooterPed = (CPed*)shooter; + + for ( int32 i = 0; i < shooterPed->m_numNearPeds; i++ ) + { + CPed *victimPed = shooterPed->m_nearPeds[i]; + ASSERT(victimPed!=nil); + + if ( (victimPed->m_nPedType != shooterPed->m_nPedType || victimPed == shooterPed->m_pSeekTarget) + && victimPed != shooterPed->m_leader || !(CGeneral::GetRandomNumber() & 31) ) + { + bool collided = false; + + CColModel *victimPedCol = &CTempColModels::ms_colModelPed1; + if ( victimPed->OnGround() || !victimPed->IsPedHeadAbovePos(-0.3f) ) + victimPedCol = &CTempColModels::ms_colModelPedGroundHit; + + + float victimPedRadius = victimPed->GetBoundRadius() + info->m_fRadius; + if ( victimPed->bUsesCollision || victimPed->Dead() || victimPed->Driving() ) + { + CVector victimPedPos = victimPed->GetPosition(); + if ( SQR(victimPedRadius) > (victimPedPos-fireSource).MagnitudeSqr() ) + { + CVector collisionDist; + + int32 s = 0; + while ( s < victimPedCol->numSpheres ) + { + CColSphere *sphere = &victimPedCol->spheres[s]; + collisionDist = victimPedPos+sphere->center-fireSource; + + if ( SQR(sphere->radius + info->m_fRadius) > collisionDist.MagnitudeSqr() ) + { + collided = true; + break; + } + s++; + } + + if ( !(victimPed->IsPlayer() && victimPed->GetPedState() == PED_GETUP) ) + { + if ( collided ) + { + float victimPedHealth = victimPed->m_fHealth; + CVector bloodPos = fireSource + (collisionDist*0.7f); + + CVector2D posOffset(shooterPed->GetPosition().x-victimPedPos.x, shooterPed->GetPosition().y-victimPedPos.y); + + int32 localDir = victimPed->GetLocalDirection(posOffset); + + bool isBat = m_eWeaponType == WEAPONTYPE_BASEBALLBAT; + + if ( !victimPed->DyingOrDead() ) + victimPed->ReactToAttack(shooterPed); + + uint8 hitLevel = HITLEVEL_HIGH; + if ( isBat && victimPed->OnGround() ) + hitLevel = HITLEVEL_GROUND; + + victimPed->StartFightDefend(localDir, hitLevel, 10); + + if ( !victimPed->DyingOrDead() ) + { + if ( shooterPed->IsPlayer() && isBat && anim2Playing ) + victimPed->InflictDamage(shooterPed, m_eWeaponType, 100.0f, PEDPIECE_TORSO, localDir); + else if ( shooterPed->IsPlayer() && ((CPlayerPed*)shooterPed)->m_bAdrenalineActive ) + victimPed->InflictDamage(shooterPed, m_eWeaponType, 3.5f*info->m_nDamage, PEDPIECE_TORSO, localDir); + else + { + if ( victimPed->IsPlayer() && isBat ) // wtf, it's not fair + victimPed->InflictDamage(shooterPed, m_eWeaponType, 2.0f*info->m_nDamage, PEDPIECE_TORSO, localDir); + else + victimPed->InflictDamage(shooterPed, m_eWeaponType, info->m_nDamage, PEDPIECE_TORSO, localDir); + } + } + + if ( CGame::nastyGame ) + { + if ( victimPed->GetIsOnScreen() ) + { + CVector dir = collisionDist * RecipSqrt(1.0f, 10.0f*collisionDist.MagnitudeSqr()); + + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + + if ( isBat ) + { + dir.x += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + dir.y += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + + dir.x += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + dir.y += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir); + } + } + } + + if ( !victimPed->OnGround() ) + { + if ( victimPed->m_fHealth > 0.0f + && (victimPed->m_fHealth < 20.0f && victimPedHealth > 20.0f || isBat && !victimPed->IsPlayer()) ) + { + posOffset.Normalise(); + victimPed->bIsStanding = false; + victimPed->ApplyMoveForce(posOffset.x*-5.0f, posOffset.y*-5.0f, 3.0f); + + if ( isBat && victimPed->IsPlayer() ) + victimPed->SetFall(3000, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), false); + else + victimPed->SetFall(1500, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), false); + + shooterPed->m_pSeekTarget = victimPed; + shooterPed->m_pSeekTarget->RegisterReference(&shooterPed->m_pSeekTarget); + } + } + else if (victimPed->Dying() && !anim2Playing) + { + posOffset.Normalise(); + victimPed->bIsStanding = false; + victimPed->ApplyMoveForce(posOffset.x*-5.0f, posOffset.y*-5.0f, 3.0f); + } + + m_eWeaponState = WEAPONSTATE_MELEE_MADECONTACT; + + if ( victimPed->m_nPedType == PEDTYPE_COP ) + CEventList::RegisterEvent(EVENT_ASSAULT_POLICE, EVENT_ENTITY_PED, victimPed, shooterPed, 2000); + else + CEventList::RegisterEvent(EVENT_ASSAULT, EVENT_ENTITY_PED, victimPed, shooterPed, 2000); + } + } + } + } + } + } + + return true; +} + +bool +CWeapon::FireInstantHit(CEntity *shooter, CVector *fireSource) +{ + ASSERT(shooter!=nil); + ASSERT(fireSource!=nil); + + CWeaponInfo *info = GetInfo(); + + CVector source, target; + CColPoint point; + CEntity *victim = nil; + + float heading = RADTODEG(shooter->GetForward().Heading()); + float angle = DEGTORAD(heading); + + CVector2D ahead(-Sin(angle), Cos(angle)); + ahead.Normalise(); + + CVector vel = ((CPed *)shooter)->m_vecMoveSpeed; + int32 shooterMoving = false; + if ( Abs(vel.x) > 0.0f && Abs(vel.y) > 0.0f ) + shooterMoving = true; + + if ( shooter == FindPlayerPed() ) + { + static float prev_heading = 0.0f; + prev_heading = ((CPed*)shooter)->m_fRotationCur; + } + + if ( shooter->IsPed() && ((CPed *)shooter)->m_pPointGunAt ) + { + CPed *shooterPed = (CPed *)shooter; + if ( shooterPed->m_pedIK.m_flags & CPedIK::GUN_POINTED_SUCCESSFULLY ) + { + int32 accuracy = shooterPed->m_wepAccuracy; + int32 inaccuracy = 100-accuracy; + + if ( accuracy != 100 ) + FindPlayerPed(); //what ? + + CPed *threatAttack = (CPed*)shooterPed->m_pPointGunAt; + if ( threatAttack->IsPed() ) + { + threatAttack->m_pedIK.GetComponentPosition(target, PED_MID); + threatAttack->ReactToPointGun(shooter); + } + else + target = threatAttack->GetPosition(); + + target -= *fireSource; + target *= info->m_fRange / target.Magnitude(); + target += *fireSource; + + if ( inaccuracy != 0 ) + { + target.x += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f) * inaccuracy; + target.y += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f) * inaccuracy; + target.z += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * inaccuracy; + } + + CWorld::bIncludeDeadPeds = true; + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + CWorld::bIncludeDeadPeds = false; + } + else + { + target.x = info->m_fRange; + target.y = 0.0f; + target.z = 0.0f; + + shooterPed->TransformToNode(target, PED_HANDR); + + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + } +#ifdef FIX_BUGS + // fix muzzleflash rotation + heading = CGeneral::GetAngleBetweenPoints(fireSource->x, fireSource->y, target.x, target.y); + angle = DEGTORAD(heading); + + ahead = CVector2D(-Sin(angle), Cos(angle)); + ahead.Normalise(); +#endif + } + else if ( shooter == FindPlayerPed() && TheCamera.Cams[0].Using3rdPersonMouseCam() ) + { + CVector src, trgt; +#ifdef FREE_CAM + if (CCamera::bFreeCam) { + CPlayerPed *shooterPed = (CPlayerPed*)shooter; + Find3rdPersonCamTargetVectorFromCachedVectors(info->m_fRange, *fireSource, src, trgt, shooterPed->m_cachedCamSource, shooterPed->m_cachedCamFront, shooterPed->m_cachedCamUp); + if ((shooterPed->m_pedIK.m_flags & CPedIK::GUN_POINTED_SUCCESSFULLY) == 0) { + trgt.x = info->m_fRange; + trgt.y = 0.0f; + trgt.z = 0.0f; + + shooterPed->TransformToNode(trgt, PED_HANDR); + } + } else +#endif + { + TheCamera.Find3rdPersonCamTargetVector(info->m_fRange, *fireSource, src, trgt); + } + +#ifdef FIX_BUGS + // fix muzzleflash rotation + heading = CGeneral::GetAngleBetweenPoints(src.x, src.y, trgt.x, trgt.y); + angle = DEGTORAD(heading); + + ahead = CVector2D(-Sin(angle), Cos(angle)); + ahead.Normalise(); +#endif + + CWorld::bIncludeDeadPeds = true; + ProcessLineOfSight(src, trgt,point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + CWorld::bIncludeDeadPeds = false; + + int32 rotSpeed = 1; + if ( m_eWeaponType == WEAPONTYPE_M16 ) + rotSpeed = 4; + + CVector bulletPos; + if ( CHeli::TestBulletCollision(&src, &trgt, &bulletPos, 4) ) + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, bulletPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, rotSpeed); + } + } + else + { + float shooterHeading = RADTODEG(shooter->GetForward().Heading()); + float shooterAngle = DEGTORAD(shooterHeading); + + CVector2D rotOffset(-Sin(shooterAngle), Cos(shooterAngle)); + rotOffset.Normalise(); + + target = *fireSource; + target.x += rotOffset.x * info->m_fRange; + target.y += rotOffset.y * info->m_fRange; + + if ( shooter->IsPed() ) + DoDoomAiming(shooter, fireSource, &target); + + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + + int32 rotSpeed = 1; + if ( m_eWeaponType == WEAPONTYPE_M16 ) + rotSpeed = 4; + + CVector bulletPos; + if ( CHeli::TestBulletCollision(fireSource, &target, &bulletPos, 4) ) + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, bulletPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, rotSpeed); + } + } + + if ( victim && shooter->IsPed() && victim == ((CPed*)shooter)->m_leader ) + return false; + + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, shooter, (CPed *)shooter, 1000); + + if ( shooter == FindPlayerPed() ) + { + CStats::InstantHitsFiredByPlayer++; + if ( !(CTimer::GetFrameCounter() & 3) ) + MakePedsJumpAtShot((CPhysical*)shooter, fireSource, &target); + } + + switch ( m_eWeaponType ) + { + case WEAPONTYPE_AK47: + { + static uint8 counter = 0; + + if ( !(++counter & 1) ) + { + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *fireSource, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CVector gunflashPos = *fireSource; + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.10f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.08f); + gunflashPos += CVector(0.05f*ahead.x, 0.05f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.06f); + gunflashPos += CVector(0.04f*ahead.x, 0.04f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + + CVector gunsmokePos = *fireSource; + float rnd = CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*rnd, ahead.y*rnd, 0.0f)); + + CVector gunshellPos = *fireSource; + gunshellPos -= CVector(0.5f*ahead.x, 0.5f*ahead.y, 0.0f); + CVector dir = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + dir.Normalise2D(); + AddGunshell(shooter, gunshellPos, CVector2D(dir.x, dir.y), 0.018f); + } + + break; + } + + case WEAPONTYPE_M16: + { + static uint8 counter = 0; + + if ( !(++counter & 1) ) + { + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *fireSource, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CVector gunflashPos = *fireSource; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.08f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.06f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.06f); + + gunflashPos = *fireSource; + gunflashPos += CVector(-0.1f*ahead.x, -0.1f*ahead.y, 0.0f); + gunflashPos.z += 0.04f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos.z += 0.04f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + gunflashPos.z += 0.03f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + gunflashPos = *fireSource; + gunflashPos += CVector(-0.1f*ahead.x, -0.1f*ahead.y, 0.0f); + gunflashPos.z -= 0.04f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos.z -= 0.04f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + gunflashPos.z -= 0.03f; + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + CVector offset = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + offset.Normalise2D(); + + gunflashPos = *fireSource; + gunflashPos += CVector(-0.1f*ahead.x, -0.1f*ahead.y, 0.0f); + gunflashPos += CVector(0.06f*offset.x, 0.06f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos += CVector(0.04f*offset.x, 0.04f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.03f); + gunflashPos += CVector(0.03f*offset.x, 0.03f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + gunflashPos = *fireSource; + gunflashPos += CVector(-0.1f*ahead.x, -0.1f*ahead.y, 0.0f); + gunflashPos -= CVector(0.06f*offset.x, 0.06f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos -= CVector(0.04f*offset.x, 0.04f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.03f); + gunflashPos -= CVector(0.03f*offset.x, 0.03f*offset.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + CVector gunsmokePos = *fireSource; + float rnd = CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*rnd, ahead.y*rnd, 0.0f)); + + CVector gunshellPos = *fireSource; + gunshellPos -= CVector(0.65f*ahead.x, 0.65f*ahead.y, 0.0f); + CVector dir = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + dir.Normalise2D(); + AddGunshell(shooter, gunshellPos, CVector2D(dir.x, dir.y), 0.02f); + } + + break; + } + + case WEAPONTYPE_UZI: + { + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *fireSource, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CVector gunflashPos = *fireSource; + + if ( shooterMoving ) + gunflashPos += CVector(1.5f*vel.x, 1.5f*vel.y, 0.0f); + + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.07f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.05f); + gunflashPos += CVector(0.04f*ahead.x, 0.04f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos += CVector(0.04f*ahead.x, 0.04f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.03f); + gunflashPos += CVector(0.03f*ahead.x, 0.03f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.03f); + gunflashPos += CVector(0.03f*ahead.x, 0.03f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + gunflashPos += CVector(0.02f*ahead.x, 0.02f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.01f); + + CVector gunsmokePos = *fireSource; + float rnd = CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*rnd, ahead.y*rnd, 0.0f)); + + CVector gunshellPos = *fireSource; + gunshellPos -= CVector(0.2f*ahead.x, 0.2f*ahead.y, 0.0f); + CVector dir = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + dir.Normalise2D(); + AddGunshell(shooter, gunshellPos, CVector2D(dir.x, dir.y), 0.015f); + + break; + } + + case WEAPONTYPE_COLT45: + { + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *fireSource, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CVector gunflashPos = *fireSource; + + if ( shooterMoving ) + gunflashPos += CVector(1.5f*vel.x, 1.5f*vel.y, 0.0f); + + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.06f); + gunflashPos += CVector(0.06f*ahead.x, 0.06f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.04f); + gunflashPos += CVector(0.04f*ahead.x, 0.04f*ahead.y, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.02f); + + CVector gunsmokePos = *fireSource; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*0.10f, ahead.y*0.10f, 0.0f), nil, 0.005f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*0.15f, ahead.y*0.15f, 0.0f), nil, 0.015f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x*0.20f, ahead.y*0.20f, 0.0f), nil, 0.025f); + + CVector gunshellPos = *fireSource; + gunshellPos -= CVector(0.2f*ahead.x, 0.2f*ahead.y, 0.0f); + CVector dir = CrossProduct(CVector(ahead.x, ahead.y, 0.0f), CVector(0.0f, 0.0f, 5.0f)); + dir.Normalise2D(); + AddGunshell(shooter, gunshellPos, CVector2D(dir.x, dir.y), 0.015f); + + break; + } + default: break; + } + + DoBulletImpact(shooter, victim, fireSource, &target, &point, ahead); + + return true; +} + +void +CWeapon::AddGunshell(CEntity *shooter, CVector const &source, CVector2D const &direction, float size) +{ + ASSERT(shooter!=nil); + + if ( shooter == nil) + return; + + CVector dir(direction.x*0.05f, direction.y*0.05f, CGeneral::GetRandomNumberInRange(0.02f, 0.08f)); + + static CVector prevEntityPosition(0.0f, 0.0f, 0.0f); + CVector entityPosition = shooter->GetPosition(); + + CVector diff = entityPosition - prevEntityPosition; + + if ( Abs(diff.x)+Abs(diff.y)+Abs(diff.z) > 1.5f ) + { + prevEntityPosition = entityPosition; + + CParticle::AddParticle(PARTICLE_GUNSHELL_FIRST, + source, dir, nil, size, CGeneral::GetRandomNumberInRange(-20.0f, 20.0f)); + } + else + { + CParticle::AddParticle(PARTICLE_GUNSHELL, + source, dir, nil, size, CGeneral::GetRandomNumberInRange(-20.0f, 20.0f)); + } +} + +void +CWeapon::DoBulletImpact(CEntity *shooter, CEntity *victim, + CVector *source, CVector *target, CColPoint *point, CVector2D ahead) +{ + ASSERT(shooter!=nil); + ASSERT(source!=nil); + ASSERT(target!=nil); + ASSERT(point!=nil); + + CWeaponInfo *info = GetInfo(); + + if ( victim ) + { + CGlass::WasGlassHitByBullet(victim, point->point); + + CVector traceTarget = point->point; + CBulletTraces::AddTrace(source, &traceTarget); + + if ( shooter != nil ) + { + if ( shooter == FindPlayerPed() ) + { + if ( victim->IsPed() || victim->IsVehicle() ) + CStats::InstantHitsHitByPlayer++; + } + } + + if ( victim->IsPed() && ((CPed*)shooter)->m_nPedType != ((CPed*)victim)->m_nPedType || ((CPed*)shooter)->m_nPedType == PEDTYPE_PLAYER2 ) + { + CPed *victimPed = (CPed *)victim; + if ( !victimPed->DyingOrDead() && victim != shooter ) + { + if ( victimPed->DoesLOSBulletHitPed(*point) ) + { + CVector pos = victimPed->GetPosition(); + + CVector2D posOffset(source->x-pos.x, source->y-pos.y); + int32 localDir = victimPed->GetLocalDirection(posOffset); + + victimPed->ReactToAttack(shooter); + + if ( !victimPed->IsPedInControl() || victimPed->bIsDucking ) + { + victimPed->InflictDamage(shooter, m_eWeaponType, info->m_nDamage, (ePedPieceTypes)point->pieceB, localDir); + } + else + { + if ( m_eWeaponType == WEAPONTYPE_SHOTGUN || m_eWeaponType == WEAPONTYPE_HELICANNON ) + { + posOffset.Normalise(); + victimPed->bIsStanding = false; + + victimPed->ApplyMoveForce(posOffset.x*-5.0f, posOffset.y*-5.0f, 5.0f); + victimPed->SetFall(1500, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), false); + + victimPed->InflictDamage(shooter, m_eWeaponType, info->m_nDamage, (ePedPieceTypes)point->pieceB, localDir); + } + else + { + if ( victimPed->IsPlayer() ) + { + CPlayerPed *victimPlayer = (CPlayerPed *)victimPed; + if ( victimPlayer->m_nHitAnimDelayTimer < CTimer::GetTimeInMilliseconds() ) + { + victimPed->ClearAttackByRemovingAnim(); + + CAnimBlendAssociation *asoc = CAnimManager::AddAnimation(victimPed->GetClump(), ASSOCGRP_STD, AnimationId(ANIM_STD_HITBYGUN_FRONT + localDir)); + ASSERT(asoc!=nil); + + asoc->blendAmount = 0.0f; + asoc->blendDelta = 8.0f; + + if ( m_eWeaponType == WEAPONTYPE_AK47 || m_eWeaponType == WEAPONTYPE_M16 ) + victimPlayer->m_nHitAnimDelayTimer = CTimer::GetTimeInMilliseconds() + 2500; + else + victimPlayer->m_nHitAnimDelayTimer = CTimer::GetTimeInMilliseconds() + 1000; + } + } + else + { + victimPed->ClearAttackByRemovingAnim(); + + CAnimBlendAssociation *asoc = CAnimManager::AddAnimation(victimPed->GetClump(), ASSOCGRP_STD, AnimationId(ANIM_STD_HITBYGUN_FRONT + localDir)); + ASSERT(asoc!=nil); + + asoc->blendAmount = 0.0f; + asoc->blendDelta = 8.0f; + } + + victimPed->InflictDamage(shooter, m_eWeaponType, info->m_nDamage, (ePedPieceTypes)point->pieceB, localDir); + } + } + + if ( victimPed->m_nPedType == PEDTYPE_COP ) + CEventList::RegisterEvent(EVENT_SHOOT_COP, EVENT_ENTITY_PED, victim, (CPed*)shooter, 10000); + else + CEventList::RegisterEvent(EVENT_SHOOT_PED, EVENT_ENTITY_PED, victim, (CPed*)shooter, 10000); + + if ( CGame::nastyGame ) + { + uint8 bloodAmount = 8; + if ( m_eWeaponType == WEAPONTYPE_SHOTGUN || m_eWeaponType == WEAPONTYPE_HELICANNON ) + bloodAmount = 32; + + CVector dir = (point->point - victim->GetPosition()) * 0.01f; + dir.z = 0.01f; + + if ( victimPed->GetIsOnScreen() ) + { + for ( uint8 i = 0; i < bloodAmount; i++ ) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point->point, dir); + } + } + } + } + else + { + if ( CGame::nastyGame ) + { + CVector dir = (point->point - victim->GetPosition()) * 0.01f; + dir.z = 0.01f; + + if ( victim->GetIsOnScreen() ) + { + for ( int32 i = 0; i < 8; i++ ) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point->point + CVector(0.0f, 0.0f, 0.15f), dir); + } + + if ( victimPed->Dead() ) + { + CAnimBlendAssociation *asoc; + if ( RpAnimBlendClumpGetFirstAssociation(victimPed->GetClump(), ASSOC_FRONTAL) ) + asoc = CAnimManager::BlendAnimation(victimPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f); + else + asoc = CAnimManager::BlendAnimation(victimPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR, 8.0f); + + if ( asoc ) + { + asoc->SetCurrentTime(0.0f); + asoc->flags |= ASSOC_RUNNING; + asoc->flags &= ~ASSOC_FADEOUTWHENDONE; + } + } + } + } + } + else + { + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point->point, point->normal*0.05f); + +#ifndef FIX_BUGS + CVector dist = point->point - (*source); + CVector offset = dist - Max(0.2f * dist.Magnitude(), 2.0f) * CVector(ahead.x, ahead.y, 0.0f); + CVector smokePos = *source + offset; +#else + CVector smokePos = point->point; +#endif // !FIX_BUGS + + smokePos.x += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + smokePos.y += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + smokePos.z += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, smokePos, CVector(0.0f, 0.0f, 0.0f)); + + break; + } + case ENTITY_TYPE_VEHICLE: + { + ((CVehicle *)victim)->InflictDamage(shooter, m_eWeaponType, info->m_nDamage); + + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point->point, point->normal*0.05f); + +#ifndef FIX_BUGS + CVector dist = point->point - (*source); + CVector offset = dist - Max(0.2f*dist.Magnitude(), 0.5f) * CVector(ahead.x, ahead.y, 0.0f); + CVector smokePos = *source + offset; +#else + CVector smokePos = point->point; +#endif // !FIX_BUGS + + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, smokePos, CVector(0.0f, 0.0f, 0.0f)); + + if ( shooter->IsPed() ) + { + CPed *shooterPed = (CPed *)shooter; + + if ( shooterPed->bNotAllowedToDuck ) + { + if ( shooterPed->bKindaStayInSamePlace && victim != shooterPed->m_pPointGunAt ) + { + shooterPed->bKindaStayInSamePlace = false; + shooterPed->m_duckAndCoverTimer = CTimer::GetTimeInMilliseconds() + 15000; + } + } + } + + break; + } + case ENTITY_TYPE_OBJECT: + { + for ( int32 i = 0; i < 8; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point->point, point->normal*0.05f); + + CObject *victimObject = (CObject *)victim; + + if ( !victimObject->bInfiniteMass ) + { + if ( victimObject->GetIsStatic() && victimObject->m_fUprootLimit <= 0.0f ) + { + victimObject->SetIsStatic(false); + victimObject->AddToMovingList(); + } + + if ( !victimObject->GetIsStatic()) + { + CVector moveForce = point->normal*-4.0f; + victimObject->ApplyMoveForce(moveForce.x, moveForce.y, moveForce.z); + } + } + + break; + } + default: break; + } + } + + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_1, point->point); + break; + } + case ENTITY_TYPE_VEHICLE: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_2, point->point); + break; + } + case ENTITY_TYPE_DUMMY: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_3, point->point); + break; + } + default: break; + } + } + else + CBulletTraces::AddTrace(source, target); + + if ( shooter == FindPlayerPed() ) + CPad::GetPad(0)->StartShake_Distance(240, 128, FindPlayerPed()->GetPosition().x, FindPlayerPed()->GetPosition().y, FindPlayerPed()->GetPosition().z); + + BlowUpExplosiveThings(victim); +} + +bool +CWeapon::FireShotgun(CEntity *shooter, CVector *fireSource) +{ + ASSERT(shooter!=nil); + ASSERT(fireSource!=nil); + + CWeaponInfo *info = GetInfo(); + + float heading = RADTODEG(shooter->GetForward().Heading()); + float angle = DEGTORAD(heading); + + CVector2D rotOffset(-Sin(angle), Cos(angle)); + rotOffset.Normalise(); + + CVector gunflashPos = *fireSource; + gunflashPos += CVector(rotOffset.x*0.1f, rotOffset.y*0.1f, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f); + gunflashPos += CVector(rotOffset.x*0.1f, rotOffset.y*0.1f, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.15f); + gunflashPos += CVector(rotOffset.x*0.1f, rotOffset.y*0.1f, 0.0f); + CParticle::AddParticle(PARTICLE_GUNFLASH, gunflashPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.2f); + CParticle::AddParticle(PARTICLE_GUNFLASH, *fireSource, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f); + + CVector gunsmokePos = *fireSource; + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(rotOffset.x*0.10f, rotOffset.y*0.10f, 0.0f), nil, 0.1f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(rotOffset.x*0.15f, rotOffset.y*0.15f, 0.0f), nil, 0.1f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(rotOffset.x*0.20f, rotOffset.y*0.20f, 0.0f), nil, 0.1f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(rotOffset.x*0.25f, rotOffset.y*0.25f, 0.0f), nil, 0.1f); + + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, shooter, (CPed*)shooter, 1000); + + CPointLights::AddLight(CPointLights::LIGHT_POINT, *fireSource, CVector(0.0, 0.0, 0.0), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + float shooterAngle; + + if ( shooter->IsPed() && ((CPed*)shooter)->m_pPointGunAt != nil ) + { + CEntity *threatAttack = ((CPed*)shooter)->m_pPointGunAt; + shooterAngle = CGeneral::GetAngleBetweenPoints(threatAttack->GetPosition().x, threatAttack->GetPosition().y, + (*fireSource).x, (*fireSource).y); + } + else + shooterAngle = RADTODEG(shooter->GetForward().Heading()); + + + for ( int32 i = 0; i < 5; i++ ) // five shoots at once + { + float shootAngle = DEGTORAD(7.5f*i + shooterAngle - 15.0f); + CVector2D shootRot(-Sin(shootAngle), Cos(shootAngle)); + shootRot.Normalise(); + + CVector source, target; + CColPoint point; + CEntity *victim; + + if ( shooter == FindPlayerPed() && TheCamera.Cams[0].Using3rdPersonMouseCam() ) + { + CVector Left; +#ifdef FREE_CAM + if (CCamera::bFreeCam) { + CPlayerPed* shooterPed = (CPlayerPed*)shooter; + Find3rdPersonCamTargetVectorFromCachedVectors(1.0f, *fireSource, source, target, shooterPed->m_cachedCamSource, shooterPed->m_cachedCamFront, shooterPed->m_cachedCamUp); + Left = CrossProduct(shooterPed->m_cachedCamFront, shooterPed->m_cachedCamUp); + } + else +#endif + { + TheCamera.Find3rdPersonCamTargetVector(1.0f, *fireSource, source, target); + Left = CrossProduct(TheCamera.Cams[TheCamera.ActiveCam].Front, TheCamera.Cams[TheCamera.ActiveCam].Up); + } + + float f = float(i - 2) * (DEGTORAD(7.5f) / 2); + target = f * Left + target - source; + target *= info->m_fRange; + target += source; + + ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + } + else + { + target = *fireSource; + target.x += shootRot.x * info->m_fRange; + target.y += shootRot.y * info->m_fRange; + + if ( shooter->IsPed() ) + { + CPed *shooterPed = (CPed *)shooter; + + if ( shooterPed->m_pPointGunAt == nil ) + DoDoomAiming(shooter, fireSource, &target); + else + { + float distToTarget = (shooterPed->m_pPointGunAt->GetPosition() - (*fireSource)).Magnitude2D(); + target.z += info->m_fRange / distToTarget * (shooterPed->m_pPointGunAt->GetPosition().z - target.z); + } + } + + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + } + + if ( victim ) + { + CGlass::WasGlassHitByBullet(victim, point.point); + + CBulletTraces::AddTrace(fireSource, &point.point); + + if ( victim->IsPed() ) + { + CPed *victimPed = (CPed *)victim; + if ( !victimPed->OnGround() && victim != shooter && victimPed->DoesLOSBulletHitPed(point) ) + { + bool cantStandup = true; + + CVector pos = victimPed->GetPosition(); + + CVector2D posOffset((*fireSource).x-pos.x, (*fireSource).y-pos.y); + int32 localDir = victimPed->GetLocalDirection(posOffset); + + victimPed->ReactToAttack(FindPlayerPed()); + + posOffset.Normalise(); + + if ( victimPed->m_getUpTimer > (CTimer::GetTimeInMilliseconds() - 3000) ) + cantStandup = false; + + if ( victimPed->bIsStanding && cantStandup ) + { + victimPed->bIsStanding = false; + + victimPed->ApplyMoveForce(posOffset.x*-6.0f, posOffset.y*-6.0f, 5.0f); + } + else + victimPed->ApplyMoveForce(posOffset.x*-2.0f, posOffset.y*-2.0f, 0.0f); + + if ( cantStandup ) + victimPed->SetFall(1500, AnimationId(ANIM_STD_HIGHIMPACT_FRONT + localDir), false); + + victimPed->InflictDamage(shooter, m_eWeaponType, info->m_nDamage, (ePedPieceTypes)point.pieceB, localDir); + + if ( victimPed->m_nPedType == PEDTYPE_COP ) + CEventList::RegisterEvent(EVENT_SHOOT_COP, EVENT_ENTITY_PED, victim, (CPed*)shooter, 10000); + else + CEventList::RegisterEvent(EVENT_SHOOT_PED, EVENT_ENTITY_PED, victim, (CPed*)shooter, 10000); + + if ( CGame::nastyGame ) + { + uint8 bloodAmount = 8; + if ( m_eWeaponType == WEAPONTYPE_SHOTGUN ) + bloodAmount = 32; + + CVector dir = (point.point - victim->GetPosition()) * 0.01f; + dir.z = 0.01f; + + if ( victimPed->GetIsOnScreen() ) + { + for ( uint8 i = 0; i < bloodAmount; i++ ) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point.point, dir); + } + } + } + } + else + { + switch ( victim->GetType() ) + { + case ENTITY_TYPE_VEHICLE: + { + ((CVehicle *)victim)->InflictDamage(shooter, m_eWeaponType, info->m_nDamage); + + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal*0.05f); + +#ifndef FIX_BUGS + CVector dist = point.point - (*fireSource); + CVector offset = dist - Max(0.2f*dist.Magnitude(), 2.0f) * CVector(shootRot.x, shootRot.y, 0.0f); + CVector smokePos = *fireSource + offset; +#else + CVector smokePos = point.point; +#endif + + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, smokePos, CVector(0.0f, 0.0f, 0.0f)); + + break; + } + + case ENTITY_TYPE_BUILDING: + case ENTITY_TYPE_OBJECT: + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal*0.05f); + +#ifndef FIX_BUGS + CVector dist = point.point - (*fireSource); + CVector offset = dist - Max(0.2f*dist.Magnitude(), 2.0f) * CVector(shootRot.x, shootRot.y, 0.0f); + CVector smokePos = *fireSource + offset; +#else + CVector smokePos = point.point; +#endif + + smokePos.x += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + smokePos.y += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + smokePos.z += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, smokePos, CVector(0.0f, 0.0f, 0.0f)); + + if ( victim->IsObject() ) + { + CObject *victimObject = (CObject *)victim; + + if ( !victimObject->bInfiniteMass ) + { + if ( victimObject->GetIsStatic() && victimObject->m_fUprootLimit <= 0.0f ) + { + victimObject->SetIsStatic(false); + victimObject->AddToMovingList(); + } + + if ( !victimObject->GetIsStatic()) + { + CVector moveForce = point.normal*-5.0f; + victimObject->ApplyMoveForce(moveForce.x, moveForce.y, moveForce.z); + } + } + } + + break; + } + default: break; + } + } + + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_1, point.point); + break; + } + case ENTITY_TYPE_VEHICLE: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_2, point.point); + break; + } + case ENTITY_TYPE_DUMMY: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_3, point.point); + break; + } + default: break; + } + } + else + { + CVector traceTarget = *fireSource; + traceTarget += (target - (*fireSource)) * Min(info->m_fRange, 30.0f) / info->m_fRange; + CBulletTraces::AddTrace(fireSource, &traceTarget); + } + } + + if ( shooter == FindPlayerPed() ) + CPad::GetPad(0)->StartShake_Distance(240, 128, FindPlayerPed()->GetPosition().x, FindPlayerPed()->GetPosition().y, FindPlayerPed()->GetPosition().z); + + return true; +} + +bool +CWeapon::FireProjectile(CEntity *shooter, CVector *fireSource, float power) +{ + ASSERT(shooter!=nil); + ASSERT(fireSource!=nil); + + CVector source, target; + + if ( m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER ) + { + source = *fireSource; + + if ( shooter->IsPed() && ((CPed*)shooter)->IsPlayer() ) + { + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if (!( mode == CCam::MODE_M16_1STPERSON + || mode == CCam::MODE_SNIPER + || mode == CCam::MODE_ROCKETLAUNCHER + || mode == CCam::MODE_M16_1STPERSON_RUNABOUT + || mode == CCam::MODE_SNIPER_RUNABOUT + || mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT) ) + { + return false; + } + + *fireSource += TheCamera.Cams[TheCamera.ActiveCam].Front; + } + else + *fireSource += shooter->GetForward(); + + target = *fireSource; + } + else + { + float dot = DotProduct(*fireSource-shooter->GetPosition(), shooter->GetForward()); + + if ( dot < 0.3f ) + *fireSource += (0.3f-dot) * shooter->GetForward(); + + target = *fireSource; + + if ( target.z - shooter->GetPosition().z > 0.0f ) + target += 0.6f*shooter->GetForward(); + + source = *fireSource - shooter->GetPosition(); + + source = *fireSource - DotProduct(source, shooter->GetForward()) * shooter->GetForward(); + } + + if ( !CWorld::GetIsLineOfSightClear(source, target, true, true, false, true, false, false, false) ) + { + if ( m_eWeaponType != WEAPONTYPE_GRENADE ) + CProjectileInfo::RemoveNotAdd(shooter, m_eWeaponType, *fireSource); + else + { + if ( shooter->IsPed() ) + { + source = shooter->GetPosition() - shooter->GetForward(); + source.z -= 0.4f; + + if ( !CWorld::TestSphereAgainstWorld(source, 0.5f, nil, false, false, true, false, false, false) ) + CProjectileInfo::AddProjectile(shooter, m_eWeaponType, source, 0.0f); + else + CProjectileInfo::RemoveNotAdd(shooter, m_eWeaponType, *fireSource); + } + } + } + else + CProjectileInfo::AddProjectile(shooter, m_eWeaponType, *fireSource, power); + + return true; +} + +void +CWeapon::GenerateFlameThrowerParticles(CVector pos, CVector dir) +{ + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); + + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); + + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); + + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); + + dir *= 0.7f; + CParticle::AddParticle(PARTICLE_FIREBALL, pos, dir); +} + +bool +CWeapon::FireAreaEffect(CEntity *shooter, CVector *fireSource) +{ + ASSERT(shooter!=nil); + ASSERT(fireSource!=nil); + + CWeaponInfo *info = GetInfo(); + + float heading = RADTODEG(shooter->GetForward().Heading()); + + CVector source; + CVector target; + CVector dir; + + if ( shooter == FindPlayerPed() && TheCamera.Cams[0].Using3rdPersonMouseCam() ) + { +#ifdef FREE_CAM + if (CCamera::bFreeCam) { + CPlayerPed *shooterPed = (CPlayerPed*)shooter; + Find3rdPersonCamTargetVectorFromCachedVectors(info->m_fRange, *fireSource, source, target, shooterPed->m_cachedCamSource, shooterPed->m_cachedCamFront, shooterPed->m_cachedCamUp); + } + else +#endif + { + TheCamera.Find3rdPersonCamTargetVector(info->m_fRange, *fireSource, source, target); + } + float norm = (1.0f / info->m_fRange); + dir = (target - source) * norm; + } + else + { + float angle = DEGTORAD(heading); + dir = CVector(-Sin(angle)*0.5f, Cos(angle)*0.5f, 0.0f); + target = *fireSource + dir; + } + + CShotInfo::AddShot(shooter, m_eWeaponType, *fireSource, target); + CWeapon::GenerateFlameThrowerParticles(*fireSource, dir); + + return true; +} + +bool +CWeapon::FireSniper(CEntity *shooter) +{ + ASSERT(shooter!=nil); + + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if (!( mode == CCam::MODE_M16_1STPERSON + || mode == CCam::MODE_SNIPER + || mode == CCam::MODE_ROCKETLAUNCHER + || mode == CCam::MODE_M16_1STPERSON_RUNABOUT + || mode == CCam::MODE_SNIPER_RUNABOUT + || mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT) ) + { + return false; + } + +#ifndef FIX_BUGS + CWeaponInfo *info = GetInfo(); //unused +#endif + + CCam *cam = &TheCamera.Cams[TheCamera.ActiveCam]; + ASSERT(cam!=nil); + + CVector source = cam->Source; + CVector dir = cam->Front; + + if ( DotProduct(dir, CVector(0.0f, -0.9894f, 0.145f)) > 0.997f ) + CCoronas::bSmallMoon = !CCoronas::bSmallMoon; + + dir.Normalise(); + dir *= 16.0f; + + CBulletInfo::AddBullet(shooter, m_eWeaponType, source, dir); + + if ( shooter == FindPlayerPed() ) + CStats::InstantHitsFiredByPlayer++; + + if ( shooter == FindPlayerPed() ) + { + CPad::GetPad(0)->StartShake_Distance(240, 128, + FindPlayerPed()->GetPosition().x, + FindPlayerPed()->GetPosition().y, + FindPlayerPed()->GetPosition().z); + + CamShakeNoPos(&TheCamera, 0.2f); + } + + return true; +} + +bool +CWeapon::FireM16_1stPerson(CEntity *shooter) +{ + ASSERT(shooter!=nil); + + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + + if (!( mode == CCam::MODE_M16_1STPERSON + || mode == CCam::MODE_SNIPER + || mode == CCam::MODE_ROCKETLAUNCHER + || mode == CCam::MODE_M16_1STPERSON_RUNABOUT + || mode == CCam::MODE_SNIPER_RUNABOUT + || mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT + || mode == CCam::MODE_HELICANNON_1STPERSON) ) + { + return false; + } + + CWeaponInfo *info = GetInfo(); + + CColPoint point; + CEntity *victim; + + CWorld::bIncludeCarTyres = true; + CWorld::pIgnoreEntity = shooter; + CWorld::bIncludeDeadPeds = true; + + CCam *cam = &TheCamera.Cams[TheCamera.ActiveCam]; + ASSERT(cam!=nil); + + CVector source = cam->Source; + CVector target = cam->Front*info->m_fRange + source; + + ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + CWorld::bIncludeDeadPeds = false; + CWorld::pIgnoreEntity = nil; + CWorld::bIncludeCarTyres = false; + + CVector2D front(cam->Front.x, cam->Front.y); + front.Normalise(); + + DoBulletImpact(shooter, victim, &source, &target, &point, front); + + CVector bulletPos; + if ( CHeli::TestBulletCollision(&source, &target, &bulletPos, 4) ) + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, bulletPos, CVector(0.0f, 0.0f, 0.0f)); + } + + if ( shooter == FindPlayerPed() ) + { +#ifdef FIX_BUGS + CStats::InstantHitsFiredByPlayer++; +#endif + CPad::GetPad(0)->StartShake_Distance(240, 128, FindPlayerPed()->GetPosition().x, FindPlayerPed()->GetPosition().y, FindPlayerPed()->GetPosition().z); + + if ( m_eWeaponType == WEAPONTYPE_M16 ) + { + TheCamera.Cams[TheCamera.ActiveCam].Beta += float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0003f; + TheCamera.Cams[TheCamera.ActiveCam].Alpha += float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0003f; + } + else if ( m_eWeaponType == WEAPONTYPE_HELICANNON ) + { + TheCamera.Cams[TheCamera.ActiveCam].Beta += float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0001f; + TheCamera.Cams[TheCamera.ActiveCam].Alpha += float((CGeneral::GetRandomNumber() & 127) - 64) * 0.0001f; + } + } + + return true; +} + +bool +CWeapon::FireInstantHitFromCar(CAutomobile *shooter, bool left) +{ + CWeaponInfo *info = GetInfo(); + + CVehicleModelInfo *modelInfo = shooter->GetModelInfo(); + + CVector source, target; + if ( left ) + { + source = shooter->GetMatrix() * CVector(-shooter->GetColModel()->boundingBox.max.x + -0.2f, + float(CGeneral::GetRandomNumber() & 255) * 0.001f + modelInfo->GetFrontSeatPosn().y, + modelInfo->GetFrontSeatPosn().z + 0.5f); + source += CTimer::GetTimeStep() * shooter->m_vecMoveSpeed; + + + target = shooter->GetMatrix() * CVector(-info->m_fRange, + modelInfo->GetFrontSeatPosn().y, + modelInfo->GetFrontSeatPosn().z + 0.5f); + } + else + { + source = shooter->GetMatrix() * CVector(shooter->GetColModel()->boundingBox.max.x + 0.2f, + float(CGeneral::GetRandomNumber() & 255) * 0.001f + modelInfo->GetFrontSeatPosn().y, + modelInfo->GetFrontSeatPosn().z + 0.5f); + source += CTimer::GetTimeStep() * shooter->m_vecMoveSpeed; + + target = shooter->GetMatrix() * CVector(info->m_fRange, + modelInfo->GetFrontSeatPosn().y, + modelInfo->GetFrontSeatPosn().z + 0.5f); + } + #undef FRONTSEATPOS + + if ( TheCamera.GetLookingLRBFirstPerson() && !left ) + { + source -= 0.3f * shooter->GetForward(); + target -= 0.3f * shooter->GetForward(); + } + + target += CVector(float(CGeneral::GetRandomNumber()&255)*0.01f-1.28f, + float(CGeneral::GetRandomNumber()&255)*0.01f-1.28f, + float(CGeneral::GetRandomNumber()&255)*0.01f-1.28f); + + DoDriveByAutoAiming(FindPlayerPed(), &source, &target); + + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, FindPlayerPed(), FindPlayerPed(), 1000); + + if ( !TheCamera.GetLookingLRBFirstPerson() ) + CParticle::AddParticle(PARTICLE_GUNFLASH, source, CVector(0.0f, 0.0f, 0.0f)); + else + CamShakeNoPos(&TheCamera, 0.01f); + + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_VEHICLE, shooter, FindPlayerPed(), 1000); + + CPointLights::AddLight(CPointLights::LIGHT_POINT, source, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CColPoint point; + CEntity *victim; + ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + + if ( !(CTimer::GetFrameCounter() & 3) ) + MakePedsJumpAtShot(shooter, &source, &target); + + if ( victim ) + { + CVector traceTarget = point.point; + CBulletTraces::AddTrace(&source, &traceTarget); + + if ( victim->IsPed() ) + { + CPed *victimPed = (CPed*)victim; + + if ( !victimPed->DyingOrDead() && victim != (CEntity *)shooter ) + { + CVector pos = victimPed->GetPosition(); + + CVector2D posOffset(source.x-pos.x, source.y-pos.y); + int32 localDir = victimPed->GetLocalDirection(posOffset); + + victimPed->ReactToAttack(FindPlayerPed()); + victimPed->ClearAttackByRemovingAnim(); + + CAnimBlendAssociation *asoc = CAnimManager::AddAnimation(victimPed->GetClump(), ASSOCGRP_STD, AnimationId(ANIM_STD_HITBYGUN_FRONT + localDir)); + ASSERT(asoc!=nil); + asoc->blendAmount = 0.0f; + asoc->blendDelta = 8.0f; + + victimPed->InflictDamage(shooter, WEAPONTYPE_UZI_DRIVEBY, 3*info->m_nDamage, (ePedPieceTypes)point.pieceB, localDir); + + pos.z += 0.8f; + + if ( victimPed->GetIsOnScreen() ) + { + if ( CGame::nastyGame ) + { + for ( int32 i = 0; i < 4; i++ ) + { + CVector dir; + dir.x = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + dir.y = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + dir.z = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + + CParticle::AddParticle(PARTICLE_BLOOD, pos, dir); + } + } + } + + if ( victimPed->m_nPedType == PEDTYPE_COP ) + CEventList::RegisterEvent(EVENT_SHOOT_COP, EVENT_ENTITY_PED, victimPed, FindPlayerPed(), 10000); + else + CEventList::RegisterEvent(EVENT_SHOOT_PED, EVENT_ENTITY_PED, victimPed, FindPlayerPed(), 10000); + } + } + else if ( victim->IsVehicle() ) + ((CVehicle *)victim)->InflictDamage(FindPlayerPed(), WEAPONTYPE_UZI_DRIVEBY, info->m_nDamage); + else + CGlass::WasGlassHitByBullet(victim, point.point); + + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_1, point.point); + break; + } + case ENTITY_TYPE_VEHICLE: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_2, point.point); + break; + } + case ENTITY_TYPE_DUMMY: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_3, point.point); + break; + } + default: break; + } + } + else + { + float norm = 30.0f/info->m_fRange; + CVector traceTarget = (target-source)*norm + source; + CBulletTraces::AddTrace(&source, &traceTarget); + } + + if ( shooter == FindPlayerVehicle() ) + CPad::GetPad(0)->StartShake_Distance(240, 128, FindPlayerVehicle()->GetPosition().x, FindPlayerVehicle()->GetPosition().y, FindPlayerVehicle()->GetPosition().z); + + return true; +} + +void +CWeapon::DoDoomAiming(CEntity *shooter, CVector *source, CVector *target) +{ + ASSERT(shooter!=nil); + ASSERT(source!=nil); + ASSERT(target !=nil); + +#ifndef FIX_BUGS + CEntity entity; // unused +#endif + + CPed *shooterPed = (CPed*)shooter; + if ( shooterPed->IsPed() && shooterPed->bCrouchWhenShooting ) + return; + + int16 lastEntity; + CEntity *entities[16]; + CWorld::FindObjectsInRange(*source, (*target-*source).Magnitude(), true, &lastEntity, 15, entities, false, true, true, false, false); + + float closestEntityDist = 10000.0f; + int16 closestEntity; + + for ( int32 i = 0; i < lastEntity; i++ ) + { + CEntity *victim = entities[i]; + ASSERT(victim!=nil); + + if ( (CEntity*)shooterPed != victim && shooterPed->CanSeeEntity(victim, DEGTORAD(22.5f)) ) + { + if ( !(victim->GetStatus() == STATUS_TRAIN_MOVING + || victim->GetStatus() == STATUS_TRAIN_NOT_MOVING + || victim->GetStatus() == STATUS_HELI + || victim->GetStatus() == STATUS_PLANE) ) + { + float distToVictim = (shooterPed->GetPosition()-victim->GetPosition()).Magnitude2D(); + float distToVictimZ = Abs(shooterPed->GetPosition().z-victim->GetPosition().z); + + if ( 1.5f*distToVictimZ < distToVictim ) + { + float entityDist = Sqrt(SQR(distToVictim) + SQR(distToVictimZ)); + + if ( entityDist < closestEntityDist ) + { + closestEntityDist = entityDist; + closestEntity = i; + } + } + } + } + } + + if ( closestEntityDist < DOOMAUTOAIMING_MAXDIST ) + { + CEntity *victim = entities[closestEntity]; + ASSERT(victim !=nil); + + float distToTarget = (*target - *source).Magnitude2D(); + float distToSource = (victim->GetPosition() - *source).Magnitude2D(); + + float victimZ = victim->GetPosition().z + 0.3f; + if ( victim->IsPed() ) + { + if ( ((CPed*)victim)->bIsDucking ) + victimZ -= 0.8f; + } + + (*target).z = (distToTarget / distToSource) * (victimZ - (*source).z) + (*source).z; + } +} + +void +CWeapon::DoTankDoomAiming(CEntity *shooter, CEntity *driver, CVector *source, CVector *target) +{ + ASSERT(shooter!=nil); + ASSERT(driver!=nil); + ASSERT(source!=nil); + ASSERT(target!=nil); + +#ifndef FIX_BUGS + CEntity entity; // unused +#endif + + int16 lastEntity; + CEntity *entities[16]; + CWorld::FindObjectsInRange(*source, (*target-*source).Magnitude(), true, &lastEntity, 15, entities, false, true, false, false, false); + + float closestEntityDist = 10000.0f; + int16 closestEntity; + + float normZ = (target->z - source->z) / (*target-*source).Magnitude(); + + for ( int32 i = 0; i < lastEntity; i++ ) + { + CEntity *victim = entities[i]; + + ASSERT(victim!=nil); + + if ( shooter != victim && driver != victim ) + { + if ( !(victim->GetStatus() == STATUS_TRAIN_MOVING + || victim->GetStatus() == STATUS_TRAIN_NOT_MOVING + || victim->GetStatus() == STATUS_HELI + || victim->GetStatus() == STATUS_PLANE) ) + { + if ( !(victim->IsVehicle() && victim->bRenderScorched) ) + { + float distToVictim = (shooter->GetPosition()-victim->GetPosition()).Magnitude2D(); + float distToVictimZ = Abs(shooter->GetPosition().z - (distToVictim*normZ + victim->GetPosition().z)); + + if ( 3.0f*distToVictimZ < distToVictim ) + { + CVector tmp = CVector(victim->GetPosition().x, victim->GetPosition().y, 0.0f); + if ( CCollision::DistToLine(source, target, + &tmp) < victim->GetBoundRadius()*3.0f ) + { + float vehicleDist = Sqrt(SQR(distToVictim) + SQR(distToVictimZ)); + if ( vehicleDist < closestEntityDist ) + { + closestEntityDist = vehicleDist; + closestEntity = i; + } + } + } + } + } + } + } + + if ( closestEntityDist < DOOMAUTOAIMING_MAXDIST ) + { + CEntity *victim = entities[closestEntity]; + ASSERT(victim!=nil); + + float distToTarget = (*target - *source).Magnitude2D(); + float distToSource = (victim->GetPosition() - *source).Magnitude2D(); + + (*target).z = (distToTarget / distToSource) * (0.3f + victim->GetPosition().z - (*source).z) + (*source).z; + } +} + +void +CWeapon::DoDriveByAutoAiming(CEntity *shooter, CVector *source, CVector *target) +{ + ASSERT(shooter!=nil); + ASSERT(source!=nil); + ASSERT(target!=nil); + +#ifndef FIX_BUGS + CEntity entity; // unused +#endif + + CPed *shooterPed = (CPed*)shooter; + if ( shooterPed->IsPed() && shooterPed->bCrouchWhenShooting ) + return; + + int16 lastEntity; + CEntity *entities[16]; + CWorld::FindObjectsInRange(*source, (*target-*source).Magnitude(), true, &lastEntity, 15, entities, false, false, true, false, false); + + float closestEntityDist = 10000.0f; + int16 closestEntity; + + for ( int32 i = 0; i < lastEntity; i++ ) + { + CEntity *victim = entities[i]; + ASSERT(victim!=nil); + + if ( shooter != victim ) + { + float lineDist = CCollision::DistToLine(source, target, &victim->GetPosition()); + float distToVictim = (victim->GetPosition() - shooter->GetPosition()).Magnitude(); + float pedDist = 0.15f*distToVictim + lineDist; + + if ( DotProduct((*target-*source), victim->GetPosition()-*source) > 0.0f && pedDist < closestEntityDist) + { + closestEntity = i; + closestEntityDist = pedDist; + } + } + } + + if ( closestEntityDist < DRIVEBYAUTOAIMING_MAXDIST ) + { + CEntity *victim = entities[closestEntity]; + ASSERT(victim!=nil); + + float distToTarget = (*source - *target).Magnitude(); + float distToSource = (*source - victim->GetPosition()).Magnitude(); + *target = (distToTarget / distToSource) * (victim->GetPosition() - *source) + *source; + } +} + +void +CWeapon::Reload(void) +{ + if (m_nAmmoTotal == 0) + return; + + CWeaponInfo *info = GetInfo(); + + if (m_nAmmoTotal >= info->m_nAmountofAmmunition) + m_nAmmoInClip = info->m_nAmountofAmmunition; + else + m_nAmmoInClip = m_nAmmoTotal; +} + +void +CWeapon::Update(int32 audioEntity) +{ + switch ( m_eWeaponState ) + { + case WEAPONSTATE_MELEE_MADECONTACT: + { + m_eWeaponState = WEAPONSTATE_READY; + break; + } + + case WEAPONSTATE_FIRING: + { + if ( m_eWeaponType == WEAPONTYPE_SHOTGUN && AEHANDLE_IS_OK(audioEntity) ) + { + uint32 timePassed = m_nTimer - gReloadSampleTime[WEAPONTYPE_SHOTGUN]; + if ( CTimer::GetPreviousTimeInMilliseconds() < timePassed && CTimer::GetTimeInMilliseconds() >= timePassed ) + DMAudio.PlayOneShot(audioEntity, SOUND_WEAPON_RELOAD, 0.0f); + } + + if ( CTimer::GetTimeInMilliseconds() > m_nTimer ) + { + if ( GetInfo()->m_eWeaponFire != WEAPON_FIRE_MELEE && m_nAmmoTotal == 0 ) + m_eWeaponState = WEAPONSTATE_OUT_OF_AMMO; + else + m_eWeaponState = WEAPONSTATE_READY; + } + + break; + } + + case WEAPONSTATE_RELOADING: + { + if ( AEHANDLE_IS_OK(audioEntity) && m_eWeaponType < WEAPONTYPE_LAST_WEAPONTYPE ) + { + uint32 timePassed = m_nTimer - gReloadSampleTime[m_eWeaponType]; + if ( CTimer::GetPreviousTimeInMilliseconds() < timePassed && CTimer::GetTimeInMilliseconds() >= timePassed ) + DMAudio.PlayOneShot(audioEntity, SOUND_WEAPON_RELOAD, 0.0f); + } + + if ( CTimer::GetTimeInMilliseconds() > m_nTimer ) + { + Reload(); + m_eWeaponState = WEAPONSTATE_READY; + } + + break; + } + default: break; + } +} + +void +FireOneInstantHitRound(CVector *source, CVector *target, int32 damage) +{ + ASSERT(source!=nil); + ASSERT(target!=nil); + + CParticle::AddParticle(PARTICLE_GUNFLASH, *source, CVector(0.0f, 0.0f, 0.0f)); + + CPointLights::AddLight(CPointLights::LIGHT_POINT, + *source, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + + CColPoint point; + CEntity *victim; + CWorld::ProcessLineOfSight(*source, *target, point, victim, true, true, true, true, true, true, false); + + CParticle::AddParticle(PARTICLE_HELI_ATTACK, *source, ((*target) - (*source)) * 0.15f); + + if ( victim ) + { + if ( victim->IsPed() ) + { + CPed *victimPed = (CPed *)victim; + if ( !victimPed->DyingOrDead() ) + { + CVector pos = victimPed->GetPosition(); + + CVector2D posOffset((*source).x-pos.x, (*source).y-pos.y); + int32 localDir = victimPed->GetLocalDirection(posOffset); + + victimPed->ClearAttackByRemovingAnim(); + + CAnimBlendAssociation *asoc = CAnimManager::AddAnimation(victimPed->GetClump(), ASSOCGRP_STD, AnimationId(ANIM_STD_HITBYGUN_FRONT + localDir)); + ASSERT(asoc!=nil); + asoc->blendAmount = 0.0f; + asoc->blendDelta = 8.0f; + + victimPed->InflictDamage(nil, WEAPONTYPE_UZI, damage, (ePedPieceTypes)point.pieceB, localDir); + + pos.z += 0.8f; + + if ( victimPed->GetIsOnScreen() ) + { + if ( CGame::nastyGame ) + { + for ( int32 i = 0; i < 4; i++ ) + { + CVector dir; + dir.x = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + dir.y = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + dir.z = CGeneral::GetRandomNumberInRange(-0.1f, 0.1f); + + CParticle::AddParticle(PARTICLE_BLOOD, pos, dir); + } + } + } + } + } + else if ( victim->IsVehicle() ) + ((CVehicle *)victim)->InflictDamage(nil, WEAPONTYPE_UZI, damage); + //BUG ? no CGlass::WasGlassHitByBullet + + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_1, point.point); + CParticle::AddParticle(PARTICLE_SMOKE, point.point, CVector(0.0f, 0.0f, 0.01f)); + break; + } + case ENTITY_TYPE_VEHICLE: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_2, point.point); + break; + } + case ENTITY_TYPE_DUMMY: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_3, point.point); + break; + } + default: break; + } + } + else + { + float waterLevel; + if ( CWaterLevel::GetWaterLevel((*target).x, (*target).y, (*target).z + 10.0f, &waterLevel, false) ) + { + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, CVector((*target).x, (*target).y, waterLevel), CVector(0.0f, 0.0f, 0.01f)); + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_WATER, point.point); // no sound(empty) + } + } +} + +bool +CWeapon::IsTypeMelee(void) +{ + return m_eWeaponType == WEAPONTYPE_UNARMED || m_eWeaponType == WEAPONTYPE_BASEBALLBAT; +} + +bool +CWeapon::IsType2Handed(void) +{ + return m_eWeaponType >= WEAPONTYPE_SHOTGUN && m_eWeaponType <= WEAPONTYPE_FLAMETHROWER && m_eWeaponType != WEAPONTYPE_ROCKETLAUNCHER; +} + +void +CWeapon::MakePedsJumpAtShot(CPhysical *shooter, CVector *source, CVector *target) +{ + ASSERT(shooter!=nil); + ASSERT(source!=nil); + ASSERT(target!=nil); + + float minx = Min(source->x, target->x) - 2.0f; + float maxx = Max(source->x, target->x) + 2.0f; + float miny = Min(source->y, target->y) - 2.0f; + float maxy = Max(source->y, target->y) + 2.0f; + float minz = Min(source->z, target->z) - 2.0f; + float maxz = Max(source->z, target->z) + 2.0f; + + for ( int32 i = CPools::GetPedPool()->GetSize() - 1; i >= 0; i--) + { + CPed *ped = CPools::GetPedPool()->GetSlot(i); + + if ( ped ) + { + if ( ped->GetPosition().x > minx && ped->GetPosition().x < maxx + && ped->GetPosition().y > miny && ped->GetPosition().y < maxy + && ped->GetPosition().z > minz && ped->GetPosition().z < maxz ) + { + if ( ped != FindPlayerPed() && !((uint8)(ped->m_randomSeed ^ CGeneral::GetRandomNumber()) & 31) ) + ped->SetEvasiveDive(shooter, 1); + } + } + } +} + +bool +CWeapon::HitsGround(CEntity *holder, CVector *fireSource, CEntity *aimingTo) +{ + ASSERT(holder!=nil); + ASSERT(aimingTo!=nil); + + if (!holder->IsPed() || !((CPed*)holder)->m_pSeekTarget) + return false; + + CWeaponInfo *info = GetInfo(); + + CVector adjustedOffset = info->m_vecFireOffset; + adjustedOffset.z += 0.6f; + + CVector source, target; + CEntity *foundEnt = nil; + CColPoint foundCol; + + if (fireSource) + source = *fireSource; + else + source = holder->GetMatrix() * adjustedOffset; + + CEntity *aimEntity = aimingTo ? aimingTo : ((CPed*)holder)->m_pSeekTarget; + ASSERT(aimEntity!=nil); + + target = aimEntity->GetPosition(); + target.z += 0.6f; + + CWorld::ProcessLineOfSight(source, target, foundCol, foundEnt, true, false, false, false, false, false, false); + if (foundEnt && foundEnt->IsBuilding()) { + // That was supposed to be Magnitude, according to leftover code in assembly + float diff = (foundCol.point.z - source.z); + if (diff < 0.0f && diff > -3.0f) + return true; + } + + return false; +} + +void +CWeapon::BlowUpExplosiveThings(CEntity *thing) +{ +#ifdef FIX_BUGS + if ( thing && thing->IsObject() ) +#else + if ( thing ) +#endif + { + CObject *object = (CObject*)thing; + int32 mi = object->GetModelIndex(); + if ( IsExplosiveThingModel(mi) && !object->bHasBeenDamaged ) + { + object->bHasBeenDamaged = true; + + CExplosion::AddExplosion(object, FindPlayerPed(), EXPLOSION_BARREL, object->GetPosition()+CVector(0.0f,0.0f,0.5f), 100); + + if ( MI_EXPLODINGBARREL == mi ) + object->m_vecMoveSpeed.z += 0.75f; + else + object->m_vecMoveSpeed.z += 0.45f; + + object->m_vecMoveSpeed.x += float((CGeneral::GetRandomNumber()&255) - 128) * 0.0002f; + object->m_vecMoveSpeed.y += float((CGeneral::GetRandomNumber()&255) - 128) * 0.0002f; + + if ( object->GetIsStatic()) + { + object->SetIsStatic(false); + object->AddToMovingList(); + } + } + } +} + +bool +CWeapon::HasWeaponAmmoToBeUsed(void) +{ + switch (m_eWeaponType) { + case WEAPONTYPE_UNARMED: + case WEAPONTYPE_BASEBALLBAT: + return true; + default: + return m_nAmmoTotal != 0; + } +} + +bool +CPed::IsPedDoingDriveByShooting(void) +{ + if (FindPlayerPed() == this && GetWeapon()->m_eWeaponType == WEAPONTYPE_UZI) { + if (TheCamera.Cams[TheCamera.ActiveCam].LookingLeft || TheCamera.Cams[TheCamera.ActiveCam].LookingRight) + return true; + } + return false; +} + +bool +CWeapon::ProcessLineOfSight(CVector const &point1, CVector const &point2, CColPoint &point, CEntity *&entity, eWeaponType type, CEntity *shooter, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects) +{ + return CWorld::ProcessLineOfSight(point1, point2, point, entity, checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, ignoreSeeThrough, ignoreSomeObjects); +} + +#ifdef COMPATIBLE_SAVES +#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipSaveBuf(buf, sizeof(data)); +void +CWeapon::Save(uint8*& buf) +{ + CopyToBuf(buf, m_eWeaponType); + CopyToBuf(buf, m_eWeaponState); + CopyToBuf(buf, m_nAmmoInClip); + CopyToBuf(buf, m_nAmmoTotal); + CopyToBuf(buf, m_nTimer); + CopyToBuf(buf, m_bAddRotOffset); + ZeroSaveBuf(buf, 3); +} + +void +CWeapon::Load(uint8*& buf) +{ + CopyFromBuf(buf, m_eWeaponType); + CopyFromBuf(buf, m_eWeaponState); + CopyFromBuf(buf, m_nAmmoInClip); + CopyFromBuf(buf, m_nAmmoTotal); + CopyFromBuf(buf, m_nTimer); + CopyFromBuf(buf, m_bAddRotOffset); + SkipSaveBuf(buf, 3); +} + +#undef CopyFromBuf +#undef CopyToBuf +#endif diff --git a/src/weapons/Weapon.h b/src/weapons/Weapon.h new file mode 100644 index 0000000..c7685e0 --- /dev/null +++ b/src/weapons/Weapon.h @@ -0,0 +1,78 @@ +#pragma once + +#include "WeaponType.h" + +#define DRIVEBYAUTOAIMING_MAXDIST (2.5f) +#define DOOMAUTOAIMING_MAXDIST (9000.0f) + +class CEntity; +class CPhysical; +class CAutomobile; +struct CColPoint; +class CWeaponInfo; + +class CWeapon +{ +public: + eWeaponType m_eWeaponType; + eWeaponState m_eWeaponState; + int32 m_nAmmoInClip; + int32 m_nAmmoTotal; + uint32 m_nTimer; + bool m_bAddRotOffset; + + CWeapon() { + m_bAddRotOffset = false; + } + + CWeaponInfo *GetInfo(); + + static void InitialiseWeapons(void); + static void ShutdownWeapons (void); + static void UpdateWeapons (void); + + void Initialise(eWeaponType type, int32 ammo); + + bool Fire (CEntity *shooter, CVector *fireSource); + bool FireFromCar (CAutomobile *shooter, bool left); + bool FireMelee (CEntity *shooter, CVector &fireSource); + bool FireInstantHit(CEntity *shooter, CVector *fireSource); + + void AddGunshell (CEntity *shooter, CVector const &source, CVector2D const &direction, float size); + void DoBulletImpact(CEntity *shooter, CEntity *victim, CVector *source, CVector *target, CColPoint *point, CVector2D ahead); + + bool FireShotgun (CEntity *shooter, CVector *fireSource); + bool FireProjectile(CEntity *shooter, CVector *fireSource, float power); + + static void GenerateFlameThrowerParticles(CVector pos, CVector dir); + + bool FireAreaEffect (CEntity *shooter, CVector *fireSource); + bool FireSniper (CEntity *shooter); + bool FireM16_1stPerson (CEntity *shooter); + bool FireInstantHitFromCar(CAutomobile *shooter, bool left); + + static void DoDoomAiming (CEntity *shooter, CVector *source, CVector *target); + static void DoTankDoomAiming (CEntity *shooter, CEntity *driver, CVector *source, CVector *target); + static void DoDriveByAutoAiming(CEntity *shooter, CVector *source, CVector *target); + + void Reload(void); + void Update(int32 audioEntity); + bool IsTypeMelee (void); + bool IsType2Handed(void); + + static void MakePedsJumpAtShot(CPhysical *shooter, CVector *source, CVector *target); + + bool HitsGround(CEntity *holder, CVector *fireSource, CEntity *aimingTo); + static void BlowUpExplosiveThings(CEntity *thing); + bool HasWeaponAmmoToBeUsed(void); + + static bool ProcessLineOfSight(CVector const &point1, CVector const &point2, CColPoint &point, CEntity *&entity, eWeaponType type, CEntity *shooter, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies, bool ignoreSeeThrough, bool ignoreSomeObjects); + +#ifdef COMPATIBLE_SAVES + void Save(uint8*& buf); + void Load(uint8*& buf); +#endif +}; +VALIDATE_SIZE(CWeapon, 0x18); + +void FireOneInstantHitRound(CVector *source, CVector *target, int32 damage); \ No newline at end of file diff --git a/src/weapons/WeaponEffects.cpp b/src/weapons/WeaponEffects.cpp new file mode 100644 index 0000000..32e55fb --- /dev/null +++ b/src/weapons/WeaponEffects.cpp @@ -0,0 +1,104 @@ +#include "common.h" + +#include "main.h" +#include "WeaponEffects.h" +#include "TxdStore.h" +#include "Sprite.h" + +RwTexture *gpCrossHairTex; +RwRaster *gpCrossHairRaster; + +CWeaponEffects gCrossHair; + +CWeaponEffects::CWeaponEffects() +{ + +} + +CWeaponEffects::~CWeaponEffects() +{ + +} + +void +CWeaponEffects::Init(void) +{ + gCrossHair.m_bActive = false; + gCrossHair.m_vecPos = CVector(0.0f, 0.0f, 0.0f); + gCrossHair.m_nRed = 0; + gCrossHair.m_nGreen = 0; + gCrossHair.m_nBlue = 0; + gCrossHair.m_nAlpha = 255; + gCrossHair.m_fSize = 1.0f; + gCrossHair.m_fRotation = 0.0f; + + + CTxdStore::PushCurrentTxd(); + int32 slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + + gpCrossHairTex = RwTextureRead("crosshair", nil); + gpCrossHairRaster = RwTextureGetRaster(gpCrossHairTex); + + CTxdStore::PopCurrentTxd(); +} + +void +CWeaponEffects::Shutdown(void) +{ + RwTextureDestroy(gpCrossHairTex); +#if GTA_VERSION >= GTA3_PC_11 + gpCrossHairTex = nil; +#endif +} + +void +CWeaponEffects::MarkTarget(CVector pos, uint8 red, uint8 green, uint8 blue, uint8 alpha, float size) +{ + gCrossHair.m_bActive = true; + gCrossHair.m_vecPos = pos; + gCrossHair.m_nRed = red; + gCrossHair.m_nGreen = green; + gCrossHair.m_nBlue = blue; + gCrossHair.m_nAlpha = alpha; + gCrossHair.m_fSize = size; +} + +void +CWeaponEffects::ClearCrossHair(void) +{ + gCrossHair.m_bActive = false; +} + +void +CWeaponEffects::Render(void) +{ + if ( gCrossHair.m_bActive ) + { + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpCrossHairRaster); + + RwV3d pos; + float w, h; + if ( CSprite::CalcScreenCoors(gCrossHair.m_vecPos, &pos, &w, &h, true) ) + { + PUSH_RENDERGROUP("CWeaponEffects::Render"); + + float recipz = 1.0f / pos.z; + CSprite::RenderOneXLUSprite(pos.x, pos.y, pos.z, + gCrossHair.m_fSize * w, gCrossHair.m_fSize * h, + gCrossHair.m_nRed, gCrossHair.m_nGreen, gCrossHair.m_nBlue, 255, + recipz, 255); + + POP_RENDERGROUP(); + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + } +} \ No newline at end of file diff --git a/src/weapons/WeaponEffects.h b/src/weapons/WeaponEffects.h new file mode 100644 index 0000000..f6592b3 --- /dev/null +++ b/src/weapons/WeaponEffects.h @@ -0,0 +1,26 @@ +#pragma once + +class CWeaponEffects +{ +public: + bool m_bActive; + CVector m_vecPos; + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + uint8 m_nAlpha; + float m_fSize; + float m_fRotation; + +public: + CWeaponEffects(); + ~CWeaponEffects(); + + static void Init(void); + static void Shutdown(void); + static void MarkTarget(CVector pos, uint8 red, uint8 green, uint8 blue, uint8 alpha, float size); + static void ClearCrossHair(void); + static void Render(void); +}; + +VALIDATE_SIZE(CWeaponEffects, 0x1C); \ No newline at end of file diff --git a/src/weapons/WeaponInfo.cpp b/src/weapons/WeaponInfo.cpp new file mode 100644 index 0000000..ba87245 --- /dev/null +++ b/src/weapons/WeaponInfo.cpp @@ -0,0 +1,189 @@ +#include "common.h" + +#include "main.h" +#include "FileMgr.h" +#include "WeaponInfo.h" +#include "AnimManager.h" +#include "AnimBlendAssociation.h" +#include "Weapon.h" + +static CWeaponInfo aWeaponInfo[WEAPONTYPE_TOTALWEAPONS]; + +static char ms_aWeaponNames[][32] = { + "Unarmed", + "BaseballBat", + "Colt45", + "Uzi", + "Shotgun", + "AK47", + "M16", + "SniperRifle", + "RocketLauncher", + "FlameThrower", + "Molotov", + "Grenade", + "Detonator", + "HeliCannon" +}; + +CWeaponInfo* +CWeaponInfo::GetWeaponInfo(eWeaponType weaponType) { + return &aWeaponInfo[weaponType]; +} + +void +CWeaponInfo::Initialise(void) +{ + debug("Initialising CWeaponInfo...\n"); + for (int i = 0; i < WEAPONTYPE_TOTALWEAPONS; i++) { + aWeaponInfo[i].m_eWeaponFire = WEAPON_FIRE_INSTANT_HIT; + aWeaponInfo[i].m_AnimToPlay = ANIM_STD_PUNCH; + aWeaponInfo[i].m_Anim2ToPlay = ANIM_STD_NUM; + aWeaponInfo[i].m_Flags = WEAPONFLAG_USE_GRAVITY | WEAPONFLAG_SLOWS_DOWN | WEAPONFLAG_RAND_SPEED | WEAPONFLAG_EXPANDS | WEAPONFLAG_EXPLODES; + } + debug("Loading weapon data...\n"); + LoadWeaponData(); + debug("CWeaponInfo ready\n"); +} + +void +CWeaponInfo::LoadWeaponData(void) +{ + float spread, speed, lifeSpan, radius; + float range, fireOffsetX, fireOffsetY, fireOffsetZ; + float delayBetweenAnimAndFire, delayBetweenAnim2AndFire, animLoopStart, animLoopEnd; + int flags, ammoAmount, damage, reload, weaponType; + int firingRate, modelId; + char line[256], weaponName[32], fireType[32]; + char animToPlay[32], anim2ToPlay[32]; + + CAnimBlendAssociation *animAssoc; + AnimationId animId; + + size_t bp, buflen; + int lp, linelen; + + CFileMgr::SetDir("DATA"); + buflen = CFileMgr::LoadFile("WEAPON.DAT", work_buff, sizeof(work_buff), "r"); + CFileMgr::SetDir(""); + + for (bp = 0; bp < buflen; ) { + // read file line by line + for (linelen = 0; work_buff[bp] != '\n' && bp < buflen; bp++) { + line[linelen++] = work_buff[bp]; + } + bp++; + line[linelen] = '\0'; + + // skip white space + for (lp = 0; line[lp] <= ' ' && line[lp] != '\0'; lp++); + + if (line[lp] == '\0' || line[lp] == '#') + continue; + + spread = 0.0f; + flags = 0; + speed = 0.0f; + ammoAmount = 0; + lifeSpan = 0.0f; + radius = 0.0f; + range = 0.0f; + damage = 0; + reload = 0; + firingRate = 0; + fireOffsetX = 0.0f; + weaponName[0] = '\0'; + fireType[0] = '\0'; + fireOffsetY = 0.0f; + fireOffsetZ = 0.0f; + animId = ANIM_STD_WALK; + sscanf( + &line[lp], + "%s %s %f %d %d %d %d %f %f %f %f %f %f %f %s %s %f %f %f %f %d %d", + weaponName, + fireType, + &range, + &firingRate, + &reload, + &ammoAmount, + &damage, + &speed, + &radius, + &lifeSpan, + &spread, + &fireOffsetX, + &fireOffsetY, + &fireOffsetZ, + animToPlay, + anim2ToPlay, + &animLoopStart, + &animLoopEnd, + &delayBetweenAnimAndFire, + &delayBetweenAnim2AndFire, + &modelId, + &flags); + + if (strncmp(weaponName, "ENDWEAPONDATA", 13) == 0) + return; + + weaponType = FindWeaponType(weaponName); + + animAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, animToPlay); + animId = static_cast(animAssoc->animId); + + if (strcmp(anim2ToPlay, "null") != 0) { + animAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, anim2ToPlay); + aWeaponInfo[weaponType].m_Anim2ToPlay = (AnimationId) animAssoc->animId; + } + + CVector vecFireOffset(fireOffsetX, fireOffsetY, fireOffsetZ); + + aWeaponInfo[weaponType].m_eWeaponFire = FindWeaponFireType(fireType); + aWeaponInfo[weaponType].m_fRange = range; + aWeaponInfo[weaponType].m_nFiringRate = firingRate; + aWeaponInfo[weaponType].m_nReload = reload; + aWeaponInfo[weaponType].m_nAmountofAmmunition = ammoAmount; + aWeaponInfo[weaponType].m_nDamage = damage; + aWeaponInfo[weaponType].m_fSpeed = speed; + aWeaponInfo[weaponType].m_fRadius = radius; + aWeaponInfo[weaponType].m_fLifespan = lifeSpan; + aWeaponInfo[weaponType].m_fSpread = spread; + aWeaponInfo[weaponType].m_vecFireOffset = vecFireOffset; + aWeaponInfo[weaponType].m_AnimToPlay = animId; + aWeaponInfo[weaponType].m_fAnimLoopStart = animLoopStart / 30.0f; + aWeaponInfo[weaponType].m_fAnimLoopEnd = animLoopEnd / 30.0f; + aWeaponInfo[weaponType].m_fAnimFrameFire = delayBetweenAnimAndFire / 30.0f; + aWeaponInfo[weaponType].m_fAnim2FrameFire = delayBetweenAnim2AndFire / 30.0f; + aWeaponInfo[weaponType].m_nModelId = modelId; + aWeaponInfo[weaponType].m_Flags = flags; + } +} + +eWeaponType +CWeaponInfo::FindWeaponType(char *name) +{ + for (int i = 0; i < WEAPONTYPE_TOTALWEAPONS; i++) { + if (strcmp(ms_aWeaponNames[i], name) == 0) { + return static_cast(i); + } + } + return WEAPONTYPE_UNARMED; +} + +eWeaponFire +CWeaponInfo::FindWeaponFireType(char *name) +{ + if (strcmp(name, "MELEE") == 0) return WEAPON_FIRE_MELEE; + if (strcmp(name, "INSTANT_HIT") == 0) return WEAPON_FIRE_INSTANT_HIT; + if (strcmp(name, "PROJECTILE") == 0) return WEAPON_FIRE_PROJECTILE; + if (strcmp(name, "AREA_EFFECT") == 0) return WEAPON_FIRE_AREA_EFFECT; + Error("Unknown weapon fire type, WeaponInfo.cpp"); + return WEAPON_FIRE_INSTANT_HIT; +} + +void +CWeaponInfo::Shutdown(void) +{ + debug("Shutting down CWeaponInfo...\n"); + debug("CWeaponInfo shut down\n"); +} diff --git a/src/weapons/WeaponInfo.h b/src/weapons/WeaponInfo.h new file mode 100644 index 0000000..96e2ecf --- /dev/null +++ b/src/weapons/WeaponInfo.h @@ -0,0 +1,52 @@ +#pragma once + +#include "AnimationId.h" +#include "WeaponType.h" + +enum +{ + WEAPONFLAG_USE_GRAVITY = 1, + WEAPONFLAG_SLOWS_DOWN = 1 << 1, + WEAPONFLAG_DISSIPATES = 1 << 2, + WEAPONFLAG_RAND_SPEED = 1 << 3, + WEAPONFLAG_EXPANDS = 1 << 4, + WEAPONFLAG_EXPLODES = 1 << 5, + WEAPONFLAG_CANAIM = 1 << 6, + WEAPONFLAG_CANAIM_WITHARM = 1 << 7, + WEAPONFLAG_1ST_PERSON = 1 << 8, + WEAPONFLAG_HEAVY = 1 << 9, + WEAPONFLAG_THROW = 1 << 10, +}; + +class CWeaponInfo { +public: + eWeaponFire m_eWeaponFire; + float m_fRange; + uint32 m_nFiringRate; + uint32 m_nReload; + int32 m_nAmountofAmmunition; + uint32 m_nDamage; + float m_fSpeed; + float m_fRadius; + float m_fLifespan; + float m_fSpread; + CVector m_vecFireOffset; + AnimationId m_AnimToPlay; + AnimationId m_Anim2ToPlay; + float m_fAnimLoopStart; + float m_fAnimLoopEnd; + float m_fAnimFrameFire; + float m_fAnim2FrameFire; + int32 m_nModelId; + uint32 m_Flags; + + static void Initialise(void); + static void LoadWeaponData(void); + static CWeaponInfo *GetWeaponInfo(eWeaponType weaponType); + static eWeaponFire FindWeaponFireType(char *name); + static eWeaponType FindWeaponType(char *name); + static void Shutdown(void); + bool IsFlagSet(uint32 flag) const { return (m_Flags & flag) != 0; } +}; + +VALIDATE_SIZE(CWeaponInfo, 0x54); \ No newline at end of file diff --git a/src/weapons/WeaponType.h b/src/weapons/WeaponType.h new file mode 100644 index 0000000..b45740b --- /dev/null +++ b/src/weapons/WeaponType.h @@ -0,0 +1,49 @@ +#pragma once + +enum eWeaponType +{ + WEAPONTYPE_UNARMED, + WEAPONTYPE_BASEBALLBAT, + WEAPONTYPE_COLT45, + WEAPONTYPE_UZI, + WEAPONTYPE_SHOTGUN, + WEAPONTYPE_AK47, + WEAPONTYPE_M16, + WEAPONTYPE_SNIPERRIFLE, + WEAPONTYPE_ROCKETLAUNCHER, + WEAPONTYPE_FLAMETHROWER, + WEAPONTYPE_MOLOTOV, + WEAPONTYPE_GRENADE, + WEAPONTYPE_DETONATOR, + WEAPONTYPE_HELICANNON, + WEAPONTYPE_LAST_WEAPONTYPE, + WEAPONTYPE_ARMOUR, + WEAPONTYPE_RAMMEDBYCAR, + WEAPONTYPE_RUNOVERBYCAR, + WEAPONTYPE_EXPLOSION, + WEAPONTYPE_UZI_DRIVEBY, + WEAPONTYPE_DROWNING, + WEAPONTYPE_FALL, + WEAPONTYPE_UNIDENTIFIED, + + WEAPONTYPE_TOTALWEAPONS = WEAPONTYPE_LAST_WEAPONTYPE, + WEAPONTYPE_TOTAL_INVENTORY_WEAPONS = 13, +}; + +enum eWeaponFire { + WEAPON_FIRE_MELEE, + WEAPON_FIRE_INSTANT_HIT, + WEAPON_FIRE_PROJECTILE, + WEAPON_FIRE_AREA_EFFECT, + WEAPON_FIRE_USE +}; + +// Taken from MTA SA, seems it's unchanged +enum eWeaponState +{ + WEAPONSTATE_READY, + WEAPONSTATE_FIRING, + WEAPONSTATE_RELOADING, + WEAPONSTATE_OUT_OF_AMMO, + WEAPONSTATE_MELEE_MADECONTACT +}; \ No newline at end of file diff --git a/utils/gxt/american.txt b/utils/gxt/american.txt new file mode 100644 index 0000000..57ec4d7 --- /dev/null +++ b/utils/gxt/american.txt @@ -0,0 +1,8110 @@ +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Hey! Get back in the vehicle! + +[IN_VEH2] +~g~You need some wheels for this job! + +[IN_BOAT] +~g~You need a boat for this job! + +[HEY] +~g~Don't go solo, keep your posse together! + +[HEY2] +~g~Don't split up, keep the group together! + +[HEY3] +~g~You've dropped your main man, go back and get 8-Ball! + +[HEY4] +~g~Lose Misty and Luigi will lose your face! Go and get her! + +[HEY5] +~g~One of the girls is AWOL, Go back and round her up! + +[HEY6] +~g~You left your honor with the Yakuza Kanbu. You must protect him! + +[HEY7] +~g~An extra gun could be useful. Go back and pick up your contact! + +[HEY8] +~g~Protection means just that -Protect the Old Oriental Gentleman! + +[HEY9] +~g~You want the word on the street? Go see the contact! + +[HELP2_A] +Press the ~h~/ button~w~ when running to ~h~sprint. + +[HELP3] +You can only sprint for short periods before becoming tired. + +[HELP4_A] +Press the~h~ ~k~~VEHICLE_ACCELERATE~ button~w~ to ~h~accelerate. + +[HELP4_D] +Push the~h~ right analog stick~w~ up to ~h~accelerate. + +[HELP5_A] +Press the~h~ ~k~~VEHICLE_BRAKE~ button~w~ to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP5_D] +Pull the ~h~right analog stick~w~ back to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP6_A] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP6_C] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP6_D] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP7_A] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ button ~w~to ~h~target~w~ with the sniper rifle. + +[HELP7_D] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ button ~w~to ~h~target ~w~with the sniper rifle. + +[HELP8_A] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ button ~w~to ~h~zoom in ~w~with the rifle and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ button ~w~to ~h~zoom out ~w~again. + +[HELP9_A] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP10] +This badge indicates you have a police wanted level. + +[HELP11] +The more badges the higher your wanted level. + +[HELP13] +Sometimes you may need to use pathways not shown on the radar. + +[TIMER] +This is a timed mission, you must complete it before the timer counts down to zero. + +[MISTY1] +~r~Misty is morgue-meat! + +[OUT_VEH] +~g~Get out of the vehicle! + +[GARAGE] +Drive the vehicle into the garage, then walk outside. + +[WANTED1] +~g~Shake the cops and lose your wanted level! + +[NODOORS] +~g~They ain't sardines! Get some wheels with enough seats. + +[TRASH] +~g~You've junked your wheels real bad! Get your vehicle repaired! + +[WRECKED] +~r~The vehicle is wrecked! + +[HORN] +~g~Sound the horn. + +[NOMONEY] +~g~You need more cash! + +[OUTTIME] +~r~Too slow, man, too slow! + +[SPOTTED] +~r~They're on to you! + +[REWARD] +REWARD $~1~ + +[GAMEOVR] +GAME OVER + +[Z] +Z-axis value: ~1~ + +[M_FAIL] +MISSION FAILED! + +[M_PASS] +MISSION PASSED! $~1~ + +[O_PASS] +ODD JOB PASSED! + +[O_FAIL] +ODD JOB FAILED! + +[DEAD] +WASTED! + +[BUSTED] +BUSTED! + +[S_PROMP] +When not on a mission you can ~h~save your game here~w~, this will advance time six hours. + +[NUMBER] +~1~ + +[SCORE] +$~1~ + +[LOADCAR] +LOADING VEHICLE... (PRESS L1 TO CANCEL) + +[CARSOFF] +Cars turned off. + +[CARS_ON] +Cars turned on. + +[TEXTXYZ] +Writing coordinates to file... + +[CHEATON] +Cheat mode ON + +[CHEATOF] +Cheat mode OFF + +[UZI_IN] +The Uzi is now in stock at Ammunation! + +[IMPORT1] +Go outside and wait for your vehicle. + +[PAGEB1] +Pistol delivered to hideout + +[PAGEB2] +Uzi delivered to hideout + +[PAGEB3] +Body armor delivered to hideout + +[PAGEB4] +Shotgun delivered to hideout + +[PAGEB5] +grenades delivered to hideout + +[PAGEB6] +molotovs delivered to hideout + +[PAGEB7] +AK47 delivered to hideout + +[PAGEB8] +Sniper rifle delivered to hideout + +[PAGEB9] +M16 delivered to hideout + +[PAGEB10] +Rocket Launcher delivered to hideout + +[PAGEB11] +Flamethrower delivered to hideout + +[WANT_A] +You will only be arrested if you have a ~h~wanted level. + +[WANT_B] +Your ~h~wanted level~w~ is represented by the row of stars in the top right of the screen. + +[WANT_C] +You now have a ~h~wanted level~w~ of one... + +[WANT_D] +two... + +[WANT_E] +three... + +[WANT_F] +As your ~h~wanted level~w~ increases you will attract more powerful forms of law enforcement. + +[WANT_G] +When you are ~h~'busted'~w~ you are returned to the nearest police station. + +[WANT_H] +The cops will take all your weapons and some of your cash as a bribe. + +[WANT_I] +Any mission you were on will be failed. + +[WANT_J] +You will find ways of reducing your wanted level the more you play. + +[WANT_K] +If you are in a car, ~h~SPRAY SHOPS~w~ will ~h~clear your wanted level. + +[HEAL_B] +When you are ~h~'wasted'~w~ you are returned to the nearest hospital. + +[HEAL_C] +You will lose your weapons and the doctors will take some cash for patching you up. + +[HEAL_E] +You will find ways of healing or protecting yourself the more you play the game. + +[DAM] +DAMAGE: + +[KILLS] +KILLS: + +[FARES] +FARES: + +[BULL] +BULLION: + +[EVID] +EVIDENCE: + +[HEALTH] +CAR HEALTH: + +[COLLECT] +COLLECTED: + +[BOMB] +Drive your vehicle into the bomb shop to attach a ~h~bomb~w~. Cost - ~h~$1000. + +[SAVE1] +Walk through the doorway to ~h~Save the game~w~. You cannot save during a mission. + +[SAVE2] +Any vehicle left in this garage will be stored when the game is saved. + +[AMMU] +Go inside Ammu-Nation to buy a weapon. + +[BRIDGE1] +When the Callahan Bridge is repaired you will be able to drive to Staunton Island. + +[TUNNEL] +When the Porter Tunnel is opened you will be able to drive to Staunton Island. + +[LUIGI] +LUIGI MISSIONS + +[TONI] +TONI MISSIONS + +[JOEY] +JOEY MISSIONS + +[FRANK] +SALVATORE MISSIONS + +[DIABLO] +DIABLO MISSIONS + +[ASUKA] +ASUKA MISSIONS + +[B_SITE] +ASUKA SUBURBAN MISSIONS + +[KENJI] +KENJI MISSIONS + +[RAY] +RAY MISSIONS + +[LOVE] +LOVE MISSIONS + +[YARDIE] +YARDIE MISSIONS + +[HOOD] +HOOD MISSIONS + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Atlantic Quays + +[PORT_E] +Portland Harbor + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Portland Beach + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Red Light District + +[TOWERS] +Hepburn Heights + +[HARWOOD] +Harwood + +[ROADBR1] +Callahan Bridge + +[ROADBR2] +Callahan Bridge + +[TUNNELP] +Porter Tunnel + +[BOMB1] +8-Ball's Garage + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Liberty Campus + +[CONSTRU] +Fort Staunton + +[PARK] +Belleville Park + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Francis Intl. Airport + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Cochrane Dam + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Ambulance + +[CAR_2] +Firetruck + +[CAR_3] +Police + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +FBIcar + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[LUIGIS] +Luigi's Place + +[GOAWAY] +~g~You are already on a mission! + +[LUIGGO] +~g~Luigi's interviewing some new girls -Come back later! + +[JOEYGO] +~g~Joey's out on the town with Misty -Drop by later! + +[TONIGO] +~g~Toni's taken his Momma to the opera -Call in some other time! + +[KEMUGO] +~g~Maria and Kemuri are all tied up at the moment -Drop by later! + +[KENJGO] +~g~Kenji is attending a Yakuza meeting -Call by some other time! + +[RAYGO] +~g~Ray has other toilets to hang around -Try again later! + +[LOVEGO] +~g~Donald Love has other business to attend to -Make an appointment later! + +[KENSGO] +~g~Kenji is busy! -Call by later! + +[HOODGO] +~g~The Hoods are not available at this time! + +[WRONGT1] +~g~Come back between 05:00 and 21:00 for a job + +[WRONGT2] +~g~Come back between 06:00 and 14:00 for a job + +[WRONGT3] +~g~Come back between 15:00 and 00:00 for a job + +[GUN_1A] +Use the ~h~~k~~PED_CYCLE_WEAPON_RIGHT~ button ~w~and the ~h~~k~~PED_CYCLE_WEAPON_LEFT~ button ~w~to cycle through your weapons. + +[GUN_2A] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_2C] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_2D] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_3A] +While holding the ~h~~k~~PED_LOCK_TARGET~ button,~w~ press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button~w~ or the ~h~~k~~PED_CYCLE_TARGET_RIGHT~ button to switch target. + +[GUN_3B] +While holding the ~h~~k~~PED_LOCK_TARGET~ button,~w~ press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button~w~ or the ~h~~k~~PED_CYCLE_TARGET_RIGHT~ button to switch target. + +[GUN_4A] +While holding the ~h~~k~~PED_LOCK_TARGET~ button~w~ you can walk or run while remaining locked onto a target. + +[GUN_4B] +While holding the ~h~~k~~PED_LOCK_TARGET~ button~w~ you can walk or run while remaining locked onto a target. + +[GUN_5] +You can practice targeting and shooting on these paper targets. When you are finished resume the mission. + +[TAXI1] +~g~Look for a fare. + +[FARE1] +~g~Destination ~w~'Meeouch Sex Kitten Club' ~g~in Redlight. + +[FARE2] +~g~Destination ~w~'Supa Save' ~g~in Portland View. + +[FARE3] +~g~Destination ~w~'old school hall' ~g~in Chinatown. + +[FARE4] +~g~Destination ~w~'Greasy Joe's Cafe' ~g~in Callahan Point. + +[FARE5] +~g~Destination ~w~'AmmuNation' ~g~in Redlight. + +[FARE6] +~g~Destination ~w~'Easy Credit Autos' ~g~in Saint Mark's. + +[FARE7] +~g~Destination ~w~'Woody's topless bar' ~g~in Redlight. + +[FARE8] +~g~Destination ~w~'Marcos Bistro' ~g~in Saint Mark's. + +[FARE9] +~g~Destination ~w~'import export garage' ~g~in Portland Harbor. + +[FARE10] +~g~Destination ~w~'Punk Noodles' ~g~in Chinatown. + +[FARE12] +~g~Destination ~w~'Football Stadium' ~g~in Aspatria. + +[FARE13] +~g~Destination ~w~'The Church' ~g~in Bedford Point + +[FARE14] +~g~Destination ~w~'The Casino' ~g~in Torrington + +[FARE15] +~g~Destination ~w~'Liberty University' ~g~in Liberty Campus + +[FARE16] +~g~Destination ~w~'Shopping Mall' ~g~in Belleville Park Area + +[FARE17] +~g~Destination ~w~'Museum' ~g~in Newport + +[FARE18] +~g~Destination ~w~'AmCo Building' ~g~in Torrington + +[FARE19] +~g~Destination ~w~'Bolt Burgers' ~g~in Bedford Point + +[FARE20] +~g~Destination ~w~'The Park' ~g~in Belleville + +[FARE21] +~g~Destination ~w~'Francis intl. Airport' + +[FARE22] +~g~Destination ~w~'Cochrane Dam' + +[FARE24] +~g~Destination ~w~'The hospital' ~g~in Pike Creek + +[FARE25] +~g~Destination ~w~'The Park' ~g~in Shoreside Vale + +[FARE26] +~g~Destination ~w~'North West Towers' ~g~in Wichita Gardens + +[NEW_TAX] +BIGGER! FASTER! HARDER! new Borgnine taxis open for business in Harwood. Call 555-BORGNINE today! + +[TSCORE2] +$~1~ + +[IN_ROW] +~1~ IN A ROW bonus! $~1~ + +[TTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle taxi missions on or off. + +[TTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle taxi missions on or off. + +[A_TIME] ++~1~ seconds + +[A_FULL] +~r~Ambulance full!! + +[A_RANGE] +~g~The ambulance radio is out of range, get closer to a hospital! + +[FTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle fire truck missions on or off. + +[FTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle fire truck missions on or off. + +[F_PASS1] +Fire extinguished! + +[F_RANGE] +~g~The fire truck radio is out of range, get closer to a fire station! + +[C_BREIF] +~g~Suspect last seen in the ~a~ area. + +[C_RANGE] +~g~The police radio is out of range, get closer to a police station! + +[DODO_FT] +You flew for ~1~ seconds! + +[EBAL_A] +I know a place on the edge of the Red Light District where we can lay low, + +[EBAL_A1] +but my hands are all messed up so you better drive, brother. + +[EBAL_1] +Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a vehicle. + +[EBAL_1B] +Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a vehicle. + +[EBAL_2] +~g~Get back into the car! + +[EBAL_3] +This is the ~h~radar~w~. Use it to navigate the city, follow the ~h~blip~w~ on the ~h~radar~w~ to find the hideout! + +[EBAL_D] +I know a guy, he's connected, his name's Luigi. + +[EBAL_D1] +Me an' him go back so I could probably get you some work. C'mon lets head over there. + +[EBAL_E] +C'mon, lets drop by and I'll introduce you. + +[EBAL_I] +The boss will be out to see you shortly... + +[EBAL_J] +8-Ball's got some business up stairs. + +[EBAL_K] +Maybe you can do me a favor. + +[EBAL_L] +One of my girls needs a ride so grab a car and pick up Misty from the clinic. Then bring her back here. + +[EBAL_N] +So keep your hands on the wheel! + +[EBAL_4] +~r~8-Ball's dead! + +[EBAL_5] +~g~Get a vehicle! + +[EBAL_6] +~g~Pick up Misty! + +[LM1] +'LUIGI'S GIRLS' + +[LM2] +'DON'T SPANK MA BITCH UP' + +[LM3] +'DRIVE MISTY FOR ME' + +[LM5] +'THE FUZZ BALL' + +[LM1_2] +~g~Take Misty to Luigi's Club. + +[LM1_3] +~g~Press the horn to get the girl into the car. + +[LM1_6] +~g~Get back into the car! + +[LM1_7] +Stop the vehicle next to Misty and allow her to enter it. + +[LM1_8] +You can go and see Luigi for more work or check out Liberty City. + +[LM2_A] +There's a new high on the street goes by the name of SPANK. + +[LM2_E] +Some wiseguy's been introducing this trash to my girls down Portland Harbor. + +[LM2_B] +Go and introduce a bat to his face! + +[LM2_G] +I want compensation for this insult! + +[LM2_1] +~g~Take his car and get it resprayed. + +[LM2_2A] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat! + +[LM2_2C] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat~w~! + +[LM2_2D] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat~w~! + +[LM2_3] +~g~Stash the car in Luigi's lockup! + +[LM2_4] +~g~Respray the car! + +[LM3_A] +Hey I've gotta talk to you... All right Mick I'll talk to yah later. + +[LM3_B] +How yah doing kid? + +[LM3_C] +The Don's son, Joey Leone, he wants some action from his regular girl Misty. + +[LM3_D] +Go pick her up at Hepburn Heights... + +[LM3_E] +but watch yourself that's Diablo turf. + +[LM3_F] +Then run her over to his garage in Trenton and make it quick, + +[LM3_H] +so keep your eyes on the road and off Misty! + +[LM3_2] +~g~Take Misty to Joey's. + +[LM3_4] +~g~Go pick up Misty! + +[LM3_5] +You working regular for Luigi now huh? It's about time he got a driver we can trust! + +[LM3_7] +I'll be with you in a minute spark plug. + +[LM3_10] +~g~Get a vehicle! + +[LM4_B] +Go and take care of things for me. + +[LM4_C] +If you need a piece go around the back of AmmuNation opposite the subway. + +[LM5_A] +The Policeman's Ball is being held at the old school hall near the Callahan Bridge + +[LM5_B] +and they'll be looking for some 'old school' action. + +[LM5_C] +Now I got girls all over town walking the streets. + +[LM5_D] +Get'em to the ball they'll make a bundle. + +[LM5_1] +~g~You pack these ladies too tight, they gonna bruise! ~g~Drop these girls off first, then come back for more. + +[LM5_2] +~r~One of Luigi's girls is bodybag meat! + +[LM5_3] +~g~You need a car! + +[LM5_4] +~g~Pick up the girls working St. Marks. + +[LM5_5] +~g~Take the girls to the Fuzz Ball! + +[LM5_8] +~g~Girls working the Ball: ~1~ + +[JM2] +'FAREWELL 'CHUNKY' LEE CHONG' + +[JM4] +'CIPRIANI'S CHAUFFEUR' + +[JM5] +'DEAD SKUNK IN THE TRUNK' + +[JM1_1] +~g~Take Forelli's car to 8-Ball's garage North of here, behind 'Easy Credit Autos'. + +[JM1_2] +~g~Park the car back at Marco's Bistro. + +[JM1_3] +~g~Activate the car bomb then get out of there! + +[JM1_4] +~g~You're trashing the vehicle! Get it repaired! + +[JM1_5] +~g~The car bomb's not set! + +[JM1_6] +~g~Put the car back in the correct position. + +[JM1_8A] +~y~Hey, it's my main man! + +[JM1_8B] +~y~The bomb shop's automated. Just drive in, stop your car and the shop will do the rest. + +[JM1_8C] +~y~Here, your first can be free, but after that it'll cost. + +[JM2_A] +Chunky Lee Chong is pushing spank for some new gang from Colombia... or Colorado... or something.... + +[JM2_B] +I'm not really sure. Who needs details anyway. + +[JM2_D] +That rat has sold his last stir fry. + +[JM2_E] +I want you to take him out! + +[JM2_G] +Sort yourself with a nine, you know where it is, right? + +[JM2_H] +Well remember, just watch your back in China Town, it's Triad territory. + +[JM3_A] +Alright, we're gonna hit the pay role van. + +[JM3_B] +It leaves the edge of China Town everyday. + +[JM3_C] +Bullets won't even dent the van's armor, so get a car and ram it off the road. + +[JM3_D] +Now hit it hard and the punk ass security guards should bail. + +[JM3_E] +Then take it to the warehouse at the docks and my guys are gonna take over from there. + +[JM3_F] +Now it won't be doin' it's rounds all day, so don't hang around. + +[JM3_1] +~g~Take the van to the lock up. + +[JM3_2] +~g~Ram the van until its damage is below 70 percent. + +[JM4_B] +Oh! Here's the guy I was telling you about! + +[JM4_C] +Alright Listen. This guy ain't Italian and he's no mechanic but he can get things fixed. + +[JM4_D] +This is Pops Capo, Toni Cipriani. + +[JM4_E] +Yeah, I'm Toni Cipriani + +[JM4_F] +Take him to Momma's restaurant at St Marks, alright. + +[JM4_G] +Now listen to me, I'm planning a job that needs a good driver so drop by sometime later Ok? + +[JM4_2] +Wait here! Keep the engine running. This ain't a social call. + +[JM4_3] +It's a Triad ambush! Get us out of here kid! + +[JM4_4] +The Triads think they can mess with me, the triads, with ME! + +[JM4_6] +Hey watch the car! I said no fancy crap. + +[JM4_7] +~g~Take Toni to his momma's restaurant. + +[JM4_8] +~r~Toni's been wasted! + +[JM5_A] +Beautiful! Just beautiful. + +[JM5_B] +Alright, Just the guy I need to talk to! + +[JM5_D] +One of the Forellis thought he was a wise guy, so he got what he had coming to him. + +[JM5_E] +Take the corpse to the crusher in Harwood, alright? + +[JM5_1] +~g~Take it to the crusher! + +[JM5_2] +~g~It's the Forelli brothers! + +[JM6_A] +What a ride she's gonna be, huh? + +[JM6_B] +Alright, listen. Get some wheels to the safehouse at St. Marks and pick up a few friends of mine. + +[JM6_C] +They're hittin' a bank and they need a driver. + +[JM6_D] +I gave my word that you were the man, so don't screw this up. + +[JM6_E] +Get them to the bank before five o'clock, not a minute after. + +[JM6_2] +Keep the engine running we'll be in and out in no time. + +[JM6_3] +Get us out of here!! + +[JM6_4] +Shake the cops and get us to the safehouse!! + +[JM6_6] +~g~Go and get a vehicle less conspicuous! + +[JM6_7] +~g~You need all 3 to rob the bank! + +[TM1] +'TAKING OUT THE LAUNDRY' + +[TM2] +'THE PICK-UP' + +[TM3] +'SALVATORE'S CALLED A MEETING' + +[TM4] +'TRIADS AND TRIBULATIONS' + +[TM5] +'BLOW FISH' + +[TONI_P] +I've got some urgent work for you! -Toni + +[TM1_A] +~w~Take a seat kid, take a god damned seat. + +[TM1_B] +~w~So the laundry won't pay any protection eh? + +[TM1_C] +~w~The Triads think they can mess with me? + +[TM1_D] +~w~Let's teach these would be tough guys what it means to be a tough guy. + +[TM1_E] +~w~Yeah, teach 'em some respect. No son of mine gets it from some Triads. + +[TM1_F] +~w~Your father, god rest his soul, took no crap from no Triads back in Sicily. + +[TM1_G] +~w~Sorry Ma. Yes Ma. + +[TM1_H] +~w~I want you to destroy their laundry vans + +[TM1_I] +~w~and mangle any triad gimp that gets in your way. + +[TM1_J] +~w~8-Ball can supply you with what you're gonna need. + +[TM2_A] +~w~TONI's off making people bleed or trying to. + +[TM2_AA] +~w~He'll never be as tough as his Pop, but he left you a note on the table. + +[TM2_B] +~w~The laundry has agreed to pay - you did real good kid! + +[TM2_C] +~w~Go collect the cash and bring it back here. Watch out for the Triads. + +[TM2_D] +~w~They may be shoving a firecracker up your ass, but don't take no crap. + +[TM2_E] +~w~Nobody I mean nobody, messes with TONI CIPRIANI! + +[TM2_1] +~g~Get the cash back to Toni's!! + +[TM2_2] +~g~You iced them all! + +[TM3_MA] +~w~I don't know where he is! + +[TM3_MB] +~w~I swear that boy of mine don't know himself sometimes. + +[TM3_MC] +~w~Now his father, he was different. Always on top, in charge, manful... + +[TM3_A] +~w~Don Salvatore has called a meeting. + +[TM3_B] +~w~I need you to collect the limo and his boy, Joey, from the garage. + +[TM3_C] +~w~Then get Luigi from his club, come back here and pick me up, + +[TM3_D] +~w~then we'll all drive over to the boss's place together. + +[TM3_E] +~w~Those Triads, they don't know when to stop. + +[TM3_F] +~w~They want a war. They got a war. + +[TM3_G] +~w~Now get going. + +[TM3_1] +~g~Pick up the Stretch from Joey's. + +[TM3_2] +~g~Now go pick up Luigi. + +[TM3_3] +~g~Now go pick up Toni. + +[TM3_4] +~g~Drive the goodfellas to Salvatore's place. + +[TM3_5] +~y~It's a triad ambush!! + +[TM4_B] +~w~We're at WAR! The Triads have a fish factory as a front. + +[TM4_C] +~w~Most of their business goes down at the fish market in Chinatown. + +[TM4_D] +~w~That laundry still owes us protection. + +[TM4_E] +~w~They reckon the Triads are protecting them now, so I say we exact a fitting punishment. + +[TM4_F] +~w~Take these boys over and whack the Triad Warlords! + +[TM4_G] +~w~Hell, if you get a chance, pop some of their soldiers too. + +[TM4_GAT] +~g~You need a 'Triad fish van' to enter. + +[TM5_B] +~w~OK, I've had enough of this shit. + +[TM5_C] +~w~We're gonna finish the Triads in Liberty once and for all! + +[TM5_D] +8-Ball's rigged a dustcart with a bomb. + +[TM5_E] +~w~It's on a timer so if you mess up there'll be no evidence. Go and pick up the dustcart. + +[TM5_F] +~w~Careful, 8-Ball says it's real sensitive and the slightest bump could set that thing off! + +[TM5_G] +~w~Their fish factory will open its gates for a dustcart, so you can drive right in. + +[TM5_H] +~w~Park up between the gas canisters and get the hell out of there! + +[TM5_I] +~w~I want it to rain mackerel. + +[TM5_J] +~w~We're talking real biblical here, nothing low budget. + +[FM2] +'CUTTING THE GRASS' + +[FM4] +'LAST REQUESTS' + +[FM1_A] +~w~Me an' the fellas need to talk business + +[FM1_B] +~w~so you're gonna look after my girl for the evening. + +[FM1_C] +~w~HEY MARIA! MOVE YOUR BUTT! + +[FM1_D] +~w~Dumb broad does this every time. + +[FM1_E] +~w~And here she is, the one and only Queen of Sheba! + +[FM1_F] +~w~What were you doing up there? + +[FM1_G] +~w~Whatever it was, I bet it cost me money. + +[FM1_H] +~w~Well, you don't think I hang around for the conversation, do you? + +[FM1_I] +~w~Get in that car and keep your big mouth shut. + +[FM1_J] +~w~Take the limo but bring it back in one piece, y'hear me? + +[FM1_K] +~w~And watch her, she can be trouble. + +[FM1_L] +~w~Yeah, yeah, yeah! I'm sure your new lap dog has everything covered, + +[FM1_M] +~w~and isn't he big and strong? + +[FM1_N] +~w~Hey Fido, Let's go visit Chico and get some party treats! + +[FM1_P] +~g~That's Chico over there, pull up next to him. + +[FM1_S] +~w~Here you go lady. + +[FM1_TT] +~w~IT'S A POLICE RAID! + +[FM1_1] +~g~Get back into the Stretch! + +[FM1_2] +~g~Get into the Stretch! + +[FM1_3] +~r~Leave Maria and Salvatore will have you whacked, go back and pick her up. + +[FM1_4] +~g~You've dumped the Don's woman! Get back to the warehouse and wait for Maria! + +[FM1_5] +~g~Get Maria safely back to Salvatore's! + +[FM1_6] +~g~Chico won't be there forever, get Maria to the waterfront! + +[FM1_7] +~r~Maria's dead! Salvatore won't be too pleased... + +[FM1_8] +~r~You wasted Maria's supplier! + +[FM2_J] +Leave us alone for a minute. + +[FM2_A] +The Colombian Cartel is making SPANK somewhere in Liberty. + +[FM2_K] +but we don't know where, and they seem to know everything we're doin' before we do. + +[FM2_L] +There is a guy named Curly Bob works the bar at Luigi's. + +[FM2_M] +He's been throwing more money around than he's earning. + +[FM2_N] +He usually gets a taxi home after work. So follow him. + +[FM2_O] +And if he's rattin' us out... kill him. + +[FM2_F] +Here comes our little friend. Mr big mouth himself. + +[FM2_G] +Were you followed? You know what goes on here is our little secret. + +[FM2_H] +No..no, I wasn't followed. You got my stuff? + +[FM2_I] +Here's your SPANK, squealer, now talk. + +[FM2_P] +OK, so the Leone's are fighting wars on two fronts. + +[FM2_Q] +They're in a turf war with the Triads with no sign of either side giving up. + +[FM2_R] +Meanwhile Joey Leone has stirred up some bad blood with the Forellis. + +[FM2_S] +Every day they're losing men and influence in the city. + +[FM2_T] +Salvatore is becoming dangerous and paranoid. He suspects everybody and everything. + +[FM2_U] +With loyalty like yours, what has he possibly got to worry about. + +[FM2_1] +~g~There's Curly Bob! + +[FM2_2] +~g~Curly's left the club, tail him! + +[FM2_5] +~g~Take him to Portland Harbor. + +[FM2_6] +~r~Curly won't get into a smashed-up taxi! + +[FM2_7] +~r~Curly's spooked! The meeting's off! + +[FM2_8] +~g~Whack Curly Bob! + +[FM2_9] +~r~Curly Bob's dead! + +[FM2_10] +~r~Curly got away! + +[FM2_11] +~g~Park out the front of Luigi's Club, Curly Bob will be leaving shortly. + +[FM2_12] +~r~He gave you the slip! + +[FM3_A] +~w~We should take these Colombian bastards out, + +[FM3_B] +~w~but while we're at war with the Triads we ain't strong enough. + +[FM3_C] +~w~The Cartel has got bottomless funds from pushing that SPANK crap. + +[FM3_D] +~w~If we make an open attack on them, they'll wipe the floor with us. + +[FM3_E] +~w~They must be making SPANK on that big boat that Curly lead you to. + +[FM3_F] +~w~So we gotta use our heads, or rather one head. Your head. + +[FM3_G] +~w~I'm asking you to destroy that SPANK factory as a personal favor to me, Salvatore Leone. + +[FM3_H] +~w~If you do this for me, you will be a made man, anything you want. + +[FM3_I] +~w~Go and see 8-Ball, you'll need his expertise to blow-up that boat. + +[FM3_8A] +~w~Yo my man! Salvatore phoned ahead, + +[FM3_8B] +~w~but a job like this is gonna need a lot of fireworks. + +[FM3_8D] +~w~but you know with me you get a lot of bang for your buck. + +[FM3_8E] +~w~Okay, let's do this thing! + +[FM3_8F] +~w~I can set this baby to detonate, but I still can't use a piece with these hands. + +[FM3_8G] +~w~Here, this rifle should help you pop some heads! + +[FM3_4] +~g~Stop the vehicle and let 8-Ball out! + +[FM3_7] +~r~8-Ball's been iced! + +[FM3_8] +~r~The guards have been alerted! + +[FM4_A] +~w~It's my favorite cleaner. + +[FM4_B] +~w~I'm proud of you my boy, you kicked the shit out of those grease balls. + +[FM4_C] +~w~I've got just one little job for you before we can all celebrate. + +[FM4_D] +~w~There's a car around the block from Luigi's club. + +[FM4_E] +~w~The inside is covered in brains. + +[FM4_F] +~w~We had to help some guy make up his mind and it proved a little messy. + +[FM4_H] +~w~Take it to the crusher before the cops find it. + +[AM3] +'PAPARAZZI PURGE' + +[AM4] +'PAYDAY FOR RAY' + +[AM5] +'TWO-FACED TANNER' + +[AM1_A] +We have certain issues to clear up before we can continue any form of relationship, + +[AM1_B] +business or otherwise. Lets lay our cards on the table. + +[AM1_C] +I am Yakuza and I know you worked for Salvatore Leone's family. + +[AM1_D] +I can give you work with our organization, + +[AM1_E] +But first you must prove to me that your ties with the Mafia are truly broken. + +[AM1_G] +Make sure he doesn't reach his club alive. + +[AM1_H] +Meanwhile Maria and I will catch up on old times. + +[AM1_I] +Oh..Asuka, you've got a massager. + +[AM1_J] +That's not a massager. + +[AM1_1] +~g~Salvatore is now leaving Luigi's! + +[AM1_2] +~r~You have been spotted! + +[AM1_3] +~r~You've missed Salvatore! + +[AM1_4] +~r~Nice going, you scared off the target! Call yourself a hitman? + +[AM1_5] +~g~Get to the Red Light District and wait for Salvatore to leave the club. + +[AM1_7] +~r~Salvatore's home, safe and sipping a cocktail. Ain't no one gonna call you the 'Jackal'! + +[AM1_8] +~g~Salvatore will be leaving Luigi's at about ~1~:~1~ + +[AM2_4] +~g~They seen you coming like a dayglow elephant! + +[AM3_A] +A reporter has been nosing around. + +[AM3_B] +Maria and I have taken a little holiday together until you can get rid of this perverted voyeur. + +[AM4_A] +It's my handsome handyman! + +[AM4_B] +Maria's all tied up at the moment but I'll tell her you called. + +[AM4_C] +Who's that? Asuka? I know I've been a naughty girl but I really need to pee! OK? + +[AM4_D] +It's time you met our man inside the LPD. + +[AM4_E] +Here's his payment for the last little job he did for us. + +[AM4_F] +He is understandably cautious. + +[AM4_G] +Get to the pay phone in Torrington as quick as you can and await his instructions. + +[AM5_A] +Maria and I have gone shopping. + +[AM5_B] +Our source in the police has informed us that one of our drivers is a strangely animated undercover cop! + +[AM5_C] +He's more or less useless out of his car, so we've tagged it with a tracer. + +[AM5_D] +Make him bleed! + +[AM5_1] +Tanner's on to you! + +[AS1] +'BAIT' + +[AS2] +'ESPRESSO-2-GO!' + +[AS4] +'RANSOM' + +[AS1_A] +~w~Miguel seems to think I'm mistreating him. + +[AS1_B] +~w~Still, he's revealed the extent to which Catalina fears your quest for revenge. + +[AS2_A] +~w~We underestimated Catalina's plans for SPANK. + +[AS2_B] +~w~It reaches far beyond the Yardies selling it on the street corners. + +[AS2_D] +~w~They've been selling SPANK through the street stalls. + +[AS2_1] +~g~All espresso stalls in Portland wrecked!! + +[AS2_2] +~g~All espresso stalls in Staunton Island wrecked!! + +[AS2_3] +~g~All espresso stalls in Shoreside Vale wrecked!! + +[AS2_4] +~r~The Cartel have warned their pushers!! + +[AS2_5] +~g~There are still espresso stalls in Shoreside Vale and on Staunton Island! + +[AS2_6] +~g~There are still espresso stalls in Shoreside Vale! + +[AS2_7] +~g~There are still espresso stalls on Staunton Island! + +[AS2_8] +~g~There are still espresso stalls in Portland! + +[AS2_9] +~g~There are still espresso stalls in Portland and Shoreside Vale! + +[AS2_10] +~g~There are still espresso stalls in Portland and on Staunton Island + +[AS2_12] +~g~Cruise Liberty's districts to find ~b~Espresso-2-Go stalls! + +[AS3_A] +~W~Do we tighten it some more now, or just wait for it to turn black and fall off? + +[AS3_B] +~w~Give it a quick prod... + +[AS3_D] +~w~My Handyman! + +[AS3_E] +~w~I was bored so I came over to keep Asuka company. + +[AS3_1] +~g~Find the ~r~boat~g~ and get to the ~b~marker buoy! + +[AS3_3] +~g~Wait for the ~y~plane~g~ to start its approach! + +[AS3_5] +~g~Collect the cargo! + +[AS3_4] +~g~Use a rocket launcher to shoot the ~y~plane~g~ down!! + +[AS3_2] +~b~Get to the runway marker buoys! ~y~The plane is on its final approach!! + +[AS3_6] +~g~~1~ OF 8 + +[KM1] +'KANBU BUST-OUT' + +[KM3] +'DEAL STEAL' + +[KM4] +'SHIMA' + +[KM5] +'SMACK DOWN' + +[KM1_A] +My sister speaks highly of you, + +[KM1_E] +though I am yet to be convinced that a gaijin can offer anything but disappointment. + +[KM1_B] +Perhaps you could help deal with a situation that has me at a disadvantage. + +[KM1_F] +Of course failure has its own disgrace. + +[KM1_C] +A Yakuza Kanbu is in custody awaiting transfer for trial. + +[KM1_G] +He is a valued member of the family. + +[KM1_H] +Break him out of custody and get him to the dojo at Bedford Point. + +[KM1_D] +We thankyou for your selfless actions. If you ever need help the dojo will be honoured to provide two men who will stand at your side. + +[KM1_1] +~g~Steal a cop car! + +[KM1_2] +~g~Rig the car with a bomb! + +[KM1_3] +~g~Now get him to the Yakuza dojo. + +[KM1_5] +~g~Okay now go to the police station. + +[KM1_6] +~g~Fit the car with a bomb! + +[KM1_7] +~g~Authorised police vehicles only! + +[KM1_9] +~r~You did not use a car bomb to destroy the wall + +[KM1_10] +~r~The Yakuza Kanbu is dead -along with your honor! + +[KM1_11] +~r~You brought the heat down on yourself! + +[KM2_A] +It is impossible to over-estimate the importance of etiquette in this line of work. + +[KM2_B] +To my eternal shame, a man once did me a favor and I have never had the opportunity to repay his kindness. + +[KM2_C] +The man's weakness is motor cars and he has requested that we acquire him certain models for his collection. + +[KM2_F] +My honor demands it. + +[KM2_2] +~g~Car delivered. + +[KM3_A] +When trouble looms, the fool turns his back, while the wise man faces it down. + +[KM3_B] +The Colombian Cartel have ignored repeated requests to leave our interests in Liberty well alone. + +[KM3_C] +Now they are negotiating terms with the Jamaicans in order to humiliate us further. + +[KM3_D] +They are finalizing a deal across town. + +[KM3_F] +Take one of my men, steal a Yardie car, and go and pay your respects to the Colombians. + +[KM3_E] +Our Honor demands that you leave no one alive. + +[KM3_2] +~g~Go and pick up your contact. + +[KM3_3] +~g~The meeting is being held in the hospital parking lot in Rockford! + +[KM3_4] +~r~They got away! + +[KM3_6] +~g~Kill them, kill them all! + +[KM3_8] +~g~You need a Yardie car to get on with the job! + +[KM3_9] +~r~One of the Colombians is dead, the deal's off. + +[KM3_10] +~r~The contact is dead! + +[KM4_A] +To be truly strong, it is important that you never show weakness. + +[KM4_C] +Go and collect the money immediately, so we can enter it into the casino accounts. + +[KM4_1] +I can't pay you and I wouldn't pay you if I could! + +[KM4_9] +Some young gang just jacked out the place! They took everything! + +[KM4_2] +You guys are useless. + +[KM4_10] +What kind of Yakuza are YOU anyway...? + +[KM4_3] +This ain't what I pay you goons for. If I wanted this kind of protection I'd have used the god damn police service + +[KM4_4] +~g~Punish the gang responsible and retrieve the ~b~protection money~g~! + +[KM4_7] +~r~The shopkeeper's breathed his last! + +[KM4_5] +Donald Love wishes you to drop by his tea garden so you and he can talk. + +[KM4_6] +There's the money its all there! + +[KM4_8] +~g~Briefcase collected! + +[KM5_A] +YOU! How fitting you should choose this moment to show your worthless face! + +[KM5_B] +It would appear your attempts to dissuade the Jamaicans + +[KM5_B1] +from becoming bed fellows with the Cartel were wholly inadequate! + +[KM5_C] +Yardie pushers line Liberty's streets selling packets of SPANK like they were selling hotdogs! + +[KM5_D] +Those Cartel pigs are laughing at us, at me! + +[KM5_E] +I will give you one last chance to prove my sister's faith in you to be well founded! + +[KM5_F] +Run these scumbags into the ground and wash your shame in rivers of our enemies' blood!!! + +[KM5_3] +~r~You failed to kill at least ~1~ yardies. + +[KM5_4] +~g~Congratulations you killed ~1~ Yardies. + +[KM5_5] +~g~Congratulations you killed ~1~ Yardies. BONUS $~1~ + +[RM1] +'SILENCE THE SNEAK' + +[RM3] +'EVIDENCE DASH' + +[RM4] +'GONE FISHING' + +[RM5] +'PLASTER BLASTER' + +[RM1_D] +He's under armed protection in WitSec property down in Newport, some apartment behind the car park. + +[RM1_E] +Torch that place, that should flush 'em out, and you'll hunt 'em down, make sure he never talks to nobody. + +[RM1_1] +~g~Check out the witness protection house. + +[RM1_2] +~g~Take out McAffrey! + +[RM2_A1] +Hey kid over here! + +[RM2_A] +An old army buddy of mine runs a business in Rockford. + +[RM2_D] +He's gonna need some back-up and in return he'll give you knock-down rates on any hardware you buy. + +[RM2_E] +Ray phoned ahead....but I thought there'd be more of you. + +[RM2_F] +Well, three arms are better than one, so grab whatever you need. + +[RM2_G] +~g~Go and check on Phil! + +[RM2_H] +~r~Phil has been killed!! + +[RM2_L] +Heh-hey! If I'd teamed up with you in Nicaragua maybe I'd still have my arm! + +[RM2_N] +Leave the cash behind. Now get out of here, I'll handle the cops. + +[RM3_D] +The evidence is being driven across town. + +[RM3_E] +You are going to have to ram that car and collect each little bit of evidence as it falls out. + +[RM3_F] +When you've got it all, leave it in the car and torch it. + +[RM3_G] +We're both gonna do pretty well outta this kid. + +[RM3_1] +~g~Leave the evidence in a car then torch the car. + +[RM3_4] +~g~The Prosecution has dropped the evidence! + +[RM3_6] +~r~The photos will be washed up all over Liberty! + +[RM3_7] +~g~Now torch the car! + +[RM4_A] +I think my partner's a rat. + +[RM4_C] +He goes fishing out of his boat near the lighthouse on Portland Rock most nights. + +[RM4_D] +Steal a police boat and make sure his back stabbing plans are sunk! + +[RM4_1] +~g~Go and steal a police boat. + +[RM4_2] +~g~Get to the lighthouse and 'rub out' Ray's partner! + +[RM5_A] +You useless bastard! + +[RM5_A1] +You totally messed up! My ass is on the line and you can't even kill a god damned fly. + +[RM5_B] +I paid you good money to kill that witness and he ain't dead! + +[RM5_B1] +And today he's gonna make a Federal Deposition! + +[RM5_C] +He's being moved any second now from the Carson General Hospital up in Rockford. + +[RM5_D] +If he squeals, I squeal.... + +[RM5_E] +so go do the job you were paid for! + +[RM5_1] +~g~Intercept the ambulance. + +[RM5_2] +~g~You've been spotted!! + +[RM5_3] +~g~It was a decoy! + +[RM5_4] +~g~Bullets won't get through that armored bodycast!! + +[RM5_5] +~g~That armored bodycast is flame retardant!! + +[RM5_7] +~r~Witness has been delivered!! + +[RM5_8] +~g~Witness has drowned!! + +[LOVE2] +'WAKA-GASHIRA WIPEOUT!' + +[LOVE3] +'A DROP IN THE OCEAN' + +[LOVE1_A] +First of all, let me thank you for dealing with that personal matter. + +[LOVE1_F] +People will read something into anything these days. + +[LOVE1_D] +They're trying to extort additional funds from me but I don't believe in re-negotiation. + +[LOVE1_E] +A deal is a deal, so they'll not see a penny from me. + +[LOVE1_G] +Go and rescue my friend, do whatever it takes. + +[LOVE1_2] +~g~Rescue the Old Oriental Gentleman. + +[LOVE1_3] +~g~Take the Old Oriental Gentleman back to Donald Love's building. + +[LOVE1_4] +~g~The Old Oriental Gentleman must be in one of the garages.... + +[LOVE1_6] +~r~The Old Oriental Gentleman's guts are all over the street! + +[LOVE1_7] +~g~The gate will only open for a Colombian Gang-car. + +[LOVE2_A] +Nothing drives down real estate prices like a good old fashioned gang war, + +[LOVE2_B] +apart from an outbreak of plague......but that might be going too far in this case. + +[LOVE2_C] +I've noticed the Yakuza and the Colombians are far from friends. + +[LOVE2_D] +Let's capitalise on this business opportunity. + +[LOVE2_E] +I want you to kill the Yakuza Waka-gashira, Kenji Kasen. + +[LOVE2_F] +Kenji is attending a meeting at the top of the multi-story carpark in Newport. + +[LOVE2_G] +Get a Cartel gangcar and eliminate him! + +[LOVE2_H] +The Yakuza must blame the Cartel for this declaration of war. + +[LOVE2_1] +~g~Go to Fort Staunton and steal a Colombian gangcar! + +[LOVE2_2] +~g~Now get to the ~p~multi-storey in Newport~g~ and whack Kenji! + +[LOVE2_3] +~r~If you proceed without a Cartel car you will be identified!! + +[LOVE2_4] +~r~The Yakuza have identified you!! + +[LOVE2_6] +~r~You've killed all the witnesses!! + +[LOVE3_A] +In these days of moral hypocrisy certain valuable commodities can be hard to import. + +[LOVE3_C] +It will drop several packages into the water. + +[LOVE3_D] +Make sure you pick them up before anyone else does. + +[LOVE3_1] +~g~Get a ~r~boat~g~ and follow the ~y~plane~g~! + +[LOVE4] +'GRAND THEFT AERO' + +[LOVE5] +'ESCORT SERVICE' + +[LOVE4_A] +Thank you for retrieving those packages, but they were only a decoy. + +[LOVE4_B] +Sorry about that, but that's sometimes the way in business. + +[LOVE4_C] +My real objective was hidden on the plane all along. + +[LOVE4_F] +I've paid off the officials. + +[LOVE4_1] +~r~The Colombian Cartel is here!! + +[LOVE4_2] +~g~The package is gone! Track down the Colombians and retrieve it. + +[LOVE4_3] +~g~Panlantic Construction...? + +[LOVE4_5] +~g~The package should be in the plane.... + +[LOVE4_6] +~g~Take the lift up the tower! + +[LOVE5_B] +My Oriental friend will need an escort while he takes my latest acquisition to be authenticated. + +[LOVE5_1] +~g~Lets go! + +[LOVE5_2] +~g~You'll need a car! + +[LOVE5_3] +~g~Go ahead and scout the exit of the tunnel! + +[LOVE5_4] +~r~Protect the truck! + +[RM6] +'MARKED MAN' + +[RM6_A] +You weren't followed? Good. + +[RM6_B] +This is it, I'm way over my head and I'm starting to drown here! + +[RM6_D] +I'm a marked man, so I'm getting out of here. + +[RM6_E] +Get me to my flight at the airport and I'll make it worth your while! + +[RM6_666] +Take care of my bullet proof Patriot. See you in Miami, Ray + +[CAT1] +'RANSOM' + +[CAT2] +'THE EXCHANGE' + +[CAT1_A] +I've got your precious Maria. If you don't want her face to look like she fell out with the butcher. + +[CAT2_F] +I broke a nail, and my hair's ruined. Can you believe it? This one cost me fifty dollars! + +[CAT2_G] +I was so scared, but then I thought to myself, you're a big girl now. + +[CAT2_H] +Oh we're going to have such fun, cause, you know, my sister said she wanted to come to stay with her two kids, + +[CAT2_I] +because her husband's playing around again and.. + +[CAT1_E] +XXXX + +[CAT1_F] +Get to Catalina before the time runs out! + +[CAT_MON] +~g~You don't have enough money yet. You need $500,000. + +[BITCH_D] +~g~Maria's dead! + +[WEATHER] +FORCE WEATHER + +[WEATHE2] +WEATHER NORMAL + +[8001] +You failed miserably!! + +[1000] +YOU ARE DEAD + +[1001] +YOU ARE DEAD + +[1002] +YOU ARE DEAD + +[1003] +YOU ARE DEAD + +[1004] +YOU ARE DEAD + +[1005] +BUSTED + +[1006] +BUSTED + +[1007] +BUSTED + +[1008] +BUSTED + +[1009] +BUSTED + +[GA_4] +Car bombs are $1000 each + +[GA_5] +Your car is already fitted with a bomb. + +[GA_6] { re3 change } +Park it, prime it by pressing the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ and LEG IT! + +[GA_7] { re3 change } +Arm with ~h~~k~~VEHICLE_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + +[GA_8] +Use the detonator to activate the bomb. + +[GA_9] +You collected ~1~ out of 10 special cars + +[GA_10] +Nice one. Here's your $~1~ + +[GA_11] +We got these wheels already. It's worthless to us! + +[GA_12] +Bomb armed + +[GA_13] +Delivered like a pro. Complete the list and there'll be a bonus for you. + +[GA_14] +All the cars. NICE! Here's a little something. + +[GA_15] +Hope you like the new color. + +[GA_16] +Respray is complementary. + +[GA_19] +We're not interested in that model. + +[GA_20] +We got more of these than we can shift. Sorry man, no deal. + +[CR_1] +Crane cannot lift this vehicle. + +[PU_MONY] +You don't have enough cash. + +[CO_ALL] +You got all of them. Here's a little something... + +[PAUSED] +GAME PAUSED + +[HEALTH1] +Get outta here! You're perfectly healthy. + +[HEALTH2] +Healthcare costs. + +[HEALTH3] +I'll just fix you up. + +[HEALTH4] +That will be $250. + +[FEB_STA] +Stats + +[FEB_BRI] +Briefs + +[FEB_CON] +Controls + +[FEB_AUD] +Audio + +[FEB_DIS] +Display + +[FEB_LAN] +Language + +[FEP_STA] +STATS + +[FEP_BRI] +BRIEFS + +[FEP_CON] +CONTROLS + +[FEP_AUD] +AUDIO + +[FEP_DIS] +DISPLAY + +[FEP_LAN] +LANGUAGE + +[FEF_ST1] +Who's the bad man? + +[FEF_ST2] +How much havoc have you caused + +[FEF_BR1] +Lost the plot? + +[FEF_CO1] +Need more control, freak? + +[FEF_CO2] +Choose the best contoller set-up for your playing style + +[FEF_SA1] +Keep your place in the pile! + +[FEF_SA2] +Save and load your games + +[FEF_AU1] +Pump up the volume! + +[FEF_AU2] +Select a radio station and sound effect + +[FEF_DI1] +Change the game! + +[FEF_DI2] +Customize the game for your TV + +[FEF_LA1] +What you talking about willis? + +[FEF_LA2] +Choose your preferred parlance + +[FEB_PMB] +Previous Mission Briefs: + +[FEC_NA] +NA + +[FEC_CWL] +Cycle Weapon left + +[FEC_CWR] +Cycle Weapon right + +[FEC_LOF] +Look forward + +[FEC_TAR] +Target + +[FEC_MOV] +Movement + +[FEC_CAM] +Camera modes + +[FEC_PAU] +Pause + +[FEC_ENV] +Enter vehicle + +[FEC_JUM] +Jump + +[FEC_ATT] +Attack or Fire weapon + +[FEC_RUN] +Run + +[FEC_FPC] +First person camera + +[FEC_LL] +Look left + +[FEC_LB1] +Look + +[FEC_LB2] +behind + +[FEC_LB] +Look behind + +[FEC_LR] +Look right + +[FEC_HOR] +Horn + +[FEC_VES] +Vehicle control + +[FEC_RSC] +Radio station cycle + +[FEC_BRA] +Brake or Reverse + +[FEC_HAB] +Hand brake + +[FEC_CAW] +Car weapon + +[FEC_ACC] +Accelerate + +[FEC_SMT] +Special mission trigger + +[FEA_OUT] +Output: + +[FEA_ST] +Stereo + +[FEA_MNO] +Mono + +[FEA_NON] +None + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Debug Streaming Requests + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Memory Card Menu + +[FEM_RMC] +Register MemCard One + +[FEM_TFM] +Test Format MemCard One + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_CRD] +Create Root Dir + +[FEM_CLI] +Create And Load Icons + +[FEM_FFF] +Fill First File with Guff + +[FEM_SOG] +Save Only The Game + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_STG] +Save The Game + +[FEM_STS] +Save The Game under GTA3 name + +[FEM_CPD] +Create copy protected mag directory + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_TS] +Test Save: + +[FEM_TL] +Test Load: + +[FEM_TD] +Test Delete: + +[PL_STAT] +Player stats + +[PE_WAST] +People you've wasted + +[PE_WSOT] +People wasted by others + +[CAR_EXP] +Cars exploded + +[TM_BUST] +Times busted + +[M_WASTE] +Civilian males wasted + +[F_WASTE] +Civilian females wasted + +[PIG_WST] +Cops wasted + +[GNG_WST] +Gang members wasted + +[MED_WST] +Medics wasted + +[FIRE_WS] +Firemen wasted + +[DED_CRI] +Criminals wasted + +[DED_DED] +Deadbeats wasted + +[DED_HOK] +Hookers wasted + +[HEL_DST] +Helicopters destroyed + +[PER_COM] +Percentage completed + +[KGS_EXP] +Kgs of explosives used + +[ACCURA] +Accuracy + +[ELBURRO] +Best Turismo time in secs + +[CAR_CRU] +Cars crushed + +[HED_EX] +Heads exploded + +[TM_DED] +Hospital visits + +[DAYSPS] +Days passed in game + +[MMRAIN] +Mm rain fallen + +[MXCARD] +Max. INSANE Jump dist. (ft) + +[MXCARJ] +Max. INSANE Jump height (ft) + +[MXCARDM] +Max. INSANE Jump dist. (m) + +[MXCARJM] +Max. INSANE Jump height (m) + +[MXFLIP] +Max. INSANE Jump flips + +[MXJUMP] +Max. INSANE Jump rotation + +[BSTSTU] +Best INSANE stunt so far: + +[INSTUN] +Insane stunt + +[PRINST] +Perfect insane stunt + +[DBINST] +Double insane stunt + +[DBPINS] +Perfect double insane stunt + +[TRINST] +Triple insane stunt + +[PRTRST] +Perfect triple insane stunt + +[QUINST] +Quadruple insane stunt + +[PQUINS] +Perfect quadruple insane stunt + +[NOSTUC] +No INSANE stunts completed + +[NOUNIF] +Unique Jumps completed + +[NOUNGM] +Total Unique Jumps + +[NMISON] +Mission attempts + +[NMMISP] +Missions passed + +[PASDRO] +Passengers dropped off + +[MONTAX] +Cash made in taxi + +[DAYPLC] +Daily police spending + +[CRIMRA] +Criminal rating: + +[GMSTOR] +Game Store + +[PREBRF] +Previous Briefs + +[CNTLS] +Controls + +[MUSMEN] +Music/SFX + +[GAMSET] +Game Settings + +[LANGUA] +Language + +[DSPLAY] +Display + +[DEBUGM] +Debug Menu + +[QUITOP] +Quit Options + +[CONTRL] +Control Configuration + +[SET1EN] +SetUp 1. Enabled + +[SET1] +SetUp 1. + +[SET2EN] +SetUp 2. Enabled + +[SET2] +SetUp 2 + +[SET3EN] +SetUp 3. Enabled + +[SET3] +SetUp 3 + +[SET4EN] +SetUp 4. Enabled + +[SET4] +SetUp 4 + +[GOBACK] +GoBack + +[SOUND] +SOUND + +[MUSVOL] +Music Volume + +[SFXVOL] +SFX Volume + +[SCROPT] +SCREEN OPTIONS + +[CTRSCR] +Center Screen + +[SCRFOR] +Screen format + +[GMSVLQ] +GAME SAVE-LOAD-QUIT + +[GMREST] +Restart Game + +[NOGMSV] +Can save only at your hideout. + +[DLFILE] +Delete Grand Theft Auto III Files + +[CHFILE] +CHOOSE FILE TO LOAD + +[CHFIDL] +CHOOSE FILE TO DELETE + +[SVCONF] +SAVE CONFIRMATION + +[LANGSL] +LANGUAGE SELECTION + +[ENGLIS] +English + +[GERMAN] +German + +[ITALIA] +Italian + +[FRENCH] +French + +[SPAIN] +Spanish + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (teststuff) + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[GORLEV] +Gore Level + +[SICASS] +Sick Fuck + +[SICSIC] +Sick Fucker + +[SCASSL] +Sick Fuck Selected + +[SCSCSL] +Sick Fucker Selected + +[PRVMEN] +Previous Mission Briefs + +[FORMEN] +Format Menu + +[MEMTST] +MemoryCardTest screen + +[REGCAR] +Register MemoryCard One + +[TEFONE] +Test Format MemCard One + +[TEUFON] +Test UnFormat MemCard One + +[CRROOT] +CreateRootDir + +[CRLDIC] +Create and Load Icons + +[FLFSGF] +Fill First File With Guff + +[PUSAVE] +Save Only the game + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +SaveTheGame + +[CNTSAV] +Can't save the game. On a mission. + +[CNCSAV] +Can't save the game. You're in a car + +[CRMGSV] +Create copy protected magazine directory + +[MGSVCN] +MagazineDirectory Created + +[MGSVNC] +MagazineDirectory Not Created + +[YES] +Yes + +[NO] +No + +[X] +x + +[LAST] +Last message. + +[FEDS_XB] +Select + +[FEDS_ST] +START button - RESUME + +[FEST_OO] +out of + +[FEC_TUC] +Turret control + +[FEC_SM3] +Special mission trigger (R3 button) + +[FEC_RS3] +Radio station cycle (L3 button) + +[FEC_HO3] +Horn (L3 button) + +[DIAB1] +'TURISMO' + +[DIAB2] +'I SCREAM, YOU SCREAM' + +[DIAB3] +'TRIAL BY FIRE' + +[DIAB4] +'BIG'N'VEINY' + +[DIAB1_A] +El Burro wants to offer you an opportunity. Get to the payphone in Hepburn Heights if you want more info. + +[DIAB1_C] +You drive a mean race. Drop by the payphone again and 'El Burro' may have some work for you. + +[DIAB1_1] +~g~3..2..1.. GO GO GO! + +[DIAB1_4] +~g~Get a fast car and get to the starting grid. + +[DIAB1_3] +~r~You couldn't win a raffle, LOSER! + +[DIAB1_2] +~g~Congratulations you won, with an incredible time of ~1~ seconds. + +[FIRST] +~g~1st + +[SECOND] +~g~2nd + +[THIRD] +~g~3rd + +[FOURTH] +~g~4th + +[DIAB2_1] +~g~Pick up the briefcase in Harwood. + +[DIAB2_2] +~g~Find an icecream van. + +[DIAB2_3] +~g~Park the icecream van down at Atlantic Quays. + +[DIAB2_4] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_6] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_7] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_5] +~g~Exit the van then use the remote to detonate the Icecream van. + +[YD1] +'BLING-BLING SCRAMBLE' + +[YD2] +'UZI RIDER' + +[YD3] +'GANGCAR ROUND-UP' + +[YD4] +'KINGDOM COME' + +[YD_P] +King Courtney would like a word. Get to the payphone in Aspatria!! + +[YD1_A] +~w~This is King Courtney. + +[YD1_A1] +~w~My Yardie posse could do with a driver and you've got a reputation for hot moves. + +[YD1_B] +~w~Get to the waste ground opposite the stadium in a car and wait for the other hopefuls. + +[YD1_C] +~w~I've got men watching checkpoints all over Staunton. + +[YD1_D] +~w~First driver to a checkpoint gets a Grand, then it's on to the next stop. + +[YD1_D1] +~w~If you get more checkpoints than any other driver, I could have some work for you. + +[YD1_E] +~g~Prepare to race! + +[YD1_F] +~g~You jumped the start -I like your style!! + +[YD1_G] +~r~This is a CAR RACE. You need a CAR fool! + +[YD1GO] +~g~GO!! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000!! + +[Y1_1ST] +~g~You came first with ~1~ successful checkpoints! + +[Y1_2ND] +~y~2nd with ~1~ successful checkpoints. ~y~Close, but you just ain't the best! + +[Y1_3RD] +~r~3rd with ~1~ successful checkpoints. ~r~I thought you said you were good! + +[Y1_LAST] +~r~You were last! ~r~You wasted my time FOOL! + +[Y1_J1ST] +~y~Joint 1st with ~1~ successful checkpoints. ~y~Good, but you gotta be the best to drive for Queen Lizzy! + +[Y1_J2ND] +~r~Joint 2nd with ~1~ successful checkpoints. You drive like a crazed monkey! + +[Y1JLAST] +~r~Joint last! You talk like a driver, but you drive like a talker! + +[Y1_TEST] +CAR IN WATER!! + +[YD2_A] +~w~I need to see if you're capable of doing my dirty work. + +[YD2_A1] +~w~See if you can be trusted. + +[YD2_B] +~w~Two of my boys will be there any second to take you for a ride, + +[YD2_B1] +~w~see if you are who you say you are. + +[YD2_C] +~w~We're going for a little ride into Hepburn Heights, whack us some filthy Diablos been dissing Queen Lizzy. + +[YD2_CC] +~w~Here, you'll need a 'piece'. + +[YD2_D] +~w~You do the driving and shooting. We'll make sure you don't get cold feet. + +[YD2_E] +~w~Let's drive!! + +[YD2_F] +~r~He's bailed out on us, cap his yellow ass!!! + +[YD2_G1] +~w~Hepburn Heights..Let's kill me some filthy Diablos... + +[YD2_G2] +~w~But remember, ~r~You don't leave this car!! + +[YD2_H] +~w~OK, Get us back to Yardie turf! GO GO GO!! + +[YD2_L] +~w~You did good, Reaperman! + +[YD2_M] +~r~He's wrecked my car! Waste him! + +[YD2_N] +~w~Get your ass back in this car! + +[YD3_A] +I want you to boost some gang cars + +[YD3_A1] +so we can do hits on our enemies' turf. + +[YD3_B] +I need a Mafia Sentinel, + +[YD3_B1] +a Yakuza Stinger and a + +[YD3_B2] +Diablo Stallion so we can hit any gang in Liberty. + +[YD3_C] +Drop them off at the garage in Newport and remember, + +[YD3_C1] +they're no use to us wrecked!! + +[YD3_D] +Spare text label + +[YD3_E] +~r~You've already boosted a diablo gangcar! + +[YD3_F] +~r~You've already boosted a Mafia gangcar! + +[YD3_G] +~r~You've already boosted a Yakuza gangcar! + +[YD3_H] +~g~Diablo gangcar boosted! + +[YD3_I] +~g~Mafia gangcar boosted! + +[YD3_J] +~g~Yakuza gangcar boosted! + +[YD3_K] +~r~The car's nearly wrecked! Get it repaired! + +[YD3_L] +~g~Take it to the ~p~garage! + +[YD3_M] +~r~You've flipped it! Get another one! + +[YD4_A] +Listen up! + +[YD4_A1] +Get over to Bedford Point. + +[YD4_A2] +There's a stash in an old car I need pronto! + +[YD4_B] +LETTER: I hear you've been a busy boy. Well I've been a busy girl. + +[YD4_C] +I think it's time you witnessed the real power of 'SPANK'! Besos y fuderes, Catalina, xxx. + +[YD4_D] +PS: DIE PEEG DOG, DIE!! + +[YD4_1] +~g~SPANKED-up madmen! + +[YD4_2] +~g~Destroy the madmens' vans!! + +[HM_1] +'UZI MONEY' + +[HM_2] +'TOYMINATOR' + +[HM_3] +'RIGGED TO BLOW' + +[HM_5] +'RUMBLE' + +[HOOD1_A] +Get to the payphone in Wichita Gardens and we'll talk business. + +[HM1_A] +Yo! This is D-Ice of the Red Jacks! + +[HM1_C] +These young punks, they come onto the streets and they got nothing but guns and SPANK on their minds. + +[HM1_3] +~g~The 'Nines' walk their turf in Wichita Gardens. + +[HM2_3] +If you hit a vehicle's wheels the RC buggy will detonate! + +[HM2_4] +If it goes out of range the RC buggy will detonate! + +[HM2_5] +~r~Out of range! + +[HM3_1] +~g~Get to the garage but watch out if the car takes too much damage it will blow! + +[HM3_2] +~g~Take the car back it has to be in perfect condition - no damage! + +[HM3_3] +~g~Get the car repaired! + +[HM4_D] +~g~Get a vehicle! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_1] +~g~Head to the location where the cargo is scattered, you need to collect 30 pieces of bullion. + +[HM4_2] +~g~Remember when the vehicle becomes too heavy and slow goto the garage and drop off the cargo. + +[HM5_3] +~r~You were told to use a baseball bat only! + +[HM5_4] +~r~Your contact's dead! + +[MEA1] +'THE CROOK' + +[MEA2] +'THE THIEVES' + +[MEA3] +'THE WIFE' + +[MEA4] +'HER LOVER' + +[MEAT1_A] +A friend said you could fix some problems I got. Get to the payphone in Trenton if you think you can help. + +[MEA1_B3] +~g~Go and meet the Bank Manager. + +[MEA1_B6] +~g~Take the car to the crusher to get rid of evidence, get out of the car and the crane will pick it up. + +[MEA1_1] +~r~The Bank Manager's dead! + +[MEA1_2] +~r~You were told to crush the vehicle! + +[MEA1_3] +~g~Get out of the car! + +[MEA1_4] +~r~You have left the Bank Manager behind! + +[MEA2_B3] +~g~Go and meet the thieves. + +[MEA2_B4] +~g~Take them to the Bitch'n' Dog Food Factory + +[MEA2_B6] +~g~Get the car resprayed to get rid of any evidence. + +[MEA2_1] +~r~You were told to crush the vehicle! + +[MEA2_2] +~r~A thief's dead! + +[MEA2_4] +~r~You have left a thief behind! + +[MEA3_B3] +~g~Go and collect Mrs. Chonks + +[MEA3_B6] +~g~Take the car and dump it into the sea, this will get rid of any evidence. + +[MEA3_1] +~r~The wife's dead! + +[MEA3_2] +~r~You were supposed to dump the vehicle in the water! + +[MEA3_3] +~r~You have left his wife behind! + +[MEA4_B3] +~g~Pick up his wife's lover + +[MEA4_B6] +It's far too late for that Marty. You had your chance, but now I'm taking over the business... + +[MEA4_1] +~r~Carlos is dead! + +[MEA4_3] +~r~You have left Carlos the loan shark behind! + +[LOOK_A] +Press and hold the ~h~~k~~VEHICLE_LOOKLEFT~ button ~w~or the ~h~~k~~VEHICLE_LOOKRIGHT~ button~w~ to look ~h~left~w~ or ~h~right~w~ while in a vehicle. Press both to look ~h~behind~w~. + +[LOVE6_1] +~g~Now lead the cops away from the warehouse! + +[LOVE6_2] +~r~You failed to lead the police far enough away! + +[RM4_3] +~r~Ray's partner has escaped! + +[RM6_C] +The CIA seem to have a vested interest in SPANK + +[RM6_C1] +and they don't like us screwing with the Cartel. + +[C_PASS] +THREAT ELIMINATED! + +[CTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Vigilante missions on or off. + +[CTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Vigilante missions on or off. + +[COPCART] +~g~You have ~1~ seconds to return to a police vehicle before the mission ends. + +[C_FAIL] +Vigilante mission ended! + +[C_CANC] +~r~Vigilante mission cancelled! + +[C_ESCP] +~r~The suspect has escaped! + +[C_TIME] +~r~Your time as a law enforcer is over! + +[C_VIGIL] +VIGILANTE BONUS!! + +[A_FAIL2] +~r~Your lack of urgency has been fatal to the patient! + +[A_FAIL3] +~r~The patient is dead!! + +[A_PASS] +Rescued! + +[F_FAIL2] +~r~You're too late! + +[A_COMP2] +You will never get tired! + +[RM2_M] +If you need any firepower just drop by and take what you need from the lockers. + +[HEAL_A] +Your ~h~health~w~ is displayed in orange in the top right of the screen. + +[YD1_CNT] +~1~ of 15! + +[FM1_9] +~g~Thats the party up ahead, drop Maria off out front. + +[FM1_Y] +~w~You know I enjoyed myself for the first time in a long while, and you treated me really good. With respect and everything. + +[FM1_AA] +~w~Oh, I'd better go. I'll see you around I hope. + +[NOCONTE] +Please re-insert the analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2) in controller port 1 to continue + +[WRCONT] +The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto III requires an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). + +[WRCONTE] +The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto III requires an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). + +[WRONGCD] +Incorrect disc. Please insert correct disc. + +[NOCD] +The disc tray is empty. Please insert disc. + +[OPENCD] +The disc tray is open. Please close the disc tray. + +[CDERROR] +Error reading the Grand Theft Auto III DVD + +[RESTART] +Starting new game + +[GA_3] +No more freebies. $1000 to respray! + +[GA_1] +Whoa! I don't touch nothing THAT hot! + +[GA_1A] +Come back when you're not so busy... + +[S_PROM2] +The garage next door can store one vehicle when you save your game. + +[STOCK] +out of stock + +[FM1_O] +~w~He's at the rail station at the Chinatown waterfront I think. + +[EBAL_B] +This is the place right here, let's get off the street and find a change of clothes! + +[EBAL_G] +This is Luigi's club. Let's go round the back and use the service door. + +[AM4_3] +You must be Asuka's new errand boy! + +[AM4_4] +You got the money? Is it all here? + +[AM4_5] +I know what you're thinking, another bent cop. + +[AM4_6] +Well, it's a bent world. + +[AM4_7] +Just 'cause I lost a few partners, those suckers from internal affairs have started sniffing around. + +[AM4_8] +Reckon they can smell me. + +[AM4_9] +Well, this city is just one big open sewer. + +[AM4_10] +But I'm gonna need some non-union help. + +[AM4_11] +And if you're interested you'll know where to find me. + +[CAM_A] +Press the ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~ button~w~ to change ~h~camera ~w~modes when on foot or in a vehicle. + +[CAM_B] +Press the ~h~directional button up~w~ and ~h~down~w~ to change ~h~camera ~w~modes when on foot or in a vehicle. + +[KM2_1] +~g~Repair the car, it's gotta be mint. + +[LM3_6] +Joey... + +[LM3_6A] +Am I goin' to play with your big end again? + +[LM3_9A] +there might be some work for you. + +[LM3_9B] +Alright? + +[AWAY2] +~r~They got away. + +[AWAY] +~r~He's clean out of here! + +[JM6_1] +Get to the bank on the main drag. + +[GA_6B] { re3 change } +Park it, prime it by pressing the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ and LEG IT! + +[GA_7B] { re3 change } +Arm with ~h~~k~~VEHICLE_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + +[BAT1] +~g~Pick up the bat! + +[EBAL_O] +If you don't mess this up, maybe there be more work for you. Now get outta here! + +[HELP9_B] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP9_C] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[JM6_8] +~r~You've lost all the robbers! + +[COLT_IN] +The Pistol is now in stock at Ammunation! + +[TAXI2] +~r~You're out of time! + +[TAXI3] +~r~Your passenger fled in terror! + +[TAXI7] +~r~Your car is trashed, get it repaired. + +[TAXI4] +Fare complete! + +[TAXI5] +SPEED BONUS!! + +[TAXI6] +Taxi mission over + +[FRANGO] +~g~Salvatore wants you to help Toni deal with the Triads first! + +[PAGEB12] +Police Bribe delivered to hideout + +[PAGEB13] +Health delivered to hideout + +[PAGEB14] +Adrenaline delivered to hideout + +[KM1_4] +~g~You need a cop car to do the job! + +[CAT1_B] +bring $500,000 to the Villa at Cedar Grove. + +[JM2_C] +He's got a noodle stand down in China Town. + +[RM6_1] +Here's a key to a lock-up. + +[RM6_2] +You'll find some cash and some 'supplies' I'd stashed in case things got tight. + +[RM6_3] +See y'around. + +[FE_INIP] +Initialising and loading Pause Menu... Please wait. + +[FESZ_CA] +Cancel + +[FESZ_QU] +Quit + +[FESZ_L1] +Game saved successfully! + +[FESZ_L2] +Your saved filename is: + +[FESZ_OK] +OK + +[FES_LGA] +Load Game + +[FES_NGA] +New Game + +[FES_CAN] +Cancel + +[FESZ_QL] +All unsaved progress in your current game will be lost. Proceed with loading? + +[FESZ_QD] +Proceed with deleting this saved game? + +[FESZ_QO] +Proceed with overwriting this saved game? + +[FESZ_QR] +Are you sure you want to start a new game? All progress since the last save game will be lost. Proceed? + +[FESZ_QS] +PROCEED WITH SAVE ? + +[T4X4_1] +'PATRIOT PLAYGROUND' + +[T4X4_2] +'A RIDE IN THE PARK' + +[T4X4_3] +'GRIPPED!' + +[MM_1] +'MULTISTOREY MAYHEM' + +[T4X4_1A] +~g~You have ~y~5 minutes~g~ to collect ~y~15~g~ checkpoints. ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_1B] +~1~ of 15! + +[T4X4_1C] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~20 SECONDS~g~. + +[T4X4_2A] +~g~You have ~y~2 minutes~g~ to collect ~y~12~g~ checkpoints!! ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_2B] +~1~ of 12! + +[T4X4_2C] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~10 SECONDS~g~. + +[T4X4_3A] +~g~You have ~y~5 minutes~g~ to collect ~y~20~g~ checkpoints. ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_3B] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~15 SECONDS~g~. + +[T4X4_3C] +~1~ of 20! + +[T4X4_F] +~r~You bailed! Too tough for you?! + +[MM_1_A] +~g~You have ~y~2 minutes~g~ to collect ~y~20 checkpoints~g~ in the multistorey! ~g~You may collect them in ~y~ANY ORDER. + +[MM_1_B] +~1~ of 20! + +[MM_1_C] +~g~That's 20 seconds, plus ~y~5 SECONDS~g~ for each checkpoint. ~g~The timer will start ~y~IMMEDIATELY. + +[FM2_14] +~r~You got too close and spooked Curly! + +[FM2_15] +~g~Don't get too close or Curly will suspect something! + +[UPSIDE] +~r~You flipped your wheels! + +[FM2_16] +SPOOKOMETER: + +[LM3_11] +~g~Misty won't ride in a bus get another vehicle! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Firetruck + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulance + +[FBICAR] +Fbi Car + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Police + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Train + +[HELI] +Helicopter + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[BELLYUP] +Triad Fish Van + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Mafia Sentinel + +[YARDICR] +Yardie Lobo + +[YAKUZCR] +Yakuza Stinger + +[DIABLCR] +Diablo Stallion + +[COLOMCR] +Cartel Cruiser + +[HOODSCR] +Hoods Rumpo XL + +[AEROPL] +Aeroplane + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Dist. travelled on foot (miles) + +[FEST_DC] +Dist. travelled by car (miles) + +[FESTDFM] +Distance travelled on foot (m) + +[FESTDCM] +Distance travelled by car (m) + +[FEST_R1] +Patriot Playground in secs + +[FEST_R2] +A Ride In The Park in secs + +[FEST_R3] +Gripped! in secs + +[FEST_RM] +Multistorey Mayhem in secs + +[FEST_LS] +People saved in an Ambulance + +[FEST_CC] +Criminals killed on Vigilante Mission + +[FEST_FE] +Total fires extinguished + +[FEST_LF] +Longest flight in Dodo + +[FEST_BD] +Best time for bomb defusal + +[FEST_RP] +Rampages passed + +[FEST_MP] +Missions passed + +[FEST_BB] +Bling-bling Scramble: + +[FEST_H0] +Most checkpoints + +[FEST_GC] +Gang Cars Totalled: + +[FEST_H1] +Diablo destruction + +[FEST_H2] +Mafia Massacre + +[FEST_H3] +Casino Calamity + +[FEST_H4] +Rumpo Wrecker + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ] +UNIQUE STUNT BONUS! + +[SPRAY] +Drive your vehicle into the spray shop to lose your ~h~wanted level~w~, ~h~repair ~w~and ~h~respray ~w~your vehicle. Cost - ~h~$1000. + +[HM1_1] +~g~Ice 20 Purple Nines in 2 minutes 30 seconds. + +[KM1_8A] { re3 change } +Press the~h~ ~k~~VEHICLE_FIREWEAPON~ button~w~ to ~h~activate the bomb,~w~ remember to get out of the way. + +[KM1_8D] { re3 change } +Press the~h~ ~k~~VEHICLE_FIREWEAPON~ button~w~ to ~h~activate the bomb,~w~ remember to get out of the way. + +[KM1_12] +~g~Get him to the dojo but get rid of the cops first! + +[RATNG1] +Pickpocket + +[RATNG2] +Bully + +[RATNG3] +Thug + +[RATNG4] +Hustler + +[RATNG5] +Goon + +[RATNG6] +Wheelman + +[RATNG7] +Hired muscle + +[RATNG8] +Fixer + +[RATNG9] +Associate + +[RATNG10] +Cleaner + +[RATNG11] +Assassin + +[RATNG12] +Right-hand man + +[RATNG13] +Executioner + +[RATNG14] +Capo + +[RATNG15] +Boss + +[1010] +~r~Your vehicle is upside down + +[1011] +~r~Your vehicle is upside down + +[1012] +~r~Your vehicle is upside down + +[1013] +~r~Your vehicle is upside down + +[1014] +~r~Your vehicle is upside down + +[JM4_10] +OK, Kid. Drive me to the laundry in Chinatown first, I got a bit of business to take care of. + +[JM4_11] +Those washer women aint been payin' their protection money. + +[JM4_12] +And watch the car, Joey just fixed this junk heap. + +[JM4_13] +So no fancy crap, OK? + +[KM4_11] +~g~Take the money back to the casino! + +[FEF_BR2] +Find it again by reading any mission briefs collected to date. + +[TRAIN_1] +Kurowski Station + +[TRAIN_2] +Rothwell Station + +[TRAIN_3] +Baillie Station + +[SUBWAY1] +Portland Station + +[SUBWAY2] +Rockford Station + +[SUBWAY3] +Staunton South Station + +[SUBWAY4] +Shoreside Terminal + +[MEA4_2] +~r~Marty Chonks is dead! + +[SPRAY1] +Drive your vehicle into the spray shop to lose your ~h~wanted level~w~, ~h~repair ~w~and ~h~respray ~w~your vehicle. Cost - ~h~$1000~w~. This time it's free. + +[JM4_A] +Yeah, I know Toni, I've tuned her real sweet. She purrs, you know what I mean? + +[JM4_5] +Drop by later and we'll give them something to launder, their own blood stained clothes! + +[AMMU_A] +Luigi said you'd need a piece... + +[AMMU_B] +Joey told me to tool you up... + +[AMMU_C] +So go around back of the shop. I left you a nine in the yard. + +[AMMU_D] +I got all your home defence needs. + +[AMMU_E] +You want a license too? + +[AMMU_F] +I don't need to see any I.D. you look trustworthy. + +[DETON] +DETONATION: + +[DRIVE_A] { re3 change } +Have an Uzi selected when entering a vehicle then look left or right and press the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ to fire. + +[DRIVE_B] { re3 change } +Have an Uzi selected when entering a vehicle then look left or right and press the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ to fire. + +[RECORD] +~g~NEW RECORD!! + +[NRECORD] +~r~NO NEW RECORD! + +[RCHELP] { re3 change } +Press ~k~~VEHICLE_FIREWEAPON~, or drive the RC car into a vehicle's wheels to detonate. + +[RCHELPA] { re3 change } +Press the ~k~~VEHICLE_FIREWEAPON~ button, or drive the RC car into a vehicle's wheels to detonate. + +[RC_1] +You have 2 minutes to blow up as many Diablo Gang Cars as possible! + +[RC_2] +You have 2 minutes to blow up as many Mafia Gang Cars as possible! + +[RC_3] +You have 2 minutes to blow up as many Yakuza Gang Cars as possible! + +[RC_4] +You have 2 minutes to blow up as many Yardie Gang Cars as possible! + +[RC_5] +You have 2 minutes to blow up as many Hood Gang Cars as possible! + +[RC_6] +You have 2 minutes to blow up as many Cartel Gang Cars as possible! + +[RAMPAGE] +RAMPAGE!! + +[RAMP_P] +RAMPAGE COMPLETE! + +[RAMP_F] +RAMPAGE FAILED + +[PAGE_00] +. + +[PAGE_01] +Murder ~1~ Diablos in 120 seconds! + +[PAGE_02] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_03] +Kill ~1~ Mafia in 120 seconds! + +[PAGE_04] +Kill ~1~ Triads in 120 seconds! + +[PAGE_05] +Kill ~1~ Triads in 120 seconds! + +[PAGE_06] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_07] +Pop ~1~ Yardie heads in 120 seconds! + +[PAGE_08] +Burn ~1~ Yakuza in 120 seconds! + +[PAGE_09] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_10] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_11] +Annihialate ~1~ Yardies in 120 seconds! + +[PAGE_12] +Torch ~1~ Yakuza in 120 seconds! + +[PAGE_13] +Explode ~1~ Yardies in 120 seconds! + +[PAGE_14] +Fry ~1~ Colombians in 120 seconds! + +[PAGE_15] +Splatter ~1~ Hoods in 120 seconds! + +[PAGE_16] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_17] +Splatter ~1~ Colombians with a car in 120 seconds! + +[PAGE_18] +Driveby and Destroy ~1~ vehicles in 120 seconds! + +[PAGE_19] +Remove ~1~ Colombian heads in 120 seconds! + +[PAGE_20] +Behead ~1~ Hoods in 120 seconds! + +[JM1_A] +Hey, I'm bored when you gonna drill me? + +[JM1_B] +In a moment sweet heart, I got a little business to take care of. + +[JM1_C] +I got a little job for you pal. + +[JM1_D] +The Forelli brothers have owed me money for too long + +[JM1_E] +and they need to be taught some respect. + +[JM1_F] +Lips Forelli is stuffing his fat face in St Marks Bistro, + +[JM1_G] +so steal his car and take it to 8-Ball's bomb shop up in Harwood. + +[JM1_H] +You know 8-Ball right? + +[JM1_I] +Once he's fitted it with a bomb, go park the car where you found it. + +[JM1_J] +Then sit back and watch the whole show. + +[JM1_K] +But hurry up, he won't be eating forever. + +[CAT2_A1] +Come on you dumb bitch! + +[CAT2_A] +The real question is, did you turn up to rescue Maria or to get me back? + +[CAT2_B] +Well I got news for you, + +[CAT2_B2] +shooting you will be a pleasure but dating you was only business. + +[CAT2_C] +You are muy peccinno amigo! + +[CAT2_D] +Throw over the cash. + +[CAT2_E] +You have been a busy boy! + +[CAT2_E2] +But you haven't learned, I'm not to be trusted. + +[CAT2_E3] +Kill the idiot. + +[CAT2_J] +Get this thing airborne!! + +[HM5_1] +Yo, Ice said was comin'. There rules. Bats only. No guns, no cars. + +[HM5_5] +This is a battle for respect, you cool? + +[HELP14] +To collect weapons walk through them. These cannot be collected while in a vehicle. + +[CRUSH] +Park in the marked area and exit your vehicle. The vehicle will then be crushed. + +[DIAB2_B] +A gang of no-goods has threatened to remove my starring member if I don't pay them a cut. + +[DIAB2_C] +They threaten the wrong man, amigo. + +[DIAB2_D] +They have a weakness for the icecream. + +[DIAB2_E] +Pick up the bomb I've hidden in Harwood, + +[DIAB2_F] +hijack the regular icecream van on its rounds. + +[DIAB2_G] +and lure these fools to their doom with the jeengle-jeengle. + +[DIAB2_H] +They hide in a warehouse on Atlantic Quay. + +[DIAB3_A] +Some insolent Triads stole my beautiful car last night, + +[DIAB3_B] +wrecked it and left it burning. + +[DIAB3_C] +Some of my most precious donkey memorabilia was in the trunk - + +[DIAB3_D] +real collectibles that are irreplaceable my friend. + +[DIAB3_E] +I've hidden a throbbing weapon on the edge of Chinatown. + +[DIAB3_F] +Take it and teach these Triad vandals to fear El Burro's well-endowed wrath. + +[DIAB3_1] +KILL 25 TRIADS + +[DIAB4_A] +A thieving opportunist has stolen a van of my latest publication hot off the press! + +[DIAB4_B] +But that SPANKED-up idiot has left the rear doors open + +[DIAB4_C] +and now my beautifully produced, + +[DIAB4_D] +tastefully photographed adult literature is being dropped all over Liberty! + +[DIAB4_E] +Take the van and follow that trail of Donkey Does Dallas volumes 1, 2 and 3 + +[DIAB4_F] +collecting it as you go. + +[DIAB4_G] +When you've followed the trail to that thieving SPANK-head, waste him! + +[DIAB4_H] +Then deliver my donkey derby to XXX Mags in the Red Light District. + +[DIAB4_1] +~g~Take the van to the back of XXX Magazines. + +[HM1_E] +I want you to show these punk ass bitches how a real drive-by works. + +[HM1_H] +Take these nines off of here!! + +[HM2_A] +Those Nines are pressing me. + +[HM2_B] +These Bitches got armored cars and now they're running SPANK... + +[HM2_C] +and slinging it to brothers with no fear. + +[HM2_D] +There's a car parked up the way. + +[HM2_E] +There's some stuff in there to put these sissys on blass... + +[HM3_A] +Some effa has wired my wheels to blow. + +[HM3_B] +If I lose those wheels, my rep on the street will be dead. + +[HM3_C] +Pick up my car and take it over to the garage on St. Marks, a'right yo. + +[HM3_D] +Let them diffuse that, let them take care of that bomb. + +[HM3_E] +The clocks ticking and the wiring is messed up. + +[HM3_F] +One pot hole too many and that thing could blow. + +[HM3_G] +Now move it! + +[HM4_A] +Yo, a Federal Reserve flight just smashed down at Francis International. + +[HM4_B] +There's platinum all over the strip. + +[HM4_C] +Get a car and snatch up as much as you can. + +[HM4_F] +You can drop the bling off at one of my garages. + +[HM4_G] +This platinum is mad heavy and it will slow your wheels down some. + +[HM4_H] +So make regular drop off's at the garage. + +[HM5_A] +Them Nines are down to a few scabby herds... + +[HM5_B] +but they still wanna bring it. + +[HM5_C] +They agreed to go toe to toe. + +[HM5_D] +A gang of them against two of us, or rather... + +[HM5_E] +two of yaw + +[HM5_F] +I'd join you but... + +[HM5_G] +I ain't due my parole hearing for another three months now, + +[HM5_H] +y'know what I mean? + +[HM5_I] +Go and meet my baby brother, + +[HM5_J] +He'll show you where they are fighting a'right son. + +[MEA1_B] +The name's Chonks, Marty Chonks. + +[MEA1_C] +I run the Bitchin' Dog Food factory around the corner. + +[MEA1_D] +I got money troubles, but hey, who doesn't right? + +[MEA1_E] +I'm meeting my bank manager later. + +[MEA1_F] +He's a crooked bastard that keeps bumping up the loan repayments so he can cut a slice. + +[MEA1_G] +Take my car, pick him up and bring him back here. + +[MEA1_H] +I've got a little surprise for that blood sucking leech!! + +[MEA2_A] +I hired some thieves to break into my apartment... + +[MEA2_C] +The thieving bastards are threatening to tell the insurance company, + +[MEA2_D] +if I don't give them a cut. + +[MEA2_E] +Can you believe it? + +[MEA2_F] +I've left a car inside the factory gates. + +[MEA2_G] +Use it to go and pick them up from their turf in the Red Light district. + +[MEA2_H] +Then bring 'em back to the factory so I can make 'em see Marty's point of view. + +[MEA3_A] +The business is going to go under unless I get hold of some serious cash soon. + +[MEA3_B] +My wife has an insurance policy and all she's ever been to me is a hole in my pocket. + +[MEA3_C] +I've left a car in the usual place. + +[MEA3_D] +Go and pick up my wife from Classic Nails and bring her back to the factory. + +[MEA4_A] +Damn, I'm in trouble! + +[MEA4_B] +Turns out my wife was seeing some guy I owe money to. + +[MEA4_C] +He's got real angry and he's looking for payback! + +[MEA4_E] +he thinks I'm gonna pay him off... + +[MEA4_F] +but my guess is... + +[MEA4_G] +Liberty's dogs are gonna get yet another flavor this month! + +[WELCOME] +WELCOME TO + +[HM1_2] +~g~Get a vehicle, remember only Uzi drive by kills count! + +[HELP8_B] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ button ~w~to ~h~zoom in ~w~with the rifle and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ button ~w~to ~h~zoom out ~w~again. + +[LRQC_1] +Asuka and I are gonna have to talk, uh, + +[LRQC_2] +Why don't you go cruise around? + +[LRQC_3] +You'll need a place to lie low. + +[LRQC_4] +There's a warehouse at the edge of Belleville that should suit your needs. + +[LRQC_5] +Come back here to my Condo when you are ready, + +[LRQC_6] +and we can have a little chat. + +[JM6_5] +~g~You need a getaway vehicle, Idiot! + +[JM2_F] +If you need a piece go around back of AmmuNation opposite the subway. + +[LOVE4_7] +~g~There's a construction yard in Staunton Island, maybe they took the package there. + +[LOVE4_8] +~g~You'll need a car to open the garage. + +[TSCORE] +EARNINGS: $~1~ + +[AM1_9] +~r~Salvatore has escaped back into Luigi's Club! + +[AM1_6] +~g~If you hang around Luigi's club, the Mafia will spot you! + +[TM2_3] +~g~It's a trap! Waste them all!! + +[FM4_1] +This is Maria. The car's a trap! Meet me at the slip south of Callahan Bridge. + +[JM1_7] +~g~Close the car door! He'll notice! + +[KM5_1] +~g~DEALER MINCED!!. + +[KM5_6] +~g~You must murder at least 8 Yardie dealers. + +[KM5_7] +~g~Kill them quickly! Once they've pushed their SPANK they're off the streets. + +[RM3_8] +~r~That car is a decoy!! + +[LM3_8] +Hey, I'm Joey. + +[LM3_9] +Luigi said you were reliable so come back later, + +[KM3_5] +~g~Press the horn to get the deal going. + +[LOVE7] +LOVE'S DISAPPEARANCE + +[LOVE2_5] +~g~Kenji is fender meat! Get out of Newport and dump the car! + +[AS2_11] +~g~~1~ OF 9! + +[GARAGE1] +~g~Get out of the vehicle and walk outside. + +[KM3_11] +~g~The Cartel have been attacked and the briefcase has not been recovered. + +[KM3_12] +~g~Kill all of the Colombians, destory the vehicles and recover the briefcase. + +[KM3_13] +~g~Take the briefcase back to the casino. + +[RM5_6] +~g~He's bailed out!! Smash his bodycast with a vehicle or an explosion!! + +[PBOAT_1] { re3 change } +Press the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ to fire the boat cannons. + +[PBOAT_2] { re3 change } +Press the ~h~~k~~VEHICLE_FIREWEAPON~ button~w~ to fire the boat cannons. + +[DIAB1_B] +This is El Burro of the Diablos. + +[DIAB1_D] +You're new in Liberty, but already you are gaining a reputation on the streets. + +[DIAB1_E] +There's a street race starting by the old school hall near the Callahan Bridge. + +[DIAB1_F] +Get yourself some wheels and first through all the checkpoints wins the prize. + +[HM2_1] { re3 change } +Use the RC buggies to destroy the armored cars. Press the ~h~~k~~VEHICLE_FIREWEAPON~ button ~w~to detonate. + +[HM2_1A] { re3 change } +Use the RC buggies to destroy the armored cars. Press the ~h~~k~~VEHICLE_FIREWEAPON~ button ~w~to detonate. + +[HM2_2] +~r~You failed to destroy all the armored cars! + +[HM2_6] +~g~Armored Car destroyed! + +[RM3_A] +I know a real important man in town, a soft touch, + +[RM3_H] +with shall we say, exotic tastes and the money to indulge them. + +[RM3_B] +He's involved in a legal matter and the prosecution has some rather embarrassing photos of him + +[RM3_C] +at a morgue party or something. + +[LOVE6_A] +A lesson in business, my friend. + +[LOVE6_E] +If you have a unique commodity, the world and his wife will try to wrestle it from your grasp... + +[LOVE6_C] +SWAT teams have cordoned off the area around my associate and the package. + +[LOVE6_D] +Get over there, pick up the van and act as a decoy. + +[LOVE6_F] +Keep them busy and he should make good his escape. + +[AM3_C] +He's probably out in the bay as you read this! Steal a police boat, and sink his career! + +[FESZ_UC] +CANCEL + +[FEDS_SM] +L1,R1-CHANGE MENU + +[FEDS_AS] +;=-CHANGE SELECTION + +[FEDSAS2] +<>-CHANGE SELECTION + +[FEDS_SS] +L1,R1-CHANGE SELECTION + +[FEDSSC1] +;-FASTER SCROLLING + +[FEDSSC2] +=-STOP SCROLLING + +[MEA2_3] +~g~Bring the car back to the factory. + +[RM1_3] +~r~McAffrey escaped! + +[RM1_4] +~g~You have used all the grenades! Get some more from ammunation! + +[RM1_5] +~g~Go back and torch the safehouse! + +[RM6_4] +~g~Go over to the lockup and collect Ray's stash. + +[RM6_5] +~g~The CIA have the bridge under surveillance, find another route across. + +[HM2_F] +and wreck all their armored stuff. + +[HM_4] +'BULLION RUN' + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA4_B7] +but if you just step into my office... + +[MEA3_B4] +Marty wants to see me? Well it better be quick because I have to get my hair done. + +[KM3_7] +It's a Yakuza trap man! + +[FES_LOF] +Load Failed. + +[FES_SLO] +SAVE FILE + +[FES_ISC] +IS CORRUPTED + +[FESZ_TI] +SAVE Z1 + +[FESZ_SA] +Save game + +[MC_LDFL] +Load Failed! + +[MC_NWRE] +Now Restarting Game. + +[LOVE6_3] +~g~You have ~1~ seconds to return to the Securicar before you fail the mission. + +[LOVE6_4] +~r~You ditched the Decoy Securicar! + +[HELP1] +Stop in the center of the blue marker. + +[HELP12] +Walk into the center of the blue marker to trigger a mission. + +[HJSTAT] +Distance: ~1~.~1~m Height: ~1~.~1~m Flips: ~1~ Rotation: ~1~_ + +[HJSTATW] +Distance: ~1~.~1~m Height: ~1~.~1~m Flips: ~1~ Rotation: ~1~_ And what a great landing! + +[DIAB1_5] +RACE TIME: + +[LOVE3_4] +~r~You destroyed the plane!! + +[F_FAIL1] +Fire Truck mission ended. + +[F_CANC] +~r~Fire Fighter mission cancelled! + +[F_EXTIN] +FIRES: + +[A_COMP1] +Paramedic missions complete! + +[A_CANC] +~r~Paramedic mission cancelled! + +[A_COMP3] +Paramedic missions complete! You will never get tired when running! + +[ATUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Paramedic missions on or off. + +[ATUTOR3] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Paramedic missions on or off. + +[ALEVEL] +Paramedic Mission Level ~1~ + +[A_FAIL1] +Paramedic mission ended. + +[FEST_HA] +Highest Paramedic Mission level + +[A_SAVES] +PEOPLE SAVED: ~1~ + +[C_KILLS] +CRIMINALS KILLED: ~1~ + +[HM1_B] +I got a problem they tryin' to play me. + +[AM2_A] +Salvatore's death comes as pleasurable news, + +[AM2_A2] +you are an efficient killer. I like that in a man. + +[AM2_B] +This is my brother Kenji. + +[AM2_C] +Asuka has a little job for you, but when you're done, drop by my casino and we can talk. + +[AM2_D] +Just like Kenji, always trying to play with my toys. + +[AM2_E] +My police source indicates that the Mafia are watching our interests around the city + +[AM2_E2] +in a bid to track you down. + +[AM2_F] +We cannot continue our operations until they are dealt with. + +[AM2_G] +Take out these spying fools and end this vendetta once and for all. + +[F_START] +~g~Burning vehicle reported in the ~a~ area. Go and extinguish the fire. + +[AM4_1A] +Get to the Phone in West Belleville Park. + +[AM4_1B] +Get to the Phone on Liberty Campus. + +[AM4_1C] +Get to the Phone in South Belleville Park. + +[AM4_1D] +Meet me in the toilet block in the park. + +[HJSTATF] +Distance: ~1~ft Height: ~1~ft Flips: ~1~ Rotation: ~1~_ + +[HJSTAWF] +Distance: ~1~ft Height: ~1~ft Flips: ~1~ Rotation: ~1~_ And what a great landing! + +[HM1_F] +Watch your back though, there'll be Jacks on the street who'll think you're trying to blast them too! + +[HM1_D] +'Nines' is their tag and purple is their flag and each day they rock their colors... + +[HM1_G] +is another day the 'Jacks' look soft. + +[MEA2_B] +and steal some stuff so I could claim on the insurance as you do. + +[TM3_H] +~w~You did good back there kid, real good. + +[TM3_I] +~w~Come on, let's introduce you to the Don. + +[TM3_J] +~w~Heeyyy! Luigi! + +[TM3_K] +~w~Oh my girls have been missing you so long Salvatore, you been away too long. + +[TM3_L] +~w~You tell them that once this unfortunate business is taken care of, + +[TM3_M] +~w~we'll all go down to the club and celebrate, ok? + +[TM3_N] +~w~Here's my boy. + +[TM3_N2] +~w~How you doin' pop? + +[TM3_O] +~w~You got yourself a good woman yet? + +[TM3_P] +~w~Hey, your mother, god bless her soul, would be turning over in her grave + +[TM3_Q] +~w~to see you without a wife. + +[TM3_R] +~w~I know Pop, I'm working on it. + +[TM3_S] +~w~TONI! How's your Momma? + +[TM3_T] +~w~She's a great woman you know. Strong. Firenze. + +[TM3_U] +~w~She's good...fine. + +[TM3_V] +~w~Terrific, Terrific. Now listen you guys, you go inside while I talk to our new friend here. + +[TM3_W] +~w~I see nothing but good things for you my boy... + +[RM1_A] +That scumbag McAffrey, he took more bribes than anyone. + +[RM1_B] +He thinks he's gonna get an honorable discharge if he turns states evidence. + +[RM1_C] +He just squealed! + +[RM4_B] +We gotta shut him up, permanently. + +[RM4_E] +I want him sleeping with the fishes, not eating them. + +[LOVE3_B] +On its approach to the airport tonight, a light aircraft will pass over the bay. + +[LOVE4_D] +Unfortunately the port authorities seized the plane and were stripping it down + +[LOVE4_H] +until I intervened at great personal expense. + +[LOVE4_E] +Cross the bridge to Shoreside Vale and go to Francis International Airport. + +[GTAB_A] +Hey, let's get this out of here. God knows what it is + +[GTAB_B] +but he seems to want it badly enough so it must be worth something. + +[GTAB_C] +Who the Heck! + +[GTAB_D] +YOU! + +[GTAB_E] +Hey take it easy amigo! De nada! De nada! + +[GTAB_F] +I left you pouring your heart out into the gutter! + +[GTAB_G] +Don't shoot amigo. No problem. We all friends. Here, take this. + +[GTAB_H] +Don't be such a pussy! + +[GTAB_I] +We got no choice baby! + +[GTAB_J] +We always got a choice you dumb bastard! + +[GTAB_K] +I'm sorry about that crazy bitch man, they all the same...por favor?? + +[GTAB_L] +So the whore got away. + +[GTAB_M] +But you've done me a favor, + +[GTAB_N] +you're not the only one that has a score to settle with the Cartel, + +[GTAB_O] +this worm killed my brother! + +[GTAB_P] +I never killed no Yakuza! + +[GTAB_Q] +LIAR! We all saw the Cartel assassin. + +[GTAB_R] +We are going to hunt down and kill all you Colombian dogs! + +[GTAB_S] +I'll be operating on our friend here to extract information and a little pleasure. + +[GTAB_T] +You, drop by later, I'm sure I'll require your services. + +[GTAB_U] +Please amigo, don't leave me with her, she psycho chica! Amigo? Hey AMEEEGO!!!...Aiiieeeeaaargghh! + +[LOVE5_A] +You are proving to be a safe investment, a rare thing in these days of false hood. + +[KM3_1] +~g~The Cartel are expecting a Yardie Posse go and steal a Yardie car! Head north you'll find one in Newport. + +[LOVE1_1] +~g~Go 'jack a Colombian gang car, so you can infiltrate the hideout, head north you'll find one in Fort Staunton. + +[FM1_Q1] +~w~You looking for some fun? A little...hmm? Some SPANK? + +[FM1_R] +~w~Hi Chico. Nah, just the usual. + +[FM1_T] +~w~Thanks Chico. See you around. + +[FM1_W] +~w~Alright Fido, you wait here and look after the car while I go and shake my butt alright. + +[FM1_X] +~w~OK Fido, let's get out of here. Wooooh! + +[FM1_Q] +~w~Hey Maria! It's my favorite lady! + +[FM1_S1] +~w~Hey, maybe you should check out the warehouse party at the east end of Atlantic Quays. + +[FM1_U] +~w~Gracias and enjoy. That's good stuff. + +[FM1_V] +~w~C'mon Fido, let's go and check out this party! + +[FM1_SS] +~r~SCANNER: ~g~Four-five to all units: Assist narcotics raid Atlantic Quays... + +[LOVE6_B] +even if they have little understanding as to its true value. + +[TM3_A1] +~r~Joey's Fried! + +[TM3_A2] +~r~Joey and Luigi have been cremated! + +[TM3_A3] +~r~Joey, Luigi and Toni are Toast! + +[FM4_2] +Listen, Salvatore thinks that we're going behind his back, + +[FM4_3] +so he was offering you to the Cartel in order to make a deal. + +[FM4_4] +I couldn't let him do that, I mean the worst thing is, + +[FM4_4B] +it's all my fault... because I told him, we were an item. + +[FM4_5] +Don't ask me why. I don't know. + +[FM4_6] +Look you're a marked man on Mafia turf and I've got to get out of here too. + +[FM4_6B] +I've seen too much killing. Too much blood! + +[FM4_7] +This is a friend of mine ok, she's an old friend.. it's Asuka, she's someone we can trust. + +[FM4_8] +C'mon, Enough of the speeches. + +[FM4_9] +We better get out of here before we get more hysterical Italians wanting less friendly reunions. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +CHARACTERS + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERS + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CRED270] +MIKE HONG + +[CINCAM] +Cinematic Camera + +[KM1_13] +Drive the vehicle into the garage! + +[KM3_14] +~r~You have been spotted the deal is off! + +[EBAL_H] +Wait here man while I go in and talk to Luigi. + +[EBAL_M] +Remember no one messes with my girls! + +[LM2_F] +Then take his car, respray it. + +[LM2_D] +here, here take it. + +[LM1_9] +Hi I'm Misty. + +[LM4_A] +Some Diablo scumbag has been pimping his skuzzy bitches in my backyard. + +[FM2_B] +We got us a rat! + +[FM2_C] +He ain't pimpin' or pushin' so he must be talking. + +[FM3_CC] +~w~Come back brother when you have the money. + +[FEDS_AM] +<>-CHANGE MENU + +[LOVE5_5] +~r~You failed to protect the truck! + +[RM6_6] +~r~Ray is dead! + +[RM6_7] +~r~Ray has missed his flight. + +[RM6_8] +~g~You have left Ray behind, go back and get him. + +[FM1_10] +~g~You have left Maria behind, go back and pick her up. + +[LOVE4_9] +~r~The plane has been destroyed! + +[LOV4_10] +~r~The only lead to where the package has gone has been destroyed! + +[KM2_D] +Needless to say, we must give him the cars as a gift, to repay the debt that I owe him. + +[KM4_B] +The business's fortunate enough to have our protection settle their accounts today. + +[KM2_E] +You must obtain the cars on this list and deliver them to a garage behind the car park in Newport. + +[FM3_8I] +~w~Get a good vantage point then I'll head in when you fire the first shot. + +[LOVE1_B] +Experience has taught me that a man like you can be very loyal for the right price, + +[LOVE1_H] +but groups of men get greedy. + +[LOVE1_C] +A valued resource, an old oriental gentleman I know, + +[LOVE1_I] +has been kept hostage by some South Americans in Aspatria. + +[MEA4_D] +I've agreed to see him... + +[MEA4_B4] +Marty sent you huh? OK, I'm gonna show that creep the meaning of the word business. + +[MEA4_B5] +Carl, hi! i eerr, I need more time to get your money. + +[MEA1_B4] +Ah, Mr Chonks sent you did he. Let's go and pay the fellow a visit. + +[HM5_6] +Let's go crack some skulls... + +[LOVE1_5] +~g~Stop hanging around, get a Colombian Gang car and rescue Love's associate. + +[AS1_D] +~w~Act as the bait, and get the death squads to follow you to Pike Creek + +[AS1_E] +~w~where some of my men will be waiting for them. + +[AS2_C] +~w~The Cartel have a front company, The Kappa Coffee House. + +[AS2_E] +~w~We have no choice but to put these drug stands out of operation. + +[AS2_F] +~w~Smash them to splinters!! + +[AS2_A1] +~w~Miguel certainly has some of that famous Latin stamina. + +[AS2_A2] +~w~I'm quite exhausted. + +[SIREN_3] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[SIREN_4] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[AS3_C] +~w~Eeeeeeyoooo! What IS that gooey yellow stuff? + +[AS3_C1] +~w~Oh hi Babe. + +[AS3_F] +~w~She's got the makings of a natural this girl. + +[AS3_F1] +~w~She's managed to extract this little gem from our guest. + +[AS3_G] +~w~There is a plane coming into Francis International in 2 hours time. + +[AS3_G1] +~w~It is full of Catalina's poison. + +[AS3_H] +~w~You can avoid airport security by getting a boat out to the runway-light buoys + +[AS3_H1] +and shooting the plane down on its approach. + +[AS3_I] +~w~Collect the cargo from the debris and stash it! + +[AS3_J] +~w~Oh you be careful now, OK baby? + +[AS3_K] +~w~Now try the chilli oil..... + +[RM2_F1] +Those Colombians'll be here any minute! + +[RM2_K] +Goddam they're here!! LOCK'N'LOAD!! + +[LOVE2_7] +~g~Now dump the car! + +[LOVE2_8] +~g~Now get out of Newport! + +[AM1_F] +Salvatore Leone will be leaving Luigi's in about three hours time. (~1~:~1~) + +[LOVE5_C] +I want you to follow him, and make sure both he and my package get to Pike Creek unharmed. + +[FESZ_SR] +Save Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FESZ_FO] +Would you like to format the Memory Card (PS2) in MEMORY CARD slot 1? + +[FELZ_FO] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FES_NOC] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[FES_LOE] +Load Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FES_DEE] +Deleting Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[SLONFM] +Error formatting Memory Card (PS2) in MEMORY CARD slot 1. + +[SLONDR] +Insufficient space to save. Please insert a Memory Card (PS2) with at least 500KB of free space available into MEMORY CARD slot 1. + +[SLNSP] +Insufficient space to save. Please insert a Memory Card (PS2) with at least 200KB of free space available into MEMORY CARD slot 1. + +[FEFD_WR] +Formatting Memory Card (PS2) in MEMORY CARD slot 1. Please do not remove the Memory Card (PS2), reset or switch off the console. + +[FES_ISF] +NOT PRESENT + +[FES_SAG] +PRESENT + +[SLONNO] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[SLONNF] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FESZ_FM] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. Would you like to format Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_FF] +Format Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[MCDNSP] +There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + +[MCGNSP] +There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + +[FESZ_WR] +Saving data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FESZ_OW] +Overwriting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FELD_WR] +Loading data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FEDL_WR] +Deleting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[LM2_C] +Luigi said to, to give you this so... + +[LM3_G] +Joey ain't the kind you keep waiting, remember, this is your foot in the door... + +[LM5_E] +Get as many of them as you can before the cops drink away their green. + +[JM5_C] +Alright, there's a car stuffed with a stiff at the cafe near Callahan Point. + +[RM2_B] +We saw action in Nicaragua, back when the country knew what it was doing. + +[RM2_C] +Some Cartel scum roughed him up yesterday, said they'd be back for some of his stock today. + +[RM2_D1] +I'd go myself but the old sciatica's playing up again -cough cough- so, eerr-hhrrmmm, good luck. + +[CATINF1] +~g~Get Catalina! + +[CATINF2] +~g~Follow the chopper to find Catalina. + +[BOATIN1] +Jump into a boat and press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~to get in. + +[BOATIN2] +You can press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~if you are near a boat to get in it. + +[BOATIN3] +Jump into a boat and press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~to get in. + +[BOATIN4] +You can press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~if you are near a boat to get in it. + +[JM6] +'THE GETAWAY' + +[FM1] +'CHAPERONE' + +[JM1] +'MIKE LIPS LAST LUNCH' + +[FM21] +'BOMB DA BASE: ACT I' + +[FM3] +'BOMB DA BASE: ACT II' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'UNDER SURVEILLANCE' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'S.A.M.' + +[RM2] +'ARMS SHORTAGE' + +[LOVE6] +'DECOY' + +[LOVE1] +'LIBERATOR' + +[RC1] +'DIABLO DESTRUCTION' + +[RC2] +'MAFIA MASSACRE' + +[RC3] +'CASINO CALAMITY' + +[RC4] +'RUMPO RAMPAGE' + +[RM2_E1] +I can't believe those yellow-bellied bastards left me without proper cover again! + +[GREN_1] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[GREN_2] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[GREN_3] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[LOVE4_G] +My property will be waiting for you at the customs hanger in the aircraft's fuselage. + +[KABOOM] +KABOOOM! + +[SPLAT] +SPLAT! + +[PANCAK] +PANCAKED! + +[SOAKED] +SOAKED! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +When the subway opens you will be able to catch a train to Staunton Island. + +[TUBE2] +When Shoreside Vale opens you will be able to Exit Shoreside Terminal to Francis International Airport. + +[TUBE_2] +To board a subway train, press the ~h~'enter vehicle' button~w~. + +[LEGAL] +~g~Eliminate the criminal threat! + +[GA_2] +New engine and paint job. The cops won't recognize you! + +[LM1_8A] +To earn some extra cash, why not 'borrow' a taxi... + +[TAXIH1] +Stop near a highlighted pedestrian to pick them up then drive them to their destination before the time runs out. + +[LM5_7] +~g~Less than four girls working the ~p~Fuzz Ball~g~ and Luigi won't be happy! + +[KM2_3] +~g~Remember the ~r~cars~g~ have to be in mint condition to be accepted by the ~p~garage~g~. + +[KM5_2] +~g~A Yardie is off the streets. + +[BETRA_A] +Sorry, babe. + +[BETRA_B] +I'm an ambitious girl and you, + +[BETRA_C] +you're just small time. + +[JAILB_C] +* + +[JAILB_D] +* + +[JAILB_E] +* + +[JAILB_F] +* + +[JAILB_G] +* + +[JAILB_H] +* + +[JAILB_I] +* + +[JAILB_J] +* + +[JAILB_P] +* + +[JAILB_Q] +Come on! + +[JAILB_R] +Senor dickhead! + +[JAILB_S] +It's no problem to kill you. + +[JAILB_T] +You gonna be sorry. + +[JAILB_U] +A'right, a'right. Get lost. + +[HELP15] +When on foot press the ~h~~k~~PED_LOOKBEHIND~ button~w~ to ~h~look behind~w~. + +[FEC_LB3] +Look behind + +[FEC_R3] +(R3 button) + +[FES_AFO] +This Memory Card (PS2) is already formatted. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CHANGE SELECTION + +[FEDSAS4] +;=<> - CHANGE SELECTION + +[SPRAY_4] { re3 change } +Use the ~h~~k~~VEHICLE_FIREWEAPON~ button ~w~to fire the water cannon. + +[SPRAY_1] { re3 change } +Use the ~h~~k~~VEHICLE_FIREWEAPON~ button ~w~to fire the water cannon. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore will be leaving Luigi's at about 0~1~:~1~ + +[JAILB_V] +* + +[JAILB_A] +* + +[JAILB_B] +* + +[JAILB_W] +* + +[JAILB_K] +* + +[JAILB_L] +* + +[JAILB_M] +* + +[JAILB_N] +* + +[JAILB_O] +* + +[JAILB_X] +* + +[FEDS_SE] +/ button - SELECT + +[FEDS_SB] +/ button - SELECT " button - BACK + +[TM4_A] +~w~Oh it's you. TONI ain't here. + +[TM4_A2] +~w~But he left one of his sugary love letters for you. + +[DIAB2_A] +I started my exotic entertainment business with nothing but the sizeable contents of my leather pants! + +[LM5_9] +GIRLS: + +[PERPIC] +Hidden Packages found + +[CO_ONE] +Hidden Package ~1~ of ~1~ + +[LOVE3_3] +~g~The plane has dropped ~1~ of 6 packages. + +[FARE11] +~g~Destination ~w~'Construction site' ~g~in Fort staunton. + +[GA_21] +You cannot store any more cars in this garage. + +[CHEAT1] +Cheat activated + +[CHEAT2] +Weapon cheat + +[CHEAT3] +Health cheat + +[CHEAT4] +Armor cheat + +[CHEAT5] +Wanted level cheat + +[CHEAT6] +Money cheat + +[CHEAT7] +Weather cheat + +[AS1_H] +~r~You failed to lead the Deathsquad into the Yakuza trap!! + +[FEDS_BA] +" button - BACK + +[RAMP_A] +ALL RAMPAGES COMPLETED! + +[USJ_ALL] +ALL UNIQUE STUNTS COMPLETED! + +[FARE23] +~g~Destination ~w~'import export garage' ~g~in Cochrane Dam district + +[L_TRN_1] +You can ride the L-train around Portland. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[L_TRN_2] +You can ride the L-train around Portland. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[S_TRN_1] +You can take the subway trains across Liberty. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[S_TRN_2] +You can take the subway trains across Liberty. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[AS1_C] +~w~She has three death squads dotted around Liberty, whose sole job is to hunt you down. + +[AS1_G] +~r~All the Yakuza are dead!! + +[JAN] +Jan + +[FEB] +Feb + +[MAR] +Mar + +[APR] +Apr + +[MAY] +May + +[JUN] +Jun + +[JUL] +Jul + +[AUG] +Aug + +[SEP] +Sept + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Dec + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +BUGGIES LEFT: + +[BONUS] +~g~BONUS $~1~ + +[HORN1] +Press the ~h~L3 button ~w~to activate the ~h~horn. + +[HORN2] +Press the ~h~L1 button ~w~to activate the ~h~horn + +[HORN3] +Press the ~h~R1 button ~w~to activate the ~h~horn + +[LM3_1A] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[LM3_1B] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[LM3_1C] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[RADIO_A] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_B] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_C] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_D] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[FEC_EXV] +Enter and exit vehicle + +[TAXI_M] +'TAXI DRIVER' + +[COP_M] +'VIGILANTE' + +[FIRE_M] +'FIREFIGHTER' + +[AMBUL_M] +'PARAMEDIC' + +[HJ_IS] +INSANE STUNT BONUS: $~1~ + +[HJ_PIS] +PERFECT INSANE STUNT BONUS: $~1~ + +[HJ_DIS] +DOUBLE INSANE STUNT BONUS: $~1~ + +[HJ_PDIS] +PERFECT DOUBLE INSANE STUNT BONUS: $~1~ + +[HJ_TIS] +TRIPLE INSANE STUNT BONUS: $~1~ + +[HJ_PTIS] +PERFECT TRIPLE INSANE STUNT BONUS: $~1~ + +[HJ_QIS] +QUADRUPLE INSANE STUNT BONUS: $~1~ + +[HJ_PQIS] +PERFECT QUADRUPLE INSANE STUNT BONUS: $~1~ + +[AM1_K] +Salvatore Leone will be leaving Luigi's in about three hours time. (0~1~:~1~) + +[IMPEXPP] +Import/Export garage, Portland Harbor. We have orders for various vehicles. Check our notice board for our requirements. + +[VANHSTP] +Any more Securicars you want cracked? Bring them to our garage in the Portland Harbor. + +[EMVHPUP] +Great rates paid for new and used Emergency Vehicles. Bring them to the crane in the north east of Portland Harbor. + +[STANDS] +STALLS WRECKED: + +[STASH] +~g~Stash the SPANK back at the ~p~construction site! + +[MCSTNS] +There is no Memory Card (PS2) in MEMORY CARD slot 1. Do you wish to start? (YES or NO) + +[LOVE3_5] +~g~The plane is now in range. + +[LOVE3_6] +~r~The Police got to the packages first! + +[SIREN_1] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[SIREN_2] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[FM3_8C] +~w~I'll need $100,000 to cover expenses, + +[MCLOAD] +Loading Data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FES_GME] +Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + +[FESZ_QF] +Are you sure you wish to format the Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_LS] +Load Successful. + +[RM3_5] +~g~You have ~1~ of 6 evidence packages. + +[LOVE3_2] +~g~You have all the packages! Take them back to Donald Love. + +[LOVE4_4] +~g~Take the package back to Donald Love! + +[FEB_SAV] +Load + +[FEP_SAV] +LOAD GAME + +[AS2_12A] +~g~After you trash the first stall, you will have 8 minutes before the Cartel warn their pushers! + +[AS3_1A] +~g~Now get to the ~b~marker buoy! + +[NOCONT] +Please reconnect an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). to controller port 1 to continue + +[BET_JB] +BETRAYED BY HIS LOVER CATALINA AND LEFT FOR DEAD. CONVICTED AND SENTENCED, HE BEGINS HIS JOURNEY TO LIBERTY CITY PENITENTIARY. BUT ONLY ONE THOUGHT BURNS IN HIS CRIMINAL MIND......REVENGE! + +[END_A] +Residents in Cedar Grove have been coming to terms + +[END_B] +with the emotional aftermath of a full blown war + +[END_C] +that hit the area yesterday. + +[END_D] +Local resident, Clive Denver described to police + +[END_E] +a single gunman that he saw fleeing the scene, with a dark haired woman. + +[END_F] +Oh, you know, we're gonna have such fun, 'cos you know, you know, + +[END_G] +I love you, I, I, I, I really do, 'cos you're such a big strong man + +[END_H] +and that's just what I need. + +[END_I] +Anyway, what was I saying? + +[END_J] +Oh, you know, I forget. But you know what it's like, don't you? + +[END_K] +The sound of explosions shook nearby homes as people ran for cover. + +[END_L] +Several citizens were injured in the panic as ground fire was exchanged + +[END_M] +between ground forces and a helicopter circling the dam. + +[END_N] +Yeah, we got a good view from down here in the gardens. + +[END_O] +When the 'copter finally got taken out, + +[END_P] +better than the fireworks on the 4th of July. + +[END_Q] +With the death toll already over twenty, + +[END_R] +police are still finding bodies. + +[END_S] +There have been no official denials concerning rumours + +[END_T] +that the dead were members of the Colombian Cartel, + +[END_U] +and still no leads as to the cause of the massacre. + +[END_V] +I broke a nail and my hair is ruined, I mean can you believe it? + +[END_W] +This one cost me fifty dollars... + +[PAPER1] +* + +[PAPER2] +* + +[PAPER3] +* + +[FEB_CPC] +Control Configuration + +[FEC_PED] +Controls On Foot + +[FEC_VEH] +Controls In Vehicle + +[FEC_FPR] +Controls For First Person + +[FEC_CMM] +Common Controls + +[FEC_PWL] +GO Left + +[FEC_PWR] +Go Right + +[FEC_PWF] +Walk Forward + +[FEC_PWT] +Walk towards camera + +[FEC_PLB] +Look Behind. + +[FEC_PFR] +Fire Weapon + +[FEC_CLE] +Cycle Weapon Left + +[FEC_CRI] +Cycle Weapon Right + +[FEC_LKT] +Lock Target + +[FEC_PJP] +Ped Jump + +[FEC_PSP] +Ped Sprint + +[FEC_PSH] +Ped Shoot + +[FEC_TLF] +Next Target To Left + +[FEC_TRG] +Next Target to Right + +[FEC_CCM] +Center Camera Behind player. + +[FEC_SZI] +Sniper Rifle Zoom In + +[FEC_SZO] +Sniper Rifle Zoom Out + +[FEC_LKL] +First Person Look Left + +[FEC_LRT] +First Person Look Right + +[FEC_LUP] +1st Person Look Up + +[FEC_LDN] +1st Person Look Down + +[FEC_LBH] +Look Behind Vehicle + +[FEC_LLF] +Look Left of Vehicle + +[FEC_LRG] +Look Right of Vehicle + +[FEC_HRN] +Horn + +[FEC_HBR] +Vehicle Handbrake + +[FEC_ACL] +Vehicle Accelerate + +[FEC_BRK] +Vehicle Brake + +[FEC_TSM] +Toggle SubMissions + +[FEC_CRD] +Change Radio Station + +[FEC_ENT] +Enter/Exit Vehicle + +[FEC_WPN] +Fire Weapon + +[FEC_PAS] +Pause + +[FEC_FPO] +1st Person Weapons + +[FEC_SMS] +Show mouse pointer + +[FEC_CMS] +Change camera mode all situations. + +[FEC_TSS] +Take Screen Shot + +[FEN_NET] +Network + +[FEN_CON] +Connection + +[FEN_GAM] +Find game + +[FEN_TYP] +Game type + +[FEN_TY0] +Deathmatch + +[FEN_TY1] +Deathmatch stealth + +[FEN_TY2] +Team Deathmatch + +[FEN_TY3] +Team Deathmatch stealth + +[FEN_TY4] +Stash the Cash + +[FEN_TY5] +Capture the Flag + +[FEN_TY6] +Rat Race + +[FEN_TY7] +Domination + +[FEN_NAM] +Name: + +[FEN_GNA] +Game name: + +[FEM_MAP] +Select map + +[FEN_PLS] +Player settings + +[FEN_PLC] +Player color + +[FEM_MA0] +Liberty City + +[FEM_MA1] +RedLight + +[FEM_MA2] +Chinatown + +[FEM_MA3] +The Tower + +[FEM_MA4] +The Sewer + +[FEM_MA5] +Industrial Park + +[FEM_MA6] +The Docks + +[FEM_MA7] +Staunton + +[FEC_EMS] +Unique Keyboard Keys only please. + +[FEC_DBG] +DEBUG MENU + +[FEC_TGD] +Toggle Pad Game/Debug + +[FEC_TDO] +Turn Debug Camera Off + +[FEC_IVH] +Invert Mouse Horizontally: + +[FEC_MSL] +LMB + +[FEC_MSM] +MMB + +[FEC_MSR] +RMB + +[FEC_QUE] +??? + +[FEC_TWO] +Only Two Keyboard Keys Allowed + +[FEC_UMS] +Unique Mouse Keys only please. + +[FEC_OMS] +Only One Mouse Keys Allowed + +[FEC_UJS] +Unique Joystick buttons only please. + +[FEC_OJS] +Only One Joystick Buttons per action allowed + +[FEC_PTL] +Use LockTarget with Weapon Switch Left. + +[FEC_PTR] +Use LockTarget with Weapon Switch Right. + +[FEC_LBC] +Use Look Left With Look Right. + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Cannot pause in multiplayer. Press twice to quit! + +[FEM_SL1] +Slot 1 is free + +[FEM_SL2] +Slot 2 is free + +[FEM_SL3] +Slot 3 is free + +[FEM_SL4] +Slot 4 is free + +[FEM_SL5] +Slot 5 is free + +[FEM_SL6] +Slot 6 is free + +[FEM_SL7] +Slot 7 is free + +[FEM_SL8] +Slot 8 is free + +[FEM_MM] +MAIN MENU + +[FEQ_SRE] +Are you sure you want to quit? All progress since the last save game will be lost. Proceed? + +[FEQ_SRW] +Are you sure you want to quit the game? + +[FEG_SRV] +SERVER + +[FEG_MAP] +MAP + +[FEG_PLY] +PLAYERS + +[FEG_TYP] +TYPE + +[FEG_PNG] +PING + +[FET_FG] +FIND GAME + +[FET_SP] +SINGLE PLAYER + +[FET_MP] +MULTIPLAYER + +[FET_HG] +HOST GAME + +[FET_PS] +PLAYER SETUP + +[FET_CON] +CONNECTION + +[FET_AUD] +AUDIO SETUP + +[FET_DIS] +DISPLAY SETUP + +[FET_LAN] +LANGUAGE SETUP + +[FET_LG] +LOAD GAME + +[FET_DG] +DELETE GAME + +[FET_NG] +NEW GAME + +[FET_SG] +SAVE GAME + +[FET_MAP] +SELECT MAP + +[FET_GT] +GAME TYPE + +[FET_CTL] +CONTROLLER SETUP + +[FET_OPT] +OPTIONS + +[FET_QG] +QUIT GAME + +[FET_STA] +STATISTICS + +[FET_BRE] +BRIEFS + +[FEC_WAR] +Warning + +[FEC_OKK] +O.K. + +[FED_CON] +Delete File Confirmation + +[FES_SSC] +Game successfully saved. + +[DEL_FNM] +File Successfully Deleted. + +[PCLOAD] +Loading File Data + +[PCRESRT] +Restarting Grand Theft Auto III + +[FEC_DLF] +Delete Failed. + +[FEC_SVU] +Save Unsuccessful. + +[FEC_LUN] +Load Unsuccessful. File Corrupted, Please delete. + +[FEN_PLA] +Number of players: + +[FET_NON] +NO GAMES AVAILABLE + +[FET_SFG] +SEARCHING FOR GAMES... + +[FET_SRT] +SORTING GAMES... + +[FEF_LAN] +LAN + +[FEF_INT] +INTERNET + +[FET_REF] +Refresh + +[FET_FIL] +Filter + +[FET_JG] +Join + +[FEC_NTW] +Talk To Network + +[FEC_ESR] +Escape key is Restricted + +[FEC_GSL] +Show head bob: + +[FIL_FLT] +FILTER GAMES LIST + +[FET_SAN] +START NEW GAME + +[FIL_MAP] +Map: + +[FIL_SRV] +Server: + +[FIL_TYP] +Game type: + +[FIL_SPC] +Games with Space available? + +[FIL_PNG] +Ping: + +[FEN_UKH] +Unknown host + +[FEN_UKM] +Map not found + +[FEN_UKT] +Game type not found + +[FEN_NCI] +NOT CONNECTED TO THE INTERNET + +[FET_PAU] +PAUSE MENU + +[FET_SGA] +START GAME + +[FEC_SGJ] +Set Game Joystick + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FEC_WHL] +Steering wheel + +[FEC_CNT] +Controller type: + +[FES_CSA] +Select a skin from the list below: + +[FES_SKN] +SKIN NAME + +[FES_DAT] +DATE + +[FES_NON] +NO SKINS AVAILABLE + +[FEA_FM9] +MP3 PLAYER + +[FESZ_QZ] +Are you sure you want to save this game? + +[FES_CGA] +Current game slots available: + +[FES_SCG] +Save the current game? + +[FES_LCG] +Load the game and continue playing? + +[FEC_FIR] +Fire + +[FEC_NWE] +Next weapon + +[FEC_PWE] +Previous weapon + +[FEC_FOR] +Forward + +[FEC_BAC] +Backwards + +[FEC_LEF] +Left + +[FEC_RIG] +Right + +[FEC_ZIN] +Zoom in + +[FEC_ZOT] +Zoom out + +[FEC_EEX] +Enter /exit + +[FEC_RAD] +Radio + +[FEC_SUB] +Sub-mission + +[FEC_CMR] +Change camera + +[FEC_JMP] +Jump + +[FEC_SPN] +Sprint + +[FEC_HND] +Handbrake + +[FEC_TUL] +Turret left + +[FEC_TUR] +Turret right + +[FEC_LOL] +Look left + +[FEC_LOR] +Look right + +[FEC_NTR] +Next target + +[FEC_PTT] +Previous target + +[FEC_LBA] +Look behind + +[FEC_CEN] +Center camera + +[FEC_UND] +(NO) + +[FET_CFT] +ON FOOT + +[FET_CCR] +IN CAR + +[CVT_MSG] +Converting textures to optimal format for your video card + +[FET_CAC] +ACTION + +[FEC_IBT] +- + +[FEC_SPC] +SPC + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +UNBOUND + +[FET_CME] +CONTROL METHOD + +[FET_RDK] +REDEFINE CONTROLS + +[FET_AMS] +MOUSE SETTINGS + +[FET_STI] +STANDARD CONTROL CONFIGURATION + +[FET_CTI] +CLASSIC CONTROL CONFIGURATION + +[FET_MTI] +MOUSE CONTROL CONFIGURATION + +[FET_DAM] +DYNAMIC ACOUSTIC MODELING + +[FEC_TFL] +Turret Left + +[FEC_TFR] +Turret Right + +[FEC_MWF] +WHEEL UP + +[FEC_MWB] +WHEEL DN + +[FEC_ORR] +or + +[FEC_NUS] +NOT USED + +[FEC_LUD] +Look Up + +[FEC_LDU] +Look Down + +[FEC_CMP] +COMBO: LOOK L+R + +[FEC_NTT] +No Text Yet For This Key + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INS + +[FEC_DLL] +DEL + +[FEC_HME] +HOME + +[FEC_END] +END + +[FEC_PGU] +PGUP + +[FEC_PGD] +PGDN + +[FEC_UPA] +UP + +[FEC_DWA] +DOWN + +[FEC_LFA] +LEFT + +[FEC_RFA] +RIGHT + +[FEC_NUM] +NUM + +[FEC_NMN] +NUM~1~ + +[FEC_FWS] +NUM / + +[FEC_PLS] +NUM + + +[FEC_MIN] +NUM - + +[FEC_DOT] +NUM . + +[FEC_NLK] +NUMLOCK + +[FEC_ETR] +ENT + +[FEC_SLK] +SCROLL LOCK + +[FEC_PSB] +BREAK + +[FEC_BSP] +BSPACE + +[FEC_TAB] +TAB + +[FEC_CLK] +CAPSLOCK + +[FEC_RTN] +RET + +[FEC_LSF] +LSHIFT + +[FEC_RSF] +RSHIFT + +[FEC_LCT] +LCTRL + +[FEC_RCT] +RCTRL + +[FEC_LAL] +LALT + +[FEC_RAL] +RALT + +[FEC_LWD] +LWIN + +[FEC_RWD] +RWIN + +[FEC_WRC] +WINCLICK + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Grand Theft Auto III cannot run on Windows 95 + +[WIN_DX] +Grand Theft Auto III requires at least DirectX version 8.1 + +[WIN_VDM] +Grand Theft Auto III requires at least 12MB of available video memory + +[DIAB3_G] +Arriba! + +[FEM_RES] +RESUME GAME + +[FES_SNG] +START NEW GAME + +[FEM_SP] +SINGLE PLAYER + +[FEM_MP] +MULTIPLAYER + +[FEM_QT] +QUIT GAME + +[FES_SG] +START NEW GAME + +[FES_LG] +LOAD GAME + +[FEM_HST] +HOST GAME + +[FEM_OPT] +OPTIONS + +[FEM_DBG] +DEBUG + +[FET_PSU] +PLAYER SETUP + +[FET_DEF] +RESTORE DEFAULTS + +[FED_BRI] +BRIGHTNESS + +[FED_TRA] +TRAILS + +[FEM_LOD] +DRAW DISTANCE + +[FEM_VSC] +FRAME SYNC + +[FEM_FRM] +FRAME LIMITER + +[FED_RES] +SCREEN RESOLUTION + +[FED_WIS] +WIDE SCREEN + +[FEDS_TB] +BACK + +[FEA_MUS] +MUSIC VOLUME + +[FEA_SFX] +SFX VOLUME + +[FEA_RSS] +RADIO STATION + +[FEL_ENG] +ENGLISH + +[FEL_FRE] +FRENCH + +[FEL_GER] +GERMAN + +[FEL_ITA] +ITALIAN + +[FEL_SPA] +SPANISH + +[FEA_3DH] +AUDIO HARDWARE + +[FEA_SPK] +SPEAKERS CONFIGURATION + +[FEA_2SP] +2 SPEAKERS + +[FEA_4SP] +MORE THAN 2 SPEAKERS + +[FEA_EAR] +HEADPHONES + +[FEA_NAH] +NO AUDIO HARDWARE + +[FET_SNG] +START NEW GAME + +[FEN_STA] +START GAME + +[GMLOAD] +LOAD GAME + +[GMSAVE] +SAVE GAME + +[FES_DGA] +DELETE GAME + +[FEM_NON] +NONE + +[FEC_IVV] +INVERT MOUSE VERTICALLY + +[FEC_MSH] +MOUSE SENSITIVITY + +[FET_CCN] +CONTROLS: CLASSIC + +[FET_SCN] +CONTROLS: STANDARD + +[FES_SET] +USE SKIN + +[GHOST] +Ghost + +[WIN_RSZ] +Failed to select new screen resolution + +[FEC_TFU] +Turret /Dodo up + +[FEC_TFD] +Turret /Dodo down + +[FET_APL] +APPLY + +[FET_APP] +LMB,RETURN TO APPLY THE NEW SETTING + +[FET_HRD] +DEFAULT SETTINGS RESTORED + +[FET_MST] +MOUSE CONTROLLED STEERING + +[FEC_STR] +NUM STAR + +[FET_MIG] +LEFT,RIGHT,MOUSEWHEEL TO ADJUST + +[FET_CIG] +BACKSPACE TO CLEAR - LMB,RETURN TO CHANGE + +[FET_RIG] +SELECT A NEW CONTROL FOR THIS ACTION OR ESC TO CANCEL + +[FET_EIG] +CANNOT SET A CONTROL FOR THIS ACTION + +[NO_PCCD] +Please insert Grand Theft Auto III - disc 2 into the drive or press ESC to cancel + +[CVT_ERR] +You have run out of disk space. Please make some space on your harddisk before continuing. Press ESC to cancel. + +[FED_SUB] +SUBTITLES + +[FET_DSN] +Default Player Skin.bmp + +[JM3] +'VAN HEIST' + +[ATUTOR2] +~g~Drive the patients to Hospital CAREFULLY. Each bump reduces their chances of survival. + +[EBAL] +'GIVE ME LIBERTY' + +[LM4] +'PUMP-ACTION PIMP' + +[REPLAY] +REPLAY + +[FEC_SFT] +SHIFT + +[CRED254] +STUDIO MANAGER + +[CVT_CRT] +Cannot convert textures for your video card. You must login to an Administrator account to do this. Press ESC to quit. + +[FEM_ON] +ON + +[FEM_OFF] +OFF + +[FEM_YES] +YES + +[FEM_NO] +NO + +[FES_WAR] +Saving, please wait... + +[FED_DLW] +Deleting, please wait... + +[FED_LDW] +Loading, please wait... + +[FEC_SLC] +Slot is corrupted + +[FED_LFL] +Loading save game has failed. The game will restart now. + +[FET_RSO] +ORIGINAL SETTING RESTORED + +[FET_RSC] +HARDWARE NOT AVAILABLE - ORIGINAL SETTING RESTORED + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPANESE + +[FEL_POL] +POLISH + +[FEL_RUS] +RUSSIAN + +{ new display menus } +[FET_GFX] +GRAPHICS SETUP + +[FED_MIP] +MIP MAPPING + +[FED_AAS] +ANTI ALIASING + +[FED_FIL] +TEXTURE FILTERING + +[FED_BIL] +BILINEAR + +[FED_TRL] +TRILINEAR + +[FED_WND] +WINDOWED + +[FED_FLS] +FULLSCREEN + +[FEM_CSB] +CUTSCENE BORDERS + +[FEM_SCF] +SCREEN FORMAT + +[FEM_ISL] +MAP MEMORY USAGE + +[FEM_LOW] +LOW + +[FEM_MED] +MEDIUM + +[FEM_HIG] +HIGH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +FREE CAM + +{ Linux joy detection } +[FEC_JOD] +DETECT JOYSTICK + +[FEC_JPR] +Press any key on the joystick of your choice that you want to use on the game, and it will be selected. + +[FEC_JDE] +Detected joystick + +{ mission restart } +[FET_RMS] +REPLAY MISSION + +[FESZ_RM] +RETRY? + +{ more graphics } +[FED_VPL] +VEHICLE PIPELINE + +[FED_PRM] +PED RIM LIGHT + +[FED_RGL] +ROAD GLOSS + +[FED_CLF] +COLOUR FILTER + +[FED_WLM] +WORLD LIGHTMAPS + +[FED_MBL] +MOTION BLUR + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +{ controls } +[FEC_IVP] +INVERT PAD VERTICALLY + +{ map } +[FEM_TWP] +Toggle Waypoint + +[FEA_FMN] +RADIO OFF + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD TYPE + +[FEC_CCF] +CONFIGURATION + +[FEC_CF1] +SETUP 1 + +[FEC_CF2] +SETUP 2 + +[FEC_CF3] +SETUP 3 + +[FEC_CF4] +SETUP 4 + +[FEC_CDP] +CONTROLLER DISPLAY + +[FEC_ONF] +ON FOOT + +[FEC_INC] +IN CAR + +[FEC_VIB] +VIBRATION + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/english.txt b/utils/gxt/english.txt new file mode 100644 index 0000000..adc6378 --- /dev/null +++ b/utils/gxt/english.txt @@ -0,0 +1,7026 @@ +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Hey! Get back in the vehicle! + +[IN_VEH2] +~g~You need some wheels for this job! + +[IN_BOAT] +~g~You need a boat for this job! + +[HEY] +~g~Don't go solo, keep your posse together! + +[HEY2] +~g~Don't split up, keep the group together! + +[HEY3] +~g~You've dropped your main man, go back and get 8-Ball! + +[HEY4] +~g~Lose Misty and Luigi will lose your face! Go and get her! + +[HEY5] +~g~One of the girls is AWOL, Go back and round her up! + +[HEY6] +~g~You left your honor with the Yakuza Kanbu. You must protect him! + +[HEY7] +~g~An extra gun could be useful. Go back and pick up your contact! + +[HEY8] +~g~Protection means just that -Protect the Old Oriental Gentleman! + +[HEY9] +~g~You want the word on the street? Go see the contact! + +[HELP2_A] +Press the ~h~/ button~w~ when running to ~h~sprint. + +[HELP3] +You can only sprint for short periods before becoming tired. + +[HELP4_A] +Press the~h~ ~k~~VEHICLE_ACCELERATE~ button~w~ to ~h~accelerate. + +[HELP4_D] +Push the~h~ right analog stick~w~ up to ~h~accelerate. + +[HELP5_A] +Press the~h~ ~k~~VEHICLE_BRAKE~ button~w~ to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP5_D] +Pull the ~h~right analog stick~w~ back to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP6_A] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP6_C] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP6_D] +Press the~h~ ~k~~VEHICLE_HANDBRAKE~ button ~w~to apply the vehicle's ~h~handbrake. + +[HELP7_A] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ button ~w~to ~h~target~w~ with the sniper rifle. + +[HELP7_D] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ button ~w~to ~h~target ~w~with the sniper rifle. + +[HELP8_A] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ button ~w~to ~h~zoom in ~w~with the rifle and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ button ~w~to ~h~zoom out ~w~again. + +[HELP9_A] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP10] +This badge indicates you have a police wanted level. + +[HELP11] +The more badges the higher your wanted level. + +[HELP13] +Sometimes you may need to use pathways not shown on the radar. + +[TIMER] +This is a timed mission, you must complete it before the timer counts down to zero. + +[MISTY1] +~r~Misty is morgue-meat! + +[OUT_VEH] +~g~Get out of the vehicle! + +[GARAGE] +Drive the vehicle into the garage, then walk outside. + +[WANTED1] +~g~Shake the cops and lose your wanted level! + +[NODOORS] +~g~They ain't sardines! Get some wheels with enough seats. + +[TRASH] +~g~You've junked your wheels real bad! Get your vehicle repaired! + +[WRECKED] +~r~The vehicle is wrecked! + +[HORN] +~g~Sound the horn. + +[NOMONEY] +~g~You need more cash! + +[OUTTIME] +~r~Too slow, man, too slow! + +[SPOTTED] +~r~They're on to you! + +[REWARD] +REWARD $~1~ + +[GAMEOVR] +GAME OVER + +[Z] +Z-axis value: ~1~ + +[M_FAIL] +MISSION FAILED! + +[M_PASS] +MISSION PASSED! $~1~ + +[O_PASS] +ODD JOB PASSED! + +[O_FAIL] +ODD JOB FAILED! + +[DEAD] +WASTED! + +[BUSTED] +BUSTED! + +[S_PROMP] +When not on a mission you can ~h~save your game here~w~, this will advance time six hours. + +[NUMBER] +~1~ + +[SCORE] +$~1~ + +[LOADCAR] +LOADING VEHICLE... (PRESS L1 TO CANCEL) + +[CARSOFF] +Cars turned off. + +[CARS_ON] +Cars turned on. + +[TEXTXYZ] +Writing coordinates to file... + +[CHEATON] +Cheat mode ON + +[CHEATOF] +Cheat mode OFF + +[UZI_IN] +The Uzi is now in stock at Ammunation! + +[IMPORT1] +Go outside and wait for your vehicle. + +[PAGEB1] +Pistol delivered to hideout + +[PAGEB2] +Uzi delivered to hideout + +[PAGEB3] +Body armor delivered to hideout + +[PAGEB4] +Shotgun delivered to hideout + +[PAGEB5] +grenades delivered to hideout + +[PAGEB6] +molotovs delivered to hideout + +[PAGEB7] +AK47 delivered to hideout + +[PAGEB8] +Sniper rifle delivered to hideout + +[PAGEB9] +M16 delivered to hideout + +[PAGEB10] +Rocket Launcher delivered to hideout + +[PAGEB11] +Flamethrower delivered to hideout + +[WANT_A] +You will only be arrested if you have a ~h~wanted level. + +[WANT_B] +Your ~h~wanted level~w~ is represented by the row of stars in the top right of the screen. + +[WANT_C] +You now have a ~h~wanted level~w~ of one... + +[WANT_D] +two... + +[WANT_E] +three... + +[WANT_F] +As your ~h~wanted level~w~ increases you will attract more powerful forms of law enforcement. + +[WANT_G] +When you are ~h~'busted'~w~ you are returned to the nearest police station. + +[WANT_H] +The cops will take all your weapons and some of your cash as a bribe. + +[WANT_I] +Any mission you were on will be failed. + +[WANT_J] +You will find ways of reducing your wanted level the more you play. + +[WANT_K] +If you are in a car, ~h~SPRAY SHOPS~w~ will ~h~clear your wanted level. + +[HEAL_B] +When you are ~h~'wasted'~w~ you are returned to the nearest hospital. + +[HEAL_C] +You will lose your weapons and the doctors will take some cash for patching you up. + +[HEAL_E] +You will find ways of healing or protecting yourself the more you play the game. + +[DAM] +DAMAGE: + +[KILLS] +KILLS: + +[FARES] +FARES: + +[BULL] +BULLION: + +[EVID] +EVIDENCE: + +[HEALTH] +CAR HEALTH: + +[COLLECT] +COLLECTED: + +[BOMB] +Drive your vehicle into the bomb shop to attach a ~h~bomb~w~. Cost - ~h~$1000. + +[SAVE1] +Walk through the doorway to ~h~Save the game~w~. You cannot save during a mission. + +[SAVE2] +Any vehicle left in this garage will be stored when the game is saved. + +[AMMU] +Go inside Ammu-Nation to buy a weapon. + +[BRIDGE1] +When the Callahan Bridge is repaired you will be able to drive to Staunton Island. + +[TUNNEL] +When the Porter Tunnel is opened you will be able to drive to Staunton Island. + +[LUIGI] +LUIGI MISSIONS + +[TONI] +TONI MISSIONS + +[JOEY] +JOEY MISSIONS + +[FRANK] +SALVATORE MISSIONS + +[DIABLO] +DIABLO MISSIONS + +[ASUKA] +ASUKA MISSIONS + +[B_SITE] +ASUKA SUBURBAN MISSIONS + +[KENJI] +KENJI MISSIONS + +[RAY] +RAY MISSIONS + +[LOVE] +LOVE MISSIONS + +[YARDIE] +YARDIE MISSIONS + +[HOOD] +HOOD MISSIONS + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Atlantic Quays + +[PORT_E] +Portland Harbor + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Portland Beach + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Red Light District + +[TOWERS] +Hepburn Heights + +[HARWOOD] +Harwood + +[ROADBR1] +Callahan Bridge + +[ROADBR2] +Callahan Bridge + +[TUNNELP] +Porter Tunnel + +[BOMB1] +8-Ball's Garage + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Liberty Campus + +[CONSTRU] +Fort Staunton + +[PARK] +Belleville Park + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Francis Intl. Airport + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Cochrane Dam + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Ambulance + +[CAR_2] +Firetruck + +[CAR_3] +Police + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +FBIcar + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[LUIGIS] +Luigi's Place + +[GOAWAY] +~g~You are already on a mission! + +[LUIGGO] +~g~Luigi's interviewing some new girls -Come back later! + +[JOEYGO] +~g~Joey's out on the town with Misty -Drop by later! + +[TONIGO] +~g~Toni's taken his Momma to the opera -Call in some other time! + +[KEMUGO] +~g~Maria and Kemuri are all tied up at the moment -Drop by later! + +[KENJGO] +~g~Kenji is attending a Yakuza meeting -Call by some other time! + +[RAYGO] +~g~Ray has other toilets to hang around -Try again later! + +[LOVEGO] +~g~Donald Love has other business to attend to -Make an appointment later! + +[KENSGO] +~g~Kenji is busy! -Call by later! + +[HOODGO] +~g~The Hoods are not available at this time! + +[WRONGT1] +~g~Come back between 05:00 and 21:00 for a job + +[WRONGT2] +~g~Come back between 06:00 and 14:00 for a job + +[WRONGT3] +~g~Come back between 15:00 and 00:00 for a job + +[GUN_1A] +Use the ~h~~k~~PED_CYCLE_WEAPON_RIGHT~ button ~w~and the ~h~~k~~PED_CYCLE_WEAPON_LEFT~ button ~w~to cycle through your weapons. + +[GUN_2A] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_2C] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_2D] +Hold the ~h~~k~~PED_LOCK_TARGET~ button ~w~to ~h~auto-target~w~, press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire! Try shooting the targets... + +[GUN_3A] +While holding the ~h~~k~~PED_LOCK_TARGET~ button,~w~ press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button~w~ or the ~h~~k~~PED_CYCLE_TARGET_RIGHT~ button to switch target. + +[GUN_3B] +While holding the ~h~~k~~PED_LOCK_TARGET~ button,~w~ press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button~w~ or the ~h~~k~~PED_CYCLE_TARGET_RIGHT~ button to switch target. + +[GUN_4A] +While holding the ~h~~k~~PED_LOCK_TARGET~ button~w~ you can walk or run while remaining locked onto a target. + +[GUN_4B] +While holding the ~h~~k~~PED_LOCK_TARGET~ button~w~ you can walk or run while remaining locked onto a target. + +[GUN_5] +You can practice targeting and shooting on these paper targets. When you are finished resume the mission. + +[TAXI1] +~g~Look for a fare. + +[FARE1] +~g~Destination ~w~'Meeouch Sex Kitten Club' ~g~in Redlight. + +[FARE2] +~g~Destination ~w~'Supa Save' ~g~in Portland View. + +[FARE3] +~g~Destination ~w~'old school hall' ~g~in Chinatown. + +[FARE4] +~g~Destination ~w~'Greasy Joe's Cafe' ~g~in Callahan Point. + +[FARE5] +~g~Destination ~w~'AmmuNation' ~g~in Redlight. + +[FARE6] +~g~Destination ~w~'Easy Credit Autos' ~g~in Saint Mark's. + +[FARE7] +~g~Destination ~w~'Woody's topless bar' ~g~in Redlight. + +[FARE8] +~g~Destination ~w~'Marcos Bistro' ~g~in Saint Mark's. + +[FARE9] +~g~Destination ~w~'import export garage' ~g~in Portland Harbor. + +[FARE10] +~g~Destination ~w~'Punk Noodles' ~g~in Chinatown. + +[FARE12] +~g~Destination ~w~'Football Stadium' ~g~in Aspatria. + +[FARE13] +~g~Destination ~w~'The Church' ~g~in Bedford Point + +[FARE14] +~g~Destination ~w~'The Casino' ~g~in Torrington + +[FARE15] +~g~Destination ~w~'Liberty University' ~g~in Liberty Campus + +[FARE16] +~g~Destination ~w~'Shopping Mall' ~g~in Belleville Park Area + +[FARE17] +~g~Destination ~w~'Museum' ~g~in Newport + +[FARE18] +~g~Destination ~w~'AmCo Building' ~g~in Torrington + +[FARE19] +~g~Destination ~w~'Bolt Burgers' ~g~in Bedford Point + +[FARE20] +~g~Destination ~w~'The Park' ~g~in Belleville + +[FARE21] +~g~Destination ~w~'Francis intl. Airport' + +[FARE22] +~g~Destination ~w~'Cochrane Dam' + +[FARE24] +~g~Destination ~w~'The hospital' ~g~in Pike Creek + +[FARE25] +~g~Destination ~w~'The Park' ~g~in Shoreside Vale + +[FARE26] +~g~Destination ~w~'North West Towers' ~g~in Wichita Gardens + +[NEW_TAX] +BIGGER! FASTER! HARDER! new Borgnine taxis open for business in Harwood. Call 555-BORGNINE today! + +[TSCORE2] +$~1~ + +[IN_ROW] +~1~ IN A ROW bonus! $~1~ + +[TTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle taxi missions on or off. + +[TTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle taxi missions on or off. + +[ATUTOR2] +~g~Drive the patients to Hospital CAREFULLY. Each bump reduces their chances of survival. + +[A_TIME] ++~1~ seconds + +[A_FULL] +~r~Ambulance full!! + +[A_RANGE] +~g~The ambulance radio is out of range, get closer to a hospital! + +[FTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle fire truck missions on or off. + +[FTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle fire truck missions on or off. + +[F_PASS1] +Fire extinguished! + +[F_RANGE] +~g~The fire truck radio is out of range, get closer to a fire station! + +[C_BREIF] +~g~Suspect last seen in the ~a~ area. + +[C_RANGE] +~g~The police radio is out of range, get closer to a police station! + +[DODO_FT] +You flew for ~1~ seconds! + +[EBAL] +'GIVE ME LIBERTY' + +[EBAL_A] +I know a place on the edge of the Red Light District where we can lay low, + +[EBAL_A1] +but my hands are all messed up so you better drive, brother. + +[EBAL_1] +Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a vehicle. + +[EBAL_1B] +Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a vehicle. + +[EBAL_2] +~g~Get back into the car! + +[EBAL_3] +This is the ~h~radar~w~. Use it to navigate the city, follow the ~h~blip~w~ on the ~h~radar~w~ to find the hideout! + +[EBAL_D] +I know a guy, he's connected, his name's Luigi. + +[EBAL_D1] +Me an' him go back so I could probably get you some work. C'mon lets head over there. + +[EBAL_E] +C'mon, lets drop by and I'll introduce you. + +[EBAL_I] +The boss will be out to see you shortly... + +[EBAL_J] +8-Ball's got some business up stairs. + +[EBAL_K] +Maybe you can do me a favor. + +[EBAL_L] +One of my girls needs a ride so grab a car and pick up Misty from the clinic. Then bring her back here. + +[EBAL_N] +So keep your hands on the wheel! + +[EBAL_4] +~r~8-Ball's dead! + +[EBAL_5] +~g~Get a vehicle! + +[EBAL_6] +~g~Pick up Misty! + +[LM1] +'LUIGI'S GIRLS' + +[LM2] +'DON'T SPANK MA BITCH UP' + +[LM3] +'DRIVE MISTY FOR ME' + +[LM4] +'PUMP-ACTION PIMP' + +[LM5] +'THE FUZZ BALL' + +[LM1_2] +~g~Take Misty to Luigi's Club. + +[LM1_3] +~g~Press the horn to get the girl into the car. + +[LM1_6] +~g~Get back into the car! + +[LM1_7] +Stop the vehicle next to Misty and allow her to enter it. + +[LM1_8] +You can go and see Luigi for more work or check out Liberty City. + +[LM2_A] +There's a new high on the street goes by the name of SPANK. + +[LM2_E] +Some wiseguy's been introducing this trash to my girls down Portland Harbor. + +[LM2_B] +Go and introduce a bat to his face! + +[LM2_G] +I want compensation for this insult! + +[LM2_1] +~g~Take his car and get it resprayed. + +[LM2_2A] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat! + +[LM2_2C] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat~w~! + +[LM2_2D] +Use the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~punch ~w~and ~h~kick~w~ or ~h~swing ~w~the bat~w~! + +[LM2_3] +~g~Stash the car in Luigi's lockup! + +[LM2_4] +~g~Respray the car! + +[LM3_A] +Hey I've gotta talk to you... All right Mick I'll talk to yah later. + +[LM3_B] +How yah doing kid? + +[LM3_C] +The Don's son, Joey Leone, he wants some action from his regular girl Misty. + +[LM3_D] +Go pick her up at Hepburn Heights... + +[LM3_E] +but watch yourself that's Diablo turf. + +[LM3_F] +Then run her over to his garage in Trenton and make it quick, + +[LM3_H] +so keep your eyes on the road and off Misty! + +[LM3_2] +~g~Take Misty to Joey's. + +[LM3_4] +~g~Go pick up Misty! + +[LM3_5] +You working regular for Luigi now huh? It's about time he got a driver we can trust! + +[LM3_7] +I'll be with you in a minute spark plug. + +[LM3_10] +~g~Get a vehicle! + +[LM4_B] +Go and take care of things for me. + +[LM4_C] +If you need a piece go around the back of AmmuNation opposite the subway. + +[LM5_A] +The Policeman's Ball is being held at the old school hall near the Callahan Bridge + +[LM5_B] +and they'll be looking for some 'old school' action. + +[LM5_C] +Now I got girls all over town walking the streets. + +[LM5_D] +Get'em to the ball they'll make a bundle. + +[LM5_1] +~g~You pack these ladies too tight, they gonna bruise! ~g~Drop these girls off first, then come back for more. + +[LM5_2] +~r~One of Luigi's girls is bodybag meat! + +[LM5_3] +~g~You need a car! + +[LM5_4] +~g~Pick up the girls working St. Marks. + +[LM5_5] +~g~Take the girls to the Fuzz Ball! + +[LM5_8] +~g~Girls working the Ball: ~1~ + +[JM2] +'FAREWELL 'CHUNKY' LEE CHONG' + +[JM3] +'VAN HEIST' + +[JM4] +'CIPRIANI'S CHAUFFEUR' + +[JM5] +'DEAD SKUNK IN THE TRUNK' + +[JM1_1] +~g~Take Forelli's car to 8-Ball's garage North of here, behind 'Easy Credit Autos'. + +[JM1_2] +~g~Park the car back at Marco's Bistro. + +[JM1_3] +~g~Activate the car bomb then get out of there! + +[JM1_4] +~g~You're trashing the vehicle! Get it repaired! + +[JM1_5] +~g~The car bomb's not set! + +[JM1_6] +~g~Put the car back in the correct position. + +[JM1_8A] +~y~Hey, it's my main man! + +[JM1_8B] +~y~The bomb shop's automated. Just drive in, stop your car and the shop will do the rest. + +[JM1_8C] +~y~Here, your first can be free, but after that it'll cost. + +[JM2_A] +Chunky Lee Chong is pushing spank for some new gang from Colombia... or Colorado... or something.... + +[JM2_B] +I'm not really sure. Who needs details anyway. + +[JM2_D] +That rat has sold his last stir fry. + +[JM2_E] +I want you to take him out! + +[JM2_G] +Sort yourself with a nine, you know where it is, right? + +[JM2_H] +Well remember, just watch your back in China Town, it's Triad territory. + +[JM3_A] +Alright, we're gonna hit the pay role van. + +[JM3_B] +It leaves the edge of China Town everyday. + +[JM3_C] +Bullets won't even dent the van's armor, so get a car and ram it off the road. + +[JM3_D] +Now hit it hard and the punk ass security guards should bail. + +[JM3_E] +Then take it to the warehouse at the docks and my guys are gonna take over from there. + +[JM3_F] +Now it won't be doin' it's rounds all day, so don't hang around. + +[JM3_1] +~g~Take the van to the lock up. + +[JM3_2] +~g~Ram the van until its damage is below 70 percent. + +[JM4_B] +Oh! Here's the guy I was telling you about! + +[JM4_C] +Alright Listen. This guy ain't Italian and he's no mechanic but he can get things fixed. + +[JM4_D] +This is Pops Capo, Toni Cipriani. + +[JM4_E] +Yeah, I'm Toni Cipriani + +[JM4_F] +Take him to Momma's restaurant at St Marks, alright. + +[JM4_G] +Now listen to me, I'm planning a job that needs a good driver so drop by sometime later Ok? + +[JM4_2] +Wait here! Keep the engine running. This ain't a social call. + +[JM4_3] +It's a Triad ambush! Get us out of here kid! + +[JM4_4] +The Triads think they can mess with me, the triads, with ME! + +[JM4_6] +Hey watch the car! I said no fancy crap. + +[JM4_7] +~g~Take Toni to his momma's restaurant. + +[JM4_8] +~r~Toni's been wasted! + +[JM5_A] +Beautiful! Just beautiful. + +[JM5_B] +Alright, Just the guy I need to talk to! + +[JM5_D] +One of the Forellis thought he was a wise guy, so he got what he had coming to him. + +[JM5_E] +Take the corpse to the crusher in Harwood, alright? + +[JM5_1] +~g~Take it to the crusher! + +[JM5_2] +~g~It's the Forelli brothers! + +[JM6_A] +What a ride she's gonna be, huh? + +[JM6_B] +Alright, listen. Get some wheels to the safehouse at St. Marks and pick up a few friends of mine. + +[JM6_C] +They're hittin' a bank and they need a driver. + +[JM6_D] +I gave my word that you were the man, so don't screw this up. + +[JM6_E] +Get them to the bank before five o'clock, not a minute after. + +[JM6_2] +Keep the engine running we'll be in and out in no time. + +[JM6_3] +Get us out of here!! + +[JM6_4] +Shake the cops and get us to the safehouse!! + +[JM6_6] +~g~Go and get a vehicle less conspicuous! + +[JM6_7] +~g~You need all 3 to rob the bank! + +[TM1] +'TAKING OUT THE LAUNDRY' + +[TM2] +'THE PICK-UP' + +[TM3] +'SALVATORE'S CALLED A MEETING' + +[TM4] +'TRIADS AND TRIBULATIONS' + +[TM5] +'BLOW FISH' + +[TONI_P] +I've got some urgent work for you! -Toni + +[TM1_A] +~w~Take a seat kid, take a god damned seat. + +[TM1_B] +~w~So the laundry won't pay any protection eh? + +[TM1_C] +~w~The Triads think they can mess with me? + +[TM1_D] +~w~Let's teach these would be tough guys what it means to be a tough guy. + +[TM1_E] +~w~Yeah, teach 'em some respect. No son of mine gets it from some Triads. + +[TM1_F] +~w~Your father, god rest his soul, took no crap from no Triads back in Sicily. + +[TM1_G] +~w~Sorry Ma. Yes Ma. + +[TM1_H] +~w~I want you to destroy their laundry vans + +[TM1_I] +~w~and mangle any triad gimp that gets in your way. + +[TM1_J] +~w~8-Ball can supply you with what you're gonna need. + +[TM2_A] +~w~TONI's off making people bleed or trying to. + +[TM2_AA] +~w~He'll never be as tough as his Pop, but he left you a note on the table. + +[TM2_B] +~w~The laundry has agreed to pay - you did real good kid! + +[TM2_C] +~w~Go collect the cash and bring it back here. Watch out for the Triads. + +[TM2_D] +~w~They may be shoving a firecracker up your ass, but don't take no crap. + +[TM2_E] +~w~Nobody I mean nobody, messes with TONI CIPRIANI! + +[TM2_1] +~g~Get the cash back to Toni's!! + +[TM2_2] +~g~You iced them all! + +[TM3_MA] +~w~I don't know where he is! + +[TM3_MB] +~w~I swear that boy of mine don't know himself sometimes. + +[TM3_MC] +~w~Now his father, he was different. Always on top, in charge, manful... + +[TM3_A] +~w~Don Salvatore has called a meeting. + +[TM3_B] +~w~I need you to collect the limo and his boy, Joey, from the garage. + +[TM3_C] +~w~Then get Luigi from his club, come back here and pick me up, + +[TM3_D] +~w~then we'll all drive over to the boss's place together. + +[TM3_E] +~w~Those Triads, they don't know when to stop. + +[TM3_F] +~w~They want a war. They got a war. + +[TM3_G] +~w~Now get going. + +[TM3_1] +~g~Pick up the Stretch from Joey's. + +[TM3_2] +~g~Now go pick up Luigi. + +[TM3_3] +~g~Now go pick up Toni. + +[TM3_4] +~g~Drive the goodfellas to Salvatore's place. + +[TM3_5] +~y~It's a triad ambush!! + +[TM4_B] +~w~We're at WAR! The Triads have a fish factory as a front. + +[TM4_C] +~w~Most of their business goes down at the fish market in Chinatown. + +[TM4_D] +~w~That laundry still owes us protection. + +[TM4_E] +~w~They reckon the Triads are protecting them now, so I say we exact a fitting punishment. + +[TM4_F] +~w~Take these boys over and whack the Triad Warlords! + +[TM4_G] +~w~Hell, if you get a chance, pop some of their soldiers too. + +[TM4_GAT] +~g~You need a 'Triad fish van' to enter. + +[TM5_B] +~w~OK, I've had enough of this shit. + +[TM5_C] +~w~We're gonna finish the Triads in Liberty once and for all! + +[TM5_D] +8-Ball's rigged a dustcart with a bomb. + +[TM5_E] +~w~It's on a timer so if you mess up there'll be no evidence. Go and pick up the dustcart. + +[TM5_F] +~w~Careful, 8-Ball says it's real sensitive and the slightest bump could set that thing off! + +[TM5_G] +~w~Their fish factory will open its gates for a dustcart, so you can drive right in. + +[TM5_H] +~w~Park up between the gas canisters and get the hell out of there! + +[TM5_I] +~w~I want it to rain mackerel. + +[TM5_J] +~w~We're talking real biblical here, nothing low budget. + +[FM2] +'CUTTING THE GRASS' + +[FM4] +'LAST REQUESTS' + +[FM1_A] +~w~Me an' the fellas need to talk business + +[FM1_B] +~w~so you're gonna look after my girl for the evening. + +[FM1_C] +~w~HEY MARIA! MOVE YOUR BUTT! + +[FM1_D] +~w~Dumb broad does this every time. + +[FM1_E] +~w~And here she is, the one and only Queen of Sheba! + +[FM1_F] +~w~What were you doing up there? + +[FM1_G] +~w~Whatever it was, I bet it cost me money. + +[FM1_H] +~w~Well, you don't think I hang around for the conversation, do you? + +[FM1_I] +~w~Get in that car and keep your big mouth shut. + +[FM1_J] +~w~Take the limo but bring it back in one piece, y'hear me? + +[FM1_K] +~w~And watch her, she can be trouble. + +[FM1_L] +~w~Yeah, yeah, yeah! I'm sure your new lap dog has everything covered, + +[FM1_M] +~w~and isn't he big and strong? + +[FM1_N] +~w~Hey Fido, Let's go visit Chico and get some party treats! + +[FM1_P] +~g~That's Chico over there, pull up next to him. + +[FM1_S] +~w~Here you go lady. + +[FM1_TT] +~w~IT'S A POLICE RAID! + +[FM1_1] +~g~Get back into the Stretch! + +[FM1_2] +~g~Get into the Stretch! + +[FM1_3] +~r~Leave Maria and Salvatore will have you whacked, go back and pick her up. + +[FM1_4] +~g~You've dumped the Don's woman! Get back to the warehouse and wait for Maria! + +[FM1_5] +~g~Get Maria safely back to Salvatore's! + +[FM1_6] +~g~Chico won't be there forever, get Maria to the waterfront! + +[FM1_7] +~r~Maria's dead! Salvatore won't be too pleased... + +[FM1_8] +~r~You wasted Maria's supplier! + +[FM2_J] +Leave us alone for a minute. + +[FM2_A] +The Colombian Cartel is making SPANK somewhere in Liberty. + +[FM2_K] +but we don't know where, and they seem to know everything we're doin' before we do. + +[FM2_L] +There is a guy named Curly Bob works the bar at Luigi's. + +[FM2_M] +He's been throwing more money around than he's earning. + +[FM2_N] +He usually gets a taxi home after work. So follow him. + +[FM2_O] +And if he's rattin' us out... kill him. + +[FM2_F] +Here comes our little friend. Mr big mouth himself. + +[FM2_G] +Were you followed? You know what goes on here is our little secret. + +[FM2_H] +No..no, I wasn't followed. You got my stuff? + +[FM2_I] +Here's your SPANK, squealer, now talk. + +[FM2_P] +OK, so the Leone's are fighting wars on two fronts. + +[FM2_Q] +They're in a turf war with the Triads with no sign of either side giving up. + +[FM2_R] +Meanwhile Joey Leone has stirred up some bad blood with the Forellis. + +[FM2_S] +Every day they're losing men and influence in the city. + +[FM2_T] +Salvatore is becoming dangerous and paranoid. He suspects everybody and everything. + +[FM2_U] +With loyalty like yours, what has he possibly got to worry about. + +[FM2_1] +~g~There's Curly Bob! + +[FM2_2] +~g~Curly's left the club, tail him! + +[FM2_5] +~g~Take him to Portland Harbor. + +[FM2_6] +~r~Curly won't get into a smashed-up taxi! + +[FM2_7] +~r~Curly's spooked! The meeting's off! + +[FM2_8] +~g~Whack Curly Bob! + +[FM2_9] +~r~Curly Bob's dead! + +[FM2_10] +~r~Curly got away! + +[FM2_11] +~g~Park out the front of Luigi's Club, Curly Bob will be leaving shortly. + +[FM2_12] +~r~He gave you the slip! + +[FM3_A] +~w~We should take these Colombian bastards out, + +[FM3_B] +~w~but while we're at war with the Triads we ain't strong enough. + +[FM3_C] +~w~The Cartel has got bottomless funds from pushing that SPANK crap. + +[FM3_D] +~w~If we make an open attack on them, they'll wipe the floor with us. + +[FM3_E] +~w~They must be making SPANK on that big boat that Curly lead you to. + +[FM3_F] +~w~So we gotta use our heads, or rather one head. Your head. + +[FM3_G] +~w~I'm asking you to destroy that SPANK factory as a personal favor to me, Salvatore Leone. + +[FM3_H] +~w~If you do this for me, you will be a made man, anything you want. + +[FM3_I] +~w~Go and see 8-Ball, you'll need his expertise to blow-up that boat. + +[FM3_8A] +~w~Yo my man! Salvatore phoned ahead, + +[FM3_8B] +~w~but a job like this is gonna need a lot of fireworks. + +[FM3_8D] +~w~but you know with me you get a lot of bang for your buck. + +[FM3_8E] +~w~Okay, let's do this thing! + +[FM3_8F] +~w~I can set this baby to detonate, but I still can't use a piece with these hands. + +[FM3_8G] +~w~Here, this rifle should help you pop some heads! + +[FM3_4] +~g~Stop the vehicle and let 8-Ball out! + +[FM3_7] +~r~8-Ball's been iced! + +[FM3_8] +~r~The guards have been alerted! + +[FM4_A] +~w~It's my favorite cleaner. + +[FM4_B] +~w~I'm proud of you my boy, you kicked the shit out of those grease balls. + +[FM4_C] +~w~I've got just one little job for you before we can all celebrate. + +[FM4_D] +~w~There's a car around the block from Luigi's club. + +[FM4_E] +~w~The inside is covered in brains. + +[FM4_F] +~w~We had to help some guy make up his mind and it proved a little messy. + +[FM4_H] +~w~Take it to the crusher before the cops find it. + +[AM3] +'PAPARAZZI PURGE' + +[AM4] +'PAYDAY FOR RAY' + +[AM5] +'TWO-FACED TANNER' + +[AM1_A] +We have certain issues to clear up before we can continue any form of relationship, + +[AM1_B] +business or otherwise. Lets lay our cards on the table. + +[AM1_C] +I am Yakuza and I know you worked for Salvatore Leone's family. + +[AM1_D] +I can give you work with our organization, + +[AM1_E] +But first you must prove to me that your ties with the Mafia are truly broken. + +[AM1_G] +Make sure he doesn't reach his club alive. + +[AM1_H] +Meanwhile Maria and I will catch up on old times. + +[AM1_I] +Oh..Asuka, you've got a massager. + +[AM1_J] +That's not a massager. + +[AM1_1] +~g~Salvatore is now leaving Luigi's! + +[AM1_2] +~r~You have been spotted! + +[AM1_3] +~r~You've missed Salvatore! + +[AM1_4] +~r~Nice going, you scared off the target! Call yourself a hitman? + +[AM1_5] +~g~Get to the Red Light District and wait for Salvatore to leave the club. + +[AM1_7] +~r~Salvatore's home, safe and sipping a cocktail. Ain't no one gonna call you the 'Jackal'! + +[AM1_8] +~g~Salvatore will be leaving Luigi's at about ~1~:~1~ + +[AM2_4] +~g~They seen you coming like a dayglow elephant! + +[AM3_A] +A reporter has been nosing around. + +[AM3_B] +Maria and I have taken a little holiday together until you can get rid of this perverted voyeur. + +[AM4_A] +It's my handsome handyman! + +[AM4_B] +Maria's all tied up at the moment but I'll tell her you called. + +[AM4_C] +Who's that? Asuka? I know I've been a naughty girl but I really need to pee! OK? + +[AM4_D] +It's time you met our man inside the LPD. + +[AM4_E] +Here's his payment for the last little job he did for us. + +[AM4_F] +He is understandably cautious. + +[AM4_G] +Get to the pay phone in Torrington as quick as you can and await his instructions. + +[AM5_A] +Maria and I have gone shopping. + +[AM5_B] +Our source in the police has informed us that one of our drivers is a strangely animated undercover cop! + +[AM5_C] +He's more or less useless out of his car, so we've tagged it with a tracer. + +[AM5_D] +Make him bleed! + +[AM5_1] +Tanner's on to you! + +[AS1] +'BAIT' + +[AS2] +'ESPRESSO-2-GO!' + +[AS4] +'RANSOM' + +[AS1_A] +~w~Miguel seems to think I'm mistreating him. + +[AS1_B] +~w~Still, he's revealed the extent to which Catalina fears your quest for revenge. + +[AS2_A] +~w~We underestimated Catalina's plans for SPANK. + +[AS2_B] +~w~It reaches far beyond the Yardies selling it on the street corners. + +[AS2_D] +~w~They've been selling SPANK through the street stalls. + +[AS2_1] +~g~All espresso stalls in Portland wrecked!! + +[AS2_2] +~g~All espresso stalls in Staunton Island wrecked!! + +[AS2_3] +~g~All espresso stalls in Shoreside Vale wrecked!! + +[AS2_4] +~r~The Cartel have warned their pushers!! + +[AS2_5] +~g~There are still espresso stalls in Shoreside Vale and on Staunton Island! + +[AS2_6] +~g~There are still espresso stalls in Shoreside Vale! + +[AS2_7] +~g~There are still espresso stalls on Staunton Island! + +[AS2_8] +~g~There are still espresso stalls in Portland! + +[AS2_9] +~g~There are still espresso stalls in Portland and Shoreside Vale! + +[AS2_10] +~g~There are still espresso stalls in Portland and on Staunton Island + +[AS2_12] +~g~Cruise Liberty's districts to find ~b~Espresso-2-Go stalls! + +[AS3_A] +~W~Do we tighten it some more now, or just wait for it to turn black and fall off? + +[AS3_B] +~w~Give it a quick prod... + +[AS3_D] +~w~My Handyman! + +[AS3_E] +~w~I was bored so I came over to keep Asuka company. + +[AS3_1] +~g~Find the ~r~boat~g~ and get to the ~b~marker buoy! + +[AS3_3] +~g~Wait for the ~y~plane~g~ to start its approach! + +[AS3_5] +~g~Collect the cargo! + +[AS3_4] +~g~Use a rocket launcher to shoot the ~y~plane~g~ down!! + +[AS3_2] +~b~Get to the runway marker buoys! ~y~The plane is on its final approach!! + +[AS3_6] +~g~~1~ OF 8 + +[KM1] +'KANBU BUST-OUT' + +[KM3] +'DEAL STEAL' + +[KM4] +'SHIMA' + +[KM5] +'SMACK DOWN' + +[KM1_A] +My sister speaks highly of you, + +[KM1_E] +though I am yet to be convinced that a gaijin can offer anything but disappointment. + +[KM1_B] +Perhaps you could help deal with a situation that has me at a disadvantage. + +[KM1_F] +Of course failure has its own disgrace. + +[KM1_C] +A Yakuza Kanbu is in custody awaiting transfer for trial. + +[KM1_G] +He is a valued member of the family. + +[KM1_H] +Break him out of custody and get him to the dojo at Bedford Point. + +[KM1_D] +We thankyou for your selfless actions. If you ever need help the dojo will be honoured to provide two men who will stand at your side. + +[KM1_1] +~g~Steal a cop car! + +[KM1_2] +~g~Rig the car with a bomb! + +[KM1_3] +~g~Now get him to the Yakuza dojo. + +[KM1_5] +~g~Okay now go to the police station. + +[KM1_6] +~g~Fit the car with a bomb! + +[KM1_7] +~g~Authorised police vehicles only! + +[KM1_9] +~r~You did not use a car bomb to destroy the wall + +[KM1_10] +~r~The Yakuza Kanbu is dead -along with your honor! + +[KM1_11] +~r~You brought the heat down on yourself! + +[KM2_A] +It is impossible to over-estimate the importance of etiquette in this line of work. + +[KM2_B] +To my eternal shame, a man once did me a favor and I have never had the opportunity to repay his kindness. + +[KM2_C] +The man's weakness is motor cars and he has requested that we acquire him certain models for his collection. + +[KM2_F] +My honor demands it. + +[KM2_2] +~g~Car delivered. + +[KM3_A] +When trouble looms, the fool turns his back, while the wise man faces it down. + +[KM3_B] +The Colombian Cartel have ignored repeated requests to leave our interests in Liberty well alone. + +[KM3_C] +Now they are negotiating terms with the Jamaicans in order to humiliate us further. + +[KM3_D] +They are finalizing a deal across town. + +[KM3_F] +Take one of my men, steal a Yardie car, and go and pay your respects to the Colombians. + +[KM3_E] +Our Honor demands that you leave no one alive. + +[KM3_2] +~g~Go and pick up your contact. + +[KM3_3] +~g~The meeting is being held in the hospital parking lot in Rockford! + +[KM3_4] +~r~They got away! + +[KM3_6] +~g~Kill them, kill them all! + +[KM3_8] +~g~You need a Yardie car to get on with the job! + +[KM3_9] +~r~One of the Colombians is dead, the deal's off. + +[KM3_10] +~r~The contact is dead! + +[KM4_A] +To be truly strong, it is important that you never show weakness. + +[KM4_C] +Go and collect the money immediately, so we can enter it into the casino accounts. + +[KM4_1] +I can't pay you and I wouldn't pay you if I could! + +[KM4_9] +Some young gang just jacked out the place! They took everything! + +[KM4_2] +You guys are useless. + +[KM4_10] +What kind of Yakuza are YOU anyway...? + +[KM4_3] +This ain't what I pay you goons for. If I wanted this kind of protection I'd have used the god damn police service + +[KM4_4] +~g~Punish the gang responsible and retrieve the ~b~protection money~g~! + +[KM4_7] +~r~The shopkeeper's breathed his last! + +[KM4_5] +Donald Love wishes you to drop by his tea garden so you and he can talk. + +[KM4_6] +There's the money its all there! + +[KM4_8] +~g~Briefcase collected! + +[KM5_A] +YOU! How fitting you should choose this moment to show your worthless face! + +[KM5_B] +It would appear your attempts to dissuade the Jamaicans + +[KM5_B1] +from becoming bed fellows with the Cartel were wholly inadequate! + +[KM5_C] +Yardie pushers line Liberty's streets selling packets of SPANK like they were selling hotdogs! + +[KM5_D] +Those Cartel pigs are laughing at us, at me! + +[KM5_E] +I will give you one last chance to prove my sister's faith in you to be well founded! + +[KM5_F] +Run these scumbags into the ground and wash your shame in rivers of our enemies' blood!!! + +[KM5_3] +~r~You failed to kill at least ~1~ yardies. + +[KM5_4] +~g~Congratulations you killed ~1~ Yardies. + +[KM5_5] +~g~Congratulations you killed ~1~ Yardies. BONUS $~1~ + +[RM1] +'SILENCE THE SNEAK' + +[RM3] +'EVIDENCE DASH' + +[RM4] +'GONE FISHING' + +[RM5] +'PLASTER BLASTER' + +[RM1_D] +He's under armed protection in WitSec property down in Newport, some apartment behind the car park. + +[RM1_E] +Torch that place, that should flush 'em out, and you'll hunt 'em down, make sure he never talks to nobody. + +[RM1_1] +~g~Check out the witness protection house. + +[RM1_2] +~g~Take out McAffrey! + +[RM2_A1] +Hey kid over here! + +[RM2_A] +An old army buddy of mine runs a business in Rockford. + +[RM2_D] +He's gonna need some back-up and in return he'll give you knock-down rates on any hardware you buy. + +[RM2_E] +Ray phoned ahead....but I thought there'd be more of you. + +[RM2_F] +Well, three arms are better than one, so grab whatever you need. + +[RM2_G] +~g~Go and check on Phil! + +[RM2_H] +~r~Phil has been killed!! + +[RM2_L] +Heh-hey! If I'd teamed up with you in Nicaragua maybe I'd still have my arm! + +[RM2_N] +Leave the cash behind. Now get out of here, I'll handle the cops. + +[RM3_D] +The evidence is being driven across town. + +[RM3_E] +You are going to have to ram that car and collect each little bit of evidence as it falls out. + +[RM3_F] +When you've got it all, leave it in the car and torch it. + +[RM3_G] +We're both gonna do pretty well outta this kid. + +[RM3_1] +~g~Leave the evidence in a car then torch the car. + +[RM3_4] +~g~The Prosecution has dropped the evidence! + +[RM3_6] +~r~The photos will be washed up all over Liberty! + +[RM3_7] +~g~Now torch the car! + +[RM4_A] +I think my partner's a rat. + +[RM4_C] +He goes fishing out of his boat near the lighthouse on Portland Rock most nights. + +[RM4_D] +Steal a police boat and make sure his back stabbing plans are sunk! + +[RM4_1] +~g~Go and steal a police boat. + +[RM4_2] +~g~Get to the lighthouse and 'rub out' Ray's partner! + +[RM5_A] +You useless bastard! + +[RM5_A1] +You totally messed up! My ass is on the line and you can't even kill a god damned fly. + +[RM5_B] +I paid you good money to kill that witness and he ain't dead! + +[RM5_B1] +And today he's gonna make a Federal Deposition! + +[RM5_C] +He's being moved any second now from the Carson General Hospital up in Rockford. + +[RM5_D] +If he squeals, I squeal.... + +[RM5_E] +so go do the job you were paid for! + +[RM5_1] +~g~Intercept the ambulance. + +[RM5_2] +~g~You've been spotted!! + +[RM5_3] +~g~It was a decoy! + +[RM5_4] +~g~Bullets won't get through that armored bodycast!! + +[RM5_5] +~g~That armored bodycast is flame retardant!! + +[RM5_7] +~r~Witness has been delivered!! + +[RM5_8] +~g~Witness has drowned!! + +[LOVE2] +'WAKA-GASHIRA WIPEOUT!' + +[LOVE3] +'A DROP IN THE OCEAN' + +[LOVE1_A] +First of all, let me thank you for dealing with that personal matter. + +[LOVE1_F] +People will read something into anything these days. + +[LOVE1_D] +They're trying to extort additional funds from me but I don't believe in re-negotiation. + +[LOVE1_E] +A deal is a deal, so they'll not see a penny from me. + +[LOVE1_G] +Go and rescue my friend, do whatever it takes. + +[LOVE1_2] +~g~Rescue the Old Oriental Gentleman. + +[LOVE1_3] +~g~Take the Old Oriental Gentleman back to Donald Love's building. + +[LOVE1_4] +~g~The Old Oriental Gentleman must be in one of the garages.... + +[LOVE1_6] +~r~The Old Oriental Gentleman's guts are all over the street! + +[LOVE1_7] +~g~The gate will only open for a Colombian Gang-car. + +[LOVE2_A] +Nothing drives down real estate prices like a good old fashioned gang war, + +[LOVE2_B] +apart from an outbreak of plague......but that might be going too far in this case. + +[LOVE2_C] +I've noticed the Yakuza and the Colombians are far from friends. + +[LOVE2_D] +Let's capitalise on this business opportunity. + +[LOVE2_E] +I want you to kill the Yakuza Waka-gashira, Kenji Kasen. + +[LOVE2_F] +Kenji is attending a meeting at the top of the multi-story carpark in Newport. + +[LOVE2_G] +Get a Cartel gangcar and eliminate him! + +[LOVE2_H] +The Yakuza must blame the Cartel for this declaration of war. + +[LOVE2_1] +~g~Go to Fort Staunton and steal a Colombian gangcar! + +[LOVE2_2] +~g~Now get to the ~p~multi-storey in Newport~g~ and whack Kenji! + +[LOVE2_3] +~r~If you proceed without a Cartel car you will be identified!! + +[LOVE2_4] +~r~The Yakuza have identified you!! + +[LOVE2_6] +~r~You've killed all the witnesses!! + +[LOVE3_A] +In these days of moral hypocrisy certain valuable commodities can be hard to import. + +[LOVE3_C] +It will drop several packages into the water. + +[LOVE3_D] +Make sure you pick them up before anyone else does. + +[LOVE3_1] +~g~Get a ~r~boat~g~ and follow the ~y~plane~g~! + +[LOVE4] +'GRAND THEFT AERO' + +[LOVE5] +'ESCORT SERVICE' + +[LOVE4_A] +Thank you for retrieving those packages, but they were only a decoy. + +[LOVE4_B] +Sorry about that, but that's sometimes the way in business. + +[LOVE4_C] +My real objective was hidden on the plane all along. + +[LOVE4_F] +I've paid off the officials. + +[LOVE4_1] +~r~The Colombian Cartel is here!! + +[LOVE4_2] +~g~The package is gone! Track down the Colombians and retrieve it. + +[LOVE4_3] +~g~Panlantic Construction...? + +[LOVE4_5] +~g~The package should be in the plane.... + +[LOVE4_6] +~g~Take the lift up the tower! + +[LOVE5_B] +My Oriental friend will need an escort while he takes my latest acquisition to be authenticated. + +[LOVE5_1] +~g~Lets go! + +[LOVE5_2] +~g~You'll need a car! + +[LOVE5_3] +~g~Go ahead and scout the exit of the tunnel! + +[LOVE5_4] +~r~Protect the truck! + +[RM6] +'MARKED MAN' + +[RM6_A] +You weren't followed? Good. + +[RM6_B] +This is it, I'm way over my head and I'm starting to drown here! + +[RM6_D] +I'm a marked man, so I'm getting out of here. + +[RM6_E] +Get me to my flight at the airport and I'll make it worth your while! + +[RM6_666] +Take care of my bullet proof Patriot. See you in Miami, Ray + +[CAT1] +'RANSOM' + +[CAT2] +'THE EXCHANGE' + +[CAT1_A] +I've got your precious Maria. If you don't want her face to look like she fell out with the butcher. + +[CAT2_F] +I broke a nail, and my hair's ruined. Can you believe it? This one cost me fifty dollars! + +[CAT2_G] +I was so scared, but then I thought to myself, you're a big girl now. + +[CAT2_H] +Oh we're going to have such fun, cause, you know, my sister said she wanted to come to stay with her two kids, + +[CAT2_I] +because her husband's playing around again and.. + +[CAT1_E] +XXXX + +[CAT1_F] +Get to Catalina before the time runs out! + +[CAT_MON] +~g~You don't have enough money yet. You need $500,000. + +[BITCH_D] +~g~Maria's dead! + +[WEATHER] +FORCE WEATHER + +[WEATHE2] +WEATHER NORMAL + +[8001] +You failed miserably!! + +[1000] +YOU ARE DEAD + +[1001] +YOU ARE DEAD + +[1002] +YOU ARE DEAD + +[1003] +YOU ARE DEAD + +[1004] +YOU ARE DEAD + +[1005] +BUSTED + +[1006] +BUSTED + +[1007] +BUSTED + +[1008] +BUSTED + +[1009] +BUSTED + +[GA_4] +Car bombs are $1000 each + +[GA_5] +Your car is already fitted with a bomb. + +[GA_6] +Park it, prime it by pressing the ~h~~k~~PED_FIREWEAPON~ button~w~ and LEG IT! + +[GA_7] +Arm with ~h~~k~~PED_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + +[GA_8] +Use the detonator to activate the bomb. + +[GA_9] +You collected ~1~ out of 10 special cars + +[GA_10] +Nice one. Here's your $~1~ + +[GA_11] +We got these wheels already. It's worthless to us! + +[GA_12] +Bomb armed + +[GA_13] +Delivered like a pro. Complete the list and there'll be a bonus for you. + +[GA_14] +All the cars. NICE! Here's a little something. + +[GA_15] +Hope you like the new color. + +[GA_16] +Respray is complementary. + +[GA_19] +We're not interested in that model. + +[GA_20] +We got more of these than we can shift. Sorry man, no deal. + +[CR_1] +Crane cannot lift this vehicle. + +[PU_MONY] +You don't have enough cash. + +[CO_ALL] +You got all of them. Here's a little something... + +[PAUSED] +GAME PAUSED + +[HEALTH1] +Get outta here! You're perfectly healthy. + +[HEALTH2] +Healthcare costs. + +[HEALTH3] +I'll just fix you up. + +[HEALTH4] +That will be $250. + +[FEB_STA] +Stats + +[FEB_BRI] +Briefs + +[FEB_CON] +Controls + +[FEB_AUD] +Audio + +[FEB_DIS] +Display + +[FEB_LAN] +Language + +[FEP_STA] +STATS + +[FEP_BRI] +BRIEFS + +[FEP_CON] +CONTROLS + +[FEP_AUD] +AUDIO + +[FEP_DIS] +DISPLAY + +[FEP_LAN] +LANGUAGE + +[FEF_ST1] +Who's the bad man? + +[FEF_ST2] +How much havoc have you caused + +[FEF_BR1] +Lost the plot? + +[FEF_CO1] +Need more control, freak? + +[FEF_CO2] +Choose the best contoller set-up for your playing style + +[FEF_SA1] +Keep your place in the pile! + +[FEF_SA2] +Save and load your games + +[FEF_AU1] +Pump up the volume! + +[FEF_AU2] +Select a radio station and sound effect + +[FEF_DI1] +Change the game! + +[FEF_DI2] +Customize the game for your TV + +[FEF_LA1] +What you talking about willis? + +[FEF_LA2] +Choose your preferred parlance + +[FEM_ON] +On + +[FEM_OFF] +Off + +[FEM_YES] +Yes + +[FEM_NO] +No + +[FEM_NON] +None + +[FEB_PMB] +Previous Mission Briefs: + +[FEC_NA] +NA + +[FEC_CWL] +Cycle Weapon left + +[FEC_CWR] +Cycle Weapon right + +[FEC_LOF] +Look forward + +[FEC_TAR] +Target + +[FEC_MOV] +Movement + +[FEC_CAM] +Camera modes + +[FEC_PAU] +Pause + +[FEC_ENV] +Enter vehicle + +[FEC_JUM] +Jump + +[FEC_ATT] +Attack or Fire weapon + +[FEC_RUN] +Run + +[FEC_FPC] +First person camera + +[FEC_LL] +Look left + +[FEC_LB1] +Look + +[FEC_LB2] +behind + +[FEC_LB] +Look behind + +[FEC_LR] +Look right + +[FEC_HOR] +Horn + +[FEC_VES] +Vehicle control + +[FEC_RSC] +Radio station cycle + +[FEC_BRA] +Brake or Reverse + +[FEC_HAB] +Hand brake + +[FEC_CAW] +Car weapon + +[FEC_ACC] +Accelerate + +[FEC_SMT] +Special mission trigger + +[FEC_CCF] +Configuration: + +[FEC_CF1] +Setup1 + +[FEC_CF2] +Setup2 + +[FEC_CF3] +Setup3 + +[FEC_CF4] +Setup4 + +[FEC_CDP] +Controller Display: + +[FEC_ONF] +On Foot + +[FEC_INC] +In Car + +[FEC_VIB] +Vibration: + +[FEA_MUS] +Music Volume + +[FEA_SFX] +SFX Volume + +[FEA_OUT] +Output: + +[FEA_ST] +Stereo + +[FEA_MNO] +Mono + +[FEA_RSS] +Radio station select: + +[FEA_NON] +None + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_BRI] +Brightness + +[FED_TRA] +Trails: + +[FEL_ENG] +English + +[FEL_FRE] +French + +[FEL_GER] +German + +[FEL_ITA] +Italian + +[FEL_SPA] +Spanish + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Debug Streaming Requests + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Memory Card Menu + +[FEM_RMC] +Register MemCard One + +[FEM_TFM] +Test Format MemCard One + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_CRD] +Create Root Dir + +[FEM_CLI] +Create And Load Icons + +[FEM_FFF] +Fill First File with Guff + +[FEM_SOG] +Save Only The Game + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_STG] +Save The Game + +[FEM_STS] +Save The Game under GTA3 name + +[FEM_CPD] +Create copy protected mag directory + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_TS] +Test Save: + +[FEM_TL] +Test Load: + +[FEM_TD] +Test Delete: + +[FEM_SL0] +Slot0 + +[FEM_SL1] +Slot1 + +[FEM_SL2] +Slot2 + +[FEM_SL3] +Slot3 + +[FEM_SL4] +Slot4 + +[FEM_SL5] +Slot5 + +[FEM_SL6] +Slot6 + +[FEM_SL7] +Slot7 + +[PL_STAT] +Player stats + +[PE_WAST] +People you've wasted + +[PE_WSOT] +People wasted by others + +[CAR_EXP] +Cars exploded + +[TM_BUST] +Times busted + +[M_WASTE] +Civilian males wasted + +[F_WASTE] +Civilian females wasted + +[PIG_WST] +Cops wasted + +[GNG_WST] +Gang members wasted + +[MED_WST] +Medics wasted + +[FIRE_WS] +Firemen wasted + +[DED_CRI] +Criminals wasted + +[DED_DED] +Deadbeats wasted + +[DED_HOK] +Hookers wasted + +[HEL_DST] +Helicopters destroyed + +[PER_COM] +Percentage completed + +[KGS_EXP] +Kgs of explosives used + +[ACCURA] +Accuracy + +[ELBURRO] +Best Turismo time in secs + +[CAR_CRU] +Cars crushed + +[HED_EX] +Heads exploded + +[TM_DED] +Hospital visits + +[DAYSPS] +Days passed in game + +[MMRAIN] +Mm rain fallen + +[MXCARD] +Max. INSANE Jump dist. (ft) + +[MXCARJ] +Max. INSANE Jump height (ft) + +[MXCARDM] +Max. INSANE Jump dist. (m) + +[MXCARJM] +Max. INSANE Jump height (m) + +[MXFLIP] +Max. INSANE Jump flips + +[MXJUMP] +Max. INSANE Jump rotation + +[BSTSTU] +Best INSANE stunt so far: + +[INSTUN] +Insane stunt + +[PRINST] +Perfect insane stunt + +[DBINST] +Double insane stunt + +[DBPINS] +Perfect double insane stunt + +[TRINST] +Triple insane stunt + +[PRTRST] +Perfect triple insane stunt + +[QUINST] +Quadruple insane stunt + +[PQUINS] +Perfect quadruple insane stunt + +[NOSTUC] +No INSANE stunts completed + +[NOUNIF] +Unique Jumps completed + +[NOUNGM] +Total Unique Jumps + +[NMISON] +Mission attempts + +[NMMISP] +Missions passed + +[PASDRO] +Passengers dropped off + +[MONTAX] +Cash made in taxi + +[DAYPLC] +Daily police spending + +[CRIMRA] +Criminal rating + +[GMSTOR] +Game Store + +[PREBRF] +Previous Briefs + +[CNTLS] +Controls + +[MUSMEN] +Music/SFX + +[GAMSET] +Game Settings + +[LANGUA] +Language + +[DSPLAY] +Display + +[DEBUGM] +Debug Menu + +[QUITOP] +Quit Options + +[CONTRL] +Control Configuration + +[SET1EN] +SetUp 1. Enabled + +[SET1] +SetUp 1. + +[SET2EN] +SetUp 2. Enabled + +[SET2] +SetUp 2 + +[SET3EN] +SetUp 3. Enabled + +[SET3] +SetUp 3 + +[SET4EN] +SetUp 4. Enabled + +[SET4] +SetUp 4 + +[GOBACK] +GoBack + +[SOUND] +SOUND + +[MUSVOL] +Music Volume + +[SFXVOL] +SFX Volume + +[SCROPT] +SCREEN OPTIONS + +[CTRSCR] +Center Screen + +[SCRFOR] +Screen format + +[GMSVLQ] +GAME SAVE-LOAD-QUIT + +[GMREST] +Restart Game + +[GMLOAD] +Load Game + +[GMSAVE] +Save Game + +[NOGMSV] +Can save only at your hideout. + +[DLFILE] +Delete Grand Theft Auto 3 Files + +[CHFILE] +CHOOSE FILE TO LOAD + +[CHFIDL] +CHOOSE FILE TO DELETE + +[SVCONF] +SAVE CONFIRMATION + +[SVFNAM] +Your saved file name is + +[LANGSL] +LANGUAGE SELECTION + +[ENGLIS] +English + +[GERMAN] +German + +[ITALIA] +Italian + +[FRENCH] +French + +[SPAIN] +Spanish + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (teststuff) + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[GORLEV] +Gore Level + +[SICASS] +Sick Fuck + +[SICSIC] +Sick Fucker + +[SCASSL] +Sick Fuck Selected + +[SCSCSL] +Sick Fucker Selected + +[PRVMEN] +Previous Mission Briefs + +[FORMEN] +Format Menu + +[MEMTST] +MemoryCardTest screen + +[REGCAR] +Register MemoryCard One + +[TEFONE] +Test Format MemCard One + +[TEUFON] +Test UnFormat MemCard One + +[CRROOT] +CreateRootDir + +[CRLDIC] +Create and Load Icons + +[FLFSGF] +Fill First File With Guff + +[PUSAVE] +Save Only the game + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +SaveTheGame + +[CNTSAV] +Can't save the game. On a mission. + +[CNCSAV] +Can't save the game. You're in a car + +[CRMGSV] +Create copy protected magazine directory + +[MGSVCN] +MagazineDirectory Created + +[MGSVNC] +MagazineDirectory Not Created + +[YES] +Yes + +[NO] +No + +[X] +x + +[LAST] +Last message. + +[FEDS_XB] +Select + +[FEDS_TB] +Back + +[FEDS_ST] +START button - RESUME + +[FEST_OO] +out of + +[FED_SUB] +Subtitles: + +[FEC_TUC] +Turret control + +[FEC_SM3] +Special mission trigger (R3 button) + +[FEC_RS3] +Radio station cycle (L3 button) + +[FEC_HO3] +Horn (L3 button) + +[DIAB1] +'TURISMO' + +[DIAB2] +'I SCREAM, YOU SCREAM' + +[DIAB3] +'TRIAL BY FIRE' + +[DIAB4] +'BIG'N'VEINY' + +[DIAB1_A] +El Burro wants to offer you an opportunity. Get to the payphone in Hepburn Heights if you want more info. + +[DIAB1_C] +You drive a mean race. Drop by the payphone again and 'El Burro' may have some work for you. + +[DIAB1_1] +~g~3..2..1.. GO GO GO! + +[DIAB1_4] +~g~Get a fast car and get to the starting grid. + +[DIAB1_3] +~r~You couldn't win a raffle, LOSER! + +[DIAB1_2] +~g~Congratulations you won, with an incredible time of ~1~ seconds. + +[FIRST] +~g~1st + +[SECOND] +~g~2nd + +[THIRD] +~g~3rd + +[FOURTH] +~g~4th + +[DIAB2_1] +~g~Pick up the briefcase in Harwood. + +[DIAB2_2] +~g~Find an icecream van. + +[DIAB2_3] +~g~Park the icecream van down at Atlantic Quays. + +[DIAB2_4] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_6] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_7] +~g~Press the ~w~~k~~VEHICLE_HORN~ button ~g~to activate the Icecream jingle. + +[DIAB2_5] +~g~Exit the van then use the remote to detonate the Icecream van. + +[YD1] +'BLING-BLING SCRAMBLE' + +[YD2] +'UZI RIDER' + +[YD3] +'GANGCAR ROUND-UP' + +[YD4] +'KINGDOM COME' + +[YD_P] +King Courtney would like a word. Get to the payphone in Aspatria!! + +[YD1_A] +~w~This is King Courtney. + +[YD1_A1] +~w~My Yardie posse could do with a driver and you've got a reputation for hot moves. + +[YD1_B] +~w~Get to the waste ground opposite the stadium in a car and wait for the other hopefuls. + +[YD1_C] +~w~I've got men watching checkpoints all over Staunton. + +[YD1_D] +~w~First driver to a checkpoint gets a Grand, then it's on to the next stop. + +[YD1_D1] +~w~If you get more checkpoints than any other driver, I could have some work for you. + +[YD1_E] +~g~Prepare to race! + +[YD1_F] +~g~You jumped the start -I like your style!! + +[YD1_G] +~r~This is a CAR RACE. You need a CAR fool! + +[YD1GO] +~g~GO!! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000!! + +[Y1_1ST] +~g~You came first with ~1~ successful checkpoints! + +[Y1_2ND] +~y~2nd with ~1~ successful checkpoints. ~y~Close, but you just ain't the best! + +[Y1_3RD] +~r~3rd with ~1~ successful checkpoints. ~r~I thought you said you were good! + +[Y1_LAST] +~r~You were last! ~r~You wasted my time FOOL! + +[Y1_J1ST] +~y~Joint 1st with ~1~ successful checkpoints. ~y~Good, but you gotta be the best to drive for Queen Lizzy! + +[Y1_J2ND] +~r~Joint 2nd with ~1~ successful checkpoints. You drive like a crazed monkey! + +[Y1JLAST] +~r~Joint last! You talk like a driver, but you drive like a talker! + +[Y1_TEST] +CAR IN WATER!! + +[YD2_A] +~w~I need to see if you're capable of doing my dirty work. + +[YD2_A1] +~w~See if you can be trusted. + +[YD2_B] +~w~Two of my boys will be there any second to take you for a ride, + +[YD2_B1] +~w~see if you are who you say you are. + +[YD2_C] +~w~We're going for a little ride into Hepburn Heights, whack us some filthy Diablos been dissing Queen Lizzy. + +[YD2_CC] +~w~Here, you'll need a 'piece'. + +[YD2_D] +~w~You do the driving and shooting. We'll make sure you don't get cold feet. + +[YD2_E] +~w~Let's drive!! + +[YD2_F] +~r~He's bailed out on us, cap his yellow ass!!! + +[YD2_G1] +~w~Hepburn Heights..Let's kill me some filthy Diablos... + +[YD2_G2] +~w~But remember, ~r~You don't leave this car!! + +[YD2_H] +~w~OK, Get us back to Yardie turf! GO GO GO!! + +[YD2_L] +~w~You did good, Reaperman! + +[YD2_M] +~r~He's wrecked my car! Waste him! + +[YD2_N] +~w~Get your ass back in this car! + +[YD3_A] +I want you to boost some gang cars + +[YD3_A1] +so we can do hits on our enemies' turf. + +[YD3_B] +I need a Mafia Sentinel, + +[YD3_B1] +a Yakuza Stinger and a + +[YD3_B2] +Diablo Stallion so we can hit any gang in Liberty. + +[YD3_C] +Drop them off at the garage in Newport and remember, + +[YD3_C1] +they're no use to us wrecked!! + +[YD3_D] +Spare text label + +[YD3_E] +~r~You've already boosted a diablo gangcar! + +[YD3_F] +~r~You've already boosted a Mafia gangcar! + +[YD3_G] +~r~You've already boosted a Yakuza gangcar! + +[YD3_H] +~g~Diablo gangcar boosted! + +[YD3_I] +~g~Mafia gangcar boosted! + +[YD3_J] +~g~Yakuza gangcar boosted! + +[YD3_K] +~r~The car's nearly wrecked! Get it repaired! + +[YD3_L] +~g~Take it to the ~p~garage! + +[YD3_M] +~r~You've flipped it! Get another one! + +[YD4_A] +Listen up! + +[YD4_A1] +Get over to Bedford Point. + +[YD4_A2] +There's a stash in an old car I need pronto! + +[YD4_B] +LETTER: I hear you've been a busy boy. Well I've been a busy girl. + +[YD4_C] +I think it's time you witnessed the real power of 'SPANK'! Besos y fuderes, Catalina, xxx. + +[YD4_D] +PS: DIE PEEG DOG, DIE!! + +[YD4_1] +~g~SPANKED-up madmen!! + +[YD4_2] +~g~Destroy the madmens' vans!! + +[HM_1] +'UZI MONEY' + +[HM_2] +'TOYMINATOR' + +[HM_3] +'RIGGED TO BLOW' + +[HM_5] +'RUMBLE' + +[HOOD1_A] +Get to the payphone in Wichita Gardens and we'll talk business. + +[HM1_A] +Yo! This is D-Ice of the Red Jacks! + +[HM1_C] +These young punks, they come onto the streets and they got nothing but guns and SPANK on their minds. + +[HM1_3] +~g~The 'Nines' walk their turf in Wichita Gardens. + +[HM2_3] +If you hit a vehicle's wheels the RC buggy will detonate! + +[HM2_4] +If it goes out of range the RC buggy will detonate! + +[HM2_5] +~r~Out of range! + +[HM3_1] +~g~Get to the garage but watch out if the car takes too much damage it will blow! + +[HM3_2] +~g~Take the car back it has to be in perfect condition - no damage! + +[HM3_3] +~g~Get the car repaired! + +[HM4_D] +~g~Get a vehicle! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_1] +~g~Head to the location where the cargo is scattered, you need to collect 30 pieces of bullion. + +[HM4_2] +~g~Remember when the vehicle becomes too heavy and slow goto the garage and drop off the cargo. + +[HM5_3] +~r~You were told to use a baseball bat only! + +[HM5_4] +~r~Your contact's dead! + +[MEA1] +'THE CROOK' + +[MEA2] +'THE THIEVES' + +[MEA3] +'THE WIFE' + +[MEA4] +'HER LOVER' + +[MEAT1_A] +A friend said you could fix some problems I got. Get to the payphone in Trenton if you think you can help. + +[MEA1_B3] +~g~Go and meet the Bank Manager. + +[MEA1_B6] +~g~Take the car to the crusher to get rid of evidence, get out of the car and the crane will pick it up. + +[MEA1_1] +~r~The Bank Manager's dead! + +[MEA1_2] +~r~You were told to crush the vehicle! + +[MEA1_3] +~g~Get out of the car! + +[MEA1_4] +~r~You have left the Bank Manager behind! + +[MEA2_B3] +~g~Go and meet the thieves. + +[MEA2_B4] +~g~Take them to the Bitch'n' Dog Food Factory + +[MEA2_B6] +~g~Get the car resprayed to get rid of any evidence. + +[MEA2_1] +~r~You were told to crush the vehicle! + +[MEA2_2] +~r~A thief's dead! + +[MEA2_4] +~r~You have left a thief behind! + +[MEA3_B3] +~g~Go and collect Mrs. Chonks + +[MEA3_B6] +~g~Take the car and dump it into the sea, this will get rid of any evidence. + +[MEA3_1] +~r~The wife's dead! + +[MEA3_2] +~r~You were supposed to dump the vehicle in the water! + +[MEA3_3] +~r~You have left his wife behind! + +[MEA4_B3] +~g~Pick up his wife's lover + +[MEA4_B6] +It's far too late for that Marty. You had your chance, but now I'm taking over the business... + +[MEA4_1] +~r~Carlos is dead! + +[MEA4_3] +~r~You have left Carlos the loan shark behind! + +[LOOK_A] +Press and hold the ~h~~k~~VEHICLE_LOOKLEFT~ button ~w~or the ~h~~k~~VEHICLE_LOOKRIGHT~ button~w~ to look ~h~left~w~ or ~h~right~w~ while in a vehicle. Press both to look ~h~behind~w~. + +[LOVE6_1] +~g~Now lead the cops away from the warehouse! + +[LOVE6_2] +~r~You failed to lead the police far enough away! + +[RM4_3] +~r~Ray's partner has escaped! + +[RM6_C] +The CIA seem to have a vested interest in SPANK + +[RM6_C1] +and they don't like us screwing with the Cartel. + +[C_PASS] +THREAT ELIMINATED! + +[CTUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Vigilante missions on or off. + +[CTUTOR2] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Vigilante missions on or off. + +[COPCART] +~g~You have ~1~ seconds to return to a police vehicle before the mission ends. + +[C_FAIL] +Vigilante mission ended! + +[C_CANC] +~r~Vigilante mission cancelled! + +[C_ESCP] +~r~The suspect has escaped! + +[C_TIME] +~r~Your time as a law enforcer is over! + +[C_VIGIL] +VIGILANTE BONUS!! + +[A_FAIL2] +~r~Your lack of urgency has been fatal to the patient! + +[A_FAIL3] +~r~The patient is dead!! + +[A_PASS] +Rescued! + +[F_FAIL2] +~r~You're too late! + +[A_COMP2] +You will never get tired! + +[RM2_M] +If you need any firepower just drop by and take what you need from the lockers. + +[HEAL_A] +Your ~h~health~w~ is displayed in orange in the top right of the screen. + +[YD1_CNT] +~1~ of 15! + +[FM1_9] +~g~Thats the party up ahead, drop Maria off out front. + +[FM1_Y] +~w~You know I enjoyed myself for the first time in a long while, and you treated me really good. With respect and everything. + +[FM1_AA] +~w~Oh, I'd better go. I'll see you around I hope. + +[NOCONTE] +Please re-insert an analog controller (DUALSHOCK#) or analog controller (DUALSHOCK#2) in controller port 1 to continue. + +[WRCONT] +The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto 3 requires an analog controller (DUALSHOCK#) or analog controller (DUALSHOCK#2). + +[WRCONTE] +The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto 3 requires an analog controller (DUALSHOCK#) or analog controller (DUALSHOCK#2). + +[WRONGCD] +Incorrect disc. Please insert correct disc. + +[NOCD] +The disc tray is empty. Please insert disc. + +[OPENCD] +The disc tray is open. Please close the disc tray. + +[CDERROR] +Error reading the Grand Theft Auto 3 DVD + +[RESTART] +Starting new game + +[GA_3] +No more freebies. $1000 to respray! + +[GA_1] +Whoa! I don't touch nothing THAT hot! + +[GA_1A] +Come back when you're not so busy... + +[S_PROM2] +The garage next door can store one vehicle when you save your game. + +[STOCK] +out of stock + +[FM1_O] +~w~He's at the rail station at the Chinatown waterfront I think. + +[EBAL_B] +This is the place right here, let's get off the street and find a change of clothes! + +[EBAL_G] +This is Luigi's club. Let's go round the back and use the service door. + +[AM4_3] +You must be Asuka's new errand boy! + +[AM4_4] +You got the money? Is it all here? + +[AM4_5] +I know what you're thinking, another bent cop. + +[AM4_6] +Well, it's a bent world. + +[AM4_7] +Just 'cause I lost a few partners, those suckers from internal affairs have started sniffing around. + +[AM4_8] +Reckon they can smell me. + +[AM4_9] +Well, this city is just one big open sewer. + +[AM4_10] +But I'm gonna need some non-union help. + +[AM4_11] +And if you're interested you'll know where to find me. + +[CAM_A] +Press the ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~ button~w~ to change ~h~camera ~w~modes when on foot or in a vehicle. + +[CAM_B] +Press the ~h~directional button up~w~ and ~h~down~w~ to change ~h~camera ~w~modes when on foot or in a vehicle. + +[KM2_1] +~g~Repair the car, it's gotta be mint. + +[LM3_6] +Joey... + +[LM3_6A] +Am I goin' to play with your big end again? + +[LM3_9A] +there might be some work for you. + +[LM3_9B] +Alright? + +[AWAY2] +~r~They got away. + +[AWAY] +~r~He's clean out of here! + +[JM6_1] +Get to the bank on the main drag. + +[GA_6B] +Park it, prime it by pressing the ~h~~k~~PED_FIREWEAPON~ button~w~ and LEG IT! + +[GA_7B] +Arm with ~h~~k~~PED_FIREWEAPON~ button~w~. Bomb will go off when engine is started. + +[BAT1] +~g~Pick up the bat! + +[EBAL_O] +If you don't mess this up, maybe there be more work for you. Now get outta here! + +[HELP9_B] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP9_C] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[JM6_8] +~r~You've lost all the robbers! + +[COLT_IN] +The Pistol is now in stock at Ammunation! + +[TAXI2] +~r~You're out of time! + +[TAXI3] +~r~Your passenger fled in terror! + +[TAXI7] +~r~Your car is trashed, get it repaired. + +[TAXI4] +Fare complete! + +[TAXI5] +SPEED BONUS!! + +[TAXI6] +Taxi mission over + +[FRANGO] +~g~Salvatore wants you to help Toni deal with the Triads first! + +[PAGEB12] +Police Bribe delivered to hideout + +[PAGEB13] +Health delivered to hideout + +[PAGEB14] +Adrenaline delivered to hideout + +[KM1_4] +~g~You need a cop car to do the job! + +[CAT1_B] +bring $500,000 to the Villa at Cedar Grove. + +[JM2_C] +He's got a noodle stand down in China Town. + +[RM6_1] +Here's a key to a lock-up. + +[RM6_2] +You'll find some cash and some 'supplies' I'd stashed in case things got tight. + +[RM6_3] +See y'around. + +[FE_INIP] +Initialising and loading Pause Menu... Please wait. + +[FESZ_CA] +Cancel + +[FESZ_QU] +Quit + +[FESZ_L1] +Game saved successfully! + +[FESZ_L2] +Your saved filename is: + +[FESZ_OK] +OK + +[FES_LGA] +Load Game + +[FES_DGA] +Delete Game + +[FES_NGA] +New Game + +[FES_CAN] +Cancel + +[FESZ_QL] +All unsaved progress in your current game will be lost. Proceed with loading? + +[FESZ_QD] +Proceed with deleting this saved game? + +[FESZ_QO] +Proceed with overwriting this saved game? + +[FESZ_QR] +Are you sure you want to start a new game? All progress since the last save game will be lost. Proceed? + +[FESZ_QS] +PROCEED WITH SAVE ? + +[T4X4_1] +'PATRIOT PLAYGROUND' + +[T4X4_2] +'A RIDE IN THE PARK' + +[T4X4_3] +'GRIPPED!' + +[MM_1] +'MULTISTOREY MAYHEM' + +[T4X4_1A] +~g~You have ~y~5 minutes~g~ to collect ~y~15~g~ checkpoints. ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_1B] +~1~ of 15! + +[T4X4_1C] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~20 SECONDS~g~. + +[T4X4_2A] +~g~You have ~y~2 minutes~g~ to collect ~y~12~g~ checkpoints!! ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_2B] +~1~ of 12! + +[T4X4_2C] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~10 SECONDS~g~. + +[T4X4_3A] +~g~You have ~y~5 minutes~g~ to collect ~y~20~g~ checkpoints. ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_3B] +~y~PASS THROUGH~g~ the first checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~15 SECONDS~g~. + +[T4X4_3C] +~1~ of 20! + +[T4X4_F] +~r~You bailed! Too tough for you?! + +[MM_1_A] +~g~You have ~y~2 minutes~g~ to collect ~y~20 checkpoints~g~ in the multistorey! ~g~You may collect them in ~y~ANY ORDER. + +[MM_1_B] +~1~ of 20! + +[MM_1_C] +~g~That's 20 seconds, plus ~y~5 SECONDS~g~ for each checkpoint. ~g~The timer will start ~y~IMMEDIATELY. + +[FM2_14] +~r~You got too close and spooked Curly! + +[FM2_15] +~g~Don't get too close or Curly will suspect something! + +[UPSIDE] +~r~You flipped your wheels! + +[FM2_16] +SPOOKOMETER: + +[LM3_11] +~g~Misty won't ride in a bus get another vehicle! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Firetruck + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulance + +[FBICAR] +Fbi Car + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Police + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Train + +[HELI] +Helicopter + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[BELLYUP] +Triad Fish Van + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Mafia Sentinel + +[YARDICR] +Yardie Lobo + +[YAKUZCR] +Yakuza Stinger + +[DIABLCR] +Diablo Stallion + +[COLOMCR] +Cartel Cruiser + +[HOODSCR] +Hoods Rumpo XL + +[AEROPL] +Aeroplane + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Dist. travelled on foot (miles) + +[FEST_DC] +Dist. travelled by car (miles) + +[FESTDFM] +Distance travelled on foot (m) + +[FESTDCM] +Distance travelled by car (m) + +[FEST_R1] +Patriot Playground in secs + +[FEST_R2] +A Ride In The Park in secs + +[FEST_R3] +Gripped! in secs + +[FEST_RM] +Multistorey Mayhem in secs + +[FEST_LS] +People saved in an Ambulance + +[FEST_CC] +Criminals killed on Vigilante Mission + +[FEST_FE] +Total fires extinguished + +[FEST_LF] +Longest flight in Dodo + +[FEST_BD] +Best time for bomb defusal + +[FEST_RP] +Rampages passed + +[FEST_MP] +Missions passed + +[FEST_BB] +Bling-bling Scramble: + +[FEST_H0] +Most checkpoints + +[FEST_GC] +Gang Cars Totalled: + +[FEST_H1] +Diablo destruction + +[FEST_H2] +Mafia Massacre + +[FEST_H3] +Casino Calamity + +[FEST_H4] +Rumpo Wrecker + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ] +UNIQUE STUNT BONUS! + +[SPRAY] +Drive your vehicle into the spray shop to lose your ~h~wanted level~w~, ~h~repair ~w~and ~h~respray ~w~your vehicle. Cost - ~h~$1000. + +[HM1_1] +~g~Ice 20 Purple Nines in 2 minutes 30 seconds. + +[KM1_8A] +Press the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~activate the bomb,~w~ remember to get out of the way. + +[KM1_8D] +Press the~h~ ~k~~PED_FIREWEAPON~ button~w~ to ~h~activate the bomb,~w~ remember to get out of the way. + +[KM1_12] +~g~Get him to the dojo but get rid of the cops first! + +[RATNG1] +Pickpocket + +[RATNG2] +Bully + +[RATNG3] +Thug + +[RATNG4] +Hustler + +[RATNG5] +Goon + +[RATNG6] +Wheelman + +[RATNG7] +Hired muscle + +[RATNG8] +Fixer + +[RATNG9] +Associate + +[RATNG10] +Cleaner + +[RATNG11] +Assassin + +[RATNG12] +Right-hand man + +[RATNG13] +Executioner + +[RATNG14] +Capo + +[RATNG15] +Boss + +[1010] +~r~Your vehicle is upside down + +[1011] +~r~Your vehicle is upside down + +[1012] +~r~Your vehicle is upside down + +[1013] +~r~Your vehicle is upside down + +[1014] +~r~Your vehicle is upside down + +[JM4_10] +OK, Kid. Drive me to the laundry in Chinatown first, I got a bit of business to take care of. + +[JM4_11] +Those washer women aint been payin' their protection money. + +[JM4_12] +And watch the car, Joey just fixed this junk heap. + +[JM4_13] +So no fancy crap, OK? + +[KM4_11] +~g~Take the money back to the casino! + +[FEF_BR2] +Find it again by reading any mission briefs collected to date. + +[TRAIN_1] +Kurowski Station + +[TRAIN_2] +Rothwell Station + +[TRAIN_3] +Baillie Station + +[SUBWAY1] +Portland Station + +[SUBWAY2] +Rockford Station + +[SUBWAY3] +Staunton South Station + +[SUBWAY4] +Shoreside Terminal + +[MEA4_2] +~r~Marty Chonks is dead! + +[SPRAY1] +Drive your vehicle into the spray shop to lose your ~h~wanted level~w~, ~h~repair ~w~and ~h~respray ~w~your vehicle. Cost - ~h~$1000~w~. This time it's free. + +[JM4_A] +Yeah, I know Toni, I've tuned her real sweet. She purrs, you know what I mean? + +[JM4_5] +Drop by later and we'll give them something to launder, their own blood stained clothes! + +[AMMU_A] +Luigi said you'd need a piece... + +[AMMU_B] +Joey told me to tool you up... + +[AMMU_C] +So go around back of the shop. I left you a nine in the yard. + +[AMMU_D] +I got all your home defence needs. + +[AMMU_E] +You want a license too? + +[AMMU_F] +I don't need to see any I.D. you look trustworthy. + +[DETON] +DETONATION: + +[DRIVE_A] +Have an Uzi selected when entering a vehicle then look left or right and press the ~h~~k~~PED_FIREWEAPON~ button~w~ to fire. + +[DRIVE_B] +Have an Uzi selected when entering a vehicle then look left or right and press the ~h~~k~~PED_FIREWEAPON~ button~w~ to fire. + +[RECORD] +~g~NEW RECORD!! + +[NRECORD] +~r~NO NEW RECORD! + +[RCHELP] +Press ~k~~PED_FIREWEAPON~, or drive the RC car into a vehicle's wheels to detonate. + +[RCHELPA] +Press the ~k~~PED_FIREWEAPON~ button, or drive the RC car into a vehicle's wheels to detonate. + +[RC_1] +You have 2 minutes to blow up as many Diablo Gang Cars as possible! + +[RC_2] +You have 2 minutes to blow up as many Mafia Gang Cars as possible! + +[RC_3] +You have 2 minutes to blow up as many Yakuza Gang Cars as possible! + +[RC_4] +You have 2 minutes to blow up as many Yardie Gang Cars as possible! + +[RC_5] +You have 2 minutes to blow up as many Hood Gang Cars as possible! + +[RC_6] +You have 2 minutes to blow up as many Cartel Gang Cars as possible! + +[RAMPAGE] +RAMPAGE!! + +[RAMP_P] +RAMPAGE COMPLETE! + +[RAMP_F] +RAMPAGE FAILED + +[PAGE_00] +. + +[PAGE_01] +Murder ~1~ Diablos in 120 seconds! + +[PAGE_02] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_03] +Kill ~1~ Mafia in 120 seconds! + +[PAGE_04] +Kill ~1~ Triads in 120 seconds! + +[PAGE_05] +Kill ~1~ Triads in 120 seconds! + +[PAGE_06] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_07] +Pop ~1~ Yardie heads in 120 seconds! + +[PAGE_08] +Burn ~1~ Yakuza in 120 seconds! + +[PAGE_09] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_10] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_11] +Annihialate ~1~ Yardies in 120 seconds! + +[PAGE_12] +Torch ~1~ Yakuza in 120 seconds! + +[PAGE_13] +Explode ~1~ Yardies in 120 seconds! + +[PAGE_14] +Fry ~1~ Colombians in 120 seconds! + +[PAGE_15] +Splatter ~1~ Hoods in 120 seconds! + +[PAGE_16] +Destroy ~1~ vehicles in 120 seconds! + +[PAGE_17] +Splatter ~1~ Colombians with a car in 120 seconds! + +[PAGE_18] +Driveby and Destroy ~1~ vehicles in 120 seconds! + +[PAGE_19] +Remove ~1~ Colombian heads in 120 seconds! + +[PAGE_20] +Behead ~1~ Hoods in 120 seconds! + +[JM1_A] +Hey, I'm bored when you gonna drill me? + +[JM1_B] +In a moment sweet heart, I got a little business to take care of. + +[JM1_C] +I got a little job for you pal. + +[JM1_D] +The Forelli brothers have owed me money for too long + +[JM1_E] +and they need to be taught some respect. + +[JM1_F] +Lips Forelli is stuffing his fat face in St Marks Bistro, + +[JM1_G] +so steal his car and take it to 8-Ball's bomb shop up in Harwood. + +[JM1_H] +You know 8-Ball right? + +[JM1_I] +Once he's fitted it with a bomb, go park the car where you found it. + +[JM1_J] +Then sit back and watch the whole show. + +[JM1_K] +But hurry up, he won't be eating forever. + +[CAT2_A1] +Come on you dumb bitch! + +[CAT2_A] +The real question is, did you turn up to rescue Maria or to get me back? + +[CAT2_B] +Well I got news for you, + +[CAT2_B2] +shooting you will be a pleasure but dating you was only business. + +[CAT2_C] +You are muy peccinno amigo! + +[CAT2_D] +Throw over the cash. + +[CAT2_E] +You have been a busy boy! + +[CAT2_E2] +But you haven't learned, I'm not to be trusted. + +[CAT2_E3] +Kill the idiot. + +[CAT2_J] +Get this thing airborne!! + +[HM5_1] +Yo, Ice said was comin'. There rules. Bats only. No guns, no cars. + +[HM5_5] +This is a battle for respect, you cool? + +[HELP14] +To collect weapons walk through them. These cannot be collected while in a vehicle. + +[CRUSH] +Park in the marked area and exit your vehicle. The vehicle will then be crushed. + +[DIAB2_B] +A gang of no-goods has threatened to remove my starring member if I don't pay them a cut. + +[DIAB2_C] +They threaten the wrong man, amigo. + +[DIAB2_D] +They have a weakness for the icecream. + +[DIAB2_E] +Pick up the bomb I've hidden in Harwood, + +[DIAB2_F] +hijack the regular icecream van on its rounds. + +[DIAB2_G] +and lure these fools to their doom with the jeengle-jeengle. + +[DIAB2_H] +They hide in a warehouse on Atlantic Quay. + +[DIAB3_A] +Some insolent Triads stole my beautiful car last night, + +[DIAB3_B] +wrecked it and left it burning. + +[DIAB3_C] +Some of my most precious donkey memorabilia was in the trunk - + +[DIAB3_D] +real collectibles that are irreplaceable my friend. + +[DIAB3_E] +I've hidden a throbbing weapon on the edge of Chinatown. + +[DIAB3_F] +Take it and teach these Triad vandals to fear El Burro's well-endowed wrath. + +[DIAB3_G] +Ariba! + +[DIAB3_1] +KILL 25 TRIADS + +[DIAB4_A] +A thieving opportunist has stolen a van of my latest publication hot off the press! + +[DIAB4_B] +But that SPANKED-up idiot has left the rear doors open + +[DIAB4_C] +and now my beautifully produced, + +[DIAB4_D] +tastefully photographed adult literature is being dropped all over Liberty! + +[DIAB4_E] +Take the van and follow that trail of Donkey Does Dallas volumes 1, 2 and 3 + +[DIAB4_F] +collecting it as you go. + +[DIAB4_G] +When you've followed the trail to that thieving SPANK-head, waste him! + +[DIAB4_H] +Then deliver my donkey derby to XXX Mags in the Red Light District. + +[DIAB4_1] +~g~Take the van to the back of XXX Magazines. + +[HM1_E] +I want you to show these punk ass bitches how a real drive-by works. + +[HM1_H] +Take these nines off of here!! + +[HM2_A] +Those Nines are pressing me. + +[HM2_B] +These Bitches got armored cars and now they're running SPANK... + +[HM2_C] +and slinging it to brothers with no fear. + +[HM2_D] +There's a car parked up the way. + +[HM2_E] +There's some stuff in there to put these sissys on blass... + +[HM3_A] +Some effa has wired my wheels to blow. + +[HM3_B] +If I lose those wheels, my rep on the street will be dead. + +[HM3_C] +Pick up my car and take it over to the garage on St. Marks, a'right yo. + +[HM3_D] +Let them diffuse that, let them take care of that bomb. + +[HM3_E] +The clocks ticking and the wiring is messed up. + +[HM3_F] +One pot hole too many and that thing could blow. + +[HM3_G] +Now move it! + +[HM4_A] +Yo, a Federal Reserve flight just smashed down at Francis International. + +[HM4_B] +There's platinum all over the strip. + +[HM4_C] +Get a car and snatch up as much as you can. + +[HM4_F] +You can drop the bling off at one of my garages. + +[HM4_G] +This platinum is mad heavy and it will slow your wheels down some. + +[HM4_H] +So make regular drop off's at the garage. + +[HM5_A] +Them Nines are down to a few scabby herds... + +[HM5_B] +but they still wanna bring it. + +[HM5_C] +They agreed to go toe to toe. + +[HM5_D] +A gang of them against two of us, or rather... + +[HM5_E] +two of yaw + +[HM5_F] +I'd join you but... + +[HM5_G] +I ain't due my parole hearing for another three months now, + +[HM5_H] +y'know what I mean? + +[HM5_I] +Go and meet my baby brother, + +[HM5_J] +He'll show you where they are fighting a'right son. + +[MEA1_B] +The name's Chonks, Marty Chonks. + +[MEA1_C] +I run the Bitchin' Dog Food factory around the corner. + +[MEA1_D] +I got money troubles, but hey, who doesn't right? + +[MEA1_E] +I'm meeting my bank manager later. + +[MEA1_F] +He's a crooked bastard that keeps bumping up the loan repayments so he can cut a slice. + +[MEA1_G] +Take my car, pick him up and bring him back here. + +[MEA1_H] +I've got a little surprise for that blood sucking leech!! + +[MEA2_A] +I hired some thieves to break into my apartment... + +[MEA2_C] +The thieving bastards are threatening to tell the insurance company, + +[MEA2_D] +if I don't give them a cut. + +[MEA2_E] +Can you believe it? + +[MEA2_F] +I've left a car inside the factory gates. + +[MEA2_G] +Use it to go and pick them up from their turf in the Red Light district. + +[MEA2_H] +Then bring 'em back to the factory so I can make 'em see Marty's point of view. + +[MEA3_A] +The business is going to go under unless I get hold of some serious cash soon. + +[MEA3_B] +My wife has an insurance policy and all she's ever been to me is a hole in my pocket. + +[MEA3_C] +I've left a car in the usual place. + +[MEA3_D] +Go and pick up my wife from Classic Nails and bring her back to the factory. + +[MEA4_A] +Damn, I'm in trouble! + +[MEA4_B] +Turns out my wife was seeing some guy I owe money to. + +[MEA4_C] +He's got real angry and he's looking for payback! + +[MEA4_E] +he thinks I'm gonna pay him off... + +[MEA4_F] +but my guess is... + +[MEA4_G] +Liberty's dogs are gonna get yet another flavor this month! + +[WELCOME] +WELCOME TO + +[HM1_2] +~g~Get a vehicle, remember only Uzi drive by kills count! + +[HELP8_B] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ button ~w~to ~h~zoom in ~w~with the rifle and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ button ~w~to ~h~zoom out ~w~again. + +[LRQC_1] +Asuka and I are gonna have to talk, uh, + +[LRQC_2] +Why don't you go cruise around? + +[LRQC_3] +You'll need a place to lie low. + +[LRQC_4] +There's a warehouse at the edge of Belleville that should suit your needs. + +[LRQC_5] +Come back here to my Condo when you are ready, + +[LRQC_6] +and we can have a little chat. + +[JM6_5] +~g~You need a getaway vehicle, Idiot! + +[JM2_F] +If you need a piece go around back of AmmuNation opposite the subway. + +[LOVE4_7] +~g~There's a construction yard in Staunton Island, maybe they took the package there. + +[LOVE4_8] +~g~You'll need a car to open the garage. + +[TSCORE] +EARNINGS: $~1~ + +[AM1_9] +~r~Salvatore has escaped back into Luigi's Club! + +[AM1_6] +~g~If you hang around Luigi's club, the Mafia will spot you! + +[TM2_3] +~g~It's a trap! Waste them all!! + +[FM4_1] +This is Maria. The car's a trap! Meet me at the slip south of Callahan Bridge. + +[JM1_7] +~g~Close the car door! He'll notice! + +[KM5_1] +~g~DEALER MINCED!!. + +[KM5_6] +~g~You must murder at least 8 Yardie dealers. + +[KM5_7] +~g~Kill them quickly! Once they've pushed their SPANK they're off the streets. + +[RM3_8] +~r~That car is a decoy!! + +[LM3_8] +Hey, I'm Joey. + +[LM3_9] +Luigi said you were reliable so come back later, + +[KM3_5] +~g~Press the horn to get the deal going. + +[LOVE7] +LOVE'S DISAPPEARANCE + +[LOVE2_5] +~g~Kenji is fender meat! Get out of Newport and dump the car! + +[AS2_11] +~g~~1~ OF 9! + +[GARAGE1] +~g~Get out of the vehicle and walk outside. + +[KM3_11] +~g~The Cartel have been attacked and the briefcase has not been recovered. + +[KM3_12] +~g~Kill all of the Colombians, destory the vehicles and recover the briefcase. + +[KM3_13] +~g~Take the briefcase back to the casino. + +[RM5_6] +~g~He's bailed out!! Smash his bodycast with a vehicle or an explosion!! + +[PBOAT_1] +Press the ~h~~k~~PED_FIREWEAPON~ button~w~ to fire the boat cannons. + +[PBOAT_2] +Press the ~h~~k~~PED_FIREWEAPON~ button~w~ to fire the boat cannons. + +[DIAB1_B] +This is El Burro of the Diablos. + +[DIAB1_D] +You're new in Liberty, but already you are gaining a reputation on the streets. + +[DIAB1_E] +There's a street race starting by the old school hall near the Callahan Bridge. + +[DIAB1_F] +Get yourself some wheels and first through all the checkpoints wins the prize. + +[HM2_1] +Use the RC buggies to destroy the armored cars. Press the ~h~~k~~PED_FIREWEAPON~ button ~w~to detonate. + +[HM2_1A] +Use the RC buggies to destroy the armored cars. Press the ~h~~k~~PED_FIREWEAPON~ button ~w~to detonate. + +[HM2_2] +~r~You failed to destroy all the armored cars! + +[HM2_6] +~g~Armored Car destroyed! + +[RM3_A] +I know a real important man in town, a soft touch, + +[RM3_H] +with shall we say, exotic tastes and the money to indulge them. + +[RM3_B] +He's involved in a legal matter and the prosecution has some rather embarrassing photos of him + +[RM3_C] +at a morgue party or something. + +[LOVE6_A] +A lesson in business, my friend. + +[LOVE6_E] +If you have a unique commodity, the world and his wife will try to wrestle it from your grasp... + +[LOVE6_C] +SWAT teams have cordoned off the area around my associate and the package. + +[LOVE6_D] +Get over there, pick up the van and act as a decoy. + +[LOVE6_F] +Keep them busy and he should make good his escape. + +[AM3_C] +He's probably out in the bay as you read this! Steal a police boat, and sink his career! + +[FESZ_UC] +CANCEL + +[FEDS_SM] +L1,R1-CHANGE MENU + +[FEDS_AS] +;=-CHANGE SELECTION + +[FEDSAS2] +<>-CHANGE SELECTION + +[FEDS_SS] +L1,R1-CHANGE SELECTION + +[FEDSSC1] +;-FASTER SCROLLING + +[FEDSSC2] +=-STOP SCROLLING + +[MEA2_3] +~g~Bring the car back to the factory. + +[RM1_3] +~r~McAffrey escaped! + +[RM1_4] +~g~You have used all the grenades! Get some more from ammunation! + +[RM1_5] +~g~Go back and torch the safehouse! + +[RM6_4] +~g~Go over to the lockup and collect Ray's stash. + +[RM6_5] +~g~The CIA have the bridge under surveillance, find another route across. + +[HM2_F] +and wreck all their armored stuff. + +[HM_4] +'BULLION RUN' + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA4_B7] +but if you just step into my office... + +[MEA3_B4] +Marty wants to see me? Well it better be quick because I have to get my hair done. + +[KM3_7] +It's a Yakuza trap man! + +[FES_LOF] +Load Failed. + +[FES_SLO] +SAVE FILE + +[FES_ISC] +IS CORRUPTED + +[FESZ_TI] +SAVE Z1 + +[FESZ_SA] +Save game + +[MC_LDFL] +Load Failed! + +[MC_NWRE] +Now Restarting Game. + +[LOVE6_3] +~g~You have ~1~ seconds to return to the Securicar before you fail the mission. + +[LOVE6_4] +~r~You ditched the Decoy Securicar! + +[HELP1] +Stop in the center of the blue marker. + +[HELP12] +Walk into the center of the blue marker to trigger a mission. + +[HJSTAT] +Distance: ~1~.~1~m Height: ~1~.~1~m Flips: ~1~ Rotation: ~1~_ + +[HJSTATW] +Distance: ~1~.~1~m Height: ~1~.~1~m Flips: ~1~ Rotation: ~1~_ And what a great landing! + +[DIAB1_5] +RACE TIME: + +[LOVE3_4] +~r~You destroyed the plane!! + +[F_FAIL1] +Fire Truck mission ended. + +[F_CANC] +~r~Fire Fighter mission cancelled! + +[F_EXTIN] +FIRES: + +[A_COMP1] +Paramedic missions complete! + +[A_CANC] +~r~Paramedic mission cancelled! + +[A_COMP3] +Paramedic missions complete! You will never get tired when running! + +[ATUTOR] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Paramedic missions on or off. + +[ATUTOR3] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Paramedic missions on or off. + +[ALEVEL] +Paramedic Mission Level ~1~ + +[A_FAIL1] +Paramedic mission ended. + +[FEST_HA] +Highest Paramedic Mission level + +[A_SAVES] +PEOPLE SAVED: ~1~ + +[C_KILLS] +CRIMINALS KILLED: ~1~ + +[HM1_B] +I got a problem they tryin' to play me. + +[AM2_A] +Salvatore's death comes as pleasurable news, + +[AM2_A2] +you are an efficient killer. I like that in a man. + +[AM2_B] +This is my brother Kenji. + +[AM2_C] +Asuka has a little job for you, but when you're done, drop by my casino and we can talk. + +[AM2_D] +Just like Kenji, always trying to play with my toys. + +[AM2_E] +My police source indicates that the Mafia are watching our interests around the city + +[AM2_E2] +in a bid to track you down. + +[AM2_F] +We cannot continue our operations until they are dealt with. + +[AM2_G] +Take out these spying fools and end this vendetta once and for all. + +[F_START] +~g~Burning vehicle reported in the ~a~ area. Go and extinguish the fire. + +[AM4_1A] +Get to the Phone in West Belleville Park. + +[AM4_1B] +Get to the Phone on Liberty Campus. + +[AM4_1C] +Get to the Phone in South Belleville Park. + +[AM4_1D] +Meet me in the toilet block in the park. + +[HJSTATF] +Distance: ~1~ft Height: ~1~ft Flips: ~1~ Rotation: ~1~_ + +[HJSTAWF] +Distance: ~1~ft Height: ~1~ft Flips: ~1~ Rotation: ~1~_ And what a great landing! + +[HM1_F] +Watch your back though, there'll be Jacks on the street who'll think you're trying to blast them too! + +[HM1_D] +'Nines' is their tag and purple is their flag and each day they rock their colors... + +[HM1_G] +is another day the 'Jacks' look soft. + +[MEA2_B] +and steal some stuff so I could claim on the insurance as you do. + +[TM3_H] +~w~You did good back there kid, real good. + +[TM3_I] +~w~Come on, let's introduce you to the Don. + +[TM3_J] +~w~Heeyyy! Luigi! + +[TM3_K] +~w~Oh my girls have been missing you so long Salvatore, you been away too long. + +[TM3_L] +~w~You tell them that once this unfortunate business is taken care of, + +[TM3_M] +~w~we'll all go down to the club and celebrate, ok? + +[TM3_N] +~w~Here's my boy. + +[TM3_N2] +~w~How you doin' pop? + +[TM3_O] +~w~You got yourself a good woman yet? + +[TM3_P] +~w~Hey, your mother, god bless her soul, would be turning over in her grave + +[TM3_Q] +~w~to see you without a wife. + +[TM3_R] +~w~I know Pop, I'm working on it. + +[TM3_S] +~w~TONI! How's your Momma? + +[TM3_T] +~w~She's a great woman you know. Strong. Firenze. + +[TM3_U] +~w~She's good...fine. + +[TM3_V] +~w~Terrific, Terrific. Now listen you guys, you go inside while I talk to our new friend here. + +[TM3_W] +~w~I see nothing but good things for you my boy... + +[RM1_A] +That scumbag McAffrey, he took more bribes than anyone. + +[RM1_B] +He thinks he's gonna get an honorable discharge if he turns states evidence. + +[RM1_C] +He just squealed! + +[RM4_B] +We gotta shut him up, permanently. + +[RM4_E] +I want him sleeping with the fishes, not eating them. + +[LOVE3_B] +On its approach to the airport tonight, a light aircraft will pass over the bay. + +[LOVE4_D] +Unfortunately the port authorities seized the plane and were stripping it down + +[LOVE4_H] +until I intervened at great personal expense. + +[LOVE4_E] +Cross the bridge to Shoreside Vale and go to Francis International Airport. + +[GTAB_A] +Hey, let's get this out of here. God knows what it is + +[GTAB_B] +but he seems to want it badly enough so it must be worth something. + +[GTAB_C] +Who the Heck! + +[GTAB_D] +YOU! + +[GTAB_E] +Hey take it easy amigo! De nada! De nada! + +[GTAB_F] +I left you pouring your heart out into the gutter! + +[GTAB_G] +Don't shoot amigo. No problem. We all friends. Here, take this. + +[GTAB_H] +Don't be such a pussy! + +[GTAB_I] +We got no choice baby! + +[GTAB_J] +We always got a choice you dumb bastard! + +[GTAB_K] +I'm sorry about that crazy bitch man, they all the same...por favor?? + +[GTAB_L] +So the whore got away. + +[GTAB_M] +But you've done me a favor, + +[GTAB_N] +you're not the only one that has a score to settle with the Cartel, + +[GTAB_O] +this worm killed my brother! + +[GTAB_P] +I never killed no Yakuza! + +[GTAB_Q] +LIAR! We all saw the Cartel assassin. + +[GTAB_R] +We are going to hunt down and kill all you Colombian dogs! + +[GTAB_S] +I'll be operating on our friend here to extract information and a little pleasure. + +[GTAB_T] +You, drop by later, I'm sure I'll require your services. + +[GTAB_U] +Please amigo, don't leave me with her, she psycho chica! Amigo? Hey AMEEEGO!!!...Aiiieeeeaaargghh! + +[LOVE5_A] +You are proving to be a safe investment, a rare thing in these days of false hood. + +[KM3_1] +~g~The Cartel are expecting a Yardie Posse go and steal a Yardie car! Head north you'll find one in Newport. + +[LOVE1_1] +~g~Go 'jack a Colombian gang car, so you can infiltrate the hideout, head north you'll find one in Fort Staunton. + +[FM1_Q1] +~w~You looking for some fun? A little...hmm? Some SPANK? + +[FM1_R] +~w~Hi Chico. Nah, just the usual. + +[FM1_T] +~w~Thanks Chico. See you around. + +[FM1_W] +~w~Alright Fido, you wait here and look after the car while I go and shake my butt alright. + +[FM1_X] +~w~OK Fido, let's get out of here. Wooooh! + +[FM1_Q] +~w~Hey Maria! It's my favorite lady! + +[FM1_S1] +~w~Hey, maybe you should check out the warehouse party at the east end of Atlantic Quays. + +[FM1_U] +~w~Gracias and enjoy. That's good stuff. + +[FM1_V] +~w~C'mon Fido, let's go and check out this party! + +[FM1_SS] +~r~SCANNER: ~g~Four-five to all units: Assist narcotics raid Atlantic Quays... + +[LOVE6_B] +even if they have little understanding as to its true value. + +[TM3_A1] +~r~Joey's Fried! + +[TM3_A2] +~r~Joey and Luigi have been cremated! + +[TM3_A3] +~r~Joey, Luigi and Toni are Toast! + +[FM4_2] +Listen, Salvatore thinks that we're going behind his back, + +[FM4_3] +so he was offering you to the Cartel in order to make a deal. + +[FM4_4] +I couldn't let him do that, I mean the worst thing is, + +[FM4_4B] +it's all my fault... because I told him, we were an item. + +[FM4_5] +Don't ask me why. I don't know. + +[FM4_6] +Look you're a marked man on Mafia turf and I've got to get out of here too. + +[FM4_6B] +I've seen too much killing. Too much blood! + +[FM4_7] +This is a friend of mine ok, she's an old friend.. it's Asuka, she's someone we can trust. + +[FM4_8] +C'mon, Enough of the speeches. + +[FM4_9] +We better get out of here before we get more hysterical Italians wanting less friendly reunions. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +CHARACTERS + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERS + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR DMA DESIGN + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED254] +STUDIO MANAGER + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CINCAM] +Cinematic Camera + +[KM1_13] +Drive the vehicle into the garage! + +[KM3_14] +~r~You have been spotted the deal is off! + +[EBAL_H] +Wait here man while I go in and talk to Luigi. + +[EBAL_M] +Remember no one messes with my girls! + +[LM2_F] +Then take his car, respray it. + +[LM2_D] +here, here take it. + +[LM1_9] +Hi I'm Misty. + +[LM4_A] +Some Diablo scumbag has been pimping his skuzzy bitches in my backyard. + +[FM2_B] +We got us a rat! + +[FM2_C] +He ain't pimpin' or pushin' so he must be talking. + +[FM3_CC] +~w~Come back brother when you have the money. + +[FEDS_AM] +<>-CHANGE MENU + +[LOVE5_5] +~r~You failed to protect the truck! + +[RM6_6] +~r~Ray is dead! + +[RM6_7] +~r~Ray has missed his flight. + +[RM6_8] +~g~You have left Ray behind, go back and get him. + +[FM1_10] +~g~You have left Maria behind, go back and pick her up. + +[LOVE4_9] +~r~The plane has been destroyed! + +[LOV4_10] +~r~The only lead to where the package has gone has been destroyed! + +[KM2_D] +Needless to say, we must give him the cars as a gift, to repay the debt that I owe him. + +[KM4_B] +The business's fortunate enough to have our protection settle their accounts today. + +[KM2_E] +You must obtain the cars on this list and deliver them to a garage behind the car park in Newport. + +[FM3_8I] +~w~Get a good vantage point then I'll head in when you fire the first shot. + +[LOVE1_B] +Experience has taught me that a man like you can be very loyal for the right price, + +[LOVE1_H] +but groups of men get greedy. + +[LOVE1_C] +A valued resource, an old oriental gentleman I know, + +[LOVE1_I] +has been kept hostage by some South Americans in Aspatria. + +[MEA4_D] +I've agreed to see him... + +[MEA4_B4] +Marty sent you huh? OK, I'm gonna show that creep the meaning of the word business. + +[MEA4_B5] +Carl, hi! i eerr, I need more time to get your money. + +[MEA1_B4] +Ah, Mr Chonks sent you did he. Let's go and pay the fellow a visit. + +[HM5_6] +Let's go crack some skulls... + +[LOVE1_5] +~g~Stop hanging around, get a Colombian Gang car and rescue Love's associate. + +[AS1_D] +~w~Act as the bait, and get the death squads to follow you to Pike Creek + +[AS1_E] +~w~where some of my men will be waiting for them. + +[AS2_C] +~w~The Cartel have a front company, The Kappa Coffee House. + +[AS2_E] +~w~We have no choice but to put these drug stands out of operation. + +[AS2_F] +~w~Smash them to splinters!! + +[AS2_A1] +~w~Miguel certainly has some of that famous Latin stamina. + +[AS2_A2] +~w~I'm quite exhausted. + +[SIREN_3] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[SIREN_4] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[AS3_C] +~w~Eeeeeeyoooo! What IS that gooey yellow stuff? + +[AS3_C1] +~w~Oh hi Babe. + +[AS3_F] +~w~She's got the makings of a natural this girl. + +[AS3_F1] +~w~She's managed to extract this little gem from our guest. + +[AS3_G] +~w~There is a plane coming into Francis International in 2 hours time. + +[AS3_G1] +~w~It is full of Catalina's poison. + +[AS3_H] +~w~You can avoid airport security by getting a boat out to the runway-light buoys + +[AS3_H1] +and shooting the plane down on its approach. + +[AS3_I] +~w~Collect the cargo from the debris and stash it! + +[AS3_J] +~w~Oh you be careful now, OK baby? + +[AS3_K] +~w~Now try the chilli oil..... + +[RM2_F1] +Those Colombians'll be here any minute! + +[RM2_K] +Goddam they're here!! LOCK'N'LOAD!! + +[LOVE2_7] +~g~Now dump the car! + +[LOVE2_8] +~g~Now get out of Newport! + +[AM1_F] +Salvatore Leone will be leaving Luigi's in about three hours time. (~1~:~1~) + +[LOVE5_C] +I want you to follow him, and make sure both he and my package get to Pike Creek unharmed. + +[FESZ_SR] +Save Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again. + +[FESZ_FO] +Would you like to format the memory card (PS2) in MEMORY CARD slot 1? + +[FELZ_FO] +Memory card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FES_NOC] +No memory card (PS2) in MEMORY CARD slot 1. + +[FES_LOE] +Load Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again. + +[FES_DEE] +Deleting Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again. + +[SLONFM] +Error formatting memory card (PS2) in MEMORY CARD slot 1. + +[SLONDR] +Insufficient space to save. Please insert a memory card (PS2) with at least 500KB of free space available into MEMORY CARD slot 1. + +[SLNSP] +Insufficient space to save. Please insert a memory card (PS2) with at least 200KB of free space available into MEMORY CARD slot 1. + +[FEFD_WR] +Formatting memory card (PS2) in MEMORY CARD slot 1. Please do not remove the memory card (PS2), reset or switch off the console. + +[FES_ISF] +NOT PRESENT + +[FES_SAG] +PRESENT + +[SLONNO] +No memory card (PS2) in MEMORY CARD slot 1. + +[SLONNF] +Memory card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FESZ_FM] +Memory card (PS2) in MEMORY CARD slot 1 is unformatted. Would you like to format memory card (PS2) in MEMORY CARD slot 1? + +[FESZ_FF] +Format Failed! Check memory card (PS2) in MEMORY CARD slot 1 and please try again. + +[MCDNSP] +There is insufficient space on the memory card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + +[MCGNSP] +There is insufficient space on the memory card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + +[FESZ_WR] +Saving data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FESZ_OW] +Overwriting data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FELD_WR] +Loading data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FEDL_WR] +Deleting data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[LM2_C] +Luigi said to, to give you this so... + +[LM3_G] +Joey ain't the kind you keep waiting, remember, this is your foot in the door... + +[LM5_E] +Get as many of them as you can before the cops drink away their green. + +[JM5_C] +Alright, there's a car stuffed with a stiff at the cafe near Callahan Point. + +[RM2_B] +We saw action in Nicaragua, back when the country knew what it was doing. + +[RM2_C] +Some Cartel scum roughed him up yesterday, said they'd be back for some of his stock today. + +[RM2_D1] +I'd go myself but the old sciatica's playing up again -cough cough- so, eerr-hhrrmmm, good luck. + +[CATINF1] +~g~Get Catalina! + +[CATINF2] +~g~Follow the chopper to find Catalina. + +[BOATIN1] +Jump into a boat and press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~to get in. + +[BOATIN2] +You can press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~if you are near a boat to get in it. + +[BOATIN3] +Jump into a boat and press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~to get in. + +[BOATIN4] +You can press the ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~if you are near a boat to get in it. + +[JM6] +'THE GETAWAY' + +[FM1] +'CHAPERONE' + +[JM1] +'MIKE LIPS LAST LUNCH' + +[FM21] +'BOMB DA BASE: ACT I' + +[FM3] +'BOMB DA BASE: ACT II' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'UNDER SURVEILLANCE' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'S.A.M.' + +[RM2] +'ARMS SHORTAGE' + +[LOVE6] +'DECOY' + +[LOVE1] +'LIBERATOR' + +[RC1] +'DIABLO DESTRUCTION' + +[RC2] +'MAFIA MASSACRE' + +[RC3] +'CASINO CALAMITY' + +[RC4] +'RUMPO RAMPAGE' + +[RM2_E1] +I can't believe those yellow-bellied bastards left me without proper cover again! + +[GREN_1] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[GREN_2] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[GREN_3] +The longer you hold the ~h~~k~~PED_FIREWEAPON~ button~w~, the further you will throw the grenade. + +[LOVE4_G] +My property will be waiting for you at the customs hanger in the aircraft's fuselage. + +[KABOOM] +KABOOOM! + +[SPLAT] +SPLAT! + +[PANCAK] +PANCAKED! + +[SOAKED] +SOAKED! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +When the subway opens you will be able to catch a train to Staunton Island. + +[TUBE2] +When Shoreside Vale opens you will be able to Exit Shoreside Terminal to Francis International Airport. + +[TUBE_2] +To board a subway train, press the ~h~'enter vehicle' button~w~. + +[LEGAL] +~g~Eliminate the criminal threat! + +[GA_2] +New engine and paint job. The cops won't recognize you! + +[LM1_8A] +To earn some extra cash, why not 'borrow' a taxi... + +[TAXIH1] +Stop near a highlighted pedestrian to pick them up then drive them to their destination before the time runs out. + +[LM5_7] +~g~Less than four girls working the ~p~Fuzz Ball~g~ and Luigi won't be happy! + +[KM2_3] +~g~Remember the ~r~cars~g~ have to be in mint condition to be accepted by the ~p~garage~g~. + +[KM5_2] +~g~A Yardie is off the streets. + +[BETRA_A] +Sorry, babe. + +[BETRA_B] +I'm an ambitious girl and you, + +[BETRA_C] +you're just small time. + +[JAILB_Q] +Come on! + +[JAILB_R] +Senor dickhead! + +[JAILB_S] +It's no problem to kill you. + +[JAILB_T] +You gonna be sorry. + +[JAILB_U] +A'right, a'right. Get lost. + +[HELP15] +When on foot press the ~h~~k~~PED_LOOKBEHIND~ button~w~ to ~h~look behind~w~. + +[FEC_LB3] +Look behind + +[FEC_R3] +(R3 button) + +[FES_AFO] +This memory card (PS2) is already formatted. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CHANGE SELECTION + +[FEDSAS4] +;=<> - CHANGE SELECTION + +[SPRAY_4] +Use the ~h~~k~~PED_FIREWEAPON~ button ~w~to fire the water cannon. + +[SPRAY_1] +Use the ~h~~k~~PED_FIREWEAPON~ button ~w~to fire the water cannon. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore will be leaving Luigi's at about 0~1~:~1~ + +[FEDS_SE] +/ button - SELECT + +[FEDS_SB] +/ button - SELECT " button - BACK + +[TM4_A] +~w~Oh it's you. TONI ain't here. + +[TM4_A2] +~w~But he left one of his sugary love letters for you. + +[DIAB2_A] +I started my exotic entertainment business with nothing but the sizeable contents of my leather pants! + +[LM5_9] +GIRLS: + +[PERPIC] +Hidden Packages found + +[CO_ONE] +Hidden Package ~1~ of ~1~ + +[LOVE3_3] +~g~The plane has dropped ~1~ of 6 packages. + +[FARE11] +~g~Destination ~w~'Construction site' ~g~in Fort staunton. + +[GA_21] +You cannot store any more cars in this garage. + +[CHEAT1] +Cheat activated + +[CHEAT2] +Weapon cheat + +[CHEAT3] +Health cheat + +[CHEAT4] +Armor cheat + +[CHEAT5] +Wanted level cheat + +[CHEAT6] +Money cheat + +[CHEAT7] +Weather cheat + +[AS1_H] +~r~You failed to lead the Deathsquad into the Yakuza trap!! + +[FEDS_BA] +" button - BACK + +[FED_WIS] +Wide Screen: + +[RAMP_A] +ALL RAMPAGES COMPLETED! + +[USJ_ALL] +ALL UNIQUE STUNTS COMPLETED! + +[FARE23] +~g~Destination ~w~'import export garage' ~g~in Cochrane Dam district + +[L_TRN_1] +You can ride the L-train around Portland. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[L_TRN_2] +You can ride the L-train around Portland. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[S_TRN_1] +You can take the subway trains across Liberty. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[S_TRN_2] +You can take the subway trains across Liberty. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ button~w~ to ~h~enter ~w~or ~h~exit~w~ a train. + +[AS1_C] +~w~She has three death squads dotted around Liberty, whose sole job is to hunt you down. + +[AS1_G] +~r~All the Yakuza are dead!! + +[JAN] +Jan + +[FEB] +Feb + +[MAR] +Mar + +[APR] +Apr + +[MAY] +May + +[JUN] +Jun + +[JUL] +Jul + +[AUG] +Aug + +[SEP] +Sept + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Dec + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +BUGGIES LEFT: + +[BONUS] +~g~BONUS $~1~ + +[HORN1] +Press the ~h~L3 button ~w~to activate the ~h~horn. + +[HORN2] +Press the ~h~L1 button ~w~to activate the ~h~horn + +[HORN3] +Press the ~h~R1 button ~w~to activate the ~h~horn + +[LM3_1A] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[LM3_1B] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[LM3_1C] +Press the~h~ ~k~~VEHICLE_HORN~ button~w~ to activate the ~h~horn~w~ and let Misty know you are here. + +[RADIO_A] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_B] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_C] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[RADIO_D] +Press the ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ button~w~ to cycle through the ~h~radio stations. + +[FEC_EXV] +Enter and exit vehicle + +[TAXI_M] +'TAXI DRIVER' + +[COP_M] +'VIGILANTE' + +[FIRE_M] +'FIREFIGHTER' + +[AMBUL_M] +'PARAMEDIC' + +[HJ_IS] +INSANE STUNT BONUS: $~1~ + +[HJ_PIS] +PERFECT INSANE STUNT BONUS: $~1~ + +[HJ_DIS] +DOUBLE INSANE STUNT BONUS: $~1~ + +[HJ_PDIS] +PERFECT DOUBLE INSANE STUNT BONUS: $~1~ + +[HJ_TIS] +TRIPLE INSANE STUNT BONUS: $~1~ + +[HJ_PTIS] +PERFECT TRIPLE INSANE STUNT BONUS: $~1~ + +[HJ_QIS] +QUADRUPLE INSANE STUNT BONUS: $~1~ + +[HJ_PQIS] +PERFECT QUADRUPLE INSANE STUNT BONUS: $~1~ + +[AM1_K] +Salvatore Leone will be leaving Luigi's in about three hours time. (0~1~:~1~) + +[IMPEXPP] +Import/Export garage, Portland Harbor. We have orders for various vehicles. Check our notice board for our requirements. + +[VANHSTP] +Any more Securicars you want cracked? Bring them to our garage in the Portland Harbor. + +[EMVHPUP] +Great rates paid for new and used Emergency Vehicles. Bring them to the crane in the north east of Portland Harbor. + +[STANDS] +STALLS WRECKED: + +[STASH] +~g~Stash the SPANK back at the ~p~construction site! + +[MCSTNS] +There is no memory card (PS2) in MEMORY CARD slot 1. Do you wish to start? (YES or NO) + +[LOVE3_5] +~g~The plane is now in range. + +[LOVE3_6] +~r~The Police got to the packages first! + +[SIREN_1] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[SIREN_2] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ button~w~. + +[FM3_8C] +~w~I'll need $100,000 to cover expenses, + +[MCLOAD] +Loading Data. Please do not remove the memory card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FES_GME] +Error Reading memory card (PS2) in MEMORY CARD slot 1 please check and try again. + +[FESZ_QF] +Are you sure you wish to format the memory card (PS2) in MEMORY CARD slot 1? + +[FESZ_LS] +Load Successful. + +[RM3_5] +~g~You have ~1~ of 6 evidence packages. + +[LOVE3_2] +~g~You have all the packages! Take them back to Donald Love. + +[LOVE4_4] +~g~Take the package back to Donald Love! + +[FEB_SAV] +Load + +[FEP_SAV] +LOAD GAME + +[AS2_12A] +~g~After you trash the first stall, you will have 8 minutes before the Cartel warn their pushers! + +[AS3_1A] +~g~Now get to the ~b~marker buoy! + +[NOCONT] +Please reconnect analog controller (DUALSHOCK#) or analog controller (DUALSHOCK#2) to controller port 1 to continue + +[BET_JB] +BETRAYED BY HIS LOVER CATALINA AND LEFT FOR DEAD. CONVICTED AND SENTENCED, HE BEGINS HIS JOURNEY TO LIBERTY CITY PENITENTIARY. BUT ONLY ONE THOUGHT BURNS IN HIS CRIMINAL MIND......REVENGE! + +[END_A] +Residents in Cedar Grove have been coming to terms + +[END_B] +with the emotional aftermath of a full blown war + +[END_C] +that hit the area yesterday. + +[END_D] +Local resident, Clive Denver described to police + +[END_E] +a single gunman that he saw fleeing the scene, with a dark haired woman. + +[END_F] +Oh, you know, we're gonna have such fun, 'cos you know, you know, + +[END_G] +I love you, I, I, I, I really do, 'cos you're such a big strong man + +[END_H] +and that's just what I need. + +[END_I] +Anyway, what was I saying? + +[END_J] +Oh, you know, I forget. But you know what it's like, don't you? + +[END_K] +The sound of explosions shook nearby homes as people ran for cover. + +[END_L] +Several citizens were injured in the panic as ground fire was exchanged + +[END_M] +between ground forces and a helicopter circling the dam. + +[END_N] +Yeah, we got a good view from down here in the gardens. + +[END_O] +When the 'copter finally got taken out, + +[END_P] +better than the fireworks on the 4th of July. + +[END_Q] +With the death toll already over twenty, + +[END_R] +police are still finding bodies. + +[END_S] +There have been no official denials concerning rumours + +[END_T] +that the dead were members of the Colombian Cartel, + +[END_U] +and still no leads as to the cause of the massacre. + +[END_V] +I broke a nail and my hair is ruined, I mean can you believe it? + +[END_W] +This one cost me fifty dollars... + +[PAPER1] +* + +[PAPER2] +* + +[PAPER3] +* + +[JAILB_V] +* + +[JAILB_A] +* + +[JAILB_B] +* + +[JAILB_C] +* + +[JAILB_E] +* + +[JAILB_F] +* + +[JAILB_G] +* + +[JAILB_I] +* + +[JAILB_W] +* + +[JAILB_K] +* + +[JAILB_L] +* + +[JAILB_X] +* + +[JAILB_M] +* + +[JAILB_N] +* + +[JAILB_O] +* + +[JAILB_P] +* + +[JAILB_D] +* + +[JAILB_H] +* + +[JAILB_J] +* + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/french.txt b/utils/gxt/french.txt new file mode 100644 index 0000000..bdb12c7 --- /dev/null +++ b/utils/gxt/french.txt @@ -0,0 +1,8378 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Hé! Retourne dans ta caisse! + +[IN_VEH2] +~g~T'as besoin d'une caisse pour cette mission! + +[IN_BOAT] +~g~T'as besoin d'un bateau pour cette mission! + +[HEY] +~g~Te la joue pas perso, pense à tes potes! + +[HEY2] +~g~Restez groupés! + +[HEY3] +~g~T'as perdu ton meilleur homme, retournes-y et ramène-le! + +[HEY4] +~g~Perds Misty, et Luigi te fera sauter la tête ! Alors, retourne la chercher! + +[HEY5] +~g~L'une des filles manque à l'appel! Retourne la chercher! + +[HEY6] +~g~Ton honneur est lié à celui du Yakuza Kanbu. Tu dois le protéger! + +[HEY7] +~g~Un flingue de plus ferait pas de mal! Retourne en arrière et embarque ton contact! + +[HEY8] +~g~Dans protection, y'a protection, compris ? Alors protège le vieux bridé! + +[HEY9] +~g~Tu veux savoir ce qui se passe dans la rue ? Ben va voir ton contact! + +[HELP2_A] +Appuie sur la ~h~touche /~w~ quand tu cours pour piquer un ~h~sprint. + +[HELP3] +Tu ne peux sprinter que pendant une courte durée, avant d'être claqué! + +[HELP4_A] +Appuie sur la ~h~touche ~k~~VEHICLE_ACCELERATE~~w~ pour ~h~accélérer. + +[HELP4_D] +Pousse le ~h~stick analogique de droit~w~ vers le haut pour accélérer. + +[HELP5_A] +Appuie sur la~h~ touche ~k~~VEHICLE_BRAKE~~w~ pour ~h~freiner~w~ ou pour ~h~passer la marche arrière~w~ si ta caisse est à l'arrêt. + +[HELP5_D] +Pousse le ~h~stick analogique de droit~w~ vers le bas pour ~h~freiner~w~ ou pour ~h~passer la marche arrière~w~ si ta caisse est à l'arrêt. + +[HELP6_A] +Appuie sur la ~h~touche ~k~~VEHICLE_HANDBRAKE~~w~ pour utiliser le ~h~frein à main du véhicule. + +[HELP6_C] +Appuie sur la ~h~touche ~k~~VEHICLE_HANDBRAKE~~w~ pour utiliser le ~h~frein à main du véhicule. + +[HELP6_D] +Appuie sur la ~h~touche ~k~~VEHICLE_HANDBRAKE~~w~ pour utiliser le ~h~frein à main du caisse. + +[HELP7_A] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser~w~ avec le fusil à lunette. + +[HELP7_D] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser~w~ avec le fusil à lunette. + +[HELP8_A] +Appuie sur la ~h~touche ~k~~PED_SNIPER_ZOOM_IN~~w~ pour faire un ~h~zoom avant~w~ avec le fusil et sur la ~h~touche ~k~~PED_SNIPER_ZOOM_OUT~~w~ pour faire un ~h~zoom arrière~w~. + +[HELP9_A] +Appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~tirer~w~ au fusil à lunette. + +[HELP10] +Ce badge t'indique que la police te recherche. + +[HELP11] +Plus il y a de badges, plus il y a de flics à tes trousses. + +[HELP13] +Tu as parfois intérêt à utiliser des chemins qui n'apparaissent pas sur le radar. + +[TIMER] +C'est une mission en temps limité, alors il faut la finir avant que le compte à rebours arrive à zéro! + +[MISTY1] +~r~Misty bouffe les pissenlits par la racine! + +[OUT_VEH] +~g~Sors du véhicule! + +[GARAGE] +Conduis ta caisse dans le garage et repars à pied. + +[WANTED1] +~g~Largue les flics pour en avoir moins à tes basques! + +[NODOORS] +~g~Hé, c'est pas des sardines! Trouve une caisse avec assez de sièges! + +[TRASH] +~g~T'as vachement bousillé ta bagnole ! Fais-la réparer ! + +[WRECKED] +~r~Le caisse est fortue! + +[HORN] +~g~Klaxonne! + +[HORN4] +Appuie sur la ~h~touche L3~w~ pour ~h~klaxonner. + +[NOMONEY] +~g~T'as besoin de thune! + +[OUTTIME] +~r~T'es lent, mec, t'es trop lent! + +[SPOTTED] +~r~Ils en ont après ta peau! + +[REWARD] +~1~$ de récompense + +[GAMEOVR] +FIN DE PARTIE + +[Z] +Valeur axe Z : ~1~ + +[M_FAIL] +ECHEC DE LA MISSION! + +[M_PASS] +MISSION REUSSIE! ~1~$ + +[O_PASS] +PETIT BOULOT REUSSI! + +[O_FAIL] +PETIT BOULOT RATE! + +[DEAD] +T'ES MORT! + +[BUSTED] +TU T'ES FAIT COFFRER! + +[S_PROMP] +Si tu n'es pas en mission, tu peux ~h~sauvegarder le jeu ici~w~, ça fera avancer la montre de six heures. + +[NUMBER] +~1~ + +[SCORE] +~1~$ + +[LOADCAR] +CHARGEMENT DU VEHICULE... + +[CARSOFF] +Trafic désactivé + +[CARS_ON] +Trafic activé + +[TEXTXYZ] +Ecriture des coordonnées sur le fichier... + +[CHEATON] +Mode Triche activé + +[CHEATOF] +Fonction tricher OFF + +[UZI_IN] +L'Uzi est disponible maintenant à Ammu-Nation! + +[IMPORT1] +Va dehors et attends ton véhicule. + +[PAGEB1] +Pistolet livré à la planque. + +[PAGEB2] +Uzi livré à la planque. + +[PAGEB3] +Armure livrée à la planque. + +[PAGEB4] +Fusil à pompe livré à la planque. + +[PAGEB5] +Grenades livrées à la planque. + +[PAGEB6] +Cokctails molotov livrés à la planque. + +[PAGEB7] +AK47 livré à la planque. + +[PAGEB8] +Fusil à lunette livré à la planque. + +[PAGEB9] +M16 livré à la planque. + +[PAGEB10] +Lance-roquettes livré à la planque. + +[PAGEB11] +Lance-flammes livré à la planque. + +[WANT_A] +Tu ne seras arrêté que si tu possèdes un ~h~indice de recherche. + +[WANT_B] +Ton ~h~indice de recherche~w~ est symbolisé par les étoiles dans le coin supérieur droit de l'écran. + +[WANT_C] +Ton ~h~indice de recherche~w~ est de 1... + +[WANT_D] +2... + +[WANT_E] +3... + +[WANT_F] +Plus ton ~h~indice de recherche~w~ augmente, plus les forces de l'ordre t'en veulent. + +[WANT_G] +Quand tu te fais ~h~choper~w~, tu es amené au poste de police le plus proche. + +[WANT_H] +Les flics se laisseront corrompre contre tes armes et une partie de ton oseille. + +[WANT_I] +Toute mission en cours sera automatiquement un échec. + +[WANT_J] +Plus tu joueras, plus tu trouveras de moyens de diminuer ton indice de recherche. + +[WANT_K] +En voiture, les ~h~ateliers de peinture~w~ te permettront d'~h~enrayer ton indice de recherche. + +[HEAL_B] +Quand tu es ~h~H.S.~w~, t'es amené à l'hosto le plus proche. + +[HEAL_C] +On te confisque alors toute ton artillerie et les toubibs te pompent ton flouze pour recoller les bouts. + +[HEAL_E] +En jouant, tu trouveras d'autres moyens de te soigner ou de te protéger. + +[DAM] +DEGATS : + +[KILLS] +VICTIMES : + +[FARES] +FRAIS : + +[BULL] +FRIC : + +[EVID] +PREUVES : + +[HEALTH] +ETAT DU VEHICULE : + +[COLLECT] +RECUPERE : + +[BOMB] +Conduis ton véhicule chez l'artificier et piège-le avec une ~h~bombe~w~. Coût - ~h~1.000 dollars. + +[SAVE1] +Franchis la porte pour ~h~sauvegarder la partie~w~. Tu ne peux pas sauvegarder en cours de mission. + +[SAVE2] +Tout véhicule laissé dans ce garage sera enregistré lors de la sauvegarde. + +[AMMU] +Va chez Ma-Gnum pour acheter une arme. + +[BRIDGE1] +Quand le pont Callahan sera réparé, tu pourras te rendre à l'île Staunton . + +[TUNNEL] +Quand le tunnel Porter aura réouvert, tu pourras te rendre à l'île Staunton . + +[LUIGI] +MISSIONS DE LUIGI + +[TONI] +MISSIONS DE TONI + +[JOEY] +MISSIONS DE JOEY + +[FRANK] +MISSIONS DE SALVATORE + +[DIABLO] +MISSIONS DE DIABLO + +[ASUKA] +MISSIONS D'ASUKA + +[B_SITE] +MISSIONS DE BANLIEUE D'ASUKA + +[KENJI] +MISSIONS DE KENJI + +[RAY] +MISSIONS DE RAY + +[LOVE] +MISSIONS DE LOVE + +[YARDIE] +MISSIONS DE YARDIE + +[HOOD] +MISSIONS DE HOOD + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Point Callahan + +[PORT_S] +Atlantic Quays + +[PORT_E] +Port de Portland + +[PORT_I] +Trenton + +[S_VIEW] +Vue de Portland + +[CHINA] +Chinatown + +[EASTBAY] +Plage de Portland + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Le Quartier Rouge + +[TOWERS] +Hauteurs de Hepburn + +[HARWOOD] +Harwood + +[ROADBR1] +Pont Callahan + +[ROADBR2] +Pont Callahan + +[TUNNELP] +Tunnel Porter + +[BOMB1] +Garage de 8-Ball + +[COM_ZON] +Ile de Staunton + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Campus Liberty + +[CONSTRU] +Fort Staunton + +[PARK] +Parc Belleville + +[COM_EAS] +Newport + +[SHOPING] +Point Bedford + +[YAKUSA] +Torrington + +[SUB_ZON] +Vallée Shoreside + +[AIRPORT] +Aéroport intl. Francis + +[PROJECT] +Jardins Wichita + +[SUB_IND] +Crique de Pike + +[SWANKS] +Bosquet Cedar + +[BIG_DAM] +Ecluse Cochrane + +[SUB_ZO2] +Vallée Shoreside + +[SUB_ZO3] +Vallée Shoreside + +[CAR_1] +Ambulance + +[CAR_2] +Camion de pompier + +[CAR_3] +Voiture de police + +[CAR_4] +Enforcer + +[CAR_5] +Barraquements + +[CAR_6] +Rhino + +[CAR_7] +Voiture du FBI + +[CAR_8] +Sécuricar + +[CAR_9] +Moonbeam + +[CAR_10] +Autobus + +[CAR_11] +Camion + +[CAR_12] +Linerunner + +[CAR_13] +Camion-poubelle + +[CAR_14] +Patriot + +[CAR_15] +M. Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinelle + +[CAR_25] +Guépard + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Tout-terrain + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Etalon + +[CAR_37] +Taxi + +[CAR_38] +Tacot + +[CAR_39] +Buggy + +[LUIGIS] +Chez Luigi + +[GOAWAY] +~g~T'es déjà en mission, imbécile! + +[LUIGGO] +~g~Luigi fait passer un entretien à de nouvelles filles. Reviens plus tard! + +[JOEYGO] +~g~Joey est en ville avec Misty. Repasse plus tard! + +[TONIGO] +~g~Toni a emmené sa mère à l'opéra. Rappelle plus tard! + +[KEMUGO] +~g~Maria et Kenuri sont occupés. Reviens un peu plus tard! + +[KENJGO] +~g~Kenji est à un congrès de Yakuzas. Repasse plus tard. + +[RAYGO] +Ray a autre chose à faire que de voir ta tronche. Va faire un tour! + +[LOVEGO] +~g~Donald Love a d'autres chats à fouetter. Prends rendez-vous la prochaine fois! + +[KENSGO] +~g~Kenji est occupé. Repasse à un autre moment. + +[ASUSGO] +~g~Asuka n'est pas dispo pour l'instant. + +[HOODGO] +~g~Les Hoods ne sont pas là pour le moment. + +[WRONGT1] +~g~Repasse entre 05:00 et 21:00 pour du boulot. + +[WRONGT2] +~g~Repasse entre 06:00 et 14:00 pour du taf. + +[WRONGT3] +~g~Ramène ta fraise entre 15:00 et 00:00 pour bosser. + +[GUN_1A] +Sers-toi de la ~h~touche ~k~~PED_CYCLE_WEAPON_RIGHT~~w~ et de la ~h~touche ~k~~PED_CYCLE_WEAPON_LEFT~~w~ pour faire défiler tes armes. + +[GUN_2A] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser automatiquement~w~ tes ennemis et appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour tirer. Entraîne-toi sur les cibles... + +[GUN_2C] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser automatiquement~w~ tes ennemis et appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour tirer. Entraîne-toi sur les cibles... + +[GUN_2D] +Maintiens la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée pour ~h~viser automatiquement~w~ tes ennemis et appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour tirer. Entraîne-toi sur les cibles... + +[GUN_3A] +Tout en appuyant sur la ~h~touche ~k~~PED_LOCK_TARGET~~w~, appuie sur la ~h~touche ~k~~PED_CYCLE_TARGET_LEFT~~w~ ou la ~h~touche ~k~~PED_CYCLE_TARGET_RIGHT~~w~ pour changer de cible. + +[GUN_3B] +Tout en appuyant sur la ~h~touche ~k~~PED_LOCK_TARGET~~w~, appuie sur la ~h~touche ~k~~PED_CYCLE_TARGET_LEFT~~w~ ou la ~h~touche ~k~~PED_CYCLE_TARGET_RIGHT~~w~ pour changer de cible. + +[GUN_4A] +Tu peux marcher ou courir tout en gardant la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée afin de verrouiller une cible. + +[GUN_4B] +Tu peux marcher ou courir tout en gardant la ~h~touche ~k~~PED_LOCK_TARGET~~w~ enfoncée afin de verrouiller une cible. + +[GUN_5] +Tu peux t'entraîner à cibler et tirer sur ces cibles en papier. Quand tu as finis, reprends ta mission. + +[TAXI1] +~g~Cherche une course. + +[FARE1] +~g~Destination : ~w~Le 'Club Sex Meeouch'~g~ dans Le Quartier Rouge. + +[FARE2] +~g~Destination : ~w~'Pribas'~g~ au Belvédère de Portland. + +[FARE3] +~g~Destination : ~w~la 'Vieille Ecole'~g~ à Chinatown. + +[FARE4] +~g~Destination : ~w~le 'Cafe greasy Joes' ~g~au Point Callahan. + +[FARE5] +~g~Destination : ~w~'AmmuNation'~g~ dans Le Quartier Rouge. + +[FARE6] +~g~Destination : ~w~'Caisses à crédit'~g~ à Saint Mark's. + +[FARE7] +~g~Destination : ~w~le 'Woody's topless bar' ~g~ dans le Quartier Rouge. + +[FARE8] +~g~Destination : ~w~le 'Bistro de Marco'~g~ à Saint Mark's. + +[FARE9] +~g~Destination : ~w~le 'Garage import export' ~g~au Port de Portland. + +[FARE10] +~g~Destination : ~w~'Têtes de Punk' ~g~ à Chinatown. + +[FARE12] +~g~Destination : ~w~le 'Stade de Football'~g~ à Aspatria. + +[FARE13] +~g~Destination : ~w~'L'église'~g~ au Point Bedford. + +[FARE14] +~g~Destination : ~w~'Le Casino'~g~ à Torrington. + +[FARE15] +~g~Destination : ~w~'Université de Liberty'~g~ au Campus Liberty. + +[FARE16] +~g~Destination : ~w~le 'Centre commercial~g~ dans le coin du Parc Belleville. + +[FARE17] +~g~Destination : ~w~le 'Musée'~g~ de Newport + +[FARE18] +~g~Destination : ~w~le 'Bâtiment Am'~g~ de Torrington. + +[FARE19] +~g~Destination : ~w~'Burgers bourgeois'~g~ à Point Bedford. + +[FARE20] +~g~Destination : ~w~'Le parc'~g~ à Belleville. + +[FARE21] +~g~Destination : ~w~'Aéroport internationnal Francis'~g~. + +[FARE22] +~g~Destination : ~w~'l'écluse de Cochrane'~g~. + +[FARE24] +~g~Destination : ~w~'L'hôpital'~g~ de la Crique Pike. + +[FARE25] +~g~Destination : ~w~le 'Parc'~g~ à la vallée Shoreside. + +[FARE26] +~g~Destination : ~w~les 'Tours North West'~g~ des jardins Wichita. + +[NEW_TAX] +PLUS GROS! PLUS RAPIDES! PLUS SOLIDES! Les taxis Borgnine s'installent à Harwood. Contactez-les dès aujourd'hui au 555-Borgnine! + +[TSCORE2] +~1~$ + +[IN_ROW] +~1~ DE SUITE! Bonus : ~1~$ + +[TTUTOR] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver l'affichage des missions taxi. + +[TTUTOR2] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver l'affichage des missions taxi. + +[ATUTOR2] +~g~Conduis les patients à l'hôpital. DOUCEMENT. Chaque secousse réduit leurs chances de survie. + +[A_TIME] ++~1~ secondes. + +[A_FULL] +~r~Ambulance pleine! + +[A_RANGE] +~g~La radio de l'ambulance ne capte plus rien. Rapproche-toi d'un hôpital! + +[FTUTOR] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions camion de pompier. + +[FTUTOR2] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions camion de pompier. + +[F_PASS1] +Feu éteint! + +[F_RANGE] +~g~La radio du camion de pompier ne capte plus rien. Rapproche-toi d'une caserne de pompiers! + +[C_BREIF] +~g~Suspect aperçu pour la dernière fois dans le secteur : ~a~. + +[C_RANGE] +~g~La radio de la voiture de police ne capte plus rien. Rapproche-toi d'un poste de police! + +[DODO_FT] +Tu as 'volé' pendant ~1~ secondes! + +[EBAL_A] +Je connais un coin dans Le Quartier Rouge où on pourra se planquer, + +[EBAL_A1] +faut que tu prennes le volant, mes mains tremblent trop. + +[EBAL_1] +Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~sortir~w~ d'un véhicule. + +[EBAL_1B] +Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~sortir~w~ d'un véhicule. + +[EBAL_2] +~g~Remonte dans la bagnole! + +[EBAL_3] +C'est le ~h~radar~w~. Utilise-le pour te repérer dans la ville, et suis le ~h~symbole~w~ sur le ~h~radar~w~ pour trouver la planque! + +[EBAL_D] +Je connais un mec qu'a des relations. Il s'appelle Luigi. + +[EBAL_D1] +On est pote, alors il pourra certainement te trouver du boulot. Viens, on y va. + +[EBAL_E] +Allez, viens, on va y faire un tour, histoire de te présenter. + +[EBAL_I] +Le patron viendra bientôt vous voir... + +[EBAL_J] +8-Ball a des trucs à faire en haut. + +[EBAL_K] +Tu peux peut-être me rendre un service. + +[EBAL_L] +Une de mes filles a besoin d'un taxi. Tire une bagnole et va chercher Misty à la clinique. Ensuite, ramène-la ici. + +[EBAL_N] +Et garde bien tes mains sur le volant, compris ? + +[EBAL_4] +~r~8-Ball est mort! + +[EBAL_5] +~g~Prenez une caisse! + +[EBAL_6] +~g~Va chercher Misty! + +[LM1] +'LES FILLES DE LUIGI' + +[LM2] +'PAS DE SPANK POUR LA PEPEE' + +[LM3] +'LA MYSTERIEUSE MISTY' + +[LM5] +'LE BAL A BALLES' + +[LM1_2] +~g~Emmène Misty au club de Luigi. + +[LM1_3] +~g~Klaxonne pour faire monter la fille. + +[LM1_6] +~g~Rentre dans la caisse! + +[LM1_7] +Arrête la bagnole près de Misty et laisse-la monter à bord. + +[LM1_8] +Tu peux retourner voir Luigi pour le boulot, ou visiter Liberty City. + +[LM2_A] +Y'a une nouvelle saloperie sur le marché. Ca s'appelle la SPANK. + +[LM2_E] +Un petit malin fourgue cette merde à mes filles du Port de Portland. + +[LM2_B] +Va lui foutre une raclée! + +[LM2_G] +Je me vengerai! + +[LM2_1] +~g~Pique sa caisse et fais-la repeindre. + +[LM2_2A] +Sers-toi de la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~donner un coup de poing~w~, ~h~un coup de pied~w~ ou un ~h~coup de batte~w~. + +[LM2_2C] +Sers-toi de la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~donner un coup de poing~w~, ~h~un coup de pied~w~ ou un ~h~coup de batte~w~. + +[LM2_2D] +Sers-toi de la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~donner un coup de poing~w~, ~h~un coup de pied~w~ ou un ~h~coup de batte~w~. + +[LM2_3] +~g~Planque la bagnole dans le garage de Luigi! + +[LM2_4] +~g~Repeins la bagnole! + +[LM3_A] +Hé, faut que je te cause... OK, Mick, je te parle plus tard. + +[LM3_B] +Comment ça va, gamin ? + +[LM3_C] +Le fils du Don, Joey Leone, il veut voir sa régulière, Misty. + +[LM3_D] +Va la chercher à Hauteurs de Hepburn... + +[LM3_E] +Mais fais gaffe, c'est le territoire de Diablo. + +[LM3_F] +Amène-la ensuite au garage de joey, à Trenton et vite! + +[LM3_H] +Donc tes yeux, ils regardent la route et pas Misty, ok ? + +[LM3_1D] +Appuie sur la ~h~touche L3~w~ pour ~h~klaxonner~w~ et indiquer à Misty que tu es là. + +[LM3_2] +~g~Emmène Misty chez Joey! + +[LM3_4] +~g~Va chercher Misty! + +[LM3_5] +Tu bosses pour Luigi, hein ? Il était temps qu'il se trouve un chauffeur digne de confiance. + +[LM3_7] +Je suis à toi dans une minute, poupée. + +[LM3_10] +~g~Trouve une caisse! + +[LM4_B] +Va t'occuper de cette affaire pour moi. + +[LM4_C] +Si t'as besoin d'un calibre, va derrière Ammu-Nation en face du métro. + +[LM5_A] +Le Bal de la police a lieu dans la vieille école près du Pont de Callahan. + +[LM5_B] +Alors les flics auront besoin d'action à l'ancienne! + +[LM5_C] +J'ai des filles partout dans les rues. + +[LM5_D] +Emmène-les au bal, histoire de faire d'une pierre deux coups : profit et détente! + +[LM5_1] +~g~Si tu les serres trop, elles vont avoir des bleus! ~g~Livre d'abord celles-la et reviens en chercher d'autres. + +[LM5_2] +~r~L'une des filles de Luigi est bonne pour la morgue! + +[LM5_3] +~g~T'as besoin d'un véhicule! + +[LM5_4] +~g~Va ramasser les filles qui tapinent à St. Mark's. + +[LM5_5] +~g~Emmène les filles au bal! + +[LM5_8] +~g~Filles au bal : ~1~ + +[JM2] +'AU REVOIR LEE' + +[JM4] +'LE CHAUFFEUR DE MONSIEUR' + +[JM5] +'TRANSPORT DE MARCHANDISES AVARIEES' + +[JM1_1] +~g~Amène la voiture de Forelli au garage de 8-Ball, au nord, derrière 'Caisses à crédit'. + +[JM1_2] +~g~Ramène la bagnole au Bistro de Marco. + +[JM1_3] +~g~Arme la bombe et CASSE-TOI EN VITESSE! + +[JM1_4] +~g~T'as bousillé la caisse! Fais-la réparer! + +[JM1_5] +~g~T'as pas armé la bombe! + +[JM1_6] +~g~Gare la bagnole correctement! + +[JM1_8A] +~y~Hé, c'est mon gars préféré! + +[JM1_8B] +~y~Chez l'artifier, tout est automatique. T'amènes la voiture, tu la gares, et le reste se fait tout seul! + +[JM1_8C] +~y~Viens, le premier est gratuit, mais juste le premier, compris ? + +[JM2_A] +Chunky Lee Chong fout la merde avec la Spank pour un nouveau gang de Colombie... ou du Colorado... un coin comme ça... + +[JM2_B] +J'en sais rien. On s'en fout, après tout. + +[JM2_D] +Il a dépassé les bornes! + +[JM2_E] +Il faut lui régler son compte! + +[JM2_G] +Débrouille-toi avec un 9 mm, tu sais où il se trouve, non ? + +[JM2_H] +Et souviens-toi : fais gaffe à Chinatown, c'est le territoire de la Triade. + +[JM3_A] +Bon, on va s'attaquer à un fourgon blindé. + +[JM3_B] +Il part de Chinatown tous les jours. + +[JM3_C] +Les balles traverseront pas son blindage, alors vole une caisse et rentre-lui dedans. + +[JM3_D] +Cogne bien fort et les larbins de sécurité devraient pas demander leur reste! + +[JM3_E] +Ensuite, t'emmènes le fourgon à l'entrepôt des docks et mes gars prennent le relais. + +[JM3_F] +Mais bon, ils vont pas faire leur ronde toute la journée, alors traîne pas en route! + +[JM3_1] +~g~Emmène le fourgon à la planque. + +[JM3_2] +~g~Fonce dans le fourgon jusqu'à ce que ses dégâts soient inférieurs à 70%. + +[JM4_B] +Hé! C'est le gars dont je te parlais! + +[JM4_C] +Ok. Ce gars, c'est pas un Italien, c'est pas un mécano, mais il peut réparer les choses. + +[JM4_D] +C'est Pops Capo, Toni Cipriani. + +[JM4_E] +Ouais, je suis Toni Cipriani. + +[JM4_F] +Emmène-le au resto de la Mamma, à St Mark's, ok ? + +[JM4_G] +Maintenant, écoute. Je prépare un truc et j'ai besoin d'un bon conducteur. Alors, passe me voir, ok ? + +[JM4_2] +Attends-moi ici. Et laisse tourner le moulin. C'est pas une visite de courtoisie. + +[JM4_3] +Une embuscade de la Triade! Sors-nous d'ici, gamin! + +[JM4_4] +La Triade pense qu'elle peut s'attaquer à moi! Hum! Tu te rends compte, A MOI! + +[JM4_6] +Hé, fais gaffe à la bagnole! J'ai dit pas de lézards! + +[JM4_7] +~g~Emmène Toni au restaurant de la Mamma. + +[JM4_8] +~r~Toni sert d'engrais aux chrisantèmes! + +[JM5_A] +Magnifique! Réellement magnifique! + +[JM5_B] +Très bien, c'est l'homme qu'il me fallait! + +[JM5_D] +Un des Forelli s'est cru un peu trop malin et il en a pris pour son grade! + +[JM5_E] +Emmène le corps au broyeur de Harwood, ok ? + +[JM5_1] +~g~Emmène-le au broyeur! + +[JM5_2] +~g~C'est les frères Forelli! + +[JM6_A] +Joli morceau, hein? + +[JM6_B] +Ok, écoute : choisis une caisse dans l'entrepôt de Saint Mark's et va chercher quelques potes à moi. + +[JM6_C] +Il font un retrait à la banque et ils ont besoin d'un taxi. + +[JM6_D] +J'ai dit que tu ferais parfaitement l'affaire, alors me déçois pas! + +[JM6_E] +Amène-les à la banque avant 5 heures, et sois pas en retard! + +[JM6_2] +Laisse le moulin tourner, y'en a pas pour longtemps! + +[JM6_3] +Sors-nous de là! + +[JM6_4] +Débarrasse-toi des poulets et amène-nous à l'entrepôt! + +[JM6_6] +~g~Va voler une caisse moins voyante! + +[JM6_7] +~g~Faut que tu prennes les 3 pour voler la banque! + +[TM1] +'LINGE SALE' + +[TM2] +'LA LIVRAISON' + +[TM3] +'LA RENCONTRE' + +[TM4] +'LE TRIANGLE DES TRIADES' + +[TM5] +'LA HUITIEME PLAIE' + +[TONI_P] +J'ai un boulot urgent pour toi! + +[TM1_A] +~w~Assieds-toi, gamin. Prends une de ces putains de chaises. + +[TM1_B] +~w~Alors, la laverie veut pas payer pour sa protection, hein ? + +[TM1_C] +~w~La Triade pense qu'elle peut se mêler de mes affaires ? + +[TM1_D] +~w~On va apprendre à ces faux durs ce que c'est que des vrais hommes! + +[TM1_E] +~w~Ouais, on va leur apprendre à nous respecter! Aucun de mes gars ne se laisse intimider par une Triade minable! + +[TM1_F] +~w~Ton père, qu'il repose en paix, se laissait pas faire par les Triades, à l'époque, en Sicile! + +[TM1_G] +~w~Pardon Ma. Oui Ma. + +[TM1_H] +~w~Je veux que tu détruises les camionnettes de la laverie. + +[TM1_I] +~w~Et roule sur tous les gars de la Triade que tu croiseras. + +[TM1_J] +~w~8-Ball te donnera ce dont tu as besoin. + +[TM2_A] +~w~Toni est parti en faire saigner plus d'un, ou du moins, il essaie. + +[TM2_AA] +Il ne sera jamais aussi fort que son papa. Il t'a laissé un mot sur la table. + +[TM2_B] +~w~La laverie a accepté de payer. C'est du bon boulot! + +[TM2_C] +~w~Va chercher la thune et ramène-la ici. Et fais gaffe à la Triade. + +[TM2_D] +~w~C'est comme les roquets : ça aboie, mais ça mord pas! + +[TM2_E] +~w~Personne, je dis bien PERSONNE, ne se mêle des affaires de TONY CIPRIANI! + +[TM2_1] +~g~Apporte le flouze à Toni! + +[TM2_2] +~g~Tu les as tous refroidis! + +[TM3_MA] +~w~Je ne sais pas où il est! + +[TM3_MB] +~w~Ce gamin, des fois, il sait même pas où il se trouve! + +[TM3_MC] +~w~Son père, c'est sûr, c'était différent. Toujours au top, sûr de lui, viril... + +[TM3_A] +~w~Don Salvatore a convoqué une assemblée. + +[TM3_B] +~w~J'ai besoin de toi pour aller chercher la Stretch et Joey, son fils, au garage. + +[TM3_C] +~w~Alors va chercher Luigi à son club et reviens ensuite me chercher. + +[TM3_D] +~w~On ira tous ensemble chez le don. + +[TM3_E] +~w~Ces Triades, elles savent jamais quand s'arrêter. + +[TM3_F] +~w~Elles veulent la guerre, elles auront la guerre. + +[TM3_G] +~w~Maintenant, faut y aller. + +[TM3_1] +~g~Prends la Stretch chez Joey. + +[TM3_2] +~g~Va chercher Luigi. + +[TM3_3] +~g~Va chercher Toni. + +[TM3_4] +~g~Conduis tout le monde chez Salvatore. + +[TM3_5] +~y~Une embuscade de la Triade! + +[TM4_B] +~w~Nous sommes en GUERRE! La Triade se sert d'une conserverie de poisson comme façade! + +[TM4_C] +~w~Ils règlent la majeure partie de leurs affaires au marché aux poissons de Chinatown. + +[TM4_D] +~w~Cette laverie doit toujours payer son assurance... + +[TM4_E] +~w~Ils pensent que la Triade les protège, donc il faut leur montrer que ce n'est pas le cas. + +[TM4_F] +~w~Prends les garçons avec toi pour éliminer les chefs de la Triade! + +[TM4_G] +~w~Et puis, si t'en as l'occasion, descends quelques-uns de leurs porte-flingues. + +[TM4_GAT] +~g~Tu as besoin d'un 'Camion de poisson de la Triade' pour entrer. + +[TM5_A] +TEXT NO LONGER REQUIRED + +[TM5_B] +~w~Ok, j'en ai marre de toutes ces conneries. + +[TM5_C] +~w~On va en finir une bonne fois pour toute avec la Triade de Liberty!. + +[TM5_D] +8-Ball a installé une bombe sur une benne à ordures. + +[TM5_E] +~w~Il y a un minuteur donc, si tu te chies dessus, y'aura pas de preuves. Va chercher la benne. + +[TM5_F] +~w~Fais gaffe, 8-Ball dit que c'est super-sensible et que la moindre secousse peut tout faire sauter. + +[TM5_G] +~w~La conserverie de poisson laissera entrer la benne, après ce sera à toi de jouer. + +[TM5_H] +~w~Gare-toi entre les bonbonnes de gaz et casse-toi en vitesse! + +[TM5_I] +~w~Je veux qu'il pleuve des maquereaux! + +[TM5_J] +~w~C'est du Cecil B. DeMille que je veux, pas de la série Z! + +[FM2] +'LA FILLE DU MOGHOL' + +[FM4] +'DERNIERES VOLONTES' + +[FM1_A] +~w~Moi et les gars, on a besoin de causer affaires. + +[FM1_B] +~w~Alors tu vas veiller sur ma fille pendant la soirée. + +[FM1_C] +~w~MARIA! RAMENE TES FESSES PAR ICI! + +[FM1_D] +~w~Cette petite conne fait toujours ça. + +[FM1_E] +~w~Et la voilà, la seule et unique reine de Saba! + +[FM1_F] +~w~Qu'est-ce que tu faisais là-bas ? + +[FM1_G] +~w~Enfin, peu importe, je parie que ça m'a coûté de l'argent! + +[FM1_H] +~w~Bon, tu crois quand même pas que je suis dans le coin pour te faire la conversation ? + +[FM1_I] +~w~Monte dans cette bagnole et ferme ta grande gueule! + +[FM1_J] +~w~Prends la Stretch, mais tu la ramènes en un seul morceau, compris? + +[FM1_K] +~w~Et surveille-la, elle peut foutre la merde! + +[FM1_L] +~w~Ouais, ouais, ouais, je suis sûr que le neurone de ton nouveau larbin a tout enregistré! + +[FM1_M] +~w~Et pis il est taillé comme une armoire normande, alors y'a pas de risque! + +[FM1_N] +~w~Allez, Fido. On va chez Chico s'éclater un peu! + +[FM1_P] +~g~C'est Chico, là! Arrête-moi à côté! + +[FM1_S] +~w~Bonsoir, jolie dame. + +[FM1_TT] +~w~22, V'LA LES FLICS! + +[FM1_1] +~g~Retourne dans la Stretch! + +[FM1_2] +~g~Monte dans la Stretch! + +[FM1_3] +~r~Si tu laisses Maria, Salvatore va l'avoir mauvaise, alors retourne la chercher! + +[FM1_4] +~g~Tu viens de larguer la femme du Don! Retourne à l'entrepôt et attends Maria! + +[FM1_5] +~g~Ramène Maria chez Salvatore! + +[FM1_6] +~g~Chico ne restera pas là tout le temps, alors emmène Maria voir la mer. + +[FM1_7] +~r~Maria est morte! Salvatore va être furax... + +[FM1_8] +~r~T'as trucidé le fournisseur de Maria! + +[FM2_J] +Laisse-nous seuls un moment. + +[FM2_A] +Le Cartel colombien fabrique de la SPANK quelque part à Liberty. + +[FM2_K] +Mais on sait pas où, et on dirait qu'ils sont au courant de nos moindres faits et gestes. + +[FM2_L] +Y'a un gars, Bob le frisé, qui bosse au bar chaz Luigi. + +[FM2_M] +Il dépense plus qu'il ne gagne... + +[FM2_N] +Il prend souvent un taxi pour rentrer chez lui après le boulot. Alors, suis-le. + +[FM2_O] +Et si c'est lui la balance, descends-le... + +[FM2_F] +Voilà notre copain. Monsieur grande gueule en personne. + +[FM2_G] +T'as été suivi ? Tu sais que ce qui se passe ici, c'est notre petit secret... + +[FM2_H] +Non, non, j'ai pas été suivi. T'as la came ? + +[FM2_I] +Voilà ta SPANK, alors crache le morceau. + +[FM2_P] +Ok, alors ce qui se passe, c'est que les Leone mènent la guerre sur deux fronts à la fois. + +[FM2_Q] +Y'a une guerre de territoire avec la Triade qu'est pas prête de finir + +[FM2_R] +et Joey Leone a foutu la merde avec les Forelli. + +[FM2_S] +Ils perdent du terrain et des hommes tous les jours. + +[FM2_T] +Salvatore devient de plus en plus irascible et parano chaque jour. Il soupçonne tout le monde. + +[FM2_U] +Ben, quand je te vois, je me dis qu'il a pas forcément tort... + +[FM2_1] +~g~Voilà Bob le frisé! + +[FM2_2] +~g~Bob a quitté le club, suis-le! + +[FM2_5] +~g~Amène-le au port de Portland. + +[FM2_6] +~r~Bob montera jamais dans un taxi bousillé! + +[FM2_7] +~r~Bob le frisé s'est dégonflé. Le rendez-vous est annulé. + +[FM2_8] +~g~Cogne Bob le frisé! + +[FM2_9] +~r~Bob le frisé est mort! + +[FM2_10] +~r~Bob le frisé s'est fait la malle! + +[FM2_11] +~g~Gare-toi devant le club de Luigi, Bob le frisé devrait pas tarder à sortir. + +[FM2_12] +~r~Il t'a faussé compagnie! + +[FM3_A] +~w~On devrait régler leur compte à ces bâtards de Colombiens. + +[FM3_B] +~w~Mais tant qu'on est en guerre avec les Triades, on n'est pas assez fort. + +[FM3_C] +~w~Les fonds du Cartel sont illimités, avec tout l'argent qu'ils se font sur la SPANK. + +[FM3_D] +~w~Si on les attaquait de front, ils nous lamineraient! + +[FM3_E] +~w~Ils doivent fabriquer la SPANK sur le bateau vers lequel Le frisé t'a conduit. + +[FM3_F] +~w~Va falloir utiliser nos cerveaux, ou plutôt un. Le tien! + +[FM3_G] +~w~Je te demande de détruire cette usine de SPANK comme une faveur, pour moi, Salvatore Leone. + +[FM3_H] +~w~Si tu y arrives, ton avenir est assuré, tu auras tout ce que tu veux! + +[FM3_I] +~w~Va voir 8-Ball, il te dira comment faire, c'est un expert en explosifs. + +[FM3_8A] +~w~Salut mon gars! Salvatore m'a téléphoné. + +[FM3_8B] +~w~Pour un boulot comme ça, il va te falloir un sacré paquet de feux d'artifices! + +[FM3_8D] +~w~Mais tu sais que tu en as toujours pour ton pognon avec moi! + +[FM3_8E] +~w~Ok, au boulot! + +[FM3_8F] +~w~Je peux régler le détonateur de ce bébé, mais je peux toujours pas utiliser un flingue avec ces mains. + +[FM3_8G] +~w~Tiens, cette pétoire devrait te servir à faire sauter quelques têtes. + +[FM3_4] +~g~Arrête la bagnole et laisse 8-Ball sortir. + +[FM3_7] +~r~8-Ball s'est fait refroidir! + +[FM3_8] +~r~Les gardes ont été prévenus! + +[FM4_A] +~w~Ah, voilà mon nettoyeur favori! + +[FM4_B] +~w~Je suis fier de toi, mon garçon, tu leur en as mis plein la gueule! + +[FM4_C] +~w~J'ai juste un autre petit travail à te confier avant de pouvoir sabrer le champagne. + +[FM4_D] +~w~Il y a une voiture devant le club de Luigi. + +[FM4_E] +~w~L'intérieur est tapissé de cervelle! + +[FM4_F] +~w~On a aidé un gars à se faire une idée, et y'a eu quelques salissures. + +[FM4_H] +~w~Emmène-le au broyeur avant que les flics ne le trouvent. + +[AM3] +'PURGE DE PAPARAZZI' + +[AM4] +'JOUR DE PAYE' + +[AM5] +'TANNER L'AGENT DOUBLE' + +[AM1_A] +Nous devons éclaircir certains points avant de poursuivre nos relations, + +[AM1_B] +affaires ou autres. Mettons cartes sur table. + +[AM1_C] +Moi, je suis Yakuza et je sais que tu travailles pour la famille de Salvatore Leone. + +[AM1_D] +Nous pouvons te donner du travail, + +[AM1_E] +mais il faut d'abord nous prouver que t'as plus aucun lien avec la mafia. + +[AM1_G] +Débrouille-toi pour qu'il n'arrive pas vivant à son club. + +[AM1_H] +Pendant ce temps-là, Maria et moi, nous nous remémorerons le bon vieux temps. + +[AM1_I] +Oh... Asuka, tu t'es trouvé un messager. + +[AM1_J] +Ce n'est pas un simple messager. + +[AM1_1] +~g~Salvatore part de chez Luigi! + +[AM1_2] +~r~Tu t'es fait repérer! + +[AM1_3] +~r~Tu as raté Salvatore! + +[AM1_4] +~r~Bravo, t'as effrayé la cible! Et t'es censé être un tireur d'élite, c'est ça ? + +[AM1_5] +~g~Va au Le Quartier Rouge et attends que Salvatore sorte du club. + +[AM1_7] +~r~Salvatore est chez lui, à siroter un whisky. T'es vraiment pas une flèche, toi! + +[AM1_8] +~g~Salvatore va sortir de chez Luigi à environ ~1~:~1~. + +[AM2_4] +~g~Ils t'ont vu venir aussi nettement que si t'étais un éléphant rose! + +[AM3_A] +Y'a un journaliste qui nous tourne autour. + +[AM3_B] +Maria et moi, on part en vacances, histoire de te laisser le temps de te débarrasser de ce voyeur pervers. + +[AM4_A] +Ah, mon meilleur homme! + +[AM4_B] +Maria est très occupée, mais je lui dirai que t'es passé. + +[AM4_C] +Qui est là ? Asuka ? Je sais que je n'ai pas été très sage, mais j'ai le droit d'aller toute seule au petit coin, ok ? + +[AM4_D] +Il est temps que tu rencontres notre homme au LPD. + +[AM4_E] +Voilà sa paye pour le dernier service qu'il nous a rendu. + +[AM4_F] +Il est terriblement prudent. + +[AM4_G] +Va à la cabine de téléphone de Torrington aussi vite que tu peux et attends ses instructions. + +[AM5_A] +Maria et moi, on est allé faire des courses. + +[AM5_B] +Notre informateur dans la police nous a dit qu'un de nos conducteurs est un flic sous couverture! + +[AM5_C] +Il ne vaut pas grand chose hors de sa voiture, alors on y a installé un capteur. + +[AM5_D] +Je veux qu'il souffre! + +[AM5_1] +Tanner est à tes trousses! + +[AS1] +'L'APPAT' + +[AS2] +'CAFE RAPIDO' + +[AS4] +'LA RANCON' + +[AS1_A] +~w~Miguel semble croire que je l'ai maltraité. + +[AS1_B] +Il nous a néanmoins révélé à quel point Catalina s'inquiète de ta soif de vengeance. + +[AS2_A] +~w~On a sous-estimé les ambitions de Catalina avec la SPANK. + +[AS2_B] +~w~Cela va plus loin qu'une vente à la sauvette dans les rues. + +[AS2_D] +~w~Il fourgue sa came sur des étals. + +[AS2_1] +~g~Tous les étals des vendeurs de café de Portland ont été renversés! + +[AS2_2] +~g~Tous les étals des vendeurs de café de l'île de Staunton ont été renversés! + +[AS2_3] +~g~Tous les étals des vendeurs de café la vallée Shoreside ont été renversés! + +[AS2_4] +~r~Le Cartel a mis en garde ses revendeurs! + +[AS2_5] +~g~Il y a toujours des stands de café à la vallée Shoreside et sur l'île Staunton! + +[AS2_6] +~g~Il y a toujours des stands de café à la vallée Shoreside! + +[AS2_7] +~g~Il y a toujours des stands de café sur l'île Staunton! + +[AS2_8] +~g~Il y a encore des étals de vendeurs de café à Portland! + +[AS2_9] +~g~Il y a toujours des stands de café dans Portland et la vallée Shoreside! + +[AS2_10] +~g~Il y a toujours des stands de café dans Portland et sur l'île Staunton! + +[AS2_12] +~g~Parcours les districts de Liberty pour trouver ~b~les étals de Café Rapido! + +[AS3_A] +~W~Est-ce qu'on le serre un peu plus maintenant ou on attend juste qu'il vire au noir et qu'il tombe tout seul? + +[AS3_B] +~w~Allez, encore un peu... + +[AS3_D] +~w~Mon homme à tout faire! + +[AS3_E] +~w~Je m'ennuyais alors je suis venue tenir compagnie à Asuka. + +[AS3_1] +~g~Trouve un ~r~bateau~g~ et rejoins la ~b~bouée repère! + +[AS3_3] +~g~Attends que l'~y~avion~g~ s'approche! + +[AS3_5] +~g~Récupère la marchandise! + +[AS3_4] +~g~Sers-toi d'un lance-roquettes pour descendre l'~y~avion~g~! + +[AS3_2] +~b~Va vers les bouées repères! ~y~L'avion est en phase d'approche! + +[AS3_6] +~g~~1~ SUR 8 + +[KM1] +'LA GRANDE EVASION' + +[KM3] +'NEGOCIATION' + +[KM4] +'SHIMA' + +[KM5] +'LES RIVIERES POURPRES' + +[KM1_A] +Ma soeur m'a parlé de toi en des termes élogieux, + +[KM1_E] +même si je suis toujours pas convaincu qu'un gaijin à autre chose à offrir que des déceptions. + +[KM1_B] +Quoique, tu pourrais peut-être m'aider à régler un petit problème. + +[KM1_F] +Bien sûr, un échec te serait fatal. + +[KM1_C] +Un kanbu Yakuza est en garde à vue, en attente d'un transfert au palais de justice. + +[KM1_G] +C'est un membre apprécié de notre famille. + +[KM1_H] +Libère-le et amène-le au dojo de Bedford Point. + +[KM1_D] +Nous te remercions pour ton aide désintéressée. Si toi aussi, tu as besoin d'aide un jour, le dojo te fournira avec plaisir deux hommes pour t'assister. + +[KM1_1] +~g~Vole une bagnole de flics! + +[KM1_2] +~g~Equipe-la d'une bombe! + +[KM1_3] +~g~Maintenant, emmène-la au dojo des Yakuzas! + +[KM1_5] +~g~Bien, maintenant, va au poste de police. + +[KM1_6] +~g~Equipe la voiture d'une bombe! + +[KM1_7] +~g~Réservé aux véhicules de police! + +[KM1_9] +~r~Tu n'as pas utilisé de voiture piégée pour détruire le mur. + +[KM1_10] +~r~Le Yakuza Kanbu est mort, tout comme ton honneur! + +[KM1_11] +~r~Tu t'es fouré tout seul dans le pétrin! + +[KM2_A] +Dans ce milieu, l'étiquette ne doit jamais être sous-estimée. Au contraire! + +[KM2_B] +J'ai honte mais un homme m'a autrefois rendu service et je n'ai jamais eu l'occasion de lui montrer ma reconnaissance. + +[KM2_C] +Cet homme est un passionné de voitures et il a demandé que nous lui fournissions certains modèles pour sa collection. + +[KM2_F] +Mon honneur m'interdit de refuser. + +[KM2_2] +~g~Voiture livrée. + +[KM3_A] +Face au danger, l'inconscient tourne le dos alors que le sage fait front. + +[KM3_B] +Nous avons prévenu à plusieurs reprises le Cartel colombien qu'il fallait laisser nos intérêts à Liberty tranquilles. + +[KM3_C] +En ce moment même, ils négocient avec les Jamaïcains pour se moquer encore plus de nous. + +[KM3_D] +Ils sont en train de passer un accord pour l'ensemble de la ville. + +[KM3_F] +Prends un de mes hommes, vole une bagnole de Yardie et va présenter nos respects aux Colombiens. + +[KM3_E] +Pour notre honneur, ils doivent tous mourir! + +[KM3_2] +~g~Va prendre ton contact. + +[KM3_3] +~g~La réunion a lieu sur le parking de l'hôpital de Rockford! + +[KM3_4] +~r~Ils ont filé! + +[KM3_6] +~g~Supprime-les, tue-les tous! + +[KM3_8] +~g~Il te faut une bagnole de Yardie pour ce boulot! + +[KM3_9] +~r~L'un des Colombiens est mort, l'accord est caduc. + +[KM3_10] +~r~Le contact est mort! + +[KM4_A] +La seule vraie force consiste à ne jamais montrer ses faiblesses. + +[KM4_C] +Va récupérer l'argent immédiatement pour qu'on puisse le placer sur les comptes du casino. + +[KM4_1] +Je peux pas vous payer et même si je pouvais, je le ferais pas! + +[KM4_9] +Un gang de jeunes vient de dévaliser cet endroit! Ils ont tout pris! + +[KM4_2] +Vous ne servez à rien! + +[KM4_10] +Quel genre de Yakuza êtes-vous? hein? + +[KM4_3] +C'est pas pour ça que je vous paye! Si j'avais voulu ce genre de protection, je me serais adressé directement aux flics! + +[KM4_4] +~g~Réglez son compte au gang et récupérez ~b~l'argent de la protection~g~! + +[KM4_7] +~r~Le commerçant est mort! + +[KM4_5] +Donald Love aimerait que vous passiez prendre le thé chez lui afin d'avoir une conversation avec vous. + +[KM4_6] +Tout l'argent est là! + +[KM4_8] +~g~Malette récupérée! + +[KM5_A] +TOI! Et c'est maintenant que tu te décides à te montrer! + +[KM5_B] +Il semblerait que ta tentative pour empêcher les Jamaïcains + +[KM5_B1] +de s'allier aux Colombiens ait complètement raté! + +[KM5_C] +Y a des dealers du gang des Caraïbes dans tout Liberty qui vendent des sachets de SPANK comme si c'était des friandises! + +[KM5_D] +Les membres du Cartel se foutent de nous et de moi surtout! + +[KM5_E] +Je te donne une dernière chance de me prouver que ma soeur avait raison de te faire confiance! + +[KM5_F] +Débarrasse-moi de tous ces rats et rachète-toi avec leur sang! + +[KM5_3] +~r~Tu n'as pas réussi à tuer au moins ~1~ Yardies. + +[KM5_4] +~g~Bravo, tu as tué ~1~ Yardies. + +[KM5_5] +~g~Bravo! Tu as tué ~1~ Yardies. Bonus : ~1~$ + +[RM1] +'EQUILIBRER LA BALANCE' + +[RM3] +'PREUVES A PROUVER' + +[RM4] +'PECHE AU GROS' + +[RM5] +'LE MARTEAU ET L'ENCLUME' + +[RM1_D] +Il est sous protection dans une propriété de WitSec à Newport, des appartements à l'arrière du parking. + +[RM1_E] +Enfume-moi cet endroit, ça devrait les faire sortir, et tu me les butes tous, fais en sorte que Mc Machin ne parle plus à personne. + +[RM1_1] +~g~Vérifie la maison du programme de protection des témoins. + +[RM1_2] +~g~Chope McAffrey! + +[RM2_A1] +Hé, gamin, par là! + +[RM2_A] +Un vieux pote à moi de l'armée a une affaire à Rockford. + +[RM2_D] +Il a besoin d'un coup de main et, en échange, il te fera des prix d'enfer sur son matos. + +[RM2_E] +Ray a téléphoné... mais je pensais que vous seriez plusieurs... + +[RM2_F] +Bon, trois bras valent mieux qu'un, alors prends ce qu'il te faut. + +[RM2_G] +~g~Va voir Phil! + +[RM2_H] +~r~Phil a été tué! + +[RM2_L] +Héhé! Si je t'avais eu comme partenaire au Nicaragua, j'aurais peut-être encore mes deux bras! + +[RM2_N] +Laisse le fric. Maintenant, dégage, je m'occupe des poulets. + +[RM3_D] +La pièce à conviction est en train de rouler dans la ville. + +[RM3_E] +Va falloir que tu tamponnes cette caisse et que tu récupères les preuves au fur et à mesure qu'elles tombent! + +[RM3_F] +Quand t'auras tout, laisse-les dans la voiture et brûle-la. + +[RM3_G] +C'est une bonne affaire pour tous les deux, tu sais, gamin. + +[RM3_1] +~g~Laisse les preuves dans la voiture et brûle-la. + +[RM3_4] +~g~L'accusation n'a pas tenu compte des pièces à conviction! + +[RM3_6] +~r~Ces photos seront dans tout Liberty! + +[RM3_7] +~g~Brûle la voiture! + +[RM4_A] +Mon partenaire est un rat. + +[RM4_C] +Il va souvent pêcher en bateau près du phare de Portland Rock, la nuit. + +[RM4_D] +Vole une vedette de la police et assure-toi que ses sales plans de traître tombent à l'eau! + +[RM4_1] +~g~Vole une vedette de la police. + +[RM4_2] +~g~Va jusqu'au phare et 'occupe-toi' du partenaire de Ray! + +[RM5_A] +Espèce de bon à rien! + +[RM5_A1] +Tu t'es chié dessus! Mon cul est en première ligne et t'es même pas foutu de buter une mouche! + +[RM5_B] +Je t'ai filé de la thune pour buter ce témoin et il est encore en vie! + +[RM5_B1] +Et il va faire une déposition devant les fédéraux aujourd'hui! + +[RM5_C] +Ils vont le transférer d'un moment à l'autre de l'Hôpital Général Carson à Rockford! + +[RM5_D] +S'il crache le morceau, je vais cracher mes dents, et plus encore... + +[RM5_E] +Alors fais ce putain de boulot pour lequel je t'ai payé! + +[RM5_1] +~g~Intercepte l'ambulance. + +[RM5_2] +~g~Tu t'es fait repérer! + +[RM5_3] +~g~C'était un piège à cons! + +[RM5_4] +~g~Les balles ne peuvent pas traverser cette armure! + +[RM5_5] +~g~Cette armure est recouverte d'amiante! + +[RM5_7] +~r~Le témoin est arrivé à bon port! + +[RM5_8] +~g~Le témoin s'est noyé! + +[LOVE2] +'HARA-KIRI WAKA-SHIRA' + +[LOVE3] +'RIEN QU'UNE GOUTTE DANS L'OCEAN' + +[LOVE1_A] +Tout d'abord, je tiens à te remercier pour m'avoir aidé à régler cette affaire privée. + +[LOVE1_F] +Les gens croient tout ce qu'on leur raconte de nos jours! + +[LOVE1_D] +Ils essaient de m'extorquer plus d'argent, mais j'aime pas renégocier mes accords! + +[LOVE1_E] +Un accord est un accord et il n'auront rien de plus de moi! + +[LOVE1_G] +Va aider mon ami, tu as carte blanche. + +[LOVE1_2] +~g~Sauve le vieux bridé. + +[LOVE1_3] +~g~Amène le vieux bridé à l'appartement de Donald Love. + +[LOVE1_4] +~g~Le vieux bridé doit se trouver dans un de ces garages... + +[LOVE1_6] +~r~Les tripes du vieux bridé sont étalées sur le trottoir. Attention, ça glisse! + +[LOVE1_7] +~g~Les portes ne s'ouvriront que pour laisser passer une voiture du gang des Colombiens. + +[LOVE2_A] +Rien de mieux qu'une bonne vieille guerre des gangs pour faire marcher l'immobilier, + +[LOVE2_B] +en dehors d'une bonne épidémie... mais ça serait peut-être un peu trop. + +[LOVE2_C] +J'ai remarqué que les Yakuzas et les Colombiens n'étaient pas très copains. + +[LOVE2_D] +C'est une opportunité que nous devons saisir! + +[LOVE2_E] +Je veux que tu tues le waka-gashira des Yakuzas, Kenji Kasen. + +[LOVE2_F] +Kenji est à une réunion au sommet du parking à plusieurs étages de Newport. + +[LOVE2_G] +Procure-toi une voiture du Cartel et tue-le! + +[LOVE2_H] +Les Yakuzas doivent accuser le Cartel de leur déclarer la guerre. + +[LOVE2_1] +~g~Va à Fort Staunton et vole une voiture du gang des Colombiens. + +[LOVE2_2] +~g~Maintenant, rends-toi au ~p~parking de Newport~g~ et élimine Kenji. + +[LOVE2_3] +~r~Si tu n'as pas de voiture du Cartel, tu seras démasqué! + +[LOVE2_4] +~r~Les Yakuzas t'ont reconnu! + +[LOVE2_6] +~r~Tu as tué tous les témoins! + +[LOVE3_A] +De nos jours, certaines marchandises de valeur sont difficiles à importer. + +[LOVE3_C] +Plusieurs paquets vont être jetés à l'eau. + +[LOVE3_D] +Récupère-les avant tout le monde. + +[LOVE3_1] +~g~Procure-toi un ~r~bateau~g~ et suis l'~y~avion~g~! + +[LOVE4] +'VOL AU VENT' + +[LOVE5] +'ACCORTE ESCORTE!' + +[LOVE4_A] +Merci beaucoup pour ces paquets, mais ils n'étaient qu'un leurre! + +[LOVE4_B] +Je suis désolé pour ça, mais c'est quelque fois comme ça dans les affaires. + +[LOVE4_C] +Mon réel objectif se trouvait depuis le début caché à bord de l'avion. + +[LOVE4_F] +J'avais graissé la patte aux autorités. + +[LOVE4_1] +~r~Les Colombiens sont là! + +[LOVE4_2] +~g~Le paquet est parti! Suis les Colombiens et récupère-le! + +[LOVE4_3] +~g~Panlantic construction ? + +[LOVE4_5] +~g~Le paquet devrait être à bord de l'avion... + +[LOVE4_6] +~g~Prends l'ascenseur pour accéder au sommet de la tour! + +[LOVE5_B] +Mon ami oriental a besoin d'une escorte pour l'accompagner pendant l'authentification de ma dernière acquisition. + +[LOVE5_1] +~g~Vas-y! + +[LOVE5_2] +~g~Tu vas avoir besoin d'une caisse! + +[LOVE5_3] +~g~Passe devant et contrôle la sortie du tunnel! + +[LOVE5_4] +~r~Protège le camion! + +[RM6] +'L'HOMME A ABATTRE!' + +[RM6_A] +T'as pas été suivi ? Parfait! + +[RM6_B] +Ca y est! Je suis cuit, déjà mort, en quelque sorte! + +[RM6_D] +J'ai un contrat sur ma tête, alors je me casse! + +[RM6_E] +Amène-moi à l'aéroport et t'auras un bon pourboire! + +[RM6_666] +Prends soin de ma Patriot blindée. On se verra à Miami, Ray! + +[CAT1] +'LA RANCON' + +[CAT2] +'L'ECHANGE' + +[CAT1_A] +C'est moi qui ai Maria. Je pense pas que t'aies l'envie que son visage ressemble à un foie de veau trop faisandé, hein ? + +[CAT2_F] +Je me suis cassé un ongle et ma mise en plis est foutue! Tu le crois, ça ? Ca m'avait coûté 50 dollars! + +[CAT2_G] +J'étais complètement flippée, mais, je me suis dit, Maria, t'es une grande fille maintenant. + +[CAT2_H] +Oh on va bien s'amuser, tu sais, parce que ma soeur, elle a dit qu'elle voulait venir s'installer avec nous, elle et ses deux gosses. + +[CAT2_I] +Son mec a recommencé à aller voir ailleurs et... + +[CAT1_F] +Rattrape Catalina avant la fin du temps imparti! + +[CAT_MON] +~g~T'as pas encore assez d'argent. T'as besoin de 500 000$. + +[BITCH_D] +~g~Maria est morte! + +[WEATHER] +METEO AGITEE + +[WEATHE2] +METEO NORMALE + +[8001] +Tu as échoué lamentablement! + +[1000] +TU ES MORT + +[1001] +TU ES MORT + +[1002] +TU ES MORT + +[1003] +TU ES MORT + +[1004] +TU ES MORT + +[1005] +CAPTURE! + +[1006] +CAPTURE! + +[1007] +CAPTURE! + +[1008] +CAPTURE! + +[1009] +CAPTURE! + +[GA_4] +Une voiture piégée coûte 1000$ pièce! + +[GA_5] +Ta voiture est déjà équipée d'une bombe. + +[GA_6] { re3 change } +Gare-la, arme la bombe avec la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ et CASSE-TOI! + +[GA_7] { re3 change } +Arme la bombe avec la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~. Elle explose au démarrage. + +[GA_8] +Utilise le détonateur pour armer la bombe. + +[GA_9] +Tu as récupéré ~1~ des 10 voitures spéciales. + +[GA_10] +Beau morceau. Voilà tes ~1~$. + +[GA_11] +On en a déjà des comme ça. Ca nous sert à rien! + +[GA_12] +Bombe armée. + +[GA_13] +Comme un pro! Finis la liste et t'auras un bonus! + +[GA_14] +Toutes les voitures! Parfait! Tiens, voilà un petit quelque chose! + +[GA_15] +J'espère que t'aimes la nouvelle couleur! + +[GA_16] +La peinture est en option! + +[GA_19] +Ce modèle, ça nous intéresse pas! + +[GA_20] +On en a trop des comme ça! Désolé, mec. + +[CR_1] +La grue ne peut pas soulever ce véhicule. + +[PU_MONY] +T'as pas assez d'argent. + +[CO_ALL] +Tu les as toutes eues. Tiens, voilà pour toi... + +[PAUSED] +PAUSE + +[HEALTH1] +Sors de là! T'es en pleine forme! + +[HEALTH2] +Coût des soins + +[HEALTH3] +Je vais vous remettre sur pied! + +[HEALTH4] +Ca fera 250$. + +[FEB_STA] +Statistiques + +[FEB_BRI] +Briefings + +[FEB_CON] +Commandes + +[FEB_AUD] +Son + +[FEB_DIS] +Affichage + +[FEB_LAN] +Langue + +[FEP_STA] +STATISTIQUES + +[FEP_BRI] +BRIEFINGS + +[FEP_CON] +COMMANDES + +[FEP_AUD] +SON + +[FEP_DIS] +AFFICHAGE + +[FEP_LAN] +LANGUE + +[FEF_ST1] +C'est qui, le méchant ? + +[FEF_ST2] +T'as foutu un sacré merdier! + +[FEF_BR1] +T'as perdu le fil de l'histoire ? + +[FEF_CO1] +Tu veux prendre les commandes, c'est ça ? + +[FEF_CO2] +Choisis la configuration qui correspond le mieux à ta façon de jouer. + +[FEF_SA1] +Fais la queue! + +[FEF_SA2] +Sauvegarde et charge tes parties + +[FEF_AU1] +Fais péter la sono! + +[FEF_AU2] +Sélectionne une station de radio et des effets sonores + +[FEF_DI1] +Change de partie! + +[FEF_DI2] +Configure le jeu pour ta télé pourrie! + +[FEF_LA1] +Mais qu'est-ce que tu racontes ? + +[FEF_LA2] +Do tu hablar Deutsh ? + +[FEB_PMB] +Briefings de mission précédents: + +[FEC_NA] +Oups! + +[FEC_CWL] +Montrer les armes à gauche + +[FEC_CWR] +Montrer les armes à droite + +[FEC_LOF] +Regarder devant + +[FEC_TAR] +Cible + +[FEC_MOV] +Déplacement + +[FEC_CAM] +Modes caméra + +[FEC_PAU] +Pause + +[FEC_ENV] +Monter dans un véhicule + +[FEC_JUM] +Sauter + +[FEC_ATT] +Attaquer ou tirer + +[FEC_RUN] +Courir + +[FEC_FPC] +Vue subjective + +[FEC_LL] +Regarder à gauche + +[FEC_LB1] +Regarder + +[FEC_LB2] +derrière + +[FEC_LB] +Regarder derrière + +[FEC_LR] +Regarder à droite + +[FEC_HOR] +Klaxon + +[FEC_VES] +Commandes du véhicule + +[FEC_RSC] +Chang. les stations de radio + +[FEC_BRA] +Frein ou marche arrière + +[FEC_HAB] +Frein à main + +[FEC_CAW] +Arme du véhicule + +[FEC_ACC] +Accélérateur + +[FEC_SMT] +Déclencheur de mission spéciale + +[FEA_OUT] +Sortie audio: + +[FEA_ST] +Stéréo + +[FEA_MNO] +Mono + +[FEA_NON] +Aucune + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_DBG] +Menu Debug + +[FEM_MCM] +Menu memory card + +[FEM_RMC] +Enregistrer memory card 1 + +[FEM_TFM] +Test memory card 1 formatée + +[FEM_TUM] +Test memory card 1 non formatée + +[FEM_CRD] +Créer rép. racine + +[FEM_CLI] +Créer et charger des icones + +[FEM_FFF] +Remplir le premier fichier de conneries + +[FEM_SOG] +Sauvegarder uniquement la partie + +[FEM_CES] +Vérifier chaque sauvegarde de 0kb4 + +[FEM_STG] +Sauvegarder la partie + +[FEM_STS] +Sauvegarder la partie sous le nom GTA3 + +[FEM_CPD] +Créer un répertoire protégé contre les copies + +[FEM_MC2] +Menu memory card 2 + +[FEM_TS] +Test de sauvegarde : + +[FEM_TL] +Test de chargement : + +[FEM_TD] +Test de suppression : + +[PL_STAT] +Statistiques du joueur + +[PE_WAST] +Victimes + +[PE_WSOT] +Personnes tuées par d'autres + +[CAR_EXP] +Véhicules détruits + +[TM_BUST] +Nb de fois capturé + +[M_WASTE] +Hommes tués + +[F_WASTE] +Femmes tuées + +[PIG_WST] +Flics tués + +[GNG_WST] +Gangsters tués + +[MED_WST] +Toubibs tués + +[FIRE_WS] +Pompiers tués + +[DED_CRI] +Criminels tués + +[DED_DED] +Parasites tués + +[DED_HOK] +Putes tuées + +[HEL_DST] +Hélicoptères détruits + +[PER_COM] +Pourcentage accompli + +[KGS_EXP] +Kilos d'explosif utilisés + +[ACCURA] +Précision + +[ELBURRO] +Meilleur temps en sec. + +[CAR_CRU] +Véhicules défoncés + +[HED_EX] +Têtes explosées + +[TM_DED] +Visites à l'hôpital + +[DAYSPS] +Jours de jeu + +[MMRAIN] +Mm de pluie tombés + +[MXCARD] +Distance max. de sauts dangereux (pieds) + +[MXCARJ] +Hauteur max de sauts dangereux (pieds) + +[MXCARDM] +Distance max. de sauts dangereux (m) + +[MXCARJM] +Hauteur max de sauts dangereux (m) + +[MXFLIP] +Saltos max. de sauts dangereux + +[MXJUMP] +Rotation max. de sauts dangereux + +[BSTSTU] +Meilleures cascades jusqu'à maintenant: + +[INSTUN] +Cascade dangereuse + +[PRINST] +Cascade dangereuse parfaite + +[DBINST] +Double cascade dangereuse + +[DBPINS] +Double cascade dangereuse parfaite + +[TRINST] +Triple cascade dangereuse + +[PRTRST] +Triple cascade dangereuse parfaite + +[QUINST] +Quadruple cascade dangereuse + +[PQUINS] +Quadruple cascade dangereuse parfaite + +[NOSTUC] +Aucune cascade dangereuse accomplie + +[NOUNIF] +Sauts uniques accomplis + +[NOUNGM] +Total sauts uniques + +[NMISON] +Missions commencées + +[NMMISP] +Missions réussies + +[PASDRO] +Passagers perdus + +[MONTAX] +Total courses en taxi + +[DAYPLC] +Dépenses quotidiennes de police + +[CRIMRA] +Taux de criminalité + +[GMSTOR] +Magasin du jeu + +[PREBRF] +Briefings précédents + +[CNTLS] +Commandes + +[MUSMEN] +Musique/Effets + +[GAMSET] +Paramètres du jeu + +[LANGUA] +Langue + +[DSPLAY] +Affichage + +[DEBUGM] +Menu Debug + +[QUITOP] +Options de fin + +[CONTRL] +Configuration des manettes + +[SET1EN] +Config 1. activée + +[SET1] +Config 1. + +[SET2EN] +Config 2. activée + +[SET2] +Config 2. + +[SET3EN] +Config 3. activée + +[SET3] +Config 3. + +[SET4EN] +Config 4. activée + +[SET4] +Config 4. + +[GOBACK] +Retour + +[SOUND] +AUDIO + +[MUSVOL] +Volume de la musique + +[SFXVOL] +Volume des effets + +[SCROPT] +OPTIONS D'AFFICHAGE + +[CTRSCR] +Centrer l'écran + +[SCRFOR] +Format d'écran + +[GMSVLQ] +SAUVEGARDER-CHARGER-QUITTER + +[GMREST] +Recommencer + +[NOGMSV] +Tu ne peux sauvegarder que depuis ta planque. + +[DLFILE] +Supprimer les fichiers de GTA3 + +[CHFILE] +CHOISIR LE FICHIER A CHARGER + +[CHFIDL] +CHOISIR LE FICHIER A SUPPRIMER + +[SVCONF] +CONFIRMER SAUVEGARDE + +[SVFNAM] +Le nom du fichier de sauvegarde est + +[SAVEDN] +Erreur. Echec de la sauvegarde. + +[LANGSL] +CHOIX DE LA LANGUE + +[ENGLIS] +Anglais + +[GERMAN] +Allemand + +[ITALIA] +Italien + +[FRENCH] +Français + +[SPAIN] +Espagnol + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[PRVMEN] +Briefings de mission précédents + +[DOSVGM] +Tu veux sauvegarder la partie ? + +[FORMEN] +Menu formatage + +[MEMTST] +Ecran de test memory card + +[REGCAR] +Enregistrer memory card 1 + +[TEFONE] +Test memory card 1 formatée + +[TEUFON] +Test memory card 1 non formatée + +[CRROOT] +Créer rép. racine + +[CRLDIC] +Créer et charger des icones + +[FLFSGF] +Remplir le premier fichier de conneries + +[PUSAVE] +Sauvegarder uniquement la partie + +[CHEVOK] +Vérifier chaque sauvegarde de 0kb4 + +[SVGMON] +Sauvegarder + +[CNTSAV] +Sauvegarde impossible. Tu es en mission. + +[CNCSAV] +Sauvegarde impossible. Tu es en voiture. + +[CRMGSV] +Créer un répertoire protégé contre les copies + +[MGSVCN] +Répertoire créé + +[MGSVNC] +Répertoire non créé + +[YES] +Oui + +[NO] +Non + +[X] +x + +[LAST] +Dernier message. + +[FEDS_XB] +Choisir + +[FEDS_ST] +touche START - REPRENDRE + +[FEST_OO] +sur + +[FEC_TUC] +Commandes tourelle + +[FEC_SM3] +Déclencheur mission spéciale (touche R3) + +[FEC_RS3] +Chang. les stations de radio (touche L3) + +[FEC_HO3] +Klaxonner (touche L3) + +[DIAB1] +'TOURISME' + +[DIAB2] +'DE LA GLACE, T'ES REFROIDI' + +[DIAB3] +'JUGÉ PAR LE FEU' + +[DIAB4] +'STAR DU X' + +[DIAB1_A] +El Burro veut te donner une chance. Va à la cabine téléphonique de Hauteurs de Hepburn si tu veux plus d'infos. + +[DIAB1_C] +Tu t'es bien défendu. Retourne à la cabine et El Burro aura peut-être du boulot pour toi. + +[DIAB1_1] +~g~3..2..1..GO GO GO! + +[DIAB1_4] +~g~Trouve une voiture rapide et va sur la grille de départ. + +[DIAB1_3] +~r~Tu ne pourrais même pas gagner à une tombol, blaireau! + +[DIAB1_2] +~g~Félicitations tu as gagné, avec un temps incroyable de ~1~ secondes + +[FIRST] +~g~1er + +[SECOND] +~g~2e + +[THIRD] +~g~3e + +[FOURTH] +~g~4e + +[DIAB2_1] +~g~Va prendre la malette à Harwood. + +[DIAB2_2] +~g~Trouve une camionnette de vendeur de glaces. + +[DIAB2_3] +~g~Gare la camionette à Atlantic Quays. + +[DIAB2_4] +~g~Appuie sur la ~w~touche ~k~~VEHICLE_HORN~ ~g~pour actionner la clochette. + +[DIAB2_6] +~g~Appuie sur la ~w~touche ~k~~VEHICLE_HORN~ ~g~pour actionner la clochette. + +[DIAB2_7] +~g~Appuie sur la ~w~touche ~k~~VEHICLE_HORN~ ~g~pour actionner la clochette. + +[DIAB2_5] +~g~Sors de la camionette et sers-toi de la télécommande pour faire exploser la camionnette. + +[YD1] +'LES FOUS DU VOLANT' + +[YD2] +'TOUS A L'UZI' + +[YD3] +'LA VALSE DES VOITURES' + +[YD4] +'A NOUS LE ROYAUME' + +[YD_P] +King Courtney voudrait te dire un mot. Va à la cabine d'Aspatria! + +[YD1_A] +~w~Voila King Courtney. + +[YD1_A1] +~w~Mon gang de Yardies aurait besoin d'un bon chauffeur et t'as une réputation de rapide. + +[YD1_B] +~w~Va à la décharge en face du stade et attends les autres joueurs. + +[YD1_C] +~w~J'ai des gars qui surveillent tous les points de passage de Staunton. + +[YD1_D] +~w~Le premier pilote qui franchit un point de passage gagne 1000$ et ainsi de suite. + +[YD1_D1] +~w~Si tu passes plus de checkpoints que tous les autres pilotes, j'aurais peut-être du boulot pour toi. + +[YD1_E] +~g~Prépare-toi à partir! + +[YD1_F] +~g~T'as foncé dès le départ. J'adore ton style! + +[YD1_G] +~r~C'est une course de bagnoles. T'as besoin d'une bagnole, patate! + +[YD1GO] +~g~GO! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +1000$! + +[Y1_1ST] +~g~T'es arrivé premier en franchissant ~1~ points de passage! + +[Y1_2ND] +~y~2e avec ~1~ points de passage gagnants. ~y~Pas mal, mais t'es pas le meilleur. + +[Y1_3RD] +~r~3e avec ~1~ points de passage gagnants. ~r~Je croyais que t'étais bon! + +[Y1_LAST] +~r~Tu es le dernier! ~r~Tu m'as fait perdre mon temps, imbécile! + +[Y1_J1ST] +~y~1er ex aequo avec ~1~ points de passage gagnants. ~y~C'est bien, mais tu dois être le meilleur pour courir sur Queen lizzy! + +[Y1_J2ND] +~r~2e ex aequo avec ~1~ points de passage gagnants. Tu conduis comme un vieux singe dingo! + +[Y1JLAST] +~r~Dernier ex æquo! Tu parles comme un pilote, mais tu pilotes comme un beau-parleur! + +[Y1_TEST] +VOITURE A L'EAU! + +[YD2_A] +~w~J'ai besoin de voir si tu peux faire mon sale boulot. + +[YD2_A1] +~w~Faut voir si on peut te faire confiance. + +[YD2_B] +~w~Deux de mes gars seront là-bas dans peu de temps pour te faire faire un tour, + +[YD2_B1] +~w~histoire de voir si tu vaux quelque chose... + +[YD2_C] +~w~On va aller faire un tour à Hauteurs de Hepburn, flingue-nous quelques Diablos qui font des misères à Queen Lizzy. + +[YD2_CC] +~w~Tiens, t'auras besoin d'un calibre. + +[YD2_D] +~w~Tu conduis et tu tires. On s'arrangera pour que tu te fasses pas descendre. + +[YD2_E] +~w~Allons-y!! + +[YD2_F] +~r~Il nous a échappé, colle-lui au cul!!! + +[YD2_G1] +~w~Hauteurs de Hepburn... On va se farcir quelques Diablos de malheur... + +[YD2_G2] +~w~Mais n'oublie pas, ~r~tu restes dans la bagnole!! + +[YD2_H] +~w~OK, ramène-nous sur le territoire des Yardies! Allez, FONCE! + +[YD2_L] +~w~Tu t'es bien débrouillé, le bourreau. + +[YD2_M] +~r~Il a bousillé ma bagnole! Descends-le! + +[YD2_N] +~w~Ramène ta fraise dans cette bagnole! + +[YD3_A] +Je veux que tu piques quelques bagnoles de gangs + +[YD3_A1] +comme ça, on pourra faire un carton chez nos ennemis. + +[YD3_B] +Je veux une Sentinelle de la Mafia, + +[YD3_B1] +une Stinger des Yakuzas et une + +[YD3_B2] +Stallion des Diablos comme ça, on pourra frapper dans tous les gangs de Liberty. + +[YD3_C] +Tu les amènes au garage de Newport et souviens-toi, + +[YD3_C1] +elles ne nous sont utiles qu'en bon état! + +[YD3_D] +Spare text label + +[YD3_E] +~r~Tu as déjà piqué une voiture des Diablos! + +[YD3_F] +~r~Tu as déjà piqué une voiture de la Mafia! + +[YD3_G] +~r~Tu as déjà piqué une voiture des Yazukas! + +[YD3_H] +~g~Bagnole des Diablos chourrée! + +[YD3_I] +~g~Bagnole de la Mafia chourrée! + +[YD3_J] +~g~Bagnole des Yasukas chourrée! + +[YD3_K] +~r~La bagnole est presque une épave! Fais-la réparer! + +[YD3_L] +Emmène-la au garage! + +[YD3_M] +~r~T'as fait un tonneau avec! Va en chercher une autre! + +[YD4_A] +Ecoute-moi bien! + +[YD4_A1] +Tu vas aller à Bedford Point. + +[YD4_A2] +Il y a de la came dont j'ai besoin rapidos dans une vieille bagnole! + +[YD4_B] +LETTRE : je sais que tu es très occupé. Eh bien moi aussi, je suis très occupée. + +[YD4_C] +Je pense qu'il est temps que tu te rendes compte de ce que c'est, de la SPANK! Catalina, Bisous. + +[YD4_D] +PS : CREVE SALE CHIEN, CREVE! + +[YD4_1] +Des drogués défoncés ! + +[YD4_2] +Détruis les camionnettes de SPANK !! + +[HM_1] +'DESCENTE A L'UZI' + +[HM_2] +'BUGGY FAIT DES RAVAGES' + +[HM_3] +'VOITURE PIEGEE' + +[HM_5] +'LA RIXE' + +[HOOD1_A] +Va à la cabine dans Wichita Gardens et on parlera affaires. + +[HM1_A] +Salut! C'est D-Ice des Red Jacks! + +[HM1_C] +Ces jeunes cons, ils viennent dans la rue et ils ont qu'une idée en tête, la SPANK et les flingues. + +[HM1_3] +~g~Les 'Nines' font leur business dans Wichita Gardens. + +[HM2_3] +Si tu touches les roues d'une bagnole, le buggy télécommandé explosera! + +[HM2_4] +Si le buggy télécommandé est hors de portée, il explosera! + +[HM2_5] +~r~Hors de portée! + +[HM3_1] +~g~Va au garage mais fais gaffe, si tu brusques trop la bagnole, elle va exploser! + +[HM3_2] +~g~Ramène la voiture en parfait état, pas de grabuge! + +[HM3_3] +~g~Fais réparer la voiture! + +[HM4_D] +~g~Trouve une caisse! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_1] +~g~Va à l'endroit où la cargaison est tombée, tu dois ramasser 30 lingots. + +[HM4_2] +~g~Souviens-toi que quand la bagnole est trop chargée, elle se traîne alors quand c'est le cas, fonce au garage pour livrer la marchandise. + +[HM5_3] +~r~On t'a dit de te servir d'une batte de baseball et c'est tout! + +[HM5_4] +~r~Ton contact est mort! + +[MEA1] +'L'ESCROC' + +[MEA2] +'LES BRAQUEURS' + +[MEA3] +'LA FEMME' + +[MEA4] +'SON AMANT' + +[MEAT1_A] +Un ami m'a dit que tu pouvais régler certains de mes problèmes. Si tu penses que tu peux m'aider, va à la cabine de Trenton. + +[MEA1_B3] +~g~Va voir le banquier. + +[MEA1_B6] +~g~Amène la voiture à la casse pour se débarrasser des preuves. Une fois sur place, sors de la voiture et laisse faire la grue! + +[MEA1_1] +~r~Le banquier est mort! + +[MEA1_2] +~r~On t'a dit d'écrabouiller la voiture! + +[MEA1_3] +~g~Sors de la bagnole! + +[MEA1_4] +~r~T'as laissé le banquier derrière toi! + +[MEA2_B3] +~g~Va voir les braqueurs. + +[MEA2_B4] +~g~Amène-les à l'usine de pâtée pour chiens. + +[MEA2_B6] +~g~Fais repeindre la voiture, comme ça y'aura pas de preuve. + +[MEA2_1] +~r~On t'a dit d'écrabouiller la voiture! + +[MEA2_2] +~r~Un braqueur est mort! + +[MEA2_4] +~r~T'as laissé un des braqueurs derrière toi! + +[MEA3_B3] +~g~Va chercher Mme Chonks. + +[MEA3_B6] +~g~Prends la voiture et fous-la à l'eau, comme ça y'aura pas de preuve. + +[MEA3_1] +~r~La femme est morte! + +[MEA3_2] +~r~Tu étais censé balancer la bagnole à la mer! + +[MEA3_3] +~r~T'as laissé sa femme derrière! + +[MEA4_B3] +~g~Va chercher l'amant de sa femme. + +[MEA4_B6] +C'est beaucoup trop tard, Marty. Je t'ai donné une chance, mais maintenant je reprends tout en charge! + +[MEA4_1] +~r~Carlos est mort! + +[MEA4_3] +~r~T'as laissé Carlos l'usurier derrière! + +[LOOK_A] +Appuie et maintiens enfoncée la ~h~touche ~k~~VEHICLE_LOOKLEFT~ ~w~ou ~h~touche ~k~~VEHICLE_LOOKRIGHT~ ~w~ pour regarder ~h~à gauche~w~ ou ~h~à droite~w~ quand tu es en voiture. Appuie sur ces deux touches pour regarder ~h~derrière~w~. + +[LOVE6_1] +~g~Maintenant, attire les flics loin de l'entrepôt! + +[LOVE6_2] +~r~T'as pas réussi à attirer les flics assez loin! + +[RM4_3] +~r~L'associé de Ray s'est échappé! + +[RM6_C] +La CIA semble s'intéresser de près à la SPANK. + +[RM6_C1] +Et elle n'aime pas qu'on cherche des noises au Cartel. + +[C_PASS] +MENACE ENRAYEE! + +[CTUTOR] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions de caisse. + +[CTUTOR2] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions de caisse. + +[COPCART] +~g~Tu as ~1~ secondes pour retourner à une voiture de police avant que la mission ne s'achève. + +[C_FAIL] +Mission de caisse terminée! + +[C_CANC] +~r~Mission de caisse annulée! + +[C_ESCP] +~r~Le suspect s'est échappé! + +[C_TIME] +~r~Ta carrière de flic est terminée! + +[C_VIGIL] +BONUS POLICE! + +[A_FAIL2] +~r~Ton manque de précipitation a été fatal pour le patient! + +[A_FAIL3] +~r~Le patient est mort! + +[A_PASS] +Sauvé! + +[F_FAIL2] +~r~T'arrives trop tard! + +[A_COMP2] +T'en as jamais assez ? + +[RM2_M] +Si tu as besoin d'artillerie, passe prendre ce dont t'as besoin dans les armoires. + +[HEAL_A] +Ton niveau de ~h~santé~w~ s'affiche en orange en haut à droite de l'écran. + +[YD1_CNT] +~1~ sur 15! + +[FM1_9] +~g~C'est la fête un peu plus haut. Dépose Maria devant. + +[FM1_Y] +~w~Ça faisait longtemps que je m'étais pas amusée comme ça et tu m'as traitée vraiment bien ... avec respect et tout. + +[FM1_AA] +~w~Oh, faut que j'y aille, à bientôt j'espère. + +[NOCONTE] +Reconnecte la manette analogique (DUALSHOCK#) ou manette analogique (DUALSHOCK#2) au port de manette 1 pour continuer. + +[WRCONT] +La manette connectée au port de manette 1 n'est pas une manette compatible. GTA3 nécessite une manette analogique (DUALSHOCK#) ou manette analogique (DUALSHOCK#2). + +[WRCONTE] +La manette connectée au port de manette 1 n'est pas une manette compatible. GTA3 nécessite une manette analogique (DUALSHOCK#) ou manette analogique (DUALSHOCK#2). + +[WRONGCD] +Disque invalide. Veuillez insérer le bon disque. + +[NOCD] +Le compartiment à disque est vide. Veuillez insérer le disque. + +[OPENCD] +Le compartiment à disque est ouvert. Veuillez refermer le compartiment à disque. + +[CDERROR] +Erreur de lecture du DVD de GTA3 + +[RESTART] +Démarrage d'une nouvelle partie + +[GA_3] +Plus de cadeaux. 1000$ pour repeindre! + +[GA_1] +Oula! Je ne touche à rien d'aussi chaud, moi! + +[GA_1A] +Reviens quand tu seras moins occupé... + +[S_PROM2] +Le garage d'à côté peut garder une voiture quand tu sauvegardes la partie. + +[STOCK] +Stock épuisé + +[FM1_O] +~w~Je pense qu'il est à la gare sur le bord de mer de Chinatown. + +[EBAL_B] +Tiens c'est là! Allez, on va se garer et on va trouver de nouvelles fringues! + +[EBAL_G] +Ça, c'est la boîte de Luigi. Viens, on va rentrer par la porte de derrière. + +[AM4_3] +T'es le nouveau coursier d'Azuka! + +[AM4_4] +T'as le fric ? Le compte y est ? + +[AM4_5] +Je sais ce que tu penses, encore un ripoux. + +[AM4_6] +Ben, le monde est pourri. + +[AM4_7] +Parce que j'ai perdu quelques équipiers, ces enfoirés de poulets commencent à renifler partout. + +[AM4_8] +J'imagine qu'ils peuvent me sentir. + +[AM4_9] +Et oui, cette ville est un vrai dépotoir. + +[AM4_10] +Mais je vais avoir besoin d'aide. + +[AM4_11] +Si ça t'intéresse, tu sais où me trouver. + +[CAM_A] +Appuie sur la ~h~touche ~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ pour changer les modes ~h~caméra ~w~quand tu es à pied ou en voiture. + +[CAM_B] +~w~Appuie sur la ~h~touche directionnelle haut~w~ ou ~h~bas~w~ pour changer les modes ~h~caméra~w~ quand tu es à pied ou en voiture. + +[KM2_1] +~g~Répare la voiture, elle doit être comme neuve. + +[LM3_6] +Joey... + +[LM3_6A] +Est-ce que je vais jouer avec ton gros canon ? + +[LM3_9A] +Y'a peut-être du travail pour toi. + +[LM3_9B] +D'accord ? + +[AWAY2] +~r~Ils se sont enfuis. + +[AWAY] +~r~Il s'est barré d'ici! + +[JM6_1] +Va à la banque par la rue principale. + +[GA_6B] { re3 change } +Tu la gares, tu l'amorces en appuyant sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ et tu te barres! + +[GA_7B] { re3 change } +Pour armer la bombe, appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~. Elle explosera au démarrage. + +[BAT1] +~g~Ramasse la batte! + +[EBAL_O] +Si t'es réglo, j'ai du travail pour toi. Et maintenant, casse-toi! + +[HELP9_B] +Appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~tirer~w~ avec le fusil à lunettes. + +[HELP9_C] +Appuie sur la ~h~touche ~k~~PED_FIREWEAPON~~w~ pour ~h~tirer~w~ avec le fusil à lunettes. + +[JM6_8] +~r~T'as semé tous les braqueurs! + +[COLT_IN] +Le pistolet est disponible au Ma-Gnum. + +[TAXI2] +~r~Tu n'as plus le temps! + +[TAXI3] +~r~Ton client est parti terrorisé! + +[TAXI7] +~r~Ta voiture est endommagée, fais-la réparer. + +[TAXI4] +La course est finie! + +[TAXI5] +BONUS DE VITESSE! + +[TAXI6] +La mission Taxi est finie. + +[FRANGO] +~g~Salvatore veux que tu aides d'abord Toni à régler son affaire avec la Triade! + +[PAGEB12] +Pot-de-vin des flics livré à la planque + +[PAGEB13] +Santé livrée à la planque + +[PAGEB14] +Adrénaline livrée à la planque + +[KM1_4] +~g~T'as besoin d'une bagnole de flic pour ce job! + +[CAT1_B] +Apporte 500 000$ à la Villa de Cedar Grove. + +[JM2_C] +Il a un stand de nouilles dans Chinatown. + +[RM6_1] +Voilà la clé d'un coffre. + +[RM6_2] +T'y trouveras du cash et des provisions. J'avais mis ça de coté au cas où les choses tourneraient mal. + +[RM6_3] +A plus. + +[FE_INIP] +Initialisation et chargement du menu pause... Veuillez patienter. + +[FESZ_CA] +Annuler + +[FESZ_QU] +Quitter + +[FESZ_L1] +Partie sauvegardée avec succès! + +[FESZ_L2] +Le nom du fichier sauvegardé est : + +[FESZ_OK] +OK + +[FES_NGA] +Nouvelle partie + +[FES_CAN] +Annuler + +[FESZ_QL] +Toutes les étapes non sauvegardées de cette partie seront perdues. Charger la partie? + +[FESZ_QD] +Effacer cette sauvegarde? + +[FESZ_QO] +Ecraser cette sauvegarde? + +[FESZ_QR] +Veux-tu vraiment commencer une autre partie? Toutes vos données depuis la dernière partie sauvegardée seront perdues. Continuer? + +[FESZ_QS] +PASSEZ EN SAUVEGARDE? + +[SLONFP] +Port 1 Fichier Protégé. + +[T4X4_1] +'TERRAIN DE JEU DU PATRIOT' + +[T4X4_2] +'UN TOUR DANS LE PARC' + +[T4X4_3] +'ADHÉRENCE!' + +[MM_1] +'GAZ À TOUS LES ÉTAGES' + +[T4X4_1A] +~g~T'as ~y~5 minutes~g~ pour franchir ~y~15~g~ points de passage. + +[T4X4_1B] +~1~ sur 15! + +[T4X4_1C] +~g~Chaque point de passage te rapportera ~y~20 secondes~g~. + +[T4X4_2A] +~g~Tu as ~y~2 minutes~g~ pour franchir ~y~12~g~ points de passage! ~g~Tu peux les franchir dans ~y~n'importe quel ordre. + +[T4X4_2B] +~1~ sur 12! + +[T4X4_2C] +~y~Passe le premier point de passage pour déclencher le chrono. ~y~ Chaque point de passage te rapportera ~y~10 secondes~g~. + +[T4X4_3A] +~g~Tu as ~y~5 minutes~g~ pour franchir ~y~20~g~ points de passage. ~g~Tu peux les franchir dans ~y~n'importe quel ordre. + +[T4X4_3B] +~y~Franchis~g~ le premier point de passage pour déclencher le chrono. ~g~Chaque point de passage te rapportera ~y~15 secondes~g~. + +[T4X4_3C] +~1~ sur 20! + +[T4X4_F] +~r~Tu t'es barré! Trop dur pour toi ?! + +[MM_1_A] +~g~Tu as ~y~2 minutes~g~ pour franchir les ~y~20 points de passage~g~ dans le parking à plusieurs étages! ~g~Tu peux les franchir dans n'importe quel ordre. + +[MM_1_B] +~1~ sur 20! + +[MM_1_C] +~g~Ca fait 20 secondes, plus ~y~5 secondes~g~ pour chaque point de passage. ~g~Le chrono commence ~y~immédiatement. + +[FM2_14] +~r~Tu t'es trop approché et Bill t'a vu! + +[FM2_15] +~g~Ne t'approche pas trop ou Bill va se douter de quelque chose! + +[UPSIDE] +~r~Tu t'es retourné! + +[FM2_16] +BALANÇOMETRE : + +[LM3_11] +~g~Misty ne montera pas dans un bus, trouve un autre véhicule! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinelle + +[PATRIOT] +Patriot + +[FIRETRK] +Camion de pompier + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulance + +[FBICAR] +F.B.I. + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +M. Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Police + +[ENFORCR] +Enforcer + +[SECURI] +Sécuricar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Train + +[HELI] +Hélicoptère + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[BELLYUP] +Triad + +[MRWONGS] +M. Wongs + +[MAFIACR] +Mafia + +[YARDICR] +Yardie + +[YAKUZCR] +Yakuza + +[DIABLCR] +Diablo + +[COLOMCR] +Cartel + +[HOODSCR] +Hoods + +[AEROPL] +Avion + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Distance parcourue à pied (miles) + +[FEST_DC] +Distance parcourue en voiture (miles) + +[FESTDFM] +Distance parcourue à pied (m) + +[FESTDCM] +Distance parcourue en voiture (m) + +[FEST_R1] +Terrain de jeu du Patriot en secondes + +[FEST_R2] +Un tour dans le parc en secondes + +[FEST_R3] +Adhérence en secondes + +[FEST_RM] +Gaz à tous les étages en secondes + +[FEST_LS] +Nbre de personnes sauvées dans l'ambulance + +[FEST_CC] +Criminels tués dans la mission de police + +[FEST_FE] +Nombre total de feux éteints + +[FEST_LF] +Vol le plus long en Dodo + +[FEST_BD] +Meilleur temps pour désamorcer la bombe + +[FEST_RP] +Rodéos réussis + +[FEST_MP] +Missions réussies + +[FEST_BB] +Les fous du volants: + +[FEST_H0] +La plupart des points de passage + +[FEST_GC] +Nombre total de voitures de gang: + +[FEST_H1] +Destruction de Diablo + +[FEST_H2] +Massacre de la Mafia + +[FEST_H3] +Le désastre du casino + +[FEST_H4] +Le destructeur de Rumpo + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ] +BONUS POUR CASCADE UNIQUE! + +[SPRAY] +Amène ton véhicule à l'atelier de peinture pour annuler ton ~h~indice de recherche~w~, ~h~répare ~w~et ~h~repeins ~w~ton véhicule. Coût -~h~ 1000$. + +[HM1_1] +~G~Refroidis 20 Nines violets en 2 minutes et 30 secondes. + +[KM1_8A] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour ~h~activer la bombe~w~, n'oublie pas de t'éloigner. + +[KM1_8D] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour ~h~activer la bombe~w~, n'oublie pas de t'éloigner. + +[KM1_12] +~g~Amène-le au dojo mais débarrasse-toi des flics d'abord! + +[RATNG1] +Pickpocket + +[RATNG2] +Rascal + +[RATNG3] +Voyou + +[RATNG4] +Prostituée + +[RATNG5] +Idiot + +[RATNG6] +Pilote + +[RATNG7] +Gros Bras + +[RATNG8] +Réparateur + +[RATNG9] +Associé + +[RATNG10] +Nettoyeur + +[RATNG11] +Assassin + +[RATNG12] +Bras droit + +[RATNG13] +Exécutant + +[RATNG14] +Capo + +[RATNG15] +Patron + +[1010] +~r~Ta bagnole est sur le toit + +[1011] +~r~Ta bagnole est sur le toit + +[1012] +~r~Ta bagnole est sur le toit + +[1013] +~r~Ta bagnole est sur le toit + +[1014] +~r~Ta bagnole est sur le toit + +[JM4_10] +OK, Petit. Emmène-moi d'abord à la laverie de Chinatown, j'ai une affaire à régler. + +[JM4_11] +Cette petite laveuse ne paie pas pour sa protection. + +[JM4_12] +Et fais gaffe à la bagnole, Joey vient juste de la faire réparer. + +[JM4_13] +Alors pas de conneries, ok ? + +[KM4_11] +~g~Ramène la monnaie au casino! + +[FEF_BR2] +Retrouve-les en lisant les précédents briefings de missions. + +[TRAIN_1] +Station Kurowski + +[TRAIN_2] +Station Rothwell + +[TRAIN_3] +Station Baillie + +[SUBWAY1] +Station Portland + +[SUBWAY2] +Station Rockford + +[SUBWAY3] +Station Staunton South + +[SUBWAY4] +Terminal Shoreside + +[MEA4_2] +~r~Marty Chonks est mort! + +[SPRAY1] +Amène ton véhicule à l'atelier de peinture pour annuler ton ~h~indice de recherche~w~, ~h~répare ~w~et ~h~repeins ~w~ton véhicule. Coût -~h~ 1000$. Pour cette fois, c'est gratos. + +[JM4_A] +Ouais, je sais Tony, je l'ai défoncée en douceur. Elle miaule, tu vois ce que je veux dire ? + +[JM4_5] +Reviens plus tard et on leur donnera de la lessive à faire, leurs propres fringues avec leur sang dessus! + +[AMMU_A] +Luigi m'a dit que t'as besoin d'un calibre... + +[AMMU_B] +Joey m'a dit de t'équiper... + +[AMMU_C] +Alors tu vas aller derrière le magasin. Je t'ai laissé un 9mm dans le jardin. + +[AMMU_D] +J'ai tout ce qu'il faut en matière de sécurité. + +[AMMU_E] +Tu veux un permis aussi ? + +[AMMU_F] +J'ai pas besoin de tes papiers d'identité. Je pense qu'on peut te faire confiance. + +[DETON] +DETONATION : + +[DRIVE_A] { re3 change } +Selectionne un Uzi quand tu montes dans la voiture, regarde à gauche ou à droite et appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer. + +[DRIVE_B] { re3 change } +Selectionne un Uzi quand tu montes dans la voiture, regarde à gauche ou à droite et appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer. + +[RECORD] +~g~NOUVEAU RECORD! + +[NRECORD] +~r~PAS DE RECORD! + +[RCHELP] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ ou heurte une roue de voiture avec le véhicule télécommandé pour le faire exploser. + +[RCHELPA] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ ou heurte une roue de voiture avec le véhicule télécommandé pour le faire exploser. + +[RC_1] +Tu as 2 minutes pour faire péter autant de voitures de Diablo que tu peux! + +[RC_2] +Tu as 2 minutes pour faire péter autant de voitures de Mafia que tu peux! + +[RC_3] +Tu as 2 minutes pour faire péter autant de voitures de Yasuka que tu peux! + +[RC_4] +Tu as 2 minutes pour faire péter autant de voitures de Yardie que tu peux! + +[RC_5] +Tu as 2 minutes pour faire péter autant de voitures des Hoods que tu peux! + +[RC_6] +Tu as 2 minutes pour faire péter autant de voitures du Cartel que tu peux! + +[RAMPAGE] +RODEO! + +[RAMP_P] +RODEO REUSSI! + +[RAMP_F] +RODEO RATE + +[PAGE_00] +.. + +[PAGE_01] +Elimine ~1~ Diablos en 120 secondes! + +[PAGE_02] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_03] +Tue ~1~ Mafia en 120 secondes! + +[PAGE_04] +Tue ~1~ Triades en 120 secondes! + +[PAGE_05] +Tue ~1~ Triades en 120 secondes! + +[PAGE_06] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_07] +Dégomme ~1~ têtes de Yardies en 120 secondes! + +[PAGE_08] +Brûle ~1~ Yakuzas en 120 secondes! + +[PAGE_09] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_10] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_11] +Bute-moi ~1~ Yardies en 120 secondes! + +[PAGE_12] +Flambe ~1~ Yakuzas en 120 secondes! + +[PAGE_13] +Explose ~1~ Yardies en 120 secondes! + +[PAGE_14] +Grille-moi ~1~ Colombiens en 120 secondes! + +[PAGE_15] +Eclate ~1~ Hoods en 120 secondes! + +[PAGE_16] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_17] +Eclate ~1~ Colombiens avec une voiture en 120 secondes! + +[PAGE_18] +Détruis ~1~ véhicules en 120 secondes! + +[PAGE_19] +Fais sauter ~1~ têtes de Colombiens en 120 secondes! + +[PAGE_20] +Décapite ~1~ Hoods en 120 secondes! + +[JM1_A] +Hé, je m'fais chier, quand est-ce que tu me sautes ? + +[JM1_B] +Ça va pas tarder mon coeur, je dois juste m'occuper d'un truc. + +[JM1_C] +J'ai un petit boulot pour toi, mon gars. + +[JM1_D] +Les frères Forelli me doivent du fric depuis trop longtemps + +[JM1_E] +et ils ont besoin qu'on leur apprenne un peu ce que c'est que le respect. + +[JM1_F] +Forelli Grosses Babines est en train de s'empiffrer au Bistro de St. Marks, + +[JM1_G] +alors tu lui prends sa tire et tu l'amènes à l'atelier de 8-Ball, à Harwood. + +[JM1_H] +Tu connais 8-Ball, pas vrai ? + +[JM1_I] +Une fois qu'il l'a truffée avec une bombe, tu remets la voiture où tu l'as trouvée. + +[JM1_J] +Et puis tu t'assois et tu regardes le spectacle. + +[JM1_K] +Mais grouille, il va mettre trois plombes à bouffer. + +[CAT2_A1] +Viens là, petite pute! + +[CAT2_A] +La question est : tu es venu pour sauver Maria ou pour me récupérer ? + +[CAT2_B] +Eh bien, j'ai des infos pour toi, + +[CAT2_B2] +te tuer sera un vrai plaisir mais sortir avec toi faisait partie du business. + +[CAT2_C] +Tu estas muy peccino amigo! + +[CAT2_D] +Lance-moi l'argent + +[CAT2_E] +T'as été très occupé! + +[CAT2_E2] +Mais tu n'as rien appris, on ne peut pas me faire confiance. + +[CAT2_E3] +Descends cet idiot. + +[CAT2_J] +Fais-moi voler ça! + +[HM5_1] +Salut. D-Ice a dit que tu venais. Y'a des règles : juste des battes, pas de flingue, pas de bagnole. + +[HM5_5] +On se bat pour le respect, tu piges ? + +[HELP14] +Pour ramasser les armes, marche dessus. Tu ne peux pas en récupérer depuis un véhicule. + +[CRUSH] +Gare-toi sur l'emplacement indiqué et sors de la voiture. La voiture sera alors compressée. + +[DIAB2_B] +Un gang de bons à rien m'a menacé de me couper mon outil de travail si je ne leur paye pas un impôt. + +[DIAB2_C] +Ils ont menacé le mauvais gars, amigo. + +[DIAB2_D] +Je sais qu'ils ont un faible pour les glaces. + +[DIAB2_E] +Va chercher la bombe que j'ai planquée à Harwood + +[DIAB2_F] +et pique la camionnette du vendeur de glaces pendant sa tournée. + +[DIAB2_G] +Attire ces abrutis vers leur destinée fatale avec la clochette. + +[DIAB2_H] +Ils se planquent dans un entrepôt à Atlantic Quays. + +[DIAB3_A] +Des prétentieux de la Triade ont volé ma magnifique bagnole la nuit dernière, + +[DIAB3_B] +ils l'ont bousillée et l'ont brûlée. + +[DIAB3_C] +Quelques-uns de mes plus précieux objets étaient dans le coffre : + +[DIAB3_D] +des collectors irremplaçables! + +[DIAB3_E] +J'ai planqué une arme à la lisière de Chinatown. + +[DIAB3_F] +Prends-la et montre à ces vandales de la Triade qu'El Burro est en colère! + +[DIAB3_1] +TUE 25 TRIADES + +[DIAB4_A] +Un petit malin a piqué un camion plein de mes dernières publications fraîchement imprimées. + +[DIAB4_B] +Mais cet idiot camé à la Spank a laissé les portes de la camionette ouvertes + +[DIAB4_C] +et toutes les belles photos + +[DIAB4_D] +de ma dernière production pour adultes, sont semées à travers Liberty! + +[DIAB4_E] +Prends le camion et suis la piste des magazines Le Taureau se fait Suelen, volumes 1, 2 et 3. + +[DIAB4_F] +Dès que t'en trouves un, ramasse-le. + +[DIAB4_G] +Quand tu auras retrouvé ce voleur bourré à la Spank, descends-le! + +[DIAB4_H] +Après, tu iras livrer les magazines porno à XXX Mags dans Le Quartier Rouge. + +[DIAB4_1] +~g~Amène la camionnette à l'arrière de XXX Magazines. + +[HM1_E] +Je veux que tu montres à ces assoiffés de sang comment ça marche une vraie descente. + +[HM1_H] +Fais-moi déguerpir tous ces 'Nines'! + +[HM2_A] +Ces Nines me poussent à bout. + +[HM2_B] +Ces salauds ont des voitures blindées et maintenant ils dealent de la SPANK... + +[HM2_C] +et ils poussent à la conso chez nous sans être inquiétés. + +[HM2_D] +Y'a une bagnole garée plus haut. + +[HM2_E] +Y'a des trucs dedans pour mettre ces abrutis hors d'état de nuire... + +[HM3_A] +Y'en a qui ont piégé ma bagnole pour qu'elle saute. + +[HM3_B] +Si je perds ma bagnole, ma réputation est foutue. + +[HM3_C] +Va chercher ma caisse et amène-la au garage en haut de St. Marks, t'as pigé ? + +[HM3_D] +Laisse-les faire, laisse-les s'occuper de la bombe. + +[HM3_E] +L'horloge tourne et le cablage est un vrai bordel. + +[HM3_F] +Un nid de poule de trop et ça peut péter. + +[HM3_G] +Maintenant grouille! + +[HM4_A] +Un vol de la réserve fédérale s'est crashé à l'atterrissage, à l'aéroport Francis. + +[HM4_B] +Y'a du platine partout sur la piste. + +[HM4_C] +Prends une bagnole et va ramasser tout ce que tu peux. + +[HM4_F] +Tu peux balancer les lingots dans un de mes garages. + +[HM4_G] +Ce platine, ça pèse le poids d'un âne mort et ça va ralentir ta bagnole. + +[HM4_H] +Alors fais plusieurs voyages au garage. + +[HM5_A] +Les Nines se sont faits massacrer... + +[HM5_B] +mais ils veulent toujours la guerre. + +[HM5_C] +Ils sont d'accord pour un duel. + +[HM5_D] +Un de leurs gangs contre 2 des nôtres, ou plutôt + +[HM5_E] +deux des vôtres. + +[HM5_F] +J'irais bien mais... + +[HM5_G] +je suis en conditionnelle pour encore trois mois. + +[HM5_H] +Tu vois ce que je veux dire ? + +[HM5_I] +Va voir mon petit frère, + +[HM5_J] +il te montrera où il faut que t'ailles. + +[MEA1_B] +Mon nom est Chonks, Marty Chonks. + +[MEA1_C] +Je dirige l'usine de pâtée pour chiens juste à coté. + +[MEA1_D] +J'ai des problèmes de fric, mais qui n'en a pas, hein ? + +[MEA1_E] +Je dois rencontrer mon banquier plus tard. + +[MEA1_F] +C'est un sale voleur qui arrête pas d'augmenter les mensualités de mon emprunt pour s'en foutre une part dans la fouille. + +[MEA1_G] +Prends ma bagnole, va le chercher et ramène-le ici. + +[MEA1_H] +J'ai une petite surprise pour cette sangsue! + +[MEA2_A] +J'ai engagé des cambrioleurs pour braquer mon appart... + +[MEA2_C] +Ces bâtards menacent de tout dire à la compagnie d'assurance, + +[MEA2_D] +si je les arrose pas plus. + +[MEA2_E] +T'y crois toi ? + +[MEA2_F] +J'ai laissé une voiture dans l'usine. + +[MEA2_G] +Prends-la et va les chercher chez eux dans le Le Quartier Rouge. + +[MEA2_H] +Et puis tu les ramènes à l'usine, comme ça je vais pouvoir leur expliquer le point de vue de Marty. + +[MEA3_A] +si je trouve pas du cash tout de suite, mon affaire va couler. + +[MEA3_B] +Ma femme a une assurance-vie et tout ce qu'elle a jamais fait pour moi, c'est un trou dans mon budget. + +[MEA3_C] +J'ai laissé la voiture à sa place. + +[MEA3_D] +Va checher ma femme chez la manucure Classic Nails et ramène-la à l'usine. + +[MEA4_A] +Putain, je suis dans la merde! + +[MEA4_B] +Ben, ma femme voyait un mec à qui je dois de l'argent. + +[MEA4_C] +Il est est très fâché et lui, tout ce qu'il veut, c'est récupérer son pognon! + +[MEA4_E] +Il pense que je vais le rembourser... + +[MEA4_F] +mais moi je pense plutôt que... + +[MEA4_G] +les chiens de Liberty vont devoir s'habituer à une nouvelle pâtée ce mois-ci! + +[WELCOME] +BIENVENUE A + +[HM1_2] +~g~Trouve une bagnole et souviens-toi que seuls les meurtres à l'Uzi sont pris en considération! + +[HELP8_B] +Appuie sur la~h~ touche ~k~~PED_SNIPER_ZOOM_IN~ ~w~pour faire un ~h~zoom avant ~w~avec la lunette et sur la ~h~touche ~k~~PED_SNIPER_ZOOM_OUT~~w~ pour faire un ~h~zoom arrière~w~. + +[LRQC_1] +Asuka et moi, on doit parler, hein, + +[LRQC_2] +alors va faire un tour, ok ? + +[LRQC_3] +T'auras besoin d'un endroit pour te planquer. + +[LRQC_4] +Il y a un entrepôt au bord de Belleville qui te conviendra. + +[LRQC_5] +Reviens ici dans mon Condo quand tu es prêt + +[LRQC_6] +et on aura une petite conversation. + +[JM6_5] +~g~Il te faut une caisse pour t'enfuir, imbécile! + +[JM2_F] +Si t'as besoin d'un calibre, va derrière le AmmuNation, en face du métro. + +[LOVE4_7] +~g~Il y a un chantier à l'île Staunton . Ils ont peut-être amené la marchandise là-bas. + +[LOVE4_8] +~G~Tu auras besoin d'une voiture pour ouvrir le garage. + +[TSCORE] +GAINS : ~1~$ + +[AM1_9] +~r~Salvatore est retourné au club de Luigi! + +[AM1_6] +~g~Si tu restes autour du club de Luigi, la Mafia va te repérer! + +[TM2_3] +~g~C'est un piège! Descends-les tous! + +[FM4_1] +C'est Maria. La voiture est piégée! Rejoins-moi sur le quai au sud du pont de Callahan. + +[JM1_7] +~g~Ferme la portière! Il va nous repérer! + +[KM5_1] +~g~DEALER DESCENDU! + +[KM5_6] +~g~Tu dois tuer au moins 8 dealers de Yardie. + +[KM5_7] +~g~Ne perds pas de temps! Dès qu'ils auront dealer la SPANK, ils ne resteront pas dans les parages bien longtemps! + +[RM3_8] +~r~Cette bagnole est un leurre! + +[LM3_8] +Salut. Moi, c'est Joey. + +[LM3_9] +Luigi m'a dit que t'étais réglo alors reviens plus tard. + +[KM3_5] +~g~Klaxonne pour donner le signal. + +[LOVE7] +LA DISPARITION DE LOVE + +[LOVE2_5] +~g~Kenji est écrabouillé! Tire-toi de Newport et débarrasse-toi de la bagnole. + +[AS2_11] +~g~~1~ SUR 9! + +[GARAGE1] +~g~Sors de la bagnole et continue à pied. + +[KM3_11] +~g~Le Cartel a été attaqué et la malette n'a pas été retrouvée. + +[KM3_12] +~g~Tue tous les Colombiens, détruis les bagnoles et retrouve la malette. + +[KM3_13] +~g~Ramène la malette au casino. + +[RM5_6] +~g~Il s'est enfui! Bousille-lui son plâtre avec une bagnole ou une explosion! + +[PBOAT_1] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer avec les canons du bateau. + +[PBOAT_2] { re3 change } +Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer avec les canons du bateau. + +[DIAB1_B] +C'est El Burro des Diablos. + +[DIAB1_D] +T'es nouveau à Liberty mais tu t'es déjà fait une sacrée réputation dans la rue. + +[DIAB1_E] +Il y a une course de bagnoles qui va partir de la vieille école près du pont de Callahan. + +[DIAB1_F] +Trouve-toi une caisse et le premier qui franchit tous les points de passage, gagne le gros lot. + +[HM2_1] { re3 change } +Utilise les buggies télécommandés pour détruire les voitures blindées. Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour les faire exploser. + +[HM2_1A] { re3 change } +Utilise les buggies télécommandés pour détruire les voitures blindées. Appuie sur la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour les faire exploser. + +[HM2_2] +~r~T'as pas réussi à détruire toutes leurs voitures blindées! + +[HM2_6] +~g~Voiture blindée détruite! + +[RM3_A] +Je connais un mec important en ville, un coeur tendre, + +[RM3_H] +avec ce qu'on pourrait appeler des envies exotiques et l'argent pour les assouvir. + +[RM3_B] +Il est compromis dans une affaire et l'avocat général a des photos assez embarrassantes de lui, + +[RM3_C] +pendant une séance de tir ou quelque chose comme ça. + +[LOVE6_A] +Une leçon en affaires, mon ami. + +[LOVE6_E] +Si tu as quelque chose d'unique, le monde entier essaiera de te le subtiliser.. + +[LOVE6_C] +Les équipes spéciales de la police ont fermé le périmètre autour de mon associé et du paquet. + +[LOVE6_D] +Va là-bas, prends la camionnette et fais diversion. + +[LOVE6_F] +Occupe-les pour que nos hommes puissent s'échapper. + +[AM3_C] +Il est sûrement dans la baie à l'heure qu'il est! Vole un bateau de la police et fais-le couler! + +[FESZ_UC] +ANNULER + +[FEDS_SM] +L1,R1-CHANGER MENU + +[FEDS_AS] +;=-CHANGER SELECTION + +[FEDSAS2] +<>-CHANGER SELECTION + +[FEDS_SS] +L1,R1-CHANGER SELECTION + +[FEDSSC1] +;-DEFILEMENT RAPIDE + +[FEDSSC2] +=-ARRETER DEFILEMENT + +[MEA2_3] +~g~Ramène la voiture à l'usine. + +[RM1_3] +~r~McCaffrey s'est enfui! + +[RM1_4] +~g~T'as utilisé toutes les grenades! Va en chercher chez Ma-Gnum! + +[RM1_5] +~g~Retourne là-bas et brûle la barraque! + +[RM6_4] +~g~Va aux coffres et ramasse la came de Ray. + +[RM6_5] +~g~La CIA surveille le pont, trouve une autre route. + +[HM2_F] +Et bousille tous leurs fourgons blindés. + +[HM_4] +'LA COURSE AUX LINGOTS' + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA4_B7] +Mais si tu viens dans mon bureau... + +[MEA3_B4] +Marty veut me voir ? Ben, il a intérêt à se dépêcher parce que j'ai rendez-vous chez le coiffeur après. + +[KM3_7] +C'est un piège des Yakuzas, mec! + +[FES_LOF] +Echec du chargement. + +[P1INSA] +Port 1 carte mémoire insérée. ~1~Ko d'espace libre. ~1~Ko requis. + +[P1INSN] +Port 1 carte mémoire. Espace libre insuffisant. Veuillez effacer certains fichiers. + +[FES_SLO] +FICHIER + +[FES_ISC] +EST CORROMPUE + +[FESZ_TI] +SAUVEGARDER Z1 + +[FESZ_SA] +Sauvegarder la partie + +[P1NOIN] +Port 1. Carte mémoire non insérée. + +[P1INSE] +Port 1. Carte mémoire insérée. + +[MC_LDFL] +Echec du chargement. + +[MC_NWRE] +Redémarrage du jeu... + +[LOVE6_3] +~g~Tu as ~1~ secondes pour retourner au fourgon blindé avant de rater la mission. + +[LOVE6_4] +~r~Tu t'es débarrassé de la fausse Sécuricar! + +[HELP1] +Arrête-toi au centre du repère bleu. + +[HELP12] +Va au milieu du repère bleu pour lancer une mission. + +[HJSTAT] +Distance : ~1~.~1~m Hauteur : ~1~.~1~m Saltos :~1~ Rotation : ~1~' + +[HJSTATW] +Distance : ~1~.~1~m Hauteur : ~1~.~1~m Saltos : ~1~ Rotation : ~1~ Et quelle belle réception! + +[DIAB1_5] +TEMPS DE COURSE : + +[LOVE3_4] +~r~Tu as détruit l'avion! + +[F_FAIL1] +Mission Camion de Pompier terminée. + +[F_CANC] +~r~Mission Pompier annulée! + +[F_EXTIN] +FEUX : + +[A_COMP1] +Missions Ambulance réussies! + +[A_CANC] +~r~Mission Ambulance annulée! + +[A_COMP3] +Missions Ambulance réussies! Tu ne seras jamais fatigué en courant! + +[ATUTOR] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Ambulance. + +[ATUTOR3] +Appuie sur la ~h~touche ~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Ambulance. + +[ALEVEL] +Mission Ambulance Niveau ~1~ + +[A_FAIL1] +Mission Ambulance terminée. + +[FEST_HA] +Mission Ambulance Niveau Maximum + +[A_SAVES] +PERSONNES SAUVEES : ~1~ + +[C_KILLS] +CRIMINELS TUES : ~1~ + +[HM1_B] +J'ai un problème, ils essaient de me rouler. + +[AM2_A] +La mort de Salvatore est une bonne nouvelle, + +[AM2_A2] +tu es un tueur efficace, j'aime ça chez un homme. + +[AM2_B] +Voilà mon frère, Kenji. + +[AM2_C] +Asuka a un petit boulot pour toi, mais quand t'as fini, passe à mon casino et on va causer. + +[AM2_D] +C'est bien Kenji, faut toujours qu'il joue avec mes jouets. + +[AM2_E] +Mon indic à la police m'a dit que la Mafia surveille nos activités en ville + +[AM2_E2] +et qu'elle est à tes trousses. + +[AM2_F] +On ne peut pas reprendre nos activités, tant qu'ils sont dans les parages. + +[AM2_G] +Elimine ces fouille-merde et mets un terme à cette vendetta une bonne fois pour toutes. + +[F_START] +~g~Véhicule en feu signalé dans le secteur ~a~. Vas-y et éteins le feu. + +[AM4_1A] +Va à la cabine de parc de Belleville ouest. + +[AM4_1B] +Va à la cabine du campus de Liberty. + +[AM4_1C] +Va à la cabine du parc de Belleville sud. + +[AM4_1D] +Retrouve-moi dans les toilettes du parc. + +[HJSTATF] +Distance : ~1~ft Hauteur : ~1~ft Saltos :~1~ Rotation : ~1~- + +[HJSTAWF] +Distance : ~1~ft Hauteur : ~1~ft Saltos :~1~ Rotation : ~1~- Et quelle belle réception! + +[HM1_F] +Mais fais gaffe à toi, y'aura aussi des Jacks dans la rue qui vont croire que tu veux aussi les buter. + +[HM1_D] +Leur nom, c'est 'Nines' et leur drapeau est violet. Et chaque fois qu'ils font parader leurs couleurs... + +[HM1_G] +les 'Jacks' perdent la face. + +[MEA2_B] +et voler plein de trucs, comme ça l'assurance me remboursera. + +[TM3_H] +~w~T'as fait du bon boulot là-bas petit, c'est très bien. + +[TM3_I] +~w~Allez, on va te présenter au Don. + +[TM3_J] +~w~Hé! Luigi! + +[TM3_K] +~w~Oh tu as beaucoup manqué à mes filles, Salvatore, tu as été absent trop longtemps. + +[TM3_L] +Tu leur diras que, quand tous ces emmerdes seront terminés, + +[TM3_M] +on ira tous à la boîte pour fêter ça, ok ? + +[TM3_N] +~w~Voila mon petit. + +[TM3_N2] +~w~Comment ça va, Papa ? + +[TM3_O] +~w~Alors tu t'es enfin trouvé une femme ? + +[TM3_P] +~w~Ta mère, paix à son âme, se retournerait dans sa tombe + +[TM3_Q] +~w~de te voir sans femme. + +[TM3_R] +~w~Je sais 'Pa, j'y travaille. + +[TM3_S] +~w~Toni! Comment va ta mère ? + +[TM3_T] +~w~C'est une femme bien. Forte. De Florence. + +[TM3_U] +~w~Ça va... elle va bien. + +[TM3_V] +~w~Très bien. Bon les gars, entrez pendant que je parle à notre nouveau venu. + +[TM3_W] +~w~Je ne vois que des bonnes choses pour toi, mon petit... + +[RM1_A] +Cet enfoiré de McAffrey a accepté plus de pots-de-vin que n'importe qui. + +[RM1_B] +Il pense qu'il ne va pas être poursuivi s'il donne des preuves aux condés. + +[RM1_C] +Il a balancé! + +[RM4_B] +Faut qu'on le réduise au silence définitivement. + +[RM4_E] +Je le veux dormant avec les poissons, pas en train de les bouffer. + +[LOVE3_B] +Ce soir en approchant de l'aéroport de Liberty, un petit avion passera au dessus de la baie. + +[LOVE4_D] +Malheureusement, les autorités du port ont saisi l'avion et l'ont mis en pièces + +[LOVE4_H] +avant que je puisse intervenir et ça m'a coûté cher. + +[LOVE4_E] +Traverse le pont et va à l'aéroport International Francis. + +[GTAB_A] +Hé, débarrasse-toi de ça. On ne sait pas ce que c'est + +[GTAB_B] +mais il a l'air d'y tenir beaucoup, donc ça doit valoir quelque chose. + +[GTAB_C] +Qui est-ce qui... + +[GTAB_D] +TOI! + +[GTAB_E] +Hé calme-toi amigo! De nada! De nada! + +[GTAB_F] +Je t'ai laissé à moitié mort dans le caniveau! + +[GTAB_G] +Ne tire pas amigo. Pas de problèmes. On est tous amis. Tiens, prends ça. + +[GTAB_H] +Arrête de faire la tapette! + +[GTAB_I] +On n'a pas le choix, bébé! + +[GTAB_J] +On a toujours le choix, espèce d'abruti! + +[GTAB_K] +Je m'excuse pour cette pute hystérique mec, elles sont toutes les mêmes... por favor ?? + +[GTAB_L] +Alors la pute s'est barrée. + +[GTAB_M] +Mais tu m'as fait une fleur, + +[GTAB_N] +tu n'es pas le seul à avoir à régler des comptes avec le Cartel, + +[GTAB_O] +ce cafard a tué mon frère! + +[GTAB_P] +Je n'ai jamais tué de Yakuza! + +[GTAB_Q] +MENTEUR! On a tous vu l'assassin du Cartel. + +[GTAB_R] +On va tous vous retrouver et vous descendre, vous, les chiens de Colombiens! + +[GTAB_S] +Je vais travailler avec votre ami ici, pour obtenir des informations et un peu de plaisir. + +[GTAB_T] +Toi, tu reviens plus tard, je suis sûr que je vais avoir besoin de tes services. + +[GTAB_U] +S'il te plait amigo, ne me laisse pas avec elle, c'est une minette psychotique! Amigo ? Hé AMIIIGO!!!... Aïïïïïïïeeeeee! + +[LOVE5_A] +Tu as prouvé que tu étais un bon investissement, ce qui est rare en ces jours de récession. + +[KM3_1] +~g~Le Cartel s'attend à ce qu'un des Yardies vole une voiture de Yardie! Va vers le Nord, tu en trouveras une à Newport. + +[LOVE1_1] +~g~Va piquer une voiture du gang des Colombiens, comme ça tu pourras rentrer dans leur planque. Dirige-toi vers le Nord, tu en trouveras une à Fort Stanton. + +[FM1_Q1] +~w~Vous voulez vous amuser ? Un petit... hmm ? Du Spank ? + +[FM1_R] +~w~Salut Chico. Non, comme d'habitude. + +[FM1_T] +~w~Merci Chico. A plus tard. + +[FM1_W] +~w~Ok Fido, tu attends ici et tu surveilles la caisse pendant que je vais m'éclater. + +[FM1_X] +~w~Ok Fido, tirons-nous d'ici. Ouuulaa! + +[FM1_Q] +~w~Hé Maria! C'est ma plus belle jument! + +[FM1_S1] +~w~Tu devrais passer à la fête de l'entrepôt à l'est d'Atlantic Quays. + +[FM1_U] +~w~Gracias et éclate-toi. C'est de la bombe. + +[FM1_V] +~w~Allez Fido, on va aller faire un tour à cette fête! + +[FM1_SS] +~r~SCANNER : ~w~Quatre-Cinq à toutes les unités : rejoignez les stups à Atlantic Quays... + +[LOVE6_B] +Même s'ils n'ont pas la moindre idée de sa vraie valeur. + +[TM3_A1] +~r~Joey s'est fait avoir! + +[TM3_A2] +~r~Joey et Luigi se sont fait descendre! + +[TM3_A3] +~r~Joey, Luigi et Toni se sont fait buter! + +[FM4_2] +Salvatore pense qu'on le court-circuite, + +[FM4_3] +alors il voulait te donner au Cartel pour pouvoir faire un deal. + +[FM4_4] +Je pouvais pas le laisser faire, je veux dire, + +[FM4_4B] +tout ça, c'est de ma faute... parce que je lui ai dit qu'on était ensemble. + +[FM4_5] +Me demande pas pourquoi. J'en sais rien. + +[FM4_6] +T'es sur la liste rouge dans la Mafia et je veux me tirer d'ici, moi aussi. + +[FM4_6B] +J'ai vu trop de crimes. Trop de sang! + +[FM4_7] +C'est une amie à moi Ok, une vieille copine... Elle s'appelle Asuka, on peut lui faire confiance. + +[FM4_8] +Allez, on a assez causé. + +[FM4_9] +On ferait mieux de se barrer avant d'avoir tous ces Italiens hystériques aux fesses. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +CHARACTERS + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERS + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[J_EP] +PRODUCTEUR EXECUTIF + +[N_EP] +SAM HOUSER + +[J_PROD] +PRODUCTEUR + +[N_PROD] +LESLIE BENZIES + +[J_AD] +DIRECTEUR ARTISTIQUE + +[N_AD] +AARON GARBUT + +[J_TD] +DIRECTEURS TECHNIQUES + +[N_TD1] +OBBE VERMEIJ + +[N_TD2] +ADAM FOWLER + +[J_COD] +ENCODEURS + +[N_COD1] +ALEXANDER ROGER + +[N_COD2] +GRAEME WILLIAMSON + +[N_COD3] +MARK HANLON + +[N_COD4] +ALAN CAMPBELL + +[N_COD5] +RAYMOND USHER + +[N_COD6] +ANDRZEJ MADAJCZYK + +[J_ART] +INFOGRAPHISTES + +[N_ART1] +ADAM COCHRANE + +[N_ART2] +ALISDAIR WOOD + +[N_ART3] +GARY MCADAM + +[N_ART4] +ANDREW SOOSAY + +[N_ART5] +KEIRAN BAILLIE + +[J_AUTO] +CONCEPTION AUTOMOBILE + +[N_AUTO] +PAUL KUROWSKI + +[J_CHAR] +PERSONNAGES + +[N_CHAR] +IAN MCQUE + +[J_ANIM] +ANIMATION ET REALISATION + +[N_ANIM1] +ALEX HORTON + +[N_ANIM2] +NAVID KHONSARI + +[N_ANIM3] +LEE MONTGOMERY + +[J_SND] +CONCEPTION SON + +[N_SND1] +ALLAN WALKER + +[J_SCR] +MUSIQUE + +[N_SCR1] +CRAIG CONNER + +[N_SCR2] +STUART ROSS + +[J_DSGN] +CONCEPTION + +[N_DSGN1] +CRAIG FILSHIE + +[N_DSGN2] +WILLIAM MILLS + +[N_DSGN3] +CHRIS ROTHWELL + +[N_DSGN4] +JAMES WORRALL + +[J_WRT] +SCENARIO + +[N_WRT1] +JAMES WORRALL + +[N_WRT2] +DAN HOUSER + +[N_WRT3] +PAUL KUROWSKI + +[J_IT] +ASSISTANCE TECHNIQUE + +[N_IT1] +LORRAINE ROY + +[N_IT2] +CHRISTINE CHALMERS + +[J_IQA] +RESPONSABLE DES TESTS + +[N_IQA1] +CRAIG ARBUTHNOTT + +[LEAD_T] +TESTEURS PRINCIPAUX + +[N_IQA2] +JOHN HAIME + +[N_IQA3] +NEIL CORBETT + +[N_IQA4] +ANDY DUTHIE + +[TEST] +TESTEURS + +[N_IQA5] +GRAEME JENNINGS + +[N_IQA6] +DAVID MURDOCH + +[N_IQA7] +DAVID BEDDOES + +[N_IQA8] +EDWIN SMITH + +[N_IQA9] +MARK FLETT + +[N_IQ10] +MICHAEL SUTHERLAND + +[J_EQA] +ROCKSTAR NEW YORK + +[N_EQA1] +JEFF ROSA + +[LEAD_T2] +TESTEUR PRINCIPAL + +[N_EQA2] +ADAM DAVIDSON + +[N_EQA3] +JOE HOWELL + +[N_EQA4] +JOE GREEN + +[N_EQA5] +RICH HUIE + +[N_EQA6] +JEREMY POPE + +[N_EQA7] +KAHLEEM POOLE + +[N_EQA8] +HAKLIN NG + +[N_EQA9] +MIKE HONG + +[N_EQA10] +BRIAN PLANAR + +[N_EQA11] +JAMEEL VEGA + +[CINCAM] +Caméra Cinématique + +[KM1_13] +Amène cette bagnole au garage! + +[KM3_14] +~r~Tu t'es fait repérer, le marché est annulé! + +[EBAL_H] +Attends-moi ici pendant que je vais aller parler à luigi. + +[EBAL_M] +Rappelle-toi que personne n'embrouille mes filles! + +[LM2_F] +Tu lui prends sa caisse et tu la repeins. + +[LM2_D] +Ben voilà, prends-le. + +[LM1_9] +Salut, je m'appelle Misty. + +[LM4_A] +Y'a des connards de Diablos qui font tapiner leurs sales putes sur mon territoire. + +[FM2_B] +On a une balance! + +[FM2_C] +Il fait pas le maquereau,ni le dealer, donc il doit parler. + +[FM3_CC] +~w~Reviens quand t'auras le pognon, frangin. + +[FEDS_AM] +<>-CHANGER MENU + +[LOVE5_5] +~r~T'as pas su protéger le camion! + +[RM6_6] +~r~Ray est mort! + +[RM6_7] +. ~r~Ray a raté son vol. + +[RM6_8] +~g~Tu as laissé tomber Ray, retourne le chercher. + +[FM1_10] +~g~Tu as laissé tomber Maria, retourne la chercher. + +[LOVE4_9] +~r~L'avion a été détruit! + +[LOV4_10] +~r~La seule piste pour retrouver le paquet est partie en fumée! + +[KM2_D] +Cela va sans dire, on va lui en faire cadeau, pour éponger la dette que j'ai envers lui. + +[KM4_B] +Les mecs qui ont la chance de bénéficier de notre protection, font leurs comptes aujourd'hui. + +[KM2_E] +Tu dois trouver les voitures qui sont sur cette liste et les livrer au garage derrière le parking de Newport. + +[FM3_8I] +~w~Trouve une bonne place et je rentrerai quand tu tireras le premier coup. + +[LOVE1_B] +L'expérience m'a appris que quelqu'un comme toi peut être très loyal si on le paye bien, + +[LOVE1_H] +mais ca fait des jaloux. + +[LOVE1_C] +Un vieux bridé que je connais, un homme de confiance, + +[LOVE1_I] +est retenu en otage par des Sud-Américains à Aspatria. + +[MEA4_D] +J'ai accepté de le voir... + +[MEA4_B4] +C'est Marty qui t'envoie, hein ? D'accord, je vais lui apprendre, moi, le sens des affaires. + +[MEA4_B5] +Carl, salut! Euh... j'ai besoin de plus de temps pour ton pognon. + +[MEA1_B4] +C'est M. Chonks qui t'envoie, n'est-ce pas ? Allons lui rendre visite. + +[HM5_6] +Allons fracasser des crânes... + +[LOVE1_5] +~g~Arrête de tourner en rond, trouve une bagnole des Colombiens et sauve l'associé de Love. + +[AS1_D] +~w~T'as qu'à faire l'appât et attirer les escadrons de la mort dans la Crique de Pike. + +[AS1_E] +~w~Mes hommes les attendront là-bas. + +[AS2_C] +~w~Le Cartel a une couverture : l'usine de café Kappa. + +[AS2_E] +~w~On a pas d'autre choix que de neutraliser ces charettes à drogue. + +[AS2_F] +~w~Fais-en des allumettes!! + +[AS2_A1] +~w~Miguel a sûrement un peu de cette fameuse énergie latine. + +[AS2_A2] +~w~Je suis crevé. + +[SIREN_3] +Pour activer la sirène, appuie sur la ~h~touche ~k~~VEHICLE_HORN~~w~. + +[SIREN_4] +Pour activer la sirène, appuie sur la ~h~touche ~k~~VEHICLE_HORN~~w~. + +[AS3_C] +~w~Oulala! C'est quoi ce truc jaune ? + +[AS3_C1] +~w~Salut ma poule. + +[AS3_F] +~w~Elle se classe tout de suite dans les meilleures, cette nana. + +[AS3_F1] +~w~Elle s'est arrangée pour dérober ce joli petit bijou à notre invité. + +[AS3_G] +~w~Y'a un avion qui arrive à l'aéroport international Francis dans 2 heures. + +[AS3_G1] +~w~Il est rempli de poison de Catalina. + +[AS3_H] +~w~Tu peux éviter la sécurité de l'aéroport en prenant un bateau jusqu'aux bouées lumineuses d'approche. + +[AS3_H1] +~w~Et dès que l'avion descends, tu l'explose! + +[AS3_I] +~w~Récupère la marchandise au milieu des débris. + +[AS3_J] +~w~Maintenant, fais attention ma poule! + +[AS3_K] +~w~Essaie avec l'huile pimentée... + +[RM2_F1] +Ces Colombiens seront là d'une minute à l'autre! + +[RM2_K] +Merde ils sont là!! Feu à volonté!! + +[LOVE2_7] +~g~Maintenant, largue la bagnole! + +[LOVE2_8] +~g~Dégage de Newport! + +[AM1_F] +Salvatore Leone va partir de chez Luigi vers ~1~:~1~. + +[LOVE5_C] +Je veux que tu le suives et que tu fasses en sorte que lui et mon paquet arrivent à la Crique de Pike sains et saufs. + +[FESZ_SR] +Echec de la sauvegarde! Vérifie la memory card (PS2) dans la fente pour MEMORY CARD 1 et réessaie. + +[FESZ_FO] +Veux-tu formater la memory card (PS2) dans la fente pour MEMORY CARD 1? + +[FELZ_FO] +La memory card (PS2) dans la fente pour MEMORY CARD 1 n'est pas formatée. + +[FES_NOC] +Aucune memory card (PS2) dans la fente pour MEMORY CARD 1. + +[FES_LOE] +Echec du chargement! Vérifie la memory card (PS2) dans la fente pour MEMORY CARD 1 et réessaie. + +[FES_DEE] +Echec de la suppression! Vérifie la memory card (PS2) dans la fente pour MEMORY CARD 1 et réessaie. + +[FORSUC] +Formatage réussi de la memory card (PS2) dans la fente pour MEMORY CARD 1. + +[ERFOUN] +Echec du formatage de la memory card (PS2) dans la fente pour MEMORY CARD 1. + +[ERMCNP] +Aucune memory card (PS2) dans la fente pour MEMORY CARD 1. + +[SVMEM1] +Sauvegarder sur la memory card (PS2) dans la fente pour MEMORY CARD 1. + +[FORSLO] +Formater la memory card (PS2) dans la fente pour MEMORY CARD 1. + +[SLONFM] +Erreur de formatage de la Memort Card (PS2) dans la fente pour MEMORY CARD 1. + +[SLONDR] +Espace insuffisant pour sauvegarder. Insère une memory card (PS2) dotée d'au moins 500 Ko d'espace disponible, dans la fente pour MEMORY CARD 1. + +[SLNSP] +Espace insuffisant pour sauvegarder. Insère une memory card (PS2) dotée d'au moins 200 Ko d'espace disponible, dans la fente pour MEMORY CARD 1. + +[FEFD_WR] +Formatage de la memory card (PS2) dans la fente pour MEMORY CARD 1. Ne pas retirer la memory card (PS2), ni réinitialiser ou éteindre la console. + +[FES_ISF] +ABSENT + +[FES_SAG] +EXISTANT + +[SLONNO] +Aucune memory card (PS2) dans la fente pour MEMORY CARD 1. + +[SLONNF] +La memory card (PS2) dans la fente pour MEMORY CARD 1 pas formatée. + +[FESZ_FM] +La memory card (PS2) dans la fente pour MEMORY CARD 1 n'est pas formatée. Veux-tu la formater? + +[FESZ_FF] +Echec du formatage! Vérifie la memory card (PS2) dans la fente pour MEMORY CARD 1 et réessaie. + +[MCDNSP] +Espace insuffisant sur la memory card (PS2) dans la fente pour MEMORY CARD 1. 500 Ko minimum sont requis pour sauvegarder. Veux-tu commencer? (OUI ou NON) + +[MCGNSP] +Espace insuffisant sur la memory card (PS2) dans la fente pour MEMORY CARD 1. 200 Ko minimum sont requis pour sauvegarder. Veux-tu commencer ? (OUI ou NON) + +[FESZ_WR] +Sauvegarde en cours. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[FESZ_OW] +Ecrasement en cours. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[FELD_WR] +Chargement en cours. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[FEDL_WR] +Effacement en cours. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[LM2_C] +Luigi m'a dit de te donner ça, alors... + +[LM3_G] +Joey n'est pas du genre à être patient, rappelle-toi, c'est ton ticket d'entrée... + +[LM5_E] +Ramènes-en autant que tu peux avant que ces poulets aient cramé tout leur blé. + +[JM5_C] +Y'a une bagnole avec un macchabée devant le café à coté de Point Callahan. + +[RM2_B] +On a fait le Nicaragua ensemble, à l'époque où ce pays savait ce qu'il faisait. + +[RM2_C] +Un enfoiré du Cartel l'a bousculé hier et lui a dit qu'ils reviendraient aujourd'hui pour de la marchandise. + +[RM2_D1] +J'y serais bien allé moi-même mais ma vieille sciatique s'est réveillée - hhurrh hhurrh - alors, bonne chance. + +[CATINF1] +~g~Chope Catalina! + +[CATINF2] +~g~Suis l'hélico pour trouver Catalina. + +[BOATIN1] +Saute dans un bateau et appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour entrer dedans. + +[BOATIN2] +Tu peux appuyer sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~ ~w~si tu es près d'un bateau pour te glisser à l'intérieur. + +[BOATIN3] +Saute dans un bateau et appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~ ~w~pour entrer dedans. + +[BOATIN4] +Tu peux appuyer sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~ ~w~si tu es près d'un bateau pour te glisser à l'intérieur. + +[JM6] +'L'EVASION' + +[FM1] +'LE CHAPERON' + +[JM1] +'LE DERNIER REPAS DE MIKE 'BABINES' + +[FM21] +'LA BOMBE : ACTE 1' + +[FM3] +'LA BOMBE : ACTE 2' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'SOUS SURVEILLANCE' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'S.A.M.' + +[RM2] +'PENURIE D'ARMES' + +[LOVE6] +'L'APPAT' + +[LOVE1] +'LIBERATEUR' + +[RC1] +'DESTRUCTION DE DIABLO' + +[RC2] +'MASSACRE DE LA MAFIA' + +[RC3] +'CALAMITÉ AU CASINO' + +[RC4] +'RODEO DE RUMPO' + +[RM2_E1] +Je peux pas croire que ce trouillard m'ait encore laissé sans protection! + +[GREN_1] +Plus tu maintiendras la ~h~touche ~k~~PED_FIREWEAPON~~w~ enfoncée, plus tu lanceras loin la grenade. + +[GREN_2] +Plus tu maintiendras la ~h~touche ~k~~PED_FIREWEAPON~~w~ enfoncée, plus tu lanceras loin la grenade. + +[GREN_3] +Plus tu maintiendras la ~h~touche ~k~~PED_FIREWEAPON~~w~ enfoncée, plus tu lanceras loin la grenade. + +[LOVE4_G] +Mon bien t'attendra dans le hangar des douanes à l'intérieur du fuselage de l'avion. + +[KABOOM] +KABOOOM! + +[SPLAT] +DESSOUDE! + +[PANCAK] +REFROIDI! + +[SOAKED] +FLINGUE! + +[HEAD] +Radio tête + +[DBL_CLF] +Radio double FM + +[FLASHB] +Nostalgia FM + +[RISE] +Lévitation FM + +[LIPS] +Love 106 + +[CHAT] +Causette FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Echec FM + +[MSX_FM] +MSX FM + +[TUBE1] +Quand le métro ouvrira, tu pourras prendre une rame pour aller à l'île Staunton . + +[TUBE2] +Quand Shoreside Vale sera ouvert, tu pourras sortir au Shoreside Terminal pour aller à l'aéroport international Francis. + +[TUBE_2] +Pour prendre le métro, appuie sur la ~h~touche 'monter véhicule'~w~. + +[LEGAL] +~g~Enraye toute menace criminelle! + +[GA_2] +Nouveau moteur et nouvelle peinture. Les flics ne te reconnaîtront jamais! + +[LM1_8A] +Pour te faire du fric en plus, tu pourrais peut-être 'emprunter' un taxi... + +[TAXIH1] +Arrête-toi près d'un passage piéton pour prendre des passagers et emmène-les à destination avant la fin du temps imparti. + +[LM5_7] +~g~Y'a moins de quatre filles qui bossent au ~p~Bal~g~, Luigi va pas être content! + +[KM2_3] +~g~Rappelle-toi que les ~r~voitures~g~ doivent être en parfait état pour être acceptées par le ~p~garage~g~. + +[KM5_2] +~g~Un homme du gang de Yardie est hors d'état de nuire. + +[BETRA_A] +Désolé, chérie. + +[BETRA_B] +Je suis une fille ambitieuse et toi, + +[BETRA_C] +t'es juste un passe-temps. + +[HELP15] +A pied, appuie sur la ~h~touche ~k~~PED_LOOKBEHIND~~w~ pour ~h~regarder derrière~w~. + +[FEC_LB3] +Regarder derrière + +[FEC_R3] +(touche R3) + +[FES_AFO] +Cette memory card (PS2) est déjà formatée. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CHANGER SELECTION + +[FEDSAS4] +;=<> - CHANGER SELECTION + +[SPRAY_4] { re3 change } +Utilise la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer à l'aide du canon à eau. + +[SPRAY_1] { re3 change } +Utilise la ~h~touche ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer à l'aide du canon à eau. + +[AM1_10] +~g~Salvatore Leone partira de chez Luigi vers 0~1~:~1~. + +[JAILB_V] +Liberty City est en état de choc! + +[JAILB_A] +La police et les services d'urgence s'occupent des conséquences + +[JAILB_B] +d'une attaque lancée contre un convoi de la police ce matin. + +[JAILB_C] +On n'a reçu aucune info sur les prisonniers qui étaient transférés ce matin. + +[JAILB_D] +Et aucun groupe, n'a revendiqué cette attaque. + +[JAILB_E] +Le convoi a quitté le Q.G. de la police tôt ce matin... + +[JAILB_F] +pour un transfert vers le pénitencier de Liberty. + +[JAILB_G] +L'attaque a eu lieu sur le pont de Callahan, + +[JAILB_H] +laissant peu de témoins et un pont gravement endommagé. + +[JAILB_I] +On suppose que certains prisonniers sont morts dans l'explosion... + +[JAILB_J] +qui a suivi la première attaque. + +[JAILB_W] +Le professionnalisme de cette attaque a pris de court la police, + +[JAILB_K] +lorsque l'identification des prisonniers évadés a été ralentie... + +[JAILB_L] +par la découverte d'un piratage informatique simultané des bases de données du Q.G. de la police. + +[JAILB_O] +Le chantier du tunnel Porter prenant de plus en plus de retard, + +[JAILB_P] +cette catastrophe laisse Portland isolé du reste de la ville. + +[JAILB_Q] +Allez, viens! + +[JAILB_R] +Monsieur tête de noeud + +[JAILB_S] +Ca me pose aucun problème de te tuer. + +[JAILB_T] +Tu vas le regretter. + +[JAILB_U] +D'accord, d'accord. Allez, dégage! + +[JAILB_M] +Le Maire O'Donovan a fait clairement comprendre que la police considérait + +[JAILB_N] +* + +[JAILB_X] +Dans une déclaration faite ce matin, + +[FEDS_SE] +Touche / - SELECTIONNER + +[FEDS_SB] +Touche / - SELECTIONNER Touche " - RETOUR + +[TM4_A] +~w~Oh, c'est toi. Toni n'est pas là. + +[TM4_A2] +~w~Mais il a laissé une de ses lettres d'amour pour toi. + +[DIAB2_A] +J'ai commencé dans les loisirs exotiques avec rien d'autre que le contenu, pas si négligeable que ça, de mon pantalon de cuir! + +[LM5_9] +FILLES : + +[PERPIC] +Paquets cachés trouvés + +[CO_ONE] +~1~ paquet caché sur ~1~ trouvé. + +[LOVE3_3] +~g~L'avion a largué ~1~ paquets sur 6. + +[FARE11] +~g~Destination : ~w~'Chantier'~g~ de Fort Staunton. + +[GA_21] +Impossible de garer plus de véhicules dans ce garage. + +[CHEAT1] +Codes activés + +[CHEAT2] +Code d'arme + +[CHEAT3] +Code de santé + +[CHEAT4] +Code d'armure + +[CHEAT5] +Code d'indice de recherche + +[CHEAT6] +Code d'argent + +[CHEAT7] +Code de météo + +[AS1_H] +~r~T'as pas réussi à amener l'escadron de la mort dans le piège des Yakuzas! + +[FEDS_BA] +Touche " - RETOUR + +[RAMP_A] +TOUS LES RODEOS ONT ETE ACCOMPLIS! + +[USJ_ALL] +TOUTES LES CASCADES ONT ETE ACCOMPLIES! + +[FARE23] +~g~Destination : ~w~'Transport-Export'~g~ au Barrage Cochrane. + +[L_TRN_1] +Tu peux prendre le train-L à Portland. Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~descendre~w~ du train. + +[L_TRN_2] +Tu peux prendre le train-L à Portland. Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~descendre~w~ du train. + +[S_TRN_1] +Tu peux prendre le métro à Liberty. Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~descendre~w~ du train. + +[S_TRN_2] +Tu peux prendre le métro à Liberty. Appuie sur la ~h~touche ~k~~VEHICLE_ENTER_EXIT~~w~ pour ~h~monter~w~ ou ~h~descendre~w~ du train. + +[AS1_C] +~w~Il y a trois escadrons de la mort autour de Liberty et tout ce qu'ils veulent, c'est te buter! + +[AS1_G] +~r~Tous les Yakuzas sont morts! + +[JAN] +Jan + +[FEB] +Fév + +[MAR] +Mar + +[APR] +Avr + +[MAY] +Mai + +[JUN] +Juin + +[JUL] +Juil + +[AUG] +Août + +[SEP] +Sep + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Déc + +[DEFDT] +Date de sauvegarde invalide + +[BUGGY] +BUGGIES RESTANTS : + +[BONUS] +~g~BONUS ~1~$ + +[HORN1] +Appuie sur la ~h~touche L3 ~w~pour ~h~klaxonner. + +[HORN2] +Appuie sur la ~h~touche L1 ~w~pour ~h~klaxonner. + +[HORN3] +Appuie sur la ~h~touche R1 ~w~pour ~h~klaxonner. + +[LM3_1A] +Appuie sur la ~h~touche ~k~~VEHICLE_HORN~ ~w~pour ~h~klaxonner~w~ et prévenir Misty de ton arrivée. + +[LM3_1B] +Appuie sur la ~h~touche ~k~~VEHICLE_HORN~ ~w~pour ~h~klaxonner~w~ et prévenir Misty de ton arrivée. + +[LM3_1C] +Appuie sur la ~h~touche ~k~~VEHICLE_HORN~ ~w~pour ~h~klaxonner~w~ et prévenir Misty de ton arrivée. + +[RADIO_A] +Appuie sur la ~h~touche ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ pour faire défiler les ~h~stations de radio. + +[RADIO_B] +Appuie sur la ~h~touche ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ pour faire défiler les ~h~stations de radio. + +[RADIO_C] +Appuie sur la ~h~touche ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ pour faire défiler les ~h~stations de radio. + +[RADIO_D] +Appuie sur la ~h~touche ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ pour faire défiler les ~h~stations de radio. + +[FEC_EXV] +Entrer\sortir d'un véhicule + +[TAXI_M] +'TAXI DRIVER' + +[COP_M] +'POLICE' + +[FIRE_M] +'POMPIER' + +[AMBUL_M] +'AMBULANCE' + +[HJ_IS] +BONUS DE CASCADE DANGEREUSE : ~1~$ + +[HJ_PIS] +BONUS DE CASCADE DANGEREUSE PARFAITE : ~1~$ + +[HJ_DIS] +BONUS DE DOUBLE CASCADE DANGEREUSE : ~1~$ + +[HJ_PDIS] +BONUS DE DOUBLE CASCADE DANGEREUSE PARFAITE : ~1~$ + +[HJ_TIS] +BONUS DE TRIPLE CASCADE DANGEREUSE : ~1~$ + +[HJ_PTIS] +BONUS DE TRIPLE CASCADE DANGEREUSE PARFAITE : ~1~$ + +[HJ_QIS] +BONUS DE QUADRUPLE CASCADE DANGEREUSE : ~1~$ + +[HJ_PQIS] +BONUS DE QUADRUPLE CASCADE DANGEREUSE PARFAITE : ~1~$ + +[AM1_K] +Salvatore Leone partira de chez Luigi dans environ trois heures. (0~1~:~1~) + +[IMPEXPP] +Transport-Export, port de Portland. On a commandé différents véhicules. Consulte notre panneau d'affichage pour avoir plus d'infos. + +[VANHSTP] +Si t'arrives à choper d'autres Sécuricars, amène-les à notre garage dans le port de Portland. + +[EMVHPUP] +Achat de véhicules de secours neufs et d'occasion à très bons prix. Apporte-les à la grue, au nord-est du port de Portland. + +[STANDS] +ETALS RENVERSES : + +[STASH] +~g~Planque la SPANK sur le ~p~chantier! + +[MCSTNS] +Aucune memory card (PS2) dans la fente pour MEMORY CARD 1. Veux-tu commencer? (OUI ou NON) + +[LOVE3_5] +~g~L'avion est à portée. + +[LOVE3_6] +~r~La police a réussi à récupérer les paquets! + +[SIREN_1] +Pour déclencher la sirène de ce véhicule, appuie brièvement sur la ~h~touche ~k~~VEHICLE_HORN~~w~. + +[SIREN_2] +Pour déclencher la sirène de ce véhicule, appuie brièvement sur la ~h~touche ~k~~VEHICLE_HORN~~w~. + +[FM3_8C] +~w~J'ai besoin de 100 000$ pour couvrir mes dépenses, + +[MCLOAD] +Chargement des données. Ne pas retirer la memory card (PS2) de la fente pour MEMORY CARD 1, ni réinitialiser ou éteindre la console. + +[FES_GME] +Erreur de lecture sur la memory card (PS2) de la fente pour MEMORY CARD 1. Vérifie-la et réessaie. + +[FESZ_QF] +Veux-tu vraiment formater la memory card (PS2) de la fente pour MEMORY CARD 1? + +[FESZ_LS] +Chargement réussi. + +[RM3_5] +~g~Tu as ~1~ des 6 paquets de preuves. + +[LOVE3_2] +~g~Tu as récupéré tous les paquets! Rapporte-les à Donald Love. + +[LOVE4_4] +~g~Rapporte le paquet à Donald Love! + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_DSR] +Debug Streaming Requests + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_SCP] +gbShowCollisionPolys + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_SPR] +Show Ped Road Groups + +[GORLEV] +Gore Level + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[PARSHP] +Parse Heap + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[SCASSL] +Sick Fuck Selected + +[SCSCSL] +Sick Fucker Selected + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[SICSIC] +Sick Fucker + +[SICASS] +Sick Fuck + +[FEB_SAV] +Charger + +[FEP_SAV] +CHARGER PARTIE + +[AS2_12A] +~g~Quand t'auras renversé le premier étal, t'auras plus que 8 minutes avant que le Cartel prévienne ses revendeurs! + +[AS3_1A] +~g~Et maintenant, rejoins la ~b~bouée repère! + +[NOCONT] +Reconnecte une manette analogique (DUALSHOCK#) ou manette analogique (DUALSHOCK#2) au port de manette 1 pour continuer. + +[BET_JB] +TRAHI PAR CATALINA, SA PETITE AMIE, ET LAISSE POUR MORT. JUGE COUPABLE ET CONDAMNE A UNE PEINE DE PRISON, IL COMMENCE SA PEINE AU PENITENCIER DE LIBERTY. MAIS UNE SEULE ET UNIQUE PENSEE LE HANTE...... LA VENGEANCE ! + +[END_A] +Les habitants de Cedar Grove subissent les conséquences + +[END_B] +psychologiques de l'attentat qui a frappé + +[END_C] +leur quartier, hier. + +[END_D] +Un riverain, Clive Denver, a donné à la police la description + +[END_E] +d'un homme armé qu'il a vu fuir du secteur, accompagné d'une femme aux cheveux noirs. + +[END_F] +Oh, tu sais, on va bien s'amuser, parce que, tu sais, oui, tu le sais, + +[END_G] +je t'aime, hein, je t'aime de tout mon coeur, tu es si beau, si fort, + +[END_H] +tu es l'homme que j'ai toujours rêvé d'avoir à mes côtés ! + +[END_I] +Peu importe, j'en étais où, moi ? + +[END_J] +Oh, je sais plus. Mais tu sais ce que c'est, hein ? + +[END_K] +Les explosions ont retenti près des habitations, semant Le trouble dans le quartier. Les gens couraient dans tous les sens à la recherche d'un abri. + +[END_L] +Plusieurs civils ont été blessés dans la panique alors que les forces de l'ordre + +[END_M] +échangeaient des coups de feu avec un hélicoptère qui survolait le barrage. + +[END_N] +Ouais, d'ici, on a une vue imprenable sur les jardins. + +[END_O] +Lorsque l'hélico a enfin été bousillé, + +[END_P] +c'était encore plus grandiose qu'un feu d'artifices ! + +[END_Q] +On recense déjà plus de vingt morts mais + +[END_R] +la police continue à découvrir des corps sous les décombres. + +[END_S] +Les rumeurs selon lesquelles les morts appartenaient au cartel des Colombiens + +[END_T] +n'ont pas été officiellement démenties. + +[END_U] +Il n'y a toujours aucune piste qui expliquerait la raison de ce massacre. + +[END_V] +Je me suis cassé un ongle et ma mise en plis est foutue ! Tu le crois, ça ? + +[END_W] +Ca m'avait coûté 50 dollars... + +[PAPER1] +UN CRIMINEL TRAHI PAR SA PETITE AMIE ET COMPLICE. LES JURES RECONNAISSENT, LE VOLEUR ARME, COUPABLE A L'UNANIMITE! + +[PAPER2] +LOVE, CONDAMNE A DIX ANS FERME ! + +[FEB_CPC] +Configuration des commandes + +[FEC_PED] +Commandes à pied + +[FEC_VEH] +Commandes des véhicules + +[FEC_FPR] +Commandes en vue subjective + +[FEC_CMM] +Commandes principales + +[FEC_PWL] +Aller à gauche + +[FEC_PWR] +Aller à droite + +[FEC_PWF] +Avancer + +[FEC_PWT] +Avancer vers caméra + +[FEC_PLB] +Vue arrière + +[FEC_PFR] +Tirer + +[FEC_CLE] +Défilement Gauche des armes + +[FEC_CRI] +Défilement Droite des armes + +[FEC_LKT] +Verrouiller cible + +[FEC_PJP] +Saut à pied + +[FEC_PSP] +Sprint à pied + +[FEC_PSH] +Tir à pied + +[FEC_TLF] +Cible suivante Gauche + +[FEC_TRG] +Cible suivante Droite + +[FEC_CCM] +Centrer caméra derrière joueur + +[FEC_SZI] +Fusil à lunette zoom avant + +[FEC_SZO] +Fusil à lunette zoom arrière + +[FEC_LKL] +Regarder à gauche en vue subjective + +[FEC_LRT] +Regarder à droite en vue subjective First Person Look Right + +[FEC_LUP] +Regarder en haut en vue subjective + +[FEC_LDN] +Regarder en bas en vue subjective + +[FEC_LBH] +Regarder derrière le véhicule + +[FEC_LLF] +Regarder à gauche du véhicule + +[FEC_LRG] +Regarder à droite du véhicule + +[FEC_HRN] +Klaxon + +[FEC_HBR] +Frein à main + +[FEC_ACL] +Accélérer + +[FEC_BRK] +Freiner + +[FEC_TSM] +Activer/Désactiver sous-missions + +[FEC_CRD] +Changer la station de radio + +[FEC_ENT] +Entrer/Sortir d'un véhicule + +[FEC_WPN] +Tirer + +[FEC_PAS] +Pause + +[FEC_FPO] +Changer d'arme en vue subjective + +[FEC_SMS] +Afficher/Masquer curseur + +[FEC_CMS] +Changer de mode de caméra + +[FEC_TSS] +Faire une capture d'écran + +[FEN_NET] +Réseau + +[FEN_CON] +Connexion + +[FEN_GAM] +Trouver partie + +[FEN_TYP] +Type de partie + +[FEN_TY0] +Deathmatch + +[FEN_TY1] +Furtif en Deathmatch + +[FEN_TY2] +Deathmatch par équipes + +[FEN_TY3] +Furtif en Deathmatch par équipes + +[FEN_TY4] +Planquer l'argent + +[FEN_TY5] +Capturer le drapeau + +[FEN_TY6] +Rat Race + +[FEN_TY7] +Domination + +[FEN_NAM] +Nom : + +[FEN_GNA] +Nom partie : + +[FEM_MAP] +Choisir carte + +[FEN_PLS] +Réglages joueur + +[FEN_PLC] +Couleur joueur + +[FEM_MA0] +Liberty City + +[FEM_MA1] +Le Quartier Rouge + +[FEM_MA2] +Chinatown + +[FEM_MA3] +La Tour + +[FEM_MA4] +Le Dépotoir + +[FEM_MA5] +Le Parc Industriel + +[FEM_MA6] +Les Docks + +[FEM_MA7] +Staunton + +[FEC_EMS] +Touches clavier uniquement + +[FEC_DBG] +Menu Debug + +[FEC_TGD] +Alterner manette jeu/debug + +[FEC_TDO] +Désactiver caméra debug + +[FEC_IVH] +Inverser souris horizontale + +[FEC_MSL] +BGS + +[FEC_MSM] +BMS + +[FEC_MSR] +BDS + +[FEC_QUE] +??? + +[FEC_TWO] +Deux touches clavier au maximum + +[FEC_UMS] +Boutons souris uniquement + +[FEC_OMS] +Un bouton souris au maximum + +[FEC_UJS] +Un bouton joystick au maximum + +[FEC_OJS] +Un bouton joystick maximum par action + +[FEC_PTL] +Utiliser verrouillage de cible avec commande de tir gauche + +[FEC_PTR] +Utiliser verrouillage de cible avec commande de tir droite + +[FEC_LBC] +Utiliser regarder gauche avec regarder droite + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Impossible de mettre en pause en multijoueur. Appuyez deux fois pour quitter ! + +[FEM_SL1] +Emplacement 1 libre + +[FEM_SL2] +Emplacement 2 libre + +[FEM_SL3] +Emplacement 3 libre + +[FEM_SL4] +Emplacement 4 libre + +[FEM_SL5] +Emplacement 5 libre + +[FEM_SL6] +Emplacement 6 libre + +[FEM_SL7] +Emplacement 7 libre + +[FEM_SL8] +Emplacement 8 libre + +[FEM_MM] +MENU PRINCIPAL + +[FEM_SNG] +Nouvelle partie + +[FEM_QTW] +Quitter + +[FEQ_SRE] +Etes-vous sûr de vouloir quitter ? Votre progression depuis la dernière sauvegarde sera perdue. Continuer ? + +[FEQ_SRW] +Etes-vous sûr de vouloir quitter la partie ? + +[FEG_SRV] +SERVEUR + +[FEG_MAP] +CARTE + +[FEG_PLY] +JOUEURS + +[FEG_TYP] +TYPE + +[FEG_PNG] +PING + +[FET_FG] +TROUVER PARTIE + +[FET_SP] +SOLO + +[FET_MP] +MULTIJOUEUR + +[FET_HG] +CREER UNE PARTIE + +[FET_PS] +CONFIG. JOUEURS + +[FET_CON] +CONNEXION + +[FET_AUD] +CONFIG. AUDIO + +[FET_DIS] +CONFIG. AFFICHAGE + +[FET_LAN] +CHOIX LANGUE + +[FET_LG] +CHARGER PARTIE + +[FET_DG] +SUPPRIMER PARTIE + +[FET_NG] +NOUVELLE PARTIE + +[FET_SG] +SAUVEGARDER PARTIE + +[FET_MAP] +CHOISIR CARTE + +[FET_GT] +TYPE DE PARTIE + +[FET_CTL] +CONFIG. PERIPHERIQUE + +[FET_OPT] +OPTIONS + +[FET_QG] +QUITTER PARTIE + +[FET_STA] +STATISTIQUES + +[FET_BRE] +BRIEFINGS + +[FEC_WAR] +Avertissement + +[FEC_OKK] +O.K. + +[FED_CON] +Confirmation de suppression de fichier + +[FES_SSC] +Sauvegarde de la partie effectuée + +[DEL_FNM] +Suppression du fichier effectuée + +[PCLOAD] +Chargement des données du fichier + +[PCRESRT] +Redémarrage de Grand Theft Auto III + +[FEC_DLF] +Erreur lors de suppression + +[FEC_SVU] +Erreur lors de la sauvegarde + +[FEC_LUN] +Erreur lors du chargement. Fichier corrompu, veuillez le supprimer. + +[FEN_PLA] +Nombre de joueurs : + +[FET_NON] +AUCUNE PARTIE DISPONIBLE + +[FET_SFG] +RECHERCHE DE PARTIES... + +[FET_SRT] +TRI DES PARTIES... + +[FEF_LAN] +RESEAU + +[FEF_INT] +INTERNET + +[FET_REF] +Rafraîchir + +[FET_FIL] +Filtre + +[FET_JG] +Rejoindre + +[FEC_NTW] +Talk To Network + +[FEC_ESR] +Utilisation restreinte de la touche Echap + +[FEC_GSL] +Show head bob: + +[FIL_FLT] +FILTRER LISTE DES PARTIES + +[FET_SAN] +NOUVELLE PARTIE + +[FIL_MAP] +Carte : + +[FIL_SRV] +Serveur : + +[FIL_TYP] +Type de partie : + +[FIL_SPC] +Parties avec espace disponible ? + +[FIL_PNG] +Ping : + +[FEN_UKH] +Hôte inconnu + +[FEN_UKM] +Carte non trouvée + +[FEN_UKT] +Type de partie non trouvé + +[FEN_NCI] +VOUS N'ETES PAS CONNECTE A INTERNET + +[FET_PAU] +MENU PAUSE + +[FET_SGA] +COMMENCER PARTIE + +[FEC_SGJ] +Régler joystick + +[FEC_PAD] +Manette + +[FEC_JOY] +Joystick + +[FEC_WHL] +Volant + +[FEC_CNT] +Type de périphérique : + +[FET_APL] +APPLIQUER + +[FES_CSA] +Sélectionnez une apparence dans la liste suivante : + +[FES_SKN] +NOM DE L'APPARENCE + +[FES_DAT] +DATE + +[FES_NON] +AUCUNE APPARENCE DISPONIBLE + +[FEA_FM9] +LECTEUR MP3 + +[FESZ_QZ] +Etes-vous sûr de vouloir sauvegarder cette partie ? + +[FES_CGA] +Emplacements disponibles : + +[FES_SCG] +Sauvegarder la partie actuelle ? + +[FES_LCG] +Charger la partie et continuer à jouer ? + +[FEC_FIR] +Tirer + +[FEC_NWE] +Arme suivante + +[FEC_PWE] +Arme précédente + +[FEC_FOR] +Avant + +[FEC_BAC] +Arrière + +[FEC_LEF] +Gauche + +[FEC_RIG] +Droite + +[FEC_ZIN] +Zoom avant + +[FEC_ZOT] +Zoom arrière + +[FEC_EEX] +Entrer+sortir + +[FEC_RAD] +Radio + +[FEC_SUB] +Sous-mission + +[FEC_CMR] +Changer caméra + +[FEC_JMP] +Sauter + +[FEC_SPN] +Sprint + +[FEC_HND] +Frein à main + +[FEC_TUL] +Tourelle gauche + +[FEC_TUR] +Tourelle droite + +[FEC_LOL] +Regarder à gauche + +[FEC_LOR] +Regarder à droite + +[FEC_NTR] +Cible suivante + +[FEC_PTT] +Cible précédente + +[FEC_LBA] +Regarder en arrière + +[FEC_CEN] +Centrer caméra + +[FEC_UND] +(NON) + +[FET_CFT] +A PIED + +[FET_CCR] +EN VOITURE + +[CVT_MSG] +Conversion des textures vers un format optimal pour votre carte graphique + +[FET_CAC] +ACTION + +[FEC_IBT] +- + +[FEC_SPC] +ESP + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +NON UTILISE + +[FET_CME] +TYPE DE COMMANDES + +[FET_RDK] +REDEFINIR COMMANDES + +[FET_AMS] +PARAMETRES SOURIS + +[FET_STI] +CONFIG. COMMANDES STANDARD + +[FET_CTI] +CONFIG. COMMANDES NORMALES + +[FET_MTI] +CONFIG. SOURIS + +[FET_DAM] +MODELAGE ACCOUST. DYNAMIQUE + +[FEC_TFL] +Tourelle Gauche + +[FEC_TFR] +Tourelle Droite + +[FEC_TFU] +Tourelle /Dodo Haut + +[FEC_TFD] +Tourelle /Dodo Bas + +[FEC_MWF] +MOLETTE HAUT + +[FEC_MWB] +MOLETTE BAS + +[FEC_ORR] +ou + +[FEC_NUS] +NON UTILISE + +[FEC_LUD] +Regarder Haut + +[FEC_LDU] +Regarder Bas + +[FEC_CMP] +COMBO : REGARDER G+D + +[FEC_NTT] +No Text Yet For This Key + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INSER + +[FEC_DLL] +SUPPR + +[FEC_HME] +ORIG + +[FEC_END] +FIN + +[FEC_PGU] +PAGE HAUT + +[FEC_PGD] +PAGE BAS + +[FEC_UPA] +HAUT + +[FEC_DWA] +BAS + +[FEC_LFA] +GAUCHE + +[FEC_RFA] +DROITE + +[FEC_NUM] +PAV.NUM + +[FEC_NMN] +PAV.NUM~1~ + +[FEC_FWS] +PAV.NUM / + +[FEC_PLS] +PAV.NUM + + +[FEC_MIN] +PAV.NUM - + +[FEC_DOT] +PAV.NUM . + +[FEC_NLK] +VERR NUM + +[FEC_ETR] +ENTR + +[FEC_SLK] +ARRET DEFIL + +[FEC_PSB] +PAUSE + +[FEC_BSP] +RET. ARR. + +[FEC_TAB] +TAB + +[FEC_CLK] +VERR MAJ + +[FEC_RTN] +RETOUR + +[FEC_LSF] +MAJ. G + +[FEC_RSF] +MAJ. D + +[FEC_LCT] +CTRL G + +[FEC_RCT] +CTRL D + +[FEC_LAL] +ALT G + +[FEC_RAL] +ALT D + +[FEC_LWD] +WIN G + +[FEC_RWD] +WIN D + +[FEC_WRC] +CLIC WIN + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Grand Theft Auto III n'est pas compatible WINDOWS 95 + +[WIN_DX] +Grand Theft Auto III requiert la version 8.1 de DirectX minimum. + +[WIN_VDM] +Grand Theft Auto III requiert au moins 12 Mo de mémoire vidéo libre. + +[DIAB3_G] +Arriba ! + +[FEM_RES] +REPRENDRE PARTIE + +[FES_SNG] +NOUVELLE PARTIE + +[FEM_SP] +MODE SOLO + +[FEM_MP] +MODE MULTIJOUEUR + +[FEM_QT] +QUITTER + +[FES_SG] +NOUVELLE PARTIE + +[FES_LG] +CHARGER PARTIE + +[FEM_HST] +HEBERGER PARTIE + +[FEM_OPT] +OPTIONS + +[FEM_DBG] +DEBUG + +[FET_PSU] +PARAMETRES JOUEUR + +[FET_DEF] +PAR DEFAUT + +[FED_BRI] +LUMINOSITE + +[FED_TRA] +TRAINEES + +[FEM_LOD] +DISTANCE MODELES + +[FEM_VSC] +SYNCHRO VIDEO + +[FEM_FRM] +RESTRICTION VIDEO + +[FED_RES] +RESOLUTION ECRAN + +[FED_WIS] +PLEIN ECRAN + +[FEDS_TB] +RETOUR + +[FEA_MUS] +MUSIQUE + +[FEA_SFX] +EFFETS SPECIAUX + +[FEA_RSS] +STATION RADIO + +[FEL_ENG] +ANGLAIS + +[FEL_FRE] +FRANCAIS + +[FEL_GER] +ALLEMAND + +[FEL_ITA] +ITALIEN + +[FEL_SPA] +ESPAGNOL + +[FEA_3DH] +CONFIG. CARTE-SON + +[FEA_SPK] +CONFIG. HAUT-PARLEURS + +[FEA_2SP] +2 HAUT-PARLEURS + +[FEA_4SP] +PLUS DE 2 HAUT-PARLEURS + +[FEA_EAR] +CASQUE + +[FEA_NAH] +PAS DE CARTE-SON + +[FET_SNG] +NOUVELLE PARTIE + +[FEN_STA] +COMMENCER PARTIE + +[GMLOAD] +CHARGER PARTIE + +[GMSAVE] +SAUVEGARDER PARTIE + +[FES_DGA] +EFFACER PARTIE + +[FEM_NON] +AUCUN + +[FEC_IVV] +INVERSER SOURIS VERTIC. + +[FEC_MSH] +SENSIBILITE SOURIS + +[FET_CCN] +COMMANDES : NORMALES + +[FET_SCN] +COMMANDES : STANDARD + +[FES_SET] +UTILISER MODELE + +[GHOST] +Fantôme + +[WIN_RSZ] +Impossible de choisir la nouvelle résolution. + +[FET_APP] +BGS, RETOUR POUR APPLIQUER LE NOUV. PARAMETRE + +[FET_HRD] +PARAMETRES PAR DEFAUT RETABLIS + +[FET_MST] +DIRECTION CONTROLEE PAR LA SOURIS + +[FEC_STR] +ETOILE PAV.NUM. + +[FET_MIG] +GAUCHE, DROITE, MOLETTE SOURIS POUR REGLER + +[FET_CIG] +RETOUR ARRIERE POUR EFFACER - BGS, RETOUR POUR CHANGER + +[FET_RIG] +SELECTIONNEZ NOUV. TOUCHE POUR CETTE ACTION OU ECHAP POUR ANNULER + +[FET_EIG] +IMPOSSIBLE DE PARAMETRER UNE TOUCHE POUR CETTE ACTION + +[NO_PCCD] +Insérez le disque 2 de Grand Theft Auto III dans le lecteur ou appuyez sur ECHAP pour annuler. + +[CVT_ERR] +Espace disque épuisé. Libérez de la mémoire sur votre disque dur pour continuer. Appuyez sur ECHAP pour annuler. + +[FED_SUB] +SOUS-TITRES + +[FET_DSN] +Skin joueur par défaut.bmp + +[JM3] +'HAUT LES MAINS' + +[EBAL] +'A MOI LIBERTY' + +[LM4] +'MAQUEREAU EN BOITE' + +[REPLAY] +RALENTI + +[FEC_SFT] +MAJ + +[CRED254] +RESPONSABLE STUDIO + +[CVT_CRT] +Pour convertir les textures pour votre carte graphique, connectez-vous à un compte Administrateur. Pour quitter, appuyez sur ECHAP. + +[FEM_ON] +AVEC + +[FEM_OFF] +SANS + +[FEM_YES] +OUI + +[FEM_NO] +NON + +[FES_WAR] +Sauvegarde en cours... + +[FED_DLW] +Suppression en cours... + +[FED_LDW] +Chargement en cours... + +[FEC_SLC] +Emplacement corrompu + +[FED_LFL] +Echec du chargement de la sauvegarde. La partie va être relancée. + +[FET_RSO] +PARAMETRE D'ORIGINE RETABLI + +[FET_RSC] +MATERIEL INDISPONIBLE - PARAMETRE D'ORIGINE RETABLI + +[CRED270] +MIKE HONG + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPONAIS + +[FEL_POL] +POLONAIS + +[FEL_RUS] +RUSSE + +{ new display menus } +[FET_GFX] { this probably needs to be retranslated } +CONFIG. EFFETS SPECIAUX + +[FED_MIP] +MIP MAPPING + +[FED_AAS] +ANTI ALIASING + +[FED_FIL] +TEXTURE FILTERING + +[FED_BIL] +BILINEAR + +[FED_TRL] +TRILINEAR + +[FED_WND] +WINDOWED + +[FED_FLS] +FULLSCREEN + +[FEM_CSB] +CUTSCENE BORDERS + +[FEM_SCF] +SCREEN FORMAT + +[FEM_ISL] +MAP MEMORY USAGE + +[FEM_LOW] +LOW + +[FEM_MED] +MEDIUM + +[FEM_HIG] +HIGH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +FREE CAM + +{ Linux joy detection } +[FEC_JOD] +DETECT JOYSTICK + +[FEC_JPR] +Press any key on the joystick of your choice that you want to use on the game, and it will be selected. + +[FEC_JDE] +Detected joystick + +{ mission restart } +[FET_RMS] +REJOUER MISSION + +[FESZ_RM] +REJOUER? + +{ more graphics } +[FED_VPL] +VEHICLE PIPELINE + +[FED_PRM] +PED RIM LIGHT + +[FED_RGL] +ROAD GLOSS + +[FED_CLF] +COLOUR FILTER + +[FED_WLM] +WORLD LIGHTMAPS + +[FED_MBL] +MOTION BLUR + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +{ controls } +[FEC_IVP] +INVERT PAD VERTICALLY + +{ map } +[FEM_TWP] +Toggle Waypoint + +[FEA_FMN] +RADIO ETEINTE + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD TYPE + +[FEC_CCF] +CONFIGURATION + +[FEC_CF1] +CONFIGURATION 1 + +[FEC_CF2] +CONFIGURATION 2 + +[FEC_CF3] +CONFIGURATION 3 + +[FEC_CF4] +CONFIGURATION 4 + +[FEC_CDP] +AFFICHAGE DE LA MANETTE + +[FEC_ONF] +A PIED + +[FEC_INC] +DANS UN VÉHICULE + +[FEC_VIB] +VIBRATIONS + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/german.txt b/utils/gxt/german.txt new file mode 100644 index 0000000..b77cb17 --- /dev/null +++ b/utils/gxt/german.txt @@ -0,0 +1,8193 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[ARSE] +ü ß ä + +[IN_VEH] +~g~Hey! Zurück ins Auto!! + +[IN_VEH2] +~g~Du brauchst einen Schlitten für diesen Job! + +[IN_BOAT] +~g~Du brauchst ein Boot für diesen Job! + +[HEY] +~g~Keine Alleingänge. Halt die Gang beisammen! + +[HEY2] +~g~Nicht aufteilen. Halt die Leute zusammen! + +[HEY3] +~g~Du hast deinen besten Mann verloren. Los, zurück! Hol 8-Ball! + +[HEY4] +~g~Wenn du Misty verlierst, kriegst du's mit Luigi zu tun. Los, hol sie. + +[HEY5] +~g~Eines der Girls fehlt. Los, zurück! Treib das Mädchen auf! + +[HEY6] +~g~Du stehst mit deiner Ehre für den Yakuza Kanbu ein. Du musst ihn beschützen! + +[HEY7] +~g~Ein Mann mehr kann nicht schaden. Los, zurück. Hol deinen Kontaktmann ab! + +[HEY8] +~g~Beschützen heißt so viel wie beschützen - Beschütze den alten Asiaten! + +[HEY9] +~g~Du willst wissen, was so geredet wird? Sprich mit deinem Kontaktmann! + +[HELP2_A] +Drücke die ~h~/-Taste~w~, um zu ~h~sprinten. + +[HELP3] +Du kannst nur kurze Zeit sprinten, ohne müde zu werden. + +[HELP4_A] +Drücke die~h~ ~k~~VEHICLE_ACCELERATE~-Taste~w~, um zu ~h~beschleunigen. + +[HELP4_D] +Drücke den~h~ Rechten Analog-Stick nach oben, um zu ~h~beschleunigen. + +[HELP5_A] +Drücke die~h~ ~k~~VEHICLE_BRAKE~-Taste~w~, um zu ~h~bremsen~w~, oder um ~h~zurückzusetzen~w~, wenn das Fahrzeug steht. + +[HELP5_D] +Zieh den ~h~Rechten Analog-Stick~w~ zurück, um zu ~h~bremsen~w~, oder um ~h~zurückzusetzen~w~, wenn das Fahrzeug steht. + +[HELP6_A] +Drücke die~h~ ~k~~VEHICLE_HANDBRAKE~-Taste~w~, um die ~h~Handbremse anzuziehen. + +[HELP6_C] +Drücke die~h~ ~k~~VEHICLE_HANDBRAKE~-Taste~w~, um die ~h~Handbremse anzuziehen. + +[HELP6_D] +Drücke die~h~ ~k~~VEHICLE_HANDBRAKE~-Taste~w~, um die ~h~Handbremse anzuziehen. + +[HELP7_A] +Halte die~h~ ~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um mit dem Präzisionsgewehr zu zielen. + +[HELP7_D] +Halte die~h~ ~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um mit dem Präzisionsgewehr zu zielen. + +[HELP8_A] +Drücke die~h~ ~k~~PED_SNIPER_ZOOM_IN~-Taste~w~, um ~h~an das Ziel heranzuzoomen ~w~und die~h~ ~k~~PED_SNIPER_ZOOM_OUT~-Taste~w~,um ~h~herauszuzoomen ~w~. + +[HELP9_A] +Drücke die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um das Präzisionsgewehr abzufeuern. + +[HELP10] +Dieser Stern zeigt an, dass du von der Polizei gesucht wirst. + +[HELP11] +Je mehr Sterne, desto dringender wirst du gesucht. + +[HELP13] +Manchmal musst du vielleicht Wege finden, die das Radar nicht zeigt. + +[TIMER] +Diese Mission hat ein Zeitlimit. Du musst sie beendet haben, bevor die Zeit um ist. + +[MISTY1] +~r~Misty ist hinüber! + +[OUT_VEH] +~g~Raus aus dem Fahrzeug! + +[GARAGE] +Fahr den Wagen in eine Garage und geh dann nach draußen. + +[WANTED1] +~g~Schüttle die Cops ab. Verringere deinen Fahndungslevel. + +[NODOORS] +~g~Das sind keine Sardinen! Besorg einen Wagen mit ausreichend Sitzplätzen. + +[TRASH] +~g~Du hast deine Karre ziemlich geschrottet! Repariere sie! + +[WRECKED] +~r~Das Fahrzeug ist Schrott! + +[HORN] +~g~Drück auf die Hupe. + +[HORN4] +Drück die ~h~L3-Taste~w~, um zu hupen. + +[NOMONEY] +~g~Du brauchst mehr Cash! + +[OUTTIME] +~r~Zu langsam, Mann, zu langsam! + +[SPOTTED] +~r~Sie sind dir auf den Fersen! + +[REWARD] +BELOHNUNG $~1~ + +[GAMEOVR] +GAME OVER + +[Z] +Z-Achse Wert: ~1~ + +[M_FAIL] +MISSION FEHLGESCHLAGEN! + +[M_PASS] +MISSION ERFÜLLT! $~1~ + +[O_PASS] +JOB ERLEDIGT! + +[O_FAIL] +JOB FEHLGESCHLAGEN! + +[DEAD] +AUSSER GEFECHT! + +[BUSTED] +VERHAFTET! + +[S_PROMP] +Außerhalb einer Mission kannst du dein ~h~Spiel hier speichern~w~. Dies rückt die Uhr um sechs Stunden vor. + +[NUMBER] +~1~ + +[SCORE] +$~1~ + +[LOADCAR] +LADE FAHRZEUG... (ABBRECHEN MIT L1) + +[CARSOFF] +Deaktivierte Fahrzeuge. + +[CARS_ON] +Aktivierte Fahrzeuge. + +[TEXTXYZ] +Schreibe Koordinaten in Datei... + +[CHEATON] +Cheat Modus AN + +[CHEATOF] +Cheat Modus AUS + +[UZI_IN] +Die Uzi ist jetzt im AmmuNation zu haben! + +[IMPORT1] +Geh nach draußen und warte auf dein Fahrzeug. + +[PAGEB1] +Pistole wurde im Versteck angeliefert. + +[PAGEB2] +Uzi wurde im Versteck angeliefert. + +[PAGEB3] +Kugelsichere Weste wurde im Versteck angeliefert. + +[PAGEB4] +Schrotflinte wurde im Versteck angeliefert. + +[PAGEB5] +Granaten wurden im Versteck angeliefert. + +[PAGEB6] +Molotowcocktails wurden im Versteck angeliefert. + +[PAGEB7] +AK47 wurde im Versteck angeliefert. + +[PAGEB8] +Präzisionsgewehr wurde im Versteck angeliefert. + +[PAGEB9] +M16 wurde im Versteck angeliefert. + +[PAGEB10] +Raketenwerfer wurde im Versteck angeliefert. + +[PAGEB11] +Flammenwerfer wurde im Versteck angeliefert. + +[WANT_A] +Verhaftet wirst du nur, wenn die Polizei nach dir ~h~fahndet. + +[WANT_B] +Dein ~h~Fahndungslevel~w~ wird durch die Reihe von Sternen oben rechts auf dem Bildschirm dargestellt. + +[WANT_C] +Du hast jetzt einen ~h~Fahndungslevel~w~ von eins... + +[WANT_D] +zwei... + +[WANT_E] +drei... + +[WANT_F] +Steigt dein ~h~Fahndungslevel~w~, wirst du von besser ausgebildeten Polizisten gejagt. + +[WANT_G] +Wirst du ~h~verhaftet~w~, wirst du zum nächsten Polizeirevier gebracht. + +[WANT_H] +Die Cops werden dir alle Waffen abnehmen und kassieren ein wenig Bestechungsgeld von dir. + +[WANT_I] +Wenn dir das auf einer Mission passiert, ist die Mission fehlgeschlagen. + +[WANT_J] +Im Verlauf des Spiels wirst du Möglichkeiten entdecken, deinen Fahndungslevel zu reduzieren. + +[WANT_K] +Wenn du in einem Wagen sitzt, werden ~h~LACKIEREREIEN~w~ den Fahndungslevel ~h~annullieren. + +[HEAL_B] +Wenn du ~h~'außer Gefecht'~w~ bist, wirst du zur nächsten Klinik gebracht. + +[HEAL_C] +Du verlierst alle Waffen, und die Ärzte knöpfen dir ein wenig Cash für die Behandlung ab. + +[HEAL_E] +Je länger du spielst, desto mehr Wege wirst du finden, dich selbst zu verarzten oder zu schützen. + +[DAM] +SCHADEN: + +[KILLS] +HITS: + +[FARES] +FAHRTEN + +[BULL] +GOLDBARREN + +[EVID] +BEWEISMITTEL + +[HEALTH] +ZUSTAND AUTO + +[COLLECT] +GESAMMELT: + +[BOMB] +Fahr deinen Wagen in die Bombenwerkstatt, um eine ~h~Bombe~w~ anzubringen. Kosten - ~h~$1000. + +[SAVE1] +Geh durch den Eingang. So kannst du dein ~h~Spiel speichern~w~. Während einer Mission kannst du nicht speichern. + +[SAVE2] +Jedes Fahrzeug, das in dieser Garage abgestellt wird, wird für dich aufbewahrt, wenn das Spiel gespeichert wird. + +[AMMU] +Betritt den AmmuNation-Laden, um eine Waffe zu kaufen. + +[BRIDGE1] +Wenn die Callahan Bridge repariert ist, kannst du nach Staunton Island rüber fahren. + +[TUNNEL] +Wenn der Porter Tunnel geöffnet ist, kannst du nach Staunton Island rüber fahren. + +[LUIGI] +LUIGI MISSIONEN + +[TONI] +TONI MISSIONEN + +[JOEY] +JOEY MISSIONEN + +[FRANK] +SALVATORE MISSIONEN + +[DIABLO] +DIABLO MISSIONEN + +[ASUKA] +ASUKA MISSIONEN + +[B_SITE] +ASUKA VORSTADT-MISSIONEN + +[KENJI] +KENJI MISSIONEN + +[RAY] +RAY MISSIONEN + +[LOVE] +LOVE MISSIONEN + +[YARDIE] +YARDIE MISSIONEN + +[HOOD] +HOOD MISSIONEN + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Atlantic Quays + +[PORT_E] +Portland Harbor + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Portland Beach + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Rotlichtbezirk + +[TOWERS] +Hepburn Heights + +[HARWOOD] +Harwood + +[ROADBR1] +Callahan Bridge + +[ROADBR2] +Callahan Bridge + +[TUNNELP] +Porter Tunnel + +[BOMB1] +8-Balls Werkstatt + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Liberty Campus + +[CONSTRU] +Fort Staunton + +[PARK] +Belleville Park + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Francis Int. Airport + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Cochrane Dam + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Krankenwagen + +[CAR_2] +Feuerwehrwagen + +[CAR_3] +Polizei + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +FBI-Wagen + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Kleinbus + +[CAR_11] +Lkw + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch Limo + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[LUIGIS] +Luigis Club + +[GOAWAY] +~g~Du bist bereits auf einer Mission! + +[LUIGGO] +~g~Luigi checkt gerade ein paar neue Girls aus. Komm später wieder! + +[JOEYGO] +~g~Joey ist mit Misty in der Stadt unterwegs. Komm später wieder! + +[TONIGO] +~g~Toni ist mit seiner Mamma in der Oper. Probier's ein andermal! + +[KEMUGO] +~g~Maria und Kemuri sind gerade beschäftigt. Versuch's später nochmal! + +[KENJGO] +~g~Kenji ist bei einem Yakuza-Treffen. Schau ein andermal wieder vorbei. + +[RAYGO] +~g~Ray hängt gerade auf irgend einem anderen Klo rum. Komm später wieder! + +[LOVEGO] +~g~Donald Love hat anderes zu tun. Vielleicht hat er später Zeit! + +[KENSGO] +~g~Kenji hat zu tun! Komm später wieder! + +[ASUSGO] +~g~Asuka hat gerade überhaupt keine Zeit! + +[HOODGO] +~g~Die Hoods haben gerade keine Zeit! + +[WRONGT1] +~g~Komm zwischen 05:00 und 21:00 wieder. Dann gibt's einen Job. + +[WRONGT2] +~g~Komm zwischen 06:00 und 14:00 wieder. Dann gibt's einen Job. + +[WRONGT3] +~g~Komm zwischen 15:00 und 00:00 wieder. Dann gibt's einen Job. + +[GUN_1A] +Benutze die ~h~~k~~PED_CYCLE_WEAPON_RIGHT~-Taste ~w~und die ~h~~k~~PED_CYCLE_WEAPON_LEFT~-Taste~w~, um zwischen deinen Waffen zu wechseln. + +[GUN_2A] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um automatisch zu zielen. Drücke die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu feuern! Versuch, die Ziele zu treffen... + +[GUN_2C] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um automatisch zu zielen. Drücke die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu feuern! Versuch, die Ziele zu treffen... + +[GUN_2D] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste ~w~gedrückt, um automatisch zu zielen. Drücke die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu feuern! Versuch, die Ziele zu treffen... + +[GUN_3A] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste~w~ gedrückt und drücke die ~h~~k~~PED_CYCLE_TARGET_LEFT~-Taste~w~ oder die ~h~~k~~PED_CYCLE_TARGET_RIGHT~-Taste, um das Ziel zu wechseln. + +[GUN_3B] +Halte die ~h~~k~~PED_LOCK_TARGET~-Taste~w~ gedrückt und drücke die ~h~~k~~PED_CYCLE_TARGET_LEFT~-Taste~w~ oder die ~h~~k~~PED_CYCLE_TARGET_RIGHT~-Taste, um das Ziel zu wechseln. + +[GUN_4A] +Mit gedrückter ~h~~k~~PED_LOCK_TARGET~-Taste~w~ kannst du gehen oder laufen und behältst dein Ziel im Visier. + +[GUN_4B] +Mit gedrückter ~h~~k~~PED_LOCK_TARGET~-Taste~w~ kannst du gehen oder laufen und behältst dein Ziel im Visier. + +[GUN_5] +An diesen Pappkameraden kannst du zielen und schießen üben. Wenn du fertig bist, widme dich wieder deiner Mission. + +[TAXI1] +~g~Besorg dir einen Passagier. + +[FARE1] +~g~Fahrziel: ~w~'Meeouch Sex Kitten Club' ~g~im Rotlichtbezirk. + +[FARE2] +~g~Fahrtziel: ~w~'Supa Save' ~g~in Portland View. + +[FARE3] +~g~Fahrtziel: ~w~'Alte Schulhalle' ~g~in Chinatown. + +[FARE4] +~g~Fahrtziel: ~w~'Greasy Joe's Cafe' ~g~in Callahan Point. + +[FARE5] +~g~Fahrtziel: ~w~'AmmuNation' ~g~im Rotlichtbezirk. + +[FARE6] +~g~Fahrtziel: ~w~'Easy Credit Autos' ~g~in Saint Mark's. + +[FARE7] +~g~Fahrtziel: ~w~'Woody's Topless Bar' ~g~im Rotlichtbezirk. + +[FARE8] +~g~Fahrtziel: ~w~'Marcos Bistro' ~g~in Saint Mark's. + +[FARE9] +~g~Fahrtziel: ~w~'Import-Export Garage' ~g~in Portland Harbour. + +[FARE10] +~g~Fahrtziel: ~w~'Punk Noodles' ~g~in Chinatown. + +[FARE12] +~g~Fahrtziel: ~w~'Football Stadion' ~g~in Aspatria. + +[FARE13] +~g~Fahrtziel: ~w~'Die Kirche' ~g~in Bedford Point. + +[FARE14] +~g~Fahrtziel: ~w~'Das Casino' ~g~in Torrington. + +[FARE15] +~g~Fahrtziel: ~w~'Liberty University' ~g~in Liberty Campus. + +[FARE16] +~g~Fahrtziel: ~w~'Einkaufszentrum' ~g~in der Belleville Park Area. + +[FARE17] +~g~Fahrtziel: ~w~'Museum' ~g~in Newport. + +[FARE18] +~g~Fahrtziel: ~w~'AmCo Gebäude' ~g~in Torrington. + +[FARE19] +~g~Fahrtziel: ~w~'Bolt Burgers' ~g~in Bedford Point. + +[FARE20] +~g~Fahrtziel: ~w~'Der Park' ~g~in Belleville. + +[FARE21] +~g~Fahrtziel: ~w~'Francis Int. Airport'. + +[FARE22] +~g~Fahrtziel: ~w~'Cochrane Dam'. + +[FARE24] +~g~Fahrtziel: ~w~'Die Klinik' ~g~in Pike Creek. + +[FARE25] +~g~Fahrtziel: ~w~'Der Park' ~g~in Shoreside Vale. + +[FARE26] +~g~Fahrtziel: ~w~'North West Towers' ~g~in Wichita Gardens. + +[NEW_TAX] +GRÖSSER! SCHNELLER! HÄRTER! Neu! Borgnine Taxis jetzt in Harwood! Rufen Sie 555-BORGNINE! Heute noch! + +[TSCORE2] +$~1~ + +[IN_ROW] +~1~ SERIEN-Bonus! $~1~ + +[TTUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Taxi-Missionen an- oder abzuschalten. + +[TTUTOR2] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Taxi-Missionen an- oder abzuschalten. + +[ATUTOR2] +~g~Fahre die Patienten VORSICHTIG in die Klinik. + +[A_TIME] ++~1~ Sekunden + +[A_FULL] +~r~Krankenwagen voll!! + +[A_RANGE] +~g~Du bist außer Reichweite des Notarztfunks. Fahr näher an die Klinik heran! + +[FTUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Feuerwehr Missionen an- oder abzuschalten. + +[FTUTOR2] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Feuerwehr Missionen an- oder abzuschalten. + +[F_PASS1] +Feuer gelöscht! + +[F_RANGE] +~g~Du bist außer Reichweite des Feuerwehrfunks. Fahr näher an eine Feuerwache heran! + +[C_BREIF] +~g~Verdächtiger wurde zuletzt in der Gegend von ~a~ gesichtet. + +[C_RANGE] +~g~Du bist außer Reichweite des Polizeifunks. Fahr näher an ein Polizeirevier heran! + +[DODO_FT] +Du bist ~1~ Sekunden geflogen! + +[EBAL_A] +Ich kenn ein Plätzchen im Rotlichtbezirk, wo wir untertauchen können. + +[EBAL_A1] +Aber meine Hände sind im Eimer. Also, fahr du. + +[EBAL_1] +Drücke die~h~ ~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in ein Fahrzeug ~h~ein- oder auszusteigen~w~. + +[EBAL_1B] +Drücke die~h~ ~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in ein Fahrzeug ~h~ein- oder auszusteigen~w~. + +[EBAL_2] +~g~Steig wieder in den Wagen! + +[EBAL_3] +Dies ist das ~h~Radar~w~. Damit navigierst du durch die Stadt. Folge dem ~h~Leuchtpunkt~w~ auf dem ~h~Radar~w~, um das Versteck zu finden! + +[EBAL_D] +Ich kenn einen, der hat Beziehungen zur Mafia. Er heißt Luigi. + +[EBAL_D1] +Wir sind alte Bekannte. Vielleicht kann ich dir 'nen Job bei ihm verschaffen. Komm, hier rüber. + +[EBAL_E] +Komm, wir gehen zu ihm. Ich stell dich vor. + +[EBAL_I] +Der Boss kommt gleich zu dir raus... + +[EBAL_J] +8-Ball hat oben was zu erledigen. + +[EBAL_K] +Du könntest mir einen Gefallen tun. + +[EBAL_L] +Eines meiner Girls braucht 'nen Fahrer. Schnapp dir ein Auto, hol Misty von der Klinik ab und bring sie her. + +[EBAL_N] +Also lass die Hände am Lenkrad! + +[EBAL_4] +~r~8-Ball ist tot! + +[EBAL_5] +~g~Besorg dir ein Fahrzeug! + +[EBAL_6] +~g~Hol Misty ab! + +[LM1] +'LUIGIS GIRLS' + +[LM2] +'KEIN SPANK FÜR DIE LADIES' + +[LM3] +'MISTY UND DER MAFIOSO' + +[LM5] +'DER BULLEN-BALL' + +[LM1_2] +~g~Bring Misty zu Luigis Club. + +[LM1_3] +~g~Drück auf die Hupe, damit die Kleine einsteigt. + +[LM1_6] +~g~Steig wieder in den Wagen! + +[LM1_7] +Halte neben Misty an und lass sie einsteigen. + +[LM1_8] +Du kannst dir bei Luigi den nächsten Job abholen oder Liberty City erkunden. + +[LM2_A] +Da ist eine neue Droge in Umlauf, sie heißt SPANK. + +[LM2_E] +Irgendein Kerl hat diesen Müll meinen Girls in Portland Harbour verabreicht. + +[LM2_B] +Fahr hin und verabreich ihm ein paar mit 'nem Baseballschläger! + +[LM2_G] +Der Typ soll bezahlen für diese Beleidigung! + +[LM2_1] +~g~Nimm sein Auto und spritz es um. + +[LM2_2A] +Benutze die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu ~h~schlagen und zu treten~w~ oder um ~h~den Schläger zu schwingen~w~! + +[LM2_2C] +Benutze die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu ~h~schlagen und zu treten~w~ oder um ~h~den Schläger zu schwingen~w~! + +[LM2_2D] +Benutze die~h~ ~k~~PED_FIREWEAPON~-Taste~w~, um zu ~h~schlagen und zu treten~w~ oder um ~h~den Schläger zu schwingen~w~! + +[LM2_3] +~g~Stell das Auto in Luigis Garage ab! + +[LM2_4] +~g~Lackiere das Auto um! + +[LM3_A] +He, ich muss mit dir reden... Okay, Mick, wir reden später. + +[LM3_B] +Na? Alles klar, mein Junge? + +[LM3_C] +Der Sohn des Don, Joey Leone, will seine kleine Misty sehen. + +[LM3_D] +Hol sie in Hepburn Heights ab. + +[LM3_E] +Aber Vorsicht, das ist Diablo-Gebiet. + +[LM3_F] +Dann bringst du sie rüber zu seiner Werkstatt in Trenton. Aber dalli. + +[LM3_H] +Also, Augen auf die Straße und nicht auf Misty! + +[LM3_1D] +Drücke die~h~ L3-Taste~w~, um zu ~h~hupen~w~. So weiß Misty, dass du da bist. + +[LM3_2] +~g~Fahr Misty zu Joey. + +[LM3_4] +~g~Hol Misty ab! + +[LM3_5] +Du arbeitest jetzt fest für Luigi? War auch Zeit, dass er 'nen verlässlichen Fahrer anbringt. + +[LM3_7] +Ich bin gleich bei dir, Süße. + +[LM3_10] +~g~Besorg dir ein Auto! + +[LM4_B] +Fahr hin und regle das für mich. + +[LM4_C] +Wenn du 'ne Knarre brauchst, geh zum Hintereingang von AmmuNation, gegenüber der U-Bahn. + +[LM5_A] +Der Polizeiball findet in der alten Schulhalle nahe der Callahan Bridge statt, + +[LM5_B] +und bei solchen Bällen möchten auch Cops ein wenig 'Action' haben. + +[LM5_C] +Ich hab Girls in der ganzen Stadt stehen. + +[LM5_D] +Bring sie zu dem Ball. Das bringt 'nen Haufen Kohle. + +[LM5_1] +~g~Wenn du zu viele Ladies ins Auto stopfst, holen sie sich Schrammen! ~g~Liefere erst diese Mädchen ab und hol dann den Rest. + +[LM5_2] +~r~Eins von Luigis Girls ist hinüber! + +[LM5_3] +~g~Du brauchst ein Auto! + +[LM5_4] +~g~Hol die Girls, die in St. Mark's arbeiten. + +[LM5_5] +~g~Bring die Girls zum Polizeiball! + +[LM5_8] +~g~Girls auf dem Ball: ~1~ + +[JM2] +'ADIEU, 'CHUNKY' LEE CHONG' + +[JM3] +' DER GELDTRANSPORTER' + +[JM4] +'CIPRIANIS CHAUFFEUR' + +[JM5] +'DER TOTE PASSAGIER' + +[JM1_1] +~g~Bring Forellis Wagen zu 8-Balls Werkstatt nördlich von hier, hinter 'Easy Credit Autos'. + +[JM1_2] +~g~Park den Wagen wieder vor Marcos Bistro. + +[JM1_3] +~g~Aktiviere die Autobombe und dann nichts wie weg! + +[JM1_4] +~g~Du schrottest das Auto! Repariere es! + +[JM1_5] +~g~Die Autobombe ist nicht aktiviert! + +[JM1_6] +~g~Stell den Wagen wieder an den richtigen Platz. + +[JM1_8A] +~y~Hey, mein alter Freund! + +[JM1_8B] +~y~Die Bombenwerkstatt ist automatisiert. Einfach reinfahren und anhalten, der Rest passiert von selbst. + +[JM1_8C] +~y~Hier, die erste ist umsonst, jede weitere kostet aber. + +[JM2_A] +Chunky Lee Chong verhökert Spank für irgend so eine neue Gang aus Kolumbien oder Colorado oder so... + +[JM2_B] +Ich weiß nicht genau. Aber wen interessieren schon die Details? + +[JM2_D] +Diese Ratte hat seine letzte Frühlingsrolle verkauft. + +[JM2_E] +Ich möchte, dass du ihn erledigst. + +[JM2_G] +Besorg dir 'ne 9mm. Du weißt ja, wo du sie findest, oder? + +[JM2_H] +Und sei vorsichtig in Chinatown. Das ist Triaden-Gebiet. + +[JM3_A] +Also, wir überfallen den Transporter mit den Lohngeldern. + +[JM3_B] +Er startet jeden Tag an der Grenze zu Chinatown. + +[JM3_C] +Kugeln können der Karre nichts anhaben. Also besorg dir einen Wagen und ramm ihn von der Straße. + +[JM3_D] +Fahr ihm voll rein, dann dürften die Wachmänner schnell abhauen. + +[JM3_E] +Fahr den Transporter dann zum Lagerhaus bei den Docks, von da an übernehmen meine Leute. + +[JM3_F] +Der Transporter ist nicht ewig unterwegs, also beeil dich. + +[JM3_1] +~g~Fahr den Transporter zu der Garage. + +[JM3_2] +~g~Ramm den Wagen, bis der Schadenswert unter 70 Prozent liegt. + +[JM4_B] +Oh! Da ist der Typ, von dem ich dir erzählt habe! + +[JM4_C] +Okay, hör zu. Der Typ ist kein Italiener und kein Mechaniker, aber er kann alles 'richten'. + +[JM4_D] +Das ist Paps' Capo, Toni Cipriani. + +[JM4_E] +Ja, ich bin Toni Cipriani. + +[JM4_F] +Bring ihn zu Mammas Restaurant in St. Mark's. + +[JM4_G] +Hör zu, ich plane eine Sache, da brauche ich einen guten Fahrer. Also komm später wieder, okay? + +[JM4_2] +Warte hier. Lass den Motor laufen. Das ist kein Freundschaftsbesuch. + +[JM4_3] +Ein Hinterhalt der Triaden! Bring uns hier raus! + +[JM4_4] +Die Triaden denken wohl, sie können mich fertigmachen. Die! MICH! + +[JM4_6] +Hey, Vorsicht! Ich sagte, keine künstlerischen Einlagen! + +[JM4_7] +~g~Fahr Toni zu Mammas Restaurant. + +[JM4_8] +~r~Toni ist tot! + +[JM5_A] +Großartig! Einfach großartig! + +[JM5_B] +Na also. Genau der, mit dem ich jetzt reden muss! + +[JM5_D] +Einer der Forellis meinte, er weiß zu viel, also hat er gekriegt, was er verdiente. + +[JM5_E] +Schaff die Leiche zu der Schrottpresse in Harwood, okay? + +[JM5_1] +~g~Bring ihn zu der Schrottpresse! + +[JM5_2] +~g~Die Forelli Brüder! + +[JM6_A] +Nicht schlecht, das Ding, was? + +[JM6_B] +Hör zu. Fahr mit einem Wagen zu der sicheren Wohnung in St. Mark's und hol ein paar Freunde von mir ab. + +[JM6_C] +Die überfallen eine Bank und brauchen einen Fahrer. + +[JM6_D] +Ich hab ihnen gesagt, du bist der richtige. Also, vermassle es nicht. + +[JM6_E] +Bring sie vor 5 Uhr zu der Bank, keine Minute später. + +[JM6_2] +Lass den Motor laufen. Wir sind gleich wieder da. + +[JM6_3] +Bring uns hier weg!! + +[JM6_4] +Häng die Cops ab und bring uns in die sichere Wohnung! + +[JM6_6] +~g~Los, besorge ein weniger verdächtiges Fahrzeug! + +[JM6_7] +~g~Du brauchst alle 3 für den Überfall! + +[TM1] +'SCHMUTZIGE WÄSCHE' + +[TM2] +'DER GELDBOTE' + +[TM3] +'DAS TREFFEN BEI SALVATORE' + +[TM4] +'TRIADEN UND ANDERE KLEINE FISCHE' + +[TM5] +'EXPLODIERENDE FISCHE' + +[TONI_P] +Ich habe einen dringenden Job für dich! -Toni + +[TM1_A] +~w~Setz dich, Junge. Los, mach's dir bequem. + +[TM1_B] +~w~Die Wäscherei will also kein Schutzgeld zahlen, was? + +[TM1_C] +~w~Denken die Triaden, sie können mich verscheißern? + +[TM1_D] +~w~Diesen Möchtegern-Gangstern werden wir eine Lektion erteilen. + +[TM1_E] +~w~Ja, ich werde denen Respekt beibringen. Die rühren keinen meiner Söhne ungestraft an. + +[TM1_F] +~w~Dein Vater - Gott hab ihn selig - hat sich von den Triaden nie etwas gefallen lassen. + +[TM1_G] +~w~Sorry, Ma. Ja, Ma. + +[TM1_H] +~w~Ich will, dass du ihre Wäscherei-Transporter zerstörst + +[TM1_I] +~w~und jeden Triaden-Tölpel niedermachst, der dir in die Quere kommt. + +[TM1_J] +~w~8-Ball liefert dir alles, was du dazu brauchst. + +[TM2_A] +~w~TONI ist unterwegs, um jemanden zu erledigen - oder er versucht es jedenfalls. + +[TM2_AA] +Er wird nie so sein wie sein Papa. Auf dem Tisch hat er dir eine Nachricht hinterlassen. + +[TM2_B] +~w~Die Wäscherei will jetzt bezahlen. Gute Arbeit, mein Junge! + +[TM2_C] +~w~Hol das Geld ab und bring es hierher. Pass auf die Triaden auf. + +[TM2_D] +~w~Die wollen dich wahrscheinlich zu Chop Suey verarbeiten, aber lass dir nichts gefallen. + +[TM2_E] +~w~Niemand, wirklich niemand, macht TONI CIPRIANI fertig! + +[TM2_1] +~g~Bring das Geld zu Toni!! + +[TM2_2] +~g~Du hast sie alle erledigt! + +[TM3_MA] +~w~Ich weiß nicht, wo er ist! + +[TM3_MB] +~w~Ach, mein Sohn weiß manchmal selbst nicht, wer er ist. + +[TM3_MC] +~w~Ja, sein Vater, der war da ganz anders. Immer auf Draht, top, ein echter Mann... + +[TM3_A] +~w~Don Salvatore hat ein Treffen angesetzt. + +[TM3_B] +~w~Du musst erst die Limo und seinen Sohn Joey aus der Werkstatt abholen. + +[TM3_C] +~w~Dann holst du Luigi aus seinem Club ab und dann kommst du wieder her und holst mich ab. + +[TM3_D] +~w~Dann fahren wir alle gemeinsam zum Boss. + +[TM3_E] +~w~Diese Triaden wissen einfach nicht, wann Schluss ist. + +[TM3_F] +~w~Wenn sie Krieg wollen, sollen sie Krieg haben. + +[TM3_G] +~w~Also, los jetzt. + +[TM3_1] +~g~Hol die Limousine bei Joey ab. + +[TM3_2] +~g~Jetzt hol Luigi ab. + +[TM3_3] +~g~Jetzt hol Toni ab. + +[TM3_4] +~g~Jetzt fahr die Männer zu Salvatore. + +[TM3_5] +~y~Ein Hinterhalt der Triaden!! + +[TM4_B] +~w~Es herrscht KRIEG! Die Triaden betreiben zur Tarnung einen Fischmarkt in Chinatown. + +[TM4_C] +~w~Die meisten ihrer Geschäfte werden auf diesem Fischmarkt durchgezogen. + +[TM4_D] +~w~Diese Wäscherei schuldet uns immer noch Geld. + +[TM4_E] +~w~Die denken, die Triaden beschützen sie jetzt. Ich schlage vor, wir führen eine Strafaktion durch. + +[TM4_F] +~w~Nimm dir diese Jungs und knöpf dir die Köpfe der Triaden vor! + +[TM4_G] +~w~Und wenn es geht, macht auch ein paar von deren Soldaten fertig. + +[TM4_GAT] +~g~Du brauchst einen 'Triaden-Packwagon', um da reinzukommen. + +[TM5_A] +TEXT NO LONGER REQUIRED + +[TM5_B] +~w~Okay, jetzt hab ich aber die Schnauze voll. + +[TM5_C] +~w~Wir machen die Triaden ein für alle Mal fertig. + +[TM5_D] +8-Ball hat einen Müllkarren mit einer Bombe präpariert. + +[TM5_E] +~w~Sie hat einen Zeitzünder. Wenn du's vermasselst, hinterlassen wir keine Spuren. Hol den Müllkarren ab. + +[TM5_F] +~w~Fahr vorsichtig. 8-Ball sagt, die Bombe ist extrem empfindlich, das kleinste Schlagloch und sie geht hoch. + +[TM5_G] +~w~In ihrer Fischfabrik werden sie dich reinlassen mit dem Müllkarren. + +[TM5_H] +~w~Stell das Ding zwischen den Benzinkanistern ab und dann nichts wie weg. + +[TM5_I] +~w~Es soll rummsen, dass es Fische vom Himmel regnet. + +[TM5_J] +~w~Ne biblische Apokalypse will ich haben, nichts popeliges. + +[FM2] +'CURLYS GEHEIMKONTAKTE' + +[FM4] +'DER LETZTE WUNSCH' + +[FM1_A] +~w~Die Jungs und ich haben einiges zu besprechen, + +[FM1_B] +~w~du wirst dich heute abend um meine Kleine kümmern. + +[FM1_C] +~w~HEY, MARIA! WO BLEIBST DU? + +[FM1_D] +~w~Dämliche Ziege. Jedes Mal dasselbe. + +[FM1_E] +~w~Und hier ist sie, die Königin der Nacht höchstpersönlich! + +[FM1_F] +~w~Was hast du denn da oben getrieben? + +[FM1_G] +~w~Was es auch war, jede Wette, es hat mich Geld gekostet. + +[FM1_H] +~w~Du glaubst doch nicht, ich bin zum Palavern hier, oder? + +[FM1_I] +~w~Halt die Klappe und steig in den Wagen. + +[FM1_J] +~w~Nimm die Limo, aber bring sie mir heil wieder, hörst du? + +[FM1_K] +~w~Und pass auf sie auf, sie kann eine Menge Ärger machen. + +[FM1_L] +~w~Ja, ja, ja! Dein neues Schoßhündchen wird schon alles im Griff haben. + +[FM1_M] +~w~Er ist ja auch so groß und stark. + +[FM1_N] +~w~Hey, Fiffi, los, wir besuchen Chico und besorgen uns was zum 'Naschen'! + +[FM1_P] +~g~Da ist Chico. Halt neben ihm an. + +[FM1_S] +~w~Bitte sehr, die Dame. + +[FM1_TT] +~w~EINE POLIZEI-RAZZIA! + +[FM1_1] +~g~Zurück in die Limo! + +[FM1_2] +~g~Steig in die Limo! + +[FM1_3] +~r~Wenn du Maria im Stich lässt, bringt Salvatore dich um. Kehr um und hol sie! + +[FM1_4] +~g~Du hast die Frau des Don im Stich gelassen! Los, zurück zur Lagerhalle! Warte dort auf Maria! + +[FM1_5] +~g~Bring Maria wohlbehalten zu Salvatore zurück! + +[FM1_6] +~g~Chico ist nicht ewig dort. Bring Maria zu diesem Ufer! + +[FM1_7] +~r~Maria ist tot! Das wird Salvatore nicht gefallen... + +[FM1_8] +~r~Du hast Marias Lieferanten erledigt! + +[FM2_J] +Lasst uns eine Minute alleine. + +[FM2_A] +Das kolumbianische Kartell stellt irgendwo in Liberty SPANK her. + +[FM2_K] +Aber wir wissen nicht wo. Und die scheinen jeden unserer Schritte im Voraus zu kennen. + +[FM2_L] +Es gibt da einen Typ namens Curly Bob. Er arbeitet in Luigis Bar. + +[FM2_M] +Der verpulvert schon dauernd mehr Geld als er verdient. + +[FM2_N] +Normalerweise fährt er nach der Arbeit mit dem Taxi nach Hause. Folge ihm. + +[FM2_O] +Und wenn er der Verräter ist, mach ihn fertig. + +[FM2_F] +Da kommt ja unser kleiner, gesprächiger Freund. + +[FM2_G] +Ist man dir gefolgt? Du weißt, was hier läuft, muss unter uns bleiben. + +[FM2_H] +Nein, nein, niemand ist mir gefolgt. Hast du meinen Stoff? + +[FM2_I] +Hier ist dein SPANK, du Ratte, und jetzt rede. + +[FM2_P] +Okay. Die Leones führen einen Zwei-Fronten-Krieg. + +[FM2_Q] +Sie kämpfen mit den Triaden um ein Territorium, und keiner der beiden gibt nach. + +[FM2_R] +Gleichzeitig hat Joey Leone Streit mit den Forellis angefangen. + +[FM2_S] +Jeden Tag verlieren sie Leute und Einfluss in der Stadt. + +[FM2_T] +Salvatore wird gefährlich und paranoid. Er verdächtigt alles und jeden. + +[FM2_U] +Bei treuen Gefolgsleuten wie dir, wie kann er sich da nur Sorgen machen? + +[FM2_1] +~g~Da ist Curly Bob! + +[FM2_2] +~g~Curly hat den Club verlassen. Folge ihm! + +[FM2_5] +~g~Bring ihn nach Portland Harbour. + +[FM2_6] +~r~Curly steigt in kein geschrottetes Taxi! + +[FM2_7] +~r~Curly hat Angst! Das Treffen ist abgeblasen! + +[FM2_8] +~g~Knöpf dir Curly Bob vor! + +[FM2_9] +~r~Curly Bob ist tot! + +[FM2_10] +~r~Curly ist entwischt! + +[FM2_11] +~g~Parke vor Luigis Club, Curly Bob kommt gleich heraus. + +[FM2_12] +~r~Er ist dir entwischt! + +[FM3_A] +~w~Wir sollten diese kolumbianischen Mistkerle fertigmachen, + +[FM3_B] +~w~aber durch den Krieg mit den Triaden sind wir dazu zu geschwächt. + +[FM3_C] +~w~Das Kartell hat unendlich Geld aus dem Handel mit diesem Mistzeug SPANK. + +[FM3_D] +~w~Wenn wir sie offen angreifen, putzen sie uns weg. + +[FM3_E] +~w~Die müssen das SPANK auf diesem großen Schiff machen, zu dem dich Curly geführt hat. + +[FM3_F] +~w~Wir müssen also mit Köpfchen vorgehen. Genauer gesagt, mit DEINEM Köpfchen. + +[FM3_G] +~w~Ich bitte dich, mir, Salvatore Leone zuliebe, dieses SPANK Labor zu zerstören. + +[FM3_H] +~w~Wenn du das für mich tust, bist du ein gemachter Mann. Du kriegst alles, was du willst. + +[FM3_I] +~w~Geh zu 8-Ball. Du brauchst einen Fachmann, um dieses Schiff hochzujagen. + +[FM3_8A] +~w~Hi, Kumpel! Salvatore hat schon angerufen, + +[FM3_8B] +~w~aber für so einen Job brauchst du eine Menge Chinaböller. + +[FM3_8D] +~w~Aber du kennst mich. Dafür scheppert's dann auch gewaltig. + +[FM3_8E] +~w~Okay, dann wollen wir mal! + +[FM3_8F] +~w~Ich kann das Baby scharf machen, aber eine Knarre kann ich mit diesen Händen immer noch nicht halten. + +[FM3_8G] +~w~Hier, das Gewehr hier wirst du sicher brauchen. + +[FM3_4] +~g~Halt an und lass 8-Ball aussteigen! + +[FM3_7] +~r~8-Ball hat's erwischt! + +[FM3_8] +~r~Die Wachmänner wurden alarmiert! + +[FM4_A] +~w~Ah, sieh an! Mein bester Troubleshooter. + +[FM4_B] +~w~Ich bin stolz auf dich, meine Junge. Du hast es diesen Mistkerlen gezeigt. + +[FM4_C] +~w~Ich hab nur noch einen kleinen Job für dich, bevor wir alle feiern können. + +[FM4_D] +~w~Um die Ecke von Luigis Club steht ein Wagen. + +[FM4_E] +~w~Innen drin sieht's ziemlich aus. + +[FM4_F] +~w~Wir haben so einem Typ versehentlich ein Loch in den Kopf gemacht. + +[FM4_H] +~w~Bring den Wagen zur Schrottpresse, bevor die Cops ihn finden. + +[AM3] +'DER PAPARAZZO' + +[AM4] +'ZAHLTAG FÜR RAY' + +[AM5] +'V-MANN TANNER' + +[AM1_A] +Wir müssen ein paar Dinge klären, bevor wir unsere Beziehungen fortsetzen, + +[AM1_B] +geschäftlich oder sonstwie. Legen wir also die Karten auf den Tisch. + +[AM1_C] +Ich bin eine Yakuza und ich weiß, dass du für Salvatore Leones Familie gearbeitet hast. + +[AM1_D] +Ich kann dir einen Job in unserer Organisation verschaffen, + +[AM1_E] +aber zuerst musst du mir beweisen, dass du dich wirklich von der Mafia losgesagt hast. + +[AM1_G] +Sorge dafür, dass er seinen Club nicht lebend erreicht. + +[AM1_H] +Maria und ich reden inzwischen ein bisschen über die alten Zeiten. + +[AM1_I] +Oh, Asuka, du hast einen Massagestab. + +[AM1_J] +Das ist kein Massagestab. + +[AM1_1] +~g~Salvatore verlässt jetzt Luigis Club! + +[AM1_2] +~r~Man hat dich entdeckt! + +[AM1_3] +~r~Du hast Salvatore verpasst! + +[AM1_4] +~r~Na, prima! Du hast dein Opfer verscheucht. Und du willst ein Profi sein? + +[AM1_5] +~g~Begib dich in den Rotlichtbezirk und warte, bis Salvatore den Club verlässt. + +[AM1_7] +~r~Salvatore sitzt bequem zu Hause und schlürft einen Cocktail. 'Der Schakal' bist du nicht gerade! + +[AM1_8] +~g~Salvatore wird Luigis Club um zirka ~1~:~1~ verlassen. + +[AM2_4] +~g~Du bist für die so unsichtbar wie ein Hochhaus! + +[AM3_A] +Ein Reporter hat rumgeschnüffelt. + +[AM3_B] +Maria und ich sind ein bisschen ins Grüne gefahren, bis du diesen miesen Voyeur beseitigt hast. + +[AM4_A] +Ah, mein hübsches Helferlein! + +[AM4_B] +Maria ist gerade beschäftigt, aber ich richte ihr aus, dass du hier warst. + +[AM4_C] +Wer ist da? Asuka? Ich weiß, ich war ein böses Mädchen, aber ich muss dringend pinkeln! + +[AM4_D] +Wird Zeit dass du unseren Mann bei der Polizei kennenlernst. + +[AM4_E] +Das ist seine Bezahlung für den letzten Job, den er für uns erledigt hat. + +[AM4_F] +Verständlicherweise ist er vorsichtig. + +[AM4_G] +Begib dich so schnell wie möglich zu dem öffentlichen Fernsprecher in Torrington und warte auf seine Anweisungen. + +[AM5_A] +Maria und ich sind shoppen gegangen. + +[AM5_B] +Unser Spitzel hat uns informiert, dass einer unserer Fahrer ein übereifriger Undercover Cop ist! + +[AM5_C] +Ohne sein Auto ist er praktisch ein Nichts. Wir haben seinen Wagen mit einem Sender versehen. + +[AM5_D] +Knöpf ihn dir vor! + +[AM5_1] +Tanner hat dich bemerkt! + +[AS1] +'DER KÖDER' + +[AS2] +'ESPRESSO-2-GO!' + +[AS4] +'DAS LÖSEGELD' + +[AS1_A] +~w~Miguel findet anscheinend, dass ich ihn schlecht behandle. + +[AS1_B] +~w~Trotzdem hat er uns mitgeteilt, wie sehr Catalina deine Rache fürchtet. + +[AS2_A] +~w~Wir haben Catalinas Pläne mit dem SPANK unterschätzt. + +[AS2_B] +~w~Das beschränkt sich bei weitem nicht darauf, dass die Yardies es an der Straßenecke verkaufen. + +[AS2_D] +~w~Die verkaufen SPANK über Kaffeestände. + +[AS2_1] +~g~Alle Espressostände in Portland zerstört!! + +[AS2_2] +~g~Alle Espressostände auf Staunton Island zerstört!! + +[AS2_3] +~g~Alle Espressostände in Shoreside Vale zerstört!! + +[AS2_4] +~r~Das Kartell hat seine Dealer gewarnt!! + +[AS2_5] +~g~Da sind noch Espressostände in Shoreside Vale und auf Staunton Island! + +[AS2_6] +~g~Da sind noch Espressostände in Shoreside Vale! + +[AS2_7] +~g~Da sind noch Espressostände auf Staunton Island! + +[AS2_8] +~g~Da sind noch Espressostände in Portland! + +[AS2_9] +~g~Da sind noch Espressostände in Portland und Shoreside Vale! + +[AS2_10] +~g~Da sind noch Espressostände in Portland und auf Staunton Island! + +[AS2_12] +~g~Suche in den Sadtteilen von Liberty City nach ~b~Espresso-2-Go-Ständen! + +[AS3_A] +~W~Drücken wir noch fester zu oder warten wir, bis es von selbst abfällt? + +[AS3_B] +~w~Hau einfach drauf... + +[AS3_D] +~w~Mein Helferlein! + +[AS3_E] +~w~Mir war langweilig, da dachte ich mir, ich leiste Asuka Gesellschaft. + +[AS3_1] +~g~Such dir ein ~r~Boot~g~ und fahre zu der ~b~Markierungsboje! + +[AS3_3] +~g~Warte, bis die ~y~Maschine~g~ zur Landung ansetzt! + +[AS3_5] +~g~Sammle die Ladung ein! + +[AS3_4] +~g~Benutze einen Raketenwerfer, um das ~y~Flugzeug~g~ abzuschießen!! + +[AS3_2] +~b~Fahr zu der Markierungsboje! ~y~Das Flugzeug landet gleich!! + +[AS3_6] +~g~~1~ VON 8 + +[KM1] +'DIE BEFREIUNG DES KANBU' + +[KM3] +'DER JAMAICA DEAL' + +[KM4] +'DIE GANG' + +[KM5] +'DIE ABRECHNUNG' + +[KM1_A] +Meine Schwester hält große Stücke auf dich, + +[KM1_E] +aber ich bin noch nicht überzeugt, dass ein Gajin wie du was auf dem Kasten hat. + +[KM1_B] +Vielleicht kannst du mir bei einer etwas kniffligen Sache helfen. + +[KM1_F] +Ein Fehlschlag wäre natürlich unverzeihlich. + +[KM1_C] +Ein Yakuza Kanbu sitzt in Haft und wartet auf seine Überführung zum Prozess. + +[KM1_G] +Er ist ein geschätztes Mitglied der Familie. + +[KM1_H] +Befreie ihn aus der Haft und bring ihn in das Dojo beim Bedford Point. + +[KM1_D] +Wir danken dir für deinen selbstlosen Einsatz. Solltest du jemals Hilfe brauchen, wird das Dojo dir jederzeit zwei Mann zur Seite stellen. + +[KM1_1] +~g~Klau ein Polizeiauto! + +[KM1_2] +~g~Bau eine Bombe in den Wagen ein! + +[KM1_3] +~g~Jetzt bring ihn zu dem Yakuza Dojo. + +[KM1_5] +~g~Okay, jetzt fahr zum Polizeirevier. + +[KM1_6] +~g~Bau eine Bombe in das Auto ein! + +[KM1_7] +~g~Nur autorisierte Polizeifahrzeuge! + +[KM1_9] +~r~Du hast keine Autobombe benutzt, um die Wand zu sprengen. + +[KM1_10] +~r~Der Yakuza Kanbu ist tot - genau wie deine Ehre! + +[KM1_11] +~r~Du hast dich selbst in Schwierigkeiten gebracht! + +[KM2_A] +Gewisse Umgangsformen sind in diesem Beruf von nicht zu unterschätzender Wichtigkeit. + +[KM2_B] +Es ist eine Schande. Jemand hat mir einmal einen Gefallen getan, und ich konnte mich nie dafür erkenntlich zeigen. + +[KM2_C] +Der Mann ist ein Autonarr, und er hat gebeten, dass wir ihm bestimmte Modelle für seine Sammlung besorgen. + +[KM2_F] +Mein Ehrgefühl verlangt das von mir. + +[KM2_2] +~g~Auto abgeliefert. + +[KM3_A] +Wenn Ungemach droht, wendet der Narr sich ab, während der Weise sich ihm stellt. + +[KM3_B] +Das kolumbianische Kartell hat unsere wiederholten Bitten ignoriert, unsere Interessen in Liberty zu berücksichtigen. + +[KM3_C] +Jetzt verhandeln die mit den Jamaikanern, um uns weiter zu demütigen. + +[KM3_D] +Sie wollen den Deal am anderen Ende der Stadt besiegeln. + +[KM3_F] +Nimm einen meiner Männer, klau einen Yardie-Wagen und statte den Kolumbianern einen Besuch ab. + +[KM3_E] +Unser Ehrgefühl verlangt es, dass niemand am Leben bleibt. + +[KM3_2] +~g~Hole deinen Kontaktmann ab. + +[KM3_3] +~g~Das Treffen findet auf dem Krankenhaus-Parkplatz in Rockford statt! + +[KM3_4] +~r~Sie sind entkommen! + +[KM3_6] +~g~Knöpf sie dir vor! Mach sie fertig! + +[KM3_8] +~g~Du brauchst einen Yardie-Wagen für diesen Job! + +[KM3_9] +~r~Einer der Kolumbianer ist tot. Der Deal ist geplatzt. + +[KM3_10] +~r~Der Kontaktmann ist tot! + +[KM4_A] +Um wahrhaft stark zu sein, darfst du niemals Schwäche zeigen. + +[KM4_C] +Sammle die Gelder umgehend ein, damit wir sie in unsere Casinos stecken können. + +[KM4_1] +Ich kann euch nicht bezahlen, und selbst wenn ich könnte, würde ich es nicht tun. + +[KM4_9] +Eine Jugendbande hat mich gerade überfallen! Sie haben mir alles genommen! + +[KM4_2] +Ihr seid zu nichts nutze. + +[KM4_10] +Was sind SIE denn eigentlich für ein Yakuza..? + +[KM4_3] +Dafür bezahle ich euch Gangster nicht. Diese Art von Schutz kann ich auch von der verdammten Polizei bekommen! + +[KM4_4] +~g~Bestrafe die verantwortliche Gang und stelle das ~b~Schutzgeld~g~ sicher! + +[KM4_7] +~r~Der Ladenbesitzer hat sein Leben verröchelt. + +[KM4_5] +Donald Love möchte dich in seinem Teegarten sehen, um mit dir zu reden. + +[KM4_6] +Da ist das Geld. Es ist alles da! + +[KM4_8] +~g~Tasche aufgesammelt! + +[KM5_A] +DU! Wie passend, dass du ausgerechnet jetzt dein ehrloses Gesicht zeigst! + +[KM5_B] +Es scheint, deine Versuche, die Jamaikaner davon abzuhalten, + +[KM5_B1] +sich mit dem Kartell einzulassen, sind komplett fehlgeschlagen! + +[KM5_C] +Yardie-Pusher verdealen päckchenweise SPANK in den Straßen von Liberty, als würden sie Hotdogs verkaufen! + +[KM5_D] +Die Mistkerle vom Kartell lachen uns aus, lachen MICH aus! + +[KM5_E] +Ich gebe dir eine letzte Chance das Vertrauen zu rechtfertigen, das meine Schwester in dich setzt! + +[KM5_F] +Mach diese Dreckskerle fertig und wasch deine befleckte Ehre im Blut unserer Feinde rein!!! + +[KM5_3] +~r~Du hast mindestens ~1~ der Yardies nicht erwischt. + +[KM5_4] +~g~Du hast ~1~ der Yardies erwischt. + +[KM5_5] +~g~Du hast ~1~ der Yardies erwischt. BONUS $~1~ + +[RM1] +'DAS SCHWEIGEN DES VERRÄTERS' + +[RM3] +'BRENNENDE BEWEISE' + +[RM4] +'TÖDLICHE BOOTSFAHRT' + +[RM5] +'DER GEPANZERTE ZEUGE' + +[RM1_D] +Er steht unter Zeugenschutz und sitzt mit bewaffneten Leibwächtern in einer Wohnung in Newport, irgendwo hinter dem Parkplatz. + +[RM1_E] +Zünde die Bude an, und wenn sie rausgerannt kommen, kannst du sie dir vornehmen. Sorge dafür, dass er mit niemandem redet. + +[RM1_1] +~g~Check das Zeugenschutz-Haus aus. + +[RM1_2] +~g~Knöpf dir McAffrey vor! + +[RM2_A1] +Hey, Junge! Hier rüber! + +[RM2_A] +Ein alter Kumpel aus der Army macht Geschäfte in Rockford. + +[RM2_D] +Er braucht Hilfe. Zum Dank will er dir Superpreise machen für alles, was du bei ihm kaufst. + +[RM2_E] +Ray hat schon angerufen... Aber ich dachte, er schickt mehr Leute. + +[RM2_F] +Na ja, drei Arme sind besser als einer, also nimm dir, was du brauchst. + +[RM2_G] +~g~Sieh nach Phil! + +[RM2_H] +~r~Phil hat's erwischt!! + +[RM2_L] +Heh-hey! Wäre ich mit DIR in Nicaragua gewesen, hätte ich vielleicht meinen Arm noch! + +[RM2_N] +Lass das Geld da. Und jetzt verschwinde. Ich regle das mit den Cops. + +[RM3_D] +Das Beweismaterial wird gerade quer durch die Stadt transportiert. + +[RM3_E] +Du wirst diesen Wagen rammen und jedes kleine Beweisstück einsammeln, wenn es rausfällt. + +[RM3_F] +Wenn du alles hast, lass das Zeug im Wagen und zünde ihn an. + +[RM3_G] +Das bringt uns beiden eine Stange Geld ein, mein Junge. + +[RM3_1] +~g~Lass das Beweismaterial in einem Auto und zünde das Auto dann an. + +[RM3_4] +~g~Der Staatsanwalt hat die Beweisfotos verloren! + +[RM3_6] +~r~Die Fotos werden sich über ganz Liberty verteilen! + +[RM3_7] +~g~Steck jetzt das Auto in Brand! + +[RM4_A] +Ich glaube, mein Partner ist ein Verräter. + +[RM4_C] +Meistens fährt er abends mit seinem Boot fischen, nahe dem Leuchtturm auf dem Portland Rock. + +[RM4_D] +Klau ein Polizeiboot und mach seinen miesen Machenschaften ein Ende! + +[RM4_1] +~g~Klau ein Polizeiboot. + +[RM4_2] +~g~Fahr zum Leuchtturm und nimm dir Rays Partner vor! + +[RM5_A] +Du unfähiger Idiot! + +[RM5_A1] +Du hast alles vermasselt! Es geht um mein Leben, und du kannst nicht mal eine Fliege totschlagen! + +[RM5_B] +Ich hab dir viel Geld gezahlt, dafür, dass du diesen Zeugen beseitigst, aber er lebt! + +[RM5_B1] +Und heute wird er vor dem FBI aussagen! + +[RM5_C] +Er muss jeden Moment aus dem Carson General Hospital in Rockford rausgebracht werden. + +[RM5_D] +Wenn er singt, singe ich auch... + +[RM5_E] +Also los, erledige den Job, für den ich dich bezahlt habe! + +[RM5_1] +~g~Fang den Krankenwagen ab. + +[RM5_2] +~g~Man hat dich bemerkt!! + +[RM5_3] +~g~Das war ein Ablenkungsmanöver! + +[RM5_4] +~g~Kugel können den Panzer nicht durchschlagen!! + +[RM5_5] +~g~Der Panzer ist feuersicher!! + +[RM5_7] +~r~Zeuge wurde abgeliefert!! + +[RM5_8] +~g~Zeuge ist ertrunken!! + +[LOVE2] +'DAS KENJI-KOMPLOTT' + +[LOVE3] +'NÄCHTLICHER FISCHZUG' + +[LOVE1_A] +Zunächst möchte ich dir danken, dass du diese Sache für mich geregelt hast. + +[LOVE1_F] +Die Leute interpretieren heute in alles etwas hinein. + +[LOVE1_D] +Sie versuchen, mir immer noch mehr Geld abzupressen, aber ich halte nichts von Verhandlungen. + +[LOVE1_E] +Deal ist Deal, die sehen keinen Penny von mir. + +[LOVE1_G] +Befreie meinen Freund, egal wie. + +[LOVE1_2] +~g~Rette den alten asiatischen Gentleman. + +[LOVE1_3] +~g~Bring den alten Asiaten zu Love. + +[LOVE1_4] +~g~Der alte Asiate muss in einer der Garagen sein... + +[LOVE1_6] +~r~Der alte Asiate ist von uns gegangen! + +[LOVE1_7] +~g~Das Tor öffnet sich nur für Autos der kolumbianischen Gang. + +[LOVE2_A] +Nichts lässt die Grundstückspreise so tief purzeln wie ein guter alter Bandenkrieg, + +[LOVE2_B] +außer vielleicht eine Pestepidemie... Aber das ginge hier wohl zu weit. + +[LOVE2_C] +Wie ich bemerkt habe, sind die Yakuza und die Kolumbianer nicht eben Busenfreunde. + +[LOVE2_D] +Daraus sollte man Kapital schlagen. + +[LOVE2_E] +Ich möchte, dass du dir den Yakuza WAKA-Gashira Kenji Kasen vornimmst. + +[LOVE2_F] +Kenji ist bei einem Treffen auf dem Dach der Parkgarage in Newport. + +[LOVE2_G] +Besorg dir einen Wagen des Kartells und nimm ihn dir vor. + +[LOVE2_H] +Die Yakuza werden das als Kriegserklärung des Kartells auffassen. + +[LOVE2_1] +~g~Klau in Fort Staunton einen Wagen der kolumbianischen Gang! + +[LOVE2_2] +~g~Fahre jetzt zu dem ~p~Parkhaus in Newport~g~ und zieh Kenji aus dem Verkehr! + +[LOVE2_3] +~r~Wenn du nicht mit einem Auto des Kartells aufkreuzt, wird man dich erkennen! + +[LOVE2_4] +~r~Die Yakuza haben dich erkannt! + +[LOVE2_6] +~r~Du hast alle Zeugen aus dem Weg geräumt!! + +[LOVE3_A] +In scheinheiligen Zeiten wie diesen sind bestimmte wertvolle Waren schwer zu importieren. + +[LOVE3_C] +Es wird einige kleine Päckchen ins Wasser abwerfen. + +[LOVE3_D] +Sammle sie ein, bevor es ein anderer tut. + +[LOVE3_1] +~g~Besorg dir ein ~r~Boot~g~ und folge dem ~y~Flugzeug~g~! + +[LOVE4] +'DER FLUGHAFEN-COUP' + +[LOVE5] +'BODYGUARD ACTION' + +[LOVE4_A] +Danke, dass du die Päckchen geholt hast. Aber die sollten nur als Köder dienen. + +[LOVE4_B] +Sorry, aber so läuft das manchmal in dem Geschäft. + +[LOVE4_C] +Die Ware, um die es mir wirklich geht, ist noch in dem Flugzeug versteckt. + +[LOVE4_F] +Ich habe die Beamten bestochen. + +[LOVE4_1] +~r~Das kolumbianische Kartell ist da!! + +[LOVE4_2] +~g~Das Päckchen ist weg! Du musst die Kolumbianer finden und es ihnen abjagen. + +[LOVE4_3] +~g~Bauunternehmen Panlantic...? + +[LOVE4_5] +~g~Das Päckchen müsste im Flugzeug sein... + +[LOVE4_6] +~g~Nimm den Lift nach oben in den Tower! + +[LOVE5_B] +Mein asiatischer Freund braucht einen Bodyguard. Er lässt meine neue Lieferung auf Qualität überprüfen. + +[LOVE5_1] +~g~Los! + +[LOVE5_2] +~g~Du wirst ein Auto brauchen! + +[LOVE5_3] +~g~Los, sieh nach, ob am Tunnelende die Luft rein ist. + +[LOVE5_4] +~r~Dem Truck darf nichts passieren! + +[RM6] +'IM FADENKREUZ' + +[RM6_A] +Dir ist niemand gefolgt? Gut. + +[RM6_B] +Es wird langsam brenzlig, ich stecke bis zum Hals in der Scheiße! + +[RM6_D] +Die haben mich im Visier, ich muss mich absetzen. + +[RM6_E] +Wenn du mich rechtzeitig zum Flughafen bringst, lass ich ordentlich was springen. + +[RM6_666] +Pass gut auf meinen kugelsicheren Patriot auf, Ray. Wir sehen uns in Miami. + +[CAT1] +'LÖSEGELD' + +[CAT2] +'DIE ÜBERGABE' + +[CAT1_A] +Ich habe deine Maria. Wenn ihr Gesicht nicht aussehen soll, als wär's in einen Fleischwolf geraten, + +[CAT2_F] +Ich hab mir 'nen Fingernagel abgebrochen und meine Frisur ist hin! Fünfzig Dollar im Eimer! + +[CAT2_G] +Mann, hatte ich Angst. Aber dann dachte ich mir, du bist doch kein kleines Mädchen mehr. + +[CAT2_H] +Du, das wird lustig, weißt du, meine Schwester will nämlich mit ihren zwei Kindern eine zeitlang bei uns wohnen, + +[CAT2_I] +weil ihr Mann gerade mal wieder fremdgeht und... + +[CAT1_C] +XXXX + +[CAT1_D] +XXXX + +[CAT1_E] +XXXX + +[CAT1_F] +Du musst rechtzeitig bei Catalina sein! + +[CAT1_G] +XXXX + +[CAT1_H] +XXXX + +[CAT1_I] +XXXX + +[CAT1_J] +XXXX + +[CAT1_K] +XXXX + +[CAT1_L] +XXXX + +[AS4_1] +XXXX + +[CAT_MON] +~g~Du hast noch nicht genug Geld. Du brauchst $500.000. + +[BITCH_D] +~g~Maria ist tot! + +[WEATHER] +WETTER ÄNDERN + +[WEATHE2] +WETTER NORMAL + +[8001] +Du hast komplett versagt! + +[1000] +DU BIST TOT + +[1001] +DU BIST TOT + +[1002] +DU BIST TOT + +[1003] +DU BIST TOT + +[1004] +DU BIST TOT + +[1005] +VERHAFTET + +[1006] +VERHAFTET + +[1007] +VERHAFTET + +[1008] +VERHAFTET + +[1009] +VERHAFTET + +[GA_4] +Autobomben kosten $1000 pro Stück. + +[GA_5] +In deinem Wagen ist schon eine Autobombe. + +[GA_6] { re3 change } +Park die Karre, mach sie durch Drücken der ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~ scharf, und dann nichts wie weg! + +[GA_7] { re3 change } +Mach die Bombe mit der ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~ scharf. Dann geht sie hoch, wenn der Wagen angelassen wird. + +[GA_8] +Benutze den Zünder, um die Bombe hochgehen zu lassen. + +[GA_9] +Du hast ~1~ von zehn Spezialautos beschafft. + +[GA_10] +Hübsche Karre. Hier sind deine $~1~. + +[GA_11] +So eine Karre haben wir schon. Die können wir nicht gebrauchen. + +[GA_12] +Bombe ist scharf. + +[GA_13] +Auf dich ist Verlass. Wenn du alle, die auf der Liste stehen, abgeliefert hast, kriegst du einen Bonus. + +[GA_14] +Du hast alle georderten Karren geliefert. Sehr gut. Hier, für dich. + +[GA_15] +Hoffentlich gefällt dir die neue Farbe. + +[GA_16] +Das Umspritzen ist gratis. + +[GA_19] +An dem Modell haben wir kein Interesse. + +[GA_20] +Von der Sorte haben wir schon mehr als genug. Sorry, da kommen wir nicht ins Geschäft. + +[CR_1] +Kran kann dieses Fahrzeug nicht anheben. + +[PU_MONY] +Du hast nicht genug Geld. + +[CO_ALL] +Du hast sie alle geliefert. Hier, für dich. + +[PAUSED] +PAUSE + +[HEALTH1] +Stell dich nicht so an! Dir fehlt doch überhaupt nichts. + +[HEALTH2] +Medizinische Versorgung ist teuer. + +[HEALTH3] +Ich flick dich wieder zusammen. + +[HEALTH4] +Das macht $250. + +[FEB_STA] +Statistik + +[FEB_BRI] +Mission + +[FEB_CON] +Steuerung + +[FEB_AUD] +Audio + +[FEB_DIS] +Anzeige + +[FEB_LAN] +Sprache + +[FEP_STA] +STATISTIKEN + +[FEP_BRI] +MISSIONSINFOS + +[FEP_CON] +STEUERUNG + +[FEP_AUD] +AUDIO + +[FEP_DIS] +ANZEIGE + +[FEP_LAN] +SPRACHE + +[FEF_ST1] +Wer ist der Schurke? + +[FEF_ST2] +Wie viel Chaos hast du angerichtet? + +[FEF_BR1] +Du blickst bei der Story nicht mehr durch? + +[FEF_CO1] +Taste dich ran, Mann! + +[FEF_CO2] +Wähle das Controller-Setup, das zu deinem Spielstil am besten passt + +[FEF_SA1] +Bring deine Daten in Sicherheit! + +[FEF_SA2] +Spiele laden und speichern + +[FEF_AU1] +Volle Dröhnung gefällig? + +[FEF_AU2] +Radiosender und Soundeffekt auswählen + +[FEF_DI1] +Andere Optik? + +[FEF_DI2] +Spiel für deinen Fernseher optimieren + +[FEF_LA1] +Was für ein Gefasel! + +[FEF_LA2] +Sprache auswählen + +[FEB_PMB] +Vorherige Missionsinfos: + +[FEC_NA] +Nicht verfügbar + +[FEC_CWL] +Eine Waffe nach links + +[FEC_CWR] +Eine Waffe nach rechts + +[FEC_LOF] +Nach vorne schauen + +[FEC_TAR] +Zielen + +[FEC_MOV] +Bewegung + +[FEC_CAM] +Blickwinkel + +[FEC_PAU] +Pause + +[FEC_ENV] +In Fahrzeug einsteigen + +[FEC_JUM] +Springen + +[FEC_ATT] +Angreifen\Waffe abfeuern + +[FEC_RUN] +Rennen + +[FEC_FPC] +Subjektive Kamera + +[FEC_LB1] +Schau + +[FEC_LL] +Nach links schauen + +[FEC_LB2] +nach hinten + +[FEC_LB] +Nach hinten schauen + +[FEC_LR] +Nach rechts schauen + +[FEC_HOR] +Hupe + +[FEC_VES] +Fahrzeug steuern + +[FEC_RSC] +Radiosender auswählen + +[FEC_BRA] +Bremsen\rückwärts fahren + +[FEC_HAB] +Handbremse + +[FEC_CAW] +Fahrzeugwaffe + +[FEC_ACC] +Beschleunigen + +[FEC_SMT] +Spezialmission aktivieren + +[FEA_OUT] +Tonausgabe: + +[FEA_ST] +Stereo + +[FEA_MNO] +Mono + +[FEA_NON] +Keinen + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Debug Streaming Requests + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Memory Card Menü + +[FEM_RMC] +Register MemCard One + +[FEM_TFM] +Test Format MemCard One + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_CRD] +Create Root Dir + +[FEM_CLI] +Create And Load Icons + +[FEM_FFF] +Fill First File with Guff + +[FEM_SOG] +Save Only The Game + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_STG] +Save The Game + +[FEM_STS] +Save The Game under GTA3 name + +[FEM_CPD] +Create copy protected mag directory + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_TS] +Test Save: + +[FEM_TL] +Test Load: + +[FEM_TD] +Test Delete: + +[PL_STAT] +Spielerstatistiken + +[PE_WAST] +Von dir abservierte Personen + +[PE_WSOT] +Von anderen abservierte Personen + +[CAR_EXP] +Explodierte Autos + +[TM_BUST] +Zahl deiner Verhaftungen + +[M_WASTE] +Männliche Passanten + +[F_WASTE] +Weibliche Passanten + +[PIG_WST] +Cops + +[GNG_WST] +Gang-Mitglieder + +[MED_WST] +Sanitäter + +[FIRE_WS] +Feuerwehrmänner + +[DED_CRI] +Kriminelle + +[DED_DED] +Schnorrer + +[DED_HOK] +Girls + +[HEL_DST] +Zerstörte Helikopter + +[PER_COM] +Absolviert (in Prozent) + +[KGS_EXP] +Sprengstoffverbrauch in kg + +[ACCURA] +Treffsicherheit + +[ELBURRO] +Beste Turismo-Zeit in Sekunden + +[CAR_CRU] +Geschrottete Autos + +[HED_EX] +Köpfe + +[TM_DED] +Krankenhausbesuche + +[DAYSPS] +Im Spiel verstrichene Tage + +[MMRAIN] +Regenfälle in mm + +[MXCARD] +Weitester IRRSINNS-Sprung (in Fuß) + +[MXCARJ] +Höchster IRRSINNS-Sprung (in Fuß) + +[MXCARDM] +Weitester IRRSINNS-Sprung (m) + +[MXCARJM] +Höchster IRRSINNS-Sprung (m) + +[MXFLIP] +Max. Anzahl Saltos + +[MXJUMP] +Max. Drehungen im Sprung + +[BSTSTU] +Bester IRRSINNS-Stunt bisher: + +[INSTUN] +Irrsinns-Stunt + +[PRINST] +Super Irrsinns-Stunt + +[DBINST] +Doppelter Irrsinns-Stunt + +[DBPINS] +Super-Doppel-Irrsinns-Stunt + +[TRINST] +Dreifacher Irrsinns-Stunt + +[PRTRST] +Super-Dreifach-Irrsinns-Stunt + +[QUINST] +Vierfacher Irrsinns-Stunt + +[PQUINS] +Super-Vierfach-Irrsinns-Stunt + +[NOSTUC] +Bisher keine Stunts geschafft + +[NOUNIF] +Monster-Stunts geschafft + +[NOUNGM] +Monster-Stunts insgesamt + +[NMISON] +Begonnene Missionen + +[NMMISP] +Erfüllte Missionen + +[PASDRO] +Beförderte Fahrgäste + +[MONTAX] +Mit Taxi verdientes Geld + +[DAYPLC] +Tagesetat für Polizei + +[CRIMRA] +Punktzahl: + +[GMSTOR] +Spielarchiv + +[PREBRF] +Vorherige Missionsinfos + +[CNTLS] +Steuerung + +[MUSMEN] +Musik SFX + +[GAMSET] +Spieleinstellungen + +[LANGUA] +Sprache + +[DSPLAY] +Anzeige + +[DEBUGM] +Debug Menu + +[QUITOP] +Optionen verlassen + +[CONTRL] +Konfiguration d. Steuerung + +[SET1EN] +Konfiguration 1 aktiviert + +[SET1] +Konfiguration 1 + +[SET2EN] +Konfiguration 2 aktiviert + +[SET2] +Konfiguration 2 + +[SET3EN] +Konfiguration 3 aktiviert + +[SET3] +Konfiguration 3 + +[SET4EN] +Konfiguration 4 aktiviert + +[SET4] +Konfiguration 4 + +[GOBACK] +Zurück + +[SOUND] +SOUND + +[MUSVOL] +Lautstärke Musik + +[SFXVOL] +Lautstärke SFX + +[SCROPT] +BILDSCHIRMOPTIONEN + +[CTRSCR] +Bildschirm zentrieren + +[SCRFOR] +Bildschirmformat + +[GMSVLQ] +SPEICHERN-LADEN-BEENDEN + +[GMREST] +Spiel neu starten + +[NOGMSV] +Du kannst nur in deinem Unterschlupf speichern. + +[DLFILE] +GTA3 Dateien löschen + +[CHFILE] +ZU LADENDE DATEI AUSWÄHLEN + +[CHCDLD] +Wähle Memory Card (PS2) von der geladen werden soll + +[CDUNFR] +Memory Card (PS2) ist nicht formatiert. + +[CHFIDL] +ZU LÖSCHENDE DATEI AUSWÄHLEN + +[SVCONF] +SPEICHERBESTÄTIGUNG + +[SVFNAM] +Dateiname des gespeicherten Spiels: + +[SAVEDN] +Fehler - Speicherung nicht vollständig. + +[LANGSL] +SPRACHAUSWAHL + +[ENGLIS] +Englisch + +[GERMAN] +Deutsch + +[ITALIA] +Italienisch + +[FRENCH] +Französisch + +[SPAIN] +Spanisch + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (teststuff) + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[GORLEV] +Gewalt-Level + +[SICASS] +Mittel + +[SICSIC] +Hoch + +[SCASSL] +Gewalt-Level 'mittel' gewählt + +[SCSCSL] +Gewalt-Level 'hoch' gewählt + +[PRVMEN] +Vorherige Missionsinfos + +[DOSVGM] +Spiel speichern? + +[FORMEN] +Formatierungsmenü + +[MEMTST] +Memory Card-Testmenü + +[REGCAR] +Memory Card 1 registrieren + +[TEFONE] +Memory Card 1 testweise formatieren + +[TEUFON] +Memory Card 1 testweise de-formatieren + +[CRROOT] +CreateRootDir + +[CRLDIC] +Create and Load Icons + +[FLFSGF] +Fill First File With Guff + +[PUSAVE] +Save Only the game + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +SaveTheGame + +[CNTSAV] +Spiel kann nicht gespeichert werden. Mitten in Mission. + +[CNCSAV] +Spiel kann nicht gespeichert werden. Du bist im Auto. + +[CRMGSV] +Create copy protected magazine directory + +[MGSVCN] +MagazineDirectory Created + +[MGSVNC] +MagazineDirectory Not Created + +[YES] +Ja + +[NO] +Nein + +[X] +x + +[LAST] +Letzte Nachricht + +[FEDS_XB] +Auswählen + +[FEDS_ST] +START-Taste - WEITER + +[FEST_OO] +von + +[FEC_TUC] +Geschützsteuerung + +[FEC_SM3] +Spezialmission aktivieren (R3-Taste) + +[FEC_RS3] +Radiosender auswählen (L3-Taste) + +[FEC_HO3] +Hupe (L3-Taste) + +[DIAB1] +'GRAN TURISMO' + +[DIAB2] +'BRANDHEISSE EISCREME' + +[DIAB3] +'FEUERTAUFE' + +[DIAB4] +'JÄGER DES VERLORENEN SCHUNDES' + +[DIAB1_A] +El Burro bietet dir eine Chance. Über den öffentlichen Fernsprecher in Hepburn Heights erfährst du näheres. + +[DIAB1_C] +Du bist kein übler Fahrer. Komm wieder zu dem Telefon. Vielleicht hat El Burro noch mehr Jobs für dich. + +[DIAB1_1] +~g~3...2...1... LOS. LOS, LOS! + +[DIAB1_4] +~g~Schnapp dir einen schnellen Wagen und fahr zum Start. + +[DIAB1_3] +~r~Du würdest nicht mal beim Sackhüpfen gewinnen, du LOSER! + +[DIAB1_2] +~g~Gratulation! Du hast gesiegt. In der unglaublichen Zeit von ~1~ Sekunden. + +[FIRST] +~g~1. + +[SECOND] +~g~2. + +[THIRD] +~g~3. + +[FOURTH] +~g~4. + +[DIAB2_1] +~g~Hol die Aktentasche in Harwood ab. + +[DIAB2_2] +~g~Such dir einen Eis-Wagen. + +[DIAB2_3] +~g~Parke den Eis-Wagen unten bei den Atlantic Quays. + +[DIAB2_4] +~g~Drücke die ~w~~k~~VEHICLE_HORN~-Taste~g~, um den Eiscreme-Jingle abzuspielen. + +[DIAB2_6] +~g~Drücke die ~w~~k~~VEHICLE_HORN~-Taste~g~, um den Eiscreme-Jingle abzuspielen. + +[DIAB2_7] +~g~Drücke die ~w~~k~~VEHICLE_HORN~-Taste~g~, um den Eiscreme-Jingle abzuspielen. + +[DIAB2_5] +~g~Steig aus und sprenge den Eis-Wagen mit dem Fernzünder. + +[YD1] +'SCHNELLE AUTOS, SCHNELLES GELD' + +[YD2] +'UZI RIDER' + +[YD3] +'DER GROSSE AUTOKLAU' + +[YD4] +'TAG DER RACHE' + +[YD_P] +King Courtney will dich sprechen - am Telefon in Aspatria!! + +[YD1_A] +~w~Hier spricht King Courtney. + +[YD1_A1] +~w~Meine Yardies könnten einen Fahrer brauchen, und du hast keinen schlechten Ruf. + +[YD1_B] +~w~Fahr mit einem Wagen zu dem Gelände gegenüber dem Stadion und warte auf die anderen Mitbewerber. + +[YD1_C] +~w~Meine Männer beobachten Checkpoints überall in Staunton. + +[YD1_D] +~w~Wer einen Checkpoint als erster erreicht, kriegt $1000. Dann geht's weiter zur nächsten Station. + +[YD1_D1] +~w~Wenn du mehr Checkpoints als die anderen gewinnst, habe ich vielleicht Arbeit für dich. + +[YD1_E] +~g~Fertigmachen zum Start! + +[YD1_F] +~g~Du bist zu früh gestartet. Das gefällt mir!! + +[YD1_G] +~r~Dies ist ein AUTORENNEN. Du brauchst ein AUTO, Hirni! + +[YD1GO] +~g~LOS!! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000!! + +[Y1_1ST] +~g~Du bist Erster mit ~1~ gewonnenen Checkpoints! + +[Y1_2ND] +~y~Zweiter mit ~1~ gewonnenen Checkpoints. ~y~Tja, knapp daneben ist auch versagt! + +[Y1_3RD] +~r~Dritter mit ~1~ gewonnenen Checkpoints. ~r~Findest du das okay? + +[Y1_LAST] +~r~Du bist Letzter! ~r~Du vergeudest meine Zeit, BLÖDMANN! + +[Y1_J1ST] +~y~Du teilst dir den 1. Platz. ~1~ gewonnene Checkpoints. ~y~Gut, aber du musst der Beste sein, um für Queen Lizzy zu fahren! + +[Y1_J2ND] +~r~Du teilst dir den 2. Platz. ~1~ gewonnene Checkpoints. Du Schläfer! + +[Y1JLAST] +~r~Du bist unter den Letzten! Wo hast du deinen Führerschein gemacht? + +[Y1_TEST] +AUTO IM WASSER!! + +[YD2_A] +~w~Ich will sehen, ob du die Drecksjobs für mich machen kannst. + +[YD2_A1] +Mal sehen, ob man dir trauen kann. + +[YD2_B] +Gleich kommen zwei meiner Jungs und holen dich ab. + +[YD2_B1] +Wollen sehen, ob du so gut bist, wie du sagst. + +[YD2_C] +~w~Wir fahren nach Hepburn Heights und nehmen uns ein paar Diablos vor, die Queen Lizzy angemacht haben. + +[YD2_CC] +~w~Hier, du wirst 'ne Knarre brauchen. + +[YD2_D] +~w~Du fährst UND ballerst. Wir achten drauf, dass du keine kalten Füße kriegst. + +[YD2_E] +~w~Los geht's!! + +[YD2_F] +~r~Er haut ab! Schnapp dir den Feigling!!! + +[YD2_G1] +~w~Hepburn Heights. Knöpf dir ein paar Diablos vor. + +[YD2_G2] +~w~Aber denk dran, ~r~du steigst nicht aus dem Wagen!! + +[YD2_H] +~w~Okay, fahr uns zurück auf Yardie-Gebiet! LOS, LOS, LOS!! + +[YD2_L] +~w~Gut gemacht, Sichler! + +[YD2_M] +~r~Er hat mein Auto geschrottet! Mach ihn fertig! + +[YD2_N] +~w~Steig sofort wieder in den Wagen! + +[YD3_A] +Besorg ein paar Bandenautos, + +[YD3_A1] +damit wir auf Feindgebiet operieren können. + +[YD3_B] +Ich brauche einen Mafia Sentinel, + +[YD3_B1] +einen Yakuza Stinger und einen + +[YD3_B2] +Diablo Stallion. So können wir jede Gang in Liberty angreifen. + +[YD3_C] +Stell sie in der Garage in Newport ab. Aber denk dran: , + +[YD3_C1] +Geschrottet nützen sie uns nichts!! + +[YD3_D] +Spare text label + +[YD3_E] +~r~Du hast bereits einen Diablo-Wagen! + +[YD3_F] +~r~Du hast bereits einen Mafia-Wagen! + +[YD3_G] +~r~Du hast bereits einen Yakuza-Wagen! + +[YD3_H] +~g~Diablo-Wagen besorgt! + +[YD3_I] +~g~Mafia-Wagen besorgt! + +[YD3_J] +~g~Yakuza-Wagen besorgt! + +[YD3_K] +~r~Das Auto ist praktisch Schrott! Repariere es! + +[YD3_L] +~g~Fahr das Auto zu der ~p~Garage! + +[YD3_M] +~r~Du hast dich überschlagen! Besorg ein anderes Auto! + +[YD4_A] +Hör zu! + +[YD4_A1] +Komm nach Bedford Point. + +[YD4_A2] +In einem alten Wagen ist etwas versteckt. Das brauche ich pronto! + +[YD4_B] +BRIEF: Man hört, du bist ein viel beschäftigter Mann. Nun, ich bin eine viel beschäftigte Frau. + +[YD4_C] +Es wird Zeit, dass du die wahre Macht von 'SPANK' kennen lernst! Besos y fuderes, Catalina, xxx. + +[YD4_D] +PS: FAHR ZUR HÖLLE!! + +[YD4_1] +Irre auf SPANK!! + +[YD4_2] +~g~Zerstöre die SPANK-Lieferwagen!! + +[HM_1] +'DRIVE-BY ACTION' + +[HM_2] +'TÖDLICHES SPIELZEUG' + +[HM_3] +'DAS HÖLLENFAHRTSKOMMANDO' + +[HM_5] +'SHOWDOWN' + +[HOOD1_A] +Komm zu dem Fernsprecher in Wichita Gardens. Es gibt Arbeit. + +[HM1_A] +Yo! Hier spricht D-Ice von den Red Jacks! + +[HM1_C] +Diese pubertären Idioten treiben sich hier rum und haben nichts als Knarren und SPANK im Sinn. + +[HM1_3] +~g~Die 'Nines' hängen in ihrem Gebiet in Wichita Gardens rum. + +[HM2_3] +Wenn du die Reifen eines Wagens triffst, explodiert der Buggy! + +[HM2_4] +Wenn er außer Reichweite kommt, explodiert der Buggy! + +[HM2_5] +~r~Außer Reichweite! + +[HM3_1] +~g~Fahr zur Garage. Aber Vorsicht: Wird der Wagen zu stark beschädigt, explodiert er! + +[HM3_2] +~g~Bring den Wagen zurück. Er muss in 1A Zustand sein. Keine Delle! + +[HM3_3] +~g~Repariere den Wagen! + +[HM4_D] +~g~Besorg dir ein Fahrzeug! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_1] +~g~Begib dich an den Ort, wo die Ladung herumliegt. Du musst 30 Goldbarren einsammeln. + +[HM4_2] +~g~Denk dran: Wird der Wagen zu schwer und zu langsam, fahr in die Garage und lade das Zeug aus. + +[HM5_3] +~r~Du solltest nur einen Baseball-Schläger verwenden! + +[HM5_4] +~r~Deine Kontaktperson ist tot! + +[MEA1] +'DER ABKOCHER' + +[MEA2] +'DIE DIEBE' + +[MEA3] +'DIE FRAU' + +[MEA4] +'IHR LIEBHABER' + +[MEAT1_A] +Man sagt, du kannst mir bei einigen Problemen helfen. Wenn das so ist, komm zu dem Fernsprecher in Trenton. + +[MEA1_B3] +~g~Triff dich mit dem Bankier. + +[MEA1_B6] +~g~Fahr das Auto zur Schrottpresse, um Beweise zu beseitigen. Steig aus, der Kran hebt das Auto rein. + +[MEA1_1] +~r~Der Bankier ist tot! + +[MEA1_2] +~r~Du solltest den Wagen doch verschrotten! + +[MEA1_3] +~g~Steig aus dem Wagen! + +[MEA1_4] +~r~Du hast den Bankier nicht mitgenommen! + +[MEA2_B3] +~g~Triff dich mit den Dieben. + +[MEA2_B4] +~g~Bring sie zur Bitchin' Dog Food Fleischfabrik. + +[MEA2_B6] +~g~Spritz den Wagen um, um Beweise zu beseitigen. + +[MEA2_1] +~r~Du solltest den Wagen doch verschrotten! + +[MEA2_2] +~r~Ein Dieb ist tot! + +[MEA2_4] +~r~Du hast einen Dieb nicht mitgenommen! + +[MEA3_B3] +~g~Hole Mrs. Chonks ab. + +[MEA3_B6] +~g~Versenke den Wagen im Meer, um Beweismaterial zu beseitigen. + +[MEA3_1] +~r~Die Frau ist tot! + +[MEA3_2] +~r~Du solltest den Wagen doch im Meer versenken! + +[MEA3_3] +~r~Du hast die Frau nicht mitgenommen! + +[MEA4_B3] +~g~Hol den Liebhaber seiner Frau ab. + +[MEA4_B6] +Dazu ist es jetzt zu spät, Marty. Du hattest deine Chance. Jetzt übernehme ich den Laden hier. + +[MEA4_1] +~r~Carlos ist tot! + +[MEA4_3] +~r~Du hast Carlos den Kredithai nicht mitgenommen! + +[LOOK_A] +Halte die ~h~~k~~VEHICLE_LOOKLEFT~-Taste ~w~oder die ~h~~k~~VEHICLE_LOOKRIGHT~-Taste~w~ gedrückt, um im Wagen sitzend nach ~h~links~w~ oder ~h~rechts~w~ zu sehen. Drücke beide Tasten, um nach ~h~hinten~w~ zu sehen. + +[LOVE6_1] +~g~Locke jetzt die Cops vom Lagerhaus weg! + +[LOVE6_2] +~r~Du hast die Cops nicht weit genug weggelockt! + +[RM4_3] +~r~Rays Partner ist entkommen! + +[RM6_C] +Die CIA scheint sich sehr für SPANK zu interessieren. + +[RM6_C1] +Sie wollen, dass wir das Kartell in Ruhe lassen. + +[C_PASS] +BEDROHUNG AUSGERÄUMT! + +[CTUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Bürgerwehr Missionen zu aktivieren oder zu deaktivieren. + +[CTUTOR2] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Bürgerwehr Missionen zu aktivieren oder zu deaktivieren. + +[COPCART] +~g~Du hast ~1~ Sekunden, um zu einem Polizeiwagen zurückzukehren, bevor die Mission endet. + +[C_FAIL] +Mission beendet! + +[C_CANC] +~r~Bürgerwehr Mission abgebrochen! + +[C_ESCP] +~r~Der Verdächtige ist entwischt! + +[C_TIME] +~r~Deine Zeit als Gesetzeshüter ist vorbei! + +[C_VIGIL] +BÜRGERWEHR BONUS!! + +[A_FAIL2] +~r~Deine Bummelei war tödlich für den Patienten! + +[A_FAIL3] +~r~Der Patient ist tot!! + +[A_PASS] +Gerettet! + +[F_FAIL2] +~r~Du kommst zu spät! + +[A_COMP2] +Du ermüdest nie! + +[RM2_M] +Wenn du eine Waffe brauchst, komm vorbei und nimm dir aus den Kästen, was du brauchst. + +[HEAL_A] +Dein ~h~Gesundheit~w~ wird rechts oben auf dem Bildschirm in Orange angezeigt. + +[YD1_CNT] +~1~ von 15! + +[FM1_9] +~g~Da vorne ist die Party. Setz Maria vor dem Gebäude ab. + +[FM1_Y] +~w~Das war seit langem mal wieder ein guter Abend. Und du hast mich wirklich gut behandelt, mit Respekt und so. + +[FM1_AA] +~w~Oh, ich geh jetzt besser. Ich hoffe, wir sehen uns. + +[NOCONTE] +Bitte stecken Sie einen Analog Controller (DUALSHOCK#) oder einen Analog Controller (DUALSHOCK#2) in Controller-Anschluss 1, um fortzufahren. + +[WRCONT] +Der Controller an Controller-Anschluss 1 wird nicht unterstützt. GTA3 benötigt einen Analog Controller (DUALSHOCK#) oder einen Analog Controller (DUALSHOCK#2). + +[WRCONTE] +Der Controller an Controller-Anschluss 1 wird nicht unterstützt. GTA3 benötigt einen Analog Controller (DUALSHOCK#) oder einen Analog Controller (DUALSHOCK#2). + +[WRONGCD] +Falsche DVD. Bitte legen Sie die richtige DVD ein. + +[NOCD] +Die DVD-Lade ist leer. Bitte DVD einlegen. + +[OPENCD] +Die DVD-Lade ist offen. Bitte schließen. + +[CDERROR] +Fehler beim Lesen der GTA3 DVD. + +[RESTART] +Neues Spiel wird gestartet + +[GA_3] +Keine Gratisjobs mehr. Umspritzen kostet $1000! + +[GA_1] +So was heißes rühre ich nicht an! + +[GA_1A] +Komm wieder, wenn du nicht so viel zu tun hast... + +[S_PROM2] +In die Garage nebenan kann 1 Fahrzeug eingestellt werden, wenn du das Spiel speicherst. + +[STOCK] +Nicht vorrätig + +[FM1_O] +~w~Er ist beim Bahnhof am Chinatown-Ufer, glaube ich. + +[EBAL_B] +Hier ist es! Los, wir tauchen ab und besorgen uns neue Klamotten! + +[EBAL_G] +Hier ist Luigis Club. Wir gehen hinten rum und nehmen den Lieferanteneingang. + +[AM4_3] +Du musst Asukas neuer Botenjunge sein! + +[AM4_4] +Hast du das Geld? Die ganze Summe? + +[AM4_5] +Ich weiß, was du denkst. Wieder so ein korrupter Cop. + +[AM4_6] +Tja, die Welt ist schlecht. + +[AM4_7] +Nur weil ich ein paar Partner verloren habe, sitzen mir die Typen von der Dienstaufsicht im Genick. + +[AM4_8] +Die glauben, ich habe Dreck am Stecken. + +[AM4_9] +Tja, die ganze Stadt ist ein einziges Dreckloch. + +[AM4_10] +Aber ich brauch Hilfe von außerhalb der Polizei. + +[AM4_11] +Falls du interessiert bist... du weißt, wo du mich findest. + +[CAM_A] +Drücke die ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~-taste~w~, um den ~h~Blickwinkel ~w~zu verändern, wenn du zu Fuß oder in einem Fahrzeug unterwegs bist. + +[CAM_B] +Drücke die ~h~Richtungstaste Oben~w~ und die ~h~Richtungstaste Unten~w~, um den ~h~Blickwinkel ~w~zu verändern, wenn du zu Fuß oder in einem Fahrzeug unterwegs bist. + +[KM2_1] +~g~Repariere den Wagen. Er muss top aussehen. + +[LM3_6] +Joey... + +[LM3_6A] +Na, ein bisschen Nahkampf mit deiner Braut gefällig? + +[LM3_9A] +Vielleicht gibt's Arbeit für dich. + +[LM3_9B] +Okay? + +[AWAY2] +~r~Sie sind entkommen. + +[AWAY] +~r~Er ist verschwunden! + +[JM6_1] +Fahr zu der Bank auf dem Boulevard. + +[GA_6B] { re3 change } +Park die Karre, mach sie durch Drücken der ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~ scharf, und dann HAU AB! + +[GA_7B] { re3 change } +Mach die Bombe mit der ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~ scharf. Sie geht hoch, wenn der Wagen angelassen wird. + +[BAT1] +~g~Nimm dir den Schläger! + +[EBAL_O] +Wenn du deine Sache gut machst, gibt's vielleicht noch mehr Jobs für dich. Und jetzt verschwinde! + +[HELP9_B] +Drücke die ~h~~k~~PED_FIREWEAPON~-Taste~w~, um mit dem Präzisionsgewehr zu ~h~feuern~w~. + +[HELP9_C] +Drücke die ~h~~k~~PED_FIREWEAPON~-Taste~w~, um mit dem Präzisionsgewehr zu ~h~feuern~w~. + +[JM6_8] +~r~Du hast alle Räuber verloren! + +[COLT_IN] +Die Pistole ist jetzt im AmmuNation vorrätig! + +[TAXI2] +~r~Die Zeit ist um! + +[TAXI3] +~r~Dein Fahrgast ist entsetzt geflohen! + +[TAXI7] +~r~Dein Wagen ist Schrott. Repariere ihn. + +[TAXI4] +Fahrt abgeschlossen! + +[TAXI5] +SPEED BONUS!! + +[TAXI6] +Taxi-Mission beendet + +[FRANGO] +~g~Salvatore sagt, du sollst zuerst Toni mit den Triaden helfen. + +[PAGEB12] +Polizei-Schmiergelder wurde im Versteck angeliefert. + +[PAGEB13] +Gesundheits-Powerups wurde im Versteck angeliefert. + +[PAGEB14] +Adrenalin wurde im Versteck vorrätig. + +[KM1_4] +~g~Du brauchst einen Polizeiwagen für diesen Job! + +[CAT1_B] +dann bring $500 000 zu der Villa in Cedar Grove. + +[JM2_C] +Er hat eine Imbissbude in Chinatown. + +[RM6_1] +Hier ist ein Schlüssel für eine Garage. + +[RM6_2] +Darin findest du Bargeld und ein paar 'Requisiten' für alle Fälle. + +[RM6_3] +Bis dann. + +[FE_INIP] +Initalisiere und lade Pausenmenü. Bitte warten. + +[FESZ_CA] +Abbrechen + +[FESZ_QU] +Beenden + +[FESZ_L1] +Spiel erfolgreich gespeichert! + +[FESZ_L2] +Spiel gespeichert unter: + +[FESZ_OK] +OK + +[FES_LGA] +Spiel laden + +[FES_NGA] +Neues Spiel + +[FES_CAN] +Abbrechen + +[FESZ_QL] +Alle nicht gespeicherten Daten des aktuellen Spiels werden verlorengehen. Ladevorgang fortsetzen? + +[FESZ_QD] +Dieses gespeicherte Spiel wirklich löschen? + +[FESZ_QO] +Dieses gespeicherte Spiel wirklich überschreiben? + +[FESZ_QR] +Wirklich ein neues Spiel beginnen? Alle Daten seit dem letzten Speichern werden verlorengehen. Weiter? + +[FESZ_QS] +SPEICHERN FORTSETZEN? + +[SLONFP] +MEMORY CARD-Steckplatz 1: Datei geschützt. + +[T4X4_1] +'PATRIOT RALLYE' + +[T4X4_2] +'SPAZIERFAHRT IM PARK' + +[T4X4_3] +'CHECKPOINT FIEBER' + +[MM_1] +'VOLLGAS IM PARKHAUS' + +[T4X4_1A] +~g~Du hast ~y~5 Minuten~g~, um ~y~15~g~ Checkpoints abzufahren. ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[T4X4_1B] +~1~ von 15! + +[T4X4_1C] +~y~PASSIERE~g~ den ersten Checkpoint, dann läuft die Zeit. ~g~Jeder Checkpoint bringt dir ~y~20 SEKUNDEN~g~. + +[T4X4_2A] +~g~Du hast ~y~2 Minuten~g~, um ~y~12~g~ Checkpoints abzufahren. ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[T4X4_2B] +~1~ von 12! + +[T4X4_2C] +~y~PASSIERE~g~ den ersten Checkpoint, dann läuft die Zeit. ~g~Jeder Checkpoint bringt dir ~y~10 SEKUNDEN~g~. + +[T4X4_3A] +~g~Du hast ~y~5 Minuten~g~, um ~y~20~g~ Checkpoints abzufahren. ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[T4X4_3B] +~y~PASSIERE~g~ den ersten Checkpoint, dann läuft die Zeit. ~g~Jeder Checkpoint bringt dir ~y~15 SEKUNDEN~g~. + +[T4X4_3C] +~1~ von 20! + +[T4X4_F] +~r~Du hast gekniffen! Schon überfordert? + +[MM_1_A] +~g~Du hast ~y~2 Minuten~g~, um ~y~20 Checkpoints~g~ in dem Parkhaus abzufahren! ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[MM_1_B] +~1~ von 20! + +[MM_1_C] +~g~Das macht 20 Sekunden plus ~y~5 SEKUNDEN~g~ für jeden Checkpoint. ~g~Die Zeit läuft ~y~AB SOFORT. + +[FM2_14] +~r~Du warst zu nah dran und hast Curly aufgeschreckt! + +[FM2_15] +~g~Nicht zu nahe ran, sonst schöpft Curly Verdacht! + +[UPSIDE] +~r~Du hast dich überschlagen! + +[FM2_16] +SCHRECK-O-METER: + +[LM3_11] +~g~Misty steigt in keinen Bus. Besorg ein anderes Fahrzeug! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Feuerwehrwagen + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Krankenwagen + +[FBICAR] +F.B.I. + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Polizei + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Zug + +[HELI] +Helikopter + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[BELLYUP] +Triad + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Mafia + +[YARDICR] +Yardie + +[YAKUZCR] +Yakuza + +[DIABLCR] +Diablo + +[COLOMCR] +Cartel + +[HOODSCR] +Hoods + +[AEROPL] +Flugzeug + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Zu Fuß zurückgel. Meilen + +[FEST_DC] +Mit Auto gefahrene Meilen + +[FESTDFM] +Zu Fuß zurückgel. Meter + +[FESTDCM] +Mit Auto gefahrene Meter + +[FEST_R1] +Patriot Rallye in Sek. + +[FEST_R2] +Spazierfahrt im Park in Sek. + +[FEST_R3] +Checkpoint-Fieber in Sek. + +[FEST_RM] +Vollgas durch die Parkgarage in Sek. + +[FEST_LS] +Mit Krankenwagen gerettete Menschen + +[FEST_CC] +Kriminelle bei Bürgerwehr Mission + +[FEST_FE] +Gelöschte Feuer gesamt + +[FEST_LF] +Längster Flug in Dodo + +[FEST_BD] +Bestzeit Bombenentschärfung + +[FEST_RP] +Bestandene Amokfahrten + +[FEST_MP] +Erledigte Missionen + +[FEST_BB] +Schnelle Autos, Schnelles Geld: + +[FEST_H0] +Meiste Checkpoints + +[FEST_GC] +Geschrottete Gang-Autos: + +[FEST_H1] +Im Dschungel der Diablos + +[FEST_H2] +Mafia-Massaker + +[FEST_H3] +Der Casino-Coup + +[FEST_H4] +Rock'n'Roll mit Rumpo + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ] +MONSTER-STUNT-BONUS! + +[SPRAY] +Fahre deinen Wagen in die Lackiererei, um deinen ~h~Fahndungslevel~w~ loszuwerden und das Auto zu ~h~reparieren~w~ und ~h~umzuspritzen~w~. Kosten - ~h~$1000. + +[HM1_1] +~g~Fertige 20 Purple Nines in 2 Min. 30 Sek. ab. + +[KM1_8A] { re3 change } +Drücke die~h~ ~k~~VEHICLE_FIREWEAPON~-Taste~w~ zum ~h~Zünden der Bombe~w~. Aber geh vorher in Deckung! + +[KM1_8D] { re3 change } +Drücke die~h~ ~k~~VEHICLE_FIREWEAPON~-Taste~w~ zum ~h~Zünden der Bombe~w~. Aber geh vorher in Deckung! + +[KM1_12] +~g~Bring ihm zum Dojo, aber häng vorher die Cops ab! + +[RATNG1] +Taschendieb + +[RATNG2] +Laufbursche + +[RATNG3] +Gauner + +[RATNG4] +Soldat + +[RATNG5] +Profi + +[RATNG6] +Fahrer + +[RATNG7] +Gangster + +[RATNG8] +Obergangster + +[RATNG9] +Partner + +[RATNG10] +Troubleshooter + +[RATNG11] +Vollstrecker + +[RATNG12] +Capo + +[RATNG13] +Rechte Hand + +[RATNG14] +Vize + +[RATNG15] +Boss + +[1010] +~r~Dein Fahrzeug liegt auf dem Kopf + +[1011] +~r~Dein Fahrzeug liegt auf dem Kopf + +[1012] +~r~Dein Fahrzeug liegt auf dem Kopf + +[1013] +~r~Dein Fahrzeug liegt auf dem Kopf + +[1014] +~r~Dein Fahrzeug liegt auf dem Kopf + +[JM4_10] +Okay, Junge. Fahr mich zuerst in die Wäscherei in Chinatown. Ich hab da was zu erledigen. + +[JM4_11] +Die Waschweiber da haben ihr Schutzgeld nicht bezahlt. + +[JM4_12] +Und pass auf den Wagen auf. Joey hat ihn gerade repariert. + +[JM4_13] +Also, keine Dummheiten, okay? + +[KM4_11] +~g~Bring das Geld ins Casino! + +[FEF_BR2] +Orientiere dich, indem du alle bisherigen Einsatzbesprechungen durchsiehst. + +[TRAIN_1] +Kurowski Station + +[TRAIN_2] +Rothwell Station + +[TRAIN_3] +Baillie Station + +[SUBWAY1] +Portland Station + +[SUBWAY2] +Rockford Station + +[SUBWAY3] +Staunton South Station + +[SUBWAY4] +Shoreside Terminal + +[MEA4_2] +~r~Marty Chonks ist tot! + +[SPRAY1] +Fahre deinen Wagen in die Lackiererei, um deinen ~h~Fahndungslevel~w~ loszuwerden und das Auto zu ~h~reparieren~w~ und ~h~umzuspritzen~w~. Kosten - ~h~$1000~w~. Diesmal kostet es nichts. + +[JM4_A] +Ja, ich weiß, Toni, ich hab sie gut erzogen. Sie schnurrt, falls du verstehst, was ich meine. + +[JM4_5] +Komm später wieder, dann zeigen wir den Kerlen, was Sache ist. + +[AMMU_A] +Luigi sagt, du brauchst 'ne Knarre... + +[AMMU_B] +Joey sagt, ich soll dich 'ausrüsten'... + +[AMMU_C] +Geh hinten um den Laden herum. Ich hab eine 9mm im Hof deponiert. + +[AMMU_D] +Ich hab alles, was man so zur Selbstverteidigung braucht. + +[AMMU_E] +Willst du auch 'nen Waffenschein? + +[AMMU_F] +Auf den Ausweis verzichte ich. Du siehst vertrauenswürdig aus. + +[DETON] +DETONATION: + +[DRIVE_A] { re3 change } +Halt eine Uzi im Anschlag, wenn du in ein Fahrzeug steigst. Schau dann nach links oder rechts und drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~, um zu feuern. + +[DRIVE_B] { re3 change } +Halt eine Uzi im Anschlag, wenn du in ein Fahrzeug steigst. Schau dann nach links oder rechts und drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~, um zu feuern. + +[RECORD] +~g~NEUER REKORD!! + +[NRECORD] +~r~KEIN NEUER REKORD! + +[RCHELP] { re3 change } +Drücke die ~k~~VEHICLE_FIREWEAPON~-Taste oder fahre das ferngesteuerte Auto in die Räder eines Fahrzeugs, um es zu sprengen. + +[RCHELPA] { re3 change } +Drücke die ~k~~VEHICLE_FIREWEAPON~-Taste oder fahre das ferngesteuerte Auto in die Räder eines Fahrzeugs, um es zu sprengen. + +[RC_1] +Du hast 2 Minuten, um so viele Diablo-Autos wie möglich zu sprengen! + +[RC_2] +Du hast 2 Minuten, um so viele Mafia-Autos wie möglich zu sprengen! + +[RC_3] +Du hast 2 Minuten, um so viele Yakuza-Autos wie möglich zu sprengen! + +[RC_4] +Du hast 2 Minuten, um so viele Yardie-Autos wie möglich zu sprengen! + +[RC_5] +Du hast 2 Minuten, um so viele Hood-Autos wie möglich zu sprengen! + +[RC_6] +Du hast 2 Minuten, um so viele Kartell-Autos wie möglich zu sprengen! + +[RAMPAGE] +AMOKLAUF!! + +[RAMP_P] +AMOKLAUF BEENDET! + +[RAMP_F] +AMOKLAUF FEHLGESCHLAGEN + +[PAGE_00] +. + +[PAGE_01] +Dein Job: ~1~ Diablos in 120 Sekunden! + +[PAGE_02] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_03] +Dein Job: ~1~ Mafiosi in 120 Sekunden! + +[PAGE_04] +Dein Job: ~1~ Triaden in 120 Sekunden! + +[PAGE_05] +Dein Job: ~1~ Triaden in 120 Sekunden! + +[PAGE_06] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_07] +Dein Job: ~1~ Yardies in 120 Sekunden! + +[PAGE_08] +Dein Job: ~1~Yakuza in 120 Sekunden! + +[PAGE_09] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_10] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_11] +Dein Job: ~1~ Yardies in 120 Sekunden! + +[PAGE_12] +Dein Job: ~1~Yakuza in 120 Sekunden! + +[PAGE_13] +Dein Job: ~1~ Yardies in 120 Sekunden! + +[PAGE_14] +Dein Job: ~1~ Kolumbianer in 120 Sekunden! + +[PAGE_15] +Dein Job: ~1~ Hoods in 120 Sekunden! + +[PAGE_16] +Zerstöre ~1~ Fahrzeuge in 120 Sekunden! + +[PAGE_17] +Nimm dir mit einem Auto ~1~ Kolumbianer in 120 Sekunden vor! + +[PAGE_18] +Zerstöre ~1~ Fahrzeuge mit einem Drive-by in 120 Sekunden! + +[PAGE_19] +Dein Job: ~1~ Kolumbianer in 120 Sekunden! + +[PAGE_20] +Dein Job: ~1~ Hoods in 120 Sekunden! + +[JM1_A] +Mir ist langweilig. Wann läuft hier endlich mal was ab? + +[JM1_B] +Gleich, Schätzchen. Muss nur schnell was erledigen. + +[JM1_C] +Ich hab 'nen kleinen Job für dich. + +[JM1_D] +Die Forelli Brüder zahlen ihre Schulden nicht. + +[JM1_E] +Ich muss ihnen ein bisschen Respekt einbläuen. + +[JM1_F] +Lips Forelli stopft sich gerade in Marcos Bistro seinen fetten Bauch voll. + +[JM1_G] +Klau ihm sein Auto und bring es in 8-Balls Bombenwerkstatt in Harwood. + +[JM1_H] +Du kennst doch 8-Ball, oder? + +[JM1_I] +Sobald er 'ne Bombe eingebaut hat, parkst du die Karre an ihrem alten Platz. + +[JM1_J] +Dann lehn dich zurück und sieh dir die Show an. + +[JM1_K] +Aber Beeilung. Der Typ isst nicht ewig. + +[CAT2_A1] +Komm schon, du dummes Luder! + +[CAT2_A] +Bist du eigentlich gekommen, um Maria zu retten, oder weil du heiß auf mich bist? + +[CAT2_B] +Falls du's noch nicht weißt: + +[CAT2_B2] +Die Dates mit dir waren Arbeit - dich fertig zu machen, wird ein Vergnügen. + +[CAT2_C] +Du bist ein armes Würstchen, Amigo. + +[CAT2_D] +Wirf die Kohle rüber! + +[CAT2_E] +Du bist an sich ein fähiger Bursche. + +[CAT2_E2] +Aber du kapierst nicht, dass man mir nicht trauen kann. + +[CAT2_E3] +Macht den Idioten fertig. + +[CAT2_J] +Bring das Ding in die Luft!! + +[HM5_1] +Yo, Ice hat dich angekündigt. Es gibt hier Regeln. Nur Schläger. Keine Knarren, keine Autos. + +[HM5_5] +Hier geht's um Respekt und Achtung, klar? + +[HELP14] +Um Waffen aufzunehmen, gehe darüber hinweg. Dies funktioniert nicht, wenn du in einem Fahrzeug sitzt. + +[CRUSH] +Parke in der markierten Zone und steig aus. Das Fahrzeug wird dann geschrottet. + +[DIAB2_B] +Eine Bande von Taugenichtsen will mir ans Leder, falls ich sie nicht an meinen Geschäften beteilige. + +[DIAB2_C] +Aber da sind sie an den Falschen geraten, Amigo. + +[DIAB2_D] +Die haben eine Schwäche für Eiscreme. + +[DIAB2_E] +Hol die Bombe ab, die ich in Harwood versteckt habe, + +[DIAB2_F] +schnapp dir einen normalen Eis-Wagen, der rumfährt + +[DIAB2_G] +und locke diese Idioten mit dem Eis-Jingle in ihr Verderben. + +[DIAB2_H] +Sie verstecken sich in einem Lagerhaus auf den Atlantic Quays. + +[DIAB3_A] +Irgendwelche frechen Triaden haben gestern Nacht meinen Traumwagen geklaut, + +[DIAB3_B] +geschrottet und ausbrennen lassen. + +[DIAB3_C] +Im Kofferraum waren einige sehr wertvolle Erinnerungsstücke, + +[DIAB3_D] +unersetzliche Sammlerstücke, mein Freund. + +[DIAB3_E] +Ich hab eine ziemlich heiße Waffe am Rand von Chinatown versteckt. + +[DIAB3_F] +Hol sie dir und lehre die Triaden, El Burros Zorn zu fürchten. + +[DIAB3_1] +NIMM DIR 25 TRIADEN VOR + +[DIAB4_A] +Ein mieser Dieb hat einen Transporter mit der gesamten neuen Ausgabe meines Magazins gestohlen! + +[DIAB4_B] +Aber dieser SPANK-Junkie hat die Hecktüren offen gelassen + +[DIAB4_C] +und jetzt verteilt sich mein wunderschönes, + +[DIAB4_D] +geschmackvoll fotografiertes Magazin gleichmäßig über ganz Liberty! + +[DIAB4_E] +Nimm den Transporter, folge der Spur von 'Donkey Does Dallas' Nr. 1, 2 und 3. + +[DIAB4_F] +Und sammle das Zeug wieder ein. + +[DIAB4_G] +Wenn du bei diesem diebischen Junkie angekommen bist, nimm ihn dir vor! + +[DIAB4_H] +Und dann lieferst du meine Magazine an das XXX Mags im Rotlichtviertel. + +[DIAB4_1] +~g~Fahr den Transporter zur Rückseite des XXX Mags. + +[HM1_E] +Ich möchte, dass du diesen Vollidioten zeigst, wie ein echter Driveby aussieht. + +[HM1_H] +Schaff mir diese Nines vom Hals!! + +[HM2_A] +Die Nines bringen mich in die Klemme. + +[HM2_B] +Die Kerle haben gepanzerte Autos, und jetzt verhökern sie SPANK + +[HM2_C] +an meine Brüder, als wäre das gar nichts. + +[HM2_D] +Auf dem Weg steht ein Wagen geparkt. + +[HM2_E] +Da ist was drin, was diesen Feiglingen Beine machen wird. + +[HM3_A] +Irgend so ein Mistkerl hat 'ne Bombe in meine Karre eingebaut. + +[HM3_B] +Wenn ich die Karre verliere, stehe ich als Trottel da. + +[HM3_C] +Hol meinen Wagen ab und bring ihn in die Werkstatt in St. Mark's. + +[HM3_D] +Die sollen die Bombe da entschärfen. + +[HM3_E] +Die Uhr tickt und die Drähte sind locker. + +[HM3_F] +Ein Schlagloch zuviel, und das Ding geht hoch. + +[HM3_G] +Also, Beeilung! + +[HM4_A] +Yo, ein Flugzeug der Staatsbank ist gerade beim Francis Int. Airport abgestürzt. + +[HM4_B] +Auf dem ganzen Rollfeld liegt Platin rum + +[HM4_C] +Schnapp dir ein Auto und sack so viel wie möglich davon ein. + +[HM4_F] +Du kannst das Zeug in einer meiner Garagen abladen. + +[HM4_G] +Platin ist verdammt schwer. Dein Auto wird etwas langsamer laufen. + +[HM4_H] +Also, lade regelmäßig etwas davon in einer Garage ab. + +[HM5_A] +Die Nines sind nur noch ein versprengter Haufen... + +[HM5_B] +aber sie machen immer noch Stress. + +[HM5_C] +Jetzt wollen sie's endgültig wissen. + +[HM5_D] +Eine Gang von denen gegen zwei von uns. Oder vielmehr... + +[HM5_E] +zwei von euch. + +[HM5_F] +Ich wäre ja dabei, aber... + +[HM5_G] +ich hab noch drei Monate Bewährung, + +[HM5_H] +verstehst du, was ich meine? + +[HM5_I] +Triff dich mit meinem kleinen Bruder. + +[HM5_J] +Er zeigt dir, wo der Fight stattfindet, okay? + +[MEA1_B] +Mein Name ist Chonks, Marty Chonks. + +[MEA1_C] +Mir gehört die Bitchin' Dog Food Fleischfabrik um die Ecke. + +[MEA1_D] +Ich hab Geldsorgen, aber wer hat die nicht, was? + +[MEA1_E] +Ich treff mich später mit meinem Banker. + +[MEA1_F] +Das ist ein linker Säger, der mir dauernd die Kreditraten raufsetzt, um sich 'ne Scheibe abzuschneiden. + +[MEA1_G] +Nimm mein Auto, hol ihn ab und bring ihn hierher. + +[MEA1_H] +Ich habe eine kleine Überraschung für diesen Blutsauger!! + +[MEA2_A] +Ich hab Diebe angeheuert, um in meine Wohnung einzubrechen. + +[MEA2_C] +Jetzt drohen diese Dreckskerle, sie erzählen alles der Versicherung, + +[MEA2_D] +wenn ich sie nicht beteilige. + +[MEA2_E] +Ist das zu fassen? + +[MEA2_F] +Ich hab einen Wagen innerhalb der Fabrik abgestellt. + +[MEA2_G] +Hol damit die Kerle in ihrem Revier im Rotlichtbezirk ab. + +[MEA2_H] +Bring sie in die Fabrik, damit ich ihnen meinen Standpunkt klarmachen kann. + +[MEA3_A] +Wenn nicht bald ein Haufen Geld ins Haus kommt, gehe ich pleite. + +[MEA3_B] +Meine Frau hat eine Versicherung, aber mir hat sie immer nur das Geld aus der Tasche gezogen. + +[MEA3_C] +Ich hab ein Auto am gewohnten Ort abgestellt. + +[MEA3_D] +Hol damit meine Frau vom Classic Nails Studio ab und bring sie in die Fabrik. + +[MEA4_A] +Mist, ich steck in der Klemme! + +[MEA4_B] +Meine Frau hatte was mit einem Typen, dem ich Geld schulde. + +[MEA4_C] +Der ist jetzt ziemlich sauer und will sich rächen! + +[MEA4_E] +Der denkt, er kann mich erpressen... + +[MEA4_F] +aber ich glaube eher, + +[MEA4_G] +dass auch dieser gute Mann als Hundefutter enden wird. + +[WELCOME] +WILLKOMMEN IN + +[HM1_2] +~g~Besorg dir ein Fahrzeug. Beachte: Hier zählt nur die Uzi! + +[HELP8_B] +Drücke die ~h~~k~~PED_SNIPER_ZOOM_IN~-Taste~w~ zum ~h~Heranzoomen~w~ mit dem Gewehr, und die ~h~~k~~PED_SNIPER_ZOOM_OUT~-Taste~w~ zum ~h~Wegzoomen~w~. + +[LRQC_1] +Asuka und ich haben was zu besprechen. + +[LRQC_2] +Fahr doch ein bisschen durch die Gegend. + +[LRQC_3] +Du wirst einen Unterschlupf brauchen. + +[LRQC_4] +Am Rand von Belleville ist ein Lagerhaus. Das könnte was für dich sein. + +[LRQC_5] +Komm wieder hierher, wenn du fertig bist. + +[LRQC_6] +Dann können wir uns ein bisschen unterhalten. + +[JM6_5] +~g~Du brauchst einen Fluchtwagen, du Idiot! + +[JM2_F] +Wenn du 'ne Knarre brauchst, geh zum Hintereingang vom AmmuNation gegenüber der U-Bahn. + +[LOVE4_7] +~g~Es gibt einen Bauhof auf Staunton Island. Vielleicht haben sie das Päckchen dorthin gebracht. + +[LOVE4_8] +~g~Du brauchst ein Auto, um die Garage zu öffnen. + +[TSCORE] +EINKÜNFTE: $~1~ + +[AM1_9] +~r~Salvatore hat sich in Luigis Club abgesetzt! + +[AM1_6] +~g~Wenn du vor Luigis Club rumhängst, bemerken dich die Mafiosi! + +[TM2_3] +~g~Das ist eine Falle! Nimm sie dir alle vor! + +[FM4_1] +Hier Maria. Das mit dem Wagen ist eine Falle! Komm zu dem Steg südlich der Callahan Bridge. + +[JM1_7] +~g~Mach die Wagentür zu, sonst merkt er was! + +[KM5_1] +~g~DEALER ERLEDIGT!! + +[KM5_6] +~g~Du musst mind. 8 Yardie Dealer außer Gefecht setzen. + +[KM5_7] +~g~Beeilung! Sobald sie ihr SPANK verhökert haben, verschwinden sie wieder. + +[RM3_8] +~r~Dieser Wagen dient nur zur Ablenkung!! + +[LM3_8] +Hi, ich bin Joey. + +[LM3_9] +Luigi sagt, du bist verlässlich. Also komm später wieder. + +[KM3_5] +~g~Drück die Hupe, damit der Deal in Gang kommt. + +[LOVE7] +LOVES VERSCHWINDEN + +[LOVE2_5] +~g~Auftrag erledigt. Verschwinde aus Newport und dann weg mit dem Wagen! + +[AS2_11] +~g~~1~ VON 9! + +[GARAGE1] +~g~Steig aus und geh nach draußen. + +[KM3_11] +~g~Das Kartell wurde angegriffen. Die Aktentasche wurde nicht sichergestellt. + +[KM3_12] +~g~Nimm dir sämtliche Kolumbianer vor, zerstöre die Fahrzeuge und stell die Aktentasche sicher. + +[KM3_13] +~g~Bring die Aktentasche ins Casino. + +[RM5_6] +~g~Er ist abgehauen! Zerstöre seine Panzerung mit einem Auto oder einer Explosion!! + +[PBOAT_1] { re3 change } +Drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~, um die Bordkanonen abzufeuern. + +[PBOAT_2] { re3 change } +Drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~, um die Bordkanonen abzufeuern. + +[DIAB1_B] +Hier El Burro, von den Diablos. + +[DIAB1_D] +Du bist neu in Liberty, aber du hast bereits eine guten Ruf auf der Straße. + +[DIAB1_E] +Bei der alten Schulhalle nahe der Callahan Bridge findet ein Rennen statt. + +[DIAB1_F] +Besorg dir 'nen fahrbaren Untersatz. Wer als erster alle Checkpoints abfährt, ist Sieger. + +[HM2_1] { re3 change } +Zerstöre die gepanzerten Fahrzeuge mit den Buggies. Zur Zündung drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~. + +[HM2_1A] { re3 change } +Zerstöre die gepanzerten Fahrzeuge mit den Buggies. Zur Zündung drücke die ~h~~k~~VEHICLE_FIREWEAPON~-Taste~w~. + +[HM2_2] +~r~Du hast nicht alle gepanzerten Fahrzeuge zerstört! + +[HM2_6] +~g~Gepanzertes Fahrzeug zerstört! + +[RM3_A] +Ich kenne einen wichtigen Mann in der Stadt, mit + +[RM3_H] +sagen wir mal, einem Sinn für's Exotische und dem nötigen Kleingeld dafür. + +[RM3_B] +Er steht unter Anklage und der Staatsanwalt hat einige ziemlich peinliche Fotos von ihm + +[RM3_C] +auf einer Friedhofsparty oder so. + +[LOVE6_A] +Kleine Lektion in Sachen Business, mein Freund: + +[LOVE6_E] +Hast du eine hochinteressante Ware, wird Gott und die Welt sie dir abjagen wollen... + +[LOVE6_C] +Spezialeinheiten haben die Gegend um meinen Geschäftsfreund und das Päckchen abgeriegelt. + +[LOVE6_D] +Mach, dass du hinkommst, nimm den Transporter und lenk sie ab. + +[LOVE6_F] +Beschäftige die Typen, damit er sich absetzen kann. + +[AM3_C] +Vermutlich ist er in der Bucht draußen, während du dies liest. Nimm mein Boot und mach seiner Karriere ein Ende! + +[FESZ_UC] +ABBRECHEN + +[FEDS_SM] +L1, R1-MENÜ WECHSELN + +[FEDS_AS] +;=-AUSWAHL ÄNDERN + +[FEDSAS2] +<>-AUSWAHL ÄNDERN + +[FEDS_SS] +L1, R1-AUSWAHL ÄNDERN + +[FEDSSC1] +;-SCHNELLER BILDLAUF + +[FEDSSC2] +=-BILDLAUF STOP + +[MEA2_3] +~g~Bring das Auto zurück zur Fabrik. + +[RM1_3] +~r~McAffrey ist entkommen! + +[RM1_4] +~g~Du hast alle Granaten verbraucht! Hol neue im AmmuNation! + +[RM1_5] +~g~Zurück! Zünde die sichere Wohnung an! + +[RM6_4] +~g~Du musst zu der Garage und Rays Auto mit den Waffen holen. + +[RM6_5] +~g~Die Brücke wird von der CIA überwacht. Such dir eine andere Route. + +[HM2_F] +Und zerstöre alle ihre gepanzerten Wagen. + +[HM_4] +'GOLDRAUSCH' + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA4_B7] +aber wenn du in mein Büro kommst, könnte ich... + +[MEA3_B4] +Marty will mich sehen? Das muss aber schnell gehen, ich hab nämlich einen Friseurtermin. + +[KM3_7] +Das ist eine Falle der Yakuza, Mann! + +[FES_LOF] +Laden fehlgeschlagen. + +[P1INSA] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1: ~1~K freier Speicherplatz. Erforderlich: ~1~K. + +[P1INSN] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1: Kein freier Speicherplatz. Bitte einige Dateien löschen. + +[FES_SLO] +DATEI + +[FES_ISC] +IST BESCHÄDIGT + +[FESZ_TI] +Z1 SPEICHERN + +[FESZ_SA] +Spiel speichern + +[P1NOIN] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1 + +[P1INSE] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 vorhanden + +[MC_LDFL] +Laden fehlgeschlagen! + +[MC_NWRE] +Spiel wird neu gestartet + +[LOVE6_3] +~g~Du hast ~1~ Sekunden, um zu dem Securicar zurückzukehren, bevor die Mission fehlschlägt. + +[LOVE6_4] +~r~Du hast den falschen Securicar abgehängt! + +[HELP1] +Halte in der Mitte der blauen Markierung. + +[HELP12] +Geh ins Zentrum der blauen Markierung, um eine Mission zu starten. + +[HJSTAT] +Distanz: ~1~.~1~m Höhe: ~1~.~1~m Saltos: ~1~ Drehungen: ~1~_ + +[HJSTATW] +Distanz: ~1~.~1~m Höhe: ~1~.~1~m Saltos: ~1~ Drehungen: ~1~_ Und was für eine Landung! + +[DIAB1_5] +ZEIT: + +[LOVE3_4] +~r~Du hast das Flugzeug zerstört! + +[F_FAIL1] +Feuerwehr -Mission beendet + +[F_CANC] +~r~Feuerwehr -Mission abgebrochen! + +[F_EXTIN] +FEUER: + +[A_COMP1] +Krankenwagen Missionen abgeschlossen! + +[A_CANC] +~r~Krankenwagen Mission abgebrochen! + +[A_COMP3] +Krankenwagen Missionen abgeschlossen! Du wirst beim Laufen nie ermüden! + +[ATUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Krankenwagen Missionen an- oder abzuschalten. + +[ATUTOR3] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~-Taste~w~, um Krankenwagen Missionen an- oder abzuschalten. + +[ALEVEL] +Krankenwagen Mission Level ~1~ + +[A_FAIL1] +Krankenwagen Mission beendet + +[FEST_HA] +Höchster Krankenwagen Missions Level + +[A_SAVES] +GERETTETE MENSCHEN: ~1~ + +[C_KILLS] +KRIMINELLE: ~1~ + +[HM1_B] +Ich hab 'n Problem. Die wollen mich verscheißern. + +[AM2_A] +Sehr erfreulich, dass Salvatore nicht mehr unter uns weilt. + +[AM2_A2] +Du bist ein effizienter Mann - das gefällt mir. + +[AM2_B] +Das ist mein Bruder Kenji. + +[AM2_C] +Asuka hat einen kleinen Job für dich, aber wenn du fertig bist, komm in mein Casino, dann reden wir. + +[AM2_D] +Typisch Kenji, immer will er mit meinen Spielsachen spielen. + +[AM2_E] +Mein Spitzel bei der Polizei hat mir gesteckt, dass die Mafia unsere Aktivitäten im Auge behält. + +[AM2_E2] +Sie wollen dich aufstöbern. + +[AM2_F] +Wir müssen unsere Operationen ruhen lassen, bis wir ihnen das Handwerk gelegt haben. + +[AM2_G] +Zieh die Kerle aus dem Verkehr und mach dieser Vendetta ein für alle mal ein Ende. + +[F_START] +~g~Brennendes Fahrzeug in der Gegend von ~a~ gemeldet. Lösche den Brand. + +[AM4_1A] +Komm zu dem Fernsprecher im West Belleville Park. + +[AM4_1B] +Komm zu dem Fernsprecher auf dem Liberty Campus. + +[AM4_1C] +Komm zu dem Fernsprecher im South Belleville Park. + +[AM4_1D] +Wir treffen uns im Toilettenhäuschen im Park. + +[HJSTATF] +Distanz: ~1~Fuß Höhe: ~1~Fuß Saltos: ~1~ Drehungen: ~1~_ + +[HJSTAWF] +Distanz: ~1~Fuß Höhe: ~1~Fuß Saltos: ~1~ Drehungen: ~1~_ Und was für eine Landung! + +[HM1_F] +Aber pass auf. Es werden Jacks da sein, und die werden denken, du willst auch ihnen ans Leder! + +[HM1_D] +Sie nennen sich 'Nines' und ihre Farbe ist 'Lila'. Und jeder Tag im Zeichen dieser Farbe... + +[HM1_G] +ist ein Tag, an dem die Jacks schlecht aussehen. + +[MEA2_B] +Und stiehl ein paar Sachen, damit ich die Versicherung kassieren kann. + +[TM3_H] +~w~Das hast du gut gemacht vorhin, wirklich gut, mein Junge. + +[TM3_I] +~w~Komm, wir stellen dich dem Don vor. + +[TM3_J] +~w~Heeyyy! Luigi! + +[TM3_K] +~w~Meine Girls fragen andauernd nach dir, Salvatore. Du warst so lange nicht mehr bei uns. + +[TM3_L] +~w~Sag ihnen, wenn diese lästige Geschichte hier vorbei ist, + +[TM3_M] +~w~gehen wir alle in den Club und feiern, okay? + +[TM3_N] +~w~Ah, mein Junge! + +[TM3_N2] +~w~Hallo, Paps. + +[TM3_O] +~w~Hast du endlich eine Frau gefunden? + +[TM3_P] +~w~Hey, Deine Mutter - Gott hab sie selig - würde sich im Grab umdrehen, + +[TM3_Q] +~w~wenn du keine abkriegen würdest. + +[TM3_R] +~w~Ich weiß, Paps, ich arbeite dran. + +[TM3_S] +~w~TONI! Wie geht's deiner Mamma? + +[TM3_T] +~w~Sie ist eine großartige Frau! Stark. Florentinerin eben. + +[TM3_U] +~w~Es geht ihr gut. Sehr gut. + +[TM3_V] +~w~Hervorragend. Okay, geht schon mal vor, ich hab was mit unserem neuen Freund hier zu bereden. + +[TM3_W] +~w~Ich sehe goldene Zeiten auf dich zukommen, mein Junge... + +[RM1_A] +Dieser McAffrey! Der hat mehr Bestechungsgeld kassiert als jeder andere. + +[RM1_B] +Jetzt denkt er, er wird ehrenhaft entlassen, wenn er als Kronzeuge aussagt. + +[RM1_C] +Er ist einfach ein Verräter. + +[RM4_B] +Wir müssen ihn zum Schweigen bringen - für immer. + +[RM4_E] +Er soll bei den Fischen schlafen, statt sie zu essen. + +[LOVE3_B] +Beim Landeanflug auf den Flughafen wird ein kleines Flugzeug die Bucht überfliegen. + +[LOVE4_D] +Leider hat sich die Hafenbehörde die Maschine geschnappt und wollte sie auseinandernehmen, + +[LOVE4_H] +bis ich das mit erheblichen Unkosten verhindert habe. + +[LOVE4_E] +Fahr über die Brücke nach Shoreside Vale und dann zum Francis Int. Airport. + +[GTAB_A] +Hey, schaffen wir das hier weg. Weiß der Teufel, was das ist. + +[GTAB_B] +Aber da er's unbedingt haben will, muss es wohl was wert sein. + +[GTAB_C] +Wer zum Teufel...? + +[GTAB_D] +DU! + +[GTAB_E] +Hey, ganz ruhig, Amigo! De nada! De nada! + +[GTAB_F] +Ich dachte, ich hätte dich erledigt! + +[GTAB_G] +Nicht schießen, Amigo! Kein Problem. Wir sind alle Freunde. Hier, nimm. + +[GTAB_H] +Nicht so memmenhaft! + +[GTAB_I] +Wir haben keine Wahl, Baby! + +[GTAB_J] +Wir haben immer eine Wahl, du dämlicher Idiot! + +[GTAB_K] +Sorry wegen dem abgedrehten Flittchen, Mann. Die sind doch alle gleich. Por favor? + +[GTAB_L] +Das Luder ist also entwischt. + +[GTAB_M] +Aber du hast mir einen Gefallen getan. + +[GTAB_N] +Du bist nicht der einzige, der eine Rechnung mit dem Kartell offen hat. + +[GTAB_O] +Dieser Wurm hat meinen Bruder auf dem Gewissen! + +[GTAB_P] +I hab nie einen Yakuza umgebracht! + +[GTAB_Q] +Lügner! Wir alle haben den Kartell-Attentäter gesehen. + +[GTAB_R] +Wir werden euch kolumbianische Hunde alle erledigen! + +[GTAB_S] +Ich befasse mich mit unserem Freund. Mal sehen, was ich aus ihm rauskriege. + +[GTAB_T] +Komm später wieder. Ich werde deine Dienste brauchen. + +[GTAB_U] +Bitte, Amigo, lass mich nicht hier bei ihr! Die ist 'n Psycho! Amigo? Hey, AMIIIGO!!!...Aiiieeeeaaargghh! + +[LOVE5_A] +Du bist eine sichere Bank. So etwas ist selten in diesen schlechten Zeiten. + +[KM3_1] +~g~Das Kartell erwartet eine Yardie Gang. Klau einen Yardie-Wagen! Fahr nach Norden. In Newport fndest du einen. + +[LOVE1_1] +~g~Mit einem Wagen der Kolumbianer kommst du in ihren Unterschlupf rein. Im Norden, in Fort Staunton, findest du einen. + +[FM1_Q1] +~w~Na, kleine Erfrischung gefällig? Ein bisschen SPANK? + +[FM1_R] +~w~Hi, Chico. Nein, nur das übliche. + +[FM1_T] +~w~Danke, Chico. Bis bald. + +[FM1_W] +~w~Okay, Fiffi, du passt hier auf den Wagen auf, ich geh ein bisschen abtanzen. + +[FM1_X] +~w~Okay, Fiffi, lass uns 'nen Abgang machen. Huuh! + +[FM1_Q] +~w~Hey, Maria! Meine Traumfrau! + +[FM1_S1] +~w~Vielleicht solltest du mal die Party in der Lagerhalle am Ostende von Atlantic Quays auschecken. + +[FM1_U] +~w~Gracias. Und viel Spaß. Ist gutes Zeug. + +[FM1_V] +~w~Na los, Fiffi, sehen wir uns mal die Party an! + +[FM1_SS] +~r~SCANNER: ~g~4-5 an alle Einheiten: Assistieren Sie bei der Rauschgift-Razzia in Atlantic Quays... + +[LOVE6_B] +Auch wenn kaum einer weiß, wie viel sie wirklich wert ist. + +[TM3_A1] +~r~Joey ist hinüber! + +[TM3_A2] +~r~Joey und Luigi sind hinüber! + +[TM3_A3] +~r~Joey, Luigi und Toni sind hinüber! + +[FM4_2] +Hör zu, Salvatore denkt, ich betrüge ihn mit dir, + +[FM4_3] +da hat er dem Kartell deinen Kopf angeboten, um einen Deal mit denen zu machen. + +[FM4_4] +Das kann ich nicht zulassen. Das ist alles meine Schuld. + +[FM4_4B] +Ich hab ihm erzählt, dass wir uns gut verstehen. + +[FM4_5] +Frag mich nicht wieso. Ich weiß es nicht. + +[FM4_6] +Auf Mafia-Gebiet bist du Freiwild, und ich muss auch abhauen. + +[FM4_6B] +Ich hab genug von all der Gewalt, all dem Blut. + +[FM4_7] +Das ist eine Freundin von mir, eine alte Freundin. Sie heißt Asuka. Ihr können wir trauen. + +[FM4_8] +Kommt. Genug geredet. + +[FM4_9] +Wir verschwinden lieber, bevor uns noch mehr hysterische Italiener unbedingt wiedersehen möchten. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +CHARACTERS + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERS + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CINCAM] +Cinematic-Kamera + +[KM1_13] +Fahr das Fahrzeug in die Garage! + +[KM3_14] +~r~Du bist gesehen worden. Der Deal fällt flach! + +[EBAL_H] +Warte hier. Ich gehe rein und rede mit Luigi. + +[EBAL_M] +Und merk dir: Finger weg von meinen Girls. + +[LM2_F] +Dann schnapp dir seine Karre und spritze sie um. + +[LM2_D] +Hier. Hier, nimm. + +[LM1_9] +Hi. Ich bin Misty. + +[LM4_A] +So ein Mistkerl von den Diablos hat in meinem Gebiet Mädchen laufen. + +[FM2_B] +Wir haben einen Verräter unter uns! + +[FM2_C] +Er verdient kein Geld mit Mädchen oder Dealen, also wird er Informationen verkaufen. + +[FM3_CC] +~w~Komm wieder, wenn du die Kohle hast, Bruder. + +[FEDS_AM] +<>-MENÜ WECHSELN + +[LOVE5_5] +~r~Du hast es nicht geschafft, den Truck zu beschützen! + +[RM6_6] +~r~Ray ist tot! + +[RM6_7] +~r~Ray hat seinen Flug verpasst. + +[RM6_8] +~g~Du hast Ray zurückgelassen. Kehr um und hol ihn. + +[FM1_10] +~g~Du hast Maria zurückgelassen. Kehr um und hol sie. + +[LOVE4_9] +~r~Das Flugzeug ist zerstört worden! + +[LOV4_10] +~r~Die einzige Spur auf den Verbleib des Päckchens ist vernichtet worden. + +[KM2_D] +Unnötig zu sagen, dass wir ihm die Autos schenken müssen, um meine Schuld bei ihm zu begleichen. + +[KM4_B] +Für Läden, die das Glück haben, unter unserem Schutz zu stehen, ist heute Zahltag. + +[KM2_E] +Du musst die Autos auf der Liste besorgen und zu einer Garage hinter dem Parkplatz in Newport bringen. + +[FM3_8I] +~w~Such dir eine günstige Position. Ich geh rein, wenn du den ersten Schuss abfeuerst. + +[LOVE1_B] +Ich weiß, dass einer wie du sehr loyal sein kann, wenn das Geld stimmt. + +[LOVE1_H] +Aber je mehr Leute, desto größer die Gier. + +[LOVE1_C] +Ein werter Geschäftsfreund, ein alter Asiate, + +[LOVE1_I] +wird von irgendwelchen Südamerikanern in Aspatria als Geisel festgehalten. + +[MEA4_D] +Ich habe ein Treffen mit ihm vereinbart... + +[MEA4_B4] +Marty schickt dich also? Dem Penner werde ich zeigen, was es heißt, mit mir Geschäfte zu machen. + +[MEA4_B5] +Carl, hi. Ich, äh, ich brauche noch ein bisschen Zeit, um dein Geld aufzutreiben. + +[MEA1_B4] +Mr Chonks schickt dich also. Wollen wir dem Burschen doch mal einen Besuch abstatten. + +[HM5_6] +Dann wollen wir doch mal ein paar Leute aufmischen... + +[LOVE1_5] +~g~Häng hier nicht rum, besorg dir ein Auto der Kolumbianer und rette Loves Geschäftsfreund. + +[AS1_D] +~w~Spiel den Köder und locke die Killerkommandos nach Pine Creek, + +[AS1_E] +~w~wo meine Leute sie erwarten werden. + +[AS2_C] +~w~Das Kartell betreibt eine Scheinfirma zur Tarnung - das Kappa Coffee House. + +[AS2_E] +~w~Wir haben keine Wahl. Wir müssen diese Drogenbuden zerstören. + +[AS2_F] +~w~Zerleg diese Dinger!! + +[AS2_A1] +~w~Miguel ist ein echter Latin Lover. Der hat ein Stehvermögen! + +[AS2_A2] +~w~Ich bin völlig erschöpft. + +[SIREN_3] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~-Taste~w~. + +[SIREN_4] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~-Taste~w~. + +[AS3_C] +~w~Iiiiiiiiiiih! Was ist denn das für ein gelbes Glibberzeug? + +[AS3_C1] +~w~Oh, hi, Baby. + +[AS3_F] +~w~Das Mädchen ist ein Naturtalent. + +[AS3_F1] +~w~Sie hat unserem Gast eine kleine Info entlockt. + +[AS3_G] +~w~In 2 Stunden landet ein Flugzeug auf dem Francis Int. Airport. + +[AS3_G1] +~w~Es ist voll mit Catalinas Giftzeug. + +[AS3_H] +~w~Du entgehst den Security Checks am Flughafen, wenn du dir ein Boot besorgst, zu den Leuchtbojen rausfährst + +[AS3_H1] +und die Maschine im Anflug abschießt. + +[AS3_I] +~w~Krame die Ladung aus den Trümmern! + +[AS3_J] +~w~Und sei vorsichtig, okay, Baby? + +[AS3_K] +~w~Versuch's mal mit Chili-Öl... + +[RM2_F1] +Die Kolumbianer müssen jeden Moment hier sein! + +[RM2_K] +Verdammt, da sind sie!! LOS, LADEN!! + +[LOVE2_7] +~g~Jetzt lass den Wagen stehen! + +[LOVE2_8] +~g~Jetzt verschwinde aus Newport! + +[AM1_F] +Salvatore Leone wird Luigis Club in zirka drei Stunden verlassen (~1~:~1~). + +[LOVE5_C] +Folge ihm und sorg dafür, dass er und mein Päckchen wohlbehalten in Pike Creek ankommen. + +[FESZ_SR] +Speicherung fehlgeschlagen! Bitte Memory Card (PS2) in MEMORY CARD-Steckplatz 1 überprüfen und noch einmal versuchen. + +[FESZ_FO] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 formatieren? + +[FELZ_FO] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 ist nicht formatiert. + +[FES_NOC] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[FES_LOE] +Laden fehlgeschlagen! Bitte Memory Card (PS2) in MEMORY CARD-Steckplatz 1 überprüfen und noch einmal versuchen. + +[FES_DEE] +Löschen fehlgeschlagen! Bitte Memory Card (PS2) in MEMORY CARD-Steckplatz 1 überprüfen und noch einmal versuchen. + +[FORSUC] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 wurde formatiert. + +[ERFOUN] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 konnte nicht formatiert werden. + +[ERMCNP] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[SVMEM1] +Speichern auf Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[FORSLO] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 formatieren. + +[SLONFM] +Fehler beim Formatieren der Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[SLONDR] +Nicht genug Speicherplatz. Bitte eine Memory Card (PS2) mit mindestens 500KB freiem Speicherplatz in MEMORY CARD-Steckplatz 1 einstecken. + +[SLNSP] +Nicht genug Speicherplatz. Bitte eine Memory Card (PS2) mit mindestens 200KB freiem Speicherplatz in MEMORY CARD-Steckplatz 1 einstecken. + +[FEFD_WR] +Formatiere Memory Card (PS2) in MEMORY CARD-Steckplatz 1. Bitte die Memory Card (PS2) nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FES_ISF] +NICHT VORHANDEN + +[FES_SAG] +VORHANDEN + +[SLONNO] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1. + +[SLONNF] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 ist nicht formatiert. + +[FESZ_FM] +Memory Card (PS2) in MEMORY CARD-Steckplatz 1 ist nicht formatiert. Soll die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 formatiert werden? + +[FESZ_FF] +Formatierung fehlgeschlagen. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 überprüfen und noch einmal versuchen. + +[MCDNSP] +Nicht genug Platz auf Memory Card (PS2) in MEMORY CARD-Steckplatz 1. Es werden mind. 500KB Speicherplatz benötigt, um Spieldaten zu speichern. Trotzdem starten? (JA oder NEIN) + +[MCGNSP] +Nicht genug Platz auf Memory Card (PS2) in MEMORY CARD-Steckplatz 1. Es werden mind. 200KB Speicherplatz benötigt, um Spieldaten zu speichern. Trotzdem starten? (JA oder NEIN) + +[FESZ_WR] +Daten werden gespeichert. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FESZ_OW] +Daten werden überschrieben. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FELD_WR] +Daten werden geladen. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FEDL_WR] +Daten werden gelöscht. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[LM2_C] +Luigi sagt, das soll ich dir geben... + +[LM3_G] +Joey ist einer, den man nicht warten lässt. Das ist deine Chance. + +[LM5_E] +Bring so viele wie möglich hin, bevor die Cops ihr ganzes Geld versaufen. + +[JM5_C] +Vor dem Cafe in der Nähe vom Callahan Point steht ein Auto mit einem Toten drin. + +[RM2_B] +Wir waren zusammen in Nicaragua, als dieses Land noch wusste, was es tut. + +[RM2_C] +Irgendwelche Dreckskerle vom Kartell haben ihn gestern verprügelt und kommen heute wieder, um ihm seine Ware abzunehmen. + +[RM2_D1] +Ich würde es selbst machen, aber meine Bronchien melden sich wieder. Tja, äh, viel Glück. + +[CATINF1] +~g~Schnapp dir Catalina! + +[CATINF2] +~g~Um Catalina zu finden, folge dem Hubschrauber. + +[BOATIN1] +Spring auf ein Boot und drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um hinein zu gelangen. + +[BOATIN2] +Wenn du in der Nähe eines Bootes bist, kannst du die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste ~w~benutzen, um an Bord zu gelangen. + +[BOATIN3] +Spring auf ein Boot und drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um hinein zu gelangen. + +[BOATIN4] +Wenn du in der Nähe eines Bootes bist, kannst du die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste ~w~benutzen, um an Bord zu gelangen. + +[JM6] +'DIE FLUCHT' + +[FM1] +'AUF PISTE MIT MARIA' + +[JM1] +'MIKE 'LIPS' LETZTE LASAGNE' + +[FM21] +'DER BOMBENANSCHLAG, TEIL 1' + +[FM3] +'DER BOMBENANSCHLAG, TEIL 2' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'UNTER ÜBERWACHUNG' + +[KM2] +'GTA' + +[AS3] +'DER ABFANGJÄGER' + +[RM2] +'DAS WAFFENARSENAL' + +[LOVE6] +'LOCKVOGEL' + +[LOVE1] +'DIE BEFREIUNGSAKTION' + +[RC1] +'IM DSCHUNGEL DER DIABLOS' + +[RC2] +'DAS MAFIA-MASSAKER' + +[RC3] +'DER CASINO-COUP' + +[RC4] +'ROCK'N ROLL MIT RUMPO' + +[RM2_E1] +Unglaublich, dass die feigen Säcke mich wieder ohne ausreichenden Schutz ins Gefecht schicken. + +[GREN_1] +Je länger du die ~h~~k~~PED_FIREWEAPON~-Taste~w~ gedrückt hältst, desto weiter kannst du die Granate werfen. + +[GREN_2] +Je länger du die ~h~~k~~PED_FIREWEAPON~-Taste~w~ gedrückt hältst, desto weiter kannst du die Granate werfen. + +[GREN_3] +Je länger du die ~h~~k~~PED_FIREWEAPON~-Taste~w~ gedrückt hältst, desto weiter kannst du die Granate werfen. + +[LOVE4_G] +Die Maschine mit meiner Ware steht im Zollhangar. + +[KABOOM] +KAWUMM! + +[SPLAT] +WUSCH! + +[PANCAK] +PLATT! + +[SOAKED] +AARGH! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +Wenn der U-Bahnhof öffnet, kannst du die U-Bahn nach Staunton Island nehmen. + +[TUBE2] +Wenn der U-Bahnhof in Shoreside Vale aufmacht, dann kannst du das Shoreside Terminal in Richtung Francis Int. Airport verlassen. + +[TUBE_2] +Um in eine U-Bahn einzusteigen, ~h~'Einsteigen'-Taste~w~ drücken. + +[LEGAL] +~g~Schalte die kriminelle Bedrohung aus! + +[GA_2] +Neuer Motor und neue Lackierung. Die Cops werden dich nicht identifizieren! + +[LM1_8A] +Warum nicht ein Taxi 'ausleihen', um dir was dazu zu verdienen...? + +[TAXIH1] +Halte neben einem gehighlighteten Fußgänger, um ihn einsteigen zu lassen, dann bringe ihn rechtzeitig an sein Fahrtziel. + +[LM5_7] +~g~Wenn weniger als vier Girls bei dem ~p~Polizeiball~g~ auftauchen, wird Luigi sauer! + +[KM2_3] +~g~Die ~r~Autos~g~ müssen in 1A-Zustand sein, damit die ~p~Garage~g~ sie annimmt. + +[KM5_2] +~g~Ein Yardie ist weg. + +[BETRA_A] +Sorry, Baby. + +[BETRA_B] +Ich bin ein anspruchsvolles Mädchen und du... + +[BETRA_C] +... du bist ein kleiner Fisch. + +[JAILB_C] +Noch gibt es keine näheren Informationen zu den Häftlingen, die mit dem Konvoi transportiert wurden, + +[JAILB_E] +Der Konvoi war am frühen Morgen vom Polizei-Hauptquartier aus + +[JAILB_F] +zu einem Gefangenentransport mit Ziel Haftanstalt Liberty aufgebrochen. + +[JAILB_G] +Der Anschlag erfolgte an der Callahan-Brücke. + +[JAILB_P] +ist Portland durch diese Katastrophe vom Rest der Stadt abgeschnitten. + +[JAILB_Q] +Los jetzt! + +[JAILB_R] +Vollidiot! + +[JAILB_S] +Kein Problem, dich alle zu machen. + +[JAILB_T] +Das wird dir noch leid tun. + +[JAILB_U] +Okay, okay. Hau ab. + +[HELP15] +Wenn zu Fuß unterwegs, drücke die ~h~~k~~PED_LOOKBEHIND~-Taste~w~, um ~h~nach hinten zu schauen~w~. + +[FEC_LB3] +Nach hinten schauen + +[FEC_R3] +(R3-Taste) + +[FES_AFO] +Diese Memory Card (PS2) ist bereits formatiert. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- AUSWAHL ÄNDERN + +[FEDSAS4] +;=<> - AUSWAHL ÄNDERN + +[SPRAY_4] { re3 change } +~h~~k~~VEHICLE_FIREWEAPON~-Taste ~w~benutzen, um die Wasserkanone abzufeuern. + +[SPRAY_1] { re3 change } +~h~~k~~VEHICLE_FIREWEAPON~-Taste ~w~benutzen, um die Wasserkanone abzufeuern. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore Leone wird Luigis Club um zirka 0~1~:~1~ verlassen. + +[JAILB_V] +Liberty City ist heute vom Schrecken gezeichnet. + +[JAILB_A] +Polizei und Noteinsatzkräfte arbeiten unter Hochdruck, nachdem heute morgen + +[JAILB_B] +ein verheerender Anschlag auf einen Polizeikonvoi verübt wurde. + +[JAILB_W] +Nach stundenlangen Ermittlungen wurde klar, dass der Anschlag das Werk von Profis war. + +[JAILB_K] +Die Identifizierung der noch vermissten Kriminellen wurde darüber hinaus + +[JAILB_L] +durch eine Hacker-Attacke auf den Zentral-Computer der Polizei erschwert. + +[JAILB_M] +stellte Bürgermeister O'Donovan klar, dass die Polizei + +[JAILB_N] +* + +[JAILB_O] +Da die Fertigstellung des Porter Tunnels sich verzögert, + +[JAILB_X] +In einer ersten Stellungnahme + +[FEDS_SE] +/-Taste - AUSWAHL + +[FEDS_SB] +/-Taste - AUSWAHL "-Taste - ZURÜCK + +[TM4_A] +~w~Ach, du bist es. Toni ist nicht da. + +[TM4_A2] +~w~Aber er hat wieder ein Liebesbriefchen für dich hinterlassen. + +[DIAB2_A] +Ich habe mein Entertainment-Business mit nichts als dem üppigen Inhalt meiner Lederhose gestartet. + +[LM5_9] +GIRLS: + +[PERPIC] +Versteckte Päckchen gefunden + +[CO_ONE] +Verstecktes Päckchen ~1~ von ~1~ + +[LOVE3_3] +~g~Das Flugzeug hat ~1~ von 6 Päckchen abgeworfen. + +[FARE11] +~g~Fahrziel: ~w~'Baustelle' ~g~in Fort Staunton. + +[GA_21] +In dieser Garage bringst du keine Autos mehr unter. + +[CHEAT1] +Cheat aktiviert + +[CHEAT2] +Waffen-Cheat + +[CHEAT3] +Health-Cheat + +[CHEAT4] +Panzerungs-Cheat + +[CHEAT5] +Fahndungslevel-Cheat + +[CHEAT6] +Geld-Cheat + +[CHEAT7] +Wetter-Cheat + +[AS1_H] +~r~Es ist dir nicht gelungen, das Killerkommando in die Yakuza-Falle zu locken! + +[FEDS_BA] +"-Taste - ZURÜCK + +[RAMP_A] +ALLE AMOKLÄUFE BEENDET! + +[USJ_ALL] +ALLE MONSTER-STUNTS ABSOLVIERT! + +[FARE23] +~g~Fahrtziel: ~w~'Import-Export Garage' ~g~im Cochrane Dam Distrikt. + +[L_TRN_1] +Mit der U-Bahnlinie L kannst du in Portland herumfahren. Drücke die~h~ ~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in eine U-Bahn ~h~ein- oder auszusteigen~w~. + +[L_TRN_2] +Mit der U-Bahnlinie L kannst du in Portland herumfahren. Drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in eine U-Bahn ~h~ein- oder auszusteigen~w~. + +[S_TRN_1] +Mit der U-Bahn kannst du in Liberty herumfahren. Drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in eine U-Bahn ~h~ein- oder auszusteigen~w~. + +[S_TRN_2] +Mit der U-Bahn kannst du in Liberty herumfahren. Drücke die ~h~~k~~VEHICLE_ENTER_EXIT~-Taste~w~, um in eine U-Bahn ~h~ein- oder auszusteigen~w~. + +[AS1_C] +~w~Sie hat drei Killerkommandos in Liberty verteilt, die dich zur Strecke bringen sollen. + +[AS1_G] +~r~Alle Yakuza sind erledigt! + +[JAN] +Jan + +[FEB] +Feb + +[MAR] +Mär + +[APR] +Apr + +[MAY] +Mai + +[JUN] +Jun + +[JUL] +Jul + +[AUG] +Aug + +[SEP] +Sept + +[OCT] +Okt + +[NOV] +Nov + +[DEC] +Dez + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +VERBLEIBENDE BUGGIES: + +[BONUS] +~g~BONUS $~1~ + +[HORN1] +Drück die ~h~L3-Taste~w~, um zu ~h~hupen. + +[HORN2] +Drück die ~h~L1-Taste~w~, um zu ~h~hupen. + +[HORN3] +Drück die ~h~R1-Taste~w~, um zu ~h~hupen. + +[LM3_1A] +Drück die ~h~~k~~VEHICLE_HORN~-Taste~w~, um zu ~h~hupen. So weiß Misty, dass du da bist. + +[LM3_1B] +Drück die~h~ ~k~~VEHICLE_HORN~-Taste~w~, um zu ~h~hupen. So weiß Misty, dass du da bist. + +[LM3_1C] +Drück die~h~ ~k~~VEHICLE_HORN~-Taste~w~, um zu ~h~hupen. So weiß Misty, dass du da bist. + +[RADIO_A] +Drück die ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~-Taste~w~, um die verschiedenen ~h~Radiosender zu hören. + +[RADIO_B] +Drück die ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~-taste~w~, um die verschiedenen ~h~Radiosender zu hören. + +[RADIO_C] +Drück die ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~-Taste~w~, um die verschiedenen ~h~Radiosender zu hören. + +[RADIO_D] +Drück die ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~-Taste~w~, um die verschiedenen ~h~Radiosender zu hören. + +[FEC_EXV] +In Fahrzeug ein-\aussteigen + +[TAXI_M] +'TAXI DRIVER' + +[COP_M] +'BÜRGERWEHR' + +[FIRE_M] +'FEUERWEHR' + +[AMBUL_M] +'KRANKENWAGEN' + +[HJ_IS] +IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_PIS] +SUPER IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_DIS] +DOPPELTER IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_PDIS] +SUPER-DOPPEL-IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_TIS] +DREIFACHER IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_PTIS] +SUPER-DREIFACH-IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_QIS] +VIERFACHER IRRSINNS-STUNT-BONUS: $~1~ + +[HJ_PQIS] +SUPER-VIERFACH-IRRSINNS-STUNT-BONUS: $~1~ + +[AM1_K] +Salvatore Leone wird Luigis Club in zirka drei Stunden verlassen. (0~1~:~1~) + +[IMPEXPP] +Import-Export Garage, Portland Harbor. Wir haben Bestellungen für verschiedene Fahrzeuge. Näheres steht auf unserem Schwarzen Brett. + +[VANHSTP] +Willst du noch mehr Securicars aufgebrochen haben? Bring sie zu unserer Garage in Portland Harbor. + +[EMVHPUP] +Zahlen Bestpreise für Neu- und Gebrauchtwagen. Bring sie zum Kran im Nordosten von Portland Harbor. + +[STANDS] +ZERSTÖRTE ESPRESSOSTÄNDE: + +[STASH] +~g~Deponiere das SPANK auf der ~p~Baustelle! + +[MCSTNS] +Keine Memory Card (PS2) in MEMORY CARD-Steckplatz 1. Trotzdem starten? (JA oder NEIN) + +[LOVE3_5] +~g~Das Flugzeug ist jetzt in Reichweite. + +[LOVE3_6] +~r~Die Polizei war vor dir bei den Päckchen! + +[SIREN_1] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~-Taste~w~. + +[SIREN_2] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~-Taste~w~. + +[FM3_8C] +~w~Ich brauch $100 000 für Auslagen. + +[MCLOAD] +Daten werden geladen. Bitte die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 nicht entfernen, kein Reset vornehmen und die Konsole nicht ausschalten. + +[FES_GME] +Fehler beim Lesen der Memory Card (PS2) in MEMORY CARD-Steckplatz 1! Bitte überprüfen und noch einmal versuchen. + +[FESZ_QF] +Soll die Memory Card (PS2) in MEMORY CARD-Steckplatz 1 wirklich formatiert werden? + +[FESZ_LS] +Ladevorgang abgeschlossen. + +[RM3_5] +~g~Du hast ~1~ von 6 Päckchen mit Beweisfotos. + +[LOVE3_2] +~g~Du hast alle Päckchen. Bring sie zu Donald Love. + +[LOVE4_4] +~g~Bring das Päckchen zu Donald Love! + +[FEB_SAV] +Laden + +[FEP_SAV] +SPIEL LADEN + +[AS2_12A] +~g~Wenn du den ersten Espressostand zerstört hast, hast du 8 Minuten Zeit, bis das Kartell seine Dealer warnt! + +[AS3_1A] +~g~Jetzt fahr zu der ~b~Markierungsboje! + +[NOCONT] +Bitte stecken Sie einen Analog Controller (DUALSHOCK#) oder einen Analog Controller (DUALSHOCK#2) in Controller-Anschluss 1, um fortzufahren. + +[BET_JB] +VON CATALINA, SEINER GELIEBTEN, IM STICH GELASSEN, FÜR SCHULDIG BEFUNDEN UND VERURTEILT, BEFINDET ER SICH AUF DEM WEG INS GEFÄNGNIS VON LIBERTY CITY. DOCH IN SEINEM KOPF HÄMMERT EIN GEDANKE... MIT EUCH BIN ICH NOCH NICHT FERTIG! + +[END_A] +Die Bewohner von Cedar Grove erholen sich langsam + +[END_B] +von dem Schock der schrecklichen Ereignisse, + +[END_C] +die sich gestern hier abgespielt haben. + +[END_D] +Clive Denver, ein Augenzeuge, beschrieb der Polizei + +[END_E] +den Täter, den er zusammen mit einer dunkelhaarigen Frau flüchten sah. + +[END_F] +Hör mal, wir werden sehr viel Spaß miteinander haben. Weißt du, + +[END_G] +ich liebe dich nämlich. Wirklich, du bist so groß und stark, + +[END_H] +und genau so einen Mann brauche ich. + +[END_I] +Jedenfalls - was wollte ich gerade sagen? + +[END_J] +Weiß nicht mehr. Aber du verstehst doch, was ich meine, oder? + +[END_K] +Der Donner von Explosionen erschütterte umliegende Häuser. Menschen rannten in Deckung. + +[END_L] +Mehrere Anwohner wurden verletzt, als es in dem Chaos zu einem Schusswechsel + +[END_M] +zwischen Bodeneinheiten und einem Helikopter kam, der über dem Damm kreiste. + +[END_N] +Ja, von hier in den Gärten konnten wir alles genau beobachten. + +[END_O] +Wie sie den Helikopter dann abgeschossen haben, + +[END_P] +das war ein ziemliches Feuerwerk. + +[END_Q] +Die Zahl der Toten ist inzwischen auf über 20 angestiegen + +[END_R] +und die Polizei findet immer noch weitere Leichen. + +[END_S] +Die Gerüchte, dass es sich bei den Opfern um Angehörige des kolumbianischen Kartells + +[END_T] +handelt, wurden von offizieller Seite bisher nicht dementiert. + +[END_U] +Und es gibt nach wie vor keine Anhaltspunkte für das Motiv der Tat. + +[END_V] +Ich hab mir 'nen Fingernagel abgebrochen und meine Frisur ist hin! + +[END_W] +Fünfzig Dollar im Eimer! + +[PAPER1] +GANGSTER VON KOMPLIZIN, DIE ER LIEBT, VERRATEN. GERICHT BEFINDET RÄUBER EINSTIMMIG FÜR SCHULDIG. + +[PAPER2] +ZEHN JAHRE AUS LIEBE! + +[JAILB_D] +und es hat sich bisher auch keine kriminelle Vereinigung zu dem Attentat bekannt. + +[JAILB_H] +Die meisten Zeugen kamen ums Leben und die Brücke wurde schwer beschädigt. + +[JAILB_I] +Man geht davon aus, dass auch einige der Häftlinge bei der sich ereignenden Explosion umkamen. + +[JAILB_J] +* + +[FEB_CPC] +Konfiguration d. Steuerung + +[FEC_PED] +Steuerung zu Fuß + +[FEC_VEH] +Steuerung in Fahrzeug + +[FEC_FPR] +Steuerung für First-Person + +[FEC_CMM] +Allgemeine Steuerung + +[FEC_PWL] +Nach links + +[FEC_PWR] +Nach rechts + +[FEC_PWF] +Vorwärts gehen + +[FEC_PWT] +Auf Kamera zugehen + +[FEC_PLB] +Nach hinten schauen + +[FEC_PFR] +Waffe abfeuern + +[FEC_CLE] +Eine Waffe nach links + +[FEC_CRI] +Eine Waffe nach rechts + +[FEC_LKT] +Ziel fixieren + +[FEC_PJP] +Fußgänger springen + +[FEC_PSP] +Fußgänger sprinten + +[FEC_PSH] +Fußgänger schießen + +[FEC_TLF] +Ein Ziel nach links + +[FEC_TRG] +Ein Ziel nach rechts + +[FEC_CCM] +Kamera hinter Spieler zentrieren + +[FEC_SZI] +Mit Präzisionsgewehr heranzoomen + +[FEC_SZO] +Mit Präzisionsgewehr herauszoomen + +[FEC_LKL] +First-Person nach links schauen + +[FEC_LRT] +First-Person nach rechts schauen + +[FEC_LUP] +First-Person nach oben schauen + +[FEC_LDN] +First-Person nach unten schauen + +[FEC_LBH] +Aus Fahrzeug nach hinten schauen + +[FEC_LLF] +Aus Fahrzeug nach links schauen + +[FEC_LRG] +Aus Fahrzeug nach rechts schauen + +[FEC_HRN] +Hupe + +[FEC_HBR] +Handbremse + +[FEC_ACL] +Gas geben + +[FEC_BRK] +Bremsen + +[FEC_TSM] +Spezialmissionen An/Aus + +[FEC_CRD] +Radiosender wechseln + +[FEC_ENT] +In Fahrzeug Ein-/Aussteigen + +[FEC_WPN] +Waffe abfeuern + +[FEC_PAS] +Pause + +[FEC_FPO] +First Person Weapons Toggle. + +[FEC_SMS] +Mauszeiger An/Aus + +[FEC_CMS] +Blickwinkel wechseln. + +[FEC_TSS] +Screen Shot + +[FEN_NET] +Netzwerk + +[FEN_CON] +Verbindung + +[FEN_GAM] +Spiel suchen + +[FEN_TYP] +Spiel-Typ + +[FEN_TY0] +Deathmatch + +[FEN_TY1] +Deathmatch Stealth + +[FEN_TY2] +Team Deathmatch + +[FEN_TY3] +Team Deathmatch Stealth + +[FEN_TY4] +Stash the Cash + +[FEN_TY5] +Capture the Flag + +[FEN_TY6] +Rat Race + +[FEN_TY7] +Domination + +[FEN_NAM] +Name: + +[FEN_GNA] +Spiel-Name: + +[FEM_MAP] +Karte auswählen + +[FEN_PLS] +Spieler-Einstellungen + +[FEN_PLC] +Spieler-Farbe + +[FEM_MA0] +Liberty City + +[FEM_MA1] +Rotlichtbezirk + +[FEM_MA2] +Chinatown + +[FEM_MA3] +Der Tower + +[FEM_MA4] +Die Kanalisation + +[FEM_MA5] +Industriepark + +[FEM_MA6] +Docks + +[FEM_MA7] +Staunton + +[FEC_DBG] +Debug-Menü + +[FEC_TGD] +Mit Pad zwischen Spiel- u. Debug-Modus wechseln + +[FEC_TDO] +Debug-Kamera Aus + +[FEC_IVH] +Maus horizontal invertieren + +[FEC_MSL] +MAUSTASTE L + +[FEC_MSM] +MAUSTASTE M + +[FEC_MSR] +MAUSTASTE R + +[FEC_QUE] +??? + +[FEC_TWO] +Nur zwei Tastaturtasten erlaubt + +[FEC_OMS] +Nur eine Maustaste erlaubt + +[FEC_OJS] +Nur ein Joystick-Button pro Aktion erlaubt + +[FEC_PTL] +"Ziel fixieren" u. "Waffenauswahl links" gleichzeitig drücken. + +[FEC_PTR] +"Ziel fixieren" u. "Waffenauswahl rechts" gleichzeitig drücken. + +[FEC_LBC] +"Nach links schauen" u. "Nach rechts schauen" gleichzeitig drücken. + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Pause in Multiplayer nicht möglich. Zum Beenden zwei Mal drücken! + +[FEM_SL1] +Speicherplatz 1 ist frei + +[FEM_SL2] +Speicherplatz 2 ist frei + +[FEM_SL3] +Speicherplatz 3 ist frei + +[FEM_SL4] +Speicherplatz 4 ist frei + +[FEM_SL5] +Speicherplatz 5 ist frei + +[FEM_SL6] +Speicherplatz 6 ist frei + +[FEM_SL7] +Speicherplatz 7 ist frei + +[FEM_SL8] +Speicherplatz 8 ist frei + +[FEM_MM] +HAUPTMENÜ + +[FEM_SNG] +Neues Spiel starten + +[FEM_QTW] +Beenden + +[FEQ_SRE] +Wirklich beenden? Alle Daten seit dem letzten Speichern werden verlorengehen. Weiter? + +[FEQ_SRW] +Spiel wirklich beenden? + +[FEG_SRV] +SERVER + +[FEG_MAP] +KARTE + +[FEG_PLY] +SPIELER + +[FEG_TYP] +TYP + +[FEG_PNG] +PING + +[FET_FG] +SPIEL SUCHEN + +[FET_SP] +SINGLEPLAYER + +[FET_MP] +MULTIPLAYER + +[FET_HG] +SPIEL HOSTEN + +[FET_PS] +SPIELER SETUP + +[FET_CON] +VERBINDUNG + +[FET_AUD] +AUDIO-SETUP + +[FET_DIS] +ANZEIGEN-SETUP + +[FET_LAN] +SPRACHEN-SETUP + +[FET_LG] +SPIEL LADEN + +[FET_DG] +SPIEL LÖSCHEN + +[FET_NG] +NEUES SPIEL + +[FET_SG] +SPIEL SPEICHERN + +[FET_MAP] +KARTE AUSWÄHLEN + +[FET_GT] +SPIEL-TYP + +[FET_CTL] +CONTROLLER-SETUP + +[FET_OPT] +OPTIONEN + +[FET_QG] +SPIEL BEENDEN + +[FET_STA] +STATISTIK + +[FET_BRE] +MISSIONSINFOS + +[FEC_WAR] +Achtung! + +[FEC_OKK] +OK + +[FED_CON] +Löschen bestätigen + +[FES_SSC] +Spiel wurde gespeichert. + +[DEL_FNM] +Datei wurde gelöscht. + +[PCLOAD] +Datei wird geladen + +[PCRESRT] +GTA 3 wird neu gestartet + +[FEC_DLF] +Löschen fehlgeschlagen. + +[FEC_SVU] +Speichern fehlgeschlagen. + +[FEC_LUN] +Laden fehlgeschlagen. Datei beschädigt. Bitte löschen. + +[FEN_PLA] +Anzahl der Spieler: + +[FET_NON] +KEINE SPIELE VERFÜGBAR + +[FET_SFG] +SPIELE WERDEN GESUCHT... + +[FET_SRT] +SPIELE WERDEN SORTIERT... + +[FEF_LAN] +LAN + +[FEF_INT] +INTERNET + +[FET_REF] +Aktualisieren + +[FET_FIL] +Filter + +[FET_JG] +Beitreten + +[FEC_NTW] +Talk To Network + +[FEC_ESR] +Esc-Taste nicht zugelassen + +[FEC_GSL] +Show head bob: + +[FIL_FLT] +SPIELE-LISTE FILTERN + +[FET_SAN] +NEUES SPIEL STARTEN + +[FIL_MAP] +Karte: + +[FIL_SRV] +Server: + +[FIL_TYP] +Spiel-Typ + +[FIL_SPC] +Offene Spiele? + +[FIL_PNG] +Ping: + +[FEN_UKH] +Unbekannter Host + +[FEN_UKM] +Map nicht gefunden + +[FEN_UKT] +Spiel-Typ nicht gefunden + +[FEN_NCI] +KEINE INTERNET-VERBINDUNG + +[FET_PAU] +PAUSENMENÜ + +[FET_SGA] +SPIEL STARTEN + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FEC_WHL] +Lenkrad + +[FEC_CNT] +Controller-Typ: + +[FET_APL] +ÜBERNEHMEN + +[FES_CSA] +Wählen Sie eine Skin aus der Liste aus: + +[FES_SKN] +SKIN-NAME + +[FES_DAT] +DATUM + +[FES_NON] +KEINE SKINS VERFÜGBAR + +[FEA_FM9] +SPIELER MP3 + +[FESZ_QZ] +Dieses Spiel wirklich speichern? + +[FES_CGA] +Momentan verfügbare Speicherplätze: + +[FES_SCG] +Laufendes Spiel speichern? + +[FES_LCG] +Spiel laden und weiterspielen? + +[FEC_FIR] +Feuern + +[FEC_NWE] +Nächste Waffe + +[FEC_PWE] +Vorherige Waffe + +[FEC_FOR] +Vorwärts + +[FEC_BAC] +Rückwärts + +[FEC_LEF] +Links + +[FEC_RIG] +Rechts + +[FEC_ZIN] +Heranzoomen + +[FEC_ZOT] +Herauszoomen + +[FEC_EEX] +Ein-/Aussteigen + +[FEC_RAD] +Radio + +[FEC_SUB] +Spezialmission + +[FEC_CMR] +Blickwinkel ändern + +[FEC_JMP] +Springen + +[FEC_SPN] +Sprinten + +[FEC_HND] +Handbremse + +[FEC_TUL] +Geschütz links + +[FEC_TUR] +Geschütz rechts + +[FEC_LOL] +Nach links schauen + +[FEC_LOR] +Nach rechts schauen + +[FEC_NTR] +Nächstes Ziel + +[FEC_PTT] +Vorheriges Ziel + +[FEC_LBA] +Nach hinten schauen + +[FEC_CEN] +Kamera zentrieren + +[FEC_UND] +(NEIN) + +[FET_CFT] +ZU FUSS + +[FET_CCR] +IN FAHRZEUG + +[CVT_MSG] +Texturen werden in optimales Format für Ihre Grafikkarte konvertiert + +[FET_CAC] +AKTION + +[FEC_IBT] +- + +[FEC_SPC] +LEERT. + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +NICHT BEL. + +[FET_CME] +STEUERUNGSART + +[FET_RDK] +STEUERUNG ÄNDERN + +[FET_AMS] +MAUS-EINSTELLG. + +[FET_STI] +STANDARD STEURUNGSKONFIG. + +[FET_CTI] +CLASSIC STEURUNGSKONFIG. + +[FET_MTI] +MAUS STEURUNGSKONFIG. + +[FET_DAM] +DYNAMISCHE AKUSTIK + +[FEC_TFL] +Geschütz Links + +[FEC_TFR] +Geschütz Rechts + +[FEC_TFU] +Geschütz /Dodo aufwärts + +[FEC_TFD] +Geschütz /Dodo abwärts + +[FEC_MWF] +RAD AUFW. + +[FEC_MWB] +RAD ABW. + +[FEC_ORR] +oder + +[FEC_NUS] +NICHT VERWENDET + +[FEC_LUD] +Aufw. Sehen + +[FEC_LDU] +Abw. Sehen + +[FEC_CMP] +COMBO: L+R SEHEN + +[FEC_NTT] +Noch kein Text für diese Taste + +[FEC_FNC] +F~1~ + +[FEC_IRT] +EINFG + +[FEC_DLL] +ENTF + +[FEC_HME] +POS1 + +[FEC_END] +ENDE + +[FEC_PGU] +BILD AUF + +[FEC_PGD] +BILD AB + +[FEC_UPA] +AUF + +[FEC_DWA] +AB + +[FEC_LFA] +LINKS + +[FEC_RFA] +RECHTS + +[FEC_NUM] +NUM + +[FEC_NMN] +NUM~1~ + +[FEC_FWS] +NUM / + +[FEC_PLS] +NUM + + +[FEC_MIN] +NUM - + +[FEC_DOT] +NUM , + +[FEC_NLK] +NUMLOCK + +[FEC_ETR] +ENT + +[FEC_SLK] +ROLLEN + +[FEC_PSB] +UNTBR + +[FEC_BSP] +RÜCKT. + +[FEC_TAB] +TAB + +[FEC_CLK] +CAPSLOCK + +[FEC_RTN] +RET + +[FEC_LSF] +LUMSCHALT + +[FEC_RSF] +RUMSCHALT + +[FEC_LCT] +LSTRG + +[FEC_RCT] +RSTRG + +[FEC_LAL] +LALT + +[FEC_RAL] +RALT + +[FEC_LWD] +LWIN + +[FEC_RWD] +RWIN + +[FEC_WRC] +WINKLICK + +[WIN_TTL] +GTA 3 + +[WIN_95] +GTA 3 läuft nicht unter Windows 95 + +[WIN_DX] +GTA 3 benötigt mind. DirectX Version 8.1 + +[WIN_VDM] +GTA 3 benötigt mind. 12MB freien Grafikspeicher + +[DIAB3_G] +Arriba! + +[FEM_RES] +SPIEL FORTSETZEN + +[FES_SNG] +NEUES SPIEL STARTEN + +[FEM_SP] +SINGLEPLAYER + +[FEM_MP] +MULTIPLAYER + +[FEM_QT] +SPIEL BEENDEN + +[FES_SG] +NEUES SPIEL STARTEN + +[FES_LG] +SPIEL LADEN + +[FEM_HST] +SPIEL HOSTEN + +[FEM_OPT] +OPTIONEN + +[FEM_DBG] +DEBUG + +[FET_PSU] +SPIELER SETUP + +[FET_DEF] +STANDARD WIEDERHERST. + +[FED_BRI] +HELLIGKEIT + +[FED_TRA] +UNSCHÄRFE-FX + +[FEM_LOD] +DISTANZ-DARSTELLG. + +[FEM_VSC] +FRAME SYNC + +[FEM_FRM] +FRAME LIMITER + +[FED_RES] +BILDSCHIRMAUFLSG. + +[FED_WIS] +BREITBILD + +[FEDS_TB] +ZURÜCK + +[FEA_MUS] +MUSIK VOLUME + +[FEA_SFX] +SFX VOLUME + +[FEA_RSS] +RADIOSENDER + +[FEL_ENG] +ENGLISCH + +[FEL_FRE] +FRANZ. + +[FEL_GER] +DEUTSCH + +[FEL_ITA] +ITALIEN. + +[FEL_SPA] +SPANISCH + +[FEA_3DH] +AUDIO HARDWARE + +[FEA_SPK] +BOXEN KONFIGURATION + +[FEA_2SP] +2 BOXEN + +[FEA_4SP] +MEHR ALS 2 BOXEN + +[FEA_EAR] +KOPFHÖRER + +[FEA_NAH] +KEINE AUDIO HARDWARE + +[FET_SNG] +NEUES SPIEL STARTEN + +[FEN_STA] +SPIEL STARTEN + +[GMLOAD] +SPIEL LADEN + +[GMSAVE] +SPIEL SPEICHERN + +[FES_DGA] +SPIEL LÖSCHEN + +[FEM_NON] +KEIN + +[FEC_IVV] +MAUS VERTIKAL INVERTIEREN + +[FEC_MSH] +MAUSEMPFINDLICHKEIT + +[FET_CCN] +STEUERUNG: CLASSIC + +[FET_SCN] +STEUERUNG: STANDARD + +[FES_SET] +SKIN VERWENDEN + +[GHOST] +Ghost + +[WIN_RSZ] +Neue Auflösung konnte nicht aktiviert werden + +[FET_APP] +LMT,RETURN,UM NEUE EINSTLLG. ZU SPEICHERN + +[FET_HRD] +STANDARDEINSTLLG. WIEDERHERGESTELLT + +[FET_MST] +MAUSSTEUERUNG + +[FEC_STR] +NUM STERN + +[FET_MIG] +LINKS,RECHTS,MAUSRAD ZUR EINSTLLG. + +[FET_CIG] +RÜCKT. ZUM LÖSCHEN - LMT,RETURN ZUM ÄNDERN + +[FET_RIG] +NEUE STEUERUNG FÜR DIESE AKTION WÄHLEN ODER ESC FÜR ABBRUCH + +[FET_EIG] +KANN DIESER AKTION KEINE STEUERUNG ZUWEISEN + +[NO_PCCD] +Bitte legen Sie die GTA 3 Disk 2 ein oder drücken Sie ESC zum Abbrechen + +[CVT_ERR] +Kein Platz mehr auf der Festplatte. Bitte schaffen Sie Speicherplatz, bevor Sie fortfahren. ESC zum Abbrechen. + +[FED_SUB] +UNTERTITEL + +[FET_DSN] +Standard-Player Skin.bmp + +[EBAL] +'GIB MIR LIBERTY' + +[LM4] +'PUMP-ACTION-THRILLER' + +[REPLAY] +WIEDERHOLUNG + +[FEC_SFT] +UMSCHALT + +[CRED254] +STUDIO MANAGER + +[CVT_CRT] +Texturen können nicht für Ihre Grafikkarte konvertiert werden. Sie müssen sich als Administrator einloggen, damit dies möglich ist. Verlassen mit ESC. + +[FEM_ON] +AN + +[FEM_OFF] +AUS + +[FEM_YES] +JA + +[FEM_NO] +NEIN + +[FES_WAR] +Speichere Daten, bitte warten... + +[FED_DLW] +Lösche Daten, bitte warten... + +[FED_LDW] +Lade Daten, bitte warten... + +[FEC_SLC] +Slot ist beschädigt + +[FED_LFL] +Spiel konnte nicht geladen werden. Das Spiel wird neu gestartet. + +[FET_RSO] +ORIGINAL-EINSTELLG. WIEDERHERGESTELLT + +[FET_RSC] +HARDWARE NICHT VERFÜGBAR - ORIGINAL-EINSTELLG. WIEDERHERGESTELLT + +[CRED270] +MIKE HONG + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPANISCH + +[FEL_POL] +POLNISCH + +[FEL_RUS] +RUSSISCH + +{ new display menus } +[FET_GFX] +GRAFIK-SETUP + +[FED_MIP] +MIP MAPPING + +[FED_AAS] +KANTENGLÄTTUNG + +[FED_FIL] +TEXTURFILTER + +[FED_BIL] +BILINEAR + +[FED_TRL] +TRILINEAR + +[FED_WND] +FENSTERMODUS + +[FED_FLS] +VOLLBILD + +[FEM_CSB] +CUTSCENE BALKEN + +[FEM_SCF] +BILDSCHIRMFORMAT + +[FEM_ISL] +KARTENSPEICHERNUTZUNG + +[FEM_LOW] +NIEDRIG + +[FEM_MED] +MITTEL + +[FEM_HIG] +HOCH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +FREIE KAMERA + +{ Linux joy detection } +[FEC_JOD] +JOYSTICK ERKENNEN + +[FEC_JPR] +Drücke eine beliebige Taste auf dem Joystick der für das Spiel verwendet werden soll, und er wird ausgewählt. + +[FEC_JDE] +Joystick erkannt + +{ mission restart } +[FET_RMS] +MISSION WIEDERHOLEN + +[FESZ_RM] +WIEDERHOLEN? + +{ more graphics } +[FED_VPL] +FAHRZEUG-PIPELINE + +[FED_PRM] +CHARAKTER KANTEN LICHT + +[FED_RGL] +GLÄNZENDE STRAßEN + +[FED_CLF] +FARBFILTER + +[FED_WLM] +WELT LIGHTMAPS + +[FED_MBL] +BEWEGUNGSUNSCHÄRFE + +[FEM_SIM] +SIMPEL + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +{ controls } +[FEC_IVP] +PAD VERTIKAL INVERTIEREN + +{ map } +[FEM_TWP] +Wegpunkt umschalten + +[FEA_FMN] +RADIO AUS + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD-TYP + +[FEC_CCF] +KONFIGURATION + +[FEC_CF1] +KONFIGURATION 1 + +[FEC_CF2] +KONFIGURATION 2 + +[FEC_CF3] +KONFIGURATION 3 + +[FEC_CF4] +KONFIGURATION 4 + +[FEC_CDP] +CONTROLLER-ANZEIGE + +[FEC_ONF] +Zu Fuß + +[FEC_INC] +Im Auto + +[FEC_VIB] +Vibration : + +[FET_AGS] +KONTROLLEREINSTELLUNGEN + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED diff --git a/utils/gxt/gxt.exe b/utils/gxt/gxt.exe new file mode 100644 index 0000000..0f55b76 Binary files /dev/null and b/utils/gxt/gxt.exe differ diff --git a/utils/gxt/italian.txt b/utils/gxt/italian.txt new file mode 100644 index 0000000..9dfee85 --- /dev/null +++ b/utils/gxt/italian.txt @@ -0,0 +1,8205 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBFàèéìòùÀÈÉÌÒÙ + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Ehi! Torna nel veicolo! + +[IN_VEH2] +~g~Avrai bisogno di un veicolo per questo lavoro! + +[IN_BOAT] +~g~Hai bisogno di un'imbarcazione per questo lavoro! + +[HEY] +~g~Non procedere da solo, stai insieme ai tuoi compagni! + +[HEY2] +~g~Non dividetevi, tieni il gruppo compatto! + +[HEY3] +~g~Hai abbandonato il tuo uomo! Torna indietro e recupera 8-Ball! + +[HEY4] +~g~Se abbandoni Misty, Luigi ti fa a pezzi! Vai a recuperarla! + +[HEY5] +~g~Manca una ragazza alla lista! Vai a recuperarla! + +[HEY6] +~g~Hai perso l'onore insieme a Yakuza Kanbu. Devi proteggerlo! + +[HEY7] +~g~Un'arma extra può tornare utile. Torna indietro e recupera il tuo contatto! + +[HEY8] +~g~Forse non hai ben presente il concetto di protezione: non abbandonare il vecchietto orientale! + +[HEY9] +~g~Vuoi sapere cosa si dice in giro? Fa'una visita al tuo contatto! + +[HELP2_A] +Premi il ~h~tasto /~w~ mentre corri per effettuare uno ~h~scatto~w~. + +[HELP3] +Ricorda che puoi eseguire uno scatto solo per brevi tratti. + +[HELP4_A] +Premi il ~h~tasto ~k~~VEHICLE_ACCELERATE~~w~ per ~h~accelerare~w~. + +[HELP4_D] +Sposta la ~h~levetta analogica destra~w~ verso l'alto per ~h~accelerare~w~. + +[HELP5_A] +Premi il ~h~tasto ~k~~VEHICLE_BRAKE~~w~ per ~h~frenare~w~ o, se il veicolo è fermo, per inserire la ~h~retromarcia~w~. + +[HELP5_D] +Sposta la ~h~levetta analogica destra~w~ verso il basso per ~h~frenare~w~ o, se il veicolo è fermo, per inserire la ~h~retromarcia~w~. + +[HELP6_A] +Premi il ~h~tasto ~k~~VEHICLE_HANDBRAKE~~w~ per tirare il ~h~freno a mano~w~. + +[HELP6_C] +Premi il ~h~tasto ~k~~VEHICLE_HANDBRAKE~~w~ per tirare il ~h~freno a mano~w~. + +[HELP6_D] +Premi il ~h~tasto ~k~~VEHICLE_HANDBRAKE~~w~ per tirare il ~h~freno a mano~w~. + +[HELP7_A] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per ~h~mirare~w~ con il fucile di precisione. + +[HELP7_D] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per ~h~mirare~w~ con il fucile di precisione. + +[HELP8_A] +Premi il ~h~tasto ~k~~PED_SNIPER_ZOOM_IN~~w~ per ~h~zoomare~w~ col fucile e il ~h~tasto ~k~~PED_SNIPER_ZOOM_OUT~~w~ per ~h~allargare il campo~w~. + +[HELP9_A] +Premi il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ con il fucile di precisione. + +[HELP10] +Questo distintivo indica che hai un livello di sospetto. + +[HELP11] +Più distintivi hai, maggiore è il tuo livello di sospetto. + +[HELP13] +In alcuni casi potresti dover utilizzare passaggi non indicati sul radar. + +[TIMER] +Questa è una missione a tempo: devi completarla prima che il contatore raggiunga lo zero. + +[MISTY1] +~r~Mistry è pronta per l'obitorio! + +[OUT_VEH] +~g~Esci dal veicolo! + +[GARAGE] +Porta l'auto dentro il garage e poi esci a piedi all'esterno. + +[WANTED1] +~g~Semina i poliziotti per perdere il tuo livello di sospetto. + +[NODOORS] +~g~Non sono sardine! Trova un veicolo con sedili a sufficienza. + +[TRASH] +~g~Hai ridotto proprio male il tuo veicolo! Vedi di farlo riparare! + +[WRECKED] +~g~Il veicolo è a pezzi! + +[HORN] +~g~Suona il clacson. + +[HORN4] +Premi il ~h~tasto R3~w~ per attivare il ~h~clacson~w~. + +[NOMONEY] +~g~Ti servono più soldi! + +[OUTTIME] +~r~Troppo lento, troppo lento! + +[SPOTTED] +~r~Ti hanno visto! + +[REWARD] +RICOMPENSA ~1~$ + +[GAMEOVR] +GAME OVER + +[Z] +Valore asse Z: ~1~ + +[M_FAIL] +MISSIONE FALLITA! + +[M_PASS] +MISSIONE COMPIUTA! ~1~$ + +[O_PASS] +LAVORO OCCASIONALE ESEGUITO! + +[O_FAIL] +LAVORO OCCASIONALE FALLITO! + +[DEAD] +MASSACRATO! + +[BUSTED] +BECCATO! + +[S_PROMP] +Quando non stai eseguendo una missione, puoi ~h~salvare la partita qui~w~ in questo modo il tempo avanzerà di sei ore. + +[NUMBER] +~1~ + +[SCORE] +~1~$ + +[LOADCAR] +CARICAMENTO VEICOLO... (PREMI L1 PER ANNULLARE) + +[CARSOFF] +Veicoli disabilitati. + +[CARS_ON] +Veicoli attivati. + +[TEXTXYZ] +Scrittura delle coordinate sul file... + +[CHEATON] +Modalità trucchi attivata + +[CHEATOF] +Modalità trucchi disattivata + +[UZI_IN] +L'Uzi adesso e' disponibile da AmmuNation! + +[IMPORT1] +Esci fuori e aspetta il tuo veicolo. + +[PAGEB1] +Pistola depositata nel nascondiglio + +[PAGEB2] +Uzi depositato nel nascondiglio + +[PAGEB3] +Armatura depositata nel nascondiglio + +[PAGEB4] +Fucile a pompa depositato nel nascondiglio + +[PAGEB5] +Granate depositate nel nascondiglio + +[PAGEB6] +Molotov depositate nel nascondiglio + +[PAGEB7] +AK47 depositato nel nascondiglio + +[PAGEB8] +Fucile di precisione depositato nel nascondiglio + +[PAGEB9] +M16 depositato nel nascondiglio + +[PAGEB10] +Lanciamissili depositato nel nascondiglio + +[PAGEB11] +Lanciafiamme depositato nel nascondiglio + +[WANT_A] +Non puoi essere arrestato se il tuo ~h~livello di sospetto~w~ è nullo. + +[WANT_B] +Il tuo ~h~livello di sospetto~w~ è rappresentato da una riga di stelle nell'angolo superiore destro dello schermo. + +[WANT_C] +Adesso hai un ~h~livello di sospetto~w~ pari a uno... + +[WANT_D] +due... + +[WANT_E] +tre... + +[WANT_F] +Man mano che il tuo ~h~livello di sospetto~w~ aumenta, attirerai l'attenzione di forze dell'ordine sempre più potenti. + +[WANT_G] +Se vieni ~h~'beccato'~w~, verrai portato alla più vicina stazione di polizia. + +[WANT_H] +I poliziotti prenderanno tutte le tue armi e parte dei tuoi risparmi come bustarella. + +[WANT_I] +Qualsiasi missione stessi affrontando, sarà considerata fallita. + +[WANT_J] +Scoprirai alcuni modi per ridurre il tuo livello di sospetto procedendo nel gioco. + +[WANT_K] +Se sei in un veicolo, i ~h~carozzieri~w~ potranno ~h~azzerare il tuo livello di sospetto~w~. + +[HEAL_B] +Quando sei ~h~'massacrato'~w~, verrai trasportato al più vicino ospedale. + +[HEAL_C] +Perderai tutte le tue armi e i dottori prenderanno parte dei tuoi risparmi per rimetterti in sesto. + +[HEAL_E] +Scoprirai alcuni modi per curarti o per proteggerti dagli attacchi procedendo nel gioco. + +[DAM] +DANNO: + +[KILLS] +UCCISIONI: + +[FARES] +CLIENTI: + +[BULL] +LINGOTTI: + +[EVID] +PROVE: + +[HEALTH] +CONDIZIONI VEICOLO: + +[COLLECT] +RACCOLTO: + +[BOMB] +Guida il veicolo dentro un'armeria per installare una ~h~bomba~w~. Costo - ~h~1000$~w~. + +[SAVE1] +Passa attraverso la porta per ~h~salvare la partita~w~. Non puoi salvare durante una missione. + +[SAVE2] +Qualsiasi veicolo parcheggiato nel garage verrà salvato insieme alla partita. + +[AMMU] +Entra in AmmuNation per comprare un'arma. + +[BRIDGE1] +Quando verrà riparato il ponte Callahan, potrai raggiungere Staunton Island. + +[TUNNEL] +Quando il sottopassaggio Porter verrà inaugurato, potrai raggiungere Staunton Island. + +[LUIGI] +MISSIONI LUIGI + +[TONI] +MISSIONI TONI + +[JOEY] +MISSIONI JOEY + +[FRANK] +MISSIONI SALVATORE + +[DIABLO] +MISSIONI DIABLO + +[ASUKA] +MISSIONI ASUKA + +[B_SITE] +MISSIONI SUBURBANE ASUKA + +[KENJI] +MISSIONI KENJI + +[RAY] +MISSIONI RAY + +[LOVE] +MISSIONI LOVE + +[YARDIE] +MISSIONI YARDIE + +[HOOD] +MISSIONI HOOD + +[CITYZON] +Città di Liberty + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Molo atlantico + +[PORT_E] +Porto di Portland + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Spiaggia di Portland + +[LITTLEI] +Saint Mark + +[REDLIGH] +Distretto a luci rosse + +[TOWERS] +Hepburn Heights + +[HARWOOD] +Harwood + +[ROADBR1] +Ponte Callahan + +[ROADBR2] +Ponte Callahan + +[TUNNELP] +Sottopassaggio Porter + +[BOMB1] +Garage di 8-Ball + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Campus Liberty + +[CONSTRU] +Fort Staunton + +[PARK] +Parco Belleville + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Aeroporto Francis + +[PROJECT] +Giardini Wichita + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Diga Cochrane + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Ambulanza + +[CAR_2] +Camion dei pompieri + +[CAR_3] +Polizia + +[CAR_4] +Cellulare + +[CAR_5] +Caserma + +[CAR_6] +Rhino + +[CAR_7] +Auto dell'FBI + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Familiare + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Vecchio taxi + +[CAR_39] +Maggiolino + +[LUIGIS] +Club Luigi's + +[GOAWAY] +~g~Stai già svolgendo una missione! + +[LUIGGO] +~g~Luigi sta intervistando delle nuove ragazze. Torna più tari! + +[JOEYGO] +~g~Joey è fuori città con Misty. Passa più tardi! + +[TONIGO] +~g~Toni ha portato sua madre a teatro. Passa un'altra volta! + +[KEMUGO] +~g~Maria e Kemuri sono impegnati la momento. Passa più tardi! + +[KENJGO] +~g~Kenji è a una riunione della Yakuza. Passa un'altra volta! + +[RAYGO] +~g~Ray è andato a un centro estetico. Passa un'altra volta! + +[LOVEGO] +~g~Donald Love si sta occupando di affari urgenti. Prendi un appuntamento più tardi! + +[KENSGO] +~g~Kenji e' occupato! Torna più tardi! + +[ASUSGO] +~g~Asuka adesso non è disponibile! + +[HOODGO] +~g~Gli Hood non sono disponibili! + +[WRONGT1] +~g~Passa tra le 05:00 e le 21:00 per un lavoro + +[WRONGT2] +~g~Passa tra le 06:00 e le 14:00 per un lavoro + +[WRONGT3] +~g~Passa tra le 15:00 e le 00:00 per un lavoro + +[GUN_1A] +Usa il ~h~tasto ~k~~PED_CYCLE_WEAPON_RIGHT~~w~ e il ~h~tasto ~k~~PED_CYCLE_WEAPON_LEFT~~w~ per passare in rassegna le armi. + +[GUN_2A] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per la ~h~mira automatica~w~ e premi il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per sparare! Allenati con i bersagli... + +[GUN_2C] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per la ~h~mira automatica~w~ e premi il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per sparare! Allenati con i bersagli... + +[GUN_2D] +Tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~ per la ~h~mira automatica~w~ e premi il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per sparare! Allenati con i bersagli... + +[GUN_3A] +Mentre tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~, premi il ~h~tasto ~k~~PED_CYCLE_TARGET_LEFT~~w~ o il ~h~tasto ~k~~PED_CYCLE_TARGET_RIGHT~~w~ per cambiare bersaglio. + +[GUN_3B] +Mentre tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~, premi il ~h~tasto ~k~~PED_CYCLE_TARGET_LEFT~~w~ o il ~h~tasto ~k~~PED_CYCLE_TARGET_RIGHT~~w~ per cambiare bersaglio. + +[GUN_4A] +Mentre tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~, puoi camminare o correre tenendo sotto mira il bersaglio. + +[GUN_4B] +Mentre tieni premuto il ~h~tasto ~k~~PED_LOCK_TARGET~~w~, puoi camminare o correre tenendo sotto mira il bersaglio. + +[GUN_5] +Puoi far pratica sparando a questi bersagli di carta. Quando hai finito, riprendi la missione. + +[TAXI1] +~g~Trova un passeggero. + +[FARE1] +~g~Destinazione: ~w~'Meeouch Sex Kitten Club' ~g~nel distretto a luci rosse. + +[FARE2] +~g~Destinazione: ~w~'Supa Save' ~g~a Portland View. + +[FARE3] +~g~Destinazione: ~w~'l'auditorio della scuola' ~g~a Chinatown. + +[FARE4] +~g~Destinazione: ~w~'Greasy Joe's Cafe' ~g~a Callahan Point. + +[FARE5] +~g~Destinazione: ~w~'AmmuNation' ~g~nel distretto a luci rosse. + +[FARE6] +~g~Destinazione: ~w~'Easy Credit Autos' ~g~a Saint Mark. + +[FARE7] +~g~Destinazione: ~w~'Woody's topless bar' ~g~nel distretto a luci rosse. + +[FARE8] +~g~Destinazione: ~w~'Marcos Bistro' ~g~a Saint Mark. + +[FARE9] +~g~Destinazione: ~w~'Garage importazioni-esportazioni' ~g~al porto di Portland. + +[FARE10] +~g~Destinazione: ~w~'Punk Noodles' ~g~a Chinatown. + +[FARE12] +~g~Destinazione: ~w~'lo stadio di football' ~g~in Aspatria. + +[FARE13] +~g~Destinazione: ~w~'la chiesa' ~g~a Bedford Point. + +[FARE14] +~g~Destinazione: ~w~'il Casinò' ~g~in Torrington. + +[FARE15] +~g~Destinazione: ~w~'università di Liberty' ~g~al campus di Liberty. + +[FARE16] +~g~Destinazione: ~w~'il grande magazzino' ~g~nel parco Belleville. + +[FARE17] +~g~Destinazione: ~w~'il museo' ~g~a Newport. + +[FARE18] +~g~Destinazione: ~w~'AmCo Building' ~g~a Torrington. + +[FARE19] +~g~Destinazione: ~w~'Bolt Burgers' ~g~a Bedford Point. + +[FARE20] +~g~Destinazione: ~w~'il parco' ~g~a Belleville. + +[FARE21] +~g~Destinazione: ~w~'Aeroporto Francis'~g~. + +[FARE22] +~g~Destinazione: ~w~'la diga di Cochrane'~g~. + +[FARE24] +~g~Destinazione: ~w~'l'ospedale' ~g~a Pike Creek. + +[FARE25] +~g~Destinazione: ~w~'il parco' ~g~a Shoreside Vale. + +[FARE26] +~g~Destinazione: ~w~'North West Towers' ~g~ai giardini Wichita. + +[NEW_TAX] +PIÙ GRANDI! PIÙ VELOCI! PIÙ ROBUSTI! I nuovi taxi Borgnine disponibili ad Harwood. Chiamate subito il numero 555-BORGNINE! + +[TSCORE2] +~1~$ + +[IN_ROW] +Bonus ~1~ di seguito! ~1~$ + +[TTUTOR] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Taxi. + +[TTUTOR2] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Taxi. + +[A_TIME] ++~1~ secondi + +[A_FULL] +~r~Ambulanza piena! + +[A_RANGE] +~g~Sei fuori dal raggio d'azione della radio. Ritorna in prossimità dell'ospedale! + +[FTUTOR] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Camion dei pompieri. + +[FTUTOR2] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Camion dei pompieri. + +[F_PASS1] +Incendio spento! + +[F_RANGE] +~g~Sei fuori dal raggio d'azione della radio. Ritorna in prossimità della stazione dei pompieri! + +[C_BREIF] +~g~Sospetto visto in prossimità dell'area ~a~. + +[C_RANGE] +~g~Sei fuori dal raggio d'azione della radio. Ritorna in prossimità della stazione di polizia! + +[DODO_FT] +Hai volato per ~1~ secondi! + +[EBAL_A] +Conosco un posto nei dintorni del distretto a luci rosse dove si puo'parlare, + +[EBAL_A1] +ma le mie mani sono a pezzi, quindi è meglio se guidi te, fratello. + +[EBAL_1] +Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un veicolo. + +[EBAL_1B] +Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un veicolo. + +[EBAL_2] +~g~Rientra in macchina! + +[EBAL_3] +Questo è il ~h~radar~w~: usalo per orientarti nella città. Segui il ~h~puntino~w~ sul ~h~radar~w~ per trovare il nascondiglio! + +[EBAL_D] +Conosco un tipo, è nel giro. Si chiama Luigi. + +[EBAL_D1] +Forza, raggiungiamolo, magari riesco a procurarti qualche lavoretto. + +[EBAL_E] +Forza, facciamo un salto. Te lo voglio presentare. + +[EBAL_I] +Il boss sara' con voi al più presto... + +[EBAL_J] +8-Ball ha alcune faccende da sbrigare al piano di sopra. + +[EBAL_K] +Forse potresti farmi un favore. + +[EBAL_L] +Una delle mie ragazze ha bisogno di un passaggio, prendi una macchina, recupera Misty dalla clinica e portala qua. + +[EBAL_N] +E tieni le mani sul volante! + +[EBAL_4] +~r~8-Ball è morto! + +[EBAL_5] +~g~Trova un veicolo! + +[EBAL_6] +~g~Recupera Misty! + +[LM1] +'LE RAGAZZE DI LUIGI' + +[LM2] +'NIENTE SPANK PER LE RAGAZZE' + +[LM3] +'PORTA IN GIRO MISTY PER ME' + +[LM5] +'FESTA DEGLI SBIRRI' + +[LM1_2] +~g~Porta Misty al club Luigi's. + +[LM1_3] +~g~Premi il clacson per far entrare la ragazza in macchina. + +[LM1_6] +~g~Torna in macchina! + +[LM1_7] +~g~Ferma il veicolo in prossimità di Misty per permetterle di salire. + +[LM1_8] +Puoi tornare da Luigi per altri lavori o gironzolare per la città di Liberty. + +[LM2_A] +C'è una nuova droga in giro per la città chiamata SPANK. + +[LM2_E] +Qualche farabutto ne ha passata un po' alle mie ragazze giù al porto di Portland. + +[LM2_B] +Raggiungi lo spacciatore e gioca a baseball con la sua testa! + +[LM2_G] +Voglio soddisfazione per questo insulto! + +[LM2_1] +~g~Prendi la sua macchina e falla riverniciare. + +[LM2_2A] +Usa il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per tirare ~h~pugni~w~ e ~h~calci~w~ o per ~h~usare~w~ la mazza! + +[LM2_2C] +Usa il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per tirare ~h~pugni~w~ e ~h~calci~w~ o per ~h~usare~w~ la mazza! + +[LM2_2D] +Usa il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per tirare ~h~pugni~w~ e ~h~calci~w~ o per ~h~usare~w~ la mazza! + +[LM2_3] +~g~Parcheggia la macchina nel rifugio di Luigi! + +[LM2_4] +~g~Rivernicia la macchina! + +[LM3_A] +Ehi, devo parlarti... Va bene Mick, ne discutiamo più tardi. + +[LM3_B] +Come va ragazzo? + +[LM3_C] +Il figlio del Don, Joey Leone, ha bisogno delle doti di Misty, la sua ragazza. + +[LM3_D] +Valla a prendere a Hepburn Heights... + +[LM3_E] +ma fa attenzione: è il territorio dei Diablo. + +[LM3_F] +Poi portala in fretta fino al suo garage in Trenton. + +[LM3_H] +Mi raccomando, tieni gli occhi sulla strada e lontano da Misty! + +[LM3_1D] +Premi il ~h~tasto L3~w~ per suonare il ~h~clacson~w~ e avvertire Misty che sei arrivato. + +[LM3_2] +~g~Porta Misty da Joey. + +[LM3_4] +~g~Vai a prendere Misty! + +[LM3_5] +Adesso lavori regolarmente per Luigi, vero? Era ora che trovasse un autista di cui ci si può fidare! + +[LM3_7] +Sarò da te fra un minuto, stellina mia. + +[LM3_10] +~g~Recupera un veicolo! + +[LM4_B] +Occupati di ciò che ti ho chiesto. + +[LM4_C] +Se ti serve un'arma, vai sul retro di AmmuNation dal lato opposto della metropolitana. + +[LM5_A] +La festa degli sbirri viene tenuta in una vecchia scuola presso il ponte Callahan. + +[LM5_B] +A quanto sembra vogliono un po' di azione vecchio stile... + +[LM5_C] +Ora ho ragazze su tutti i marciapiedi della città. + +[LM5_D] +Portale alla festa il prima possibile. + +[LM5_1] +~g~Non sovraffollate il mezzo o mi sciuperai le ragazze! ~g~Scarica subito queste e poi vai a cercarne altre. + +[LM5_2] +~r~Una delle ragazze di Luigi è ridotta a pezzi! + +[LM5_3] +~g~Hai bisogno di un mezzo! + +[LM5_4] +~g~Raccogli le ragazze che lavorano presso St. Marks. + +[LM5_5] +~g~Porta le ragazze alla festa degli sbirri! + +[LM5_8] +~g~Ragazze alla festa: ~1~ + +[JM2] +'ADDIO 'CHUNKY' LEE CHONG' + +[JM4] +'L'AUTISTA DI CIPRIANI' + +[JM5] +'CADAVERE NEL BAGAGLIAIO' + +[JM1_1] +~g~Porta la macchina di Forelli al garage di 8-Ball a nord di qui, dietro a 'Easy Credit Autos'. + +[JM1_2] +~g~Parcheggia la macchina nuovamente di fronte al Marco's Bistro. + +[JM1_3] +~g~Attiva la bomba e vattene in fretta! + +[JM1_4] +~g~Hai rovinato il veicolo! Fallo riparare! + +[JM1_5] +~g~La bomba non è stata piazzata! + +[JM1_6] +~g~Parcheggia la macchina nel posto giusto. + +[JM1_8A] +~y~Ehi, amico mio! + +[JM1_8B] +~y~L'armeria è automatizzata: entra dentro, ferma la macchina e il resto dell'operazione verrà eseguito automaticamente. + +[JM1_8C] +~y~La prima volta è gratis, ma le altre volte dovrai pagare. + +[JM2_A] +Chunky Lee Chong sta spacciando SPANK per una nuova gang dalla Colombia... o dal Colorado...o qualcosa del genere... + +[JM2_B] +Non ricordo bene... ma del resto che importa? + +[JM2_D] +Quel verme ha venduto la sua roba per l'ultima volta. + +[JM2_E] +Voglio che tu lo elimini! + +[JM2_G] +Equipaggiati con una calibro nove: sai dove trovarla, vero? + +[JM2_H] +Guardati le spalle a Chinatown: è territorio della Triade. + +[JM3_A] +Bene, colpiremo il furgone portavalori. + +[JM3_B] +Esce da Chinatown tutti i giorni. + +[JM3_C] +I proiettili non scalfiscono neanche la sua blindatura, per cui prendi una macchina e fallo uscire fuori strada. + +[JM3_D] +Colpiscilo forte e le guardie dovrebbero arrendersi. + +[JM3_E] +Ora portalo fino al magazzino vicino agli impianti portuali e i miei ragazzi si occuperanno del resto. + +[JM3_F] +Ricorda che non starà in giro per delle ore, per cui non perdere tempo. + +[JM3_1] +~g~Porta il furgone a destinazione. + +[JM3_2] +~g~Sperona il furgone finché non è danneggiato oltre al 70 percento. + +[JM4_B] +Oh! Ecco il tipo di cui ti stavo parlando. + +[JM4_C] +Bene, ascolta: questo tipo non è italiano e non è un meccanico, ma sa come riparare le cose. + +[JM4_D] +Il suo nome è Toni Cipriani. + +[JM4_E] +Piacere, Toni Cipriani. + +[JM4_F] +Portalo al ristorante Momma's a St Marks, OK? + +[JM4_G] +Adesso ascolta, sto pianificando un lavoro che richiede un buon autista, per cui passa a trovarmi un giorno di questi, OK? + +[JM4_2] +Aspetta qua! Tieni il motore acceso, potrebbero esserci grane. + +[JM4_3] +È un'imboscata della Triade! Portaci via di qua, ragazzo! + +[JM4_4] +La Triade pensa di potersi prendere gioco di me, la Triade... di ME! + +[JM4_6] +Fai attenzione alla macchina! Ti ho detto di non fare stronzate. + +[JM4_7] +~g~Accompagna Toni al ristorante Momma's. + +[JM4_8] +~r~Toni è stato ucciso! + +[JM5_A] +Perfetto! Semplicemente perfetto. + +[JM5_B] +Sei proprio la persona di cui avevo bisogno! + +[JM5_D] +Uno dei Forelli ha voluto fare il furbo e si è beccato quello che si meritava. + +[JM5_E] +Porta il cadavere al rottamatore ad Hardwood, OK? + +[JM5_1] +~g~Porta il cadavere al rottamatore! + +[JM5_2] +~g~Sono i fratelli Forelli! + +[JM6_A] +Ci si prospetta una bella corsetta, vero? + +[JM6_B] +Bene, recupera un veicolo e raggiungi il rifugio a St. Marks e recupera alcuni miei amici. + +[JM6_C] +Stanno per fare un colpo a una banca e hanno bisogno dell'autista. + +[JM6_D] +Ho dato loro la mia parola che tu sei il migliore sul mercato. + +[JM6_E] +Portali in banca prima delle cinque, non un minuto più tardi! + +[JM6_2] +Tieni il motore acceso: torneremo in un attimo! + +[JM6_3] +Portaci via di qua!! + +[JM6_4] +Semina i poliziotti e portaci al rifugio! + +[JM6_6] +~g~Prendi un veicolo meno appariscente! + +[JM6_7] +~g~Hai bisogno di tutti e tre per rapinare la banca! + +[TM1] +'RIPULIRE LA LAVANDERIA' + +[TM2] +'LA RACCOLTA' + +[TM3] +'SALVATORE RICHIEDE UN INCONTRO' + +[TM4] +'TRIADE E TRIBOLAZIONE' + +[TM5] +'PESCE ESPLOSIVO' + +[TONI_P] +Ho un lavoro urgente per te! -Toni + +[TM1_A] +~w~Prendi una sedia, ragazzo, prendi una maledetta sedia. + +[TM1_B] +~w~Allora la lavanderia non intende pagare il pizzo, eh? + +[TM1_C] +~w~La Triade pensa di poter mettersi contro di me? + +[TM1_D] +~w~Insegniamo a questi presuntuosi cosa significa fare sul serio. + +[TM1_E] +~w~Sì, insegniamo loro un po' di rispetto. Nessun mio figliolo si fa fregare dalla Triade. + +[TM1_F] +Tuo padre, pace all'anima sua, non si è mai fatto fregare da quelli della Triade in Sicilia. + +[TM1_G] +~w~Scusa mamma. Sì mamma. + +[TM1_H] +~w~Voglio che tu distrugga i furgoni della lavanderia + +[TM1_I] +~w~e faccia a pezzi qualsiasi idiota della Triade che oserà mettersi in mezzo. + +[TM1_J] +~w~8-Ball ti fornirà qualsiasi cosa di cui tu possa aver bisogno. + +[TM2_A] +~w~Toni vuole fare il duro, + +[TM2_AA] +ma non riuscirà mai a eguagliare suo padre. Ha lasciato una nota per te sul tavolo. + +[TM2_B] +~w~La lavanderia ha accettato di pagare: bel lavoro, ragazzo! + +[TM2_C] +~w~Vai a recuperare i contanti e portali qua. Fai attenzione alla Triade. + +[TM2_D] +~w~Potrebbero volerti ficcare qualche petardo nel sedere, ma tu non farti impressionare. + +[TM2_E] +~w~Nessuno, e intendo nessuno, fa le scarpe a TONI CIPRIANI! + +[TM2_1] +~g~Riporta i contanti a Toni!!! + +[TM2_2] +~g~Li hai freddati tutti! + +[TM3_MA] +~w~Non so dove sia! + +[TM3_MB] +~w~Giuro che ogni tanto anche lui non si rende conto di cosa fa. + +[TM3_MC] +~w~Suo padre invece era diverso. Sempre in prima linea, sempre in carica, coraggioso... + +[TM3_A] +~w~Don Salvatore richiede un incontro. + +[TM3_B] +~w~Recupera la limousine da suo garage e il suo ragazzo, Joey. + +[TM3_C] +~w~Poi passa a prendere Luigi dal suo club e torna qua a prendere me. + +[TM3_D] +~w~Poi andremo tutti assieme al luogo dell'incontro. + +[TM3_E] +~w~Quelli della Triade non sanno quando è l'ora di fermarsi. + +[TM3_F] +~w~Se vogliono la guerra, avranno la guerra! + +[TM3_G] +~w~Adesso muoviamoci. + +[TM3_1] +~g~Prendi la limousine da Joey. + +[TM3_2] +~g~Ora vai a prendere Luigi. + +[TM3_3] +~g~Ora vai a prendere Toni. + +[TM3_4] +~g~Porta i tuoi passeggeri da Salvatore. + +[TM3_5] +~y~Un'imboscata della Triade!!! + +[TM4_B] +~w~Siamo in GUERRA! La Triade utilizza uno stabilimento per il pesce come facciata. + +[TM4_C] +~w~La maggior parte del loro lavoro si svolge nel mercato del pesce di Chinatown. + +[TM4_D] +~w~La lavanderia ha smesso nuovamente di pagarci il pizzo. + +[TM4_E] +~w~Pensano di essere sotto la protezione della Triade, per cui si meritano una punizione esemplare. + +[TM4_F] +~w~Prendi questi ragazzi e fai fuori i signori della Triade! + +[TM4_G] +~w~E se ne hai il tempo, elimina anche qualcuno dei loro scagnozzi. + +[TM4_GAT] +~w~Avrai bisogno di un 'furgone del pesce della Triade' per riuscire a entrare. + +[TM5_A] +TESTO NON PIÙ NECESSARIO + +[TM5_B] +~w~Basta, ne ho avuto abbastanza! + +[TM5_C] +~w~Elimineremo una volta per tutte la Triade da Liberty! + +[TM5_D] +8-Ball ha messo una bomba in un camion della nettezza urbana. + +[TM5_E] +~w~La bomba è collegata a un timer, per cui se fai errori non resteranno prove. Vai a prendere il camion. + +[TM5_F] +~w~Fai attenzione: 8-Ball ha detto che il sistema è molto sensibile e potrebbe esplodere se prendi un colpo. + +[TM5_G] +~w~Il loro stabilimento aprirà i cancelli al camion. + +[TM5_H] +~w~Parcheggia tra i due serbatoi del gas e allontanati in fretta! + +[TM5_I] +~w~Voglio che piovano sgombri. + +[TM5_J] +~w~Stiamo facendo le cose in grande, basta con gli scherzi. + +[FM2] +'TAGLIARE L'ERBA' + +[FM4] +'ULTIME RICHIESTE' + +[FM1_A] +~w~Io e i ragazzi dobbiamo parlare di lavoro, + +[FM1_B] +~w~per cui dovrai occuparti della mia ragazza questa sera. + +[FM1_C] +~w~EHI MARIA! MUOVI IL CULO! + +[FM1_D] +~w~Quella vacca fa sempre così. + +[FM1_E] +~w~Ed eccola qua, la vera e unica regina di Sheba! + +[FM1_F] +~w~Cosa stavi facendo di sopra? + +[FM1_G] +~w~Qualsiasi cosa fosse, sono certo che mi è costato molto. + +[FM1_H] +~w~Beh, credevo non mi volessi attorno quando parli di lavoro, vero? + +[FM1_I] +~w~Entra in macchina e tieni chiusa la boccaccia. + +[FM1_J] +~w~Prendi la limousine, ma riportala indietro come nuova, capito? + +[FM1_K] +~w~E fai attenzione alla ragazza, può causarti dei problemi. + +[FM1_L] +~w~Tranquillo, sono certa che il tuo nuovo cagnolino sa cosa deve fare: + +[FM1_M] +~w~non è grande e grosso a sufficienza? + +[FM1_N] +~w~Ehi, Fido: andiamo a far visita a Chico e prendiamo della roba per divertirci. + +[FM1_P] +~g~Ecco, quello è Chico. Fermati vicino a lui. + +[FM1_S] +~w~Buongiorno signora. + +[FM1_TT] +~w~È UN RETATA DELLA POLIZZIA! + +[FM1_1] +~g~Torna nella limousine! + +[FM1_2] +~g~Rientra nella limousine! + +[FM1_3] +~r~Se abbandoni Maria, Salvatore ti farà ammazzare! Torna indietro a prenderla. + +[FM1_4] +~g~Hai scaricato la donna del Don. Torna al magazzino e aspetta Maria! + +[FM1_5] +~g~Riporta Maria sana e salva a Salvatore! + +[FM1_6] +~g~Chico non aspetterà per sempre. Porta Maria al porto. + +[FM1_7] +~r~Maria è morta! Salvatore non ne sarà entusiasta... + +[FM1_8] +~r~Hai fatto fuori lo spacciatore di Maria! + +[FM2_J] +Lasciaci da soli per un minuto. + +[FM2_A] +Il Cartello Colombiano sta producendo SPANK da qualche parte a Liberty. + +[FM2_K] +Non sappiamo però dove, e loro sembrano conoscere ogni nostra mossa in anticipo. + +[FM2_L] +C'è un tipo chiamato Ricciolino Bob che lavora al bar di Luigi. + +[FM2_M] +Sta spendendo molti più soldi di quanti ne guadagna. + +[FM2_N] +Normalmente prende un taxi per tornare a casa. Voglio che tu lo segua. + +[FM2_O] +E se ci sta vendendo... fallo fuori! + +[FM2_F] +Ecco il nostro caro amico: mister bocca larga. + +[FM2_G] +Sei stato seguito? Lo sai che questo deve restare un nostro segreto. + +[FM2_H] +No, no... nessuno mi ha seguito. Hai la mia roba? + +[FM2_I] +Ecco il tuo SPANK, e adesso parla! + +[FM2_P] +OK, i Leone stanno combattendo su due fronti. + +[FM2_Q] +Sono ai ferri corti con la Triade e sembra che nessuno dei due sia intenzionato a cedere. + +[FM2_R] +Contemporaneamente, Joey Leone si è fatto dei nemici tra i Forelli. + +[FM2_S] +Ogni giorno perdono uomini e influenza sulla città. + +[FM2_T] +Salvatore sta diventando pericoloso e paranoico. Sospetta di tutto e di tutti. + +[FM2_U] +Con persone fedeli come te, non capisco di cosa si preoccupi. + +[FM2_1] +~g~Ecco Ricciolino Bob! + +[FM2_2] +~g~Ricciolino ha lasciato il club: seguilo! + +[FM2_5] +~g~Andiamo al porto di Portland. + +[FM2_6] +~r~Ricciolino non salirà su un taxi distrutto! + +[FM2_7] +~r~Ricciolino è spaventato! L'appuntamento è saltato! + +[FM2_8] +~g~Elimina Ricciolino Bob! + +[FM2_9] +~r~Ricciolino Bob è morto! + +[FM2_10] +~r~Ricciolino è scappato! + +[FM2_11] +~g~Parcheggia di fronte al club Luigi's: Ricciolino Bob uscirà fra poco. + +[FM2_12] +~r~Ti ha seminato! + +[FM3_A] +~w~Dovremmo eliminare i fottuti Colombiani, + +[FM3_B] +~w~ma siamo già in guerra con la Triade e non siamo abbastanza forti. + +[FM3_C] +~w~Il Cartello ha fondi infiniti grazie allo smercio dello SPANK. + +[FM3_D] +~w~Se li attacchiamo apertamente, ci spazzeranno via come foglie secche. + +[FM3_E] +~w~Probalbimente producono lo SPANK sull'imbarcazione dove si è diretto Ricciolino. + +[FM3_F] +~w~Per cui dovremo usare la testa... meglio ancora la tua testa. + +[FM3_G] +~w~Ti chiedo di distruggere la fabbrica di SPANK come favore personale a me, Salvatore Leone. + +[FM3_H] +~w~Se ci riuscirai, sarai una persona felice: potrai avere tutto ciò che vuoi. + +[FM3_I] +~w~Vai a trovare 8-Ball: avrai bisogno della sua esperienza per far saltare l'imbarcazione. + +[FM3_8A] +~w~Ehi amico, Salvatore ha appena chiamato: + +[FM3_8B] +~w~per un lavoro come questo avrai bisogno di una bella potenza di fuoco. + +[FM3_8D] +~w~ma tu sai che sono soldi spesi bene. + +[FM3_8E] +~w~OK, procediamo allora! + +[FM3_8F] +~w~Posso impostare questo bambino e farlo esplodere, ma non posso sparare con queste mani. + +[FM3_8G] +~w~Prendi questo fucile e fai saltare un po' di teste! + +[FM3_4] +~g~Ferma il veicolo e fai scendere 8-Ball! + +[FM3_7] +~r~8-Ball è stato freddato! + +[FM3_8] +~r~Le guardie sono state messe in guardia! + +[FM4_A] +~w~Sei l'uomo delle pulizie che preferisco. + +[FM4_B] +~w~Sono fiero di te, hai saputo prendere a calci quei maledetti! + +[FM4_C] +~w~Ho un ultimo lavoro per te prima di celebrare. + +[FM4_D] +~w~C'è una macchina vicino al club Luigi's. + +[FM4_E] +~w~All'interno è schizzato cervello dappertutto. + +[FM4_F] +~w~Abbiamo dovuto far ragionare un tipo e la cosa si è rilevata un po', ehm, sporca. + +[FM4_H] +~w~Portala al rottamatore prima che la trovino i poliziotti. + +[AM3] +'STRONCA I PAPARAZZI' + +[AM4] +'GIORNO DI PAGA PER RAY' + +[AM5] +'DOPPIA FACCIA' + +[AM1_A] +Ci sono alcuni punti da chiarire prima di procedere con la nostra relazione di lavoro. + +[AM1_B] +Direi che è il caso di mostrare le carte. + +[AM1_C] +Io sono della Yakuza e so che hai lavorato per la famiglia di Salvatore Leone. + +[AM1_D] +La mia organizzazione può darti lavoro, + +[AM1_E] +ma prima devi dimostrare che i tuoi legami con la Mafia sono definitivamente chiusi. + +[AM1_G] +Assicurati che non raggiunga vivo il suo club. + +[AM1_H] +Nel frattempo io e Maria discuteremo dei tempi passati. + +[AM1_I] +Oh... Asuka, hai un massaggiatore. + +[AM1_J] +Non è un massaggiatore. + +[AM1_1] +~g~Salvatore sta lasciando adesso il club Luigi's! + +[AM1_2] +~r~Sei stato avvistato! + +[AM1_3] +~r~Hai mancato Salvatore! + +[AM1_4] +~r~Ma bravo, hai spaventato il bersaglio! E tu dovresti essere un professionista? + +[AM1_5] +~g~Raggiungi il distretto a luci rosse e aspetta che Salvatore esca dal club. + +[AM1_7] +~r~Salvatore è arrivato a casa e si sta bevendo un cocktail. Ma non ti chiamavano lo sciacallo? + +[AM1_8] +~g~Salvatore uscirà dal club Luigi's attorno alle ~1~:~1~ + +[AM2_4] +~g~Sei più appariscente di un elefante fluorescente! + +[AM3_A] +Un reporter ci è ronzato troppo attorno. + +[AM3_B] +Io e Maria ci prenderemo un po' di vacanza fino a quando non ti sarai liberato di questo guardone. + +[AM4_A] +Il mio truffatore preferito! + +[AM4_B] +Maria è molto presa al momento: le dirò che sei passato. + +[AM4_C] +Chi è? Asuka? Lo so, sono stata una bambina cattiva, ma devo andare in bagno! OK? + +[AM4_D] +È giunta l'ora che incontri il nostro uomo nella polizia. + +[AM4_E] +Ecco la ricompensa per l'ultimo lavoro che ha svolto per noi. + +[AM4_F] +È un tipo molto cauto. + +[AM4_G] +Raggiungi in fretta il telefono pubblico a Torrington e aspetta le istruzioni. + +[AM5_A] +Maria e io siamo andati a fare spese. + +[AM5_B] +La nostra fonte alla polizia ci ha informato che uno dei nostri autisti è un maledetto infiltrato! + +[AM5_C] +Fuori dalla sua macchina è particolarmente vulnerabile: gli abbiamo posizionato addosso un tracciante. + +[AM5_D] +Fallo sanguinare! + +[AM5_1] +Ben fatto! + +[AS1] +'ESCA' + +[AS2] +'ESPRESSO E VIA!' + +[AS4] +'RISCATTO' + +[AS1_A] +~w~Miguel pensa non lo stia trattando correttamente. + +[AS1_B] +~w~Ciò nonostante, mi ha rilevato quanta paura ha Catalina di una tua possibile vendetta. + +[AS2_A] +~w~Abbiamo sottovalutato i piani di Catalina per lo SPANK. + +[AS2_B] +~w~È ben più avanti dei Yardie nelle vendite per strada. + +[AS2_D] +~w~Sembra spaccino lo SPANK attraverso i chioschi per strada. + +[AS2_1] +~g~Distrutti tutti i chioschi a Portland!!! + +[AS2_2] +~g~Distrutti tutti i chioschi a Staunton Island!!! + +[AS2_3] +~g~Distrutti tutti i chioschi a Shoreside Vale!!! + +[AS2_4] +~r~Il Cartello ha avvertito i loro spacciatori! + +[AS2_5] +~g~Ci sono ancora dei chioschi a Shoreside Vale e a Staunton Island! + +[AS2_6] +~g~Ci sono ancora dei chioschi a Shoreside Vale! + +[AS2_7] +~g~Ci sono ancora dei chioschi a Staunton Island! + +[AS2_8] +~g~Ci sono ancora dei chioschi a Portland! + +[AS2_9] +~g~Ci sono ancora dei chioschi a Portland e a Shoreside Vale! + +[AS2_10] +~g~Ci sono ancora dei chioschi a Portland e a Staunton Island! + +[AS2_12] +~g~Gira per Liberty alla ricerca dei ~b~chioschi~g~! + +[AS3_A] +~w~Dovremmo stringerlo ancora un po' o aspettare che diventi nero e cada? + +[AS3_B] +~w~Diamogli uno sprone... + +[AS3_D] +~w~Caro il mio truffatore! + +[AS3_E] +~w~Mi stavo annoiando, così sono venuto a fare compagnia a Asuka. + +[AS3_1] +~g~Trova una ~r~barca~g~ e raggiungi la ~b~boa segnalatrice~g~! + +[AS3_3] +~g~Attendi fino a quando l'~y~aeroplano~g~ non inizia l'atterraggio! + +[AS3_5] +~g~Raccogli il carico! + +[AS3_4] +~g~Usa un lanciamissili per abbattere l'~g~aeroplano~g~!!! + +[AS3_2] +~b~Raggiungi la boa di segnalazione della pista! ~y~L'aereo sta per atterrare! + +[AS3_6] +~g~~1~ SU 8 + +[KM1] +'LA FUGA DI KANBU' + +[KM3] +'STRONCA IL PATTO' + +[KM4] +'SHIMA' + +[KM5] +'VENDETTA' + +[KM1_A] +Mia sorella mi ha parlato bene di te, + +[KM1_E] +anche se credo ancora che voi gaijin siete tutti quanti deludenti. + +[KM1_B] +Forse mi potresti aiutare a risolvere una situazione che si sta rilevando svantaggiosa. + +[KM1_F] +Chiaramente il fallimento ha le sue conseguenze. + +[KM1_C] +Un Kambu della Yakuza è tenuto in custodia per essere processato. + +[KM1_G] +È un elemento importante della famiglia. + +[KM1_H] +Liberalo e portalo al dojo a Bedford Point. + +[KM1_D] +Grazie per la tua azione altruista. Se avrai mai bisogno di aiuto, il dojo sarà onorato di fornirti due uomini pronti ad affiancarti. + +[KM1_1] +~g~Ruba una macchina della polizia! + +[KM1_2] +Installa una bomba sulla macchina! + +[KM1_3] +~g~Raggiungi il dojo della Yakuza. + +[KM1_5] +~g~Adesso raggiungi la stazione di polizia. + +[KM1_6] +Installa una bomba sulla macchina! + +[KM1_7] +~g~Solo mezzi della polizia autorizzati! + +[KM1_9] +~r~Non hai utilizzato un'auto esplosiva per distruggere il muro! + +[KM1_10] +~r~Il Kandu della Yakuza è morto - insieme al tuo onore! + +[KM1_11] +~r~Hai attirato la sventura su di te! + +[KM2_A] +Non è consigliabile sottovalutare l'importanza dell'etichetta in questo ambiente. + +[KM2_B] +Un uomo mi ha fatto una volta un favore e io non ho avuto l'opportunità di ricambiare la sua gentilezza. + +[KM2_C] +La persona in questione ha la passione delle automobili e mi ha richiesto alcuni modelli per la sua collezione. + +[KM2_F] +Il mio onore lo richiede. + +[KM2_2] +~g~Macchine consegnate. + +[KM3_A] +Quando i problemi insorgono, lo stolto mostra la schiena mentre il saggio si prepara ad affrontarli. + +[KM3_B] +Il Cartello Colombiano ha ignorato ripetutamente le nostre richieste di non interferire con i nostri interessi a Liberty. + +[KM3_C] +Adesso stanno negoziando con i Giamaicani per nel tentativo di umiliarci ulteriormente. + +[KM3_D] +Stanno completando un accordo in città. + +[KM3_F] +Prendi uno dei miei uomini, ruba una macchina degli Yardie e vai a porgere i miei saluti ai Colombiani. + +[KM3_E] +Il nostro onore richiede che nessuno di loro sopravviva. + +[KM3_2] +~g~Vai a prendere il tuo contatto. + +[KM3_3] +~g~L'incontro si svolge nel parcheggio dell'ospedale a Rockford! + +[KM3_4] +~r~Sono fuggiti! + +[KM3_6] +~g~Uccidili, uccidili tutti quanti! + +[KM3_8] +~g~Hai bisogno di una macchia degli Yardie per completare questa missione! + +[KM3_9] +~r~Uno dei Colombiani è morto: l'accordo è saltato. + +[KM3_10] +~r~Il contatto è morto! + +[KM4_A] +Per essere veramente forte, non devi mai mostrare debolezze. + +[KM4_C] +Vai e raccogli immediatamente i soldi per farli riciclare dal Casinò. + +[KM4_1] +Non ti posso pagare e non lo fare neanche se potessi! + +[KM4_9] +Alcuni delinquenti da strapazzo mi hanno appena rapinato! Hanno preso tutto quanto! + +[KM4_2] +La vostra protezione è nulla. + +[KM4_10] +E tu saresti della Yakuza? Con quella faccia? + +[KM4_3] +Non è per questo che vi pago; se volevo essere protetto così bene mi affidavo alla polizia. + +[KM4_4] +~g~Punisci la gang responsabile del furto dei ~b~soldi~g~! + +[KM4_7] +~r~Il negoziante ha respirato per l'ultima volta! + +[KM4_5] +Donald Love vorrebbe che tu passassi a trovarlo nel suo giardino. + +[KM4_6] +Ci sono soldi dappertutto! + +[KM4_8] +~g~Valigetta raccolta! + +[KM5_A] +TU! Che piacevole coincidenza vedere il tuo sporco muso proprio adesso! + +[KM5_B] +Sembrerebbe che i tuoi vani tentativi di dissuadere i Giamaicani + +[KM5_B1] +a collaborare con il Cartello siano miseramente falliti! + +[KM5_C] +Gli spacciatori Yardie stanno vendendo lo SPANK per ogni strada come se si trattasse di hotdog! + +[KM5_D] +I maiali del Cartello stanno ridendoci dietro! + +[KM5_E] +Ti darò un'ultima possibilità per provarmi che la fiducia a te data da mia sorella non era del tutto infondata. + +[KM5_F] +Abbatti tutti i nostri avversari e lava il tuo onore nel loro sangue!!! + +[KM5_3] +~r~Hai fallito nell'eliminare almeno ~1~ Yardie. + +[KM5_4] +~g~Congratulazioni, hai eliminato ~1~ Yardie. + +[KM5_5] +~g~Congratulazioni, hai eliminato ~1~ Yardie. BONUS ~1~$ + +[RM1] +'FAI TACERE L'UCCELLINO' + +[RM3] +'ELIMINA LE PROVE' + +[RM4] +'FUORI A PESCARE' + +[RM5] +'STRONCA L'INFILTRATO' + +[RM1_D] +Si trova sotto protezione in un palazzo della sicurezza testimoni. Il suo appartamento è dietro al parcheggio. + +[RM1_E] +Dai fuoco al palazzo per farlo uscire allo scoperto e fai in modo che non parli mai più. + +[RM1_1] +~g~Controlla l'appartamento della sicurezza testimoni. + +[RM1_2] +~g~Elimina McAffrey! + +[RM2_A1] +Ehi ragazzo, da questa parte! + +[RM2_A] +Un mio vecchio amico ha uno spaccio d'armi a Rockford. + +[RM2_D] +Ha bisogno di una mano ed è pronto a farti uno scontro sui migliori armamenti in circolazione. + +[RM2_E] +Ray ha appena chiamato... non credevo però che saresti venuto da solo. + +[RM2_F] +Bene, tre pistole sono meglio di una: prendete ciò che vi serve. + +[RM2_G] +~g~Vai a parlare con Phil! + +[RM2_H] +~r~Phil è stato ucciso!!! + +[RM2_L] +Ehi! Se avessi avuto te come compagno in Nicaragua, probabilmente avrei ancora il braccio. + +[RM2_N] +Lascia qua il bottino e vattene: mi occuperò io dei poliziotti. + +[RM3_D] +Le prove sono trasportate da un veicolo attraverso la città. + +[RM3_E] +Dovrai speronare quella macchina e raccogliere ogni prova che lascerà cadere. + +[RM3_F] +Quando le avrai raccolte tutte, lasciale in macchina e dalle fuoco. + +[RM3_G] +Sono sicuro che non avrai problemi a gestire la faccenda. + +[RM3_1] +~g~Lascia le prove in macchina e dalle fuoco. + +[RM3_4] +~g~L'accusa ha fatto cadere delle prove! + +[RM3_6] +~r~Le foro saranno sparse per tutta Liberty! + +[RM3_7] +~g~Adesso dai fuco alla macchina! + +[RM4_A] +Inizio a pensare che il mio socio sia una talpa. + +[RM4_C] +Esce fuori a pescare sulla sua imbarcazione quasi tutte le notti vicino al faro di Portland. + +[RM4_D] +Ruba una lancia della polizia e manda a fondo i suoi piani doppiogiochisti! + +[RM4_1] +~g~Ruba una lancia della polizia. + +[RM4_2] +~g~Raggiungi il faro e affonda il socio di Ray! + +[RM5_A] +Fottuto inutile! + +[RM5_A1] +Hai combinato un vero casino! Sono con il culo scoperto perché non sei neanche capace di ammazzare una mosca. + +[RM5_B] +Ti ho pagato profumatamente per eliminare il testimone ed è ancora vivo! + +[RM5_B1] +E oggi deve fare una deposizione ai federali! + +[RM5_C] +Sta per essere trasferito dall'ospedale Carson fino a Rockford. + +[RM5_D] +Se canta sono fregato... + +[RM5_E] +per cui vai ed esegui il lavoro per cui sei stato pagato! + +[RM5_1] +~g~Intercetta l'ambulanza. + +[RM5_2] +~g~Sei stato individuato!!! + +[RM5_3] +~g~Era un diversivo! + +[RM5_4] +~g~I proiettili non scalfiranno la blindatura!!! + +[RM5_5] +~g~La blindatura è ignifuga!!! + +[RM5_7] +~r~Il testimone è arrivato a destinazione!!! + +[RM5_8] +~g~Il testimone è affogato!!! + +[LOVE2] +'SPAZZA VIA WAKA-GASHIRA' + +[LOVE3] +'UNA GOCCIA NELL'OCEANO' + +[LOVE1_A] +Prima di tutto grazie per aver risolto il problema personale... + +[LOVE1_F] +La gente crede a tutto in questi giorni. + +[LOVE1_D] +Stanno cercando di estorcermi altri fondi, ma a me non piace chi ritratta all'ultimo minuto. + +[LOVE1_E] +Un patto è un patto, per cui non gli sgancerò neppure un altro penny. + +[LOVE1_G] +Vai a salvare il mio amico: fai tutto ciò che si rivelerà necessario. + +[LOVE1_2] +~g~Salva il vecchietto orientale. + +[LOVE1_3] +~g~Riporta il vecchietto orientale all'edificio di Donald Love. + +[LOVE1_4] +~g~Il vecchietto orientale deve trovarsi in uno dei garage... + +[LOVE1_6] +~r~Le interiora del vecchietto orientale sono sparse per tutta la strada! + +[LOVE1_7] +~g~Il cancello verrà aperto solo per un'auto dei Colombiani. + +[LOVE2_A] +Niente abbassa i prezzi degli immobili quanto una bella guerra fra gang... + +[LOVE2_B] +tranne forse un'epidemia di peste... ma potrebbe non essere la soluzione migliore in questo caso. + +[LOVE2_C] +È evidente chi la Yakuza e i Colombiani non sono amici per la pelle. + +[LOVE2_D] +Perché non investire in questa opportunità? + +[LOVE2_E] +Voglio che tu uccida il Waka-Gashira della Yakuza, Kenji Kasen. + +[LOVE2_F] +Kenji partecipa a un incontro al parcheggio all'ultimo piano di un palazzo a Newport. + +[LOVE2_G] +Recupera una macchina del Cartello ed eliminalo! + +[LOVE2_H] +La Yakuza deve dare la colpa al Cartello per questa dichiarazione di guerra. + +[LOVE2_1] +~g~Raggiungi Fort Staunton e ruba una macchina dei Colombiani! + +[LOVE2_2] +~g~Raggiungi il ~p~palazzo a Newport~g~ ed elimina Kenji! + +[LOVE2_3] +~r~Se procedi con una macchina non del Cartello, verrai identificato!!! + +[LOVE2_4] +~r~Gli uomini della Yakuza ti hanno identificato!!! + +[LOVE2_6] +~r~Hai ucciso tutti i testimoni!!! + +[LOVE3_A] +In questi giorni di ipocrisia globale, alcune piacevoli comodità potrebbero essere difficili da importare. + +[LOVE3_C] +Farò depositare alcuni pacchi in mare. + +[LOVE3_D] +Assicurati di raccoglierli prima che qualcun altro lo faccia. + +[LOVE3_1] +~g~Recupera un'~r~imbarcazione~g~ e insegui l'~y~aeroplano~g~! + +[LOVE4] +'GRAND THEFT AERO' + +[LOVE5] +'SERVIZIO DI SCORTA' + +[LOVE4_A] +Grazie per aver recuperato i pacchi, ma si trattava solo di un diversivo. + +[LOVE4_B] +Mi dispiace, ma sono gli inconvenienti del mestiere. + +[LOVE4_C] +Il mio vero obiettivo era nascosto all'interno dell'aereo. + +[LOVE4_F] +Ho corrotto gli ufficiali. + +[LOVE4_1] +~r~Il Cartello Colombiano è in zona!!! + +[LOVE4_2] +~g~Il pacco è scomparso! Scova i Colombiani e recuperalo! + +[LOVE4_3] +~g~Panlantic Construction...? + +[LOVE4_5] +~g~Il pacco dovrebbe essere a bordo dell'aereo... + +[LOVE4_6] +~g~Prendi l'ascensore fino alla cima della torre! + +[LOVE5_B] +Il mio amico orientale ha bisogno di una scorta mentre porta la mia ultima acquisizione a far autenticare. + +[LOVE5_1] +~g~Procedi! + +[LOVE5_2] +~g~Avrai bisogno di una macchina! + +[LOVE5_3] +~g~Vai avanti a esplorare l'uscita dal sottopassaggio! + +[LOVE5_4] +~r~Proteggi il camion! + +[RM6] +'RICERCATO' + +[RM6_A] +Non sei stato seguito? Bene. + +[RM6_B] +Adesso basta, sono nei guai fino alla gola e sto iniziando a sprofondare! + +[RM6_D] +Sono ricercato, me la batto. + +[RM6_E] +Portami al mio volo all'aeroporto e non te ne pentirai! + +[RM6_666] +Prenditi cura della mia Patriot antiproiettile. Ci vediamo a Miami! Ray + +[CAT1] +'RISCATTO' + +[CAT2] +'LO SCAMBIO' + +[CAT1_A] +Ho la tua cara Maria. Non credo tu voglia che la sua faccia finisca come una polpetta per cani, vero? + +[CAT2_F] +Mi sono rotta un'unghia e i miei capelli sono un disastro. Ma ci puoi credere? Mi è costato 50 dollari! + +[CAT2_G] +Ero così spaventata, ma mi sono detta: adesso sei una signorina. + +[CAT2_H] +Oh, ci divertiremo un mondo! Mia sorella mi ha detto che verrà a stare con me insieme ai suoi due figli + +[CAT2_I] +poiché suo marito continua a tradirla a ogni occasione... + +[CAT1_C] +XXXX + +[CAT1_D] +XXXX + +[CAT1_E] +XXXX + +[CAT1_F] +Raggiungi Catalina prima dello scadere del tempo! + +[CAT1_G] +XXXX + +[CAT1_H] +XXXX + +[CAT1_I] +XXXX + +[CAT1_J] +XXXX + +[CAT1_K] +XXXX + +[CAT1_L] +XXXX + +[AS4_1] +XXXX + +[CAT_MON] +~g~Non hai abbastanza soldi. Ti servono 500.000$. + +[BITCH_D] +~g~Maria è morta! + +[WEATHER] +TEMPO AVVERSO + +[WEATHE2] +TEMPO NORMALE + +[8001] +Hai fallito miseramente!!! + +[1000] +SEI MORTO + +[1001] +SEI MORTO + +[1002] +SEI MORTO + +[1003] +SEI MORTO + +[1004] +SEI MORTO + +[1005] +SEI STATO CATTURATO + +[1006] +SEI STATO CATTURATO + +[1007] +SEI STATO CATTURATO + +[1008] +SEI STATO CATTURATO + +[1009] +SEI STATO CATTURATO + +[GA_4] +Le bombe per le macchine costano 1000$ + +[GA_5] +La tua macchina ha già una bomba installata. + +[GA_6] { re3 change } +Parcheggiala, attivala premendo il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ e DATTELA A GAMBE! + +[GA_7] { re3 change } +Arma la bomba con il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~: esploderà non appena qualcuno cercherà di avviarla. + +[GA_8] +Usa il detonatore per attivare la bomba. + +[GA_9] +Hai raccolto ~1~ su 10 macchine speciali + +[GA_10] +Non male. Eccoti ~1~$ + +[GA_11] +Ne abbiamo già una. A noi non serve! + +[GA_12] +Bomba innescata + +[GA_13] +Un furto da manuale. Completa la lista e ci sarà un bonus per te. + +[GA_14] +Hai portato tutte le macchine. BENE! Eccoti un riconoscimento... + +[GA_15] +Spero che ti piaccia il nuovo colore. + +[GA_16] +La riverniciatura non è necessaria. + +[GA_19] +Non siamo interessati a questo modello. + +[GA_20] +Di questo modello ne abbiamo in abbondanza. Mi dispiace, non siamo interessati. + +[CR_1] +La gru non può alzare questo veicolo. + +[PU_MONY] +Non hai abbastanza soldi. + +[CO_ALL] +Li hai presi tutti. Eccoti un piccolo extra... + +[PAUSED] +PAUSA + +[HEALTH1] +Via di qui! Sei in perfetta forma. + +[HEALTH2] +L'assistenza medica costa. + +[HEALTH3] +Ti rimetterò in sesto. + +[HEALTH4] +Fanno 250$. + +[FEB_STA] +Statistiche + +[FEB_BRI] +Briefing + +[FEB_CON] +Comandi + +[FEB_AUD] +Audio + +[FEB_DIS] +Video + +[FEB_LAN] +Lingua + +[FEP_STA] +STATISTICHE + +[FEP_BRI] +BRIEFING + +[FEP_CON] +COMANDI + +[FEP_AUD] +AUDIO + +[FEP_DIS] +VIDEO + +[FEP_LAN] +LINGUA + +[FEF_ST1] +Chi è il vero cattivo? + +[FEF_ST2] +Che casino hai scatenato? + +[FEF_BR1] +Hai perso il filo? + +[FEF_CO1] +Hai bisogno di altri controlli? + +[FEF_CO2] +Scegli la configurazione più adatta al tuo stile di gioco + +[FEF_SA1] +Pensa alla tua posizione, salvala! + +[FEF_SA2] +Salva e carica le partite + +[FEF_AU1] +Pompa il volume! + +[FEF_AU2] +Scegli la stazione radio e gli effetti + +[FEF_DI1] +Cambia il gioco! + +[FEF_DI2] +Personalizza il gioco per la tua TV + +[FEF_LA1] +What you talking about willis? Che cavolo stai dicendo Willis? + +[FEF_LA2] +Scegli il tuo gergo preferito + +[FEB_PMB] +Brifing di missione precedenti: + +[FEC_NA] +NA + +[FEC_CWL] +Scorri armi a sinistra + +[FEC_CWR] +Scorri armi a destra + +[FEC_LOF] +Guarda avanti + +[FEC_TAR] +Bersaglio + +[FEC_MOV] +Movimento + +[FEC_CAM] +Modalità telecamera + +[FEC_PAU] +Pausa + +[FEC_ENV] +Sali sul veicolo + +[FEC_JUM] +Salta + +[FEC_ATT] +Attacca o fuoco con arma + +[FEC_RUN] +Corri + +[FEC_FPC] +Visuale in prima persona + +[FEC_LL] +Guarda a sinistra + +[FEC_LB1] +Guarda + +[FEC_LB2] +indietro + +[FEC_LB] +Guarda indietro + +[FEC_LR] +Guarda a destra + +[FEC_HOR] +Clacson + +[FEC_VES] +Comandi veicolo + +[FEC_RSC] +Cambia stazione radio + +[FEC_BRA] +Freno o retromarcia + +[FEC_HAB] +Freno a mano + +[FEC_CAW] +Arma vettura + +[FEC_ACC] +Accelera + +[FEC_SMT] +Attivatore missione speciale + +[FEA_OUT] +Uscita: + +[FEA_ST] +Stereo + +[FEA_MNO] +Mono + +[FEA_NON] +Nessuna + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX 109 + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Debug Streaming Requests + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Memory Card Menu + +[FEM_RMC] +Registra MEMORY CARD uno + +[FEM_TFM] +Test Format MemCard One + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_CRD] +Create Root Dir + +[FEM_CLI] +Crea e carica icone + +[FEM_FFF] +Riempi il primo file con fuffa + +[FEM_SOG] +Salva solo la partita + +[FEM_CES] +Verifica ogni salvataggio 0kB4 + +[FEM_STG] +Salva la partita + +[FEM_STS] +Salva la partita con il nome GTA3 + +[FEM_CPD] +Crea una cartella mag protetta + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_TS] +Prova salvataggio: + +[FEM_TL] +Prova caricamento: + +[FEM_TD] +Prova eliminazione: + +[PL_STAT] +Statistiche giocatore + +[PE_WAST] +Persone massacrate + +[PE_WSOT] +Persone massacrate da altri + +[CAR_EXP] +Macchine esplose + +[TM_BUST] +Tempi battuti + +[M_WASTE] +Uomini civili massacrati + +[F_WASTE] +Donne civili massacrate + +[PIG_WST] +Poliziotti massacrati + +[GNG_WST] +Membri delle gang massacrati + +[MED_WST] +Medici massacrati + +[FIRE_WS] +Pompieri massacrati + +[DED_CRI] +Criminali massacrati + +[DED_DED] +Scrocconi massacrati + +[DED_HOK] +Prostitute massacrate + +[HEL_DST] +Elicotteri distrutti + +[PER_COM] +Percentuale completata + +[KGS_EXP] +Kg di esplosivi utilizzati + +[ACCURA] +Accuratezza + +[ELBURRO] +Miglior tempo di corsa in sec + +[CAR_CRU] +Macchine distrutte + +[HED_EX] +Teste esplose + +[TM_DED] +Visite in ospedale + +[DAYSPS] +Giorni trascorsi nel gioco + +[MMRAIN] +Pioggia caduta in mm + +[MXCARD] +Distanza max salto FOLLE (ft) + +[MXCARJ] +Altezza max salto FOLLE (ft) + +[MXCARDM] +Distanza max salto FOLLE (m) + +[MXCARJM] +Altezza max salto FOLLE (m) + +[MXFLIP] +Numero max ribaltamenti FOLLI in aria + +[MXJUMP] +Numero max rotazioni FOLLI in aria + +[BSTSTU] +Migliore acrobazia FOLLE: + +[INSTUN] +Acrobazia folle + +[PRINST] +Acrobazia folle perfetta + +[DBINST] +Doppia acrobazia folle + +[DBPINS] +Doppia acrobazia folle perfetta + +[TRINST] +Tripla acrobazia folle + +[PRTRST] +Tripla acrobazia folle perfetta + +[QUINST] +Quadrupla acrobazia folle + +[PQUINS] +Quadrupla acrobazia folle perfetta + +[NOSTUC] +Nessuna acrobazia FOLLE effettuata + +[NOUNIF] +Acrobazie uniche effettuate + +[NOUNGM] +Acrobazie uniche complessive + +[NMISON] +Missioni provate + +[NMMISP] +Missioni superate + +[PASDRO] +Passeggeri scaricati + +[MONTAX] +Soldi guadagnati in taxi + +[DAYPLC] +Spesa giornaliera polizia + +[CRIMRA] +Livello criminalità: + +[GMSTOR] +Negozio dei giochi + +[PREBRF] +Briefing precedenti + +[CNTLS] +Comandi + +[MUSMEN] +Musica-FX + +[GAMSET] +Impostazioni + +[LANGUA] +Lingua + +[DSPLAY] +Video + +[DEBUGM] +Menu di debug + +[QUITOP] +Esci dalle opzioni + +[CONTRL] +Configurazione comandi + +[SET1EN] +Configurazione 1. Abilitata. + +[SET1] +Configurazione 1 + +[SET2EN] +Configurazione 2. Abilitata. + +[SET2] +Configurazione 2 + +[SET3EN] +Configurazione 3. Abilitata. + +[SET3] +Configurazione 3 + +[SET4EN] +Configurazione 4. Abilitata. + +[SET4] +Configurazione 4 + +[GOBACK] +Indietro + +[SOUND] +SONORO + +[MUSVOL] +Volume musica + +[SFXVOL] +Volume FX + +[SCROPT] +OPZIONI SCHERMO + +[CTRSCR] +Centra schermo + +[SCRFOR] +Formato schermo + +[GMSVLQ] +SALVA-CARICA-ESCI + +[GMREST] +Ricomincia partita + +[NOGMSV] +Puoi salvare solo al tuo nascondiglio. + +[DLFILE] +Elimina i file di Grand Theft Auto III + +[CHFILE] +SCEGLI IL FILE DA CARICARE + +[CHCDLD] +Choose Card to Load From + +[CDUNFR] +Card is unformatted. + +[CHFIDL] +SCEGLI IL FILE DA CANCELLARE + +[SVCONF] +SALVA CONFERMA + +[SVFNAM] +Il nome del tuo salvataggio è + +[SAVEDN] +Errore. Salvataggio non completato. + +[LANGSL] +SCELTA LINGUA + +[ENGLIS] +Inglese + +[GERMAN] +Tedesco + +[ITALIA] +Italiano + +[FRENCH] +Francese + +[SPAIN] +Spagnolo + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoadGroups On + +[CRGOFF] +ShowCarRoadGroups Off + +[CLZOON] +Show Cull Zones On + +[CLZOOF] +Show Cull Zones Off + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (teststuff) + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[GORLEV] +Livello violenza + +[SICASS] +Stupro + +[SICSIC] +Stupratore + +[SCASSL] +Stupro selezionato + +[SCSCSL] +Stupratore selezionato + +[PRVMEN] +Briefing di missione precedente + +[DOSVGM] +Vuoi salvare la partita? + +[FORMEN] +Format Menu + +[MEMTST] +MemoryCardTest screen + +[REGCAR] +Register MemoryCard One + +[TEFONE] +Test Format MemCard One + +[TEUFON] +Test UnFormat MemCard One + +[CRROOT] +CreaDirRoot + +[CRLDIC] +Crea e carica icone + +[FLFSGF] +Riempi il primo file con fuffa + +[PUSAVE] +Salva solo il gioco + +[CHEVOK] +VerificaOgniSalvataggioOKB4 + +[SVGMON] +SalvaIlGioco + +[CNTSAV] +Impossibile salvare il gioco, sei in missione. + +[CNCSAV] +Impossibile salvare il gioco, sei in macchina. + +[CRMGSV] +Crea cartella magazine protetta + +[MGSVCN] +Cartella Magazine creata + +[MGSVNC] +Cartella Magazine non creata + +[YES] +Sì + +[NO] +No + +[X] +x + +[LAST] +Ultimo messaggio. + +[FEDS_XB] +Selezione + +[FEDS_ST] +Tasto START - RIPRENDI + +[FEST_OO] +su + +[FEC_TUC] +Torretta di controllo + +[FEC_SM3] +Attivazione missione speciale (tasto R3) + +[FEC_RS3] +Stazioni radio (tasto L3) + +[FEC_HO3] +Clacson (tasto L3) + +[DIAB1] +'TURISMO' + +[DIAB2] +'IO GRIDO, TU GRIDI' + +[DIAB3] +'BATTESIMO DEL FUOCO' + +[DIAB4] +'GROSSO E ARTERIOSO' + +[DIAB1_A] +El Burro ti vuole offrire un'opportunità. Vai alla cabina telefonica di Hepburn Heights se vuoi saperne di più. + +[DIAB1_C] +Sei un pilota provetto. Ripassa per la cabina telefonica, 'El Burro' potrebbe avere altro lavoro per te. + +[DIAB1_1] +~g~3..2..1.. VIA VIA VIA! + +[DIAB1_4] +~g~Procurati un'auto veloce e vai alla griglia di partenza. + +[DIAB1_3] +~r~Non riusciresti a vincere neanche una lotteria, FALLITO! + +[DIAB1_2] +~g~Congratulazioni, hai vinto con l'incredibile tempo di ~1~ secondi. + +[FIRST] +~g~Primo + +[SECOND] +~g~Secondo + +[THIRD] +~g~Terzo + +[FOURTH] +~g~Quarto + +[DIAB2_1] +~g~Prendi la valigia ad Harwood. + +[DIAB2_2] +~g~Trova un furgone dei gelati. + +[DIAB2_3] +~g~Parcheggia il furgone dei gelati al molo atlantico. + +[DIAB2_4] +~g~Premi il ~w~tasto ~k~~VEHICLE_HORN~ ~g~per attivare il campanello. + +[DIAB2_6] +~g~Premi il ~w~tasto ~k~~VEHICLE_HORN~ ~g~per attivare il campanello. + +[DIAB2_7] +~g~Premi il ~w~tasto ~k~~VEHICLE_HORN~ ~g~per attivare il campanello. + +[DIAB2_5] +~g~Esci dal furgone dei gelati e usa il telecomando per farlo esplodere. + +[YD1] +'BLING-BLING SCRAMBLE' + +[YD2] +'UZI RIDER' + +[YD3] +'CORSA TRA GANGSTER' + +[YD4] +'VENGA IL TUO REGNO' + +[YD_P] +King Courtney vuole parlarti. Vai alla cabina telefonica ad Aspatria! + +[YD1_A] +~w~Sono King Courtney. + +[YD1_A1] +~w~Alla mia cricca Yardie servirebbe un buon autista e tu hai un'ottima reputazione. + +[YD1_B] +~w~Vai in macchina alla discarica dalla parte opposta dello stadio e aspetta gli altri aspiranti. + +[YD1_C] +~w~Ho uomini di guardia in postazioni sparse per tutta Staunton. + +[YD1_D] +~w~Il primo pilota che raggiunge una postazione prende $1000, e cosi via fino alla tappa successiva. + +[YD1_D1] +~w~Se raggiungi per primo più postazioni degli altri piloti, potrei avere del lavoro per te. + +[YD1_E] +~g~Preparati alla gara! + +[YD1_F] +~g~Hai anticipato la partenza; mi piace il tuo stile! + +[YD1_G] +~r~È una GARA AUTOMOBILISTICA. Ti serve un'AUTO, idiota! + +[YD1GO] +~g~VIA! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000! + +[Y1_1ST] +~g~Sei arrivato primo con ~1~ postazioni! + +[Y1_2ND] +~y~Sei arrivato secondo con ~1~ postazioni. ~y~Non male, ma non sei il migliore! + +[Y1_3RD] +~r~Sei arrivato terzo con ~1~ postazioni. ~r~Credevo avessi detto di essere in gamba! + +[Y1_LAST] +~r~Sei arrivato ultimo! ~r~Mi hai fatto perdere tempo, IDIOTA! + +[Y1_J1ST] +~y~Primo a pari merito con ~1~ postazioni. ~y~Bravo, ma devi essere il migliore per guidare per Queen Lizzy! + +[Y1_J2ND] +~r~Secondo a pari merito con ~1~ postazioni. Guidi come una scimmia impazzita! + +[Y1JLAST] +~r~Ultimo a pari merito! Sei più veloce con la lingua che con l'auto! + +[Y1_TEST] +AUTO IN ACQUA! + +[YD2_A] +~w~Devo vedere se sei in grado di eseguire certi lavoretti per me. + +[YD2_A1] +~w~Vediamo se ci si può fidare di te. + +[YD2_B] +~w~Due dei miei ragazzi saranno lì a momenti per accompagnarti in un giro: + +[YD2_B1] +~w~vedremo se sei davvero chi dici di essere. + +[YD2_C] +~w~Andiamo a fare un giro a Hepburn Heights: levaci di torno alcuni di quei luridi Diablo che mancano di rispetto a Queen Lizzy. + +[YD2_CC] +~w~Prendi, ti serviranno 'i ferri del mestiere'. + +[YD2_D] +~w~Dovrai guidare e sparare. Noi faremo in modo che tu non finisca al creatore. + +[YD2_E] +~w~Comincia a guidare! + +[YD2_F] +~r~Ci è scappato! Facciamo secco quel bastardo muso giallo!!! + +[YD2_G1] +~w~Hepburn Heights... Facciamo fuori qualche lurido Diablo... + +[YD2_G2] +~w~Ma ricorda: ~r~non pensare di uscire da quest'auto! + +[YD2_H] +~w~OK, riportaci nel territorio di Yardie! VIA VIA VIA!!! + +[YD2_L] +~w~Bel lavoro, Cecchino! + +[YD2_M] +~r~Ha distrutto la mia auto! Fallo secco! + +[YD2_N] +~w~Rimetti il culo in macchina! + +[YD3_A] +Devi rubare alcune auto delle gang avversarie + +[YD3_A1] +per poter colpire nel loro territorio. + +[YD3_B] +Mi serve una Sentinel della Mafia, + +[YD3_B1] +una Stinger della Yakuza e uno + +[YD3_B2] +Stallion dei Diablo per poter attaccare qualunque gang di Liberty. + +[YD3_C] +Parcheggiale nel garage a Newport e ricorda, + +[YD3_C1] +se le distruggi sono inutilizzabili! + +[YD3_D] +Etichetta testo aggiuntiva + +[YD3_E] +~r~Hai già rubato un'auto dei Diablo! + +[YD3_F] +~r~Hai già rubato un'auto della Mafia! + +[YD3_G] +~r~Hai già rubato un'auto della Yakuza! + +[YD3_H] +~g~Auto dei Diablo rubata! + +[YD3_I] +~g~Auto della Mafia rubata! + +[YD3_J] +~g~Auto della Yakuza rubata! + +[YD3_K] +~r~L'auto è quasi un rottame! Falla riparare! + +[YD3_L] +~g~Portala al ~p~garage! + +[YD3_M] +~r~Hai cappottato l'auto! Procuratene un'altra! + +[YD4_A] +Stammi a sentire! + +[YD4_A1] +Arriva fino a Bedford Point. + +[YD4_A2] +C'è della roba nascosta in una vecchia automobile che mi serve subito! + +[YD4_B] +LETTERA: Ho saputo che ti sei dato da fare. Beh mi sono data da fare anch'io. + +[YD4_C] +Credo sia ora che tu provi il vero potere dello 'SPANK'! Baci e abbracci, Catalina, con amore. + +[YD4_D] +PS: MUORI CANE BASTARDO, MUORI! + +[YD4_1] +Pazzi strafatti di SPANK! + +[YD4_2] +Distruggi i camion di SPANK! + +[HM_1] +'SOLDI E UZI' + +[HM_2] +'STERMINATOR' + +[HM_3] +'PRONTO A ESPLODERE' + +[HM_5] +'RISSA' + +[HOOD1_A] +Raggiungi il telefono pubblico dei giardini Wichita se vuoi parlare d'affari. + +[HM1_A] +Ehi, sono D-Ice dei Red Jack! + +[HM1_C] +Questi stronzetti scorrazzano per le strade con nient'altro che armi e SPANK nella testa. + +[HM1_3] +~g~I 'Nine' controllano il territorio dei giardini Wichita. + +[HM2_3] +Se colpisci i pneumatici, il maggiolino radiocomandato esploderà! + +[HM2_4] +Se supera la portata massima, il maggiolino radiocomandato esploderà! + +[HM2_5] +~r~Fuori dalla portata massima! + +[HM3_1] +~g~Vai al garage, ma stai attento: se l'auto viene danneggiata troppo, salterà in aria! + +[HM3_2] +~g~Riporta l'auto in ottime condizioni: nessun danno! + +[HM3_3] +~g~Fai riparare l'auto! + +[HM4_D] +~g~Prendi un veicolo! + +[HM4_E] +TESTO NON PIÙ NECESSARIO + +[HM4_1] +~g~Dirigiti nel posto dove il carico s'è rovesciato: dovrai raccogliere almeno 30 lingotti. + +[HM4_2] +~g~Ricorda di raggiungere il garage e depositare il carico quando il veicolo diventa troppo pesante e lento. + +[HM5_3] +~r~Dovevi usare solo una mazza da baseball! + +[HM5_4] +~r~Il tuo intermediario è morto! + +[MEA1] +'L'IMBROGLIONE' + +[MEA2] +'I LADRI' + +[MEA3] +'LA MOGLIE' + +[MEA4] +'L'AMANTE' + +[MEAT1_A] +Un amico mi ha detto che puoi risolvermi dei problemi. Vai alla cabina telefonica a Trenton se credi di essere la persona giusta. + +[MEA1_B3] +~g~Devi incontrare il direttore della banca. + +[MEA1_B6] +~g~Porta l'auto dal rottamatore per distruggere le prove: esci dall'auto e la gru la raccoglierà. + +[MEA1_1] +~r~Il direttore della banca è morto! + +[MEA1_2] +~r~Ti era stato detto di rottamare il veicolo! + +[MEA1_3] +~g~Esci dalla macchina! + +[MEA1_4] +~r~Ti sei dimenticato del direttore della banca! + +[MEA2_B3] +~g~Vai a incontrare i ladri. + +[MEA2_B4] +~g~Accompagnali alla fabbrica di cibo per cani Bitch'n' Dog. + +[MEA2_B6] +~g~Fai riverniciare l'auto per eliminare ogni prova. + +[MEA2_1] +~r~Ti era stato detto di far rottamare il veicolo! + +[MEA2_2] +~r~Un ladro è morto! + +[MEA2_4] +~r~Ti sei dimenticato un ladro! + +[MEA3_B3] +~g~Vai a prelevare la signora Chonks. + +[MEA3_B6] +~g~Prendi l'auto e scaricala in mare: questo cancellerà ogni prova. + +[MEA3_1] +~r~La moglie è morta! + +[MEA3_2] +~r~Dovevi scaricare il veicolo in mare! + +[MEA3_3] +~r~Ti sei dimenticato di della moglie! + +[MEA4_B3] +~g~Vai a prendere l'amante della moglie. + +[MEA4_B6] +È davvero troppo tardi per farlo, Marty. Ti ho dato una chance, ma adesso prenderò io il controllo del tuo business... + +[MEA4_1] +~r~Carlos è morto! + +[MEA4_3] +~r~Ti sei dimenticato di Carlos lo strozzino! + +[LOOK_A] +Tieni premuto il ~h~tasto ~k~~VEHICLE_LOOKLEFT~ ~w~o il ~h~tasto ~k~~VEHICLE_LOOKRIGHT~~w~ per guardare a ~h~sinistra~w~ o a ~h~destra~w~ mentre sei nel veicolo. Premi entrambi i tasti per guardare ~h~indietro~w~. + +[LOVE6_1] +~g~Adesso attira gli sbirri lontano dai magazzini! + +[LOVE6_2] +~r~Non sei riuscito ad attirare gli sbirri abbastanza lontano! + +[RM4_3] +~r~Il socio di Ray è fuggito! + +[RM6_C] +La CIA sembra interessata allo SPANK + +[RM6_C1] +e non vogliono che interferiamo con il Cartello. + +[C_PASS] +MINACCIA ELIMINATA! + +[CTUTOR] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Vigilante. + +[CTUTOR2] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Vigilante. + +[COPCART] +~g~Hai ~1~ secondi per tornare a un mezzo della polizia prima che termini la missione. + +[C_FAIL] +Missione Vigilante terminata! + +[C_CANC] +~r~Missione Vigilante annullata! + +[C_ESCP] +~r~Il sospetto è fuggito! + +[C_TIME] +~r~È scaduto il tuo tempo come tutore della legge! + +[C_VIGIL] +BONUS VIGILANTE! + +[A_FAIL2] +~r~La tua scarsa sollecitudine è stata fatale al paziente! + +[A_FAIL3] +~r~Il paziente è morto! + +[A_PASS] +Salvato! + +[F_FAIL2] +~r~Sei arrivato tardi! + +[A_COMP2] +Non ti stancherai mai! + +[RM2_M] +Se ti servono armi da fuoco, passa di qui e prendi ciò che ti serve dagli armadietti. + +[HEAL_A] +La tua ~h~salute~w~ è indicata in color arancione in alto a destra sullo schermo. + +[YD1_CNT] +~1~ su 15! + +[FM1_9] +~g~Lì davanti c'è il party: fai scendere Maria all'ingresso. + +[FM1_Y] +~w~Sai, non mi divertivo così tanto da una vita, e mi hai trattato proprio bene. Con molto rispetto. + +[FM1_AA] +~w~Oh, devo andare. Spero di rivederti. + +[NOCONTE] +Ricollega il controller analogico (DUALSHOCK#) o controller analogico (DUALSHOCK#2) all'ingresso controller 1 per continuare. + +[WRCONT] +Il controller collegato all'ingresso controller 1 non è supportato. Grand Theft Auto III richiede un controller analogico (DUALSHOCK#) o (DUALSHOCK#2). + +[WRCONTE] +Il controller collegato all'ingresso controller 1 non è supportato. Grand Theft Auto III richiede un controller analogico (DUALSHOCK#) o controller analogico (DUALSHOCK#2). + +[WRONGCD] +Disco errato. Inserisci il disco corretto. + +[NOCD] +Il vano cassetto del disco è vuoto. Inserisci il disco. + +[OPENCD] +Il vano cassetto del disco è aperto. Chiudi il vano. + +[CDERROR] +Errore di lettura del DVD Grand Theft Auto III. + +[RESTART] +Avvio di una nuova partita + +[GA_3] +Niente più sconti. $1000 per la riverniciatura! + +[GA_1] +Wow! Non tocco niente di COSÌ caldo! + +[GA_1A] +Torna quando non sarai così occupato... + +[S_PROM2] +Nel garage qua accanto puoi parcheggiare un veicolo quando salvi la partita. + +[STOCK] +scorte esaurite + +[FM1_O] +~w~Credo si trovi alla stazione vicino al porto di Chinatown. + +[EBAL_B] +Siamo arrivati, togliamoci dalla strada e cerchiamo di cambiarci d'abito! + +[EBAL_G] +Questo è il club Luigi's. Passiamo dal retro attraverso la porta di servizio. + +[AM4_3] +Tu devi essere il nuovo tuttofare di Asuka! + +[AM4_4] +Hai i soldi? Tutto qui? + +[AM4_5] +So cosa pensi, un altro sbirro corrotto. + +[AM4_6] +Beh, il mondo è corrotto. + +[AM4_7] +Solo perché ho perso dei partner, quegli idioti degli Affari Interni hanno cominciato a curiosare. + +[AM4_8] +Immagino che sentano puzza di bruciato. + +[AM4_9] +Beh, questa città è una grande fogna. + +[AM4_10] +Mi servirà aiuto anche fuori dai sindacati. + +[AM4_11] +Se sei interessato, sai dove trovarmi. + +[CAM_A] +Premi il ~h~tasto ~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ per cambiare le ~h~modalità di visuale ~w~quando sei a piedi o in un veicolo. + +[CAM_B] +Premi il ~h~tasto direzionale in alto~w~ e ~h~in basso~w~ per cambiare le ~h~modalità di visuale ~w~quando sei a piedi o in un veicolo. + +[KM2_1] +~g~Ripara l'auto, sarà come nuova. + +[LM3_6] +Joey... + +[LM3_6A] +Mi farai giocare ancora col tuo pistone? + +[LM3_9A] +Potrebbe esserci del lavoro per te. + +[LM3_9B] +OK? + +[AWAY2] +~r~Sono scappati. + +[AWAY] +~r~È scomparso nel nulla! + +[JM6_1] +Raggiungi la banca sulla strada principale. + +[GA_6B] { re3 change } +Parcheggiala, innesca la bomba schiacciando il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ e BATTITELA! + +[GA_7B] { re3 change } +Innescala col ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~. La bomba esploderà quando si tenterà di avviare il motore. + +[BAT1] +~g~Prendi la mazza! + +[EBAL_O] +Se andrà tutto liscio, forse potrei darti altro lavoro. Adesso fuori di qui! + +[HELP9_B] +Premere il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile di precisione. + +[HELP9_C] +Premere il ~h~tasto ~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile di precisione. + +[JM6_8] +~r~Hai perso tutti i rapinatori! + +[COLT_IN] +La pistola è adesso disponibile da AmmuNation! + +[TAXI2] +~r~Tempo scaduto! + +[TAXI3] +~r~Il passeggero è fuggito terrorizzato! + +[TAXI7] +~r~La tua auto è distrutta, falla riparare. + +[TAXI4] +Tariffa completa! + +[TAXI5] +BONUS VELOCITÀ! + +[TAXI6] +Missione taxi terminata + +[FRANGO] +~g~Salvatore vuole che tu aiuti prima Toni ad affrontare le Triadi! + +[PAGEB12] +Tangente per la Polizia consegnata al nascondiglio + +[PAGEB13] +Salute consegnata al nascondiglio + +[PAGEB14] +Adrenalina consegnata al nascondiglio + +[KM1_4] +~g~Ti serve un'auto della polizia per svolgere il lavoro! + +[CAT1_B] +Porta 500.000$ alla Villa a Cedar Grove. + +[JM2_C] +Ha un chiosco di spaghetti cinesi a China Town. + +[RM6_1] +Eccoti la chiave di un deposito. + +[RM6_2] +Troverai del denaro e alcune 'dotazioni' da utilizzare nei momenti difficili. + +[RM6_3] +Ci vediamo. + +[FE_INIP] +Inizializzazione e caricamento menu pausa... Un momento. + +[FESZ_CA] +Annulla + +[FESZ_QU] +Esci + +[FESZ_L1] +Partita salvata con successo! + +[FESZ_L2] +Il nome del file salvato è: + +[FESZ_OK] +OK + +[FES_LGA] +Carica partita + +[FES_NGA] +Nuova partita + +[FES_CAN] +Annulla + +[FESZ_QL] +Tutti i progressi della partita attuale non ancora salvati andranno perduti. Vuoi procedere con il caricamento? + +[FESZ_QD] +Vuoi eliminare questa partita salvata? + +[FESZ_QO] +Vuoi procedere con la sovrascrittura di questa partita salvata? + +[FESZ_QR] +Sei sicuro di voler iniziare una nuova partita? Tutti i progressi fatti fino all'ultimo salvataggio andranno perduti. Vuoi procedere? + +[FESZ_QS] +VUOI PROCEDERE CON IL SALVATAGGIO? + +[SLONFP] +Ingresso 1. File protetto. + +[T4X4_1] +'CAMPO DEL PATRIOTA' + +[T4X4_2] +'UN GIRO NEL PARCO' + +[T4X4_3] +'AFFERRATO' + +[MM_1] +'MASSACRO NEL PALAZZO' + +[T4X4_1A] +~g~Hai ~y~5 minuti~g~ per attraversare ~y~15~g~ punti di controllo. ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[T4X4_1B] +~1~ su 15! + +[T4X4_1C] +~y~ATTRAVERSA~g~ il primo punto di controllo per attivare il timer. ~g~Ogni punto di controllo ti fornirà ~y~20 SECONDI~g~ extra. + +[T4X4_2A] +~g~Hai ~y~2 minuti~g~ per attraversare ~y~12~g~ punti di controllo!!! ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[T4X4_2B] +~1~ su 12! + +[T4X4_2C] +~y~ATTRAVERSA~g~ il primo punto di controllo per attivare il timer. ~g~Ogni punto di controllo ti fornirà ~y~10 SECONDI~g~ extra. + +[T4X4_3A] +~g~Hai ~y~5 minuti~g~ per attraversare ~y~20~g~ punti di controllo!!! ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[T4X4_3B] +~y~ATTRAVERSA~g~ il primo punto di controllo per attivare il timer. ~g~Ogni punto di controllo ti fornirà ~y~10 SECONDI~g~ extra. + +[T4X4_3C] +~1~ su 20! + +[T4X4_F] +~r~Sei scappato! Troppo difficile per te? + +[MM_1_A] +~g~Hai ~y~2 minuti~g~ per attraversare ~y~20 punti di controllo~g~ nel palazzo! ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[MM_1_B] +~1~ su 20! + +[MM_1_C] +~g~Sono 20 secondi più ~y~5 secondi~g~ per ogni punto di controllo. ~g~Il timer partirà ~y~IMMEDIATAMENTE~g~. + +[FM2_14] +~r~Ti sei avvicinato troppo e hai spaventato Ricciolino! + +[FM2_15] +~g~Non ti avvicinare troppo o Ricciolino si insospettirà! + +[UPSIDE] +~r~Hai cappottato la macchina! + +[FM2_16] +PAURIMETRO: + +[LM3_11] +~g~Misty non sale su un autobus, prendi un altro veicolo! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Camion dei pompieri + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mulo + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulanza + +[FBICAR] +F.B.I. + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Polizia + +[ENFORCR] +Cellulare + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Caserma OL + +[TRAIN] +Treno + +[HELI] +Elicottero + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +Bandito RC + +[BELLYUP] +Triad + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Mafia + +[YARDICR] +Yardie + +[YAKUZCR] +Yakuza + +[DIABLCR] +Diablo + +[COLOMCR] +Cartel + +[HOODSCR] +Hoods + +[AEROPL] +Aeroplano + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Distanza percorsa a piedi (miglia) + +[FEST_DC] +Distanza percorsa in auto (miglia) + +[FESTDFM] +Distanza percorsa a piedi (m) + +[FESTDCM] +Distanza percorsa in auto (m) + +[FEST_R1] +Campo del Patriota (secondi) + +[FEST_R2] +un Giro nel Parco (secondi) + +[FEST_R3] +Afferrato! (secondi) + +[FEST_RM] +Massacro Multi-livello (secondi) + +[FEST_LS] +Persone salvate in ambulanza + +[FEST_CC] +Missione Vigilante - Criminali uccisi + +[FEST_FE] +Totale incendi spenti + +[FEST_LF] +Volo più lungo nel Dodo + +[FEST_BD] +Disinnesco bomba - Miglior tempo + +[FEST_RP] +Violenze eseguite + +[FEST_MP] +Missioni eseguite + +[FEST_BB] +Bling-bling Scramble: + +[FEST_H0] +Maggior numero di checkpoints + +[FEST_GC] +Auto delle gang distrutte: + +[FEST_H1] +Distruzione Diablo + +[FEST_H2] +Massacro Mafioso + +[FEST_H3] +Calamità al Casinò + +[FEST_H4] +Demolizioni Rumpo + +[USJI1] +TESTO NON PIÙ RICHIESTO + +[USJI2] +TESTO NON PIÙ RICHIESTO + +[USJI3] +TESTO NON PIÙ RICHIESTO + +[USJ] +BONUS PER ACROBAZIA UNICA! + +[SPRAY] +Porta l'auto dal carrozziere per perdere il ~h~livello di sospetto~w~, ~h~riparare~w~ e ~h~riverniciare~w~ il veicolo. Costo - ~h~1000$~w~. + +[HM1_1] +~g~Fredda 20 Purple Nine in 2 minuti e 30 secondi. + +[KM1_8A] { re3 change } +Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per ~h~attivare la bomba~w~: ricorda di toglierti di mezzo! + +[KM1_8D] { re3 change } +Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per ~h~attivare la bomba~w~: e ricorda di toglierti di mezzo! + +[KM1_12] +~g~Portalo nel dojo, ma prima sbarazzati degli sbirri! + +[RATNG1] +Borsaiolo + +[RATNG2] +Prepotente + +[RATNG3] +Teppista + +[RATNG4] +Attaccabrighe + +[RATNG5] +Gorilla + +[RATNG6] +Autista + +[RATNG7] +Aiutante + +[RATNG8] +Riparatore + +[RATNG9] +Socio + +[RATNG10] +Uomo delle pulizie + +[RATNG11] +Assassino + +[RATNG12] +Braccio destro + +[RATNG13] +Boia + +[RATNG14] +Capo + +[RATNG15] +Boss + +[1010] +~r~Il tuo veicolo si è ribaltato + +[1011] +~r~Il tuo veicolo si è ribaltato + +[1012] +~r~Il tuo veicolo si è ribaltato + +[1013] +~r~Il tuo veicolo si è ribaltato + +[1014] +~r~Il tuo veicolo si è ribaltato + +[JM4_10] +OK, ragazzo. Accompagnami prima alla lavanderia di Chinatown, devo occuparmi di alcuni affari. + +[JM4_11] +La donna della lavanderia non mi ha pagato i soldi del pizzo. + +[JM4_12] +E bada all'auto: Joey ha appena riparato questo rottame. + +[JM4_13] +Quindi, niente colpi di testa, OK? + +[KM4_11] +~g~Riporta i soldi al Casinò! + +[FEF_BR2] +Ritrovalo leggendo i sommari di missione che hai già ricevuto. + +[TRAIN_1] +Stazione Kurowski + +[TRAIN_2] +Stazione Rothwell + +[TRAIN_3] +Stazione Baillie + +[SUBWAY1] +Stazione Portland + +[SUBWAY2] +Stazione Rockford + +[SUBWAY3] +Stazione Sud Staunton + +[SUBWAY4] +Capolinea Shoreside + +[MEA4_2] +~r~Marty Chonks è morto! + +[SPRAY1] +Porta l'auto dal carrozziere per perdere il ~h~livello di sospetto~w~, ~h~riparare~w~ e ~h~rivernicirea~w~ il veicolo. Costo - ~h~1000$~w~. Per questa volta è gratis. + +[JM4_A] +Sì, lo so Toni, l'ho sistemata per bene. Il motore fa le fusa, non so se mi spiego. + +[JM4_5] +Ripassa più tardi e darò loro qualcosa da lavare... i panni macchiati del loro sangue! + +[AMMU_A] +Luigi dice che ti serve 'un ferro del mestiere'... + +[AMMU_B] +Joey mi ha detto di equipaggiarti... + +[AMMU_C] +Vai sul retro del negozio. Ti ho lasciato una calibro nove in cortile. + +[AMMU_D] +Ho tutto quello che ti serve per la tua auto-difesa. + +[AMMU_E] +Vuoi anche una licenza? + +[AMMU_F] +Non mi serve che mi mostri i documenti, hai una faccia fidata. + +[DETON] +DETONAZIONE: + +[DRIVE_A] { re3 change } +Seleziona un Uzi quando entri in un veicolo, poi guarda a destra o a sinistra e premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare. + +[DRIVE_B] { re3 change } +Seleziona un Uzi quando entri in un veicolo, poi guarda a destra o a sinistra e premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare. + +[RECORD] +~g~NUOVO RECORD!!! + +[NRECORD] +~r~NESSUN NUOVO RECORD! + +[RCHELP] { re3 change } +Premi il tasto ~k~~VEHICLE_FIREWEAPON~ o dirigi l'auto radiocomandata contro i pneumatici di un veicolo per provocarne la detonazione. + +[RCHELPA] { re3 change } +Premi il tasto ~k~~VEHICLE_FIREWEAPON~ o dirigi l'auto radiocomandata contro i pneumatici di un veicolo per provocarne la detonazione. + +[RC_1] +Hai 2 minuti per far esplodere quante più auto dei Diablo possibile! + +[RC_2] +Hai 2 minuti per far esplodere quante più auto Mafiose possibile! + +[RC_3] +Hai 2 minuti per far esplodere quante più auto della Yakuza possibile! + +[RC_4] +Hai 2 minuti per far esplodere quante più auto degli Yardie possibile! + +[RC_5] +Hai 2 minuti per far esplodere quante più auto degli Hood possibile! + +[RC_6] +Hai 2 minuti per far esplodere quante più auto del Cartello possibile! + +[RAMPAGE] +VIOLENZA!! + +[RAMP_P] +VIOLENZA COMPIUTA! + +[RAMP_F] +VIOLENZA FALLITA + +[PAGE_00] +. + +[PAGE_01] +Uccidi ~1~ Diablo in 120 secondi! + +[PAGE_02] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_03] +Uccidi ~1~ Mafiosi in 120 secondi! + +[PAGE_04] +Uccidi ~1~ elementi della Triade in 120 secondi! + +[PAGE_05] +Uccidi ~1~ elementi della Triade in 120 secondi! + +[PAGE_06] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_07] +Fai saltare la testa a ~1~ Yardie in 120 secondi! + +[PAGE_08] +Brucia ~1~ elementi della Yakuza in 120 secondi! + +[PAGE_09] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_10] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_11] +Annienta ~1~ Yardie in 120 secondi! + +[PAGE_12] +Carbonizza ~1~ elementi della Yakuza in 120 secondi! + +[PAGE_13] +Fai esplodere ~1~ Yardie in 120 secondi! + +[PAGE_14] +Fai fuori ~1~ ColombianI in 120 secondi! + +[PAGE_15] +Polverizza ~1~ Hood in 120 secondi! + +[PAGE_16] +Distruggi ~1~ veicoli in 120 secondi! + +[PAGE_17] +Investi in macchina ~1~ Colombiani in 120 secondi! + +[PAGE_18] +Distruggi guidando ~1~ veicoli in 120 secondi! + +[PAGE_19] +Stacca la testa a ~1~ Colombiani in 120 secondi! + +[PAGE_20] +Decapita ~1~ Hood in 120 secondi! + +[JOEY_1] +TESTO NON PIÙ RICHIESTO + +[JM1_A] +Ehi, sono stufa, quand'è che si passa ai fatti? + +[JM1_B] +Tra un minuto, cocca, ho una cosetta da sbrigare. + +[JM1_C] +Ho un lavoretto per te, amico. + +[JM1_D] +I fratelli Forelli mi devono soldi da troppo tempo + +[JM1_E] +e occorre insegnargli un po' di rispetto. + +[JM1_F] +'Labbra' Forelli si sta ingozzando come un porco al St Mark's Bistro, + +[JM1_G] +quindi rubagli l'auto e portala nell'armeria di 8-Ball ad Harwood. + +[JM1_H] +Conosci 8-Ball, vero? + +[JM1_I] +Dopo che sarà stata installata una bomba, parcheggia l'auto dove l'hai trovata. + +[JM1_J] +Poi rilassati e goditi lo spettacolo. + +[JM1_K] +Ma fai in fretta, non starà a pranzo una vita. + +[CAT2_A1] +Forza, brutta deficiente! + +[CAT2_A] +La domanda è: sei venuto a salvare Maria o a riportarmi indietro? + +[CAT2_B] +Beh, stammi a sentire, + +[CAT2_B2] +spararti sarà un piacere, ma uscire con te è stato solo lavoro. + +[CAT2_C] +Sei il mio peccinno amigo! + +[CAT2_D] +Butta qui la grana. + +[CAT2_E] +Ti sei dato molto da fare! + +[CAT2_E2] +Ma non hai imparato che non devi fidarti di me. + +[CAT2_E3] +Ammazzate quell'idiota. + +[CAT2_J] +Fai decollare questo affare! + +[HM5_1] +Ehi, Ice mi ha detto che saresti arrivato. Ecco le regole. Solo mazze. Niente pistole, niente auto. + +[HM5_5] +È una battaglia per il rispetto, chiaro?. + +[HELP14] +Per raccogliere le armi, passaci sopra a piedi. Non puoi raccoglierle mentre sei in un veicolo. + +[CRUSH] +Parcheggia nell'area contrassegnata ed esci dal veicolo. Il veicolo verrà rottamato. + +[DIAB2_B] +Un gruppo di gentaglia mi ha minacciato di asportare il mio attributo di tutto rispetto se non gli sgancio dei soldi. + +[DIAB2_C] +Hanno minacciato l'uomo sbagliato, amigo. + +[DIAB2_D] +Hanno un debole per il gelato. + +[DIAB2_E] +Prendi la bomba che ho nascosto ad Harwood, + +[DIAB2_F] +dirotta il furgone dei gelati durante i suoi spostamenti + +[DIAB2_G] +e attira quegli idioti verso il loro destino con il campanello. + +[DIAB2_H] +Si nascondono in un magazzino ad Atlantic Quay. + +[DIAB3_A] +Qualche insolente della Triade ha rubato la mia stupenda macchina ieri notte, + +[DIAB3_B] +l'ha distrutta e carbonizzata. + +[DIAB3_C] +Uno dei miei più cari escrementi asinini si trovava nel bagagliaio + +[DIAB3_D] +veri oggetti da collezione, insostituibili amico mio. + +[DIAB3_E] +Ho nascosto un'arma lancinante alla periferia di Chinatown. + +[DIAB3_F] +Prendila e insegna ai vandali della Triade a temere l'ira super-dotata di El Burro! + +[DIAB3_1] +UCCIDI 25 ELEMENTI DELLA TRIADE + +[DIAB4_A] +Un ladruncolo approfittatore ha rubato un furgone carico di copie delle mie ultime pubblicazioni! + +[DIAB4_B] +Ma quell'idiota fatto di SPANK ha lasciato lo sportello posteriore aperto, + +[DIAB4_C] +per cui tutto quello splendido capolavoro patinato + +[DIAB4_D] +con intriganti foto pornografiche è stato sparso per tutta Liberty! + +[DIAB4_E] +Prendi il furgone e segui la scia di copie di Un Asino si fa Dallas volume 1, 2 e 3 + +[DIAB4_F] +raccogliendole lungo la strada. + +[DIAB4_G] +Quando raggiungi quel ladruncolo imbottito di SPANK, fallo fuori! + +[DIAB4_H] +Poi consegna il mio materiale asinino a Riviste XXX nel quartiere a luci rosse. + +[DIAB4_1] +~g~Porta il furgone nel retro di Riviste XXX. + +[HM1_E] +Devi far vedere a queste depravate come si fa una vera sparatoria. + +[HM1_H] +Togli di torno questi Nine! + +[HM2_A] +Questi Nine mi stanno causando problemi. + +[HM2_B] +Queste stronze hanno auto blindate e adesso stanno spacciando SPANK... + +[HM2_C] +e lo smerciano senza paura ai miei fratelli. + +[HM2_D] +C'è un'auto parcheggiata in strada. + +[HM2_E] +Dentro c'è della roba che metterà le sorelline fuori combattimento... + +[HM3_A] +Qualche bastardo ha messo una bomba nella mia auto. + +[HM3_B] +Se perdo l'auto, la mia reputazione nel giro sarà finita. + +[HM3_C] +Prendi la mia macchina e portala al garage a St. Marks, chiaro? + +[HM3_D] +Fagli disinnescare la bomba, lascia che se ne occupino loro. + +[HM3_E] +Il tempo corre e l'ordigno è tutto incasinato. + +[HM3_F] +Se becchi una buca potrebbe saltare tutto in aria. + +[HM3_G] +Adesso muoviti! + +[HM4_A] +Ehi, un aereo federale portavalute si è appena schiantato all'aeroporto Francis. + +[HM4_B] +C'è del platino sparso per tutta la pista. + +[HM4_C] +Prendi un'auto e raccogline quanto ne puoi! + +[HM4_F] +Puoi scaricare il malloppo in uno dei miei garage. + +[HM4_G] +Il platino è molto pesante e rallenterà un po' l'automobile. + +[HM4_H] +Quindi fai diverse consegne nel garage. + +[HM5_A] +Quei Nine sono rimasti in una manciata di rognosi... + +[HM5_B] +ma insistono ancora. + +[HM5_C] +Ci siamo accordati per uno scontro. + +[HM5_D] +Un gruppo dei loro contro due di noi, anzi... + +[HM5_E] +due di te. + +[HM5_F] +Ti accompagnerei, ma... + +[HM5_G] +non sarò in libertà vigilata per altri tre mesi. + +[HM5_H] +Tu mi capisci, vero? + +[HM5_I] +Vai a trovare il mio fratellino, + +[HM5_J] +Ti accompagnerà sul luogo della rissa, amico. + +[MEA1_B] +Mi chiamo Chonks, Marty Chonks. + +[MEA1_C] +Gestisco la fabbrica di cibo per cani Bitch'n'Dog qui vicino. + +[MEA1_D] +Ho dei problemi finanziari, ma in fondo chi non ne ha? + +[MEA1_E] +Più tardi mi incontrerò col direttore della mia banca. + +[MEA1_F] +È un imbroglione schifoso che continua a far crescere gli interessi di un prestito per arricchirsi. + +[MEA1_G] +Usa la mia auto, vallo a prendere e portalo qui. + +[MEA1_H] +Ho una sorpresina per quella maledetta sanguisuga! + +[MEA2_A] +Ho assunto dei ladri per entrare nel mio appartamento... + +[MEA2_C] +Quei ladruncoli bastardi minacciano di dire tutto all'assicurazione, + +[MEA2_D] +se non do loro una percentuale. + +[MEA2_E] +Roba da non credere! + +[MEA2_F] +Ho parcheggiato un'auto dietro i cancelli della fabbrica. + +[MEA2_G] +Usala e valli a prelevare nel loro territorio nel quartiere a luci rosse. + +[MEA2_H] +Poi portali qui in fabbrica così che comprendano il mio punto di vista. + +[MEA3_A] +L'azienda farà bancarotta se non riesco a procurarmi in fretta parecchi contanti. + +[MEA3_B] +Mia moglie ha un'assicurazione e l'unica cosa che sa fare è sperperare i miei soldi. + +[MEA3_C] +Ho lasciato un'auto al solito posto. + +[MEA3_D] +Vai a prendere mia moglie al salone di bellezza Classic Nails e accompagnala alla fabbrica. + +[MEA4_A] +Maledizione, sono nei guai! + +[MEA4_B] +Sembra che mia moglie avesse una storia con un tipo a cui devo dei soldi. + +[MEA4_C] +È molto arrabiato e vuole vendetta! + +[MEA4_E] +Lui si aspetta che cercherò di patteggiare... + +[MEA4_F] +ma io credo che... + +[MEA4_G] +i cani di Liberty assaggeranno dei bocconcini extra questo mese! + +[WELCOME] +BENVENUTI A + +[HM1_2] +~g~Prendi un veicolo e ricorda: contano solo le uccisioni fatte con l'Uzi mentre guidi! + +[HELP8_B] +Premi il ~h~tasto ~k~~PED_SNIPER_ZOOM_IN~ ~w~per ~h~zoomare ~w~col fucile e il~h~ tasto ~k~~PED_SNIPER_ZOOM_OUT~~w~ per ~h~allargare il campo~w~. + +[LRQC_1] +Asuka e io dobbiamo parlare, OK? + +[LRQC_2] +Perché non ti fai un giro? + +[LRQC_3] +Ti servira un posto dove nasconderti. + +[LRQC_4] +C'è un magazzino al confine di Bellville che farà al caso tuo. + +[LRQC_5] +Ritorna qui nel mio appartamento quando sei pronto, + +[LRQC_6] +Così possiamo chiacchierare un po'. + +[JM6_5] +~g~Ti serve un veicolo per la fuga, idiota! + +[JM2_F] +Se ti serve un'arma, vai sul retro di AmmuNation dal lato opposto della metropolitana. + +[LOVE4_7] +~g~C'è un cantiere edile a Staunton Island, forse hanno portato lì il pacco. + +[LOVE4_8] +~g~Ti servirà un'auto per accedere al garage. + +[TSCORE] +GUADAGNI: ~1~$ + +[AM1_9] +~r~Salvatore è fuggito di nuovo dentro il club Luigi's! + +[AM1_6] +~g~Se ti aggiri attorno al club Luigi's, la Mafia ti avvisterà! + +[TM2_3] +~g~È una trappola! Falli fuori tutti!!! + +[FM4_1] +Sono Maria. L'auto è una trappola! Incontriamoci alla rampa a sud del ponte Callahan. + +[JM1_7] +~g~Chiudi la portiera! Lo noterà! + +[KM5_1] +~g~TRITATO DI SPACCIATORE! + +[KM5_6] +~g~Devi uccidere almeno 8 spacciatori Yardie. + +[KM5_7] +~g~Uccidili in fretta! Dopo aver venduto lo SPANK spariscono dalla circolazione. + +[RM3_8] +~r~Quell'auto è un tranello!!! + +[LM3_8] +Ehi, sono Joey. + +[LM3_9] +Luigi mi ha detto che sei affidabile, quindi torna più tardi, + +[KM3_5] +~g~Suona il clacson per avviare l'affare. + +[LOVE7] +LA SCOMPARSA DI LOVE + +[LOVE2_5] +~g~Kenji è un tritato di carne! Allontanati da Newport e abbandona l'auto! + +[AS2_11] +~g~~1~ SU 9! + +[GARAGE1] +~g~Esci dal veicolo ed esci fuori. + +[KM3_11] +~g~Il Cartello è stato attaccato e la valigia non è stata recuperata. + +[KM3_12] +~g~Uccidi tutti i Colombiani, distruggi i veicoli e recupera la valigia. + +[KM3_13] +~g~Riconsegna la valigia al Casinò. + +[RM5_6] +~g~Ci è scappato! Spaccagli la corazza con un veicolo o con una bella esplosione! + +[PBOAT_1] { re3 change } +Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare con i cannoni della barca. + +[PBOAT_2] { re3 change } +Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare con i cannoni della barca. + +[DIAB1_B] +Sono El Burro dei Diablo. + +[DIAB1_D] +Sei da poco a Liberty, ma ti sei già guadagnato una reputazione in città. + +[DIAB1_E] +C'e una corsa d'auto con inizio alla vecchia scuola vicino al ponte Callahan. + +[DIAB1_F] +Procurati un mezzo, il primo che attraversa tutti i posti di blocco vince il premio. + +[HM2_1] { re3 change } +Usa i maggiolini radiocomandati per distruggere le auto blindate. Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per farli esplodere. + +[HM2_1A] { re3 change } +Usa i maggiolini radiocomandati per distruggere le auto blindate. Premi il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per farli esplodere. + +[HM2_2] +~r~Non sei riuscito a distruggere tutte le auto blindate! + +[HM2_6] +~g~Auto blindata distrutta! + +[RM3_A] +Conosco un uomo molto importante in città, di manica larga, + +[RM3_H] +con gusti, diciamo, esotici e i soldi per permetterseli. + +[RM3_B] +È coinvolto in un processo e l'accusa ha delle sue foto piuttosto imbarazzanti + +[RM3_C] +in cui appare in un party all'obitorio. + +[LOVE6_A] +Una lezione d'affari, amico. + +[LOVE6_E] +Se possiedi qualcosa di unico, il mondo intero cercherà di strappartelo dalle mani... + +[LOVE6_C] +Squadre speciali di polizia hanno circondato la zona attorno al mio socio e al pacco. + +[LOVE6_D] +Vai li, prendi il furgone e fuggi per attirarli dietro di te. + +[LOVE6_F] +Tienili occupati, così che lui possa sfuggire! + +[AM3_C] +Probabilmente quando lo leggerai questo sarà in alto mare! Ruba un'imbarcazione della polizia e mandalo a picco! + +[FESZ_UC] +ANNULLA + +[FEDS_SM] +L1,R1-CAMBIA MENU + +[FEDS_AS] +;=-CAMBIA SELEZIONE + +[FEDSAS2] +<>-CAMBIA SELEZIONE + +[FEDS_SS] +L1,R1-CAMBIA SELEZIONE + +[FEDSSC1] +;-SCORRIMENTO RAPIDO + +[FEDSSC2] +=-FERMA SCORRIMENTO + +[MEA2_3] +~g~Riporta l'auto alla fabbrica. + +[RM1_3] +~r~McAffrey è scappato! + +[RM1_4] +~g~Hai usato tutte le granate! Procuratene altre da AmmuNation! + +[RM1_5] +~g~Torna indietro e incendia il rifugio! + +[RM6_4] +~g~Vai al nascondiglio e preleva la roba di Ray. + +[RM6_5] +~g~La CIA ha messo il ponte sotto sorveglianza, trova un altro punto d'accesso. + +[HM2_F] +e distruggi tutte le loro attrezzature blindate. + +[HM_4] +'CORSA AL LINGOTTO' + +[MEA2_B5] +TESTO NON PIÙ NECESSARIO + +[MEA1_B5] +TESTO NON PIÙ NECESSARIO + +[MEA3_B5] +TESTO NON PIÙ NECESSARIO + +[MEA4_B7] +ma se entri un attimo nel mio ufficio... + +[MEA3_B4] +Marty vuole vedermi? È meglio che si sbrighi perché devo acconciarmi i capelli. + +[KM3_7] +È un esperto in trappole della Yakuza! + +[FES_LOF] +Caricamento fallito. + +[P1INSA] +Ingresso 1. memory card (PS2) inserita. ~1~ K di spazio disponibile per salvare. ~1~ K necessari. + +[P1INSN] +Ingresso 1. memory card (PS2). Spazio disponibile insufficiente. Elimina alcuni file. + +[FES_SLO] +SALVA FILE + +[FES_ISC] +CORROTTO + +[FESZ_TI] +SALVA Z1 + +[FESZ_SA] +Salva partita + +[P1NOIN] +Ingresso 1. memory card (PS2) non inserita + +[P1INSE] +Ingresso 1. memory card (PS2) inserita. + +[MC_LDFL] +Caricamento fallito! + +[MC_NWRE] +Partita in fase di riavvio. + +[LOVE6_3] +~g~Hai ~1~ secondi per tornare alla Securicar prima di fallire la missione. + +[LOVE6_4] +~r~Hai distrutto la Securicar da depistaggio! + +[HELP1] +Fermati nel centro del segnale blu. + +[HELP12] +Cammina nel centro del segnale blu per attivare una missione. + +[HJSTAT] +Distanza: ~1~.~1~m Altezza: ~1~.~1~m Ribaltamenti: ~1~ Rotazioni: ~1~_ + +[HJSTATW] +Distanza: ~1~.~1~m Altezza: ~1~.~1~m Ribaltamenti: ~1~ Rotazioni: ~1~_ E che grande atterraggio! + +[DIAB1_5] +TEMPO DI GARA: + +[LOVE3_4] +~r~Hai distrutto l'aereo!!! + +[F_FAIL1] +Missione Camion dei pompieri terminata. + +[F_CANC] +~r~Missione Pompieri annullata! + +[F_EXTIN] +INCENDI: + +[A_COMP1] +Missioni Infermieri completate! + +[A_CANC] +~r~Missione Infermieri annullata! + +[A_COMP3] +Missioni Infermieri completate! Ora non ti stancherai mai mentre corri! + +[ATUTOR] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Infermieri. + +[ATUTOR3] +Premi il ~h~tasto ~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Infermieri. + +[ALEVEL] +Missione Infermieri Livello ~1~ + +[A_FAIL1] +Missione Infermieri terminata. + +[FEST_HA] +Missione Infermieri - Livello Max. + +[A_SAVES] +PERSONE SALVATE: ~1~ + +[C_KILLS] +CRIMINALI UCCISI: ~1~ + +[HM1_B] +Ho un problema. Stanno cercando di farsi gioco di me. + +[AM2_A] +La morte di Salvatore è un'ottima notizia, + +[AM2_A2] +sei un killer efficiente. Mi piace come qualità in un uomo. + +[AM2_B] +Lui è mio fratello Kenji. + +[AM2_C] +Asuka ha un lavoretto per te: quando hai finito, passa al Casinò così parliamo. + +[AM2_D] +Proprio come Kenji, che cerca sempre di usare i miei giocattoli. + +[AM2_E] +La mia fonte alla polizia mi ha informato che la Mafia sta studiando le nostre operazioni nella città + +[AM2_E2] +nel tentativo di scovarti. + +[AM2_F] +Non possiamo continuare le nostre operazioni fino a quando il problema non verrà risolto. + +[AM2_G] +Elimina questi insulsi spioni e termina questa vendetta una volta per tutte. + +[F_START] +~g~Veicolo in fiamme avvistato nell'area ~a~. Vai a spegnere il fuoco. + +[AM4_1A] +Vai al telefono al parco Belleville ovest. + +[AM4_1B] +Vai al telefono nel campus Liberty. + +[AM4_1C] +Vai al telefono al parco Belleville sud. + +[AM4_1D] +Incontriamoci nei bagni pubblici nel parco. + +[HJSTATF] +Distanza: ~1~ piedi Altezza: ~1~ piedi Ribaltamenti: ~1~ Rotazioni: ~1~_ + +[HJSTAWF] +Distanza: ~1~ piedi Altezza: ~1~ piedi Ribaltamenti: ~1~ Rotazioni: ~1~_ E che grande atterraggio! + +[HM1_F] +Stai in guardia, però. Ci saranno dei Jack in giro che penseranno che vuoi far fuori anche loro! + +[HM1_D] +'Nine' è il loro nome e la loro bandiera è porpora e ogni giorno che portano i loro colori... + +[HM1_G] +è un giorno in cui i 'Jack' appaiono rammolliti. + +[MEA2_B] +e ruba qualcosa così che io possa incassare l'assicurazione come fai tu. + +[TM3_H] +~w~Hai fatto un bel lavoro laggiù, molto bravo. + +[TM3_I] +~w~Forza, adesso ti presento al Don. + +[TM3_J] +~w~Eehi!!! Luigi! + +[TM3_K] +~w~Oh, le mie ragazze hanno sentito la sua mancanza, Don Salvatore, è mancato per molto tempo. + +[TM3_L] +~w~Dì loro che una volta risolta questa situazione spiacevole, + +[TM3_M] +~w~andremo tutti al club a festeggiare, OK? + +[TM3_N] +~w~Ecco il mio ragazzo! + +[TM3_N2] +~w~Come va, papà? + +[TM3_O] +~w~Ti sei trovato una brava mogliettina? + +[TM3_P] +~w~Ehi, tua madre, pace all'anima sua, si rivolterebbe nella tomba + +[TM3_Q] +~w~vedendoti ancora senza moglie. + +[TM3_R] +~w~Lo so, papà, ci sto pensando su. + +[TM3_S] +~w~TONI! Come sta tua madre? + +[TM3_T] +~w~È una donna eccezionale. Forte. + +[TM3_U] +~w~Sta bene... tutto OK. + +[TM3_V] +~w~Bene, bene. Voi ragazzi entrate pure, mentre io chiacchiero col nostro nuovo amico. + +[TM3_W] +~w~Vedo un futuro roseo per te, figghiu miu... + +[RM1_A] +Quell'infame di McAffrey, si prende più bustarelle di chiunque. + +[RM1_B] +Pensa di venir assolto dignitosamente presentando delle prove inequivocabili. + +[RM1_C] +Ha appena cantato! + +[RM4_B] +Dobbiamo metterlo a tacere, per sempre. + +[RM4_E] +Voglio che dorma con i pesci, in senso letterale. + +[LOVE3_B] +Avvicinandosi all'aeroporto stasera, un piccolo aereo sorvolerà la baia. + +[LOVE4_D] +Sfortunatamente le autorità aeroportuali avevano già iniziato a smantellare l'aereo + +[LOVE4_H] +finché non sono intervenuto sborsando una fortuna. + +[LOVE4_E] +Attraversa il ponte per Shoreside Vale e vai all'aeroporto internazionale Francis. + +[GTAB_A] +Ehi, portiamo questo fuori di qui. Dio solo sa cos'è + +[GTAB_B] +ma sembra che lui lo voglia davvero, quindi dovrà pur valere qualcosa. + +[GTAB_C] +Chi diavolo è! + +[GTAB_D] +TU! + +[GTAB_E] +Ehi, in gamba amigo, stai calmo, amigo! Di niente, di niente! + +[GTAB_F] +Ti ho lasciato sfogare! + +[GTAB_G] +Non sparare amigo. Nessun problema. Siamo tutti amici. Ecco, prendi questo. + +[GTAB_H] +Non fare la femminuccia! + +[GTAB_I] +Non abbiamo scelta, baby! + +[GTAB_J] +C'è sempre una scelta, stupido bastardo! + +[GTAB_K] +Mi spiace per quella cretina, amigo, sono tutte uguali... por favor? + +[GTAB_L] +Allora la puttana è scappata. + +[GTAB_M] +Ma mi hai fatto un favore, + +[GTAB_N] +non sei il solo che ha un conto in sospeso col Cartello, + +[GTAB_O] +questo verme ha ucciso mio fratello! + +[GTAB_P] +Non ho mai ucciso nessun Yakuza! + +[GTAB_Q] +BUGIARDO! Abbiamo visto tutti l'assassino del Cartello. + +[GTAB_R] +Daremo la caccia ed elimineremo tutti voi cani Colombiani! + +[GTAB_S] +Farò un'operazione al nostro amichetto per strappargli qualche informazione e per divertirmi. + +[GTAB_T] +Tu, passa più tardi, sono certo che avrò bisogno del tuo aiuto. + +[GTAB_U] +Ti prego, amigo, non lasciarmi con lei, è tutta matta! Amigo? Ehi Amiiigooo!!! Aieeeeeeaaaaahh!!! + +[LOVE5_A] +Ti stai dimostrando un buon investimento, cosa rara in questi giorni. + +[KM3_1] +~g~Il Cartello aspetta una gang di Yardie. Vai a rubare una loro auto! Dirigiti a nord, ne troverai una a Newport. + +[LOVE1_1] +~g~Procurati un'auto dei gangster Colombiani per infiltrarti nel loro nascondiglio: dirigiti a nord, ne troverai una a Fort Staunton. + +[FM1_Q1] +~w~Ti va di divertirti? Un po' di... hmmm? Un po' di SPANK? + +[FM1_R] +~w~Ciao Chico. No, il solito. + +[FM1_T] +~w~Grazie Chico, a presto. + +[FM1_W] +~w~OK, Fido, aspetta qui e tieni d'occhio l'auto mentre io vado a sculettare un po'. + +[FM1_X] +~w~OK, Fido, andiamocene via di qui. Uuuhhh! + +[FM1_Q] +~w~Ehi, Maria! La mia ragazza preferita! + +[FM1_S1] +~w~Ehi, forse dovresti dare un'occhiata al party nei magazzini sulla costa est del molo atlantico. + +[FM1_U] +~w~Gracias e buon divertimento. È roba buona. + +[FM1_V] +~w~Forza, Fido, facciamo un salto a questo party! + +[FM1_SS] +~r~SCANNER: ~g~4-5 a tutte le unità: rinforzi per l'operazione antinarcotici al molo atlantico... + +[LOVE6_B] +Anche se hanno scarsa comprensione del suo autentico valore. + +[TM3_A1] +~r~Joey è spacciato! + +[TM3_A2] +~r~Joey e Luigi sono stati cremati! + +[TM3_A3] +~r~Joey, Luigi e Toni sono fritti! + +[FM4_2] +Ascolta, Salvatore pensa che vogliamo tradirlo, + +[FM4_3] +per cui ti stava per consegnare al Cartello per raggiungere un accordo. + +[FM4_4] +Non potevo lasciarglielo fare, e poi la cosa peggiore è che + +[FM4_4B] +è tutta colpa mia... perché gli ho detto che stavamo insieme. + +[FM4_5] +Non chiedermi il perché. Non lo so. + +[FM4_6] +Senti, sei un uomo spacciato in territorio mafioso, e anch'io devo svignarmela. + +[FM4_6B] +Ho visto troppi omicidi. Troppo sangue! + +[FM4_7] +Questa è una mia vecchia amica, OK, di vecchia data. È Asuka, ed è una tipa fidata. + +[FM4_8] +Forza, basta con i discorsi. + +[FM4_9] +Dovremmo andarcene prima che arrivino altri italiani isterici poco propensi a una discussione amichevole. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUTTORE + +[CRED003] +LESLIE BENZIES + +[CRED004] +DIREZIONE ARTISTICA + +[CRED005] +AARON GARBUT + +[CRED006] +DIREZIONE TECNICA + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DESIGN + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +SCRITTO DA + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +PERSONAGGI + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMAZIONE E REGIA + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +DESIGN AUTOMOBILI + +[CRED024] +PAUL KUROWSKI + +[CRED025] +GRAFICI + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODIFICATORI + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +MUSICHE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +DESIGN AUDIO E MASTERIZZAZIONE + +[CRED042] +ALLAN WALKER + +[CRED043] +PROGRAMMAZIONE AUDIO + +[CRED044] +RAYMOND USHER + +[CRED045] +DIRETTORE TESTING + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +TESTER PRINCIPALI + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTER + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +SUPPORTO TECNICO + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +PRODUTTORE ESECUTIVO + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUTTORE + +[CRED064] +DAN HOUSER + +[CRED065] +DIRETTORE DI SVILUPPO + +[CRED066] +JAMIE KING + +[CRED067] +PRODUTTORE TECNICO + +[CRED068] +GARY J. FOREMAN + +[CRED069] +PRODUTTORE ASSOCIATO + +[CRED070] +JEREMY POPE + +[CRED071] +SUPERVISORE MUSICHE + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VICEPRESIDENTE MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +COORDINATORE TECNICO + +[CRED088] +BRANDON ROSE + +[CRED089] +RESPONSABILE CONTROLLO QUALITÀ + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +ANALISTA DEL GIOCO + +[CRED094] +RICHARD HUIE + +[CRED095] +SQUADRA TESTING + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +PIANTA DI LIBERTY + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +SEQUENZE ANIMATE + +[CRED108] +COPIONE SCRITTO DA DAN HOUSER E JAMES WORRALL + +[CRED109] +REGISTRAZIONE AUDIO DIRETTA DA DAN HOUSER + +[CRED110] +AUDIO PRODOTTO DA RENAUD SEBBANE + +[CRED111] +CASTING + +[CRED112] +FRANK VINCENT NEL RUOLO DI SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO NEL RUOLO DI LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN NEL RUOLO DI TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT NEL RUOLO DI JOEY LEONE + +[CRED116] +DEBBI MAZAR NEL RUOLO DI MARIA + +[CRED117] +KYLE MACLACHLAN NEL RUOLO DI DONALD LOVE + +[CRED118] +ROBERT LOGGIA NEL RUOLO DI RAY MACHOWSKI + +[CRED119] +GURU NEL RUOLO DI 8-BALL + +[CRED120] +SONDRA JAMES NEL RUOLO DI MOMMA + +[CRED121] +LIANA PAI NEL RUOLO DI ASUKA + +[CRED122] +LES MAU NEL RUOLO DI KENJI + +[CRED123] +CYNTHIA FARRELL NEL RUOLO DI CATALINA + +[CRED124] +AL ESPINOSA NEL RUOLO DI MIGUEL + +[CRED125] +CHRIS PHILLIPS NEL RUOLO DI EL BURRO + +[CRED126] +HUNTER PLATIN NEL RUOLO DI CHICO + +[CRED127] +WALTER MUDU NEL RUOLO DI D-ICE + +[CRED128] +CURTIS MCCLARIN NEL RUOLO DI CURTLY + +[CRED129] +BILL FIORE NEL RUOLO DI DARKEL + +[CRED130] +CHRIS PHILLIPS NEL RUOLO DI MARTY CHONKS + +[CRED131] +HUNTER PLATIN NEL RUOLO DI RICCIOLINO BOB + +[CRED132] +WALTER MUDU NEL RUOLO DI KING COURTNEY + +[CRED133] +HUNTER PLATIN NEL RUOLO DI ONE-ARMED PHIL + +[CRED134] +KIM GURNEY NEL RUOLO DI MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATO DA + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRETTO DA + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODOTTO DA + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +REGISTRATO PRESSO GLI STUDI MODERN UPRISING, BROOKLYN + +[CRED140] +ATTORI + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS + +[CRED141] +DIALOGO DEI PEDONI + +[CRED142] +SCRITTO DA DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRETTO DA CRAIG CONNER, DAN HOUSER E LAZLOW + +[CRED144] +PRODOTTO DA RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +STAZIONI RADIO E MUSICA + +[CRED218] +PRODUTTORI PER ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +COORDINATORE COLONNA SONORA + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUTTORE PER ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +POST-PRODOTTA DA + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER E IMAGING SCRITTE DA + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +RINGRAZIAMENTI SPECIALI A + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CRED270] +MIKE HONG + +[CINCAM] +Camera mobile + +[KM1_13] +Entra in auto dentro il garage! + +[KM3_14] +~r~Sei stato scoperto, l'affare è saltato! + +[EBAL_H] +Aspetta qui mentre entro a parlare con Luigi. + +[EBAL_M] +Ricorda, niente casini con le mie ragazze! + +[LM2_F] +Poi prendi la sua macchina, riverniciala. + +[LM2_D] +tieni, prendi. + +[LM1_9] +Ciao, sono Misty. + +[LM4_A] +I papponi dei Diablo hanno messo a battere le loro mignotte nella mia zona. + +[FM2_B] +C'è di mezzo una spia. + +[FM2_C] +Non è ne un pusher ne un pappone, quindi dev'essere una spia. + +[FM3_CC] +~w~Fratello, torna quando avrai i soldi. + +[LOVE5_5] +~r~Non sei riuscito a proteggere il camion! + +[RM6_6] +~r~Ray è morto! + +[RM6_7] +~r~Ray ha perso il volo. + +[RM6_8] +~g~Ti sei dimenticato Ray, torna a prenderlo. + +[FM1_10] +~g~Ti sei dimenticato Maria, torna a prenderla. + +[LOVE4_9] +~r~L'aereo è stato distrutto! + +[LOV4_10] +~r~Il solo indizio che indicasse dov'è finito il pacco è stato distrutto! + +[KM2_D] +Non occorre dire che dobbiamo donargli le auto, per ripagare il debito che gli è dovuto. + +[KM4_B] +Le imprese che godono della nostra protezione oggi salderanno i conti. + +[KM2_E] +Devi procurarti le auto nella lista e consegnarle nel garage dietro il parcheggio a Newport. + +[FM3_8I] +~w~Scegli una postazione vantaggiosa, poi io entrerò dopo che hai sparato il primo colpo. + +[LOVE1_B] +L'esperienza mi ha insegnato che un uomo come te sa essere molto leale se ben pagato, + +[LOVE1_H] +ma gruppi di uomini diventano avidi. + +[LOVE1_C] +Un personaggio chiave, un vecchietto orientale che conosco, + +[LOVE1_I] +è ostaggio di alcuni sud-americani in Aspatria. + +[MEA4_D] +Ho preso accordi per vederlo... + +[MEA4_B4] +Ti manda Marty, eh? OK, mostrerò a quello schifoso il significato della parola affari. + +[MEA4_B5] +Carl, ciao! Mi, uh, mi serve più tempo per pagarti. + +[MEA1_B4] +Ah, ti manda il signor Chonk, vero? Andiamo a fargli un visita. + +[HM5_6] +Andiamo a spaccare qualche cranio... + +[LOVE1_5] +~g~Non perdere altro tempo, procurati un'auto dei Colombiani e salva il socio di Love. + +[AS1_D] +~w~Agisci da esca e fatti inseguire dai plotoni d'esecuzione fino a Pike Creek + +[AS1_E] +~w~dove i miei uomini li aspetteranno. + +[AS2_C] +~w~Il Cartello ha un negozio di copertura, la compagnia Kappa Coffee House. + +[AS2_E] +~w~Non abbiamo scelta, dobbiamo mettere questi spacciatori ambulanti fuori combattimento. + +[AS2_F] +~w~Riducili in poltiglia!! + +[AS2_A1] +~w~Miguel ha di certo un po' di quella famosa prestanza latina. + +[AS2_A2] +~w~Io non ce la faccio più. + +[SIREN_3] +Per attivare la sirena di questo veicolo premi il ~h~tasto ~k~~VEHICLE_HORN~~w~. + +[SIREN_4] +Per attivare la sirena di questo veicolo premi il ~h~tasto ~k~~VEHICLE_HORN~~w~. + +[AS3_C] +~w~Eeeeyooo! Cos'è quella roba gialla e collosa? + +[AS3_C1] +~w~Oh, ciao pupa. + +[AS3_F] +~w~Ha un talento naturale questa ragazza. + +[AS3_F1] +~w~È riuscita a strappare questa perla al nostro ospite. + +[AS3_G] +~w~C'è un aereo in arrivo all'aeroporto internazionale Francis in 2 ore. + +[AS3_G1] +~w~È pieno del veleno di Catalina. + +[AS3_H] +~w~Puoi evitare la sicurezza dell'aeroporto andando in barca vicino alle boe di segnalazione della pista di atterraggio + +[AS3_H1] +e abbattendo l'aereo quando si avvicina. + +[AS3_I] +~w~Raccogli il carico dalle macerie! + +[AS3_J] +~w~Oh, adesso fa attenzione, OK baby? + +[AS3_K] +~w~Adesso prova l'olio di chili... + +[RM2_F1] +Quei Colombiani dovrebbero essere qui a momenti! + +[RM2_K] +Maledizione, sono arrivati!!! CARICA LE ARMI!!! + +[LOVE2_7] +~g~Adesso abbandona l'auto! + +[LOVE2_8] +~g~Ora allontanati da Newport! + +[AM1_F] +Salvatore Leone lascerà Luigi's all'incirca alle ~1~:~1~. + +[LOVE5_C] +Voglio che tu lo segua che ti accerti che lui e il mio pacco giungano a Pine Creex illesi. + +[FESZ_SR] +Salvataggio fallito! Controlla la memory card (PS2) nell'ingresso MEMORY CARD 1 e riprova. + +[FESZ_FO] +Vuoi formattare la memory card (PS2) nell'ingresso MEMORY CARD 1? + +[FELZ_FO] +La memory card (PS2) nell'ingresso MEMORY CARD 1 non è formattata. + +[FES_NOC] +Nessuna memory card (PS2) nell'ingresso MEMORY CARD 1. + +[FES_LOE] +Caricamento fallito! Controlla la memory card (PS2) nell'ingresso MEMORY CARD 1 e riprova. + +[FES_DEE] +Eliminazione fallita! Controlla la memory card (PS2) nell'ingresso MEMORY CARD 1 e riprova. + +[FORSUC] +Formattazione della memory card (PS2) nell'ingresso MEMORY CARD 1 completata. + +[ERFOUN] +Formattazione della memory card (PS2) nell'ingresso MEMORY CARD 1 fallita. + +[ERMCNP] +Nessuna memory card (PS2) nell'ingresso MEMORY CARD 1. + +[SVMEM1] +Salva sulla memory card (PS2) nell'ingresso MEMORY CARD 1. + +[FORSLO] +Formatta la memory card (PS2) nell'ingresso MEMORY CARD 1. + +[SLONFM] +Errore di formattazione della memory card (PS2) nell'ingresso MEMORY CARD 1. + +[SLONDR] +Spazio insufficiente. Inserire una memory card (PS2) con almeno 500KB di spazio libero nell'ingresso MEMORY CARD 1. + +[SLNSP] +Spazio insufficiente. Inserire una memory card (PS2) con almeno 200KB di spazio libero nell'ingresso MEMORY CARD 1. + +[FEFD_WR] +Formattazione della memory card (PS2) nell'ingresso MEMORY CARD 1. Non rimuovere la memory card (PS2), riavviare o spegnere la console. + +[FES_ISF] +NON PRESENTE + +[FES_SAG] +PRESENTE + +[SLONNO] +Nessuna memory card (PS2) nell'ingresso MEMORY CARD 1. + +[SLONNF] +Memory card (PS2) nell'ingresso MEMORY CARD 1 non formattata. + +[FESZ_FM] +Memory card (PS2) nell'ingresso MEMORY CARD 1 non formattata. Vuoi formattare la memory card (PS2) nell'ingresso MEMORY CARD 1? + +[FESZ_FF] +Formattazione fallita! Controlla la memory card (PS2) nell'ingresso MEMORY CARD 1 e riprova. + +[MCDNSP] +Spazio insufficiente sulla memory card (PS2) nell'ingresso MEMORY CARD 1. Sono necessari almeno 500KB di spazio libero. Si desidera procedere? (SÌ o NO) + +[MCGNSP] +Spazio insufficiente sulla memory card (PS2) nell'ingresso MEMORY CARD 1. Sono necessari almeno 200KB di spazio libero. Si desidera procedere? (SÌ o NO) + +[FESZ_WR] +Salvataggio dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[FESZ_OW] +Sovrascrittura dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[FELD_WR] +Caricamento dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[FEDL_WR] +Eliminazione dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[LM2_C] +Luigi dice di, di darti questo, quindi... + +[LM3_G] +Joey non è tipo da aspettare: ricorda, questa è la tua grande occasione... + +[LM5_E] +Portane più che puoi prima che gli sbirri si bevano tutto lo stipendio. + +[JM5_C] +OK, c'è un'auto con ripieno di cadavere al bar vicino Callahan Point. + +[RM2_B] +Abbiamo visto i casini in Nicaragua, ai tempi in cui la nazione aveva ancora un senso. + +[RM2_C] +Qualche bastardo del Cartello l'ha maltrattato ieri e ha detto che ritornerà oggi per la sua merce. + +[RM2_D1] +Andrei io, ma la mia vecchia sciatica si fa sentire -cough cough- quindi, uhh, in bocca al lupo. + +[CATINF1] +~g~Prendi Catalina! + +[CATINF2] +~g~Segui l'elicottero per trovare Catalina. + +[BOATIN1] +Salta su una barca e premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per entrare. + +[BOATIN2] +Se sei vicino a una barca, puoi premere il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per tirarla verso di te. + +[BOATIN3] +Salta su una barca e premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per entrare. + +[BOATIN4] +Se sei vicino a una barca, puoi premere il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per tirarla verso di te. + +[JM6] +'LA PARTENZA' + +[FM1] +L'AUTISTA + +[JM1] +'L'ULTIMA CENA DI MIKE 'LABBRA'' + +[FM21] +'BOMBARDA LA BASE: ATTO PRIMO' + +[FM3] +'BOMBARDA LA BASE: ATTO SECONDO' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'SOTTO SORVEGLIANZA' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'S.A.M.' + +[RM2] +'A CORTO D'ARMI' + +[LOVE6] +'TRANELLO' + +[LOVE1] +'LIBERATORE' + +[RC1] +'DISTRUZIONE DEI DIABLO' + +[RC2] +'MASSACRO MAFIOSO' + +[RC3] +'CALAMITÀ AL CASINÒ' + +[RC4] +'LA FURIA DI RUMPO' + +[RM2_E1] +Non posso credere che quei bastardi musi gialli se ne siano andati lasciandomi di nuovo senza copertura! + +[GREN_1] +Quanto più a lungo tieni premuto il ~h~tasto ~k~~PED_FIREWEAPON~~w~, tanto più lontano lancerai la granata. + +[GREN_2] +Quanto più a lungo tieni premuto il ~h~tasto ~k~~PED_FIREWEAPON~~w~, tanto più lontano lancerai la granata. + +[GREN_3] +Quanto più a lungo tieni premuto il ~h~tasto ~k~~PED_FIREWEAPON~~w~, tanto più lontano lancerai la granata. + +[LOVE4_G] +Le mie proprietà ti aspettano all'interno dell'aereo nell'hangar doganale. + +[KABOOM] +KABOOOM! + +[SPLAT] +SPIACCICATO! + +[PANCAK] +SCHIACCIATO! + +[SOAKED] +ANNEGATO! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +Quando aprirà la metropolitana potrai prendere un treno verso Staunton Island. + +[TUBE2] +Quando aprirà Shoreside Vale potrai uscire dallo Shoreside terminal verso l'aeroporto internazionale Francis. + +[TUBE_2] +Per salire sulla metropolitana, premi il ~h~tasto 'entra nel veicolo'~w~. + +[LEGAL] +~g~Elimina la minaccia criminale! + +[GA_2] +Motore nuovo e carrozzeria riverniciata. Gli sbirri non ti riconosceranno! + +[LM1_8A] +Per guadagnare qualche soldo extra, perché non 'prendere in prestito' un taxi... + +[TAXIH1] +Fermati vicino a un pedone evidenziato per farlo salire a bordo e portarlo a destinazione prima che scada il tempo. + +[LM5_7] +~g~Se ci sono meno di quattro ragazze alla ~p~festa degli sbirri~g~, Luigi sarà nero! + +[KM2_3] +~g~Ricorda, le ~r~macchine~g~ devono essere in condizioni eccellenti per essere accolte nel ~p~garage~g~. + +[KM5_2] +~g~Uno Yardie è in giro per strada. + +[BETRA_A] +Scusa baby. + +[BETRA_B] +Sono una ragazza ambiziosa e tu, + +[BETRA_C] +sei una mezza calzetta. + +[JAILB_C] +Nessun dettaglio è stato fornito riguardo i prigionieri che venivano trasferiti dalla scorta, + +[JAILB_E] +La scorta aveva lasciato la centrale di polizia questa mattina presto... + +[JAILB_F] +per un trasferimento ordinario verso il penitenziario di Liberty. + +[JAILB_G] +L'attacco è avvenuto sul ponte Callahan, + +[JAILB_I] +Alcuni dei detenuti sembrerebbero essere deceduti a seguito dell'esplosione... + +[JAILB_P] +questo disastro lascia Portland isolata dal resto della città. + +[JAILB_Q] +Forza! + +[JAILB_R] +Signor stronzo! + +[JAILB_S] +Non è un problema ucciderti. + +[JAILB_T] +Te ne pentirai. + +[JAILB_U] +Va bene, va bene. Sparisci. + +[HELP15] +Quando sei a piedi, premi il ~h~tasto ~k~~PED_LOOKBEHIND~~w~ per ~h~guardare indietro~w~. + +[FEC_LB3] +Guarda indietro + +[FEC_R3] +(tasto R3) + +[FES_AFO] +Questa memory card (PS2) è già formattata. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CAMBIA SELEZIONE + +[FEDSAS4] +;=<> - CAMBIA SELEZIONE + +[SPRAY_4] { re3 change } +Usa il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare col cannone ad acqua. + +[SPRAY_1] { re3 change } +Usa il ~h~tasto ~k~~VEHICLE_FIREWEAPON~~w~ per sparare col cannone ad acqua. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +Salvatore uscirà da Luigi's attorno alle 0~1~:~1~. + +[JAILB_V] +La città di Liberty è sotto shock. + +[JAILB_A] +Mentre la polizia e i servizi di emergenza affrontano le conseguenze... + +[JAILB_B] +di un devastante attacco alla scorta avvenuto questa mattina. + +[JAILB_W] +Rivelazioni sulla professionalità dell'attacco hanno sconvolto le forze dell'ordine, + +[JAILB_K] +Inoltre, l'identificazione dei detenuti scomparsi è stata ulteriormente ostacolata... + +[JAILB_L] +da un attacco informatico al computer principale della centrale di Polizia. + +[JAILB_M] +Il Sindaco O'Donovan ha dichiarato che la polizia considera l'attentato... + +[JAILB_N] +* + +[JAILB_O] +Considerato il ritardo nella costruzione del sottopassaggio Porter, + +[JAILB_X] +In un'affermazione resa nota ieri, + +[FEDS_SE] +Tasto / - SELEZIONA + +[FEDS_SB] +Tasto / - SELEZIONA Tasto " - INDIETRO + +[TM4_A] +~w~Oh, sei te. TONI non è qua. + +[TM4_A2] +~w~Ma ha lasciato una delle sue dolci lettere per te. + +[DIAB2_A] +Ho iniziato a lavorare nel business dell'intrattenimento solo grazie al voluminoso contenuto dei miei pantaloni di pelle! + +[LM5_9] +RAGAZZE: + +[PERPIC] +Pacchetti speciali recuperati + +[CO_ONE] +Pacchetto speciale ~1~ su ~1~ + +[LOVE3_3] +~g~L'aeroplano ha fatto cadere ~1~ dei 6 pacchi. + +[FARE11] +~g~Destinazione: ~w~'il cantiere costruzioni'~g~ a Fort Staunton. + +[GA_21] +Non puoi parcheggiare altri veicoli in questo garage. + +[CHEAT1] +Trucco attivato + +[CHEAT2] +Trucco armi + +[CHEAT3] +Trucco salute + +[CHEAT4] +Trucco armatura + +[CHEAT5] +Trucco livello di sospetto + +[CHEAT6] +Trucco soldi + +[CHEAT7] +Trucco tempo atmosferico + +[AS1_H] +~r~Hai fallito nel far cadere la squadra della morte nella trappola della Yakuza!!! + +[FEDS_BA] +Tasto " - INDIETRO + +[RAMP_A] +TUTTE LE VIOLENZE COMPLETATE! + +[USJ_ALL] +TUTTE LE ACROBAZIE UNICHE COMPLETATE + +[FARE23] +~g~Destinazione: ~w~'Garage importazioni-esportazioni' ~g~nel distretto della diga Cochrane. + +[L_TRN_1] +Puoi prendere il treno sopraelevato a Portland. Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un vagone. + +[L_TRN_2] +Puoi prendere il treno sopraelevato a Portland. Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un vagone. + +[S_TRN_1] +Puoi prendere la metropolitana attraverso Liberty. Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un vagone. + +[S_TRN_2] +Puoi prendere la metropolitana attraverso Liberty. Premi il ~h~tasto ~k~~VEHICLE_ENTER_EXIT~~w~ per ~h~entrare~w~ o ~h~uscire~w~ da un vagone. + +[AS1_C] +~w~Ha tre squadre della morte in giro per Liberty il cui unico scopo è beccarti. + +[AS1_G] +~r~Tutti i membri della Yakuza sono morti!!! + +[JAN] +Gen + +[FEB] +Feb + +[MAR] +Mar + +[APR] +Apr + +[MAY] +Mag + +[JUN] +Giu + +[JUL] +Lug + +[AUG] +Ago + +[SEP] +Set + +[OCT] +Ott + +[NOV] +Nov + +[DEC] +Dic + +[DEFDT] +Nessun salvataggio valido + +[BUGGY] +MAGGIOLINI RIMASTI: + +[BONUS] +~g~BONUS $~1~$ + +[HORN1] +Premi il ~h~tasto L3~w~ per suonare il ~h~clacson~w~. + +[HORN2] +Premi il ~h~tasto L1~w~ per suonare il ~h~clacson~w~. + +[HORN3] +Premi il ~h~tasto R1~w~ per suonare il ~h~clacson~w~. + +[LM3_1A] +Premi il ~h~tasto ~k~~VEHICLE_HORN~~w~ per suonare il ~h~clacson~w~ e avvertire Misty che sei arrivato. + +[LM3_1B] +Premi il ~h~tasto ~k~~VEHICLE_HORN~~w~ per suonare il ~h~clacson~w~ e avvertire Misty che sei arrivato. + +[LM3_1C] +Premi il ~h~tasto ~k~~VEHICLE_HORN~~w~ per suonare il ~h~clacson~w~ e avvertire Misty che sei arrivato. + +[RADIO_A] +Premi il ~h~tasto ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ per passare in rassegna le ~h~stazioni radio~w~. + +[RADIO_B] +Premi il ~h~tasto ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ per passare in rassegna le ~h~stazioni radio~w~. + +[RADIO_C] +Premi il ~h~tasto ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ per passare in rassegna le ~h~stazioni radio~w~. + +[RADIO_D] +Premi il ~h~tasto ~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ per passare in rassegna le ~h~stazioni radio~w~. + +[FEC_EXV] +Entra o esci dal veicolo + +[TAXI_M] +'TAXISTA' + +[COP_M] +'VIGILANTE' + +[FIRE_M] +'POMPIERE' + +[AMBUL_M] +'PARAMEDICO' + +[HJ_IS] +BONUS ACROBAZIA FOLLE: ~1~$ + +[HJ_PIS] +BONUS ACROBAZIA FOLLE PERFETTA: $~1~ + +[HJ_DIS] +BONUS DOPPIA ACROBAZIA FOLLE: $~1~ + +[HJ_PDIS] +BONUS DOPPIA ACROBAZIA FOLLE PERFETTA: $~1~ + +[HJ_TIS] +BONUS TRIPLA ACROBAZIA FOLLE: $~1~ + +[HJ_PTIS] +BONUS TRIPLA ACROBAZIA FOLLE PERFETTA: $~1~ + +[HJ_QIS] +BONUS QUADRUPLA ACROBAZIA FOLLE: $~1~ + +[HJ_PQIS] +BONUS QUADRUPLA ACROBAZIA FOLLE PERFETTA: $~1~ + +[AM1_K] +Salvatore Leone uscirà da Luigi's fra circa tre ore. (0~1~:~1~) + +[IMPEXPP] +Garage importazioni-esportazioni, porto di Portland. Abbiamo richieste per vari mezzi: consulta la bacheca per le saperne di più. + +[VANHSTP] +Hai scassinato altre Securicar? Portale al nostro garage al porto di Portland. + +[EMVHPUP] +Ottimi prezzi per veicoli di soccorso nuovi o usati. Portali vicino alla gru a nord est del porto di Portland. + +[STANDS] +CHIOSCHI DISTRUTTI: + +[STASH] +~g~Nascondi lo SPANK nel ~p~cantiere edile~g~! + +[MCSTNS] +Nessuna memory card (PS2) nell'ingresso MEMORY CARD 1. Vuoi procedere comunque? (SÌ o NO) + +[LOVE3_5] +~g~L'aeroplano è a tiro. + +[LOVE3_6] +~r~La polizia è arrivata ai pacchi per prima! + +[SIREN_1] +Per accendere le sirene di questo veicolo, premi il ~h~tasto ~k~~VEHICLE_HORN~~w~. + +[SIREN_2] +Per accendere le sirene di questo veicolo, premi il ~h~tasto ~k~~VEHICLE_HORN~~w~. + +[FM3_8C] +~w~Avrò bisogno di 100.000$ per coprire le spese, + +[MCLOAD] +Caricamento dati. Non rimuovere la memory card (PS2) dall'ingresso MEMORY CARD 1, riavviare o spegnere la console. + +[FES_GME] +Errore di lettura della memory card (PS2) nell'ingresso MEMORY CARD 1! Controlla la memory card (PS2) e riprova. + +[FESZ_QF] +Sei sicuro di voler formattare la memory card (PS2) nell'ingresso MEMORY CARD 1? + +[FESZ_LS] +Caricamento riuscito. + +[RM3_5] +~g~Hai raccolto ~1~ su 6 prove. + +[LOVE3_2] +~g~Hai tutti i pacchi! Riportali subito a Donald Love. + +[LOVE4_4] +~g~Riporta il pacco a Donald Love. + +[FEDS_AM] +<>-CAMBIA MENU + +[FEB_SAV] +Carica + +[FEP_SAV] +CARICA PARTITA + +[AS2_12A] +~g~Dopo aver distrutto il primo chiosco, avrai 8 minuti prima che il Cartello informi i propri spacciatori! + +[AS3_1A] +~g~Adesso raggiungi la ~b~boa segnalatrice~g~! + +[NOCONT] +Ricollega il controller analogico (DUALSHOCK#) o controller analogico (DUALSHOCK#2) all'ingresso controller 1 per continuare. + +[BET_JB] +TRADITO DALLA SUA AMANTE CATALANA E ABBANDONATO MEZZO MORTO. GIUDICATO COLPEVOLE, INIZIA LA SUA AVVENTURA NEL PENITENZIARIO DELLA CITTÀ DI LIBERTY. HA UN SOLO PENSIERO FISSO NELLA TESTA... LA VENDETTA! + +[END_A] +I residenti in Cedar Grove si sono finalmente ripresi + +[END_B] +dallo spavento per i violenti scontri + +[END_C] +avvenuti ieri nell'area. + +[END_D] +Un abitante della zona, Clive Denver, ha dichiarato alla polizia + +[END_E] +di aver visto un uomo armato che sia allontanava dalla scena del crimine con una donna dai capelli neri. + +[END_F] +Oh, lo sai, vedrai che ci divertiremo, perché lo sai, cioè, + +[END_G] +io ti amo, io... io ti amo davvero perché sei un vero uomo + +[END_H] +ed è ciò di cui ho bisogno. + +[END_I] +Comunque, cosa stavo dicendo? + +[END_J] +Oh, lo sai, me lo sono dimenticato. Sai com'è che succede, vero? + +[END_K] +Il suono delle esplosioni ha fatto tremare le case mentre la gente correva al riparo. + +[END_L] +Molti cittadini sono rimasti feriti per il panico mentre venivano sparati colpi + +[END_M] +tra le forze terrestri e l'elicottero che sorvolava la diga. + +[END_N] +Sì, c'è proprio una bella vista da qui in giardino. + +[END_O] +Quando hanno fatto saltare l'elicottero + +[END_P] +è stato meglio dei fuochi d'artificio del 4 luglio. + +[END_Q] +Il numero di cadaveri recuperati ha già superato la ventina, + +[END_R] +ma la polizia continua a trovare nuovi corpi. + +[END_S] +Non sono state smentite le voci secondo cui + +[END_T] +i morti appartenessero al Cartello Colombiano, + +[END_U] +ciò nonostante, non ci sono indizi sulle ragioni di questa strage. + +[END_V] +Mi sono rotta un'unghia e i miei capelli sono un disastro. Ma ci puoi credere? + +[END_W] +Mi è costato 50 dollari... + +[PAPER1] +CRIMINALE FERITO DALLA FIDANZATA COMPLICE. LA CORTE GIUDICA IL RAPINATORE COLPEVOLE CON VERDETTO UNANIME. + +[PAPER2] +DIECI ANNI PER L'AMORE! + +[JAILB_J] +che è seguita all'attacco iniziale. + +[JAILB_D] +E nessun gruppo ha rivendicato l'attentato. + +[JAILB_H] +lasciando pochi testimoni e il ponte stesso seriamente danneggiato. + +[FEB_CPC] +Configurazione comandi + +[FEC_PED] +Comandi a piedi + +[FEC_VEH] +Comandi in un veicolo + +[FEC_FPR] +Comandi in prima persona + +[FEC_CMM] +Comandi comuni + +[FEC_PWL] +Cammina a sinistra + +[FEC_PWR] +Cammina a destra + +[FEC_PWF] +Cammina in avanti + +[FEC_PWT] +Cammina verso la telecamera + +[FEC_PLB] +Guardati indietro + +[FEC_PFR] +Spara con l'arma + +[FEC_CLE] +Arma precedente + +[FEC_CRI] +Arma successiva + +[FEC_LKT] +Aggancia bersaglio + +[FEC_PJP] +Salto Ped + +[FEC_PSP] +Scatto Ped + +[FEC_PSH] +Sparo Ped + +[FEC_TLF] +Bersaglio successivo a sinistra + +[FEC_TRG] +Bersaglio successivo a destra + +[FEC_CCM] +Centra visuale dietro al giocatore + +[FEC_SZI] +Ingrandimento fucile di precisione + +[FEC_SZO] +Riduzione fucile di precisione + +[FEC_LKL] +Sguardo a sinistra + +[FEC_LRT] +Sguardo a destra + +[FEC_LUP] +Sguardo verso l'alto + +[FEC_LDN] +Sguardo verso il basso + +[FEC_LBH] +Guarda dietro al veicolo + +[FEC_LLF] +Guarda a sinistra del veicolo + +[FEC_LRG] +Guarda a destra del veicolo + +[FEC_HRN] +Clacson + +[FEC_HBR] +Freno a mano + +[FEC_ACL] +Acceleratore + +[FEC_BRK] +Freno + +[FEC_TSM] +Attiva/Disattiva sottomissioni + +[FEC_CRD] +Cambia stazione radio + +[FEC_ENT] +Entra/Esci da un veicolo + +[FEC_WPN] +Spara con l'arma + +[FEC_PAS] +Pausa + +[FEC_FPO] +Attiva/Disattiva visuale di puntamento + +[FEC_SMS] +Mostra/Nascondi puntatore del mouse + +[FEC_CMS] +Cambia la modalità di visuale in qualsiasi situazione + +[FEC_TSS] +Salva un'immagine di gioco + +[FEN_NET] +Rete + +[FEN_CON] +Connessione + +[FEN_GAM] +Trova partite + +[FEN_TYP] +Modalità + +[FEN_TY0] +Deathmatch + +[FEN_TY1] +Deathmatch stealth + +[FEN_TY2] +Deathmatch a squadre + +[FEN_TY3] +Deathmatch stealth a squadre + +[FEN_TY4] +Accumula denaro + +[FEN_TY5] +Cattura la bandiera + +[FEN_TY6] +Rat Race + +[FEN_TY7] +Dominazione + +[FEN_NAM] +Nome: + +[FEN_GNA] +Nome partita: + +[FEM_MAP] +Seleziona mappa + +[FEN_PLS] +Impostazioni giocatore + +[FEN_PLC] +Colore giocatore + +[FEM_MA0] +Città di Liberty + +[FEM_MA1] +Luci rosse + +[FEM_MA2] +Chinatown + +[FEM_MA3] +La torre + +[FEM_MA4] +Le fogne + +[FEM_MA5] +Area industriale + +[FEM_MA6] +Porto + +[FEM_MA7] +Staunton + +[FEC_EMS] +Un tasto può essere assegnato a un solo comando! + +[FEC_DBG] +Menu debug + +[FEC_TGD] +Pad game/Debug + +[FEC_TDO] +Disabilita visuale debug + +[FEC_IVH] +Inverti mouse orizzontalmente + +[FEC_MSL] +PULSANTE SX + +[FEC_MSM] +PULSANTE CEN + +[FEC_MSR] +PULSANTE DX + +[FEC_QUE] +??? + +[FEC_TWO] +Un massimo di due tasti per comando. + +[FEC_UMS] +Un pulsante del mouse può essere assegnato a un solo comando! + +[FEC_OMS] +Solo un pulsante del mouse. + +[FEC_UJS] +Un pulsante del joystick può essere usato per un solo comando! + +[FEC_OJS] +Solo un pulsante del joystick. + +[FEC_PTL] +Usa Aggancia bersaglio con Arma precedente + +[FEC_PTR] +Usa Aggancia bersaglio con Arma successiva + +[FEC_LBC] +Usa Guarda a sinistra con Guarda a destra + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Pausa non disponibile in modalità multigiocatore. Premi ancora per uscire. + +[FEM_SL1] +Blocco 1 libero + +[FEM_SL2] +Blocco 2 libero + +[FEM_SL3] +Blocco 3 libero + +[FEM_SL4] +Blocco 4 libero + +[FEM_SL5] +Blocco 5 libero + +[FEM_SL6] +Blocco 6 libero + +[FEM_SL7] +Blocco 7 libero + +[FEM_SL8] +Blocco 8 libero + +[FEM_MM] +MENU PRINCIPALE + +[FEM_SNG] +Nuova partita + +[FEM_QTW] +Esci + +[FEQ_SRE] +Sei sicuro di voler uscire? Tutti i progressi dall'ultimo salvataggio verranno persi. Vuoi procedere? + +[FEQ_SRW] +Sei sicuro di voler uscire dal gioco? + +[FEG_SRV] +SERVER + +[FEG_MAP] +MAPPA + +[FEG_PLY] +GIOCATORI + +[FEG_TYP] +MODALITÀ + +[FEG_PNG] +PING + +[FET_FG] +TROVA PARTITA + +[FET_SP] +GIOCATORE SINGOLO + +[FET_MP] +MULTIGIOCATORE + +[FET_HG] +OSPITA PARTITA + +[FET_PS] +IMPOSTAZIONI GIOCATORE + +[FET_CON] +CONNESSIONE + +[FET_AUD] +IMPOSTAZIONI AUDIO + +[FET_DIS] +IMPOSTAZIONI VIDEO + +[FET_LAN] +IMPOSTAZIONI LINGUA + +[FET_LG] +CARICA PARTITA + +[FET_DG] +ELIMINA PARTITA + +[FET_NG] +NUOVA PARTITA + +[FET_SG] +SALVA PARTITA + +[FET_MAP] +SELEZIONA MAPPA + +[FET_GT] +MODALITÀ DI GIOCO + +[FET_CTL] +IMPOSTAZIONI DEI COMANDI + +[FET_OPT] +OPZIONI + +[FET_QG] +ESCI DAL GIOCO + +[FET_STA] +STATISTICHE + +[FET_BRE] +MISSIONI + +[FEC_WAR] +Attenzione + +[FEC_OKK] +OK + +[FED_CON] +Conferma eliminazione file + +[FES_SSC] +Partita salvata con successo + +[DEL_FNM] +File eliminato con successo. + +[PCLOAD] +Caricamento dati in corso + +[PCRESRT] +Riavvio di Grand Theft Auto III + +[FEC_DLF] +Eliminazione fallita. + +[FEC_SVU] +Salvataggio fallito. + +[FEC_LUN] +Caricamento fallito. File corrotto: è necessario eliminarlo. + +[FEN_PLA] +Numero di giocatori: + +[FET_NON] +NESSUNA PARTITA DISPONIBILE + +[FET_SFG] +RICERCA DI PARTITE... + +[FET_SRT] +CLASSIFICAZIONE PARTITE... + +[FEF_LAN] +LAN + +[FEF_INT] +INTERNET + +[FET_REF] +Aggiorna + +[FET_FIL] +Filtra + +[FET_JG] +Partecipa + +[FEC_NTW] +Talk To Network + +[FEC_ESR] +Il tasto ESC è riservato + +[FEC_GSL] +Show head bob: + +[FIL_FLT] +FILTRA ELENCO PARTITE + +[FET_SAN] +NUOVA PARTITA + +[FIL_MAP] +Mappa: + +[FIL_SRV] +Server: + +[FIL_TYP] +Modalità: + +[FIL_SPC] +Partite non piene? + +[FIL_PNG] +Ping: + +[FEN_UKH] +Host sconosciuto + +[FEN_UKM] +Mappa non trovata + +[FEN_UKT] +Modalità non trovata + +[FEN_NCI] +NON CONNESSO A INTERNET + +[FET_PAU] +MENU PAUSA + +[FET_SGA] +AVVIA PARTITA + +[FEC_SGJ] +Imposta joystick + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FEC_WHL] +Volante + +[FEC_CNT] +Periferica di controllo: + +[FET_APL] +APPLICA + +[FES_CSA] +Seleziona una skin dalla seguente lista: + +[FES_SKN] +NOME SKIN + +[FES_DAT] +DATA + +[FES_NON] +NESSUNA SKIN DISPONIBILE + +[FEA_FM9] +RIPRODUTTORE MP3 + +[FESZ_QZ] +Sei sicuro di voler salvare la partita? + +[FES_CGA] +Blocchi di gioco disponibili: + +[FES_SCG] +Vuoi salvare la partita attuale? + +[FES_LCG] +Vuoi caricare e continuare la partita? + +[FEC_FIR] +Fuoco + +[FEC_NWE] +Arma successiva + +[FEC_PWE] +Arma precedente + +[FEC_FOR] +Avanti + +[FEC_BAC] +Indietro + +[FEC_LEF] +Sinistra + +[FEC_RIG] +Destra + +[FEC_ZIN] +Ingrandimento + +[FEC_ZOT] +Riduzione + +[FEC_EEX] +Entra/Esci + +[FEC_RAD] +Radio + +[FEC_SUB] +Sottomissioni + +[FEC_CMR] +Cambia visuale + +[FEC_JMP] +Salto + +[FEC_SPN] +Scatto + +[FEC_HND] +Freno a mano + +[FEC_TUL] +Torretta a sx + +[FEC_TUR] +Torretta a dx + +[FEC_LOL] +Guarda a sinistra + +[FEC_LOR] +Guarda a destra + +[FEC_NTR] +Bersaglio successivo + +[FEC_PTT] +Bersaglio precedente + +[FEC_LBA] +Guarda indietro + +[FEC_CEN] +Centra visuale + +[FEC_UND] +(NO) + +[FET_CFT] +A PIEDI + +[FET_CCR] +IN MACCHINA + +[CVT_MSG] +Conversione texture per prestazioni ottimali con la tua scheda video + +[FET_CAC] +AZIONE + +[FEC_IBT] +- + +[FEC_SPC] +BARRA + +[FEC_MXO] +P1MX + +[FEC_MXT] +P2MX + +[FEC_UNB] +NESSUNO + +[FET_CME] +METODO DI CONTROLLO + +[FET_RDK] +RIDEFINISCI COMANDI + +[FET_AMS] +IMPOSTAZIONI MOUSE + +[FET_STI] +CONFIGURAZIONE PREDEFINITA + +[FET_CTI] +CONFIGURAZIONE CLASSICA + +[FET_MTI] +CONFIGURAZIONE COL MOUSE + +[FET_DAM] +MODELLAZIONE ACUSTICA DINAMICA + +[FEC_TFL] +Torretta a sinistra + +[FEC_TFR] +Torretta a destra + +[FEC_TFU] +Torretta /Aereo su + +[FEC_TFD] +Torretta /Aereo giù + +[FEC_MWF] +VOLANTE SU + +[FEC_MWB] +VOLANTE GIÙ + +[FEC_ORR] +o + +[FEC_NUS] +NON USATO + +[FEC_LUD] +Sguardo su + +[FEC_LDU] +Sguardo giù + +[FEC_CMP] +COMBO: SGUARDO S+D + +[FEC_NTT] +Nessun testo assegnato a questo tasto + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INS + +[FEC_DLL] +CANC + +[FEC_HME] +HOME + +[FEC_END] +FINE + +[FEC_PGU] +PAG SU + +[FEC_PGD] +PAG GIÙ + +[FEC_UPA] +SU + +[FEC_DWA] +GIÙ + +[FEC_LFA] +SINISTRA + +[FEC_RFA] +DESTRA + +[FEC_NUM] +NUM + +[FEC_NMN] +NUM~1~ + +[FEC_FWS] +NUM / + +[FEC_PLS] +NUM + + +[FEC_MIN] +NUM - + +[FEC_DOT] +NUM . + +[FEC_NLK] +BLOC NUM + +[FEC_ETR] +INVIO + +[FEC_SLK] +BLOC SCORR + +[FEC_PSB] +PAUSA + +[FEC_BSP] +INDIETRO + +[FEC_TAB] +TAB + +[FEC_CLK] +BLOC MAIUSC + +[FEC_RTN] +RET + +[FEC_LSF] +MAIUSC SX + +[FEC_RSF] +MAIUSC DX + +[FEC_LCT] +CTRL SX + +[FEC_RCT] +CTRL DX + +[FEC_LAL] +ALT SX + +[FEC_RAL] +ALT DX + +[FEC_LWD] +WIN SX + +[FEC_RWD] +WIN DX + +[FEC_WRC] +CLIC WIN + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Grand Theft Auto III non è supportato da Windows 95 + +[WIN_DX] +Grand Theft Auto III richiede DirectX versione 8.1 o superiore + +[WIN_VDM] +Grand Theft Auto III richiede almeno 12 MB di memoria video disponibile + +[DIAB3_G] +Arriba! + +[FEM_RES] +RIPRENDI PARTITA + +[FES_SNG] +NUOVA PARTITA + +[FEM_SP] +GIOCATORE SINGOLO + +[FEM_MP] +MULTIGIOCATORE + +[FEM_QT] +ESCI DAL GIOCO + +[FES_SG] +NUOVA PARTITA + +[FES_LG] +CARICA PARTITA + +[FEM_HST] +OSPITA PARTITA + +[FEM_OPT] +OPZIONI + +[FEM_DBG] +DEBUG + +[FET_PSU] +IMPOSTAZIONI GIOCATORE + +[FET_DEF] +RIPRISTINA PREDEFINITI + +[FED_BRI] +LUMINOSITÀ + +[FED_TRA] +TRACCE + +[FEM_LOD] +DISTANZA VISUALE + +[FEM_VSC] +SINCRONIA FRAME + +[FEM_FRM] +LIMITATORE FRAME + +[FED_RES] +RISOLUZIONE + +[FED_WIS] +SCHERMO 16:9 + +[FEDS_TB] +INDIETRO + +[FEA_MUS] +VOLUME MUSICA + +[FEA_SFX] +VOLUME EFFETTI + +[FEA_RSS] +STAZIONE RADIO + +[FEL_ENG] +INGLESE + +[FEL_FRE] +FRANCESE + +[FEL_GER] +TEDESCO + +[FEL_ITA] +ITALIANO + +[FEL_SPA] +SPAGNOLO + +[FEA_3DH] +HARDWARE AUDIO + +[FEA_SPK] +CONFIGURAZIONE ALTOPARLANTI + +[FEA_2SP] +DUE CASSE + +[FEA_4SP] +PIÙ DI DUE CASSE + +[FEA_EAR] +CUFFIE + +[FEA_NAH] +NESSUN HARDWARE AUDIO + +[FET_SNG] +NUOVA PARTITA + +[FEN_STA] +AVVIA LA PARTITA + +[GMLOAD] +CARICA PARTITA + +[GMSAVE] +SALVA PARTITA + +[FES_DGA] +ELIMINA PARTITA + +[FEM_NON] +NESSUNO + +[FEC_IVV] +INVERTI MOUSE VERTICALMENTE + +[FEC_MSH] +SENSIBILITÀ MOUSE + +[FET_CCN] +COMANDI: CLASSICO + +[FET_SCN] +COMANDI: PREDEFINITO + +[FES_SET] +USA SKIN + +[GHOST] +Fantasma + +[WIN_RSZ] +Impossibile impostare la nuova risoluzione + +[FET_APP] +PULSANTE SX, INVIO PER APPLICARE LE NUOVE IMPOSTAZIONI + +[FET_HRD] +IMPOSTAZIONI PREDEFINITE RIPRISTINATE + +[FET_MST] +STERZO VIA MOUSE + +[FEC_STR] +NUM ASTERISCO + +[FET_MIG] +SINISTRA, DESTRA, ROTELLA PER MODIFICARE + +[FET_CIG] +INDIETRO PER AZZERARE - PULSANTE SX, INVIO PER MODIFICARE + +[FET_RIG] +SELEZIONA UN NUOVO COMANDO PER QUESTA AZIONE O PREMI ESC PER ANNULLARE + +[FET_EIG] +IMPOSSIBILE ASSEGNARE UN COMANDO A QUESTA AZIONE + +[NO_PCCD] +Inserisci il disco 2 di Grand Theft Auto III nel lettore o premi ESC per annullare + +[CVT_ERR] +Spazio su disco esaurito; è necessario liberare dello spazio prima di poter procedere. Premi ESC per annullare. + +[FED_SUB] +SOTTOTITOLI + +[FET_DSN] +Skin predefinita.bmp + +[JM3] +'RAPINA AL FURGONE' + +[EBAL] +'GIVE ME LIBERTY' + +[LM4] +'ARMI PRONTE ALL'AZIONE' + +[ATUTOR2] +~g~Porta i pazienti all'ospedale! Qualsiasi scossone ridurrà le probabilità che arrivino vivi. + +[REPLAY] +REPLAY + +[FEC_SFT] +MAIUSC + +[CRED254] +RESPONSABILE STUDIO + +[CVT_CRT] +Impossibile convertire le texture per la scheda video. Deve collegarti come Amministratore per poterlo fare. Premi ESC per uscire. + +[FEM_ON] +ON + +[FEM_OFF] +OFF + +[FEM_YES] +SÌ + +[FEM_NO] +NO + +[FES_WAR] +Salvataggio, attendere... + +[FED_DLW] +Eliminazione, attendere... + +[FED_LDW] +Caricamento, attendere... + +[FEC_SLC] +Slot non valido + +[FED_LFL] +Caricamento posizione salvata fallito. Il gioco verrà riavviato. + +[FET_RSO] +RIPRISTINATE IMPOSTAZIONI ORIGINALI + +[FET_RSC] +HARDWARE NON DISPONIBILE - RIPRISTINATE IMPOSTAZIONI ORIGINALI + +{ re3 updates } +{ new languages } +[FEL_JAP] +GIAPPONESE + +[FEL_POL] +POLACCO + +[FEL_RUS] +RUSSO + +{ new display menus } +[FET_GFX] +IMPOSTAZIONI GRAFICA + +[FED_MIP] +MIP MAPPING + +[FED_AAS] +ANTI ALIASING + +[FED_FIL] +TEXTURE FILTERING + +[FED_BIL] +BILINEAR + +[FED_TRL] +TRILINEAR + +[FED_WND] +WINDOWED + +[FED_FLS] +FULLSCREEN + +[FEM_CSB] +CUTSCENE BORDERS + +[FEM_SCF] +SCREEN FORMAT + +[FEM_ISL] +MAP MEMORY USAGE + +[FEM_LOW] +LOW + +[FEM_MED] +MEDIUM + +[FEM_HIG] +HIGH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +FREE CAM + +{ Linux joy detection } +[FEC_JOD] +DETECT JOYSTICK + +[FEC_JPR] +Press any key on the joystick of your choice that you want to use on the game, and it will be selected. + +[FEC_JDE] +Detected joystick + +{ mission restart } +[FET_RMS] +RIGIOCA MISSIONE + +[FESZ_RM] +RIGIOCA? + +{ more graphics } +[FED_VPL] +VEHICLE PIPELINE + +[FED_PRM] +PED RIM LIGHT + +[FED_RGL] +ROAD GLOSS + +[FED_CLF] +COLOUR FILTER + +[FED_WLM] +WORLD LIGHTMAPS + +[FED_MBL] +MOTION BLUR + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +{ controls } +[FEC_IVP] +INVERT PAD VERTICALLY + +{ map } +[FEM_TWP] +Toggle Waypoint + +[FEA_FMN] +RADIO SPENTA + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD TYPE + +[FEC_CCF] +CONFIGURAZIONE + +[FEC_CF1] +CONFIGURAZIONE 1 + +[FEC_CF2] +CONFIGURAZIONE 2 + +[FEC_CF3] +CONFIGURAZIONE 3 + +[FEC_CF4] +CONFIGURAZIONE 4 + +[FEC_CDP] +SCHERMATA CONTROLLER + +[FEC_ONF] +A PIEDI + +[FEC_INC] +IN MACCHINA + +[FEC_VIB] +VIBRAZIONE + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/polish.txt b/utils/gxt/polish.txt new file mode 100644 index 0000000..84b1d00 --- /dev/null +++ b/utils/gxt/polish.txt @@ -0,0 +1,8117 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789'$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~Hej! Wracaj do samochodu! + +[IN_VEH2] +~g~Do tej roboty jest ci potrzebna jakaś gablota! + +[IN_BOAT] +~g~Do tej roboty jest ci potrzebna łódź! + +[HEY] +~g~Nie idź sam, trzymaj się całej paczki! + +[HEY2] +~g~Nie rozdzielajcie się, niech cała grupa porusza się razem! + +[HEY3] +~g~Straciłeś z oczu swojego podopiecznego - wracaj i odszukaj 8-Balla! + +[HEY4] +~g~Jeżeli stracisz Misty, to Luigi zadba, abyś stracił życie! Wracaj po nią! + +[HEY5] +~g~One of the girls is AWOL, Go back and round her up! + +[HEY6] +~g~Twój honor jest związany z osobą Kanbu z Yakuzy. Musisz go chronić! + +[HEY7] +~g~Przydałaby się dodatkowa spluwa. Wracaj i zabierz ze sobą swój kontakt! + +[HEY8] +~g~Ochrona oznacza właśnie to, co podejrzewasz - ochraniaj starego dżentelmena z dalekiego wschodu! + +[HEY9] +~g~Chcesz posłuchać, co się dzieje w mieście? Odszukaj swój kontakt! + +[HELP2_A] +Jeżeli w trakcie biegu chcesz ~h~przyspieszyć~h~, naciśnij klawisz ~h~/~w~. + +[HELP3] +Sprintem można pokonywać wyłącznie krótkie odcinki, dopóki postać ma zapas sił. + +[HELP4_A] +Aby ~h~przyspieszyć~w~, naciśnij klawisz ~h~ ~k~~VEHICLE_ACCELERATE~~w~. + +[HELP4_D] +Aby ~h~przyspieszyć~w~, popchnij ~h~prawy drążek analogowy~w~ do góry. + +[HELP5_A] +Naciśnij klawisz ~h~ ~k~~VEHICLE_BRAKE~~w~, aby ~h~zahamować~w~ lub włączyć ~h~wsteczny bieg~w~, jeżeli pojazd już stoi. + +[HELP5_D] +Pociągnij ~h~prawy drążek analogowy~w~ do tyłu, aby ~h~zahamować~w~ lub włączyć ~h~wsteczny bieg~w~, jeżeli pojazd już stoi. + +[HELP6_A] +Aby skorzystać z ~h~hamulca ręcznego~w~, naciśnij klawisz ~h~ ~k~~VEHICLE_HANDBRAKE~~w~. + +[HELP6_C] +Aby skorzystać z ~h~hamulca ręcznego~w~, naciśnij klawisz ~h~ ~k~~VEHICLE_HANDBRAKE~~w~. + +[HELP6_D] +Aby skorzystać z ~h~hamulca ręcznego~w~, naciśnij klawisz ~h~ ~k~~VEHICLE_HANDBRAKE~~w~. + +[HELP7_A] +Aby ~h~wycelować~w~ karabin snajperski, naciśnij i przytrzymaj klawisz~h~ ~k~~PED_LOCK_TARGET~~w~. + +[HELP7_D] +Aby ~h~wycelować~w~ karabin snajperski, naciśnij i przytrzymaj klawisz~h~ ~k~~PED_LOCK_TARGET~~w~. + +[HELP8_A] +Naciśnij klawisz~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, aby ~h~przybliżyć ~w~widok przez lunetkę karabinu oraz klawisz~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, aby ~h~oddalić~w~ widok. + +[HELP9_A] +Naciśnij klawisz~h~ ~k~~PED_FIREWEAPON~~w~, aby oddać ~h~strzał~w~ z karabinu snajperskiego. + +[HELP10] +Gwiazda szeryfa oznacza, że jesteś poszukiwany przez policję. + +[HELP11] +Im więcej gwiazdek, tym wyższy jest poziom twojej złej sławy. + +[HELP13] +Niekiedy trzeba będzie skorzystać z tras, które nie są zaznaczone na radarze. + +[TIMER] +Jest to misja na czas - musisz ją wykonać, zanim licznik czasu osiągnie zero. + +[MISTY1] +~r~Misty nadaje się już tylko na łóżko w kostnicy! + +[OUT_VEH] +~g~Wysiądź z samochodu! + +[GARAGE] +Wprowadź samochód do garażu, a potem wyjdź na zewnątrz. + +[WANTED1] +~g~Zgub gliniarzy i obniż swój poziom złej sławy! + +[NODOORS] +~g~To nie są sardynki! Załatw furę, która pomieści więcej osób. + +[TRASH] +~g~Doprowadziłeś gablotę do ruiny! Połataj ją trochę! + +[WRECKED] +~r~Samochód jest zniszczony! + +[HORN] +~g~Naciśnij klakson. + +[NOMONEY] +~g~Masz za mało forsy! + +[OUTTIME] +~r~Za wolno, koleś, za wolno! + +[SPOTTED] +~r~Gonią cię! + +[REWARD] +NAGRODA $~1~ + +[GAMEOVR] +KONIEC GRY + +[Z] +Wartość osi Z: ~1~ + +[M_FAIL] +MISJA NIEUDANA! + +[M_PASS] +MISJA ZALICZONA! $~1~ + +[O_PASS] +ROBOTA ZAKOŃCZONA + +[O_FAIL] +ROBOTA NIEUDANA + +[DEAD] +KONIEC Z TOBĄ! + +[BUSTED] +WPADKA! + +[S_PROMP] +Kiedy nie jesteś w trakcie misji, możesz w tym miejscu ~h~zapisać stan gry~w~. Wiąże się to z upływem sześciu godzin w grze. + +[NUMBER] +~1~ + +[SCORE] +$~1~ + +[LOADCAR] +ŁADOWANIE POJAZDU (NACIŚNIJ L1, ABY ANULOWAĆ) + +[CARSOFF] +Wyłączone samochody: + +[CARS_ON] +Uruchomione samochody: + +[TEXTXYZ] +Zapisywanie współrzędnych w pliku... + +[CHEATON] +Tryb ułatwień WŁĄCZONY + +[CHEATOF] +Tryb ułatwień WYŁĄCZONY + +[UZI_IN] +Amu-Nacja zaczyna sprzedawać uzi! + +[IMPORT1] +Wyjdź na zewnątrz i poczekaj na swój samochód. + +[PAGEB1] +Pistolet dostarczony do kryjówki + +[PAGEB2] +Uzi dostarczone do kryjówki + +[PAGEB3] +Pancerz dostarczony do kryjówki + +[PAGEB4] +Obrzyn dostarczony do kryjówki + +[PAGEB5] +Granaty dostarczone do kryjówki + +[PAGEB6] +Koktajle Mołotowa dostarczone do kryjówki + +[PAGEB7] +AK 47 dostarczony do kryjówki + +[PAGEB8] +Karabin snajperski dostarczony do kryjówki + +[PAGEB9] +M16 dostarczony do kryjówki + +[PAGEB10] +Wyrzutnia rakiet dostarczona do kryjówki + +[PAGEB11] +Miotacz ognia dostarczony do kryjówki + +[WANT_A] +Aresztowanie grozi ci dopiero wtedy, kiedy posiadasz ~h~złą sławę. + +[WANT_B] +Twój ~h~poziom złej sławy~w~ przedstawia rząd gwiazdek znajdujących się w prawym górnym rogu ekranu. + +[WANT_C] +W tym momencie twój ~h~poziom złej sławy~w~ wynosi jeden... + +[WANT_D] +dwa... + +[WANT_E] +trzy... + +[WANT_F] +W miarę wzrostu ~h~złej sławy~w~ ścigać cię będą coraz potężniejsze siły policyjne. + +[WANT_G] +Kiedy zaliczysz ~h~'wpadkę'~w~, zostajesz odwieziony na najbliższy posterunek policji. + +[WANT_H] +Gliniarze zarekwirują ci całą broń i będziesz musiał wypłacić im małą łapówkę. + +[WANT_I] +Misja, którą właśnie wykonywałeś, zostanie uznana za nieudaną. + +[WANT_J] +W miarę rozwoju gry odkryjesz sposoby zmniejszania swojego poziomu złej sławy. + +[WANT_K] +Jeżeli jesdziesz samochodem, ~h~WARSZTATY LAKIERNICZE~w~ umożliwiają ~h~usunięcie złej sławy. + +[HEAL_B] +Kiedy zostajesz ~h~'załatwiony'~w~, trafiasz do najbliższego szpitala. + +[HEAL_C] +Tracisz całą broń oraz musisz zapłacić lekarzom trochę forsy za ich wysiłki. + +[HEAL_E] +W trakcie gry poznasz rozmaite metody leczenia bądź chronienia głównego bohatera. + +[DAM] +USZKODZENIA: + +[KILLS] +OFIARY: + +[FARES] +PRZEJAZDY: + +[BULL] +SZTABKI: + +[EVID] +DOWODY: + +[HEALTH] +STAN POJAZDU: + +[COLLECT] +ZEBRANO: + +[BOMB] +Wprowadź samochód do garażu, aby przymocować do niego ~h~bombę~w~. Cena - ~h~$1000. + +[SAVE1] +Aby ~h~zapisać stan gry~w~, przejdź przez drzwi. Jeżeli jesteś w trakcie misji, nie można zapisać gry. + +[SAVE2] +Samochody pozostawione w tym garażu zostaną zachowane wraz z zapisem stanu gry. + +[AMMU] +Wejdź do Amu-nacji, aby zakupić broń. + +[BRIDGE1] +Kiedy naprawa Mostu Callahan zostanie ukończona, będziesz mógł przejechać nim na Wyspę Staunton. + +[TUNNEL] +Kiedy Tunel Porter zostanie otwarty, będziesz mógł przejechać na Wyspę Staunton. + +[LUIGI] +MISJE LUIGIEGO + +[TONI] +MISJE TONIEGO + +[JOEY] +MISJE JOEYA + +[FRANK] +MISJE SALVATORE + +[DIABLO] +MISJE DIABLO + +[ASUKA] +MISJE ASUKI + +[B_SITE] +MISJE PODMIEJSKIE ASUKI + +[KENJI] +MISJE KENJIEGO + +[RAY] +MISJE RAYA + +[LOVE] +MISJE LOVE'A + +[YARDIE] +MISJE DLA GANGU YARDIE + +[HOOD] +MISJE DLA GANGU HOOD + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Atlantic Quays + +[PORT_E] +Portland Harbor + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Plaza Portland + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Dz. Czerwonych Świateł + +[TOWERS] +Wzgórza Hepburn + +[HARWOOD] +Harwood + +[ROADBR1] +Most Callahan + +[ROADBR2] +Most Callahan + +[TUNNELP] +Tunel Porter + +[BOMB1] +Warsztat 8-Balla. + +[COM_ZON] +Wyspa Staunton + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Kampus Liberty + +[CONSTRU] +Fort Staunton + +[PARK] +Park Belleville + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Port Lotniczy Francis + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Tama Cochrane + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Karetka + +[CAR_2] +Straż pożarna + +[CAR_3] +Radiowóz + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Hipcio + +[CAR_7] +Samochód FBI + +[CAR_8] +Konwojowóz + +[CAR_9] +Moonbeam + +[CAR_10] +Autokar + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Śmieciożer + +[CAR_14] +Patriot + +[CAR_15] +Pan Smakołyk + +[CAR_16] +Muł + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Autobus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Demon + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Ogier + +[CAR_37] +Taksówka + +[CAR_38] +Taksówka + +[CAR_39] +Buggy + +[LUIGIS] +Lokal Luigiego + +[GOAWAY] +~g~Już podjąłeś się jednej misji! + +[LUIGGO] +~g~Luigi sprawdza nowe dziewczyny. Przyjdź później! + +[JOEYGO] +~g~Joey wyszedł do miasta z Misty. Przyjdź później. + +[TONIGO] +~g~Toni zabrał swoją Mamuśkę do opery - wpadnij kiedy indziej! + +[KEMUGO] +~g~Maria i Kemuri mają chwilowo inne obowiązki - wpadnij później! + +[KENJGO] +~g~Kenji jest na naradzie Yakuzy. Wpadnij innym razem! + +[RAYGO] +~g~Ray kręci się przy innych kiblach - wpadnij później! + +[LOVEGO] +~g~Donald Love chwilowo zajmuje się innymi sprawami. Umów się z nim na późniejszą godzinę! + +[KENSGO] +~g~Kenji jest zajęty! Wpadnij później! + +[HOODGO] +~g~Hoods są aktualnie niedostępni! + +[WRONGT1] +~g~Jeżeli szukasz zajęcia, wróć między 5:00 a 21:00. + +[WRONGT2] +~g~Jeżeli szukasz zajęcia, wróć między 6:00 a 14:00. + +[WRONGT3] +~g~Jeżeli szukasz zajęcia, wróć między 15:00 a 00:00. + +[GUN_1A] +Użyj klawiszy ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ oraz ~h~~k~~PED_CYCLE_WEAPON_LEFT~ ~w~, aby przełączać się między kolejnymi rodzajami broni. + +[GUN_2A] +Przytrzymaj klawisz ~h~~k~~PED_LOCK_TARGET~ ~w~, aby ~h~wykonać autocelowanie~w~ i naciśnij klawisz ~h~ ~k~~PED_FIREWEAPON~ ~w~, aby ~h~otworzyć ogień! Spróbuj postrzelać do celu... + +[GUN_2C] +Przytrzymaj klawisz ~h~~k~~PED_LOCK_TARGET~ ~w~, aby ~h~wykonać autocelowanie~w~ i naciśnij klawisz ~h~ ~k~~PED_FIREWEAPON~ ~w~, aby ~h~otworzyć ogień! Spróbuj postrzelać do celu... + +[GUN_2D] +Przytrzymaj klawisz ~h~~k~~PED_LOCK_TARGET~ ~w~, aby ~h~wykonać autocelowanie~w~ i naciśnij klawisz ~h~ ~k~~PED_FIREWEAPON~ ~w~, aby ~h~otworzyć ogień! Spróbuj postrzelać do celu... + +[GUN_3A] +Trzymając wciśnięty klawisz ~h~~k~~PED_LOCK_TARGET~,~w~ naciśnij klawisz ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ lub klawisz ~h~~k~~PED_CYCLE_TARGET_RIGHT~ , aby przełączać się między celami. + +[GUN_3B] +Trzymając wciśnięty klawisz ~h~~k~~PED_LOCK_TARGET~,~w~ naciśnij klawisz ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ lub klawisz ~h~~k~~PED_CYCLE_TARGET_RIGHT~ , aby przełączać się między celami. + +[GUN_4A] +Trzymając wciśnięty klawisz ~h~~k~~PED_LOCK_TARGET~~w~, możesz chodzić lub biegać, a celownik cały czas pozostanie zablokowany na wybranym celu. + +[GUN_4B] +Trzymając wciśnięty klawisz ~h~~k~~PED_LOCK_TARGET~~w~, możesz chodzić lub biegać, a celownik cały czas pozostanie zablokowany na wybranym celu. + +[GUN_5] +Możesz przećwiczyć wybieranie celów i prowadzenie ognia na tych papierowych celach. Po treningu wróć do wykonywania misji. + +[TAXI1] +~g~Poszukaj pasażera. + +[FARE1] +~g~Cel podróży ~w~'Klub Seksownego Kociaka Miauu'~g~ w Dzielnicy Czerwonych Świateł. + +[FARE2] +~g~Cel podróży ~w~Super Przecena~g~ w Portland View. + +[FARE3] +~g~Cel podróży ~w~'stara szkoła'~g~ w Chinatown. + +[FARE4] +~g~Cel podróży ~w~'kawiarenka Tłustego Joe'~g~ w Callahan Point. + +[FARE5] +~g~Cel podróży ~w~'Amu-Nacja'~g~ w Dzielnicy Czerwonych Świateł. + +[FARE6] +~g~Cel podróży ~w~'Auta na Kredyt'~g~ w Saint Mark's. + +[FARE7] +~g~Cel podróży ~w~'bar topless 'U Woody'ego''~g~ w Dzielnicy Czerwonych Świateł. + +[FARE8] +~g~Cel podróży ~w~'Bistro Marcos'~g~ w Saint Mark's. + +[FARE9] +~g~Cel podróży ~w~'warsztat importowo-eksportowy'~g~ w Portland Harbor. + +[FARE10] +~g~Cel podróży ~w~'Smażony Makaron'~g~ w Chinatown. + +[FARE12] +~g~Cel podróży ~w~'stadion piłkarski'~g~ w Aspatrii. + +[FARE13] +~g~Cel podróży ~w~'kościół'~g~ w Bedford Point. + +[FARE14] +~g~Cel podróży ~w~'kasyno'~g~ w Torrington. + +[FARE15] +~g~Cel podróży ~w~biblioteka Liberty~g~ w Kampusie Liberty. + +[FARE16] +~g~Cel podróży ~w~galeria handlowa~g~ w okolicach Belville Park. + +[FARE17] +~g~Cel podróży ~w~muzeum~g~ w Newport. + +[FARE18] +~g~Cel podróży ~w~siedziba AmCo~g~ w Torrington. + +[FARE19] +~g~Cel podróży ~w~Bolt Burgers~g~ w Bedford Point. + +[FARE20] +~g~Cel podróży ~w~park~g~ w Belville. + +[FARE21] +~g~Cel podróży ~w~Port Lotniczy im. Francisa. + +[FARE22] +~g~Cel podróży ~w~'Tama Cochrane'. + +[FARE24] +~g~Cel podróży ~w~'szpital' ~g~w Pike Creek. + +[FARE25] +~g~Cel podróży ~w~'park'~g~ w Soherside Vale. + +[FARE26] +~g~Cel podróży ~w~'North West Towers'~g~ w Wichita Gardens. + +[NEW_TAX] +WIĘKSZE! SZYBSZE! MOCNIEJSZE! Nowe taksówki Borgnine rozpoczynają pracę w Harwood. Dzwoń już dziś: 555-BORGNINE. + +[TSCORE2] +$~1~ + +[IN_ROW] +~1~Premia za SEKWENCJĘ! $~1~ + +[TTUTOR] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyć misje w taksówce. + +[TTUTOR2] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyć misje taksówkarskie. + +[A_TIME] ++~1~ sekund + +[A_FULL] +~r~Karetka jest pełna! + +[A_RANGE] +~g~Radiostacja w karetce ma zbyt mały zasięg, zbliż się bardziej w stronę szpitala! + +[FTUTOR] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyc misje strażackie. + +[FTUTOR2] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyc misje strażackie. + +[F_PASS1] +Pożar ugaszony! + +[F_RANGE] +~g~Radiostacja w samochodzie strażackim ma zbyt mały zasięg - zbliż się bardziej w stronę remizy! + +[C_BREIF] +~g~Podejrzany ostatnio widziany był w rejonie ~a~. + +[C_RANGE] +~g~Radiostacja w samochodzie ma zbyt mały zasięg, zbliż się bardziej w stronę komisariatu! + +[DODO_FT] +Leciałeś przez ~1~ sekund! + +[EBAL_A] +Znam takie miejsce na skraju Dzielnicy Czerwonych Świateł, w którym możemy się zamelinować na jakiś czas, + +[EBAL_A1] +ale moje ręce są do niczego, więc lepiej ty prowadź, brachu. + +[EBAL_1] +Naciśnij klawisz ~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pojazdu. + +[EBAL_1B] +Naciśnij klawisz ~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pojazdu. + +[EBAL_2] +~g~Wracaj do samochodu! + +[EBAL_3] +To jest ~h~radar~w~. Korzystaj z niego podczas poruszania się po mieście. Jedź za ~h~kropką~w~ na ~h~radarze~w~, aby dotrzeć do kryjówki! + +[EBAL_D] +Jest tu taki jeden gość, który zna kogo trzeba. Nazywa się Luigi. + +[EBAL_D1] +Niejedno razem przeszliśmy. Dla ciebie też pewnie znajdzie się jakaś robota. Zajrzyjmy do niego! + +[EBAL_E] +Wpadniemy tam, a ja przedstawię cię komu trzeba. + +[EBAL_I] +Szef zaraz wyjdzie do ciebie... + +[EBAL_J] +8-Ball ma jakiś interes na górze. + +[EBAL_K] +Może zrobisz coś dla mnie? + +[EBAL_L] +Trzeba podwieźć jedną z moich dziewczynek. Załatw gablotę i odbierz Misty z kliniki. Przywieź ją tutaj. + +[EBAL_N] +I lepiej cały czas gap się tylko na drogę! + +[EBAL_4] +~r~8-Ball nie żyje~! + +[EBAL_5] +~g~Załatw samochód! + +[EBAL_6] +~g~Zabierz Misty! + +[LM1] +'DZIEWCZYNKI LUIGIEGO' + +[LM2] +'MOJE DZIWKI NIE ĆPAJĄ!' + +[LM3] +'WOŻĄC PANNĘ MISTY' + +[LM5] +'ACH, CÓŻ TO BYŁ ZA BAL...' + +[LM1_2] +~g~Zabierz Misty do klubu 'U Luigiego'. + +[LM1_3] +~g~Naciśnij klakson, aby zaprosić dziewczynę do samochodu. + +[LM1_6] +~g~Wracaj do samochodu! + +[LM1_7] +Zatrzymaj samochód obok Misty i poczekaj, aż wsiądzie. + +[LM1_8] +Możesz wrócić do Luigiego i zapytać go o pracę albo pozwiedzać Liberty City. + +[LM2_A] +Na ulicy pojawił się nowy towar, HEROINA. + +[LM2_E] +Jakiś mądrala wciska ten syf moim dziewczynkom na Portland Harbor. + +[LM2_B] +Jedź tam i zapoznaj jego twarz z bejsbolem! + +[LM2_G] +Należy mi się jakaś rekompensata za tę zniewagę! + +[LM2_1] +~g~Weź jego samochód i przemaluj go. + +[LM2_2A] +Użyj klawisza ~h~ ~k~~PED_FIREWEAPON~~w~, aby zadawać ciosy ~h~pięścią ~w~i ~h~nogą~w~ lub ~h~uderzyć kijem ~w~! + +[LM2_2C] +Użyj klawisza ~h~ ~k~~PED_FIREWEAPON~~w~, aby zadawać ciosy ~h~pięścią ~w~i ~h~nogą~w~ lub ~h~uderzyć kijem ~w~! + +[LM2_2D] +Użyj klawisza ~h~ ~k~~PED_FIREWEAPON~~w~, aby zadawać ciosy ~h~pięścią ~w~i ~h~nogą~w~ lub ~h~uderzyć kijem ~w~! + +[LM2_3] +~g~Schowaj samochód w kryjówce Luigiego! + +[LM2_4] +~g~Przemaluj samochód! + +[LM3_A] +Ej, muszę z tobą pogadać... Spoko, Mick, dokończymy później. + +[LM3_B] +Jak leci, młody? + +[LM3_C] +Syn Dona, Joey, chce się zabawić ze swoją ulubioną dziewczynką, Misty. + +[LM3_D] +Jedź po nią na Wzgórza Hepburn... + +[LM3_E] +ale uważaj, to terytorium gangu Diablo. + +[LM3_F] +Potem odwieź ją do warsztatu Joeya w Trenton, byle szybko. + +[LM3_H] +więc gap się na drogę, a nie na cycki Misty! + +[LM3_2] +~g~Zabierz Misty do Joeya. + +[LM3_4] +~g~Jedź po Misty! + +[LM3_5] +To ty pracujesz teraz jako szoferak dla Luigiego, co? Najwyższy czas, od dawna potrzebujemy zaufanego kierowcy! + +[LM3_7] +Zajmę się tobą za chwilkę, iskierko. + +[LM3_10] +~g~Zdobądź samochód! + +[LM4_B] +Jedź tam i załatw dla mnie ten problem. + +[LM4_C] +Jeżeli potrzebujesz spluwy, wpadnij na zaplecze sklepu Amu-Nacja, naprzeciwko stacji metra. + +[LM5_A] +Przy Moście Callahan, w budynku starej szkoły trwa Bal Policjanta. + +[LM5_B] +Skoro budynek jest stary, to i goście zapewne będą szukać rozrywek 'w starym stylu'. + +[LM5_C] +Moje dziewczyny pracują na ulicach w całym mieście. + +[LM5_D] +Zawieź je na bal, aby mogły solidnie popracować. + +[LM5_1] +~g~Jeżeli zabierzesz za dużo dziewczyn, to się poobijają w środku!~g~ Najpierw wysadź te, które już masz, potem wróć po następne. + +[LM5_2] +~r~Jedna z dziewczyn Luigiego jest już tylko kupą padliny! + +[LM5_3] +~g~Jest ci potrzebny samochód! + +[LM5_4] +~g~Zgarnij dziewczyny pracujące w St. Mark's. + +[LM5_5] +~g~Zawieź dziewczyny na Bal Policjanta! + +[LM5_8] +~g~Dziewczyny na balu: ~1~ + +[JM2] +'ŻEGNAJ 'OKRĄGŁY' LEE CHONG' + +[JM4] +'SZOFER CIPRIANIEGO' + +[JM5] +'TRUP W BAGAŻNIKU' + +[JM1_1] +~g~Zabierz samochód Forelliego do warsztatu 8-Balla na północ stąd, zaraz za salonem 'Auta na Kredyt'. + +[JM1_2] +~g~Potem zaparkuj wózek z powrotem na właściwym miejscu pod Bistro Marcos. + +[JM1_3] +~g~Uaktywnij bombę i spadaj stamtąd! + +[JM1_4] +~g~Niszczysz samochód! Napraw go! + +[JM1_5] +~g~Bomba w samochodzie nie została uzbrojona! + +[JM1_6] +~g~Zaparkuj samochód we właściwej pozycji. + +[JM1_8A] +~y~Hej, przecież to mój kumpel! + +[JM1_8B] +~y~Warsztat jest zautomatyzowany. Po prostu wjedź do środka, zatrzymaj samochód, a obsługa załatwi całą resztę. + +[JM1_8C] +~y~Pierwszy raz montujemy ładunek za darmo, ale za każdym następnym razem będziesz musiał zapłacić. + +[JM2_A] +'Okrągły' Lee Chong handluje prochami dla jakiegoś nowego gangu z Kolumbii... Czy z Kolorado... Nieważne... + +[JM2_B] +Nie pamiętam. Zresztą, kogo to obchodzi. + +[JM2_D] +Ten szczurek właśnie sprzedaje ostatnie sajgonki, rozumiesz? + +[JM2_E] +Masz go zdmuchnąć! + +[JM2_G] +Załatw sobie dziewiątkę. Znajdziesz ten sklep, nie? + +[JM2_H] +Tylko pamiętaj, w Chinatown lepiej dobrze pilnować własnego dupska. To terytorium Triady. + +[JM3_A] +W porządku, mam zamiar skubnąć ciężarówkę z wypłatami. + +[JM3_B] +Codziennie wyjeżdża na miasto z okolic Chinatown. + +[JM3_C] +Zwykłe kule nawet nie zarysują jej pancerza, więc musisz po prostu zdobyć ciężki samochód i zepchnąć ją z drogi. + +[JM3_D] +Jak mocno walniesz, to ci tchórze z ochrony zwieją, gdzie pieprz rośnie. + +[JM3_E] +Wtedy zabierz ciężarówkę do magazynu w dokach - moi ludzie już się nią tam zajmą. + +[JM3_F] +Ciężarówka nie będzie na ciebie czekać, dlatego lepiej się streszczaj. + +[JM3_1] +~g~Zabierz ciężarówkę do kryjówki. + +[JM3_2] +~g~Taranuj ciężarówkę dopóki poziom jej uszkodzeń nie przekroczy 70 procent. + +[JM4_B] +O, to jest ten gość, o którym ci mówiłem! + +[JM4_C] +Posłuchaj, on nie jest Włochem i żaden z niego mechanik, ale potrafi radzić sobie z problemami. + +[JM4_D] +To jest Pops Capo, a to Tony Cipriani. + +[JM4_E] +Tak, Tony Cipriani to ja. + +[JM4_F] +Podrzuć go do restauracji Mamuśki w St. Marks. + +[JM4_G] +Jeszcze jedno. Posłuchaj, planuję taką robótkę, do której potrzebny jest dobry kierowca. Wpadnij później, to pogadamy, OK? + +[JM4_2] +Zaczekaj tutaj. Nie gaś silnika - to nie jest wizyta towarzyska. + +[JM4_3] +To pułapka Triad! Zabierz nas stąd, młody! + +[JM4_4] +Triady myślą, że mogą ze mną zadzierać... ZE MNĄ! + +[JM4_6] +Ostrożnie z tym wozem! Mówiłem, bez numerów! + +[JM4_7] +~g~Zabierz Toniego do restauracji jego matki. + +[JM4_8] +~r~Toni został załatwiony! + +[JM5_A] +Pięknie! Po prostu pięknie. + +[JM5_B] +Oto i facet, którego mi potrzeba! + +[JM5_D] +Jeden z braci Forelli myślał, że jest bystrzejszy niż woda w kiblu, a więc doczekał się odpowiedniej kary. + +[JM5_E] +Zabierz samochód z ciałem do zgniatarki w Harwood, OK? + +[JM5_1] +~g~Zabierz samochód do zgniatarki! + +[JM5_2] +~g~To bracia Forelli! + +[JM6_A] +Ale z niej sztuka, co? + +[JM6_B] +W porząsiu, posłuchaj. Załatw sobie gablotę i jedź do kryjówki w St. Marks. Tam zabierzesz paru moich kumpli. + +[JM6_C] +Robią napad na bank i potrzebują kierowcy. + +[JM6_D] +Dałem im słowo, że znasz się na tym fachu, więc nie spieprz sprawy, dobra? + +[JM6_E] +Dowieź ich do banku przed godziną piątą i nie waż się spóźnić nawet minutę. + +[JM6_2] +Nie gaś silnika, zaraz wracamy. + +[JM6_3] +Zabierz nas stąd!! + +[JM6_4] +Zgub gliniarzy i dowież nas do kryjówki! + +[JM6_6] +~g~Skombinuj jakiś samochód, który będzie trochę mniej rzucał się w oczy. + +[JM6_7] +~g~Musisz zabrać wszystkich 3 bandytów, aby móc przeprowadzić skok! + +[TM1] +'ZABIERZ BRUDY DO PRALNI' + +[TM2] +'HARACZ' + +[TM3] +'SALVATORE ZWOŁUJE NARADĘ' + +[TM4] +'TRIADY I TROSKI' + +[TM5] +'SMAŻONE RYBY' + +[TONI_P] +Mam dla ciebie pilną pracę, Toni! + +[TM1_A] +~w~Siadaj, synu! Siadaj, zamknij jadaczkę i słuchaj. + +[TM1_B] +~w~Więc pralnie nie chcą płacić haraczu, co? + +[TM1_C] +~w~Triady myślą, że mogą mi podskakiwać? + +[TM1_D] +~w~Nauczymy tych dmuchanych twardzieli, co to znaczy być prawdziwym twardzielem. + +[TM1_E] +~w~Tak, damy im lekcję pokory. Mój syn nie będzie znosić upokorzeń od jakiejś Triady! + +[TM1_F] +~w~Twój ojciec, Panie świeć nad jego duszą, jeszcze na Sycylii trzymał ich krótko i nie pozwalał, żeby Triady wciskały mu kit.v + +[TM1_G] +~w~Przepraszam, Mamo. Tak jest, Mamo. + +[TM1_H] +~w~Chcę, żebyś zniszczył ciężarówki z pralni + +[TM1_I] +~w~i rozdeptał każdego sługusa Triad, który wejdzie ci w drogę. + +[TM1_J] +~w~8-Ball załatwi ci wszystko, co jest potrzebne do tej roboty. + +[TM2_A] +~w~TONI gdzieś polazł, znowu będzie straszyć ludzi albo przynajmniej usiłować kogoś przestraszyć. + +[TM2_AA] +~w~Nigdy nie będzie nawet w połowie takim twardzielem jak jego Papa. Zostawił dla ciebie kartkę na stole. + +[TM2_B] +~w~Pralnie zgodziły się zapłacić - spisałeś się dobrze, synu! + +[TM2_C] +~w~Odbierz forsę i przywieź ją tutaj. Uważaj na Triady. + +[TM2_D] +~w~Możliwe, że będą chcieli podłożyć nam świnię, ale nie daj sobie wciskać żadnego gówna. + +[TM2_E] +~w~Nikt, powtarzam, nikt nie zadziera z TONIM CIPRIANI! + +[TM2_1] +~g~Odwieź forsę Toniemu!!! + +[TM2_2] +~g~Zdmuchnąłeś ich wszystkich! + +[TM3_MA] +~w~Nie wiem, gdzie on się podziewa! + +[TM3_MB] +~w~Jak pragnę zdrowia, ten mój chłopak czasami jest taki głupi. + +[TM3_MC] +~w~Jego ojciec był zupełnie inny. Zawsze silny, męski, u steru wydarzeń... + +[TM3_A] +~w~Don Salvatore zwołał naradę. + +[TM3_B] +~w~Masz odebrać z warsztatu limuzynę i jego chłopaka, Joeya. + +[TM3_C] +~w~Potem zabierz Luigiego z klubu i wróć tutaj po mnie. + +[TM3_D] +~w~Razem pojedziemy do domu szefa. + +[TM3_E] +~w~Triady nie znają umiaru. + +[TM3_F] +~w~Chcą wojny, będą mieli wojnę. + +[TM3_G] +~w~Teraz zmykaj. + +[TM3_1] +~g~Odbierz limuzynę od Joeya. + +[TM3_2] +~g~Teraz jedź po Luigiego. + +[TM3_3] +~g~Teraz jedź po Toniego. + +[TM3_4] +~g~Zawieź ferajnę do rezydencji Salvatore. + +[TM3_5] +~y~Triady przygotowały zasadzkę! + +[TM4_B] +~w~A więc WOJNA! Triady używają jako przykrywki fabryki przetwórstwa ryb. + +[TM4_C] +~w~Większość ich interesów odbywa się na targu rybnym w chińskiej dzielnicy. + +[TM4_D] +~w~Pralnie nadal wiszą nam haracz za ochronę. + +[TM4_E] +~w~Żółtki myślą, że Triady ich obronią, więc czas wymierzyć im stosowną karę. + +[TM4_F] +~w~Weź tych dwóch chłopaków i załatwcie szefów Triady! + +[TM4_G] +~w~Tam, u diabła, jeżeli nadarzy się okazja, to załatwcie też paru żołnierzy Triady. + +[TM4_GAT] +~g~Do środka może wjechać tylko ciężarówka Triad. + +[TM5_B] +~w~Wystarczy już tego pitolenia. + +[TM5_C] +~w~Czas wykończyć Triady w Liberty raz na zawsze! + +[TM5_D] +8-Ball założył ładunek wybuchowy w śmieciarce. + +[TM5_E] +~w~To jest bomba z zegarem czasowym, więc jak się spóźnisz, nie pozostanie po tobie żaden ślad. Jedź po śmieciarkę. + +[TM5_F] +~w~Tylko ostrożnie, 8-Ball mówi, że mechanizm jest cholernie czuły i byle wstrząs może spowodować wybuch! + +[TM5_G] +~w~Zaparkuj między cysternami z benzyną i znikaj stamtąd! + +[TM5_H] +~w~Zaparkuj pomiędzy zbiornikami z gazem i spadaj gdzie pieprz rośnie! + +[TM5_I] +~w~Chcę, żeby na miasto spadł deszcz makreli. + +[TM5_J] +~w~Zobaczysz, tym razem to nie żadne wielkanocne bum-bum, tylko prawdziwa plaga biblijna! + +[FM2] +'RÓWNO Z TRAWĄ' + +[FM4] +'OSTATNIE PROŚBY' + +[FM1_A] +~w~Ja i moi ludzie musimy obgadać parę kwestii, + +[FM1_B] +~w~a więc ty zapewnisz mojej dziewczynie rozrywkę na wieczór. + +[FM1_C] +~w~HEJ, MARIA! RUSZ DUPĘ! + +[FM1_D] +~w~Głupia suka, zawsze się tak zachowuje. + +[FM1_E] +~w~A oto i ona, jedyna w swoim rodzaju królowa piękności! + +[FM1_F] +~w~Co tam robiłaś tyle czasu? + +[FM1_G] +~w~Cokolwiek to było, na pewno straciłem na tym jakieś pieniądze. + +[FM1_H] +~w~Chyba nie myślisz, że jestem tu, bo lubię z tobą gadać? + +[FM1_I] +~w~Wsiadaj do samochodu i zamknij swoją wielką gębę. + +[FM1_J] +~w~Możesz zabrać limuzynę, ale przyprowadź ją z powrotem w jednym kawałku, zrozumiano? + +[FM1_K] +~w~I uważaj na nią! Ona lubi pakować się w kłopoty! + +[FM1_L] +~w~Dość! Twój nowy piesek gończy na pewno zna już te historyjki, + +[FM1_M] +~w~a poza tym to przecież kawał chłopa! + +[FM1_N] +~w~Hej, piesku! Odwiedzimy Chico i załatwimy jakieś dopalacze na imprezę. + +[FM1_P] +~g~To właśnie Chico, podjedź do niego. + +[FM1_S] +~w~Jak sobie życzysz, moja pani. + +[FM1_TT] +~w~NALOT POLICYJNY! + +[FM1_1] +~g~Wracaj do limuzyny! + +[FM1_2] +~g~Wsiadaj do limuzyny! + +[FM1_3] +~r~Jeżeli zostawisz Marię, Salvatore każe cię obić jak psa! Zawróćj i zabierz ją ze sobą! + +[FM1_4] +~g~Zostawiłeś kobietę Dona! Wracaj do magazynu i zaczekaj na Marię! + +[FM1_5] +~g~Zawieź Marię do domu Salvatore tak, aby włos jej z głowy nie spadł! + +[FM1_6] +~g~Chico nie będzie czekał wiecznie - zabierz Marię na wybrzeże! + +[FM1_7] +~r~Maria nie żyje! Salvatore nie ucieszy się z tej wiadomości... + +[FM1_8] +~r~Załatwiłeś dostawcę Marii!!! + +[FM2_J] +Zostaw nas na chwilkę samych. + +[FM2_A] +Kartel Kolumbijski produkuje HEROINĘ gdzieś na terenie Liberty. + +[FM2_K] +ale nadal nie wiemy, gdzie, a oni działają tak, jakby z góry znali wszystkie nasze posunięcia. + +[FM2_L] +Za barem 'U Luigiego' pracuje pewien gość. Nazywa się Kudłaty Bob. + +[FM2_M] +Ostatnio wydaje więcej kasy niż mógłby zarobić. + +[FM2_N] +Zazwyczaj po pracy wraca do domu taksówką. Jedź za nim. + +[FM2_O] +A jeżeli okaże się, że to nasz kret... Zabij go! + +[FM2_F] +Oto nasz mały przyjaciel, pan wielka gęba we własnej osobie. + +[FM2_G] +Ktoś cię śledził? Wiesz, że to, co się tu dzieje, to nasz mały sekret. + +[FM2_H] +Nie, nikt mnie nie śledził. Masz mój towar? + +[FM2_I] +Masz swoje prochy, śmieciu, a teraz mów. + +[FM2_P] +A więc rodzina Leone prowadzi teraz wojnę na dwa fronty. + +[FM2_Q] +Z Triadami toczą wojnę o terytorium i wygląda na to, że żadna ze stron nie ma zamiaru się poddać. + +[FM2_R] +Z drugiej strony, Joey Leone wplątał się w krwawe porachunki z rodziną Forellich. + +[FM2_S] +Rodzina Leone z każdym dniem traci ludzi i wpływy w mieście. + +[FM2_T] +Salvatore zaczyna zachowywać się jak niebezpieczny dla otoczenia paranoik. Podejrzewa wszystkich i wszędzie wietrzy spisek. + +[FM2_U] +Ale jeżeli wszyscy są tak lojalni jak ty, to nie ma się o co martwić, prawda? + +[FM2_1] +~g~To Kudłaty Bob! + +[FM2_2] +~g~Kudłaty wyszedł z klubu - śledź go! + +[FM2_5] +~g~Zabierz go do Portland Harbor. + +[FM2_6] +~r~Kudłaty nie wsiądzie do porozbijanej taksówki! + +[FM2_7] +~r~Kudłaty zwiał! Spotkanie odwołane! + +[FM2_8] +~g~Walnij Kudłatego Boba! + +[FM2_9] +~r~Kudłaty Bob nie żyje! + +[FM2_10] +~r~Kudłaty ucieka! + +[FM2_11] +~g~Zaparkuj przed klubem 'U Luigiego'! Kudłaty Bob powinien niedługo wyjść. + +[FM2_12] +~r~Zgubiłeś go! + +[FM3_A] +~w~Powinniśmy sprzątnąć tych łajdaków z Kartelu, + +[FM3_B] +~w~ale dopóki mamy na głowie wojnę z Triadami, nie mamy na to dość sił. + +[FM3_C] +~w~Kartel zarabia ogromne pieniądze na sprzedaży tej syfiastej HEROINY. + +[FM3_D] +~w~Jeżeli przypuścimy otwarty atak, rozsmarują nas na miazgę. + +[FM3_E] +~w~Zapewne produkują prochy na tym wielkim statku, do którego doprowadził cię Kudłaty. + +[FM3_F] +~w~Dlatego właśnie musimy działać z głową. I to z twoją głową. + +[FM3_G] +~w~Chciałbym prosić cię, abyś zrobił mi osobistą przysługę i zniszczył tę fabrykę HERY, Salvatore. + +[FM3_H] +~w~Jeżeli to ci się uda, spędzisz resztę życia w dostatku. + +[FM3_I] +~w~Spotkaj się z 8-Ballem. On zna się na dynamicie jak nikt inny - będziesz potrzebował jego doświadczenia. + +[FM3_8A] +~w~Mój czarnuch! Salvatore już dzwonił, + +[FM3_8B] +~w~ale do takiej roboty potrzeba będzie sporo fajerwerków. + +[FM3_8D] +~w~ale chyba wiesz, że żaden dolar z tej sumy nie zostanie zmarnowany. + +[FM3_8E] +~w~W porządku, zróbmy to! + +[FM3_8F] +~w~Mogę nastawić to maleństwo na wybuch, ale rany na moich łapskach jeszcze się nie wygoiły i nie poradzę sobię z giwerą. + +[FM3_8G] +~w~Weź karabin i rozwal parę łbów. + +[FM3_4] +~g~Zatrzymaj samochód i wypuść 8-Balla! + +[FM3_7] +~r~Zgubiłeś 8-Balla! + +[FM3_8] +~r~Strażnicy podnieśli alarm! + +[FM4_A] +~w~To mój ulubiony czyściciel. + +[FM4_B] +~w~Jestem z ciebie dumny, chłopcze Wybiłeś tym tępakom to ich gówno z głów. + +[FM4_C] +~w~Zanim będziemy mogli uczcić twój sukces, mam dla ciebie jeszcze jedną małą robótkę. + +[FM4_D] +~w~Na następnej przecznicy stoi samochód z klubu Lugiego. + +[FM4_E] +~w~W środku jest cały upaćkany mózgiem. + +[FM4_F] +~w~Musieliśmy pomóc jednemu gościowi namyślić się nad paroma sprawami i wyszedł z tego niezły bałagan. + +[FM4_H] +~w~Zabierz ten samochód do zgniatacza, zanim wypatrzą go gliniarze. + +[AM3] +'PAPARAZZO NA DNIE' + +[AM4] +'WYPŁATA DLA RAYA' + +[AM5] +DWULICOWY TANNER + +[AM1_A] +Zanim przejdziemy do jakichkolwiek interesów, musimy wyjaśnić sobie + +[AM1_B] +pewne kwestie. Wyłóżmy nasze karty na stół. + +[AM1_C] +Należę do Yakuzy i wiem, że pracowałeś dla rodziny Salvatore Leone. + +[AM1_D] +Myślę, że znajdzie się dla ciebie miejsce w naszej organiacji. + +[AM1_E] +Ale najpierw musisz udowodnić, że naprawdę zerwałeś wszystkie związki z Mafią. + +[AM1_G] +Zadbaj, aby nie dotarł do celu żywy. + +[AM1_H] +W międzyczasie, ja i Maria pogawędzimy o starych dobrych czasach. + +[AM1_I] +Asuka, przyszedł twój masażysta. + +[AM1_J] +To nie żaden masażysta. + +[AM1_1] +~g~Salvatore wychodzi z knajpy 'U Luigiego'! + +[AM1_2] +~r~Zostałeś zauważony! + +[AM1_3] +~r~Spudłowałeś do Salvatore! + +[AM1_4] +~r~Znakomicie, wystraszyłeś cel! I ty uważasz się za zawodowca? + +[AM1_5] +~g~Jedź do Dzielnicy Czerwonych Świateł i poczekaj, aż Salvatore wyjdzie z klubu. + +[AM1_7] +~r~Salvatore siedzi sobie bezpiecznie w domu i sączy swój koktajl. Nie spodziewaj się, że ktoś nazwie cię 'Szakalem'! + +[AM1_8] +~g~Salvatore będzie wychodzić z klubu Luigiego około ~1~:~1~ + +[AM2_4] +~g~Skradałeś się z gracją słonia w składzie porcelany! + +[AM3_A] +Jakiś nieproszony reporter wtykał tu swój nos + +[AM3_B] +Maria i ja jedziemy razem na małe wakacje, a ty pozbądź się tego zboczonego podglądacza. + +[AM4_A] +A oto i mój ulubiony człowiek do każdej roboty! + +[AM4_B] +Maria chciałaby cię widzieć, ale... nie może podejść. Powiem jej, że pytałeś. + +[AM4_C] +Kto tam? Asuka? Wiem, że byłam niegrzeczną dziewczynką, ale naprawdę muszę do toalety! OK? + +[AM4_D] +Czas, abyś poznał naszą wtyczkę w tutejszej policji. + +[AM4_E] +Tu masz zapłatę za ostatnią robótkę, jaką dla nas wykonał. + +[AM4_F] +Facet jest bardzo ostrożny, to chyba jasne. + +[AM4_G] +Jak najszybciej odszukaj budkę telefoniczną w Torrington i czekaj na instrukcje. + +[AM5_A] +Maria i ja wyszliśmy na zakupy. + +[AM5_B] +Nasze źródło w policji donosi, że jeden z naszych kierowców to policyjny tajniak. + +[AM5_C] +Bez samochodu nie jest groźny. Przyczepiliśmy do jego gabloty nadajnik. + +[AM5_D] +Niech się dowie, czym grozi taka zabawa. + +[AM5_1] +Tanner chyba ma coś do ciebie! + +[AS1] +'PRZYNĘTA' + +[AS2] +'ESPRESSO NA WYNOS!' + +[AS4] +'OKUP' + +[AS1_A] +~w~Miguel chyba uważa, że źle go traktuję. + +[AS1_B] +~w~Mimo to zdradził mi, jak bardzo Catalina obawia się twojej zemsty. + +[AS2_A] +~w~Nie doceniliśmy planów, jakie Catalina wiąże ze swoją heroiną. + +[AS2_B] +~w~Marzy jej się coś więcej niż banda Yardies sprzedająca prochy na rogu. + +[AS2_D] +~w~Sprzedają herę w swojej sieci budek ulicznych. + +[AS2_1] +~g~Wszystkie budki z espresso w Portland zostałyzniszczone!! + +[AS2_2] +~g~Wszystkie budki z espresso na Wyspie Staunton zostały zniszczone!! + +[AS2_3] +~g~Wszystkie budki z espresso w Shoreside Vale zostały zniszczone!! + +[AS2_4] +~r~Kartel ostrzegł swoich dilerów!! + +[AS2_5] +~g~W Shoreside Vale i na Wyspie Staunton nadal stoi kilka budek z espresso! + +[AS2_6] +~g~W Shoreside Vale nadal stoi kilka budek z espresso! + +[AS2_7] +~g~Na Wyspie Staunton nadal stoi kilka budek z espresso! + +[AS2_8] +~g~W Portland nadal stoi kilka budek z espresso + +[AS2_9] +~g~W Portland i Shoreside Vale nadal stoi kilka budek z espresso + +[AS2_10] +~g~W Portland i na Wyspie Staunton nadal stoi kilka budek z espresso + +[AS2_12] +~g~Badaj dzielnice miasta i szukaj budek Espresso-2-Go! + +[AS3_A] +~W~Przyciskamy już teraz czy poczekamy, aż trochę osłabnie? + +[AS3_B] +~w~Popieść go trochę... + +[AS3_D] +~w~Mój Człowiek do Każdej Roboty! + +[AS3_E] +~w~Nudziłam się, więc wpadłam żeby dotrzymać Asuce towarzystwa. + +[AS3_1] +~g~Odszukaj ~r~łódź~g~ i dopłyń do ~b~boi! + +[AS3_3] +~g~Odczekaj, aż ~y~samolot~g~ rozpocznie podejście!! + +[AS3_5] +~g~Zbierz ładunek! + +[AS3_4] +~g~Użyj wyrzutni rakietowej aby zestrzelić ~y~samolot~g~!! + +[AS3_2] +~b~Płyń do boi wyznaczających lądowisko! ~y~Samolot wykonuje już ostatnie podejście!! + +[AS3_6] +~g~~1~ Z 8! + +[KM1] +'UCIECZKA KANBU' + +[KM3] +'ZDUSIĆ UKŁADY' + +[KM4] +'SHIMA' + +[KM5] +'UDERZENIE' + +[KM1_A] +Moja siostra ma o tobie dobre mniemanie, + +[KM1_E] +choć nie mogę sobie wyobrazić, żeby gaijin mógł dać nam coś innego niż rozczarowania. + +[KM1_B] +Być może mógłbyś pomóc w rozwiązaniu problemu, który leży mi na sercu. + +[KM1_F] +Rzecz jasna, porażka oznacza utratę honoru. + +[KM1_C] +Kanbu, członek Yakuzy, przebywa w areszcie, gdzie czeka na proces. + +[KM1_G] +To ceniony członek rodziny. + +[KM1_H] +Odbij go z aresztu i przywieź do dojo w Bedford Point. + +[KM1_D] +Dziękujemy ci za twoje bezinteresowne działania. Jeżeli kiedykolwiek będziesz potrzebował pomocy, dojo z radością przydzieli ci dwóch wojowników, którzy staną u twego boku. + +[KM1_1] +~g~Ukradnij radiowóz! + +[KM1_2] +~g~Załóż bombę w samochodzie! + +[KM1_3] +~g~Teraz zawieź go do dojo Yakuzy. + +[KM1_5] +~g~W porządku, teraz jazda na posterunek. + +[KM1_6] +~g~Zamontuj w samochodzie ładunek wybuchowy! + +[KM1_7] +~g~Tylko dla pojazdów policyjnych! + +[KM1_9] +~r~Nie użyłeś bomby samochodowej, aby zniszczyć mur. + +[KM1_10] +~r~Kanbu z Yakuzy jest trupem - tak samo jak twój honor! + +[KM1_11] +~r~Ściągnąłeś sobie na głowę kłopoty! + +[KM2_A] +Nie sposób przecenić znaczenia etykiety w tej branży. + +[KM2_B] +Na moją hańbę, pewien człowiek oddał mi kiedyś przysługę, a ja nigdy nie miałem okazji, aby mu się odwdzięczyć. + +[KM2_C] +Konikiem tego człowieka są samochody. Poprosił mnie, abym zgromadził pewne modele aut do jego kolekcji. + +[KM2_F] +Od tego zależy mój honor. + +[KM2_2] +~g~Samochód dostarczony. + +[KM3_A] +Kiedy nadciągają ciemne chmury, głupiec odwraca wzrok, a mędrzec stawia im czoła. + +[KM3_B] +Kartel Kolumbijski zignorował liczne prośby, aby nie naruszać naszych interesów w Liberty City. + +[KM3_C] +Teraz negocjują układ z Jamajczykami, aby jeszcze bardziej nas upokorzyć. + +[KM3_D] +Właśnie finalizują układ o podziale wpływów w mieście. + +[KM3_F] +Weź jednego z moich ludzi, ukradnij samochód gangu Yardie i jedź przekazać Kolumbijczykom nasze wyrazy szacunku. + +[KM3_E] +Nasz honor wymaga, aby wszyscy umarli. + +[KM3_2] +~g~Jedź po swój kontakt. + +[KM3_3] +~g~Spotkanie odbędzie się na parkingu szpitalnym w Rockford! + +[KM3_4] +~r~Uciekają! + +[KM3_6] +~g~Zabij ich, zabij ich wszystkich! + +[KM3_8] +~g~Aby wykonać zadanie, potrzebujesz samochodu gangu Yardie. + +[KM3_9] +~r~Jeden z Kolumbiczyków nie żyje, układ odwołany. + +[KM3_10] +~r~Twój kontakt nie żyje! + +[KM4_A] +Jeżeli chcesz być naprawdę silny, nie możesz nigdy okazywać słabości. + +[KM4_C] +Jak najszybciej odbierz pieniądze, abyśmy mogli wpłacić je na konto kasyna. + +[KM4_1] +Nie mam wam czym zapłacić, ale nawet gdybym miał, to i tak bym tego nie zrobił! + +[KM4_9] +Jakaś banda szczeniaków właśnie stąd uciekła! Zabrali wszystko! + +[KM4_2] +Nie ma z was żadnego pożytku. + +[KM4_10] +A czy ty w ogóle należysz do Yakuzy...? + +[KM4_3] +Nie za to wam płacę, obwiesie. Gdybym chciał takiej ochrony, to zaprosiłbym cholerną policję. + +[KM4_4] +~g~Wymierz karę gangowi odpowiedzialnemu za napad i odzyskaj ~b~opłatę za ochronę~g~! + +[KM4_7] +~r~Sklepikarz wydał ostatnie tchnienie! + +[KM4_5] +Donald Love zaprasza cię do swojego ogrodu herbacianego na rozmowę. + +[KM4_6] +Tam są pieniądze! + +[KM4_8] +~r~Teczka odebrana! + +[KM5_A] +TO TY! Wybrałeś najwłaściwszy moment, aby pokazać swoją bezwartościową postać! + +[KM5_B] +Zdaje się, że twoje marne próby, aby odwieść Jamajczyków + +[KM5_B1] +od skumplowania się z Kartelem były całkowicie chybione! + +[KM5_C] +Handlarze Yardie krążą po ulicach Liberty, sprzedając woreczki HERY tak, jakby sprzedawali hotdogi! + +[KM5_D] +Te wieprze z Kartelu śmieją się z nas, ze mnie! + +[KM5_E] +Dam ci ostatnią szansę, abyś mógł dowieść, że zaufanie, jakim obdarzyła cię moja siostra, nie było bezpodstawne. + +[KM5_F] +Rozjedź tych śmieci i spłucz swoją hańbę w potokach krwi naszych wrogów!!! + +[KM5_3] +~r~Nie udało ci się zabić co najmniej ~1~ członków gangu Yardie. + +[KM5_4] +~g~Gratulacje, zabiłeś ~1~ członków gangu Yardie. + +[KM5_5] +~g~Gratulacje, zabiłeś ~1~ członków gangu Yardie. PREMIA $~1~. + +[RM1] +'UCISZYĆ KAPUSIA' + +[RM3] +'GONIĄC DOWODY' + +[RM4] +'NA RYBY' + +[RM5] +'DOKOŃCZYĆ DZIEŁO' + +[RM1_D] +Siedzi pod ochroną policji w budynku WitSec w Newport, w którymś z mieszkań za parkingiem. + +[RM1_E] +Podpal tę budę, to powinno ich wypłoszyć. Wtedy na nich zapoluj - zadbaj, aby McAffrey już nigdy z nikim nie rozmawiał! + +[RM1_1] +~g~Znajdź miejsce, w którym przebywa chroniony świadek. + +[RM1_2] +~g~Wykończ McAffrey'a! + +[RM2_A1] +Hej, synu, chodź tutaj! + +[RM2_A] +Mój stary kumpel z wojska prowadzi sklepik w Rockford. + +[RM2_D] +Potrzeba mu wsparcia. W zamian możesz liczyć na spore obniżki cen na spluwy, które ma na składzie. + +[RM2_E] +Ray wspominał, że ktoś przyjdzie... ale nie myślałem, że przyśle takiego szczeniaka. + +[RM2_F] +No cóż, trzy ramiona to zawsze więcej niż jedno, więc bierz broń według życzenia. + +[RM2_G] +~g~Zasuwaj i pilnuj Phila! + +[RM2_H] +~r~Phil zginął! + +[RM2_L] +No, no! Gdybyś był z nami wtedy w Nikaragui, może jeszcze miałbym swoją rękę! + +[RM2_N] +Zostaw forsę. Teraz lepiej znikaj, sam zajmę się policją. + +[RM3_D] +Dowody będą przewożone przez miasto. + +[RM3_E] +Musisz staranować ten samochód i zebrać wszystkie dowody, co do jednego! + +[RM3_F] +Kiedy już je zbierzesz, zostaw je w samochodzie i podpal go. + +[RM3_G] +Obaj będziemy mieć sporo korzyści, chłopcze. + +[RM3_1] +~g~Zostaw dowody w samochodzie i podpal wóz. + +[RM3_4] +~g~Samochód prokuratury zgubił dowody! + +[RM3_6] +~r~Teraz te fotografie obejrzy całe miasto! + +[RM3_7] +~g~Podpal samochód! + +[RM4_A] +Podejrzewam, że mój wspólnik to kret. + +[RM4_C] +On każdego wieczoru wypływa na morze, w okolice latarni przy Portland Rock, aby łowić ryby. + +[RM4_D] +Podwędź policyjną łódź i dopilnuj, aby poszedł na dno razem ze swoimi zdradzieckimi planami! + +[RM4_1] +~g~Ukradnij łódź policyjną! + +[RM4_2] +~g~Płyń do latarni morskiej i załatw kolegę Raya! + +[RM5_A] +Ty nieudaczny łajdaku! + +[RM5_A1] +Schrzaniłeś robotę! Moja dupa już się zaczyna smażyć, a ty nie potrafisz zabić nawet cholernej muchy. + +[RM5_B] +Zapłaciłem ci kupę szmalu, żebyś sprzątnął świadka, a on dalej żyje! + +[RM5_B1] +Dzisiaj będzie składał pierwsze zeznania w Sądzie Federalnym! + +[RM5_C] +Lada chwila będzie wyjeżdżał ze Szpitala Ogólnego Carson w Rockford. + +[RM5_D] +Jeżeli on zacznie sypać, koniec ze mną... + +[RM5_E] +więc lepiej zrób to, za co ci zapłaciłem! + +[RM5_1] +~g~Przechwyć karetkę. + +[RM5_2] +~g~Zostałeś rozpoznany! + +[RM5_3] +~g~To była tylko przynęta! + +[RM5_4] +~g~Kule nie przebiją pancernego kadłuba! + +[RM5_5] +~g~Pancerna karoseria jest ognioodporna! + +[RM5_7] +~r~Świadek dotarł na miejsce! + +[RM5_8] +~g~Świadek poszedł na dno! + +[LOVE2] +'SPRZĄTNĄĆ WAKA-GASHIRĘ' + +[LOVE3] +'KROPLA W OCEANIE' + +[LOVE1_A] +Przede wszystkim pozwól mi podziękować, że zechciałeś zająć się tą sprawą o charakterze osobistym. + +[LOVE1_F] +W dzisiejszych czasach ludzie nie szanują żadnych porozumień. + +[LOVE1_D] +Usiłują wymusić na mnie dodatkowe pieniądze, ale ja nie wierzę w renegocjacje. + +[LOVE1_E] +Umowa to umowa, więc nie powinni spodziewać się ode mnie nawet grosza. + +[LOVE1_G] +Uratuj mojego przyjaciela, zrób wszystko, co będzie trzeba. + +[LOVE1_2] +~g~Uratuj starego pana z dalekiego wschodu. + +[LOVE1_3] +~g~Zabierz starego pana z dalekiego wschodu do budynku Donalda Love'a. + +[LOVE1_4] +~g~Stary pan z dalekiego wschodu musi się znajdować w jednym z tych garaży... + +[LOVE1_6] +~r~Flaki starego pana z dalekiego wschodu zostały rozsmarowane po całej ulicy! + +[LOVE1_7] +~g~Brama otworzy się wyłącznie przed samochodem gangu kolumbijskiego. + +[LOVE2_A] +Nic tak nie wpływa na spadek cen nieruchomości, jak stara dobra wojna gangów. + +[LOVE2_B] +No, może z wyjątkiem wybuchu epidemii... ale w tym wypadku nie trzeba się posuwać aż tak daleko. + +[LOVE2_C] +Zauważyłem, że Yakuza i Kolumbijczycy nie są do siebie przyjaźnie nastawieni. + +[LOVE2_D] +Skorzystajmy z tej szansy. + +[LOVE2_E] +Masz zabić Waka-gashirę gangu Yakuzy, Kenji'ego Kasena. + +[LOVE2_F] +Kenji właśnie jest na spotkaniu na szczycie parkingu piętrowego w Newport. + +[LOVE2_G] +Załatw sobie samochód Kartelu i rozsmaruj go na ścianie! + +[LOVE2_H] +Zrób to tak, aby Yakuza obciążyła Kartel za ten akt. + +[LOVE2_1] +~g~Jedź do Fort Staunton i zwędź samochód gangu kolumbijskiego! + +[LOVE2_2] +~g~Teraz jedź na ~p~parking wielopiętrowy w Newport~p~ i załatw Kenjiego! + +[LOVE2_3] +~r~Jeżeli pojedziesz tam bez samochodu Kartelu, zostaniesz rozpoznany! + +[LOVE2_4] +~r~Członkowie Yakuzy cię rozpoznali! + +[LOVE2_6] +~r~Zabiłeś wszystkich świadków!!! + +[LOVE3_A] +W czasach hipokryzji moralnej ciężko jest zdobyć niektóre cenne towary z zagranicy. + +[LOVE3_C] +Pilot zrzuci do wody kilka pakunków. + +[LOVE3_D] +Zbierz je, zanim wpadną w niepowołane ręce. + +[LOVE3_1] +~g~Załatw sobie ~r~łódź~g~ i płyń za ~y~samolotem~g~! + +[LOVE4] +'GRAND THEFT AERO' + +[LOVE5] +'KONWOJENT' + +[LOVE4_A] +Dziękuję za odzyskanie paczek. Przykro mi to mówić, ale była to jedynie przynęta. + +[LOVE4_B] +Nie chciałem cię urazić, po prostu czasami w interesach trzeba tak postąpić. + +[LOVE4_C] +Mój prawdziwy cel przez cały czas był ukryty w samolocie. + +[LOVE4_F] +Przekupiłem kogo trzeba. + +[LOVE4_1] +~r~Są tu ludzie z Kartelu Kolumbijskiego! + +[LOVE4_2] +~g~Pakunek zniknął! Wyśledź Kolumbijczyków i odzyskaj ładunek. + +[LOVE4_3] +~g~Firma budowlana Panlantic Construction? + +[LOVE4_5] +~g~Paczka powinna nadal być w samolocie... + +[LOVE4_6] +~g~Wjedź windą na wieżę! + +[LOVE5_B] +Mój orientalny przyjaciel potrzebuje eskorty, kiedy będze wiózł mój najnowszy nabytek do specjalistów. + +[LOVE5_1] +~g~Ruszamy! + +[LOVE5_2] +~g~Potrzebujesz samochodu! + +[LOVE5_3] +~g~Jedź przodem i zbadaj wylot tunelu! + +[LOVE5_4] +~r~Osłaniaj ciężarówkę! + +[RM6] +'NA WIDELCU' + +[RM6_A] +Nikt cię nie śledził? To dobrze. + +[RM6_B] +To już koniec. Jestem po uszy w gównie i nadal się zapadam. + +[RM6_D] +Jestem na widelcu, więc postanowiłem zniknąć. + +[RM6_E] +Zawieź mnie na mój samolot, a dobrze ci się odwdzięczę! + +[RM6_666] +Zatroszcz się o mojego kuloodpornego Patriota. Do zobaczenia w Miami, Ray. + +[CAT1] +'OKUP' + +[CAT2] +'WYMIANA' + +[CAT1_A] +Mam twoją słodziutką Marię. Jeżeli nie chcesz, żeby jej twarz wyglądała jak po randce z rzeźnikiem, + +[CAT2_F] +Złamałam paznokieć i cała jestem potargana. Nie do wiary! Ta fryzura kosztowała mnie pięćdziesiąt dolców! + +[CAT2_G] +Strasznie się bałam, ale w końcu powiedziałam sobie: jesteś już przecież dużą dziewczyną. + +[CAT2_H] +Och, będziemy się wspaniale bawić, bo moja siostra powiedziała, że chciałaby wpaść do nas ze swoimi dziećmi, + +[CAT2_I] +bo jej mąż znowu się gdzieś szwenda i... + +[CAT1_E] +XXXX + +[CAT1_F] +Dotrzyj do Cataliny sprzed upływem wyznaczonego czasu! + +[CAT_MON] +~g~Nie masz jeszcze tyle pieniędzy. Potrzebujesz $500.000 + +[BITCH_D] +~g~Maria nie żyje! + +[WEATHER] +POGODA WYMUSZONA + +[WEATHE2] +ZWYKŁA POGODA + +[8001] +Marnie kończysz!! + +[1000] +JESTEŚ MARTWY + +[1001] +JESTEŚ MARTWY + +[1002] +JESTEŚ MARTWY + +[1003] +JESTEŚ MARTWY + +[1004] +JESTEŚ MARTWY + +[1005] +WPADKA + +[1006] +WPADKA + +[1007] +WPADKA + +[1008] +WPADKA + +[1009] +WPADKA + +[GA_4] +Bomby samochodowe kosztują 1000 dolarów za sztukę. + +[GA_5] +W twoim samochodzie bomba już została zainstalowana. + +[GA_6] { re3 change } +Zaparkuj wóz, włącz mechanizm klawiszem ~h~~k~~VEHICLE_FIREWEAPON~~w~ i W NOGI! + +[GA_7] { re3 change } +Uaktywnij bombę za pomocą klawisza ~h~~k~~VEHICLE_FIREWEAPON~~w~. Bomba wybuchnie w momencie włączenia silnika. + +[GA_8] +Użyj detonatora, aby aktywować bombę. + +[GA_9] +Zgromadziłeś ~1~ z 10 samochodów specjalnych. + +[GA_10] +Ładne cacko. Oto twoje ~1~$. + +[GA_11] +Mamy już taki wózek. Dla nas jest on bez wartości. + +[GA_12] +Bomba uzbrojona + +[GA_13] +Robota zawodowca. Skombinuj dla mnie wszystkie wózki z listy, a czeka cię premia. + +[GA_14] +Wszystkie samochody? DOSKONALE! Oto niespodzianka dla ciebie! + +[GA_15] +Mam nadzieję, że podoba ci się nowy kolor. + +[GA_16] +Lakierowanie zakończone. + +[GA_19] +Nie interesuje nas ten model. + +[GA_20] +Mamy tego więcej, niż możemy zepchnąć. Sorry, facet, ale nie wchodzę w to. + +[CR_1] +Dźwig nie jest w stanie podnieść tego pojazdu. + +[PU_MONY] +Nie masz dość forsy. + +[CO_ALL] +Masz już wszystkie. Oto mała niespodzianka... + +[PAUSED] +GRA ZATRZYMANA + +[HEALTH1] +Spadaj stąd! Jesteś zdrów jak ryba. + +[HEALTH2] +Koszty opieki medycznej. + +[HEALTH3] +Trochę cię połatam. + +[HEALTH4] +To kosztuje 250 dolarów. + +[FEB_STA] +Statystyki + +[FEB_BRI] +Zadania + +[FEB_CON] +Sterowanie + +[FEB_AUD] +Audio + +[FEB_DIS] +Ekran + +[FEB_LAN] +Język + +[FEP_STA] +STATYSTYKI + +[FEP_BRI] +CELE + +[FEP_CON] +STEROWANIE + +[FEP_AUD] +DŹWIĘK + +[FEP_DIS] +EKRAN + +[FEP_LAN] +JĘZYK + +[FEF_ST1] +Kto tu jest złym facetem? + +[FEF_ST2] +Ile paniki dzisiaj wzbudziłeś? + +[FEF_BR1] +Straciłeś wątek? + +[FEF_CO1] +Potrzebujesz lepszej kontroli, perfekcjonisto? + +[FEF_CO2] +Określ taką konfigurację klawiszy sterujących, która najlepiej odpowiada preferowanemu stylowi gry. + +[FEF_SA1] +Trzymaj wszystko na kupie! + +[FEF_SA2] +Zapisuj i wczytuj swoje gry + +[FEF_AU1] +Więcej czadu! + +[FEF_AU2] +Wybierz stację radiową oraz efekty dźwiękowe + +[FEF_DI1] +Zmień grę! + +[FEF_DI2] +Dostosuj grę do odbiornika TV + +[FEF_LA1] +O czym gadasz? + +[FEF_LA2] +Wybierz preferowany język + +[FEB_PMB] +Cele poprzednich misji: + +[FEC_NA] +N.D. + +[FEC_CWL] +Przełącz rodzaj broni w lewo + +[FEC_CWR] +Przełącz rodzaj broni w prawo + +[FEC_LOF] +Spójrz do przodu + +[FEC_TAR] +Cel + +[FEC_MOV] +Ruch + +[FEC_CAM] +Tryby kamery + +[FEC_PAU] +Pauza + +[FEC_ENV] +Wsiadanie do pojazdu + +[FEC_JUM] +Skok + +[FEC_ATT] +Atak lub strzał z broni + +[FEC_RUN] +Bieg + +[FEC_FPC] +Kamera - widok z oczu postaci + +[FEC_LL] +Spójrz w lewo + +[FEC_LB1] +Spójrz + +[FEC_LB2] +do tyłu + +[FEC_LB] +Spójrz do tyłu + +[FEC_LR] +Spójrz w prawo + +[FEC_HOR] +Klakson + +[FEC_VES] +Sterowanie w pojeździe + +[FEC_RSC] +Przełącz stacje radiowe + +[FEC_BRA] +Hamulec lub wsteczny + +[FEC_HAB] +Hamulec ręczny + +[FEC_CAW] +Broń w samochodzie + +[FEC_ACC] +Przyspieszenie + +[FEC_SMT] +Włączenie misji specjalnych + +[FEA_OUT] +Wyjście: + +[FEA_ST] +stereo + +[FEA_MNO] +mono + +[FEA_NON] +Brak + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +GADUŁA 109 + +[FED_DBG] +Menu debugowania + +[FED_RID] +Ponowne wczytanie IDE + +[FED_RIP] +Ponowne wczytanie IPL + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Big White Debug Light Switched + +[FED_SPR] +Show Ped Road Groups + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_DSR] +Żądania przetworzenia w trybie debugowania + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Menu karty pamięci + +[FEM_RMC] +Register MemCard One + +[FEM_TFM] +Próbne formatowanie karty pamięci 1 + +[FEM_TUM] +Próbne odformatowanie karty pamięci 1 + +[FEM_CRD] +Utwórz katalog główny + +[FEM_CLI] +Twórz i wczytuj ikony + +[FEM_FFF] +Fill First File with Guff + +[FEM_SOG] +Zapisz tylko grę + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_STG] +Zapisz grę + +[FEM_STS] +Zapisz grę pod nazwą GTA3 + +[FEM_CPD] +Utwórz chroniony katalog magazynowy + +[FEM_MC2] +Menu karty pamięci 2 + +[FEM_TS] +Próbne zapisywanie: + +[FEM_TL] +Próbne wczytywanie: + +[FEM_TD] +Próbne kasowanie: + +[PL_STAT] +Statystyki gracza + +[PE_WAST] +Ludzie załatwieni przez gracza + +[PE_WSOT] +Ludzie załatwieni przez innych + +[CAR_EXP] +Wysadzone samochody: + +[TM_BUST] +Liczba wpadek + +[M_WASTE] +Załatwieni mężczyźni-cywile + +[F_WASTE] +Załatwione kobiety-cywile: + +[PIG_WST] +Załatwieni gliniarze + +[GNG_WST] +Członkowie gangu załatwieni. + +[MED_WST] +Załatwieni lekarze + +[FIRE_WS] +Strażak załatwiony + +[DED_CRI] +Załatwieni przestępcy: + +[DED_DED] +Załatwione lumpy: + +[DED_HOK] +Załatwione dziwki: + +[HEL_DST] +Zniszczone helikoptery + +[PER_COM] +Procent ukończenia gry + +[KGS_EXP] +Użyte materiały wybuchowe (kg) + +[ACCURA] +Dokładność + +[ELBURRO] +Najlepsze czasy wyścigu w sekundach: + +[CAR_CRU] +Zmiażdżone samochody: + +[HED_EX] +Rozbite głowy + +[TM_DED] +Wizyty w szpitalu + +[DAYSPS] +Liczba dni, które upłynęły w grze: + +[MMRAIN] +mm deszczu + +[MXCARD] +Maks. odległość SZALONEGO skoku (w stopach) + +[MXCARJ] +Maks. wysokość SZALONEGO skoku (w stopach) + +[MXCARDM] +Maks. odległość SZALONEGO skoku (w metrach) + +[MXCARJM] +Maks. wysokość SZALONEGO skoku (w metrach) + +[MXFLIP] +Maks. liczba salt w SZALONYM skoku + +[MXJUMP] +Maks. liczba obrotów w SZALONYM skoku + +[BSTSTU] +Najlepszy SZALONY skok do tej pory: + +[INSTUN] +Szalony skok + +[PRINST] +Bezbłędny szalony skok + +[DBINST] +Podwójny szalony skok + +[DBPINS] +Bezbłędny podwójny szalony skok + +[TRINST] +Potrójny szalony skok + +[PRTRST] +Bezbłędny potrójny szalony skok + +[QUINST] +Poczwórny szalony skok + +[PQUINS] +Bezbłędny poczwórny szalony skok + +[NOSTUC] +Nie wykonano żadnych SZALONYCH skoków + +[NOUNIF] +Wyjątkowe skoki wykonane + +[NOUNGM] +Wyjątkowe skoki razem + +[NMISON] +Próby wykonania misji + +[NMMISP] +Wykonane misje + +[PASDRO] +Zgubieni pasażerowie + +[MONTAX] +Forsa zarobiona w taksówce + +[DAYPLC] +Dzienne wydatki policji: + +[CRIMRA] +Ranking zbrodni: + +[GMSTOR] +Zachowanie gry + +[PREBRF] +Poprzednie zapisy + +[CNTLS] +Sterowanie + +[MUSMEN] +Muzyka/dźwięki + +[GAMSET] +Ustawienia gry + +[LANGUA] +Język + +[DSPLAY] +Ekran + +[DEBUGM] +Menu funkcji debugowania + +[QUITOP] +Wyjście z menu opcji + +[CONTRL] +Konfiguracja sterowania + +[SET1EN] +SetUp 1. Enabled + +[SET1] +SetUp 1. + +[SET2EN] +SetUp 2. Enabled + +[SET2] +SetUp 2 + +[SET3EN] +SetUp 3. Enabled + +[SET3] +SetUp 3 + +[SET4EN] +SetUp 4. Enabled + +[SET4] +SetUp 4 + +[GOBACK] +Wróć + +[SOUND] +DŹWIĘK + +[MUSVOL] +Głośność muzyki + +[SFXVOL] +Głośność efektów dźwiękowych + +[SCROPT] +OPCJE EKRANU + +[CTRSCR] +Wyśrodkowanie Ekranu + +[SCRFOR] +Format ekranu + +[GMSVLQ] +WCZYTAJ-ZAPISZ-WYJDŹ Z GRY + +[GMREST] +Ponowne uruchomienie gry + +[NOGMSV] +Zapisywanie stanu gry jest możliwe tylko w kryjówce. + +[DLFILE] +Skasować pliki Grand Theft Auto III + +[CHFILE] +WYBIERZ PLIK, KTÓRY MA ZOSTAĆ WCZYTANY + +[CHFIDL] +WYBIERZ PLIK, KTÓRY MA ZOSTAĆ SKASOWANY + +[SVCONF] +POTWIERDZENIE ZAPISU + +[LANGSL] +WYBÓR JĘZYKA + +[ENGLIS] +Polski + +[GERMAN] +Niemiecki + +[ITALIA] +Włoski + +[FRENCH] +Francuski + +[SPAIN] +Hiszpański + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Parse Heap + +[DBGFON] +CTheScripts::DbgFlag On + +[DBFOFF] +CTheScripts::DbgFlag Off + +[BGWHON] +Big White Debug Light - włączony + +[BGWOFF] +Big White Debug Light - wyłączony + +[DSTRON] +Debug Streaming Requests On + +[DSTROFF] +Debug Streaming Requests Off + +[PDRGON] +ShowPedRoadGroups On + +[PRGOFF] +ShowPedRoadGroups Off + +[CRRGON] +ShowCarRoad Group Włączone + +[CRGOFF] +ShowCarRoadGroups Wyłączone + +[CLZOON] +Wyłączone pokazywanie stref zniszczeń + +[CLZOOF] +Włączone pokazywanie stref zniszczeń + +[SHPLON] +gbShowCollisionPolys On + +[SHPLOF] +gbShowCollisionPolys Off + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (element testowy) + +[UNFRM1] +UnFormatMemCard 1 (element próbny) + +[GORLEV] +Poziom 'Krwawy' + +[SICASS] +Sick Fuck + +[SICSIC] +Sick Fucker + +[SCASSL] +Sick Fuck wybrany + +[SCSCSL] +Sick Fucker wybrany + +[PRVMEN] +Cele poprzednich misji + +[FORMEN] +Menu formatu + +[MEMTST] +Ekran TestKartPamięci + +[REGCAR] +Rejestracja KartaPamięci Jeden + +[TEFONE] +Próbne formatowanie karty pamięci 1 + +[TEUFON] +Próbne odformatowanie karty pamięci 1 + +[CRROOT] +Utwórz Katalog Główny + +[CRLDIC] +Tworzenie i wczytywanie ikon + +[FLFSGF] +Fill First File With Guff + +[PUSAVE] +Zapisz tylko grę + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +Zapisz grę + +[CNTSAV] +Nie można zapisać stanu gry. Jesteś w trakcie misji. + +[CNCSAV] +Nie można zapisać stanu gry. Jesteś w samochodzie. + +[CRMGSV] +Utwórz chroniony katalog magazynowy + +[MGSVCN] +Katalog magazynowy utworzony + +[MGSVNC] +Katalog magazynowy nieutworzony + +[YES] +Tak + +[NO] +Nie + +[X] +x + +[LAST] +Ostatnia wiadomość + +[FEDS_XB] +Wybierz + +[FEDS_ST] +klawisz START - WZNÓW + +[FEST_OO] +z + +[FEC_TUC] +Sterowanie wieżyczką + +[FEC_SM3] +Włączenie misji specjalnych (klawisz R3) + +[FEC_RS3] +Przełącz stacje radiowe (klawisz L3) + +[FEC_HO3] +Klakson (klawisz lewy SHIFT) + +[DIAB1] +'WYŚCIG' + +[DIAB2] +'PRZEŁAMAĆ LODY' + +[DIAB3] +'PRÓBA OGNIA' + +[DIAB4] +'WIELKI I ŻYLASTY' + +[DIAB1_A] +El Burro ma dla ciebie propozycję. Jeżeli jesteś zainteresowany, odszukaj budkę telefoniczną w Hepburn Heights. + +[DIAB1_C] +Niezły z ciebie kierowca! Jedź do wskazanej budki telefonicznej, a może El Burro da ci jakieś zajęcie. + +[DIAB1_1] +~g~3... 2... 1... NAPRZÓD! NAPRZÓD! NAPRZÓD! + +[DIAB1_4] +~g~Załatw sobie szybki wóz i jedź na miejsce startu. + +[DIAB1_3] +~r~Nie wygrałbyś nawet z własną babcią, LESZCZU! + +[DIAB1_2] +~g~Gratulacje, wygrywasz, uzyskując niesamowity czas: ~1~sekund. + +[FIRST] +~g~pierwszy + +[SECOND] +~g~drugi + +[THIRD] +~g~trzeci + +[FOURTH] +~g~4 + +[DIAB2_1] +~g~Zabierz teczkę z Harwood. + +[DIAB2_2] +~g~Odszukaj półciężarówkę lodziarza. + +[DIAB2_3] +~g~Zaparkuj samochód lodziarza na Atlantic Quays. + +[DIAB2_4] +~g~Naciśnij klawisz ~w~~k~~VEHICLE_HORN~~g~, aby włączyć sygnał reklamujący lody. + +[DIAB2_6] +~g~Naciśnij klawisz ~w~~k~~VEHICLE_HORN~~g~, aby włączyć sygnał reklamujący lody. + +[DIAB2_7] +~g~Naciśnij klawisz ~w~~k~~VEHICLE_HORN~~g~, aby włączyć sygnał reklamujący lody. + +[DIAB2_5] +~g~Wysiądź z samochodu, a następnie zdetonuj go za pomocą nadajnika. + +[YD1] +'SZUKAJ PUNKTÓW!' + +[YD2] +'RUCHOMY CEL' + +[YD3] +'KOLEKCJONER WOZÓW' + +[YD4] +'KRÓLESTWO NIEBIESKIE' + +[YD_P] +King Courtney prosi cię na słówko. Znajdź budkę telefoniczną w Aspatrii!! + +[YD1_A] +~w~Z tej strony King Courtney. + +[YD1_A1] +~w~Moja paczka, Yardies, potrzebuje kierowcy, a ty masz reputację bystrego faceta. + +[YD1_B] +~w~Jedź na wysypisko naprzeciwko stadionu i poczekaj na innych zawodników. + +[YD1_C] +~w~Moi ludzie pilnują punktów kontrolnych w całym Staunton. + +[YD1_D] +~w~Kierowca, który pierwszy dotrze do takiego miejsca, otrzymuje jeden punkt. Potem ścigamy się do następnego przystanku. + +[YD1_D1] +~w~Jeżeli zaliczysz więcej punktów niż inni kierowcy, być może będę miał dla ciebie zadanie. + +[YD1_E] +~g~Gotowi do wyścigu! + +[YD1_F] +~g~Minąłeś punkt startu - podoba mi się twój styl!!! + +[YD1_G] +~r~To jest WYŚCIG SAMOCHODOWY. Masz jechać SAMOCHODEM, IDIOTO! + +[YD1GO] +~g~START! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +$1000!! + +[Y1_1ST] +~g~Kończysz na pierwszym miejscu i pomyślnie zaliczasz ~1~ punktów kontrolnych! + +[Y1_2ND] +~y~Jesteś drugi, pomyślnie zaliczyłeś ~1~ punktów kontrolnych. ~r~Było blisko, ale trochę ci jeszcze brakuje. + +[Y1_3RD] +~r~Jesteś trzeci, pomyślnie zaliczyłeś ~1~ punktów kontrolnych. ~r~A mówiłeś, że jesteś niezły! + +[Y1_LAST] +~r~Jesteś ostatni! ~r~Tylko marnujesz mój czas, IDIOTO! + +[Y1_J1ST] +~y~Pierwsze miejsce ex aequo, pomyślnie zaliczyłeś ~1~ punktów kontrolnych. ~y~Nieźle, ale musisz być najlepszy z najlepszych, aby móc jeździć dla Królowej Lizzy! + +[Y1_J2ND] +~r~Drugie miejsce ex aequo, pomyślnie zaliczyłeś ~1~ punktów kontrolnych. Jechałeś jak wściekły goryl! + +[Y1JLAST] +~r~Ostatnie miejsce ex aequo! Wymądrzałeś się jak stary kierowca, ale kierowałeś jak stary przemądrzalec! + +[Y1_TEST] +SAMOCHÓD W WODZIE!! + +[YD2_A] +~w~Muszę sprawdzić, czy dajesz sobie radę z mokrą robotą. + +[YD2_A1] +~w~Zobaczymy, czy można ci zaufać. + +[YD2_B] +~w~Dwóch moich chłopców zaraz po ciebie przyjedzie, żeby zabrać cię na przejażdżkę + +[YD2_B1] +~w~i sprawdzić, czy naprawdę umiesz tyle, ile twierdzisz. + +[YD2_C] +~w~Jedziemy na mały wypad na Wzgórza Hepburn, żeby sprzątnąc paru śmierdziuchów z gangu Diablo, którzy wkurzali Królową Lizzy. + +[YD2_CC] +~w~Będziesz potrzebował gnata, trzymaj. + +[YD2_D] +~w~Ty kierujesz i strzelasz. My zadbamy, żeby nie zabrakło ci odwagi. + +[YD2_E] +~w~Jazda! + +[YD2_F] +~w~Oszukał nas! Dorwać jego zdradliwą dupę! + +[YD2_G1] +~w~Wzgórza Hepburn. Zabijmy paru śmierdzących Diablo... + +[YD2_G2] +~w~Tylko pamiętaj, ~r~ masz nie wysiadać z samochodu!!! + +[YD2_H] +~w~W porządku, wracamy na terytorium Yardies! JAZDA, SZYBCIEJ!! + +[YD2_L] +~w~Dobrze się spisałeś, żniwiarzu! + +[YD2_M] +~r~Rozwalił mój samochód! Załatwić go! + +[YD2_N] +~w~Posadź tyłek z powrotem w samochodzie! + +[YD3_A] +Masz porwać dla mnie kilka samochodów gangów tak, + +[YD3_A1] +abyśmy mogli uderzyć we wrogów na ich własnym terytorium. + +[YD3_B] +Potrzebuję mafijnego Sentinela, + +[YD3_B1] +Stingera Yakuzy oraz + +[YD3_B2] +Ogiera gangu Diablo. Wtedy będzie można uderzyć na każdego w Liberty. + +[YD3_C] +Zostaw je przy garażu w Newport, ale pamiętaj, + +[YD3_C1] +potrzebujemy tylko fury w dobrym stanie!!! + +[YD3_D] +Wolne miejsce na tekst + +[YD3_E] +~r~Już zdobyłeś samochód gangu Diablo! + +[YD3_F] +~r~Już zdobyłeś samochód mafii! + +[YD3_G] +~r~Już zdobyłeś samochód Yakuzy! + +[YD3_H] +~r~Zdobyłeś samochód gangu Diablo! + +[YD3_I] +~r~Zdobyłeś samochód mafii! + +[YD3_J] +~r~Zdobyłeś samochód Yakuzy! + +[YD3_K] +~r~Ten samochód to ruina! Musisz go naprawić! + +[YD3_L] +~g~Zabierz samochód do garażu! + +[YD3_M] +~r~Straciłeś wóz! Musisz zdobyć jeszcze jeden! + +[YD4_A] +Posłuchaj! + +[YD4_A1] +Jedź do Bedford Point. + +[YD4_A2] +W starym samochodzie jest coś, czego potrzebuję, pronto! + +[YD4_B] +LIST: Słyszałam, że ostatnio byłeś pilnym uczniem. Cóż, ja byłam pilną uczennicą. + +[YD4_C] +Czas, abyś poznał prawdziwą siłę HEROINY! Besos y fuderes, Catalina, xxx. + +[YD4_D] +PS. ZDYCHAJ KUNDLU! + +[YD4_1] +~g~Naćpani szaleńcy! + +[YD4_2] +~g~Zniszcz ciężarówki wariatów! + +[HM_1] +'AGRESYWNA JAZDA' + +[HM_2] +'ZABAWKOWY ZABÓJCA' + +[HM_3] +'ZDĄŻYĆ PRZED WYBUCHEM'v + +[HM_5] +'ROZRÓBA' + +[HOOD1_A] +Znajdź budkę telefoniczną w Wichita Gardens, to pogadamy o interesach. + +[HM1_A] +Yo! Z tej strony D-Ice z gangu Red Jacks! + +[HM1_C] +Te szczeniaki wyłażą na ulice i myślą tylko o tym, kogo by tu zastrzelić i co zaćpać. + +[HM1_3] +~g~'Dziewiątki' mają swoje terytorium w Wichita Gardens. + +[HM2_3] +Jeżeli uderzysz zdalnie sterowanym samochodzikiem w koła pojazdu, ładunek wybuchnie! + +[HM2_4] +Jeżeli samochodzik wyjedzie poza zasięg nadajnika, ładunek wybuchnie! + +[HM2_5] +~r~Samochodzik poza zasięgiem! + +[HM3_1] +~g~Zabierz samochód do warsztatu, ale uważaj! Jeżeli samochód zostanie mocno uszkodzony, ładunek może wybuchnąć! + +[HM3_2] +~g~Zwróć samochód właścicielowi. Jedź ostrożnie, wóz musi być w doskonałym stanie! + +[HM3_3] +~g~Napraw samochód! + +[HM4_D] +~g~Zdobądź samochód! + +[HM4_E] +TEKST NIEPOTRZEBNY + +[HM4_1] +~g~Jedź do miejsca, w któym rozsypał się ładunek. Musisz zebrać 30 sztabek. + +[HM4_2] +~g~Pamiętaj, kiedy samochód zrobi się ciężki, poturlaj się do garażu i wysyp ładunek. + +[HM5_3] +~r~Miałeś używać wyłącznie kija bejsbolowego! + +[HM5_4] +~r~Twój kontakt nie żyje! + +[MEA1] +'CWANIAK' + +[MEA2] +'ZŁODZIEJE' + +[MEA3] +'ŻONA' + +[MEA4] +'KOCHANEK' + +[MEAT1_A] +Znajomy powiedział mi, że potrafisz rozwiązywać problemy. Jeżeli chcesz zająć się moimi kłopotami, znajdź budkę telefoniczną w Trenton. + +[MEA1_B3] +~g~Spotkaj się z kierownikiem banku. + +[MEA1_B6] +~g~Zabierz samochód do zgniatarki, aby pozbyć się dowodów. Wysiądź z samochodu, a dźwig już się wszystkim zajmie. + +[MEA1_1] +~r~Kierownik banku nie żyje! + +[MEA1_2] +~r~Miałeś zniszczyć ten pojazd! + +[MEA1_3] +~g~Wysiądź z samochodu! + +[MEA1_4] +~r~Zgubiłeś kierownika banku! + +[MEA2_B3] +~g~Jedź po złodziei. + +[MEA2_B4] +~g~Zabierz ich do fabryki Delikatesów Pod Psem. + +[MEA2_B6] +~g~Przemaluj samochód, aby zatrzeć ślady. + +[MEA2_1] +~r~Miałeś zniszczyć ten pojazd! + +[MEA2_2] +~r~Złodziej nie żyje! + +[MEA2_4] +~r~Zgubiłeś jednego ze złodziei! + +[MEA3_B3] +~g~Jedź po panią Chonks. + +[MEA3_B6] +~g~Zabierz samochód i wrzuć go do wody, aby pozbyć się dowodów. + +[MEA3_1] +~r~Żona nie żyje! + +[MEA3_2] +~r~Miałeś wrzucić samochód do wody! + +[MEA3_3] +~r~Zgubiłeś żonę Marty'ego! + +[MEA4_B3] +~g~Zabierz kochanka żony. + +[MEA4_B6] +Na to już za późno, Marty. Miałeś szansę, ale teraz przejmuję twoją budę... + +[MEA4_1] +~r~Carlos nie żyje! + +[MEA4_3] +~r~Zgubiłeś Carlosa lichwiarza! + +[LOOK_A] +Naciśnij i przytrzymaj klawisz ~h~~k~~VEHICLE_LOOKLEFT~ ~w~lub klawisz ~h~~k~~VEHICLE_LOOKRIGHT~ ~w~, aby spojrzeć ~h~w lewo~w~ lub ~h~w prawo~w~ przez szyby pojadu. Naciśnij oba klawisze naraz, aby spojrzeć ~h~do tyłu~w~. + +[LOVE6_1] +~g~Teraz odciągnij gliniarzy od magazynu! + +[LOVE6_2] +~r~Nie udało ci się odciągnąć glin na wystarczającą odległość! + +[RM4_3] +~r~Kumpel Raya zwiał! + +[RM6_C] +Zdaje się, że CIA ma jakiś swój interes w utrzymaniu handlu PROCHAMI + +[RM6_C1] +i nie spodobało im się, że zadarliśmy z Kartelem. + +[C_PASS] +ZAGROŻENIE ZLIKWIDOWANE + +[CTUTOR] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~, aby włączyć lub wyłączyć misje patrolowe. + +[CTUTOR2] +Naciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~, aby włączyć lub wyłączyć misje patrolowe. + +[COPCART] +~g~Masz ~1~ sekund na powrót do radiowozu albo misja zakończy się. + +[C_FAIL] +Misja patrolowa zakończona! + +[C_CANC] +~r~Misja patrolowa anulowana! + +[C_ESCP] +~r~Podejrzany uciekł! + +[C_TIME] +~r~Twój czas w roli stróża prawa minął! + +[C_VIGIL] +PREMIA PATROLOWA!! + +[A_FAIL2] +~r~Twoje ślamazarność kosztowała pacjenta życie! + +[A_FAIL3] +~r~Pacjent nie żyje! + +[A_PASS] +Uratowany! + +[F_FAIL2] +~r~Spóźniłeś się! + +[A_COMP2] +Ty chyba nigdy się nie męczysz! + +[RM2_M] +Jak będziesz potrzebował spluwy, wpadaj do mnie jak w dym i bierz z szafek, co ci się podoba. + +[HEAL_A] +Twój ~h~poziom życia~w~ jest wyświetlony na pomarańczowo w prawym górnym narożniku ekranu. + +[YD1_CNT] +~1~ z 15! + +[FM1_9] +~g~Przed nami miejsce imprezy - wysadź Marię przed budynkiem. + +[FM1_Y] +~w~Wiesz, dawno się tak dobrze nie bawiłam, a ty traktowałeś mnie naprawdę dobrze. Z szacunkiem i w ogóle.. + +[FM1_AA] +~w~Chyba już pójdę. W takim razie - do zobaczenia! + +[NOCONTE] +Aby kontynuować, proszę ponownie umieścić wtyczki kontrolera analogowego (DUALSHOCK@) lub kontrolera analogowego (DUALSHOCK@2) w porcie kontrolerów gry nr 1. + +[WRCONT] +Kontroler w porcie nr 1 nie jest rozpoznany. Gra Grand Theft Auto III wymaga kontrolera analogowego (DUALSHOCK@) lub kontrolera analogowego (DUALSHOCK@2). + +[WRCONTE] +Kontroler w porcie nr 2 nie jest rozpoznany. Gra Grand Theft Auto III wymaga kontrolera analogowego (DUALSHOCK@) lub kontrolera analogowego (DUALSHOCK@2). + +[WRONGCD] +Niewłaściwa płyta. Proszę włożyć właściwą płytę. + +[NOCD] +Nie znaleziono GTAIII CD w czytniku. + +[OPENCD] +Taca napędu jest wysunięta. Wsuń tacę napędu CD-ROM. + +[CDERROR] +Błąd w odczycie płyty Grand Theft Auto III. + +[RESTART] +Trwa rozpoczynanie nowej gry + +[GA_3] +Koniec z promocjami. 1000 dolców za malowanie! + +[GA_1] +Coś ty! Nawet nie dotknę takiego trefnego towaru! + +[GA_1A] +Wróć, kiedy będziesz miał chwilę wolnego czasu... + +[S_PROM2] +Garaż znajdujący się za sąsiednimi drzwiami służy do przechowywania pojazdów podczas zapisywania stanu gry. + +[STOCK] +brak towaru + +[FM1_O] +~w~Myślę, że znajdziemy go nad brzegiem morza, w okolicach Chinatown. + +[EBAL_B] +To właśnie tu. Zjedźmy z ulicy i poszukajmy jakichś ciuchów, żeby zmienić te więzienne łachy! + +[EBAL_G] +To jest właśnie klub 'U Luigiego'. Obejdziemy tę budę i skorzystamy z tylnych drzwi. + +[AM4_3] +A więc to ty jesteś nowym chłopcem na posyłki Asuki? + +[AM4_4] +Masz forsę? Mam nadzieję, że wszystko jest jak trzeba? + +[AM4_5] +Wiem, co sobie myślisz, następny sprzedajny gliniarz. + +[AM4_6] +Cóż, każdy orze jak może. + +[AM4_7] +Straciłem ostatnio paru partnerów i ci frajerzy z wydziału wewnetrznego zaczęli coś przewąchiwać. + +[AM4_8] +Żeby tylko nie wyniuchali moich śladów. + +[AM4_9] +To miasto to jeden wielki otwarty ściek. + +[AM4_10] +Przyda mi się pomoc kogoś niezrzeszonego. + +[AM4_11] +Jeżeli masz jakiś interes, wiesz gdzie mnie znaleźć. + +[CAM_A] +Wciskaj klawisz ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~, aby zmieniać tryby pracy ~h~kamery ~w~, zarówno w samochodzie jak i poza nim. + +[CAM_B] +Wciskaj klawisz ~h~strzałki w gorę~w~ oraz ~h~strzałki w dół~w~, aby zmieniać tryby pracy ~h~kamery ~w~, zarówno w pojeździe jak i poza nim. + +[KM2_1] +~g~Napraw samochód. Wóz musi być w idealnym stanie. + +[LM3_6] +Joey... + +[LM3_6A] +Znowu będę mogła się pobawić twoim drągiem? + +[LM3_9A] +może będę miał dla ciebie jakieś zajęcie. + +[LM3_9B] +W porządku? + +[AWAY2] +~r~Uciekli. + +[AWAY] +~r~Zwiał stąd, gdzie pieprz rośnie! + +[JM6_1] +Jedź do banku na głównej ulicy. + +[GA_6B] { re3 change } +Zaparkuj wóz, włącz mechanizm klawiszem ~h~~k~~VEHICLE_FIREWEAPON~~w~ i W NOGI! + +[GA_7B] { re3 change } +Uaktywnij bombę za pomocą klawisza ~h~~k~~VEHICLE_FIREWEAPON~~w~. Bomba wybuchnie w momencie włączenia silnika. + +[BAT1] +~g~Podnieś kij bejsbolowy! + +[EBAL_O] +Jeśli nic nie schrzanisz, może znajdzie się dla ciebie jakaś praca. A teraz zjeżdżaj! + +[HELP9_B] +Naciśnij klawisz~h~ ~k~~PED_FIREWEAPON~~w~, aby oddać ~h~strzał~w~ z karabinu snajperskiego. + +[HELP9_C] +Naciśnij klawisz~h~ ~k~~PED_FIREWEAPON~~w~, aby oddać ~h~strzał~w~ z karabinu snajperskiego. + +[JM6_8] +~r~Straciłeś wszystkich złodziei! + +[COLT_IN] +Ammu-nacja zaczyna sprzedaż pistoletów! + +[TAXI2] +~r~Koniec czasu! + +[TAXI3] +~r~Przerażony pasażer ucieka! + +[TAXI7] +~r~Twoja taksówka to ruina, połataj ją trochę. + +[TAXI4] +Kurs wykonany! + +[TAXI5] +PREMIA ZA SZYBKOŚĆ!!! + +[TAXI6] +Koniec misji w taksówce + +[FRANGO] +~g~Salvatore chce, abyś najpierw pomógł Toniemu załatwić porachunki z Triadami! + +[PAGEB12] +Łapówka policyjna dostarczona do kryjówki + +[PAGEB13] +Życie dostarczone do kryjówki + +[PAGEB14] +Adrenalina dostarczona do kryjówki + +[KM1_4] +~g~Do tej roboty przydałby się radiowóz! + +[CAT1_B] +przynieś 500.000 $ do Willi w Cedar Grove. + +[JM2_C] +Gość ma budę z makaronem w Chinatown. + +[RM6_1] +Tu masz klucz do dziupli. + +[RM6_2] +Znajdziesz tam trochę forsy i 'zapasów', które zbierałem na czarną godzinę. + +[RM6_3] +Trzymaj się. + +[FE_INIP] +Inicjalizacja i wczytywanie menu pauzy... Proszę czekać. + +[FESZ_CA] +Anuluj + +[FESZ_QU] +Wyjście + +[FESZ_L1] +Gra została pomyślnie zapisana. + +[FESZ_L2] +Gra została zapisana w pliku o nazwie: + +[FESZ_OK] +OK + +[FES_LGA] +Wczytaj grę + +[FES_NGA] +Nowa gra + +[FES_CAN] +Anuluj + +[FESZ_QL] +Wszelkie niezapisane osiągnięcia i zdobycze w trwającej grze zostaną utracone. Wczytać grę? + +[FESZ_QD] +Czy skasować ten zapis gry? + +[FESZ_QO] +Czy nadpisać tę grę na starszym pliku? + +[FESZ_QR] +Czy jesteś pewien, że chcesz rozpocząć nową grę? Wszelkie osiągnięcia i postępy poczynione od momentu ostatniego zapisu gry zostaną utracone. Kontynuować? + +[FESZ_QS] +KONTYNUOWAĆ ZAPIS? + +[T4X4_1] +'PLAC ZABAW PATRIOTÓW' + +[T4X4_2] +'PRZEJAŻDŻKA W PARKU' + +[T4X4_3] +'W POTRZASKU!' + +[MM_1] +'KOSZMAR WIELU PIĘTER' + +[T4X4_1A] +~g~Masz ~y~5 minut~g~ na zaliczenie ~y~15~g~ punktów kontrolnych. ~g~Możesz zaliczać je w ~y~DOWOLNEJ KOLEJNOŚCI. + +[T4X4_1B] +~1~ z 15! + +[T4X4_1C] +~y~PRZEJEDŹ PRZEZ~g~ pierwszy punkt kontrolny, aby uruchomić odliczanie czasu. ~g~Zaliczenie każdego punktu jest premiowane dodatkowymi ~y~20 SEKUNDAMI~g~ + +[T4X4_2A] +~g~Masz ~y~2 minuty~g~ na zaliczenie ~y~12~g~ punktów kontrolnych. ~g~Możesz zaliczać je w ~y~DOWOLNEJ KOLEJNOŚCI. + +[T4X4_2B] +~1~ z 12! + +[T4X4_2C] +~y~PRZEJEDŹ PRZEZ~g~ pierwszy punkt kontrolny, aby uruchomić odliczanie czasu. ~g~Zaliczenie każdego punktu jest premiowane dodatkowymi ~y~10 SEKUNDAMI~g~ + +[T4X4_3A] +~g~Masz ~y~5 minut~g~ na zaliczenie ~y~20~g~ punktów kontrolnych. ~g~Możesz zaliczać je w ~y~DOWOLNEJ KOLEJNOŚCI. + +[T4X4_3B] +~y~PRZEJEDŹ PRZEZ~g~ pierwszy punkt kontrolny, aby uruchomić odliczanie czasu. ~g~Zaliczenie każdego punktu jest premiowane dodatkowymi ~y~15 SEKUNDAMI~g~ + +[T4X4_3C] +~1~ z 20! + +[T4X4_F] +~r~Wymiękasz! Może lepiej sprawdzisz się w wyścigach na hulajnodze?! + +[MM_1_A] +~g~Masz ~y~2 minuty~g~ na zaliczenie ~y~20 punktów kontrolnych~g~ w całym obiekcie! ~g~Możesz zaliczać punkty w ~y~DOWOLNEJ KOLEJNOŚCI. + +[MM_1_B] +~1~ z 20! + +[MM_1_C] +~g~To oznacza 20 sekund plus ~y~5 SEKUND~g~ premii za każdy zaliczony punkt. ~g~Zegar zaczyna odliczanie ~y~NATYCHMIAST. + +[FM2_14] +~r~Zbliżyłeś się za bardzo i wystraszyłeś Kudłatego! + +[FM2_15] +~g~Nie zbliżaj się zbytnio, bo Kudłaty zacznie coś podejrzewać! + +[UPSIDE] +~r~Przewróciłeś samochód! + +[FM2_16] +STRACHOMETR: + +[LM3_11] +~g~Misty nie będzie jeździć autobusem, załatw inny pojazd! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Wóz strażacki + +[TRASHM] +Śmieciożer + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Muł + +[CHEETAH] +Cheetah + +[AMBULAN] +Karetka pogotowia + +[FBICAR] +Samochód FBI: + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taksówka + +[KURUMA] +KURUMA + +[BOBCAT] +Bobcat + +[WHOOPEE] +Pan Smakołyk + +[BFINJC] +Zastrzyk BF + +[POLICAR] +Policja + +[ENFORCR] +Enforcer + +[SECURI] +Konwojowóz + +[BANSHEE] +Demon + +[PREDATR] +Predator + +[BUS] +Autobus + +[RHINO] +Hipcio + +[BARRCKS] +Koszary OL + +[TRAIN] +Pociąg + +[HELI] +Helikopter + +[DODO] +Dodo + +[COACH] +Autokar + +[CABBIE] +Taksówka + +[STALION] +Ogier + +[RUMPO] +Rumpo + +[RCBANDT] +Bandziorek + +[BELLYUP] +Ciężarówka Triady + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Sentinel mafii + +[YARDICR] +Lobo gangu Yardie + +[YAKUZCR] +Stinger gangu Yakuza + +[DIABLCR] +Ogier gangu Diablo + +[COLOMCR] +Krążownik Kartelu + +[HOODSCR] +Rumpo XL gangu Hoods + +[AEROPL] +Samolot + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +ZABAWKI + +[FEST_DF] +Odległość przebyta pieszo (w milach) + +[FEST_DC] +Odległość przebyta samochodem (w milach) + +[FESTDFM] +Odległość przebyta pieszo (w metrach) + +[FESTDCM] +Odległość przebyta samochodem (w metrach) + +[FEST_R1] +Plac Zabaw Patriotów w sekundach + +[FEST_R2] +Przejażdżka w parku w sekundach + +[FEST_R3] +W Potrzasku! w sekundach + +[FEST_RM] +Koszmar Wielu Pięter w sekundach + +[FEST_LS] +Ludzie uratowani przez karetkę + +[FEST_CC] +Przestępcy zabici podczas misji patrolowych + +[FEST_FE] +Liczba ugaszonych pożarów + +[FEST_LF] +Najdłuższy lot dodo + +[FEST_BD] +Najlepszy czas rozbrojenia bomby + +[FEST_RP] +Wykonane rozwałki: + +[FEST_MP] +Wykonane misje + +[FEST_BB] +Szukaj Punktów + +[FEST_H0] +Najwięcej punktów kontrolnych + +[FEST_GC] +Łączna liczba pojazdów gangów: + +[FEST_H1] +Diabelska demolka + +[FEST_H2] +Mafijna masakra + +[FEST_H3] +Krwawe kasyno + +[FEST_H4] +Rumpo-rozróba + +[USJI1] +TEKST DŁUŻEJ NIEPOTRZEBNY + +[USJI2] +TEKST DŁUŻEJ NIEPOTRZEBNY + +[USJI3] +TEKST DŁUŻEJ NIEPOTRZEBNY + +[USJ] +PREMIA ZA NIETYPOWY SKOK! + +[SPRAY] +Wprowadź samochód do warsztatu lakierniczego, aby obniżyć swój ~h~poziom złek sławy~w~, ~h~naprawić~h~ oraz przemalować~w~ swój wóz. Koszt - ~h~$1000. + +[HM1_1] +~g~Załatw 20 Purpurowych Dziewiątek w 2 minuty 30 sekund. + +[KM1_8A] { re3 change } +Naciśnij klawisz ~h~ ~k~~VEHICLE_FIREWEAPON~ ~w~, aby ~h~aktywować bombę.~w~ Nie zapomnij oddalić się od miejsca eksplozji. + +[KM1_8D] { re3 change } +Naciśnij klawisz ~h~ ~k~~VEHICLE_FIREWEAPON~ ~w~, aby ~h~aktywować bombę.~w~ Nie zapomnij oddalić się od miejsca eksplozji. + +[KM1_12] +~g~Odwieź go do dojo, ale najpierw pozbądź się gliniarzy! + +[RATNG1] +Kieszonkowiec + +[RATNG2] +Mięśniak + +[RATNG3] +Łotr + +[RATNG4] +Hazardzista + +[RATNG5] +Zbir + +[RATNG6] +Kierowca + +[RATNG7] +Twardziel do wynajęcia + +[RATNG8] +Oszust + +[RATNG9] +Współpracownik + +[RATNG10] +Sprzątacz + +[RATNG11] +Zabójca + +[RATNG12] +Złota rączka + +[RATNG13] +Egzekutor + +[RATNG14] +Capo + +[RATNG15] +Szef + +[1010] +~r~Twój pojazd dachował + +[1011] +~r~Twój pojazd dachował + +[1012] +~r~Twój pojazd dachował + +[1013] +~r~Twój pojazd dachował + +[1014] +~r~Twój pojazd dachował + +[JM4_10] +Słuchaj, młody! Najpierw zawieź mnie do pralni w chińskiej dzielnicy. Mam małą sprawę do załatwienia. + +[JM4_11] +Praczki przestały płacić haracz za ochronę. + +[JM4_12] +Tylko uważaj na wóz, Joey dopiero poskładał ten szmelc. + +[JM4_13] +Więc bez żadnych numerów, OK? + +[KM4_11] +~g~Odwieź pieniądze do kasyna! + +[FEF_BR2] +Możesz przypomnieć sobie fabułę gry, czytając zebrane dotąd streszczenia celów misji. + +[TRAIN_1] +Stacja Kurowski + +[TRAIN_2] +Stacja Rothwell + +[TRAIN_3] +Stacja Baillie + +[SUBWAY1] +Portland Station + +[SUBWAY2] +Rockford Station + +[SUBWAY3] +Staunton South Station + +[SUBWAY4] +Shoreside Terminal + +[MEA4_2] +~r~Marty Chonks nie żyje! + +[SPRAY1] +Wprowadź samochód do warsztatu lakierniczego, aby obniżyć swój ~h~poziom złej sławy~w~, ~h~naprawić~h~ oraz przemalować~w~ swój wóz. Koszt - ~h~$1000~w~. Tym razem zrobimy to za darmo. + +[JM4_A] +Tak, wiem Toni, naprawdę nieźle ją sobie wychowałem. Aż mruczy z zadowolenia, kapujesz? + +[JM4_5] +Wpadnij później to damy im coś do prania - ich własne pokrwawione gacie! + +[AMMU_A] +Luigi mówił, że potrzebujesz gnata... + +[AMMU_B] +Joey wspominał, że potrzebna ci artyleria... + +[AMMU_C] +Idź na tył sklepu. Na podwórzu zostawiłem dla ciebie dziewiątkę. + +[AMMU_D] +Mam wszystko, co potrzeba do obrony własnego gospodarstwa domowego. + +[AMMU_E] +Chcesz jeszcze pozwolenie? + +[AMMU_F] +Nie musisz pokazywać dowodu, wyglądasz na wiarygodnego gościa. + +[DETON] +DETONACJA: + +[DRIVE_A] { re3 change } +Wybierz jako broń uzi i wsiądź do pojazdu. Następnie spójrz w lewo lub w prawo - aby otworzyć ogień, naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[DRIVE_B] { re3 change } +Wybierz jako broń uzi i wsiądź do pojazdu. Następnie spójrz w lewo lub w prawo - aby otworzyć ogień, naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[RECORD] +~g~NOWY REKORD! + +[NRECORD] +~r~NIE MA NOWEGO REKORDU! + +[RCHELP] { re3 change } +Naciśnij klawisz ~k~~VEHICLE_FIREWEAPON~ lub uderz zdalnie sterowanym samochodzikiem w koła pojazdu, aby spowodować eksplozję. + +[RCHELPA] { re3 change } +Naciśnij klawisz ~k~~VEHICLE_FIREWEAPON~ lub uderz zdalnie sterowanym samochodzikiem w koła pojazdu, aby spowodować eksplozję. + +[RC_1] +Masz 2 minuty, aby wysadzić tyle samochodów gangu Diablo, ile tylko się da! + +[RC_2] +Masz 2 minuty, aby wysadzić tyle samochodów mafii, ile tylko się da! + +[RC_3] +Masz 2 minuty, aby wysadzić tyle samochodów Yakuzy, ile tylko się da! + +[RC_4] +Masz 2 minuty, aby wysadzić tyle samochodów gangu Yardie, ile tylko się da! + +[RC_5] +Masz 2 minuty, aby wysadzić tyle samochodów gangu Hoods, ile tylko się da! + +[RC_6] +Masz 2 minuty, aby wysadzić tyle samochodów Kartelu, ile tylko się da! + +[RAMPAGE] +ROZWAŁKA!! + +[RAMP_P] +ROZWAŁKA WYKONANA! + +[RAMP_F] +ROZWAŁKA NIEUDANA! + +[PAGE_00] +. + +[PAGE_01] +Załatw ~1~ludzi z gangu Diablo w 120 sekund! + +[PAGE_02] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_03] +Zabij ~1~ członków mafii w ciągu 120 sekund! + +[PAGE_04] +Zabij ~1~ członków Triady w ciągu 120 sekund! + +[PAGE_05] +Zabij ~1~ członków Triady w ciągu 120 sekund! + +[PAGE_06] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_07] +Rozwal ~1~ łebków z gangu Yardie w ciągu 120 sekund! + +[PAGE_08] +Spal ~1~ członków Yakuzy w ciągu 120 sekund! + +[PAGE_09] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_10] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_11] +Skasuj ~1~ członków gangu Yardie w ciągu 120 sekund! + +[PAGE_12] +Podpal ~1~ członków Yakuzy w ciągu 120 sekund! + +[PAGE_13] +Wysadź w powietrze ~1~ członków gangu Yardie w ciągu 120 sekund! + +[PAGE_14] +Usmaż ~1~ Kolumbijczyków w ciągu 120 sekund. + +[PAGE_15] +Rozjedź ~1~ członków gangu Hoods w ciągu 120 sekund! + +[PAGE_16] +Zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_17] +Rozjedź samochodem ~1~ Kolumbijczyków w ciągu 120 sekund! + +[PAGE_18] +Rozjedź i zniszcz ~1~ pojazdów w ciągu 120 sekund! + +[PAGE_19] +Urwij ~1~ głów Kolumbijczyków w ciągu 120 sekund! + +[PAGE_20] +Obetnij głowy ~1~ członkom gangu Hoods w ciągu 120 sekund! + +[JM1_A] +Hej, umieram z nudów! Kiedy w końcu mnie przelecisz? + +[JM1_B] +Za chwileczkę, złotko! Muszę się zająć jedną drobną kwestią... + +[JM1_C] +Mam dla ciebie robótkę, kolego. + +[JM1_D] +Bracia Forelli od dawna wiszą mi kasę. Od zbyt dawna. + +[JM1_E] +Trzeba dać im lekcję szacunku. + +[JM1_F] +Buźka Forelli napycha właśnie swój bęben w Bistro w St. Marks, + +[JM1_G] +więc ukradnij jego samochód i zabierz go do warsztatu 8-Balla w Harwood. + +[JM1_H] +Znasz 8-Balla, nie? + +[JM1_I] +Kiedy 8-Ball założy w samochodzie ładunek, odprowadź furę na to samo miejsce, z którego ją wziąłeś. + +[JM1_J] +Potem usiądź w bezpiecznej odległości i podziwiaj fajerwerki. + +[JM1_K] +Tylko się pospiesz, grubas nie będzie przecież jadł cały dzień. + +[CAT2_A1] +Jazda, głupia dziwko! + +[CAT2_A] +Trzeba zadać sobie pytanie: czy przyjechałeś ratować Marię czy też żeby spotkać się ze mną? + +[CAT2_B] +Mam dla ciebie wiadomość: + +[CAT2_B2] +romans z tobą to był wyłącznie interes, za to zastrzelę cię dla przyjemności. + +[CAT2_C] +Jesteś muy peccino amigo! + +[CAT2_D] +Rzuć forsę. + +[CAT2_E] +Ostatnio byłeś bardzo pilnym uczniem! + +[CAT2_E2] +Ale nic się nie nauczyłeś. Mnie nie wolno ufać. + +[CAT2_E3] +Zabić tego idiotę. + +[CAT2_J] +Poderwij ten złom w powietrze! + +[HM5_1] +Yo, Ice mówił, że przyjdziesz. Teraz zasady: bierzemy tylko bejsbole - bez spluw i bez samochodów. + +[HM5_5] +To bitwa o honor, czaisz? + +[HELP14] +Aby podnieść broń, po prostu wejdź na nią. Nie możesz podnieść broni, jeżeli siedzisz w samochodzie. + +[CRUSH] +Zaparkuj w oznaczonym miejscu i wysiądź z pojazdu. Samochód zostanie zgnieciony. + +[DIAB2_B] +Gang brzydkich panów zagroził, że pozbawi mnie mojego gwiazdora, jeżeli nie odpalę im doli. + +[DIAB2_C] +Zatańczyli z niewłaściwym człowiekiem, amigo. + +[DIAB2_D] +Oni mają słabość do lodów. + +[DIAB2_E] +Odszukaj bombę, którą zostawiłem w Harwood, + +[DIAB2_F] +porwij jeden z samochodów sprzedających lody w całym mieście, + +[DIAB2_G] +a potem zwab tych idiotów reklamowym sygnałem lodziarza. + +[DIAB2_H] +Ukrywają się w magazynach przy Atlantic Quay. + +[DIAB3_A] +Jacyś niegrzeczni członkowie Triady ukradli wczoraj w nocy mój samochód, + +[DIAB3_B] +rozbili go i zostawili, aby się dopalił. + +[DIAB3_C] +W bagażniku miałem kilka wyjątkowo cennych pamiątek - + +[DIAB3_D] +prawdziwe rzadkie okazy, których nie da się niczym zastąpić, mój przyjacielu. + +[DIAB3_E] +Na granicy Chinatown ukryłem dla ciebie naprawdę potężną broń. + +[DIAB3_F] +Skorzystaj z niej i naucz wandali z Triady, co oznacza zasłużony gniew El Burro. + +[DIAB3_1] +ZABIJ 25 CZŁONKÓW TRIADY + +[DIAB4_A] +Jakiś marny złodziejaszek ukradł mi półciężarówkę z najnowszym wydaniem moich magazynów... Prosto z drukarni! + +[DIAB4_B] +Ale ten zaćpany idiota nie zamknął tylnych drzwi + +[DIAB4_C] +i teraz moja starannie opracowana literatura dla dorosłych, + +[DIAB4_D] +opatrzona wysmakowanymi zdjęciami, wala się po całym Liberty! + +[DIAB4_E] +Weź półciężarówkę i jedź śladem magazynów 'Donkey Daje Całemu Dallas' część 1, 2 i 3. + +[DIAB4_F] +Zbieraj wszystko, co znajdziesz. + +[DIAB4_G] +Kiedy dotrzesz po tropie do tego złodziejskiego ĆPUNA, załatw go! + +[DIAB4_H] +A potem zawieź moje książeczki z Donkey do Magazynów XXX w Dzielnicy Czerwonych Świateł. + +[DIAB4_1] +~g~Zabierz samochód na zaplecze Magazynów XXX. + +[HM1_E] +Pokaż tym zaćpanym siuśkom, na czym polega prawdziwa jazda samochodem. + +[HM1_H] +Usuń mi te 'Dziewiątki' z widoku! + +[HM2_A] +Te 'Dziewiątki' nadal nadeptują mi na odcisk. + +[HM2_B] +Szczeniaki załatwiły sobie samochody opancerzone i sprzedają PROCHY... + +[HM2_C] +...naszym niewinnym czarnym braciom. + +[HM2_D] +Zostawiłem dla ciebie samochód. + +[HM2_E] +W środku znajdziesz parę zabawek, które pomogą ci dać siuśkom nauczkę... + +[HM3_A] +Jakiś samobójca wsadził bombę do mojej gabloty. + +[HM3_B] +Jeżeli stracę tę furę, mogę pożegnać się z moją reputacją na ulicach. + +[HM3_C] +Weź mój wóz i zabierz go do warsztatu w St. Marks, brachu. + +[HM3_D] +Niech chłopaki się nim zajmą i rozbroją bombę. + +[HM3_E] +Zegar już odlicza czas, a bomba chyba jest uszkodzona. + +[HM3_F] +Wpadniesz w jedną dziurę za dużo i to cacko wyleci w powietrze. + +[HM3_G] +Na co jeszcze czekasz? + +[HM4_A] +Yo, na lotnisku im. Francisa właśnie roztrzaskał się samolot Banku Narodowego. + +[HM4_B] +Platyna wala się po całym pasie startowym. + +[HM4_C] +Załatw samochód i zgarnij tyle, ile tylko się da. + +[HM4_F] +Możesz wysypać platynę przy jednym z moich garaży. + +[HM4_G] +Platyna jest cholernie ciężka, więc nie zdziw się, kiedy przeciążysz gablotę i fura będzie się wlokła jak ślimak. + +[HM4_H] +Lepiej regularnie zrzucaj towar przy jakimś garażu. + +[HM5_A] +Z gangu 'Dziewiątek' zostały już tylko niedobitki... + +[HM5_B] +ale nadal chcą się pobawić. + +[HM5_C] +Zgodzili się na pojedynek twarzą w twarz. + +[HM5_D] +Ich banda przeciwko dwóm spośród nas, a raczej... + +[HM5_E] +przeciwko tobie i jeszcze komuś + +[HM5_F] +Poszedłbym z tobą, ale... + +[HM5_G] +jeszcze przez trzy miesiące mam wyrok w zawieszeniu i nie mogę rozrabiać, + +[HM5_H] +sam rozumiesz. + +[HM5_I] +Weźmiesz ze sobą mojego młodszego brata. + +[HM5_J] +On ci pokaże, gdzie jesteście umówieni. + +[MEA1_B] +Nazywam się Chonks, Marty Chonks. + +[MEA1_C] +Prowadzę Delikatesy Pod Psem, tuż za rogiem. + +[MEA1_D] +Mam kłopoty z kasą, ale kto ich dzisiaj nie ma? + +[MEA1_E] +Jestem umówiony z kierownikiem mojego banku. + +[MEA1_F] +Ten cwaniaczek cały czas podnosi odsetki mojego kredytu, żeby móc odkroić swoją działkę. + +[MEA1_G] +Weź mój samochód, jedź po niego i przywieź go tutaj. + +[MEA1_H] +Mam małą niespodziankę dla tego krwiopijcy!! + +[MEA2_A] +Wynająłem paru złodziejaszków, aby włamali się do mojego mieszkania + +[MEA2_C] +Te złodziejskie szumowiny grożą, że zakapują mnie w firmie ubezpieczeniowej, + +[MEA2_D] +jeżeli nie odpalę im doli. + +[MEA2_E] +To się po prostu w głowie nie mieści! + +[MEA2_F] +W fabryce zostawiłem samochód. + +[MEA2_G] +Skorzystaj z niego i zabierz złodziei z Dzielnicy Czerwonych Świateł. + +[MEA2_H] +Potem przywieź ich do fabryki. Tam wytłumaczę im mój punkt widzenia w tej sprawie. + +[MEA3_A] +Mój interes zbankrutuje, jeżeli szybko nie dostanę do rąk większej gotówki. + +[MEA3_B] +Moja żona ma sporą polisę ubezpieczeniową, a i tak przez całe życie tylko wyciągała ode mnie pieniądze. + +[MEA3_C] +Zostawiłem samochód w umówionym miejscu. + +[MEA3_D] +Jedź po moją żonę do pawilonu 'Klasyczny Manicure' i przywieź ją do fabryki. + +[MEA4_A] +Cholera, wpakowałem się w tarapaty! + +[MEA4_B] +Okazuje się, że moja żona romansowała z gościem, któremu wiszę pieniądze. + +[MEA4_C] +Jest mocno wkurzony i chce mi się zrewanżować! + +[MEA4_E] +on myśli, że chcę oddać mu kasę... + +[MEA4_F] +ale mnie się zdaje... + +[MEA4_G] +że do misek psów z Liberty jeszcze w tym miesiącu trafi kolejny rodzaj mięska! + +[WELCOME] +WITAMY W + +[HM1_2] +~g~Zdobądź samochód! Pamiętaj, że liczą się tylko kolesie rozjechani samochodem! + +[HELP8_B] +Naciśnij klawisz~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, aby ~h~przybliżyć ~w~widok przez lunetkę karabinu oraz klawisz~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, aby ~h~oddalić~w~ widok. + +[LRQC_1] +Muszę, hm, porozmawiać z Asuką. + +[LRQC_2] +Może wyskoczysz na spacer po mieście? + +[LRQC_3] +Musisz znaleźć sobie jakąś kryjówkę. + +[LRQC_4] +W Belville jest magazyn, który powinien ci odpowiadać. + +[LRQC_5] +Kiedy będziesz gotowy, wróć do mojego apartamentu, + +[LRQC_6] +to pogadamy, co robić dalej. + +[JM6_5] +~g~Musisz załatwić pojazd, którym uciekniemy, idioto! + +[JM2_F] +Jeżeli potrzebujesz giwery, to idź na zaplecze Amu-Nacji naprzeciwko stacji metra. + +[LOVE4_7] +~g~Na Wyspie Staunton jest jakiś plac budowy, może to właśnie tam zabrali pakunek. + +[LOVE4_8] +~g~Aby otworzyć ten garaż, musisz mieć samochód. + +[TSCORE] +ZAROBEK: $~1~ + +[AM1_9] +~r~Salvatore uciekł z powrotem do klubu 'U Luigiego'! + +[AM1_6] +~g~Jeżeli będziesz kręcił się wokół klubu Luigiego, to mafia z pewnością cię wypatrzy! + +[TM2_3] +~g~To pułapka! Załatw ich!!! + +[FM4_1] +Tu mówi Maria. Ten samochód to pułapka! Spotkaj się ze mną na południowym końcu Mostu Callahan. + +[JM1_7] +~g~Zamknij drzwi samochodu! Mike może coś zwąchać! + +[KM5_1] +~g~DILER ROZJECHANY!!! + +[KM5_6] +~g~Musisz zamordować co najmniej 8 dilerów z gangu Yardie. + +[KM5_7] +~g~Zabijaj jak najszybciej! Kiedy sprzedadzą cały towar, pochowają się w swoich norach! + +[RM3_8] +~r~Ten samochód to tylko przynęta!! + +[LM3_8] +Cześć, jestem Joey. + +[LM3_9] +Luigi mówił, że można ci ufać, więc wpadnij później, + +[KM3_5] +~g~Naciśnij klakson, aby zacząć rozmowy. + +[LOVE7] +ZNIKNIĘCIE LOVE'A + +[LOVE2_5] +~g~Z Kenji'ego została już tylko kupa mięsa na twoje masce! Uciekaj z Newport i pozbądź się samochodu! + +[AS2_11] +~g~~1~ Z 9! + +[GARAGE1] +~g~Wysiądź z samochodu i wyjdź na zewnątrz. + +[KM3_11] +~g~Kartel został zaatakowany, a teczka nie została odzyskana. + +[KM3_12] +~g~Zabij wszystkich Kolumbijczyków, zniszcz pojazdy i odzyskaj teczkę. + +[KM3_13] +~g~Odwieź teczkę do kasyna. + +[RM5_6] +~g~Prawie go masz! Staranuj jego wóz swoim pojazdem albo rozwal materiałami wybuchowymi! + +[PBOAT_1] { re3 change } +Naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby otworzyć ogień z działek na łodzi. + +[PBOAT_2] { re3 change } +Naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby otworzyć ogień z działek na łodzi. + +[DIAB1_B] +Mówi El Burro z gangu Diablo. + +[DIAB1_D] +Jesteś nowy w Liberty, ale na ulicach już zaczyna być o tobie głośno. + +[DIAB1_E] +Organizuję dla rozrywki mały wyścig. Punkt startu znajduje się przy starej szkole w okolicach Mostu Callahan. + +[DIAB1_F] +Skołuj sobie gablotę. Wygrywa ten, kto pierwszy zaliczy wszystkie punkty na trasie. + +[HM2_1] { re3 change } +Użyj zdalnie sterowanych samochodzików, aby zniszczyć samochody opancerzone. Naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby zdetonować ładunek. + +[HM2_1A] { re3 change } +Użyj zdalnie sterowanych samochodzików, aby zniszczyć samochody opancerzone. Naciśnij klawisz ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby zdetonować ładunek. + +[HM2_2] +~r~Nie udało ci się zniszczyć wszystkich samochodów opancerzonych! + +[HM2_6] +~g~Samochód opancerzony został zniszczony! + +[RM3_A] +Znam w tym mieście jednego bardzo ważnego faceta, prawdziwą grubą rybę, + +[RM3_H] +który słynie ze swych, jak to ująć, nietypowych upodobań i wielkiej fortuny, jaką na nie wydaje. + +[RM3_B] +Uwikłał się w proces sądowy, a prokuratura zdobyła kompromitujące go fotografie. + +[RM3_C] +Zrobili je na imprezie w kostnicy czy coś takiego. + +[LOVE6_A] +Przyjacielu, przyjmij ode mnie lekcję prowadzenia interesów. + +[LOVE6_E] +Jeżeli posiadasz przedmiot jedyny w swoim rodzaju, to dokładnie wszyscy, nawet ze swoimi żonami, będą się ze wszystkich sił starać ci go odebrać, + +[LOVE6_C] +Oddziały antyterrorystyczne otoczyły obszar, na którym znajduje się mój współpracownik wraz z pakunkiem. + +[LOVE6_D] +Jedź tam i weź ciężarówkę. Posłużysz jako przynęta. + +[LOVE6_F] +Odciągnij ich uwagę tak, aby mój przyjaciel mógł spokojnie opuścić to miejsce. + +[AM3_C] +Teraz najprawdopodobniej czyha na zatoce! Ukradnij łódź policyjną i raz na zawsze zakończ jego karierę! + +[FESZ_UC] +ANULUJ + +[FEDS_SM] +L1, R1 - ZMIANA MENU + +[FEDS_AS] +;= - ZMIANA WYBORU + +[FEDSAS2] +<> - ZMIANA WYBORU + +[FEDS_SS] +L1, R1 - ZMIANA WYBORU + +[FEDSSC1] +; - SZYBSZE PRZEWIJANIE + +[FEDSSC2] +Err:509 + +[MEA2_3] +~g~Odwieź samochód do fabryki. + +[RM1_3] +~r~McAffrey zwiał! + +[RM1_4] +~g~Zużyłeś wszystkie granaty! Wróć po nowy zapas do Amu-Nacji! + +[RM1_5] +~g~Wracaj i podpal ten dom! + +[RM6_4] +~g~Jedź do dziupli i zabierz rzeczy Raya. + +[RM6_5] +~g~CIA nieustannie obserwuje most, znajdź inną trasę. + +[HM2_F] +i sprzątnąć ich pancerny złom. + +[HM_4] +'W POGONI ZA KASĄ' + +[MEA2_B5] +TEKST JUŻ NIEPOTRZEBNY + +[MEA1_B5] +TEKST JUŻ NIEPOTRZEBNY + +[MEA3_B5] +TEKST JUŻ NIEPOTRZEBNY + +[MEA4_B7] +ale jeżeli zechcesz wpaść do mojego biura... + +[MEA3_B4] +Marty chce się ze mną widzieć? Lepiej niech się streszcza, bo muszę jeszcze zrobić sobie dzisiaj nową fryzurę. + +[KM3_7] +Ludzie, to pułapka Yakuzy! + +[FES_LOF] +Wczytywanie nieudane. + +[FES_SLO] +ZAPISZ PLIK + +[FES_ISC] +USZKODZONY + +[FESZ_TI] +ZAPISZ Z1 + +[FESZ_SA] +Zapis gry + +[MC_LDFL] +Wczytywanie nieudane! + +[MC_NWRE] +Trwa ponowne uruchamianie gry. + +[LOVE6_3] +~g~Masz ~1~ sekund na powrót do konwojowozu albo misja zakończy się porażką. + +[LOVE6_4] +~r~Straciłeś fałszywy konwojowóz! + +[HELP1] +Zatrzymaj się wewnątrz niebieskiego pola. + +[HELP12] +Stań na niebieskim polu, aby rozpocząć misję. + +[HJSTAT] +Odległość: ~1~,~1~m Wysokość:~1~,~1~m Salta: ~1~ Obroty: ~1~_ + +[HJSTATW] +Odległość: ~1~.~1~m Wysokość: ~1~.~1~m Salta: ~1~ Obroty: ~1~_ Plus doskonałe lądowanie! + +[DIAB1_5] +CZAS WYŚCIGU: + +[LOVE3_4] +~r~Zniszczyłeś samolot! + +[F_FAIL1] +Misja strażacka zakończona. + +[F_CANC] +~r~Misja strażacka anulowana! + +[F_EXTIN] +POŻARY: + +[A_COMP1] +Misja ratunkowa wykonana! + +[A_CANC] +~R~Misja ratunkowa anulowana! + +[A_COMP3] +Misja ratunkowa wykonana! Ty chyba nigdy się nie męczysz! + +[ATUTOR] +Wciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyć misje ratunkowe. + +[ATUTOR3] +Wciśnij klawisz ~h~~k~~TOGGLE_SUBMISSIONS~~w~, aby włączyć lub wyłączyć misje ratunkowe. + +[ALEVEL] +Misja Ratunkowa, Poziom ~1~ + +[A_FAIL1] +Misja ratunkowa zakończona. + +[FEST_HA] +Najwyższy poziom misji ratunkowej + +[A_SAVES] +URATOWANI LUDZIE:~1~ + +[C_KILLS] +ZABICI PRZESTĘPCY: ~1~ + +[HM1_B] +Mam problem z paroma frajerami. + +[AM2_A] +Śmierć Salvatore to radosna wiadomość, + +[AM2_A2] +widać, że jesteś dobrym zabójcą. Lubię tę cechę u ludzi. + +[AM2_B] +To mój brat Kenji. + +[AM2_C] +Asuka ma dla ciebie małą robótkę. Kiedy skończysz, wpadnij do mojego kasyna, to pogadamy. + +[AM2_D] +Zupełnie jak Kenji, on też zawsze chce bawić się moimi zabawkami. + +[AM2_E] +Moja wtyczka w policji donosi, że Mafia obserwuje nasze lokale w całym mieście. + +[AM2_E2] +Prawdopodobnie usiłują cię wytropić. + +[AM2_F] +Dopóki nie załatwimy tej sprawy, nie możemy prowadzić zwykłej działalności. + +[AM2_G] +Załatw tych głupawych szpiegów i raz na zawsze zakończ tę wendettę. + +[F_START] +~g~W okolicach ~a~ zauważono płonący pojazd. Udaj się tam i ugaś pożar. + +[AM4_1A] +Odszukaj telefon na Park West Belleville. + +[AM4_1B] +Odszukaj telefon na kampusie Liberty. + +[AM4_1C] +Odszukaj telefon na Park South Belleville. + +[AM4_1D] +Spotkajmy się w parku przy toaletach. + +[HJSTATF] +Odległość: ~1~ stóp Wysokość: ~1~ stóp Salta: ~1~ Obroty: ~1~_ + +[HJSTAWF] +Odległość: ~1~.~1~ stóp Wysokość: ~1~.~1~ stóp Salta: ~1~ Obroty: ~1~_ Plus doskonałe lądowanie! + +[HM1_F] +Lepiej uważaj - na ulicach będą też ludzie z Jacks, którzy mogą uznać, że polujesz również na nich! + +[HM1_D] +Nazywają się 'Dziewiątki' i ubierają się na purpurowo. Każdy dzień, kiedy te leszcze obnoszą się ze swoim barwami, + +[HM1_G] +to dzień wstydu dla mojego gangu. + +[MEA2_B] +i ukradli parę rzeczy, co pozwoli mi wyciągnąć kasę z odszkodowania. + +[TM3_H] +~w~Dobrze się spisałeś, młody, naprawdę dobrze. + +[TM3_I] +~w~Chodź, Don chce cię poznać. + +[TM3_J] +~w~Heeeej, Luigi! + +[TM3_K] +~w~Moje dziewczynki tęsknią za tobą, Salvatore, dawno cię u nas nie było. + +[TM3_L] +~w~Przekaż im ode mnie, że kiedy załatwimy całą tę nieszczęsną sprawę, + +[TM3_M] +~w~razem pojedziemy do klubu i uczcimy zwycięstwo + +[TM3_N] +~w~Oto i mój chłopak. + +[TM3_N2] +~w~Jak się masz, tato? + +[TM3_O] +~w~Znalazłeś już sobie przyzwoitą kobietę? + +[TM3_P] +~w~Ech, twoja matka, wieczny odpoczynek racz jej dać Panie, przewraca się w grobie, + +[TM3_Q] +~w~bo jej syn jeszcze nie ma żony. + +[TM3_R] +~w~Wiem, tato. Pracuję nad tym. + +[TM3_S] +~w~TONI! Jak twoja Mamuśka? + +[TM3_T] +~w~To wspaniała kobieta. Silna. Firenze. + +[TM3_U] +~w~Mama ma się dobrze... Znakomicie. + +[TM3_V] +~w~Doskonale, doskonale. Posłuchajcie, panowie, rozgośćcie się w środku, a ja porozmawiam z naszym nowym przyjacielem. + +[TM3_W] +~w~Widzę przed tobą wielką przyszłość, chłopcze... + +[RM1_A] +Ta szuja McAffrey wziął więcej łapówek niż ktokolwiek inny. + +[RM1_B] +Teraz myśli, że zasłuży na wygodną emeryturę, jeżeli zakapuje nas policji. + +[RM1_C] +Właśnie nas wsypał! + +[RM4_B] +Trzeba go uciszyć raz na zawsze. + +[RM4_E] +Od jutra ma spać z rybami, a nie jadać je na kolację. + +[LOVE3_B] +Dziś w nocy mały samolot przeleci nad zatoką, podchodząc do lądowania. + +[LOVE4_D] +Niestety, władze lotniska przejęły samolot i zaczęły rozbierać go na części, + +[LOVE4_H] +dopóki nie rzuciłem na szalę całego swojego autorytetu. + +[LOVE4_E] +Przejedź przez most do Shoreside Vale i jedź na lotnisko im. Francisa. + +[GTAB_A] +Lepiej stąd znikajmy. Cholera wie, co to jest, + +[GTAB_B] +ale zdaje się, że temu facetowi bardzo na tym zależy, więc to z pewnością musi mieć jakąś wartość. + +[GTAB_C] +Co, u diabła! + +[GTAB_D] +TO TY! + +[GTAB_E] +Hej, spokojnie, amigo! De nada! De nada! + +[GTAB_F] +Kiedy ostatni raz cię widziałem, twoje truchło spływało do ścieków! + +[GTAB_G] +Nie strzelaj, amigo. Nie ma problemu. My przyjaciele. Proszę, weź to sobie. + +[GTAB_H] +Nie bądź taką ciotą! + +[GTAB_I] +Nie mamy wyboru, kotku! + +[GTAB_J] +Zawsze mamy wybór, tępaku! + +[GTAB_K] +Przepraszam za tę głupią sukę, one wszystkie są jednakowe... por favor? + +[GTAB_L] +Więc ta dziwka zwiała? + +[GTAB_M] +Ale zrobiłeś mi przysługę, + +[GTAB_N] +nie jesteś jedyną osobą, która ma rachunki do wyrównania z Kartelem. + +[GTAB_O] +Ten gnidy zabiły mi brata! + +[GTAB_P] +Nigdy nie zabiłem żadnego członka Yakuzy! + +[GTAB_Q] +KŁAMIESZ! Wszyscy widzieliśmy zabójcę z Kartelu. + +[GTAB_R] +Wytropimy i wytłuczemy was wszystkich, wy kolumbijskie kundle! + +[GTAB_S] +Pobawię się z twoim przyjacielem, aby wydobyć z niego jakieś informacje i troszkę się rozerwać. + +[GTAB_T] +Ej, wpadnij później, będe cię jeszcze potrzebował. + +[GTAB_U] +Proszę, amigo, nie zostawiaj mnie z nią! Ta chica to wariatka! Amigo? Hej, AMIGOOO!!!... Aaaaaa! + +[LOVE5_A] +Raz po raz dowodzisz, że warto w ciebie inwestować, a to rzadkość w dzisiejszych czasach kłamstwa i obłudy. + +[KM3_1] +~g~Kartel spodziewa się kolesia z Yardies, więc idź i ukradnij samochód gangu Yardie! Powinieneś znaleźć ich w Newport, na północy. + +[LOVE1_1] +~g~Podwędź samochód gangu kolumbijskiego, abyś mógł swobodnie przeniknąć do ich kryjówki. Samochód znajdziesz na północ stąd, w Fort Staunton. + +[FM1_Q1] +~w~Szukasz mocnych wrażeń? Może odrobinę... Hm? Odrobinę HEROINY? + +[FM1_R] +~w~Czołem, Chico. Nie, daj mi to, co zawsze. + +[FM1_T] +~w~Dzięki, Chico. Na razie! + +[FM1_W] +~w~W porządku, piesku! Posiedź tu i popilnuj samochodu, a ja wyskoczę i poruszam trochę tyłkiem. + +[FM1_X] +~w~OK, piesku! Znikajmy stąd. Hau hau! + +[FM1_Q] +~g~Cześć, Mario! Moja ulubiona klientka! + +[FM1_S1] +~w~Hej, może wpadniesz na imprezę w tej pustej hali magazynowej na wschodnim krańcu Atlantic Quays? + +[FM1_U] +~w~Gracias i życzę przyjemnych wrażeń. To niezły towar... + +[FM1_V] +~w~Jazda, piesku! Jedziemy zajrzeć na tę imprezę! + +[FM1_SS] +~r~NASŁUCH RADIOWY: ~g~Cztery-pięć do wszystkich jednostek: Zapewnić wsparcie akcji antynarkotykowej w Atlantic Quays... + +[LOVE6_B] +nawet jeżeli mają tylko blade pojęcie na temat jego rzeczywistej wartości. + +[TM3_A1] +~r~Joey się usmażył! + +[TM3_A2] +~r~Joey i Lugi spiekli się na węgiel! + +[TM3_A3] +~r~Joey, Luigi i Toni usmażeni! + +[FM4_2] +Posłuchaj, Salvatore podejrzewa, że kombinujemy coś za jego plecami, + +[FM4_3] +dlatego postanowił sprzedać cię Kartelowi. + +[FM4_4] +Nie mogę do tego dopuścić. Najgorsze w tym wszystkim jest to, + +[FM4_4B] +że to moja wina... To ja mu powiedziałam, że między nami coś jest... + +[FM4_5] +Nie pytaj mnie, po co. Sama nie wiem. + +[FM4_6] +Posłuchaj, na terytorium mafii jesteś poszukiwany, ja też chciałabym się stąd wyrwać. + +[FM4_6B] +Widziałam już za dużo śmierci, zbyt wiele krwi! + +[FM4_7] +Mam starą dobrą przyjaciółkę... Nazywa się Asuka. Możemy jej zaufać. + +[FM4_8] +Dobra, wystarczy już tych przemówień. + +[FM4_9] +Zbierajmy się stąd, zanim pojawią się tu całe wycieczki rozhisteryzowanych Włochów, którzy będą chcieli rozstrzygać z nami rodzinne zatargi. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCENT + +[CRED003] +LESLIE BENZIES + +[CRED004] +KIEROWNIK ARTYSTYCZNY + +[CRED005] +AARON GARBUT + +[CRED006] +KIEROWNIK TECHNICZNY + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +PROJEKT + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +SCENARIUSZ + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +POSTACI + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMACJA & REŻYSERIA + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +PROJEKTY POJAZDÓW + +[CRED024] +PAUL KUROWSKI + +[CRED025] +GRAFICY + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +KODERZY + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +MUZYKA + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +KONCEPCJA I MASTERING DŹWIĘKU + +[CRED042] +ALLAN WALKER + +[CRED043] +PROGRAMOWANIE AUDIO + +[CRED044] +RAYMOND USHER + +[CRED045] +KIEROWNIK TESTÓW + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +GŁÓWNI TESTERZY + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +TESTERZY + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +POMOC TECHNICZNA + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +PRODUCENT WYKONAWCZY + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCENT + +[CRED064] +DAN HOUSER + +[CRED065] +DYREKTOR DS. ROZWOJU + +[CRED066] +JAMIE KING + +[CRED067] +PRODUCENT TECHNICZNY + +[CRED068] +GARY J. FOREMAN + +[CRED069] +PRODUCENT POMOCNICZY + +[CRED070] +JEREMY POPE + +[CRED071] +KOORDYNACJA MUZYCZNA + +[CRED072] +TERRY DONOVAN + +[CRED073] +ZESPÓŁ PRODUKCYJNY ROCKSTAR + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +WICEPREZES DS. MARKETINGU + +[CRED086] +TERRY DONOVAN + +[CRED087] +KOORDYNACJA TECHNICZNA + +[CRED088] +BRANDON ROSE + +[CRED089] +KIEROWNIK DS. ZAPEWNIENIA JAKOŚCI + +[CRED090] +JEFF ROSA + +[CRED091] +GŁÓWNY ANALITYK + +[CRED092] +ADAM DAVIDSON + +[CRED093] +ANALITYK GRY + +[CRED094] +RICHARD HUIE + +[CRED095] +ZESPÓŁ TESTUJĄCY + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +REDAKCJA 'LIBERTY TREE' + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +SEKWENCJE FILMOWE + +[CRED108] +SCENARIUSZ: DAN HOUSER I JAMES WORALL + +[CRED109] +REŻYSERIA DŹWIĘKU: DAN HOUSER + +[CRED110] +PRODUKCJA DŹWIĘKU: RENAUD SEBBANE + +[CRED111] +OBSADA + +[CRED112] +FRANK VINCENT JAKO SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO JAKO LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN JAKO TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT JAKO JOEY LEONE + +[CRED116] +DEBBI MAZAR JAKO MARIA + +[CRED117] +KYLE MACLACHLAN JAKO DONALD LOVE + +[CRED118] +ROBERT LOGGIA JAKO RAY MACHOWSKI + +[CRED119] +GURU JAKO 8-BALL + +[CRED120] +SONDRA JAMES JAKO MAMUŚKA + +[CRED121] +LIANA PAI JAKO ASUKA + +[CRED122] +LES MAU JAKO KENJI + +[CRED123] +CYNTHIA FARRELL JAKO CATALINA + +[CRED124] +AL. ESPINOSA JAKO MIGUEL + +[CRED125] +CHRIS PHILLIPS JAKO EL BURRO + +[CRED126] +HUNTER PLATIN JAKO CHICO + +[CRED127] +WALTER MUDU JAKO D-ICE + +[CRED128] +CURTIS MCCLARIN JAKO CURTLY + +[CRED129] +BILL FIORE JAKO DARKEL + +[CRED130] +CHRIS PHILLIPS JAKO MARTY CHONKS + +[CRED131] +HUNTER PLATIN JAKO KUDŁATY BOB + +[CRED132] +WALTER MUDU JAKO KING COURTNEY + +[CRED133] +HUNTER PLATIN JAKO JEDNORĘKI PHIL + +[CRED134] +KIM GURNEY JAKO MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMACJA + +[CRD136A] +ALEX HORTON + +[CRED137] +REŻYSERIA + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUKCJA + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +NAGRANIA PRZEPROWADZONO W MODERN UPRISINGS STUDIOS, BROOKLYN + +[CRED140] +AKTORZY + +[CRD140A] +MARTINEZ + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRED141] +DIALOGI PRZECHODNIÓW + +[CRED142] +TEKST: DAN HOUSER, NAVID KHONSARI I JAMES WORALL + +[CRED143] +REŻYSERIA: CRAIG CONNER, DAN HOUSER I LAZLOW + +[CRED144] +PRODUKCJA: RENAUD SEBBANE + +[CRED145] +OBSADA + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +STACJE RADIOWE ORAZ MUZYKA + +[CRED218] +PRODUKCJA DLA ROCKSTAR UK + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +KOORDYNATOR ŚCIEŻKI DŹWIĘKOWEJ + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUKCJA DLA ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +REDAKCJA + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +TEKSTY I WIZERUNKI PREZENTERÓW: + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECJALNE PODZIĘKOWANIA DLA: + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTYŚCI + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CRED270] +MIKE HONG + +[CINCAM] +Kamera Filmowa + +[KM1_13] +Wprowadź samochód do garażu! + +[KM3_14] +~r~Zostałeś zauważony, układ odwołany! + +[EBAL_H] +Poczekaj tutaj, brachu, a ja pójdę do środka i pogadam z Luigim. + +[EBAL_M] +Tylko pamiętaj - nic nie kombinuj z moimi dziewczynami! + +[LM2_F] +Potem zabierz jego samochód i przemaluj go. + +[LM2_D] +proszę bardzo. + +[LM1_9] +Cześć, jestem Misty... + +[LM4_A] +Jakaś gnida z gangu Diablo nasyła swoje brudne dziwki na moje terytorium. + +[FM2_B] +Mamy kreta! + +[FM2_C] +Żaden z niego alfons czy diler, więc pewnie dorabia na boku sprzedając informacje. + +[FM3_CC] +~w~Bracie, wróć, kiedy będziesz miał pieniądze. + +[FEDS_AM] +<> - ZMIANA MENU + +[LOVE5_5] +~r~Nie udało cię się ochronić ciężarówki! + +[RM6_6] +~r~Ray nie żyje! + +[RM6_7] +~r~Ray spóźnił się na samolot. + +[RM6_8] +~r~Zgubiłeś Raya, wracaj po niego. + +[FM1_10] +~g~Zostawiłeś Marię - zawróć i ją zabierz. + +[LOVE4_9] +~r~Samolot został zniszczony! + +[LOV4_10] +~r~Jedyny ślad, który wskazywał, gdzie zniknęła paczka, został zniszczony! + +[KM2_D] +Nie muszę chyba dodawać, że przekażę mu te samochody w darze, aby spłacić dług, który u niego zaciągnąłem. + +[KM4_B] +Interes idzie na tyle dobrze, że dziś możemy odebrać należną nam opłatę za ochronę. + +[KM2_E] +Zdobądź samochody z tej listy i dostarcz je do warsztatu za parkingiem w Newport. + +[FM3_8I] +~w~Znajdź dobre stanowisko strzeleckie. Kiedy oddasz pierwszy strzał, ja zrobię to, co do mnie należy. + +[LOVE1_B] +Doświadczenie uczy mnie, że ludzie tacy jak ty potrafią być niezwykle lojalni za odpowiednią cenę. + +[LOVE1_H] +ale niektórzy ludzie robią się coraz bardziej chciwi. + +[LOVE1_C] +Znam pewnego starszego pana, pochodzącego z krajów Orientu, który jest dla mnie niezwykle cenny. + +[LOVE1_I] +Niestety został on porwany przez jakiś gang z Ameryki Południowej w okolicach Aspatrii. + +[MEA4_D] +Zgodziłem się z nim spotkać... + +[MEA4_B4] +Marty cię przysyła, co? W porząsiu, pokażę tej gliście, jak się robi interesy. + +[MEA4_B5] +Carl, cześć! Ee, potrzebuję jeszcze trochę czasu, żeby zebrać dla ciebie pieniądze. + +[MEA1_B4] +Ach, przysłał cię Chonks, prawda? Chodźmy odwiedzić naszego wspólnego przyjaciela. + +[HM5_6] +Trzeba rozłupać parę łbów... + +[LOVE1_5] +~g~Przestań się obijać, załatw samochód Kolumbijczyków i uratuj przyjaciela Love'a. + +[AS1_D] +~w~Posłuzysz jako przynęta i ściągniesz szwadrony śmierci za sobą do Pike Creek, + +[AS1_E] +~w~gdzie moi ludzie urzadzą im właściwe przyjęcie. + +[AS2_C] +~w~Kartel działa pod przykrywką firmy Dom Kawy Kappa. + +[AS2_E] +~w~Nie mamy wyjścia, trzeba wyłączyć z gry te punkty sprzedaży dragów. + +[AS2_F] +~w~Rozwal je w drzazgi!! + +[AS2_A1] +~w~Miguel to doskonały przykład słynnej latynoskiej odporności. + +[AS2_A2] +~w~Ręce opadają mi ze zmęczenia. + +[SIREN_3] +Aby włączyc syrenę pojazdu, naciśnij klawisz ~h~~k~~VEHICLE_HORN~ ~w~. + +[SIREN_4] +Aby włączyc syrenę pojazdu, naciśnij klawisz ~h~~k~~VEHICLE_HORN~ ~w~. + +[AS3_C] +~w~Eeej! Co to za lepkie żółte świństwo? + +[AS3_C1] +~w~O, cześć, kochanie. + +[AS3_F] +~w~Ta dziewczyna po prostu ma talent. + +[AS3_F1] +~w~Udało jej się wydobyć z naszego gościa ten oto klejnocik. + +[AS3_G] +~w~Za 2 godziny na Lotnisko Francisa przyleci pewien samolot. + +[AS3_G1] +~w~Jest on wyładowany trucizną Cataliny. + +[AS3_H] +~w~Możesz ominąć lotniskowe służby bezpieczeństwa, jeżeli popłyniesz łodzią + +[AS3_H1] +do boi wyznaczających lądowisko i zestrzelisz lądujący samolot. + +[AS3_I] +~w~Zabierz z wraku ładunek! + +[AS3_J] +~w~Tylko bądź ostrożny, dobrze, kochanie? + +[AS3_K] +~w~A teraz spróbujemy olejku chilli... + +[RM2_F1] +Kolumbijczycy będą tutaj lada chwila! + +[RM2_K] +Cholera, już tu są! OGNIA! + +[LOVE2_7] +~g~Teraz pozbądź się samochodu!v + +[LOVE2_8] +~g~Uciekaj z Newport! + +[AM1_F] +Za około trzy godziny (~1~:~1~), Salvatore Leone będzie wychodził z klubu 'U Luigiego'. + +[LOVE5_C] +Masz za nim jechać i pilnować, aby zarówno on, jak i mój pakunek dotarli do Pike Creek bez szwanku. + +[FESZ_SR] +Zapisywanie zakończone niepowodzeniem! Sprawdź kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 i ponów próbę. + +[FESZ_FO] +Czy chcesz sformatować kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1? + +[FELZ_FO] +Karta pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 nie jest sformatowana. + +[FES_NOC] +Brak karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. + +[FES_LOE] +Wczytywanie zakończone niepowodzeniem! Sprawdź kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 i ponów próbę. + +[FES_DEE] +Kasowanie zakończone niepowodzeniem! Sprawdź kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 i ponów próbę. + +[SLONFM] +Błąd podczas formatowania karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. + +[SLONDR] +Niewystarczająca ilość miejsca, aby zapisać stan gry. Proszę włożyć kartę pamięci (PS2) zawierającą co najmniej 500KB wolnego miejsca do gniazda KART PAMIĘCI nr 1. + +[SLNSP] +Niewystarczająca ilość miejsca, aby zapisać stan gry. Proszę włożyć kartę pamięci (PS2) zawierającą co najmniej 200KB wolnego miejsca do gniazda KART PAMIĘCI nr 1. + +[FEFD_WR] +Trwa formatowanie karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. Proszę nie wyjmować karty pamięci (PS2), nie resetować ani nie wyłączać konsoli + +[FES_ISF] +NIEOBECNY + +[FES_SAG] +OBECNY + +[SLONNO] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[SLONNF] +Brak karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. + +[FESZ_FM] +Karta pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 nie jest sformatowana. Czy chcesz sformatować kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1? + +[FESZ_FF] +Formatowanie zakończone niepowodzeniem! Sprawdź kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 i ponów próbę. + +[MCDNSP] +Na karcie pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 jest zbyt mało wolnego miejsca. Aby zapisać dane aplikacji wymagane jest co najmniej 500KB. Czy chcesz kontynuować? (TAK lub NIE) + +[MCGNSP] +Na karcie pamięci (PS2) w gnieździe KART PAMIĘCI nr 1 jest zbyt mało wolnego miejsca. Aby zapisać dane aplikacji wymagane jest co najmniej 200KB. Czy chcesz kontynuować? (TAK lub NIE) + +[FESZ_WR] +Trwa zapisywanie danych. Proszę nie wyjmować karty pamięci (PS2) z gniazda KART PAMIĘCI nr 1, nie resetować ani nie wyłączać konsoli. + +[FESZ_OW] +Trwa nadpisywanie danych. Proszę nie wyjmować karty pamięci (PS2) z gniazda KART PAMIĘCI nr 1, nie resetować ani nie wyłączać konsoli. + +[FELD_WR] +Trwa wczytywanie danych. Proszę nie wyjmować karty pamięci (PS2), nie resetować ani nie wyłączać konsoli. + +[FEDL_WR] +Trwa usuwanie danych. Proszę nie wyjmować karty pamięci (PS2) z gniazda KART PAMIĘCI nr 1, nie resetować ani nie wyłączać konsoli. + +[LM2_C] +Luigi kazał ci to przekazać, więc... + +[LM3_G] +Joey nie lubi czekać. Pamiętaj, to może być twoja szansa na karierę... + +[LM5_E] +Zawieź tam jak najwięcej dziewczyn, zanim gliniarze przepiją wszystkie pieniądze! + +[JM5_C] +Jest taka kwestia: przy kawiarni w Callahan Point stoi samochód z truposzem w środku. + +[RM2_B] +Powąchaliśmy razem prochu w Nikaragui, kiedy w tym kraju jeszcze rządzili ludzie, którzy wiedzieli, o co chodzi. + +[RM2_C] +Wczoraj groziły mu jakieś mendy z Kartelu. Powiedzieli, że wrócą i zabiorą mu towar. + +[RM2_D1] +Poszedłbym sam, ale moje korzonki znowu się odzywają - więc, ee... Powodzenia! + +[CATINF1] +~g~Dorwij Catalinę! + +[CATINF2] +~g~Śledź helikopter, aby odszukać Catalinę. + +[BOATIN1] +Wskakuj do łodzi i wciśnij klawisz ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~, aby zająć miejsce. + +[BOATIN2] +Jeżeli jesteś w pobliżu łodzi, możesz wcisnąć klawisz ~h~~k~~VEHICLE_ENTER_EXIT~~w~, aby zająć w niej miejsce. + +[BOATIN3] +Wskakuj do łodzi i wciśnij klawisz ~h~~k~~VEHICLE_ENTER_EXIT~ button ~w~, aby zająć miejsce. + +[BOATIN4] +Jeżeli jesteś w pobliżu łodzi, możesz wcisnąć klawisz ~h~~k~~VEHICLE_ENTER_EXIT~~w~, aby zająć w niej miejsce. + +[JM6] +'UCIECZKA' + +[FM1] +'NIAŃKA' + +[JM1] +'OSTATNI OBIAD MIKE'A BUŹKI' + +[FM21] +'CIOS W SERCE: AKT I' + +[FM3] +'CIOS W SERCE: AKT II' + +[AM1] +'SAYONARA SALVATORE' + +[AM2] +'POD OBSERWACJĄ' + +[KM2] +'GRAND THEFT AUTO' + +[AS3] +'WYRZUTNIA ZIEMIA-POWIETRZE' + +[RM2] +'TOWARZYSZE BRONI' + +[LOVE6] +'ZMYŁKA' + +[LOVE1] +'WYZWOLICIEL' + +[RC1] +'DIABELSKA DEMOLKA' + +[RC2] +'MAFIJNA MASAKRA' + +[RC3] +'KRWAWE KASYNO' + +[RC4] +'RUMPO-ROZRÓBA' + +[RM2_E1] +Nie mogę uwierzyć, że te żółte pokraki znowu zostawiły mnie bez ochrony. + +[GREN_1] +Im dłużej przytrzymasz klawisz ~h~~k~~PED_FIREWEAPON~~w~, tym dalej rzucisz granatem. + +[GREN_2] +Im dłużej przytrzymasz klawisz ~h~~k~~PED_FIREWEAPON~~w~, tym dalej rzucisz granatem. + +[GREN_3] +Im dłużej przytrzymasz klawisz ~h~~k~~PED_FIREWEAPON~~w~, tym dalej rzucisz granatem. + +[LOVE4_G] +Rzecz, która do mnie należy, będzie czekać na ciebie w hangarze celnym przy kadłubie samolotu. + +[KABOOM] +BUM! + +[SPLAT] +PLASK! + +[PANCAK] +UPIECZONY! + +[SOAKED] +DO SUCHEJ NITKI! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Gaduła FM + +[K_JAH] +Radio K-Jah + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +Kiedy metro zostanie otwarte, będziesz mógł przejechać pociągiem na Wyspę Staunton. + +[TUBE2] +Kiedy Shoreside Vale zostanie otwarte, będziesz mógł dotrzeć do stacji końcowej Shoreside Terminal i na Lotnisko Międzynarodowe im. Francisa. + +[TUBE_2] +Aby wsiąść do metra, naciśnij klawisz ~h~'wsiadania do pojazdu'~w~. + +[LEGAL] +~g~Wyeliminuj element przestępczy! + +[GA_2] +Nowy silnik i lakier. Masz spokój z gliniarzami! + +[LM1_8A] +Jeżeli chcesz trochę sobie dorobić, możesz spróbować 'pożyczyć' taksówkę... + +[TAXIH1] +Zatrzymaj się w pobliżu podświetlonego przechodnia i pozwól mu wsiąść, a potem zawieź go pod wskazany adres przed upływem wyznaczonego czasu. + +[LM5_7] +~g~Jeżeli na ~p~Balu Policjanta~g~ pojawią się mniej niż cztery dziewczyny, Luigi będzie niezadowolony! + +[KM2_3] +~g~Pamiętaj, ~r~samochody~g~ muszą być w idealnym stanie albo nie zostaną przyjęte w ~p~warsztacie~g~. + +[KM5_2] +~g~Yardies zniknęli z ulic. + +[BETRA_A] +Przykro mi, kotku. + +[BETRA_B] +Jestem ambitną dziewczyną, + +[BETRA_C] +a ty jesteś tylko małą płotką. + +[JAILB_C] +* + +[JAILB_D] +* + +[JAILB_E] +* + +[JAILB_F] +* + +[JAILB_G] +* + +[JAILB_H] +* + +[JAILB_I] +* + +[JAILB_J] +* + +[JAILB_P] +* + +[JAILB_Q] +Jazda! + +[JAILB_R] +Panie frajerze! + +[JAILB_S] +Zabiję cię bez najmniejszego problemu. + +[JAILB_T] +Pożałujesz tego. + +[JAILB_U] +Dobrze, dobrze! Znikaj stąd. + +[HELP15] +Jeżeli jesteś poza pojazdem i chcesz ~h~spojrzeć za siebie~w~, naciśnij klawisz ~h~~k~~PED_LOOKBEHIND~~w~. + +[FEC_LB3] +Spójrz do tyłu + +[FEC_R3] +(klawisz R3) + +[FES_AFO] +Ta karta pamięci (PS2) jest już sformatowana. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- ZMIANA WYBORU + +[FEDSAS4] +;=<> - ZMIANA WYBORU + +[SPRAY_4] { re3 change } +Użyj klawisza ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby strzelać z armatki wodnej. + +[SPRAY_1] { re3 change } +Użyj klawisza ~h~~k~~VEHICLE_FIREWEAPON~~w~, aby strzelać z armatki wodnej. + +[LITTLE] +MAŁY T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore opuści knajpę 'U Luigiego' około 0~1~:~1~ + +[JAILB_V] +* + +[JAILB_A] +* + +[JAILB_B] +* + +[JAILB_W] +* + +[JAILB_K] +* + +[JAILB_L] +* + +[JAILB_M] +* + +[JAILB_N] +* + +[JAILB_O] +* + +[JAILB_X] +* + +[FEDS_SE] +klawisz / - WYBIERZ + +[FEDS_SB] +klawisz / - WYBIERZ klawisz ' - WSTECZ + +[TM4_A] +~w~Ach, to ty. TONIEGO nie ma. + +[TM4_A2] +~w~Ale zostawił na stole jeden ze swoich słodziutkich listów miłosnych do ciebie. + +[DIAB2_A] +Kiedy zaczynałem robić interesy w branży rozrywek egzotycznych, nie miałem nic oprócz pokaźnej zawartości mojego rozporka! + +[LM5_9] +DZIEWCZYNY: + +[PERPIC] +Znalezione ukryte paczki + +[CO_ONE] +Ukryta Paczka ~1~ z ~1~ + +[LOVE3_3] +~g~Samolot zrzucił ~1~ z 6 pakunków. + +[FARE11] +~g~Cel podróży ~w~'Teren budowy'~g~ w Fort Staunton. + +[GA_21] +W tym garażu nie możesz przechowywać więcej samochodów. + +[CHEAT1] +Ułatwienie uaktywnione + +[CHEAT2] +Ułatwienie - broń + +[CHEAT3] +Ułatwienie - życie + +[CHEAT4] +Ułatwienie - pancerz + +[CHEAT5] +Ułatwienie - poziom wanted + +[CHEAT6] +Ułatwienie - pieniądze + +[CHEAT7] +Ułatwienie - pogoda + +[AS1_H] +~w~Nie udało ci się wprowadzić Szwadronu Śmierci w pułapkę Yakuzy!! + +[FEDS_BA] +klawisz ' - WSTECZ + +[RAMP_A] +WSZYSTKIE ROZWAŁKI ZALICZONE! + +[USJ_ALL] +WSZYSTKIE NIETYPOWE SKOKI WYKONANE! + +[FARE23] +~g~Cel podróży ~w~'warsztat importowo-eksportowy'~g~ w okolicach Tamy Cochrane. + +[L_TRN_1] +Po Portland możesz się też poruszać kolejką. Naciśnij klawisz ~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pociągu. + +[L_TRN_2] +Po Portland możesz się też poruszać kolejką. Naciśnij klawisz ~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pociągu. + +[S_TRN_1] +Przez Liberty możesz także podróżować metrem. Naciśnij klawisz~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pociągu. + +[S_TRN_2] +Przez Liberty możesz także podróżować metrem. Naciśnij klawisz~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, aby ~h~wsiąść ~w~lub ~h~wysiąść~w~ z pociągu. + +[AS1_C] +~w~Rozmieściła w całym Liberty trzy szwadrony śmierci, których zadaniem jest wytropić twój tyłek. + +[AS1_G] +~w~Wszyscy członkowie Yakuzy nie żyją!! + +[JAN] +stycz. + +[FEB] +luty + +[MAR] +marzec + +[APR] +kwiec. + +[MAY] +maj + +[JUN] +czerw. + +[JUL] +lip. + +[AUG] +sierp. + +[SEP] +wrz. + +[OCT] +paźdz. + +[NOV] +listop. + +[DEC] +grudz. + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +POZOSTAŁE GARBUSKI: + +[BONUS] +~g~PREMIA $~1~v + +[HORN1] +Naciśnij ~h~klawisz L3 ~w~, aby użyć ~h~klaksonu. + +[HORN2] +Naciśnij ~h~klawisz L1 ~w~, aby użyć ~h~klaksonu. + +[HORN3] +Naciśnij ~h~klawisz R1 ~w~, aby użyć ~h~klaksonu. + +[LM3_1A] +Naciśnij klawisz~h~ ~k~~VEHICLE_HORN~~w~, aby użyć ~h~klaksonu~w~ i zaprosić Misty do środka. + +[LM3_1B] +Naciśnij klawisz~h~ ~k~~VEHICLE_HORN~~w~, aby użyć ~h~klaksonu~w~ i zaprosić Misty do środka. + +[LM3_1C] +Naciśnij klawisz~h~ ~k~~VEHICLE_HORN~~w~, aby użyć ~h~klaksonu~w~ i zaprosić Misty do środka. + +[RADIO_A] +Naciśnij klawisz ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~, aby przełączać ~h~stacje radiowe. + +[RADIO_B] +Naciśnij klawisz ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~, aby przełączać ~h~stacje radiowe. + +[RADIO_C] +Naciśnij klawisz ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~, aby przełączać ~h~stacje radiowe.v + +[RADIO_D] +Naciśnij klawisz ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~, aby przełączać ~h~stacje radiowe. + +[FEC_EXV] +Wsiadanie i wysiadanie z pojazdu + +[TAXI_M] +'TAKSÓWKARZ' + +[COP_M] +'PATROL' + +[FIRE_M] +'STRAŻAK' + +[AMBUL_M] +'SANITARIUSZ' + +[HJ_IS] +PREMIA ZA SZALONY SKOK: $~1~ + +[HJ_PIS] +PREMIA ZA BEZBŁĘDNY SZALONY SKOK: $~1~ + +[HJ_DIS] +PREMIA ZA PODWÓJNY SZALONY SKOK: $~1~ + +[HJ_PDIS] +PREMIA ZA BEZBŁĘDNY PODWÓJNY SZALONY SKOK: $~1~ + +[HJ_TIS] +PREMIA ZA POTRÓJNY SZALONY SKOK: $~1~ + +[HJ_PTIS] +PREMIA ZA BEZBŁĘDNY POTRÓJNY SZALONY SKOK: $~1~ + +[HJ_QIS] +PREMIA ZA POCZWÓRNY SZALONY SKOK: $~1~ + +[HJ_PQIS] +PREMIA ZA BEZBŁĘDNY POCZWÓRNY SZALONY SKOK: $~1~ + +[AM1_K] +Za około trzy godziny (0~1~:~1~), Salvatore Leone będzie wychodził z klubu 'U Luigiego'. + +[IMPEXPP] +Warsztat import-eksport w Portland Harbor. Mamy zamówienia na różne pojazdy. Listę aktualnie skupowanych wozów znajdziesz na naszej tablicy ogłoszeniowej. + +[VANHSTP] +Chcesz rozbić konwojowóz? Przywieź go do naszego warsztatu w Portland Harbor! + +[EMVHPUP] +Doskonałe ceny za nowe i używane pojazdy służb miejskich! Skup odbywa się przy dźwigu na północny wschód od Portland Harbor. + +[STANDS] +ROZBITE BUDKI: + +[STASH] +~g~Odwieź PROCHY z powrotem na ~p~teren budowy! + +[MCSTNS] +W gnieździe KART PAMIĘCI nr 1 nie ma karty pamięci (PS2). Czy chcesz kontynuować? (TAK lub NIE) + +[LOVE3_5] +~g~Samolot jest w zasięgu. + +[LOVE3_6] +~r~Policja dotarła do pakunków przed tobą! + +[SIREN_1] +Aby włączyc syrenę pojazdu, naciśnij klawisz ~h~~k~~VEHICLE_HORN~ ~w~. + +[SIREN_2] +Aby włączyc syrenę pojazdu, naciśnij klawisz ~h~~k~~VEHICLE_HORN~ ~w~. + +[FM3_8C] +~w~Potrzebuję jakieś 100 000 dolarów na niezbędne wydatki, + +[MCLOAD] +Trwa wczytywanie danych. Proszę nie wyjmować karty pamięci (PS2) z gniazda KART PAMIĘCI nr 1, nie resetować ani nie wyłączać konsoli. + +[FES_GME] +Błąd odczytu danych z karty pamięci (PS2) w gnieździe KART PAMIĘCI nr 1. Sprawdź kartę i ponów próbę. + +[FESZ_QF] +Czy na pewno chcesz sformatować kartę pamięci (PS2) w gnieździe KART PAMIĘCI nr 1? + +[FESZ_LS] +Pomyślne wczytywanie + +[RM3_5] +~g~Masz już ~1~ z 6 pakietów dowodów. + +[LOVE3_2] +~g~Masz już wszystkie pakunki! Zawieź je do Donalda Love'a. + +[LOVE4_4] +~g~Zabierz pakunek do Donalda Love'a! + +[FEB_SAV] +Wczytanie + +[FEP_SAV] +WCZYTAJ GRĘ + +[AS2_12A] +~g~Od momentu rozwalenia pierwszej budki będziesz miał 8 minut, zanim Kartel ostrzeże swoich dilerów! + +[AS3_1A] +~g~Teraz płyń do ~b~boi ! + +[NOCONT] +Aby kontynuować, proszę ponownie podłączyć kontroler analogowy (DUALSHOCK@) lub kontroler analogowy (DUALSHOCK@2) do portu kontrolerów gry nr 1. + +[BET_JB] +CATALINA, JEGO KOCHANKA, ZDRADZIŁA GO I POZOSTAWIŁA NA PEWNĄ ŚMIERĆ. TERAZ, OSĄDZONY I SKAZANY, ROZPOCZYNA PODRÓŻ DO ZAKŁADU KARNEGO W LIBERTY CITY. ALE W JEGO ZBRODNICZYM UMYŚLE PŁONIE TYLKO JEDNA MYŚL......ZEMSTA! + +[END_A] +Mieszkańcy Cedar Grove nadal dochodzą do siebie + +[END_B] +po gwałtownych wydarzeniach, jakie rozegrały się wczoraj + +[END_C] +w tej dzielnicy, a przypominały regularną wojnę. + +[END_D] +Pan Clive Denver, mieszkający w tej okolicy od lat, opisał wczoraj policji + +[END_E] +samotnego strzelca, który uciekał z miejsca przestępstwa z ciemnowłosą kobietą. + +[END_F] +Och, będziemy się razem świetnie bawić! Bo... no, wiesz... + +[END_G] +Ja cię kocham, naprawdę... Bo jesteś taki silny i w ogóle... + +[END_H] +A właśnie kogoś takiego szukałam. + +[END_I] +Ale... o czym to ja mówiłam? + +[END_J] +Wyleciało mi z głowy! Ale wiesz, o co mi chodzi, prawda? + +[END_K] +Odgłosy eksplozji wstrząsały okolicznymi budynkami, z których wybiegali przerażeni ludzie. + +[END_L] +Kilkunastu obywateli doznało obrażań w wyniku wymiany ognia + +[END_M] +między bandytami na ziemi a helikopterem krążącym nad tamą. + +[END_N] +Tak, tutaj - z ogrodu - miałem na wszystko doskonały widok. + +[END_O] +Kiedy helikopter w końcu został trafiony, + +[END_P] +wyglądało to lepiej niż fajerwerki na Wielkanoc! + +[END_Q] +Liczba zabitych przekroczyła już dwadzieścia osób, + +[END_R] +ale policja ciągle odnajduje nowe ciała. + +[END_S] +Jak dotąd nie wydano żadnego oficjalnego komunikatu, który zaprzeczyłby pogłoskom, + +[END_T] +że ofiary to członkowie Kartelu Kolumbijskiego. + +[END_U] +Nadal nie ma też żadnych informacji na temat przyczyn tej masakry. + +[END_V] +Złamałam paznokieć i zrujnowałam sobie całą fryzurę. To skandal! + +[END_W] +Kosztowała mnie pięćdziesiąt dolców... + +[PAPER1] +* + +[PAPER2] +* + +[PAPER3] +* + +[FEB_CPC] +Konfiguracja klawiszy sterujących + +[FEC_PED] +Sterowanie postacią poza pojazdem + +[FEC_VEH] +Klawisze sterujące pojazdem + +[FEC_FPR] +Sterowanie dla trybu widoku z oczu postaci + +[FEC_CMM] +Standardowe sterowanie + +[FEC_PWL] +IDŹ w lewo + +[FEC_PWR] +Idź w prawo + +[FEC_PWF] +Idź do przodu + +[FEC_PWT] +Idź w stronę kamery + +[FEC_PLB] +Spójrz za siebie + +[FEC_PFR] +Strzał z broni + +[FEC_CLE] +Przełącz rodzaj broni w lewo + +[FEC_CRI] +Przełącz rodzaj broni w prawo + +[FEC_LKT] +Zablokuj cel + +[FEC_PJP] +Skok + +[FEC_PSP] +Szybki bieg + +[FEC_PSH] +Strzał + +[FEC_TLF] +Następny cel w lewo + +[FEC_TRG] +Następny cel w prawo + +[FEC_CCM] +Wyśrodkuj kamerę za graczem + +[FEC_SZI] +Karabin snajperski - przybliżenie + +[FEC_SZO] +Karabin snajperski - oddalenie + +[FEC_LKL] +Spójrz w lewo w trybie widoku z oczu postaci + +[FEC_LRT] +Spójrz w prawo w trybie widoku z oczu postaci + +[FEC_LUP] +Spójrz w górę w trybie widoku z oczu postaci + +[FEC_LDN] +Spójrz w dół w trybie widoku z oczu postaci + +[FEC_LBH] +Spójrz przez tylną szybę + +[FEC_LLF] +Spójrz przez lewą szybę + +[FEC_LRG] +Spójrz przez prawą szybę + +[FEC_HRN] +Klakson + +[FEC_HBR] +Hamulec ręczny pojazdu + +[FEC_ACL] +Przyspieszenie pojazdu + +[FEC_BRK] +Hamulec pojazdu + +[FEC_TSM] +Włącz podmisje + +[FEC_CRD] +Zmiana stacji radiowej + +[FEC_ENT] +Wsiadanie do pojazdu/wysiadanie z pojazdu + +[FEC_WPN] +Strzał z broni + +[FEC_PAS] +Pauza + +[FEC_FPO] +Broń w trybie widoku z oczu postaci + +[FEC_SMS] +Pokazuj kursor myszy + +[FEC_CMS] +Zmiana trybu kamery dla wszystkich sytuacji. + +[FEC_TSS] +Zapisz wygląd ekranu + +[FEN_NET] +Sieć + +[FEN_CON] +Połączenie + +[FEN_GAM] +Szukaj sesji gry + +[FEN_TYP] +Rodzaj gry + +[FEN_TY0] +Tryb Deathmatch + +[FEN_TY1] +Tryb Niewidzialny Deathmatch + +[FEN_TY2] +Zespołowy tryb Deathmatch + +[FEN_TY3] +Zespołowy tryb Niewidzialny Deathmatch + +[FEN_TY4] +Gromadź szmal + +[FEN_TY5] +Zdobądź flagę + +[FEN_TY6] +Wyścig szczurów + +[FEN_TY7] +Dominacja + +[FEN_NAM] +Nazwa: + +[FEN_GNA] +Nazwa gry: + +[FEM_MAP] +Wybierz mapę + +[FEN_PLS] +Ustawienia gracza + +[FEN_PLC] +Kolor gracza + +[FEM_MA0] +Liberty City + +[FEM_MA1] +Dz. Czerwonych Świateł + +[FEM_MA2] +Chinatown + +[FEM_MA3] +Wieża + +[FEM_MA4] +Kanały + +[FEM_MA5] +Park Przemysłowy + +[FEM_MA6] +Doki + +[FEM_MA7] +Staunton + +[FEC_EMS] +Tylko klawisze z klawiatur niestandardowych + +[FEC_DBG] +MENU DEBUGOWANIA + +[FEC_TGD] +Przełącznik gra/debugowanie + +[FEC_TDO] +Wyłącz kamerę trybu debugowania + +[FEC_IVH] +Odwróć osie myszy w poziomie: + +[FEC_MSL] +Lewy przycisk myszy + +[FEC_MSM] +Śr. przycisk myszy + +[FEC_MSR] +Prawy przycisk myszy + +[FEC_QUE] +??? + +[FEC_TWO] +Dozwolone są tylko dwa klawisze z klawiatury + +[FEC_UMS] +Tylko przyciski myszy niestandardowych + +[FEC_OMS] +Dozwolony jest tylko jeden przycisk myszy + +[FEC_UJS] +Tylko przyciski joysticków niestandardowych + +[FEC_OJS] +Dozwolony jest tylko jeden przycisk joysticka na daną czynność . + +[FEC_PTL] +Użyj blokowania celu oraz przełączenia broni w lewo + +[FEC_PTR] +Użyj blokowania celu oraz przełączenia broni w prawo + +[FEC_LBC] +Użyj klawiszy 'spójrz w lewo' oraz 'spójrz w prawo' + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +Nie można zatrzymać gry w trybie wieloosobowym. Dwukrotnie naciśnij klawisz, aby wyjść z gry! + +[FEM_SL1] +Gniazdo 1 jest wolne + +[FEM_SL2] +Gniazdo 2 jest wolne + +[FEM_SL3] +Gniazdo 3 jest wolne + +[FEM_SL4] +Gniazdo 4 jest wolne + +[FEM_SL5] +Gniazdo 5 jest wolne + +[FEM_SL6] +Gniazdo 6 jest wolne + +[FEM_SL7] +Gniazdo 7 jest wolne + +[FEM_SL8] +Gniazdo 8 jest wolne + +[FEM_MM] +MENU GŁÓWNE + +[FEQ_SRE] +Czy na pewno chcesz wyjść z gry? Efekty wszystkich działań podjętych od czasu ostatniego zapisu gry zostaną utracone. Kontynuować? + +[FEQ_SRW] +Czy na pewno chcesz wyjść z gry? + +[FEG_SRV] +SERWER + +[FEG_MAP] +MAPA + +[FEG_PLY] +GRACZE + +[FEG_TYP] +RODZAJ + +[FEG_PNG] +PING + +[FET_FG] +ODSZUKAJ SESJĘ GRY + +[FET_SP] +TRYB DLA JEDNEGO GRACZA + +[FET_MP] +TRYB WIELOOSOBOWY + +[FET_HG] +HOSTUJ GRĘ + +[FET_PS] +KONFIGURACJA GRACZA + +[FET_CON] +POŁĄCZENIE + +[FET_AUD] +KONFIGURACJA DŻWIĘKU + +[FET_DIS] +KONFIGURACJA EKRANU + +[FET_LAN] +OKREŚL JĘZYK + +[FET_LG] +WCZYTAJ GRĘ + +[FET_DG] +USUŃ GRĘ + +[FET_NG] +NOWA GRA + +[FET_SG] +ZAPISZ GRĘ + +[FET_MAP] +WYBIERZ MAPĘ + +[FET_GT] +RODZAJ GRY + +[FET_CTL] +KONFIGURACJA STEROWANIA + +[FET_OPT] +OPCJE + +[FET_QG] +WYJDŹ Z GRY + +[FET_STA] +STATYSTYKI + +[FET_BRE] +CELE + +[FEC_WAR] +Ostrzeżenie + +[FEC_OKK] +OK + +[FED_CON] +Potwierdzenie usunięcia pliku + +[FES_SSC] +Gra została pomyślnie zapisana. + +[DEL_FNM] +Plik został pomyślnie usunięty. + +[PCLOAD] +Wczytywanie danych z pliku + +[PCRESRT] +Trwa uruchamianie Grand Theft Auto III + +[FEC_DLF] +Kasowanie nieudane. + +[FEC_SVU] +Zapis nieudany. + +[FEC_LUN] +Wczytywanie nieudane. Plik uszkodzony. Proszę go usunąć. + +[FEN_PLA] +Liczba graczy: + +[FET_NON] +BRAK DOSTĘPNYCH GIER + +[FET_SFG] +WYSZUKIWANIE SESJI GRY... + +[FET_SRT] +SORTOWANIE SESJI GRY... + +[FEF_LAN] +LAN + +[FEF_INT] +INTERNET + +[FET_REF] +Odśwież + +[FET_FIL] +Filtr + +[FET_JG] +Dołącz + +[FEC_NTW] +Rozmowa przez sieć + +[FEC_ESR] +Klawisz ESC jest zastrzeżony + +[FEC_GSL] +Pokazuj wstrząsy głowy: + +[FIL_FLT] +FILTR LISTY SESJI GRY + +[FET_SAN] +ROZPOCZNIJ NOWĄ GRĘ + +[FIL_MAP] +Mapa: + +[FIL_SRV] +Serwer: + +[FIL_TYP] +Rodzaj gry: + +[FIL_SPC] +Gry z wolnym miejscem? + +[FIL_PNG] +Ping: + +[FEN_UKH] +Nieznany host + +[FEN_UKM] +Nie odnaleziono mapy + +[FEN_UKT] +Nie odnaleziono danego rodzaju gry + +[FEN_NCI] +BRAK POŁĄCZENIA Z INTERNETEM + +[FET_PAU] +MENU PAUZY + +[FET_SGA] +ROZPOCZNIJ GRĘ + +[FEC_SGJ] +Ustaw joystick do gry + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FEC_WHL] +Kierownica + +[FEC_CNT] +Typ sterownika: + +[FES_CSA] +Wybierz 'skórę' z poniższej listy: + +[FES_SKN] +NAZWA SKÓRY + +[FES_DAT] +DATA + +[FES_NON] +BRAK DOSTĘPNYCH 'SKÓR' + +[FEA_FM9] +ODTWARZACZ MP3 + +[FESZ_QZ] +Czy jesteś pewien, że chcesz zapisać grę? + +[FES_CGA] +Dostępne gniazda gry: + +[FES_SCG] +Zapisać bieżącą grę? + +[FES_LCG] +Wczytać grę i kontynuować rozgrywkę? + +[FEC_FIR] +Strzał + +[FEC_NWE] +Następna broń + +[FEC_PWE] +Poprzednia broń + +[FEC_FOR] +Do przodu + +[FEC_BAC] +Do tyłu + +[FEC_LEF] +W lewo + +[FEC_RIG] +W prawo + +[FEC_ZIN] +Przybliżenie + +[FEC_ZOT] +Oddalenie + +[FEC_EEX] +Wejście/wyjście + +[FEC_RAD] +Radio + +[FEC_SUB] +Podmisja + +[FEC_CMR] +Zmiana kamery + +[FEC_JMP] +Skok + +[FEC_SPN] +Sprint + +[FEC_HND] +Hamulec ręczny + +[FEC_TUL] +Wieżyczka w lewo + +[FEC_TUR] +Wieżyczka w prawo + +[FEC_LOL] +Spójrz w lewo + +[FEC_LOR] +Spójrz w prawo + +[FEC_NTR] +Następny cel + +[FEC_PTT] +Poprzedni cel + +[FEC_LBA] +Spójrz do tyłu + +[FEC_CEN] +Wyśrodkuj kamerę + +[FEC_UND] +(NIE) + +[FET_CFT] +PIESZO + +[FET_CCR] +W AUCIE + +[CVT_MSG] +Trwa konwersja tekstur do formatu optymalnego dla zainstalowanej karty graficznej. + +[FET_CAC] +CZYNNOŚĆ + +[FEC_IBT] +- + +[FEC_SPC] +SPACJA + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +UNBOUND + +[FET_CME] +SPOSÓB STEROWANIA + +[FET_RDK] +ZMIANA KLAWISZY STERUJĄCYCH + +[FET_AMS] +USTAWIENIA MYSZY + +[FET_STI] +ZWYKŁA KONFIGURACJA STEROWANIA + +[FET_CTI] +STANDARDOWA KONFIGURACJA STEROWANIA + +[FET_MTI] +KONFIGURACJA STEROWANIA MYSZĄ + +[FET_DAM] +DYNAMICZNE MODELOWANIE AKUSTYCZNE + +[FEC_TFL] +Wieżyczka w lewo + +[FEC_TFR] +Wieżyczka w prawo + +[FEC_MWF] +KÓŁKO W GÓRĘ + +[FEC_MWB] +KÓŁKO W DÓŁ + +[FEC_ORR] +lub + +[FEC_NUS] +NIEUŻYWANY + +[FEC_LUD] +Spójrz w górę + +[FEC_LDU] +Spójrz w dół + +[FEC_CMP] +KOMBINACJA: SPÓJRZ W LEWO + SPÓJRZ W PRAWO + +[FEC_NTT] +Brak tekstu dla tego klawisza + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INSERT + +[FEC_DLL] +DELETE + +[FEC_HME] +HOME + +[FEC_END] +END + +[FEC_PGU] +PAGE UP + +[FEC_PGD] +PAGE DOWN + +[FEC_UPA] +STRZAŁKA W GÓRĘ + +[FEC_DWA] +STRZAŁKA W DÓŁ + +[FEC_LFA] +W LEWO + +[FEC_RFA] +STRZAŁKA W PRAWO + +[FEC_NUM] +NUM + +[FEC_NMN] +NUM~1~ + +[FEC_FWS] +NUM / + +[FEC_PLS] +NUM + + +[FEC_MIN] +NUM - + +[FEC_DOT] +NUM . + +[FEC_NLK] +NUM LOCK + +[FEC_ETR] +ENTER + +[FEC_SLK] +SCROLL LOCK + +[FEC_PSB] +PAUSE BREAK + +[FEC_BSP] +BACKSPACE + +[FEC_TAB] +TAB + +[FEC_CLK] +CAPS LOCK + +[FEC_RTN] +RETURN + +[FEC_LSF] +LEWY SHIFT + +[FEC_RSF] +PRAWY SHIFT + +[FEC_LCT] +LEWY CTRL + +[FEC_RCT] +PRAWY CTRL + +[FEC_LAL] +LEWY ALT + +[FEC_RAL] +PRAWY ALT + +[FEC_LWD] +LEWY KLAWISZ WINDOWS + +[FEC_RWD] +PRAWY KLAWISZ WINDOWS + +[FEC_WRC] +WINCLICK + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Gra Grand Theft Auto III nie pracuje pod systemem Windows 95. + +[WIN_DX] +Gra Grand Theft Auto III wymaga bibliotek DirectX w wersji 8.1 lub nowszych. + +[WIN_VDM] +Gra Grand Theft Auto III wymaga karty graficznej z przynajmniej 12 MB RAM. + +[DIAB3_G] +Arriba! + +[FEM_RES] +WZNÓW GRĘ + +[FES_SNG] +ROZPOCZNIJ NOWĄ GRĘ + +[FEM_SP] +TRYB DLA JEDNEGO GRACZA + +[FEM_MP] +TRYB WIELOOSOBOWY + +[FEM_QT] +WYJDŹ Z GRY + +[FES_SG] +ROZPOCZNIJ NOWĄ GRĘ + +[FES_LG] +WCZYTAJ GRĘ + +[FEM_HST] +HOST GRY + +[FEM_OPT] +OPCJE + +[FEM_DBG] +DEBUGOWANIE + +[FET_PSU] +KONFIGURACJA GRACZA + +[FET_DEF] +PRZYWRÓĆ USTAWIENIA DOMYŚLNE + +[FED_BRI] +JASNOŚĆ + +[FED_TRA] +ŚLADY + +[FEM_LOD] +ODLEGŁOŚC RYSOWANIA + +[FEM_VSC] +SYNCHRONIZACJA KLATEK + +[FEM_FRM] +OGRANICZENIE KLATEK + +[FED_RES] +ROZDZIELCZOŚĆ EKRANU + +[FED_WIS] +SZEROKI EKRAN + +[FEDS_TB] +WSTECZ + +[FEA_MUS] +GŁOŚNOŚĆ MUZYKI + +[FEA_SFX] +GŁOŚNOŚĆ EFEKTÓW + +[FEA_RSS] +STACJA RADIOWA + +[FEL_ENG] +ANGIELSKI + +[FEL_FRE] +FRANCUSKI + +[FEL_GER] +NIEMIECKI + +[FEL_ITA] +WŁOSKI + +[FEL_SPA] +HISZPAŃSKI + +[FEA_3DH] +SPRZĘT AUDIO + +[FEA_SPK] +KONFIGURACJA GŁOŚNIKÓW + +[FEA_2SP] +DWA GŁOŚNIKI + +[FEA_4SP] +WIĘCEJ NIŻ DWA GŁOŚNIKI + +[FEA_EAR] +SŁUCHAWKI + +[FEA_NAH] +BRAK SPRZĘTU AUDIO + +[FET_SNG] +ROZPOCZNIJ NOWĄ GRĘ + +[FEN_STA] +ROZPOCZNIJ GRĘ + +[GMLOAD] +WCZYTAJ GRĘ + +[GMSAVE] +ZAPISZ GRĘ + +[FES_DGA] +USUŃ GRĘ + +[FEM_NON] +BRAK + +[FEC_IVV] +ODWRÓĆ OSIE MYSZY W PIONIE + +[FEC_MSH] +CZUŁOŚĆ MYSZY + +[FET_CCN] +STEROWANIE: STANDARDOWE + +[FET_SCN] +STEROWANIE: ZWYKŁE + +[FES_SET] +UŻYJ 'SKÓRY' + +[GHOST] +Ghost + +[WIN_RSZ] +Zmiana rozmiaru ekranu nieudana. + +[FEC_TFU] +Wież./dodo w górę + +[FEC_TFD] +Wież./dodo w dół + +[FET_APL] +ZASTOSUJ + +[FET_APP] +KLIKNIJ LPM LUB RETURN, ABY ZASTOSOWAĆ NOWE USTAWIENIA + +[FET_HRD] +PRZYWRÓCONO USTAWIENIA DOMYŚLNE + +[FET_MST] +STEROWANIE POJAZDAMI ZA POMOCĄ MYSZY + +[FEC_STR] +NUM * + +[FET_MIG] +STRZAŁKA W LEWO, W PRAWO, KÓŁKO MYSZY, ABY REGULOWAĆ + +[FET_CIG] +BACKSPACE: USUWANIE - LPM, RETURN - ZMIANA + +[FET_RIG] +WYBIERZ NOWY KLAWISZ LUB NACIŚNIJ ESC, ABY ANULOWAĆ + +[FET_EIG] +NIE MOŻNA PRZYPISAĆ KLAWISZA TEJ CZYNNOŚCI + +[NO_PCCD] +Włóż do napędu CD-ROM płytę z grą Grand Theft Auto III nr 2 albo naciśnij ESC, aby anulować. + +[CVT_ERR] +Na dysku twardym zabrakło wolnego miejsca. Przed dalszą pracą z programem zwolnij odpowiednią ilość pamięci. Aby wyjść, naciśnij ESC. + +[FED_SUB] +NAPISY + +[FET_DSN] +Skin gracza.bmp + +[JM3] +'SKOK NA KONWÓJ' + +[ATUTOR2] +~g~OSTROŻNIE przewoź pacjentów do Szpitala. Każde zderzenie zmniejsza ich szanse przeżycia. + +[EBAL] +'WOLNOŚĆ W LIBERTY' + +[LM4] +'ALFONS' + +[REPLAY] +POWTÓRKA + +[FEC_SFT] +SHIFT + +[CRED254] +KIEROWNIK STUDIA + +[CVT_CRT] +Nie można dokonać konwersji tekstur dla zainstalowanej karty graficznej. W tym celu należy zalogować się na konto Administratora. Aby wyjść, naciśnij ESC. + +[FEM_ON] +WŁ. + +[FEM_OFF] +WYŁ. + +[FEM_YES] +TAK + +[FEM_NO] +NIE + +[FES_WAR] +Trwa zapisywanie, proszę czekać... + +[FED_DLW] +Trwa kasowanie, proszę czekać... + +[FED_LDW] +Trwa wczytywanie, proszę czekać... + +[FEC_SLC] +Gniazdo jest uszkodzone + +[FED_LFL] +Nieudane wczytanie zapisanej gry. Za chwilę gra uruchomi się ponownie. + +[FET_RSO] +PRZYWRÓCONO PIERWOTNE USTAWIENIA + +[FET_RSC] +URZĄDZENIE NIEDOSTĘPNE - PRZYWRÓCONO PIERWOTNE USTAWIENIA + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPOŃSKI + +[FEL_POL] +POLSKI + +[FEL_RUS] +ROSYJSKI + +{ new display menus } +[FET_GFX] +USTAWIENIA GRAFIKI + +[FED_MIP] +MIPMAPPING + +[FED_AAS] +WYGŁADZANIE KRAWĘDZI + +[FED_FIL] +FILTROWANIE TEKSTUR + +[FED_BIL] +DWULINIOWE + +[FED_TRL] +TRÓJLINIOWE + +[FED_WND] +OKIENKOWY + +[FED_FLS] +PEŁNY EKRAN + +[FEM_CSB] +RAMKI CUTSCENEK + +[FEM_SCF] +FORMAT OBRAZU + +[FEM_ISL] +PRZYPISZ WYKORZYSTANIE PAMIĘCI + +[FEM_LOW] +NISKIE + +[FEM_MED] +ŚREDNIE + +[FEM_HIG] +WYSOKIE + +[FEM_2PR] +ALFA TEST PS2 + +[FEC_FRC] +SWOBODNA KAMERA + +{ Linux joy detection } +[FEC_JOD] +WYKRYJ PADA + +[FEC_JPR] +Naciśnij dowolny klawisz na padzie, którego chcesz użyć w grze. + +[FEC_JDE] +Wykryto pada + +{ mission restart } +[FET_RMS] +PONÓW MISJĘ + +[FESZ_RM] +PONOWIĆ? + +{ more graphics } +[FED_VPL] +POTOK POJAZDÓW + +[FED_PRM] +PODŚWIETLENIE PED + +[FED_RGL] +POŁYSK DROGI + +[FED_CLF] +FILTR KOLORU + +[FED_WLM] +LIGHTMAPY ŚWIATA + +[FED_MBL] +ROZMYCIE RUCHU + +[FEM_SIM] +PROSTE + +[FEM_NRM] +NORMALNY + +[FEM_MOB] +MOBILNY + +[FED_MFX] +MATFX + +[FED_NEO] +PODŚWIETLENIE + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +AUTO + +[FEC_IVP] +ODWRÓĆ OŚ PADA W PIONIE + +{ map } +[FEM_TWP] +Toggle Waypoint + +[FEA_FMN] +RADIO OFF + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +XBOX 360 CONTROLLER + +[FEC_ONE] +XBOX ONE CONTROLLER + +[FEC_NSW] +NINTENDO SWITCH CONTROLLER + +[FEC_TYP] +GAMEPAD TYPE + +[FEC_CCF] +KONFIGURACJA + +[FEC_CF1] +SETUP1 + +[FEC_CF2] +SETUP2 + +[FEC_CF3] +SETUP3 + +[FEC_CF4] +SETUP4 + +[FEC_CDP] +STEROWNIK EKRANU + +[FEC_ONF] +PIESZO + +[FEC_INC] +W AUCIE + +[FEC_VIB] +WIBRACJA + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] + +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/russian.txt b/utils/gxt/russian.txt new file mode 100644 index 0000000..3b42bb2 --- /dev/null +++ b/utils/gxt/russian.txt @@ -0,0 +1,8118 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. +} + +[1000] +ТЫ ПОКОЙНИК + +[1001] +ТЫ ПОКОЙНИК + +[1002] +ТЫ ПОКОЙНИК + +[1003] +ТЫ ПОКОЙНИК + +[1004] +ТЫ ПОКОЙНИК + +[1005] +АРЕСТОВАН + +[1006] +АРЕСТОВАН + +[1007] +АРЕСТОВАН + +[1008] +АРЕСТОВАН + +[1009] +АРЕСТОВАН + +[1010] +~r~Твоя тачка перевернулась + +[1011] +~r~Твоя тачка перевернулась + +[1012] +~r~Твоя тачка перевернулась + +[1013] +~r~Твоя тачка перевернулась + +[1014] +~r~Твоя тачка перевернулась + +[8001] +Тебе крупно не повезло!! + +[ACCURA] +Меткость + +[AEROPL] +Самолет + +[AIRPORT] +Аэропорт Фрэнсис + +[ALEVEL] +Скорая помощь. Задание ~1~ + +[AM1] +'САЙОНАРА, САЛЬВАТОРЕ' + +[AM1_1] +~g~Сейчас Сальваторе выходит из клуба Луиджи! + +[AM1_10] +~g~Сальваторе уйдет от Луиджи в 0~1~:~1~ + +[AM1_2] +~r~Тебя засекли! + +[AM1_3] +~r~Ты упустил Сальваторе! + +[AM1_4] +~r~Кретин, ты его спугнул! Какой ты к черту киллер? + +[AM1_5] +~g~Отправляйся в квартал красных фонарей и жди, когда Сальваторе выйдет из клуба. + +[AM1_6] +~g~Если ты будешь болтаться около клуба Луиджи, то тебя увидит мафия! + +[AM1_7] +~r~Сальваторе дома в безопасности попивает коктейль. Все с тобой ясно, шакал! + +[AM1_8] +~g~Сальваторе уйдет от Луиджи примерно в ~1~:~1~ + +[AM1_9] +~r~Сальваторе скрылся в клубе Луиджи! + +[AM1_A] +Нам нужно кое что прояснить, прежде чем начнем налаживать какие-либо отношения, + +[AM1_B] +деловые или нет. Давай раскроем карты. + +[AM1_C] +Я из Якудзы и знаю, что ты работал на семью Сальваторе Леоне. + +[AM1_D] +Я могу дать тебе работу в нашей организации, + +[AM1_E] +но сначала тебе придется доказать, что ты на самом деле порвал с мафией. + +[AM1_F] +Сальваторе Леоне выйдет от Луиджи примерно через три часа. (~1~:~1~) + +[AM1_G] +Постарайся, чтобы он не добрался до дома живым. + +[AM1_H] +Ну а мы с Марией пока вспомним былые времена. + +[AM1_I] +О, Асука, у тебя новый массажист. + +[AM1_J] +Это не массажист. + +[AM1_K] +Сальваторе Леоне выйдет от Луиджи примерно в три часа. (0~1~:~1~) + +[AM2] +'ПОД НАДЗОРОМ' + +[AM2_4] +~g~Они тебя засекли, ты шел как слепой слон! + +[AM2_A] +Смерть Сальваторе - очень приятная новость, + +[AM2_A2] +ты отличный киллер. Мне нравятся такие мужики. + +[AM2_B] +Это мой брат Кенжи. + +[AM2_C] +У Асуки для тебя есть работа, но как только освободишься - загляни ко мне в казино. + +[AM2_D] +Кенжи как всегда тянет руки к моим игрушкам. + +[AM2_E] +Мои люди в полиции сообщили, что мафия следит за всеми нашими делами в городе, + +[AM2_E2] +так они пытаются выйти на тебя. + +[AM2_F] +Они мешают нам работать. Нужно устранить эту проблему. + +[AM2_G] +Разберись с этими шпионами-самоучками и прекрати вендетту раз и навсегда. + +[AM3] +'ПРОЩАЙ, ПАПАРАЦЦИ' + +[AM3_A] +Тут один журналист что-то вынюхивает. + +[AM3_B] +Мы с Марией немного отдохнем, пока ты не избавишься от этого назойливого писаки. + +[AM3_C] +Пока ты это читаешь, он, вероятно, в гавани! Угони полицейский катер и 'потопи' его карьеру! + +[AM4] +'ДЕНЬГИ ДЛЯ РЭЯ' + +[AM4_10] +Знаешь, я тоже иногда могу подкинуть работенку. + +[AM4_11] +Если что - ты знаешь, где меня найти. + +[AM4_1A] +Доберись до телефона к западу от парка Бельвиль. + +[AM4_1B] +Доберись до телефона в Учебном городке. + +[AM4_1C] +Доберись до телефона к югу от парка Бельвиль. + +[AM4_1D] +Встречаемся в туалете, расположенном в парке. + +[AM4_3] +Ты, должно быть, новый посыльный Асуки! + +[AM4_4] +Ты принес мне деньги? Это все? + +[AM4_5] +Я знаю, ты думаешь - еще один продажный коп. + +[AM4_6] +Да, мы живем в продажном мире. + +[AM4_7] +Мной занялась служба внутреннего расследования только из-за того, что я потерял двух напарников. + +[AM4_8] +Приходится быть начеку. + +[AM4_9] +Да, весь этот город - одна большая помойная яма. + +[AM4_A] +Да это же мой красавчик! + +[AM4_B] +Мария сейчас очень занята, но я ей передам, что ты заходил. + +[AM4_C] +Асука, кто это? Я бы и сама вышла, но я очень хочу писать, окей? + +[AM4_D] +Ты должен встретиться с моим знакомым копом. + +[AM4_E] +Это деньги за его последнюю работу, что он сделал для нас. + +[AM4_F] +Разумеется, он очень осторожен. + +[AM4_G] +Немедленно отправляйся к телефону-автомату в Торрингтоне и жди его инструкций. + +[AM5] +'ДВУЛИКИЙ ТАННЕР' + +[AM5_1] +Таннер у тебя на хвосте! + +[AM5_A] +Мы с Марией пошли по магазинам. + +[AM5_B] +Мой человек в полиции сообщил, что один из наших водил, на самом деле - полицейский под прикрытием! + +[AM5_C] +Он не вылезает из своей тачки, поэтому мы прикрепили к ней маячок. + +[AM5_D] +Пусти ему кровь! + +[AMBULAN] +Скорая + +[AMBUL_M] +'СКОРАЯ ПОМОЩЬ' + +[AMMU] +Чтобы купить оружие - зайди в магазин. + +[AMMU_A] +Луиджи сказал, тебе нужен ствол... + +[AMMU_B] +Джоуи кое что для тебя заказал... + +[AMMU_C] +Иди во двор магазина, я там тебе кое-что припас. + +[AMMU_D] +У меня есть все что тебе понадобится. + +[AMMU_E] +Тебе и лицензия нужна? + +[AMMU_F] +Документов можешь не показывать, я и так тебе верю. + +[APR] +Апр + +[AS1] +'НАЖИВКА' + +[AS1_A] +~w~Мигель считает, что я плохо с ним обращаюсь. + +[AS1_B] +~w~Но он все-таки рассказал, что Каталина жутко боится твоей мести. + +[AS1_C] +~w~Она послала в город три группы убийц, чтобы они выследили и прикончили тебя. + +[AS1_D] +~w~Тебе придется побыть наживкой и постараться заманить их в Пайк Крик, + +[AS1_E] +~w~там их уже будут поджидать мои люди. + +[AS1_G] +~r~Все якудза перебиты! + +[AS1_H] +~r~Тебе не удалось заманить убийц в ловушку Якудзы! + +[AS2] +'КОФЕ НА ВЫНОС!' + +[AS2_1] +~g~В Портланде уничтожены все кофейные ларьки! + +[AS2_10] +~g~Кофейные ларьки еще стоят в Портланде и на острове Стаунтон. + +[AS2_11] +~g~~1~ ИЗ 9! + +[AS2_12] +~g~Объедь все районы города и отыщи ~b~кофейные ларьки~g~! + +[AS2_12A] +~g~После того как ты уничтожишь первый ларек, у тебя будет 8 минут, прежде чем Картель забьет тревогу! + +[AS2_2] +~g~На острове Стаунтон уничтожены все кофейные ларьки! + +[AS2_3] +~g~В Шорсайд Вейл уничтожены все кофейные ларьки! + +[AS2_4] +~r~Картель предупредил продавцов наркоты! + +[AS2_5] +~g~Кофейные ларьки еще стоят в Шорсайд Вейл и на острове Стаунтон! + +[AS2_6] +~g~Кофейные ларьки еще стоят в Шорсайд Вейл! + +[AS2_7] +~g~Кофейные ларьки еще стоят на острове Стаунтон! + +[AS2_8] +~g~Кофейные ларьки еще стоят в Портланде! + +[AS2_9] +~g~Кофейные ларьки еще стоят в Портланде и в Шорсайд Вейл! + +[AS2_A] +~w~Мы недооценили план Каталины по реализации СПАНКа. + +[AS2_A1] +~w~Мигель вынослив, как настоящий латиноамериканец. + +[AS2_A2] +~w~Я уже выдохлась. + +[AS2_B] +~w~Ярди - торгующие СПАНКом по всему городу - это мелкие сошки. + +[AS2_C] +~w~Картель действует через свою компанию 'The Kappa Coffee House'. + +[AS2_D] +~w~Они продают СПАНК через сеть уличных ларьков. + +[AS2_E] +~w~Нам не остается ничего другого, кроме как прикрыть все эти лавочки. + +[AS2_F] +~w~Разнеси их в щепки! + +[AS3] +'БЫЛО ВАШЕ, СТАЛО НАШЕ' + +[AS3_1] +~g~Найди ~r~катер~g~ и встань около ~b~буя~g~! + +[AS3_1A] +~g~Теперь двигайся к ~b~бую~g~! + +[AS3_2] +~b~Двигайся к буям на посадочной полосе! ~y~Самолет уже на подлете! + +[AS3_3] +~g~Подожди, пока не покажется ~y~самолет~g~! + +[AS3_4] +~g~Сбей ~y~самолет~g~ из ракетницы! + +[AS3_5] +~g~Собирай товар! + +[AS3_6] +~g~~1~ ИЗ 8 + +[AS3_A] +~W~Ну что, затянем немного потуже, или просто подождем, пока почернеет и отвалится? + +[AS3_B] +~w~Ну ка, давай посмотрим... + +[AS3_C] +~w~Фууууууууу! что это за желтая липкая гадость? + +[AS3_C1] +~w~Привет малыш. + +[AS3_D] +~w~Мой работничек! + +[AS3_E] +~w~Мне было скучно, и я решила составить Асуке компанию. + +[AS3_F] +~w~Похоже она очень одаренная девочка. + +[AS3_F1] +~w~Ей удалось узнать кое что важное у нашего гостя. + +[AS3_G] +~w~В два часа в аэропорт Фрэнсис Интернешнл прибывает самолет. + +[AS3_G1] +~w~Он просто набит отравой Каталины. + +[AS3_H] +~w~Чтобы тебе не мешалась служба безопасности, возьми катер чтобы добраться до буев + +[AS3_H1] +и взорвать самолет, когда тот пойдет на посадку. + +[AS3_I] +~w~Найди среди обломков груз и забери его! + +[AS3_J] +~w~Малыш, будь поосторожней там, Окей? + +[AS3_K] +~w~Попробуй-ка соус чили... + +[AS4] +'ВЫКУП' + +[ASUKA] +ЗАДАНИЯ АСУКИ + +[ATUTOR] +Чтобы получить задание Скорой или отказаться от него нажми ~h~~k~~TOGGLE_SUBMISSIONS~~w~. + +[ATUTOR2] +~g~ОСТОРОЖНО отвози пациентов в больницу. Каждое столкновение ухудшает их состояние. + +[ATUTOR3] +Чтобы получить задание Скорой или отказаться от него нажми ~h~~k~~TOGGLE_SUBMISSIONS~~w~. + +[AUG] +Авг + +[AWAY] +~r~Он свалил отсюда! + +[AWAY2] +~r~Они свалили. + +[A_CANC] +~r~Задание скорой отменено! + +[A_COMP1] +Все задания скорой выполнены! + +[A_COMP2] +Ты никогда не будешь уставать! + +[A_COMP3] +Все задания скорой выполнены! Ты никогда не будешь уставать при быстром беге! + +[A_FAIL1] +Задание скорой прервано. + +[A_FAIL2] +~r~Из-за твоей медлительности скончался пациент! + +[A_FAIL3] +~r~Пациент скончался! + +[A_FULL] +~r~Скорая переполнена! + +[A_PASS] +Спасен! + +[A_RANGE] +~g~Рация в скорой не ловит сигнал, подъедь ближе к больнице! + +[A_SAVES] +ЛЮДЕЙ СПАСЕНО: ~1~ + +[A_TIME] ++~1~ секунд + +[BANSHEE] +Банши + +[BARRCKS] +Барракс OL + +[BAT1] +~g~Возьми биту! + +[BELLYUP] +Фургон Триад + +[BETRA_A] +Прости, детка. + +[BETRA_B] +У меня свои планы, а ты... + +[BETRA_C] +Ты мне больше не нужен. + +[BET_JB] +ПРЕДАННЫЙ СВОЕЙ ВОЗЛЮБЛЕННОЙ КАТАЛИНОЙ, БРОСИВШЕЙ ЕГО УМИРАТЬ. ОСУЖДЕННЫЙ И ПРИГОВОРЕННЫЙ, ОН УЖЕ НА ПУТИ В ТЮРЬМУ. ОН НЕ МОЖЕТ ДУМАТЬ НИ О ЧЕМ ИНОМ, КРОМЕ...... МЕСТИ! + +[BFINJC] +Иджэкшен BF + +[BGWHON] +Big White Debug Light Switched On + +[BGWOFF] +Big White Debug Light Switched Off + +[BIG_DAM] +Плотина Кокрейн + +[BITCH_D] +~g~Мария мертва! + +[BLISTA] +Блиста + +[BOATIN1] +Запрыгни в катер и нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~ чтобы сесть за руль. + +[BOATIN2] +Если ты рядом с катером, то нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~, чтобы в него сесть. + +[BOATIN3] +Запрыгни в катер и нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~ чтобы сесть за руль. + +[BOATIN4] +Если ты рядом с катером, то нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~, чтобы в него сесть. + +[BOBCAT] +Рысь + +[BOMB] +Чтобы установить ~h~бомбу~w~ - поставь тачку в гараж. Стоимость бомбы - ~h~$1000~w~. + +[BOMB1] +Гараж Лысого + +[BONUS] +~g~ПРИЗ $~1~ + +[BORGNIN] +Борнини + +[BRIDGE1] +Как только отремонтируют мост Каллахан, ты сможешь попасть на остров Стаунтон. + +[BSTSTU] +Лучший БЕЗУМНЫЙ трюк: + +[BUGGY] +ОСТАЛОСЬ: + +[BULL] +СЛИТКОВ: + +[BUS] +Автобус + +[BUSTED] +АРЕСТОВАН! + +[B_SITE] +ЗАДАНИЯ АСУКИ (ПРИГОРОД) + +[CABBIE] +Кэб + +[CAM_A] +Чтобы сменить расположение ~h~камеры~w~, нажми ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~. + +[CAM_B] +Чтобы сменить расположение ~h~камеры~w~, нажми ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~. + +[CARSOFF] +Машины выключены. + +[CARS_ON] +Машины включены. + +[CAR_1] +Ambulance + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_2] +Firetruck + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Bus + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_3] +Police + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Manana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +FBIcar + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_CRU] +Машин разбито + +[CAR_EXP] +Машин взорвано + +[CAT1] +'ВЫКУП' + +[CAT1_A] +Твоя драгоценная Мария у меня. Если не хочешь, чтобы ее личико было похоже на мясной фарш, + +[CAT1_B] +привези мне $500,000 на виллу в Кедровой роще. + +[CAT1_E] +XXXX + +[CAT1_F] +Езжай к Каталине, пока не вышло время! + +[CAT2] +'ОБМЕН' + +[CAT2_A] +Интересно, ты пришел ради того, чтобы спасти Марию или вернуться ко мне? + +[CAT2_A1] +Да пошла ты, глупая сучка! + +[CAT2_B] +Хочу сказать тебе кое-что, + +[CAT2_B2] +мне так и хочется тебя пристрелить, но бизнес прежде всего. + +[CAT2_C] +Ты просто находка для меня, амиго! + +[CAT2_D] +Давай бабки. + +[CAT2_E] +О, да ты без дела не шатался! + +[CAT2_E2] +Но ты так и не понял, что мне нельзя верить. + +[CAT2_E3] +Пристрелите кретина. + +[CAT2_F] +Я сломала ноготь, и моя прическа растрепалась. А она обошлась мне в 50 долларов, представляешь? + +[CAT2_G] +Я была так напугана, но потом я сказала себе - ты уже взрослая девочка. + +[CAT2_H] +О, у нас будет так весело, потому что, ты знаешь, моя сестра сказала, что она поживет у нас со своими детьми, + +[CAT2_I] +потому что у ее мужа снова интрижка и... + +[CAT2_J] +Быстрее, взлетаем! + +[CATINF1] +~g~Убей Каталину! + +[CATINF2] +~g~Чтобы найти Каталину - следуй за вертушкой. + +[CAT_MON] +~g~У тебя недостаточно зелени. Необходимо $500,000 баксов. + +[CDERROR] +Ошибка чтения диска с игрой Grand Theft Auto III + +[CHAT] +Chatterbox FM + +[CHEAT1] +Чит-код активирован + +[CHEAT2] +Чит-код: оружие + +[CHEAT3] +Чит-код: здоровье + +[CHEAT4] +Чит-код: броня + +[CHEAT5] +Чит-код: в розыске + +[CHEAT6] +Чит-код: деньги + +[CHEAT7] +Чит-код: погода + +[CHEATOF] +Режим чит-кодов Выкл. + +[CHEATON] +Режим чит-кодов Вкл. + +[CHEETAH] +Гепард + +[CHEVOK] +CheckEveryOkB4Save + +[CHFIDL] +ВЫБЕРИТЕ ЗАПИСЬ ДЛЯ УДАЛЕНИЯ + +[CHFILE] +ВЫБЕРИТЕ ИГРУ ДЛЯ ЗАГРУЗКИ + +[CHINA] +Чайнатаун + +[CINCAM] +Вид из телекамеры + +[CITYZON] +Либерти Сити + +[CLZOOF] +Show Cull Zones Off + +[CLZOON] +Show Cull Zones On + +[CNCSAV] +Нельзя сохранить игру, сидя в тачке. + +[CNTLS] +Управление + +[CNTSAV] +Во время задания запись невозможна. + +[COACH] +Автобус-люкс + +[COLLECT] +СОБРАНО: + +[COLOMCR] +Круизер картеля + +[COLT_IN] +В магазин завезли пистолеты! + +[COM_EAS] +Ньюпорт + +[COM_ZON] +Остров Стаунтон + +[CONSTRU] +Форт Стаунтон + +[CONTRL] +Задание управления + +[COPCART] +~g~У тебя есть ~1~ секунд чтобы вернуться в полицейскую машину, иначе задание будет отменено. + +[COP_M] +'ПОЛИЦЕЙСКИЙ' + +[CO_ALL] +Ты взял всех преступников. Вот тебе кое-что... + +[CO_ONE] +Особые пакеты: ~1~ из ~1~ + +[CRD050A] +ТЕСТЕРЫ + +[CRD136A] +ALEX HORTON + +[CRD137A] +NAVID KHONSARI + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS. + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCER + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTION + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +ДИЗАЙН + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +АВТОРЫ СЮЖЕТА + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +ГЕРОИ + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMATION & DIRECTION + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +AUTO DESIGN + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +CODERS + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +SCORE + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +SOUND DESIGN & MASTERING + +[CRED042] +ALLAN WALKER + +[CRED043] +AUDIO PROGRAMMING + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD TESTERS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +TECHNICAL PRODUCER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +ROCKSTAR PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +VP OF MARKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +TECHNICAL COORDINATOR + +[CRED088] +BRANDON ROSE + +[CRED089] +QA MANAGER + +[CRED090] +JEFF ROSA + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +LIBERTY TREE EDITORIAL + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT-SCENES + +[CRED108] +SCRIPT BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER + +[CRED110] +AUDIO PRODUCED BY RENAUD SEBBANE + +[CRED111] +CAST + +[CRED112] +FRANK VINCENT AS SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO AS LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN AS TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT AS JOEY LEONE + +[CRED116] +DEBBI MAZAR AS MARIA + +[CRED117] +KYLE MACLACHLAN AS DONALD LOVE + +[CRED118] +ROBERT LOGGIA AS RAY MACHOWSKI + +[CRED119] +GURU AS 8-BALL + +[CRED120] +SONDRA JAMES AS MOMMA + +[CRED121] +LIANA PAI AS ASUKA + +[CRED122] +LES MAU AS KENJI + +[CRED123] +CYNTHIA FARRELL AS CATALINA + +[CRED124] +AL ESPINOSA AS MIGUEL + +[CRED125] +CHRIS PHILLIPS AS EL BURRO + +[CRED126] +HUNTER PLATIN AS CHICO + +[CRED127] +WALTER MUDU AS D-ICE + +[CRED128] +CURTIS MCCLARIN AS CURTLY + +[CRED129] +BILL FIORE AS DARKEL + +[CRED130] +CHRIS PHILLIPS AS MARTY CHONKS + +[CRED131] +HUNTER PLATIN AS CURLY BOB + +[CRED132] +WALTER MUDU AS KING COURTNEY + +[CRED133] +HUNTER PLATIN AS ONE-ARMED PHIL + +[CRED134] +KIM GURNEY AS MISTY + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRED137] +DIRECTED BY + +[CRED138] +PRODUCED BY + +[CRED139] +RECORDED AT MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORS + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRED142] +WRITTEN BY DAN HOUSER, NAVID KHONSARI & JAMES WORRALL + +[CRED143] +DIRECTED BY CRAIG CONNER, DAN HOUSER AND LAZLOW + +[CRED144] +PRODUCED BY RENAUD SEBBANE + +[CRED145] +CAST + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +RADIO STATIONS AND MUSIC + +[CRED218] +PRODUCERS FOR ROCKSTAR UK + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDITED BY + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING WRITTEN BY + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRSTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED254] +STUDIO MANAGER + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTIST + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CRED270] +MIKE HONG + +[CRGOFF] +ShowCarRoadGroups Off + +[CRIMRA] +Ваш рейтинг: + +[CRLDIC] +Создание и загрузка иконок + +[CRMGSV] +Create copy protected magazine directory + +[CRRGON] +ShowCarRoadGroups On + +[CRROOT] +CreateRootDir + +[CRUSH] +Поставь машину в голубой круг и выйди из нее. После этого машина будет утилизирована. + +[CR_1] +Кран не может поднять эту машину. + +[CTRSCR] +Центровка экрана + +[CTUTOR] +Нажми ~h~~k~~TOGGLE_SUBMISSIONS~~w~, чтобы заняться работой полицейского или отказаться от нее. + +[CTUTOR2] +Нажми ~h~~k~~TOGGLE_SUBMISSIONS~~w~, чтобы заняться работой полицейского или отказаться от нее. + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[CVT_CRT] +Конвертация текстур не выполнена. Чтобы ее выполнить, вам нужно войти под администраторским паролем. Нажмите ESC для отмены. + +[CVT_ERR] +У вас закончилось место на жестком диске. Пожалуйста, освободите место перед запуском игры. Нажмите ESC для отмены. + +[CVT_MSG] +Конвертация текстур в формат оптимальный для вашей видеокарты. + +[C_BREIF] +~g~~a~ - там в последний раз видели подозреваемого. + +[C_CANC] +~r~Миссия полицейского прервана! + +[C_ESCP] +~r~Подозреваемый скрылся! + +[C_FAIL] +Миссия полицейского не выполнена! + +[C_KILLS] +ПРЕСТУПНИКОВ УБИТО: ~1~ + +[C_PASS] +СПРАВЕДЛИВОСТЬ ВОСТОРЖЕСТВОВАЛА! + +[C_RANGE] +~g~Полицейская рация не ловит сигнал, надо подъехать ближе к участку! + +[C_TIME] +~r~Время выполнения задания вышло! + +[C_VIGIL] +НАГРАДА ОТ ПОЛИЦИИ! + +[DAM] +ПОВРЕЖДЕНИЯ: + +[DAYPLC] +Ежедневные расходы полиции + +[DAYSPS] +Дней проведенных за игрой + +[DBFOFF] +CTheScripts::DbgFlag Off + +[DBGFON] +CTheScripts::DbgFlag On + +[DBINST] +Двойной безумный трюк + +[DBL_CLF] +Double Clef FM + +[DBPINS] +Идеальный безумный трюк + +[DEAD] +ВЫРУБИЛСЯ! + +[DEBUGM] +Отладочное меню + +[DEC] +Дек + +[DED_CRI] +Убито преступников + +[DED_DED] +Убито должников + +[DED_HOK] +Убито шлюх + +[DEFDT] +--:---:---- --:--:-- + +[DEFNAM] +Клод---------------------- + +[DEL_FNM] +Файл удален. + +[DETON] +ВЗРЫВ ЧЕРЕЗ: + +[DIAB1] +'ГОНКА' + +[DIAB1_1] +~g~3..2..1.. СТАРТ! + +[DIAB1_2] +~g~Поздравляем, ты победил с отличным результатом: ~1~ секунд. + +[DIAB1_3] +~r~Куда тебе с нами тягаться, НЕУДАЧНИК! + +[DIAB1_4] +~g~Найди быструю тачку, и приезжай на старт. + +[DIAB1_5] +ВРЕМЯ: + +[DIAB1_A] +Эль Бурро хочет предложить тебе одно дельце. Двигай к телефону в Хепберн Хейтс, если тебя это заинтересовало. + +[DIAB1_B] +Это Эль Бурро из банды Дьяволов. + +[DIAB1_C] +Не так уж ты и крут. Подойди к телефону, и возможно Эль Бурро предложит тебе работенку. + +[DIAB1_D] +Ты новичок в городе, но на улицах о тебе уже ходят слухи. + +[DIAB1_E] +Мы решили устроить гонку, которая начнется у старой школы, что у моста Каллахан. + +[DIAB1_F] +Достань себе тачку получше, и если выиграешь гонку, получишь приз. + +[DIAB2] +'СМЕРТЬ ОТ МОРОЖЕНОГО' + +[DIAB2_1] +~g~Возьми дипломат в Харвуде. + +[DIAB2_2] +~g~Найди фургон с мороженым. + +[DIAB2_3] +~g~Припаркуй фургон с мороженым на пристани Атлантик. + +[DIAB2_4] +~g~Понажимай ~w~~k~~VEHICLE_HORN~~g~ чтобы включить на фургоне колокольчик. + +[DIAB2_5] +~g~Выйди из фургона и затем с помощью дистанционника взорви его. + +[DIAB2_6] +~g~Понажимай ~w~~k~~VEHICLE_HORN~~g~ чтобы включить на фургоне колокольчик. + +[DIAB2_7] +~g~Понажимай ~w~~k~~VEHICLE_HORN~~g~ чтобы включить колокольчик на фургоне. + +[DIAB2_A] +Когда я начал свой экзотический бизнес, моим единственным активом было содержимое моих кожаных штанов! + +[DIAB2_B] +Одна банда стала угрожать, что лишит меня этой штуки, если я не буду отстегивать им процент. + +[DIAB2_C] +Они не с тем связались, амиго. + +[DIAB2_D] +Эти кретины западают на мороженое. + +[DIAB2_E] +Возьми бомбу, что я спрятал в Харвуде, + +[DIAB2_F] +и угони фургон с мороженым, что катается по городу. + +[DIAB2_G] +Эти глупцы побегут на верную смерть, услышав колокольчик фургона. + +[DIAB2_H] +Они прячутся на складе пристани Атлантик. + +[DIAB3] +'ИСПЫТАНИЕ ОГНЕМ' + +[DIAB3_1] +УБЕЙ 25 ТРИАДОВЦЕВ + +[DIAB3_A] +Какой-то наглый триадовец прошлой ночью спер мою тачку, + +[DIAB3_B] +разбил ее и бросил догорать. + +[DIAB3_C] +В багажнике было несколько моих любимых безделушек - + +[DIAB3_D] +настоящая гордость коллекционера, таких сейчас не достать. + +[DIAB3_E] +Неподалеку от Чайнатауна я припрятал огнемет. + +[DIAB3_F] +Возьми его и научи этих вандалов из Триады бояться грозного орудия Эль Бурро. + +[DIAB3_G] +Арриба! + +[DIAB4] +'ПОРНОКРАД' + +[DIAB4_1] +~g~Подгони фургон на задний двор магазина порнухи. + +[DIAB4_A] +Один слишком шустрый делец угнал фургон с последним тиражом моего издания! + +[DIAB4_B] +Но этот обкурившийся кретин не закрыл дверцы фургона, + +[DIAB4_C] +и теперь все мои прекрасные + +[DIAB4_D] +порножурналы с изумительными фотографиями разбросаны по всему городу! + +[DIAB4_E] +Садись в фургон и иди по следу 'Петушка из Портленда' выпусков 1, 2 и 3, + +[DIAB4_F] +ну и собирай их заодно по пути. + +[DIAB4_G] +Как только выследишь этого шустрого отморозка - разберись с ним! + +[DIAB4_H] +Затем отгони мой фургон с грузом к сексшопу в квартале красных фонарей. + +[DIABLCR] +Жеребец Дьяволов + +[DIABLO] +ЗАДАНИЯ ДЬЯВОЛОВ + +[DLFILE] +Удаление файлов игры GTA3 + +[DODO] +Додо + +[DODO_FT] +Вы были в воздухе ~1~ секунд! + +[DRIVE_A] { re3 change } +Садясь в машину, возьми в руки Узи. Посмотри вправо или влево и нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~ для выстрела. + +[DRIVE_B] { re3 change } +Садясь в машину, возьми в руки Узи. Посмотри вправо или влево и нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~ для выстрела. + +[DSPLAY] +Экран + +[DSTROFF] +Debug Streaming Requests Off + +[DSTRON] +Debug Streaming Requests On + +[EASTBAY] +Портланд Бич + +[EBAL] +'НА СВОБОДУ' + +[EBAL_1] +Нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~, чтобы ~h~сесть за руль ~w~или ~h~выйти~w~ из машины. + +[EBAL_1B] +Нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~, чтобы ~h~сесть за руль ~w~или ~h~выйти~w~ из машины. + +[EBAL_2] +~g~Возвращайся в машину! + +[EBAL_3] +Это ~h~радар~w~. Он очень полезен для навигации в городе. Сейчас ~h~цветная отметка~w~ на нем показывает расположение укрытия! + +[EBAL_4] +~r~Лысый погиб! + +[EBAL_5] +~g~Достань тачку! + +[EBAL_6] +~g~Подбери Мисти! + +[EBAL_A] +Я знаю место на углу квартала Красных фонарей, где мы сможем залечь, + +[EBAL_A1] +но я хреновый водитель, так что, браток, лучше садись ты за руль. + +[EBAL_B] +Вот и приехали. Давай зайдем внутрь и сменим эти чертовы шмотки! + +[EBAL_D] +Я знаю одного парня с большими связями, его зовут Луиджи. + +[EBAL_D1] +Я с ним работал раньше, так что, может, он и тебе работенку подкинет. Давай, съездим к нему. + +[EBAL_E] +Давай, пошли, я тебя представлю. + +[EBAL_G] +Это клуб Луиджи. Давай не будем светиться и пройдем через черный ход. + +[EBAL_H] +Погоди меня тут, пока я схожу и потолкую с Луиджи. + +[EBAL_I] +Босс решил выйти и взглянуть на тебя... + +[EBAL_J] +У Лысого какие-то дела там наверху. + +[EBAL_K] +Сделай мне одолжение, парень. + +[EBAL_L] +Одной из моих девиц нужно прокатиться, так что бери тачку, забирай Мисти из клиники и вези сюда. + +[EBAL_M] +Запомни, никто не лапает моих девок! + +[EBAL_N] +Так что держи свои руки на баранке! + +[EBAL_O] +Если не завалишь это дело, то может найтись и другая работенка. Все, проваливай! + +[ELBURRO] +Лучший результат в гонке (сек) + +[EMVHPUP] +Мы заплатим хорошие бабки за старые и новые машины скорой помощи. Подгони их к крану на северо-востоке порта Портланда. + +[END_A] +Жители Кедровой рощи едва успели оправиться + +[END_B] +от шока, вызванного настоящей криминальной войной, + +[END_C] +всполошившей вчера весь район. + +[END_D] +Местный житель Клив Денвер рассказал полиции, + +[END_E] +что видел, как с места происшествия убегал бандит и какая-то брюнетка. + +[END_F] +Ой, ты знаешь, нам будет так клево, потому что, ну... знаешь, + +[END_G] +я люблю тебя, я, я, я правда люблю, потому что ты такой крутой сильный мужик + +[END_H] +и ты именно тот, кто мне нужен. + +[END_I] +Ой, о чем это я? + +[END_J] +Ну вот, я забыла. Но ты понял, что я имела в виду, правда? + +[END_K] +Люди бросились в укрытия, когда взрывы стали сотрясать ближайшие дома. + +[END_L] +Несколько жителей пострадало во время ожесточенной перестрелки + +[END_M] +между бандитами и вертолетом, кружившим над плотиной. + +[END_N] +Да, из садов открывался отличный вид на это зрелище! + +[END_O] +Когда вертолет наконец сбили, + +[END_P] +был фейерверк получше, чем на Четвертое июля! + +[END_Q] +Хотя найдено уже более двадцати тел, + +[END_R] +полиция все еще находит останки. + +[END_S] +Мы не получили официального опровержения слухов, о том, + +[END_T] +что все тела принадлежат членам колумбийского Картеля. + +[END_U] +Истинные причины этой бойни до сих пор неизвестны. + +[END_V] +Представляешь, я сломала ноготь и испортила прическу! + +[END_W] +А она обошлась мне в пятьдесят баксов... + +[ENFORCR] +Энфорсер + +[ENGLIS] +English + +[ESPERAN] +Эсперанто + +[EVID] +УЛИКИ: + +[FARE1] +~g~Едем в ~w~'Клуб секс кошечек' ~g~в Квартале красных фонарей. + +[FARE10] +~g~Едем в ~w~'Лапшу по-китайски' ~g~в Чайнатауне. + +[FARE11] +~g~Едем на ~w~строительную площадку ~g~в форте Стаунтон. + +[FARE12] +~g~Едем на ~w~футбольный стадион ~g~на Аспатрии. + +[FARE13] +~g~Едем в ~w~церковь ~g~в Бедфорд Поинт. + +[FARE14] +~g~Едем в ~w~казино ~g~в Торрингтоне. + +[FARE15] +~g~Едем к ~w~зданию университета ~g~в учебном городке. + +[FARE16] +~g~Едем в ~w~торговый комплекс ~g~в в Районе парка Бельвиль. + +[FARE17] +~g~Едем в ~w~музей ~g~в Ньюпорте. + +[FARE18] +~g~Едем к ~w~зданию 'Амко' ~g~в Торрингтоне. + +[FARE19] +~g~Едем в ~w~'Болт Бургер' ~g~в Бедфорд Поинт. + +[FARE2] +~g~Едем в ~w~'Сюпа Сейв' ~g~на Портланд Вью. + +[FARE20] +~g~Едем в ~w~парк ~g~в Бельвиле. + +[FARE21] +~g~Едем в ~w~аэропорт Фрэнсис~g~. + +[FARE22] +~g~Едем к ~w~плотине Кокрейн~g~. + +[FARE23] +~g~Едем к ~w~автосалону ~g~ рядом с плотиной Кокрейн. + +[FARE24] +~g~Едем в ~w~больницу ~g~в Пайк Крик. + +[FARE25] +~g~Едем в ~w~парк ~g~в Шорсайд Вейл. + +[FARE26] +~g~Едем к ~w~Северо-западным башням ~g~в Садах Вичита. + +[FARE3] +~g~Едем к ~w~старой школе ~g~в Чайнатауне. + +[FARE4] +~g~Едем в ~w~'Кафе Грейзи Джо' ~g~на мысе Каллахан. + +[FARE5] +~g~Едем в ~w~оружейный магазин ~g~в квартале красных фонарей. + +[FARE6] +~g~Едем в ~w~'Автомобили в кредит' ~g~на Сент-Марк. + +[FARE7] +~g~Едем в ~w~'Топлесс-Бар Вуди' ~g~в квартале красных фонарей. + +[FARE8] +~g~Едем в ~w~бистро Марко ~g~на Сент-Марк. + +[FARE9] +~g~Едем в ~w~Автосалон ~g~в гавани Портланда. + +[FARES] +ОТВЕЗЕНО: + +[FBICAR] +Машина ФБР + +[FEA_2SP] +ДВЕ КОЛОНКИ + +[FEA_3DH] +СПОСОБ ВЫВОДА ЗВУКА + +[FEA_4SP] +БОЛЬШЕ ДВУХ КОЛОНОК + +[FEA_DO] += + +[FEA_EAR] +НАУШНИКИ + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEFF FM + +[FEA_FM2] +JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +ПУСТОМЕЛЯ 109 + +[FEA_FM9] +ПРОИГРЫВАТЕЛЬ MP3 + +[FEA_LE] +< + +[FEA_MNO] +Моно + +[FEA_MUS] +ГРОМКОСТЬ МУЗЫКИ + +[FEA_NAH] +НЕТ ЗВУКОВОЙ КАРТЫ + +[FEA_NON] +Нет + +[FEA_OUT] +Выход: + +[FEA_RI] +> + +[FEA_RSS] +РАДИОСТАНЦИЯ + +[FEA_SFX] +ГРОМКОСТЬ ЗВУКОВ + +[FEA_SPK] +ТИП АКУСТИКИ + +[FEA_ST] +Стерео + +[FEA_UP] +; + +[FEB] +Фев + +[FEB_AUD] +Аудио + +[FEB_BRI] +Сообщения + +[FEB_CON] +Управление + +[FEB_CPC] +Настройка управления + +[FEB_DIS] +Экран + +[FEB_LAN] +Язык + +[FEB_PMB] +Задание предыдущей миссии: + +[FEB_SAV] +Загрузить + +[FEB_STA] +Статистика + +[FEC_ACC] +Газ + +[FEC_ACL] +Педаль газа + +[FEC_ATT] +Удар или выстрел из оружия + +[FEC_BAC] +Назад + +[FEC_BRA] +Тормоз / Задний ход + +[FEC_BRK] +Педаль тормоза + +[FEC_BSP] +ЗАБОЙ + +[FEC_CAM] +Расположение камеры + +[FEC_CAW] +Оружие в машине + +[FEC_CCM] +Поставить камеру сзади игрока. + +[FEC_CEN] +Центровка камеры + +[FEC_CLE] +Прокрутка оружия влево + +[FEC_CLK] +CAPSLOCK + +[FEC_CMM] +Общее управления + +[FEC_CMP] +ВМЕСТЕ: ВЗГЛЯД ВЛЕВО + ВЗГЛЯД ВПРАВО + +[FEC_CMR] +Выбор позиции камеры + +[FEC_CMS] +Изменение расположения камеры для всех вариантов. + +[FEC_CNT] +Тип управления: + +[FEC_CRD] +Перебор радиостанций + +[FEC_CRI] +Прокрутка оружия вправо + +[FEC_CWL] +Прокрутка оружия влево + +[FEC_CWR] +Прокрутка оружия вправо + +[FEC_DBG] +ОТЛАДОЧНОЕ МЕНЮ + +[FEC_DLF] +Удаление невозможно. + +[FEC_DLL] +DEL + +[FEC_DOT] +ДОП. + +[FEC_DWA] +СТРЕЛКА ВНИЗ + +[FEC_EEX] +В машину /Из машины + +[FEC_EMS] +Эту клавишу назначить нельзя. + +[FEC_END] +END + +[FEC_ENT] +В машину /Из машины + +[FEC_ENV] +Сесть в машину + +[FEC_ESR] +Клавишу ESC задавать нельзя + +[FEC_ETR] +ДОП ВВОД + +[FEC_EXV] +В машину /Из машины + +[FEC_FIR] +Выстрел + +[FEC_FNC] +F~1~ + +[FEC_FOR] +Вперед + +[FEC_FPC] +Вид от первого лица + +[FEC_FPO] +Оружие человека + +[FEC_FPR] +Управление от первого лица + +[FEC_FWS] +ДОП / + +[FEC_GSL] +Покачивание камеры при ходьбе: + +[FEC_HAB] +Ручной тормоз + +[FEC_HBR] +Ручной тормоз машины + +[FEC_HME] +HOME + +[FEC_HND] +Тормоз + +[FEC_HO3] +Сигнал (кнопка L3) + +[FEC_HOR] +Сигнал + +[FEC_HRN] +Сигнал + +[FEC_IBT] +- + +[FEC_IRT] +INS + +[FEC_IVH] +Инвертирование мыши по горизонтали: + +[FEC_IVV] +ИНВЕРТИРОВАНИЕ МЫШИ ПО ВЕРТИКАЛИ + +[FEC_JBO] +ДЖ. ~1~ + +[FEC_JMP] +Прыжок + +[FEC_JOY] +Джойстик + +[FEC_JUM] +Прыжок + +[FEC_LAL] +ЛЕВ.ALT + +[FEC_LB] +Оглянуться назад + +[FEC_LB1] +Взгляд + +[FEC_LB2] +назад + +[FEC_LB3] +Оглянуться назад + +[FEC_LBA] +Оглянуться назад + +[FEC_LBC] +Нажать взгляд Влево и Вправо. + +[FEC_LBH] +Посмотреть назад + +[FEC_LCT] +ЛЕВ.CTRL + +[FEC_LDN] +Посмотреть вниз + +[FEC_LDU] +Посмотреть вниз + +[FEC_LEF] +Влево + +[FEC_LFA] +СТРЕЛКА ВЛЕВО + +[FEC_LKL] +Посмотреть влево + +[FEC_LKT] +Зафиксировать цель + +[FEC_LL] +Посмотреть налево + +[FEC_LLF] +Взгляд налево из машины + +[FEC_LOF] +Посмотреть вперед + +[FEC_LOL] +Посмотреть налево + +[FEC_LOR] +Посмотреть направо + +[FEC_LR] +Посмотреть направо + +[FEC_LRG] +Взгляд вправо из машины + +[FEC_LRT] +Посмотреть вправо + +[FEC_LSF] +ЛЕВ.SHIFT + +[FEC_LUD] +Посмотреть вверх + +[FEC_LUN] +Загрузка невозможна. Файл поврежден, пожалуйста удалите эту запись. + +[FEC_LUP] +Посмотреть вверх + +[FEC_LWD] +ЛЕВ.WIN + +[FEC_MIN] +ДОП - + +[FEC_MOV] +Движение + +[FEC_MSH] +ЧУВСТВИТЕЛЬНОСТЬ МЫШИ + +[FEC_MSL] +ЛЕВ.КН.МЫШИ + +[FEC_MSM] +СРЕД.КН.МЫШИ + +[FEC_MSR] +ПРАВ.КН.МЫШИ + +[FEC_MWB] +КОЛЕСО МЫШИ ВНИЗ + +[FEC_MWF] +КОЛЕСО МЫШИ ВВЕРХ + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_NA] +НЕТ + +[FEC_NLK] +NUMLOCK + +[FEC_NMN] +ДОП ~1~ + +[FEC_NTR] +Следующая цель + +[FEC_NTT] +Неизвестная клавиша + +[FEC_NTW] +Разговор по сети + +[FEC_NUM] +ДОП + +[FEC_NUS] +НЕ ИСПОЛЬЗУЕТСЯ + +[FEC_NWE] +Следующее оружие + +[FEC_OJS] +Для каждого действия можно назначить лишь одну кнопку джойстика + +[FEC_OKK] +О.К. + +[FEC_OMS] +Для действия можно задать лишь одну кнопку мыши + + +[FEC_ORR] +или + +[FEC_PAD] +Геймпад + +[FEC_PAS] +Пауза + +[FEC_PAU] +Пауза + +[FEC_PED] +Управление героем + +[FEC_PFR] +Выстрел из оружия + +[FEC_PGD] +PGDN + +[FEC_PGU] +PGUP + +[FEC_PJP] +Прыжок + +[FEC_PLB] +Взгляд назад. + +[FEC_PLS] +ДОП + + +[FEC_PSB] +BREAK + +[FEC_PSH] +Выстрел + +[FEC_PSP] +Бежать + +[FEC_PTL] +Use LockTarget with Weapon Switch Left. + +[FEC_PTR] +Use LockTarget with Weapon Switch Right. + +[FEC_PTT] +Предыдущая цель + +[FEC_PWE] +Предыдущее оружие + +[FEC_PWF] +Идти вперед + +[FEC_PWL] +Идти влево + +[FEC_PWR] +Идти вправо + +[FEC_PWT] +Движение относительно камеры + +[FEC_QUE] +??? + +[FEC_R3] +(R3 button) + +[FEC_RAD] +Радио + +[FEC_RAL] +ПР.ALT + +[FEC_RCT] +ПР.CTRL + +[FEC_RFA] +СТРЕЛКА ВПРАВО + +[FEC_RIG] +Вправо + +[FEC_RS3] +Перебор радиостанций (L3 button) + +[FEC_RSC] +Перебор радиостанций + +[FEC_RSF] +ПР.SHIFT + +[FEC_RTN] +ВВОД + +[FEC_RUN] +Бежать + +[FEC_RWD] +ПР.WIN + +[FEC_SFT] +SHIFT + +[FEC_SGJ] +Настройка джойстика + +[FEC_SLC] +Slot is corrupted + +[FEC_SLK] +SCROLL LOCK + +[FEC_SM3] +Special mission trigger (R3 button) + +[FEC_SMS] +Показывать указатель мыши + +[FEC_SMT] +Вызов спецзадания + +[FEC_SPC] +ПРОБЕЛ + +[FEC_SPN] +Бежать + +[FEC_STR] +ДОП ЗВЕЗДОЧКА + +[FEC_SUB] +Задание доп. миссий + +[FEC_SVU] +Запись не удалась. + +[FEC_SZI] +Снайперский прицел + + +[FEC_SZO] +Снайперский прицел - + +[FEC_TAB] +TAB + +[FEC_TAR] +Цель + +[FEC_TDO] +Камера отладки ВЫКЛ. + +[FEC_TFD] +Башню /Додо вниз + +[FEC_TFL] +Башню влево + +[FEC_TFR] +Башню вправо + +[FEC_TFU] +Башню /Додо вверх + +[FEC_TGD] +Toggle Pad Game/Debug + +[FEC_TLF] +Следующая цель слева + +[FEC_TRG] +Следующая цель справа + +[FEC_TSM] +Задание доп. миссий + +[FEC_TSS] +Записать картинку с экрана + +[FEC_TUC] +Управление башней + +[FEC_TUL] +Башню влево + +[FEC_TUR] +Башню вправо + +[FEC_TWO] +Можно задать не более двух клавиш. + +[FEC_UJS] +Эту кнопку джойстика назначить нельзя. + +[FEC_UMS] +Эту кнопку мыши назначить нельзя. + +[FEC_UNB] +НЕ ЗАДАНО + +[FEC_UND] +(НЕТ) + +[FEC_UPA] +СТРЕЛКА ВВЕРХ + +[FEC_VEH] +Управление машиной + +[FEC_VES] +Управление машиной + +[FEC_WAR] +Внимание! + +[FEC_WHL] +Рулевое управление + +[FEC_WPN] +Выстрел из оружия + +[FEC_WRC] +WINCLICK + +[FEC_ZIN] +Приблизить + +[FEC_ZOT] +Отдалить + +[FEDL_WR] +Deleting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FEDSAS2] +<>-CHANGE SELECTION + +[FEDSAS3] +- CHANGE SELECTION + +[FEDSAS4] +;=<> - CHANGE SELECTION + +[FEDSSC1] +;-FASTER SCROLLING + +[FEDSSC2] +=-STOP SCROLLING + +[FEDS_AM] +<>-CHANGE MENU + +[FEDS_AS] +;=-CHANGE SELECTION + +[FEDS_BA] +" button - BACK + +[FEDS_SB] +/ button - SELECT " button - BACK + +[FEDS_SE] +/ button - SELECT + +[FEDS_SM] +L1,R1-CHANGE MENU + +[FEDS_SS] +L1,R1-CHANGE SELECTION + +[FEDS_ST] +START button - RESUME + +[FEDS_TB] +НАЗАД + +[FEDS_XB] +Выбор + +[FED_BRI] +ЯРКОСТЬ + +[FED_CON] +Подтвердите удаление записи + +[FED_DBG] +Отладочное меню + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Большая отладочная лампа переключена + +[FED_DLW] +Идет удаление. Пожалуйста, подождите... + +[FED_DSR] +Debug Streaming Requests + +[FED_LDW] +Идет загрузка. Пожалуйста, подождите... + +[FED_LFL] +Игру загрузить не удалось. Игра будет перезапущена. + +[FED_PAH] +Parse Heap + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_RES] +РАЗРЕШЕНИЕ ЭКРАНА + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_SCP] +gbShowCollisionPolys + +[FED_SCR] +Show Car Road Grups + +[FED_SCZ] +Show Cull Zones + +[FED_SPR] +Show Ped Road Groups + +[FED_SUB] +СУБТИТРЫ + +[FED_TRA] +ШЛЕЙФ ОТ ДВИЖУЩИХСЯ ОБЪЕКТОВ + +[FED_WIS] +ШИРОКИЙ ЭКРАН + +[FEFD_WR] +Formatting Memory Card (PS2) in MEMORY CARD slot 1. Please do not remove the Memory Card (PS2), reset or switch off the console. + +[FEF_AU1] +Прибавьте громкости! + +[FEF_AU2] +Выберите радиостанцию и звуковой эффект + +[FEF_BR1] +Не знаете, что делать? + +[FEF_BR2] +Прочтите тексты последних заданий, отсортированных по дате. + +[FEF_CO1] +Вас не устраивает такой способ управления? + +[FEF_CO2] +Выберите наиболее удобное для вас управление + +[FEF_DI1] +Игра изменена! + +[FEF_DI2] +Настройка игры под ваш телевизор + +[FEF_INT] +ИНТЕРНЕТ + +[FEF_LA1] +О чем ты, черт побери, говоришь? + +[FEF_LA2] +Выбери такую манеру выражаться, какая тебя устроит. + +[FEF_LAN] +ЛОКАЛЬНАЯ СЕТЬ + +[FEF_SA1] +Борись за свое место в списке! + +[FEF_SA2] +Загрузка и сохранение вашей игры + +[FEF_ST1] +Кто тут у нас плохой? + +[FEF_ST2] +Сколько ты вызвал аварий + +[FEG_MAP] +КАРТА + +[FEG_PLY] +ИГРОКИ + +[FEG_PNG] +ПИНГ + +[FEG_SRV] +СЕРВЕР + +[FEG_TYP] +ТИП + +[FELD_WR] +Loading data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FELZ_FO] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + +[FEL_ENG] +АНГЛИЙСКИЙ + +[FEL_FRE] +ФРАНЦУЗСКИЙ + +[FEL_GER] +НЕМЕЦКИЙ + +[FEL_ITA] +ИТАЛЬЯНСКИЙ + +[FEL_SPA] +ИСПАНСКИЙ + +[FEM_CES] +Check Every 0kB4 Save + +[FEM_CLI] +Создание и загрузка иконок + +[FEM_CPD] +Create copy protected mag directory + +[FEM_CRD] +Create Root Dir + +[FEM_DBG] +ОТЛАДКА + +[FEM_FFF] +Fill First File with Guff + +[FEM_FRM] +ОГРАНИЧЕНИЕ КОЛИЧЕСТВА КАДРОВ + +[FEM_HST] +СОЗДАТЕЛЬ ИГРЫ + +[FEM_LOD] +ДАЛЬНОСТЬ ПРОРИСОВКИ + +[FEM_MA0] +Либерти Сити + +[FEM_MA1] +Кв. красных фонарей + +[FEM_MA2] +Чайнатаун + +[FEM_MA3] +Башня + +[FEM_MA4] +Канализация + +[FEM_MA5] +Промышленный парк + +[FEM_MA6] +Доки + +[FEM_MA7] +Стаунтон + +[FEM_MAP] +Выбор карты + +[FEM_MC2] +Memory Card Menu 2 + +[FEM_MCM] +Memory Card Menu + +[FEM_MM] +ГЛАВНОЕ МЕНЮ + +[FEM_MP] +ИГРА ПО СЕТИ + +[FEM_NO] +НЕТ + +[FEM_NON] +НЕТ + +[FEM_OFF] +ВЫКЛ + +[FEM_ON] +ВКЛ + +[FEM_OPT] +НАСТРОЙКИ + +[FEM_QT] +ВЫХОД + +[FEM_RES] +ВЕРНУТЬСЯ В ИГРУ + +[FEM_RMC] +Register MemCard One + +[FEM_SL1] +Ячейка 1 свободна + +[FEM_SL2] +Ячейка 2 свободна + +[FEM_SL3] +Ячейка 3 свободна + +[FEM_SL4] +Ячейка 4 свободна + +[FEM_SL5] +Ячейка 5 свободна + +[FEM_SL6] +Ячейка 6 свободна + +[FEM_SL7] +Ячейка 7 свободна + +[FEM_SL8] +Ячейка 8 свободна + +[FEM_SOG] +Save Only The Game + +[FEM_SP] +ОДИН ИГРОК + +[FEM_STG] +Запись игры + +[FEM_STS] +Save The Game under GTA3 name + +[FEM_TD] +Test Delete: + +[FEM_TFM] +Test Format MemCard One + +[FEM_TL] +Test Load: + +[FEM_TS] +Test Save: + +[FEM_TUM] +Test UnFormat MemCard One + +[FEM_VSC] +СИНХРОНИЗАЦИЯ КАДРОВ + +[FEM_YES] +ДА + +[FEN_CON] +Подключение + +[FEN_GAM] +Поиск игр + +[FEN_GNA] +Имя игры: + +[FEN_NAM] +Имя: + +[FEN_NCI] +НЕТ СВЯЗИ С ИНТЕРНЕТОМ + +[FEN_NET] +Сеть + +[FEN_PLA] +Число игроков: + +[FEN_PLC] +Цвет игрока + +[FEN_PLS] +Настройки игрока + +[FEN_STA] +НАЧАТЬ ИГРУ + +[FEN_TY0] +Сражение + +[FEN_TY1] +Партизанская война + +[FEN_TY2] +Командный бой + +[FEN_TY3] +Командная партизанская война + +[FEN_TY4] +Кража денег + +[FEN_TY5] +Захват флага + +[FEN_TY6] +Крысиные гонки + +[FEN_TY7] +Доминатор + +[FEN_TYP] +Тип игры + +[FEN_UKH] +Неизвестный сервер + +[FEN_UKM] +Карта не найдена + +[FEN_UKT] +Неизвестный тип игры + +[FEP_AUD] +ЗВУК + +[FEP_BRI] +СООБЩЕНИЯ + +[FEP_CON] +УПРАВЛЕНИЕ + +[FEP_DIS] +ЭКРАН + +[FEP_LAN] +ЯЗЫК + +[FEP_SAV] +ПРОДОЛЖИТЬ ИГРУ + +[FEP_STA] +СТАТИСТИКА + +[FEQ_SRE] +Вы точно решили выйти? Все что вы успели сделать после последнего сохранения будет потеряно. Выходим? + +[FEQ_SRW] +Вы точно решили выйти из игры? + +[FESTDCM] +Расстояние, пройденное на машине (м) + +[FESTDFM] +Расстояние, пройденное пешком (м) + +[FEST_BB] +Быстрые тачки - легкие деньги: + +[FEST_BD] +Лучшее время деактивации бомбы + +[FEST_CC] +Убито преступников во время работы полицейским + +[FEST_DC] +Расстояние, пройденное на машине (миль) + +[FEST_DF] +Расстояние, пройденное пешком (миль) + +[FEST_FE] +Потушено пожаров + +[FEST_GC] +Взорвано бандитских машин: + +[FEST_H0] +Пройдено контрольных точек + +[FEST_H1] +Драка с Дьяволами + +[FEST_H2] +Мордобой с мафией + +[FEST_H3] +Катастрофа в казино + +[FEST_H4] +Разборка с растаманами + +[FEST_HA] +Максимальный уровень заданий 'скорой' + +[FEST_LF] +Лучшее время полета на Додо + +[FEST_LS] +Спасено людей во время работы на 'скорой' + +[FEST_MP] +Заданий выполнено + +[FEST_OO] +из + +[FEST_R1] +'Пэтриот Ралли' (сек) + +[FEST_R2] +'Гонка в парке' (сек) + +[FEST_R3] +Догнал! за (сек) + +[FEST_RM] +'Гонка по гаражу' (сек) + +[FEST_RP] +Схваток завершено + +[FESZ_CA] +Отмена + +[FESZ_FF] +Format Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FESZ_FM] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. Would you like to format Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_FO] +Would you like to format the Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_L1] +Игра успешно сохранена! + +[FESZ_L2] +Игра сохранена в файле: + +[FESZ_LS] +Загрузка завершена. + +[FESZ_OK] +OK + +[FESZ_OW] +Overwriting data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FESZ_QD] +Вы точно решили удалить эту запись игры? + +[FESZ_QF] +Are you sure you wish to format the Memory Card (PS2) in MEMORY CARD slot 1? + +[FESZ_QL] +Все что вы успели сделать после последнего сохранения будет потеряно. Вы точно решили загрузить сохраненную игру? + +[FESZ_QO] +Вы решили записать игру на место предыдущей записи? + +[FESZ_QR] +Вы точно решили начать новую игру? Все что вы успели сделать после последнего сохранения будет потеряно. Начнем? + +[FESZ_QS] +ЗАПИСАТЬ ИГРУ ? + +[FESZ_QU] +Выход + +[FESZ_QZ] +Вы уверены, что хотите сохранить игру? + +[FESZ_SA] +Запись игры + +[FESZ_SR] +Save Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FESZ_TI] +SAVE Z1 + +[FESZ_UC] +ОТМЕНА + +[FESZ_WR] +Saving data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[FES_AFO] +This Memory Card (PS2) is already formatted. + +[FES_CAN] +Отмена + +[FES_CGA] +Доступные ячейки для сохранения: + +[FES_CSA] +Выберите одежду из списка: + +[FES_DAT] + ДАТА + +[FES_DEE] +Deleting Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FES_DGA] +УДАЛЕНИЕ ЗАПИСЕЙ + +[FES_GME] +Error Reading Memory Card (PS2) in MEMORY CARD slot 1 please check and try again. + +[FES_ISC] +ПОВРЕЖДЕН + +[FES_ISF] +ОТСУТСТВУЕТ + +[FES_LCG] +Продолжить игру с того места, где вы записались? + +[FES_LG] +ЗАГРУЗКА ИГРЫ + +[FES_LGA] +Загрузить игру + +[FES_LOE] +Load Failed! Check Memory Card (PS2) in MEMORY CARD slot 1 and please try again. + +[FES_LOF] +Загрузка не удалась. + +[FES_NGA] +Новая игра + +[FES_NOC] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[FES_NON] +ВАРИАНТЫ ОДЕЖДЫ ОТСУТСТВУЮТ + +[FES_SAG] +ИМЕЕТСЯ + +[FES_SCG] +Сохранить текущую игру? + +[FES_SET] +ПЕРЕОДЕТЬСЯ + +[FES_SG] +НАЧАТЬ НОВУЮ ИГРУ + +[FES_SKN] +НАЗВАНИЕ ОДЕЖДЫ + +[FES_SLO] +ФАЙЛ ЗАПИСИ + +[FES_SNG] +НАЧАТЬ НОВУЮ ИГРУ + +[FES_SSC] +Игра успешно сохранена. + +[FES_WAR] +Идет запись, подождите... + +[FET_AMS] +НАСТРОЙКА МЫШИ + +[FET_APL] + ОК? + +[FET_APP] +ЛЕВАЯ КНОПКА МЫШИ ИЛИ ВВОД ДЛЯ ПРИНЯТИЯ ИЗМЕНЕНИЙ + +[FET_AUD] +НАСТРОЙКА ЗВУКА + +[FET_BRE] +СООБЩЕНИЯ + +[FET_CAC] +ДЕЙСТВИЯ + +[FET_CCN] +УПРАВЛЕНИЕ: СТАНДАРТНОЕ + +[FET_CCR] +В МАШИНЕ + +[FET_CFT] +ПЕШКОМ + +[FET_CIG] +ЗАБОЙ - УБРАТЬ КЛАВИШИ, ЛЕВАЯ КНОПКА МЫШИ ИЛИ ВВОД - ЗАДАТЬ + +[FET_CME] +ТИП УПРАВЛЕНИЯ + +[FET_CON] +СОЕДИНЕНИЕ + +[FET_CTI] +СТАНДАРТНЫЙ ТИП УПРАВЛЕНИЯ + +[FET_CTL] +НАСТРОЙКА УПРАВЛЕНИЯ + +[FET_DAM] +ДИНАМИЧЕСКАЯ МОДЕЛЬ АКУСТИКИ + +[FET_DEF] +СТАНДАРТНЫЕ НАСТРОЙКИ + +[FET_DG] +УДАЛЕНИЕ ИГРЫ + +[FET_DIS] +НАСТРОЙКА ДИСПЛЕЯ + +[FET_DSN] +Обычная}одежда.bmp + +[FET_EIG] +ДЛЯ ЭТОГО ДЕЙСТВИЯ НЕЛЬЗЯ ЗАДАТЬ КНОПКУ + +[FET_FG] +НАЙТИ ИГРУ + +[FET_FIL] +Фильтр + +[FET_GT] +ТИП ИГРЫ + +[FET_HG] +СОЗДАТЬ ИГРУ + +[FET_HRD] +УСТАНОВЛЕНЫ СТАНДАРТНЫЕ НАСТРОЙКИ + +[FET_JG] +Подключиться + +[FET_LAN] +ВЫБОР ЯЗЫКА + +[FET_LG] +ЗАГРУЗКА ИГРЫ + +[FET_MAP] +ВЫБОР КАРТЫ + +[FET_MIG] +ВЫБОР ПАРАМЕТРА СТРЕЛКАМИ ВЛЕВО, ВПРАВО И КОЛЕСИКОМ МЫШИ + +[FET_MP] +ИГРА ПО СЕТИ + +[FET_MST] +РУЛИТЬ МЫШЬЮ + +[FET_MTI] +ЗАДАНИЕ ПАРАМЕТРОВ МЫШИ + +[FET_NG] +НОВАЯ ИГРА + +[FET_NON] +НЕТ ДОСТУПНЫХ ИГР + +[FET_OPT] +НАСТРОЙКИ + +[FET_PAU] +ПАУЗА В ИГРЕ + +[FET_PS] +ВЫБОР ОДЕЖДЫ + +[FET_PSU] +ВЫБОР ОДЕЖДЫ + +[FET_QG] +ВЫХОД + +[FET_RDK] +ЗАДАТЬ УПРАВЛЕНИЕ + +[FET_REF] +Частота обновления + +[FET_RIG] +ВЫБЕРИТЕ КНОПКУ ДЛЯ ЭТОГО ДЕЙСТВИЯ ИЛИ НАЖМИТЕ ESC ДЛЯ ОТМЕНЫ + +[FET_RSC] +НЕВОЗМОЖНО УСТАНОВИТЬ ЭТОТ ПАРАМЕТР + +[FET_RSO] +ВОССТАНОВЛЕНЫ СТАНДАРТНЫЕ НАСТРОЙКИ + +[FET_SAN] +НАЧАТЬ НОВУЮ ИГРУ + +[FET_SCN] +УПРАВЛЕНИЕ: ОБЫЧНОЕ + +[FET_SFG] +ПОИСК ИГР... + +[FET_SG] +СОХРАНЕНИЕ ИГРЫ + +[FET_SGA] +НАЧАТЬ ИГРУ + +[FET_SNG] +НАЧАТЬ НОВУЮ ИГРУ + +[FET_SP] +ОДИН ИГРОК + +[FET_SRT] +СОРТИРОВКА ИГР... + +[FET_STA] +СТАТИСТИКА + +[FET_STI] +ОБЫЧНЫЙ ТИП УПРАВЛЕНИЯ + +[FE_INIP] +Инициализация и загрузка меню... Подождите. + +[FIL_FLT] +ФИЛЬТР СПИСКА ИГР + +[FIL_MAP] +Карта: + +[FIL_PNG] +Пинг: + +[FIL_SPC] +Доступна игра одному? + +[FIL_SRV] +Создатель: + +[FIL_TYP] +Тип игры: + +[FIRETRK] +Пожарная + +[FIRE_M] +'ПОЖАРНЫЙ' + +[FIRE_WS] +Пожарник погиб + +[FIRST] +~g~первый + +[FLASHB] +Flashback FM + +[FLATBED] +Флетбед + +[FLFSGF] +Fill First File With Guff + +[FM1] +'ПРОГУЛКА' + +[FM1_1] +~g~Вернись назад в Стретч! + +[FM1_10] +~g~Ты проехал мимо Марии, возвращайся и посади ее в машину. + +[FM1_2] +~g~Залезай в Стретч! + +[FM1_3] +~r~Вернись за Марией, иначе Сальваторе с тобой рассчитается. + +[FM1_4] +~g~Ты бросил дочь дона! Возвращайся на склад и жди Марию! + +[FM1_5] +~g~Отвези Марию назад к Сальваторе! + +[FM1_6] +~g~Чико не будет ждать вечно! Отвези Марию на берег! + +[FM1_7] +~r~Мария мертва! Сальваторе это не понравится... + +[FM1_8] +~r~Ты замочил дилера Марии! + +[FM1_9] +~g~Там какая-то гулянка, высади Марию неподалеку. + +[FM1_A] +~w~Мне с приятелями нужно поговорить о деле, + +[FM1_AA] +~w~Я лучше пойду. Надеюсь, увидимся попозже. + +[FM1_B] +~w~Этим вечером тебе придется присматривать за моей девочкой. + +[FM1_C] +~w~ЭЙ МАРИЯ! ПОДНИМАЙ СВОЮ ЗАДНИЦУ! + +[FM1_D] +~w~Эту глупую девку постоянно приходится звать. + +[FM1_E] +~w~А вот и она, единственная и неповторимая королева красоты! + +[FM1_F] +~w~Чем ты там занималась? + +[FM1_G] +~w~Наверняка просаживала мои деньги. + +[FM1_H] +~w~Ну ты же не хочешь, чтобы я крутилась тут и мешала вашему разговору? + +[FM1_I] +~w~Заткнись и иди в машину. + +[FM1_J] +~w~Возьми мой лимузин, но верни его в целости и сохранности, понял? + +[FM1_K] +~w~И смотри чтобы она не влипла в историю. + +[FM1_L] +~w~Да, да, да! Я думаю, твой новый пес может обо мне позаботиться - + +[FM1_M] +~w~вон он какой здоровый. + +[FM1_N] +~w~Эй, Тузик, давай разживемся гостинцами у Чико! + +[FM1_O] +~w~Я думаю, он где-то на железнодорожной станции, у берега Чайнатауна. + +[FM1_P] +~g~А вот и Чико, давай рули к нему. + +[FM1_Q] +~w~Привет, Мария! Прелесть моя! + +[FM1_Q1] +~w~Хочешь поразвлечься? Ну немного... а? Может СПАНКа? + +[FM1_R] +~w~Привет, Чико. Нет, я как обычно. + +[FM1_S] +~w~Держи, золотце. + +[FM1_S1] +~w~Эй, может ты заглянешь ко мне на вечеринку на складе к востоку от пристаней Атлантик. + +[FM1_SS] +~r~РАЦИЯ: ~g~Четыре-пять всем подразделениям: Требуется помощь в облаве на наркоторговцев у пристаней Атлантик... + +[FM1_T] +~w~Спасибо Чико. До скорого. + +[FM1_TT] +~w~ЭТО ОБЛАВА! + +[FM1_U] +~w~Благодарствую, наслаждайтесь. Это хороший товар. + +[FM1_V] +~w~Давай, Тузик, скатаемся на его вечеринку! + +[FM1_W] +~w~Хорошо, Тузик, присматривай за тачкой и жди меня, а я пока пойду подвигаю попой. + +[FM1_X] +~w~Эй, Тузик, давай сматываться отсюда. Ихууу! + +[FM1_Y] +~w~Знаешь, я уже давно так не веселилась, и ты хорошо со мной обращался. С уважением и все такое. + +[FM2] +'СТРИЖКА ТРАВЫ' + +[FM21] +'БОМБА НА БАЗЕ: ЧАСТЬ I' + +[FM2_1] +~g~А вот и Кудрявый Боб! + +[FM2_10] +~r~Кудрявый слинял! + +[FM2_11] +~g~Встань у клуба Луиджи, Кудрявый Боб скоро выйдет. + +[FM2_12] +~r~Он улизнул от тебя! + +[FM2_14] +~r~Ты подошел слишком близко к Кудрявому и спугнул его! + +[FM2_15] +~g~Не приближайся к Кудрявому, а то он заподозрит неладное! + +[FM2_16] +ИСПУГ БОБА: + +[FM2_2] +~g~Кудрявый вышел из клуба, начинай следить! + +[FM2_5] +~g~Отвези его в гавань Портланда. + +[FM2_6] +~r~Кудрявый не сядет в разбитое такси! + +[FM2_7] +~r~Кудрявый испугался! Оне не пойдет на встречу! + +[FM2_8] +~g~Разделайся с Кудрявым Бобом! + +[FM2_9] +~r~Кудрявый Боб мертв! + +[FM2_A] +Колумбийский картель производит СПАНК где-то в городе, + +[FM2_B] +У нас завелась крыса! + +[FM2_C] +Телками или дурью он не торгует - значит, стучит. + +[FM2_F] +А вот и наш дружок - мистер Болтун. + +[FM2_G] +За тобой следили? Лучше, чтобы никто не знал про наш маленький секрет. + +[FM2_H] +Нет..нет, за мной нет хвоста. Дурь у тебя? + +[FM2_I] +Вот твой СПАНК, нытик, теперь выкладывай. + +[FM2_J] +Оставь нас одних, нам нужно поговорить. + +[FM2_K] +но мы не знаем где, а вот они, похоже, знают о каждом нашем шаге, еще до того как мы его сделаем. + +[FM2_L] +Есть один парень - Кудрявый Боб, он работает в баре у Луиджи. + +[FM2_M] +Он тратит больше бабок, чем зарабатывает. + +[FM2_N] +Домой он обычно ездит на такси. Выследи его, + +[FM2_O] +и, если он и есть та самая крыса... прикончи его. + +[FM2_P] +В общем, так: семья Леоне воюет на два фронта. + +[FM2_Q] +Они дерутся за сферы влияния с Триадой, но пока ни одна из сторон не уступает. + +[FM2_R] +А тем временем Джоуи Леоне пустил немного крови Форелли. + +[FM2_S] +Каждый день они теряют людей и свое влияние в городе. + +[FM2_T] +Сальваторе становится опасным параноиком. Он всюду видит одни заговоры. + +[FM2_U] +С такими преданными людьми, как ты, ему не о чем беспокоиться. + +[FM3] +'БОМБА НА БАЗЕ: ЧАСТЬ II' + +[FM3_4] +~g~Останови тачку и дай Лысому выйти! + +[FM3_7] +~r~Лысого замочили! + +[FM3_8] +~r~Охранники подняли тревогу! + +[FM3_8A] +~w~Здорово, друган! Сальваторе мне уже звонил. + +[FM3_8B] +~w~Для этой работенки потребуется много взрывчатки. + +[FM3_8C] +~w~Мне нужно $100,000, чтобы покрыть расходы, + +[FM3_8D] +~w~но ты же знаешь, что я отработаю каждый доллар. + +[FM3_8E] +~w~Окей, давай провернем это дельце! + +[FM3_8F] +~w~Я бы взорвал эту крошку, но пока еще не могу удержать оружие в руках. + +[FM3_8G] +~w~Вот, это ружьишко поможет тебе снести несколько голов! + +[FM3_8I] +~w~Займи удобную позицию, я пойду сразу, после твоего первого выстрела. + +[FM3_A] +~w~Мы должны убрать этих колумбийских ублюдков, + +[FM3_B] +~w~но эта война с Триадой сильно истощила наши ряды. + +[FM3_C] +~w~У Картеля очень много зелени, полученной с продажи этого СПАНКа. + +[FM3_CC] +~w~Браток, приходи, когда наскребешь бабок. + +[FM3_D] +~w~Если мы пойдем в открытую атаку, то они просто размажут нас по стенке. + +[FM3_E] +~w~Похоже, они производят СПАНК на том большом корабле, к которому тебя вывел Кудрявый. + +[FM3_F] +~w~Так что нам придется пораскинуть мозгами. Твоими мозгами. + +[FM3_G] +~w~Я, Сальваторе Леоне, лично прошу тебя сделать мне одолжение и уничтожить эту фабрику по производству СПАНКа. + +[FM3_H] +~w~Если ты провернешь для меня это дело - проси все что пожелаешь. + +[FM3_I] +~w~Иди к Лысому. Без его помощи тебе эту работу не сделать. + +[FM4] +'ПОСЛЕДНЯЯ ПРОСЬБА' + +[FM4_1] +Это Мария. Эта машина - ловушка! Давай встретимся южнее моста Каллахан. + +[FM4_2] +Послушай, Сальваторе думает что мы за его спиной крутим роман, + +[FM4_3] +поэтому он заключил с картелем сделку и заложил тебя. + +[FM4_4] +Я не могла этого допустить, ведь тебя бы прикончили, + +[FM4_4B] +и это была бы моя вина... потому что я сказала, что мы были вместе. + +[FM4_5] +Я сама не знаю, почему я это ему сказала. + +[FM4_6] +Похоже теперь за тобой будет охотиться мафия, да и за мной, видимо, тоже. + +[FM4_6B] +Я уже устала от убийств. От этих рек крови! + +[FM4_7] +Это моя хорошая знакомая, она старый друг... это Асука, она из тех, кому можно доверять. + +[FM4_8] +Пошли, хватит распинаться. + +[FM4_9] +Нам лучше убираться, пока здесь не появились наши старые друзья, которым уже нельзя доверять. + +[FM4_A] +~w~А, мой лучший чистильщик. + +[FM4_B] +~w~Я горжусь тобой, мальчик, ты выбил дерьмо из этих жирных свиней. + +[FM4_C] +~w~Но выполни еще одно маленькое задание, прежде чем мы отпразднуем победу. + +[FM4_D] +~w~Рядом с клубом Луиджи стоит одна машина. + +[FM4_E] +~w~Внутри все залито кровью. + +[FM4_F] +~w~Мы вправили одному парню мозги, но получилось не очень аккуратно. + +[FM4_H] +~w~Отгони машину в утилизатор, чтобы не смущать копов. + +[FORMEN] +Format Menu + +[FORMM1] +FormatMemCard 1 (teststuff) + +[FOURTH] +~g~четвертый + +[FRANGO] +~g~Сальваторе хочет, чтобы ты сперва помог Тони разобраться с Триадой! + +[FRANK] +ЗАДАНИЯ САЛЬВАТОРЕ + +[FRENCH] +Французский + +[FTUTOR] +Нажми кнопку ~h~~k~~TOGGLE_SUBMISSIONS~~w~, чтобы заняться работой пожарного, или отказаться от нее. + +[FTUTOR2] +Нажми кнопку ~h~~k~~TOGGLE_SUBMISSIONS~~w~, чтобы заняться работой пожарного, или отказаться от нее. + +[F_CANC] +~r~Задание пожарного прервано! + +[F_EXTIN] +ПОЖАРЫ: + +[F_FAIL1] +Задание не выполнено. + +[F_FAIL2] +~r~Ты опоздал! + +[F_PASS1] +Огонь потушен! + +[F_RANGE] +~g~Рация в машине не ловит сигнал, подъедь поближе к пожарной части! + +[F_START] +~g~Место возгорания машины: ~a~. Езжай и сбей огонь. + +[F_WASTE] +Женщин убито + +[GAMEOVR] +ИГРА ОКОНЧЕНА + +[GAMSET] +Настройки игры + +[GAM_FM] +Радио Game FM + +[GARAGE] +Поставь машину в гараж и выйди наружу. + +[GARAGE1] +~g~Вылазь из машины и выходи из гаража. + +[GA_1] +Ух, ты! Я не трогал ничего БОЛЕЕ горячего! + +[GA_10] +Отлично. Вот тебе ~1~ баксов. + +[GA_11] +У нас уже есть такая тачка. Больше нам уже не нужно! + +[GA_12] +Бомба активирована + +[GA_13] +Отличная работа! Пригонишь остальные - получишь вознаграждение. + +[GA_14] +Все тачки. КРУТО! Вот тебе за это. + +[GA_15] +Надеюсь, тебе понравится этот цвет. + +[GA_16] +Перекраска бесплатно. + +[GA_19] +Нам такая колымага не нужна. + +[GA_1A] +Возвращайся, когда будешь свободен... + +[GA_2] +Машина отремонтирована и перекрашена. Теперь копы тебя не узнают! + +[GA_20] +У нас этого хлама больше чем нужно. Прости, сделка не состоится. + +[GA_21] +Больше в этот гараж поставить машин нельзя. + +[GA_3] +Подарков больше не будет. Окраска стоит $1000! + +[GA_4] +Установка бомбы в машину стоит $1000 + +[GA_5] +Я уже поставил в бомбу в эту тачку. + +[GA_6] { re3 change } +Припаркуй тачку на место, нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~ и ДЕЛАЙ НОГИ! + +[GA_6B] { re3 change } +Припаркуй тачку на место, нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~ и ДЕЛАЙ НОГИ! + +[GA_7] { re3 change } +Включи бомбу, нажав на ~h~~k~~VEHICLE_FIREWEAPON~~w~. Бомба взорвется как только заведется мотор. + +[GA_7B] { re3 change } +Включи бомбу, нажав на ~h~~k~~VEHICLE_FIREWEAPON~~w~. Бомба взорвется как только заведется мотор. + +[GA_8] +Взорви бомбу с помощью детонатора. + +[GA_9] +Ты собрал ~1~ из 10 особых тачек + +[GERMAN] +German + +[GHOST] +Призрак + +[GMLOAD] +ПРОДОЛЖИТЬ ИГРУ + +[GMREST] +Перезапуск игры + +[GMSAVE] +СОХРАНЕНИЕ ИГРЫ + +[GMSTOR] +Game Store + +[GMSVLQ] +GAME SAVE-LOAD-QUIT + +[GNG_WST] +Убито членов банд + +[GOAWAY] +~g~Ты уже на задании! + +[GOBACK] +GoBack + +[GORLEV] +Уровень крови + +[GREN_1] +Чем дольше ты держишь ~h~~k~~PED_FIREWEAPON~~w~, тем дальше ты бросишь гранату. + +[GREN_2] +Чем дольше ты держишь ~h~~k~~PED_FIREWEAPON~~w~, тем дальше ты бросишь гранату. + +[GREN_3] +Чем дольше ты держишь ~h~~k~~PED_FIREWEAPON~~w~, тем дальше ты бросишь гранату. + +[GTAB_A] +Эй, давайте уберите это отсюда. Черт его знает, что это такое, + +[GTAB_B] +но раз он так хотел это заполучить, то это, наверное, что-то ценное. + +[GTAB_C] +Что за черт! + +[GTAB_D] +ТЫ! + +[GTAB_E] +Успокойся, амиго! Де нада! Де нада! + +[GTAB_F] +Я порежу тебя и брошу в канаву истекать кровью! + +[GTAB_G] +Не стреляй, амиго. Без базара. Мы все друзья. Вот, забирай. + +[GTAB_H] +Не будь таким засранцем! + +[GTAB_I] +У нас нет выбора, крошка! + +[GTAB_J] +Выбор есть всегда, ты чертов ублюдок! + +[GTAB_K] +Прости, это все эта бешеная сучка, это все она...пор фавор? + +[GTAB_L] +Итак, этой сучки больше нет. + +[GTAB_M] +Но ты оказал мне услугу, + +[GTAB_N] +ты не единственный, у кого есть счеты с Картелем, + +[GTAB_O] +эта мразь убила моего брата! + +[GTAB_P] +Я никогда не убивал якудза! + +[GTAB_Q] +ЛЖЕЦ. Мы все видели убийцу Картеля. + +[GTAB_R] +Мы выследим и убьем всех вас, колумбийских собак! + +[GTAB_S] +Я поиграю немного с твоим другом, чтобы получить информацию и доставить себе удовольствие. + +[GTAB_T] +Я жду тебя позже, я уверена, мне понадобятся твои услуги. + +[GTAB_U] +Пожалуйста, амиго, не оставляй меня с ней, она ненормальная! Друг? Эй, ДРУУУГ!!!... Аииииаааахххх! + +[GUN_1A] +Для смены оружия используй ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ и ~h~~k~~PED_CYCLE_WEAPON_LEFT~. + +[GUN_2A] +Держи ~h~~k~~PED_LOCK_TARGET~~w~, чтобы ~h~прицелиться~w~, а затем нажимай ~h~~k~~PED_FIREWEAPON~~w~, чтобы выстрелить! Потренируйся на мишенях... + +[GUN_2C] +Держи ~h~~k~~PED_LOCK_TARGET~~w~, чтобы ~h~прицелиться~w~, а затем нажимай ~h~~k~~PED_FIREWEAPON~~w~, чтобы выстрелить! Потренируйся на мишенях... + +[GUN_2D] +Держи ~h~~k~~PED_LOCK_TARGET~~w~ чтобы ~h~прицелиться~w~, а затем нажимай ~h~~k~~PED_FIREWEAPON~~w~ чтобы выстрелить! Потренируйся на мишенях... + +[GUN_3A] +Чтобы сменить цель, удерживая ~h~~k~~PED_LOCK_TARGET~~w~, нажимай ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ или ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~. + +[GUN_3B] +Чтобы сменить цель, удерживая ~h~~k~~PED_LOCK_TARGET~~w~, нажимай ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ или ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~. + +[GUN_4A] +Когда ты держишь ~h~~k~~PED_LOCK_TARGET~~w~, то можешь ходить или бегать, не выпуская цель из перекрестия. + +[GUN_4B] +Когда ты держишь ~h~~k~~PED_LOCK_TARGET~~w~, то можешь ходить или бегать, не выпуская цель из перекрестия. + +[GUN_5] +Ты можешь попрактиковаться на этих мишенях в прицеливании и стрельбе. Как только закончишь - возвращайся к заданию. + +[HARWOOD] +Харвуд + +[HEAD] +Head Radio + +[HEALTH] +ЦЕЛОСТНОСТЬ: + +[HEALTH1] +Проваливай! Ты полностью здоров. + +[HEALTH2] +Лечение не бесплатное. + +[HEALTH3] +Сейчас я тебя подлатаю. + +[HEALTH4] +Это будет стоить тебе $250. + +[HEAL_A] +Твое ~h~здоровье~w~ отображается оранжевыми цифрами в верхнем правом углу экрана. + +[HEAL_B] +Когда ты ~h~вырубаешься,~w~ тебя отвозят в ближайший госпиталь + +[HEAL_C] +При этом ты теряешь все свое оружие и доктора берут с тебя немного денег за лечение. + +[HEAL_E] +Во время игры ты найдешь способы, как подлечиться или сберечь свое здоровье. + +[HED_EX] +Голов расколото + +[HELI] +Вертолет + +[HELP1] +Остановите машину в центре голубого круга. + +[HELP10] +Эти значки показывают, насколько сильно стремление копов арестовать тебя. + +[HELP11] +Чем больше этих значков, тем более усердно тебя преследует полиция. + +[HELP12] +Войди в голубой круг, чтобы получить новое задание. + +[HELP13] +Иногда тебе придется ехать по дорогам, которые не показаны на радаре. + +[HELP14] +Чтобы подобрать оружие, тебе нужно к нему подойти. Сидя в машине, ты оружие не возьмешь. + +[HELP15] +Когда ты без машины, то, нажав ~h~~k~~PED_LOOKBEHIND~~w~, можешь ~h~оглянуться назад~w~. + +[HELP2_A] +Чтобы ~h~разогнаться~w~, держи ~h~кнопку /~w~ во время бега. + +[HELP3] +Мчаться ты можешь лишь до тех пор, пока не устанешь. + +[HELP4_A] +Чтобы ~h~поехать вперед~w~, держи кнопку ~h~ ~k~~VEHICLE_ACCELERATE~~w~. + +[HELP4_D] +Push the~h~ right analog stick~w~ up to ~h~accelerate. + +[HELP5_A] +Держи кнопку~h~ ~k~~VEHICLE_BRAKE~~w~, чтобы ~h~затормозить~w~, или ~h~поехать назад~w~ после того, как машина остановится. + +[HELP5_D] +Pull the ~h~right analog stick~w~ back to ~h~brake~w~, or to ~h~reverse~w~ if the vehicle has stopped. + +[HELP6_A] +Держи кнопку~h~ ~k~~VEHICLE_HANDBRAKE~~w~, чтобы воспользоваться ~h~ручным тормозом. + +[HELP6_C] +Держи кнопку~h~ ~k~~VEHICLE_HANDBRAKE~~w~, чтобы воспользоваться ~h~ручным тормозом. + +[HELP6_D] +Держи кнопку~h~ ~k~~VEHICLE_HANDBRAKE~~w~, чтобы воспользоваться ~h~ручным тормозом. + +[HELP7_A] +Нажми и удерживай~h~ ~k~~PED_LOCK_TARGET~~w~ кнопку, чтобы ~h~прицелиться~w~ из снайперской винтовки. + +[HELP7_D] +Нажми и удерживай~h~ ~k~~PED_LOCK_TARGET~~w~ кнопку, чтобы ~h~прицелиться~w~ из снайперской винтовки. + +[HELP8_A] +Нажми ~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, чтобы ~h~увеличить ~w~ изображение или ~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, чтобы ~h~уменьшить~w~ его при прицеливании из снайперской винтовки. + +[HELP8_B] +Нажми ~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, чтобы ~h~увеличить ~w~ изображение или ~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, чтобы ~h~уменьшить~w~ его при прицеливании из снайперской винтовки. + +[HELP9_A] +Нажми ~h~ ~k~~PED_FIREWEAPON~~w~, чтобы ~h~выстрелить~w~ из снайперской винтовки. + +[HELP9_B] +Нажми ~h~ ~k~~PED_FIREWEAPON~~w~, чтобы ~h~выстрелить~w~ из снайперской винтовки. + +[HELP9_C] +Нажми ~h~ ~k~~PED_FIREWEAPON~~w~, чтобы ~h~выстрелить~w~ из снайперской винтовки. + +[HEL_DST] +Сбито вертолетов + +[HEY] +~g~Не ходи в одиночку, иди с компанией! + +[HEY2] +~g~Не разделяйтесь, идите вместе! + +[HEY3] +~g~Ты потерял своего напарника, вернись к Лысому! + +[HEY4] +~g~Потеряешь Мисти - к Луиджи лучше не возвращайся! Иди и найди ее! + +[HEY5] +~g~Для полного кайфа не хватает еще одной телки. Сгоняй за ней! + +[HEY6] +~g~Твоя честь зависит от якудза Канбу. Ты должен защищать его! + +[HEY7] +~g~Возможно стоит получше вооружиться. Иди и забери своего напарника! + +[HEY8] +~g~Защита - это значит 'защищать Старого Азиата'! + +[HEY9] +~g~Хочешь совет? Иди, встреться с напарником! + +[HJSTAT] +Дальность: ~1~.~1~м Высота: ~1~.~1~м Переворотов: ~1~ Поворот на: ~1~_ + +[HJSTATF] +Дальность: ~1~ft Высота: ~1~ft Переворотов: ~1~ Поворот на: ~1~_ + +[HJSTATW] +Дальность: ~1~.~1~м Высота: ~1~.~1~м Переворотов: ~1~ Поворот на: ~1~_ И отличное приземление! + +[HJSTAWF] +Дальность: ~1~ft Высота: ~1~ft Переворотов: ~1~ Поворот на: ~1~_ И отличное приземление! + +[HJ_DIS] +ПРИЗ ЗА ДВОЙНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_IS] +ПРИЗ ЗА БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_PDIS] +ПРИЗ ЗА ИДЕАЛЬНЫЙ ДВОЙНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_PIS] +ПРИЗ ЗА ИДЕАЛЬНЫЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_PQIS] +ПРИЗ ЗА ИДЕАЛЬНЫЙ ЧЕТВЕРНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_PTIS] +ПРИЗ ЗА ИДЕАЛЬНЫЙ ТРОЙНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_QIS] +ПРИЗ ЗА ЧЕТВЕРНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HJ_TIS] +ПРИЗ ЗА ТРОЙНОЙ БЕЗУМНЫЙ ТРЮК: $~1~ + +[HM1_1] +~g~Прикончи 20 Пурпурных Девяток за 2 минуты 30 секунд. + +[HM1_2] +~g~Возьми тачку, но не забывай, что из машины можно стрелять лишь из Узи! + +[HM1_3] +~g~'Девятки' любят шататься в Садах Вичита. + +[HM1_A] +Йо! Это ДиАйс из Красных Валетов! + +[HM1_B] +Тут кое кто решил со мной поиграть. + +[HM1_C] +У этих молодых панков, что заполонили весь город, головы забиты лишь СПАНком и оружием. + +[HM1_D] +'Девятка' - это их знак, они придумали себе пурпурный флаг и готовы трясти им целыми днями... + +[HM1_E] +Я хочу, чтобы ты показал этим малолетним ублюдкам, как работают профессионалы. + +[HM1_F] +Будь поосторожней. На улицах могут оказаться Валеты, которые решат, что ты и их решил заодно мочкануть! + +[HM1_G] +эти засранцы начинают теснить 'Валетов'. + +[HM1_H] +Сделай так, чтобы этих Девяток здесь не было! + +[HM2_1] { re3 change } +Используй радиоуправляемые машинки, чтобы подорвать броневики. Взрыв бомбы - ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[HM2_1A] { re3 change } +Используй радиоуправляемые машинки, чтобы подорвать броневики. Взрыв бомбы - ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[HM2_2] +~r~Ты так и не смог подорвать все броневики! + +[HM2_3] +Если машинка стукнется о колесо, то она взорвется! + +[HM2_4] +Если машинка окажется вне действия радиоуправления, то она взорвется! + +[HM2_5] +~r~Вне зоны приема сигнала! + +[HM2_6] +~g~Броневик уничтожен! + +[HM2_A] +Эти Девятки достали меня. + +[HM2_B] +Уроды раздобыли где-то броневики и развозят на них СПАНК... + +[HM2_C] +и без страха впаривают его желающим. + +[HM2_D] +Выше по улице припаркована тачка. + +[HM2_E] +В ней для тебя я кое-что припас, чтобы ты смог проучить этих тупиц... + +[HM2_F] +и раздолбать их бронированные игрушки. + +[HM3_1] +~g~Двигай к гаражу, но смотри, если сильно помнешь тачку - она взорвется! + +[HM3_2] +~g~Забирай тачку, теперь она в полном порядке! + +[HM3_3] +~g~Отремонтируй тачку! + +[HM3_A] +Какая-то сволочь установила мне бомбу в тачку. + +[HM3_B] +Если я потеряю колеса, то лишусь своей репутации в районе. + +[HM3_C] +Давай отгони мою колымагу в гараж на Сент-Марк, ты понял, йо. + +[HM3_D] +Как снять бомбу - это уже пусть они сами думают. + +[HM3_E] +Часы уже тикают, давай, гони. + +[HM3_F] +Но учти, одного столкновения достаточно, чтобы взлететь на воздух. + +[HM3_G] +Ну же, вперед! + +[HM4_1] +~g~Отправляйся к месту крушения самолета, тебе нужно собрать не менее 30 слитков. + +[HM4_2] +~g~Запомни, по мере заполнения тачка будет тяжелеть и хуже слушаться руля, съезди в гараж и вывали там груз. + +[HM4_A] +Йо, рейсовый самолет, перевозящий федеральные деньги разбился в аэропорту Фрэнсис. + +[HM4_B] +Слитки платины валяются по всей взлетной полосе. + +[HM4_C] +Бери тачку и сопри, сколько сможешь. + +[HM4_D] +~g~Возьми тачку! + +[HM4_E] +TEXT NO LONGER REQUIRED + +[HM4_F] +Ты можешь возить слитки в один из моих гаражей. + +[HM4_G] +Эти слитки чертовски тяжелые и будут замедлять движение. + +[HM4_H] +Так что не забывай почаще сваливать груз. + +[HM5_1] +Йо, Айс сказал ты придешь. Правила просты. Только биты. Никаких пушек, никаких машин. + +[HM5_3] +~r~Тебе же сказали, что можно пускать в ход лишь биту! + +[HM5_4] +~r~Твой напарник мертв! + +[HM5_5] +Эта драка для поднятия авторитета, ты въехал? + +[HM5_6] +Пошли, расколем несколько черепушек... + +[HM5_A] +Эти Девятки уже в полном дерьме... + +[HM5_B] +но им не терпится поквитаться. + +[HM5_C] +Они согласились прийти на разборку. + +[HM5_D] +Их банда против нас двоих, или точнее... + +[HM5_E] +вас двоих, + +[HM5_F] +я с тобой идти не могу... + +[HM5_G] +мне еще три месяца нельзя светиться, у меня условное, + +[HM5_H] +ты уловил, о чем я? + +[HM5_I] +Мой младшенький встретит тебя, + +[HM5_J] +он и покажет, где намечается устроить разборку, успехов, сынок. + +[HM_1] +'РАБОТА ДЛЯ УЗИ' + +[HM_2] +'ИГРУШЕЧНАЯ ВОЙНА' + +[HM_3] +'ТАЧКА С БОМБОЙ' + +[HM_4] +'МАННА НЕБЕСНАЯ' + +[HM_5] +'РАЗБОРКА' + +[HOOD] +ЗАДАНИЯ КАПЮШОНОВ + +[HOOD1_A] +Сними трубку телефона стоящего в Садах Вичита, и мы обсудим одно дело. + +[HOODGO] +~g~Сейчас у Капюшонов нет заданий! + +[HOODSCR] +Рампо XL Капюшонов + +[HORN] +~g~Посигналь. + +[HORN1] +Press the ~h~L3 button ~w~to activate the ~h~horn. + +[HORN2] +Press the ~h~L1 button ~w~to activate the ~h~horn + +[HORN3] +Press the ~h~R1 button ~w~to activate the ~h~horn + +[HOSPI_2] +Рокфорд + +[IDAHO] +Айдахо + +[IMPEXPP] +Автосалон в Портланде. Есть заказы на различные машины. Подробности - на нашей доске объявлений. + +[IMPORT1] +Выйди наружу и жди, когда тебе подадут тачку. + +[IND_ZON] +Портланд + +[INFERNS] +Инфернус + +[INSTUN] +Обычный безумный трюк + +[IN_BOAT] +~g~Без катера тебе это задание не выполнить! + +[IN_ROW] +Приз за ~1~ ПОДРЯД! $~1~ + +[IN_VEH] +~g~Эй! Ты куда? Давай садись за руль! + +[IN_VEH2] +~g~Без машины тебе это задание не выполнить! + +[ITALIA] +Italian + +[JAILB_A] +Утром полиция и служба спасения напряженно работали над устранением + +[JAILB_B] +чрезвычайной ситуации, вызванной нападением на полицейский конвой. + +[JAILB_C] +У нас пока нет никаких сведений ни о том, кого сегодня перевозили, + +[JAILB_D] +ни о том, какая преступная группировка ответственна за это дерзкое нападение. + +[JAILB_E] +Как обычно, ранним утром полицейский конвой покинул участок, + +[JAILB_F] +чтобы перевезти заключенных в городскую тюрьму. + +[JAILB_G] +Нападение произошло на мосту Каллахан. + +[JAILB_H] +Большинство свидетелей преступления погибло при взрыве, который сильно повредил мост. + +[JAILB_I] +Полиция полагает, что при взрыве также могли погибнуть + +[JAILB_J] +и некоторые заключенные. + +[JAILB_K] +Выяснение личностей сбежавших преступников осложнено + +[JAILB_L] +из-за атаки хакеров, испортивших базу данных полиции. + +[JAILB_M] +Мэр ОТДонован объяснил, что полиция + +[JAILB_N] +* + +[JAILB_O] +Из-за этой катастрофы район Портланда оказался изолирован от города, + +[JAILB_P] +так как прокладка туннеля Портер все еще не завершена. + +[JAILB_Q] +Ну же, открывайте! + +[JAILB_R] +А, мистер членоголовый! + +[JAILB_S] +Я бы мог спокойно тебя пристрелить. + +[JAILB_T] +Вы еще об этом пожалеете. + +[JAILB_U] +Эй, вы, давайте, смывайтесь. + +[JAILB_V] +Либерти Сити был потрясен сегодняшним преступлением. + +[JAILB_W] +Полиция считает, что это нападение - несомненно, дело рук профессионалов. + +[JAILB_X] +По предварительным данным + +[JAN] +Янв + +[JM1] +'ПОСЛЕДНИЙ УЖИН ГУБАСТОГО' + +[JM1_1] +~g~Отгони машину Форелли в гараж к Лысому, он на севере отсюда за 'Автомобилями в кредит'. + +[JM1_2] +~g~Припаркуй тачку на стоянке у бистро Марко. + +[JM1_3] +~g~Включи бомбу и быстрее сваливай! + +[JM1_4] +~g~Ты помял тачку! Ее нужно отремонтировать! + +[JM1_5] +~g~Ты не включил бомбу! + +[JM1_6] +~g~Поставь машину так, как она стояла раньше. + +[JM1_7] +~g~Закрой дверцу машины, а то он заподозрит неладное! + +[JM1_8A] +~y~Эй, это мой главный работник! + +[JM1_8B] +~y~Здесь все автоматизировано. Просто заезжай внутрь и жди, пока установят бомбу. + +[JM1_8C] +~y~Вот, первый раз даром, но потом плати бабки. + +[JM1_A] +Эй, мне скучно, может, потрахаемся? + +[JM1_B] +Погоди, дорогуша, мне тут нужно одно дельце провернуть. + +[JM1_C] +Для тебя, друг, есть одна работенка. + +[JM1_D] +Братья Форелли забыли мне вернуть старый должок. + +[JM1_E] +Надо бы преподать им хороший урок, чтоб все знали. + +[JM1_F] +Губастый Форелли сейчас набивает пузо в бистро на Сент-Марк, + +[JM1_G] +отгони его машину в гараж Лысого, тот что в Харвуде. + +[JM1_H] +Ты же знаком с Лысым? + +[JM1_I] +Как только он установит бомбу, верни машину туда, где взял. + +[JM1_J] +Ну а потом тебе останется только наблюдать. + +[JM1_K] +Но давай живее, толстяк не будет жрать вечно. + +[JM2] +'ПРОЩАЙ, ЧАНКИ ЛИ ЧОНГ' + +[JM2_A] +Чанки Ли Чонг толкает СПАНК новой банде из Колумбии... или Колорадо... или как там... + +[JM2_B] +Короче, я не знаю точно, да это и не важно. + +[JM2_C] +У него киоск с лапшой в центре Чайнатауна. + +[JM2_D] +Лучше бы этот крысеныш лишь свою жратву толкал. + +[JM2_E] +Я хочу, чтобы ты с ним разобрался! + +[JM2_F] +Если тебе нужен ствол, зайди во двор оружейного магазина, который рядом с метро. + +[JM2_G] +Знаешь, где этот магазинчик? Возьмешь там пистолет. + +[JM2_H] +Да, не забывай, что Чайнатаун - территория Триады, будь там поосторожней. + +[JM3] +'ОГРАБЛЕНИЕ ФУРГОНА' + +[JM3_1] +~g~Отгони фургон в гараж. + +[JM3_2] +~g~Тарань фургон до тех пор, пока не разобьешь его на 70%. + +[JM3_A] +Мы решили грабануть инкассаторский фургон. + +[JM3_B] +Он каждый день выезжает из Чайнатауна. + +[JM3_C] +Пули его броню не возьмут, так что тебе придется таранить его своей тачкой + +[JM3_D] +Хорошенько его помни, и сраный охранник сам его бросит. + +[JM3_E] +Потом отгони его на склад в доках, а мои парни сделают все остальное. + +[JM3_F] +Давай за работу. Фургон не будет вечно кататься по городу. + +[JM4] +'ШОФЕР СИПРИАНИ' + +[JM4_10] +Окей, парень. Сначала мы заглянем в прачечную в Чайнатауне, у меня там есть кое-какие дела. + +[JM4_11] +Эти прачки не желают платить крыше. + +[JM4_12] +И поаккуратней веди, Джоуи только что починил эту развалюху. + +[JM4_13] +Не превращай ее снова в груду металла, окей? + +[JM4_2] +Подожди здесь! Не глуши мотор, мало ли что может случиться. + +[JM4_3] +Это засада Триады! Парень, сваливаем отсюда! + +[JM4_4] +Триада думает, что может безнаказанно перейти мне дорогу, Триада, МНЕ! + +[JM4_5] +Зайди ко мне попозже, зададим им работенку - отстирывать с одежды собственную кровь! + +[JM4_6] +Эй, аккуратней! Я же тебе говорил. + +[JM4_7] +~g~Отвези Тони в мамочкин ресторан. + +[JM4_8] +~r~Тони загасили! + +[JM4_A] +Да, я знаю, Тони, я все отрегулировал. Она прямо мурлычет, послушай сам. + +[JM4_B] +О! А вот и тот парень, о котором я тебе говорил! + +[JM4_C] +Познакомься. Этот парень не итальянец, и не механик, но с работой справляется. + +[JM4_D] +Это Папс Капо, Тони Сиприани. + +[JM4_E] +Хай, я Тони Сиприани. + +[JM4_F] +Отвези его в мамочкин ресторан на Сент-Марк, хорошо? + +[JM4_G] +И еще, зайди ко мне попозже, я планирую одно дельце и мне понадобится хороший водила. + +[JM5] +'КАТАФАЛК СО СКУНСОМ' + +[JM5_1] +~g~Отвези его в утилизатор! + +[JM5_2] +~g~Это братья Форелли! + +[JM5_A] +Прекрасно! Просто великолепно. + +[JM5_B] +Отлично, с тобой-то я и хотел поговорить! + +[JM5_C] +В-общем, у кафе рядом с мысом Каллахан стоит катафалк со жмуриком. + +[JM5_D] +Один из братьев Форелли решил, что он слишком умный, ну и получил за это по заслугам. + +[JM5_E] +Отвези его тело к утилизатору в Харвуд, ладно? + +[JM6] +'БЕГСТВО' + +[JM6_1] +Поехали к банку по главной улице. + +[JM6_2] +Не глуши мотор, мы по-быстрому. Туда и обратно. + +[JM6_3] +Быстрее, сваливаем! + +[JM6_4] +Стряхни копов с хвоста и гони в укрытие! + +[JM6_5] +~g~Понадобится быстрая тачка, идиот! + +[JM6_6] +~g~Иди, найди менее заметную тачку! + +[JM6_7] +~g~Чтобы ограбить банк, тебе нужны все трое! + +[JM6_8] +~r~Ты потерял всех налетчиков! + +[JM6_A] +Классная будет тачка, да? + +[JM6_B] +Короче слушай. Достань тачку получше и езжай к убежищу на Сент-Марк, где подберешь моих друзей. + +[JM6_C] +Они решили взять банк, но им нужен водила. + +[JM6_D] +Я замолвил за тебя словечко, так что не напортачь. + +[JM6_E] +Отвези их к банку до пяти часов, и ни минутой позже. + +[JOEY] +ЗАДАНИЯ ЗОУИ + +[JOEYGO] +~g~Джоуи развлекается в городе вместе с Мисти. Приходи позже! + +[JUL] +Июл + +[JUN] +Июн + +[KABOOM] +БАБАХ! + +[KEMUGO] +~g~Мария и Кемури сейчас заняты. Загляни попозже! + +[KENJGO] +~g~Кенжи сейчас на собрании Якудзы. Заходи как-нибудь потом! + +[KENJI] +ЗАДАНИЯ КЕНДЖИ + +[KENSGO] +~g~Кенжи занят! Зайди попозже! + +[KGS_EXP] +Использовано взрывчатки (Кг.) + +[KILLS] +УБИТО: + +[KM1] +'ОСВОБОЖДЕНИЕ КАНБУ' + +[KM1_1] +~g~Укради полицейскую машину! + +[KM1_10] +~r~Якудза Канбу погиб - как и твоя честь! + +[KM1_11] +~r~Ты поджарил сам себя! + +[KM1_12] +~g~Отвези его к Доджо, но сперва разберись с полицейскими! + +[KM1_13] +Поставь машину в гараж! + +[KM1_2] +~g~Начини машину взрывчаткой! + +[KM1_3] +~g~Теперь вези его к Доджо Якудзы. + +[KM1_4] +~g~Для этого задания тебе понадобится полицейская машина! + +[KM1_5] +~g~Отлично, теперь вали к полицейскому участку. + +[KM1_6] +~g~Установи в машину бомбу! + +[KM1_7] +~g~Проезд только для полицейских! + +[KM1_8A] { re3 change } +Чтобы ~h~подорвать бомбу~w~, нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~, но не забудь отойти подальше от машины. + +[KM1_8D] { re3 change } +Чтобы ~h~подорвать бомбу~w~, нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~, но не забудь отойти подальше от машины. + +[KM1_9] +~r~Установленной в машине бомбой ты должен был взорвать стену! + +[KM1_A] +Моя сестра хорошо о тебе отзывается, + +[KM1_B] +Возможно, ты сможешь урегулировать одну ситуацию, которая ставит меня в неловкое положение. + +[KM1_C] +Якудза Канбу сейчас в участке и ждет отправки в суд. + +[KM1_D] +Мы благодарны тебе за такой самоотверженный поступок. Если тебе вдруг понадобится помощь, то Доджо даст тебе двух своих лучших людей. + +[KM1_E] +но я, пока что, не видел от вас, гайдзинов, ничего, кроме разочарований. + +[KM1_F] +Разумеется, в случае провала мы опозоримся. + +[KM1_G] +Он очень ценный член семьи. + +[KM1_H] +Вызволи его из заключения и отвези к Доджо на Бедфорд Поинт. + +[KM2] +'ВЕЛИКИЙ АВТОВОР' + +[KM2_1] +~g~Отремонтируй тачку, она должна сверкать. + +[KM2_2] +~g~Тачка пригнана. + +[KM2_3] +~g~Запомни, ~r~тачка~g~ должна быть в идеальном состоянии, иначе ее не примут в ~p~гараже~g~. + +[KM2_A] +Ты даже не представляешь, сколько в нашей работе внимания приходится уделять этикету. + +[KM2_B] +К моему великому стыду, один человек оказал мне услугу, а я так и не смог отплатить ему за его доброту. + +[KM2_C] +Его слабость - машины. Он просил, чтобы мы приобрели для его коллекции несколько редких экземпляров. + +[KM2_D] +Эти автомобили мы преподнесем ему как подарок, в счет погашения моего долга. + +[KM2_E] +Ты должен раздобыть эти машины и доставить в гараж за стоянкой в Ньюпорте. + +[KM2_F] +Для меня это дело чести. + +[KM3] +'ДОГОВОР' + +[KM3_1] +~g~Картель ожидает прибытия ямайцев, иди и укради тачку Ярди! Ты найдешь ее к северу отсюда, в Ньюпорте. + +[KM3_10] +~r~Напарник убит! + +[KM3_11] +~g~Картель атакован и дипломат забрать не удастся. + +[KM3_12] +~g~Убей всех колумбийцев, взорви их тачки и забери дипломат. + +[KM3_13] +~g~Отвези дипломат назад в казино. + +[KM3_14] +~r~Тебя опознали, ты напортачил! + +[KM3_2] +~g~Езжай и подбери своего напарника. + +[KM3_3] +~g~Они встречаются на автостоянке больницы в Рокфорде! + +[KM3_4] +~r~Они смылись! + +[KM3_5] +~g~Чтобы дела пошли - посигналь. + +[KM3_6] +~g~Убей их, убей их всех! + +[KM3_7] +Это подстава Якудзы! + +[KM3_8] +~g~Чтобы выполнить эту работу, тебе нужна тачка Ярди! + +[KM3_9] +~r~Один из Колумбийцев мертв, дело не выгорит. + +[KM3_A] +Дурак подставляет опасности спину, а умный смотрит ей в лицо. + +[KM3_B] +Колумбийский Картель проигнорировал наши многочисленные требования убраться из нашей сферы влияния. + +[KM3_C] +Сейчас они договариваются о сотрудничестве с Ямайцами, чтобы сообща выступить против нас. + +[KM3_D] +Сейчас в городе они ведут последнюю стадию переговоров. + +[KM3_E] +Это дело чести, чтобы ты их там всех порешил. + +[KM3_F] +Возьми моего человека, укради машину Ярди, иди и окажи уважение Колумбийцам. + +[KM4] +'ШИМА' + +[KM4_1] +Мне нечем платить, но я не стал бы платить, даже если бы мог! + +[KM4_10] +Да какие ВЫ к черту якудза...? + +[KM4_11] +~g~Верни деньги назад в казино! + +[KM4_2] +От вас никакого толка. + +[KM4_3] +Я не за этим вам плачу. Если бы мне нужна была такая защита, я обратился бы к этим сраным копам. + +[KM4_4] +~g~Разберись с этой бандой и верни ~b~дань~g~, принадлежащую Якудзе! + +[KM4_5] +Дональд Лав желает принять тебя в своем чайном саду, и обсудить кое-какие дела. + +[KM4_6] +Вот деньги, все как договаривались! + +[KM4_7] +~r~Хозяин магазина сдох! + +[KM4_8] +~g~Ты взял чемоданчик! + +[KM4_9] +Какие-то юные бандиты обчистили это место! Они забрали все что было! + +[KM4_A] +Чтобы быть действительно сильным, важно никогда не проявлять слабость. + +[KM4_B] +Дела идут довольно успешно, сегодня как раз нужно собрать дань. + +[KM4_C] +Иди и немедленно забери деньги, чтобы мы могли положить их на счет в казино. + +[KM5] +'РАЗЪЯСНЕНИЯ' + +[KM5_1] +~g~ПРОДАВЕЦ УБИТ! + +[KM5_2] +~g~Ярди слинял. + +[KM5_3] +~r~Ты не успел убить даже ~1~ Ярди. + +[KM5_4] +~g~Поздравляем, ты прикончил ~1~ Ярди. + +[KM5_5] +~g~Поздравляем, ты прикончил ~1~ Ярди. ПРИЗ $~1~ + +[KM5_6] +~g~Ты должен убить как минимум 8 торговцев-Ярди. + +[KM5_7] +~g~Убивай их побыстрее! Продав СПАНК, они смоются. + +[KM5_A] +ТЫ! Отличный момент чтобы прийти и показать свою тупую морду! + +[KM5_B] +Ты в курсе, что твоя прошлая попытка объяснить ямайцам, + +[KM5_B1] +что им не стоит водить дружбу с Картелем, не принесла никаких плодов? + +[KM5_C] +Ярди заполонили весь город, и торгуют пакетиками со СПАНКом, словно горячими пирожками! + +[KM5_D] +Эти Картельные свиньи смеются над нами, надо мной! + +[KM5_E] +Я даю тебе последний шанс доказать, что моя сестра в тебе не ошиблась! + +[KM5_F] +Иди же, прикончи этих подонков, и пусть твой позор смоют реки крови врагов!!! + +[KURUMA] +Курума + +[K_JAH] +Радио K-Jah + +[LANDSTK] +Лэндсталкер + +[LANGSL] +ВЫБОР ЯЗЫКА + +[LANGUA] +Язык + +[LAST] +Последнее сообщение. + +[LEGAL] +~g~Разберитесь с преступником! + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBFабвгдежзийклмнопрстуфхцчшщъыьэюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ + +[LINERUN] +Лайнранер + +[LIPS] +Губки 106 + +[LITTLE] +LITTLE T + +[LITTLEI] +Сент-Марк + +[LM1] +'ДЕВОЧКИ ЛУИДЖИ' + +[LM1_2] +~g~Отвези Мисти в клуб к Луиджи. + +[LM1_3] +~g~Посигналь, чтобы девица подошла к машине. + +[LM1_6] +~g~Вернись в машину! + +[LM1_7] +Остановись рядом с Мисти и подожди, пока она не сядет в тачку. + +[LM1_8] +Ты можешь пойти к Луиджи и получить у него задание, или покататься по городу. + +[LM1_8A] +Чтобы подзаработать немного деньжат, можно 'одолжить' такси... + +[LM1_9] +Привет, я Мисти. + +[LM2] +'ОТВАЛИ ОТ МОИХ ТЕЛОК' + +[LM2_1] +~g~Отгони его тачку в мастерскую для перекраски. + +[LM2_2A] +Нажав на ~h~ ~k~~PED_FIREWEAPON~~w~ ты ~h~ударишь~w~, ~h~пнешь~w~, или ~h~двинешь~w~ битой противника! + +[LM2_2C] +Нажав на ~h~ ~k~~PED_FIREWEAPON~~w~ ты ~h~ударишь~w~, ~h~пнешь~w~, или ~h~двинешь~w~ битой противника! + +[LM2_2D] +Нажав на ~h~ ~k~~PED_FIREWEAPON~~w~ ты ~h~ударишь~w~, ~h~пнешь~w~, или ~h~двинешь~w~ битой противника! + +[LM2_3] +~g~Отгони машину в гараж к Луиджи! + +[LM2_4] +~g~Перекрась машину! + +[LM2_A] +На улицах появился новый наркотик под названием СПАНК. + +[LM2_B] +Иди и впарь ему битой по морде! + +[LM2_C] +Луиджи велел передать тебе... + +[LM2_D] +вот это, держи. + +[LM2_E] +Какой-то умник впаривает эту дрянь моим девкам в Портландской гавани. + +[LM2_F] +Потом возьми его тачку и перекрась ее. + +[LM2_G] +Я требую возмещения морального ущерба! + +[LM3] +'ПРИВЕЗИ МНЕ МИСТИ' + +[LM3_10] +~g~Достань тачку! + +[LM3_11] +~g~Мисти в автобусе не ездит, достань нормальную тачку! + +[LM3_1A] +Нажми ~h~ ~k~~VEHICLE_HORN~~w~, чтобы ~h~посигналить~w~ и вызвать Мисти из дома. + +[LM3_1B] +Нажми ~h~ ~k~~VEHICLE_HORN~~w~, чтобы ~h~посигналить~w~ и вызвать Мисти из дома. + +[LM3_1C] +Нажми ~h~ ~k~~VEHICLE_HORN~~w~, чтобы ~h~посигналить~w~ и вызвать Мисти из дома. + +[LM3_2] +~g~Отвези Мисти к Джоуи. + +[LM3_4] +~g~Отправляйся за Мисти! + +[LM3_5] +Ты работаешь на Луиджи, да? Похоже, он наконец нашел водилу, которому можно доверять! + +[LM3_6] +Джоуи... + +[LM3_6A] +Твоя большая штука опять по мне соскучилась? + +[LM3_7] +Иди, я сейчас, заводная моя. + +[LM3_8] +Хай, я Джоуи. + +[LM3_9] +Луиджи сказал, что ты свой парень, так что заходи как-нибудь, + +[LM3_9A] +может, и у меня будет для тебя работенка. + +[LM3_9B] +Ладно. + +[LM3_A] +Эй, иди сюда, потолкуем... Мик, я с тобой попозже закончу. + +[LM3_B] +Как поживаешь, парень? + +[LM3_C] +Джоуи Леоне, сын дона, хочет поразвлечься со своей девчонкой Мисти. + +[LM3_D] +Сгоняй за ней в Хепберн Хейтс... + +[LM3_E] +но учти, этот район контролируют Дьяволы. + +[LM3_F] +Отвези ее к нему в гараж на Трентоне, да поживее, + +[LM3_G] +Джоуи не из тех, кто привык ждать, так что одна нога здесь... + +[LM3_H] +да, и советую смотреть на дорогу, а не на Мисти! + +[LM4] +'ОСАТАНЕЛЫЙ СУТЕНЕР' + +[LM4_A] +Какой-то урод из Дьяволов поставил своих потаскух работать на моей территории. + +[LM4_B] +Иди, разберись с этим козлом. + +[LM4_C] +Если нужен ствол - возьми его во дворе магазина, который напротив метро. + +[LM5] +'ВЕЧЕРИНКА У КОПОВ' + +[LM5_1] +~g~У тебя телок в тачке, как сельдей в бочке! ~g~Отвези их на вечеринку и приезжай за новыми. + +[LM5_2] +~r~Девка Луиджи теперь похожа на мешок с мясом! + +[LM5_3] +~g~Тебе нужна тачка! + +[LM5_4] +~g~Подбери телок работающих на бульваре Св.Марка. + +[LM5_5] +~g~Отвези девок на вечеринку к копам! + +[LM5_7] +~g~Луиджи хочет, чтобы на ~p~вечеринке у копов~g~ было не менее четырех девок! + +[LM5_8] +~g~Телок на вечеринке: ~1~ + +[LM5_9] +ДЕВОЧЕК: + +[LM5_A] +Копы решили устроить вечеринку в зале старой школы, которая рядом с мостом Каллахан, + +[LM5_B] +и их теперь потянуло на старые школьные забавы. + +[LM5_C] +Но сейчас все мои девки работают на панели. + +[LM5_D] +Привези им побольше моих телок, доставь всем удовольствие. + +[LM5_E] +Привези побольше, пока копы не пропили все свои бабки. + +[LOADCAR] +LOADING VEHICLE... (PRESS L1 TO CANCEL) + +[LOOK_A] +Удерживай кнопку ~h~~k~~VEHICLE_LOOKLEFT~~w~ или ~h~~k~~VEHICLE_LOOKRIGHT~~w~, чтобы посмотреть из машины ~h~налево~w~ или ~h~направо~w~. Если нажать обе, то посмотришь ~h~назад~w~. + +[LOV4_10] +~r~Ты уничтожил единственный способ выяснить, куда делся пакет! + +[LOVE] +ЗАДАНИЯ ДОНАЛЬДА + +[LOVE1] +'СПАСИТЕЛЬ' + +[LOVE1_1] +~g~Чтобы попасть в логово колумбийцев, тебе понадобится одна из их тачек. Возможно, ты ее найдешь в форте Стаунтона. + +[LOVE1_2] +~g~Спасай Старого Азиата. + +[LOVE1_3] +~g~Отвези старого азиата к Дональду Лаву. + +[LOVE1_4] +~g~Старый Азиат должен быть в одном из этих гаражей... + +[LOVE1_5] +~g~Хватит болтаться без дела, достань машину колумбийцев и спаси товарища Дональда. + +[LOVE1_6] +~r~Да... теперь кишки азиата размазаны по всей улице! + +[LOVE1_7] +~g~Через эти ворота пропускают лишь машины колумбийской банды. + +[LOVE1_A] +Во первых, я хочу поблагодарить вас за ту услугу, которую вы мне оказали. + +[LOVE1_B] +Но, жизненный опыт подсказывает мне, что преданность такого человека, как вы, можно купить, + +[LOVE1_C] +У меня есть один очень ценный знакомый - старый азиат. + +[LOVE1_D] +Они пытаются увеличить сумму назначенного ранее выкупа, но я не привык менять условия сделки. + +[LOVE1_E] +Сделка есть сделка, они не получат ни пенса больше. + +[LOVE1_F] +Люди всегда пытаются истолковать все по-своему. + +[LOVE1_G] +Вы должны спасти моего друга любой ценой. + +[LOVE1_H] +в отличие от преданности банды. + +[LOVE1_I] +Его держат в заложниках какие-то южноамериканцы в Аспатрии. + +[LOVE2] +'КОНЕЦ ВАКАГАСИРА!' + +[LOVE2_1] +~g~Отправляйся в форт Стаунтон и достань тачку колумбийцев! + +[LOVE2_2] +~g~Теперь заезжай в ~p~многоэтажный гараж в Ньюпорте~g~ и прихлопни Кенжи! + +[LOVE2_3] +~r~Если ты будешь без машины Картеля, тебя сразу узнают! + +[LOVE2_4] +~r~Якудза узнали тебя! + +[LOVE2_5] +~g~Кенжи теперь похож на бифштекс! Сваливай из Ньюпорта и уничтожь машину! + +[LOVE2_6] +~r~Ты замочил всех свидетелей! + +[LOVE2_7] +~g~Теперь уничтожь машину! + +[LOVE2_8] +~g~Быстрее сваливай из Ньюпорта! + +[LOVE2_A] +Ничто так не сбивает цены на недвижимость, как старая добрая гангстерская война, + +[LOVE2_B] +не считая, конечно, вспышки чумы......но она может доставить куда больше проблем. + +[LOVE2_C] +Я слышал о натянутых отношениях между Якудзой и колумбийцами. + +[LOVE2_D] +Нам стоит погреть руки на их дурацкой вражде. + +[LOVE2_E] +Я хочу чтобы вы прикончили вакагасира, Кенжи Касена. + +[LOVE2_F] +Сейчас у Кенжи деловая встреча на крыше многоэтажного гаража а Ньюпорте. + +[LOVE2_G] +Достаньте машину Картеля и разделайтесь с ним! + +[LOVE2_H] +Якудза решит, что это убийство - дело рук картеля, и объявит войну. + +[LOVE3] +'ГРУЗ В ОКЕАНЕ' + +[LOVE3_1] +~g~Возьми ~r~катер~g~ и следуй за ~y~самолетом~g~! + +[LOVE3_2] +~g~Ты собрал весь груз! Отвези его Дональду Лаву. + +[LOVE3_3] +~g~Самолет сбросил ~1~ из 6 упаковок. + +[LOVE3_4] +~r~Ты уничтожил самолет! + +[LOVE3_5] +~g~Самолет уже рядом. + +[LOVE3_6] +~r~Упаковка досталась полиции! + +[LOVE3_A] +Некоторые ценные товары очень сложно импортировать из-за лицемерия властей. + +[LOVE3_B] +Сегодня ночью один маленький самолетик на подлете к аэродрому откроет свой трюм. + +[LOVE3_C] +Он сбросит в воду несколько ценных упаковок. + +[LOVE3_D] +Постарайтесь опередить полицию и собрать груз первым. + +[LOVE4] +'ВЕЛИКИЙ АЭРОВОР' + +[LOVE4_1] +~r~Картель уже здесь! + +[LOVE4_2] +~g~Посылки нет! Выследи колумбийцев и забери у них груз. + +[LOVE4_3] +~g~Панлантик Констракшн...? + +[LOVE4_4] +~g~Доставь посылку к Дональду Лаву! + +[LOVE4_5] +~g~Посылка должна быть в самолете.... + +[LOVE4_6] +~g~Отправляйся на лифте в башню! + +[LOVE4_7] +~g~На острове Стаунтон есть одна стройка, возможно они повезли груз туда. + +[LOVE4_8] +~g~Без машины ты в гараж не попадешь. + +[LOVE4_9] +~r~Самолет уничтожен! + +[LOVE4_A] +Спасибо, что привезли мне этот груз, но он был всего лишь приманкой. + +[LOVE4_B] +Простите, но ради крупного дела на это приходится идти. + +[LOVE4_C] +По настоящему важный груз до сих пор все еще в самолете. + +[LOVE4_D] +К несчастью, власти аэропорта все же решили задержать и обыскать самолет, + +[LOVE4_E] +Поезжайте через мост в Шорсайд Вейл и затем в Международный аэропорт Фрэнсис. + +[LOVE4_F] +Деньги властям уже уплачены. + +[LOVE4_G] +Моя посылка ожидает вас в фюзеляже самолета, который стоит в ангаре на таможне. + +[LOVE4_H] +так что мне пришлось заплатить им за издержки. + +[LOVE5] +'ЭСКОРТ' + +[LOVE5_1] +~g~Поехали! + +[LOVE5_2] +~g~Тебе нужна тачка! + +[LOVE5_3] +~g~Езжай вперед и посмотри, что там в конце тоннеля! + +[LOVE5_4] +~r~Защищай грузовик! + +[LOVE5_5] +~r~Ты не сумел защитить грузовик! + +[LOVE5_A] +На вас можно положиться, друг мой. Что ни говори, а это сейчас большая редкость. + +[LOVE5_B] +Мой азиатский друг повезет доставленный вами товар по назначению, но ему потребуется эскорт. + +[LOVE5_C] +Я бы хотел, чтобы вы помогли ему в целости и сохранности доставить этот груз в Пайк Крик + +[LOVE6] +'ПРИМАНКА' + +[LOVE6_1] +~g~Теперь уводи копов подальше от склада! + +[LOVE6_2] +~r~Тебе не удалось увести полицию на достаточное расстояние! + +[LOVE6_3] +У тебя ~1~ секунд, чтобы вернуться к инкассаторской машине, иначе задание будет провалено. + +[LOVE6_4] +~r~Ты разбил приманку для копов! + +[LOVE6_A] +Урок бизнеса, мой друг. + +[LOVE6_B] +даже если она вообще не представляет его истинной ценности. + +[LOVE6_C] +Полиция оцепила весь район, в котором сейчас находится мой друг с грузом. + +[LOVE6_D] +Отправляйтесь туда и используйте фургон как приманку для копов. + +[LOVE6_E] +Если у вас есть особый товар, то каждая собака будет пытаться его у вас отнять... + +[LOVE6_F] +Пусть они за вами погоняются, а он тем временем уйдет. + +[LOVE7] +ИСЧЕЗНОВЕНИЕ ЛАВА + +[LOVEGO] +~g~У Дональда Лава возникли неотложные дела. Заходи попозже! + +[LRQC_1] +Нам с Асукой нужно кое-что обсудить, + +[LRQC_2] +а ты пока можешь тут осмотреться. + +[LRQC_3] +Тебе нужно место, где бы ты мог залечь. + +[LRQC_4] +На углу Бельвилля есть склад, в котором есть все, что тебе нужно. + +[LRQC_5] +Приходи ко мне сюда, когда будешь готов, + +[LRQC_6] +и я обсужу с тобой кое-какие дела. + +[LUIGGO] +~g~Луиджи осматривает новых девочек. Загляни попозже! + +[LUIGI] +ЗАДАНИЯ ЛУИДЖИ + +[LUIGIS] +Жилище Луиджи + +[L_TRN_1] +Ты можешь попасть в другой район Портланда, воспользовавшись метро. Нажми~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, чтобы ~h~сесть~w~ или ~h~выйти~w~ из поезда. + +[L_TRN_2] +Ты можешь попасть в другой район Портланда, воспользовавшись метро. Нажми~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, чтобы ~h~сесть~w~ или ~h~выйти~w~ из поезда. + +[MAFIACR] +Сентинел Мафии + +[MANANA] +Манана + +[MAR] +Мар + +[MAY] +Май + +[MCDNSP] +There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 500KB is needed to save this application data. Do you wish to start? (YES or NO) + +[MCGNSP] +There is insufficient space on the Memory Card (PS2) in MEMORY CARD slot 1. At least 200KB is needed to save this application data. Do you wish to start? (YES or NO) + +[MCLOAD] +Loading Data. Please do not remove the Memory Card (PS2) in MEMORY CARD slot 1, reset or switch off the console. + +[MCSTNS] +There is no Memory Card (PS2) in MEMORY CARD slot 1. Do you wish to start? (YES or NO) + +[MC_LDFL] +Загрузка не прошла! + +[MC_NWRE] +Идет перезапуск игры. + +[MEA1] +'МОШЕННИК' + +[MEA1_1] +~r~Управляющий банка мертв! + +[MEA1_2] +~r~Тебе же сказали уничтожить машину! + +[MEA1_3] +~g~Выбирайся из машины! + +[MEA1_4] +~r~Ты проехал мимо управляющего банка! + +[MEA1_B] +Я Чонкс, Марти Чонкс. + +[MEA1_B3] +~g~Езжай и встреть управляющего банка. + +[MEA1_B4] +А, тебя послал мистер Чонкс. Ну поехали, нанесем ему визит. + +[MEA1_B5] +TEXT NO LONGER NEEDED + +[MEA1_B6] +~g~Отгони машину в утилизатор, чтобы избавиться от улик. Выйди из машины и ее подцепит кран. + +[MEA1_C] +Я тут на углу построил заводик по производству корма для кошек и собак. + +[MEA1_D] +У меня возникли проблемы с финансами, но у кого их не бывает? + +[MEA1_E] +Я собирался встретиться с управляющим из банка. + +[MEA1_F] +Этот мерзкий мошенник решил поживиться за мой счет, и поднял процент займа. + +[MEA1_G] +Возьми мою тачку, встреть его и привези сюда. + +[MEA1_H] +Я приготовил небольшой сюрприз для этого кровопийцы! + +[MEA2] +'КРАЖА' + +[MEA2_1] +~r~Тебе же сказали уничтожить тачку! + +[MEA2_2] +~r~Вор мертв! + +[MEA2_3] +~g~Отгони машину назад на фабрику. + +[MEA2_4] +~r~Ты проехал мимо вора! + +[MEA2_A] +Я нанял воров, чтобы они забрались в мой офис... + +[MEA2_B] +и кое-что вынесли. А я бы потом смог потребовать страховку. + +[MEA2_B3] +~g~Езжай, встреть воров. + +[MEA2_B4] +~g~Отвези их к фабрике по производству корма. + +[MEA2_B5] +TEXT NO LONGER NEEDED + +[MEA2_B6] +~g~Перекрась машину, чтобы избавиться от улик. + +[MEA2_C] +Но эти ублюдки теперь угрожают тем, что расскажут все страховой компании, + +[MEA2_D] +если я с ними не поделюсь. + +[MEA2_E] +Нет, ты представляешь? + +[MEA2_F] +За воротами фабрики я поставил машину. + +[MEA2_G] +Возьми ее и сгоняй за ними. Они тусуются где-то в квартале красных фонарей. + +[MEA2_H] +Привези их ко мне на фабрику и я покажу им кузькину мать. + +[MEA3] +'ЖЕНА' + +[MEA3_1] +~r~Жена мертва! + +[MEA3_2] +~r~Ты должен был утопить тачку! + +[MEA3_3] +~r~Ты проехал мимо его жены! + +[MEA3_A] +Чтобы продолжить свой бизнес, мне нужно как быстрее решить финансовые проблемы. + +[MEA3_B] +У моей жены хорошая страховка жизни, и к тому же она постоянно транжирит мои деньги. + +[MEA3_B3] +~g~Езжай и подбери миссис Чонкс. + +[MEA3_B4] +Марти хочет меня видеть? Давай быстрее, а то мне нужно еще успеть в парикмахерскую. + +[MEA3_B5] +TEXT NO LONGER NEEDED + +[MEA3_B6] +~g~Чтобы избавиться от улик, утопи тачку. + +[MEA3_C] +Я поставил машину в обычном месте. + +[MEA3_D] +Забери мою жену из маникюрного салона и привези ко мне на фабрику. + +[MEA4] +'ЛЮБОВНИК' + +[MEA4_1] +~r~Карлос мертв! + +[MEA4_2] +~r~Марти Чонкс мертв! + +[MEA4_3] +~r~Ты проехал мимо Карлоса! + +[MEA4_A] +Проклятье, у меня проблемы! + +[MEA4_B] +Оказалось, что моя жена спала с тем, кому я задолжал. + +[MEA4_B3] +~g~Подбери любовника жены Чонкса. + +[MEA4_B4] +Тебя послал Марти, да? Я объясню этой мерзкой крысе, что такое настоящий бизнес. + +[MEA4_B5] +Карл, привет! Мне эээ... нужно время, чтобы отдать долг. + +[MEA4_B6] +Нет, Марти, прошло и так много времени. У тебя был шанс, а теперь я забираю эту фабрику... + +[MEA4_B7] +Ну ты хоть ко мне в офис зайди... + +[MEA4_C] +Он очень зол и требует, чтобы я вернул ему деньги! + +[MEA4_D] +Мне нужно с ним поговорить... + +[MEA4_E] +он думает, что я отдам ему долг... + +[MEA4_F] +но я решил... + +[MEA4_G] +что в этом месяце городских дворняг ждет новое блюдо! + +[MEAT1_A] +Друзья посоветовали мне обратиться к тебе, чтобы решить все мои проблемы. Если ты согласен, подойди к телефону в Трентоне. + +[MED_WST] +Убито медиков + +[MEMTST] +MemoryCardTest screen + +[MGSVCN] +MagazineDirectory Created + +[MGSVNC] +MagazineDirectory Not Created + +[MISTY1] +~r~Мисти теперь лучше везти в морг! + +[MMRAIN] +Количество выпавших осадков (мм) + +[MM_1] +'ГОНКА ПО ГАРАЖУ' + +[MM_1_A] +~g~У тебя ~y~2 минуты~g~, чтобы проехать ~y~20 контрольных пунктов~g~ в высотном гараже! ~g~Ты можешь проезжать их ~y~В ЛЮБОМ ПОРЯДКЕ~g~. + +[MM_1_B] +~1~ из 20! + +[MM_1_C] +~g~У тебя 20 секунд, после прохождения каждого контрольного пункта тебе будут добавлять по ~y~5 секунд~g~. ~g~Время ~y~УЖЕ~g~ пошло. + +[MONTAX] +Денег заработано на такси + +[MOONBM] +Лунный луч + +[MRWONGS] +М-р.Вонг + +[MSX_FM] +MSX FM + +[MULE] +Тягач + +[MUSMEN] +Музыка/Звуки + +[MUSVOL] +Громкость музыки + +[MXCARD] +Длина лучшего БЕЗУМНОГО прыжка (ft) + +[MXCARDM] +Длина лучшего БЕЗУМНОГО прыжка (м) + +[MXCARJ] +Высота лучшего БЕЗУМНОГО прыжка (ft) + +[MXCARJM] +Высота лучшего БЕЗУМНОГО прыжка (м) + +[MXFLIP] +Переворотов в лучшем БЕЗУМНОМ прыжке + +[MXJUMP] +Разворот в лучшем БЕЗУМНОМ прыжке + +[M_FAIL] +ЗАДАНИЕ ПРОВАЛЕНО! + +[M_PASS] +ЗАДАНИЕ ВЫПОЛНЕНО! $~1~ + +[M_WASTE] +Мужчин убито + +[NEW_TAX] +БОЛЬШЕ! БЫСТРЕЕ! ЛУЧШЕ! В Харвуде открылся новый таксопарк Борнини. Звоните 555-БОРНИНИ! + +[NICK] +НИК ЛАВ + +[NMISON] +Заданий начато + +[NMMISP] +Заданий завершено + +[NO] +Нет + +[NOCD] +CD-привод пуст. Пожалуйста, вставьте диск. + +[NOCONT] +Please reconnect an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). to controller port 1 to continue + +[NOCONTE] +Please re-insert the analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2) in controller port 1 to continue + +[NODOORS] +~g~Они все туда не влезут! Достань тачку попросторней! + +[NOGMSV] +Записаться можно лишь в твоем гараже. + +[NOMONEY] +~g~У тебя не хватает денег! + +[NOSTUC] +Не выполнено ни одного БЕЗУМНОГО трюка + +[NOUNGM] +Всего уникальных прыжков + +[NOUNIF] +Выполнено уникальных прыжков + +[NOV] +Ноя + +[NO_PAUZ] +При игре по сети пауза не работает. Чтобы выйти нажми ESC дважды! + +[NO_PCCD] +Пожалуйста, вставьте второй диск игры GTA3 в CDROM привод, или нажмите ESC для выхода из игры. + +[NRECORD] +~r~РЕКОРД НЕ ПОБИТ! + +[NUMBER] +~1~ + +[OCT] +Окт + +[OPENCD] +CRDOM-привод открыт. Пожалуйста, закройте CDROM-привод. + +[OUTTIME] +~r~Очень медленно, друг, очень медленно! + +[OUT_VEH] +~g~Выйди из тачки! + +[O_FAIL] +ДОПОЛНИТЕЛЬНОЕ ЗАДАНИЕ ПРОВАЛЕНО! + +[O_PASS] +ДОПОЛНИТЕЛЬНОЕ ЗАДАНИЕ ВЫПОЛНЕНО! + +[PAGEB1] +В убежище доставлен пистолет + +[PAGEB10] +В убежище доставлена ракетница + +[PAGEB11] +В убежище доставлен огнемет + +[PAGEB12] +В убежище доставлена взятка для полиции + +[PAGEB13] +В убежище доставлено лекарство + +[PAGEB14] +В убежище доставлен адреналин + +[PAGEB2] +В убежище доставлен 'Узи' + +[PAGEB3] +В убежище доставлена броня + +[PAGEB4] +В убежище доставлен дробовик + +[PAGEB5] +В убежище доставлены гранаты + +[PAGEB6] +В убежище доставлены коктейли Молотова + +[PAGEB7] +В убежище доставлен AK-47 + +[PAGEB8] +В убежище доставлена снайперская винтовка + +[PAGEB9] +В убежище доставлена винтовка M16 + +[PAGE_00] +. + +[PAGE_01] +Убей ~1~ Дьяволов за 120 секунд! + +[PAGE_02] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_03] +Убей ~1~ мафиози за 120 секунд! + +[PAGE_04] +Убей ~1~ триадовцев за 120 секунд! + +[PAGE_05] +Убей ~1~ триадовцев за 120 секунд! + +[PAGE_06] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_07] +Расколи ~1~ голов Ярди за 120 секунд! + +[PAGE_08] +Сожги ~1~ членов Якудзы за 120 секунд! + +[PAGE_09] +Уничтож ~1~ машин за 120 секунд! + +[PAGE_10] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_11] +Истреби ~1~ Ярди за 120 секунд! + +[PAGE_12] +Подожги ~1~ членов Якудзы за 120 секунд! + +[PAGE_13] +Взорви ~1~ Ярди за 120 секунд! + +[PAGE_14] +Поджарь ~1~ колумбийцев за 120 секунд! + +[PAGE_15] +Задави ~1~ Капюшонов за 120 секунд! + +[PAGE_16] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_17] +Задави ~1~ колумбийцев за 120 секунд! + +[PAGE_18] +Уничтожь ~1~ машин за 120 секунд! + +[PAGE_19] +Лиши голов ~1~ колумбийцев за 120 секунд! + +[PAGE_20] +Обезглавь ~1~ Капюшонов за 120 секунд! + +[PANCAK] +РАСПЛЮЩИЛ! + +[PANLANT] +Панлантик + +[PAPER1] +* + +[PAPER2] +* + +[PAPER3] +* + +[PARK] +Парк Бельвиль + +[PARSHP] +Parse Heap + +[PASDRO] +Потеряно пассажиров + +[PATRIOT] +Пэтриот + +[PAUSED] +ИГРА ОСТАНОВЛЕНА + +[PBOAT_1] { re3 change } +Чтобы выстрелить из орудия катера нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[PBOAT_2] { re3 change } +Чтобы выстрелить из орудия катера нажми ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[PCLOAD] +Loading File Data + +[PCRESRT] +Перезапуск Grand Theft Auto III + +[PDRGON] +ShowPedRoadGroups On + +[PEREN] +Многолетник + +[PERPIC] +Найдено особых пакетов + +[PER_COM] +Процент завершения игры + +[PE_WAST] +Убито вами людей + +[PE_WSOT] +Убито преступниками людей + +[PIG_WST] +Убито копов + +[PL_STAT] +ДАННЫЕ ОБ ИГРОКЕ + +[POLICAR] +Полицейская + +[PONY] +Пони + +[PORT_E] +Порт Портланда + +[PORT_I] +Трентон + +[PORT_S] +Пристань Атлантик + +[PORT_W] +Мыс Каллахан + +[PQUINS] +Идеальный четверной безумный трюк + +[PREBRF] +Предыдущие сообщения + +[PREDATR] +Хищник + +[PRGOFF] +ShowPedRoadGroups Off + +[PRINST] +Идеальный безумный трюк + +[PROJECT] +Сады Вичита + +[PRTRST] +Идеальный тройной безумный трюк + +[PRVMEN] +Задание предыдущей миссии + +[PUSAVE] +Save Only the game + +[PU_MONY] +Не хватает денег. + +[QUINST] +Четверной безумный трюк + +[QUITOP] +Quit Options + +[RADIO_A] +Нажимай ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ ~w~, чтобы менять ~h~ радиоволну. + +[RADIO_B] +Нажимай ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ ~w~, чтобы менять ~h~ радиоволну. + +[RADIO_C] +Нажимай ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ ~w~, чтобы менять ~h~ радиоволну. + +[RADIO_D] +Нажимай ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~ ~w~, чтобы менять ~h~ радиоволну. + +[RAMPAGE] +СХВАТКА!! + +[RAMP_A] +ВСЕ СХВАТКИ ПРОЙДЕНЫ! + +[RAMP_F] +СХВАТКА ПРОИГРАНА + +[RAMP_P] +СХВАТКА ВЫИГРАНА! + +[RATNG1] +Воришка + +[RATNG10] +Чистильщик + +[RATNG11] +Профессионал + +[RATNG12] +Киллер + +[RATNG13] +Правая рука + +[RATNG14] +Главарь + +[RATNG15] +Босс + +[RATNG2] +Мошенник + +[RATNG3] +Хулиган + +[RATNG4] +Шестерка + +[RATNG5] +Грабитель + +[RATNG6] +Громила + +[RATNG7] +Бандит + +[RATNG8] +Головорез + +[RATNG9] +Братан + +[RAY] +ЗАДАНИЯ РЭЯ + +[RAYGO] +~g~Рэй пошел в другой туалет. Зайди сюда попозже! + +[RC1] +'ДРАКА С ДЬЯВОЛАМИ' + +[RC2] +'МОРДОБОЙ С МАФИЕЙ' + +[RC3] +'КАТАСТРОФА В КАЗИНО' + +[RC4] +'РАЗБОРКА С РАСТАМАНАМИ' + +[RCBANDT] +Багги Бандит + +[RCHELP] { re3 change } +Чтобы подорвать машинку, нажми ~k~~VEHICLE_FIREWEAPON~, или врежься в колесо жертвы. + +[RCHELPA] { re3 change } +Чтобы подорвать машинку, нажми ~k~~VEHICLE_FIREWEAPON~, или врежься в колесо жертвы. + +[RC_1] +У тебя 2 минуты, чтобы подорвать как можно больше машин Дьяволов! + +[RC_2] +У тебя 2 минуты, чтобы подорвать как можно больше машин мафии! + +[RC_3] +У тебя 2 минуты, чтобы подорвать как можно больше машин Якудзы! + +[RC_4] +У тебя 2 минуты, чтобы подорвать как можно больше машин Ярди! + +[RC_5] +У тебя 2 минуты, чтобы подорвать как можно больше машин Капюшонов! + +[RC_6] +У тебя 2 минуты, чтобы подорвать как можно больше машин Картеля! + +[RECORD] +~g~НОВЫЙ РЕКОРД!! + +[REDLIGH] +Квартал красных фонарей + +[REEFER] +Рифер + +[REGCAR] +Register MemoryCard One + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[REPLAY] +ПОВТОР + +[RESTART] +Новая игра + +[REWARD] +НАГРАДА $~1~ + +[RHINO] +Носорог + +[RISE] +Rise FM + +[RM1] +'КОНЕЦ СТУКАЧА' + +[RM1_1] +~g~Осмотри дом защиты свидетелей. + +[RM1_2] +~g~Прикончи МакАффри! + +[RM1_3] +~r~МакАффри скрылся! + +[RM1_4] +~g~Ты использовал все гранаты! Иди купи в магазине еще! + +[RM1_5] +~g~Вернись и подожги дом! + +[RM1_A] +Этот подонок МакАффри брал взяток больше, чем кто-либо. + +[RM1_B] +Он решил всех заложить, и таким образом оправдать все свои темные делишки. + +[RM1_C] +Хренов стукач! + +[RM1_D] +Он в центре Ньюпорта под вооруженной охраной в здании защиты свидетелей, которое позади стоянки. + +[RM1_E] +Чтобы выкурить его наружу - подожги этот дом, ну а потом, я думаю, ты сможешь заставить его замолчать. + +[RM2] +'РУКА ПОМОЩИ' + +[RM2_A] +Мой старый армейский друг организовал бизнес в Рокфорде. + +[RM2_A1] +Эй, парень, сюда! + +[RM2_B] +Мы с ним были в Никарагуа в те годы, когда правительство знало, что делает. Ну так вот. + +[RM2_C] +Его вчера избили какие-то сволочи из Картеля, и сказали что сегодня заберут кое-что из его товара. + +[RM2_D] +Ему нужно помочь. Ну, а он в долгу тоже не останется - даст тебе затовариться 'скобяными изделиями'. + +[RM2_D1] +Я бы, конечно, и сам помог ему, но старость не радость - кхе кхе - вот, в-общем, удачи тебе. + +[RM2_E] +Рэй мне звонил... но я думал, он пришлет побольше народу. + +[RM2_E1] +Я не могу поверить, что эти трусливые ублюдки снова оставили меня без прикрытия! + +[RM2_F] +Ну, три руки лучше чем одна, так что бери все, что тебе пригодится. + +[RM2_F1] +Колумбийцы прибудут с минуты на минуту! + +[RM2_G] +~g~Проверь, как дела у Фила! + +[RM2_H] +~r~Фила убили! + +[RM2_K] +Проклятье, вот они! ПОЛУЧАЙТЕ! + +[RM2_L] +Эй! Если бы ты тогда был в Никарагуа, глядишь, я и не потерял бы руку! + +[RM2_M] +Если тебе понадобится оружие, то не стесняйся, бери все что нужно из ящиков. + +[RM2_N] +Только не забудь заплатить. А сейчас сваливай, с копами я все улажу. + +[RM3] +'УНИЧТОЖЕНИЕ УЛИК' + +[RM3_1] +~g~Оставь улики в машине и подожги ее. + +[RM3_4] +~g~Обвинение лишилось улик! + +[RM3_5] +~g~У тебя ~1~ из 6 пакетов с уликами. + +[RM3_6] +~r~Фотографии разлетятся по всему городу! + +[RM3_7] +~g~Теперь поджигай тачку! + +[RM3_8] +~r~Эта тачка была приманкой! + +[RM3_A] +Я знаю одного очень важного человека в городе, + +[RM3_B] +Против него выдвинуто обвинение. Есть какие-то непристойные фотки, где его сняли + +[RM3_C] +на вечеринке в морге или еще где. + +[RM3_D] +Улики скоро собираются везти в суд. + +[RM3_E] +Тебе нужно протаранить машину с уликами и собрать все до одной выпавшие фотографии. + +[RM3_F] +Как только соберешь все улики - оставь их в машине и взорви ее. + +[RM3_G] +Если ты это провернешь, то все будут в плюсе. + +[RM3_H] +с довольно экзотическим вкусом и деньгами для его удовлетворения. + +[RM4] +'РЫБАЛКА' + +[RM4_1] +~g~Тебе нужно угнать полицейский катер. + +[RM4_2] +~g~Отправляйся к маяку и пусти напарника Рэя на корм рыбам! + +[RM4_3] +~r~Партнер Рэя скрылся! + +[RM4_A] +Думаю, мой напарник - стукач. + +[RM4_B] +Нужно побыстрее заткнуть ему пасть. + +[RM4_C] +На своем катере он вечерами ходит рыбачить к маяку на утесе Портланда. + +[RM4_D] +Возьми полицейский катер и разберись с этим чертовым стукачем! + +[RM4_E] +Хватит ему рыбу ловить. Пусть он ее теперь покормит. + +[RM5] +'ЖИВАЯ МУМИЯ' + +[RM5_1] +~g~Перехвати скорую. + +[RM5_2] +~g~Тебя засекли! + +[RM5_3] +~g~Это была приманка! + +[RM5_4] +~g~Пули эту загипсованную куклу не берут! + +[RM5_5] +~g~Этой загипсованной кукле огнемет не страшен! + +[RM5_6] +~g~Он вывалился! Раскатай его по асфальту, или взорви гранатами! + +[RM5_7] +~r~Свидетеля доставили на место! + +[RM5_8] +~g~Свидетеля больше нет! + +[RM5_A] +Ты никчемный ублюдок! + +[RM5_A1] +Ты все завалил! Я уже на крючке, а ты даже не смог поджарить этого хренова шпиона. + +[RM5_B] +Я же тебе хорошо заплатил за то, чтобы ты убрал этого стукача! + +[RM5_B1] +Сегодня этот урод собирается давать показания! + +[RM5_C] +Его с минуты на минуту собираются перевезти из госпиталя Карсон в участок Рокфорда. + +[RM5_D] +Если он заговорит, мне конец... + +[RM5_E] +так что иди, и доделывай свою работу! + +[RM6] +'НА МУШКЕ' + +[RM6_1] +Вот тебе ключ от тайника. + +[RM6_2] +В нем я на черный день припрятал наличные и кое-какие полезные вещички. + +[RM6_3] +Увидимся. + +[RM6_4] +~g~Отправляйся к тайнику и забери вещички Рэя. + +[RM6_5] +~g~Мост под наблюдением ЦРУ. Тебе придется найти другой путь. + +[RM6_6] +~r~Рэй мертв! + +[RM6_666] +Возьми мой пуленепробиваемый Пэтриот. Увидимся в Майами, Рэй + +[RM6_7] +~r~Рэй опоздал на самолет. + +[RM6_8] +~g~Ты проехал мимо Рэя, вернись и посади его. + +[RM6_A] +За тобой хвоста нет? Хорошо. + +[RM6_B] +Похоже, я прыгнул выше головы и перешел кое-кому дорогу! + +[RM6_C] +Я так понял, что ЦРУ имеет долю от продажи СПАНКА + +[RM6_C1] +и они убирают тех, кто мешает Картелю работать. + +[RM6_D] +Я у них на мушке, мне нужно быстрее сваливать. + +[RM6_E] +Мой самолет вот-вот отлетает, гони в аэропорт, я хорошо заплачу! + +[ROADBR1] +Мост Каллахан + +[ROADBR2] +Мост Каллахан + +[RUMPO] +Рампо + +[SAVE1] +Чтобы ~h~сохранить игру~w~ - пройди в дверь. Во время задания записаться нельзя. + +[SAVE2] +Если в гараже оставить машину, то она будет сохранена при записи игры. + +[SCASSL] +Sick Fuck Selected + +[SCORE] +$~1~ + +[SCRFOR] +Screen format + +[SCROPT] +НАСТРОЙКИ ЭКРАНА + +[SCSCSL] +Sick Fucker Selected + +[SECOND] +~g~второй + +[SECURI] +Инкассатор + +[SENTINL] +Сентинел + +[SEP] +Сен + +[SET1] +SetUp 1. + +[SET1EN] +SetUp 1. Enabled + +[SET2] +SetUp 2 + +[SET2EN] +SetUp 2. Enabled + +[SET3] +SetUp 3 + +[SET3EN] +SetUp 3. Enabled + +[SET4] +SetUp 4 + +[SET4EN] +SetUp 4. Enabled + +[SFXVOL] +Громкость звуков + +[SHOPING] +Бедфорд Поинт + +[SHPLOF] +gbShowCollisionPolys Off + +[SHPLON] +gbShowCollisionPolys On + +[SICASS] +Sick Fuck + +[SICSIC] +Sick Fucker + +[SIREN_1] +Чтобы включить сирену, понажимай кнопку ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_2] +Чтобы включить сирену, понажимай кнопку ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_3] +Чтобы включить сирену, понажимай кнопку ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_4] +Чтобы включить сирену, понажимай кнопку ~h~~k~~VEHICLE_HORN~~w~. + +[SLNSP] +Insufficient space to save. Please insert a Memory Card (PS2) with at least 200KB of free space available into MEMORY CARD slot 1. + +[SLONDR] +Insufficient space to save. Please insert a Memory Card (PS2) with at least 500KB of free space available into MEMORY CARD slot 1. + +[SLONFM] +Error formatting Memory Card (PS2) in MEMORY CARD slot 1. + +[SLONNF] +Memory Card (PS2) in MEMORY CARD slot 1 is unformatted. + +[SLONNO] +No Memory Card (PS2) in MEMORY CARD slot 1. + +[SOAKED] +ЧЕРТ! + +[SOUND] +ЗВУК + +[SPAIN] +Spanish + +[SPEEDER] +Speeder + +[SPLAT] +ГОТОВ! + +[SPOTTED] +~r~Они у тебя на хвосте! + +[SPRAY] +В мастерской твою тачку ~h~отремонтируют~w~ и ~h~перекрасят~w~. Копы тебя больше ~h~не узнают~w~. Стоимость - ~h~$1000~w~. + +[SPRAY1] +В мастерской твою тачку ~h~отремонтируют~w~ и ~h~перекрасят~w~. Копы тебя больше ~h~не узнают~w~. Стоимость - ~h~$1000~w~. Первый раз это бесплатно. + +[SPRAY_1] { re3 change } +Чтобы включить брандспойт, нажми на ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[SPRAY_4] { re3 change } +Чтобы включить брандспойт, нажми на ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[STADIUM] +Аспатрия + +[STALION] +Жеребец + +[STANDS] +ЛАРЬКОВ РАЗБИТО: + +[STASH] +~g~Отвези СПАНК на ~p~стройку~g~! + +[STINGER] +Стингер + +[STOCK] +отсутствует + +[STRETCH] +Стретч + +[SUBWAY1] +Станция Портланд + +[SUBWAY2] +Станция Рокфорд + +[SUBWAY3] +Станция Южный Стаунтон + +[SUBWAY4] +Вокзал Шорсайд + +[SUB_IND] +Пайк Крик + +[SUB_ZO2] +Шорсайд Вейл + +[SUB_ZO3] +Шорсайд Вейл + +[SUB_ZON] +Шорсайд Вейл + +[SVCONF] +ПОДТВЕРДИТЕ ЗАПИСЬ + +[SVGMON] +SaveTheGame + +[SWANKS] +Кедровая роща + +[S_PROM2] +Если в этот гараж поставить машину, то она будет сохранена при записи игры. + +[S_PROMP] +Когда ты не на задании, то здесь можно ~h~сохранить игру~w~, но это добавит ко времени шесть часов. + +[S_TRN_1] +Воспользовавшись метро, ты можешь попасть в другой район города. Нажми~h~ ~k~~VEHICLE_ENTER_EXIT~~w~ чтобы ~h~сесть~w~ или ~h~выйти~w~ из поезда. + +[S_TRN_2] +Воспользовавшись метро, ты можешь попасть в другой район города. Нажми~h~ ~k~~VEHICLE_ENTER_EXIT~~w~ чтобы ~h~сесть~w~ или ~h~выйти~w~ из поезда. + +[S_VIEW] +Портланд Вью + +[T4X4_1] +'ПЭТРИОТ РАЛЛИ' + +[T4X4_1A] +~g~У тебя ~y~5 минут~g~ чтобы пройти ~y~15 контрольных пунктов~g~. Ты можешь их проходить в ~y~ЛЮБОМ~g~ порядке. + +[T4X4_1B] +~1~ из 15! + +[T4X4_1C] +~g~Отсчет времени пойдет, как только ты ~y~ПРОЕДЕШЬ~g~ через первый контрольный пункт. После прохождения каждой точки тебе будет добавляться ~y~20 секунд~g~. + +[T4X4_2] +'ГОНКА В ПАРКЕ' + +[T4X4_2A] +~g~У тебя ~y~5 минут~g~ чтобы пройти ~y~12 контрольных пунктов~g~. Ты можешь их проходить в ~y~ЛЮБОМ~g~ порядке. + +[T4X4_2B] +~1~ из 12! + +[T4X4_2C] +~g~Отсчет времени пойдет, как только ты ~y~ПРОЕДЕШЬ~g~ через первый контрольный пункт. После прохождения каждой точке тебе будет добавляться ~y~10 секунд~g~. + +[T4X4_3] +'ЖАРКАЯ ГОНКА' + +[T4X4_3A] +~g~У тебя ~y~5 минут~g~, чтобы пройти ~y~20 контрольных пунктов~g~. Ты можешь их проходить в ~y~ЛЮБОМ~g~ порядке. + +[T4X4_3B] +~g~Отсчет времени пойдет, как только ты ~y~ПРОЕДЕШЬ~g~ через первый контрольный пункт. После прохождения каждой точки тебе будет добавляться ~y~15 секунд~g~. + +[T4X4_3C] +~1~ из 20! + +[T4X4_F] +~r~Ты не успел! Слишком сложно для тебя?! + +[TAXI] +Такси + +[TAXI1] +~g~Ищи пассажира. + +[TAXI2] +~r~Ты не успел вовремя! + +[TAXI3] +~r~Твой пассажир бежал в ужасе! + +[TAXI4] +Пассажир доставлен! + +[TAXI5] +ПРИЗ ЗА СКОРОСТЬ! + +[TAXI6] +Задание такси окончено + +[TAXI7] +~r~Такси слишком помято, отремонтируй его. + +[TAXIH1] +Остановись рядом с указанным пешеходом, подожди, пока он не сядет в такси и за указанное время отвези его куда он хочет. + +[TAXI_M] +'ВОДИТЕЛЬ ТАКСИ' + +[TEFONE] +Test Format MemCard One + +[TEUFON] +Test UnFormat MemCard One + +[TEXTXYZ] +Writing coordinates to file... + +[THIRD] +~g~третий + +[TIMER] +Это задание на время, ты должен успеть его выполнить, пока тикает таймер. + +[TM1] +'БОЛЬШАЯ СТИРКА' + +[TM1_A] +~w~Присаживайся, малыш, давай присаживайся. + +[TM1_B] +~w~Итак, прачечная не хочет платить за защиту? + +[TM1_C] +~w~Триадовцы думают, что могут тягаться со мной? + +[TM1_D] +~w~Давай-ка покажем этим 'крутым парням', что значит быть действительно крутым. + +[TM1_E] +~w~Да, преподай им урок. Еще никто из нас не отступал перед Триадой. + +[TM1_F] +~w~Твой отец, упокой господь его душу, не цацкался с ними на Сицилии. + +[TM1_G] +~w~Извини Ма. Да, Ма. + +[TM1_H] +~w~Я хочу чтобы ты взорвал все фургоны прачечной + +[TM1_I] +~w~и прикончил Триадовцев, что встанут у тебя на пути. + +[TM1_J] +~w~Все, что для этого нужно, ты можешь взять у Лысого. + +[TM2] +'МЗДА' + +[TM2_1] +~g~Отвези деньги Тони! + +[TM2_2] +~g~Ты загасил их всех! + +[TM2_3] +~g~Это ловушка! Прикончи их! + +[TM2_A] +~w~Тони пошел на очередное дело. + +[TM2_AA] +~w~Он никогда не станет таким авторитетом, как отец. Вон тебе записка. + +[TM2_B] +~w~Прачечная согласна платить - ты все сделал как нужно! + +[TM2_C] +~w~Иди, забери деньги и принеси их мне. Но остерегайся триадовцев. + +[TM2_D] +~w~Они решили поджарить твою задницу, но не бери это в голову, сынок. + +[TM2_E] +~w~Никто, повторяю, никто не связывается с ТОНИ СИПРИАНИ! + +[TM3] +'СОБРАНИЕ СЕМЬИ САЛЬВАТОРЕ' + +[TM3_1] +~g~Возьми у Джоуи Стретч. + +[TM3_2] +~g~Теперь езжай за Луиджи. + +[TM3_3] +~g~Теперь забери Тони. + +[TM3_4] +~g~Отвези приятелей в особняк Сальваторе. + +[TM3_5] +~y~Это засада Триады! + +[TM3_A] +~w~Дон Сальваторе решил нас всех собрать + +[TM3_A1] +~r~Джоуи поджарился! + +[TM3_A2] +~r~Джоуи и Луиджи кремированы! + +[TM3_A3] +~r~Джоуи, Тони и Луиджи спеклись! + +[TM3_B] +~w~Нужно заехать к Джоуи, и забрать лимузин и его самого из гаража. + +[TM3_C] +~w~Затем отправишься в клуб за Луиджи, и потом заедешь за мной, + +[TM3_D] +~w~Сегодня нам всем босс назначил встречу у него в особняке. + +[TM3_E] +~w~Эти Триадовцы должны знать свое место. + +[TM3_F] +~w~Им нужна война, они ее получат. + +[TM3_G] +~w~Давай, отправляйся. + +[TM3_H] +~w~Ты молодец, парень, просто молодец. + +[TM3_I] +~w~Пошли, познакомлю тебя с доном. + +[TM3_J] +~w~Эээй! Луиджи! + +[TM3_K] +~w~О, Сальваторе, мои девочки по тебе уже соскучились, тебя так долго не было. + +[TM3_L] +~w~Передай им, что как только мы закончим это небольшое дельце, + +[TM3_M] +~w~то отправимся в клуб и отпразднуем, хорошо? + +[TM3_MA] +~w~Да не знаю я, где он! + +[TM3_MB] +~w~Я уверена, что он сам себя иногда не понимает. + +[TM3_MC] +~w~Они с отцом такие разные. Отец, тот всегда при деле, решительный... + +[TM3_N] +~w~Вот он, мой мальчик. + +[TM3_N2] +~w~Как у тебя дела, пап? + +[TM3_O] +~w~Ты еще не нашел себе хорошую девушку? + +[TM3_P] +~w~Эй, твоя мама, упокой господь ее душу, перевернулась бы в могиле, + +[TM3_Q] +~w~если бы увидела тебя без жены. + +[TM3_R] +~w~Я знаю, па, я над этим работаю. + +[TM3_S] +~w~Тони, как твоя мама? + +[TM3_T] +~w~Знаешь, она чудесная женщина. Сильная. Умная. + +[TM3_U] +~w~У нее все...хорошо. + +[TM3_V] +~w~Прекрасно, прекрасно. Слушайте, парни, проходите внутрь, а я пока поговорю с нашим новым другом. + +[TM3_W] +~w~Я смотрю ты многое для нас сделал, парень... + +[TM4] +'ПЕРЕПОЛОХ В ЧАЙНАТАУНЕ' + +[TM4_A] +~w~Ах, это ты. Тони сейчас нет. + +[TM4_A2] +~w~Но он оставил тебе одно из своих любовных посланий. + +[TM4_B] +~w~Мы в состоянии ВОЙНЫ! Триада использует рыбный завод в качестве прикрытия. + +[TM4_C] +~w~Они проворачивают свои темные делишки на рыбном рынке в Чайнатауне. + +[TM4_D] +~w~Прачечная опять задолжала нам деньги. + +[TM4_E] +~w~Они считают, что за защиту лучше платить Триаде, так что придется их снова наказать. + +[TM4_F] +~w~Накажи этих умников, и нанеси визит боссам Триады! + +[TM4_G] +~w~Черт, если представится шанс, убей заодно и несколько их солдат. + +[TM4_GAT] +~g~Чтобы тебя пропустили, возьми фургон Триады. + +[TM5] +'ВЗРЫВ НА ЗАВОДЕ' + +[TM5_B] +~w~OK, мне надоело это дерьмо. + +[TM5_C] +~w~Нужно закончить разборки с Триадой раз и навсегда! + +[TM5_D] +Лысый установил бомбу в мусоровоз. + +[TM5_E] +~w~Бомба на таймере, так что много времени на размышления у тебя не будет. Бери машину. + +[TM5_F] +~w~Будь осторожен на дороге, Лысый сказал. что бомба может рвануть от любого столкновения! + +[TM5_G] +~w~Они откроют ворота для мусоровоза, так что ты без проблем проедешь на завод. + +[TM5_H] +~w~Припаркуй мусорку между двумя резервуарами с бензином и быстрее уноси ноги! + +[TM5_I] +~w~Пусть пойдет дождь из скумбрии. + +[TM5_J] +~w~Нам нужен библейский апокалипсис, а не какая-то дешевка. + +[TM_BUST] +Число ваших арестов + +[TM_DED] +Число визитов в больницу + +[TONI] +ЗАДАНИЯ ТОНИ + +[TONIGO] +~g~Тони повез свою мамочку в оперу. Зайди позже! + +[TONI_P] +У меня есть для тебя одно срочное дело! - Тони + +[TOWERS] +Хепберн Хейтс + +[TOYZ] +Фургон игрушек + +[TRAIN] +Поезд + +[TRAIN_1] +Станция Куровски + +[TRAIN_2] +Станция Ротвелл + +[TRAIN_3] +Станция Бейли + +[TRASH] +~g~Ты сильно помял свою тачку! Езжай и отремонтируй ее! + +[TRASHM] +Мусоровоз + +[TRINST] +Тройной безумный трюк + +[TSCORE] +ЗАРАБОТАНО: $~1~ + +[TSCORE2] +$~1~ + +[TTUTOR] +Чтобы заняться работой таксиста или отказаться от нее, нажми кнопку ~h~~k~~TOGGLE_SUBMISSIONS~~w~. + +[TTUTOR2] +Чтобы заняться работой таксиста или отказаться от нее, нажми кнопку ~h~~k~~TOGGLE_SUBMISSIONS~~w~. + +[TUBE1] +Как только откроется метро, ты сможешь на нем проехать на остров Стаунтон. + +[TUBE2] +Как только откроется Шорсайд Вейл, ты сможешь выйти на вокзале Шорсайд, чтобы попасть в аэропорт Фрэнсис. + +[TUBE_2] +чтобы сесть на электричку в метро - нажми ~h~~k~~VEHICLE_ENTER_EXIT~~w~. + +[TUNNEL] +Как только откроется туннель Портер, ты сможешь проехать на остров Стаунтон. + +[TUNNELP] +Туннель Портер + +[UNFRM1] +UnFormatMemCard 1 (teststuff) + +[UNIVERS] +Учебный городок + +[UPSIDE] +~r~Ты перевернул тачку! + +[USJ] +ПРИЗ ЗА ОСОБЫЙ ТРЮК! + +[USJI1] +TEXT NO LONGER REQUIRED + +[USJI2] +TEXT NO LONGER REQUIRED + +[USJI3] +TEXT NO LONGER REQUIRED + +[USJ_ALL] +ТЫ ИСПОЛНИЛ ВСЕ ОСОБЫЕ ТРЮКИ! + +[UZI_IN] +В магазин оружия завезли 'Узи'! + +[VANHSTP] +Решил вскрыть еще несколько Инкассаторов? Пригони их в наш гараж в гавани Портланда. + +[WANTED1] +~g~Стряхни копов с хвоста и понизь их заинтересованность в тебе! + +[WANT_A] +Тебя арестовывают лишь в том случае, если ты ~h~объявлен в розыск~w~. + +[WANT_B] +Величина ~h~уровня розыска~w~ отображается строкой звездочек в верхнем правом углу экрана. + +[WANT_C] +Сейчас твой ~h~уровень розыска~w~ равен одному... + +[WANT_D] +двум... + +[WANT_E] +трем... + +[WANT_F] +Чем выше твой ~h~уровень розыска~w~, тем больше сил будет брошено на твой арест. + +[WANT_G] +После ~h~ареста~w~ тебя отвозят в ближайший полицейский участок + +[WANT_H] +Копы забирают у тебя все оружие и часть денег в качестве взятки. + +[WANT_I] +Твое текущее задание будет считаться проваленным. + +[WANT_J] +Во время игры ты найдешь разные способы понижать ~h~уровень розыска~w~. + +[WANT_K] +~h~Уровень розыска~w~ сбрасывается после того, как машина будет ~h~ПЕРЕКРАШЕНА~w~. + +[WEATHE2] +ОБЫЧНАЯ ПОГОДА + +[WEATHER] +НЕНАСТЬЕ + +[WELCOME] +ДОБРО ПОЖАЛОВАТЬ + +[WHOOPEE] +М-р.Вупи + +[WIN_95] +Grand Theft Auto III }u ~ytu‚ ~t Windows 95 + +[WIN_DX] +Grand Theft Auto III ‚Ђuqѓu‚ DirectX ruЂЃxx }u }xvu 8.1 + +[WIN_RSZ] +]u ѓtp{~ЃЊ ѓЃ‚p}~rx‚Њ }~r~u ЂpwЂu€u}xu ЌzЂp}p + +[WIN_TTL] +Grand Theft Auto III + +[WIN_VDM] +Grand Theft Auto III ‚Ђuqѓu‚ }p rxtu~zpЂ‚u }u |u}uu 12\q p|Џ‚x + +[WRCONT] +The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto III requires an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). + +[WRCONTE] +The controller connected to controller port 1 is an unsupported controller. Grand Theft Auto III requires an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). + +[WRECKED] +~r~Машина разбита! + +[WRONGCD] +Неверный диск. Пожалуйста, вставьте нужный диск. + +[WRONGT1] +~g~За работой приходи между 05:00 и 21:00 + +[WRONGT2] +~g~За работой приходи между 06:00 и 14:00 + +[WRONGT3] +~g~За работой приходи между 15:00 и 00:00 + +[X] +x + +[Y1JLAST] +~r~Пришел последним! Ты болтаешь как водила, а водишь как болтун! + +[Y1_1ST] +~g~Ты пришел первым, пройдя ~1~ контрольных пунктов! + +[Y1_2ND] +~y~Ты пришел вторым, пройдя ~1~ контрольных пунктов. ~y~Неплохо, но ты не победил! + +[Y1_3RD] +~r~Ты пришел третьим, пройдя ~1~ контрольных пунктов. ~r~Я был о тебе более высокого мнения! + +[Y1_J1ST] +~y~Ты пришел первым, пройдя ~1~ контрольных пунктов. ~y~Хорошо, но ты должен быть лучшим, чтобы получить Королеву Лиззи! + +[Y1_J2ND] +~r~Ты пришел вторым, пройдя ~1~ контрольных пунктов. Ты водишь как бешеная макака! + +[Y1_LAST] +~r~Ты пришел последним! ~r~Ты просто тратишь мое время, ДУРАК! + +[Y1_TEST] +МАШИНА В ВОДЕ! + +[YAKUSA] +Торрингтон + +[YAKUZCR] +Стингер Якудзы + +[YANKEE] +Янки + +[YARDICR] +Лобо Ярди + +[YARDIE] +ЗАДАНИЯ ЯРДИ + +[YD1] +'БЫСТРЫЕ ТАЧКИ - ЛЕГКИЕ ДЕНЬГИ' + +[YD1GO] +~g~СТАРТ! + +[YD1_1] +~r~ОДИН + +[YD1_2] +~r~ДВА + +[YD1_3] +~r~ТРИ + +[YD1_A] +~w~Это король Куртни. + +[YD1_A1] +~w~Мои Ярди хотят устроить хорошую гонку, а я слышал, что ты крутой водила. + +[YD1_B] +~w~Приезжай на своей тачке на пустырь за стадионом и жди там других участников заезда. + +[YD1_BON] +$1000!! + +[YD1_C] +~w~Мои люди будут наблюдать за всеми контрольными точками. + +[YD1_CNT] +~1~ из 15! + +[YD1_D] +~w~Тот кто пройдет контрольный пункт первым - получит приз, и так далее. + +[YD1_D1] +~w~Если ты проедешь больше контрольных пунктов, чем мои ребята, я дам тебе работу. + +[YD1_E] +~g~Готовься к гонке! + +[YD1_F] +~g~Мощный старт - мне нравится твой стиль! + +[YD1_G] +~r~Это гонка на МАШИНАХ. Тебе нужна МАШИНА, кретин! + +[YD2] +'РЕЙД С 'УЗИ' + +[YD2_A] +~w~Посмотрим, сможешь ли ты сделать за меня грязную работу. + +[YD2_A1] +~w~Узнаем, можно ли тебе доверять. + +[YD2_B] +~w~Сейчас подойдут двое моих парней, и ты пойдешь покатаешься с ними, + +[YD2_B1] +~w~посмотрим, чего ты стоишь на самом деле. + +[YD2_C] +~w~Давай, покатайся по Хепберн Хейтс и прикончи нескольких Дьяволов - они надоедают королеве Лиззи. + +[YD2_CC] +~w~Вот, это тебе пригодится. + +[YD2_D] +~w~Вести и стрелять будешь ты, ну а мы проследим чтобы ты не скопытился. + +[YD2_E] +~w~Поехали! + +[YD2_F] +~r~Он имел что-то против нас, пореши этого ублюдка! + +[YD2_G1] +~w~Хепберн Хейтс...Давай ка прикончи несколько тупых Дьяволов... + +[YD2_G2] +~w~Но запомни, ~r~Из машины тебе выходить нельзя! + +[YD2_H] +~w~Ок, возвращаемся на нашу территорию! ВПЕРЕД! + +[YD2_L] +~w~Отличная работа, Жнец! + +[YD2_M] +~r~Он разбил мою тачку! Прикончи его! + +[YD2_N] +~w~У тебя 5 секунд, чтобы вернуться в тачку! + +[YD3] +'МАШИНЫ БАНД' + +[YD3_A] +Я хотел бы получить тачки некоторых банд + +[YD3_A1] +чтобы без помех провернуть дела на их территории. + +[YD3_B] +Мне нужен Сентинел Мафии, + +[YD3_B1] +Стингер Якудзы и + +[YD3_B2] +Жеребец Дьяволов, для маскировки под их людей. + +[YD3_C] +Отгони их в мой гараж в Ньюпорте, но запомни, + +[YD3_C1] +мне не нужны помятые тачки! + +[YD3_D] +Перебей номера + +[YD3_E] +~r~Ты уже своровал тачку Дьяволов! + +[YD3_F] +~r~Ты уже своровал тачку Мафии! + +[YD3_G] +~r~Ты уже своровал тачку Якудзы! + +[YD3_H] +~g~Тачка Дьяволов сворована! + +[YD3_I] +~g~Тачка мафии сворована! + +[YD3_J] +~g~Тачка Якудзы сворована! + +[YD3_K] +~r~Машина сильно помята! Отремонтируй ее! + +[YD3_L] +~g~Отгони ее в ~p~гараж~g~! + +[YD3_M] +~r~Ты перевернулся! Ищи другую тачку! + +[YD4] +'ПОДСТАВА' + +[YD4_1] +~g~Обкурившиеся психи! + +[YD4_2] +~g~Взорви фургоны камикадзе! + +[YD4_A] +Короче, слушай! + +[YD4_A1] +Отправляйся в Бедфорд Поинт. + +[YD4_A2] +Там у меня тайник в старой тачке, сгоняй за ней! + +[YD4_B] +ПИСЬМО: Говорят, ты нашел себе работу. Я тоже не сидела сложа руки. + +[YD4_C] +Пришло тебе время почувствовать тебе силу СПАНКа! Besos y fuderes, Каталина, xxx. + +[YD4_D] +PS: УМРИ, ПАРШИВЫЙ ПЕС, УМРИ! + +[YD_P] +С тобой хочет побазарить король Куртни. Подойди к телефону на Аспатрии! + +[YES] +Да + +[Z] +Значение по оси Z: ~1~ + +{ re3 updates } +{ new languages } +[FEL_JAP] +ЯПОНСКИЙ + +[FEL_POL] +ПОЛЬСКИЙ + +[FEL_RUS] +РУССКИЙ + +{ new display menus } +[FET_GFX] +НАСТРОЙКА ГРАФИКИ + +[FED_MIP] +МИП-МАППИНГ + +[FED_AAS] +СГЛАЖИВАНИЕ + +[FED_FIL] +ТЕКСТУРНАЯ ФИЛЬТРАЦИЯ + +[FED_BIL] +БИЛИНЕЙНАЯ + +[FED_TRL] +ТРИЛИНЕЙНАЯ + +[FED_WND] +ОКОННЫЙ + +[FED_FLS] +ПОЛНОЭКРАННЫЙ + +[FEM_CSB] +CUTSCENE BORDERS + +[FEM_SCF] +ФОРМАТ ОКНА + +[FEM_ISL] +MAP MEMORY USAGE + +[FEM_LOW] +LOW + +[FEM_MED] +MEDIUM + +[FEM_HIG] +HIGH + +[FEM_2PR] +PS2 ALPHA TEST + +[FEC_FRC] +СВОБОДНАЯ КАМЕРА + +{ Linux joy detection } +[FEC_JOD] +DETECT JOYSTICK + +[FEC_JPR] +Press any key on the joystick of your choice that you want to use on the game, and it will be selected. + +[FEC_JDE] +Detected joystick + +{ mission restart } +[FET_RMS] +ПОВТОРИТЬ ЗАДАНИЕ + +[FESZ_RM] +НАЧАТЬ ЗАНОВО? + +{ more graphics } +[FED_VPL] +VEHICLE PIPELINE + +[FED_PRM] +PED RIM LIGHT + +[FED_RGL] +ROAD GLOSS + +[FED_CLF] +COLOUR FILTER + +[FED_WLM] +WORLD LIGHTMAPS + +[FED_MBL] +MOTION BLUR + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MOBILE + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] { aspect ratio related } +АВТО + +{ controls } +[FEC_IVP] +ИНВЕРТИРОВАТЬ ВЕРТИКАЛЬНУЮ ОСЬ + +{ map } +[FEM_TWP] +Поставить метку + +[FEA_FMN] +РАДИО ВЫКЛ + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +КОНТРОЛЛЕР XBOX 360 + +[FEC_ONE] +КОНТРОЛЛЕР XBOX ONE + +[FEC_NSW] +КОНТРОЛЛЕР NINTENDO SWITCH + +[FEC_TYP] +ГЕЙМПАД + +[FEC_CCF] +КОНФИГУРАЦИЯ + +[FEC_CF1] +СХЕМА 1 + +[FEC_CF2] +СХЕМА 2 + +[FEC_CF3] +СХЕМА 3 + +[FEC_CF4] +СХЕМА 4 + +[FEC_CDP] +ТИП УПРАВЛЕНИЯ + +[FEC_ONF] +ПЕШКОМ + +[FEC_INC] +В МАШИНЕ + +[FEC_VIB] +ВИБРАЦИЯ + +[FET_AGS] +НАСТРОЙКИ ГЕЙМПАДА + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/utils/gxt/spanish.txt b/utils/gxt/spanish.txt new file mode 100644 index 0000000..2775d34 --- /dev/null +++ b/utils/gxt/spanish.txt @@ -0,0 +1,8177 @@ +{ + New strings are at the bottom of file. + Do not change the order of strings. + You can fix the typos of existing translation but please refrain from + unnecessary edits like rephasing because you think it suits better for your taste. + + SPANISH NOTE: + This is European Spanish, do not mix it with Latin American Spanish. + If you want the Latin American Spanish translation you'd have to create + a separate txt for it. +} + +{ Grand Theft Auto III Spanish (Spain) Translation } +{ Contains some of the official fixes made by Rockstar for the iOS port } +{ Additional translation rewrites, corrections and fixes by IlDucci } + +[LETTER1] +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"$,.'-?!!SDBF + +[DEFNAM] +Claude---------------------- + +[IN_VEH] +~g~¡Oye! ¡Vuelve al vehículo! + +[IN_VEH2] +~g~¡Necesitas un coche para realizar este trabajo! + +[IN_BOAT] +~g~¡Necesitas una lancha para realizar este trabajo! + +[HEY] +~g~¡No te vayas por tu cuenta! ¡Mantén a tu grupo unido! + +[HEY2] +~g~¡No te separes del grupo! + +[HEY3] +~g~¡Has perdido a tu colega! ¡Vuelve a por 8-Ball! + +[HEY4] +~g~¡Si pierdes a Misty, Luigi te linchará! ¡Ve y recógela! + +[HEY5] +~g~Una de las chicas se ha escapado, ¡vuelve y recógela! + +[HEY6] +~g~Abandonaste tu honor con el kanbu de la yakuza. ¡Debes protegerlo! + +[HEY7] +~g~Te vendría bien una ayudita. ¡Vuelve y encuentra a tu contacto! + +[HEY8] +~g~Protección significa exactamente eso: ¡Protege al anciano oriental! + +[HEY9] +~g~¿Sabes lo que tienes que hacer? ¡Ver a tu contacto! + +[HELP2_A] +Pulsa el ~h~botón /~w~ mientras corres para ~h~esprintar~w~. + +[HELP3] +Sólo puedes esprintar durante cortos periodos de tiempo antes de cansarte. + +[HELP4_A] +Pulsa ~h~~k~~VEHICLE_ACCELERATE~~w~ para ~h~acelerar~w~. + +[HELP4_D] +Mueve el~h~ joystick analógico derecho~w~ hacia arriba para ~h~acelerar~w~. + +[HELP5_A] +Pulsa ~h~~k~~VEHICLE_BRAKE~~w~ para ~h~frenar~w~ o para ~h~dar marcha atrás~w~ si el vehículo está detenido. + +[HELP5_D] +Mueve el ~h~joystick analógico derecho~w~ hacia atrás para ~h~frenar~w~ o para ~h~dar marcha atrás~w~ si el vehículo está detenido. + +[HELP6_A] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~ ~w~para utilizar el ~h~freno de mano del vehículo~w~. + +[HELP6_C] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~ ~w~para utilizar el ~h~freno de mano del vehículo~w~. + +[HELP6_D] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~ ~w~para utilizar el ~h~freno de mano del vehículo~w~. + +[HELP7_A] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar~w~ con el fusil de francotirador. + +[HELP7_D] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar~w~ con el fusil de francotirador. + +[HELP8_A] +Pulsa ~h~~k~~PED_SNIPER_ZOOM_IN~ ~w~para ~h~aumentar el zoom ~w~de la mira del fusil y ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ para ~h~reducirlo~w~. + +[HELP9_A] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar ~w~el fusil de francotirador. + +[HELP10] +Esta insignia indica que la policía te está buscando. + +[HELP11] +Cuantas más insignias tengas, mayor será tu nivel de búsqueda. + +[HELP13] +En ocasiones necesitarás ir por caminos que no aparecen en el radar. + +[TIMER] +Ésta es una misión cronometrada, debes completarla antes de que el cronómetro llegue a cero. + +[MISTY1] +~r~¡Misty está para el arrastre! + +[OUT_VEH] +~g~¡Sal del vehículo! + +[GARAGE] +Mete el vehículo dentro del garaje y sal andando. + +[WANTED1] +~g~¡Despista a la poli y pierde tu nivel de búsqueda! + +[NODOORS] +~g~¡No son sardinas! Consigue un vehículo con suficientes asientos. + +[TRASH] +~g~¡Has dejado tu vehículo hecho unos zorros! ¡Haz que lo reparen! + +[WRECKED] +~r~¡El vehículo está destrozado! + +[HORN] +~g~Toca el claxon. + +[HORN4] +Pulsa el ~h~botón L3~w~ para tocar el ~h~claxon. + +[NOMONEY] +~g~¡Necesitas más pasta! + +[OUTTIME] +~r~¡Demasiado lento, tío, demasiado lento! + +[SPOTTED] +~r~¡Te siguen la pista! + +[REWARD] +RECOMPENSA: ~1~ $ + +[GAMEOVR] +FIN DE LA PARTIDA + +[Z] +Valor del eje Z: ~1~ + +[M_FAIL] +¡MISIÓN FALLIDA! + +[M_PASS] +¡MISIÓN SUPERADA! ~1~ $ + +[O_PASS] +¡TRABAJO ESPORÁDICO SUPERADO! + +[O_FAIL] +¡TRABAJO ESPORÁDICO FRACASADO! + +[DEAD] +¡ELIMINADO! + +[BUSTED] +¡TRINCADO! + +[S_PROMP] +Cuando no estés en una misión, podrás ~h~guardar tu partida aquí~w~. Esto hará avanzar el tiempo seis horas. + +[NUMBER] +~1~ + +[SCORE] +~1~ $ + +[LOADCAR] +CARGANDO VEHÍCULO... (PULSA EL BOTÓN L1 PARA CANCELAR) + +[CARSOFF] +Coches activados. + +[CARS_ON] +Coches desactivados. + +[TEXTXYZ] +Escribiendo coordenadas al archivo... + +[CHEATON] +Trucos ACTIVADOS + +[CHEATOF] +Trucos DESACTIVADOS + +[UZI_IN] +¡La Uzi ya está disponible en la tienda Ammu-Nation! + +[IMPORT1] +Sal y espera a tu vehículo. + +[PAGEB1] +Pistola entregada en tu guarida. + +[PAGEB2] +Uzi entregada en tu guarida. + +[PAGEB3] +Chaleco antibalas entregado en tu guarida. + +[PAGEB4] +Escopeta entregada en tu guarida. + +[PAGEB5] +Granadas entregadas en tu guarida. + +[PAGEB6] +Molotovs entregados en tu guarida. + +[PAGEB7] +AK47 entregado en tu guarida. + +[PAGEB8] +Fusil de francotirador entregado en tu guarida. + +[PAGEB9] +M16 entregado en tu guarida. + +[PAGEB10] +Lanzacohetes entregado en tu guarida. + +[PAGEB11] +Lanzallamas entregado en tu guarida. + +[WANT_A] +Sólo serás arrestado si tienes un ~h~nivel de búsqueda~w~. + +[WANT_B] +Tu ~h~nivel de búsqueda~w~ está representado por una hilera de estrellas en la parte superior derecha de la pantalla. + +[WANT_C] +Ahora tienes un ~h~nivel de búsqueda~w~ de una estrella... + +[WANT_D] +dos... + +[WANT_E] +tres... + +[WANT_F] +A medida que aumente tu ~h~nivel de búsqueda~w~, serás perseguido por fuerzas con una mayor potencia de fuego. + +[WANT_G] +Si te ~h~trinca~w~ la poli, te llevarán a la comisaría más cercana. + +[WANT_H] +Los agentes te requisarán todas tus armas y se quedarán con parte de tu dinero como soborno. + +[WANT_I] +Fracasarás cualquier misión que estuvieses llevando a cabo. + +[WANT_J] +A medida que avances en el juego, encontrarás formas de reducir tu nivel de búsqueda. + +[WANT_K] +Si estás en un coche, los ~h~TALLERES DE PINTURA~w~ te quitarán ~h~tu nivel de búsqueda~w~. + +[HEAL_B] +Cuando seas ~h~eliminado~w~, te llevarán al hospital más cercano. + +[HEAL_C] +Te quitarán tus armas y los médicos te cobrarán un dinero por curarte. + +[HEAL_E] +A medida que avances en el juego, encontrarás modos de curarte o de protegerte. + +[DAM] +DAÑO: + +[KILLS] +MUERTES: + +[FARES] +CARRERAS: + +[BULL] +LINGOTES: + +[EVID] +PRUEBAS: + +[HEALTH] +ESTADO DEL COCHE: + +[COLLECT] +RECOGIDOS: + +[BOMB] +Mete tu vehículo en la tienda de ~h~bombas~w~ para poner una. Coste: ~h~1.000 $~w~. + +[SAVE1] +Pasa por la puerta para ~h~guardar la partida~w~. No podrás guardar durante una misión. + +[SAVE2] +Cualquier vehículo que dejes en este garaje se conservará al guardar la partida. + +[AMMU] +Entra en la tienda Ammu-Nation para comprar armas. + +[BRIDGE1] +Podrás conducir hasta Staunton Island cuando el puente Callahan esté reparado. + +[TUNNEL] +Podrás conducir hasta Staunton Island cuando el túnel Porter esté abierto. + +[LUIGI] +MISIONES DE LUIGI + +[TONI] +MISIONES DE TONI + +[JOEY] +MISIONES DE JOEY + +[FRANK] +MISIONES DE SALVATORE + +[DIABLO] +MISIONES DE DIABLO + +[ASUKA] +MISIONES DE ASUKA + +[B_SITE] +MISIONES SUBURBANAS DE ASUKA + +[KENJI] +MISIONES DE KENJI + +[RAY] +MISIONES DE RAY + +[LOVE] +MISIONES DE LOVE + +[YARDIE] +MISIONES DE JAMAICANOS + +[HOOD] +MISIONES DE HOOD + +[CITYZON] +Liberty City + +[IND_ZON] +Portland + +[PORT_W] +Callahan Point + +[PORT_S] +Muelle Atlantic + +[PORT_E] +Puerto de Portland + +[PORT_I] +Trenton + +[S_VIEW] +Portland View + +[CHINA] +Chinatown + +[EASTBAY] +Playa de Portland + +[LITTLEI] +Saint Mark's + +[REDLIGH] +Red Light District + +[TOWERS] +Cerros de Hepburn + +[HARWOOD] +Harwood + +[ROADBR1] +Puente Callahan + +[ROADBR2] +Puente Callahan + +[TUNNELP] +Túnel Porter + +[BOMB1] +Taller de 8-Ball + +[COM_ZON] +Staunton Island + +[STADIUM] +Aspatria + +[HOSPI_2] +Rockford + +[UNIVERS] +Campus de Liberty + +[CONSTRU] +Fort Staunton + +[PARK] +Parque Belleville + +[COM_EAS] +Newport + +[SHOPING] +Bedford Point + +[YAKUSA] +Torrington + +[SUB_ZON] +Shoreside Vale + +[AIRPORT] +Aeropuerto Int. Francis + +[PROJECT] +Wichita Gardens + +[SUB_IND] +Pike Creek + +[SWANKS] +Cedar Grove + +[BIG_DAM] +Presa Cochrane + +[SUB_ZO2] +Shoreside Vale + +[SUB_ZO3] +Shoreside Vale + +[CAR_1] +Ambulancia + +[CAR_2] +Camión de bomberos + +[CAR_3] +Coche patrulla + +[CAR_4] +Enforcer + +[CAR_5] +Barracks + +[CAR_6] +Rhino + +[CAR_7] +Coche del FBI + +[CAR_8] +Securicar + +[CAR_9] +Moonbeam + +[CAR_10] +Coach + +[CAR_11] +Flatbed + +[CAR_12] +Linerunner + +[CAR_13] +Trashmaster + +[CAR_14] +Patriot + +[CAR_15] +Mr. Whoopee + +[CAR_16] +Mule + +[CAR_17] +Yankee + +[CAR_18] +Pony + +[CAR_19] +Bobcat + +[CAR_20] +Rumpo + +[CAR_21] +Blista + +[CAR_22] +Dodo + +[CAR_23] +Autobús + +[CAR_24] +Sentinel + +[CAR_25] +Cheetah + +[CAR_26] +Banshee + +[CAR_27] +Stinger + +[CAR_28] +Infernus + +[CAR_29] +Esperanto + +[CAR_30] +Kuruma + +[CAR_31] +Stretch + +[CAR_32] +Perennial + +[CAR_33] +Landstalker + +[CAR_34] +Mañana + +[CAR_35] +Idaho + +[CAR_36] +Stallion + +[CAR_37] +Taxi + +[CAR_38] +Cabbie + +[CAR_39] +Buggy + +[LUIGIS] +Club de Luigi + +[GOAWAY] +~g~¡Ya estás en una misión! + +[LUIGGO] +~g~Luigi está entrevistando a unas chicas nuevas... ¡Vuelve más tarde! + +[JOEYGO] +~g~Joey ha salido con Misty. ¡Pásate más tarde! + +[TONIGO] +~g~Toni ha llevado a su mamá a la ópera... ¡Vente en otro momento! + +[KEMUGO] +~g~María y Kemuri están muy liadas... ¡Pásate más tarde! + +[KENJGO] +~g~Kenji está reunido con la yakuza, ¡ven en otro momento! + +[RAYGO] +~g~Ray tiene otros baños por los que rondar, ¡ven en otro momento! + +[LOVEGO] +~g~Donald Love tiene otros asuntos que atender. ¡Pide una cita más tarde! + +[KENSGO] +~g~¡Kenji está ocupado! ¡Llama más tarde! + +[ASUSGO] +~g~¡Asuka no está disponible en este momento! + +[HOODGO] +~g~¡Los Hoods no pueden atenderte en este momento! + +[WRONGT1] +~g~Vuelve entre las 05:00 y las 21:00 para buscar trabajo. + +[WRONGT2] +~g~Vuelve entre las 06:00 y las 14:00 para buscar trabajo. + +[WRONGT3] +~g~Vuelve entre las 15:00 y las 00:00 para buscar trabajo. + +[GUN_1A] +Pulsa ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ y ~h~~k~~PED_CYCLE_WEAPON_LEFT~ ~w~para cambiar de arma. + +[GUN_2A] +¡Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar automáticamente~w~ y pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar! Intenta darle a las dianas... + +[GUN_2C] +¡Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar automáticamente~w~ y pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar! Intenta darle a las dianas... + +[GUN_2D] +¡Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar automáticamente~w~ y pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar! Intenta darle a las dianas... + +[GUN_3A] +Mientras mantienes pulsado ~h~~k~~PED_LOCK_TARGET~,~w~ pulsa ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ o ~h~~k~~PED_CYCLE_TARGET_RIGHT~ para cambiar de objetivo. + +[GUN_3B] +Mientras mantienes pulsado ~h~~k~~PED_LOCK_TARGET~,~w~ pulsa ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ o ~h~~k~~PED_CYCLE_TARGET_RIGHT~ para cambiar de objetivo. + +[GUN_4A] +Mientras mantienes pulsado ~h~~k~~PED_LOCK_TARGET~~w~, puedes moverte mientras sigues apuntando a tu objetivo. + +[GUN_4B] +Mientras mantienes pulsado ~h~~k~~PED_LOCK_TARGET~~w~, puedes moverte mientras sigues apuntando a tu objetivo. + +[GUN_5] +Puedes practicar disparando a estas dianas. Vuelve a la misión cuando hayas terminado. + +[TAXI1] +~g~Busca un cliente. + +[FARE1] +~g~Ve al ~w~club Sex Kitten Meeouch ~g~en el Red Light District. + +[FARE2] +~g~Ve a ~w~Supa Save ~g~en Portland View. + +[FARE3] +~g~Ve a ~w~la sala Clásica ~g~en Chinatown. + +[FARE4] +~g~Ve a la ~w~cafetería Greasy Joe ~g~en Callahan Point. + +[FARE5] +~g~Ve a la ~w~tienda de armas Ammu-Nation ~g~en el Red Light District. + +[FARE6] +~g~Ve a ~w~Easy Credit Autos ~g~en Saint Mark's. + +[FARE7] +~g~Ve al ~w~bar de topless de Woody ~g~en el Red Light District. + +[FARE8] +~g~Ve al ~w~restaurante Marcos ~g~en Saint Mark's. + +[FARE9] +~g~Ve al ~w~taller de importación y exportación ~g~en el puerto de Portland. + +[FARE10] +~g~Ve a ~w~Punk Noodles ~g~en Chinatown. + +[FARE12] +~g~Ve al ~w~estadio de fútbol ~g~en Aspatria. + +[FARE13] +~g~Ve a la ~w~iglesia ~g~de Bedford Point. + +[FARE14] +~g~Ve al ~w~casino ~g~de Torrington. + +[FARE15] +~g~Ve a la ~w~universidad ~g~en el Campus de Liberty. + +[FARE16] +~g~Ve al ~w~centro comercial ~g~en la zona del parque Belleville. + +[FARE17] +~g~Ve al ~w~museo ~g~de Newport. + +[FARE18] +~g~Ve al ~w~edificio de AmCo ~g~en Torrington. + +[FARE19] +~g~Ve a ~w~Bolt Burgers ~g~en Bedford Point. + +[FARE20] +~g~Ve al ~w~parque ~g~de Belleville. + +[FARE21] +~g~Ve al ~w~Aeropuerto Internacional Francis. + +[FARE22] +~g~Ve a la ~w~presa Cochrane~g~. + +[FARE24] +~g~Ve al ~w~hospital ~g~de Pike Creek. + +[FARE25] +~g~Ve al ~w~parque ~g~de Shoreside Vale. + +[FARE26] +~g~Ve a las ~w~Torres del Noroeste ~g~de Wichita Gardens. + +[NEW_TAX] +¡MÁS GRANDES! ¡MÁS RÁPIDOS! ¡MÁS DUROS! Los nuevos taxis Borgnine abren sus puertas en Harwood. ¡Llame hoy al 555-BORGNINE! + +[TSCORE2] +~1~ $ + +[IN_ROW] +¡Racha de ~1~ clientes! Premio de ~1~ $. + +[TTUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de taxi. + +[TTUTOR2] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de taxi. + +[ATUTOR2] +~g~Lleva a los pacientes al hospital CON CUIDADO. Cada golpe reducirá sus posibilidades de supervivencia. + +[A_TIME] ++~1~ segundos + +[A_FULL] +~r~¡Ambulancia llena! + +[A_RANGE] +~g~La radio de la ambulancia está fuera de cobertura, ¡acércate a un hospital! + +[FTUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones del camión de bomberos. + +[FTUTOR2] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones del camión de bomberos. + +[F_PASS1] +¡Fuego apagado! + +[F_RANGE] +~g~La radio del camión de bomberos está fuera de cobertura, ¡acércate a una estación de bomberos! + +[C_BREIF] +~g~Sospechoso visto por última vez en: ~a~. + +[C_RANGE] +~g~La radio de la policía está fuera de cobertura, ¡acércate a una comisaría! + +[DODO_FT] +¡Has volado durante ~1~ segundos! + +[EBAL_A] +Conozco un lugar en las afueras del Red Light District donde podemos escondernos, + +[EBAL_A1] +pero mis manos están destrozadas, así que conduce tú, hermano. + +[EBAL_1] +Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un vehículo. + +[EBAL_1B] +Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un vehículo. + +[EBAL_2] +~g~¡Vuelve al coche! + +[EBAL_3] +Éste es el ~h~radar~w~. Utilízalo para guiarte por la ciudad. ¡Ve hasta la ~h~señal~w~ del ~h~radar~w~ para encontrar el escondite! + +[EBAL_D] +Conozco a un tipo con contactos, se llama Luigi. + +[EBAL_D1] +Somos viejos amigos, así que podremos buscarte curro. Vamos a verlo. + +[EBAL_E] +Venga, vamos a pasarnos por allí y te presentaré. + +{ UNUSED } + +[EBAL_I] +El jefe os recibirá enseguida... + +[EBAL_J] +8-Ball tiene cosas que hacer arriba. + +[EBAL_K] +Quizá puedas hacerme un favor. + +[EBAL_L] +Una de mis chicas necesita que la lleven, así que consigue un coche, recoge a Misty en la clínica y tráela aquí. + +[EBAL_N] +¡Así que no quites las manos del volante! + +[EBAL_4] +~r~¡8-Ball ha muerto! + +[EBAL_5] +~g~¡Consigue un vehículo! + +[EBAL_6] +~g~¡Recoge a Misty! + +[LM1] +'LAS CHICAS DE LUIGI' + +[LM2] +'NO DROGUES A MI ZORRA' + +[LM3] +'LLEVA A MISTY POR MÍ' + +[LM5] +'EL BAILE DE LA POLICÍA' + +[LM1_2] +~g~Lleva a Misty al club de Luigi. + +[LM1_3] +~g~Toca el claxon para que la chica entre en el coche. + +[LM1_6] +~g~¡Vuelve al coche! + +[LM1_7] +Para el vehículo junto a Misty y déjala que suba. + +[LM1_8] +Puedes ir a ver a Luigi para que te dé más trabajo o explorar Liberty City. + +[LM2_A] +Hay una nueva droga en la ciudad llamada SPANK. + +[LM2_E] +Algún listillo ha pasado esta basura a mis chicas en el puerto de Portland. + +[LM2_B] +¡Ve y repásale la cara con un bate de béisbol! + +[LM2_G] +¡Quiero una compensación por este insulto! + +[LM2_1] +~g~Llévate su coche y repíntalo. + +[LM2_2A] +¡Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~dar puñetazos~w~, ~h~patadas~w~ o ~h~pegar ~w~con el bate! + +[LM2_2C] +¡Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~dar puñetazos~w~, ~h~patadas~w~ o ~h~pegar ~w~con el bate! + +[LM2_2D] +¡Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~dar puñetazos~w~, ~h~patadas~w~ o ~h~pegar ~w~con el bate! + +[LM2_3] +~g~¡Mete el coche en el garaje de Luigi! + +[LM2_4] +~g~¡Vuelve a pintar el coche! + +[LM3_A] +Hola, tengo que hablar contigo... Bueno, Mick, ya hablaremos. + +[LM3_B] +¿Cómo te va, chico? + +[LM3_C] +El hijo del Don, Joey Leone, quiere fiesta con su chica de siempre, Misty. + +[LM3_D] +Recógela en los cerros de Hepburn, + +[LM3_E] +pero cuidado, ese es territorio de los Diablos. + +[LM3_F] +Luego llévala rapidito hasta su taller en Trenton, + +[LM3_H] +¡así que fíjate en el camino y no en Misty! + +[LM3_1D] +Pulsa ~h~~k~~VEHICLE_HORN~~w~ para tocar el ~h~claxon~w~ y avisar a Misty. + +[LM3_2] +~g~Lleva a Misty al local de Joey. + +[LM3_4] +~g~¡Recoge a Misty! + +[LM3_5] +Ahora trabajas para Luigi, ¿no? ¡Ya era hora de que tuviese un conductor de confianza! + +[LM3_7] +Estaré contigo en un momento, bujía mía. + +[LM3_10] +~g~¡Consigue un vehículo! + +[LM4_B] +Resuelve esto por mí. + +[LM4_C] +Si necesitas un arma, ve a la parte de atrás del Ammu-Nation que está enfrente del metro. + +[LM5_A] +El baile de la policía se está celebrando en la sala Clásica, cerca del puente, + +[LM5_B] +y parece que tienen ganas de una fiesta más ''clásica''. + +[LM5_C] +Tengo chicas haciendo la calle. + +[LM5_D] +Llévalas al baile, sacarán una buena tajada. + +[LM5_1] +~g~¡Tienes a las chicas tan embutidas que les van a salir moratones! Primero deja a las que llevas y luego ve a por más. + +[LM5_2] +~r~¡Una chica de Luigi la ha espichado! + +[LM5_3] +~g~¡Necesitas un coche! + +[LM5_4] +~g~Recoge a las chicas que trabajan en Saint Mark's. + +[LM5_5] +~g~¡Lleva a las chicas al baile de la policía! + +[LM5_8] +~g~Chicas trabajando en el baile: ~1~ + +[JM2] +'DESPEDIDA A LEE CHONG ''EL GORDO''' + +[JM4] +'EL CHÓFER DE CIPRIANI' + +[JM5] +'FIAMBRE EN EL MALETERO' + +[JM1_1] +~g~Lleva el coche de Forelli al taller que tiene 8-Ball al norte, tras Easy Credit Autos. + +[JM1_2] +~g~Vuelve a aparcar el coche frente al restaurante de Marco. + +[JM1_3] +~g~¡Activa la bomba del coche y luego sal de ahí! + +[JM1_4] +~g~¡Te estás cargando el coche! ¡Repáralo! + +[JM1_5] +~g~¡La bomba del coche no está activada! + +[JM1_6] +~g~Vuelve a aparcar el coche en el sitio correcto. + +[JM1_8A] +~y~¡Ey, pero si es mi colega! + +[JM1_8B] +~y~El taller de bombas está automatizado. Sólo tienes que meter el coche, pararlo y el taller hará el resto. + +[JM1_8C] +~y~Ten, tu primer coche es gratis, pero los siguientes te los cobraré. + +[JM2_A] +Lee Chong ''el Gordo'' está traficando SPANK para una nueva banda de Colombia, o Colorado... O como se llame. + +[JM2_B] +No me acuerdo. El caso es que no importa. + +[JM2_D] +Ese cabrito ha vendido su último fideo. + +[JM2_E] +¡Lo quiero muerto! + +[JM2_G] +Búscate un nueve, queda claro, ¿no? + +[JM2_H] +Y recuerda, ve con ojo en Chinatown: es territorio de las Tríadas. + +[JM3_A] +Vamos a robar un furgón blindado. + +[JM3_B] +Sale de Chinatown todos los días. + +[JM3_C] +Las balas ni siquiera abollarán el furgón, así que coge un coche y échalo de la carretera. + +[JM3_D] +Si lo zurras bien, los imbéciles de los guardias se achantarán. + +[JM3_E] +Luego llévalo al almacén de los muelles y mis chicos se encargarán del resto. + +[JM3_F] +El furgón no estará fuera todo el día, así que no pierdas el tiempo. + +[JM3_1] +~g~Lleva el furgón al almacén. + +[JM3_2] +~g~Carga contra el furgón hasta que su daño esté por debajo del 70 por ciento. + +[JM4_B] +¡Ah, aquí está el tío que te decía! + +[JM4_C] +Bueno, éste no es italiano ni tampoco mecánico, pero sabe arreglar cosas. + +[JM4_D] +Él es el capo de papá, Toni Cipriani. + +[JM4_E] +Sí, soy Toni Cipriani. + +[JM4_F] +Llévale al restaurante de Mamma en Saint Mark's, ¿va? + +[JM4_G] +Oye, estoy planeando un curro que necesita un buen conductor, pásate por aquí más tarde, ¿estamos? + +[JM4_2] +¡Espera aquí! Deja el motor en marcha. Ésta no es una visita social. + +[JM4_3] +¡Es una emboscada de las Tríadas! ¡Sácanos de aquí, chico! + +[JM4_4] +Las Tríadas creen que pueden meterse conmigo. ¡Las Tríadas, CONMIGO! + +[JM4_6] +¡Mi coche! Dije que fueras con cuidado. + +[JM4_7] +~g~Lleva a Toni al restaurante de Mamma. + +[JM4_8] +~r~¡Toni la ha palmado! + +[JM5_A] +Guapo, muy guapo... + +[JM5_B] +¡Justo te estaba buscando! + +[JM5_D] +Uno de los Forelli se las daba de mafioso y se llevó su merecido. + +[JM5_E] +Lleva el cuerpo a la trituradora en Harwood, ¿vale? + +[JM5_1] +~g~¡Llévalo a la trituradora! + +[JM5_2] +~g~¡Son los hermanos Forelli! + +[JM6_A] +Vaya cochazo, ¿eh? + +[JM6_B] +Bueno, escucha. Lleva un coche a la casa franca de Saint Mark's y recoge a unos colegas. + +[JM6_C] +Van a atracar un banco y necesitan un conductor. + +[JM6_D] +Di mi palabra de que eras su hombre, así que no la cagues. + +[JM6_E] +Llévalos al banco antes de las cinco en punto, ni un minuto después. + +[JM6_2] +Mantén el motor en marcha, entraremos y saldremos enseguida. + +[JM6_3] +¡Sácanos de aquí! + +[JM6_4] +¡Líbrate de la poli y llévanos a la casa franca! + +[JM6_6] +~g~¡Busca un vehículo menos llamativo! + +[JM6_7] +~g~¡Necesitas a los 3 para robar el banco! + +[TM1] +'SACANDO LA COLADA' + +[TM2] +'LA ENTREGA' + +[TM3] +'SALVATORE HA CONVOCADO UNA REUNIÓN' + +[TM4] +'TRÍADAS Y TRIBULACIONES' + +[TM5] +'LLUVIA DE PECES' + +[TONI_P] +¡Tengo un trabajo urgente para ti! -Toni + +[TM1_A] +Siéntate, chico, siéntate de una vez. + +[TM1_B] +La lavandería no quiere pagar la protección, ¿eh? + +[TM1_C] +¿Las Tríadas creen que pueden meterse conmigo? + +[TM1_D] +¡Se van a enterar esos aspirantes a tipos duros de lo que es ser un tipo duro! + +[TM1_E] +¡Eso, enséñales respeto! ¡Las Tríadas no se ríen de ninguno de mis hijos! + +[TM1_F] +Tu padre, Dios acoja su alma, no toleraba ni media a las Tríadas en Sicilia. + +[TM1_G] +Lo siento, mamá. Sí, mamá. + +[TM1_H] +Quiero que te cargues sus furgonetas de lavandería + +[TM1_I] +y a cualquiera de las Tríadas que se cruce en tu camino. + +[TM1_J] +8-Ball te dará lo que necesites. + +[TM2_A] +Toni ha salido a partir cabezas, o a intentarlo... + +[TM2_AA] +Nunca será tan duro como su padre. Te dejó una nota en la mesa. + +[TM2_B] +La lavandería ya quiere pagar. ¡Buen trabajo, chico! + +[TM2_C] +Ve a por el dinero y tráelo aquí. Cuidado con las Tríadas. + +[TM2_D] +Puede que intenten metértela doblada, pero no te dejes. + +[TM2_E] +¡Nadie, quiero decir nadie, se mete con Toni Cipriani! + +[TM2_1] +~g~¡Lleva el dinero a Toni! + +[TM2_2] +~g~¡Los dejaste tiesos a todos! + +[TM3_MA] +¡No sé dónde está! + +[TM3_MB] +Te juro que mi hijo a veces no sabe lo que hace. + +[TM3_MC] +Ahora bien, su padre era diferente. Siempre en la cumbre, pendiente de todo, valiente... + +[TM3_A] +Don Salvatore ha convocado una reunión. + +[TM3_B] +Necesito que vayas a por la limusina y su hijo, Joey, a su taller. + +[TM3_C] +Luego recoge a Luigi en su club, ven a buscarme, + +[TM3_D] +y nos iremos todos juntos a la casa del jefe. + +[TM3_E] +Los de las Tríadas no saben cuándo parar... + +[TM3_F] +Si quieren guerra, la tendrán. + +[TM3_G] +Ponte en marcha. + +[TM3_1] +~g~Ve al taller de Joey a por la limusina. + +[TM3_2] +~g~Recoge a Luigi. + +[TM3_3] +~g~Ahora recoge a Toni. + +[TM3_4] +~g~Lleva a los mafiosos a la casa de Salvatore. + +[TM3_5] +~y~¡Es una emboscada de las Tríadas! + +[TM4_B] +¡Esto es la guerra! Las Tríadas tienen una piscifactoría como tapadera. + +[TM4_C] +Casi todos sus negocios pasan por la lonja de Chinatown. + +[TM4_D] +La lavandería todavía no ha pagado la protección. + +[TM4_E] +Creen que está protegida por las Tríadas, así que creo que se merecen un castigo. + +[TM4_F] +¡Llévate a estos chicos y elimina a los líderes de las Tríadas! + +[TM4_G] +Qué narices, si ves la oportunidad, cárgate también a unos cuántos de sus soldados. + +[TM4_GAT] +~g~Necesitas un camión de pescado de las Tríadas para entrar. + +[TM5_B] +Vale, ya me he hartado de esta mierda. + +[TM5_C] +¡Vamos a acabar con las Tríadas en Liberty de una vez por todas! + +[TM5_D] +8-Ball ha instalado una bomba en un camión de la basura. + +[TM5_E] +Lleva un temporizador, así que si te despistas, no quedará nada de ti. Ve a por el camión. + +[TM5_F] +¡Cuidado, 8-Ball dice que es muy sensible y que cualquier golpe puede hacerlo estallar! + +[TM5_G] +Su piscifactoría abrirá sus puertas a uno de sus camiones, así que podrás entrar sin más. + +[TM5_H] +¡Aparca entre los tanques de gas y lárgate de ahí! + +[TM5_I] +Quiero que llueva pescado. + +[TM5_J] +En plan bíblico, nada de bajo presupuesto. + +[FM2] +'CORTANDO LA HIERBA' + +[FM4] +'ÚLTIMA VOLUNTAD' + +[FM1_A] +Mis chicos y yo queremos hablar de negocios, + +[FM1_B] +así que esta tarde vas a cuidar de mi chica. + +[FM1_C] +¡OYE, MARÍA! ¡MUEVE ESE CULO! + +[FM1_D] +La muy idiota siempre hace lo mismo. + +[FM1_E] +Y aquí está, ¡la primera y única reina de Saba! + +[FM1_F] +¿Qué hacías arriba? + +[FM1_G] +Fuera lo que fuera, seguro que me cuesta dinero. + +[FM1_H] +Bueno, no pensarás que estoy aquí para conversar, ¿o sí? + +[FM1_I] +Métete en el coche y cierra el pico. + +[FM1_J] +Llévate la limusina, pero devuélvela de una pieza, ¿me has oído? + +[FM1_K] +Y cuidado con ella, es una lianta. + +[FM1_L] +¡Sí, sí, sí! Seguro que tu nuevo perrito faldero está pendiente de todo. + +[FM1_M] +¿No es fuerte y grande? + +[FM1_N] +¡Oye, tú, vamos a ver a Chico para que nos dé unas pirulas! + +[FM1_P] +~g~Ése de ahí es Chico, párate cerca. + +[FM1_S] +Aquí tienes, señorita. + +[FM1_TT] +¡UNA REDADA! + +[FM1_1] +~g~¡Vuelve al Stretch! + +[FM1_2] +~g~¡Entra en el Stretch! + +[FM1_3] +~r~Si abandonas a María, Salvatore hará que te maten. Vuelve a por ella. + +[FM1_4] +~g~¡Has dejado a la chica del Don tirada! ¡Vuelve al almacén y espera a María! + +[FM1_5] +~g~¡Lleva a María sana y salva con Salvatore! + +[FM1_6] +~g~¡Chico no estará esperando todo el día, lleva a María al muelle! + +[FM1_7] +~r~¡María está muerta! A Salvatore le va a sentar muy mal... + +[FM1_8] +~r~¡Te cargaste al camello de María! + +[FM2_J] +Déjanos a solas un momento. + +[FM2_A] +El cártel colombiano fabrica SPANK en Liberty, + +[FM2_K] +pero no sabemos dónde y parece como si supieran qué vamos a hacer antes de que lo pensemos. + +[FM2_L] +Hay un tipo llamado Curly Bob que trabaja en el bar de Luigi. + +[FM2_M] +Ha estado gastando más dinero del que gana. + +[FM2_N] +Suele ir del trabajo a casa en taxi, así que síguelo. + +[FM2_O] +Y si nos está vendiendo..., mátalo. + +[FM2_F] +Aquí viene nuestro amiguito. El señor Bocón en persona. + +[FM2_G] +¿Te siguieron? Ya sabes que esto es nuestro secretito. + +[FM2_H] +No, no, no me han seguido. ¿Tienes lo mío? + +[FM2_I] +Toma tu SPANK, soplón, ahora habla. + +[FM2_P] +Vale, los Leone libran una guerra en dos frentes. + +[FM2_Q] +Tienen una guerra territorial con las Tríadas y no parece que ninguno se vaya a rendir. + +[FM2_R] +Mientras tanto, Joey Leone ha cabreado a los Forelli. + +[FM2_S] +Cada día pierden más hombres e influencia. + +[FM2_T] +Salvatore se está volviendo peligroso y paranoico. Sospecha de todos y de todo. + +[FM2_U] +Con lo leales que son algunos, ¿de qué tendría que preocuparse? + +[FM2_1] +~g~¡Ahí está Curly Bob! + +[FM2_2] +~g~¡Curly ha salido del club, síguelo! + +[FM2_5] +~g~Llévale al puerto de Portland. + +[FM2_6] +~r~¡Curly no se meterá en un taxi cascado! + +[FM2_7] +~r~¡Curly se ha asustado! ¡Han cancelado la reunión! + +[FM2_8] +~g~¡Elimina a Curly Bob! + +[FM2_9] +~r~¡Curly Bob la ha espichado! + +[FM2_10] +~r~¡Curly se ha largado! + +[FM2_11] +~g~Aparca enfrente del club de Luigi;Curly Bob saldrá pronto. + +[FM2_12] +~r~¡Te ha dado esquinazo! + +[FM3_A] +Tenemos que despachar a esos puñeteros colombianos, + +[FM3_B] +pero mientras estemos en guerra con las Tríadas, no tendremos la fuerza necesaria. + +[FM3_C] +El cártel tiene fondos ilimitados gracias a esa mierda del SPANK. + +[FM3_D] +Si les atacamos abiertamente, nos harán picadillo. + +[FM3_E] +Estarán fabricando el SPANK en ese barco grande al que te llevó Curly. + +[FM3_F] +Así que tenemos que actuar con cuidado, o mejor dicho, tú tienes que hacerlo. + +[FM3_G] +Te estoy pidiendo que destruyas esa fábrica de SPANK como un favor personal para mí, Salvatore Leone. + +[FM3_H] +Si lo haces, entrarás en la familia. Tendrás todo lo que quieras. + +[FM3_I] +Ve a ver a 8-Ball, necesitarás su ayuda para volar ese barco. + +[FM3_8A] +¡Hombre, colega! Salvatore me avisó, + +[FM3_8B] +pero este trabajo va a necesitar muchos fuegos artificiales. + +[FM3_8D] +pero ya sabes que conmigo valdrá la pena. + +[FM3_8E] +Venga, ¡vamos allá! + +[FM3_8F] +Puedo reventar el barco, pero aún no puedo usar una pipa con estas manos. + +[FM3_8G] +¡Toma, con este fusil podrás reventar unas cuantas cabezas! + +[FM3_4] +~g~¡Para el coche y deja que 8-Ball salga! + +[FM3_7] +~r~¡8-Ball ha pasado a mejor vida! + +[FM3_8] +~r~¡Los guardias han sido alertados! + +[FM4_A] +¡Mi socio favorito! + +[FM4_B] +Estoy orgulloso, hijo, pusiste finos a esos sudacas. + +[FM4_C] +Tengo un trabajito más para ti antes de que podamos celebrarlo. + +[FM4_D] +Hay un coche a la vuelta del edificio del club de Luigi. + +[FM4_E] +El interior está pringado de sesos. + +[FM4_F] +Tuvimos que ayudar a un tipo a que se decidiera y fue un poco... sucio. + +[FM4_H] +Llévalo a la trituradora antes de que lo encuentre la pasma. + +[AM3] +'PURGA DEL PAPARAZZI' + +[AM4] +'LA PAGA DE RAY' + +[AM5] +'TANNER EL TRAIDOR' + +[AM1_A] +Tenemos que aclarar ciertos temas antes de seguir con cualquier clase de relación, + +[AM1_B] +sea de negocios u otra. Pongamos las cartas sobre la mesa. + +[AM1_C] +Soy yakuza y sé que trabajaste para la familia de Salvatore Leone. + +[AM1_D] +Puedo darte trabajo en nuestra organización, + +[AM1_E] +pero primero debes demostrarme que has cortado todos tus lazos con la mafia. + +[AM1_G] +Asegúrate de que no salga con vida. + +[AM1_H] +Mientras tanto, María y yo nos pondremos al día. + +[AM1_I] +Uy, Asuka, tienes un masajeador... + +[AM1_J] +Eso no es un masajeador. + +[AM1_1] +~g~¡Salvatore está saliendo del club de Luigi! + +[AM1_2] +~r~¡Te han descubierto! + +[AM1_3] +~r~¡Has perdido a Salvatore! + +[AM1_4] +~r~Muy bonito, ¡has asustado al objetivo! ¿Y te consideras un asesino? + +[AM1_5] +~g~Ve al Red Light District y espera a que Salvatore salga del club. + +[AM1_7] +~r~Salvatore está en su casita, sano y salvo, tomándose un cóctel. ¡Nadie te va a llamar ''Chacal''! + +[AM1_8] +~g~Salvatore saldrá del club de Luigi sobre las ~1~:~1~. + +[AM2_4] +~g~¡Has irrumpido como un elefante en una cacharrería! + +[AM3_A] +Un periodista ha estado husmeando por aquí. + +[AM3_B] +María y yo nos hemos ido de vacaciones hasta que puedas librarte de ese mirón pervertido. + +[AM4_A] +¡Si es mi guapo manitas! + +[AM4_B] +María está un poquito liada, pero le diré que has venido. + +[AM4_C] +¿Quién es, Asuka? Sé que he sido muy traviesa, ¡pero necesito mear! ¿Vale? + +[AM4_D] +Es hora de que conozcas a nuestro espía en la policía. + +[AM4_E] +Aquí está su pago por el último trabajito que hizo para nosotros. + +[AM4_F] +Él es comprensiblemente cauteloso. + +[AM4_G] +Ve a la cabina en Torrington tan rápido como puedas y espera sus instrucciones. + +[AM5_A] +María y yo hemos ido de compras. + +[AM5_B] +¡Nuestro contacto en la policía nos ha informado de que uno de nuestros conductores es un poli infiltrado que se mueve de forma extraña! + +[AM5_C] +Sin un coche es prácticamente un inútil, así que le hemos puesto un localizador. + +[AM5_D] +¡Haz que sufra! + +[AM5_1] +¡Tanner te ha pillado! + +[AS1] +'CEBO' + +[AS2] +'CAFÉ PARA LLEVAR' + +[AS4] +'RESCATE' + +[AS1_A] +Parece que Miguel piensa que le maltrato. + +[AS1_B] +Pero ha revelado cuánto teme Catalina tu búsqueda de venganza. + +[AS2_A] +Subestimamos los planes que tiene Catalina para el SPANK. + +[AS2_B] +Va mucho más allá de hacer que los jamaicanos lo vendan en las esquinas. + +[AS2_D] +Vende SPANK en puestos callejeros. + +[AS2_1] +~g~¡Todos los puestos de café en Portland han sido destruidos! + +[AS2_2] +~g~¡Todos los puestos de café en Staunton Island han sido destruidos! + +[AS2_3] +~g~¡Todos los puestos de café en Shoreside Vale han sido destruidos! + +[AS2_4] +~r~¡El cártel ha avisado a sus camellos! + +[AS2_5] +~g~¡Todavía hay puestos de café en Shoreside Vale y en Staunton Island! + +[AS2_6] +~g~¡Todavía hay puestos de café en Shoreside Vale! + +[AS2_7] +~g~¡Todavía hay puestos de café en Staunton Island! + +[AS2_8] +~g~¡Todavía hay puestos de café en Portland! + +[AS2_9] +~g~¡Todavía hay puestos de café en Portland y en Shoreside Vale! + +[AS2_10] +~g~¡Todavía hay puestos de café en Portland y en Staunton Island! + +[AS2_12] +~g~¡Recorre los distritos de Liberty y busca puestos de café ~b~Espresso-2-Go~g~! + +[AS3_A] +¿Lo apretamos un poco más o esperamos a que se vuelva negro y se caiga? + +[AS3_B] +Dale un toque rápido... + +[AS3_D] +¡Mi manitas! + +[AS3_E] +Me aburría, así que vine a hacer compañía a Asuka. + +[AS3_1] +~g~¡Busca una ~r~lancha~g~ y ve a la ~b~boya señalada~g~! + +[AS3_3] +~g~¡Espera a que la ~y~avioneta~g~ comience a acercarse! + +[AS3_5] +~g~¡Recupera el cargamento! + +[AS3_4] +~g~¡Usa un lanzacohetes para derribar ~y~la avioneta~g~! + +[AS3_2] +~b~¡Ve a la boya del aeropuerto señalada! ~y~¡El avión se está acercando! + +[AS3_6] +~g~~1~ DE 8 + +[KM1] +'LA FUGA DEL KANBU' + +[KM3] +'UN MAL TRATO' + +[KM4] +'SHIMA' + +[KM5] +'CONTRINCANTES TRAFICANTES' + +[KM1_A] +Mi hermana te tiene en gran estima, + +[KM1_E] +pero yo no estoy convencido de que un gaijin pueda ofrecerme algo más que desilusiones. + +[KM1_B] +Tal vez puedas ayudar con una situación que me perjudica. + +[KM1_F] +Por supuesto, el fracaso conllevará una desgracia. + +[KM1_C] +Un kanbu, o jefe de la yakuza, está bajo custodia a la espera de juicio. + +[KM1_G] +Es un miembro importante de la familia. + +[KM1_H] +Libéralo y llévalo al dojo de Bedford Point. + +[KM1_D] +Agradecemos tus generosas acciones. Si alguna vez necesitas ayuda, el dojo tendrá el honor de proporcionarte dos hombres que te ayudarán. + +[KM1_1] +~g~¡Roba un coche de la policía! + +[KM1_2] +~g~¡Instala una bomba en el coche! + +[KM1_3] +~g~Ahora llévalo al dojo de la yakuza. + +[KM1_5] +~g~Ahora ve a la comisaría. + +[KM1_6] +~g~¡Pon una bomba en el coche! + +[KM1_7] +~g~¡Sólo pueden entrar vehículos de policía autorizados! + +[KM1_9] +~r~No destruiste la pared con un coche bomba. + +[KM1_10] +~r~El kanbu está muerto, ¡y tu honor también! + +[KM1_11] +~r~¡La policía te ha seguido! + +[KM2_A] +Es imposible sobreestimar la importancia del protocolo en este trabajo. + +[KM2_B] +Para mi vergüenza eterna, un hombre me hizo una vez un favor y nunca pude corresponder su amabilidad. + +[KM2_C] +Su debilidad son los coches y nos ha pedido que adquiramos ciertos modelos para su colección. + +[KM2_F] +Mi honor lo exige. + +[KM2_2] +~g~Coche entregado. + +[KM3_A] +Cuando los problemas acechan, el tonto les da la espalda, mientras que el sabio los enfrenta. + +[KM3_B] +El cártel colombiano ha ignorado nuestras repetidas peticiones de que dejen en paz nuestros intereses en Liberty. + +[KM3_C] +Ahora están negociando con los jamaicanos para humillarnos aún más. + +[KM3_D] +Están cerrando un trato en la ciudad. + +[KM3_F] +Llévate a uno de mis hombres, roba un coche de los jamaicanos y presenta tus respetos a los colombianos. + +[KM3_E] +¡Nuestro honor exige que no dejes a nadie vivo! + +[KM3_2] +~g~Recoge a tu contacto. + +[KM3_3] +~g~¡La reunión está teniendo lugar en el aparcamiento del hospital de Rockford! + +[KM3_4] +~r~¡Han escapado! + +[KM3_6] +~g~¡Mátalos, mátalos a todos! + +[KM3_8] +~g~¡Necesitas un coche de los jamaicanos para este trabajo! + +[KM3_9] +~r~Uno de los colombianos ha muerto, se ha cancelado el trato. + +[KM3_10] +~r~¡El contacto está muerto! + +[KM4_A] +Para ser verdaderamente fuerte, es importante no mostrar debilidad. + +[KM4_C] +Hazte con el dinero inmediatamente para que podamos ingresarlo en las cuentas del casino. + +[KM4_1] +¡Ni puedo pagarte, ni lo haría aunque pudiera! + +[KM4_9] +¡Una banda de chavales acaba de arrasar el local! ¡Se han llevado todo! + +[KM4_2] +¡Sois inútiles! + +[KM4_10] +Además, ¿qué clase de yakuza eres tú? + +{ Unused string? } + +[KM4_3] +No pago a matones como vosotros para esto. Si quisiera esta clase de protección, llamaría a la policía, demonios. + +[KM4_4] +~g~¡Castiga a la banda responsable y recupera el ~b~dinero de la protección~g~! + +[KM4_7] +~r~¡El tendero la acaba de palmar! + +[KM4_5] +Donald Love desea que te pases por su terraza para tener una charla. + +[KM4_6] +¡Aquí está todo el dinero! + +[KM4_8] +~g~¡Maletín conseguido! + +[KM5_A] +¡Tú! ¡Qué oportuno por tu parte que muestres ahora tu despreciable cara! + +[KM5_B] +¡Parece que tu ataque para disuadir a los jamaicanos + +[KM5_B1] +de convertirse en aliados del cártel ha sido completamente inútil! + +[KM5_C] +¡Los traficantes jamaicanos están vendiendo SPANK por las calles de Liberty como si fueran perritos calientes! + +[KM5_D] +Estos cerdos del cártel se están riendo de nosotros, ¡de mí! + +[KM5_E] +¡Te daré una última oportunidad para demostrar que la fe que tiene mi hermana en ti está justificada! + +[KM5_F] +¡Aplasta a estos canallas y lava tu vergüenza en el río de la sangre de nuestros enemigos! + +[KM5_3] +~r~No has matado a ~1~ jamaicanos. + +[KM5_4] +~g~Enhorabuena, has matado a ~1~ jamaicanos. + +[KM5_5] +~g~Enhorabuena, has matado a ~1~ jamaicanos. PREMIO DE ~1~ $ + +[RM1] +'SILENCIA AL SOPLÓN' + +[RM3] +'FALTA DE PRUEBAS' + +[RM4] +'DE PESCA' + +[RM5] +'DESESCAYOLADOR' + +[RM1_D] +Se encuentra bajo protección armada en una propiedad de WitSec en el centro de Newport, en algún piso que hay detrás del aparcamiento. + +[RM1_E] +Quema el lugar, así saldrán y podrás cazarlos. Asegúrate de que no hable con nadie. + +[RM1_1] +~g~Ve a la casa de protección de testigos. + +[RM1_2] +~g~¡Cárgate a McAffrey! + +[RM2_A1] +¡Chico, estoy aquí! + +[RM2_A] +Un viejo colega del ejército tiene un negocio en Rockford. + +[RM2_D] +Necesitará refuerzos y a cambio te venderá todo su material a precios de ganga. + +[RM2_E] +Ray me avisó... Pero pensé que vendría más gente. + +[RM2_F] +Bueno, tres brazos son mejor que uno, así que sírvete. + +[RM2_G] +~g~¡Ve a ver a Phil! + +[RM2_H] +~r~¡Phil ha muerto! + +[RM2_L] +¡Caray! ¡Si hubieras estado a mi lado en Nicaragua, a lo mejor conservaría el brazo! + +[RM2_N] +Deja el dinero en el suelo. Ahora vete, ya me ocupo yo de la poli. + +[RM3_D] +Están transportando las pruebas por la ciudad. + +[RM3_E] +Tendrás que embestir al coche y recoger hasta la última prueba que se le caiga. + +[RM3_F] +En cuanto tengas todas, déjalas en tu coche y préndele fuego. + +[RM3_G] +Cuando acabes, nos irá mucho mejor, chico. + +[RM3_1] +~g~Deja las pruebas en un coche y después préndele fuego. + +[RM3_4] +~g~¡Al fiscal se le ha caído una prueba! + +[RM3_6] +~r~¡Las fotos circularán por toda la ciudad! + +[RM3_7] +~g~¡Ahora préndele fuego al coche! + +[RM4_A] +Creo que mi socio es un soplón. + +[RM4_C] +Casi todas las noches sale a pescar con su lancha cerca del faro que hay en Portland Rock. + +[RM4_D] +¡Roba una lancha de la policía y húndele las ganas de dar puñaladas traperas! + +[RM4_1] +~g~Roba una lancha de la policía. + +[RM4_2] +~g~¡Ve al faro y despacha al socio de Ray! + +[RM5_A] +¡Inútil de mierda! + +[RM5_A1] +¡La has cagado! Me la estoy jugando y ni siquiera eres capaz de matar a una maldita mosca. + +[RM5_B] +¡Te pagué una pasta para matar al testigo y aún sigue vivo! + +[RM5_B1] +¡Y hoy va a declarar ante los federales! + +[RM5_C] +Está a punto de ser trasladado del hospital general Carson, en Rockford. + +[RM5_D] +¡Si él canta, yo canto! + +[RM5_E] +¡Así que termina el trabajo por el que te pagué! + +[RM5_1] +~g~Intercepta la ambulancia. + +[RM5_2] +~g~¡Te han descubierto! + +[RM5_3] +~g~¡Era un señuelo! + +[RM5_4] +~g~¡Las balas no atraviesan esa escayola de cuerpo entero! + +[RM5_5] +~g~¡Esa escayola de cuerpo entero es ignífugo! + +[RM5_7] +~r~¡El testigo está a salvo! + +[RM5_8] +~g~¡El testigo se ha ahogado! + +[LOVE2] +'¡EXTERMINA AL WAKA-GASHIRA!' + +[LOVE3] +'UNA GOTA EN EL OCÉANO' + +[LOVE1_A] +Antes que nada, permíteme agradecerte que te hayas ocupado de ese asunto personal. + +[LOVE1_F] +Últimamente la gente tiende a malinterpretar las cosas. + +[LOVE1_D] +Pretenden exigirme más de lo pactado, pero no me gusta renegociar. + +[LOVE1_E] +Un trato es un trato, así que no van a ver ni un dólar mío. + +[LOVE1_G] +Rescata a mi amigo cueste lo que cueste. + +[LOVE1_2] +~g~Rescata al anciano oriental. + +[LOVE1_3] +~g~Lleva al anciano oriental de vuelta al edificio de Donald Love. + +[LOVE1_4] +~g~El anciano oriental debe estar detrás de alguna de estas puertas... + +[LOVE1_6] +~r~¡Las tripas del anciano oriental están desparramadas por las calles! + +[LOVE1_7] +~g~La puerta sólo se abrirá ante un coche de los colombianos. + +[LOVE2_A] +No hay nada como una clásica guerra de bandas para hacer que bajen los precios de los bienes inmuebles, + +[LOVE2_B] +excepto una plaga... pero eso sería excesivo en este caso. + +[LOVE2_C] +He observado que la yakuza y los colombianos no son precisamente buenos amigos. + +[LOVE2_D] +Vamos a aprovechar esta oportunidad de negocio. + +[LOVE2_E] +Quiero que mates a Kenji Kasen, el waka-gashira (segundo al mando) de la yakuza. + +[LOVE2_F] +Kenji está asistiendo a una reunión en lo alto del aparcamiento de Newport. + +[LOVE2_G] +¡Hazte con un coche de la banda del cártel y elimínalo! + +[LOVE2_H] +La yakuza debe culpar al cártel de esta declaración de guerra. + +[LOVE2_1] +~g~¡Ve a Fort Staunton y roba un coche de la banda de los colombianos! + +[LOVE2_2] +~g~¡Ahora ve al ~p~aparcamiento de Newport~g~ y cárgate a Kenji! + +[LOVE2_3] +~r~¡Si no llevas un coche del cártel, te descubrirán! + +[LOVE2_4] +~r~¡La yakuza te ha reconocido! + +[LOVE2_6] +~r~¡Has matado a todos los testigos! + +[LOVE3_A] +En estos días de hipocresía moral, ciertos artículos pueden resultar difíciles de importar. + +[LOVE3_C] +Echará varios paquetes al agua. + +[LOVE3_D] +Asegúrate de los consigues antes que nadie. + +[LOVE3_1] +~g~¡Consigue una ~r~lancha~g~ y sigue a la ~y~avioneta~g~! + +[LOVE4] +'ROBO DE ALTOS VUELOS' + +[LOVE5] +'SERVICIO DE ESCOLTA' + +[LOVE4_A] +Gracias por conseguir esos paquetes, pero sólo eran un señuelo. + +[LOVE4_B] +Lo siento, pero a veces los negocios son así. + +[LOVE4_C] +Mi verdadero objetivo se ocultaba en la avioneta. + +[LOVE4_F] +He sobornado a los funcionarios. + +[LOVE4_1] +~r~¡El cártel colombiano está aquí! + +[LOVE4_2] +~g~¡El paquete ha desparecido! Síguele la pista a los colombianos y recupéralo. + +[LOVE4_3] +~g~¿''Panlantic Construction''...? + +[LOVE4_5] +~g~El paquete debería estar en la avioneta... + +[LOVE4_6] +~g~¡Usa el ascensor! + +[LOVE5_B] +Mi amigo oriental necesita que le escolten mientras comprueba la autenticidad de mi última adquisición. + +[LOVE5_1] +~g~¡En marcha! + +[LOVE5_2] +~g~¡Necesitas un coche! + +[LOVE5_3] +~g~¡Comprueba la salida del túnel! + +[LOVE5_4] +~r~¡Protege el camión! + +[RM6] +'HOMBRE MUERTO' + +[RM6_A] +¿No te han seguido? Bien. + +[RM6_B] +Se acabó, ¡esto se me va de las manos y estoy con la soga al cuello! + +[RM6_D] +Soy hombre muerto, así que tengo que largarme. + +[RM6_E] +¡Consigue que no pierda mi vuelo en el aeropuerto y haré que tu trabajo haya merecido la pena! + +[RM6_666] +Cuida mi Patriot a prueba de balas. Nos vemos en Miami, Ray + +[CAT1] +'RESCATE' + +[CAT2] +'EL INTERCAMBIO' + +[CAT1_A] +Tengo a tu linda María. Si no quieres que parezca que un carnicero se ensañó con ella, + +{ UNUSED DUPLICATE } + +[CAT2_F] +Me he roto una uña y se me ha estropeado el peinado. Es increíble. ¡Me había costado cincuenta dólares! + +{ UNUSED DUPLICATE } + +[CAT2_G] +Estaba muerta de miedo, pero luego me dije a mí misma: ''Ya eres mayorcita''. + +{ UNUSED DUPLICATE } + +[CAT2_H] +Oh, nos lo vamos a pasar a lo grande, porque, ¿sabes?, mi hermana dijo que quería venirse a vivir con sus dos hijos, + +{ UNUSED DUPLICATE } + +[CAT2_I] +porque su marido a vuelto a enredar por ahí y... + +[CAT1_F] +¡Ve a donde está Catalina antes de que se acabe el tiempo! + +[CAT_MON] +~g~Todavía te falta dinero. Necesitas 500.000 dólares. + +[BITCH_D] +~g~¡María está muerta! + +[WEATHER] +FORZAR EL CLIMA + +[WEATHE2] +CLIMA NORMAL + +[8001] +¡Has fallado miserablemente! + +[1000] +HAS MUERTO + +[1001] +HAS MUERTO + +[1002] +HAS MUERTO + +[1003] +HAS MUERTO + +[1004] +HAS MUERTO + +[1005] +TRINCADO + +[1006] +TRINCADO + +[1007] +TRINCADO + +[1008] +TRINCADO + +[1009] +TRINCADO + +[GA_4] +Una bomba de coche vale 1.000 dólares. + +[GA_5] +Tu coche ya tiene una bomba instalada. + +[GA_6] { re3 change } +¡Apárcalo, actívala pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~ y SAL PITANDO! + +[GA_7] { re3 change } +Activa la bomba pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~. Estallará cuando se arranque el motor. + +[GA_8] +Utiliza el detonador para activar la bomba. + +[GA_9] +Ha conseguido ~1~ de los 10 coches especiales. + +[GA_10] +Buen trabajo. Aquí tienes tus ~1~ dólares. + +[GA_11] +Ya tenemos este modelo. ¡No nos sirve para nada! + +[GA_12] +Bomba armada + +[GA_13] +Eres todo un profesional. Completa la lista y conseguirás una recompensa. + +[GA_14] +Ya están todos los coches. ¡Estupendo! Toma un detallito. + +[GA_15] +Espero que te guste el nuevo color. + +[GA_16] +Esta mano de pintura es gratuita. + +[GA_19] +No nos interesa ese modelo. + +[GA_20] +De ese modelo tenemos más de los que podemos colocar. Lo siento, tío, no hay trato. + +[CR_1] +La grúa no puede levantar este vehículo. + +[PU_MONY] +No tienes suficiente dinero. + +[CO_ALL] +Te has hecho con todos. Toma un detallito... + +[PAUSED] +PARTIDA EN PAUSA + +[HEALTH1] +¡Largo! Estás completamente sano. + +[HEALTH2] +La sanidad cuesta dinero. + +[HEALTH3] +Te pondré como nuevo. + +[HEALTH4] +Te va a costar 250 $. + +[FEB_STA] +Estadísticas + +[FEB_BRI] +Resumen + +[FEB_CON] +Controles + +[FEB_AUD] +Sonido + +[FEB_DIS] +Pantalla + +[FEB_LAN] +Idioma + +[FEP_STA] +ESTADÍSTICAS + +[FEP_BRI] +RESUMEN + +[FEP_CON] +CONTROLES + +[FEP_AUD] +SONIDO + +[FEP_DIS] +PANTALLA + +[FEP_LAN] +IDIOMA + +[FEF_ST1] +¿Quién es el malo? + +[FEF_ST2] +Cuánta destrucción has producido + +[FEF_BR1] +¿Has perdido el hilo? + +[FEF_CO1] +¿Necesitas más control? + +[FEF_CO2] +Elige la mejor configuración del mando para tu estilo de juego + +[FEF_SA1] +¡Mantén tu sitio en el bloque! + +[FEF_SA2] +Guarda y carga tus partidas + +[FEF_AU1] +¡Dale candela al volumen! + +[FEF_AU2] +Elige una emisora de radio y un efecto de sonido + +[FEF_DI1] +¡Cambia el juego! + +[FEF_DI2] +Personaliza el juego para tu televisión + +[FEF_LA1] +¿Qué dices de los Willis? + +[FEF_LA2] +Elige tu idioma preferido + +[FEB_PMB] +Resumen de la última misión: + +[FEC_NA] +No disponible + +[FEC_CWL] +Siguiente arma + +[FEC_CWR] +Arma anterior + +[FEC_LOF] +Mirar hacia delante + +{ !!!!!!!!!!!!!!!!!! ACHTUNG! THIS STRING IS USED FOR BOTH THE MAP'S CURRENT TARGET AS WELL AS FOR THE ACTION OF TARGETING WITH A GUN! THIS WILL SCREW AROUND TRANSLATIONS !!!!!!!!!!!!!!! } +[FEC_TAR] +Objetivo + +[FEC_MOV] +Movimiento + +[FEC_CAM] +Cambiar cámara + +[FEC_PAU] +Pausa + +[FEC_ENV] +Entrar en vehículo + +[FEC_JUM] +Saltar + +[FEC_ATT] +Atacar o disparar + +[FEC_RUN] +Correr + +[FEC_FPC] +Cámara en primera persona + +[FEC_LL] +Mirar a la izquierda + +[FEC_LB1] +Mirar + +[FEC_LB2] +atrás + +[FEC_LB] +Mirar atrás + +[FEC_LR] +Mirar a la derecha + +[FEC_HOR] +Claxon + +[FEC_VES] +Controlar vehículo + +[FEC_RSC] +Cambiar emisora de radio + +[FEC_BRA] +Freno o marcha atrás + +[FEC_HAB] +Freno de mano + +[FEC_CAW] +Arma del vehículo + +[FEC_ACC] +Acelerar + +[FEC_SMT] +Activar misión especial + +[FEA_OUT] +Salida: + +[FEA_ST] +Estéreo + +[FEA_MNO] +Mono + +[FEA_NON] +Ninguna + +[FEA_FM0] +HEAD RADIO + +[FEA_FM1] +DOUBLE CLEF FM + +[FEA_FM2] +K-JAH RADIO + +[FEA_FM3] +RISE FM + +[FEA_FM4] +LIPS 106 + +[FEA_FM5] +GAME FM + +[FEA_FM6] +MSX FM + +[FEA_FM7] +FLASHBACK 95.6 + +[FEA_FM8] +CHATTERBOX FM + +[FED_DBG] +Menú de depuración + +[FED_RID] +Recargar IDE + +[FED_RIP] +Recargar IPL + +[FED_PAH] +Analizar pila + +[FED_RCD] +CCullZones::RecalculateCullZoneData + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Cambiar luz blanca grande de depuración + +[FED_SPR] +Mostrar rutas de aceras + +[FED_SCR] +Mostrar rutas de carreteras + +[FED_SCZ] +Mostrar zonas de máscara selectiva + +[FED_DSR] +Solicitudes de depuración de streaming + +[FED_SCP] +gbShowCollisionPolys + +[FEM_MCM] +Menú de Memory Card + +[FEM_RMC] +Registrar MemCard uno + +[FEM_TFM] +Probar MemCard uno formateada + +[FEM_TUM] +Probar Memcard uno sin formato + +[FEM_CRD] +Crear directorio raíz + +[FEM_CLI] +Crear y cargar iconos + +[FEM_FFF] +Llenar primer archivo con basura + +[FEM_SOG] +Guardar sólo partida + +[FEM_CES] +Comprobar cada guardado de 0kB4 + +[FEM_STG] +Guardar partida + +[FEM_STS] +Guardar partida bajo el nombre GTA3 + +[FEM_CPD] +Crear copia protegida del directorio mag + +[FEM_MC2] +Menú de Memory Card 2 + +[FEM_TS] +Prueba de guardado: + +[FEM_TL] +Prueba de carga: + +[FEM_TD] +Prueba de borrado: + +[PL_STAT] +Estadísticas de jugador + +[PE_WAST] +Personas asesinadas + +[PE_WSOT] +Personas asesinadas por otros + +[CAR_EXP] +Coches reventados + +[TM_BUST] +Veces arrestado + +[M_WASTE] +Hombres civiles asesinados + +[F_WASTE] +Mujeres civiles asesinadas + +[PIG_WST] +Polis asesinados + +[GNG_WST] +Miembros de bandas asesinados + +[MED_WST] +Médicos asesinados + +[FIRE_WS] +Bomberos asesinados + +[DED_CRI] +Criminales asesinados + +[DED_DED] +Vagabundos asesinados + +[DED_HOK] +Prostitutas asesinadas + +[HEL_DST] +Helicópteros destruidos + +[PER_COM] +Porcentaje completado + +[KGS_EXP] +Kgs de explosivos empleados + +[ACCURA] +Precisión de tiro + +[ELBURRO] +Mejor tiempo de ''Turismo'' en segundos + +[CAR_CRU] +Coches destrozados + +[HED_EX] +Cabezas explotadas + +[TM_DED] +Visitas al hospital + +[DAYSPS] +Días de juego transcurridos + +[MMRAIN] +mm de lluvia caídos + +[MXCARD] +Dist. máx. de salto demencial (pies) + +[MXCARJ] +Altura máx. de salto demencial (pies) + +[MXCARDM] +Dist. máx. de salto demencial (metros) + +[MXCARJM] +Altura máx. de salto demencial (metros) + +[MXFLIP] +Vueltas máximas en salto demencial + +[MXJUMP] +Rotación máxima en salto demencial + +[BSTSTU] +Mayor acrobacia demencial: + +[INSTUN] +Acrobacia demencial + +[PRINST] +Acrobacia demencial perfecta + +[DBINST] +Acrobacia demencial doble + +[DBPINS] +Acrobacia demencial doble perfecta + +[TRINST] +Acrobacia demencial triple + +[PRTRST] +Acrobacia demencial triple perfecta + +[QUINST] +Acrobacia demencial cuádruple + +[PQUINS] +Acrobacia demencial cuádruple perfecta + +[NOSTUC] +No se han hecho acrobacias + +[NOUNIF] +Saltos únicos completados + +[NOUNGM] +Saltos únicos + +[NMISON] +Intentos de misión + +[NMMISP] +Misiones superadas + +[PASDRO] +Pasajeros transportados + +[MONTAX] +Dinero conseguido en taxi + +[DAYPLC] +Gastos diarios provocados a la policía + +[CRIMRA] +Rango de criminal: + +[GMSTOR] +Almacenar partida + +[PREBRF] +Resúmenes anteriores + +[CNTLS] +Controles + +[MUSMEN] +Música/Efectos + +[GAMSET] +Ajustes del juego + +[LANGUA] +Idioma + +[DSPLAY] +Pantalla + +[DEBUGM] +Menú de depuración + +[QUITOP] +Salir de las opciones + +[CONTRL] +Configuración de controles + +[SET1EN] +Ajuste 1. Activado + +[SET1] +Ajuste 1. + +[SET2EN] +Ajuste 2. Activado + +[SET2] +Ajuste 2 + +[SET3EN] +Ajuste 3. Activado + +[SET3] +Ajuste 3 + +[SET4EN] +Ajuste 4. Activado + +[SET4] +Ajuste 4 + +[GOBACK] +Volver + +[SOUND] +SONIDO + +[MUSVOL] +Volumen de la música + +[SFXVOL] +Volumen de los efectos + +[SCROPT] +OPCIONES DE PANTALLA + +[CTRSCR] +Centrar pantalla + +[SCRFOR] +Formato de imagen + +[GMSVLQ] +GUARDAR-CARGAR-SALIR + +[GMREST] +Reiniciar partida + +[GMLOAD] +CARGAR PARTIDA + +[NOGMSV] +Sólo puedes guardar desde tu escondite. + +[DLFILE] +Borrar archivos de Grand Theft Auto III + +[CHFILE] +ELEGIR ARCHIVO A CARGAR + +[CHCDLD] +ELEGIR MEMORY CARD A USAR + +[CDUNFR] +Memory Card sin formato. + +[CHFIDL] +ELEGIR ARCHIVO A BORRAR + +[SVCONF] +CONFIRMACIÓN + +[SVFNAM] +El nombre de tu archivo guardado es + +[SAVEDN] +Error. Fallo al guardar. + +[LANGSL] +SELECCIÓN DE IDIOMA + +[ENGLIS] +Inglés + +[GERMAN] +Alemán + +[ITALIA] +Italiano + +[FRENCH] +Francés + +[SPAIN] +Español + +[RELIDE] +ReLoadIde + +[RELIPE] +ReLoadIpl + +[PARSHP] +Analizar pila + +[DBGFON] +CTheScripts::DbgFlag activado + +[DBFOFF] +CTheScripts::DbgFlag desactivado + +[BGWHON] +Luz blanca grande de depuración activada + +[BGWOFF] +Luz blanca grande de depuración desactivada + +[DSTRON] +Solicitudes de depuración de streaming activadas + +[DSTROFF] +Solicitudes de depuración de streaming desactivadas + +[PDRGON] +ShowPedRoadGroups activado + +[PRGOFF] +ShowPedRoadGroups desactivado + +[CRRGON] +ShowCarRoadGroups activado + +[CRGOFF] +ShowCarRoadGroups desactivado + +[CLZOON] +Zonas de máscara selectiva mostradas + +[CLZOOF] +Zonas de máscara selectiva ocultadas + +[SHPLON] +gbShowCollisionPolys activado + +[SHPLOF] +gbShowCollisionPolys desactivado + +[CULREC] +CCullZones::RecalculateCullZoneData() + +[FORMM1] +FormatMemCard 1 (prueba) + +[UNFRM1] +UnFormatMemCard 1 (prueba) + +[GORLEV] +Nivel de violencia + +[SICASS] +Puta locura + +[SICSIC] +Puto manicomio + +[SCASSL] +Seleccionado Puta locura + +[SCSCSL] +Seleccionado Puto manicomio + +[PRVMEN] +Resúmenes de misiones anteriores + +[DOSVGM] +¿Deseas guardar la partida? + +[FORMEN] +Menú de formato + +[MEMTST] +Pantalla MemoryCardTest + +[REGCAR] +Registrar MemoryCard uno + +[TEFONE] +Probar MemCard uno con formato + +[TEUFON] +Probar MemCard uno sin formato + +[CRROOT] +CreateRootDir + +[CRLDIC] +Crear y cargar iconos + +[FLFSGF] +Llenar el primer archivo con basura + +[PUSAVE] +Guardar sólo la partida + +[CHEVOK] +CheckEveryOkB4Save + +[SVGMON] +SaveTheGame + +[CNTSAV] +No se puede guardar la partida en una misión. + +[CNCSAV] +No se puede guardar la partida dentro de un vehículo. + +[CRMGSV] +Crear directorio de cargador con protección de copia + +[MGSVNC] +MagazineDirectory no creado + +[MGSVCN] +MagazineDirectory creado + +[YES] +Sí + +[NO] +No + +[X] +x + +[LAST] +Último mensaje + +[FEDS_XB] +Seleccionar + +[FEDS_ST] +Botón START - CONTINUAR + +[FEST_OO] +de + +[FEC_TUC] +Controlar torreta + +[FEC_SM3] +Activar misión secundaria (botón R3) + +[FEC_RS3] +Cambiar emisora de radio (botón L3) + +[FEC_HO3] +Claxon (botón L3) + +[DIAB1] +'TURISMO' + +[DIAB2] +'IRA HELADA' + +[DIAB3] +'A LA HOGUERA' + +[DIAB4] +'GRANDE Y VENOSA' + +[DIAB1_A] +El Burro quiere ofrecerte una oportunidad. Ve al teléfono público de los cerros de Hepburn si te interesa. + +[DIAB1_C] +Se te da bien correr. Pásate por el teléfono público y puede que El Burro tenga trabajo para ti. + +[DIAB1_1] +~g~3... 2... 1... ¡ADELANTE! + +[DIAB1_4] +~g~Hazte con un coche rápido y ve a la parrilla de salida. + +[DIAB1_3] +~r~No sabes ganar ni una rifa, ¡FRACASADO! + +[DIAB1_2] +~g~Felicidades, has ganado con un tiempo increíble de ~1~ segundos. + +[FIRST] +~g~1 + +[SECOND] +~g~2 + +[THIRD] +~g~3 + +[FOURTH] +~g~4 + +[DIAB2_1] +~g~Recoge el maletín en Harwood. + +[DIAB2_2] +~g~Encuentra un camión de helados. + +[DIAB2_3] +~g~Aparca el camión de helados en el muelle Atlantic. + +[DIAB2_4] +~g~Pulsa ~w~~k~~VEHICLE_HORN~~g~ para activar la melodía del camión de helados. + +[DIAB2_6] +~g~Pulsa ~w~~k~~VEHICLE_HORN~~g~ para activar la melodía del camión de helados. + +[DIAB2_7] +~g~Pulsa ~w~~k~~VEHICLE_HORN~~g~ para activar la melodía del camión de helados. + +[DIAB2_5] +~g~Sal del camión y luego usa el mando a distancia para detonarlo. + +[YD1] +'CORRE A POR LA PASTA' + +[YD2] +'JINETE CON UZI' + +[YD3] +'COLECCIONANDO COCHES DE BANDAS' + +[YD4] +'POR LOS AIRES' + +[YD_P] +El Rey Courtney quiere hablarte. ¡Ve al teléfono público de Aspatria! + +[YD1_A] +Habla el rey Courtney. + +[YD1_A1] +Mi banda jamaicana necesita un conductor y tú tienes buena reputación. + +[YD1_B] +Ve en un coche al vertedero que hay detrás del estadio y espera a los otros candidatos. + +[YD1_C] +Tengo hombres vigilando puntos de control por toda Staunton. + +[YD1_D] +El primer conductor que llegue a un punto de control se lleva mil pavos, y así de punto a punto. + +[YD1_D1] +Si ganas más puntos de control que los demás, podría tener trabajo para ti. + +[YD1_E] +~g~¡Prepárate! + +[YD1_F] +~g~Te has saltado la salida. ¡Me gusta tu estilo! + +[YD1_G] +~r~Esto es una carrera DE COCHES. ¡Necesitas un COCHE, idiota! + +[YD1GO] +~g~¡VAMOS! + +[YD1_1] +~r~1 + +[YD1_2] +~r~2 + +[YD1_3] +~r~3 + +[YD1_BON] +¡1.000 $! + +[Y1_1ST] +~G~¡Has quedado primero con ~1~ puntos de control! + +[Y1_2ND] +~y~Eres el segundo con ~1~ puntos de control. ~y~¡Casi, pero no! + +[Y1_3RD] +~r~Eres el tercero con ~1~ puntos de control. ~r~¿No decías que eras bueno? + +[Y1_LAST] +~r~¡Has quedado el último! ~r~¡Me has hecho perder el tiempo, IDIOTA! + +[Y1_J1ST] +~y~Has empatado el primero con ~1~ puntos de control. ~y~Bien, ¡pero debes ser el mejor si quieres conducir para la Reina Lizzy! + +[Y1_J2ND] +~r~Has empatado el segundo con ~1~ puntos de control. ¡Conduces como un mono loco! + +[Y1JLAST] +~r~¡Has empatado el último! ¡Hablas como un conductor, pero conduces como un hablador! + +[Y1_TEST] +¡COCHE AL AGUA! + +[YD2_A] +Necesito ver si eres capaz de hacer mi trabajo sucio, + +[YD2_A1] +si se puede confiar en ti. + +[YD2_B] +Dos de mis chicos llegarán allí enseguida para llevarte de paseo, + +[YD2_B1] +a ver si eres quien dices que eres. + +[YD2_C] +Vamos a dar un paseíto por los cerros de Hepburn para cargarnos a los asquerosos Diablos que han estado metiéndose con la reina Lizzy. + +[YD2_CC] +Toma, necesitarás una pipa. + +[YD2_D] +Tú te ocupas de conducir y disparar. Nosotros nos aseguraremos de que no te eches atrás. + +[YD2_E] +¡Conduce! + +[YD2_F] +~r~Se quiere escapar, ¡cárgatelo! + +[YD2_G1] +Los cerros de Hepburn... Vamos a matar a algunos asquerosos Diablos... + +[YD2_G2] +Pero recuerda, ~r~¡no salgas de este coche! + +[YD2_H] +¡Vale, ya está! ¡Llévanos de vuelta a territorio jamaicano! ¡Vamos, vamos, vamos! + +[YD2_L] +¡Eres la Muerte personificada! + +[YD2_M] +~r~¡Ha destrozado mi coche! ¡Liquídalo! + +[YD2_N] +¡Vuelve al coche! + +[YD3_A] +Quiero que robes coches de otras bandas + +[YD3_A1] +para que podamos atacar sus territorios. + +[YD3_B] +Necesito un Mafia Sentinel, + +[YD3_B1] +un Yakuza Stinger y un + +[YD3_B2] +Diablo Stallion para que podamos ir a por cualquier banda de Liberty. + +[YD3_C] +Déjalos en el garaje de Newport y recuerda, + +[YD3_C1] +¡dañados no nos sirven de nada! + +[YD3_D] +UNUSED + +[YD3_E] +~r~¡Ya has entregado un coche de los Diablos! + +[YD3_F] +~r~¡Ya has entregado un coche de la mafia! + +[YD3_G] +~r~¡Ya has entregado un coche de la yakuza! + +[YD3_H] +~g~¡Coche de los Diablos entregado! + +[YD3_I] +~g~¡Coche de la mafia entregado! + +[YD3_J] +~g~¡Coche de la yakuza entregado! + +[YD3_K] +~r~¡El coche está casi destrozado! ¡Haz que lo reparen! + +[YD3_L] +~g~¡Llévalo al ~p~garaje~g~! + +[YD3_M] +~r~¡Has volcado el vehículo! ¡Consigue otro! + +[YD4_A] +¡Escucha! + +[YD4_A1] +Mueve el culo a Bedford Point. + +[YD4_A2] +¡Hay un cargamento en una tartana que necesito pronto! + +[YD4_B] +CARTA: Dicen que estuviste entretenido. Igual que yo. + +[YD4_C] +¡Es hora de que seas testigo del verdadero poder del SPANK! Con mucho amor, Catalina. + +[YD4_D] +P.D. ¡MUERE, BASURA, MUERE! + +[YD4_1] +~g~¡Son psicópatas adictos al SPANK! + +[YD4_2] +~g~¡Destruye las furgonetas de SPANK! + +[HM_1] +'PASADA AMETRALLADA' + +[HM_2] +'BOMBINATOR' + +[HM_3] +'PREPARADO PARA ESTALLAR' + +[HM_5] +'ENFRENTAMIENTO' + +[HOOD1_A] +Ve al teléfono público de Wichita Gardens y hablaremos de negocios. + +[HM1_A] +¡Ey! ¡Soy D-Ice, de los Red Jacks! + +[HM1_C] +Hay unos macarrillas en la calle que no piensan más que en pistolas y en SPANK. + +[HM1_3] +~g~Los Nines tienen su territorio en Wichita Gardens. + +[HM2_3] +¡Si golpeas las ruedas de un vehículo, el coche teledirigido estallará! + +[HM2_4] +¡Si el coche teledirigido sale fuera de cobertura, estallará solo! + +[HM2_5] +~r~¡Te has ido de cobertura! + +[HM3_1] +~g~¡Ve al garaje, pero pon atención, porque el coche explotará si se daña demasiado! + +[HM3_2] +~g~Lleva el coche de vuelta. ¡Tiene que estar inmaculado! + +[HM3_3] +~g~¡Haz que reparen el vehículo! + +[HM4_D] +~g~¡Hazte con un vehículo! + +[HM4_1] +~g~Dirígete al lugar donde está el cargamento. Necesitas conseguir 30 lingotes. + +[HM4_2] +~g~Recuerda: cuando el vehículo esté pesado y vaya más despacio, ve al garaje y deja el cargamento. + +[HM5_3] +~r~¡Te han dicho que uses sólo un bate de béisbol! + +[HM5_4] +~r~¡Tu contacto está muerto! + +[MEA1] +'EL CRIMINAL' + +[MEA2] +'LOS LADRONES' + +[MEA3] +'LA MUJER' + +[MEA4] +'SU AMANTE' + +[MEAT1_A] +Un amigo dijo que tú podías arreglar algunos problemas que tengo. Ve al teléfono público de Trenton si crees que puedes ayudar. + +[MEA1_B3] +~g~Reúnete con el director del banco. + +[MEA1_B6] +~g~Lleva el coche a la trituradora para eliminar las pruebas, allí sal del coche y la grúa lo recogerá. + +[MEA1_1] +~r~¡El director del banco está muerto! + +[MEA1_2] +~r~¡Te dijeron que trituraras el coche! + +[MEA1_3] +~g~¡Sal del coche! + +[MEA1_4] +~r~¡Has abandonado al director del banco! + +[MEA2_B3] +~g~Reúnete con los ladrones. + +[MEA2_B4] +~g~Llévalos a la fábrica de comestibles Bitch'n' Dog. + +[MEA2_B6] +~g~Haz que repinten el coche para borrar las pruebas. + +[MEA2_1] +~r~¡Te dijeron que trituraras el vehículo! + +[MEA2_2] +~r~¡Un ladrón ha muerto! + +[MEA2_4] +~r~¡Has abandonado a un ladrón! + +[MEA3_B3] +~g~Recoge a la Sra. Chonks. + +[MEA3_B6] +~g~Llévate el coche y tíralo al mar para eliminar las pruebas. + +[MEA3_1] +~r~¡La mujer ha muerto! + +[MEA3_2] +~r~¡Se suponía que debías tirar el coche al agua! + +[MEA3_3] +~r~¡Has abandonado a su mujer! + +[MEA4_B3] +~g~Recoge al amante de su mujer. + +[MEA4_B6] +Ya es muy tarde, Marty. Tuviste tu oportunidad, pero ahora yo me ocuparé de tu negocio... + +[MEA4_1] +~r~¡Carlos está muerto! + +[MEA4_3] +~r~¡Has abandonado a Carlos, el usurero! + +[LOOK_A] +Mantén pulsado ~h~~k~~VEHICLE_LOOKLEFT~~w~ o ~h~~k~~VEHICLE_LOOKRIGHT~~w~ para mirar~h~ a la izquierda~w~ o ~h~a la derecha ~w~estando dentro de un vehículo. Pulsa ambos para mirar~h~ atrás~w~. + +[LOVE6_1] +~g~¡Ahora aleja a los policías del almacén! + +[LOVE6_2] +~r~¡No has distraído a la policía! + +[RM4_3] +~r~¡El socio de Ray ha escapado! + +[RM6_C] +La CIA parece tener intereses con el SPANK + +[RM6_C1] +y no les gusta que incordiemos al cártel. + +[C_PASS] +¡AMENAZA ELIMINADA! + +[CTUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de justiciero. + +[CTUTOR2] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de justiciero. + +[COPCART] +~g~Tienes ~1~ segundos para volver a un vehículo de la policía antes de que termine la misión. + +[C_FAIL] +¡Misión de justiciero terminada! + +[C_CANC] +~r~¡Misión de justiciero cancelada! + +[C_ESCP] +~r~¡El sospechoso ha escapado! + +[C_TIME] +~r~¡Tu trabajo como defensor de la ley ha terminado! + +[C_VIGIL] +¡PREMIO POR JUSTICIERO! + +[A_FAIL2] +~r~¡Tu falta de rapidez ha sido fatal para el paciente! + +[A_FAIL3] +~r~¡Tu paciente ha muerto! + +[A_PASS] +¡Paciente a salvo! + +[F_FAIL2] +~r~¡Llegas demasiado tarde! + +[A_COMP2] +¡Nunca te cansarás! + +[RM2_M] +Si necesitas armas nuevas, pasa por aquí y coge lo que necesites de las taquillas. + +[HEAL_A] +Tu ~h~salud ~w~está indicada en naranja en la parte superior derecha de la pantalla. + +[YD1_CNT] +¡~1~ de 15! + +[FM1_9] +~g~La fiesta es allí. Deja a María enfrente. + +[FM1_Y] +Quiero que sepas que me lo he pasado muy bien por primera vez en mucho tiempo y que me has tratado muy bien, con respeto. + +[FM1_AA] +Bueno, será mejor que me vaya. Espero que nos veamos pronto. + +[NOCONTE] +Vuelve a conectar un mando analógico (DUALSHOCK#) o mando analógico (DUALSHOCK#2) en el puerto de mando 1 para continuar. + +[WRCONT] +El mando conectado al puerto de mando 1 es un mando no compatible. Grand Theft Auto III necesita un mando analógico (DUALSHOCK#) o mando analógico (DUALSHOCK#2). + +[WRCONTE] +El mando conectado al puerto de mando 1 es un mando no compatible. Grand Theft Auto III necesita un mando analógico (DUALSHOCK#) o mando analógico (DUALSHOCK#2). + +[WRONGCD] +Disco incorrecto. Introduce el disco correcto. + +[NOCD] +La bandeja del disco está vacía. Introduce un disco. + +[OPENCD] +La bandeja del disco está abierta. Cierra la bandeja de disco. + +[CDERROR] +Error al leer el DVD de Grand Theft Auto III + +[RESTART] +Empezando una nueva partida + +[GA_3] +¡Ya no es gratis! ¡La mano de pintura son 1.000 dólares! + +[GA_1] +¡Hala! ¡Demasiado peligroso para mi gusto! + +[GA_1A] +Vuelve cuando no estés tan ocupado... + +[S_PROM2] +Puedes alojar un vehículo en el garaje de al lado al guardar tu partida. + +[STOCK] +Mercancía agotada + +[FM1_O] +Creo que está en la estación de ferrocarril del muelle de Chinatown. + +[EBAL_B] +Es aquí, ¡entremos y cambiémonos de ropa! + +[EBAL_G] +Éste es el club de Luigi. Vamos a la parte de atrás, a la puerta de servicio. + +[AM4_3] +¡Tú debes de ser el nuevo recadero de Asuka! + +[AM4_4] +¿Tienes el dinero? ¿Está todo? + +[AM4_5] +Sé lo que estás pensando: ''otro poli corrupto''. + +[AM4_6] +Pues éste es un mundo corrupto. + +[AM4_7] +He perdido a un par de socios y esos papanatas de Asuntos Internos ya se han puesto a husmear. + +[AM4_8] +¡Y seguro que les llega mi olor! + +[AM4_9] +¡Pues esta ciudad es una gran alcantarilla! + +[AM4_10] +Pero voy a necesitar un ayudita ajena al cuerpo. + +[AM4_11] +Si estás interesado, ya sabes dónde encontrarme. + +[CAM_A] +Pulsa ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ para cambiar la ~h~cámara ~w~cuando vayas a pie o estés en un vehículo. + +[CAM_B] +Pulsa el ~h~botón de dirección hacia arriba ~w~y ~h~abajo~w~ para cambiar la ~h~cámara ~w~cuando vayas a pie o estés en un vehículo. + +[KM2_1] +~g~Repara el coche, tiene que estar en perfecto estado. + +[LM3_6] +¡Joey...! + +[LM3_6A] +¿Voy a jugar con tu paquetín otra vez? + +[LM3_9A] +que tendré trabajo para ti. + +[LM3_9B] +¿De acuerdo? + +[AWAY2] +~r~Se ha escapado. + +[AWAY] +~r~¡Se ha pirado! + +[JM6_1] +Llévanos al banco de la avenida principal. + +[GA_6B] { re3 change } +¡Apárcalo, actívala pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~ y SAL PITANDO! + +[GA_7B] { re3 change } +Activa la bomba pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~. Estallará cuando se arranque el motor. + +[BAT1] +~g~¡Coge el bate! + +[EBAL_O] +Si no la cagas, puede que haya más trabajo para ti. ¡Ahora largo! + +[HELP9_B] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar ~w~el fusil de francotirador. + +[HELP9_C] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar ~w~el fusil de francotirador. + +[JM6_8] +~r~¡Has perdido a todos los ladrones! + +[COLT_IN] +¡La pistola ya está disponible en la tienda Ammu-Nation! + +[TAXI2] +~r~¡Se te acabó el tiempo! + +[TAXI3] +~r~¡Tu cliente ha salido por patas! + +[TAXI7] +~r~Tu coche está destrozado, repáralo. + +[TAXI4] +¡Carrera terminada! + +[TAXI5] +¡VIAJE RÁPIDO! + +[TAXI6] +Misión de taxista cancelada + +[FRANGO] +~g~¡Salvatore quiere que ayudes primero a Toni a ocuparse de las Tríadas! + +[PAGEB12] +Soborno policial entregado en tu guarida. + +[PAGEB13] +Salud entregada en tu guarida. + +[PAGEB14] +Adrenalina entregada en tu guarida. + +[KM1_4] +~g~¡Necesitas un coche de policía para hacer el trabajo! + +[CAT1_B] +lleva 500.000 dólares a la mansión de Cedar Grove. + +[JM2_C] +Tiene un puesto de fideos en Chinatown. + +[RM6_1] +Esta llave es de un almacén. + +[RM6_2] +Encontrarás dinero en metálico y ''suministros'' que guardé por si las cosas se ponían feas. + +[RM6_3] +Hasta la vista. + +[FE_INIP] +Iniciando y cargando el menú de pausa... Espera, por favor. + +[FESZ_CA] +Cancelar + +[FESZ_QU] +Abandonar + +[FESZ_L1] +¡La partida se ha guardado! + +[FESZ_L2] +El nombre guardado es: + +[FESZ_OK] +ACEPTAR + +[FES_NGA] +Nueva partida + +[FES_CAN] +Cancelar + +[FESZ_QL] +Perderás todo el progreso en tu partida actual. ¿Quieres continuar con la carga? + +[FESZ_QD] +¿Quieres borrar esta partida guardada? + +[FESZ_QO] +¿Quieres sobreescribir esta partida guardada? + +[FESZ_QR] +¿Seguro que quieres empezar una nueva partida? Perderás todo el progreso desde la última partida guardada. + +[FESZ_QS] +¿QUIERES GUARDAR? + +[SLONFP] +Puerto 1. Archivo protegido. + +[T4X4_1] +'PATIO DE RECREO DEL PATRIOT' + +[T4X4_2] +'UN PASEO POR EL PARQUE' + +[T4X4_3] +'¡AGARRADO!' + +[MM_1] +'CAOS EN EL APARCAMIENTO' + +[T4X4_1A] +~g~Tienes ~y~5 minutos~g~ para pasar por ~y~15~g~ puntos de control. ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[T4X4_1B] +¡~1~ de 15! + +[T4X4_1C] +~y~ATRAVIESA~g~ el primer punto de control para poner en marcha el cronómetro. ~g~Cada punto de control te dará ~y~20 SEGUNDOS~g~ adicionales. + +[T4X4_2A] +~g~¡Tienes ~y~2 minutos~g~ para pasar por ~y~12~g~ puntos de control! ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[T4X4_2B] +¡~1~ de 12! + +[T4X4_2C] +~y~ATRAVIESA~g~ el primer punto de control para poner en marcha el cronómetro. ~g~Cada punto de control te dará ~y~10 SEGUNDOS~g~ adicionales. + +[T4X4_3A] +~g~Tienes ~y~5 minutos~g~ para pasar por ~y~20~g~ puntos de control. ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[T4X4_3B] +~y~ATRAVIESA~g~ el primer punto de control para poner en marcha el cronómetro. ~g~Cada punto de control te dará ~y~15 SEGUNDOS~g~ adicionales. + +[T4X4_3C] +¡~1~ de 20! + +[T4X4_F] +~r~¡Te has escapado! ¿Demasiado difícil para ti? + +[MM_1_A] +~g~¡Tienes ~y~2 minutos~g~ para pasar por ~y~20 puntos de control~g~ en el aparcamiento! ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[MM_1_B] +¡~1~ de 20! + +[MM_1_C] +~g~Tendrás 20 segundos, más ~y~5 SEGUNDOS~g~ por cada punto de control. ~g~El cronómetro comenzará ~y~INMEDIATAMENTE. + +[FM2_14] +~r~¡Te acercaste demasiado y asustaste a Curly! + +[FM2_15] +~g~No te acerques mucho, ¡o Curly sospechará! + +[UPSIDE] +~r~¡Has volcado! + +[FM2_16] +ASUSTÓMETRO: + +[LM3_11] +~g~Misty no irá en un autobús, ¡hazte con otro vehículo! + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[PATRIOT] +Patriot + +[FIRETRK] +Camión de bomberos + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Mañana + +[INFERNS] +Infernus + +[BLISTA] +Blista + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulancia + +[FBICAR] +Coche del FBI + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[KURUMA] +Kuruma + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr. Whoopee + +[BFINJC] +BF Injection + +[POLICAR] +Coche patrulla + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Autobús + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[TRAIN] +Tren + +[HELI] +Helicóptero + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +Bandit RC + +[BELLYUP] +Furgoneta de las Tríadas + +[MRWONGS] +Mr Wongs + +[MAFIACR] +Sentinel de la mafia + +[YARDICR] +Lobo jamaicano + +[YAKUZCR] +Stinger de la yakuza + +[DIABLCR] +Stallion de los Diablos + +[COLOMCR] +Cruiser del cártel + +[HOODSCR] +Rumpo XL de los hood + +[AEROPL] +Avioneta + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[PANLANT] +Panlantic + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[BORGNIN] +Borgnine + +[TOYZ] +TOYZ + +[FEST_DF] +Distancia recorrida a pie (millas) + +[FEST_DC] +Distancia recorrida en coche (millas) + +[FESTDFM] +Distancia recorrida a pie (metros) + +[FESTDCM] +Distancia recorrida en coche (metros) + +[FEST_R1] +''Patio de recreo del Patriot'' en segundos + +[FEST_R2] +''Un paseo por el parque'' en segundos + +[FEST_R3] +''¡Agarrado!'' en segundos + +[FEST_RM] +''Caos en el aparcamiento'' en segundos + +[FEST_LS] +Personas salvadas con una ambulancia + +[FEST_CC] +Criminales asesinados en misiones de justiciero + +[FEST_FE] +Incendios extinguidos + +[FEST_LF] +Vuelo más largo en Dodo + +[FEST_BD] +Mejor tiempo al desactivar la bomba + +[FEST_RP] +Masacres superadas + +[FEST_MP] +Misiones superadas + +[FEST_BB] +''Corre a por la pasta'': + +[FEST_H0] +Puntos de control alcanzados + +[FEST_GC] +Coches de bandas destruidos: + +[FEST_H1] +Destrucción de los Diablos + +[FEST_H2] +La masacre de la mafia + +[FEST_H3] +Calamidad en el casino + +[FEST_H4] +Exterminio de Rumpos + +[USJ] +¡PREMIO POR ACROBACIA ÚNICA! + +[SPRAY] +Mete tu vehículo en el taller de pintura para perder tu ~h~nivel de búsqueda~w~, ~h~reparar~w~ y~h~ repintar~w~ tu vehículo. Coste: ~h~1.000 $~w~. + +[HM1_1] +~g~Cepíllate a 20 Purple Nines en 2 minutos y 30 segundos. + +[KM1_8A] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para ~h~activar la bomba~w~. Acuérdate de alejarte de ella. + +[KM1_8D] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para ~h~activar la bomba~w~. Acuérdate de alejarte de ella. + +[KM1_12] +~g~¡Llévalo al dojo, pero deshazte primero de la policía! + +[RATNG1] +Carterista + +[RATNG2] +Abusón + +[RATNG3] +Chorizo + +[RATNG4] +Granuja + +[RATNG5] +Secuaz + +[RATNG6] +Conductor + +[RATNG7] +Guardaespaldas + +[RATNG8] +Negociador + +[RATNG9] +Socio + +[RATNG10] +Sicario + +[RATNG11] +Asesino + +[RATNG12] +Mano derecha + +[RATNG13] +Verdugo + +[RATNG14] +Capo + +[RATNG15] +Jefe + +[1010] +~r~Tu vehículo ha volcado + +[1011] +~r~Tu vehículo ha volcado + +[1012] +~r~Tu vehículo ha volcado + +[1013] +~r~Tu vehículo ha volcado + +[1014] +~r~Tu vehículo ha volcado + +[JM4_10] +Vale, chaval, llévame primero a la lavandería de Chinatown, tengo un asunto del que ocuparme. + +[JM4_11] +Las lavanderas no están pagando por su protección. + +[JM4_12] +Y ojo con el coche, Joey acaba de arreglar esta chatarra. + +[JM4_13] +Así que ve con cuidado, ¿vale? + +[KM4_11] +~g~¡Lleva el dinero de vuelta al casino! + +[FEF_BR2] +Encuéntralo de nuevo leyendo los resúmenes de las misiones jugadas hasta la fecha. + +[TRAIN_1] +Estación Kurowski + +[TRAIN_2] +Estación Rothwell + +[TRAIN_3] +Estación Baillie + +[SUBWAY1] +Estación Portland + +[SUBWAY2] +Estación Rockford + +[SUBWAY3] +Estación Staunton sur + +[SUBWAY4] +Terminal Shoreside + +[MEA4_2] +~r~¡Marty Chonks ha muerto! + +[SPRAY1] +Mete tu vehículo en el taller de pintura para perder tu ~h~nivel de búsqueda~w~, ~h~reparar~w~ y~h~ repintar~w~ tu vehículo. Coste: ~h~1.000 $~w~. Esta vez es gratis. + +[JM4_A] +Sí, Toni, ya está a punto. Ahora es una delicia, ¿sabes? + +[JM4_5] +Pasa más tarde y les daremos algo que lavar... ¡Su propia ropa manchada de sangre! + +[AMMU_A] +Luigi dijo que necesitabas una pipa... + +[AMMU_B] +Joey me pidió que te armara... + +[AMMU_C] +Ve a la parte de atrás. Te dejé un nueve en el patio. + +[AMMU_D] +Tengo todo lo necesario para defender tu casa. + +[AMMU_E] +¿También quieres una licencia? + +[AMMU_F] +No necesito tu documentación, pareces de fiar. + +[DETON] +DETONACIÓN: + +[DRIVE_A] { re3 change } +Ten una Uzi seleccionada cuando entres en un vehículo, luego mira a la izquierda o a la derecha y pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar. + +[DRIVE_B] { re3 change } +Ten una Uzi seleccionada cuando entres en un vehículo, luego mira a la izquierda o a la derecha y pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar. + +[RECORD] +~g~¡NUEVO RÉCORD! + +[NRECORD] +~r~¡NO HAY UN NUEVO RÉCORD! + +[RCHELP] { re3 change } +Pulsa ~k~~VEHICLE_FIREWEAPON~ o lleva el coche teledirigido hasta las ruedas de otro coche para detonarlo. + +[RCHELPA] { re3 change } +Pulsa ~k~~VEHICLE_FIREWEAPON~ o lleva el coche teledirigido hasta las ruedas de otro coche para detonarlo. + +[RC_1] +¡Tienes 2 minutos para destruir todos los coches de los Diablos que puedas! + +[RC_2] +¡Tienes 2 minutos para destruir todos los coches de la mafia que puedas! + +[RC_3] +¡Tienes 2 minutos para destruir todos los coches de la yakuza que puedas! + +[RC_4] +¡Tienes 2 minutos para destruir todos los coches de los jamaicanos que puedas! + +[RC_5] +¡Tienes 2 minutos para destruir todos los coches de los Hood que puedas! + +[RC_6] +¡Tienes 2 minutos para destruir todos los coches del cártel que puedas! + +[RAMPAGE] +¡MASACRE! + +[RAMP_P] +¡MASACRE COMPLETADA! + +[RAMP_F] +MASACRE FALLIDA + +[PAGE_00] +. + +[PAGE_01] +¡Liquida a ~1~ Diablos en 120 segundos! + +[PAGE_02] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_03] +¡Mata a ~1~ mafiosos en 120 segundos! + +[PAGE_04] +¡Mata a ~1~ Tríadas en 120 segundos! + +[PAGE_05] +¡Mata a ~1~ Tríadas en 120 segundos! + +[PAGE_06] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_07] +¡Revienta ~1~ cabezas jamaicanas en 120 segundos! + +[PAGE_08] +¡Quema a ~1~ yakuzas en 120 segundos! + +[PAGE_09] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_10] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_11] +¡Aniquila a ~1~ jamaicanos en 120 segundos! + +[PAGE_12] +¡Quema a ~1~ yakuzas en 120 segundos! + +[PAGE_13] +¡Vuela a ~1~ jamaicanos en 120 segundos! + +[PAGE_14] +¡Fríe a ~1~ colombianos en 120 segundos! + +[PAGE_15] +¡Machaca a ~1~ Hoods en 120 segundos! + +[PAGE_16] +¡Destruye ~1~ vehículos en 120 segundos! + +[PAGE_17] +¡Arrolla a ~1~ colombianos con un coche en 120 segundos! + +[PAGE_18] +¡Destruye ~1~ vehículos disparando desde otro en 120 segundos! + +[PAGE_19] +¡Revienta ~1~ cabezas colombianas en 120 segundos! + +[PAGE_20] +¡Decapita a ~1~ Hoods en 120 segundos! + +[JM1_A] +Jo, me aburro, ¿cuándo me la vas a meter? + +[JM1_B] +Dame un momento, cariño, que tengo un asuntillo que tratar. + +[JM1_C] +Tengo un trabajito para ti, colega. + +[JM1_D] +Los hermanos Forelli me deben dinero desde hace mucho tiempo + +[JM1_E] +y hay que enseñarles modales. + +[JM1_F] +''Labios'' Forelli está atiborrándose en el restaurante de Saint Mark's, + +[JM1_G] +así que róbale el coche y llévalo al taller de bombas de 8-Ball en Harwood. + +[JM1_H] +Conoces a 8-Ball, ¿verdad? + +[JM1_I] +En cuanto le ponga una bomba, aparca el coche donde lo encontraste. + +[JM1_J] +Luego salte y disfruta del espectáculo. + +[JM1_K] +Pero cuidado, no va a estar comiendo toda la vida. + +[CAT2_A1] +¡Vamos, perra! + +[CAT2_A] +La verdadera pregunta es: ¿vienes buscando a María o buscándome a mí? + +[CAT2_B] +Tengo una noticia para ti: + +[CAT2_B2] +matarte será un placer, pero salir contigo sólo fue un negocio. + +[CAT2_C] +¡Eres muy pequeñito, amigo! + +[CAT2_D] +Dame el dinero. + +[CAT2_E] +¡Has estado muy ocupado! + +[CAT2_E2] +Pero no has aprendido que yo no soy de fiar. + +[CAT2_E3] +¡Mata al idiota! + +[CAT2_J] +¡Despega de una vez! + +[HM5_1] +Hola, Ice dijo que vendrías. Las reglas son sólo bates. Cero armas, cero coches. + +[HM5_5] +Es una batalla por respeto, ¿vale? + +[HELP14] +Para conseguir un arma, pasa sobre ella. No podrás recogerlas estando dentro de un vehículo. + +[CRUSH] +Aparca en la zona señalada y sal de tu vehículo para que sea triturado. + +[DIAB2_B] +Una banda de roñosos amenazó con quitarme a mi protagonista si no les pago. + +[DIAB2_C] +Amenazaron al hombre equivocado, amigo. + +[DIAB2_D] +Su debilidad son los helados. + +[DIAB2_E] +Hazte con la bomba que dejé en Harwood, + +[DIAB2_F] +secuestra la camioneta del heladero mientras hace su ronda + +[DIAB2_G] +y engaña a esos idiotas con esa musiquita. + +[DIAB2_H] +Se esconden en un almacén del muelle Atlantic. + +[DIAB3_A] +¡Unas Tríadas insolentes robaron mi lindo carro anoche, + +[DIAB3_B] +lo destrozaron y lo quemaron! + +[DIAB3_C] +En el baúl había algunas de mis más preciadas posesiones sobre burros... + +[DIAB3_D] +Objetos de coleccionista irremplazables, amigo. + +[DIAB3_E] +Escondí un arma caliente en el borde de Chinatown. + +[DIAB3_F] +Tómala y enseña a esos vándalos de las Tríadas a tenerle miedo a la ira pijuda de El Burro. + +[DIAB3_1] +MATA A 25 TRÍADAS + +[DIAB4_A] +¡Un ladrón oportunista robó una camioneta con mi última publicación! + +[DIAB4_B] +Pero ese idiota drogado de SPANK dejó las puertas de atrás abiertas, + +[DIAB4_C] +¡y ahora mi literatura para adultos + +[DIAB4_D] +tan bellamente producida, tan gustosamente fotografiada, está siendo esparcida por toda Liberty! + +[DIAB4_E] +Monta en la camioneta y sigue el rastro de los volúmenes 1, 2 y 3 de Donkey Does Dallas, + +[DIAB4_F] +tomándolos por el camino. + +[DIAB4_G] +¡Cuando alcances a ese bandido espaciado con SPANK, chíngatelo! + +[DIAB4_H] +Luego reparte mis revistas XXX por el Red Light District. + +[DIAB4_1] +~g~Lleva la furgoneta a la parte de atrás de Revistas XXX. + +[HM1_E] +Quiero que esos mierdecillas sepan lo que es un verdadero tiroteo desde un vehículo. + +[HM1_H] +¡Haz que esos Nines se larguen! + +[HM2_A] +Los Nines me están presionando. + +[HM2_B] +Tienen coches blindados y ahora están pasando SPANK + +[HM2_C] +a los hermanos sin ningún pudor. + +[HM2_D] +Hay un coche aparcado en la calle. + +[HM2_E] +Dentro lleva algo que te servirá para poner a esos gallinas en su sitio + +[HM3_A] +Algún cazurro me ha puesto una bomba en el coche. + +[HM3_B] +Si lo pierdo, mi reputación en la calle se irá a la porra. + +[HM3_C] +Coge mi coche y llévalo al taller de Saint Mark's, ¿valiendo? + +[HM3_D] +Que se encarguen ellos de desarmar la bomba. + +[HM3_E] +El tiempo corre y ese trasto es un peligro. + +[HM3_F] +Un golpe más de la cuenta y podría saltar por los aires. + +[HM3_G] +¡Tira! + +[HM4_A] +Tío, un vuelo de la Reserva Federal se la acaba de pegar en el Aeropuerto Francis. + +[HM4_B] +Hay platino por toda la pista. + +[HM4_C] +Consigue un coche y agarra todo lo que puedas. + +[HM4_F] +Puedes dejar la mercancía en uno de mis garajes. + +[HM4_G] +El platino pesa un huevo y hará que tu buga vaya a paso de tortuga, + +[HM4_H] +así que ve dejándolo de vez en cuando en el garaje. + +[HM5_A] +Ya sólo quedan unos pocos Nines, + +[HM5_B] +pero todavía quieren guerra. + +[HM5_C] +Han aceptado un mano a mano. + +[HM5_D] +Un puñado de los suyos contra dos de los nuestros, + +[HM5_E] +o más bien, tú y uno más. + +[HM5_F] +Os ayudaría, pero... + +[HM5_G] +No quiero jugarme mi libertad condicional, + +[HM5_H] +¿me captas? + +[HM5_I] +Reúnete con mi hermano pequeño, + +[HM5_J] +él te enseñará dónde será la pelea. + +[MEA1_B] +Me llamo Chonks, Marty Chonks. + +[MEA1_C] +Soy el dueño de la fábrica de comestibles Bitch'n' Dog que está a la vuelta de la esquina. + +[MEA1_D] +Tengo problemas económicos, ¿pero y quién no? + +[MEA1_E] +He quedado con el director de mi banco. + +[MEA1_F] +Es un chorizo que no para de inflar los intereses del préstamo para sacarme una buena tajada. + +[MEA1_G] +Coge mi coche, búscale y tráelo aquí. + +[MEA1_H] +¡Tengo una sorpresita para ese parásito chupasangre! + +[MEA2_A] +Contraté a unos ladrones para que entraran en mi piso + +[MEA2_C] +Ahora los muy cabritos me han amenazado con delatarme + +[MEA2_D] +si no les doy una parte. + +[MEA2_E] +¿Te lo puedes creer? + +[MEA2_F] +He dejado un coche dentro de la fábrica. + +[MEA2_G] +Ve a recogerles con él en su territorio, en el Red Light District. + +[MEA2_H] +Luego tráelos a la fábrica para que conozcan la opinión de Marty. + +[MEA3_A] +El negocio se irá a pique si no consigo un pastizal muy pronto. + +[MEA3_B] +Mi esposa tiene un seguro de vida y ella no ha hecho más que vaciarme los bolsillos. + +[MEA3_C] +He dejado un coche donde siempre. + +[MEA3_D] +Ve a por a mi esposa a Classic Nails y tráela a la fábrica. + +[MEA4_A] +Mierda, ¡la he liado! + +[MEA4_B] +Resulta que mi esposa salía con uno al que debo dinero. + +[MEA4_C] +¡Se ha cabreado y ahora quiere vengarse! + +[MEA4_E] +y piensa que voy a devolverle el dinero. + +[MEA4_F] +Pero creo... + +[MEA4_G] +¡que los perros de Liberty van a disfrutar de un sabor nuevo este mes! + +[WELCOME] +BIENVENIDO A + +[HM1_2] +~g~Hazte con un vehículo y recuerda que sólo cuentan las muertes a tiros desde el coche. + +[HELP8_B] +Pulsa ~h~~k~~PED_SNIPER_ZOOM_IN~ ~w~para ~h~aumentar el zoom ~w~de la mira del fusil y ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ para ~h~reducirlo~w~. + +[LRQC_1] +Asuka y yo tenemos que hablar... + +[LRQC_2] +¿Por qué no te das una vuelta? + +[LRQC_3] +Necesitarás un escondite. + +[LRQC_4] +Hay un almacén en la orilla de Belleville que podría servirte. + +[LRQC_5] +Regresa a mi apartamento cuando estés listo + +[LRQC_6] +y podremos tener una charla. + +[JM6_5] +~g~Necesitas un vehículo de fuga, ¡idiota! + +[JM2_F] +Si necesitas una pipa, ve a la parte de atrás del Ammu-Nation que está enfrente del metro. + +[LOVE4_7] +~g~Hay un edificio en construcción en Staunton Island, puede que hayan llevado el paquete allí. + +[LOVE4_8] +~g~Necesitarás un coche para abrir el garaje. + +[TSCORE] +GANANCIAS: ~1~ $ + +[AM1_9] +~r~¡Salvatore ha vuelto a meterse en el club de Luigi! + +[AM1_6] +~g~Si te dejas ver por el club de Luigi, ¡la mafia te descubrirá! + +[TM2_3] +~g~¡Es una trampa! ¡Liquidadlos a todos! + +[FM4_1] +Soy María. ¡El coche es una trampa! Estoy en el lado sur del puente Callahan. + +[JM1_7] +~g~¡Cierra la puerta del coche o se dará cuenta! + +[KM5_1] +~g~¡TRAFICANTE LIQUIDADO! + +[KM5_6] +~g~Debes matar a al menos 8 traficantes jamaicanos. + +[KM5_7] +~g~¡Mátalos rápido! En cuanto vendan el SPANK se retirarán de las calles. + +[RM3_8] +~r~¡El coche es un señuelo! + +[LM3_8] +Hola, soy Joey. + +[LM3_9] +Luigi dijo que eras de fiar, así que vuelve más tarde, + +[KM3_5] +~g~Toca el claxon para empezar con el trato. + +[LOVE7] +'LA DESAPARICIÓN DE LOVE' + +[LOVE2_5] +~g~¡Kenji ha palmado! ¡Sal de Newport y deshazte del coche! + +[AS2_11] +~g~¡~1~ DE 9! + +[GARAGE1] +~g~Sal del vehículo y aléjate caminando. + +[KM3_11] +~g~El cártel ha sido atacado y el maletín no ha sido recuperado. + +[KM3_12] +~g~Mata a todos los colombianos, destruye los vehículos y recupera el maletín. + +[KM3_13] +~g~Lleva el maletín de vuelta al casino. + +[RM5_6] +~g~¡Ha salido de la ambulancia! ¡Cárgate su escayola con un vehículo o una explosión! + +[PBOAT_1] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar los cañones de la lancha. + +[PBOAT_2] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar los cañones de la lancha. + +[DIAB1_B] +Al habla El Burro, de los Diablos. + +[DIAB1_D] +Eres nuevo en Liberty, pero ya te estás ganando una reputación en las calles. + +[DIAB1_E] +Hay una carrera que empezará junto a la sala Clásica, cerca del puente Callahan. + +[DIAB1_F] +Consíguete un buen carro y el primero que pase por todos los puntos de control se llevará el premio. + +[HM2_1] { re3 change } +Usa los coches teledirigidos para destruir los furgones blindados. Pulsa ~h~~k~~VEHICLE_FIREWEAPON~ ~w~para detonarlos. + +[HM2_1A] { re3 change } +Usa los coches teledirigidos para destruir los furgones blindados. Pulsa ~h~~k~~VEHICLE_FIREWEAPON~ ~w~para detonarlos. + +[HM2_2] +~r~¡No has destruido todos los furgones blindados! + +[HM2_6] +~g~¡Te has cargado un furgón blindado! + +[RM3_A] +Conozco a un pez gordo de la ciudad, un tipo muy cándido, + +[RM3_H] +con, digamos... gustos exóticos y el dinero para pagárselos. + +[RM3_B] +Está involucrado en un asunto legal y la fiscalía tiene fotos de él muy comprometedoras + +[RM3_C] +en una fiesta en la morgue o algo así. + +[LOVE6_A] +Una lección sobre negocios, amigo mío: + +[LOVE6_E] +Si tienes una mercancía única, todo el mundo tratará de arrebatártela, + +[LOVE6_C] +Unos SWAT han acordonado la zona donde se encuentran mi socio y el paquete. + +[LOVE6_D] +Ve allí, recoge la furgoneta y haz de señuelo. + +[LOVE6_F] +Mantenlos ocupados para que él pueda escapar. + +[AM3_C] +Ahora mismo estará en la bahía. ¡Roba una lancha de la policía y hunde su carrera! + +[FESZ_UC] +CANCELAR + +[FEDS_SM] +L1, R1 - CAMBIAR MENÚ + +[FEDS_AS] +;= - CAMBIAR SELECCIÓN + +[FEDSAS2] +<> - CAMBIAR SELECCIÓN + +[FEDS_SS] +L1,R1 - CAMBIAR SELECCIÓN + +[FEDSSC1] +; - ACELERAR + +[FEDSSC2] += - RALENTIZAR + +[MEA2_3] +~g~Lleva el coche de vuelta a la fábrica. + +[RM1_3] +~r~¡McAffrey se ha escapado! + +[RM1_4] +~g~¡Has usado todas las granadas! ¡Consigue más en la tienda Ammu-Nation! + +[RM1_5] +~g~¡Vuelve y quema la casa franca! + +[RM6_4] +~g~Ve al almacén a por el cargamento de Ray. + +[RM6_5] +~g~La CIA vigila el puente, busca otro camino. + +[HM2_F] +y cargarte sus blindados. + +[HM_4] +'FIEBRE DEL PLATINO' + +[MEA4_B7] +Pero si pasas por mi oficina... + +[MEA3_B4] +¿Marty quiere verme? Bueno, pues que sea rápido, porque tengo que ir a la peluquería. + +[KM3_7] +¡Es una trampa de la yakuza, man! + +[FES_LOF] +Fallo al cargar. + +[P1INSA] +La Memory Card (PS2) insertada en la ranura para MEMORY CARD 1 tiene ~1~ KB de espacio disponible. Necesitas ~1~ KB para guardar. + +[P1INSN] +La Memory Card (PS2) insertada en la ranura para MEMORY CARD 1 no tiene espacio suficiente. Por favor, borra algunos archivos. + +[FES_SLO] +ARCHIVO + +[FES_ISC] +ESTÁ DAÑADO + +[FESZ_TI] +ARCHIVO Z1 + +[FESZ_SA] +Guardar partida + +[P1NOIN] +No hay una Memory Card (PS2) insertada en la ranura para MEMORY CARD 1. + +[P1INSE] +Memory Card (PS2) insertada en la ranura para MEMORY CARD 1. + +[MC_LDFL] +¡Fallo al cargar! + +[MC_NWRE] +Reiniciando partida. + +[LOVE6_3] +~g~Tienes ~1~ segundos para volver al Securicar antes de fracasar la misión. + +[LOVE6_4] +~r~¡Has abandonado el Securicar señuelo! + +[HELP1] +Detente en el centro del marcador azul. + +[HELP12] +Entra en el marcador azul para comenzar una misión. + +[HJSTAT] +Distancia: ~1~,~1~ m. Altura: ~1~,~1~ m. Vueltas: ~1~. Rotación: ~1~_. + +[HJSTATW] +Distancia: ~1~,~1~ m. Altura: ~1~,~1~ m. Vueltas: ~1~. Rotación: ~1~_. ¡Y qué buen aterrizaje! + +[DIAB1_5] +TIEMPO DE CARRERA: + +[LOVE3_4] +~r~¡Has destruido la avioneta! + +[F_FAIL1] +¡Misión del camión de bomberos terminada! + +[F_CANC] +~r~¡Misión del camión de bomberos cancelada! + +[F_EXTIN] +INCENDIOS: + +[A_COMP1] +¡Misiones de conductor de ambulancia completadas! + +[A_CANC] +~r~¡Misión de conductor de ambulancia cancelada! + +[A_COMP3] +¡Misiones de conductor de ambulancia completadas! ¡Ahora no te cansarás al esprintar! + +[ATUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de conductor de ambulancia. + +[ATUTOR3] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de conductor de ambulancia. + +[ALEVEL] +Nivel de misión de conductor de ambulancia: ~1~ + +[A_FAIL1] +Misión de conductor de ambulancia terminada. + +[FEST_HA] +Mayor nivel de misión de cond. de ambulancia + +[A_SAVES] +PERSONAS SALVADAS: ~1~ + +[C_KILLS] +CRIMINALES ABATIDOS: ~1~ + +[HM1_B] +Tengo un problema, me la están liando. + +[AM2_A] +La muerte de Salvatore es una placentera noticia, + +[AM2_A2] +eres un asesino eficaz. Eso me gusta en un hombre. + +[AM2_B] +Éste es mi hermano Kenji. + +[AM2_C] +Asuka tiene un trabajito para ti, pero cuando acabes, pásate por mi casino y podremos hablar. + +[AM2_D] +Típico de Kenji, siempre está detrás de mis juguetes. + +{ The voiced dialogue doesn't match the English subtitle for the next string } + +[AM2_E] +Mi contacto en la policía me dice que el FBI ha montado un operativo de vigilancia + +{ The voiced dialogue doesn't match the English subtitle for the next string } + +[AM2_E2] +en varios puntos de la ciudad. + +{ The voiced dialogue doesn't match the English subtitle for the next string } + +[AM2_F] +No tenemos tiempo para contactar con nadie y evitar que nos incriminen. + +{ The voiced dialogue doesn't match the English subtitle for the next string } + +[AM2_G] +Liquida a esos polis espías, pero cuidado: tendrán apoyo. + +[F_START] +~g~Se ha avistado un vehículo en llamas en: ~a~. Ve y extingue el fuego. + +[AM4_1A] +Ve a la cabina al oeste del parque Belleville. + +[AM4_1B] +Ve a la cabina del campus de Liberty. + +[AM4_1C] +Ve a la cabina al sur del parque Belleville. + +[AM4_1D] +Ven a verme a los baños públicos del parque. + +[HJSTATF] +Distancia: ~1~ pies. Altura: ~1~ pies. Vueltas: ~1~. Rotación: ~1~_. + +[HJSTAWF] +Distancia: ~1~ pies. Altura: ~1~ pies. Vueltas: ~1~. Rotación: ~1~_. ¡Y qué buen aterrizaje! + +[HM1_F] +Pero ojo, también habrá Jacks que se creerán que vas a por ellos. + +[HM1_D] +Se llaman ''Nines'', van de púrpura, y cada día en el que se hacen notar + +[HM1_G] +es otro día más que los Jacks parecemos blandos. + +[MEA2_B] +y robaran cuanto pillaran para que yo pudiera reclamar a la aseguradora. + +[TM3_H] +Buen trabajo, chaval, muy bueno. + +[TM3_I] +Vente, te presentaré al Don. + +[TM3_J] +¡Hola! ¡Luigi! + +[TM3_K] +Mis chicas te han echado de menos, Salvatore. Hace bastante que no te vemos. + +[TM3_L] +Diles que en cuanto resolvamos este desafortunado incidente + +[TM3_M] +iremos todos al club para celebrarlo, ¿vale? + +[TM3_N] +¡Mi niño! + +[TM3_N2] +¿Cómo estás, papá? + +[TM3_O] +¿Has encontrado ya una buena mujer? + +[TM3_P] +Tu madre, que en paz descanse, se retorcería en la tumba + +[TM3_Q] +si te viera sin una mujer. + +[TM3_R] +Lo sé, papá, estoy en ello. + +[TM3_S] +¡Toni! ¿Cómo está tu madre? + +[TM3_T] +Es una gran mujer, ¿sabes? Fuerte, ''firenze''. + +[TM3_U] +Está bien, estupendamente. + +[TM3_V] +Fantástico, fantástico. Bien, muchachos, entrad mientras yo hablo con nuestro nuevo amigo. + +[TM3_W] +Tienes un gran futuro por delante, hijo mío... + +[RM1_A] +¡Ese canalla de McAffrey...! ¡Aceptó más sobornos que nadie, + +[RM1_B] +se piensa que le darán la pensión completa si se convierte en un testigo de cargo! + +[RM1_C] +¡Y ha cantado! + +[RM4_B] +Tenemos que cerrarle la boca para siempre. + +[RM4_E] +¡Quiero que deje de comer peces y se vaya a dormir con ellos! + +[LOVE3_B] +Esta noche, durante su aproximación al aeropuerto, una avioneta sobrevolará la bahía. + +[LOVE4_D] +Desgraciadamente, las autoridades aduaneras incautaron la avioneta y la comenzaron a desmontar + +[LOVE4_H] +hasta que yo intervine a través de mi fortuna. + +[LOVE4_E] +Cruza el puente hacia Shoreside Vale y ve al Aeropuerto Internacional Francis. + +[GTAB_A] +Oye, saquemos esto de aquí. Dios sabrá lo que será, + +[GTAB_B] +pero él lo quiere a toda costa, así que tendrá algún valor. + +[GTAB_C] +¡¿Qué diablos?! + +[GTAB_D] +¡TÚ! + +[GTAB_E] +¡Tranquilo, amigo! ¡No es nada! ¡No es nada! + +[GTAB_F] +¡Te dejé desangrándote entre la basura! + +[GTAB_G] +No dispares, amigo. No hay problema. Somos amigos. Mira, coge esto. + +[GTAB_H] +¡No seas cagón! + +[GTAB_I] +¡No tenemos elección, nena! + +[GTAB_J] +¡Siempre la hay, imbécil! + +[GTAB_K] +¡Siento mucho lo de esa perra enloquecida, todas son iguales...! ¿Por favor? + +[GTAB_L] +Así que la zorra se fue. + +[GTAB_M] +Pero me has hecho un favor, + +[GTAB_N] +no eres el único que tiene una cuenta pendiente con el cártel... + +[GTAB_O] +¡Este gusano mató a mi hermano! + +[GTAB_P] +¡Nunca maté a ningún yakuza! + +[GTAB_Q] +¡Mientes! Todos vimos al asesino del cártel. + +[GTAB_R] +¡Vamos a cazaros y a mataros a todos, perros colombianos! + +[GTAB_S] +Me trabajaré a nuestro querido amigo para extraerle información y un poco de placer. + +[GTAB_T] +Tú, ven más tarde, estoy segura de que voy a necesitar tus servicios. + +[GTAB_U] +¡Por favor, amigo! ¡No me dejes con ella, esa chica está loca! ¿Amigo? ¡Oye, amigo! ¡Amigo...! + +[LOVE5_A] +Estás demostrando ser una inversión segura, algo muy raro en estos días. + +[KM3_1] +~g~El cártel espera a una banda jamaicana, ¡así que roba uno de sus coches! Dirígete al norte, encontrarás uno en Newport. + +[LOVE1_1] +~g~Roba un coche de la banda colombiana para que puedas entrar en su escondite. Ve al norte, encontrarás uno en Fort Staunton. + +[FM1_Q1] +¿Quieres un poco de diversión? ¿Un poco de... hmmm, de SPANK? + +[FM1_R] +Hola, Chico. No, sólo lo de siempre. + +[FM1_T] +Gracias, Chico, nos vemos. + +[FM1_W] +Oye, tú, espera aquí y quédate pendiente del coche mientras yo voy a menear el esqueleto, ¿vale? + +[FM1_X] +¡Vale, tú, salgamos de aquí! ¡Uaaah! + +[FM1_Q] +¡Ay, mira, es mi chica favorita! + +[FM1_S1] +Ey, deberías echar un vistazo a la fiesta del almacén en el lado este del muelle Atlantic. + +[FM1_U] +Gracias y disfruta. Es buen material. + +[FM1_V] +¡Venga, tú, vamos a ver esa fiesta! + +[FM1_SS] +~r~ESCÁNER: ~g~Cuatro-cinco a todas las unidades: asistan redada de narcóticos en el muelle Atlantic. + +[LOVE6_B] +aun sin conocer su verdadera valía. + +[TM3_A1] +~r~¡Joey está frito! + +[TM3_A2] +~r~¡Joey y Luigi han sido incinerados! + +[TM3_A3] +~r~¡Joey, Luigi y Toni están calcinados! + +[FM4_2] +Mira, Salvatore cree que se la estamos jugando, + +[FM4_3] +así que te vendió al cártel para llegar a un trato. + +[FM4_4] +No podía permitírselo, o sea, lo peor es... + +[FM4_4B] +que yo tengo la culpa, porque le dije que somos pareja. + +[FM4_5] +¡No me preguntes por qué, no lo sé! + +[FM4_6] +Mira, la mafia te quiere muerto y yo también tengo que salir de aquí. + +[FM4_6B] +¡He visto demasiados asesinatos, demasiada sangre! + +[FM4_7] +Es una amiga mía, ¿vale?, una vieja amiga... Es Asuka, es de fiar. + +[FM4_8] +Venga, dejémonos de discursos. + +[FM4_9] +Mejor nos vamos antes de que lleguen más italianos histéricos con intenciones menos amistosas. + +[CRED001] +ROCKSTAR STUDIOS + +[CRED002] +PRODUCTOR + +[CRED003] +LESLIE BENZIES + +[CRED004] +DIRECTOR DE ARTE + +[CRED005] +AARON GARBUT + +[CRED006] +DIRECCIÓN TÉCNICA + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED009] +DISEÑO + +[CRED010] +CRAIG FILSHIE + +[CRED011] +WILLIAM MILLS + +[CRED012] +CHRIS ROTHWELL + +[CRED013] +JAMES WORRALL + +[CRED014] +GUIÓN + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +PERSONAJES + +[CRED019] +IAN MCQUE + +[CRED020] +ANIMACIÓN Y DIRECCIÓN + +[CRED021] +ALEX HORTON + +[CRED022] +LEE MONTGOMERY + +[CRED023] +DISEÑO DE VEHÍCULOS + +[CRED024] +PAUL KUROWSKI + +[CRED025] +ARTISTAS + +[CRED026] +KEIRAN BAILLIE + +[CRED027] +ADAM COCHRANE + +[CRED028] +GARY MCADAM + +[CRED029] +MICHAEL PIRSO + +[CRED030] +ANDREW SOOSAY + +[CRED031] +ALISDAIR WOOD + +[CRED032] +PROGRAMADORES + +[CRED033] +ALAN CAMPBELL + +[CRED034] +MARK HANLON + +[CRED035] +ANDRZEJ MADAJCZYK + +[CRED036] +ALEXANDER ROGER + +[CRED037] +GRAEME WILLIAMSON + +[CRED038] +MÚSICA + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +DISEÑO DE SONIDO Y MÁSTER + +[CRED042] +ALLAN WALKER + +[CRED043] +PROGRAM. DE AUDIO + +[CRED044] +RAYMOND USHER + +[CRED045] +DIRECTOR DE PRUEBAS + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +JEFES DE PRUEBAS + +[CRED048] +ANDY DUTHIE + +[CRED049] +JOHN HAIME + +[CRED050] +NEIL CORBETT + +[CRD050A] +PROBADORES + +[CRED051] +GRAEME JENNINGS + +[CRED052] +DAVID MURDOCH + +[CRED053] +DAVID BEDDOES + +[CRED054] +EDWIN SMITH + +[CRED055] +MARK FLETT + +[CRED056] +MICHAEL SUTHERLAND + +[CRED057] +SOPORTE TÉCNICO + +[CRED058] +LORRAINE ROY + +[CRED059] +CHRISTINE CHALMERS + +[CRED060] +ROCKSTAR + +[CRED061] +PRODUCTOR EJECUTIVO + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCTOR + +[CRED064] +DAN HOUSER + +[CRED065] +DIRECTOR DE DESARROLLO + +[CRED066] +JAMIE KING + +[CRED067] +PRODUCTOR TÉCNICO + +[CRED068] +GARY J. FOREMAN + +[CRED069] +PRODUCTOR ASOCIADO + +[CRED070] +JEREMY POPE + +[CRED071] +SUPERVISOR MUSICAL + +[CRED072] +TERRY DONOVAN + +[CRED073] +EQUIPO DE PRODUCCIÓN DE ROCKSTAR + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +CHRIS CARRO + +[CRED080] +ADAM TEDMAN + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +PAUL YEATES + +[CRED084] +STANTON SARJEANT + +[CRED085] +V.P. DE MÁRKETING + +[CRED086] +TERRY DONOVAN + +[CRED087] +COORDINADOR TÉCNICO + +[CRED088] +BRANDON ROSE + +[CRED089] +DIRECTOR DE CONTROL DE CALIDAD + +[CRED090] +JEFF ROSA + +[CRED091] +JEFE DE ANÁLISIS + +[CRED092] +ADAM DAVIDSON + +[CRED093] +ANALISTA DEL JUEGO + +[CRED094] +RICHARD HUIE + +[CRED095] +EQUIPO DE PRUEBAS + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRED099] +OSWALD GREENE + +[CRED100] +EDITORIAL DEL LIBERTY TREE + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CINEMÁTICAS + +[CRED108] +GUIÓN DE DAN HOUSER Y JAMES WORRALL + +[CRED109] +AUDIO DIRIGIDO POR DAN HOUSER + +[CRED110] +AUDIO PRODUCIDO POR RENAUD SEBBANE + +[CRED111] +REPARTO + +[CRED112] +FRANK VINCENT - SALVATORE LEONE + +[CRED113] +JOE PANTOLIANO - LUIGI GOTERELLI + +[CRED114] +MICHAEL MADSEN - TONI CIPRIANI + +[CRED115] +MICHAEL RAPAPORT - JOEY LEONE + +[CRED116] +DEBBI MAZAR - MARÍA + +[CRED117] +KYLE MACLACHAN - DONALD LOVE + +[CRED118] +ROBERT LOGGIA - RAY MACHOWSKI + +[CRED119] +GURU - 8-BALL + +[CRED120] +SONDRA JAMES - MAMMA + +[CRED121] +LIANA PAI - ASUKA + +[CRED122] +LES MAU - KENJI + +[CRED123] +CYNTHIA FARRELL - CATALINA + +[CRED124] +AL ESPINOSA - MIGUEL + +[CRED125] +CHRIS PHILLIPS - EL BURRO + +[CRED126] +HUNTER PLATIN - CHICO + +[CRED127] +WALTER MUDU - D-ICE + +[CRED128] +CURTIS MCCLARIN - CURTLY + +[CRED129] +BILL FIORE - DARKEL + +[CRED130] +CHRIS PHILLIPS - MARTY CHONKS + +[CRED131] +HUNTER PLATIN - CURLY BOB + +[CRED132] +WALTER MUDU - REY COURTNEY + +[CRED133] +HUNTER PLATIN - PHIL EL MANCO + +[CRED134] +KIM GURNEY - MISTY + +[CRED135] +CAPTURA DE MOVIM. + +[CRED136] +ANIMACIÓN + +[CRD136A] +ALEX HORTON + +[CRED137] +DIRECCIÓN + +[CRD137A] +NAVID KHONSARI + +[CRED138] +PRODUCCIÓN + +[CRD138A] +JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +GRABADA EN MODERN UPRISING STUDIOS, BROOKLYN + +[CRED140] +ACTORES + +[CRD140A] +RENAUD SEBBANE + +[CRD140B] +GISELLE JONES + +[CRD140C] +STEPHEN DANIELS + +[CRD140D] +ROBERT STIO + +[CRD140E] +JENNY GROSS + +[CRED141] +DIÁLOGO DE PEATONES + +[CRED142] +ESCRITO POR DAN HOUSER, NAVID KHONSARI Y JAMES WORRALL + +[CRED143] +DIRIGIDO POR CRAIG CONNER, DAN HOUSER Y LAZLOW + +[CRED144] +PRODUCIDO POR RENAUD SEBBANE + +[CRED145] +REPARTO + +[CRED146] +HUNTER PLATIN + +[CRED147] +DAN HOUSER + +[CRED148] +RENAUD SEBBANE + +[CRED149] +MARIA CHAMBERS + +[CRED150] +JEFF STANTON + +[CRED151] +RYAN CROY + +[CRED152] +DEENA BERMAN + +[CRED153] +MARIA CHAMBERS + +[CRED154] +ALICE B. SALTZMAN + +[CRED155] +ALEX ANTHONY SIOUKAS + +[CRED156] +SEAN R. LYNCH + +[CRED157] +AMY SALZMAN + +[CRED158] +COLIN MCSHANE + +[CRED159] +COREY WADE + +[CRED160] +GERALD COSGROVE + +[CRED161] +STEPHANIE ROY + +[CRED162] +DORIS WOO + +[CRED163] +JOSEPH GREENE + +[CRED164] +LAZLOW JONES + +[CRED165] +HSIANG LIN + +[CRED166] +STEVE MICHAEL ROBERT + +[CRED167] +MATHEW MURRAY + +[CRED168] +RICHARD HUIE + +[CRED169] +GARVIN ATWELL + +[CRED170] +STEVE KNEZEVICH + +[CRED171] +YUKIMURA SATO + +[CRED172] +FRANK CHAVEZ + +[CRED173] +LIEZL JACINTO + +[CRED174] +CANAAN MCKOY + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCION + +[CRED180] +CHARLES HEROLD + +[CRED181] +ANDREW GREENWALD + +[CRED182] +JAMES MIELKE + +[CRED183] +PETER SUCIU + +[CRED184] +ALEX ODULIO + +[CRED185] +DON NKRUMAH + +[CRED186] +KENDALL PITTMAN + +[CRED187] +SAL SUAZO + +[CRED188] +EREK MATEO + +[CRED189] +CHRIS DIFATE + +[CRED190] +LEILA MILTON + +[CRED191] +DARREN ZOLTOWSKI + +[CRED192] +VIRGINIA SMITH + +[CRED193] +KEVIN CASSIN + +[CRED194] +JASON SHIGEMORI + +[CRED195] +KELLY KINSELLA + +[CRED196] +MOLLIE STICKNEY + +[CRED197] +STANTON SARJEANT + +[CRED198] +LAURA WALSH + +[CRED199] +MARK GARONE + +[CRED200] +JOANNA SLY + +[CRED201] +ELIZABETH HOWELL + +[CRED202] +ANA HERCULES + +[CRED203] +SHIRLEY IRICK + +[CRED204] +KASHONA FIELDS + +[CRED205] +JOEL M. LILJE + +[CRED206] +JOHN DIBENEDETTO + +[CRED207] +NANCY GILES + +[CRED208] +RYAN CROY + +[CRED209] +JENNIFER KOLBE + +[CRED210] +LIAM BURKE + +[CRED211] +SIGRID PREISSL + +[CRED212] +ANITA FITZSIMONS + +[CRED213] +PHILIPPA RASELLI + +[CRED214] +WIL QUESNEL + +[CRED215] +FALKO BURKERT + +[CRED216] +SARA SEWELL + +[CRED217] +EMISORAS DE RADIO Y MÚSICA + +[CRED218] +PRODUCTORES DE ROCKSTAR REINO UNIDO + +[CRD218A] +CRAIG CONNER + +[CRD218B] +STUART ROSS + +[CRED219] +COORDINADOR DE BANDA SONORA + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCTOR DE ROCKSTAR GAMES + +[CRED222] +DAN HOUSER + +[CRED223] +EDICIÓN + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +GUIÓN DE LOCUTORES Y CUÑAS + +[CRED228] +DAN HOUSER + +[CRED229] +LAZLOW + +[CRED230] +AGRADECIMIENTOS + +[CRED231] +ADAM TEDMAN + +[CRED232] +ALEX MASON + +[CRED233] +JUDY HENDERSON CASTING + +[CRED234] +HAMISH BROWN + +[CRED235] +CHRISSY HOBAN + +[CRED236] +INNES RICARD + +[CRED237] +LILION BROZSKA + +[CRED238] +BOB HILLARY + +[CRED239] +EMILY ANDERSON + +[CRED240] +RICHIE HENDERSON + +[CRED241] +CHRISTIAN CANTAMESSA + +[CRED242] +JERONIMO BARRERA + +[CRED243] +ALEXANDER ILLES + +[CRED244] +BARANE CHAN + +[CRED245] +DUNCAN SHIELDS + +[CRED246] +BARANE CHAN + +[CRED247] +DEREK PAYNE + +[CRED248] +KEVIN WONG + +[CRED249] +ROSS ELLIOTT + +[CRED250] +ROSS BEAZLEY + +[CRED251] +ALEX BAZLINTON + +[CRED252] +DAVE WATSON + +[CRED253] +MALCOLM SMITH + +[CRED255] +ANDREW SEMPLE + +[CRED256] +ARTISTAS + +[CRED257] +STUART PETRI + +[CRED258] +JERONIMO BARRERA + +[CRED259] +CARLY SLATER + +[CRED260] +GREG LAU + +[CRED261] +STEVE KNEZEVICH + +[CRED262] +DEVIN WINTERBOTTOM + +[CRED263] +JAMEEL VEGA + +[CRED264] +LEE CUMMINGS + +[CRED265] +DEVIN BENNET + +[CRED266] +ELIZABETH SATTERWHITE + +[CRED267] +AARON RIGBY + +[CRED268] +STEVE K. + +[CRED269] +GREG LAU + +[CINCAM] +Cámara cinematográfica + +[KM1_13] +¡Mete el vehículo en el garaje! + +[KM3_14] +~r~¡Te han descubierto, han cancelado el trato! + +[EBAL_H] +Espera, tío, que voy a hablar con Luigi. + +[EBAL_M] +¡Recuerda: nadie se mete con mis chicas! + +[LM2_F] +Luego coge su coche y repíntalo. + +[LM2_D] +Toma, toma, para ti. + +[LM1_9] +Hola, soy Misty. + +[LM4_A] +Algún Diablo de las narices ha estado vendiendo a sus putitas en mi territorio. + +[FM2_B] +¡Tenemos a un chivato! + +[FM2_C] +No está ni chuleando ni traficando, así que estará cantando. + +[FM3_CC] +Regresa cuando tengas el dinero. + +[FEDS_AM] +<> - CAMBIAR MENÚ + +[LOVE5_5] +~r~¡No has protegido al camión! + +[RM6_6] +~r~¡Ray ha muerto! + +[RM6_7] +~r~¡Ray ha perdido su avión! + +[RM6_8] +~g~Has abandonado a Ray, vuelve a por él. + +[FM1_10] +~g~Has abandonado a María, vuelve a por ella. + +[LOVE4_9] +~r~¡El avión ha sido destruido! + +[LOV4_10] +~r~¡La única pista sobre dónde se encuentra el paquete ha sido destruida! + +[KM2_D] +No hace falta decir que debemos darle los coches como un regalo para pagar mi deuda con él. + +[KM4_B] +El negocio es lo bastante afortunado como para permitir que nuestra protección salde sus cuentas hoy mismo. + +[KM2_E] +Debes obtener los coches de la lista y llevarlos a un garaje que hay tras el aparcamiento de Newport. + +[FM3_8I] +Encuentra una posición elevada. Entraré cuando dispares el primer tiro. + +[LOVE1_B] +La experiencia me ha enseñado que un hombre como tú puede ser muy leal por el precio correcto, + +[LOVE1_H] +pero los grupos de hombres se vuelven codiciosos. + +[LOVE1_C] +Un activo valioso, un anciano oriental que conozco, + +[LOVE1_I] +está siendo retenido por unos sudamericanos en Aspatria. + +[MEA4_D] +He quedado con él, + +[MEA4_B4] +¿Te envía Marty? Vale, le voy a enseñar a ese sinvergüenza el significado de la palabra negocio. + +[MEA4_B5] +¡Carl, hola! Ehhh... Necesito más tiempo para conseguir tu dinero. + +[MEA1_B4] +Ah, te envió el Sr. Chonks, ¿verdad? Vayamos a visitarlo. + +[HM5_6] +Vamos a partir cabezas... + +[LOVE1_5] +~g~Deja de perder el tiempo, hazte con un coche de los colombianos y rescata al socio de Love. + +[AS1_D] +Haz de cebo y consigue que los escuadrones te sigan hasta Pike Creek, + +[AS1_E] +donde estarán esperando algunos de mis hombres. + +[AS2_C] +El cártel tiene una tapadera, el Kappa Coffee House. + +[AS2_E] +No tenemos otra opción más que sabotear esos puntos de venta. + +[AS2_F] +¡Redúcelos a cenizas! + +[AS2_A1] +¡Está claro que Miguel tiene esa famosa resistencia latina! + +[AS2_A2] +Estoy agotada. + +[SIREN_3] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_4] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[AS3_C] +¡Buaj! ¿Qué es esa cosa amarilla pegajosa? + +[AS3_C1] +¡Hola, guapo! + +[AS3_F] +Esta chica tiene un talento nato para la tortura. + +[AS3_F1] +Se las ha arreglado para extraerle esta joyita a nuestro invitado. + +[AS3_G] +Hay una avioneta que llegará al Aeropuerto Francis en dos horas. + +[AS3_G1] +Está llena del veneno de Catalina. + +[AS3_H] +Podrás evitar la seguridad del aeropuerto si conduces una lancha hasta las boyas luminosas, + +[AS3_H1] +así podrás disparar a la avioneta al aterrizar. + +[AS3_I] +¡Recoge el cargamento de entre los escombros y tráelo! + +[AS3_J] +Ten cuidado, guapo, ¿vale? + +[AS3_K] +Ahora prueba con el aceite picante... + +[RM2_F1] +¡Los colombianos llegarán en cualquier momento! + +[RM2_K] +Maldita sea, ¡están aquí! ¡FUEGO A DISCRECIÓN! + +[LOVE2_7] +~g~¡Ahora deshazte del coche! + +[LOVE2_8] +~g~¡Sal de Newport! + +[AM1_F] +Salvatore Leone saldrá del club de Luigi dentro de unas tres horas. (~1~:~1~) + +[LOVE5_C] +Quiero que le sigas y que te asegures de que tanto él como mi paquete llegan a Pike Creek sin daño alguno. + +[FESZ_SR] +¡Error al guardar! Comprueba la Memory Card (PS2) de la ranura de MEMORY CARD 1 e inténtalo de nuevo. + +[FESZ_FO] +¿Deseas formatear la Memory Card (PS2) en la ranura de MEMORY CARD 1? + +[FELZ_FO] +La Memory Card (PS2) de la ranura de MEMORY CARD 1 no tiene formato. + +[FES_NOC] +No hay una Memory Card (PS2) insertada en la ranura para MEMORY CARD 1. + +[FES_LOE] +¡Fallo al cargar! Comprueba la Memory Card (PS2) de la ranura de MEMORY CARD 1 e inténtalo de nuevo. + +[FES_DEE] +¡Fallo al borrar! Comprueba la Memory Card (PS2) de la ranura de MEMORY CARD 1 e inténtalo de nuevo. + +[FORSUC] +Memory Card (PS2) de la ranura de MEMORY CARD 1 formateada con éxito. + +[ERFOUN] +¡Error al formatear la Memory Card (PS2)! + +[ERMCNP] +No hay Memory Card (PS2) en la ranura de MEMORY CARD 1. + +[SVMEM1] +Guardando en la Memory Card (PS2) de la ranura de MEMORY CARD 1. + +[FORSLO] +Formateando Memory Card (PS2) de la ranura de MEMORY CARD 1. + +[SLONFM] +¡Error al formatear la Memory Card (PS2)! + +[SLONDR] +No hay espacio suficiente en la Memory Card (PS2). Inserta una Memory Card (PS2) con, al menos, 500KB de espacio libre en la ranura de MEMORY CARD 1. + +[SLNSP] +No hay espacio suficiente en la Memory Card (PS2). Inserta una Memory Card (PS2) con, al menos, 200KB de espacio libre en la ranura de MEMORY CARD 1. + +[FEFD_WR] +Formateando la Memory Card (PS2) de la ranura de MEMORY CARD 1. No extraigas la Memory Card (PS2), ni reinicies o apagues la consola. + +[FES_ISF] +NO PRESENTE + +[FES_SAG] +PRESENTE + +[SLONNO] +No hay una Memory Card (PS2) en la ranura de MEMORY CARD 1. + +[SLONNF] +La Memory Card (PS2) de la ranura de MEMORY CARD 1 no tiene formato. + +[FESZ_FM] +La Memory Card (PS2) de la ranura de MEMORY CARD 1 no tiene formato. ¿Deseas formatear la Memory Card (PS2)? + +[FESZ_FF] +¡Fallo al formatear! Comprueba la Memory Card (PS2) de la ranura de MEMORY CARD 1 e inténtalo de nuevo. + +[MCDNSP] +No hay espacio suficiente en la Memory Card (PS2) de la ranura de MEMORY CARD 1. Inserta una Memory Card (PS2) con, al menos, 500KB de espacio libre para guardar los datos de esta aplicación. ¿Deseas empezar? (SÍ o NO) + +[MCGNSP] +No hay espacio suficiente en la Memory Card (PS2) de la ranura de MEMORY CARD 1. Se necesitan al menos 200KB para guardar los datos de esta aplicación. ¿Deseas empezar? (SÍ o NO) + +[FESZ_WR] +Guardando datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[FESZ_OW] +Sobrescribiendo datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[FELD_WR] +Cargando datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[FEDL_WR] +Borrando datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[LM2_C] +Luigi dijo que... que te diera esto... + +[LM3_G] +que a Joey no le gusta esperar. Recuerda, tienes un pie dentro... + +[LM5_E] +Saca todo el dinero que puedas a los polis antes de que se lo beban. + +[JM5_C] +Vale, hay un coche cargado con un fiambre en el bar cercano a Callahan Point. + +[RM2_B] +Combatimos juntos en Nicaragua, cuando el país sabía lo que hacía. + +[RM2_C] +En fin, ayer unos cerdos del cártel lo zurraron y le dijeron que volverían hoy para quitarle parte de su género. + +[RM2_D1] +Iría yo mismo, pero la ciática ya está en sus trece... ¡Cof, cof! Así que... buena suerte. + +[CATINF1] +~g~¡Liquida a Catalina! + +[CATINF2] +~g~Sigue al helicóptero para dar con Catalina. + +[BOATIN1] +Sube a una lancha y pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ponerte a los mandos. + +[BOATIN2] +Puedes pulsar ~h~~k~~VEHICLE_ENTER_EXIT~~w~ si estás cerca de una lancha para abordarla. + +[BOATIN3] +Sube a una lancha y pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ponerte a los mandos. + +[BOATIN4] +Puedes pulsar ~h~~k~~VEHICLE_ENTER_EXIT~~w~ si estás cerca de una lancha para abordarla. + +[JM6] +'LA HUIDA' + +[FM1] +'CARABINA' + +[JM1] +'EL ÚLTIMO ALMUERZO DE MIKE ''LABIOS'' ' + +[FM21] +'BASE FUERA: ACTO I' + +[FM3] +'BASE FUERA: ACTO II' + +[AM1] +'SAYONARA, SALVATORE' + +[AM2] +'BAJO VIGILANCIA' + +[KM2] +'ROBO DE VEHÍCULOS' + +[AS3] +'TIERRA-AIRE' + +[RM2] +'ESCASEZ DE MANOS' + +[LOVE6] +'SEÑUELO' + +[LOVE1] +'EL LIBERADOR' + +[RC1] +'DESTRUCCIÓN DE LOS DIABLOS' + +[RC2] +'LA MASACRE DE LA MAFIA' + +[RC3] +'CALAMIDAD EN EL CASINO' + +[RC4] +'EXTERMINIO DE RUMPOS' + +[RM2_E1] +¡No puedo creer que esos mamones amarillos me hayan vuelto a dejar con el culo al aire! + +[GREN_1] +Cuanto más tiempo mantengas pulsado ~h~~k~~PED_FIREWEAPON~~w~, más lejos lanzarás la granada. + +[GREN_2] +Cuanto más tiempo mantengas pulsado ~h~~k~~PED_FIREWEAPON~~w~, más lejos lanzarás la granada. + +[GREN_3] +Cuanto más tiempo mantengas pulsado ~h~~k~~PED_FIREWEAPON~~w~, más lejos lanzarás la granada. + +[LOVE4_G] +Mis pertenencias estarán esperándote en el hangar de aduanas, dentro de la avioneta. + +[KABOOM] +¡BUUUM! + +[SPLAT] +¡PLOF! + +[PANCAK] +¡APLASTADO! + +[SOAKED] +¡AHOGADO! + +[HEAD] +Head Radio + +[DBL_CLF] +Double Clef FM + +[FLASHB] +Flashback FM + +[RISE] +Rise FM + +[LIPS] +Lips 106 + +[CHAT] +Chatterbox FM + +[K_JAH] +K-Jah Radio + +[GAM_FM] +Game Radio FM + +[MSX_FM] +MSX FM + +[TUBE1] +Cuando abra el metro, podrás usarlo para ir a Staunton Island. + +[TUBE2] +Cuando abra Shoreside Vale, podrás salir por la terminal Shoreside al Aeropuerto Internacional Francis. + +[TUBE_2] +Para subirte a un vagón, pulsa el ~h~botón Entrar al vehículo~w~. + +[LEGAL] +~g~¡Elimina la amenaza criminal! + +[GA_2] +He cambiado el motor y la mano de pintura. ¡La poli no te reconocerá! + +[LM1_8A] +Si quieres ganar un dinerillo extra, siempre puedes ''coger prestado'' un taxi... + +[TAXIH1] +Para cerca de un peatón señalado para recogerlo y llevarlo a su destino antes de que se acabe el tiempo. + +[LM5_7] +~g~¡Luigi no estará contento si hay menos de cuatro chicas trabajando en el ~p~baile de la policía~g~! + +[KM2_3] +~g~Recuerda que los ~r~coches ~g~tienen que estar en perfecto estado para ser aceptados en el ~p~garaje~g~. + +[KM5_2] +~g~Uno de los jamaicanos se ha ido. + +[BETRA_A] +Lo siento, cariño. + +[BETRA_B] +Soy una chica ambiciosa, ¿pero tú? + +[BETRA_C] +Eres un pez pequeñito. + +[JAILB_Q] +¡Ándele! + +[JAILB_R] +¡Señor malparido! + +[JAILB_S] +No nos importará matarte. + +[JAILB_T] +Os vais a arrepentir. + +[JAILB_U] +Bien, bien, piérdete. + +[HELP15] +Cuando vayas a pie, mantén pulsado ~h~~k~~PED_LOOKBEHIND~~w~ para~h~ mirar atrás~w~. + +[FEC_LB3] +Mirar atrás + +[FEC_R3] +(botón R3) + +[FES_AFO] +Esta Memory Card (PS2) ya está formateada. + +[FEA_UP] +; + +[FEA_DO] += + +[FEA_LE] +< + +[FEA_RI] +> + +[FEDSAS3] +- CAMBIAR SELECCIÓN + +[FEDSAS4] +;=<> - CAMBIAR SELECCIÓN + +[SPRAY_4] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar el cañón de agua. + +[SPRAY_1] { re3 change } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar el cañón de agua. + +[LITTLE] +LITTLE T + +[NICK] +NICK LOVE + +[AM1_10] +~g~Salvatore saldrá del club de Luigi sobre las 0~1~:~1~. + +[JAILB_V] +Hoy, Liberty City se ha visto conmocionada. + +[JAILB_A] +La policía y los servicios de emergencia lidian con las consecuencias + +[JAILB_B] +de un devastador ataque a un convoy policial ocurrido esta mañana. + +[JAILB_C] +No se ha informado de quiénes eran los prisioneros trasladados en el convoy + +[JAILB_D] +y ningún grupo ha reconocido la autoría. + +[JAILB_E] +El convoy abandonó las oficinas de la policía esta mañana + +[JAILB_F] +para hacer una transferencia de reos a la penitenciaría de Liberty. + +[JAILB_G] +El ataque se produjo en el puente Callahan, + +[JAILB_H] +dejando pocos testigos y al puente seriamente dañado. + +[JAILB_I] +Se cree que algunos de los convictos han perecido en la explosión + +[JAILB_J] +posterior al ataque inicial. + +[JAILB_W] +Pasadas las horas se hizo evidente que el ataque era obra de profesionales, + +[JAILB_K] +ya que la identificación de los prófugos se complicó + +[JAILB_L] +cuando piratas informáticos atacaron las bases de datos de la policía. + +[JAILB_O] +Con el retraso del proyecto del túnel Porter, + +[JAILB_P] +este desastre deja a Portland aislada del resto de la ciudad. + +[JAILB_M] +* + +[JAILB_N] +* + +[JAILB_X] +UNUSED + +[FEDS_SE] +Botón / - ELEGIR + +[FEDS_SB] +Botón / - ELEGIR Botón " - VOLVER + +[TM4_A] +Ah, eres tú. Toni no está. + +[TM4_A2] +Pero te ha dejado una de sus cartitas de amor. + +[DIAB2_A] +¡Empecé mi negocio de entretenimiento exótico sin nada más que lo que cabía en mis pantalones de cuero! + +[LM5_9] +CHICAS: + +[PERPIC] +Paquetes ocultos encontrados + +[CO_ONE] +Paquete oculto ~1~ de ~1~ + +[LOVE3_3] +~g~La avioneta ha tirado ~1~ de los 6 paquetes. + +[FARE11] +~g~Ve al ~w~edificio en construcción ~g~de Fort Staunton. + +[GA_21] +No puedes guardar más coches en este garaje. + +[CHEAT1] +Trucos activados + +[CHEAT2] +Arma trucada + +[CHEAT3] +Salud trucada + +[CHEAT4] +Armadura trucada + +[CHEAT5] +Busca y captura trucado + +[CHEAT6] +Dinero trucado + +[CHEAT7] +Clima trucado + +[AS1_H] +~r~¡No has llevado al escuadrón de la muerte hasta la trampa de la yakuza! + +[FEDS_BA] +Botón " - VOLVER + +[RAMP_A] +¡TODAS LAS MASACRES COMPLETADAS! + +[USJ_ALL] +¡TODAS LAS ACROBACIAS COMPLETADAS! + +[FARE23] +~g~Ve al ~w~taller de importación/exportación~g~ en el distrito de la presa Cochrane. + +[L_TRN_1] +Puedes coger el tren para recorrer Portland. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un tren. + +[L_TRN_2] +Puedes coger el tren para recorrer Portland. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un tren. + +[S_TRN_1] +Puedes coger el metro para recorrer Liberty City. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un tren. + +[S_TRN_2] +Puedes coger el metro para recorrer Liberty City. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para ~h~entrar ~w~o ~h~salir~w~ de un tren. + +[AS1_C] +Ella cuenta con tres escuadrones de la muerte que patrullan por Liberty con el único fin de darte caza. + +[AS1_G] +~r~¡Todos los yakuza están muertos! + +[JAN] +Ene + +[FEB] +Feb + +[MAR] +Mar + +[APR] +Abr + +[MAY] +May + +[JUN] +Jun + +[JUL] +Jul + +[AUG] +Ago + +[SEP] +Sept + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Dic + +[DEFDT] +--:---:---- --:--:-- + +[BUGGY] +COCHES RESTANTES: + +[BONUS] +~g~PRIMA DE ~1~ $ + +[HORN1] +Pulsa el ~h~botón L3~w~ para tocar el ~h~claxon. + +[HORN2] +Pulsa el ~h~botón L1~w~ para tocar el ~h~claxon. + +[HORN3] +Pulsa el ~h~botón R1~w~ para tocar el ~h~claxon. + +[LM3_1A] +Pulsa ~h~~k~~VEHICLE_HORN~~w~ para tocar el ~h~claxon~w~ y avisar a Misty. + +[LM3_1B] +Pulsa ~h~~k~~VEHICLE_HORN~~w~ para tocar el ~h~claxon~w~ y avisar a Misty. + +[LM3_1C] +Pulsa ~h~~k~~VEHICLE_HORN~~w~ para tocar el ~h~claxon~w~ y avisar a Misty. + +[RADIO_A] +Pulsa ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ para cambiar de ~h~emisora de radio. + +[RADIO_B] +Pulsa ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ para cambiar de ~h~emisora de radio. + +[RADIO_C] +Pulsa ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ para cambiar de ~h~emisora de radio. + +[RADIO_D] +Pulsa ~h~~k~~VEHICLE_CHANGE_RADIO_STATION~~w~ para cambiar de ~h~emisora de radio. + +[FEC_EXV] +Entrar/salir de vehículo + +[TAXI_M] +'TAXISTA' + +[COP_M] +'JUSTICIERO' + +[FIRE_M] +'BOMBERO' + +[AMBUL_M] +'CONDUCTOR DE AMBULANCIA' + +[HJ_IS] +PREMIO POR ACROBACIA DEMENCIAL: ~1~ $ + +[HJ_PIS] +PREMIO POR ACROBACIA DEMENCIAL PERFECTA: ~1~ $ + +[HJ_DIS] +PREMIO POR ACROBACIA DEMENCIAL DOBLE: ~1~ $ + +[HJ_PDIS] +PREMIO POR ACROBACIA DEMENCIAL DOBLE PERFECTA: ~1~ $ + +[HJ_TIS] +PREMIO POR ACROBACIA DEMENCIAL TRIPLE: ~1~ $ + +[HJ_PTIS] +PREMIO POR ACROBACIA DEMENCIAL TRIPLE PERFECTA: ~1~ $ + +[HJ_QIS] +PREMIO POR ACROBACIA DEMENCIAL CUÁDRUPLE: ~1~ $ + +[HJ_PQIS] +PREMIO POR ACROBACIA DEMENCIAL CUÁDRUPLE PERFECTA: ~1~ $ + +[AM1_K] +Salvatore Leone saldrá del club de Luigi dentro de unas tres horas. (0~1~:~1~) + +[IMPEXPP] +Garaje de importación y exportación, puerto de Portland. Requerimos varios vehículos, revisa nuestro tablón de notas para saber más. + +[VANHSTP] +¿Quieres abrir algún Securicar? Llévalo a nuestro garaje en el puerto de Portland. + +[EMVHPUP] +Se compran vehículos de emergencia nuevos y usados a buen precio. Llévalos a la grúa que hay al noroeste del puerto de Portland. + +[STANDS] +PUESTOS DESTROZADOS: + +[STASH] +~g~¡Lleva el SPANK de vuelta al ~p~edificio en construcción~g~! + +[MCSTNS] +No hay una Memory Card (PS2) insertada en la ranura para MEMORY CARD 1. ¿Quieres empezar? (SÍ o NO) + +[LOVE3_5] +~g~La avioneta ha llegado a la zona. + +[LOVE3_6] +~r~¡La bofia ha llegado a los paquetes antes que tú! + +[SIREN_1] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_2] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[FM3_8C] +Necesitaré 100.000 dólares para cubrir gastos, + +[MCLOAD] +Cargando datos. No extraigas la Memory Card (PS2) de la ranura de MEMORY CARD 1, ni reinicies o apagues la consola. + +[FES_GME] +Fallo al leer la Memory Card (PS2) de la ranura de MEMORY CARD 1. Compruébala e inténtalo de nuevo. + +[FESZ_QF] +¿Seguro que quieres formatear la Memory Card (PS2) de la ranura de MEMORY CARD 1? + +[FESZ_LS] +Carga completada. + +[RM3_5] +~g~Tienes ~1~ de 6 paquetes de pruebas. + +[LOVE3_2] +~g~¡Tienes todos los paquetes! Llévaselos a Donald Love. + +[LOVE4_4] +~g~¡Lleva el paquete a Donald Love! + +[FEB_SAV] +Cargar + +[FEP_SAV] +CARGAR PARTIDA + +[AS2_12A] +~g~Cuando destruyas el primer puesto, ¡tendrás 8 mintutos antes que el cártel avise a sus camellos! + +[AS3_1A] +~g~¡Ahora ve a la ~b~boya señalada~g~! + +[NOCONT] +Vuelve a conectar un mando analógico (DUALSHOCK#) o mando analógico (DUALSHOCK#2) en el puerto de mando 1 para continuar. + +{ Possibly unused } + +[BET_JB] +TRAICIONADO POR SU AMANTE CATALINA Y DADO POR MUERTO. TRAS SER CONDENADO Y SENTENCIADO, INICIA SU VIAJE A LA PRISIÓN DE LIBERTY CITY. PERO SÓLO TIENE UN PENSAMIENTO... ¡VENGANZA! + +[END_A] +Los residentes de Cedar Grove todavía están asumiendo + +[END_B] +las consecuencias emocionales provocadas + +[END_C] +por la guerra que estalló ayer en la zona. + +[END_D] +Un residente local, Clive Denver, dijo a la policía + +[END_E] +que vio a un hombre armado huyendo de la escena, acompañado de una mujer de pelo negro. + +[END_F] +¿Sabes? Vamos a pasar un buen rato, porque, como sabes... + +[END_G] +¡Te amo! Yo, yo, yo realmente te amo, porque eres superfuerte, + +[END_H] +y eso es todo lo que necesito. + +[END_I] +Bueno, ¿qué estaba diciendo? + +[END_J] +Lo he olvidado. Pero me entiendes, ¿verdad? + +[END_K] +Las explosiones sacudieron las casas más próximas mientra sus residentes buscaban refugio. + +[END_L] +Varios ciudadanos resultaron heridos entre el caos debido al tiroteo + +[END_M] +entre las fuerzas terrestres y un helicóptero que rodeaba a la presa. + +[END_N] +Sí, en estos jardines tuvimos una vista excelente. + +[END_O] +El momento en el que derribaron el helicóptero + +[END_P] +fue mejor que los fuegos artificiales del 4 de julio. + +[END_Q] +El número de víctimas asciende a las veinte + +[END_R] +mientras la policía sigue encontrando cuerpos. + +[END_S] +No se han negado oficialmente los rumores + +[END_T] +de que las víctimas eran miembros del cártel colombiano, + +[END_U] +y aún no hay pistas que aclaren las causas de la masacre. + +[END_V] +Me he roto una uña y me pelo está hecho un asco, ¿puedes creerlo? + +[PAPER1] +CRIMINAL HERIDO DE BALA POR SU NOVIA Y CÓMPLICE; EL TRIBUNAL ENCUENTRA CULPABLE AL ATRACADOR CON UN VEREDICTO UNÁNIME. + +[PAPER2] +¡DIEZ AÑOS POR AMOR! + +[END_W] +Me costó cincuenta dólares... + +[FEB_CPC] +Configuración de controles + +[FEC_PED] +Controles a pie + +[FEC_VEH] +Controles en vehículos + +[FEC_FPR] +Controles en primera persona + +[FEC_CMM] +Controles comunes + +[FEC_PWL] +Ir a la izquierda + +[FEC_PWR] +Ir a la derecha + +[FEC_PWF] +Andar hacia delante + +[FEC_PWT] +Andar hacia la cámara + +[FEC_PLB] +Mirar hacia atrás + +[FEC_PFR] +Disparar arma + +[FEC_CLE] +Arma de la izquierda + +[FEC_CRI] +Arma de la derecha + +[FEC_LKT] +Fijar objetivo + +[FEC_PJP] +Saltar a pie + +[FEC_PSP] +Esprintar a pie + +[FEC_PSH] +Disparar a pie + +[FEC_TLF] +Objetivo a la izq. + +[FEC_TRG] +Objetivo a la dcha. + +[FEC_CCM] +Centrar cámara tras jugador + +[FEC_SZI] +Acercar zoom de fusil + +[FEC_SZO] +Alejar zoom de fusil + +[FEC_LKL] +Mirar izq. en primera persona + +[FEC_LRT] +Mirar dcha. en primera persona + +[FEC_LUP] +Mirar arr. en primera persona + +[FEC_LDN] +Mirar abj. en primera persona + +[FEC_LBH] +Mirar hacia atrás en vehículo + +[FEC_LLF] +Mirar a la izq. en vehículo + +[FEC_LRG] +Mirar a la dch. en vehículo + +[FEC_HRN] +Claxon + +[FEC_HBR] +Freno de mano del vehículo + +[FEC_ACL] +Acelerar vehículo + +[FEC_BRK] +Frenar vehículo + +[FEC_TSM] +Misiones secundarias + +[FEC_CRD] +Cambiar emisora de radio + +[FEC_ENT] +Entrar /salir de vehículo + +[FEC_WPN] +Disparar arma + +[FEC_PAS] +Pausa + +[FEC_FPO] +Armas en primera persona + +[FEC_SMS] +Mostrar puntero del ratón + +[FEC_CMS] +Cambiar cámara en todas las situaciones + +[FEC_TSS] +Capturar pantalla + +[FEN_STA] +INICIAR PARTIDA +[FEN_NET] +Red + +[FEN_CON] +Conexión + +[FEN_GAM] +Buscar partida + +[FEN_TYP] +Tipo de partida + +[FEN_TY0] +Duelo a muerte + +[FEN_TY1] +Duelo a muerte sigiloso + +[FEN_TY2] +Duelo a muerte en equipo + +[FEN_TY3] +Duelo a muerte sigiloso en equipo + +[FEN_TY4] +Busca la pasta + +[FEN_TY5] +Captura la bandera + +[FEN_TY6] +Carrera de locos + +[FEN_TY7] +Dominación + +[FEN_NAM] +Nombre: + +[FEN_GNA] +Nombre de partida: + +[FEM_MAP] +Elegir mapa + +[FEN_PLS] +Ajustes del jugador + +[FEN_PLC] +Color del jugador + +[FEM_MA0] +Liberty City + +[FEM_MA1] +Red Light District + +[FEM_MA2] +Chinatown + +[FEM_MA3] +La Torre + +[FEM_MA4] +Alcantarillas + +[FEM_MA5] +Zona industrial + +[FEM_MA6] +Muelles + +[FEM_MA7] +Staunton + +[FEC_EMS] +Sólo se permiten teclas del teclado. + +[FEC_DBG] +MENÚ DE DEPURACIÓN + +[FEC_TGD] +Cambiar mando de juego/depuración + +[FEC_TDO] +Desactivar cámara de depuración + +[FEC_IVH] +Invertir horizontalidad ratón + +[FEC_MSL] +BIR + +[FEC_MSM] +BCR + +[FEC_MSR] +BDR + +[FEC_QUE] +¿? + +[FEC_TWO] +Sólo se permiten dos teclas del teclado. + +[FEC_UMS] +Sólo se permiten botones del ratón. + +[FEC_OMS] +Sólo se permite un botón del ratón. + +[FEC_UJS] +Sólo se permiten botones del joystick. + +[FEC_OJS] +Sólo se permite un botón del joystick por acción. + +[FEC_PTL] +Usar Fijar objetivo con Siguiente arma. + +[FEC_PTR] +Usar Fijar objetivo con Arma anterior. + +[FEC_LBC] +Usar Mirar a izq. con Mirar a la dcha. + +[FEC_JBO] +JOY ~1~ + +[NO_PAUZ] +No se puede parar una partida multijugador. ¡Pulsa dos veces para salir! + +[FEM_SL1] +Espacio 1 libre + +[FEM_SL2] +Espacio 2 libre + +[FEM_SL3] +Espacio 3 libre + +[FEM_SL4] +Espacio 4 libre + +[FEM_SL5] +Espacio 5 libre + +[FEM_SL6] +Espacio 6 libre + +[FEM_SL7] +Espacio 7 libre + +[FEM_SL8] +Espacio 8 libre + +[FEM_MM] +MENÚ PRINCIPAL + +[FEM_SNG] +NUEVA PARTIDA + +[FEM_QTW] +Salir + +[FEQ_SRE] +¿Seguro que quieres salir del juego? Se perderán todos los progresos desde la última partida guardada. + +[FEQ_SRW] +¿Seguro que quieres salir del juego? + +[FEG_SRV] +SERVIDOR + +[FEG_MAP] +MAPA + +[FEG_PLY] +JUGADORES + +[FEG_TYP] +TIPO + +[FEG_PNG] +PING + +[FET_FG] +ENCONTRAR PARTIDA + +[FET_SP] +UN JUGADOR + +[FET_MP] +MULTIJUGADOR + +[FET_HG] +CREAR PARTIDA + +[FET_PS] +AJUSTES DE JUGADOR + +[FET_CON] +CONEXIÓN + +[FET_AUD] +AJUSTES DE AUDIO + +[FET_DIS] +AJUSTES DE PANTALLA + +[FET_LAN] +AJUSTES DE IDIOMA + +[FET_LG] +CARGAR PARTIDA + +[FET_DG] +BORRAR PARTIDA + +[FET_NG] +NUEVA PARTIDA + +[FET_SG] +GUARDAR PARTIDA + +[FET_MAP] +ELEGIR MAPA + +[FET_GT] +TIPO DE JUEGO + +[FET_CTL] +AJUSTES DE CONTROL + +[FET_OPT] +OPCIONES + +[FET_QG] +SALIR DEL JUEGO + +[FET_STA] +ESTADÍSTICAS + +[FET_BRE] +RESUMEN + +[FEC_WAR] +Aviso + +[FEC_OKK] +ACEPTAR + +[FED_CON] +Confirmación de borrado + +[FES_SSC] +Partida guardada. + +[DEL_FNM] +Partida eliminada. + +[PCLOAD] +Cargando datos de la partida + +[PCRESRT] +Reiniciando Grand Theft Auto III + +[FEC_DLF] +Fallo al borrar. + +[FEC_SVU] +Fallo al guardar. + +[FEC_LUN] +Fallo al cargar. Archivo dañado, por favor, bórralo. + +[FEN_PLA] +Número de jugadores: + +[FET_NON] +NO HAY PARTIDAS DISPONIBLES + +[FET_SFG] +BUSCANDO PARTIDAS... + +[FET_SRT] +ORDENANDO PARTIDAS... + +[FEF_LAN] +RED LOCAL + +[FEF_INT] +INTERNET + +[FET_REF] +Actualizar + +[FET_FIL] +Filtrar + +[FET_JG] +Unirse + +[FEC_NTW] +Conectar con red + +[FEC_ESR] +Tecla Esc limitada + +[FEC_GSL] +Balanceo de cabeza: + +[FIL_FLT] +FILTRAR LISTAS DE PARTIDAS + +[FET_SAN] +INICIAR NUEVA PARTIDA + +[FIL_MAP] +Mapa: + +[FIL_SRV] +Servidor: + +[FIL_TYP] +Tipo de juego: + +[FIL_SPC] +¿Partidas que no estén llenas? + +[FIL_PNG] +Latencia: + +[FEN_UKH] +Anfitrión desconocido + +[FEN_UKM] +Mapa no encontrado + +[FEN_UKT] +Tipo de juego no encontrado + +[FEN_NCI] +NO CONECTADO A INTERNET + +[FET_PAU] +MENÚ DE PAUSA + +[FET_SGA] +INICIAR PARTIDA + +[FEC_SGJ] +Establecer joystick de juego + +[FEC_PAD] +Mando + +[FEC_JOY] +Joystick + +[FEC_WHL] +Volante + +[FEC_CNT] +Tipo de mando: + +[FET_APL] +APLICAR + +[FES_CSA] +Selecciona una apariencia: + +[FES_SKN] +NOMBRE DE APARIENCIA + +[FES_DAT] +FECHA + +[FES_NON] +NO HAY APARIENCIAS DISPONIBLES + +[FEA_FM9] +REPRODUCTOR MP3 + +[FESZ_QZ] +¿Seguro que deseas guardar esta partida? + +[FES_CGA] +Espacios de partida disponibles: + +[FES_SCG] +¿Guardar la partida actual? + +[FES_LCG] +¿Cargar la partida y seguir jugando? + +[FEC_FIR] +Disparar + +[FEC_NWE] +Siguiente arma + +[FEC_PWE] +Arma anterior + +[FEC_FOR] +Avanzar + +[FEC_BAC] +Retroceder + +[FEC_LEF] +Izquierda + +[FEC_RIG] +Derecha + +[FEC_ZIN] +Acercar zoom + +[FEC_ZOT] +Alejar zoom + +[FEC_EEX] +Entrar /salir + +[FEC_RAD] +Radio + +[FEC_SUB] +Misión secundaria + +[FEC_CMR] +Cambiar cámara + +[FEC_JMP] +Saltar + +[FEC_SPN] +Esprintar + +[FEC_HND] +Freno de mano + +[FEC_TUL] +Torreta a izq. + +[FEC_TUR] +Torreta a dcha. + +[FEC_LOL] +Mirar a izq. + +[FEC_LOR] +Mirar a dcha. + +[FEC_NTR] +Siguiente objetivo + +[FEC_PTT] +Objetivo anterior + +[FEC_LBA] +Mirar atrás + +[FEC_CEN] +Centrar cámara + +[FEC_UND] +(NO) + +[FET_CFT] +A PIE + +[FET_CCR] +EN VEHÍCULO + +[CVT_MSG] +Convirtiendo texturas a un formato óptimo para tu tarjeta de vídeo + +[FET_CAC] +ACCIÓN + +[FEC_IBT] +- + +[FEC_SPC] +ESPACIO + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +SIN ASIGNAR + +[FET_CME] +MÉTODO DE CONTROL + +[FET_RDK] +REDEFINIR CONTROLES + +[FET_AMS] +AJUSTES DEL RATÓN + +[FET_STI] +CONFIGURACIÓN DE CONTROL ESTÁNDAR + +[FET_CTI] +CONFIGURACIÓN DE CONTROL CLÁSICA + +[FET_MTI] +CONFIGURACIÓN DE CONTROL CON RATÓN + +[FET_DAM] +MODELADO ACÚSTICO DINÁMICO + +[FEC_TFL] +Torreta a izq. + +[FEC_TFR] +Torreta a dcha. + +[FEC_TFU] +Torreta /Dodo arriba + +[FEC_TFD] +Torreta /Dodo abajo + +[FEC_MWF] +RUEDA ARR. + +[FEC_MWB] +RUEDA AB. + +[FEC_ORR] +o + +[FEC_NUS] +SIN USO + +[FEC_LUD] +Mirar arriba + +[FEC_LDU] +Mirar abajo + +[FEC_CMP] +COMBO: MIRAR I+D + +[FEC_NTT] +No hay texto para esta tecla + +[FEC_FNC] +F~1~ + +[FEC_IRT] +INSERT + +[FEC_DLL] +SUPR + +[FEC_HME] +INICIO + +[FEC_END] +FIN + +[FEC_PGU] +RE PÁG + +[FEC_PGD] +AV PÁG + +[FEC_UPA] +ARRIBA + +[FEC_DWA] +ABAJO + +[FEC_LFA] +IZDA + +[FEC_RFA] +DCHA + +[FEC_NUM] +NUM. + +[FEC_NMN] +NUM. ~1~ + +[FEC_FWS] +NUM. / + +[FEC_PLS] +NUM. + + +[FEC_MIN] +NUM. - + +[FEC_DOT] +NUM. . + +[FEC_NLK] +BLOQ NUM + +[FEC_ETR] +INTRO + +[FEC_SLK] +BLOQ DESPL + +[FEC_PSB] +PAUSA/INTER + +[FEC_BSP] +RETROCESO + +[FEC_TAB] +TAB + +[FEC_CLK] +BLOQ MAYÚS + +[FEC_RTN] +INTRO + +[FEC_LSF] +MAYÚS IZQ + +[FEC_RSF] +MAYÚS DCHA + +[FEC_LCT] +CTRL IZQ + +[FEC_RCT] +CTRL DCHO + +[FEC_LAL] +ALT IZQ + +[FEC_RAL] +ALT DCHO + +[FEC_LWD] +WIN IZQ + +[FEC_RWD] +WIN DCHO + +[FEC_WRC] +APMENÚ + +[WIN_TTL] +Grand Theft Auto III + +[WIN_95] +Grand Theft Auto III no se puede ejecutar bajo Windows 95 + +[WIN_DX] +Grand Theft Auto III necesita al menos la versión 8.1 de DirectX + +[WIN_VDM] +Grand Theft Auto III necesita al menos 12 MB de memoria de vídeo + +[DIAB3_G] +¡Arriba! + +[FEM_RES] +REANUDAR PARTIDA + +[FES_SNG] +INICIAR NUEVA PARTIDA + +[FEM_SP] +UN JUGADOR + +[FEM_MP] +MULTIJUGADOR + +[FEM_QT] +SALIR + +[FES_SG] +INICIAR NUEVA PARTIDA + +[FES_LG] +CARGAR PARTIDA + +[FEM_HST] +CREAR PARTIDA + +[FEM_OPT] +OPCIONES + +[FEM_DBG] +DEPURACIÓN + +[FET_PSU] +AJUSTES DE JUGADOR + +[FET_DEF] +RESTAURAR PREDETERMINADOS + +[FED_BRI] +BRILLO + +[FED_TRA] +ESTELAS + +[FEM_LOD] +DISTANCIA DE DIBUJADO + +[FEM_VSC] +SINCRONIZAR IMAGEN + +[FEM_FRM] +LIMITADOR DE FOTOGRAMAS + +[FED_RES] +RESOLUCIÓN + +[FED_WIS] +PANTALLA PANORÁMICA + +[FEDS_TB] +ATRÁS + +[FEA_MUS] +VOLUMEN DE MÚSICA + +[FEA_SFX] +VOLUMEN DE EFECTOS + +[FEA_RSS] +EMISORA DE RADIO + +[FEL_ENG] +INGLÉS + +[FEL_FRE] +FRANCÉS + +[FEL_GER] +ALEMÁN + +[FEL_ITA] +ITALIANO + +[FEL_SPA] +ESPAÑOL + +[FEA_3DH] +HARDWARE DE SONIDO + +[FEA_SPK] +CONFIGURACIÓN DE ALTAVOCES + +[FEA_2SP] +2 ALTAVOCES + +[FEA_4SP] +MÁS DE 2 ALTAVOCES + +[FEA_EAR] +AURICULARES + +[FEA_NAH] +NO HAY HARDWARE DE SONIDO + +[FET_SNG] +INICIAR NUEVA PARTIDA + +[GMSAVE] +GUARDAR PARTIDA + +[FES_DGA] +BORRAR PARTIDA + +{ STRING MISSING FROM THE OFFICIAL SPANISH TRANSLATION. } + +[FEM_NON] +NADA + +[FEC_IVV] +INVERTIR VERTICALIDAD RATÓN + +[FEC_MSH] +SENSIBILIDAD DEL RATÓN + +[FET_CCN] +CONTROL: CLÁSICO + +[FET_SCN] +CONTROL: ESTÁNDAR + +[FES_SET] +USAR APARIENCIA + +[GHOST] +Ghost + +[WIN_RSZ] +Error al seleccionar la nueva resolución de pantalla + +[FET_APP] +BIR, INTRO: APLICAR AJUSTES + +[FET_HRD] +AJUSTES PREDETERMINADOS RESTAURADOS + +[FET_MST] +CONDUCCIÓN CONTROLADA POR EL RATÓN + +[FEC_STR] +NUM. INICIO + +[FET_MIG] +IZQUIERDA, DERECHA, RUEDA DEL RATÓN: AJUSTAR + +[FET_CIG] +RETROCESO: QUITAR - BIR, INTRO: CAMBIAR + +[FET_RIG] +SELECCIONA UN NUEVO CONTROL O PULSA ESC PARA CANCELAR + +[FET_EIG] +NO SE PUEDE DEFINIR UN CONTROL PARA ESTA ACCIÓN + +[NO_PCCD] +Por favor, introduce el disco 2 de Grand Theft Auto III en la unidad o pulsa ESC para cancelar + +[CVT_ERR] +Te has quedado sin espacio en el disco duro. Por favor, libera espacio en tu disco duro antes de continuar. Pulsa ESC para cancelar. + +[FED_SUB] +SUBTÍTULOS + +[FET_DSN] +Apariencia predeterminada del jugador.bmp + +[JM3] +'EL ROBO DEL FURGÓN' + +[EBAL] +'DAME LIBERTAD' + +[LM4] +'UN CHULO Y SU ESCOPETA' + +[REPLAY] +REPETICIÓN + +[FEC_SFT] +MAYÚS + +[CRED254] +JEFE DEL ESTUDIO + +[CVT_CRT] +No se pueden convertir las texturas para tu tarjeta de vídeo. Necesitas privilegios de administrador. Pulsa ESC para salir. + +[FEM_ON] +SÍ + +[FEM_OFF] +NO + +[FEM_YES] +SÍ + +[FEM_NO] +NO + +[FES_WAR] +Guardando, espera... + +[FED_DLW] +Borrando, espera... + +[FED_LDW] +Cargando, espera... + +[FEC_SLC] +Ranura dañada + +[FED_LFL] +Error al cargar la partida. El juego se reiniciará. + +[FET_RSO] +SE HAN REINICIADO LOS AJUSTES + +[FET_RSC] +HARDWARE NO DISPONIBLE. RESTAURADO AJUSTE ORIGINAL + +[CRED270] +MIKE HONG + +{ re3 updates } +{ new languages } +[FEL_JAP] +JAPONÉS + +[FEL_POL] +POLACO + +[FEL_RUS] +RUSO + +{ new display menus } +[FET_GFX] +AJUSTES GRÁFICOS + +[FED_MIP] +MIPMAPPING + +[FED_AAS] +SUAVIZADO DE BORDES + +[FED_FIL] +FILTRO DE TEXTURAS + +[FED_BIL] +BILINEAL + +[FED_TRL] +TRILINEAL + +[FED_WND] +VENTANA + +[FED_FLS] +PANTALLA COMPLETA + +[FEM_CSB] +BORDES EN CINEMÁTICAS + +[FEM_SCF] +FORMATO DE IMAGEN + +[FEM_ISL] +USO DE MEMORIA DEL MAPA + +[FEM_LOW] +BAJO + +[FEM_MED] +MEDIO + +[FEM_HIG] +ALTO + +[FEM_2PR] +ALPHA TEST TIPO PS2 + +[FEC_FRC] +CÁMARA LIBRE + +{ Linux joy detection } +[FEC_JOD] +DETECTAR JOYSTICK + +[FEC_JPR] +Pulsa cualquier botón del joystick que quieras usar con el juego para seleccionarlo. + +[FEC_JDE] +Joystick detectado + +{ mission restart } +[FET_RMS] +REPETIR MISIÓN + +[FESZ_RM] +¿REINTENTAR? + +[FED_VPL] +RENDERIZADO DE VEHÍCULOS + +[FED_PRM] +ILUMINAR CONTORNOS DE PEATONES + +[FED_RGL] +BRILLO DE CARRETERAS + +[FED_CLF] +FILTRO DE COLOR + +[FED_WLM] +MAPAS DE LUZ DEL MUNDO + +[FED_MBL] +DESENFOQUE DE MOVIMIENTO + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MÓVIL + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEM_AUT] +AUTOM. + +[FEC_IVP] +INVERTIR VERTICALIDAD MANDO + +[FEM_TWP] +Poner o quitar punto de referencia + +[FEA_FMN] +RADIO APAGADA + +[FEC_DS2] +DUALSHOCK 2 + +[FEC_DS3] +DUALSHOCK 3 + +[FEC_DS4] +DUALSHOCK 4 + +[FEC_360] +MANDO DE XBOX 360 + +[FEC_ONE] +MANDO DE XBOX ONE + +[FEC_NSW] +MANDO DE NINTENDO SWITCH + +[FEC_TYP] +TIPO DE MANDO + +[FEC_CCF] +CONFIGURACIÓN + +[FEC_CF1] +AJUSTE 1 + +[FEC_CF2] +AJUSTE 2 + +[FEC_CF3] +AJUSTE 3 + +[FEC_CF4] +AJUSTE 4 + +[FEC_CDP] +CONTROLES A MOSTRAR + +[FEC_ONF] +A PIE + +[FEC_INC] +EN VEHÍCULO + +[FEC_VIB] +VIBRACIÓN + +[FET_AGS] +AJUSTES DE MANDO + +[FEM_PED] +PED DENSITY + +[FEM_CAR] +CAR DENSITY + +{ end of file } + +[DUMMY] +THIS LABEL NEEDS TO BE HERE !!! +AS THE LAST LABEL DOES NOT GET COMPILED \ No newline at end of file diff --git a/vendor/librw/.appveyor.yml b/vendor/librw/.appveyor.yml new file mode 100644 index 0000000..f74fc38 --- /dev/null +++ b/vendor/librw/.appveyor.yml @@ -0,0 +1,32 @@ +image: Visual Studio 2017 +configuration: Release +environment: + GLFW_BASE: glfw-3.3.4.bin.WIN64 + GLFW_URL: https://github.com/glfw/glfw/releases/download/3.3.4/%GLFW_BASE%.zip + SDL2_BASE: SDL2-devel-2.0.14-VC + SDL2_URL: https://www.libsdl.org/release/%SDL2_BASE%.zip + SDL2_DIRAME: SDL2-2.0.14 + PREMAKE5_URL: https://github.com/premake/premake-core/releases/download/v5.0.0-alpha16/premake-5.0.0-alpha16-windows.zip + matrix: + - PLATFORM: win-amd64-null + - PLATFORM: win-amd64-gl3 + PREMAKE5_EXTRA_ARGS: --gfxlib=glfw + - PLATFORM: win-amd64-gl3 + PREMAKE5_EXTRA_ARGS: --gfxlib=sdl2 + - PLATFORM: win-amd64-d3d9 + +install: + - appveyor DownloadFile %GLFW_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE%.zip" + - 7z x "%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE%.zip" + - appveyor DownloadFile %SDL2_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/%SDL2_BASE%.zip" + - 7z x "%APPVEYOR_BUILD_FOLDER%/%SDL2_BASE%.zip" + - appveyor DownloadFile %PREMAKE5_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/premake5.zip" + - mkdir "%APPVEYOR_BUILD_FOLDER%/bin" && cd "%APPVEYOR_BUILD_FOLDER%/bin" && 7z x "%APPVEYOR_BUILD_FOLDER%/premake5.zip" + - set PATH=%APPVEYOR_BUILD_FOLDER%/bin;%PATH% +before_build: + - mkdir "%APPVEYOR_BUILD_FOLDER%/build" + - cd "%APPVEYOR_BUILD_FOLDER%" + - premake5 vs2017 --glfwdir64=%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE% --sdl2dir=%APPVEYOR_BUILD_FOLDER%/%SDL2_DIRAME% %PREMAKE5_EXTRA_ARGS% +build: + project: c:\projects\librw\build\librw.sln + verbosity: minimal diff --git a/vendor/librw/.github/workflows/build-cmake-conan.yml b/vendor/librw/.github/workflows/build-cmake-conan.yml new file mode 100644 index 0000000..43f85a9 --- /dev/null +++ b/vendor/librw/.github/workflows/build-cmake-conan.yml @@ -0,0 +1,60 @@ +name: Conan +on: + pull_request: + push: + release: + types: published +jobs: + build-cmake: + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + platform: ['null', 'gl3', 'd3d9'] + gl3_gfxlib: ['glfw', 'sdl2'] + exclude: + - os: ubuntu-latest + platform: d3d9 + - os: macos-latest + platform: d3d9 + - platform: 'null' + gl3_gfxlib: sdl2 + - platform: d3d9 + gl3_gfxlib: sdl2 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: "Setup conan" + run: | + python -m pip install conan + conan config init + conan config set log.print_run_commands=True + conan config set general.revisions_enabled=1 + conan remote add bincrafters https://bincrafters.jfrog.io/artifactory/api/conan/public-conan + - name: "Create host profile" + shell: bash + run: | + cp ~/.conan/profiles/default host_profile + - name: "Download/build dependencies (conan install)" + run: | + conan install ${{ github.workspace }} librw/master@ -if build -o librw:platform=${{ matrix.platform }} -o librw:gl3_gfxlib=${{ matrix.gl3_gfxlib }} --build missing -pr:h ./host_profile -pr:b default + env: + CONAN_SYSREQUIRES_MODE: enabled + - name: "Build librw (conan build)" + run: | + conan build ${{ github.workspace }} -if build -bf build -pf package + - name: "Package librw (conan package)" + run: | + conan package ${{ github.workspace }} -if build -bf build -pf package + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "${{ matrix.os }}-${{ matrix.platform }}" + path: build/*.tar.xz + if-no-files-found: error diff --git a/vendor/librw/.github/workflows/build-ps2.yml b/vendor/librw/.github/workflows/build-ps2.yml new file mode 100644 index 0000000..bb0b0ec --- /dev/null +++ b/vendor/librw/.github/workflows/build-ps2.yml @@ -0,0 +1,29 @@ +name: Playstation 2 (by ps2dev) +on: + pull_request: + push: + release: + types: published +jobs: + build-playstation-2: + runs-on: ubuntu-latest + container: ps2dev/ps2dev + steps: + - uses: actions/checkout@v2 + - name: "Install dependencies" + run: | + apk add build-base cmake + - name: "Build files" + run: | + cmake -S. -Bbuild -DLIBRW_INSTALL=ON -DLIBRW_PLATFORM=PS2 -DCMAKE_TOOLCHAIN_FILE=cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake + cmake --build build --parallel + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "ps2" + path: build/*.tar.xz + if-no-files-found: error diff --git a/vendor/librw/.github/workflows/build-switch.yml b/vendor/librw/.github/workflows/build-switch.yml new file mode 100644 index 0000000..cfda93c --- /dev/null +++ b/vendor/librw/.github/workflows/build-switch.yml @@ -0,0 +1,26 @@ +name: Nintendo Switch (by devkitPro) +on: + pull_request: + push: + release: + types: published +jobs: + build-nintendo-switch: + runs-on: ubuntu-latest + container: devkitpro/devkita64:latest + steps: + - uses: actions/checkout@v2 + - name: "Build files" + run: | + /opt/devkitpro/portlibs/switch/bin/aarch64-none-elf-cmake -S. -Bbuild -DLIBRW_PLATFORM=GL3 -DLIBRW_GL3_GFXLIB=GLFW -DLIBRW_INSTALL=True + cmake --build build --parallel + - name: "Create binary package (cpack)" + working-directory: ./build + run: | + cpack + - name: "Archive binary package (github artifacts)" + uses: actions/upload-artifact@v2 + with: + name: "switch-gl3" + path: build/*.tar.xz + if-no-files-found: error diff --git a/vendor/librw/.gitignore b/vendor/librw/.gitignore new file mode 100644 index 0000000..2ab0d93 --- /dev/null +++ b/vendor/librw/.gitignore @@ -0,0 +1,10 @@ +/bin +/build +/obj +/lib +/output +/.vs +librw.vcxproj.user +librw.VC.db +librw.VC.VC.opendb +imgui.ini diff --git a/vendor/librw/.travis.yml b/vendor/librw/.travis.yml new file mode 100644 index 0000000..91411e6 --- /dev/null +++ b/vendor/librw/.travis.yml @@ -0,0 +1,46 @@ +language: cpp + +matrix: + include: + - os: linux + env: TARGET=release_linux-amd64-null + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 gmake && cd /librw/build && make config=$TARGET verbose=1" + - os: linux + env: TARGET=release_linux-amd64-gl3 GFXLIB=glfw + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 --gfxlib=$GFXLIB gmake && cd /librw/build && make config=$TARGET verbose=1" + - os: linux + env: TARGET=release_linux-amd64-gl3 GFXLIB=sdl2 + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 --gfxlib=$GFXLIB gmake && cd /librw/build && make config=$TARGET verbose=1" + - name: "ps2" + os: linux + env: TARGET=release_ps2 + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 gmake && cd /librw/build && make config=$TARGET verbose=1" + allow_failures: + - name: "ps2" diff --git a/vendor/librw/ARCHITECTURE.MD b/vendor/librw/ARCHITECTURE.MD new file mode 100644 index 0000000..dad04fa --- /dev/null +++ b/vendor/librw/ARCHITECTURE.MD @@ -0,0 +1,174 @@ +This document gives an overview of the architecture of librw. + +Disclaimer: Some of these design decision were taken over from original RW, +some are my own. I only take partial responsibility. + +Differently from original RW, librw has no neat separation into modules. +Some things could be made optional, but in particular RW's RpWorld +plugin is integrated with everything else. + +# Plugins + +To extend structs with custom data, +RW (and librw) provides a plugin mechanism +for certain structs. +This can be used to tack more data onto a struct +and register custom streaming functions. +Plugins must be registered before any instance +of that struct is allocated. + +# Pipelines + +RW's pipeline architecture was designed for very flexible data flow. +Unfortunately for RW most of the rendering pipeline moved into the GPU +causing RW's pipeline architecture to be severely overengineered. + +librw's pipeline architecture is therefore much simplified +and only implements what is actually needed, +but the name *pipeline* is retained. + +Three pipelines are implemented in librw itself: +Default, Skin, MatFX (only env map so far). +Others can be implemented by applications using librw. + +# RW Objects + +## Frame + +A Frame is an orientation in space, arranged in a hierarchy. +Camera, Lights and Atomics can be attached to it. +It has two matrices: a (so called) model matrix, +which is relative to its parent, +and a local transformation matrix (LTM) which is relative to the world. +The LTM is updated automatically as needed whenever the hierarchy gets dirty. + +## Camera + +A Camera is attached to a Frame to position it in space +and has a framebuffer and a z-buffer attached to render to. +Rendering is started by `beginUpdate` and ended by `endUpdate`. +This sets up things like framebuffers and matrices +so that the Camera's raster can be rendered to. + +## Light + +Lights are attached to a Frame to position it in space. +They are used to light Atomics for rendering. +Different types of light are possible. + +## Geometry + +A Geometry contains the raw geometry data that can be rendered. +It has a list of materials that are applied to its triangles. +The latter are sorted by materials into meshes for easier instancing. + +## Atomic + +An Atomic is attached to a Frame to position it in space +and references a Geometry. +Atomics are the objects that are rendered by pipelines. + +## Clump + +A Clump is a container of Atomics, Lights and Cameras. +Clumps can be read from and written to DFF files. +Rendering a Clump will be render all of its Atomics. + +# Engine + +Due to the versatility of librw, +there are three levels of code: +Platform indpendent code, +platform specific code, +and render device specific code. + +The second category does not exist in original RW, +but because librw is supposed to be able to +convert between all sorts of platform specific files, +the code for that has to be available no matter +the render platform used. +The common interface for all device-independent +platform code is the `Driver` struct. +The `Engine` struct has an array with one for each platform. + +The render device specific code +is used for actually rendering something to the screen. +The common interface for the device-dependent +code is the `Device` struct and the `Engine` +struct only has a single one, as there can only be one render device +(i.e. you cannot select D3D or OpenGL at runtime). + +Thus when implementing a new backend +you have to implement the `Driver` and `Device` interfaces. +But do note that the `Driver` can be extended with plugins! + +# Driver + +The driver is mostly concerned with conversion +between platform independent data to platform dependent one, and vice versa. +This concerns the following two cases. + +## Raster, Images + +Images contain platform independent uncompressed pixel data. +Rasters contain platform dependent (and possibly compressed) pixel data. +A driver has to be able to convert an image to a raster for the purposes of loading textures +from files or having them converted from foreign rasters. +Converting from rasters to images is not absolutely necessary but it's needed e.g. for taking screenshots. +librw has a set of default raster formats that the platform is +expected to implement for the most part, however not all have to be supported necessarily. +A driver is also free to implement its own formats; +this is done for texture compression. + +Rasters have different types, +`TEXTURE` and `CAMERATEXTURE` rasters can be used as textures, +`CAMERA` and `CAMERATEXTURE` can be used as render targets, +`ZBUFFER` is used as a depth-buffer. + +## Pipelines + +A librw ObjPipeline implements essentially +an instance stage which converts platform independent geometry +to a format that can efficiently be rendered, +and a render stage that actually renders it. +(There is also an uninstance function, +but this is only used to convert platform specific geometry back to the generic format +and hence is not necessary.) + +# Device + +The device implements everything that is actually needed for rendering. + +This includes starting, handling and closing the rendering device, +setting render states, +allocating and destroying buffers and textures, +im2d and im3d rendering, +and the render functions of the pipelines. + +## System + +The `system` function implements certain device requests +from the engine (why these aren't separate functions I don't know, RW design). + +The `Engine` is started in different stages, at various points of which +the render device gets different requests. +At the end the device is initialized and ready for rendering. +A similar but reverse sequence happens on shutdown. + +Subsystems (screens) and video modes are queried through +the `system` by the application before the device is started. + +## Immediate mode + +Im2d and im3d are immediate-mode style rendering interface. + +## Pipelines + +For instancing the typical job is to allocate and fill +a struct to hold some data about an atomic, +an array of structs for all the meshes, +and vertex and index buffers to hold geometry for rendering. + +The render function will render the previously instanced +data by doing a per-object setup and then iterating over +and rendering all the meshes. diff --git a/vendor/librw/CMakeLists.txt b/vendor/librw/CMakeLists.txt new file mode 100644 index 0000000..1f4d3ce --- /dev/null +++ b/vendor/librw/CMakeLists.txt @@ -0,0 +1,160 @@ +set(librw_MAINPROJECT ON) +if(DEFINED PROJECT_NAME) + set(librw_MAINPROJECT OFF) +endif() + +cmake_minimum_required(VERSION 3.8) +project(librw + VERSION 0.0.1 + LANGUAGES C CXX +) +set(librw_AUTHOR aap) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") + +if(WIN32) + set(LIBRW_PLATFORMS "NULL" "GL3" "D3D9") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL ON) +elseif(NINTENDO_SWITCH) + set(LIBRW_PLATFORMS "NULL" "GL3") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL OFF) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/nx") + include(NXFunctions) +elseif(PS2) + set(LIBRW_PLATFORMS "PS2") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL OFF) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/ps2") + include(PS2Functions) +else() + set(LIBRW_PLATFORMS "NULL" "GL3") + set(LIBRW_PLATFORM_GL3_REQUIRES_OPENGL ON) +endif() +list(GET LIBRW_PLATFORMS 0 LIBRW_PLATFORM_DEFAULT) +set(LIBRW_PLATFORM "${LIBRW_PLATFORM_DEFAULT}" CACHE STRING "Platform") +set_property(CACHE LIBRW_PLATFORM PROPERTY STRINGS ${LIBRW_PLATFORMS}) +message(STATUS "LIBRW_PLATFORM = ${LIBRW_PLATFORM} (choices=${LIBRW_PLATFORMS})") +set("LIBRW_PLATFORM_${LIBRW_PLATFORM}" ON) +if(NOT LIBRW_PLATFORM IN_LIST LIBRW_PLATFORMS) + message(FATAL_ERROR "Illegal LIBRW_PLATFORM=${LIBRW_PLATFORM}") +endif() + +set(LIBRW_GL3_GFXLIBS "GLFW" "SDL2") +set(LIBRW_GL3_GFXLIB "GLFW" CACHE STRING "gfxlib for gl3") +set_property(CACHE LIBRW_GL3_GFXLIB PROPERTY STRINGS ${LIBRW_GL3_GFXLIBS}) +if(LIBRW_PLATFORM_GL3) + message(STATUS "LIBRW_GL3_GFXLIB = ${LIBRW_GL3_GFXLIB} (choices=${LIBRW_GL3_GFXLIBS})") +endif() +if(NOT LIBRW_GL3_GFXLIB IN_LIST LIBRW_GL3_GFXLIBS) + message(FATAL_ERROR "Illegal LIBRW_GL3_GFXLIB=${LIBRW_GL3_GFXLIB}") +endif() + +if(LIBRW_PLATFORM_PS2) + enable_language(DSM) +endif() + +if(NOT COMMAND librw_platform_target) + function(librw_platform_target) + endfunction() +endif() + +include(CMakeDependentOption) + +option(LIBRW_TOOLS "Build librw tools" ${librw_MAINPROJECT}) +option(LIBRW_INSTALL "Install librw files" ${librw_MAINPROJECT}) +cmake_dependent_option(LIBRW_EXAMPLES "Build librw examples" ON "LIBRW_TOOLS;NOT LIBRW_PLATFORM_NULL" OFF) + +if(LIBRW_INSTALL) + include(GNUInstallDirs) + set(LIBRW_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/librw") +endif() + +add_subdirectory(src) + +if(LIBRW_TOOLS AND NOT LIBRW_PLATFORM_PS2 AND NOT LIBRW_PLATFORM_NULL) + add_subdirectory(skeleton) +endif() + +add_subdirectory(tools) + +if(LIBRW_INSTALL) + include(CMakePackageConfigHelpers) + configure_package_config_file(cmake/librw-config.cmake.in librw-config.cmake + INSTALL_DESTINATION "${CMAKE_INSTALL_PREFIX}" + ) + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/librw-config.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + ) + install( + EXPORT librw-targets NAMESPACE librw:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + ) + + if(LIBRW_GL3_GFXLIB STREQUAL "SDL2") + install( + FILES "${CMAKE_CURRENT_LIST_DIR}/cmake/FindSDL2.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake" + ) + endif() + + string(REPLACE "." ";" cmake_c_compiler_version_list "${CMAKE_C_COMPILER_VERSION}") + list(GET cmake_c_compiler_version_list 0 cmake_c_compiler_version_major) + + string(TOLOWER "${LIBRW_PLATFORM}" librw_platform) + set(compiler) + set(os) + if(NOT LIBRW_PLATFORM STREQUAL "PS2") + if(MSVC) + set(compiler "-msvc${MSVC_VERSION}") + elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(compiler "-gcc${cmake_c_compiler_version_major}") + elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(compiler "-clang${cmake_c_compiler_version_major}") + elseif(CMAKE_C_COMPILER_ID STREQUAL "AppleClang") + set(compiler "-appleclang${cmake_c_compiler_version_major}") + else() + set(compiler "-UNK") + message(WARNING "Unknown compiler. Created cpack package will be wrong. (override using cpack -P)") + endif() + endif() + if(LIBRW_PLATFORM_NULL) + set(platform "-null") + elseif(LIBRW_PLATFORM_PS2) + set(platform "-ps2") + elseif(LIBRW_PLATFORM_GL3) + if(LIBRW_GL3_GFXLIB STREQUAL "GLFW") + set(platform "-gl3-glfw") + else() + set(platform "-gl3-sdl2") + endif() + elseif(LIBRW_PLATFORM_D3D9) + set(platform "-d3d9") + endif() + if(NOT LIBRW_PLATFORM_PS2) + if(WIN32) + set(os "-win") + elseif(NINTENDO_SWITCH) + set(os "-switch") + elseif(PS2) + set(os "-ps2") + elseif(APPLE) + set(os "-apple") + elseif(UNIX) + set(os "-linux") + else() + set(compiler "-UNK") + message(WARNING "Unknown os. Created cpack package will be wrong. (override using cpack -P)") + endif() + endif() + + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}${platform}${os}${compiler}") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A (partial) re-implementation of RenderWare Graphics") + set(CPACK_PACKAGE_VENDOR "aap") + set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") + set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_GENERATOR "TXZ") + include(CPack) +endif() diff --git a/vendor/librw/Dockerfile b/vendor/librw/Dockerfile new file mode 100644 index 0000000..a664173 --- /dev/null +++ b/vendor/librw/Dockerfile @@ -0,0 +1,60 @@ +FROM ubuntu:rolling + +ENV PS2DEV /ps2dev +ENV PS2SDK $PS2DEV/ps2sdk +ENV PATH $PATH:$PS2DEV/bin:$PS2DEV/ee/bin:$PS2DEV/iop/bin:$PS2DEV/dvp/bin:$PS2SDK/bin + +ENV DEBIAN_FRONTEND noninteractive + +ENV TOOLCHAIN_GIT_URL git://github.com/ps2dev/ps2toolchain.git +ENV TOOLCHAIN_GIT_BRANCH master + +ENV PREMAKE5_URL=https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-linux.tar.gz + +RUN mkdir -p "$PS2DEV" "$PS2SDK" \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y \ + build-essential \ + cmake \ + autoconf \ + bzip2 \ + gcc \ + git \ + libucl-dev \ + make \ + patch \ + vim \ + wget \ + zip \ + zlib1g-dev \ + libglfw3-dev \ + libsdl2-dev \ + && git clone -b $TOOLCHAIN_GIT_BRANCH $TOOLCHAIN_GIT_URL /toolchain \ + && cd /toolchain \ + && ./toolchain.sh \ + && git clone git://github.com/ps2dev/ps2eth.git /ps2dev/ps2eth \ + && make -C /ps2dev/ps2eth \ + && git clone git://github.com/ps2dev/ps2-packer.git /ps2-packer \ + && make install -C /ps2-packer \ + && rm -rf \ + /ps2-packer \ + /ps2dev/ps2eth/.git \ + /ps2dev/ps2sdk/test.tmp \ + /ps2dev/test.tmp \ + /toolchain \ + && rm -rf /var/lib/apt/lists/* \ + && wget "$PREMAKE5_URL" -O /tmp/premake5.tar.gz \ + && tar xf /tmp/premake5.tar.gz -C /usr/bin/ \ + && rm /tmp/premake5.tar.gz \ + && groupadd 1000 -g 1000 \ + && groupadd 1001 -g 1001 \ + && groupadd 2000 -g 2000 \ + && groupadd 999 -g 999 \ + && useradd -ms /bin/bash builder -g 1001 -G 1000,2000,999 \ + && printf "builder:builder" | chpasswd \ + && adduser builder sudo \ + && printf "builder ALL= NOPASSWD: ALL\\n" >> /etc/sudoers + +USER builder +WORKDIR /home/builder diff --git a/vendor/librw/LICENSE b/vendor/librw/LICENSE new file mode 100644 index 0000000..ef53cc2 --- /dev/null +++ b/vendor/librw/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 aap + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/librw/README.cmake b/vendor/librw/README.cmake new file mode 100644 index 0000000..59493b1 --- /dev/null +++ b/vendor/librw/README.cmake @@ -0,0 +1,9 @@ +Build with cmake +================ + +Linux + + mkdir build + cd build + cmake .. -DLIBRW_PLATFORM=GL3 -DLIBRW_GL3_GFXLIB=SDL2 + make diff --git a/vendor/librw/README.md b/vendor/librw/README.md new file mode 100644 index 0000000..77defd0 --- /dev/null +++ b/vendor/librw/README.md @@ -0,0 +1,26 @@ +librw +===== + +This library is supposed to be a re-implementation of RenderWare graphics, +or a good part of it anyway. + +It is intended to be cross-platform in two senses: +support rendering on different platforms similar to RW; +supporting all file formats for all platforms at all times and provide +way to convert to all other platforms. + +Supported file formats are DFF and TXD for PS2, D3D8, D3D9 and Xbox. +Not all pre-instanced PS2 DFFs are supported. +BSP is not supported at all. + +For rendering we have D3D9 and OpenGL (>=2.1, ES >= 2.0) backends. +Rendering some things on the PS2 is working as a test only. + +# Uses + +librw can be used for rendering [GTA](https://github.com/gtamodding/re3). + +# Building + +Get premake5. Generate a config, e.g. with ``premake5 gmake``, +and look in the build directory. diff --git a/vendor/librw/TODO b/vendor/librw/TODO new file mode 100644 index 0000000..5c2c220 --- /dev/null +++ b/vendor/librw/TODO @@ -0,0 +1,24 @@ +TODO: +- tristrips +- examples + skeleton + interface + camtex + matfx1 (more new shaders) + hanim1 + fog + imlight? + patch? +- morphing +- Pipelines (PDS, Xbox, PC) +- bsp + +driver +- metrics + +ps2 +- rendering! +- ADC conversion + + +BUGS: + - fseek with negative offset on ps2 over ps2link messes up the current position diff --git a/vendor/librw/args.h b/vendor/librw/args.h new file mode 100644 index 0000000..0e666c1 --- /dev/null +++ b/vendor/librw/args.h @@ -0,0 +1,24 @@ +extern char *argv0; +#define USED(x) ((void)x) +#define SET(x) ((x)=0) + +#define ARGBEGIN for((argv0||(argv0=*argv)),argv++,argc--;\ + argv[0] && argv[0][0]=='-' && argv[0][1];\ + argc--, argv++) {\ + char *_args, *_argt;\ + char _argc;\ + _args = &argv[0][1];\ + if(_args[0]=='-' && _args[1]==0){\ + argc--; argv++; break;\ + }\ + _argc = 0;\ + while(*_args && (_argc = *_args++))\ + switch(_argc) +#define ARGEND SET(_argt);USED(_argt);USED(_argc);USED(_args);}USED(argv);USED(argc); +#define ARGF() (_argt=_args, _args=(char*)"",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): 0)) +#define EARGF(x) (_argt=_args, _args=(char*)"",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): ((x), abort(), (char*)0))) + +#define ARGC() _argc + diff --git a/vendor/librw/cmake/FindSDL2.cmake b/vendor/librw/cmake/FindSDL2.cmake new file mode 100644 index 0000000..24288b4 --- /dev/null +++ b/vendor/librw/cmake/FindSDL2.cmake @@ -0,0 +1,38 @@ +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(SDL2 IMPORTED_TARGET "sdl2") + if(TARGET PkgConfig::SDL2 AND NOT TARGET SDL2::SDL2) + add_library(SDL2::SDL2 INTERFACE IMPORTED) + set_property(TARGET SDL2::SDL2 PROPERTY INTERFACE_LINK_LIBRARIES PkgConfig::SDL2) + endif() +endif() + +find_library(SDL2main_LIBRARY SDL2main) + +if(NOT SDL2_FOUND) + find_path(SDL2_INCLUDE_DIR sdl2.h) + find_library(SDL2_LIBRARY SDL2 SDL2d) + + find_library(SDL2main_LIBRARY SDL2main) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(libuv + REQUIRED_VARS SDL2_INCLUDE_DIR SDL2_LIBRARY + ) + + if(NOT TARGET SDL2::SDL2) + add_library(SDL2::SDL2 UNKNOWN IMPORTED) + set_target_properties(SDL2::SDL2 PROPERTIES + IMPORTED_LOCATION "${SDL2_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}" + ) + endif() +endif() + +if(SDL2main_LIBRARY AND NOT TARGET SDL2::SDL2main) + add_library(SDL2::SDL2main UNKNOWN IMPORTED) + set_target_properties(SDL2::SDL2main PROPERTIES + IMPORTED_LOCATION "${SDL2main_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}" + ) +endif() diff --git a/vendor/librw/cmake/librw-config.cmake.in b/vendor/librw/cmake/librw-config.cmake.in new file mode 100644 index 0000000..47b3487 --- /dev/null +++ b/vendor/librw/cmake/librw-config.cmake.in @@ -0,0 +1,22 @@ +include("${CMAKE_CURRENT_LIST_DIR}/librw-targets.cmake") + +set(LIBRW_PLATFORM "@LIBRW_PLATFORM@") +set(LIBRW_PLATFORMS "@LIBRW_PLATFORMS@") +set(LIBRW_PLATFORM_@LIBRW_PLATFORM@ ON) + +if(LIBRW_PLATFORM_GL3) + set(LIBRW_GL3_GFXLIB "@LIBRW_GL3_GFXLIB@") + set(LIBRW_GL3_GFXLIBS "@LIBRW_GL3_GFXLIBS@") + + set(OpenGL_GL_PREFERENCE GLVND) + find_package(OpenGL) + if(NOT TARGET OpenGL::OpenGL AND NOT TARGET OpenGL::EGL AND NOT TARGET OpenGL::GL) + message(FATAL_ERROR "find_package(OpenGL) failed: no target was created") + endif() + + if(LIBRW_GL3_GFXLIB STREQUAL "GLFW") + find_package(glfw3 REQUIRED) + elseif(LIBRW_GL3_GFXLIB STREQUAL "SDL2") + find_package(SDL2 REQUIRED) + endif() +endif() diff --git a/vendor/librw/cmake/nx/NXFunctions.cmake b/vendor/librw/cmake/nx/NXFunctions.cmake new file mode 100644 index 0000000..bab3360 --- /dev/null +++ b/vendor/librw/cmake/nx/NXFunctions.cmake @@ -0,0 +1,37 @@ +if(NOT COMMAND nx_generate_nacp) + message(FATAL_ERROR "The `nx_generate_nacp` cmake command is not available. Please use an appropriate Nintendo Switch toolchain.") +endif() + +if(NOT COMMAND nx_create_nro) + message(FATAL_ERROR "The `nx_create_nro` cmake command is not available. Please use an appropriate Nintendo Switch toolchain.") +endif() + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") + +function(librw_platform_target TARGET) + cmake_parse_arguments(LPT "INSTALL" "" "" ${ARGN}) + + get_target_property(TARGET_TYPE "${TARGET}" TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + nx_generate_nacp(${TARGET}.nacp + NAME "${TARGET}" + AUTHOR "${librw_AUTHOR}" + VERSION "${librw_VERSION}" + ) + + nx_create_nro(${TARGET} + NACP ${TARGET}.nacp + ) + + if(LIBRW_INSTALL AND LPT_INSTALL) + get_target_property(TARGET_OUTPUT_NAME ${TARGET} OUTPUT_NAME) + if(NOT TARGET_OUTPUT_NAME) + set(TARGET_OUTPUT_NAME "${TARGET}") + endif() + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_OUTPUT_NAME}.nro" + DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + endif() + endif() +endfunction() diff --git a/vendor/librw/cmake/ps2/PS2Functions.cmake b/vendor/librw/cmake/ps2/PS2Functions.cmake new file mode 100644 index 0000000..5cd81dd --- /dev/null +++ b/vendor/librw/cmake/ps2/PS2Functions.cmake @@ -0,0 +1,18 @@ +if(NOT COMMAND add_erl_executable) + message(FATAL_ERROR "The `add_erl_executable` cmake command is not available. Please use an appropriate Playstation 2 toolchain.") +endif() + +function(librw_platform_target TARGET) + cmake_parse_arguments(LPT "INSTALL" "" "" ${ARGN}) + + get_target_property(TARGET_TYPE "${TARGET}" TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + add_erl_executable(${TARGET} OUTPUT_VAR ERL_FILE) + + if(LIBRW_INSTALL AND LPT_INSTALL) + install(FILES "${ERL_FILE}" + DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + endif() + endif() +endfunction() diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in new file mode 100644 index 0000000..9a55d4c --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMCompiler.cmake.in @@ -0,0 +1,16 @@ +set(CMAKE_DSM_COMPILER "@_CMAKE_DSM_COMPILER@") +set(CMAKE_DSM_COMPILER_ARG1 "@_CMAKE_DSM_COMPILER_ARG1@") +set(CMAKE_DSM_COMPILER_AR "@_CMAKE_DSM_COMPILER_AR@") +set(CMAKE_RANLIB "@CMAKE_RANLIB@") +set(CMAKE_DSM_COMPILER_RANLIB "@_CMAKE_DSM_COMPILER_RANLIB@") +set(CMAKE_LINKER "@CMAKE_LINKER@") +set(CMAKE_DSM_COMPILER_LOADED 1) +set(CMAKE_DSM_COMPILER_ID "@_CMAKE_DSM_COMPILER_ID@") +set(CMAKE_DSM_COMPILER_VERSION "@_CMAKE_DSM_COMPILER_VERSION@") +set(CMAKE_DSM_COMPILER_ENV_VAR "@_CMAKE_DSM_COMPILER_ENV_VAR@") +@_SET_CMAKE_DSM_COMPILER_ARCHITECTURE_ID@ + +set(CMAKE_DSM_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) +set(CMAKE_DSM_LINKER_PREFERENCE 0) + +@CMAKE_DSM_COMPILER_CUSTOM_CODE@ diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake new file mode 100644 index 0000000..27b56d6 --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDSMInformation.cmake @@ -0,0 +1,79 @@ +if(UNIX) + set(CMAKE_DSM_OUTPUT_EXTENSION .o) +else() + set(CMAKE_DSM_OUTPUT_EXTENSION .obj) +endif() + +set(CMAKE_INCLUDE_FLAG_DSM "-I") + +set(CMAKE_DSM_FLAGS_INIT "$ENV{DSMFLAGS} ${CMAKE_DSM_FLAGS_INIT}") + +# replace for CMake >= 3.11 +foreach(c "" _DEBUG _RELEASE _MINSIZEREL _RELWITHDEBINFO) + string(STRIP "${CMAKE_DSM_FLAGS${c}_INIT}" CMAKE_DSM_FLAGS${c}_INIT) +endforeach() + +set (CMAKE_DSM_FLAGS "${CMAKE_DSM_FLAGS_INIT}" CACHE STRING + "Flags used by the assembler during all build types.") + +if(NOT CMAKE_NOT_USING_CONFIG_FLAGS) + get_property(_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + # default build type is none + if(NOT _GENERATOR_IS_MULTI_CONFIG AND NOT CMAKE_NO_BUILD_TYPE) + set (CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE_INIT} CACHE STRING + "Choose the type of build, options are: None, Debug Release RelWithDebInfo MinSizeRel.") + endif() + unset(_GENERATOR_IS_MULTI_CONFIG) + set (CMAKE_DSM_FLAGS_DEBUG "${CMAKE_DSM_FLAGS_DEBUG_INIT}" CACHE STRING + "Flags used by the assembler during debug builds.") + set (CMAKE_DSM_FLAGS_MINSIZEREL "${CMAKE_DSM_FLAGS_MINSIZEREL_INIT}" CACHE STRING + "Flags used by the assembler during release minsize builds.") + set (CMAKE_DSM_FLAGS_RELEASE "${CMAKE_DSM_FLAGS_RELEASE_INIT}" CACHE STRING + "Flags used by the assembler during release builds.") + set (CMAKE_DSM_FLAGS_RELWITHDEBINFO "${CMAKE_DSM_FLAGS_RELWITHDEBINFO_INIT}" CACHE STRING + "Flags used by the assembler during Release with Debug Info builds.") +endif() + +mark_as_advanced(CMAKE_DSM_FLAGS + CMAKE_DSM_FLAGS_DEBUG + CMAKE_DSM_FLAGS_MINSIZEREL + CMAKE_DSM_FLAGS_RELEASE + CMAKE_DSM_FLAGS_RELWITHDEBINFO + ) +# WITH: cmake_initialize_per_config_variable(CMAKE_DSM_FLAGS "Flags used by the DSM compiler") + +if(NOT CMAKE_DSM_COMPILE_OBJECT) + set(CMAKE_DSM_COMPILE_OBJECT " -o ") +endif() + +if(NOT CMAKE_DSM_CREATE_STATIC_LIBRARY) + set(CMAKE_DSM_CREATE_STATIC_LIBRARY + " cr " + " ") +endif() + +if(NOT CMAKE_DSM_LINK_EXECUTABLE) + set(CMAKE_DSM_LINK_EXECUTABLE + " -o ") +endif() + +if(NOT CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG) + set(CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG ${CMAKE_SHARED_LIBRARY_RUNTIME_DSM_FLAG}) +endif() + +if(NOT CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG_SEP) + set(CMAKE_EXECUTABLE_RUNTIME_DSM_FLAG_SEP ${CMAKE_SHARED_LIBRARY_RUNTIME_DSM_FLAG_SEP}) +endif() + +if(NOT CMAKE_EXECUTABLE_RPATH_LINK_DSM_FLAG) + set(CMAKE_EXECUTABLE_RPATH_LINK_DSM_FLAG ${CMAKE_SHARED_LIBRARY_RPATH_LINK_DSM_FLAG}) +endif() + +# to be done +if(NOT CMAKE_DSM_CREATE_SHARED_LIBRARY) + set(CMAKE_DSM_CREATE_SHARED_LIBRARY) +endif() + +if(NOT CMAKE_DSM_CREATE_SHARED_MODULE) + set(CMAKE_DSM_CREATE_SHARED_MODULE) +endif() diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake new file mode 100644 index 0000000..d3bda8b --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeDetermineDSMCompiler.cmake @@ -0,0 +1,87 @@ +include(${CMAKE_ROOT}/Modules/CMakeDetermineCompiler.cmake) + +if (NOT CMAKE_DSM_COMPILER) + message(FATAL_ERROR "Need CMAKE_DSM_COMPILER set") +endif() + +_cmake_find_compiler_path(DSM) +mark_as_advanced(CMAKE_DSM_COMPILER) + +if (NOT CMAKE_DSM_COMPILER_ID) + # Table of per-vendor compiler id flags with expected output. + list(APPEND CMAKE_DSM_COMPILER_ID_VENDORS GNU ) + set(CMAKE_DSM_COMPILER_ID_VENDOR_FLAGS_GNU "--version") + set(CMAKE_DSM_COMPILER_ID_VENDOR_REGEX_GNU "(GNU assembler)|(GCC)|(Free Software Foundation)") + + include(CMakeDetermineCompilerId) + cmake_determine_compiler_id_vendor(DSM "") + +endif() + +if (NOT _CMAKE_TOOLCHAIN_LOCATION) + get_filename_component(_CMAKE_TOOLCHAIN_LOCATION "${CMAKE_DSM_COMPILER}" PATH) +endif() + +if (CMAKE_DSM_COMPILER_ID) + if (CMAKE_DSM_COMPILER_VERSION) + set(_version " ${CMAKE_DSM_COMPILER_VERSION}") + else() + set(_version "") + endif() + message(STATUS "The DSM compiler identification is ${CMAKE_DSM_COMPILER_ID}${_version}") + unset(_version) +else() + message(STATUS "The DSM compiler identification is unknown") +endif() + +if (NOT _CMAKE_TOOLCHAIN_PREFIX) + get_filename_component(COMPILER_BASENAME "${CMAKE_DSM_COMPILER}" NAME) + if (COMPILER_BASENAME MATCHES "^(.+1)g?as(-[0-9]+\\.[0-9]+\\.[0-9]+)?(\\.exe)?$") + set(_CMAKE_TOOLCHAIN_PREFIX ${CMAKE_MATCH_1}) + endif() + +endif() + +set(_CMAKE_PROCESSING_LANGUAGE "DSM") +find_program(CMAKE_DSM_COMPILER_AR NAMES ${_CMAKE_TOOLCHAIN_PREFIX}ar HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_RANLIB NAMES ${_CMAKE_TOOLCHAIN_PREFIX}ranlib HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_STRIP NAMES ${_CMAKE_TOOLCHAIN_PREFIX}strip HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_NM NAMES ${_CMAKE_TOOLCHAIN_PREFIX}nm HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_OBJDUMP NAMES ${_CMAKE_TOOLCHAIN_PREFIX}objdump HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) +find_program(CMAKE_DSM_COMPILER_OBJCOPY NAMES ${_CMAKE_TOOLCHAIN_PREFIX}objcopy HINTS ${_CMAKE_TOOLCHAIN_LOCATION}) + +unset(_CMAKE_PROCESSING_LANGUAGE) + +set(CMAKE_DSM_COMPILER_ENV_VAR "DSM") + +if (CMAKE_DSM_COMPILER) + message(STATUS "Found DSM assembler: ${CMAKE_DSM_COMPILER}") +else() + message(STATUS "Didn't find assembler") +endif() + +foreach(_var + COMPILER + COMPILER_ID + COMPILER_ARG1 + COMPILER_ENV_VAR + COMPILER_AR + COMPILER_RANLIB + COMPILER_VERSION + ) + set(_CMAKE_DSM_${_var} "${CMAKE_DSM_${_var}}") +endforeach() + +configure_file("${CMAKE_CURRENT_LIST_DIR}/CMakeDSMCompiler.cmake.in" + "${CMAKE_PLATFORM_INFO_DIR}/CMakeDSMCompiler.cmake" @ONLY) + +foreach(_var + COMPILER + COMPILER_ID + COMPILER_ARG1 + COMPILER_ENV_VAR + COMPILER_AR + COMPILER_VERSION + ) + unset(_CMAKE_DSM_${_var}) +endforeach() diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake new file mode 100644 index 0000000..5c514ea --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/CMakeTestDSMCompiler.cmake @@ -0,0 +1,7 @@ +set(_ASM_COMPILER_WORKS 0) + +if(CMAKE_DSM_COMPILER) + set(_DSM_COMPILER_WORKS) +endif() + +set(CMAKE_DSM_COMPILER_WORKS ${_DSM_COMPILER_WORKS} CACHE INTERNAL "") diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake new file mode 100644 index 0000000..bd2995e --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/Platform/PlayStation2.cmake @@ -0,0 +1 @@ +set(CMAKE_EXECUTABLE_SUFFIX ".elf") diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py b/vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py new file mode 100644 index 0000000..4cc9d3c --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/conanfile.py @@ -0,0 +1,24 @@ +from conans import ConanFile +import os +import shutil + + +class Ps2devCMakeToolchainConan(ConanFile): + name = "ps2dev-cmaketoolchain" + description = "CMake toolchain script for ps2dev" + topics = "ps2", "sdk", "library", "sony", "playstation", "ps2" + + def export_sources(self): + self.copy("*.cmake*", dst="cmake") + self.copy("Platform", dst="cmake") + + def package(self): + shutil.copytree(os.path.join(self.source_folder, "cmake"), + os.path.join(self.package_folder, "cmake")) + + def package_info(self): + self.user_info.cmake_dir = os.path.join(self.package_folder, "cmake").replace("\\", "/") + + cmake_toolchain_file = os.path.join(self.package_folder, "cmake", "toolchain_ps2_ee.cmake").replace("\\", "/") + self.user_info.cmake_toolchain_file = cmake_toolchain_file + self.cpp_info.CONAN_CMAKE_TOOLCHAIN_FILE = cmake_toolchain_file diff --git a/vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake b/vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake new file mode 100644 index 0000000..da1efad --- /dev/null +++ b/vendor/librw/cmake/ps2/cmaketoolchain/toolchain_ps2_ee.cmake @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 3.7) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + +set(CMAKE_SYSTEM_NAME "PlayStation2") +set(CMAKE_SYSTEM_PROCESSOR "mips64r5900el") +set(CMAKE_SYSTEM_VERSION 1) + +set(CMAKE_NO_SYSTEM_FROM_IMPORTED ON) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +if(NOT DEFINED ENV{PS2DEV}) + message(FATAL_ERROR "Need environment variable PS2DEV set") +endif() +if(NOT DEFINED ENV{PS2SDK}) + message(FATAL_ERROR "Need environment variable PS2SDK set") +endif() +if(NOT DEFINED ENV{GSKIT}) + message(FATAL_ERROR "Need environment variable PS2SDK set") +endif() + +set(PS2DEV "$ENV{PS2DEV}") +set(PS2SDK "$ENV{PS2SDK}") +set(GSKIT "$ENV{GSKIT}") + +if(NOT IS_DIRECTORY "${PS2DEV}") + message(FATAL_ERROR "PS2DEV must be a folder path (${PS2DEV})") +endif() + +if(NOT IS_DIRECTORY "${PS2SDK}") + message(FATAL_ERROR "PS2SDK must be a folder path (${PS2SDK})") +endif() + +if(NOT IS_DIRECTORY "${GSKIT}") + message(FATAL_ERROR "GSKIT must be a folder path (${GSKIT})") +endif() + +set(CMAKE_DSM_SOURCE_FILE_EXTENSIONS "dsm") + +set(CMAKE_C_COMPILER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-gcc" CACHE FILEPATH "C compiler") +set(CMAKE_CXX_COMPILER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-g++" CACHE FILEPATH "CXX compiler") +set(CMAKE_ASM_COMPILER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-g++" CACHE FILEPATH "ASM assembler") +set(CMAKE_DSM_COMPILER "${PS2DEV}/dvp/bin/dvp-as" CACHE FILEPATH "DSM assembler") +set(CMAKE_AR "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-ar" CACHE FILEPATH "archiver") +set(CMAKE_LINKER "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-ld" CACHE FILEPATH "Linker") +set(CMAKE_RANLIB "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-ranlib" CACHE FILEPATH "ranlib") +set(CMAKE_STRIP "${PS2DEV}/ee/bin/mips64r5900el-ps2-elf-strip" CACHE FILEPATH "strip") + +set(CMAKE_ASM_FLAGS_INIT "-G0 -I\"${PS2SDK}/ee/include\" -I\"${PS2SDK}/common/include\" -D_EE") +set(CMAKE_C_FLAGS_INIT "-G0 -fno-common -I\"${PS2SDK}/ee/include\" -I\"${PS2SDK}/common/include\" -D_EE") +set(CMAKE_CXX_FLAGS_INIT "-G0 -fno-common -I\"${PS2SDK}/ee/include\" -I\"${PS2SDK}/common/include\" -D_EE") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-G0 -L\"${PS2SDK}/ee/lib\" -Wl,-r -Wl,-d") + +set(CMAKE_FIND_ROOT_PATH "${PS2DEV}/ee" "${PS2SDK}/ee" "${GSKIT}") +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +set(PS2 1) +set(EE 1) + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") + +function(add_erl_executable TARGET) + cmake_parse_arguments("AEE" "" "OUTPUT_VAR" "" ${ARGN}) + + get_target_property(output_dir "${TARGET}" RUNTIME_OUTPUT_DIRECTORY) + if(NOT output_dir) + set(output_dir ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + get_target_property(output_name ${TARGET} OUTPUT_NAME) + if(NOT output_name) + set(output_name ${TARGET}) + endif() + set(outfile "${output_dir}/${output_name}.erl") + + add_custom_command(OUTPUT "${outfile}" + COMMAND "${CMAKE_COMMAND}" -E copy "$" "${outfile}" + COMMAND "${CMAKE_STRIP}" --strip-unneeded -R .mdebug.eabi64 -R .reginfo -R .comment "${outfile}" + DEPENDS ${TARGET} + ) + add_custom_target("${TARGET}_erl" ALL + DEPENDS "${outfile}" + ) + + if(AEE_OUTPUT_VAR) + set("${AEE_OUTPUT_VAR}" "${outfile}" PARENT_SCOPE) + endif() +endfunction() diff --git a/vendor/librw/conan/playstation2 b/vendor/librw/conan/playstation2 new file mode 100644 index 0000000..ddad52b --- /dev/null +++ b/vendor/librw/conan/playstation2 @@ -0,0 +1,12 @@ +[settings] +os=Playstation2 +arch=mips +compiler=gcc +compiler.version=3.2 +compiler.libcxx=libstdc++ +build_type=Release +[options] +librw:platform=ps2 +[build_requires] +ps2dev-ps2toolchain/unknown@madebr/testing +[env] diff --git a/vendor/librw/conanfile.py b/vendor/librw/conanfile.py new file mode 100644 index 0000000..4cefb49 --- /dev/null +++ b/vendor/librw/conanfile.py @@ -0,0 +1,136 @@ +from conans import ConanFile, CMake, tools +from conans.errors import ConanException, ConanInvalidConfiguration +import os +import shutil +import textwrap + + +class LibrwConan(ConanFile): + name = "librw" + version = "master" + license = "MIT" + settings = "os", "arch", "compiler", "build_type" + generators = "cmake", "cmake_find_package" + options = { + "platform": ["null", "gl3", "d3d9", "ps2"], + "gl3_gfxlib": ["glfw", "sdl2"], + } + default_options = { + "platform": "gl3", + "gl3_gfxlib": "glfw", + "openal:with_external_libs": 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 config_options(self): + if self._os_is_playstation2: + self.options.platform = "ps2" + if self.settings.os == "Windows": + self.options.platform = "d3d9" + self.options["sdl2"].directx = False + + def configure(self): + if self.options.platform != "gl3": + del self.options.gl3_gfxlib + + def validate(self): + if self.options.platform == "d3d9" and self.settings.os != "Windows": + raise ConanInvalidConfiguration("platform=d3d9 can only be built for os=Windows") + if self._os_is_playstation2: + if self.options.platform not in ("null", "ps2"): + raise ConanInvalidConfiguration("os=Playstation2 only supports platform=(null,ps2)") + + def requirements(self): + if self.options.platform == "gl3": + if self.options.gl3_gfxlib == "glfw": + self.requires("glfw/3.3.2") + elif self.options.gl3_gfxlib == "sdl2": + self.requires("sdl2/2.0.12@bincrafters/stable") + elif self.options.platform == "ps2": + self.requires("ps2dev-ps2sdk/unknown@madebr/testing") + if self._os_is_playstation2: + self.requires("ps2dev-cmaketoolchain/{}".format(self.version)) + + def export_sources(self): + for d in ("cmake", "skeleton", "src", "tools"): + shutil.copytree(src=d, dst=os.path.join(self.export_sources_folder, d)) + self.copy("args.h") + self.copy("rw.h") + self.copy("CMakeLists.txt") + self.copy("LICENSE") + + @property + def _librw_platform(self): + return { + "null": "NULL", + "gl3": "GL3", + "d3d9": "D3D9", + "ps2": "PS2", + }[str(self.options.platform)] + + def build(self): + if self.source_folder == self.build_folder: + raise Exception("cannot build with source_folder == build_folder") + if self.options.platform == "gl3" and self.options.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) + + add_subdirectory("{}" librw) + """).format(self.install_folder.replace("\\", "/"), + self.source_folder.replace("\\", "/"))) + cmake = CMake(self) + env = {} + cmake.definitions["LIBRW_PLATFORM"] = self._librw_platform + cmake.definitions["LIBRW_INSTALL"] = True + cmake.definitions["LIBRW_TOOLS"] = True + if self.options.platform == "gl3": + cmake.definitions["LIBRW_GL3_GFXLIB"] = str(self.options.gl3_gfxlib).upper() + if self._os_is_playstation2: + 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() + + def package_info(self): + self.cpp_info.includedirs.append(os.path.join("include", "librw")) + self.cpp_info.libs = ["librw" if self.settings.compiler == "Visual Studio" else "rw"] + if self.options.platform == "null": + self.cpp_info.defines.append("RW_NULL") + elif self.options.platform == "gl3": + self.cpp_info.defines.append("RW_GL3") + if self.options.gl3_gfxlib == "sdl2": + self.cpp_info.defines.append("LIBRW_SDL2") + elif self.options.platform == "d3d9": + self.cpp_info.defines.append("RW_D3D9") + elif self.options.platform == "ps2": + self.cpp_info.defines.append("RW_PS2") diff --git a/vendor/librw/docker_rebuild_ps2.sh b/vendor/librw/docker_rebuild_ps2.sh new file mode 100644 index 0000000..cf8148f --- /dev/null +++ b/vendor/librw/docker_rebuild_ps2.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +TARGET=release_ps2 + +set -e + +LIBRWDIR=$(dirname "$(readlink -f "$0")") +echo "LIBRWDIR is $LIBRWDIR" + +cd "$LIBRWDIR" + +premake5 gmake + +docker rm librw_instance -f >/dev/null 2>&1 || true +docker stop librw_instance -t 0 >/dev/null 2>&1 || true + +docker pull librw/librw +docker run -v "$LIBRWDIR:/librw:rw,z" --name librw_instance -d librw/librw sleep infinity +docker exec -u builder librw_instance /bin/bash -c "cd /librw/build && make clean config=$TARGET && make config=$TARGET verbose=1" + +docker rm librw_instance -f >/dev/null 2>&1 || true +docker stop librw_instance -t 0 >/dev/null 2>&1 || true diff --git a/vendor/librw/premake-vs2019.cmd b/vendor/librw/premake-vs2019.cmd new file mode 100644 index 0000000..18298f5 --- /dev/null +++ b/vendor/librw/premake-vs2019.cmd @@ -0,0 +1 @@ +premake5 vs2019 \ No newline at end of file diff --git a/vendor/librw/premake5.exe b/vendor/librw/premake5.exe new file mode 100644 index 0000000..9048d51 Binary files /dev/null and b/vendor/librw/premake5.exe differ diff --git a/vendor/librw/premake5.lua b/vendor/librw/premake5.lua new file mode 100644 index 0000000..323aa41 --- /dev/null +++ b/vendor/librw/premake5.lua @@ -0,0 +1,277 @@ +newoption { + trigger = "gfxlib", + value = "LIBRARY", + description = "Choose a particular development library", + default = "glfw", + allowed = { + { "glfw", "GLFW" }, + { "sdl2", "SDL2" }, + }, +} + +newoption { + trigger = "glfwdir64", + value = "PATH", + description = "Directory of glfw", + default = "../glfw-3.3.4.bin.WIN64", +} + +newoption { + trigger = "glfwdir32", + value = "PATH", + description = "Directory of glfw", + default = "../glfw-3.3.4.bin.WIN32", +} + +newoption { + trigger = "sdl2dir", + value = "PATH", + description = "Directory of sdl2", + default = "../SDL2-2.0.14", +} + +workspace "librw" + location "build" + language "C++" + + configurations { "Release", "Debug" } + filter { "system:windows" } + configurations { "ReleaseStatic" } + platforms { "win-x86-null", "win-x86-gl3", "win-x86-d3d9", + "win-amd64-null", "win-amd64-gl3", "win-amd64-d3d9" } + filter { "system:linux" } + platforms { "linux-x86-null", "linux-x86-gl3", + "linux-amd64-null", "linux-amd64-gl3", + "linux-arm-null", "linux-arm-gl3", + "ps2" } + if _OPTIONS["gfxlib"] == "sdl2" then + includedirs { "/usr/include/SDL2" } + end + filter {} + + filter "configurations:Debug" + defines { "DEBUG" } + symbols "On" + filter "configurations:Release*" + defines { "NDEBUG" } + optimize "On" + filter "configurations:ReleaseStatic" + staticruntime("On") + + filter { "platforms:*null" } + defines { "RW_NULL" } + filter { "platforms:*gl3" } + defines { "RW_GL3" } + if _OPTIONS["gfxlib"] == "sdl2" then + defines { "LIBRW_SDL2" } + end + filter { "platforms:*d3d9" } + defines { "RW_D3D9" } + filter { "platforms:ps2" } + defines { "RW_PS2" } + toolset "gcc" + gccprefix 'ee-' + buildoptions { "-nostdlib", "-fno-common" } + includedirs { "$(PS2SDK)/ee/include", "$(PS2SDK)/common/include" } + optimize "Off" + + filter { "platforms:*amd64*" } + architecture "x86_64" + filter { "platforms:*x86*" } + architecture "x86" + filter { "platforms:*arm*" } + architecture "ARM" + + filter { "platforms:win*" } + system "windows" + filter { "platforms:linux*" } + system "linux" + + filter { "platforms:win*gl3" } + includedirs { path.join(_OPTIONS["sdl2dir"], "include") } + filter { "platforms:win-x86-gl3" } + includedirs { path.join(_OPTIONS["glfwdir32"], "include") } + filter { "platforms:win-amd64-gl3" } + includedirs { path.join(_OPTIONS["glfwdir64"], "include") } + + filter "action:vs*" + buildoptions { "/wd4996", "/wd4244" } + + filter { "platforms:win*gl3", "action:not vs*" } + if _OPTIONS["gfxlib"] == "sdl2" then + includedirs { "/mingw/include/SDL2" } -- TODO: Detect this properly + end + + filter {} + + Libdir = "lib/%{cfg.platform}/%{cfg.buildcfg}" + Bindir = "bin/%{cfg.platform}/%{cfg.buildcfg}" + +project "librw" + kind "StaticLib" + targetname "rw" + targetdir (Libdir) + defines { "LODEPNG_NO_COMPILE_CPP" } + files { "src/*.*" } + files { "src/*/*.*" } + filter { "platforms:*gl3" } + files { "src/gl/glad/*.*" } + +project "dumprwtree" + kind "ConsoleApp" + targetdir (Bindir) + removeplatforms { "*gl3", "*d3d9", "ps2" } + files { "tools/dumprwtree/*" } + includedirs { "." } + libdirs { Libdir } + links { "librw" } + +function findlibs() + filter { "platforms:linux*gl3" } + links { "GL" } + if _OPTIONS["gfxlib"] == "glfw" then + links { "glfw" } + else + links { "SDL2" } + end + filter { "platforms:win-amd64-gl3" } + libdirs { path.join(_OPTIONS["glfwdir64"], "lib-vc2015") } + libdirs { path.join(_OPTIONS["sdl2dir"], "lib/x64") } + filter { "platforms:win-x86-gl3" } + libdirs { path.join(_OPTIONS["glfwdir32"], "lib-vc2015") } + libdirs { path.join(_OPTIONS["sdl2dir"], "lib/x86") } + filter { "platforms:win*gl3" } + links { "opengl32" } + if _OPTIONS["gfxlib"] == "glfw" then + links { "glfw3" } + else + links { "SDL2" } + end + filter { "platforms:*d3d9" } + links { "gdi32", "d3d9" } + filter { "platforms:*d3d9", "action:vs*" } + links { "Xinput9_1_0" } + filter {} +end + +function skeleton() + files { "skeleton/*.cpp", "skeleton/*.h" } + files { "skeleton/imgui/*.cpp", "skeleton/imgui/*.h" } + includedirs { "skeleton" } +end + +function skeltool(dir) + targetdir (Bindir) + files { path.join("tools", dir, "*.cpp"), + path.join("tools", dir, "*.h") } + vpaths { + {["src"] = { path.join("tools", dir, "*") }}, + {["skeleton"] = { "skeleton/*" }}, + } + skeleton() + debugdir ( path.join("tools", dir) ) + includedirs { "." } + libdirs { Libdir } + links { "librw" } + findlibs() +end + +function vucode() + filter "files:**.dsm" + buildcommands { + 'cpp "%{file.relpath}" | dvp-as -o "%{cfg.objdir}/%{file.basename}.o"' + } + buildoutputs { '%{cfg.objdir}/%{file.basename}.o' } + filter {} +end + +project "playground" + kind "WindowedApp" + characterset ("MBCS") + skeltool("playground") + entrypoint("WinMainCRTStartup") + removeplatforms { "*null" } + removeplatforms { "ps2" } -- for now + +project "imguitest" + kind "WindowedApp" + characterset ("MBCS") + skeltool("imguitest") + entrypoint("WinMainCRTStartup") + removeplatforms { "*null" } + removeplatforms { "ps2" } + +project "lights" + kind "WindowedApp" + characterset ("MBCS") + skeltool("lights") + entrypoint("WinMainCRTStartup") + removeplatforms { "*null" } + removeplatforms { "ps2" } + +project "subrast" + kind "WindowedApp" + characterset ("MBCS") + skeltool("subrast") + entrypoint("WinMainCRTStartup") + removeplatforms { "*null" } + removeplatforms { "ps2" } + +project "camera" + kind "WindowedApp" + characterset ("MBCS") + skeltool("camera") + entrypoint("WinMainCRTStartup") + removeplatforms { "*null" } + removeplatforms { "ps2" } + +project "im2d" + kind "WindowedApp" + characterset ("MBCS") + skeltool("im2d") + entrypoint("WinMainCRTStartup") + removeplatforms { "*null" } + removeplatforms { "ps2" } + +project "im3d" + kind "WindowedApp" + characterset ("MBCS") + skeltool("im3d") + entrypoint("WinMainCRTStartup") + removeplatforms { "*null" } + removeplatforms { "ps2" } + +project "ska2anm" + kind "ConsoleApp" + characterset ("MBCS") + targetdir (Bindir) + files { path.join("tools/ska2anm", "*.cpp"), + path.join("tools/ska2anm", "*.h") } + debugdir ( path.join("tools/ska2nm") ) + includedirs { "." } + libdirs { Libdir } + links { "librw" } + findlibs() + removeplatforms { "*gl3", "*d3d9", "*ps2" } + +project "ps2test" + kind "ConsoleApp" + targetdir (Bindir) + vucode() + removeplatforms { "*gl3", "*d3d9", "*null" } + targetextension '.elf' + includedirs { "." } + files { "tools/ps2test/*.cpp", + "tools/ps2test/vu/*.dsm", + "tools/ps2test/*.h" } + libdirs { "$(PS2SDK)/ee/lib" } + links { "librw" } + +--project "ps2rastertest" +-- kind "ConsoleApp" +-- targetdir (Bindir) +-- removeplatforms { "*gl3", "*d3d9" } +-- files { "tools/ps2rastertest/*.cpp" } +-- includedirs { "." } +-- libdirs { Libdir } +-- links { "librw" } diff --git a/vendor/librw/rw.h b/vendor/librw/rw.h new file mode 100644 index 0000000..fb9829f --- /dev/null +++ b/vendor/librw/rw.h @@ -0,0 +1,25 @@ +#include +#include +#include + +#include "src/rwbase.h" +#include "src/rwerror.h" +#include "src/rwplg.h" +#include "src/rwrender.h" +#include "src/rwengine.h" +#include "src/rwpipeline.h" +#include "src/rwobjects.h" +#include "src/rwanim.h" +#include "src/rwplugins.h" +#include "src/rwuserdata.h" +#include "src/rwcharset.h" +#include "src/ps2/rwps2.h" +#include "src/ps2/rwps2plg.h" +#include "src/d3d/rwxbox.h" +#include "src/d3d/rwd3d.h" +#include "src/d3d/rwd3d8.h" +#include "src/d3d/rwd3d9.h" +#include "src/gl/rwwdgl.h" +#include "src/gl/rwgl3.h" +#include "src/gl/rwgl3shader.h" +#include "src/gl/rwgl3plg.h" diff --git a/vendor/librw/skeleton/CMakeLists.txt b/vendor/librw/skeleton/CMakeLists.txt new file mode 100644 index 0000000..957afb5 --- /dev/null +++ b/vendor/librw/skeleton/CMakeLists.txt @@ -0,0 +1,80 @@ +add_library(librw_skeleton + glfw.cpp + sdl2.cpp + skeleton.cpp + skeleton.h + win.cpp + + imgui/imgui_impl_rw.cpp + imgui/imgui_impl_rw.h + + imgui/imconfig.h + imgui/imgui.cpp + imgui/imgui.h + imgui/imgui_demo.cpp + imgui/imgui_draw.cpp + imgui/imgui_internal.h + imgui/imgui_tables.cpp + imgui/imgui_widgets.cpp + imgui/imstb_rectpack.h + imgui/imstb_textedit.h + imgui/imstb_truetype.h + + imgui/ImGuizmo.cpp + imgui/ImGuizmo.h +) +add_library(librw::skeleton ALIAS librw_skeleton) + +set_target_properties(librw_skeleton + PROPERTIES + PREFIX "" + EXPORT_NAME skeleton + CXX_STANDARD 11 +) + +target_link_libraries(librw_skeleton + PRIVATE + librw +) + +target_include_directories(librw_skeleton + PUBLIC + $ + $ +) + +if(LIBRW_INSTALL) + install( + FILES + skeleton.h + DESTINATION "${LIBRW_INSTALL_INCLUDEDIR}/skeleton" + ) + + install( + FILES + imgui/imconfig.h + imgui/imgui.h + imgui/imgui_impl_rw.h + imgui/imgui_internal.h + imgui/ImGuizmo.h + imgui/imstb_rectpack.h + imgui/imstb_textedit.h + imgui/imstb_truetype.h + DESTINATION "${LIBRW_INSTALL_INCLUDEDIR}/skeleton/imgui" + ) + + install( + FILES + imgui/LICENSE_imgui.txt + imgui/LICENSE_imguizmo.txt + DESTINATION "${CMAKE_INSTALL_DOCDIR}" + ) + + install( + TARGETS librw_skeleton + EXPORT librw-targets + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ) +endif() diff --git a/vendor/librw/skeleton/glfw.cpp b/vendor/librw/skeleton/glfw.cpp new file mode 100644 index 0000000..23832c0 --- /dev/null +++ b/vendor/librw/skeleton/glfw.cpp @@ -0,0 +1,262 @@ +#ifndef LIBRW_SDL2 + +#include +#include "skeleton.h" + +using namespace sk; +using namespace rw; + +#ifdef RW_OPENGL + +GLFWwindow *window; +int keymap[GLFW_KEY_LAST+1]; + +static void +initkeymap(void) +{ + int i; + for(i = 0; i < GLFW_KEY_LAST+1; i++) + keymap[i] = KEY_NULL; + keymap[GLFW_KEY_SPACE] = ' '; + keymap[GLFW_KEY_APOSTROPHE] = '\''; + keymap[GLFW_KEY_COMMA] = ','; + keymap[GLFW_KEY_MINUS] = '-'; + keymap[GLFW_KEY_PERIOD] = '.'; + keymap[GLFW_KEY_SLASH] = '/'; + keymap[GLFW_KEY_0] = '0'; + keymap[GLFW_KEY_1] = '1'; + keymap[GLFW_KEY_2] = '2'; + keymap[GLFW_KEY_3] = '3'; + keymap[GLFW_KEY_4] = '4'; + keymap[GLFW_KEY_5] = '5'; + keymap[GLFW_KEY_6] = '6'; + keymap[GLFW_KEY_7] = '7'; + keymap[GLFW_KEY_8] = '8'; + keymap[GLFW_KEY_9] = '9'; + keymap[GLFW_KEY_SEMICOLON] = ';'; + keymap[GLFW_KEY_EQUAL] = '='; + keymap[GLFW_KEY_A] = 'A'; + keymap[GLFW_KEY_B] = 'B'; + keymap[GLFW_KEY_C] = 'C'; + keymap[GLFW_KEY_D] = 'D'; + keymap[GLFW_KEY_E] = 'E'; + keymap[GLFW_KEY_F] = 'F'; + keymap[GLFW_KEY_G] = 'G'; + keymap[GLFW_KEY_H] = 'H'; + keymap[GLFW_KEY_I] = 'I'; + keymap[GLFW_KEY_J] = 'J'; + keymap[GLFW_KEY_K] = 'K'; + keymap[GLFW_KEY_L] = 'L'; + keymap[GLFW_KEY_M] = 'M'; + keymap[GLFW_KEY_N] = 'N'; + keymap[GLFW_KEY_O] = 'O'; + keymap[GLFW_KEY_P] = 'P'; + keymap[GLFW_KEY_Q] = 'Q'; + keymap[GLFW_KEY_R] = 'R'; + keymap[GLFW_KEY_S] = 'S'; + keymap[GLFW_KEY_T] = 'T'; + keymap[GLFW_KEY_U] = 'U'; + keymap[GLFW_KEY_V] = 'V'; + keymap[GLFW_KEY_W] = 'W'; + keymap[GLFW_KEY_X] = 'X'; + keymap[GLFW_KEY_Y] = 'Y'; + keymap[GLFW_KEY_Z] = 'Z'; + keymap[GLFW_KEY_LEFT_BRACKET] = '['; + keymap[GLFW_KEY_BACKSLASH] = '\\'; + keymap[GLFW_KEY_RIGHT_BRACKET] = ']'; + keymap[GLFW_KEY_GRAVE_ACCENT] = '`'; + keymap[GLFW_KEY_ESCAPE] = KEY_ESC; + keymap[GLFW_KEY_ENTER] = KEY_ENTER; + keymap[GLFW_KEY_TAB] = KEY_TAB; + keymap[GLFW_KEY_BACKSPACE] = KEY_BACKSP; + keymap[GLFW_KEY_INSERT] = KEY_INS; + keymap[GLFW_KEY_DELETE] = KEY_DEL; + keymap[GLFW_KEY_RIGHT] = KEY_RIGHT; + keymap[GLFW_KEY_LEFT] = KEY_LEFT; + keymap[GLFW_KEY_DOWN] = KEY_DOWN; + keymap[GLFW_KEY_UP] = KEY_UP; + keymap[GLFW_KEY_PAGE_UP] = KEY_PGUP; + keymap[GLFW_KEY_PAGE_DOWN] = KEY_PGDN; + keymap[GLFW_KEY_HOME] = KEY_HOME; + keymap[GLFW_KEY_END] = KEY_END; + keymap[GLFW_KEY_CAPS_LOCK] = KEY_CAPSLK; + keymap[GLFW_KEY_SCROLL_LOCK] = KEY_NULL; + keymap[GLFW_KEY_NUM_LOCK] = KEY_NULL; + keymap[GLFW_KEY_PRINT_SCREEN] = KEY_NULL; + keymap[GLFW_KEY_PAUSE] = KEY_NULL; + + keymap[GLFW_KEY_F1] = KEY_F1; + keymap[GLFW_KEY_F2] = KEY_F2; + keymap[GLFW_KEY_F3] = KEY_F3; + keymap[GLFW_KEY_F4] = KEY_F4; + keymap[GLFW_KEY_F5] = KEY_F5; + keymap[GLFW_KEY_F6] = KEY_F6; + keymap[GLFW_KEY_F7] = KEY_F7; + keymap[GLFW_KEY_F8] = KEY_F8; + keymap[GLFW_KEY_F9] = KEY_F9; + keymap[GLFW_KEY_F10] = KEY_F10; + keymap[GLFW_KEY_F11] = KEY_F11; + keymap[GLFW_KEY_F12] = KEY_F12; + keymap[GLFW_KEY_F13] = KEY_NULL; + keymap[GLFW_KEY_F14] = KEY_NULL; + keymap[GLFW_KEY_F15] = KEY_NULL; + keymap[GLFW_KEY_F16] = KEY_NULL; + keymap[GLFW_KEY_F17] = KEY_NULL; + keymap[GLFW_KEY_F18] = KEY_NULL; + keymap[GLFW_KEY_F19] = KEY_NULL; + keymap[GLFW_KEY_F20] = KEY_NULL; + keymap[GLFW_KEY_F21] = KEY_NULL; + keymap[GLFW_KEY_F22] = KEY_NULL; + keymap[GLFW_KEY_F23] = KEY_NULL; + keymap[GLFW_KEY_F24] = KEY_NULL; + keymap[GLFW_KEY_F25] = KEY_NULL; + keymap[GLFW_KEY_KP_0] = KEY_NULL; + keymap[GLFW_KEY_KP_1] = KEY_NULL; + keymap[GLFW_KEY_KP_2] = KEY_NULL; + keymap[GLFW_KEY_KP_3] = KEY_NULL; + keymap[GLFW_KEY_KP_4] = KEY_NULL; + keymap[GLFW_KEY_KP_5] = KEY_NULL; + keymap[GLFW_KEY_KP_6] = KEY_NULL; + keymap[GLFW_KEY_KP_7] = KEY_NULL; + keymap[GLFW_KEY_KP_8] = KEY_NULL; + keymap[GLFW_KEY_KP_9] = KEY_NULL; + keymap[GLFW_KEY_KP_DECIMAL] = KEY_NULL; + keymap[GLFW_KEY_KP_DIVIDE] = KEY_NULL; + keymap[GLFW_KEY_KP_MULTIPLY] = KEY_NULL; + keymap[GLFW_KEY_KP_SUBTRACT] = KEY_NULL; + keymap[GLFW_KEY_KP_ADD] = KEY_NULL; + keymap[GLFW_KEY_KP_ENTER] = KEY_NULL; + keymap[GLFW_KEY_KP_EQUAL] = KEY_NULL; + keymap[GLFW_KEY_LEFT_SHIFT] = KEY_LSHIFT; + keymap[GLFW_KEY_LEFT_CONTROL] = KEY_LCTRL; + keymap[GLFW_KEY_LEFT_ALT] = KEY_LALT; + keymap[GLFW_KEY_LEFT_SUPER] = KEY_NULL; + keymap[GLFW_KEY_RIGHT_SHIFT] = KEY_RSHIFT; + keymap[GLFW_KEY_RIGHT_CONTROL] = KEY_RCTRL; + keymap[GLFW_KEY_RIGHT_ALT] = KEY_RALT; + keymap[GLFW_KEY_RIGHT_SUPER] = KEY_NULL; + keymap[GLFW_KEY_MENU] = KEY_NULL; +} + +static void KeyUp(int key) { EventHandler(KEYUP, &key); } +static void KeyDown(int key) { EventHandler(KEYDOWN, &key); } + +static void +keypress(GLFWwindow *window, int key, int scancode, int action, int mods) +{ + if(key >= 0 && key <= GLFW_KEY_LAST){ + if(action == GLFW_RELEASE) KeyUp(keymap[key]); + else if(action == GLFW_PRESS) KeyDown(keymap[key]); + else if(action == GLFW_REPEAT) KeyDown(keymap[key]); + } +} + +static void +charinput(GLFWwindow *window, unsigned int c) +{ + EventHandler(CHARINPUT, (void*)(uintptr)c); +} + +static void +resize(GLFWwindow *window, int w, int h) +{ + rw::Rect r; + r.x = 0; + r.y = 0; + r.w = w; + r.h = h; + EventHandler(RESIZE, &r); +} + +static void +mousemove(GLFWwindow *window, double x, double y) +{ + sk::MouseState ms; + ms.posx = x; + ms.posy = y; + EventHandler(MOUSEMOVE, &ms); +} + +static void +mousebtn(GLFWwindow *window, int button, int action, int mods) +{ + static int buttons = 0; + sk::MouseState ms; + + switch(button){ + case GLFW_MOUSE_BUTTON_LEFT: + if(action == GLFW_PRESS) + buttons |= 1; + else + buttons &= ~1; + break; + case GLFW_MOUSE_BUTTON_MIDDLE: + if(action == GLFW_PRESS) + buttons |= 2; + else + buttons &= ~2; + break; + case GLFW_MOUSE_BUTTON_RIGHT: + if(action == GLFW_PRESS) + buttons |= 4; + else + buttons &= ~4; + break; + } + + ms.buttons = buttons; + EventHandler(MOUSEBTN, &ms); +} + +int +main(int argc, char *argv[]) +{ + args.argc = argc; + args.argv = argv; + + if(EventHandler(INITIALIZE, nil) == EVENTERROR) + return 0; + + engineOpenParams.width = sk::globals.width; + engineOpenParams.height = sk::globals.height; + engineOpenParams.windowtitle = sk::globals.windowtitle; + engineOpenParams.window = &window; + + if(EventHandler(RWINITIALIZE, nil) == EVENTERROR) + return 0; + + initkeymap(); + glfwSetKeyCallback(window, keypress); + glfwSetCharCallback(window, charinput); + glfwSetWindowSizeCallback(window, resize); + glfwSetCursorPosCallback(window, mousemove); + glfwSetMouseButtonCallback(window, mousebtn); + + float lastTime = glfwGetTime()*1000; + while(!sk::globals.quit && !glfwWindowShouldClose(window)){ + float currTime = glfwGetTime()*1000; + float timeDelta = (currTime - lastTime)*0.001f; + glfwPollEvents(); + + EventHandler(IDLE, &timeDelta); + + lastTime = currTime; + } + + EventHandler(RWTERMINATE, nil); + + return 0; +} + +namespace sk { + +void +SetMousePosition(int x, int y) +{ + glfwSetCursorPos(*engineOpenParams.window, (double)x, (double)y); +} + +} + +#endif +#endif diff --git a/vendor/librw/skeleton/imgui/ImGuizmo.cpp b/vendor/librw/skeleton/imgui/ImGuizmo.cpp new file mode 100644 index 0000000..3a22a26 --- /dev/null +++ b/vendor/librw/skeleton/imgui/ImGuizmo.cpp @@ -0,0 +1,2724 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "imgui.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" +#include "ImGuizmo.h" +#if !defined(_WIN32) +#define _malloca(x) alloca(x) +#define _freea(x) +#else +#include +#endif + +// includes patches for multiview from +// https://github.com/CedricGuillemet/ImGuizmo/issues/15 + +namespace ImGuizmo +{ + static const float ZPI = 3.14159265358979323846f; + static const float RAD2DEG = (180.f / ZPI); + static const float DEG2RAD = (ZPI / 180.f); + const float screenRotateSize = 0.06f; + + static OPERATION operator&(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + static bool operator!=(OPERATION lhs, int rhs) + { + return static_cast(lhs) != rhs; + } + + static bool operator==(OPERATION lhs, int rhs) + { + return static_cast(lhs) == rhs; + } + + static bool Intersects(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) != 0; + } + + // True if lhs contains rhs + static bool Contains(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) == rhs; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // utility and math + + void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) + { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + } + + void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) + { + float temp, temp2, temp3, temp4; + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + m16[0] = temp / temp2; + m16[1] = 0.0; + m16[2] = 0.0; + m16[3] = 0.0; + m16[4] = 0.0; + m16[5] = temp / temp3; + m16[6] = 0.0; + m16[7] = 0.0; + m16[8] = (right + left) / temp2; + m16[9] = (top + bottom) / temp3; + m16[10] = (-zfar - znear) / temp4; + m16[11] = -1.0f; + m16[12] = 0.0; + m16[13] = 0.0; + m16[14] = (-temp * zfar) / temp4; + m16[15] = 0.0; + } + + void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) + { + float ymax, xmax; + ymax = znear * tanf(fovyInDegrees * DEG2RAD); + xmax = ymax * aspectRatio; + Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); + } + + void Cross(const float* a, const float* b, float* r) + { + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; + } + + float Dot(const float* a, const float* b) + { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + void Normalize(const float* a, float* r) + { + float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); + r[0] = a[0] * il; + r[1] = a[1] * il; + r[2] = a[2] * il; + } + + void LookAt(const float* eye, const float* at, const float* up, float* m16) + { + float X[3], Y[3], Z[3], tmp[3]; + + tmp[0] = eye[0] - at[0]; + tmp[1] = eye[1] - at[1]; + tmp[2] = eye[2] - at[2]; + Normalize(tmp, Z); + Normalize(up, Y); + Cross(Y, Z, tmp); + Normalize(tmp, X); + Cross(Z, X, tmp); + Normalize(tmp, Y); + + m16[0] = X[0]; + m16[1] = Y[0]; + m16[2] = Z[0]; + m16[3] = 0.0f; + m16[4] = X[1]; + m16[5] = Y[1]; + m16[6] = Z[1]; + m16[7] = 0.0f; + m16[8] = X[2]; + m16[9] = Y[2]; + m16[10] = Z[2]; + m16[11] = 0.0f; + m16[12] = -Dot(X, eye); + m16[13] = -Dot(Y, eye); + m16[14] = -Dot(Z, eye); + m16[15] = 1.0f; + } + + template T Clamp(T x, T y, T z) { return ((x < y) ? y : ((x > z) ? z : x)); } + template T max(T x, T y) { return (x > y) ? x : y; } + template T min(T x, T y) { return (x < y) ? x : y; } + template bool IsWithin(T x, T y, T z) { return (x >= y) && (x <= z); } + + struct matrix_t; + struct vec_t + { + public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) + { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; } + + vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; } + vec_t& operator *= (float v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + vec_t operator * (float f) const; + vec_t operator - () const; + vec_t operator - (const vec_t& v) const; + vec_t operator + (const vec_t& v) const; + vec_t operator * (const vec_t& v) const; + + const vec_t& operator + () const { return (*this); } + float Length() const { return sqrtf(x * x + y * y + z * z); }; + float LengthSq() const { return (x * x + y * y + z * z); }; + vec_t Normalize() { (*this) *= (1.f / Length()); return (*this); } + vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); } + vec_t Abs() const; + + void Cross(const vec_t& v) + { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + + void Cross(const vec_t& v1, const vec_t& v2) + { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + + float Dot(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + + float Dot3(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t& s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); } + + float& operator [] (size_t index) { return ((float*)&x)[index]; } + const float& operator [] (size_t index) const { return ((float*)&x)[index]; } + bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)); } + }; + + vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; } + vec_t makeVect(ImVec2 v) { vec_t res; res.x = v.x; res.y = v.y; res.z = 0.f; res.w = 0.f; return res; } + vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w * f); } + vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); } + vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); } + vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); } + vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); } + vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); } + + vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; } + vec_t Cross(const vec_t& v1, const vec_t& v2) + { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; + } + + float Dot(const vec_t& v1, const vec_t& v2) + { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); + } + + vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) + { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; + } + + struct matrix_t + { + public: + + union + { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + matrix_t(const matrix_t& other) { memcpy(&m16[0], &other.m16[0], sizeof(float) * 16); } + matrix_t() {} + + operator float* () { return m16; } + operator const float* () const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) + { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator *= (const matrix_t& mat) + { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator * (const matrix_t& mat) const + { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t& matrix) + { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t& m1, const matrix_t& m2) + { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const + { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t& srcMatrix, bool affine = false); + void SetToIdentity() + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() + { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t& axis, float angle); + + void OrthoNormalize() + { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } + }; + + void vec_t::Transform(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::Transform(const vec_t& s, const matrix_t& matrix) + { + *this = s; + Transform(matrix); + } + + void vec_t::TransformPoint(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::TransformVector(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) + { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } + else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; + } + + void matrix_t::RotationAxis(const vec_t& axis, float angle) + { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + enum MOVETYPE + { + MT_NONE, + MT_MOVE_X, + MT_MOVE_Y, + MT_MOVE_Z, + MT_MOVE_YZ, + MT_MOVE_ZX, + MT_MOVE_XY, + MT_MOVE_SCREEN, + MT_ROTATE_X, + MT_ROTATE_Y, + MT_ROTATE_Z, + MT_ROTATE_SCREEN, + MT_SCALE_X, + MT_SCALE_Y, + MT_SCALE_Z, + MT_SCALE_XYZ + }; + + static bool IsTranslateType(int type) + { + return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; + } + + static bool IsRotateType(int type) + { + return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; + } + + static bool IsScaleType(int type) + { + return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; + } + + // Matches MT_MOVE_AB order + static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; + + struct Context + { + Context() : mbUsing(false), mbEnable(true), mbUsingBounds(false) + { + } + + ImDrawList* mDrawList; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + ImVec2 mScreenSquareCenter; + ImVec2 mScreenSquareMin; + ImVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbEnable; + + bool mReversed; // reversed projection matrix + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + vec_t mTranslationLastDelta; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + //vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + vec_t mScaleLast; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + // bounds stretching + vec_t mBoundsPivot; + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + int mBoundsAxis[2]; + bool mbUsingBounds; + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + float mDisplayRatio = 1.f; + + bool mIsOrthographic = false; + + int mActualID = -1; + int mEditingID = -1; + OPERATION mOperation = OPERATION(-1); + + bool mAllowAxisFlip = true; + float mGizmoSizeClipSpace = 0.1f; + }; + + static Context gContext; + + static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; + static const ImU32 directionColor[3] = { IM_COL32(0xAA, 0, 0, 0xFF), IM_COL32(0, 0xAA, 0, 0xFF), IM_COL32(0, 0, 0xAA, 0XFF) }; + + // Alpha: 100%: FF, 87%: DE, 70%: B3, 54%: 8A, 50%: 80, 38%: 61, 12%: 1F + static const ImU32 planeColor[3] = { IM_COL32(0xAA, 0, 0, 0x61), IM_COL32(0, 0xAA, 0, 0x61), IM_COL32(0, 0, 0xAA, 0x61) }; + static const ImU32 selectionColor = IM_COL32(0xFF, 0x80, 0x10, 0x8A); + static const ImU32 inactiveColor = IM_COL32(0x99, 0x99, 0x99, 0x99); + static const ImU32 translationLineColor = IM_COL32(0xAA, 0xAA, 0xAA, 0xAA); + static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", + "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", + "X : %5.3f Y : %5.3f Z : %5.3f" }; + static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; + static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; + static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 }; + static const float quadMin = 0.5f; + static const float quadMax = 0.8f; + static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; + static const int halfCircleSegmentCount = 64; + static const float snapTension = 0.5f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); + static int GetRotateType(OPERATION op); + static int GetScaleType(OPERATION op); + + static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= size.x; + trans.y *= size.y; + trans.x += position.x; + trans.y += position.y; + return ImVec2(trans.x, trans.y); + } + + static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + ImGuiIO& io = ImGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; + const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; + + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; + const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); + + rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); + } + + static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end) + { + vec_t startOfSegment = start; + startOfSegment.TransformPoint(gContext.mMVP); + if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + startOfSegment *= 1.f / startOfSegment.w; + } + + vec_t endOfSegment = end; + endOfSegment.TransformPoint(gContext.mMVP); + if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + endOfSegment *= 1.f / endOfSegment.w; + } + + vec_t clipSpaceAxis = endOfSegment - startOfSegment; + clipSpaceAxis.y /= gContext.mDisplayRatio; + float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); + return segmentLengthInClipSpace; + } + + static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) + { + vec_t pts[] = { ptO, ptA, ptB }; + for (unsigned int i = 0; i < 3; i++) + { + pts[i].TransformPoint(gContext.mMVP); + if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction + { + pts[i] *= 1.f / pts[i].w; + } + } + vec_t segA = pts[1] - pts[0]; + vec_t segB = pts[2] - pts[0]; + segA.y /= gContext.mDisplayRatio; + segB.y /= gContext.mDisplayRatio; + vec_t segAOrtho = makeVect(-segA.y, segA.x); + segAOrtho.Normalize(); + float dt = segAOrtho.Dot3(segB); + float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); + return surface; + } + + inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) + { + vec_t c = point - vertPos1; + vec_t V; + + V.Normalize(vertPos2 - vertPos1); + float d = (vertPos2 - vertPos1).Length(); + float t = V.Dot3(c); + + if (t < 0.f) + { + return vertPos1; + } + + if (t > d) + { + return vertPos2; + } + + return vertPos1 + V * t; + } + + static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) + { + float numer = plan.Dot3(rOrigin) - plan.w; + float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + { + return -1.0f; + } + + return -(numer / denom); + } + + static float DistanceToPlane(const vec_t& point, const vec_t& plan) + { + return plan.Dot3(point) + plan.w; + } + + static bool IsInContextRect(ImVec2 p) + { + return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); + } + + void SetRect(float x, float y, float width, float height) + { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + gContext.mDisplayRatio = width / height; + } + + void SetOrthographic(bool isOrthographic) + { + gContext.mIsOrthographic = isOrthographic; + } + + void SetDrawlist(ImDrawList* drawlist) + { + gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList(); + } + + void SetImGuiContext(ImGuiContext* ctx) + { + ImGui::SetCurrentContext(ctx); + } + + void BeginFrame() + { + const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + +#ifdef IMGUI_HAS_VIEWPORT + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos); +#else + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::SetNextWindowPos(ImVec2(0, 0)); +#endif + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + ImGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = ImGui::GetWindowDrawList(); + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + + bool IsUsing() + { + return gContext.mbUsing || gContext.mbUsingBounds; + } + + bool IsOver() + { + return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || + (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || + (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); + } + + bool IsOver(OPERATION op) + { + if(IsUsing()) + { + return true; + } + if(Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) + { + return true; + } + return false; + } + + void Enable(bool enable) + { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } + } + + static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) + { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + + if (mode == LOCAL) + { + gContext.mModel = *(matrix_t*)matrix; + gContext.mModel.OrthoNormalize(); + } + else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + + // projection reverse + vec_t nearPos, farPos; + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + + gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w); + + // compute scale from the size of camera right vector projected on screen at the matrix position + vec_t pointRight = viewInverse.v.right; + pointRight.TransformPoint(gContext.mViewProjection); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / (pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w); + + vec_t rightViewInverse = viewInverse.v.right; + rightViewInverse.TransformVector(gContext.mModelInverse); + float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + + ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); + } + + static void ComputeColors(ImU32* colors, int type, OPERATION operation) + { + if (gContext.mbEnable) + { + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : directionColor[i]; + colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : planeColor[i]; + colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : directionColor[i]; + } + break; + case SCALE: + colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : directionColor[i]; + } + break; + // note: this internal function is only called with three possible values for operation + default: + break; + } + } + else + { + for (int i = 0; i < 7; i++) + { + colors[i] = inactiveColor; + } + } + } + + static void ComputeTripodAxisAndVisibility(int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit) + { + dirAxis = directionUnary[axisIndex]; + dirPlaneX = directionUnary[(axisIndex + 1) % 3]; + dirPlaneY = directionUnary[(axisIndex + 2) % 3]; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; + belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; + + dirAxis *= gContext.mAxisFactor[axisIndex]; + dirPlaneX *= gContext.mAxisFactor[(axisIndex + 1) % 3]; + dirPlaneY *= gContext.mAxisFactor[(axisIndex + 2) % 3]; + } + else + { + // new method + float lenDir = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis); + float lenDirMinus = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirAxis); + + float lenDirPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneX); + float lenDirMinusPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneX); + + float lenDirPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneY); + float lenDirMinusPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneY); + + // For readability + bool & allowFlip = gContext.mAllowAxisFlip; + float mulAxis = (allowFlip && lenDir < lenDirMinus&& fabsf(lenDir - lenDirMinus) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX&& fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY&& fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; + dirAxis *= mulAxis; + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + // for axis + float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor); + + float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); + belowPlaneLimit = (paraSurf > 0.0025f); + belowAxisLimit = (axisLengthInClipSpace > 0.02f); + + // and store values + gContext.mAxisFactor[axisIndex] = mulAxis; + gContext.mAxisFactor[(axisIndex + 1) % 3] = mulAxisX; + gContext.mAxisFactor[(axisIndex + 2) % 3] = mulAxisY; + gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit; + gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit; + } + } + + static void ComputeSnap(float* value, float snap) + { + if (snap <= FLT_EPSILON) + { + return; + } + + float modulo = fmodf(*value, snap); + float moduloRatio = fabsf(modulo) / snap; + if (moduloRatio < snapTension) + { + *value -= modulo; + } + else if (moduloRatio > (1.f - snapTension)) + { + *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); + } + } + static void ComputeSnap(vec_t& value, const float* snap) + { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } + } + + static float ComputeAngleOnPlan() + { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; + } + + static void DrawRotationGizmo(OPERATION op, int type) + { + if(!Intersects(op, ROTATE)) + { + return; + } + ImDrawList* drawList = gContext.mDrawList; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t cameraToModelNormalized; + if (gContext.mIsOrthographic) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); + cameraToModelNormalized = viewInverse.v.dir; + } + else + { + cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + } + + cameraToModelNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + + bool hasRSC = Intersects(op, ROTATE_SCREEN); + int circleMul = hasRSC ? 1 : 2; + for (int axis = 0; axis < 3; axis++) + { + if(!Intersects(op, static_cast(ROTATE_Z >> axis))) + { + continue; + } + ImVec2* circlePos = (ImVec2*) alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1)); + + float angleStart = atan2f(cameraToModelNormalized[(4 - axis) % 3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; + + for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) + { + float ng = angleStart + circleMul * ZPI * ((float)i / (float)halfCircleSegmentCount); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + + float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); + if (radiusAxis > gContext.mRadiusSquareCenter) + { + gContext.mRadiusSquareCenter = radiusAxis; + } + + drawList->AddPolyline(circlePos, circleMul * halfCircleSegmentCount + 1, colors[3 - axis], false, 2); + } + if(hasRSC) + { + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, 3.f); + } + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(type)) + { + ImVec2 circlePos[halfCircleSegmentCount + 1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount; i++) + { + float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount, IM_COL32(0xFF, 0x80, 0x10, 0x80)); + drawList->AddPolyline(circlePos, halfCircleSegmentCount, IM_COL32(0xFF, 0x80, 0x10, 0xFF), true, 2); + + ImVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } + } + + static void DrawHatchedAxis(const vec_t& axis) + { + for (int j = 1; j < 10; j++) + { + ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, IM_COL32(0, 0, 0, 0x80), 6.f); + } + } + + static void DrawScaleGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if(!Intersects(op, SCALE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (unsigned int i = 0; i < 3; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)) ; + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); + } + + if(!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + drawList->AddCircleFilled(worldDirSSpace, 6.f, colors[i + 1]); + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis * scaleDisplay[i]); + } + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } + } + + + static void DrawTranslationGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + if (!drawList) + { + return; + } + + if(!Intersects(op, TRANSLATE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (unsigned int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + // draw axis + if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) + { + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + + // Arrow head begin + ImVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= 6.0f; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis); + } + } + + // draw plane + if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) + { + ImVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, directionColor[i], true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(type)) + { + ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_MOVE_X) * 3; + ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } + } + + static bool CanActivate() + { + if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) + { + return true; + } + return false; + } + + static void HandleAndDrawLocalBounds(const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) + { + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* drawList = gContext.mDrawList; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (unsigned int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); + if (dt >= bestDot) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if (dt >= 0.1f) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if (numAxes == 0) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + + else if (bestAxis != axes[0]) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if (axes[i] == bestAxis) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // corners + vec_t aabb[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + + for (int i = 0; i < 4; i++) + { + aabb[i][3] = aabb[i][bestAxis] = 0.f; + aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80); + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (int i = 0; i < 4; i++) + { + ImVec2 worldBound1 = worldToPos(aabb[i], boundsMVP); + ImVec2 worldBound2 = worldToPos(aabb[(i + 1) % 4], boundsMVP); + if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) + { + continue; + } + float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min(stepCount, 1000); + float stepLength = 1.f / (float)stepCount; + for (int j = 0; j < stepCount; j++) + { + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); + ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); + //drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); + drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); + } + vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4]) * 0.5f; + ImVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + + int type = MT_NONE; + vec_t gizmoHitProportion; + + if(Intersects(operation, TRANSLATE)) + { + type = GetMoveType(operation, &gizmoHitProportion); + } + if(Intersects(operation, ROTATE) && type == MT_NONE) + { + type = GetRotateType(operation); + } + if(Intersects(operation, SCALE) && type == MT_NONE) + { + type = GetScaleType(operation); + } + + if (type != MT_NONE) + { + overBigAnchor = false; + overSmallAnchor = false; + } + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); + + drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis]; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis , thirdAxis }; + gContext.mBoundsAxis[0] = indices[i % 2]; + gContext.mBoundsAxis[1] = -1; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + } + + if (gContext.mbUsingBounds && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + matrix_t scale; + scale.SetToIdentity(); + + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int i = 0; i < 2; i++) + { + int axisIndex1 = gContext.mBoundsAxis[i]; + if (axisIndex1 == -1) + { + continue; + } + + float ratioAxis = 1.f; + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + + float dtAxis = axisDir.Dot(referenceVector); + float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1]; + if (dtAxis > FLT_EPSILON) + { + ratioAxis = axisDir.Dot(deltaVector) / dtAxis; + } + + if (snapValues) + { + float length = boundSize * ratioAxis; + ComputeSnap(&length, snapValues[axisIndex1]); + if (boundSize > FLT_EPSILON) + { + ratioAxis = length / boundSize; + } + } + scale.component[axisIndex1] *= ratioAxis; + } + + // transform matrix + matrix_t preScale, postScale; + preScale.Translation(-gContext.mBoundsLocalPivot); + postScale.Translation(gContext.mBoundsLocalPivot); + matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix; + *matrix = res; + + // info text + char tmps[512]; + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + ImFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z:%.2f" + , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length() + , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length() + , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length() + ); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), IM_COL32_BLACK, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), IM_COL32_WHITE, tmps); + } + + if (!io.MouseDown[0]) { + gContext.mbUsingBounds = false; + gContext.mEditingID = -1; + } + if (gContext.mbUsingBounds) + { + break; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + static int GetScaleType(OPERATION op) + { + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, SCALE)) + { + type = MT_SCALE_XYZ; + } + + // compute + for (unsigned int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; + const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; + const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); + + vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + + if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size + { + type = MT_SCALE_X + i; + } + } + return type; + } + + static int GetRotateType(OPERATION op) + { + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 1.0f) && dist < (gContext.mRadiusSquareCenter + 1.0f)) + { + type = MT_ROTATE_SCREEN; + } + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + vec_t modelViewPos; + modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); + + for (unsigned int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(ROTATE_X << i))) + { + continue; + } + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t intersectViewPos; + intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); + + if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON) + { + continue; + } + + const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; + vec_t idealPosOnCircle = Normalized(localPos); + idealPosOnCircle.TransformVector(gContext.mModelInverse); + const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * gContext.mScreenFactor, gContext.mMVP); + + //gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE); + const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; + + const float distance = makeVect(distanceOnScreen).Length(); + if (distance < 8.f) // pixel size + { + type = MT_ROTATE_X + i; + } + } + + return type; + } + + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) + { + if(!Intersects(op, TRANSLATE)) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, TRANSLATE)) + { + type = MT_MOVE_SCREEN; + } + + const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY)); + + // compute + for (unsigned int i = 0; i < 3 && type == MT_NONE; i++) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + + vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size + { + type = MT_MOVE_X + i; + } + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) + { + type = MT_MOVE_YZ + i; + } + + if (gizmoHitProportion) + { + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + } + return type; + } + + static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, TRANSLATE) || type != MT_NONE) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; + bool modified = false; + + // move + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) + { + ImGui::CaptureMouseFromApp(); + const float len = fabsf(IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan)); // near plan + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } + else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + + } + + if (delta != gContext.mTranslationLastDelta) + { + modified = true; + } + gContext.mTranslationLastDelta = delta; + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + { + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + } + + matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + } + + type = gContext.mCurrentOperation; + } + else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = GetMoveType(op, &gizmoHitProportion); + if (type != MT_NONE) + { + ImGui::CaptureMouseFromApp(); + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + -gContext.mCameraDir }; + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + for (unsigned int i = 0; i < 3; i++) + { + vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + return modified; + } + + static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, SCALE) || type != MT_NONE) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool modified = false; + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = GetScaleType(op); + if (type != MT_NONE) + { + ImGui::CaptureMouseFromApp(); + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) + { + ImGui::CaptureMouseFromApp(); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModel.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } + else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3; i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + if (gContext.mScaleLast != gContext.mScale) + { + modified = true; + } + gContext.mScaleLast = gContext.mScale; + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModel; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; + + vec_t originalScaleDivider; + originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; + originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; + originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; + + deltaScale = deltaScale * originalScaleDivider; + + deltaMatrixScale.Scale(deltaScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mScale.Set(1.f, 1.f, 1.f); + } + + type = gContext.mCurrentOperation; + } + return modified; + } + + static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, ROTATE) || type != MT_NONE) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + bool modified = false; + + if (!gContext.mbUsing) + { + type = GetRotateType(op); + + if (type != MT_NONE) + { + ImGui::CaptureMouseFromApp(); + } + + if (type == MT_ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); + } + else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) + { + ImGui::CaptureMouseFromApp(); + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) + { + modified = true; + } + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModel; + } + else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mEditingID = -1; + } + type = gContext.mCurrentOperation; + } + return modified; + } + + void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) + { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; + } + + void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) + { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3; i++) + { + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + } + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + { + validScale[i] = 0.001f; + } + else + { + validScale[i] = scale[i]; + } + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); + } + + void SetID(int id) + { + gContext.mActualID = id; + } + + void AllowAxisFlip(bool value) + { + gContext.mAllowAxisFlip = value; + } + + bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap) + { + ComputeContext(view, projection, matrix, mode); + + // set delta to identity + if (deltaMatrix) + { + ((matrix_t*)deltaMatrix)->SetToIdentity(); + } + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f) + { + return false; + } + + // -- + int type = MT_NONE; + bool manipulated = false; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + manipulated = HandleTranslation(matrix, deltaMatrix, operation, type, snap) || + HandleScale(matrix, deltaMatrix, operation, type, snap) || + HandleRotation(matrix, deltaMatrix, operation, type, snap); + } + } + + if (localBounds && !gContext.mbUsing) + { + HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); + } + + gContext.mOperation = operation; + if (!gContext.mbUsingBounds) + { + DrawRotationGizmo(operation, type); + DrawTranslationGizmo(operation, type); + DrawScaleGizmo(operation, type); + } + return manipulated; + } + + void SetGizmoSizeClipSpace(float value) + { + gContext.mGizmoSizeClipSpace = value; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////// + void ComputeFrustumPlanes(vec_t* frustum, const float* clip) + { + frustum[0].x = clip[3] - clip[0]; + frustum[0].y = clip[7] - clip[4]; + frustum[0].z = clip[11] - clip[8]; + frustum[0].w = clip[15] - clip[12]; + + frustum[1].x = clip[3] + clip[0]; + frustum[1].y = clip[7] + clip[4]; + frustum[1].z = clip[11] + clip[8]; + frustum[1].w = clip[15] + clip[12]; + + frustum[2].x = clip[3] + clip[1]; + frustum[2].y = clip[7] + clip[5]; + frustum[2].z = clip[11] + clip[9]; + frustum[2].w = clip[15] + clip[13]; + + frustum[3].x = clip[3] - clip[1]; + frustum[3].y = clip[7] - clip[5]; + frustum[3].z = clip[11] - clip[9]; + frustum[3].w = clip[15] - clip[13]; + + frustum[4].x = clip[3] - clip[2]; + frustum[4].y = clip[7] - clip[6]; + frustum[4].z = clip[11] - clip[10]; + frustum[4].w = clip[15] - clip[14]; + + frustum[5].x = clip[3] + clip[2]; + frustum[5].y = clip[7] + clip[6]; + frustum[5].z = clip[11] + clip[10]; + frustum[5].w = clip[15] + clip[14]; + + for (int i = 0; i < 6; i++) + { + frustum[i].Normalize(); + } + } + + void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + struct CubeFace + { + float z; + ImVec2 faceCoordsScreen[4]; + ImU32 color; + }; + CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); + + if (!faces) + { + return; + } + + vec_t frustum[6]; + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + ComputeFrustumPlanes(frustum, viewProjection.m16); + + int cubeFaceCount = 0; + for (int cube = 0; cube < matrixCount; cube++) + { + const float* matrix = &matrices[cube * 16]; + + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + /* + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + { + continue; + } + */ + vec_t centerPosition, centerPositionVP; + centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); + centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); + + bool inFrustum = true; + for (int iFrustum = 0; iFrustum < 6; iFrustum++) + { + float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); + if (dist < 0.f) + { + inFrustum = false; + break; + } + } + + if (!inFrustum) + { + continue; + } + CubeFace& cubeFace = faces[cubeFaceCount]; + + // 3D->2D + //ImVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + } + cubeFace.color = directionColor[normalIndex] | IM_COL32(0x80, 0x80, 0x80, 0); + + cubeFace.z = centerPositionVP.z / centerPositionVP.w; + cubeFaceCount++; + } + } + qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { + CubeFace* a = (CubeFace*)_a; + CubeFace* b = (CubeFace*)_b; + if (a->z < b->z) + { + return 1; + } + return -1; + }); + // draw face with lighter color + for (int iFace = 0; iFace < cubeFaceCount; iFace++) + { + const CubeFace& cubeFace = faces[iFace]; + gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); + } + + _freea(faces); + } + + void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) + { + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + vec_t frustum[6]; + ComputeFrustumPlanes(frustum, viewProjection.m16); + matrix_t res = *(matrix_t*)matrix * viewProjection; + + for (float f = -gridSize; f <= gridSize; f += 1.f) + { + for (int dir = 0; dir < 2; dir++) + { + vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); + vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); + bool visible = true; + for (int i = 0; i < 6; i++) + { + float dA = DistanceToPlane(ptA, frustum[i]); + float dB = DistanceToPlane(ptB, frustum[i]); + if (dA < 0.f && dB < 0.f) + { + visible = false; + break; + } + if (dA > 0.f && dB > 0.f) + { + continue; + } + if (dA < 0.f) + { + float len = fabsf(dA - dB); + float t = fabsf(dA) / len; + ptA.Lerp(ptB, t); + } + if (dB < 0.f) + { + float len = fabsf(dB - dA); + float t = fabsf(dB) / len; + ptB.Lerp(ptA, t); + } + } + if (visible) + { + ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF); + col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col; + col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF): col; + + float thickness = 1.f; + thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; + thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; + + gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); + } + } + } + } + + void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + static bool isDraging = false; + static bool isClicking = false; + static bool isInside = false; + static vec_t interpolationUp; + static vec_t interpolationDir; + static int interpolationFrames = 0; + const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); + + matrix_t svgView, svgProjection; + svgView = gContext.mViewMat; + svgProjection = gContext.mProjectionMat; + + ImGuiIO& io = ImGui::GetIO(); + gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; + + // view/projection matrices + const float distance = 3.f; + matrix_t cubeProjection, cubeView; + float fov = acosf(distance / (sqrtf(distance * distance + 3.f))) * RAD2DEG; + Perspective(fov / sqrtf(2.f), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16); + + vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); + vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); + vec_t eye = dir * distance; + vec_t zero = makeVect(0.f, 0.f); + LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); + + // set context + gContext.mViewMat = cubeView; + gContext.mProjectionMat = cubeProjection; + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); + + const matrix_t res = cubeView * cubeProjection; + + // panels + static const ImVec2 panelPosition[9] = { ImVec2(0.75f,0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f), + ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f), + ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) }; + + static const ImVec2 panelSize[9] = { ImVec2(0.25f,0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f), + ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f), + ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) }; + + // tag faces + bool boxes[27]{}; + for (int iPass = 0; iPass < 2; iPass++) + { + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + const vec_t indexVectorX = directionUnary[perpXIndex] * invert; + const vec_t indexVectorY = directionUnary[perpYIndex] * invert; + const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; + + // plan local space + const vec_t n = directionUnary[normalIndex] * invert; + vec_t viewSpaceNormal = n; + vec_t viewSpacePoint = n * 0.5f; + viewSpaceNormal.TransformVector(cubeView); + viewSpaceNormal.Normalize(); + viewSpacePoint.TransformPoint(cubeView); + const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); + + // back face culling + if (viewSpaceFacePlan.w > 0.f) + { + continue; + } + + const vec_t facePlan = BuildPlan(n * 0.5f, n); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); + + float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; + float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; + + // panels + const vec_t dx = directionUnary[perpXIndex]; + const vec_t dy = directionUnary[perpYIndex]; + const vec_t origin = directionUnary[normalIndex] - dx - dy; + for (int iPanel = 0; iPanel < 9; iPanel++) + { + vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); + const ImVec2 p = panelPosition[iPanel] * 2.f; + const ImVec2 s = panelSize[iPanel] * 2.f; + ImVec2 faceCoordsScreen[4]; + vec_t panelPos[4] = { dx * p.x + dy * p.y, + dx * p.x + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * p.y }; + + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); + } + + const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; + bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x&& localy > panelCorners[0].y && localy < panelCorners[1].y; + int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); + assert(boxCoordInt < 27); + boxes[boxCoordInt] |= insidePanel && (!isDraging); + + // draw face with lighter color + if (iPass) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor[normalIndex] | IM_COL32(0x80, 0x80, 0x80, 0x80)) | (isInside ? IM_COL32(0x08, 0x08, 0x08, 0) : 0)); + if (boxes[boxCoordInt]) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, IM_COL32(0xF0, 0xA0, 0x60, 0x80)); + + if (!io.MouseDown[0] && !isDraging && isClicking) + { + // apply new view direction + int cx = boxCoordInt / 9; + int cy = (boxCoordInt - cx * 9) / 3; + int cz = boxCoordInt % 3; + interpolationDir = makeVect(1.f - cx, 1.f - cy, 1.f - cz); + interpolationDir.Normalize(); + + if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) + { + vec_t right = viewInverse.v.right; + if (fabsf(right.x) > fabsf(right.z)) + { + right.z = 0.f; + } + else + { + right.x = 0.f; + } + right.Normalize(); + interpolationUp = Cross(interpolationDir, right); + interpolationUp.Normalize(); + } + else + { + interpolationUp = referenceUp; + } + interpolationFrames = 40; + isClicking = false; + } + if (io.MouseDown[0] && !isDraging) + { + isClicking = true; + } + } + } + } + } + } + if (interpolationFrames) + { + interpolationFrames--; + vec_t newDir = viewInverse.v.dir; + newDir.Lerp(interpolationDir, 0.2f); + newDir.Normalize(); + + vec_t newUp = viewInverse.v.up; + newUp.Lerp(interpolationUp, 0.3f); + newUp.Normalize(); + newUp = interpolationUp; + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &newUp.x, view); + } + isInside = ImRect(position, position + size).Contains(io.MousePos); + + // drag view + if (!isDraging && io.MouseDown[0] && isInside && (fabsf(io.MouseDelta.x) > 0.f || fabsf(io.MouseDelta.y) > 0.f)) + { + isDraging = true; + isClicking = false; + } + else if (isDraging && !io.MouseDown[0]) + { + isDraging = false; + } + + if (isDraging) + { + matrix_t rx, ry, roll; + + rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); + ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); + + roll = rx * ry; + + vec_t newDir = viewInverse.v.dir; + newDir.TransformVector(roll); + newDir.Normalize(); + + // clamp + vec_t planDir = Cross(viewInverse.v.right, referenceUp); + planDir.y = 0.f; + planDir.Normalize(); + float dt = Dot(planDir, newDir); + if (dt < 0.0f) + { + newDir += planDir * dt; + newDir.Normalize(); + } + + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); + } + + // restore view/projection because it was used to compute ray + ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); + } +}; diff --git a/vendor/librw/skeleton/imgui/ImGuizmo.h b/vendor/librw/skeleton/imgui/ImGuizmo.h new file mode 100644 index 0000000..06428b9 --- /dev/null +++ b/vendor/librw/skeleton/imgui/ImGuizmo.h @@ -0,0 +1,213 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.61 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2019/11/03 View gizmo +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#ifdef USE_IMGUI_API +#include "imconfig.h" +#endif +#ifndef IMGUI_API +#define IMGUI_API +#endif + +namespace ImGuizmo +{ + // call inside your own window and before Manipulate() in order to draw gizmo to that window. + // Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()). + IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr); + + // call BeginFrame right after ImGui_XXXX_NewFrame(); + IMGUI_API void BeginFrame(); + + // this is necessary because when imguizmo is compiled into a dll, and imgui into another + // globals are not shared between them. + // More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam + // expose method to set imgui context + IMGUI_API void SetImGuiContext(ImGuiContext* ctx); + + // return true if mouse cursor is over any gizmo control (axis, plan or screen component) + IMGUI_API bool IsOver(); + + // return true if mouse IsOver or if the gizmo is in moving state + IMGUI_API bool IsUsing(); + + // enable/disable the gizmo. Stay in the state until next call to Enable. + // gizmo is rendered with gray half transparent color when disabled + IMGUI_API void Enable(bool enable); + + // helper functions for manualy editing translation/rotation/scale with an input float + // translation, rotation and scale float points to 3 floats each + // Angles are in degrees (more suitable for human editing) + // example: + // float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + // ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + // ImGui::InputFloat3("Tr", matrixTranslation, 3); + // ImGui::InputFloat3("Rt", matrixRotation, 3); + // ImGui::InputFloat3("Sc", matrixScale, 3); + // ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); + // + // These functions have some numerical stability issues for now. Use with caution. + IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); + IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); + + IMGUI_API void SetRect(float x, float y, float width, float height); + // default is false + IMGUI_API void SetOrthographic(bool isOrthographic); + + // Render a cube with face color corresponding to face normal. Usefull for debug/tests + IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); + IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); + + // call it when you want a gizmo + // Needs view and projection matrices. + // matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional + // translation is applied in world space + enum OPERATION + { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z + }; + + inline OPERATION operator|(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + enum MODE + { + LOCAL, + WORLD + }; + + IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL); + // + // Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en + // It seems to be a defensive patent in the US. I don't think it will bring troubles using it as + // other software are using the same mechanics. But just in case, you are now warned! + // + IMGUI_API void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + IMGUI_API void SetID(int id); + + // return true if the cursor is over the operation's gizmo + IMGUI_API bool IsOver(OPERATION op); + IMGUI_API void SetGizmoSizeClipSpace(float value); + + // Allow axis to flip + // When true (default), the guizmo axis flip for better visibility + // When false, they always stay along the positive world/local axis + IMGUI_API void AllowAxisFlip(bool value); +} diff --git a/vendor/librw/skeleton/imgui/LICENSE_imgui.txt b/vendor/librw/skeleton/imgui/LICENSE_imgui.txt new file mode 100644 index 0000000..780533d --- /dev/null +++ b/vendor/librw/skeleton/imgui/LICENSE_imgui.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2021 Omar Cornut + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/librw/skeleton/imgui/LICENSE_imguizmo.txt b/vendor/librw/skeleton/imgui/LICENSE_imguizmo.txt new file mode 100644 index 0000000..dcbee45 --- /dev/null +++ b/vendor/librw/skeleton/imgui/LICENSE_imguizmo.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Cedric Guillemet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/librw/skeleton/imgui/imconfig.h b/vendor/librw/skeleton/imgui/imconfig.h new file mode 100644 index 0000000..ce60ddf --- /dev/null +++ b/vendor/librw/skeleton/imgui/imconfig.h @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// COMPILE-TIME OPTIONS FOR DEAR IMGUI +// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. +// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. +//----------------------------------------------------------------------------- +// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) +// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. +//----------------------------------------------------------------------------- +// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp +// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. +// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. +// Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. +//----------------------------------------------------------------------------- + +#pragma once + +//---- Define assertion handler. Defaults to calling assert(). +// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) +//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts + +//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows +// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. +// DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() +// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. +//#define IMGUI_API __declspec( dllexport ) +//#define IMGUI_API __declspec( dllimport ) + +//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. +//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//---- Disable all of Dear ImGui or don't implement standard windows. +// It is very strongly recommended to NOT disable the demo windows during development. Please read comments in imgui_demo.cpp. +//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. +//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. Not recommended. +//#define IMGUI_DISABLE_METRICS_WINDOW // Disable metrics/debugger window: ShowMetricsWindow() will be empty. + +//---- Don't implement some functions to reduce linkage requirements. +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. (imm32.lib/.a) +//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). +//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). +//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) +//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. +//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) +//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. +//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). + +//---- Include imgui_user.h at the end of imgui.h as a convenience +//#define IMGUI_INCLUDE_IMGUI_USER_H + +//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) +//#define IMGUI_USE_BGRA_PACKED_COLOR + +//---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) +//#define IMGUI_USE_WCHAR32 + +//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version +// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. +//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" +//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION + +//---- Use stb_printf's faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) +// Requires 'stb_sprintf.h' to be available in the include path. Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by STB sprintf. +// #define IMGUI_USE_STB_SPRINTF + +//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) +// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). +// On Windows you may use vcpkg with 'vcpkg install freetype' + 'vcpkg integrate install'. +//#define IMGUI_ENABLE_FREETYPE + +//---- Use stb_truetype to build and rasterize the font atlas (default) +// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. +//#define IMGUI_ENABLE_STB_TRUETYPE + +//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. +// This will be inlined as part of ImVec2 and ImVec4 class declarations. +/* +#define IM_VEC2_CLASS_EXTRA \ + ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ + operator MyVec2() const { return MyVec2(x,y); } + +#define IM_VEC4_CLASS_EXTRA \ + ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ + operator MyVec4() const { return MyVec4(x,y,z,w); } +*/ + +//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. +// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). +// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. +// Read about ImGuiBackendFlags_RendererHasVtxOffset for details. +//#define ImDrawIdx unsigned int + +//---- Override ImDrawCallback signature (will need to modify renderer backends accordingly) +//struct ImDrawList; +//struct ImDrawCmd; +//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); +//#define ImDrawCallback MyImDrawCallback + +//---- Debug Tools: Macro to break in Debugger +// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) +//#define IM_DEBUG_BREAK IM_ASSERT(0) +//#define IM_DEBUG_BREAK __debugbreak() + +//---- Debug Tools: Have the Item Picker break in the ItemAdd() function instead of ItemHoverable(), +// (which comes earlier in the code, will catch a few extra items, allow picking items other than Hovered one.) +// This adds a small runtime cost which is why it is not enabled by default. +//#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX + +//---- Debug Tools: Enable slower asserts +//#define IMGUI_DEBUG_PARANOID + +//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. +/* +namespace ImGui +{ + void MyFunction(const char* name, const MyMatrix44& v); +} +*/ diff --git a/vendor/librw/skeleton/imgui/imgui.cpp b/vendor/librw/skeleton/imgui/imgui.cpp new file mode 100644 index 0000000..d08f819 --- /dev/null +++ b/vendor/librw/skeleton/imgui/imgui.cpp @@ -0,0 +1,11589 @@ +// dear imgui, v1.83 +// (main code and documentation) + +// Help: +// - Read FAQ at http://dearimgui.org/faq +// - Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase. +// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. +// Read imgui.cpp for details, links and comments. + +// Resources: +// - FAQ http://dearimgui.org/faq +// - Homepage & latest https://github.com/ocornut/imgui +// - Releases & changelog https://github.com/ocornut/imgui/releases +// - Gallery https://github.com/ocornut/imgui/issues/3793 (please post your screenshots/video there!) +// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Issues & support https://github.com/ocornut/imgui/issues +// - Discussions https://github.com/ocornut/imgui/discussions + +// Developed by Omar Cornut and every direct or indirect contributors to the GitHub. +// See LICENSE.txt for copyright and licensing details (standard MIT License). +// This library is free but needs your support to sustain development and maintenance. +// Businesses: you can support continued development via invoiced technical support, maintenance and sponsoring contracts. Please reach out to "contact AT dearimgui.com". +// Individuals: you can support continued development via donations. See docs/README or web page. + +// It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library. +// Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without +// modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't +// come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you +// to a better solution or official support for them. + +/* + +Index of this file: + +DOCUMENTATION + +- MISSION STATEMENT +- END-USER GUIDE +- PROGRAMMER GUIDE + - READ FIRST + - HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI + - GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE + - HOW A SIMPLE APPLICATION MAY LOOK LIKE + - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE + - USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS +- API BREAKING CHANGES (read me when you update!) +- FREQUENTLY ASKED QUESTIONS (FAQ) + - Read all answers online: https://www.dearimgui.org/faq, or in docs/FAQ.md (with a Markdown viewer) + +CODE +(search for "[SECTION]" in the code to find them) + +// [SECTION] INCLUDES +// [SECTION] FORWARD DECLARATIONS +// [SECTION] CONTEXT AND MEMORY ALLOCATORS +// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) +// [SECTION] MISC HELPERS/UTILITIES (Geometry functions) +// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions) +// [SECTION] MISC HELPERS/UTILITIES (File functions) +// [SECTION] MISC HELPERS/UTILITIES (ImText* functions) +// [SECTION] MISC HELPERS/UTILITIES (Color functions) +// [SECTION] ImGuiStorage +// [SECTION] ImGuiTextFilter +// [SECTION] ImGuiTextBuffer +// [SECTION] ImGuiListClipper +// [SECTION] STYLING +// [SECTION] RENDER HELPERS +// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +// [SECTION] ERROR CHECKING +// [SECTION] LAYOUT +// [SECTION] SCROLLING +// [SECTION] TOOLTIPS +// [SECTION] POPUPS +// [SECTION] KEYBOARD/GAMEPAD NAVIGATION +// [SECTION] DRAG AND DROP +// [SECTION] LOGGING/CAPTURING +// [SECTION] SETTINGS +// [SECTION] VIEWPORTS +// [SECTION] PLATFORM DEPENDENT HELPERS +// [SECTION] METRICS/DEBUGGER WINDOW + +*/ + +//----------------------------------------------------------------------------- +// DOCUMENTATION +//----------------------------------------------------------------------------- + +/* + + MISSION STATEMENT + ================= + + - Easy to use to create code-driven and data-driven tools. + - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools. + - Easy to hack and improve. + - Minimize setup and maintenance. + - Minimize state storage on user side. + - Portable, minimize dependencies, run on target (consoles, phones, etc.). + - Efficient runtime and memory consumption. + + Designed for developers and content-creators, not the typical end-user! Some of the current weaknesses includes: + + - Doesn't look fancy, doesn't animate. + - Limited layout features, intricate layouts are typically crafted in code. + + + END-USER GUIDE + ============== + + - Double-click on title bar to collapse window. + - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin(). + - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents). + - Click and drag on any empty space to move window. + - TAB/SHIFT+TAB to cycle through keyboard editable fields. + - CTRL+Click on a slider or drag box to input value as text. + - Use mouse wheel to scroll. + - Text editor: + - Hold SHIFT or use mouse to select text. + - CTRL+Left/Right to word jump. + - CTRL+Shift+Left/Right to select words. + - CTRL+A our Double-Click to select all. + - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/ + - CTRL+Z,CTRL+Y to undo/redo. + - ESCAPE to revert text to its original value. + - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!) + - Controls are automatically adjusted for OSX to match standard OSX text editing operations. + - General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard. + - General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See suggested mappings in imgui.h ImGuiNavInput_ + download PNG/PSD at http://dearimgui.org/controls_sheets + + + PROGRAMMER GUIDE + ================ + + READ FIRST + ---------- + - Remember to check the wonderful Wiki (https://github.com/ocornut/imgui/wiki) + - Your code creates the UI, if your code doesn't run the UI is gone! The UI can be highly dynamic, there are no construction or + destruction steps, less superfluous data retention on your side, less state duplication, less state synchronization, fewer bugs. + - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. + - The library is designed to be built from sources. Avoid pre-compiled binaries and packaged versions. See imconfig.h to configure your build. + - Dear ImGui is an implementation of the IMGUI paradigm (immediate-mode graphical user interface, a term coined by Casey Muratori). + You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861 & more links in Wiki. + - Dear ImGui is a "single pass" rasterizing implementation of the IMGUI paradigm, aimed at ease of use and high-performances. + For every application frame, your UI code will be called only once. This is in contrast to e.g. Unity's implementation of an IMGUI, + where the UI code is called multiple times ("multiple passes") from a single entry point. There are pros and cons to both approaches. + - Our origin is on the top-left. In axis aligned bounding boxes, Min = top-left, Max = bottom-right. + - This codebase is also optimized to yield decent performances with typical "Debug" builds settings. + - Please make sure you have asserts enabled (IM_ASSERT redirects to assert() by default, but can be redirected). + If you get an assert, read the messages and comments around the assert. + - C++: this is a very C-ish codebase: we don't rely on C++11, we don't include any C++ headers, and ImGui:: is a namespace. + - C++: ImVec2/ImVec4 do not expose math operators by default, because it is expected that you use your own math types. + See FAQ "How can I use my own math types instead of ImVec2/ImVec4?" for details about setting up imconfig.h for that. + However, imgui_internal.h can optionally export math operators for ImVec2/ImVec4, which we use in this codebase. + - C++: pay attention that ImVector<> manipulates plain-old-data and does not honor construction/destruction (avoid using it in your code!). + + + HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI + ---------------------------------------------- + - Overwrite all the sources files except for imconfig.h (if you have modified your copy of imconfig.h) + - Or maintain your own branch where you have imconfig.h modified as a top-most commit which you can regularly rebase over "master". + - You can also use '#define IMGUI_USER_CONFIG "my_config_file.h" to redirect configuration to your own file. + - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. + If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed + from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will + likely be a comment about it. Please report any issue to the GitHub page! + - To find out usage of old API, you can add '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in your configuration file. + - Try to keep your copy of Dear ImGui reasonably up to date. + + + GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE + --------------------------------------------------------------- + - Run and study the examples and demo in imgui_demo.cpp to get acquainted with the library. + - In the majority of cases you should be able to use unmodified backends files available in the backends/ folder. + - Add the Dear ImGui source files + selected backend source files to your projects or using your preferred build system. + It is recommended you build and statically link the .cpp files as part of your project and NOT as a shared library (DLL). + - You can later customize the imconfig.h file to tweak some compile-time behavior, such as integrating Dear ImGui types with your own maths types. + - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. + - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide. + Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" + phases of your own application. All rendering information is stored into command-lists that you will retrieve after calling ImGui::Render(). + - Refer to the backends and demo applications in the examples/ folder for instruction on how to setup your code. + - If you are running over a standard OS with a common graphics API, you should be able to use unmodified imgui_impl_*** files from the examples/ folder. + + + HOW A SIMPLE APPLICATION MAY LOOK LIKE + -------------------------------------- + EXHIBIT 1: USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder). + The sub-folders in examples/ contain examples applications following this structure. + + // Application init: create a dear imgui context, setup some options, load fonts + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls. + // TODO: Fill optional fields of the io structure later. + // TODO: Load TTF/OTF fonts if you don't want to use the default font. + + // Initialize helper Platform and Renderer backends (here we are using imgui_impl_win32.cpp and imgui_impl_dx11.cpp) + ImGui_ImplWin32_Init(hwnd); + ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); + + // Application main loop + while (true) + { + // Feed inputs to dear imgui, start new frame + ImGui_ImplDX11_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + // Any application code here + ImGui::Text("Hello, world!"); + + // Render dear imgui into screen + ImGui::Render(); + ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + g_pSwapChain->Present(1, 0); + } + + // Shutdown + ImGui_ImplDX11_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + + EXHIBIT 2: IMPLEMENTING CUSTOM BACKEND / CUSTOM ENGINE + + // Application init: create a dear imgui context, setup some options, load fonts + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls. + // TODO: Fill optional fields of the io structure later. + // TODO: Load TTF/OTF fonts if you don't want to use the default font. + + // Build and load the texture atlas into a texture + // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer) + int width, height; + unsigned char* pixels = NULL; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + // At this point you've got the texture data and you need to upload that to your graphic system: + // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'. + // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about ImTextureID. + MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32) + io.Fonts->SetTexID((void*)texture); + + // Application main loop + while (true) + { + // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows message handlers, etc. + // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo Platform Backends) + io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds) + io.DisplaySize.x = 1920.0f; // set the current display width + io.DisplaySize.y = 1280.0f; // set the current display height here + io.MousePos = my_mouse_pos; // set the mouse position + io.MouseDown[0] = my_mouse_buttons[0]; // set the mouse button states + io.MouseDown[1] = my_mouse_buttons[1]; + + // Call NewFrame(), after this point you can use ImGui::* functions anytime + // (So you want to try calling NewFrame() as early as you can in your main loop to be able to use Dear ImGui everywhere) + ImGui::NewFrame(); + + // Most of your application code here + ImGui::Text("Hello, world!"); + MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); + MyGameRender(); // may use any Dear ImGui functions as well! + + // Render dear imgui, swap buffers + // (You want to try calling EndFrame/Render as late as you can, to be able to use Dear ImGui in your own game rendering code) + ImGui::EndFrame(); + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + MyImGuiRenderFunction(draw_data); + SwapBuffers(); + } + + // Shutdown + ImGui::DestroyContext(); + + To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application, + you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! + Please read the FAQ and example applications for details about this! + + + HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE + --------------------------------------------- + The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function. + + void void MyImGuiRenderFunction(ImDrawData* draw_data) + { + // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled + // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize + // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize + // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by Dear ImGui + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by Dear ImGui + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // The texture for the draw call is specified by pcmd->GetTexID(). + // The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization. + MyEngineBindTexture((MyTexture*)pcmd->GetTexID()); + + // We are using scissoring to clip some objects. All low-level graphics API should support it. + // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches + // (some elements visible outside their bounds) but you can fix that once everything else works! + // - Clipping coordinates are provided in imgui coordinates space: + // - For a given viewport, draw_data->DisplayPos == viewport->Pos and draw_data->DisplaySize == viewport->Size + // - In a single viewport application, draw_data->DisplayPos == (0,0) and draw_data->DisplaySize == io.DisplaySize, but always use GetMainViewport()->Pos/Size instead of hardcoding those values. + // - In the interest of supporting multi-viewport applications (see 'docking' branch on github), + // always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space. + // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min) + ImVec2 pos = draw_data->DisplayPos; + MyEngineScissor((int)(pcmd->ClipRect.x - pos.x), (int)(pcmd->ClipRect.y - pos.y), (int)(pcmd->ClipRect.z - pos.x), (int)(pcmd->ClipRect.w - pos.y)); + + // Render 'pcmd->ElemCount/3' indexed triangles. + // By default the indices ImDrawIdx are 16-bit, you can change them to 32-bit in imconfig.h if your engine doesn't support 16-bit indices. + MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + } + + + USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS + ------------------------------------------ + - The gamepad/keyboard navigation is fairly functional and keeps being improved. + - Gamepad support is particularly useful to use Dear ImGui on a console system (e.g. PS4, Switch, XB1) without a mouse! + - You can ask questions and report issues at https://github.com/ocornut/imgui/issues/787 + - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable. + - Keyboard: + - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable. + NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays. + - When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag + will be set. For more advanced uses, you may want to read from: + - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. + - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used). + - or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions. + Please reach out if you think the game vs navigation input sharing could be improved. + - Gamepad: + - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable. + - Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + fill the io.NavInputs[] fields before calling NewFrame(). + Note that io.NavInputs[] is cleared by EndFrame(). + - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values: + 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks. + - We use a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone. + Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.). + - You can download PNG/PSD files depicting the gamepad controls for common controllers at: http://dearimgui.org/controls_sheets + - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo + to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved. + - Mouse: + - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback. + - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + uSynergy.c (on your console/tablet/phone app) to share your PC mouse/keyboard. + - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiConfigFlags_NavEnableSetMousePos flag. + Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements. + When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved. + When that happens your backend NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the backends in examples/ do that. + (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, imgui will misbehave as it will see your mouse moving back and forth!) + (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want + to set a boolean to ignore your other external mouse positions until the external source is moved again.) + + + API BREAKING CHANGES + ==================== + + Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix. + Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code. + When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. + You can read releases logs https://github.com/ocornut/imgui/releases for more details. + + - 2021/05/19 (1.83) - backends: obsoleted direct access to ImDrawCmd::TextureId in favor of calling ImDrawCmd::GetTexID(). + - if you are using official backends from the source tree: you have nothing to do. + - if you have copied old backend code or using your own: change access to draw_cmd->TextureId to draw_cmd->GetTexID(). + - 2021/03/12 (1.82) - upgraded ImDrawList::AddRect(), AddRectFilled(), PathRect() to use ImDrawFlags instead of ImDrawCornersFlags. + - ImDrawCornerFlags_TopLeft -> use ImDrawFlags_RoundCornersTopLeft + - ImDrawCornerFlags_BotRight -> use ImDrawFlags_RoundCornersBottomRight + - ImDrawCornerFlags_None -> use ImDrawFlags_RoundCornersNone etc. + flags now sanely defaults to 0 instead of 0x0F, consistent with all other flags in the API. + breaking: the default with rounding > 0.0f is now "round all corners" vs old implicit "round no corners": + - rounding == 0.0f + flags == 0 --> meant no rounding --> unchanged (common use) + - rounding > 0.0f + flags != 0 --> meant rounding --> unchanged (common use) + - rounding == 0.0f + flags != 0 --> meant no rounding --> unchanged (unlikely use) + - rounding > 0.0f + flags == 0 --> meant no rounding --> BREAKING (unlikely use): will now round all corners --> use ImDrawFlags_RoundCornersNone or rounding == 0.0f. + this ONLY matters for hard coded use of 0 + rounding > 0.0f. Use of named ImDrawFlags_RoundCornersNone (new) or ImDrawCornerFlags_None (old) are ok. + the old ImDrawCornersFlags used awkward default values of ~0 or 0xF (4 lower bits set) to signify "round all corners" and we sometimes encouraged using them as shortcuts. + legacy path still support use of hard coded ~0 or any value from 0x1 or 0xF. They will behave the same with legacy paths enabled (will assert otherwise). + - 2021/03/11 (1.82) - removed redirecting functions/enums names that were marked obsolete in 1.66 (September 2018): + - ImGui::SetScrollHere() -> use ImGui::SetScrollHereY() + - 2021/03/11 (1.82) - clarified that ImDrawList::PathArcTo(), ImDrawList::PathArcToFast() won't render with radius < 0.0f. Previously it sorts of accidentally worked but would generally lead to counter-clockwise paths and have an effect on anti-aliasing. + - 2021/03/10 (1.82) - upgraded ImDrawList::AddPolyline() and PathStroke() "bool closed" parameter to "ImDrawFlags flags". The matching ImDrawFlags_Closed value is guaranteed to always stay == 1 in the future. + - 2021/02/22 (1.82) - win32+mingw: Re-enabled IME functions by default even under MinGW. In July 2016, issue #738 had me incorrectly disable those default functions for MinGW. MinGW users should: either link with -limm32, either set their imconfig file with '#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS'. + - 2021/02/17 (1.82) - renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) to style.CircleTessellationMaxError (new default = 0.30f) as the meaning of the value changed. + - 2021/02/03 (1.81) - renamed ListBoxHeader(const char* label, ImVec2 size) to BeginListBox(). Kept inline redirection function (will obsolete). + - removed ListBoxHeader(const char* label, int items_count, int height_in_items = -1) in favor of specifying size. Kept inline redirection function (will obsolete). + - renamed ListBoxFooter() to EndListBox(). Kept inline redirection function (will obsolete). + - 2021/01/26 (1.81) - removed ImGuiFreeType::BuildFontAtlas(). Kept inline redirection function. Prefer using '#define IMGUI_ENABLE_FREETYPE', but there's a runtime selection path available too. The shared extra flags parameters (very rarely used) are now stored in ImFontAtlas::FontBuilderFlags. + - renamed ImFontConfig::RasterizerFlags (used by FreeType) to ImFontConfig::FontBuilderFlags. + - renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. + - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.63 (August 2018): + - ImGui::IsItemDeactivatedAfterChange() -> use ImGui::IsItemDeactivatedAfterEdit(). + - ImGuiCol_ModalWindowDarkening -> use ImGuiCol_ModalWindowDimBg + - ImGuiInputTextCallback -> use ImGuiTextEditCallback + - ImGuiInputTextCallbackData -> use ImGuiTextEditCallbackData + - 2020/12/21 (1.80) - renamed ImDrawList::AddBezierCurve() to AddBezierCubic(), and PathBezierCurveTo() to PathBezierCubicCurveTo(). Kept inline redirection function (will obsolete). + - 2020/12/04 (1.80) - added imgui_tables.cpp file! Manually constructed project files will need the new file added! + - 2020/11/18 (1.80) - renamed undocumented/internals ImGuiColumnsFlags_* to ImGuiOldColumnFlags_* in prevision of incoming Tables API. + - 2020/11/03 (1.80) - renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature will apply to other data structures + - 2020/10/14 (1.80) - backends: moved all backends files (imgui_impl_XXXX.cpp, imgui_impl_XXXX.h) from examples/ to backends/. + - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.60 (April 2018): + - io.RenderDrawListsFn pointer -> use ImGui::GetDrawData() value and call the render function of your backend + - ImGui::IsAnyWindowFocused() -> use ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow) + - ImGui::IsAnyWindowHovered() -> use ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) + - ImGuiStyleVar_Count_ -> use ImGuiStyleVar_COUNT + - ImGuiMouseCursor_Count_ -> use ImGuiMouseCursor_COUNT + - removed redirecting functions names that were marked obsolete in 1.61 (May 2018): + - InputFloat (... int decimal_precision ...) -> use InputFloat (... const char* format ...) with format = "%.Xf" where X is your value for decimal_precision. + - same for InputFloat2()/InputFloat3()/InputFloat4() variants taking a `int decimal_precision` parameter. + - 2020/10/05 (1.79) - removed ImGuiListClipper: Renamed constructor parameters which created an ambiguous alternative to using the ImGuiListClipper::Begin() function, with misleading edge cases (note: imgui_memory_editor <0.40 from imgui_club/ used this old clipper API. Update your copy if needed). + - 2020/09/25 (1.79) - renamed ImGuiSliderFlags_ClampOnInput to ImGuiSliderFlags_AlwaysClamp. Kept redirection enum (will obsolete sooner because previous name was added recently). + - 2020/09/25 (1.79) - renamed style.TabMinWidthForUnselectedCloseButton to style.TabMinWidthForCloseButton. + - 2020/09/21 (1.79) - renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), reverting the change from 1.77. For varieties of reason this is more self-explanatory. + - 2020/09/21 (1.79) - removed return value from OpenPopupOnItemClick() - returned true on mouse release on an item - because it is inconsistent with other popup APIs and makes others misleading. It's also and unnecessary: you can use IsWindowAppearing() after BeginPopup() for a similar result. + - 2020/09/17 (1.79) - removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied after scaling and not very meaningful/useful outside of being needed by the default ProggyClean font. If you scaled this value after calling AddFontDefault(), this is now done automatically. It was also getting in the way of better font scaling, so let's get rid of it now! + - 2020/08/17 (1.78) - obsoleted use of the trailing 'float power=1.0f' parameter for DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(), DragFloatRange2(), DragScalar(), DragScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(), SliderScalar(), SliderScalarN(), VSliderFloat() and VSliderScalar(). + replaced the 'float power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags). + worked out a backward-compatibility scheme so hopefully most C++ codebase should not be affected. in short, when calling those functions: + - if you omitted the 'power' parameter (likely!), you are not affected. + - if you set the 'power' parameter to 1.0f (same as previous default value): 1/ your compiler may warn on float>int conversion, 2/ everything else will work. 3/ you can replace the 1.0f value with 0 to fix the warning, and be technically correct. + - if you set the 'power' parameter to >1.0f (to enable non-linear editing): 1/ your compiler may warn on float>int conversion, 2/ code will assert at runtime, 3/ in case asserts are disabled, the code will not crash and enable the _Logarithmic flag. 4/ you can replace the >1.0f value with ImGuiSliderFlags_Logarithmic to fix the warning/assert and get a _similar_ effect as previous uses of power >1.0f. + see https://github.com/ocornut/imgui/issues/3361 for all details. + kept inline redirection functions (will obsolete) apart for: DragFloatRange2(), VSliderFloat(), VSliderScalar(). For those three the 'float power=1.0f' version was removed directly as they were most unlikely ever used. + for shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. + - obsoleted use of v_min > v_max in DragInt, DragFloat, DragScalar to lock edits (introduced in 1.73, was not demoed nor documented very), will be replaced by a more generic ReadOnly feature. You may use the ImGuiSliderFlags_ReadOnly internal flag in the meantime. + - 2020/06/23 (1.77) - removed BeginPopupContextWindow(const char*, int mouse_button, bool also_over_items) in favor of BeginPopupContextWindow(const char*, ImGuiPopupFlags flags) with ImGuiPopupFlags_NoOverItems. + - 2020/06/15 (1.77) - renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will obsolete). [NOTE: THIS WAS REVERTED IN 1.79] + - 2020/06/15 (1.77) - removed CalcItemRectClosestPoint() entry point which was made obsolete and asserting in December 2017. + - 2020/04/23 (1.77) - removed unnecessary ID (first arg) of ImFontAtlas::AddCustomRectRegular(). + - 2020/01/22 (1.75) - ImDrawList::AddCircle()/AddCircleFilled() functions don't accept negative radius any more. + - 2019/12/17 (1.75) - [undid this change in 1.76] made Columns() limited to 64 columns by asserting above that limit. While the current code technically supports it, future code may not so we're putting the restriction ahead. + - 2019/12/13 (1.75) - [imgui_internal.h] changed ImRect() default constructor initializes all fields to 0.0f instead of (FLT_MAX,FLT_MAX,-FLT_MAX,-FLT_MAX). If you used ImRect::Add() to create bounding boxes by adding multiple points into it, you may need to fix your initial value. + - 2019/12/08 (1.75) - removed redirecting functions/enums that were marked obsolete in 1.53 (December 2017): + - ShowTestWindow() -> use ShowDemoWindow() + - IsRootWindowFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootWindow) + - IsRootWindowOrAnyChildFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) + - SetNextWindowContentWidth(w) -> use SetNextWindowContentSize(ImVec2(w, 0.0f) + - GetItemsLineHeightWithSpacing() -> use GetFrameHeightWithSpacing() + - ImGuiCol_ChildWindowBg -> use ImGuiCol_ChildBg + - ImGuiStyleVar_ChildWindowRounding -> use ImGuiStyleVar_ChildRounding + - ImGuiTreeNodeFlags_AllowOverlapMode -> use ImGuiTreeNodeFlags_AllowItemOverlap + - IMGUI_DISABLE_TEST_WINDOWS -> use IMGUI_DISABLE_DEMO_WINDOWS + - 2019/12/08 (1.75) - obsoleted calling ImDrawList::PrimReserve() with a negative count (which was vaguely documented and rarely if ever used). Instead, we added an explicit PrimUnreserve() API. + - 2019/12/06 (1.75) - removed implicit default parameter to IsMouseDragging(int button = 0) to be consistent with other mouse functions (none of the other functions have it). + - 2019/11/21 (1.74) - ImFontAtlas::AddCustomRectRegular() now requires an ID larger than 0x110000 (instead of 0x10000) to conform with supporting Unicode planes 1-16 in a future update. ID below 0x110000 will now assert. + - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS to IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS for consistency. + - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_MATH_FUNCTIONS to IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS for consistency. + - 2019/10/22 (1.74) - removed redirecting functions/enums that were marked obsolete in 1.52 (October 2017): + - Begin() [old 5 args version] -> use Begin() [3 args], use SetNextWindowSize() SetNextWindowBgAlpha() if needed + - IsRootWindowOrAnyChildHovered() -> use IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) + - AlignFirstTextHeightToWidgets() -> use AlignTextToFramePadding() + - SetNextWindowPosCenter() -> use SetNextWindowPos() with a pivot of (0.5f, 0.5f) + - ImFont::Glyph -> use ImFontGlyph + - 2019/10/14 (1.74) - inputs: Fixed a miscalculation in the keyboard/mouse "typematic" repeat delay/rate calculation, used by keys and e.g. repeating mouse buttons as well as the GetKeyPressedAmount() function. + if you were using a non-default value for io.KeyRepeatRate (previous default was 0.250), you can add +io.KeyRepeatDelay to it to compensate for the fix. + The function was triggering on: 0.0 and (delay+rate*N) where (N>=1). Fixed formula responds to (N>=0). Effectively it made io.KeyRepeatRate behave like it was set to (io.KeyRepeatRate + io.KeyRepeatDelay). + If you never altered io.KeyRepeatRate nor used GetKeyPressedAmount() this won't affect you. + - 2019/07/15 (1.72) - removed TreeAdvanceToLabelPos() which is rarely used and only does SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()). Kept redirection function (will obsolete). + - 2019/07/12 (1.72) - renamed ImFontAtlas::CustomRect to ImFontAtlasCustomRect. Kept redirection typedef (will obsolete). + - 2019/06/14 (1.72) - removed redirecting functions/enums names that were marked obsolete in 1.51 (June 2017): ImGuiCol_Column*, ImGuiSetCond_*, IsItemHoveredRect(), IsPosHoveringAnyWindow(), IsMouseHoveringAnyWindow(), IsMouseHoveringWindow(), IMGUI_ONCE_UPON_A_FRAME. Grep this log for details and new names, or see how they were implemented until 1.71. + - 2019/06/07 (1.71) - rendering of child window outer decorations (bg color, border, scrollbars) is now performed as part of the parent window. If you have + overlapping child windows in a same parent, and relied on their relative z-order to be mapped to their submission order, this will affect your rendering. + This optimization is disabled if the parent window has no visual output, because it appears to be the most common situation leading to the creation of overlapping child windows. + Please reach out if you are affected. + - 2019/05/13 (1.71) - renamed SetNextTreeNodeOpen() to SetNextItemOpen(). Kept inline redirection function (will obsolete). + - 2019/05/11 (1.71) - changed io.AddInputCharacter(unsigned short c) signature to io.AddInputCharacter(unsigned int c). + - 2019/04/29 (1.70) - improved ImDrawList thick strokes (>1.0f) preserving correct thickness up to 90 degrees angles (e.g. rectangles). If you have custom rendering using thick lines, they will appear thicker now. + - 2019/04/29 (1.70) - removed GetContentRegionAvailWidth(), use GetContentRegionAvail().x instead. Kept inline redirection function (will obsolete). + - 2019/03/04 (1.69) - renamed GetOverlayDrawList() to GetForegroundDrawList(). Kept redirection function (will obsolete). + - 2019/02/26 (1.69) - renamed ImGuiColorEditFlags_RGB/ImGuiColorEditFlags_HSV/ImGuiColorEditFlags_HEX to ImGuiColorEditFlags_DisplayRGB/ImGuiColorEditFlags_DisplayHSV/ImGuiColorEditFlags_DisplayHex. Kept redirection enums (will obsolete). + - 2019/02/14 (1.68) - made it illegal/assert when io.DisplayTime == 0.0f (with an exception for the first frame). If for some reason your time step calculation gives you a zero value, replace it with an arbitrarily small value! + - 2019/02/01 (1.68) - removed io.DisplayVisibleMin/DisplayVisibleMax (which were marked obsolete and removed from viewport/docking branch already). + - 2019/01/06 (1.67) - renamed io.InputCharacters[], marked internal as was always intended. Please don't access directly, and use AddInputCharacter() instead! + - 2019/01/06 (1.67) - renamed ImFontAtlas::GlyphRangesBuilder to ImFontGlyphRangesBuilder. Kept redirection typedef (will obsolete). + - 2018/12/20 (1.67) - made it illegal to call Begin("") with an empty string. This somehow half-worked before but had various undesirable side-effects. + - 2018/12/10 (1.67) - renamed io.ConfigResizeWindowsFromEdges to io.ConfigWindowsResizeFromEdges as we are doing a large pass on configuration flags. + - 2018/10/12 (1.66) - renamed misc/stl/imgui_stl.* to misc/cpp/imgui_stdlib.* in prevision for other C++ helper files. + - 2018/09/28 (1.66) - renamed SetScrollHere() to SetScrollHereY(). Kept redirection function (will obsolete). + - 2018/09/06 (1.65) - renamed stb_truetype.h to imstb_truetype.h, stb_textedit.h to imstb_textedit.h, and stb_rect_pack.h to imstb_rectpack.h. + If you were conveniently using the imgui copy of those STB headers in your project you will have to update your include paths. + - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427) + - 2018/08/31 (1.64) - added imgui_widgets.cpp file, extracted and moved widgets code out of imgui.cpp into imgui_widgets.cpp. Re-ordered some of the code remaining in imgui.cpp. + NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED. + Because of this, any local modifications to imgui.cpp will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions. + - 2018/08/22 (1.63) - renamed IsItemDeactivatedAfterChange() to IsItemDeactivatedAfterEdit() for consistency with new IsItemEdited() API. Kept redirection function (will obsolete soonish as IsItemDeactivatedAfterChange() is very recent). + - 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete). + - 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of (ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly). + - 2018/08/01 (1.63) - removed per-window ImGuiWindowFlags_ResizeFromAnySide beta flag in favor of a global io.ConfigResizeWindowsFromEdges [update 1.67 renamed to ConfigWindowsResizeFromEdges] to enable the feature. + - 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65], io.OptMacOSXBehaviors to ConfigMacOSXBehaviors for consistency. + - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point imprecisions over time. + - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with other features. Kept redirection enum (will obsolete). + - 2018/06/08 (1.62) - examples: the imgui_impl_XXX files have been split to separate platform (Win32, GLFW, SDL2, etc.) from renderer (DX11, OpenGL, Vulkan, etc.). + old backends will still work as is, however prefer using the separated backends as they will be updated to support multi-viewports. + when adopting new backends follow the main.cpp code of your preferred examples/ folder to know which functions to call. + in particular, note that old backends called ImGui::NewFrame() at the end of their ImGui_ImplXXXX_NewFrame() function. + - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and discourage using the full set. + - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details. + - 2018/05/03 (1.61) - DragInt(): the default compile-time format string has been changed from "%.0f" to "%d", as we are not using integers internally any more. + If you used DragInt() with custom format strings, make sure you change them to use %d or an integer-compatible format. + To honor backward-compatibility, the DragInt() code will currently parse and modify format strings to replace %*f with %d, giving time to users to upgrade their code. + If you have IMGUI_DISABLE_OBSOLETE_FUNCTIONS enabled, the code will instead assert! You may run a reg-exp search on your codebase for e.g. "DragInt.*%f" to help you find them. + - 2018/04/28 (1.61) - obsoleted InputFloat() functions taking an optional "int decimal_precision" in favor of an equivalent and more flexible "const char* format", + consistent with other functions. Kept redirection functions (will obsolete). + - 2018/04/09 (1.61) - IM_DELETE() helper function added in 1.60 doesn't clear the input _pointer_ reference, more consistent with expectation and allows passing r-value. + - 2018/03/20 (1.60) - renamed io.WantMoveMouse to io.WantSetMousePos for consistency and ease of understanding (was added in 1.52, _not_ used by core and only honored by some backend ahead of merging the Nav branch). + - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors now. + - 2018/03/08 (1.60) - changed ImFont::DisplayOffset.y to default to 0 instead of +1. Fixed rounding of Ascent/Descent to match TrueType renderer. If you were adding or subtracting to ImFont::DisplayOffset check if your fonts are correctly aligned vertically. + - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums. + - 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is not really usable in many situations at the moment. + - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display. + - 2018/02/07 (1.60) - reorganized context handling to be more explicit, + - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END. + - removed Shutdown() function, as DestroyContext() serve this purpose. + - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwise CreateContext() will create its own font atlas instance. + - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts. + - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts. + - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your paths. + - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData. + - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it it's easy to replicate on your side. + - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete). + - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags + - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame. + - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set. + - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete). + - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete). + - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete). + - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete). + - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete). + - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed. + - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. + Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions. + - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency. + - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg. + - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding. + - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); + - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency. + - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it. + - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details. + removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting. + IsItemHoveredRect() --> IsItemHovered(ImGuiHoveredFlags_RectOnly) + IsMouseHoveringAnyWindow() --> IsWindowHovered(ImGuiHoveredFlags_AnyWindow) + IsMouseHoveringWindow() --> IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) [weird, old behavior] + - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! + - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). + - 2017/09/26 (1.52) - renamed ImFont::Glyph to ImFontGlyph. Kept redirection typedef (will obsolete). + - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete). + - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your backend if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". + - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)! + - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). + - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). + - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. + - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix. + - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame type. + - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely. + - 2017/08/13 (1.51) - renamed ImGuiCol_Column to ImGuiCol_Separator, ImGuiCol_ColumnHovered to ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive to ImGuiCol_SeparatorActive. Kept redirection enums (will obsolete). + - 2017/08/11 (1.51) - renamed ImGuiSetCond_Always to ImGuiCond_Always, ImGuiSetCond_Once to ImGuiCond_Once, ImGuiSetCond_FirstUseEver to ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing to ImGuiCond_Appearing. Kept redirection enums (will obsolete). + - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton(). + - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu. + - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the various new options. + - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0, 0))' + - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse + - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. + - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. + - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetID() and use it instead of passing string to BeginChild(). + - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. + - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc. + - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully, breakage should be minimal. + - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore. + If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you, otherwise if <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. + This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color: + ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) { float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); } + If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color. + - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext(). + - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. + - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen). + - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDrawList::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer. + - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref GitHub issue #337). + - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337) + - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). + - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert. + - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you. + - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis. + - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete. + - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position. + GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side. + GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a problem, but watch out! + - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize + - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project. + - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason + - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure. + you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text. + - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost. + this necessary change will break your rendering function! the fix should be very easy. sorry for that :( + - if you are using a vanilla copy of one of the imgui_impl_XXX.cpp provided in the example, you just need to update your copy and you can ignore the rest. + - the signature of the io.RenderDrawListsFn handler has changed! + old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) + new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data). + parameters: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount' + ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new. + ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' becomes 'TextureId'. + - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from the index buffer. + - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering! + - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade! + - 2015/07/10 (1.43) - changed SameLine() parameters from int to float. + - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). + - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount. + - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence + - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely used. Sorry! + - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). + - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete). + - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons. + - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if the popup is opened. + - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). + - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50. + - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API + - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive. + - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead. + - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50. + - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing + - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50. + - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing) + - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50. + - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once. + - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now. + - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior + - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing() + - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused) + - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions. + - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. + - 2015/01/11 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. + - old: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); [..Upload texture to GPU..]; + - new: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); [..Upload texture to GPU..]; io.Fonts->SetTexID(YourTexIdentifier); + you now have more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. It is now recommended that you sample the font texture with bilinear interpolation. + - 2015/01/11 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to call io.Fonts->SetTexID() + - 2015/01/11 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) + - 2015/01/11 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets + - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) + - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph) + - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility + - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() + - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) + - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) + - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() + - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn + - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) + - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite + - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes + + + FREQUENTLY ASKED QUESTIONS (FAQ) + ================================ + + Read all answers online: + https://www.dearimgui.org/faq or https://github.com/ocornut/imgui/blob/master/docs/FAQ.md (same url) + Read all answers locally (with a text editor or ideally a Markdown viewer): + docs/FAQ.md + Some answers are copied down here to facilitate searching in code. + + Q&A: Basics + =========== + + Q: Where is the documentation? + A: This library is poorly documented at the moment and expects the user to be acquainted with C/C++. + - Run the examples/ and explore them. + - See demo code in imgui_demo.cpp and particularly the ImGui::ShowDemoWindow() function. + - The demo covers most features of Dear ImGui, so you can read the code and see its output. + - See documentation and comments at the top of imgui.cpp + effectively imgui.h. + - Dozens of standalone example applications using e.g. OpenGL/DirectX are provided in the + examples/ folder to explain how to integrate Dear ImGui with your own engine/application. + - The Wiki (https://github.com/ocornut/imgui/wiki) has many resources and links. + - The Glossary (https://github.com/ocornut/imgui/wiki/Glossary) page also may be useful. + - Your programming IDE is your friend, find the type or function declaration to find comments + associated with it. + + Q: What is this library called? + Q: Which version should I get? + >> This library is called "Dear ImGui", please don't call it "ImGui" :) + >> See https://www.dearimgui.org/faq for details. + + Q&A: Integration + ================ + + Q: How to get started? + A: Read 'PROGRAMMER GUIDE' above. Read examples/README.txt. + + Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application? + A: You should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! + >> See https://www.dearimgui.org/faq for a fully detailed answer. You really want to read this. + + Q. How can I enable keyboard controls? + Q: How can I use this without a mouse, without a keyboard or without a screen? (gamepad, input share, remote display) + Q: I integrated Dear ImGui in my engine and little squares are showing instead of text... + Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around... + Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries... + >> See https://www.dearimgui.org/faq + + Q&A: Usage + ---------- + + Q: Why is my widget not reacting when I click on it? + Q: How can I have widgets with an empty label? + Q: How can I have multiple widgets with the same label? + Q: How can I display an image? What is ImTextureID, how does it works? + Q: How can I use my own math types instead of ImVec2/ImVec4? + Q: How can I interact with standard C++ types (such as std::string and std::vector)? + Q: How can I display custom shapes? (using low-level ImDrawList API) + >> See https://www.dearimgui.org/faq + + Q&A: Fonts, Text + ================ + + Q: How should I handle DPI in my application? + Q: How can I load a different font than the default? + Q: How can I easily use icons in my application? + Q: How can I load multiple fonts? + Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? + >> See https://www.dearimgui.org/faq and https://github.com/ocornut/imgui/edit/master/docs/FONTS.md + + Q&A: Concerns + ============= + + Q: Who uses Dear ImGui? + Q: Can you create elaborate/serious tools with Dear ImGui? + Q: Can you reskin the look of Dear ImGui? + Q: Why using C++ (as opposed to C)? + >> See https://www.dearimgui.org/faq + + Q&A: Community + ============== + + Q: How can I help? + A: - Businesses: please reach out to "contact AT dearimgui.com" if you work in a place using Dear ImGui! + We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts. + This is among the most useful thing you can do for Dear ImGui. With increased funding, we can hire more people working on this project. + - Individuals: you can support continued development via PayPal donations. See README. + - If you are experienced with Dear ImGui and C++, look at the GitHub issues, look at the Wiki, read docs/TODO.txt + and see how you want to help and can help! + - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. + You may post screenshot or links in the gallery threads. Visuals are ideal as they inspire other programmers. + But even without visuals, disclosing your use of dear imgui helps the library grow credibility, and help other teams and programmers with taking decisions. + - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on GitHub or privately). + +*/ + +//------------------------------------------------------------------------- +// [SECTION] INCLUDES +//------------------------------------------------------------------------- + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +// System includes +#include // toupper +#include // vsnprintf, sscanf, printf +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +// [Windows] OS specific includes (optional) +#if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) +#define IMGUI_DISABLE_WIN32_FUNCTIONS +#endif +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef __MINGW32__ +#include // _wfopen, OpenClipboard +#else +#include +#endif +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) // UWP doesn't have all Win32 functions +#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS +#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS +#endif +#endif + +// [Apple] OS specific includes +#if defined(__APPLE__) +#include +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types +#endif +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning: declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning: declaration requires a global destructor // similar to above, not sure what the exact difference is. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wformat-pedantic" // warning: format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic. +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type 'int' +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +// We disable -Wpragmas because GCC doesn't provide an has_warning equivalent and some forks/patches may not following the warning/version association. +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif + +// Debug options +#define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Display last moving direction matches when holding CTRL +#define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window +#define IMGUI_DEBUG_INI_SETTINGS 0 // Save additional comments in .ini file (particularly helps for Docking, but makes saving slower) + +// When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. +static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in +static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear + +// Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend) +static const float WINDOWS_HOVER_PADDING = 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow(). +static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time. +static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 2.00f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. + +//------------------------------------------------------------------------- +// [SECTION] FORWARD DECLARATIONS +//------------------------------------------------------------------------- + +static void SetCurrentWindow(ImGuiWindow* window); +static void FindHoveredWindow(); +static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags); +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); + +static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list); +static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window); + +// Settings +static void WindowSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*); +static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); +static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); +static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*); +static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); + +// Platform Dependents default implementation for IO functions +static const char* GetClipboardTextFn_DefaultImpl(void* user_data); +static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text); +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y); + +namespace ImGui +{ +// Navigation +static void NavUpdate(); +static void NavUpdateWindowing(); +static void NavUpdateWindowingOverlay(); +static void NavUpdateMoveResult(); +static void NavUpdateInitResult(); +static float NavUpdatePageUpPageDown(); +static inline void NavUpdateAnyRequestFlag(); +static void NavEndFrame(); +static bool NavScoreItem(ImGuiNavItemData* result, ImRect cand); +static void NavApplyItemToResult(ImGuiNavItemData* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel); +static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, ImGuiID id); +static ImVec2 NavCalcPreferredRefPos(); +static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); +static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); +static void NavRestoreLayer(ImGuiNavLayer layer); +static int FindWindowFocusIndex(ImGuiWindow* window); + +// Error Checking +static void ErrorCheckNewFrameSanityChecks(); +static void ErrorCheckEndFrameSanityChecks(); + +// Misc +static void UpdateSettings(); +static void UpdateMouseInputs(); +static void UpdateMouseWheel(); +static void UpdateTabFocus(); +static void UpdateDebugToolItemPicker(); +static bool UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect); +static void RenderWindowOuterBorders(ImGuiWindow* window); +static void RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size); +static void RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open); + +// Viewports +static void UpdateViewportsNewFrame(); + +} + +//----------------------------------------------------------------------------- +// [SECTION] CONTEXT AND MEMORY ALLOCATORS +//----------------------------------------------------------------------------- + +// DLL users: +// - Heaps and globals are not shared across DLL boundaries! +// - You will need to call SetCurrentContext() + SetAllocatorFunctions() for each static/DLL boundary you are calling from. +// - Same applies for hot-reloading mechanisms that are reliant on reloading DLL (note that many hot-reloading mechanisms work without DLL). +// - Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. +// - Confused? In a debugger: add GImGui to your watch window and notice how its value changes depending on your current location (which DLL boundary you are in). + +// Current context pointer. Implicitly used by all Dear ImGui functions. Always assumed to be != NULL. +// - ImGui::CreateContext() will automatically set this pointer if it is NULL. +// Change to a different context by calling ImGui::SetCurrentContext(). +// - Important: Dear ImGui functions are not thread-safe because of this pointer. +// If you want thread-safety to allow N threads to access N different contexts: +// - Change this variable to use thread local storage so each thread can refer to a different context, in your imconfig.h: +// struct ImGuiContext; +// extern thread_local ImGuiContext* MyImGuiTLS; +// #define GImGui MyImGuiTLS +// And then define MyImGuiTLS in one of your cpp files. Note that thread_local is a C++11 keyword, earlier C++ uses compiler-specific keyword. +// - Future development aims to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 +// - If you need a finite number of contexts, you may compile and use multiple instances of the ImGui code from a different namespace. +// - DLL users: read comments above. +#ifndef GImGui +ImGuiContext* GImGui = NULL; +#endif + +// Memory Allocator functions. Use SetAllocatorFunctions() to change them. +// - You probably don't want to modify that mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction. +// - DLL users: read comments above. +#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS +static void* MallocWrapper(size_t size, void* user_data) { IM_UNUSED(user_data); return malloc(size); } +static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_data); free(ptr); } +#else +static void* MallocWrapper(size_t size, void* user_data) { IM_UNUSED(user_data); IM_UNUSED(size); IM_ASSERT(0); return NULL; } +static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_UNUSED(ptr); IM_ASSERT(0); } +#endif +static ImGuiMemAllocFunc GImAllocatorAllocFunc = MallocWrapper; +static ImGuiMemFreeFunc GImAllocatorFreeFunc = FreeWrapper; +static void* GImAllocatorUserData = NULL; + +//----------------------------------------------------------------------------- +// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) +//----------------------------------------------------------------------------- + +ImGuiStyle::ImGuiStyle() +{ + Alpha = 1.0f; // Global alpha applies to everything in ImGui + WindowPadding = ImVec2(8,8); // Padding within a window + WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. + WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowMinSize = ImVec2(32,32); // Minimum window size + WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text + WindowMenuButtonPosition= ImGuiDir_Left; // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left. + ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows + ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. + PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows + PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. + FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) + FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). + FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. + ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines + ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + CellPadding = ImVec2(4,2); // Padding within a table cell + TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). + ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar + ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar + GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. + TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. + TabBorderSize = 0.0f; // Thickness of border around tabs. + TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appears on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. + ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. + ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. + SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. + DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. + DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. + AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering. + AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). + CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. + + // Default theme + ImGui::StyleColorsDark(this); +} + +// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. +// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. +void ImGuiStyle::ScaleAllSizes(float scale_factor) +{ + WindowPadding = ImFloor(WindowPadding * scale_factor); + WindowRounding = ImFloor(WindowRounding * scale_factor); + WindowMinSize = ImFloor(WindowMinSize * scale_factor); + ChildRounding = ImFloor(ChildRounding * scale_factor); + PopupRounding = ImFloor(PopupRounding * scale_factor); + FramePadding = ImFloor(FramePadding * scale_factor); + FrameRounding = ImFloor(FrameRounding * scale_factor); + ItemSpacing = ImFloor(ItemSpacing * scale_factor); + ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); + CellPadding = ImFloor(CellPadding * scale_factor); + TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); + IndentSpacing = ImFloor(IndentSpacing * scale_factor); + ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); + ScrollbarSize = ImFloor(ScrollbarSize * scale_factor); + ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor); + GrabMinSize = ImFloor(GrabMinSize * scale_factor); + GrabRounding = ImFloor(GrabRounding * scale_factor); + LogSliderDeadzone = ImFloor(LogSliderDeadzone * scale_factor); + TabRounding = ImFloor(TabRounding * scale_factor); + TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImFloor(TabMinWidthForCloseButton * scale_factor) : FLT_MAX; + DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor); + DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor); + MouseCursorScale = ImFloor(MouseCursorScale * scale_factor); +} + +ImGuiIO::ImGuiIO() +{ + // Most fields are initialized with zero + memset(this, 0, sizeof(*this)); + IM_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT && IM_ARRAYSIZE(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT); // Our pre-C++11 IM_STATIC_ASSERT() macros triggers warning on modern compilers so we don't use it here. + + // Settings + ConfigFlags = ImGuiConfigFlags_None; + BackendFlags = ImGuiBackendFlags_None; + DisplaySize = ImVec2(-1.0f, -1.0f); + DeltaTime = 1.0f / 60.0f; + IniSavingRate = 5.0f; + IniFilename = "imgui.ini"; + LogFilename = "imgui_log.txt"; + MouseDoubleClickTime = 0.30f; + MouseDoubleClickMaxDist = 6.0f; + for (int i = 0; i < ImGuiKey_COUNT; i++) + KeyMap[i] = -1; + KeyRepeatDelay = 0.275f; + KeyRepeatRate = 0.050f; + UserData = NULL; + + Fonts = NULL; + FontGlobalScale = 1.0f; + FontDefault = NULL; + FontAllowUserScaling = false; + DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + + // Miscellaneous options + MouseDrawCursor = false; +#ifdef __APPLE__ + ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag +#else + ConfigMacOSXBehaviors = false; +#endif + ConfigInputTextCursorBlink = true; + ConfigWindowsResizeFromEdges = true; + ConfigWindowsMoveFromTitleBarOnly = false; + ConfigMemoryCompactTimer = 60.0f; + + // Platform Functions + BackendPlatformName = BackendRendererName = NULL; + BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL; + GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; + ClipboardUserData = NULL; + ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; + ImeWindowHandle = NULL; + + // Input (NB: we already have memset zero the entire structure!) + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); + MouseDragThreshold = 6.0f; + for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f; +} + +// Pass in translated ASCII characters for text input. +// - with glfw you can get those from the callback set in glfwSetCharCallback() +// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message +void ImGuiIO::AddInputCharacter(unsigned int c) +{ + if (c != 0) + InputQueueCharacters.push_back(c <= IM_UNICODE_CODEPOINT_MAX ? (ImWchar)c : IM_UNICODE_CODEPOINT_INVALID); +} + +// UTF16 strings use surrogate pairs to encode codepoints >= 0x10000, so +// we should save the high surrogate. +void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c) +{ + if (c == 0 && InputQueueSurrogate == 0) + return; + + if ((c & 0xFC00) == 0xD800) // High surrogate, must save + { + if (InputQueueSurrogate != 0) + InputQueueCharacters.push_back(IM_UNICODE_CODEPOINT_INVALID); + InputQueueSurrogate = c; + return; + } + + ImWchar cp = c; + if (InputQueueSurrogate != 0) + { + if ((c & 0xFC00) != 0xDC00) // Invalid low surrogate + { + InputQueueCharacters.push_back(IM_UNICODE_CODEPOINT_INVALID); + } + else + { +#if IM_UNICODE_CODEPOINT_MAX == 0xFFFF + cp = IM_UNICODE_CODEPOINT_INVALID; // Codepoint will not fit in ImWchar +#else + cp = (ImWchar)(((InputQueueSurrogate - 0xD800) << 10) + (c - 0xDC00) + 0x10000); +#endif + } + + InputQueueSurrogate = 0; + } + InputQueueCharacters.push_back(cp); +} + +void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) +{ + while (*utf8_chars != 0) + { + unsigned int c = 0; + utf8_chars += ImTextCharFromUtf8(&c, utf8_chars, NULL); + if (c != 0) + InputQueueCharacters.push_back((ImWchar)c); + } +} + +void ImGuiIO::ClearInputCharacters() +{ + InputQueueCharacters.resize(0); +} + +//----------------------------------------------------------------------------- +// [SECTION] MISC HELPERS/UTILITIES (Geometry functions) +//----------------------------------------------------------------------------- + +ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments) +{ + IM_ASSERT(num_segments > 0); // Use ImBezierCubicClosestPointCasteljau() + ImVec2 p_last = p1; + ImVec2 p_closest; + float p_closest_dist2 = FLT_MAX; + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + { + ImVec2 p_current = ImBezierCubicCalc(p1, p2, p3, p4, t_step * i_step); + ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p); + float dist2 = ImLengthSqr(p - p_line); + if (dist2 < p_closest_dist2) + { + p_closest = p_line; + p_closest_dist2 = dist2; + } + p_last = p_current; + } + return p_closest; +} + +// Closely mimics PathBezierToCasteljau() in imgui_draw.cpp +static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_closest, ImVec2& p_last, float& p_closest_dist2, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +{ + float dx = x4 - x1; + float dy = y4 - y1; + float d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + { + ImVec2 p_current(x4, y4); + ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p); + float dist2 = ImLengthSqr(p - p_line); + if (dist2 < p_closest_dist2) + { + p_closest = p_line; + p_closest_dist2 = dist2; + } + p_last = p_current; + } + else if (level < 10) + { + float x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; + float x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; + float x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; + float x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; + float x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; + float x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + } +} + +// tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationTol +// Because those ImXXX functions are lower-level than ImGui:: we cannot access this value automatically. +ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol) +{ + IM_ASSERT(tess_tol > 0.0f); + ImVec2 p_last = p1; + ImVec2 p_closest; + float p_closest_dist2 = FLT_MAX; + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); + return p_closest; +} + +ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) +{ + ImVec2 ap = p - a; + ImVec2 ab_dir = b - a; + float dot = ap.x * ab_dir.x + ap.y * ab_dir.y; + if (dot < 0.0f) + return a; + float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; + if (dot > ab_len_sqr) + return b; + return a + ab_dir * dot / ab_len_sqr; +} + +bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; + bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; + bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; + return ((b1 == b2) && (b2 == b3)); +} + +void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) +{ + ImVec2 v0 = b - a; + ImVec2 v1 = c - a; + ImVec2 v2 = p - a; + const float denom = v0.x * v1.y - v1.x * v0.y; + out_v = (v2.x * v1.y - v1.x * v2.y) / denom; + out_w = (v0.x * v2.y - v2.x * v0.y) / denom; + out_u = 1.0f - out_v - out_w; +} + +ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + ImVec2 proj_ab = ImLineClosestPoint(a, b, p); + ImVec2 proj_bc = ImLineClosestPoint(b, c, p); + ImVec2 proj_ca = ImLineClosestPoint(c, a, p); + float dist2_ab = ImLengthSqr(p - proj_ab); + float dist2_bc = ImLengthSqr(p - proj_bc); + float dist2_ca = ImLengthSqr(p - proj_ca); + float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca)); + if (m == dist2_ab) + return proj_ab; + if (m == dist2_bc) + return proj_bc; + return proj_ca; +} + +//----------------------------------------------------------------------------- +// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions) +//----------------------------------------------------------------------------- + +// Consider using _stricmp/_strnicmp under Windows or strcasecmp/strncasecmp. We don't actually use either ImStricmp/ImStrnicmp in the codebase any more. +int ImStricmp(const char* str1, const char* str2) +{ + int d; + while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } + return d; +} + +int ImStrnicmp(const char* str1, const char* str2, size_t count) +{ + int d = 0; + while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; } + return d; +} + +void ImStrncpy(char* dst, const char* src, size_t count) +{ + if (count < 1) + return; + if (count > 1) + strncpy(dst, src, count - 1); + dst[count - 1] = 0; +} + +char* ImStrdup(const char* str) +{ + size_t len = strlen(str); + void* buf = IM_ALLOC(len + 1); + return (char*)memcpy(buf, (const void*)str, len + 1); +} + +char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src) +{ + size_t dst_buf_size = p_dst_size ? *p_dst_size : strlen(dst) + 1; + size_t src_size = strlen(src) + 1; + if (dst_buf_size < src_size) + { + IM_FREE(dst); + dst = (char*)IM_ALLOC(src_size); + if (p_dst_size) + *p_dst_size = src_size; + } + return (char*)memcpy(dst, (const void*)src, src_size); +} + +const char* ImStrchrRange(const char* str, const char* str_end, char c) +{ + const char* p = (const char*)memchr(str, (int)c, str_end - str); + return p; +} + +int ImStrlenW(const ImWchar* str) +{ + //return (int)wcslen((const wchar_t*)str); // FIXME-OPT: Could use this when wchar_t are 16-bit + int n = 0; + while (*str++) n++; + return n; +} + +// Find end-of-line. Return pointer will point to either first \n, either str_end. +const char* ImStreolRange(const char* str, const char* str_end) +{ + const char* p = (const char*)memchr(str, '\n', str_end - str); + return p ? p : str_end; +} + +const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line +{ + while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') + buf_mid_line--; + return buf_mid_line; +} + +const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) +{ + if (!needle_end) + needle_end = needle + strlen(needle); + + const char un0 = (char)toupper(*needle); + while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) + { + if (toupper(*haystack) == un0) + { + const char* b = needle + 1; + for (const char* a = haystack + 1; b < needle_end; a++, b++) + if (toupper(*a) != toupper(*b)) + break; + if (b == needle_end) + return haystack; + } + haystack++; + } + return NULL; +} + +// Trim str by offsetting contents when there's leading data + writing a \0 at the trailing position. We use this in situation where the cost is negligible. +void ImStrTrimBlanks(char* buf) +{ + char* p = buf; + while (p[0] == ' ' || p[0] == '\t') // Leading blanks + p++; + char* p_start = p; + while (*p != 0) // Find end of string + p++; + while (p > p_start && (p[-1] == ' ' || p[-1] == '\t')) // Trailing blanks + p--; + if (p_start != buf) // Copy memory if we had leading blanks + memmove(buf, p_start, p - p_start); + buf[p - p_start] = 0; // Zero terminate +} + +const char* ImStrSkipBlank(const char* str) +{ + while (str[0] == ' ' || str[0] == '\t') + str++; + return str; +} + +// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). +// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. +// B) When buf==NULL vsnprintf() will return the output size. +#ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS + +// We support stb_sprintf which is much faster (see: https://github.com/nothings/stb/blob/master/stb_sprintf.h) +// You may set IMGUI_USE_STB_SPRINTF to use our default wrapper, or set IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS +// and setup the wrapper yourself. (FIXME-OPT: Some of our high-level operations such as ImGuiTextBuffer::appendfv() are +// designed using two-passes worst case, which probably could be improved using the stbsp_vsprintfcb() function.) +#ifdef IMGUI_USE_STB_SPRINTF +#define STB_SPRINTF_IMPLEMENTATION +#include "stb_sprintf.h" +#endif + +#if defined(_MSC_VER) && !defined(vsnprintf) +#define vsnprintf _vsnprintf +#endif + +int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); +#ifdef IMGUI_USE_STB_SPRINTF + int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args); +#else + int w = vsnprintf(buf, buf_size, fmt, args); +#endif + va_end(args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} + +int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) +{ +#ifdef IMGUI_USE_STB_SPRINTF + int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args); +#else + int w = vsnprintf(buf, buf_size, fmt, args); +#endif + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} +#endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS + +// CRC32 needs a 1KB lookup table (not cache friendly) +// Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily: +// - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe. +static const ImU32 GCrc32LookupTable[256] = +{ + 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91, + 0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5, + 0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59, + 0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D, + 0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01, + 0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65, + 0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, + 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD, + 0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1, + 0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5, + 0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79, + 0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D, + 0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21, + 0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, + 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9, + 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D, +}; + +// Known size hash +// It is ok to call ImHashData on a string with known length but the ### operator won't be supported. +// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. +ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed) +{ + ImU32 crc = ~seed; + const unsigned char* data = (const unsigned char*)data_p; + const ImU32* crc32_lut = GCrc32LookupTable; + while (data_size-- != 0) + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++]; + return ~crc; +} + +// Zero-terminated string hash, with support for ### to reset back to seed value +// We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. +// Because this syntax is rarely used we are optimizing for the common case. +// - If we reach ### in the string we discard the hash so far and reset to the seed. +// - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in Debug build) +// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. +ImGuiID ImHashStr(const char* data_p, size_t data_size, ImU32 seed) +{ + seed = ~seed; + ImU32 crc = seed; + const unsigned char* data = (const unsigned char*)data_p; + const ImU32* crc32_lut = GCrc32LookupTable; + if (data_size != 0) + { + while (data_size-- != 0) + { + unsigned char c = *data++; + if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#') + crc = seed; + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; + } + } + else + { + while (unsigned char c = *data++) + { + if (c == '#' && data[0] == '#' && data[1] == '#') + crc = seed; + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; + } + } + return ~crc; +} + +//----------------------------------------------------------------------------- +// [SECTION] MISC HELPERS/UTILITIES (File functions) +//----------------------------------------------------------------------------- + +// Default file functions +#ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS + +ImFileHandle ImFileOpen(const char* filename, const char* mode) +{ +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(__CYGWIN__) && !defined(__GNUC__) + // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. + // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32! + const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); + const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0); + ImVector buf; + buf.resize(filename_wsize + mode_wsize); + ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, (wchar_t*)&buf[0], filename_wsize); + ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, (wchar_t*)&buf[filename_wsize], mode_wsize); + return ::_wfopen((const wchar_t*)&buf[0], (const wchar_t*)&buf[filename_wsize]); +#else + return fopen(filename, mode); +#endif +} + +// We should in theory be using fseeko()/ftello() with off_t and _fseeki64()/_ftelli64() with __int64, waiting for the PR that does that in a very portable pre-C++11 zero-warnings way. +bool ImFileClose(ImFileHandle f) { return fclose(f) == 0; } +ImU64 ImFileGetSize(ImFileHandle f) { long off = 0, sz = 0; return ((off = ftell(f)) != -1 && !fseek(f, 0, SEEK_END) && (sz = ftell(f)) != -1 && !fseek(f, off, SEEK_SET)) ? (ImU64)sz : (ImU64)-1; } +ImU64 ImFileRead(void* data, ImU64 sz, ImU64 count, ImFileHandle f) { return fread(data, (size_t)sz, (size_t)count, f); } +ImU64 ImFileWrite(const void* data, ImU64 sz, ImU64 count, ImFileHandle f) { return fwrite(data, (size_t)sz, (size_t)count, f); } +#endif // #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS + +// Helper: Load file content into memory +// Memory allocated with IM_ALLOC(), must be freed by user using IM_FREE() == ImGui::MemFree() +// This can't really be used with "rt" because fseek size won't match read size. +void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_file_size, int padding_bytes) +{ + IM_ASSERT(filename && mode); + if (out_file_size) + *out_file_size = 0; + + ImFileHandle f; + if ((f = ImFileOpen(filename, mode)) == NULL) + return NULL; + + size_t file_size = (size_t)ImFileGetSize(f); + if (file_size == (size_t)-1) + { + ImFileClose(f); + return NULL; + } + + void* file_data = IM_ALLOC(file_size + padding_bytes); + if (file_data == NULL) + { + ImFileClose(f); + return NULL; + } + if (ImFileRead(file_data, 1, file_size, f) != file_size) + { + ImFileClose(f); + IM_FREE(file_data); + return NULL; + } + if (padding_bytes > 0) + memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes); + + ImFileClose(f); + if (out_file_size) + *out_file_size = file_size; + + return file_data; +} + +//----------------------------------------------------------------------------- +// [SECTION] MISC HELPERS/UTILITIES (ImText* functions) +//----------------------------------------------------------------------------- + +// Convert UTF-8 to 32-bit character, process single character input. +// A nearly-branchless UTF-8 decoder, based on work of Christopher Wellons (https://github.com/skeeto/branchless-utf8). +// We handle UTF-8 decoding error by skipping forward. +int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) +{ + static const char lengths[32] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 }; + static const int masks[] = { 0x00, 0x7f, 0x1f, 0x0f, 0x07 }; + static const uint32_t mins[] = { 0x400000, 0, 0x80, 0x800, 0x10000 }; + static const int shiftc[] = { 0, 18, 12, 6, 0 }; + static const int shifte[] = { 0, 6, 4, 2, 0 }; + int len = lengths[*(const unsigned char*)in_text >> 3]; + int wanted = len + !len; + + if (in_text_end == NULL) + in_text_end = in_text + wanted; // Max length, nulls will be taken into account. + + // Copy at most 'len' bytes, stop copying at 0 or past in_text_end. Branch predictor does a good job here, + // so it is fast even with excessive branching. + unsigned char s[4]; + s[0] = in_text + 0 < in_text_end ? in_text[0] : 0; + s[1] = in_text + 1 < in_text_end ? in_text[1] : 0; + s[2] = in_text + 2 < in_text_end ? in_text[2] : 0; + s[3] = in_text + 3 < in_text_end ? in_text[3] : 0; + + // Assume a four-byte character and load four bytes. Unused bits are shifted out. + *out_char = (uint32_t)(s[0] & masks[len]) << 18; + *out_char |= (uint32_t)(s[1] & 0x3f) << 12; + *out_char |= (uint32_t)(s[2] & 0x3f) << 6; + *out_char |= (uint32_t)(s[3] & 0x3f) << 0; + *out_char >>= shiftc[len]; + + // Accumulate the various error conditions. + int e = 0; + e = (*out_char < mins[len]) << 6; // non-canonical encoding + e |= ((*out_char >> 11) == 0x1b) << 7; // surrogate half? + e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8; // out of range? + e |= (s[1] & 0xc0) >> 2; + e |= (s[2] & 0xc0) >> 4; + e |= (s[3] ) >> 6; + e ^= 0x2a; // top two bits of each tail byte correct? + e >>= shifte[len]; + + if (e) + { + // No bytes are consumed when *in_text == 0 || in_text == in_text_end. + // One byte is consumed in case of invalid first byte of in_text. + // All available bytes (at most `len` bytes) are consumed on incomplete/invalid second to last bytes. + // Invalid or incomplete input may consume less bytes than wanted, therefore every byte has to be inspected in s. + wanted = ImMin(wanted, !!s[0] + !!s[1] + !!s[2] + !!s[3]); + *out_char = IM_UNICODE_CODEPOINT_INVALID; + } + + return wanted; +} + +int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) +{ + ImWchar* buf_out = buf; + ImWchar* buf_end = buf + buf_size; + while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + *buf_out++ = (ImWchar)c; + } + *buf_out = 0; + if (in_text_remaining) + *in_text_remaining = in_text; + return (int)(buf_out - buf); +} + +int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) +{ + int char_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + char_count++; + } + return char_count; +} + +// Based on stb_to_utf8() from github.com/nothings/stb/ +static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) +{ + if (c < 0x80) + { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) + { + if (buf_size < 2) return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c < 0x10000) + { + if (buf_size < 3) return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c ) & 0x3f)); + return 3; + } + if (c <= 0x10FFFF) + { + if (buf_size < 4) return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c ) & 0x3f)); + return 4; + } + // Invalid code point, the max unicode is 0x10FFFF + return 0; +} + +// Not optimal but we very rarely use this function. +int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end) +{ + unsigned int unused = 0; + return ImTextCharFromUtf8(&unused, in_text, in_text_end); +} + +static inline int ImTextCountUtf8BytesFromChar(unsigned int c) +{ + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c < 0x10000) return 3; + if (c <= 0x10FFFF) return 4; + return 3; +} + +int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) +{ + char* buf_out = buf; + const char* buf_end = buf + buf_size; + while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + *buf_out++ = (char)c; + else + buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end - buf_out - 1), c); + } + *buf_out = 0; + return (int)(buf_out - buf); +} + +int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) +{ + int bytes_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + bytes_count++; + else + bytes_count += ImTextCountUtf8BytesFromChar(c); + } + return bytes_count; +} + +//----------------------------------------------------------------------------- +// [SECTION] MISC HELPERS/UTILITIES (Color functions) +// Note: The Convert functions are early design which are not consistent with other API. +//----------------------------------------------------------------------------- + +IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b) +{ + float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; + int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t); + return IM_COL32(r, g, b, 0xFF); +} + +ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) +{ + float s = 1.0f / 255.0f; + return ImVec4( + ((in >> IM_COL32_R_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); +} + +ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) +{ + ImU32 out; + out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT; + return out; +} + +// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 +// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv +void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) +{ + float K = 0.f; + if (g < b) + { + ImSwap(g, b); + K = -1.f; + } + if (r < g) + { + ImSwap(r, g); + K = -2.f / 6.f - K; + } + + const float chroma = r - (g < b ? g : b); + out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f)); + out_s = chroma / (r + 1e-20f); + out_v = r; +} + +// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 +// also http://en.wikipedia.org/wiki/HSL_and_HSV +void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) +{ + if (s == 0.0f) + { + // gray + out_r = out_g = out_b = v; + return; + } + + h = ImFmod(h, 1.0f) / (60.0f / 360.0f); + int i = (int)h; + float f = h - (float)i; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + + switch (i) + { + case 0: out_r = v; out_g = t; out_b = p; break; + case 1: out_r = q; out_g = v; out_b = p; break; + case 2: out_r = p; out_g = v; out_b = t; break; + case 3: out_r = p; out_g = q; out_b = v; break; + case 4: out_r = t; out_g = p; out_b = v; break; + case 5: default: out_r = v; out_g = p; out_b = q; break; + } +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiStorage +// Helper: Key->value storage +//----------------------------------------------------------------------------- + +// std::lower_bound but without the bullshit +static ImGuiStorage::ImGuiStoragePair* LowerBound(ImVector& data, ImGuiID key) +{ + ImGuiStorage::ImGuiStoragePair* first = data.Data; + ImGuiStorage::ImGuiStoragePair* last = data.Data + data.Size; + size_t count = (size_t)(last - first); + while (count > 0) + { + size_t count2 = count >> 1; + ImGuiStorage::ImGuiStoragePair* mid = first + count2; + if (mid->key < key) + { + first = ++mid; + count -= count2 + 1; + } + else + { + count = count2; + } + } + return first; +} + +// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. +void ImGuiStorage::BuildSortByKey() +{ + struct StaticFunc + { + static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs) + { + // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. + if (((const ImGuiStoragePair*)lhs)->key > ((const ImGuiStoragePair*)rhs)->key) return +1; + if (((const ImGuiStoragePair*)lhs)->key < ((const ImGuiStoragePair*)rhs)->key) return -1; + return 0; + } + }; + if (Data.Size > 1) + ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), StaticFunc::PairCompareByID); +} + +int ImGuiStorage::GetInt(ImGuiID key, int default_val) const +{ + ImGuiStoragePair* it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_i; +} + +bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const +{ + return GetInt(key, default_val ? 1 : 0) != 0; +} + +float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const +{ + ImGuiStoragePair* it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_f; +} + +void* ImGuiStorage::GetVoidPtr(ImGuiID key) const +{ + ImGuiStoragePair* it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return NULL; + return it->val_p; +} + +// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. +int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, ImGuiStoragePair(key, default_val)); + return &it->val_i; +} + +bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) +{ + return (bool*)GetIntRef(key, default_val ? 1 : 0); +} + +float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, ImGuiStoragePair(key, default_val)); + return &it->val_f; +} + +void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, ImGuiStoragePair(key, default_val)); + return &it->val_p; +} + +// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame) +void ImGuiStorage::SetInt(ImGuiID key, int val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, ImGuiStoragePair(key, val)); + return; + } + it->val_i = val; +} + +void ImGuiStorage::SetBool(ImGuiID key, bool val) +{ + SetInt(key, val ? 1 : 0); +} + +void ImGuiStorage::SetFloat(ImGuiID key, float val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, ImGuiStoragePair(key, val)); + return; + } + it->val_f = val; +} + +void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) +{ + ImGuiStoragePair* it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, ImGuiStoragePair(key, val)); + return; + } + it->val_p = val; +} + +void ImGuiStorage::SetAllInt(int v) +{ + for (int i = 0; i < Data.Size; i++) + Data[i].val_i = v; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiTextFilter +//----------------------------------------------------------------------------- + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) +{ + if (default_filter) + { + ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); + Build(); + } + else + { + InputBuf[0] = 0; + CountGrep = 0; + } +} + +bool ImGuiTextFilter::Draw(const char* label, float width) +{ + if (width != 0.0f) + ImGui::SetNextItemWidth(width); + bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); + if (value_changed) + Build(); + return value_changed; +} + +void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector* out) const +{ + out->resize(0); + const char* wb = b; + const char* we = wb; + while (we < e) + { + if (*we == separator) + { + out->push_back(ImGuiTextRange(wb, we)); + wb = we + 1; + } + we++; + } + if (wb != we) + out->push_back(ImGuiTextRange(wb, we)); +} + +void ImGuiTextFilter::Build() +{ + Filters.resize(0); + ImGuiTextRange input_range(InputBuf, InputBuf + strlen(InputBuf)); + input_range.split(',', &Filters); + + CountGrep = 0; + for (int i = 0; i != Filters.Size; i++) + { + ImGuiTextRange& f = Filters[i]; + while (f.b < f.e && ImCharIsBlankA(f.b[0])) + f.b++; + while (f.e > f.b && ImCharIsBlankA(f.e[-1])) + f.e--; + if (f.empty()) + continue; + if (Filters[i].b[0] != '-') + CountGrep += 1; + } +} + +bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const +{ + if (Filters.empty()) + return true; + + if (text == NULL) + text = ""; + + for (int i = 0; i != Filters.Size; i++) + { + const ImGuiTextRange& f = Filters[i]; + if (f.empty()) + continue; + if (f.b[0] == '-') + { + // Subtract + if (ImStristr(text, text_end, f.b + 1, f.e) != NULL) + return false; + } + else + { + // Grep + if (ImStristr(text, text_end, f.b, f.e) != NULL) + return true; + } + } + + // Implicit * grep + if (CountGrep == 0) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiTextBuffer +//----------------------------------------------------------------------------- + +// On some platform vsnprintf() takes va_list by reference and modifies it. +// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. +#ifndef va_copy +#if defined(__GNUC__) || defined(__clang__) +#define va_copy(dest, src) __builtin_va_copy(dest, src) +#else +#define va_copy(dest, src) (dest = src) +#endif +#endif + +char ImGuiTextBuffer::EmptyString[1] = { 0 }; + +void ImGuiTextBuffer::append(const char* str, const char* str_end) +{ + int len = str_end ? (int)(str_end - str) : (int)strlen(str); + + // Add zero-terminator the first time + const int write_off = (Buf.Size != 0) ? Buf.Size : 1; + const int needed_sz = write_off + len; + if (write_off + len >= Buf.Capacity) + { + int new_capacity = Buf.Capacity * 2; + Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity); + } + + Buf.resize(needed_sz); + memcpy(&Buf[write_off - 1], str, (size_t)len); + Buf[write_off - 1 + len] = 0; +} + +void ImGuiTextBuffer::appendf(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + appendfv(fmt, args); + va_end(args); +} + +// Helper: Text buffer for logging/accumulating text +void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) +{ + va_list args_copy; + va_copy(args_copy, args); + + int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. + if (len <= 0) + { + va_end(args_copy); + return; + } + + // Add zero-terminator the first time + const int write_off = (Buf.Size != 0) ? Buf.Size : 1; + const int needed_sz = write_off + len; + if (write_off + len >= Buf.Capacity) + { + int new_capacity = Buf.Capacity * 2; + Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity); + } + + Buf.resize(needed_sz); + ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy); + va_end(args_copy); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiListClipper +// This is currently not as flexible/powerful as it should be and really confusing/spaghetti, mostly because we changed +// the API mid-way through development and support two ways to using the clipper, needs some rework (see TODO) +//----------------------------------------------------------------------------- + +// FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell. +// The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous. +static bool GetSkipItemForListClipping() +{ + ImGuiContext& g = *GImGui; + return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems); +} + +// Helper to calculate coarse clipping of large list of evenly sized items. +// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. +// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX +void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.LogEnabled) + { + // If logging is active, do not perform any clipping + *out_items_display_start = 0; + *out_items_display_end = items_count; + return; + } + if (GetSkipItemForListClipping()) + { + *out_items_display_start = *out_items_display_end = 0; + return; + } + + // We create the union of the ClipRect and the NavScoringRect which at worst should be 1 page away from ClipRect + ImRect unclipped_rect = window->ClipRect; + if (g.NavMoveRequest) + unclipped_rect.Add(g.NavScoringRect); + if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId) + unclipped_rect.Add(ImRect(window->Pos + window->NavRectRel[0].Min, window->Pos + window->NavRectRel[0].Max)); + + const ImVec2 pos = window->DC.CursorPos; + int start = (int)((unclipped_rect.Min.y - pos.y) / items_height); + int end = (int)((unclipped_rect.Max.y - pos.y) / items_height); + + // When performing a navigation request, ensure we have one item extra in the direction we are moving to + if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up) + start--; + if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Down) + end++; + + start = ImClamp(start, 0, items_count); + end = ImClamp(end + 1, start, items_count); + *out_items_display_start = start; + *out_items_display_end = end; +} + +static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height) +{ + // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor. + // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. + // The clipper should probably have a 4th step to display the last item in a regular manner. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + float off_y = pos_y - window->DC.CursorPos.y; + window->DC.CursorPos.y = pos_y; + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y); + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage. + window->DC.PrevLineSize.y = (line_height - g.Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. + if (ImGuiOldColumns* columns = window->DC.CurrentColumns) + columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly + if (ImGuiTable* table = g.CurrentTable) + { + if (table->IsInsideRow) + ImGui::TableEndRow(table); + table->RowPosY2 = window->DC.CursorPos.y; + const int row_increase = (int)((off_y / line_height) + 0.5f); + //table->CurrentRow += row_increase; // Can't do without fixing TableEndRow() + table->RowBgColorCounter += row_increase; + } +} + +ImGuiListClipper::ImGuiListClipper() +{ + memset(this, 0, sizeof(*this)); + ItemsCount = -1; +} + +ImGuiListClipper::~ImGuiListClipper() +{ + IM_ASSERT(ItemsCount == -1 && "Forgot to call End(), or to Step() until false?"); +} + +// Use case A: Begin() called from constructor with items_height<0, then called again from Step() in StepNo 1 +// Use case B: Begin() called from constructor with items_height>0 +// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style. +void ImGuiListClipper::Begin(int items_count, float items_height) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (ImGuiTable* table = g.CurrentTable) + if (table->IsInsideRow) + ImGui::TableEndRow(table); + + StartPosY = window->DC.CursorPos.y; + ItemsHeight = items_height; + ItemsCount = items_count; + ItemsFrozen = 0; + StepNo = 0; + DisplayStart = -1; + DisplayEnd = 0; +} + +void ImGuiListClipper::End() +{ + if (ItemsCount < 0) // Already ended + return; + + // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. + if (ItemsCount < INT_MAX && DisplayStart >= 0) + SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); + ItemsCount = -1; + StepNo = 3; +} + +bool ImGuiListClipper::Step() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + ImGuiTable* table = g.CurrentTable; + if (table && table->IsInsideRow) + ImGui::TableEndRow(table); + + // No items + if (ItemsCount == 0 || GetSkipItemForListClipping()) + { + End(); + return false; + } + + // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height) + if (StepNo == 0) + { + // While we are in frozen row state, keep displaying items one by one, unclipped + // FIXME: Could be stored as a table-agnostic state. + if (table != NULL && !table->IsUnfrozenRows) + { + DisplayStart = ItemsFrozen; + DisplayEnd = ItemsFrozen + 1; + ItemsFrozen++; + return true; + } + + StartPosY = window->DC.CursorPos.y; + if (ItemsHeight <= 0.0f) + { + // Submit the first item so we can measure its height (generally it is 0..1) + DisplayStart = ItemsFrozen; + DisplayEnd = ItemsFrozen + 1; + StepNo = 1; + return true; + } + + // Already has item height (given by user in Begin): skip to calculating step + DisplayStart = DisplayEnd; + StepNo = 2; + } + + // Step 1: the clipper infer height from first element + if (StepNo == 1) + { + IM_ASSERT(ItemsHeight <= 0.0f); + if (table) + { + const float pos_y1 = table->RowPosY1; // Using this instead of StartPosY to handle clipper straddling the frozen row + const float pos_y2 = table->RowPosY2; // Using this instead of CursorPos.y to take account of tallest cell. + ItemsHeight = pos_y2 - pos_y1; + window->DC.CursorPos.y = pos_y2; + } + else + { + ItemsHeight = window->DC.CursorPos.y - StartPosY; + } + IM_ASSERT(ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); + StepNo = 2; + } + + // Reached end of list + if (DisplayEnd >= ItemsCount) + { + End(); + return false; + } + + // Step 2: calculate the actual range of elements to display, and position the cursor before the first element + if (StepNo == 2) + { + IM_ASSERT(ItemsHeight > 0.0f); + + int already_submitted = DisplayEnd; + ImGui::CalcListClipping(ItemsCount - already_submitted, ItemsHeight, &DisplayStart, &DisplayEnd); + DisplayStart += already_submitted; + DisplayEnd += already_submitted; + + // Seek cursor + if (DisplayStart > already_submitted) + SetCursorPosYAndSetupForPrevLine(StartPosY + (DisplayStart - ItemsFrozen) * ItemsHeight, ItemsHeight); + + StepNo = 3; + return true; + } + + // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), + // Advance the cursor to the end of the list and then returns 'false' to end the loop. + if (StepNo == 3) + { + // Seek cursor + if (ItemsCount < INT_MAX) + SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); // advance cursor + ItemsCount = -1; + return false; + } + + IM_ASSERT(0); + return false; +} + +//----------------------------------------------------------------------------- +// [SECTION] STYLING +//----------------------------------------------------------------------------- + +ImGuiStyle& ImGui::GetStyle() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); + return GImGui->Style; +} + +ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = style.Colors[idx]; + c.w *= style.Alpha * alpha_mul; + return ColorConvertFloat4ToU32(c); +} + +ImU32 ImGui::GetColorU32(const ImVec4& col) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = col; + c.w *= style.Alpha; + return ColorConvertFloat4ToU32(c); +} + +const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) +{ + ImGuiStyle& style = GImGui->Style; + return style.Colors[idx]; +} + +ImU32 ImGui::GetColorU32(ImU32 col) +{ + ImGuiStyle& style = GImGui->Style; + if (style.Alpha >= 1.0f) + return col; + ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (ImU32)(a * style.Alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + +// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32 +void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) +{ + ImGuiContext& g = *GImGui; + ImGuiColorMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorStack.push_back(backup); + g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); +} + +void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) +{ + ImGuiContext& g = *GImGui; + ImGuiColorMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorStack.push_back(backup); + g.Style.Colors[idx] = col; +} + +void ImGui::PopStyleColor(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiColorMod& backup = g.ColorStack.back(); + g.Style.Colors[backup.Col] = backup.BackupValue; + g.ColorStack.pop_back(); + count--; + } +} + +struct ImGuiStyleVarInfo +{ + ImGuiDataType Type; + ImU32 Count; + ImU32 Offset; + void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); } +}; + +static const ImGuiStyleVarInfo GStyleVarInfo[] = +{ + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign +}; + +static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) +{ + IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT); + IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT); + return &GStyleVarInfo[idx]; +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) + { + ImGuiContext& g = *GImGui; + float* pvar = (float*)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0 && "Called PushStyleVar() float variant but variable is not a float!"); +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) + { + ImGuiContext& g = *GImGui; + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!"); +} + +void ImGui::PopStyleVar(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it. + ImGuiStyleMod& backup = g.StyleVarStack.back(); + const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); + void* data = info->GetVarPtr(&g.Style); + if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; } + else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; } + g.StyleVarStack.pop_back(); + count--; + } +} + +const char* ImGui::GetStyleColorName(ImGuiCol idx) +{ + // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; + switch (idx) + { + case ImGuiCol_Text: return "Text"; + case ImGuiCol_TextDisabled: return "TextDisabled"; + case ImGuiCol_WindowBg: return "WindowBg"; + case ImGuiCol_ChildBg: return "ChildBg"; + case ImGuiCol_PopupBg: return "PopupBg"; + case ImGuiCol_Border: return "Border"; + case ImGuiCol_BorderShadow: return "BorderShadow"; + case ImGuiCol_FrameBg: return "FrameBg"; + case ImGuiCol_FrameBgHovered: return "FrameBgHovered"; + case ImGuiCol_FrameBgActive: return "FrameBgActive"; + case ImGuiCol_TitleBg: return "TitleBg"; + case ImGuiCol_TitleBgActive: return "TitleBgActive"; + case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; + case ImGuiCol_MenuBarBg: return "MenuBarBg"; + case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; + case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; + case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; + case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; + case ImGuiCol_CheckMark: return "CheckMark"; + case ImGuiCol_SliderGrab: return "SliderGrab"; + case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; + case ImGuiCol_Button: return "Button"; + case ImGuiCol_ButtonHovered: return "ButtonHovered"; + case ImGuiCol_ButtonActive: return "ButtonActive"; + case ImGuiCol_Header: return "Header"; + case ImGuiCol_HeaderHovered: return "HeaderHovered"; + case ImGuiCol_HeaderActive: return "HeaderActive"; + case ImGuiCol_Separator: return "Separator"; + case ImGuiCol_SeparatorHovered: return "SeparatorHovered"; + case ImGuiCol_SeparatorActive: return "SeparatorActive"; + case ImGuiCol_ResizeGrip: return "ResizeGrip"; + case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; + case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; + case ImGuiCol_Tab: return "Tab"; + case ImGuiCol_TabHovered: return "TabHovered"; + case ImGuiCol_TabActive: return "TabActive"; + case ImGuiCol_TabUnfocused: return "TabUnfocused"; + case ImGuiCol_TabUnfocusedActive: return "TabUnfocusedActive"; + case ImGuiCol_PlotLines: return "PlotLines"; + case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; + case ImGuiCol_PlotHistogram: return "PlotHistogram"; + case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; + case ImGuiCol_TableHeaderBg: return "TableHeaderBg"; + case ImGuiCol_TableBorderStrong: return "TableBorderStrong"; + case ImGuiCol_TableBorderLight: return "TableBorderLight"; + case ImGuiCol_TableRowBg: return "TableRowBg"; + case ImGuiCol_TableRowBgAlt: return "TableRowBgAlt"; + case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; + case ImGuiCol_DragDropTarget: return "DragDropTarget"; + case ImGuiCol_NavHighlight: return "NavHighlight"; + case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight"; + case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg"; + case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg"; + } + IM_ASSERT(0); + return "Unknown"; +} + + +//----------------------------------------------------------------------------- +// [SECTION] RENDER HELPERS +// Some of those (internal) functions are currently quite a legacy mess - their signature and behavior will change, +// we need a nicer separation between low-level functions and high-level functions relying on the ImGui context. +// Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: context. +//----------------------------------------------------------------------------- + +const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) +{ + const char* text_display_end = text; + if (!text_end) + text_end = (const char*)-1; + + while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#')) + text_display_end++; + return text_display_end; +} + +// Internal ImGui functions to render text +// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText() +void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Hide anything after a '##' string + const char* text_display_end; + if (hide_text_after_hash) + { + text_display_end = FindRenderedTextEnd(text, text_end); + } + else + { + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + text_display_end = text_end; + } + + if (text != text_display_end) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); + } +} + +void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + + if (text != text_end) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_end); + } +} + +// Default clip_rect uses (pos_min,pos_max) +// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) +void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +{ + // Perform CPU side clipping for single clipped element to avoid using scissor state + ImVec2 pos = pos_min; + const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); + + const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min; + const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max; + bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); + if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min + need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); + + // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. + if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); + if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); + + // Render + if (need_clipping) + { + ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); + draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + } + else + { + draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + } +} + +void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +{ + // Hide anything after a '##' string + const char* text_display_end = FindRenderedTextEnd(text, text_end); + const int text_len = (int)(text_display_end - text); + if (text_len == 0) + return; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + RenderTextClippedEx(window->DrawList, pos_min, pos_max, text, text_display_end, text_size_if_known, align, clip_rect); + if (g.LogEnabled) + LogRenderedText(&pos_min, text, text_display_end); +} + + +// Another overly complex function until we reorganize everything into a nice all-in-one helper. +// This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) which define _where_ the ellipsis is, from actual clipping of text and limit of the ellipsis display. +// This is because in the context of tabs we selectively hide part of the text when the Close Button appears, but we don't want the ellipsis to move. +void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end_full, const ImVec2* text_size_if_known) +{ + ImGuiContext& g = *GImGui; + if (text_end_full == NULL) + text_end_full = FindRenderedTextEnd(text); + const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f); + + //draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 4), IM_COL32(0, 0, 255, 255)); + //draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y-2), ImVec2(ellipsis_max_x, pos_max.y+2), IM_COL32(0, 255, 0, 255)); + //draw_list->AddLine(ImVec2(clip_max_x, pos_min.y), ImVec2(clip_max_x, pos_max.y), IM_COL32(255, 0, 0, 255)); + // FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels. + if (text_size.x > pos_max.x - pos_min.x) + { + // Hello wo... + // | | | + // min max ellipsis_max + // <-> this is generally some padding value + + const ImFont* font = draw_list->_Data->Font; + const float font_size = draw_list->_Data->FontSize; + const char* text_end_ellipsis = NULL; + + ImWchar ellipsis_char = font->EllipsisChar; + int ellipsis_char_count = 1; + if (ellipsis_char == (ImWchar)-1) + { + ellipsis_char = (ImWchar)'.'; + ellipsis_char_count = 3; + } + const ImFontGlyph* glyph = font->FindGlyph(ellipsis_char); + + float ellipsis_glyph_width = glyph->X1; // Width of the glyph with no padding on either side + float ellipsis_total_width = ellipsis_glyph_width; // Full width of entire ellipsis + + if (ellipsis_char_count > 1) + { + // Full ellipsis size without free spacing after it. + const float spacing_between_dots = 1.0f * (draw_list->_Data->FontSize / font->FontSize); + ellipsis_glyph_width = glyph->X1 - glyph->X0 + spacing_between_dots; + ellipsis_total_width = ellipsis_glyph_width * (float)ellipsis_char_count - spacing_between_dots; + } + + // We can now claim the space between pos_max.x and ellipsis_max.x + const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_total_width) - pos_min.x, 1.0f); + float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x; + if (text == text_end_ellipsis && text_end_ellipsis < text_end_full) + { + // Always display at least 1 character if there's no room for character + ellipsis + text_end_ellipsis = text + ImTextCountUtf8BytesFromChar(text, text_end_full); + text_size_clipped_x = font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text, text_end_ellipsis).x; + } + while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1])) + { + // Trim trailing space before ellipsis (FIXME: Supporting non-ascii blanks would be nice, for this we need a function to backtrack in UTF-8 text) + text_end_ellipsis--; + text_size_clipped_x -= font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text_end_ellipsis, text_end_ellipsis + 1).x; // Ascii blanks are always 1 byte + } + + // Render text, render ellipsis + RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f)); + float ellipsis_x = pos_min.x + text_size_clipped_x; + if (ellipsis_x + ellipsis_total_width <= ellipsis_max_x) + for (int i = 0; i < ellipsis_char_count; i++) + { + font->RenderChar(draw_list, font_size, ImVec2(ellipsis_x, pos_min.y), GetColorU32(ImGuiCol_Text), ellipsis_char); + ellipsis_x += ellipsis_glyph_width; + } + } + else + { + RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_full, &text_size, ImVec2(0.0f, 0.0f)); + } + + if (g.LogEnabled) + LogRenderedText(&pos_min, text, text_end_full); +} + +// Render a rectangle shaped with optional rounding and borders +void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); + const float border_size = g.Style.FrameBorderSize; + if (border && border_size > 0.0f) + { + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); + } +} + +void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const float border_size = g.Style.FrameBorderSize; + if (border_size > 0.0f) + { + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); + } +} + +void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags) +{ + ImGuiContext& g = *GImGui; + if (id != g.NavId) + return; + if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw)) + return; + ImGuiWindow* window = g.CurrentWindow; + if (window->DC.NavHideHighlightOneFrame) + return; + + float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; + ImRect display_rect = bb; + display_rect.ClipWith(window->ClipRect); + if (flags & ImGuiNavHighlightFlags_TypeDefault) + { + const float THICKNESS = 2.0f; + const float DISTANCE = 3.0f + THICKNESS * 0.5f; + display_rect.Expand(ImVec2(DISTANCE, DISTANCE)); + bool fully_visible = window->ClipRect.Contains(display_rect); + if (!fully_visible) + window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); + window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), display_rect.Max - ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, 0, THICKNESS); + if (!fully_visible) + window->DrawList->PopClipRect(); + } + if (flags & ImGuiNavHighlightFlags_TypeThin) + { + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, 1.0f); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +//----------------------------------------------------------------------------- + +// ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods +ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) : DrawListInst(NULL) +{ + memset(this, 0, sizeof(*this)); + Name = ImStrdup(name); + NameBufLen = (int)strlen(name) + 1; + ID = ImHashStr(name); + IDStack.push_back(ID); + MoveId = GetID("#MOVE"); + ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); + AutoFitFramesX = AutoFitFramesY = -1; + AutoPosLastDirection = ImGuiDir_None; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); + LastFrameActive = -1; + LastTimeActive = -1.0f; + FontWindowScale = 1.0f; + SettingsOffset = -1; + DrawList = &DrawListInst; + DrawList->_Data = &context->DrawListSharedData; + DrawList->_OwnerName = Name; +} + +ImGuiWindow::~ImGuiWindow() +{ + IM_ASSERT(DrawList == &DrawListInst); + IM_DELETE(Name); + for (int i = 0; i != ColumnsStorage.Size; i++) + ColumnsStorage[i].~ImGuiOldColumns(); +} + +ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); + ImGui::KeepAliveID(id); +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGuiContext& g = *GImGui; + IMGUI_TEST_ENGINE_ID_INFO2(id, ImGuiDataType_String, str, str_end); +#endif + return id; +} + +ImGuiID ImGuiWindow::GetID(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&ptr, sizeof(void*), seed); + ImGui::KeepAliveID(id); +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGuiContext& g = *GImGui; + IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_Pointer, ptr); +#endif + return id; +} + +ImGuiID ImGuiWindow::GetID(int n) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&n, sizeof(n), seed); + ImGui::KeepAliveID(id); +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGuiContext& g = *GImGui; + IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_S32, (intptr_t)n); +#endif + return id; +} + +ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGuiContext& g = *GImGui; + IMGUI_TEST_ENGINE_ID_INFO2(id, ImGuiDataType_String, str, str_end); +#endif + return id; +} + +ImGuiID ImGuiWindow::GetIDNoKeepAlive(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&ptr, sizeof(void*), seed); +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGuiContext& g = *GImGui; + IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_Pointer, ptr); +#endif + return id; +} + +ImGuiID ImGuiWindow::GetIDNoKeepAlive(int n) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&n, sizeof(n), seed); +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGuiContext& g = *GImGui; + IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_S32, (intptr_t)n); +#endif + return id; +} + +// This is only used in rare/specific situations to manufacture an ID out of nowhere. +ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) +{ + ImGuiID seed = IDStack.back(); + const int r_rel[4] = { (int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x), (int)(r_abs.Max.y - Pos.y) }; + ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed); + ImGui::KeepAliveID(id); + return id; +} + +static void SetCurrentWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow = window; + g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL; + if (window) + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +void ImGui::GcCompactTransientMiscBuffers() +{ + ImGuiContext& g = *GImGui; + g.ItemFlagsStack.clear(); + g.GroupStack.clear(); + TableGcCompactSettings(); +} + +// Free up/compact internal window buffers, we can use this when a window becomes unused. +// Not freed: +// - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data) +// This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost. +void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window) +{ + window->MemoryCompacted = true; + window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity; + window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity; + window->IDStack.clear(); + window->DrawList->_ClearFreeMemory(); + window->DC.ChildWindows.clear(); + window->DC.ItemWidthStack.clear(); + window->DC.TextWrapPosStack.clear(); +} + +void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window) +{ + // We stored capacity of the ImDrawList buffer to reduce growth-caused allocation/copy when awakening. + // The other buffers tends to amortize much faster. + window->MemoryCompacted = false; + window->DrawList->IdxBuffer.reserve(window->MemoryDrawListIdxCapacity); + window->DrawList->VtxBuffer.reserve(window->MemoryDrawListVtxCapacity); + window->MemoryDrawListIdxCapacity = window->MemoryDrawListVtxCapacity = 0; +} + +void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.ActiveIdIsJustActivated = (g.ActiveId != id); + if (g.ActiveIdIsJustActivated) + { + g.ActiveIdTimer = 0.0f; + g.ActiveIdHasBeenPressedBefore = false; + g.ActiveIdHasBeenEditedBefore = false; + g.ActiveIdMouseButton = -1; + if (id != 0) + { + g.LastActiveId = id; + g.LastActiveIdTimer = 0.0f; + } + } + g.ActiveId = id; + g.ActiveIdAllowOverlap = false; + g.ActiveIdNoClearOnFocusLoss = false; + g.ActiveIdWindow = window; + g.ActiveIdHasBeenEditedThisFrame = false; + if (id) + { + g.ActiveIdIsAlive = id; + g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse; + } + + // Clear declaration of inputs claimed by the widget + // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet) + g.ActiveIdUsingMouseWheel = false; + g.ActiveIdUsingNavDirMask = 0x00; + g.ActiveIdUsingNavInputMask = 0x00; + g.ActiveIdUsingKeyInputMask = 0x00; +} + +void ImGui::ClearActiveID() +{ + SetActiveID(0, NULL); // g.ActiveId = 0; +} + +void ImGui::SetHoveredID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.HoveredId = id; + g.HoveredIdAllowOverlap = false; + g.HoveredIdUsingMouseWheel = false; + if (id != 0 && g.HoveredIdPreviousFrame != id) + g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f; +} + +ImGuiID ImGui::GetHoveredID() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame; +} + +void ImGui::KeepAliveID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + g.ActiveIdIsAlive = id; + if (g.ActiveIdPreviousFrame == id) + g.ActiveIdPreviousFrameIsAlive = true; +} + +void ImGui::MarkItemEdited(ImGuiID id) +{ + // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit(). + // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need need to fill the data. + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive); + IM_UNUSED(id); // Avoid unused variable warnings when asserts are compiled out. + //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); + g.ActiveIdHasBeenEditedThisFrame = true; + g.ActiveIdHasBeenEditedBefore = true; + g.CurrentWindow->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited; +} + +static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) +{ + // An active popup disable hovering on other windows (apart from its own children) + // FIXME-OPT: This could be cached/stored within the window. + ImGuiContext& g = *GImGui; + if (g.NavWindow) + if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) + if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + { + // For the purpose of those flags we differentiate "standard popup" from "modal popup" + // NB: The order of those two tests is important because Modal windows are also Popups. + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) + return false; + if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return false; + } + return true; +} + +// This is roughly matching the behavior of internal-facing ItemHoverable() +// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered() +// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId +bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavDisableMouseHover && !g.NavDisableHighlight) + return IsItemFocused(); + + // Test for bounding box overlap, as updated as ItemAdd() + ImGuiItemStatusFlags status_flags = window->DC.LastItemStatusFlags; + if (!(status_flags & ImGuiItemStatusFlags_HoveredRect)) + return false; + IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function + + // Test if we are hovering the right window (our window could be behind another window) + // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851) + // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable + // to use IsItemHovered() after EndChild() itself. Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was + // the test that has been running for a long while. + if (g.HoveredWindow != window && (status_flags & ImGuiItemStatusFlags_HoveredWindow) == 0) + if ((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0) + return false; + + // Test if another item is active (e.g. being dragged) + if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0) + if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + return false; + + // Test if interactions on this window are blocked by an active popup or modal. + // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here. + if (!IsWindowContentHoverable(window, flags)) + return false; + + // Test if the item is disabled + if ((g.CurrentItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + return false; + + // Special handling for calling after Begin() which represent the title bar or tab. + // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. + if (window->DC.LastItemId == window->MoveId && window->WriteAccessed) + return false; + return true; +} + +// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). +bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow != window) + return false; + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + return false; + if (!IsMouseHoveringRect(bb.Min, bb.Max)) + return false; + if (g.NavDisableMouseHover) + return false; + if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None) || (g.CurrentItemFlags & ImGuiItemFlags_Disabled)) + { + g.HoveredIdDisabled = true; + return false; + } + + // We exceptionally allow this function to be called with id==0 to allow using it for easy high-level + // hover test in widgets code. We could also decide to split this function is two. + if (id != 0) + { + SetHoveredID(id); + + // [DEBUG] Item Picker tool! + // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making + // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered + // items if we perform the test in ItemAdd(), but that would incur a small runtime cost. + // #define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX in imconfig.h if you want this check to also be performed in ItemAdd(). + if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id) + GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255)); + if (g.DebugItemPickerBreakId == id) + IM_DEBUG_BREAK(); + } + + return true; +} + +bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!bb.Overlaps(window->ClipRect)) + if (id == 0 || (id != g.ActiveId && id != g.NavId)) + if (clip_even_when_logged || !g.LogEnabled) + return true; + return false; +} + +// This is also inlined in ItemAdd() +// Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set window->DC.LastItemDisplayRect! +void ImGui::SetLastItemData(ImGuiWindow* window, ImGuiID item_id, ImGuiItemStatusFlags item_flags, const ImRect& item_rect) +{ + window->DC.LastItemId = item_id; + window->DC.LastItemStatusFlags = item_flags; + window->DC.LastItemRect = item_rect; +} + +// Process TAB/Shift+TAB. Be mindful that this function may _clear_ the ActiveID when tabbing out. +void ImGui::ItemFocusable(ImGuiWindow* window, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(id != 0 && id == window->DC.LastItemId); + + // Increment counters + // FIXME: ImGuiItemFlags_Disabled should disable more. + const bool is_tab_stop = (g.CurrentItemFlags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0; + window->DC.FocusCounterRegular++; + if (is_tab_stop) + { + window->DC.FocusCounterTabStop++; + if (g.NavId == id) + g.NavIdTabCounter = window->DC.FocusCounterTabStop; + } + + // Process TAB/Shift-TAB to tab *OUT* of the currently focused item. + // (Note that we can always TAB out of a widget that doesn't allow tabbing in) + if (g.ActiveId == id && g.TabFocusPressed && !IsActiveIdUsingKey(ImGuiKey_Tab) && g.TabFocusRequestNextWindow == NULL) + { + g.TabFocusRequestNextWindow = window; + g.TabFocusRequestNextCounterTabStop = window->DC.FocusCounterTabStop + (g.IO.KeyShift ? (is_tab_stop ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. + } + + // Handle focus requests + if (g.TabFocusRequestCurrWindow == window) + { + if (window->DC.FocusCounterRegular == g.TabFocusRequestCurrCounterRegular) + { + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_FocusedByCode; + return; + } + if (is_tab_stop && window->DC.FocusCounterTabStop == g.TabFocusRequestCurrCounterTabStop) + { + g.NavJustTabbedId = id; + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_FocusedByTabbing; + return; + } + + // If another item is about to be focused, we clear our own active id + if (g.ActiveId == id) + ClearActiveID(); + } +} + +float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) +{ + if (wrap_pos_x < 0.0f) + return 0.0f; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (wrap_pos_x == 0.0f) + { + // We could decide to setup a default wrapping max point for auto-resizing windows, + // or have auto-wrap (with unspecified wrapping pos) behave as a ContentSize extending function? + //if (window->Hidden && (window->Flags & ImGuiWindowFlags_AlwaysAutoResize)) + // wrap_pos_x = ImMax(window->WorkRect.Min.x + g.FontSize * 10.0f, window->WorkRect.Max.x); + //else + wrap_pos_x = window->WorkRect.Max.x; + } + else if (wrap_pos_x > 0.0f) + { + wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space + } + + return ImMax(wrap_pos_x - pos.x, 1.0f); +} + +// IM_ALLOC() == ImGui::MemAlloc() +void* ImGui::MemAlloc(size_t size) +{ + if (ImGuiContext* ctx = GImGui) + ctx->IO.MetricsActiveAllocations++; + return (*GImAllocatorAllocFunc)(size, GImAllocatorUserData); +} + +// IM_FREE() == ImGui::MemFree() +void ImGui::MemFree(void* ptr) +{ + if (ptr) + if (ImGuiContext* ctx = GImGui) + ctx->IO.MetricsActiveAllocations--; + return (*GImAllocatorFreeFunc)(ptr, GImAllocatorUserData); +} + +const char* ImGui::GetClipboardText() +{ + ImGuiContext& g = *GImGui; + return g.IO.GetClipboardTextFn ? g.IO.GetClipboardTextFn(g.IO.ClipboardUserData) : ""; +} + +void ImGui::SetClipboardText(const char* text) +{ + ImGuiContext& g = *GImGui; + if (g.IO.SetClipboardTextFn) + g.IO.SetClipboardTextFn(g.IO.ClipboardUserData, text); +} + +const char* ImGui::GetVersion() +{ + return IMGUI_VERSION; +} + +// Internal state access - if you want to share Dear ImGui state between modules (e.g. DLL) or allocate it yourself +// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module +ImGuiContext* ImGui::GetCurrentContext() +{ + return GImGui; +} + +void ImGui::SetCurrentContext(ImGuiContext* ctx) +{ +#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC + IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. +#else + GImGui = ctx; +#endif +} + +void ImGui::SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data) +{ + GImAllocatorAllocFunc = alloc_func; + GImAllocatorFreeFunc = free_func; + GImAllocatorUserData = user_data; +} + +// This is provided to facilitate copying allocators from one static/DLL boundary to another (e.g. retrieve default allocator of your executable address space) +void ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data) +{ + *p_alloc_func = GImAllocatorAllocFunc; + *p_free_func = GImAllocatorFreeFunc; + *p_user_data = GImAllocatorUserData; +} + +ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) +{ + ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas); + if (GImGui == NULL) + SetCurrentContext(ctx); + Initialize(ctx); + return ctx; +} + +void ImGui::DestroyContext(ImGuiContext* ctx) +{ + if (ctx == NULL) + ctx = GImGui; + Shutdown(ctx); + if (GImGui == ctx) + SetCurrentContext(NULL); + IM_DELETE(ctx); +} + +// No specific ordering/dependency support, will see as needed +ImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook) +{ + ImGuiContext& g = *ctx; + IM_ASSERT(hook->Callback != NULL && hook->HookId == 0 && hook->Type != ImGuiContextHookType_PendingRemoval_); + g.Hooks.push_back(*hook); + g.Hooks.back().HookId = ++g.HookIdNext; + return g.HookIdNext; +} + +// Deferred removal, avoiding issue with changing vector while iterating it +void ImGui::RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_id) +{ + ImGuiContext& g = *ctx; + IM_ASSERT(hook_id != 0); + for (int n = 0; n < g.Hooks.Size; n++) + if (g.Hooks[n].HookId == hook_id) + g.Hooks[n].Type = ImGuiContextHookType_PendingRemoval_; +} + +// Call context hooks (used by e.g. test engine) +// We assume a small number of hooks so all stored in same array +void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type) +{ + ImGuiContext& g = *ctx; + for (int n = 0; n < g.Hooks.Size; n++) + if (g.Hooks[n].Type == hook_type) + g.Hooks[n].Callback(&g, &g.Hooks[n]); +} + +ImGuiIO& ImGui::GetIO() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); + return GImGui->IO; +} + +// Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame() +ImDrawData* ImGui::GetDrawData() +{ + ImGuiContext& g = *GImGui; + ImGuiViewportP* viewport = g.Viewports[0]; + return viewport->DrawDataP.Valid ? &viewport->DrawDataP : NULL; +} + +double ImGui::GetTime() +{ + return GImGui->Time; +} + +int ImGui::GetFrameCount() +{ + return GImGui->FrameCount; +} + +static ImDrawList* GetViewportDrawList(ImGuiViewportP* viewport, size_t drawlist_no, const char* drawlist_name) +{ + // Create the draw list on demand, because they are not frequently used for all viewports + ImGuiContext& g = *GImGui; + IM_ASSERT(drawlist_no < IM_ARRAYSIZE(viewport->DrawLists)); + ImDrawList* draw_list = viewport->DrawLists[drawlist_no]; + if (draw_list == NULL) + { + draw_list = IM_NEW(ImDrawList)(&g.DrawListSharedData); + draw_list->_OwnerName = drawlist_name; + viewport->DrawLists[drawlist_no] = draw_list; + } + + // Our ImDrawList system requires that there is always a command + if (viewport->DrawListsLastFrame[drawlist_no] != g.FrameCount) + { + draw_list->_ResetForNewFrame(); + draw_list->PushTextureID(g.IO.Fonts->TexID); + draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); + viewport->DrawListsLastFrame[drawlist_no] = g.FrameCount; + } + return draw_list; +} + +ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport) +{ + return GetViewportDrawList((ImGuiViewportP*)viewport, 0, "##Background"); +} + +ImDrawList* ImGui::GetBackgroundDrawList() +{ + ImGuiContext& g = *GImGui; + return GetBackgroundDrawList(g.Viewports[0]); +} + +ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport) +{ + return GetViewportDrawList((ImGuiViewportP*)viewport, 1, "##Foreground"); +} + +ImDrawList* ImGui::GetForegroundDrawList() +{ + ImGuiContext& g = *GImGui; + return GetForegroundDrawList(g.Viewports[0]); +} + +ImDrawListSharedData* ImGui::GetDrawListSharedData() +{ + return &GImGui->DrawListSharedData; +} + +void ImGui::StartMouseMovingWindow(ImGuiWindow* window) +{ + // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows. + // We _also_ call this when clicking in a window empty space when io.ConfigWindowsMoveFromTitleBarOnly is set, but clear g.MovingWindow afterward. + // This is because we want ActiveId to be set even when the window is not permitted to move. + ImGuiContext& g = *GImGui; + FocusWindow(window); + SetActiveID(window->MoveId, window); + g.NavDisableHighlight = true; + g.ActiveIdNoClearOnFocusLoss = true; + g.ActiveIdClickOffset = g.IO.MousePos - window->RootWindow->Pos; + + bool can_move_window = true; + if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindow->Flags & ImGuiWindowFlags_NoMove)) + can_move_window = false; + if (can_move_window) + g.MovingWindow = window; +} + +// Handle mouse moving window +// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing() +// FIXME: We don't have strong guarantee that g.MovingWindow stay synched with g.ActiveId == g.MovingWindow->MoveId. +// This is currently enforced by the fact that BeginDragDropSource() is setting all g.ActiveIdUsingXXXX flags to inhibit navigation inputs, +// but if we should more thoroughly test cases where g.ActiveId or g.MovingWindow gets changed and not the other. +void ImGui::UpdateMouseMovingWindowNewFrame() +{ + ImGuiContext& g = *GImGui; + if (g.MovingWindow != NULL) + { + // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window). + // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency. + KeepAliveID(g.ActiveId); + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); + ImGuiWindow* moving_window = g.MovingWindow->RootWindow; + if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos)) + { + ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; + if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y) + { + MarkIniSettingsDirty(moving_window); + SetWindowPos(moving_window, pos, ImGuiCond_Always); + } + FocusWindow(g.MovingWindow); + } + else + { + ClearActiveID(); + g.MovingWindow = NULL; + } + } + else + { + // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others. + if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId) + { + KeepAliveID(g.ActiveId); + if (!g.IO.MouseDown[0]) + ClearActiveID(); + } + } +} + +// Initiate moving window when clicking on empty space or title bar. +// Handle left-click and right-click focus. +void ImGui::UpdateMouseMovingWindowEndFrame() +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId != 0 || g.HoveredId != 0) + return; + + // Unless we just made a window/popup appear + if (g.NavWindow && g.NavWindow->Appearing) + return; + + // Click on empty space to focus window and start moving + // (after we're done with all our widgets) + if (g.IO.MouseClicked[0]) + { + // Handle the edge case of a popup being closed while clicking in its empty space. + // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more. + ImGuiWindow* root_window = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; + const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(root_window->PopupId, ImGuiPopupFlags_AnyPopupLevel); + + if (root_window != NULL && !is_closed_popup) + { + StartMouseMovingWindow(g.HoveredWindow); //-V595 + + // Cancel moving if clicked outside of title bar + if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(root_window->Flags & ImGuiWindowFlags_NoTitleBar)) + if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0])) + g.MovingWindow = NULL; + + // Cancel moving if clicked over an item which was disabled or inhibited by popups (note that we know HoveredId == 0 already) + if (g.HoveredIdDisabled) + g.MovingWindow = NULL; + } + else if (root_window == NULL && g.NavWindow != NULL && GetTopMostPopupModal() == NULL) + { + // Clicking on void disable focus + FocusWindow(NULL); + } + } + + // With right mouse button we close popups without changing focus based on where the mouse is aimed + // Instead, focus will be restored to the window under the bottom-most closed popup. + // (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger) + if (g.IO.MouseClicked[1]) + { + // Find the top-most window between HoveredWindow and the top-most Modal Window. + // This is where we can trim the popup stack. + ImGuiWindow* modal = GetTopMostPopupModal(); + bool hovered_window_above_modal = g.HoveredWindow && IsWindowAbove(g.HoveredWindow, modal); + ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal, true); + } +} + +static bool IsWindowActiveAndVisible(ImGuiWindow* window) +{ + return (window->Active) && (!window->Hidden); +} + +static void ImGui::UpdateMouseInputs() +{ + ImGuiContext& g = *GImGui; + + // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well) + if (IsMousePosValid(&g.IO.MousePos)) + g.IO.MousePos = g.LastValidMousePos = ImFloor(g.IO.MousePos); + + // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta + if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev)) + g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; + else + g.IO.MouseDelta = ImVec2(0.0f, 0.0f); + if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f) + g.NavDisableMouseHover = false; + + g.IO.MousePosPrev = g.IO.MousePos; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f; + g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; + g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; + g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; + g.IO.MouseDoubleClicked[i] = false; + if (g.IO.MouseClicked[i]) + { + if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime) + { + ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); + if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) + g.IO.MouseDoubleClicked[i] = true; + g.IO.MouseClickedTime[i] = -g.IO.MouseDoubleClickTime * 2.0f; // Mark as "old enough" so the third click isn't turned into a double-click + } + else + { + g.IO.MouseClickedTime[i] = g.Time; + } + g.IO.MouseClickedPos[i] = g.IO.MousePos; + g.IO.MouseDownWasDoubleClick[i] = g.IO.MouseDoubleClicked[i]; + g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); + g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; + } + else if (g.IO.MouseDown[i]) + { + // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold + ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); + g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos)); + g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x); + g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y); + } + if (!g.IO.MouseDown[i] && !g.IO.MouseReleased[i]) + g.IO.MouseDownWasDoubleClick[i] = false; + if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + g.NavDisableMouseHover = false; + } +} + +static void StartLockWheelingWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.WheelingWindow == window) + return; + g.WheelingWindow = window; + g.WheelingWindowRefMousePos = g.IO.MousePos; + g.WheelingWindowTimer = WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER; +} + +void ImGui::UpdateMouseWheel() +{ + ImGuiContext& g = *GImGui; + + // Reset the locked window if we move the mouse or after the timer elapses + if (g.WheelingWindow != NULL) + { + g.WheelingWindowTimer -= g.IO.DeltaTime; + if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold) + g.WheelingWindowTimer = 0.0f; + if (g.WheelingWindowTimer <= 0.0f) + { + g.WheelingWindow = NULL; + g.WheelingWindowTimer = 0.0f; + } + } + + if (g.IO.MouseWheel == 0.0f && g.IO.MouseWheelH == 0.0f) + return; + + if ((g.ActiveId != 0 && g.ActiveIdUsingMouseWheel) || (g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrameUsingMouseWheel)) + return; + + ImGuiWindow* window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow; + if (!window || window->Collapsed) + return; + + // Zoom / Scale window + // FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned. + if (g.IO.MouseWheel != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling) + { + StartLockWheelingWindow(window); + const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); + const float scale = new_font_scale / window->FontWindowScale; + window->FontWindowScale = new_font_scale; + if (window == window->RootWindow) + { + const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; + SetWindowPos(window, window->Pos + offset, 0); + window->Size = ImFloor(window->Size * scale); + window->SizeFull = ImFloor(window->SizeFull * scale); + } + return; + } + + // Mouse wheel scrolling + // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent + if (g.IO.KeyCtrl) + return; + + // As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead + // (we avoid doing it on OSX as it the OS input layer handles this already) + const bool swap_axis = g.IO.KeyShift && !g.IO.ConfigMacOSXBehaviors; + const float wheel_y = swap_axis ? 0.0f : g.IO.MouseWheel; + const float wheel_x = swap_axis ? g.IO.MouseWheel : g.IO.MouseWheelH; + + // Vertical Mouse Wheel scrolling + if (wheel_y != 0.0f) + { + StartLockWheelingWindow(window); + while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.y == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) + window = window->ParentWindow; + if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) + { + float max_step = window->InnerRect.GetHeight() * 0.67f; + float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step)); + SetScrollY(window, window->Scroll.y - wheel_y * scroll_step); + } + } + + // Horizontal Mouse Wheel scrolling, or Vertical Mouse Wheel w/ Shift held + if (wheel_x != 0.0f) + { + StartLockWheelingWindow(window); + while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.x == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) + window = window->ParentWindow; + if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) + { + float max_step = window->InnerRect.GetWidth() * 0.67f; + float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step)); + SetScrollX(window, window->Scroll.x - wheel_x * scroll_step); + } + } +} + +void ImGui::UpdateTabFocus() +{ + ImGuiContext& g = *GImGui; + + // Pressing TAB activate widget focus + g.TabFocusPressed = (g.NavWindow && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)); + if (g.ActiveId == 0 && g.TabFocusPressed) + { + // - This path is only taken when no widget are active/tabbed-into yet. + // Subsequent tabbing will be processed by FocusableItemRegister() + // - Note that SetKeyboardFocusHere() sets the Next fields mid-frame. To be consistent we also + // manipulate the Next fields here even though they will be turned into Curr fields below. + g.TabFocusRequestNextWindow = g.NavWindow; + g.TabFocusRequestNextCounterRegular = INT_MAX; + if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX) + g.TabFocusRequestNextCounterTabStop = g.NavIdTabCounter + (g.IO.KeyShift ? -1 : 0); + else + g.TabFocusRequestNextCounterTabStop = g.IO.KeyShift ? -1 : 0; + } + + // Turn queued focus request into current one + g.TabFocusRequestCurrWindow = NULL; + g.TabFocusRequestCurrCounterRegular = g.TabFocusRequestCurrCounterTabStop = INT_MAX; + if (g.TabFocusRequestNextWindow != NULL) + { + ImGuiWindow* window = g.TabFocusRequestNextWindow; + g.TabFocusRequestCurrWindow = window; + if (g.TabFocusRequestNextCounterRegular != INT_MAX && window->DC.FocusCounterRegular != -1) + g.TabFocusRequestCurrCounterRegular = ImModPositive(g.TabFocusRequestNextCounterRegular, window->DC.FocusCounterRegular + 1); + if (g.TabFocusRequestNextCounterTabStop != INT_MAX && window->DC.FocusCounterTabStop != -1) + g.TabFocusRequestCurrCounterTabStop = ImModPositive(g.TabFocusRequestNextCounterTabStop, window->DC.FocusCounterTabStop + 1); + g.TabFocusRequestNextWindow = NULL; + g.TabFocusRequestNextCounterRegular = g.TabFocusRequestNextCounterTabStop = INT_MAX; + } + + g.NavIdTabCounter = INT_MAX; +} + +// The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app) +void ImGui::UpdateHoveredWindowAndCaptureFlags() +{ + ImGuiContext& g = *GImGui; + g.WindowsHoverPadding = ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_HOVER_PADDING, WINDOWS_HOVER_PADDING)); + + // Find the window hovered by mouse: + // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. + // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point of the frame. + // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. + bool clear_hovered_windows = false; + FindHoveredWindow(); + + // Modal windows prevents mouse from hovering behind them. + ImGuiWindow* modal_window = GetTopMostPopupModal(); + if (modal_window && g.HoveredWindow && !IsWindowChildOf(g.HoveredWindow->RootWindow, modal_window)) + clear_hovered_windows = true; + + // Disabled mouse? + if (g.IO.ConfigFlags & ImGuiConfigFlags_NoMouse) + clear_hovered_windows = true; + + // We track click ownership. When clicked outside of a window the click is owned by the application and won't report hovering nor request capture even while dragging over our windows afterward. + int mouse_earliest_button_down = -1; + bool mouse_any_down = false; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + if (g.IO.MouseClicked[i]) + g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (g.OpenPopupStack.Size > 0); + mouse_any_down |= g.IO.MouseDown[i]; + if (g.IO.MouseDown[i]) + if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down]) + mouse_earliest_button_down = i; + } + const bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down]; + + // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. + // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) + const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0; + if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload) + clear_hovered_windows = true; + + if (clear_hovered_windows) + g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; + + // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse info to Dear ImGui + app) + if (g.WantCaptureMouseNextFrame != -1) + g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0); + else + g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (g.OpenPopupStack.Size > 0); + + // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to imgui, false = dispatch keyboard info to Dear ImGui + app) + if (g.WantCaptureKeyboardNextFrame != -1) + g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); + else + g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); + if (g.IO.NavActive && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard)) + g.IO.WantCaptureKeyboard = true; + + // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible + g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; +} + +ImGuiKeyModFlags ImGui::GetMergedKeyModFlags() +{ + ImGuiContext& g = *GImGui; + ImGuiKeyModFlags key_mod_flags = ImGuiKeyModFlags_None; + if (g.IO.KeyCtrl) { key_mod_flags |= ImGuiKeyModFlags_Ctrl; } + if (g.IO.KeyShift) { key_mod_flags |= ImGuiKeyModFlags_Shift; } + if (g.IO.KeyAlt) { key_mod_flags |= ImGuiKeyModFlags_Alt; } + if (g.IO.KeySuper) { key_mod_flags |= ImGuiKeyModFlags_Super; } + return key_mod_flags; +} + +void ImGui::NewFrame() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); + ImGuiContext& g = *GImGui; + + // Remove pending delete hooks before frame start. + // This deferred removal avoid issues of removal while iterating the hook vector + for (int n = g.Hooks.Size - 1; n >= 0; n--) + if (g.Hooks[n].Type == ImGuiContextHookType_PendingRemoval_) + g.Hooks.erase(&g.Hooks[n]); + + CallContextHooks(&g, ImGuiContextHookType_NewFramePre); + + // Check and assert for various common IO and Configuration mistakes + ErrorCheckNewFrameSanityChecks(); + + // Load settings on first frame, save settings when modified (after a delay) + UpdateSettings(); + + g.Time += g.IO.DeltaTime; + g.WithinFrameScope = true; + g.FrameCount += 1; + g.TooltipOverrideCount = 0; + g.WindowsActiveCount = 0; + g.MenusIdSubmittedThisFrame.resize(0); + + // Calculate frame-rate for the user, as a purely luxurious feature + g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; + g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; + g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); + g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(g.FramerateSecPerFrame)); + g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)g.FramerateSecPerFrameCount)) : FLT_MAX; + + UpdateViewportsNewFrame(); + + // Setup current font and draw list shared data + g.IO.Fonts->Locked = true; + SetCurrentFont(GetDefaultFont()); + IM_ASSERT(g.Font->IsLoaded()); + ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + for (int n = 0; n < g.Viewports.Size; n++) + virtual_space.Add(g.Viewports[n]->GetMainRect()); + g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); + g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); + g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; + if (g.Style.AntiAliasedLines) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; + if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; + if (g.Style.AntiAliasedFill) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; + + // Mark rendering data as invalid to prevent user who may have a handle on it to use it. + for (int n = 0; n < g.Viewports.Size; n++) + { + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->DrawDataP.Clear(); + } + + // Drag and drop keep the source ID alive so even if the source disappear our state is consistent + if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId) + KeepAliveID(g.DragDropPayload.SourceId); + + // Update HoveredId data + if (!g.HoveredIdPreviousFrame) + g.HoveredIdTimer = 0.0f; + if (!g.HoveredIdPreviousFrame || (g.HoveredId && g.ActiveId == g.HoveredId)) + g.HoveredIdNotActiveTimer = 0.0f; + if (g.HoveredId) + g.HoveredIdTimer += g.IO.DeltaTime; + if (g.HoveredId && g.ActiveId != g.HoveredId) + g.HoveredIdNotActiveTimer += g.IO.DeltaTime; + g.HoveredIdPreviousFrame = g.HoveredId; + g.HoveredIdPreviousFrameUsingMouseWheel = g.HoveredIdUsingMouseWheel; + g.HoveredId = 0; + g.HoveredIdAllowOverlap = false; + g.HoveredIdUsingMouseWheel = false; + g.HoveredIdDisabled = false; + + // Update ActiveId data (clear reference to active widget if the widget isn't alive anymore) + if (g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) + ClearActiveID(); + if (g.ActiveId) + g.ActiveIdTimer += g.IO.DeltaTime; + g.LastActiveIdTimer += g.IO.DeltaTime; + g.ActiveIdPreviousFrame = g.ActiveId; + g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow; + g.ActiveIdPreviousFrameHasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore; + g.ActiveIdIsAlive = 0; + g.ActiveIdHasBeenEditedThisFrame = false; + g.ActiveIdPreviousFrameIsAlive = false; + g.ActiveIdIsJustActivated = false; + if (g.TempInputId != 0 && g.ActiveId != g.TempInputId) + g.TempInputId = 0; + if (g.ActiveId == 0) + { + g.ActiveIdUsingNavDirMask = 0x00; + g.ActiveIdUsingNavInputMask = 0x00; + g.ActiveIdUsingKeyInputMask = 0x00; + } + + // Drag and drop + g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; + g.DragDropAcceptIdCurr = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropWithinSource = false; + g.DragDropWithinTarget = false; + g.DragDropHoldJustPressedId = 0; + + // Update keyboard input state + // Synchronize io.KeyMods with individual modifiers io.KeyXXX bools + g.IO.KeyMods = GetMergedKeyModFlags(); + memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) + g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Update gamepad/keyboard navigation + NavUpdate(); + + // Update mouse input state + UpdateMouseInputs(); + + // Find hovered window + // (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame) + UpdateHoveredWindowAndCaptureFlags(); + + // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) + UpdateMouseMovingWindowNewFrame(); + + // Background darkening/whitening + if (GetTopMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f)) + g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f); + else + g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f); + + g.MouseCursor = ImGuiMouseCursor_Arrow; + g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; + g.PlatformImePos = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default + + // Mouse wheel scrolling, scale + UpdateMouseWheel(); + + // Update legacy TAB focus + UpdateTabFocus(); + + // Mark all windows as not visible and compact unused memory. + IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size); + const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer; + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + window->WasActive = window->Active; + window->BeginCount = 0; + window->Active = false; + window->WriteAccessed = false; + + // Garbage collect transient buffers of recently unused windows + if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time) + GcCompactTransientWindowBuffers(window); + } + + // Garbage collect transient buffers of recently unused tables + for (int i = 0; i < g.TablesLastTimeActive.Size; i++) + if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time) + TableGcCompactTransientBuffers(g.Tables.GetByIndex(i)); + for (int i = 0; i < g.TablesTempDataStack.Size; i++) + if (g.TablesTempDataStack[i].LastTimeActive >= 0.0f && g.TablesTempDataStack[i].LastTimeActive < memory_compact_start_time) + TableGcCompactTransientBuffers(&g.TablesTempDataStack[i]); + if (g.GcCompactAll) + GcCompactTransientMiscBuffers(); + g.GcCompactAll = false; + + // Closing the focused window restore focus to the first active root window in descending z-order + if (g.NavWindow && !g.NavWindow->WasActive) + FocusTopMostWindowUnderOne(NULL, NULL); + + // No window should be open at the beginning of the frame. + // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. + g.CurrentWindowStack.resize(0); + g.BeginPopupStack.resize(0); + g.ItemFlagsStack.resize(0); + g.ItemFlagsStack.push_back(ImGuiItemFlags_None); + g.GroupStack.resize(0); + ClosePopupsOverWindow(g.NavWindow, false); + + // [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack. + UpdateDebugToolItemPicker(); + + // Create implicit/fallback window - which we will only render it if the user has added something to it. + // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. + // This fallback is particularly important as it avoid ImGui:: calls from crashing. + g.WithinFrameScopeWithImplicitWindow = true; + SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); + Begin("Debug##Default"); + IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true); + + CallContextHooks(&g, ImGuiContextHookType_NewFramePost); +} + +// [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack. +void ImGui::UpdateDebugToolItemPicker() +{ + ImGuiContext& g = *GImGui; + g.DebugItemPickerBreakId = 0; + if (g.DebugItemPickerActive) + { + const ImGuiID hovered_id = g.HoveredIdPreviousFrame; + SetMouseCursor(ImGuiMouseCursor_Hand); + if (IsKeyPressedMap(ImGuiKey_Escape)) + g.DebugItemPickerActive = false; + if (IsMouseClicked(0) && hovered_id) + { + g.DebugItemPickerBreakId = hovered_id; + g.DebugItemPickerActive = false; + } + SetNextWindowBgAlpha(0.60f); + BeginTooltip(); + Text("HoveredId: 0x%08X", hovered_id); + Text("Press ESC to abort picking."); + TextColored(GetStyleColorVec4(hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled), "Click to break in debugger!"); + EndTooltip(); + } +} + +void ImGui::Initialize(ImGuiContext* context) +{ + ImGuiContext& g = *context; + IM_ASSERT(!g.Initialized && !g.SettingsLoaded); + + // Add .ini handle for ImGuiWindow type + { + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Window"; + ini_handler.TypeHash = ImHashStr("Window"); + ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll; + ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen; + ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine; + ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll; + ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll; + g.SettingsHandlers.push_back(ini_handler); + } + + // Add .ini handle for ImGuiTable type + TableSettingsInstallHandler(context); + + // Create default viewport + ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); + g.Viewports.push_back(viewport); + +#ifdef IMGUI_HAS_DOCK +#endif // #ifdef IMGUI_HAS_DOCK + + g.Initialized = true; +} + +// This function is merely here to free heap allocations. +void ImGui::Shutdown(ImGuiContext* context) +{ + // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + ImGuiContext& g = *context; + if (g.IO.Fonts && g.FontAtlasOwnedByContext) + { + g.IO.Fonts->Locked = false; + IM_DELETE(g.IO.Fonts); + } + g.IO.Fonts = NULL; + + // Cleanup of other data are conditional on actually having initialized Dear ImGui. + if (!g.Initialized) + return; + + // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file) + if (g.SettingsLoaded && g.IO.IniFilename != NULL) + { + ImGuiContext* backup_context = GImGui; + SetCurrentContext(&g); + SaveIniSettingsToDisk(g.IO.IniFilename); + SetCurrentContext(backup_context); + } + + CallContextHooks(&g, ImGuiContextHookType_Shutdown); + + // Clear everything else + for (int i = 0; i < g.Windows.Size; i++) + IM_DELETE(g.Windows[i]); + g.Windows.clear(); + g.WindowsFocusOrder.clear(); + g.WindowsTempSortBuffer.clear(); + g.CurrentWindow = NULL; + g.CurrentWindowStack.clear(); + g.WindowsById.Clear(); + g.NavWindow = NULL; + g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; + g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; + g.MovingWindow = NULL; + g.ColorStack.clear(); + g.StyleVarStack.clear(); + g.FontStack.clear(); + g.OpenPopupStack.clear(); + g.BeginPopupStack.clear(); + + for (int i = 0; i < g.Viewports.Size; i++) + IM_DELETE(g.Viewports[i]); + g.Viewports.clear(); + + g.TabBars.Clear(); + g.CurrentTabBarStack.clear(); + g.ShrinkWidthBuffer.clear(); + + g.Tables.Clear(); + for (int i = 0; i < g.TablesTempDataStack.Size; i++) + g.TablesTempDataStack[i].~ImGuiTableTempData(); + g.TablesTempDataStack.clear(); + g.DrawChannelsTempMergeBuffer.clear(); + + g.ClipboardHandlerData.clear(); + g.MenusIdSubmittedThisFrame.clear(); + g.InputTextState.ClearFreeMemory(); + + g.SettingsWindows.clear(); + g.SettingsHandlers.clear(); + + if (g.LogFile) + { +#ifndef IMGUI_DISABLE_TTY_FUNCTIONS + if (g.LogFile != stdout) +#endif + ImFileClose(g.LogFile); + g.LogFile = NULL; + } + g.LogBuffer.clear(); + + g.Initialized = false; +} + +// FIXME: Add a more explicit sort order in the window structure. +static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) +{ + const ImGuiWindow* const a = *(const ImGuiWindow* const *)lhs; + const ImGuiWindow* const b = *(const ImGuiWindow* const *)rhs; + if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup)) + return d; + if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) + return d; + return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); +} + +static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) +{ + out_sorted_windows->push_back(window); + if (window->Active) + { + int count = window->DC.ChildWindows.Size; + if (count > 1) + ImQsort(window->DC.ChildWindows.Data, (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer); + for (int i = 0; i < count; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (child->Active) + AddWindowToSortBuffer(out_sorted_windows, child); + } + } +} + +static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list) +{ + // Remove trailing command if unused. + // Technically we could return directly instead of popping, but this make things looks neat in Metrics/Debugger window as well. + draw_list->_PopUnusedDrawCmd(); + if (draw_list->CmdBuffer.Size == 0) + return; + + // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. + // May trigger for you if you are using PrimXXX functions incorrectly. + IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); + IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); + if (!(draw_list->Flags & ImDrawListFlags_AllowVtxOffset)) + IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); + + // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) + // If this assert triggers because you are drawing lots of stuff manually: + // - First, make sure you are coarse clipping yourself and not trying to draw many things outside visible bounds. + // Be mindful that the ImDrawList API doesn't filter vertices. Use the Metrics/Debugger window to inspect draw list contents. + // - If you want large meshes with more than 64K vertices, you can either: + // (A) Handle the ImDrawCmd::VtxOffset value in your renderer backend, and set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset'. + // Most example backends already support this from 1.71. Pre-1.71 backends won't. + // Some graphics API such as GL ES 1/2 don't have a way to offset the starting vertex so it is not supported for them. + // (B) Or handle 32-bit indices in your renderer backend, and uncomment '#define ImDrawIdx unsigned int' line in imconfig.h. + // Most example backends already support this. For example, the OpenGL example code detect index size at compile-time: + // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + // Your own engine or render API may use different parameters or function calls to specify index sizes. + // 2 and 4 bytes indices are generally supported by most graphics API. + // - If for some reason neither of those solutions works for you, a workaround is to call BeginChild()/EndChild() before reaching + // the 64K limit to split your draw commands in multiple draw lists. + if (sizeof(ImDrawIdx) == 2) + IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); + + out_list->push_back(draw_list); +} + +static void AddWindowToDrawData(ImGuiWindow* window, int layer) +{ + ImGuiContext& g = *GImGui; + ImGuiViewportP* viewport = g.Viewports[0]; + g.IO.MetricsRenderWindows++; + AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[layer], window->DrawList); + for (int i = 0; i < window->DC.ChildWindows.Size; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (IsWindowActiveAndVisible(child)) // Clipped children may have been marked not active + AddWindowToDrawData(child, layer); + } +} + +// Layer is locked for the root window, however child windows may use a different viewport (e.g. extruding menu) +static void AddRootWindowToDrawData(ImGuiWindow* window) +{ + int layer = (window->Flags & ImGuiWindowFlags_Tooltip) ? 1 : 0; + AddWindowToDrawData(window, layer); +} + +void ImDrawDataBuilder::FlattenIntoSingleLayer() +{ + int n = Layers[0].Size; + int size = n; + for (int i = 1; i < IM_ARRAYSIZE(Layers); i++) + size += Layers[i].Size; + Layers[0].resize(size); + for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++) + { + ImVector& layer = Layers[layer_n]; + if (layer.empty()) + continue; + memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*)); + n += layer.Size; + layer.resize(0); + } +} + +static void SetupViewportDrawData(ImGuiViewportP* viewport, ImVector* draw_lists) +{ + ImGuiIO& io = ImGui::GetIO(); + ImDrawData* draw_data = &viewport->DrawDataP; + draw_data->Valid = true; + draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL; + draw_data->CmdListsCount = draw_lists->Size; + draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; + draw_data->DisplayPos = viewport->Pos; + draw_data->DisplaySize = viewport->Size; + draw_data->FramebufferScale = io.DisplayFramebufferScale; + for (int n = 0; n < draw_lists->Size; n++) + { + draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size; + draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size; + } +} + +// Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering. +// - When using this function it is sane to ensure that float are perfectly rounded to integer values, +// so that e.g. (int)(max.x-min.x) in user's render produce correct result. +// - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect(): +// some frequently called functions which to modify both channels and clipping simultaneously tend to use the +// more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds. +void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +void ImGui::PopClipRect() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PopClipRect(); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal. +void ImGui::EndFrame() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); + + // Don't process EndFrame() multiple times. + if (g.FrameCountEnded == g.FrameCount) + return; + IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?"); + + CallContextHooks(&g, ImGuiContextHookType_EndFramePre); + + ErrorCheckEndFrameSanityChecks(); + + // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) + if (g.IO.ImeSetInputScreenPosFn && (g.PlatformImeLastPos.x == FLT_MAX || ImLengthSqr(g.PlatformImeLastPos - g.PlatformImePos) > 0.0001f)) + { + g.IO.ImeSetInputScreenPosFn((int)g.PlatformImePos.x, (int)g.PlatformImePos.y); + g.PlatformImeLastPos = g.PlatformImePos; + } + + // Hide implicit/fallback "Debug" window if it hasn't been used + g.WithinFrameScopeWithImplicitWindow = false; + if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) + g.CurrentWindow->Active = false; + End(); + + // Update navigation: CTRL+Tab, wrap-around requests + NavEndFrame(); + + // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) + if (g.DragDropActive) + { + bool is_delivered = g.DragDropPayload.Delivery; + bool is_elapsed = (g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceAutoExpirePayload) || !IsMouseDown(g.DragDropMouseButton)); + if (is_delivered || is_elapsed) + ClearDragDrop(); + } + + // Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing. + if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + g.DragDropWithinSource = true; + SetTooltip("..."); + g.DragDropWithinSource = false; + } + + // End frame + g.WithinFrameScope = false; + g.FrameCountEnded = g.FrameCount; + + // Initiate moving window + handle left-click and right-click focus + UpdateMouseMovingWindowEndFrame(); + + // Sort the window list so that all child windows are after their parent + // We cannot do that on FocusWindow() because children may not exist yet + g.WindowsTempSortBuffer.resize(0); + g.WindowsTempSortBuffer.reserve(g.Windows.Size); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it + continue; + AddWindowToSortBuffer(&g.WindowsTempSortBuffer, window); + } + + // This usually assert if there is a mismatch between the ImGuiWindowFlags_ChildWindow / ParentWindow values and DC.ChildWindows[] in parents, aka we've done something wrong. + IM_ASSERT(g.Windows.Size == g.WindowsTempSortBuffer.Size); + g.Windows.swap(g.WindowsTempSortBuffer); + g.IO.MetricsActiveWindows = g.WindowsActiveCount; + + // Unlock font atlas + g.IO.Fonts->Locked = false; + + // Clear Input data for next frame + g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f; + g.IO.InputQueueCharacters.resize(0); + memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs)); + + CallContextHooks(&g, ImGuiContextHookType_EndFramePost); +} + +void ImGui::Render() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); + + if (g.FrameCountEnded != g.FrameCount) + EndFrame(); + g.FrameCountRendered = g.FrameCount; + g.IO.MetricsRenderWindows = 0; + + CallContextHooks(&g, ImGuiContextHookType_RenderPre); + + // Add background ImDrawList (for each active viewport) + for (int n = 0; n != g.Viewports.Size; n++) + { + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->DrawDataBuilder.Clear(); + if (viewport->DrawLists[0] != NULL) + AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport)); + } + + // Add ImDrawList to render + ImGuiWindow* windows_to_render_top_most[2]; + windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL; + windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL); + for (int n = 0; n != g.Windows.Size; n++) + { + ImGuiWindow* window = g.Windows[n]; + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" + if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_top_most[0] && window != windows_to_render_top_most[1]) + AddRootWindowToDrawData(window); + } + for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_top_most); n++) + if (windows_to_render_top_most[n] && IsWindowActiveAndVisible(windows_to_render_top_most[n])) // NavWindowingTarget is always temporarily displayed as the top-most window + AddRootWindowToDrawData(windows_to_render_top_most[n]); + + // Setup ImDrawData structures for end-user + g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = 0; + for (int n = 0; n < g.Viewports.Size; n++) + { + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->DrawDataBuilder.FlattenIntoSingleLayer(); + + // Draw software mouse cursor if requested by io.MouseDrawCursor flag + if (g.IO.MouseDrawCursor) + RenderMouseCursor(GetForegroundDrawList(viewport), g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor, IM_COL32_WHITE, IM_COL32_BLACK, IM_COL32(0, 0, 0, 48)); + + // Add foreground ImDrawList (for each active viewport) + if (viewport->DrawLists[1] != NULL) + AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[0], GetForegroundDrawList(viewport)); + + SetupViewportDrawData(viewport, &viewport->DrawDataBuilder.Layers[0]); + ImDrawData* draw_data = &viewport->DrawDataP; + g.IO.MetricsRenderVertices += draw_data->TotalVtxCount; + g.IO.MetricsRenderIndices += draw_data->TotalIdxCount; + } + + CallContextHooks(&g, ImGuiContextHookType_RenderPost); +} + +// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. +// CalcTextSize("") should return ImVec2(0.0f, g.FontSize) +ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) +{ + ImGuiContext& g = *GImGui; + + const char* text_display_end; + if (hide_text_after_double_hash) + text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string + else + text_display_end = text_end; + + ImFont* font = g.Font; + const float font_size = g.FontSize; + if (text == text_display_end) + return ImVec2(0.0f, font_size); + ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); + + // Round + // FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out. + // FIXME: Investigate using ceilf or e.g. + // - https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c + // - https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html + text_size.x = IM_FLOOR(text_size.x + 0.99999f); + + return text_size; +} + +// Find window given position, search front-to-back +// FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programmatically +// with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is +// called, aka before the next Begin(). Moving window isn't affected. +static void FindHoveredWindow() +{ + ImGuiContext& g = *GImGui; + + ImGuiWindow* hovered_window = NULL; + ImGuiWindow* hovered_window_ignoring_moving_window = NULL; + if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs)) + hovered_window = g.MovingWindow; + + ImVec2 padding_regular = g.Style.TouchExtraPadding; + ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular; + for (int i = g.Windows.Size - 1; i >= 0; i--) + { + ImGuiWindow* window = g.Windows[i]; + IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. + if (!window->Active || window->Hidden) + continue; + if (window->Flags & ImGuiWindowFlags_NoMouseInputs) + continue; + + // Using the clipped AABB, a child window will typically be clipped by its parent (not always) + ImRect bb(window->OuterRectClipped); + if (window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) + bb.Expand(padding_regular); + else + bb.Expand(padding_for_resize); + if (!bb.Contains(g.IO.MousePos)) + continue; + + // Support for one rectangular hole in any given window + // FIXME: Consider generalizing hit-testing override (with more generic data, callback, etc.) (#1512) + if (window->HitTestHoleSize.x != 0) + { + ImVec2 hole_pos(window->Pos.x + (float)window->HitTestHoleOffset.x, window->Pos.y + (float)window->HitTestHoleOffset.y); + ImVec2 hole_size((float)window->HitTestHoleSize.x, (float)window->HitTestHoleSize.y); + if (ImRect(hole_pos, hole_pos + hole_size).Contains(g.IO.MousePos)) + continue; + } + + if (hovered_window == NULL) + hovered_window = window; + IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. + if (hovered_window_ignoring_moving_window == NULL && (!g.MovingWindow || window->RootWindow != g.MovingWindow->RootWindow)) + hovered_window_ignoring_moving_window = window; + if (hovered_window && hovered_window_ignoring_moving_window) + break; + } + + g.HoveredWindow = hovered_window; + g.HoveredWindowUnderMovingWindow = hovered_window_ignoring_moving_window; +} + +// Test if mouse cursor is hovering given rectangle +// NB- Rectangle is clipped by our current clip setting +// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) +bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) +{ + ImGuiContext& g = *GImGui; + + // Clip + ImRect rect_clipped(r_min, r_max); + if (clip) + rect_clipped.ClipWith(g.CurrentWindow->ClipRect); + + // Expand for touch input + const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); + if (!rect_for_touch.Contains(g.IO.MousePos)) + return false; + return true; +} + +int ImGui::GetKeyIndex(ImGuiKey imgui_key) +{ + IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT); + ImGuiContext& g = *GImGui; + return g.IO.KeyMap[imgui_key]; +} + +// Note that dear imgui doesn't know the semantic of each entry of io.KeysDown[]! +// Use your own indices/enums according to how your backend/engine stored them into io.KeysDown[]! +bool ImGui::IsKeyDown(int user_key_index) +{ + if (user_key_index < 0) + return false; + ImGuiContext& g = *GImGui; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + return g.IO.KeysDown[user_key_index]; +} + +// t0 = previous time (e.g.: g.Time - g.IO.DeltaTime) +// t1 = current time (e.g.: g.Time) +// An event is triggered at: +// t = 0.0f t = repeat_delay, t = repeat_delay + repeat_rate*N +int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate) +{ + if (t1 == 0.0f) + return 1; + if (t0 >= t1) + return 0; + if (repeat_rate <= 0.0f) + return (t0 < repeat_delay) && (t1 >= repeat_delay); + const int count_t0 = (t0 < repeat_delay) ? -1 : (int)((t0 - repeat_delay) / repeat_rate); + const int count_t1 = (t1 < repeat_delay) ? -1 : (int)((t1 - repeat_delay) / repeat_rate); + const int count = count_t1 - count_t0; + return count; +} + +int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate) +{ + ImGuiContext& g = *GImGui; + if (key_index < 0) + return 0; + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[key_index]; + return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate); +} + +bool ImGui::IsKeyPressed(int user_key_index, bool repeat) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) + return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[user_key_index]; + if (t == 0.0f) + return true; + if (repeat && t > g.IO.KeyRepeatDelay) + return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; + return false; +} + +bool ImGui::IsKeyReleased(int user_key_index) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index]; +} + +bool ImGui::IsMouseDown(ImGuiMouseButton button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDown[button]; +} + +bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + const float t = g.IO.MouseDownDuration[button]; + if (t == 0.0f) + return true; + + if (repeat && t > g.IO.KeyRepeatDelay) + { + // FIXME: 2019/05/03: Our old repeat code was wrong here and led to doubling the repeat rate, which made it an ok rate for repeat on mouse hold. + int amount = CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate * 0.50f); + if (amount > 0) + return true; + } + return false; +} + +bool ImGui::IsMouseReleased(ImGuiMouseButton button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseReleased[button]; +} + +bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDoubleClicked[button]; +} + +// Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame. +// [Internal] This doesn't test if the button is pressed +bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; +} + +bool ImGui::IsMouseDragging(ImGuiMouseButton button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (!g.IO.MouseDown[button]) + return false; + return IsMouseDragPastThreshold(button, lock_threshold); +} + +ImVec2 ImGui::GetMousePos() +{ + ImGuiContext& g = *GImGui; + return g.IO.MousePos; +} + +// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! +ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() +{ + ImGuiContext& g = *GImGui; + if (g.BeginPopupStack.Size > 0) + return g.OpenPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos; + return g.IO.MousePos; +} + +// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position. +bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) +{ + // The assert is only to silence a false-positive in XCode Static Analysis. + // Because GImGui is not dereferenced in every code path, the static analyzer assume that it may be NULL (which it doesn't for other functions). + IM_ASSERT(GImGui != NULL); + const float MOUSE_INVALID = -256000.0f; + ImVec2 p = mouse_pos ? *mouse_pos : GImGui->IO.MousePos; + return p.x >= MOUSE_INVALID && p.y >= MOUSE_INVALID; +} + +bool ImGui::IsAnyMouseDown() +{ + ImGuiContext& g = *GImGui; + for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++) + if (g.IO.MouseDown[n]) + return true; + return false; +} + +// Return the delta from the initial clicking position while the mouse button is clicked or was just released. +// This is locked and return 0.0f until the mouse moves past a distance threshold at least once. +// NB: This is only valid if IsMousePosValid(). backends in theory should always keep mouse position valid when dragging even outside the client window. +ImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + if (g.IO.MouseDown[button] || g.IO.MouseReleased[button]) + if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold) + if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MouseClickedPos[button])) + return g.IO.MousePos - g.IO.MouseClickedPos[button]; + return ImVec2(0.0f, 0.0f); +} + +void ImGui::ResetMouseDragDelta(ImGuiMouseButton button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr + g.IO.MouseClickedPos[button] = g.IO.MousePos; +} + +ImGuiMouseCursor ImGui::GetMouseCursor() +{ + return GImGui->MouseCursor; +} + +void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) +{ + GImGui->MouseCursor = cursor_type; +} + +void ImGui::CaptureKeyboardFromApp(bool capture) +{ + GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0; +} + +void ImGui::CaptureMouseFromApp(bool capture) +{ + GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0; +} + +bool ImGui::IsItemActive() +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId) + { + ImGuiWindow* window = g.CurrentWindow; + return g.ActiveId == window->DC.LastItemId; + } + return false; +} + +bool ImGui::IsItemActivated() +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId) + { + ImGuiWindow* window = g.CurrentWindow; + if (g.ActiveId == window->DC.LastItemId && g.ActiveIdPreviousFrame != window->DC.LastItemId) + return true; + } + return false; +} + +bool ImGui::IsItemDeactivated() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDeactivated) + return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; + return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId); +} + +bool ImGui::IsItemDeactivatedAfterEdit() +{ + ImGuiContext& g = *GImGui; + return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore)); +} + +// == GetItemID() == GetFocusID() +bool ImGui::IsItemFocused() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (g.NavId != window->DC.LastItemId || g.NavId == 0) + return false; + return true; +} + +// Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()! +// Most widgets have specific reactions based on mouse-up/down state, mouse position etc. +bool ImGui::IsItemClicked(ImGuiMouseButton mouse_button) +{ + return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None); +} + +bool ImGui::IsItemToggledOpen() +{ + ImGuiContext& g = *GImGui; + return (g.CurrentWindow->DC.LastItemStatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false; +} + +bool ImGui::IsItemToggledSelection() +{ + ImGuiContext& g = *GImGui; + return (g.CurrentWindow->DC.LastItemStatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false; +} + +bool ImGui::IsAnyItemHovered() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0; +} + +bool ImGui::IsAnyItemActive() +{ + ImGuiContext& g = *GImGui; + return g.ActiveId != 0; +} + +bool ImGui::IsAnyItemFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavId != 0 && !g.NavDisableHighlight; +} + +bool ImGui::IsItemVisible() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(window->DC.LastItemRect); +} + +bool ImGui::IsItemEdited() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Edited) != 0; +} + +// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. +// FIXME: Although this is exposed, its interaction and ideal idiom with using ImGuiButtonFlags_AllowItemOverlap flag are extremely confusing, need rework. +void ImGui::SetItemAllowOverlap() +{ + ImGuiContext& g = *GImGui; + ImGuiID id = g.CurrentWindow->DC.LastItemId; + if (g.HoveredId == id) + g.HoveredIdAllowOverlap = true; + if (g.ActiveId == id) + g.ActiveIdAllowOverlap = true; +} + +void ImGui::SetItemUsingMouseWheel() +{ + ImGuiContext& g = *GImGui; + ImGuiID id = g.CurrentWindow->DC.LastItemId; + if (g.HoveredId == id) + g.HoveredIdUsingMouseWheel = true; + if (g.ActiveId == id) + g.ActiveIdUsingMouseWheel = true; +} + +ImVec2 ImGui::GetItemRectMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Min; +} + +ImVec2 ImGui::GetItemRectMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Max; +} + +ImVec2 ImGui::GetItemRectSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.GetSize(); +} + +bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = g.CurrentWindow; + + flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_ChildWindow; + flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag + + // Size + const ImVec2 content_avail = GetContentRegionAvail(); + ImVec2 size = ImFloor(size_arg); + const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); + if (size.x <= 0.0f) + size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues) + if (size.y <= 0.0f) + size.y = ImMax(content_avail.y + size.y, 4.0f); + SetNextWindowSize(size); + + // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. + if (name) + ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%s/%s_%08X", parent_window->Name, name, id); + else + ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%s/%08X", parent_window->Name, id); + + const float backup_border_size = g.Style.ChildBorderSize; + if (!border) + g.Style.ChildBorderSize = 0.0f; + bool ret = Begin(g.TempBuffer, NULL, flags); + g.Style.ChildBorderSize = backup_border_size; + + ImGuiWindow* child_window = g.CurrentWindow; + child_window->ChildId = id; + child_window->AutoFitChildAxises = (ImS8)auto_fit_axises; + + // Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually. + // While this is not really documented/defined, it seems that the expected thing to do. + if (child_window->BeginCount == 1) + parent_window->DC.CursorPos = child_window->Pos; + + // Process navigation-in immediately so NavInit can run on first frame + if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavHasScroll)) + { + FocusWindow(child_window); + NavInitWindow(child_window, false); + SetActiveID(id + 1, child_window); // Steal ActiveId with another arbitrary id so that key-press won't activate child item + g.ActiveIdSource = ImGuiInputSource_Nav; + } + return ret; +} + +bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags); +} + +bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + IM_ASSERT(id != 0); + return BeginChildEx(NULL, id, size_arg, border, extra_flags); +} + +void ImGui::EndChild() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + IM_ASSERT(g.WithinEndChild == false); + IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() calls + + g.WithinEndChild = true; + if (window->BeginCount > 1) + { + End(); + } + else + { + ImVec2 sz = window->Size; + if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f + sz.x = ImMax(4.0f, sz.x); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y)) + sz.y = ImMax(4.0f, sz.y); + End(); + + ImGuiWindow* parent_window = g.CurrentWindow; + ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); + ItemSize(sz); + if ((window->DC.NavLayersActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened)) + { + ItemAdd(bb, window->ChildId); + RenderNavHighlight(bb, window->ChildId); + + // When browsing a window that has no activable items (scroll only) we keep a highlight on the child + if (window->DC.NavLayersActiveMask == 0 && window == g.NavWindow) + RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavHighlightFlags_TypeThin); + } + else + { + // Not navigable into + ItemAdd(bb, 0); + } + if (g.HoveredWindow == window) + parent_window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + } + g.WithinEndChild = false; + g.LogLinePosY = -FLT_MAX; // To enforce a carriage return +} + +// Helper to create a child window / scrolling region that looks like a normal widget frame. +bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + bool ret = BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); + PopStyleVar(3); + PopStyleColor(); + return ret; +} + +void ImGui::EndChildFrame() +{ + EndChild(); +} + +static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) +{ + window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); + window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags); + window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags); +} + +ImGuiWindow* ImGui::FindWindowByID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id); +} + +ImGuiWindow* ImGui::FindWindowByName(const char* name) +{ + ImGuiID id = ImHashStr(name); + return FindWindowByID(id); +} + +static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) +{ + window->Pos = ImFloor(ImVec2(settings->Pos.x, settings->Pos.y)); + if (settings->Size.x > 0 && settings->Size.y > 0) + window->Size = window->SizeFull = ImFloor(ImVec2(settings->Size.x, settings->Size.y)); + window->Collapsed = settings->Collapsed; +} + +static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + //IMGUI_DEBUG_LOG("CreateNewWindow '%s', flags = 0x%08X\n", name, flags); + + // Create window the first time + ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); + window->Flags = flags; + g.WindowsById.SetVoidPtr(window->ID, window); + + // Default/arbitrary window position. Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + window->Pos = main_viewport->Pos + ImVec2(60, 60); + + // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. + if (!(flags & ImGuiWindowFlags_NoSavedSettings)) + if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID)) + { + // Retrieve settings from .ini file + window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings); + SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); + ApplyWindowSettings(window, settings); + } + window->DC.CursorStartPos = window->DC.CursorMaxPos = window->Pos; // So first call to CalcContentSize() doesn't return crazy values + + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) + { + window->AutoFitFramesX = window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } + else + { + if (window->Size.x <= 0.0f) + window->AutoFitFramesX = 2; + if (window->Size.y <= 0.0f) + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); + } + + if (!(flags & ImGuiWindowFlags_ChildWindow)) + { + g.WindowsFocusOrder.push_back(window); + window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1); + } + + if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) + g.Windows.push_front(window); // Quite slow but rare and only once + else + g.Windows.push_back(window); + return window; +} + +static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& size_desired) +{ + ImGuiContext& g = *GImGui; + ImVec2 new_size = size_desired; + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) + { + // Using -1,-1 on either X/Y axis to preserve the current size. + ImRect cr = g.NextWindowData.SizeConstraintRect; + new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; + new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; + if (g.NextWindowData.SizeCallback) + { + ImGuiSizeCallbackData data; + data.UserData = g.NextWindowData.SizeCallbackUserData; + data.Pos = window->Pos; + data.CurrentSize = window->SizeFull; + data.DesiredSize = new_size; + g.NextWindowData.SizeCallback(&data); + new_size = data.DesiredSize; + } + new_size.x = IM_FLOOR(new_size.x); + new_size.y = IM_FLOOR(new_size.y); + } + + // Minimum size + if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) + { + ImGuiWindow* window_for_height = window; + const float decoration_up_height = window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight(); + new_size = ImMax(new_size, g.Style.WindowMinSize); + new_size.y = ImMax(new_size.y, decoration_up_height + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows + } + return new_size; +} + +static void CalcWindowContentSizes(ImGuiWindow* window, ImVec2* content_size_current, ImVec2* content_size_ideal) +{ + bool preserve_old_content_sizes = false; + if (window->Collapsed && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + preserve_old_content_sizes = true; + else if (window->Hidden && window->HiddenFramesCannotSkipItems == 0 && window->HiddenFramesCanSkipItems > 0) + preserve_old_content_sizes = true; + if (preserve_old_content_sizes) + { + *content_size_current = window->ContentSize; + *content_size_ideal = window->ContentSizeIdeal; + return; + } + + content_size_current->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_FLOOR(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x); + content_size_current->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_FLOOR(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y); + content_size_ideal->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_FLOOR(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x); + content_size_ideal->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_FLOOR(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y); +} + +static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_contents) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); + ImVec2 size_pad = window->WindowPadding * 2.0f; + ImVec2 size_desired = size_contents + size_pad + ImVec2(0.0f, decoration_up_height); + if (window->Flags & ImGuiWindowFlags_Tooltip) + { + // Tooltip always resize + return size_desired; + } + else + { + // Maximum window size is determined by the viewport size or monitor size + const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) != 0; + const bool is_menu = (window->Flags & ImGuiWindowFlags_ChildMenu) != 0; + ImVec2 size_min = style.WindowMinSize; + if (is_popup || is_menu) // Popups and menus bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) + size_min = ImMin(size_min, ImVec2(4.0f, 4.0f)); + + // FIXME-VIEWPORT-WORKAREA: May want to use GetWorkSize() instead of Size depending on the type of windows? + ImVec2 avail_size = ImGui::GetMainViewport()->Size; + ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, avail_size - style.DisplaySafeAreaPadding * 2.0f)); + + // When the window cannot fit all contents (either because of constraints, either because screen is too small), + // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding. + ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit); + bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - 0.0f < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); + bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - decoration_up_height < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); + if (will_have_scrollbar_x) + size_auto_fit.y += style.ScrollbarSize; + if (will_have_scrollbar_y) + size_auto_fit.x += style.ScrollbarSize; + return size_auto_fit; + } +} + +ImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow* window) +{ + ImVec2 size_contents_current; + ImVec2 size_contents_ideal; + CalcWindowContentSizes(window, &size_contents_current, &size_contents_ideal); + ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents_ideal); + ImVec2 size_final = CalcWindowSizeAfterConstraint(window, size_auto_fit); + return size_final; +} + +static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags) +{ + if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) + return ImGuiCol_PopupBg; + if (flags & ImGuiWindowFlags_ChildWindow) + return ImGuiCol_ChildBg; + return ImGuiCol_WindowBg; +} + +static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) +{ + ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left + ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right + ImVec2 size_expected = pos_max - pos_min; + ImVec2 size_constrained = CalcWindowSizeAfterConstraint(window, size_expected); + *out_pos = pos_min; + if (corner_norm.x == 0.0f) + out_pos->x -= (size_constrained.x - size_expected.x); + if (corner_norm.y == 0.0f) + out_pos->y -= (size_constrained.y - size_expected.y); + *out_size = size_constrained; +} + +// Data for resizing from corner +struct ImGuiResizeGripDef +{ + ImVec2 CornerPosN; + ImVec2 InnerDir; + int AngleMin12, AngleMax12; +}; +static const ImGuiResizeGripDef resize_grip_def[4] = +{ + { ImVec2(1, 1), ImVec2(-1, -1), 0, 3 }, // Lower-right + { ImVec2(0, 1), ImVec2(+1, -1), 3, 6 }, // Lower-left + { ImVec2(0, 0), ImVec2(+1, +1), 6, 9 }, // Upper-left (Unused) + { ImVec2(1, 0), ImVec2(-1, +1), 9, 12 } // Upper-right (Unused) +}; + +// Data for resizing from borders +struct ImGuiResizeBorderDef +{ + ImVec2 InnerDir; + ImVec2 SegmentN1, SegmentN2; + float OuterAngle; +}; +static const ImGuiResizeBorderDef resize_border_def[4] = +{ + { ImVec2(+1, 0), ImVec2(0, 1), ImVec2(0, 0), IM_PI * 1.00f }, // Left + { ImVec2(-1, 0), ImVec2(1, 0), ImVec2(1, 1), IM_PI * 0.00f }, // Right + { ImVec2(0, +1), ImVec2(0, 0), ImVec2(1, 0), IM_PI * 1.50f }, // Up + { ImVec2(0, -1), ImVec2(1, 1), ImVec2(0, 1), IM_PI * 0.50f } // Down +}; + +static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) +{ + ImRect rect = window->Rect(); + if (thickness == 0.0f) + rect.Max -= ImVec2(1, 1); + if (border_n == ImGuiDir_Left) { return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); } + if (border_n == ImGuiDir_Right) { return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness, rect.Max.y - perp_padding); } + if (border_n == ImGuiDir_Up) { return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding, rect.Min.y + thickness); } + if (border_n == ImGuiDir_Down) { return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y + thickness); } + IM_ASSERT(0); + return ImRect(); +} + +// 0..3: corners (Lower-right, Lower-left, Unused, Unused) +ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n) +{ + IM_ASSERT(n >= 0 && n < 4); + ImGuiID id = window->ID; + id = ImHashStr("#RESIZE", 0, id); + id = ImHashData(&n, sizeof(int), id); + return id; +} + +// Borders (Left, Right, Up, Down) +ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir) +{ + IM_ASSERT(dir >= 0 && dir < 4); + int n = (int)dir + 4; + ImGuiID id = window->ID; + id = ImHashStr("#RESIZE", 0, id); + id = ImHashData(&n, sizeof(int), id); + return id; +} + +// Handle resize for: Resize Grips, Borders, Gamepad +// Return true when using auto-fit (double click on resize grip) +static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect) +{ + ImGuiContext& g = *GImGui; + ImGuiWindowFlags flags = window->Flags; + + if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + return false; + if (window->WasActive == false) // Early out to avoid running this code for e.g. an hidden implicit/fallback Debug window. + return false; + + bool ret_auto_fit = false; + const int resize_border_count = g.IO.ConfigWindowsResizeFromEdges ? 4 : 0; + const float grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); + const float grip_hover_inner_size = IM_FLOOR(grip_draw_size * 0.75f); + const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_HOVER_PADDING : 0.0f; + + ImVec2 pos_target(FLT_MAX, FLT_MAX); + ImVec2 size_target(FLT_MAX, FLT_MAX); + + // Resize grips and borders are on layer 1 + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + + // Manual resize grips + PushID("#RESIZE"); + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& def = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, def.CornerPosN); + + // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window + bool hovered, held; + ImRect resize_rect(corner - def.InnerDir * grip_hover_outer_size, corner + def.InnerDir * grip_hover_inner_size); + if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x); + if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y); + ImGuiID resize_grip_id = window->GetID(resize_grip_n); // == GetWindowResizeCornerID() + ButtonBehavior(resize_rect, resize_grip_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); + //GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255)); + if (hovered || held) + g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + + if (held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) + { + // Manual auto-fit when double-clicking + size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit); + ret_auto_fit = true; + ClearActiveID(); + } + else if (held) + { + // Resize from any of the four corners + // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position + ImVec2 clamp_min = ImVec2(def.CornerPosN.x == 1.0f ? visibility_rect.Min.x : -FLT_MAX, def.CornerPosN.y == 1.0f ? visibility_rect.Min.y : -FLT_MAX); + ImVec2 clamp_max = ImVec2(def.CornerPosN.x == 0.0f ? visibility_rect.Max.x : +FLT_MAX, def.CornerPosN.y == 0.0f ? visibility_rect.Max.y : +FLT_MAX); + ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + ImLerp(def.InnerDir * grip_hover_outer_size, def.InnerDir * -grip_hover_inner_size, def.CornerPosN); // Corner of the window corresponding to our corner grip + corner_target = ImClamp(corner_target, clamp_min, clamp_max); + CalcResizePosSizeFromAnyCorner(window, corner_target, def.CornerPosN, &pos_target, &size_target); + } + + // Only lower-left grip is visible before hovering/activating + if (resize_grip_n == 0 || held || hovered) + resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); + } + for (int border_n = 0; border_n < resize_border_count; border_n++) + { + const ImGuiResizeBorderDef& def = resize_border_def[border_n]; + const ImGuiAxis axis = (border_n == ImGuiDir_Left || border_n == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y; + + bool hovered, held; + ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_HOVER_PADDING); + ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID() + ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren); + //GetForegroundDrawLists(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255)); + if ((hovered && g.HoveredIdTimer > WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) || held) + { + g.MouseCursor = (axis == ImGuiAxis_X) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + if (held) + *border_held = border_n; + } + if (held) + { + ImVec2 clamp_min(border_n == ImGuiDir_Right ? visibility_rect.Min.x : -FLT_MAX, border_n == ImGuiDir_Down ? visibility_rect.Min.y : -FLT_MAX); + ImVec2 clamp_max(border_n == ImGuiDir_Left ? visibility_rect.Max.x : +FLT_MAX, border_n == ImGuiDir_Up ? visibility_rect.Max.y : +FLT_MAX); + ImVec2 border_target = window->Pos; + border_target[axis] = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + WINDOWS_HOVER_PADDING; + border_target = ImClamp(border_target, clamp_min, clamp_max); + CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target, &size_target); + } + } + PopID(); + + // Restore nav layer + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + + // Navigation resize (keyboard/gamepad) + if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window) + { + ImVec2 nav_resize_delta; + if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavInputSource == ImGuiInputSource_Gamepad) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down); + if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f) + { + const float NAV_RESIZE_SPEED = 600.0f; + nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); + nav_resize_delta = ImMax(nav_resize_delta, visibility_rect.Min - window->Pos - window->Size); + g.NavWindowingToggleLayer = false; + g.NavDisableMouseHover = true; + resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); + // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck. + size_target = CalcWindowSizeAfterConstraint(window, window->SizeFull + nav_resize_delta); + } + } + + // Apply back modified position/size to window + if (size_target.x != FLT_MAX) + { + window->SizeFull = size_target; + MarkIniSettingsDirty(window); + } + if (pos_target.x != FLT_MAX) + { + window->Pos = ImFloor(pos_target); + MarkIniSettingsDirty(window); + } + + window->Size = window->SizeFull; + return ret_auto_fit; +} + +static inline void ClampWindowRect(ImGuiWindow* window, const ImRect& visibility_rect) +{ + ImGuiContext& g = *GImGui; + ImVec2 size_for_clamping = window->Size; + if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) + size_for_clamping.y = window->TitleBarHeight(); + window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max); +} + +static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + float rounding = window->WindowRounding; + float border_size = window->WindowBorderSize; + if (border_size > 0.0f && !(window->Flags & ImGuiWindowFlags_NoBackground)) + window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); + + int border_held = window->ResizeBorderHeld; + if (border_held != -1) + { + const ImGuiResizeBorderDef& def = resize_border_def[border_held]; + ImRect border_r = GetResizeBorderRect(window, border_held, rounding, 0.0f); + window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle); + window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f); + window->DrawList->PathStroke(GetColorU32(ImGuiCol_SeparatorActive), 0, ImMax(2.0f, border_size)); // Thicker than usual + } + if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) + { + float y = window->Pos.y + window->TitleBarHeight() - 1; + window->DrawList->AddLine(ImVec2(window->Pos.x + border_size, y), ImVec2(window->Pos.x + window->Size.x - border_size, y), GetColorU32(ImGuiCol_Border), g.Style.FrameBorderSize); + } +} + +// Draw background and borders +// Draw and handle scrollbars +void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImGuiWindowFlags flags = window->Flags; + + // Ensure that ScrollBar doesn't read last frame's SkipItems + IM_ASSERT(window->BeginCount == 0); + window->SkipItems = false; + + // Draw window + handle manual resize + // As we highlight the title bar when want_focus is set, multiple reappearing windows will have have their title bar highlighted on their reappearing frame. + const float window_rounding = window->WindowRounding; + const float window_border_size = window->WindowBorderSize; + if (window->Collapsed) + { + // Title bar only + float backup_border_size = style.FrameBorderSize; + g.Style.FrameBorderSize = window->WindowBorderSize; + ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); + RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); + g.Style.FrameBorderSize = backup_border_size; + } + else + { + // Window background + if (!(flags & ImGuiWindowFlags_NoBackground)) + { + ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags)); + bool override_alpha = false; + float alpha = 1.0f; + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha) + { + alpha = g.NextWindowData.BgAlphaVal; + override_alpha = true; + } + if (override_alpha) + bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT); + window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom); + } + + // Title bar + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + { + ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundCornersTop); + } + + // Menu bar + if (flags & ImGuiWindowFlags_MenuBar) + { + ImRect menu_bar_rect = window->MenuBarRect(); + menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. + window->DrawList->AddRectFilled(menu_bar_rect.Min + ImVec2(window_border_size, 0), menu_bar_rect.Max - ImVec2(window_border_size, 0), GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); + if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) + window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Scrollbars + if (window->ScrollbarX) + Scrollbar(ImGuiAxis_X); + if (window->ScrollbarY) + Scrollbar(ImGuiAxis_Y); + + // Render resize grips (after their input handling so we don't have a frame of latency) + if (!(flags & ImGuiWindowFlags_NoResize)) + { + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, window_border_size))); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, window_border_size) : ImVec2(window_border_size, resize_grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); + window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); + } + } + + // Borders + RenderWindowOuterBorders(window); + } +} + +// Render title text, collapse button, close button +void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImGuiWindowFlags flags = window->Flags; + + const bool has_close_button = (p_open != NULL); + const bool has_collapse_button = !(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None); + + // Close & Collapse button are on the Menu NavLayer and don't default focus (unless there's nothing else on that layer) + const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + + // Layout buttons + // FIXME: Would be nice to generalize the subtleties expressed here into reusable code. + float pad_l = style.FramePadding.x; + float pad_r = style.FramePadding.x; + float button_sz = g.FontSize; + ImVec2 close_button_pos; + ImVec2 collapse_button_pos; + if (has_close_button) + { + pad_r += button_sz; + close_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, title_bar_rect.Min.y); + } + if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Right) + { + pad_r += button_sz; + collapse_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, title_bar_rect.Min.y); + } + if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Left) + { + collapse_button_pos = ImVec2(title_bar_rect.Min.x + pad_l - style.FramePadding.x, title_bar_rect.Min.y); + pad_l += button_sz; + } + + // Collapse button (submitting first so it gets priority when choosing a navigation init fallback) + if (has_collapse_button) + if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos)) + window->WantCollapseToggle = true; // Defer actual collapsing to next frame as we are too far in the Begin() function + + // Close button + if (has_close_button) + if (CloseButton(window->GetID("#CLOSE"), close_button_pos)) + *p_open = false; + + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + g.CurrentItemFlags = item_flags_backup; + + // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker) + // FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code.. + const char* UNSAVED_DOCUMENT_MARKER = "*"; + const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? CalcTextSize(UNSAVED_DOCUMENT_MARKER, NULL, false).x : 0.0f; + const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f); + + // As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button, + // while uncentered title text will still reach edges correctly. + if (pad_l > style.FramePadding.x) + pad_l += g.Style.ItemInnerSpacing.x; + if (pad_r > style.FramePadding.x) + pad_r += g.Style.ItemInnerSpacing.x; + if (style.WindowTitleAlign.x > 0.0f && style.WindowTitleAlign.x < 1.0f) + { + float centerness = ImSaturate(1.0f - ImFabs(style.WindowTitleAlign.x - 0.5f) * 2.0f); // 0.0f on either edges, 1.0f on center + float pad_extend = ImMin(ImMax(pad_l, pad_r), title_bar_rect.GetWidth() - pad_l - pad_r - text_size.x); + pad_l = ImMax(pad_l, pad_extend * centerness); + pad_r = ImMax(pad_r, pad_extend * centerness); + } + + ImRect layout_r(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r, title_bar_rect.Max.y); + ImRect clip_r(layout_r.Min.x, layout_r.Min.y, ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y); + //if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] + //if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] + RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r); + if (flags & ImGuiWindowFlags_UnsavedDocument) + { + ImVec2 marker_pos = ImVec2(ImMax(layout_r.Min.x, layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x) + text_size.x, layout_r.Min.y) + ImVec2(2 - marker_size_x, 0.0f); + ImVec2 off = ImVec2(0.0f, IM_FLOOR(-g.FontSize * 0.25f)); + RenderTextClipped(marker_pos + off, layout_r.Max + off, UNSAVED_DOCUMENT_MARKER, NULL, NULL, ImVec2(0, style.WindowTitleAlign.y), &clip_r); + } +} + +void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window) +{ + window->ParentWindow = parent_window; + window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; + if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) + window->RootWindow = parent_window->RootWindow; + if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight; + while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) + { + IM_ASSERT(window->RootWindowForNav->ParentWindow != NULL); + window->RootWindowForNav = window->RootWindowForNav->ParentWindow; + } +} + +// Push a new Dear ImGui window to add widgets to. +// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. +// - Begin/End can be called multiple times during the frame with the same window name to append content. +// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). +// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file. +// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned. +// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed. +bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + IM_ASSERT(name != NULL && name[0] != '\0'); // Window name required + IM_ASSERT(g.WithinFrameScope); // Forgot to call ImGui::NewFrame() + IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet + + // Find or create + ImGuiWindow* window = FindWindowByName(name); + const bool window_just_created = (window == NULL); + if (window_just_created) + window = CreateNewWindow(name, flags); + + // Automatically disable manual moving/resizing when NoInputs is set + if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs) + flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + + if (flags & ImGuiWindowFlags_NavFlattened) + IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow); + + const int current_frame = g.FrameCount; + const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); + window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow); + + // Update the Appearing flag + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; + window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed + window_just_activated_by_user |= (window != popup_ref.Window); + } + window->Appearing = window_just_activated_by_user; + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + + // Update Flags, LastFrameActive, BeginOrderXXX fields + if (first_begin_of_the_frame) + { + window->Flags = (ImGuiWindowFlags)flags; + window->LastFrameActive = current_frame; + window->LastTimeActive = (float)g.Time; + window->BeginOrderWithinParent = 0; + window->BeginOrderWithinContext = (short)(g.WindowsActiveCount++); + } + else + { + flags = window->Flags; + } + + // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack + ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); + ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; + IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); + + // We allow window memory to be compacted so recreate the base stack when needed. + if (window->IDStack.Size == 0) + window->IDStack.push_back(window->ID); + + // Add to stack + // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() + g.CurrentWindowStack.push_back(window); + g.CurrentWindow = window; + window->DC.StackSizesOnBegin.SetToCurrentState(); + g.CurrentWindow = NULL; + + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; + popup_ref.Window = window; + g.BeginPopupStack.push_back(popup_ref); + window->PopupId = popup_ref.PopupId; + } + + // Update ->RootWindow and others pointers (before any possible call to FocusWindow) + if (first_begin_of_the_frame) + UpdateWindowParentAndRootLinks(window, flags, parent_window); + + // Process SetNextWindow***() calls + // (FIXME: Consider splitting the HasXXX flags into X/Y components + bool window_pos_set_by_api = false; + bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) + { + window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0; + if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f) + { + // May be processed on the next frame if this is our first frame and we are measuring size + // FIXME: Look into removing the branch so everything can go through this same code path for consistency. + window->SetWindowPosVal = g.NextWindowData.PosVal; + window->SetWindowPosPivot = g.NextWindowData.PosPivotVal; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + } + else + { + SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond); + } + } + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) + { + window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f); + window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f); + SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); + } + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasScroll) + { + if (g.NextWindowData.ScrollVal.x >= 0.0f) + { + window->ScrollTarget.x = g.NextWindowData.ScrollVal.x; + window->ScrollTargetCenterRatio.x = 0.0f; + } + if (g.NextWindowData.ScrollVal.y >= 0.0f) + { + window->ScrollTarget.y = g.NextWindowData.ScrollVal.y; + window->ScrollTargetCenterRatio.y = 0.0f; + } + } + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasContentSize) + window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal; + else if (first_begin_of_the_frame) + window->ContentSizeExplicit = ImVec2(0.0f, 0.0f); + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasCollapsed) + SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasFocus) + FocusWindow(window); + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + + // When reusing window again multiple times a frame, just append content (don't need to setup again) + if (first_begin_of_the_frame) + { + // Initialize + const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) + window->Active = true; + window->HasCloseButton = (p_open != NULL); + window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX); + window->IDStack.resize(1); + window->DrawList->_ResetForNewFrame(); + window->DC.CurrentTableIdx = -1; + + // Restore buffer capacity when woken from a compacted state, to avoid + if (window->MemoryCompacted) + GcAwakeTransientWindowBuffers(window); + + // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged). + // The title bar always display the 'name' parameter, so we only update the string storage if it needs to be visible to the end-user elsewhere. + bool window_title_visible_elsewhere = false; + if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB + window_title_visible_elsewhere = true; + if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0) + { + size_t buf_len = (size_t)window->NameBufLen; + window->Name = ImStrdupcpy(window->Name, &buf_len, name); + window->NameBufLen = (int)buf_len; + } + + // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS + + // Update contents size from last frame for auto-fitting (or use explicit size) + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesCannotSkipItems > 0); + CalcWindowContentSizes(window, &window->ContentSize, &window->ContentSizeIdeal); + if (window->HiddenFramesCanSkipItems > 0) + window->HiddenFramesCanSkipItems--; + if (window->HiddenFramesCannotSkipItems > 0) + window->HiddenFramesCannotSkipItems--; + if (window->HiddenFramesForRenderOnly > 0) + window->HiddenFramesForRenderOnly--; + + // Hide new windows for one frame until they calculate their size + if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api)) + window->HiddenFramesCannotSkipItems = 1; + + // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) + // We reset Size/ContentSize for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size. + if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0) + { + window->HiddenFramesCannotSkipItems = 1; + if (flags & ImGuiWindowFlags_AlwaysAutoResize) + { + if (!window_size_x_set_by_api) + window->Size.x = window->SizeFull.x = 0.f; + if (!window_size_y_set_by_api) + window->Size.y = window->SizeFull.y = 0.f; + window->ContentSize = window->ContentSizeIdeal = ImVec2(0.f, 0.f); + } + } + + // SELECT VIEWPORT + // FIXME-VIEWPORT: In the docking/viewport branch, this is the point where we select the current viewport (which may affect the style) + SetCurrentWindow(window); + + // LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies) + + if (flags & ImGuiWindowFlags_ChildWindow) + window->WindowBorderSize = style.ChildBorderSize; + else + window->WindowBorderSize = ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; + window->WindowPadding = style.WindowPadding; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) + window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); + + // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size. + window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); + window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; + + // Collapse window by double-clicking on title bar + // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) + { + // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar. + ImRect title_bar_rect = window->TitleBarRect(); + if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]) + window->WantCollapseToggle = true; + if (window->WantCollapseToggle) + { + window->Collapsed = !window->Collapsed; + MarkIniSettingsDirty(window); + } + } + else + { + window->Collapsed = false; + } + window->WantCollapseToggle = false; + + // SIZE + + // Calculate auto-fit size, handle automatic resize + const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal); + bool use_current_size_for_scrollbar_x = window_just_created; + bool use_current_size_for_scrollbar_y = window_just_created; + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed) + { + // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. + if (!window_size_x_set_by_api) + { + window->SizeFull.x = size_auto_fit.x; + use_current_size_for_scrollbar_x = true; + } + if (!window_size_y_set_by_api) + { + window->SizeFull.y = size_auto_fit.y; + use_current_size_for_scrollbar_y = true; + } + } + else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + { + // Auto-fit may only grow window during the first few frames + // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. + if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) + { + window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; + use_current_size_for_scrollbar_x = true; + } + if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) + { + window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + use_current_size_for_scrollbar_y = true; + } + if (!window->Collapsed) + MarkIniSettingsDirty(window); + } + + // Apply minimum/maximum window size constraints and final size + window->SizeFull = CalcWindowSizeAfterConstraint(window, window->SizeFull); + window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; + + // Decoration size + const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); + + // POSITION + + // Popup latch its initial position, will position itself when it appears next frame + if (window_just_activated_by_user) + { + window->AutoPosLastDirection = ImGuiDir_None; + if ((flags & ImGuiWindowFlags_Popup) != 0 && !(flags & ImGuiWindowFlags_Modal) && !window_pos_set_by_api) // FIXME: BeginPopup() could use SetNextWindowPos() + window->Pos = g.BeginPopupStack.back().OpenPopupPos; + } + + // Position child window + if (flags & ImGuiWindowFlags_ChildWindow) + { + IM_ASSERT(parent_window && parent_window->Active); + window->BeginOrderWithinParent = (short)parent_window->DC.ChildWindows.Size; + parent_window->DC.ChildWindows.push_back(window); + if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip) + window->Pos = parent_window->DC.CursorPos; + } + + const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesCannotSkipItems == 0); + if (window_pos_with_pivot) + SetWindowPos(window, window->SetWindowPosVal - window->Size * window->SetWindowPosPivot, 0); // Position given a pivot (e.g. for centering) + else if ((flags & ImGuiWindowFlags_ChildMenu) != 0) + window->Pos = FindBestWindowPosForPopup(window); + else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) + window->Pos = FindBestWindowPosForPopup(window); + else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) + window->Pos = FindBestWindowPosForPopup(window); + + // Calculate the range of allowed position for that window (to be movable and visible past safe area padding) + // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect. + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); + ImRect viewport_rect(viewport->GetMainRect()); + ImRect viewport_work_rect(viewport->GetWorkRect()); + ImVec2 visibility_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); + ImRect visibility_rect(viewport_work_rect.Min + visibility_padding, viewport_work_rect.Max - visibility_padding); + + // Clamp position/size so window stays visible within its viewport or monitor + // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. + if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + if (viewport_rect.GetWidth() > 0.0f && viewport_rect.GetHeight() > 0.0f) + ClampWindowRect(window, visibility_rect); + window->Pos = ImFloor(window->Pos); + + // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) + // Large values tend to lead to variety of artifacts and are not recommended. + window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + + // For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to completely hide artifacts. + //if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar)) + // window->WindowRounding = ImMin(window->WindowRounding, g.FontSize + style.FramePadding.y * 2.0f); + + // Apply window focus (new and reactivated windows are moved to front) + bool want_focus = false; + if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) + { + if (flags & ImGuiWindowFlags_Popup) + want_focus = true; + else if ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) == 0) + want_focus = true; + } + + // Handle manual resize: Resize Grips, Borders, Gamepad + int border_held = -1; + ImU32 resize_grip_col[4] = {}; + const int resize_grip_count = g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it. + const float resize_grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); + if (!window->Collapsed) + if (UpdateWindowManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect)) + use_current_size_for_scrollbar_x = use_current_size_for_scrollbar_y = true; + window->ResizeBorderHeld = (signed char)border_held; + + // SCROLLBAR VISIBILITY + + // Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized Size). + if (!window->Collapsed) + { + // When reading the current size we need to read it after size constraints have been applied. + // When we use InnerRect here we are intentionally reading last frame size, same for ScrollbarSizes values before we set them again. + ImVec2 avail_size_from_current_frame = ImVec2(window->SizeFull.x, window->SizeFull.y - decoration_up_height); + ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + window->ScrollbarSizes; + ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f; + float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x; + float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y; + //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons? + window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + if (window->ScrollbarX && !window->ScrollbarY) + window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); + } + + // UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING) + // Update various regions. Variables they depends on should be set above in this function. + // We set this up after processing the resize grip so that our rectangles doesn't lag by a frame. + + // Outer rectangle + // Not affected by window border size. Used by: + // - FindHoveredWindow() (w/ extra padding when border resize is enabled) + // - Begin() initial clipping rect for drawing window background and borders. + // - Begin() clipping whole child + const ImRect host_rect = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) ? parent_window->ClipRect : viewport_rect; + const ImRect outer_rect = window->Rect(); + const ImRect title_bar_rect = window->TitleBarRect(); + window->OuterRectClipped = outer_rect; + window->OuterRectClipped.ClipWith(host_rect); + + // Inner rectangle + // Not affected by window border size. Used by: + // - InnerClipRect + // - ScrollToBringRectIntoView() + // - NavUpdatePageUpPageDown() + // - Scrollbar() + window->InnerRect.Min.x = window->Pos.x; + window->InnerRect.Min.y = window->Pos.y + decoration_up_height; + window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x; + window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y; + + // Inner clipping rectangle. + // Will extend a little bit outside the normal work region. + // This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space. + // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. + // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior. + // Affected by window/frame border size. Used by: + // - Begin() initial clip rect + float top_border_size = (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); + window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(ImFloor(window->WindowPadding.x * 0.5f), window->WindowBorderSize)); + window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size); + window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(ImFloor(window->WindowPadding.x * 0.5f), window->WindowBorderSize)); + window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - window->WindowBorderSize); + window->InnerClipRect.ClipWithFull(host_rect); + + // Default item width. Make it proportional to window size if window manually resizes + if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) + window->ItemWidthDefault = ImFloor(window->Size.x * 0.65f); + else + window->ItemWidthDefault = ImFloor(g.FontSize * 16.0f); + + // SCROLLING + + // Lock down maximum scrolling + // The value of ScrollMax are ahead from ScrollbarX/ScrollbarY which is intentionally using InnerRect from previous rect in order to accommodate + // for right/bottom aligned items without creating a scrollbar. + window->ScrollMax.x = ImMax(0.0f, window->ContentSize.x + window->WindowPadding.x * 2.0f - window->InnerRect.GetWidth()); + window->ScrollMax.y = ImMax(0.0f, window->ContentSize.y + window->WindowPadding.y * 2.0f - window->InnerRect.GetHeight()); + + // Apply scrolling + window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window); + window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + + // DRAWING + + // Setup draw list and outer clipping rectangle + IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0); + window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + PushClipRect(host_rect.Min, host_rect.Max, false); + + // Draw modal window background (darkens what is behind them, all viewports) + const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetTopMostPopupModal() && window->HiddenFramesCannotSkipItems <= 0; + const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && (window == g.NavWindowingTargetAnim->RootWindow); + if (dim_bg_for_modal || dim_bg_for_window_list) + { + const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio); + window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, dim_bg_col); + } + + // Draw navigation selection/windowing rectangle background + if (dim_bg_for_window_list && window == g.NavWindowingTargetAnim) + { + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding); + } + + // Since 1.71, child window can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call. + // When using overlapping child windows, this will break the assumption that child z-order is mapped to submission order. + // We disable this when the parent window has zero vertices, which is a common pattern leading to laying out multiple overlapping child. + // We also disabled this when we have dimming overlay behind this specific one child. + // FIXME: More code may rely on explicit sorting of overlapping child window and would need to disable this somehow. Please get in contact if you are affected. + { + bool render_decorations_in_parent = false; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) + if (window->DrawList->CmdBuffer.back().ElemCount == 0 && parent_window->DrawList->VtxBuffer.Size > 0) + render_decorations_in_parent = true; + if (render_decorations_in_parent) + window->DrawList = parent_window->DrawList; + + // Handle title bar, scrollbar, resize grips and resize borders + const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow; + const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight); + RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, resize_grip_count, resize_grip_col, resize_grip_draw_size); + + if (render_decorations_in_parent) + window->DrawList = &window->DrawListInst; + } + + // Draw navigation selection/windowing rectangle border + if (g.NavWindowingTargetAnim == window) + { + float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward + { + bb.Expand(-g.FontSize - 1.0f); + rounding = window->WindowRounding; + } + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, 0, 3.0f); + } + + // UPDATE RECTANGLES (2- THOSE AFFECTED BY SCROLLING) + + // Work rectangle. + // Affected by window padding and border size. Used by: + // - Columns() for right-most edge + // - TreeNode(), CollapsingHeader() for right-most edge + // - BeginTabBar() for right-most edge + const bool allow_scrollbar_x = !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar); + const bool allow_scrollbar_y = !(flags & ImGuiWindowFlags_NoScrollbar); + const float work_rect_size_x = (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : ImMax(allow_scrollbar_x ? window->ContentSize.x : 0.0f, window->Size.x - window->WindowPadding.x * 2.0f - window->ScrollbarSizes.x)); + const float work_rect_size_y = (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : ImMax(allow_scrollbar_y ? window->ContentSize.y : 0.0f, window->Size.y - window->WindowPadding.y * 2.0f - decoration_up_height - window->ScrollbarSizes.y)); + window->WorkRect.Min.x = ImFloor(window->InnerRect.Min.x - window->Scroll.x + ImMax(window->WindowPadding.x, window->WindowBorderSize)); + window->WorkRect.Min.y = ImFloor(window->InnerRect.Min.y - window->Scroll.y + ImMax(window->WindowPadding.y, window->WindowBorderSize)); + window->WorkRect.Max.x = window->WorkRect.Min.x + work_rect_size_x; + window->WorkRect.Max.y = window->WorkRect.Min.y + work_rect_size_y; + window->ParentWorkRect = window->WorkRect; + + // [LEGACY] Content Region + // FIXME-OBSOLETE: window->ContentRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it. + // Used by: + // - Mouse wheel scrolling + many other things + window->ContentRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x; + window->ContentRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + decoration_up_height; + window->ContentRegionRect.Max.x = window->ContentRegionRect.Min.x + (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : (window->Size.x - window->WindowPadding.x * 2.0f - window->ScrollbarSizes.x)); + window->ContentRegionRect.Max.y = window->ContentRegionRect.Min.y + (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : (window->Size.y - window->WindowPadding.y * 2.0f - decoration_up_height - window->ScrollbarSizes.y)); + + // Setup drawing context + // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) + window->DC.Indent.x = 0.0f + window->WindowPadding.x - window->Scroll.x; + window->DC.GroupOffset.x = 0.0f; + window->DC.ColumnsOffset.x = 0.0f; + window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.Indent.x + window->DC.ColumnsOffset.x, decoration_up_height + window->WindowPadding.y - window->Scroll.y); + window->DC.CursorPos = window->DC.CursorStartPos; + window->DC.CursorPosPrevLine = window->DC.CursorPos; + window->DC.CursorMaxPos = window->DC.CursorStartPos; + window->DC.IdealMaxPos = window->DC.CursorStartPos; + window->DC.CurrLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f); + window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; + + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + window->DC.NavLayersActiveMask = window->DC.NavLayersActiveMaskNext; + window->DC.NavLayersActiveMaskNext = 0x00; + window->DC.NavHideHighlightOneFrame = false; + window->DC.NavHasScroll = (window->ScrollMax.y > 0.0f); + + window->DC.MenuBarAppending = false; + window->DC.MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); + window->DC.TreeDepth = 0; + window->DC.TreeJumpToParentOnPopMask = 0x00; + window->DC.ChildWindows.resize(0); + window->DC.StateStorage = &window->StateStorage; + window->DC.CurrentColumns = NULL; + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; + window->DC.FocusCounterRegular = window->DC.FocusCounterTabStop = -1; + + window->DC.ItemWidth = window->ItemWidthDefault; + window->DC.TextWrapPos = -1.0f; // disabled + window->DC.ItemWidthStack.resize(0); + window->DC.TextWrapPosStack.resize(0); + + if (window->AutoFitFramesX > 0) + window->AutoFitFramesX--; + if (window->AutoFitFramesY > 0) + window->AutoFitFramesY--; + + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) + if (want_focus) + { + FocusWindow(window); + NavInitWindow(window, false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls + } + + // Title bar + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + RenderWindowTitleBarContents(window, ImRect(title_bar_rect.Min.x + window->WindowBorderSize, title_bar_rect.Min.y, title_bar_rect.Max.x - window->WindowBorderSize, title_bar_rect.Max.y), name, p_open); + + // Clear hit test shape every frame + window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0; + + // Pressing CTRL+C while holding on a window copy its content to the clipboard + // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. + // Maybe we can support CTRL+C on every element? + /* + //if (g.NavWindow == window && g.ActiveId == 0) + if (g.ActiveId == window->MoveId) + if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) + LogToClipboard(); + */ + + // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). + // This is useful to allow creating context menus on title bar only, etc. + SetLastItemData(window, window->MoveId, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); + +#ifdef IMGUI_ENABLE_TEST_ENGINE + if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) + IMGUI_TEST_ENGINE_ITEM_ADD(window->DC.LastItemRect, window->DC.LastItemId); +#endif + } + else + { + // Append + SetCurrentWindow(window); + } + + // Pull/inherit current state + g.CurrentItemFlags = g.ItemFlagsStack.back(); // Inherit from shared stack + window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->DC.NavFocusScopeIdCurrent : 0; // Inherit from parent only // -V595 + + PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); + + // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) + window->WriteAccessed = false; + window->BeginCount++; + g.NextWindowData.ClearFlags(); + + // Update visibility + if (first_begin_of_the_frame) + { + if (flags & ImGuiWindowFlags_ChildWindow) + { + // Child window can be out of sight and have "negative" clip windows. + // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar). + IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); + if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) // FIXME: Doesn't make sense for ChildWindow?? + if (!g.LogEnabled) + if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y) + window->HiddenFramesCanSkipItems = 1; + + // Hide along with parent or if parent is collapsed + if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCanSkipItems > 0)) + window->HiddenFramesCanSkipItems = 1; + if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCannotSkipItems > 0)) + window->HiddenFramesCannotSkipItems = 1; + } + + // Don't render if style alpha is 0.0 at the time of Begin(). This is arbitrary and inconsistent but has been there for a long while (may remove at some point) + if (style.Alpha <= 0.0f) + window->HiddenFramesCanSkipItems = 1; + + // Update the Hidden flag + window->Hidden = (window->HiddenFramesCanSkipItems > 0) || (window->HiddenFramesCannotSkipItems > 0) || (window->HiddenFramesForRenderOnly > 0); + + // Disable inputs for requested number of frames + if (window->DisableInputsFrames > 0) + { + window->DisableInputsFrames--; + window->Flags |= ImGuiWindowFlags_NoInputs; + } + + // Update the SkipItems flag, used to early out of all items functions (no layout required) + bool skip_items = false; + if (window->Collapsed || !window->Active || window->Hidden) + if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0) + skip_items = true; + window->SkipItems = skip_items; + } + + return !window->SkipItems; +} + +void ImGui::End() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Error checking: verify that user hasn't called End() too many times! + if (g.CurrentWindowStack.Size <= 1 && g.WithinFrameScopeWithImplicitWindow) + { + IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size > 1, "Calling End() too many times!"); + return; + } + IM_ASSERT(g.CurrentWindowStack.Size > 0); + + // Error checking: verify that user doesn't directly call End() on a child window. + if (window->Flags & ImGuiWindowFlags_ChildWindow) + IM_ASSERT_USER_ERROR(g.WithinEndChild, "Must call EndChild() and not End()!"); + + // Close anything that is open + if (window->DC.CurrentColumns) + EndColumns(); + PopClipRect(); // Inner window clip rectangle + + // Stop logging + if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + LogFinish(); + + // Pop from window stack + g.CurrentWindowStack.pop_back(); + if (window->Flags & ImGuiWindowFlags_Popup) + g.BeginPopupStack.pop_back(); + window->DC.StackSizesOnBegin.CompareWithCurrentState(); + SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); +} + +void ImGui::BringWindowToFocusFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window == window->RootWindow); + + const int cur_order = window->FocusOrder; + IM_ASSERT(g.WindowsFocusOrder[cur_order] == window); + if (g.WindowsFocusOrder.back() == window) + return; + + const int new_order = g.WindowsFocusOrder.Size - 1; + for (int n = cur_order; n < new_order; n++) + { + g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1]; + g.WindowsFocusOrder[n]->FocusOrder--; + IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n); + } + g.WindowsFocusOrder[new_order] = window; + window->FocusOrder = (short)new_order; +} + +void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* current_front_window = g.Windows.back(); + if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better) + return; + for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window + if (g.Windows[i] == window) + { + memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*)); + g.Windows[g.Windows.Size - 1] = window; + break; + } +} + +void ImGui::BringWindowToDisplayBack(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.Windows[0] == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) + { + memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); + g.Windows[0] = window; + break; + } +} + +// Moving window to front of display and set focus (which happens to be back of our sorted list) +void ImGui::FocusWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + if (g.NavWindow != window) + { + g.NavWindow = window; + if (window && g.NavDisableMouseHover) + g.NavMousePosDirty = true; + g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId + g.NavFocusScopeId = 0; + g.NavIdIsAlive = false; + g.NavLayer = ImGuiNavLayer_Main; + g.NavInitRequest = g.NavMoveRequest = false; + NavUpdateAnyRequestFlag(); + //IMGUI_DEBUG_LOG("FocusWindow(\"%s\")\n", window ? window->Name : NULL); + } + + // Close popups if any + ClosePopupsOverWindow(window, false); + + // Move the root window to the top of the pile + IM_ASSERT(window == NULL || window->RootWindow != NULL); + ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; // NB: In docking branch this is window->RootWindowDockStop + ImGuiWindow* display_front_window = window ? window->RootWindow : NULL; + + // Steal active widgets. Some of the cases it triggers includes: + // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run. + // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId) + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window) + if (!g.ActiveIdNoClearOnFocusLoss) + ClearActiveID(); + + // Passing NULL allow to disable keyboard focus + if (!window) + return; + + // Bring to front + BringWindowToFocusFront(focus_front_window); + if (((window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) + BringWindowToDisplayFront(display_front_window); +} + +void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window) +{ + ImGuiContext& g = *GImGui; + + const int start_idx = ((under_this_window != NULL) ? FindWindowFocusIndex(under_this_window) : g.WindowsFocusOrder.Size) - 1; + for (int i = start_idx; i >= 0; i--) + { + // We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but that may feel more confusing to the user. + ImGuiWindow* window = g.WindowsFocusOrder[i]; + IM_ASSERT(window == window->RootWindow); + if (window != ignore_window && window->WasActive) + if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) + { + ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(window); + FocusWindow(focus_window); + return; + } + } + FocusWindow(NULL); +} + +// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only. +void ImGui::SetCurrentFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(font->Scale > 0.0f); + g.Font = font; + g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale); + g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + + ImFontAtlas* atlas = g.Font->ContainerAtlas; + g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; + g.DrawListSharedData.TexUvLines = atlas->TexUvLines; + g.DrawListSharedData.Font = g.Font; + g.DrawListSharedData.FontSize = g.FontSize; +} + +void ImGui::PushFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + if (!font) + font = GetDefaultFont(); + SetCurrentFont(font); + g.FontStack.push_back(font); + g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); +} + +void ImGui::PopFont() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DrawList->PopTextureID(); + g.FontStack.pop_back(); + SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); +} + +void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) +{ + ImGuiContext& g = *GImGui; + ImGuiItemFlags item_flags = g.CurrentItemFlags; + IM_ASSERT(item_flags == g.ItemFlagsStack.back()); + if (enabled) + item_flags |= option; + else + item_flags &= ~option; + g.CurrentItemFlags = item_flags; + g.ItemFlagsStack.push_back(item_flags); +} + +void ImGui::PopItemFlag() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ItemFlagsStack.Size > 1); // Too many calls to PopItemFlag() - we always leave a 0 at the bottom of the stack. + g.ItemFlagsStack.pop_back(); + g.CurrentItemFlags = g.ItemFlagsStack.back(); +} + +// FIXME: Look into renaming this once we have settled the new Focus/Activation/TabStop system. +void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) +{ + PushItemFlag(ImGuiItemFlags_NoTabStop, !allow_keyboard_focus); +} + +void ImGui::PopAllowKeyboardFocus() +{ + PopItemFlag(); +} + +void ImGui::PushButtonRepeat(bool repeat) +{ + PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); +} + +void ImGui::PopButtonRepeat() +{ + PopItemFlag(); +} + +void ImGui::PushTextWrapPos(float wrap_pos_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos); + window->DC.TextWrapPos = wrap_pos_x; +} + +void ImGui::PopTextWrapPos() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPos = window->DC.TextWrapPosStack.back(); + window->DC.TextWrapPosStack.pop_back(); +} + +bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent) +{ + if (window->RootWindow == potential_parent) + return true; + while (window != NULL) + { + if (window == potential_parent) + return true; + window = window->ParentWindow; + } + return false; +} + +bool ImGui::IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below) +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + { + ImGuiWindow* candidate_window = g.Windows[i]; + if (candidate_window == potential_above) + return true; + if (candidate_window == potential_below) + return false; + } + return false; +} + +bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) +{ + IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function + ImGuiContext& g = *GImGui; + if (g.HoveredWindow == NULL) + return false; + + if ((flags & ImGuiHoveredFlags_AnyWindow) == 0) + { + ImGuiWindow* window = g.CurrentWindow; + switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) + { + case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: + if (g.HoveredWindow->RootWindow != window->RootWindow) + return false; + break; + case ImGuiHoveredFlags_RootWindow: + if (g.HoveredWindow != window->RootWindow) + return false; + break; + case ImGuiHoveredFlags_ChildWindows: + if (!IsWindowChildOf(g.HoveredWindow, window)) + return false; + break; + default: + if (g.HoveredWindow != window) + return false; + break; + } + } + + if (!IsWindowContentHoverable(g.HoveredWindow, flags)) + return false; + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId) + return false; + return true; +} + +bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) +{ + ImGuiContext& g = *GImGui; + + if (flags & ImGuiFocusedFlags_AnyWindow) + return g.NavWindow != NULL; + + IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End() + switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows)) + { + case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_RootWindow: + return g.NavWindow == g.CurrentWindow->RootWindow; + case ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow); + default: + return g.NavWindow == g.CurrentWindow; + } +} + +// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) +// Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically. +// If you want a window to never be focused, you may use the e.g. NoInputs flag. +bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) +{ + return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); +} + +float ImGui::GetWindowWidth() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.x; +} + +float ImGui::GetWindowHeight() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.y; +} + +ImVec2 ImGui::GetWindowPos() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + return window->Pos; +} + +void ImGui::SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowPosAllowFlags & cond) == 0) + return; + + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX); + + // Set + const ImVec2 old_pos = window->Pos; + window->Pos = ImFloor(pos); + ImVec2 offset = window->Pos - old_pos; + window->DC.CursorPos += offset; // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor + window->DC.CursorMaxPos += offset; // And more importantly we need to offset CursorMaxPos/CursorStartPos this so ContentSize calculation doesn't get affected. + window->DC.IdealMaxPos += offset; + window->DC.CursorStartPos += offset; +} + +void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + SetWindowPos(window, pos, cond); +} + +void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowPos(window, pos, cond); +} + +ImVec2 ImGui::GetWindowSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Size; +} + +void ImGui::SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowSizeAllowFlags & cond) == 0) + return; + + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + if (size.x > 0.0f) + { + window->AutoFitFramesX = 0; + window->SizeFull.x = IM_FLOOR(size.x); + } + else + { + window->AutoFitFramesX = 2; + window->AutoFitOnlyGrows = false; + } + if (size.y > 0.0f) + { + window->AutoFitFramesY = 0; + window->SizeFull.y = IM_FLOOR(size.y); + } + else + { + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } +} + +void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond) +{ + SetWindowSize(GImGui->CurrentWindow, size, cond); +} + +void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowSize(window, size, cond); +} + +void ImGui::SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0) + return; + window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + window->Collapsed = collapsed; +} + +void ImGui::SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size) +{ + IM_ASSERT(window->HitTestHoleSize.x == 0); // We don't support multiple holes/hit test filters + window->HitTestHoleSize = ImVec2ih(size); + window->HitTestHoleOffset = ImVec2ih(pos - window->Pos); +} + +void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond); +} + +bool ImGui::IsWindowCollapsed() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Collapsed; +} + +bool ImGui::IsWindowAppearing() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Appearing; +} + +void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowCollapsed(window, collapsed, cond); +} + +void ImGui::SetWindowFocus() +{ + FocusWindow(GImGui->CurrentWindow); +} + +void ImGui::SetWindowFocus(const char* name) +{ + if (name) + { + if (ImGuiWindow* window = FindWindowByName(name)) + FocusWindow(window); + } + else + { + FocusWindow(NULL); + } +} + +void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasPos; + g.NextWindowData.PosVal = pos; + g.NextWindowData.PosPivotVal = pivot; + g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSize; + g.NextWindowData.SizeVal = size; + g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint; + g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max); + g.NextWindowData.SizeCallback = custom_callback; + g.NextWindowData.SizeCallbackUserData = custom_callback_user_data; +} + +// Content size = inner scrollable rectangle, padded with WindowPadding. +// SetNextWindowContentSize(ImVec2(100,100) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100 item. +void ImGui::SetNextWindowContentSize(const ImVec2& size) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasContentSize; + g.NextWindowData.ContentSizeVal = ImFloor(size); +} + +void ImGui::SetNextWindowScroll(const ImVec2& scroll) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasScroll; + g.NextWindowData.ScrollVal = scroll; +} + +void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasCollapsed; + g.NextWindowData.CollapsedVal = collapsed; + g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowFocus() +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus; +} + +void ImGui::SetNextWindowBgAlpha(float alpha) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasBgAlpha; + g.NextWindowData.BgAlphaVal = alpha; +} + +ImDrawList* ImGui::GetWindowDrawList() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DrawList; +} + +ImFont* ImGui::GetFont() +{ + return GImGui->Font; +} + +float ImGui::GetFontSize() +{ + return GImGui->FontSize; +} + +ImVec2 ImGui::GetFontTexUvWhitePixel() +{ + return GImGui->DrawListSharedData.TexUvWhitePixel; +} + +void ImGui::SetWindowFontScale(float scale) +{ + IM_ASSERT(scale > 0.0f); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->FontWindowScale = scale; + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +void ImGui::ActivateItem(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.NavNextActivateId = id; +} + +void ImGui::PushFocusScope(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + g.FocusScopeStack.push_back(window->DC.NavFocusScopeIdCurrent); + window->DC.NavFocusScopeIdCurrent = id; +} + +void ImGui::PopFocusScope() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(g.FocusScopeStack.Size > 0); // Too many PopFocusScope() ? + window->DC.NavFocusScopeIdCurrent = g.FocusScopeStack.back(); + g.FocusScopeStack.pop_back(); +} + +void ImGui::SetKeyboardFocusHere(int offset) +{ + IM_ASSERT(offset >= -1); // -1 is allowed but not below + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + g.TabFocusRequestNextWindow = window; + g.TabFocusRequestNextCounterRegular = window->DC.FocusCounterRegular + 1 + offset; + g.TabFocusRequestNextCounterTabStop = INT_MAX; +} + +void ImGui::SetItemDefaultFocus() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!window->Appearing) + return; + if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == window->DC.NavLayerCurrent) + { + g.NavInitRequest = false; + g.NavInitResultId = window->DC.LastItemId; + g.NavInitResultRectRel = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); + NavUpdateAnyRequestFlag(); + if (!IsItemVisible()) + SetScrollHereY(); + } +} + +void ImGui::SetStateStorage(ImGuiStorage* tree) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + window->DC.StateStorage = tree ? tree : &window->StateStorage; +} + +ImGuiStorage* ImGui::GetStateStorage() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->DC.StateStorage; +} + +void ImGui::PushID(const char* str_id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetIDNoKeepAlive(str_id); + window->IDStack.push_back(id); +} + +void ImGui::PushID(const char* str_id_begin, const char* str_id_end) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetIDNoKeepAlive(str_id_begin, str_id_end); + window->IDStack.push_back(id); +} + +void ImGui::PushID(const void* ptr_id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetIDNoKeepAlive(ptr_id); + window->IDStack.push_back(id); +} + +void ImGui::PushID(int int_id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetIDNoKeepAlive(int_id); + window->IDStack.push_back(id); +} + +// Push a given id value ignoring the ID stack as a seed. +void ImGui::PushOverrideID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->IDStack.push_back(id); +} + +// Helper to avoid a common series of PushOverrideID -> GetID() -> PopID() call +// (note that when using this pattern, TestEngine's "Stack Tool" will tend to not display the intermediate stack level. +// for that to work we would need to do PushOverrideID() -> ItemAdd() -> PopID() which would alter widget code a little more) +ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed) +{ + ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); + ImGui::KeepAliveID(id); +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGuiContext& g = *GImGui; + IMGUI_TEST_ENGINE_ID_INFO2(id, ImGuiDataType_String, str, str_end); +#endif + return id; +} + +void ImGui::PopID() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + IM_ASSERT(window->IDStack.Size > 1); // Too many PopID(), or could be popping in a wrong/different window? + window->IDStack.pop_back(); +} + +ImGuiID ImGui::GetID(const char* str_id) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->GetID(str_id); +} + +ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->GetID(str_id_begin, str_id_end); +} + +ImGuiID ImGui::GetID(const void* ptr_id) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->GetID(ptr_id); +} + +bool ImGui::IsRectVisible(const ImVec2& size) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); +} + +bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); +} + + +//----------------------------------------------------------------------------- +// [SECTION] ERROR CHECKING +//----------------------------------------------------------------------------- + +// Helper function to verify ABI compatibility between caller code and compiled version of Dear ImGui. +// Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit +// If the user has inconsistent compilation settings, imgui configuration #define, packing pragma, etc. your user code +// may see different structures than what imgui.cpp sees, which is problematic. +// We usually require settings to be in imconfig.h to make sure that they are accessible to all compilation units involved with Dear ImGui. +bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_vert, size_t sz_idx) +{ + bool error = false; + if (strcmp(version, IMGUI_VERSION) != 0) { error = true; IM_ASSERT(strcmp(version, IMGUI_VERSION) == 0 && "Mismatched version string!"); } + if (sz_io != sizeof(ImGuiIO)) { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); } + if (sz_style != sizeof(ImGuiStyle)) { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!"); } + if (sz_vec2 != sizeof(ImVec2)) { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); } + if (sz_vec4 != sizeof(ImVec4)) { error = true; IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!"); } + if (sz_vert != sizeof(ImDrawVert)) { error = true; IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!"); } + if (sz_idx != sizeof(ImDrawIdx)) { error = true; IM_ASSERT(sz_idx == sizeof(ImDrawIdx) && "Mismatched struct layout!"); } + return !error; +} + +static void ImGui::ErrorCheckNewFrameSanityChecks() +{ + ImGuiContext& g = *GImGui; + + // Check user IM_ASSERT macro + // (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means your assert macro is incorrectly defined! + // If your macro uses multiple statements, it NEEDS to be surrounded by a 'do { ... } while (0)' block. + // This is a common C/C++ idiom to allow multiple statements macros to be used in control flow blocks.) + // #define IM_ASSERT(EXPR) if (SomeCode(EXPR)) SomeMoreCode(); // Wrong! + // #define IM_ASSERT(EXPR) do { if (SomeCode(EXPR)) SomeMoreCode(); } while (0) // Correct! + if (true) IM_ASSERT(1); else IM_ASSERT(0); + + // Check user data + // (We pass an error message in the assert expression to make it visible to programmers who are not using a debugger, as most assert handlers display their argument) + IM_ASSERT(g.Initialized); + IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); + IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); + IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); + IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()?"); + IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()?"); + IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); + IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); + IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations + IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting."); + IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); + for (int n = 0; n < ImGuiKey_COUNT; n++) + IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)"); + + // Check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only added in 1.60 WIP) + if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) + IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); + + // Check: the io.ConfigWindowsResizeFromEdges option requires backend to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag accordingly. + if (g.IO.ConfigWindowsResizeFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors)) + g.IO.ConfigWindowsResizeFromEdges = false; +} + +static void ImGui::ErrorCheckEndFrameSanityChecks() +{ + ImGuiContext& g = *GImGui; + + // Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame() + // One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame(). + // It is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will + // send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs. + // We silently accommodate for this case by ignoring/ the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0), + // while still correctly asserting on mid-frame key press events. + const ImGuiKeyModFlags key_mod_flags = GetMergedKeyModFlags(); + IM_ASSERT((key_mod_flags == 0 || g.IO.KeyMods == key_mod_flags) && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); + IM_UNUSED(key_mod_flags); + + // Recover from errors + //ErrorCheckEndFrameRecover(); + + // Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the Begin/BeginChild API requires you + // to always call End/EndChild even if Begin/BeginChild returns false! (this is unfortunately inconsistent with most other Begin* API). + if (g.CurrentWindowStack.Size != 1) + { + if (g.CurrentWindowStack.Size > 1) + { + IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?"); + while (g.CurrentWindowStack.Size > 1) + End(); + } + else + { + IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?"); + } + } + + IM_ASSERT_USER_ERROR(g.GroupStack.Size == 0, "Missing EndGroup call!"); +} + +// Experimental recovery from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls. +// Must be called during or before EndFrame(). +// This is generally flawed as we are not necessarily End/Popping things in the right order. +// FIXME: Can't recover from inside BeginTabItem/EndTabItem yet. +// FIXME: Can't recover from interleaved BeginTabBar/Begin +void ImGui::ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data) +{ + // PVS-Studio V1044 is "Loop break conditions do not depend on the number of iterations" + ImGuiContext& g = *GImGui; + while (g.CurrentWindowStack.Size > 0) + { + while (g.CurrentTable && (g.CurrentTable->OuterWindow == g.CurrentWindow || g.CurrentTable->InnerWindow == g.CurrentWindow)) + { + if (log_callback) log_callback(user_data, "Recovered from missing EndTable() in '%s'", g.CurrentTable->OuterWindow->Name); + EndTable(); + } + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(window != NULL); + while (g.CurrentTabBar != NULL) //-V1044 + { + if (log_callback) log_callback(user_data, "Recovered from missing EndTabBar() in '%s'", window->Name); + EndTabBar(); + } + while (window->DC.TreeDepth > 0) + { + if (log_callback) log_callback(user_data, "Recovered from missing TreePop() in '%s'", window->Name); + TreePop(); + } + while (g.GroupStack.Size > window->DC.StackSizesOnBegin.SizeOfGroupStack) + { + if (log_callback) log_callback(user_data, "Recovered from missing EndGroup() in '%s'", window->Name); + EndGroup(); + } + while (window->IDStack.Size > 1) + { + if (log_callback) log_callback(user_data, "Recovered from missing PopID() in '%s'", window->Name); + PopID(); + } + while (g.ColorStack.Size > window->DC.StackSizesOnBegin.SizeOfColorStack) + { + if (log_callback) log_callback(user_data, "Recovered from missing PopStyleColor() in '%s' for ImGuiCol_%s", window->Name, GetStyleColorName(g.ColorStack.back().Col)); + PopStyleColor(); + } + while (g.StyleVarStack.Size > window->DC.StackSizesOnBegin.SizeOfStyleVarStack) + { + if (log_callback) log_callback(user_data, "Recovered from missing PopStyleVar() in '%s'", window->Name); + PopStyleVar(); + } + while (g.FocusScopeStack.Size > window->DC.StackSizesOnBegin.SizeOfFocusScopeStack) + { + if (log_callback) log_callback(user_data, "Recovered from missing PopFocusScope() in '%s'", window->Name); + PopFocusScope(); + } + if (g.CurrentWindowStack.Size == 1) + { + IM_ASSERT(g.CurrentWindow->IsFallbackWindow); + break; + } + IM_ASSERT(window == g.CurrentWindow); + if (window->Flags & ImGuiWindowFlags_ChildWindow) + { + if (log_callback) log_callback(user_data, "Recovered from missing EndChild() for '%s'", window->Name); + EndChild(); + } + else + { + if (log_callback) log_callback(user_data, "Recovered from missing End() for '%s'", window->Name); + End(); + } + } +} + +// Save current stack sizes for later compare +void ImGuiStackSizes::SetToCurrentState() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + SizeOfIDStack = (short)window->IDStack.Size; + SizeOfColorStack = (short)g.ColorStack.Size; + SizeOfStyleVarStack = (short)g.StyleVarStack.Size; + SizeOfFontStack = (short)g.FontStack.Size; + SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size; + SizeOfGroupStack = (short)g.GroupStack.Size; + SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size; +} + +// Compare to detect usage errors +void ImGuiStackSizes::CompareWithCurrentState() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_UNUSED(window); + + // Window stacks + // NOT checking: DC.ItemWidth, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) + IM_ASSERT(SizeOfIDStack == window->IDStack.Size && "PushID/PopID or TreeNode/TreePop Mismatch!"); + + // Global stacks + // For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them. + IM_ASSERT(SizeOfGroupStack == g.GroupStack.Size && "BeginGroup/EndGroup Mismatch!"); + IM_ASSERT(SizeOfBeginPopupStack == g.BeginPopupStack.Size && "BeginPopup/EndPopup or BeginMenu/EndMenu Mismatch!"); + IM_ASSERT(SizeOfColorStack >= g.ColorStack.Size && "PushStyleColor/PopStyleColor Mismatch!"); + IM_ASSERT(SizeOfStyleVarStack >= g.StyleVarStack.Size && "PushStyleVar/PopStyleVar Mismatch!"); + IM_ASSERT(SizeOfFontStack >= g.FontStack.Size && "PushFont/PopFont Mismatch!"); + IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!"); +} + + +//----------------------------------------------------------------------------- +// [SECTION] LAYOUT +//----------------------------------------------------------------------------- +// - ItemSize() +// - ItemAdd() +// - SameLine() +// - GetCursorScreenPos() +// - SetCursorScreenPos() +// - GetCursorPos(), GetCursorPosX(), GetCursorPosY() +// - SetCursorPos(), SetCursorPosX(), SetCursorPosY() +// - GetCursorStartPos() +// - Indent() +// - Unindent() +// - SetNextItemWidth() +// - PushItemWidth() +// - PushMultiItemsWidths() +// - PopItemWidth() +// - CalcItemWidth() +// - CalcItemSize() +// - GetTextLineHeight() +// - GetTextLineHeightWithSpacing() +// - GetFrameHeight() +// - GetFrameHeightWithSpacing() +// - GetContentRegionMax() +// - GetContentRegionMaxAbs() [Internal] +// - GetContentRegionAvail(), +// - GetWindowContentRegionMin(), GetWindowContentRegionMax() +// - GetWindowContentRegionWidth() +// - BeginGroup() +// - EndGroup() +// Also see in imgui_widgets: tab bars, columns. +//----------------------------------------------------------------------------- + +// Advance cursor given item size for layout. +// Register minimum needed size so it can extend the bounding box used for auto-fit calculation. +// See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different. +void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + // We increase the height in this function to accommodate for baseline offset. + // In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor, + // but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect. + const float offset_to_match_baseline_y = (text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f; + const float line_height = ImMax(window->DC.CurrLineSize.y, size.y + offset_to_match_baseline_y); + + // Always align ourselves on pixel boundaries + //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] + window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x; + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y; + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line + window->DC.CursorPos.y = IM_FLOOR(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y); // Next line + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + + window->DC.PrevLineSize.y = line_height; + window->DC.CurrLineSize.y = 0.0f; + window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y); + window->DC.CurrLineTextBaseOffset = 0.0f; + + // Horizontal layout mode + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + SameLine(); +} + +void ImGui::ItemSize(const ImRect& bb, float text_baseline_y) +{ + ItemSize(bb.GetSize(), text_baseline_y); +} + +// Declare item bounding box for clipping and interaction. +// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface +// declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemAddFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (id != 0) + { + // Navigation processing runs prior to clipping early-out + // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget + // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests + // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of + // thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. + // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able + // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). + // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null. + // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere. + window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent); + if (g.NavId == id || g.NavAnyRequest) + if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) + if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) + NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id); + + // [DEBUG] Item Picker tool, when enabling the "extended" version we perform the check in ItemAdd() +#ifdef IMGUI_DEBUG_TOOL_ITEM_PICKER_EX + if (id == g.DebugItemPickerBreakId) + { + IM_DEBUG_BREAK(); + g.DebugItemPickerBreakId = 0; + } +#endif + } + + // Equivalent to calling SetLastItemData() + window->DC.LastItemId = id; + window->DC.LastItemRect = bb; + window->DC.LastItemStatusFlags = ImGuiItemStatusFlags_None; + g.NextItemData.Flags = ImGuiNextItemDataFlags_None; + +#ifdef IMGUI_ENABLE_TEST_ENGINE + if (id != 0) + IMGUI_TEST_ENGINE_ITEM_ADD(nav_bb_arg ? *nav_bb_arg : bb, id); +#endif + + // Clipping test + const bool is_clipped = IsClippedEx(bb, id, false); + if (is_clipped) + return false; + //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + + // Tab stop handling (previously was using internal ItemFocusable() api) + // FIXME-NAV: We would now want to move this above the clipping test, but this would require being able to scroll and currently this would mean an extra frame. (#4079, #343) + if (flags & ImGuiItemAddFlags_Focusable) + ItemFocusable(window, id); + + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) + if (IsMouseHoveringRect(bb.Min, bb.Max)) + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect; + return true; +} + +// Gets back to previous line and continue with horizontal layout +// offset_from_start_x == 0 : follow right after previous item +// offset_from_start_x != 0 : align to specified x position (relative to window/group left) +// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 +// spacing_w >= 0 : enforce spacing amount +void ImGui::SameLine(float offset_from_start_x, float spacing_w) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + if (offset_from_start_x != 0.0f) + { + if (spacing_w < 0.0f) spacing_w = 0.0f; + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + offset_from_start_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + else + { + if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x; + window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + window->DC.CurrLineSize = window->DC.PrevLineSize; + window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; +} + +ImVec2 ImGui::GetCursorScreenPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos; +} + +void ImGui::SetCursorScreenPos(const ImVec2& pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. +// Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'. +ImVec2 ImGui::GetCursorPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos - window->Pos + window->Scroll; +} + +float ImGui::GetCursorPosX() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x; +} + +float ImGui::GetCursorPosY() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y; +} + +void ImGui::SetCursorPos(const ImVec2& local_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = window->Pos - window->Scroll + local_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +void ImGui::SetCursorPosX(float x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x; + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); +} + +void ImGui::SetCursorPosY(float y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y; + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); +} + +ImVec2 ImGui::GetCursorStartPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorStartPos - window->Pos; +} + +void ImGui::Indent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x; +} + +void ImGui::Unindent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x; +} + +// Affect large frame+labels widgets only. +void ImGui::SetNextItemWidth(float item_width) +{ + ImGuiContext& g = *GImGui; + g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasWidth; + g.NextItemData.Width = item_width; +} + +// FIXME: Remove the == 0.0f behavior? +void ImGui::PushItemWidth(float item_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width + window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); + g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth; +} + +void ImGui::PushMultiItemsWidths(int components, float w_full) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiStyle& style = g.Style; + const float w_item_one = ImMax(1.0f, IM_FLOOR((w_full - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components)); + const float w_item_last = ImMax(1.0f, IM_FLOOR(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components - 1))); + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width + window->DC.ItemWidthStack.push_back(w_item_last); + for (int i = 0; i < components - 2; i++) + window->DC.ItemWidthStack.push_back(w_item_one); + window->DC.ItemWidth = (components == 1) ? w_item_last : w_item_one; + g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth; +} + +void ImGui::PopItemWidth() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidth = window->DC.ItemWidthStack.back(); + window->DC.ItemWidthStack.pop_back(); +} + +// Calculate default item width given value passed to PushItemWidth() or SetNextItemWidth(). +// The SetNextItemWidth() data is generally cleared/consumed by ItemAdd() or NextItemData.ClearFlags() +float ImGui::CalcItemWidth() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + float w; + if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth) + w = g.NextItemData.Width; + else + w = window->DC.ItemWidth; + if (w < 0.0f) + { + float region_max_x = GetContentRegionMaxAbs().x; + w = ImMax(1.0f, region_max_x - window->DC.CursorPos.x + w); + } + w = IM_FLOOR(w); + return w; +} + +// [Internal] Calculate full item size given user provided 'size' parameter and default width/height. Default width is often == CalcItemWidth(). +// Those two functions CalcItemWidth vs CalcItemSize are awkwardly named because they are not fully symmetrical. +// Note that only CalcItemWidth() is publicly exposed. +// The 4.0f here may be changed to match CalcItemWidth() and/or BeginChild() (right now we have a mismatch which is harmless but undesirable) +ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_w, float default_h) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + + ImVec2 region_max; + if (size.x < 0.0f || size.y < 0.0f) + region_max = GetContentRegionMaxAbs(); + + if (size.x == 0.0f) + size.x = default_w; + else if (size.x < 0.0f) + size.x = ImMax(4.0f, region_max.x - window->DC.CursorPos.x + size.x); + + if (size.y == 0.0f) + size.y = default_h; + else if (size.y < 0.0f) + size.y = ImMax(4.0f, region_max.y - window->DC.CursorPos.y + size.y); + + return size; +} + +float ImGui::GetTextLineHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize; +} + +float ImGui::GetTextLineHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.ItemSpacing.y; +} + +float ImGui::GetFrameHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f; +} + +float ImGui::GetFrameHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; +} + +// FIXME: All the Contents Region function are messy or misleading. WE WILL AIM TO OBSOLETE ALL OF THEM WITH A NEW "WORK RECT" API. Thanks for your patience! + +// FIXME: This is in window space (not screen space!). +ImVec2 ImGui::GetContentRegionMax() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImVec2 mx = window->ContentRegionRect.Max - window->Pos; + if (window->DC.CurrentColumns || g.CurrentTable) + mx.x = window->WorkRect.Max.x - window->Pos.x; + return mx; +} + +// [Internal] Absolute coordinate. Saner. This is not exposed until we finishing refactoring work rect features. +ImVec2 ImGui::GetContentRegionMaxAbs() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImVec2 mx = window->ContentRegionRect.Max; + if (window->DC.CurrentColumns || g.CurrentTable) + mx.x = window->WorkRect.Max.x; + return mx; +} + +ImVec2 ImGui::GetContentRegionAvail() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return GetContentRegionMaxAbs() - window->DC.CursorPos; +} + +// In window space (not screen space!) +ImVec2 ImGui::GetWindowContentRegionMin() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ContentRegionRect.Min - window->Pos; +} + +ImVec2 ImGui::GetWindowContentRegionMax() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ContentRegionRect.Max - window->Pos; +} + +float ImGui::GetWindowContentRegionWidth() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ContentRegionRect.GetWidth(); +} + +// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) +// Groups are currently a mishmash of functionalities which should perhaps be clarified and separated. +void ImGui::BeginGroup() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + g.GroupStack.resize(g.GroupStack.Size + 1); + ImGuiGroupData& group_data = g.GroupStack.back(); + group_data.WindowID = window->ID; + group_data.BackupCursorPos = window->DC.CursorPos; + group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; + group_data.BackupIndent = window->DC.Indent; + group_data.BackupGroupOffset = window->DC.GroupOffset; + group_data.BackupCurrLineSize = window->DC.CurrLineSize; + group_data.BackupCurrLineTextBaseOffset = window->DC.CurrLineTextBaseOffset; + group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; + group_data.BackupHoveredIdIsAlive = g.HoveredId != 0; + group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive; + group_data.EmitItem = true; + + window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x; + window->DC.Indent = window->DC.GroupOffset; + window->DC.CursorMaxPos = window->DC.CursorPos; + window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); + if (g.LogEnabled) + g.LogLinePosY = -FLT_MAX; // To enforce a carriage return +} + +void ImGui::EndGroup() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(g.GroupStack.Size > 0); // Mismatched BeginGroup()/EndGroup() calls + + ImGuiGroupData& group_data = g.GroupStack.back(); + IM_ASSERT(group_data.WindowID == window->ID); // EndGroup() in wrong window? + + ImRect group_bb(group_data.BackupCursorPos, ImMax(window->DC.CursorMaxPos, group_data.BackupCursorPos)); + + window->DC.CursorPos = group_data.BackupCursorPos; + window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); + window->DC.Indent = group_data.BackupIndent; + window->DC.GroupOffset = group_data.BackupGroupOffset; + window->DC.CurrLineSize = group_data.BackupCurrLineSize; + window->DC.CurrLineTextBaseOffset = group_data.BackupCurrLineTextBaseOffset; + if (g.LogEnabled) + g.LogLinePosY = -FLT_MAX; // To enforce a carriage return + + if (!group_data.EmitItem) + { + g.GroupStack.pop_back(); + return; + } + + window->DC.CurrLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. + ItemSize(group_bb.GetSize()); + ItemAdd(group_bb, 0); + + // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group. + // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets. + // Also if you grep for LastItemId you'll notice it is only used in that context. + // (The two tests not the same because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the frame.) + const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId; + const bool group_contains_prev_active_id = (group_data.BackupActiveIdPreviousFrameIsAlive == false) && (g.ActiveIdPreviousFrameIsAlive == true); + if (group_contains_curr_active_id) + window->DC.LastItemId = g.ActiveId; + else if (group_contains_prev_active_id) + window->DC.LastItemId = g.ActiveIdPreviousFrame; + window->DC.LastItemRect = group_bb; + + // Forward Hovered flag + const bool group_contains_curr_hovered_id = (group_data.BackupHoveredIdIsAlive == false) && g.HoveredId != 0; + if (group_contains_curr_hovered_id) + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + + // Forward Edited flag + if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame) + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited; + + // Forward Deactivated flag + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDeactivated; + if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame) + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Deactivated; + + g.GroupStack.pop_back(); + //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] +} + + +//----------------------------------------------------------------------------- +// [SECTION] SCROLLING +//----------------------------------------------------------------------------- + +// Helper to snap on edges when aiming at an item very close to the edge, +// So the difference between WindowPadding and ItemSpacing will be in the visible area after scrolling. +// When we refactor the scrolling API this may be configurable with a flag? +// Note that the effect for this won't be visible on X axis with default Style settings as WindowPadding.x == ItemSpacing.x by default. +static float CalcScrollEdgeSnap(float target, float snap_min, float snap_max, float snap_threshold, float center_ratio) +{ + if (target <= snap_min + snap_threshold) + return ImLerp(snap_min, target, center_ratio); + if (target >= snap_max - snap_threshold) + return ImLerp(target, snap_max, center_ratio); + return target; +} + +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) +{ + ImVec2 scroll = window->Scroll; + if (window->ScrollTarget.x < FLT_MAX) + { + float decoration_total_width = window->ScrollbarSizes.x; + float center_x_ratio = window->ScrollTargetCenterRatio.x; + float scroll_target_x = window->ScrollTarget.x; + if (window->ScrollTargetEdgeSnapDist.x > 0.0f) + { + float snap_x_min = 0.0f; + float snap_x_max = window->ScrollMax.x + window->SizeFull.x - decoration_total_width; + scroll_target_x = CalcScrollEdgeSnap(scroll_target_x, snap_x_min, snap_x_max, window->ScrollTargetEdgeSnapDist.x, center_x_ratio); + } + scroll.x = scroll_target_x - center_x_ratio * (window->SizeFull.x - decoration_total_width); + } + if (window->ScrollTarget.y < FLT_MAX) + { + float decoration_total_height = window->TitleBarHeight() + window->MenuBarHeight() + window->ScrollbarSizes.y; + float center_y_ratio = window->ScrollTargetCenterRatio.y; + float scroll_target_y = window->ScrollTarget.y; + if (window->ScrollTargetEdgeSnapDist.y > 0.0f) + { + float snap_y_min = 0.0f; + float snap_y_max = window->ScrollMax.y + window->SizeFull.y - decoration_total_height; + scroll_target_y = CalcScrollEdgeSnap(scroll_target_y, snap_y_min, snap_y_max, window->ScrollTargetEdgeSnapDist.y, center_y_ratio); + } + scroll.y = scroll_target_y - center_y_ratio * (window->SizeFull.y - decoration_total_height); + } + scroll.x = IM_FLOOR(ImMax(scroll.x, 0.0f)); + scroll.y = IM_FLOOR(ImMax(scroll.y, 0.0f)); + if (!window->Collapsed && !window->SkipItems) + { + scroll.x = ImMin(scroll.x, window->ScrollMax.x); + scroll.y = ImMin(scroll.y, window->ScrollMax.y); + } + return scroll; +} + +// Scroll to keep newly navigated item fully into view +ImVec2 ImGui::ScrollToBringRectIntoView(ImGuiWindow* window, const ImRect& item_rect) +{ + ImGuiContext& g = *GImGui; + ImRect window_rect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1)); + //GetForegroundDrawList(window)->AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG] + + ImVec2 delta_scroll; + if (!window_rect.Contains(item_rect)) + { + if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x) + SetScrollFromPosX(window, item_rect.Min.x - window->Pos.x - g.Style.ItemSpacing.x, 0.0f); + else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x) + SetScrollFromPosX(window, item_rect.Max.x - window->Pos.x + g.Style.ItemSpacing.x, 1.0f); + if (item_rect.Min.y < window_rect.Min.y) + SetScrollFromPosY(window, item_rect.Min.y - window->Pos.y - g.Style.ItemSpacing.y, 0.0f); + else if (item_rect.Max.y >= window_rect.Max.y) + SetScrollFromPosY(window, item_rect.Max.y - window->Pos.y + g.Style.ItemSpacing.y, 1.0f); + + ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window); + delta_scroll = next_scroll - window->Scroll; + } + + // Also scroll parent window to keep us into view if necessary + if (window->Flags & ImGuiWindowFlags_ChildWindow) + delta_scroll += ScrollToBringRectIntoView(window->ParentWindow, ImRect(item_rect.Min - delta_scroll, item_rect.Max - delta_scroll)); + + return delta_scroll; +} + +float ImGui::GetScrollX() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Scroll.x; +} + +float ImGui::GetScrollY() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Scroll.y; +} + +float ImGui::GetScrollMaxX() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ScrollMax.x; +} + +float ImGui::GetScrollMaxY() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ScrollMax.y; +} + +void ImGui::SetScrollX(ImGuiWindow* window, float scroll_x) +{ + window->ScrollTarget.x = scroll_x; + window->ScrollTargetCenterRatio.x = 0.0f; + window->ScrollTargetEdgeSnapDist.x = 0.0f; +} + +void ImGui::SetScrollY(ImGuiWindow* window, float scroll_y) +{ + window->ScrollTarget.y = scroll_y; + window->ScrollTargetCenterRatio.y = 0.0f; + window->ScrollTargetEdgeSnapDist.y = 0.0f; +} + +void ImGui::SetScrollX(float scroll_x) +{ + ImGuiContext& g = *GImGui; + SetScrollX(g.CurrentWindow, scroll_x); +} + +void ImGui::SetScrollY(float scroll_y) +{ + ImGuiContext& g = *GImGui; + SetScrollY(g.CurrentWindow, scroll_y); +} + +// Note that a local position will vary depending on initial scroll value, +// This is a little bit confusing so bear with us: +// - local_pos = (absolution_pos - window->Pos) +// - So local_x/local_y are 0.0f for a position at the upper-left corner of a window, +// and generally local_x/local_y are >(padding+decoration) && <(size-padding-decoration) when in the visible area. +// - They mostly exists because of legacy API. +// Following the rules above, when trying to work with scrolling code, consider that: +// - SetScrollFromPosY(0.0f) == SetScrollY(0.0f + scroll.y) == has no effect! +// - SetScrollFromPosY(-scroll.y) == SetScrollY(-scroll.y + scroll.y) == SetScrollY(0.0f) == reset scroll. Of course writing SetScrollY(0.0f) directly then makes more sense +// We store a target position so centering and clamping can occur on the next frame when we are guaranteed to have a known window size +void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio) +{ + IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f); + window->ScrollTarget.x = IM_FLOOR(local_x + window->Scroll.x); // Convert local position to scroll offset + window->ScrollTargetCenterRatio.x = center_x_ratio; + window->ScrollTargetEdgeSnapDist.x = 0.0f; +} + +void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio) +{ + IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); + const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); // FIXME: Would be nice to have a more standardized access to our scrollable/client rect; + local_y -= decoration_up_height; + window->ScrollTarget.y = IM_FLOOR(local_y + window->Scroll.y); // Convert local position to scroll offset + window->ScrollTargetCenterRatio.y = center_y_ratio; + window->ScrollTargetEdgeSnapDist.y = 0.0f; +} + +void ImGui::SetScrollFromPosX(float local_x, float center_x_ratio) +{ + ImGuiContext& g = *GImGui; + SetScrollFromPosX(g.CurrentWindow, local_x, center_x_ratio); +} + +void ImGui::SetScrollFromPosY(float local_y, float center_y_ratio) +{ + ImGuiContext& g = *GImGui; + SetScrollFromPosY(g.CurrentWindow, local_y, center_y_ratio); +} + +// center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item. +void ImGui::SetScrollHereX(float center_x_ratio) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + float spacing_x = ImMax(window->WindowPadding.x, g.Style.ItemSpacing.x); + float target_pos_x = ImLerp(window->DC.LastItemRect.Min.x - spacing_x, window->DC.LastItemRect.Max.x + spacing_x, center_x_ratio); + SetScrollFromPosX(window, target_pos_x - window->Pos.x, center_x_ratio); // Convert from absolute to local pos + + // Tweak: snap on edges when aiming at an item very close to the edge + window->ScrollTargetEdgeSnapDist.x = ImMax(0.0f, window->WindowPadding.x - spacing_x); +} + +// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. +void ImGui::SetScrollHereY(float center_y_ratio) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + float spacing_y = ImMax(window->WindowPadding.y, g.Style.ItemSpacing.y); + float target_pos_y = ImLerp(window->DC.CursorPosPrevLine.y - spacing_y, window->DC.CursorPosPrevLine.y + window->DC.PrevLineSize.y + spacing_y, center_y_ratio); + SetScrollFromPosY(window, target_pos_y - window->Pos.y, center_y_ratio); // Convert from absolute to local pos + + // Tweak: snap on edges when aiming at an item very close to the edge + window->ScrollTargetEdgeSnapDist.y = ImMax(0.0f, window->WindowPadding.y - spacing_y); +} + +//----------------------------------------------------------------------------- +// [SECTION] TOOLTIPS +//----------------------------------------------------------------------------- + +void ImGui::BeginTooltip() +{ + BeginTooltipEx(ImGuiWindowFlags_None, ImGuiTooltipFlags_None); +} + +void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, ImGuiTooltipFlags tooltip_flags) +{ + ImGuiContext& g = *GImGui; + + if (g.DragDropWithinSource || g.DragDropWithinTarget) + { + // The default tooltip position is a little offset to give space to see the context menu (it's also clamped within the current viewport/monitor) + // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor. + // Whatever we do we want to call SetNextWindowPos() to enforce a tooltip position and disable clipping the tooltip without our display area, like regular tooltip do. + //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding; + ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale); + SetNextWindowPos(tooltip_pos); + SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f); + //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :( + tooltip_flags |= ImGuiTooltipFlags_OverridePreviousTooltip; + } + + char window_name[16]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); + if (tooltip_flags & ImGuiTooltipFlags_OverridePreviousTooltip) + if (ImGuiWindow* window = FindWindowByName(window_name)) + if (window->Active) + { + // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. + window->Hidden = true; + window->HiddenFramesCanSkipItems = 1; // FIXME: This may not be necessary? + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); + } + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize; + Begin(window_name, NULL, flags | extra_flags); +} + +void ImGui::EndTooltip() +{ + IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls + End(); +} + +void ImGui::SetTooltipV(const char* fmt, va_list args) +{ + BeginTooltipEx(0, ImGuiTooltipFlags_OverridePreviousTooltip); + TextV(fmt, args); + EndTooltip(); +} + +void ImGui::SetTooltip(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + SetTooltipV(fmt, args); + va_end(args); +} + +//----------------------------------------------------------------------------- +// [SECTION] POPUPS +//----------------------------------------------------------------------------- + +// Supported flags: ImGuiPopupFlags_AnyPopupId, ImGuiPopupFlags_AnyPopupLevel +bool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags) +{ + ImGuiContext& g = *GImGui; + if (popup_flags & ImGuiPopupFlags_AnyPopupId) + { + // Return true if any popup is open at the current BeginPopup() level of the popup stack + // This may be used to e.g. test for another popups already opened to handle popups priorities at the same level. + IM_ASSERT(id == 0); + if (popup_flags & ImGuiPopupFlags_AnyPopupLevel) + return g.OpenPopupStack.Size > 0; + else + return g.OpenPopupStack.Size > g.BeginPopupStack.Size; + } + else + { + if (popup_flags & ImGuiPopupFlags_AnyPopupLevel) + { + // Return true if the popup is open anywhere in the popup stack + for (int n = 0; n < g.OpenPopupStack.Size; n++) + if (g.OpenPopupStack[n].PopupId == id) + return true; + return false; + } + else + { + // Return true if the popup is open at the current BeginPopup() level of the popup stack (this is the most-common query) + return g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].PopupId == id; + } + } +} + +bool ImGui::IsPopupOpen(const char* str_id, ImGuiPopupFlags popup_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiID id = (popup_flags & ImGuiPopupFlags_AnyPopupId) ? 0 : g.CurrentWindow->GetID(str_id); + if ((popup_flags & ImGuiPopupFlags_AnyPopupLevel) && id != 0) + IM_ASSERT(0 && "Cannot use IsPopupOpen() with a string id and ImGuiPopupFlags_AnyPopupLevel."); // But non-string version is legal and used internally + return IsPopupOpen(id, popup_flags); +} + +ImGuiWindow* ImGui::GetTopMostPopupModal() +{ + ImGuiContext& g = *GImGui; + for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--) + if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window) + if (popup->Flags & ImGuiWindowFlags_Modal) + return popup; + return NULL; +} + +void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags) +{ + ImGuiContext& g = *GImGui; + OpenPopupEx(g.CurrentWindow->GetID(str_id), popup_flags); +} + +void ImGui::OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags) +{ + OpenPopupEx(id, popup_flags); +} + +// Mark popup as open (toggle toward open state). +// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. +// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). +// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) +void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = g.CurrentWindow; + const int current_stack_size = g.BeginPopupStack.Size; + + if (popup_flags & ImGuiPopupFlags_NoOpenOverExistingPopup) + if (IsPopupOpen(0u, ImGuiPopupFlags_AnyPopupId)) + return; + + ImGuiPopupData popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. + popup_ref.PopupId = id; + popup_ref.Window = NULL; + popup_ref.SourceWindow = g.NavWindow; + popup_ref.OpenFrameCount = g.FrameCount; + popup_ref.OpenParentId = parent_window->IDStack.back(); + popup_ref.OpenPopupPos = NavCalcPreferredRefPos(); + popup_ref.OpenMousePos = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : popup_ref.OpenPopupPos; + + IMGUI_DEBUG_LOG_POPUP("OpenPopupEx(0x%08X)\n", id); + if (g.OpenPopupStack.Size < current_stack_size + 1) + { + g.OpenPopupStack.push_back(popup_ref); + } + else + { + // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui + // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing + // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand. + if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) + { + g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount; + } + else + { + // Close child popups if any, then flag popup for open/reopen + ClosePopupToLevel(current_stack_size, false); + g.OpenPopupStack.push_back(popup_ref); + } + + // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow(). + // This is equivalent to what ClosePopupToLevel() does. + //if (g.OpenPopupStack[current_stack_size].PopupId == id) + // FocusWindow(parent_window); + } +} + +// When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. +// This function closes any popups that are over 'ref_window'. +void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size == 0) + return; + + // Don't close our own child popup windows. + int popup_count_to_keep = 0; + if (ref_window) + { + // Find the highest popup which is a descendant of the reference window (generally reference window = NavWindow) + for (; popup_count_to_keep < g.OpenPopupStack.Size; popup_count_to_keep++) + { + ImGuiPopupData& popup = g.OpenPopupStack[popup_count_to_keep]; + if (!popup.Window) + continue; + IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); + if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) + continue; + + // Trim the stack unless the popup is a direct parent of the reference window (the reference window is often the NavWindow) + // - With this stack of window, clicking/focusing Popup1 will close Popup2 and Popup3: + // Window -> Popup1 -> Popup2 -> Popup3 + // - Each popups may contain child windows, which is why we compare ->RootWindow! + // Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child + bool ref_window_is_descendent_of_popup = false; + for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++) + if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window) + if (popup_window->RootWindow == ref_window->RootWindow) + { + ref_window_is_descendent_of_popup = true; + break; + } + if (!ref_window_is_descendent_of_popup) + break; + } + } + if (popup_count_to_keep < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the statement below + { + IMGUI_DEBUG_LOG_POPUP("ClosePopupsOverWindow(\"%s\") -> ClosePopupToLevel(%d)\n", ref_window->Name, popup_count_to_keep); + ClosePopupToLevel(popup_count_to_keep, restore_focus_to_window_under_popup); + } +} + +void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup) +{ + ImGuiContext& g = *GImGui; + IMGUI_DEBUG_LOG_POPUP("ClosePopupToLevel(%d), restore_focus_to_window_under_popup=%d\n", remaining, restore_focus_to_window_under_popup); + IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size); + + // Trim open popup stack + ImGuiWindow* focus_window = g.OpenPopupStack[remaining].SourceWindow; + ImGuiWindow* popup_window = g.OpenPopupStack[remaining].Window; + g.OpenPopupStack.resize(remaining); + + if (restore_focus_to_window_under_popup) + { + if (focus_window && !focus_window->WasActive && popup_window) + { + // Fallback + FocusTopMostWindowUnderOne(popup_window, NULL); + } + else + { + if (g.NavLayer == ImGuiNavLayer_Main && focus_window) + focus_window = NavRestoreLastChildNavWindow(focus_window); + FocusWindow(focus_window); + } + } +} + +// Close the popup we have begin-ed into. +void ImGui::CloseCurrentPopup() +{ + ImGuiContext& g = *GImGui; + int popup_idx = g.BeginPopupStack.Size - 1; + if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.BeginPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId) + return; + + // Closing a menu closes its top-most parent popup (unless a modal) + while (popup_idx > 0) + { + ImGuiWindow* popup_window = g.OpenPopupStack[popup_idx].Window; + ImGuiWindow* parent_popup_window = g.OpenPopupStack[popup_idx - 1].Window; + bool close_parent = false; + if (popup_window && (popup_window->Flags & ImGuiWindowFlags_ChildMenu)) + if (parent_popup_window == NULL || !(parent_popup_window->Flags & ImGuiWindowFlags_Modal)) + close_parent = true; + if (!close_parent) + break; + popup_idx--; + } + IMGUI_DEBUG_LOG_POPUP("CloseCurrentPopup %d -> %d\n", g.BeginPopupStack.Size - 1, popup_idx); + ClosePopupToLevel(popup_idx, true); + + // A common pattern is to close a popup when selecting a menu item/selectable that will open another window. + // To improve this usage pattern, we avoid nav highlight for a single frame in the parent window. + // Similarly, we could avoid mouse hover highlight in this window but it is less visually problematic. + if (ImGuiWindow* window = g.NavWindow) + window->DC.NavHideHighlightOneFrame = true; +} + +// Attention! BeginPopup() adds default flags which BeginPopupEx()! +bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + if (!IsPopupOpen(id, ImGuiPopupFlags_None)) + { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + return false; + } + + char name[20]; + if (flags & ImGuiWindowFlags_ChildMenu) + ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth + else + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame + + flags |= ImGuiWindowFlags_Popup; + bool is_open = Begin(name, NULL, flags); + if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) + EndPopup(); + + return is_open; +} + +bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size <= g.BeginPopupStack.Size) // Early out for performance + { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + return false; + } + flags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings; + return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags); +} + +// If 'p_open' is specified for a modal popup window, the popup will have a regular close button which will close the popup. +// Note that popup visibility status is owned by Dear ImGui (and manipulated with e.g. OpenPopup) so the actual value of *p_open is meaningless here. +bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiID id = window->GetID(name); + if (!IsPopupOpen(id, ImGuiPopupFlags_None)) + { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + return false; + } + + // Center modal windows by default for increased visibility + // (this won't really last as settings will kick in, and is mostly for backward compatibility. user may do the same themselves) + // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0) + { + const ImGuiViewport* viewport = GetMainViewport(); + SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); + } + + flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse; + const bool is_open = Begin(name, p_open, flags); + if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + { + EndPopup(); + if (is_open) + ClosePopupToLevel(g.BeginPopupStack.Size, true); + return false; + } + return is_open; +} + +void ImGui::EndPopup() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls + IM_ASSERT(g.BeginPopupStack.Size > 0); + + // Make all menus and popups wrap around for now, may need to expose that policy. + if (g.NavWindow == window) + NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY); + + // Child-popups don't need to be laid out + IM_ASSERT(g.WithinEndChild == false); + if (window->Flags & ImGuiWindowFlags_ChildWindow) + g.WithinEndChild = true; + End(); + g.WithinEndChild = false; +} + +// Helper to open a popup if mouse button is released over the item +// - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() +void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + OpenPopupEx(id, popup_flags); + } +} + +// This is a helper to handle the simplest case of associating one named popup to one given widget. +// - To create a popup associated to the last item, you generally want to pass a NULL value to str_id. +// - To create a popup with a specific identifier, pass it in str_id. +// - This is useful when using using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a Text() call. +// - This is useful when multiple code locations may want to manipulate/open the same popup, given an explicit id. +// - You may want to handle the whole on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// This is essentially the same as: +// id = str_id ? GetID(str_id) : GetItemID(); +// OpenPopupOnItemClick(str_id); +// return BeginPopup(id); +// Which is essentially the same as: +// id = str_id ? GetID(str_id) : GetItemID(); +// if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) +// OpenPopup(id); +// return BeginPopup(id); +// The main difference being that this is tweaked to avoid computing the ID twice. +bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (window->SkipItems) + return false; + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + OpenPopupEx(id, popup_flags); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_flags) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (!str_id) + str_id = "window_context"; + ImGuiID id = window->GetID(str_id); + int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); + if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) + OpenPopupEx(id, popup_flags); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flags) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (!str_id) + str_id = "void_context"; + ImGuiID id = window->GetID(str_id); + int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); + if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + if (GetTopMostPopupModal() == NULL) + OpenPopupEx(id, popup_flags); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); +} + +// r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) +// r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. +// (r_outer is usually equivalent to the viewport rectangle minus padding, but when multi-viewports are enabled and monitor +// information are available, it may represent the entire platform monitor from the frame of reference of the current viewport. +// this allows us to have tooltips/popups displayed out of the parent viewport.) +ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy) +{ + ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); + //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255)); + //GetForegroundDrawList()->AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255)); + + // Combo Box policy (we want a connecting edge) + if (policy == ImGuiPopupPositionPolicy_ComboBox) + { + const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + ImVec2 pos; + if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default) + if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right + if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left + if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left + if (!r_outer.Contains(ImRect(pos, pos + size))) + continue; + *last_dir = dir; + return pos; + } + } + + // Tooltip and Default popup policy + // (Always first try the direction we used on the last frame, if any) + if (policy == ImGuiPopupPositionPolicy_Tooltip || policy == ImGuiPopupPositionPolicy_Default) + { + const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + + const float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); + const float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); + + // If there not enough room on one axis, there's no point in positioning on a side on this axis (e.g. when not enough width, use a top/bottom position to maximize available width) + if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right)) + continue; + if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down)) + continue; + + ImVec2 pos; + pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; + pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; + + // Clamp top-left corner of popup + pos.x = ImMax(pos.x, r_outer.Min.x); + pos.y = ImMax(pos.y, r_outer.Min.y); + + *last_dir = dir; + return pos; + } + } + + // Fallback when not enough room: + *last_dir = ImGuiDir_None; + + // For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. + if (policy == ImGuiPopupPositionPolicy_Tooltip) + return ref_pos + ImVec2(2, 2); + + // Otherwise try to keep within display + ImVec2 pos = ref_pos; + pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); + pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); + return pos; +} + +// Note that this is used for popups, which can overlap the non work-area of individual viewports. +ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(window); + ImRect r_screen = ((ImGuiViewportP*)(void*)GetMainViewport())->GetMainRect(); + ImVec2 padding = g.Style.DisplaySafeAreaPadding; + r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f)); + return r_screen; +} + +ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + ImRect r_outer = GetWindowAllowedExtentRect(window); + if (window->Flags & ImGuiWindowFlags_ChildMenu) + { + // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds. + // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. + IM_ASSERT(g.CurrentWindow == window); + ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2]; + float horizontal_overlap = g.Style.ItemInnerSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x). + ImRect r_avoid; + if (parent_window->DC.MenuBarAppending) + r_avoid = ImRect(-FLT_MAX, parent_window->ClipRect.Min.y, FLT_MAX, parent_window->ClipRect.Max.y); // Avoid parent menu-bar. If we wanted multi-line menu-bar, we may instead want to have the calling window setup e.g. a NextWindowData.PosConstraintAvoidRect field + else + r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX); + return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Default); + } + if (window->Flags & ImGuiWindowFlags_Popup) + { + ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1); + return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Default); + } + if (window->Flags & ImGuiWindowFlags_Tooltip) + { + // Position tooltip (always follows mouse) + float sc = g.Style.MouseCursorScale; + ImVec2 ref_pos = NavCalcPreferredRefPos(); + ImRect r_avoid; + if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)) + r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); + else + r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. + return FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Tooltip); + } + IM_ASSERT(0); + return window->Pos; +} + +//----------------------------------------------------------------------------- +// [SECTION] KEYBOARD/GAMEPAD NAVIGATION +//----------------------------------------------------------------------------- + +// FIXME-NAV: The existence of SetNavID vs SetFocusID properly needs to be clarified/reworked. +void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindow != NULL); + IM_ASSERT(nav_layer == ImGuiNavLayer_Main || nav_layer == ImGuiNavLayer_Menu); + g.NavId = id; + g.NavLayer = nav_layer; + g.NavFocusScopeId = focus_scope_id; + g.NavWindow->NavLastIds[nav_layer] = id; + g.NavWindow->NavRectRel[nav_layer] = rect_rel; + //g.NavDisableHighlight = false; + //g.NavDisableMouseHover = g.NavMousePosDirty = true; +} + +void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(id != 0); + + // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and window->DC.NavFocusScopeIdCurrent are valid. + // Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text) + const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent; + if (g.NavWindow != window) + g.NavInitRequest = false; + g.NavWindow = window; + g.NavId = id; + g.NavLayer = nav_layer; + g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; + window->NavLastIds[nav_layer] = id; + if (window->DC.LastItemId == id) + window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); + + if (g.ActiveIdSource == ImGuiInputSource_Nav) + g.NavDisableMouseHover = true; + else + g.NavDisableHighlight = true; +} + +ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy) +{ + if (ImFabs(dx) > ImFabs(dy)) + return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left; + return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up; +} + +static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1) +{ + if (a1 < b0) + return a1 - b0; + if (b1 < a0) + return a0 - b1; + return 0.0f; +} + +static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect) +{ + if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) + { + r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y); + r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y); + } + else + { + r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x); + r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x); + } +} + +// Scoring function for gamepad/keyboard directional navigation. Based on https://gist.github.com/rygorous/6981057 +static bool ImGui::NavScoreItem(ImGuiNavItemData* result, ImRect cand) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavLayer != window->DC.NavLayerCurrent) + return false; + + const ImRect& curr = g.NavScoringRect; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) + g.NavScoringCount++; + + // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring + if (window->ParentWindow == g.NavWindow) + { + IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened); + if (!window->ClipRect.Overlaps(cand)) + return false; + cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window + } + + // We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items) + // For example, this ensure that items in one column are not reached when moving vertically from items in another column. + NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect); + + // Compute distance between boxes + // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. + float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); + float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items + if (dby != 0.0f && dbx != 0.0f) + dbx = (dbx / 1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); + float dist_box = ImFabs(dbx) + ImFabs(dby); + + // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter) + float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x); + float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y); + float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee) + + // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance + ImGuiDir quadrant; + float dax = 0.0f, day = 0.0f, dist_axial = 0.0f; + if (dbx != 0.0f || dby != 0.0f) + { + // For non-overlapping boxes, use distance between boxes + dax = dbx; + day = dby; + dist_axial = dist_box; + quadrant = ImGetDirQuadrantFromDelta(dbx, dby); + } + else if (dcx != 0.0f || dcy != 0.0f) + { + // For overlapping boxes with different centers, use distance between centers + dax = dcx; + day = dcy; + dist_axial = dist_center; + quadrant = ImGetDirQuadrantFromDelta(dcx, dcy); + } + else + { + // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) + quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; + } + +#if IMGUI_DEBUG_NAV_SCORING + char buf[128]; + if (IsMouseHoveringRect(cand.Min, cand.Max)) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]); + ImDrawList* draw_list = GetForegroundDrawList(window); + draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100)); + draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200)); + draw_list->AddRectFilled(cand.Max - ImVec2(4, 4), cand.Max + CalcTextSize(buf) + ImVec2(4, 4), IM_COL32(40,0,0,150)); + draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf); + } + else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate. + { + if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; } + if (quadrant == g.NavMoveDir) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); + ImDrawList* draw_list = GetForegroundDrawList(window); + draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200)); + draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf); + } + } +#endif + + // Is it in the quadrant we're interesting in moving to? + bool new_best = false; + if (quadrant == g.NavMoveDir) + { + // Does it beat the current best candidate? + if (dist_box < result->DistBox) + { + result->DistBox = dist_box; + result->DistCenter = dist_center; + return true; + } + if (dist_box == result->DistBox) + { + // Try using distance between center points to break ties + if (dist_center < result->DistCenter) + { + result->DistCenter = dist_center; + new_best = true; + } + else if (dist_center == result->DistCenter) + { + // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items + // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index), + // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis. + if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance + new_best = true; + } + } + } + + // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches + // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness) + // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too. + // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward. + // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option? + if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match + if (g.NavLayer == ImGuiNavLayer_Menu && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f)) + { + result->DistAxial = dist_axial; + new_best = true; + } + + return new_best; +} + +static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel) +{ + result->Window = window; + result->ID = id; + result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; + result->RectRel = nav_bb_rel; +} + +// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) +static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) +{ + ImGuiContext& g = *GImGui; + //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. + // return; + + const ImGuiItemFlags item_flags = g.CurrentItemFlags; + const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); + + // Process Init Request + if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) + { + // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) + { + g.NavInitResultId = id; + g.NavInitResultRectRel = nav_bb_rel; + } + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) + { + g.NavInitRequest = false; // Found a match, clear request + NavUpdateAnyRequestFlag(); + } + } + + // Process Move Request (scoring for navigation) + // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy) + if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & (ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNav))) + { + ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; +#if IMGUI_DEBUG_NAV_SCORING + // [DEBUG] Score all items in NavWindow at all times + if (!g.NavMoveRequest) + g.NavMoveDir = g.NavMoveDirLast; + bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest; +#else + bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); +#endif + if (new_best) + NavApplyItemToResult(result, window, id, nav_bb_rel); + + // Features like PageUp/PageDown need to maintain a separate score for the visible set of items. + const float VISIBLE_RATIO = 0.70f; + if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) + if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) + if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb)) + NavApplyItemToResult(&g.NavMoveResultLocalVisibleSet, window, id, nav_bb_rel); + } + + // Update window-relative bounding box of navigated item + if (g.NavId == id) + { + g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. + g.NavLayer = window->DC.NavLayerCurrent; + g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; + g.NavIdIsAlive = true; + window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) + } +} + +bool ImGui::NavMoveRequestButNoResultYet() +{ + ImGuiContext& g = *GImGui; + return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; +} + +void ImGui::NavMoveRequestCancel() +{ + ImGuiContext& g = *GImGui; + g.NavMoveRequest = false; + NavUpdateAnyRequestFlag(); +} + +void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None); + NavMoveRequestCancel(); + g.NavMoveDir = move_dir; + g.NavMoveClipDir = clip_dir; + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + g.NavMoveRequestFlags = move_flags; + g.NavWindow->NavRectRel[g.NavLayer] = bb_rel; +} + +void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags) +{ + ImGuiContext& g = *GImGui; + + // Navigation wrap-around logic is delayed to the end of the frame because this operation is only valid after entire + // popup is assembled and in case of appended popups it is not clear which EndPopup() call is final. + g.NavWrapRequestWindow = window; + g.NavWrapRequestFlags = move_flags; +} + +// FIXME: This could be replaced by updating a frame number in each window when (window == NavWindow) and (NavLayer == 0). +// This way we could find the last focused window among our children. It would be much less confusing this way? +static void ImGui::NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window) +{ + ImGuiWindow* parent = nav_window; + while (parent && parent->RootWindow != parent && (parent->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + parent = parent->ParentWindow; + if (parent && parent != nav_window) + parent->NavLastChildNavWindow = nav_window; +} + +// Restore the last focused child. +// Call when we are expected to land on the Main Layer (0) after FocusWindow() +static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window) +{ + if (window->NavLastChildNavWindow && window->NavLastChildNavWindow->WasActive) + return window->NavLastChildNavWindow; + return window; +} + +void ImGui::NavRestoreLayer(ImGuiNavLayer layer) +{ + ImGuiContext& g = *GImGui; + if (layer == ImGuiNavLayer_Main) + g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow); + ImGuiWindow* window = g.NavWindow; + if (window->NavLastIds[layer] != 0) + { + SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); + g.NavDisableHighlight = false; + g.NavDisableMouseHover = g.NavMousePosDirty = true; + } + else + { + g.NavLayer = layer; + NavInitWindow(window, true); + } +} + +static inline void ImGui::NavUpdateAnyRequestFlag() +{ + ImGuiContext& g = *GImGui; + g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL); + if (g.NavAnyRequest) + IM_ASSERT(g.NavWindow != NULL); +} + +// This needs to be called before we submit any widget (aka in or before Begin) +void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window == g.NavWindow); + + if (window->Flags & ImGuiWindowFlags_NoNavInputs) + { + g.NavId = g.NavFocusScopeId = 0; + return; + } + + bool init_for_nav = false; + if (window == window->RootWindow || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) + init_for_nav = true; + IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\"%s\", layer=%d\n", init_for_nav, window->Name, g.NavLayer); + if (init_for_nav) + { + SetNavID(0, g.NavLayer, 0, ImRect()); + g.NavInitRequest = true; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavInitResultRectRel = ImRect(); + NavUpdateAnyRequestFlag(); + } + else + { + g.NavId = window->NavLastIds[0]; + g.NavFocusScopeId = 0; + } +} + +static ImVec2 ImGui::NavCalcPreferredRefPos() +{ + ImGuiContext& g = *GImGui; + if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow) + { + // Mouse (we need a fallback in case the mouse becomes invalid after being used) + if (IsMousePosValid(&g.IO.MousePos)) + return g.IO.MousePos; + return g.LastValidMousePos; + } + else + { + // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item. + const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer]; + ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x * 4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); + ImGuiViewport* viewport = GetMainViewport(); + return ImFloor(ImClamp(pos, viewport->Pos, viewport->Pos + viewport->Size)); // ImFloor() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta. + } +} + +float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode) +{ + ImGuiContext& g = *GImGui; + if (mode == ImGuiInputReadMode_Down) + return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user) + + const float t = g.IO.NavInputsDownDuration[n]; + if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input. + return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f); + if (t < 0.0f) + return 0.0f; + if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input. + return (t == 0.0f) ? 1.0f : 0.0f; + if (mode == ImGuiInputReadMode_Repeat) + return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay * 0.72f, g.IO.KeyRepeatRate * 0.80f); + if (mode == ImGuiInputReadMode_RepeatSlow) + return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay * 1.25f, g.IO.KeyRepeatRate * 2.00f); + if (mode == ImGuiInputReadMode_RepeatFast) + return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay * 0.72f, g.IO.KeyRepeatRate * 0.30f); + return 0.0f; +} + +ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor) +{ + ImVec2 delta(0.0f, 0.0f); + if (dir_sources & ImGuiNavDirSourceFlags_Keyboard) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadDPad) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadLStick) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode)); + if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta *= slow_factor; + if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= fast_factor; + return delta; +} + +static void ImGui::NavUpdate() +{ + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + + io.WantSetMousePos = false; + g.NavWrapRequestWindow = NULL; + g.NavWrapRequestFlags = ImGuiNavMoveFlags_None; +#if 0 + if (g.NavScoringCount > 0) IMGUI_DEBUG_LOG("NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); +#endif + + // Set input source as Gamepad when buttons are pressed (as some features differs when used with Gamepad vs Keyboard) + // (do it before we map Keyboard input!) + bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + if (nav_gamepad_active && g.NavInputSource != ImGuiInputSource_Gamepad) + { + if (io.NavInputs[ImGuiNavInput_Activate] > 0.0f || io.NavInputs[ImGuiNavInput_Input] > 0.0f || io.NavInputs[ImGuiNavInput_Cancel] > 0.0f || io.NavInputs[ImGuiNavInput_Menu] > 0.0f + || io.NavInputs[ImGuiNavInput_DpadLeft] > 0.0f || io.NavInputs[ImGuiNavInput_DpadRight] > 0.0f || io.NavInputs[ImGuiNavInput_DpadUp] > 0.0f || io.NavInputs[ImGuiNavInput_DpadDown] > 0.0f) + g.NavInputSource = ImGuiInputSource_Gamepad; + } + + // Update Keyboard->Nav inputs mapping + if (nav_keyboard_active) + { + #define NAV_MAP_KEY(_KEY, _NAV_INPUT) do { if (IsKeyDown(io.KeyMap[_KEY])) { io.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_Keyboard; } } while (0) + NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate ); + NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input ); + NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel ); + NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ ); + NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_); + NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ ); + NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ ); + if (io.KeyCtrl) + io.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; + if (io.KeyShift) + io.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; + + // AltGR is normally Alt+Ctrl but we can't reliably detect it (not all backends/systems/layout emit it as Alt+Ctrl) + // But also even on keyboards without AltGR we don't want Alt+Ctrl to open menu anyway. + if (io.KeyAlt && !io.KeyCtrl) + io.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; + + // We automatically cancel toggling nav layer when any text has been typed while holding Alt. (See #370) + if (io.KeyAlt && !io.KeyCtrl && g.NavWindowingToggleLayer && io.InputQueueCharacters.Size > 0) + g.NavWindowingToggleLayer = false; + + #undef NAV_MAP_KEY + } + memcpy(io.NavInputsDownDurationPrev, io.NavInputsDownDuration, sizeof(io.NavInputsDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) + io.NavInputsDownDuration[i] = (io.NavInputs[i] > 0.0f) ? (io.NavInputsDownDuration[i] < 0.0f ? 0.0f : io.NavInputsDownDuration[i] + io.DeltaTime) : -1.0f; + + // Process navigation init request (select first/default focus) + if (g.NavInitResultId != 0) + NavUpdateInitResult(); + g.NavInitRequest = false; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavJustMovedToId = 0; + + // Process navigation move request + if (g.NavMoveRequest) + NavUpdateMoveResult(); + + // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame + if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive) + { + IM_ASSERT(g.NavMoveRequest); + if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) + g.NavDisableHighlight = false; + g.NavMoveRequestForward = ImGuiNavForward_None; + } + + // Apply application mouse position movement, after we had a chance to process move request result. + if (g.NavMousePosDirty && g.NavIdIsAlive) + { + // Set mouse position given our knowledge of the navigated item position from last frame + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) + if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) + { + io.MousePos = io.MousePosPrev = NavCalcPreferredRefPos(); + io.WantSetMousePos = true; + } + g.NavMousePosDirty = false; + } + g.NavIdIsAlive = false; + g.NavJustTabbedId = 0; + IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1); + + // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0 + if (g.NavWindow) + NavSaveLastChildNavWindowIntoParent(g.NavWindow); + if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == ImGuiNavLayer_Main) + g.NavWindow->NavLastChildNavWindow = NULL; + + // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.) + NavUpdateWindowing(); + + // Set output flags for user application + io.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); + io.NavVisible = (io.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL); + + // Process NavCancel input (to close a popup, get back to parent, clear focus) + if (IsNavInputTest(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed)) + { + IMGUI_DEBUG_LOG_NAV("[nav] ImGuiNavInput_Cancel\n"); + if (g.ActiveId != 0) + { + if (!IsActiveIdUsingNavInput(ImGuiNavInput_Cancel)) + ClearActiveID(); + } + else if (g.NavLayer != ImGuiNavLayer_Main) + { + // Leave the "menu" layer + NavRestoreLayer(ImGuiNavLayer_Main); + } + else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) + { + // Exit child window + ImGuiWindow* child_window = g.NavWindow; + ImGuiWindow* parent_window = g.NavWindow->ParentWindow; + IM_ASSERT(child_window->ChildId != 0); + ImRect child_rect = child_window->Rect(); + FocusWindow(parent_window); + SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, ImRect(child_rect.Min - parent_window->Pos, child_rect.Max - parent_window->Pos)); + } + else if (g.OpenPopupStack.Size > 0) + { + // Close open popup/menu + if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) + ClosePopupToLevel(g.OpenPopupStack.Size - 1, true); + } + else + { + // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were + if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) + g.NavWindow->NavLastIds[0] = 0; + g.NavId = g.NavFocusScopeId = 0; + } + } + + // Process manual activation request + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0; + if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); + bool activate_pressed = activate_down && IsNavInputTest(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed); + if (g.ActiveId == 0 && activate_pressed) + g.NavActivateId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) + g.NavActivateDownId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed) + g.NavActivatePressedId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputTest(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed)) + g.NavInputId = g.NavId; + } + if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + g.NavDisableHighlight = true; + if (g.NavActivateId != 0) + IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + g.NavMoveRequest = false; + + // Process programmatic activation request + if (g.NavNextActivateId != 0) + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId; + g.NavNextActivateId = 0; + + // Initiate directional inputs request + if (g.NavMoveRequestForward == ImGuiNavForward_None) + { + g.NavMoveDir = ImGuiDir_None; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_None; + if (g.NavWindow && !g.NavWindowingTarget && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + const ImGuiInputReadMode read_mode = ImGuiInputReadMode_Repeat; + if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && (IsNavInputTest(ImGuiNavInput_DpadLeft, read_mode) || IsNavInputTest(ImGuiNavInput_KeyLeft_, read_mode))) { g.NavMoveDir = ImGuiDir_Left; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && (IsNavInputTest(ImGuiNavInput_DpadRight, read_mode) || IsNavInputTest(ImGuiNavInput_KeyRight_, read_mode))) { g.NavMoveDir = ImGuiDir_Right; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && (IsNavInputTest(ImGuiNavInput_DpadUp, read_mode) || IsNavInputTest(ImGuiNavInput_KeyUp_, read_mode))) { g.NavMoveDir = ImGuiDir_Up; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && (IsNavInputTest(ImGuiNavInput_DpadDown, read_mode) || IsNavInputTest(ImGuiNavInput_KeyDown_, read_mode))) { g.NavMoveDir = ImGuiDir_Down; } + } + g.NavMoveClipDir = g.NavMoveDir; + } + else + { + // Forwarding previous request (which has been modified, e.g. wrap around menus rewrite the requests with a starting rectangle at the other side of the window) + // (Preserve g.NavMoveRequestFlags, g.NavMoveClipDir which were set by the NavMoveRequestForward() function) + IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None); + IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_ForwardQueued); + IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestForward %d\n", g.NavMoveDir); + g.NavMoveRequestForward = ImGuiNavForward_ForwardActive; + } + + // Update PageUp/PageDown/Home/End scroll + // FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag? + float nav_scoring_rect_offset_y = 0.0f; + if (nav_keyboard_active) + nav_scoring_rect_offset_y = NavUpdatePageUpPageDown(); + + // If we initiate a movement request and have no current NavId, we initiate a InitDefautRequest that will be used as a fallback if the direction fails to find a match + if (g.NavMoveDir != ImGuiDir_None) + { + g.NavMoveRequest = true; + g.NavMoveRequestKeyMods = io.KeyMods; + g.NavMoveDirLast = g.NavMoveDir; + } + if (g.NavMoveRequest && g.NavId == 0) + { + IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", g.NavWindow->Name, g.NavLayer); + g.NavInitRequest = g.NavInitRequestFromMove = true; + // Reassigning with same value, we're being explicit here. + g.NavInitResultId = 0; // -V1048 + g.NavDisableHighlight = false; + } + NavUpdateAnyRequestFlag(); + + // Scrolling + if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget) + { + // *Fallback* manual-scroll with Nav directional keys when window has no navigable item + ImGuiWindow* window = g.NavWindow; + const float scroll_speed = IM_ROUND(window->CalcFontSize() * 100 * io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) + { + if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) + SetScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); + if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) + SetScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); + } + + // *Normal* Manual scroll with NavScrollXXX keys + // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. + ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f / 10.0f, 10.0f); + if (scroll_dir.x != 0.0f && window->ScrollbarX) + SetScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed)); + if (scroll_dir.y != 0.0f) + SetScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); + } + + // Reset search results + g.NavMoveResultLocal.Clear(); + g.NavMoveResultLocalVisibleSet.Clear(); + g.NavMoveResultOther.Clear(); + + // When using gamepad, we project the reference nav bounding box into window visible area. + // This is to allow resuming navigation inside the visible area after doing a large amount of scrolling, since with gamepad every movements are relative + // (can't focus a visible object like we can with the mouse). + if (g.NavMoveRequest && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main) + { + ImGuiWindow* window = g.NavWindow; + ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1)); + if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) + { + IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel\n"); + float pad = window->CalcFontSize() * 0.5f; + window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item + window->NavRectRel[g.NavLayer].ClipWithFull(window_rect_rel); + g.NavId = g.NavFocusScopeId = 0; + } + } + + // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) + ImRect nav_rect_rel = g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted() ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0); + g.NavScoringRect = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : ImRect(0, 0, 0, 0); + g.NavScoringRect.TranslateY(nav_scoring_rect_offset_y); + g.NavScoringRect.Min.x = ImMin(g.NavScoringRect.Min.x + 1.0f, g.NavScoringRect.Max.x); + g.NavScoringRect.Max.x = g.NavScoringRect.Min.x; + IM_ASSERT(!g.NavScoringRect.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem(). + //GetForegroundDrawList()->AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG] + g.NavScoringCount = 0; +#if IMGUI_DEBUG_NAV_RECTS + if (g.NavWindow) + { + ImDrawList* draw_list = GetForegroundDrawList(g.NavWindow); + if (1) { for (int layer = 0; layer < 2; layer++) draw_list->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG] + if (1) { ImU32 col = (!g.NavWindow->Hidden) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); draw_list->AddCircleFilled(p, 3.0f, col); draw_list->AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } + } +#endif +} + +static void ImGui::NavUpdateInitResult() +{ + // In very rare cases g.NavWindow may be null (e.g. clearing focus after requesting an init request, which does happen when releasing Alt while clicking on void) + ImGuiContext& g = *GImGui; + if (!g.NavWindow) + return; + + // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) + // FIXME-NAV: On _NavFlattened windows, g.NavWindow will only be updated during subsequent frame. Not a problem currently. + IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", g.NavInitResultId, g.NavLayer, g.NavWindow->Name); + SetNavID(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel); + if (g.NavInitRequestFromMove) + { + g.NavDisableHighlight = false; + g.NavDisableMouseHover = g.NavMousePosDirty = true; + } +} + +// Apply result from previous frame navigation directional move request +static void ImGui::NavUpdateMoveResult() +{ + ImGuiContext& g = *GImGui; + if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) + { + // In a situation when there is no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is not considered as a possible result) + if (g.NavId != 0) + { + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + } + return; + } + + // Select which result to use + ImGuiNavItemData* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + + // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page. + if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) + if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId) + result = &g.NavMoveResultLocalVisibleSet; + + // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules. + if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) + if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter)) + result = &g.NavMoveResultOther; + IM_ASSERT(g.NavWindow && result->Window); + + // Scroll to keep newly navigated item fully into view. + if (g.NavLayer == ImGuiNavLayer_Main) + { + ImVec2 delta_scroll; + if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_ScrollToEdge) + { + float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f; + delta_scroll.y = result->Window->Scroll.y - scroll_target; + SetScrollY(result->Window, scroll_target); + } + else + { + ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos); + delta_scroll = ScrollToBringRectIntoView(result->Window, rect_abs); + } + + // Offset our result position so mouse position can be applied immediately after in NavUpdate() + result->RectRel.TranslateX(-delta_scroll.x); + result->RectRel.TranslateY(-delta_scroll.y); + } + + ClearActiveID(); + g.NavWindow = result->Window; + if (g.NavId != result->ID) + { + // Don't set NavJustMovedToId if just landed on the same spot (which may happen with ImGuiNavMoveFlags_AllowCurrentNavId) + g.NavJustMovedToId = result->ID; + g.NavJustMovedToFocusScopeId = result->FocusScopeId; + g.NavJustMovedToKeyMods = g.NavMoveRequestKeyMods; + } + IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", result->ID, g.NavLayer, g.NavWindow->Name); + SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel); + g.NavDisableHighlight = false; + g.NavDisableMouseHover = g.NavMousePosDirty = true; +} + +// Handle PageUp/PageDown/Home/End keys +static float ImGui::NavUpdatePageUpPageDown() +{ + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + + if (g.NavMoveDir != ImGuiDir_None || g.NavWindow == NULL) + return 0.0f; + if ((g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL || g.NavLayer != ImGuiNavLayer_Main) + return 0.0f; + + ImGuiWindow* window = g.NavWindow; + const bool page_up_held = IsKeyDown(io.KeyMap[ImGuiKey_PageUp]) && !IsActiveIdUsingKey(ImGuiKey_PageUp); + const bool page_down_held = IsKeyDown(io.KeyMap[ImGuiKey_PageDown]) && !IsActiveIdUsingKey(ImGuiKey_PageDown); + const bool home_pressed = IsKeyPressed(io.KeyMap[ImGuiKey_Home]) && !IsActiveIdUsingKey(ImGuiKey_Home); + const bool end_pressed = IsKeyPressed(io.KeyMap[ImGuiKey_End]) && !IsActiveIdUsingKey(ImGuiKey_End); + if (page_up_held != page_down_held || home_pressed != end_pressed) // If either (not both) are pressed + { + if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll) + { + // Fallback manual-scroll when window has no navigable item + if (IsKeyPressed(io.KeyMap[ImGuiKey_PageUp], true)) + SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight()); + else if (IsKeyPressed(io.KeyMap[ImGuiKey_PageDown], true)) + SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight()); + else if (home_pressed) + SetScrollY(window, 0.0f); + else if (end_pressed) + SetScrollY(window, window->ScrollMax.y); + } + else + { + ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer]; + const float page_offset_y = ImMax(0.0f, window->InnerRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight()); + float nav_scoring_rect_offset_y = 0.0f; + if (IsKeyPressed(io.KeyMap[ImGuiKey_PageUp], true)) + { + nav_scoring_rect_offset_y = -page_offset_y; + g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset up, we request the down direction (so we can always land on the last item) + g.NavMoveClipDir = ImGuiDir_Up; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; + } + else if (IsKeyPressed(io.KeyMap[ImGuiKey_PageDown], true)) + { + nav_scoring_rect_offset_y = +page_offset_y; + g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset down, we request the up direction (so we can always land on the last item) + g.NavMoveClipDir = ImGuiDir_Down; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; + } + else if (home_pressed) + { + // FIXME-NAV: handling of Home/End is assuming that the top/bottom most item will be visible with Scroll.y == 0/ScrollMax.y + // Scrolling will be handled via the ImGuiNavMoveFlags_ScrollToEdge flag, we don't scroll immediately to avoid scrolling happening before nav result. + // Preserve current horizontal position if we have any. + nav_rect_rel.Min.y = nav_rect_rel.Max.y = -window->Scroll.y; + if (nav_rect_rel.IsInverted()) + nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; + g.NavMoveDir = ImGuiDir_Down; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge; + } + else if (end_pressed) + { + nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ScrollMax.y + window->SizeFull.y - window->Scroll.y; + if (nav_rect_rel.IsInverted()) + nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; + g.NavMoveDir = ImGuiDir_Up; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge; + } + return nav_scoring_rect_offset_y; + } + } + return 0.0f; +} + +static void ImGui::NavEndFrame() +{ + ImGuiContext& g = *GImGui; + + // Show CTRL+TAB list window + if (g.NavWindowingTarget != NULL) + NavUpdateWindowingOverlay(); + + // Perform wrap-around in menus + ImGuiWindow* window = g.NavWrapRequestWindow; + ImGuiNavMoveFlags move_flags = g.NavWrapRequestFlags; + if (window != NULL && g.NavWindow == window && NavMoveRequestButNoResultYet() && g.NavMoveRequestForward == ImGuiNavForward_None && g.NavLayer == ImGuiNavLayer_Main) + { + IM_ASSERT(move_flags != 0); // No points calling this with no wrapping + ImRect bb_rel = window->NavRectRel[0]; + + ImGuiDir clip_dir = g.NavMoveDir; + if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) + { + bb_rel.Min.x = bb_rel.Max.x = + ImMax(window->SizeFull.x, window->ContentSize.x + window->WindowPadding.x * 2.0f) - window->Scroll.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) + { + bb_rel.TranslateY(-bb_rel.GetHeight()); + clip_dir = ImGuiDir_Up; + } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) + { + bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) + { + bb_rel.TranslateY(+bb_rel.GetHeight()); + clip_dir = ImGuiDir_Down; + } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + { + bb_rel.Min.y = bb_rel.Max.y = + ImMax(window->SizeFull.y, window->ContentSize.y + window->WindowPadding.y * 2.0f) - window->Scroll.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) + { + bb_rel.TranslateX(-bb_rel.GetWidth()); + clip_dir = ImGuiDir_Left; + } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + { + bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) + { + bb_rel.TranslateX(+bb_rel.GetWidth()); + clip_dir = ImGuiDir_Right; + } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + } +} + +static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + int order = window->FocusOrder; + IM_ASSERT(g.WindowsFocusOrder[order] == window); + return order; +} + +static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = i_start; i >= 0 && i < g.WindowsFocusOrder.Size && i != i_stop; i += dir) + if (ImGui::IsWindowNavFocusable(g.WindowsFocusOrder[i])) + return g.WindowsFocusOrder[i]; + return NULL; +} + +static void NavUpdateWindowingHighlightWindow(int focus_change_dir) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget); + if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) + return; + + const int i_current = ImGui::FindWindowFocusIndex(g.NavWindowingTarget); + ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir); + if (!window_target) + window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.WindowsFocusOrder.Size - 1) : 0, i_current, focus_change_dir); + if (window_target) // Don't reset windowing target if there's a single window in the list + g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target; + g.NavWindowingToggleLayer = false; +} + +// Windowing management mode +// Keyboard: CTRL+Tab (change focus/move/resize), Alt (toggle menu layer) +// Gamepad: Hold Menu/Square (change focus/move/resize), Tap Menu/Square (toggle menu layer) +static void ImGui::NavUpdateWindowing() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* apply_focus_window = NULL; + bool apply_toggle_layer = false; + + ImGuiWindow* modal_window = GetTopMostPopupModal(); + bool allow_windowing = (modal_window == NULL); + if (!allow_windowing) + g.NavWindowingTarget = NULL; + + // Fade out + if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL) + { + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f); + if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f) + g.NavWindowingTargetAnim = NULL; + } + + // Start CTRL-TAB or Square+L/R window selection + bool start_windowing_with_gamepad = allow_windowing && !g.NavWindowingTarget && IsNavInputTest(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed); + bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard); + if (start_windowing_with_gamepad || start_windowing_with_keyboard) + if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) + { + g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; // FIXME-DOCK: Will need to use RootWindowDockStop + g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; + g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; + g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad; + } + + // Gamepad update + g.NavWindowingTimer += g.IO.DeltaTime; + if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Gamepad) + { + // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); + + // Select window to focus + const int focus_change_dir = (int)IsNavInputTest(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputTest(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow); + if (focus_change_dir != 0) + { + NavUpdateWindowingHighlightWindow(focus_change_dir); + g.NavWindowingHighlightAlpha = 1.0f; + } + + // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered top-most) + if (!IsNavInputDown(ImGuiNavInput_Menu)) + { + g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. + if (g.NavWindowingToggleLayer && g.NavWindow) + apply_toggle_layer = true; + else if (!g.NavWindowingToggleLayer) + apply_focus_window = g.NavWindowingTarget; + g.NavWindowingTarget = NULL; + } + } + + // Keyboard: Focus + if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Keyboard) + { + // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f + if (IsKeyPressedMap(ImGuiKey_Tab, true)) + NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); + if (!g.IO.KeyCtrl) + apply_focus_window = g.NavWindowingTarget; + } + + // Keyboard: Press and Release ALT to toggle menu layer + // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of backend clearing releases all keys on ALT-TAB + if (IsNavInputTest(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Pressed)) + g.NavWindowingToggleLayer = true; + if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && g.NavWindowingToggleLayer && IsNavInputTest(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released)) + if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev)) + apply_toggle_layer = true; + + // Move window + if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) + { + ImVec2 move_delta; + if (g.NavInputSource == ImGuiInputSource_Keyboard && !g.IO.KeyShift) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavInputSource == ImGuiInputSource_Gamepad) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); + if (move_delta.x != 0.0f || move_delta.y != 0.0f) + { + const float NAV_MOVE_SPEED = 800.0f; + const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't handle variable framerate very well + ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindow; + SetWindowPos(moving_window, moving_window->Pos + move_delta * move_speed, ImGuiCond_Always); + MarkIniSettingsDirty(moving_window); + g.NavDisableMouseHover = true; + } + } + + // Apply final focus + if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) + { + ClearActiveID(); + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); + ClosePopupsOverWindow(apply_focus_window, false); + FocusWindow(apply_focus_window); + if (apply_focus_window->NavLastIds[0] == 0) + NavInitWindow(apply_focus_window, false); + + // If the window has ONLY a menu layer (no main layer), select it directly + // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame, + // so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since + // the target window as already been previewed once. + // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases, + // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask* + // won't be valid. + if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu)) + g.NavLayer = ImGuiNavLayer_Menu; + } + if (apply_focus_window) + g.NavWindowingTarget = NULL; + + // Apply menu/layer toggle + if (apply_toggle_layer && g.NavWindow) + { + ClearActiveID(); + + // Move to parent menu if necessary + ImGuiWindow* new_nav_window = g.NavWindow; + while (new_nav_window->ParentWindow + && (new_nav_window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0 + && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 + && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + new_nav_window = new_nav_window->ParentWindow; + if (new_nav_window != g.NavWindow) + { + ImGuiWindow* old_nav_window = g.NavWindow; + FocusWindow(new_nav_window); + new_nav_window->NavLastChildNavWindow = old_nav_window; + } + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + + // Reinitialize navigation when entering menu bar with the Alt key. + const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) ? (ImGuiNavLayer)((int)g.NavLayer ^ 1) : ImGuiNavLayer_Main; + if (new_nav_layer == ImGuiNavLayer_Menu) + g.NavWindow->NavLastIds[new_nav_layer] = 0; + NavRestoreLayer(new_nav_layer); + } +} + +// Window has already passed the IsWindowNavFocusable() +static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) +{ + if (window->Flags & ImGuiWindowFlags_Popup) + return "(Popup)"; + if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0) + return "(Main menu bar)"; + return "(Untitled)"; +} + +// Overlay displayed when using CTRL+TAB. Called by EndFrame(). +void ImGui::NavUpdateWindowingOverlay() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget != NULL); + + if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY) + return; + + if (g.NavWindowingListWindow == NULL) + g.NavWindowingListWindow = FindWindowByName("###NavWindowingList"); + const ImGuiViewport* viewport = GetMainViewport(); + SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); + SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); + Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); + for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) + { + ImGuiWindow* window = g.WindowsFocusOrder[n]; + IM_ASSERT(window != NULL); // Fix static analyzers + if (!IsWindowNavFocusable(window)) + continue; + const char* label = window->Name; + if (label == FindRenderedTextEnd(label)) + label = GetFallbackWindowNameForWindowingList(window); + Selectable(label, g.NavWindowingTarget == window); + } + End(); + PopStyleVar(); +} + + +//----------------------------------------------------------------------------- +// [SECTION] DRAG AND DROP +//----------------------------------------------------------------------------- + +void ImGui::ClearDragDrop() +{ + ImGuiContext& g = *GImGui; + g.DragDropActive = false; + g.DragDropPayload.Clear(); + g.DragDropAcceptFlags = ImGuiDragDropFlags_None; + g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropAcceptFrameCount = -1; + + g.DragDropPayloadBufHeap.clear(); + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); +} + +// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() +// If the item has an identifier: +// - This assume/require the item to be activated (typically via ButtonBehavior). +// - Therefore if you want to use this with a mouse button other than left mouse button, it is up to the item itself to activate with another button. +// - We then pull and use the mouse button that was used to activate the item and use it to carry on the drag. +// If the item has no identifier: +// - Currently always assume left mouse button. +bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // FIXME-DRAGDROP: While in the common-most "drag from non-zero active id" case we can tell the mouse button, + // in both SourceExtern and id==0 cases we may requires something else (explicit flags or some heuristic). + ImGuiMouseButton mouse_button = ImGuiMouseButton_Left; + + bool source_drag_active = false; + ImGuiID source_id = 0; + ImGuiID source_parent_id = 0; + if (!(flags & ImGuiDragDropFlags_SourceExtern)) + { + source_id = window->DC.LastItemId; + if (source_id != 0) + { + // Common path: items with ID + if (g.ActiveId != source_id) + return false; + if (g.ActiveIdMouseButton != -1) + mouse_button = g.ActiveIdMouseButton; + if (g.IO.MouseDown[mouse_button] == false) + return false; + g.ActiveIdAllowOverlap = false; + } + else + { + // Uncommon path: items without ID + if (g.IO.MouseDown[mouse_button] == false) + return false; + + // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: + // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. + if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) + { + IM_ASSERT(0); + return false; + } + + // Early out + if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window)) + return false; + + // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() + // We build a throwaway ID based on current ID stack + relative AABB of items in window. + // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. + // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. + source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); + bool is_hovered = ItemHoverable(window->DC.LastItemRect, source_id); + if (is_hovered && g.IO.MouseClicked[mouse_button]) + { + SetActiveID(source_id, window); + FocusWindow(window); + } + if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. + g.ActiveIdAllowOverlap = is_hovered; + } + if (g.ActiveId != source_id) + return false; + source_parent_id = window->IDStack.back(); + source_drag_active = IsMouseDragging(mouse_button); + + // Disable navigation and key inputs while dragging + g.ActiveIdUsingNavDirMask = ~(ImU32)0; + g.ActiveIdUsingNavInputMask = ~(ImU32)0; + g.ActiveIdUsingKeyInputMask = ~(ImU64)0; + } + else + { + window = NULL; + source_id = ImHashStr("#SourceExtern"); + source_drag_active = true; + } + + if (source_drag_active) + { + if (!g.DragDropActive) + { + IM_ASSERT(source_id != 0); + ClearDragDrop(); + ImGuiPayload& payload = g.DragDropPayload; + payload.SourceId = source_id; + payload.SourceParentId = source_parent_id; + g.DragDropActive = true; + g.DragDropSourceFlags = flags; + g.DragDropMouseButton = mouse_button; + if (payload.SourceId == g.ActiveId) + g.ActiveIdNoClearOnFocusLoss = true; + } + g.DragDropSourceFrameCount = g.FrameCount; + g.DragDropWithinSource = true; + + if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit) + // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. + BeginTooltip(); + if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) + { + ImGuiWindow* tooltip_window = g.CurrentWindow; + tooltip_window->SkipItems = true; + tooltip_window->HiddenFramesCanSkipItems = 1; + } + } + + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) + window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + + return true; + } + return false; +} + +void ImGui::EndDragDropSource() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + IM_ASSERT(g.DragDropWithinSource && "Not after a BeginDragDropSource()?"); + + if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + EndTooltip(); + + // Discard the drag if have not called SetDragDropPayload() + if (g.DragDropPayload.DataFrameCount == -1) + ClearDragDrop(); + g.DragDropWithinSource = false; +} + +// Use 'cond' to choose to submit payload on drag start or every frame +bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + ImGuiPayload& payload = g.DragDropPayload; + if (cond == 0) + cond = ImGuiCond_Always; + + IM_ASSERT(type != NULL); + IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); + IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); + IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + + if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) + { + // Copy payload + ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + g.DragDropPayloadBufHeap.resize(0); + if (data_size > sizeof(g.DragDropPayloadBufLocal)) + { + // Store in heap + g.DragDropPayloadBufHeap.resize((int)data_size); + payload.Data = g.DragDropPayloadBufHeap.Data; + memcpy(payload.Data, data, data_size); + } + else if (data_size > 0) + { + // Store locally + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + payload.Data = g.DragDropPayloadBufLocal; + memcpy(payload.Data, data, data_size); + } + else + { + payload.Data = NULL; + } + payload.DataSize = (int)data_size; + } + payload.DataFrameCount = g.FrameCount; + + return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); +} + +bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; + if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow) + return false; + IM_ASSERT(id != 0); + if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) + return false; + if (window->SkipItems) + return false; + + IM_ASSERT(g.DragDropWithinTarget == false); + g.DragDropTargetRect = bb; + g.DragDropTargetId = id; + g.DragDropWithinTarget = true; + return true; +} + +// We don't use BeginDragDropTargetCustom() and duplicate its code because: +// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. +// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. +// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) +bool ImGui::BeginDragDropTarget() +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; + if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow) + return false; + + const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; + ImGuiID id = window->DC.LastItemId; + if (id == 0) + id = window->GetIDFromRectangle(display_rect); + if (g.DragDropPayload.SourceId == id) + return false; + + IM_ASSERT(g.DragDropWithinTarget == false); + g.DragDropTargetRect = display_rect; + g.DragDropTargetId = id; + g.DragDropWithinTarget = true; + return true; +} + +bool ImGui::IsDragDropPayloadBeingAccepted() +{ + ImGuiContext& g = *GImGui; + return g.DragDropActive && g.DragDropAcceptIdPrev != 0; +} + +const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiPayload& payload = g.DragDropPayload; + IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? + if (type != NULL && !payload.IsDataType(type)) + return NULL; + + // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. + // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! + const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); + ImRect r = g.DragDropTargetRect; + float r_surface = r.GetWidth() * r.GetHeight(); + if (r_surface <= g.DragDropAcceptIdCurrRectSurface) + { + g.DragDropAcceptFlags = flags; + g.DragDropAcceptIdCurr = g.DragDropTargetId; + g.DragDropAcceptIdCurrRectSurface = r_surface; + } + + // Render default drop visuals + payload.Preview = was_accepted_previously; + flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) + if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) + { + // FIXME-DRAGDROP: Settle on a proper default visuals for drop target. + r.Expand(3.5f); + bool push_clip_rect = !window->ClipRect.Contains(r); + if (push_clip_rect) window->DrawList->PushClipRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1)); + window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); + if (push_clip_rect) window->DrawList->PopClipRect(); + } + + g.DragDropAcceptFrameCount = g.FrameCount; + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() + if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) + return NULL; + + return &payload; +} + +const ImGuiPayload* ImGui::GetDragDropPayload() +{ + ImGuiContext& g = *GImGui; + return g.DragDropActive ? &g.DragDropPayload : NULL; +} + +// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. +void ImGui::EndDragDropTarget() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + IM_ASSERT(g.DragDropWithinTarget); + g.DragDropWithinTarget = false; +} + +//----------------------------------------------------------------------------- +// [SECTION] LOGGING/CAPTURING +//----------------------------------------------------------------------------- +// All text output from the interface can be captured into tty/file/clipboard. +// By default, tree nodes are automatically opened during logging. +//----------------------------------------------------------------------------- + +// Pass text data straight to log (without being displayed) +static inline void LogTextV(ImGuiContext& g, const char* fmt, va_list args) +{ + if (g.LogFile) + { + g.LogBuffer.Buf.resize(0); + g.LogBuffer.appendfv(fmt, args); + ImFileWrite(g.LogBuffer.c_str(), sizeof(char), (ImU64)g.LogBuffer.size(), g.LogFile); + } + else + { + g.LogBuffer.appendfv(fmt, args); + } +} + +void ImGui::LogText(const char* fmt, ...) +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + va_list args; + va_start(args, fmt); + LogTextV(g, fmt, args); + va_end(args); +} + +void ImGui::LogTextV(const char* fmt, va_list args) +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + LogTextV(g, fmt, args); +} + +// Internal version that takes a position to decide on newline placement and pad items according to their depth. +// We split text into individual lines to add current tree level padding +// FIXME: This code is a little complicated perhaps, considering simplifying the whole system. +void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const char* prefix = g.LogNextPrefix; + const char* suffix = g.LogNextSuffix; + g.LogNextPrefix = g.LogNextSuffix = NULL; + + if (!text_end) + text_end = FindRenderedTextEnd(text, text_end); + + const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + g.Style.FramePadding.y + 1); + if (ref_pos) + g.LogLinePosY = ref_pos->y; + if (log_new_line) + { + LogText(IM_NEWLINE); + g.LogLineFirstItem = true; + } + + if (prefix) + LogRenderedText(ref_pos, prefix, prefix + strlen(prefix)); // Calculate end ourself to ensure "##" are included here. + + // Re-adjust padding if we have popped out of our starting depth + if (g.LogDepthRef > window->DC.TreeDepth) + g.LogDepthRef = window->DC.TreeDepth; + const int tree_depth = (window->DC.TreeDepth - g.LogDepthRef); + + const char* text_remaining = text; + for (;;) + { + // Split the string. Each new line (after a '\n') is followed by indentation corresponding to the current depth of our log entry. + // We don't add a trailing \n yet to allow a subsequent item on the same line to be captured. + const char* line_start = text_remaining; + const char* line_end = ImStreolRange(line_start, text_end); + const bool is_last_line = (line_end == text_end); + if (line_start != line_end || !is_last_line) + { + const int line_length = (int)(line_end - line_start); + const int indentation = g.LogLineFirstItem ? tree_depth * 4 : 1; + LogText("%*s%.*s", indentation, "", line_length, line_start); + g.LogLineFirstItem = false; + if (*line_end == '\n') + { + LogText(IM_NEWLINE); + g.LogLineFirstItem = true; + } + } + if (is_last_line) + break; + text_remaining = line_end + 1; + } + + if (suffix) + LogRenderedText(ref_pos, suffix, suffix + strlen(suffix)); +} + +// Start logging/capturing text output +void ImGui::LogBegin(ImGuiLogType type, int auto_open_depth) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(g.LogEnabled == false); + IM_ASSERT(g.LogFile == NULL); + IM_ASSERT(g.LogBuffer.empty()); + g.LogEnabled = true; + g.LogType = type; + g.LogNextPrefix = g.LogNextSuffix = NULL; + g.LogDepthRef = window->DC.TreeDepth; + g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault); + g.LogLinePosY = FLT_MAX; + g.LogLineFirstItem = true; +} + +// Important: doesn't copy underlying data, use carefully (prefix/suffix must be in scope at the time of the next LogRenderedText) +void ImGui::LogSetNextTextDecoration(const char* prefix, const char* suffix) +{ + ImGuiContext& g = *GImGui; + g.LogNextPrefix = prefix; + g.LogNextSuffix = suffix; +} + +void ImGui::LogToTTY(int auto_open_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + IM_UNUSED(auto_open_depth); +#ifndef IMGUI_DISABLE_TTY_FUNCTIONS + LogBegin(ImGuiLogType_TTY, auto_open_depth); + g.LogFile = stdout; +#endif +} + +// Start logging/capturing text output to given file +void ImGui::LogToFile(int auto_open_depth, const char* filename) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + + // FIXME: We could probably open the file in text mode "at", however note that clipboard/buffer logging will still + // be subject to outputting OS-incompatible carriage return if within strings the user doesn't use IM_NEWLINE. + // By opening the file in binary mode "ab" we have consistent output everywhere. + if (!filename) + filename = g.IO.LogFilename; + if (!filename || !filename[0]) + return; + ImFileHandle f = ImFileOpen(filename, "ab"); + if (!f) + { + IM_ASSERT(0); + return; + } + + LogBegin(ImGuiLogType_File, auto_open_depth); + g.LogFile = f; +} + +// Start logging/capturing text output to clipboard +void ImGui::LogToClipboard(int auto_open_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + LogBegin(ImGuiLogType_Clipboard, auto_open_depth); +} + +void ImGui::LogToBuffer(int auto_open_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + LogBegin(ImGuiLogType_Buffer, auto_open_depth); +} + +void ImGui::LogFinish() +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + LogText(IM_NEWLINE); + switch (g.LogType) + { + case ImGuiLogType_TTY: +#ifndef IMGUI_DISABLE_TTY_FUNCTIONS + fflush(g.LogFile); +#endif + break; + case ImGuiLogType_File: + ImFileClose(g.LogFile); + break; + case ImGuiLogType_Buffer: + break; + case ImGuiLogType_Clipboard: + if (!g.LogBuffer.empty()) + SetClipboardText(g.LogBuffer.begin()); + break; + case ImGuiLogType_None: + IM_ASSERT(0); + break; + } + + g.LogEnabled = false; + g.LogType = ImGuiLogType_None; + g.LogFile = NULL; + g.LogBuffer.clear(); +} + +// Helper to display logging buttons +// FIXME-OBSOLETE: We should probably obsolete this and let the user have their own helper (this is one of the oldest function alive!) +void ImGui::LogButtons() +{ + ImGuiContext& g = *GImGui; + + PushID("LogButtons"); +#ifndef IMGUI_DISABLE_TTY_FUNCTIONS + const bool log_to_tty = Button("Log To TTY"); SameLine(); +#else + const bool log_to_tty = false; +#endif + const bool log_to_file = Button("Log To File"); SameLine(); + const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); + PushAllowKeyboardFocus(false); + SetNextItemWidth(80.0f); + SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL); + PopAllowKeyboardFocus(); + PopID(); + + // Start logging at the end of the function so that the buttons don't appear in the log + if (log_to_tty) + LogToTTY(); + if (log_to_file) + LogToFile(); + if (log_to_clipboard) + LogToClipboard(); +} + + +//----------------------------------------------------------------------------- +// [SECTION] SETTINGS +//----------------------------------------------------------------------------- +// - UpdateSettings() [Internal] +// - MarkIniSettingsDirty() [Internal] +// - CreateNewWindowSettings() [Internal] +// - FindWindowSettings() [Internal] +// - FindOrCreateWindowSettings() [Internal] +// - FindSettingsHandler() [Internal] +// - ClearIniSettings() [Internal] +// - LoadIniSettingsFromDisk() +// - LoadIniSettingsFromMemory() +// - SaveIniSettingsToDisk() +// - SaveIniSettingsToMemory() +// - WindowSettingsHandler_***() [Internal] +//----------------------------------------------------------------------------- + +// Called by NewFrame() +void ImGui::UpdateSettings() +{ + // Load settings on first frame (if not explicitly loaded manually before) + ImGuiContext& g = *GImGui; + if (!g.SettingsLoaded) + { + IM_ASSERT(g.SettingsWindows.empty()); + if (g.IO.IniFilename) + LoadIniSettingsFromDisk(g.IO.IniFilename); + g.SettingsLoaded = true; + } + + // Save settings (with a delay after the last modification, so we don't spam disk too much) + if (g.SettingsDirtyTimer > 0.0f) + { + g.SettingsDirtyTimer -= g.IO.DeltaTime; + if (g.SettingsDirtyTimer <= 0.0f) + { + if (g.IO.IniFilename != NULL) + SaveIniSettingsToDisk(g.IO.IniFilename); + else + g.IO.WantSaveIniSettings = true; // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves. + g.SettingsDirtyTimer = 0.0f; + } + } +} + +void ImGui::MarkIniSettingsDirty() +{ + ImGuiContext& g = *GImGui; + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +void ImGui::MarkIniSettingsDirty(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings)) + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name) +{ + ImGuiContext& g = *GImGui; + +#if !IMGUI_DEBUG_INI_SETTINGS + // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() + // Preserve the full string when IMGUI_DEBUG_INI_SETTINGS is set to make .ini inspection easier. + if (const char* p = strstr(name, "###")) + name = p; +#endif + const size_t name_len = strlen(name); + + // Allocate chunk + const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1; + ImGuiWindowSettings* settings = g.SettingsWindows.alloc_chunk(chunk_size); + IM_PLACEMENT_NEW(settings) ImGuiWindowSettings(); + settings->ID = ImHashStr(name, name_len); + memcpy(settings->GetName(), name, name_len + 1); // Store with zero terminator + + return settings; +} + +ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (settings->ID == id) + return settings; + return NULL; +} + +ImGuiWindowSettings* ImGui::FindOrCreateWindowSettings(const char* name) +{ + if (ImGuiWindowSettings* settings = FindWindowSettings(ImHashStr(name))) + return settings; + return CreateNewWindowSettings(name); +} + +ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) +{ + ImGuiContext& g = *GImGui; + const ImGuiID type_hash = ImHashStr(type_name); + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].TypeHash == type_hash) + return &g.SettingsHandlers[handler_n]; + return NULL; +} + +void ImGui::ClearIniSettings() +{ + ImGuiContext& g = *GImGui; + g.SettingsIniData.clear(); + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].ClearAllFn) + g.SettingsHandlers[handler_n].ClearAllFn(&g, &g.SettingsHandlers[handler_n]); +} + +void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) +{ + size_t file_data_size = 0; + char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size); + if (!file_data) + return; + LoadIniSettingsFromMemory(file_data, (size_t)file_data_size); + IM_FREE(file_data); +} + +// Zero-tolerance, no error reporting, cheap .ini parsing +void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); + //IM_ASSERT(!g.WithinFrameScope && "Cannot be called between NewFrame() and EndFrame()"); + //IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0); + + // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter). + // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy.. + if (ini_size == 0) + ini_size = strlen(ini_data); + g.SettingsIniData.Buf.resize((int)ini_size + 1); + char* const buf = g.SettingsIniData.Buf.Data; + char* const buf_end = buf + ini_size; + memcpy(buf, ini_data, ini_size); + buf_end[0] = 0; + + // Call pre-read handlers + // Some types will clear their data (e.g. dock information) some types will allow merge/override (window) + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].ReadInitFn) + g.SettingsHandlers[handler_n].ReadInitFn(&g, &g.SettingsHandlers[handler_n]); + + void* entry_data = NULL; + ImGuiSettingsHandler* entry_handler = NULL; + + char* line_end = NULL; + for (char* line = buf; line < buf_end; line = line_end + 1) + { + // Skip new lines markers, then find end of the line + while (*line == '\n' || *line == '\r') + line++; + line_end = line; + while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') + line_end++; + line_end[0] = 0; + if (line[0] == ';') + continue; + if (line[0] == '[' && line_end > line && line_end[-1] == ']') + { + // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. + line_end[-1] = 0; + const char* name_end = line_end - 1; + const char* type_start = line + 1; + char* type_end = (char*)(void*)ImStrchrRange(type_start, name_end, ']'); + const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; + if (!type_end || !name_start) + continue; + *type_end = 0; // Overwrite first ']' + name_start++; // Skip second '[' + entry_handler = FindSettingsHandler(type_start); + entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL; + } + else if (entry_handler != NULL && entry_data != NULL) + { + // Let type handler parse the line + entry_handler->ReadLineFn(&g, entry_handler, entry_data, line); + } + } + g.SettingsLoaded = true; + + // [DEBUG] Restore untouched copy so it can be browsed in Metrics (not strictly necessary) + memcpy(buf, ini_data, ini_size); + + // Call post-read handlers + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].ApplyAllFn) + g.SettingsHandlers[handler_n].ApplyAllFn(&g, &g.SettingsHandlers[handler_n]); +} + +void ImGui::SaveIniSettingsToDisk(const char* ini_filename) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + if (!ini_filename) + return; + + size_t ini_data_size = 0; + const char* ini_data = SaveIniSettingsToMemory(&ini_data_size); + ImFileHandle f = ImFileOpen(ini_filename, "wt"); + if (!f) + return; + ImFileWrite(ini_data, sizeof(char), ini_data_size, f); + ImFileClose(f); +} + +// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer +const char* ImGui::SaveIniSettingsToMemory(size_t* out_size) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + g.SettingsIniData.Buf.resize(0); + g.SettingsIniData.Buf.push_back(0); + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + { + ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n]; + handler->WriteAllFn(&g, handler, &g.SettingsIniData); + } + if (out_size) + *out_size = (size_t)g.SettingsIniData.size(); + return g.SettingsIniData.c_str(); +} + +static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Windows.Size; i++) + g.Windows[i]->SettingsOffset = -1; + g.SettingsWindows.clear(); +} + +static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + ImGuiWindowSettings* settings = ImGui::FindOrCreateWindowSettings(name); + ImGuiID id = settings->ID; + *settings = ImGuiWindowSettings(); // Clear existing if recycling previous entry + settings->ID = id; + settings->WantApply = true; + return (void*)settings; +} + +static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) +{ + ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; + int x, y; + int i; + if (sscanf(line, "Pos=%i,%i", &x, &y) == 2) { settings->Pos = ImVec2ih((short)x, (short)y); } + else if (sscanf(line, "Size=%i,%i", &x, &y) == 2) { settings->Size = ImVec2ih((short)x, (short)y); } + else if (sscanf(line, "Collapsed=%d", &i) == 1) { settings->Collapsed = (i != 0); } +} + +// Apply to existing windows (if any) +static void WindowSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + ImGuiContext& g = *ctx; + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (settings->WantApply) + { + if (ImGuiWindow* window = ImGui::FindWindowByID(settings->ID)) + ApplyWindowSettings(window, settings); + settings->WantApply = false; + } +} + +static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +{ + // Gather data from windows that were active during this session + // (if a window wasn't opened in this session we preserve its settings) + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Flags & ImGuiWindowFlags_NoSavedSettings) + continue; + + ImGuiWindowSettings* settings = (window->SettingsOffset != -1) ? g.SettingsWindows.ptr_from_offset(window->SettingsOffset) : ImGui::FindWindowSettings(window->ID); + if (!settings) + { + settings = ImGui::CreateNewWindowSettings(window->Name); + window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings); + } + IM_ASSERT(settings->ID == window->ID); + settings->Pos = ImVec2ih((short)window->Pos.x, (short)window->Pos.y); + settings->Size = ImVec2ih((short)window->SizeFull.x, (short)window->SizeFull.y); + settings->Collapsed = window->Collapsed; + } + + // Write to text buffer + buf->reserve(buf->size() + g.SettingsWindows.size() * 6); // ballpark reserve + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + { + const char* settings_name = settings->GetName(); + buf->appendf("[%s][%s]\n", handler->TypeName, settings_name); + buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y); + buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y); + buf->appendf("Collapsed=%d\n", settings->Collapsed); + buf->append("\n"); + } +} + + +//----------------------------------------------------------------------------- +// [SECTION] VIEWPORTS, PLATFORM WINDOWS +//----------------------------------------------------------------------------- +// - GetMainViewport() +// - UpdateViewportsNewFrame() [Internal] +// (this section is more complete in the 'docking' branch) +//----------------------------------------------------------------------------- + +ImGuiViewport* ImGui::GetMainViewport() +{ + ImGuiContext& g = *GImGui; + return g.Viewports[0]; +} + +// Update viewports and monitor infos +static void ImGui::UpdateViewportsNewFrame() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Viewports.Size == 1); + + // Update main viewport with current platform position. + // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent. + ImGuiViewportP* main_viewport = g.Viewports[0]; + main_viewport->Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp; + main_viewport->Pos = ImVec2(0.0f, 0.0f); + main_viewport->Size = g.IO.DisplaySize; + + for (int n = 0; n < g.Viewports.Size; n++) + { + ImGuiViewportP* viewport = g.Viewports[n]; + + // Lock down space taken by menu bars and status bars, reset the offset for fucntions like BeginMainMenuBar() to alter them again. + viewport->WorkOffsetMin = viewport->BuildWorkOffsetMin; + viewport->WorkOffsetMax = viewport->BuildWorkOffsetMax; + viewport->BuildWorkOffsetMin = viewport->BuildWorkOffsetMax = ImVec2(0.0f, 0.0f); + viewport->UpdateWorkRect(); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] DOCKING +//----------------------------------------------------------------------------- + +// (this section is filled in the 'docking' branch) + + +//----------------------------------------------------------------------------- +// [SECTION] PLATFORM DEPENDENT HELPERS +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) + +#ifdef _MSC_VER +#pragma comment(lib, "user32") +#pragma comment(lib, "kernel32") +#endif + +// Win32 clipboard implementation +// We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown() +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + ImGuiContext& g = *GImGui; + g.ClipboardHandlerData.clear(); + if (!::OpenClipboard(NULL)) + return NULL; + HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT); + if (wbuf_handle == NULL) + { + ::CloseClipboard(); + return NULL; + } + if (const WCHAR* wbuf_global = (const WCHAR*)::GlobalLock(wbuf_handle)) + { + int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL); + g.ClipboardHandlerData.resize(buf_len); + ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL); + } + ::GlobalUnlock(wbuf_handle); + ::CloseClipboard(); + return g.ClipboardHandlerData.Data; +} + +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + if (!::OpenClipboard(NULL)) + return; + const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0); + HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR)); + if (wbuf_handle == NULL) + { + ::CloseClipboard(); + return; + } + WCHAR* wbuf_global = (WCHAR*)::GlobalLock(wbuf_handle); + ::MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf_global, wbuf_length); + ::GlobalUnlock(wbuf_handle); + ::EmptyClipboard(); + if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL) + ::GlobalFree(wbuf_handle); + ::CloseClipboard(); +} + +#elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS) + +#include // Use old API to avoid need for separate .mm file +static PasteboardRef main_clipboard = 0; + +// OSX clipboard implementation +// If you enable this you will need to add '-framework ApplicationServices' to your linker command-line! +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + if (!main_clipboard) + PasteboardCreate(kPasteboardClipboard, &main_clipboard); + PasteboardClear(main_clipboard); + CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, strlen(text)); + if (cf_data) + { + PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0); + CFRelease(cf_data); + } +} + +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + if (!main_clipboard) + PasteboardCreate(kPasteboardClipboard, &main_clipboard); + PasteboardSynchronize(main_clipboard); + + ItemCount item_count = 0; + PasteboardGetItemCount(main_clipboard, &item_count); + for (ItemCount i = 0; i < item_count; i++) + { + PasteboardItemID item_id = 0; + PasteboardGetItemIdentifier(main_clipboard, i + 1, &item_id); + CFArrayRef flavor_type_array = 0; + PasteboardCopyItemFlavors(main_clipboard, item_id, &flavor_type_array); + for (CFIndex j = 0, nj = CFArrayGetCount(flavor_type_array); j < nj; j++) + { + CFDataRef cf_data; + if (PasteboardCopyItemFlavorData(main_clipboard, item_id, CFSTR("public.utf8-plain-text"), &cf_data) == noErr) + { + ImGuiContext& g = *GImGui; + g.ClipboardHandlerData.clear(); + int length = (int)CFDataGetLength(cf_data); + g.ClipboardHandlerData.resize(length + 1); + CFDataGetBytes(cf_data, CFRangeMake(0, length), (UInt8*)g.ClipboardHandlerData.Data); + g.ClipboardHandlerData[length] = 0; + CFRelease(cf_data); + return g.ClipboardHandlerData.Data; + } + } + } + return NULL; +} + +#else + +// Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers. +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + ImGuiContext& g = *GImGui; + return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin(); +} + +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + ImGuiContext& g = *GImGui; + g.ClipboardHandlerData.clear(); + const char* text_end = text + strlen(text); + g.ClipboardHandlerData.resize((int)(text_end - text) + 1); + memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text)); + g.ClipboardHandlerData[(int)(text_end - text)] = 0; +} + +#endif + +// Win32 API IME support (for Asian languages, etc.) +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) + +#include +#ifdef _MSC_VER +#pragma comment(lib, "imm32") +#endif + +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) +{ + // Notify OS Input Method Editor of text input position + ImGuiIO& io = ImGui::GetIO(); + if (HWND hwnd = (HWND)io.ImeWindowHandle) + if (HIMC himc = ::ImmGetContext(hwnd)) + { + COMPOSITIONFORM cf; + cf.ptCurrentPos.x = x; + cf.ptCurrentPos.y = y; + cf.dwStyle = CFS_FORCE_POSITION; + ::ImmSetCompositionWindow(himc, &cf); + ::ImmReleaseContext(hwnd, himc); + } +} + +#else + +static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {} + +#endif + +//----------------------------------------------------------------------------- +// [SECTION] METRICS/DEBUGGER WINDOW +//----------------------------------------------------------------------------- +// - RenderViewportThumbnail() [Internal] +// - RenderViewportsThumbnails() [Internal] +// - MetricsHelpMarker() [Internal] +// - ShowMetricsWindow() +// - DebugNodeColumns() [Internal] +// - DebugNodeDrawList() [Internal] +// - DebugNodeDrawCmdShowMeshAndBoundingBox() [Internal] +// - DebugNodeStorage() [Internal] +// - DebugNodeTabBar() [Internal] +// - DebugNodeViewport() [Internal] +// - DebugNodeWindow() [Internal] +// - DebugNodeWindowSettings() [Internal] +// - DebugNodeWindowsList() [Internal] +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_METRICS_WINDOW + +void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + ImVec2 scale = bb.GetSize() / viewport->Size; + ImVec2 off = bb.Min - viewport->Pos * scale; + float alpha_mul = 1.0f; + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul * 0.40f)); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* thumb_window = g.Windows[i]; + if (!thumb_window->WasActive || (thumb_window->Flags & ImGuiWindowFlags_ChildWindow)) + continue; + + ImRect thumb_r = thumb_window->Rect(); + ImRect title_r = thumb_window->TitleBarRect(); + thumb_r = ImRect(ImFloor(off + thumb_r.Min * scale), ImFloor(off + thumb_r.Max * scale)); + title_r = ImRect(ImFloor(off + title_r.Min * scale), ImFloor(off + ImVec2(title_r.Max.x, title_r.Min.y) * scale) + ImVec2(0,5)); // Exaggerate title bar height + thumb_r.ClipWithFull(bb); + title_r.ClipWithFull(bb); + const bool window_is_focused = (g.NavWindow && thumb_window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight); + window->DrawList->AddRectFilled(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_WindowBg, alpha_mul)); + window->DrawList->AddRectFilled(title_r.Min, title_r.Max, GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg, alpha_mul)); + window->DrawList->AddRect(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_Border, alpha_mul)); + window->DrawList->AddText(g.Font, g.FontSize * 1.0f, title_r.Min, GetColorU32(ImGuiCol_Text, alpha_mul), thumb_window->Name, FindRenderedTextEnd(thumb_window->Name)); + } + draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul)); +} + +static void RenderViewportsThumbnails() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // We don't display full monitor bounds (we could, but it often looks awkward), instead we display just enough to cover all of our viewports. + float SCALE = 1.0f / 8.0f; + ImRect bb_full(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + for (int n = 0; n < g.Viewports.Size; n++) + bb_full.Add(g.Viewports[n]->GetMainRect()); + ImVec2 p = window->DC.CursorPos; + ImVec2 off = p - bb_full.Min * SCALE; + for (int n = 0; n < g.Viewports.Size; n++) + { + ImGuiViewportP* viewport = g.Viewports[n]; + ImRect viewport_draw_bb(off + (viewport->Pos) * SCALE, off + (viewport->Pos + viewport->Size) * SCALE); + ImGui::DebugRenderViewportThumbnail(window->DrawList, viewport, viewport_draw_bb); + } + ImGui::Dummy(bb_full.GetSize() * SCALE); +} + +// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. +static void MetricsHelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +#ifndef IMGUI_DISABLE_DEMO_WINDOWS +namespace ImGui { void ShowFontAtlas(ImFontAtlas* atlas); } +#endif + +void ImGui::ShowMetricsWindow(bool* p_open) +{ + if (!Begin("Dear ImGui Metrics/Debugger", p_open)) + { + End(); + return; + } + + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + + // Basic info + Text("Dear ImGui %s", GetVersion()); + Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3); + Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows); + Text("%d active allocations", io.MetricsActiveAllocations); + //SameLine(); if (SmallButton("GC")) { g.GcCompactAll = true; } + + Separator(); + + // Debugging enums + enum { WRT_OuterRect, WRT_OuterRectClipped, WRT_InnerRect, WRT_InnerClipRect, WRT_WorkRect, WRT_Content, WRT_ContentIdeal, WRT_ContentRegionRect, WRT_Count }; // Windows Rect Type + const char* wrt_rects_names[WRT_Count] = { "OuterRect", "OuterRectClipped", "InnerRect", "InnerClipRect", "WorkRect", "Content", "ContentIdeal", "ContentRegionRect" }; + enum { TRT_OuterRect, TRT_InnerRect, TRT_WorkRect, TRT_HostClipRect, TRT_InnerClipRect, TRT_BackgroundClipRect, TRT_ColumnsRect, TRT_ColumnsWorkRect, TRT_ColumnsClipRect, TRT_ColumnsContentHeadersUsed, TRT_ColumnsContentHeadersIdeal, TRT_ColumnsContentFrozen, TRT_ColumnsContentUnfrozen, TRT_Count }; // Tables Rect Type + const char* trt_rects_names[TRT_Count] = { "OuterRect", "InnerRect", "WorkRect", "HostClipRect", "InnerClipRect", "BackgroundClipRect", "ColumnsRect", "ColumnsWorkRect", "ColumnsClipRect", "ColumnsContentHeadersUsed", "ColumnsContentHeadersIdeal", "ColumnsContentFrozen", "ColumnsContentUnfrozen" }; + if (cfg->ShowWindowsRectsType < 0) + cfg->ShowWindowsRectsType = WRT_WorkRect; + if (cfg->ShowTablesRectsType < 0) + cfg->ShowTablesRectsType = TRT_WorkRect; + + struct Funcs + { + static ImRect GetTableRect(ImGuiTable* table, int rect_type, int n) + { + if (rect_type == TRT_OuterRect) { return table->OuterRect; } + else if (rect_type == TRT_InnerRect) { return table->InnerRect; } + else if (rect_type == TRT_WorkRect) { return table->WorkRect; } + else if (rect_type == TRT_HostClipRect) { return table->HostClipRect; } + else if (rect_type == TRT_InnerClipRect) { return table->InnerClipRect; } + else if (rect_type == TRT_BackgroundClipRect) { return table->BgClipRect; } + else if (rect_type == TRT_ColumnsRect) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table->LastOuterHeight); } + else if (rect_type == TRT_ColumnsWorkRect) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->WorkRect.Min.y, c->WorkMaxX, table->WorkRect.Max.y); } + else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; } + else if (rect_type == TRT_ColumnsContentHeadersUsed){ ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersUsed, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // Note: y1/y2 not always accurate + else if (rect_type == TRT_ColumnsContentHeadersIdeal){ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersIdeal, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } + else if (rect_type == TRT_ColumnsContentFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXFrozen, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } + else if (rect_type == TRT_ColumnsContentUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight, c->ContentMaxXUnfrozen, table->InnerClipRect.Max.y); } + IM_ASSERT(0); + return ImRect(); + } + + static ImRect GetWindowRect(ImGuiWindow* window, int rect_type) + { + if (rect_type == WRT_OuterRect) { return window->Rect(); } + else if (rect_type == WRT_OuterRectClipped) { return window->OuterRectClipped; } + else if (rect_type == WRT_InnerRect) { return window->InnerRect; } + else if (rect_type == WRT_InnerClipRect) { return window->InnerClipRect; } + else if (rect_type == WRT_WorkRect) { return window->WorkRect; } + else if (rect_type == WRT_Content) { ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding; return ImRect(min, min + window->ContentSize); } + else if (rect_type == WRT_ContentIdeal) { ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding; return ImRect(min, min + window->ContentSizeIdeal); } + else if (rect_type == WRT_ContentRegionRect) { return window->ContentRegionRect; } + IM_ASSERT(0); + return ImRect(); + } + }; + + // Tools + if (TreeNode("Tools")) + { + // The Item Picker tool is super useful to visually select an item and break into the call-stack of where it was submitted. + if (Button("Item Picker..")) + DebugStartItemPicker(); + SameLine(); + MetricsHelpMarker("Will call the IM_DEBUG_BREAK() macro to break in debugger.\nWarning: If you don't have a debugger attached, this will probably crash."); + + Checkbox("Show windows begin order", &cfg->ShowWindowsBeginOrder); + Checkbox("Show windows rectangles", &cfg->ShowWindowsRects); + SameLine(); + SetNextItemWidth(GetFontSize() * 12); + cfg->ShowWindowsRects |= Combo("##show_windows_rect_type", &cfg->ShowWindowsRectsType, wrt_rects_names, WRT_Count, WRT_Count); + if (cfg->ShowWindowsRects && g.NavWindow != NULL) + { + BulletText("'%s':", g.NavWindow->Name); + Indent(); + for (int rect_n = 0; rect_n < WRT_Count; rect_n++) + { + ImRect r = Funcs::GetWindowRect(g.NavWindow, rect_n); + Text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), wrt_rects_names[rect_n]); + } + Unindent(); + } + Checkbox("Show ImDrawCmd mesh when hovering", &cfg->ShowDrawCmdMesh); + Checkbox("Show ImDrawCmd bounding boxes when hovering", &cfg->ShowDrawCmdBoundingBoxes); + + Checkbox("Show tables rectangles", &cfg->ShowTablesRects); + SameLine(); + SetNextItemWidth(GetFontSize() * 12); + cfg->ShowTablesRects |= Combo("##show_table_rects_type", &cfg->ShowTablesRectsType, trt_rects_names, TRT_Count, TRT_Count); + if (cfg->ShowTablesRects && g.NavWindow != NULL) + { + for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) + { + ImGuiTable* table = g.Tables.GetByIndex(table_n); + if (table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow)) + continue; + + BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + Indent(); + char buf[128]; + for (int rect_n = 0; rect_n < TRT_Count; rect_n++) + { + if (rect_n >= TRT_ColumnsRect) + { + if (rect_n != TRT_ColumnsRect && rect_n != TRT_ColumnsClipRect) + continue; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImRect r = Funcs::GetTableRect(table, rect_n, column_n); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]); + Selectable(buf); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + } + } + else + { + ImRect r = Funcs::GetTableRect(table, rect_n, -1); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]); + Selectable(buf); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + } + } + Unindent(); + } + } + + TreePop(); + } + + // Windows + DebugNodeWindowsList(&g.Windows, "Windows"); + //DebugNodeWindowsList(&g.WindowsFocusOrder, "WindowsFocusOrder"); + + // DrawLists + int drawlist_count = 0; + for (int viewport_i = 0; viewport_i < g.Viewports.Size; viewport_i++) + drawlist_count += g.Viewports[viewport_i]->DrawDataBuilder.GetDrawListCount(); + if (TreeNode("DrawLists", "DrawLists (%d)", drawlist_count)) + { + for (int viewport_i = 0; viewport_i < g.Viewports.Size; viewport_i++) + { + ImGuiViewportP* viewport = g.Viewports[viewport_i]; + for (int layer_i = 0; layer_i < IM_ARRAYSIZE(viewport->DrawDataBuilder.Layers); layer_i++) + for (int draw_list_i = 0; draw_list_i < viewport->DrawDataBuilder.Layers[layer_i].Size; draw_list_i++) + DebugNodeDrawList(NULL, viewport->DrawDataBuilder.Layers[layer_i][draw_list_i], "DrawList"); + } + TreePop(); + } + + // Viewports + if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size)) + { + Indent(GetTreeNodeToLabelSpacing()); + RenderViewportsThumbnails(); + Unindent(GetTreeNodeToLabelSpacing()); + for (int i = 0; i < g.Viewports.Size; i++) + DebugNodeViewport(g.Viewports[i]); + TreePop(); + } + + // Details for Popups + if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) + { + for (int i = 0; i < g.OpenPopupStack.Size; i++) + { + ImGuiWindow* window = g.OpenPopupStack[i].Window; + BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : ""); + } + TreePop(); + } + + // Details for TabBars + if (TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetSize())) + { + for (int n = 0; n < g.TabBars.GetSize(); n++) + DebugNodeTabBar(g.TabBars.GetByIndex(n), "TabBar"); + TreePop(); + } + + // Details for Tables + if (TreeNode("Tables", "Tables (%d)", g.Tables.GetSize())) + { + for (int n = 0; n < g.Tables.GetSize(); n++) + DebugNodeTable(g.Tables.GetByIndex(n)); + TreePop(); + } + + // Details for Fonts +#ifndef IMGUI_DISABLE_DEMO_WINDOWS + ImFontAtlas* atlas = g.IO.Fonts; + if (TreeNode("Fonts", "Fonts (%d)", atlas->Fonts.Size)) + { + ShowFontAtlas(atlas); + TreePop(); + } +#endif + + // Details for Docking +#ifdef IMGUI_HAS_DOCK + if (TreeNode("Docking")) + { + TreePop(); + } +#endif // #ifdef IMGUI_HAS_DOCK + + // Settings + if (TreeNode("Settings")) + { + if (SmallButton("Clear")) + ClearIniSettings(); + SameLine(); + if (SmallButton("Save to memory")) + SaveIniSettingsToMemory(); + SameLine(); + if (SmallButton("Save to disk")) + SaveIniSettingsToDisk(g.IO.IniFilename); + SameLine(); + if (g.IO.IniFilename) + Text("\"%s\"", g.IO.IniFilename); + else + TextUnformatted(""); + Text("SettingsDirtyTimer %.2f", g.SettingsDirtyTimer); + if (TreeNode("SettingsHandlers", "Settings handlers: (%d)", g.SettingsHandlers.Size)) + { + for (int n = 0; n < g.SettingsHandlers.Size; n++) + BulletText("%s", g.SettingsHandlers[n].TypeName); + TreePop(); + } + if (TreeNode("SettingsWindows", "Settings packed data: Windows: %d bytes", g.SettingsWindows.size())) + { + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + DebugNodeWindowSettings(settings); + TreePop(); + } + + if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size())) + { + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + DebugNodeTableSettings(settings); + TreePop(); + } + +#ifdef IMGUI_HAS_DOCK +#endif // #ifdef IMGUI_HAS_DOCK + + if (TreeNode("SettingsIniData", "Settings unpacked data (.ini): %d bytes", g.SettingsIniData.size())) + { + InputTextMultiline("##Ini", (char*)(void*)g.SettingsIniData.c_str(), g.SettingsIniData.Buf.Size, ImVec2(-FLT_MIN, GetTextLineHeight() * 20), ImGuiInputTextFlags_ReadOnly); + TreePop(); + } + TreePop(); + } + + // Misc Details + if (TreeNode("Internal state")) + { + const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad", "Nav", "Clipboard" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT); + + Text("WINDOWING"); + Indent(); + Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); + Text("HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindow->Name : "NULL"); + Text("HoveredWindowUnderMovingWindow: '%s'", g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL"); + Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL"); + Unindent(); + + Text("ITEMS"); + Indent(); + Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, input_source_names[g.ActiveIdSource]); + Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); + Text("HoveredId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not + Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); + Unindent(); + + Text("NAV,FOCUS"); + Indent(); + Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); + Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); + Text("NavInputSource: %s", input_source_names[g.NavInputSource]); + Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); + Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId); + Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); + Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId); + Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL"); + Unindent(); + + TreePop(); + } + + // Overlay: Display windows Rectangles and Begin Order + if (cfg->ShowWindowsRects || cfg->ShowWindowsBeginOrder) + { + for (int n = 0; n < g.Windows.Size; n++) + { + ImGuiWindow* window = g.Windows[n]; + if (!window->WasActive) + continue; + ImDrawList* draw_list = GetForegroundDrawList(window); + if (cfg->ShowWindowsRects) + { + ImRect r = Funcs::GetWindowRect(window, cfg->ShowWindowsRectsType); + draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255)); + } + if (cfg->ShowWindowsBeginOrder && !(window->Flags & ImGuiWindowFlags_ChildWindow)) + { + char buf[32]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext); + float font_size = GetFontSize(); + draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255)); + draw_list->AddText(window->Pos, IM_COL32(255, 255, 255, 255), buf); + } + } + } + + // Overlay: Display Tables Rectangles + if (cfg->ShowTablesRects) + { + for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) + { + ImGuiTable* table = g.Tables.GetByIndex(table_n); + if (table->LastFrameActive < g.FrameCount - 1) + continue; + ImDrawList* draw_list = GetForegroundDrawList(table->OuterWindow); + if (cfg->ShowTablesRectsType >= TRT_ColumnsRect) + { + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n); + ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255); + float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f; + draw_list->AddRect(r.Min, r.Max, col, 0.0f, 0, thickness); + } + } + else + { + ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, -1); + draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255)); + } + } + } + +#ifdef IMGUI_HAS_DOCK + // Overlay: Display Docking info + if (show_docking_nodes && g.IO.KeyCtrl) + { + } +#endif // #ifdef IMGUI_HAS_DOCK + + End(); +} + +// [DEBUG] Display contents of Columns +void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) +{ + if (!TreeNode((void*)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID, columns->Count, columns->Flags)) + return; + BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->OffMaxX - columns->OffMinX, columns->OffMinX, columns->OffMaxX); + for (int column_n = 0; column_n < columns->Columns.Size; column_n++) + BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, columns->Columns[column_n].OffsetNorm, GetColumnOffsetFromNorm(columns, columns->Columns[column_n].OffsetNorm)); + TreePop(); +} + +// [DEBUG] Display contents of ImDrawList +void ImGui::DebugNodeDrawList(ImGuiWindow* window, const ImDrawList* draw_list, const char* label) +{ + ImGuiContext& g = *GImGui; + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + int cmd_count = draw_list->CmdBuffer.Size; + if (cmd_count > 0 && draw_list->CmdBuffer.back().ElemCount == 0 && draw_list->CmdBuffer.back().UserCallback == NULL) + cmd_count--; + bool node_open = TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, cmd_count); + if (draw_list == GetWindowDrawList()) + { + SameLine(); + TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) + if (node_open) + TreePop(); + return; + } + + ImDrawList* fg_draw_list = GetForegroundDrawList(window); // Render additional visuals into the top-most draw list + if (window && IsItemHovered()) + fg_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); + if (!node_open) + return; + + if (window && !window->WasActive) + TextDisabled("Warning: owning Window is inactive. This DrawList is not being rendered!"); + + for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.Data; pcmd < draw_list->CmdBuffer.Data + cmd_count; pcmd++) + { + if (pcmd->UserCallback) + { + BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); + continue; + } + + char buf[300]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex 0x%p, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", + pcmd->ElemCount / 3, (void*)(intptr_t)pcmd->TextureId, + pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); + bool pcmd_node_open = TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "%s", buf); + if (IsItemHovered() && (cfg->ShowDrawCmdMesh || cfg->ShowDrawCmdBoundingBoxes) && fg_draw_list) + DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, cfg->ShowDrawCmdMesh, cfg->ShowDrawCmdBoundingBoxes); + if (!pcmd_node_open) + continue; + + // Calculate approximate coverage area (touched pixel count) + // This will be in pixels squared as long there's no post-scaling happening to the renderer output. + const ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; + const ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + pcmd->VtxOffset; + float total_area = 0.0f; + for (unsigned int idx_n = pcmd->IdxOffset; idx_n < pcmd->IdxOffset + pcmd->ElemCount; ) + { + ImVec2 triangle[3]; + for (int n = 0; n < 3; n++, idx_n++) + triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos; + total_area += ImTriangleArea(triangle[0], triangle[1], triangle[2]); + } + + // Display vertex information summary. Hover to get all triangles drawn in wire-frame + ImFormatString(buf, IM_ARRAYSIZE(buf), "Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d, Area: ~%0.f px", pcmd->ElemCount, pcmd->VtxOffset, pcmd->IdxOffset, total_area); + Selectable(buf); + if (IsItemHovered() && fg_draw_list) + DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, true, false); + + // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. + ImGuiListClipper clipper; + clipper.Begin(pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. + while (clipper.Step()) + for (int prim = clipper.DisplayStart, idx_i = pcmd->IdxOffset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++) + { + char* buf_p = buf, * buf_end = buf + IM_ARRAYSIZE(buf); + ImVec2 triangle[3]; + for (int n = 0; n < 3; n++, idx_i++) + { + const ImDrawVert& v = vtx_buffer[idx_buffer ? idx_buffer[idx_i] : idx_i]; + triangle[n] = v.pos; + buf_p += ImFormatString(buf_p, buf_end - buf_p, "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", + (n == 0) ? "Vert:" : " ", idx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); + } + + Selectable(buf, false); + if (fg_draw_list && IsItemHovered()) + { + ImDrawListFlags backup_flags = fg_draw_list->Flags; + fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); + fg_draw_list->Flags = backup_flags; + } + } + TreePop(); + } + TreePop(); +} + +// [DEBUG] Display mesh/aabb of a ImDrawCmd +void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb) +{ + IM_ASSERT(show_mesh || show_aabb); + ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; + ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + draw_cmd->VtxOffset; + + // Draw wire-frame version of all triangles + ImRect clip_rect = draw_cmd->ClipRect; + ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + ImDrawListFlags backup_flags = out_draw_list->Flags; + out_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + for (unsigned int idx_n = draw_cmd->IdxOffset; idx_n < draw_cmd->IdxOffset + draw_cmd->ElemCount; ) + { + ImVec2 triangle[3]; + for (int n = 0; n < 3; n++, idx_n++) + vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos)); + if (show_mesh) + out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); // In yellow: mesh triangles + } + // Draw bounding boxes + if (show_aabb) + { + out_draw_list->AddRect(ImFloor(clip_rect.Min), ImFloor(clip_rect.Max), IM_COL32(255, 0, 255, 255)); // In pink: clipping rectangle submitted to GPU + out_draw_list->AddRect(ImFloor(vtxs_rect.Min), ImFloor(vtxs_rect.Max), IM_COL32(0, 255, 255, 255)); // In cyan: bounding box of triangles + } + out_draw_list->Flags = backup_flags; +} + +// [DEBUG] Display contents of ImGuiStorage +void ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label) +{ + if (!TreeNode(label, "%s: %d entries, %d bytes", label, storage->Data.Size, storage->Data.size_in_bytes())) + return; + for (int n = 0; n < storage->Data.Size; n++) + { + const ImGuiStorage::ImGuiStoragePair& p = storage->Data[n]; + BulletText("Key 0x%08X Value { i: %d }", p.key, p.val_i); // Important: we currently don't store a type, real value may not be integer. + } + TreePop(); +} + +// [DEBUG] Display contents of ImGuiTabBar +void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) +{ + // Standalone tab bars (not associated to docking/windows functionality) currently hold no discernible strings. + char buf[256]; + char* p = buf; + const char* buf_end = buf + IM_ARRAYSIZE(buf); + const bool is_active = (tab_bar->PrevFrameVisible >= GetFrameCount() - 2); + p += ImFormatString(p, buf_end - p, "%s 0x%08X (%d tabs)%s", label, tab_bar->ID, tab_bar->Tabs.Size, is_active ? "" : " *Inactive*"); + IM_UNUSED(p); + if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } + bool open = TreeNode(tab_bar, "%s", buf); + if (!is_active) { PopStyleColor(); } + if (is_active && IsItemHovered()) + { + ImDrawList* draw_list = GetForegroundDrawList(); + draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); + draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + } + if (open) + { + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + const ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + PushID(tab); + if (SmallButton("<")) { TabBarQueueReorder(tab_bar, tab, -1); } SameLine(0, 2); + if (SmallButton(">")) { TabBarQueueReorder(tab_bar, tab, +1); } SameLine(); + Text("%02d%c Tab 0x%08X '%s' Offset: %.1f, Width: %.1f/%.1f", + tab_n, (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, (tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : "", tab->Offset, tab->Width, tab->ContentWidth); + PopID(); + } + TreePop(); + } +} + +void ImGui::DebugNodeViewport(ImGuiViewportP* viewport) +{ + SetNextItemOpen(true, ImGuiCond_Once); + if (TreeNode("viewport0", "Viewport #%d", 0)) + { + ImGuiWindowFlags flags = viewport->Flags; + BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f", + viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y, + viewport->WorkOffsetMin.x, viewport->WorkOffsetMin.y, viewport->WorkOffsetMax.x, viewport->WorkOffsetMax.y); + BulletText("Flags: 0x%04X =%s%s%s", viewport->Flags, + (flags & ImGuiViewportFlags_IsPlatformWindow) ? " IsPlatformWindow" : "", + (flags & ImGuiViewportFlags_IsPlatformMonitor) ? " IsPlatformMonitor" : "", + (flags & ImGuiViewportFlags_OwnedByApp) ? " OwnedByApp" : ""); + for (int layer_i = 0; layer_i < IM_ARRAYSIZE(viewport->DrawDataBuilder.Layers); layer_i++) + for (int draw_list_i = 0; draw_list_i < viewport->DrawDataBuilder.Layers[layer_i].Size; draw_list_i++) + DebugNodeDrawList(NULL, viewport->DrawDataBuilder.Layers[layer_i][draw_list_i], "DrawList"); + TreePop(); + } +} + +void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) +{ + if (window == NULL) + { + BulletText("%s: NULL", label); + return; + } + + ImGuiContext& g = *GImGui; + const bool is_active = window->WasActive; + ImGuiTreeNodeFlags tree_node_flags = (window == g.NavWindow) ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None; + if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } + const bool open = TreeNodeEx(label, tree_node_flags, "%s '%s'%s", label, window->Name, is_active ? "" : " *Inactive*"); + if (!is_active) { PopStyleColor(); } + if (IsItemHovered() && is_active) + GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); + if (!open) + return; + + if (window->MemoryCompacted) + TextDisabled("Note: some memory buffers have been compacted/freed."); + + ImGuiWindowFlags flags = window->Flags; + DebugNodeDrawList(window, window->DrawList, "DrawList"); + BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), ContentSize (%.1f,%.1f) Ideal (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->ContentSize.x, window->ContentSize.y, window->ContentSizeIdeal.x, window->ContentSizeIdeal.y); + BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s%s..)", flags, + (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "", + (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "", + (flags & ImGuiWindowFlags_NoMouseInputs)? "NoMouseInputs":"", (flags & ImGuiWindowFlags_NoNavInputs) ? "NoNavInputs" : "", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : ""); + BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s", window->Scroll.x, window->ScrollMax.x, window->Scroll.y, window->ScrollMax.y, window->ScrollbarX ? "X" : "", window->ScrollbarY ? "Y" : ""); + BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1); + BulletText("Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesCanSkipItems, window->HiddenFramesCannotSkipItems, window->SkipItems); + for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) + { + ImRect r = window->NavRectRel[layer]; + if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y) + { + BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]); + continue; + } + BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); + if (IsItemHovered()) + GetForegroundDrawList(window)->AddRect(r.Min + window->Pos, r.Max + window->Pos, IM_COL32(255, 255, 0, 255)); + } + BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); + if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); } + if (window->ParentWindow != NULL) { DebugNodeWindow(window->ParentWindow, "ParentWindow"); } + if (window->DC.ChildWindows.Size > 0) { DebugNodeWindowsList(&window->DC.ChildWindows, "ChildWindows"); } + if (window->ColumnsStorage.Size > 0 && TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size)) + { + for (int n = 0; n < window->ColumnsStorage.Size; n++) + DebugNodeColumns(&window->ColumnsStorage[n]); + TreePop(); + } + DebugNodeStorage(&window->StateStorage, "Storage"); + TreePop(); +} + +void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings) +{ + Text("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d", + settings->ID, settings->GetName(), settings->Pos.x, settings->Pos.y, settings->Size.x, settings->Size.y, settings->Collapsed); +} + +void ImGui::DebugNodeWindowsList(ImVector* windows, const char* label) +{ + if (!TreeNode(label, "%s (%d)", label, windows->Size)) + return; + Text("(In front-to-back order:)"); + for (int i = windows->Size - 1; i >= 0; i--) // Iterate front to back + { + PushID((*windows)[i]); + DebugNodeWindow((*windows)[i], "Window"); + PopID(); + } + TreePop(); +} + +#else + +void ImGui::ShowMetricsWindow(bool*) {} +void ImGui::DebugNodeColumns(ImGuiOldColumns*) {} +void ImGui::DebugNodeDrawList(ImGuiWindow*, const ImDrawList*, const char*) {} +void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} +void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} +void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} +void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} +void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {} +void ImGui::DebugNodeWindowsList(ImVector*, const char*) {} +void ImGui::DebugNodeViewport(ImGuiViewportP*) {} + +#endif + +//----------------------------------------------------------------------------- + +// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. +// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github. +#ifdef IMGUI_INCLUDE_IMGUI_USER_INL +#include "imgui_user.inl" +#endif + +//----------------------------------------------------------------------------- + +#endif // #ifndef IMGUI_DISABLE diff --git a/vendor/librw/skeleton/imgui/imgui.h b/vendor/librw/skeleton/imgui/imgui.h new file mode 100644 index 0000000..06a610d --- /dev/null +++ b/vendor/librw/skeleton/imgui/imgui.h @@ -0,0 +1,2852 @@ +// dear imgui, v1.83 +// (headers) + +// Help: +// - Read FAQ at http://dearimgui.org/faq +// - Newcomers, read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. +// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. +// Read imgui.cpp for details, links and comments. + +// Resources: +// - FAQ http://dearimgui.org/faq +// - Homepage & latest https://github.com/ocornut/imgui +// - Releases & changelog https://github.com/ocornut/imgui/releases +// - Gallery https://github.com/ocornut/imgui/issues/3793 (please post your screenshots/video there!) +// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Issues & support https://github.com/ocornut/imgui/issues +// - Discussions https://github.com/ocornut/imgui/discussions + +/* + +Index of this file: +// [SECTION] Header mess +// [SECTION] Forward declarations and basic types +// [SECTION] Dear ImGui end-user API functions +// [SECTION] Flags & Enumerations +// [SECTION] Helpers: Memory allocations macros, ImVector<> +// [SECTION] ImGuiStyle +// [SECTION] ImGuiIO +// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) +// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, ImColor) +// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) +// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) +// [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) +// [SECTION] Obsolete functions and types + +*/ + +#pragma once + +// Configuration file with compile-time options (edit imconfig.h or '#define IMGUI_USER_CONFIG "myfilename.h" from your build system') +#ifdef IMGUI_USER_CONFIG +#include IMGUI_USER_CONFIG +#endif +#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H) +#include "imconfig.h" +#endif + +#ifndef IMGUI_DISABLE + +//----------------------------------------------------------------------------- +// [SECTION] Header mess +//----------------------------------------------------------------------------- + +// Includes +#include // FLT_MIN, FLT_MAX +#include // va_list, va_start, va_end +#include // ptrdiff_t, NULL +#include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp + +// Version +// (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) +#define IMGUI_VERSION "1.83" +#define IMGUI_VERSION_NUM 18300 +#define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) +#define IMGUI_HAS_TABLE + +// Define attributes of all API symbols declarations (e.g. for DLL under Windows) +// IMGUI_API is used for core imgui functions, IMGUI_IMPL_API is used for the default backends files (imgui_impl_xxx.h) +// Using dear imgui via a shared library is not recommended, because we don't guarantee backward nor forward ABI compatibility (also function call overhead, as dear imgui is a call-heavy API) +#ifndef IMGUI_API +#define IMGUI_API +#endif +#ifndef IMGUI_IMPL_API +#define IMGUI_IMPL_API IMGUI_API +#endif + +// Helper Macros +#ifndef IM_ASSERT +#include +#define IM_ASSERT(_EXPR) assert(_EXPR) // You can override the default assert handler by editing imconfig.h +#endif +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers! +#define IM_UNUSED(_VAR) ((void)(_VAR)) // Used to silence "unused variable warnings". Often useful as asserts may be stripped out from final builds. +#if (__cplusplus >= 201100) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201100) +#define IM_OFFSETOF(_TYPE,_MEMBER) offsetof(_TYPE, _MEMBER) // Offset of _MEMBER within _TYPE. Standardized as offsetof() in C++11 +#else +#define IM_OFFSETOF(_TYPE,_MEMBER) ((size_t)&(((_TYPE*)0)->_MEMBER)) // Offset of _MEMBER within _TYPE. Old style macro. +#endif + +// Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions. +#if !defined(IMGUI_USE_STB_SPRINTF) && defined(__clang__) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) +#elif !defined(IMGUI_USE_STB_SPRINTF) && defined(__GNUC__) && defined(__MINGW32__) +#define IM_FMTARGS(FMT) __attribute__((format(gnu_printf, FMT, FMT+1))) +#define IM_FMTLIST(FMT) __attribute__((format(gnu_printf, FMT, 0))) +#else +#define IM_FMTARGS(FMT) +#define IM_FMTLIST(FMT) +#endif + +// Disable some of MSVC most aggressive Debug runtime checks in function header/footer (used in some simple/low-level functions) +#if defined(_MSC_VER) && !defined(__clang__) && !defined(IMGUI_DEBUG_PARANOID) +#define IM_MSVC_RUNTIME_CHECKS_OFF __pragma(runtime_checks("",off)) __pragma(check_stack(off)) __pragma(strict_gs_check(push,off)) +#define IM_MSVC_RUNTIME_CHECKS_RESTORE __pragma(runtime_checks("",restore)) __pragma(check_stack()) __pragma(strict_gs_check(pop)) +#else +#define IM_MSVC_RUNTIME_CHECKS_OFF +#define IM_MSVC_RUNTIME_CHECKS_RESTORE +#endif + +// Warnings +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Forward declarations and basic types +//----------------------------------------------------------------------------- + +// Forward declarations +struct ImDrawChannel; // Temporary storage to output draw commands out of order, used by ImDrawListSplitter and ImDrawList::ChannelsSplit() +struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call, unless it is a callback) +struct ImDrawData; // All draw command lists required to render the frame + pos/size coordinates to use for the projection matrix. +struct ImDrawList; // A single draw command list (generally one per window, conceptually you may see this as a dynamic "mesh" builder) +struct ImDrawListSharedData; // Data shared among multiple draw lists (typically owned by parent ImGui context, but you may create one yourself) +struct ImDrawListSplitter; // Helper to split a draw list into different layers which can be drawn into out of order, then flattened back. +struct ImDrawVert; // A single vertex (pos + uv + col = 20 bytes by default. Override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) +struct ImFont; // Runtime data for a single font within a parent ImFontAtlas +struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader +struct ImFontBuilderIO; // Opaque interface to a font builder (stb_truetype or FreeType). +struct ImFontConfig; // Configuration data when adding a font or merging fonts +struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) +struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data +struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) +struct ImGuiContext; // Dear ImGui context (opaque structure, unless including imgui_internal.h) +struct ImGuiIO; // Main configuration and I/O between your application and ImGui +struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use) +struct ImGuiListClipper; // Helper to manually clip large list of items +struct ImGuiOnceUponAFrame; // Helper for running a block of code not more than once a frame, used by IMGUI_ONCE_UPON_A_FRAME macro +struct ImGuiPayload; // User data payload for drag and drop operations +struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) +struct ImGuiStorage; // Helper for key->value storage +struct ImGuiStyle; // Runtime data for styling/colors +struct ImGuiTableSortSpecs; // Sorting specifications for a table (often handling sort specs for a single column, occasionally more) +struct ImGuiTableColumnSortSpecs; // Sorting specification for one column of a table +struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder) +struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbbb][,ccccc]") +struct ImGuiViewport; // A Platform Window (always only one in 'master' branch), in the future may represent Platform Monitor + +// Enums/Flags (declared as int for compatibility with old C++, to allow using as flags and to not pollute the top of this file) +// - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! +// In Visual Studio IDE: CTRL+comma ("Edit.NavigateTo") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +typedef int ImGuiCol; // -> enum ImGuiCol_ // Enum: A color identifier for styling +typedef int ImGuiCond; // -> enum ImGuiCond_ // Enum: A condition for many Set*() functions +typedef int ImGuiDataType; // -> enum ImGuiDataType_ // Enum: A primary data type +typedef int ImGuiDir; // -> enum ImGuiDir_ // Enum: A cardinal direction +typedef int ImGuiKey; // -> enum ImGuiKey_ // Enum: A key identifier (ImGui-side enum) +typedef int ImGuiNavInput; // -> enum ImGuiNavInput_ // Enum: An input identifier for navigation +typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A mouse button identifier (0=left, 1=right, 2=middle) +typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier +typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) +typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling +typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A color target for TableSetBgColor() +typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions +typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance +typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build +typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags +typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for InvisibleButton() +typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit4(), ColorPicker4() etc. +typedef int ImGuiConfigFlags; // -> enum ImGuiConfigFlags_ // Flags: for io.ConfigFlags +typedef int ImGuiComboFlags; // -> enum ImGuiComboFlags_ // Flags: for BeginCombo() +typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: for BeginDragDropSource(), AcceptDragDropPayload() +typedef int ImGuiFocusedFlags; // -> enum ImGuiFocusedFlags_ // Flags: for IsWindowFocused() +typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: for IsItemHovered(), IsWindowHovered() etc. +typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText(), InputTextMultiline() +typedef int ImGuiKeyModFlags; // -> enum ImGuiKeyModFlags_ // Flags: for io.KeyMods (Ctrl/Shift/Alt/Super) +typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() +typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable() +typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. +typedef int ImGuiTabBarFlags; // -> enum ImGuiTabBarFlags_ // Flags: for BeginTabBar() +typedef int ImGuiTabItemFlags; // -> enum ImGuiTabItemFlags_ // Flags: for BeginTabItem() +typedef int ImGuiTableFlags; // -> enum ImGuiTableFlags_ // Flags: For BeginTable() +typedef int ImGuiTableColumnFlags; // -> enum ImGuiTableColumnFlags_// Flags: For TableSetupColumn() +typedef int ImGuiTableRowFlags; // -> enum ImGuiTableRowFlags_ // Flags: For TableNextRow() +typedef int ImGuiTreeNodeFlags; // -> enum ImGuiTreeNodeFlags_ // Flags: for TreeNode(), TreeNodeEx(), CollapsingHeader() +typedef int ImGuiViewportFlags; // -> enum ImGuiViewportFlags_ // Flags: for ImGuiViewport +typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: for Begin(), BeginChild() + +// Other types +#ifndef ImTextureID // ImTextureID [configurable type: override in imconfig.h with '#define ImTextureID xxx'] +typedef void* ImTextureID; // User data for rendering backend to identify a texture. This is whatever to you want it to be! read the FAQ about ImTextureID for details. +#endif +typedef unsigned int ImGuiID; // A unique ID used by widgets, typically hashed from a stack of string. +typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data); // Callback function for ImGui::InputText() +typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); // Callback function for ImGui::SetNextWindowSizeConstraints() +typedef void* (*ImGuiMemAllocFunc)(size_t sz, void* user_data); // Function signature for ImGui::SetAllocatorFunctions() +typedef void (*ImGuiMemFreeFunc)(void* ptr, void* user_data); // Function signature for ImGui::SetAllocatorFunctions() + +// Character types +// (we generally use UTF-8 encoded string in the API. This is storage specifically for a decoded character used for keyboard input and display) +typedef unsigned short ImWchar16; // A single decoded U16 character/code point. We encode them as multi bytes UTF-8 when used in strings. +typedef unsigned int ImWchar32; // A single decoded U32 character/code point. We encode them as multi bytes UTF-8 when used in strings. +#ifdef IMGUI_USE_WCHAR32 // ImWchar [configurable type: override in imconfig.h with '#define IMGUI_USE_WCHAR32' to support Unicode planes 1-16] +typedef ImWchar32 ImWchar; +#else +typedef ImWchar16 ImWchar; +#endif + +// Basic scalar data types +typedef signed char ImS8; // 8-bit signed integer +typedef unsigned char ImU8; // 8-bit unsigned integer +typedef signed short ImS16; // 16-bit signed integer +typedef unsigned short ImU16; // 16-bit unsigned integer +typedef signed int ImS32; // 32-bit signed integer == int +typedef unsigned int ImU32; // 32-bit unsigned integer (often used to store packed colors) +#if defined(_MSC_VER) && !defined(__clang__) +typedef signed __int64 ImS64; // 64-bit signed integer (pre and post C++11 with Visual Studio) +typedef unsigned __int64 ImU64; // 64-bit unsigned integer (pre and post C++11 with Visual Studio) +#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100) +#include +typedef int64_t ImS64; // 64-bit signed integer (pre C++11) +typedef uint64_t ImU64; // 64-bit unsigned integer (pre C++11) +#else +typedef signed long long ImS64; // 64-bit signed integer (post C++11) +typedef unsigned long long ImU64; // 64-bit unsigned integer (post C++11) +#endif + +// 2D vector (often used to store positions or sizes) +IM_MSVC_RUNTIME_CHECKS_OFF +struct ImVec2 +{ + float x, y; + ImVec2() { x = y = 0.0f; } + ImVec2(float _x, float _y) { x = _x; y = _y; } + float operator[] (size_t idx) const { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. + float& operator[] (size_t idx) { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. +#ifdef IM_VEC2_CLASS_EXTRA + IM_VEC2_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec2. +#endif +}; + +// 4D vector (often used to store floating-point colors) +struct ImVec4 +{ + float x, y, z, w; + ImVec4() { x = y = z = w = 0.0f; } + ImVec4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; } +#ifdef IM_VEC4_CLASS_EXTRA + IM_VEC4_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec4. +#endif +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + +//----------------------------------------------------------------------------- +// [SECTION] Dear ImGui end-user API functions +// (Note that ImGui:: being a namespace, you can add extra ImGui:: functions in your own separate file. Please don't modify imgui source files!) +//----------------------------------------------------------------------------- + +namespace ImGui +{ + // Context creation and access + // - Each context create its own ImFontAtlas by default. You may instance one yourself and pass it to CreateContext() to share a font atlas between contexts. + // - DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() + // for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for details. + IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL); + IMGUI_API void DestroyContext(ImGuiContext* ctx = NULL); // NULL = destroy current context + IMGUI_API ImGuiContext* GetCurrentContext(); + IMGUI_API void SetCurrentContext(ImGuiContext* ctx); + + // Main + IMGUI_API ImGuiIO& GetIO(); // access the IO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags) + IMGUI_API ImGuiStyle& GetStyle(); // access the Style structure (colors, sizes). Always use PushStyleCol(), PushStyleVar() to modify style mid-frame! + IMGUI_API void NewFrame(); // start a new Dear ImGui frame, you can submit any command from this point until Render()/EndFrame(). + IMGUI_API void EndFrame(); // ends the Dear ImGui frame. automatically called by Render(). If you don't need to render data (skipping rendering) you may call EndFrame() without Render()... but you'll have wasted CPU already! If you don't need to render, better to not create any windows and not call NewFrame() at all! + IMGUI_API void Render(); // ends the Dear ImGui frame, finalize the draw data. You can then get call GetDrawData(). + IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. + + // Demo, Debug, Information + IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create Demo window. demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! + IMGUI_API void ShowMetricsWindow(bool* p_open = NULL); // create Metrics/Debugger window. display Dear ImGui internals: windows, draw commands, various internal state, etc. + IMGUI_API void ShowAboutWindow(bool* p_open = NULL); // create About window. display Dear ImGui version, credits and build/system information. + IMGUI_API void ShowStyleEditor(ImGuiStyle* ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style) + IMGUI_API bool ShowStyleSelector(const char* label); // add style selector block (not a window), essentially a combo listing the default styles. + IMGUI_API void ShowFontSelector(const char* label); // add font selector block (not a window), essentially a combo listing the loaded fonts. + IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as a end-user (mouse/keyboard controls). + IMGUI_API const char* GetVersion(); // get the compiled version string e.g. "1.80 WIP" (essentially the value for IMGUI_VERSION from the compiled version of imgui.cpp) + + // Styles + IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL); // new, recommended style (default) + IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); // best used with borders and a custom, thicker font + IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); // classic imgui style + + // Windows + // - Begin() = push window to the stack and start appending to it. End() = pop window from the stack. + // - Passing 'bool* p_open != NULL' shows a window-closing widget in the upper-right corner of the window, + // which clicking will set the boolean to false when clicked. + // - You may append multiple times to the same window during the same frame by calling Begin()/End() pairs multiple times. + // Some information such as 'flags' or 'p_open' will only be considered by the first call to Begin(). + // - Begin() return false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting + // anything to the window. Always call a matching End() for each Begin() call, regardless of its return value! + // [Important: due to legacy reason, this is inconsistent with most other functions such as BeginMenu/EndMenu, + // BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function + // returned true. Begin and BeginChild are the only odd ones out. Will be fixed in a future update.] + // - Note that the bottom of window stack always contains a window called "Debug". + IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); + IMGUI_API void End(); + + // Child Windows + // - Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window. Child windows can embed their own child. + // - For each independent axis of 'size': ==0.0f: use remaining host window size / >0.0f: fixed size / <0.0f: use remaining window size minus abs(size) / Each axis can use a different mode, e.g. ImVec2(0,400). + // - BeginChild() returns false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting anything to the window. + // Always call a matching EndChild() for each BeginChild() call, regardless of its return value. + // [Important: due to legacy reason, this is inconsistent with most other functions such as BeginMenu/EndMenu, + // BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function + // returned true. Begin and BeginChild are the only odd ones out. Will be fixed in a future update.] + IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0, 0), bool border = false, ImGuiWindowFlags flags = 0); + IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0, 0), bool border = false, ImGuiWindowFlags flags = 0); + IMGUI_API void EndChild(); + + // Windows Utilities + // - 'current window' = the window we are appending into while inside a Begin()/End() block. 'next window' = next window we will Begin() into. + IMGUI_API bool IsWindowAppearing(); + IMGUI_API bool IsWindowCollapsed(); + IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags=0); // is current window focused? or its root/child, depending on flags. see flags for options. + IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags=0); // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. NB: If you are trying to check whether your mouse should be dispatched to imgui or to your app, you should use the 'io.WantCaptureMouse' boolean for that! Please read the FAQ! + IMGUI_API ImDrawList* GetWindowDrawList(); // get draw list associated to the current window, to append your own drawing primitives + IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (useful if you want to do your own drawing via the DrawList API) + IMGUI_API ImVec2 GetWindowSize(); // get current window size + IMGUI_API float GetWindowWidth(); // get current window width (shortcut for GetWindowSize().x) + IMGUI_API float GetWindowHeight(); // get current window height (shortcut for GetWindowSize().y) + + // Prefer using SetNextXXX functions (before Begin) rather that SetXXX functions (after Begin). + IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0, 0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. + IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() + IMGUI_API void SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Sizes will be rounded down. Use callback to apply non-trivial programmatic constraints. + IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (~ scrollable client area, which enforce the range of scrollbars). Not including window decorations (title bar, menu bar, etc.) nor WindowPadding. set an axis to 0.0f to leave it automatic. call before Begin() + IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() + IMGUI_API void SetNextWindowFocus(); // set next window to be focused / top-most. call before Begin() + IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily override the Alpha component of ImGuiCol_WindowBg/ChildBg/PopupBg. you may also use ImGuiWindowFlags_NoBackground. + IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. + IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. + IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). + IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / top-most. prefer using SetNextWindowFocus(). + IMGUI_API void SetWindowFontScale(float scale); // set font scale. Adjust IO.FontGlobalScale if you want to scale all windows. This is an old API! For correct scaling, prefer to reload font + rebuild ImFontAtlas + call style.ScaleAllSizes(). + IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. + IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. + IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state + IMGUI_API void SetWindowFocus(const char* name); // set named window to be focused / top-most. use NULL to remove focus. + + // Content region + // - Retrieve available space from a given point. GetContentRegionAvail() is frequently useful. + // - Those functions are bound to be redesigned (they are confusing, incomplete and the Min/Max return values are in local window coordinates which increases confusion) + IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() + IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates + IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min (roughly (0,0)-Scroll), in window coordinates + IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max (roughly (0,0)+Size-Scroll) where Size can be override with SetNextWindowContentSize(), in window coordinates + IMGUI_API float GetWindowContentRegionWidth(); // + + // Windows Scrolling + IMGUI_API float GetScrollX(); // get scrolling amount [0 .. GetScrollMaxX()] + IMGUI_API float GetScrollY(); // get scrolling amount [0 .. GetScrollMaxY()] + IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0 .. GetScrollMaxX()] + IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0 .. GetScrollMaxY()] + IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.x - WindowSize.x - DecorationsSize.x + IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.y - WindowSize.y - DecorationsSize.y + IMGUI_API void SetScrollHereX(float center_x_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_x_ratio=0.0: left, 0.5: center, 1.0: right. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. + IMGUI_API void SetScrollHereY(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. + IMGUI_API void SetScrollFromPosX(float local_x, float center_x_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. + IMGUI_API void SetScrollFromPosY(float local_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. + + // Parameters stacks (shared) + IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + IMGUI_API void PopFont(); + IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); // modify a style color. always use this if you modify the style after NewFrame(). + IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); + IMGUI_API void PopStyleColor(int count = 1); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); // modify a style float variable. always use this if you modify the style after NewFrame(). + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); // modify a style ImVec2 variable. always use this if you modify the style after NewFrame(). + IMGUI_API void PopStyleVar(int count = 1); + IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // == tab stop enable. Allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets + IMGUI_API void PopAllowKeyboardFocus(); + IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. + IMGUI_API void PopButtonRepeat(); + + // Parameters stacks (current window) + IMGUI_API void PushItemWidth(float item_width); // push width of items for common large "item+label" widgets. >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -FLT_MIN always align width to the right side). + IMGUI_API void PopItemWidth(); + IMGUI_API void SetNextItemWidth(float item_width); // set width of the _next_ common large "item+label" widget. >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -FLT_MIN always align width to the right side) + IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position. NOT necessarily the width of last item unlike most 'Item' functions. + IMGUI_API void PushTextWrapPos(float wrap_local_pos_x = 0.0f); // push word-wrapping position for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space + IMGUI_API void PopTextWrapPos(); + + // Style read access + IMGUI_API ImFont* GetFont(); // get current font + IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied + IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API + IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier, packed as a 32-bit value suitable for ImDrawList + IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList + IMGUI_API ImU32 GetColorU32(ImU32 col); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList + IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwise use GetColorU32() to get style color with style alpha baked in. + + // Cursor / Layout + // - By "cursor" we mean the current output position. + // - The typical widget behavior is to output themselves at the current cursor position, then move the cursor one line down. + // - You can call SameLine() between widgets to undo the last carriage return and output at the right of the preceding widget. + // - Attention! We currently have inconsistencies between window-local and absolute positions we will aim to fix with future API: + // Window-local coordinates: SameLine(), GetCursorPos(), SetCursorPos(), GetCursorStartPos(), GetContentRegionMax(), GetWindowContentRegion*(), PushTextWrapPos() + // Absolute coordinate: GetCursorScreenPos(), SetCursorScreenPos(), all ImDrawList:: functions. + IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. + IMGUI_API void SameLine(float offset_from_start_x=0.0f, float spacing=-1.0f); // call between widgets or groups to layout them horizontally. X position given in window coordinates. + IMGUI_API void NewLine(); // undo a SameLine() or force a new line when in an horizontal-layout context. + IMGUI_API void Spacing(); // add vertical spacing. + IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size. unlike InvisibleButton(), Dummy() won't take the mouse click or be navigable into. + IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by indent_w, or style.IndentSpacing if indent_w <= 0 + IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by indent_w, or style.IndentSpacing if indent_w <= 0 + IMGUI_API void BeginGroup(); // lock horizontal starting position + IMGUI_API void EndGroup(); // unlock horizontal starting position + capture the whole group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) + IMGUI_API ImVec2 GetCursorPos(); // cursor position in window coordinates (relative to window position) + IMGUI_API float GetCursorPosX(); // (some functions are using window-relative coordinates, such as: GetCursorPos, GetCursorStartPos, GetContentRegionMax, GetWindowContentRegion* etc. + IMGUI_API float GetCursorPosY(); // other functions such as GetCursorScreenPos or everything in ImDrawList:: + IMGUI_API void SetCursorPos(const ImVec2& local_pos); // are using the main, absolute coordinate system. + IMGUI_API void SetCursorPosX(float local_x); // GetWindowPos() + GetCursorPos() == GetCursorScreenPos() etc.) + IMGUI_API void SetCursorPosY(float local_y); // + IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position in window coordinates + IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute coordinates (useful to work with ImDrawList API). generally top-left == GetMainViewport()->Pos == (0,0) in single viewport mode, and bottom-right == GetMainViewport()->Pos+Size == io.DisplaySize in single-viewport mode. + IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position in absolute coordinates + IMGUI_API void AlignTextToFramePadding(); // vertically align upcoming text baseline to FramePadding.y so that it will align properly to regularly framed items (call if you have text on a line before a framed item) + IMGUI_API float GetTextLineHeight(); // ~ FontSize + IMGUI_API float GetTextLineHeightWithSpacing(); // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text) + IMGUI_API float GetFrameHeight(); // ~ FontSize + style.FramePadding.y * 2 + IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets) + + // ID stack/scopes + // - Read the FAQ for more details about how ID are handled in dear imgui. If you are creating widgets in a loop you most + // likely want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them. + // - The resulting ID are hashes of the entire stack. + // - You can also use the "Label##foobar" syntax within widget label to distinguish them from each others. + // - In this header file we use the "label"/"name" terminology to denote a string that will be displayed and used as an ID, + // whereas "str_id" denote a string that is only used as an ID and not normally displayed. + IMGUI_API void PushID(const char* str_id); // push string into the ID stack (will hash string). + IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); // push string into the ID stack (will hash string). + IMGUI_API void PushID(const void* ptr_id); // push pointer into the ID stack (will hash pointer). + IMGUI_API void PushID(int int_id); // push integer into the ID stack (will hash integer). + IMGUI_API void PopID(); // pop from the ID stack. + IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself + IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); + IMGUI_API ImGuiID GetID(const void* ptr_id); + + // Widgets: Text + IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. + IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // formatted text + IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void TextDisabled(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextDisabledV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextWrapped(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). + IMGUI_API void TextWrappedV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void LabelText(const char* label, const char* fmt, ...) IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets + IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() + IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); + + // Widgets: Main + // - Most widgets return true when the value has been changed or when pressed/selected + // - You may also use one of the many IsItemXXX functions (e.g. IsItemActive, IsItemHovered, etc.) to query widget state. + IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0, 0)); // button + IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text + IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size, ImGuiButtonFlags flags = 0); // flexible button behavior without the visuals, frequently useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.) + IMGUI_API bool ArrowButton(const char* str_id, ImGuiDir dir); // square button with an arrow shape + IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); + IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding + IMGUI_API bool Checkbox(const char* label, bool* v); + IMGUI_API bool CheckboxFlags(const char* label, int* flags, int flags_value); + IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); + IMGUI_API bool RadioButton(const char* label, bool active); // use with e.g. if (RadioButton("one", my_value==1)) { my_value = 1; } + IMGUI_API bool RadioButton(const char* label, int* v, int v_button); // shortcut to handle the above pattern when value is an integer + IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-FLT_MIN, 0), const char* overlay = NULL); + IMGUI_API void Bullet(); // draw a small circle + keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses + + // Widgets: Combo Box + // - The BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() items. + // - The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. This is analogous to how ListBox are created. + IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0); + IMGUI_API void EndCombo(); // only call EndCombo() if BeginCombo() returns true! + IMGUI_API bool Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1); + IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1); // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0" + IMGUI_API bool Combo(const char* label, int* current_item, bool(*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int popup_max_height_in_items = -1); + + // Widgets: Drag Sliders + // - CTRL+Click on any drag box to turn them into an input box. Manually input values aren't clamped and can go off-bounds. + // - For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x + // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. + // - Format string may also be set to NULL or use the default format ("%f" or "%d"). + // - Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). + // - Use v_min < v_max to clamp edits to given limits. Note that CTRL+Click manual input can override those limits. + // - Use v_max = FLT_MAX / INT_MAX etc to avoid clamping to a maximum, same with v_min = -FLT_MAX / INT_MIN to avoid clamping to a minimum. + // - We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them. + // - Legacy: Pre-1.78 there are DragXXX() function signatures that takes a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. + // If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361 + IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", ImGuiSliderFlags flags = 0); // If v_min >= v_max we have no bound + IMGUI_API bool DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", ImGuiSliderFlags flags = 0); + IMGUI_API bool DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", ImGuiSliderFlags flags = 0); + IMGUI_API bool DragFloat4(const char* label, float v[4], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", ImGuiSliderFlags flags = 0); + IMGUI_API bool DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", const char* format_max = NULL, ImGuiSliderFlags flags = 0); + IMGUI_API bool DragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", ImGuiSliderFlags flags = 0); // If v_min >= v_max we have no bound + IMGUI_API bool DragInt2(const char* label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", ImGuiSliderFlags flags = 0); + IMGUI_API bool DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", ImGuiSliderFlags flags = 0); + IMGUI_API bool DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", ImGuiSliderFlags flags = 0); + IMGUI_API bool DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", const char* format_max = NULL, ImGuiSliderFlags flags = 0); + IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed = 1.0f, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0); + IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed = 1.0f, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0); + + // Widgets: Regular Sliders + // - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped and can go off-bounds. + // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. + // - Format string may also be set to NULL or use the default format ("%f" or "%d"). + // - Legacy: Pre-1.78 there are SliderXXX() function signatures that takes a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. + // If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361 + IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. + IMGUI_API bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderAngle(const char* label, float* v_rad, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f, const char* format = "%.0f deg", ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderInt(const char* label, int* v, int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0); + IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); + IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0); + IMGUI_API bool VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0); + + // Widgets: Input with Keyboard + // - If you want to use InputText() with std::string or any custom dynamic string type, see misc/cpp/imgui_stdlib.h and comments in imgui_demo.cpp. + // - Most of the ImGuiInputTextFlags flags are only useful for InputText() and not for InputFloatX, InputIntX, InputDouble etc. + IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, const char* format = "%.3f", ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputFloat2(const char* label, float v[2], const char* format = "%.3f", ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputFloat3(const char* label, float v[3], const char* format = "%.3f", ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputFloat4(const char* label, float v[4], const char* format = "%.3f", ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100, ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputDouble(const char* label, double* v, double step = 0.0, double step_fast = 0.0, const char* format = "%.6f", ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step = NULL, const void* p_step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags flags = 0); + IMGUI_API bool InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step = NULL, const void* p_step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags flags = 0); + + // Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little color square that can be left-clicked to open a picker, and right-clicked to open an option menu.) + // - Note that in C++ a 'float v[X]' function argument is the _same_ as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. + // - You can pass the address of a first float element out of a contiguous structure, e.g. &myvector.x + IMGUI_API bool ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL); + IMGUI_API bool ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0, 0)); // display a color square/button, hover for details, return true when pressed. + IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. + + // Widgets: Trees + // - TreeNode functions return true when the node is open, in which case you need to also call TreePop() when you are finished displaying the tree node contents. + IMGUI_API bool TreeNode(const char* label); + IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2); // helper variation to easily decorelate the id from the displayed string. Read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). + IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2); // " + IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0); + IMGUI_API bool TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API void TreePush(const char* str_id); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call TreePush/TreePop yourself if desired. + IMGUI_API void TreePush(const void* ptr_id = NULL); // " + IMGUI_API void TreePop(); // ~ Unindent()+PopId() + IMGUI_API float GetTreeNodeToLabelSpacing(); // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode + IMGUI_API bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). + IMGUI_API bool CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags = 0); // when 'p_visible != NULL': if '*p_visible==true' display an additional small close button on upper right of the header which will set the bool to false when clicked, if '*p_visible==false' don't display the header. + IMGUI_API void SetNextItemOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. + + // Widgets: Selectables + // - A selectable highlights when hovered, and can display another color when selected. + // - Neighbors selectable extend their highlight bounds in order to leave no gap between them. This is so a series of selected Selectable appear contiguous. + IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height + IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper. + + // Widgets: List Boxes + // - This is essentially a thin wrapper to using BeginChild/EndChild with some stylistic changes. + // - The BeginListBox()/EndListBox() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() or any items. + // - The simplified/old ListBox() api are helpers over BeginListBox()/EndListBox() which are kept available for convenience purpose. This is analoguous to how Combos are created. + // - Choose frame width: size.x > 0.0f: custom / size.x < 0.0f or -FLT_MIN: right-align / size.x = 0.0f (default): use current ItemWidth + // - Choose frame height: size.y > 0.0f: custom / size.y < 0.0f or -FLT_MIN: bottom-align / size.y = 0.0f (default): arbitrary default height which can fit ~7 items + IMGUI_API bool BeginListBox(const char* label, const ImVec2& size = ImVec2(0, 0)); // open a framed scrolling region + IMGUI_API void EndListBox(); // only call EndListBox() if BeginListBox() returned true! + IMGUI_API bool ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1); + IMGUI_API bool ListBox(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1); + + // Widgets: Data Plotting + // - Consider using ImPlot (https://github.com/epezent/implot) + IMGUI_API void PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); + IMGUI_API void PlotLines(const char* label, float(*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0)); + IMGUI_API void PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); + IMGUI_API void PlotHistogram(const char* label, float(*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0)); + + // Widgets: Value() Helpers. + // - Those are merely shortcut to calling Text() with a format string. Output single value in "name: value" format (tip: freely declare more in your code to handle your types. you can add functions to the ImGui namespace) + IMGUI_API void Value(const char* prefix, bool b); + IMGUI_API void Value(const char* prefix, int v); + IMGUI_API void Value(const char* prefix, unsigned int v); + IMGUI_API void Value(const char* prefix, float v, const char* float_format = NULL); + + // Widgets: Menus + // - Use BeginMenuBar() on a window ImGuiWindowFlags_MenuBar to append to its menu bar. + // - Use BeginMainMenuBar() to create a menu bar at the top of the screen and append to it. + // - Use BeginMenu() to create a menu. You can call BeginMenu() multiple time with the same identifier to append more items to it. + // - Not that MenuItem() keyboardshortcuts are displayed as a convenience but _not processed_ by Dear ImGui at the moment. + IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). + IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true! + IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. + IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true! + IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! + IMGUI_API void EndMenu(); // only call EndMenu() if BeginMenu() returns true! + IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. + IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL + + // Tooltips + // - Tooltip are windows following the mouse. They do not take focus away. + IMGUI_API void BeginTooltip(); // begin/append a tooltip window. to create full-featured tooltip (with any kind of items). + IMGUI_API void EndTooltip(); + IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set a text-only tooltip, typically use with ImGui::IsItemHovered(). override any previous call to SetTooltip(). + IMGUI_API void SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); + + // Popups, Modals + // - They block normal mouse hovering detection (and therefore most mouse interactions) behind them. + // - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE. + // - Their visibility state (~bool) is held internally instead of being held by the programmer as we are used to with regular Begin*() calls. + // - The 3 properties above are related: we need to retain popup visibility state in the library because popups may be closed as any time. + // - You can bypass the hovering restriction by using ImGuiHoveredFlags_AllowWhenBlockedByPopup when calling IsItemHovered() or IsWindowHovered(). + // - IMPORTANT: Popup identifiers are relative to the current ID stack, so OpenPopup and BeginPopup generally needs to be at the same level of the stack. + // This is sometimes leading to confusing mistakes. May rework this in the future. + // Popups: begin/end functions + // - BeginPopup(): query popup state, if open start appending into the window. Call EndPopup() afterwards. ImGuiWindowFlags are forwarded to the window. + // - BeginPopupModal(): block every interactions behind the window, cannot be closed by user, add a dimming background, has a title bar. + IMGUI_API bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0); // return true if the popup is open, and you can start outputting to it. + IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // return true if the modal is open, and you can start outputting to it. + IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! + // Popups: open/close functions + // - OpenPopup(): set popup state to open. ImGuiPopupFlags are available for opening options. + // - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE. + // - CloseCurrentPopup(): use inside the BeginPopup()/EndPopup() scope to close manually. + // - CloseCurrentPopup() is called by default by Selectable()/MenuItem() when activated (FIXME: need some options). + // - Use ImGuiPopupFlags_NoOpenOverExistingPopup to avoid opening a popup if there's already one at the same level. This is equivalent to e.g. testing for !IsAnyPopupOpen() prior to OpenPopup(). + // - Use IsWindowAppearing() after BeginPopup() to tell if a window just opened. + IMGUI_API void OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0); // call to mark popup as open (don't call every frame!). + IMGUI_API void OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags = 0); // id overload to facilitate calling from nested stacks + IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) + IMGUI_API void CloseCurrentPopup(); // manually close the popup we have begin-ed into. + // Popups: open+begin combined functions helpers + // - Helpers to do OpenPopup+BeginPopup where the Open action is triggered by e.g. hovering an item and right-clicking. + // - They are convenient to easily create context menus, hence the name. + // - IMPORTANT: Notice that BeginPopupContextXXX takes ImGuiPopupFlags just like OpenPopup() and unlike BeginPopup(). For full consistency, we may add ImGuiWindowFlags to the BeginPopupContextXXX functions in the future. + // - IMPORTANT: we exceptionally default their flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter, so if you add other flags remember to re-add the ImGuiPopupFlags_MouseButtonRight. + IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! + IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);// open+begin popup when clicked on current window. + IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked in void (where there are no windows). + // Popups: query functions + // - IsPopupOpen(): return true if the popup is open at the current BeginPopup() level of the popup stack. + // - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId: return true if any popup is open at the current BeginPopup() level of the popup stack. + // - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId + ImGuiPopupFlags_AnyPopupLevel: return true if any popup is open. + IMGUI_API bool IsPopupOpen(const char* str_id, ImGuiPopupFlags flags = 0); // return true if the popup is open. + + // Tables + // [BETA API] API may evolve slightly! If you use this, please update to the next version when it comes out! + // - Full-featured replacement for old Columns API. + // - See Demo->Tables for demo code. + // - See top of imgui_tables.cpp for general commentary. + // - See ImGuiTableFlags_ and ImGuiTableColumnFlags_ enums for a description of available flags. + // The typical call flow is: + // - 1. Call BeginTable(). + // - 2. Optionally call TableSetupColumn() to submit column name/flags/defaults. + // - 3. Optionally call TableSetupScrollFreeze() to request scroll freezing of columns/rows. + // - 4. Optionally call TableHeadersRow() to submit a header row. Names are pulled from TableSetupColumn() data. + // - 5. Populate contents: + // - In most situations you can use TableNextRow() + TableSetColumnIndex(N) to start appending into a column. + // - If you are using tables as a sort of grid, where every columns is holding the same type of contents, + // you may prefer using TableNextColumn() instead of TableNextRow() + TableSetColumnIndex(). + // TableNextColumn() will automatically wrap-around into the next row if needed. + // - IMPORTANT: Comparatively to the old Columns() API, we need to call TableNextColumn() for the first column! + // - Summary of possible call flow: + // -------------------------------------------------------------------------------------------------------- + // TableNextRow() -> TableSetColumnIndex(0) -> Text("Hello 0") -> TableSetColumnIndex(1) -> Text("Hello 1") // OK + // TableNextRow() -> TableNextColumn() -> Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK + // TableNextColumn() -> Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK: TableNextColumn() automatically gets to next row! + // TableNextRow() -> Text("Hello 0") // Not OK! Missing TableSetColumnIndex() or TableNextColumn()! Text will not appear! + // -------------------------------------------------------------------------------------------------------- + // - 5. Call EndTable() + IMGUI_API bool BeginTable(const char* str_id, int column, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f); + IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! + IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. + IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return true when column is visible. + IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true when column is visible. + // Tables: Headers & Columns declaration + // - Use TableSetupColumn() to specify label, resizing policy, default width/weight, id, various other flags etc. + // - Use TableHeadersRow() to create a header row and automatically submit a TableHeader() for each column. + // Headers are required to perform: reordering, sorting, and opening the context menu. + // The context menu can also be made available in columns body using ImGuiTableFlags_ContextMenuInBody. + // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in + // some advanced use cases (e.g. adding custom widgets in header row). + // - Use TableSetupScrollFreeze() to lock columns/rows so they stay visible when scrolled. + IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = 0.0f, ImGuiID user_id = 0); + IMGUI_API void TableSetupScrollFreeze(int cols, int rows); // lock columns/rows so they stay visible when scrolled. + IMGUI_API void TableHeadersRow(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu + IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) + // Tables: Sorting + // - Call TableGetSortSpecs() to retrieve latest sort specs for the table. NULL when not sorting. + // - When 'SpecsDirty == true' you should sort your data. It will be true when sorting specs have changed + // since last call, or the first time. Make sure to set 'SpecsDirty = false' after sorting, else you may + // wastefully sort your data every frame! + // - Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). + IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). + // Tables: Miscellaneous functions + // - Functions args 'int column_n' treat the default value of -1 as the same as passing the current column index. + IMGUI_API int TableGetColumnCount(); // return number of columns (value passed to BeginTable) + IMGUI_API int TableGetColumnIndex(); // return current column index. + IMGUI_API int TableGetRowIndex(); // return current row index. + IMGUI_API const char* TableGetColumnName(int column_n = -1); // return "" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. + IMGUI_API ImGuiTableColumnFlags TableGetColumnFlags(int column_n = -1); // return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags. Pass -1 to use current column. + IMGUI_API void TableSetColumnEnabled(int column_n, bool v);// change enabled/disabled state of a column, set to false to hide the column. Note that end-user can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) + IMGUI_API void TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. + + // Legacy Columns API (2020: prefer using Tables!) + // - You can also use SameLine(pos_x) to mimic simplified columns. + IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); + IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished + IMGUI_API int GetColumnIndex(); // get current column index + IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column + IMGUI_API void SetColumnWidth(int column_index, float width); // set column width (in pixels). pass -1 to use current column + IMGUI_API float GetColumnOffset(int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f + IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column + IMGUI_API int GetColumnsCount(); + + // Tab Bars, Tabs + IMGUI_API bool BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0); // create and append into a TabBar + IMGUI_API void EndTabBar(); // only call EndTabBar() if BeginTabBar() returns true! + IMGUI_API bool BeginTabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags flags = 0); // create a Tab. Returns true if the Tab is selected. + IMGUI_API void EndTabItem(); // only call EndTabItem() if BeginTabItem() returns true! + IMGUI_API bool TabItemButton(const char* label, ImGuiTabItemFlags flags = 0); // create a Tab behaving like a button. return true when clicked. cannot be selected in the tab bar. + IMGUI_API void SetTabItemClosed(const char* tab_or_docked_window_label); // notify TabBar or Docking system of a closed tab/window ahead (useful to reduce visual flicker on reorderable tab bars). For tab-bar: call after BeginTabBar() and before Tab submissions. Otherwise call with a window name. + + // Logging/Capture + // - All text output from the interface can be captured into tty/file/clipboard. By default, tree nodes are automatically opened during logging. + IMGUI_API void LogToTTY(int auto_open_depth = -1); // start logging to tty (stdout) + IMGUI_API void LogToFile(int auto_open_depth = -1, const char* filename = NULL); // start logging to file + IMGUI_API void LogToClipboard(int auto_open_depth = -1); // start logging to OS clipboard + IMGUI_API void LogFinish(); // stop logging (close file, etc.) + IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard + IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) + IMGUI_API void LogTextV(const char* fmt, va_list args) IM_FMTLIST(1); + + // Drag and Drop + // - On source items, call BeginDragDropSource(), if it returns true also call SetDragDropPayload() + EndDragDropSource(). + // - On target candidates, call BeginDragDropTarget(), if it returns true also call AcceptDragDropPayload() + EndDragDropTarget(). + // - If you stop calling BeginDragDropSource() the payload is preserved however it won't have a preview tooltip (we currently display a fallback "..." tooltip, see #1725) + // - An item can be both drag source and drop target. + IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0); // call after submitting an item which may be dragged. when this return true, you can call SetDragDropPayload() + EndDragDropSource() + IMGUI_API bool SetDragDropPayload(const char* type, const void* data, size_t sz, ImGuiCond cond = 0); // type is a user defined string of maximum 32 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui. + IMGUI_API void EndDragDropSource(); // only call EndDragDropSource() if BeginDragDropSource() returns true! + IMGUI_API bool BeginDragDropTarget(); // call after submitting an item that may receive a payload. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget() + IMGUI_API const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0); // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released. + IMGUI_API void EndDragDropTarget(); // only call EndDragDropTarget() if BeginDragDropTarget() returns true! + IMGUI_API const ImGuiPayload* GetDragDropPayload(); // peek directly into the current payload from anywhere. may return NULL. use ImGuiPayload::IsDataType() to test for the payload type. + + // Clipping + // - Mouse hovering is affected by ImGui::PushClipRect() calls, unlike direct calls to ImDrawList::PushClipRect() which are render only. + IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect); + IMGUI_API void PopClipRect(); + + // Focus, Activation + // - Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHereY()" when applicable to signify "this is the default item" + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. + IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + + // Item/Widgets Utilities and Query Functions + // - Most of the functions are referring to the previous Item that has been submitted. + // - See Demo Window under "Widgets->Querying Status" for an interactive visualization of most of those functions. + IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. + IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited. This will continuously return true while holding mouse button on an item. Items that don't interact will always return false) + IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? + IMGUI_API bool IsItemClicked(ImGuiMouseButton mouse_button = 0); // is the last item hovered and mouse clicked on? (**) == IsMouseClicked(mouse_button) && IsItemHovered()Important. (**) this it NOT equivalent to the behavior of e.g. Button(). Read comments in function definition. + IMGUI_API bool IsItemVisible(); // is the last item visible? (items may be out of sight because of clipping/scrolling) + IMGUI_API bool IsItemEdited(); // did the last item modify its underlying value this frame? or was pressed? This is generally the same as the "bool" return value of many widgets. + IMGUI_API bool IsItemActivated(); // was the last item just made active (item was previously inactive). + IMGUI_API bool IsItemDeactivated(); // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that requires continuous editing. + IMGUI_API bool IsItemDeactivatedAfterEdit(); // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that requires continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item). + IMGUI_API bool IsItemToggledOpen(); // was the last item open state toggled? set by TreeNode(). + IMGUI_API bool IsAnyItemHovered(); // is any item hovered? + IMGUI_API bool IsAnyItemActive(); // is any item active? + IMGUI_API bool IsAnyItemFocused(); // is any item focused? + IMGUI_API ImVec2 GetItemRectMin(); // get upper-left bounding rectangle of the last item (screen space) + IMGUI_API ImVec2 GetItemRectMax(); // get lower-right bounding rectangle of the last item (screen space) + IMGUI_API ImVec2 GetItemRectSize(); // get size of last item + IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. + + // Viewports + // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. + // - In 'docking' branch with multi-viewport enabled, we extend this concept to have multiple active viewports. + // - In the future we will extend this concept further to also represent Platform Monitor and support a "no main platform window" operation mode. + IMGUI_API ImGuiViewport* GetMainViewport(); // return primary/default viewport. This can never be NULL. + + // Miscellaneous Utilities + IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. + IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. + IMGUI_API double GetTime(); // get global imgui time. incremented by io.DeltaTime every frame. + IMGUI_API int GetFrameCount(); // get global imgui frame count. incremented by 1 every frame. + IMGUI_API ImDrawList* GetBackgroundDrawList(); // this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. + IMGUI_API ImDrawList* GetForegroundDrawList(); // this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. + IMGUI_API ImDrawListSharedData* GetDrawListSharedData(); // you may use this when creating your own ImDrawList instances. + IMGUI_API const char* GetStyleColorName(ImGuiCol idx); // get a string corresponding to the enum value (for display, saving, etc.). + IMGUI_API void SetStateStorage(ImGuiStorage* storage); // replace current window storage with our own (if you want to manipulate it yourself, typically clear subsection of it) + IMGUI_API ImGuiStorage* GetStateStorage(); + IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // calculate coarse clipping for large list of evenly sized items. Prefer using the ImGuiListClipper higher-level helper if you can. + IMGUI_API bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0); // helper to create a child window / scrolling region that looks like a normal widget frame + IMGUI_API void EndChildFrame(); // always call EndChildFrame() regardless of BeginChildFrame() return values (which indicates a collapsed/clipped window) + + // Text Utilities + IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + + // Color Utilities + IMGUI_API ImVec4 ColorConvertU32ToFloat4(ImU32 in); + IMGUI_API ImU32 ColorConvertFloat4ToU32(const ImVec4& in); + IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); + IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); + + // Inputs Utilities: Keyboard + // - For 'int user_key_index' you can use your own indices/enums according to how your backend/engine stored them in io.KeysDown[]. + // - We don't know the meaning of those value. You can use GetKeyIndex() to map a ImGuiKey_ value into the user index. + IMGUI_API int GetKeyIndex(ImGuiKey imgui_key); // map ImGuiKey_* values into user's key index. == io.KeyMap[key] + IMGUI_API bool IsKeyDown(int user_key_index); // is key being held. == io.KeysDown[user_key_index]. + IMGUI_API bool IsKeyPressed(int user_key_index, bool repeat = true); // was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate + IMGUI_API bool IsKeyReleased(int user_key_index); // was key released (went from Down to !Down)? + IMGUI_API int GetKeyPressedAmount(int key_index, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate + IMGUI_API void CaptureKeyboardFromApp(bool want_capture_keyboard_value = true); // attention: misleading name! manually override io.WantCaptureKeyboard flag next frame (said flag is entirely left for your application to handle). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting "io.WantCaptureKeyboard = want_capture_keyboard_value"; after the next NewFrame() call. + + // Inputs Utilities: Mouse + // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. + // - You can also use regular integer: it is forever guaranteed that 0=Left, 1=Right, 2=Middle. + // - Dragging operations are only reported after mouse has moved a certain distance away from the initial clicking position (see 'lock_threshold' and 'io.MouseDraggingThreshold') + IMGUI_API bool IsMouseDown(ImGuiMouseButton button); // is mouse button held? + IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, bool repeat = false); // did mouse button clicked? (went from !Down to Down) + IMGUI_API bool IsMouseReleased(ImGuiMouseButton button); // did mouse button released? (went from Down to !Down) + IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button); // did mouse button double-clicked? (note that a double-click will also report IsMouseClicked() == true) + IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true);// is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but disregarding of other consideration of focus/window ordering/popup-block. + IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // by convention we use (-FLT_MAX,-FLT_MAX) to denote that there is no mouse available + IMGUI_API bool IsAnyMouseDown(); // is any mouse button held? + IMGUI_API ImVec2 GetMousePos(); // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls + IMGUI_API ImVec2 GetMousePosOnOpeningCurrentPopup(); // retrieve mouse position at the time of opening popup we have BeginPopup() into (helper to avoid user backing that value themselves) + IMGUI_API bool IsMouseDragging(ImGuiMouseButton button, float lock_threshold = -1.0f); // is mouse dragging? (if lock_threshold < -1.0f, uses io.MouseDraggingThreshold) + IMGUI_API ImVec2 GetMouseDragDelta(ImGuiMouseButton button = 0, float lock_threshold = -1.0f); // return the delta from the initial clicking position while the mouse button is pressed or was just released. This is locked and return 0.0f until the mouse moves past a distance threshold at least once (if lock_threshold < -1.0f, uses io.MouseDraggingThreshold) + IMGUI_API void ResetMouseDragDelta(ImGuiMouseButton button = 0); // + IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired cursor type, reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you + IMGUI_API void SetMouseCursor(ImGuiMouseCursor cursor_type); // set desired cursor type + IMGUI_API void CaptureMouseFromApp(bool want_capture_mouse_value = true); // attention: misleading name! manually override io.WantCaptureMouse flag next frame (said flag is entirely left for your application to handle). This is equivalent to setting "io.WantCaptureMouse = want_capture_mouse_value;" after the next NewFrame() call. + + // Clipboard Utilities + // - Also see the LogToClipboard() function to capture GUI into clipboard, or easily output text data to the clipboard. + IMGUI_API const char* GetClipboardText(); + IMGUI_API void SetClipboardText(const char* text); + + // Settings/.Ini Utilities + // - The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). + // - Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually. + IMGUI_API void LoadIniSettingsFromDisk(const char* ini_filename); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename). + IMGUI_API void LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source. + IMGUI_API void SaveIniSettingsToDisk(const char* ini_filename); // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext). + IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings. + + // Debug Utilities + // - This is used by the IMGUI_CHECKVERSION() macro. + IMGUI_API bool DebugCheckVersionAndDataLayout(const char* version_str, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_drawvert, size_t sz_drawidx); // This is called by IMGUI_CHECKVERSION() macro. + + // Memory Allocators + // - Those functions are not reliant on the current context. + // - DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() + // for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. + IMGUI_API void SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data = NULL); + IMGUI_API void GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data); + IMGUI_API void* MemAlloc(size_t size); + IMGUI_API void MemFree(void* ptr); + +} // namespace ImGui + +//----------------------------------------------------------------------------- +// [SECTION] Flags & Enumerations +//----------------------------------------------------------------------------- + +// Flags for ImGui::Begin() +enum ImGuiWindowFlags_ +{ + ImGuiWindowFlags_None = 0, + ImGuiWindowFlags_NoTitleBar = 1 << 0, // Disable title-bar + ImGuiWindowFlags_NoResize = 1 << 1, // Disable user resizing with the lower-right grip + ImGuiWindowFlags_NoMove = 1 << 2, // Disable user moving the window + ImGuiWindowFlags_NoScrollbar = 1 << 3, // Disable scrollbars (window can still scroll with mouse or programmatically) + ImGuiWindowFlags_NoScrollWithMouse = 1 << 4, // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set. + ImGuiWindowFlags_NoCollapse = 1 << 5, // Disable user collapsing window by double-clicking on it + ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame + ImGuiWindowFlags_NoBackground = 1 << 7, // Disable drawing background color (WindowBg, etc.) and outside border. Similar as using SetNextWindowBgAlpha(0.0f). + ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file + ImGuiWindowFlags_NoMouseInputs = 1 << 9, // Disable catching mouse, hovering test with pass through. + ImGuiWindowFlags_MenuBar = 1 << 10, // Has a menu-bar + ImGuiWindowFlags_HorizontalScrollbar = 1 << 11, // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. + ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12, // Disable taking focus when transitioning from hidden to visible state + ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking on it or programmatically giving it focus) + ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) + ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) + ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) + ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window + ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) + ImGuiWindowFlags_UnsavedDocument = 1 << 20, // Append '*' to title without affecting the ID, as a convenience to avoid using the ### operator. When used in a tab/docking context, tab is selected on closure and closure is deferred by one frame to allow code to cancel the closure (with a confirmation popup, etc.) without flicker. + ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse, + ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + + // [Internal] + ImGuiWindowFlags_NavFlattened = 1 << 23, // [BETA] Allow gamepad/keyboard navigation to cross over parent border to this child (only use on child that have no scrolling!) + ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() + ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() + ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() + ImGuiWindowFlags_ChildMenu = 1 << 28 // Don't use! For internal use by BeginMenu() + + // [Obsolete] + //ImGuiWindowFlags_ResizeFromAnySide = 1 << 17, // --> Set io.ConfigWindowsResizeFromEdges=true and make sure mouse cursors are supported by backend (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) +}; + +// Flags for ImGui::InputText() +enum ImGuiInputTextFlags_ +{ + ImGuiInputTextFlags_None = 0, + ImGuiInputTextFlags_CharsDecimal = 1 << 0, // Allow 0123456789.+-*/ + ImGuiInputTextFlags_CharsHexadecimal = 1 << 1, // Allow 0123456789ABCDEFabcdef + ImGuiInputTextFlags_CharsUppercase = 1 << 2, // Turn a..z into A..Z + ImGuiInputTextFlags_CharsNoBlank = 1 << 3, // Filter out spaces, tabs + ImGuiInputTextFlags_AutoSelectAll = 1 << 4, // Select entire text when first taking mouse focus + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 5, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider looking at the IsItemDeactivatedAfterEdit() function. + ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Callback on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Callback on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Callback on each iteration. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 9, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. + ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter). + ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12, // Disable following the cursor horizontally + ImGuiInputTextFlags_AlwaysOverwrite = 1 << 13, // Overwrite mode + ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode + ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' + ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + ImGuiInputTextFlags_CharsScientific = 1 << 17, // Allow 0123456789.+-*/eE (Scientific notation input) + ImGuiInputTextFlags_CallbackResize = 1 << 18, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) + ImGuiInputTextFlags_CallbackEdit = 1 << 19 // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) + + // Obsolete names (will be removed soon) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiInputTextFlags_AlwaysInsertMode = ImGuiInputTextFlags_AlwaysOverwrite // [renamed in 1.82] name was not matching behavior +#endif +}; + +// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() +enum ImGuiTreeNodeFlags_ +{ + ImGuiTreeNodeFlags_None = 0, + ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected + ImGuiTreeNodeFlags_Framed = 1 << 1, // Draw frame with background (e.g. for CollapsingHeader) + ImGuiTreeNodeFlags_AllowItemOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one + ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack + ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) + ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open + ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Need double-click to open node + ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open. + ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). + ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow + ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding(). + ImGuiTreeNodeFlags_SpanAvailWidth = 1 << 11, // Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line. In the future we may refactor the hit system to be front-to-back, allowing natural overlaps and then this can become the default. + ImGuiTreeNodeFlags_SpanFullWidth = 1 << 12, // Extend hit box to the left-most and right-most edges (bypass the indented area). + ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 13, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) + //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 14, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog +}; + +// Flags for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() functions. +// - To be backward compatible with older API which took an 'int mouse_button = 1' argument, we need to treat +// small flags values as a mouse button index, so we encode the mouse button in the first few bits of the flags. +// It is therefore guaranteed to be legal to pass a mouse button index in ImGuiPopupFlags. +// - For the same reason, we exceptionally default the ImGuiPopupFlags argument of BeginPopupContextXXX functions to 1 instead of 0. +// IMPORTANT: because the default parameter is 1 (==ImGuiPopupFlags_MouseButtonRight), if you rely on the default parameter +// and want to another another flag, you need to pass in the ImGuiPopupFlags_MouseButtonRight flag. +// - Multiple buttons currently cannot be combined/or-ed in those functions (we could allow it later). +enum ImGuiPopupFlags_ +{ + ImGuiPopupFlags_None = 0, + ImGuiPopupFlags_MouseButtonLeft = 0, // For BeginPopupContext*(): open on Left Mouse release. Guaranteed to always be == 0 (same as ImGuiMouseButton_Left) + ImGuiPopupFlags_MouseButtonRight = 1, // For BeginPopupContext*(): open on Right Mouse release. Guaranteed to always be == 1 (same as ImGuiMouseButton_Right) + ImGuiPopupFlags_MouseButtonMiddle = 2, // For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle) + ImGuiPopupFlags_MouseButtonMask_ = 0x1F, + ImGuiPopupFlags_MouseButtonDefault_ = 1, + ImGuiPopupFlags_NoOpenOverExistingPopup = 1 << 5, // For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack + ImGuiPopupFlags_NoOpenOverItems = 1 << 6, // For BeginPopupContextWindow(): don't return true when hovering items, only when hovering empty space + ImGuiPopupFlags_AnyPopupId = 1 << 7, // For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup. + ImGuiPopupFlags_AnyPopupLevel = 1 << 8, // For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level) + ImGuiPopupFlags_AnyPopup = ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel +}; + +// Flags for ImGui::Selectable() +enum ImGuiSelectableFlags_ +{ + ImGuiSelectableFlags_None = 0, + ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this don't close parent popup window + ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Selectable frame can span all columns (text will still fit in current column) + ImGuiSelectableFlags_AllowDoubleClick = 1 << 2, // Generate press events on double clicks too + ImGuiSelectableFlags_Disabled = 1 << 3, // Cannot be selected, display grayed out text + ImGuiSelectableFlags_AllowItemOverlap = 1 << 4 // (WIP) Hit testing to allow subsequent widgets to overlap this one +}; + +// Flags for ImGui::BeginCombo() +enum ImGuiComboFlags_ +{ + ImGuiComboFlags_None = 0, + ImGuiComboFlags_PopupAlignLeft = 1 << 0, // Align the popup toward the left by default + ImGuiComboFlags_HeightSmall = 1 << 1, // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() + ImGuiComboFlags_HeightRegular = 1 << 2, // Max ~8 items visible (default) + ImGuiComboFlags_HeightLarge = 1 << 3, // Max ~20 items visible + ImGuiComboFlags_HeightLargest = 1 << 4, // As many fitting items as possible + ImGuiComboFlags_NoArrowButton = 1 << 5, // Display on the preview box without the square arrow button + ImGuiComboFlags_NoPreview = 1 << 6, // Display only a square arrow button + ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest +}; + +// Flags for ImGui::BeginTabBar() +enum ImGuiTabBarFlags_ +{ + ImGuiTabBarFlags_None = 0, + ImGuiTabBarFlags_Reorderable = 1 << 0, // Allow manually dragging tabs to re-order them + New tabs are appended at the end of list + ImGuiTabBarFlags_AutoSelectNewTabs = 1 << 1, // Automatically select new tabs when they appear + ImGuiTabBarFlags_TabListPopupButton = 1 << 2, // Disable buttons to open the tab list popup + ImGuiTabBarFlags_NoCloseWithMiddleMouseButton = 1 << 3, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. + ImGuiTabBarFlags_NoTabListScrollingButtons = 1 << 4, // Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll) + ImGuiTabBarFlags_NoTooltip = 1 << 5, // Disable tooltips when hovering a tab + ImGuiTabBarFlags_FittingPolicyResizeDown = 1 << 6, // Resize tabs when they don't fit + ImGuiTabBarFlags_FittingPolicyScroll = 1 << 7, // Add scroll buttons when tabs don't fit + ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll, + ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyResizeDown +}; + +// Flags for ImGui::BeginTabItem() +enum ImGuiTabItemFlags_ +{ + ImGuiTabItemFlags_None = 0, + ImGuiTabItemFlags_UnsavedDocument = 1 << 0, // Append '*' to title without affecting the ID, as a convenience to avoid using the ### operator. Also: tab is selected on closure and closure is deferred by one frame to allow code to undo it without flicker. + ImGuiTabItemFlags_SetSelected = 1 << 1, // Trigger flag to programmatically make the tab selected when calling BeginTabItem() + ImGuiTabItemFlags_NoCloseWithMiddleMouseButton = 1 << 2, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. + ImGuiTabItemFlags_NoPushId = 1 << 3, // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem() + ImGuiTabItemFlags_NoTooltip = 1 << 4, // Disable tooltip for the given tab + ImGuiTabItemFlags_NoReorder = 1 << 5, // Disable reordering this tab or having another tab cross over this tab + ImGuiTabItemFlags_Leading = 1 << 6, // Enforce the tab position to the left of the tab bar (after the tab list popup button) + ImGuiTabItemFlags_Trailing = 1 << 7 // Enforce the tab position to the right of the tab bar (before the scrolling buttons) +}; + +// Flags for ImGui::BeginTable() +// [BETA API] API may evolve slightly! If you use this, please update to the next version when it comes out! +// - Important! Sizing policies have complex and subtle side effects, more so than you would expect. +// Read comments/demos carefully + experiment with live demos to get acquainted with them. +// - The DEFAULT sizing policies are: +// - Default to ImGuiTableFlags_SizingFixedFit if ScrollX is on, or if host window has ImGuiWindowFlags_AlwaysAutoResize. +// - Default to ImGuiTableFlags_SizingStretchSame if ScrollX is off. +// - When ScrollX is off: +// - Table defaults to ImGuiTableFlags_SizingStretchSame -> all Columns defaults to ImGuiTableColumnFlags_WidthStretch with same weight. +// - Columns sizing policy allowed: Stretch (default), Fixed/Auto. +// - Fixed Columns will generally obtain their requested width (unless the table cannot fit them all). +// - Stretch Columns will share the remaining width. +// - Mixed Fixed/Stretch columns is possible but has various side-effects on resizing behaviors. +// The typical use of mixing sizing policies is: any number of LEADING Fixed columns, followed by one or two TRAILING Stretch columns. +// (this is because the visible order of columns have subtle but necessary effects on how they react to manual resizing). +// - When ScrollX is on: +// - Table defaults to ImGuiTableFlags_SizingFixedFit -> all Columns defaults to ImGuiTableColumnFlags_WidthFixed +// - Columns sizing policy allowed: Fixed/Auto mostly. +// - Fixed Columns can be enlarged as needed. Table will show an horizontal scrollbar if needed. +// - When using auto-resizing (non-resizable) fixed columns, querying the content width to use item right-alignment e.g. SetNextItemWidth(-FLT_MIN) doesn't make sense, would create a feedback loop. +// - Using Stretch columns OFTEN DOES NOT MAKE SENSE if ScrollX is on, UNLESS you have specified a value for 'inner_width' in BeginTable(). +// If you specify a value for 'inner_width' then effectively the scrolling space is known and Stretch or mixed Fixed/Stretch columns become meaningful again. +// - Read on documentation at the top of imgui_tables.cpp for details. +enum ImGuiTableFlags_ +{ + // Features + ImGuiTableFlags_None = 0, + ImGuiTableFlags_Resizable = 1 << 0, // Enable resizing columns. + ImGuiTableFlags_Reorderable = 1 << 1, // Enable reordering columns in header row (need calling TableSetupColumn() + TableHeadersRow() to display headers) + ImGuiTableFlags_Hideable = 1 << 2, // Enable hiding/disabling columns in context menu. + ImGuiTableFlags_Sortable = 1 << 3, // Enable sorting. Call TableGetSortSpecs() to obtain sort specs. Also see ImGuiTableFlags_SortMulti and ImGuiTableFlags_SortTristate. + ImGuiTableFlags_NoSavedSettings = 1 << 4, // Disable persisting columns order, width and sort settings in the .ini file. + ImGuiTableFlags_ContextMenuInBody = 1 << 5, // Right-click on columns body/contents will display table context menu. By default it is available in TableHeadersRow(). + // Decorations + ImGuiTableFlags_RowBg = 1 << 6, // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent of calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually) + ImGuiTableFlags_BordersInnerH = 1 << 7, // Draw horizontal borders between rows. + ImGuiTableFlags_BordersOuterH = 1 << 8, // Draw horizontal borders at the top and bottom. + ImGuiTableFlags_BordersInnerV = 1 << 9, // Draw vertical borders between columns. + ImGuiTableFlags_BordersOuterV = 1 << 10, // Draw vertical borders on the left and right sides. + ImGuiTableFlags_BordersH = ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_BordersOuterH, // Draw horizontal borders. + ImGuiTableFlags_BordersV = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersOuterV, // Draw vertical borders. + ImGuiTableFlags_BordersInner = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersInnerH, // Draw inner borders. + ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH, // Draw outer borders. + ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. + ImGuiTableFlags_NoBordersInBody = 1 << 11, // [ALPHA] Disable vertical borders in columns Body (borders will always appears in Headers). -> May move to style + ImGuiTableFlags_NoBordersInBodyUntilResize = 1 << 12, // [ALPHA] Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers). -> May move to style + // Sizing Policy (read above for defaults) + ImGuiTableFlags_SizingFixedFit = 1 << 13, // Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching contents width. + ImGuiTableFlags_SizingFixedSame = 2 << 13, // Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching the maximum contents width of all columns. Implicitly enable ImGuiTableFlags_NoKeepColumnsVisible. + ImGuiTableFlags_SizingStretchProp = 3 << 13, // Columns default to _WidthStretch with default weights proportional to each columns contents widths. + ImGuiTableFlags_SizingStretchSame = 4 << 13, // Columns default to _WidthStretch with default weights all equal, unless overridden by TableSetupColumn(). + // Sizing Extra Options + ImGuiTableFlags_NoHostExtendX = 1 << 16, // Make outer width auto-fit to columns, overriding outer_size.x value. Only available when ScrollX/ScrollY are disabled and Stretch columns are not used. + ImGuiTableFlags_NoHostExtendY = 1 << 17, // Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible. + ImGuiTableFlags_NoKeepColumnsVisible = 1 << 18, // Disable keeping column always minimally visible when ScrollX is off and table gets too small. Not recommended if columns are resizable. + ImGuiTableFlags_PreciseWidths = 1 << 19, // Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth. + // Clipping + ImGuiTableFlags_NoClip = 1 << 20, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with TableSetupScrollFreeze(). + // Padding + ImGuiTableFlags_PadOuterX = 1 << 21, // Default if BordersOuterV is on. Enable outer-most padding. Generally desirable if you have headers. + ImGuiTableFlags_NoPadOuterX = 1 << 22, // Default if BordersOuterV is off. Disable outer-most padding. + ImGuiTableFlags_NoPadInnerX = 1 << 23, // Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off). + // Scrolling + ImGuiTableFlags_ScrollX = 1 << 24, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Changes default sizing policy. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. + ImGuiTableFlags_ScrollY = 1 << 25, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. + // Sorting + ImGuiTableFlags_SortMulti = 1 << 26, // Hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1). + ImGuiTableFlags_SortTristate = 1 << 27, // Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0). + + // [Internal] Combinations and masks + ImGuiTableFlags_SizingMask_ = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame + + // Obsolete names (will be removed soon) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //, ImGuiTableFlags_ColumnsWidthFixed = ImGuiTableFlags_SizingFixedFit, ImGuiTableFlags_ColumnsWidthStretch = ImGuiTableFlags_SizingStretchSame // WIP Tables 2020/12 + //, ImGuiTableFlags_SizingPolicyFixed = ImGuiTableFlags_SizingFixedFit, ImGuiTableFlags_SizingPolicyStretch = ImGuiTableFlags_SizingStretchSame // WIP Tables 2021/01 +#endif +}; + +// Flags for ImGui::TableSetupColumn() +enum ImGuiTableColumnFlags_ +{ + // Input configuration flags + ImGuiTableColumnFlags_None = 0, + ImGuiTableColumnFlags_DefaultHide = 1 << 0, // Default as a hidden/disabled column. + ImGuiTableColumnFlags_DefaultSort = 1 << 1, // Default as a sorting column. + ImGuiTableColumnFlags_WidthStretch = 1 << 2, // Column will stretch. Preferable with horizontal scrolling disabled (default if table sizing policy is _SizingStretchSame or _SizingStretchProp). + ImGuiTableColumnFlags_WidthFixed = 1 << 3, // Column will not stretch. Preferable with horizontal scrolling enabled (default if table sizing policy is _SizingFixedFit and table is resizable). + ImGuiTableColumnFlags_NoResize = 1 << 4, // Disable manual resizing. + ImGuiTableColumnFlags_NoReorder = 1 << 5, // Disable manual reordering this column, this will also prevent other columns from crossing over this column. + ImGuiTableColumnFlags_NoHide = 1 << 6, // Disable ability to hide/disable this column. + ImGuiTableColumnFlags_NoClip = 1 << 7, // Disable clipping for this column (all NoClip columns will render in a same draw command). + ImGuiTableColumnFlags_NoSort = 1 << 8, // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). + ImGuiTableColumnFlags_NoSortAscending = 1 << 9, // Disable ability to sort in the ascending direction. + ImGuiTableColumnFlags_NoSortDescending = 1 << 10, // Disable ability to sort in the descending direction. + ImGuiTableColumnFlags_NoHeaderWidth = 1 << 11, // Disable header text width contribution to automatic column width. + ImGuiTableColumnFlags_PreferSortAscending = 1 << 12, // Make the initial sort direction Ascending when first sorting on this column (default). + ImGuiTableColumnFlags_PreferSortDescending = 1 << 13, // Make the initial sort direction Descending when first sorting on this column. + ImGuiTableColumnFlags_IndentEnable = 1 << 14, // Use current Indent value when entering cell (default for column 0). + ImGuiTableColumnFlags_IndentDisable = 1 << 15, // Ignore current Indent value when entering cell (default for columns > 0). Indentation changes _within_ the cell will still be honored. + + // Output status flags, read-only via TableGetColumnFlags() + ImGuiTableColumnFlags_IsEnabled = 1 << 20, // Status: is enabled == not hidden by user/api (referred to as "Hide" in _DefaultHide and _NoHide) flags. + ImGuiTableColumnFlags_IsVisible = 1 << 21, // Status: is visible == is enabled AND not clipped by scrolling. + ImGuiTableColumnFlags_IsSorted = 1 << 22, // Status: is currently part of the sort specs + ImGuiTableColumnFlags_IsHovered = 1 << 23, // Status: is hovered by mouse + + // [Internal] Combinations and masks + ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthFixed, + ImGuiTableColumnFlags_IndentMask_ = ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_IndentDisable, + ImGuiTableColumnFlags_StatusMask_ = ImGuiTableColumnFlags_IsEnabled | ImGuiTableColumnFlags_IsVisible | ImGuiTableColumnFlags_IsSorted | ImGuiTableColumnFlags_IsHovered, + ImGuiTableColumnFlags_NoDirectResize_ = 1 << 30 // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) + + // Obsolete names (will be removed soon) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //ImGuiTableColumnFlags_WidthAuto = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize, // Column will not stretch and keep resizing based on submitted contents. +#endif +}; + +// Flags for ImGui::TableNextRow() +enum ImGuiTableRowFlags_ +{ + ImGuiTableRowFlags_None = 0, + ImGuiTableRowFlags_Headers = 1 << 0 // Identify header row (set default background color + width of its contents accounted different for auto column width) +}; + +// Enum for ImGui::TableSetBgColor() +// Background colors are rendering in 3 layers: +// - Layer 0: draw with RowBg0 color if set, otherwise draw with ColumnBg0 if set. +// - Layer 1: draw with RowBg1 color if set, otherwise draw with ColumnBg1 if set. +// - Layer 2: draw with CellBg color if set. +// The purpose of the two row/columns layers is to let you decide if a background color changes should override or blend with the existing color. +// When using ImGuiTableFlags_RowBg on the table, each row has the RowBg0 color automatically set for odd/even rows. +// If you set the color of RowBg0 target, your color will override the existing RowBg0 color. +// If you set the color of RowBg1 or ColumnBg1 target, your color will blend over the RowBg0 color. +enum ImGuiTableBgTarget_ +{ + ImGuiTableBgTarget_None = 0, + ImGuiTableBgTarget_RowBg0 = 1, // Set row background color 0 (generally used for background, automatically set when ImGuiTableFlags_RowBg is used) + ImGuiTableBgTarget_RowBg1 = 2, // Set row background color 1 (generally used for selection marking) + ImGuiTableBgTarget_CellBg = 3 // Set cell background color (top-most color) +}; + +// Flags for ImGui::IsWindowFocused() +enum ImGuiFocusedFlags_ +{ + ImGuiFocusedFlags_None = 0, + ImGuiFocusedFlags_ChildWindows = 1 << 0, // IsWindowFocused(): Return true if any children of the window is focused + ImGuiFocusedFlags_RootWindow = 1 << 1, // IsWindowFocused(): Test from root window (top most parent of the current hierarchy) + ImGuiFocusedFlags_AnyWindow = 1 << 2, // IsWindowFocused(): Return true if any window is focused. Important: If you are trying to tell how to dispatch your low-level inputs, do NOT use this. Use 'io.WantCaptureMouse' instead! Please read the FAQ! + ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows +}; + +// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() +// Note: if you are trying to check whether your mouse should be dispatched to Dear ImGui or to your app, you should use 'io.WantCaptureMouse' instead! Please read the FAQ! +// Note: windows with the ImGuiWindowFlags_NoInputs flag are ignored by IsWindowHovered() calls. +enum ImGuiHoveredFlags_ +{ + ImGuiHoveredFlags_None = 0, // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. + ImGuiHoveredFlags_ChildWindows = 1 << 0, // IsWindowHovered() only: Return true if any children of the window is hovered + ImGuiHoveredFlags_RootWindow = 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) + ImGuiHoveredFlags_AnyWindow = 1 << 2, // IsWindowHovered() only: Return true if any window is hovered + ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 3, // Return true even if a popup window is normally blocking access to this item/window + //ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 4, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet. + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 5, // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. + ImGuiHoveredFlags_AllowWhenOverlapped = 1 << 6, // Return true even if the position is obstructed or overlapped by another window + ImGuiHoveredFlags_AllowWhenDisabled = 1 << 7, // Return true even if the item is disabled + ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, + ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows +}; + +// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() +enum ImGuiDragDropFlags_ +{ + ImGuiDragDropFlags_None = 0, + // BeginDragDropSource() flags + ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0, // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disable this behavior. + ImGuiDragDropFlags_SourceNoDisableHover = 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return false, to avoid subsequent user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on the source item. + ImGuiDragDropFlags_SourceNoHoldToOpenOthers = 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item. + ImGuiDragDropFlags_SourceAllowNullID = 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit. + ImGuiDragDropFlags_SourceExtern = 1 << 4, // External source (from outside of dear imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously. + ImGuiDragDropFlags_SourceAutoExpirePayload = 1 << 5, // Automatically expire the payload if the source cease to be submitted (otherwise payloads are persisting while being dragged) + // AcceptDragDropPayload() flags + ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered. + ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11, // Do not draw the default highlight rectangle when hovering over target. + ImGuiDragDropFlags_AcceptNoPreviewTooltip = 1 << 12, // Request hiding the BeginDragDropSource tooltip from the BeginDragDropTarget site. + ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect // For peeking ahead and inspecting the payload before delivery. +}; + +// Standard Drag and Drop payload types. You can define you own payload types using short strings. Types starting with '_' are defined by Dear ImGui. +#define IMGUI_PAYLOAD_TYPE_COLOR_3F "_COL3F" // float[3]: Standard type for colors, without alpha. User code may use this type. +#define IMGUI_PAYLOAD_TYPE_COLOR_4F "_COL4F" // float[4]: Standard type for colors. User code may use this type. + +// A primary data type +enum ImGuiDataType_ +{ + ImGuiDataType_S8, // signed char / char (with sensible compilers) + ImGuiDataType_U8, // unsigned char + ImGuiDataType_S16, // short + ImGuiDataType_U16, // unsigned short + ImGuiDataType_S32, // int + ImGuiDataType_U32, // unsigned int + ImGuiDataType_S64, // long long / __int64 + ImGuiDataType_U64, // unsigned long long / unsigned __int64 + ImGuiDataType_Float, // float + ImGuiDataType_Double, // double + ImGuiDataType_COUNT +}; + +// A cardinal direction +enum ImGuiDir_ +{ + ImGuiDir_None = -1, + ImGuiDir_Left = 0, + ImGuiDir_Right = 1, + ImGuiDir_Up = 2, + ImGuiDir_Down = 3, + ImGuiDir_COUNT +}; + +// A sorting direction +enum ImGuiSortDirection_ +{ + ImGuiSortDirection_None = 0, + ImGuiSortDirection_Ascending = 1, // Ascending = 0->9, A->Z etc. + ImGuiSortDirection_Descending = 2 // Descending = 9->0, Z->A etc. +}; + +// User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array +enum ImGuiKey_ +{ + ImGuiKey_Tab, + ImGuiKey_LeftArrow, + ImGuiKey_RightArrow, + ImGuiKey_UpArrow, + ImGuiKey_DownArrow, + ImGuiKey_PageUp, + ImGuiKey_PageDown, + ImGuiKey_Home, + ImGuiKey_End, + ImGuiKey_Insert, + ImGuiKey_Delete, + ImGuiKey_Backspace, + ImGuiKey_Space, + ImGuiKey_Enter, + ImGuiKey_Escape, + ImGuiKey_KeyPadEnter, + ImGuiKey_A, // for text edit CTRL+A: select all + ImGuiKey_C, // for text edit CTRL+C: copy + ImGuiKey_V, // for text edit CTRL+V: paste + ImGuiKey_X, // for text edit CTRL+X: cut + ImGuiKey_Y, // for text edit CTRL+Y: redo + ImGuiKey_Z, // for text edit CTRL+Z: undo + ImGuiKey_COUNT +}; + +// To test io.KeyMods (which is a combination of individual fields io.KeyCtrl, io.KeyShift, io.KeyAlt set by user/backend) +enum ImGuiKeyModFlags_ +{ + ImGuiKeyModFlags_None = 0, + ImGuiKeyModFlags_Ctrl = 1 << 0, + ImGuiKeyModFlags_Shift = 1 << 1, + ImGuiKeyModFlags_Alt = 1 << 2, + ImGuiKeyModFlags_Super = 1 << 3 +}; + +// Gamepad/Keyboard navigation +// Keyboard: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays. +// Gamepad: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable. Backend: set ImGuiBackendFlags_HasGamepad and fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). +// Read instructions in imgui.cpp for more details. Download PNG/PSD at http://dearimgui.org/controls_sheets. +enum ImGuiNavInput_ +{ + // Gamepad Mapping + ImGuiNavInput_Activate, // activate / open / toggle / tweak value // e.g. Cross (PS4), A (Xbox), A (Switch), Space (Keyboard) + ImGuiNavInput_Cancel, // cancel / close / exit // e.g. Circle (PS4), B (Xbox), B (Switch), Escape (Keyboard) + ImGuiNavInput_Input, // text input / on-screen keyboard // e.g. Triang.(PS4), Y (Xbox), X (Switch), Return (Keyboard) + ImGuiNavInput_Menu, // tap: toggle menu / hold: focus, move, resize // e.g. Square (PS4), X (Xbox), Y (Switch), Alt (Keyboard) + ImGuiNavInput_DpadLeft, // move / tweak / resize window (w/ PadMenu) // e.g. D-pad Left/Right/Up/Down (Gamepads), Arrow keys (Keyboard) + ImGuiNavInput_DpadRight, // + ImGuiNavInput_DpadUp, // + ImGuiNavInput_DpadDown, // + ImGuiNavInput_LStickLeft, // scroll / move window (w/ PadMenu) // e.g. Left Analog Stick Left/Right/Up/Down + ImGuiNavInput_LStickRight, // + ImGuiNavInput_LStickUp, // + ImGuiNavInput_LStickDown, // + ImGuiNavInput_FocusPrev, // next window (w/ PadMenu) // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_FocusNext, // prev window (w/ PadMenu) // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + ImGuiNavInput_TweakSlow, // slower tweaks // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch) + ImGuiNavInput_TweakFast, // faster tweaks // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch) + + // [Internal] Don't use directly! This is used internally to differentiate keyboard from gamepad inputs for behaviors that require to differentiate them. + // Keyboard behavior that have no corresponding gamepad mapping (e.g. CTRL+TAB) will be directly reading from io.KeysDown[] instead of io.NavInputs[]. + ImGuiNavInput_KeyMenu_, // toggle menu // = io.KeyAlt + ImGuiNavInput_KeyLeft_, // move left // = Arrow keys + ImGuiNavInput_KeyRight_, // move right + ImGuiNavInput_KeyUp_, // move up + ImGuiNavInput_KeyDown_, // move down + ImGuiNavInput_COUNT, + ImGuiNavInput_InternalStart_ = ImGuiNavInput_KeyMenu_ +}; + +// Configuration flags stored in io.ConfigFlags. Set by user/application. +enum ImGuiConfigFlags_ +{ + ImGuiConfigFlags_None = 0, + ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. NewFrame() will automatically fill io.NavInputs[] based on io.KeysDown[]. + ImGuiConfigFlags_NavEnableGamepad = 1 << 1, // Master gamepad navigation enable flag. This is mostly to instruct your imgui backend to fill io.NavInputs[]. Backend also needs to set ImGuiBackendFlags_HasGamepad. + ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2, // Instruct navigation to move the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantSetMousePos=true. If enabled you MUST honor io.WantSetMousePos requests in your backend, otherwise ImGui will react as if the mouse is jumping around back and forth. + ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // Instruct navigation to not set the io.WantCaptureKeyboard flag when io.NavActive is set. + ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct imgui to clear mouse position/buttons in NewFrame(). This allows ignoring the mouse information set by the backend. + ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct backend to not alter mouse cursor shape and visibility. Use if the backend cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead. + + // User storage (to allow your backend/engine to communicate to code that may be shared between multiple projects. Those flags are not used by core Dear ImGui) + ImGuiConfigFlags_IsSRGB = 1 << 20, // Application is SRGB-aware. + ImGuiConfigFlags_IsTouchScreen = 1 << 21 // Application is using a touch screen instead of a mouse. +}; + +// Backend capabilities flags stored in io.BackendFlags. Set by imgui_impl_xxx or custom backend. +enum ImGuiBackendFlags_ +{ + ImGuiBackendFlags_None = 0, + ImGuiBackendFlags_HasGamepad = 1 << 0, // Backend Platform supports gamepad and currently has one connected. + ImGuiBackendFlags_HasMouseCursors = 1 << 1, // Backend Platform supports honoring GetMouseCursor() value to change the OS cursor shape. + ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if ImGuiConfigFlags_NavEnableSetMousePos is set). + ImGuiBackendFlags_RendererHasVtxOffset = 1 << 3 // Backend Renderer supports ImDrawCmd::VtxOffset. This enables output of large meshes (64K+ vertices) while still using 16-bit indices. +}; + +// Enumeration for PushStyleColor() / PopStyleColor() +enum ImGuiCol_ +{ + ImGuiCol_Text, + ImGuiCol_TextDisabled, + ImGuiCol_WindowBg, // Background of normal windows + ImGuiCol_ChildBg, // Background of child windows + ImGuiCol_PopupBg, // Background of popups, menus, tooltips windows + ImGuiCol_Border, + ImGuiCol_BorderShadow, + ImGuiCol_FrameBg, // Background of checkbox, radio button, plot, slider, text input + ImGuiCol_FrameBgHovered, + ImGuiCol_FrameBgActive, + ImGuiCol_TitleBg, + ImGuiCol_TitleBgActive, + ImGuiCol_TitleBgCollapsed, + ImGuiCol_MenuBarBg, + ImGuiCol_ScrollbarBg, + ImGuiCol_ScrollbarGrab, + ImGuiCol_ScrollbarGrabHovered, + ImGuiCol_ScrollbarGrabActive, + ImGuiCol_CheckMark, + ImGuiCol_SliderGrab, + ImGuiCol_SliderGrabActive, + ImGuiCol_Button, + ImGuiCol_ButtonHovered, + ImGuiCol_ButtonActive, + ImGuiCol_Header, // Header* colors are used for CollapsingHeader, TreeNode, Selectable, MenuItem + ImGuiCol_HeaderHovered, + ImGuiCol_HeaderActive, + ImGuiCol_Separator, + ImGuiCol_SeparatorHovered, + ImGuiCol_SeparatorActive, + ImGuiCol_ResizeGrip, + ImGuiCol_ResizeGripHovered, + ImGuiCol_ResizeGripActive, + ImGuiCol_Tab, + ImGuiCol_TabHovered, + ImGuiCol_TabActive, + ImGuiCol_TabUnfocused, + ImGuiCol_TabUnfocusedActive, + ImGuiCol_PlotLines, + ImGuiCol_PlotLinesHovered, + ImGuiCol_PlotHistogram, + ImGuiCol_PlotHistogramHovered, + ImGuiCol_TableHeaderBg, // Table header background + ImGuiCol_TableBorderStrong, // Table outer and header borders (prefer using Alpha=1.0 here) + ImGuiCol_TableBorderLight, // Table inner borders (prefer using Alpha=1.0 here) + ImGuiCol_TableRowBg, // Table row background (even rows) + ImGuiCol_TableRowBgAlt, // Table row background (odd rows) + ImGuiCol_TextSelectedBg, + ImGuiCol_DragDropTarget, + ImGuiCol_NavHighlight, // Gamepad/keyboard: current highlighted item + ImGuiCol_NavWindowingHighlight, // Highlight window when using CTRL+TAB + ImGuiCol_NavWindowingDimBg, // Darken/colorize entire screen behind the CTRL+TAB window list, when active + ImGuiCol_ModalWindowDimBg, // Darken/colorize entire screen behind a modal window, when one is active + ImGuiCol_COUNT +}; + +// Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. +// - The enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. +// During initialization or between frames, feel free to just poke into ImGuiStyle directly. +// - Tip: Use your programming IDE navigation facilities on the names in the _second column_ below to find the actual members and their description. +// In Visual Studio IDE: CTRL+comma ("Edit.NavigateTo") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +// - When changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type. +enum ImGuiStyleVar_ +{ + // Enum name --------------------- // Member in ImGuiStyle structure (see ImGuiStyle for descriptions) + ImGuiStyleVar_Alpha, // float Alpha + ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding + ImGuiStyleVar_WindowRounding, // float WindowRounding + ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize + ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize + ImGuiStyleVar_WindowTitleAlign, // ImVec2 WindowTitleAlign + ImGuiStyleVar_ChildRounding, // float ChildRounding + ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize + ImGuiStyleVar_PopupRounding, // float PopupRounding + ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize + ImGuiStyleVar_FramePadding, // ImVec2 FramePadding + ImGuiStyleVar_FrameRounding, // float FrameRounding + ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize + ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing + ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing + ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_CellPadding, // ImVec2 CellPadding + ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize + ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding + ImGuiStyleVar_GrabMinSize, // float GrabMinSize + ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_TabRounding, // float TabRounding + ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign + ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign + ImGuiStyleVar_COUNT +}; + +// Flags for InvisibleButton() [extended in imgui_internal.h] +enum ImGuiButtonFlags_ +{ + ImGuiButtonFlags_None = 0, + ImGuiButtonFlags_MouseButtonLeft = 1 << 0, // React on left mouse button (default) + ImGuiButtonFlags_MouseButtonRight = 1 << 1, // React on right mouse button + ImGuiButtonFlags_MouseButtonMiddle = 1 << 2, // React on center mouse button + + // [Internal] + ImGuiButtonFlags_MouseButtonMask_ = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle, + ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft +}; + +// Flags for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() +enum ImGuiColorEditFlags_ +{ + ImGuiColorEditFlags_None = 0, + ImGuiColorEditFlags_NoAlpha = 1 << 1, // // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (will only read 3 components from the input pointer). + ImGuiColorEditFlags_NoPicker = 1 << 2, // // ColorEdit: disable picker when clicking on color square. + ImGuiColorEditFlags_NoOptions = 1 << 3, // // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. + ImGuiColorEditFlags_NoSmallPreview = 1 << 4, // // ColorEdit, ColorPicker: disable color square preview next to the inputs. (e.g. to show only the inputs) + ImGuiColorEditFlags_NoInputs = 1 << 5, // // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview color square). + ImGuiColorEditFlags_NoTooltip = 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. + ImGuiColorEditFlags_NoLabel = 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). + ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small color square preview instead. + ImGuiColorEditFlags_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source. + ImGuiColorEditFlags_NoBorder = 1 << 10, // // ColorButton: disable border (which is enforced by default) + + // User Options (right-click on widget to change some of them). + ImGuiColorEditFlags_AlphaBar = 1 << 16, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. + ImGuiColorEditFlags_AlphaPreview = 1 << 17, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque. + ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 18, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque. + ImGuiColorEditFlags_HDR = 1 << 19, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). + ImGuiColorEditFlags_DisplayRGB = 1 << 20, // [Display] // ColorEdit: override _display_ type among RGB/HSV/Hex. ColorPicker: select any combination using one or more of RGB/HSV/Hex. + ImGuiColorEditFlags_DisplayHSV = 1 << 21, // [Display] // " + ImGuiColorEditFlags_DisplayHex = 1 << 22, // [Display] // " + ImGuiColorEditFlags_Uint8 = 1 << 23, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. + ImGuiColorEditFlags_Float = 1 << 24, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. + ImGuiColorEditFlags_PickerHueBar = 1 << 25, // [Picker] // ColorPicker: bar for Hue, rectangle for Sat/Value. + ImGuiColorEditFlags_PickerHueWheel = 1 << 26, // [Picker] // ColorPicker: wheel for Hue, triangle for Sat/Value. + ImGuiColorEditFlags_InputRGB = 1 << 27, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. + ImGuiColorEditFlags_InputHSV = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. + + // Defaults Options. You can set application defaults using SetColorEditOptions(). The intent is that you probably don't want to + // override them in most of your calls. Let the user choose via the option menu and/or call SetColorEditOptions() once during startup. + ImGuiColorEditFlags__OptionsDefault = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar, + + // [Internal] Masks + ImGuiColorEditFlags__DisplayMask = ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_DisplayHex, + ImGuiColorEditFlags__DataTypeMask = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float, + ImGuiColorEditFlags__PickerMask = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar, + ImGuiColorEditFlags__InputMask = ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_InputHSV + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiColorEditFlags_RGB = ImGuiColorEditFlags_DisplayRGB, ImGuiColorEditFlags_HSV = ImGuiColorEditFlags_DisplayHSV, ImGuiColorEditFlags_HEX = ImGuiColorEditFlags_DisplayHex // [renamed in 1.69] +#endif +}; + +// Flags for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. +// We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them. +enum ImGuiSliderFlags_ +{ + ImGuiSliderFlags_None = 0, + ImGuiSliderFlags_AlwaysClamp = 1 << 4, // Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds. + ImGuiSliderFlags_Logarithmic = 1 << 5, // Make the widget logarithmic (linear otherwise). Consider using ImGuiSliderFlags_NoRoundToFormat with this if using a format-string with small amount of digits. + ImGuiSliderFlags_NoRoundToFormat = 1 << 6, // Disable rounding underlying value to match precision of the display format string (e.g. %.3f values are rounded to those 3 digits) + ImGuiSliderFlags_NoInput = 1 << 7, // Disable CTRL+Click or Enter key allowing to input text directly into the widget + ImGuiSliderFlags_InvalidMask_ = 0x7000000F // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiSliderFlags_ClampOnInput = ImGuiSliderFlags_AlwaysClamp // [renamed in 1.79] +#endif +}; + +// Identify a mouse button. +// Those values are guaranteed to be stable and we frequently use 0/1 directly. Named enums provided for convenience. +enum ImGuiMouseButton_ +{ + ImGuiMouseButton_Left = 0, + ImGuiMouseButton_Right = 1, + ImGuiMouseButton_Middle = 2, + ImGuiMouseButton_COUNT = 5 +}; + +// Enumeration for GetMouseCursor() +// User code may request backend to display given cursor by calling SetMouseCursor(), which is why we have some cursors that are marked unused here +enum ImGuiMouseCursor_ +{ + ImGuiMouseCursor_None = -1, + ImGuiMouseCursor_Arrow = 0, + ImGuiMouseCursor_TextInput, // When hovering over InputText, etc. + ImGuiMouseCursor_ResizeAll, // (Unused by Dear ImGui functions) + ImGuiMouseCursor_ResizeNS, // When hovering over an horizontal border + ImGuiMouseCursor_ResizeEW, // When hovering over a vertical border or a column + ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window + ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window + ImGuiMouseCursor_Hand, // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) + ImGuiMouseCursor_NotAllowed, // When hovering something with disallowed interaction. Usually a crossed circle. + ImGuiMouseCursor_COUNT +}; + +// Enumeration for ImGui::SetWindow***(), SetNextWindow***(), SetNextItem***() functions +// Represent a condition. +// Important: Treat as a regular enum! Do NOT combine multiple values using binary operators! All the functions above treat 0 as a shortcut to ImGuiCond_Always. +enum ImGuiCond_ +{ + ImGuiCond_None = 0, // No condition (always set the variable), same as _Always + ImGuiCond_Always = 1 << 0, // No condition (always set the variable) + ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call will succeed) + ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the object/window has no persistently saved data (no entry in .ini file) + ImGuiCond_Appearing = 1 << 3 // Set the variable if the object/window is appearing after being hidden/inactive (or the first time) +}; + +//----------------------------------------------------------------------------- +// [SECTION] Helpers: Memory allocations macros, ImVector<> +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// IM_MALLOC(), IM_FREE(), IM_NEW(), IM_PLACEMENT_NEW(), IM_DELETE() +// We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. +// Defining a custom placement new() with a custom parameter allows us to bypass including which on some platforms complains when user has disabled exceptions. +//----------------------------------------------------------------------------- + +struct ImNewWrapper {}; +inline void* operator new(size_t, ImNewWrapper, void* ptr) { return ptr; } +inline void operator delete(void*, ImNewWrapper, void*) {} // This is only required so we can use the symmetrical new() +#define IM_ALLOC(_SIZE) ImGui::MemAlloc(_SIZE) +#define IM_FREE(_PTR) ImGui::MemFree(_PTR) +#define IM_PLACEMENT_NEW(_PTR) new(ImNewWrapper(), _PTR) +#define IM_NEW(_TYPE) new(ImNewWrapper(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE +template void IM_DELETE(T* p) { if (p) { p->~T(); ImGui::MemFree(p); } } + +//----------------------------------------------------------------------------- +// ImVector<> +// Lightweight std::vector<>-like class to avoid dragging dependencies (also, some implementations of STL with debug enabled are absurdly slow, we bypass it so our code runs fast in debug). +//----------------------------------------------------------------------------- +// - You generally do NOT need to care or use this ever. But we need to make it available in imgui.h because some of our public structures are relying on it. +// - We use std-like naming convention here, which is a little unusual for this codebase. +// - Important: clear() frees memory, resize(0) keep the allocated buffer. We use resize(0) a lot to intentionally recycle allocated buffers across frames and amortize our costs. +// - Important: our implementation does NOT call C++ constructors/destructors, we treat everything as raw data! This is intentional but be extra mindful of that, +// Do NOT use this class as a std::vector replacement in your own code! Many of the structures used by dear imgui can be safely initialized by a zero-memset. +//----------------------------------------------------------------------------- + +IM_MSVC_RUNTIME_CHECKS_OFF +template +struct ImVector +{ + int Size; + int Capacity; + T* Data; + + // Provide standard typedefs but we don't use them ourselves. + typedef T value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + // Constructors, destructor + inline ImVector() { Size = Capacity = 0; Data = NULL; } + inline ImVector(const ImVector& src) { Size = Capacity = 0; Data = NULL; operator=(src); } + inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); memcpy(Data, src.Data, (size_t)Size * sizeof(T)); return *this; } + inline ~ImVector() { if (Data) IM_FREE(Data); } + + inline bool empty() const { return Size == 0; } + inline int size() const { return Size; } + inline int size_in_bytes() const { return Size * (int)sizeof(T); } + inline int max_size() const { return 0x7FFFFFFF / (int)sizeof(T); } + inline int capacity() const { return Capacity; } + inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } + inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } + + inline void clear() { if (Data) { Size = Capacity = 0; IM_FREE(Data); Data = NULL; } } + inline T* begin() { return Data; } + inline const T* begin() const { return Data; } + inline T* end() { return Data + Size; } + inline const T* end() const { return Data + Size; } + inline T& front() { IM_ASSERT(Size > 0); return Data[0]; } + inline const T& front() const { IM_ASSERT(Size > 0); return Data[0]; } + inline T& back() { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline const T& back() const { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline void swap(ImVector& rhs) { int rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; int rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; T* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } + + inline int _grow_capacity(int sz) const { int new_capacity = Capacity ? (Capacity + Capacity / 2) : 8; return new_capacity > sz ? new_capacity : sz; } + inline void resize(int new_size) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; } + inline void resize(int new_size, const T& v) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) memcpy(&Data[n], &v, sizeof(v)); Size = new_size; } + inline void shrink(int new_size) { IM_ASSERT(new_size <= Size); Size = new_size; } // Resize a vector to a smaller size, guaranteed not to cause a reallocation + inline void reserve(int new_capacity) { if (new_capacity <= Capacity) return; T* new_data = (T*)IM_ALLOC((size_t)new_capacity * sizeof(T)); if (Data) { memcpy(new_data, Data, (size_t)Size * sizeof(T)); IM_FREE(Data); } Data = new_data; Capacity = new_capacity; } + + // NB: It is illegal to call push_back/push_front/insert with a reference pointing inside the ImVector data itself! e.g. v.push_back(v[10]) is forbidden. + inline void push_back(const T& v) { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); memcpy(&Data[Size], &v, sizeof(v)); Size++; } + inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + inline void push_front(const T& v) { if (Size == 0) push_back(v); else insert(Data, v); } + inline T* erase(const T* it) { IM_ASSERT(it >= Data && it < Data + Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(T)); Size--; return Data + off; } + inline T* erase(const T* it, const T* it_last){ IM_ASSERT(it >= Data && it < Data + Size && it_last > it && it_last <= Data + Size); const ptrdiff_t count = it_last - it; const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + count, ((size_t)Size - (size_t)off - count) * sizeof(T)); Size -= (int)count; return Data + off; } + inline T* erase_unsorted(const T* it) { IM_ASSERT(it >= Data && it < Data + Size); const ptrdiff_t off = it - Data; if (it < Data + Size - 1) memcpy(Data + off, Data + Size - 1, sizeof(T)); Size--; return Data + off; } + inline T* insert(const T* it, const T& v) { IM_ASSERT(it >= Data && it <= Data + Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(_grow_capacity(Size + 1)); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(T)); memcpy(&Data[off], &v, sizeof(v)); Size++; return Data + off; } + inline bool contains(const T& v) const { const T* data = Data; const T* data_end = Data + Size; while (data < data_end) if (*data++ == v) return true; return false; } + inline T* find(const T& v) { T* data = Data; const T* data_end = Data + Size; while (data < data_end) if (*data == v) break; else ++data; return data; } + inline const T* find(const T& v) const { const T* data = Data; const T* data_end = Data + Size; while (data < data_end) if (*data == v) break; else ++data; return data; } + inline bool find_erase(const T& v) { const T* it = find(v); if (it < Data + Size) { erase(it); return true; } return false; } + inline bool find_erase_unsorted(const T& v) { const T* it = find(v); if (it < Data + Size) { erase_unsorted(it); return true; } return false; } + inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < Data + Size); const ptrdiff_t off = it - Data; return (int)off; } +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiStyle +//----------------------------------------------------------------------------- +// You may modify the ImGui::GetStyle() main instance during initialization and before NewFrame(). +// During the frame, use ImGui::PushStyleVar(ImGuiStyleVar_XXXX)/PopStyleVar() to alter the main style values, +// and ImGui::PushStyleColor(ImGuiCol_XXX)/PopStyleColor() for colors. +//----------------------------------------------------------------------------- + +struct ImGuiStyle +{ + float Alpha; // Global alpha applies to everything in Dear ImGui. + ImVec2 WindowPadding; // Padding within a window. + float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. + float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constraint individual windows, use SetNextWindowSizeConstraints(). + ImVec2 WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. + ImGuiDir WindowMenuButtonPosition; // Side of the collapsing/docking button in the title bar (None/Left/Right). Defaults to ImGuiDir_Left. + float ChildRounding; // Radius of child window corners rounding. Set to 0.0f to have rectangular windows. + float ChildBorderSize; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + float PopupRounding; // Radius of popup window corners rounding. (Note that tooltip windows use WindowRounding) + float PopupBorderSize; // Thickness of border around popup/tooltip windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 FramePadding; // Padding within a framed rectangle (used by most widgets). + float FrameRounding; // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets). + float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. + ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). + ImVec2 CellPadding; // Padding within a table cell + ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). + float ScrollbarSize; // Width of the vertical scrollbar, Height of the horizontal scrollbar. + float ScrollbarRounding; // Radius of grab corners for scrollbar. + float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. + float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + float LogSliderDeadzone; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. + float TabRounding; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. + float TabBorderSize; // Thickness of border around tabs. + float TabMinWidthForCloseButton; // Minimum width for close button to appears on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. + ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. + ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). + ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. + ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. + ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! + float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). + bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering. Latched at the beginning of the frame (copied to ImDrawList). + bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). + float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. + ImVec4 Colors[ImGuiCol_COUNT]; + + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); +}; + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiIO +//----------------------------------------------------------------------------- +// Communicate most settings and inputs/outputs to Dear ImGui using this structure. +// Access via ImGui::GetIO(). Read 'Programmer guide' section in .cpp file for general usage. +//----------------------------------------------------------------------------- + +struct ImGuiIO +{ + //------------------------------------------------------------------ + // Configuration (fill once) // Default value + //------------------------------------------------------------------ + + ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Gamepad/keyboard navigation options, etc. + ImGuiBackendFlags BackendFlags; // = 0 // See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend. + ImVec2 DisplaySize; // // Main display size, in pixels (generally == GetMainViewport()->Size) + float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. + float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. + const char* IniFilename; // = "imgui.ini" // Path to .ini file. Set NULL to disable automatic .ini loading/saving, if e.g. you want to manually load/save from memory. + const char* LogFilename; // = "imgui_log.txt"// Path to .log file (default parameter to ImGui::LogToFile when no file is specified). + float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. + float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. + float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging. + int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. + float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). + float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. + void* UserData; // = NULL // Store your own data for retrieval by callbacks. + + ImFontAtlas*Fonts; // // Font atlas: load, rasterize and pack one or more fonts into a single texture. + float FontGlobalScale; // = 1.0f // Global scale all fonts + bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. + ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. + ImVec2 DisplayFramebufferScale; // = (1, 1) // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale. + + // Miscellaneous options + bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations. + bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl. + bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). + bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. + bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) + bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. + float ConfigMemoryCompactTimer; // = 60.0f // Timer (in seconds) to free transient windows/tables memory buffers when unused. Set to -1.0f to disable. + + //------------------------------------------------------------------ + // Platform Functions + // (the imgui_impl_xxxx backend files are setting those up for you) + //------------------------------------------------------------------ + + // Optional: Platform/Renderer backend name (informational only! will be displayed in About Window) + User data for backend/wrappers to store their own stuff. + const char* BackendPlatformName; // = NULL + const char* BackendRendererName; // = NULL + void* BackendPlatformUserData; // = NULL // User data for platform backend + void* BackendRendererUserData; // = NULL // User data for renderer backend + void* BackendLanguageUserData; // = NULL // User data for non C++ programming language backend + + // Optional: Access OS clipboard + // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) + const char* (*GetClipboardTextFn)(void* user_data); + void (*SetClipboardTextFn)(void* user_data, const char* text); + void* ClipboardUserData; + + // Optional: Notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME on Windows) + // (default to use native imm32 api on Windows) + void (*ImeSetInputScreenPosFn)(int x, int y); + void* ImeWindowHandle; // = NULL // (Windows) Set this to your HWND to get automatic IME cursor positioning. + + //------------------------------------------------------------------ + // Input - Fill before calling NewFrame() + //------------------------------------------------------------------ + + ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX, -FLT_MAX) if mouse is unavailable (on another screen, etc.) + bool MouseDown[5]; // Mouse buttons: 0=left, 1=right, 2=middle + extras (ImGuiMouseButton_COUNT == 5). Dear ImGui mostly uses left and right buttons. Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. + float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. + float MouseWheelH; // Mouse wheel Horizontal. Most users don't have a mouse with an horizontal wheel, may not be filled by all backends. + bool KeyCtrl; // Keyboard modifier pressed: Control + bool KeyShift; // Keyboard modifier pressed: Shift + bool KeyAlt; // Keyboard modifier pressed: Alt + bool KeySuper; // Keyboard modifier pressed: Cmd/Super/Windows + bool KeysDown[512]; // Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). + float NavInputs[ImGuiNavInput_COUNT]; // Gamepad inputs. Cleared back to zero by EndFrame(). Keyboard keys will be auto-mapped and be written here by NewFrame(). + + // Functions + IMGUI_API void AddInputCharacter(unsigned int c); // Queue new character input + IMGUI_API void AddInputCharacterUTF16(ImWchar16 c); // Queue new character input from an UTF-16 character, it can be a surrogate + IMGUI_API void AddInputCharactersUTF8(const char* str); // Queue new characters input from an UTF-8 string + IMGUI_API void ClearInputCharacters(); // Clear the text input buffer manually + + //------------------------------------------------------------------ + // Output - Updated by NewFrame() or EndFrame()/Render() + // (when reading from the io.WantCaptureMouse, io.WantCaptureKeyboard flags to dispatch your inputs, it is + // generally easier and more correct to use their state BEFORE calling NewFrame(). See FAQ for details!) + //------------------------------------------------------------------ + + bool WantCaptureMouse; // Set when Dear ImGui will use mouse inputs, in this case do not dispatch them to your main game/application (either way, always pass on mouse inputs to imgui). (e.g. unclicked mouse is hovering over an imgui window, widget is active, mouse was clicked over an imgui window, etc.). + bool WantCaptureKeyboard; // Set when Dear ImGui will use keyboard inputs, in this case do not dispatch them to your main game/application (either way, always pass keyboard inputs to imgui). (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.). + bool WantTextInput; // Mobile/console: when set, you may display an on-screen keyboard. This is set by Dear ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). + bool WantSetMousePos; // MousePos has been altered, backend should reposition mouse on next frame. Rarely used! Set only when ImGuiConfigFlags_NavEnableSetMousePos flag is enabled. + bool WantSaveIniSettings; // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify your application that you can call SaveIniSettingsToMemory() and save yourself. Important: clear io.WantSaveIniSettings yourself after saving! + bool NavActive; // Keyboard/Gamepad navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. + bool NavVisible; // Keyboard/Gamepad navigation is visible and allowed (will handle ImGuiKey_NavXXX events). + float Framerate; // Rough estimate of application framerate, in frame per second. Solely for convenience. Rolling average estimation based on io.DeltaTime over 120 frames. + int MetricsRenderVertices; // Vertices output during last call to Render() + int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 + int MetricsRenderWindows; // Number of visible windows + int MetricsActiveWindows; // Number of active windows + int MetricsActiveAllocations; // Number of active allocations, updated by MemAlloc/MemFree based on current context. May be off if you have multiple imgui contexts. + ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. + + //------------------------------------------------------------------ + // [Internal] Dear ImGui will maintain those fields. Forward compatibility not guaranteed! + //------------------------------------------------------------------ + + ImGuiKeyModFlags KeyMods; // Key mods flags (same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags), updated by NewFrame() + ImVec2 MousePosPrev; // Previous mouse position (note that MouseDelta is not necessary == MousePos-MousePosPrev, in case either position is invalid) + ImVec2 MouseClickedPos[5]; // Position at time of clicking + double MouseClickedTime[5]; // Time of last click (used to figure out double-click) + bool MouseClicked[5]; // Mouse button went from !Down to Down + bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? + bool MouseReleased[5]; // Mouse button went from Down to !Down + bool MouseDownOwned[5]; // Track if button was clicked inside a dear imgui window. We don't request mouse capture from the application if click started outside ImGui bounds. + bool MouseDownWasDoubleClick[5]; // Track if button down was a double-click + float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) + float MouseDownDurationPrev[5]; // Previous time the mouse button has been down + ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point + float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point + float KeysDownDuration[512]; // Duration the keyboard key has been down (0.0f == just pressed) + float KeysDownDurationPrev[512]; // Previous duration the key has been down + float NavInputsDownDuration[ImGuiNavInput_COUNT]; + float NavInputsDownDurationPrev[ImGuiNavInput_COUNT]; + float PenPressure; // Touch/Pen pressure (0.0f to 1.0f, should be >0.0f only when MouseDown[0] == true). Helper storage currently unused by Dear ImGui. + ImWchar16 InputQueueSurrogate; // For AddInputCharacterUTF16 + ImVector InputQueueCharacters; // Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper. + + IMGUI_API ImGuiIO(); +}; + +//----------------------------------------------------------------------------- +// [SECTION] Misc data structures +//----------------------------------------------------------------------------- + +// Shared state of InputText(), passed as an argument to your callback when a ImGuiInputTextFlags_Callback* flag is used. +// The callback function should return 0 by default. +// Callbacks (follow a flag name and see comments in ImGuiInputTextFlags_ declarations for more details) +// - ImGuiInputTextFlags_CallbackEdit: Callback on buffer edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) +// - ImGuiInputTextFlags_CallbackAlways: Callback on each iteration +// - ImGuiInputTextFlags_CallbackCompletion: Callback on pressing TAB +// - ImGuiInputTextFlags_CallbackHistory: Callback on pressing Up/Down arrows +// - ImGuiInputTextFlags_CallbackCharFilter: Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. +// - ImGuiInputTextFlags_CallbackResize: Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. +struct ImGuiInputTextCallbackData +{ + ImGuiInputTextFlags EventFlag; // One ImGuiInputTextFlags_Callback* // Read-only + ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only + void* UserData; // What user passed to InputText() // Read-only + + // Arguments for the different callback events + // - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize callback if necessary. + // - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state. + ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character with another one, or set to zero to drop. return 1 is equivalent to setting EventChar=0; + ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only // [Completion,History] + char* Buf; // Text buffer // Read-write // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! + int BufTextLen; // Text length (in bytes) // Read-write // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length() + int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1 + bool BufDirty; // Set if you modify Buf/BufTextLen! // Write // [Completion,History,Always] + int CursorPos; // // Read-write // [Completion,History,Always] + int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection) + int SelectionEnd; // // Read-write // [Completion,History,Always] + + // Helper functions for text manipulation. + // Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection. + IMGUI_API ImGuiInputTextCallbackData(); + IMGUI_API void DeleteChars(int pos, int bytes_count); + IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL); + void SelectAll() { SelectionStart = 0; SelectionEnd = BufTextLen; } + void ClearSelection() { SelectionStart = SelectionEnd = BufTextLen; } + bool HasSelection() const { return SelectionStart != SelectionEnd; } +}; + +// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin(). +// NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough. +struct ImGuiSizeCallbackData +{ + void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraints() + ImVec2 Pos; // Read-only. Window position, for reference. + ImVec2 CurrentSize; // Read-only. Current window size. + ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. +}; + +// Data payload for Drag and Drop operations: AcceptDragDropPayload(), GetDragDropPayload() +struct ImGuiPayload +{ + // Members + void* Data; // Data (copied and owned by dear imgui) + int DataSize; // Data size + + // [Internal] + ImGuiID SourceId; // Source item id + ImGuiID SourceParentId; // Source parent id (if available) + int DataFrameCount; // Data timestamp + char DataType[32 + 1]; // Data type tag (short user-supplied string, 32 characters max) + bool Preview; // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle overlapping drag targets) + bool Delivery; // Set when AcceptDragDropPayload() was called and mouse button is released over the target item. + + ImGuiPayload() { Clear(); } + void Clear() { SourceId = SourceParentId = 0; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Preview = Delivery = false; } + bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; } + bool IsPreview() const { return Preview; } + bool IsDelivery() const { return Delivery; } +}; + +// Sorting specification for one column of a table (sizeof == 12 bytes) +struct ImGuiTableColumnSortSpecs +{ + ImGuiID ColumnUserID; // User id of the column (if specified by a TableSetupColumn() call) + ImS16 ColumnIndex; // Index of the column + ImS16 SortOrder; // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here) + ImGuiSortDirection SortDirection : 8; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending (you can use this or SortSign, whichever is more convenient for your sort function) + + ImGuiTableColumnSortSpecs() { memset(this, 0, sizeof(*this)); } +}; + +// Sorting specifications for a table (often handling sort specs for a single column, occasionally more) +// Obtained by calling TableGetSortSpecs(). +// When 'SpecsDirty == true' you can sort your data. It will be true with sorting specs have changed since last call, or the first time. +// Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame! +struct ImGuiTableSortSpecs +{ + const ImGuiTableColumnSortSpecs* Specs; // Pointer to sort spec array. + int SpecsCount; // Sort spec count. Most often 1. May be > 1 when ImGuiTableFlags_SortMulti is enabled. May be == 0 when ImGuiTableFlags_SortTristate is enabled. + bool SpecsDirty; // Set to true when specs have changed since last time! Use this to sort again, then clear the flag. + + ImGuiTableSortSpecs() { memset(this, 0, sizeof(*this)); } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, ImColor) +//----------------------------------------------------------------------------- + +// Helper: Unicode defines +#define IM_UNICODE_CODEPOINT_INVALID 0xFFFD // Invalid Unicode code point (standard value). +#ifdef IMGUI_USE_WCHAR32 +#define IM_UNICODE_CODEPOINT_MAX 0x10FFFF // Maximum Unicode code point supported by this build. +#else +#define IM_UNICODE_CODEPOINT_MAX 0xFFFF // Maximum Unicode code point supported by this build. +#endif + +// Helper: Execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within deep-nested code that runs multiple times every frame. +// Usage: static ImGuiOnceUponAFrame oaf; if (oaf) ImGui::Text("This will be called only once per frame"); +struct ImGuiOnceUponAFrame +{ + ImGuiOnceUponAFrame() { RefFrame = -1; } + mutable int RefFrame; + operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } +}; + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextFilter +{ + IMGUI_API ImGuiTextFilter(const char* default_filter = ""); + IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build + IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; + IMGUI_API void Build(); + void Clear() { InputBuf[0] = 0; Build(); } + bool IsActive() const { return !Filters.empty(); } + + // [Internal] + struct ImGuiTextRange + { + const char* b; + const char* e; + + ImGuiTextRange() { b = e = NULL; } + ImGuiTextRange(const char* _b, const char* _e) { b = _b; e = _e; } + bool empty() const { return b == e; } + IMGUI_API void split(char separator, ImVector* out) const; + }; + char InputBuf[256]; + ImVectorFilters; + int CountGrep; +}; + +// Helper: Growable text buffer for logging/accumulating text +// (this could be called 'ImGuiTextBuilder' / 'ImGuiStringBuilder') +struct ImGuiTextBuffer +{ + ImVector Buf; + IMGUI_API static char EmptyString[1]; + + ImGuiTextBuffer() { } + inline char operator[](int i) const { IM_ASSERT(Buf.Data != NULL); return Buf.Data[i]; } + const char* begin() const { return Buf.Data ? &Buf.front() : EmptyString; } + const char* end() const { return Buf.Data ? &Buf.back() : EmptyString; } // Buf is zero-terminated, so end() will point on the zero-terminator + int size() const { return Buf.Size ? Buf.Size - 1 : 0; } + bool empty() const { return Buf.Size <= 1; } + void clear() { Buf.clear(); } + void reserve(int capacity) { Buf.reserve(capacity); } + const char* c_str() const { return Buf.Data ? Buf.Data : EmptyString; } + IMGUI_API void append(const char* str, const char* str_end = NULL); + IMGUI_API void appendf(const char* fmt, ...) IM_FMTARGS(2); + IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); +}; + +// Helper: Key->Value storage +// Typically you don't have to worry about this since a storage is held within each Window. +// We use it to e.g. store collapse state for a tree (Int 0/1) +// This is optimized for efficient lookup (dichotomy into a contiguous buffer) and rare insertion (typically tied to user interactions aka max once a frame) +// You can use it as custom user storage for temporary values. Declare your own storage if, for example: +// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state). +// - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient) +// Types are NOT stored, so it is up to you to make sure your Key don't collide with different types. +struct ImGuiStorage +{ + // [Internal] + struct ImGuiStoragePair + { + ImGuiID key; + union { int val_i; float val_f; void* val_p; }; + ImGuiStoragePair(ImGuiID _key, int _val_i) { key = _key; val_i = _val_i; } + ImGuiStoragePair(ImGuiID _key, float _val_f) { key = _key; val_f = _val_f; } + ImGuiStoragePair(ImGuiID _key, void* _val_p) { key = _key; val_p = _val_p; } + }; + + ImVector Data; + + // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) + // - Set***() functions find pair, insertion on demand if missing. + // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair. + void Clear() { Data.clear(); } + IMGUI_API int GetInt(ImGuiID key, int default_val = 0) const; + IMGUI_API void SetInt(ImGuiID key, int val); + IMGUI_API bool GetBool(ImGuiID key, bool default_val = false) const; + IMGUI_API void SetBool(ImGuiID key, bool val); + IMGUI_API float GetFloat(ImGuiID key, float default_val = 0.0f) const; + IMGUI_API void SetFloat(ImGuiID key, float val); + IMGUI_API void* GetVoidPtr(ImGuiID key) const; // default_val is NULL + IMGUI_API void SetVoidPtr(ImGuiID key, void* val); + + // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do Get+Set. + // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. + // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue session if you can't modify existing struct) + // float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); some_var += *pvar; + IMGUI_API int* GetIntRef(ImGuiID key, int default_val = 0); + IMGUI_API bool* GetBoolRef(ImGuiID key, bool default_val = false); + IMGUI_API float* GetFloatRef(ImGuiID key, float default_val = 0.0f); + IMGUI_API void** GetVoidPtrRef(ImGuiID key, void* default_val = NULL); + + // Use on your own storage if you know only integer are being stored (open/close all tree nodes) + IMGUI_API void SetAllInt(int val); + + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. + IMGUI_API void BuildSortByKey(); +}; + +// Helper: Manually clip large list of items. +// If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse +// clipping based on visibility to save yourself from processing those items at all. +// The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. +// (Dear ImGui already clip items based on their bounds but it needs to measure text size to do so, whereas manual coarse clipping before submission makes this cost and your own data fetching/submission cost almost null) +// Usage: +// ImGuiListClipper clipper; +// clipper.Begin(1000); // We have 1000 elements, evenly spaced. +// while (clipper.Step()) +// for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) +// ImGui::Text("line number %d", i); +// Generally what happens is: +// - Clipper lets you process the first element (DisplayStart = 0, DisplayEnd = 1) regardless of it being visible or not. +// - User code submit one element. +// - Clipper can measure the height of the first element +// - Clipper calculate the actual range of elements to display based on the current clipping rectangle, position the cursor before the first visible element. +// - User code submit visible elements. +struct ImGuiListClipper +{ + int DisplayStart; + int DisplayEnd; + + // [Internal] + int ItemsCount; + int StepNo; + int ItemsFrozen; + float ItemsHeight; + float StartPosY; + + IMGUI_API ImGuiListClipper(); + IMGUI_API ~ImGuiListClipper(); + + // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step) + // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). + IMGUI_API void Begin(int items_count, float items_height = -1.0f); // Automatically called by constructor if you passed 'items_count' or by Step() in Step 1. + IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. + IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset(this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79] +#endif +}; + +// Helpers macros to generate 32-bit encoded colors +#ifdef IMGUI_USE_BGRA_PACKED_COLOR +#define IM_COL32_R_SHIFT 16 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 0 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#else +#define IM_COL32_R_SHIFT 0 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 16 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#endif +#define IM_COL32(R,G,B,A) (((ImU32)(A)<> IM_COL32_R_SHIFT) & 0xFF) * sc; Value.y = (float)((rgba >> IM_COL32_G_SHIFT) & 0xFF) * sc; Value.z = (float)((rgba >> IM_COL32_B_SHIFT) & 0xFF) * sc; Value.w = (float)((rgba >> IM_COL32_A_SHIFT) & 0xFF) * sc; } + ImColor(float r, float g, float b, float a = 1.0f) { Value.x = r; Value.y = g; Value.z = b; Value.w = a; } + ImColor(const ImVec4& col) { Value = col; } + inline operator ImU32() const { return ImGui::ColorConvertFloat4ToU32(Value); } + inline operator ImVec4() const { return Value; } + + // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers. + inline void SetHSV(float h, float s, float v, float a = 1.0f){ ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); Value.w = a; } + static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r, g, b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r, g, b, a); } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData) +// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. +//----------------------------------------------------------------------------- + +// The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. +#ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX +#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (63) +#endif + +// ImDrawCallback: Draw callbacks for advanced uses [configurable type: override in imconfig.h] +// NB: You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering, +// you can poke into the draw list for that! Draw callback may be useful for example to: +// A) Change your GPU render state, +// B) render a complex 3D scene inside a UI element without an intermediate texture/render target, etc. +// The expected behavior from your rendering function is 'if (cmd.UserCallback != NULL) { cmd.UserCallback(parent_list, cmd); } else { RenderTriangles() }' +// If you want to override the signature of ImDrawCallback, you can simply use e.g. '#define ImDrawCallback MyDrawCallback' (in imconfig.h) + update rendering backend accordingly. +#ifndef ImDrawCallback +typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); +#endif + +// Special Draw callback value to request renderer backend to reset the graphics/render state. +// The renderer backend needs to handle this special value, otherwise it will crash trying to call a function at this address. +// This is useful for example if you submitted callbacks which you know have altered the render state and you want it to be restored. +// It is not done by default because they are many perfectly useful way of altering render state for imgui contents (e.g. changing shader/blending settings before an Image call). +#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-1) + +// Typically, 1 command = 1 GPU draw call (unless command is a callback) +// - VtxOffset/IdxOffset: When 'io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset' is enabled, +// those fields allow us to render meshes larger than 64K vertices while keeping 16-bit indices. +// Pre-1.71 backends will typically ignore the VtxOffset/IdxOffset fields. +// - The ClipRect/TextureId/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for). +struct ImDrawCmd +{ + ImVec4 ClipRect; // 4*4 // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in "viewport" coordinates + ImTextureID TextureId; // 4-8 // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + unsigned int VtxOffset; // 4 // Start offset in vertex buffer. ImGuiBackendFlags_RendererHasVtxOffset: always 0, otherwise may be >0 to support meshes larger than 64K vertices with 16-bit indices. + unsigned int IdxOffset; // 4 // Start offset in index buffer. Always equal to sum of ElemCount drawn so far. + unsigned int ElemCount; // 4 // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. + ImDrawCallback UserCallback; // 4-8 // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. + void* UserCallbackData; // 4-8 // The draw callback code can access this. + + ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed + + // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) + inline ImTextureID GetTexID() const { return TextureId; } +}; + +// Vertex index, default to 16-bit +// To allow large meshes with 16-bit indices: set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset in the renderer backend (recommended). +// To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in imconfig.h. +#ifndef ImDrawIdx +typedef unsigned short ImDrawIdx; +#endif + +// Vertex layout +#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT +struct ImDrawVert +{ + ImVec2 pos; + ImVec2 uv; + ImU32 col; +}; +#else +// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h +// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify integration in your engine. +// The type has to be described within the macro (you can either declare the struct or use a typedef). This is because ImVec2/ImU32 are likely not declared a the time you'd want to set your type up. +// NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM. +IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; +#endif + +// [Internal] For use by ImDrawList +struct ImDrawCmdHeader +{ + ImVec4 ClipRect; + ImTextureID TextureId; + unsigned int VtxOffset; +}; + +// [Internal] For use by ImDrawListSplitter +struct ImDrawChannel +{ + ImVector _CmdBuffer; + ImVector _IdxBuffer; +}; + + +// Split/Merge functions are used to split the draw list into different layers which can be drawn into out of order. +// This is used by the Columns/Tables API, so items of each column can be batched together in a same draw call. +struct ImDrawListSplitter +{ + int _Current; // Current channel number (0) + int _Count; // Number of active channels (1+) + ImVector _Channels; // Draw channels (not resized down so _Count might be < Channels.Size) + + inline ImDrawListSplitter() { memset(this, 0, sizeof(*this)); } + inline ~ImDrawListSplitter() { ClearFreeMemory(); } + inline void Clear() { _Current = 0; _Count = 1; } // Do not clear Channels[] so our allocations are reused next frame + IMGUI_API void ClearFreeMemory(); + IMGUI_API void Split(ImDrawList* draw_list, int count); + IMGUI_API void Merge(ImDrawList* draw_list); + IMGUI_API void SetCurrentChannel(ImDrawList* draw_list, int channel_idx); +}; + +// Flags for ImDrawList functions +// (Legacy: bit 0 must always correspond to ImDrawFlags_Closed to be backward compatible with old API using a bool. Bits 1..3 must be unused) +enum ImDrawFlags_ +{ + ImDrawFlags_None = 0, + ImDrawFlags_Closed = 1 << 0, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason) + ImDrawFlags_RoundCornersTopLeft = 1 << 4, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-left corner only (when rounding > 0.0f, we default to all corners). Was 0x01. + ImDrawFlags_RoundCornersTopRight = 1 << 5, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-right corner only (when rounding > 0.0f, we default to all corners). Was 0x02. + ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-left corner only (when rounding > 0.0f, we default to all corners). Was 0x04. + ImDrawFlags_RoundCornersBottomRight = 1 << 7, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-right corner only (when rounding > 0.0f, we default to all corners). Wax 0x08. + ImDrawFlags_RoundCornersNone = 1 << 8, // AddRect(), AddRectFilled(), PathRect(): disable rounding on all corners (when rounding > 0.0f). This is NOT zero, NOT an implicit flag! + ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, + ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, + ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, + ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight, + ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, + ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified. + ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone +}; + +// Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. +// It is however possible to temporarily alter flags between calls to ImDrawList:: functions. +enum ImDrawListFlags_ +{ + ImDrawListFlags_None = 0, + ImDrawListFlags_AntiAliasedLines = 1 << 0, // Enable anti-aliased lines/borders (*2 the number of triangles for 1.0f wide line or lines thin enough to be drawn using textures, otherwise *3 the number of triangles) + ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1, // Enable anti-aliased lines/borders using textures when possible. Require backend to render with bilinear filtering. + ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). + ImDrawListFlags_AllowVtxOffset = 1 << 3 // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. +}; + +// Draw command list +// This is the low-level list of polygons that ImGui:: functions are filling. At the end of the frame, +// all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. +// Each dear imgui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to +// access the current window draw list and draw custom primitives. +// You can interleave normal ImGui:: calls and adding primitives to the current draw list. +// In single viewport mode, top-left is == GetMainViewport()->Pos (generally 0,0), bottom-right is == GetMainViewport()->Pos+Size (generally io.DisplaySize). +// You are totally free to apply whatever transformation matrix to want to the data (depending on the use of the transformation you may want to apply it to ClipRect as well!) +// Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. +struct ImDrawList +{ + // This is what you have to render + ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. + ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those + ImVector VtxBuffer; // Vertex buffer. + ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + + // [Internal, used while building lists] + unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. + const ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) + const char* _OwnerName; // Pointer to owner window's name for debugging + ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImVector _ClipRectStack; // [Internal] + ImVector _TextureIdStack; // [Internal] + ImVector _Path; // [Internal] current path building + ImDrawCmdHeader _CmdHeader; // [Internal] template of active commands. Fields should match those of CmdBuffer.back(). + ImDrawListSplitter _Splitter; // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) + float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content + + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) + ImDrawList(const ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); _Data = shared_data; } + + ~ImDrawList() { _ClearFreeMemory(); } + IMGUI_API void PushClipRect(ImVec2 clip_rect_min, ImVec2 clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) + IMGUI_API void PushClipRectFullScreen(); + IMGUI_API void PopClipRect(); + IMGUI_API void PushTextureID(ImTextureID texture_id); + IMGUI_API void PopTextureID(); + inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } + inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } + + // Primitives + // - For rectangular primitives, "p_min" and "p_max" represent the upper-left and lower-right corners. + // - For circle primitives, use "num_segments == 0" to automatically calculate tessellation (preferred). + // In older versions (until Dear ImGui 1.77) the AddCircle functions defaulted to num_segments == 12. + // In future versions we will use textures to provide cheaper and higher-quality circles. + // Use AddNgon() and AddNgonFilled() functions if you need to guaranteed a specific number of sides. + IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); // a: upper-left, b: lower-right (== upper-left + size) + IMGUI_API void AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) + IMGUI_API void AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); + IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col); + IMGUI_API void AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col); + IMGUI_API void AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments = 0, float thickness = 1.0f); + IMGUI_API void AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments = 0); + IMGUI_API void AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness = 1.0f); + IMGUI_API void AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments); + IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); + IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); // Note: Anti-aliased filling requires points to be in clockwise order. + IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) + IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) + + // Image primitives + // - Read FAQ to understand what ImTextureID is. + // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle. + // - "uv_min" and "uv_max" represent the normalized texture coordinates to use for those corners. Using (0,0)->(1,1) texture coordinates will generally display the entire texture. + IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); + + // Stateful path API, add points then finish with PathFillConvex() or PathStroke() + inline void PathClear() { _Path.Size = 0; } + inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } + inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } + inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } // Note: Anti-aliased filling requires points to be in clockwise order. + inline void PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); _Path.Size = 0; } + IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); + IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle + IMGUI_API void PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0); // Cubic Bezier (4 control points) + IMGUI_API void PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments = 0); // Quadratic Bezier (3 control points) + IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, ImDrawFlags flags = 0); + + // Advanced + IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. + IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible + IMGUI_API ImDrawList* CloneOutput() const; // Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer. + + // Advanced: Channels + // - Use to split render into layers. By switching channels to can render out-of-order (e.g. submit FG primitives before BG primitives) + // - Use to minimize draw calls (e.g. if going back-and-forth between multiple clipping rectangles, prefer to append into separate channels then merge at the end) + // - FIXME-OBSOLETE: This API shouldn't have been in ImDrawList in the first place! + // Prefer using your own persistent instance of ImDrawListSplitter as you can stack them. + // Using the ImDrawList::ChannelsXXXX you cannot stack a split over another. + inline void ChannelsSplit(int count) { _Splitter.Split(this, count); } + inline void ChannelsMerge() { _Splitter.Merge(this); } + inline void ChannelsSetCurrent(int n) { _Splitter.SetCurrentChannel(this, n); } + + // Advanced: Primitives allocations + // - We render triangles (three vertices) + // - All primitives needs to be reserved via PrimReserve() beforehand. + IMGUI_API void PrimReserve(int idx_count, int vtx_count); + IMGUI_API void PrimUnreserve(int idx_count, int vtx_count); + IMGUI_API void PrimRect(const ImVec2& a, const ImVec2& b, ImU32 col); // Axis aligned rectangle (composed of two triangles) + IMGUI_API void PrimRectUV(const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col); + IMGUI_API void PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col); + inline void PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { _VtxWritePtr->pos = pos; _VtxWritePtr->uv = uv; _VtxWritePtr->col = col; _VtxWritePtr++; _VtxCurrentIdx++; } + inline void PrimWriteIdx(ImDrawIdx idx) { *_IdxWritePtr = idx; _IdxWritePtr++; } + inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } // Write vertex with unique index + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline void AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0) { AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments); } + inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } +#endif + + // [Internal helpers] + IMGUI_API void _ResetForNewFrame(); + IMGUI_API void _ClearFreeMemory(); + IMGUI_API void _PopUnusedDrawCmd(); + IMGUI_API void _OnChangedClipRect(); + IMGUI_API void _OnChangedTextureID(); + IMGUI_API void _OnChangedVtxOffset(); + IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; + IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); + IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); +}; + +// All draw data to render a Dear ImGui frame +// (NB: the style and the naming convention here is a little inconsistent, we currently preserve them for backward compatibility purpose, +// as this is one of the oldest structure exposed by the library! Basically, ImDrawList == CmdList) +struct ImDrawData +{ + bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. + int CmdListsCount; // Number of ImDrawList* to render + int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size + int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size + ImDrawList** CmdLists; // Array of ImDrawList* to render. The ImDrawList are owned by ImGuiContext and only pointed to from here. + ImVec2 DisplayPos; // Top-left position of the viewport to render (== top-left of the orthogonal projection matrix to use) (== GetMainViewport()->Pos for the main viewport, == (0.0) in most single-viewport applications) + ImVec2 DisplaySize; // Size of the viewport to render (== GetMainViewport()->Size for the main viewport, == io.DisplaySize in most single-viewport applications) + ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Based on io.DisplayFramebufferScale. Generally (1,1) on normal display, (2,2) on OSX with Retina display. + + // Functions + ImDrawData() { Clear(); } + void Clear() { memset(this, 0, sizeof(*this)); } // The ImDrawList are owned by ImGuiContext! + IMGUI_API void DeIndexAllBuffers(); // Helper to convert all buffers from indexed to non-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! + IMGUI_API void ScaleClipRects(const ImVec2& fb_scale); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than Dear ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +}; + +//----------------------------------------------------------------------------- +// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) +//----------------------------------------------------------------------------- + +struct ImFontConfig +{ + void* FontData; // // TTF/OTF data + int FontDataSize; // // TTF/OTF data size + bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + int FontNo; // 0 // Index of font within TTF/OTF file + float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). + int OversampleH; // 3 // Rasterize at higher quality for sub-pixel positioning. Note the difference between 2 and 3 is minimal so you can reduce this to 2 to save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. + int OversampleV; // 1 // Rasterize at higher quality for sub-pixel positioning. This is not really useful as we don't use sub-pixel positions on the Y axis. + bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. + ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. + const ImWchar* GlyphRanges; // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font + float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs + bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. + unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. + float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. + ImWchar EllipsisChar; // -1 // Explicitly specify unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. + + // [Internal] + char Name[40]; // Name (strictly to ease debugging) + ImFont* DstFont; + + IMGUI_API ImFontConfig(); +}; + +// Hold rendering data for one glyph. +// (Note: some language parsers may fail to convert the 31+1 bitfield members, in this case maybe drop store a single u32 or we can rework this) +struct ImFontGlyph +{ + unsigned int Colored : 1; // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops) + unsigned int Visible : 1; // Flag to indicate glyph has no visible pixels (e.g. space). Allow early out when rendering. + unsigned int Codepoint : 30; // 0x0000..0x10FFFF + float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) + float X0, Y0, X1, Y1; // Glyph corners + float U0, V0, U1, V1; // Texture coordinates +}; + +// Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges(). +// This is essentially a tightly packed of vector of 64k booleans = 8KB storage. +struct ImFontGlyphRangesBuilder +{ + ImVector UsedChars; // Store 1-bit per Unicode code point (0=unused, 1=used) + + ImFontGlyphRangesBuilder() { Clear(); } + inline void Clear() { int size_in_bytes = (IM_UNICODE_CODEPOINT_MAX + 1) / 8; UsedChars.resize(size_in_bytes / (int)sizeof(ImU32)); memset(UsedChars.Data, 0, (size_t)size_in_bytes); } + inline bool GetBit(size_t n) const { int off = (int)(n >> 5); ImU32 mask = 1u << (n & 31); return (UsedChars[off] & mask) != 0; } // Get bit n in the array + inline void SetBit(size_t n) { int off = (int)(n >> 5); ImU32 mask = 1u << (n & 31); UsedChars[off] |= mask; } // Set bit n in the array + inline void AddChar(ImWchar c) { SetBit(c); } // Add character + IMGUI_API void AddText(const char* text, const char* text_end = NULL); // Add string (each character of the UTF-8 string are added) + IMGUI_API void AddRanges(const ImWchar* ranges); // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault()) to force add all of ASCII/Latin+Ext + IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges +}; + +// See ImFontAtlas::AddCustomRectXXX functions. +struct ImFontAtlasCustomRect +{ + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned short X, Y; // Output // Packed position in Atlas + unsigned int GlyphID; // Input // For custom font glyphs only (ID < 0x110000) + float GlyphAdvanceX; // Input // For custom font glyphs only: glyph xadvance + ImVec2 GlyphOffset; // Input // For custom font glyphs only: glyph display offset + ImFont* Font; // Input // For custom font glyphs only: target font + ImFontAtlasCustomRect() { Width = Height = 0; X = Y = 0xFFFF; GlyphID = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } +}; + +// Flags for ImFontAtlas build +enum ImFontAtlasFlags_ +{ + ImFontAtlasFlags_None = 0, + ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two + ImFontAtlasFlags_NoMouseCursors = 1 << 1, // Don't build software mouse cursors into the atlas (save a little texture memory) + ImFontAtlasFlags_NoBakedLines = 1 << 2 // Don't build thick line textures into the atlas (save a little texture memory). The AntiAliasedLinesUseTex features uses them, otherwise they will be rendered using polygons (more expensive for CPU/GPU). +}; + +// Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: +// - One or more fonts. +// - Custom graphics data needed to render the shapes needed by Dear ImGui. +// - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas). +// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api. +// - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you. +// - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. +// - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples) +// - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. +// This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. +// Common pitfalls: +// - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the +// atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. +// - Important: By default, AddFontFromMemoryTTF() takes ownership of the data. Even though we are not writing to it, we will free the pointer on destruction. +// You can set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed, +// - Even though many functions are suffixed with "TTF", OTF data is supported just as well. +// - This is an old API and it is currently awkward for those and and various other reasons! We will address them in the future! +struct ImFontAtlas +{ + IMGUI_API ImFontAtlas(); + IMGUI_API ~ImFontAtlas(); + IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); + IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. + IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. + IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + IMGUI_API void ClearInputData(); // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. + IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void ClearFonts(); // Clear output font data (glyphs storage, UV coordinates). + IMGUI_API void Clear(); // Clear all input and output. + + // Build atlas, retrieve pixel data. + // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // The pitch is always = Width * BytesPerPixels (1 or 4) + // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into + // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + bool IsBuilt() const { return Fonts.Size > 0 && (TexPixelsAlpha8 != NULL || TexPixelsRGBA32 != NULL); } + void SetTexID(ImTextureID id) { TexID = id; } + + //------------------------------------------- + // Glyph Ranges + //------------------------------------------- + + // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) + // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. + // NB: Consider using ImFontGlyphRangesBuilder to build glyph ranges from textual data. + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters + IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChineseFull(); // Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChineseSimplifiedCommon();// Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese + IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters + IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters + IMGUI_API const ImWchar* GetGlyphRangesVietnamese(); // Default + Vietnamese characters + + //------------------------------------------- + // [BETA] Custom Rectangles/Glyphs API + //------------------------------------------- + + // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. + // - After calling Build(), you can query the rectangle position and render your pixels. + // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of prefered texture format. + // - You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), + // so you can render e.g. custom colorful icons and use them as regular glyphs. + // - Read docs/FONTS.md for more details about using colorful icons. + // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. + IMGUI_API int AddCustomRectRegular(int width, int height); + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); + ImFontAtlasCustomRect* GetCustomRectByIndex(int index) { IM_ASSERT(index >= 0); return &CustomRects[index]; } + + // [Internal] + IMGUI_API void CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; + IMGUI_API bool GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); + + //------------------------------------------- + // Members + //------------------------------------------- + + ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) + ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0. + bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. + + // [Internal] + // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. + bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format. + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; // Texture width calculated during Build(). + int TexHeight; // Texture height calculated during Build(). + ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. + ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. + ImVector ConfigData; // Configuration data + ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + + // [Internal] Font builder + const ImFontBuilderIO* FontBuilderIO; // Opaque interface to a font builder (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). + unsigned int FontBuilderFlags; // Shared flags (for all fonts) for custom font builder. THIS IS BUILD IMPLEMENTATION DEPENDENT. Per-font override is also available in ImFontConfig. + + // [Internal] Packing data + int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors + int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ + typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ +#endif +}; + +// Font runtime data and rendering +// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). +struct ImFont +{ + // Members: Hot ~20/24 bytes (for CalcTextSize) + ImVector IndexAdvanceX; // 12-16 // out // // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this this info, and are often bottleneck in large UI). + float FallbackAdvanceX; // 4 // out // = FallbackGlyph->AdvanceX + float FontSize; // 4 // in // // Height of characters/line, set during loading (don't change after loading) + + // Members: Hot ~28/40 bytes (for CalcTextSize + render loop) + ImVector IndexLookup; // 12-16 // out // // Sparse. Index glyphs by Unicode code-point. + ImVector Glyphs; // 12-16 // out // // All glyphs. + const ImFontGlyph* FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) + + // Members: Cold ~32/40 bytes + ImFontAtlas* ContainerAtlas; // 4-8 // out // // What we has been loaded into + const ImFontConfig* ConfigData; // 4-8 // in // // Pointer within ContainerAtlas->ConfigData + short ConfigDataCount; // 2 // in // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. + ImWchar FallbackChar; // 2 // in // = '?' // Replacement character if a glyph isn't found. Only set via SetFallbackChar() + ImWchar EllipsisChar; // 2 // out // = -1 // Character used for ellipsis rendering. + bool DirtyLookupTables; // 1 // out // + float Scale; // 4 // in // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetWindowFontScale() + float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] + int MetricsTotalSurface;// 4 // out // // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + ImU8 Used4kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/4096/8]; // 2 bytes if ImWchar=ImWchar16, 34 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + + // Methods + IMGUI_API ImFont(); + IMGUI_API ~ImFont(); + IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; + IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c) const; + float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + bool IsLoaded() const { return ContainerAtlas != NULL; } + const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } + + // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. + // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, ImWchar c) const; + IMGUI_API void RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + + // [Internal] Don't use! + IMGUI_API void BuildLookupTable(); + IMGUI_API void ClearOutputData(); + IMGUI_API void GrowIndex(int new_size); + IMGUI_API void AddGlyph(const ImFontConfig* src_cfg, ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); + IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. + IMGUI_API void SetGlyphVisible(ImWchar c, bool visible); + IMGUI_API void SetFallbackChar(ImWchar c); + IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); +}; + +//----------------------------------------------------------------------------- +// [SECTION] Viewports +//----------------------------------------------------------------------------- + +// Flags stored in ImGuiViewport::Flags +enum ImGuiViewportFlags_ +{ + ImGuiViewportFlags_None = 0, + ImGuiViewportFlags_IsPlatformWindow = 1 << 0, // Represent a Platform Window + ImGuiViewportFlags_IsPlatformMonitor = 1 << 1, // Represent a Platform Monitor (unused yet) + ImGuiViewportFlags_OwnedByApp = 1 << 2 // Platform Window: is created/managed by the application (rather than a dear imgui backend) +}; + +// - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. +// - In 'docking' branch with multi-viewport enabled, we extend this concept to have multiple active viewports. +// - In the future we will extend this concept further to also represent Platform Monitor and support a "no main platform window" operation mode. +// - About Main Area vs Work Area: +// - Main Area = entire viewport. +// - Work Area = entire viewport minus sections used by main menu bars (for platform windows), or by task bar (for platform monitor). +// - Windows are generally trying to stay within the Work Area of their host viewport. +struct ImGuiViewport +{ + ImGuiViewportFlags Flags; // See ImGuiViewportFlags_ + ImVec2 Pos; // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates) + ImVec2 Size; // Main Area: Size of the viewport. + ImVec2 WorkPos; // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos) + ImVec2 WorkSize; // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size) + + ImGuiViewport() { memset(this, 0, sizeof(*this)); } + + // Helpers + ImVec2 GetCenter() const { return ImVec2(Pos.x + Size.x * 0.5f, Pos.y + Size.y * 0.5f); } + ImVec2 GetWorkCenter() const { return ImVec2(WorkPos.x + WorkSize.x * 0.5f, WorkPos.y + WorkSize.y * 0.5f); } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Obsolete functions and types +// (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) +// Please keep your copy of dear imgui up to date! Occasionally set '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in imconfig.h to stay ahead. +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui +{ + // OBSOLETED in 1.81 (from February 2021) + IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // Helper to calculate size from items_count and height_in_items + static inline bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0, 0)) { return BeginListBox(label, size); } + static inline void ListBoxFooter() { EndListBox(); } + // OBSOLETED in 1.79 (from August 2020) + static inline void OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1) { OpenPopupOnItemClick(str_id, mb); } // Bool return value removed. Use IsWindowAppearing() in BeginPopup() instead. Renamed in 1.77, renamed back in 1.79. Sorry! + // OBSOLETED in 1.78 (from June 2020) + // Old drag/sliders functions that took a 'float power = 1.0' argument instead of flags. + // For shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. + IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power); + IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power); + static inline bool DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); } + static inline bool DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); } + static inline bool DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power); } + static inline bool DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power) { return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); } + IMGUI_API bool SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power); + IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_min, const void* p_max, const char* format, float power); + static inline bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power) { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); } + static inline bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); } + static inline bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); } + static inline bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); } + // OBSOLETED in 1.77 (from June 2020) + static inline bool BeginPopupContextWindow(const char* str_id, ImGuiMouseButton mb, bool over_items) { return BeginPopupContextWindow(str_id, mb | (over_items ? 0 : ImGuiPopupFlags_NoOpenOverItems)); } + // OBSOLETED in 1.72 (from April 2019) + static inline void TreeAdvanceToLabelPos() { SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()); } + // OBSOLETED in 1.71 (from June 2019) + static inline void SetNextTreeNodeOpen(bool open, ImGuiCond cond = 0) { SetNextItemOpen(open, cond); } + // OBSOLETED in 1.70 (from May 2019) + static inline float GetContentRegionAvailWidth() { return GetContentRegionAvail().x; } + // OBSOLETED in 1.69 (from Mar 2019) + static inline ImDrawList* GetOverlayDrawList() { return GetForegroundDrawList(); } +} + +// OBSOLETED in 1.82 (from Mars 2021): flags for AddRect(), AddRectFilled(), AddImageRounded(), PathRect() +typedef ImDrawFlags ImDrawCornerFlags; +enum ImDrawCornerFlags_ +{ + ImDrawCornerFlags_None = ImDrawFlags_RoundCornersNone, // Was == 0 prior to 1.82, this is now == ImDrawFlags_RoundCornersNone which is != 0 and not implicit + ImDrawCornerFlags_TopLeft = ImDrawFlags_RoundCornersTopLeft, // Was == 0x01 (1 << 0) prior to 1.82. Order matches ImDrawFlags_NoRoundCorner* flag (we exploit this internally). + ImDrawCornerFlags_TopRight = ImDrawFlags_RoundCornersTopRight, // Was == 0x02 (1 << 1) prior to 1.82. + ImDrawCornerFlags_BotLeft = ImDrawFlags_RoundCornersBottomLeft, // Was == 0x04 (1 << 2) prior to 1.82. + ImDrawCornerFlags_BotRight = ImDrawFlags_RoundCornersBottomRight, // Was == 0x08 (1 << 3) prior to 1.82. + ImDrawCornerFlags_All = ImDrawFlags_RoundCornersAll, // Was == 0x0F prior to 1.82 + ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, + ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, + ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, + ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight +}; + +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//----------------------------------------------------------------------------- + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +// Include imgui_user.h at the end of imgui.h (convenient for user to only explicitly include vanilla imgui.h) +#ifdef IMGUI_INCLUDE_IMGUI_USER_H +#include "imgui_user.h" +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/vendor/librw/skeleton/imgui/imgui_demo.cpp b/vendor/librw/skeleton/imgui/imgui_demo.cpp new file mode 100644 index 0000000..5437301 --- /dev/null +++ b/vendor/librw/skeleton/imgui/imgui_demo.cpp @@ -0,0 +1,7725 @@ +// dear imgui, v1.83 +// (demo code) + +// Help: +// - Read FAQ at http://dearimgui.org/faq +// - Newcomers, read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. +// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. +// Read imgui.cpp for more details, documentation and comments. +// Get the latest version at https://github.com/ocornut/imgui + +// Message to the person tempted to delete this file when integrating Dear ImGui into their codebase: +// Do NOT remove this file from your project! Think again! It is the most useful reference code that you and other +// coders will want to refer to and call. Have the ImGui::ShowDemoWindow() function wired in an always-available +// debug menu of your game/app! Removing this file from your project is hindering access to documentation for everyone +// in your team, likely leading you to poorer usage of the library. +// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). +// If you want to link core Dear ImGui in your shipped builds but want a thorough guarantee that the demo will not be +// linked, you can setup your imconfig.h with #define IMGUI_DISABLE_DEMO_WINDOWS and those functions will be empty. +// In another situation, whenever you have Dear ImGui available you probably want this to be available for reference. +// Thank you, +// -Your beloved friend, imgui_demo.cpp (which you won't delete) + +// Message to beginner C/C++ programmers about the meaning of the 'static' keyword: +// In this demo code, we frequently use 'static' variables inside functions. A static variable persists across calls, +// so it is essentially like a global variable but declared inside the scope of the function. We do this as a way to +// gather code and data in the same place, to make the demo source code faster to read, faster to write, and smaller +// in size. It also happens to be a convenient way of storing simple UI related information as long as your function +// doesn't need to be reentrant or used in multiple threads. This might be a pattern you will want to use in your code, +// but most of the real data you would be editing is likely going to be stored outside your functions. + +// The Demo code in this file is designed to be easy to copy-and-paste into your application! +// Because of this: +// - We never omit the ImGui:: prefix when calling functions, even though most code here is in the same namespace. +// - We try to declare static variables in the local scope, as close as possible to the code using them. +// - We never use any of the helpers/facilities used internally by Dear ImGui, unless available in the public API. +// - We never use maths operators on ImVec2/ImVec4. For our other sources files we use them, and they are provided +// by imgui_internal.h using the IMGUI_DEFINE_MATH_OPERATORS define. For your own sources file they are optional +// and require you either enable those, either provide your own via IM_VEC2_CLASS_EXTRA in imconfig.h. +// Because we can't assume anything about your support of maths operators, we cannot use them in imgui_demo.cpp. + +// Navigating this file: +// - In Visual Studio IDE: CTRL+comma ("Edit.NavigateTo") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. + +/* + +Index of this file: + +// [SECTION] Forward Declarations, Helpers +// [SECTION] Demo Window / ShowDemoWindow() +// - sub section: ShowDemoWindowWidgets() +// - sub section: ShowDemoWindowLayout() +// - sub section: ShowDemoWindowPopups() +// - sub section: ShowDemoWindowTables() +// - sub section: ShowDemoWindowMisc() +// [SECTION] About Window / ShowAboutWindow() +// [SECTION] Font Viewer / ShowFontAtlas() +// [SECTION] Style Editor / ShowStyleEditor() +// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() +// [SECTION] Example App: Debug Console / ShowExampleAppConsole() +// [SECTION] Example App: Debug Log / ShowExampleAppLog() +// [SECTION] Example App: Simple Layout / ShowExampleAppLayout() +// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() +// [SECTION] Example App: Long Text / ShowExampleAppLongText() +// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() +// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() +// [SECTION] Example App: Simple overlay / ShowExampleAppSimpleOverlay() +// [SECTION] Example App: Fullscreen window / ShowExampleAppFullscreen() +// [SECTION] Example App: Manipulating window titles / ShowExampleAppWindowTitles() +// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() +// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +// System includes +#include // toupper +#include // INT_MIN, INT_MAX +#include // sqrtf, powf, cosf, sinf, floorf, ceilf +#include // vsnprintf, sscanf, printf +#include // NULL, malloc, free, atoi +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // warning: 'xx' is deprecated: The POSIX name for this.. // for strdup used in demo code (so user can copy & paste the code) +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type +#pragma clang diagnostic ignored "-Wformat-security" // warning: format string is not a string literal +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning: declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used // we define snprintf/vsnprintf on Windows so they are available, but not always used. +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning: macro name is a reserved identifier +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat-security" // warning: format string is not a string literal (potentially insecure) +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wmisleading-indentation" // [__GNUC__ >= 6] warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. +#endif + +// Play it nice with Windows users (Update: May 2018, Notepad now supports Unix-style carriage returns!) +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +// Helpers +#if defined(_MSC_VER) && !defined(snprintf) +#define snprintf _snprintf +#endif +#if defined(_MSC_VER) && !defined(vsnprintf) +#define vsnprintf _vsnprintf +#endif + +// Format specifiers, printing 64-bit hasn't been decently standardized... +// In a real application you should be using PRId64 and PRIu64 from (non-windows) and on Windows define them yourself. +#ifdef _MSC_VER +#define IM_PRId64 "I64d" +#define IM_PRIu64 "I64u" +#else +#define IM_PRId64 "lld" +#define IM_PRIu64 "llu" +#endif + +// Helpers macros +// We normally try to not use many helpers in imgui_demo.cpp in order to make code easier to copy and paste, +// but making an exception here as those are largely simplifying code... +// In other imgui sources we can use nicer internal functions from imgui_internal.h (ImMin/ImMax) but not in the demo. +#define IM_MIN(A, B) (((A) < (B)) ? (A) : (B)) +#define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B)) +#define IM_CLAMP(V, MN, MX) ((V) < (MN) ? (MN) : (V) > (MX) ? (MX) : (V)) + +// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall +#ifndef IMGUI_CDECL +#ifdef _MSC_VER +#define IMGUI_CDECL __cdecl +#else +#define IMGUI_CDECL +#endif +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Forward Declarations, Helpers +//----------------------------------------------------------------------------- + +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) + +// Forward Declarations +static void ShowExampleAppDocuments(bool* p_open); +static void ShowExampleAppMainMenuBar(); +static void ShowExampleAppConsole(bool* p_open); +static void ShowExampleAppLog(bool* p_open); +static void ShowExampleAppLayout(bool* p_open); +static void ShowExampleAppPropertyEditor(bool* p_open); +static void ShowExampleAppLongText(bool* p_open); +static void ShowExampleAppAutoResize(bool* p_open); +static void ShowExampleAppConstrainedResize(bool* p_open); +static void ShowExampleAppSimpleOverlay(bool* p_open); +static void ShowExampleAppFullscreen(bool* p_open); +static void ShowExampleAppWindowTitles(bool* p_open); +static void ShowExampleAppCustomRendering(bool* p_open); +static void ShowExampleMenuFile(); + +// Helper to display a little (?) mark which shows a tooltip when hovered. +// In your own code you may want to display an actual icon if you are using a merged icon fonts (see docs/FONTS.md) +static void HelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +// Helper to display basic user controls. +void ImGui::ShowUserGuide() +{ + ImGuiIO& io = ImGui::GetIO(); + ImGui::BulletText("Double-click on title bar to collapse window."); + ImGui::BulletText( + "Click and drag on lower corner to resize window\n" + "(double-click to auto fit window to its contents)."); + ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); + ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + if (io.FontAllowUserScaling) + ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); + ImGui::BulletText("While inputing text:\n"); + ImGui::Indent(); + ImGui::BulletText("CTRL+Left/Right to word jump."); + ImGui::BulletText("CTRL+A or double-click to select all."); + ImGui::BulletText("CTRL+X/C/V to use clipboard cut/copy/paste."); + ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); + ImGui::BulletText("ESCAPE to revert."); + ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract."); + ImGui::Unindent(); + ImGui::BulletText("With keyboard navigation enabled:"); + ImGui::Indent(); + ImGui::BulletText("Arrow keys to navigate."); + ImGui::BulletText("Space to activate a widget."); + ImGui::BulletText("Return to input text into a widget."); + ImGui::BulletText("Escape to deactivate a widget, close popup, exit child window."); + ImGui::BulletText("Alt to jump to the menu layer of a window."); + ImGui::BulletText("CTRL+Tab to select a window."); + ImGui::Unindent(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Demo Window / ShowDemoWindow() +//----------------------------------------------------------------------------- +// - ShowDemoWindowWidgets() +// - ShowDemoWindowLayout() +// - ShowDemoWindowPopups() +// - ShowDemoWindowTables() +// - ShowDemoWindowColumns() +// - ShowDemoWindowMisc() +//----------------------------------------------------------------------------- + +// We split the contents of the big ShowDemoWindow() function into smaller functions +// (because the link time of very large functions grow non-linearly) +static void ShowDemoWindowWidgets(); +static void ShowDemoWindowLayout(); +static void ShowDemoWindowPopups(); +static void ShowDemoWindowTables(); +static void ShowDemoWindowColumns(); +static void ShowDemoWindowMisc(); + +// Demonstrate most Dear ImGui features (this is big function!) +// You may execute this function to experiment with the UI and understand what it does. +// You may then search for keywords in the code when you are interested by a specific feature. +void ImGui::ShowDemoWindow(bool* p_open) +{ + // Exceptionally add an extra assert here for people confused about initial Dear ImGui setup + // Most ImGui functions would normally just crash if the context is missing. + IM_ASSERT(ImGui::GetCurrentContext() != NULL && "Missing dear imgui context. Refer to examples app!"); + + // Examples Apps (accessible from the "Examples" menu) + static bool show_app_main_menu_bar = false; + static bool show_app_documents = false; + + static bool show_app_console = false; + static bool show_app_log = false; + static bool show_app_layout = false; + static bool show_app_property_editor = false; + static bool show_app_long_text = false; + static bool show_app_auto_resize = false; + static bool show_app_constrained_resize = false; + static bool show_app_simple_overlay = false; + static bool show_app_fullscreen = false; + static bool show_app_window_titles = false; + static bool show_app_custom_rendering = false; + + if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); + if (show_app_documents) ShowExampleAppDocuments(&show_app_documents); + + if (show_app_console) ShowExampleAppConsole(&show_app_console); + if (show_app_log) ShowExampleAppLog(&show_app_log); + if (show_app_layout) ShowExampleAppLayout(&show_app_layout); + if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); + if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); + if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); + if (show_app_simple_overlay) ShowExampleAppSimpleOverlay(&show_app_simple_overlay); + if (show_app_fullscreen) ShowExampleAppFullscreen(&show_app_fullscreen); + if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); + if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); + + // Dear ImGui Apps (accessible from the "Tools" menu) + static bool show_app_metrics = false; + static bool show_app_style_editor = false; + static bool show_app_about = false; + + if (show_app_metrics) { ImGui::ShowMetricsWindow(&show_app_metrics); } + if (show_app_about) { ImGui::ShowAboutWindow(&show_app_about); } + if (show_app_style_editor) + { + ImGui::Begin("Dear ImGui Style Editor", &show_app_style_editor); + ImGui::ShowStyleEditor(); + ImGui::End(); + } + + // Demonstrate the various window flags. Typically you would just use the default! + static bool no_titlebar = false; + static bool no_scrollbar = false; + static bool no_menu = false; + static bool no_move = false; + static bool no_resize = false; + static bool no_collapse = false; + static bool no_close = false; + static bool no_nav = false; + static bool no_background = false; + static bool no_bring_to_front = false; + + ImGuiWindowFlags window_flags = 0; + if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; + if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; + if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar; + if (no_move) window_flags |= ImGuiWindowFlags_NoMove; + if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; + if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; + if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; + if (no_background) window_flags |= ImGuiWindowFlags_NoBackground; + if (no_bring_to_front) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; + if (no_close) p_open = NULL; // Don't pass our bool* to Begin + + // We specify a default position/size in case there's no data in the .ini file. + // We only do it to make the demo applications a little more welcoming, but typically this isn't required. + const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 650, main_viewport->WorkPos.y + 20), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); + + // Main body of the Demo window starts here. + if (!ImGui::Begin("Dear ImGui Demo", p_open, window_flags)) + { + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + + // Most "big" widgets share a common width settings by default. See 'Demo->Layout->Widgets Width' for details. + + // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align) + //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); + + // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. + ImGui::PushItemWidth(ImGui::GetFontSize() * -12); + + // Menu Bar + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) + { + ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); + ImGui::MenuItem("Console", NULL, &show_app_console); + ImGui::MenuItem("Log", NULL, &show_app_log); + ImGui::MenuItem("Simple layout", NULL, &show_app_layout); + ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); + ImGui::MenuItem("Long text display", NULL, &show_app_long_text); + ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); + ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); + ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay); + ImGui::MenuItem("Fullscreen window", NULL, &show_app_fullscreen); + ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); + ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); + ImGui::MenuItem("Documents", NULL, &show_app_documents); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Tools")) + { + ImGui::MenuItem("Metrics/Debugger", NULL, &show_app_metrics); + ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor); + ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + ImGui::Text("dear imgui says hello. (%s)", IMGUI_VERSION); + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("Help")) + { + ImGui::Text("ABOUT THIS DEMO:"); + ImGui::BulletText("Sections below are demonstrating many aspects of the library."); + ImGui::BulletText("The \"Examples\" menu above leads to more demo contents."); + ImGui::BulletText("The \"Tools\" menu above gives access to: About Box, Style Editor,\n" + "and Metrics/Debugger (general purpose Dear ImGui debugging tool)."); + ImGui::Separator(); + + ImGui::Text("PROGRAMMER GUIDE:"); + ImGui::BulletText("See the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!"); + ImGui::BulletText("See comments in imgui.cpp."); + ImGui::BulletText("See example applications in the examples/ folder."); + ImGui::BulletText("Read the FAQ at http://www.dearimgui.org/faq/"); + ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls."); + ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls."); + ImGui::Separator(); + + ImGui::Text("USER GUIDE:"); + ImGui::ShowUserGuide(); + } + + if (ImGui::CollapsingHeader("Configuration")) + { + ImGuiIO& io = ImGui::GetIO(); + + if (ImGui::TreeNode("Configuration##2")) + { + ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); + ImGui::SameLine(); HelpMarker("Enable keyboard controls."); + ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", &io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); + ImGui::SameLine(); HelpMarker("Enable gamepad controls. Require backend to set io.BackendFlags |= ImGuiBackendFlags_HasGamepad.\n\nRead instructions in imgui.cpp for details."); + ImGui::CheckboxFlags("io.ConfigFlags: NavEnableSetMousePos", &io.ConfigFlags, ImGuiConfigFlags_NavEnableSetMousePos); + ImGui::SameLine(); HelpMarker("Instruct navigation to move the mouse cursor. See comment for ImGuiConfigFlags_NavEnableSetMousePos."); + ImGui::CheckboxFlags("io.ConfigFlags: NoMouse", &io.ConfigFlags, ImGuiConfigFlags_NoMouse); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) + { + // The "NoMouse" option can get us stuck with a disabled mouse! Let's provide an alternative way to fix it: + if (fmodf((float)ImGui::GetTime(), 0.40f) < 0.20f) + { + ImGui::SameLine(); + ImGui::Text("<>"); + } + if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Space))) + io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; + } + ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", &io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); + ImGui::SameLine(); HelpMarker("Instruct backend to not alter mouse cursor shape and visibility."); + ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); + ImGui::SameLine(); HelpMarker("Enable blinking cursor (optional as some users consider it to be distracting)"); + ImGui::Checkbox("io.ConfigDragClickToInputText", &io.ConfigDragClickToInputText); + ImGui::SameLine(); HelpMarker("Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving)."); + ImGui::Checkbox("io.ConfigWindowsResizeFromEdges", &io.ConfigWindowsResizeFromEdges); + ImGui::SameLine(); HelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback."); + ImGui::Checkbox("io.ConfigWindowsMoveFromTitleBarOnly", &io.ConfigWindowsMoveFromTitleBarOnly); + ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); + ImGui::SameLine(); HelpMarker("Instruct Dear ImGui to render a mouse cursor itself. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + ImGui::Text("Also see Style->Rendering for rendering options."); + ImGui::TreePop(); + ImGui::Separator(); + } + + if (ImGui::TreeNode("Backend Flags")) + { + HelpMarker( + "Those flags are set by the backends (imgui_impl_xxx files) to specify their capabilities.\n" + "Here we expose then as read-only fields to avoid breaking interactions with your backend."); + + // Make a local copy to avoid modifying actual backend flags. + ImGuiBackendFlags backend_flags = io.BackendFlags; + ImGui::CheckboxFlags("io.BackendFlags: HasGamepad", &backend_flags, ImGuiBackendFlags_HasGamepad); + ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", &backend_flags, ImGuiBackendFlags_HasMouseCursors); + ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", &backend_flags, ImGuiBackendFlags_HasSetMousePos); + ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", &backend_flags, ImGuiBackendFlags_RendererHasVtxOffset); + ImGui::TreePop(); + ImGui::Separator(); + } + + if (ImGui::TreeNode("Style")) + { + HelpMarker("The same contents can be accessed in 'Tools->Style Editor' or by calling the ShowStyleEditor() function."); + ImGui::ShowStyleEditor(); + ImGui::TreePop(); + ImGui::Separator(); + } + + if (ImGui::TreeNode("Capture/Logging")) + { + HelpMarker( + "The logging API redirects all text output so you can easily capture the content of " + "a window or a block. Tree nodes can be automatically expanded.\n" + "Try opening any of the contents below in this window and then click one of the \"Log To\" button."); + ImGui::LogButtons(); + + HelpMarker("You can also call ImGui::LogText() to output directly to the log without a visual output."); + if (ImGui::Button("Copy \"Hello, world!\" to clipboard")) + { + ImGui::LogToClipboard(); + ImGui::LogText("Hello, world!"); + ImGui::LogFinish(); + } + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Window options")) + { + if (ImGui::BeginTable("split", 3)) + { + ImGui::TableNextColumn(); ImGui::Checkbox("No titlebar", &no_titlebar); + ImGui::TableNextColumn(); ImGui::Checkbox("No scrollbar", &no_scrollbar); + ImGui::TableNextColumn(); ImGui::Checkbox("No menu", &no_menu); + ImGui::TableNextColumn(); ImGui::Checkbox("No move", &no_move); + ImGui::TableNextColumn(); ImGui::Checkbox("No resize", &no_resize); + ImGui::TableNextColumn(); ImGui::Checkbox("No collapse", &no_collapse); + ImGui::TableNextColumn(); ImGui::Checkbox("No close", &no_close); + ImGui::TableNextColumn(); ImGui::Checkbox("No nav", &no_nav); + ImGui::TableNextColumn(); ImGui::Checkbox("No background", &no_background); + ImGui::TableNextColumn(); ImGui::Checkbox("No bring to front", &no_bring_to_front); + ImGui::EndTable(); + } + } + + // All demo contents + ShowDemoWindowWidgets(); + ShowDemoWindowLayout(); + ShowDemoWindowPopups(); + ShowDemoWindowTables(); + ShowDemoWindowMisc(); + + // End of ShowDemoWindow() + ImGui::PopItemWidth(); + ImGui::End(); +} + +static void ShowDemoWindowWidgets() +{ + if (!ImGui::CollapsingHeader("Widgets")) + return; + + if (ImGui::TreeNode("Basic")) + { + static int clicked = 0; + if (ImGui::Button("Button")) + clicked++; + if (clicked & 1) + { + ImGui::SameLine(); + ImGui::Text("Thanks for clicking me!"); + } + + static bool check = true; + ImGui::Checkbox("checkbox", &check); + + static int e = 0; + ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); + ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); + ImGui::RadioButton("radio c", &e, 2); + + // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. + for (int i = 0; i < 7; i++) + { + if (i > 0) + ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.8f, 0.8f)); + ImGui::Button("Click"); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + + // Use AlignTextToFramePadding() to align text baseline to the baseline of framed widgets elements + // (otherwise a Text+SameLine+Button sequence will have the text a little too high by default!) + // See 'Demo->Layout->Text Baseline Alignment' for details. + ImGui::AlignTextToFramePadding(); + ImGui::Text("Hold to repeat:"); + ImGui::SameLine(); + + // Arrow buttons with Repeater + static int counter = 0; + float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::PushButtonRepeat(true); + if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { counter--; } + ImGui::SameLine(0.0f, spacing); + if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { counter++; } + ImGui::PopButtonRepeat(); + ImGui::SameLine(); + ImGui::Text("%d", counter); + + ImGui::Text("Hover over me"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip"); + + ImGui::SameLine(); + ImGui::Text("- or me"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("I am a fancy tooltip"); + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); + ImGui::EndTooltip(); + } + + ImGui::Separator(); + + ImGui::LabelText("label", "Value"); + + { + // Using the _simplified_ one-liner Combo() api here + // See "Combo" section for examples of how to use the more flexible BeginCombo()/EndCombo() api. + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIIIIII", "JJJJ", "KKKKKKK" }; + static int item_current = 0; + ImGui::Combo("combo", &item_current, items, IM_ARRAYSIZE(items)); + ImGui::SameLine(); HelpMarker( + "Using the simplified one-liner Combo API here.\nRefer to the \"Combo\" section below for an explanation of how to use the more flexible and general BeginCombo/EndCombo API."); + } + + { + // To wire InputText() with std::string or any other custom string type, + // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file. + static char str0[128] = "Hello, world!"; + ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); + ImGui::SameLine(); HelpMarker( + "USER:\n" + "Hold SHIFT or use mouse to select text.\n" + "CTRL+Left/Right to word jump.\n" + "CTRL+A or double-click to select all.\n" + "CTRL+X,CTRL+C,CTRL+V clipboard.\n" + "CTRL+Z,CTRL+Y undo/redo.\n" + "ESCAPE to revert.\n\n" + "PROGRAMMER:\n" + "You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputText() " + "to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example (this is not demonstrated " + "in imgui_demo.cpp)."); + + static char str1[128] = ""; + ImGui::InputTextWithHint("input text (w/ hint)", "enter text here", str1, IM_ARRAYSIZE(str1)); + + static int i0 = 123; + ImGui::InputInt("input int", &i0); + ImGui::SameLine(); HelpMarker( + "You can apply arithmetic operators +,*,/ on numerical values.\n" + " e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\n" + "Use +- to subtract."); + + static float f0 = 0.001f; + ImGui::InputFloat("input float", &f0, 0.01f, 1.0f, "%.3f"); + + static double d0 = 999999.00000001; + ImGui::InputDouble("input double", &d0, 0.01f, 1.0f, "%.8f"); + + static float f1 = 1.e10f; + ImGui::InputFloat("input scientific", &f1, 0.0f, 0.0f, "%e"); + ImGui::SameLine(); HelpMarker( + "You can input value using the scientific notation,\n" + " e.g. \"1e+8\" becomes \"100000000\"."); + + static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + ImGui::InputFloat3("input float3", vec4a); + } + + { + static int i1 = 50, i2 = 42; + ImGui::DragInt("drag int", &i1, 1); + ImGui::SameLine(); HelpMarker( + "Click and drag to edit value.\n" + "Hold SHIFT/ALT for faster/slower edit.\n" + "Double-click or CTRL+click to input value."); + + ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp); + + static float f1 = 1.00f, f2 = 0.0067f; + ImGui::DragFloat("drag float", &f1, 0.005f); + ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); + } + + { + static int i1 = 0; + ImGui::SliderInt("slider int", &i1, -1, 3); + ImGui::SameLine(); HelpMarker("CTRL+click to input value."); + + static float f1 = 0.123f, f2 = 0.0f; + ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f"); + ImGui::SliderFloat("slider float (log)", &f2, -10.0f, 10.0f, "%.4f", ImGuiSliderFlags_Logarithmic); + + static float angle = 0.0f; + ImGui::SliderAngle("slider angle", &angle); + + // Using the format string to display a name instead of an integer. + // Here we completely omit '%d' from the format string, so it'll only display a name. + // This technique can also be used with DragInt(). + enum Element { Element_Fire, Element_Earth, Element_Air, Element_Water, Element_COUNT }; + static int elem = Element_Fire; + const char* elems_names[Element_COUNT] = { "Fire", "Earth", "Air", "Water" }; + const char* elem_name = (elem >= 0 && elem < Element_COUNT) ? elems_names[elem] : "Unknown"; + ImGui::SliderInt("slider enum", &elem, 0, Element_COUNT - 1, elem_name); + ImGui::SameLine(); HelpMarker("Using the format string parameter to display a name instead of the underlying integer."); + } + + { + static float col1[3] = { 1.0f, 0.0f, 0.2f }; + static float col2[4] = { 0.4f, 0.7f, 0.0f, 0.5f }; + ImGui::ColorEdit3("color 1", col1); + ImGui::SameLine(); HelpMarker( + "Click on the color square to open a color picker.\n" + "Click and hold to use drag and drop.\n" + "Right-click on the color square to show options.\n" + "CTRL+click on individual component to input value.\n"); + + ImGui::ColorEdit4("color 2", col2); + } + + { + // Using the _simplified_ one-liner ListBox() api here + // See "List boxes" section for examples of how to use the more flexible BeginListBox()/EndListBox() api. + const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; + static int item_current = 1; + ImGui::ListBox("listbox", &item_current, items, IM_ARRAYSIZE(items), 4); + ImGui::SameLine(); HelpMarker( + "Using the simplified one-liner ListBox API here.\nRefer to the \"List boxes\" section below for an explanation of how to use the more flexible and general BeginListBox/EndListBox API."); + } + + ImGui::TreePop(); + } + + // Testing ImGuiOnceUponAFrame helper. + //static ImGuiOnceUponAFrame once; + //for (int i = 0; i < 5; i++) + // if (once) + // ImGui::Text("This will be displayed only once."); + + if (ImGui::TreeNode("Trees")) + { + if (ImGui::TreeNode("Basic trees")) + { + for (int i = 0; i < 5; i++) + { + // Use SetNextItemOpen() so set the default state of a node to be open. We could + // also use TreeNodeEx() with the ImGuiTreeNodeFlags_DefaultOpen flag to achieve the same thing! + if (i == 0) + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + + if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) + { + ImGui::Text("blah blah"); + ImGui::SameLine(); + if (ImGui::SmallButton("button")) {} + ImGui::TreePop(); + } + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Advanced, with Selectable nodes")) + { + HelpMarker( + "This is a more typical looking tree with selectable nodes.\n" + "Click to select, CTRL+Click to toggle, click on arrows or double-click to open."); + static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; + static bool align_label_with_current_x_position = false; + static bool test_drag_and_drop = false; + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnArrow", &base_flags, ImGuiTreeNodeFlags_OpenOnArrow); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", &base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", &base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::SameLine(); HelpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node."); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &base_flags, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position); + ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop); + ImGui::Text("Hello!"); + if (align_label_with_current_x_position) + ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); + + // 'selection_mask' is dumb representation of what may be user-side selection state. + // You may retain selection state inside or outside your objects in whatever format you see fit. + // 'node_clicked' is temporary storage of what node we have clicked to process selection at the end + /// of the loop. May be a pointer to your own node type, etc. + static int selection_mask = (1 << 2); + int node_clicked = -1; + for (int i = 0; i < 6; i++) + { + // Disable the default "open on single-click behavior" + set Selected flag according to our selection. + ImGuiTreeNodeFlags node_flags = base_flags; + const bool is_selected = (selection_mask & (1 << i)) != 0; + if (is_selected) + node_flags |= ImGuiTreeNodeFlags_Selected; + if (i < 3) + { + // Items 0..2 are Tree Node + bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + if (test_drag_and_drop && ImGui::BeginDragDropSource()) + { + ImGui::SetDragDropPayload("_TREENODE", NULL, 0); + ImGui::Text("This is a drag and drop source"); + ImGui::EndDragDropSource(); + } + if (node_open) + { + ImGui::BulletText("Blah blah\nBlah Blah"); + ImGui::TreePop(); + } + } + else + { + // Items 3..5 are Tree Leaves + // The only reason we use TreeNode at all is to allow selection of the leaf. Otherwise we can + // use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text(). + node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet + ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + if (test_drag_and_drop && ImGui::BeginDragDropSource()) + { + ImGui::SetDragDropPayload("_TREENODE", NULL, 0); + ImGui::Text("This is a drag and drop source"); + ImGui::EndDragDropSource(); + } + } + } + if (node_clicked != -1) + { + // Update selection state + // (process outside of tree loop to avoid visual inconsistencies during the clicking frame) + if (ImGui::GetIO().KeyCtrl) + selection_mask ^= (1 << node_clicked); // CTRL+click to toggle + else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, may want to preserve selection when clicking on item that is part of the selection + selection_mask = (1 << node_clicked); // Click to single-select + } + if (align_label_with_current_x_position) + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Collapsing Headers")) + { + static bool closable_group = true; + ImGui::Checkbox("Show 2nd header", &closable_group); + if (ImGui::CollapsingHeader("Header", ImGuiTreeNodeFlags_None)) + { + ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("Some content %d", i); + } + if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) + { + ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("More content %d", i); + } + /* + if (ImGui::CollapsingHeader("Header with a bullet", ImGuiTreeNodeFlags_Bullet)) + ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); + */ + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Bullets")) + { + ImGui::BulletText("Bullet point 1"); + ImGui::BulletText("Bullet point 2\nOn multiple lines"); + if (ImGui::TreeNode("Tree node")) + { + ImGui::BulletText("Another bullet point"); + ImGui::TreePop(); + } + ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)"); + ImGui::Bullet(); ImGui::SmallButton("Button"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text")) + { + if (ImGui::TreeNode("Colorful Text")) + { + // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. + ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "Pink"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Yellow"); + ImGui::TextDisabled("Disabled"); + ImGui::SameLine(); HelpMarker("The TextDisabled color is stored in ImGuiStyle."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Word Wrapping")) + { + // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. + ImGui::TextWrapped( + "This text should automatically wrap on the edge of the window. The current implementation " + "for text wrapping follows simple rules suitable for English and possibly other languages."); + ImGui::Spacing(); + + static float wrap_width = 200.0f; + ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f"); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + for (int n = 0; n < 2; n++) + { + ImGui::Text("Test paragraph %d:", n); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec2 marker_min = ImVec2(pos.x + wrap_width, pos.y); + ImVec2 marker_max = ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + if (n == 0) + ImGui::Text("The lazy dog is a good dog. This paragraph should fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); + else + ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); + + // Draw actual text bounding box, following by marker of our expected limit (should not overlap!) + draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 255, 0, 255)); + draw_list->AddRectFilled(marker_min, marker_max, IM_COL32(255, 0, 255, 255)); + ImGui::PopTextWrapPos(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("UTF-8 Text")) + { + // UTF-8 test with Japanese characters + // (Needs a suitable font? Try "Google Noto" or "Arial Unicode". See docs/FONTS.md for details.) + // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 + // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. in Visual Studio, you + // can save your source files as 'UTF-8 without signature'). + // - FOR THIS DEMO FILE ONLY, BECAUSE WE WANT TO SUPPORT OLD COMPILERS, WE ARE *NOT* INCLUDING RAW UTF-8 + // CHARACTERS IN THIS SOURCE FILE. Instead we are encoding a few strings with hexadecimal constants. + // Don't do this in your application! Please use u8"text in any language" in your application! + // Note that characters values are preserved even by InputText() if the font cannot be displayed, + // so you can safely copy & paste garbled characters into another application. + ImGui::TextWrapped( + "CJK text will only appears if the font was loaded with the appropriate CJK character ranges. " + "Call io.Fonts->AddFontFromFileTTF() manually to load extra character ranges. " + "Read docs/FONTS.md for details."); + ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); // Normally we would use u8"blah blah" with the proper characters directly in the string. + ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); + static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; + //static char buf[32] = u8"NIHONGO"; // <- this is how you would write it with C++11, using real kanjis + ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Images")) + { + ImGuiIO& io = ImGui::GetIO(); + ImGui::TextWrapped( + "Below we are displaying the font texture (which is the only texture we have access to in this demo). " + "Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. " + "Hover the texture for a zoomed view!"); + + // Below we are displaying the font texture because it is the only texture we have access to inside the demo! + // Remember that ImTextureID is just storage for whatever you want it to be. It is essentially a value that + // will be passed to the rendering backend via the ImDrawCmd structure. + // If you use one of the default imgui_impl_XXXX.cpp rendering backend, they all have comments at the top + // of their respective source file to specify what they expect to be stored in ImTextureID, for example: + // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer + // - The imgui_impl_opengl3.cpp renderer expect a GLuint OpenGL texture identifier, etc. + // More: + // - If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers + // to ImGui::Image(), and gather width/height through your own functions, etc. + // - You can use ShowMetricsWindow() to inspect the draw data that are being passed to your renderer, + // it will help you debug issues if you are confused about it. + // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). + // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md + // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples + ImTextureID my_tex_id = io.Fonts->TexID; + float my_tex_w = (float)io.Fonts->TexWidth; + float my_tex_h = (float)io.Fonts->TexHeight; + { + ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left + ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right + ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint + ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); // 50% opaque white + ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, tint_col, border_col); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + float region_sz = 32.0f; + float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; + float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; + float zoom = 4.0f; + if (region_x < 0.0f) { region_x = 0.0f; } + else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; } + if (region_y < 0.0f) { region_y = 0.0f; } + else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; } + ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y); + ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz); + ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h); + ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h); + ImGui::Image(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, tint_col, border_col); + ImGui::EndTooltip(); + } + } + ImGui::TextWrapped("And now some textured buttons.."); + static int pressed_count = 0; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); + int frame_padding = -1 + i; // -1 == uses default padding (style.FramePadding) + ImVec2 size = ImVec2(32.0f, 32.0f); // Size of the image we want to make visible + ImVec2 uv0 = ImVec2(0.0f, 0.0f); // UV coordinates for lower-left + ImVec2 uv1 = ImVec2(32.0f / my_tex_w, 32.0f / my_tex_h);// UV coordinates for (32,32) in our texture + ImVec4 bg_col = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); // Black background + ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint + if (ImGui::ImageButton(my_tex_id, size, uv0, uv1, frame_padding, bg_col, tint_col)) + pressed_count += 1; + ImGui::PopID(); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::Text("Pressed %d times.", pressed_count); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Combo")) + { + // Expose flags as checkbox for the demo + static ImGuiComboFlags flags = 0; + ImGui::CheckboxFlags("ImGuiComboFlags_PopupAlignLeft", &flags, ImGuiComboFlags_PopupAlignLeft); + ImGui::SameLine(); HelpMarker("Only makes a difference if the popup is larger than the combo"); + if (ImGui::CheckboxFlags("ImGuiComboFlags_NoArrowButton", &flags, ImGuiComboFlags_NoArrowButton)) + flags &= ~ImGuiComboFlags_NoPreview; // Clear the other flag, as we cannot combine both + if (ImGui::CheckboxFlags("ImGuiComboFlags_NoPreview", &flags, ImGuiComboFlags_NoPreview)) + flags &= ~ImGuiComboFlags_NoArrowButton; // Clear the other flag, as we cannot combine both + + // Using the generic BeginCombo() API, you have full control over how to display the combo contents. + // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively + // stored in the object itself, etc.) + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; + static int item_current_idx = 0; // Here we store our selection data as an index. + const char* combo_label = items[item_current_idx]; // Label to preview before opening the combo (technically it could be anything) + if (ImGui::BeginCombo("combo 1", combo_label, flags)) + { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) + { + const bool is_selected = (item_current_idx == n); + if (ImGui::Selectable(items[n], is_selected)) + item_current_idx = n; + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + // Simplified one-liner Combo() API, using values packed in a single constant string + static int item_current_2 = 0; + ImGui::Combo("combo 2 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + + // Simplified one-liner Combo() using an array of const char* + static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview + ImGui::Combo("combo 3 (array)", &item_current_3, items, IM_ARRAYSIZE(items)); + + // Simplified one-liner Combo() using an accessor function + struct Funcs { static bool ItemGetter(void* data, int n, const char** out_str) { *out_str = ((const char**)data)[n]; return true; } }; + static int item_current_4 = 0; + ImGui::Combo("combo 4 (function)", &item_current_4, &Funcs::ItemGetter, items, IM_ARRAYSIZE(items)); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("List boxes")) + { + // Using the generic BeginListBox() API, you have full control over how to display the combo contents. + // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively + // stored in the object itself, etc.) + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; + static int item_current_idx = 0; // Here we store our selection data as an index. + if (ImGui::BeginListBox("listbox 1")) + { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) + { + const bool is_selected = (item_current_idx == n); + if (ImGui::Selectable(items[n], is_selected)) + item_current_idx = n; + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndListBox(); + } + + // Custom size: use all width, 5 items tall + ImGui::Text("Full-width:"); + if (ImGui::BeginListBox("##listbox 2", ImVec2(-FLT_MIN, 5 * ImGui::GetTextLineHeightWithSpacing()))) + { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) + { + const bool is_selected = (item_current_idx == n); + if (ImGui::Selectable(items[n], is_selected)) + item_current_idx = n; + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndListBox(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Selectables")) + { + // Selectable() has 2 overloads: + // - The one taking "bool selected" as a read-only selection information. + // When Selectable() has been clicked it returns true and you can alter selection state accordingly. + // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases) + // The earlier is more flexible, as in real application your selection may be stored in many different ways + // and not necessarily inside a bool value (e.g. in flags within objects, as an external list, etc). + if (ImGui::TreeNode("Basic")) + { + static bool selection[5] = { false, true, false, false, false }; + ImGui::Selectable("1. I am selectable", &selection[0]); + ImGui::Selectable("2. I am selectable", &selection[1]); + ImGui::Text("3. I am not selectable"); + ImGui::Selectable("4. I am selectable", &selection[3]); + if (ImGui::Selectable("5. I am double clickable", selection[4], ImGuiSelectableFlags_AllowDoubleClick)) + if (ImGui::IsMouseDoubleClicked(0)) + selection[4] = !selection[4]; + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Single Selection")) + { + static int selected = -1; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selected == n)) + selected = n; + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Selection State: Multiple Selection")) + { + HelpMarker("Hold CTRL and click to select multiple items."); + static bool selection[5] = { false, false, false, false, false }; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selection[n])) + { + if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held + memset(selection, 0, sizeof(selection)); + selection[n] ^= 1; + } + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Rendering more text into the same line")) + { + // Using the Selectable() override that takes "bool* p_selected" parameter, + // this function toggle your bool value automatically. + static bool selected[3] = { false, false, false }; + ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(300); ImGui::Text("12,345 bytes"); + ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("In columns")) + { + static bool selected[10] = {}; + + if (ImGui::BeginTable("split1", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings)) + { + for (int i = 0; i < 10; i++) + { + char label[32]; + sprintf(label, "Item %d", i); + ImGui::TableNextColumn(); + ImGui::Selectable(label, &selected[i]); // FIXME-TABLE: Selection overlap + } + ImGui::EndTable(); + } + ImGui::Separator(); + if (ImGui::BeginTable("split2", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings)) + { + for (int i = 0; i < 10; i++) + { + char label[32]; + sprintf(label, "Item %d", i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Selectable(label, &selected[i], ImGuiSelectableFlags_SpanAllColumns); + ImGui::TableNextColumn(); + ImGui::Text("Some other contents"); + ImGui::TableNextColumn(); + ImGui::Text("123456"); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Grid")) + { + static char selected[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } }; + + // Add in a bit of silly fun... + const float time = (float)ImGui::GetTime(); + const bool winning_state = memchr(selected, 0, sizeof(selected)) == NULL; // If all cells are selected... + if (winning_state) + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f + 0.5f * cosf(time * 2.0f), 0.5f + 0.5f * sinf(time * 3.0f))); + + for (int y = 0; y < 4; y++) + for (int x = 0; x < 4; x++) + { + if (x > 0) + ImGui::SameLine(); + ImGui::PushID(y * 4 + x); + if (ImGui::Selectable("Sailor", selected[y][x] != 0, 0, ImVec2(50, 50))) + { + // Toggle clicked cell + toggle neighbors + selected[y][x] ^= 1; + if (x > 0) { selected[y][x - 1] ^= 1; } + if (x < 3) { selected[y][x + 1] ^= 1; } + if (y > 0) { selected[y - 1][x] ^= 1; } + if (y < 3) { selected[y + 1][x] ^= 1; } + } + ImGui::PopID(); + } + + if (winning_state) + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Alignment")) + { + HelpMarker( + "By default, Selectables uses style.SelectableTextAlign but it can be overridden on a per-item " + "basis using PushStyleVar(). You'll probably want to always keep your default situation to " + "left-align otherwise it becomes difficult to layout multiple items on a same line"); + static bool selected[3 * 3] = { true, false, true, false, true, false, true, false, true }; + for (int y = 0; y < 3; y++) + { + for (int x = 0; x < 3; x++) + { + ImVec2 alignment = ImVec2((float)x / 2.0f, (float)y / 2.0f); + char name[32]; + sprintf(name, "(%.1f,%.1f)", alignment.x, alignment.y); + if (x > 0) ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, alignment); + ImGui::Selectable(name, &selected[3 * y + x], ImGuiSelectableFlags_None, ImVec2(80, 80)); + ImGui::PopStyleVar(); + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + // To wire InputText() with std::string or any other custom string type, + // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file. + if (ImGui::TreeNode("Text Input")) + { + if (ImGui::TreeNode("Multi-line Text Input")) + { + // Note: we are using a fixed-sized buffer for simplicity here. See ImGuiInputTextFlags_CallbackResize + // and the code in misc/cpp/imgui_stdlib.h for how to setup InputText() for dynamically resizing strings. + static char text[1024 * 16] = + "/*\n" + " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n" + " the hexadecimal encoding of one offending instruction,\n" + " more formally, the invalid operand with locked CMPXCHG8B\n" + " instruction bug, is a design flaw in the majority of\n" + " Intel Pentium, Pentium MMX, and Pentium OverDrive\n" + " processors (all in the P5 microarchitecture).\n" + "*/\n\n" + "label:\n" + "\tlock cmpxchg8b eax\n"; + + static ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput; + HelpMarker("You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example. (This is not demonstrated in imgui_demo.cpp because we don't want to include in here)"); + ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", &flags, ImGuiInputTextFlags_ReadOnly); + ImGui::CheckboxFlags("ImGuiInputTextFlags_AllowTabInput", &flags, ImGuiInputTextFlags_AllowTabInput); + ImGui::CheckboxFlags("ImGuiInputTextFlags_CtrlEnterForNewLine", &flags, ImGuiInputTextFlags_CtrlEnterForNewLine); + ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16), flags); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Filtered Text Input")) + { + struct TextFilters + { + // Return 0 (pass) if the character is 'i' or 'm' or 'g' or 'u' or 'i' + static int FilterImGuiLetters(ImGuiInputTextCallbackData* data) + { + if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) + return 0; + return 1; + } + }; + + static char buf1[64] = ""; ImGui::InputText("default", buf1, 64); + static char buf2[64] = ""; ImGui::InputText("decimal", buf2, 64, ImGuiInputTextFlags_CharsDecimal); + static char buf3[64] = ""; ImGui::InputText("hexadecimal", buf3, 64, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + static char buf4[64] = ""; ImGui::InputText("uppercase", buf4, 64, ImGuiInputTextFlags_CharsUppercase); + static char buf5[64] = ""; ImGui::InputText("no blank", buf5, 64, ImGuiInputTextFlags_CharsNoBlank); + static char buf6[64] = ""; ImGui::InputText("\"imgui\" letters", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Password Input")) + { + static char password[64] = "password123"; + ImGui::InputText("password", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password); + ImGui::SameLine(); HelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); + ImGui::InputTextWithHint("password (w/ hint)", "", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password); + ImGui::InputText("password (clear)", password, IM_ARRAYSIZE(password)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Completion, History, Edit Callbacks")) + { + struct Funcs + { + static int MyCallback(ImGuiInputTextCallbackData* data) + { + if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion) + { + data->InsertChars(data->CursorPos, ".."); + } + else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory) + { + if (data->EventKey == ImGuiKey_UpArrow) + { + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, "Pressed Up!"); + data->SelectAll(); + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, "Pressed Down!"); + data->SelectAll(); + } + } + else if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit) + { + // Toggle casing of first character + char c = data->Buf[0]; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) data->Buf[0] ^= 32; + data->BufDirty = true; + + // Increment a counter + int* p_int = (int*)data->UserData; + *p_int = *p_int + 1; + } + return 0; + } + }; + static char buf1[64]; + ImGui::InputText("Completion", buf1, 64, ImGuiInputTextFlags_CallbackCompletion, Funcs::MyCallback); + ImGui::SameLine(); HelpMarker("Here we append \"..\" each time Tab is pressed. See 'Examples>Console' for a more meaningful demonstration of using this callback."); + + static char buf2[64]; + ImGui::InputText("History", buf2, 64, ImGuiInputTextFlags_CallbackHistory, Funcs::MyCallback); + ImGui::SameLine(); HelpMarker("Here we replace and select text each time Up/Down are pressed. See 'Examples>Console' for a more meaningful demonstration of using this callback."); + + static char buf3[64]; + static int edit_count = 0; + ImGui::InputText("Edit", buf3, 64, ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count); + ImGui::SameLine(); HelpMarker("Here we toggle the casing of the first character on every edits + count edits."); + ImGui::SameLine(); ImGui::Text("(%d)", edit_count); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Resize Callback")) + { + // To wire InputText() with std::string or any other custom string type, + // you can use the ImGuiInputTextFlags_CallbackResize flag + create a custom ImGui::InputText() wrapper + // using your preferred type. See misc/cpp/imgui_stdlib.h for an implementation of this using std::string. + HelpMarker( + "Using ImGuiInputTextFlags_CallbackResize to wire your custom string type to InputText().\n\n" + "See misc/cpp/imgui_stdlib.h for an implementation of this for std::string."); + struct Funcs + { + static int MyResizeCallback(ImGuiInputTextCallbackData* data) + { + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) + { + ImVector* my_str = (ImVector*)data->UserData; + IM_ASSERT(my_str->begin() == data->Buf); + my_str->resize(data->BufSize); // NB: On resizing calls, generally data->BufSize == data->BufTextLen + 1 + data->Buf = my_str->begin(); + } + return 0; + } + + // Note: Because ImGui:: is a namespace you would typically add your own function into the namespace. + // For example, you code may declare a function 'ImGui::InputText(const char* label, MyString* my_str)' + static bool MyInputTextMultiline(const char* label, ImVector* my_str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0) + { + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + return ImGui::InputTextMultiline(label, my_str->begin(), (size_t)my_str->size(), size, flags | ImGuiInputTextFlags_CallbackResize, Funcs::MyResizeCallback, (void*)my_str); + } + }; + + // For this demo we are using ImVector as a string container. + // Note that because we need to store a terminating zero character, our size/capacity are 1 more + // than usually reported by a typical string class. + static ImVector my_str; + if (my_str.empty()) + my_str.push_back(0); + Funcs::MyInputTextMultiline("##MyStr", &my_str, ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16)); + ImGui::Text("Data: %p\nSize: %d\nCapacity: %d", (void*)my_str.begin(), my_str.size(), my_str.capacity()); + ImGui::TreePop(); + } + + ImGui::TreePop(); + } + + // Tabs + if (ImGui::TreeNode("Tabs")) + { + if (ImGui::TreeNode("Basic")) + { + ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None; + if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) + { + if (ImGui::BeginTabItem("Avocado")) + { + ImGui::Text("This is the Avocado tab!\nblah blah blah blah blah"); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Broccoli")) + { + ImGui::Text("This is the Broccoli tab!\nblah blah blah blah blah"); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Cucumber")) + { + ImGui::Text("This is the Cucumber tab!\nblah blah blah blah blah"); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Advanced & Close Button")) + { + // Expose a couple of the available flags. In most cases you may just call BeginTabBar() with no flags (0). + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable; + ImGui::CheckboxFlags("ImGuiTabBarFlags_Reorderable", &tab_bar_flags, ImGuiTabBarFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", &tab_bar_flags, ImGuiTabBarFlags_AutoSelectNewTabs); + ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); + ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", &tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton); + if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) + tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_; + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) + tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) + tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); + + // Tab Bar + const char* names[4] = { "Artichoke", "Beetroot", "Celery", "Daikon" }; + static bool opened[4] = { true, true, true, true }; // Persistent user state + for (int n = 0; n < IM_ARRAYSIZE(opened); n++) + { + if (n > 0) { ImGui::SameLine(); } + ImGui::Checkbox(names[n], &opened[n]); + } + + // Passing a bool* to BeginTabItem() is similar to passing one to Begin(): + // the underlying bool will be set to false when the tab is closed. + if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) + { + for (int n = 0; n < IM_ARRAYSIZE(opened); n++) + if (opened[n] && ImGui::BeginTabItem(names[n], &opened[n], ImGuiTabItemFlags_None)) + { + ImGui::Text("This is the %s tab!", names[n]); + if (n & 1) + ImGui::Text("I am an odd tab."); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("TabItemButton & Leading/Trailing flags")) + { + static ImVector active_tabs; + static int next_tab_id = 0; + if (next_tab_id == 0) // Initialize with some default tabs + for (int i = 0; i < 3; i++) + active_tabs.push_back(next_tab_id++); + + // TabItemButton() and Leading/Trailing flags are distinct features which we will demo together. + // (It is possible to submit regular tabs with Leading/Trailing flags, or TabItemButton tabs without Leading/Trailing flags... + // but they tend to make more sense together) + static bool show_leading_button = true; + static bool show_trailing_button = true; + ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button); + ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); + + // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown; + ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) + tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) + tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); + + if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) + { + // Demo a Leading TabItemButton(): click the "?" button to open a menu + if (show_leading_button) + if (ImGui::TabItemButton("?", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip)) + ImGui::OpenPopup("MyHelpMenu"); + if (ImGui::BeginPopup("MyHelpMenu")) + { + ImGui::Selectable("Hello!"); + ImGui::EndPopup(); + } + + // Demo Trailing Tabs: click the "+" button to add a new tab (in your app you may want to use a font icon instead of the "+") + // Note that we submit it before the regular tabs, but because of the ImGuiTabItemFlags_Trailing flag it will always appear at the end. + if (show_trailing_button) + if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) + active_tabs.push_back(next_tab_id++); // Add new tab + + // Submit our regular tabs + for (int n = 0; n < active_tabs.Size; ) + { + bool open = true; + char name[16]; + snprintf(name, IM_ARRAYSIZE(name), "%04d", active_tabs[n]); + if (ImGui::BeginTabItem(name, &open, ImGuiTabItemFlags_None)) + { + ImGui::Text("This is the %s tab!", name); + ImGui::EndTabItem(); + } + + if (!open) + active_tabs.erase(active_tabs.Data + n); + else + n++; + } + + ImGui::EndTabBar(); + } + ImGui::Separator(); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + // Plot/Graph widgets are not very good. + // Consider writing your own, or using a third-party one, see: + // - ImPlot https://github.com/epezent/implot + // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions + if (ImGui::TreeNode("Plots Widgets")) + { + static bool animate = true; + ImGui::Checkbox("Animate", &animate); + + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); + + // Fill an array of contiguous float values to plot + // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float + // and the sizeof() of your structure in the "stride" parameter. + static float values[90] = {}; + static int values_offset = 0; + static double refresh_time = 0.0; + if (!animate || refresh_time == 0.0) + refresh_time = ImGui::GetTime(); + while (refresh_time < ImGui::GetTime()) // Create data at fixed 60 Hz rate for the demo + { + static float phase = 0.0f; + values[values_offset] = cosf(phase); + values_offset = (values_offset + 1) % IM_ARRAYSIZE(values); + phase += 0.10f * values_offset; + refresh_time += 1.0f / 60.0f; + } + + // Plots can display overlay texts + // (in this example, we will display an average value) + { + float average = 0.0f; + for (int n = 0; n < IM_ARRAYSIZE(values); n++) + average += values[n]; + average /= (float)IM_ARRAYSIZE(values); + char overlay[32]; + sprintf(overlay, "avg %f", average); + ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f)); + } + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); + + // Use functions to generate output + // FIXME: This is rather awkward because current plot API only pass in indices. + // We probably want an API passing floats and user provide sample rate/count. + struct Funcs + { + static float Sin(void*, int i) { return sinf(i * 0.1f); } + static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; } + }; + static int func_type = 0, display_count = 70; + ImGui::Separator(); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::Combo("func", &func_type, "Sin\0Saw\0"); + ImGui::SameLine(); + ImGui::SliderInt("Sample count", &display_count, 1, 400); + float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; + ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); + ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); + ImGui::Separator(); + + // Animate a simple progress bar + static float progress = 0.0f, progress_dir = 1.0f; + if (animate) + { + progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } + if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + } + + // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width, + // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. + ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f)); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Progress Bar"); + + float progress_saturated = IM_CLAMP(progress, 0.0f, 1.0f); + char buf[32]; + sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753); + ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Color/Picker Widgets")) + { + static ImVec4 color = ImVec4(114.0f / 255.0f, 144.0f / 255.0f, 154.0f / 255.0f, 200.0f / 255.0f); + + static bool alpha_preview = true; + static bool alpha_half_preview = false; + static bool drag_and_drop = true; + static bool options_menu = true; + static bool hdr = false; + ImGui::Checkbox("With Alpha Preview", &alpha_preview); + ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); + ImGui::Checkbox("With Drag and Drop", &drag_and_drop); + ImGui::Checkbox("With Options Menu", &options_menu); ImGui::SameLine(); HelpMarker("Right-click on the individual color widget to show options."); + ImGui::Checkbox("With HDR", &hdr); ImGui::SameLine(); HelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); + ImGuiColorEditFlags misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (drag_and_drop ? 0 : ImGuiColorEditFlags_NoDragDrop) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); + + ImGui::Text("Color widget:"); + ImGui::SameLine(); HelpMarker( + "Click on the color square to open a color picker.\n" + "CTRL+click on individual component to input value.\n"); + ImGui::ColorEdit3("MyColor##1", (float*)&color, misc_flags); + + ImGui::Text("Color widget HSV with Alpha:"); + ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_DisplayHSV | misc_flags); + + ImGui::Text("Color widget with Float Display:"); + ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | misc_flags); + + ImGui::Text("Color button with Picker:"); + ImGui::SameLine(); HelpMarker( + "With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\n" + "With the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only " + "be used for the tooltip and picker popup."); + ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | misc_flags); + + ImGui::Text("Color button with Custom Picker Popup:"); + + // Generate a default palette. The palette will persist and can be edited. + static bool saved_palette_init = true; + static ImVec4 saved_palette[32] = {}; + if (saved_palette_init) + { + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, + saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); + saved_palette[n].w = 1.0f; // Alpha + } + saved_palette_init = false; + } + + static ImVec4 backup_color; + bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags); + ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x); + open_popup |= ImGui::Button("Palette"); + if (open_popup) + { + ImGui::OpenPopup("mypicker"); + backup_color = color; + } + if (ImGui::BeginPopup("mypicker")) + { + ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); + ImGui::Separator(); + ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); + ImGui::SameLine(); + + ImGui::BeginGroup(); // Lock X position + ImGui::Text("Current"); + ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40)); + ImGui::Text("Previous"); + if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40))) + color = backup_color; + ImGui::Separator(); + ImGui::Text("Palette"); + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::PushID(n); + if ((n % 8) != 0) + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + + ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip; + if (ImGui::ColorButton("##palette", saved_palette[n], palette_button_flags, ImVec2(20, 20))) + color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! + + // Allow user to drop colors into each palette entry. Note that ColorButton() is already a + // drag source by default, unless specifying the ImGuiColorEditFlags_NoDragDrop flag. + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3); + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4); + ImGui::EndDragDropTarget(); + } + + ImGui::PopID(); + } + ImGui::EndGroup(); + ImGui::EndPopup(); + } + + ImGui::Text("Color button only:"); + static bool no_border = false; + ImGui::Checkbox("ImGuiColorEditFlags_NoBorder", &no_border); + ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags | (no_border ? ImGuiColorEditFlags_NoBorder : 0), ImVec2(80, 80)); + + ImGui::Text("Color picker:"); + static bool alpha = true; + static bool alpha_bar = true; + static bool side_preview = true; + static bool ref_color = false; + static ImVec4 ref_color_v(1.0f, 0.0f, 1.0f, 0.5f); + static int display_mode = 0; + static int picker_mode = 0; + ImGui::Checkbox("With Alpha", &alpha); + ImGui::Checkbox("With Alpha Bar", &alpha_bar); + ImGui::Checkbox("With Side Preview", &side_preview); + if (side_preview) + { + ImGui::SameLine(); + ImGui::Checkbox("With Ref Color", &ref_color); + if (ref_color) + { + ImGui::SameLine(); + ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags); + } + } + ImGui::Combo("Display Mode", &display_mode, "Auto/Current\0None\0RGB Only\0HSV Only\0Hex Only\0"); + ImGui::SameLine(); HelpMarker( + "ColorEdit defaults to displaying RGB inputs if you don't specify a display mode, " + "but the user can change it with a right-click.\n\nColorPicker defaults to displaying RGB+HSV+Hex " + "if you don't specify a display mode.\n\nYou can change the defaults using SetColorEditOptions()."); + ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0Hue bar + SV rect\0Hue wheel + SV triangle\0"); + ImGui::SameLine(); HelpMarker("User can right-click the picker to change mode."); + ImGuiColorEditFlags flags = misc_flags; + if (!alpha) flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4() + if (alpha_bar) flags |= ImGuiColorEditFlags_AlphaBar; + if (!side_preview) flags |= ImGuiColorEditFlags_NoSidePreview; + if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel; + if (display_mode == 1) flags |= ImGuiColorEditFlags_NoInputs; // Disable all RGB/HSV/Hex displays + if (display_mode == 2) flags |= ImGuiColorEditFlags_DisplayRGB; // Override display mode + if (display_mode == 3) flags |= ImGuiColorEditFlags_DisplayHSV; + if (display_mode == 4) flags |= ImGuiColorEditFlags_DisplayHex; + ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL); + + ImGui::Text("Set defaults in code:"); + ImGui::SameLine(); HelpMarker( + "SetColorEditOptions() is designed to allow you to set boot-time default.\n" + "We don't have Push/Pop functions because you can force options on a per-widget basis if needed," + "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid" + "encouraging you to persistently save values that aren't forward-compatible."); + if (ImGui::Button("Default: Uint8 + HSV + Hue Bar")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar); + if (ImGui::Button("Default: Float + HDR + Hue Wheel")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel); + + // HSV encoded support (to avoid RGB<>HSV round trips and singularities when S==0 or V==0) + static ImVec4 color_hsv(0.23f, 1.0f, 1.0f, 1.0f); // Stored as HSV! + ImGui::Spacing(); + ImGui::Text("HSV encoded colors"); + ImGui::SameLine(); HelpMarker( + "By default, colors are given to ColorEdit and ColorPicker in RGB, but ImGuiColorEditFlags_InputHSV" + "allows you to store colors as HSV and pass them to ColorEdit and ColorPicker as HSV. This comes with the" + "added benefit that you can manipulate hue values with the picker even when saturation or value are zero."); + ImGui::Text("Color widget with InputHSV:"); + ImGui::ColorEdit4("HSV shown as RGB##1", (float*)&color_hsv, ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputHSV | ImGuiColorEditFlags_Float); + ImGui::ColorEdit4("HSV shown as HSV##1", (float*)&color_hsv, ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_InputHSV | ImGuiColorEditFlags_Float); + ImGui::DragFloat4("Raw HSV values", (float*)&color_hsv, 0.01f, 0.0f, 1.0f); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Drag/Slider Flags")) + { + // Demonstrate using advanced flags for DragXXX and SliderXXX functions. Note that the flags are the same! + static ImGuiSliderFlags flags = ImGuiSliderFlags_None; + ImGui::CheckboxFlags("ImGuiSliderFlags_AlwaysClamp", &flags, ImGuiSliderFlags_AlwaysClamp); + ImGui::SameLine(); HelpMarker("Always clamp value to min/max bounds (if any) when input manually with CTRL+Click."); + ImGui::CheckboxFlags("ImGuiSliderFlags_Logarithmic", &flags, ImGuiSliderFlags_Logarithmic); + ImGui::SameLine(); HelpMarker("Enable logarithmic editing (more precision for small values)."); + ImGui::CheckboxFlags("ImGuiSliderFlags_NoRoundToFormat", &flags, ImGuiSliderFlags_NoRoundToFormat); + ImGui::SameLine(); HelpMarker("Disable rounding underlying value to match precision of the format string (e.g. %.3f values are rounded to those 3 digits)."); + ImGui::CheckboxFlags("ImGuiSliderFlags_NoInput", &flags, ImGuiSliderFlags_NoInput); + ImGui::SameLine(); HelpMarker("Disable CTRL+Click or Enter key allowing to input text directly into the widget."); + + // Drags + static float drag_f = 0.5f; + static int drag_i = 50; + ImGui::Text("Underlying float value: %f", drag_f); + ImGui::DragFloat("DragFloat (0 -> 1)", &drag_f, 0.005f, 0.0f, 1.0f, "%.3f", flags); + ImGui::DragFloat("DragFloat (0 -> +inf)", &drag_f, 0.005f, 0.0f, FLT_MAX, "%.3f", flags); + ImGui::DragFloat("DragFloat (-inf -> 1)", &drag_f, 0.005f, -FLT_MAX, 1.0f, "%.3f", flags); + ImGui::DragFloat("DragFloat (-inf -> +inf)", &drag_f, 0.005f, -FLT_MAX, +FLT_MAX, "%.3f", flags); + ImGui::DragInt("DragInt (0 -> 100)", &drag_i, 0.5f, 0, 100, "%d", flags); + + // Sliders + static float slider_f = 0.5f; + static int slider_i = 50; + ImGui::Text("Underlying float value: %f", slider_f); + ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags); + ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Range Widgets")) + { + static float begin = 10, end = 90; + static int begin_i = 100, end_i = 1000; + ImGui::DragFloatRange2("range float", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%", ImGuiSliderFlags_AlwaysClamp); + ImGui::DragIntRange2("range int", &begin_i, &end_i, 5, 0, 1000, "Min: %d units", "Max: %d units"); + ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %d units", "Max: %d units"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Data Types")) + { + // DragScalar/InputScalar/SliderScalar functions allow various data types + // - signed/unsigned + // - 8/16/32/64-bits + // - integer/float/double + // To avoid polluting the public API with all possible combinations, we use the ImGuiDataType enum + // to pass the type, and passing all arguments by pointer. + // This is the reason the test code below creates local variables to hold "zero" "one" etc. for each types. + // In practice, if you frequently use a given type that is not covered by the normal API entry points, + // you can wrap it yourself inside a 1 line function which can take typed argument as value instead of void*, + // and then pass their address to the generic function. For example: + // bool MySliderU64(const char *label, u64* value, u64 min = 0, u64 max = 0, const char* format = "%lld") + // { + // return SliderScalar(label, ImGuiDataType_U64, value, &min, &max, format); + // } + + // Setup limits (as helper variables so we can take their address, as explained above) + // Note: SliderScalar() functions have a maximum usable range of half the natural type maximum, hence the /2. + #ifndef LLONG_MIN + ImS64 LLONG_MIN = -9223372036854775807LL - 1; + ImS64 LLONG_MAX = 9223372036854775807LL; + ImU64 ULLONG_MAX = (2ULL * 9223372036854775807LL + 1); + #endif + const char s8_zero = 0, s8_one = 1, s8_fifty = 50, s8_min = -128, s8_max = 127; + const ImU8 u8_zero = 0, u8_one = 1, u8_fifty = 50, u8_min = 0, u8_max = 255; + const short s16_zero = 0, s16_one = 1, s16_fifty = 50, s16_min = -32768, s16_max = 32767; + const ImU16 u16_zero = 0, u16_one = 1, u16_fifty = 50, u16_min = 0, u16_max = 65535; + const ImS32 s32_zero = 0, s32_one = 1, s32_fifty = 50, s32_min = INT_MIN/2, s32_max = INT_MAX/2, s32_hi_a = INT_MAX/2 - 100, s32_hi_b = INT_MAX/2; + const ImU32 u32_zero = 0, u32_one = 1, u32_fifty = 50, u32_min = 0, u32_max = UINT_MAX/2, u32_hi_a = UINT_MAX/2 - 100, u32_hi_b = UINT_MAX/2; + const ImS64 s64_zero = 0, s64_one = 1, s64_fifty = 50, s64_min = LLONG_MIN/2, s64_max = LLONG_MAX/2, s64_hi_a = LLONG_MAX/2 - 100, s64_hi_b = LLONG_MAX/2; + const ImU64 u64_zero = 0, u64_one = 1, u64_fifty = 50, u64_min = 0, u64_max = ULLONG_MAX/2, u64_hi_a = ULLONG_MAX/2 - 100, u64_hi_b = ULLONG_MAX/2; + const float f32_zero = 0.f, f32_one = 1.f, f32_lo_a = -10000000000.0f, f32_hi_a = +10000000000.0f; + const double f64_zero = 0., f64_one = 1., f64_lo_a = -1000000000000000.0, f64_hi_a = +1000000000000000.0; + + // State + static char s8_v = 127; + static ImU8 u8_v = 255; + static short s16_v = 32767; + static ImU16 u16_v = 65535; + static ImS32 s32_v = -1; + static ImU32 u32_v = (ImU32)-1; + static ImS64 s64_v = -1; + static ImU64 u64_v = (ImU64)-1; + static float f32_v = 0.123f; + static double f64_v = 90000.01234567890123456789; + + const float drag_speed = 0.2f; + static bool drag_clamp = false; + ImGui::Text("Drags:"); + ImGui::Checkbox("Clamp integers to 0..50", &drag_clamp); + ImGui::SameLine(); HelpMarker( + "As with every widgets in dear imgui, we never modify values unless there is a user interaction.\n" + "You can override the clamping limits by using CTRL+Click to input a value."); + ImGui::DragScalar("drag s8", ImGuiDataType_S8, &s8_v, drag_speed, drag_clamp ? &s8_zero : NULL, drag_clamp ? &s8_fifty : NULL); + ImGui::DragScalar("drag u8", ImGuiDataType_U8, &u8_v, drag_speed, drag_clamp ? &u8_zero : NULL, drag_clamp ? &u8_fifty : NULL, "%u ms"); + ImGui::DragScalar("drag s16", ImGuiDataType_S16, &s16_v, drag_speed, drag_clamp ? &s16_zero : NULL, drag_clamp ? &s16_fifty : NULL); + ImGui::DragScalar("drag u16", ImGuiDataType_U16, &u16_v, drag_speed, drag_clamp ? &u16_zero : NULL, drag_clamp ? &u16_fifty : NULL, "%u ms"); + ImGui::DragScalar("drag s32", ImGuiDataType_S32, &s32_v, drag_speed, drag_clamp ? &s32_zero : NULL, drag_clamp ? &s32_fifty : NULL); + ImGui::DragScalar("drag u32", ImGuiDataType_U32, &u32_v, drag_speed, drag_clamp ? &u32_zero : NULL, drag_clamp ? &u32_fifty : NULL, "%u ms"); + ImGui::DragScalar("drag s64", ImGuiDataType_S64, &s64_v, drag_speed, drag_clamp ? &s64_zero : NULL, drag_clamp ? &s64_fifty : NULL); + ImGui::DragScalar("drag u64", ImGuiDataType_U64, &u64_v, drag_speed, drag_clamp ? &u64_zero : NULL, drag_clamp ? &u64_fifty : NULL); + ImGui::DragScalar("drag float", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f"); + ImGui::DragScalar("drag float log", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f", ImGuiSliderFlags_Logarithmic); + ImGui::DragScalar("drag double", ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, NULL, "%.10f grams"); + ImGui::DragScalar("drag double log",ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, &f64_one, "0 < %.10f < 1", ImGuiSliderFlags_Logarithmic); + + ImGui::Text("Sliders"); + ImGui::SliderScalar("slider s8 full", ImGuiDataType_S8, &s8_v, &s8_min, &s8_max, "%d"); + ImGui::SliderScalar("slider u8 full", ImGuiDataType_U8, &u8_v, &u8_min, &u8_max, "%u"); + ImGui::SliderScalar("slider s16 full", ImGuiDataType_S16, &s16_v, &s16_min, &s16_max, "%d"); + ImGui::SliderScalar("slider u16 full", ImGuiDataType_U16, &u16_v, &u16_min, &u16_max, "%u"); + ImGui::SliderScalar("slider s32 low", ImGuiDataType_S32, &s32_v, &s32_zero, &s32_fifty,"%d"); + ImGui::SliderScalar("slider s32 high", ImGuiDataType_S32, &s32_v, &s32_hi_a, &s32_hi_b, "%d"); + ImGui::SliderScalar("slider s32 full", ImGuiDataType_S32, &s32_v, &s32_min, &s32_max, "%d"); + ImGui::SliderScalar("slider u32 low", ImGuiDataType_U32, &u32_v, &u32_zero, &u32_fifty,"%u"); + ImGui::SliderScalar("slider u32 high", ImGuiDataType_U32, &u32_v, &u32_hi_a, &u32_hi_b, "%u"); + ImGui::SliderScalar("slider u32 full", ImGuiDataType_U32, &u32_v, &u32_min, &u32_max, "%u"); + ImGui::SliderScalar("slider s64 low", ImGuiDataType_S64, &s64_v, &s64_zero, &s64_fifty,"%" IM_PRId64); + ImGui::SliderScalar("slider s64 high", ImGuiDataType_S64, &s64_v, &s64_hi_a, &s64_hi_b, "%" IM_PRId64); + ImGui::SliderScalar("slider s64 full", ImGuiDataType_S64, &s64_v, &s64_min, &s64_max, "%" IM_PRId64); + ImGui::SliderScalar("slider u64 low", ImGuiDataType_U64, &u64_v, &u64_zero, &u64_fifty,"%" IM_PRIu64 " ms"); + ImGui::SliderScalar("slider u64 high", ImGuiDataType_U64, &u64_v, &u64_hi_a, &u64_hi_b, "%" IM_PRIu64 " ms"); + ImGui::SliderScalar("slider u64 full", ImGuiDataType_U64, &u64_v, &u64_min, &u64_max, "%" IM_PRIu64 " ms"); + ImGui::SliderScalar("slider float low", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one); + ImGui::SliderScalar("slider float low log", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one, "%.10f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderScalar("slider float high", ImGuiDataType_Float, &f32_v, &f32_lo_a, &f32_hi_a, "%e"); + ImGui::SliderScalar("slider double low", ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f grams"); + ImGui::SliderScalar("slider double low log",ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderScalar("slider double high", ImGuiDataType_Double, &f64_v, &f64_lo_a, &f64_hi_a, "%e grams"); + + ImGui::Text("Sliders (reverse)"); + ImGui::SliderScalar("slider s8 reverse", ImGuiDataType_S8, &s8_v, &s8_max, &s8_min, "%d"); + ImGui::SliderScalar("slider u8 reverse", ImGuiDataType_U8, &u8_v, &u8_max, &u8_min, "%u"); + ImGui::SliderScalar("slider s32 reverse", ImGuiDataType_S32, &s32_v, &s32_fifty, &s32_zero, "%d"); + ImGui::SliderScalar("slider u32 reverse", ImGuiDataType_U32, &u32_v, &u32_fifty, &u32_zero, "%u"); + ImGui::SliderScalar("slider s64 reverse", ImGuiDataType_S64, &s64_v, &s64_fifty, &s64_zero, "%" IM_PRId64); + ImGui::SliderScalar("slider u64 reverse", ImGuiDataType_U64, &u64_v, &u64_fifty, &u64_zero, "%" IM_PRIu64 " ms"); + + static bool inputs_step = true; + ImGui::Text("Inputs"); + ImGui::Checkbox("Show step buttons", &inputs_step); + ImGui::InputScalar("input s8", ImGuiDataType_S8, &s8_v, inputs_step ? &s8_one : NULL, NULL, "%d"); + ImGui::InputScalar("input u8", ImGuiDataType_U8, &u8_v, inputs_step ? &u8_one : NULL, NULL, "%u"); + ImGui::InputScalar("input s16", ImGuiDataType_S16, &s16_v, inputs_step ? &s16_one : NULL, NULL, "%d"); + ImGui::InputScalar("input u16", ImGuiDataType_U16, &u16_v, inputs_step ? &u16_one : NULL, NULL, "%u"); + ImGui::InputScalar("input s32", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%d"); + ImGui::InputScalar("input s32 hex", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputScalar("input u32", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%u"); + ImGui::InputScalar("input u32 hex", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputScalar("input s64", ImGuiDataType_S64, &s64_v, inputs_step ? &s64_one : NULL); + ImGui::InputScalar("input u64", ImGuiDataType_U64, &u64_v, inputs_step ? &u64_one : NULL); + ImGui::InputScalar("input float", ImGuiDataType_Float, &f32_v, inputs_step ? &f32_one : NULL); + ImGui::InputScalar("input double", ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-component Widgets")) + { + static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + static int vec4i[4] = { 1, 5, 100, 255 }; + + ImGui::InputFloat2("input float2", vec4f); + ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); + ImGui::InputInt2("input int2", vec4i); + ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); + ImGui::SliderInt2("slider int2", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat3("input float3", vec4f); + ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); + ImGui::InputInt3("input int3", vec4i); + ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); + ImGui::SliderInt3("slider int3", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat4("input float4", vec4f); + ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); + ImGui::InputInt4("input int4", vec4i); + ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); + ImGui::SliderInt4("slider int4", vec4i, 0, 255); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Vertical Sliders")) + { + const float spacing = 4; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + + static int int_value = 0; + ImGui::VSliderInt("##int", ImVec2(18, 160), &int_value, 0, 5); + ImGui::SameLine(); + + static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; + ImGui::PushID("set1"); + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i / 7.0f, 0.5f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i / 7.0f, 0.9f, 0.9f)); + ImGui::VSliderFloat("##v", ImVec2(18, 160), &values[i], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values[i]); + ImGui::PopStyleColor(4); + ImGui::PopID(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set2"); + static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; + const int rows = 3; + const ImVec2 small_slider_size(18, (float)(int)((160.0f - (rows - 1) * spacing) / rows)); + for (int nx = 0; nx < 4; nx++) + { + if (nx > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + for (int ny = 0; ny < rows; ny++) + { + ImGui::PushID(nx * rows + ny); + ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values2[nx]); + ImGui::PopID(); + } + ImGui::EndGroup(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set3"); + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); + ImGui::VSliderFloat("##v", ImVec2(40, 160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); + ImGui::PopStyleVar(); + ImGui::PopID(); + } + ImGui::PopID(); + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Drag and Drop")) + { + if (ImGui::TreeNode("Drag and drop in standard widgets")) + { + // ColorEdit widgets automatically act as drag source and drag target. + // They are using standardized payload strings IMGUI_PAYLOAD_TYPE_COLOR_3F and IMGUI_PAYLOAD_TYPE_COLOR_4F + // to allow your own widgets to use colors in their drag and drop interaction. + // Also see 'Demo->Widgets->Color/Picker Widgets->Palette' demo. + HelpMarker("You can drag from the color squares."); + static float col1[3] = { 1.0f, 0.0f, 0.2f }; + static float col2[4] = { 0.4f, 0.7f, 0.0f, 0.5f }; + ImGui::ColorEdit3("color 1", col1); + ImGui::ColorEdit4("color 2", col2); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Drag and drop to copy/swap items")) + { + enum Mode + { + Mode_Copy, + Mode_Move, + Mode_Swap + }; + static int mode = 0; + if (ImGui::RadioButton("Copy", mode == Mode_Copy)) { mode = Mode_Copy; } ImGui::SameLine(); + if (ImGui::RadioButton("Move", mode == Mode_Move)) { mode = Mode_Move; } ImGui::SameLine(); + if (ImGui::RadioButton("Swap", mode == Mode_Swap)) { mode = Mode_Swap; } + static const char* names[9] = + { + "Bobby", "Beatrice", "Betty", + "Brianna", "Barry", "Bernard", + "Bibi", "Blaine", "Bryn" + }; + for (int n = 0; n < IM_ARRAYSIZE(names); n++) + { + ImGui::PushID(n); + if ((n % 3) != 0) + ImGui::SameLine(); + ImGui::Button(names[n], ImVec2(60, 60)); + + // Our buttons are both drag sources and drag targets here! + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + // Set payload to carry the index of our item (could be anything) + ImGui::SetDragDropPayload("DND_DEMO_CELL", &n, sizeof(int)); + + // Display preview (could be anything, e.g. when dragging an image we could decide to display + // the filename and a small preview of the image, etc.) + if (mode == Mode_Copy) { ImGui::Text("Copy %s", names[n]); } + if (mode == Mode_Move) { ImGui::Text("Move %s", names[n]); } + if (mode == Mode_Swap) { ImGui::Text("Swap %s", names[n]); } + ImGui::EndDragDropSource(); + } + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_DEMO_CELL")) + { + IM_ASSERT(payload->DataSize == sizeof(int)); + int payload_n = *(const int*)payload->Data; + if (mode == Mode_Copy) + { + names[n] = names[payload_n]; + } + if (mode == Mode_Move) + { + names[n] = names[payload_n]; + names[payload_n] = ""; + } + if (mode == Mode_Swap) + { + const char* tmp = names[n]; + names[n] = names[payload_n]; + names[payload_n] = tmp; + } + } + ImGui::EndDragDropTarget(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Drag to reorder items (simple)")) + { + // Simple reordering + HelpMarker( + "We don't use the drag and drop api at all here! " + "Instead we query when the item is held but not hovered, and order items accordingly."); + static const char* item_names[] = { "Item One", "Item Two", "Item Three", "Item Four", "Item Five" }; + for (int n = 0; n < IM_ARRAYSIZE(item_names); n++) + { + const char* item = item_names[n]; + ImGui::Selectable(item); + + if (ImGui::IsItemActive() && !ImGui::IsItemHovered()) + { + int n_next = n + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1); + if (n_next >= 0 && n_next < IM_ARRAYSIZE(item_names)) + { + item_names[n] = item_names[n_next]; + item_names[n_next] = item; + ImGui::ResetMouseDragDelta(); + } + } + } + ImGui::TreePop(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Querying Status (Edited/Active/Focused/Hovered etc.)")) + { + // Select an item type + const char* item_names[] = + { + "Text", "Button", "Button (w/ repeat)", "Checkbox", "SliderFloat", "InputText", "InputFloat", + "InputFloat3", "ColorEdit4", "MenuItem", "TreeNode", "TreeNode (w/ double-click)", "Combo", "ListBox" + }; + static int item_type = 1; + ImGui::Combo("Item Type", &item_type, item_names, IM_ARRAYSIZE(item_names), IM_ARRAYSIZE(item_names)); + ImGui::SameLine(); + HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered()."); + + // Submit selected item item so we can query their status in the code following it. + bool ret = false; + static bool b = false; + static float col4f[4] = { 1.0f, 0.5, 0.0f, 1.0f }; + static char str[16] = {}; + if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction + if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button + if (item_type == 2) { ImGui::PushButtonRepeat(true); ret = ImGui::Button("ITEM: Button"); ImGui::PopButtonRepeat(); } // Testing button (with repeater) + if (item_type == 3) { ret = ImGui::Checkbox("ITEM: Checkbox", &b); } // Testing checkbox + if (item_type == 4) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); } // Testing basic item + if (item_type == 5) { ret = ImGui::InputText("ITEM: InputText", &str[0], IM_ARRAYSIZE(str)); } // Testing input text (which handles tabbing) + if (item_type == 6) { ret = ImGui::InputFloat("ITEM: InputFloat", col4f, 1.0f); } // Testing +/- buttons on scalar input + if (item_type == 7) { ret = ImGui::InputFloat3("ITEM: InputFloat3", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) + if (item_type == 8) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) + if (item_type == 9) { ret = ImGui::MenuItem("ITEM: MenuItem"); } // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy) + if (item_type == 10){ ret = ImGui::TreeNode("ITEM: TreeNode"); if (ret) ImGui::TreePop(); } // Testing tree node + if (item_type == 11){ ret = ImGui::TreeNodeEx("ITEM: TreeNode w/ ImGuiTreeNodeFlags_OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_NoTreePushOnOpen); } // Testing tree node with ImGuiButtonFlags_PressedOnDoubleClick button policy. + if (item_type == 12){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::Combo("ITEM: Combo", ¤t, items, IM_ARRAYSIZE(items)); } + if (item_type == 13){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } + + // Display the values of IsItemHovered() and other common item state functions. + // Note that the ImGuiHoveredFlags_XXX flags can be combined. + // Because BulletText is an item itself and that would affect the output of IsItemXXX functions, + // we query every state in a single call to avoid storing them and to simplify the code. + ImGui::BulletText( + "Return value = %d\n" + "IsItemFocused() = %d\n" + "IsItemHovered() = %d\n" + "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsItemHovered(_AllowWhenOverlapped) = %d\n" + "IsItemHovered(_RectOnly) = %d\n" + "IsItemActive() = %d\n" + "IsItemEdited() = %d\n" + "IsItemActivated() = %d\n" + "IsItemDeactivated() = %d\n" + "IsItemDeactivatedAfterEdit() = %d\n" + "IsItemVisible() = %d\n" + "IsItemClicked() = %d\n" + "IsItemToggledOpen() = %d\n" + "GetItemRectMin() = (%.1f, %.1f)\n" + "GetItemRectMax() = (%.1f, %.1f)\n" + "GetItemRectSize() = (%.1f, %.1f)", + ret, + ImGui::IsItemFocused(), + ImGui::IsItemHovered(), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly), + ImGui::IsItemActive(), + ImGui::IsItemEdited(), + ImGui::IsItemActivated(), + ImGui::IsItemDeactivated(), + ImGui::IsItemDeactivatedAfterEdit(), + ImGui::IsItemVisible(), + ImGui::IsItemClicked(), + ImGui::IsItemToggledOpen(), + ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y, + ImGui::GetItemRectMax().x, ImGui::GetItemRectMax().y, + ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y + ); + + static bool embed_all_inside_a_child_window = false; + ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window); + if (embed_all_inside_a_child_window) + ImGui::BeginChild("outer_child", ImVec2(0, ImGui::GetFontSize() * 20.0f), true); + + // Testing IsWindowFocused() function with its various flags. + // Note that the ImGuiFocusedFlags_XXX flags can be combined. + ImGui::BulletText( + "IsWindowFocused() = %d\n" + "IsWindowFocused(_ChildWindows) = %d\n" + "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" + "IsWindowFocused(_RootWindow) = %d\n" + "IsWindowFocused(_AnyWindow) = %d\n", + ImGui::IsWindowFocused(), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); + + // Testing IsWindowHovered() function with its various flags. + // Note that the ImGuiHoveredFlags_XXX flags can be combined. + ImGui::BulletText( + "IsWindowHovered() = %d\n" + "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsWindowHovered(_ChildWindows) = %d\n" + "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" + "IsWindowHovered(_ChildWindows|_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_RootWindow) = %d\n" + "IsWindowHovered(_AnyWindow) = %d\n", + ImGui::IsWindowHovered(), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)); + + ImGui::BeginChild("child", ImVec2(0, 50), true); + ImGui::Text("This is another child window for testing the _ChildWindows flag."); + ImGui::EndChild(); + if (embed_all_inside_a_child_window) + ImGui::EndChild(); + + static char unused_str[] = "This widget is only here to be able to tab-out of the widgets above."; + ImGui::InputText("unused", unused_str, IM_ARRAYSIZE(unused_str), ImGuiInputTextFlags_ReadOnly); + + // Calling IsItemHovered() after begin returns the hovered status of the title bar. + // This is useful in particular if you want to create a context menu associated to the title bar of a window. + static bool test_window = false; + ImGui::Checkbox("Hovered/Active tests after Begin() for title bar testing", &test_window); + if (test_window) + { + ImGui::Begin("Title bar Hovered/Active tests", &test_window); + if (ImGui::BeginPopupContextItem()) // <-- This is using IsItemHovered() + { + if (ImGui::MenuItem("Close")) { test_window = false; } + ImGui::EndPopup(); + } + ImGui::Text( + "IsItemHovered() after begin = %d (== is title bar hovered)\n" + "IsItemActive() after begin = %d (== is window being clicked/moved)\n", + ImGui::IsItemHovered(), ImGui::IsItemActive()); + ImGui::End(); + } + + ImGui::TreePop(); + } +} + +static void ShowDemoWindowLayout() +{ + if (!ImGui::CollapsingHeader("Layout & Scrolling")) + return; + + if (ImGui::TreeNode("Child windows")) + { + HelpMarker("Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window."); + static bool disable_mouse_wheel = false; + static bool disable_menu = false; + ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); + ImGui::Checkbox("Disable Menu", &disable_menu); + + // Child 1: no border, enable horizontal scrollbar + { + ImGuiWindowFlags window_flags = ImGuiWindowFlags_HorizontalScrollbar; + if (disable_mouse_wheel) + window_flags |= ImGuiWindowFlags_NoScrollWithMouse; + ImGui::BeginChild("ChildL", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, 260), false, window_flags); + for (int i = 0; i < 100; i++) + ImGui::Text("%04d: scrollable region", i); + ImGui::EndChild(); + } + + ImGui::SameLine(); + + // Child 2: rounded border + { + ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; + if (disable_mouse_wheel) + window_flags |= ImGuiWindowFlags_NoScrollWithMouse; + if (!disable_menu) + window_flags |= ImGuiWindowFlags_MenuBar; + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::BeginChild("ChildR", ImVec2(0, 260), true, window_flags); + if (!disable_menu && ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + if (ImGui::BeginTable("split", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings)) + { + for (int i = 0; i < 100; i++) + { + char buf[32]; + sprintf(buf, "%03d", i); + ImGui::TableNextColumn(); + ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f)); + } + ImGui::EndTable(); + } + ImGui::EndChild(); + ImGui::PopStyleVar(); + } + + ImGui::Separator(); + + // Demonstrate a few extra things + // - Changing ImGuiCol_ChildBg (which is transparent black in default styles) + // - Using SetCursorPos() to position child window (the child window is an item from the POV of parent window) + // You can also call SetNextWindowPos() to position the child window. The parent window will effectively + // layout from this position. + // - Using ImGui::GetItemRectMin/Max() to query the "item" state (because the child window is an item from + // the POV of the parent window). See 'Demo->Querying Status (Active/Focused/Hovered etc.)' for details. + { + static int offset_x = 0; + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::DragInt("Offset X", &offset_x, 1.0f, -1000, 1000); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (float)offset_x); + ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 0, 0, 100)); + ImGui::BeginChild("Red", ImVec2(200, 100), true, ImGuiWindowFlags_None); + for (int n = 0; n < 50; n++) + ImGui::Text("Some test %d", n); + ImGui::EndChild(); + bool child_is_hovered = ImGui::IsItemHovered(); + ImVec2 child_rect_min = ImGui::GetItemRectMin(); + ImVec2 child_rect_max = ImGui::GetItemRectMax(); + ImGui::PopStyleColor(); + ImGui::Text("Hovered: %d", child_is_hovered); + ImGui::Text("Rect of child window is: (%.0f,%.0f) (%.0f,%.0f)", child_rect_min.x, child_rect_min.y, child_rect_max.x, child_rect_max.y); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Widgets Width")) + { + static float f = 0.0f; + static bool show_indented_items = true; + ImGui::Checkbox("Show indented items", &show_indented_items); + + // Use SetNextItemWidth() to set the width of a single upcoming item. + // Use PushItemWidth()/PopItemWidth() to set the width of a group of items. + // In real code use you'll probably want to choose width values that are proportional to your font size + // e.g. Using '20.0f * GetFontSize()' as width instead of '200.0f', etc. + + ImGui::Text("SetNextItemWidth/PushItemWidth(100)"); + ImGui::SameLine(); HelpMarker("Fixed width."); + ImGui::PushItemWidth(100); + ImGui::DragFloat("float##1b", &f); + if (show_indented_items) + { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##1b", &f); + ImGui::Unindent(); + } + ImGui::PopItemWidth(); + + ImGui::Text("SetNextItemWidth/PushItemWidth(-100)"); + ImGui::SameLine(); HelpMarker("Align to right edge minus 100"); + ImGui::PushItemWidth(-100); + ImGui::DragFloat("float##2a", &f); + if (show_indented_items) + { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##2b", &f); + ImGui::Unindent(); + } + ImGui::PopItemWidth(); + + ImGui::Text("SetNextItemWidth/PushItemWidth(GetContentRegionAvail().x * 0.5f)"); + ImGui::SameLine(); HelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)"); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f); + ImGui::DragFloat("float##3a", &f); + if (show_indented_items) + { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##3b", &f); + ImGui::Unindent(); + } + ImGui::PopItemWidth(); + + ImGui::Text("SetNextItemWidth/PushItemWidth(-GetContentRegionAvail().x * 0.5f)"); + ImGui::SameLine(); HelpMarker("Align to right edge minus half"); + ImGui::PushItemWidth(-ImGui::GetContentRegionAvail().x * 0.5f); + ImGui::DragFloat("float##4a", &f); + if (show_indented_items) + { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##4b", &f); + ImGui::Unindent(); + } + ImGui::PopItemWidth(); + + // Demonstrate using PushItemWidth to surround three items. + // Calling SetNextItemWidth() before each of them would have the same effect. + ImGui::Text("SetNextItemWidth/PushItemWidth(-FLT_MIN)"); + ImGui::SameLine(); HelpMarker("Align to right edge"); + ImGui::PushItemWidth(-FLT_MIN); + ImGui::DragFloat("##float5a", &f); + if (show_indented_items) + { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##5b", &f); + ImGui::Unindent(); + } + ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Basic Horizontal Layout")) + { + ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); + + // Text + ImGui::Text("Two items: Hello"); ImGui::SameLine(); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Adjust spacing + ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Button + ImGui::AlignTextToFramePadding(); + ImGui::Text("Normal buttons"); ImGui::SameLine(); + ImGui::Button("Banana"); ImGui::SameLine(); + ImGui::Button("Apple"); ImGui::SameLine(); + ImGui::Button("Corniflower"); + + // Button + ImGui::Text("Small buttons"); ImGui::SameLine(); + ImGui::SmallButton("Like this one"); ImGui::SameLine(); + ImGui::Text("can fit within a text block."); + + // Aligned to arbitrary position. Easy/cheap column. + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::Text("x=150"); + ImGui::SameLine(300); ImGui::Text("x=300"); + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::SmallButton("x=150"); + ImGui::SameLine(300); ImGui::SmallButton("x=300"); + + // Checkbox + static bool c1 = false, c2 = false, c3 = false, c4 = false; + ImGui::Checkbox("My", &c1); ImGui::SameLine(); + ImGui::Checkbox("Tailor", &c2); ImGui::SameLine(); + ImGui::Checkbox("Is", &c3); ImGui::SameLine(); + ImGui::Checkbox("Rich", &c4); + + // Various + static float f0 = 1.0f, f1 = 2.0f, f2 = 3.0f; + ImGui::PushItemWidth(80); + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" }; + static int item = -1; + ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine(); + ImGui::SliderFloat("X", &f0, 0.0f, 5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Y", &f1, 0.0f, 5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Z", &f2, 0.0f, 5.0f); + ImGui::PopItemWidth(); + + ImGui::PushItemWidth(80); + ImGui::Text("Lists:"); + static int selection[4] = { 0, 1, 2, 3 }; + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); + ImGui::PopID(); + //if (ImGui::IsItemHovered()) ImGui::SetTooltip("ListBox %d hovered", i); + } + ImGui::PopItemWidth(); + + // Dummy + ImVec2 button_sz(40, 40); + ImGui::Button("A", button_sz); ImGui::SameLine(); + ImGui::Dummy(button_sz); ImGui::SameLine(); + ImGui::Button("B", button_sz); + + // Manually wrapping + // (we should eventually provide this as an automatic layout feature, but for now you can do it manually) + ImGui::Text("Manually wrapping:"); + ImGuiStyle& style = ImGui::GetStyle(); + int buttons_count = 20; + float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; + for (int n = 0; n < buttons_count; n++) + { + ImGui::PushID(n); + ImGui::Button("Box", button_sz); + float last_button_x2 = ImGui::GetItemRectMax().x; + float next_button_x2 = last_button_x2 + style.ItemSpacing.x + button_sz.x; // Expected position if next button was on same line + if (n + 1 < buttons_count && next_button_x2 < window_visible_x2) + ImGui::SameLine(); + ImGui::PopID(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Groups")) + { + HelpMarker( + "BeginGroup() basically locks the horizontal position for new line. " + "EndGroup() bundles the whole group so that you can use \"item\" functions such as " + "IsItemHovered()/IsItemActive() or SameLine() etc. on the whole group."); + ImGui::BeginGroup(); + { + ImGui::BeginGroup(); + ImGui::Button("AAA"); + ImGui::SameLine(); + ImGui::Button("BBB"); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Button("CCC"); + ImGui::Button("DDD"); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::Button("EEE"); + ImGui::EndGroup(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("First group hovered"); + } + // Capture the group size and create widgets using the same size + ImVec2 size = ImGui::GetItemRectSize(); + const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; + ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); + + ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x) * 0.5f, size.y)); + ImGui::SameLine(); + ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x) * 0.5f, size.y)); + ImGui::EndGroup(); + ImGui::SameLine(); + + ImGui::Button("LEVERAGE\nBUZZWORD", size); + ImGui::SameLine(); + + if (ImGui::BeginListBox("List", size)) + { + ImGui::Selectable("Selected", true); + ImGui::Selectable("Not Selected", false); + ImGui::EndListBox(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text Baseline Alignment")) + { + { + ImGui::BulletText("Text baseline:"); + ImGui::SameLine(); HelpMarker( + "This is testing the vertical alignment that gets applied on text to keep it aligned with widgets. " + "Lines only composed of text or \"small\" widgets use less vertical space than lines with framed widgets."); + ImGui::Indent(); + + ImGui::Text("KO Blahblah"); ImGui::SameLine(); + ImGui::Button("Some framed item"); ImGui::SameLine(); + HelpMarker("Baseline of button will look misaligned with text.."); + + // If your line starts with text, call AlignTextToFramePadding() to align text to upcoming widgets. + // (because we don't know what's coming after the Text() statement, we need to move the text baseline + // down by FramePadding.y ahead of time) + ImGui::AlignTextToFramePadding(); + ImGui::Text("OK Blahblah"); ImGui::SameLine(); + ImGui::Button("Some framed item"); ImGui::SameLine(); + HelpMarker("We call AlignTextToFramePadding() to vertically align the text baseline by +FramePadding.y"); + + // SmallButton() uses the same vertical padding as Text + ImGui::Button("TEST##1"); ImGui::SameLine(); + ImGui::Text("TEST"); ImGui::SameLine(); + ImGui::SmallButton("TEST##2"); + + // If your line starts with text, call AlignTextToFramePadding() to align text to upcoming widgets. + ImGui::AlignTextToFramePadding(); + ImGui::Text("Text aligned to framed item"); ImGui::SameLine(); + ImGui::Button("Item##1"); ImGui::SameLine(); + ImGui::Text("Item"); ImGui::SameLine(); + ImGui::SmallButton("Item##2"); ImGui::SameLine(); + ImGui::Button("Item##3"); + + ImGui::Unindent(); + } + + ImGui::Spacing(); + + { + ImGui::BulletText("Multi-line text:"); + ImGui::Indent(); + ImGui::Text("One\nTwo\nThree"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("One\nTwo\nThree"); + + ImGui::Button("HOP##1"); ImGui::SameLine(); + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("HOP##2"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + ImGui::Unindent(); + } + + ImGui::Spacing(); + + { + ImGui::BulletText("Misc items:"); + ImGui::Indent(); + + // SmallButton() sets FramePadding to zero. Text baseline is aligned to match baseline of previous Button. + ImGui::Button("80x80", ImVec2(80, 80)); + ImGui::SameLine(); + ImGui::Button("50x50", ImVec2(50, 50)); + ImGui::SameLine(); + ImGui::Button("Button()"); + ImGui::SameLine(); + ImGui::SmallButton("SmallButton()"); + + // Tree + const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::Button("Button##1"); + ImGui::SameLine(0.0f, spacing); + if (ImGui::TreeNode("Node##1")) + { + // Placeholder tree data + for (int i = 0; i < 6; i++) + ImGui::BulletText("Item %d..", i); + ImGui::TreePop(); + } + + // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. + // Otherwise you can use SmallButton() (smaller fit). + ImGui::AlignTextToFramePadding(); + + // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add + // other contents below the node. + bool node_open = ImGui::TreeNode("Node##2"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); + if (node_open) + { + // Placeholder tree data + for (int i = 0; i < 6; i++) + ImGui::BulletText("Item %d..", i); + ImGui::TreePop(); + } + + // Bullet + ImGui::Button("Button##3"); + ImGui::SameLine(0.0f, spacing); + ImGui::BulletText("Bullet text"); + + ImGui::AlignTextToFramePadding(); + ImGui::BulletText("Node"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); + ImGui::Unindent(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Scrolling")) + { + // Vertical scroll functions + HelpMarker("Use SetScrollHereY() or SetScrollFromPosY() to scroll to a given vertical position."); + + static int track_item = 50; + static bool enable_track = true; + static bool enable_extra_decorations = false; + static float scroll_to_off_px = 0.0f; + static float scroll_to_pos_px = 200.0f; + + ImGui::Checkbox("Decoration", &enable_extra_decorations); + + ImGui::Checkbox("Track", &enable_track); + ImGui::PushItemWidth(100); + ImGui::SameLine(140); enable_track |= ImGui::DragInt("##item", &track_item, 0.25f, 0, 99, "Item = %d"); + + bool scroll_to_off = ImGui::Button("Scroll Offset"); + ImGui::SameLine(140); scroll_to_off |= ImGui::DragFloat("##off", &scroll_to_off_px, 1.00f, 0, FLT_MAX, "+%.0f px"); + + bool scroll_to_pos = ImGui::Button("Scroll To Pos"); + ImGui::SameLine(140); scroll_to_pos |= ImGui::DragFloat("##pos", &scroll_to_pos_px, 1.00f, -10, FLT_MAX, "X/Y = %.0f px"); + ImGui::PopItemWidth(); + + if (scroll_to_off || scroll_to_pos) + enable_track = false; + + ImGuiStyle& style = ImGui::GetStyle(); + float child_w = (ImGui::GetContentRegionAvail().x - 4 * style.ItemSpacing.x) / 5; + if (child_w < 1.0f) + child_w = 1.0f; + ImGui::PushID("##VerticalScrolling"); + for (int i = 0; i < 5; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + const char* names[] = { "Top", "25%", "Center", "75%", "Bottom" }; + ImGui::TextUnformatted(names[i]); + + const ImGuiWindowFlags child_flags = enable_extra_decorations ? ImGuiWindowFlags_MenuBar : 0; + const ImGuiID child_id = ImGui::GetID((void*)(intptr_t)i); + const bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(child_w, 200.0f), true, child_flags); + if (ImGui::BeginMenuBar()) + { + ImGui::TextUnformatted("abc"); + ImGui::EndMenuBar(); + } + if (scroll_to_off) + ImGui::SetScrollY(scroll_to_off_px); + if (scroll_to_pos) + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_pos_px, i * 0.25f); + if (child_is_visible) // Avoid calling SetScrollHereY when running with culled items + { + for (int item = 0; item < 100; item++) + { + if (enable_track && item == track_item) + { + ImGui::TextColored(ImVec4(1, 1, 0, 1), "Item %d", item); + ImGui::SetScrollHereY(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom + } + else + { + ImGui::Text("Item %d", item); + } + } + } + float scroll_y = ImGui::GetScrollY(); + float scroll_max_y = ImGui::GetScrollMaxY(); + ImGui::EndChild(); + ImGui::Text("%.0f/%.0f", scroll_y, scroll_max_y); + ImGui::EndGroup(); + } + ImGui::PopID(); + + // Horizontal scroll functions + ImGui::Spacing(); + HelpMarker( + "Use SetScrollHereX() or SetScrollFromPosX() to scroll to a given horizontal position.\n\n" + "Because the clipping rectangle of most window hides half worth of WindowPadding on the " + "left/right, using SetScrollFromPosX(+1) will usually result in clipped text whereas the " + "equivalent SetScrollFromPosY(+1) wouldn't."); + ImGui::PushID("##HorizontalScrolling"); + for (int i = 0; i < 5; i++) + { + float child_height = ImGui::GetTextLineHeight() + style.ScrollbarSize + style.WindowPadding.y * 2.0f; + ImGuiWindowFlags child_flags = ImGuiWindowFlags_HorizontalScrollbar | (enable_extra_decorations ? ImGuiWindowFlags_AlwaysVerticalScrollbar : 0); + ImGuiID child_id = ImGui::GetID((void*)(intptr_t)i); + bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(-100, child_height), true, child_flags); + if (scroll_to_off) + ImGui::SetScrollX(scroll_to_off_px); + if (scroll_to_pos) + ImGui::SetScrollFromPosX(ImGui::GetCursorStartPos().x + scroll_to_pos_px, i * 0.25f); + if (child_is_visible) // Avoid calling SetScrollHereY when running with culled items + { + for (int item = 0; item < 100; item++) + { + if (item > 0) + ImGui::SameLine(); + if (enable_track && item == track_item) + { + ImGui::TextColored(ImVec4(1, 1, 0, 1), "Item %d", item); + ImGui::SetScrollHereX(i * 0.25f); // 0.0f:left, 0.5f:center, 1.0f:right + } + else + { + ImGui::Text("Item %d", item); + } + } + } + float scroll_x = ImGui::GetScrollX(); + float scroll_max_x = ImGui::GetScrollMaxX(); + ImGui::EndChild(); + ImGui::SameLine(); + const char* names[] = { "Left", "25%", "Center", "75%", "Right" }; + ImGui::Text("%s\n%.0f/%.0f", names[i], scroll_x, scroll_max_x); + ImGui::Spacing(); + } + ImGui::PopID(); + + // Miscellaneous Horizontal Scrolling Demo + HelpMarker( + "Horizontal scrolling for a window is enabled via the ImGuiWindowFlags_HorizontalScrollbar flag.\n\n" + "You may want to also explicitly specify content width by using SetNextWindowContentWidth() before Begin()."); + static int lines = 7; + ImGui::SliderInt("Lines", &lines, 1, 15); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); + ImVec2 scrolling_child_size = ImVec2(0, ImGui::GetFrameHeightWithSpacing() * 7 + 30); + ImGui::BeginChild("scrolling", scrolling_child_size, true, ImGuiWindowFlags_HorizontalScrollbar); + for (int line = 0; line < lines; line++) + { + // Display random stuff. For the sake of this trivial demo we are using basic Button() + SameLine() + // If you want to create your own time line for a real application you may be better off manipulating + // the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets + // yourself. You may also want to use the lower-level ImDrawList API. + int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); + for (int n = 0; n < num_buttons; n++) + { + if (n > 0) ImGui::SameLine(); + ImGui::PushID(n + line * 1000); + char num_buf[16]; + sprintf(num_buf, "%d", n); + const char* label = (!(n % 15)) ? "FizzBuzz" : (!(n % 3)) ? "Fizz" : (!(n % 5)) ? "Buzz" : num_buf; + float hue = n * 0.05f; + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); + ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + } + float scroll_x = ImGui::GetScrollX(); + float scroll_max_x = ImGui::GetScrollMaxX(); + ImGui::EndChild(); + ImGui::PopStyleVar(2); + float scroll_x_delta = 0.0f; + ImGui::SmallButton("<<"); + if (ImGui::IsItemActive()) + scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; + ImGui::SameLine(); + ImGui::Text("Scroll from code"); ImGui::SameLine(); + ImGui::SmallButton(">>"); + if (ImGui::IsItemActive()) + scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; + ImGui::SameLine(); + ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x); + if (scroll_x_delta != 0.0f) + { + // Demonstrate a trick: you can use Begin to set yourself in the context of another window + // (here we are already out of your child window) + ImGui::BeginChild("scrolling"); + ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta); + ImGui::EndChild(); + } + ImGui::Spacing(); + + static bool show_horizontal_contents_size_demo_window = false; + ImGui::Checkbox("Show Horizontal contents size demo window", &show_horizontal_contents_size_demo_window); + + if (show_horizontal_contents_size_demo_window) + { + static bool show_h_scrollbar = true; + static bool show_button = true; + static bool show_tree_nodes = true; + static bool show_text_wrapped = false; + static bool show_columns = true; + static bool show_tab_bar = true; + static bool show_child = false; + static bool explicit_content_size = false; + static float contents_size_x = 300.0f; + if (explicit_content_size) + ImGui::SetNextWindowContentSize(ImVec2(contents_size_x, 0.0f)); + ImGui::Begin("Horizontal contents size demo window", &show_horizontal_contents_size_demo_window, show_h_scrollbar ? ImGuiWindowFlags_HorizontalScrollbar : 0); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 0)); + HelpMarker("Test of different widgets react and impact the work rectangle growing when horizontal scrolling is enabled.\n\nUse 'Metrics->Tools->Show windows rectangles' to visualize rectangles."); + ImGui::Checkbox("H-scrollbar", &show_h_scrollbar); + ImGui::Checkbox("Button", &show_button); // Will grow contents size (unless explicitly overwritten) + ImGui::Checkbox("Tree nodes", &show_tree_nodes); // Will grow contents size and display highlight over full width + ImGui::Checkbox("Text wrapped", &show_text_wrapped);// Will grow and use contents size + ImGui::Checkbox("Columns", &show_columns); // Will use contents size + ImGui::Checkbox("Tab bar", &show_tab_bar); // Will use contents size + ImGui::Checkbox("Child", &show_child); // Will grow and use contents size + ImGui::Checkbox("Explicit content size", &explicit_content_size); + ImGui::Text("Scroll %.1f/%.1f %.1f/%.1f", ImGui::GetScrollX(), ImGui::GetScrollMaxX(), ImGui::GetScrollY(), ImGui::GetScrollMaxY()); + if (explicit_content_size) + { + ImGui::SameLine(); + ImGui::SetNextItemWidth(100); + ImGui::DragFloat("##csx", &contents_size_x); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + 10, p.y + 10), IM_COL32_WHITE); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(p.x + contents_size_x - 10, p.y), ImVec2(p.x + contents_size_x, p.y + 10), IM_COL32_WHITE); + ImGui::Dummy(ImVec2(0, 10)); + } + ImGui::PopStyleVar(2); + ImGui::Separator(); + if (show_button) + { + ImGui::Button("this is a 300-wide button", ImVec2(300, 0)); + } + if (show_tree_nodes) + { + bool open = true; + if (ImGui::TreeNode("this is a tree node")) + { + if (ImGui::TreeNode("another one of those tree node...")) + { + ImGui::Text("Some tree contents"); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::CollapsingHeader("CollapsingHeader", &open); + } + if (show_text_wrapped) + { + ImGui::TextWrapped("This text should automatically wrap on the edge of the work rectangle."); + } + if (show_columns) + { + ImGui::Text("Tables:"); + if (ImGui::BeginTable("table", 4, ImGuiTableFlags_Borders)) + { + for (int n = 0; n < 4; n++) + { + ImGui::TableNextColumn(); + ImGui::Text("Width %.2f", ImGui::GetContentRegionAvail().x); + } + ImGui::EndTable(); + } + ImGui::Text("Columns:"); + ImGui::Columns(4); + for (int n = 0; n < 4; n++) + { + ImGui::Text("Width %.2f", ImGui::GetColumnWidth()); + ImGui::NextColumn(); + } + ImGui::Columns(1); + } + if (show_tab_bar && ImGui::BeginTabBar("Hello")) + { + if (ImGui::BeginTabItem("OneOneOne")) { ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("TwoTwoTwo")) { ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("ThreeThreeThree")) { ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("FourFourFour")) { ImGui::EndTabItem(); } + ImGui::EndTabBar(); + } + if (show_child) + { + ImGui::BeginChild("child", ImVec2(0, 0), true); + ImGui::EndChild(); + } + ImGui::End(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Clipping")) + { + static ImVec2 size(100.0f, 100.0f); + static ImVec2 offset(30.0f, 30.0f); + ImGui::DragFloat2("size", (float*)&size, 0.5f, 1.0f, 200.0f, "%.0f"); + ImGui::TextWrapped("(Click and drag to scroll)"); + + for (int n = 0; n < 3; n++) + { + if (n > 0) + ImGui::SameLine(); + ImGui::PushID(n); + ImGui::BeginGroup(); // Lock X position + + ImGui::InvisibleButton("##empty", size); + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) + { + offset.x += ImGui::GetIO().MouseDelta.x; + offset.y += ImGui::GetIO().MouseDelta.y; + } + const ImVec2 p0 = ImGui::GetItemRectMin(); + const ImVec2 p1 = ImGui::GetItemRectMax(); + const char* text_str = "Line 1 hello\nLine 2 clip me!"; + const ImVec2 text_pos = ImVec2(p0.x + offset.x, p0.y + offset.y); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + switch (n) + { + case 0: + HelpMarker( + "Using ImGui::PushClipRect():\n" + "Will alter ImGui hit-testing logic + ImDrawList rendering.\n" + "(use this if you want your clipping rectangle to affect interactions)"); + ImGui::PushClipRect(p0, p1, true); + draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); + draw_list->AddText(text_pos, IM_COL32_WHITE, text_str); + ImGui::PopClipRect(); + break; + case 1: + HelpMarker( + "Using ImDrawList::PushClipRect():\n" + "Will alter ImDrawList rendering only.\n" + "(use this as a shortcut if you are only using ImDrawList calls)"); + draw_list->PushClipRect(p0, p1, true); + draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); + draw_list->AddText(text_pos, IM_COL32_WHITE, text_str); + draw_list->PopClipRect(); + break; + case 2: + HelpMarker( + "Using ImDrawList::AddText() with a fine ClipRect:\n" + "Will alter only this specific ImDrawList::AddText() rendering.\n" + "(this is often used internally to avoid altering the clipping rectangle and minimize draw calls)"); + ImVec4 clip_rect(p0.x, p0.y, p1.x, p1.y); // AddText() takes a ImVec4* here so let's convert. + draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); + draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize(), text_pos, IM_COL32_WHITE, text_str, NULL, 0.0f, &clip_rect); + break; + } + ImGui::EndGroup(); + ImGui::PopID(); + } + + ImGui::TreePop(); + } +} + +static void ShowDemoWindowPopups() +{ + if (!ImGui::CollapsingHeader("Popups & Modal windows")) + return; + + // The properties of popups windows are: + // - They block normal mouse hovering detection outside them. (*) + // - Unless modal, they can be closed by clicking anywhere outside them, or by pressing ESCAPE. + // - Their visibility state (~bool) is held internally by Dear ImGui instead of being held by the programmer as + // we are used to with regular Begin() calls. User can manipulate the visibility state by calling OpenPopup(). + // (*) One can use IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) to bypass it and detect hovering even + // when normally blocked by a popup. + // Those three properties are connected. The library needs to hold their visibility state BECAUSE it can close + // popups at any time. + + // Typical use for regular windows: + // bool my_tool_is_active = false; if (ImGui::Button("Open")) my_tool_is_active = true; [...] if (my_tool_is_active) Begin("My Tool", &my_tool_is_active) { [...] } End(); + // Typical use for popups: + // if (ImGui::Button("Open")) ImGui::OpenPopup("MyPopup"); if (ImGui::BeginPopup("MyPopup") { [...] EndPopup(); } + + // With popups we have to go through a library call (here OpenPopup) to manipulate the visibility state. + // This may be a bit confusing at first but it should quickly make sense. Follow on the examples below. + + if (ImGui::TreeNode("Popups")) + { + ImGui::TextWrapped( + "When a popup is active, it inhibits interacting with windows that are behind the popup. " + "Clicking outside the popup closes it."); + + static int selected_fish = -1; + const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" }; + static bool toggles[] = { true, false, false, false, false }; + + // Simple selection popup (if you want to show the current selection inside the Button itself, + // you may want to build a string using the "###" operator to preserve a constant ID with a variable label) + if (ImGui::Button("Select..")) + ImGui::OpenPopup("my_select_popup"); + ImGui::SameLine(); + ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); + if (ImGui::BeginPopup("my_select_popup")) + { + ImGui::Text("Aquarium"); + ImGui::Separator(); + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + if (ImGui::Selectable(names[i])) + selected_fish = i; + ImGui::EndPopup(); + } + + // Showing a menu with toggles + if (ImGui::Button("Toggle..")) + ImGui::OpenPopup("my_toggle_popup"); + if (ImGui::BeginPopup("my_toggle_popup")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + + ImGui::Separator(); + ImGui::Text("Tooltip here"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip over a popup"); + + if (ImGui::Button("Stacked Popup")) + ImGui::OpenPopup("another popup"); + if (ImGui::BeginPopup("another popup")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + if (ImGui::Button("Stacked Popup")) + ImGui::OpenPopup("another popup"); + if (ImGui::BeginPopup("another popup")) + { + ImGui::Text("I am the last one here."); + ImGui::EndPopup(); + } + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } + ImGui::EndPopup(); + } + + // Call the more complete ShowExampleMenuFile which we use in various places of this demo + if (ImGui::Button("File Menu..")) + ImGui::OpenPopup("my_file_popup"); + if (ImGui::BeginPopup("my_file_popup")) + { + ShowExampleMenuFile(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Context menus")) + { + HelpMarker("\"Context\" functions are simple helpers to associate a Popup to a given Item or Window identifier."); + + // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: + // if (id == 0) + // id = GetItemID(); // Use last item id + // if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) + // OpenPopup(id); + // return BeginPopup(id); + // For advanced advanced uses you may want to replicate and customize this code. + // See more details in BeginPopupContextItem(). + + // Example 1 + // When used after an item that has an ID (e.g. Button), we can skip providing an ID to BeginPopupContextItem(), + // and BeginPopupContextItem() will use the last item ID as the popup ID. + { + const char* names[5] = { "Label1", "Label2", "Label3", "Label4", "Label5" }; + for (int n = 0; n < 5; n++) + { + ImGui::Selectable(names[n]); + if (ImGui::BeginPopupContextItem()) // <-- use last item id as popup id + { + ImGui::Text("This a popup for \"%s\"!", names[n]); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Right-click to open popup"); + } + } + + // Example 2 + // Popup on a Text() element which doesn't have an identifier: we need to provide an identifier to BeginPopupContextItem(). + // Using an explicit identifier is also convenient if you want to activate the popups from different locations. + { + HelpMarker("Text() elements don't have stable identifiers so we need to provide one."); + static float value = 0.5f; + ImGui::Text("Value = %.3f <-- (1) right-click this value", value); + if (ImGui::BeginPopupContextItem("my popup")) + { + if (ImGui::Selectable("Set to zero")) value = 0.0f; + if (ImGui::Selectable("Set to PI")) value = 3.1415f; + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); + ImGui::EndPopup(); + } + + // We can also use OpenPopupOnItemClick() to toggle the visibility of a given popup. + // Here we make it that right-clicking this other text element opens the same popup as above. + // The popup itself will be submitted by the code above. + ImGui::Text("(2) Or right-click this text"); + ImGui::OpenPopupOnItemClick("my popup", ImGuiPopupFlags_MouseButtonRight); + + // Back to square one: manually open the same popup. + if (ImGui::Button("(3) Or click this button")) + ImGui::OpenPopup("my popup"); + } + + // Example 3 + // When using BeginPopupContextItem() with an implicit identifier (NULL == use last item ID), + // we need to make sure your item identifier is stable. + // In this example we showcase altering the item label while preserving its identifier, using the ### operator (see FAQ). + { + HelpMarker("Showcase using a popup ID linked to item ID, with the item having a changing label + stable ID using the ### operator."); + static char name[32] = "Label1"; + char buf[64]; + sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label + ImGui::Button(buf); + if (ImGui::BeginPopupContextItem()) + { + ImGui::Text("Edit name:"); + ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Modals")) + { + ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside."); + + if (ImGui::Button("Delete..")) + ImGui::OpenPopup("Delete?"); + + // Always center this window when appearing + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); + ImGui::Separator(); + + //static int unused_i = 0; + //ImGui::Combo("Combo", &unused_i, "Delete\0Delete harder\0"); + + static bool dont_ask_me_next_time = false; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time); + ImGui::PopStyleVar(); + + if (ImGui::Button("OK", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } + ImGui::SetItemDefaultFocus(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } + ImGui::EndPopup(); + } + + if (ImGui::Button("Stacked modals..")) + ImGui::OpenPopup("Stacked 1"); + if (ImGui::BeginPopupModal("Stacked 1", NULL, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Some menu item")) {} + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDimBg] behind it."); + + // Testing behavior of widgets stacking their own regular popups over the modal. + static int item = 1; + static float color[4] = { 0.4f, 0.7f, 0.0f, 0.5f }; + ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + ImGui::ColorEdit4("color", color); + + if (ImGui::Button("Add another modal..")) + ImGui::OpenPopup("Stacked 2"); + + // Also demonstrate passing a bool* to BeginPopupModal(), this will create a regular close button which + // will close the popup. Note that the visibility state of popups is owned by imgui, so the input value + // of the bool actually doesn't matter here. + bool unused_open = true; + if (ImGui::BeginPopupModal("Stacked 2", &unused_open)) + { + ImGui::Text("Hello from Stacked The Second!"); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Menus inside a regular window")) + { + ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); + ImGui::Separator(); + + // Note: As a quirk in this very specific example, we want to differentiate the parent of this menu from the + // parent of the various popup menus above. To do so we are encloding the items in a PushID()/PopID() block + // to make them two different menusets. If we don't, opening any popup above and hovering our menu here would + // open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, + // which is the desired behavior for regular menus. + ImGui::PushID("foo"); + ImGui::MenuItem("Menu item", "CTRL+M"); + if (ImGui::BeginMenu("Menu inside a regular window")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::PopID(); + ImGui::Separator(); + ImGui::TreePop(); + } +} + +// Dummy data structure that we use for the Table demo. +// (pre-C++11 doesn't allow us to instantiate ImVector template if this structure if defined inside the demo function) +namespace +{ +// We are passing our own identifier to TableSetupColumn() to facilitate identifying columns in the sorting code. +// This identifier will be passed down into ImGuiTableSortSpec::ColumnUserID. +// But it is possible to omit the user id parameter of TableSetupColumn() and just use the column index instead! (ImGuiTableSortSpec::ColumnIndex) +// If you don't use sorting, you will generally never care about giving column an ID! +enum MyItemColumnID +{ + MyItemColumnID_ID, + MyItemColumnID_Name, + MyItemColumnID_Action, + MyItemColumnID_Quantity, + MyItemColumnID_Description +}; + +struct MyItem +{ + int ID; + const char* Name; + int Quantity; + + // We have a problem which is affecting _only this demo_ and should not affect your code: + // As we don't rely on std:: or other third-party library to compile dear imgui, we only have reliable access to qsort(), + // however qsort doesn't allow passing user data to comparing function. + // As a workaround, we are storing the sort specs in a static/global for the comparing function to access. + // In your own use case you would probably pass the sort specs to your sorting/comparing functions directly and not use a global. + // We could technically call ImGui::TableGetSortSpecs() in CompareWithSortSpecs(), but considering that this function is called + // very often by the sorting algorithm it would be a little wasteful. + static const ImGuiTableSortSpecs* s_current_sort_specs; + + // Compare function to be used by qsort() + static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs) + { + const MyItem* a = (const MyItem*)lhs; + const MyItem* b = (const MyItem*)rhs; + for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) + { + // Here we identify columns using the ColumnUserID value that we ourselves passed to TableSetupColumn() + // We could also choose to identify columns based on their index (sort_spec->ColumnIndex), which is simpler! + const ImGuiTableColumnSortSpecs* sort_spec = &s_current_sort_specs->Specs[n]; + int delta = 0; + switch (sort_spec->ColumnUserID) + { + case MyItemColumnID_ID: delta = (a->ID - b->ID); break; + case MyItemColumnID_Name: delta = (strcmp(a->Name, b->Name)); break; + case MyItemColumnID_Quantity: delta = (a->Quantity - b->Quantity); break; + case MyItemColumnID_Description: delta = (strcmp(a->Name, b->Name)); break; + default: IM_ASSERT(0); break; + } + if (delta > 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1; + if (delta < 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; + } + + // qsort() is instable so always return a way to differenciate items. + // Your own compare function may want to avoid fallback on implicit sort specs e.g. a Name compare if it wasn't already part of the sort specs. + return (a->ID - b->ID); + } +}; +const ImGuiTableSortSpecs* MyItem::s_current_sort_specs = NULL; +} + +// Make the UI compact because there are so many fields +static void PushStyleCompact() +{ + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, (float)(int)(style.FramePadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, (float)(int)(style.ItemSpacing.y * 0.60f))); +} + +static void PopStyleCompact() +{ + ImGui::PopStyleVar(2); +} + +// Show a combo box with a choice of sizing policies +static void EditTableSizingFlags(ImGuiTableFlags* p_flags) +{ + struct EnumDesc { ImGuiTableFlags Value; const char* Name; const char* Tooltip; }; + static const EnumDesc policies[] = + { + { ImGuiTableFlags_None, "Default", "Use default sizing policy:\n- ImGuiTableFlags_SizingFixedFit if ScrollX is on or if host window has ImGuiWindowFlags_AlwaysAutoResize.\n- ImGuiTableFlags_SizingStretchSame otherwise." }, + { ImGuiTableFlags_SizingFixedFit, "ImGuiTableFlags_SizingFixedFit", "Columns default to _WidthFixed (if resizable) or _WidthAuto (if not resizable), matching contents width." }, + { ImGuiTableFlags_SizingFixedSame, "ImGuiTableFlags_SizingFixedSame", "Columns are all the same width, matching the maximum contents width.\nImplicitly disable ImGuiTableFlags_Resizable and enable ImGuiTableFlags_NoKeepColumnsVisible." }, + { ImGuiTableFlags_SizingStretchProp, "ImGuiTableFlags_SizingStretchProp", "Columns default to _WidthStretch with weights proportional to their widths." }, + { ImGuiTableFlags_SizingStretchSame, "ImGuiTableFlags_SizingStretchSame", "Columns default to _WidthStretch with same weights." } + }; + int idx; + for (idx = 0; idx < IM_ARRAYSIZE(policies); idx++) + if (policies[idx].Value == (*p_flags & ImGuiTableFlags_SizingMask_)) + break; + const char* preview_text = (idx < IM_ARRAYSIZE(policies)) ? policies[idx].Name + (idx > 0 ? strlen("ImGuiTableFlags") : 0) : ""; + if (ImGui::BeginCombo("Sizing Policy", preview_text)) + { + for (int n = 0; n < IM_ARRAYSIZE(policies); n++) + if (ImGui::Selectable(policies[n].Name, idx == n)) + *p_flags = (*p_flags & ~ImGuiTableFlags_SizingMask_) | policies[n].Value; + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 50.0f); + for (int m = 0; m < IM_ARRAYSIZE(policies); m++) + { + ImGui::Separator(); + ImGui::Text("%s:", policies[m].Name); + ImGui::Separator(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetStyle().IndentSpacing * 0.5f); + ImGui::TextUnformatted(policies[m].Tooltip); + } + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +static void EditTableColumnsFlags(ImGuiTableColumnFlags* p_flags) +{ + ImGui::CheckboxFlags("_DefaultHide", p_flags, ImGuiTableColumnFlags_DefaultHide); + ImGui::CheckboxFlags("_DefaultSort", p_flags, ImGuiTableColumnFlags_DefaultSort); + if (ImGui::CheckboxFlags("_WidthStretch", p_flags, ImGuiTableColumnFlags_WidthStretch)) + *p_flags &= ~(ImGuiTableColumnFlags_WidthMask_ ^ ImGuiTableColumnFlags_WidthStretch); + if (ImGui::CheckboxFlags("_WidthFixed", p_flags, ImGuiTableColumnFlags_WidthFixed)) + *p_flags &= ~(ImGuiTableColumnFlags_WidthMask_ ^ ImGuiTableColumnFlags_WidthFixed); + ImGui::CheckboxFlags("_NoResize", p_flags, ImGuiTableColumnFlags_NoResize); + ImGui::CheckboxFlags("_NoReorder", p_flags, ImGuiTableColumnFlags_NoReorder); + ImGui::CheckboxFlags("_NoHide", p_flags, ImGuiTableColumnFlags_NoHide); + ImGui::CheckboxFlags("_NoClip", p_flags, ImGuiTableColumnFlags_NoClip); + ImGui::CheckboxFlags("_NoSort", p_flags, ImGuiTableColumnFlags_NoSort); + ImGui::CheckboxFlags("_NoSortAscending", p_flags, ImGuiTableColumnFlags_NoSortAscending); + ImGui::CheckboxFlags("_NoSortDescending", p_flags, ImGuiTableColumnFlags_NoSortDescending); + ImGui::CheckboxFlags("_NoHeaderWidth", p_flags, ImGuiTableColumnFlags_NoHeaderWidth); + ImGui::CheckboxFlags("_PreferSortAscending", p_flags, ImGuiTableColumnFlags_PreferSortAscending); + ImGui::CheckboxFlags("_PreferSortDescending", p_flags, ImGuiTableColumnFlags_PreferSortDescending); + ImGui::CheckboxFlags("_IndentEnable", p_flags, ImGuiTableColumnFlags_IndentEnable); ImGui::SameLine(); HelpMarker("Default for column 0"); + ImGui::CheckboxFlags("_IndentDisable", p_flags, ImGuiTableColumnFlags_IndentDisable); ImGui::SameLine(); HelpMarker("Default for column >0"); +} + +static void ShowTableColumnsStatusFlags(ImGuiTableColumnFlags flags) +{ + ImGui::CheckboxFlags("_IsEnabled", &flags, ImGuiTableColumnFlags_IsEnabled); + ImGui::CheckboxFlags("_IsVisible", &flags, ImGuiTableColumnFlags_IsVisible); + ImGui::CheckboxFlags("_IsSorted", &flags, ImGuiTableColumnFlags_IsSorted); + ImGui::CheckboxFlags("_IsHovered", &flags, ImGuiTableColumnFlags_IsHovered); +} + +static void ShowDemoWindowTables() +{ + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (!ImGui::CollapsingHeader("Tables & Columns")) + return; + + // Using those as a base value to create width/height that are factor of the size of our font + const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; + const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + + ImGui::PushID("Tables"); + + int open_action = -1; + if (ImGui::Button("Open all")) + open_action = 1; + ImGui::SameLine(); + if (ImGui::Button("Close all")) + open_action = 0; + ImGui::SameLine(); + + // Options + static bool disable_indent = false; + ImGui::Checkbox("Disable tree indentation", &disable_indent); + ImGui::SameLine(); + HelpMarker("Disable the indenting of tree nodes so demo tables can use the full window width."); + ImGui::Separator(); + if (disable_indent) + ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 0.0f); + + // About Styling of tables + // Most settings are configured on a per-table basis via the flags passed to BeginTable() and TableSetupColumns APIs. + // There are however a few settings that a shared and part of the ImGuiStyle structure: + // style.CellPadding // Padding within each cell + // style.Colors[ImGuiCol_TableHeaderBg] // Table header background + // style.Colors[ImGuiCol_TableBorderStrong] // Table outer and header borders + // style.Colors[ImGuiCol_TableBorderLight] // Table inner borders + // style.Colors[ImGuiCol_TableRowBg] // Table row background when ImGuiTableFlags_RowBg is enabled (even rows) + // style.Colors[ImGuiCol_TableRowBgAlt] // Table row background when ImGuiTableFlags_RowBg is enabled (odds rows) + + // Demos + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Basic")) + { + // Here we will showcase three different ways to output a table. + // They are very simple variations of a same thing! + + // [Method 1] Using TableNextRow() to create a new row, and TableSetColumnIndex() to select the column. + // In many situations, this is the most flexible and easy to use pattern. + HelpMarker("Using TableNextRow() + calling TableSetColumnIndex() _before_ each cell, in a loop."); + if (ImGui::BeginTable("table1", 3)) + { + for (int row = 0; row < 4; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Row %d Column %d", row, column); + } + } + ImGui::EndTable(); + } + + // [Method 2] Using TableNextColumn() called multiple times, instead of using a for loop + TableSetColumnIndex(). + // This is generally more convenient when you have code manually submitting the contents of each columns. + HelpMarker("Using TableNextRow() + calling TableNextColumn() _before_ each cell, manually."); + if (ImGui::BeginTable("table2", 3)) + { + for (int row = 0; row < 4; row++) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Row %d", row); + ImGui::TableNextColumn(); + ImGui::Text("Some contents"); + ImGui::TableNextColumn(); + ImGui::Text("123.456"); + } + ImGui::EndTable(); + } + + // [Method 3] We call TableNextColumn() _before_ each cell. We never call TableNextRow(), + // as TableNextColumn() will automatically wrap around and create new roes as needed. + // This is generally more convenient when your cells all contains the same type of data. + HelpMarker( + "Only using TableNextColumn(), which tends to be convenient for tables where every cells contains the same type of contents.\n" + "This is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); + if (ImGui::BeginTable("table3", 3)) + { + for (int item = 0; item < 14; item++) + { + ImGui::TableNextColumn(); + ImGui::Text("Item %d", item); + } + ImGui::EndTable(); + } + + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Borders, background")) + { + // Expose a few Borders related flags interactively + enum ContentsType { CT_Text, CT_FillButton }; + static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; + static bool display_headers = false; + static int contents_type = CT_Text; + + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags, ImGuiTableFlags_RowBg); + ImGui::CheckboxFlags("ImGuiTableFlags_Borders", &flags, ImGuiTableFlags_Borders); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterH"); + ImGui::Indent(); + + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", &flags, ImGuiTableFlags_BordersH); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", &flags, ImGuiTableFlags_BordersOuterH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", &flags, ImGuiTableFlags_BordersInnerH); + ImGui::Unindent(); + + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", &flags, ImGuiTableFlags_BordersOuterV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", &flags, ImGuiTableFlags_BordersInnerV); + ImGui::Unindent(); + + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", &flags, ImGuiTableFlags_BordersOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInner", &flags, ImGuiTableFlags_BordersInner); + ImGui::Unindent(); + + ImGui::AlignTextToFramePadding(); ImGui::Text("Cell contents:"); + ImGui::SameLine(); ImGui::RadioButton("Text", &contents_type, CT_Text); + ImGui::SameLine(); ImGui::RadioButton("FillButton", &contents_type, CT_FillButton); + ImGui::Checkbox("Display headers", &display_headers); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); + PopStyleCompact(); + + if (ImGui::BeginTable("table1", 3, flags)) + { + // Display headers so we can inspect their interaction with borders. + // (Headers are not the main purpose of this section of the demo, so we are not elaborating on them too much. See other sections for details) + if (display_headers) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + } + + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + char buf[32]; + sprintf(buf, "Hello %d,%d", column, row); + if (contents_type == CT_Text) + ImGui::TextUnformatted(buf); + else if (contents_type) + ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f)); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Resizable, stretch")) + { + // By default, if we don't enable ScrollX the sizing policy for each columns is "Stretch" + // Each columns maintain a sizing weight, and they will occupy all available width. + static ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); + ImGui::SameLine(); HelpMarker("Using the _Resizable flag automatically enables the _BordersInnerV flag as well, this is why the resize borders are still showing when unchecking this."); + PopStyleCompact(); + + if (ImGui::BeginTable("table1", 3, flags)) + { + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Resizable, fixed")) + { + // Here we use ImGuiTableFlags_SizingFixedFit (even though _ScrollX is not set) + // So columns will adopt the "Fixed" policy and will maintain a fixed width regardless of the whole available width (unless table is small) + // If there is not enough available width to fit all columns, they will however be resized down. + // FIXME-TABLE: Providing a stretch-on-init would make sense especially for tables which don't have saved settings + HelpMarker( + "Using _Resizable + _SizingFixedFit flags.\n" + "Fixed-width columns generally makes more sense if you want to use horizontal scrolling.\n\n" + "Double-click a column border to auto-fit the column to its contents."); + PushStyleCompact(); + static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody; + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendX", &flags, ImGuiTableFlags_NoHostExtendX); + PopStyleCompact(); + + if (ImGui::BeginTable("table1", 3, flags)) + { + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Resizable, mixed")) + { + HelpMarker( + "Using TableSetupColumn() to alter resizing policy on a per-column basis.\n\n" + "When combining Fixed and Stretch columns, generally you only want one, maybe two trailing columns to use _WidthStretch."); + static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + + if (ImGui::BeginTable("table1", 3, flags)) + { + ImGui::TableSetupColumn("AAA", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("BBB", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("CCC", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableHeadersRow(); + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%s %d,%d", (column == 2) ? "Stretch" : "Fixed", column, row); + } + } + ImGui::EndTable(); + } + if (ImGui::BeginTable("table2", 6, flags)) + { + ImGui::TableSetupColumn("AAA", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("BBB", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("CCC", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("DDD", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("EEE", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("FFF", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultHide); + ImGui::TableHeadersRow(); + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 6; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%s %d,%d", (column >= 3) ? "Stretch" : "Fixed", column, row); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Reorderable, hideable, with headers")) + { + HelpMarker( + "Click and drag column headers to reorder columns.\n\n" + "Right-click on a header to open a context menu."); + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", &flags, ImGuiTableFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", &flags, ImGuiTableFlags_Hideable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers)"); + PopStyleCompact(); + + if (ImGui::BeginTable("table1", 3, flags)) + { + // Submit columns name with TableSetupColumn() and call TableHeadersRow() to create a row with a header in each column. + // (Later we will show how TableSetupColumn() has other uses, optional flags, sizing weight etc.) + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + for (int row = 0; row < 6; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", column, row); + } + } + ImGui::EndTable(); + } + + // Use outer_size.x == 0.0f instead of default to make the table as tight as possible (only valid when no scrolling and no stretch column) + if (ImGui::BeginTable("table2", 3, flags | ImGuiTableFlags_SizingFixedFit, ImVec2(0.0f, 0.0f))) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + for (int row = 0; row < 6; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Fixed %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Padding")) + { + // First example: showcase use of padding flags and effect of BorderOuterV/BorderInnerV on X padding. + // We don't expose BorderOuterH/BorderInnerH here because they have no effect on X padding. + HelpMarker( + "We often want outer padding activated when any using features which makes the edges of a column visible:\n" + "e.g.:\n" + "- BorderOuterV\n" + "- any form of row selection\n" + "Because of this, activating BorderOuterV sets the default to PadOuterX. Using PadOuterX or NoPadOuterX you can override the default.\n\n" + "Actual padding values are using style.CellPadding.\n\n" + "In this demo we don't show horizontal borders to emphasis how they don't affect default horizontal padding."); + + static ImGuiTableFlags flags1 = ImGuiTableFlags_BordersV; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_PadOuterX", &flags1, ImGuiTableFlags_PadOuterX); + ImGui::SameLine(); HelpMarker("Enable outer-most padding (default if ImGuiTableFlags_BordersOuterV is set)"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoPadOuterX", &flags1, ImGuiTableFlags_NoPadOuterX); + ImGui::SameLine(); HelpMarker("Disable outer-most padding (default if ImGuiTableFlags_BordersOuterV is not set)"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoPadInnerX", &flags1, ImGuiTableFlags_NoPadInnerX); + ImGui::SameLine(); HelpMarker("Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off)"); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", &flags1, ImGuiTableFlags_BordersOuterV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", &flags1, ImGuiTableFlags_BordersInnerV); + static bool show_headers = false; + ImGui::Checkbox("show_headers", &show_headers); + PopStyleCompact(); + + if (ImGui::BeginTable("table_padding", 3, flags1)) + { + if (show_headers) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + } + + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + if (row == 0) + { + ImGui::Text("Avail %.2f", ImGui::GetContentRegionAvail().x); + } + else + { + char buf[32]; + sprintf(buf, "Hello %d,%d", column, row); + ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f)); + } + //if (ImGui::TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) + // ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, IM_COL32(0, 100, 0, 255)); + } + } + ImGui::EndTable(); + } + + // Second example: set style.CellPadding to (0.0) or a custom value. + // FIXME-TABLE: Vertical border effectively not displayed the same way as horizontal one... + HelpMarker("Setting style.CellPadding to (0,0) or a custom value."); + static ImGuiTableFlags flags2 = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; + static ImVec2 cell_padding(0.0f, 0.0f); + static bool show_widget_frame_bg = true; + + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Borders", &flags2, ImGuiTableFlags_Borders); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", &flags2, ImGuiTableFlags_BordersH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags2, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInner", &flags2, ImGuiTableFlags_BordersInner); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", &flags2, ImGuiTableFlags_BordersOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags2, ImGuiTableFlags_RowBg); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags2, ImGuiTableFlags_Resizable); + ImGui::Checkbox("show_widget_frame_bg", &show_widget_frame_bg); + ImGui::SliderFloat2("CellPadding", &cell_padding.x, 0.0f, 10.0f, "%.0f"); + PopStyleCompact(); + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cell_padding); + if (ImGui::BeginTable("table_padding_2", 3, flags2)) + { + static char text_bufs[3 * 5][16]; // Mini text storage for 3x5 cells + static bool init = true; + if (!show_widget_frame_bg) + ImGui::PushStyleColor(ImGuiCol_FrameBg, 0); + for (int cell = 0; cell < 3 * 5; cell++) + { + ImGui::TableNextColumn(); + if (init) + strcpy(text_bufs[cell], "edit me"); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::PushID(cell); + ImGui::InputText("##cell", text_bufs[cell], IM_ARRAYSIZE(text_bufs[cell])); + ImGui::PopID(); + } + if (!show_widget_frame_bg) + ImGui::PopStyleColor(); + init = false; + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Sizing policies")) + { + static ImGuiTableFlags flags1 = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_RowBg | ImGuiTableFlags_ContextMenuInBody; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags1, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendX", &flags1, ImGuiTableFlags_NoHostExtendX); + PopStyleCompact(); + + static ImGuiTableFlags sizing_policy_flags[4] = { ImGuiTableFlags_SizingFixedFit, ImGuiTableFlags_SizingFixedSame, ImGuiTableFlags_SizingStretchProp, ImGuiTableFlags_SizingStretchSame }; + for (int table_n = 0; table_n < 4; table_n++) + { + ImGui::PushID(table_n); + ImGui::SetNextItemWidth(TEXT_BASE_WIDTH * 30); + EditTableSizingFlags(&sizing_policy_flags[table_n]); + + // To make it easier to understand the different sizing policy, + // For each policy: we display one table where the columns have equal contents width, and one where the columns have different contents width. + if (ImGui::BeginTable("table1", 3, sizing_policy_flags[table_n] | flags1)) + { + for (int row = 0; row < 3; row++) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("Oh dear"); + ImGui::TableNextColumn(); ImGui::Text("Oh dear"); + ImGui::TableNextColumn(); ImGui::Text("Oh dear"); + } + ImGui::EndTable(); + } + if (ImGui::BeginTable("table2", 3, sizing_policy_flags[table_n] | flags1)) + { + for (int row = 0; row < 3; row++) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); ImGui::Text("AAAA"); + ImGui::TableNextColumn(); ImGui::Text("BBBBBBBB"); + ImGui::TableNextColumn(); ImGui::Text("CCCCCCCCCCCC"); + } + ImGui::EndTable(); + } + ImGui::PopID(); + } + + ImGui::Spacing(); + ImGui::TextUnformatted("Advanced"); + ImGui::SameLine(); + HelpMarker("This section allows you to interact and see the effect of various sizing policies depending on whether Scroll is enabled and the contents of your columns."); + + enum ContentsType { CT_ShowWidth, CT_ShortText, CT_LongText, CT_Button, CT_FillButton, CT_InputText }; + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable; + static int contents_type = CT_ShowWidth; + static int column_count = 3; + + PushStyleCompact(); + ImGui::PushID("Advanced"); + ImGui::PushItemWidth(TEXT_BASE_WIDTH * 30); + EditTableSizingFlags(&flags); + ImGui::Combo("Contents", &contents_type, "Show width\0Short Text\0Long Text\0Button\0Fill Button\0InputText\0"); + if (contents_type == CT_FillButton) + { + ImGui::SameLine(); + HelpMarker("Be mindful that using right-alignment (e.g. size.x = -FLT_MIN) creates a feedback loop where contents width can feed into auto-column width can feed into contents width."); + } + ImGui::DragInt("Columns", &column_count, 0.1f, 1, 64, "%d", ImGuiSliderFlags_AlwaysClamp); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_PreciseWidths", &flags, ImGuiTableFlags_PreciseWidths); + ImGui::SameLine(); HelpMarker("Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth."); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags, ImGuiTableFlags_ScrollX); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); + ImGui::CheckboxFlags("ImGuiTableFlags_NoClip", &flags, ImGuiTableFlags_NoClip); + ImGui::PopItemWidth(); + ImGui::PopID(); + PopStyleCompact(); + + if (ImGui::BeginTable("table2", column_count, flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 7))) + { + for (int cell = 0; cell < 10 * column_count; cell++) + { + ImGui::TableNextColumn(); + int column = ImGui::TableGetColumnIndex(); + int row = ImGui::TableGetRowIndex(); + + ImGui::PushID(cell); + char label[32]; + static char text_buf[32] = ""; + sprintf(label, "Hello %d,%d", column, row); + switch (contents_type) + { + case CT_ShortText: ImGui::TextUnformatted(label); break; + case CT_LongText: ImGui::Text("Some %s text %d,%d\nOver two lines..", column == 0 ? "long" : "longeeer", column, row); break; + case CT_ShowWidth: ImGui::Text("W: %.1f", ImGui::GetContentRegionAvail().x); break; + case CT_Button: ImGui::Button(label); break; + case CT_FillButton: ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); break; + case CT_InputText: ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText("##", text_buf, IM_ARRAYSIZE(text_buf)); break; + } + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Vertical scrolling, with clipping")) + { + HelpMarker("Here we activate ScrollY, which will create a child window container to allow hosting scrollable contents.\n\nWe also demonstrate using ImGuiListClipper to virtualize the submission of many items."); + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); + PopStyleCompact(); + + // When using ScrollX or ScrollY we need to specify a size for our table container! + // Otherwise by default the table will fit all available space, like a BeginChild() call. + ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 8); + if (ImGui::BeginTable("table_scrolly", 3, flags, outer_size)) + { + ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible + ImGui::TableSetupColumn("One", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Two", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Three", ImGuiTableColumnFlags_None); + ImGui::TableHeadersRow(); + + // Demonstrate using clipper for large vertical lists + ImGuiListClipper clipper; + clipper.Begin(1000); + while (clipper.Step()) + { + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", column, row); + } + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Horizontal scrolling")) + { + HelpMarker( + "When ScrollX is enabled, the default sizing policy becomes ImGuiTableFlags_SizingFixedFit, " + "as automatically stretching columns doesn't make much sense with horizontal scrolling.\n\n" + "Also note that as of the current version, you will almost always want to enable ScrollY along with ScrollX," + "because the container window won't automatically extend vertically to fix contents (this may be improved in future versions)."); + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + static int freeze_cols = 1; + static int freeze_rows = 1; + + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags, ImGuiTableFlags_ScrollX); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_cols", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_rows", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + PopStyleCompact(); + + // When using ScrollX or ScrollY we need to specify a size for our table container! + // Otherwise by default the table will fit all available space, like a BeginChild() call. + ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 8); + if (ImGui::BeginTable("table_scrollx", 7, flags, outer_size)) + { + ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); + ImGui::TableSetupColumn("Line #", ImGuiTableColumnFlags_NoHide); // Make the first column not hideable to match our use of TableSetupScrollFreeze() + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableSetupColumn("Four"); + ImGui::TableSetupColumn("Five"); + ImGui::TableSetupColumn("Six"); + ImGui::TableHeadersRow(); + for (int row = 0; row < 20; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 7; column++) + { + // Both TableNextColumn() and TableSetColumnIndex() return true when a column is visible or performing width measurement. + // Because here we know that: + // - A) all our columns are contributing the same to row height + // - B) column 0 is always visible, + // We only always submit this one column and can skip others. + // More advanced per-column clipping behaviors may benefit from polling the status flags via TableGetColumnFlags(). + if (!ImGui::TableSetColumnIndex(column) && column > 0) + continue; + if (column == 0) + ImGui::Text("Line %d", row); + else + ImGui::Text("Hello world %d,%d", column, row); + } + } + ImGui::EndTable(); + } + + ImGui::Spacing(); + ImGui::TextUnformatted("Stretch + ScrollX"); + ImGui::SameLine(); + HelpMarker( + "Showcase using Stretch columns + ScrollX together: " + "this is rather unusual and only makes sense when specifying an 'inner_width' for the table!\n" + "Without an explicit value, inner_width is == outer_size.x and therefore using Stretch columns + ScrollX together doesn't make sense."); + static ImGuiTableFlags flags2 = ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg | ImGuiTableFlags_ContextMenuInBody; + static float inner_width = 1000.0f; + PushStyleCompact(); + ImGui::PushID("flags3"); + ImGui::PushItemWidth(TEXT_BASE_WIDTH * 30); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags2, ImGuiTableFlags_ScrollX); + ImGui::DragFloat("inner_width", &inner_width, 1.0f, 0.0f, FLT_MAX, "%.1f"); + ImGui::PopItemWidth(); + ImGui::PopID(); + PopStyleCompact(); + if (ImGui::BeginTable("table2", 7, flags2, outer_size, inner_width)) + { + for (int cell = 0; cell < 20 * 7; cell++) + { + ImGui::TableNextColumn(); + ImGui::Text("Hello world %d,%d", ImGui::TableGetColumnIndex(), ImGui::TableGetRowIndex()); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Columns flags")) + { + // Create a first table just to show all the options/flags we want to make visible in our example! + const int column_count = 3; + const char* column_names[column_count] = { "One", "Two", "Three" }; + static ImGuiTableColumnFlags column_flags[column_count] = { ImGuiTableColumnFlags_DefaultSort, ImGuiTableColumnFlags_None, ImGuiTableColumnFlags_DefaultHide }; + static ImGuiTableColumnFlags column_flags_out[column_count] = { 0, 0, 0 }; // Output from TableGetColumnFlags() + + if (ImGui::BeginTable("table_columns_flags_checkboxes", column_count, ImGuiTableFlags_None)) + { + PushStyleCompact(); + for (int column = 0; column < column_count; column++) + { + ImGui::TableNextColumn(); + ImGui::PushID(column); + ImGui::AlignTextToFramePadding(); // FIXME-TABLE: Workaround for wrong text baseline propagation + ImGui::Text("'%s'", column_names[column]); + ImGui::Spacing(); + ImGui::Text("Input flags:"); + EditTableColumnsFlags(&column_flags[column]); + ImGui::Spacing(); + ImGui::Text("Output flags:"); + ShowTableColumnsStatusFlags(column_flags_out[column]); + ImGui::PopID(); + } + PopStyleCompact(); + ImGui::EndTable(); + } + + // Create the real table we care about for the example! + // We use a scrolling table to be able to showcase the difference between the _IsEnabled and _IsVisible flags above, otherwise in + // a non-scrolling table columns are always visible (unless using ImGuiTableFlags_NoKeepColumnsVisible + resizing the parent window down) + const ImGuiTableFlags flags + = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY + | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV + | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable; + ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 9); + if (ImGui::BeginTable("table_columns_flags", column_count, flags, outer_size)) + { + for (int column = 0; column < column_count; column++) + ImGui::TableSetupColumn(column_names[column], column_flags[column]); + ImGui::TableHeadersRow(); + for (int column = 0; column < column_count; column++) + column_flags_out[column] = ImGui::TableGetColumnFlags(column); + float indent_step = (float)((int)TEXT_BASE_WIDTH / 2); + for (int row = 0; row < 8; row++) + { + ImGui::Indent(indent_step); // Add some indentation to demonstrate usage of per-column IndentEnable/IndentDisable flags. + ImGui::TableNextRow(); + for (int column = 0; column < column_count; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%s %s", (column == 0) ? "Indented" : "Hello", ImGui::TableGetColumnName(column)); + } + } + ImGui::Unindent(indent_step * 8.0f); + + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Columns widths")) + { + HelpMarker("Using TableSetupColumn() to setup default width."); + + static ImGuiTableFlags flags1 = ImGuiTableFlags_Borders | ImGuiTableFlags_NoBordersInBodyUntilResize; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags1, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags1, ImGuiTableFlags_NoBordersInBodyUntilResize); + PopStyleCompact(); + if (ImGui::BeginTable("table1", 3, flags1)) + { + // We could also set ImGuiTableFlags_SizingFixedFit on the table and all columns will default to ImGuiTableColumnFlags_WidthFixed. + ImGui::TableSetupColumn("one", ImGuiTableColumnFlags_WidthFixed, 100.0f); // Default to 100.0f + ImGui::TableSetupColumn("two", ImGuiTableColumnFlags_WidthFixed, 200.0f); // Default to 200.0f + ImGui::TableSetupColumn("three", ImGuiTableColumnFlags_WidthFixed); // Default to auto + ImGui::TableHeadersRow(); + for (int row = 0; row < 4; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + if (row == 0) + ImGui::Text("(w: %5.1f)", ImGui::GetContentRegionAvail().x); + else + ImGui::Text("Hello %d,%d", column, row); + } + } + ImGui::EndTable(); + } + + HelpMarker("Using TableSetupColumn() to setup explicit width.\n\nUnless _NoKeepColumnsVisible is set, fixed columns with set width may still be shrunk down if there's not enough space in the host."); + + static ImGuiTableFlags flags2 = ImGuiTableFlags_None; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", &flags2, ImGuiTableFlags_NoKeepColumnsVisible); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", &flags2, ImGuiTableFlags_BordersInnerV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", &flags2, ImGuiTableFlags_BordersOuterV); + PopStyleCompact(); + if (ImGui::BeginTable("table2", 4, flags2)) + { + // We could also set ImGuiTableFlags_SizingFixedFit on the table and all columns will default to ImGuiTableColumnFlags_WidthFixed. + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 15.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 30.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 15.0f); + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 4; column++) + { + ImGui::TableSetColumnIndex(column); + if (row == 0) + ImGui::Text("(w: %5.1f)", ImGui::GetContentRegionAvail().x); + else + ImGui::Text("Hello %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Nested tables")) + { + HelpMarker("This demonstrate embedding a table into another table cell."); + + if (ImGui::BeginTable("table_nested1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) + { + ImGui::TableSetupColumn("A0"); + ImGui::TableSetupColumn("A1"); + ImGui::TableHeadersRow(); + + ImGui::TableNextColumn(); + ImGui::Text("A0 Row 0"); + { + float rows_height = TEXT_BASE_HEIGHT * 2; + if (ImGui::BeginTable("table_nested2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) + { + ImGui::TableSetupColumn("B0"); + ImGui::TableSetupColumn("B1"); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::TableNextColumn(); + ImGui::Text("B0 Row 0"); + ImGui::TableNextColumn(); + ImGui::Text("B1 Row 0"); + ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::TableNextColumn(); + ImGui::Text("B0 Row 1"); + ImGui::TableNextColumn(); + ImGui::Text("B1 Row 1"); + + ImGui::EndTable(); + } + } + ImGui::TableNextColumn(); ImGui::Text("A1 Row 0"); + ImGui::TableNextColumn(); ImGui::Text("A0 Row 1"); + ImGui::TableNextColumn(); ImGui::Text("A1 Row 1"); + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Row height")) + { + HelpMarker("You can pass a 'min_row_height' to TableNextRow().\n\nRows are padded with 'style.CellPadding.y' on top and bottom, so effectively the minimum row height will always be >= 'style.CellPadding.y * 2.0f'.\n\nWe cannot honor a _maximum_ row height as that would requires a unique clipping rectangle per row."); + if (ImGui::BeginTable("table_row_height", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerV)) + { + for (int row = 0; row < 10; row++) + { + float min_row_height = (float)(int)(TEXT_BASE_HEIGHT * 0.30f * row); + ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); + ImGui::TableNextColumn(); + ImGui::Text("min_row_height = %.2f", min_row_height); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Outer size")) + { + // Showcasing use of ImGuiTableFlags_NoHostExtendX and ImGuiTableFlags_NoHostExtendY + // Important to that note how the two flags have slightly different behaviors! + ImGui::Text("Using NoHostExtendX and NoHostExtendY:"); + PushStyleCompact(); + static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX; + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendX", &flags, ImGuiTableFlags_NoHostExtendX); + ImGui::SameLine(); HelpMarker("Make outer width auto-fit to columns, overriding outer_size.x value.\n\nOnly available when ScrollX/ScrollY are disabled and Stretch columns are not used."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendY", &flags, ImGuiTableFlags_NoHostExtendY); + ImGui::SameLine(); HelpMarker("Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit).\n\nOnly available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible."); + PopStyleCompact(); + + ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 5.5f); + if (ImGui::BeginTable("table1", 3, flags, outer_size)) + { + for (int row = 0; row < 10; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableNextColumn(); + ImGui::Text("Cell %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::SameLine(); + ImGui::Text("Hello!"); + + ImGui::Spacing(); + + ImGui::Text("Using explicit size:"); + if (ImGui::BeginTable("table2", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImVec2(TEXT_BASE_WIDTH * 30, 0.0f))) + { + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableNextColumn(); + ImGui::Text("Cell %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::SameLine(); + if (ImGui::BeginTable("table3", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImVec2(TEXT_BASE_WIDTH * 30, 0.0f))) + { + for (int row = 0; row < 3; row++) + { + ImGui::TableNextRow(0, TEXT_BASE_HEIGHT * 1.5f); + for (int column = 0; column < 3; column++) + { + ImGui::TableNextColumn(); + ImGui::Text("Cell %d,%d", column, row); + } + } + ImGui::EndTable(); + } + + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Background color")) + { + static ImGuiTableFlags flags = ImGuiTableFlags_RowBg; + static int row_bg_type = 1; + static int row_bg_target = 1; + static int cell_bg_type = 1; + + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Borders", &flags, ImGuiTableFlags_Borders); + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags, ImGuiTableFlags_RowBg); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_RowBg automatically sets RowBg0 to alternative colors pulled from the Style."); + ImGui::Combo("row bg type", (int*)&row_bg_type, "None\0Red\0Gradient\0"); + ImGui::Combo("row bg target", (int*)&row_bg_target, "RowBg0\0RowBg1\0"); ImGui::SameLine(); HelpMarker("Target RowBg0 to override the alternating odd/even colors,\nTarget RowBg1 to blend with them."); + ImGui::Combo("cell bg type", (int*)&cell_bg_type, "None\0Blue\0"); ImGui::SameLine(); HelpMarker("We are colorizing cells to B1->C2 here."); + IM_ASSERT(row_bg_type >= 0 && row_bg_type <= 2); + IM_ASSERT(row_bg_target >= 0 && row_bg_target <= 1); + IM_ASSERT(cell_bg_type >= 0 && cell_bg_type <= 1); + PopStyleCompact(); + + if (ImGui::BeginTable("table1", 5, flags)) + { + for (int row = 0; row < 6; row++) + { + ImGui::TableNextRow(); + + // Demonstrate setting a row background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBgX, ...)' + // We use a transparent color so we can see the one behind in case our target is RowBg1 and RowBg0 was already targeted by the ImGuiTableFlags_RowBg flag. + if (row_bg_type != 0) + { + ImU32 row_bg_color = ImGui::GetColorU32(row_bg_type == 1 ? ImVec4(0.7f, 0.3f, 0.3f, 0.65f) : ImVec4(0.2f + row * 0.1f, 0.2f, 0.2f, 0.65f)); // Flat or Gradient? + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0 + row_bg_target, row_bg_color); + } + + // Fill cells + for (int column = 0; column < 5; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%c%c", 'A' + row, '0' + column); + + // Change background of Cells B1->C2 + // Demonstrate setting a cell background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ...)' + // (the CellBg color will be blended over the RowBg and ColumnBg colors) + // We can also pass a column number as a third parameter to TableSetBgColor() and do this outside the column loop. + if (row >= 1 && row <= 2 && column >= 1 && column <= 2 && cell_bg_type == 1) + { + ImU32 cell_bg_color = ImGui::GetColorU32(ImVec4(0.3f, 0.3f, 0.7f, 0.65f)); + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color); + } + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Tree view")) + { + static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; + + if (ImGui::BeginTable("3ways", 3, flags)) + { + // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 18.0f); + ImGui::TableHeadersRow(); + + // Simple storage to output a dummy file-system. + struct MyTreeNode + { + const char* Name; + const char* Type; + int Size; + int ChildIdx; + int ChildCount; + static void DisplayNode(const MyTreeNode* node, const MyTreeNode* all_nodes) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + const bool is_folder = (node->ChildCount > 0); + if (is_folder) + { + bool open = ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::TableNextColumn(); + ImGui::TextDisabled("--"); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(node->Type); + if (open) + { + for (int child_n = 0; child_n < node->ChildCount; child_n++) + DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes); + ImGui::TreePop(); + } + } + else + { + ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::TableNextColumn(); + ImGui::Text("%d", node->Size); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(node->Type); + } + } + }; + static const MyTreeNode nodes[] = + { + { "Root", "Folder", -1, 1, 3 }, // 0 + { "Music", "Folder", -1, 4, 2 }, // 1 + { "Textures", "Folder", -1, 6, 3 }, // 2 + { "desktop.ini", "System file", 1024, -1,-1 }, // 3 + { "File1_a.wav", "Audio file", 123000, -1,-1 }, // 4 + { "File1_b.wav", "Audio file", 456000, -1,-1 }, // 5 + { "Image001.png", "Image file", 203128, -1,-1 }, // 6 + { "Copy of Image001.png", "Image file", 203256, -1,-1 }, // 7 + { "Copy of Image001 (Final2).png","Image file", 203512, -1,-1 }, // 8 + }; + + MyTreeNode::DisplayNode(&nodes[0], nodes); + + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Item width")) + { + HelpMarker( + "Showcase using PushItemWidth() and how it is preserved on a per-column basis.\n\n" + "Note that on auto-resizing non-resizable fixed columns, querying the content width for e.g. right-alignment doesn't make sense."); + if (ImGui::BeginTable("table_item_width", 3, ImGuiTableFlags_Borders)) + { + ImGui::TableSetupColumn("small"); + ImGui::TableSetupColumn("half"); + ImGui::TableSetupColumn("right-align"); + ImGui::TableHeadersRow(); + + for (int row = 0; row < 3; row++) + { + ImGui::TableNextRow(); + if (row == 0) + { + // Setup ItemWidth once (instead of setting up every time, which is also possible but less efficient) + ImGui::TableSetColumnIndex(0); + ImGui::PushItemWidth(TEXT_BASE_WIDTH * 3.0f); // Small + ImGui::TableSetColumnIndex(1); + ImGui::PushItemWidth(-ImGui::GetContentRegionAvail().x * 0.5f); + ImGui::TableSetColumnIndex(2); + ImGui::PushItemWidth(-FLT_MIN); // Right-aligned + } + + // Draw our contents + static float dummy_f = 0.0f; + ImGui::PushID(row); + ImGui::TableSetColumnIndex(0); + ImGui::SliderFloat("float0", &dummy_f, 0.0f, 1.0f); + ImGui::TableSetColumnIndex(1); + ImGui::SliderFloat("float1", &dummy_f, 0.0f, 1.0f); + ImGui::TableSetColumnIndex(2); + ImGui::SliderFloat("float2", &dummy_f, 0.0f, 1.0f); + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + // Demonstrate using TableHeader() calls instead of TableHeadersRow() + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Custom headers")) + { + const int COLUMNS_COUNT = 3; + if (ImGui::BeginTable("table_custom_headers", COLUMNS_COUNT, ImGuiTableFlags_Borders | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) + { + ImGui::TableSetupColumn("Apricot"); + ImGui::TableSetupColumn("Banana"); + ImGui::TableSetupColumn("Cherry"); + + // Dummy entire-column selection storage + // FIXME: It would be nice to actually demonstrate full-featured selection using those checkbox. + static bool column_selected[3] = {}; + + // Instead of calling TableHeadersRow() we'll submit custom headers ourselves + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < COLUMNS_COUNT; column++) + { + ImGui::TableSetColumnIndex(column); + const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::Checkbox("##checkall", &column_selected[column]); + ImGui::PopStyleVar(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TableHeader(column_name); + ImGui::PopID(); + } + + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + char buf[32]; + sprintf(buf, "Cell %d,%d", column, row); + ImGui::TableSetColumnIndex(column); + ImGui::Selectable(buf, column_selected[column]); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + // Demonstrate creating custom context menus inside columns, while playing it nice with context menus provided by TableHeadersRow()/TableHeader() + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Context menus")) + { + HelpMarker("By default, right-clicking over a TableHeadersRow()/TableHeader() line will open the default context-menu.\nUsing ImGuiTableFlags_ContextMenuInBody we also allow right-clicking over columns body."); + static ImGuiTableFlags flags1 = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_ContextMenuInBody; + + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", &flags1, ImGuiTableFlags_ContextMenuInBody); + PopStyleCompact(); + + // Context Menus: first example + // [1.1] Right-click on the TableHeadersRow() line to open the default table context menu. + // [1.2] Right-click in columns also open the default table context menu (if ImGuiTableFlags_ContextMenuInBody is set) + const int COLUMNS_COUNT = 3; + if (ImGui::BeginTable("table_context_menu", COLUMNS_COUNT, flags1)) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + + // [1.1]] Right-click on the TableHeadersRow() line to open the default table context menu. + ImGui::TableHeadersRow(); + + // Submit dummy contents + for (int row = 0; row < 4; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < COLUMNS_COUNT; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Cell %d,%d", column, row); + } + } + ImGui::EndTable(); + } + + // Context Menus: second example + // [2.1] Right-click on the TableHeadersRow() line to open the default table context menu. + // [2.2] Right-click on the ".." to open a custom popup + // [2.3] Right-click in columns to open another custom popup + HelpMarker("Demonstrate mixing table context menu (over header), item context button (over button) and custom per-colum context menu (over column body)."); + ImGuiTableFlags flags2 = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders; + if (ImGui::BeginTable("table_context_menu_2", COLUMNS_COUNT, flags2)) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + + // [2.1] Right-click on the TableHeadersRow() line to open the default table context menu. + ImGui::TableHeadersRow(); + for (int row = 0; row < 4; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < COLUMNS_COUNT; column++) + { + // Submit dummy contents + ImGui::TableSetColumnIndex(column); + ImGui::Text("Cell %d,%d", column, row); + ImGui::SameLine(); + + // [2.2] Right-click on the ".." to open a custom popup + ImGui::PushID(row * COLUMNS_COUNT + column); + ImGui::SmallButton(".."); + if (ImGui::BeginPopupContextItem()) + { + ImGui::Text("This is the popup for Button(\"..\") in Cell %d,%d", column, row); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::PopID(); + } + } + + // [2.3] Right-click anywhere in columns to open another custom popup + // (instead of testing for !IsAnyItemHovered() we could also call OpenPopup() with ImGuiPopupFlags_NoOpenOverExistingPopup + // to manage popup priority as the popups triggers, here "are we hovering a column" are overlapping) + int hovered_column = -1; + for (int column = 0; column < COLUMNS_COUNT + 1; column++) + { + ImGui::PushID(column); + if (ImGui::TableGetColumnFlags(column) & ImGuiTableColumnFlags_IsHovered) + hovered_column = column; + if (hovered_column == column && !ImGui::IsAnyItemHovered() && ImGui::IsMouseReleased(1)) + ImGui::OpenPopup("MyPopup"); + if (ImGui::BeginPopup("MyPopup")) + { + if (column == COLUMNS_COUNT) + ImGui::Text("This is a custom popup for unused space after the last column."); + else + ImGui::Text("This is a custom popup for Column %d", column); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::PopID(); + } + + ImGui::EndTable(); + ImGui::Text("Hovered column: %d", hovered_column); + } + ImGui::TreePop(); + } + + // Demonstrate creating multiple tables with the same ID + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Synced instances")) + { + HelpMarker("Multiple tables with the same identifier will share their settings, width, visibility, order etc."); + for (int n = 0; n < 3; n++) + { + char buf[32]; + sprintf(buf, "Synced Table %d", n); + bool open = ImGui::CollapsingHeader(buf, ImGuiTreeNodeFlags_DefaultOpen); + if (open && ImGui::BeginTable("Table", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + for (int cell = 0; cell < 9; cell++) + { + ImGui::TableNextColumn(); + ImGui::Text("this cell %d", cell); + } + ImGui::EndTable(); + } + } + ImGui::TreePop(); + } + + // Demonstrate using Sorting facilities + // This is a simplified version of the "Advanced" example, where we mostly focus on the code necessary to handle sorting. + // Note that the "Advanced" example also showcase manually triggering a sort (e.g. if item quantities have been modified) + static const char* template_items_names[] = + { + "Banana", "Apple", "Cherry", "Watermelon", "Grapefruit", "Strawberry", "Mango", + "Kiwi", "Orange", "Pineapple", "Blueberry", "Plum", "Coconut", "Pear", "Apricot" + }; + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Sorting")) + { + // Create item list + static ImVector items; + if (items.Size == 0) + { + items.resize(50, MyItem()); + for (int n = 0; n < items.Size; n++) + { + const int template_n = n % IM_ARRAYSIZE(template_items_names); + MyItem& item = items[n]; + item.ID = n; + item.Name = template_items_names[template_n]; + item.Quantity = (n * n - n) % 20; // Assign default quantities + } + } + + // Options + static ImGuiTableFlags flags = + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti + | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody + | ImGuiTableFlags_ScrollY; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_SortMulti", &flags, ImGuiTableFlags_SortMulti); + ImGui::SameLine(); HelpMarker("When sorting is enabled: hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1)."); + ImGui::CheckboxFlags("ImGuiTableFlags_SortTristate", &flags, ImGuiTableFlags_SortTristate); + ImGui::SameLine(); HelpMarker("When sorting is enabled: allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0)."); + PopStyleCompact(); + + if (ImGui::BeginTable("table_sorting", 4, flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 15), 0.0f)) + { + // Declare columns + // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. + // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! + // Demonstrate using a mixture of flags among available sort-related flags: + // - ImGuiTableColumnFlags_DefaultSort + // - ImGuiTableColumnFlags_NoSort / ImGuiTableColumnFlags_NoSortAscending / ImGuiTableColumnFlags_NoSortDescending + // - ImGuiTableColumnFlags_PreferSortAscending / ImGuiTableColumnFlags_PreferSortDescending + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_ID); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Name); + ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Action); + ImGui::TableSetupColumn("Quantity", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, 0.0f, MyItemColumnID_Quantity); + ImGui::TableSetupScrollFreeze(0, 1); // Make row always visible + ImGui::TableHeadersRow(); + + // Sort our data if sort specs have been changed! + if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) + if (sorts_specs->SpecsDirty) + { + MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. + if (items.Size > 1) + qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); + MyItem::s_current_sort_specs = NULL; + sorts_specs->SpecsDirty = false; + } + + // Demonstrate using clipper for large vertical lists + ImGuiListClipper clipper; + clipper.Begin(items.Size); + while (clipper.Step()) + for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; row_n++) + { + // Display a data item + MyItem* item = &items[row_n]; + ImGui::PushID(item->ID); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%04d", item->ID); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(item->Name); + ImGui::TableNextColumn(); + ImGui::SmallButton("None"); + ImGui::TableNextColumn(); + ImGui::Text("%d", item->Quantity); + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + // In this example we'll expose most table flags and settings. + // For specific flags and settings refer to the corresponding section for more detailed explanation. + // This section is mostly useful to experiment with combining certain flags or settings with each others. + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // [DEBUG] + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Advanced")) + { + static ImGuiTableFlags flags = + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable + | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti + | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_NoBordersInBody + | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY + | ImGuiTableFlags_SizingFixedFit; + + enum ContentsType { CT_Text, CT_Button, CT_SmallButton, CT_FillButton, CT_Selectable, CT_SelectableSpanRow }; + static int contents_type = CT_SelectableSpanRow; + const char* contents_type_names[] = { "Text", "Button", "SmallButton", "FillButton", "Selectable", "Selectable (span row)" }; + static int freeze_cols = 1; + static int freeze_rows = 1; + static int items_count = IM_ARRAYSIZE(template_items_names) * 2; + static ImVec2 outer_size_value = ImVec2(0.0f, TEXT_BASE_HEIGHT * 12); + static float row_min_height = 0.0f; // Auto + static float inner_width_with_scroll = 0.0f; // Auto-extend + static bool outer_size_enabled = true; + static bool show_headers = true; + static bool show_wrapped_text = false; + //static ImGuiTextFilter filter; + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // FIXME-TABLE: Enabling this results in initial clipped first pass on table which tend to affects column sizing + if (ImGui::TreeNode("Options")) + { + // Make the UI compact because there are so many fields + PushStyleCompact(); + ImGui::PushItemWidth(TEXT_BASE_WIDTH * 28.0f); + + if (ImGui::TreeNodeEx("Features:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", &flags, ImGuiTableFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", &flags, ImGuiTableFlags_Hideable); + ImGui::CheckboxFlags("ImGuiTableFlags_Sortable", &flags, ImGuiTableFlags_Sortable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoSavedSettings", &flags, ImGuiTableFlags_NoSavedSettings); + ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", &flags, ImGuiTableFlags_ContextMenuInBody); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Decorations:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags, ImGuiTableFlags_RowBg); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", &flags, ImGuiTableFlags_BordersOuterV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", &flags, ImGuiTableFlags_BordersInnerV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", &flags, ImGuiTableFlags_BordersH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", &flags, ImGuiTableFlags_BordersOuterH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", &flags, ImGuiTableFlags_BordersInnerH); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers)"); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Sizing:", ImGuiTreeNodeFlags_DefaultOpen)) + { + EditTableSizingFlags(&flags); + ImGui::SameLine(); HelpMarker("In the Advanced demo we override the policy of each column so those table-wide settings have less effect that typical."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendX", &flags, ImGuiTableFlags_NoHostExtendX); + ImGui::SameLine(); HelpMarker("Make outer width auto-fit to columns, overriding outer_size.x value.\n\nOnly available when ScrollX/ScrollY are disabled and Stretch columns are not used."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendY", &flags, ImGuiTableFlags_NoHostExtendY); + ImGui::SameLine(); HelpMarker("Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit).\n\nOnly available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", &flags, ImGuiTableFlags_NoKeepColumnsVisible); + ImGui::SameLine(); HelpMarker("Only available if ScrollX is disabled."); + ImGui::CheckboxFlags("ImGuiTableFlags_PreciseWidths", &flags, ImGuiTableFlags_PreciseWidths); + ImGui::SameLine(); HelpMarker("Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoClip", &flags, ImGuiTableFlags_NoClip); + ImGui::SameLine(); HelpMarker("Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options."); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Padding:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::CheckboxFlags("ImGuiTableFlags_PadOuterX", &flags, ImGuiTableFlags_PadOuterX); + ImGui::CheckboxFlags("ImGuiTableFlags_NoPadOuterX", &flags, ImGuiTableFlags_NoPadOuterX); + ImGui::CheckboxFlags("ImGuiTableFlags_NoPadInnerX", &flags, ImGuiTableFlags_NoPadInnerX); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Scrolling:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags, ImGuiTableFlags_ScrollX); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_cols", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_rows", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Sorting:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::CheckboxFlags("ImGuiTableFlags_SortMulti", &flags, ImGuiTableFlags_SortMulti); + ImGui::SameLine(); HelpMarker("When sorting is enabled: hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1)."); + ImGui::CheckboxFlags("ImGuiTableFlags_SortTristate", &flags, ImGuiTableFlags_SortTristate); + ImGui::SameLine(); HelpMarker("When sorting is enabled: allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0)."); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Other:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("show_headers", &show_headers); + ImGui::Checkbox("show_wrapped_text", &show_wrapped_text); + + ImGui::DragFloat2("##OuterSize", &outer_size_value.x); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Checkbox("outer_size", &outer_size_enabled); + ImGui::SameLine(); + HelpMarker("If scrolling is disabled (ScrollX and ScrollY not set):\n" + "- The table is output directly in the parent window.\n" + "- OuterSize.x < 0.0f will right-align the table.\n" + "- OuterSize.x = 0.0f will narrow fit the table unless there are any Stretch column.\n" + "- OuterSize.y then becomes the minimum size for the table, which will extend vertically if there are more rows (unless NoHostExtendY is set)."); + + // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. + // To facilitate toying with this demo we will actually pass 0.0f to the BeginTable() when ScrollX is disabled. + ImGui::DragFloat("inner_width (when ScrollX active)", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX); + + ImGui::DragFloat("row_min_height", &row_min_height, 1.0f, 0.0f, FLT_MAX); + ImGui::SameLine(); HelpMarker("Specify height of the Selectable item."); + + ImGui::DragInt("items_count", &items_count, 0.1f, 0, 9999); + ImGui::Combo("items_type (first column)", &contents_type, contents_type_names, IM_ARRAYSIZE(contents_type_names)); + //filter.Draw("filter"); + ImGui::TreePop(); + } + + ImGui::PopItemWidth(); + PopStyleCompact(); + ImGui::Spacing(); + ImGui::TreePop(); + } + + // Update item list if we changed the number of items + static ImVector items; + static ImVector selection; + static bool items_need_sort = false; + if (items.Size != items_count) + { + items.resize(items_count, MyItem()); + for (int n = 0; n < items_count; n++) + { + const int template_n = n % IM_ARRAYSIZE(template_items_names); + MyItem& item = items[n]; + item.ID = n; + item.Name = template_items_names[template_n]; + item.Quantity = (template_n == 3) ? 10 : (template_n == 4) ? 20 : 0; // Assign default quantities + } + } + + const ImDrawList* parent_draw_list = ImGui::GetWindowDrawList(); + const int parent_draw_list_draw_cmd_count = parent_draw_list->CmdBuffer.Size; + ImVec2 table_scroll_cur, table_scroll_max; // For debug display + const ImDrawList* table_draw_list = NULL; // " + + // Submit table + const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : 0.0f; + if (ImGui::BeginTable("table_advanced", 6, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use)) + { + // Declare columns + // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. + // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHide, 0.0f, MyItemColumnID_ID); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Name); + ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Action); + ImGui::TableSetupColumn("Quantity", ImGuiTableColumnFlags_PreferSortDescending, 0.0f, MyItemColumnID_Quantity); + ImGui::TableSetupColumn("Description", (flags & ImGuiTableFlags_NoHostExtendX) ? 0 : ImGuiTableColumnFlags_WidthStretch, 0.0f, MyItemColumnID_Description); + ImGui::TableSetupColumn("Hidden", ImGuiTableColumnFlags_DefaultHide | ImGuiTableColumnFlags_NoSort); + ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); + + // Sort our data if sort specs have been changed! + ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs(); + if (sorts_specs && sorts_specs->SpecsDirty) + items_need_sort = true; + if (sorts_specs && items_need_sort && items.Size > 1) + { + MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. + qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); + MyItem::s_current_sort_specs = NULL; + sorts_specs->SpecsDirty = false; + } + items_need_sort = false; + + // Take note of whether we are currently sorting based on the Quantity field, + // we will use this to trigger sorting when we know the data of this column has been modified. + const bool sorts_specs_using_quantity = (ImGui::TableGetColumnFlags(3) & ImGuiTableColumnFlags_IsSorted) != 0; + + // Show headers + if (show_headers) + ImGui::TableHeadersRow(); + + // Show data + // FIXME-TABLE FIXME-NAV: How we can get decent up/down even though we have the buttons here? + ImGui::PushButtonRepeat(true); +#if 1 + // Demonstrate using clipper for large vertical lists + ImGuiListClipper clipper; + clipper.Begin(items.Size); + while (clipper.Step()) + { + for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; row_n++) +#else + // Without clipper + { + for (int row_n = 0; row_n < items.Size; row_n++) +#endif + { + MyItem* item = &items[row_n]; + //if (!filter.PassFilter(item->Name)) + // continue; + + const bool item_is_selected = selection.contains(item->ID); + ImGui::PushID(item->ID); + ImGui::TableNextRow(ImGuiTableRowFlags_None, row_min_height); + + // For the demo purpose we can select among different type of items submitted in the first column + ImGui::TableSetColumnIndex(0); + char label[32]; + sprintf(label, "%04d", item->ID); + if (contents_type == CT_Text) + ImGui::TextUnformatted(label); + else if (contents_type == CT_Button) + ImGui::Button(label); + else if (contents_type == CT_SmallButton) + ImGui::SmallButton(label); + else if (contents_type == CT_FillButton) + ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); + else if (contents_type == CT_Selectable || contents_type == CT_SelectableSpanRow) + { + ImGuiSelectableFlags selectable_flags = (contents_type == CT_SelectableSpanRow) ? ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap : ImGuiSelectableFlags_None; + if (ImGui::Selectable(label, item_is_selected, selectable_flags, ImVec2(0, row_min_height))) + { + if (ImGui::GetIO().KeyCtrl) + { + if (item_is_selected) + selection.find_erase_unsorted(item->ID); + else + selection.push_back(item->ID); + } + else + { + selection.clear(); + selection.push_back(item->ID); + } + } + } + + if (ImGui::TableSetColumnIndex(1)) + ImGui::TextUnformatted(item->Name); + + // Here we demonstrate marking our data set as needing to be sorted again if we modified a quantity, + // and we are currently sorting on the column showing the Quantity. + // To avoid triggering a sort while holding the button, we only trigger it when the button has been released. + // You will probably need a more advanced system in your code if you want to automatically sort when a specific entry changes. + if (ImGui::TableSetColumnIndex(2)) + { + if (ImGui::SmallButton("Chop")) { item->Quantity += 1; } + if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } + ImGui::SameLine(); + if (ImGui::SmallButton("Eat")) { item->Quantity -= 1; } + if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } + } + + if (ImGui::TableSetColumnIndex(3)) + ImGui::Text("%d", item->Quantity); + + ImGui::TableSetColumnIndex(4); + if (show_wrapped_text) + ImGui::TextWrapped("Lorem ipsum dolor sit amet"); + else + ImGui::Text("Lorem ipsum dolor sit amet"); + + if (ImGui::TableSetColumnIndex(5)) + ImGui::Text("1234"); + + ImGui::PopID(); + } + } + ImGui::PopButtonRepeat(); + + // Store some info to display debug details below + table_scroll_cur = ImVec2(ImGui::GetScrollX(), ImGui::GetScrollY()); + table_scroll_max = ImVec2(ImGui::GetScrollMaxX(), ImGui::GetScrollMaxY()); + table_draw_list = ImGui::GetWindowDrawList(); + ImGui::EndTable(); + } + static bool show_debug_details = false; + ImGui::Checkbox("Debug details", &show_debug_details); + if (show_debug_details && table_draw_list) + { + ImGui::SameLine(0.0f, 0.0f); + const int table_draw_list_draw_cmd_count = table_draw_list->CmdBuffer.Size; + if (table_draw_list == parent_draw_list) + ImGui::Text(": DrawCmd: +%d (in same window)", + table_draw_list_draw_cmd_count - parent_draw_list_draw_cmd_count); + else + ImGui::Text(": DrawCmd: +%d (in child window), Scroll: (%.f/%.f) (%.f/%.f)", + table_draw_list_draw_cmd_count - 1, table_scroll_cur.x, table_scroll_max.x, table_scroll_cur.y, table_scroll_max.y); + } + ImGui::TreePop(); + } + + ImGui::PopID(); + + ShowDemoWindowColumns(); + + if (disable_indent) + ImGui::PopStyleVar(); +} + +// Demonstrate old/legacy Columns API! +// [2020: Columns are under-featured and not maintained. Prefer using the more flexible and powerful BeginTable() API!] +static void ShowDemoWindowColumns() +{ + bool open = ImGui::TreeNode("Legacy Columns API"); + ImGui::SameLine(); + HelpMarker("Columns() is an old API! Prefer using the more flexible and powerful BeginTable() API!"); + if (!open) + return; + + // Basic columns + if (ImGui::TreeNode("Basic")) + { + ImGui::Text("Without border:"); + ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border + ImGui::Separator(); + for (int n = 0; n < 14; n++) + { + char label[32]; + sprintf(label, "Item %d", n); + if (ImGui::Selectable(label)) {} + //if (ImGui::Button(label, ImVec2(-FLT_MIN,0.0f))) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + + ImGui::Text("With border:"); + ImGui::Columns(4, "mycolumns"); // 4-ways, with border + ImGui::Separator(); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Text("Hovered"); ImGui::NextColumn(); + ImGui::Separator(); + const char* names[3] = { "One", "Two", "Three" }; + const char* paths[3] = { "/path/one", "/path/two", "/path/three" }; + static int selected = -1; + for (int i = 0; i < 3; i++) + { + char label[32]; + sprintf(label, "%04d", i); + if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) + selected = i; + bool hovered = ImGui::IsItemHovered(); + ImGui::NextColumn(); + ImGui::Text(names[i]); ImGui::NextColumn(); + ImGui::Text(paths[i]); ImGui::NextColumn(); + ImGui::Text("%d", hovered); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Borders")) + { + // NB: Future columns API should allow automatic horizontal borders. + static bool h_borders = true; + static bool v_borders = true; + static int columns_count = 4; + const int lines_count = 3; + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::DragInt("##columns_count", &columns_count, 0.1f, 2, 10, "%d columns"); + if (columns_count < 2) + columns_count = 2; + ImGui::SameLine(); + ImGui::Checkbox("horizontal", &h_borders); + ImGui::SameLine(); + ImGui::Checkbox("vertical", &v_borders); + ImGui::Columns(columns_count, NULL, v_borders); + for (int i = 0; i < columns_count * lines_count; i++) + { + if (h_borders && ImGui::GetColumnIndex() == 0) + ImGui::Separator(); + ImGui::Text("%c%c%c", 'a' + i, 'a' + i, 'a' + i); + ImGui::Text("Width %.2f", ImGui::GetColumnWidth()); + ImGui::Text("Avail %.2f", ImGui::GetContentRegionAvail().x); + ImGui::Text("Offset %.2f", ImGui::GetColumnOffset()); + ImGui::Text("Long text that is likely to clip"); + ImGui::Button("Button", ImVec2(-FLT_MIN, 0.0f)); + ImGui::NextColumn(); + } + ImGui::Columns(1); + if (h_borders) + ImGui::Separator(); + ImGui::TreePop(); + } + + // Create multiple items in a same cell before switching to next column + if (ImGui::TreeNode("Mixed items")) + { + ImGui::Columns(3, "mixed"); + ImGui::Separator(); + + ImGui::Text("Hello"); + ImGui::Button("Banana"); + ImGui::NextColumn(); + + ImGui::Text("ImGui"); + ImGui::Button("Apple"); + static float foo = 1.0f; + ImGui::InputFloat("red", &foo, 0.05f, 0, "%.3f"); + ImGui::Text("An extra line here."); + ImGui::NextColumn(); + + ImGui::Text("Sailor"); + ImGui::Button("Corniflower"); + static float bar = 1.0f; + ImGui::InputFloat("blue", &bar, 0.05f, 0, "%.3f"); + ImGui::NextColumn(); + + if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Word wrapping + if (ImGui::TreeNode("Word-wrapping")) + { + ImGui::Columns(2, "word-wrapping"); + ImGui::Separator(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Left"); + ImGui::NextColumn(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Right"); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); + ImVec2 child_size = ImVec2(0, ImGui::GetFontSize() * 20.0f); + ImGui::BeginChild("##ScrollingRegion", child_size, false, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::Columns(10); + + // Also demonstrate using clipper for large vertical lists + int ITEMS_COUNT = 2000; + ImGuiListClipper clipper; + clipper.Begin(ITEMS_COUNT); + while (clipper.Step()) + { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + for (int j = 0; j < 10; j++) + { + ImGui::Text("Line %d Column %d...", i, j); + ImGui::NextColumn(); + } + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Tree")) + { + ImGui::Columns(2, "tree", true); + for (int x = 0; x < 3; x++) + { + bool open1 = ImGui::TreeNode((void*)(intptr_t)x, "Node%d", x); + ImGui::NextColumn(); + ImGui::Text("Node contents"); + ImGui::NextColumn(); + if (open1) + { + for (int y = 0; y < 3; y++) + { + bool open2 = ImGui::TreeNode((void*)(intptr_t)y, "Node%d.%d", x, y); + ImGui::NextColumn(); + ImGui::Text("Node contents"); + if (open2) + { + ImGui::Text("Even more contents"); + if (ImGui::TreeNode("Tree in column")) + { + ImGui::Text("The quick brown fox jumps over the lazy dog"); + ImGui::TreePop(); + } + } + ImGui::NextColumn(); + if (open2) + ImGui::TreePop(); + } + ImGui::TreePop(); + } + } + ImGui::Columns(1); + ImGui::TreePop(); + } + + ImGui::TreePop(); +} + +static void ShowDemoWindowMisc() +{ + if (ImGui::CollapsingHeader("Filtering")) + { + // Helper class to easy setup a text filter. + // You may want to implement a more feature-full filtering scheme in your own application. + static ImGuiTextFilter filter; + ImGui::Text("Filter usage:\n" + " \"\" display all lines\n" + " \"xxx\" display lines containing \"xxx\"\n" + " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" + " \"-xxx\" hide lines containing \"xxx\""); + filter.Draw(); + const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; + for (int i = 0; i < IM_ARRAYSIZE(lines); i++) + if (filter.PassFilter(lines[i])) + ImGui::BulletText("%s", lines[i]); + } + + if (ImGui::CollapsingHeader("Inputs, Navigation & Focus")) + { + ImGuiIO& io = ImGui::GetIO(); + + // Display ImGuiIO output flags + ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse); + ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard); + ImGui::Text("WantTextInput: %d", io.WantTextInput); + ImGui::Text("WantSetMousePos: %d", io.WantSetMousePos); + ImGui::Text("NavActive: %d, NavVisible: %d", io.NavActive, io.NavVisible); + + // Display Mouse state + if (ImGui::TreeNode("Mouse State")) + { + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); + else + ImGui::Text("Mouse pos: "); + ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); + ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDown(i)) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } + ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse dblclick:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)){ ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); + ImGui::Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused + ImGui::TreePop(); + } + + // Display Keyboard/Mouse state + if (ImGui::TreeNode("Keyboard & Navigation State")) + { + ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyDown(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X) (%.02f secs)", i, i, io.KeysDownDuration[i]); } + ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X)", i, i); } + ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X)", i, i); } + ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); + ImGui::Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; ImGui::SameLine(); ImGui::Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. + + ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f (%.02f secs)", i, io.NavInputs[i], io.NavInputsDownDuration[i]); } + ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f) { ImGui::SameLine(); ImGui::Text("[%d]", i); } + + ImGui::Button("Hovering me sets the\nkeyboard capture flag"); + if (ImGui::IsItemHovered()) + ImGui::CaptureKeyboardFromApp(true); + ImGui::SameLine(); + ImGui::Button("Holding me clears the\nthe keyboard capture flag"); + if (ImGui::IsItemActive()) + ImGui::CaptureKeyboardFromApp(false); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Tabbing")) + { + ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); + static char buf[32] = "hello"; + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); + ImGui::PushAllowKeyboardFocus(false); + ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); + ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); + ImGui::PopAllowKeyboardFocus(); + ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focus from code")) + { + bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); + bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine(); + bool focus_3 = ImGui::Button("Focus on 3"); + int has_focus = 0; + static char buf[128] = "click on a button to set focus"; + + if (focus_1) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 1; + + if (focus_2) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 2; + + ImGui::PushAllowKeyboardFocus(false); + if (focus_3) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 3; + ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); + ImGui::PopAllowKeyboardFocus(); + + if (has_focus) + ImGui::Text("Item with focus: %d", has_focus); + else + ImGui::Text("Item with focus: "); + + // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item + static float f3[3] = { 0.0f, 0.0f, 0.0f }; + int focus_ahead = -1; + if (ImGui::Button("Focus on X")) { focus_ahead = 0; } ImGui::SameLine(); + if (ImGui::Button("Focus on Y")) { focus_ahead = 1; } ImGui::SameLine(); + if (ImGui::Button("Focus on Z")) { focus_ahead = 2; } + if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead); + ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f); + + ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Dragging")) + { + ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); + for (int button = 0; button < 3; button++) + { + ImGui::Text("IsMouseDragging(%d):", button); + ImGui::Text(" w/ default threshold: %d,", ImGui::IsMouseDragging(button)); + ImGui::Text(" w/ zero threshold: %d,", ImGui::IsMouseDragging(button, 0.0f)); + ImGui::Text(" w/ large threshold: %d,", ImGui::IsMouseDragging(button, 20.0f)); + } + + ImGui::Button("Drag Me"); + if (ImGui::IsItemActive()) + ImGui::GetForegroundDrawList()->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f); // Draw a line between the button and the mouse cursor + + // Drag operations gets "unlocked" when the mouse has moved past a certain threshold + // (the default threshold is stored in io.MouseDragThreshold). You can request a lower or higher + // threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta(). + ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f); + ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0); + ImVec2 mouse_delta = io.MouseDelta; + ImGui::Text("GetMouseDragDelta(0):"); + ImGui::Text(" w/ default threshold: (%.1f, %.1f)", value_with_lock_threshold.x, value_with_lock_threshold.y); + ImGui::Text(" w/ zero threshold: (%.1f, %.1f)", value_raw.x, value_raw.y); + ImGui::Text("io.MouseDelta: (%.1f, %.1f)", mouse_delta.x, mouse_delta.y); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Mouse cursors")) + { + const char* mouse_cursors_names[] = { "Arrow", "TextInput", "ResizeAll", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand", "NotAllowed" }; + IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); + + ImGuiMouseCursor current = ImGui::GetMouseCursor(); + ImGui::Text("Current mouse cursor = %d: %s", current, mouse_cursors_names[current]); + ImGui::Text("Hover to see mouse cursors:"); + ImGui::SameLine(); HelpMarker( + "Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. " + "If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, " + "otherwise your backend needs to handle it."); + for (int i = 0; i < ImGuiMouseCursor_COUNT; i++) + { + char label[32]; + sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); + ImGui::Bullet(); ImGui::Selectable(label, false); + if (ImGui::IsItemHovered()) + ImGui::SetMouseCursor(i); + } + ImGui::TreePop(); + } + } +} + +//----------------------------------------------------------------------------- +// [SECTION] About Window / ShowAboutWindow() +// Access from Dear ImGui Demo -> Tools -> About +//----------------------------------------------------------------------------- + +void ImGui::ShowAboutWindow(bool* p_open) +{ + if (!ImGui::Begin("About Dear ImGui", p_open, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::End(); + return; + } + ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); + ImGui::Separator(); + ImGui::Text("By Omar Cornut and all Dear ImGui contributors."); + ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); + + static bool show_config_info = false; + ImGui::Checkbox("Config/Build Information", &show_config_info); + if (show_config_info) + { + ImGuiIO& io = ImGui::GetIO(); + ImGuiStyle& style = ImGui::GetStyle(); + + bool copy_to_clipboard = ImGui::Button("Copy to clipboard"); + ImVec2 child_size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 18); + ImGui::BeginChildFrame(ImGui::GetID("cfg_infos"), child_size, ImGuiWindowFlags_NoMove); + if (copy_to_clipboard) + { + ImGui::LogToClipboard(); + ImGui::LogText("```\n"); // Back quotes will make text appears without formatting when pasting on GitHub + } + + ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); + ImGui::Separator(); + ImGui::Text("sizeof(size_t): %d, sizeof(ImDrawIdx): %d, sizeof(ImDrawVert): %d", (int)sizeof(size_t), (int)sizeof(ImDrawIdx), (int)sizeof(ImDrawVert)); + ImGui::Text("define: __cplusplus=%d", (int)__cplusplus); +#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_OBSOLETE_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_WIN32_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_WIN32_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_FILE_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_FILE_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_DEFAULT_ALLOCATORS + ImGui::Text("define: IMGUI_DISABLE_DEFAULT_ALLOCATORS"); +#endif +#ifdef IMGUI_USE_BGRA_PACKED_COLOR + ImGui::Text("define: IMGUI_USE_BGRA_PACKED_COLOR"); +#endif +#ifdef _WIN32 + ImGui::Text("define: _WIN32"); +#endif +#ifdef _WIN64 + ImGui::Text("define: _WIN64"); +#endif +#ifdef __linux__ + ImGui::Text("define: __linux__"); +#endif +#ifdef __APPLE__ + ImGui::Text("define: __APPLE__"); +#endif +#ifdef _MSC_VER + ImGui::Text("define: _MSC_VER=%d", _MSC_VER); +#endif +#ifdef _MSVC_LANG + ImGui::Text("define: _MSVC_LANG=%d", (int)_MSVC_LANG); +#endif +#ifdef __MINGW32__ + ImGui::Text("define: __MINGW32__"); +#endif +#ifdef __MINGW64__ + ImGui::Text("define: __MINGW64__"); +#endif +#ifdef __GNUC__ + ImGui::Text("define: __GNUC__=%d", (int)__GNUC__); +#endif +#ifdef __clang_version__ + ImGui::Text("define: __clang_version__=%s", __clang_version__); +#endif + ImGui::Separator(); + ImGui::Text("io.BackendPlatformName: %s", io.BackendPlatformName ? io.BackendPlatformName : "NULL"); + ImGui::Text("io.BackendRendererName: %s", io.BackendRendererName ? io.BackendRendererName : "NULL"); + ImGui::Text("io.ConfigFlags: 0x%08X", io.ConfigFlags); + if (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) ImGui::Text(" NavEnableKeyboard"); + if (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) ImGui::Text(" NavEnableGamepad"); + if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) ImGui::Text(" NavEnableSetMousePos"); + if (io.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard) ImGui::Text(" NavNoCaptureKeyboard"); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) ImGui::Text(" NoMouse"); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) ImGui::Text(" NoMouseCursorChange"); + if (io.MouseDrawCursor) ImGui::Text("io.MouseDrawCursor"); + if (io.ConfigMacOSXBehaviors) ImGui::Text("io.ConfigMacOSXBehaviors"); + if (io.ConfigInputTextCursorBlink) ImGui::Text("io.ConfigInputTextCursorBlink"); + if (io.ConfigWindowsResizeFromEdges) ImGui::Text("io.ConfigWindowsResizeFromEdges"); + if (io.ConfigWindowsMoveFromTitleBarOnly) ImGui::Text("io.ConfigWindowsMoveFromTitleBarOnly"); + if (io.ConfigMemoryCompactTimer >= 0.0f) ImGui::Text("io.ConfigMemoryCompactTimer = %.1f", io.ConfigMemoryCompactTimer); + ImGui::Text("io.BackendFlags: 0x%08X", io.BackendFlags); + if (io.BackendFlags & ImGuiBackendFlags_HasGamepad) ImGui::Text(" HasGamepad"); + if (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) ImGui::Text(" HasMouseCursors"); + if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos) ImGui::Text(" HasSetMousePos"); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) ImGui::Text(" RendererHasVtxOffset"); + ImGui::Separator(); + ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexWidth, io.Fonts->TexHeight); + ImGui::Text("io.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y); + ImGui::Text("io.DisplayFramebufferScale: %.2f,%.2f", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); + ImGui::Separator(); + ImGui::Text("style.WindowPadding: %.2f,%.2f", style.WindowPadding.x, style.WindowPadding.y); + ImGui::Text("style.WindowBorderSize: %.2f", style.WindowBorderSize); + ImGui::Text("style.FramePadding: %.2f,%.2f", style.FramePadding.x, style.FramePadding.y); + ImGui::Text("style.FrameRounding: %.2f", style.FrameRounding); + ImGui::Text("style.FrameBorderSize: %.2f", style.FrameBorderSize); + ImGui::Text("style.ItemSpacing: %.2f,%.2f", style.ItemSpacing.x, style.ItemSpacing.y); + ImGui::Text("style.ItemInnerSpacing: %.2f,%.2f", style.ItemInnerSpacing.x, style.ItemInnerSpacing.y); + + if (copy_to_clipboard) + { + ImGui::LogText("\n```\n"); + ImGui::LogFinish(); + } + ImGui::EndChildFrame(); + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Font viewer / ShowFontAtlas() +//----------------------------------------------------------------------------- +// - ShowFontSelector() +// - ShowFont() +// - ShowFontAtlas() +//----------------------------------------------------------------------------- + +// This isn't worth putting in public API but we want Metrics to use it +namespace ImGui { void ShowFontAtlas(ImFontAtlas* atlas); } + +// Demo helper function to select among loaded fonts. +// Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. +void ImGui::ShowFontSelector(const char* label) +{ + ImGuiIO& io = ImGui::GetIO(); + ImFont* font_current = ImGui::GetFont(); + if (ImGui::BeginCombo(label, font_current->GetDebugName())) + { + for (int n = 0; n < io.Fonts->Fonts.Size; n++) + { + ImFont* font = io.Fonts->Fonts[n]; + ImGui::PushID((void*)font); + if (ImGui::Selectable(font->GetDebugName(), font == font_current)) + io.FontDefault = font; + ImGui::PopID(); + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + HelpMarker( + "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and docs/FONTS.md for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); +} + +// [Internal] Display details for a single font, called by ShowStyleEditor(). +static void ShowFont(ImFont* font) +{ + ImGuiIO& io = ImGui::GetIO(); + ImGuiStyle& style = ImGui::GetStyle(); + bool font_details_opened = ImGui::TreeNode(font, "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", + font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount); + ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) { io.FontDefault = font; } + if (!font_details_opened) + return; + + // Display preview text + ImGui::PushFont(font); + ImGui::Text("The quick brown fox jumps over the lazy dog"); + ImGui::PopFont(); + + // Display details + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font + ImGui::SameLine(); HelpMarker( + "Note than the default embedded font is NOT meant to be scaled.\n\n" + "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " + "You may oversample them to get some flexibility with scaling. " + "You can also render at multiple sizes and select which one to use at runtime.\n\n" + "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); + ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + ImGui::Text("Fallback character: '%c' (U+%04X)", font->FallbackChar, font->FallbackChar); + ImGui::Text("Ellipsis character: '%c' (U+%04X)", font->EllipsisChar, font->EllipsisChar); + const int surface_sqrt = (int)sqrtf((float)font->MetricsTotalSurface); + ImGui::Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); + for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) + if (font->ConfigData) + if (const ImFontConfig* cfg = &font->ConfigData[config_i]) + ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); + + // Display all glyphs of the fonts in separate pages of 256 characters + if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + { + const ImU32 glyph_col = ImGui::GetColorU32(ImGuiCol_Text); + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + { + // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) + // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT + // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) + if (!(base & 4095) && font->IsGlyphRangeUnused(base, base + 4095)) + { + base += 4096 - 256; + continue; + } + + int count = 0; + for (unsigned int n = 0; n < 256; n++) + if (font->FindGlyphNoFallback((ImWchar)(base + n))) + count++; + if (count <= 0) + continue; + if (!ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) + continue; + float cell_size = font->FontSize * 1; + float cell_spacing = style.ItemSpacing.y; + ImVec2 base_pos = ImGui::GetCursorScreenPos(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + for (unsigned int n = 0; n < 256; n++) + { + // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions + // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); + const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); + if (glyph) + font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); + if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) + { + ImGui::BeginTooltip(); + ImGui::Text("Codepoint: U+%04X", base + n); + ImGui::Separator(); + ImGui::Text("Visible: %d", glyph->Visible); + ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); + ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); + ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + ImGui::EndTooltip(); + } + } + ImGui::Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::TreePop(); +} + +void ImGui::ShowFontAtlas(ImFontAtlas* atlas) +{ + for (int i = 0; i < atlas->Fonts.Size; i++) + { + ImFont* font = atlas->Fonts[i]; + ImGui::PushID(font); + ShowFont(font); + ImGui::PopID(); + } + if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + { + ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); + ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0, 0), ImVec2(1, 1), tint_col, border_col); + ImGui::TreePop(); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Style Editor / ShowStyleEditor() +//----------------------------------------------------------------------------- +// - ShowStyleSelector() +// - ShowStyleEditor() +//----------------------------------------------------------------------------- + +// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. +// Here we use the simplified Combo() api that packs items into a single literal string. +// Useful for quick combo boxes where the choices are known locally. +bool ImGui::ShowStyleSelector(const char* label) +{ + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Dark\0Light\0Classic\0")) + { + switch (style_idx) + { + case 0: ImGui::StyleColorsDark(); break; + case 1: ImGui::StyleColorsLight(); break; + case 2: ImGui::StyleColorsClassic(); break; + } + return true; + } + return false; +} + +void ImGui::ShowStyleEditor(ImGuiStyle* ref) +{ + // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to + // (without a reference style pointer, we will use one compared locally as a reference) + ImGuiStyle& style = ImGui::GetStyle(); + static ImGuiStyle ref_saved_style; + + // Default to using internal storage as reference + static bool init = true; + if (init && ref == NULL) + ref_saved_style = style; + init = false; + if (ref == NULL) + ref = &ref_saved_style; + + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + + if (ImGui::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + ImGui::ShowFontSelector("Fonts##Selector"); + + // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding + { bool border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } + ImGui::SameLine(); + { bool border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } + ImGui::SameLine(); + { bool border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } + + // Save/Revert button + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) + style = *ref; + ImGui::SameLine(); + HelpMarker( + "Save/Revert in local non-persistent storage. Default Colors definition are not affected. " + "Use \"Export\" below to save them somewhere."); + + ImGui::Separator(); + + if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) + { + if (ImGui::BeginTabItem("Sizes")) + { + ImGui::Text("Main"); + ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + ImGui::Text("Borders"); + ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::Text("Rounding"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::Text("Alignment"); + ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + int window_menu_button_position = style.WindowMenuButtonPosition + 1; + if (ImGui::Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0")) + style.WindowMenuButtonPosition = window_menu_button_position - 1; + ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); + ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); + ImGui::SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); + ImGui::SliderFloat2("SelectableTextAlign", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); + ImGui::SameLine(); HelpMarker("Alignment applies when a selectable is larger than its text content."); + ImGui::Text("Safe Area Padding"); + ImGui::SameLine(); HelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); + ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Colors")) + { + static int output_dest = 0; + static bool output_only_modified = true; + if (ImGui::Button("Export")) + { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const ImVec4& col = style.Colors[i]; + const char* name = ImGui::GetStyleColorName(i); + if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) + ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, + name, 23 - (int)strlen(name), "", col.x, col.y, col.z, col.w); + } + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", ImGui::GetFontSize() * 16); + + static ImGuiColorEditFlags alpha_flags = 0; + if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine(); + if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine(); + if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine(); + HelpMarker( + "In the color list:\n" + "Left-click on color square to open color picker,\n" + "Right-click to open edit options menu."); + + ImGui::BeginChild("##colors", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) + { + // Tips: in a real user application, you may want to merge and use an icon font into the main font, + // so instead of "Save"/"Revert" you'd use icons! + // Read the FAQ and docs/FONTS.md about using icon fonts. It's really easy and super convenient! + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) { style.Colors[i] = ref->Colors[i]; } + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Fonts")) + { + ImGuiIO& io = ImGui::GetIO(); + ImFontAtlas* atlas = io.Fonts; + HelpMarker("Read FAQ and docs/FONTS.md for details on font loading."); + ImGui::ShowFontAtlas(atlas); + + // Post-baking font scaling. Note that this is NOT the nice way of scaling fonts, read below. + // (we enforce hard clamping manually as by default DragFloat/SliderFloat allows CTRL+Click text to get out of bounds). + const float MIN_SCALE = 0.3f; + const float MAX_SCALE = 2.0f; + HelpMarker( + "Those are old settings provided for convenience.\n" + "However, the _correct_ way of scaling your UI is currently to reload your font at the designed size, " + "rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\n" + "Using those settings here will give you poor quality results."); + static float window_scale = 1.0f; + ImGui::PushItemWidth(ImGui::GetFontSize() * 8); + if (ImGui::DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window + ImGui::SetWindowFontScale(window_scale); + ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp); // Scale everything + ImGui::PopItemWidth(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Rendering")) + { + ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); + ImGui::SameLine(); + HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); + + ImGui::Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); + ImGui::SameLine(); + HelpMarker("Faster lines using texture data. Require backend to render with bilinear filtering (not point/nearest filtering)."); + + ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + ImGui::PushItemWidth(ImGui::GetFontSize() * 8); + ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); + if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; + + // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. + ImGui::DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + if (ImGui::IsItemActive()) + { + ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); + ImGui::BeginTooltip(); + ImGui::TextUnformatted("(R = radius, N = number of segments)"); + ImGui::Spacing(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x; + for (int n = 0; n < 8; n++) + { + const float RAD_MIN = 5.0f; + const float RAD_MAX = 70.0f; + const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (8.0f - 1.0f); + + ImGui::BeginGroup(); + + ImGui::Text("R: %.f\nN: %d", rad, draw_list->_CalcCircleAutoSegmentCount(rad)); + + const float canvas_width = IM_MAX(min_widget_width, rad * 2.0f); + const float offset_x = floorf(canvas_width * 0.5f); + const float offset_y = floorf(RAD_MAX); + + const ImVec2 p1 = ImGui::GetCursorScreenPos(); + draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); + ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + + /* + const ImVec2 p2 = ImGui::GetCursorScreenPos(); + draw_list->AddCircleFilled(ImVec2(p2.x + offset_x, p2.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); + ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + */ + + ImGui::EndGroup(); + ImGui::SameLine(); + } + ImGui::EndTooltip(); + } + ImGui::SameLine(); + HelpMarker("When drawing circle primitives with \"num_segments == 0\" tesselation will be calculated automatically."); + + ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. + ImGui::PopItemWidth(); + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::PopItemWidth(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() +//----------------------------------------------------------------------------- +// - ShowExampleAppMainMenuBar() +// - ShowExampleMenuFile() +//----------------------------------------------------------------------------- + +// Demonstrate creating a "main" fullscreen menu bar and populating it. +// Note the difference between BeginMainMenuBar() and BeginMenuBar(): +// - BeginMenuBar() = menu-bar inside current window (which needs the ImGuiWindowFlags_MenuBar flag!) +// - BeginMainMenuBar() = helper to create menu-bar-sized window at the top of the main viewport + call BeginMenuBar() into it. +static void ShowExampleAppMainMenuBar() +{ + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::MenuItem("Undo", "CTRL+Z")) {} + if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item + ImGui::Separator(); + if (ImGui::MenuItem("Cut", "CTRL+X")) {} + if (ImGui::MenuItem("Copy", "CTRL+C")) {} + if (ImGui::MenuItem("Paste", "CTRL+V")) {} + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} + +// Note that shortcuts are currently provided for display only +// (future version will add explicit flags to BeginMenu() to request processing shortcuts) +static void ShowExampleMenuFile() +{ + ImGui::MenuItem("(demo menu)", NULL, false, false); + if (ImGui::MenuItem("New")) {} + if (ImGui::MenuItem("Open", "Ctrl+O")) {} + if (ImGui::BeginMenu("Open Recent")) + { + ImGui::MenuItem("fish_hat.c"); + ImGui::MenuItem("fish_hat.inl"); + ImGui::MenuItem("fish_hat.h"); + if (ImGui::BeginMenu("More..")) + { + ImGui::MenuItem("Hello"); + ImGui::MenuItem("Sailor"); + if (ImGui::BeginMenu("Recurse..")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Save", "Ctrl+S")) {} + if (ImGui::MenuItem("Save As..")) {} + + ImGui::Separator(); + if (ImGui::BeginMenu("Options")) + { + static bool enabled = true; + ImGui::MenuItem("Enabled", "", &enabled); + ImGui::BeginChild("child", ImVec2(0, 60), true); + for (int i = 0; i < 10; i++) + ImGui::Text("Scrolling Text %d", i); + ImGui::EndChild(); + static float f = 0.5f; + static int n = 0; + ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); + ImGui::InputFloat("Input", &f, 0.1f); + ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Colors")) + { + float sz = ImGui::GetTextLineHeight(); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName((ImGuiCol)i); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + sz, p.y + sz), ImGui::GetColorU32((ImGuiCol)i)); + ImGui::Dummy(ImVec2(sz, sz)); + ImGui::SameLine(); + ImGui::MenuItem(name); + } + ImGui::EndMenu(); + } + + // Here we demonstrate appending again to the "Options" menu (which we already created above) + // Of course in this demo it is a little bit silly that this function calls BeginMenu("Options") twice. + // In a real code-base using it would make senses to use this feature from very different code locations. + if (ImGui::BeginMenu("Options")) // <-- Append! + { + static bool b = true; + ImGui::Checkbox("SomeOption", &b); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Disabled", false)) // Disabled + { + IM_ASSERT(0); + } + if (ImGui::MenuItem("Checked", NULL, true)) {} + if (ImGui::MenuItem("Quit", "Alt+F4")) {} +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Debug Console / ShowExampleAppConsole() +//----------------------------------------------------------------------------- + +// Demonstrate creating a simple console window, with scrolling, filtering, completion and history. +// For the console example, we are using a more C++ like approach of declaring a class to hold both data and functions. +struct ExampleAppConsole +{ + char InputBuf[256]; + ImVector Items; + ImVector Commands; + ImVector History; + int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + ImGuiTextFilter Filter; + bool AutoScroll; + bool ScrollToBottom; + + ExampleAppConsole() + { + ClearLog(); + memset(InputBuf, 0, sizeof(InputBuf)); + HistoryPos = -1; + + // "CLASSIFY" is here to provide the test case where "C"+[tab] completes to "CL" and display multiple matches. + Commands.push_back("HELP"); + Commands.push_back("HISTORY"); + Commands.push_back("CLEAR"); + Commands.push_back("CLASSIFY"); + AutoScroll = true; + ScrollToBottom = false; + AddLog("Welcome to Dear ImGui!"); + } + ~ExampleAppConsole() + { + ClearLog(); + for (int i = 0; i < History.Size; i++) + free(History[i]); + } + + // Portable helpers + static int Stricmp(const char* s1, const char* s2) { int d; while ((d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; } return d; } + static int Strnicmp(const char* s1, const char* s2, int n) { int d = 0; while (n > 0 && (d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; n--; } return d; } + static char* Strdup(const char* s) { IM_ASSERT(s); size_t len = strlen(s) + 1; void* buf = malloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); } + static void Strtrim(char* s) { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; } + + void ClearLog() + { + for (int i = 0; i < Items.Size; i++) + free(Items[i]); + Items.clear(); + } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + // FIXME-OPT + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf)-1] = 0; + va_end(args); + Items.push_back(Strdup(buf)); + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open)) + { + ImGui::End(); + return; + } + + // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. + // So e.g. IsItemHovered() will return true when hovering the title bar. + // Here we create a context menu only available from the title bar. + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::MenuItem("Close Console")) + *p_open = false; + ImGui::EndPopup(); + } + + ImGui::TextWrapped( + "This example implements a console with basic coloring, completion (TAB key) and history (Up/Down keys). A more elaborate " + "implementation may want to store entries along with extra data such as timestamp, emitter, etc."); + ImGui::TextWrapped("Enter 'HELP' for help."); + + // TODO: display items starting from the bottom + + if (ImGui::SmallButton("Add Debug Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } + ImGui::SameLine(); + if (ImGui::SmallButton("Add Debug Error")) { AddLog("[error] something went wrong"); } + ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { ClearLog(); } + ImGui::SameLine(); + bool copy_to_clipboard = ImGui::SmallButton("Copy"); + //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } + + ImGui::Separator(); + + // Options menu + if (ImGui::BeginPopup("Options")) + { + ImGui::Checkbox("Auto-scroll", &AutoScroll); + ImGui::EndPopup(); + } + + // Options, Filter + if (ImGui::Button("Options")) + ImGui::OpenPopup("Options"); + ImGui::SameLine(); + Filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + ImGui::Separator(); + + // Reserve enough left-over height for 1 separator + 1 input text + const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::Selectable("Clear")) ClearLog(); + ImGui::EndPopup(); + } + + // Display every line as a separate entry so we can change their color or add custom widgets. + // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping + // to only process visible items. The clipper will automatically measure the height of your first item and then + // "seek" to display only items in the visible area. + // To use the clipper we can replace your standard loop: + // for (int i = 0; i < Items.Size; i++) + // With: + // ImGuiListClipper clipper; + // clipper.Begin(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // - That your items are evenly spaced (same height) + // - That you have cheap random access to your elements (you can access them given their index, + // without processing all the ones before) + // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. + // We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices + // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage + // to improve this example code! + // If your items are of variable height: + // - Split them into same height items would be simpler and facilitate random-seeking into your list. + // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing + if (copy_to_clipboard) + ImGui::LogToClipboard(); + for (int i = 0; i < Items.Size; i++) + { + const char* item = Items[i]; + if (!Filter.PassFilter(item)) + continue; + + // Normally you would store more information in your item than just a string. + // (e.g. make Items[] an array of structure, store color/type etc.) + ImVec4 color; + bool has_color = false; + if (strstr(item, "[error]")) { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; } + else if (strncmp(item, "# ", 2) == 0) { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; } + if (has_color) + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(item); + if (has_color) + ImGui::PopStyleColor(); + } + if (copy_to_clipboard) + ImGui::LogFinish(); + + if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) + ImGui::SetScrollHereY(1.0f); + ScrollToBottom = false; + + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::Separator(); + + // Command-line + bool reclaim_focus = false; + ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory; + if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), input_text_flags, &TextEditCallbackStub, (void*)this)) + { + char* s = InputBuf; + Strtrim(s); + if (s[0]) + ExecCommand(s); + strcpy(s, ""); + reclaim_focus = true; + } + + // Auto-focus on window apparition + ImGui::SetItemDefaultFocus(); + if (reclaim_focus) + ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget + + ImGui::End(); + } + + void ExecCommand(const char* command_line) + { + AddLog("# %s\n", command_line); + + // Insert into history. First find match and delete it so it can be pushed to the back. + // This isn't trying to be smart or optimal. + HistoryPos = -1; + for (int i = History.Size - 1; i >= 0; i--) + if (Stricmp(History[i], command_line) == 0) + { + free(History[i]); + History.erase(History.begin() + i); + break; + } + History.push_back(Strdup(command_line)); + + // Process command + if (Stricmp(command_line, "CLEAR") == 0) + { + ClearLog(); + } + else if (Stricmp(command_line, "HELP") == 0) + { + AddLog("Commands:"); + for (int i = 0; i < Commands.Size; i++) + AddLog("- %s", Commands[i]); + } + else if (Stricmp(command_line, "HISTORY") == 0) + { + int first = History.Size - 10; + for (int i = first > 0 ? first : 0; i < History.Size; i++) + AddLog("%3d: %s\n", i, History[i]); + } + else + { + AddLog("Unknown command: '%s'\n", command_line); + } + + // On command input, we scroll to bottom even if AutoScroll==false + ScrollToBottom = true; + } + + // In C++11 you'd be better off using lambdas for this sort of forwarding callbacks + static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) + { + ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; + return console->TextEditCallback(data); + } + + int TextEditCallback(ImGuiInputTextCallbackData* data) + { + //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + // Example of TEXT COMPLETION + + // Locate beginning of current word + const char* word_end = data->Buf + data->CursorPos; + const char* word_start = word_end; + while (word_start > data->Buf) + { + const char c = word_start[-1]; + if (c == ' ' || c == '\t' || c == ',' || c == ';') + break; + word_start--; + } + + // Build a list of candidates + ImVector candidates; + for (int i = 0; i < Commands.Size; i++) + if (Strnicmp(Commands[i], word_start, (int)(word_end - word_start)) == 0) + candidates.push_back(Commands[i]); + + if (candidates.Size == 0) + { + // No match + AddLog("No match for \"%.*s\"!\n", (int)(word_end - word_start), word_start); + } + else if (candidates.Size == 1) + { + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing. + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); + data->InsertChars(data->CursorPos, candidates[0]); + data->InsertChars(data->CursorPos, " "); + } + else + { + // Multiple matches. Complete as much as we can.. + // So inputing "C"+Tab will complete to "CL" then display "CLEAR" and "CLASSIFY" as matches. + int match_len = (int)(word_end - word_start); + for (;;) + { + int c = 0; + bool all_candidates_matches = true; + for (int i = 0; i < candidates.Size && all_candidates_matches; i++) + if (i == 0) + c = toupper(candidates[i][match_len]); + else if (c == 0 || c != toupper(candidates[i][match_len])) + all_candidates_matches = false; + if (!all_candidates_matches) + break; + match_len++; + } + + if (match_len > 0) + { + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); + data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); + } + + // List matches + AddLog("Possible matches:\n"); + for (int i = 0; i < candidates.Size; i++) + AddLog("- %s\n", candidates[i]); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + // Example of HISTORY + const int prev_history_pos = HistoryPos; + if (data->EventKey == ImGuiKey_UpArrow) + { + if (HistoryPos == -1) + HistoryPos = History.Size - 1; + else if (HistoryPos > 0) + HistoryPos--; + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (HistoryPos != -1) + if (++HistoryPos >= History.Size) + HistoryPos = -1; + } + + // A better implementation would preserve the data on the current input line along with cursor position. + if (prev_history_pos != HistoryPos) + { + const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : ""; + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, history_str); + } + } + } + return 0; + } +}; + +static void ShowExampleAppConsole(bool* p_open) +{ + static ExampleAppConsole console; + console.Draw("Example: Console", p_open); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Debug Log / ShowExampleAppLog() +//----------------------------------------------------------------------------- + +// Usage: +// static ExampleAppLog my_log; +// my_log.AddLog("Hello %d world\n", 123); +// my_log.Draw("title"); +struct ExampleAppLog +{ + ImGuiTextBuffer Buf; + ImGuiTextFilter Filter; + ImVector LineOffsets; // Index to lines offset. We maintain this with AddLog() calls. + bool AutoScroll; // Keep scrolling if already at the bottom. + + ExampleAppLog() + { + AutoScroll = true; + Clear(); + } + + void Clear() + { + Buf.clear(); + LineOffsets.clear(); + LineOffsets.push_back(0); + } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + int old_size = Buf.size(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') + LineOffsets.push_back(old_size + 1); + } + + void Draw(const char* title, bool* p_open = NULL) + { + if (!ImGui::Begin(title, p_open)) + { + ImGui::End(); + return; + } + + // Options menu + if (ImGui::BeginPopup("Options")) + { + ImGui::Checkbox("Auto-scroll", &AutoScroll); + ImGui::EndPopup(); + } + + // Main window + if (ImGui::Button("Options")) + ImGui::OpenPopup("Options"); + ImGui::SameLine(); + bool clear = ImGui::Button("Clear"); + ImGui::SameLine(); + bool copy = ImGui::Button("Copy"); + ImGui::SameLine(); + Filter.Draw("Filter", -100.0f); + + ImGui::Separator(); + ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); + + if (clear) + Clear(); + if (copy) + ImGui::LogToClipboard(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + const char* buf = Buf.begin(); + const char* buf_end = Buf.end(); + if (Filter.IsActive()) + { + // In this example we don't use the clipper when Filter is enabled. + // This is because we don't have a random access on the result on our filter. + // A real application processing logs with ten of thousands of entries may want to store the result of + // search/filter.. especially if the filtering function is not trivial (e.g. reg-exp). + for (int line_no = 0; line_no < LineOffsets.Size; line_no++) + { + const char* line_start = buf + LineOffsets[line_no]; + const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; + if (Filter.PassFilter(line_start, line_end)) + ImGui::TextUnformatted(line_start, line_end); + } + } + else + { + // The simplest and easy way to display the entire buffer: + // ImGui::TextUnformatted(buf_begin, buf_end); + // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward + // to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are + // within the visible area. + // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them + // on your side is recommended. Using ImGuiListClipper requires + // - A) random access into your data + // - B) items all being the same height, + // both of which we can handle since we an array pointing to the beginning of each line of text. + // When using the filter (in the block of code above) we don't have random access into the data to display + // anymore, which is why we don't use the clipper. Storing or skimming through the search result would make + // it possible (and would be recommended if you want to search through tens of thousands of entries). + ImGuiListClipper clipper; + clipper.Begin(LineOffsets.Size); + while (clipper.Step()) + { + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + { + const char* line_start = buf + LineOffsets[line_no]; + const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; + ImGui::TextUnformatted(line_start, line_end); + } + } + clipper.End(); + } + ImGui::PopStyleVar(); + + if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + ImGui::SetScrollHereY(1.0f); + + ImGui::EndChild(); + ImGui::End(); + } +}; + +// Demonstrate creating a simple log window with basic filtering. +static void ShowExampleAppLog(bool* p_open) +{ + static ExampleAppLog log; + + // For the demo: add a debug button _BEFORE_ the normal log window contents + // We take advantage of a rarely used feature: multiple calls to Begin()/End() are appending to the _same_ window. + // Most of the contents of the window will be added by the log.Draw() call. + ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver); + ImGui::Begin("Example: Log", p_open); + if (ImGui::SmallButton("[Debug] Add 5 entries")) + { + static int counter = 0; + const char* categories[3] = { "info", "warn", "error" }; + const char* words[] = { "Bumfuzzled", "Cattywampus", "Snickersnee", "Abibliophobia", "Absquatulate", "Nincompoop", "Pauciloquent" }; + for (int n = 0; n < 5; n++) + { + const char* category = categories[counter % IM_ARRAYSIZE(categories)]; + const char* word = words[counter % IM_ARRAYSIZE(words)]; + log.AddLog("[%05d] [%s] Hello, current time is %.1f, here's a word: '%s'\n", + ImGui::GetFrameCount(), category, ImGui::GetTime(), word); + counter++; + } + } + ImGui::End(); + + // Actually call in the regular Log helper (which will Begin() into the same window as we just did) + log.Draw("Example: Log", p_open); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Simple Layout / ShowExampleAppLayout() +//----------------------------------------------------------------------------- + +// Demonstrate create a window with multiple child windows. +static void ShowExampleAppLayout(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Example: Simple layout", p_open, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // Left + static int selected = 0; + { + ImGui::BeginChild("left pane", ImVec2(150, 0), true); + for (int i = 0; i < 100; i++) + { + char label[128]; + sprintf(label, "MyObject %d", i); + if (ImGui::Selectable(label, selected == i)) + selected = i; + } + ImGui::EndChild(); + } + ImGui::SameLine(); + + // Right + { + ImGui::BeginGroup(); + ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us + ImGui::Text("MyObject: %d", selected); + ImGui::Separator(); + if (ImGui::BeginTabBar("##Tabs", ImGuiTabBarFlags_None)) + { + if (ImGui::BeginTabItem("Description")) + { + ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Details")) + { + ImGui::Text("ID: 0123456789"); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + ImGui::EndChild(); + if (ImGui::Button("Revert")) {} + ImGui::SameLine(); + if (ImGui::Button("Save")) {} + ImGui::EndGroup(); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() +//----------------------------------------------------------------------------- + +static void ShowPlaceholderObject(const char* prefix, int uid) +{ + // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. + ImGui::PushID(uid); + + // Text and Tree nodes are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing to make the tree lines equal high. + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); + ImGui::TableSetColumnIndex(1); + ImGui::Text("my sailor is rich"); + + if (node_open) + { + static float placeholder_members[8] = { 0.0f, 0.0f, 1.0f, 3.1416f, 100.0f, 999.0f }; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); // Use field index as identifier. + if (i < 2) + { + ShowPlaceholderObject("Child", 424242); + } + else + { + // Here we use a TreeNode to highlight on hover (we could use e.g. Selectable as well) + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet; + ImGui::TreeNodeEx("Field", flags, "Field_%d", i); + + ImGui::TableSetColumnIndex(1); + ImGui::SetNextItemWidth(-FLT_MIN); + if (i >= 5) + ImGui::InputFloat("##value", &placeholder_members[i], 1.0f); + else + ImGui::DragFloat("##value", &placeholder_members[i], 0.01f); + ImGui::NextColumn(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::PopID(); +} + +// Demonstrate create a simple property editor. +static void ShowExampleAppPropertyEditor(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Property editor", p_open)) + { + ImGui::End(); + return; + } + + HelpMarker( + "This example shows how you may implement a property editor using two columns.\n" + "All objects/fields data are dummies here.\n" + "Remember that in many simple cases, you can use ImGui::SameLine(xxx) to position\n" + "your cursor horizontally instead of using the Columns() API."); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + if (ImGui::BeginTable("split", 2, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable)) + { + // Iterate placeholder objects (all the same data) + for (int obj_i = 0; obj_i < 4; obj_i++) + { + ShowPlaceholderObject("Object", obj_i); + //ImGui::Separator(); + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Long Text / ShowExampleAppLongText() +//----------------------------------------------------------------------------- + +// Demonstrate/test rendering huge amount of text, and the incidence of clipping. +static void ShowExampleAppLongText(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Long text display", p_open)) + { + ImGui::End(); + return; + } + + static int test_type = 0; + static ImGuiTextBuffer log; + static int lines = 0; + ImGui::Text("Printing unusually long amount of text."); + ImGui::Combo("Test type", &test_type, + "Single call to TextUnformatted()\0" + "Multiple calls to Text(), clipped\0" + "Multiple calls to Text(), not clipped (slow)\0"); + ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size()); + if (ImGui::Button("Clear")) { log.clear(); lines = 0; } + ImGui::SameLine(); + if (ImGui::Button("Add 1000 lines")) + { + for (int i = 0; i < 1000; i++) + log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines + i); + lines += 1000; + } + ImGui::BeginChild("Log"); + switch (test_type) + { + case 0: + // Single call to TextUnformatted() with a big buffer + ImGui::TextUnformatted(log.begin(), log.end()); + break; + case 1: + { + // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGuiListClipper clipper; + clipper.Begin(lines); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + case 2: + // Multiple calls to Text(), not clipped (slow) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + for (int i = 0; i < lines; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + ImGui::EndChild(); + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() +//----------------------------------------------------------------------------- + +// Demonstrate creating a window which gets auto-resized according to its content. +static void ShowExampleAppAutoResize(bool* p_open) +{ + if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::End(); + return; + } + + static int lines = 10; + ImGui::TextUnformatted( + "Window will resize every-frame to the size of its content.\n" + "Note that you probably don't want to query the window size to\n" + "output your content because that would create a feedback loop."); + ImGui::SliderInt("Number of lines", &lines, 1, 20); + for (int i = 0; i < lines; i++) + ImGui::Text("%*sThis is line %d", i * 4, "", i); // Pad with space to extend size horizontally + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() +//----------------------------------------------------------------------------- + +// Demonstrate creating a window with custom resize constraints. +static void ShowExampleAppConstrainedResize(bool* p_open) +{ + struct CustomConstraints + { + // Helper functions to demonstrate programmatic constraints + static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize.x = data->DesiredSize.y = IM_MAX(data->DesiredSize.x, data->DesiredSize.y); } + static void Step(ImGuiSizeCallbackData* data) { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } + }; + + const char* test_desc[] = + { + "Resize vertical only", + "Resize horizontal only", + "Width > 100, Height > 100", + "Width 400-500", + "Height 400-500", + "Custom: Always Square", + "Custom: Fixed Steps (100)", + }; + + static bool auto_resize = false; + static int type = 0; + static int display_lines = 10; + if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only + if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only + if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 + if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width 400-500 + if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, 500)); // Height 400-500 + if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square + if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)(intptr_t)100); // Fixed Step + + ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; + if (ImGui::Begin("Example: Constrained Resize", p_open, flags)) + { + if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); + if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); + if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } + ImGui::SetNextItemWidth(200); + ImGui::Combo("Constraint", &type, test_desc, IM_ARRAYSIZE(test_desc)); + ImGui::SetNextItemWidth(200); + ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); + ImGui::Checkbox("Auto-resize", &auto_resize); + for (int i = 0; i < display_lines; i++) + ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Simple overlay / ShowExampleAppSimpleOverlay() +//----------------------------------------------------------------------------- + +// Demonstrate creating a simple static window with no decoration +// + a context-menu to choose which corner of the screen to use. +static void ShowExampleAppSimpleOverlay(bool* p_open) +{ + const float PAD = 10.0f; + static int corner = 0; + ImGuiIO& io = ImGui::GetIO(); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; + if (corner != -1) + { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImVec2 work_pos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any! + ImVec2 work_size = viewport->WorkSize; + ImVec2 window_pos, window_pos_pivot; + window_pos.x = (corner & 1) ? (work_pos.x + work_size.x - PAD) : (work_pos.x + PAD); + window_pos.y = (corner & 2) ? (work_pos.y + work_size.y - PAD) : (work_pos.y + PAD); + window_pos_pivot.x = (corner & 1) ? 1.0f : 0.0f; + window_pos_pivot.y = (corner & 2) ? 1.0f : 0.0f; + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + window_flags |= ImGuiWindowFlags_NoMove; + } + ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background + if (ImGui::Begin("Example: Simple overlay", p_open, window_flags)) + { + ImGui::Text("Simple overlay\n" "in the corner of the screen.\n" "(right-click to change position)"); + ImGui::Separator(); + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse Position: (%.1f,%.1f)", io.MousePos.x, io.MousePos.y); + else + ImGui::Text("Mouse Position: "); + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::MenuItem("Custom", NULL, corner == -1)) corner = -1; + if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; + if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; + if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; + if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; + if (p_open && ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndPopup(); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Fullscreen window / ShowExampleAppFullscreen() +//----------------------------------------------------------------------------- + +// Demonstrate creating a window covering the entire screen/viewport +static void ShowExampleAppFullscreen(bool* p_open) +{ + static bool use_work_area = true; + static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; + + // We demonstrate using the full viewport area or the work area (without menu-bars, task-bars etc.) + // Based on your use case you may want one of the other. + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos); + ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size); + + if (ImGui::Begin("Example: Fullscreen window", p_open, flags)) + { + ImGui::Checkbox("Use work area instead of main area", &use_work_area); + ImGui::SameLine(); + HelpMarker("Main Area = entire viewport,\nWork Area = entire viewport minus sections used by the main menu bars, task bars etc.\n\nEnable the main-menu bar in Examples menu to see the difference."); + + ImGui::CheckboxFlags("ImGuiWindowFlags_NoBackground", &flags, ImGuiWindowFlags_NoBackground); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoDecoration", &flags, ImGuiWindowFlags_NoDecoration); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoTitleBar", &flags, ImGuiWindowFlags_NoTitleBar); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoCollapse", &flags, ImGuiWindowFlags_NoCollapse); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoScrollbar", &flags, ImGuiWindowFlags_NoScrollbar); + ImGui::Unindent(); + + if (p_open && ImGui::Button("Close this window")) + *p_open = false; + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() +//----------------------------------------------------------------------------- + +// Demonstrate using "##" and "###" in identifiers to manipulate ID generation. +// This apply to all regular items as well. +// Read FAQ section "How can I have multiple widgets with the same label?" for details. +static void ShowExampleAppWindowTitles(bool*) +{ + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + const ImVec2 base_pos = viewport->Pos; + + // By default, Windows are uniquely identified by their title. + // You can use the "##" and "###" markers to manipulate the display/ID. + + // Using "##" to display same title but have unique identifier. + ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 100), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##1"); + ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); + ImGui::End(); + + ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 200), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##2"); + ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); + ImGui::End(); + + // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" + char buf[128]; + sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime() / 0.25f) & 3], ImGui::GetFrameCount()); + ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 300), ImGuiCond_FirstUseEver); + ImGui::Begin(buf); + ImGui::Text("This window has a changing title."); + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() +//----------------------------------------------------------------------------- + +// Demonstrate using the low-level ImDrawList to draw custom shapes. +static void ShowExampleAppCustomRendering(bool* p_open) +{ + if (!ImGui::Begin("Example: Custom rendering", p_open)) + { + ImGui::End(); + return; + } + + // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of + // overloaded operators, etc. Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your + // types and ImVec2/ImVec4. Dear ImGui defines overloaded operators but they are internal to imgui.cpp and not + // exposed outside (to avoid messing with your types) In this example we are not using the maths operators! + + if (ImGui::BeginTabBar("##TabBar")) + { + if (ImGui::BeginTabItem("Primitives")) + { + ImGui::PushItemWidth(-ImGui::GetFontSize() * 15); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Draw gradients + // (note that those are currently exacerbating our sRGB/Linear issues) + // Calling ImGui::GetColorU32() multiplies the given colors by the current Style Alpha, but you may pass the IM_COL32() directly as well.. + ImGui::Text("Gradients"); + ImVec2 gradient_size = ImVec2(ImGui::CalcItemWidth(), ImGui::GetFrameHeight()); + { + ImVec2 p0 = ImGui::GetCursorScreenPos(); + ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); + ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 0, 0, 255)); + ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 255, 255, 255)); + draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); + ImGui::InvisibleButton("##gradient1", gradient_size); + } + { + ImVec2 p0 = ImGui::GetCursorScreenPos(); + ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); + ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 255, 0, 255)); + ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 0, 0, 255)); + draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); + ImGui::InvisibleButton("##gradient2", gradient_size); + } + + // Draw a bunch of primitives + ImGui::Text("All primitives"); + static float sz = 36.0f; + static float thickness = 3.0f; + static int ngon_sides = 6; + static bool circle_segments_override = false; + static int circle_segments_override_v = 12; + static bool curve_segments_override = false; + static int curve_segments_override_v = 8; + static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); + ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 100.0f, "%.0f"); + ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f"); + ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); + ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + circle_segments_override |= ImGui::SliderInt("Circle segments override", &circle_segments_override_v, 3, 40); + ImGui::Checkbox("##curvessegmentoverride", &curve_segments_override); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); + ImGui::ColorEdit4("Color", &colf.x); + + const ImVec2 p = ImGui::GetCursorScreenPos(); + const ImU32 col = ImColor(colf); + const float spacing = 10.0f; + const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; + const float rounding = sz / 5.0f; + const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; + const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; + float x = p.x + 4.0f; + float y = p.y + 4.0f; + for (int n = 0; n < 2; n++) + { + // First line uses a thickness of 1.0f, second line uses the configurable thickness + float th = (n == 0) ? 1.0f : thickness; + draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon + draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th); x += sz + spacing; // Square + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th); x += sz + spacing; // Square with all rounded corners + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th); x += sz + spacing; // Square with two rounded corners + draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle + //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle + draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) + draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) + draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line + + // Quadratic Bezier Curve (3 control points) + ImVec2 cp3[3] = { ImVec2(x, y + sz * 0.6f), ImVec2(x + sz * 0.5f, y - sz * 0.4f), ImVec2(x + sz, y + sz) }; + draw_list->AddBezierQuadratic(cp3[0], cp3[1], cp3[2], col, th, curve_segments); x += sz + spacing; + + // Cubic Bezier Curve (4 control points) + ImVec2 cp4[4] = { ImVec2(x, y), ImVec2(x + sz * 1.3f, y + sz * 0.3f), ImVec2(x + sz - sz * 1.3f, y + sz - sz * 0.3f), ImVec2(x + sz, y + sz) }; + draw_list->AddBezierCubic(cp4[0], cp4[1], cp4[2], cp4[3], col, th, curve_segments); + + x = p.x + 4; + y += sz + spacing; + } + draw_list->AddNgonFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz*0.5f, col, ngon_sides); x += sz + spacing; // N-gon + draw_list->AddCircleFilled(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments); x += sz + spacing; // Circle + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col); x += sz + spacing; // Square + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f); x += sz + spacing; // Square with all rounded corners + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br); x += sz + spacing; // Square with two rounded corners + draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col); x += sz + spacing; // Triangle + //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness) + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col); x += sz; // Pixel (faster than AddLine) + draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); + + ImGui::Dummy(ImVec2((sz + spacing) * 10.2f, (sz + spacing) * 3.0f)); + ImGui::PopItemWidth(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Canvas")) + { + static ImVector points; + static ImVec2 scrolling(0.0f, 0.0f); + static bool opt_enable_grid = true; + static bool opt_enable_context_menu = true; + static bool adding_line = false; + + ImGui::Checkbox("Enable grid", &opt_enable_grid); + ImGui::Checkbox("Enable context menu", &opt_enable_context_menu); + ImGui::Text("Mouse Left: drag to add lines,\nMouse Right: drag to scroll, click for context menu."); + + // Typically you would use a BeginChild()/EndChild() pair to benefit from a clipping region + own scrolling. + // Here we demonstrate that this can be replaced by simple offsetting + custom drawing + PushClipRect/PopClipRect() calls. + // To use a child window instead we could use, e.g: + // ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Disable padding + // ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(50, 50, 50, 255)); // Set a background color + // ImGui::BeginChild("canvas", ImVec2(0.0f, 0.0f), true, ImGuiWindowFlags_NoMove); + // ImGui::PopStyleColor(); + // ImGui::PopStyleVar(); + // [...] + // ImGui::EndChild(); + + // Using InvisibleButton() as a convenience 1) it will advance the layout cursor and 2) allows us to use IsItemHovered()/IsItemActive() + ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! + ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available + if (canvas_sz.x < 50.0f) canvas_sz.x = 50.0f; + if (canvas_sz.y < 50.0f) canvas_sz.y = 50.0f; + ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); + + // Draw border and background color + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(50, 50, 50, 255)); + draw_list->AddRect(canvas_p0, canvas_p1, IM_COL32(255, 255, 255, 255)); + + // This will catch our interactions + ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + const bool is_hovered = ImGui::IsItemHovered(); // Hovered + const bool is_active = ImGui::IsItemActive(); // Held + const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); // Lock scrolled origin + const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x, io.MousePos.y - origin.y); + + // Add first and second point + if (is_hovered && !adding_line && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + points.push_back(mouse_pos_in_canvas); + points.push_back(mouse_pos_in_canvas); + adding_line = true; + } + if (adding_line) + { + points.back() = mouse_pos_in_canvas; + if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) + adding_line = false; + } + + // Pan (we use a zero mouse threshold when there's no context menu) + // You may decide to make that threshold dynamic based on whether the mouse is hovering something etc. + const float mouse_threshold_for_pan = opt_enable_context_menu ? -1.0f : 0.0f; + if (is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) + { + scrolling.x += io.MouseDelta.x; + scrolling.y += io.MouseDelta.y; + } + + // Context menu (under default mouse threshold) + ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); + if (opt_enable_context_menu && ImGui::IsMouseReleased(ImGuiMouseButton_Right) && drag_delta.x == 0.0f && drag_delta.y == 0.0f) + ImGui::OpenPopupOnItemClick("context"); + if (ImGui::BeginPopup("context")) + { + if (adding_line) + points.resize(points.size() - 2); + adding_line = false; + if (ImGui::MenuItem("Remove one", NULL, false, points.Size > 0)) { points.resize(points.size() - 2); } + if (ImGui::MenuItem("Remove all", NULL, false, points.Size > 0)) { points.clear(); } + ImGui::EndPopup(); + } + + // Draw grid + all lines in the canvas + draw_list->PushClipRect(canvas_p0, canvas_p1, true); + if (opt_enable_grid) + { + const float GRID_STEP = 64.0f; + for (float x = fmodf(scrolling.x, GRID_STEP); x < canvas_sz.x; x += GRID_STEP) + draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y), ImVec2(canvas_p0.x + x, canvas_p1.y), IM_COL32(200, 200, 200, 40)); + for (float y = fmodf(scrolling.y, GRID_STEP); y < canvas_sz.y; y += GRID_STEP) + draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y), ImVec2(canvas_p1.x, canvas_p0.y + y), IM_COL32(200, 200, 200, 40)); + } + for (int n = 0; n < points.Size; n += 2) + draw_list->AddLine(ImVec2(origin.x + points[n].x, origin.y + points[n].y), ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y), IM_COL32(255, 255, 0, 255), 2.0f); + draw_list->PopClipRect(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("BG/FG draw lists")) + { + static bool draw_bg = true; + static bool draw_fg = true; + ImGui::Checkbox("Draw in Background draw list", &draw_bg); + ImGui::SameLine(); HelpMarker("The Background draw list will be rendered below every Dear ImGui windows."); + ImGui::Checkbox("Draw in Foreground draw list", &draw_fg); + ImGui::SameLine(); HelpMarker("The Foreground draw list will be rendered over every Dear ImGui windows."); + ImVec2 window_pos = ImGui::GetWindowPos(); + ImVec2 window_size = ImGui::GetWindowSize(); + ImVec2 window_center = ImVec2(window_pos.x + window_size.x * 0.5f, window_pos.y + window_size.y * 0.5f); + if (draw_bg) + ImGui::GetBackgroundDrawList()->AddCircle(window_center, window_size.x * 0.6f, IM_COL32(255, 0, 0, 200), 0, 10 + 4); + if (draw_fg) + ImGui::GetForegroundDrawList()->AddCircle(window_center, window_size.y * 0.6f, IM_COL32(0, 255, 0, 200), 0, 10); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() +//----------------------------------------------------------------------------- + +// Simplified structure to mimic a Document model +struct MyDocument +{ + const char* Name; // Document title + bool Open; // Set when open (we keep an array of all available documents to simplify demo code!) + bool OpenPrev; // Copy of Open from last update. + bool Dirty; // Set when the document has been modified + bool WantClose; // Set when the document + ImVec4 Color; // An arbitrary variable associated to the document + + MyDocument(const char* name, bool open = true, const ImVec4& color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f)) + { + Name = name; + Open = OpenPrev = open; + Dirty = false; + WantClose = false; + Color = color; + } + void DoOpen() { Open = true; } + void DoQueueClose() { WantClose = true; } + void DoForceClose() { Open = false; Dirty = false; } + void DoSave() { Dirty = false; } + + // Display placeholder contents for the Document + static void DisplayContents(MyDocument* doc) + { + ImGui::PushID(doc); + ImGui::Text("Document \"%s\"", doc->Name); + ImGui::PushStyleColor(ImGuiCol_Text, doc->Color); + ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); + ImGui::PopStyleColor(); + if (ImGui::Button("Modify", ImVec2(100, 0))) + doc->Dirty = true; + ImGui::SameLine(); + if (ImGui::Button("Save", ImVec2(100, 0))) + doc->DoSave(); + ImGui::ColorEdit3("color", &doc->Color.x); // Useful to test drag and drop and hold-dragged-to-open-tab behavior. + ImGui::PopID(); + } + + // Display context menu for the Document + static void DisplayContextMenu(MyDocument* doc) + { + if (!ImGui::BeginPopupContextItem()) + return; + + char buf[256]; + sprintf(buf, "Save %s", doc->Name); + if (ImGui::MenuItem(buf, "CTRL+S", false, doc->Open)) + doc->DoSave(); + if (ImGui::MenuItem("Close", "CTRL+W", false, doc->Open)) + doc->DoQueueClose(); + ImGui::EndPopup(); + } +}; + +struct ExampleAppDocuments +{ + ImVector Documents; + + ExampleAppDocuments() + { + Documents.push_back(MyDocument("Lettuce", true, ImVec4(0.4f, 0.8f, 0.4f, 1.0f))); + Documents.push_back(MyDocument("Eggplant", true, ImVec4(0.8f, 0.5f, 1.0f, 1.0f))); + Documents.push_back(MyDocument("Carrot", true, ImVec4(1.0f, 0.8f, 0.5f, 1.0f))); + Documents.push_back(MyDocument("Tomato", false, ImVec4(1.0f, 0.3f, 0.4f, 1.0f))); + Documents.push_back(MyDocument("A Rather Long Title", false)); + Documents.push_back(MyDocument("Some Document", false)); + } +}; + +// [Optional] Notify the system of Tabs/Windows closure that happened outside the regular tab interface. +// If a tab has been closed programmatically (aka closed from another source such as the Checkbox() in the demo, +// as opposed to clicking on the regular tab closing button) and stops being submitted, it will take a frame for +// the tab bar to notice its absence. During this frame there will be a gap in the tab bar, and if the tab that has +// disappeared was the selected one, the tab bar will report no selected tab during the frame. This will effectively +// give the impression of a flicker for one frame. +// We call SetTabItemClosed() to manually notify the Tab Bar or Docking system of removed tabs to avoid this glitch. +// Note that this completely optional, and only affect tab bars with the ImGuiTabBarFlags_Reorderable flag. +static void NotifyOfDocumentsClosedElsewhere(ExampleAppDocuments& app) +{ + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (!doc->Open && doc->OpenPrev) + ImGui::SetTabItemClosed(doc->Name); + doc->OpenPrev = doc->Open; + } +} + +void ShowExampleAppDocuments(bool* p_open) +{ + static ExampleAppDocuments app; + + // Options + static bool opt_reorderable = true; + static ImGuiTabBarFlags opt_fitting_flags = ImGuiTabBarFlags_FittingPolicyDefault_; + + bool window_contents_visible = ImGui::Begin("Example: Documents", p_open, ImGuiWindowFlags_MenuBar); + if (!window_contents_visible) + { + ImGui::End(); + return; + } + + // Menu + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + int open_count = 0; + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + open_count += app.Documents[doc_n].Open ? 1 : 0; + + if (ImGui::BeginMenu("Open", open_count < app.Documents.Size)) + { + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (!doc->Open) + if (ImGui::MenuItem(doc->Name)) + doc->DoOpen(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Close All Documents", NULL, false, open_count > 0)) + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + app.Documents[doc_n].DoQueueClose(); + if (ImGui::MenuItem("Exit", "Alt+F4")) {} + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // [Debug] List documents with one checkbox for each + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (doc_n > 0) + ImGui::SameLine(); + ImGui::PushID(doc); + if (ImGui::Checkbox(doc->Name, &doc->Open)) + if (!doc->Open) + doc->DoForceClose(); + ImGui::PopID(); + } + + ImGui::Separator(); + + // Submit Tab Bar and Tabs + { + ImGuiTabBarFlags tab_bar_flags = (opt_fitting_flags) | (opt_reorderable ? ImGuiTabBarFlags_Reorderable : 0); + if (ImGui::BeginTabBar("##tabs", tab_bar_flags)) + { + if (opt_reorderable) + NotifyOfDocumentsClosedElsewhere(app); + + // [DEBUG] Stress tests + //if ((ImGui::GetFrameCount() % 30) == 0) docs[1].Open ^= 1; // [DEBUG] Automatically show/hide a tab. Test various interactions e.g. dragging with this on. + //if (ImGui::GetIO().KeyCtrl) ImGui::SetTabItemSelected(docs[1].Name); // [DEBUG] Test SetTabItemSelected(), probably not very useful as-is anyway.. + + // Submit Tabs + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (!doc->Open) + continue; + + ImGuiTabItemFlags tab_flags = (doc->Dirty ? ImGuiTabItemFlags_UnsavedDocument : 0); + bool visible = ImGui::BeginTabItem(doc->Name, &doc->Open, tab_flags); + + // Cancel attempt to close when unsaved add to save queue so we can display a popup. + if (!doc->Open && doc->Dirty) + { + doc->Open = true; + doc->DoQueueClose(); + } + + MyDocument::DisplayContextMenu(doc); + if (visible) + { + MyDocument::DisplayContents(doc); + ImGui::EndTabItem(); + } + } + + ImGui::EndTabBar(); + } + } + + // Update closing queue + static ImVector close_queue; + if (close_queue.empty()) + { + // Close queue is locked once we started a popup + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (doc->WantClose) + { + doc->WantClose = false; + close_queue.push_back(doc); + } + } + } + + // Display closing confirmation UI + if (!close_queue.empty()) + { + int close_queue_unsaved_documents = 0; + for (int n = 0; n < close_queue.Size; n++) + if (close_queue[n]->Dirty) + close_queue_unsaved_documents++; + + if (close_queue_unsaved_documents == 0) + { + // Close documents when all are unsaved + for (int n = 0; n < close_queue.Size; n++) + close_queue[n]->DoForceClose(); + close_queue.clear(); + } + else + { + if (!ImGui::IsPopupOpen("Save?")) + ImGui::OpenPopup("Save?"); + if (ImGui::BeginPopupModal("Save?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("Save change to the following items?"); + float item_height = ImGui::GetTextLineHeightWithSpacing(); + if (ImGui::BeginChildFrame(ImGui::GetID("frame"), ImVec2(-FLT_MIN, 6.25f * item_height))) + { + for (int n = 0; n < close_queue.Size; n++) + if (close_queue[n]->Dirty) + ImGui::Text("%s", close_queue[n]->Name); + ImGui::EndChildFrame(); + } + + ImVec2 button_size(ImGui::GetFontSize() * 7.0f, 0.0f); + if (ImGui::Button("Yes", button_size)) + { + for (int n = 0; n < close_queue.Size; n++) + { + if (close_queue[n]->Dirty) + close_queue[n]->DoSave(); + close_queue[n]->DoForceClose(); + } + close_queue.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No", button_size)) + { + for (int n = 0; n < close_queue.Size; n++) + close_queue[n]->DoForceClose(); + close_queue.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", button_size)) + { + close_queue.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + } + + ImGui::End(); +} + +// End of Demo code +#else + +void ImGui::ShowAboutWindow(bool*) {} +void ImGui::ShowDemoWindow(bool*) {} +void ImGui::ShowUserGuide() {} +void ImGui::ShowStyleEditor(ImGuiStyle*) {} + +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/vendor/librw/skeleton/imgui/imgui_draw.cpp b/vendor/librw/skeleton/imgui/imgui_draw.cpp new file mode 100644 index 0000000..54490ed --- /dev/null +++ b/vendor/librw/skeleton/imgui/imgui_draw.cpp @@ -0,0 +1,4152 @@ +// dear imgui, v1.83 +// (drawing and font code) + +/* + +Index of this file: + +// [SECTION] STB libraries implementation +// [SECTION] Style functions +// [SECTION] ImDrawList +// [SECTION] ImDrawListSplitter +// [SECTION] ImDrawData +// [SECTION] Helpers ShadeVertsXXX functions +// [SECTION] ImFontConfig +// [SECTION] ImFontAtlas +// [SECTION] ImFontAtlas glyph ranges helpers +// [SECTION] ImFontGlyphRangesBuilder +// [SECTION] ImFont +// [SECTION] ImGui Internal Render Helpers +// [SECTION] Decompression code +// [SECTION] Default font data (ProggyClean.ttf) + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif + +#include "imgui_internal.h" +#ifdef IMGUI_ENABLE_FREETYPE +#include "misc/freetype/imgui_freetype.h" +#endif + +#include // vsnprintf, sscanf, printf +#if !defined(alloca) +#if defined(__GLIBC__) || defined(__sun) || defined(__APPLE__) || defined(__NEWLIB__) +#include // alloca (glibc uses . Note that Cygwin may have _WIN32 defined, so the order matters here) +#elif defined(_WIN32) +#include // alloca +#if !defined(alloca) +#define alloca _alloca // for clang with MS Codegen +#endif +#else +#include // alloca +#endif +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#pragma warning (disable: 6255) // [Static Analyzer] _alloca indicates failure by raising a stack overflow exception. Consider using _malloca instead. +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer) +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#if __has_warning("-Walloca") +#pragma clang diagnostic ignored "-Walloca" // warning: use of function '__builtin_alloca' is discouraged +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning: declaration requires a global destructor // similar to above, not sure what the exact difference is. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wcomma" // warning: possible misuse of comma operator here +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning: macro name is a reserved identifier +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wstack-protector" // warning: stack protector not protecting local variables: variable length buffer +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif + +//------------------------------------------------------------------------- +// [SECTION] STB libraries implementation +//------------------------------------------------------------------------- + +// Compile time options: +//#define IMGUI_STB_NAMESPACE ImStb +//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" +//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION + +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE +{ +#endif + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration +#pragma warning (disable: 6011) // (stb_rectpack) Dereferencing NULL pointer 'cur->next'. +#pragma warning (disable: 6385) // (stb_truetype) Reading invalid data from 'buffer': the readable size is '_Old_3`kernel_width' bytes, but '3' bytes may be read. +#pragma warning (disable: 28182) // (stb_rectpack) Dereferencing NULL pointer. 'cur' contains the same NULL value as 'cur->next' did. +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wcast-qual" // warning: cast from 'const xxxx *' to 'xxx *' drops const qualifier +#endif + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#endif + +#ifndef STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) +#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in another compilation unit +#define STBRP_STATIC +#define STBRP_ASSERT(x) do { IM_ASSERT(x); } while (0) +#define STBRP_SORT ImQsort +#define STB_RECT_PACK_IMPLEMENTATION +#endif +#ifdef IMGUI_STB_RECT_PACK_FILENAME +#include IMGUI_STB_RECT_PACK_FILENAME +#else +#include "imstb_rectpack.h" +#endif +#endif + +#ifdef IMGUI_ENABLE_STB_TRUETYPE +#ifndef STB_TRUETYPE_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) +#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION // in case the user already have an implementation in another compilation unit +#define STBTT_malloc(x,u) ((void)(u), IM_ALLOC(x)) +#define STBTT_free(x,u) ((void)(u), IM_FREE(x)) +#define STBTT_assert(x) do { IM_ASSERT(x); } while(0) +#define STBTT_fmod(x,y) ImFmod(x,y) +#define STBTT_sqrt(x) ImSqrt(x) +#define STBTT_pow(x,y) ImPow(x,y) +#define STBTT_fabs(x) ImFabs(x) +#define STBTT_ifloor(x) ((int)ImFloorSigned(x)) +#define STBTT_iceil(x) ((int)ImCeil(x)) +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#else +#define STBTT_DEF extern +#endif +#ifdef IMGUI_STB_TRUETYPE_FILENAME +#include IMGUI_STB_TRUETYPE_FILENAME +#else +#include "imstb_truetype.h" +#endif +#endif +#endif // IMGUI_ENABLE_STB_TRUETYPE + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif + +#ifdef IMGUI_STB_NAMESPACE +} // namespace ImStb +using namespace IMGUI_STB_NAMESPACE; +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Style functions +//----------------------------------------------------------------------------- + +void ImGui::StyleColorsDark(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = colors[ImGuiCol_Border]; + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.20f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); +} + +void ImGui::StyleColorsClassic(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.85f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); + colors[ImGuiCol_Border] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.47f, 0.47f, 0.69f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.41f, 0.64f, 0.69f); + colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.48f, 0.71f, 0.79f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.46f, 0.54f, 0.80f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); + colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 0.60f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.10f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); + colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.27f, 0.27f, 0.38f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.45f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.26f, 0.26f, 0.28f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); +} + +// Those light colors are better suited with a thicker font than the default one + FrameBorder +void ImGui::StyleColorsLight(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 0.62f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.35f, 0.35f, 0.35f, 0.17f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.90f); + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.78f, 0.87f, 0.98f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.57f, 0.57f, 0.64f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.68f, 0.68f, 0.74f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.30f, 0.30f, 0.30f, 0.09f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.70f, 0.70f, 0.70f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImDrawList +//----------------------------------------------------------------------------- + +ImDrawListSharedData::ImDrawListSharedData() +{ + memset(this, 0, sizeof(*this)); + for (int i = 0; i < IM_ARRAYSIZE(ArcFastVtx); i++) + { + const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(ArcFastVtx); + ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); + } + ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); +} + +void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) +{ + if (CircleSegmentMaxError == max_error) + return; + + IM_ASSERT(max_error > 0.0f); + CircleSegmentMaxError = max_error; + for (int i = 0; i < IM_ARRAYSIZE(CircleSegmentCounts); i++) + { + const float radius = (float)i; + CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : 0); + } + ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); +} + +// Initialize before use in a new frame. We always have a command ready in the buffer. +void ImDrawList::_ResetForNewFrame() +{ + // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. + // (those should be IM_STATIC_ASSERT() in theory but with our pre C++11 setup the whole check doesn't compile with GCC) + IM_ASSERT(IM_OFFSETOF(ImDrawCmd, ClipRect) == 0); + IM_ASSERT(IM_OFFSETOF(ImDrawCmd, TextureId) == sizeof(ImVec4)); + IM_ASSERT(IM_OFFSETOF(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID)); + + CmdBuffer.resize(0); + IdxBuffer.resize(0); + VtxBuffer.resize(0); + Flags = _Data->InitialFlags; + memset(&_CmdHeader, 0, sizeof(_CmdHeader)); + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.resize(0); + _TextureIdStack.resize(0); + _Path.resize(0); + _Splitter.Clear(); + CmdBuffer.push_back(ImDrawCmd()); + _FringeScale = 1.0f; +} + +void ImDrawList::_ClearFreeMemory() +{ + CmdBuffer.clear(); + IdxBuffer.clear(); + VtxBuffer.clear(); + Flags = ImDrawListFlags_None; + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.clear(); + _TextureIdStack.clear(); + _Path.clear(); + _Splitter.ClearFreeMemory(); +} + +ImDrawList* ImDrawList::CloneOutput() const +{ + ImDrawList* dst = IM_NEW(ImDrawList(_Data)); + dst->CmdBuffer = CmdBuffer; + dst->IdxBuffer = IdxBuffer; + dst->VtxBuffer = VtxBuffer; + dst->Flags = Flags; + return dst; +} + +void ImDrawList::AddDrawCmd() +{ + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = _CmdHeader.ClipRect; // Same as calling ImDrawCmd_HeaderCopy() + draw_cmd.TextureId = _CmdHeader.TextureId; + draw_cmd.VtxOffset = _CmdHeader.VtxOffset; + draw_cmd.IdxOffset = IdxBuffer.Size; + + IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w); + CmdBuffer.push_back(draw_cmd); +} + +// Pop trailing draw command (used before merging or presenting to user) +// Note that this leaves the ImDrawList in a state unfit for further commands, as most code assume that CmdBuffer.Size > 0 && CmdBuffer.back().UserCallback == NULL +void ImDrawList::_PopUnusedDrawCmd() +{ + if (CmdBuffer.Size == 0) + return; + ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + if (curr_cmd->ElemCount == 0 && curr_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); +} + +void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) +{ + ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + IM_ASSERT(curr_cmd->UserCallback == NULL); + if (curr_cmd->ElemCount != 0) + { + AddDrawCmd(); + curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + } + curr_cmd->UserCallback = callback; + curr_cmd->UserCallbackData = callback_data; + + AddDrawCmd(); // Force a new command after us (see comment below) +} + +// Compare ClipRect, TextureId and VtxOffset with a single memcmp() +#define ImDrawCmd_HeaderSize (IM_OFFSETOF(ImDrawCmd, VtxOffset) + sizeof(unsigned int)) +#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TextureId, VtxOffset +#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TextureId, VtxOffset + +// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack. +// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only. +void ImDrawList::_OnChangedClipRect() +{ + // If current command is used with different settings we need to add a new command + ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + if (curr_cmd->ElemCount != 0 && memcmp(&curr_cmd->ClipRect, &_CmdHeader.ClipRect, sizeof(ImVec4)) != 0) + { + AddDrawCmd(); + return; + } + IM_ASSERT(curr_cmd->UserCallback == NULL); + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = curr_cmd - 1; + if (curr_cmd->ElemCount == 0 && CmdBuffer.Size > 1 && ImDrawCmd_HeaderCompare(&_CmdHeader, prev_cmd) == 0 && prev_cmd->UserCallback == NULL) + { + CmdBuffer.pop_back(); + return; + } + + curr_cmd->ClipRect = _CmdHeader.ClipRect; +} + +void ImDrawList::_OnChangedTextureID() +{ + // If current command is used with different settings we need to add a new command + ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + if (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != _CmdHeader.TextureId) + { + AddDrawCmd(); + return; + } + IM_ASSERT(curr_cmd->UserCallback == NULL); + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = curr_cmd - 1; + if (curr_cmd->ElemCount == 0 && CmdBuffer.Size > 1 && ImDrawCmd_HeaderCompare(&_CmdHeader, prev_cmd) == 0 && prev_cmd->UserCallback == NULL) + { + CmdBuffer.pop_back(); + return; + } + + curr_cmd->TextureId = _CmdHeader.TextureId; +} + +void ImDrawList::_OnChangedVtxOffset() +{ + // We don't need to compare curr_cmd->VtxOffset != _CmdHeader.VtxOffset because we know it'll be different at the time we call this. + _VtxCurrentIdx = 0; + ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + //IM_ASSERT(curr_cmd->VtxOffset != _CmdHeader.VtxOffset); // See #3349 + if (curr_cmd->ElemCount != 0) + { + AddDrawCmd(); + return; + } + IM_ASSERT(curr_cmd->UserCallback == NULL); + curr_cmd->VtxOffset = _CmdHeader.VtxOffset; +} + +int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const +{ + // Automatic segment count + const int radius_idx = (int)(radius + 0.999999f); // ceil to never reduce accuracy + if (radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts)) + return _Data->CircleSegmentCounts[radius_idx]; // Use cached value + else + return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError); +} + +// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) +void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect) +{ + ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y); + if (intersect_with_current_clip_rect) + { + ImVec4 current = _CmdHeader.ClipRect; + if (cr.x < current.x) cr.x = current.x; + if (cr.y < current.y) cr.y = current.y; + if (cr.z > current.z) cr.z = current.z; + if (cr.w > current.w) cr.w = current.w; + } + cr.z = ImMax(cr.x, cr.z); + cr.w = ImMax(cr.y, cr.w); + + _ClipRectStack.push_back(cr); + _CmdHeader.ClipRect = cr; + _OnChangedClipRect(); +} + +void ImDrawList::PushClipRectFullScreen() +{ + PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w)); +} + +void ImDrawList::PopClipRect() +{ + _ClipRectStack.pop_back(); + _CmdHeader.ClipRect = (_ClipRectStack.Size == 0) ? _Data->ClipRectFullscreen : _ClipRectStack.Data[_ClipRectStack.Size - 1]; + _OnChangedClipRect(); +} + +void ImDrawList::PushTextureID(ImTextureID texture_id) +{ + _TextureIdStack.push_back(texture_id); + _CmdHeader.TextureId = texture_id; + _OnChangedTextureID(); +} + +void ImDrawList::PopTextureID() +{ + _TextureIdStack.pop_back(); + _CmdHeader.TextureId = (_TextureIdStack.Size == 0) ? (ImTextureID)NULL : _TextureIdStack.Data[_TextureIdStack.Size - 1]; + _OnChangedTextureID(); +} + +// Reserve space for a number of vertices and indices. +// You must finish filling your reserved data before calling PrimReserve() again, as it may reallocate or +// submit the intermediate results. PrimUnreserve() can be used to release unused allocations. +void ImDrawList::PrimReserve(int idx_count, int vtx_count) +{ + // Large mesh support (when enabled) + IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0); + if (sizeof(ImDrawIdx) == 2 && (_VtxCurrentIdx + vtx_count >= (1 << 16)) && (Flags & ImDrawListFlags_AllowVtxOffset)) + { + // FIXME: In theory we should be testing that vtx_count <64k here. + // In practice, RenderText() relies on reserving ahead for a worst case scenario so it is currently useful for us + // to not make that check until we rework the text functions to handle clipping and large horizontal lines better. + _CmdHeader.VtxOffset = VtxBuffer.Size; + _OnChangedVtxOffset(); + } + + ImDrawCmd* draw_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + draw_cmd->ElemCount += idx_count; + + int vtx_buffer_old_size = VtxBuffer.Size; + VtxBuffer.resize(vtx_buffer_old_size + vtx_count); + _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size; + + int idx_buffer_old_size = IdxBuffer.Size; + IdxBuffer.resize(idx_buffer_old_size + idx_count); + _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; +} + +// Release the a number of reserved vertices/indices from the end of the last reservation made with PrimReserve(). +void ImDrawList::PrimUnreserve(int idx_count, int vtx_count) +{ + IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0); + + ImDrawCmd* draw_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + draw_cmd->ElemCount -= idx_count; + VtxBuffer.shrink(VtxBuffer.Size - vtx_count); + IdxBuffer.shrink(IdxBuffer.Size - idx_count); +} + +// Fully unrolled with inline call to keep our debug builds decently fast. +void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +// On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds. +// Those macros expects l-values. +#define IM_NORMALIZE2F_OVER_ZERO(VX,VY) do { float d2 = VX*VX + VY*VY; if (d2 > 0.0f) { float inv_len = ImRsqrt(d2); VX *= inv_len; VY *= inv_len; } } while (0) +#define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) +#define IM_FIXNORMAL2F(VX,VY) do { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } while (0) + +// TODO: Thickness anti-aliased lines cap are missing their AA fringe. +// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags, float thickness) +{ + if (points_count < 2) + return; + + const bool closed = (flags & ImDrawFlags_Closed) != 0; + const ImVec2 opaque_uv = _Data->TexUvWhitePixel; + const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw + const bool thick_line = (thickness > _FringeScale); + + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + // Anti-aliased stroke + const float AA_SIZE = _FringeScale; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + // Thicknesses <1.0 should behave like thickness 1.0 + thickness = ImMax(thickness, 1.0f); + const int integer_thickness = (int)thickness; + const float fractional_thickness = thickness - integer_thickness; + + // Do we want to draw this line using a texture? + // - For now, only draw integer-width lines using textures to avoid issues with the way scaling occurs, could be improved. + // - If AA_SIZE is not 1.0f we cannot use the texture path. + const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f); + + // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTex unless ImFontAtlasFlags_NoBakedLines is off + IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)); + + const int idx_count = use_texture ? (count * 6) : (thick_line ? count * 18 : count * 12); + const int vtx_count = use_texture ? (points_count * 2) : (thick_line ? points_count * 4 : points_count * 3); + PrimReserve(idx_count, vtx_count); + + // Temporary buffer + // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point + ImVec2* temp_normals = (ImVec2*)alloca(points_count * ((use_texture || !thick_line) ? 3 : 5) * sizeof(ImVec2)); //-V630 + ImVec2* temp_points = temp_normals + points_count; + + // Calculate normals (tangents) for each line segment + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; + float dx = points[i2].x - points[i1].x; + float dy = points[i2].y - points[i1].y; + IM_NORMALIZE2F_OVER_ZERO(dx, dy); + temp_normals[i1].x = dy; + temp_normals[i1].y = -dx; + } + if (!closed) + temp_normals[points_count - 1] = temp_normals[points_count - 2]; + + // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point + if (use_texture || !thick_line) + { + // [PATH 1] Texture-based lines (thick or non-thick) + // [PATH 2] Non texture-based lines (non-thick) + + // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA. + // - In the texture-based path, we don't use AA_SIZE here because the +1 is tied to the generated texture + // (see ImFontAtlasBuildRenderLinesTexData() function), and so alternate values won't work without changes to that code. + // - In the non texture-based paths, we would allow AA_SIZE to potentially be != 1.0f with a patch (e.g. fringe_scale patch to + // allow scaling geometry while preserving one-screen-pixel AA fringe). + const float half_draw_size = use_texture ? ((thickness * 0.5f) + 1) : AA_SIZE; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * half_draw_size; + temp_points[1] = points[0] - temp_normals[0] * half_draw_size; + temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * half_draw_size; + temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * half_draw_size; + } + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment + for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment + { + const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; // i2 is the second point of the line segment + const unsigned int idx2 = ((i1 + 1) == points_count) ? _VtxCurrentIdx : (idx1 + (use_texture ? 2 : 3)); // Vertex index for end of segment + + // Average normals + float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f; + float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f; + IM_FIXNORMAL2F(dm_x, dm_y); + dm_x *= half_draw_size; // dm_x, dm_y are offset to the outer edge of the AA area + dm_y *= half_draw_size; + + // Add temporary vertexes for the outer edges + ImVec2* out_vtx = &temp_points[i2 * 2]; + out_vtx[0].x = points[i2].x + dm_x; + out_vtx[0].y = points[i2].y + dm_y; + out_vtx[1].x = points[i2].x - dm_x; + out_vtx[1].y = points[i2].y - dm_y; + + if (use_texture) + { + // Add indices for two triangles + _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 1); // Right tri + _IdxWritePtr[3] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[4] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Left tri + _IdxWritePtr += 6; + } + else + { + // Add indexes for four triangles + _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 2); // Right tri 1 + _IdxWritePtr[3] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[4] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Right tri 2 + _IdxWritePtr[6] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[7] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[8] = (ImDrawIdx)(idx1 + 0); // Left tri 1 + _IdxWritePtr[9] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1); // Left tri 2 + _IdxWritePtr += 12; + } + + idx1 = idx2; + } + + // Add vertexes for each point on the line + if (use_texture) + { + // If we're using textures we only need to emit the left/right edge vertices + ImVec4 tex_uvs = _Data->TexUvLines[integer_thickness]; + /*if (fractional_thickness != 0.0f) // Currently always zero when use_texture==false! + { + const ImVec4 tex_uvs_1 = _Data->TexUvLines[integer_thickness + 1]; + tex_uvs.x = tex_uvs.x + (tex_uvs_1.x - tex_uvs.x) * fractional_thickness; // inlined ImLerp() + tex_uvs.y = tex_uvs.y + (tex_uvs_1.y - tex_uvs.y) * fractional_thickness; + tex_uvs.z = tex_uvs.z + (tex_uvs_1.z - tex_uvs.z) * fractional_thickness; + tex_uvs.w = tex_uvs.w + (tex_uvs_1.w - tex_uvs.w) * fractional_thickness; + }*/ + ImVec2 tex_uv0(tex_uvs.x, tex_uvs.y); + ImVec2 tex_uv1(tex_uvs.z, tex_uvs.w); + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = temp_points[i * 2 + 0]; _VtxWritePtr[0].uv = tex_uv0; _VtxWritePtr[0].col = col; // Left-side outer edge + _VtxWritePtr[1].pos = temp_points[i * 2 + 1]; _VtxWritePtr[1].uv = tex_uv1; _VtxWritePtr[1].col = col; // Right-side outer edge + _VtxWritePtr += 2; + } + } + else + { + // If we're not using a texture, we need the center vertex as well + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col; // Center of line + _VtxWritePtr[1].pos = temp_points[i * 2 + 0]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col_trans; // Left-side outer edge + _VtxWritePtr[2].pos = temp_points[i * 2 + 1]; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col_trans; // Right-side outer edge + _VtxWritePtr += 3; + } + } + } + else + { + // [PATH 2] Non texture-based lines (thick): we need to draw the solid line core and thus require four vertices per point + const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (!closed) + { + const int points_last = points_count - 1; + temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); + temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); + temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[points_last * 4 + 0] = points[points_last] + temp_normals[points_last] * (half_inner_thickness + AA_SIZE); + temp_points[points_last * 4 + 1] = points[points_last] + temp_normals[points_last] * (half_inner_thickness); + temp_points[points_last * 4 + 2] = points[points_last] - temp_normals[points_last] * (half_inner_thickness); + temp_points[points_last * 4 + 3] = points[points_last] - temp_normals[points_last] * (half_inner_thickness + AA_SIZE); + } + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment + for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment + { + const int i2 = (i1 + 1) == points_count ? 0 : (i1 + 1); // i2 is the second point of the line segment + const unsigned int idx2 = (i1 + 1) == points_count ? _VtxCurrentIdx : (idx1 + 4); // Vertex index for end of segment + + // Average normals + float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f; + float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f; + IM_FIXNORMAL2F(dm_x, dm_y); + float dm_out_x = dm_x * (half_inner_thickness + AA_SIZE); + float dm_out_y = dm_y * (half_inner_thickness + AA_SIZE); + float dm_in_x = dm_x * half_inner_thickness; + float dm_in_y = dm_y * half_inner_thickness; + + // Add temporary vertices + ImVec2* out_vtx = &temp_points[i2 * 4]; + out_vtx[0].x = points[i2].x + dm_out_x; + out_vtx[0].y = points[i2].y + dm_out_y; + out_vtx[1].x = points[i2].x + dm_in_x; + out_vtx[1].y = points[i2].y + dm_in_y; + out_vtx[2].x = points[i2].x - dm_in_x; + out_vtx[2].y = points[i2].y - dm_in_y; + out_vtx[3].x = points[i2].x - dm_out_x; + out_vtx[3].y = points[i2].y - dm_out_y; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[4] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 1); + _IdxWritePtr[6] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[7] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[8] = (ImDrawIdx)(idx1 + 0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1); + _IdxWritePtr[12] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[13] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[14] = (ImDrawIdx)(idx1 + 3); + _IdxWritePtr[15] = (ImDrawIdx)(idx1 + 3); _IdxWritePtr[16] = (ImDrawIdx)(idx2 + 3); _IdxWritePtr[17] = (ImDrawIdx)(idx2 + 2); + _IdxWritePtr += 18; + + idx1 = idx2; + } + + // Add vertices + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = temp_points[i * 4 + 0]; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col_trans; + _VtxWritePtr[1].pos = temp_points[i * 4 + 1]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = temp_points[i * 4 + 2]; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = temp_points[i * 4 + 3]; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = col_trans; + _VtxWritePtr += 4; + } + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // [PATH 4] Non texture-based, Non anti-aliased lines + const int idx_count = count * 6; + const int vtx_count = count * 4; // FIXME-OPT: Not sharing edges + PrimReserve(idx_count, vtx_count); + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; + const ImVec2& p1 = points[i1]; + const ImVec2& p2 = points[i2]; + + float dx = p2.x - p1.x; + float dy = p2.y - p1.y; + IM_NORMALIZE2F_OVER_ZERO(dx, dy); + dx *= (thickness * 0.5f); + dy *= (thickness * 0.5f); + + _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + 1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + 2); + _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx + 2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx + 3); + _IdxWritePtr += 6; + _VtxCurrentIdx += 4; + } + } +} + +// We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. +void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) +{ + if (points_count < 3) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + + if (Flags & ImDrawListFlags_AntiAliasedFill) + { + // Anti-aliased Fill + const float AA_SIZE = _FringeScale; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + const int idx_count = (points_count - 2)*3 + points_count * 6; + const int vtx_count = (points_count * 2); + PrimReserve(idx_count, vtx_count); + + // Add indexes for fill + unsigned int vtx_inner_idx = _VtxCurrentIdx; + unsigned int vtx_outer_idx = _VtxCurrentIdx + 1; + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + ((i - 1) << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (i << 1)); + _IdxWritePtr += 3; + } + + // Compute normals + ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); //-V630 + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + float dx = p1.x - p0.x; + float dy = p1.y - p0.y; + IM_NORMALIZE2F_OVER_ZERO(dx, dy); + temp_normals[i0].x = dy; + temp_normals[i0].y = -dx; + } + + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2& n0 = temp_normals[i0]; + const ImVec2& n1 = temp_normals[i1]; + float dm_x = (n0.x + n1.x) * 0.5f; + float dm_y = (n0.y + n1.y) * 0.5f; + IM_FIXNORMAL2F(dm_x, dm_y); + dm_x *= AA_SIZE * 0.5f; + dm_y *= AA_SIZE * 0.5f; + + // Add vertices + _VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner + _VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer + _VtxWritePtr += 2; + + // Add indexes for fringes + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); + _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); + _IdxWritePtr += 6; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Fill + const int idx_count = (points_count - 2)*3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + for (int i = 0; i < vtx_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr++; + } + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + i - 1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + i); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} + +void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step) +{ + if (radius <= 0.0f) + { + _Path.push_back(center); + return; + } + + // Calculate arc auto segment step size + if (a_step <= 0) + a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius); + + // Make sure we never do steps larger than one quarter of the circle + a_step = ImClamp(a_step, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + + const int sample_range = ImAbs(a_max_sample - a_min_sample); + const int a_next_step = a_step; + + int samples = sample_range + 1; + bool extra_max_sample = false; + if (a_step > 1) + { + samples = sample_range / a_step + 1; + const int overstep = sample_range % a_step; + + if (overstep > 0) + { + extra_max_sample = true; + samples++; + + // When we have overstep to avoid awkwardly looking one long line and one tiny one at the end, + // distribute first step range evenly between them by reducing first step size. + if (sample_range > 0) + a_step -= (a_step - overstep) / 2; + } + } + + _Path.resize(_Path.Size + samples); + ImVec2* out_ptr = _Path.Data + (_Path.Size - samples); + + int sample_index = a_min_sample; + if (sample_index < 0 || sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX) + { + sample_index = sample_index % IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + if (sample_index < 0) + sample_index += IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + } + + if (a_max_sample >= a_min_sample) + { + for (int a = a_min_sample; a <= a_max_sample; a += a_step, sample_index += a_step, a_step = a_next_step) + { + // a_step is clamped to IM_DRAWLIST_ARCFAST_SAMPLE_MAX, so we have guaranteed that it will not wrap over range twice or more + if (sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX) + sample_index -= IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + + const ImVec2 s = _Data->ArcFastVtx[sample_index]; + out_ptr->x = center.x + s.x * radius; + out_ptr->y = center.y + s.y * radius; + out_ptr++; + } + } + else + { + for (int a = a_min_sample; a >= a_max_sample; a -= a_step, sample_index -= a_step, a_step = a_next_step) + { + // a_step is clamped to IM_DRAWLIST_ARCFAST_SAMPLE_MAX, so we have guaranteed that it will not wrap over range twice or more + if (sample_index < 0) + sample_index += IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + + const ImVec2 s = _Data->ArcFastVtx[sample_index]; + out_ptr->x = center.x + s.x * radius; + out_ptr->y = center.y + s.y * radius; + out_ptr++; + } + } + + if (extra_max_sample) + { + int normalized_max_sample = a_max_sample % IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + if (normalized_max_sample < 0) + normalized_max_sample += IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + + const ImVec2 s = _Data->ArcFastVtx[normalized_max_sample]; + out_ptr->x = center.x + s.x * radius; + out_ptr->y = center.y + s.y * radius; + out_ptr++; + } + + IM_ASSERT_PARANOID(_Path.Data + _Path.Size == out_ptr); +} + +void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) +{ + if (radius <= 0.0f) + { + _Path.push_back(center); + return; + } + + // Note that we are adding a point at both a_min and a_max. + // If you are trying to draw a full closed circle you don't want the overlapping points! + _Path.reserve(_Path.Size + (num_segments + 1)); + for (int i = 0; i <= num_segments; i++) + { + const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); + _Path.push_back(ImVec2(center.x + ImCos(a) * radius, center.y + ImSin(a) * radius)); + } +} + +// 0: East, 3: South, 6: West, 9: North, 12: East +void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12) +{ + if (radius <= 0.0f) + { + _Path.push_back(center); + return; + } + _PathArcToFastEx(center, radius, a_min_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, a_max_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0); +} + +void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) +{ + if (radius <= 0.0f) + { + _Path.push_back(center); + return; + } + + if (num_segments > 0) + { + _PathArcToN(center, radius, a_min, a_max, num_segments); + return; + } + + // Automatic segment count + if (radius <= _Data->ArcFastRadiusCutoff) + { + const bool a_is_reverse = a_max < a_min; + + // We are going to use precomputed values for mid samples. + // Determine first and last sample in lookup table that belong to the arc. + const float a_min_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_min / (IM_PI * 2.0f); + const float a_max_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_max / (IM_PI * 2.0f); + + const int a_min_sample = a_is_reverse ? (int)ImFloorSigned(a_min_sample_f) : (int)ImCeil(a_min_sample_f); + const int a_max_sample = a_is_reverse ? (int)ImCeil(a_max_sample_f) : (int)ImFloorSigned(a_max_sample_f); + const int a_mid_samples = a_is_reverse ? ImMax(a_min_sample - a_max_sample, 0) : ImMax(a_max_sample - a_min_sample, 0); + + const float a_min_segment_angle = a_min_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + const float a_max_segment_angle = a_max_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + const bool a_emit_start = (a_min_segment_angle - a_min) != 0.0f; + const bool a_emit_end = (a_max - a_max_segment_angle) != 0.0f; + + _Path.reserve(_Path.Size + (a_mid_samples + 1 + (a_emit_start ? 1 : 0) + (a_emit_end ? 1 : 0))); + if (a_emit_start) + _Path.push_back(ImVec2(center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius)); + if (a_mid_samples > 0) + _PathArcToFastEx(center, radius, a_min_sample, a_max_sample, 0); + if (a_emit_end) + _Path.push_back(ImVec2(center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius)); + } + else + { + const float arc_length = ImAbs(a_max - a_min); + const int circle_segment_count = _CalcCircleAutoSegmentCount(radius); + const int arc_segment_count = ImMax((int)ImCeil(circle_segment_count * arc_length / (IM_PI * 2.0f)), (int)(2.0f * IM_PI / arc_length)); + _PathArcToN(center, radius, a_min, a_max, arc_segment_count); + } +} + +ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t) +{ + float u = 1.0f - t; + float w1 = u * u * u; + float w2 = 3 * u * u * t; + float w3 = 3 * u * t * t; + float w4 = t * t * t; + return ImVec2(w1 * p1.x + w2 * p2.x + w3 * p3.x + w4 * p4.x, w1 * p1.y + w2 * p2.y + w3 * p3.y + w4 * p4.y); +} + +ImVec2 ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float t) +{ + float u = 1.0f - t; + float w1 = u * u; + float w2 = 2 * u * t; + float w3 = t * t; + return ImVec2(w1 * p1.x + w2 * p2.x + w3 * p3.x, w1 * p1.y + w2 * p2.y + w3 * p3.y); +} + +// Closely mimics ImBezierCubicClosestPointCasteljau() in imgui.cpp +static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +{ + float dx = x4 - x1; + float dy = y4 - y1; + float d2 = (x2 - x4) * dy - (y2 - y4) * dx; + float d3 = (x3 - x4) * dy - (y3 - y4) * dx; + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + { + path->push_back(ImVec2(x4, y4)); + } + else if (level < 10) + { + float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f; + float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f; + float x34 = (x3 + x4) * 0.5f, y34 = (y3 + y4) * 0.5f; + float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f; + float x234 = (x23 + x34) * 0.5f, y234 = (y23 + y34) * 0.5f; + float x1234 = (x123 + x234) * 0.5f, y1234 = (y123 + y234) * 0.5f; + PathBezierCubicCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); + PathBezierCubicCurveToCasteljau(path, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + } +} + +static void PathBezierQuadraticCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float tess_tol, int level) +{ + float dx = x3 - x1, dy = y3 - y1; + float det = (x2 - x3) * dy - (y2 - y3) * dx; + if (det * det * 4.0f < tess_tol * (dx * dx + dy * dy)) + { + path->push_back(ImVec2(x3, y3)); + } + else if (level < 10) + { + float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f; + float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f; + float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f; + PathBezierQuadraticCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, tess_tol, level + 1); + PathBezierQuadraticCurveToCasteljau(path, x123, y123, x23, y23, x3, y3, tess_tol, level + 1); + } +} + +void ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments) +{ + ImVec2 p1 = _Path.back(); + if (num_segments == 0) + { + PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); // Auto-tessellated + } + else + { + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + _Path.push_back(ImBezierCubicCalc(p1, p2, p3, p4, t_step * i_step)); + } +} + +void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments) +{ + ImVec2 p1 = _Path.back(); + if (num_segments == 0) + { + PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, _Data->CurveTessellationTol, 0);// Auto-tessellated + } + else + { + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + _Path.push_back(ImBezierQuadraticCalc(p1, p2, p3, t_step * i_step)); + } +} + +IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4)); +static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) +{ +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All) + // ~0 --> ImDrawFlags_RoundCornersAll or 0 + if (flags == ~0) + return ImDrawFlags_RoundCornersAll; + + // Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations) + // 0x01 --> ImDrawFlags_RoundCornersTopLeft (VALUE 0x01 OVERLAPS ImDrawFlags_Closed but ImDrawFlags_Closed is never valid in this path!) + // 0x02 --> ImDrawFlags_RoundCornersTopRight + // 0x03 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight + // 0x04 --> ImDrawFlags_RoundCornersBotLeft + // 0x05 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBotLeft + // ... + // 0x0F --> ImDrawFlags_RoundCornersAll or 0 + // (See all values in ImDrawCornerFlags_) + if (flags >= 0x01 && flags <= 0x0F) + return (flags << 4); + + // We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f' +#endif + + // If this triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values. + // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc... + IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); + + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + + return flags; +} + +void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags) +{ + flags = FixRectCornerFlags(flags); + rounding = ImMin(rounding, ImFabs(b.x - a.x) * ( ((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f ) - 1.0f); + rounding = ImMin(rounding, ImFabs(b.y - a.y) * ( ((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f ) - 1.0f); + + if (rounding <= 0.0f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + { + PathLineTo(a); + PathLineTo(ImVec2(b.x, a.y)); + PathLineTo(b); + PathLineTo(ImVec2(a.x, b.y)); + } + else + { + const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? rounding : 0.0f; + const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? rounding : 0.0f; + const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? rounding : 0.0f; + const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? rounding : 0.0f; + PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); + PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6); + } +} + +void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(p1 + ImVec2(0.5f, 0.5f)); + PathLineTo(p2 + ImVec2(0.5f, 0.5f)); + PathStroke(col, 0, thickness); +} + +// p_min = upper-left, p_max = lower-right +// Note we don't render 1 pixels sized rectangles properly. +void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + if (Flags & ImDrawListFlags_AntiAliasedLines) + PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); + else + PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. + PathStroke(col, ImDrawFlags_Closed, thickness); +} + +void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + if (rounding <= 0.0f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + { + PrimReserve(6, 4); + PrimRect(p_min, p_max, col); + } + else + { + PathRect(p_min, p_max, rounding, flags); + PathFillConvex(col); + } +} + +// p_min = upper-left, p_max = lower-right +void ImDrawList::AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) +{ + if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + PrimReserve(6, 4); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 2)); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 3)); + PrimWriteVtx(p_min, uv, col_upr_left); + PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv, col_upr_right); + PrimWriteVtx(p_max, uv, col_bot_right); + PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv, col_bot_left); +} + +void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(p1); + PathLineTo(p2); + PathLineTo(p3); + PathLineTo(p4); + PathStroke(col, ImDrawFlags_Closed, thickness); +} + +void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(p1); + PathLineTo(p2); + PathLineTo(p3); + PathLineTo(p4); + PathFillConvex(col); +} + +void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(p1); + PathLineTo(p2); + PathLineTo(p3); + PathStroke(col, ImDrawFlags_Closed, thickness); +} + +void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(p1); + PathLineTo(p2); + PathLineTo(p3); + PathFillConvex(col); +} + +void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0 || radius <= 0.0f) + return; + + // Obtain segment count + if (num_segments <= 0) + { + // Automatic segment count + num_segments = _CalcCircleAutoSegmentCount(radius); + } + else + { + // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX); + } + + // Because we are filling a closed shape we remove 1 from the count of segments/points + const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; + if (num_segments == 12) + PathArcToFast(center, radius - 0.5f, 0, 12 - 1); + else + PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); + PathStroke(col, ImDrawFlags_Closed, thickness); +} + +void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0 || radius <= 0.0f) + return; + + // Obtain segment count + if (num_segments <= 0) + { + // Automatic segment count + num_segments = _CalcCircleAutoSegmentCount(radius); + } + else + { + // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX); + } + + // Because we are filling a closed shape we remove 1 from the count of segments/points + const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; + if (num_segments == 12) + PathArcToFast(center, radius, 0, 12 - 1); + else + PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); + PathFillConvex(col); +} + +// Guaranteed to honor 'num_segments' +void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) + return; + + // Because we are filling a closed shape we remove 1 from the count of segments/points + const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); + PathStroke(col, ImDrawFlags_Closed, thickness); +} + +// Guaranteed to honor 'num_segments' +void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) + return; + + // Because we are filling a closed shape we remove 1 from the count of segments/points + const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); + PathFillConvex(col); +} + +// Cubic Bezier takes 4 controls points +void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(p1); + PathBezierCubicCurveTo(p2, p3, p4, num_segments); + PathStroke(col, 0, thickness); +} + +// Quadratic Bezier takes 3 controls points +void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(p1); + PathBezierQuadraticCurveTo(p2, p3, num_segments); + PathStroke(col, 0, thickness); +} + +void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (text_end == NULL) + text_end = text_begin + strlen(text_begin); + if (text_begin == text_end) + return; + + // Pull default font/size from the shared ImDrawListSharedData instance + if (font == NULL) + font = _Data->Font; + if (font_size == 0.0f) + font_size = _Data->FontSize; + + IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TextureId); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. + + ImVec4 clip_rect = _CmdHeader.ClipRect; + if (cpu_fine_clip_rect) + { + clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); + clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); + clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); + clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); + } + font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); +} + +void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) +{ + AddText(NULL, 0.0f, pos, col, text_begin, text_end); +} + +void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimRectUV(p_min, p_max, uv_min, uv_max, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimQuadUV(p1, p2, p3, p4, uv1, uv2, uv3, uv4, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + flags = FixRectCornerFlags(flags); + if (rounding <= 0.0f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + { + AddImage(user_texture_id, p_min, p_max, uv_min, uv_max, col); + return; + } + + const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + if (push_texture_id) + PushTextureID(user_texture_id); + + int vert_start_idx = VtxBuffer.Size; + PathRect(p_min, p_max, rounding, flags); + PathFillConvex(col); + int vert_end_idx = VtxBuffer.Size; + ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true); + + if (push_texture_id) + PopTextureID(); +} + + +//----------------------------------------------------------------------------- +// [SECTION] ImDrawListSplitter +//----------------------------------------------------------------------------- +// FIXME: This may be a little confusing, trying to be a little too low-level/optimal instead of just doing vector swap.. +//----------------------------------------------------------------------------- + +void ImDrawListSplitter::ClearFreeMemory() +{ + for (int i = 0; i < _Channels.Size; i++) + { + if (i == _Current) + memset(&_Channels[i], 0, sizeof(_Channels[i])); // Current channel is a copy of CmdBuffer/IdxBuffer, don't destruct again + _Channels[i]._CmdBuffer.clear(); + _Channels[i]._IdxBuffer.clear(); + } + _Current = 0; + _Count = 1; + _Channels.clear(); +} + +void ImDrawListSplitter::Split(ImDrawList* draw_list, int channels_count) +{ + IM_UNUSED(draw_list); + IM_ASSERT(_Current == 0 && _Count <= 1 && "Nested channel splitting is not supported. Please use separate instances of ImDrawListSplitter."); + int old_channels_count = _Channels.Size; + if (old_channels_count < channels_count) + { + _Channels.reserve(channels_count); // Avoid over reserving since this is likely to stay stable + _Channels.resize(channels_count); + } + _Count = channels_count; + + // Channels[] (24/32 bytes each) hold storage that we'll swap with draw_list->_CmdBuffer/_IdxBuffer + // The content of Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to. + // When we switch to the next channel, we'll copy draw_list->_CmdBuffer/_IdxBuffer into Channels[0] and then Channels[1] into draw_list->CmdBuffer/_IdxBuffer + memset(&_Channels[0], 0, sizeof(ImDrawChannel)); + for (int i = 1; i < channels_count; i++) + { + if (i >= old_channels_count) + { + IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel(); + } + else + { + _Channels[i]._CmdBuffer.resize(0); + _Channels[i]._IdxBuffer.resize(0); + } + } +} + +void ImDrawListSplitter::Merge(ImDrawList* draw_list) +{ + // Note that we never use or rely on _Channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use. + if (_Count <= 1) + return; + + SetCurrentChannel(draw_list, 0); + draw_list->_PopUnusedDrawCmd(); + + // Calculate our final buffer sizes. Also fix the incorrect IdxOffset values in each command. + int new_cmd_buffer_count = 0; + int new_idx_buffer_count = 0; + ImDrawCmd* last_cmd = (_Count > 0 && draw_list->CmdBuffer.Size > 0) ? &draw_list->CmdBuffer.back() : NULL; + int idx_offset = last_cmd ? last_cmd->IdxOffset + last_cmd->ElemCount : 0; + for (int i = 1; i < _Count; i++) + { + ImDrawChannel& ch = _Channels[i]; + + // Equivalent of PopUnusedDrawCmd() for this channel's cmdbuffer and except we don't need to test for UserCallback. + if (ch._CmdBuffer.Size > 0 && ch._CmdBuffer.back().ElemCount == 0) + ch._CmdBuffer.pop_back(); + + if (ch._CmdBuffer.Size > 0 && last_cmd != NULL) + { + ImDrawCmd* next_cmd = &ch._CmdBuffer[0]; + if (ImDrawCmd_HeaderCompare(last_cmd, next_cmd) == 0 && last_cmd->UserCallback == NULL && next_cmd->UserCallback == NULL) + { + // Merge previous channel last draw command with current channel first draw command if matching. + last_cmd->ElemCount += next_cmd->ElemCount; + idx_offset += next_cmd->ElemCount; + ch._CmdBuffer.erase(ch._CmdBuffer.Data); // FIXME-OPT: Improve for multiple merges. + } + } + if (ch._CmdBuffer.Size > 0) + last_cmd = &ch._CmdBuffer.back(); + new_cmd_buffer_count += ch._CmdBuffer.Size; + new_idx_buffer_count += ch._IdxBuffer.Size; + for (int cmd_n = 0; cmd_n < ch._CmdBuffer.Size; cmd_n++) + { + ch._CmdBuffer.Data[cmd_n].IdxOffset = idx_offset; + idx_offset += ch._CmdBuffer.Data[cmd_n].ElemCount; + } + } + draw_list->CmdBuffer.resize(draw_list->CmdBuffer.Size + new_cmd_buffer_count); + draw_list->IdxBuffer.resize(draw_list->IdxBuffer.Size + new_idx_buffer_count); + + // Write commands and indices in order (they are fairly small structures, we don't copy vertices only indices) + ImDrawCmd* cmd_write = draw_list->CmdBuffer.Data + draw_list->CmdBuffer.Size - new_cmd_buffer_count; + ImDrawIdx* idx_write = draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size - new_idx_buffer_count; + for (int i = 1; i < _Count; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (int sz = ch._CmdBuffer.Size) { memcpy(cmd_write, ch._CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; } + if (int sz = ch._IdxBuffer.Size) { memcpy(idx_write, ch._IdxBuffer.Data, sz * sizeof(ImDrawIdx)); idx_write += sz; } + } + draw_list->_IdxWritePtr = idx_write; + + // Ensure there's always a non-callback draw command trailing the command-buffer + if (draw_list->CmdBuffer.Size == 0 || draw_list->CmdBuffer.back().UserCallback != NULL) + draw_list->AddDrawCmd(); + + // If current command is used with different settings we need to add a new command + ImDrawCmd* curr_cmd = &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1]; + if (curr_cmd->ElemCount == 0) + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) + draw_list->AddDrawCmd(); + + _Count = 1; +} + +void ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx) +{ + IM_ASSERT(idx >= 0 && idx < _Count); + if (_Current == idx) + return; + + // Overwrite ImVector (12/16 bytes), four times. This is merely a silly optimization instead of doing .swap() + memcpy(&_Channels.Data[_Current]._CmdBuffer, &draw_list->CmdBuffer, sizeof(draw_list->CmdBuffer)); + memcpy(&_Channels.Data[_Current]._IdxBuffer, &draw_list->IdxBuffer, sizeof(draw_list->IdxBuffer)); + _Current = idx; + memcpy(&draw_list->CmdBuffer, &_Channels.Data[idx]._CmdBuffer, sizeof(draw_list->CmdBuffer)); + memcpy(&draw_list->IdxBuffer, &_Channels.Data[idx]._IdxBuffer, sizeof(draw_list->IdxBuffer)); + draw_list->_IdxWritePtr = draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size; + + // If current command is used with different settings we need to add a new command + ImDrawCmd* curr_cmd = (draw_list->CmdBuffer.Size == 0) ? NULL : &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1]; + if (curr_cmd == NULL) + draw_list->AddDrawCmd(); + else if (curr_cmd->ElemCount == 0) + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) + draw_list->AddDrawCmd(); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImDrawData +//----------------------------------------------------------------------------- + +// For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! +void ImDrawData::DeIndexAllBuffers() +{ + ImVector new_vtx_buffer; + TotalVtxCount = TotalIdxCount = 0; + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + if (cmd_list->IdxBuffer.empty()) + continue; + new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); + for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) + new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; + cmd_list->VtxBuffer.swap(new_vtx_buffer); + cmd_list->IdxBuffer.resize(0); + TotalVtxCount += cmd_list->VtxBuffer.Size; + } +} + +// Helper to scale the ClipRect field of each ImDrawCmd. +// Use if your final output buffer is at a different scale than draw_data->DisplaySize, +// or if there is a difference between your window resolution and framebuffer resolution. +void ImDrawData::ScaleClipRects(const ImVec2& fb_scale) +{ + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + ImDrawCmd* cmd = &cmd_list->CmdBuffer[cmd_i]; + cmd->ClipRect = ImVec4(cmd->ClipRect.x * fb_scale.x, cmd->ClipRect.y * fb_scale.y, cmd->ClipRect.z * fb_scale.x, cmd->ClipRect.w * fb_scale.y); + } + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Helpers ShadeVertsXXX functions +//----------------------------------------------------------------------------- + +// Generic linear color gradient, write to RGB fields, leave A untouched. +void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) +{ + ImVec2 gradient_extent = gradient_p1 - gradient_p0; + float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent); + ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx; + ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx; + const int col0_r = (int)(col0 >> IM_COL32_R_SHIFT) & 0xFF; + const int col0_g = (int)(col0 >> IM_COL32_G_SHIFT) & 0xFF; + const int col0_b = (int)(col0 >> IM_COL32_B_SHIFT) & 0xFF; + const int col_delta_r = ((int)(col1 >> IM_COL32_R_SHIFT) & 0xFF) - col0_r; + const int col_delta_g = ((int)(col1 >> IM_COL32_G_SHIFT) & 0xFF) - col0_g; + const int col_delta_b = ((int)(col1 >> IM_COL32_B_SHIFT) & 0xFF) - col0_b; + for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) + { + float d = ImDot(vert->pos - gradient_p0, gradient_extent); + float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + int r = (int)(col0_r + col_delta_r * t); + int g = (int)(col0_g + col_delta_g * t); + int b = (int)(col0_b + col_delta_b * t); + vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); + } +} + +// Distribute UV over (a, b) rectangle +void ImGui::ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp) +{ + const ImVec2 size = b - a; + const ImVec2 uv_size = uv_b - uv_a; + const ImVec2 scale = ImVec2( + size.x != 0.0f ? (uv_size.x / size.x) : 0.0f, + size.y != 0.0f ? (uv_size.y / size.y) : 0.0f); + + ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx; + ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx; + if (clamp) + { + const ImVec2 min = ImMin(uv_a, uv_b); + const ImVec2 max = ImMax(uv_a, uv_b); + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max); + } + else + { + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontConfig +//----------------------------------------------------------------------------- + +ImFontConfig::ImFontConfig() +{ + memset(this, 0, sizeof(*this)); + FontDataOwnedByAtlas = true; + OversampleH = 3; // FIXME: 2 may be a better default? + OversampleV = 1; + GlyphMaxAdvanceX = FLT_MAX; + RasterizerMultiply = 1.0f; + EllipsisChar = (ImWchar)-1; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas +//----------------------------------------------------------------------------- + +// A work of art lies ahead! (. = white layer, X = black layer, others are blank) +// The 2x2 white texels on the top left are the ones we'll use everywhere in Dear ImGui to render filled shapes. +const int FONT_ATLAS_DEFAULT_TEX_DATA_W = 108; // Actual texture will be 2 times that + 1 spacing. +const int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27; +static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] = +{ + "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX- XX " + "..- -X.....X- X.X - X.X -X.....X - X.....X- X..X " + "--- -XXX.XXX- X...X - X...X -X....X - X....X- X..X " + "X - X.X - X.....X - X.....X -X...X - X...X- X..X " + "XX - X.X -X.......X- X.......X -X..X.X - X.X..X- X..X " + "X.X - X.X -XXXX.XXXX- XXXX.XXXX -X.X X.X - X.X X.X- X..XXX " + "X..X - X.X - X.X - X.X -XX X.X - X.X XX- X..X..XXX " + "X...X - X.X - X.X - XX X.X XX - X.X - X.X - X..X..X..XX " + "X....X - X.X - X.X - X.X X.X X.X - X.X - X.X - X..X..X..X.X " + "X.....X - X.X - X.X - X..X X.X X..X - X.X - X.X -XXX X..X..X..X..X" + "X......X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X XX-XX X.X -X..XX........X..X" + "X.......X - X.X - X.X -X.....................X- X.X X.X-X.X X.X -X...X...........X" + "X........X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X..X-X..X.X - X..............X" + "X.........X -XXX.XXX- X.X - X..X X.X X..X - X...X-X...X - X.............X" + "X..........X-X.....X- X.X - X.X X.X X.X - X....X-X....X - X.............X" + "X......XXXXX-XXXXXXX- X.X - XX X.X XX - X.....X-X.....X - X............X" + "X...X..X --------- X.X - X.X - XXXXXXX-XXXXXXX - X...........X " + "X..X X..X - -XXXX.XXXX- XXXX.XXXX ------------------------------------- X..........X " + "X.X X..X - -X.......X- X.......X - XX XX - - X..........X " + "XX X..X - - X.....X - X.....X - X.X X.X - - X........X " + " X..X - X...X - X...X - X..X X..X - - X........X " + " XX - X.X - X.X - X...XXXXXXXXXXXXX...X - - XXXXXXXXXX " + "------------ - X - X -X.....................X- ------------------" + " ----------------------------------- X...XXXXXXXXXXXXX...X - " + " - X..X X..X - " + " - X.X X.X - " + " - XX XX - " +}; + +static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3] = +{ + // Pos ........ Size ......... Offset ...... + { ImVec2( 0,3), ImVec2(12,19), ImVec2( 0, 0) }, // ImGuiMouseCursor_Arrow + { ImVec2(13,0), ImVec2( 7,16), ImVec2( 1, 8) }, // ImGuiMouseCursor_TextInput + { ImVec2(31,0), ImVec2(23,23), ImVec2(11,11) }, // ImGuiMouseCursor_ResizeAll + { ImVec2(21,0), ImVec2( 9,23), ImVec2( 4,11) }, // ImGuiMouseCursor_ResizeNS + { ImVec2(55,18),ImVec2(23, 9), ImVec2(11, 4) }, // ImGuiMouseCursor_ResizeEW + { ImVec2(73,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNESW + { ImVec2(55,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNWSE + { ImVec2(91,0), ImVec2(17,22), ImVec2( 5, 0) }, // ImGuiMouseCursor_Hand +}; + +ImFontAtlas::ImFontAtlas() +{ + memset(this, 0, sizeof(*this)); + TexGlyphPadding = 1; + PackIdMouseCursors = PackIdLines = -1; +} + +ImFontAtlas::~ImFontAtlas() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + Clear(); +} + +void ImFontAtlas::ClearInputData() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + for (int i = 0; i < ConfigData.Size; i++) + if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas) + { + IM_FREE(ConfigData[i].FontData); + ConfigData[i].FontData = NULL; + } + + // When clearing this we lose access to the font name and other information used to build the font. + for (int i = 0; i < Fonts.Size; i++) + if (Fonts[i]->ConfigData >= ConfigData.Data && Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size) + { + Fonts[i]->ConfigData = NULL; + Fonts[i]->ConfigDataCount = 0; + } + ConfigData.clear(); + CustomRects.clear(); + PackIdMouseCursors = PackIdLines = -1; +} + +void ImFontAtlas::ClearTexData() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + if (TexPixelsAlpha8) + IM_FREE(TexPixelsAlpha8); + if (TexPixelsRGBA32) + IM_FREE(TexPixelsRGBA32); + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; + TexPixelsUseColors = false; +} + +void ImFontAtlas::ClearFonts() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + for (int i = 0; i < Fonts.Size; i++) + IM_DELETE(Fonts[i]); + Fonts.clear(); +} + +void ImFontAtlas::Clear() +{ + ClearInputData(); + ClearTexData(); + ClearFonts(); +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Build atlas on demand + if (TexPixelsAlpha8 == NULL) + { + if (ConfigData.empty()) + AddFontDefault(); + Build(); + } + + *out_pixels = TexPixelsAlpha8; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Convert to RGBA32 format on demand + // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp + if (!TexPixelsRGBA32) + { + unsigned char* pixels = NULL; + GetTexDataAsAlpha8(&pixels, NULL, NULL); + if (pixels) + { + TexPixelsRGBA32 = (unsigned int*)IM_ALLOC((size_t)TexWidth * (size_t)TexHeight * 4); + const unsigned char* src = pixels; + unsigned int* dst = TexPixelsRGBA32; + for (int n = TexWidth * TexHeight; n > 0; n--) + *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + } + } + + *out_pixels = (unsigned char*)TexPixelsRGBA32; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; +} + +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); + IM_ASSERT(font_cfg->SizePixels > 0.0f); + + // Create new font + if (!font_cfg->MergeMode) + Fonts.push_back(IM_NEW(ImFont)); + else + IM_ASSERT(!Fonts.empty() && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + + ConfigData.push_back(*font_cfg); + ImFontConfig& new_font_cfg = ConfigData.back(); + if (new_font_cfg.DstFont == NULL) + new_font_cfg.DstFont = Fonts.back(); + if (!new_font_cfg.FontDataOwnedByAtlas) + { + new_font_cfg.FontData = IM_ALLOC(new_font_cfg.FontDataSize); + new_font_cfg.FontDataOwnedByAtlas = true; + memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + } + + if (new_font_cfg.DstFont->EllipsisChar == (ImWchar)-1) + new_font_cfg.DstFont->EllipsisChar = font_cfg->EllipsisChar; + + // Invalidate texture + ClearTexData(); + return new_font_cfg.DstFont; +} + +// Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) +static unsigned int stb_decompress_length(const unsigned char* input); +static unsigned int stb_decompress(unsigned char* output, const unsigned char* input, unsigned int length); +static const char* GetDefaultCompressedFontDataTTFBase85(); +static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } +static void Decode85(const unsigned char* src, unsigned char* dst) +{ + while (*src) + { + unsigned int tmp = Decode85Byte(src[0]) + 85 * (Decode85Byte(src[1]) + 85 * (Decode85Byte(src[2]) + 85 * (Decode85Byte(src[3]) + 85 * Decode85Byte(src[4])))); + dst[0] = ((tmp >> 0) & 0xFF); dst[1] = ((tmp >> 8) & 0xFF); dst[2] = ((tmp >> 16) & 0xFF); dst[3] = ((tmp >> 24) & 0xFF); // We can't assume little-endianness. + src += 5; + dst += 4; + } +} + +// Load embedded ProggyClean.ttf at size 13, disable oversampling +ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (!font_cfg_template) + { + font_cfg.OversampleH = font_cfg.OversampleV = 1; + font_cfg.PixelSnapH = true; + } + if (font_cfg.SizePixels <= 0.0f) + font_cfg.SizePixels = 13.0f * 1.0f; + if (font_cfg.Name[0] == '\0') + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf, %dpx", (int)font_cfg.SizePixels); + font_cfg.EllipsisChar = (ImWchar)0x0085; + font_cfg.GlyphOffset.y = 1.0f * IM_FLOOR(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units + + const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); + const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault(); + ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); + return font; +} + +ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + size_t data_size = 0; + void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); + if (!data) + { + IM_ASSERT_USER_ERROR(0, "Could not load font file!"); + return NULL; + } + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (font_cfg.Name[0] == '\0') + { + // Store a short copy of filename into into the font name for convenience + const char* p; + for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + } + return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges); +} + +// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). +ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontData = ttf_data; + font_cfg.FontDataSize = ttf_size; + font_cfg.SizePixels = size_pixels; + if (glyph_ranges) + font_cfg.GlyphRanges = glyph_ranges; + return AddFont(&font_cfg); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + const unsigned int buf_decompressed_size = stb_decompress_length((const unsigned char*)compressed_ttf_data); + unsigned char* buf_decompressed_data = (unsigned char*)IM_ALLOC(buf_decompressed_size); + stb_decompress(buf_decompressed_data, (const unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size); + + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontDataOwnedByAtlas = true; + return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) +{ + int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4; + void* compressed_ttf = IM_ALLOC((size_t)compressed_ttf_size); + Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf); + ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges); + IM_FREE(compressed_ttf); + return font; +} + +int ImFontAtlas::AddCustomRectRegular(int width, int height) +{ + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + ImFontAtlasCustomRect r; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +{ +#ifdef IMGUI_USE_WCHAR32 + IM_ASSERT(id <= IM_UNICODE_CODEPOINT_MAX); +#endif + IM_ASSERT(font != NULL); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + ImFontAtlasCustomRect r; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + r.GlyphID = id; + r.GlyphAdvanceX = advance_x; + r.GlyphOffset = offset; + r.Font = font; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const +{ + IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates + IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed + *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); + *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); +} + +bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) +{ + if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_COUNT) + return false; + if (Flags & ImFontAtlasFlags_NoMouseCursors) + return false; + + IM_ASSERT(PackIdMouseCursors != -1); + ImFontAtlasCustomRect* r = GetCustomRectByIndex(PackIdMouseCursors); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->X, (float)r->Y); + ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; + *out_size = size; + *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; + out_uv_border[0] = (pos) * TexUvScale; + out_uv_border[1] = (pos + size) * TexUvScale; + pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + out_uv_fill[0] = (pos) * TexUvScale; + out_uv_fill[1] = (pos + size) * TexUvScale; + return true; +} + +bool ImFontAtlas::Build() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + + // Select builder + // - Note that we do not reassign to atlas->FontBuilderIO, since it is likely to point to static data which + // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are + // using a hot-reloading scheme that messes up static data, store your own instance of ImFontBuilderIO somewhere + // and point to it instead of pointing directly to return value of the GetBuilderXXX functions. + const ImFontBuilderIO* builder_io = FontBuilderIO; + if (builder_io == NULL) + { +#ifdef IMGUI_ENABLE_FREETYPE + builder_io = ImGuiFreeType::GetBuilderForFreeType(); +#elif defined(IMGUI_ENABLE_STB_TRUETYPE) + builder_io = ImFontAtlasGetBuilderForStbTruetype(); +#else + IM_ASSERT(0); // Invalid Build function +#endif + } + + // Build + return builder_io->FontBuilder_Build(this); +} + +void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) +{ + for (unsigned int i = 0; i < 256; i++) + { + unsigned int value = (unsigned int)(i * in_brighten_factor); + out_table[i] = value > 255 ? 255 : (value & 0xFF); + } +} + +void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) +{ + unsigned char* data = pixels + x + y * stride; + for (int j = h; j > 0; j--, data += stride) + for (int i = 0; i < w; i++) + data[i] = table[data[i]]; +} + +#ifdef IMGUI_ENABLE_STB_TRUETYPE +// Temporary data for one source font (multiple source fonts can be merged into one destination ImFont) +// (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.) +struct ImFontBuildSrcData +{ + stbtt_fontinfo FontInfo; + stbtt_pack_range PackRange; // Hold the list of codepoints to pack (essentially points to Codepoints.Data) + stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position. + stbtt_packedchar* PackedChars; // Output glyphs + const ImWchar* SrcRanges; // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF) + int DstIndex; // Index into atlas->Fonts[] and dst_tmp_array[] + int GlyphsHighest; // Highest requested codepoint + int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font) + ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB) + ImVector GlyphsList; // Glyph codepoints list (flattened version of GlyphsMap) +}; + +// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont) +struct ImFontBuildDstData +{ + int SrcCount; // Number of source fonts targeting this destination font. + int GlyphsHighest; + int GlyphsCount; + ImBitVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font. +}; + +static void UnpackBitVectorToFlatIndexList(const ImBitVector* in, ImVector* out) +{ + IM_ASSERT(sizeof(in->Storage.Data[0]) == sizeof(int)); + const ImU32* it_begin = in->Storage.begin(); + const ImU32* it_end = in->Storage.end(); + for (const ImU32* it = it_begin; it < it_end; it++) + if (ImU32 entries_32 = *it) + for (ImU32 bit_n = 0; bit_n < 32; bit_n++) + if (entries_32 & ((ImU32)1 << bit_n)) + out->push_back((int)(((it - it_begin) << 5) + bit_n)); +} + +static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->ConfigData.Size > 0); + + ImFontAtlasBuildInit(atlas); + + // Clear atlas + atlas->TexID = (ImTextureID)NULL; + atlas->TexWidth = atlas->TexHeight = 0; + atlas->TexUvScale = ImVec2(0.0f, 0.0f); + atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); + atlas->ClearTexData(); + + // Temporary storage for building + ImVector src_tmp_array; + ImVector dst_tmp_array; + src_tmp_array.resize(atlas->ConfigData.Size); + dst_tmp_array.resize(atlas->Fonts.Size); + memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes()); + memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes()); + + // 1. Initialize font loading structure, check font data validity + for (int src_i = 0; src_i < atlas->ConfigData.Size; src_i++) + { + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + ImFontConfig& cfg = atlas->ConfigData[src_i]; + IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); + + // Find index from cfg.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices) + src_tmp.DstIndex = -1; + for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++) + if (cfg.DstFont == atlas->Fonts[output_i]) + src_tmp.DstIndex = output_i; + if (src_tmp.DstIndex == -1) + { + IM_ASSERT(src_tmp.DstIndex != -1); // cfg.DstFont not pointing within atlas->Fonts[] array? + return false; + } + // Initialize helper structure for font loading and verify that the TTF/OTF data is correct + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); + IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); + if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) + return false; + + // Measure highest codepoints + ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; + src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault(); + for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) + src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]); + dst_tmp.SrcCount++; + dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest); + } + + // 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs. + int total_glyphs_count = 0; + for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + { + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; + src_tmp.GlyphsSet.Create(src_tmp.GlyphsHighest + 1); + if (dst_tmp.GlyphsSet.Storage.empty()) + dst_tmp.GlyphsSet.Create(dst_tmp.GlyphsHighest + 1); + + for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) + for (unsigned int codepoint = src_range[0]; codepoint <= src_range[1]; codepoint++) + { + if (dst_tmp.GlyphsSet.TestBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option for MergeMode (e.g. MergeOverwrite==true) + continue; + if (!stbtt_FindGlyphIndex(&src_tmp.FontInfo, codepoint)) // It is actually in the font? + continue; + + // Add to avail set/counters + src_tmp.GlyphsCount++; + dst_tmp.GlyphsCount++; + src_tmp.GlyphsSet.SetBit(codepoint); + dst_tmp.GlyphsSet.SetBit(codepoint); + total_glyphs_count++; + } + } + + // 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another) + for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + { + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount); + UnpackBitVectorToFlatIndexList(&src_tmp.GlyphsSet, &src_tmp.GlyphsList); + src_tmp.GlyphsSet.Clear(); + IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount); + } + for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++) + dst_tmp_array[dst_i].GlyphsSet.Clear(); + dst_tmp_array.clear(); + + // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) + // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity) + ImVector buf_rects; + ImVector buf_packedchars; + buf_rects.resize(total_glyphs_count); + buf_packedchars.resize(total_glyphs_count); + memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes()); + memset(buf_packedchars.Data, 0, (size_t)buf_packedchars.size_in_bytes()); + + // 4. Gather glyphs sizes so we can pack them in our virtual canvas. + int total_surface = 0; + int buf_rects_out_n = 0; + int buf_packedchars_out_n = 0; + for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + { + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + if (src_tmp.GlyphsCount == 0) + continue; + + src_tmp.Rects = &buf_rects[buf_rects_out_n]; + src_tmp.PackedChars = &buf_packedchars[buf_packedchars_out_n]; + buf_rects_out_n += src_tmp.GlyphsCount; + buf_packedchars_out_n += src_tmp.GlyphsCount; + + // Convert our ranges in the format stb_truetype wants + ImFontConfig& cfg = atlas->ConfigData[src_i]; + src_tmp.PackRange.font_size = cfg.SizePixels; + src_tmp.PackRange.first_unicode_codepoint_in_range = 0; + src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data; + src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size; + src_tmp.PackRange.chardata_for_range = src_tmp.PackedChars; + src_tmp.PackRange.h_oversample = (unsigned char)cfg.OversampleH; + src_tmp.PackRange.v_oversample = (unsigned char)cfg.OversampleV; + + // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) + const float scale = (cfg.SizePixels > 0) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels); + const int padding = atlas->TexGlyphPadding; + for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) + { + int x0, y0, x1, y1; + const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]); + IM_ASSERT(glyph_index_in_font != 0); + stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * cfg.OversampleH, scale * cfg.OversampleV, 0, 0, &x0, &y0, &x1, &y1); + src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + padding + cfg.OversampleH - 1); + src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + padding + cfg.OversampleV - 1); + total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; + } + } + + // We need a width for the skyline algorithm, any width! + // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. + // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface. + const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1; + atlas->TexHeight = 0; + if (atlas->TexDesiredWidth > 0) + atlas->TexWidth = atlas->TexDesiredWidth; + else + atlas->TexWidth = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512; + + // 5. Start packing + // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). + const int TEX_HEIGHT_MAX = 1024 * 32; + stbtt_pack_context spc = {}; + stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, atlas->TexGlyphPadding, NULL); + ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); + + // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. + for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + { + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + if (src_tmp.GlyphsCount == 0) + continue; + + stbrp_pack_rects((stbrp_context*)spc.pack_info, src_tmp.Rects, src_tmp.GlyphsCount); + + // Extend texture height and mark missing glyphs as non-packed so we won't render them. + // FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?) + for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) + if (src_tmp.Rects[glyph_i].was_packed) + atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h); + } + + // 7. Allocate texture + atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); + atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); + atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight); + memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); + spc.pixels = atlas->TexPixelsAlpha8; + spc.height = atlas->TexHeight; + + // 8. Render/rasterize font characters into the texture + for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + { + ImFontConfig& cfg = atlas->ConfigData[src_i]; + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + if (src_tmp.GlyphsCount == 0) + continue; + + stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects); + + // Apply multiply operator + if (cfg.RasterizerMultiply != 1.0f) + { + unsigned char multiply_table[256]; + ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); + stbrp_rect* r = &src_tmp.Rects[0]; + for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++, r++) + if (r->was_packed) + ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, atlas->TexPixelsAlpha8, r->x, r->y, r->w, r->h, atlas->TexWidth * 1); + } + src_tmp.Rects = NULL; + } + + // End packing + stbtt_PackEnd(&spc); + buf_rects.clear(); + + // 9. Setup ImFont and glyphs for runtime + for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + { + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + if (src_tmp.GlyphsCount == 0) + continue; + + // When merging fonts with MergeMode=true: + // - We can have multiple input fonts writing into a same destination font. + // - dst_font->ConfigData is != from cfg which is our source configuration. + ImFontConfig& cfg = atlas->ConfigData[src_i]; + ImFont* dst_font = cfg.DstFont; + + const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels); + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + + const float ascent = ImFloor(unscaled_ascent * font_scale + ((unscaled_ascent > 0.0f) ? +1 : -1)); + const float descent = ImFloor(unscaled_descent * font_scale + ((unscaled_descent > 0.0f) ? +1 : -1)); + ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); + const float font_off_x = cfg.GlyphOffset.x; + const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent); + + for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) + { + // Register glyph + const int codepoint = src_tmp.GlyphsList[glyph_i]; + const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i]; + stbtt_aligned_quad q; + float unused_x = 0.0f, unused_y = 0.0f; + stbtt_GetPackedQuad(src_tmp.PackedChars, atlas->TexWidth, atlas->TexHeight, glyph_i, &unused_x, &unused_y, &q, 0); + dst_font->AddGlyph(&cfg, (ImWchar)codepoint, q.x0 + font_off_x, q.y0 + font_off_y, q.x1 + font_off_x, q.y1 + font_off_y, q.s0, q.t0, q.s1, q.t1, pc.xadvance); + } + } + + // Cleanup temporary (ImVector doesn't honor destructor) + for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + src_tmp_array[src_i].~ImFontBuildSrcData(); + + ImFontAtlasBuildFinish(atlas); + return true; +} + +const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype() +{ + static ImFontBuilderIO io; + io.FontBuilder_Build = ImFontAtlasBuildWithStbTruetype; + return &io; +} + +#endif // IMGUI_ENABLE_STB_TRUETYPE + +void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) +{ + if (!font_config->MergeMode) + { + font->ClearOutputData(); + font->FontSize = font_config->SizePixels; + font->ConfigData = font_config; + font->ConfigDataCount = 0; + font->ContainerAtlas = atlas; + font->Ascent = ascent; + font->Descent = descent; + } + font->ConfigDataCount++; +} + +void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque) +{ + stbrp_context* pack_context = (stbrp_context*)stbrp_context_opaque; + IM_ASSERT(pack_context != NULL); + + ImVector& user_rects = atlas->CustomRects; + IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. + + ImVector pack_rects; + pack_rects.resize(user_rects.Size); + memset(pack_rects.Data, 0, (size_t)pack_rects.size_in_bytes()); + for (int i = 0; i < user_rects.Size; i++) + { + pack_rects[i].w = user_rects[i].Width; + pack_rects[i].h = user_rects[i].Height; + } + stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); + for (int i = 0; i < pack_rects.Size; i++) + if (pack_rects[i].was_packed) + { + user_rects[i].X = pack_rects[i].x; + user_rects[i].Y = pack_rects[i].y; + IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + } +} + +void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value) +{ + IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); + IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); + unsigned char* out_pixel = atlas->TexPixelsAlpha8 + x + (y * atlas->TexWidth); + for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : 0x00; +} + +void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value) +{ + IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); + IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); + unsigned int* out_pixel = atlas->TexPixelsRGBA32 + x + (y * atlas->TexWidth); + for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : IM_COL32_BLACK_TRANS; +} + +static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) +{ + ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); + IM_ASSERT(r->IsPacked()); + + const int w = atlas->TexWidth; + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + { + // Render/copy pixels + IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); + const int x_for_white = r->X; + const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + if (atlas->TexPixelsAlpha8 != NULL) + { + ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', 0xFF); + ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', 0xFF); + } + else + { + ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', IM_COL32_WHITE); + ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE); + } + } + else + { + // Render 4 white pixels + IM_ASSERT(r->Width == 2 && r->Height == 2); + const int offset = (int)r->X + (int)r->Y * w; + if (atlas->TexPixelsAlpha8 != NULL) + { + atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + } + else + { + atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; + } + } + atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); +} + +static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) +{ + if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) + return; + + // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them + ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines); + IM_ASSERT(r->IsPacked()); + for (unsigned int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row + { + // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + unsigned int y = n; + unsigned int line_width = n; + unsigned int pad_left = (r->Width - line_width) / 2; + unsigned int pad_right = r->Width - (pad_left + line_width); + + // Write each slice + IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels + if (atlas->TexPixelsAlpha8 != NULL) + { + unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; + for (unsigned int i = 0; i < pad_left; i++) + *(write_ptr + i) = 0x00; + + for (unsigned int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = 0xFF; + + for (unsigned int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = 0x00; + } + else + { + unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; + for (unsigned int i = 0; i < pad_left; i++) + *(write_ptr + i) = IM_COL32_BLACK_TRANS; + + for (unsigned int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = IM_COL32_WHITE; + + for (unsigned int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = IM_COL32_BLACK_TRANS; + } + + // Calculate UVs for this line + ImVec2 uv0 = ImVec2((float)(r->X + pad_left - 1), (float)(r->Y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r->X + pad_left + line_width + 1), (float)(r->Y + y + 1)) * atlas->TexUvScale; + float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts + atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); + } +} + +// Note: this is called / shared by both the stb_truetype and the FreeType builder +void ImFontAtlasBuildInit(ImFontAtlas* atlas) +{ + // Register texture region for mouse cursors or standard white pixels + if (atlas->PackIdMouseCursors < 0) + { + if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + else + atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(2, 2); + } + + // Register texture region for thick lines + // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row + if (atlas->PackIdLines < 0) + { + if (!(atlas->Flags & ImFontAtlasFlags_NoBakedLines)) + atlas->PackIdLines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + } +} + +// This is called/shared by both the stb_truetype and the FreeType builder. +void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +{ + // Render into our custom data blocks + IM_ASSERT(atlas->TexPixelsAlpha8 != NULL || atlas->TexPixelsRGBA32 != NULL); + ImFontAtlasBuildRenderDefaultTexData(atlas); + ImFontAtlasBuildRenderLinesTexData(atlas); + + // Register custom rectangle glyphs + for (int i = 0; i < atlas->CustomRects.Size; i++) + { + const ImFontAtlasCustomRect* r = &atlas->CustomRects[i]; + if (r->Font == NULL || r->GlyphID == 0) + continue; + + // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, GlyphExtraSpacing, PixelSnapH + IM_ASSERT(r->Font->ContainerAtlas == atlas); + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(r, &uv0, &uv1); + r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); + } + + // Build all fonts lookup tables + for (int i = 0; i < atlas->Fonts.Size; i++) + if (atlas->Fonts[i]->DirtyLookupTables) + atlas->Fonts[i]->BuildLookupTable(); + + // Ellipsis character is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). + // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. + // FIXME: Also note that 0x2026 is currently seldom included in our font ranges. Because of this we are more likely to use three individual dots. + for (int i = 0; i < atlas->Fonts.size(); i++) + { + ImFont* font = atlas->Fonts[i]; + if (font->EllipsisChar != (ImWchar)-1) + continue; + const ImWchar ellipsis_variants[] = { (ImWchar)0x2026, (ImWchar)0x0085 }; + for (int j = 0; j < IM_ARRAYSIZE(ellipsis_variants); j++) + if (font->FindGlyphNoFallback(ellipsis_variants[j]) != NULL) // Verify glyph exists + { + font->EllipsisChar = ellipsis_variants[j]; + break; + } + } +} + +// Retrieve list of range (2 int per range, values are inclusive) +const ImWchar* ImFontAtlas::GetGlyphRangesDefault() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesKorean() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3131, 0x3163, // Korean alphabets + 0xAC00, 0xD7A3, // Korean characters + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesChineseFull() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x2000, 0x206F, // General Punctuation + 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + 0x4e00, 0x9FAF, // CJK Ideograms + 0, + }; + return &ranges[0]; +} + +static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* accumulative_offsets, int accumulative_offsets_count, ImWchar* out_ranges) +{ + for (int n = 0; n < accumulative_offsets_count; n++, out_ranges += 2) + { + out_ranges[0] = out_ranges[1] = (ImWchar)(base_codepoint + accumulative_offsets[n]); + base_codepoint += accumulative_offsets[n]; + } + out_ranges[0] = 0; +} + +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas glyph ranges helpers +//------------------------------------------------------------------------- + +const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() +{ + // Store 2500 regularly used characters for Simplified Chinese. + // Sourced from https://zh.wiktionary.org/wiki/%E9%99%84%E5%BD%95:%E7%8E%B0%E4%BB%A3%E6%B1%89%E8%AF%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8 + // This table covers 97.97% of all characters used during the month in July, 1987. + // You can use ImFontGlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters. + // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) + static const short accumulative_offsets_from_0x4E00[] = + { + 0,1,2,4,1,1,1,1,2,1,3,2,1,2,2,1,1,1,1,1,5,2,1,2,3,3,3,2,2,4,1,1,1,2,1,5,2,3,1,2,1,2,1,1,2,1,1,2,2,1,4,1,1,1,1,5,10,1,2,19,2,1,2,1,2,1,2,1,2, + 1,5,1,6,3,2,1,2,2,1,1,1,4,8,5,1,1,4,1,1,3,1,2,1,5,1,2,1,1,1,10,1,1,5,2,4,6,1,4,2,2,2,12,2,1,1,6,1,1,1,4,1,1,4,6,5,1,4,2,2,4,10,7,1,1,4,2,4, + 2,1,4,3,6,10,12,5,7,2,14,2,9,1,1,6,7,10,4,7,13,1,5,4,8,4,1,1,2,28,5,6,1,1,5,2,5,20,2,2,9,8,11,2,9,17,1,8,6,8,27,4,6,9,20,11,27,6,68,2,2,1,1, + 1,2,1,2,2,7,6,11,3,3,1,1,3,1,2,1,1,1,1,1,3,1,1,8,3,4,1,5,7,2,1,4,4,8,4,2,1,2,1,1,4,5,6,3,6,2,12,3,1,3,9,2,4,3,4,1,5,3,3,1,3,7,1,5,1,1,1,1,2, + 3,4,5,2,3,2,6,1,1,2,1,7,1,7,3,4,5,15,2,2,1,5,3,22,19,2,1,1,1,1,2,5,1,1,1,6,1,1,12,8,2,9,18,22,4,1,1,5,1,16,1,2,7,10,15,1,1,6,2,4,1,2,4,1,6, + 1,1,3,2,4,1,6,4,5,1,2,1,1,2,1,10,3,1,3,2,1,9,3,2,5,7,2,19,4,3,6,1,1,1,1,1,4,3,2,1,1,1,2,5,3,1,1,1,2,2,1,1,2,1,1,2,1,3,1,1,1,3,7,1,4,1,1,2,1, + 1,2,1,2,4,4,3,8,1,1,1,2,1,3,5,1,3,1,3,4,6,2,2,14,4,6,6,11,9,1,15,3,1,28,5,2,5,5,3,1,3,4,5,4,6,14,3,2,3,5,21,2,7,20,10,1,2,19,2,4,28,28,2,3, + 2,1,14,4,1,26,28,42,12,40,3,52,79,5,14,17,3,2,2,11,3,4,6,3,1,8,2,23,4,5,8,10,4,2,7,3,5,1,1,6,3,1,2,2,2,5,28,1,1,7,7,20,5,3,29,3,17,26,1,8,4, + 27,3,6,11,23,5,3,4,6,13,24,16,6,5,10,25,35,7,3,2,3,3,14,3,6,2,6,1,4,2,3,8,2,1,1,3,3,3,4,1,1,13,2,2,4,5,2,1,14,14,1,2,2,1,4,5,2,3,1,14,3,12, + 3,17,2,16,5,1,2,1,8,9,3,19,4,2,2,4,17,25,21,20,28,75,1,10,29,103,4,1,2,1,1,4,2,4,1,2,3,24,2,2,2,1,1,2,1,3,8,1,1,1,2,1,1,3,1,1,1,6,1,5,3,1,1, + 1,3,4,1,1,5,2,1,5,6,13,9,16,1,1,1,1,3,2,3,2,4,5,2,5,2,2,3,7,13,7,2,2,1,1,1,1,2,3,3,2,1,6,4,9,2,1,14,2,14,2,1,18,3,4,14,4,11,41,15,23,15,23, + 176,1,3,4,1,1,1,1,5,3,1,2,3,7,3,1,1,2,1,2,4,4,6,2,4,1,9,7,1,10,5,8,16,29,1,1,2,2,3,1,3,5,2,4,5,4,1,1,2,2,3,3,7,1,6,10,1,17,1,44,4,6,2,1,1,6, + 5,4,2,10,1,6,9,2,8,1,24,1,2,13,7,8,8,2,1,4,1,3,1,3,3,5,2,5,10,9,4,9,12,2,1,6,1,10,1,1,7,7,4,10,8,3,1,13,4,3,1,6,1,3,5,2,1,2,17,16,5,2,16,6, + 1,4,2,1,3,3,6,8,5,11,11,1,3,3,2,4,6,10,9,5,7,4,7,4,7,1,1,4,2,1,3,6,8,7,1,6,11,5,5,3,24,9,4,2,7,13,5,1,8,82,16,61,1,1,1,4,2,2,16,10,3,8,1,1, + 6,4,2,1,3,1,1,1,4,3,8,4,2,2,1,1,1,1,1,6,3,5,1,1,4,6,9,2,1,1,1,2,1,7,2,1,6,1,5,4,4,3,1,8,1,3,3,1,3,2,2,2,2,3,1,6,1,2,1,2,1,3,7,1,8,2,1,2,1,5, + 2,5,3,5,10,1,2,1,1,3,2,5,11,3,9,3,5,1,1,5,9,1,2,1,5,7,9,9,8,1,3,3,3,6,8,2,3,2,1,1,32,6,1,2,15,9,3,7,13,1,3,10,13,2,14,1,13,10,2,1,3,10,4,15, + 2,15,15,10,1,3,9,6,9,32,25,26,47,7,3,2,3,1,6,3,4,3,2,8,5,4,1,9,4,2,2,19,10,6,2,3,8,1,2,2,4,2,1,9,4,4,4,6,4,8,9,2,3,1,1,1,1,3,5,5,1,3,8,4,6, + 2,1,4,12,1,5,3,7,13,2,5,8,1,6,1,2,5,14,6,1,5,2,4,8,15,5,1,23,6,62,2,10,1,1,8,1,2,2,10,4,2,2,9,2,1,1,3,2,3,1,5,3,3,2,1,3,8,1,1,1,11,3,1,1,4, + 3,7,1,14,1,2,3,12,5,2,5,1,6,7,5,7,14,11,1,3,1,8,9,12,2,1,11,8,4,4,2,6,10,9,13,1,1,3,1,5,1,3,2,4,4,1,18,2,3,14,11,4,29,4,2,7,1,3,13,9,2,2,5, + 3,5,20,7,16,8,5,72,34,6,4,22,12,12,28,45,36,9,7,39,9,191,1,1,1,4,11,8,4,9,2,3,22,1,1,1,1,4,17,1,7,7,1,11,31,10,2,4,8,2,3,2,1,4,2,16,4,32,2, + 3,19,13,4,9,1,5,2,14,8,1,1,3,6,19,6,5,1,16,6,2,10,8,5,1,2,3,1,5,5,1,11,6,6,1,3,3,2,6,3,8,1,1,4,10,7,5,7,7,5,8,9,2,1,3,4,1,1,3,1,3,3,2,6,16, + 1,4,6,3,1,10,6,1,3,15,2,9,2,10,25,13,9,16,6,2,2,10,11,4,3,9,1,2,6,6,5,4,30,40,1,10,7,12,14,33,6,3,6,7,3,1,3,1,11,14,4,9,5,12,11,49,18,51,31, + 140,31,2,2,1,5,1,8,1,10,1,4,4,3,24,1,10,1,3,6,6,16,3,4,5,2,1,4,2,57,10,6,22,2,22,3,7,22,6,10,11,36,18,16,33,36,2,5,5,1,1,1,4,10,1,4,13,2,7, + 5,2,9,3,4,1,7,43,3,7,3,9,14,7,9,1,11,1,1,3,7,4,18,13,1,14,1,3,6,10,73,2,2,30,6,1,11,18,19,13,22,3,46,42,37,89,7,3,16,34,2,2,3,9,1,7,1,1,1,2, + 2,4,10,7,3,10,3,9,5,28,9,2,6,13,7,3,1,3,10,2,7,2,11,3,6,21,54,85,2,1,4,2,2,1,39,3,21,2,2,5,1,1,1,4,1,1,3,4,15,1,3,2,4,4,2,3,8,2,20,1,8,7,13, + 4,1,26,6,2,9,34,4,21,52,10,4,4,1,5,12,2,11,1,7,2,30,12,44,2,30,1,1,3,6,16,9,17,39,82,2,2,24,7,1,7,3,16,9,14,44,2,1,2,1,2,3,5,2,4,1,6,7,5,3, + 2,6,1,11,5,11,2,1,18,19,8,1,3,24,29,2,1,3,5,2,2,1,13,6,5,1,46,11,3,5,1,1,5,8,2,10,6,12,6,3,7,11,2,4,16,13,2,5,1,1,2,2,5,2,28,5,2,23,10,8,4, + 4,22,39,95,38,8,14,9,5,1,13,5,4,3,13,12,11,1,9,1,27,37,2,5,4,4,63,211,95,2,2,2,1,3,5,2,1,1,2,2,1,1,1,3,2,4,1,2,1,1,5,2,2,1,1,2,3,1,3,1,1,1, + 3,1,4,2,1,3,6,1,1,3,7,15,5,3,2,5,3,9,11,4,2,22,1,6,3,8,7,1,4,28,4,16,3,3,25,4,4,27,27,1,4,1,2,2,7,1,3,5,2,28,8,2,14,1,8,6,16,25,3,3,3,14,3, + 3,1,1,2,1,4,6,3,8,4,1,1,1,2,3,6,10,6,2,3,18,3,2,5,5,4,3,1,5,2,5,4,23,7,6,12,6,4,17,11,9,5,1,1,10,5,12,1,1,11,26,33,7,3,6,1,17,7,1,5,12,1,11, + 2,4,1,8,14,17,23,1,2,1,7,8,16,11,9,6,5,2,6,4,16,2,8,14,1,11,8,9,1,1,1,9,25,4,11,19,7,2,15,2,12,8,52,7,5,19,2,16,4,36,8,1,16,8,24,26,4,6,2,9, + 5,4,36,3,28,12,25,15,37,27,17,12,59,38,5,32,127,1,2,9,17,14,4,1,2,1,1,8,11,50,4,14,2,19,16,4,17,5,4,5,26,12,45,2,23,45,104,30,12,8,3,10,2,2, + 3,3,1,4,20,7,2,9,6,15,2,20,1,3,16,4,11,15,6,134,2,5,59,1,2,2,2,1,9,17,3,26,137,10,211,59,1,2,4,1,4,1,1,1,2,6,2,3,1,1,2,3,2,3,1,3,4,4,2,3,3, + 1,4,3,1,7,2,2,3,1,2,1,3,3,3,2,2,3,2,1,3,14,6,1,3,2,9,6,15,27,9,34,145,1,1,2,1,1,1,1,2,1,1,1,1,2,2,2,3,1,2,1,1,1,2,3,5,8,3,5,2,4,1,3,2,2,2,12, + 4,1,1,1,10,4,5,1,20,4,16,1,15,9,5,12,2,9,2,5,4,2,26,19,7,1,26,4,30,12,15,42,1,6,8,172,1,1,4,2,1,1,11,2,2,4,2,1,2,1,10,8,1,2,1,4,5,1,2,5,1,8, + 4,1,3,4,2,1,6,2,1,3,4,1,2,1,1,1,1,12,5,7,2,4,3,1,1,1,3,3,6,1,2,2,3,3,3,2,1,2,12,14,11,6,6,4,12,2,8,1,7,10,1,35,7,4,13,15,4,3,23,21,28,52,5, + 26,5,6,1,7,10,2,7,53,3,2,1,1,1,2,163,532,1,10,11,1,3,3,4,8,2,8,6,2,2,23,22,4,2,2,4,2,1,3,1,3,3,5,9,8,2,1,2,8,1,10,2,12,21,20,15,105,2,3,1,1, + 3,2,3,1,1,2,5,1,4,15,11,19,1,1,1,1,5,4,5,1,1,2,5,3,5,12,1,2,5,1,11,1,1,15,9,1,4,5,3,26,8,2,1,3,1,1,15,19,2,12,1,2,5,2,7,2,19,2,20,6,26,7,5, + 2,2,7,34,21,13,70,2,128,1,1,2,1,1,2,1,1,3,2,2,2,15,1,4,1,3,4,42,10,6,1,49,85,8,1,2,1,1,4,4,2,3,6,1,5,7,4,3,211,4,1,2,1,2,5,1,2,4,2,2,6,5,6, + 10,3,4,48,100,6,2,16,296,5,27,387,2,2,3,7,16,8,5,38,15,39,21,9,10,3,7,59,13,27,21,47,5,21,6 + }; + static ImWchar base_ranges[] = // not zero-terminated + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x2000, 0x206F, // General Punctuation + 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF // Half-width characters + }; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00) * 2 + 1] = { 0 }; + if (!full_ranges[0]) + { + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges)); + } + return &full_ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() +{ + // 2999 ideograms code points for Japanese + // - 2136 Joyo (meaning "for regular use" or "for common use") Kanji code points + // - 863 Jinmeiyo (meaning "for personal name") Kanji code points + // - Sourced from the character information database of the Information-technology Promotion Agency, Japan + // - https://mojikiban.ipa.go.jp/mji/ + // - Available under the terms of the Creative Commons Attribution-ShareAlike 2.1 Japan (CC BY-SA 2.1 JP). + // - https://creativecommons.org/licenses/by-sa/2.1/jp/deed.en + // - https://creativecommons.org/licenses/by-sa/2.1/jp/legalcode + // - You can generate this code by the script at: + // - https://github.com/vaiorabbit/everyday_use_kanji + // - References: + // - List of Joyo Kanji + // - (Official list by the Agency for Cultural Affairs) https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kakuki/14/tosin02/index.html + // - (Wikipedia) https://en.wikipedia.org/wiki/List_of_j%C5%8Dy%C5%8D_kanji + // - List of Jinmeiyo Kanji + // - (Official list by the Ministry of Justice) http://www.moj.go.jp/MINJI/minji86.html + // - (Wikipedia) https://en.wikipedia.org/wiki/Jinmeiy%C5%8D_kanji + // - Missing 1 Joyo Kanji: U+20B9F (Kun'yomi: Shikaru, On'yomi: Shitsu,shichi), see https://github.com/ocornut/imgui/pull/3627 for details. + // You can use ImFontGlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters. + // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) + static const short accumulative_offsets_from_0x4E00[] = + { + 0,1,2,4,1,1,1,1,2,1,3,3,2,2,1,5,3,5,7,5,6,1,2,1,7,2,6,3,1,8,1,1,4,1,1,18,2,11,2,6,2,1,2,1,5,1,2,1,3,1,2,1,2,3,3,1,1,2,3,1,1,1,12,7,9,1,4,5,1, + 1,2,1,10,1,1,9,2,2,4,5,6,9,3,1,1,1,1,9,3,18,5,2,2,2,2,1,6,3,7,1,1,1,1,2,2,4,2,1,23,2,10,4,3,5,2,4,10,2,4,13,1,6,1,9,3,1,1,6,6,7,6,3,1,2,11,3, + 2,2,3,2,15,2,2,5,4,3,6,4,1,2,5,2,12,16,6,13,9,13,2,1,1,7,16,4,7,1,19,1,5,1,2,2,7,7,8,2,6,5,4,9,18,7,4,5,9,13,11,8,15,2,1,1,1,2,1,2,2,1,2,2,8, + 2,9,3,3,1,1,4,4,1,1,1,4,9,1,4,3,5,5,2,7,5,3,4,8,2,1,13,2,3,3,1,14,1,1,4,5,1,3,6,1,5,2,1,1,3,3,3,3,1,1,2,7,6,6,7,1,4,7,6,1,1,1,1,1,12,3,3,9,5, + 2,6,1,5,6,1,2,3,18,2,4,14,4,1,3,6,1,1,6,3,5,5,3,2,2,2,2,12,3,1,4,2,3,2,3,11,1,7,4,1,2,1,3,17,1,9,1,24,1,1,4,2,2,4,1,2,7,1,1,1,3,1,2,2,4,15,1, + 1,2,1,1,2,1,5,2,5,20,2,5,9,1,10,8,7,6,1,1,1,1,1,1,6,2,1,2,8,1,1,1,1,5,1,1,3,1,1,1,1,3,1,1,12,4,1,3,1,1,1,1,1,10,3,1,7,5,13,1,2,3,4,6,1,1,30, + 2,9,9,1,15,38,11,3,1,8,24,7,1,9,8,10,2,1,9,31,2,13,6,2,9,4,49,5,2,15,2,1,10,2,1,1,1,2,2,6,15,30,35,3,14,18,8,1,16,10,28,12,19,45,38,1,3,2,3, + 13,2,1,7,3,6,5,3,4,3,1,5,7,8,1,5,3,18,5,3,6,1,21,4,24,9,24,40,3,14,3,21,3,2,1,2,4,2,3,1,15,15,6,5,1,1,3,1,5,6,1,9,7,3,3,2,1,4,3,8,21,5,16,4, + 5,2,10,11,11,3,6,3,2,9,3,6,13,1,2,1,1,1,1,11,12,6,6,1,4,2,6,5,2,1,1,3,3,6,13,3,1,1,5,1,2,3,3,14,2,1,2,2,2,5,1,9,5,1,1,6,12,3,12,3,4,13,2,14, + 2,8,1,17,5,1,16,4,2,2,21,8,9,6,23,20,12,25,19,9,38,8,3,21,40,25,33,13,4,3,1,4,1,2,4,1,2,5,26,2,1,1,2,1,3,6,2,1,1,1,1,1,1,2,3,1,1,1,9,2,3,1,1, + 1,3,6,3,2,1,1,6,6,1,8,2,2,2,1,4,1,2,3,2,7,3,2,4,1,2,1,2,2,1,1,1,1,1,3,1,2,5,4,10,9,4,9,1,1,1,1,1,1,5,3,2,1,6,4,9,6,1,10,2,31,17,8,3,7,5,40,1, + 7,7,1,6,5,2,10,7,8,4,15,39,25,6,28,47,18,10,7,1,3,1,1,2,1,1,1,3,3,3,1,1,1,3,4,2,1,4,1,3,6,10,7,8,6,2,2,1,3,3,2,5,8,7,9,12,2,15,1,1,4,1,2,1,1, + 1,3,2,1,3,3,5,6,2,3,2,10,1,4,2,8,1,1,1,11,6,1,21,4,16,3,1,3,1,4,2,3,6,5,1,3,1,1,3,3,4,6,1,1,10,4,2,7,10,4,7,4,2,9,4,3,1,1,1,4,1,8,3,4,1,3,1, + 6,1,4,2,1,4,7,2,1,8,1,4,5,1,1,2,2,4,6,2,7,1,10,1,1,3,4,11,10,8,21,4,6,1,3,5,2,1,2,28,5,5,2,3,13,1,2,3,1,4,2,1,5,20,3,8,11,1,3,3,3,1,8,10,9,2, + 10,9,2,3,1,1,2,4,1,8,3,6,1,7,8,6,11,1,4,29,8,4,3,1,2,7,13,1,4,1,6,2,6,12,12,2,20,3,2,3,6,4,8,9,2,7,34,5,1,18,6,1,1,4,4,5,7,9,1,2,2,4,3,4,1,7, + 2,2,2,6,2,3,25,5,3,6,1,4,6,7,4,2,1,4,2,13,6,4,4,3,1,5,3,4,4,3,2,1,1,4,1,2,1,1,3,1,11,1,6,3,1,7,3,6,2,8,8,6,9,3,4,11,3,2,10,12,2,5,11,1,6,4,5, + 3,1,8,5,4,6,6,3,5,1,1,3,2,1,2,2,6,17,12,1,10,1,6,12,1,6,6,19,9,6,16,1,13,4,4,15,7,17,6,11,9,15,12,6,7,2,1,2,2,15,9,3,21,4,6,49,18,7,3,2,3,1, + 6,8,2,2,6,2,9,1,3,6,4,4,1,2,16,2,5,2,1,6,2,3,5,3,1,2,5,1,2,1,9,3,1,8,6,4,8,11,3,1,1,1,1,3,1,13,8,4,1,3,2,2,1,4,1,11,1,5,2,1,5,2,5,8,6,1,1,7, + 4,3,8,3,2,7,2,1,5,1,5,2,4,7,6,2,8,5,1,11,4,5,3,6,18,1,2,13,3,3,1,21,1,1,4,1,4,1,1,1,8,1,2,2,7,1,2,4,2,2,9,2,1,1,1,4,3,6,3,12,5,1,1,1,5,6,3,2, + 4,8,2,2,4,2,7,1,8,9,5,2,3,2,1,3,2,13,7,14,6,5,1,1,2,1,4,2,23,2,1,1,6,3,1,4,1,15,3,1,7,3,9,14,1,3,1,4,1,1,5,8,1,3,8,3,8,15,11,4,14,4,4,2,5,5, + 1,7,1,6,14,7,7,8,5,15,4,8,6,5,6,2,1,13,1,20,15,11,9,2,5,6,2,11,2,6,2,5,1,5,8,4,13,19,25,4,1,1,11,1,34,2,5,9,14,6,2,2,6,1,1,14,1,3,14,13,1,6, + 12,21,14,14,6,32,17,8,32,9,28,1,2,4,11,8,3,1,14,2,5,15,1,1,1,1,3,6,4,1,3,4,11,3,1,1,11,30,1,5,1,4,1,5,8,1,1,3,2,4,3,17,35,2,6,12,17,3,1,6,2, + 1,1,12,2,7,3,3,2,1,16,2,8,3,6,5,4,7,3,3,8,1,9,8,5,1,2,1,3,2,8,1,2,9,12,1,1,2,3,8,3,24,12,4,3,7,5,8,3,3,3,3,3,3,1,23,10,3,1,2,2,6,3,1,16,1,16, + 22,3,10,4,11,6,9,7,7,3,6,2,2,2,4,10,2,1,1,2,8,7,1,6,4,1,3,3,3,5,10,12,12,2,3,12,8,15,1,1,16,6,6,1,5,9,11,4,11,4,2,6,12,1,17,5,13,1,4,9,5,1,11, + 2,1,8,1,5,7,28,8,3,5,10,2,17,3,38,22,1,2,18,12,10,4,38,18,1,4,44,19,4,1,8,4,1,12,1,4,31,12,1,14,7,75,7,5,10,6,6,13,3,2,11,11,3,2,5,28,15,6,18, + 18,5,6,4,3,16,1,7,18,7,36,3,5,3,1,7,1,9,1,10,7,2,4,2,6,2,9,7,4,3,32,12,3,7,10,2,23,16,3,1,12,3,31,4,11,1,3,8,9,5,1,30,15,6,12,3,2,2,11,19,9, + 14,2,6,2,3,19,13,17,5,3,3,25,3,14,1,1,1,36,1,3,2,19,3,13,36,9,13,31,6,4,16,34,2,5,4,2,3,3,5,1,1,1,4,3,1,17,3,2,3,5,3,1,3,2,3,5,6,3,12,11,1,3, + 1,2,26,7,12,7,2,14,3,3,7,7,11,25,25,28,16,4,36,1,2,1,6,2,1,9,3,27,17,4,3,4,13,4,1,3,2,2,1,10,4,2,4,6,3,8,2,1,18,1,1,24,2,2,4,33,2,3,63,7,1,6, + 40,7,3,4,4,2,4,15,18,1,16,1,1,11,2,41,14,1,3,18,13,3,2,4,16,2,17,7,15,24,7,18,13,44,2,2,3,6,1,1,7,5,1,7,1,4,3,3,5,10,8,2,3,1,8,1,1,27,4,2,1, + 12,1,2,1,10,6,1,6,7,5,2,3,7,11,5,11,3,6,6,2,3,15,4,9,1,1,2,1,2,11,2,8,12,8,5,4,2,3,1,5,2,2,1,14,1,12,11,4,1,11,17,17,4,3,2,5,5,7,3,1,5,9,9,8, + 2,5,6,6,13,13,2,1,2,6,1,2,2,49,4,9,1,2,10,16,7,8,4,3,2,23,4,58,3,29,1,14,19,19,11,11,2,7,5,1,3,4,6,2,18,5,12,12,17,17,3,3,2,4,1,6,2,3,4,3,1, + 1,1,1,5,1,1,9,1,3,1,3,6,1,8,1,1,2,6,4,14,3,1,4,11,4,1,3,32,1,2,4,13,4,1,2,4,2,1,3,1,11,1,4,2,1,4,4,6,3,5,1,6,5,7,6,3,23,3,5,3,5,3,3,13,3,9,10, + 1,12,10,2,3,18,13,7,160,52,4,2,2,3,2,14,5,4,12,4,6,4,1,20,4,11,6,2,12,27,1,4,1,2,2,7,4,5,2,28,3,7,25,8,3,19,3,6,10,2,2,1,10,2,5,4,1,3,4,1,5, + 3,2,6,9,3,6,2,16,3,3,16,4,5,5,3,2,1,2,16,15,8,2,6,21,2,4,1,22,5,8,1,1,21,11,2,1,11,11,19,13,12,4,2,3,2,3,6,1,8,11,1,4,2,9,5,2,1,11,2,9,1,1,2, + 14,31,9,3,4,21,14,4,8,1,7,2,2,2,5,1,4,20,3,3,4,10,1,11,9,8,2,1,4,5,14,12,14,2,17,9,6,31,4,14,1,20,13,26,5,2,7,3,6,13,2,4,2,19,6,2,2,18,9,3,5, + 12,12,14,4,6,2,3,6,9,5,22,4,5,25,6,4,8,5,2,6,27,2,35,2,16,3,7,8,8,6,6,5,9,17,2,20,6,19,2,13,3,1,1,1,4,17,12,2,14,7,1,4,18,12,38,33,2,10,1,1, + 2,13,14,17,11,50,6,33,20,26,74,16,23,45,50,13,38,33,6,6,7,4,4,2,1,3,2,5,8,7,8,9,3,11,21,9,13,1,3,10,6,7,1,2,2,18,5,5,1,9,9,2,68,9,19,13,2,5, + 1,4,4,7,4,13,3,9,10,21,17,3,26,2,1,5,2,4,5,4,1,7,4,7,3,4,2,1,6,1,1,20,4,1,9,2,2,1,3,3,2,3,2,1,1,1,20,2,3,1,6,2,3,6,2,4,8,1,3,2,10,3,5,3,4,4, + 3,4,16,1,6,1,10,2,4,2,1,1,2,10,11,2,2,3,1,24,31,4,10,10,2,5,12,16,164,15,4,16,7,9,15,19,17,1,2,1,1,5,1,1,1,1,1,3,1,4,3,1,3,1,3,1,2,1,1,3,3,7, + 2,8,1,2,2,2,1,3,4,3,7,8,12,92,2,10,3,1,3,14,5,25,16,42,4,7,7,4,2,21,5,27,26,27,21,25,30,31,2,1,5,13,3,22,5,6,6,11,9,12,1,5,9,7,5,5,22,60,3,5, + 13,1,1,8,1,1,3,3,2,1,9,3,3,18,4,1,2,3,7,6,3,1,2,3,9,1,3,1,3,2,1,3,1,1,1,2,1,11,3,1,6,9,1,3,2,3,1,2,1,5,1,1,4,3,4,1,2,2,4,4,1,7,2,1,2,2,3,5,13, + 18,3,4,14,9,9,4,16,3,7,5,8,2,6,48,28,3,1,1,4,2,14,8,2,9,2,1,15,2,4,3,2,10,16,12,8,7,1,1,3,1,1,1,2,7,4,1,6,4,38,39,16,23,7,15,15,3,2,12,7,21, + 37,27,6,5,4,8,2,10,8,8,6,5,1,2,1,3,24,1,16,17,9,23,10,17,6,1,51,55,44,13,294,9,3,6,2,4,2,2,15,1,1,1,13,21,17,68,14,8,9,4,1,4,9,3,11,7,1,1,1, + 5,6,3,2,1,1,1,2,3,8,1,2,2,4,1,5,5,2,1,4,3,7,13,4,1,4,1,3,1,1,1,5,5,10,1,6,1,5,2,1,5,2,4,1,4,5,7,3,18,2,9,11,32,4,3,3,2,4,7,11,16,9,11,8,13,38, + 32,8,4,2,1,1,2,1,2,4,4,1,1,1,4,1,21,3,11,1,16,1,1,6,1,3,2,4,9,8,57,7,44,1,3,3,13,3,10,1,1,7,5,2,7,21,47,63,3,15,4,7,1,16,1,1,2,8,2,3,42,15,4, + 1,29,7,22,10,3,78,16,12,20,18,4,67,11,5,1,3,15,6,21,31,32,27,18,13,71,35,5,142,4,10,1,2,50,19,33,16,35,37,16,19,27,7,1,133,19,1,4,8,7,20,1,4, + 4,1,10,3,1,6,1,2,51,5,40,15,24,43,22928,11,1,13,154,70,3,1,1,7,4,10,1,2,1,1,2,1,2,1,2,2,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1, + 3,2,1,1,1,1,2,1,1, + }; + static ImWchar base_ranges[] = // not zero-terminated + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF // Half-width characters + }; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00)*2 + 1] = { 0 }; + if (!full_ranges[0]) + { + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges)); + } + return &full_ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesCyrillic() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0400, 0x052F, // Cyrillic + Cyrillic Supplement + 0x2DE0, 0x2DFF, // Cyrillic Extended-A + 0xA640, 0xA69F, // Cyrillic Extended-B + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesThai() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + 0x2010, 0x205E, // Punctuations + 0x0E00, 0x0E7F, // Thai + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesVietnamese() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + 0x0102, 0x0103, + 0x0110, 0x0111, + 0x0128, 0x0129, + 0x0168, 0x0169, + 0x01A0, 0x01A1, + 0x01AF, 0x01B0, + 0x1EA0, 0x1EF9, + 0, + }; + return &ranges[0]; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontGlyphRangesBuilder +//----------------------------------------------------------------------------- + +void ImFontGlyphRangesBuilder::AddText(const char* text, const char* text_end) +{ + while (text_end ? (text < text_end) : *text) + { + unsigned int c = 0; + int c_len = ImTextCharFromUtf8(&c, text, text_end); + text += c_len; + if (c_len == 0) + break; + AddChar((ImWchar)c); + } +} + +void ImFontGlyphRangesBuilder::AddRanges(const ImWchar* ranges) +{ + for (; ranges[0]; ranges += 2) + for (ImWchar c = ranges[0]; c <= ranges[1]; c++) + AddChar(c); +} + +void ImFontGlyphRangesBuilder::BuildRanges(ImVector* out_ranges) +{ + const int max_codepoint = IM_UNICODE_CODEPOINT_MAX; + for (int n = 0; n <= max_codepoint; n++) + if (GetBit(n)) + { + out_ranges->push_back((ImWchar)n); + while (n < max_codepoint && GetBit(n + 1)) + n++; + out_ranges->push_back((ImWchar)n); + } + out_ranges->push_back(0); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFont +//----------------------------------------------------------------------------- + +ImFont::ImFont() +{ + FontSize = 0.0f; + FallbackAdvanceX = 0.0f; + FallbackChar = (ImWchar)'?'; + EllipsisChar = (ImWchar)-1; + FallbackGlyph = NULL; + ContainerAtlas = NULL; + ConfigData = NULL; + ConfigDataCount = 0; + DirtyLookupTables = false; + Scale = 1.0f; + Ascent = Descent = 0.0f; + MetricsTotalSurface = 0; + memset(Used4kPagesMap, 0, sizeof(Used4kPagesMap)); +} + +ImFont::~ImFont() +{ + ClearOutputData(); +} + +void ImFont::ClearOutputData() +{ + FontSize = 0.0f; + FallbackAdvanceX = 0.0f; + Glyphs.clear(); + IndexAdvanceX.clear(); + IndexLookup.clear(); + FallbackGlyph = NULL; + ContainerAtlas = NULL; + DirtyLookupTables = true; + Ascent = Descent = 0.0f; + MetricsTotalSurface = 0; +} + +void ImFont::BuildLookupTable() +{ + int max_codepoint = 0; + for (int i = 0; i != Glyphs.Size; i++) + max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); + + // Build lookup table + IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved + IndexAdvanceX.clear(); + IndexLookup.clear(); + DirtyLookupTables = false; + memset(Used4kPagesMap, 0, sizeof(Used4kPagesMap)); + GrowIndex(max_codepoint + 1); + for (int i = 0; i < Glyphs.Size; i++) + { + int codepoint = (int)Glyphs[i].Codepoint; + IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; + IndexLookup[codepoint] = (ImWchar)i; + + // Mark 4K page as used + const int page_n = codepoint / 4096; + Used4kPagesMap[page_n >> 3] |= 1 << (page_n & 7); + } + + // Create a glyph to handle TAB + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (FindGlyph((ImWchar)' ')) + { + if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times (FIXME: Flaky) + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& tab_glyph = Glyphs.back(); + tab_glyph = *FindGlyph((ImWchar)' '); + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX *= IM_TABSIZE; + IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; + IndexLookup[(int)tab_glyph.Codepoint] = (ImWchar)(Glyphs.Size - 1); + } + + // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons) + SetGlyphVisible((ImWchar)' ', false); + SetGlyphVisible((ImWchar)'\t', false); + + // Setup fall-backs + FallbackGlyph = FindGlyphNoFallback(FallbackChar); + FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f; + for (int i = 0; i < max_codepoint + 1; i++) + if (IndexAdvanceX[i] < 0.0f) + IndexAdvanceX[i] = FallbackAdvanceX; +} + +// API is designed this way to avoid exposing the 4K page size +// e.g. use with IsGlyphRangeUnused(0, 255) +bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) +{ + unsigned int page_begin = (c_begin / 4096); + unsigned int page_last = (c_last / 4096); + for (unsigned int page_n = page_begin; page_n <= page_last; page_n++) + if ((page_n >> 3) < sizeof(Used4kPagesMap)) + if (Used4kPagesMap[page_n >> 3] & (1 << (page_n & 7))) + return false; + return true; +} + +void ImFont::SetGlyphVisible(ImWchar c, bool visible) +{ + if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)c)) + glyph->Visible = visible ? 1 : 0; +} + +void ImFont::SetFallbackChar(ImWchar c) +{ + FallbackChar = c; + BuildLookupTable(); +} + +void ImFont::GrowIndex(int new_size) +{ + IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); + if (new_size <= IndexLookup.Size) + return; + IndexAdvanceX.resize(new_size, -1.0f); + IndexLookup.resize(new_size, (ImWchar)-1); +} + +// x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. +// Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). +// 'cfg' is not necessarily == 'this->ConfigData' because multiple source fonts+configs can be used to build one target font. +void ImFont::AddGlyph(const ImFontConfig* cfg, ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +{ + if (cfg != NULL) + { + // Clamp & recenter if needed + const float advance_x_original = advance_x; + advance_x = ImClamp(advance_x, cfg->GlyphMinAdvanceX, cfg->GlyphMaxAdvanceX); + if (advance_x != advance_x_original) + { + float char_off_x = cfg->PixelSnapH ? ImFloor((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f; + x0 += char_off_x; + x1 += char_off_x; + } + + // Snap to pixel + if (cfg->PixelSnapH) + advance_x = IM_ROUND(advance_x); + + // Bake spacing + advance_x += cfg->GlyphExtraSpacing.x; + } + + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& glyph = Glyphs.back(); + glyph.Codepoint = (unsigned int)codepoint; + glyph.Visible = (x0 != x1) && (y0 != y1); + glyph.Colored = false; + glyph.X0 = x0; + glyph.Y0 = y0; + glyph.X1 = x1; + glyph.Y1 = y1; + glyph.U0 = u0; + glyph.V0 = v0; + glyph.U1 = u1; + glyph.V1 = v1; + glyph.AdvanceX = advance_x; + + // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) + // We use (U1-U0)*TexWidth instead of X1-X0 to account for oversampling. + float pad = ContainerAtlas->TexGlyphPadding + 0.99f; + DirtyLookupTables = true; + MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + pad) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + pad); +} + +void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) +{ + IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. + unsigned int index_size = (unsigned int)IndexLookup.Size; + + if (dst < index_size && IndexLookup.Data[dst] == (ImWchar)-1 && !overwrite_dst) // 'dst' already exists + return; + if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op + return; + + GrowIndex(dst + 1); + IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (ImWchar)-1; + IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; +} + +const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const +{ + if (c >= (size_t)IndexLookup.Size) + return FallbackGlyph; + const ImWchar i = IndexLookup.Data[c]; + if (i == (ImWchar)-1) + return FallbackGlyph; + return &Glyphs.Data[i]; +} + +const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const +{ + if (c >= (size_t)IndexLookup.Size) + return NULL; + const ImWchar i = IndexLookup.Data[c]; + if (i == (ImWchar)-1) + return NULL; + return &Glyphs.Data[i]; +} + +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +{ + // Simple word-wrapping for English, not full-featured. Please submit failing cases! + // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) + + // For references, possible wrap point marked with ^ + // "aaa bbb, ccc,ddd. eee fff. ggg!" + // ^ ^ ^ ^ ^__ ^ ^ + + // List of hardcoded separators: .,;!?'" + + // Skip extra blanks after a line returns (that includes not counting them in width computation) + // e.g. "Hello world" --> "Hello" "World" + + // Cut words that cannot possibly fit within one line. + // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + float line_width = 0.0f; + float word_width = 0.0f; + float blank_width = 0.0f; + wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters + + const char* word_end = text; + const char* prev_word_end = NULL; + bool inside_word = true; + + const char* s = text; + while (s < text_end) + { + unsigned int c = (unsigned int)*s; + const char* next_s; + if (c < 0x80) + next_s = s + 1; + else + next_s = s + ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) + break; + + if (c < 32) + { + if (c == '\n') + { + line_width = word_width = blank_width = 0.0f; + inside_word = true; + s = next_s; + continue; + } + if (c == '\r') + { + s = next_s; + continue; + } + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX); + if (ImCharIsBlankW(c)) + { + if (inside_word) + { + line_width += blank_width; + blank_width = 0.0f; + word_end = s; + } + blank_width += char_width; + inside_word = false; + } + else + { + word_width += char_width; + if (inside_word) + { + word_end = next_s; + } + else + { + prev_word_end = word_end; + line_width += word_width + blank_width; + word_width = blank_width = 0.0f; + } + + // Allow wrapping after punctuation. + inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"'); + } + + // We ignore blank width at the end of the line (they can be skipped) + if (line_width + word_width > wrap_width) + { + // Words that cannot possibly fit within an entire line will be cut anywhere. + if (word_width < wrap_width) + s = prev_word_end ? prev_word_end : word_end; + break; + } + + s = next_s; + } + + return s; +} + +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. + + const float line_height = size; + const float scale = size / FontSize; + + ImVec2 text_size = ImVec2(0, 0); + float line_width = 0.0f; + + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + const char* s = text_begin; + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + if (text_size.x < line_width) + text_size.x = line_width; + text_size.y += line_height; + line_width = 0.0f; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + const char* prev_s = s; + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + continue; + } + if (c == '\r') + continue; + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX) * scale; + if (line_width + char_width >= max_width) + { + s = prev_s; + break; + } + + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (line_width > 0 || text_size.y == 0.0f) + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +// Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. +void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, ImWchar c) const +{ + const ImFontGlyph* glyph = FindGlyph(c); + if (!glyph || !glyph->Visible) + return; + if (glyph->Colored) + col |= ~IM_COL32_A_MASK; + float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + pos.x = IM_FLOOR(pos.x); + pos.y = IM_FLOOR(pos.y); + draw_list->PrimReserve(6, 4); + draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); +} + +// Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. +void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. + + // Align to be pixel perfect + pos.x = IM_FLOOR(pos.x); + pos.y = IM_FLOOR(pos.y); + float x = pos.x; + float y = pos.y; + if (y > clip_rect.w) + return; + + const float scale = size / FontSize; + const float line_height = FontSize * scale; + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + // Fast-forward to first visible line + const char* s = text_begin; + if (y + line_height < clip_rect.y && !word_wrap_enabled) + while (y + line_height < clip_rect.y && s < text_end) + { + s = (const char*)memchr(s, '\n', text_end - s); + s = s ? s + 1 : text_end; + y += line_height; + } + + // For large text, scan for the last visible line in order to avoid over-reserving in the call to PrimReserve() + // Note that very large horizontal line will still be affected by the issue (e.g. a one megabyte string buffer without a newline will likely crash atm) + if (text_end - s > 10000 && !word_wrap_enabled) + { + const char* s_end = s; + float y_end = y; + while (y_end < clip_rect.w && s_end < text_end) + { + s_end = (const char*)memchr(s_end, '\n', text_end - s_end); + s_end = s_end ? s_end + 1 : text_end; + y_end += line_height; + } + text_end = s_end; + } + if (s == text_end) + return; + + // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) + const int vtx_count_max = (int)(text_end - s) * 4; + const int idx_count_max = (int)(text_end - s) * 6; + const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; + draw_list->PrimReserve(idx_count_max, vtx_count_max); + + ImDrawVert* vtx_write = draw_list->_VtxWritePtr; + ImDrawIdx* idx_write = draw_list->_IdxWritePtr; + unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; + + const ImU32 col_untinted = col | ~IM_COL32_A_MASK; + + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x)); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + x = pos.x; + y += line_height; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + x = pos.x; + y += line_height; + if (y > clip_rect.w) + break; // break out of main loop + continue; + } + if (c == '\r') + continue; + } + + const ImFontGlyph* glyph = FindGlyph((ImWchar)c); + if (glyph == NULL) + continue; + + float char_width = glyph->AdvanceX * scale; + if (glyph->Visible) + { + // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w + float x1 = x + glyph->X0 * scale; + float x2 = x + glyph->X1 * scale; + float y1 = y + glyph->Y0 * scale; + float y2 = y + glyph->Y1 * scale; + if (x1 <= clip_rect.z && x2 >= clip_rect.x) + { + // Render a character + float u1 = glyph->U0; + float v1 = glyph->V0; + float u2 = glyph->U1; + float v2 = glyph->V1; + + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. + if (cpu_fine_clip) + { + if (x1 < clip_rect.x) + { + u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); + x1 = clip_rect.x; + } + if (y1 < clip_rect.y) + { + v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); + y1 = clip_rect.y; + } + if (x2 > clip_rect.z) + { + u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); + x2 = clip_rect.z; + } + if (y2 > clip_rect.w) + { + v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); + y2 = clip_rect.w; + } + if (y1 >= y2) + { + x += char_width; + continue; + } + } + + // Support for untinted glyphs + ImU32 glyph_col = glyph->Colored ? col_untinted : col; + + // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: + { + idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); + idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); + vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = glyph_col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; + vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = glyph_col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1; + vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = glyph_col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2; + vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = glyph_col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2; + vtx_write += 4; + vtx_current_idx += 4; + idx_write += 6; + } + } + } + x += char_width; + } + + // Give back unused vertices (clipped ones, blanks) ~ this is essentially a PrimUnreserve() action. + draw_list->VtxBuffer.Size = (int)(vtx_write - draw_list->VtxBuffer.Data); // Same as calling shrink() + draw_list->IdxBuffer.Size = (int)(idx_write - draw_list->IdxBuffer.Data); + draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); + draw_list->_VtxWritePtr = vtx_write; + draw_list->_IdxWritePtr = idx_write; + draw_list->_VtxCurrentIdx = vtx_current_idx; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGui Internal Render Helpers +//----------------------------------------------------------------------------- +// Vaguely redesigned to stop accessing ImGui global state: +// - RenderArrow() +// - RenderBullet() +// - RenderCheckMark() +// - RenderMouseCursor() +// - RenderArrowPointingAt() +// - RenderRectFilledRangeH() +//----------------------------------------------------------------------------- +// Function in need of a redesign (legacy mess) +// - RenderColorRectWithAlphaCheckerboard() +//----------------------------------------------------------------------------- + +// Render an arrow aimed to be aligned with text (p_min is a position in the same space text would be positioned). To e.g. denote expanded/collapsed state +void ImGui::RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale) +{ + const float h = draw_list->_Data->FontSize * 1.00f; + float r = h * 0.40f * scale; + ImVec2 center = pos + ImVec2(h * 0.50f, h * 0.50f * scale); + + ImVec2 a, b, c; + switch (dir) + { + case ImGuiDir_Up: + case ImGuiDir_Down: + if (dir == ImGuiDir_Up) r = -r; + a = ImVec2(+0.000f, +0.750f) * r; + b = ImVec2(-0.866f, -0.750f) * r; + c = ImVec2(+0.866f, -0.750f) * r; + break; + case ImGuiDir_Left: + case ImGuiDir_Right: + if (dir == ImGuiDir_Left) r = -r; + a = ImVec2(+0.750f, +0.000f) * r; + b = ImVec2(-0.750f, +0.866f) * r; + c = ImVec2(-0.750f, -0.866f) * r; + break; + case ImGuiDir_None: + case ImGuiDir_COUNT: + IM_ASSERT(0); + break; + } + draw_list->AddTriangleFilled(center + a, center + b, center + c, col); +} + +void ImGui::RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col) +{ + draw_list->AddCircleFilled(pos, draw_list->_Data->FontSize * 0.20f, col, 8); +} + +void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz) +{ + float thickness = ImMax(sz / 5.0f, 1.0f); + sz -= thickness * 0.5f; + pos += ImVec2(thickness * 0.25f, thickness * 0.25f); + + float third = sz / 3.0f; + float bx = pos.x + third; + float by = pos.y + sz - third * 0.5f; + draw_list->PathLineTo(ImVec2(bx - third, by - third)); + draw_list->PathLineTo(ImVec2(bx, by)); + draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f)); + draw_list->PathStroke(col, 0, thickness); +} + +void ImGui::RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow) +{ + if (mouse_cursor == ImGuiMouseCursor_None) + return; + IM_ASSERT(mouse_cursor > ImGuiMouseCursor_None && mouse_cursor < ImGuiMouseCursor_COUNT); + + ImFontAtlas* font_atlas = draw_list->_Data->Font->ContainerAtlas; + ImVec2 offset, size, uv[4]; + if (font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) + { + pos -= offset; + ImTextureID tex_id = font_atlas->TexID; + draw_list->PushTextureID(tex_id); + draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_id, pos, pos + size * scale, uv[2], uv[3], col_border); + draw_list->AddImage(tex_id, pos, pos + size * scale, uv[0], uv[1], col_fill); + draw_list->PopTextureID(); + } +} + +// Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. +void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) +{ + switch (direction) + { + case ImGuiDir_Left: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_Up: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Down: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_None: case ImGuiDir_COUNT: break; // Fix warnings + } +} + +static inline float ImAcos01(float x) +{ + if (x <= 0.0f) return IM_PI * 0.5f; + if (x >= 1.0f) return 0.0f; + return ImAcos(x); + //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do. +} + +// FIXME: Cleanup and move code to ImDrawList. +void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) +{ + if (x_end_norm == x_start_norm) + return; + if (x_start_norm > x_end_norm) + ImSwap(x_start_norm, x_end_norm); + + ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); + ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); + if (rounding == 0.0f) + { + draw_list->AddRectFilled(p0, p1, col, 0.0f); + return; + } + + rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding); + const float inv_rounding = 1.0f / rounding; + const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding); + const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding); + const float half_pi = IM_PI * 0.5f; // We will == compare to this because we know this is the exact value ImAcos01 can return. + const float x0 = ImMax(p0.x, rect.Min.x + rounding); + if (arc0_b == arc0_e) + { + draw_list->PathLineTo(ImVec2(x0, p1.y)); + draw_list->PathLineTo(ImVec2(x0, p0.y)); + } + else if (arc0_b == 0.0f && arc0_e == half_pi) + { + draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL + draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR + } + else + { + draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL + draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR + } + if (p1.x > rect.Min.x + rounding) + { + const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding); + const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding); + const float x1 = ImMin(p1.x, rect.Max.x - rounding); + if (arc1_b == arc1_e) + { + draw_list->PathLineTo(ImVec2(x1, p0.y)); + draw_list->PathLineTo(ImVec2(x1, p1.y)); + } + else if (arc1_b == 0.0f && arc1_e == half_pi) + { + draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR + draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3); // BR + } + else + { + draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR + draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR + } + } + draw_list->PathFillConvex(col); +} + +void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, ImRect outer, ImRect inner, ImU32 col, float rounding) +{ + const bool fill_L = (inner.Min.x > outer.Min.x); + const bool fill_R = (inner.Max.x < outer.Max.x); + const bool fill_U = (inner.Min.y > outer.Min.y); + const bool fill_D = (inner.Max.y < outer.Max.y); + if (fill_L) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, (fill_U ? 0 : ImDrawFlags_RoundCornersTopLeft) | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomLeft)); + if (fill_R) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, (fill_U ? 0 : ImDrawFlags_RoundCornersTopRight) | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomRight)); + if (fill_U) draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, (fill_L ? 0 : ImDrawFlags_RoundCornersTopLeft) | (fill_R ? 0 : ImDrawFlags_RoundCornersTopRight)); + if (fill_D) draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, (fill_L ? 0 : ImDrawFlags_RoundCornersBottomLeft) | (fill_R ? 0 : ImDrawFlags_RoundCornersBottomRight)); + if (fill_L && fill_U) draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopLeft); + if (fill_R && fill_U) draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopRight); + if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomLeft); + if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomRight); +} + +// Helper for ColorPicker4() +// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. +// Spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding altogether. +// FIXME: uses ImGui::GetColorU32 +void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, ImDrawFlags flags) +{ + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags = ImDrawFlags_RoundCornersDefault_; + if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) + { + ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col)); + ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col)); + draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags); + + int yi = 0; + for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) + { + float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); + if (y2 <= y1) + continue; + for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) + { + float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); + if (x2 <= x1) + continue; + ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; + if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersTopRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersBottomRight; } + + // Combine flags + cell_flags = (flags == ImDrawFlags_RoundCornersNone || cell_flags == ImDrawFlags_RoundCornersNone) ? ImDrawFlags_RoundCornersNone : (cell_flags & flags); + draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding, cell_flags); + } + } + } + else + { + draw_list->AddRectFilled(p_min, p_max, col, rounding, flags); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Decompression code +//----------------------------------------------------------------------------- +// Compressed with stb_compress() then converted to a C array and encoded as base85. +// Use the program in misc/fonts/binary_to_compressed_c.cpp to create the array from a TTF file. +// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. +// Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h +//----------------------------------------------------------------------------- + +static unsigned int stb_decompress_length(const unsigned char *input) +{ + return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; +} + +static unsigned char *stb__barrier_out_e, *stb__barrier_out_b; +static const unsigned char *stb__barrier_in_b; +static unsigned char *stb__dout; +static void stb__match(const unsigned char *data, unsigned int length) +{ + // INVERSE of memmove... write each byte before copying the next... + IM_ASSERT(stb__dout + length <= stb__barrier_out_e); + if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; } + if (data < stb__barrier_out_b) { stb__dout = stb__barrier_out_e+1; return; } + while (length--) *stb__dout++ = *data++; +} + +static void stb__lit(const unsigned char *data, unsigned int length) +{ + IM_ASSERT(stb__dout + length <= stb__barrier_out_e); + if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; } + if (data < stb__barrier_in_b) { stb__dout = stb__barrier_out_e+1; return; } + memcpy(stb__dout, data, length); + stb__dout += length; +} + +#define stb__in2(x) ((i[x] << 8) + i[(x)+1]) +#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) +#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) + +static const unsigned char *stb_decompress_token(const unsigned char *i) +{ + if (*i >= 0x20) { // use fewer if's for cases that expand small + if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; + else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; + else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; + else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); + else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); + else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; + else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; + } + return i; +} + +static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen = buflen % 5552; + + unsigned long i; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + s1 += buffer[5], s2 += s1; + s1 += buffer[6], s2 += s1; + s1 += buffer[7], s2 += s1; + + buffer += 8; + } + + for (; i < blocklen; ++i) + s1 += *buffer++, s2 += s1; + + s1 %= ADLER_MOD, s2 %= ADLER_MOD; + buflen -= blocklen; + blocklen = 5552; + } + return (unsigned int)(s2 << 16) + (unsigned int)s1; +} + +static unsigned int stb_decompress(unsigned char *output, const unsigned char *i, unsigned int /*length*/) +{ + if (stb__in4(0) != 0x57bC0000) return 0; + if (stb__in4(4) != 0) return 0; // error! stream is > 4GB + const unsigned int olen = stb_decompress_length(i); + stb__barrier_in_b = i; + stb__barrier_out_e = output + olen; + stb__barrier_out_b = output; + i += 16; + + stb__dout = output; + for (;;) { + const unsigned char *old_i = i; + i = stb_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + IM_ASSERT(stb__dout == output + olen); + if (stb__dout != output + olen) return 0; + if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2)) + return 0; + return olen; + } else { + IM_ASSERT(0); /* NOTREACHED */ + return 0; + } + } + IM_ASSERT(stb__dout <= output + olen); + if (stb__dout > output + olen) + return 0; + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Default font data (ProggyClean.ttf) +//----------------------------------------------------------------------------- +// ProggyClean.ttf +// Copyright (c) 2004, 2005 Tristan Grimmer +// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) +// Download and more information at http://upperbounds.net +//----------------------------------------------------------------------------- +// File: 'ProggyClean.ttf' (41208 bytes) +// Exported using misc/fonts/binary_to_compressed_c.cpp (with compression + base85 string encoding). +// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. +//----------------------------------------------------------------------------- +static const char proggy_clean_ttf_compressed_data_base85[11980 + 1] = + "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" + "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" + "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." + "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" + "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" + "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" + "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" + "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" + "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" + "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" + "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" + "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" + "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" + "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" + "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" + "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" + "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" + "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" + "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" + "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" + "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" + ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" + "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" + "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" + "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" + "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" + "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" + "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" + "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" + "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" + "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" + "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" + "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" + "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" + "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" + "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" + "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" + ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" + "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" + "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" + "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" + "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" + "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" + "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" + ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" + "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" + "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" + "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" + "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" + "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; + +static const char* GetDefaultCompressedFontDataTTFBase85() +{ + return proggy_clean_ttf_compressed_data_base85; +} + +#endif // #ifndef IMGUI_DISABLE diff --git a/vendor/librw/skeleton/imgui/imgui_impl_rw.cpp b/vendor/librw/skeleton/imgui/imgui_impl_rw.cpp new file mode 100644 index 0000000..83b139e --- /dev/null +++ b/vendor/librw/skeleton/imgui/imgui_impl_rw.cpp @@ -0,0 +1,240 @@ +#include +#include +#include + +#include "imgui/imgui.h" +#include "imgui_impl_rw.h" + +using namespace rw::RWDEVICE; + +static rw::Texture *g_FontTexture; +static Im2DVertex *g_vertbuf; +static int g_vertbufSize; + +void +ImGui_ImplRW_RenderDrawLists(ImDrawData* draw_data) +{ + ImGuiIO &io = ImGui::GetIO(); + + // minimized + if (io.DisplaySize.x <= 0.0f || io.DisplaySize.y <= 0.0f) + return; + + if(g_vertbuf == nil || g_vertbufSize < draw_data->TotalVtxCount){ + if(g_vertbuf){ + rwFree(g_vertbuf); + g_vertbuf = nil; + } + g_vertbufSize = draw_data->TotalVtxCount + 5000; + g_vertbuf = rwNewT(Im2DVertex, g_vertbufSize, 0); + } + + float xoff = 0.0f; + float yoff = 0.0f; +#ifdef RWHALFPIXEL + xoff = -0.5; + yoff = 0.5; +#endif + + rw::Camera *cam = (rw::Camera*)rw::engine->currentCamera; + Im2DVertex *vtx_dst = g_vertbuf; + float recipZ = 1.0f/cam->nearPlane; + for(int n = 0; n < draw_data->CmdListsCount; n++){ + const ImDrawList *cmd_list = draw_data->CmdLists[n]; + const ImDrawVert *vtx_src = cmd_list->VtxBuffer.Data; + for(int i = 0; i < cmd_list->VtxBuffer.Size; i++){ + vtx_dst[i].setScreenX(vtx_src[i].pos.x + xoff); + vtx_dst[i].setScreenY(vtx_src[i].pos.y + yoff); + vtx_dst[i].setScreenZ(rw::im2d::GetNearZ()); + vtx_dst[i].setCameraZ(cam->nearPlane); + vtx_dst[i].setRecipCameraZ(recipZ); + vtx_dst[i].setColor(vtx_src[i].col&0xFF, vtx_src[i].col>>8 & 0xFF, vtx_src[i].col>>16 & 0xFF, vtx_src[i].col>>24 & 0xFF); + vtx_dst[i].setU(vtx_src[i].uv.x, recipZ); + vtx_dst[i].setV(vtx_src[i].uv.y, recipZ); + } + vtx_dst += cmd_list->VtxBuffer.Size; + } + + int vertexAlpha = rw::GetRenderState(rw::VERTEXALPHA); + int srcBlend = rw::GetRenderState(rw::SRCBLEND); + int dstBlend = rw::GetRenderState(rw::DESTBLEND); + int ztest = rw::GetRenderState(rw::ZTESTENABLE); + void *tex = rw::GetRenderStatePtr(rw::TEXTURERASTER); + int addrU = rw::GetRenderState(rw::TEXTUREADDRESSU); + int addrV = rw::GetRenderState(rw::TEXTUREADDRESSV); + int filter = rw::GetRenderState(rw::TEXTUREFILTER); + int cullmode = rw::GetRenderState(rw::CULLMODE); + + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDSRCALPHA); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDINVSRCALPHA); + rw::SetRenderState(rw::ZTESTENABLE, 0); + rw::SetRenderState(rw::CULLMODE, rw::CULLNONE); + + int vtx_offset = 0; + for(int n = 0; n < draw_data->CmdListsCount; n++){ + const ImDrawList *cmd_list = draw_data->CmdLists[n]; + int idx_offset = 0; + for(int i = 0; i < cmd_list->CmdBuffer.Size; i++){ + const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[i]; + if(pcmd->UserCallback) + pcmd->UserCallback(cmd_list, pcmd); + else{ + rw::Texture *tex = (rw::Texture*)pcmd->TextureId; + if(tex && tex->raster){ + rw::SetRenderStatePtr(rw::TEXTURERASTER, tex->raster); + rw::SetRenderState(rw::TEXTUREADDRESSU, tex->getAddressU()); + rw::SetRenderState(rw::TEXTUREADDRESSV, tex->getAddressV()); + rw::SetRenderState(rw::TEXTUREFILTER, tex->getFilter()); + }else + rw::SetRenderStatePtr(rw::TEXTURERASTER, nil); + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, + g_vertbuf+vtx_offset, cmd_list->VtxBuffer.Size, + cmd_list->IdxBuffer.Data+idx_offset, pcmd->ElemCount); + } + idx_offset += pcmd->ElemCount; + } + vtx_offset += cmd_list->VtxBuffer.Size; + } + + rw::SetRenderState(rw::VERTEXALPHA,vertexAlpha); + rw::SetRenderState(rw::SRCBLEND, srcBlend); + rw::SetRenderState(rw::DESTBLEND, dstBlend); + rw::SetRenderState(rw::ZTESTENABLE, ztest); + rw::SetRenderStatePtr(rw::TEXTURERASTER, tex); + rw::SetRenderState(rw::TEXTUREADDRESSU, addrU); + rw::SetRenderState(rw::TEXTUREADDRESSV, addrV); + rw::SetRenderState(rw::TEXTUREFILTER, filter); + rw::SetRenderState(rw::CULLMODE, cullmode); +} + +bool +ImGui_ImplRW_Init(void) +{ + using namespace sk; + + ImGui::CreateContext(); + ImGuiIO &io = ImGui::GetIO(); + + io.KeyMap[ImGuiKey_Tab] = KEY_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = KEY_PGUP; + io.KeyMap[ImGuiKey_PageDown] = KEY_PGDN; + io.KeyMap[ImGuiKey_Home] = KEY_HOME; + io.KeyMap[ImGuiKey_End] = KEY_END; + io.KeyMap[ImGuiKey_Delete] = KEY_DEL; + io.KeyMap[ImGuiKey_Backspace] = KEY_BACKSP; + io.KeyMap[ImGuiKey_Enter] = KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = KEY_ESC; + io.KeyMap[ImGuiKey_A] = 'A'; + io.KeyMap[ImGuiKey_C] = 'C'; + io.KeyMap[ImGuiKey_V] = 'V'; + io.KeyMap[ImGuiKey_X] = 'X'; + io.KeyMap[ImGuiKey_Y] = 'Y'; + io.KeyMap[ImGuiKey_Z] = 'Z'; + + return true; +} + +void +ImGui_ImplRW_Shutdown(void) +{ +} + +static bool +ImGui_ImplRW_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO &io = ImGui::GetIO(); + unsigned char *pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, nil); + + rw::Image *image; + image = rw::Image::create(width, height, 32); + image->allocate(); + for(int y = 0; y < height; y++) + memcpy(image->pixels + image->stride*y, pixels + width*4* y, width*4); + g_FontTexture = rw::Texture::create(rw::Raster::createFromImage(image)); + g_FontTexture->setFilter(rw::Texture::LINEAR); + image->destroy(); + + // Store our identifier + io.Fonts->TexID = (void*)g_FontTexture; + + return true; +} + +bool +ImGui_ImplRW_CreateDeviceObjects() +{ +// if(!g_pd3dDevice) +// return false; + if(!ImGui_ImplRW_CreateFontsTexture()) + return false; + return true; +} + +void +ImGui_ImplRW_NewFrame(float timeDelta) +{ + if(!g_FontTexture) + ImGui_ImplRW_CreateDeviceObjects(); + + ImGuiIO &io = ImGui::GetIO(); + + io.DisplaySize = ImVec2(sk::globals.width, sk::globals.height); + io.DeltaTime = timeDelta; + + io.KeyCtrl = io.KeysDown[sk::KEY_LCTRL] || io.KeysDown[sk::KEY_RCTRL]; + io.KeyShift = io.KeysDown[sk::KEY_LSHIFT] || io.KeysDown[sk::KEY_RSHIFT]; + io.KeyAlt = io.KeysDown[sk::KEY_LALT] || io.KeysDown[sk::KEY_RALT]; + io.KeySuper = false; + + if(io.WantSetMousePos) + sk::SetMousePosition(io.MousePos.x, io.MousePos.y); + + ImGui::NewFrame(); +} + +sk::EventStatus +ImGuiEventHandler(sk::Event e, void *param) +{ + using namespace sk; + + ImGuiIO &io = ImGui::GetIO(); + MouseState *ms; + uint c; + + switch(e){ + case KEYDOWN: + c = *(int*)param; + if(c < 256) + io.KeysDown[c] = 1; + return EVENTPROCESSED; + case KEYUP: + c = *(int*)param; + if(c < 256) + io.KeysDown[c] = 0; + return EVENTPROCESSED; + case CHARINPUT: + c = (uint)(uintptr)param; + io.AddInputCharacter((unsigned short)c); + return EVENTPROCESSED; + case MOUSEMOVE: + ms = (MouseState*)param; + io.MousePos.x = ms->posx; + io.MousePos.y = ms->posy; + return EVENTPROCESSED; + case MOUSEBTN: + ms = (MouseState*)param; + io.MouseDown[0] = !!(ms->buttons & 1); + io.MouseDown[2] = !!(ms->buttons & 2); + io.MouseDown[1] = !!(ms->buttons & 4); + return EVENTPROCESSED; + } + return EVENTPROCESSED; +} diff --git a/vendor/librw/skeleton/imgui/imgui_impl_rw.h b/vendor/librw/skeleton/imgui/imgui_impl_rw.h new file mode 100644 index 0000000..32e961e --- /dev/null +++ b/vendor/librw/skeleton/imgui/imgui_impl_rw.h @@ -0,0 +1,5 @@ +IMGUI_API bool ImGui_ImplRW_Init(void); +IMGUI_API void ImGui_ImplRW_Shutdown(void); +IMGUI_API void ImGui_ImplRW_NewFrame(float timeDelta); +sk::EventStatus ImGuiEventHandler(sk::Event e, void *param); +void ImGui_ImplRW_RenderDrawLists(ImDrawData* draw_data); \ No newline at end of file diff --git a/vendor/librw/skeleton/imgui/imgui_internal.h b/vendor/librw/skeleton/imgui/imgui_internal.h new file mode 100644 index 0000000..8006246 --- /dev/null +++ b/vendor/librw/skeleton/imgui/imgui_internal.h @@ -0,0 +1,2688 @@ +// dear imgui, v1.83 +// (internal structures/api) + +// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! +// Set: +// #define IMGUI_DEFINE_MATH_OPERATORS +// To implement maths operators for ImVec2 (disabled by default to not collide with using IM_VEC2_CLASS_EXTRA along with your own math types+operators) + +/* + +Index of this file: + +// [SECTION] Header mess +// [SECTION] Forward declarations +// [SECTION] Context pointer +// [SECTION] STB libraries includes +// [SECTION] Macros +// [SECTION] Generic helpers +// [SECTION] ImDrawList support +// [SECTION] Widgets support: flags, enums, data structures +// [SECTION] Columns support +// [SECTION] Multi-select support +// [SECTION] Docking support +// [SECTION] Viewport support +// [SECTION] Settings support +// [SECTION] Metrics, Debug +// [SECTION] Generic context hooks +// [SECTION] ImGuiContext (main imgui context) +// [SECTION] ImGuiWindowTempData, ImGuiWindow +// [SECTION] Tab bar, Tab item support +// [SECTION] Table support +// [SECTION] ImGui internal API +// [SECTION] ImFontAtlas internal API +// [SECTION] Test Engine specific hooks (imgui_test_engine) + +*/ + +#pragma once +#ifndef IMGUI_DISABLE + +//----------------------------------------------------------------------------- +// [SECTION] Header mess +//----------------------------------------------------------------------------- + +#ifndef IMGUI_VERSION +#error Must include imgui.h before imgui_internal.h +#endif + +#include // FILE*, sscanf +#include // NULL, malloc, free, qsort, atoi, atof +#include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf +#include // INT_MIN, INT_MAX + +// Enable SSE intrinsics if available +#if defined __SSE__ || defined __x86_64__ || defined _M_X64 +#define IMGUI_ENABLE_SSE +#include +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) +#pragma warning (disable: 26812) // The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer) +#pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). + +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic push +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok, for ImFloorSigned() +#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif + +// Legacy defines +#ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS // Renamed in 1.74 +#error Use IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS +#endif +#ifdef IMGUI_DISABLE_MATH_FUNCTIONS // Renamed in 1.74 +#error Use IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS +#endif + +// Enable stb_truetype by default unless FreeType is enabled. +// You can compile with both by defining both IMGUI_ENABLE_FREETYPE and IMGUI_ENABLE_STB_TRUETYPE together. +#ifndef IMGUI_ENABLE_FREETYPE +#define IMGUI_ENABLE_STB_TRUETYPE +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Forward declarations +//----------------------------------------------------------------------------- + +struct ImBitVector; // Store 1-bit per value +struct ImRect; // An axis-aligned rectangle (2 points) +struct ImDrawDataBuilder; // Helper to build a ImDrawData instance +struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it +struct ImGuiContext; // Main Dear ImGui context +struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine +struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum +struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() +struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box +struct ImGuiLastItemDataBackup; // Backup and restore IsItemHovered() internal data +struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only +struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result +struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions +struct ImGuiNextWindowData; // Storage for SetNextWindow** functions +struct ImGuiNextItemData; // Storage for SetNextItem** functions +struct ImGuiOldColumnData; // Storage data for a single column for legacy Columns() api +struct ImGuiOldColumns; // Storage data for a columns set for legacy Columns() api +struct ImGuiPopupData; // Storage for current popup stack +struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file +struct ImGuiStackSizes; // Storage of stack sizes for debugging/asserting +struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it +struct ImGuiTabBar; // Storage for a tab bar +struct ImGuiTabItem; // Storage for a tab item (within a tab bar) +struct ImGuiTable; // Storage for a table +struct ImGuiTableColumn; // Storage for one column of a table +struct ImGuiTableTempData; // Temporary storage for one table (one per table in the stack), shared between tables. +struct ImGuiTableSettings; // Storage for a table .ini settings +struct ImGuiTableColumnsSettings; // Storage for a column .ini settings +struct ImGuiWindow; // Storage for one window +struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) +struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) + +// Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. +typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical +typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag() +typedef int ImGuiItemAddFlags; // -> enum ImGuiItemAddFlags_ // Flags: for ItemAdd() +typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for DC.LastItemStatusFlags +typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() +typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() +typedef int ImGuiNavDirSourceFlags; // -> enum ImGuiNavDirSourceFlags_ // Flags: for GetNavInputAmount2d() +typedef int ImGuiNavMoveFlags; // -> enum ImGuiNavMoveFlags_ // Flags: for navigation requests +typedef int ImGuiNextItemDataFlags; // -> enum ImGuiNextItemDataFlags_ // Flags: for SetNextItemXXX() functions +typedef int ImGuiNextWindowDataFlags; // -> enum ImGuiNextWindowDataFlags_// Flags: for SetNextWindowXXX() functions +typedef int ImGuiSeparatorFlags; // -> enum ImGuiSeparatorFlags_ // Flags: for SeparatorEx() +typedef int ImGuiTextFlags; // -> enum ImGuiTextFlags_ // Flags: for TextEx() +typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // Flags: for BeginTooltipEx() + +typedef void (*ImGuiErrorLogCallback)(void* user_data, const char* fmt, ...); + +//----------------------------------------------------------------------------- +// [SECTION] Context pointer +// See implementation of this variable in imgui.cpp for comments and details. +//----------------------------------------------------------------------------- + +#ifndef GImGui +extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer +#endif + +//------------------------------------------------------------------------- +// [SECTION] STB libraries includes +//------------------------------------------------------------------------- + +namespace ImStb +{ + +#undef STB_TEXTEDIT_STRING +#undef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_STRING ImGuiInputTextState +#define STB_TEXTEDIT_CHARTYPE ImWchar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE (-1.0f) +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#include "imstb_textedit.h" + +} // namespace ImStb + +//----------------------------------------------------------------------------- +// [SECTION] Macros +//----------------------------------------------------------------------------- + +// Debug Logging +#ifndef IMGUI_DEBUG_LOG +#define IMGUI_DEBUG_LOG(_FMT,...) printf("[%05d] " _FMT, GImGui->FrameCount, __VA_ARGS__) +#endif + +// Debug Logging for selected systems. Remove the '((void)0) //' to enable. +//#define IMGUI_DEBUG_LOG_POPUP IMGUI_DEBUG_LOG // Enable log +//#define IMGUI_DEBUG_LOG_NAV IMGUI_DEBUG_LOG // Enable log +#define IMGUI_DEBUG_LOG_POPUP(...) ((void)0) // Disable log +#define IMGUI_DEBUG_LOG_NAV(...) ((void)0) // Disable log + +// Static Asserts +#if (__cplusplus >= 201100) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201100) +#define IM_STATIC_ASSERT(_COND) static_assert(_COND, "") +#else +#define IM_STATIC_ASSERT(_COND) typedef char static_assertion_##__line__[(_COND)?1:-1] +#endif + +// "Paranoid" Debug Asserts are meant to only be enabled during specific debugging/work, otherwise would slow down the code too much. +// We currently don't have many of those so the effect is currently negligible, but onward intent to add more aggressive ones in the code. +//#define IMGUI_DEBUG_PARANOID +#ifdef IMGUI_DEBUG_PARANOID +#define IM_ASSERT_PARANOID(_EXPR) IM_ASSERT(_EXPR) +#else +#define IM_ASSERT_PARANOID(_EXPR) +#endif + +// Error handling +// Down the line in some frameworks/languages we would like to have a way to redirect those to the programmer and recover from more faults. +#ifndef IM_ASSERT_USER_ERROR +#define IM_ASSERT_USER_ERROR(_EXP,_MSG) IM_ASSERT((_EXP) && _MSG) // Recoverable User Error +#endif + +// Misc Macros +#define IM_PI 3.14159265358979323846f +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" // Play it nice with Windows users (Update: since 2018-05, Notepad finally appears to support Unix-style carriage returns!) +#else +#define IM_NEWLINE "\n" +#endif +#define IM_TABSIZE (4) +#define IM_MEMALIGN(_OFF,_ALIGN) (((_OFF) + (_ALIGN - 1)) & ~(_ALIGN - 1)) // Memory align e.g. IM_ALIGN(0,4)=0, IM_ALIGN(1,4)=4, IM_ALIGN(4,4)=4, IM_ALIGN(5,4)=8 +#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose +#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 +#define IM_FLOOR(_VAL) ((float)(int)(_VAL)) // ImFloor() is not inlined in MSVC debug builds +#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // + +// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall +#ifdef _MSC_VER +#define IMGUI_CDECL __cdecl +#else +#define IMGUI_CDECL +#endif + +// Warnings +#if defined(_MSC_VER) && !defined(__clang__) +#define IM_MSVC_WARNING_SUPPRESS(XXXX) __pragma(warning(suppress: XXXX)) +#else +#define IM_MSVC_WARNING_SUPPRESS(XXXX) +#endif + +// Debug Tools +// Use 'Metrics->Tools->Item Picker' to break into the call-stack of a specific item. +#ifndef IM_DEBUG_BREAK +#if defined(__clang__) +#define IM_DEBUG_BREAK() __builtin_debugtrap() +#elif defined (_MSC_VER) +#define IM_DEBUG_BREAK() __debugbreak() +#else +#define IM_DEBUG_BREAK() IM_ASSERT(0) // It is expected that you define IM_DEBUG_BREAK() into something that will break nicely in a debugger! +#endif +#endif // #ifndef IM_DEBUG_BREAK + +//----------------------------------------------------------------------------- +// [SECTION] Generic helpers +// Note that the ImXXX helpers functions are lower-level than ImGui functions. +// ImGui functions or the ImGui context are never called/used from other ImXXX functions. +//----------------------------------------------------------------------------- +// - Helpers: Hashing +// - Helpers: Sorting +// - Helpers: Bit manipulation +// - Helpers: String, Formatting +// - Helpers: UTF-8 <> wchar conversions +// - Helpers: ImVec2/ImVec4 operators +// - Helpers: Maths +// - Helpers: Geometry +// - Helper: ImVec1 +// - Helper: ImVec2ih +// - Helper: ImRect +// - Helper: ImBitArray +// - Helper: ImBitVector +// - Helper: ImSpan<>, ImSpanAllocator<> +// - Helper: ImPool<> +// - Helper: ImChunkStream<> +//----------------------------------------------------------------------------- + +// Helpers: Hashing +IMGUI_API ImGuiID ImHashData(const void* data, size_t data_size, ImU32 seed = 0); +IMGUI_API ImGuiID ImHashStr(const char* data, size_t data_size = 0, ImU32 seed = 0); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +static inline ImGuiID ImHash(const void* data, int size, ImU32 seed = 0) { return size ? ImHashData(data, (size_t)size, seed) : ImHashStr((const char*)data, 0, seed); } // [moved to ImHashStr/ImHashData in 1.68] +#endif + +// Helpers: Sorting +#define ImQsort qsort + +// Helpers: Color Blending +IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b); + +// Helpers: Bit manipulation +static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +static inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } +static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } + +// Helpers: String, Formatting +IMGUI_API int ImStricmp(const char* str1, const char* str2); +IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); +IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); +IMGUI_API char* ImStrdup(const char* str); +IMGUI_API char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* str); +IMGUI_API const char* ImStrchrRange(const char* str_begin, const char* str_end, char c); +IMGUI_API int ImStrlenW(const ImWchar* str); +IMGUI_API const char* ImStreolRange(const char* str, const char* str_end); // End end-of-line +IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line +IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); +IMGUI_API void ImStrTrimBlanks(char* str); +IMGUI_API const char* ImStrSkipBlank(const char* str); +IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); +IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); +IMGUI_API const char* ImParseFormatFindStart(const char* format); +IMGUI_API const char* ImParseFormatFindEnd(const char* format); +IMGUI_API const char* ImParseFormatTrimDecorations(const char* format, char* buf, size_t buf_size); +IMGUI_API int ImParseFormatPrecision(const char* format, int default_value); +static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } +static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } + +// Helpers: UTF-8 <> wchar conversions +IMGUI_API int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count +IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // read one character. return input UTF-8 bytes count +IMGUI_API int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count +IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) +IMGUI_API int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end); // return number of bytes to express one char in UTF-8 +IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string in UTF-8 + +// Helpers: ImVec2/ImVec4 operators +// We are keeping those disabled by default so they don't leak in user space, to allow user enabling implicit cast operators between ImVec2 and their own types (using IM_VEC2_CLASS_EXTRA etc.) +// We unfortunately don't have a unary- operator for ImVec2 because this would needs to be defined inside the class itself. +#ifdef IMGUI_DEFINE_MATH_OPERATORS +IM_MSVC_RUNTIME_CHECKS_OFF +static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } +static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } +static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } +static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } +static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +static inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } +static inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } +static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } +static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } +static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } +IM_MSVC_RUNTIME_CHECKS_RESTORE +#endif + +// Helpers: File System +#ifdef IMGUI_DISABLE_FILE_FUNCTIONS +#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS +typedef void* ImFileHandle; +static inline ImFileHandle ImFileOpen(const char*, const char*) { return NULL; } +static inline bool ImFileClose(ImFileHandle) { return false; } +static inline ImU64 ImFileGetSize(ImFileHandle) { return (ImU64)-1; } +static inline ImU64 ImFileRead(void*, ImU64, ImU64, ImFileHandle) { return 0; } +static inline ImU64 ImFileWrite(const void*, ImU64, ImU64, ImFileHandle) { return 0; } +#endif +#ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS +typedef FILE* ImFileHandle; +IMGUI_API ImFileHandle ImFileOpen(const char* filename, const char* mode); +IMGUI_API bool ImFileClose(ImFileHandle file); +IMGUI_API ImU64 ImFileGetSize(ImFileHandle file); +IMGUI_API ImU64 ImFileRead(void* data, ImU64 size, ImU64 count, ImFileHandle file); +IMGUI_API ImU64 ImFileWrite(const void* data, ImU64 size, ImU64 count, ImFileHandle file); +#else +#define IMGUI_DISABLE_TTY_FUNCTIONS // Can't use stdout, fflush if we are not using default file functions +#endif +IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_file_size = NULL, int padding_bytes = 0); + +// Helpers: Maths +IM_MSVC_RUNTIME_CHECKS_OFF +// - Wrapper for standard libs functions. (Note that imgui_demo.cpp does _not_ use them to keep the code easy to copy) +#ifndef IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS +#define ImFabs(X) fabsf(X) +#define ImSqrt(X) sqrtf(X) +#define ImFmod(X, Y) fmodf((X), (Y)) +#define ImCos(X) cosf(X) +#define ImSin(X) sinf(X) +#define ImAcos(X) acosf(X) +#define ImAtan2(Y, X) atan2f((Y), (X)) +#define ImAtof(STR) atof(STR) +//#define ImFloorStd(X) floorf(X) // We use our own, see ImFloor() and ImFloorSigned() +#define ImCeil(X) ceilf(X) +static inline float ImPow(float x, float y) { return powf(x, y); } // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision +static inline double ImPow(double x, double y) { return pow(x, y); } +static inline float ImLog(float x) { return logf(x); } // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision +static inline double ImLog(double x) { return log(x); } +static inline int ImAbs(int x) { return x < 0 ? -x : x; } +static inline float ImAbs(float x) { return fabsf(x); } +static inline double ImAbs(double x) { return fabs(x); } +static inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : ((x > 0.0f) ? 1.0f : 0.0f); } // Sign operator - returns -1, 0 or 1 based on sign of argument +static inline double ImSign(double x) { return (x < 0.0) ? -1.0 : ((x > 0.0) ? 1.0 : 0.0); } +#ifdef IMGUI_ENABLE_SSE +static inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } +#else +static inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } +#endif +static inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } +#endif +// - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double +// (Exceptionally using templates here but we could also redefine them for those types) +template static inline T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } +template static inline T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } +template static inline T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +template static inline T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } +template static inline void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } +template static inline T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; } +template static inline T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; } +// - Misc maths helpers +static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } +static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } +static inline ImVec2 ImClamp(const ImVec2& v, const ImVec2& mn, ImVec2 mx) { return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } +static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +static inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); } +static inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); } +static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; } +static inline float ImFloor(float f) { return (float)(int)(f); } +static inline float ImFloorSigned(float f) { return (float)((f >= 0 || (int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() +static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } +static inline int ImModPositive(int a, int b) { return (a + b) % b; } +static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } +static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +IM_MSVC_RUNTIME_CHECKS_RESTORE + +// Helpers: Geometry +IMGUI_API ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t); +IMGUI_API ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments); // For curves with explicit number of segments +IMGUI_API ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol);// For auto-tessellated curves you can use tess_tol = style.CurveTessellationTol +IMGUI_API ImVec2 ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float t); +IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p); +IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w); +inline float ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; } +IMGUI_API ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy); + +// Helper: ImVec1 (1D vector) +// (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches) +IM_MSVC_RUNTIME_CHECKS_OFF +struct ImVec1 +{ + float x; + ImVec1() { x = 0.0f; } + ImVec1(float _x) { x = _x; } +}; + +// Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) +struct ImVec2ih +{ + short x, y; + ImVec2ih() { x = y = 0; } + ImVec2ih(short _x, short _y) { x = _x; y = _y; } + explicit ImVec2ih(const ImVec2& rhs) { x = (short)rhs.x; y = (short)rhs.y; } +}; + +// Helper: ImRect (2D axis aligned bounding-box) +// NB: we can't rely on ImVec2 math operators being available here! +struct IMGUI_API ImRect +{ + ImVec2 Min; // Upper-left + ImVec2 Max; // Lower-right + + ImRect() : Min(0.0f, 0.0f), Max(0.0f, 0.0f) {} + ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} + ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} + ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} + + ImVec2 GetCenter() const { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); } + ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } + float GetWidth() const { return Max.x - Min.x; } + float GetHeight() const { return Max.y - Min.y; } + float GetArea() const { return (Max.x - Min.x) * (Max.y - Min.y); } + ImVec2 GetTL() const { return Min; } // Top-left + ImVec2 GetTR() const { return ImVec2(Max.x, Min.y); } // Top-right + ImVec2 GetBL() const { return ImVec2(Min.x, Max.y); } // Bottom-left + ImVec2 GetBR() const { return Max; } // Bottom-right + bool Contains(const ImVec2& p) const { return p.x >= Min.x && p.y >= Min.y && p.x < Max.x && p.y < Max.y; } + bool Contains(const ImRect& r) const { return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; } + bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; } + void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; } + void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; } + void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; } + void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; } + void Translate(const ImVec2& d) { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; } + void TranslateX(float dx) { Min.x += dx; Max.x += dx; } + void TranslateY(float dy) { Min.y += dy; Max.y += dy; } + void ClipWith(const ImRect& r) { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display. + void ClipWithFull(const ImRect& r) { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped. + void Floor() { Min.x = IM_FLOOR(Min.x); Min.y = IM_FLOOR(Min.y); Max.x = IM_FLOOR(Max.x); Max.y = IM_FLOOR(Max.y); } + bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } + ImVec4 ToVec4() const { return ImVec4(Min.x, Min.y, Max.x, Max.y); } +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + +// Helper: ImBitArray +inline bool ImBitArrayTestBit(const ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); return (arr[n >> 5] & mask) != 0; } +inline void ImBitArrayClearBit(ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] &= ~mask; } +inline void ImBitArraySetBit(ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] |= mask; } +inline void ImBitArraySetBitRange(ImU32* arr, int n, int n2) // Works on range [n..n2) +{ + n2--; + while (n <= n2) + { + int a_mod = (n & 31); + int b_mod = (n2 > (n | 31) ? 31 : (n2 & 31)) + 1; + ImU32 mask = (ImU32)(((ImU64)1 << b_mod) - 1) & ~(ImU32)(((ImU64)1 << a_mod) - 1); + arr[n >> 5] |= mask; + n = (n + 32) & ~31; + } +} + +// Helper: ImBitArray class (wrapper over ImBitArray functions) +// Store 1-bit per value. +template +struct IMGUI_API ImBitArray +{ + ImU32 Storage[(BITCOUNT + 31) >> 5]; + ImBitArray() { ClearAllBits(); } + void ClearAllBits() { memset(Storage, 0, sizeof(Storage)); } + void SetAllBits() { memset(Storage, 255, sizeof(Storage)); } + bool TestBit(int n) const { IM_ASSERT(n < BITCOUNT); return ImBitArrayTestBit(Storage, n); } + void SetBit(int n) { IM_ASSERT(n < BITCOUNT); ImBitArraySetBit(Storage, n); } + void ClearBit(int n) { IM_ASSERT(n < BITCOUNT); ImBitArrayClearBit(Storage, n); } + void SetBitRange(int n, int n2) { ImBitArraySetBitRange(Storage, n, n2); } // Works on range [n..n2) +}; + +// Helper: ImBitVector +// Store 1-bit per value. +struct IMGUI_API ImBitVector +{ + ImVector Storage; + void Create(int sz) { Storage.resize((sz + 31) >> 5); memset(Storage.Data, 0, (size_t)Storage.Size * sizeof(Storage.Data[0])); } + void Clear() { Storage.clear(); } + bool TestBit(int n) const { IM_ASSERT(n < (Storage.Size << 5)); return ImBitArrayTestBit(Storage.Data, n); } + void SetBit(int n) { IM_ASSERT(n < (Storage.Size << 5)); ImBitArraySetBit(Storage.Data, n); } + void ClearBit(int n) { IM_ASSERT(n < (Storage.Size << 5)); ImBitArrayClearBit(Storage.Data, n); } +}; + +// Helper: ImSpan<> +// Pointing to a span of data we don't own. +template +struct ImSpan +{ + T* Data; + T* DataEnd; + + // Constructors, destructor + inline ImSpan() { Data = DataEnd = NULL; } + inline ImSpan(T* data, int size) { Data = data; DataEnd = data + size; } + inline ImSpan(T* data, T* data_end) { Data = data; DataEnd = data_end; } + + inline void set(T* data, int size) { Data = data; DataEnd = data + size; } + inline void set(T* data, T* data_end) { Data = data; DataEnd = data_end; } + inline int size() const { return (int)(ptrdiff_t)(DataEnd - Data); } + inline int size_in_bytes() const { return (int)(ptrdiff_t)(DataEnd - Data) * (int)sizeof(T); } + inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + inline const T& operator[](int i) const { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + + inline T* begin() { return Data; } + inline const T* begin() const { return Data; } + inline T* end() { return DataEnd; } + inline const T* end() const { return DataEnd; } + + // Utilities + inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < DataEnd); const ptrdiff_t off = it - Data; return (int)off; } +}; + +// Helper: ImSpanAllocator<> +// Facilitate storing multiple chunks into a single large block (the "arena") +// - Usage: call Reserve() N times, allocate GetArenaSizeInBytes() worth, pass it to SetArenaBasePtr(), call GetSpan() N times to retrieve the aligned ranges. +template +struct ImSpanAllocator +{ + char* BasePtr; + int CurrOff; + int CurrIdx; + int Offsets[CHUNKS]; + int Sizes[CHUNKS]; + + ImSpanAllocator() { memset(this, 0, sizeof(*this)); } + inline void Reserve(int n, size_t sz, int a=4) { IM_ASSERT(n == CurrIdx && n < CHUNKS); CurrOff = IM_MEMALIGN(CurrOff, a); Offsets[n] = CurrOff; Sizes[n] = (int)sz; CurrIdx++; CurrOff += (int)sz; } + inline int GetArenaSizeInBytes() { return CurrOff; } + inline void SetArenaBasePtr(void* base_ptr) { BasePtr = (char*)base_ptr; } + inline void* GetSpanPtrBegin(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrIdx == CHUNKS); return (void*)(BasePtr + Offsets[n]); } + inline void* GetSpanPtrEnd(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrIdx == CHUNKS); return (void*)(BasePtr + Offsets[n] + Sizes[n]); } + template + inline void GetSpan(int n, ImSpan* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } +}; + +// Helper: ImPool<> +// Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer, +// Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object. +typedef int ImPoolIdx; +template +struct IMGUI_API ImPool +{ + ImVector Buf; // Contiguous data + ImGuiStorage Map; // ID->Index + ImPoolIdx FreeIdx; // Next free idx to use + + ImPool() { FreeIdx = 0; } + ~ImPool() { Clear(); } + T* GetByKey(ImGuiID key) { int idx = Map.GetInt(key, -1); return (idx != -1) ? &Buf[idx] : NULL; } + T* GetByIndex(ImPoolIdx n) { return &Buf[n]; } + ImPoolIdx GetIndex(const T* p) const { IM_ASSERT(p >= Buf.Data && p < Buf.Data + Buf.Size); return (ImPoolIdx)(p - Buf.Data); } + T* GetOrAddByKey(ImGuiID key) { int* p_idx = Map.GetIntRef(key, -1); if (*p_idx != -1) return &Buf[*p_idx]; *p_idx = FreeIdx; return Add(); } + bool Contains(const T* p) const { return (p >= Buf.Data && p < Buf.Data + Buf.Size); } + void Clear() { for (int n = 0; n < Map.Data.Size; n++) { int idx = Map.Data[n].val_i; if (idx != -1) Buf[idx].~T(); } Map.Clear(); Buf.clear(); FreeIdx = 0; } + T* Add() { int idx = FreeIdx; if (idx == Buf.Size) { Buf.resize(Buf.Size + 1); FreeIdx++; } else { FreeIdx = *(int*)&Buf[idx]; } IM_PLACEMENT_NEW(&Buf[idx]) T(); return &Buf[idx]; } + void Remove(ImGuiID key, const T* p) { Remove(key, GetIndex(p)); } + void Remove(ImGuiID key, ImPoolIdx idx) { Buf[idx].~T(); *(int*)&Buf[idx] = FreeIdx; FreeIdx = idx; Map.SetInt(key, -1); } + void Reserve(int capacity) { Buf.reserve(capacity); Map.Data.reserve(capacity); } + int GetSize() const { return Buf.Size; } +}; + +// Helper: ImChunkStream<> +// Build and iterate a contiguous stream of variable-sized structures. +// This is used by Settings to store persistent data while reducing allocation count. +// We store the chunk size first, and align the final size on 4 bytes boundaries. +// The tedious/zealous amount of casting is to avoid -Wcast-align warnings. +template +struct IMGUI_API ImChunkStream +{ + ImVector Buf; + + void clear() { Buf.clear(); } + bool empty() const { return Buf.Size == 0; } + int size() const { return Buf.Size; } + T* alloc_chunk(size_t sz) { size_t HDR_SZ = 4; sz = IM_MEMALIGN(HDR_SZ + sz, 4u); int off = Buf.Size; Buf.resize(off + (int)sz); ((int*)(void*)(Buf.Data + off))[0] = (int)sz; return (T*)(void*)(Buf.Data + off + (int)HDR_SZ); } + T* begin() { size_t HDR_SZ = 4; if (!Buf.Data) return NULL; return (T*)(void*)(Buf.Data + HDR_SZ); } + T* next_chunk(T* p) { size_t HDR_SZ = 4; IM_ASSERT(p >= begin() && p < end()); p = (T*)(void*)((char*)(void*)p + chunk_size(p)); if (p == (T*)(void*)((char*)end() + HDR_SZ)) return (T*)0; IM_ASSERT(p < end()); return p; } + int chunk_size(const T* p) { return ((const int*)p)[-1]; } + T* end() { return (T*)(void*)(Buf.Data + Buf.Size); } + int offset_from_ptr(const T* p) { IM_ASSERT(p >= begin() && p < end()); const ptrdiff_t off = (const char*)p - Buf.Data; return (int)off; } + T* ptr_from_offset(int off) { IM_ASSERT(off >= 4 && off < Buf.Size); return (T*)(void*)(Buf.Data + off); } + void swap(ImChunkStream& rhs) { rhs.Buf.swap(Buf); } + +}; + +//----------------------------------------------------------------------------- +// [SECTION] ImDrawList support +//----------------------------------------------------------------------------- + +// ImDrawList: Helper function to calculate a circle's segment count given its radius and a "maximum error" value. +// Estimation of number of circle segment based on error is derived using method described in https://stackoverflow.com/a/2244088/15194693 +// Number of segments (N) is calculated using equation: +// N = ceil ( pi / acos(1 - error / r) ) where r > 0, error <= r +// Our equation is significantly simpler that one in the post thanks for choosing segment that is +// perpendicular to X axis. Follow steps in the article from this starting condition and you will +// will get this result. +// +// Rendering circles with an odd number of segments, while mathematically correct will produce +// asymmetrical results on the raster grid. Therefore we're rounding N to next even number (7->8, 8->8, 9->10 etc.) +// +#define IM_ROUNDUP_TO_EVEN(_V) ((((_V) + 1) / 2) * 2) +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN 4 +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX 512 +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD,_MAXERROR) ImClamp(IM_ROUNDUP_TO_EVEN((int)ImCeil(IM_PI / ImAcos(1 - ImMin((_MAXERROR), (_RAD)) / (_RAD)))), IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) + +// Raw equation from IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC rewritten for 'r' and 'error'. +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N,_MAXERROR) ((_MAXERROR) / (1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI)))) +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N,_RAD) ((1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI))) / (_RAD)) + +// ImDrawList: Lookup table size for adaptive arc drawing, cover full circle. +#ifndef IM_DRAWLIST_ARCFAST_TABLE_SIZE +#define IM_DRAWLIST_ARCFAST_TABLE_SIZE 48 // Number of samples in lookup table. +#endif +#define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. + +// Data shared between all ImDrawList instances +// You may want to create your own instance of this if you want to use ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. +struct IMGUI_API ImDrawListSharedData +{ + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas + ImFont* Font; // Current/default font (optional, for simplified AddText overload) + float FontSize; // Current/default font size (optional, for simplified AddText overload) + float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() + float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc + ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() + ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) + + // [Internal] Lookup tables + ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. + float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() + ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas + + ImDrawListSharedData(); + void SetCircleTessellationMaxError(float max_error); +}; + +struct ImDrawDataBuilder +{ + ImVector Layers[2]; // Global layers for: regular, tooltip + + void Clear() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].resize(0); } + void ClearFreeMemory() { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].clear(); } + int GetDrawListCount() const { int count = 0; for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) count += Layers[n].Size; return count; } + IMGUI_API void FlattenIntoSingleLayer(); +}; + +//----------------------------------------------------------------------------- +// [SECTION] Widgets support: flags, enums, data structures +//----------------------------------------------------------------------------- + +// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). +// This is going to be exposed in imgui.h when stabilized enough. +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_None = 0, + ImGuiItemFlags_NoTabStop = 1 << 0, // false + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = 1 << 2, // false // [BETA] Disable interactions but doesn't affect visuals yet. See github.com/ocornut/imgui/issues/211 + ImGuiItemFlags_NoNav = 1 << 3, // false + ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window + ImGuiItemFlags_MixedValue = 1 << 6, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) + ImGuiItemFlags_ReadOnly = 1 << 7 // false // [ALPHA] Allow hovering interactions but underlying value is not changed. +}; + +// Flags for ItemAdd() +// FIXME-NAV: _Focusable is _ALMOST_ what you would expect to be called '_TabStop' but because SetKeyboardFocusHere() works on items with no TabStop we distinguish Focusable from TabStop. +enum ImGuiItemAddFlags_ +{ + ImGuiItemAddFlags_None = 0, + ImGuiItemAddFlags_Focusable = 1 << 0 // FIXME-NAV: In current/legacy scheme, Focusable+TabStop support are opt-in by widgets. We will transition it toward being opt-out, so this flag is expected to eventually disappear. +}; + +// Storage for LastItem data +enum ImGuiItemStatusFlags_ +{ + ImGuiItemStatusFlags_None = 0, + ImGuiItemStatusFlags_HoveredRect = 1 << 0, // Mouse position is within item rectangle (does NOT mean that the window is in correct z-order and can be hovered!, this is only one part of the most-common IsItemHovered test) + ImGuiItemStatusFlags_HasDisplayRect = 1 << 1, // window->DC.LastItemDisplayRect is valid + ImGuiItemStatusFlags_Edited = 1 << 2, // Value exposed by item was edited in the current frame (should match the bool return value of most widgets) + ImGuiItemStatusFlags_ToggledSelection = 1 << 3, // Set when Selectable(), TreeNode() reports toggling a selection. We can't report "Selected", only state changes, in order to easily handle clipping with less issues. + ImGuiItemStatusFlags_ToggledOpen = 1 << 4, // Set when TreeNode() reports toggling their open state. + ImGuiItemStatusFlags_HasDeactivated = 1 << 5, // Set if the widget/group is able to provide data for the ImGuiItemStatusFlags_Deactivated flag. + ImGuiItemStatusFlags_Deactivated = 1 << 6, // Only valid if ImGuiItemStatusFlags_HasDeactivated is set. + ImGuiItemStatusFlags_HoveredWindow = 1 << 7, // Override the HoveredWindow test to allow cross-window hover testing. + ImGuiItemStatusFlags_FocusedByCode = 1 << 8, // Set when the Focusable item just got focused from code. + ImGuiItemStatusFlags_FocusedByTabbing = 1 << 9, // Set when the Focusable item just got focused by Tabbing. + ImGuiItemStatusFlags_Focused = ImGuiItemStatusFlags_FocusedByCode | ImGuiItemStatusFlags_FocusedByTabbing + +#ifdef IMGUI_ENABLE_TEST_ENGINE + , // [imgui_tests only] + ImGuiItemStatusFlags_Openable = 1 << 20, // + ImGuiItemStatusFlags_Opened = 1 << 21, // + ImGuiItemStatusFlags_Checkable = 1 << 22, // + ImGuiItemStatusFlags_Checked = 1 << 23 // +#endif +}; + +// Extend ImGuiInputTextFlags_ +enum ImGuiInputTextFlagsPrivate_ +{ + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 26, // For internal use by InputTextMultiline() + ImGuiInputTextFlags_NoMarkEdited = 1 << 27, // For internal use by functions using InputText() before reformatting data + ImGuiInputTextFlags_MergedItem = 1 << 28 // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. +}; + +// Extend ImGuiButtonFlags_ +enum ImGuiButtonFlagsPrivate_ +{ + ImGuiButtonFlags_PressedOnClick = 1 << 4, // return true on click (mouse down event) + ImGuiButtonFlags_PressedOnClickRelease = 1 << 5, // [Default] return true on click + release on same item <-- this is what the majority of Button are using + ImGuiButtonFlags_PressedOnClickReleaseAnywhere = 1 << 6, // return true on click + release even if the release event is not done while hovering the item + ImGuiButtonFlags_PressedOnRelease = 1 << 7, // return true on release (default requires click+release) + ImGuiButtonFlags_PressedOnDoubleClick = 1 << 8, // return true on double-click (default requires click+release) + ImGuiButtonFlags_PressedOnDragDropHold = 1 << 9, // return true when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) + ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat + ImGuiButtonFlags_FlattenChildren = 1 << 11, // allow interactions even if a child window is overlapping + ImGuiButtonFlags_AllowItemOverlap = 1 << 12, // require previous frame HoveredId to either match id or be null before being usable, use along with SetItemAllowOverlap() + ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press // [UNUSED] + ImGuiButtonFlags_Disabled = 1 << 14, // disable interactions + ImGuiButtonFlags_AlignTextBaseLine = 1 << 15, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine + ImGuiButtonFlags_NoKeyModifiers = 1 << 16, // disable mouse interaction if a key modifier is held + ImGuiButtonFlags_NoHoldingActiveId = 1 << 17, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) + ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated + ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item + ImGuiButtonFlags_PressedOnMask_ = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold, + ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease +}; + +// Extend ImGuiSliderFlags_ +enum ImGuiSliderFlagsPrivate_ +{ + ImGuiSliderFlags_Vertical = 1 << 20, // Should this slider be orientated vertically? + ImGuiSliderFlags_ReadOnly = 1 << 21 +}; + +// Extend ImGuiSelectableFlags_ +enum ImGuiSelectableFlagsPrivate_ +{ + // NB: need to be in sync with last value of ImGuiSelectableFlags_ + ImGuiSelectableFlags_NoHoldingActiveID = 1 << 20, + ImGuiSelectableFlags_SelectOnClick = 1 << 21, // Override button behavior to react on Click (default is Click+Release) + ImGuiSelectableFlags_SelectOnRelease = 1 << 22, // Override button behavior to react on Release (default is Click+Release) + ImGuiSelectableFlags_SpanAvailWidth = 1 << 23, // Span all avail width even if we declared less for layout purpose. FIXME: We may be able to remove this (added in 6251d379, 2bcafc86 for menus) + ImGuiSelectableFlags_DrawHoveredWhenHeld = 1 << 24, // Always show active when held, even is not hovered. This concept could probably be renamed/formalized somehow. + ImGuiSelectableFlags_SetNavIdOnHover = 1 << 25, // Set Nav/Focus ID on mouse hover (used by MenuItem) + ImGuiSelectableFlags_NoPadWithHalfSpacing = 1 << 26 // Disable padding each side with ItemSpacing * 0.5f +}; + +// Extend ImGuiTreeNodeFlags_ +enum ImGuiTreeNodeFlagsPrivate_ +{ + ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 20 +}; + +enum ImGuiSeparatorFlags_ +{ + ImGuiSeparatorFlags_None = 0, + ImGuiSeparatorFlags_Horizontal = 1 << 0, // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar + ImGuiSeparatorFlags_Vertical = 1 << 1, + ImGuiSeparatorFlags_SpanAllColumns = 1 << 2 +}; + +enum ImGuiTextFlags_ +{ + ImGuiTextFlags_None = 0, + ImGuiTextFlags_NoWidthForLargeClippedText = 1 << 0 +}; + +enum ImGuiTooltipFlags_ +{ + ImGuiTooltipFlags_None = 0, + ImGuiTooltipFlags_OverridePreviousTooltip = 1 << 0 // Override will clear/ignore previously submitted tooltip (defaults to append) +}; + +// FIXME: this is in development, not exposed/functional as a generic feature yet. +// Horizontal/Vertical enums are fixed to 0/1 so they may be used to index ImVec2 +enum ImGuiLayoutType_ +{ + ImGuiLayoutType_Horizontal = 0, + ImGuiLayoutType_Vertical = 1 +}; + +enum ImGuiLogType +{ + ImGuiLogType_None = 0, + ImGuiLogType_TTY, + ImGuiLogType_File, + ImGuiLogType_Buffer, + ImGuiLogType_Clipboard +}; + +// X/Y enums are fixed to 0/1 so they may be used to index ImVec2 +enum ImGuiAxis +{ + ImGuiAxis_None = -1, + ImGuiAxis_X = 0, + ImGuiAxis_Y = 1 +}; + +enum ImGuiPlotType +{ + ImGuiPlotType_Lines, + ImGuiPlotType_Histogram +}; + +enum ImGuiInputSource +{ + ImGuiInputSource_None = 0, + ImGuiInputSource_Mouse, + ImGuiInputSource_Keyboard, + ImGuiInputSource_Gamepad, + ImGuiInputSource_Nav, // Stored in g.ActiveIdSource only + ImGuiInputSource_Clipboard, // Currently only used by InputText() + ImGuiInputSource_COUNT +}; + +// FIXME-NAV: Clarify/expose various repeat delay/rate +enum ImGuiInputReadMode +{ + ImGuiInputReadMode_Down, + ImGuiInputReadMode_Pressed, + ImGuiInputReadMode_Released, + ImGuiInputReadMode_Repeat, + ImGuiInputReadMode_RepeatSlow, + ImGuiInputReadMode_RepeatFast +}; + +enum ImGuiNavHighlightFlags_ +{ + ImGuiNavHighlightFlags_None = 0, + ImGuiNavHighlightFlags_TypeDefault = 1 << 0, + ImGuiNavHighlightFlags_TypeThin = 1 << 1, + ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, // Draw rectangular highlight if (g.NavId == id) _even_ when using the mouse. + ImGuiNavHighlightFlags_NoRounding = 1 << 3 +}; + +enum ImGuiNavDirSourceFlags_ +{ + ImGuiNavDirSourceFlags_None = 0, + ImGuiNavDirSourceFlags_Keyboard = 1 << 0, + ImGuiNavDirSourceFlags_PadDPad = 1 << 1, + ImGuiNavDirSourceFlags_PadLStick = 1 << 2 +}; + +enum ImGuiNavMoveFlags_ +{ + ImGuiNavMoveFlags_None = 0, + ImGuiNavMoveFlags_LoopX = 1 << 0, // On failed request, restart from opposite side + ImGuiNavMoveFlags_LoopY = 1 << 1, + ImGuiNavMoveFlags_WrapX = 1 << 2, // On failed request, request from opposite side one line down (when NavDir==right) or one line up (when NavDir==left) + ImGuiNavMoveFlags_WrapY = 1 << 3, // This is not super useful for provided for completeness + ImGuiNavMoveFlags_AllowCurrentNavId = 1 << 4, // Allow scoring and considering the current NavId as a move target candidate. This is used when the move source is offset (e.g. pressing PageDown actually needs to send a Up move request, if we are pressing PageDown from the bottom-most item we need to stay in place) + ImGuiNavMoveFlags_AlsoScoreVisibleSet = 1 << 5, // Store alternate result in NavMoveResultLocalVisibleSet that only comprise elements that are already fully visible. + ImGuiNavMoveFlags_ScrollToEdge = 1 << 6 +}; + +enum ImGuiNavForward +{ + ImGuiNavForward_None, + ImGuiNavForward_ForwardQueued, + ImGuiNavForward_ForwardActive +}; + +enum ImGuiNavLayer +{ + ImGuiNavLayer_Main = 0, // Main scrolling layer + ImGuiNavLayer_Menu = 1, // Menu layer (access with Alt/ImGuiNavInput_Menu) + ImGuiNavLayer_COUNT +}; + +enum ImGuiPopupPositionPolicy +{ + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox, + ImGuiPopupPositionPolicy_Tooltip +}; + +struct ImGuiDataTypeTempStorage +{ + ImU8 Data[8]; // Can fit any data up to ImGuiDataType_COUNT +}; + +// Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo(). +struct ImGuiDataTypeInfo +{ + size_t Size; // Size in bytes + const char* Name; // Short descriptive name for the type, for debugging + const char* PrintFmt; // Default printf format for the type + const char* ScanFmt; // Default scanf format for the type +}; + +// Extend ImGuiDataType_ +enum ImGuiDataTypePrivate_ +{ + ImGuiDataType_String = ImGuiDataType_COUNT + 1, + ImGuiDataType_Pointer, + ImGuiDataType_ID +}; + +// Stacked color modifier, backup of modified data so we can restore it +struct ImGuiColorMod +{ + ImGuiCol Col; + ImVec4 BackupValue; +}; + +// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. +struct ImGuiStyleMod +{ + ImGuiStyleVar VarIdx; + union { int BackupInt[2]; float BackupFloat[2]; }; + ImGuiStyleMod(ImGuiStyleVar idx, int v) { VarIdx = idx; BackupInt[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, float v) { VarIdx = idx; BackupFloat[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } +}; + +// Stacked storage data for BeginGroup()/EndGroup() +struct IMGUI_API ImGuiGroupData +{ + ImGuiID WindowID; + ImVec2 BackupCursorPos; + ImVec2 BackupCursorMaxPos; + ImVec1 BackupIndent; + ImVec1 BackupGroupOffset; + ImVec2 BackupCurrLineSize; + float BackupCurrLineTextBaseOffset; + ImGuiID BackupActiveIdIsAlive; + bool BackupActiveIdPreviousFrameIsAlive; + bool BackupHoveredIdIsAlive; + bool EmitItem; +}; + +// Simple column measurement, currently used for MenuItem() only.. This is very short-sighted/throw-away code and NOT a generic helper. +struct IMGUI_API ImGuiMenuColumns +{ + float Spacing; + float Width, NextWidth; + float Pos[3], NextWidths[3]; + + ImGuiMenuColumns() { memset(this, 0, sizeof(*this)); } + void Update(int count, float spacing, bool clear); + float DeclColumns(float w0, float w1, float w2); + float CalcExtraSpace(float avail_w) const; +}; + +// Internal state of the currently focused/edited text input box +// For a given item ID, access with ImGui::GetInputTextState() +struct IMGUI_API ImGuiInputTextState +{ + ImGuiID ID; // widget id owning the text state + int CurLenW, CurLenA; // we need to maintain our buffer length in both UTF-8 and wchar format. UTF-8 length is valid even if TextA is not. + ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + ImVector TextA; // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity. + ImVector InitialTextA; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + bool TextAIsValid; // temporary UTF8 buffer is not initially valid before we make the widget active (until then we pull the data from user argument) + int BufCapacityA; // end-user buffer capacity + float ScrollX; // horizontal scrolling/offset + ImStb::STB_TexteditState Stb; // state for stb_textedit.h + float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately + bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) + bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection + bool Edited; // edited this frame + ImGuiInputTextFlags Flags; // copy of InputText() flags + ImGuiInputTextCallback UserCallback; // " + void* UserCallbackData; // " + + ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } + void ClearText() { CurLenW = CurLenA = 0; TextW[0] = 0; TextA[0] = 0; CursorClamp(); } + void ClearFreeMemory() { TextW.clear(); TextA.clear(); InitialTextA.clear(); } + int GetUndoAvailCount() const { return Stb.undostate.undo_point; } + int GetRedoAvailCount() const { return STB_TEXTEDIT_UNDOSTATECOUNT - Stb.undostate.redo_point; } + void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation + + // Cursor & Selection + void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking + void CursorClamp() { Stb.cursor = ImMin(Stb.cursor, CurLenW); Stb.select_start = ImMin(Stb.select_start, CurLenW); Stb.select_end = ImMin(Stb.select_end, CurLenW); } + bool HasSelection() const { return Stb.select_start != Stb.select_end; } + void ClearSelection() { Stb.select_start = Stb.select_end = Stb.cursor; } + void SelectAll() { Stb.select_start = 0; Stb.cursor = Stb.select_end = CurLenW; Stb.has_preferred_x = 0; } +}; + +// Storage for current popup stack +struct ImGuiPopupData +{ + ImGuiID PopupId; // Set on OpenPopup() + ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow* SourceWindow; // Set on OpenPopup() copy of NavWindow at the time of opening the popup + int OpenFrameCount; // Set on OpenPopup() + ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) + ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) + ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup + + ImGuiPopupData() { memset(this, 0, sizeof(*this)); OpenFrameCount = -1; } +}; + +struct ImGuiNavItemData +{ + ImGuiWindow* Window; // Init,Move // Best candidate window (result->ItemWindow->RootWindowForNav == request->Window) + ImGuiID ID; // Init,Move // Best candidate item ID + ImGuiID FocusScopeId; // Init,Move // Best candidate focus scope ID + ImRect RectRel; // Init,Move // Best candidate bounding box in window relative space + float DistBox; // Move // Best candidate box distance to current NavId + float DistCenter; // Move // Best candidate center distance to current NavId + float DistAxial; // Move // Best candidate axial distance to current NavId + + ImGuiNavItemData() { Clear(); } + void Clear() { Window = NULL; ID = FocusScopeId = 0; RectRel = ImRect(); DistBox = DistCenter = DistAxial = FLT_MAX; } +}; + +enum ImGuiNextWindowDataFlags_ +{ + ImGuiNextWindowDataFlags_None = 0, + ImGuiNextWindowDataFlags_HasPos = 1 << 0, + ImGuiNextWindowDataFlags_HasSize = 1 << 1, + ImGuiNextWindowDataFlags_HasContentSize = 1 << 2, + ImGuiNextWindowDataFlags_HasCollapsed = 1 << 3, + ImGuiNextWindowDataFlags_HasSizeConstraint = 1 << 4, + ImGuiNextWindowDataFlags_HasFocus = 1 << 5, + ImGuiNextWindowDataFlags_HasBgAlpha = 1 << 6, + ImGuiNextWindowDataFlags_HasScroll = 1 << 7 +}; + +// Storage for SetNexWindow** functions +struct ImGuiNextWindowData +{ + ImGuiNextWindowDataFlags Flags; + ImGuiCond PosCond; + ImGuiCond SizeCond; + ImGuiCond CollapsedCond; + ImVec2 PosVal; + ImVec2 PosPivotVal; + ImVec2 SizeVal; + ImVec2 ContentSizeVal; + ImVec2 ScrollVal; + bool CollapsedVal; + ImRect SizeConstraintRect; + ImGuiSizeCallback SizeCallback; + void* SizeCallbackUserData; + float BgAlphaVal; // Override background alpha + ImVec2 MenuBarOffsetMinVal; // *Always on* This is not exposed publicly, so we don't clear it. + + ImGuiNextWindowData() { memset(this, 0, sizeof(*this)); } + inline void ClearFlags() { Flags = ImGuiNextWindowDataFlags_None; } +}; + +enum ImGuiNextItemDataFlags_ +{ + ImGuiNextItemDataFlags_None = 0, + ImGuiNextItemDataFlags_HasWidth = 1 << 0, + ImGuiNextItemDataFlags_HasOpen = 1 << 1 +}; + +struct ImGuiNextItemData +{ + ImGuiNextItemDataFlags Flags; + float Width; // Set by SetNextItemWidth() + ImGuiID FocusScopeId; // Set by SetNextItemMultiSelectData() (!= 0 signify value has been set, so it's an alternate version of HasSelectionData, we don't use Flags for this because they are cleared too early. This is mostly used for debugging) + ImGuiCond OpenCond; + bool OpenVal; // Set by SetNextItemOpen() + + ImGuiNextItemData() { memset(this, 0, sizeof(*this)); } + inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; } // Also cleared manually by ItemAdd()! +}; + +struct ImGuiShrinkWidthItem +{ + int Index; + float Width; +}; + +struct ImGuiPtrOrIndex +{ + void* Ptr; // Either field can be set, not both. e.g. Dock node tab bars are loose while BeginTabBar() ones are in a pool. + int Index; // Usually index in a main pool. + + ImGuiPtrOrIndex(void* ptr) { Ptr = ptr; Index = -1; } + ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Columns support +//----------------------------------------------------------------------------- + +// Flags for internal's BeginColumns(). Prefix using BeginTable() nowadays! +enum ImGuiOldColumnFlags_ +{ + ImGuiOldColumnFlags_None = 0, + ImGuiOldColumnFlags_NoBorder = 1 << 0, // Disable column dividers + ImGuiOldColumnFlags_NoResize = 1 << 1, // Disable resizing columns when clicking on the dividers + ImGuiOldColumnFlags_NoPreserveWidths = 1 << 2, // Disable column width preservation when adjusting columns + ImGuiOldColumnFlags_NoForceWithinWindow = 1 << 3, // Disable forcing columns to fit within window + ImGuiOldColumnFlags_GrowParentContentsSize = 1 << 4 // (WIP) Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove. + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiColumnsFlags_None = ImGuiOldColumnFlags_None, + ImGuiColumnsFlags_NoBorder = ImGuiOldColumnFlags_NoBorder, + ImGuiColumnsFlags_NoResize = ImGuiOldColumnFlags_NoResize, + ImGuiColumnsFlags_NoPreserveWidths = ImGuiOldColumnFlags_NoPreserveWidths, + ImGuiColumnsFlags_NoForceWithinWindow = ImGuiOldColumnFlags_NoForceWithinWindow, + ImGuiColumnsFlags_GrowParentContentsSize = ImGuiOldColumnFlags_GrowParentContentsSize +#endif +}; + +struct ImGuiOldColumnData +{ + float OffsetNorm; // Column start offset, normalized 0.0 (far left) -> 1.0 (far right) + float OffsetNormBeforeResize; + ImGuiOldColumnFlags Flags; // Not exposed + ImRect ClipRect; + + ImGuiOldColumnData() { memset(this, 0, sizeof(*this)); } +}; + +struct ImGuiOldColumns +{ + ImGuiID ID; + ImGuiOldColumnFlags Flags; + bool IsFirstFrame; + bool IsBeingResized; + int Current; + int Count; + float OffMinX, OffMaxX; // Offsets from HostWorkRect.Min.x + float LineMinY, LineMaxY; + float HostCursorPosY; // Backup of CursorPos at the time of BeginColumns() + float HostCursorMaxPosX; // Backup of CursorMaxPos at the time of BeginColumns() + ImRect HostInitialClipRect; // Backup of ClipRect at the time of BeginColumns() + ImRect HostBackupClipRect; // Backup of ClipRect during PushColumnsBackground()/PopColumnsBackground() + ImRect HostBackupParentWorkRect;//Backup of WorkRect at the time of BeginColumns() + ImVector Columns; + ImDrawListSplitter Splitter; + + ImGuiOldColumns() { memset(this, 0, sizeof(*this)); } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Multi-select support +//----------------------------------------------------------------------------- + +#ifdef IMGUI_HAS_MULTI_SELECT +// +#endif // #ifdef IMGUI_HAS_MULTI_SELECT + +//----------------------------------------------------------------------------- +// [SECTION] Docking support +//----------------------------------------------------------------------------- + +#ifdef IMGUI_HAS_DOCK +// +#endif // #ifdef IMGUI_HAS_DOCK + +//----------------------------------------------------------------------------- +// [SECTION] Viewport support +//----------------------------------------------------------------------------- + +// ImGuiViewport Private/Internals fields (cardinal sin: we are using inheritance!) +// Every instance of ImGuiViewport is in fact a ImGuiViewportP. +struct ImGuiViewportP : public ImGuiViewport +{ + int DrawListsLastFrame[2]; // Last frame number the background (0) and foreground (1) draw lists were used + ImDrawList* DrawLists[2]; // Convenience background (0) and foreground (1) draw lists. We use them to draw software mouser cursor when io.MouseDrawCursor is set and to draw most debug overlays. + ImDrawData DrawDataP; + ImDrawDataBuilder DrawDataBuilder; + + ImVec2 WorkOffsetMin; // Work Area: Offset from Pos to top-left corner of Work Area. Generally (0,0) or (0,+main_menu_bar_height). Work Area is Full Area but without menu-bars/status-bars (so WorkArea always fit inside Pos/Size!) + ImVec2 WorkOffsetMax; // Work Area: Offset from Pos+Size to bottom-right corner of Work Area. Generally (0,0) or (0,-status_bar_height). + ImVec2 BuildWorkOffsetMin; // Work Area: Offset being built during current frame. Generally >= 0.0f. + ImVec2 BuildWorkOffsetMax; // Work Area: Offset being built during current frame. Generally <= 0.0f. + + ImGuiViewportP() { DrawListsLastFrame[0] = DrawListsLastFrame[1] = -1; DrawLists[0] = DrawLists[1] = NULL; } + ~ImGuiViewportP() { if (DrawLists[0]) IM_DELETE(DrawLists[0]); if (DrawLists[1]) IM_DELETE(DrawLists[1]); } + + // Calculate work rect pos/size given a set of offset (we have 1 pair of offset for rect locked from last frame data, and 1 pair for currently building rect) + ImVec2 CalcWorkRectPos(const ImVec2& off_min) const { return ImVec2(Pos.x + off_min.x, Pos.y + off_min.y); } + ImVec2 CalcWorkRectSize(const ImVec2& off_min, const ImVec2& off_max) const { return ImVec2(ImMax(0.0f, Size.x - off_min.x + off_max.x), ImMax(0.0f, Size.y - off_min.y + off_max.y)); } + void UpdateWorkRect() { WorkPos = CalcWorkRectPos(WorkOffsetMin); WorkSize = CalcWorkRectSize(WorkOffsetMin, WorkOffsetMax); } // Update public fields + + // Helpers to retrieve ImRect (we don't need to store BuildWorkRect as every access tend to change it, hence the code asymmetry) + ImRect GetMainRect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } + ImRect GetWorkRect() const { return ImRect(WorkPos.x, WorkPos.y, WorkPos.x + WorkSize.x, WorkPos.y + WorkSize.y); } + ImRect GetBuildWorkRect() const { ImVec2 pos = CalcWorkRectPos(BuildWorkOffsetMin); ImVec2 size = CalcWorkRectSize(BuildWorkOffsetMin, BuildWorkOffsetMax); return ImRect(pos.x, pos.y, pos.x + size.x, pos.y + size.y); } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Settings support +//----------------------------------------------------------------------------- + +// Windows data saved in imgui.ini file +// Because we never destroy or rename ImGuiWindowSettings, we can store the names in a separate buffer easily. +// (this is designed to be stored in a ImChunkStream buffer, with the variable-length Name following our structure) +struct ImGuiWindowSettings +{ + ImGuiID ID; + ImVec2ih Pos; + ImVec2ih Size; + bool Collapsed; + bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) + + ImGuiWindowSettings() { memset(this, 0, sizeof(*this)); } + char* GetName() { return (char*)(this + 1); } +}; + +struct ImGuiSettingsHandler +{ + const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' + ImGuiID TypeHash; // == ImHashStr(TypeName) + void (*ClearAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler); // Clear all settings data + void (*ReadInitFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler); // Read: Called before reading (in registration order) + void* (*ReadOpenFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name); // Read: Called when entering into a new ini entry e.g. "[Window][Name]" + void (*ReadLineFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line); // Read: Called for every line of text within an ini entry + void (*ApplyAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler); // Read: Called after reading (in registration order) + void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf); // Write: Output every entries into 'out_buf' + void* UserData; + + ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Metrics, Debug +//----------------------------------------------------------------------------- + +struct ImGuiMetricsConfig +{ + bool ShowWindowsRects; + bool ShowWindowsBeginOrder; + bool ShowTablesRects; + bool ShowDrawCmdMesh; + bool ShowDrawCmdBoundingBoxes; + int ShowWindowsRectsType; + int ShowTablesRectsType; + + ImGuiMetricsConfig() + { + ShowWindowsRects = false; + ShowWindowsBeginOrder = false; + ShowTablesRects = false; + ShowDrawCmdMesh = true; + ShowDrawCmdBoundingBoxes = true; + ShowWindowsRectsType = -1; + ShowTablesRectsType = -1; + } +}; + +struct IMGUI_API ImGuiStackSizes +{ + short SizeOfIDStack; + short SizeOfColorStack; + short SizeOfStyleVarStack; + short SizeOfFontStack; + short SizeOfFocusScopeStack; + short SizeOfGroupStack; + short SizeOfBeginPopupStack; + + ImGuiStackSizes() { memset(this, 0, sizeof(*this)); } + void SetToCurrentState(); + void CompareWithCurrentState(); +}; + +//----------------------------------------------------------------------------- +// [SECTION] Generic context hooks +//----------------------------------------------------------------------------- + +typedef void (*ImGuiContextHookCallback)(ImGuiContext* ctx, ImGuiContextHook* hook); +enum ImGuiContextHookType { ImGuiContextHookType_NewFramePre, ImGuiContextHookType_NewFramePost, ImGuiContextHookType_EndFramePre, ImGuiContextHookType_EndFramePost, ImGuiContextHookType_RenderPre, ImGuiContextHookType_RenderPost, ImGuiContextHookType_Shutdown, ImGuiContextHookType_PendingRemoval_ }; + +struct ImGuiContextHook +{ + ImGuiID HookId; // A unique ID assigned by AddContextHook() + ImGuiContextHookType Type; + ImGuiID Owner; + ImGuiContextHookCallback Callback; + void* UserData; + + ImGuiContextHook() { memset(this, 0, sizeof(*this)); } +}; + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiContext (main imgui context) +//----------------------------------------------------------------------------- + +struct ImGuiContext +{ + bool Initialized; + bool FontAtlasOwnedByContext; // IO.Fonts-> is owned by the ImGuiContext and will be destructed along with it. + ImGuiIO IO; + ImGuiStyle Style; + ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() + float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. + float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. + ImDrawListSharedData DrawListSharedData; + double Time; + int FrameCount; + int FrameCountEnded; + int FrameCountRendered; + bool WithinFrameScope; // Set by NewFrame(), cleared by EndFrame() + bool WithinFrameScopeWithImplicitWindow; // Set by NewFrame(), cleared by EndFrame() when the implicit debug window has been pushed + bool WithinEndChild; // Set within EndChild() + bool GcCompactAll; // Request full GC + bool TestEngineHookItems; // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log() + ImGuiID TestEngineHookIdInfo; // Will call test engine hooks: ImGuiTestEngineHook_IdInfo() from GetID() + void* TestEngine; // Test engine user data + + // Windows state + ImVector Windows; // Windows, sorted in display order, back to front + ImVector WindowsFocusOrder; // Root windows, sorted in focus order, back to front. + ImVector WindowsTempSortBuffer; // Temporary buffer used in EndFrame() to reorder windows so parents are kept before their child + ImVector CurrentWindowStack; + ImGuiStorage WindowsById; // Map window's ImGuiID to ImGuiWindow* + int WindowsActiveCount; // Number of unique windows submitted by frame + ImVec2 WindowsHoverPadding; // Padding around resizable windows for which hovering on counts as hovering the window == ImMax(style.TouchExtraPadding, WINDOWS_HOVER_PADDING) + ImGuiWindow* CurrentWindow; // Window being drawn into + ImGuiWindow* HoveredWindow; // Window the mouse is hovering. Will typically catch mouse inputs. + ImGuiWindow* HoveredWindowUnderMovingWindow; // Hovered window ignoring MovingWindow. Only set if MovingWindow is set. + ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindow. + ImGuiWindow* WheelingWindow; // Track the window we started mouse-wheeling on. Until a timer elapse or mouse has moved, generally keep scrolling the same window even if during the course of scrolling the mouse ends up hovering a child window. + ImVec2 WheelingWindowRefMousePos; + float WheelingWindowTimer; + + // Item/widgets state and tracking information + ImGuiItemFlags CurrentItemFlags; // == g.ItemFlagsStack.back() + ImGuiID HoveredId; // Hovered widget, filled during the frame + ImGuiID HoveredIdPreviousFrame; + bool HoveredIdAllowOverlap; + bool HoveredIdUsingMouseWheel; // Hovered widget will use mouse wheel. Blocks scrolling the underlying window. + bool HoveredIdPreviousFrameUsingMouseWheel; + bool HoveredIdDisabled; // At least one widget passed the rect test, but has been discarded by disabled flag or popup inhibit. May be true even if HoveredId == 0. + float HoveredIdTimer; // Measure contiguous hovering time + float HoveredIdNotActiveTimer; // Measure contiguous hovering time where the item has not been active + ImGuiID ActiveId; // Active widget + ImGuiID ActiveIdIsAlive; // Active widget has been seen this frame (we can't use a bool as the ActiveId may change within the frame) + float ActiveIdTimer; + bool ActiveIdIsJustActivated; // Set at the time of activation for one frame + bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) + bool ActiveIdNoClearOnFocusLoss; // Disable losing active id if the active id window gets unfocused. + bool ActiveIdHasBeenPressedBefore; // Track whether the active id led to a press (this is to allow changing between PressOnClick and PressOnRelease without pressing twice). Used by range_select branch. + bool ActiveIdHasBeenEditedBefore; // Was the value associated to the widget Edited over the course of the Active state. + bool ActiveIdHasBeenEditedThisFrame; + bool ActiveIdUsingMouseWheel; // Active widget will want to read mouse wheel. Blocks scrolling the underlying window. + ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it) + ImU32 ActiveIdUsingNavInputMask; // Active widget will want to read those nav inputs. + ImU64 ActiveIdUsingKeyInputMask; // Active widget will want to read those key inputs. When we grow the ImGuiKey enum we'll need to either to order the enum to make useful keys come first, either redesign this into e.g. a small array. + ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) + ImGuiWindow* ActiveIdWindow; + ImGuiInputSource ActiveIdSource; // Activating with mouse or nav (gamepad/keyboard) + int ActiveIdMouseButton; + ImGuiID ActiveIdPreviousFrame; + bool ActiveIdPreviousFrameIsAlive; + bool ActiveIdPreviousFrameHasBeenEditedBefore; + ImGuiWindow* ActiveIdPreviousFrameWindow; + ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. + float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. + + // Next window/item data + ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions + ImGuiNextItemData NextItemData; // Storage for SetNextItem** functions + + // Shared stacks + ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() + ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() + ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() + ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - not inherited by Begin(), unless child window + ImVectorItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() + ImVectorGroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() + ImVectorOpenPopupStack; // Which popups are open (persistent) + ImVectorBeginPopupStack; // Which level of BeginPopup() we are in (reset every frame) + + // Viewports + ImVector Viewports; // Active viewports (Size==1 in 'master' branch). Each viewports hold their copy of ImDrawData. + + // Gamepad/keyboard Navigation + ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusWindow' + ImGuiID NavId; // Focused item for navigation + ImGuiID NavFocusScopeId; // Identify a selection scope (selection code often wants to "clear other items" when landing on an item of the selection set) + ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0, also set when calling ActivateItem() + ImGuiID NavActivateDownId; // ~~ IsNavInputDown(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavActivatePressedId; // ~~ IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0 + ImGuiID NavInputId; // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? NavId : 0 + ImGuiID NavJustTabbedId; // Just tabbed to this id. + ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). + ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). + ImGuiKeyModFlags NavJustMovedToKeyMods; + ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. + ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS WILL ONLY BE None or NavGamepad or NavKeyboard. + ImRect NavScoringRect; // Rectangle used for scoring, in screen space. Based of window->NavRectRel[], modified for directional navigation scoring. + int NavScoringCount; // Metrics for debugging + ImGuiNavLayer NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later. + int NavIdTabCounter; // == NavWindow->DC.FocusIdxTabCounter at time of NavId processing + bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRectRel is valid + bool NavMousePosDirty; // When set we will update mouse position if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) if set (NB: this not enabled by default) + bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (NB: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover) + bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again. + bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest + bool NavInitRequest; // Init request for appearing window to select first item + bool NavInitRequestFromMove; + ImGuiID NavInitResultId; // Init request result (first item of the window, or one for which SetItemDefaultFocus() was called) + ImRect NavInitResultRectRel; // Init request result rectangle (relative to parent window) + bool NavMoveRequest; // Move request for this frame + ImGuiNavMoveFlags NavMoveRequestFlags; + ImGuiNavForward NavMoveRequestForward; // None / ForwardQueued / ForwardActive (this is used to navigate sibling parent menus from a child menu) + ImGuiKeyModFlags NavMoveRequestKeyMods; + ImGuiDir NavMoveDir, NavMoveDirLast; // Direction of the move request (left/right/up/down), direction of the previous move request + ImGuiDir NavMoveClipDir; // FIXME-NAV: Describe the purpose of this better. Might want to rename? + ImGuiNavItemData NavMoveResultLocal; // Best move request candidate within NavWindow + ImGuiNavItemData NavMoveResultLocalVisibleSet; // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag) + ImGuiNavItemData NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag) + ImGuiWindow* NavWrapRequestWindow; // Window which requested trying nav wrap-around. + ImGuiNavMoveFlags NavWrapRequestFlags; // Wrap-around operation flags. + + // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) + ImGuiWindow* NavWindowingTarget; // Target window when doing CTRL+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! + ImGuiWindow* NavWindowingTargetAnim; // Record of last valid NavWindowingTarget until DimBgRatio and NavWindowingHighlightAlpha becomes 0.0f, so the fade-out can stay on it. + ImGuiWindow* NavWindowingListWindow; // Internal window actually listing the CTRL+Tab contents + float NavWindowingTimer; + float NavWindowingHighlightAlpha; + bool NavWindowingToggleLayer; + + // Legacy Focus/Tabbing system (older than Nav, active even if Nav is disabled, misnamed. FIXME-NAV: This needs a redesign!) + ImGuiWindow* TabFocusRequestCurrWindow; // + ImGuiWindow* TabFocusRequestNextWindow; // + int TabFocusRequestCurrCounterRegular; // Any item being requested for focus, stored as an index (we on layout to be stable between the frame pressing TAB and the next frame, semi-ouch) + int TabFocusRequestCurrCounterTabStop; // Tab item being requested for focus, stored as an index + int TabFocusRequestNextCounterRegular; // Stored for next frame + int TabFocusRequestNextCounterTabStop; // " + bool TabFocusPressed; // Set in NewFrame() when user pressed Tab + + // Render + float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list) + ImGuiMouseCursor MouseCursor; + + // Drag and Drop + bool DragDropActive; + bool DragDropWithinSource; // Set when within a BeginDragDropXXX/EndDragDropXXX block for a drag source. + bool DragDropWithinTarget; // Set when within a BeginDragDropXXX/EndDragDropXXX block for a drag target. + ImGuiDragDropFlags DragDropSourceFlags; + int DragDropSourceFrameCount; + int DragDropMouseButton; + ImGuiPayload DragDropPayload; + ImRect DragDropTargetRect; // Store rectangle of current target candidate (we favor small targets when overlapping) + ImGuiID DragDropTargetId; + ImGuiDragDropFlags DragDropAcceptFlags; + float DragDropAcceptIdCurrRectSurface; // Target item surface (we resolve overlapping targets by prioritizing the smaller surface) + ImGuiID DragDropAcceptIdCurr; // Target item id (set at the time of accepting the payload) + ImGuiID DragDropAcceptIdPrev; // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets) + int DragDropAcceptFrameCount; // Last time a target expressed a desire to accept the source + ImGuiID DragDropHoldJustPressedId; // Set when holding a payload just made ButtonBehavior() return a press. + ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly, ImGuiPayload only holds pointer+size + unsigned char DragDropPayloadBufLocal[16]; // Local buffer for small payloads + + // Table + ImGuiTable* CurrentTable; + int CurrentTableStackIdx; + ImPool Tables; + ImVector TablesTempDataStack; + ImVector TablesLastTimeActive; // Last used timestamp of each tables (SOA, for efficient GC) + ImVector DrawChannelsTempMergeBuffer; + + // Tab bars + ImGuiTabBar* CurrentTabBar; + ImPool TabBars; + ImVector CurrentTabBarStack; + ImVector ShrinkWidthBuffer; + + // Widget state + ImVec2 LastValidMousePos; + ImGuiInputTextState InputTextState; + ImFont InputTextPasswordFont; + ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. + ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets + float ColorEditLastHue; // Backup of last Hue associated to LastColor[3], so we can restore Hue in lossy RGB<>HSV round trips + float ColorEditLastSat; // Backup of last Saturation associated to LastColor[3], so we can restore Saturation in lossy RGB<>HSV round trips + float ColorEditLastColor[3]; + ImVec4 ColorPickerRef; // Initial/reference color at the time of opening the color picker. + float SliderCurrentAccum; // Accumulated slider delta when using navigation controls. + bool SliderCurrentAccumDirty; // Has the accumulated slider delta changed since last time we tried to apply it? + bool DragCurrentAccumDirty; + float DragCurrentAccum; // Accumulator for dragging modification. Always high-precision, not rounded by end-user precision settings + float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio + float ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? + int TooltipOverrideCount; + float TooltipSlowDelay; // Time before slow tooltips appears (FIXME: This is temporary until we merge in tooltip timer+priority work) + ImVector ClipboardHandlerData; // If no custom clipboard handler is defined + ImVector MenusIdSubmittedThisFrame; // A list of menu IDs that were rendered at least once + + // Platform support + ImVec2 PlatformImePos; // Cursor position request & last passed to the OS Input Method Editor + ImVec2 PlatformImeLastPos; + char PlatformLocaleDecimalPoint; // '.' or *localeconv()->decimal_point + + // Settings + bool SettingsLoaded; + float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero + ImGuiTextBuffer SettingsIniData; // In memory .ini settings + ImVector SettingsHandlers; // List of .ini settings handlers + ImChunkStream SettingsWindows; // ImGuiWindow .ini settings entries + ImChunkStream SettingsTables; // ImGuiTable .ini settings entries + ImVector Hooks; // Hooks for extensions (e.g. test engine) + ImGuiID HookIdNext; // Next available HookId + + // Capture/Logging + bool LogEnabled; // Currently capturing + ImGuiLogType LogType; // Capture target + ImFileHandle LogFile; // If != NULL log to stdout/ file + ImGuiTextBuffer LogBuffer; // Accumulation buffer when log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. + const char* LogNextPrefix; + const char* LogNextSuffix; + float LogLinePosY; + bool LogLineFirstItem; + int LogDepthRef; + int LogDepthToExpand; + int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call. + + // Debug Tools + bool DebugItemPickerActive; // Item picker is active (started with DebugStartItemPicker()) + ImGuiID DebugItemPickerBreakId; // Will call IM_DEBUG_BREAK() when encountering this id + ImGuiMetricsConfig DebugMetricsConfig; + + // Misc + float FramerateSecPerFrame[120]; // Calculate estimate of framerate for user over the last 2 seconds. + int FramerateSecPerFrameIdx; + int FramerateSecPerFrameCount; + float FramerateSecPerFrameAccum; + int WantCaptureMouseNextFrame; // Explicit capture via CaptureKeyboardFromApp()/CaptureMouseFromApp() sets those flags + int WantCaptureKeyboardNextFrame; + int WantTextInputNextFrame; + char TempBuffer[1024 * 3 + 1]; // Temporary text buffer + + ImGuiContext(ImFontAtlas* shared_font_atlas) + { + Initialized = false; + FontAtlasOwnedByContext = shared_font_atlas ? false : true; + Font = NULL; + FontSize = FontBaseSize = 0.0f; + IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + Time = 0.0f; + FrameCount = 0; + FrameCountEnded = FrameCountRendered = -1; + WithinFrameScope = WithinFrameScopeWithImplicitWindow = WithinEndChild = false; + GcCompactAll = false; + TestEngineHookItems = false; + TestEngineHookIdInfo = 0; + TestEngine = NULL; + + WindowsActiveCount = 0; + CurrentWindow = NULL; + HoveredWindow = NULL; + HoveredWindowUnderMovingWindow = NULL; + MovingWindow = NULL; + WheelingWindow = NULL; + WheelingWindowTimer = 0.0f; + + CurrentItemFlags = ImGuiItemFlags_None; + HoveredId = HoveredIdPreviousFrame = 0; + HoveredIdAllowOverlap = false; + HoveredIdUsingMouseWheel = HoveredIdPreviousFrameUsingMouseWheel = false; + HoveredIdDisabled = false; + HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; + ActiveId = 0; + ActiveIdIsAlive = 0; + ActiveIdTimer = 0.0f; + ActiveIdIsJustActivated = false; + ActiveIdAllowOverlap = false; + ActiveIdNoClearOnFocusLoss = false; + ActiveIdHasBeenPressedBefore = false; + ActiveIdHasBeenEditedBefore = false; + ActiveIdHasBeenEditedThisFrame = false; + ActiveIdUsingMouseWheel = false; + ActiveIdUsingNavDirMask = 0x00; + ActiveIdUsingNavInputMask = 0x00; + ActiveIdUsingKeyInputMask = 0x00; + ActiveIdClickOffset = ImVec2(-1, -1); + ActiveIdWindow = NULL; + ActiveIdSource = ImGuiInputSource_None; + ActiveIdMouseButton = -1; + ActiveIdPreviousFrame = 0; + ActiveIdPreviousFrameIsAlive = false; + ActiveIdPreviousFrameHasBeenEditedBefore = false; + ActiveIdPreviousFrameWindow = NULL; + LastActiveId = 0; + LastActiveIdTimer = 0.0f; + + NavWindow = NULL; + NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; + NavJustTabbedId = NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; + NavJustMovedToKeyMods = ImGuiKeyModFlags_None; + NavInputSource = ImGuiInputSource_None; + NavScoringRect = ImRect(); + NavScoringCount = 0; + NavLayer = ImGuiNavLayer_Main; + NavIdTabCounter = INT_MAX; + NavIdIsAlive = false; + NavMousePosDirty = false; + NavDisableHighlight = true; + NavDisableMouseHover = false; + NavAnyRequest = false; + NavInitRequest = false; + NavInitRequestFromMove = false; + NavInitResultId = 0; + NavMoveRequest = false; + NavMoveRequestFlags = ImGuiNavMoveFlags_None; + NavMoveRequestForward = ImGuiNavForward_None; + NavMoveRequestKeyMods = ImGuiKeyModFlags_None; + NavMoveDir = NavMoveDirLast = NavMoveClipDir = ImGuiDir_None; + NavWrapRequestWindow = NULL; + NavWrapRequestFlags = ImGuiNavMoveFlags_None; + + NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; + NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; + NavWindowingToggleLayer = false; + + TabFocusRequestCurrWindow = TabFocusRequestNextWindow = NULL; + TabFocusRequestCurrCounterRegular = TabFocusRequestCurrCounterTabStop = INT_MAX; + TabFocusRequestNextCounterRegular = TabFocusRequestNextCounterTabStop = INT_MAX; + TabFocusPressed = false; + + DimBgRatio = 0.0f; + MouseCursor = ImGuiMouseCursor_Arrow; + + DragDropActive = DragDropWithinSource = DragDropWithinTarget = false; + DragDropSourceFlags = ImGuiDragDropFlags_None; + DragDropSourceFrameCount = -1; + DragDropMouseButton = -1; + DragDropTargetId = 0; + DragDropAcceptFlags = ImGuiDragDropFlags_None; + DragDropAcceptIdCurrRectSurface = 0.0f; + DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; + DragDropAcceptFrameCount = -1; + DragDropHoldJustPressedId = 0; + memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + + CurrentTable = NULL; + CurrentTableStackIdx = -1; + CurrentTabBar = NULL; + + LastValidMousePos = ImVec2(0.0f, 0.0f); + TempInputId = 0; + ColorEditOptions = ImGuiColorEditFlags__OptionsDefault; + ColorEditLastHue = ColorEditLastSat = 0.0f; + ColorEditLastColor[0] = ColorEditLastColor[1] = ColorEditLastColor[2] = FLT_MAX; + SliderCurrentAccum = 0.0f; + SliderCurrentAccumDirty = false; + DragCurrentAccumDirty = false; + DragCurrentAccum = 0.0f; + DragSpeedDefaultRatio = 1.0f / 100.0f; + ScrollbarClickDeltaToGrabCenter = 0.0f; + TooltipOverrideCount = 0; + TooltipSlowDelay = 0.50f; + + PlatformImePos = PlatformImeLastPos = ImVec2(FLT_MAX, FLT_MAX); + PlatformLocaleDecimalPoint = '.'; + + SettingsLoaded = false; + SettingsDirtyTimer = 0.0f; + HookIdNext = 0; + + LogEnabled = false; + LogType = ImGuiLogType_None; + LogNextPrefix = LogNextSuffix = NULL; + LogFile = NULL; + LogLinePosY = FLT_MAX; + LogLineFirstItem = false; + LogDepthRef = 0; + LogDepthToExpand = LogDepthToExpandDefault = 2; + + DebugItemPickerActive = false; + DebugItemPickerBreakId = 0; + + memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); + FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0; + FramerateSecPerFrameAccum = 0.0f; + WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + memset(TempBuffer, 0, sizeof(TempBuffer)); + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiWindowTempData, ImGuiWindow +//----------------------------------------------------------------------------- + +// Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow. +// (That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered..) +// (This doesn't need a constructor because we zero-clear it as part of ImGuiWindow and all frame-temporary data are setup on Begin) +struct IMGUI_API ImGuiWindowTempData +{ + // Layout + ImVec2 CursorPos; // Current emitting position, in absolute coordinates. + ImVec2 CursorPosPrevLine; + ImVec2 CursorStartPos; // Initial position after Begin(), generally ~ window position + WindowPadding. + ImVec2 CursorMaxPos; // Used to implicitly calculate ContentSize at the beginning of next frame, for scrolling range and auto-resize. Always growing during the frame. + ImVec2 IdealMaxPos; // Used to implicitly calculate ContentSizeIdeal at the beginning of next frame, for auto-resize only. Always growing during the frame. + ImVec2 CurrLineSize; + ImVec2 PrevLineSize; + float CurrLineTextBaseOffset; // Baseline offset (0.0f by default on a new line, generally == style.FramePadding.y when a framed item has been added). + float PrevLineTextBaseOffset; + ImVec1 Indent; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) + ImVec1 ColumnsOffset; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. + ImVec1 GroupOffset; + + // Last item status + ImGuiID LastItemId; // ID for last item + ImGuiItemStatusFlags LastItemStatusFlags; // Status flags for last item (see ImGuiItemStatusFlags_) + ImRect LastItemRect; // Interaction rect for last item + ImRect LastItemDisplayRect; // End-user display rect for last item (only valid if LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) + + // Keyboard/Gamepad navigation + ImGuiNavLayer NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) + short NavLayersActiveMask; // Which layers have been written to (result from previous frame) + short NavLayersActiveMaskNext;// Which layers have been written to (accumulator for current frame) + ImGuiID NavFocusScopeIdCurrent; // Current focus scope ID while appending + bool NavHideHighlightOneFrame; + bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) + + // Miscellaneous + bool MenuBarAppending; // FIXME: Remove this + ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs. + ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement + int TreeDepth; // Current tree depth. + ImU32 TreeJumpToParentOnPopMask; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31.. Could be turned into a ImU64 if necessary. + ImVector ChildWindows; + ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) + ImGuiOldColumns* CurrentColumns; // Current columns set + int CurrentTableIdx; // Current table index (into g.Tables) + ImGuiLayoutType LayoutType; + ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() + int FocusCounterRegular; // (Legacy Focus/Tabbing system) Sequential counter, start at -1 and increase as assigned via FocusableItemRegister() (FIXME-NAV: Needs redesign) + int FocusCounterTabStop; // (Legacy Focus/Tabbing system) Same, but only count widgets which you can Tab through. + + // Local parameters stacks + // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. + float ItemWidth; // Current item width (>0.0: width in pixels, <0.0: align xx pixels to the right of window). + float TextWrapPos; // Current text wrap pos. + ImVector ItemWidthStack; // Store item widths to restore (attention: .back() is not == ItemWidth) + ImVector TextWrapPosStack; // Store text wrap pos to restore (attention: .back() is not == TextWrapPos) + ImGuiStackSizes StackSizesOnBegin; // Store size of various stacks for asserting +}; + +// Storage for one window +struct IMGUI_API ImGuiWindow +{ + char* Name; // Window name, owned by the window. + ImGuiID ID; // == ImHashStr(Name) + ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ + ImVec2 Pos; // Position (always rounded-up to nearest pixel) + ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) + ImVec2 SizeFull; // Size when non collapsed + ImVec2 ContentSize; // Size of contents/scrollable client area (calculated from the extents reach of the cursor) from previous frame. Does not include window decoration or window padding. + ImVec2 ContentSizeIdeal; + ImVec2 ContentSizeExplicit; // Size of contents/scrollable client area explicitly request by the user via SetNextWindowContentSize(). + ImVec2 WindowPadding; // Window padding at the time of Begin(). + float WindowRounding; // Window rounding at the time of Begin(). May be clamped lower to avoid rendering artifacts with title bar, menu bar etc. + float WindowBorderSize; // Window border size at the time of Begin(). + int NameBufLen; // Size of buffer storing Name. May be larger than strlen(Name)! + ImGuiID MoveId; // == window->GetID("#MOVE") + ImGuiID ChildId; // ID of corresponding item in parent window (for navigation to return from child window to parent window) + ImVec2 Scroll; + ImVec2 ScrollMax; + ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change) + ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered + ImVec2 ScrollTargetEdgeSnapDist; // 0.0f = no snapping, >0.0f snapping threshold + ImVec2 ScrollbarSizes; // Size taken by each scrollbars on their smaller axis. Pay attention! ScrollbarSizes.x == width of the vertical scrollbar, ScrollbarSizes.y = height of the horizontal scrollbar. + bool ScrollbarX, ScrollbarY; // Are scrollbars visible? + bool Active; // Set to true on Begin(), unless Collapsed + bool WasActive; + bool WriteAccessed; // Set to true when any widget access the current window + bool Collapsed; // Set when collapsing window to become only title-bar + bool WantCollapseToggle; + bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) + bool Appearing; // Set during the frame where the window is appearing (or re-appearing) + bool Hidden; // Do not display (== HiddenFrames*** > 0) + bool IsFallbackWindow; // Set on the "Debug##Default" window. + bool HasCloseButton; // Set when the window has a close button (p_open != NULL) + signed char ResizeBorderHeld; // Current border being held for resize (-1: none, otherwise 0-3) + short BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + short BeginOrderWithinParent; // Begin() order within immediate parent window, if we are a child window. Otherwise 0. + short BeginOrderWithinContext; // Begin() order within entire imgui context. This is mostly used for debugging submission order related issues. + short FocusOrder; // Order within WindowsFocusOrder[], altered when windows are focused. + ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) + ImS8 AutoFitFramesX, AutoFitFramesY; + ImS8 AutoFitChildAxises; + bool AutoFitOnlyGrows; + ImGuiDir AutoPosLastDirection; + ImS8 HiddenFramesCanSkipItems; // Hide the window for N frames + ImS8 HiddenFramesCannotSkipItems; // Hide the window for N frames while allowing items to be submitted so we can measure their size + ImS8 HiddenFramesForRenderOnly; // Hide the window until frame N at Render() time only + ImS8 DisableInputsFrames; // Disable window interactions for N frames + ImGuiCond SetWindowPosAllowFlags : 8; // store acceptable condition flags for SetNextWindowPos() use. + ImGuiCond SetWindowSizeAllowFlags : 8; // store acceptable condition flags for SetNextWindowSize() use. + ImGuiCond SetWindowCollapsedAllowFlags : 8; // store acceptable condition flags for SetNextWindowCollapsed() use. + ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) + ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0, 0) when positioning from top-left corner; ImVec2(0.5f, 0.5f) for centering; ImVec2(1, 1) for bottom right. + + ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack. (In theory this should be in the TempData structure) + ImGuiWindowTempData DC; // Temporary per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the "DC" variable name. + + // The best way to understand what those rectangles are is to use the 'Metrics->Tools->Show Windows Rectangles' viewer. + // The main 'OuterRect', omitted as a field, is window->Rect(). + ImRect OuterRectClipped; // == Window->Rect() just after setup in Begin(). == window->Rect() for root window. + ImRect InnerRect; // Inner rectangle (omit title bar, menu bar, scroll bar) + ImRect InnerClipRect; // == InnerRect shrunk by WindowPadding*0.5f on each side, clipped within viewport or parent clip rect. + ImRect WorkRect; // Initially covers the whole scrolling region. Reduced by containers e.g columns/tables when active. Shrunk by WindowPadding*1.0f on each side. This is meant to replace ContentRegionRect over time (from 1.71+ onward). + ImRect ParentWorkRect; // Backup of WorkRect before entering a container such as columns/tables. Used by e.g. SpanAllColumns functions to easily access. Stacked containers are responsible for maintaining this. // FIXME-WORKRECT: Could be a stack? + ImRect ClipRect; // Current clipping/scissoring rectangle, evolve as we are using PushClipRect(), etc. == DrawList->clip_rect_stack.back(). + ImRect ContentRegionRect; // FIXME: This is currently confusing/misleading. It is essentially WorkRect but not handling of scrolling. We currently rely on it as right/bottom aligned sizing operation need some size to rely on. + ImVec2ih HitTestHoleSize; // Define an optional rectangular hole where mouse will pass-through the window. + ImVec2ih HitTestHoleOffset; + + int LastFrameActive; // Last frame number the window was Active. + float LastTimeActive; // Last timestamp the window was Active (using float as we don't need high precision there) + float ItemWidthDefault; + ImGuiStorage StateStorage; + ImVector ColumnsStorage; + float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale() + int SettingsOffset; // Offset into SettingsWindows[] (offsets are always valid as we only grow the array from the back) + + ImDrawList* DrawList; // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer) + ImDrawList DrawListInst; + ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL. + ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window == Top-level window. + ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. + ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. + + ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) + ImGuiID NavLastIds[ImGuiNavLayer_COUNT]; // Last known NavId for this window, per layer (0/1) + ImRect NavRectRel[ImGuiNavLayer_COUNT]; // Reference rectangle, in window relative space + + int MemoryDrawListIdxCapacity; // Backup of last idx/vtx count, so when waking up the window we can preallocate and avoid iterative alloc/copy + int MemoryDrawListVtxCapacity; + bool MemoryCompacted; // Set when window extraneous data have been garbage collected + +public: + ImGuiWindow(ImGuiContext* context, const char* name); + ~ImGuiWindow(); + + ImGuiID GetID(const char* str, const char* str_end = NULL); + ImGuiID GetID(const void* ptr); + ImGuiID GetID(int n); + ImGuiID GetIDNoKeepAlive(const char* str, const char* str_end = NULL); + ImGuiID GetIDNoKeepAlive(const void* ptr); + ImGuiID GetIDNoKeepAlive(int n); + ImGuiID GetIDFromRectangle(const ImRect& r_abs); + + // We don't use g.FontSize because the window may be != g.CurrentWidow. + ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } + float CalcFontSize() const { ImGuiContext& g = *GImGui; float scale = g.FontBaseSize * FontWindowScale; if (ParentWindow) scale *= ParentWindow->FontWindowScale; return scale; } + float TitleBarHeight() const { ImGuiContext& g = *GImGui; return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + g.Style.FramePadding.y * 2.0f; } + ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); } + float MenuBarHeight() const { ImGuiContext& g = *GImGui; return (Flags & ImGuiWindowFlags_MenuBar) ? DC.MenuBarOffset.y + CalcFontSize() + g.Style.FramePadding.y * 2.0f : 0.0f; } + ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } +}; + +// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window has overwritten the data. +struct ImGuiLastItemDataBackup +{ + ImGuiID LastItemId; + ImGuiItemStatusFlags LastItemStatusFlags; + ImRect LastItemRect; + ImRect LastItemDisplayRect; + + ImGuiLastItemDataBackup() { Backup(); } + void Backup() { ImGuiWindow* window = GImGui->CurrentWindow; LastItemId = window->DC.LastItemId; LastItemStatusFlags = window->DC.LastItemStatusFlags; LastItemRect = window->DC.LastItemRect; LastItemDisplayRect = window->DC.LastItemDisplayRect; } + void Restore() const { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemStatusFlags = LastItemStatusFlags; window->DC.LastItemRect = LastItemRect; window->DC.LastItemDisplayRect = LastItemDisplayRect; } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Tab bar, Tab item support +//----------------------------------------------------------------------------- + +// Extend ImGuiTabBarFlags_ +enum ImGuiTabBarFlagsPrivate_ +{ + ImGuiTabBarFlags_DockNode = 1 << 20, // Part of a dock node [we don't use this in the master branch but it facilitate branch syncing to keep this around] + ImGuiTabBarFlags_IsFocused = 1 << 21, + ImGuiTabBarFlags_SaveSettings = 1 << 22 // FIXME: Settings are handled by the docking system, this only request the tab bar to mark settings dirty when reordering tabs +}; + +// Extend ImGuiTabItemFlags_ +enum ImGuiTabItemFlagsPrivate_ +{ + ImGuiTabItemFlags_SectionMask_ = ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing, + ImGuiTabItemFlags_NoCloseButton = 1 << 20, // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout) + ImGuiTabItemFlags_Button = 1 << 21 // Used by TabItemButton, change the tab item behavior to mimic a button +}; + +// Storage for one active tab item (sizeof() 28~32 bytes) +struct ImGuiTabItem +{ + ImGuiID ID; + ImGuiTabItemFlags Flags; + int LastFrameVisible; + int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance + float Offset; // Position relative to beginning of tab + float Width; // Width currently displayed + float ContentWidth; // Width of label, stored during BeginTabItem() call + ImS16 NameOffset; // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames + ImS16 BeginOrder; // BeginTabItem() order, used to re-order tabs after toggling ImGuiTabBarFlags_Reorderable + ImS16 IndexDuringLayout; // Index only used during TabBarLayout() + bool WantClose; // Marked as closed by SetTabItemClosed() + + ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; NameOffset = BeginOrder = IndexDuringLayout = -1; } +}; + +// Storage for a tab bar (sizeof() 152 bytes) +struct ImGuiTabBar +{ + ImVector Tabs; + ImGuiTabBarFlags Flags; + ImGuiID ID; // Zero for tab-bars used by docking + ImGuiID SelectedTabId; // Selected tab/window + ImGuiID NextSelectedTabId; // Next selected tab/window. Will also trigger a scrolling animation + ImGuiID VisibleTabId; // Can occasionally be != SelectedTabId (e.g. when previewing contents for CTRL+TAB preview) + int CurrFrameVisible; + int PrevFrameVisible; + ImRect BarRect; + float CurrTabsContentsHeight; + float PrevTabsContentsHeight; // Record the height of contents submitted below the tab bar + float WidthAllTabs; // Actual width of all tabs (locked during layout) + float WidthAllTabsIdeal; // Ideal width if all tabs were visible and not clipped + float ScrollingAnim; + float ScrollingTarget; + float ScrollingTargetDistToVisibility; + float ScrollingSpeed; + float ScrollingRectMinX; + float ScrollingRectMaxX; + ImGuiID ReorderRequestTabId; + ImS16 ReorderRequestOffset; + ImS8 BeginCount; + bool WantLayout; + bool VisibleTabWasSubmitted; + bool TabsAddedNew; // Set to true when a new tab item or button has been added to the tab bar during last frame + ImS16 TabsActiveCount; // Number of tabs submitted this frame. + ImS16 LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem() + float ItemSpacingY; + ImVec2 FramePadding; // style.FramePadding locked at the time of BeginTabBar() + ImVec2 BackupCursorPos; + ImGuiTextBuffer TabsNames; // For non-docking tab bar we re-append names in a contiguous buffer. + + ImGuiTabBar(); + int GetTabOrder(const ImGuiTabItem* tab) const { return Tabs.index_from_ptr(tab); } + const char* GetTabName(const ImGuiTabItem* tab) const + { + IM_ASSERT(tab->NameOffset != -1 && (int)tab->NameOffset < TabsNames.Buf.Size); + return TabsNames.Buf.Data + tab->NameOffset; + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Table support +//----------------------------------------------------------------------------- + +#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. +#define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. +#define IMGUI_TABLE_MAX_DRAW_CHANNELS (4 + 64 * 2) // See TableSetupDrawChannels() + +// Our current column maximum is 64 but we may raise that in the future. +typedef ImS8 ImGuiTableColumnIdx; +typedef ImU8 ImGuiTableDrawChannelIdx; + +// [Internal] sizeof() ~ 104 +// We use the terminology "Enabled" to refer to a column that is not Hidden by user/api. +// We use the terminology "Clipped" to refer to a column that is out of sight because of scrolling/clipping. +// This is in contrast with some user-facing api such as IsItemVisible() / IsRectVisible() which use "Visible" to mean "not clipped". +struct ImGuiTableColumn +{ + ImGuiTableColumnFlags Flags; // Flags after some patching (not directly same as provided by user). See ImGuiTableColumnFlags_ + float WidthGiven; // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be > WidthRequest to honor minimum width, may be < WidthRequest to honor shrinking columns down in tight space. + float MinX; // Absolute positions + float MaxX; + float WidthRequest; // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from StretchWeight in TableUpdateLayout() + float WidthAuto; // Automatic width + float StretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. + float InitStretchWeightOrWidth; // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_). + ImRect ClipRect; // Clipping rectangle for the column + ImGuiID UserID; // Optional, value passed to TableSetupColumn() + float WorkMinX; // Contents region min ~(MinX + CellPaddingX + CellSpacingX1) == cursor start position when entering column + float WorkMaxX; // Contents region max ~(MaxX - CellPaddingX - CellSpacingX2) + float ItemWidth; // Current item width for the column, preserved across rows + float ContentMaxXFrozen; // Contents maximum position for frozen rows (apart from headers), from which we can infer content width. + float ContentMaxXUnfrozen; + float ContentMaxXHeadersUsed; // Contents maximum position for headers rows (regardless of freezing). TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls + float ContentMaxXHeadersIdeal; + ImS16 NameOffset; // Offset into parent ColumnsNames[] + ImGuiTableColumnIdx DisplayOrder; // Index within Table's IndexToDisplayOrder[] (column may be reordered by users) + ImGuiTableColumnIdx IndexWithinEnabledSet; // Index within enabled/visible set (<= IndexToDisplayOrder) + ImGuiTableColumnIdx PrevEnabledColumn; // Index of prev enabled/visible column within Columns[], -1 if first enabled/visible column + ImGuiTableColumnIdx NextEnabledColumn; // Index of next enabled/visible column within Columns[], -1 if last enabled/visible column + ImGuiTableColumnIdx SortOrder; // Index of this column within sort specs, -1 if not sorting on this column, 0 for single-sort, may be >0 on multi-sort + ImGuiTableDrawChannelIdx DrawChannelCurrent; // Index within DrawSplitter.Channels[] + ImGuiTableDrawChannelIdx DrawChannelFrozen; + ImGuiTableDrawChannelIdx DrawChannelUnfrozen; + bool IsEnabled; // Is the column not marked Hidden by the user? (even if off view, e.g. clipped by scrolling). + bool IsEnabledNextFrame; + bool IsVisibleX; // Is actually in view (e.g. overlapping the host window clipping rectangle, not scrolled). + bool IsVisibleY; + bool IsRequestOutput; // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not. + bool IsSkipItems; // Do we want item submissions to this column to be completely ignored (no layout will happen). + bool IsPreserveWidthAuto; + ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte + ImU8 AutoFitQueue; // Queue of 8 values for the next 8 frames to request auto-fit + ImU8 CannotSkipItemsQueue; // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem + ImU8 SortDirection : 2; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending + ImU8 SortDirectionsAvailCount : 2; // Number of available sort directions (0 to 3) + ImU8 SortDirectionsAvailMask : 4; // Mask of available sort directions (1-bit each) + ImU8 SortDirectionsAvailList; // Ordered of available sort directions (2-bits each) + + ImGuiTableColumn() + { + memset(this, 0, sizeof(*this)); + StretchWeight = WidthRequest = -1.0f; + NameOffset = -1; + DisplayOrder = IndexWithinEnabledSet = -1; + PrevEnabledColumn = NextEnabledColumn = -1; + SortOrder = -1; + SortDirection = ImGuiSortDirection_None; + DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1; + } +}; + +// Transient cell data stored per row. +// sizeof() ~ 6 +struct ImGuiTableCellData +{ + ImU32 BgColor; // Actual color + ImGuiTableColumnIdx Column; // Column number +}; + +// FIXME-TABLE: more transient data could be stored in a per-stacked table structure: DrawSplitter, SortSpecs, incoming RowData +struct ImGuiTable +{ + ImGuiID ID; + ImGuiTableFlags Flags; + void* RawData; // Single allocation to hold Columns[], DisplayOrderToIndex[] and RowCellData[] + ImGuiTableTempData* TempData; // Transient data while table is active. Point within g.CurrentTableStack[] + ImSpan Columns; // Point within RawData[] + ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) + ImSpan RowCellData; // Point within RawData[]. Store cells background requests for current row. + ImU64 EnabledMaskByDisplayOrder; // Column DisplayOrder -> IsEnabled map + ImU64 EnabledMaskByIndex; // Column Index -> IsEnabled map (== not hidden by user/api) in a format adequate for iterating column without touching cold data + ImU64 VisibleMaskByIndex; // Column Index -> IsVisibleX|IsVisibleY map (== not hidden by user/api && not hidden by scrolling/cliprect) + ImU64 RequestOutputMaskByIndex; // Column Index -> IsVisible || AutoFit (== expect user to submit items) + ImGuiTableFlags SettingsLoadedFlags; // Which data were loaded from the .ini file (e.g. when order is not altered we won't save order) + int SettingsOffset; // Offset in g.SettingsTables + int LastFrameActive; + int ColumnsCount; // Number of columns declared in BeginTable() + int CurrentRow; + int CurrentColumn; + ImS16 InstanceCurrent; // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple table with same ID look are multiple tables, they are just synched. + ImS16 InstanceInteracted; // Mark which instance (generally 0) of the same ID is being interacted with + float RowPosY1; + float RowPosY2; + float RowMinHeight; // Height submitted to TableNextRow() + float RowTextBaseline; + float RowIndentOffsetX; + ImGuiTableRowFlags RowFlags : 16; // Current row flags, see ImGuiTableRowFlags_ + ImGuiTableRowFlags LastRowFlags : 16; + int RowBgColorCounter; // Counter for alternating background colors (can be fast-forwarded by e.g clipper), not same as CurrentRow because header rows typically don't increase this. + ImU32 RowBgColor[2]; // Background color override for current row. + ImU32 BorderColorStrong; + ImU32 BorderColorLight; + float BorderX1; + float BorderX2; + float HostIndentX; + float MinColumnWidth; + float OuterPaddingX; + float CellPaddingX; // Padding from each borders + float CellPaddingY; + float CellSpacingX1; // Spacing between non-bordered cells + float CellSpacingX2; + float LastOuterHeight; // Outer height from last frame + float LastFirstRowHeight; // Height of first row from last frame + float InnerWidth; // User value passed to BeginTable(), see comments at the top of BeginTable() for details. + float ColumnsGivenWidth; // Sum of current column width + float ColumnsAutoFitWidth; // Sum of ideal column width in order nothing to be clipped, used for auto-fitting and content width submission in outer window + float ResizedColumnNextWidth; + float ResizeLockMinContentsX2; // Lock minimum contents width while resizing down in order to not create feedback loops. But we allow growing the table. + float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. + ImRect OuterRect; // Note: for non-scrolling table, OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). + ImRect InnerRect; // InnerRect but without decoration. As with OuterRect, for non-scrolling tables, InnerRect.Max.y is + ImRect WorkRect; + ImRect InnerClipRect; + ImRect BgClipRect; // We use this to cpu-clip cell background color fill + ImRect Bg0ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG0/1 channel. This tends to be == OuterWindow->ClipRect at BeginTable() because output in BG0/BG1 is cpu-clipped + ImRect Bg2ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG2 channel. This tends to be a correct, tight-fit, because output to BG2 are done by widgets relying on regular ClipRect. + ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. + ImRect HostBackupInnerClipRect; // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground() + ImGuiWindow* OuterWindow; // Parent window for the table + ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window) + ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names + ImDrawListSplitter* DrawSplitter; // Shortcut to TempData->DrawSplitter while in table. Isolate draw commands per columns to avoid switching clip rect constantly + ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() + ImGuiTableColumnIdx SortSpecsCount; + ImGuiTableColumnIdx ColumnsEnabledCount; // Number of enabled columns (<= ColumnsCount) + ImGuiTableColumnIdx ColumnsEnabledFixedCount; // Number of enabled columns (<= ColumnsCount) + ImGuiTableColumnIdx DeclColumnsCount; // Count calls to TableSetupColumn() + ImGuiTableColumnIdx HoveredColumnBody; // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after the right-most column! + ImGuiTableColumnIdx HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). + ImGuiTableColumnIdx AutoFitSingleColumn; // Index of single column requesting auto-fit. + ImGuiTableColumnIdx ResizedColumn; // Index of column being resized. Reset when InstanceCurrent==0. + ImGuiTableColumnIdx LastResizedColumn; // Index of column being resized from previous frame. + ImGuiTableColumnIdx HeldHeaderColumn; // Index of column header being held. + ImGuiTableColumnIdx ReorderColumn; // Index of column being reordered. (not cleared) + ImGuiTableColumnIdx ReorderColumnDir; // -1 or +1 + ImGuiTableColumnIdx LeftMostEnabledColumn; // Index of left-most non-hidden column. + ImGuiTableColumnIdx RightMostEnabledColumn; // Index of right-most non-hidden column. + ImGuiTableColumnIdx LeftMostStretchedColumn; // Index of left-most stretched column. + ImGuiTableColumnIdx RightMostStretchedColumn; // Index of right-most stretched column. + ImGuiTableColumnIdx ContextPopupColumn; // Column right-clicked on, of -1 if opening context menu from a neutral/empty spot + ImGuiTableColumnIdx FreezeRowsRequest; // Requested frozen rows count + ImGuiTableColumnIdx FreezeRowsCount; // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset) + ImGuiTableColumnIdx FreezeColumnsRequest; // Requested frozen columns count + ImGuiTableColumnIdx FreezeColumnsCount; // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset) + ImGuiTableColumnIdx RowCellDataCurrent; // Index of current RowCellData[] entry in current row + ImGuiTableDrawChannelIdx DummyDrawChannel; // Redirect non-visible columns here. + ImGuiTableDrawChannelIdx Bg2DrawChannelCurrent; // For Selectable() and other widgets drawing across columns after the freezing line. Index within DrawSplitter.Channels[] + ImGuiTableDrawChannelIdx Bg2DrawChannelUnfrozen; + bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. + bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). + bool IsInitializing; + bool IsSortSpecsDirty; + bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. + bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). + bool IsSettingsRequestLoad; + bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data. + bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) + bool IsResetAllRequest; + bool IsResetDisplayOrderRequest; + bool IsUnfrozenRows; // Set when we got past the frozen row. + bool IsDefaultSizingPolicy; // Set if user didn't explicitly set a sizing policy in BeginTable() + bool MemoryCompacted; + bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis + + IMGUI_API ImGuiTable() { memset(this, 0, sizeof(*this)); LastFrameActive = -1; } + IMGUI_API ~ImGuiTable() { IM_FREE(RawData); } +}; + +// Transient data that are only needed between BeginTable() and EndTable(), those buffers are shared (1 per level of stacked table). +// - Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure. +// - We also leave out of this structure data that tend to be particularly useful for debugging/metrics. +// FIXME-TABLE: more transient data could be stored here: DrawSplitter, incoming RowData? +struct ImGuiTableTempData +{ + int TableIndex; // Index in g.Tables.Buf[] pool + float LastTimeActive; // Last timestamp this structure was used + + ImVec2 UserOuterSize; // outer_size.x passed to BeginTable() + ImDrawListSplitter DrawSplitter; + ImGuiTableColumnSortSpecs SortSpecsSingle; + ImVector SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would be good. + + ImRect HostBackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() + ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable() + ImVec2 HostBackupPrevLineSize; // Backup of InnerWindow->DC.PrevLineSize at the end of BeginTable() + ImVec2 HostBackupCurrLineSize; // Backup of InnerWindow->DC.CurrLineSize at the end of BeginTable() + ImVec2 HostBackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() + ImVec1 HostBackupColumnsOffset; // Backup of OuterWindow->DC.ColumnsOffset at the end of BeginTable() + float HostBackupItemWidth; // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable() + int HostBackupItemWidthStackSize;//Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable() + + IMGUI_API ImGuiTableTempData() { memset(this, 0, sizeof(*this)); LastTimeActive = -1.0f; } +}; + +// sizeof() ~ 12 +struct ImGuiTableColumnSettings +{ + float WidthOrWeight; + ImGuiID UserID; + ImGuiTableColumnIdx Index; + ImGuiTableColumnIdx DisplayOrder; + ImGuiTableColumnIdx SortOrder; + ImU8 SortDirection : 2; + ImU8 IsEnabled : 1; // "Visible" in ini file + ImU8 IsStretch : 1; + + ImGuiTableColumnSettings() + { + WidthOrWeight = 0.0f; + UserID = 0; + Index = -1; + DisplayOrder = SortOrder = -1; + SortDirection = ImGuiSortDirection_None; + IsEnabled = 1; + IsStretch = 0; + } +}; + +// This is designed to be stored in a single ImChunkStream (1 header followed by N ImGuiTableColumnSettings, etc.) +struct ImGuiTableSettings +{ + ImGuiID ID; // Set to 0 to invalidate/delete the setting + ImGuiTableFlags SaveFlags; // Indicate data we want to save using the Resizable/Reorderable/Sortable/Hideable flags (could be using its own flags..) + float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. + ImGuiTableColumnIdx ColumnsCount; + ImGuiTableColumnIdx ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher + bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) + + ImGuiTableSettings() { memset(this, 0, sizeof(*this)); } + ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); } +}; + +//----------------------------------------------------------------------------- +// [SECTION] ImGui internal API +// No guarantee of forward compatibility here! +//----------------------------------------------------------------------------- + +namespace ImGui +{ + // Windows + // We should always have a CurrentWindow in the stack (there is an implicit "Debug" window) + // If this ever crash because g.CurrentWindow is NULL it means that either + // - ImGui::NewFrame() has never been called, which is illegal. + // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. + inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } + inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } + IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id); + IMGUI_API ImGuiWindow* FindWindowByName(const char* name); + IMGUI_API void UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window); + IMGUI_API ImVec2 CalcWindowNextAutoFitSize(ImGuiWindow* window); + IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent); + IMGUI_API bool IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below); + IMGUI_API bool IsWindowNavFocusable(ImGuiWindow* window); + IMGUI_API ImRect GetWindowAllowedExtentRect(ImGuiWindow* window); + IMGUI_API void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond = 0); + IMGUI_API void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond = 0); + IMGUI_API void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0); + IMGUI_API void SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size); + + // Windows: Display Order and Focus Order + IMGUI_API void FocusWindow(ImGuiWindow* window); + IMGUI_API void FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window); + IMGUI_API void BringWindowToFocusFront(ImGuiWindow* window); + IMGUI_API void BringWindowToDisplayFront(ImGuiWindow* window); + IMGUI_API void BringWindowToDisplayBack(ImGuiWindow* window); + + // Fonts, drawing + IMGUI_API void SetCurrentFont(ImFont* font); + inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } + inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. + IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport); // get background draw list for the given viewport. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. + IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport); // get foreground draw list for the given viewport. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. + + // Init + IMGUI_API void Initialize(ImGuiContext* context); + IMGUI_API void Shutdown(ImGuiContext* context); // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext(). + + // NewFrame + IMGUI_API void UpdateHoveredWindowAndCaptureFlags(); + IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); + IMGUI_API void UpdateMouseMovingWindowNewFrame(); + IMGUI_API void UpdateMouseMovingWindowEndFrame(); + + // Generic context hooks + IMGUI_API ImGuiID AddContextHook(ImGuiContext* context, const ImGuiContextHook* hook); + IMGUI_API void RemoveContextHook(ImGuiContext* context, ImGuiID hook_to_remove); + IMGUI_API void CallContextHooks(ImGuiContext* context, ImGuiContextHookType type); + + // Settings + IMGUI_API void MarkIniSettingsDirty(); + IMGUI_API void MarkIniSettingsDirty(ImGuiWindow* window); + IMGUI_API void ClearIniSettings(); + IMGUI_API ImGuiWindowSettings* CreateNewWindowSettings(const char* name); + IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); + IMGUI_API ImGuiWindowSettings* FindOrCreateWindowSettings(const char* name); + IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); + + // Scrolling + IMGUI_API void SetNextWindowScroll(const ImVec2& scroll); // Use -1.0f on one axis to leave as-is + IMGUI_API void SetScrollX(ImGuiWindow* window, float scroll_x); + IMGUI_API void SetScrollY(ImGuiWindow* window, float scroll_y); + IMGUI_API void SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio); + IMGUI_API void SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio); + IMGUI_API ImVec2 ScrollToBringRectIntoView(ImGuiWindow* window, const ImRect& item_rect); + + // Basic Accessors + inline ImGuiID GetItemID() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.LastItemId; } // Get ID of last item (~~ often same ImGui::GetID(label) beforehand) + inline ImGuiItemStatusFlags GetItemStatusFlags() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.LastItemStatusFlags; } + inline ImGuiID GetActiveID() { ImGuiContext& g = *GImGui; return g.ActiveId; } + inline ImGuiID GetFocusID() { ImGuiContext& g = *GImGui; return g.NavId; } + inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.CurrentItemFlags; } + IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); + IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow* window); + IMGUI_API void ClearActiveID(); + IMGUI_API ImGuiID GetHoveredID(); + IMGUI_API void SetHoveredID(ImGuiID id); + IMGUI_API void KeepAliveID(ImGuiID id); + IMGUI_API void MarkItemEdited(ImGuiID id); // Mark data associated to given item as "edited", used by IsItemDeactivatedAfterEdit() function. + IMGUI_API void PushOverrideID(ImGuiID id); // Push given value as-is at the top of the ID stack (whereas PushID combines old and new hashes) + IMGUI_API ImGuiID GetIDWithSeed(const char* str_id_begin, const char* str_id_end, ImGuiID seed); + + // Basic Helpers for widget code + IMGUI_API void ItemSize(const ImVec2& size, float text_baseline_y = -1.0f); + IMGUI_API void ItemSize(const ImRect& bb, float text_baseline_y = -1.0f); + IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL, ImGuiItemAddFlags flags = 0); + IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); + IMGUI_API void ItemFocusable(ImGuiWindow* window, ImGuiID id); + IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); + IMGUI_API void SetLastItemData(ImGuiWindow* window, ImGuiID item_id, ImGuiItemStatusFlags status_flags, const ImRect& item_rect); + IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h); + IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); + IMGUI_API void PushMultiItemsWidths(int components, float width_full); + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); + IMGUI_API void PopItemFlag(); + IMGUI_API bool IsItemToggledSelection(); // Was the last item selection toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to handle clipping correctly) + IMGUI_API ImVec2 GetContentRegionMaxAbs(); + IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // If you have old/custom copy-and-pasted widgets that used FocusableItemRegister(): + // (Old) IMGUI_VERSION_NUM < 18209: using 'ItemAdd(....)' and 'bool focused = FocusableItemRegister(...)' + // (New) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool focused = (GetItemStatusFlags() & ImGuiItemStatusFlags_Focused) != 0' + // Widget code are simplified as there's no need to call FocusableItemUnregister() while managing the transition from regular widget to TempInputText() + inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) { IM_ASSERT(0); IM_UNUSED(window); IM_UNUSED(id); return false; } // -> pass ImGuiItemAddFlags_Focusable flag to ItemAdd() + inline void FocusableItemUnregister(ImGuiWindow* window) { IM_ASSERT(0); IM_UNUSED(window); } // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem +#endif + + // Logging/Capture + IMGUI_API void LogBegin(ImGuiLogType type, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name. + IMGUI_API void LogToBuffer(int auto_open_depth = -1); // Start logging/capturing to internal buffer + IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL); + IMGUI_API void LogSetNextTextDecoration(const char* prefix, const char* suffix); + + // Popups, Modals, Tooltips + IMGUI_API bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags); + IMGUI_API void OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_None); + IMGUI_API void ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup); + IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup); + IMGUI_API bool IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags); + IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); + IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, ImGuiTooltipFlags tooltip_flags); + IMGUI_API ImGuiWindow* GetTopMostPopupModal(); + IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); + IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); + IMGUI_API bool BeginViewportSideBar(const char* name, ImGuiViewport* viewport, ImGuiDir dir, float size, ImGuiWindowFlags window_flags); + + // Gamepad/Keyboard Navigation + IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); + IMGUI_API bool NavMoveRequestButNoResultYet(); + IMGUI_API void NavMoveRequestCancel(); + IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags); + IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); + IMGUI_API float GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode); + IMGUI_API ImVec2 GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor = 0.0f, float fast_factor = 0.0f); + IMGUI_API int CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate); + IMGUI_API void ActivateItem(ImGuiID id); // Remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and processed on the next frame when the item is encountered again. + IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); + + // Focus Scope (WIP) + // This is generally used to identify a selection set (multiple of which may be in the same window), as selection + // patterns generally need to react (e.g. clear selection) when landing on an item of the set. + IMGUI_API void PushFocusScope(ImGuiID id); + IMGUI_API void PopFocusScope(); + inline ImGuiID GetFocusedFocusScope() { ImGuiContext& g = *GImGui; return g.NavFocusScopeId; } // Focus scope which is actually active + inline ImGuiID GetFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.NavFocusScopeIdCurrent; } // Focus scope we are outputting into, set by PushFocusScope() + + // Inputs + // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. + IMGUI_API void SetItemUsingMouseWheel(); + inline bool IsActiveIdUsingNavDir(ImGuiDir dir) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; } + inline bool IsActiveIdUsingNavInput(ImGuiNavInput input) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavInputMask & (1 << input)) != 0; } + inline bool IsActiveIdUsingKey(ImGuiKey key) { ImGuiContext& g = *GImGui; IM_ASSERT(key < 64); return (g.ActiveIdUsingKeyInputMask & ((ImU64)1 << key)) != 0; } + IMGUI_API bool IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold = -1.0f); + inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { ImGuiContext& g = *GImGui; const int key_index = g.IO.KeyMap[key]; return (key_index >= 0) ? IsKeyPressed(key_index, repeat) : false; } + inline bool IsNavInputDown(ImGuiNavInput n) { ImGuiContext& g = *GImGui; return g.IO.NavInputs[n] > 0.0f; } + inline bool IsNavInputTest(ImGuiNavInput n, ImGuiInputReadMode rm) { return (GetNavInputAmount(n, rm) > 0.0f); } + IMGUI_API ImGuiKeyModFlags GetMergedKeyModFlags(); + + // Drag and Drop + IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); + IMGUI_API void ClearDragDrop(); + IMGUI_API bool IsDragDropPayloadBeingAccepted(); + + // Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API) + IMGUI_API void SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect); + IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). + IMGUI_API void EndColumns(); // close columns + IMGUI_API void PushColumnClipRect(int column_index); + IMGUI_API void PushColumnsBackground(); + IMGUI_API void PopColumnsBackground(); + IMGUI_API ImGuiID GetColumnsID(const char* str_id, int count); + IMGUI_API ImGuiOldColumns* FindOrCreateColumns(ImGuiWindow* window, ImGuiID id); + IMGUI_API float GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm); + IMGUI_API float GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset); + + // Tables: Candidates for public API + IMGUI_API void TableOpenContextMenu(int column_n = -1); + IMGUI_API void TableSetColumnWidth(int column_n, float width); + IMGUI_API void TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); + IMGUI_API int TableGetHoveredColumn(); // May use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead. Return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. + IMGUI_API float TableGetHeaderRowHeight(); + IMGUI_API void TablePushBackgroundChannel(); + IMGUI_API void TablePopBackgroundChannel(); + + // Tables: Internals + inline ImGuiTable* GetCurrentTable() { ImGuiContext& g = *GImGui; return g.CurrentTable; } + IMGUI_API ImGuiTable* TableFindByID(ImGuiID id); + IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); + IMGUI_API void TableBeginInitMemory(ImGuiTable* table, int columns_count); + IMGUI_API void TableBeginApplyRequests(ImGuiTable* table); + IMGUI_API void TableSetupDrawChannels(ImGuiTable* table); + IMGUI_API void TableUpdateLayout(ImGuiTable* table); + IMGUI_API void TableUpdateBorders(ImGuiTable* table); + IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table); + IMGUI_API void TableDrawBorders(ImGuiTable* table); + IMGUI_API void TableDrawContextMenu(ImGuiTable* table); + IMGUI_API void TableMergeDrawChannels(ImGuiTable* table); + IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); + IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); + IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column); + IMGUI_API void TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column); + IMGUI_API float TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column); + IMGUI_API void TableBeginRow(ImGuiTable* table); + IMGUI_API void TableEndRow(ImGuiTable* table); + IMGUI_API void TableBeginCell(ImGuiTable* table, int column_n); + IMGUI_API void TableEndCell(ImGuiTable* table); + IMGUI_API ImRect TableGetCellBgRect(const ImGuiTable* table, int column_n); + IMGUI_API const char* TableGetColumnName(const ImGuiTable* table, int column_n); + IMGUI_API ImGuiID TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no = 0); + IMGUI_API float TableGetMaxColumnWidth(const ImGuiTable* table, int column_n); + IMGUI_API void TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n); + IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable* table); + IMGUI_API void TableRemove(ImGuiTable* table); + IMGUI_API void TableGcCompactTransientBuffers(ImGuiTable* table); + IMGUI_API void TableGcCompactTransientBuffers(ImGuiTableTempData* table); + IMGUI_API void TableGcCompactSettings(); + + // Tables: Settings + IMGUI_API void TableLoadSettings(ImGuiTable* table); + IMGUI_API void TableSaveSettings(ImGuiTable* table); + IMGUI_API void TableResetSettings(ImGuiTable* table); + IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); + IMGUI_API void TableSettingsInstallHandler(ImGuiContext* context); + IMGUI_API ImGuiTableSettings* TableSettingsCreate(ImGuiID id, int columns_count); + IMGUI_API ImGuiTableSettings* TableSettingsFindByID(ImGuiID id); + + // Tab Bars + IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags); + IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id); + IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); + IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset); + IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, ImVec2 mouse_pos); + IMGUI_API bool TabBarProcessReorder(ImGuiTabBar* tab_bar); + IMGUI_API bool TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags); + IMGUI_API ImVec2 TabItemCalcSize(const char* label, bool has_close_button); + IMGUI_API void TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col); + IMGUI_API void TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped); + + // Render helpers + // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. + // NB: All position are in absolute pixels coordinates (we are never using window coordinates internally) + IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); + IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); + IMGUI_API void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); + IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); + IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); + IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); + IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); + IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight + IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. + + // Render helpers (those functions don't access any ImGui state!) + IMGUI_API void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale = 1.0f); + IMGUI_API void RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col); + IMGUI_API void RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); + IMGUI_API void RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow); + IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); + IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); + IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, ImRect outer, ImRect inner, ImU32 col, float rounding); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // [1.71: 2019/06/07: Updating prototypes of some of the internal functions. Leaving those for reference for a short while] + inline void RenderArrow(ImVec2 pos, ImGuiDir dir, float scale=1.0f) { ImGuiWindow* window = GetCurrentWindow(); RenderArrow(window->DrawList, pos, GetColorU32(ImGuiCol_Text), dir, scale); } + inline void RenderBullet(ImVec2 pos) { ImGuiWindow* window = GetCurrentWindow(); RenderBullet(window->DrawList, pos, GetColorU32(ImGuiCol_Text)); } +#endif + + // Widgets + IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); + IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); + IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos); + IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); + IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); + IMGUI_API void Scrollbar(ImGuiAxis axis); + IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float avail_v, float contents_v, ImDrawFlags flags); + IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col); + IMGUI_API ImRect GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis); + IMGUI_API ImGuiID GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis); + IMGUI_API ImGuiID GetWindowResizeCornerID(ImGuiWindow* window, int n); // 0..3: corners + IMGUI_API ImGuiID GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir); + IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags); + IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); + IMGUI_API bool CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value); + + // Widgets low-level behaviors + IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); + IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags); + IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb); + IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f); + IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); + IMGUI_API bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextItemOpen() data, if any. May return true when logging + IMGUI_API void TreePushOverrideID(ImGuiID id); + + // Template functions are instantiated in imgui_widgets.cpp for a finite number of types. + // To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link to existing instances and silence Clang warnings (see #2036). + // e.g. " extern template IMGUI_API float RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, float v); " + template IMGUI_API float ScaleRatioFromValueT(ImGuiDataType data_type, T v, T v_min, T v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_size); + template IMGUI_API T ScaleValueFromRatioT(ImGuiDataType data_type, float t, T v_min, T v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_size); + template IMGUI_API bool DragBehaviorT(ImGuiDataType data_type, T* v, float v_speed, T v_min, T v_max, const char* format, ImGuiSliderFlags flags); + template IMGUI_API bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, T* v, T v_min, T v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb); + template IMGUI_API T RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, T v); + template IMGUI_API bool CheckboxFlagsT(const char* label, T* flags, T flags_value); + + // Data type helpers + IMGUI_API const ImGuiDataTypeInfo* DataTypeGetInfo(ImGuiDataType data_type); + IMGUI_API int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format); + IMGUI_API void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg_1, const void* arg_2); + IMGUI_API bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* p_data, const char* format); + IMGUI_API int DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2); + IMGUI_API bool DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max); + + // InputText + IMGUI_API bool InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags); + IMGUI_API bool TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL); + inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.ActiveId == id && g.TempInputId == id); } + inline ImGuiInputTextState* GetInputTextState(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active + + // Color + IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); + IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); + IMGUI_API void ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags); + + // Plot + IMGUI_API int PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size); + + // Shade functions (write over already created vertices) + IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); + IMGUI_API void ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp); + + // Garbage collection + IMGUI_API void GcCompactTransientMiscBuffers(); + IMGUI_API void GcCompactTransientWindowBuffers(ImGuiWindow* window); + IMGUI_API void GcAwakeTransientWindowBuffers(ImGuiWindow* window); + + // Debug Tools + IMGUI_API void ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); + inline void DebugDrawItemRect(ImU32 col = IM_COL32(255,0,0,255)) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; GetForegroundDrawList(window)->AddRect(window->DC.LastItemRect.Min, window->DC.LastItemRect.Max, col); } + inline void DebugStartItemPicker() { ImGuiContext& g = *GImGui; g.DebugItemPickerActive = true; } + + IMGUI_API void DebugNodeColumns(ImGuiOldColumns* columns); + IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, const ImDrawList* draw_list, const char* label); + IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); + IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); + IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); + IMGUI_API void DebugNodeTable(ImGuiTable* table); + IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings* settings); + IMGUI_API void DebugNodeWindow(ImGuiWindow* window, const char* label); + IMGUI_API void DebugNodeWindowSettings(ImGuiWindowSettings* settings); + IMGUI_API void DebugNodeWindowsList(ImVector* windows, const char* label); + IMGUI_API void DebugNodeViewport(ImGuiViewportP* viewport); + IMGUI_API void DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb); + +} // namespace ImGui + + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas internal API +//----------------------------------------------------------------------------- + +// This structure is likely to evolve as we add support for incremental atlas updates +struct ImFontBuilderIO +{ + bool (*FontBuilder_Build)(ImFontAtlas* atlas); +}; + +// Helper for font builder +IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype(); +IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); +IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque); +IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value); +IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value); +IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); +IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); + +//----------------------------------------------------------------------------- +// [SECTION] Test Engine specific hooks (imgui_test_engine) +//----------------------------------------------------------------------------- + +#ifdef IMGUI_ENABLE_TEST_ENGINE +extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ctx, const ImRect& bb, ImGuiID id); +extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags); +extern void ImGuiTestEngineHook_IdInfo(ImGuiContext* ctx, ImGuiDataType data_type, ImGuiID id, const void* data_id); +extern void ImGuiTestEngineHook_IdInfo(ImGuiContext* ctx, ImGuiDataType data_type, ImGuiID id, const void* data_id, const void* data_id_end); +extern void ImGuiTestEngineHook_Log(ImGuiContext* ctx, const char* fmt, ...); +#define IMGUI_TEST_ENGINE_ITEM_ADD(_BB,_ID) if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemAdd(&g, _BB, _ID) // Register item bounding box +#define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemInfo(&g, _ID, _LABEL, _FLAGS) // Register item label and status flags (optional) +#define IMGUI_TEST_ENGINE_LOG(_FMT,...) if (g.TestEngineHookItems) ImGuiTestEngineHook_Log(&g, _FMT, __VA_ARGS__) // Custom log entry from user land into test log +#define IMGUI_TEST_ENGINE_ID_INFO(_ID,_TYPE,_DATA) if (g.TestEngineHookIdInfo == id) ImGuiTestEngineHook_IdInfo(&g, _TYPE, _ID, (const void*)(_DATA)); +#define IMGUI_TEST_ENGINE_ID_INFO2(_ID,_TYPE,_DATA,_DATA2) if (g.TestEngineHookIdInfo == id) ImGuiTestEngineHook_IdInfo(&g, _TYPE, _ID, (const void*)(_DATA), (const void*)(_DATA2)); +#else +#define IMGUI_TEST_ENGINE_ITEM_ADD(_BB,_ID) do { } while (0) +#define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) do { } while (0) +#define IMGUI_TEST_ENGINE_LOG(_FMT,...) do { } while (0) +#define IMGUI_TEST_ENGINE_ID_INFO(_ID,_TYPE,_DATA) do { } while (0) +#define IMGUI_TEST_ENGINE_ID_INFO2(_ID,_TYPE,_DATA,_DATA2) do { } while (0) +#endif + +//----------------------------------------------------------------------------- + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/vendor/librw/skeleton/imgui/imgui_tables.cpp b/vendor/librw/skeleton/imgui/imgui_tables.cpp new file mode 100644 index 0000000..593e3b0 --- /dev/null +++ b/vendor/librw/skeleton/imgui/imgui_tables.cpp @@ -0,0 +1,4028 @@ +// dear imgui, v1.83 +// (tables and columns code) + +/* + +Index of this file: + +// [SECTION] Commentary +// [SECTION] Header mess +// [SECTION] Tables: Main code +// [SECTION] Tables: Simple accessors +// [SECTION] Tables: Row changes +// [SECTION] Tables: Columns changes +// [SECTION] Tables: Columns width management +// [SECTION] Tables: Drawing +// [SECTION] Tables: Sorting +// [SECTION] Tables: Headers +// [SECTION] Tables: Context Menu +// [SECTION] Tables: Settings (.ini data) +// [SECTION] Tables: Garbage Collection +// [SECTION] Tables: Debugging +// [SECTION] Columns, BeginColumns, EndColumns, etc. + +*/ + +// Navigating this file: +// - In Visual Studio IDE: CTRL+comma ("Edit.NavigateTo") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. + +//----------------------------------------------------------------------------- +// [SECTION] Commentary +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Typical tables call flow: (root level is generally public API): +//----------------------------------------------------------------------------- +// - BeginTable() user begin into a table +// | BeginChild() - (if ScrollX/ScrollY is set) +// | TableBeginInitMemory() - first time table is used +// | TableResetSettings() - on settings reset +// | TableLoadSettings() - on settings load +// | TableBeginApplyRequests() - apply queued resizing/reordering/hiding requests +// | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) +// | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width +// - TableSetupColumn() user submit columns details (optional) +// - TableSetupScrollFreeze() user submit scroll freeze information (optional) +//----------------------------------------------------------------------------- +// - TableUpdateLayout() [Internal] followup to BeginTable(): setup everything: widths, columns positions, clipping rectangles. Automatically called by the FIRST call to TableNextRow() or TableHeadersRow(). +// | TableSetupDrawChannels() - setup ImDrawList channels +// | TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission +// | TableDrawContextMenu() - draw right-click context menu +//----------------------------------------------------------------------------- +// - TableHeadersRow() or TableHeader() user submit a headers row (optional) +// | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction +// | TableOpenContextMenu() - when right-clicked: trigger opening of the default context menu +// - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) +// - TableNextRow() user begin into a new row (also automatically called by TableHeadersRow()) +// | TableEndRow() - finish existing row +// | TableBeginRow() - add a new row +// - TableSetColumnIndex() / TableNextColumn() user begin into a cell +// | TableEndCell() - close existing column/cell +// | TableBeginCell() - enter into current column/cell +// - [...] user emit contents +//----------------------------------------------------------------------------- +// - EndTable() user ends the table +// | TableDrawBorders() - draw outer borders, inner vertical borders +// | TableMergeDrawChannels() - merge draw channels if clipping isn't required +// | EndChild() - (if ScrollX/ScrollY is set) +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// TABLE SIZING +//----------------------------------------------------------------------------- +// (Read carefully because this is subtle but it does make sense!) +//----------------------------------------------------------------------------- +// About 'outer_size': +// Its meaning needs to differ slightly depending on if we are using ScrollX/ScrollY flags. +// Default value is ImVec2(0.0f, 0.0f). +// X +// - outer_size.x <= 0.0f -> Right-align from window/work-rect right-most edge. With -FLT_MIN or 0.0f will align exactly on right-most edge. +// - outer_size.x > 0.0f -> Set Fixed width. +// Y with ScrollX/ScrollY disabled: we output table directly in current window +// - outer_size.y < 0.0f -> Bottom-align (but will auto extend, unless _NoHostExtendY is set). Not meaningful is parent window can vertically scroll. +// - outer_size.y = 0.0f -> No minimum height (but will auto extend, unless _NoHostExtendY is set) +// - outer_size.y > 0.0f -> Set Minimum height (but will auto extend, unless _NoHostExtenY is set) +// Y with ScrollX/ScrollY enabled: using a child window for scrolling +// - outer_size.y < 0.0f -> Bottom-align. Not meaningful is parent window can vertically scroll. +// - outer_size.y = 0.0f -> Bottom-align, consistent with BeginChild(). Not recommended unless table is last item in parent window. +// - outer_size.y > 0.0f -> Set Exact height. Recommended when using Scrolling on any axis. +//----------------------------------------------------------------------------- +// Outer size is also affected by the NoHostExtendX/NoHostExtendY flags. +// Important to that note how the two flags have slightly different behaviors! +// - ImGuiTableFlags_NoHostExtendX -> Make outer width auto-fit to columns (overriding outer_size.x value). Only available when ScrollX/ScrollY are disabled and Stretch columns are not used. +// - ImGuiTableFlags_NoHostExtendY -> Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY is disabled. Data below the limit will be clipped and not visible. +// In theory ImGuiTableFlags_NoHostExtendY could be the default and any non-scrolling tables with outer_size.y != 0.0f would use exact height. +// This would be consistent but perhaps less useful and more confusing (as vertically clipped items are not easily noticeable) +//----------------------------------------------------------------------------- +// About 'inner_width': +// With ScrollX disabled: +// - inner_width -> *ignored* +// With ScrollX enabled: +// - inner_width < 0.0f -> *illegal* fit in known width (right align from outer_size.x) <-- weird +// - inner_width = 0.0f -> fit in outer_width: Fixed size columns will take space they need (if avail, otherwise shrink down), Stretch columns becomes Fixed columns. +// - inner_width > 0.0f -> override scrolling width, generally to be larger than outer_size.x. Fixed column take space they need (if avail, otherwise shrink down), Stretch columns share remaining space! +//----------------------------------------------------------------------------- +// Details: +// - If you want to use Stretch columns with ScrollX, you generally need to specify 'inner_width' otherwise the concept +// of "available space" doesn't make sense. +// - Even if not really useful, we allow 'inner_width < outer_size.x' for consistency and to facilitate understanding +// of what the value does. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// COLUMNS SIZING POLICIES +//----------------------------------------------------------------------------- +// About overriding column sizing policy and width/weight with TableSetupColumn(): +// We use a default parameter of 'init_width_or_weight == -1'. +// - with ImGuiTableColumnFlags_WidthFixed, init_width <= 0 (default) --> width is automatic +// - with ImGuiTableColumnFlags_WidthFixed, init_width > 0 (explicit) --> width is custom +// - with ImGuiTableColumnFlags_WidthStretch, init_weight <= 0 (default) --> weight is 1.0f +// - with ImGuiTableColumnFlags_WidthStretch, init_weight > 0 (explicit) --> weight is custom +// Widths are specified _without_ CellPadding. If you specify a width of 100.0f, the column will be cover (100.0f + Padding * 2.0f) +// and you can fit a 100.0f wide item in it without clipping and with full padding. +//----------------------------------------------------------------------------- +// About default sizing policy (if you don't specify a ImGuiTableColumnFlags_WidthXXXX flag) +// - with Table policy ImGuiTableFlags_SizingFixedFit --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is equal to contents width +// - with Table policy ImGuiTableFlags_SizingFixedSame --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is max of all contents width +// - with Table policy ImGuiTableFlags_SizingStretchSame --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is 1.0f +// - with Table policy ImGuiTableFlags_SizingStretchWeight --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is proportional to contents +// Default Width and default Weight can be overridden when calling TableSetupColumn(). +//----------------------------------------------------------------------------- +// About mixing Fixed/Auto and Stretch columns together: +// - the typical use of mixing sizing policies is: any number of LEADING Fixed columns, followed by one or two TRAILING Stretch columns. +// - using mixed policies with ScrollX does not make much sense, as using Stretch columns with ScrollX does not make much sense in the first place! +// that is, unless 'inner_width' is passed to BeginTable() to explicitly provide a total width to layout columns in. +// - when using ImGuiTableFlags_SizingFixedSame with mixed columns, only the Fixed/Auto columns will match their widths to the width of the maximum contents. +// - when using ImGuiTableFlags_SizingStretchSame with mixed columns, only the Stretch columns will match their weight/widths. +//----------------------------------------------------------------------------- +// About using column width: +// If a column is manual resizable or has a width specified with TableSetupColumn(): +// - you may use GetContentRegionAvail().x to query the width available in a given column. +// - right-side alignment features such as SetNextItemWidth(-x) or PushItemWidth(-x) will rely on this width. +// If the column is not resizable and has no width specified with TableSetupColumn(): +// - its width will be automatic and be set to the max of items submitted. +// - therefore you generally cannot have ALL items of the columns use e.g. SetNextItemWidth(-FLT_MIN). +// - but if the column has one or more items of known/fixed size, this will become the reference width used by SetNextItemWidth(-FLT_MIN). +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// TABLES CLIPPING/CULLING +//----------------------------------------------------------------------------- +// About clipping/culling of Rows in Tables: +// - For large numbers of rows, it is recommended you use ImGuiListClipper to only submit visible rows. +// ImGuiListClipper is reliant on the fact that rows are of equal height. +// See 'Demo->Tables->Vertical Scrolling' or 'Demo->Tables->Advanced' for a demo of using the clipper. +// - Note that auto-resizing columns don't play well with using the clipper. +// By default a table with _ScrollX but without _Resizable will have column auto-resize. +// So, if you want to use the clipper, make sure to either enable _Resizable, either setup columns width explicitly with _WidthFixed. +//----------------------------------------------------------------------------- +// About clipping/culling of Columns in Tables: +// - Both TableSetColumnIndex() and TableNextColumn() return true when the column is visible or performing +// width measurements. Otherwise, you may skip submitting the contents of a cell/column, BUT ONLY if you know +// it is not going to contribute to row height. +// In many situations, you may skip submitting contents for every column but one (e.g. the first one). +// - Case A: column is not hidden by user, and at least partially in sight (most common case). +// - Case B: column is clipped / out of sight (because of scrolling or parent ClipRect): TableNextColumn() return false as a hint but we still allow layout output. +// - Case C: column is hidden explicitly by the user (e.g. via the context menu, or _DefaultHide column flag, etc.). +// +// [A] [B] [C] +// TableNextColumn(): true false false -> [userland] when TableNextColumn() / TableSetColumnIndex() return false, user can skip submitting items but only if the column doesn't contribute to row height. +// SkipItems: false false true -> [internal] when SkipItems is true, most widgets will early out if submitted, resulting is no layout output. +// ClipRect: normal zero-width zero-width -> [internal] when ClipRect is zero, ItemAdd() will return false and most widgets will early out mid-way. +// ImDrawList output: normal dummy dummy -> [internal] when using the dummy channel, ImDrawList submissions (if any) will be wasted (because cliprect is zero-width anyway). +// +// - We need to distinguish those cases because non-hidden columns that are clipped outside of scrolling bounds should still contribute their height to the row. +// However, in the majority of cases, the contribution to row height is the same for all columns, or the tallest cells are known by the programmer. +//----------------------------------------------------------------------------- +// About clipping/culling of whole Tables: +// - Scrolling tables with a known outer size can be clipped earlier as BeginTable() will return false. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// [SECTION] Header mess +//----------------------------------------------------------------------------- + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +// System includes +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types +#endif +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Tables: Main code +//----------------------------------------------------------------------------- +// - TableFixFlags() [Internal] +// - TableFindByID() [Internal] +// - BeginTable() +// - BeginTableEx() [Internal] +// - TableBeginInitMemory() [Internal] +// - TableBeginApplyRequests() [Internal] +// - TableSetupColumnFlags() [Internal] +// - TableUpdateLayout() [Internal] +// - TableUpdateBorders() [Internal] +// - EndTable() +// - TableSetupColumn() +// - TableSetupScrollFreeze() +//----------------------------------------------------------------------------- + +// Configuration +static const int TABLE_DRAW_CHANNEL_BG0 = 0; +static const int TABLE_DRAW_CHANNEL_BG2_FROZEN = 1; +static const int TABLE_DRAW_CHANNEL_NOCLIP = 2; // When using ImGuiTableFlags_NoClip (this becomes the last visible channel) +static const float TABLE_BORDER_SIZE = 1.0f; // FIXME-TABLE: Currently hard-coded because of clipping assumptions with outer borders rendering. +static const float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f; // Extend outside inner borders. +static const float TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER = 0.06f; // Delay/timer before making the hover feedback (color+cursor) visible because tables/columns tends to be more cramped. + +// Helper +inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags, ImGuiWindow* outer_window) +{ + // Adjust flags: set default sizing policy + if ((flags & ImGuiTableFlags_SizingMask_) == 0) + flags |= ((flags & ImGuiTableFlags_ScrollX) || (outer_window->Flags & ImGuiWindowFlags_AlwaysAutoResize)) ? ImGuiTableFlags_SizingFixedFit : ImGuiTableFlags_SizingStretchSame; + + // Adjust flags: enable NoKeepColumnsVisible when using ImGuiTableFlags_SizingFixedSame + if ((flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame) + flags |= ImGuiTableFlags_NoKeepColumnsVisible; + + // Adjust flags: enforce borders when resizable + if (flags & ImGuiTableFlags_Resizable) + flags |= ImGuiTableFlags_BordersInnerV; + + // Adjust flags: disable NoHostExtendX/NoHostExtendY if we have any scrolling going on + if (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) + flags &= ~(ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY); + + // Adjust flags: NoBordersInBodyUntilResize takes priority over NoBordersInBody + if (flags & ImGuiTableFlags_NoBordersInBodyUntilResize) + flags &= ~ImGuiTableFlags_NoBordersInBody; + + // Adjust flags: disable saved settings if there's nothing to save + if ((flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable)) == 0) + flags |= ImGuiTableFlags_NoSavedSettings; + + // Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set) +#ifdef IMGUI_HAS_DOCK + ImGuiWindow* window_for_settings = outer_window->RootWindowDockStop; +#else + ImGuiWindow* window_for_settings = outer_window->RootWindow; +#endif + if (window_for_settings->Flags & ImGuiWindowFlags_NoSavedSettings) + flags |= ImGuiTableFlags_NoSavedSettings; + + return flags; +} + +ImGuiTable* ImGui::TableFindByID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return g.Tables.GetByKey(id); +} + +// Read about "TABLE SIZING" at the top of this file. +bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) +{ + ImGuiID id = GetID(str_id); + return BeginTableEx(str_id, id, columns_count, flags, outer_size, inner_width); +} + +bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* outer_window = GetCurrentWindow(); + if (outer_window->SkipItems) // Consistent with other tables + beneficial side effect that assert on miscalling EndTable() will be more visible. + return false; + + // Sanity checks + IM_ASSERT(columns_count > 0 && columns_count <= IMGUI_TABLE_MAX_COLUMNS && "Only 1..64 columns allowed!"); + if (flags & ImGuiTableFlags_ScrollX) + IM_ASSERT(inner_width >= 0.0f); + + // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping rules may evolve. + const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; + const ImVec2 avail_size = GetContentRegionAvail(); + ImVec2 actual_outer_size = CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f); + ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size); + if (use_child_window && IsClippedEx(outer_rect, 0, false)) + { + ItemSize(outer_rect); + return false; + } + + // Acquire storage for the table + ImGuiTable* table = g.Tables.GetOrAddByKey(id); + const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1; + const ImGuiID instance_id = id + instance_no; + const ImGuiTableFlags table_last_flags = table->Flags; + if (instance_no > 0) + IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID"); + + // Acquire temporary buffers + const int table_idx = g.Tables.GetIndex(table); + g.CurrentTableStackIdx++; + if (g.CurrentTableStackIdx + 1 > g.TablesTempDataStack.Size) + g.TablesTempDataStack.resize(g.CurrentTableStackIdx + 1, ImGuiTableTempData()); + ImGuiTableTempData* temp_data = table->TempData = &g.TablesTempDataStack[g.CurrentTableStackIdx]; + temp_data->TableIndex = table_idx; + table->DrawSplitter = &table->TempData->DrawSplitter; + table->DrawSplitter->Clear(); + + // Fix flags + table->IsDefaultSizingPolicy = (flags & ImGuiTableFlags_SizingMask_) == 0; + flags = TableFixFlags(flags, outer_window); + + // Initialize + table->ID = id; + table->Flags = flags; + table->InstanceCurrent = (ImS16)instance_no; + table->LastFrameActive = g.FrameCount; + table->OuterWindow = table->InnerWindow = outer_window; + table->ColumnsCount = columns_count; + table->IsLayoutLocked = false; + table->InnerWidth = inner_width; + temp_data->UserOuterSize = outer_size; + + // When not using a child window, WorkRect.Max will grow as we append contents. + if (use_child_window) + { + // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent + // (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing) + ImVec2 override_content_size(FLT_MAX, FLT_MAX); + if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY)) + override_content_size.y = FLT_MIN; + + // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and + // never lead to any scrolling). We don't handle inner_width < 0.0f, we could potentially use it to right-align + // based on the right side of the child window work rect, which would require knowing ahead if we are going to + // have decoration taking horizontal spaces (typically a vertical scrollbar). + if ((flags & ImGuiTableFlags_ScrollX) && inner_width > 0.0f) + override_content_size.x = inner_width; + + if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX) + SetNextWindowContentSize(ImVec2(override_content_size.x != FLT_MAX ? override_content_size.x : 0.0f, override_content_size.y != FLT_MAX ? override_content_size.y : 0.0f)); + + // Reset scroll if we are reactivating it + if ((table_last_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0) + SetNextWindowScroll(ImVec2(0.0f, 0.0f)); + + // Create scrolling region (without border and zero window padding) + ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; + BeginChildEx(name, instance_id, outer_rect.GetSize(), false, child_flags); + table->InnerWindow = g.CurrentWindow; + table->WorkRect = table->InnerWindow->WorkRect; + table->OuterRect = table->InnerWindow->Rect(); + table->InnerRect = table->InnerWindow->InnerRect; + IM_ASSERT(table->InnerWindow->WindowPadding.x == 0.0f && table->InnerWindow->WindowPadding.y == 0.0f && table->InnerWindow->WindowBorderSize == 0.0f); + } + else + { + // For non-scrolling tables, WorkRect == OuterRect == InnerRect. + // But at this point we do NOT have a correct value for .Max.y (unless a height has been explicitly passed in). It will only be updated in EndTable(). + table->WorkRect = table->OuterRect = table->InnerRect = outer_rect; + } + + // Push a standardized ID for both child-using and not-child-using tables + PushOverrideID(instance_id); + + // Backup a copy of host window members we will modify + ImGuiWindow* inner_window = table->InnerWindow; + table->HostIndentX = inner_window->DC.Indent.x; + table->HostClipRect = inner_window->ClipRect; + table->HostSkipItems = inner_window->SkipItems; + temp_data->HostBackupWorkRect = inner_window->WorkRect; + temp_data->HostBackupParentWorkRect = inner_window->ParentWorkRect; + temp_data->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset; + temp_data->HostBackupPrevLineSize = inner_window->DC.PrevLineSize; + temp_data->HostBackupCurrLineSize = inner_window->DC.CurrLineSize; + temp_data->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos; + temp_data->HostBackupItemWidth = outer_window->DC.ItemWidth; + temp_data->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size; + inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); + + // Padding and Spacing + // - None ........Content..... Pad .....Content........ + // - PadOuter | Pad ..Content..... Pad .....Content.. Pad | + // - PadInner ........Content.. Pad | Pad ..Content........ + // - PadOuter+PadInner | Pad ..Content.. Pad | Pad ..Content.. Pad | + const bool pad_outer_x = (flags & ImGuiTableFlags_NoPadOuterX) ? false : (flags & ImGuiTableFlags_PadOuterX) ? true : (flags & ImGuiTableFlags_BordersOuterV) != 0; + const bool pad_inner_x = (flags & ImGuiTableFlags_NoPadInnerX) ? false : true; + const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? TABLE_BORDER_SIZE : 0.0f; + const float inner_spacing_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) == 0) ? g.Style.CellPadding.x : 0.0f; + const float inner_padding_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) != 0) ? g.Style.CellPadding.x : 0.0f; + table->CellSpacingX1 = inner_spacing_explicit + inner_spacing_for_border; + table->CellSpacingX2 = inner_spacing_explicit; + table->CellPaddingX = inner_padding_explicit; + table->CellPaddingY = g.Style.CellPadding.y; + + const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; + const float outer_padding_explicit = pad_outer_x ? g.Style.CellPadding.x : 0.0f; + table->OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - table->CellPaddingX; + + table->CurrentColumn = -1; + table->CurrentRow = -1; + table->RowBgColorCounter = 0; + table->LastRowFlags = ImGuiTableRowFlags_None; + table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; + table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width + table->InnerClipRect.ClipWithFull(table->HostClipRect); + table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : inner_window->ClipRect.Max.y; + + table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow + table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow() + table->FreezeRowsRequest = table->FreezeRowsCount = 0; // This will be setup by TableSetupScrollFreeze(), if any + table->FreezeColumnsRequest = table->FreezeColumnsCount = 0; + table->IsUnfrozenRows = true; + table->DeclColumnsCount = 0; + + // Using opaque colors facilitate overlapping elements of the grid + table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong); + table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight); + + // Make table current + g.CurrentTable = table; + outer_window->DC.CurrentTableIdx = table_idx; + if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly. + inner_window->DC.CurrentTableIdx = table_idx; + + if ((table_last_flags & ImGuiTableFlags_Reorderable) && (flags & ImGuiTableFlags_Reorderable) == 0) + table->IsResetDisplayOrderRequest = true; + + // Mark as used + if (table_idx >= g.TablesLastTimeActive.Size) + g.TablesLastTimeActive.resize(table_idx + 1, -1.0f); + g.TablesLastTimeActive[table_idx] = (float)g.Time; + temp_data->LastTimeActive = (float)g.Time; + table->MemoryCompacted = false; + + // Setup memory buffer (clear data if columns count changed) + ImGuiTableColumn* old_columns_to_preserve = NULL; + void* old_columns_raw_data = NULL; + const int old_columns_count = table->Columns.size(); + if (old_columns_count != 0 && old_columns_count != columns_count) + { + // Attempt to preserve width on column count change (#4046) + old_columns_to_preserve = table->Columns.Data; + old_columns_raw_data = table->RawData; + table->RawData = NULL; + } + if (table->RawData == NULL) + { + TableBeginInitMemory(table, columns_count); + table->IsInitializing = table->IsSettingsRequestLoad = true; + } + if (table->IsResetAllRequest) + TableResetSettings(table); + if (table->IsInitializing) + { + // Initialize + table->SettingsOffset = -1; + table->IsSortSpecsDirty = true; + table->InstanceInteracted = -1; + table->ContextPopupColumn = -1; + table->ReorderColumn = table->ResizedColumn = table->LastResizedColumn = -1; + table->AutoFitSingleColumn = -1; + table->HoveredColumnBody = table->HoveredColumnBorder = -1; + for (int n = 0; n < columns_count; n++) + { + ImGuiTableColumn* column = &table->Columns[n]; + if (old_columns_to_preserve && n < old_columns_count) + { + // FIXME: We don't attempt to preserve column order in this path. + *column = old_columns_to_preserve[n]; + } + else + { + float width_auto = column->WidthAuto; + *column = ImGuiTableColumn(); + column->WidthAuto = width_auto; + column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker + column->IsEnabled = column->IsEnabledNextFrame = true; + } + column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImGuiTableColumnIdx)n; + } + } + if (old_columns_raw_data) + IM_FREE(old_columns_raw_data); + + // Load settings + if (table->IsSettingsRequestLoad) + TableLoadSettings(table); + + // Handle DPI/font resize + // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well. + // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition. + // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory. + // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path. + const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ? + if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit) + { + const float scale_factor = new_ref_scale_unit / table->RefScale; + //IMGUI_DEBUG_LOG("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor); + for (int n = 0; n < columns_count; n++) + table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor; + } + table->RefScale = new_ref_scale_unit; + + // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call.. + // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user. + // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. + inner_window->SkipItems = true; + + // Clear names + // At this point the ->NameOffset field of each column will be invalid until TableUpdateLayout() or the first call to TableSetupColumn() + if (table->ColumnsNames.Buf.Size > 0) + table->ColumnsNames.Buf.resize(0); + + // Apply queued resizing/reordering/hiding requests + TableBeginApplyRequests(table); + + return true; +} + +// For reference, the average total _allocation count_ for a table is: +// + 0 (for ImGuiTable instance, we are pooling allocations in g.Tables) +// + 1 (for table->RawData allocated below) +// + 1 (for table->ColumnsNames, if names are used) +// + 1 (for table->Splitter._Channels) +// + 2 * active_channels_count (for ImDrawCmd and ImDrawIdx buffers inside channels) +// Where active_channels_count is variable but often == columns_count or columns_count + 1, see TableSetupDrawChannels() for details. +// Unused channels don't perform their +2 allocations. +void ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count) +{ + // Allocate single buffer for our arrays + ImSpanAllocator<3> span_allocator; + span_allocator.Reserve(0, columns_count * sizeof(ImGuiTableColumn)); + span_allocator.Reserve(1, columns_count * sizeof(ImGuiTableColumnIdx)); + span_allocator.Reserve(2, columns_count * sizeof(ImGuiTableCellData), 4); + table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes()); + memset(table->RawData, 0, span_allocator.GetArenaSizeInBytes()); + span_allocator.SetArenaBasePtr(table->RawData); + span_allocator.GetSpan(0, &table->Columns); + span_allocator.GetSpan(1, &table->DisplayOrderToIndex); + span_allocator.GetSpan(2, &table->RowCellData); +} + +// Apply queued resizing/reordering/hiding requests +void ImGui::TableBeginApplyRequests(ImGuiTable* table) +{ + // Handle resizing request + // (We process this at the first TableBegin of the frame) + // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling? + if (table->InstanceCurrent == 0) + { + if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX) + TableSetColumnWidth(table->ResizedColumn, table->ResizedColumnNextWidth); + table->LastResizedColumn = table->ResizedColumn; + table->ResizedColumnNextWidth = FLT_MAX; + table->ResizedColumn = -1; + + // Process auto-fit for single column, which is a special case for stretch columns and fixed columns with FixedSame policy. + // FIXME-TABLE: Would be nice to redistribute available stretch space accordingly to other weights, instead of giving it all to siblings. + if (table->AutoFitSingleColumn != -1) + { + TableSetColumnWidth(table->AutoFitSingleColumn, table->Columns[table->AutoFitSingleColumn].WidthAuto); + table->AutoFitSingleColumn = -1; + } + } + + // Handle reordering request + // Note: we don't clear ReorderColumn after handling the request. + if (table->InstanceCurrent == 0) + { + if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1) + table->ReorderColumn = -1; + table->HeldHeaderColumn = -1; + if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) + { + // We need to handle reordering across hidden columns. + // In the configuration below, moving C to the right of E will lead to: + // ... C [D] E ---> ... [D] E C (Column name/index) + // ... 2 3 4 ... 2 3 4 (Display order) + const int reorder_dir = table->ReorderColumnDir; + IM_ASSERT(reorder_dir == -1 || reorder_dir == +1); + IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); + ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn]; + ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn]; + IM_UNUSED(dst_column); + const int src_order = src_column->DisplayOrder; + const int dst_order = dst_column->DisplayOrder; + src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order; + for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir) + table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)reorder_dir; + IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir); + + // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[], + // rebuild the later from the former. + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n; + table->ReorderColumnDir = 0; + table->IsSettingsDirty = true; + } + } + + // Handle display order reset request + if (table->IsResetDisplayOrderRequest) + { + for (int n = 0; n < table->ColumnsCount; n++) + table->DisplayOrderToIndex[n] = table->Columns[n].DisplayOrder = (ImGuiTableColumnIdx)n; + table->IsResetDisplayOrderRequest = false; + table->IsSettingsDirty = true; + } +} + +// Adjust flags: default width mode + stretch columns are not allowed when auto extending +static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags flags_in) +{ + ImGuiTableColumnFlags flags = flags_in; + + // Sizing Policy + if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) + { + const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); + if (table_sizing_policy == ImGuiTableFlags_SizingFixedFit || table_sizing_policy == ImGuiTableFlags_SizingFixedSame) + flags |= ImGuiTableColumnFlags_WidthFixed; + else + flags |= ImGuiTableColumnFlags_WidthStretch; + } + else + { + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used. + } + + // Resize + if ((table->Flags & ImGuiTableFlags_Resizable) == 0) + flags |= ImGuiTableColumnFlags_NoResize; + + // Sorting + if ((flags & ImGuiTableColumnFlags_NoSortAscending) && (flags & ImGuiTableColumnFlags_NoSortDescending)) + flags |= ImGuiTableColumnFlags_NoSort; + + // Indentation + if ((flags & ImGuiTableColumnFlags_IndentMask_) == 0) + flags |= (table->Columns.index_from_ptr(column) == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable; + + // Alignment + //if ((flags & ImGuiTableColumnFlags_AlignMask_) == 0) + // flags |= ImGuiTableColumnFlags_AlignCenter; + //IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_AlignMask_)); // Check that only 1 of each set is used. + + // Preserve status flags + column->Flags = flags | (column->Flags & ImGuiTableColumnFlags_StatusMask_); + + // Build an ordered list of available sort directions + column->SortDirectionsAvailCount = column->SortDirectionsAvailMask = column->SortDirectionsAvailList = 0; + if (table->Flags & ImGuiTableFlags_Sortable) + { + int count = 0, mask = 0, list = 0; + if ((flags & ImGuiTableColumnFlags_PreferSortAscending) != 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) { mask |= 1 << ImGuiSortDirection_Ascending; list |= ImGuiSortDirection_Ascending << (count << 1); count++; } + if ((flags & ImGuiTableColumnFlags_PreferSortDescending) != 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { mask |= 1 << ImGuiSortDirection_Descending; list |= ImGuiSortDirection_Descending << (count << 1); count++; } + if ((flags & ImGuiTableColumnFlags_PreferSortAscending) == 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) { mask |= 1 << ImGuiSortDirection_Ascending; list |= ImGuiSortDirection_Ascending << (count << 1); count++; } + if ((flags & ImGuiTableColumnFlags_PreferSortDescending) == 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { mask |= 1 << ImGuiSortDirection_Descending; list |= ImGuiSortDirection_Descending << (count << 1); count++; } + if ((table->Flags & ImGuiTableFlags_SortTristate) || count == 0) { mask |= 1 << ImGuiSortDirection_None; count++; } + column->SortDirectionsAvailList = (ImU8)list; + column->SortDirectionsAvailMask = (ImU8)mask; + column->SortDirectionsAvailCount = (ImU8)count; + ImGui::TableFixColumnSortDirection(table, column); + } +} + +// Layout columns for the frame. This is in essence the followup to BeginTable(). +// Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first. +// FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for _WidthAuto columns. +// Increase feedback side-effect with widgets relying on WorkRect.Max.x... Maybe provide a default distribution for _WidthAuto columns? +void ImGui::TableUpdateLayout(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(table->IsLayoutLocked == false); + + const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); + table->IsDefaultDisplayOrder = true; + table->ColumnsEnabledCount = 0; + table->EnabledMaskByIndex = 0x00; + table->EnabledMaskByDisplayOrder = 0x00; + table->LeftMostEnabledColumn = -1; + table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE + + // [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns. + // Process columns in their visible orders as we are building the Prev/Next indices. + int count_fixed = 0; // Number of columns that have fixed sizing policies + int count_stretch = 0; // Number of columns that have stretch sizing policies + int prev_visible_column_idx = -1; + bool has_auto_fit_request = false; + bool has_resizable = false; + float stretch_sum_width_auto = 0.0f; + float fixed_max_width_auto = 0.0f; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + const int column_n = table->DisplayOrderToIndex[order_n]; + if (column_n != order_n) + table->IsDefaultDisplayOrder = false; + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Clear column setup if not submitted by user. Currently we make it mandatory to call TableSetupColumn() every frame. + // It would easily work without but we're not ready to guarantee it since e.g. names need resubmission anyway. + // We take a slight shortcut but in theory we could be calling TableSetupColumn() here with dummy values, it should yield the same effect. + if (table->DeclColumnsCount <= column_n) + { + TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None); + column->NameOffset = -1; + column->UserID = 0; + column->InitStretchWeightOrWidth = -1.0f; + } + + // Update Enabled state, mark settings/sortspecs dirty + if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide)) + column->IsEnabledNextFrame = true; + if (column->IsEnabled != column->IsEnabledNextFrame) + { + column->IsEnabled = column->IsEnabledNextFrame; + table->IsSettingsDirty = true; + if (!column->IsEnabled && column->SortOrder != -1) + table->IsSortSpecsDirty = true; + } + if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_SortMulti)) + table->IsSortSpecsDirty = true; + + // Auto-fit unsized columns + const bool start_auto_fit = (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? (column->WidthRequest < 0.0f) : (column->StretchWeight < 0.0f); + if (start_auto_fit) + column->AutoFitQueue = column->CannotSkipItemsQueue = (1 << 3) - 1; // Fit for three frames + + if (!column->IsEnabled) + { + column->IndexWithinEnabledSet = -1; + continue; + } + + // Mark as enabled and link to previous/next enabled column + column->PrevEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx; + column->NextEnabledColumn = -1; + if (prev_visible_column_idx != -1) + table->Columns[prev_visible_column_idx].NextEnabledColumn = (ImGuiTableColumnIdx)column_n; + else + table->LeftMostEnabledColumn = (ImGuiTableColumnIdx)column_n; + column->IndexWithinEnabledSet = table->ColumnsEnabledCount++; + table->EnabledMaskByIndex |= (ImU64)1 << column_n; + table->EnabledMaskByDisplayOrder |= (ImU64)1 << column->DisplayOrder; + prev_visible_column_idx = column_n; + IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder); + + // Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping) + // Combine width from regular rows + width from headers unless requested not to. + if (!column->IsPreserveWidthAuto) + column->WidthAuto = TableGetColumnWidthAuto(table, column); + + // Non-resizable columns keep their requested width (apply user value regardless of IsPreserveWidthAuto) + const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0; + if (column_is_resizable) + has_resizable = true; + if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f && !column_is_resizable) + column->WidthAuto = column->InitStretchWeightOrWidth; + + if (column->AutoFitQueue != 0x00) + has_auto_fit_request = true; + if (column->Flags & ImGuiTableColumnFlags_WidthStretch) + { + stretch_sum_width_auto += column->WidthAuto; + count_stretch++; + } + else + { + fixed_max_width_auto = ImMax(fixed_max_width_auto, column->WidthAuto); + count_fixed++; + } + } + if ((table->Flags & ImGuiTableFlags_Sortable) && table->SortSpecsCount == 0 && !(table->Flags & ImGuiTableFlags_SortTristate)) + table->IsSortSpecsDirty = true; + table->RightMostEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx; + IM_ASSERT(table->LeftMostEnabledColumn >= 0 && table->RightMostEnabledColumn >= 0); + + // [Part 2] Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible + // to avoid the column fitting having to wait until the first visible frame of the child container (may or not be a good thing). + // FIXME-TABLE: for always auto-resizing columns may not want to do that all the time. + if (has_auto_fit_request && table->OuterWindow != table->InnerWindow) + table->InnerWindow->SkipItems = false; + if (has_auto_fit_request) + table->IsSettingsDirty = true; + + // [Part 3] Fix column flags and record a few extra information. + float sum_width_requests = 0.0f; // Sum of all width for fixed and auto-resize columns, excluding width contributed by Stretch columns but including spacing/padding. + float stretch_sum_weights = 0.0f; // Sum of all weights for stretch columns. + table->LeftMostStretchedColumn = table->RightMostStretchedColumn = -1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n))) + continue; + ImGuiTableColumn* column = &table->Columns[column_n]; + + const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0; + if (column->Flags & ImGuiTableColumnFlags_WidthFixed) + { + // Apply same widths policy + float width_auto = column->WidthAuto; + if (table_sizing_policy == ImGuiTableFlags_SizingFixedSame && (column->AutoFitQueue != 0x00 || !column_is_resizable)) + width_auto = fixed_max_width_auto; + + // Apply automatic width + // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!) + if (column->AutoFitQueue != 0x00) + column->WidthRequest = width_auto; + else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n))) + column->WidthRequest = width_auto; + + // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets + // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very + // large height (= first frame scrollbar display very off + clipper would skip lots of items). + // This is merely making the side-effect less extreme, but doesn't properly fixes it. + // FIXME: Move this to ->WidthGiven to avoid temporary lossyless? + // FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller. + if (column->AutoFitQueue > 0x01 && table->IsInitializing && !column->IsPreserveWidthAuto) + column->WidthRequest = ImMax(column->WidthRequest, table->MinColumnWidth * 4.0f); // FIXME-TABLE: Another constant/scale? + sum_width_requests += column->WidthRequest; + } + else + { + // Initialize stretch weight + if (column->AutoFitQueue != 0x00 || column->StretchWeight < 0.0f || !column_is_resizable) + { + if (column->InitStretchWeightOrWidth > 0.0f) + column->StretchWeight = column->InitStretchWeightOrWidth; + else if (table_sizing_policy == ImGuiTableFlags_SizingStretchProp) + column->StretchWeight = (column->WidthAuto / stretch_sum_width_auto) * count_stretch; + else + column->StretchWeight = 1.0f; + } + + stretch_sum_weights += column->StretchWeight; + if (table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder > column->DisplayOrder) + table->LeftMostStretchedColumn = (ImGuiTableColumnIdx)column_n; + if (table->RightMostStretchedColumn == -1 || table->Columns[table->RightMostStretchedColumn].DisplayOrder < column->DisplayOrder) + table->RightMostStretchedColumn = (ImGuiTableColumnIdx)column_n; + } + column->IsPreserveWidthAuto = false; + sum_width_requests += table->CellPaddingX * 2.0f; + } + table->ColumnsEnabledFixedCount = (ImGuiTableColumnIdx)count_fixed; + + // [Part 4] Apply final widths based on requested widths + const ImRect work_rect = table->WorkRect; + const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1); + const float width_avail = ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth(); + const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests; + float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; + table->ColumnsGivenWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n))) + continue; + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest) + if (column->Flags & ImGuiTableColumnFlags_WidthStretch) + { + float weight_ratio = column->StretchWeight / stretch_sum_weights; + column->WidthRequest = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, table->MinColumnWidth) + 0.01f); + width_remaining_for_stretched_columns -= column->WidthRequest; + } + + // [Resize Rule 1] The right-most Visible column is not resizable if there is at least one Stretch column + // See additional comments in TableSetColumnWidth(). + if (column->NextEnabledColumn == -1 && table->LeftMostStretchedColumn != -1) + column->Flags |= ImGuiTableColumnFlags_NoDirectResize_; + + // Assign final width, record width in case we will need to shrink + column->WidthGiven = ImFloor(ImMax(column->WidthRequest, table->MinColumnWidth)); + table->ColumnsGivenWidth += column->WidthGiven; + } + + // [Part 5] Redistribute stretch remainder width due to rounding (remainder width is < 1.0f * number of Stretch column). + // Using right-to-left distribution (more likely to match resizing cursor). + if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths)) + for (int order_n = table->ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) + { + if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; + if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + column->WidthRequest += 1.0f; + column->WidthGiven += 1.0f; + width_remaining_for_stretched_columns -= 1.0f; + } + + table->HoveredColumnBody = -1; + table->HoveredColumnBorder = -1; + const ImRect mouse_hit_rect(table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.Max.x, ImMax(table->OuterRect.Max.y, table->OuterRect.Min.y + table->LastOuterHeight)); + const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0); + + // [Part 6] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column + // Process columns in their visible orders as we are comparing the visible order and adjusting host_clip_rect while looping. + int visible_n = 0; + bool offset_x_frozen = (table->FreezeColumnsCount > 0); + float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1; + ImRect host_clip_rect = table->InnerClipRect; + //host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2; + table->VisibleMaskByIndex = 0x00; + table->RequestOutputMaskByIndex = 0x00; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + + column->NavLayerCurrent = (ImS8)((table->FreezeRowsCount > 0 || column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); + + if (offset_x_frozen && table->FreezeColumnsCount == visible_n) + { + offset_x += work_rect.Min.x - table->OuterRect.Min.x; + offset_x_frozen = false; + } + + // Clear status flags + column->Flags &= ~ImGuiTableColumnFlags_StatusMask_; + + if ((table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)) == 0) + { + // Hidden column: clear a few fields and we are done with it for the remainder of the function. + // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper. + column->MinX = column->MaxX = column->WorkMinX = column->ClipRect.Min.x = column->ClipRect.Max.x = offset_x; + column->WidthGiven = 0.0f; + column->ClipRect.Min.y = work_rect.Min.y; + column->ClipRect.Max.y = FLT_MAX; + column->ClipRect.ClipWithFull(host_clip_rect); + column->IsVisibleX = column->IsVisibleY = column->IsRequestOutput = false; + column->IsSkipItems = true; + column->ItemWidth = 1.0f; + continue; + } + + // Detect hovered column + if (is_hovering_table && g.IO.MousePos.x >= column->ClipRect.Min.x && g.IO.MousePos.x < column->ClipRect.Max.x) + table->HoveredColumnBody = (ImGuiTableColumnIdx)column_n; + + // Lock start position + column->MinX = offset_x; + + // Lock width based on start position and minimum/maximum width for this position + float max_width = TableGetMaxColumnWidth(table, column_n); + column->WidthGiven = ImMin(column->WidthGiven, max_width); + column->WidthGiven = ImMax(column->WidthGiven, ImMin(column->WidthRequest, table->MinColumnWidth)); + column->MaxX = offset_x + column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; + + // Lock other positions + // - ClipRect.Min.x: Because merging draw commands doesn't compare min boundaries, we make ClipRect.Min.x match left bounds to be consistent regardless of merging. + // - ClipRect.Max.x: using WorkMaxX instead of MaxX (aka including padding) makes things more consistent when resizing down, tho slightly detrimental to visibility in very-small column. + // - ClipRect.Max.x: using MaxX makes it easier for header to receive hover highlight with no discontinuity and display sorting arrow. + // - FIXME-TABLE: We want equal width columns to have equal (ClipRect.Max.x - WorkMinX) width, which means ClipRect.max.x cannot stray off host_clip_rect.Max.x else right-most column may appear shorter. + column->WorkMinX = column->MinX + table->CellPaddingX + table->CellSpacingX1; + column->WorkMaxX = column->MaxX - table->CellPaddingX - table->CellSpacingX2; // Expected max + column->ItemWidth = ImFloor(column->WidthGiven * 0.65f); + column->ClipRect.Min.x = column->MinX; + column->ClipRect.Min.y = work_rect.Min.y; + column->ClipRect.Max.x = column->MaxX; //column->WorkMaxX; + column->ClipRect.Max.y = FLT_MAX; + column->ClipRect.ClipWithFull(host_clip_rect); + + // Mark column as Clipped (not in sight) + // Note that scrolling tables (where inner_window != outer_window) handle Y clipped earlier in BeginTable() so IsVisibleY really only applies to non-scrolling tables. + // FIXME-TABLE: Because InnerClipRect.Max.y is conservatively ==outer_window->ClipRect.Max.y, we never can mark columns _Above_ the scroll line as not IsVisibleY. + // Taking advantage of LastOuterHeight would yield good results there... + // FIXME-TABLE: Y clipping is disabled because it effectively means not submitting will reduce contents width which is fed to outer_window->DC.CursorMaxPos.x, + // and this may be used (e.g. typically by outer_window using AlwaysAutoResize or outer_window's horizontal scrollbar, but could be something else). + // Possible solution to preserve last known content width for clipped column. Test 'table_reported_size' fails when enabling Y clipping and window is resized small. + column->IsVisibleX = (column->ClipRect.Max.x > column->ClipRect.Min.x); + column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y); + const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY; + if (is_visible) + table->VisibleMaskByIndex |= ((ImU64)1 << column_n); + + // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output. + column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0; + if (column->IsRequestOutput) + table->RequestOutputMaskByIndex |= ((ImU64)1 << column_n); + + // Mark column as SkipItems (ignoring all items/layout) + column->IsSkipItems = !column->IsEnabled || table->HostSkipItems; + if (column->IsSkipItems) + IM_ASSERT(!is_visible); + + // Update status flags + column->Flags |= ImGuiTableColumnFlags_IsEnabled; + if (is_visible) + column->Flags |= ImGuiTableColumnFlags_IsVisible; + if (column->SortOrder != -1) + column->Flags |= ImGuiTableColumnFlags_IsSorted; + if (table->HoveredColumnBody == column_n) + column->Flags |= ImGuiTableColumnFlags_IsHovered; + + // Alignment + // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in + // many cases (to be able to honor this we might be able to store a log of cells width, per row, for + // visible rows, but nav/programmatic scroll would have visible artifacts.) + //if (column->Flags & ImGuiTableColumnFlags_AlignRight) + // column->WorkMinX = ImMax(column->WorkMinX, column->MaxX - column->ContentWidthRowsUnfrozen); + //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) + // column->WorkMinX = ImLerp(column->WorkMinX, ImMax(column->StartX, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f); + + // Reset content width variables + column->ContentMaxXFrozen = column->ContentMaxXUnfrozen = column->WorkMinX; + column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX; + + // Don't decrement auto-fit counters until container window got a chance to submit its items + if (table->HostSkipItems == false) + { + column->AutoFitQueue >>= 1; + column->CannotSkipItemsQueue >>= 1; + } + + if (visible_n < table->FreezeColumnsCount) + host_clip_rect.Min.x = ImClamp(column->MaxX + TABLE_BORDER_SIZE, host_clip_rect.Min.x, host_clip_rect.Max.x); + + offset_x += column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; + visible_n++; + } + + // [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it) + // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either + // because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu. + const float unused_x1 = ImMax(table->WorkRect.Min.x, table->Columns[table->RightMostEnabledColumn].ClipRect.Max.x); + if (is_hovering_table && table->HoveredColumnBody == -1) + { + if (g.IO.MousePos.x >= unused_x1) + table->HoveredColumnBody = (ImGuiTableColumnIdx)table->ColumnsCount; + } + if (has_resizable == false && (table->Flags & ImGuiTableFlags_Resizable)) + table->Flags &= ~ImGuiTableFlags_Resizable; + + // [Part 8] Lock actual OuterRect/WorkRect right-most position. + // This is done late to handle the case of fixed-columns tables not claiming more widths that they need. + // Because of this we are careful with uses of WorkRect and InnerClipRect before this point. + if (table->RightMostStretchedColumn != -1) + table->Flags &= ~ImGuiTableFlags_NoHostExtendX; + if (table->Flags & ImGuiTableFlags_NoHostExtendX) + { + table->OuterRect.Max.x = table->WorkRect.Max.x = unused_x1; + table->InnerClipRect.Max.x = ImMin(table->InnerClipRect.Max.x, unused_x1); + } + table->InnerWindow->ParentWorkRect = table->WorkRect; + table->BorderX1 = table->InnerClipRect.Min.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : -1.0f); + table->BorderX2 = table->InnerClipRect.Max.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : +1.0f); + + // [Part 9] Allocate draw channels and setup background cliprect + TableSetupDrawChannels(table); + + // [Part 10] Hit testing on borders + if (table->Flags & ImGuiTableFlags_Resizable) + TableUpdateBorders(table); + table->LastFirstRowHeight = 0.0f; + table->IsLayoutLocked = true; + table->IsUsingHeaders = false; + + // [Part 11] Context menu + if (table->IsContextPopupOpen && table->InstanceCurrent == table->InstanceInteracted) + { + const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); + if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings)) + { + TableDrawContextMenu(table); + EndPopup(); + } + else + { + table->IsContextPopupOpen = false; + } + } + + // [Part 13] Sanitize and build sort specs before we have a change to use them for display. + // This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change) + if (table->IsSortSpecsDirty && (table->Flags & ImGuiTableFlags_Sortable)) + TableSortSpecsBuild(table); + + // Initial state + ImGuiWindow* inner_window = table->InnerWindow; + if (table->Flags & ImGuiTableFlags_NoClip) + table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); + else + inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); +} + +// Process hit-testing on resizing borders. Actual size change will be applied in EndTable() +// - Set table->HoveredColumnBorder with a short delay/timer to reduce feedback noise +// - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets +// overlapping the same area. +void ImGui::TableUpdateBorders(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable); + + // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and + // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not + // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height). + // Actual columns highlight/render will be performed in EndTable() and not be affected. + const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; + const float hit_y1 = table->OuterRect.Min.y; + const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight); + const float hit_y2_head = hit_y1 + table->LastFirstRowHeight; + + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) + continue; + + // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders() + const float border_y2_hit = (table->Flags & ImGuiTableFlags_NoBordersInBody) ? hit_y2_head : hit_y2_body; + if ((table->Flags & ImGuiTableFlags_NoBordersInBody) && table->IsUsingHeaders == false) + continue; + + if (table->FreezeColumnsCount > 0) + if (column->MaxX < table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsCount - 1]].MaxX) + continue; + + ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent); + ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit); + //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100)); + KeepAliveID(column_id); + + bool hovered = false, held = false; + bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); + if (pressed && IsMouseDoubleClicked(0)) + { + TableSetColumnWidthAutoSingle(table, column_n); + ClearActiveID(); + held = hovered = false; + } + if (held) + { + if (table->LastResizedColumn == -1) + table->ResizeLockMinContentsX2 = table->RightMostEnabledColumn != -1 ? table->Columns[table->RightMostEnabledColumn].MaxX : -FLT_MAX; + table->ResizedColumn = (ImGuiTableColumnIdx)column_n; + table->InstanceInteracted = table->InstanceCurrent; + } + if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held) + { + table->HoveredColumnBorder = (ImGuiTableColumnIdx)column_n; + SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + } +} + +void ImGui::EndTable() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); + + // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some + // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) + //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?"); + + // If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves to ensure all our + // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc. + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + + const ImGuiTableFlags flags = table->Flags; + ImGuiWindow* inner_window = table->InnerWindow; + ImGuiWindow* outer_window = table->OuterWindow; + ImGuiTableTempData* temp_data = table->TempData; + IM_ASSERT(inner_window == g.CurrentWindow); + IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow); + + if (table->IsInsideRow) + TableEndRow(table); + + // Context menu in columns body + if (flags & ImGuiTableFlags_ContextMenuInBody) + if (table->HoveredColumnBody != -1 && !IsAnyItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) + TableOpenContextMenu((int)table->HoveredColumnBody); + + // Finalize table height + inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize; + inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize; + inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos; + const float inner_content_max_y = table->RowPosY2; + IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y); + if (inner_window != outer_window) + inner_window->DC.CursorMaxPos.y = inner_content_max_y; + else if (!(flags & ImGuiTableFlags_NoHostExtendY)) + table->OuterRect.Max.y = table->InnerRect.Max.y = ImMax(table->OuterRect.Max.y, inner_content_max_y); // Patch OuterRect/InnerRect height + table->WorkRect.Max.y = ImMax(table->WorkRect.Max.y, table->OuterRect.Max.y); + table->LastOuterHeight = table->OuterRect.GetHeight(); + + // Setup inner scrolling range + // FIXME: This ideally should be done earlier, in BeginTable() SetNextWindowContentSize call, just like writing to inner_window->DC.CursorMaxPos.y, + // but since the later is likely to be impossible to do we'd rather update both axises together. + if (table->Flags & ImGuiTableFlags_ScrollX) + { + const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; + float max_pos_x = table->InnerWindow->DC.CursorMaxPos.x; + if (table->RightMostEnabledColumn != -1) + max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border); + if (table->ResizedColumn != -1) + max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2); + table->InnerWindow->DC.CursorMaxPos.x = max_pos_x; + } + + // Pop clipping rect + if (!(flags & ImGuiTableFlags_NoClip)) + inner_window->DrawList->PopClipRect(); + inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back(); + + // Draw borders + if ((flags & ImGuiTableFlags_Borders) != 0) + TableDrawBorders(table); + +#if 0 + // Strip out dummy channel draw calls + // We have no way to prevent user submitting direct ImDrawList calls into a hidden column (but ImGui:: calls will be clipped out) + // Pros: remove draw calls which will have no effect. since they'll have zero-size cliprect they may be early out anyway. + // Cons: making it harder for users watching metrics/debugger to spot the wasted vertices. + if (table->DummyDrawChannel != (ImGuiTableColumnIdx)-1) + { + ImDrawChannel* dummy_channel = &table->DrawSplitter._Channels[table->DummyDrawChannel]; + dummy_channel->_CmdBuffer.resize(0); + dummy_channel->_IdxBuffer.resize(0); + } +#endif + + // Flatten channels and merge draw calls + ImDrawListSplitter* splitter = table->DrawSplitter; + splitter->SetCurrentChannel(inner_window->DrawList, 0); + if ((table->Flags & ImGuiTableFlags_NoClip) == 0) + TableMergeDrawChannels(table); + splitter->Merge(inner_window->DrawList); + + // Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable() + const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1); + table->ColumnsAutoFitWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if (table->EnabledMaskByIndex & ((ImU64)1 << column_n)) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize)) + table->ColumnsAutoFitWidth += column->WidthRequest; + else + table->ColumnsAutoFitWidth += TableGetColumnWidthAuto(table, column); + } + + // Update scroll + if ((table->Flags & ImGuiTableFlags_ScrollX) == 0 && inner_window != outer_window) + { + inner_window->Scroll.x = 0.0f; + } + else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX && table->InstanceInteracted == table->InstanceCurrent) + { + // When releasing a column being resized, scroll to keep the resulting column in sight + const float neighbor_width_to_keep_visible = table->MinColumnWidth + table->CellPaddingX * 2.0f; + ImGuiTableColumn* column = &table->Columns[table->LastResizedColumn]; + if (column->MaxX < table->InnerClipRect.Min.x) + SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x - neighbor_width_to_keep_visible, 1.0f); + else if (column->MaxX > table->InnerClipRect.Max.x) + SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x + neighbor_width_to_keep_visible, 1.0f); + } + + // Apply resizing/dragging at the end of the frame + if (table->ResizedColumn != -1 && table->InstanceCurrent == table->InstanceInteracted) + { + ImGuiTableColumn* column = &table->Columns[table->ResizedColumn]; + const float new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + TABLE_RESIZE_SEPARATOR_HALF_THICKNESS); + const float new_width = ImFloor(new_x2 - column->MinX - table->CellSpacingX1 - table->CellPaddingX * 2.0f); + table->ResizedColumnNextWidth = new_width; + } + + // Pop from id stack + IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!"); + IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= temp_data->HostBackupItemWidthStackSize, "Too many PopItemWidth!"); + PopID(); + + // Restore window data that we modified + const ImVec2 backup_outer_max_pos = outer_window->DC.CursorMaxPos; + inner_window->WorkRect = temp_data->HostBackupWorkRect; + inner_window->ParentWorkRect = temp_data->HostBackupParentWorkRect; + inner_window->SkipItems = table->HostSkipItems; + outer_window->DC.CursorPos = table->OuterRect.Min; + outer_window->DC.ItemWidth = temp_data->HostBackupItemWidth; + outer_window->DC.ItemWidthStack.Size = temp_data->HostBackupItemWidthStackSize; + outer_window->DC.ColumnsOffset = temp_data->HostBackupColumnsOffset; + + // Layout in outer window + // (FIXME: To allow auto-fit and allow desirable effect of SameLine() we dissociate 'used' vs 'ideal' size by overriding + // CursorPosPrevLine and CursorMaxPos manually. That should be a more general layout feature, see same problem e.g. #3414) + if (inner_window != outer_window) + { + EndChild(); + } + else + { + ItemSize(table->OuterRect.GetSize()); + ItemAdd(table->OuterRect, 0); + } + + // Override declared contents width/height to enable auto-resize while not needlessly adding a scrollbar + if (table->Flags & ImGuiTableFlags_NoHostExtendX) + { + // FIXME-TABLE: Could we remove this section? + // ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents + IM_ASSERT((table->Flags & ImGuiTableFlags_ScrollX) == 0); + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth); + } + else if (temp_data->UserOuterSize.x <= 0.0f) + { + const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.x : 0.0f; + outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth + decoration_size - temp_data->UserOuterSize.x); + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth)); + } + else + { + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Max.x); + } + if (temp_data->UserOuterSize.y <= 0.0f) + { + const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.y : 0.0f; + outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y); + outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y)); + } + else + { + // OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags_NoHostExtendY is set) + outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, table->OuterRect.Max.y); + } + + // Save settings + if (table->IsSettingsDirty) + TableSaveSettings(table); + table->IsInitializing = false; + + // Clear or restore current table, if any + IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table); + IM_ASSERT(g.CurrentTableStackIdx >= 0); + g.CurrentTableStackIdx--; + temp_data = g.CurrentTableStackIdx >= 0 ? &g.TablesTempDataStack[g.CurrentTableStackIdx] : NULL; + g.CurrentTable = temp_data ? g.Tables.GetByIndex(temp_data->TableIndex) : NULL; + if (g.CurrentTable) + { + g.CurrentTable->TempData = temp_data; + g.CurrentTable->DrawSplitter = &temp_data->DrawSplitter; + } + outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1; +} + +// See "COLUMN SIZING POLICIES" comments at the top of this file +// If (init_width_or_weight <= 0.0f) it is ignored +void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); + IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); + if (table->DeclColumnsCount >= table->ColumnsCount) + { + IM_ASSERT_USER_ERROR(table->DeclColumnsCount < table->ColumnsCount, "Called TableSetupColumn() too many times!"); + return; + } + + ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; + table->DeclColumnsCount++; + + // Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa. + // Give a grace to users of ImGuiTableFlags_ScrollX. + if (table->IsDefaultSizingPolicy && (flags & ImGuiTableColumnFlags_WidthMask_) == 0 && (flags & ImGuiTableFlags_ScrollX) == 0) + IM_ASSERT(init_width_or_weight <= 0.0f && "Can only specify width/weight if sizing policy is set explicitly in either Table or Column."); + + // When passing a width automatically enforce WidthFixed policy + // (whereas TableSetupColumnFlags would default to WidthAuto if table is not Resizable) + if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0 && init_width_or_weight > 0.0f) + if ((table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedFit || (table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame) + flags |= ImGuiTableColumnFlags_WidthFixed; + + TableSetupColumnFlags(table, column, flags); + column->UserID = user_id; + flags = column->Flags; + + // Initialize defaults + column->InitStretchWeightOrWidth = init_width_or_weight; + if (table->IsInitializing) + { + // Init width or weight + if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f) + { + if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) + column->WidthRequest = init_width_or_weight; + if (flags & ImGuiTableColumnFlags_WidthStretch) + column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f; + + // Disable auto-fit if an explicit width/weight has been specified + if (init_width_or_weight > 0.0f) + column->AutoFitQueue = 0x00; + } + + // Init default visibility/sort state + if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0) + column->IsEnabled = column->IsEnabledNextFrame = false; + if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0) + { + column->SortOrder = 0; // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs. + column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); + } + } + + // Store name (append with zero-terminator in contiguous buffer) + column->NameOffset = -1; + if (label != NULL && label[0] != 0) + { + column->NameOffset = (ImS16)table->ColumnsNames.size(); + table->ColumnsNames.append(label, label + strlen(label) + 1); + } +} + +// [Public] +void ImGui::TableSetupScrollFreeze(int columns, int rows) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); + IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); + IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit + + table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImGuiTableColumnIdx)columns : 0; + table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; + table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImGuiTableColumnIdx)rows : 0; + table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; + table->IsUnfrozenRows = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b +} + +//----------------------------------------------------------------------------- +// [SECTION] Tables: Simple accessors +//----------------------------------------------------------------------------- +// - TableGetColumnCount() +// - TableGetColumnName() +// - TableGetColumnName() [Internal] +// - TableSetColumnEnabled() [Internal] +// - TableGetColumnFlags() +// - TableGetCellBgRect() [Internal] +// - TableGetColumnResizeID() [Internal] +// - TableGetHoveredColumn() [Internal] +// - TableSetBgColor() +//----------------------------------------------------------------------------- + +int ImGui::TableGetColumnCount() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + return table ? table->ColumnsCount : 0; +} + +const char* ImGui::TableGetColumnName(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return NULL; + if (column_n < 0) + column_n = table->CurrentColumn; + return TableGetColumnName(table, column_n); +} + +const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n) +{ + if (table->IsLayoutLocked == false && column_n >= table->DeclColumnsCount) + return ""; // NameOffset is invalid at this point + const ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->NameOffset == -1) + return ""; + return &table->ColumnsNames.Buf[column->NameOffset]; +} + +// Request enabling/disabling a column (often perceived as "showing/hiding" from users point of view) +// Note that end-user can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) +// Request will be applied during next layout, which happens on the first call to TableNextRow() after BeginTable() +// For the getter you can use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsEnabled) +void ImGui::TableSetColumnEnabled(int column_n, bool enabled) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL); + if (!table) + return; + if (column_n < 0) + column_n = table->CurrentColumn; + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + ImGuiTableColumn* column = &table->Columns[column_n]; + column->IsEnabledNextFrame = enabled; +} + +// We allow querying for an extra column in order to poll the IsHovered state of the right-most section +ImGuiTableColumnFlags ImGui::TableGetColumnFlags(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return ImGuiTableColumnFlags_None; + if (column_n < 0) + column_n = table->CurrentColumn; + if (column_n == table->ColumnsCount) + return (table->HoveredColumnBody == column_n) ? ImGuiTableColumnFlags_IsHovered : ImGuiTableColumnFlags_None; + return table->Columns[column_n].Flags; +} + +// Return the cell rectangle based on currently known height. +// - Important: we generally don't know our row height until the end of the row, so Max.y will be incorrect in many situations. +// The only case where this is correct is if we provided a min_row_height to TableNextRow() and don't go below it. +// - Important: if ImGuiTableFlags_PadOuterX is set but ImGuiTableFlags_PadInnerX is not set, the outer-most left and right +// columns report a small offset so their CellBgRect can extend up to the outer border. +ImRect ImGui::TableGetCellBgRect(const ImGuiTable* table, int column_n) +{ + const ImGuiTableColumn* column = &table->Columns[column_n]; + float x1 = column->MinX; + float x2 = column->MaxX; + if (column->PrevEnabledColumn == -1) + x1 -= table->CellSpacingX1; + if (column->NextEnabledColumn == -1) + x2 += table->CellSpacingX2; + return ImRect(x1, table->RowPosY1, x2, table->RowPosY2); +} + +// Return the resizing ID for the right-side of the given column. +ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no) +{ + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + ImGuiID id = table->ID + 1 + (instance_no * table->ColumnsCount) + column_n; + return id; +} + +// Return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. +int ImGui::TableGetHoveredColumn() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return -1; + return (int)table->HoveredColumnBody; +} + +void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(target != ImGuiTableBgTarget_None); + + if (color == IM_COL32_DISABLE) + color = 0; + + // We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time. + switch (target) + { + case ImGuiTableBgTarget_CellBg: + { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + if (column_n == -1) + column_n = table->CurrentColumn; + if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) + return; + if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n) + table->RowCellDataCurrent++; + ImGuiTableCellData* cell_data = &table->RowCellData[table->RowCellDataCurrent]; + cell_data->BgColor = color; + cell_data->Column = (ImGuiTableColumnIdx)column_n; + break; + } + case ImGuiTableBgTarget_RowBg0: + case ImGuiTableBgTarget_RowBg1: + { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + IM_ASSERT(column_n == -1); + int bg_idx = (target == ImGuiTableBgTarget_RowBg1) ? 1 : 0; + table->RowBgColor[bg_idx] = color; + break; + } + default: + IM_ASSERT(0); + } +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Row changes +//------------------------------------------------------------------------- +// - TableGetRowIndex() +// - TableNextRow() +// - TableBeginRow() [Internal] +// - TableEndRow() [Internal] +//------------------------------------------------------------------------- + +// [Public] Note: for row coloring we use ->RowBgColorCounter which is the same value without counting header rows +int ImGui::TableGetRowIndex() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return 0; + return table->CurrentRow; +} + +// [Public] Starts into the first cell of a new row +void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + if (table->IsInsideRow) + TableEndRow(table); + + table->LastRowFlags = table->RowFlags; + table->RowFlags = row_flags; + table->RowMinHeight = row_min_height; + TableBeginRow(table); + + // We honor min_row_height requested by user, but cannot guarantee per-row maximum height, + // because that would essentially require a unique clipping rectangle per-cell. + table->RowPosY2 += table->CellPaddingY * 2.0f; + table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height); + + // Disable output until user calls TableNextColumn() + table->InnerWindow->SkipItems = true; +} + +// [Internal] Called by TableNextRow() +void ImGui::TableBeginRow(ImGuiTable* table) +{ + ImGuiWindow* window = table->InnerWindow; + IM_ASSERT(!table->IsInsideRow); + + // New row + table->CurrentRow++; + table->CurrentColumn = -1; + table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE; + table->RowCellDataCurrent = -1; + table->IsInsideRow = true; + + // Begin frozen rows + float next_y1 = table->RowPosY2; + if (table->CurrentRow == 0 && table->FreezeRowsCount > 0) + next_y1 = window->DC.CursorPos.y = table->OuterRect.Min.y; + + table->RowPosY1 = table->RowPosY2 = next_y1; + table->RowTextBaseline = 0.0f; + table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent + window->DC.PrevLineTextBaseOffset = 0.0f; + window->DC.CursorMaxPos.y = next_y1; + + // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. + if (table->RowFlags & ImGuiTableRowFlags_Headers) + { + TableSetBgColor(ImGuiTableBgTarget_RowBg0, GetColorU32(ImGuiCol_TableHeaderBg)); + if (table->CurrentRow == 0) + table->IsUsingHeaders = true; + } +} + +// [Internal] Called by TableNextRow() +void ImGui::TableEndRow(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(window == table->InnerWindow); + IM_ASSERT(table->IsInsideRow); + + if (table->CurrentColumn != -1) + TableEndCell(table); + + // Logging + if (g.LogEnabled) + LogRenderedText(NULL, "|"); + + // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is + // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. + window->DC.CursorPos.y = table->RowPosY2; + + // Row background fill + const float bg_y1 = table->RowPosY1; + const float bg_y2 = table->RowPosY2; + const bool unfreeze_rows_actual = (table->CurrentRow + 1 == table->FreezeRowsCount); + const bool unfreeze_rows_request = (table->CurrentRow + 1 == table->FreezeRowsRequest); + if (table->CurrentRow == 0) + table->LastFirstRowHeight = bg_y2 - bg_y1; + + const bool is_visible = (bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y); + if (is_visible) + { + // Decide of background color for the row + ImU32 bg_col0 = 0; + ImU32 bg_col1 = 0; + if (table->RowBgColor[0] != IM_COL32_DISABLE) + bg_col0 = table->RowBgColor[0]; + else if (table->Flags & ImGuiTableFlags_RowBg) + bg_col0 = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); + if (table->RowBgColor[1] != IM_COL32_DISABLE) + bg_col1 = table->RowBgColor[1]; + + // Decide of top border color + ImU32 border_col = 0; + const float border_size = TABLE_BORDER_SIZE; + if (table->CurrentRow > 0 || table->InnerWindow == table->OuterWindow) + if (table->Flags & ImGuiTableFlags_BordersInnerH) + border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight; + + const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0; + const bool draw_strong_bottom_border = unfreeze_rows_actual; + if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) + { + // In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is + // always followed by a change of clipping rectangle we perform the smallest overwrite possible here. + if ((table->Flags & ImGuiTableFlags_NoClip) == 0) + window->DrawList->_CmdHeader.ClipRect = table->Bg0ClipRectForDrawCmd.ToVec4(); + table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0); + } + + // Draw row background + // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle + if (bg_col0 || bg_col1) + { + ImRect row_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2); + row_rect.ClipWith(table->BgClipRect); + if (bg_col0 != 0 && row_rect.Min.y < row_rect.Max.y) + window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col0); + if (bg_col1 != 0 && row_rect.Min.y < row_rect.Max.y) + window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col1); + } + + // Draw cell background color + if (draw_cell_bg_color) + { + ImGuiTableCellData* cell_data_end = &table->RowCellData[table->RowCellDataCurrent]; + for (ImGuiTableCellData* cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++) + { + const ImGuiTableColumn* column = &table->Columns[cell_data->Column]; + ImRect cell_bg_rect = TableGetCellBgRect(table, cell_data->Column); + cell_bg_rect.ClipWith(table->BgClipRect); + cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column->ClipRect.Min.x); // So that first column after frozen one gets clipped + cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column->MaxX); + window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor); + } + } + + // Draw top border + if (border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y) + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), border_col, border_size); + + // Draw bottom border at the row unfreezing mark (always strong) + if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y) + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size); + } + + // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) + // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and + // get the new cursor position. + if (unfreeze_rows_request) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + column->NavLayerCurrent = (ImS8)((column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); + } + if (unfreeze_rows_actual) + { + IM_ASSERT(table->IsUnfrozenRows == false); + table->IsUnfrozenRows = true; + + // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect + float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); + table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y); + table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y; + table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen; + IM_ASSERT(table->Bg2ClipRectForDrawCmd.Min.y <= table->Bg2ClipRectForDrawCmd.Max.y); + + float row_height = table->RowPosY2 - table->RowPosY1; + table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; + table->RowPosY1 = table->RowPosY2 - row_height; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + column->DrawChannelCurrent = column->DrawChannelUnfrozen; + column->ClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y; + } + + // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y + SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); + } + + if (!(table->RowFlags & ImGuiTableRowFlags_Headers)) + table->RowBgColorCounter++; + table->IsInsideRow = false; +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Columns changes +//------------------------------------------------------------------------- +// - TableGetColumnIndex() +// - TableSetColumnIndex() +// - TableNextColumn() +// - TableBeginCell() [Internal] +// - TableEndCell() [Internal] +//------------------------------------------------------------------------- + +int ImGui::TableGetColumnIndex() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return 0; + return table->CurrentColumn; +} + +// [Public] Append into a specific column +bool ImGui::TableSetColumnIndex(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + + if (table->CurrentColumn != column_n) + { + if (table->CurrentColumn != -1) + TableEndCell(table); + IM_ASSERT(column_n >= 0 && table->ColumnsCount); + TableBeginCell(table, column_n); + } + + // Return whether the column is visible. User may choose to skip submitting items based on this return value, + // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. + return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; +} + +// [Public] Append into the next column, wrap and create a new row when already on last column +bool ImGui::TableNextColumn() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + + if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount) + { + if (table->CurrentColumn != -1) + TableEndCell(table); + TableBeginCell(table, table->CurrentColumn + 1); + } + else + { + TableNextRow(); + TableBeginCell(table, 0); + } + + // Return whether the column is visible. User may choose to skip submitting items based on this return value, + // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. + int column_n = table->CurrentColumn; + return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; +} + + +// [Internal] Called by TableSetColumnIndex()/TableNextColumn() +// This is called very frequently, so we need to be mindful of unnecessary overhead. +// FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. +void ImGui::TableBeginCell(ImGuiTable* table, int column_n) +{ + ImGuiTableColumn* column = &table->Columns[column_n]; + ImGuiWindow* window = table->InnerWindow; + table->CurrentColumn = column_n; + + // Start position is roughly ~~ CellRect.Min + CellPadding + Indent + float start_x = column->WorkMinX; + if (column->Flags & ImGuiTableColumnFlags_IndentEnable) + start_x += table->RowIndentOffsetX; // ~~ += window.DC.Indent.x - table->HostIndentX, except we locked it for the row. + + window->DC.CursorPos.x = start_x; + window->DC.CursorPos.y = table->RowPosY1 + table->CellPaddingY; + window->DC.CursorMaxPos.x = window->DC.CursorPos.x; + window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT + window->DC.CurrLineTextBaseOffset = table->RowTextBaseline; + window->DC.NavLayerCurrent = (ImGuiNavLayer)column->NavLayerCurrent; + + window->WorkRect.Min.y = window->DC.CursorPos.y; + window->WorkRect.Min.x = column->WorkMinX; + window->WorkRect.Max.x = column->WorkMaxX; + window->DC.ItemWidth = column->ItemWidth; + + // To allow ImGuiListClipper to function we propagate our row height + if (!column->IsEnabled) + window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2); + + window->SkipItems = column->IsSkipItems; + if (column->IsSkipItems) + { + window->DC.LastItemId = 0; + window->DC.LastItemStatusFlags = 0; + } + + if (table->Flags & ImGuiTableFlags_NoClip) + { + // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. + table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); + //IM_ASSERT(table->DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP); + } + else + { + // FIXME-TABLE: Could avoid this if draw channel is dummy channel? + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); + } + + // Logging + ImGuiContext& g = *GImGui; + if (g.LogEnabled && !column->IsSkipItems) + { + LogRenderedText(&window->DC.CursorPos, "|"); + g.LogLinePosY = FLT_MAX; + } +} + +// [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn() +void ImGui::TableEndCell(ImGuiTable* table) +{ + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + ImGuiWindow* window = table->InnerWindow; + + // Report maximum position so we can infer content size per column. + float* p_max_pos_x; + if (table->RowFlags & ImGuiTableRowFlags_Headers) + p_max_pos_x = &column->ContentMaxXHeadersUsed; // Useful in case user submit contents in header row that is not a TableHeader() call + else + p_max_pos_x = table->IsUnfrozenRows ? &column->ContentMaxXUnfrozen : &column->ContentMaxXFrozen; + *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x); + table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->CellPaddingY); + column->ItemWidth = window->DC.ItemWidth; + + // Propagate text baseline for the entire row + // FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one. + table->RowTextBaseline = ImMax(table->RowTextBaseline, window->DC.PrevLineTextBaseOffset); +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Columns width management +//------------------------------------------------------------------------- +// - TableGetMaxColumnWidth() [Internal] +// - TableGetColumnWidthAuto() [Internal] +// - TableSetColumnWidth() +// - TableSetColumnWidthAutoSingle() [Internal] +// - TableSetColumnWidthAutoAll() [Internal] +// - TableUpdateColumnsWeightFromWidth() [Internal] +//------------------------------------------------------------------------- + +// Maximum column content width given current layout. Use column->MinX so this value on a per-column basis. +float ImGui::TableGetMaxColumnWidth(const ImGuiTable* table, int column_n) +{ + const ImGuiTableColumn* column = &table->Columns[column_n]; + float max_width = FLT_MAX; + const float min_column_distance = table->MinColumnWidth + table->CellPaddingX * 2.0f + table->CellSpacingX1 + table->CellSpacingX2; + if (table->Flags & ImGuiTableFlags_ScrollX) + { + // Frozen columns can't reach beyond visible width else scrolling will naturally break. + if (column->DisplayOrder < table->FreezeColumnsRequest) + { + max_width = (table->InnerClipRect.Max.x - (table->FreezeColumnsRequest - column->DisplayOrder) * min_column_distance) - column->MinX; + max_width = max_width - table->OuterPaddingX - table->CellPaddingX - table->CellSpacingX2; + } + } + else if ((table->Flags & ImGuiTableFlags_NoKeepColumnsVisible) == 0) + { + // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make + // sure they are all visible. Because of this we also know that all of the columns will always fit in + // table->WorkRect and therefore in table->InnerRect (because ScrollX is off) + // FIXME-TABLE: This is solved incorrectly but also quite a difficult problem to fix as we also want ClipRect width to match. + // See "table_width_distrib" and "table_width_keep_visible" tests + max_width = table->WorkRect.Max.x - (table->ColumnsEnabledCount - column->IndexWithinEnabledSet - 1) * min_column_distance - column->MinX; + //max_width -= table->CellSpacingX1; + max_width -= table->CellSpacingX2; + max_width -= table->CellPaddingX * 2.0f; + max_width -= table->OuterPaddingX; + } + return max_width; +} + +// Note this is meant to be stored in column->WidthAuto, please generally use the WidthAuto field +float ImGui::TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column) +{ + const float content_width_body = ImMax(column->ContentMaxXFrozen, column->ContentMaxXUnfrozen) - column->WorkMinX; + const float content_width_headers = column->ContentMaxXHeadersIdeal - column->WorkMinX; + float width_auto = content_width_body; + if (!(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth)) + width_auto = ImMax(width_auto, content_width_headers); + + // Non-resizable fixed columns preserve their requested width + if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f) + if (!(table->Flags & ImGuiTableFlags_Resizable) || (column->Flags & ImGuiTableColumnFlags_NoResize)) + width_auto = column->InitStretchWeightOrWidth; + + return ImMax(width_auto, table->MinColumnWidth); +} + +// 'width' = inner column width, without padding +void ImGui::TableSetColumnWidth(int column_n, float width) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && table->IsLayoutLocked == false); + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + ImGuiTableColumn* column_0 = &table->Columns[column_n]; + float column_0_width = width; + + // Apply constraints early + // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) + IM_ASSERT(table->MinColumnWidth > 0.0f); + const float min_width = table->MinColumnWidth; + const float max_width = ImMax(min_width, TableGetMaxColumnWidth(table, column_n)); + column_0_width = ImClamp(column_0_width, min_width, max_width); + if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width) + return; + + //IMGUI_DEBUG_LOG("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthGiven, column_0_width); + ImGuiTableColumn* column_1 = (column_0->NextEnabledColumn != -1) ? &table->Columns[column_0->NextEnabledColumn] : NULL; + + // In this surprisingly not simple because of how we support mixing Fixed and multiple Stretch columns. + // - All fixed: easy. + // - All stretch: easy. + // - One or more fixed + one stretch: easy. + // - One or more fixed + more than one stretch: tricky. + // Qt when manual resize is enabled only support a single _trailing_ stretch column. + + // When forwarding resize from Wn| to Fn+1| we need to be considerate of the _NoResize flag on Fn+1. + // FIXME-TABLE: Find a way to rewrite all of this so interactions feel more consistent for the user. + // Scenarios: + // - F1 F2 F3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. Subsequent columns will be offset. + // - F1 F2 F3 resize from F3| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered. + // - F1 F2 W3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered, but it doesn't make much sense as the Stretch column will always be minimal size. + // - F1 F2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 W2 W3 resize from W1| or W2| --> ok + // - W1 W2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 F2 F3 resize from F3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 F2 resize from F2| --> ok: no-op (disabled by Resize Rule 1) + // - W1 W2 F3 resize from W1| or W2| --> ok + // - W1 F2 W3 resize from W1| or F2| --> ok + // - F1 W2 F3 resize from W2| --> ok + // - F1 W3 F2 resize from W3| --> ok + // - W1 F2 F3 resize from W1| --> ok: equivalent to resizing |F2. F3 will not move. + // - W1 F2 F3 resize from F2| --> ok + // All resizes from a Wx columns are locking other columns. + + // Possible improvements: + // - W1 W2 W3 resize W1| --> to not be stuck, both W2 and W3 would stretch down. Seems possible to fix. Would be most beneficial to simplify resize of all-weighted columns. + // - W3 F1 F2 resize W3| --> to not be stuck past F1|, both F1 and F2 would need to stretch down, which would be lossy or ambiguous. Seems hard to fix. + + // [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout(). + + // If we have all Fixed columns OR resizing a Fixed column that doesn't come after a Stretch one, we can do an offsetting resize. + // This is the preferred resize path + if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed) + if (!column_1 || table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder >= column_0->DisplayOrder) + { + column_0->WidthRequest = column_0_width; + table->IsSettingsDirty = true; + return; + } + + // We can also use previous column if there's no next one (this is used when doing an auto-fit on the right-most stretch column) + if (column_1 == NULL) + column_1 = (column_0->PrevEnabledColumn != -1) ? &table->Columns[column_0->PrevEnabledColumn] : NULL; + if (column_1 == NULL) + return; + + // Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column. + // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) + float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width); + column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width; + IM_ASSERT(column_0_width > 0.0f && column_1_width > 0.0f); + column_0->WidthRequest = column_0_width; + column_1->WidthRequest = column_1_width; + if ((column_0->Flags | column_1->Flags) & ImGuiTableColumnFlags_WidthStretch) + TableUpdateColumnsWeightFromWidth(table); + table->IsSettingsDirty = true; +} + +// Disable clipping then auto-fit, will take 2 frames +// (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns) +void ImGui::TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n) +{ + // Single auto width uses auto-fit + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsEnabled) + return; + column->CannotSkipItemsQueue = (1 << 0); + table->AutoFitSingleColumn = (ImGuiTableColumnIdx)column_n; +} + +void ImGui::TableSetColumnWidthAutoAll(ImGuiTable* table) +{ + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) // Cannot reset weight of hidden stretch column + continue; + column->CannotSkipItemsQueue = (1 << 0); + column->AutoFitQueue = (1 << 1); + } +} + +void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table) +{ + IM_ASSERT(table->LeftMostStretchedColumn != -1 && table->RightMostStretchedColumn != -1); + + // Measure existing quantity + float visible_weight = 0.0f; + float visible_width = 0.0f; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + IM_ASSERT(column->StretchWeight > 0.0f); + visible_weight += column->StretchWeight; + visible_width += column->WidthRequest; + } + IM_ASSERT(visible_weight > 0.0f && visible_width > 0.0f); + + // Apply new weights + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + column->StretchWeight = (column->WidthRequest / visible_width) * visible_weight; + IM_ASSERT(column->StretchWeight > 0.0f); + } +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Drawing +//------------------------------------------------------------------------- +// - TablePushBackgroundChannel() [Internal] +// - TablePopBackgroundChannel() [Internal] +// - TableSetupDrawChannels() [Internal] +// - TableMergeDrawChannels() [Internal] +// - TableDrawBorders() [Internal] +//------------------------------------------------------------------------- + +// Bg2 is used by Selectable (and possibly other widgets) to render to the background. +// Unlike our Bg0/1 channel which we uses for RowBg/CellBg/Borders and where we guarantee all shapes to be CPU-clipped, the Bg2 channel being widgets-facing will rely on regular ClipRect. +void ImGui::TablePushBackgroundChannel() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiTable* table = g.CurrentTable; + + // Optimization: avoid SetCurrentChannel() + PushClipRect() + table->HostBackupInnerClipRect = window->ClipRect; + SetWindowClipRectBeforeSetChannel(window, table->Bg2ClipRectForDrawCmd); + table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Bg2DrawChannelCurrent); +} + +void ImGui::TablePopBackgroundChannel() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiTable* table = g.CurrentTable; + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + + // Optimization: avoid PopClipRect() + SetCurrentChannel() + SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); +} + +// Allocate draw channels. Called by TableUpdateLayout() +// - We allocate them following storage order instead of display order so reordering columns won't needlessly +// increase overall dormant memory cost. +// - We isolate headers draw commands in their own channels instead of just altering clip rects. +// This is in order to facilitate merging of draw commands. +// - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of channels. +// - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other +// channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged. +// - We allocate 1 or 2 background draw channels. This is because we know TablePushBackgroundChannel() is only used for +// horizontal spanning. If we allowed vertical spanning we'd need one background draw channel per merge group (1-4). +// Draw channel allocation (before merging): +// - NoClip --> 2+D+1 channels: bg0/1 + bg2 + foreground (same clip rect == always 1 draw call) +// - Clip --> 2+D+N channels +// - FreezeRows --> 2+D+N*2 (unless scrolling value is zero) +// - FreezeRows || FreezeColunns --> 3+D+N*2 (unless scrolling value is zero) +// Where D is 1 if any column is clipped or hidden (dummy channel) otherwise 0. +void ImGui::TableSetupDrawChannels(ImGuiTable* table) +{ + const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; + const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsEnabledCount; + const int channels_for_bg = 1 + 1 * freeze_row_multiplier; + const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || table->VisibleMaskByIndex != table->EnabledMaskByIndex) ? +1 : 0; + const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; + table->DrawSplitter->Split(table->InnerWindow->DrawList, channels_total); + table->DummyDrawChannel = (ImGuiTableDrawChannelIdx)((channels_for_dummy > 0) ? channels_total - 1 : -1); + table->Bg2DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG2_FROZEN; + table->Bg2DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)((table->FreezeRowsCount > 0) ? 2 + channels_for_row : TABLE_DRAW_CHANNEL_BG2_FROZEN); + + int draw_channel_current = 2; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->IsVisibleX && column->IsVisibleY) + { + column->DrawChannelFrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current); + column->DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row + 1 : 0)); + if (!(table->Flags & ImGuiTableFlags_NoClip)) + draw_channel_current++; + } + else + { + column->DrawChannelFrozen = column->DrawChannelUnfrozen = table->DummyDrawChannel; + } + column->DrawChannelCurrent = column->DrawChannelFrozen; + } + + // Initial draw cmd starts with a BgClipRect that matches the one of its host, to facilitate merge draw commands by default. + // All our cell highlight are manually clipped with BgClipRect. When unfreezing it will be made smaller to fit scrolling rect. + // (This technically isn't part of setting up draw channels, but is reasonably related to be done here) + table->BgClipRect = table->InnerClipRect; + table->Bg0ClipRectForDrawCmd = table->OuterWindow->ClipRect; + table->Bg2ClipRectForDrawCmd = table->HostClipRect; + IM_ASSERT(table->BgClipRect.Min.y <= table->BgClipRect.Max.y); +} + +// This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable(). +// For simplicity we call it TableMergeDrawChannels() but in fact it only reorder channels + overwrite ClipRect, +// actual merging is done by table->DrawSplitter.Merge() which is called right after TableMergeDrawChannels(). +// +// Columns where the contents didn't stray off their local clip rectangle can be merged. To achieve +// this we merge their clip rect and make them contiguous in the channel list, so they can be merged +// by the call to DrawSplitter.Merge() following to the call to this function. +// We reorder draw commands by arranging them into a maximum of 4 distinct groups: +// +// 1 group: 2 groups: 2 groups: 4 groups: +// [ 0. ] no freeze [ 0. ] row freeze [ 01 ] col freeze [ 01 ] row+col freeze +// [ .. ] or no scroll [ 2. ] and v-scroll [ .. ] and h-scroll [ 23 ] and v+h-scroll +// +// Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). +// When the contents of a column didn't stray off its limit, we move its channels into the corresponding group +// based on its position (within frozen rows/columns groups or not). +// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect. +// This function assume that each column are pointing to a distinct draw channel, +// otherwise merge_group->ChannelsCount will not match set bit count of merge_group->ChannelsMask. +// +// Column channels will not be merged into one of the 1-4 groups in the following cases: +// - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). +// Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds +// matches, by e.g. calling SetCursorScreenPos(). +// - The channel uses more than one draw command itself. We drop all our attempt at merging stuff here.. +// we could do better but it's going to be rare and probably not worth the hassle. +// Columns for which the draw channel(s) haven't been merged with other will use their own ImDrawCmd. +// +// This function is particularly tricky to understand.. take a breath. +void ImGui::TableMergeDrawChannels(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + ImDrawListSplitter* splitter = table->DrawSplitter; + const bool has_freeze_v = (table->FreezeRowsCount > 0); + const bool has_freeze_h = (table->FreezeColumnsCount > 0); + IM_ASSERT(splitter->_Current == 0); + + // Track which groups we are going to attempt to merge, and which channels goes into each group. + struct MergeGroup + { + ImRect ClipRect; + int ChannelsCount; + ImBitArray ChannelsMask; + + MergeGroup() { ChannelsCount = 0; } + }; + int merge_group_mask = 0x00; + MergeGroup merge_groups[4]; + + // 1. Scan channels and take note of those which can be merged + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) + continue; + ImGuiTableColumn* column = &table->Columns[column_n]; + + const int merge_group_sub_count = has_freeze_v ? 2 : 1; + for (int merge_group_sub_n = 0; merge_group_sub_n < merge_group_sub_count; merge_group_sub_n++) + { + const int channel_no = (merge_group_sub_n == 0) ? column->DrawChannelFrozen : column->DrawChannelUnfrozen; + + // Don't attempt to merge if there are multiple draw calls within the column + ImDrawChannel* src_channel = &splitter->_Channels[channel_no]; + if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0) + src_channel->_CmdBuffer.pop_back(); + if (src_channel->_CmdBuffer.Size != 1) + continue; + + // Find out the width of this merge group and check if it will fit in our column + // (note that we assume that rendering didn't stray on the left direction. we should need a CursorMinPos to detect it) + if (!(column->Flags & ImGuiTableColumnFlags_NoClip)) + { + float content_max_x; + if (!has_freeze_v) + content_max_x = ImMax(column->ContentMaxXUnfrozen, column->ContentMaxXHeadersUsed); // No row freeze + else if (merge_group_sub_n == 0) + content_max_x = ImMax(column->ContentMaxXFrozen, column->ContentMaxXHeadersUsed); // Row freeze: use width before freeze + else + content_max_x = column->ContentMaxXUnfrozen; // Row freeze: use width after freeze + if (content_max_x > column->ClipRect.Max.x) + continue; + } + + const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2); + IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS); + MergeGroup* merge_group = &merge_groups[merge_group_n]; + if (merge_group->ChannelsCount == 0) + merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + merge_group->ChannelsMask.SetBit(channel_no); + merge_group->ChannelsCount++; + merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); + merge_group_mask |= (1 << merge_group_n); + } + + // Invalidate current draw channel + // (we don't clear DrawChannelFrozen/DrawChannelUnfrozen solely to facilitate debugging/later inspection of data) + column->DrawChannelCurrent = (ImGuiTableDrawChannelIdx)-1; + } + + // [DEBUG] Display merge groups +#if 0 + if (g.IO.KeyShift) + for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) + { + MergeGroup* merge_group = &merge_groups[merge_group_n]; + if (merge_group->ChannelsCount == 0) + continue; + char buf[32]; + ImFormatString(buf, 32, "MG%d:%d", merge_group_n, merge_group->ChannelsCount); + ImVec2 text_pos = merge_group->ClipRect.Min + ImVec2(4, 4); + ImVec2 text_size = CalcTextSize(buf, NULL); + GetForegroundDrawList()->AddRectFilled(text_pos, text_pos + text_size, IM_COL32(0, 0, 0, 255)); + GetForegroundDrawList()->AddText(text_pos, IM_COL32(255, 255, 0, 255), buf, NULL); + GetForegroundDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 255, 0, 255)); + } +#endif + + // 2. Rewrite channel list in our preferred order + if (merge_group_mask != 0) + { + // We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels(). + const int LEADING_DRAW_CHANNELS = 2; + g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized + ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; + ImBitArray remaining_mask; // We need 132-bit of storage + remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count); + remaining_mask.ClearBit(table->Bg2DrawChannelUnfrozen); + IM_ASSERT(has_freeze_v == false || table->Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN); + int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS); + //ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect; + ImRect host_rect = table->HostClipRect; + for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) + { + if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount) + { + MergeGroup* merge_group = &merge_groups[merge_group_n]; + ImRect merge_clip_rect = merge_group->ClipRect; + + // Extend outer-most clip limits to match those of host, so draw calls can be merged even if + // outer-most columns have some outer padding offsetting them from their parent ClipRect. + // The principal cases this is dealing with are: + // - On a same-window table (not scrolling = single group), all fitting columns ClipRect -> will extend and match host ClipRect -> will merge + // - Columns can use padding and have left-most ClipRect.Min.x and right-most ClipRect.Max.x != from host ClipRect -> will extend and match host ClipRect -> will merge + // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit + // within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect. + if ((merge_group_n & 1) == 0 || !has_freeze_h) + merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x); + if ((merge_group_n & 2) == 0 || !has_freeze_v) + merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y); + if ((merge_group_n & 1) != 0) + merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x); + if ((merge_group_n & 2) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0) + merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y); +#if 0 + GetOverlayDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, 0, 1.0f); + GetOverlayDrawList()->AddLine(merge_group->ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200)); + GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200)); +#endif + remaining_count -= merge_group->ChannelsCount; + for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++) + remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n]; + for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++) + { + // Copy + overwrite new clip rect + if (!merge_group->ChannelsMask.TestBit(n)) + continue; + merge_group->ChannelsMask.ClearBit(n); + merge_channels_count--; + + ImDrawChannel* channel = &splitter->_Channels[n]; + IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect))); + channel->_CmdBuffer[0].ClipRect = merge_clip_rect.ToVec4(); + memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); + } + } + + // Make sure Bg2DrawChannelUnfrozen appears in the middle of our groups (whereas Bg0/Bg1 and Bg2 frozen are fixed to 0 and 1) + if (merge_group_n == 1 && has_freeze_v) + memcpy(dst_tmp++, &splitter->_Channels[table->Bg2DrawChannelUnfrozen], sizeof(ImDrawChannel)); + } + + // Append unmergeable channels that we didn't reorder at the end of the list + for (int n = 0; n < splitter->_Count && remaining_count != 0; n++) + { + if (!remaining_mask.TestBit(n)) + continue; + ImDrawChannel* channel = &splitter->_Channels[n]; + memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); + remaining_count--; + } + IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size); + memcpy(splitter->_Channels.Data + LEADING_DRAW_CHANNELS, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - LEADING_DRAW_CHANNELS) * sizeof(ImDrawChannel)); + } +} + +// FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow) +void ImGui::TableDrawBorders(ImGuiTable* table) +{ + ImGuiWindow* inner_window = table->InnerWindow; + if (!table->OuterWindow->ClipRect.Overlaps(table->OuterRect)) + return; + + ImDrawList* inner_drawlist = inner_window->DrawList; + table->DrawSplitter->SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0); + inner_drawlist->PushClipRect(table->Bg0ClipRectForDrawCmd.Min, table->Bg0ClipRectForDrawCmd.Max, false); + + // Draw inner border and resizing feedback + const float border_size = TABLE_BORDER_SIZE; + const float draw_y1 = table->InnerRect.Min.y; + const float draw_y2_body = table->InnerRect.Max.y; + const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight) : draw_y1; + if (table->Flags & ImGuiTableFlags_BordersInnerV) + { + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + const bool is_hovered = (table->HoveredColumnBorder == column_n); + const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent); + const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0; + const bool is_frozen_separator = (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1); + if (column->MaxX > table->InnerClipRect.Max.x && !is_resized) + continue; + + // Decide whether right-most column is visible + if (column->NextEnabledColumn == -1 && !is_resizable) + if ((table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame || (table->Flags & ImGuiTableFlags_NoHostExtendX)) + continue; + if (column->MaxX <= column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. + continue; + + // Draw in outer window so right-most column won't be clipped + // Always draw full height border when being resized/hovered, or on the delimitation of frozen column scrolling. + ImU32 col; + float draw_y2; + if (is_hovered || is_resized || is_frozen_separator) + { + draw_y2 = draw_y2_body; + col = is_resized ? GetColorU32(ImGuiCol_SeparatorActive) : is_hovered ? GetColorU32(ImGuiCol_SeparatorHovered) : table->BorderColorStrong; + } + else + { + draw_y2 = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? draw_y2_head : draw_y2_body; + col = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? table->BorderColorStrong : table->BorderColorLight; + } + + if (draw_y2 > draw_y1) + inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, border_size); + } + } + + // Draw outer border + // FIXME: could use AddRect or explicit VLine/HLine helper? + if (table->Flags & ImGuiTableFlags_BordersOuter) + { + // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call + // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their + // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part + // of it in inner window, and the part that's over scrollbars in the outer window..) + // Either solution currently won't allow us to use a larger border size: the border would clipped. + const ImRect outer_border = table->OuterRect; + const ImU32 outer_col = table->BorderColorStrong; + if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) + { + inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size); + } + else if (table->Flags & ImGuiTableFlags_BordersOuterV) + { + inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size); + inner_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size); + } + else if (table->Flags & ImGuiTableFlags_BordersOuterH) + { + inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size); + inner_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size); + } + } + if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) + { + // Draw bottom-most row border + const float border_y = table->RowPosY2; + if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) + inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size); + } + + inner_drawlist->PopClipRect(); +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Sorting +//------------------------------------------------------------------------- +// - TableGetSortSpecs() +// - TableFixColumnSortDirection() [Internal] +// - TableGetColumnNextSortDirection() [Internal] +// - TableSetColumnSortDirection() [Internal] +// - TableSortSpecsSanitize() [Internal] +// - TableSortSpecsBuild() [Internal] +//------------------------------------------------------------------------- + +// Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set) +// You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since +// last call, or the first time. +// Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! +ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL); + + if (!(table->Flags & ImGuiTableFlags_Sortable)) + return NULL; + + // Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths. + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + + if (table->IsSortSpecsDirty) + TableSortSpecsBuild(table); + + return &table->SortSpecs; +} + +static inline ImGuiSortDirection TableGetColumnAvailSortDirection(ImGuiTableColumn* column, int n) +{ + IM_ASSERT(n < column->SortDirectionsAvailCount); + return (column->SortDirectionsAvailList >> (n << 1)) & 0x03; +} + +// Fix sort direction if currently set on a value which is unavailable (e.g. activating NoSortAscending/NoSortDescending) +void ImGui::TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column) +{ + if (column->SortOrder == -1 || (column->SortDirectionsAvailMask & (1 << column->SortDirection)) != 0) + return; + column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0); + table->IsSortSpecsDirty = true; +} + +// Calculate next sort direction that would be set after clicking the column +// - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click. +// - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op. +IM_STATIC_ASSERT(ImGuiSortDirection_None == 0 && ImGuiSortDirection_Ascending == 1 && ImGuiSortDirection_Descending == 2); +ImGuiSortDirection ImGui::TableGetColumnNextSortDirection(ImGuiTableColumn* column) +{ + IM_ASSERT(column->SortDirectionsAvailCount > 0); + if (column->SortOrder == -1) + return TableGetColumnAvailSortDirection(column, 0); + for (int n = 0; n < 3; n++) + if (column->SortDirection == TableGetColumnAvailSortDirection(column, n)) + return TableGetColumnAvailSortDirection(column, (n + 1) % column->SortDirectionsAvailCount); + IM_ASSERT(0); + return ImGuiSortDirection_None; +} + +// Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert +// the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code. +void ImGui::TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + if (!(table->Flags & ImGuiTableFlags_SortMulti)) + append_to_sort_specs = false; + if (!(table->Flags & ImGuiTableFlags_SortTristate)) + IM_ASSERT(sort_direction != ImGuiSortDirection_None); + + ImGuiTableColumnIdx sort_order_max = 0; + if (append_to_sort_specs) + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) + sort_order_max = ImMax(sort_order_max, table->Columns[other_column_n].SortOrder); + + ImGuiTableColumn* column = &table->Columns[column_n]; + column->SortDirection = (ImU8)sort_direction; + if (column->SortDirection == ImGuiSortDirection_None) + column->SortOrder = -1; + else if (column->SortOrder == -1 || !append_to_sort_specs) + column->SortOrder = append_to_sort_specs ? sort_order_max + 1 : 0; + + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) + { + ImGuiTableColumn* other_column = &table->Columns[other_column_n]; + if (other_column != column && !append_to_sort_specs) + other_column->SortOrder = -1; + TableFixColumnSortDirection(table, other_column); + } + table->IsSettingsDirty = true; + table->IsSortSpecsDirty = true; +} + +void ImGui::TableSortSpecsSanitize(ImGuiTable* table) +{ + IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable); + + // Clear SortOrder from hidden column and verify that there's no gap or duplicate. + int sort_order_count = 0; + ImU64 sort_order_mask = 0x00; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->SortOrder != -1 && !column->IsEnabled) + column->SortOrder = -1; + if (column->SortOrder == -1) + continue; + sort_order_count++; + sort_order_mask |= ((ImU64)1 << column->SortOrder); + IM_ASSERT(sort_order_count < (int)sizeof(sort_order_mask) * 8); + } + + const bool need_fix_linearize = ((ImU64)1 << sort_order_count) != (sort_order_mask + 1); + const bool need_fix_single_sort_order = (sort_order_count > 1) && !(table->Flags & ImGuiTableFlags_SortMulti); + if (need_fix_linearize || need_fix_single_sort_order) + { + ImU64 fixed_mask = 0x00; + for (int sort_n = 0; sort_n < sort_order_count; sort_n++) + { + // Fix: Rewrite sort order fields if needed so they have no gap or duplicate. + // (e.g. SortOrder 0 disappeared, SortOrder 1..2 exists --> rewrite then as SortOrder 0..1) + int column_with_smallest_sort_order = -1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if ((fixed_mask & ((ImU64)1 << (ImU64)column_n)) == 0 && table->Columns[column_n].SortOrder != -1) + if (column_with_smallest_sort_order == -1 || table->Columns[column_n].SortOrder < table->Columns[column_with_smallest_sort_order].SortOrder) + column_with_smallest_sort_order = column_n; + IM_ASSERT(column_with_smallest_sort_order != -1); + fixed_mask |= ((ImU64)1 << column_with_smallest_sort_order); + table->Columns[column_with_smallest_sort_order].SortOrder = (ImGuiTableColumnIdx)sort_n; + + // Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set. + if (need_fix_single_sort_order) + { + sort_order_count = 1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if (column_n != column_with_smallest_sort_order) + table->Columns[column_n].SortOrder = -1; + break; + } + } + } + + // Fallback default sort order (if no column had the ImGuiTableColumnFlags_DefaultSort flag) + if (sort_order_count == 0 && !(table->Flags & ImGuiTableFlags_SortTristate)) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_NoSort)) + { + sort_order_count = 1; + column->SortOrder = 0; + column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0); + break; + } + } + + table->SortSpecsCount = (ImGuiTableColumnIdx)sort_order_count; +} + +void ImGui::TableSortSpecsBuild(ImGuiTable* table) +{ + IM_ASSERT(table->IsSortSpecsDirty); + TableSortSpecsSanitize(table); + + // Write output + ImGuiTableTempData* temp_data = table->TempData; + temp_data->SortSpecsMulti.resize(table->SortSpecsCount <= 1 ? 0 : table->SortSpecsCount); + ImGuiTableColumnSortSpecs* sort_specs = (table->SortSpecsCount == 0) ? NULL : (table->SortSpecsCount == 1) ? &temp_data->SortSpecsSingle : temp_data->SortSpecsMulti.Data; + if (sort_specs != NULL) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->SortOrder == -1) + continue; + IM_ASSERT(column->SortOrder < table->SortSpecsCount); + ImGuiTableColumnSortSpecs* sort_spec = &sort_specs[column->SortOrder]; + sort_spec->ColumnUserID = column->UserID; + sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n; + sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder; + sort_spec->SortDirection = column->SortDirection; + } + table->SortSpecs.Specs = sort_specs; + table->SortSpecs.SpecsCount = table->SortSpecsCount; + table->SortSpecs.SpecsDirty = true; // Mark as dirty for user + table->IsSortSpecsDirty = false; // Mark as not dirty for us +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Headers +//------------------------------------------------------------------------- +// - TableGetHeaderRowHeight() [Internal] +// - TableHeadersRow() +// - TableHeader() +//------------------------------------------------------------------------- + +float ImGui::TableGetHeaderRowHeight() +{ + // Caring for a minor edge case: + // Calculate row height, for the unlikely case that some labels may be taller than others. + // If we didn't do that, uneven header height would highlight but smaller one before the tallest wouldn't catch input for all height. + // In your custom header row you may omit this all together and just call TableNextRow() without a height... + float row_height = GetTextLineHeight(); + int columns_count = TableGetColumnCount(); + for (int column_n = 0; column_n < columns_count; column_n++) + if (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_IsEnabled) + row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y); + row_height += GetStyle().CellPadding.y * 2.0f; + return row_height; +} + +// [Public] This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). +// The intent is that advanced users willing to create customized headers would not need to use this helper +// and can create their own! For example: TableHeader() may be preceeded by Checkbox() or other custom widgets. +// See 'Demo->Tables->Custom headers' for a demonstration of implementing a custom version of this. +// This code is constructed to not make much use of internal functions, as it is intended to be a template to copy. +// FIXME-TABLE: TableOpenContextMenu() and TableGetHeaderRowHeight() are not public. +void ImGui::TableHeadersRow() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + + // Layout if not already done (this is automatically done by TableNextRow, we do it here solely to facilitate stepping in debugger as it is frequent to step in TableUpdateLayout) + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + + // Open row + const float row_y1 = GetCursorScreenPos().y; + const float row_height = TableGetHeaderRowHeight(); + TableNextRow(ImGuiTableRowFlags_Headers, row_height); + if (table->HostSkipItems) // Merely an optimization, you may skip in your own code. + return; + + const int columns_count = TableGetColumnCount(); + for (int column_n = 0; column_n < columns_count; column_n++) + { + if (!TableSetColumnIndex(column_n)) + continue; + + // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) + // - in your own code you may omit the PushID/PopID all-together, provided you know they won't collide + // - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier. + const char* name = TableGetColumnName(column_n); + PushID(table->InstanceCurrent * table->ColumnsCount + column_n); + TableHeader(name); + PopID(); + } + + // Allow opening popup from the right-most section after the last column. + ImVec2 mouse_pos = ImGui::GetMousePos(); + if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count) + if (mouse_pos.y >= row_y1 && mouse_pos.y < row_y1 + row_height) + TableOpenContextMenu(-1); // Will open a non-column-specific popup. +} + +// Emit a column header (text + optional sort order) +// We cpu-clip text here so that all columns headers can be merged into a same draw call. +// Note that because of how we cpu-clip and display sorting indicators, you _cannot_ use SameLine() after a TableHeader() +void ImGui::TableHeader(const char* label) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableHeader() after BeginTable()!"); + IM_ASSERT(table->CurrentColumn != -1); + const int column_n = table->CurrentColumn; + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Label + if (label == NULL) + label = ""; + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, true); + ImVec2 label_pos = window->DC.CursorPos; + + // If we already got a row height, there's use that. + // FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect? + ImRect cell_r = TableGetCellBgRect(table, column_n); + float label_height = ImMax(label_size.y, table->RowMinHeight - table->CellPaddingY * 2.0f); + + // Calculate ideal size for sort order arrow + float w_arrow = 0.0f; + float w_sort_text = 0.0f; + char sort_order_suf[4] = ""; + const float ARROW_SCALE = 0.65f; + if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) + { + w_arrow = ImFloor(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x); + if (column->SortOrder > 0) + { + ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), "%d", column->SortOrder + 1); + w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x; + } + } + + // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging. + float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow; + column->ContentMaxXHeadersUsed = ImMax(column->ContentMaxXHeadersUsed, column->WorkMaxX); + column->ContentMaxXHeadersIdeal = ImMax(column->ContentMaxXHeadersIdeal, max_pos_x); + + // Keep header highlighted when context menu is open. + const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent); + ImGuiID id = window->GetID(label); + ImRect bb(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0f)); + ItemSize(ImVec2(0.0f, label_height)); // Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal + if (!ItemAdd(bb, id)) + return; + + //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] + //GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] + + // Using AllowItemOverlap mode because we cover the whole cell, and we want user to be able to submit subsequent items. + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_AllowItemOverlap); + if (g.ActiveId != id) + SetItemAllowOverlap(); + if (held || hovered || selected) + { + const ImU32 col = GetColorU32(held ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + //RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + TableSetBgColor(ImGuiTableBgTarget_CellBg, col, table->CurrentColumn); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + } + else + { + // Submit single cell bg color in the case we didn't submit a full header row + if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0) + TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn); + } + if (held) + table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n; + window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; + + // Drag and drop to re-order columns. + // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone. + if (held && (table->Flags & ImGuiTableFlags_Reorderable) && IsMouseDragging(0) && !g.DragDropActive) + { + // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x + table->ReorderColumn = (ImGuiTableColumnIdx)column_n; + table->InstanceInteracted = table->InstanceCurrent; + + // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder. + if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) + if (ImGuiTableColumn* prev_column = (column->PrevEnabledColumn != -1) ? &table->Columns[column->PrevEnabledColumn] : NULL) + if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder)) + if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinEnabledSet < table->FreezeColumnsRequest)) + table->ReorderColumnDir = -1; + if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x) + if (ImGuiTableColumn* next_column = (column->NextEnabledColumn != -1) ? &table->Columns[column->NextEnabledColumn] : NULL) + if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder)) + if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (next_column->IndexWithinEnabledSet < table->FreezeColumnsRequest)) + table->ReorderColumnDir = +1; + } + + // Sort order arrow + const float ellipsis_max = cell_r.Max.x - w_arrow - w_sort_text; + if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) + { + if (column->SortOrder != -1) + { + float x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text); + float y = label_pos.y; + if (column->SortOrder > 0) + { + PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text, 0.70f)); + RenderText(ImVec2(x + g.Style.ItemInnerSpacing.x, y), sort_order_suf); + PopStyleColor(); + x += w_sort_text; + } + RenderArrow(window->DrawList, ImVec2(x, y), GetColorU32(ImGuiCol_Text), column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Up : ImGuiDir_Down, ARROW_SCALE); + } + + // Handle clicking on column header to adjust Sort Order + if (pressed && table->ReorderColumn != column_n) + { + ImGuiSortDirection sort_direction = TableGetColumnNextSortDirection(column); + TableSetColumnSortDirection(column_n, sort_direction, g.IO.KeyShift); + } + } + + // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will + // be merged into a single draw call. + //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); + + const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x); + if (text_clipped && hovered && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay) + SetTooltip("%.*s", (int)(label_end - label), label); + + // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden + if (IsMouseReleased(1) && IsItemHovered()) + TableOpenContextMenu(column_n); +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Context Menu +//------------------------------------------------------------------------- +// - TableOpenContextMenu() [Internal] +// - TableDrawContextMenu() [Internal] +//------------------------------------------------------------------------- + +// Use -1 to open menu not specific to a given column. +void ImGui::TableOpenContextMenu(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (column_n == -1 && table->CurrentColumn != -1) // When called within a column automatically use this one (for consistency) + column_n = table->CurrentColumn; + if (column_n == table->ColumnsCount) // To facilitate using with TableGetHoveredColumn() + column_n = -1; + IM_ASSERT(column_n >= -1 && column_n < table->ColumnsCount); + if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) + { + table->IsContextPopupOpen = true; + table->ContextPopupColumn = (ImGuiTableColumnIdx)column_n; + table->InstanceInteracted = table->InstanceCurrent; + const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); + OpenPopupEx(context_menu_id, ImGuiPopupFlags_None); + } +} + +// Output context menu into current window (generally a popup) +// FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data? +void ImGui::TableDrawContextMenu(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + bool want_separator = false; + const int column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1; + ImGuiTableColumn* column = (column_n != -1) ? &table->Columns[column_n] : NULL; + + // Sizing + if (table->Flags & ImGuiTableFlags_Resizable) + { + if (column != NULL) + { + const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled; + if (MenuItem("Size column to fit###SizeOne", NULL, false, can_resize)) + TableSetColumnWidthAutoSingle(table, column_n); + } + + const char* size_all_desc; + if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount && (table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame) + size_all_desc = "Size all columns to fit###SizeAll"; // All fixed + else + size_all_desc = "Size all columns to default###SizeAll"; // All stretch or mixed + if (MenuItem(size_all_desc, NULL)) + TableSetColumnWidthAutoAll(table); + want_separator = true; + } + + // Ordering + if (table->Flags & ImGuiTableFlags_Reorderable) + { + if (MenuItem("Reset order", NULL, false, !table->IsDefaultDisplayOrder)) + table->IsResetDisplayOrderRequest = true; + want_separator = true; + } + + // Reset all (should work but seems unnecessary/noisy to expose?) + //if (MenuItem("Reset all")) + // table->IsResetAllRequest = true; + + // Sorting + // (modify TableOpenContextMenu() to add _Sortable flag if enabling this) +#if 0 + if ((table->Flags & ImGuiTableFlags_Sortable) && column != NULL && (column->Flags & ImGuiTableColumnFlags_NoSort) == 0) + { + if (want_separator) + Separator(); + want_separator = true; + + bool append_to_sort_specs = g.IO.KeyShift; + if (MenuItem("Sort in Ascending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Ascending, (column->Flags & ImGuiTableColumnFlags_NoSortAscending) == 0)) + TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Ascending, append_to_sort_specs); + if (MenuItem("Sort in Descending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Descending, (column->Flags & ImGuiTableColumnFlags_NoSortDescending) == 0)) + TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Descending, append_to_sort_specs); + } +#endif + + // Hiding / Visibility + if (table->Flags & ImGuiTableFlags_Hideable) + { + if (want_separator) + Separator(); + want_separator = true; + + PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) + { + ImGuiTableColumn* other_column = &table->Columns[other_column_n]; + const char* name = TableGetColumnName(table, other_column_n); + if (name == NULL || name[0] == 0) + name = ""; + + // Make sure we can't hide the last active column + bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; + if (other_column->IsEnabled && table->ColumnsEnabledCount <= 1) + menu_item_active = false; + if (MenuItem(name, NULL, other_column->IsEnabled, menu_item_active)) + other_column->IsEnabledNextFrame = !other_column->IsEnabled; + } + PopItemFlag(); + } +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Settings (.ini data) +//------------------------------------------------------------------------- +// FIXME: The binding/finding/creating flow are too confusing. +//------------------------------------------------------------------------- +// - TableSettingsInit() [Internal] +// - TableSettingsCalcChunkSize() [Internal] +// - TableSettingsCreate() [Internal] +// - TableSettingsFindByID() [Internal] +// - TableGetBoundSettings() [Internal] +// - TableResetSettings() +// - TableSaveSettings() [Internal] +// - TableLoadSettings() [Internal] +// - TableSettingsHandler_ClearAll() [Internal] +// - TableSettingsHandler_ApplyAll() [Internal] +// - TableSettingsHandler_ReadOpen() [Internal] +// - TableSettingsHandler_ReadLine() [Internal] +// - TableSettingsHandler_WriteAll() [Internal] +// - TableSettingsInstallHandler() [Internal] +//------------------------------------------------------------------------- +// [Init] 1: TableSettingsHandler_ReadXXXX() Load and parse .ini file into TableSettings. +// [Main] 2: TableLoadSettings() When table is created, bind Table to TableSettings, serialize TableSettings data into Table. +// [Main] 3: TableSaveSettings() When table properties are modified, serialize Table data into bound or new TableSettings, mark .ini as dirty. +// [Main] 4: TableSettingsHandler_WriteAll() When .ini file is dirty (which can come from other source), save TableSettings into .ini file. +//------------------------------------------------------------------------- + +// Clear and initialize empty settings instance +static void TableSettingsInit(ImGuiTableSettings* settings, ImGuiID id, int columns_count, int columns_count_max) +{ + IM_PLACEMENT_NEW(settings) ImGuiTableSettings(); + ImGuiTableColumnSettings* settings_column = settings->GetColumnSettings(); + for (int n = 0; n < columns_count_max; n++, settings_column++) + IM_PLACEMENT_NEW(settings_column) ImGuiTableColumnSettings(); + settings->ID = id; + settings->ColumnsCount = (ImGuiTableColumnIdx)columns_count; + settings->ColumnsCountMax = (ImGuiTableColumnIdx)columns_count_max; + settings->WantApply = true; +} + +static size_t TableSettingsCalcChunkSize(int columns_count) +{ + return sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings); +} + +ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count) +{ + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count)); + TableSettingsInit(settings, id, columns_count, columns_count); + return settings; +} + +// Find existing settings +ImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id) +{ + // FIXME-OPT: Might want to store a lookup map for this? + ImGuiContext& g = *GImGui; + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID == id) + return settings; + return NULL; +} + +// Get settings for a given table, NULL if none +ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table) +{ + if (table->SettingsOffset != -1) + { + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); + IM_ASSERT(settings->ID == table->ID); + if (settings->ColumnsCountMax >= table->ColumnsCount) + return settings; // OK + settings->ID = 0; // Invalidate storage, we won't fit because of a count change + } + return NULL; +} + +// Restore initial state of table (with or without saved settings) +void ImGui::TableResetSettings(ImGuiTable* table) +{ + table->IsInitializing = table->IsSettingsDirty = true; + table->IsResetAllRequest = false; + table->IsSettingsRequestLoad = false; // Don't reload from ini + table->SettingsLoadedFlags = ImGuiTableFlags_None; // Mark as nothing loaded so our initialized data becomes authoritative +} + +void ImGui::TableSaveSettings(ImGuiTable* table) +{ + table->IsSettingsDirty = false; + if (table->Flags & ImGuiTableFlags_NoSavedSettings) + return; + + // Bind or create settings data + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = TableGetBoundSettings(table); + if (settings == NULL) + { + settings = TableSettingsCreate(table->ID, table->ColumnsCount); + table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); + } + settings->ColumnsCount = (ImGuiTableColumnIdx)table->ColumnsCount; + + // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings + IM_ASSERT(settings->ID == table->ID); + IM_ASSERT(settings->ColumnsCount == table->ColumnsCount && settings->ColumnsCountMax >= settings->ColumnsCount); + ImGuiTableColumn* column = table->Columns.Data; + ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); + + bool save_ref_scale = false; + settings->SaveFlags = ImGuiTableFlags_None; + for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) + { + const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest; + column_settings->WidthOrWeight = width_or_weight; + column_settings->Index = (ImGuiTableColumnIdx)n; + column_settings->DisplayOrder = column->DisplayOrder; + column_settings->SortOrder = column->SortOrder; + column_settings->SortDirection = column->SortDirection; + column_settings->IsEnabled = column->IsEnabled; + column_settings->IsStretch = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0; + if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0) + save_ref_scale = true; + + // We skip saving some data in the .ini file when they are unnecessary to restore our state. + // Note that fixed width where initial width was derived from auto-fit will always be saved as InitStretchWeightOrWidth will be 0.0f. + // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present. + if (width_or_weight != column->InitStretchWeightOrWidth) + settings->SaveFlags |= ImGuiTableFlags_Resizable; + if (column->DisplayOrder != n) + settings->SaveFlags |= ImGuiTableFlags_Reorderable; + if (column->SortOrder != -1) + settings->SaveFlags |= ImGuiTableFlags_Sortable; + if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) + settings->SaveFlags |= ImGuiTableFlags_Hideable; + } + settings->SaveFlags &= table->Flags; + settings->RefScale = save_ref_scale ? table->RefScale : 0.0f; + + MarkIniSettingsDirty(); +} + +void ImGui::TableLoadSettings(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + table->IsSettingsRequestLoad = false; + if (table->Flags & ImGuiTableFlags_NoSavedSettings) + return; + + // Bind settings + ImGuiTableSettings* settings; + if (table->SettingsOffset == -1) + { + settings = TableSettingsFindByID(table->ID); + if (settings == NULL) + return; + if (settings->ColumnsCount != table->ColumnsCount) // Allow settings if columns count changed. We could otherwise decide to return... + table->IsSettingsDirty = true; + table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); + } + else + { + settings = TableGetBoundSettings(table); + } + + table->SettingsLoadedFlags = settings->SaveFlags; + table->RefScale = settings->RefScale; + + // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn + ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); + ImU64 display_order_mask = 0; + for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) + { + int column_n = column_settings->Index; + if (column_n < 0 || column_n >= table->ColumnsCount) + continue; + + ImGuiTableColumn* column = &table->Columns[column_n]; + if (settings->SaveFlags & ImGuiTableFlags_Resizable) + { + if (column_settings->IsStretch) + column->StretchWeight = column_settings->WidthOrWeight; + else + column->WidthRequest = column_settings->WidthOrWeight; + column->AutoFitQueue = 0x00; + } + if (settings->SaveFlags & ImGuiTableFlags_Reorderable) + column->DisplayOrder = column_settings->DisplayOrder; + else + column->DisplayOrder = (ImGuiTableColumnIdx)column_n; + display_order_mask |= (ImU64)1 << column->DisplayOrder; + column->IsEnabled = column->IsEnabledNextFrame = column_settings->IsEnabled; + column->SortOrder = column_settings->SortOrder; + column->SortDirection = column_settings->SortDirection; + } + + // Validate and fix invalid display order data + const ImU64 expected_display_order_mask = (settings->ColumnsCount == 64) ? ~0 : ((ImU64)1 << settings->ColumnsCount) - 1; + if (display_order_mask != expected_display_order_mask) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + table->Columns[column_n].DisplayOrder = (ImGuiTableColumnIdx)column_n; + + // Rebuild index + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n; +} + +static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Tables.GetSize(); i++) + g.Tables.GetByIndex(i)->SettingsOffset = -1; + g.SettingsTables.clear(); +} + +// Apply to existing windows (if any) +static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Tables.GetSize(); i++) + { + ImGuiTable* table = g.Tables.GetByIndex(i); + table->IsSettingsRequestLoad = true; + table->SettingsOffset = -1; + } +} + +static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + ImGuiID id = 0; + int columns_count = 0; + if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) + return NULL; + + if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(id)) + { + if (settings->ColumnsCountMax >= columns_count) + { + TableSettingsInit(settings, id, columns_count, settings->ColumnsCountMax); // Recycle + return settings; + } + settings->ID = 0; // Invalidate storage, we won't fit because of a count change + } + return ImGui::TableSettingsCreate(id, columns_count); +} + +static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) +{ + // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" + ImGuiTableSettings* settings = (ImGuiTableSettings*)entry; + float f = 0.0f; + int column_n = 0, r = 0, n = 0; + + if (sscanf(line, "RefScale=%f", &f) == 1) { settings->RefScale = f; return; } + + if (sscanf(line, "Column %d%n", &column_n, &r) == 1) + { + if (column_n < 0 || column_n >= settings->ColumnsCount) + return; + line = ImStrSkipBlank(line + r); + char c = 0; + ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n; + column->Index = (ImGuiTableColumnIdx)column_n; + if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; } + if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsStretch = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Weight=%f%n", &f, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsStretch = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->IsEnabled = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } + if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImGuiTableColumnIdx)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } + if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImGuiTableColumnIdx)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } + } +} + +static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +{ + ImGuiContext& g = *ctx; + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + { + if (settings->ID == 0) // Skip ditched settings + continue; + + // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped + // (e.g. Order was unchanged) + const bool save_size = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0; + const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; + const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; + const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0; + if (!save_size && !save_visible && !save_order && !save_sort) + continue; + + buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve + buf->appendf("[%s][0x%08X,%d]\n", handler->TypeName, settings->ID, settings->ColumnsCount); + if (settings->RefScale != 0.0f) + buf->appendf("RefScale=%g\n", settings->RefScale); + ImGuiTableColumnSettings* column = settings->GetColumnSettings(); + for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++) + { + // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" + bool save_column = column->UserID != 0 || save_size || save_visible || save_order || (save_sort && column->SortOrder != -1); + if (!save_column) + continue; + buf->appendf("Column %-2d", column_n); + if (column->UserID != 0) buf->appendf(" UserID=%08X", column->UserID); + if (save_size && column->IsStretch) buf->appendf(" Weight=%.4f", column->WidthOrWeight); + if (save_size && !column->IsStretch) buf->appendf(" Width=%d", (int)column->WidthOrWeight); + if (save_visible) buf->appendf(" Visible=%d", column->IsEnabled); + if (save_order) buf->appendf(" Order=%d", column->DisplayOrder); + if (save_sort && column->SortOrder != -1) buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); + buf->append("\n"); + } + buf->append("\n"); + } +} + +void ImGui::TableSettingsInstallHandler(ImGuiContext* context) +{ + ImGuiContext& g = *context; + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Table"; + ini_handler.TypeHash = ImHashStr("Table"); + ini_handler.ClearAllFn = TableSettingsHandler_ClearAll; + ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen; + ini_handler.ReadLineFn = TableSettingsHandler_ReadLine; + ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll; + ini_handler.WriteAllFn = TableSettingsHandler_WriteAll; + g.SettingsHandlers.push_back(ini_handler); +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Garbage Collection +//------------------------------------------------------------------------- +// - TableRemove() [Internal] +// - TableGcCompactTransientBuffers() [Internal] +// - TableGcCompactSettings() [Internal] +//------------------------------------------------------------------------- + +// Remove Table (currently only used by TestEngine) +void ImGui::TableRemove(ImGuiTable* table) +{ + //IMGUI_DEBUG_LOG("TableRemove() id=0x%08X\n", table->ID); + ImGuiContext& g = *GImGui; + int table_idx = g.Tables.GetIndex(table); + //memset(table->RawData.Data, 0, table->RawData.size_in_bytes()); + //memset(table, 0, sizeof(ImGuiTable)); + g.Tables.Remove(table->ID, table); + g.TablesLastTimeActive[table_idx] = -1.0f; +} + +// Free up/compact internal Table buffers for when it gets unused +void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table) +{ + //IMGUI_DEBUG_LOG("TableGcCompactTransientBuffers() id=0x%08X\n", table->ID); + ImGuiContext& g = *GImGui; + IM_ASSERT(table->MemoryCompacted == false); + table->SortSpecs.Specs = NULL; + table->IsSortSpecsDirty = true; + table->ColumnsNames.clear(); + table->MemoryCompacted = true; + for (int n = 0; n < table->ColumnsCount; n++) + table->Columns[n].NameOffset = -1; + g.TablesLastTimeActive[g.Tables.GetIndex(table)] = -1.0f; +} + +void ImGui::TableGcCompactTransientBuffers(ImGuiTableTempData* temp_data) +{ + temp_data->DrawSplitter.ClearFreeMemory(); + temp_data->SortSpecsMulti.clear(); + temp_data->LastTimeActive = -1.0f; +} + +// Compact and remove unused settings data (currently only used by TestEngine) +void ImGui::TableGcCompactSettings() +{ + ImGuiContext& g = *GImGui; + int required_memory = 0; + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID != 0) + required_memory += (int)TableSettingsCalcChunkSize(settings->ColumnsCount); + if (required_memory == g.SettingsTables.Buf.Size) + return; + ImChunkStream new_chunk_stream; + new_chunk_stream.Buf.reserve(required_memory); + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID != 0) + memcpy(new_chunk_stream.alloc_chunk(TableSettingsCalcChunkSize(settings->ColumnsCount)), settings, TableSettingsCalcChunkSize(settings->ColumnsCount)); + g.SettingsTables.swap(new_chunk_stream); +} + + +//------------------------------------------------------------------------- +// [SECTION] Tables: Debugging +//------------------------------------------------------------------------- +// - DebugNodeTable() [Internal] +//------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_METRICS_WINDOW + +static const char* DebugNodeTableGetSizingPolicyDesc(ImGuiTableFlags sizing_policy) +{ + sizing_policy &= ImGuiTableFlags_SizingMask_; + if (sizing_policy == ImGuiTableFlags_SizingFixedFit) { return "FixedFit"; } + if (sizing_policy == ImGuiTableFlags_SizingFixedSame) { return "FixedSame"; } + if (sizing_policy == ImGuiTableFlags_SizingStretchProp) { return "StretchProp"; } + if (sizing_policy == ImGuiTableFlags_SizingStretchSame) { return "StretchSame"; } + return "N/A"; +} + +void ImGui::DebugNodeTable(ImGuiTable* table) +{ + char buf[512]; + char* p = buf; + const char* buf_end = buf + IM_ARRAYSIZE(buf); + const bool is_active = (table->LastFrameActive >= ImGui::GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here. + ImFormatString(p, buf_end - p, "Table 0x%08X (%d columns, in '%s')%s", table->ID, table->ColumnsCount, table->OuterWindow->Name, is_active ? "" : " *Inactive*"); + if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } + bool open = TreeNode(table, "%s", buf); + if (!is_active) { PopStyleColor(); } + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); + if (IsItemVisible() && table->HoveredColumnBody != -1) + GetForegroundDrawList()->AddRect(GetItemRectMin(), GetItemRectMax(), IM_COL32(255, 255, 0, 255)); + if (!open) + return; + bool clear_settings = SmallButton("Clear settings"); + BulletText("OuterRect: Pos: (%.1f,%.1f) Size: (%.1f,%.1f) Sizing: '%s'", table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.GetWidth(), table->OuterRect.GetHeight(), DebugNodeTableGetSizingPolicyDesc(table->Flags)); + BulletText("ColumnsGivenWidth: %.1f, ColumnsAutoFitWidth: %.1f, InnerWidth: %.1f%s", table->ColumnsGivenWidth, table->ColumnsAutoFitWidth, table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : ""); + BulletText("CellPaddingX: %.1f, CellSpacingX: %.1f/%.1f, OuterPaddingX: %.1f", table->CellPaddingX, table->CellSpacingX1, table->CellSpacingX2, table->OuterPaddingX); + BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", table->HoveredColumnBody, table->HoveredColumnBorder); + BulletText("ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn); + //BulletText("BgDrawChannels: %d/%d", 0, table->BgDrawChannelUnfrozen); + float sum_weights = 0.0f; + for (int n = 0; n < table->ColumnsCount; n++) + if (table->Columns[n].Flags & ImGuiTableColumnFlags_WidthStretch) + sum_weights += table->Columns[n].StretchWeight; + for (int n = 0; n < table->ColumnsCount; n++) + { + ImGuiTableColumn* column = &table->Columns[n]; + const char* name = TableGetColumnName(table, n); + ImFormatString(buf, IM_ARRAYSIZE(buf), + "Column %d order %d '%s': offset %+.2f to %+.2f%s\n" + "Enabled: %d, VisibleX/Y: %d/%d, RequestOutput: %d, SkipItems: %d, DrawChannels: %d,%d\n" + "WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f (%.1f%%)\n" + "MinX: %.1f, MaxX: %.1f (%+.1f), ClipRect: %.1f to %.1f (+%.1f)\n" + "ContentWidth: %.1f,%.1f, HeadersUsed/Ideal %.1f/%.1f\n" + "Sort: %d%s, UserID: 0x%08X, Flags: 0x%04X: %s%s%s..", + n, column->DisplayOrder, name, column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, (n < table->FreezeColumnsRequest) ? " (Frozen)" : "", + column->IsEnabled, column->IsVisibleX, column->IsVisibleY, column->IsRequestOutput, column->IsSkipItems, column->DrawChannelFrozen, column->DrawChannelUnfrozen, + column->WidthGiven, column->WidthRequest, column->WidthAuto, column->StretchWeight, column->StretchWeight > 0.0f ? (column->StretchWeight / sum_weights) * 100.0f : 0.0f, + column->MinX, column->MaxX, column->MaxX - column->MinX, column->ClipRect.Min.x, column->ClipRect.Max.x, column->ClipRect.Max.x - column->ClipRect.Min.x, + column->ContentMaxXFrozen - column->WorkMinX, column->ContentMaxXUnfrozen - column->WorkMinX, column->ContentMaxXHeadersUsed - column->WorkMinX, column->ContentMaxXHeadersIdeal - column->WorkMinX, + column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? " (Asc)" : (column->SortDirection == ImGuiSortDirection_Descending) ? " (Des)" : "", column->UserID, column->Flags, + (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", + (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", + (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); + Bullet(); + Selectable(buf); + if (IsItemHovered()) + { + ImRect r(column->MinX, table->OuterRect.Min.y, column->MaxX, table->OuterRect.Max.y); + GetForegroundDrawList()->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255)); + } + } + if (ImGuiTableSettings* settings = TableGetBoundSettings(table)) + DebugNodeTableSettings(settings); + if (clear_settings) + table->IsResetAllRequest = true; + TreePop(); +} + +void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) +{ + if (!TreeNode((void*)(intptr_t)settings->ID, "Settings 0x%08X (%d columns)", settings->ID, settings->ColumnsCount)) + return; + BulletText("SaveFlags: 0x%08X", settings->SaveFlags); + BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax); + for (int n = 0; n < settings->ColumnsCount; n++) + { + ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; + ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; + BulletText("Column %d Order %d SortOrder %d %s Vis %d %s %7.3f UserID 0x%08X", + n, column_settings->DisplayOrder, column_settings->SortOrder, + (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", + column_settings->IsEnabled, column_settings->IsStretch ? "Weight" : "Width ", column_settings->WidthOrWeight, column_settings->UserID); + } + TreePop(); +} + +#else // #ifndef IMGUI_DISABLE_METRICS_WINDOW + +void ImGui::DebugNodeTable(ImGuiTable*) {} +void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {} + +#endif + + +//------------------------------------------------------------------------- +// [SECTION] Columns, BeginColumns, EndColumns, etc. +// (This is a legacy API, prefer using BeginTable/EndTable!) +//------------------------------------------------------------------------- +// FIXME: sizing is lossy when columns width is very small (default width may turn negative etc.) +//------------------------------------------------------------------------- +// - SetWindowClipRectBeforeSetChannel() [Internal] +// - GetColumnIndex() +// - GetColumnsCount() +// - GetColumnOffset() +// - GetColumnWidth() +// - SetColumnOffset() +// - SetColumnWidth() +// - PushColumnClipRect() [Internal] +// - PushColumnsBackground() [Internal] +// - PopColumnsBackground() [Internal] +// - FindOrCreateColumns() [Internal] +// - GetColumnsID() [Internal] +// - BeginColumns() +// - NextColumn() +// - EndColumns() +// - Columns() +//------------------------------------------------------------------------- + +// [Internal] Small optimization to avoid calls to PopClipRect/SetCurrentChannel/PushClipRect in sequences, +// they would meddle many times with the underlying ImDrawCmd. +// Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let +// the subsequent single call to SetCurrentChannel() does it things once. +void ImGui::SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect) +{ + ImVec4 clip_rect_vec4 = clip_rect.ToVec4(); + window->ClipRect = clip_rect; + window->DrawList->_CmdHeader.ClipRect = clip_rect_vec4; + window->DrawList->_ClipRectStack.Data[window->DrawList->_ClipRectStack.Size - 1] = clip_rect_vec4; +} + +int ImGui::GetColumnIndex() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0; +} + +int ImGui::GetColumnsCount() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1; +} + +float ImGui::GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm) +{ + return offset_norm * (columns->OffMaxX - columns->OffMinX); +} + +float ImGui::GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset) +{ + return offset / (columns->OffMaxX - columns->OffMinX); +} + +static const float COLUMNS_HIT_RECT_HALF_WIDTH = 4.0f; + +static float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index) +{ + // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing + // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(column_index > 0); // We are not supposed to drag column 0. + IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); + + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + COLUMNS_HIT_RECT_HALF_WIDTH - window->Pos.x; + x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); + if ((columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths)) + x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + + return x; +} + +float ImGui::GetColumnOffset(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns == NULL) + return 0.0f; + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const float t = columns->Columns[column_index].OffsetNorm; + const float x_offset = ImLerp(columns->OffMinX, columns->OffMaxX, t); + return x_offset; +} + +static float GetColumnWidthEx(ImGuiOldColumns* columns, int column_index, bool before_resize = false) +{ + if (column_index < 0) + column_index = columns->Current; + + float offset_norm; + if (before_resize) + offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; + else + offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; + return ImGui::GetColumnOffsetFromNorm(columns, offset_norm); +} + +float ImGui::GetColumnWidth(int column_index) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns == NULL) + return GetContentRegionAvail().x; + + if (column_index < 0) + column_index = columns->Current; + return GetColumnOffsetFromNorm(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); +} + +void ImGui::SetColumnOffset(int column_index, float offset) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiOldColumns* columns = window->DC.CurrentColumns; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const bool preserve_width = !(columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths) && (column_index < columns->Count - 1); + const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + + if (!(columns->Flags & ImGuiOldColumnFlags_NoForceWithinWindow)) + offset = ImMin(offset, columns->OffMaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); + columns->Columns[column_index].OffsetNorm = GetColumnNormFromOffset(columns, offset - columns->OffMinX); + + if (preserve_width) + SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); +} + +void ImGui::SetColumnWidth(int column_index, float width) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); +} + +void ImGui::PushColumnClipRect(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (column_index < 0) + column_index = columns->Current; + + ImGuiOldColumnData* column = &columns->Columns[column_index]; + PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false); +} + +// Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns) +void ImGui::PushColumnsBackground() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns->Count == 1) + return; + + // Optimization: avoid SetCurrentChannel() + PushClipRect() + columns->HostBackupClipRect = window->ClipRect; + SetWindowClipRectBeforeSetChannel(window, columns->HostInitialClipRect); + columns->Splitter.SetCurrentChannel(window->DrawList, 0); +} + +void ImGui::PopColumnsBackground() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns->Count == 1) + return; + + // Optimization: avoid PopClipRect() + SetCurrentChannel() + SetWindowClipRectBeforeSetChannel(window, columns->HostBackupClipRect); + columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); +} + +ImGuiOldColumns* ImGui::FindOrCreateColumns(ImGuiWindow* window, ImGuiID id) +{ + // We have few columns per window so for now we don't need bother much with turning this into a faster lookup. + for (int n = 0; n < window->ColumnsStorage.Size; n++) + if (window->ColumnsStorage[n].ID == id) + return &window->ColumnsStorage[n]; + + window->ColumnsStorage.push_back(ImGuiOldColumns()); + ImGuiOldColumns* columns = &window->ColumnsStorage.back(); + columns->ID = id; + return columns; +} + +ImGuiID ImGui::GetColumnsID(const char* str_id, int columns_count) +{ + ImGuiWindow* window = GetCurrentWindow(); + + // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. + // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. + PushID(0x11223347 + (str_id ? 0 : columns_count)); + ImGuiID id = window->GetID(str_id ? str_id : "columns"); + PopID(); + + return id; +} + +void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(columns_count >= 1); + IM_ASSERT(window->DC.CurrentColumns == NULL); // Nested columns are currently not supported + + // Acquire storage for the columns set + ImGuiID id = GetColumnsID(str_id, columns_count); + ImGuiOldColumns* columns = FindOrCreateColumns(window, id); + IM_ASSERT(columns->ID == id); + columns->Current = 0; + columns->Count = columns_count; + columns->Flags = flags; + window->DC.CurrentColumns = columns; + + columns->HostCursorPosY = window->DC.CursorPos.y; + columns->HostCursorMaxPosX = window->DC.CursorMaxPos.x; + columns->HostInitialClipRect = window->ClipRect; + columns->HostBackupParentWorkRect = window->ParentWorkRect; + window->ParentWorkRect = window->WorkRect; + + // Set state for first column + // We aim so that the right-most column will have the same clipping width as other after being clipped by parent ClipRect + const float column_padding = g.Style.ItemSpacing.x; + const float half_clip_extend_x = ImFloor(ImMax(window->WindowPadding.x * 0.5f, window->WindowBorderSize)); + const float max_1 = window->WorkRect.Max.x + column_padding - ImMax(column_padding - window->WindowPadding.x, 0.0f); + const float max_2 = window->WorkRect.Max.x + half_clip_extend_x; + columns->OffMinX = window->DC.Indent.x - column_padding + ImMax(column_padding - window->WindowPadding.x, 0.0f); + columns->OffMaxX = ImMax(ImMin(max_1, max_2) - window->Pos.x, columns->OffMinX + 1.0f); + columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y; + + // Clear data if columns count changed + if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) + columns->Columns.resize(0); + + // Initialize default widths + columns->IsFirstFrame = (columns->Columns.Size == 0); + if (columns->Columns.Size == 0) + { + columns->Columns.reserve(columns_count + 1); + for (int n = 0; n < columns_count + 1; n++) + { + ImGuiOldColumnData column; + column.OffsetNorm = n / (float)columns_count; + columns->Columns.push_back(column); + } + } + + for (int n = 0; n < columns_count; n++) + { + // Compute clipping rectangle + ImGuiOldColumnData* column = &columns->Columns[n]; + float clip_x1 = IM_ROUND(window->Pos.x + GetColumnOffset(n)); + float clip_x2 = IM_ROUND(window->Pos.x + GetColumnOffset(n + 1) - 1.0f); + column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); + column->ClipRect.ClipWithFull(window->ClipRect); + } + + if (columns->Count > 1) + { + columns->Splitter.Split(window->DrawList, 1 + columns->Count); + columns->Splitter.SetCurrentChannel(window->DrawList, 1); + PushColumnClipRect(0); + } + + // We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user. + float offset_0 = GetColumnOffset(columns->Current); + float offset_1 = GetColumnOffset(columns->Current + 1); + float width = offset_1 - offset_0; + PushItemWidth(width * 0.65f); + window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; +} + +void ImGui::NextColumn() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems || window->DC.CurrentColumns == NULL) + return; + + ImGuiContext& g = *GImGui; + ImGuiOldColumns* columns = window->DC.CurrentColumns; + + if (columns->Count == 1) + { + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + IM_ASSERT(columns->Current == 0); + return; + } + + // Next column + if (++columns->Current == columns->Count) + columns->Current = 0; + + PopItemWidth(); + + // Optimization: avoid PopClipRect() + SetCurrentChannel() + PushClipRect() + // (which would needlessly attempt to update commands in the wrong channel, then pop or overwrite them), + ImGuiOldColumnData* column = &columns->Columns[columns->Current]; + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); + columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); + + const float column_padding = g.Style.ItemSpacing.x; + columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); + if (columns->Current > 0) + { + // Columns 1+ ignore IndentX (by canceling it out) + // FIXME-COLUMNS: Unnecessary, could be locked? + window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + column_padding; + } + else + { + // New row/line: column 0 honor IndentX. + window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); + columns->LineMinY = columns->LineMaxY; + } + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + window->DC.CursorPos.y = columns->LineMinY; + window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); + window->DC.CurrLineTextBaseOffset = 0.0f; + + // FIXME-COLUMNS: Share code with BeginColumns() - move code on columns setup. + float offset_0 = GetColumnOffset(columns->Current); + float offset_1 = GetColumnOffset(columns->Current + 1); + float width = offset_1 - offset_0; + PushItemWidth(width * 0.65f); + window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; +} + +void ImGui::EndColumns() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + IM_ASSERT(columns != NULL); + + PopItemWidth(); + if (columns->Count > 1) + { + PopClipRect(); + columns->Splitter.Merge(window->DrawList); + } + + const ImGuiOldColumnFlags flags = columns->Flags; + columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); + window->DC.CursorPos.y = columns->LineMaxY; + if (!(flags & ImGuiOldColumnFlags_GrowParentContentsSize)) + window->DC.CursorMaxPos.x = columns->HostCursorMaxPosX; // Restore cursor max pos, as columns don't grow parent + + // Draw columns borders and handle resize + // The IsBeingResized flag ensure we preserve pre-resize columns width so back-and-forth are not lossy + bool is_being_resized = false; + if (!(flags & ImGuiOldColumnFlags_NoBorder) && !window->SkipItems) + { + // We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers. + const float y1 = ImMax(columns->HostCursorPosY, window->ClipRect.Min.y); + const float y2 = ImMin(window->DC.CursorPos.y, window->ClipRect.Max.y); + int dragging_column = -1; + for (int n = 1; n < columns->Count; n++) + { + ImGuiOldColumnData* column = &columns->Columns[n]; + float x = window->Pos.x + GetColumnOffset(n); + const ImGuiID column_id = columns->ID + ImGuiID(n); + const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH; + const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2)); + KeepAliveID(column_id); + if (IsClippedEx(column_hit_rect, column_id, false)) + continue; + + bool hovered = false, held = false; + if (!(flags & ImGuiOldColumnFlags_NoResize)) + { + ButtonBehavior(column_hit_rect, column_id, &hovered, &held); + if (hovered || held) + g.MouseCursor = ImGuiMouseCursor_ResizeEW; + if (held && !(column->Flags & ImGuiOldColumnFlags_NoResize)) + dragging_column = n; + } + + // Draw column + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + const float xi = IM_FLOOR(x); + window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col); + } + + // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. + if (dragging_column != -1) + { + if (!columns->IsBeingResized) + for (int n = 0; n < columns->Count + 1; n++) + columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; + columns->IsBeingResized = is_being_resized = true; + float x = GetDraggedColumnOffset(columns, dragging_column); + SetColumnOffset(dragging_column, x); + } + } + columns->IsBeingResized = is_being_resized; + + window->WorkRect = window->ParentWorkRect; + window->ParentWorkRect = columns->HostBackupParentWorkRect; + window->DC.CurrentColumns = NULL; + window->DC.ColumnsOffset.x = 0.0f; + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); +} + +void ImGui::Columns(int columns_count, const char* id, bool border) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); + + ImGuiOldColumnFlags flags = (border ? 0 : ImGuiOldColumnFlags_NoBorder); + //flags |= ImGuiOldColumnFlags_NoPreserveWidths; // NB: Legacy behavior + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns != NULL && columns->Count == columns_count && columns->Flags == flags) + return; + + if (columns != NULL) + EndColumns(); + + if (columns_count != 1) + BeginColumns(id, columns_count, flags); +} + +//------------------------------------------------------------------------- + +#endif // #ifndef IMGUI_DISABLE diff --git a/vendor/librw/skeleton/imgui/imgui_widgets.cpp b/vendor/librw/skeleton/imgui/imgui_widgets.cpp new file mode 100644 index 0000000..46d2174 --- /dev/null +++ b/vendor/librw/skeleton/imgui/imgui_widgets.cpp @@ -0,0 +1,8056 @@ +// dear imgui, v1.83 +// (widgets code) + +/* + +Index of this file: + +// [SECTION] Forward Declarations +// [SECTION] Widgets: Text, etc. +// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.) +// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.) +// [SECTION] Widgets: ComboBox +// [SECTION] Data Type and Data Formatting Helpers +// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. +// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. +// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. +// [SECTION] Widgets: InputText, InputTextMultiline +// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. +// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. +// [SECTION] Widgets: Selectable +// [SECTION] Widgets: ListBox +// [SECTION] Widgets: PlotLines, PlotHistogram +// [SECTION] Widgets: Value helpers +// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc. +// [SECTION] Widgets: BeginTabBar, EndTabBar, etc. +// [SECTION] Widgets: BeginTabItem, EndTabItem, etc. +// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +// System includes +#include // toupper +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +//------------------------------------------------------------------------- +// Warnings +//------------------------------------------------------------------------- + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types +#endif +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif + +//------------------------------------------------------------------------- +// Data +//------------------------------------------------------------------------- + +// Widgets +static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior. +static const float DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f; // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags. + +// Those MIN/MAX values are not define because we need to point to them +static const signed char IM_S8_MIN = -128; +static const signed char IM_S8_MAX = 127; +static const unsigned char IM_U8_MIN = 0; +static const unsigned char IM_U8_MAX = 0xFF; +static const signed short IM_S16_MIN = -32768; +static const signed short IM_S16_MAX = 32767; +static const unsigned short IM_U16_MIN = 0; +static const unsigned short IM_U16_MAX = 0xFFFF; +static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000); +static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF) +static const ImU32 IM_U32_MIN = 0; +static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF) +#ifdef LLONG_MIN +static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll); +static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll); +#else +static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1; +static const ImS64 IM_S64_MAX = 9223372036854775807LL; +#endif +static const ImU64 IM_U64_MIN = 0; +#ifdef ULLONG_MAX +static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull); +#else +static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); +#endif + +//------------------------------------------------------------------------- +// [SECTION] Forward Declarations +//------------------------------------------------------------------------- + +// For InputTextEx() +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source); +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Text, etc. +//------------------------------------------------------------------------- +// - TextEx() [Internal] +// - TextUnformatted() +// - Text() +// - TextV() +// - TextColored() +// - TextColoredV() +// - TextDisabled() +// - TextDisabledV() +// - TextWrapped() +// - TextWrappedV() +// - LabelText() +// - LabelTextV() +// - BulletText() +// - BulletTextV() +//------------------------------------------------------------------------- + +void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + IM_ASSERT(text != NULL); + const char* text_begin = text; + if (text_end == NULL) + text_end = text + strlen(text); // FIXME-OPT + + const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + const float wrap_pos_x = window->DC.TextWrapPos; + const bool wrap_enabled = (wrap_pos_x >= 0.0f); + if (text_end - text > 2000 && !wrap_enabled) + { + // Long text! + // Perform manual coarse clipping to optimize for long multi-line text + // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. + // - We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. + // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop. + const char* line = text; + const float line_height = GetTextLineHeight(); + ImVec2 text_size(0, 0); + + // Lines to skip (can't skip when logging text) + ImVec2 pos = text_pos; + if (!g.LogEnabled) + { + int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height); + if (lines_skippable > 0) + { + int lines_skipped = 0; + while (line < text_end && lines_skipped < lines_skippable) + { + const char* line_end = (const char*)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) + text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + } + + // Lines to render + if (line < text_end) + { + ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); + while (line < text_end) + { + if (IsClippedEx(line_rect, 0, false)) + break; + + const char* line_end = (const char*)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); + RenderText(pos, line, line_end, false); + line = line_end + 1; + line_rect.Min.y += line_height; + line_rect.Max.y += line_height; + pos.y += line_height; + } + + // Count remaining lines + int lines_skipped = 0; + while (line < text_end) + { + const char* line_end = (const char*)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) + text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + text_size.y = (pos - text_pos).y; + + ImRect bb(text_pos, text_pos + text_size); + ItemSize(text_size, 0.0f); + ItemAdd(bb, 0); + } + else + { + const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; + const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); + + ImRect bb(text_pos, text_pos + text_size); + ItemSize(text_size, 0.0f); + if (!ItemAdd(bb, 0)) + return; + + // Render (we don't hide text after ## in this end-user function) + RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); + } +} + +void ImGui::TextUnformatted(const char* text, const char* text_end) +{ + TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); +} + +void ImGui::Text(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextV(fmt, args); + va_end(args); +} + +void ImGui::TextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); +} + +void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextColoredV(col, fmt, args); + va_end(args); +} + +void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, col); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting + else + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextDisabled(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextDisabledV(fmt, args); + va_end(args); +} + +void ImGui::TextDisabledV(const char* fmt, va_list args) +{ + ImGuiContext& g = *GImGui; + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting + else + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextWrapped(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextWrappedV(fmt, args); + va_end(args); +} + +void ImGui::TextWrappedV(const char* fmt, va_list args) +{ + ImGuiContext& g = *GImGui; + bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set + if (need_backup) + PushTextWrapPos(0.0f); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting + else + TextV(fmt, args); + if (need_backup) + PopTextWrapPos(); +} + +void ImGui::LabelText(const char* label, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + LabelTextV(label, fmt, args); + va_end(args); +} + +// Add a label+text combo aligned to other label+value widgets +void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float w = CalcItemWidth(); + + const char* value_text_begin = &g.TempBuffer[0]; + const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImVec2 pos = window->DC.CursorPos; + const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2)); + const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(value_size.y, label_size.y) + style.FramePadding.y * 2)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0)) + return; + + // Render + RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); +} + +void ImGui::BulletText(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + BulletTextV(fmt, args); + va_end(args); +} + +// Text with a little bullet aligned to the typical tree node. +void ImGui::BulletTextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const char* text_begin = g.TempBuffer; + const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); + const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); // Empty text doesn't add padding + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrLineTextBaseOffset; + ItemSize(total_size, 0.0f); + const ImRect bb(pos, pos + total_size); + if (!ItemAdd(bb, 0)) + return; + + // Render + ImU32 text_col = GetColorU32(ImGuiCol_Text); + RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col); + RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Main +//------------------------------------------------------------------------- +// - ButtonBehavior() [Internal] +// - Button() +// - SmallButton() +// - InvisibleButton() +// - ArrowButton() +// - CloseButton() [Internal] +// - CollapseButton() [Internal] +// - GetWindowScrollbarID() [Internal] +// - GetWindowScrollbarRect() [Internal] +// - Scrollbar() [Internal] +// - ScrollbarEx() [Internal] +// - Image() +// - ImageButton() +// - Checkbox() +// - CheckboxFlagsT() [Internal] +// - CheckboxFlags() +// - RadioButton() +// - ProgressBar() +// - Bullet() +//------------------------------------------------------------------------- + +// The ButtonBehavior() function is key to many interactions and used by many/most widgets. +// Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_), +// this code is a little complex. +// By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior. +// See the series of events below and the corresponding state reported by dear imgui: +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnClickRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+0 (mouse is outside bb) - - - - - - +// Frame N+1 (mouse moves inside bb) - true - - - - +// Frame N+2 (mouse button is down) - true true true - true +// Frame N+3 (mouse button is down) - true true - - - +// Frame N+4 (mouse moves outside bb) - - true - - - +// Frame N+5 (mouse moves inside bb) - true true - - - +// Frame N+6 (mouse button is released) true true - - true - +// Frame N+7 (mouse button is released) - true - - - - +// Frame N+8 (mouse moves outside bb) - - - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+2 (mouse button is down) true true true true - true +// Frame N+3 (mouse button is down) - true true - - - +// Frame N+6 (mouse button is released) - true - - true - +// Frame N+7 (mouse button is released) - true - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+2 (mouse button is down) - true - - - true +// Frame N+3 (mouse button is down) - true - - - - +// Frame N+6 (mouse button is released) true true - - - - +// Frame N+7 (mouse button is released) - true - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnDoubleClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+0 (mouse button is down) - true - - - true +// Frame N+1 (mouse button is down) - true - - - - +// Frame N+2 (mouse button is released) - true - - - - +// Frame N+3 (mouse button is released) - true - - - - +// Frame N+4 (mouse button is down) true true true true - true +// Frame N+5 (mouse button is down) - true true - - - +// Frame N+6 (mouse button is released) - true - - true - +// Frame N+7 (mouse button is released) - true - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// Note that some combinations are supported, +// - PressedOnDragDropHold can generally be associated with any flag. +// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported. +//------------------------------------------------------------------------------------------------------------------------------------------------ +// The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set: +// Repeat+ Repeat+ Repeat+ Repeat+ +// PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick +//------------------------------------------------------------------------------------------------------------------------------------------------- +// Frame N+0 (mouse button is down) - true - true +// ... - - - - +// Frame N + RepeatDelay true true - true +// ... - - - - +// Frame N + RepeatDelay + RepeatRate*N true true - true +//------------------------------------------------------------------------------------------------------------------------------------------------- + +bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + if (flags & ImGuiButtonFlags_Disabled) + { + if (out_hovered) *out_hovered = false; + if (out_held) *out_held = false; + if (g.ActiveId == id) ClearActiveID(); + return false; + } + + // Default only reacts to left mouse button + if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) + flags |= ImGuiButtonFlags_MouseButtonDefault_; + + // Default behavior requires click + release inside bounding box + if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) + flags |= ImGuiButtonFlags_PressedOnDefault_; + + ImGuiWindow* backup_hovered_window = g.HoveredWindow; + const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window; + if (flatten_hovered_children) + g.HoveredWindow = window; + +#ifdef IMGUI_ENABLE_TEST_ENGINE + if (id != 0 && window->DC.LastItemId != id) + IMGUI_TEST_ENGINE_ITEM_ADD(bb, id); +#endif + + bool pressed = false; + bool hovered = ItemHoverable(bb, id); + + // Drag source doesn't report as hovered + if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover)) + hovered = false; + + // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button + if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) + if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + { + hovered = true; + SetHoveredID(id); + if (CalcTypematicRepeatAmount(g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, g.HoveredIdTimer + 0.0001f, DRAGDROP_HOLD_TO_OPEN_TIMER, 0.00f)) + { + pressed = true; + g.DragDropHoldJustPressedId = id; + FocusWindow(window); + } + } + + if (flatten_hovered_children) + g.HoveredWindow = backup_hovered_window; + + // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one. + if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) + hovered = false; + + // Mouse handling + if (hovered) + { + if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) + { + // Poll buttons + int mouse_button_clicked = -1; + int mouse_button_released = -1; + if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseClicked[0]) { mouse_button_clicked = 0; } + else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseClicked[1]) { mouse_button_clicked = 1; } + else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseClicked[2]) { mouse_button_clicked = 2; } + if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseReleased[0]) { mouse_button_released = 0; } + else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseReleased[1]) { mouse_button_released = 1; } + else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseReleased[2]) { mouse_button_released = 2; } + + if (mouse_button_clicked != -1 && g.ActiveId != id) + { + if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere)) + { + SetActiveID(id, window); + g.ActiveIdMouseButton = mouse_button_clicked; + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + FocusWindow(window); + } + if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[mouse_button_clicked])) + { + pressed = true; + if (flags & ImGuiButtonFlags_NoHoldingActiveId) + ClearActiveID(); + else + SetActiveID(id, window); // Hold on ID + g.ActiveIdMouseButton = mouse_button_clicked; + FocusWindow(window); + } + } + if ((flags & ImGuiButtonFlags_PressedOnRelease) && mouse_button_released != -1) + { + // Repeat mode trumps on release behavior + const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; + if (!has_repeated_at_least_once) + pressed = true; + ClearActiveID(); + } + + // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). + // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. + if (g.ActiveId == id && (flags & ImGuiButtonFlags_Repeat)) + if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, true)) + pressed = true; + } + + if (pressed) + g.NavDisableHighlight = true; + } + + // Gamepad/Keyboard navigation + // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. + if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) + if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) + hovered = true; + if (g.NavActivateDownId == id) + { + bool nav_activated_by_code = (g.NavActivateId == id); + bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed); + if (nav_activated_by_code || nav_activated_by_inputs) + pressed = true; + if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id) + { + // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. + g.NavActivateId = id; // This is so SetActiveId assign a Nav source + SetActiveID(id, window); + if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + } + } + + // Process while held + bool held = false; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (g.ActiveIdIsJustActivated) + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + + const int mouse_button = g.ActiveIdMouseButton; + IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); + if (g.IO.MouseDown[mouse_button]) + { + held = true; + } + else + { + bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0; + bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0; + if ((release_in || release_anywhere) && !g.DragDropActive) + { + // Report as pressed when releasing the mouse (this is the most common path) + bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDownWasDoubleClick[mouse_button]; + bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps + if (!is_double_click_release && !is_repeating_already) + pressed = true; + } + ClearActiveID(); + } + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + g.NavDisableHighlight = true; + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + // When activated using Nav, we hold on the ActiveID until activation button is released + if (g.NavActivateDownId != id) + ClearActiveID(); + } + if (pressed) + g.ActiveIdHasBeenPressedBefore = true; + } + + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + + return pressed; +} + +bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImVec2 pos = window->DC.CursorPos; + if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + + const ImRect bb(pos, pos + size); + ItemSize(size, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + if (g.CurrentItemFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + + if (g.LogEnabled) + LogSetNextTextDecoration("[", "]"); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + return pressed; +} + +bool ImGui::Button(const char* label, const ImVec2& size_arg) +{ + return ButtonEx(label, size_arg, ImGuiButtonFlags_None); +} + +// Small buttons fits within text without additional vertical spacing. +bool ImGui::SmallButton(const char* label) +{ + ImGuiContext& g = *GImGui; + float backup_padding_y = g.Style.FramePadding.y; + g.Style.FramePadding.y = 0.0f; + bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine); + g.Style.FramePadding.y = backup_padding_y; + return pressed; +} + +// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. +// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) +bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size. + IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f); + + const ImGuiID id = window->GetID(str_id); + ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(size); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + return pressed; +} + +bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(str_id); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + const float default_size = GetFrameHeight(); + ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); + if (!ItemAdd(bb, id)) + return false; + + if (g.CurrentItemFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + const ImU32 text_col = GetColorU32(ImGuiCol_Text); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); + RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir); + + return pressed; +} + +bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) +{ + float sz = GetFrameHeight(); + return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None); +} + +// Button to close a window +bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825) + // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible? + const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f); + ImRect bb_interact = bb; + const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea(); + if (area_to_visible_ratio < 1.5f) + bb_interact.Expand(ImFloor(bb_interact.GetSize() * -0.25f)); + + // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window. + // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible). + bool is_clipped = !ItemAdd(bb_interact, id); + + bool hovered, held; + bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held); + if (is_clipped) + return pressed; + + // Render + // FIXME: Clarify this mess + ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); + ImVec2 center = bb.GetCenter(); + if (hovered) + window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12); + + float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + ImU32 cross_col = GetColorU32(ImGuiCol_Text); + center -= ImVec2(0.5f, 0.5f); + window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); + window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); + + return pressed; +} + +bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f); + ItemAdd(bb, id); + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); + + // Render + ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImU32 text_col = GetColorU32(ImGuiCol_Text); + ImVec2 center = bb.GetCenter(); + if (hovered || held) + window->DrawList->AddCircleFilled(center/*+ ImVec2(0.0f, -0.5f)*/, g.FontSize * 0.5f + 1.0f, bg_col, 12); + RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + + // Switch to moving the window after mouse is moved beyond the initial drag threshold + if (IsItemActive() && IsMouseDragging(0)) + StartMouseMovingWindow(window); + + return pressed; +} + +ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis) +{ + return window->GetIDNoKeepAlive(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY"); +} + +// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set. +ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) +{ + const ImRect outer_rect = window->Rect(); + const ImRect inner_rect = window->InnerRect; + const float border_size = window->WindowBorderSize; + const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) + IM_ASSERT(scrollbar_size > 0.0f); + if (axis == ImGuiAxis_X) + return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x, outer_rect.Max.y); + else + return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x, inner_rect.Max.y); +} + +void ImGui::Scrollbar(ImGuiAxis axis) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImGuiID id = GetWindowScrollbarID(window, axis); + KeepAliveID(id); + + // Calculate scrollbar bounding box + ImRect bb = GetWindowScrollbarRect(window, axis); + ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone; + if (axis == ImGuiAxis_X) + { + rounding_corners |= ImDrawFlags_RoundCornersBottomLeft; + if (!window->ScrollbarY) + rounding_corners |= ImDrawFlags_RoundCornersBottomRight; + } + else + { + if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) + rounding_corners |= ImDrawFlags_RoundCornersTopRight; + if (!window->ScrollbarX) + rounding_corners |= ImDrawFlags_RoundCornersBottomRight; + } + float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; + float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; + ScrollbarEx(bb, id, axis, &window->Scroll[axis], size_avail, size_contents, rounding_corners); +} + +// Vertical/Horizontal scrollbar +// The entire piece of code below is rather confusing because: +// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) +// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar +// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. +// Still, the code should probably be made simpler.. +bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float size_avail_v, float size_contents_v, ImDrawFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + const float bb_frame_width = bb_frame.GetWidth(); + const float bb_frame_height = bb_frame.GetHeight(); + if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f) + return false; + + // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab) + float alpha = 1.0f; + if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f) + alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f)); + if (alpha <= 0.0f) + return false; + + const ImGuiStyle& style = g.Style; + const bool allow_interaction = (alpha >= 1.0f); + + ImRect bb = bb_frame; + bb.Expand(ImVec2(-ImClamp(IM_FLOOR((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_FLOOR((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f))); + + // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) + const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight(); + + // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) + // But we maintain a minimum size in pixel to allow for the user to still aim inside. + IM_ASSERT(ImMax(size_contents_v, size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. + const float win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), 1.0f); + const float grab_h_pixels = ImClamp(scrollbar_size_v * (size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v); + const float grab_h_norm = grab_h_pixels / scrollbar_size_v; + + // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). + bool held = false; + bool hovered = false; + ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); + + float scroll_max = ImMax(1.0f, size_contents_v - size_avail_v); + float scroll_ratio = ImSaturate(*p_scroll_v / scroll_max); + float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space + if (held && allow_interaction && grab_h_norm < 1.0f) + { + float scrollbar_pos_v = bb.Min[axis]; + float mouse_pos_v = g.IO.MousePos[axis]; + + // Click position in scrollbar normalized space (0.0f->1.0f) + const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); + SetHoveredID(id); + + bool seek_absolute = false; + if (g.ActiveIdIsJustActivated) + { + // On initial click calculate the distance between mouse and the center of the grab + seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm); + if (seek_absolute) + g.ScrollbarClickDeltaToGrabCenter = 0.0f; + else + g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; + } + + // Apply scroll (p_scroll_v will generally point on one member of window->Scroll) + // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position + const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm)); + *p_scroll_v = IM_ROUND(scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v)); + + // Update values for rendering + scroll_ratio = ImSaturate(*p_scroll_v / scroll_max); + grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + + // Update distance to grab now that we have seeked and saturated + if (seek_absolute) + g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; + } + + // Render + const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg); + const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha); + window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags); + ImRect grab_rect; + if (axis == ImGuiAxis_X) + grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y); + else + grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels); + window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); + + return held; +} + +void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + if (border_col.w > 0.0f) + bb.Max += ImVec2(2, 2); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + if (border_col.w > 0.0f) + { + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); + window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col)); + } + else + { + window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); + } +} + +// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390) +// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API. +bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); + window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + + return pressed; +} + +// frame_padding < 0: uses FramePadding from style (default) +// frame_padding = 0: no framing +// frame_padding > 0: set framing size +bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + PushID((void*)(intptr_t)user_texture_id); + const ImGuiID id = window->GetID("#image"); + PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding; + return ImageButtonEx(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col); +} + +bool ImGui::Checkbox(const char* label, bool* v) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const float square_sz = GetFrameHeight(); + const ImVec2 pos = window->DC.CursorPos; + const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id)) + { + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + return false; + } + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + { + *v = !(*v); + MarkItemEdited(id); + } + + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); + RenderNavHighlight(total_bb, id); + RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); + bool mixed_value = (g.CurrentItemFlags & ImGuiItemFlags_MixedValue) != 0; + if (mixed_value) + { + // Undocumented tristate/mixed/indeterminate checkbox (#2644) + // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) + ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f))); + window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); + } + else if (*v) + { + const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); + RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + } + + ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); + if (g.LogEnabled) + LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); + if (label_size.x > 0.0f) + RenderText(label_pos, label); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + return pressed; +} + +template +bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) +{ + bool all_on = (*flags & flags_value) == flags_value; + bool any_on = (*flags & flags_value) != 0; + bool pressed; + if (!all_on && any_on) + { + ImGuiContext& g = *GImGui; + ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_MixedValue; + pressed = Checkbox(label, &all_on); + g.CurrentItemFlags = backup_item_flags; + } + else + { + pressed = Checkbox(label, &all_on); + + } + if (pressed) + { + if (all_on) + *flags |= flags_value; + else + *flags &= ~flags_value; + } + return pressed; +} + +bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + +bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + +bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + +bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + +bool ImGui::RadioButton(const char* label, bool active) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const float square_sz = GetFrameHeight(); + const ImVec2 pos = window->DC.CursorPos; + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); + const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id)) + return false; + + ImVec2 center = check_bb.GetCenter(); + center.x = IM_ROUND(center.x); + center.y = IM_ROUND(center.y); + const float radius = (square_sz - 1.0f) * 0.5f; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + MarkItemEdited(id); + + RenderNavHighlight(total_bb, id); + window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); + if (active) + { + const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16); + } + + if (style.FrameBorderSize > 0.0f) + { + window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); + } + + ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); + if (g.LogEnabled) + LogRenderedText(&label_pos, active ? "(x)" : "( )"); + if (label_size.x > 0.0f) + RenderText(label_pos, label); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + return pressed; +} + +// FIXME: This would work nicely if it was a public template, e.g. 'template RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it.. +bool ImGui::RadioButton(const char* label, int* v, int v_button) +{ + const bool pressed = RadioButton(label, *v == v_button); + if (pressed) + *v = v_button; + return pressed; +} + +// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size +void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f); + ImRect bb(pos, pos + size); + ItemSize(size, style.FramePadding.y); + if (!ItemAdd(bb, 0)) + return; + + // Render + fraction = ImSaturate(fraction); + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + char overlay_buf[32]; + if (!overlay) + { + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f); + overlay = overlay_buf; + } + + ImVec2 overlay_size = CalcTextSize(overlay, NULL); + if (overlay_size.x > 0.0f) + RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb); +} + +void ImGui::Bullet() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + { + SameLine(0, style.FramePadding.x * 2); + return; + } + + // Render and stay on same line + ImU32 text_col = GetColorU32(ImGuiCol_Text); + RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col); + SameLine(0, style.FramePadding.x * 2.0f); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Low-level Layout helpers +//------------------------------------------------------------------------- +// - Spacing() +// - Dummy() +// - NewLine() +// - AlignTextToFramePadding() +// - SeparatorEx() [Internal] +// - Separator() +// - SplitterBehavior() [Internal] +// - ShrinkWidths() [Internal] +//------------------------------------------------------------------------- + +void ImGui::Spacing() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ItemSize(ImVec2(0, 0)); +} + +void ImGui::Dummy(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(size); + ItemAdd(bb, 0); +} + +void ImGui::NewLine() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; + window->DC.LayoutType = ImGuiLayoutType_Vertical; + if (window->DC.CurrLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. + ItemSize(ImVec2(0, 0)); + else + ItemSize(ImVec2(0.0f, g.FontSize)); + window->DC.LayoutType = backup_layout_type; +} + +void ImGui::AlignTextToFramePadding() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2); + window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y); +} + +// Horizontal/vertical separating line +void ImGui::SeparatorEx(ImGuiSeparatorFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected + + float thickness_draw = 1.0f; + float thickness_layout = 0.0f; + if (flags & ImGuiSeparatorFlags_Vertical) + { + // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout. + float y1 = window->DC.CursorPos.y; + float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y; + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness_draw, y2)); + ItemSize(ImVec2(thickness_layout, 0.0f)); + if (!ItemAdd(bb, 0)) + return; + + // Draw + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogText(" |"); + } + else if (flags & ImGuiSeparatorFlags_Horizontal) + { + // Horizontal Separator + float x1 = window->Pos.x; + float x2 = window->Pos.x + window->Size.x; + + // FIXME-WORKRECT: old hack (#205) until we decide of consistent behavior with WorkRect/Indent and Separator + if (g.GroupStack.Size > 0 && g.GroupStack.back().WindowID == window->ID) + x1 += window->DC.Indent.x; + + ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL; + if (columns) + PushColumnsBackground(); + + // We don't provide our width to the layout so that it doesn't get feed back into AutoFit + const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness_draw)); + ItemSize(ImVec2(0.0f, thickness_layout)); + const bool item_visible = ItemAdd(bb, 0); + if (item_visible) + { + // Draw + window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogRenderedText(&bb.Min, "--------------------------------\n"); + + } + if (columns) + { + PopColumnsBackground(); + columns->LineMinY = window->DC.CursorPos.y; + } + } +} + +void ImGui::Separator() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + // Those flags should eventually be overridable by the user + ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; + flags |= ImGuiSeparatorFlags_SpanAllColumns; + SeparatorEx(flags); +} + +// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise. +bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; + bool item_add = ItemAdd(bb, id); + g.CurrentItemFlags = item_flags_backup; + if (!item_add) + return false; + + bool hovered, held; + ImRect bb_interact = bb; + bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); + ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (g.ActiveId != id) + SetItemAllowOverlap(); + + if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) + SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + + ImRect bb_render = bb; + if (held) + { + ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min; + float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x; + + // Minimum pane size + float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1); + float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2); + if (mouse_delta < -size_1_maximum_delta) + mouse_delta = -size_1_maximum_delta; + if (mouse_delta > size_2_maximum_delta) + mouse_delta = size_2_maximum_delta; + + // Apply resize + if (mouse_delta != 0.0f) + { + if (mouse_delta < 0.0f) + IM_ASSERT(*size1 + mouse_delta >= min_size1); + if (mouse_delta > 0.0f) + IM_ASSERT(*size2 - mouse_delta >= min_size2); + *size1 += mouse_delta; + *size2 -= mouse_delta; + bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); + MarkItemEdited(id); + } + } + + // Render + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f); + + return held; +} + +static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) +{ + const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs; + const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs; + if (int d = (int)(b->Width - a->Width)) + return d; + return (b->Index - a->Index); +} + +// Shrink excess width from a set of item, by removing width from the larger items first. +// Set items Width to -1.0f to disable shrinking this item. +void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess) +{ + if (count == 1) + { + if (items[0].Width >= 0.0f) + items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); + return; + } + ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); + int count_same_width = 1; + while (width_excess > 0.0f && count_same_width < count) + { + while (count_same_width < count && items[0].Width <= items[count_same_width].Width) + count_same_width++; + float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); + if (max_width_to_remove_per_item <= 0.0f) + break; + float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item); + for (int item_n = 0; item_n < count_same_width; item_n++) + items[item_n].Width -= width_to_remove_per_item; + width_excess -= width_to_remove_per_item * count_same_width; + } + + // Round width and redistribute remainder left-to-right (could make it an option of the function?) + // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator. + width_excess = 0.0f; + for (int n = 0; n < count; n++) + { + float width_rounded = ImFloor(items[n].Width); + width_excess += items[n].Width - width_rounded; + items[n].Width = width_rounded; + } + if (width_excess > 0.0f) + for (int n = 0; n < count; n++) + if (items[n].Index < (int)(width_excess + 0.01f)) + items[n].Width += 1.0f; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ComboBox +//------------------------------------------------------------------------- +// - BeginCombo() +// - EndCombo() +// - Combo() +//------------------------------------------------------------------------- + +static float CalcMaxPopupHeightFromItemCount(int items_count) +{ + ImGuiContext& g = *GImGui; + if (items_count <= 0) + return FLT_MAX; + return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); +} + +bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) +{ + // Always consume the SetNextWindowSizeConstraint() call in our early return paths + ImGuiContext& g = *GImGui; + bool has_window_size_constraint = (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) != 0; + g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint; + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together + + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const float expected_w = CalcItemWidth(); + const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w; + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); + + const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id); + bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None); + + const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + const float value_x2 = ImMax(frame_bb.Min.x, frame_bb.Max.x - arrow_size); + RenderNavHighlight(frame_bb, id); + if (!(flags & ImGuiComboFlags_NoPreview)) + window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(value_x2, frame_bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); + if (!(flags & ImGuiComboFlags_NoArrowButton)) + { + ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImU32 text_col = GetColorU32(ImGuiCol_Text); + window->DrawList->AddRectFilled(ImVec2(value_x2, frame_bb.Min.y), frame_bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight); + if (value_x2 + arrow_size - style.FramePadding.x <= frame_bb.Max.x) + RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f); + } + RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding); + if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) + { + ImVec2 preview_pos = frame_bb.Min + style.FramePadding; + if (g.LogEnabled) + LogSetNextTextDecoration("{", "}"); + RenderTextClipped(preview_pos, ImVec2(value_x2, frame_bb.Max.y), preview_value, NULL, NULL, ImVec2(0.0f, 0.0f)); + } + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if ((pressed || g.NavActivateId == id) && !popup_open) + { + if (window->DC.NavLayerCurrent == 0) + window->NavLastIds[0] = id; + OpenPopupEx(popup_id, ImGuiPopupFlags_None); + popup_open = true; + } + + if (!popup_open) + return false; + + if (has_window_size_constraint) + { + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint; + g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); + } + else + { + if ((flags & ImGuiComboFlags_HeightMask_) == 0) + flags |= ImGuiComboFlags_HeightRegular; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one + int popup_max_height_in_items = -1; + if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; + else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; + else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; + SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + } + + char name[16]; + ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth + + // Position the window given a custom constraint (peak into expected window size so we can position it) + // This might be easier to express with an hypothetical SetNextWindowPosConstraints() function. + if (ImGuiWindow* popup_window = FindWindowByName(name)) + if (popup_window->WasActive) + { + // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us. + ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window); + if (flags & ImGuiComboFlags_PopupAlignLeft) + popup_window->AutoPosLastDirection = ImGuiDir_Left; // "Below, Toward Left" + else + popup_window->AutoPosLastDirection = ImGuiDir_Down; // "Below, Toward Right (default)" + ImRect r_outer = GetWindowAllowedExtentRect(popup_window); + ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox); + SetNextWindowPos(pos); + } + + // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx() + ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove; + + // Horizontally align ourselves with the framed text + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y)); + bool ret = Begin(name, NULL, window_flags); + PopStyleVar(); + if (!ret) + { + EndPopup(); + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + return false; + } + return true; +} + +void ImGui::EndCombo() +{ + EndPopup(); +} + +// Getter for the old Combo() API: const char*[] +static bool Items_ArrayGetter(void* data, int idx, const char** out_text) +{ + const char* const* items = (const char* const*)data; + if (out_text) + *out_text = items[idx]; + return true; +} + +// Getter for the old Combo() API: "item1\0item2\0item3\0" +static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) +{ + // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. + const char* items_separated_by_zeros = (const char*)data; + int items_count = 0; + const char* p = items_separated_by_zeros; + while (*p) + { + if (idx == items_count) + break; + p += strlen(p) + 1; + items_count++; + } + if (!*p) + return false; + if (out_text) + *out_text = p; + return true; +} + +// Old API, prefer using BeginCombo() nowadays if you can. +bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items) +{ + ImGuiContext& g = *GImGui; + + // Call the getter to obtain the preview string which is a parameter to BeginCombo() + const char* preview_value = NULL; + if (*current_item >= 0 && *current_item < items_count) + items_getter(data, *current_item, &preview_value); + + // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. + if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)) + SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + + if (!BeginCombo(label, preview_value, ImGuiComboFlags_None)) + return false; + + // Display items + // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) + bool value_changed = false; + for (int i = 0; i < items_count; i++) + { + PushID((void*)(intptr_t)i); + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + if (Selectable(item_text, item_selected)) + { + value_changed = true; + *current_item = i; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + + EndCombo(); + if (value_changed) + MarkItemEdited(g.CurrentWindow->DC.LastItemId); + + return value_changed; +} + +// Combo box helper allowing to pass an array of strings. +bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) +{ + const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); + return value_changed; +} + +// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" +bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) +{ + int items_count = 0; + const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open + while (*p) + { + p += strlen(p) + 1; + items_count++; + } + bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Data Type and Data Formatting Helpers [Internal] +//------------------------------------------------------------------------- +// - PatchFormatStringFloatToInt() +// - DataTypeGetInfo() +// - DataTypeFormatString() +// - DataTypeApplyOp() +// - DataTypeApplyOpFromText() +// - DataTypeClamp() +// - GetMinimumStepAtDecimalPrecision +// - RoundScalarWithFormat<>() +//------------------------------------------------------------------------- + +static const ImGuiDataTypeInfo GDataTypeInfo[] = +{ + { sizeof(char), "S8", "%d", "%d" }, // ImGuiDataType_S8 + { sizeof(unsigned char), "U8", "%u", "%u" }, + { sizeof(short), "S16", "%d", "%d" }, // ImGuiDataType_S16 + { sizeof(unsigned short), "U16", "%u", "%u" }, + { sizeof(int), "S32", "%d", "%d" }, // ImGuiDataType_S32 + { sizeof(unsigned int), "U32", "%u", "%u" }, +#ifdef _MSC_VER + { sizeof(ImS64), "S64", "%I64d","%I64d" }, // ImGuiDataType_S64 + { sizeof(ImU64), "U64", "%I64u","%I64u" }, +#else + { sizeof(ImS64), "S64", "%lld", "%lld" }, // ImGuiDataType_S64 + { sizeof(ImU64), "U64", "%llu", "%llu" }, +#endif + { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) + { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double +}; +IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); + +// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f". +// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls. +// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?! +static const char* PatchFormatStringFloatToInt(const char* fmt) +{ + if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case. + return "%d"; + const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%) + const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user). + if (fmt_end > fmt_start && fmt_end[-1] == 'f') + { +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (fmt_start == fmt && fmt_end[0] == 0) + return "%d"; + ImGuiContext& g = *GImGui; + ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision. + return g.TempBuffer; +#else + IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d" +#endif + } + return fmt; +} + +const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type) +{ + IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + return &GDataTypeInfo[data_type]; +} + +int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format) +{ + // Signedness doesn't matter when pushing integer arguments + if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) + return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data); + if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) + return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data); + if (data_type == ImGuiDataType_Float) + return ImFormatString(buf, buf_size, format, *(const float*)p_data); + if (data_type == ImGuiDataType_Double) + return ImFormatString(buf, buf_size, format, *(const double*)p_data); + if (data_type == ImGuiDataType_S8) + return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data); + if (data_type == ImGuiDataType_U8) + return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data); + if (data_type == ImGuiDataType_S16) + return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data); + if (data_type == ImGuiDataType_U16) + return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data); + IM_ASSERT(0); + return 0; +} + +void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2) +{ + IM_ASSERT(op == '+' || op == '-'); + switch (data_type) + { + case ImGuiDataType_S8: + if (op == '+') { *(ImS8*)output = ImAddClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); } + if (op == '-') { *(ImS8*)output = ImSubClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); } + return; + case ImGuiDataType_U8: + if (op == '+') { *(ImU8*)output = ImAddClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); } + if (op == '-') { *(ImU8*)output = ImSubClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); } + return; + case ImGuiDataType_S16: + if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); } + if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); } + return; + case ImGuiDataType_U16: + if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); } + if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); } + return; + case ImGuiDataType_S32: + if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); } + if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); } + return; + case ImGuiDataType_U32: + if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); } + if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); } + return; + case ImGuiDataType_S64: + if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); } + if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); } + return; + case ImGuiDataType_U64: + if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); } + if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); } + return; + case ImGuiDataType_Float: + if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; } + if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; } + return; + case ImGuiDataType_Double: + if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; } + if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; } + return; + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); +} + +// User can input math operators (e.g. +100) to edit a numerical values. +// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess.. +bool ImGui::DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* p_data, const char* format) +{ + while (ImCharIsBlankA(*buf)) + buf++; + + // We don't support '-' op because it would conflict with inputing negative value. + // Instead you can use +-100 to subtract from an existing value + char op = buf[0]; + if (op == '+' || op == '*' || op == '/') + { + buf++; + while (ImCharIsBlankA(*buf)) + buf++; + } + else + { + op = 0; + } + if (!buf[0]) + return false; + + // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all. + const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); + ImGuiDataTypeTempStorage data_backup; + memcpy(&data_backup, p_data, type_info->Size); + + if (format == NULL) + format = type_info->ScanFmt; + + // FIXME-LEGACY: The aim is to remove those operators and write a proper expression evaluator at some point.. + int arg1i = 0; + if (data_type == ImGuiDataType_S32) + { + int* v = (int*)p_data; + int arg0i = *v; + float arg1f = 0.0f; + if (op && sscanf(initial_value_buf, format, &arg0i) < 1) + return false; + // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision + if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract) + else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply + else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide + else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant + } + else if (data_type == ImGuiDataType_Float) + { + // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in + format = "%f"; + float* v = (float*)p_data; + float arg0f = *v, arg1f = 0.0f; + if (op && sscanf(initial_value_buf, format, &arg0f) < 1) + return false; + if (sscanf(buf, format, &arg1f) < 1) + return false; + if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) + else if (op == '*') { *v = arg0f * arg1f; } // Multiply + else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide + else { *v = arg1f; } // Assign constant + } + else if (data_type == ImGuiDataType_Double) + { + format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis + double* v = (double*)p_data; + double arg0f = *v, arg1f = 0.0; + if (op && sscanf(initial_value_buf, format, &arg0f) < 1) + return false; + if (sscanf(buf, format, &arg1f) < 1) + return false; + if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) + else if (op == '*') { *v = arg0f * arg1f; } // Multiply + else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide + else { *v = arg1f; } // Assign constant + } + else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) + { + // All other types assign constant + // We don't bother handling support for legacy operators since they are a little too crappy. Instead we will later implement a proper expression evaluator in the future. + if (sscanf(buf, format, p_data) < 1) + return false; + } + else + { + // Small types need a 32-bit buffer to receive the result from scanf() + int v32; + if (sscanf(buf, format, &v32) < 1) + return false; + if (data_type == ImGuiDataType_S8) + *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX); + else if (data_type == ImGuiDataType_U8) + *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX); + else if (data_type == ImGuiDataType_S16) + *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX); + else if (data_type == ImGuiDataType_U16) + *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX); + else + IM_ASSERT(0); + } + + return memcmp(&data_backup, p_data, type_info->Size) != 0; +} + +template +static int DataTypeCompareT(const T* lhs, const T* rhs) +{ + if (*lhs < *rhs) return -1; + if (*lhs > *rhs) return +1; + return 0; +} + +int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2) +{ + switch (data_type) + { + case ImGuiDataType_S8: return DataTypeCompareT((const ImS8* )arg_1, (const ImS8* )arg_2); + case ImGuiDataType_U8: return DataTypeCompareT((const ImU8* )arg_1, (const ImU8* )arg_2); + case ImGuiDataType_S16: return DataTypeCompareT((const ImS16* )arg_1, (const ImS16* )arg_2); + case ImGuiDataType_U16: return DataTypeCompareT((const ImU16* )arg_1, (const ImU16* )arg_2); + case ImGuiDataType_S32: return DataTypeCompareT((const ImS32* )arg_1, (const ImS32* )arg_2); + case ImGuiDataType_U32: return DataTypeCompareT((const ImU32* )arg_1, (const ImU32* )arg_2); + case ImGuiDataType_S64: return DataTypeCompareT((const ImS64* )arg_1, (const ImS64* )arg_2); + case ImGuiDataType_U64: return DataTypeCompareT((const ImU64* )arg_1, (const ImU64* )arg_2); + case ImGuiDataType_Float: return DataTypeCompareT((const float* )arg_1, (const float* )arg_2); + case ImGuiDataType_Double: return DataTypeCompareT((const double*)arg_1, (const double*)arg_2); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return 0; +} + +template +static bool DataTypeClampT(T* v, const T* v_min, const T* v_max) +{ + // Clamp, both sides are optional, return true if modified + if (v_min && *v < *v_min) { *v = *v_min; return true; } + if (v_max && *v > *v_max) { *v = *v_max; return true; } + return false; +} + +bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max) +{ + switch (data_type) + { + case ImGuiDataType_S8: return DataTypeClampT((ImS8* )p_data, (const ImS8* )p_min, (const ImS8* )p_max); + case ImGuiDataType_U8: return DataTypeClampT((ImU8* )p_data, (const ImU8* )p_min, (const ImU8* )p_max); + case ImGuiDataType_S16: return DataTypeClampT((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max); + case ImGuiDataType_U16: return DataTypeClampT((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max); + case ImGuiDataType_S32: return DataTypeClampT((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max); + case ImGuiDataType_U32: return DataTypeClampT((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max); + case ImGuiDataType_S64: return DataTypeClampT((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max); + case ImGuiDataType_U64: return DataTypeClampT((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max); + case ImGuiDataType_Float: return DataTypeClampT((float* )p_data, (const float* )p_min, (const float* )p_max); + case ImGuiDataType_Double: return DataTypeClampT((double*)p_data, (const double*)p_min, (const double*)p_max); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return false; +} + +static float GetMinimumStepAtDecimalPrecision(int decimal_precision) +{ + static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; + if (decimal_precision < 0) + return FLT_MIN; + return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision); +} + +template +static const char* ImAtoi(const char* src, TYPE* output) +{ + int negative = 0; + if (*src == '-') { negative = 1; src++; } + if (*src == '+') { src++; } + TYPE v = 0; + while (*src >= '0' && *src <= '9') + v = (v * 10) + (*src++ - '0'); + *output = negative ? -v : v; + return src; +} + +// Sanitize format +// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi +// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi. +static void SanitizeFormatString(const char* fmt, char* fmt_out, size_t fmt_out_size) +{ + IM_UNUSED(fmt_out_size); + const char* fmt_end = ImParseFormatFindEnd(fmt); + IM_ASSERT((size_t)(fmt_end - fmt + 1) < fmt_out_size); // Format is too long, let us know if this happens to you! + while (fmt < fmt_end) + { + char c = *(fmt++); + if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '. + *(fmt_out++) = c; + } + *fmt_out = 0; // Zero-terminate +} + +template +TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v) +{ + const char* fmt_start = ImParseFormatFindStart(format); + if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string + return v; + + // Sanitize format + char fmt_sanitized[32]; + SanitizeFormatString(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized)); + fmt_start = fmt_sanitized; + + // Format value with our rounding, and read back + char v_str[64]; + ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); + const char* p = v_str; + while (*p == ' ') + p++; + if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) + v = (TYPE)ImAtof(p); + else + ImAtoi(p, (SIGNEDTYPE*)&v); + return v; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. +//------------------------------------------------------------------------- +// - DragBehaviorT<>() [Internal] +// - DragBehavior() [Internal] +// - DragScalar() +// - DragScalarN() +// - DragFloat() +// - DragFloat2() +// - DragFloat3() +// - DragFloat4() +// - DragFloatRange2() +// - DragInt() +// - DragInt2() +// - DragInt3() +// - DragInt4() +// - DragIntRange2() +//------------------------------------------------------------------------- + +// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) +template +bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + const bool is_clamped = (v_min < v_max); + const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + + // Default tweak speed + if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX)) + v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); + + // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings + float adjust_delta = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) + { + adjust_delta = g.IO.MouseDelta[axis]; + if (g.IO.KeyAlt) + adjust_delta *= 1.0f / 100.0f; + if (g.IO.KeyShift) + adjust_delta *= 10.0f; + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; + adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis]; + v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); + } + adjust_delta *= v_speed; + + // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. + if (axis == ImGuiAxis_Y) + adjust_delta = -adjust_delta; + + // For logarithmic use our range is effectively 0..1 so scale the delta into that range + if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0 + adjust_delta /= (float)(v_max - v_min); + + // Clear current value on activation + // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. + bool is_just_activated = g.ActiveIdIsJustActivated; + bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); + if (is_just_activated || is_already_past_limits_and_pushing_outward) + { + g.DragCurrentAccum = 0.0f; + g.DragCurrentAccumDirty = false; + } + else if (adjust_delta != 0.0f) + { + g.DragCurrentAccum += adjust_delta; + g.DragCurrentAccumDirty = true; + } + + if (!g.DragCurrentAccumDirty) + return false; + + TYPE v_cur = *v; + FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f; + + float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true + const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense) + if (is_logarithmic) + { + // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1; + logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); + + // Convert to parametric space, apply delta, convert back + float v_old_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + float v_new_parametric = v_old_parametric + g.DragCurrentAccum; + v_cur = ScaleValueFromRatioT(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + v_old_ref_for_accum_remainder = v_old_parametric; + } + else + { + v_cur += (SIGNEDTYPE)g.DragCurrentAccum; + } + + // Round to user desired precision based on format string + if (!(flags & ImGuiSliderFlags_NoRoundToFormat)) + v_cur = RoundScalarWithFormatT(format, data_type, v_cur); + + // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. + g.DragCurrentAccumDirty = false; + if (is_logarithmic) + { + // Convert to parametric space, apply delta, convert back + float v_new_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder); + } + else + { + g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v); + } + + // Lose zero sign for float/double + if (v_cur == (TYPE)-0) + v_cur = (TYPE)0; + + // Clamp values (+ handle overflow/wrap-around for integer types) + if (*v != v_cur && is_clamped) + { + if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point)) + v_cur = v_min; + if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point)) + v_cur = v_max; + } + + // Apply result + if (*v == v_cur) + return false; + *v = v_cur; + return true; +} + +bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. + IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + if (g.ActiveId != id) + return false; + if ((g.CurrentItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + return false; + + switch (data_type) + { + case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = DragBehaviorT(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN, p_max ? *(const ImS8*)p_max : IM_S8_MAX, format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } + case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = DragBehaviorT(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU8*) p_min : IM_U8_MIN, p_max ? *(const ImU8*)p_max : IM_U8_MAX, format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; } + case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS16*)p_min : IM_S16_MIN, p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; } + case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU16*)p_min : IM_U16_MIN, p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; } + case ImGuiDataType_S32: return DragBehaviorT(data_type, (ImS32*)p_v, v_speed, p_min ? *(const ImS32* )p_min : IM_S32_MIN, p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags); + case ImGuiDataType_U32: return DragBehaviorT(data_type, (ImU32*)p_v, v_speed, p_min ? *(const ImU32* )p_min : IM_U32_MIN, p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags); + case ImGuiDataType_S64: return DragBehaviorT(data_type, (ImS64*)p_v, v_speed, p_min ? *(const ImS64* )p_min : IM_S64_MIN, p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags); + case ImGuiDataType_U64: return DragBehaviorT(data_type, (ImU64*)p_v, v_speed, p_min ? *(const ImU64* )p_min : IM_U64_MIN, p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags); + case ImGuiDataType_Float: return DragBehaviorT(data_type, (float*)p_v, v_speed, p_min ? *(const float* )p_min : -FLT_MAX, p_max ? *(const float* )p_max : FLT_MAX, format, flags); + case ImGuiDataType_Double: return DragBehaviorT(data_type, (double*)p_v, v_speed, p_min ? *(const double*)p_min : -DBL_MAX, p_max ? *(const double*)p_max : DBL_MAX, format, flags); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return false; +} + +// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional. +// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. +bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0)) + return false; + + // Default format string when passing NULL + if (format == NULL) + format = DataTypeGetInfo(data_type)->PrintFmt; + else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.) + format = PatchFormatStringFloatToInt(format); + + // Tabbing or CTRL-clicking on Drag turns it into an InputText + const bool hovered = ItemHoverable(frame_bb, id); + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) + { + const bool focus_requested = temp_input_allowed && (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Focused) != 0; + const bool clicked = (hovered && g.IO.MouseClicked[0]); + const bool double_clicked = (hovered && g.IO.MouseDoubleClicked[0]); + if (focus_requested || clicked || double_clicked || g.NavActivateId == id || g.NavInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavInputId == id)) + temp_input_is_active = true; + } + // Experimental: simple click (without moving) turns Drag into an InputText + // FIXME: Currently polling ImGuiConfigFlags_IsTouchScreen, may either poll an hypothetical ImGuiBackendFlags_HasKeyboard and/or an explicit drag settings. + if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active) + if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) + { + g.NavInputId = id; + temp_input_is_active = true; + } + } + + if (temp_input_is_active) + { + // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set + const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, p_min, p_max) < 0); + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + // Drag behavior + const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags); + if (value_changed) + MarkItemEdited(id); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + if (g.LogEnabled) + LogSetNextTextDecoration("{", "}"); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + return value_changed; +} + +bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components, CalcItemWidth()); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) + { + PushID(i); + if (i > 0) + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags); + PopID(); + PopItemWidth(); + p_data = (void*)((char*)p_data + type_size); + } + PopID(); + + const char* label_end = FindRenderedTextEnd(label); + if (label != label_end) + { + SameLine(0, g.Style.ItemInnerSpacing.x); + TextEx(label, label_end); + } + + EndGroup(); + return value_changed; +} + +bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags); +} + +// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. +bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2, CalcItemWidth()); + + float min_min = (v_min >= v_max) ? -FLT_MAX : v_min; + float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max); + ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); + bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min); + float max_max = (v_min >= v_max) ? FLT_MAX : v_max; + ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); + value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextEx(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + return value_changed; +} + +// NB: v_speed is float to allow adjusting the drag speed with more precision +bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags); +} + +// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. +bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2, CalcItemWidth()); + + int min_min = (v_min >= v_max) ? INT_MIN : v_min; + int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max); + ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); + bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min); + int max_max = (v_min >= v_max) ? INT_MAX : v_max; + ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); + value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextEx(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +// Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details. +bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power) +{ + ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None; + if (power != 1.0f) + { + IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); + IM_ASSERT(p_min != NULL && p_max != NULL); // When using a power curve the drag needs to have known bounds + drag_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths + } + return DragScalar(label, data_type, p_data, v_speed, p_min, p_max, format, drag_flags); +} + +bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power) +{ + ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None; + if (power != 1.0f) + { + IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); + IM_ASSERT(p_min != NULL && p_max != NULL); // When using a power curve the drag needs to have known bounds + drag_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths + } + return DragScalarN(label, data_type, p_data, components, v_speed, p_min, p_max, format, drag_flags); +} + +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//------------------------------------------------------------------------- +// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. +//------------------------------------------------------------------------- +// - ScaleRatioFromValueT<> [Internal] +// - ScaleValueFromRatioT<> [Internal] +// - SliderBehaviorT<>() [Internal] +// - SliderBehavior() [Internal] +// - SliderScalar() +// - SliderScalarN() +// - SliderFloat() +// - SliderFloat2() +// - SliderFloat3() +// - SliderFloat4() +// - SliderAngle() +// - SliderInt() +// - SliderInt2() +// - SliderInt3() +// - SliderInt4() +// - VSliderScalar() +// - VSliderFloat() +// - VSliderInt() +//------------------------------------------------------------------------- + +// Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT) +template +float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) +{ + if (v_min == v_max) + return 0.0f; + IM_UNUSED(data_type); + + const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); + if (is_logarithmic) + { + bool flipped = v_max < v_min; + + if (flipped) // Handle the case where the range is backwards + ImSwap(v_min, v_max); + + // Fudge min/max to avoid getting close to log(0) + FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; + FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; + + // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) + if ((v_min == 0.0f) && (v_max < 0.0f)) + v_min_fudged = -logarithmic_zero_epsilon; + else if ((v_max == 0.0f) && (v_min < 0.0f)) + v_max_fudged = -logarithmic_zero_epsilon; + + float result; + + if (v_clamped <= v_min_fudged) + result = 0.0f; // Workaround for values that are in-range but below our fudge + else if (v_clamped >= v_max_fudged) + result = 1.0f; // Workaround for values that are in-range but above our fudge + else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions + { + float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) + float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; + float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; + if (v == 0.0f) + result = zero_point_center; // Special case for exactly zero + else if (v < 0.0f) + result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L; + else + result = zero_point_snap_R + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) * (1.0f - zero_point_snap_R)); + } + else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider + result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged)); + else + result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged)); + + return flipped ? (1.0f - result) : result; + } + + // Linear slider + return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min)); +} + +// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT) +template +TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) +{ + if (v_min == v_max) + return v_min; + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + + TYPE result; + if (is_logarithmic) + { + // We special-case the extents because otherwise our fudging can lead to "mathematically correct" but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value + if (t <= 0.0f) + result = v_min; + else if (t >= 1.0f) + result = v_max; + else + { + bool flipped = v_max < v_min; // Check if range is "backwards" + + // Fudge min/max to avoid getting silly results close to zero + FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; + FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; + + if (flipped) + ImSwap(v_min_fudged, v_max_fudged); + + // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) + if ((v_max == 0.0f) && (v_min < 0.0f)) + v_max_fudged = -logarithmic_zero_epsilon; + + float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range + + if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts + { + float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space + float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; + float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; + if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R) + result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) + else if (t_with_flip < zero_point_center) + result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L)))); + else + result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R)))); + } + else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider + result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip))); + else + result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip)); + } + } + else + { + // Linear slider + if (is_floating_point) + { + result = ImLerp(v_min, v_max, t); + } + else + { + // - For integer values we want the clicking position to match the grab box so we round above + // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. + // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 + // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. + if (t < 1.0) + { + FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t; + result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5))); + } + else + { + result = v_max; + } + } + } + + return result; +} + +// FIXME: Move more of the code into SliderBehavior() +template +bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + + const float grab_padding = 2.0f; + const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f; + float grab_sz = style.GrabMinSize; + SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); + if (!is_floating_point && v_range >= 0) // v_range < 0 may happen on integer overflows + grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit + grab_sz = ImMin(grab_sz, slider_sz); + const float slider_usable_sz = slider_sz - grab_sz; + const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f; + const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f; + + float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true + float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true + if (is_logarithmic) + { + // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1; + logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); + zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f); + } + + // Process interacting with the slider + bool value_changed = false; + if (g.ActiveId == id) + { + bool set_new_value = false; + float clicked_t = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (!g.IO.MouseDown[0]) + { + ClearActiveID(); + } + else + { + const float mouse_abs_pos = g.IO.MousePos[axis]; + clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; + if (axis == ImGuiAxis_Y) + clicked_t = 1.0f - clicked_t; + set_new_value = true; + } + } + else if (g.ActiveIdSource == ImGuiInputSource_Nav) + { + if (g.ActiveIdIsJustActivated) + { + g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation + g.SliderCurrentAccumDirty = false; + } + + const ImVec2 input_delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f); + float input_delta = (axis == ImGuiAxis_X) ? input_delta2.x : -input_delta2.y; + if (input_delta != 0.0f) + { + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; + if (decimal_precision > 0) + { + input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds + if (IsNavInputDown(ImGuiNavInput_TweakSlow)) + input_delta /= 10.0f; + } + else + { + if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow)) + input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps + else + input_delta /= 100.0f; + } + if (IsNavInputDown(ImGuiNavInput_TweakFast)) + input_delta *= 10.0f; + + g.SliderCurrentAccum += input_delta; + g.SliderCurrentAccumDirty = true; + } + + float delta = g.SliderCurrentAccum; + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + { + ClearActiveID(); + } + else if (g.SliderCurrentAccumDirty) + { + clicked_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + + if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits + { + set_new_value = false; + g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate + } + else + { + set_new_value = true; + float old_clicked_t = clicked_t; + clicked_t = ImSaturate(clicked_t + delta); + + // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator + TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + if (!(flags & ImGuiSliderFlags_NoRoundToFormat)) + v_new = RoundScalarWithFormatT(format, data_type, v_new); + float new_clicked_t = ScaleRatioFromValueT(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + + if (delta > 0) + g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta); + else + g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta); + } + + g.SliderCurrentAccumDirty = false; + } + } + + if (set_new_value) + { + TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + + // Round to user desired precision based on format string + if (!(flags & ImGuiSliderFlags_NoRoundToFormat)) + v_new = RoundScalarWithFormatT(format, data_type, v_new); + + // Apply result + if (*v != v_new) + { + *v = v_new; + value_changed = true; + } + } + } + + if (slider_sz < 1.0f) + { + *out_grab_bb = ImRect(bb.Min, bb.Min); + } + else + { + // Output grab position so it can be displayed by the caller + float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + if (axis == ImGuiAxis_Y) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + if (axis == ImGuiAxis_X) + *out_grab_bb = ImRect(grab_pos - grab_sz * 0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz * 0.5f, bb.Max.y - grab_padding); + else + *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5f); + } + + return value_changed; +} + +// For 32-bit and larger types, slider bounds are limited to half the natural type range. +// So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok. +// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders. +bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb) +{ + // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. + IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + + ImGuiContext& g = *GImGui; + if ((g.CurrentItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + return false; + + switch (data_type) + { + case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } + case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_U32, &v32, *(const ImU8*)p_min, *(const ImU8*)p_max, format, flags, out_grab_bb); if (r) *(ImU8*)p_v = (ImU8)v32; return r; } + case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS16*)p_min, *(const ImS16*)p_max, format, flags, out_grab_bb); if (r) *(ImS16*)p_v = (ImS16)v32; return r; } + case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_U32, &v32, *(const ImU16*)p_min, *(const ImU16*)p_max, format, flags, out_grab_bb); if (r) *(ImU16*)p_v = (ImU16)v32; return r; } + case ImGuiDataType_S32: + IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImS32*)p_v, *(const ImS32*)p_min, *(const ImS32*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_U32: + IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImU32*)p_v, *(const ImU32*)p_min, *(const ImU32*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_S64: + IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImS64*)p_v, *(const ImS64*)p_min, *(const ImS64*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_U64: + IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImU64*)p_v, *(const ImU64*)p_min, *(const ImU64*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_Float: + IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f); + return SliderBehaviorT(bb, id, data_type, (float*)p_v, *(const float*)p_min, *(const float*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_Double: + IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f); + return SliderBehaviorT(bb, id, data_type, (double*)p_v, *(const double*)p_min, *(const double*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return false; +} + +// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required. +// Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. +bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemAddFlags_Focusable : 0)) + return false; + + // Default format string when passing NULL + if (format == NULL) + format = DataTypeGetInfo(data_type)->PrintFmt; + else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.) + format = PatchFormatStringFloatToInt(format); + + // Tabbing or CTRL-clicking on Slider turns it into an input box + const bool hovered = ItemHoverable(frame_bb, id); + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) + { + const bool focus_requested = temp_input_allowed && (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Focused) != 0; + const bool clicked = (hovered && g.IO.MouseClicked[0]); + if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + if (temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id)) + temp_input_is_active = true; + } + } + + if (temp_input_is_active) + { + // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set + const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0; + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + + // Slider behavior + ImRect grab_bb; + const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb); + if (value_changed) + MarkItemEdited(id); + + // Render grab + if (grab_bb.Max.x > grab_bb.Min.x) + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + if (g.LogEnabled) + LogSetNextTextDecoration("{", "}"); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + return value_changed; +} + +// Add multiple sliders on 1 line for compact edition of multiple components +bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components, CalcItemWidth()); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) + { + PushID(i); + if (i > 0) + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags); + PopID(); + PopItemWidth(); + v = (void*)((char*)v + type_size); + } + PopID(); + + const char* label_end = FindRenderedTextEnd(label); + if (label != label_end) + { + SameLine(0, g.Style.ItemInnerSpacing.x); + TextEx(label, label_end); + } + + EndGroup(); + return value_changed; +} + +bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags) +{ + if (format == NULL) + format = "%.0f deg"; + float v_deg = (*v_rad) * 360.0f / (2 * IM_PI); + bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags); + *v_rad = v_deg * (2 * IM_PI) / 360.0f; + return value_changed; +} + +bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags); +} + +bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(frame_bb, id)) + return false; + + // Default format string when passing NULL + if (format == NULL) + format = DataTypeGetInfo(data_type)->PrintFmt; + else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.) + format = PatchFormatStringFloatToInt(format); + + const bool hovered = ItemHoverable(frame_bb, id); + if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + + // Slider behavior + ImRect grab_bb; + const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb); + if (value_changed) + MarkItemEdited(id); + + // Render grab + if (grab_bb.Max.y > grab_bb.Min.y) + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + // For the vertical slider we allow centered text to overlap the frame padding + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags); +} + +bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags); +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +// Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details. +bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power) +{ + ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None; + if (power != 1.0f) + { + IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); + slider_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths + } + return SliderScalar(label, data_type, p_data, p_min, p_max, format, slider_flags); +} + +bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power) +{ + ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None; + if (power != 1.0f) + { + IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!"); + slider_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths + } + return SliderScalarN(label, data_type, v, components, v_min, v_max, format, slider_flags); +} + +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//------------------------------------------------------------------------- +// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. +//------------------------------------------------------------------------- +// - ImParseFormatFindStart() [Internal] +// - ImParseFormatFindEnd() [Internal] +// - ImParseFormatTrimDecorations() [Internal] +// - ImParseFormatPrecision() [Internal] +// - TempInputTextScalar() [Internal] +// - InputScalar() +// - InputScalarN() +// - InputFloat() +// - InputFloat2() +// - InputFloat3() +// - InputFloat4() +// - InputInt() +// - InputInt2() +// - InputInt3() +// - InputInt4() +// - InputDouble() +//------------------------------------------------------------------------- + +// We don't use strchr() because our strings are usually very short and often start with '%' +const char* ImParseFormatFindStart(const char* fmt) +{ + while (char c = fmt[0]) + { + if (c == '%' && fmt[1] != '%') + return fmt; + else if (c == '%') + fmt++; + fmt++; + } + return fmt; +} + +const char* ImParseFormatFindEnd(const char* fmt) +{ + // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format. + if (fmt[0] != '%') + return fmt; + const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A')); + const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a')); + for (char c; (c = *fmt) != 0; fmt++) + { + if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0) + return fmt + 1; + if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0) + return fmt + 1; + } + return fmt; +} + +// Extract the format out of a format string with leading or trailing decorations +// fmt = "blah blah" -> return fmt +// fmt = "%.3f" -> return fmt +// fmt = "hello %.3f" -> return fmt + 6 +// fmt = "%.3f hello" -> return buf written with "%.3f" +const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size) +{ + const char* fmt_start = ImParseFormatFindStart(fmt); + if (fmt_start[0] != '%') + return fmt; + const char* fmt_end = ImParseFormatFindEnd(fmt_start); + if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data. + return fmt_start; + ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size)); + return buf; +} + +// Parse display precision back from the display format string +// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed. +int ImParseFormatPrecision(const char* fmt, int default_precision) +{ + fmt = ImParseFormatFindStart(fmt); + if (fmt[0] != '%') + return default_precision; + fmt++; + while (*fmt >= '0' && *fmt <= '9') + fmt++; + int precision = INT_MAX; + if (*fmt == '.') + { + fmt = ImAtoi(fmt + 1, &precision); + if (precision < 0 || precision > 99) + precision = default_precision; + } + if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation + precision = -1; + if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX) + precision = -1; + return (precision == INT_MAX) ? default_precision : precision; +} + +// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets) +// FIXME: Facilitate using this in variety of other situations. +bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags) +{ + // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id. + // We clear ActiveID on the first frame to allow the InputText() taking it back. + ImGuiContext& g = *GImGui; + const bool init = (g.TempInputId != id); + if (init) + ClearActiveID(); + + g.CurrentWindow->DC.CursorPos = bb.Min; + bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem); + if (init) + { + // First frame we started displaying the InputText widget, we expect it to take the active id. + IM_ASSERT(g.ActiveId == id); + g.TempInputId = g.ActiveId; + } + return value_changed; +} + +// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set! +// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility. +// However this may not be ideal for all uses, as some user code may break on out of bound values. +bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max) +{ + ImGuiContext& g = *GImGui; + + char fmt_buf[32]; + char data_buf[32]; + format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); + DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); + ImStrTrimBlanks(data_buf); + + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited; + flags |= ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal); + bool value_changed = false; + if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags)) + { + // Backup old value + size_t data_type_size = DataTypeGetInfo(data_type)->Size; + ImGuiDataTypeTempStorage data_backup; + memcpy(&data_backup, p_data, data_type_size); + + // Apply new value (or operations) then clamp + DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, p_data, NULL); + if (p_clamp_min || p_clamp_max) + { + if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0) + ImSwap(p_clamp_min, p_clamp_max); + DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max); + } + + // Only mark as edited if new value is different + value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; + if (value_changed) + MarkItemEdited(id); + } + return value_changed; +} + +// Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional. +// Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. +bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + + if (format == NULL) + format = DataTypeGetInfo(data_type)->PrintFmt; + + char buf[64]; + DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); + + bool value_changed = false; + if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0) + flags |= ImGuiInputTextFlags_CharsDecimal; + flags |= ImGuiInputTextFlags_AutoSelectAll; + flags |= ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. + + if (p_step != NULL) + { + const float button_size = GetFrameHeight(); + + BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive() + PushID(label); + SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); + if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view + value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format); + + // Step buttons + const ImVec2 backup_frame_padding = style.FramePadding; + style.FramePadding.x = style.FramePadding.y; + ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups; + if (flags & ImGuiInputTextFlags_ReadOnly) + button_flags |= ImGuiButtonFlags_Disabled; + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) + { + DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) + { + DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); + value_changed = true; + } + + const char* label_end = FindRenderedTextEnd(label); + if (label != label_end) + { + SameLine(0, style.ItemInnerSpacing.x); + TextEx(label, label_end); + } + style.FramePadding = backup_frame_padding; + + PopID(); + EndGroup(); + } + else + { + if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) + value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format); + } + if (value_changed) + MarkItemEdited(window->DC.LastItemId); + + return value_changed; +} + +bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components, CalcItemWidth()); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) + { + PushID(i); + if (i > 0) + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags); + PopID(); + PopItemWidth(); + p_data = (void*)((char*)p_data + type_size); + } + PopID(); + + const char* label_end = FindRenderedTextEnd(label); + if (label != label_end) + { + SameLine(0.0f, g.Style.ItemInnerSpacing.x); + TextEx(label, label_end); + } + + EndGroup(); + return value_changed; +} + +bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags) +{ + flags |= ImGuiInputTextFlags_CharsScientific; + return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags); +} + +bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags); +} + +bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags); +} + +bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags); +} + +bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags) +{ + // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes. + const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; + return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags); +} + +bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags); +} + +bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags); +} + +bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags); +} + +bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags) +{ + flags |= ImGuiInputTextFlags_CharsScientific; + return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint +//------------------------------------------------------------------------- +// - InputText() +// - InputTextWithHint() +// - InputTextMultiline() +// - InputTextEx() [Internal] +//------------------------------------------------------------------------- + +bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); +} + +bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); +} + +bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); +} + +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) +{ + int line_count = 0; + const char* s = text_begin; + while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding + if (c == '\n') + line_count++; + s--; + if (s[0] != '\n' && s[0] != '\r') + line_count++; + *out_text_end = s; + return line_count; +} + +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) +{ + ImGuiContext& g = *GImGui; + ImFont* font = g.Font; + const float line_height = g.FontSize; + const float scale = line_height / font->FontSize; + + ImVec2 text_size = ImVec2(0, 0); + float line_width = 0.0f; + + const ImWchar* s = text_begin; + while (s < text_end) + { + unsigned int c = (unsigned int)(*s++); + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + + const float char_width = font->GetCharAdvance((ImWchar)c) * scale; + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (out_offset) + *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) +namespace ImStb +{ + +static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenW; } +static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { return obj->TextW[idx]; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *GImGui; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); } +static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; } +static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) +{ + const ImWchar* text = obj->TextW.Data; + const ImWchar* text_remaining = NULL; + const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = (int)(text_remaining - (text + line_start_idx)); +} + +// When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators. +static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } +static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (is_separator(obj->TextW[idx - 1]) && !is_separator(obj->TextW[idx]) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } +#ifdef __APPLE__ // FIXME: Move setting to IO structure +static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx]) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } +#else +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } +#endif +#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL + +static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) +{ + ImWchar* dst = obj->TextW.Data + pos; + + // We maintain our buffer length in both UTF-8 and wchar formats + obj->Edited = true; + obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); + obj->CurLenW -= n; + + // Offset remaining text (FIXME-OPT: Use memmove) + const ImWchar* src = obj->TextW.Data + pos + n; + while (ImWchar c = *src++) + *dst++ = c; + *dst = '\0'; +} + +static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len) +{ + const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; + const int text_len = obj->CurLenW; + IM_ASSERT(pos <= text_len); + + const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); + if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA)) + return false; + + // Grow internal buffer if needed + if (new_text_len + text_len + 1 > obj->TextW.Size) + { + if (!is_resizable) + return false; + IM_ASSERT(text_len < obj->TextW.Size); + obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1); + } + + ImWchar* text = obj->TextW.Data; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); + memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + + obj->Edited = true; + obj->CurLenW += new_text_len; + obj->CurLenA += new_text_len_utf8; + obj->TextW[obj->CurLenW] = '\0'; + + return true; +} + +// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) +#define STB_TEXTEDIT_K_LEFT 0x200000 // keyboard input to move cursor left +#define STB_TEXTEDIT_K_RIGHT 0x200001 // keyboard input to move cursor right +#define STB_TEXTEDIT_K_UP 0x200002 // keyboard input to move cursor up +#define STB_TEXTEDIT_K_DOWN 0x200003 // keyboard input to move cursor down +#define STB_TEXTEDIT_K_LINESTART 0x200004 // keyboard input to move cursor to start of line +#define STB_TEXTEDIT_K_LINEEND 0x200005 // keyboard input to move cursor to end of line +#define STB_TEXTEDIT_K_TEXTSTART 0x200006 // keyboard input to move cursor to start of text +#define STB_TEXTEDIT_K_TEXTEND 0x200007 // keyboard input to move cursor to end of text +#define STB_TEXTEDIT_K_DELETE 0x200008 // keyboard input to delete selection or character under cursor +#define STB_TEXTEDIT_K_BACKSPACE 0x200009 // keyboard input to delete selection or character left of cursor +#define STB_TEXTEDIT_K_UNDO 0x20000A // keyboard input to perform undo +#define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo +#define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word +#define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word +#define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page +#define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page +#define STB_TEXTEDIT_K_SHIFT 0x400000 + +#define STB_TEXTEDIT_IMPLEMENTATION +#include "imstb_textedit.h" + +// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling +// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?) +static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const STB_TEXTEDIT_CHARTYPE* text, int text_len) +{ + stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len); + ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW); + if (text_len <= 0) + return; + if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len)) + { + state->cursor = text_len; + state->has_preferred_x = 0; + return; + } + IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace() +} + +} // namespace ImStb + +void ImGuiInputTextState::OnKeyPressed(int key) +{ + stb_textedit_key(this, &Stb, key); + CursorFollow = true; + CursorAnimReset(); +} + +ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() +{ + memset(this, 0, sizeof(*this)); +} + +// Public API to manipulate UTF-8 text +// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) +// FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) +{ + IM_ASSERT(pos + bytes_count <= BufTextLen); + char* dst = Buf + pos; + const char* src = Buf + pos + bytes_count; + while (char c = *src++) + *dst++ = c; + *dst = '\0'; + + if (CursorPos >= pos + bytes_count) + CursorPos -= bytes_count; + else if (CursorPos >= pos) + CursorPos = pos; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen -= bytes_count; +} + +void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) +{ + const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; + const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); + if (new_text_len + BufTextLen >= BufSize) + { + if (!is_resizable) + return; + + // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!) + ImGuiContext& g = *GImGui; + ImGuiInputTextState* edit_state = &g.InputTextState; + IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); + IM_ASSERT(Buf == edit_state->TextA.Data); + int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; + edit_state->TextA.reserve(new_buf_size + 1); + Buf = edit_state->TextA.Data; + BufSize = edit_state->BufCapacityA = new_buf_size; + } + + if (BufTextLen != pos) + memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); + memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); + Buf[BufTextLen + new_text_len] = '\0'; + + if (CursorPos >= pos) + CursorPos += new_text_len; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen += new_text_len; +} + +// Return false to discard a character. +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source) +{ + IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard); + unsigned int c = *p_char; + + // Filter non-printable (NB: isprint is unreliable! see #2467) + if (c < 0x20) + { + bool pass = false; + pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); + pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); + if (!pass) + return false; + } + + if (input_source != ImGuiInputSource_Clipboard) + { + // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817) + if (c == 127) + return false; + + // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME) + if (c >= 0xE000 && c <= 0xF8FF) + return false; + } + + // Filter Unicode ranges we are not handling in this build + if (c > IM_UNICODE_CODEPOINT_MAX) + return false; + + // Generic named filters + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific)) + { + // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf. + // The standard mandate that programs starts in the "C" locale where the decimal point is '.'. + // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point. + // Change the default decimal_point with: + // ImGui::GetCurrentContext()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point; + ImGuiContext& g = *GImGui; + const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint; + + // Allow 0-9 . - + * / + if (flags & ImGuiInputTextFlags_CharsDecimal) + if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/')) + return false; + + // Allow 0-9 . - + * / e E + if (flags & ImGuiInputTextFlags_CharsScientific) + if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E')) + return false; + + // Allow 0-9 a-F A-F + if (flags & ImGuiInputTextFlags_CharsHexadecimal) + if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) + return false; + + // Turn a-z into A-Z + if (flags & ImGuiInputTextFlags_CharsUppercase) + if (c >= 'a' && c <= 'z') + *p_char = (c += (unsigned int)('A' - 'a')); + + if (flags & ImGuiInputTextFlags_CharsNoBlank) + if (ImCharIsBlankW(c)) + return false; + } + + // Custom callback filter + if (flags & ImGuiInputTextFlags_CallbackCharFilter) + { + ImGuiInputTextCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); + callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; + callback_data.EventChar = (ImWchar)c; + callback_data.Flags = flags; + callback_data.UserData = user_data; + if (callback(&callback_data) != 0) + return false; + *p_char = callback_data.EventChar; + if (!callback_data.EventChar) + return false; + } + + return true; +} + +// Edit a string of text +// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!". +// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match +// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator. +// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect. +// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h +// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are +// doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188) +bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + IM_ASSERT(buf != NULL && buf_size >= 0); + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + const ImGuiStyle& style = g.Style; + + const bool RENDER_SELECTION_WHEN_INACTIVE = false; + const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; + const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; + const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; + const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; + const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; + if (is_resizable) + IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! + + if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope, + BeginGroup(); + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line + const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size); + + ImGuiWindow* draw_window = window; + ImVec2 inner_size = frame_size; + if (is_multiline) + { + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable)) + { + ItemSize(total_bb, style.FramePadding.y); + EndGroup(); + return false; + } + + // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug. + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove); + PopStyleVar(2); + PopStyleColor(); + if (!child_visible) + { + EndChild(); + EndGroup(); + return false; + } + draw_window = g.CurrentWindow; // Child window + draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it. + draw_window->DC.CursorPos += style.FramePadding; + inner_size.x -= draw_window->ScrollbarSizes.x; + } + else + { + // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd) + ItemSize(total_bb, style.FramePadding.y); + if (!(flags & ImGuiInputTextFlags_MergedItem)) + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemAddFlags_Focusable)) + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + if (hovered) + g.MouseCursor = ImGuiMouseCursor_TextInput; + + // We are only allowed to access the state if we are already the active widget. + ImGuiInputTextState* state = GetInputTextState(id); + + const bool focus_requested_by_code = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_FocusedByCode) != 0; + const bool focus_requested_by_tabbing = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; + + const bool user_clicked = hovered && io.MouseClicked[0]; + const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_Keyboard)); + const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); + const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); + + bool clear_active_id = false; + bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline); + + float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; + + const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline); + const bool init_make_active = (user_clicked || user_scroll_finish || user_nav_input_start || focus_requested_by_code || focus_requested_by_tabbing); + const bool init_state = (init_make_active || user_scroll_active); + if ((init_state && g.ActiveId != id) || init_changed_specs) + { + // Access state even if we don't own it yet. + state = &g.InputTextState; + state->CursorAnimReset(); + + // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) + // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int buf_len = (int)strlen(buf); + state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->InitialTextA.Data, buf, buf_len + 1); + + // Start edition + const char* buf_end = NULL; + state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string. + state->TextA.resize(0); + state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then) + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); + state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed. + const bool recycle_state = (state->ID == id && !init_changed_specs); + if (recycle_state) + { + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + state->CursorClamp(); + } + else + { + state->ID = id; + state->ScrollX = 0.0f; + stb_textedit_initialize_state(&state->Stb, !is_multiline); + if (!is_multiline && focus_requested_by_code) + select_all = true; + } + if (flags & ImGuiInputTextFlags_AlwaysOverwrite) + state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863) + if (!is_multiline && (focus_requested_by_tabbing || (user_clicked && io.KeyCtrl))) + select_all = true; + } + + if (g.ActiveId != id && init_make_active) + { + IM_ASSERT(state && state->ID == id); + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + + // Declare our inputs + IM_ASSERT(ImGuiNavInput_COUNT < 32); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); + g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Home) | ((ImU64)1 << ImGuiKey_End); + if (is_multiline) + g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown); + if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character. + g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Tab); + } + + // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) + if (g.ActiveId == id && state == NULL) + ClearActiveID(); + + // Release focus when we click outside + if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560 + clear_active_id = true; + + // Lock the decision of whether we are going to take the path displaying the cursor or selection + const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active); + bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); + bool value_changed = false; + bool enter_pressed = false; + + // When read-only we always use the live data passed to the function + // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :( + if (is_readonly && state != NULL && (render_cursor || render_selection)) + { + const char* buf_end = NULL; + state->TextW.resize(buf_size + 1); + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end); + state->CurLenA = (int)(buf_end - buf); + state->CursorClamp(); + render_selection &= state->HasSelection(); + } + + // Select the buffer to render. + const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid; + const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + + // Password pushes a temporary font with only a fallback glyph + if (is_password && !is_displaying_hint) + { + const ImFontGlyph* glyph = g.Font->FindGlyph('*'); + ImFont* password_font = &g.InputTextPasswordFont; + password_font->FontSize = g.Font->FontSize; + password_font->Scale = g.Font->Scale; + password_font->Ascent = g.Font->Ascent; + password_font->Descent = g.Font->Descent; + password_font->ContainerAtlas = g.Font->ContainerAtlas; + password_font->FallbackGlyph = glyph; + password_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); + PushFont(password_font); + } + + // Process mouse inputs and character inputs + int backup_current_text_length = 0; + if (g.ActiveId == id) + { + IM_ASSERT(state != NULL); + backup_current_text_length = state->CurLenA; + state->Edited = false; + state->BufCapacityA = buf_size; + state->Flags = flags; + state->UserCallback = callback; + state->UserCallbackData = callback_user_data; + + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. + // Down the line we should have a cleaner library-wide concept of Selected vs Active. + g.ActiveIdAllowOverlap = !io.MouseDown[0]; + g.WantTextInputNextFrame = 1; + + // Edit in progress + const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX; + const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f)); + + const bool is_osx = io.ConfigMacOSXBehaviors; + if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0])) + { + state->SelectAll(); + state->SelectedAllMouseLock = true; + } + else if (hovered && is_osx && io.MouseDoubleClicked[0]) + { + // Double-click select a word only, OS X style (by simulating keystrokes) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + else if (io.MouseClicked[0] && !state->SelectedAllMouseLock) + { + if (hovered) + { + stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + state->CursorAnimReset(); + } + } + else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) + { + stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); + state->CursorAnimReset(); + state->CursorFollow = true; + } + if (state->SelectedAllMouseLock && !io.MouseDown[0]) + state->SelectedAllMouseLock = false; + + // It is ill-defined whether the backend needs to send a \t character when pressing the TAB keys. + // Win32 and GLFW naturally do it but not SDL. + const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper); + if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && !is_readonly) + if (!io.InputQueueCharacters.contains('\t')) + { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)c); + } + + // Process regular text input (before we check for Return because using some IME will effectively send a Return?) + // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. + if (io.InputQueueCharacters.Size > 0) + { + if (!ignore_char_inputs && !is_readonly && !user_nav_input_start) + for (int n = 0; n < io.InputQueueCharacters.Size; n++) + { + // Insert character if they pass filtering + unsigned int c = (unsigned int)io.InputQueueCharacters[n]; + if (c == '\t' && io.KeyShift) + continue; + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)c); + } + + // Consume characters + io.InputQueueCharacters.resize(0); + } + } + + // Process other shortcuts/key-presses + bool cancel_edit = false; + if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) + { + IM_ASSERT(state != NULL); + IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here. + + const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1); + state->Stb.row_count_per_page = row_count_per_page; + + const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); + const bool is_osx = io.ConfigMacOSXBehaviors; + const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift)); + const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl + const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + const bool is_ctrl_key_only = (io.KeyMods == ImGuiKeyModFlags_Ctrl); + const bool is_shift_key_only = (io.KeyMods == ImGuiKeyModFlags_Shift); + const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl); + + const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); + const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection()); + const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_readonly; + const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && !is_readonly && is_undoable); + const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && !is_readonly && is_undoable; + + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_PageUp) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; } + else if (IsKeyPressedMap(ImGuiKey_PageDown) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; } + else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Backspace) && !is_readonly) + { + if (!state->HasSelection()) + { + if (is_wordmove_key_down) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT); + else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) + state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT); + } + state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); + } + else if (IsKeyPressedMap(ImGuiKey_Enter) || IsKeyPressedMap(ImGuiKey_KeyPadEnter)) + { + bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; + if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) + { + enter_pressed = clear_active_id = true; + } + else if (!is_readonly) + { + unsigned int c = '\n'; // Insert new line + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)c); + } + } + else if (IsKeyPressedMap(ImGuiKey_Escape)) + { + clear_active_id = cancel_edit = true; + } + else if (is_undo || is_redo) + { + state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); + state->ClearSelection(); + } + else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A)) + { + state->SelectAll(); + state->CursorFollow = true; + } + else if (is_cut || is_copy) + { + // Cut, Copy + if (io.SetClipboardTextFn) + { + const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0; + const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW; + const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1; + char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char)); + ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie); + SetClipboardText(clipboard_data); + MemFree(clipboard_data); + } + if (is_cut) + { + if (!state->HasSelection()) + state->SelectAll(); + state->CursorFollow = true; + stb_textedit_cut(state, &state->Stb); + } + } + else if (is_paste) + { + if (const char* clipboard = GetClipboardText()) + { + // Filter pasted buffer + const int clipboard_len = (int)strlen(clipboard); + ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar)); + int clipboard_filtered_len = 0; + for (const char* s = clipboard; *s; ) + { + unsigned int c; + s += ImTextCharFromUtf8(&c, s, NULL); + if (c == 0) + break; + if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard)) + continue; + clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; + } + clipboard_filtered[clipboard_filtered_len] = 0; + if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + { + stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len); + state->CursorFollow = true; + } + MemFree(clipboard_filtered); + } + } + + // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame. + render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); + } + + // Process callbacks and apply result back to user's buffer. + if (g.ActiveId == id) + { + IM_ASSERT(state != NULL); + const char* apply_new_text = NULL; + int apply_new_text_length = 0; + if (cancel_edit) + { + // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. + if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0) + { + // Push records into the undo stack so we can CTRL+Z the revert operation itself + apply_new_text = state->InitialTextA.Data; + apply_new_text_length = state->InitialTextA.Size - 1; + ImVector w_text; + if (apply_new_text_length > 0) + { + w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1); + ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length); + } + stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0); + } + } + + // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. + // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. + // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize). + bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + if (apply_edit_back_to_user_buffer) + { + // Apply new value immediately - copy modified buffer back + // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. + // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. + if (!is_readonly) + { + state->TextAIsValid = true; + state->TextA.resize(state->TextW.Size * 4 + 1); + ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL); + } + + // User callback + if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0) + { + IM_ASSERT(callback != NULL); + + // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. + ImGuiInputTextFlags event_flag = 0; + ImGuiKey event_key = ImGuiKey_COUNT; + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) + { + event_flag = ImGuiInputTextFlags_CallbackCompletion; + event_key = ImGuiKey_Tab; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_UpArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_DownArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited) + { + event_flag = ImGuiInputTextFlags_CallbackEdit; + } + else if (flags & ImGuiInputTextFlags_CallbackAlways) + { + event_flag = ImGuiInputTextFlags_CallbackAlways; + } + + if (event_flag) + { + ImGuiInputTextCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); + callback_data.EventFlag = event_flag; + callback_data.Flags = flags; + callback_data.UserData = callback_user_data; + + callback_data.EventKey = event_key; + callback_data.Buf = state->TextA.Data; + callback_data.BufTextLen = state->CurLenA; + callback_data.BufSize = state->BufCapacityA; + callback_data.BufDirty = false; + + // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) + ImWchar* text = state->TextW.Data; + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end); + + // Call user code + callback(&callback_data); + + // Read back what user may have modified + IM_ASSERT(callback_data.Buf == state->TextA.Data); // Invalid to modify those fields + IM_ASSERT(callback_data.BufSize == state->BufCapacityA); + IM_ASSERT(callback_data.Flags == flags); + const bool buf_dirty = callback_data.BufDirty; + if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } + if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb.cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } + if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } + if (buf_dirty) + { + IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + if (callback_data.BufTextLen > backup_current_text_length && is_resizable) + state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL); + state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + state->CursorAnimReset(); + } + } + } + + // Will copy result string if modified + if (!is_readonly && strcmp(state->TextA.Data, buf) != 0) + { + apply_new_text = state->TextA.Data; + apply_new_text_length = state->CurLenA; + } + } + + // Copy result to user buffer + if (apply_new_text) + { + // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size + // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used + // without any storage on user's side. + IM_ASSERT(apply_new_text_length >= 0); + if (is_resizable) + { + ImGuiInputTextCallbackData callback_data; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + callback_data.Flags = flags; + callback_data.Buf = buf; + callback_data.BufTextLen = apply_new_text_length; + callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); + callback_data.UserData = callback_user_data; + callback(&callback_data); + buf = callback_data.Buf; + buf_size = callback_data.BufSize; + apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); + IM_ASSERT(apply_new_text_length <= buf_size); + } + //IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); + + // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. + ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size)); + value_changed = true; + } + + // Clear temporary user storage + state->Flags = ImGuiInputTextFlags_None; + state->UserCallback = NULL; + state->UserCallbackData = NULL; + } + + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) + if (clear_active_id && g.ActiveId == id) + ClearActiveID(); + + // Render frame + if (!is_multiline) + { + RenderNavHighlight(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + } + + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size + ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; + ImVec2 text_size(0.0f, 0.0f); + + // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line + // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. + // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash. + const int buf_display_max_length = 2 * 1024 * 1024; + const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595 + const char* buf_display_end = NULL; // We have specialized paths below for setting the length + if (is_displaying_hint) + { + buf_display = hint; + buf_display_end = hint + strlen(hint); + } + + // Render text. We currently only render selection when the widget is active or while scrolling. + // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive. + if (render_cursor || render_selection) + { + IM_ASSERT(state != NULL); + if (!is_displaying_hint) + buf_display_end = buf_display + state->CurLenA; + + // Render text (with cursor and selection) + // This is going to be messy. We need to: + // - Display the text (this alone can be more easily clipped) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) + // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. + const ImWchar* text_begin = state->TextW.Data; + ImVec2 cursor_offset, select_start_offset; + + { + // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions. + const ImWchar* searches_input_ptr[2] = { NULL, NULL }; + int searches_result_line_no[2] = { -1000, -1000 }; + int searches_remaining = 0; + if (render_cursor) + { + searches_input_ptr[0] = text_begin + state->Stb.cursor; + searches_result_line_no[0] = -1; + searches_remaining++; + } + if (render_selection) + { + searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); + searches_result_line_no[1] = -1; + searches_remaining++; + } + + // Iterate all lines to find our line numbers + // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. + searches_remaining += is_multiline ? 1 : 0; + int line_count = 0; + //for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this when wchar_t are 16-bit + for (const ImWchar* s = text_begin; *s != 0; s++) + if (*s == '\n') + { + line_count++; + if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; } + if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; } + } + line_count++; + if (searches_result_line_no[0] == -1) + searches_result_line_no[0] = line_count; + if (searches_result_line_no[1] == -1) + searches_result_line_no[1] = line_count; + + // Calculate 2d position by finding the beginning of the line and measuring distance + cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; + cursor_offset.y = searches_result_line_no[0] * g.FontSize; + if (searches_result_line_no[1] >= 0) + { + select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; + select_start_offset.y = searches_result_line_no[1] * g.FontSize; + } + + // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) + if (is_multiline) + text_size = ImVec2(inner_size.x, line_count * g.FontSize); + } + + // Scroll + if (render_cursor && state->CursorFollow) + { + // Horizontal scroll in chunks of quarter width + if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) + { + const float scroll_increment_x = inner_size.x * 0.25f; + const float visible_width = inner_size.x - style.FramePadding.x; + if (cursor_offset.x < state->ScrollX) + state->ScrollX = IM_FLOOR(ImMax(0.0f, cursor_offset.x - scroll_increment_x)); + else if (cursor_offset.x - visible_width >= state->ScrollX) + state->ScrollX = IM_FLOOR(cursor_offset.x - visible_width + scroll_increment_x); + } + else + { + state->ScrollX = 0.0f; + } + + // Vertical scroll + if (is_multiline) + { + // Test if cursor is vertically visible + if (cursor_offset.y - g.FontSize < scroll_y) + scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - inner_size.y >= scroll_y) + scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f; + const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); + scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y); + draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag + draw_window->Scroll.y = scroll_y; + } + + state->CursorFollow = false; + } + + // Draw selection + const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f); + if (render_selection) + { + const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); + const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end); + + ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. + float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + float bg_offy_dn = is_multiline ? 0.0f : 2.0f; + ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll; + for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) + { + if (rect_pos.y > clip_rect.w + g.FontSize) + break; + if (rect_pos.y < clip_rect.y) + { + //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bit + //p = p ? p + 1 : text_selected_end; + while (p < text_selected_end) + if (*p++ == '\n') + break; + } + else + { + ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = IM_FLOOR(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines + ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); + rect.ClipWith(clip_rect); + if (rect.Overlaps(clip_rect)) + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + } + rect_pos.x = draw_pos.x - draw_scroll.x; + rect_pos.y += g.FontSize; + } + } + + // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. + if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) + { + ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + + // Draw blinking cursor + if (render_cursor) + { + state->CursorAnim += io.DeltaTime; + bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = ImFloor(draw_pos + cursor_offset - draw_scroll); + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + if (!is_readonly) + g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); + } + } + else + { + // Render text only (no selection, no cursor) + if (is_multiline) + text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width + else if (!is_displaying_hint && g.ActiveId == id) + buf_display_end = buf_display + state->CurLenA; + else if (!is_displaying_hint) + buf_display_end = buf_display + strlen(buf_display); + + if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) + { + ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + } + + if (is_password && !is_displaying_hint) + PopFont(); + + if (is_multiline) + { + Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y)); + EndChild(); + EndGroup(); + } + + // Log as text + if (g.LogEnabled && (!is_password || is_displaying_hint)) + { + LogSetNextTextDecoration("{", "}"); + LogRenderedText(&draw_pos, buf_display, buf_display_end); + } + + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited)) + MarkItemEdited(id); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) + return enter_pressed; + else + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. +//------------------------------------------------------------------------- +// - ColorEdit3() +// - ColorEdit4() +// - ColorPicker3() +// - RenderColorRectWithAlphaCheckerboard() [Internal] +// - ColorPicker4() +// - ColorButton() +// - SetColorEditOptions() +// - ColorTooltip() [Internal] +// - ColorEditOptionsPopup() [Internal] +// - ColorPickerOptionsPopup() [Internal] +//------------------------------------------------------------------------- + +bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); +} + +// Edit colors components (each component in 0.0f..1.0f range). +// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item. +bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float square_sz = GetFrameHeight(); + const float w_full = CalcItemWidth(); + const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); + const float w_inputs = w_full - w_button; + const char* label_display_end = FindRenderedTextEnd(label); + g.NextItemData.ClearFlags(); + + BeginGroup(); + PushID(label); + + // If we're not showing any slider there's no point in doing any HSV conversions + const ImGuiColorEditFlags flags_untouched = flags; + if (flags & ImGuiColorEditFlags_NoInputs) + flags = (flags & (~ImGuiColorEditFlags__DisplayMask)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions; + + // Context menu: display and modify options (before defaults are applied) + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorEditOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__DisplayMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DisplayMask); + if (!(flags & ImGuiColorEditFlags__DataTypeMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask); + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask); + if (!(flags & ImGuiColorEditFlags__InputMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputMask); + flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask)); + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask)); // Check that only 1 is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); // Check that only 1 is selected + + const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; + const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; + const int components = alpha ? 4 : 3; + + // Convert to the formats we need + float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; + if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB)) + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV)) + { + // Hue is lost when converting from greyscale rgb (saturation=0). Restore it. + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) + { + if (f[1] == 0) + f[0] = g.ColorEditLastHue; + if (f[2] == 0) + f[1] = g.ColorEditLastSat; + } + } + int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; + + bool value_changed = false; + bool value_changed_as_float = false; + + const ImVec2 pos = window->DC.CursorPos; + const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f; + window->DC.CursorPos.x = pos.x + inputs_offset_x; + + if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB/HSV 0..255 Sliders + const float w_item_one = ImMax(1.0f, IM_FLOOR((w_inputs - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components)); + const float w_item_last = ImMax(1.0f, IM_FLOOR(w_inputs - (w_item_one + style.ItemInnerSpacing.x) * (components - 1))); + + const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); + static const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; + static const char* fmt_table_int[3][4] = + { + { "%3d", "%3d", "%3d", "%3d" }, // Short display + { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA + { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA + }; + static const char* fmt_table_float[3][4] = + { + { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display + { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA + { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA + }; + const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1; + + for (int n = 0; n < components; n++) + { + if (n > 0) + SameLine(0, style.ItemInnerSpacing.x); + SetNextItemWidth((n + 1 < components) ? w_item_one : w_item_last); + + // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0. + if (flags & ImGuiColorEditFlags_Float) + { + value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + value_changed_as_float |= value_changed; + } + else + { + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + } + else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB Hexadecimal Input + char buf[64]; + if (alpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255)); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255)); + SetNextItemWidth(w_inputs); + if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) + { + value_changed = true; + char* p = buf; + while (*p == '#' || ImCharIsBlankA(*p)) + p++; + i[0] = i[1] = i[2] = 0; + i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha) + int r; + if (alpha) + r = sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) + else + r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); + IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'. + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + + ImGuiWindow* picker_active_window = NULL; + if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) + { + const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x; + window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y); + + const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); + if (ColorButton("##ColorButton", col_v4, flags)) + { + if (!(flags & ImGuiColorEditFlags_NoPicker)) + { + // Store current color and open a picker + g.ColorPickerRef = col_v4; + OpenPopup("picker"); + SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1, style.ItemSpacing.y)); + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + if (BeginPopup("picker")) + { + picker_active_window = g.CurrentWindow; + if (label != label_display_end) + { + TextEx(label, label_display_end); + Spacing(); + } + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; + SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + EndPopup(); + } + } + + if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) + { + const float text_offset_x = (flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x; + window->DC.CursorPos = ImVec2(pos.x + text_offset_x, pos.y + style.FramePadding.y); + TextEx(label, label_display_end); + } + + // Convert back + if (value_changed && picker_active_window == NULL) + { + if (!value_changed_as_float) + for (int n = 0; n < 4; n++) + f[n] = i[n] / 255.0f; + if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB)) + { + g.ColorEditLastHue = f[0]; + g.ColorEditLastSat = f[1]; + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + memcpy(g.ColorEditLastColor, f, sizeof(float) * 3); + } + if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV)) + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + + col[0] = f[0]; + col[1] = f[1]; + col[2] = f[2]; + if (alpha) + col[3] = f[3]; + } + + PopID(); + EndGroup(); + + // Drag and Drop Target + // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. + if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) + { + bool accepted_drag_drop = false; + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 + value_changed = accepted_drag_drop = true; + } + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * components); + value_changed = accepted_drag_drop = true; + } + + // Drag-drop payloads are always RGB + if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV)) + ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]); + EndDragDropTarget(); + } + + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). + if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) + window->DC.LastItemId = g.ActiveId; + + if (value_changed) + MarkItemEdited(window->DC.LastItemId); + + return value_changed; +} + +bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + float col4[4] = { col[0], col[1], col[2], 1.0f }; + if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) + return false; + col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; + return true; +} + +// Helper for ColorPicker4() +static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha) +{ + ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32(0,0,0,alpha8)); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32(255,255,255,alpha8)); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32(0,0,0,alpha8)); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32(255,255,255,alpha8)); +} + +// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.) +// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) +// FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0) +bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImDrawList* draw_list = window->DrawList; + ImGuiStyle& style = g.Style; + ImGuiIO& io = g.IO; + + const float width = CalcItemWidth(); + g.NextItemData.ClearFlags(); + + PushID(label); + BeginGroup(); + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + flags |= ImGuiColorEditFlags_NoSmallPreview; + + // Context menu: display and store options. + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorPickerOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask; + if (!(flags & ImGuiColorEditFlags__InputMask)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__InputMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__InputMask; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask)); // Check that only 1 is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); // Check that only 1 is selected + if (!(flags & ImGuiColorEditFlags_NoOptions)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); + + // Setup + int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; + bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); + ImVec2 picker_pos = window->DC.CursorPos; + float square_sz = GetFrameHeight(); + float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars + float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box + float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; + float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; + float bars_triangles_half_sz = IM_FLOOR(bars_width * 0.20f); + + float backup_initial_col[4]; + memcpy(backup_initial_col, col, components * sizeof(float)); + + float wheel_thickness = sv_picker_size * 0.08f; + float wheel_r_outer = sv_picker_size * 0.50f; + float wheel_r_inner = wheel_r_outer - wheel_thickness; + ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f); + + // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. + float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); + ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. + ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. + ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. + + float H = col[0], S = col[1], V = col[2]; + float R = col[0], G = col[1], B = col[2]; + if (flags & ImGuiColorEditFlags_InputRGB) + { + // Hue is lost when converting from greyscale rgb (saturation=0). Restore it. + ColorConvertRGBtoHSV(R, G, B, H, S, V); + if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) + { + if (S == 0) + H = g.ColorEditLastHue; + if (V == 0) + S = g.ColorEditLastSat; + } + } + else if (flags & ImGuiColorEditFlags_InputHSV) + { + ColorConvertHSVtoRGB(H, S, V, R, G, B); + } + + bool value_changed = false, value_changed_h = false, value_changed_sv = false; + + PushItemFlag(ImGuiItemFlags_NoNav, true); + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Hue wheel + SV triangle logic + InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); + if (IsItemActive()) + { + ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; + ImVec2 current_off = g.IO.MousePos - wheel_center; + float initial_dist2 = ImLengthSqr(initial_off); + if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1)) + { + // Interactive with Hue wheel + H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f; + if (H < 0.0f) + H += 1.0f; + value_changed = value_changed_h = true; + } + float cos_hue_angle = ImCos(-H * 2.0f * IM_PI); + float sin_hue_angle = ImSin(-H * 2.0f * IM_PI); + if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) + { + // Interacting with SV triangle + ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); + if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) + current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); + float uu, vv, ww; + ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); + V = ImClamp(1.0f - vv, 0.0001f, 1.0f); + S = ImClamp(uu / V, 0.0001f, 1.0f); + value_changed = value_changed_sv = true; + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // SV rectangle logic + InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); + if (IsItemActive()) + { + S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1)); + V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); + value_changed = value_changed_sv = true; + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + // Hue bar logic + SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); + InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); + value_changed = value_changed_h = true; + } + } + + // Alpha bar logic + if (alpha_bar) + { + SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); + InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); + value_changed = true; + } + } + PopItemFlag(); // ImGuiItemFlags_NoNav + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + SameLine(0, style.ItemInnerSpacing.x); + BeginGroup(); + } + + if (!(flags & ImGuiColorEditFlags_NoLabel)) + { + const char* label_display_end = FindRenderedTextEnd(label); + if (label != label_display_end) + { + if ((flags & ImGuiColorEditFlags_NoSidePreview)) + SameLine(0, style.ItemInnerSpacing.x); + TextEx(label, label_display_end); + } + } + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if ((flags & ImGuiColorEditFlags_NoLabel)) + Text("Current"); + + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip; + ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)); + if (ref_col != NULL) + { + Text("Original"); + ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); + if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2))) + { + memcpy(col, ref_col, components * sizeof(float)); + value_changed = true; + } + } + PopItemFlag(); + EndGroup(); + } + + // Convert back color to RGB + if (value_changed_h || value_changed_sv) + { + if (flags & ImGuiColorEditFlags_InputRGB) + { + ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10 * 1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]); + g.ColorEditLastHue = H; + g.ColorEditLastSat = S; + memcpy(g.ColorEditLastColor, col, sizeof(float) * 3); + } + else if (flags & ImGuiColorEditFlags_InputHSV) + { + col[0] = H; + col[1] = S; + col[2] = V; + } + } + + // R,G,B and H,S,V slider color editor + bool value_changed_fix_hue_wrap = false; + if ((flags & ImGuiColorEditFlags_NoInputs) == 0) + { + PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; + if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags__DisplayMask) == 0) + if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB)) + { + // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget. + // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050) + value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap); + value_changed = true; + } + if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags__DisplayMask) == 0) + value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV); + if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags__DisplayMask) == 0) + value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex); + PopItemWidth(); + } + + // Try to cancel hue wrap (after ColorEdit4 call), if any + if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB)) + { + float new_H, new_S, new_V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); + if (new_H <= 0 && H > 0) + { + if (new_V <= 0 && V != new_V) + ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); + else if (new_S <= 0) + ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); + } + } + + if (value_changed) + { + if (flags & ImGuiColorEditFlags_InputRGB) + { + R = col[0]; + G = col[1]; + B = col[2]; + ColorConvertRGBtoHSV(R, G, B, H, S, V); + if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) // Fix local Hue as display below will use it immediately. + { + if (S == 0) + H = g.ColorEditLastHue; + if (V == 0) + S = g.ColorEditLastSat; + } + } + else if (flags & ImGuiColorEditFlags_InputHSV) + { + H = col[0]; + S = col[1]; + V = col[2]; + ColorConvertHSVtoRGB(H, S, V, R, G, B); + } + } + + const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha); + const ImU32 col_black = IM_COL32(0,0,0,style_alpha8); + const ImU32 col_white = IM_COL32(255,255,255,style_alpha8); + const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8); + const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) }; + + ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); + ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); + ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!! + + ImVec2 sv_cursor_pos; + + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Render Hue Wheel + const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out). + const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); + for (int n = 0; n < 6; n++) + { + const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps; + const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; + const int vert_start_idx = draw_list->VtxBuffer.Size; + draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); + draw_list->PathStroke(col_white, 0, wheel_thickness); + const int vert_end_idx = draw_list->VtxBuffer.Size; + + // Paint colors over existing vertices + ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner); + ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner); + ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]); + } + + // Render Cursor + preview on Hue Wheel + float cos_hue_angle = ImCos(H * 2.0f * IM_PI); + float sin_hue_angle = ImSin(H * 2.0f * IM_PI); + ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f); + float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; + int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32); + draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments); + + // Render SV triangle (rotated according to hue) + ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); + ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); + ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); + ImVec2 uv_white = GetFontTexUvWhitePixel(); + draw_list->PrimReserve(6, 6); + draw_list->PrimVtx(tra, uv_white, hue_color32); + draw_list->PrimVtx(trb, uv_white, hue_color32); + draw_list->PrimVtx(trc, uv_white, col_white); + draw_list->PrimVtx(tra, uv_white, 0); + draw_list->PrimVtx(trb, uv_white, col_black); + draw_list->PrimVtx(trc, uv_white, 0); + draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f); + sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // Render SV Square + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white); + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black); + RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f); + sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S) * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much + sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); + + // Render Hue Bar + for (int i = 0; i < 6; ++i) + draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]); + float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size); + RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha); + } + + // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) + float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f; + draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, 12); + + // Render alpha bar + if (alpha_bar) + { + float alpha = ImSaturate(col[3]); + ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); + RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK); + float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size); + RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha); + } + + EndGroup(); + + if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0) + value_changed = false; + if (value_changed) + MarkItemEdited(window->DC.LastItemId); + + PopID(); + + return value_changed; +} + +// A little color square. Return true when clicked. +// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. +// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. +// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set. +bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(desc_id); + float default_size = GetFrameHeight(); + if (size.x == 0.0f) + size.x = default_size; + if (size.y == 0.0f) + size.y = default_size; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + if (flags & ImGuiColorEditFlags_NoAlpha) + flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); + + ImVec4 col_rgb = col; + if (flags & ImGuiColorEditFlags_InputHSV) + ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z); + + ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); + float grid_step = ImMin(size.x, size.y) / 2.99f; + float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + ImRect bb_inner = bb; + float off = 0.0f; + if ((flags & ImGuiColorEditFlags_NoBorder) == 0) + { + off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. + bb_inner.Expand(off); + } + if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) + { + float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); + RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); + } + else + { + // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha; + if (col_source.w < 1.0f) + RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + else + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); + } + RenderNavHighlight(bb, id); + if ((flags & ImGuiColorEditFlags_NoBorder) == 0) + { + if (g.Style.FrameBorderSize > 0.0f) + RenderFrameBorder(bb.Min, bb.Max, rounding); + else + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border + } + + // Drag and Drop Source + // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test. + if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource()) + { + if (flags & ImGuiColorEditFlags_NoAlpha) + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once); + else + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once); + ColorButton(desc_id, col, flags); + SameLine(); + TextEx("Color"); + EndDragDropSource(); + } + + // Tooltip + if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + + return pressed; +} + +// Initialize/override default color options +void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + if ((flags & ImGuiColorEditFlags__DisplayMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DisplayMask; + if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask; + if ((flags & ImGuiColorEditFlags__PickerMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask; + if ((flags & ImGuiColorEditFlags__InputMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputMask; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DataTypeMask)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); // Check only 1 option is selected + g.ColorEditOptions = flags; +} + +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + + BeginTooltipEx(0, ImGuiTooltipFlags_OverridePreviousTooltip); + const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; + if (text_end > text) + { + TextEx(text, text_end); + Separator(); + } + + ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); + ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + SameLine(); + if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags__InputMask)) + { + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); + else + Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); + } + else if (flags & ImGuiColorEditFlags_InputHSV) + { + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]); + else + Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]); + } + EndTooltip(); +} + +void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) +{ + bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__DisplayMask); + bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); + if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + ImGuiColorEditFlags opts = g.ColorEditOptions; + if (allow_opt_inputs) + { + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayRGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHSV; + if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHex; + } + if (allow_opt_datatype) + { + if (allow_opt_inputs) Separator(); + if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; + if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; + } + + if (allow_opt_inputs || allow_opt_datatype) + Separator(); + if (Button("Copy as..", ImVec2(-1, 0))) + OpenPopup("Copy"); + if (BeginPopup("Copy")) + { + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb); + if (Selectable(buf)) + SetClipboardText(buf); + if (!(flags & ImGuiColorEditFlags_NoAlpha)) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + } + EndPopup(); + } + + g.ColorEditOptions = opts; + EndPopup(); +} + +void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags) +{ + bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); + bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); + if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + if (allow_opt_picker) + { + ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function + PushItemWidth(picker_size.x); + for (int picker_type = 0; picker_type < 2; picker_type++) + { + // Draw small/thumbnail version of each picker type (over an invisible button for selection) + if (picker_type > 0) Separator(); + PushID(picker_type); + ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha); + if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; + ImVec2 backup_pos = GetCursorScreenPos(); + if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup + g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask); + SetCursorScreenPos(backup_pos); + ImVec4 previewing_ref_col; + memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4)); + ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags); + PopID(); + } + PopItemWidth(); + } + if (allow_opt_alpha_bar) + { + if (allow_opt_picker) Separator(); + CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + } + EndPopup(); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. +//------------------------------------------------------------------------- +// - TreeNode() +// - TreeNodeV() +// - TreeNodeEx() +// - TreeNodeExV() +// - TreeNodeBehavior() [Internal] +// - TreePush() +// - TreePop() +// - GetTreeNodeToLabelSpacing() +// - SetNextItemOpen() +// - CollapsingHeader() +//------------------------------------------------------------------------- + +bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + return TreeNodeBehavior(window->GetID(label), 0, label, NULL); +} + +bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) +{ + return TreeNodeExV(str_id, 0, fmt, args); +} + +bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) +{ + return TreeNodeExV(ptr_id, 0, fmt, args); +} + +bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags, label, NULL); +} + +bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) +{ + if (flags & ImGuiTreeNodeFlags_Leaf) + return true; + + // We only write to the tree storage if the user clicks (or explicitly use the SetNextItemOpen function) + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStorage* storage = window->DC.StateStorage; + + bool is_open; + if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen) + { + if (g.NextItemData.OpenCond & ImGuiCond_Always) + { + is_open = g.NextItemData.OpenVal; + storage->SetInt(id, is_open); + } + else + { + // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. + const int stored_value = storage->GetInt(id, -1); + if (stored_value == -1) + { + is_open = g.NextItemData.OpenVal; + storage->SetInt(id, is_open); + } + else + { + is_open = stored_value != 0; + } + } + } + else + { + is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; + } + + // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). + // NB- If we are above max depth we still allow manually opened nodes to be logged. + if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand) + is_open = true; + + return is_open; +} + +bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; + const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y)); + + if (!label_end) + label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); + + // We vertically grow up to current line height up the typical widget height. + const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); + ImRect frame_bb; + frame_bb.Min.x = (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x; + frame_bb.Min.y = window->DC.CursorPos.y; + frame_bb.Max.x = window->WorkRect.Max.x; + frame_bb.Max.y = window->DC.CursorPos.y + frame_height; + if (display_frame) + { + // Framed header expand a little outside the default padding, to the edge of InnerClipRect + // (FIXME: May remove this at some point and make InnerClipRect align with WindowPadding.x instead of WindowPadding.x*0.5f) + frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f); + frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f); + } + + const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapser arrow width + Spacing + const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it + const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f); // Include collapser + ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y); + ItemSize(ImVec2(text_width, frame_height), padding.y); + + // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing + ImRect interact_bb = frame_bb; + if (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth)) == 0) + interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f; + + // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child. + // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). + // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero. + const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; + bool is_open = TreeNodeBehaviorIsOpen(id, flags); + if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth); + + bool item_add = ItemAdd(interact_bb, id); + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + window->DC.LastItemDisplayRect = frame_bb; + + if (!item_add) + { + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushOverrideID(id); + IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); + return is_open; + } + + ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None; + if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) + button_flags |= ImGuiButtonFlags_AllowItemOverlap; + if (!is_leaf) + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + + // We allow clicking on the arrow section with keyboard modifiers held, in order to easily + // allow browsing a tree while preserving selection with code implementing multi-selection patterns. + // When clicking on the rest of the tree node we always disallow keyboard modifiers. + const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x; + const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x; + const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2); + if (window != g.HoveredWindow || !is_mouse_x_over_arrow) + button_flags |= ImGuiButtonFlags_NoKeyModifiers; + + // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags. + // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support. + // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0) + // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0) + // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1) + // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1) + // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0) + // It is rather standard that arrow click react on Down rather than Up. + // We set ImGuiButtonFlags_PressedOnClickRelease on OpenOnDoubleClick because we want the item to be active on the initial MouseDown in order for drag and drop to work. + if (is_mouse_x_over_arrow) + button_flags |= ImGuiButtonFlags_PressedOnClick; + else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + else + button_flags |= ImGuiButtonFlags_PressedOnClickRelease; + + bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0; + const bool was_selected = selected; + + bool hovered, held; + bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); + bool toggled = false; + if (!is_leaf) + { + if (pressed && g.DragDropHoldJustPressedId != id) + { + if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id)) + toggled = true; + if (flags & ImGuiTreeNodeFlags_OpenOnArrow) + toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job + if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseDoubleClicked[0]) + toggled = true; + } + else if (pressed && g.DragDropHoldJustPressedId == id) + { + IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold); + if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. + toggled = true; + } + + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open) + { + toggled = true; + NavMoveRequestCancel(); + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority? + { + toggled = true; + NavMoveRequestCancel(); + } + + if (toggled) + { + is_open = !is_open; + window->DC.StateStorage->SetInt(id, is_open); + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledOpen; + } + } + if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) + SetItemAllowOverlap(); + + // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger. + if (selected != was_selected) //-V547 + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + + // Render + const ImU32 text_col = GetColorU32(ImGuiCol_Text); + ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin; + if (display_frame) + { + // Framed type + const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); + RenderNavHighlight(frame_bb, id, nav_highlight_flags); + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); + else if (!is_leaf) + RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); + else // Leaf without bullet, left-adjusted text + text_pos.x -= text_offset_x; + if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton) + frame_bb.Max.x -= g.FontSize + style.FramePadding.x; + + if (g.LogEnabled) + LogSetNextTextDecoration("###", "###"); + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + } + else + { + // Unframed typed for tree nodes + if (hovered || selected) + { + const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); + RenderNavHighlight(frame_bb, id, nav_highlight_flags); + } + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); + else if (!is_leaf) + RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f); + if (g.LogEnabled) + LogSetNextTextDecoration(">", NULL); + RenderText(text_pos, label, label_end, false); + } + + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushOverrideID(id); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); + return is_open; +} + +void ImGui::TreePush(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(str_id ? str_id : "#TreePush"); +} + +void ImGui::TreePush(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(ptr_id ? ptr_id : (const void*)"#TreePush"); +} + +void ImGui::TreePushOverrideID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + Indent(); + window->DC.TreeDepth++; + window->IDStack.push_back(id); +} + +void ImGui::TreePop() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + Unindent(); + + window->DC.TreeDepth--; + ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); + + // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) + if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + if (g.NavIdIsAlive && (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask)) + { + SetNavID(window->IDStack.back(), g.NavLayer, 0, ImRect()); + NavMoveRequestCancel(); + } + window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1; + + IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. + PopID(); +} + +// Horizontal distance preceding label when using TreeNode() or Bullet() +float ImGui::GetTreeNodeToLabelSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + (g.Style.FramePadding.x * 2.0f); +} + +// Set next TreeNode/CollapsingHeader open state. +void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen; + g.NextItemData.OpenVal = is_open; + g.NextItemData.OpenCond = cond ? cond : ImGuiCond_Always; +} + +// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). +// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). +bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label); +} + +// p_visible == NULL : regular collapsing header +// p_visible != NULL && *p_visible == true : show a small close button on the corner of the header, clicking the button will set *p_visible = false +// p_visible != NULL && *p_visible == false : do not show the header at all +// Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen. +bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + if (p_visible && !*p_visible) + return false; + + ImGuiID id = window->GetID(label); + flags |= ImGuiTreeNodeFlags_CollapsingHeader; + if (p_visible) + flags |= ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_ClipLabelForTrailingButton; + bool is_open = TreeNodeBehavior(id, flags, label); + if (p_visible != NULL) + { + // Create a small overlapping close button + // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. + // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow. + ImGuiContext& g = *GImGui; + ImGuiLastItemDataBackup last_item_backup; + float button_size = g.FontSize; + float button_x = ImMax(window->DC.LastItemRect.Min.x, window->DC.LastItemRect.Max.x - g.Style.FramePadding.x * 2.0f - button_size); + float button_y = window->DC.LastItemRect.Min.y; + ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id); + if (CloseButton(close_button_id, ImVec2(button_x, button_y))) + *p_visible = false; + last_item_backup.Restore(); + } + + return is_open; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Selectable +//------------------------------------------------------------------------- +// - Selectable() +//------------------------------------------------------------------------- + +// Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image. +// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id. +// With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowItemOverlap are also frequently used flags. +// FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported. +bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrLineTextBaseOffset; + ItemSize(size, 0.0f); + + // Fill horizontal space + // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets. + const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; + const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x; + const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; + if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth)) + size.x = ImMax(label_size.x, max_x - min_x); + + // Text stays at the submission position, but bounding box may be extended on both sides + const ImVec2 text_min = pos; + const ImVec2 text_max(min_x + size.x, pos.y + size.y); + + // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. + ImRect bb(min_x, pos.y, text_max.x, text_max.y); + if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) + { + const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; + const float spacing_y = style.ItemSpacing.y; + const float spacing_L = IM_FLOOR(spacing_x * 0.50f); + const float spacing_U = IM_FLOOR(spacing_y * 0.50f); + bb.Min.x -= spacing_L; + bb.Min.y -= spacing_U; + bb.Max.x += (spacing_x - spacing_L); + bb.Max.y += (spacing_y - spacing_U); + } + //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } + + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; + if (span_all_columns) + { + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + } + + bool item_add; + if (flags & ImGuiSelectableFlags_Disabled) + { + ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNavDefaultFocus; + item_add = ItemAdd(bb, id); + g.CurrentItemFlags = backup_item_flags; + } + else + { + item_add = ItemAdd(bb, id); + } + + if (span_all_columns) + { + window->ClipRect.Min.x = backup_clip_rect_min_x; + window->ClipRect.Max.x = backup_clip_rect_max_x; + } + + if (!item_add) + return false; + + // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, + // which would be advantageous since most selectable are not selected. + if (span_all_columns && window->DC.CurrentColumns) + PushColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + TablePushBackgroundChannel(); + + // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } + if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } + if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } + if (flags & ImGuiSelectableFlags_Disabled) { button_flags |= ImGuiButtonFlags_Disabled; } + if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } + if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; } + + if (flags & ImGuiSelectableFlags_Disabled) + selected = false; + + const bool was_selected = selected; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + + // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard + if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) + { + if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) + { + SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos)); + g.NavDisableHighlight = true; + } + } + if (pressed) + MarkItemEdited(id); + + if (flags & ImGuiSelectableFlags_AllowItemOverlap) + SetItemAllowOverlap(); + + // In this branch, Selectable() cannot toggle the selection so this will never trigger. + if (selected != was_selected) //-V547 + window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + + // Render + if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) + hovered = true; + if (hovered || selected) + { + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + } + + if (span_all_columns && window->DC.CurrentColumns) + PopColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + TablePopBackgroundChannel(); + + if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); + RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); + if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.CurrentItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) + CloseCurrentPopup(); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + return pressed; +} + +bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + if (Selectable(label, *p_selected, flags, size_arg)) + { + *p_selected = !*p_selected; + return true; + } + return false; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ListBox +//------------------------------------------------------------------------- +// - BeginListBox() +// - EndListBox() +// - ListBox() +//------------------------------------------------------------------------- + +// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty" +// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height). +bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + // Size default to hold ~7.25 items. + // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + ImVec2 size = ImFloor(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f)); + ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + g.NextItemData.ClearFlags(); + + if (!IsRectVisible(bb.Min, bb.Max)) + { + ItemSize(bb.GetSize(), style.FramePadding.y); + ItemAdd(bb, 0, &frame_bb); + return false; + } + + // FIXME-OPT: We could omit the BeginGroup() if label_size.x but would need to omit the EndGroup() as well. + BeginGroup(); + if (label_size.x > 0.0f) + { + ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y); + RenderText(label_pos, label); + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); + } + + BeginChildFrame(id, frame_bb.GetSize()); + return true; +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// OBSOLETED in 1.81 (from February 2021) +bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) +{ + // If height_in_items == -1, default height is maximum 7. + ImGuiContext& g = *GImGui; + float height_in_items_f = (height_in_items < 0 ? ImMin(items_count, 7) : height_in_items) + 0.25f; + ImVec2 size; + size.x = 0.0f; + size.y = GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f; + return BeginListBox(label, size); +} +#endif + +void ImGui::EndListBox() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?"); + IM_UNUSED(window); + + EndChildFrame(); + EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label +} + +bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) +{ + const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); + return value_changed; +} + +// This is merely a helper around BeginListBox(), EndListBox(). +// Considering using those directly to submit custom data or store selection differently. +bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) +{ + ImGuiContext& g = *GImGui; + + // Calculate size from "height_in_items" + if (height_in_items < 0) + height_in_items = ImMin(items_count, 7); + float height_in_items_f = height_in_items + 0.25f; + ImVec2 size(0.0f, ImFloor(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f)); + + if (!BeginListBox(label, size)) + return false; + + // Assume all items have even height (= 1 line of text). If you need items of different height, + // you can create a custom version of ListBox() in your code without using the clipper. + bool value_changed = false; + ImGuiListClipper clipper; + clipper.Begin(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + + PushID(i); + const bool item_selected = (i == *current_item); + if (Selectable(item_text, item_selected)) + { + *current_item = i; + value_changed = true; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + EndListBox(); + if (value_changed) + MarkItemEdited(g.CurrentWindow->DC.LastItemId); + + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: PlotLines, PlotHistogram +//------------------------------------------------------------------------- +// - PlotEx() [Internal] +// - PlotLines() +// - PlotHistogram() +//------------------------------------------------------------------------- +// Plot/Graph widgets are not very good. +// Consider writing your own, or using a third-party one, see: +// - ImPlot https://github.com/epezent/implot +// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions +//------------------------------------------------------------------------- + +int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return -1; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + if (frame_size.x == 0.0f) + frame_size.x = CalcItemWidth(); + if (frame_size.y == 0.0f) + frame_size.y = label_size.y + (style.FramePadding.y * 2); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0, &frame_bb)) + return -1; + const bool hovered = ItemHoverable(frame_bb, id); + + // Determine scale from values if not specified + if (scale_min == FLT_MAX || scale_max == FLT_MAX) + { + float v_min = FLT_MAX; + float v_max = -FLT_MAX; + for (int i = 0; i < values_count; i++) + { + const float v = values_getter(data, i); + if (v != v) // Ignore NaN values + continue; + v_min = ImMin(v_min, v); + v_max = ImMax(v_max, v); + } + if (scale_min == FLT_MAX) + scale_min = v_min; + if (scale_max == FLT_MAX) + scale_max = v_max; + } + + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1; + int idx_hovered = -1; + if (values_count >= values_count_min) + { + int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + + // Tooltip on hover + if (hovered && inner_bb.Contains(g.IO.MousePos)) + { + const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); + const int v_idx = (int)(t * item_count); + IM_ASSERT(v_idx >= 0 && v_idx < values_count); + + const float v0 = values_getter(data, (v_idx + values_offset) % values_count); + const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); + if (plot_type == ImGuiPlotType_Lines) + SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1); + else if (plot_type == ImGuiPlotType_Histogram) + SetTooltip("%d: %8.4g", v_idx, v0); + idx_hovered = v_idx; + } + + const float t_step = 1.0f / (float)res_w; + const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); + + float v0 = values_getter(data, (0 + values_offset) % values_count); + float t0 = 0.0f; + ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + + const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); + const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); + + for (int n = 0; n < res_w; n++) + { + const float t1 = t0 + t_step; + const int v1_idx = (int)(t0 * item_count + 0.5f); + IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); + const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); + const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); + + // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. + ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); + ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); + if (plot_type == ImGuiPlotType_Lines) + { + window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); + } + else if (plot_type == ImGuiPlotType_Histogram) + { + if (pos1.x >= pos0.x + 2.0f) + pos1.x -= 1.0f; + window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); + } + + t0 = t1; + tp0 = tp1; + } + } + + // Text overlay + if (overlay_text) + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + + // Return hovered index or -1 if none are hovered. + // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx(). + return idx_hovered; +} + +struct ImGuiPlotArrayGetterData +{ + const float* Values; + int Stride; + + ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } +}; + +static float Plot_ArrayGetter(void* data, int idx) +{ + ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; + const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); + return v; +} + +void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Value helpers +// Those is not very useful, legacy API. +//------------------------------------------------------------------------- +// - Value() +//------------------------------------------------------------------------- + +void ImGui::Value(const char* prefix, bool b) +{ + Text("%s: %s", prefix, (b ? "true" : "false")); +} + +void ImGui::Value(const char* prefix, int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, unsigned int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, float v, const char* float_format) +{ + if (float_format) + { + char fmt[64]; + ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); + Text(fmt, prefix, v); + } + else + { + Text("%s: %.3f", prefix, v); + } +} + +//------------------------------------------------------------------------- +// [SECTION] MenuItem, BeginMenu, EndMenu, etc. +//------------------------------------------------------------------------- +// - ImGuiMenuColumns [Internal] +// - BeginMenuBar() +// - EndMenuBar() +// - BeginMainMenuBar() +// - EndMainMenuBar() +// - BeginMenu() +// - EndMenu() +// - MenuItem() +//------------------------------------------------------------------------- + +// Helpers for internal use +void ImGuiMenuColumns::Update(int count, float spacing, bool clear) +{ + IM_ASSERT(count == IM_ARRAYSIZE(Pos)); + IM_UNUSED(count); + Width = NextWidth = 0.0f; + Spacing = spacing; + if (clear) + memset(NextWidths, 0, sizeof(NextWidths)); + for (int i = 0; i < IM_ARRAYSIZE(Pos); i++) + { + if (i > 0 && NextWidths[i] > 0.0f) + Width += Spacing; + Pos[i] = IM_FLOOR(Width); + Width += NextWidths[i]; + NextWidths[i] = 0.0f; + } +} + +float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double +{ + NextWidth = 0.0f; + NextWidths[0] = ImMax(NextWidths[0], w0); + NextWidths[1] = ImMax(NextWidths[1], w1); + NextWidths[2] = ImMax(NextWidths[2], w2); + for (int i = 0; i < IM_ARRAYSIZE(Pos); i++) + NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); + return ImMax(Width, NextWidth); +} + +float ImGuiMenuColumns::CalcExtraSpace(float avail_w) const +{ + return ImMax(0.0f, avail_w - Width); +} + +// FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere.. +// Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer. +// Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary. +// Then later the same system could be used for multiple menu-bars, scrollbars, side-bars. +bool ImGui::BeginMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + if (!(window->Flags & ImGuiWindowFlags_MenuBar)) + return false; + + IM_ASSERT(!window->DC.MenuBarAppending); + BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore + PushID("##menubar"); + + // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect. + // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy. + ImRect bar_rect = window->MenuBarRect(); + ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize), IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), IM_ROUND(bar_rect.Max.y)); + clip_rect.ClipWith(window->OuterRectClipped); + PushClipRect(clip_rect.Min, clip_rect.Max, false); + + // We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags). + window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y); + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + window->DC.MenuBarAppending = true; + AlignTextToFramePadding(); + return true; +} + +void ImGui::EndMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. + if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + { + ImGuiWindow* nav_earliest_child = g.NavWindow; + while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) + nav_earliest_child = nav_earliest_child->ParentWindow; + if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None) + { + // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. + // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost) + const ImGuiNavLayer layer = ImGuiNavLayer_Menu; + IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check + FocusWindow(window); + SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); + g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. + g.NavDisableMouseHover = g.NavMousePosDirty = true; + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + NavMoveRequestCancel(); + } + } + + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + PopClipRect(); + PopID(); + window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. + g.GroupStack.back().EmitItem = false; + EndGroup(); // Restore position on layer 0 + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + window->DC.MenuBarAppending = false; +} + +// Important: calling order matters! +// FIXME: Somehow overlapping with docking tech. +// FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts) +bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags) +{ + IM_ASSERT(dir != ImGuiDir_None); + + ImGuiWindow* bar_window = FindWindowByName(name); + if (bar_window == NULL || bar_window->BeginCount == 0) + { + // Calculate and set window size/position + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport()); + ImRect avail_rect = viewport->GetBuildWorkRect(); + ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X; + ImVec2 pos = avail_rect.Min; + if (dir == ImGuiDir_Right || dir == ImGuiDir_Down) + pos[axis] = avail_rect.Max[axis] - axis_size; + ImVec2 size = avail_rect.GetSize(); + size[axis] = axis_size; + SetNextWindowPos(pos); + SetNextWindowSize(size); + + // Report our size into work area (for next frame) using actual window size + if (dir == ImGuiDir_Up || dir == ImGuiDir_Left) + viewport->BuildWorkOffsetMin[axis] += axis_size; + else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right) + viewport->BuildWorkOffsetMax[axis] -= axis_size; + } + + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint + bool is_open = Begin(name, NULL, window_flags); + PopStyleVar(2); + + return is_open; +} + +bool ImGui::BeginMainMenuBar() +{ + ImGuiContext& g = *GImGui; + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); + + // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. + // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea? + // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings. + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; + float height = GetFrameHeight(); + bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags); + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); + + if (is_open) + BeginMenuBar(); + else + End(); + return is_open; +} + +void ImGui::EndMainMenuBar() +{ + EndMenuBar(); + + // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window + // FIXME: With this strategy we won't be able to restore a NULL focus. + ImGuiContext& g = *GImGui; + if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest) + FocusTopMostWindowUnderOne(g.NavWindow, NULL); + + End(); +} + +bool ImGui::BeginMenu(const char* label, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None); + + // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) + ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; + if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) + flags |= ImGuiWindowFlags_ChildWindow; + + // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin(). + // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame. + // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used. + if (g.MenusIdSubmittedThisFrame.contains(id)) + { + if (menu_is_open) + menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + else + g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values + return menu_is_open; + } + + // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu + g.MenusIdSubmittedThisFrame.push_back(id); + + ImVec2 label_size = CalcTextSize(label, NULL, true); + bool pressed; + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back()); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) + g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, + // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). + // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() + popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + float w = label_size.x; + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu inside a menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(min_w, 0.0f)); + ImU32 text_col = GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled); + RenderArrow(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right); + } + + const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); + if (menuset_is_open) + g.NavWindow = backed_nav_window; + + bool want_open = false; + bool want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_toward_other_child_menu = false; + + ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL; + if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar)) + { + // FIXME-DPI: Values should be derived from a master "scale" factor. + ImRect next_window_rect = child_menu_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] + } + if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu) + want_close = true; + + if (!menu_is_open && hovered && pressed) // Click to open + want_open = true; + else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open + want_open = true; + + if (g.NavActivateId == id) + { + want_close = menu_is_open; + want_open = !menu_is_open; + } + if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + else + { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } + else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None)) + ClosePopupToLevel(g.BeginPopupStack.Size, true); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); + + if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) + { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) + OpenPopup(label); + + if (menu_is_open) + { + SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos. + menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + } + else + { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + } + + return menu_is_open; +} + +void ImGui::EndMenu() +{ + // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu). + // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs. + // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical) + { + ClosePopupToLevel(g.BeginPopupStack.Size, true); + NavMoveRequestCancel(); + } + + EndPopup(); +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), + // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. + ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. + float w = label_size.x; + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + pressed = Selectable(label, selected, flags, ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu item inside a vertical menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + float shortcut_w = shortcut ? CalcTextSize(shortcut, NULL).x : 0.0f; + float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + pressed = Selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); + if (shortcut_w > 0.0f) + { + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(window->DC.MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + + IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); + return pressed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) +{ + if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) + { + if (p_selected) + *p_selected = !*p_selected; + return true; + } + return false; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: BeginTabBar, EndTabBar, etc. +//------------------------------------------------------------------------- +// - BeginTabBar() +// - BeginTabBarEx() [Internal] +// - EndTabBar() +// - TabBarLayout() [Internal] +// - TabBarCalcTabID() [Internal] +// - TabBarCalcMaxTabWidth() [Internal] +// - TabBarFindTabById() [Internal] +// - TabBarRemoveTab() [Internal] +// - TabBarCloseTab() [Internal] +// - TabBarScrollClamp() [Internal] +// - TabBarScrollToTab() [Internal] +// - TabBarQueueChangeTabOrder() [Internal] +// - TabBarScrollingButtons() [Internal] +// - TabBarTabListPopupButton() [Internal] +//------------------------------------------------------------------------- + +struct ImGuiTabBarSection +{ + int TabCount; // Number of tabs in this section. + float Width; // Sum of width of tabs in this section (after shrinking down) + float Spacing; // Horizontal spacing at the end of the section. + + ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); } +}; + +namespace ImGui +{ + static void TabBarLayout(ImGuiTabBar* tab_bar); + static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label); + static float TabBarCalcMaxTabWidth(); + static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); + static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections); + static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar); + static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar); +} + +ImGuiTabBar::ImGuiTabBar() +{ + memset(this, 0, sizeof(*this)); + CurrFrameVisible = PrevFrameVisible = -1; + LastTabItemIdx = -1; +} + +static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab) +{ + return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; +} + +static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs) +{ + const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; + const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; + const int a_section = TabItemGetSectionIdx(a); + const int b_section = TabItemGetSectionIdx(b); + if (a_section != b_section) + return a_section - b_section; + return (int)(a->IndexDuringLayout - b->IndexDuringLayout); +} + +static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs) +{ + const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; + const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; + return (int)(a->BeginOrder - b->BeginOrder); +} + +static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref) +{ + ImGuiContext& g = *GImGui; + return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index); +} + +static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + if (g.TabBars.Contains(tab_bar)) + return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar)); + return ImGuiPtrOrIndex(tab_bar); +} + +bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + ImGuiID id = window->GetID(str_id); + ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id); + ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2); + tab_bar->ID = id; + return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused); +} + +bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + if ((flags & ImGuiTabBarFlags_DockNode) == 0) + PushOverrideID(tab_bar->ID); + + // Add to stack + g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar)); + g.CurrentTabBar = tab_bar; + + // Append with multiple BeginTabBar()/EndTabBar() pairs. + tab_bar->BackupCursorPos = window->DC.CursorPos; + if (tab_bar->CurrFrameVisible == g.FrameCount) + { + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY); + tab_bar->BeginCount++; + return true; + } + + // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable + if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable))) + if (tab_bar->Tabs.Size > 1) + ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); + tab_bar->TabsAddedNew = false; + + // Flags + if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) + flags |= ImGuiTabBarFlags_FittingPolicyDefault_; + + tab_bar->Flags = flags; + tab_bar->BarRect = tab_bar_bb; + tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab() + tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible; + tab_bar->CurrFrameVisible = g.FrameCount; + tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight; + tab_bar->CurrTabsContentsHeight = 0.0f; + tab_bar->ItemSpacingY = g.Style.ItemSpacing.y; + tab_bar->FramePadding = g.Style.FramePadding; + tab_bar->TabsActiveCount = 0; + tab_bar->BeginCount = 1; + + // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY); + + // Draw separator + const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive); + const float y = tab_bar->BarRect.Max.y - 1.0f; + { + const float separator_min_x = tab_bar->BarRect.Min.x - IM_FLOOR(window->WindowPadding.x * 0.5f); + const float separator_max_x = tab_bar->BarRect.Max.x + IM_FLOOR(window->WindowPadding.x * 0.5f); + window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f); + } + return true; +} + +void ImGui::EndTabBar() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!"); + return; + } + + // Fallback in case no TabItem have been submitted + if (tab_bar->WantLayout) + TabBarLayout(tab_bar); + + // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed(). + const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); + if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing) + { + tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight); + window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight; + } + else + { + window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight; + } + if (tab_bar->BeginCount > 1) + window->DC.CursorPos = tab_bar->BackupCursorPos; + + if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) + PopID(); + + g.CurrentTabBarStack.pop_back(); + g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back()); +} + +// This is called only once a frame before by the first call to ItemTab() +// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions. +static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + tab_bar->WantLayout = false; + + // Garbage collect by compacting list + // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section) + int tab_dst_n = 0; + bool need_sort_by_section = false; + ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing + for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n]; + if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose) + { + // Remove tab + if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; } + if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; } + if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; } + continue; + } + if (tab_dst_n != tab_src_n) + tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n]; + + tab = &tab_bar->Tabs[tab_dst_n]; + tab->IndexDuringLayout = (ImS16)tab_dst_n; + + // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another) + int curr_tab_section_n = TabItemGetSectionIdx(tab); + if (tab_dst_n > 0) + { + ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1]; + int prev_tab_section_n = TabItemGetSectionIdx(prev_tab); + if (curr_tab_section_n == 0 && prev_tab_section_n != 0) + need_sort_by_section = true; + if (prev_tab_section_n == 2 && curr_tab_section_n != 2) + need_sort_by_section = true; + } + + sections[curr_tab_section_n].TabCount++; + tab_dst_n++; + } + if (tab_bar->Tabs.Size != tab_dst_n) + tab_bar->Tabs.resize(tab_dst_n); + + if (need_sort_by_section) + ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection); + + // Calculate spacing between sections + sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + + // Setup next selected tab + ImGuiID scroll_to_tab_id = 0; + if (tab_bar->NextSelectedTabId) + { + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId; + tab_bar->NextSelectedTabId = 0; + scroll_to_tab_id = tab_bar->SelectedTabId; + } + + // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot). + if (tab_bar->ReorderRequestTabId != 0) + { + if (TabBarProcessReorder(tab_bar)) + if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId) + scroll_to_tab_id = tab_bar->ReorderRequestTabId; + tab_bar->ReorderRequestTabId = 0; + } + + // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!) + const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0; + if (tab_list_popup_button) + if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! + scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; + + // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central + // (whereas our tabs are stored as: leading, central, trailing) + int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount }; + g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); + + // Compute ideal tabs widths + store them into shrink buffer + ImGuiTabItem* most_recently_selected_tab = NULL; + int curr_section_n = -1; + bool found_selected_tab_id = false; + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible); + + if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button)) + most_recently_selected_tab = tab; + if (tab->ID == tab_bar->SelectedTabId) + found_selected_tab_id = true; + if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID) + scroll_to_tab_id = tab->ID; + + // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar. + // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet, + // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window. + const char* tab_name = tab_bar->GetTabName(tab); + const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true; + tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x; + + int section_n = TabItemGetSectionIdx(tab); + ImGuiTabBarSection* section = §ions[section_n]; + section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); + curr_section_n = section_n; + + // Store data so we can build an array sorted by width if we need to shrink tabs down + IM_MSVC_WARNING_SUPPRESS(6385); + int shrink_buffer_index = shrink_buffer_indexes[section_n]++; + g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n; + g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth; + + IM_ASSERT(tab->ContentWidth > 0.0f); + tab->Width = tab->ContentWidth; + } + + // Compute total ideal width (used for e.g. auto-resizing a window) + tab_bar->WidthAllTabsIdeal = 0.0f; + for (int section_n = 0; section_n < 3; section_n++) + tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; + + // Horizontal scrolling buttons + // (note that TabBarScrollButtons() will alter BarRect.Max.x) + if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) + if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar)) + { + scroll_to_tab_id = scroll_and_select_tab->ID; + if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0) + tab_bar->SelectedTabId = scroll_to_tab_id; + } + + // Shrink widths if full tabs don't fit in their allocated space + float section_0_w = sections[0].Width + sections[0].Spacing; + float section_1_w = sections[1].Width + sections[1].Spacing; + float section_2_w = sections[2].Width + sections[2].Spacing; + bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth(); + float width_excess; + if (central_section_is_visible) + width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section + else + width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section + + // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore + if (width_excess > 0.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible)) + { + int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); + int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0); + ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess); + + // Apply shrunk values into tabs and sections + for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; + float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); + if (shrinked_width < 0.0f) + continue; + + int section_n = TabItemGetSectionIdx(tab); + sections[section_n].Width -= (tab->Width - shrinked_width); + tab->Width = shrinked_width; + } + } + + // Layout all active tabs + int section_tab_index = 0; + float tab_offset = 0.0f; + tab_bar->WidthAllTabs = 0.0f; + for (int section_n = 0; section_n < 3; section_n++) + { + ImGuiTabBarSection* section = §ions[section_n]; + if (section_n == 2) + tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset); + + for (int tab_n = 0; tab_n < section->TabCount; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n]; + tab->Offset = tab_offset; + tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); + } + tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); + tab_offset += section->Spacing; + section_tab_index += section->TabCount; + } + + // If we have lost the selected tab, select the next most recently active one + if (found_selected_tab_id == false) + tab_bar->SelectedTabId = 0; + if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL) + scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID; + + // Lock in visible tab + tab_bar->VisibleTabId = tab_bar->SelectedTabId; + tab_bar->VisibleTabWasSubmitted = false; + + // Update scrolling + if (scroll_to_tab_id != 0) + TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections); + tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim); + tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget); + if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget) + { + // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds. + // Teleport if we are aiming far off the visible line + tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize); + tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f); + const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize); + tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed); + } + else + { + tab_bar->ScrollingSpeed = 0.0f; + } + tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing; + tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing; + + // Clear name buffers + if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) + tab_bar->TabsNames.Buf.resize(0); + + // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame) + ImGuiWindow* window = g.CurrentWindow; + window->DC.CursorPos = tab_bar->BarRect.Min; + ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y); + window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal); +} + +// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack. +static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label) +{ + if (tab_bar->Flags & ImGuiTabBarFlags_DockNode) + { + ImGuiID id = ImHashStr(label); + KeepAliveID(id); + return id; + } + else + { + ImGuiWindow* window = GImGui->CurrentWindow; + return window->GetID(label); + } +} + +static float ImGui::TabBarCalcMaxTabWidth() +{ + ImGuiContext& g = *GImGui; + return g.FontSize * 20.0f; +} + +ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id) +{ + if (tab_id != 0) + for (int n = 0; n < tab_bar->Tabs.Size; n++) + if (tab_bar->Tabs[n].ID == tab_id) + return &tab_bar->Tabs[n]; + return NULL; +} + +// The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless. +void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) +{ + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) + tab_bar->Tabs.erase(tab); + if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; } + if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; } + if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; } +} + +// Called on manual closure attempt +void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + IM_ASSERT(!(tab->Flags & ImGuiTabItemFlags_Button)); + if (!(tab->Flags & ImGuiTabItemFlags_UnsavedDocument)) + { + // This will remove a frame of lag for selecting another tab on closure. + // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure + tab->WantClose = true; + if (tab_bar->VisibleTabId == tab->ID) + { + tab->LastFrameVisible = -1; + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0; + } + } + else + { + // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup) + if (tab_bar->VisibleTabId != tab->ID) + tab_bar->NextSelectedTabId = tab->ID; + } +} + +static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling) +{ + scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth()); + return ImMax(scrolling, 0.0f); +} + +// Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys +static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections) +{ + ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id); + if (tab == NULL) + return; + if (tab->Flags & ImGuiTabItemFlags_SectionMask_) + return; + + ImGuiContext& g = *GImGui; + float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) + int order = tab_bar->GetTabOrder(tab); + + // Scrolling happens only in the central section (leading/trailing sections are not scrolling) + // FIXME: This is all confusing. + float scrollable_width = tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing; + + // We make all tabs positions all relative Sections[0].Width to make code simpler + float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f); + float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f); + tab_bar->ScrollingTargetDistToVisibility = 0.0f; + if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width)) + { + // Scroll to the left + tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f); + tab_bar->ScrollingTarget = tab_x1; + } + else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width) + { + // Scroll to the right + tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f); + tab_bar->ScrollingTarget = tab_x2 - scrollable_width; + } +} + +void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset) +{ + IM_ASSERT(offset != 0); + IM_ASSERT(tab_bar->ReorderRequestTabId == 0); + tab_bar->ReorderRequestTabId = tab->ID; + tab_bar->ReorderRequestOffset = (ImS16)offset; +} + +void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* src_tab, ImVec2 mouse_pos) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(tab_bar->ReorderRequestTabId == 0); + if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0) + return; + + const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; + const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0); + + // Count number of contiguous tabs we are crossing over + const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1; + const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab); + int dst_idx = src_idx; + for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir) + { + // Reordered tabs must share the same section + const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i]; + if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder) + break; + if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_)) + break; + dst_idx = i; + + // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered. + const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x; + const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x; + //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255)); + if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2)) + break; + } + + if (dst_idx != src_idx) + TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx); +} + +bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) +{ + ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId); + if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder)) + return false; + + //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools + int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestOffset; + if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size) + return false; + + // Reordered tabs must share the same section + // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too) + ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order]; + if (tab2->Flags & ImGuiTabItemFlags_NoReorder) + return false; + if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_)) + return false; + + ImGuiTabItem item_tmp = *tab1; + ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2; + ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1; + const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset; + memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem)); + *tab2 = item_tmp; + + if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings) + MarkIniSettingsDirty(); + return true; +} + +static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f); + const float scrolling_buttons_width = arrow_button_size.x * 2.0f; + + const ImVec2 backup_cursor_pos = window->DC.CursorPos; + //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255)); + + int select_dir = 0; + ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; + arrow_col.w *= 0.5f; + + PushStyleColor(ImGuiCol_Text, arrow_col); + PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + const float backup_repeat_delay = g.IO.KeyRepeatDelay; + const float backup_repeat_rate = g.IO.KeyRepeatRate; + g.IO.KeyRepeatDelay = 0.250f; + g.IO.KeyRepeatRate = 0.200f; + float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width); + window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y); + if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + select_dir = -1; + window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y); + if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + select_dir = +1; + PopStyleColor(2); + g.IO.KeyRepeatRate = backup_repeat_rate; + g.IO.KeyRepeatDelay = backup_repeat_delay; + + ImGuiTabItem* tab_to_scroll_to = NULL; + if (select_dir != 0) + if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId)) + { + int selected_order = tab_bar->GetTabOrder(tab_item); + int target_order = selected_order + select_dir; + + // Skip tab item buttons until another tab item is found or end is reached + while (tab_to_scroll_to == NULL) + { + // If we are at the end of the list, still scroll to make our tab visible + tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; + + // Cross through buttons + // (even if first/last item is a button, return it so we can update the scroll) + if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button) + { + target_order += select_dir; + selected_order += select_dir; + tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL; + } + } + } + window->DC.CursorPos = backup_cursor_pos; + tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f; + + return tab_to_scroll_to; +} + +static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // We use g.Style.FramePadding.y to match the square ArrowButton size + const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y; + const ImVec2 backup_cursor_pos = window->DC.CursorPos; + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y); + tab_bar->BarRect.Min.x += tab_list_popup_button_width; + + ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; + arrow_col.w *= 0.5f; + PushStyleColor(ImGuiCol_Text, arrow_col); + PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest); + PopStyleColor(2); + + ImGuiTabItem* tab_to_select = NULL; + if (open) + { + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + if (tab->Flags & ImGuiTabItemFlags_Button) + continue; + + const char* tab_name = tab_bar->GetTabName(tab); + if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID)) + tab_to_select = tab; + } + EndCombo(); + } + + window->DC.CursorPos = backup_cursor_pos; + return tab_to_select; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: BeginTabItem, EndTabItem, etc. +//------------------------------------------------------------------------- +// - BeginTabItem() +// - EndTabItem() +// - TabItemButton() +// - TabItemEx() [Internal] +// - SetTabItemClosed() +// - TabItemCalcSize() [Internal] +// - TabItemBackground() [Internal] +// - TabItemLabelAndCloseButton() [Internal] +//------------------------------------------------------------------------- + +bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return false; + } + IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! + + bool ret = TabItemEx(tab_bar, label, p_open, flags); + if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; + PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label) + } + return ret; +} + +void ImGui::EndTabItem() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return; + } + IM_ASSERT(tab_bar->LastTabItemIdx >= 0); + ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; + if (!(tab->Flags & ImGuiTabItemFlags_NoPushId)) + PopID(); +} + +bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return false; + } + return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder); +} + +bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags) +{ + // Layout whole tab bar if not already done + if (tab_bar->WantLayout) + TabBarLayout(tab_bar); + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = TabBarCalcTabID(tab_bar, label); + + // If the user called us with *p_open == false, we early out and don't render. + // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID. + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + if (p_open && !*p_open) + { + PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true); + ItemAdd(ImRect(), id); + PopItemFlag(); + return false; + } + + IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button)); + IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing + + // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented) + if (flags & ImGuiTabItemFlags_NoCloseButton) + p_open = NULL; + else if (p_open == NULL) + flags |= ImGuiTabItemFlags_NoCloseButton; + + // Calculate tab contents size + ImVec2 size = TabItemCalcSize(label, p_open != NULL); + + // Acquire tab data + ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id); + bool tab_is_new = false; + if (tab == NULL) + { + tab_bar->Tabs.push_back(ImGuiTabItem()); + tab = &tab_bar->Tabs.back(); + tab->ID = id; + tab->Width = size.x; + tab_bar->TabsAddedNew = true; + tab_is_new = true; + } + tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab); + tab->ContentWidth = size.x; + tab->BeginOrder = tab_bar->TabsActiveCount++; + + const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); + const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0; + const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount); + const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0; + tab->LastFrameVisible = g.FrameCount; + tab->Flags = flags; + + // Append name with zero-terminator + tab->NameOffset = (ImS16)tab_bar->TabsNames.size(); + tab_bar->TabsNames.append(label, label + strlen(label) + 1); + + // Update selected tab + if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0) + if (!tab_bar_appearing || tab_bar->SelectedTabId == 0) + if (!is_tab_button) + tab_bar->NextSelectedTabId = id; // New tabs gets activated + if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // SetSelected can only be passed on explicit tab bar + if (!is_tab_button) + tab_bar->NextSelectedTabId = id; + + // Lock visibility + // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!) + bool tab_contents_visible = (tab_bar->VisibleTabId == id); + if (tab_contents_visible) + tab_bar->VisibleTabWasSubmitted = true; + + // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches + if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing) + if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs)) + tab_contents_visible = true; + + // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted + // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'. + if (tab_appearing && (!tab_bar_appearing || tab_is_new)) + { + PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true); + ItemAdd(ImRect(), id); + PopItemFlag(); + if (is_tab_button) + return false; + return tab_contents_visible; + } + + if (tab_bar->SelectedTabId == id) + tab->LastFrameSelected = g.FrameCount; + + // Backup current layout position + const ImVec2 backup_main_cursor_pos = window->DC.CursorPos; + + // Layout + const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; + size.x = tab->Width; + if (is_central_section) + window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f); + else + window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); + ImVec2 pos = window->DC.CursorPos; + ImRect bb(pos, pos + size); + + // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) + const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX); + if (want_clip_rect) + PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); + + ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; + ItemSize(bb.GetSize(), style.FramePadding.y); + window->DC.CursorMaxPos = backup_cursor_max_pos; + + if (!ItemAdd(bb, id)) + { + if (want_clip_rect) + PopClipRect(); + window->DC.CursorPos = backup_main_cursor_pos; + return tab_contents_visible; + } + + // Click to Select a tab + ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowItemOverlap); + if (g.DragDropActive) + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + if (pressed && !is_tab_button) + tab_bar->NextSelectedTabId = id; + hovered |= (g.HoveredId == id); + + // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered) + if (g.ActiveId != id) + SetItemAllowOverlap(); + + // Drag and drop: re-order tabs + if (held && !tab_appearing && IsMouseDragging(0)) + { + if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)) + { + // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x + if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x) + { + TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); + } + else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x) + { + TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); + } + } + } + +#if 0 + if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth) + { + // Enlarge tab display when hovering + bb.Max.x = bb.Min.x + IM_FLOOR(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f))); + display_draw_list = GetForegroundDrawList(window); + TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive)); + } +#endif + + // Render tab shape + ImDrawList* display_draw_list = window->DrawList; + const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused)); + TabItemBackground(display_draw_list, bb, flags, tab_col); + RenderNavHighlight(bb, id); + + // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. + const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1))) + if (!is_tab_button) + tab_bar->NextSelectedTabId = id; + + if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) + flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + + // Render tab label, process close button + const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0; + bool just_closed; + bool text_clipped; + TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); + if (just_closed && p_open != NULL) + { + *p_open = false; + TabBarCloseTab(tab_bar, tab); + } + + // Restore main window position so user can draw there + if (want_clip_rect) + PopClipRect(); + window->DC.CursorPos = backup_main_cursor_pos; + + // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer) + // We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar (which g.HoveredId ignores) + if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered()) + if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) + SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); + + IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected + if (is_tab_button) + return pressed; + return tab_contents_visible; +} + +// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed. +// To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar(). +// Tabs closed by the close button will automatically be flagged to avoid this issue. +void ImGui::SetTabItemClosed(const char* label) +{ + ImGuiContext& g = *GImGui; + bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode); + if (is_within_manual_tab_bar) + { + ImGuiTabBar* tab_bar = g.CurrentTabBar; + ImGuiID tab_id = TabBarCalcTabID(tab_bar, label); + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) + tab->WantClose = true; // Will be processed by next call to TabBarLayout() + } +} + +ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button) +{ + ImGuiContext& g = *GImGui; + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f); + if (has_close_button) + size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle. + else + size.x += g.Style.FramePadding.x + 1.0f; + return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); +} + +void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col) +{ + // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it. + ImGuiContext& g = *GImGui; + const float width = bb.GetWidth(); + IM_UNUSED(flags); + IM_ASSERT(width > 0.0f); + const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f)); + const float y1 = bb.Min.y + 1.0f; + const float y2 = bb.Max.y - 1.0f; + draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); + draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9); + draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12); + draw_list->PathLineTo(ImVec2(bb.Max.x, y2)); + draw_list->PathFillConvex(col); + if (g.Style.TabBorderSize > 0.0f) + { + draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2)); + draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); + draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); + draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); + draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize); + } +} + +// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic +// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter. +void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped) +{ + ImGuiContext& g = *GImGui; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + if (out_just_closed) + *out_just_closed = false; + if (out_text_clipped) + *out_text_clipped = false; + + if (bb.GetWidth() <= 1.0f) + return; + + // In Style V2 we'll have full override of all colors per state (e.g. focused, selected) + // But right now if you want to alter text color of tabs this is what you need to do. +#if 0 + const float backup_alpha = g.Style.Alpha; + if (!is_contents_visible) + g.Style.Alpha *= 0.7f; +#endif + + // Render text label (with clipping + alpha gradient) + unsaved marker + const char* TAB_UNSAVED_MARKER = "*"; + ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); + if (flags & ImGuiTabItemFlags_UnsavedDocument) + { + text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x; + ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + frame_padding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + frame_padding.y + IM_FLOOR(-g.FontSize * 0.25f)); + RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL); + } + ImRect text_ellipsis_clip_bb = text_pixel_clip_bb; + + // Return clipped state ignoring the close button + if (out_text_clipped) + { + *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x; + //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255)); + } + + // Close Button + // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap() + // 'hovered' will be true when hovering the Tab but NOT when hovering the close button + // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button + // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false + bool close_button_pressed = false; + bool close_button_visible = false; + if (close_button_id != 0) + if (is_contents_visible || bb.GetWidth() >= g.Style.TabMinWidthForCloseButton) + if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id) + close_button_visible = true; + if (close_button_visible) + { + ImGuiLastItemDataBackup last_item_backup; + const float close_button_sz = g.FontSize; + PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding); + if (CloseButton(close_button_id, ImVec2(bb.Max.x - frame_padding.x * 2.0f - close_button_sz, bb.Min.y))) + close_button_pressed = true; + PopStyleVar(); + last_item_backup.Restore(); + + // Close with middle mouse button + if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) + close_button_pressed = true; + + text_pixel_clip_bb.Max.x -= close_button_sz; + } + + // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist.. + float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f; + RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); + +#if 0 + if (!is_contents_visible) + g.Style.Alpha = backup_alpha; +#endif + + if (out_just_closed) + *out_just_closed = close_button_pressed; +} + + +#endif // #ifndef IMGUI_DISABLE diff --git a/vendor/librw/skeleton/imgui/imstb_rectpack.h b/vendor/librw/skeleton/imgui/imstb_rectpack.h new file mode 100644 index 0000000..ff2a85d --- /dev/null +++ b/vendor/librw/skeleton/imgui/imstb_rectpack.h @@ -0,0 +1,639 @@ +// [DEAR IMGUI] +// This is a slightly modified version of stb_rect_pack.h 1.00. +// Those changes would need to be pushed into nothings/stb: +// - Added STBRP__CDECL +// Grep for [DEAR IMGUI] to find the changes. + +// stb_rect_pack.h - v1.00 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +// [DEAR IMGUI] Added STBRP__CDECL +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +// [DEAR IMGUI] Added STBRP__CDECL +static int STBRP__CDECL rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +// [DEAR IMGUI] Added STBRP__CDECL +static int STBRP__CDECL rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/vendor/librw/skeleton/imgui/imstb_textedit.h b/vendor/librw/skeleton/imgui/imstb_textedit.h new file mode 100644 index 0000000..7644670 --- /dev/null +++ b/vendor/librw/skeleton/imgui/imstb_textedit.h @@ -0,0 +1,1447 @@ +// [DEAR IMGUI] +// This is a slightly modified version of stb_textedit.h 1.13. +// Those changes would need to be pushed into nothings/stb: +// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) +// Grep for [DEAR IMGUI] to find the changes. + +// stb_textedit.h - v1.13 - public domain - Sean Barrett +// Development of this library was sponsored by RAD Game Tools +// +// This C header file implements the guts of a multi-line text-editing +// widget; you implement display, word-wrapping, and low-level string +// insertion/deletion, and stb_textedit will map user inputs into +// insertions & deletions, plus updates to the cursor position, +// selection state, and undo state. +// +// It is intended for use in games and other systems that need to build +// their own custom widgets and which do not have heavy text-editing +// requirements (this library is not recommended for use for editing large +// texts, as its performance does not scale and it has limited undo). +// +// Non-trivial behaviors are modelled after Windows text controls. +// +// +// LICENSE +// +// See end of file for license information. +// +// +// DEPENDENCIES +// +// Uses the C runtime function 'memmove', which you can override +// by defining STB_TEXTEDIT_memmove before the implementation. +// Uses no other functions. Performs no runtime allocations. +// +// +// VERSION HISTORY +// +// 1.13 (2019-02-07) fix bug in undo size management +// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash +// 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield +// 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual +// 1.9 (2016-08-27) customizable move-by-word +// 1.8 (2016-04-02) better keyboard handling when mouse button is down +// 1.7 (2015-09-13) change y range handling in case baseline is non-0 +// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove +// 1.5 (2014-09-10) add support for secondary keys for OS X +// 1.4 (2014-08-17) fix signed/unsigned warnings +// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary +// 1.2 (2014-05-27) fix some RAD types that had crept into the new code +// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) +// 1.0 (2012-07-26) improve documentation, initial public release +// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode +// 0.2 (2011-11-28) fixes to undo/redo +// 0.1 (2010-07-08) initial version +// +// ADDITIONAL CONTRIBUTORS +// +// Ulf Winklemann: move-by-word in 1.1 +// Fabian Giesen: secondary key inputs in 1.5 +// Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 +// +// Bugfixes: +// Scott Graham +// Daniel Keller +// Omar Cornut +// Dan Thompson +// +// USAGE +// +// This file behaves differently depending on what symbols you define +// before including it. +// +// +// Header-file mode: +// +// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, +// it will operate in "header file" mode. In this mode, it declares a +// single public symbol, STB_TexteditState, which encapsulates the current +// state of a text widget (except for the string, which you will store +// separately). +// +// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a +// primitive type that defines a single character (e.g. char, wchar_t, etc). +// +// To save space or increase undo-ability, you can optionally define the +// following things that are used by the undo system: +// +// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// If you don't define these, they are set to permissive types and +// moderate sizes. The undo system does no memory allocations, so +// it grows STB_TexteditState by the worst-case storage which is (in bytes): +// +// [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT +// +// +// Implementation mode: +// +// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it +// will compile the implementation of the text edit widget, depending +// on a large number of symbols which must be defined before the include. +// +// The implementation is defined only as static functions. You will then +// need to provide your own APIs in the same file which will access the +// static functions. +// +// The basic concept is that you provide a "string" object which +// behaves like an array of characters. stb_textedit uses indices to +// refer to positions in the string, implicitly representing positions +// in the displayed textedit. This is true for both plain text and +// rich text; even with rich text stb_truetype interacts with your +// code as if there was an array of all the displayed characters. +// +// Symbols that must be the same in header-file and implementation mode: +// +// STB_TEXTEDIT_CHARTYPE the character type +// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// Symbols you must define for implementation mode: +// +// STB_TEXTEDIT_STRING the type of object representing a string being edited, +// typically this is a wrapper object with other data you need +// +// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) +// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters +// starting from character #n (see discussion below) +// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character +// to the xpos of the i+1'th char for a line of characters +// starting at character #n (i.e. accounts for kerning +// with previous char) +// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character +// (return type is int, -1 means not valid to insert) +// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based +// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize +// as manually wordwrapping for end-of-line positioning +// +// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i +// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) +// +// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key +// +// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left +// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right +// STB_TEXTEDIT_K_UP keyboard input to move cursor up +// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down +// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page +// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page +// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME +// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END +// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME +// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END +// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor +// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor +// STB_TEXTEDIT_K_UNDO keyboard input to perform undo +// STB_TEXTEDIT_K_REDO keyboard input to perform redo +// +// Optional: +// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode +// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), +// required for default WORDLEFT/WORDRIGHT handlers +// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to +// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to +// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT +// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT +// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line +// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line +// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text +// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text +// +// Keyboard input must be encoded as a single integer value; e.g. a character code +// and some bitflags that represent shift states. to simplify the interface, SHIFT must +// be a bitflag, so we can test the shifted state of cursor movements to allow selection, +// i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// +// You can encode other things, such as CONTROL or ALT, in additional bits, and +// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, +// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN +// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, +// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the +// API below. The control keys will only match WM_KEYDOWN events because of the +// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN +// bit so it only decodes WM_CHAR events. +// +// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed +// row of characters assuming they start on the i'th character--the width and +// the height and the number of characters consumed. This allows this library +// to traverse the entire layout incrementally. You need to compute word-wrapping +// here. +// +// Each textfield keeps its own insert mode state, which is not how normal +// applications work. To keep an app-wide insert mode, update/copy the +// "insert_mode" field of STB_TexteditState before/after calling API functions. +// +// API +// +// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +// +// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) +// +// Each of these functions potentially updates the string and updates the +// state. +// +// initialize_state: +// set the textedit state to a known good default state when initially +// constructing the textedit. +// +// click: +// call this with the mouse x,y on a mouse down; it will update the cursor +// and reset the selection start/end to the cursor point. the x,y must +// be relative to the text widget, with (0,0) being the top left. +// +// drag: +// call this with the mouse x,y on a mouse drag/up; it will update the +// cursor and the selection end point +// +// cut: +// call this to delete the current selection; returns true if there was +// one. you should FIRST copy the current selection to the system paste buffer. +// (To copy, just copy the current selection out of the string yourself.) +// +// paste: +// call this to paste text at the current cursor point or over the current +// selection if there is one. +// +// key: +// call this for keyboard inputs sent to the textfield. you can use it +// for "key down" events or for "translated" key events. if you need to +// do both (as in Win32), or distinguish Unicode characters from control +// inputs, set a high bit to distinguish the two; then you can define the +// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit +// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is +// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to +// anything other type you wante before including. +// +// +// When rendering, you can read the cursor position and selection state from +// the STB_TexteditState. +// +// +// Notes: +// +// This is designed to be usable in IMGUI, so it allows for the possibility of +// running in an IMGUI that has NOT cached the multi-line layout. For this +// reason, it provides an interface that is compatible with computing the +// layout incrementally--we try to make sure we make as few passes through +// as possible. (For example, to locate the mouse pointer in the text, we +// could define functions that return the X and Y positions of characters +// and binary search Y and then X, but if we're doing dynamic layout this +// will run the layout algorithm many times, so instead we manually search +// forward in one pass. Similar logic applies to e.g. up-arrow and +// down-arrow movement.) +// +// If it's run in a widget that *has* cached the layout, then this is less +// efficient, but it's not horrible on modern computers. But you wouldn't +// want to edit million-line files with it. + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Header-file mode +//// +//// + +#ifndef INCLUDE_STB_TEXTEDIT_H +#define INCLUDE_STB_TEXTEDIT_H + +//////////////////////////////////////////////////////////////////////// +// +// STB_TexteditState +// +// Definition of STB_TexteditState which you should store +// per-textfield; it includes cursor position, selection state, +// and undo state. +// + +#ifndef STB_TEXTEDIT_UNDOSTATECOUNT +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#endif +#ifndef STB_TEXTEDIT_UNDOCHARCOUNT +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#endif +#ifndef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_CHARTYPE int +#endif +#ifndef STB_TEXTEDIT_POSITIONTYPE +#define STB_TEXTEDIT_POSITIONTYPE int +#endif + +typedef struct +{ + // private data + STB_TEXTEDIT_POSITIONTYPE where; + STB_TEXTEDIT_POSITIONTYPE insert_length; + STB_TEXTEDIT_POSITIONTYPE delete_length; + int char_storage; +} StbUndoRecord; + +typedef struct +{ + // private data + StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; + STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; + short undo_point, redo_point; + int undo_char_point, redo_char_point; +} StbUndoState; + +typedef struct +{ + ///////////////////// + // + // public data + // + + int cursor; + // position of the text cursor within the string + + int select_start; // selection start point + int select_end; + // selection start and end point in characters; if equal, no selection. + // note that start may be less than or greater than end (e.g. when + // dragging the mouse, start is where the initial click was, and you + // can drag in either direction) + + unsigned char insert_mode; + // each textfield keeps its own insert mode state. to keep an app-wide + // insert mode, copy this value in/out of the app state + + int row_count_per_page; + // page size in number of row. + // this value MUST be set to >0 for pageup or pagedown in multilines documents. + + ///////////////////// + // + // private data + // + unsigned char cursor_at_end_of_line; // not implemented yet + unsigned char initialized; + unsigned char has_preferred_x; + unsigned char single_line; + unsigned char padding1, padding2, padding3; + float preferred_x; // this determines where the cursor up/down tries to seek to along x + StbUndoState undostate; +} STB_TexteditState; + + +//////////////////////////////////////////////////////////////////////// +// +// StbTexteditRow +// +// Result of layout query, used by stb_textedit to determine where +// the text in each row is. + +// result of layout query +typedef struct +{ + float x0,x1; // starting x location, end x location (allows for align=right, etc) + float baseline_y_delta; // position of baseline relative to previous row's baseline + float ymin,ymax; // height of row above and below baseline + int num_chars; +} StbTexteditRow; +#endif //INCLUDE_STB_TEXTEDIT_H + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Implementation mode +//// +//// + + +// implementation isn't include-guarded, since it might have indirectly +// included just the "header" portion +#ifdef STB_TEXTEDIT_IMPLEMENTATION + +#ifndef STB_TEXTEDIT_memmove +#include +#define STB_TEXTEDIT_memmove memmove +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// Mouse input handling +// + +// traverse the layout to locate the nearest character to a display position +static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) +{ + StbTexteditRow r; + int n = STB_TEXTEDIT_STRINGLEN(str); + float base_y = 0, prev_x; + int i=0, k; + + r.x0 = r.x1 = 0; + r.ymin = r.ymax = 0; + r.num_chars = 0; + + // search rows to find one that straddles 'y' + while (i < n) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (r.num_chars <= 0) + return n; + + if (i==0 && y < base_y + r.ymin) + return 0; + + if (y < base_y + r.ymax) + break; + + i += r.num_chars; + base_y += r.baseline_y_delta; + } + + // below all text, return 'after' last character + if (i >= n) + return n; + + // check if it's before the beginning of the line + if (x < r.x0) + return i; + + // check if it's before the end of the line + if (x < r.x1) { + // search characters in row for one that straddles 'x' + prev_x = r.x0; + for (k=0; k < r.num_chars; ++k) { + float w = STB_TEXTEDIT_GETWIDTH(str, i, k); + if (x < prev_x+w) { + if (x < prev_x+w/2) + return k+i; + else + return k+i+1; + } + prev_x += w; + } + // shouldn't happen, but if it does, fall through to end-of-line case + } + + // if the last character is a newline, return that. otherwise return 'after' the last character + if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) + return i+r.num_chars-1; + else + return i+r.num_chars; +} + +// API click: on mouse down, move the cursor to the clicked location, and reset the selection +static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse + // goes off the top or bottom of the text + if( state->single_line ) + { + StbTexteditRow r; + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + y = r.ymin; + } + + state->cursor = stb_text_locate_coord(str, x, y); + state->select_start = state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; +} + +// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location +static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + int p = 0; + + // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse + // goes off the top or bottom of the text + if( state->single_line ) + { + StbTexteditRow r; + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + y = r.ymin; + } + + if (state->select_start == state->select_end) + state->select_start = state->cursor; + + p = stb_text_locate_coord(str, x, y); + state->cursor = state->select_end = p; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Keyboard input handling +// + +// forward declarations +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); + +typedef struct +{ + float x,y; // position of n'th character + float height; // height of line + int first_char, length; // first char of row, and length + int prev_first; // first char of previous row +} StbFindState; + +// find the x/y location of a character, and remember info about the previous row in +// case we get a move-up event (for page up, we'll have to rescan) +static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) +{ + StbTexteditRow r; + int prev_start = 0; + int z = STB_TEXTEDIT_STRINGLEN(str); + int i=0, first; + + if (n == z) { + // if it's at the end, then find the last line -- simpler than trying to + // explicitly handle this case in the regular code + if (single_line) { + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + find->y = 0; + find->first_char = 0; + find->length = z; + find->height = r.ymax - r.ymin; + find->x = r.x1; + } else { + find->y = 0; + find->x = 0; + find->height = 1; + while (i < z) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + prev_start = i; + i += r.num_chars; + } + find->first_char = i; + find->length = 0; + find->prev_first = prev_start; + } + return; + } + + // search rows to find the one that straddles character n + find->y = 0; + + for(;;) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (n < i + r.num_chars) + break; + prev_start = i; + i += r.num_chars; + find->y += r.baseline_y_delta; + } + + find->first_char = first = i; + find->length = r.num_chars; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + + // now scan to find xpos + find->x = r.x0; + for (i=0; first+i < n; ++i) + find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); +} + +#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + +// make the selection/cursor state valid if client altered the string +static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + int n = STB_TEXTEDIT_STRINGLEN(str); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start > n) state->select_start = n; + if (state->select_end > n) state->select_end = n; + // if clamping forced them to be equal, move the cursor to match + if (state->select_start == state->select_end) + state->cursor = state->select_start; + } + if (state->cursor > n) state->cursor = n; +} + +// delete characters while updating undo +static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) +{ + stb_text_makeundo_delete(str, state, where, len); + STB_TEXTEDIT_DELETECHARS(str, where, len); + state->has_preferred_x = 0; +} + +// delete the section +static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + stb_textedit_clamp(str, state); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start < state->select_end) { + stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); + state->select_end = state->cursor = state->select_start; + } else { + stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); + state->select_start = state->cursor = state->select_end; + } + state->has_preferred_x = 0; + } +} + +// canoncialize the selection so start <= end +static void stb_textedit_sortselection(STB_TexteditState *state) +{ + if (state->select_end < state->select_start) { + int temp = state->select_end; + state->select_end = state->select_start; + state->select_start = temp; + } +} + +// move cursor to first character of selection +static void stb_textedit_move_to_first(STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + state->cursor = state->select_start; + state->select_end = state->select_start; + state->has_preferred_x = 0; + } +} + +// move cursor to last character of selection +static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->select_start = state->select_end; + state->has_preferred_x = 0; + } +} + +#ifdef STB_TEXTEDIT_IS_SPACE +static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) +{ + return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; +} + +#ifndef STB_TEXTEDIT_MOVEWORDLEFT +static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) +{ + --c; // always move at least one character + while( c >= 0 && !is_word_boundary( str, c ) ) + --c; + + if( c < 0 ) + c = 0; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous +#endif + +#ifndef STB_TEXTEDIT_MOVEWORDRIGHT +static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) +{ + const int len = STB_TEXTEDIT_STRINGLEN(str); + ++c; // always move at least one character + while( c < len && !is_word_boundary( str, c ) ) + ++c; + + if( c > len ) + c = len; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next +#endif + +#endif + +// update selection and cursor to match each other +static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) +{ + if (!STB_TEXT_HAS_SELECTION(state)) + state->select_start = state->select_end = state->cursor; + else + state->cursor = state->select_end; +} + +// API cut: delete selection +static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_delete_selection(str,state); // implicitly clamps + state->has_preferred_x = 0; + return 1; + } + return 0; +} + +// API paste: replace existing selection with passed-in text +static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +{ + // if there's a selection, the paste should delete it + stb_textedit_clamp(str, state); + stb_textedit_delete_selection(str,state); + // try to insert the characters + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { + stb_text_makeundo_insert(state, state->cursor, len); + state->cursor += len; + state->has_preferred_x = 0; + return 1; + } + // remove the undo since we didn't actually insert the characters + if (state->undostate.undo_point) + --state->undostate.undo_point; + return 0; +} + +#ifndef STB_TEXTEDIT_KEYTYPE +#define STB_TEXTEDIT_KEYTYPE int +#endif + +// API key: process a keyboard input +static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) +{ +retry: + switch (key) { + default: { + int c = STB_TEXTEDIT_KEYTOTEXT(key); + if (c > 0) { + STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; + + // can't add newline in single-line mode + if (c == '\n' && state->single_line) + break; + + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { + stb_text_makeundo_replace(str, state, state->cursor, 1, 1); + STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + ++state->cursor; + state->has_preferred_x = 0; + } + } else { + stb_textedit_delete_selection(str,state); // implicitly clamps + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + stb_text_makeundo_insert(state, state->cursor, 1); + ++state->cursor; + state->has_preferred_x = 0; + } + } + } + break; + } + +#ifdef STB_TEXTEDIT_K_INSERT + case STB_TEXTEDIT_K_INSERT: + state->insert_mode = !state->insert_mode; + break; +#endif + + case STB_TEXTEDIT_K_UNDO: + stb_text_undo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_REDO: + stb_text_redo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT: + // if currently there's a selection, move cursor to start of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else + if (state->cursor > 0) + --state->cursor; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_RIGHT: + // if currently there's a selection, move cursor to end of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else + ++state->cursor; + stb_textedit_clamp(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + // move selection left + if (state->select_end > 0) + --state->select_end; + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_MOVEWORDLEFT + case STB_TEXTEDIT_K_WORDLEFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + +#ifdef STB_TEXTEDIT_MOVEWORDRIGHT + case STB_TEXTEDIT_K_WORDRIGHT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + + case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + // move selection right + ++state->select_end; + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_DOWN: + case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: + case STB_TEXTEDIT_K_PGDOWN: + case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN; + int row_count = is_page ? state->row_count_per_page : 1; + + if (!is_page && state->single_line) { + // on windows, up&down in single-line behave like left&right + key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + for (j = 0; j < row_count; ++j) { + float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; + int start = find.first_char + find.length; + + if (find.length == 0) + break; + + // [DEAR IMGUI] + // going down while being on the last line shouldn't bring us to that line end + if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) + break; + + // now find character position down a row + state->cursor = start; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + + // go to next line + find.first_char = find.first_char + find.length; + find.length = row.num_chars; + } + break; + } + + case STB_TEXTEDIT_K_UP: + case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: + case STB_TEXTEDIT_K_PGUP: + case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP; + int row_count = is_page ? state->row_count_per_page : 1; + + if (!is_page && state->single_line) { + // on windows, up&down become left&right + key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + for (j = 0; j < row_count; ++j) { + float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; + + // can only go up if there's a previous row + if (find.prev_first == find.first_char) + break; + + // now find character position up a row + state->cursor = find.prev_first; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + + // go to previous line + // (we need to scan previous line the hard way. maybe we could expose this as a new API function?) + prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; + while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE) + --prev_scan; + find.first_char = find.prev_first; + find.prev_first = prev_scan; + } + break; + } + + case STB_TEXTEDIT_K_DELETE: + case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + int n = STB_TEXTEDIT_STRINGLEN(str); + if (state->cursor < n) + stb_textedit_delete(str, state, state->cursor, 1); + } + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_BACKSPACE: + case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + stb_textedit_clamp(str, state); + if (state->cursor > 0) { + stb_textedit_delete(str, state, state->cursor-1, 1); + --state->cursor; + } + } + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2: +#endif + case STB_TEXTEDIT_K_TEXTSTART: + state->cursor = state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2: +#endif + case STB_TEXTEDIT_K_TEXTEND: + state->cursor = STB_TEXTEDIT_STRINGLEN(str); + state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); + state->has_preferred_x = 0; + break; + + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2: +#endif + case STB_TEXTEDIT_K_LINESTART: + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2: +#endif + case STB_TEXTEDIT_K_LINEEND: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->has_preferred_x = 0; + break; + } + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Undo processing +// +// @OPTIMIZE: the undo/redo buffer should be circular + +static void stb_textedit_flush_redo(StbUndoState *state) +{ + state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; +} + +// discard the oldest entry in the undo list +static void stb_textedit_discard_undo(StbUndoState *state) +{ + if (state->undo_point > 0) { + // if the 0th undo state has characters, clean those up + if (state->undo_rec[0].char_storage >= 0) { + int n = state->undo_rec[0].insert_length, i; + // delete n characters from all other records + state->undo_char_point -= n; + STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=0; i < state->undo_point; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it + } + --state->undo_point; + STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); + } +} + +// discard the oldest entry in the redo list--it's bad if this +// ever happens, but because undo & redo have to store the actual +// characters in different cases, the redo character buffer can +// fill up even though the undo buffer didn't +static void stb_textedit_discard_redo(StbUndoState *state) +{ + int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; + + if (state->redo_point <= k) { + // if the k'th undo state has characters, clean those up + if (state->undo_rec[k].char_storage >= 0) { + int n = state->undo_rec[k].insert_length, i; + // move the remaining redo character data to the end of the buffer + state->redo_char_point += n; + STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); + // adjust the position of all the other records to account for above memmove + for (i=state->redo_point; i < k; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage += n; + } + // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' + // [DEAR IMGUI] + size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0])); + const char* buf_begin = (char*)state->undo_rec; (void)buf_begin; + const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end; + IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin); + IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end); + STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size); + + // now move redo_point to point to the new one + ++state->redo_point; + } +} + +static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) +{ + // any time we create a new undo record, we discard redo + stb_textedit_flush_redo(state); + + // if we have no free records, we have to make room, by sliding the + // existing records down + if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + stb_textedit_discard_undo(state); + + // if the characters to store won't possibly fit in the buffer, we can't undo + if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { + state->undo_point = 0; + state->undo_char_point = 0; + return NULL; + } + + // if we don't have enough free characters in the buffer, we have to make room + while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) + stb_textedit_discard_undo(state); + + return &state->undo_rec[state->undo_point++]; +} + +static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) +{ + StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); + if (r == NULL) + return NULL; + + r->where = pos; + r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len; + r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len; + + if (insert_len == 0) { + r->char_storage = -1; + return NULL; + } else { + r->char_storage = state->undo_char_point; + state->undo_char_point += insert_len; + return &state->undo_char[r->char_storage]; + } +} + +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord u, *r; + if (s->undo_point == 0) + return; + + // we need to do two things: apply the undo record, and create a redo record + u = s->undo_rec[s->undo_point-1]; + r = &s->undo_rec[s->redo_point-1]; + r->char_storage = -1; + + r->insert_length = u.delete_length; + r->delete_length = u.insert_length; + r->where = u.where; + + if (u.delete_length) { + // if the undo record says to delete characters, then the redo record will + // need to re-insert the characters that get deleted, so we need to store + // them. + + // there are three cases: + // there's enough room to store the characters + // characters stored for *redoing* don't leave room for redo + // characters stored for *undoing* don't leave room for redo + // if the last is true, we have to bail + + if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { + // the undo records take up too much character space; there's no space to store the redo characters + r->insert_length = 0; + } else { + int i; + + // there's definitely room to store the characters eventually + while (s->undo_char_point + u.delete_length > s->redo_char_point) { + // should never happen: + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + // there's currently not enough room, so discard a redo record + stb_textedit_discard_redo(s); + } + r = &s->undo_rec[s->redo_point-1]; + + r->char_storage = s->redo_char_point - u.delete_length; + s->redo_char_point = s->redo_char_point - u.delete_length; + + // now save the characters + for (i=0; i < u.delete_length; ++i) + s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); + } + + // now we can carry out the deletion + STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); + } + + // check type of recorded action: + if (u.insert_length) { + // easy case: was a deletion, so we need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); + s->undo_char_point -= u.insert_length; + } + + state->cursor = u.where + u.insert_length; + + s->undo_point--; + s->redo_point--; +} + +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord *u, r; + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + + // we need to do two things: apply the redo record, and create an undo record + u = &s->undo_rec[s->undo_point]; + r = s->undo_rec[s->redo_point]; + + // we KNOW there must be room for the undo record, because the redo record + // was derived from an undo record + + u->delete_length = r.insert_length; + u->insert_length = r.delete_length; + u->where = r.where; + u->char_storage = -1; + + if (r.delete_length) { + // the redo record requires us to delete characters, so the undo record + // needs to store the characters + + if (s->undo_char_point + u->insert_length > s->redo_char_point) { + u->insert_length = 0; + u->delete_length = 0; + } else { + int i; + u->char_storage = s->undo_char_point; + s->undo_char_point = s->undo_char_point + u->insert_length; + + // now save the characters + for (i=0; i < u->insert_length; ++i) + s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); + } + + STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); + } + + if (r.insert_length) { + // easy case: need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); + s->redo_char_point += r.insert_length; + } + + state->cursor = r.where + r.insert_length; + + s->undo_point++; + s->redo_point++; +} + +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) +{ + stb_text_createundo(&state->undostate, where, 0, length); +} + +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); + if (p) { + for (i=0; i < length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); + if (p) { + for (i=0; i < old_length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +// reset the state to default +static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) +{ + state->undostate.undo_point = 0; + state->undostate.undo_char_point = 0; + state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; + state->select_end = state->select_start = 0; + state->cursor = 0; + state->has_preferred_x = 0; + state->preferred_x = 0; + state->cursor_at_end_of_line = 0; + state->initialized = 1; + state->single_line = (unsigned char) is_single_line; + state->insert_mode = 0; + state->row_count_per_page = 0; +} + +// API initialize +static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +{ + stb_textedit_clear_state(state, is_single_line); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) +{ + return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif//STB_TEXTEDIT_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/vendor/librw/skeleton/imgui/imstb_truetype.h b/vendor/librw/skeleton/imgui/imstb_truetype.h new file mode 100644 index 0000000..fc815d7 --- /dev/null +++ b/vendor/librw/skeleton/imgui/imstb_truetype.h @@ -0,0 +1,4903 @@ +// [DEAR IMGUI] +// This is a slightly modified version of stb_truetype.h 1.20. +// Mostly fixing for compiler and static analyzer warnings. +// Grep for [DEAR IMGUI] to find the changes. + +// stb_truetype.h - v1.20 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen +// Cass Everitt Martins Mozeiko +// stoiko (Haemimont Games) Cap Petschulat +// Brian Hook Omar Cornut +// Walter van Niftrik github:aloucks +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. github:oyvindjam +// Brian Costabile github:vassvik +// +// VERSION HISTORY +// +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); //-V595 + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) //-V560 + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + // [DEAR IMGUI] Commented to fix static analyzer warning + //classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + // [DEAR IMGUI] Commented to fix static analyzer warning + //classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + } // [DEAR IMGUI] removed ; + } + } + break; + } // [DEAR IMGUI] removed ; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + + if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + // [DEAR IMGUI] Fix static analyzer warning + (void)dx; // [ImGui: fix static analyzer warning] + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && spc->skip_missing) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value; // [DEAR IMGUI] removed = 1 + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + //orig[1] = y; // [DEAR IMGUI] commented double assignment + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/vendor/librw/skeleton/sdl2.cpp b/vendor/librw/skeleton/sdl2.cpp new file mode 100644 index 0000000..86d71e6 --- /dev/null +++ b/vendor/librw/skeleton/sdl2.cpp @@ -0,0 +1,326 @@ +#ifdef LIBRW_SDL2 + +#include +#include "skeleton.h" + +using namespace sk; +using namespace rw; + +#ifdef RW_OPENGL + +SDL_Window *window; + +static int keyCodeToSkKey(SDL_Keycode keycode) { + switch (keycode) { + case SDLK_SPACE: return ' '; + case SDLK_QUOTE: return '\''; + case SDLK_COMMA: return ','; + case SDLK_MINUS: return '-'; + case SDLK_PERIOD: return '.'; + case SDLK_SLASH: return '/'; + + case SDLK_0: return '0'; + case SDLK_1: return '1'; + case SDLK_2: return '2'; + case SDLK_3: return '3'; + case SDLK_4: return '4'; + case SDLK_5: return '5'; + case SDLK_6: return '6'; + case SDLK_7: return '7'; + case SDLK_8: return '8'; + case SDLK_9: return '9'; + + case SDLK_SEMICOLON: return ';'; + case SDLK_EQUALS: return '='; + + case SDLK_a: return 'A'; + case SDLK_b: return 'B'; + case SDLK_c: return 'C'; + case SDLK_d: return 'D'; + case SDLK_e: return 'E'; + case SDLK_f: return 'F'; + case SDLK_g: return 'G'; + case SDLK_h: return 'H'; + case SDLK_i: return 'I'; + case SDLK_j: return 'J'; + case SDLK_k: return 'K'; + case SDLK_l: return 'L'; + case SDLK_m: return 'M'; + case SDLK_n: return 'N'; + case SDLK_o: return 'O'; + case SDLK_p: return 'P'; + case SDLK_q: return 'Q'; + case SDLK_r: return 'R'; + case SDLK_s: return 'S'; + case SDLK_t: return 'T'; + case SDLK_u: return 'U'; + case SDLK_v: return 'V'; + case SDLK_w: return 'W'; + case SDLK_x: return 'X'; + case SDLK_y: return 'Y'; + case SDLK_z: return 'Z'; + + case SDLK_LEFTBRACKET: return '['; + case SDLK_BACKSLASH: return '\\'; + case SDLK_RIGHTBRACKET: return ']'; + case SDLK_BACKQUOTE: return '`'; + case SDLK_ESCAPE: return KEY_ESC; + case SDLK_RETURN: return KEY_ENTER; + case SDLK_TAB: return KEY_TAB; + case SDLK_BACKSPACE: return KEY_BACKSP; + case SDLK_INSERT: return KEY_INS; + case SDLK_DELETE: return KEY_DEL; + case SDLK_RIGHT: return KEY_RIGHT; + case SDLK_LEFT: return KEY_LEFT; + case SDLK_DOWN: return KEY_DOWN; + case SDLK_UP: return KEY_UP; + case SDLK_PAGEUP: return KEY_PGUP; + case SDLK_PAGEDOWN: return KEY_PGDN; + case SDLK_HOME: return KEY_HOME; + case SDLK_END: return KEY_END; + case SDLK_CAPSLOCK: return KEY_CAPSLK; + case SDLK_SCROLLLOCK: return KEY_NULL; + case SDLK_NUMLOCKCLEAR: return KEY_NULL; + case SDLK_PRINTSCREEN: return KEY_NULL; + case SDLK_PAUSE: return KEY_NULL; + + case SDLK_F1: return KEY_F1; + case SDLK_F2: return KEY_F2; + case SDLK_F3: return KEY_F3; + case SDLK_F4: return KEY_F4; + case SDLK_F5: return KEY_F5; + case SDLK_F6: return KEY_F6; + case SDLK_F7: return KEY_F7; + case SDLK_F8: return KEY_F8; + case SDLK_F9: return KEY_F9; + case SDLK_F10: return KEY_F10; + case SDLK_F11: return KEY_F11; + case SDLK_F12: return KEY_F12; + case SDLK_F13: return KEY_NULL; + case SDLK_F14: return KEY_NULL; + case SDLK_F15: return KEY_NULL; + case SDLK_F16: return KEY_NULL; + case SDLK_F17: return KEY_NULL; + case SDLK_F18: return KEY_NULL; + case SDLK_F19: return KEY_NULL; + case SDLK_F20: return KEY_NULL; + case SDLK_F21: return KEY_NULL; + case SDLK_F22: return KEY_NULL; + case SDLK_F23: return KEY_NULL; + case SDLK_F24: return KEY_NULL; + + case SDLK_KP_0: return KEY_NULL; + case SDLK_KP_1: return KEY_NULL; + case SDLK_KP_2: return KEY_NULL; + case SDLK_KP_3: return KEY_NULL; + case SDLK_KP_4: return KEY_NULL; + case SDLK_KP_5: return KEY_NULL; + case SDLK_KP_6: return KEY_NULL; + case SDLK_KP_7: return KEY_NULL; + case SDLK_KP_8: return KEY_NULL; + case SDLK_KP_9: return KEY_NULL; + case SDLK_KP_DECIMAL: return KEY_NULL; + case SDLK_KP_DIVIDE: return KEY_NULL; + case SDLK_KP_MULTIPLY: return KEY_NULL; + case SDLK_KP_MINUS: return KEY_NULL; + case SDLK_KP_PLUS: return KEY_NULL; + case SDLK_KP_ENTER: return KEY_NULL; + case SDLK_KP_EQUALS: return KEY_NULL; + + case SDLK_LSHIFT: return KEY_LSHIFT; + case SDLK_LCTRL: return KEY_LCTRL; + case SDLK_LALT: return KEY_LALT; + case SDLK_LGUI: return KEY_NULL; + case SDLK_RSHIFT: return KEY_RSHIFT; + case SDLK_RCTRL: return KEY_RCTRL; + case SDLK_RALT: return KEY_RALT; + case SDLK_RGUI: return KEY_NULL; + case SDLK_MENU: return KEY_NULL; + } + return KEY_NULL; +} + +#if 0 +static void +keypress(SDL_Window *window, int key, int scancode, int action, int mods) +{ + if(key >= 0 && key <= GLFW_KEY_LAST){ + if(action == GLFW_RELEASE) KeyUp(keymap[key]); + else if(action == GLFW_PRESS) KeyDown(keymap[key]); + else if(action == GLFW_REPEAT) KeyDown(keymap[key]); + } +} + +static void +charinput(GLFWwindow *window, unsigned int c) +{ + EventHandler(CHARINPUT, (void*)(uintptr)c); +} + +static void +resize(GLFWwindow *window, int w, int h) +{ + rw::Rect r; + r.x = 0; + r.y = 0; + r.w = w; + r.h = h; + EventHandler(RESIZE, &r); +} + +static void +mousebtn(GLFWwindow *window, int button, int action, int mods) +{ + static int buttons = 0; + sk::MouseState ms; + + switch(button){ + case GLFW_MOUSE_BUTTON_LEFT: + if(action == GLFW_PRESS) + buttons |= 1; + else + buttons &= ~1; + break; + case GLFW_MOUSE_BUTTON_MIDDLE: + if(action == GLFW_PRESS) + buttons |= 2; + else + buttons &= ~2; + break; + case GLFW_MOUSE_BUTTON_RIGHT: + if(action == GLFW_PRESS) + buttons |= 4; + else + buttons &= ~4; + break; + } + + sk::MouseState ms; + ms.buttons = buttons; + EventHandler(MOUSEBTN, &ms); +} +#endif + +enum mousebutton { +BUTTON_LEFT = 0x1, +BUTTON_MIDDLE = 0x2, +BUTTON_RIGHT = 0x4, +}; + +int +main(int argc, char *argv[]) +{ + args.argc = argc; + args.argv = argv; + + if(EventHandler(INITIALIZE, nil) == EVENTERROR) + return 0; + + engineOpenParams.width = sk::globals.width; + engineOpenParams.height = sk::globals.height; + engineOpenParams.windowtitle = sk::globals.windowtitle; + engineOpenParams.window = &window; + + if(EventHandler(RWINITIALIZE, nil) == EVENTERROR) + return 0; + + float lastTime = SDL_GetTicks(); + SDL_Event event; + int mouseButtons = 0; + + SDL_StartTextInput(); + + while(!sk::globals.quit){ + while(SDL_PollEvent(&event)){ + switch(event.type){ + case SDL_QUIT: + sk::globals.quit = true; + break; + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_RESIZED: { + rw::Rect r; + SDL_GetWindowPosition(window, &r.x, &r.y); + r.w = event.window.data1; + r.h = event.window.data2; + EventHandler(RESIZE, &r); + break; + } + } + break; + case SDL_KEYUP: { + int c = keyCodeToSkKey(event.key.keysym.sym); + EventHandler(KEYUP, &c); + break; + } + case SDL_KEYDOWN: { + int c = keyCodeToSkKey(event.key.keysym.sym); + EventHandler(KEYDOWN, &c); + break; + } + case SDL_TEXTINPUT: { + char *c = event.text.text; + while (int ci = *c) { + EventHandler(CHARINPUT, (void*)(uintptr)ci); + ++c; + } + break; + } + case SDL_MOUSEMOTION: { + sk::MouseState ms; + ms.posx = event.motion.x; + ms.posy = event.motion.y; + EventHandler(MOUSEMOVE, &ms); + break; + } + case SDL_MOUSEBUTTONDOWN: { + switch (event.button.button) { + case SDL_BUTTON_LEFT: mouseButtons |= BUTTON_LEFT; break; + case SDL_BUTTON_MIDDLE: mouseButtons |= BUTTON_MIDDLE; break; + case SDL_BUTTON_RIGHT: mouseButtons |= BUTTON_RIGHT; break; + } + sk::MouseState ms; + ms.buttons = mouseButtons; + EventHandler(MOUSEBTN, &ms); + break; + } + case SDL_MOUSEBUTTONUP: { + switch (event.button.button) { + case SDL_BUTTON_LEFT: mouseButtons &= ~BUTTON_LEFT; break; + case SDL_BUTTON_MIDDLE: mouseButtons &= ~BUTTON_MIDDLE; break; + case SDL_BUTTON_RIGHT: mouseButtons &= ~BUTTON_RIGHT; break; + } + sk::MouseState ms; + ms.buttons = mouseButtons; + EventHandler(MOUSEBTN, &ms); + break; + } + } + } + float currTime = SDL_GetTicks(); + float timeDelta = (currTime - lastTime) * 0.001f; + + EventHandler(IDLE, &timeDelta); + + lastTime = currTime; + } + + SDL_StopTextInput(); + + EventHandler(RWTERMINATE, nil); + + return 0; +} + +namespace sk { + +void +SetMousePosition(int x, int y) +{ + SDL_WarpMouseInWindow(*engineOpenParams.window, x, y); +} + +} + +#endif +#endif diff --git a/vendor/librw/skeleton/skeleton.cpp b/vendor/librw/skeleton/skeleton.cpp new file mode 100644 index 0000000..81fb0e7 --- /dev/null +++ b/vendor/librw/skeleton/skeleton.cpp @@ -0,0 +1,192 @@ +#include +#include "skeleton.h" + + +namespace sk { + +Globals globals; +Args args; + + +bool +InitRW(void) +{ + if(!rw::Engine::init()) + return false; + if(AppEventHandler(sk::PLUGINATTACH, nil) == EVENTERROR) + return false; + if(!rw::Engine::open(&engineOpenParams)) + return false; + + SubSystemInfo info; + int i, n; + n = Engine::getNumSubSystems(); + for(i = 0; i < n; i++) + if(Engine::getSubSystemInfo(&info, i)) + printf("subsystem: %s\n", info.name); + Engine::setSubSystem(n-1); + + int want = -1; + VideoMode mode; + n = Engine::getNumVideoModes(); + for(i = 0; i < n; i++) + if(Engine::getVideoModeInfo(&mode, i)){ +// if(mode.width == 640 && mode.height == 480 && mode.depth == 32) + if(mode.width == 1920 && mode.height == 1080 && mode.depth == 32) + want = i; + printf("mode: %dx%dx%d %d\n", mode.width, mode.height, mode.depth, mode.flags); + } +// if(want >= 0) Engine::setVideoMode(want); + Engine::getVideoModeInfo(&mode, Engine::getCurrentVideoMode()); + + if(mode.flags & VIDEOMODEEXCLUSIVE){ + globals.width = mode.width; + globals.height = mode.height; + } + + if(!rw::Engine::start()) + return false; + + rw::Charset::open(); + + rw::Image::setSearchPath("./"); + return true; +} + +void +TerminateRW(void) +{ + rw::Charset::close(); + + // TODO: delete all tex dicts + rw::Engine::stop(); + rw::Engine::close(); + rw::Engine::term(); +} + +Camera* +CameraCreate(int32 width, int32 height, bool32 z) +{ + Camera *cam; + cam = Camera::create(); + cam->setFrame(Frame::create()); + cam->frameBuffer = Raster::create(width, height, 0, Raster::CAMERA); + cam->zBuffer = Raster::create(width, height, 0, Raster::ZBUFFER); + return cam; +} + +void +CameraDestroy(rw::Camera *cam) +{ + if(cam->frameBuffer){ + cam->frameBuffer->destroy(); + cam->frameBuffer = nil; + } + if(cam->zBuffer){ + cam->zBuffer->destroy(); + cam->zBuffer = nil; + } + rw::Frame *frame = cam->getFrame(); + if(frame){ + cam->setFrame(nil); + frame->destroy(); + } + cam->destroy(); +} + +void +CameraSize(Camera *cam, Rect *r, float viewWindow, float aspectRatio) +{ + if(cam->frameBuffer){ + cam->frameBuffer->destroy(); + cam->frameBuffer = nil; + } + if(cam->zBuffer){ + cam->zBuffer->destroy(); + cam->zBuffer = nil; + } + cam->frameBuffer = Raster::create(r->w, r->h, 0, Raster::CAMERA); + cam->zBuffer = Raster::create(r->w, r->h, 0, Raster::ZBUFFER); + + if(viewWindow != 0.0f){ + rw::V2d vw; + // TODO: aspect ratio when fullscreen + if(r->w > r->h){ + vw.x = viewWindow; + vw.y = viewWindow / ((float)r->w/r->h); + }else{ + vw.x = viewWindow / ((float)r->h/r->w); + vw.y = viewWindow; + } + cam->setViewWindow(&vw); + } +} + +void +CameraMove(Camera *cam, V3d *delta) +{ + rw::V3d offset; + rw::V3d::transformVectors(&offset, delta, 1, &cam->getFrame()->matrix); + cam->getFrame()->translate(&offset); +} + +void +CameraPan(Camera *cam, V3d *pos, float angle) +{ + rw::Frame *frame = cam->getFrame(); + rw::V3d trans = pos ? *pos : frame->matrix.pos; + rw::V3d negTrans = rw::scale(trans, -1.0f); + frame->translate(&negTrans); + frame->rotate(&frame->matrix.up, angle); + frame->translate(&trans); +} + +void +CameraTilt(Camera *cam, V3d *pos, float angle) +{ + rw::Frame *frame = cam->getFrame(); + rw::V3d trans = pos ? *pos : frame->matrix.pos; + rw::V3d negTrans = rw::scale(trans, -1.0f); + frame->translate(&negTrans); + frame->rotate(&frame->matrix.right, angle); + frame->translate(&trans); +} + +void +CameraRotate(Camera *cam, V3d *pos, float angle) +{ + rw::Frame *frame = cam->getFrame(); + rw::V3d trans = pos ? *pos : frame->matrix.pos; + rw::V3d negTrans = rw::scale(trans, -1.0f); + frame->translate(&negTrans); + frame->rotate(&frame->matrix.at, angle); + frame->translate(&negTrans); +} + +EventStatus +EventHandler(Event e, void *param) +{ + EventStatus s; + if (e == INITIALIZE) { + ImGui::CreateContext(); + } + + s = AppEventHandler(e, param); + if(e == QUIT){ + globals.quit = 1; + return EVENTPROCESSED; + } + if(s == EVENTNOTPROCESSED) + switch(e){ + case RWINITIALIZE: + return InitRW() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + TerminateRW(); + return EVENTPROCESSED; + default: + break; + } + return s; +} + +} diff --git a/vendor/librw/skeleton/skeleton.h b/vendor/librw/skeleton/skeleton.h new file mode 100644 index 0000000..00602e2 --- /dev/null +++ b/vendor/librw/skeleton/skeleton.h @@ -0,0 +1,120 @@ +extern rw::EngineOpenParams engineOpenParams; + +namespace sk { + +using namespace rw; + +// same as RW skeleton +enum Key +{ + // ascii... + + KEY_ESC = 128, + + KEY_F1 = 129, + KEY_F2 = 130, + KEY_F3 = 131, + KEY_F4 = 132, + KEY_F5 = 133, + KEY_F6 = 134, + KEY_F7 = 135, + KEY_F8 = 136, + KEY_F9 = 137, + KEY_F10 = 138, + KEY_F11 = 139, + KEY_F12 = 140, + + KEY_INS = 141, + KEY_DEL = 142, + KEY_HOME = 143, + KEY_END = 144, + KEY_PGUP = 145, + KEY_PGDN = 146, + + KEY_UP = 147, + KEY_DOWN = 148, + KEY_LEFT = 149, + KEY_RIGHT = 150, + + // some stuff ommitted + + KEY_BACKSP = 168, + KEY_TAB = 169, + KEY_CAPSLK = 170, + KEY_ENTER = 171, + KEY_LSHIFT = 172, + KEY_RSHIFT = 173, + KEY_LCTRL = 174, + KEY_RCTRL = 175, + KEY_LALT = 176, + KEY_RALT = 177, + + KEY_NULL, // unused + KEY_NUMKEYS, +}; + +enum EventStatus +{ + EVENTERROR, + EVENTPROCESSED, + EVENTNOTPROCESSED +}; + +enum Event +{ + INITIALIZE, + RWINITIALIZE, + RWTERMINATE, + SELECTDEVICE, + PLUGINATTACH, + KEYDOWN, + KEYUP, + CHARINPUT, + MOUSEMOVE, + MOUSEBTN, + RESIZE, + IDLE, + QUIT +}; + +struct Globals +{ + const char *windowtitle; + int32 width; + int32 height; + bool32 quit; +}; +extern Globals globals; + +// Argument to mouse events +struct MouseState +{ + int posx, posy; + int buttons; // bits 0-2 are left, middle, right button down +}; + +struct Args +{ + int argc; + char **argv; +}; +extern Args args; + +bool InitRW(void); +void TerminateRW(void); +Camera *CameraCreate(int32 width, int32 height, bool32 z); +void CameraDestroy(rw::Camera *cam); +void CameraSize(Camera *cam, Rect *r, float viewWindow = 0.0f, float aspectRatio = 0.0f); +void CameraMove(Camera *cam, V3d *delta); +void CameraPan(Camera *cam, V3d *pos, float angle); +void CameraTilt(Camera *cam, V3d *pos, float angle); +void CameraRotate(Camera *cam, V3d *pos, float angle); +void SetMousePosition(int x, int y); +EventStatus EventHandler(Event e, void *param); + +} + +sk::EventStatus AppEventHandler(sk::Event e, void *param); + +#include "imgui/imgui.h" +#include "imgui/imgui_impl_rw.h" diff --git a/vendor/librw/skeleton/win.cpp b/vendor/librw/skeleton/win.cpp new file mode 100644 index 0000000..9f1ee5a --- /dev/null +++ b/vendor/librw/skeleton/win.cpp @@ -0,0 +1,315 @@ +#ifdef _WIN32 +#include +#include +#include "skeleton.h" + +using namespace sk; +using namespace rw; + +#ifdef RW_D3D9 + +#ifndef VK_OEM_NEC_EQUAL +#define VK_OEM_NEC_EQUAL 0x92 +#endif + +static int keymap[256]; +static void +initkeymap(void) +{ + int i; + for(i = 0; i < 256; i++) + keymap[i] = KEY_NULL; + keymap[VK_SPACE] = ' '; + keymap[VK_OEM_7] = '\''; + keymap[VK_OEM_COMMA] = ','; + keymap[VK_OEM_MINUS] = '-'; + keymap[VK_OEM_PERIOD] = '.'; + keymap[VK_OEM_2] = '/'; + for(i = '0'; i <= '9'; i++) + keymap[i] = i; + keymap[VK_OEM_1] = ';'; + keymap[VK_OEM_NEC_EQUAL] = '='; + for(i = 'A'; i <= 'Z'; i++) + keymap[i] = i; + keymap[VK_OEM_4] = '['; + keymap[VK_OEM_5] = '\\'; + keymap[VK_OEM_6] = ']'; + keymap[VK_OEM_3] = '`'; + keymap[VK_ESCAPE] = KEY_ESC; + keymap[VK_RETURN] = KEY_ENTER; + keymap[VK_TAB] = KEY_TAB; + keymap[VK_BACK] = KEY_BACKSP; + keymap[VK_INSERT] = KEY_INS; + keymap[VK_DELETE] = KEY_DEL; + keymap[VK_RIGHT] = KEY_RIGHT; + keymap[VK_LEFT] = KEY_LEFT; + keymap[VK_DOWN] = KEY_DOWN; + keymap[VK_UP] = KEY_UP; + keymap[VK_PRIOR] = KEY_PGUP; + keymap[VK_NEXT] = KEY_PGDN; + keymap[VK_HOME] = KEY_HOME; + keymap[VK_END] = KEY_END; + keymap[VK_MODECHANGE] = KEY_CAPSLK; + for(i = VK_F1; i <= VK_F24; i++) + keymap[i] = i-VK_F1+KEY_F1; + keymap[VK_LSHIFT] = KEY_LSHIFT; + keymap[VK_LCONTROL] = KEY_LCTRL; + keymap[VK_LMENU] = KEY_LALT; + keymap[VK_RSHIFT] = KEY_RSHIFT; + keymap[VK_RCONTROL] = KEY_RCTRL; + keymap[VK_RMENU] = KEY_RALT; +} +bool running; + +static void KeyUp(int key) { EventHandler(KEYUP, &key); } +static void KeyDown(int key) { EventHandler(KEYDOWN, &key); } + +LRESULT CALLBACK +WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static int resizing = 0; + static int buttons = 0; + POINTS p; + + MouseState ms; + switch(msg){ + case WM_DESTROY: + PostQuitMessage(0); + break; + + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + if(wParam == VK_MENU){ + if(GetKeyState(VK_LMENU) & 0x8000) KeyDown(keymap[VK_LMENU]); + if(GetKeyState(VK_RMENU) & 0x8000) KeyDown(keymap[VK_RMENU]); + }else if(wParam == VK_CONTROL){ + if(GetKeyState(VK_LCONTROL) & 0x8000) KeyDown(keymap[VK_LCONTROL]); + if(GetKeyState(VK_RCONTROL) & 0x8000) KeyDown(keymap[VK_RCONTROL]); + }else if(wParam == VK_SHIFT){ + if(GetKeyState(VK_LSHIFT) & 0x8000) KeyDown(keymap[VK_LSHIFT]); + if(GetKeyState(VK_RSHIFT) & 0x8000) KeyDown(keymap[VK_RSHIFT]); + }else + KeyDown(keymap[wParam]); + break; + + case WM_SYSKEYUP: + case WM_KEYUP: + if(wParam == VK_MENU){ + if((GetKeyState(VK_LMENU) & 0x8000) == 0) KeyUp(keymap[VK_LMENU]); + if((GetKeyState(VK_RMENU) & 0x8000) == 0) KeyUp(keymap[VK_RMENU]); + }else if(wParam == VK_CONTROL){ + if((GetKeyState(VK_LCONTROL) & 0x8000) == 0) KeyUp(keymap[VK_LCONTROL]); + if((GetKeyState(VK_RCONTROL) & 0x8000) == 0) KeyUp(keymap[VK_RCONTROL]); + }else if(wParam == VK_SHIFT){ + if((GetKeyState(VK_LSHIFT) & 0x8000) == 0) KeyUp(keymap[VK_LSHIFT]); + if((GetKeyState(VK_RSHIFT) & 0x8000) == 0) KeyUp(keymap[VK_RSHIFT]); + }else + KeyUp(keymap[wParam]); + break; + + case WM_CHAR: + if(wParam > 0 && wParam < 0x10000) + EventHandler(CHARINPUT, (void*)wParam); + break; + + case WM_MOUSEMOVE: + p = MAKEPOINTS(lParam); + ms.posx = p.x; + ms.posy = p.y; + EventHandler(MOUSEMOVE, &ms); + break; + + case WM_LBUTTONDOWN: + buttons |= 1; goto mbtn; + case WM_LBUTTONUP: + buttons &= ~1; goto mbtn; + case WM_MBUTTONDOWN: + buttons |= 2; goto mbtn; + case WM_MBUTTONUP: + buttons &= ~2; goto mbtn; + case WM_RBUTTONDOWN: + buttons |= 4; goto mbtn; + case WM_RBUTTONUP: + buttons &= ~4; + mbtn: + ms.buttons = buttons; + EventHandler(MOUSEBTN, &ms); + break; + + case WM_SIZE: + rw::Rect r; + r.x = 0; + r.y = 0; + r.w = LOWORD(lParam); + r.h = HIWORD(lParam); + EventHandler(RESIZE, &r); + break; + + case WM_CLOSE: + DestroyWindow(hwnd); + break; + + case WM_SYSCOMMAND: + if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu + return 0; + break; + + case WM_QUIT: + running = false; + break; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +HWND +MakeWindow(HINSTANCE instance, int width, int height, const char *title) +{ + WNDCLASS wc; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = instance; + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wc.lpszMenuName = 0; + wc.lpszClassName = "librwD3D9"; + if(!RegisterClass(&wc)){ + MessageBox(0, "RegisterClass() - FAILED", 0, 0); + return 0; + } + + int offx = 100; + int offy = 100; + RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + DWORD style = WS_OVERLAPPEDWINDOW; + AdjustWindowRect(&rect, style, FALSE); + rect.right += -rect.left; + rect.bottom += -rect.top; + HWND win; + win = CreateWindow("librwD3D9", title, style, + offx, offy, rect.right, rect.bottom, 0, 0, instance, 0); + if(!win){ + MessageBox(0, "CreateWindow() - FAILED", 0, 0); + return 0; + } + ShowWindow(win, SW_SHOW); + UpdateWindow(win); + return win; +} + +void +pollEvents(void) +{ + MSG msg; + while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){ + if(msg.message == WM_QUIT){ + running = false; + break; + }else{ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, + PSTR cmdLine, int showCmd) +{ +/* + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); +*/ + + INT64 ticks; + INT64 ticksPerSecond; + if(!QueryPerformanceFrequency((LARGE_INTEGER*)&ticksPerSecond)) + return 0; + if(!QueryPerformanceCounter((LARGE_INTEGER*)&ticks)) + return 0; + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + args.argc = _argc; + args.argv = _argv; +#else + args.argc = __argc; + args.argv = __argv; +#endif + + if(EventHandler(INITIALIZE, nil) == EVENTERROR) + return 0; + + HWND win = MakeWindow(instance, + sk::globals.width, sk::globals.height, + sk::globals.windowtitle); + if(win == 0){ + MessageBox(0, "MakeWindow() - FAILED", 0, 0); + return 0; + } + engineOpenParams.window = win; + initkeymap(); + + if(EventHandler(RWINITIALIZE, nil) == EVENTERROR) + return 0; + + INT64 lastTicks; + QueryPerformanceCounter((LARGE_INTEGER *)&lastTicks); + running = true; + while((pollEvents(), running) && !globals.quit){ + QueryPerformanceCounter((LARGE_INTEGER *)&ticks); + float timeDelta = (float)(ticks - lastTicks)/ticksPerSecond; + + EventHandler(IDLE, &timeDelta); + + lastTicks = ticks; + } + + EventHandler(RWTERMINATE, nil); + + return 0; +} + +namespace sk { + +void +SetMousePosition(int x, int y) +{ + POINT pos = { x, y }; + ClientToScreen(engineOpenParams.window, &pos); + SetCursorPos(pos.x, pos.y); +} + +} + +#endif + +#ifdef RW_OPENGL +int main(int argc, char *argv[]); + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, + PSTR cmdLine, int showCmd) +{ +/* + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); +*/ + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + return main(_argc, _argv); +#else + return main(__argc, __argv); +#endif +} +#endif +#endif diff --git a/vendor/librw/src/CMakeLists.txt b/vendor/librw/src/CMakeLists.txt new file mode 100644 index 0000000..5b16901 --- /dev/null +++ b/vendor/librw/src/CMakeLists.txt @@ -0,0 +1,255 @@ +add_library(librw + "${PROJECT_SOURCE_DIR}/args.h" + "${PROJECT_SOURCE_DIR}/rw.h" + + anim.cpp + base.cpp + bmp.cpp + camera.cpp + charset.cpp + clump.cpp + engine.cpp + error.cpp + frame.cpp + geometry.cpp + geoplg.cpp + hanim.cpp + image.cpp + light.cpp + matfx.cpp + pipeline.cpp + plg.cpp + png.cpp + prim.cpp + raster.cpp + render.cpp + rwanim.h + rwengine.h + rwerror.h + rwobjects.h + rwpipeline.h + rwplg.h + rwplugins.h + rwrender.h + rwuserdata.h + skin.cpp + texture.cpp + tga.cpp + tristrip.cpp + userdata.cpp + uvanim.cpp + world.cpp + + d3d/d3d8.cpp + d3d/d3d8matfx.cpp + d3d/d3d8render.cpp + d3d/d3d8skin.cpp + d3d/d3d9.cpp + d3d/d3d9matfx.cpp + d3d/d3d9render.cpp + d3d/d3d9skin.cpp + d3d/d3d.cpp + d3d/d3ddevice.cpp + d3d/d3dimmed.cpp + d3d/d3drender.cpp + d3d/rwd3d8.h + d3d/rwd3d9.h + d3d/rwd3d.h + d3d/rwd3dimpl.h + d3d/rwxbox.h + d3d/rwxboximpl.h + d3d/xbox.cpp + d3d/xboxmatfx.cpp + d3d/xboxskin.cpp + d3d/xboxvfmt.cpp + + gl/gl3.cpp + gl/gl3device.cpp + gl/gl3immed.cpp + gl/gl3matfx.cpp + gl/gl3pipe.cpp + gl/gl3raster.cpp + gl/gl3render.cpp + gl/gl3shader.cpp + gl/gl3skin.cpp + gl/rwgl3.h + gl/rwgl3impl.h + gl/rwgl3plg.h + gl/rwgl3shader.h + gl/rwwdgl.h + gl/wdgl.cpp + gl/glad/glad.h + gl/glad/glad.c + gl/glad/khrplatform.h + + lodepng/lodepng.h + lodepng/lodepng.cpp + + ps2/pds.cpp + ps2/ps2.cpp + ps2/ps2device.cpp + ps2/ps2matfx.cpp + ps2/ps2raster.cpp + ps2/ps2skin.cpp + ps2/rwps2.h + ps2/rwps2impl.h + ps2/rwps2plg.h +) +add_library(librw::librw ALIAS librw) + +target_include_directories(librw + INTERFACE + $ + $ +) + +target_compile_definitions(librw + PRIVATE + LODEPNG_NO_COMPILE_CPP + "$,DEBUG,NDEBUG>" + PUBLIC + "RW_${LIBRW_PLATFORM}" +) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_link_libraries(librw + PRIVATE + m + ) +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(librw + PRIVATE + "-Wall" + ) + if (NOT LIBRW_PLATFORM_PS2) + target_compile_options(librw + PRIVATE + "-Wextra" + "-Wdouble-promotion" + "-Wpedantic" + ) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(librw + PUBLIC + /wd4996 /wd4244 + ) +endif() + +set_target_properties(librw + PROPERTIES + C_STANDARD 11 + C_EXTENSIONS OFF + C_STANDARD_REQUIRED ON + CXX_STANDARD 11 + CXX_EXTENSIONS OFF + CXX_STANDARD_REQUIRED ON + PREFIX "" +) + +if(LIBRW_PLATFORM_GL3) + if (LIBRW_GL3_GFXLIB STREQUAL "GLFW") + find_package(glfw3 REQUIRED) + target_link_libraries(librw + PUBLIC + glfw + ) + elseif (LIBRW_GL3_GFXLIB STREQUAL "SDL2") + find_package(SDL2 REQUIRED) + target_compile_definitions(librw PUBLIC LIBRW_SDL2) + target_link_libraries(librw + PUBLIC + SDL2::SDL2 + ) + endif() + + set(OpenGL_GL_PREFERENCE GLVND) + find_package(OpenGL) + if(TARGET OpenGL::OpenGL) + target_link_libraries(librw + PRIVATE + OpenGL::OpenGL + ) + elseif(TARGET OpenGL::EGL) + target_link_libraries(librw + PRIVATE + OpenGL::EGL + ) + elseif(TARGET OpenGL::GL) + target_link_libraries(librw + PRIVATE + OpenGL::GL + ) + else() + message(FATAL_ERROR "find_package(OpenGL) failed.") + endif() +elseif(LIBRW_PLATFORM_D3D9) + target_link_libraries(librw + PRIVATE + d3d9 + xinput + ) +endif() + +if(LIBRW_INSTALL) + install( + FILES + "${PROJECT_SOURCE_DIR}/args.h" + "${PROJECT_SOURCE_DIR}/rw.h" + DESTINATION "${LIBRW_INSTALL_INCLUDEDIR}" + ) + install( + FILES + base.err + rwbase.h + rwcharset.h + rwerror.h + rwplg.h + rwrender.h + rwengine.h + rwpipeline.h + rwobjects.h + rwanim.h + rwplugins.h + rwuserdata.h + DESTINATION "${LIBRW_INSTALL_INCLUDEDIR}/src" + ) + install( + FILES + d3d/rwxbox.h + d3d/rwd3d.h + d3d/rwd3d8.h + d3d/rwd3d9.h + DESTINATION "${LIBRW_INSTALL_INCLUDEDIR}/src/d3d" + ) + install( + FILES + ps2/rwps2.h + ps2/rwps2plg.h + DESTINATION "${LIBRW_INSTALL_INCLUDEDIR}/src/ps2" + ) + install( + FILES + gl/rwwdgl.h + gl/rwgl3.h + gl/rwgl3plg.h + gl/rwgl3shader.h + DESTINATION "${LIBRW_INSTALL_INCLUDEDIR}/src/gl" + ) + + install( + FILES + gl/glad/glad.h + gl/glad/khrplatform.h + DESTINATION "${LIBRW_INSTALL_INCLUDEDIR}/src/gl/glad" + ) + + install( + TARGETS librw + EXPORT librw-targets + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ) +endif() diff --git a/vendor/librw/src/anim.cpp b/vendor/librw/src/anim.cpp new file mode 100644 index 0000000..2003f1b --- /dev/null +++ b/vendor/librw/src/anim.cpp @@ -0,0 +1,293 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" + +#define PLUGIN_ID ID_ANIMANIMATION + +namespace rw { + +// +// AnimInterpolatorInfo +// + +#define MAXINTERPINFO 10 + +static AnimInterpolatorInfo *interpInfoList[MAXINTERPINFO]; + +void +AnimInterpolatorInfo::registerInterp(AnimInterpolatorInfo *interpInfo) +{ + for(int32 i = 0; i < MAXINTERPINFO; i++) + if(interpInfoList[i] == nil){ + interpInfoList[i] = interpInfo; + return; + } + assert(0 && "no room for interpolatorInfo"); +} + +void +AnimInterpolatorInfo::unregisterInterp(AnimInterpolatorInfo *interpInfo) +{ + for(int32 i = 0; i < MAXINTERPINFO; i++) + if(interpInfoList[i] == interpInfo){ + rwFree(interpInfoList[i]); + interpInfoList[i] = nil; + return; + } +} + +AnimInterpolatorInfo* +AnimInterpolatorInfo::find(int32 id) +{ + for(int32 i = 0; i < MAXINTERPINFO; i++){ + if(interpInfoList[i] && interpInfoList[i]->id == id) + return interpInfoList[i]; + } + return nil; +} + +// +// Animation +// + +Animation* +Animation::create(AnimInterpolatorInfo *interpInfo, int32 numFrames, + int32 flags, float duration) +{ + int32 sz = sizeof(Animation) + + numFrames*interpInfo->animKeyFrameSize + + interpInfo->customDataSize; + uint8 *data = (uint8*)rwMalloc(sz, MEMDUR_EVENT | ID_ANIMANIMATION); + if(data == nil){ + RWERROR((ERR_ALLOC, sz)); + return nil; + } + Animation *anim = (Animation*)data; + data += sizeof(Animation); + anim->interpInfo = interpInfo; + anim->numFrames = numFrames; + anim->flags = flags; + anim->duration = duration; + anim->keyframes = data; + data += anim->numFrames*interpInfo->animKeyFrameSize; + anim->customData = data; + return anim; +} + +void +Animation::destroy(void) +{ + rwFree(this); +} + +int32 +Animation::getNumNodes(void) +{ + int32 sz = this->interpInfo->animKeyFrameSize; + KeyFrameHeader *first = (KeyFrameHeader*)this->keyframes; + int32 n = 0; + for(KeyFrameHeader *f = first; f->prev != first; f = f->next(sz)) + n++; + return n; +} + +Animation* +Animation::streamRead(Stream *stream) +{ + Animation *anim; + if(stream->readI32() != 0x100) + return nil; + int32 typeID = stream->readI32(); + AnimInterpolatorInfo *interpInfo = AnimInterpolatorInfo::find(typeID); + int32 numFrames = stream->readI32(); + int32 flags = stream->readI32(); + float duration = stream->readF32(); + anim = Animation::create(interpInfo, numFrames, flags, duration); + interpInfo->streamRead(stream, anim); + return anim; +} + +Animation* +Animation::streamReadLegacy(Stream *stream) +{ + Animation *anim; + AnimInterpolatorInfo *interpInfo = AnimInterpolatorInfo::find(1); + int32 numFrames = stream->readI32(); + int32 flags = stream->readI32(); + float duration = stream->readF32(); + anim = Animation::create(interpInfo, numFrames, flags, duration); + HAnimKeyFrame *frames = (HAnimKeyFrame*)anim->keyframes; + for(int32 i = 0; i < anim->numFrames; i++){ + stream->read32(&frames[i].q, 4*4); + frames[i].q = conj(frames[i].q); + stream->read32(&frames[i].t, 3*4); + frames[i].time = stream->readF32(); + int32 prev = stream->readI32()/0x24; + frames[i].prev = &frames[prev]; + } + return anim; +} + +bool +Animation::streamWrite(Stream *stream) +{ + writeChunkHeader(stream, ID_ANIMANIMATION, this->streamGetSize()); + stream->writeI32(0x100); + stream->writeI32(this->interpInfo->id); + stream->writeI32(this->numFrames); + stream->writeI32(this->flags); + stream->writeF32(this->duration); + this->interpInfo->streamWrite(stream, this); + return true; +} + +bool +Animation::streamWriteLegacy(Stream *stream) +{ + stream->writeI32(this->numFrames); + stream->writeI32(this->flags); + stream->writeF32(this->duration); + assert(interpInfo->id == 1); + HAnimKeyFrame *frames = (HAnimKeyFrame*)this->keyframes; + for(int32 i = 0; i < this->numFrames; i++){ + frames[i].q = conj(frames[i].q); + stream->write32(&frames[i].q, 4*4); + frames[i].q = conj(frames[i].q); + stream->write32(&frames[i].t, 3*4); + stream->writeF32(frames[i].time); + stream->writeI32((frames[i].prev - frames)*0x24); + } + return true; +} + +uint32 +Animation::streamGetSize(void) +{ + uint32 size = 4 + 4 + 4 + 4 + 4; + size += this->interpInfo->streamGetSize(this); + return size; +} + +// +// AnimInterpolator +// + +AnimInterpolator* +AnimInterpolator::create(int32 numNodes, int32 maxFrameSize) +{ + AnimInterpolator *interp; + int32 sz; + int32 realsz = maxFrameSize; + + // Add some space for pointers and padding, hopefully this will be + // enough. Don't change maxFrameSize not to mess up streaming. + if(sizeof(void*) > 4) + realsz += 16; + sz = sizeof(AnimInterpolator) + numNodes*realsz; + interp = (AnimInterpolator*)rwMalloc(sz, MEMDUR_EVENT | ID_ANIMANIMATION); + if(interp == nil){ + RWERROR((ERR_ALLOC, sz)); + return nil; + } + interp->currentAnim = nil; + interp->currentTime = 0.0f; + interp->nextFrame = nil; + interp->maxInterpKeyFrameSize = maxFrameSize; + interp->currentInterpKeyFrameSize = maxFrameSize; + interp->currentAnimKeyFrameSize = -1; + interp->numNodes = numNodes;; + + return interp; +} + +void +AnimInterpolator::destroy(void) +{ + rwFree(this); +} + +bool32 +AnimInterpolator::setCurrentAnim(Animation *anim) +{ + int32 i; + AnimInterpolatorInfo *interpInfo = anim->interpInfo; + this->currentAnim = anim; + this->currentTime = 0.0f; + int32 maxkf = this->maxInterpKeyFrameSize; + if(sizeof(void*) > 4) // see above in create() + maxkf += 16; + if(interpInfo->interpKeyFrameSize > maxkf){ + RWERROR((ERR_GENERAL, "interpolation frame too big")); + return 0; + } + this->currentInterpKeyFrameSize = interpInfo->interpKeyFrameSize; + this->currentAnimKeyFrameSize = interpInfo->animKeyFrameSize; + this->applyCB = interpInfo->applyCB; + this->blendCB = interpInfo->blendCB; + this->interpCB = interpInfo->interpCB; + this->addCB = interpInfo->addCB; + for(i = 0; i < numNodes; i++){ + InterpFrameHeader *intf; + KeyFrameHeader *kf1, *kf2; + intf = this->getInterpFrame(i); + kf1 = this->getAnimFrame(i); + kf2 = this->getAnimFrame(i+numNodes); + intf->keyFrame1 = kf1; + intf->keyFrame2 = kf2; + // TODO: perhaps just implement all interpolator infos? + if(this->interpCB) + this->interpCB(intf, kf1, kf2, 0.0f, anim->customData); + } + this->nextFrame = this->getAnimFrame(numNodes*2); + return 1; +} + +void +AnimInterpolator::addTime(float32 t) +{ + int32 i; + if(t <= 0.0f) + return; + this->currentTime += t; + // reset animation + if(this->currentTime > this->currentAnim->duration){ + this->setCurrentAnim(this->currentAnim); + return; + } + KeyFrameHeader *last = this->getAnimFrame(this->currentAnim->numFrames); + KeyFrameHeader *next = (KeyFrameHeader*)this->nextFrame; + InterpFrameHeader *ifrm = nil; + while(next < last && next->prev->time <= this->currentTime){ + // find next interpolation frame to expire + for(i = 0; i < this->numNodes; i++){ + ifrm = this->getInterpFrame(i); + if(ifrm->keyFrame2 == next->prev) + break; + } + // advance interpolation frame + ifrm->keyFrame1 = ifrm->keyFrame2; + ifrm->keyFrame2 = next; + // ... and next frame + next = (KeyFrameHeader*)((uint8*)this->nextFrame + + currentAnimKeyFrameSize); + this->nextFrame = next; + } + for(i = 0; i < this->numNodes; i++){ + ifrm = this->getInterpFrame(i); + this->interpCB(ifrm, ifrm->keyFrame1, ifrm->keyFrame2, + this->currentTime, + this->currentAnim->customData); + } +} + +} diff --git a/vendor/librw/src/base.cpp b/vendor/librw/src/base.cpp new file mode 100644 index 0000000..fae47da --- /dev/null +++ b/vendor/librw/src/base.cpp @@ -0,0 +1,1121 @@ +#include +#include +#include +#include +#include +#include + +#define PSEP_C '/' +#define PSEP_S "/" +#ifdef __unix__ +#include +#include +#endif + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +namespace rw { + +#define PLUGIN_ID 0 + +int32 version = 0x36003; +int32 build = 0xFFFF; +#ifdef RW_PS2 + int32 platform = PLATFORM_PS2; +#elif RW_WDGL + int32 platform = PLATFORM_WDGL; +#elif RW_GL3 + int32 platform = PLATFORM_GL3; +#elif RW_D3D9 + int32 platform = PLATFORM_D3D9; +#else + int32 platform = PLATFORM_NULL; +#endif +bool32 streamAppendFrames = 0; +char *debugFile = nil; + +static Matrix identMat = { + { 1.0f, 0.0f, 0.0f }, Matrix::IDENTITY|Matrix::TYPEORTHONORMAL, + { 0.0f, 1.0f, 0.0f }, 0, + { 0.0f, 0.0f, 1.0f }, 0, + { 0.0f, 0.0f, 0.0f }, 0 +}; + +// lazy implementation +int +strcmp_ci(const char *s1, const char *s2) +{ + char c1, c2; + for(;;){ + c1 = tolower(*s1); + c2 = tolower(*s2); + if(c1 != c2) + return c1 - c2; + if(c1 == '\0') + return 0; + s1++; + s2++; + } +} + +int +strncmp_ci(const char *s1, const char *s2, int n) +{ + char c1, c2; + while(n--){ + c1 = tolower(*s1); + c2 = tolower(*s2); + if(c1 != c2) + return c1 - c2; + if(c1 == '\0') + return 0; + s1++; + s2++; + } + return 0; +} + +Quat +mult(const Quat &q, const Quat &p) +{ + return makeQuat(q.w*p.w - q.x*p.x - q.y*p.y - q.z*p.z, + q.w*p.x + q.x*p.w + q.y*p.z - q.z*p.y, + q.w*p.y + q.y*p.w + q.z*p.x - q.x*p.z, + q.w*p.z + q.z*p.w + q.x*p.y - q.y*p.x); +} + + +Quat* +Quat::rotate(const V3d *axis, float32 angle, CombineOp op) +{ + Quat rot = rotation(angle, *axis); + switch(op){ + case COMBINEREPLACE: + *this = rot; + break; + case COMBINEPRECONCAT: + *this = mult(*this, rot); + break; + case COMBINEPOSTCONCAT: + *this = mult(rot, *this); + break; + } + return this; +} + +Quat +lerp(const Quat &q, const Quat &p, float32 r) +{ + float32 c; + Quat q1 = q; + c = dot(q1, p); + if(c < 0.0f){ + c = -c; + q1 = negate(q1); + } + return makeQuat(q1.w + r*(p.w - q1.w), + q1.x + r*(p.x - q1.x), + q1.y + r*(p.y - q1.y), + q1.z + r*(p.z - q1.z)); +}; + +Quat +slerp(const Quat &q, const Quat &p, float32 a) +{ + float32 c; + Quat q1 = q; + c = dot(q1, p); + if(c < 0.0f){ + c = -c; + q1 = negate(q1); + } + float32 phi = acosf(c); + if(phi > 0.00001f){ + float32 s = sinf(phi); + return add(scale(q1, sinf((1.0f-a)*phi)/s), + scale(p, sinf(a*phi)/s)); + } + return q1; +} + +// +// V3d +// + +V3d +cross(const V3d &a, const V3d &b) +{ + return makeV3d(a.y*b.z - a.z*b.y, + a.z*b.x - a.x*b.z, + a.x*b.y - a.y*b.x); +} + +void +V3d::transformPoints(V3d *out, const V3d *in, int32 n, const Matrix *m) +{ + int32 i; + V3d tmp; + for(i = 0; i < n; i++){ + tmp.x = in[i].x*m->right.x + in[i].y*m->up.x + in[i].z*m->at.x + m->pos.x; + tmp.y = in[i].x*m->right.y + in[i].y*m->up.y + in[i].z*m->at.y + m->pos.y; + tmp.z = in[i].x*m->right.z + in[i].y*m->up.z + in[i].z*m->at.z + m->pos.z; + out[i] = tmp; + } +} + +void +V3d::transformVectors(V3d *out, const V3d *in, int32 n, const Matrix *m) +{ + int32 i; + V3d tmp; + for(i = 0; i < n; i++){ + tmp.x = in[i].x*m->right.x + in[i].y*m->up.x + in[i].z*m->at.x; + tmp.y = in[i].x*m->right.y + in[i].y*m->up.y + in[i].z*m->at.y; + tmp.z = in[i].x*m->right.z + in[i].y*m->up.z + in[i].z*m->at.z; + out[i] = tmp; + } +} + +// +// RawMatrix +// + +void +RawMatrix::mult(RawMatrix *dst, RawMatrix *src1, RawMatrix *src2) +{ + dst->right.x = src1->right.x*src2->right.x + src1->right.y*src2->up.x + src1->right.z*src2->at.x + src1->rightw*src2->pos.x; + dst->right.y = src1->right.x*src2->right.y + src1->right.y*src2->up.y + src1->right.z*src2->at.y + src1->rightw*src2->pos.y; + dst->right.z = src1->right.x*src2->right.z + src1->right.y*src2->up.z + src1->right.z*src2->at.z + src1->rightw*src2->pos.z; + dst->rightw = src1->right.x*src2->rightw + src1->right.y*src2->upw + src1->right.z*src2->atw + src1->rightw*src2->posw; + dst->up.x = src1->up.x*src2->right.x + src1->up.y*src2->up.x + src1->up.z*src2->at.x + src1->upw*src2->pos.x; + dst->up.y = src1->up.x*src2->right.y + src1->up.y*src2->up.y + src1->up.z*src2->at.y + src1->upw*src2->pos.y; + dst->up.z = src1->up.x*src2->right.z + src1->up.y*src2->up.z + src1->up.z*src2->at.z + src1->upw*src2->pos.z; + dst->upw = src1->up.x*src2->rightw + src1->up.y*src2->upw + src1->up.z*src2->atw + src1->upw*src2->posw; + dst->at.x = src1->at.x*src2->right.x + src1->at.y*src2->up.x + src1->at.z*src2->at.x + src1->atw*src2->pos.x; + dst->at.y = src1->at.x*src2->right.y + src1->at.y*src2->up.y + src1->at.z*src2->at.y + src1->atw*src2->pos.y; + dst->at.z = src1->at.x*src2->right.z + src1->at.y*src2->up.z + src1->at.z*src2->at.z + src1->atw*src2->pos.z; + dst->atw = src1->at.x*src2->rightw + src1->at.y*src2->upw + src1->at.z*src2->atw + src1->atw*src2->posw; + dst->pos.x = src1->pos.x*src2->right.x + src1->pos.y*src2->up.x + src1->pos.z*src2->at.x + src1->posw*src2->pos.x; + dst->pos.y = src1->pos.x*src2->right.y + src1->pos.y*src2->up.y + src1->pos.z*src2->at.y + src1->posw*src2->pos.y; + dst->pos.z = src1->pos.x*src2->right.z + src1->pos.y*src2->up.z + src1->pos.z*src2->at.z + src1->posw*src2->pos.z; + dst->posw = src1->pos.x*src2->rightw + src1->pos.y*src2->upw + src1->pos.z*src2->atw + src1->posw*src2->posw; +} + +void +RawMatrix::transpose(RawMatrix *dst, RawMatrix *src) +{ + dst->right.x = src->right.x; + dst->up.x = src->right.y; + dst->at.x = src->right.z; + dst->pos.x = src->rightw; + dst->right.y = src->up.x; + dst->up.y = src->up.y; + dst->at.y = src->up.z; + dst->pos.y = src->upw; + dst->right.z = src->at.x; + dst->up.z = src->at.y; + dst->at.z = src->at.z; + dst->pos.z = src->atw; + dst->rightw = src->pos.x; + dst->upw = src->pos.y; + dst->atw = src->pos.z; + dst->posw = src->posw; +} + +void +RawMatrix::setIdentity(RawMatrix *dst) +{ + static RawMatrix identity = { + { 1.0f, 0.0f, 0.0f }, 0.0f, + { 0.0f, 1.0f, 0.0f }, 0.0f, + { 0.0f, 0.0f, 1.0f }, 0.0f, + { 0.0f, 0.0f, 0.0f }, 1.0f + }; + *dst = identity; +} + +// +// Matrix +// + +static Matrix::Tolerance matrixDefaultTolerance = { 0.01f, 0.01f, 0.01f }; + +Matrix* +Matrix::create(void) +{ + Matrix *m = (Matrix*)rwMalloc(sizeof(Matrix), MEMDUR_EVENT | ID_MATRIX); + m->setIdentity(); + return m; +} + +void +Matrix::destroy(void) +{ + rwFree(this); +} + +void +Matrix::setIdentity(void) +{ + *this = identMat; +} + +void +Matrix::optimize(Tolerance *tolerance) +{ + bool32 isnormal, isorthogonal, isidentity; + if(tolerance == nil) + tolerance = &matrixDefaultTolerance; + isnormal = normalError() <= tolerance->normal; + isorthogonal = orthogonalError() <= tolerance->orthogonal; + isidentity = isnormal && isorthogonal && identityError() <= tolerance->identity; + if(isnormal) + flags |= TYPENORMAL; + else + flags &= ~TYPENORMAL; + if(isorthogonal) + flags |= TYPEORTHOGONAL; + else + flags &= ~TYPEORTHOGONAL; + if(isidentity) + flags |= IDENTITY; + else + flags &= ~IDENTITY; +} + +Matrix* +Matrix::mult(Matrix *dst, const Matrix *src1, const Matrix *src2) +{ + if(src1->flags & IDENTITY) + *dst = *src2; + else if(src2->flags & IDENTITY) + *dst = *src1; + else{ + mult_(dst, src1, src2); + dst->flags = src1->flags & src2->flags; + } + return dst; +} + +Matrix* +Matrix::invert(Matrix *dst, const Matrix *src) +{ + if(src->flags & IDENTITY) + *dst = *src; + else if((src->flags & TYPEMASK) == TYPEORTHONORMAL) + invertOrthonormal(dst, src); + else + return invertGeneral(dst, src); + return dst; +} + +// transpose the 3x3 submatrix, pos is set to 0 +Matrix* +Matrix::transpose(Matrix *dst, const Matrix *src) +{ + if(src->flags & IDENTITY) + *dst = *src; + dst->right.x = src->right.x; + dst->up.x = src->right.y; + dst->at.x = src->right.z; + dst->right.y = src->up.x; + dst->up.y = src->up.y; + dst->at.y = src->up.z; + dst->right.z = src->at.x; + dst->up.z = src->at.y; + dst->at.z = src->at.z; + dst->pos.x = 0.0; + dst->pos.y = 0.0; + dst->pos.z = 0.0; + return dst; +} + +Matrix* +Matrix::rotate(const V3d *axis, float32 angle, CombineOp op) +{ + Matrix tmp, rot; + makeRotation(&rot, axis, angle); + switch(op){ + case COMBINEREPLACE: + *this = rot; + break; + case COMBINEPRECONCAT: + mult(&tmp, &rot, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, &rot); + *this = tmp; + break; + } + return this; +} + +Matrix* +Matrix::rotate(const Quat &q, CombineOp op) +{ + Matrix tmp, rot; + makeRotation(&rot, q); + switch(op){ + case COMBINEREPLACE: + *this = rot; + break; + case COMBINEPRECONCAT: + mult(&tmp, &rot, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, &rot); + *this = tmp; + break; + } + return this; +} +Matrix* +Matrix::translate(const V3d *translation, CombineOp op) +{ + Matrix tmp; + Matrix trans = identMat; + trans.pos = *translation; + trans.flags &= ~IDENTITY; + switch(op){ + case COMBINEREPLACE: + *this = trans; + break; + case COMBINEPRECONCAT: + mult(&tmp, &trans, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, &trans); + *this = tmp; + break; + } + return this; +} + +Matrix* +Matrix::scale(const V3d *scale, CombineOp op) +{ + Matrix tmp; + Matrix scl = identMat; + scl.right.x = scale->x; + scl.up.y = scale->y; + scl.at.z = scale->z; + scl.flags &= ~IDENTITY; + switch(op){ + case COMBINEREPLACE: + *this = scl; + break; + case COMBINEPRECONCAT: + mult(&tmp, &scl, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, &scl); + *this = tmp; + break; + } + return this; +} + +Matrix* +Matrix::transform(const Matrix *mat, CombineOp op) +{ + Matrix tmp; + switch(op){ + case COMBINEREPLACE: + *this = *mat; + break; + case COMBINEPRECONCAT: + mult(&tmp, mat, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, mat); + *this = tmp; + break; + } + return this; +} + +Quat +Matrix::getRotation(void) +{ + Quat q = { 0.0f, 0.0f, 0.0f, 1.0f }; + float32 tr = right.x + up.y + at.z; + float s; + if(tr > 0.0f){ + s = sqrtf(1.0f + tr) * 2.0f; + q.w = s / 4.0f; + q.x = (up.z - at.y) / s; + q.y = (at.x - right.z) / s; + q.z = (right.y - up.x) / s; + }else if(right.x > up.y && right.x > at.z){ + s = sqrtf(1.0f + right.x - up.y - at.z) * 2.0f; + q.w = (up.z - at.y) / s; + q.x = s / 4.0f; + q.y = (up.x + right.y) / s; + q.z = (at.x + right.z) / s; + }else if(up.y > at.z){ + s = sqrtf(1.0f + up.y - right.x - at.z) * 2.0f; + q.w = (at.x - right.z) / s; + q.x = (up.x + right.y) / s; + q.y = s / 4.0f; + q.z = (at.y + up.z) / s; + }else{ + s = sqrtf(1.0f + at.z - right.x - up.y) * 2.0f; + q.w = (right.y - up.x) / s; + q.x = (at.x + right.z) / s; + q.y = (at.y + up.z) / s; + q.z = s / 4.0f; + } + return q; +} + +void +Matrix::lookAt(const V3d &dir, const V3d &up) +{ + // this->right is really pointing left + this->at = normalize(dir); + this->right = normalize(cross(up, this->at)); + this->up = cross(this->at, this->right); + this->flags = TYPEORTHONORMAL; +} + +/* For a row-major representation, this calculates src1 * src2. + * For column-major src2 * src1. + * i.e. a vector is first xformed by src1, then by src2 + */ +void +Matrix::mult_(Matrix *dst, const Matrix *src1, const Matrix *src2) +{ + dst->right.x = src1->right.x*src2->right.x + src1->right.y*src2->up.x + src1->right.z*src2->at.x; + dst->right.y = src1->right.x*src2->right.y + src1->right.y*src2->up.y + src1->right.z*src2->at.y; + dst->right.z = src1->right.x*src2->right.z + src1->right.y*src2->up.z + src1->right.z*src2->at.z; + dst->up.x = src1->up.x*src2->right.x + src1->up.y*src2->up.x + src1->up.z*src2->at.x; + dst->up.y = src1->up.x*src2->right.y + src1->up.y*src2->up.y + src1->up.z*src2->at.y; + dst->up.z = src1->up.x*src2->right.z + src1->up.y*src2->up.z + src1->up.z*src2->at.z; + dst->at.x = src1->at.x*src2->right.x + src1->at.y*src2->up.x + src1->at.z*src2->at.x; + dst->at.y = src1->at.x*src2->right.y + src1->at.y*src2->up.y + src1->at.z*src2->at.y; + dst->at.z = src1->at.x*src2->right.z + src1->at.y*src2->up.z + src1->at.z*src2->at.z; + dst->pos.x = src1->pos.x*src2->right.x + src1->pos.y*src2->up.x + src1->pos.z*src2->at.x + src2->pos.x; + dst->pos.y = src1->pos.x*src2->right.y + src1->pos.y*src2->up.y + src1->pos.z*src2->at.y + src2->pos.y; + dst->pos.z = src1->pos.x*src2->right.z + src1->pos.y*src2->up.z + src1->pos.z*src2->at.z + src2->pos.z; +} + +void +Matrix::invertOrthonormal(Matrix *dst, const Matrix *src) +{ + dst->right.x = src->right.x; + dst->right.y = src->up.x; + dst->right.z = src->at.x; + dst->up.x = src->right.y; + dst->up.y = src->up.y; + dst->up.z = src->at.y; + dst->at.x = src->right.z; + dst->at.y = src->up.z; + dst->at.z = src->at.z; + dst->pos.x = -(src->pos.x*src->right.x + + src->pos.y*src->right.y + + src->pos.z*src->right.z); + dst->pos.y = -(src->pos.x*src->up.x + + src->pos.y*src->up.y + + src->pos.z*src->up.z); + dst->pos.z = -(src->pos.x*src->at.x + + src->pos.y*src->at.y + + src->pos.z*src->at.z); + dst->flags = TYPEORTHONORMAL; +} + +Matrix* +Matrix::invertGeneral(Matrix *dst, const Matrix *src) +{ + float32 det, invdet; + // calculate a few cofactors + dst->right.x = src->up.y*src->at.z - src->up.z*src->at.y; + dst->right.y = src->at.y*src->right.z - src->at.z*src->right.y; + dst->right.z = src->right.y*src->up.z - src->right.z*src->up.y; + // get the determinant from that + det = src->up.x * dst->right.y + src->at.x * dst->right.z + dst->right.x * src->right.x; + invdet = 1.0; + if(det != 0.0f) + invdet = 1.0f/det; + dst->right.x *= invdet; + dst->right.y *= invdet; + dst->right.z *= invdet; + dst->up.x = invdet * (src->up.z*src->at.x - src->up.x*src->at.z); + dst->up.y = invdet * (src->at.z*src->right.x - src->at.x*src->right.z); + dst->up.z = invdet * (src->right.z*src->up.x - src->right.x*src->up.z); + dst->at.x = invdet * (src->up.x*src->at.y - src->up.y*src->at.x); + dst->at.y = invdet * (src->at.x*src->right.y - src->at.y*src->right.x); + dst->at.z = invdet * (src->right.x*src->up.y - src->right.y*src->up.x); + dst->pos.x = -(src->pos.x*dst->right.x + src->pos.y*dst->up.x + src->pos.z*dst->at.x); + dst->pos.y = -(src->pos.x*dst->right.y + src->pos.y*dst->up.y + src->pos.z*dst->at.y); + dst->pos.z = -(src->pos.x*dst->right.z + src->pos.y*dst->up.z + src->pos.z*dst->at.z); + dst->flags &= ~IDENTITY; + return dst; +} + +void +Matrix::makeRotation(Matrix *dst, const V3d *axis, float32 angle) +{ +// V3d v = normalize(*axis); + float32 len = dot(*axis, *axis); + if(len != 0.0f) len = 1.0f/sqrtf(len); + V3d v = rw::scale(*axis, len); + angle = angle*(float)M_PI/180.0f; + float32 s = sinf(angle); + float32 c = cosf(angle); + float32 t = 1.0f - c; + + dst->right.x = c + v.x*v.x*t; + dst->right.y = v.x*v.y*t + v.z*s; + dst->right.z = v.z*v.x*t - v.y*s; + dst->up.x = v.x*v.y*t - v.z*s; + dst->up.y = c + v.y*v.y*t; + dst->up.z = v.y*v.z*t + v.x*s; + dst->at.x = v.z*v.x*t + v.y*s; + dst->at.y = v.y*v.z*t - v.x*s; + dst->at.z = c + v.z*v.z*t; + dst->pos.x = 0.0; + dst->pos.y = 0.0; + dst->pos.z = 0.0; + dst->flags = TYPEORTHONORMAL; +} + +/* q must be normalized */ +void +Matrix::makeRotation(Matrix *dst, const Quat &q) +{ + float xx = q.x*q.x; + float yy = q.y*q.y; + float zz = q.z*q.z; + float yz = q.y*q.z; + float zx = q.z*q.x; + float xy = q.x*q.y; + float wx = q.w*q.x; + float wy = q.w*q.y; + float wz = q.w*q.z; + + dst->right.x = 1.0f - 2.0f*(yy + zz); + dst->right.y = 2.0f*(xy + wz); + dst->right.z = 2.0f*(zx - wy); + dst->up.x = 2.0f*(xy - wz); + dst->up.y = 1.0f - 2.0f*(xx + zz); + dst->up.z = 2.0f*(yz + wx); + dst->at.x = 2.0f*(zx + wy); + dst->at.y = 2.0f*(yz - wx); + dst->at.z = 1.0f - 2.0f*(xx + yy); + dst->pos.x = 0.0; + dst->pos.y = 0.0; + dst->pos.z = 0.0; + dst->flags = TYPEORTHONORMAL; +} + +float32 +Matrix::normalError(void) +{ + float32 x, y, z; + x = dot(right, right) - 1.0f; + y = dot(up, up) - 1.0f; + z = dot(at, at) - 1.0f; + return x*x + y*y + z*z; +} + +float32 +Matrix::orthogonalError(void) +{ + float32 x, y, z; + x = dot(at, up); + y = dot(at, right); + z = dot(up, right); + return x*x + y*y + z*z; +} + +float32 +Matrix::identityError(void) +{ + V3d r = { right.x-1.0f, right.y, right.z }; + V3d u = { up.x, up.y-1.0f, up.z }; + V3d a = { at.x, at.y, at.z-1.0f }; + return dot(r,r) + dot(u,u) + dot(a,a) + dot(pos,pos); +} + +void +correctPathCase(char *filename) +{ +#ifdef __unix__ + DIR *direct; + struct dirent *dirent; + + char *dir, *arg; + char copy[1024], sofar[1024] = "."; + strncpy(copy, filename, 1024); + arg = copy; + // hack for absolute paths + if(filename[0] == '/'){ + sofar[0] = '/'; + sofar[1] = '/'; + sofar[2] = '\0'; + arg++; + } + while((dir = strtok(arg, PSEP_S))){ + arg = nil; + if(direct = opendir(sofar), dir == nil) + return; + while(dirent = readdir(direct), dirent != nil) + if(strncmp_ci(dirent->d_name, dir, 1024) == 0){ + strncat(sofar, PSEP_S, 1024); + strncat(sofar, dirent->d_name, 1024); + break; + } + closedir(direct); + if(dirent == nil) + return; + } + strcpy(filename, sofar+2); +#endif +} + +void +makePath(char *filename) +{ + size_t len = strlen(filename); + for(size_t i = 0; i < len; i++) + if(filename[i] == '/' || filename[i] == '\\') + filename[i] = PSEP_C; +#ifdef __unix__ + correctPathCase(filename); +#endif +} + +void +memNative32_func(void *data, uint32 size) +{ + uint8 *bytes = (uint8*)data; + uint32 *words = (uint32*)data; + size >>= 2; + while(size--){ + *words++ = (uint32)bytes[0] | (uint32)bytes[1]<<8 | + (uint32)bytes[2]<<16 | (uint32)bytes[3]<<24; + bytes += 4; + } +} + +void +memNative16_func(void *data, uint32 size) +{ + uint8 *bytes = (uint8*)data; + uint16 *words = (uint16*)data; + size >>= 1; + while(size--){ + *words++ = (uint16)bytes[0] | (uint16)bytes[1]<<8; + bytes += 2; + } +} + +void +memLittle32_func(void *data, uint32 size) +{ + uint32 w; + uint8 *bytes = (uint8*)data; + uint32 *words = (uint32*)data; + size >>= 2; + while(size--){ + w = *words++; + *bytes++ = w; + *bytes++ = w >> 8; + *bytes++ = w >> 16; + *bytes++ = w >> 24; + } +} + +void +memLittle16_func(void *data, uint32 size) +{ + uint16 w; + uint8 *bytes = (uint8*)data; + uint16 *words = (uint16*)data; + size >>= 1; + while(size--){ + w = *words++; + *bytes++ = (uint8)w; + *bytes++ = w >> 8; + } +} + +uint32 +Stream::write32(const void *data, uint32 length) +{ +#ifdef BIGENDIAN + uint8 *src = (uint8*)data; + uint32 buf[256]; + int32 n, len; + for(len = length >>= 2; len > 0; len -= 256){ + n = len < 256 ? len : 256; + memcpy(buf, src, n*4); + memLittle32(buf, n*4); + write8(buf, n*4); + src += n*4; + } + return length; +#else + return write8(data, length); +#endif +} + +uint32 +Stream::write16(const void *data, uint32 length) +{ +#ifdef BIGENDIAN + uint8 *src = (uint8*)data; + uint16 buf[256]; + int32 n, len; + for(len = length >>= 1; len > 0; len -= 256){ + n = len < 256 ? len : 256; + memcpy(buf, src, n*2); + memLittle16(buf, n*2); + write8(buf, n*2); + src += n*2; + } + return length; +#else + return write8(data, length); +#endif +} + +uint32 +Stream::read32(void *data, uint32 length) +{ + uint32 ret; + ret = read8(data, length); + memNative32(data, length); + return ret; +} + +uint32 +Stream::read16(void *data, uint32 length) +{ + uint32 ret; + ret = read8(data, length); + memNative16(data, length); + return ret; +} + +int32 +Stream::writeI8(int8 val) +{ + return write8(&val, sizeof(int8)); +} + +int32 +Stream::writeU8(uint8 val) +{ + return write8(&val, sizeof(uint8)); +} + +int32 +Stream::writeI16(int16 val) +{ + return write16(&val, sizeof(int16)); +} + +int32 +Stream::writeU16(uint16 val) +{ + return write16(&val, sizeof(uint16)); +} + +int32 +Stream::writeI32(int32 val) +{ + return write32(&val, sizeof(int32)); +} + +int32 +Stream::writeU32(uint32 val) +{ + return write32(&val, sizeof(uint32)); +} + +int32 +Stream::writeF32(float32 val) +{ + return write32(&val, sizeof(float32)); +} + +int8 +Stream::readI8(void) +{ + int8 tmp; + read8(&tmp, sizeof(int8)); + return tmp; +} + +uint8 +Stream::readU8(void) +{ + uint8 tmp; + read8(&tmp, sizeof(uint8)); + return tmp; +} + +int16 +Stream::readI16(void) +{ + int16 tmp; + read16(&tmp, sizeof(int16)); + return tmp; +} + +uint16 +Stream::readU16(void) +{ + uint16 tmp; + read16(&tmp, sizeof(uint16)); + return tmp; +} + +int32 +Stream::readI32(void) +{ + int32 tmp; + read32(&tmp, sizeof(int32)); + return tmp; +} + +uint32 +Stream::readU32(void) +{ + uint32 tmp; + read32(&tmp, sizeof(uint32)); + return tmp; +} + +float32 +Stream::readF32(void) +{ + float32 tmp; + read32(&tmp, sizeof(float32)); + return tmp; +} + + + +void +StreamMemory::close(void) +{ +} + +uint32 +StreamMemory::write8(const void *data, uint32 len) +{ + if(this->eof()) + return 0; + uint32 l = len; + if(this->position+l > this->length){ + if(this->position+l > this->capacity) + l = this->capacity-this->position; + this->length = this->position+l; + } + memcpy(&this->data[this->position], data, l); + this->position += l; + if(len != l) + this->position = S_EOF; + return l; +} + +uint32 +StreamMemory::read8(void *data, uint32 len) +{ + if(this->eof()) + return 0; + uint32 l = len; + if(this->position+l > this->length) + l = this->length-this->position; + memcpy(data, &this->data[this->position], l); + this->position += l; + if(len != l) + this->position = S_EOF; + return l; +} + +void +StreamMemory::seek(int32 offset, int32 whence) +{ + if(whence == 0) + this->position = offset; + else if(whence == 1) + this->position += offset; + else + this->position = this->length-offset; + if(this->position > this->length){ + // TODO: ideally this would depend on the mode + if(this->position > this->capacity) + this->position = S_EOF; + else + this->length = this->position; + } +} + +uint32 +StreamMemory::tell(void) +{ + return this->position; +} + +bool +StreamMemory::eof(void) +{ + return this->position == S_EOF; +} + +StreamMemory* +StreamMemory::open(uint8 *data, uint32 length, uint32 capacity) +{ + this->data = data; + this->capacity = capacity; + this->length = length; + if(this->capacity < this->length) + this->capacity = this->length; + this->position = 0; + return this; +} + +uint32 +StreamMemory::getLength(void) +{ + return this->length; +} + + +StreamFile* +StreamFile::open(const char *path, const char *mode) +{ + assert(this->file == nil); + this->file = fopen(path, mode); + if(this->file == nil){ + RWERROR((ERR_FILE, path)); + return nil; + } + return this; +} + +void +StreamFile::close(void) +{ + assert(this->file); + fclose(this->file); + this->file = nil; +} + +uint32 +StreamFile::write8(const void *data, uint32 length) +{ + return (uint32)fwrite(data, 1, length, this->file); +} + +uint32 +StreamFile::read8(void *data, uint32 length) +{ + return (uint32)fread(data, 1, length, this->file); +} + +void +StreamFile::seek(int32 offset, int32 whence) +{ + fseek(this->file, offset, whence); +} + +uint32 +StreamFile::tell(void) +{ + return ftell(this->file); +} + +bool +StreamFile::eof(void) +{ + return ( feof(this->file) != 0 ); +} + +bool +writeChunkHeader(Stream *s, int32 type, int32 size) +{ + struct { + int32 type, size; + uint32 id; + } buf = { type, size, libraryIDPack(version, build) }; + s->write32(&buf, 12); + return true; +} + +bool +readChunkHeaderInfo(Stream *s, ChunkHeaderInfo *header) +{ + struct { + int32 type, size; + uint32 id; + } buf; + s->read32(&buf, 12); + if(s->eof()) + return false; + assert(header != nil); + header->type = buf.type; + header->length = buf.size; + header->version = libraryIDUnpackVersion(buf.id); + header->build = libraryIDUnpackBuild(buf.id); + return true; +} + +bool +findChunk(Stream *s, uint32 type, uint32 *length, uint32 *version) +{ + ChunkHeaderInfo header; + while(readChunkHeaderInfo(s, &header)){ + if(header.type == ID_NAOBJECT) + return false; + if(header.type == type){ + if(length) + *length = header.length; + if(version) + *version = header.version; + return true; + } + s->seek(header.length); + } + return false; +} + +int32 +findPointer(void *p, void **list, int32 num) +{ + int i; + for(i = 0; i < num; i++) + if(list[i] == p) + return i; + return -1; +} + +uint8* +getFileContents(const char *name, uint32 *len) +{ + FILE *cf = fopen(name, "rb"); + if(cf == nil) + return nil; + fseek(cf, 0, SEEK_END); + *len = ftell(cf); + fseek(cf, 0, SEEK_SET); + uint8 *data = rwNewT(uint8, *len, MEMDUR_EVENT); + fread(data, 1, *len, cf); + fclose(cf); + return data; +} + +} diff --git a/vendor/librw/src/base.err b/vendor/librw/src/base.err new file mode 100644 index 0000000..2d413e8 --- /dev/null +++ b/vendor/librw/src/base.err @@ -0,0 +1,24 @@ +ECODE(ERR_GENERAL, + "Error: %s"), +ECODE(ERR_ALLOC, + "Couldn't allocate 0x%X bytes"), +ECODE(ERR_FILE, + "Couldn't open file %s"), +ECODE(ERR_CHUNK, + "Couldn't find chunk %s"), +ECODE(ERR_VERSION, + "Unsupported version %X"), +ECODE(ERR_PLATFORM, + "Unsupported platform %d"), +ECODE(ERR_ENGINEINIT, + "Engine could not be initialized"), +ECODE(ERR_ENGINEOPEN, + "Engine could not be opened"), +ECODE(ERR_ENGINESTART, + "Engine could not be started"), +ECODE(ERR_INVRASTER, + "Invalid raster format"), +ECODE(ERR_NOTEXTURE, + "Could not create texture"), +ECODE(ERR_FORMAT_UNSUPPORTED, + "Unsupported raster format") diff --git a/vendor/librw/src/bmp.cpp b/vendor/librw/src/bmp.cpp new file mode 100644 index 0000000..f69e58d --- /dev/null +++ b/vendor/librw/src/bmp.cpp @@ -0,0 +1,284 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 0 + +namespace rw { + +// NB: this has padding and cannot be streamed directly! +struct BMPheader +{ + uint16 magic; + uint32 size; + uint16 reserved[2]; + uint32 offset; + + bool32 read(Stream *stream); + void write(Stream *stream); +}; + +// This one is aligned and can be streamed directly +struct DIBheader +{ + uint32 headerSize; + int32 width; + int32 height; + int16 numPlanes; + int16 depth; + uint32 compression; + uint32 imgSize; + int32 hres; + int32 vres; + int32 paletteLen; + int32 numImportant; + // end of 40 btyes + + uint32 rmask, gmask, bmask, amask; +}; + +bool32 +BMPheader::read(Stream *stream) +{ + magic = stream->readU16(); + size = stream->readU32(); + reserved[0] = stream->readU16(); + reserved[1] = stream->readU16(); + offset = stream->readU32(); + return magic == 0x4D42; +} + +void +BMPheader::write(Stream *stream) +{ + + stream->writeU16(magic); + stream->writeU32(size); + stream->writeU16(reserved[0]); + stream->writeU16(reserved[1]); + stream->writeU32(offset); +} + +Image* +readBMP(const char *filename) +{ + ASSERTLITTLE; + Image *image; + uint32 length; + uint8 *data; + StreamMemory file; + int i, x, y; + + bool32 noalpha; + int pad; + + data = getFileContents(filename, &length); + if(data == nil) + return nil; + file.open(data, length); + + /* read headers */ + BMPheader bmp; + DIBheader dib; + if(!bmp.read(&file)) + goto lose; + file.read8(&dib, sizeof(dib)); + file.seek(dib.headerSize-sizeof(dib)); // skip the part of the header we're ignoring + if(dib.headerSize <= 16){ + dib.compression = 0; + dib.paletteLen = 0; + } + + noalpha = true; + + // Recognize 32 bit formats + if(dib.compression == 3){ + if(dib.rmask != 0xFF0000 || + dib.gmask != 0x00FF00 || + dib.bmask != 0x0000FF) + goto lose; + dib.compression = 0; + if(dib.headerSize > 52 && dib.amask == 0xFF000000) + noalpha = false; + } + + if(dib.compression != 0) + goto lose; + + image = Image::create(dib.width, dib.height, dib.depth); + image->allocate(); + + + if(image->palette){ + int len = 1<palette; + for(i = 0; i < len; i++){ + color[i][2] = file.readU8(); // blue + color[i][1] = file.readU8(); // green + color[i][0] = file.readU8(); // red + color[i][3] = file.readU8(); // alpha + if(noalpha) + color[i][3] = 0xFF; + } + } + + file.seek(bmp.offset, 0); + + pad = image->width*image->bpp % 4; + + uint8 *px, *line; + line = image->pixels + (image->height-1)*image->stride; + for(y = 0; y < image->height; y++){ + px = line; + for(x = 0; x < image->width; x++){ + switch(image->depth){ + case 4: + i = file.readU8();; + px[x+0] = (i>>4)&0xF; + px[x+1] = i&0xF; + x++; + break; + + case 8: + px[x] = file.readU8(); + break; + + case 16: + // TODO: what format is this even? and what does Image expect? + px[x*2 + 0] = file.readU8(); + px[x*2 + 1] = file.readU8(); + break; + + case 24: + px[x*3 + 2] = file.readU8(); + px[x*3 + 1] = file.readU8(); + px[x*3 + 0] = file.readU8(); + break; + + case 32: + px[x*4 + 2] = file.readU8(); + px[x*4 + 1] = file.readU8(); + px[x*4 + 0] = file.readU8(); + px[x*4 + 3] = file.readU8(); + if(noalpha) + px[x*4 + 3] = 0xFF; + break; + + default: + goto lose; + } + } + + line -= image->stride; + file.seek(pad); + } + + + file.close(); + rwFree(data); + return image; + +lose: + file.close(); + rwFree(data); + return nil; +} + +/* can't write alpha */ +void +writeBMP(Image *image, const char *filename) +{ + ASSERTLITTLE; + uint8 *p; + StreamFile file; + if(!file.open(filename, "wb")){ + RWERROR((ERR_FILE, filename)); + return; + } + + int32 pallen = image->depth > 8 ? 0 : + image->depth == 4 ? 16 : 256; + int32 stride = image->width*image->depth/8; + int32 depth = image->depth == 32 ? 24 : image->depth; + stride = stride+3 & ~3; + + // File headers + BMPheader bmp; + bmp.magic = 0x4D42; // BM + bmp.size = 0x36 + 4*pallen + image->height*stride; + bmp.reserved[0] = 0; + bmp.reserved[1] = 0; + bmp.offset = 0x36 + 4*pallen; + bmp.write(&file); + + DIBheader dib; + dib.headerSize = 0x28; + dib.width = image->width; + dib.height = image->height; + dib.numPlanes = 1; + dib.depth = depth; + dib.compression = 0; + dib.imgSize = 0; + dib.hres = 2835; // 72dpi + dib.vres = 2835; // 72dpi + dib.paletteLen = 0; + dib.numImportant = 0; + file.write8(&dib, dib.headerSize); + + for(int i = 0; i < pallen; i++){ + file.writeU8(image->palette[i*4+2]); + file.writeU8(image->palette[i*4+1]); + file.writeU8(image->palette[i*4+0]); + file.writeU8(0xFF); + } + + uint8 *line = image->pixels + (image->height-1)*image->stride; + int32 n; + for(int y = 0; y < image->height; y++){ + p = line; + for(int x = 0; x < image->width; x++){ + switch(image->depth){ + case 4: + file.writeU8((p[0]&0xF)<<4 | (p[1]&0xF)); + p += 2; + x++; + break; + case 8: + file.writeU8(*p++); + break; + case 16: + file.writeU8(p[0]); + file.writeU8(p[1]); + p += 2; + break; + case 24: + file.writeU8(p[2]); + file.writeU8(p[1]); + file.writeU8(p[0]); + p += 3; + break; + case 32: + file.writeU8(p[2]); + file.writeU8(p[1]); + file.writeU8(p[0]); + p += 4; + } + } + n = (p-line) % 4; + while(n--) + file.writeU8(0); + line -= image->stride; + } + + file.close(); +} + +} diff --git a/vendor/librw/src/camera.cpp b/vendor/librw/src/camera.cpp new file mode 100644 index 0000000..4a87ed3 --- /dev/null +++ b/vendor/librw/src/camera.cpp @@ -0,0 +1,524 @@ +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_CAMERA + +namespace rw { + +int32 Camera::numAllocated; + +PluginList Camera::s_plglist(sizeof(Camera)); + +void +defaultBeginUpdateCB(Camera *cam) +{ + engine->currentCamera = cam; + Frame::syncDirty(); + engine->device.beginUpdate(cam); +} + +void +defaultEndUpdateCB(Camera *cam) +{ + engine->device.endUpdate(cam); +} + +static void +buildPlanes(Camera *cam) +{ + V3d *c = cam->frustumCorners; + FrustumPlane *p = cam->frustumPlanes; + V3d v51 = sub(c[1], c[5]); + V3d v73 = sub(c[3], c[7]); + + /* Far plane */ + p[0].plane.normal = cam->getFrame()->getLTM()->at; + p[0].plane.distance = dot(p[0].plane.normal, c[4]); + p[0].closestX = p[0].plane.normal.x < 0.0f ? 0 : 1; + p[0].closestY = p[0].plane.normal.y < 0.0f ? 0 : 1; + p[0].closestZ = p[0].plane.normal.z < 0.0f ? 0 : 1; + + /* Near plane */ + p[1].plane.normal = neg(p[0].plane.normal); + p[1].plane.distance = dot(p[1].plane.normal, c[0]); + p[1].closestX = p[1].plane.normal.x < 0.0f ? 0 : 1; + p[1].closestY = p[1].plane.normal.y < 0.0f ? 0 : 1; + p[1].closestZ = p[1].plane.normal.z < 0.0f ? 0 : 1; + + /* Right plane */ + p[2].plane.normal = normalize(cross(v51, + sub(c[6], c[5]))); + p[2].plane.distance = dot(p[2].plane.normal, c[1]); + p[2].closestX = p[2].plane.normal.x < 0.0f ? 0 : 1; + p[2].closestY = p[2].plane.normal.y < 0.0f ? 0 : 1; + p[2].closestZ = p[2].plane.normal.z < 0.0f ? 0 : 1; + + /* Top plane */ + p[3].plane.normal = normalize(cross(sub(c[4], c[5]), + v51)); + p[3].plane.distance = dot(p[3].plane.normal, c[1]); + p[3].closestX = p[3].plane.normal.x < 0.0f ? 0 : 1; + p[3].closestY = p[3].plane.normal.y < 0.0f ? 0 : 1; + p[3].closestZ = p[3].plane.normal.z < 0.0f ? 0 : 1; + + /* Left plane */ + p[4].plane.normal = normalize(cross(v73, + sub(c[4], c[7]))); + p[4].plane.distance = dot(p[4].plane.normal, c[3]); + p[4].closestX = p[4].plane.normal.x < 0.0f ? 0 : 1; + p[4].closestY = p[4].plane.normal.y < 0.0f ? 0 : 1; + p[4].closestZ = p[4].plane.normal.z < 0.0f ? 0 : 1; + + /* Bottom plane */ + p[5].plane.normal = normalize(cross(sub(c[6], c[7]), + v73)); + p[5].plane.distance = dot(p[5].plane.normal, c[3]); + p[5].closestX = p[5].plane.normal.x < 0.0f ? 0 : 1; + p[5].closestY = p[5].plane.normal.y < 0.0f ? 0 : 1; + p[5].closestZ = p[5].plane.normal.z < 0.0f ? 0 : 1; +} + +static void +buildClipPersp(Camera *cam) +{ + Matrix *ltm = cam->getFrame()->getLTM(); + + /* First we calculate the 4 points on the view window. */ + V3d up = scale(ltm->up, cam->viewWindow.y); + V3d left = scale(ltm->right, cam->viewWindow.x); + V3d *c = cam->frustumCorners; + c[0] = add(add(ltm->at, up), left); // top left + c[1] = sub(add(ltm->at, up), left); // top right + c[2] = sub(sub(ltm->at, up), left); // bottom right + c[3] = add(sub(ltm->at, up), left); // bottom left + + /* Now Calculate near and far corners. */ + V3d off = sub(scale(ltm->up, cam->viewOffset.y), + scale(ltm->right, cam->viewOffset.x)); + for(int32 i = 0; i < 4; i++){ + V3d corner = sub(cam->frustumCorners[i], off); + V3d pos = add(ltm->pos, off); + c[i] = add(scale(corner, cam->nearPlane), pos); + c[i+4] = add(scale(corner, cam->farPlane), pos); + } + + buildPlanes(cam); +} + +static void +buildClipParallel(Camera *cam) +{ + Matrix *ltm = cam->getFrame()->getLTM(); + float32 nearoffx = -(1.0f - cam->nearPlane)*cam->viewOffset.x; + float32 nearoffy = (1.0f - cam->nearPlane)*cam->viewOffset.y; + float32 faroffx = -(1.0f - cam->farPlane)*cam->viewOffset.x; + float32 faroffy = (1.0f - cam->farPlane)*cam->viewOffset.y; + + V3d *c = cam->frustumCorners; + c[0].x = nearoffx + cam->viewWindow.x; + c[0].y = nearoffy + cam->viewWindow.y; + c[0].z = cam->nearPlane; + + c[1].x = nearoffx - cam->viewWindow.x; + c[1].y = nearoffy + cam->viewWindow.y; + c[1].z = cam->nearPlane; + + c[2].x = nearoffx - cam->viewWindow.x; + c[2].y = nearoffy - cam->viewWindow.y; + c[2].z = cam->nearPlane; + + c[3].x = nearoffx + cam->viewWindow.x; + c[3].y = nearoffy - cam->viewWindow.y; + c[3].z = cam->nearPlane; + + c[4].x = faroffx + cam->viewWindow.x; + c[4].y = faroffy + cam->viewWindow.y; + c[4].z = cam->farPlane; + + c[5].x = faroffx - cam->viewWindow.x; + c[5].y = faroffy + cam->viewWindow.y; + c[5].z = cam->farPlane; + + c[6].x = faroffx - cam->viewWindow.x; + c[6].y = faroffy - cam->viewWindow.y; + c[6].z = cam->farPlane; + + c[7].x = faroffx + cam->viewWindow.x; + c[7].y = faroffy - cam->viewWindow.y; + c[7].z = cam->farPlane; + + V3d::transformPoints(c, c, 8, ltm); + + buildPlanes(cam); +} + +static void +cameraSync(ObjectWithFrame *obj) +{ + /* + * RW projection matrix looks like this: + * (cf. Camera View Matrix white paper) + * w = viewWindow width + * h = viewWindow height + * o = view offset + * + * perspective: + * 1/2w 0 ox/2w + 1/2 -ox/2w + * 0 -1/2h -oy/2h + 1/2 oy/2h + * 0 0 1 0 + * 0 0 1 0 + * + * parallel: + * 1/2w 0 ox/2w -ox/2w + 1/2 + * 0 -1/2h -oy/2h oy/2h + 1/2 + * 0 0 1 0 + * 0 0 0 1 + * + * The view matrix transforms from world to clip space, it is however + * not used for OpenGL or D3D since transformation to camera space + * and to clip space are handled by separate matrices there. + * On these platforms the two matrices are built in the platform's + * beginUpdate function. + * On the PS2 the z- and w-rows are the same and the + * 1/2 translation/shear is removed again on the VU1 by + * subtracting the w-row/2 from the x- and y-rows. + * + * perspective: + * 1/2w 0 ox/2w -ox/2w + * 0 -1/2h -oy/2h oy/2h + * 0 0 1 0 + * 0 0 1 0 + * + * parallel: + * 1/2w 0 ox/2w -ox/2w + * 0 -1/2h -oy/2h oy/2h + * 0 0 1 0 + * 0 0 0 1 + * + * RW builds this matrix directly without using explicit + * inversion and matrix multiplication. + */ + + Camera *cam = (Camera*)obj; + Matrix inv, proj; + Matrix::invertOrthonormal(&inv, cam->getFrame()->getLTM()); + + inv.right.x = -inv.right.x; + inv.up.x = -inv.up.x; + inv.at.x = -inv.at.x; + inv.pos.x = -inv.pos.x; + + float32 xscl = 1.0f/(2.0f*cam->viewWindow.x); + float32 yscl = 1.0f/(2.0f*cam->viewWindow.y); + + proj.flags = 0; + proj.right.x = xscl; + proj.right.y = 0.0f; + proj.right.z = 0.0f; + + proj.up.x = 0.0f; + proj.up.y = -yscl; + proj.up.z = 0.0f; + + if(cam->projection == Camera::PERSPECTIVE){ + proj.pos.x = -cam->viewOffset.x*xscl; + proj.pos.y = cam->viewOffset.y*yscl; + proj.pos.z = 0.0f; + + proj.at.x = -proj.pos.x + 0.5f; + proj.at.y = -proj.pos.y + 0.5f; + proj.at.z = 1.0f; + proj.optimize(); + Matrix::mult(&cam->viewMatrix, &inv, &proj); + buildClipPersp(cam); + }else{ + proj.at.x = cam->viewOffset.x*xscl; + proj.at.y = -cam->viewOffset.y*yscl; + proj.at.z = 1.0f; + + proj.pos.x = -proj.at.x + 0.5f; + proj.pos.y = -proj.at.y + 0.5f; + proj.pos.z = 0.0f; + proj.optimize(); + Matrix::mult(&cam->viewMatrix, &inv, &proj); + buildClipParallel(cam); + } + cam->frustumBoundBox.calculate(cam->frustumCorners, 8); +} + +void +worldBeginUpdateCB(Camera *cam) +{ + engine->currentWorld = cam->world; + cam->originalBeginUpdate(cam); +} + +void +worldEndUpdateCB(Camera *cam) +{ + cam->originalEndUpdate(cam); +} + +static void +worldCameraSync(ObjectWithFrame *obj) +{ + Camera *camera = (Camera*)obj; + camera->originalSync(obj); +} + +Camera* +Camera::create(void) +{ + Camera *cam = (Camera*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_CAMERA); + if(cam == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + cam->object.object.init(Camera::ID, 0); + cam->object.syncCB = cameraSync; + cam->beginUpdateCB = defaultBeginUpdateCB; + cam->endUpdateCB = defaultEndUpdateCB; + cam->viewWindow.set(1.0f, 1.0f); + cam->viewOffset.set(0.0f, 0.0f); + cam->nearPlane = 0.05f; + cam->farPlane = 10.0f; + cam->fogPlane = 5.0f; + cam->projection = Camera::PERSPECTIVE; + + cam->frameBuffer = nil; + cam->zBuffer = nil; + + // clump extension + cam->clump = nil; + cam->inClump.init(); + + // world extension + cam->world = nil; + cam->originalSync = cam->object.syncCB; + cam->originalBeginUpdate = cam->beginUpdateCB; + cam->originalEndUpdate = cam->endUpdateCB; + cam->object.syncCB = worldCameraSync; + cam->beginUpdateCB = worldBeginUpdateCB; + cam->endUpdateCB = worldEndUpdateCB; + + s_plglist.construct(cam); + return cam; +} + +Camera* +Camera::clone(void) +{ + Camera *cam = Camera::create(); + if(cam == nil) + return nil; + cam->object.object.copy(&this->object.object); + cam->setFrame(this->getFrame()); + cam->viewWindow = this->viewWindow; + cam->viewOffset = this->viewOffset; + cam->nearPlane = this->nearPlane; + cam->farPlane = this->farPlane; + cam->fogPlane = this->fogPlane; + cam->projection = this->projection; + + cam->frameBuffer = this->frameBuffer; + cam->zBuffer = this->zBuffer; + + if(this->world) + this->world->addCamera(cam); + + s_plglist.copy(cam, this); + return cam; +} + +void +Camera::destroy(void) +{ + s_plglist.destruct(this); + assert(this->clump == nil); + assert(this->world == nil); + this->setFrame(nil); + rwFree(this); + numAllocated--; +} + +void +Camera::clear(RGBA *col, uint32 mode) +{ + engine->device.clearCamera(this, col, mode); +} + +void +Camera::showRaster(uint32 flags) +{ + this->frameBuffer->show(flags); +} + +void +calczShiftScale(Camera *cam) +{ + float32 n = cam->nearPlane; + float32 f = cam->farPlane; + float32 N = engine->device.zNear; + float32 F = engine->device.zFar; + // RW does this + N += (F - N)/10000.0f; + F -= (F - N)/10000.0f; + if(cam->projection == Camera::PERSPECTIVE){ + cam->zScale = (N - F)*n*f/(f - n); + cam->zShift = (F*f - N*n)/(f - n); + }else{ + cam->zScale = (F - N)/(f -n); + cam->zShift = (N*f - F*n)/(f - n); + } +} + +void +Camera::setNearPlane(float32 near) +{ + this->nearPlane = near; + calczShiftScale(this); + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +void +Camera::setFarPlane(float32 far) +{ + this->farPlane = far; + calczShiftScale(this); + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +void +Camera::setViewWindow(const V2d *window) +{ + this->viewWindow = *window; + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +void +Camera::setViewOffset(const V2d *offset) +{ + this->viewOffset = *offset; + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +void +Camera::setProjection(int32 proj) +{ + this->projection = proj; + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +int32 +Camera::frustumTestSphere(const Sphere *s) const +{ + int32 res = SPHEREINSIDE; + const FrustumPlane *p = this->frustumPlanes; + for(int32 i = 0; i < 6; i++){ + float32 dist = dot(p->plane.normal, s->center) - p->plane.distance; + if(s->radius < dist) + return SPHEREOUTSIDE; + if(s->radius > -dist) + res = SPHEREBOUNDARY; + p++; + } + return res; +} + +struct CameraChunkData +{ + V2d viewWindow; + V2d viewOffset; + float32 nearPlane, farPlane; + float32 fogPlane; + int32 projection; +}; + +Camera* +Camera::streamRead(Stream *stream) +{ + CameraChunkData buf; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(&buf, sizeof(CameraChunkData)); + Camera *cam = Camera::create(); + cam->viewWindow = buf.viewWindow; + cam->viewOffset = buf.viewOffset; + cam->nearPlane = buf.nearPlane; + cam->farPlane = buf.farPlane; + cam->fogPlane = buf.fogPlane; + cam->projection = buf.projection; + if(s_plglist.streamRead(stream, cam)) + return cam; + cam->destroy(); + return nil; +} + +bool +Camera::streamWrite(Stream *stream) +{ + CameraChunkData buf; + writeChunkHeader(stream, ID_CAMERA, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, sizeof(CameraChunkData)); + buf.viewWindow = this->viewWindow; + buf.viewOffset = this->viewOffset; + buf.nearPlane = this->nearPlane; + buf.farPlane = this->farPlane; + buf.fogPlane = this->fogPlane; + buf.projection = this->projection; + stream->write32(&buf, sizeof(CameraChunkData)); + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Camera::streamGetSize(void) +{ + return 12 + sizeof(CameraChunkData) + 12 + + s_plglist.streamGetSize(this); +} + +// Assumes horizontal FOV for 4:3, but we convert to vertical FOV +void +Camera::setFOV(float32 hfov, float32 ratio) +{ + V2d v; + float w, h; + + w = (float)this->frameBuffer->width; + h = (float)this->frameBuffer->height; + if(w < 1 || h < 1){ + w = 1; + h = 1; + } + hfov = hfov*3.14159f/360.0f; // deg to rad and halved + + float ar1 = 4.0f/3.0f; + float ar2 = w/h; + float vfov = atanf(tanf(hfov/2) / ar1) *2; + hfov = atanf(tanf(vfov/2) * ar2) *2; + + float32 a = tanf(hfov); + v.set(a, a/ratio); + this->setViewWindow(&v); + v.set(0.0f, 0.0f); + this->setViewOffset(&v); +} + +} diff --git a/vendor/librw/src/charset.cpp b/vendor/librw/src/charset.cpp new file mode 100644 index 0000000..5188b80 --- /dev/null +++ b/vendor/librw/src/charset.cpp @@ -0,0 +1,244 @@ +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwrender.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwcharset.h" + +#include "ps2/rwps2.h" +#include "d3d/rwd3d.h" +#include "gl/rwgl3.h" + + +#define PLUGIN_ID 1000 // TODO: find a better ID + +#ifndef RW_NULL + +#ifdef RWHALFPIXEL +#define HALFPX (0.5f) +#else +#define HALFPX (0.0f) +#endif + +namespace rw { + +const uint8 fontbits[256*256] = { +#include "vgafont.inc" +}; + +#define NUMCHARS 100 +static uint16 *indices; +static RWDEVICE::Im2DVertex *vertices; +static int32 numChars; +static Raster *lastRaster; + +bool32 +Charset::open(void) +{ + if(indices || vertices) + return 0; + numChars = 0; + lastRaster = nil; + indices = rwNewT(uint16, NUMCHARS*6, MEMDUR_EVENT); + vertices = rwNewT(RWDEVICE::Im2DVertex, NUMCHARS*4, MEMDUR_EVENT); + if(indices == nil || vertices == nil){ + close(); + return 0; + } + return 1; +} + +void +Charset::close(void) +{ + rwFree(indices); + indices = nil; + rwFree(vertices); + vertices = nil; +} + +Charset* +Charset::create(const RGBA *foreground, const RGBA *background) +{ + Charset *charset = (Charset*)rwMalloc(sizeof(Charset), MEMDUR_EVENT); + if(charset == nil){ + RWERROR((ERR_ALLOC, sizeof(Charset))); + return nil; + } + charset->raster = nil; + if(charset->setColors(foreground, background) == nil){ + charset->destroy(); + return nil; + } + charset->desc.count = 256; + charset->desc.tileWidth = 28; + charset->desc.tileHeight = 10; + charset->desc.width = 9; + charset->desc.height = 16; + charset->desc.width_internal = 9; + charset->desc.height_internal = 16; + return charset; +} + +void +Charset::destroy(void) +{ + if(raster) + raster->destroy(); + rwFree(this); +} + +Charset* +Charset::setColors(const RGBA *foreground, const RGBA *background) +{ + Image *img = Image::create(256, 256, 8); + if(img == nil) + return nil; + img->pixels = (uint8*)fontbits; + img->stride = 256; + img->allocate(); + img->palette[0] = background->red; + img->palette[1] = background->green; + img->palette[2] = background->blue; + img->palette[3] = background->alpha; + img->palette[4] = foreground->red; + img->palette[5] = foreground->green; + img->palette[6] = foreground->blue; + img->palette[7] = foreground->alpha; + + Raster *newRaster = Raster::createFromImage(img); + img->destroy(); + if(newRaster == nil) + return nil; + if(this->raster) + this->raster->destroy(); + this->raster = newRaster; + return this; +} + +void +Charset::flushBuffer(void) +{ + if(numChars){ + rw::SetRenderStatePtr(rw::TEXTURERASTER, lastRaster); + rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + + uint32 cull = rw::GetRenderState(rw::CULLMODE); + uint32 ztest = rw::GetRenderState(rw::ZTESTENABLE); + rw::SetRenderState(rw::CULLMODE, rw::CULLNONE); + rw::SetRenderState(rw::ZTESTENABLE, 0); + + im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, + vertices, numChars*4, indices, numChars*6); + + rw::SetRenderState(rw::CULLMODE, cull); + rw::SetRenderState(rw::ZTESTENABLE, ztest); + } + + numChars = 0; + lastRaster = nil; +} + +void +Charset::printChar(int32 c, int32 x, int32 y) +{ + Camera *cam; + float recipZ; + float u, v, du, dv; + RWDEVICE::Im2DVertex *vert; + uint16 *ix; + + if(c >= this->desc.count) + return; + + if(this->raster != lastRaster || numChars >= NUMCHARS) + flushBuffer(); + lastRaster = this->raster; + + cam = (Camera*)engine->currentCamera; + vert = &vertices[numChars*4]; + ix = &indices[numChars*6]; + recipZ = 1.0f/cam->nearPlane; + + u = ((c % this->desc.tileWidth)*this->desc.width_internal + HALFPX) / (float32)this->raster->width; + v = ((c / this->desc.tileWidth)*this->desc.height_internal + HALFPX) / (float32)this->raster->height; + du = this->desc.width_internal/(float32)this->raster->width; + dv = this->desc.height_internal/(float32)this->raster->height; + + vert->setScreenX((float)x); + vert->setScreenY((float)y); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u, recipZ); + vert->setV(v, recipZ); + vert++; + + vert->setScreenX(float(x+this->desc.width_internal)); + vert->setScreenY((float)y); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u+du, recipZ); + vert->setV(v, recipZ); + vert++; + + vert->setScreenX((float)x); + vert->setScreenY(float(y+this->desc.height_internal)); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u, recipZ); + vert->setV(v+dv, recipZ); + vert++; + + vert->setScreenX(float(x+this->desc.width_internal)); + vert->setScreenY(float(y+this->desc.height_internal)); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u+du, recipZ); + vert->setV(v+dv, recipZ); + vert++; + + *ix++ = numChars*4; + *ix++ = numChars*4+1; + *ix++ = numChars*4+2; + *ix++ = numChars*4+2; + *ix++ = numChars*4+1; + *ix++ = numChars*4+3; + + numChars++; +} + +void +Charset::print(const char *str, int32 x, int32 y, bool32 hideSpaces) +{ + this->printBuffered(str, x, y, hideSpaces); + flushBuffer(); +} + +void +Charset::printBuffered(const char *str, int32 x, int32 y, bool32 hideSpaces) +{ + while(*str){ + if(!hideSpaces || *str != ' ') + printChar((uint8)*str, x, y); + x += this->desc.width; + str++; + } +} + +} + +#endif + diff --git a/vendor/librw/src/clump.cpp b/vendor/librw/src/clump.cpp new file mode 100644 index 0000000..f228298 --- /dev/null +++ b/vendor/librw/src/clump.cpp @@ -0,0 +1,669 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 2 + +namespace rw { + +int32 Clump::numAllocated; +int32 Atomic::numAllocated; + +PluginList Clump::s_plglist(sizeof(Clump)); +PluginList Atomic::s_plglist(sizeof(Atomic)); + +// +// Clump +// + +Clump* +Clump::create(void) +{ + Clump *clump = (Clump*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_CLUMP); + if(clump == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + clump->object.init(Clump::ID, 0); + clump->atomics.init(); + clump->lights.init(); + clump->cameras.init(); + + // World extension + clump->world = nil; + clump->inWorld.init(); + + s_plglist.construct(clump); + return clump; +} + +Clump* +Clump::clone(void) +{ + Clump *clump = Clump::create(); + Frame *root = this->getFrame()->cloneAndLink(); + clump->setFrame(root); + FORLIST(lnk, this->atomics){ + Atomic *a = Atomic::fromClump(lnk); + Atomic *atomic = a->clone(); + atomic->setFrame(a->getFrame()->root); + clump->addAtomic(atomic); + } + this->getFrame()->purgeClone(); + + // World extension + if(this->world) + this->world->addClump(clump); + + s_plglist.copy(clump, this); + return clump; +} + +void +Clump::destroy(void) +{ + Frame *f; + s_plglist.destruct(this); + FORLIST(lnk, this->atomics){ + Atomic *a = Atomic::fromClump(lnk); + this->removeAtomic(a); + a->destroy(); + } + FORLIST(lnk, this->lights){ + Light *l = Light::fromClump(lnk); + this->removeLight(l); + l->destroy(); + } + FORLIST(lnk, this->cameras){ + Camera *c = Camera::fromClump(lnk); + this->removeCamera(c); + c->destroy(); + } + if(f = this->getFrame(), f) + f->destroyHierarchy(); + assert(this->world == nil); + rwFree(this); + numAllocated--; +} + +void +Clump::addAtomic(Atomic *a) +{ + assert(a->clump == nil); + a->clump = this; + this->atomics.append(&a->inClump); +} + +void +Clump::removeAtomic(Atomic *a) +{ + assert(a->clump == this); + a->inClump.remove(); + a->clump = nil; +} + +void +Clump::addLight(Light *l) +{ + assert(l->clump == nil); + l->clump = this; + this->lights.append(&l->inClump); +} + +void +Clump::removeLight(Light *l) +{ + assert(l->clump == this); + l->inClump.remove(); + l->clump = nil; +} + +void +Clump::addCamera(Camera *c) +{ + assert(c->clump == nil); + c->clump = this; + this->cameras.append(&c->inClump); +} + +void +Clump::removeCamera(Camera *c) +{ + assert(c->clump == this); + c->inClump.remove(); + c->clump = nil; +} + + +Clump* +Clump::streamRead(Stream *stream) +{ + uint32 length, version; + int32 buf[3]; + Clump *clump; + int32 numGeometries; + Geometry **geometryList; + + if(!findChunk(stream, ID_STRUCT, &length, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(buf, length); + int32 numAtomics = buf[0]; + int32 numLights = 0; + int32 numCameras = 0; + if(version > 0x33000){ + numLights = buf[1]; + numCameras = buf[2]; + } + clump = Clump::create(); + if(clump == nil) + return nil; + + // Frame list + FrameList_ frmlst; + frmlst.frames = nil; + if(!findChunk(stream, ID_FRAMELIST, nil, nil)){ + RWERROR((ERR_CHUNK, "FRAMELIST")); + goto fail; + } + if(frmlst.streamRead(stream) == nil) + goto fail; + clump->setFrame(frmlst.frames[0]); + + // Geometry list + numGeometries = 0; + geometryList = nil; + if(version >= 0x30400){ + if(!findChunk(stream, ID_GEOMETRYLIST, nil, nil)){ + RWERROR((ERR_CHUNK, "GEOMETRYLIST")); + goto fail; + } + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + numGeometries = stream->readI32(); + if(numGeometries){ + size_t sz = numGeometries*sizeof(Geometry*); + geometryList = (Geometry**)rwMalloc(sz, MEMDUR_FUNCTION | ID_CLUMP); + if(geometryList == nil){ + RWERROR((ERR_ALLOC, sz)); + goto fail; + } + memset(geometryList, 0, sz); + } + for(int32 i = 0; i < numGeometries; i++){ + if(!findChunk(stream, ID_GEOMETRY, nil, nil)){ + RWERROR((ERR_CHUNK, "GEOMETRY")); + goto failgeo; + } + geometryList[i] = Geometry::streamRead(stream); + if(geometryList[i] == nil) + goto failgeo; + } + } + + // Atomics + Atomic *a; + for(int32 i = 0; i < numAtomics; i++){ + if(!findChunk(stream, ID_ATOMIC, nil, nil)){ + RWERROR((ERR_CHUNK, "ATOMIC")); + goto failgeo; + } + a = Atomic::streamReadClump(stream, &frmlst, geometryList); + if(a == nil) + goto failgeo; + clump->addAtomic(a); + } + + // Lights + int32 frm; + Light *l; + for(int32 i = 0; i < numLights; i++){ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto failgeo; + } + frm = stream->readI32(); + if(!findChunk(stream, ID_LIGHT, nil, nil)){ + RWERROR((ERR_CHUNK, "LIGHT")); + goto failgeo; + } + l = Light::streamRead(stream); + if(l == nil) + goto failgeo; + l->setFrame(frmlst.frames[frm]); + clump->addLight(l); + } + + // Cameras + Camera *cam; + for(int32 i = 0; i < numCameras; i++){ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto failgeo; + } + frm = stream->readI32(); + if(!findChunk(stream, ID_CAMERA, nil, nil)){ + RWERROR((ERR_CHUNK, "CAMERA")); + goto failgeo; + } + cam = Camera::streamRead(stream); + if(cam == nil) + goto failgeo; + cam->setFrame(frmlst.frames[frm]); + clump->addCamera(cam); + } + + for(int32 i = 0; i < numGeometries; i++) + if(geometryList[i]) + geometryList[i]->destroy(); + rwFree(geometryList); + rwFree(frmlst.frames); + if(s_plglist.streamRead(stream, clump)) + return clump; + +failgeo: + for(int32 i = 0; i < numGeometries; i++) + if(geometryList[i]) + geometryList[i]->destroy(); + rwFree(geometryList); +fail: + rwFree(frmlst.frames); + clump->destroy(); + return nil; +} + +bool +Clump::streamWrite(Stream *stream) +{ + int size = this->streamGetSize(); + writeChunkHeader(stream, ID_CLUMP, size); + int32 numAtomics = this->countAtomics(); + int32 numLights = this->countLights(); + int32 numCameras = this->countCameras(); + int32 buf[3] = { numAtomics, numLights, numCameras }; + size = version > 0x33000 ? 12 : 4; + writeChunkHeader(stream, ID_STRUCT, size); + stream->write32(buf, size); + + FrameList_ frmlst; + frmlst.numFrames = this->getFrame()->count(); + frmlst.frames = (Frame**)rwMalloc(frmlst.numFrames*sizeof(Frame*), MEMDUR_FUNCTION | ID_CLUMP); + makeFrameList(this->getFrame(), frmlst.frames); + frmlst.streamWrite(stream); + + if(rw::version >= 0x30400){ + size = 12+4; + FORLIST(lnk, this->atomics) + size += 12 + Atomic::fromClump(lnk)->geometry->streamGetSize(); + writeChunkHeader(stream, ID_GEOMETRYLIST, size); + writeChunkHeader(stream, ID_STRUCT, 4); + stream->writeI32(numAtomics); // same as numGeometries + FORLIST(lnk, this->atomics) + Atomic::fromClump(lnk)->geometry->streamWrite(stream); + } + + FORLIST(lnk, this->atomics) + Atomic::fromClump(lnk)->streamWriteClump(stream, &frmlst); + + FORLIST(lnk, this->lights){ + Light *l = Light::fromClump(lnk); + int frm = findPointer(l->getFrame(), (void**)frmlst.frames, frmlst.numFrames); + if(frm < 0) + return false; + writeChunkHeader(stream, ID_STRUCT, 4); + stream->writeI32(frm); + l->streamWrite(stream); + } + + FORLIST(lnk, this->cameras){ + Camera *c = Camera::fromClump(lnk); + int frm = findPointer(c->getFrame(), (void**)frmlst.frames, frmlst.numFrames); + if(frm < 0) + return false; + writeChunkHeader(stream, ID_STRUCT, 4); + stream->writeI32(frm); + c->streamWrite(stream); + } + + rwFree(frmlst.frames); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Clump::streamGetSize(void) +{ + uint32 size = 0; + size += 12; // Struct + size += 4; // numAtomics + if(version > 0x33000) + size += 8; // numLights, numCameras + + // Frame list + size += FrameList_::streamGetSize(this->getFrame()); + + if(rw::version >= 0x30400){ + // Geometry list + size += 12 + 12 + 4; + FORLIST(lnk, this->atomics) + size += 12 + Atomic::fromClump(lnk)->geometry->streamGetSize(); + } + + // Atomics + FORLIST(lnk, this->atomics) + size += 12 + Atomic::fromClump(lnk)->streamGetSize(); + + // Lights + FORLIST(lnk, this->lights) + size += 16 + 12 + Light::fromClump(lnk)->streamGetSize(); + + // Cameras + FORLIST(lnk, this->cameras) + size += 16 + 12 + Camera::fromClump(lnk)->streamGetSize(); + + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +void +Clump::render(void) +{ + Atomic *a; + FORLIST(lnk, this->atomics){ + a = Atomic::fromClump(lnk); + if(a->object.object.flags & Atomic::RENDER) + a->render(); + } +} + +// +// Atomic +// + +static void +atomicSync(ObjectWithFrame *obj) +{ + // TODO: interpolate + obj->object.privateFlags |= Atomic::WORLDBOUNDDIRTY; +} + + +static void +worldAtomicSync(ObjectWithFrame *obj) +{ + Atomic *atomic = (Atomic*)obj; + atomic->originalSync(obj); +} + +Atomic* +Atomic::create(void) +{ + Atomic *atomic = (Atomic*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_ATOMIC); + if(atomic == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + atomic->object.object.init(Atomic::ID, 0); + atomic->object.syncCB = atomicSync; + atomic->geometry = nil; + atomic->boundingSphere.center.set(0.0f, 0.0f, 0.0f); + atomic->boundingSphere.radius = 0.0f; + atomic->worldBoundingSphere.center.set(0.0f, 0.0f, 0.0f); + atomic->worldBoundingSphere.radius = 0.0f; + atomic->setFrame(nil); + atomic->object.object.privateFlags |= WORLDBOUNDDIRTY; + atomic->clump = nil; + atomic->inClump.init(); + atomic->pipeline = nil; + atomic->renderCB = Atomic::defaultRenderCB; + atomic->object.object.flags = Atomic::COLLISIONTEST | Atomic::RENDER; + // TODO: interpolator + + // World extension + atomic->world = nil; + atomic->originalSync = atomic->object.syncCB; + atomic->object.syncCB = worldAtomicSync; + + s_plglist.construct(atomic); + return atomic; +} + +Atomic* +Atomic::clone() +{ + Atomic *atomic = Atomic::create(); + if(atomic == nil) + return nil; + atomic->object.object.copy(&this->object.object); + atomic->object.object.privateFlags |= WORLDBOUNDDIRTY; + if(this->geometry) + atomic->setGeometry(this->geometry, 0); + atomic->renderCB = this->renderCB; + atomic->pipeline = this->pipeline; + + // World extension doesn't add to world + + s_plglist.copy(atomic, this); + return atomic; +} + +void +Atomic::destroy(void) +{ + s_plglist.destruct(this); + if(this->geometry) + this->geometry->destroy(); + assert(this->clump == nil); + assert(this->world == nil); + this->setFrame(nil); + rwFree(this); + numAllocated--; +} + +void +Atomic::setGeometry(Geometry *geo, uint32 flags) +{ + if(this->geometry) + this->geometry->destroy(); + if(geo) + geo->addRef(); + this->geometry = geo; + if(flags & SAMEBOUNDINGSPHERE) + return; + if(geo){ + this->boundingSphere = geo->morphTargets[0].boundingSphere; + if(this->getFrame()) // TODO: && getWorld??? + this->getFrame()->updateObjects(); + } +} + +Sphere* +Atomic::getWorldBoundingSphere(void) +{ + Sphere *s = &this->worldBoundingSphere; + // TODO: if we ever support morphing, check interpolation + if(!this->getFrame()->dirty() && + (this->object.object.privateFlags & WORLDBOUNDDIRTY) == 0) + return s; + Matrix *ltm = this->getFrame()->getLTM(); + // TODO: support scaling + V3d::transformPoints(&s->center, &this->boundingSphere.center, 1, ltm); + s->radius = this->boundingSphere.radius; + this->object.object.privateFlags &= ~WORLDBOUNDDIRTY; + return s; +} + +static uint32 atomicRights[2]; + +Atomic* +Atomic::streamReadClump(Stream *stream, + FrameList_ *frameList, Geometry **geometryList) +{ + int32 buf[4]; + uint32 version; + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(buf, version < 0x30400 ? 12 : 16); + Atomic *atomic = Atomic::create(); + if(atomic == nil) + return nil; + atomic->setFrame(frameList->frames[buf[0]]); + Geometry *g; + if(version < 0x30400){ + if(!findChunk(stream, ID_GEOMETRY, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + g = Geometry::streamRead(stream); + if(g == nil) + goto fail; + atomic->setGeometry(g, 0); + g->destroy(); + }else + atomic->setGeometry(geometryList[buf[1]], 0); + atomic->object.object.flags = buf[2]; + + atomicRights[0] = 0; + if(!s_plglist.streamRead(stream, atomic)) + goto fail; + if(atomicRights[0]) + s_plglist.assertRights(atomic, atomicRights[0], atomicRights[1]); + return atomic; + +fail: + atomic->destroy(); + return nil; +} + +bool +Atomic::streamWriteClump(Stream *stream, FrameList_ *frmlst) +{ + int32 buf[4] = { 0, 0, 0, 0 }; + Clump *c = this->clump; + if(c == nil) + return false; + writeChunkHeader(stream, ID_ATOMIC, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, rw::version < 0x30400 ? 12 : 16); + buf[0] = findPointer(this->getFrame(), (void**)frmlst->frames, frmlst->numFrames); + + if(version < 0x30400){ + buf[1] = this->object.object.flags; + stream->write32(buf, sizeof(int32[3])); + this->geometry->streamWrite(stream); + }else{ + buf[1] = 0; + FORLIST(lnk, c->atomics){ + if(Atomic::fromClump(lnk)->geometry == this->geometry) + goto foundgeo; + buf[1]++; + } + return false; + foundgeo: + buf[2] = this->object.object.flags; + stream->write32(buf, sizeof(buf)); + } + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Atomic::streamGetSize(void) +{ + uint32 size = 12 + 12 + 12 + s_plglist.streamGetSize(this); + if(rw::version < 0x30400) + size += 12 + this->geometry->streamGetSize(); + else + size += 4; + return size; +} + +ObjPipeline* +Atomic::getPipeline(void) +{ + return this->pipeline ? + this->pipeline : + engine->driver[platform]->defaultPipeline; +} + +void +Atomic::instance(void) +{ + if(this->geometry->flags & Geometry::NATIVE) + return; + this->getPipeline()->instance(this); + this->geometry->flags |= Geometry::NATIVE; +} + +void +Atomic::uninstance(void) +{ + if(!(this->geometry->flags & Geometry::NATIVE)) + return; + this->getPipeline()->uninstance(this); + // this should be done by the CB already, just make sure + this->geometry->flags &= ~Geometry::NATIVE; +} + +void +Atomic::defaultRenderCB(Atomic *atomic) +{ + atomic->getPipeline()->render(atomic); +} + +// Atomic Rights plugin + +static Stream* +readAtomicRights(Stream *stream, int32, void *, int32, int32) +{ + stream->read32(atomicRights, 8); + return stream; +} + +static Stream* +writeAtomicRights(Stream *stream, int32, void *object, int32, int32) +{ + Atomic *atomic = (Atomic*)object; + uint32 buffer[2]; + buffer[0] = atomic->pipeline->pluginID; + buffer[1] = atomic->pipeline->pluginData; + stream->write32(buffer, 8); + return stream; +} + +static int32 +getSizeAtomicRights(void *object, int32, int32) +{ + Atomic *atomic = (Atomic*)object; + if(atomic->pipeline == nil || atomic->pipeline->pluginID == 0) + return 0; + return 8; +} + +void +registerAtomicRightsPlugin(void) +{ + Atomic::registerPlugin(0, ID_RIGHTTORENDER, nil, nil, nil); + Atomic::registerPluginStream(ID_RIGHTTORENDER, + readAtomicRights, + writeAtomicRights, + getSizeAtomicRights); +} + +} diff --git a/vendor/librw/src/d3d/d3d.cpp b/vendor/librw/src/d3d/d3d.cpp new file mode 100644 index 0000000..f1f5f49 --- /dev/null +++ b/vendor/librw/src/d3d/d3d.cpp @@ -0,0 +1,1087 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3dimpl.h" + +#define PLUGIN_ID ID_DRIVER + +namespace rw { +namespace d3d { + +bool32 isP8supported = 1; // set to 0 when actual d3d device is used + +// stolen from d3d8to9 +static uint32 +calculateTextureSize(uint32 width, uint32 height, uint32 depth, uint32 format) +{ +#define D3DFMT_W11V11U10 65 + switch(format){ + default: + case D3DFMT_UNKNOWN: + return 0; + case D3DFMT_R3G3B2: + case D3DFMT_A8: + case D3DFMT_P8: + case D3DFMT_L8: + case D3DFMT_A4L4: + return width * height * depth; + case D3DFMT_R5G6B5: + case D3DFMT_X1R5G5B5: + case D3DFMT_A1R5G5B5: + case D3DFMT_A4R4G4B4: + case D3DFMT_A8R3G3B2: + case D3DFMT_X4R4G4B4: + case D3DFMT_A8P8: + case D3DFMT_A8L8: + case D3DFMT_V8U8: + case D3DFMT_L6V5U5: + case D3DFMT_D16_LOCKABLE: + case D3DFMT_D15S1: + case D3DFMT_D16: + case D3DFMT_UYVY: + case D3DFMT_YUY2: + return width * 2 * height * depth; + case D3DFMT_R8G8B8: + return width * 3 * height * depth; + case D3DFMT_A8R8G8B8: + case D3DFMT_X8R8G8B8: + case D3DFMT_A2B10G10R10: + case D3DFMT_A8B8G8R8: + case D3DFMT_X8B8G8R8: + case D3DFMT_G16R16: + case D3DFMT_X8L8V8U8: + case D3DFMT_Q8W8V8U8: + case D3DFMT_V16U16: + case D3DFMT_W11V11U10: + case D3DFMT_A2W10V10U10: + case D3DFMT_D32: + case D3DFMT_D24S8: + case D3DFMT_D24X8: + case D3DFMT_D24X4S4: + return width * 4 * height * depth; + case D3DFMT_DXT1: + assert(depth <= 1); + return ((width + 3) >> 2) * ((height + 3) >> 2) * 8; + case D3DFMT_DXT2: + case D3DFMT_DXT3: + case D3DFMT_DXT4: + case D3DFMT_DXT5: + assert(depth <= 1); + return ((width + 3) >> 2) * ((height + 3) >> 2) * 16; + } +} + +int vertFormatMap[] = { + -1, VERT_FLOAT2, VERT_FLOAT3, VERT_FLOAT4, VERT_ARGB, VERT_RGBA /* blend indices */ +}; + +void* +createIndexBuffer(uint32 length, bool dynamic) +{ +#ifdef RW_D3D9 + IDirect3DIndexBuffer9 *ibuf; + if(dynamic) + d3ddevice->CreateIndexBuffer(length, D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &ibuf, 0); + else + d3ddevice->CreateIndexBuffer(length, D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &ibuf, 0); + if(ibuf) + d3d9Globals.numIndexBuffers++; + return ibuf; +#else + return rwNewT(uint8, length, MEMDUR_EVENT | ID_DRIVER); +#endif +} + +void +destroyIndexBuffer(void *indexBuffer) +{ +#ifdef RW_D3D9 + if(indexBuffer){ + if(((IUnknown*)indexBuffer)->Release() != 0) + printf("indexBuffer wasn't destroyed\n"); + d3d9Globals.numIndexBuffers--; + } +#else + rwFree(indexBuffer); +#endif +} + +uint16* +lockIndices(void *indexBuffer, uint32 offset, uint32 size, uint32 flags) +{ + if(indexBuffer == nil) + return nil; +#ifdef RW_D3D9 + uint16 *indices; + IDirect3DIndexBuffer9 *ibuf = (IDirect3DIndexBuffer9*)indexBuffer; + ibuf->Lock(offset, size, (void**)&indices, flags); + return indices; +#else + (void)offset; + (void)size; + (void)flags; + return (uint16*)indexBuffer; +#endif +} + +void +unlockIndices(void *indexBuffer) +{ + if(indexBuffer == nil) + return; +#ifdef RW_D3D9 + IDirect3DIndexBuffer9 *ibuf = (IDirect3DIndexBuffer9*)indexBuffer; + ibuf->Unlock(); +#endif +} + +void* +createVertexBuffer(uint32 length, uint32 fvf, bool dynamic) +{ +#ifdef RW_D3D9 + IDirect3DVertexBuffer9 *vbuf; + if(dynamic) + d3ddevice->CreateVertexBuffer(length, D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, fvf, D3DPOOL_DEFAULT, &vbuf, 0); + else + d3ddevice->CreateVertexBuffer(length, D3DUSAGE_WRITEONLY, fvf, D3DPOOL_MANAGED, &vbuf, 0); + if(vbuf) + d3d9Globals.numVertexBuffers++; + return vbuf; +#else + (void)fvf; + return rwNewT(uint8, length, MEMDUR_EVENT | ID_DRIVER); +#endif +} + +void +destroyVertexBuffer(void *vertexBuffer) +{ +#ifdef RW_D3D9 + if(vertexBuffer){ + if(((IUnknown*)vertexBuffer)->Release() != 0) + printf("vertexBuffer wasn't destroyed\n"); + d3d9Globals.numVertexBuffers--; + } +#else + rwFree(vertexBuffer); +#endif +} + +uint8* +lockVertices(void *vertexBuffer, uint32 offset, uint32 size, uint32 flags) +{ + if(vertexBuffer == nil) + return nil; +#ifdef RW_D3D9 + uint8 *verts; + IDirect3DVertexBuffer9 *vertbuf = (IDirect3DVertexBuffer9*)vertexBuffer; + vertbuf->Lock(offset, size, (void**)&verts, flags); + return verts; +#else + (void)offset; + (void)size; + (void)flags; + return (uint8*)vertexBuffer; +#endif +} + +void +unlockVertices(void *vertexBuffer) +{ + if(vertexBuffer == nil) + return; +#ifdef RW_D3D9 + IDirect3DVertexBuffer9 *vertbuf = (IDirect3DVertexBuffer9*)vertexBuffer; + vertbuf->Unlock(); +#endif +} + +void* +createTexture(int32 width, int32 height, int32 numlevels, uint32 usage, uint32 format) +{ +#ifdef RW_D3D9 + IDirect3DTexture9 *tex; + d3ddevice->CreateTexture(width, height, numlevels, usage, + (D3DFORMAT)format, D3DPOOL_MANAGED, &tex, nil); + if(tex) + d3d9Globals.numTextures++; + return tex; +#else + int32 w = width; + int32 h = height; + int32 size = 0; + for(int32 i = 0; i < numlevels; i++){ + size += calculateTextureSize(w, h, 1, format); + w /= 2; + if(w == 0) w = 1; + h /= 2; + if(h == 0) h = 1; + } + uint8 *data = (uint8*)rwNew(sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(numlevels-1)+size, + MEMDUR_EVENT | ID_DRIVER); + RasterLevels *levels = (RasterLevels*)data; + data += sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(numlevels-1); + levels->numlevels = numlevels; + levels->format = format; + w = width; + h = height; + for(int32 i = 0; i < numlevels; i++){ + levels->levels[i].width = w; + levels->levels[i].height = h; + levels->levels[i].data = data; + levels->levels[i].size = calculateTextureSize(w, h, 1, format); + data += levels->levels[i].size; + w /= 2; + if(w == 0) w = 1; + h /= 2; + if(h == 0) h = 1; + } + return levels; +#endif +} + +void +destroyTexture(void *texture) +{ +#ifdef RW_D3D9 + if(texture){ + if(((IUnknown*)texture)->Release() != 0) + printf("texture wasn't destroyed\n"); + d3d9Globals.numTextures--; + } +#else + rwFree(texture); +#endif +} + +// Native Raster + +int32 nativeRasterOffset; + +struct RasterFormatInfo +{ + uint32 d3dformat; + int32 depth; + bool32 hasAlpha; + uint32 rwFormat; +}; + +// indexed directly by RW format +static RasterFormatInfo formatInfoRW[16] = { + { 0, 0, 0, 0}, + { D3DFMT_A1R5G5B5, 16, 1, Raster::C1555 }, + { D3DFMT_R5G6B5, 16, 0, Raster::C565 }, + { D3DFMT_A4R4G4B4, 16, 1, Raster::C4444 }, + { D3DFMT_L8, 8, 0, Raster::LUM8 }, + { D3DFMT_A8R8G8B8, 32, 1, Raster::C8888 }, + { D3DFMT_X8R8G8B8, 32, 0, Raster::C888 }, + { D3DFMT_D16, 16, 0, Raster::D16 }, + { D3DFMT_D24X8, 32, 0, Raster::D24 }, + { D3DFMT_D32, 32, 0, Raster::D32 }, + { D3DFMT_X1R5G5B5, 16, 0, Raster::C555 }, +}; + +static RasterFormatInfo formatInfoFull[] = { + { D3DFMT_R8G8B8, 0, 24, 0 }, + { D3DFMT_A8R8G8B8, 1, 32, Raster::C8888 }, + { D3DFMT_X8R8G8B8, 0, 32, Raster::C888 }, + { D3DFMT_R5G6B5, 0, 16, Raster::C565 }, + { D3DFMT_X1R5G5B5, 0, 16, Raster::C555 }, + { D3DFMT_A1R5G5B5, 1, 16, Raster::C1555 }, + { D3DFMT_A4R4G4B4, 1, 16, Raster::C4444 }, + { D3DFMT_R3G3B2, 0, 8, 0 }, + { D3DFMT_A8, 1, 8, 0 }, + { D3DFMT_A8R3G3B2, 1, 16, 0 }, + { D3DFMT_X4R4G4B4, 0, 16, 0 }, + { D3DFMT_A2B10G10R10, 1, 32, 0 }, + { D3DFMT_A8B8G8R8, 1, 32, 0 }, + { D3DFMT_X8B8G8R8, 0, 32, 0 }, + { D3DFMT_G16R16, 0, 32, 0 }, + { D3DFMT_A2R10G10B10, 1, 32, 0 }, + { D3DFMT_A16B16G16R16, 1, 64, 0 }, + { D3DFMT_A8P8, 1, 16, 0 }, +// { D3DFMT_P8, 0, 8, ... }, +// { D3DFMT_L8, 0, 8, ... }, + { D3DFMT_A8L8, 1, 16, 0 }, + { D3DFMT_A4L4, 1, 8, 0 }, + { D3DFMT_V8U8, 0, 16, 0 }, + { D3DFMT_L6V5U5, 0, 16, 0 }, + { D3DFMT_X8L8V8U8, 0, 32, 0 }, + { D3DFMT_Q8W8V8U8, 0, 32, 0 }, + { D3DFMT_V16U16, 0, 32, 0 }, + { D3DFMT_A2W10V10U10, 1, 32, 0 }, + { D3DFMT_D16_LOCKABLE, 0, 16, Raster::D16 }, + { D3DFMT_D32, 0, 32, Raster::D32 }, + { D3DFMT_D15S1, 0, 16, Raster::D16 }, + { D3DFMT_D24S8, 0, 32, Raster::D32 }, + { D3DFMT_D24X8, 0, 32, Raster::D32 }, + { D3DFMT_D24X4S4, 0, 32, Raster::D32 }, + { D3DFMT_D16, 0, 16, Raster::D16 }, + { D3DFMT_D32F_LOCKABLE, 0, 32, Raster::D32 }, + { D3DFMT_D24FS8, 0, 32, Raster::D32 }, + { D3DFMT_L16, 0, 16, 0 }, + { D3DFMT_Q16W16V16U16, 0, 64, 0 }, + { D3DFMT_R16F, 0, 16, 0 }, + { D3DFMT_G16R16F, 0, 32, 0 }, + { D3DFMT_A16B16G16R16F, 1, 64, 0 }, + { D3DFMT_R32F, 0, 32, 0 }, + { D3DFMT_G32R32F, 0, 64, 0 }, + { D3DFMT_A32B32G32R32F, 1, 128, 0 }, + { D3DFMT_CxV8U8, 0, 16, 0 }, +}; + +RasterFormatInfo* +findFormatInfoD3D(uint32 d3dformat) +{ + static RasterFormatInfo fake = { 0, 0, 0, 0 }; + int i; + for(i = 0; i < (int)nelem(formatInfoFull); i++) + if(formatInfoFull[i].d3dformat == d3dformat) + return &formatInfoFull[i]; + return &fake; +} + + +static void +rasterSetFormat(Raster *raster) +{ + if(raster->format == 0){ + // have to find a format first + // this could perhaps be a bit more intelligent + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + raster->format = Raster::C8888; + break; + +#ifdef RW_D3D9 + case Raster::ZBUFFER: + // TODO: allow other formats + raster->format = findFormatInfoD3D(d3d9Globals.present.AutoDepthStencilFormat)->rwFormat; + // can this even happen? just do something... + if(raster->format == 0) + raster->format = Raster::D32; + break; + + case Raster::CAMERATEXTURE: +// let's not use this because we apparently don't want alpha +// raster->format = findFormatInfoD3D(d3d9Globals.present.BackBufferFormat)->rwFormat; + raster->format = findFormatInfoD3D(d3d9Globals.startMode.mode.Format)->rwFormat; + // can this even happen? just do something... + if(raster->format == 0) + raster->format = Raster::C888; + break; + case Raster::CAMERA: + raster->format = findFormatInfoD3D(d3d9Globals.present.BackBufferFormat)->rwFormat; + // can this even happen? just do something... + if(raster->format == 0) + raster->format = Raster::C8888; + break; +#endif + } + } + + + D3dRaster *natras = GETD3DRASTEREXT(raster); + if(raster->format & (Raster::PAL4 | Raster::PAL8)){ + // TODO: do we even allow PAL4? + natras->format = D3DFMT_P8; + raster->depth = 8; + }else{ + natras->format = formatInfoRW[(raster->format >> 8) & 0xF].d3dformat; + raster->depth = formatInfoRW[(raster->format >> 8) & 0xF].depth; + } + natras->bpp = raster->depth/8; + natras->hasAlpha = formatInfoRW[(raster->format >> 8) & 0xF].hasAlpha; + raster->stride = raster->width*natras->bpp; + + natras->autogenMipmap = (raster->format & (Raster::MIPMAP|Raster::AUTOMIPMAP)) == (Raster::MIPMAP|Raster::AUTOMIPMAP); +} + +static Raster* +rasterCreateTexture(Raster *raster) +{ + int32 levels; + D3dRaster *natras = GETD3DRASTEREXT(raster); + + if(natras->format == D3DFMT_P8) + natras->palette = (uint8*)rwNew(4*256, MEMDUR_EVENT | ID_DRIVER); + if(natras->autogenMipmap) + levels = 0; + else if(raster->format & Raster::MIPMAP) + levels = Raster::calculateNumLevels(raster->width, raster->height); + else + levels = 1; + + assert(natras->texture == nil); + natras->texture = createTexture(raster->width, raster->height, + levels, + natras->autogenMipmap ? D3DUSAGE_AUTOGENMIPMAP : 0, + natras->format); + if(natras->texture == nil){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + return raster; +} + +#ifdef RW_D3D9 + +static Raster* +rasterCreateCameraTexture(Raster *raster) +{ + if(raster->format & (Raster::PAL4 | Raster::PAL8)){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + + int32 levels; + D3dRaster *natras = GETD3DRASTEREXT(raster); + if(natras->autogenMipmap) + levels = 0; + else if(raster->format & Raster::MIPMAP) + levels = Raster::calculateNumLevels(raster->width, raster->height); + else + levels = 1; + + IDirect3DTexture9 *tex; + d3ddevice->CreateTexture(raster->width, raster->height, + levels, + (natras->autogenMipmap ? D3DUSAGE_AUTOGENMIPMAP : 0) | D3DUSAGE_RENDERTARGET, + (D3DFORMAT)natras->format, D3DPOOL_DEFAULT, &tex, nil); + assert(natras->texture == nil); + natras->texture = tex; + if(natras->texture == nil){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + d3d9Globals.numTextures++; + addVidmemRaster(raster); + return raster; +} + +static Raster* +rasterCreateCamera(Raster *raster) +{ + D3dRaster *natras = GETD3DRASTEREXT(raster); + + natras->autogenMipmap = 0; + + natras->format = d3d9Globals.present.BackBufferFormat; + raster->depth = findFormatDepth(natras->format); + + natras->texture = nil; // global default render target + return raster; +} + +static Raster* +rasterCreateZbuffer(Raster *raster) +{ + D3dRaster *natras = GETD3DRASTEREXT(raster); + + natras->autogenMipmap = 0; + + // TODO: allow other formats + natras->format = d3d9Globals.present.AutoDepthStencilFormat; + raster->depth = findFormatDepth(natras->format); + + RECT rect; + GetClientRect(d3d9Globals.window, &rect); + // This check is done by RW but it's rather strange... + if(rect.right == raster->width && rect.bottom == raster->height) + natras->texture = d3d9Globals.defaultDepthSurf; + else{ + IDirect3DSurface9 *surf = nil; + d3ddevice->CreateDepthStencilSurface(raster->width, raster->height, (D3DFORMAT)natras->format, + d3d9Globals.present.MultiSampleType, d3d9Globals.present.MultiSampleQuality, + FALSE, &surf, nil); + assert(natras->texture == nil); + natras->texture = surf; + if(natras->texture == nil){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + } + addVidmemRaster(raster); + + return raster; +} +#endif + +Raster* +rasterCreate(Raster *raster) +{ + rasterSetFormat(raster); + + Raster *ret = raster; + + if(raster->width == 0 || raster->height == 0){ + raster->flags |= Raster::DONTALLOCATE; + raster->stride = 0; + goto ret; + } + if(raster->flags & Raster::DONTALLOCATE) + goto ret; + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + ret = rasterCreateTexture(raster); + break; + +#ifdef RW_D3D9 + case Raster::CAMERATEXTURE: + ret = rasterCreateCameraTexture(raster); + break; + case Raster::ZBUFFER: + ret = rasterCreateZbuffer(raster); + break; + case Raster::CAMERA: + ret = rasterCreateCamera(raster); + break; +#endif + + default: + RWERROR((ERR_INVRASTER)); + return nil; + } + +ret: + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->originalStride = raster->stride; + raster->originalPixels = raster->pixels; + return ret; +} + +uint8* +rasterLock(Raster *raster, int32 level, int32 lockMode) +{ + D3dRaster *natras = GETD3DRASTEREXT(raster); + + // check if already locked + if(raster->privateFlags & (Raster::PRIVATELOCK_READ|Raster::PRIVATELOCK_WRITE)) + return nil; + +#ifdef RW_D3D9 + DWORD flags = D3DLOCK_NOSYSLOCK; + if(lockMode & Raster::LOCKREAD) + flags |= D3DLOCK_READONLY | D3DLOCK_NO_DIRTY_UPDATE; + IDirect3DTexture9 *tex = (IDirect3DTexture9*)natras->texture; + IDirect3DSurface9 *surf, *rt; + D3DLOCKED_RECT lr; + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: { + tex->GetSurfaceLevel(level, &surf); + natras->lockedSurf = surf; + HRESULT res = surf->LockRect(&lr, 0, flags); + assert(res == D3D_OK); + break; + } + + case Raster::CAMERATEXTURE: + case Raster::CAMERA: { + if(lockMode & Raster::PRIVATELOCK_WRITE) + assert(0 && "can't lock framebuffer for writing"); + if(raster->type == Raster::CAMERA) + rt = d3d9Globals.defaultRenderTarget; + else + tex->GetSurfaceLevel(level, &rt); + D3DSURFACE_DESC desc; + rt->GetDesc(&desc); + HRESULT res = d3ddevice->CreateOffscreenPlainSurface(desc.Width, desc.Height, desc.Format, D3DPOOL_SYSTEMMEM, &surf, nil); + if(res != D3D_OK) + return nil; + d3ddevice->GetRenderTargetData(rt, surf); + natras->lockedSurf = surf; + res = surf->LockRect(&lr, 0, flags); + assert(res == D3D_OK); + break; + } + + default: + assert(0 && "can't lock this raster type (yet)"); + } + + raster->pixels = (uint8*)lr.pBits; + raster->width >>= level; + raster->height >>= level; + raster->stride = lr.Pitch; + if(raster->width == 0) raster->width = 1; + if(raster->height == 0) raster->height = 1; +#else + RasterLevels *levels = (RasterLevels*)natras->texture; + raster->pixels = levels->levels[level].data; + raster->width = levels->levels[level].width; + raster->height = levels->levels[level].height; + raster->stride = raster->width*natras->bpp; +#endif + if(lockMode & Raster::LOCKREAD) raster->privateFlags |= Raster::PRIVATELOCK_READ; + if(lockMode & Raster::LOCKWRITE) raster->privateFlags |= Raster::PRIVATELOCK_WRITE; + + return raster->pixels; +} + +void +rasterUnlock(Raster *raster, int32 level) +{ +#if RW_D3D9 + D3dRaster *natras = GETD3DRASTEREXT(raster); + IDirect3DSurface9 *surf = (IDirect3DSurface9*)natras->lockedSurf; + surf->UnlockRect(); + surf->Release(); + natras->lockedSurf = nil; +#endif + raster->width = raster->originalWidth; + raster->height = raster->originalHeight; + raster->stride = raster->originalStride; + raster->pixels = raster->originalPixels; + + raster->privateFlags &= ~(Raster::PRIVATELOCK_READ|Raster::PRIVATELOCK_WRITE); +} + +int32 +rasterNumLevels(Raster *raster) +{ + D3dRaster *natras = GETD3DRASTEREXT(raster); +#ifdef RW_D3D9 + IDirect3DTexture9 *tex = (IDirect3DTexture9*)natras->texture; + return tex->GetLevelCount(); +#else + RasterLevels *levels = (RasterLevels*)natras->texture; + return levels->numlevels; +#endif +} + +// Almost the same as ps2 and gl3 function +bool32 +imageFindRasterFormat(Image *img, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat) +{ + int32 width, height, depth, format; + + assert((type&0xF) == Raster::TEXTURE); + +// for(width = 1; width < img->width; width <<= 1); +// for(height = 1; height < img->height; height <<= 1); + // Perhaps non-power-of-2 textures are acceptable? + width = img->width; + height = img->height; + + depth = img->depth; + + if(depth <= 8 && !isP8supported) + depth = 32; + + switch(depth){ + case 32: + if(img->hasAlpha()) + format = Raster::C8888; + else{ + format = Raster::C888; + depth = 24; + } + break; + case 24: + format = Raster::C888; + break; + case 16: + format = Raster::C1555; + break; + case 8: + format = Raster::PAL8 | Raster::C8888; + break; + case 4: + format = Raster::PAL4 | Raster::C8888; + break; + default: + RWERROR((ERR_INVRASTER)); + return 0; + } + + format |= type; + + *pWidth = width; + *pHeight = height; + *pDepth = depth; + *pFormat = format; + + return 1; +} + +bool32 +rasterFromImage(Raster *raster, Image *image) +{ + if((raster->type&0xF) != Raster::TEXTURE) + return 0; + + void (*conv)(uint8 *out, uint8 *in) = nil; + + // Unpalettize image if necessary but don't change original + Image *truecolimg = nil; + if(image->depth <= 8 && !isP8supported){ + truecolimg = Image::create(image->width, image->height, image->depth); + truecolimg->pixels = image->pixels; + truecolimg->stride = image->stride; + truecolimg->palette = image->palette; + truecolimg->unpalettize(); + image = truecolimg; + } + + D3dRaster *natras = GETD3DRASTEREXT(raster); + int32 format = raster->format&(Raster::PAL8 | Raster::PAL4 | 0xF00); + switch(image->depth){ + case 32: + if(format == Raster::C8888) + conv = conv_BGRA8888_from_RGBA8888; + else if(format == Raster::C888) + conv = conv_BGR888_from_RGB888; + else + goto err; + break; + case 24: + if(format == Raster::C8888) + conv = conv_BGRA8888_from_RGB888; + else if(format == Raster::C888) + conv = conv_BGR888_from_RGB888; + else + goto err; + break; + case 16: + if(format == Raster::C1555) + conv = conv_ARGB1555_from_ARGB1555; + else + goto err; + break; + case 8: + if(format == (Raster::PAL8 | Raster::C8888)) + conv = conv_8_from_8; + else + goto err; + break; + case 4: + if(format == (Raster::PAL4 | Raster::C8888) || + format == (Raster::PAL8 | Raster::C8888)) + conv = conv_8_from_8; + else + goto err; + break; + default: + err: +fprintf(stderr, "%d %x\n", image->depth, format); fflush(stdout); + RWERROR((ERR_INVRASTER)); + return 0; + } + + uint8 *in, *out; + int pallength = 0; + if(raster->format & Raster::PAL4) + pallength = 16; + else if(raster->format & Raster::PAL8) + pallength = 256; + if(pallength){ + in = image->palette; + out = (uint8*)natras->palette; + for(int32 i = 0; i < pallength; i++){ + conv_RGBA8888_from_RGBA8888(out, in); + in += 4; + out += 4; + } + } + + bool unlock = false; + if(raster->pixels == nil){ + raster->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + unlock = true; + } + + uint8 *pixels = raster->pixels; + assert(pixels); + uint8 *imgpixels = image->pixels; + + int x, y; + assert(image->width == raster->width); + assert(image->height == raster->height); + for(y = 0; y < image->height; y++){ + uint8 *imgrow = imgpixels; + uint8 *rasrow = pixels; + for(x = 0; x < image->width; x++){ + conv(rasrow, imgrow); + imgrow += image->bpp; + rasrow += natras->bpp; + } + imgpixels += image->stride; + pixels += raster->stride; + } + if(unlock) + raster->unlock(0); + + if(truecolimg) + truecolimg->destroy(); + + return 1; +} + +Image* +rasterToImage(Raster *raster) +{ + int32 depth; + Image *image; + + bool unlock = false; + if(raster->pixels == nil){ + raster->lock(0, Raster::LOCKREAD); + unlock = true; + } + + D3dRaster *natras = GETD3DRASTEREXT(raster); + if(natras->customFormat){ + int w = raster->width; + int h = raster->height; + // pixels are in the upper right corner + if(w < 4) w = 4; + if(h < 4) h = 4; + image = Image::create(w, h, 32); + image->allocate(); + uint8 *pix = raster->pixels; + switch(natras->format){ + case D3DFMT_DXT1: + image->setPixelsDXT(1, pix); + if((raster->format & 0xF00) == Raster::C565) + image->removeMask(); + break; + case D3DFMT_DXT3: + image->setPixelsDXT(3, pix); + break; + case D3DFMT_DXT5: + image->setPixelsDXT(5, pix); + break; + default: + image->destroy(); + if(unlock) + raster->unlock(0); + return nil; + } + // fix it up again + image->width = raster->width; + image->height = raster->height; + + if(unlock) + raster->unlock(0); + return image; + } + + void (*conv)(uint8 *out, uint8 *in) = nil; + switch(raster->format & 0xF00){ + case Raster::C1555: + depth = 16; + conv = conv_ARGB1555_from_ARGB1555; + break; + case Raster::C8888: + depth = 32; + conv = conv_RGBA8888_from_BGRA8888; + break; + case Raster::C888: + depth = 24; + conv = conv_RGB888_from_BGR888; + break; + case Raster::C555: + depth = 16; + conv = conv_ARGB1555_from_RGB555; + break; + + default: + case Raster::C565: + case Raster::C4444: + case Raster::LUM8: + RWERROR((ERR_INVRASTER)); + return nil; + } + int32 pallength = 0; + if((raster->format & Raster::PAL4) == Raster::PAL4){ + depth = 4; + pallength = 16; + }else if((raster->format & Raster::PAL8) == Raster::PAL8){ + depth = 8; + pallength = 256; + } + + uint8 *in, *out; + image = Image::create(raster->width, raster->height, depth); + image->allocate(); + + if(pallength){ + out = image->palette; + in = (uint8*)natras->palette; + for(int32 i = 0; i < pallength; i++){ + conv_RGBA8888_from_RGBA8888(out, in); + in += 4; + out += 4; + } + } + + uint8 *imgpixels = image->pixels; + uint8 *pixels = raster->pixels; + + int x, y; + assert(image->width == raster->width); + assert(image->height == raster->height); + for(y = 0; y < image->height; y++){ + uint8 *imgrow = imgpixels; + uint8 *rasrow = pixels; + for(x = 0; x < image->width; x++){ + conv(imgrow, rasrow); + imgrow += image->bpp; + rasrow += natras->bpp; + } + imgpixels += image->stride; + pixels += raster->stride; + } + image->compressPalette(); + + if(unlock) + raster->unlock(0); + + return image; +} + +int32 +getLevelSize(Raster *raster, int32 level) +{ + D3dRaster *ras = GETD3DRASTEREXT(raster); +#ifdef RW_D3D9 + IDirect3DTexture9 *tex = (IDirect3DTexture9*)ras->texture; + D3DSURFACE_DESC desc; + tex->GetLevelDesc(level, &desc); + return calculateTextureSize(desc.Width, desc.Height, 1, desc.Format); +#else + RasterLevels *levels = (RasterLevels*)ras->texture; + return levels->levels[level].size; +#endif +} + +void +allocateDXT(Raster *raster, int32 dxt, int32 numLevels, bool32 hasAlpha) +{ + static uint32 dxtMap[] = { + 0x31545844, // DXT1 + 0x32545844, // DXT2 + 0x33545844, // DXT3 + 0x34545844, // DXT4 + 0x35545844, // DXT5 + }; + D3dRaster *ras = GETD3DRASTEREXT(raster); + ras->format = dxtMap[dxt-1]; + ras->hasAlpha = hasAlpha; + ras->customFormat = 1; + if(ras->autogenMipmap) + numLevels = 0; + else if(raster->format & Raster::MIPMAP) + {} + else + numLevels = 1; + ras->texture = createTexture(raster->width, raster->height, + numLevels, + ras->autogenMipmap ? D3DUSAGE_AUTOGENMIPMAP : 0, + ras->format); + raster->flags &= ~Raster::DONTALLOCATE; +} + +void +setPalette(Raster *raster, void *palette, int32 size) +{ + D3dRaster *ras = GETD3DRASTEREXT(raster); + memcpy(ras->palette, palette, 4*size); +} + +void +setTexels(Raster *raster, void *texels, int32 level) +{ + uint8 *dst = raster->lock(level, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + memcpy(dst, texels, getLevelSize(raster, level)); + raster->unlock(level); +} + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + D3dRaster *raster = PLUGINOFFSET(D3dRaster, object, offset); + raster->texture = nil; + raster->palette = nil; + raster->lockedSurf = nil; + raster->format = 0; + raster->hasAlpha = 0; + raster->customFormat = 0; + return object; +} + +static void* +destroyNativeRaster(void *object, int32 offset, int32) +{ + Raster *raster = (Raster*)object; + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, offset); +#ifdef RW_D3D9 + removeVidmemRaster(raster); + evictD3D9Raster(raster); +#endif + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + case Raster::CAMERATEXTURE: + destroyTexture(natras->texture); + break; + + case Raster::ZBUFFER: +#ifdef RW_D3D9 + if(raster->flags & Raster::DONTALLOCATE) + break; + if(natras->texture != d3d9Globals.defaultDepthSurf) + ((IDirect3DSurface9*)natras->texture)->Release(); + natras->texture = nil; +#endif + break; + case Raster::CAMERA: + break; + } + rwFree(natras->palette); + return object; +} + +static void* +copyNativeRaster(void *dst, void *, int32 offset, int32) +{ + D3dRaster *raster = PLUGINOFFSET(D3dRaster, dst, offset); + raster->texture = nil; + raster->palette = nil; + raster->lockedSurf = nil; + raster->format = 0; + raster->hasAlpha = 0; + raster->customFormat = 0; + return dst; +} + +void +registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(D3dRaster), + ID_RASTERD3D9, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); +} + +} +} diff --git a/vendor/librw/src/d3d/d3d8.cpp b/vendor/librw/src/d3d/d3d8.cpp new file mode 100644 index 0000000..671948f --- /dev/null +++ b/vendor/librw/src/d3d/d3d8.cpp @@ -0,0 +1,678 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3d8.h" + +#include "rwd3dimpl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace d3d8 { +using namespace d3d; + +static void* +driverOpen(void *o, int32, int32) +{ + engine->driver[PLATFORM_D3D8]->defaultPipeline = makeDefaultPipeline(); + + engine->driver[PLATFORM_D3D8]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_D3D8]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_D3D8]->rasterLock = rasterLock; + engine->driver[PLATFORM_D3D8]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_D3D8]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_D3D8]->imageFindRasterFormat = imageFindRasterFormat; + engine->driver[PLATFORM_D3D8]->rasterFromImage = rasterFromImage; + engine->driver[PLATFORM_D3D8]->rasterToImage = rasterToImage; + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_D3D8, 0, PLATFORM_D3D8, + driverOpen, driverClose); + // shared between D3D8 and 9 + if(nativeRasterOffset == 0) + registerNativeRaster(); +} + +uint32 +makeFVFDeclaration(uint32 flags, int32 numTex) +{ + uint32 fvf = 0x2; + if(flags & Geometry::NORMALS) + fvf |= 0x10; + if(flags & Geometry::PRELIT) + fvf |= 0x40; + fvf |= numTex << 8; + return fvf; +} + +int32 +getStride(uint32 flags, int32 numTex) +{ + int32 stride = 12; + if(flags & Geometry::NORMALS) + stride += 12;; + if(flags & Geometry::PRELIT) + stride += 4; + stride += numTex*8; + return stride; +} + +void* +destroyNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D8) + return object; + InstanceDataHeader *header = + (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + destroyIndexBuffer(inst->indexBuffer); + destroyVertexBuffer(inst->vertexBuffer); + inst++; + } + rwFree(header->inst); + rwFree(header); + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_D3D8){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_D3D8; + + int32 size = stream->readI32(); + uint8 *data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_GEOMETRY); + stream->read8(data, size); + uint8 *p = data; + header->serialNumber = *(uint16*)p; p += 2; + header->numMeshes = *(uint16*)p; p += 2; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + inst->minVert = *(uint32*)p; p += 4; + inst->stride = *(uint32*)p; p += 4; + inst->numVertices = *(uint32*)p; p += 4; + inst->numIndices = *(uint32*)p; p += 4; + uint32 matid = *(uint32*)p; p += 4; + inst->material = geometry->matList.materials[matid]; + inst->vertexShader = *(uint32*)p; p += 4; + inst->primType = *(uint32*)p; p += 4; + inst->indexBuffer = nil; p += 4; + inst->vertexBuffer = nil; p += 4; + inst->baseIndex = 0; p += 4; + inst->vertexAlpha = *p++; + inst->managed = 0; p++; + inst->remapped = 0; p++; // TODO: really unused? and what's that anyway? + inst++; + } + rwFree(data); + + inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + assert(inst->indexBuffer == nil); + inst->indexBuffer = createIndexBuffer(inst->numIndices*2, false); + uint16 *indices = lockIndices(inst->indexBuffer, 0, 0, 0); + stream->read8(indices, 2*inst->numIndices); + unlockIndices(inst->indexBuffer); + + inst->managed = 1; + assert(inst->vertexBuffer == nil); + inst->vertexBuffer = createVertexBuffer(inst->stride*inst->numVertices, 0, false); + uint8 *verts = lockVertices(inst->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + stream->read8(verts, inst->stride*inst->numVertices); + unlockVertices(inst->vertexBuffer); + + inst++; + } + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_STRUCT, len-12); + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D8) + return stream; + stream->writeU32(PLATFORM_D3D8); + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + + int32 size = 4 + geometry->meshHeader->numMeshes*0x2C; + uint8 *data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_GEOMETRY); + stream->writeI32(size); + uint8 *p = data; + *(uint16*)p = header->serialNumber; p += 2; + *(uint16*)p = header->numMeshes; p += 2; + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + *(uint32*)p = inst->minVert; p += 4; + *(uint32*)p = inst->stride; p += 4; + *(uint32*)p = inst->numVertices; p += 4; + *(uint32*)p = inst->numIndices; p += 4; + int32 matid = geometry->matList.findIndex(inst->material); + *(int32*)p = matid; p += 4; + *(uint32*)p = inst->vertexShader; p += 4; + *(uint32*)p = inst->primType; p += 4; + *(uint32*)p = 0; p += 4; // index buffer + *(uint32*)p = 0; p += 4; // vertex buffer + *(uint32*)p = inst->baseIndex; p += 4; + *p++ = inst->vertexAlpha; + *p++ = inst->managed; + *p++ = inst->remapped; + inst++; + } + stream->write8(data, size); + rwFree(data); + + inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + uint16 *indices = lockIndices(inst->indexBuffer, 0, 0, 0); + stream->write8(indices, 2*inst->numIndices); + unlockIndices(inst->indexBuffer); + + uint8 *verts = lockVertices(inst->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + stream->write8(verts, inst->stride*inst->numVertices); + unlockVertices(inst->vertexBuffer); + inst++; + } + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D8) + return 0; + + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + InstanceData *inst = header->inst; + int32 size = 12 + 4 + 4 + 4 + header->numMeshes*0x2C; + for(int32 i = 0; i < header->numMeshes; i++){ + size += inst->numIndices*2 + inst->numVertices*inst->stride; + inst++; + } + return size; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE + if(geo->instData) + return; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + MeshHeader *meshh = geo->meshHeader; + geo->instData = header; + header->platform = PLATFORM_D3D8; + + header->serialNumber = meshh->serialNum; + header->numMeshes = meshh->numMeshes; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + InstanceData *inst = header->inst; + Mesh *mesh = meshh->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &inst->minVert, &inst->numVertices); + inst->numIndices = mesh->numIndices; + inst->material = mesh->material; + inst->vertexShader = 0; + inst->primType = meshh->flags == 1 ? D3DPT_TRIANGLESTRIP : D3DPT_TRIANGLELIST; + inst->vertexBuffer = nil; + inst->baseIndex = 0; // (maybe) not used by us + inst->vertexAlpha = 0; + inst->managed = 0; + inst->remapped = 0; + + inst->indexBuffer = createIndexBuffer(inst->numIndices*2, false); + uint16 *indices = lockIndices(inst->indexBuffer, 0, 0, 0); + if(inst->minVert == 0) + memcpy(indices, mesh->indices, inst->numIndices*2); + else + for(int32 j = 0; j < inst->numIndices; j++) + indices[j] = mesh->indices[j] - inst->minVert; + unlockIndices(inst->indexBuffer); + + pipe->instanceCB(geo, inst); + mesh++; + inst++; + } +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_D3D8); + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + geo->allocateMeshes(geo->meshHeader->numMeshes, geo->meshHeader->totalIndices, 0); + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + InstanceData *inst = header->inst; + Mesh *mesh = geo->meshHeader->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + uint16 *indices = lockIndices(inst->indexBuffer, 0, 0, 0); + if(inst->minVert == 0) + memcpy(mesh->indices, indices, inst->numIndices*2); + else + for(int32 j = 0; j < inst->numIndices; j++) + mesh->indices[j] = indices[j] + inst->minVert; + unlockIndices(inst->indexBuffer); + + pipe->uninstanceCB(geo, inst); + mesh++; + inst++; + } + geo->generateTriangles(); + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +} + +static void +render(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE + if(geo->instData == nil) + pipe->instance(atomic); + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_D3D8); + if(pipe->renderCB) + pipe->renderCB(atomic, (InstanceDataHeader*)geo->instData); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_D3D8); + this->impl.instance = d3d8::instance; + this->impl.uninstance = d3d8::uninstance; + this->impl.render = d3d8::render; + this->instanceCB = nil; + this->uninstanceCB = nil; + this->renderCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +defaultInstanceCB(Geometry *geo, InstanceData *inst) +{ + inst->vertexShader = makeFVFDeclaration(geo->flags, geo->numTexCoordSets); + inst->stride = getStride(geo->flags, geo->numTexCoordSets); + + assert(inst->vertexBuffer == nil); + inst->vertexBuffer = createVertexBuffer(inst->numVertices*inst->stride, + inst->vertexShader, false); + inst->managed = 1; + + uint8 *dst = lockVertices(inst->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + instV3d(VERT_FLOAT3, dst, + &geo->morphTargets[0].vertices[inst->minVert], + inst->numVertices, inst->stride); + dst += 12; + + if(geo->flags & Geometry::NORMALS){ + instV3d(VERT_FLOAT3, dst, + &geo->morphTargets[0].normals[inst->minVert], + inst->numVertices, inst->stride); + dst += 12; + } + + inst->vertexAlpha = 0; + if(geo->flags & Geometry::PRELIT){ + inst->vertexAlpha = instColor(VERT_ARGB, dst, &geo->colors[inst->minVert], + inst->numVertices, inst->stride); + dst += 4; + } + + for(int32 i = 0; i < geo->numTexCoordSets; i++){ + instTexCoords(VERT_FLOAT2, dst, &geo->texCoords[i][inst->minVert], + inst->numVertices, inst->stride); + dst += 8; + } + unlockVertices(inst->vertexBuffer); +} + +void +defaultUninstanceCB(Geometry *geo, InstanceData *inst) +{ + uint8 *src = lockVertices(inst->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + uninstV3d(VERT_FLOAT3, + &geo->morphTargets[0].vertices[inst->minVert], + src, inst->numVertices, inst->stride); + src += 12; + + if(geo->flags & Geometry::NORMALS){ + uninstV3d(VERT_FLOAT3, + &geo->morphTargets[0].normals[inst->minVert], + src, inst->numVertices, inst->stride); + src += 12; + } + + inst->vertexAlpha = 0; + if(geo->flags & Geometry::PRELIT){ + uninstColor(VERT_ARGB, &geo->colors[inst->minVert], src, + inst->numVertices, inst->stride); + src += 4; + } + + for(int32 i = 0; i < geo->numTexCoordSets; i++){ + uninstTexCoords(VERT_FLOAT2, &geo->texCoords[i][inst->minVert], src, + inst->numVertices, inst->stride); + src += 8; + } + unlockVertices(inst->vertexBuffer); +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB; + return pipe; +} + +// Native Texture and Raster + +// only handles 4 and 8 bit textures right now +Raster* +readAsImage(Stream *stream, int32 width, int32 height, int32 depth, int32 format, int32 numLevels) +{ + uint8 palette[256*4]; + int32 pallen = 0; + uint8 *data = nil; + + Image *img = Image::create(width, height, 32); + img->allocate(); + + if(format & Raster::PAL4){ + pallen = 16; + stream->read8(palette, 4*32); + }else if(format & Raster::PAL8){ + pallen = 256; + stream->read8(palette, 4*256); + } + if(!Raster::formatHasAlpha(format)) + for(int32 i = 0; i < pallen; i++) + palette[i*4+3] = 0xFF; + + Raster *ras = nil; + + for(int i = 0; i < numLevels; i++){ + uint32 size = stream->readU32(); + + // don't read levels that don't exist + if(ras && i >= ras->getNumLevels()){ + stream->seek(size); + continue; + } + + // one allocation is enough, first level is largest + if(data == nil) + data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_IMAGE); + stream->read8(data, size); + + if(ras){ + ras->lock(i, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + img->width = ras->width; + img->height = ras->height; + img->stride = img->width*img->bpp; + } + + if(format & (Raster::PAL4 | Raster::PAL8)){ + uint8 *idx = data; + uint8 *pixels = img->pixels; + for(int y = 0; y < img->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < img->width; x++){ + line[0] = palette[*idx*4+0]; + line[1] = palette[*idx*4+1]; + line[2] = palette[*idx*4+2]; + if(img->bpp > 3) + line[3] = palette[*idx*4+3]; + line += img->bpp; + idx++; + } + pixels += img->stride; + } + } + + if(ras == nil){ + // Important to have filled the image with data + int32 newformat; + Raster::imageFindRasterFormat(img, format&7, &width, &height, &depth, &newformat); + newformat |= format & (Raster::MIPMAP | Raster::AUTOMIPMAP); + ras = Raster::create(width, height, depth, newformat); + ras->lock(i, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + } + + ras->setFromImage(img); + ras->unlock(i); + } + + rwFree(data); + img->destroy(); + return ras; +} + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_D3D8){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + // Texture + tex->filterAddressing = stream->readU32(); + stream->read8(tex->name, 32); + stream->read8(tex->mask, 32); + + // Raster + uint32 format = stream->readU32(); + bool32 hasAlpha = stream->readI32(); + int32 width = stream->readU16(); + int32 height = stream->readU16(); + int32 depth = stream->readU8(); + int32 numLevels = stream->readU8(); + int32 type = stream->readU8(); + int32 compression = stream->readU8(); + + int32 pallength = 0; + if(format & Raster::PAL4 || format & Raster::PAL8){ + pallength = format & Raster::PAL4 ? 32 : 256; + if(!d3d::isP8supported){ + tex->raster = readAsImage(stream, width, height, depth, format|type, numLevels); + return tex; + } + } + + Raster *raster; + D3dRaster *ras; + if(compression){ + raster = Raster::create(width, height, depth, format | type | Raster::DONTALLOCATE, PLATFORM_D3D8); + ras = GETD3DRASTEREXT(raster); + allocateDXT(raster, compression, numLevels, hasAlpha); + ras->customFormat = 1; + }else{ + raster = Raster::create(width, height, depth, format | type, PLATFORM_D3D8); + ras = GETD3DRASTEREXT(raster); + } + tex->raster = raster; + + // TODO: check if format supported and convert if necessary + + if(pallength != 0) + stream->read8(ras->palette, 4*pallength); + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = stream->readU32(); + if(i < raster->getNumLevels()){ + data = raster->lock(i, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + stream->read8(data, size); + raster->unlock(i); + }else + stream->seek(size); + } + return tex; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + int32 chunksize = getSizeNativeTexture(tex); + writeChunkHeader(stream, ID_STRUCT, chunksize-12); + stream->writeU32(PLATFORM_D3D8); + + // Texture + stream->writeU32(tex->filterAddressing); + stream->write8(tex->name, 32); + stream->write8(tex->mask, 32); + + // Raster + Raster *raster = tex->raster; + D3dRaster *ras = GETD3DRASTEREXT(raster); + int32 numLevels = raster->getNumLevels(); + stream->writeI32(raster->format); + stream->writeI32(ras->hasAlpha); + stream->writeU16(raster->width); + stream->writeU16(raster->height); + stream->writeU8(raster->depth); + stream->writeU8(numLevels); + stream->writeU8(raster->type); + int32 compression = 0; + if(ras->format) + switch(ras->format){ + case 0x31545844: // DXT1 + compression = 1; + break; + case 0x32545844: // DXT2 + compression = 2; + break; + case 0x33545844: // DXT3 + compression = 3; + break; + case 0x34545844: // DXT4 + compression = 4; + break; + case 0x35545844: // DXT5 + compression = 5; + break; + } + stream->writeU8(compression); + + if(raster->format & Raster::PAL4) + stream->write8(ras->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->write8(ras->palette, 4*256); + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = getLevelSize(raster, i); + stream->writeU32(size); + data = raster->lock(i, Raster::LOCKREAD); + stream->write8(data, size); + raster->unlock(i); + } +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 72 + 16; + int32 levels = tex->raster->getNumLevels(); + if(tex->raster->format & Raster::PAL4) + size += 4*32; + else if(tex->raster->format & Raster::PAL8) + size += 4*256; + for(int32 i = 0; i < levels; i++) + size += 4 + getLevelSize(tex->raster, i); + return size; +} + +} +} diff --git a/vendor/librw/src/d3d/d3d8matfx.cpp b/vendor/librw/src/d3d/d3d8matfx.cpp new file mode 100644 index 0000000..580203e --- /dev/null +++ b/vendor/librw/src/d3d/d3d8matfx.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwd3d.h" +#include "rwd3d8.h" + +namespace rw { +namespace d3d8 { +using namespace d3d; + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_D3D8] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_D3D8])->destroy(); + matFXGlobals.pipelines[PLATFORM_D3D8] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_D3D8, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB; + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +} +} diff --git a/vendor/librw/src/d3d/d3d8render.cpp b/vendor/librw/src/d3d/d3d8render.cpp new file mode 100644 index 0000000..c5375cb --- /dev/null +++ b/vendor/librw/src/d3d/d3d8render.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3d8.h" + +namespace rw { +namespace d3d8 { +using namespace d3d; + +#ifndef RW_D3D9 +void defaultRenderCB(Atomic*, InstanceDataHeader*) {} +#else + +// This is a bit abandoned, use d3d9 instead + +void +defaultRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + RawMatrix world; + + d3d::lightingCB_Fix(atomic); + + uint32 flags = atomic->geometry->flags; + d3d::setRenderState(D3DRS_LIGHTING, !!(flags & rw::Geometry::LIGHT)); + + Frame *f = atomic->getFrame(); + convMatrix(&world, f->getLTM()); + d3ddevice->SetTransform(D3DTS_WORLD, (D3DMATRIX*)&world); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + d3d::setTexture(0, inst->material->texture); + d3d::setMaterial(flags, inst->material->color, inst->material->surfaceProps); + + + d3d::setRenderState(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL); + d3d::setRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL); + if(flags & Geometry::PRELIT) + d3d::setRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1); + else + d3d::setRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + + d3ddevice->SetFVF(inst->vertexShader); + setStreamSource(0, (IDirect3DVertexBuffer9*)inst->vertexBuffer, 0, inst->stride); + setIndices((IDirect3DIndexBuffer9*)inst->indexBuffer); + uint32 numPrim = inst->primType == D3DPT_TRIANGLESTRIP ? inst->numIndices-2 : inst->numIndices/3; + d3d::flushCache(); + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)inst->primType, inst->baseIndex, + 0, inst->numVertices, 0, numPrim); + inst++; + } +} + +#endif +} +} diff --git a/vendor/librw/src/d3d/d3d8skin.cpp b/vendor/librw/src/d3d/d3d8skin.cpp new file mode 100644 index 0000000..d9b0921 --- /dev/null +++ b/vendor/librw/src/d3d/d3d8skin.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwd3d.h" +#include "rwd3d8.h" + +namespace rw { +namespace d3d8 { +using namespace d3d; + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_D3D8] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_D3D8])->destroy(); + skinGlobals.pipelines[PLATFORM_D3D8] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_D3D8, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB; + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + return pipe; +} + +} +} diff --git a/vendor/librw/src/d3d/d3d9.cpp b/vendor/librw/src/d3d/d3d9.cpp new file mode 100644 index 0000000..851397b --- /dev/null +++ b/vendor/librw/src/d3d/d3d9.cpp @@ -0,0 +1,853 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3d9.h" + +#include "rwd3dimpl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace d3d9 { +using namespace d3d; + +// TODO: move to header, but not as #define +#ifndef RW_D3D9 +static VertexElement _d3ddec_end = {0xFF,0,D3DDECLTYPE_UNUSED,0,0,0}; +#define D3DDECL_END() _d3ddec_end +#endif + +#define NUMDECLELT 12 + +static void* +driverOpen(void *o, int32, int32) +{ +#ifdef RW_D3D9 + createDefaultShaders(); +#endif + engine->driver[PLATFORM_D3D9]->defaultPipeline = makeDefaultPipeline(); + + engine->driver[PLATFORM_D3D9]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_D3D9]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_D3D9]->rasterLock = rasterLock; + engine->driver[PLATFORM_D3D9]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_D3D9]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_D3D9]->imageFindRasterFormat = imageFindRasterFormat; + engine->driver[PLATFORM_D3D9]->rasterFromImage = rasterFromImage; + engine->driver[PLATFORM_D3D9]->rasterToImage = rasterToImage; + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ +#ifdef RW_D3D9 + destroyDefaultShaders(); +#endif + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_D3D9, 0, PLATFORM_D3D9, + driverOpen, driverClose); + // shared between D3D8 and 9 + if(nativeRasterOffset == 0) + registerNativeRaster(); +} + +void* +createVertexDeclaration(VertexElement *elements) +{ +#ifdef RW_D3D9 + IDirect3DVertexDeclaration9 *decl = 0; + d3ddevice->CreateVertexDeclaration((D3DVERTEXELEMENT9*)elements, &decl); + if(decl) + d3d9Globals.numVertexDeclarations++; + return decl; +#else + int n = 0; + VertexElement *e = (VertexElement*)elements; + while(e[n++].stream != 0xFF) + ; + e = rwNewT(VertexElement, n, MEMDUR_EVENT | ID_DRIVER); + memcpy(e, elements, n*sizeof(VertexElement)); + return e; +#endif +} + +void +destroyVertexDeclaration(void *declaration) +{ +#ifdef RW_D3D9 + if(declaration){ + if(((IUnknown*)declaration)->Release() != 0) + printf("declaration wasn't destroyed\n"); + d3d9Globals.numVertexDeclarations--; + } +#else + rwFree(declaration); +#endif +} + +uint32 +getDeclaration(void *declaration, VertexElement *elements) +{ +#ifdef RW_D3D9 + IDirect3DVertexDeclaration9 *decl = (IDirect3DVertexDeclaration9*)declaration; + UINT numElt; + decl->GetDeclaration((D3DVERTEXELEMENT9*)elements, &numElt); + return numElt; +#else + int n = 0; + VertexElement *e = (VertexElement*)declaration; + while(e[n++].stream != 0xFF) + ; + if(elements) + memcpy(elements, declaration, n*sizeof(VertexElement)); + return n; +#endif +} + +void +freeInstanceData(Geometry *geometry) +{ + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D9) + return; + InstanceDataHeader *header = + (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + destroyVertexDeclaration(header->vertexDeclaration); + destroyIndexBuffer(header->indexBuffer); + destroyVertexBuffer(header->vertexStream[0].vertexBuffer); + destroyVertexBuffer(header->vertexStream[1].vertexBuffer); + rwFree(header->inst); + rwFree(header); + return; +} + + +void* +destroyNativeData(void *object, int32, int32) +{ + freeInstanceData((Geometry*)object); + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_D3D9){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_D3D9; + + int32 size = stream->readI32(); + uint8 *data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_GEOMETRY); + stream->read8(data, size); + uint8 *p = data; + header->serialNumber = *(uint32*)p; p += 4; + header->numMeshes = *(uint32*)p; p += 4; + header->indexBuffer = nil; p += 4; + header->primType = *(uint32*)p; p += 4; + p += 16*2; // skip vertex streams, they're repeated with the vertex buffers + header->useOffsets = *(bool32*)p; p += 4; + header->vertexDeclaration = nil; p += 4; + header->totalNumIndex = *(uint32*)p; p += 4; + header->totalNumVertex = *(uint32*)p; p += 4; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + inst->numIndex = *(uint32*)p; p += 4; + inst->minVert = *(uint32*)p; p += 4; + uint32 matid = *(uint32*)p; p += 4; + inst->material = geometry->matList.materials[matid]; + inst->vertexAlpha = *(bool32*)p; p += 4; + inst->vertexShader = nil; p += 4; + inst->baseIndex = 0; p += 4; + inst->numVertices = *(uint32*)p; p += 4; + inst->startIndex = *(uint32*)p; p += 4; + inst->numPrimitives = *(uint32*)p; p += 4; + inst++; + } + + VertexElement elements[NUMDECLELT]; + uint32 numDeclarations = stream->readU32(); + stream->read8(elements, numDeclarations*8); + header->vertexDeclaration = createVertexDeclaration(elements); + + assert(header->indexBuffer == nil); + header->indexBuffer = createIndexBuffer(header->totalNumIndex*2, false); + uint16 *indices = lockIndices(header->indexBuffer, 0, 0, 0); + stream->read8(indices, 2*header->totalNumIndex); + unlockIndices(header->indexBuffer); + + VertexStream *s; + p = data; + for(int i = 0; i < 2; i++){ + stream->read8(p, 16); + s = &header->vertexStream[i]; + s->vertexBuffer = (void*)(uintptr)*(uint32*)p; p += 4; + s->offset = 0; p += 4; + s->stride = *(uint32*)p; p += 4; + s->geometryFlags = *(uint16*)p; p += 2; + s->managed = *p++; + s->dynamicLock = *p++; + + if(s->vertexBuffer == nil) + continue; + // TODO: use dynamic VB when doing morphing + assert(s->vertexBuffer == nil); + s->vertexBuffer = createVertexBuffer(s->stride*header->totalNumVertex, 0, false); + uint8 *verts = lockVertices(s->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + stream->read8(verts, s->stride*header->totalNumVertex); + unlockVertices(s->vertexBuffer); + } + + // TODO: somehow depends on number of streams used (baseIndex = minVert when more than one) + inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + inst->baseIndex = inst->minVert + header->vertexStream[0].offset / header->vertexStream[0].stride; + inst++; + } + + rwFree(data); + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_STRUCT, len-12); + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D9) + return stream; + stream->writeU32(PLATFORM_D3D9); + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + int32 size = 64 + geometry->meshHeader->numMeshes*36; + uint8 *data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_GEOMETRY); + stream->writeI32(size); + + uint8 *p = data; + *(uint32*)p = header->serialNumber; p += 4; + *(uint32*)p = header->numMeshes; p += 4; + p += 4; // skip index buffer + *(uint32*)p = header->primType; p += 4; + p += 16*2; // skip vertex streams, they're repeated with the vertex buffers + *(bool32*)p = header->useOffsets; p += 4; + p += 4; // skip vertex declaration + *(uint32*)p = header->totalNumIndex; p += 4; + *(uint32*)p = header->totalNumVertex; p += 4; + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + *(uint32*)p = inst->numIndex; p += 4; + *(uint32*)p = inst->minVert; p += 4; + int32 matid = geometry->matList.findIndex(inst->material); + *(int32*)p = matid; p += 4; + *(bool32*)p = inst->vertexAlpha; p += 4; + *(uint32*)p = 0; p += 4; // vertex shader + *(uint32*)p = inst->baseIndex; p += 4; // not used but meh... + *(uint32*)p = inst->numVertices; p += 4; + *(uint32*)p = inst->startIndex; p += 4; + *(uint32*)p = inst->numPrimitives; p += 4; + inst++; + } + stream->write8(data, size); + + VertexElement elements[NUMDECLELT]; + uint32 numElt = getDeclaration(header->vertexDeclaration, elements); + stream->writeU32(numElt); + stream->write8(elements, 8*numElt); + + uint16 *indices = lockIndices(header->indexBuffer, 0, 0, 0); + stream->write8(indices, 2*header->totalNumIndex); + unlockIndices(header->indexBuffer); + + VertexStream *s; + for(int i = 0; i < 2; i++){ + s = &header->vertexStream[i]; + p = data; + *(uint32*)p = s->vertexBuffer ? 0xbadeaffe : 0; p += 4; + *(uint32*)p = s->offset; p += 4; + *(uint32*)p = s->stride; p += 4; + *(uint16*)p = s->geometryFlags; p += 2; + *p++ = s->managed; + *p++ = s->dynamicLock; + stream->write8(data, 16); + + if(s->vertexBuffer == nil) + continue; + uint8 *verts = lockVertices(s->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + stream->write8(verts, s->stride*header->totalNumVertex); + unlockVertices(s->vertexBuffer); + } + + rwFree(data); + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D9) + return 0; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + int32 size = 12 + 4 + 4 + 64 + header->numMeshes*36; + uint32 numElt = getDeclaration(header->vertexDeclaration, nil); + size += 4 + numElt*8; + size += 2*header->totalNumIndex; + size += 0x10 + header->vertexStream[0].stride*header->totalNumVertex; + size += 0x10 + header->vertexStream[1].stride*header->totalNumVertex; + return size; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +static InstanceDataHeader* +instanceMesh(rw::ObjPipeline *rwpipe, Geometry *geo) +{ + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + MeshHeader *meshh = geo->meshHeader; + header->platform = PLATFORM_D3D9; + + header->serialNumber = meshh->serialNum; + header->numMeshes = meshh->numMeshes; + header->primType = meshh->flags == 1 ? D3DPT_TRIANGLESTRIP : D3DPT_TRIANGLELIST; + header->useOffsets = 0; + header->vertexDeclaration = nil; + header->totalNumVertex = geo->numVertices; + header->totalNumIndex = meshh->totalIndices; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + header->indexBuffer = createIndexBuffer(header->totalNumIndex*2, false); + + uint16 *indices = lockIndices(header->indexBuffer, 0, 0, 0); + InstanceData *inst = header->inst; + Mesh *mesh = meshh->getMeshes(); + uint32 startindex = 0; + for(uint32 i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &inst->minVert, (int32*)&inst->numVertices); + inst->numIndex = mesh->numIndices; + inst->material = mesh->material; + inst->vertexAlpha = 0; + inst->vertexShader = nil; + inst->baseIndex = inst->minVert; + inst->startIndex = startindex; + inst->numPrimitives = header->primType == D3DPT_TRIANGLESTRIP ? inst->numIndex-2 : inst->numIndex/3; + if(inst->minVert == 0) + memcpy(&indices[inst->startIndex], mesh->indices, inst->numIndex*2); + else + for(uint32 j = 0; j < inst->numIndex; j++) + indices[inst->startIndex+j] = mesh->indices[j] - inst->minVert; + startindex += inst->numIndex; + mesh++; + inst++; + } + unlockIndices(header->indexBuffer); + + memset(&header->vertexStream, 0, 2*sizeof(VertexStream)); + + return header; +} + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // don't try to (re)instance native data + if(geo->flags & Geometry::NATIVE) + return; + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + if(geo->instData){ + // Already have instanced data, so check if we have to reinstance + assert(header->platform == PLATFORM_D3D9); + if(header->serialNumber != geo->meshHeader->serialNum){ + // Mesh changed, so reinstance everything + freeInstanceData(geo); + } + } + + // no instance or complete reinstance + if(geo->instData == nil){ + geo->instData = instanceMesh(rwpipe, geo); + pipe->instanceCB(geo, (InstanceDataHeader*)geo->instData, 0); + }else if(geo->lockedSinceInst) + pipe->instanceCB(geo, (InstanceDataHeader*)geo->instData, 1); + + geo->lockedSinceInst = 0; +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_D3D9); + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + geo->allocateMeshes(geo->meshHeader->numMeshes, geo->meshHeader->totalIndices, 0); + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + uint16 *indices = lockIndices(header->indexBuffer, 0, 0, 0); + InstanceData *inst = header->inst; + Mesh *mesh = geo->meshHeader->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + if(inst->minVert == 0) + memcpy(mesh->indices, &indices[inst->startIndex], inst->numIndex*2); + else + for(uint32 j = 0; j < inst->numIndex; j++) + mesh->indices[j] = indices[inst->startIndex+j] + inst->minVert; + mesh++; + inst++; + } + unlockIndices(header->indexBuffer); + + pipe->uninstanceCB(geo, header); + geo->generateTriangles(); + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +} + +static void +render(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + pipe->instance(atomic); + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_D3D9); + if(pipe->renderCB) + pipe->renderCB(atomic, (InstanceDataHeader*)geo->instData); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_D3D9); + this->impl.instance = d3d9::instance; + this->impl.uninstance = d3d9::uninstance; + this->impl.render = d3d9::render; + this->instanceCB = nil; + this->uninstanceCB = nil; + this->renderCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +defaultInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) +{ + int i = 0; + VertexElement dcl[NUMDECLELT]; + VertexStream *s = &header->vertexStream[0]; + + bool isPrelit = (geo->flags & Geometry::PRELIT) != 0; + bool hasNormals = (geo->flags & Geometry::NORMALS) != 0; + + // TODO: support both vertex buffers + + if(!reinstance){ + // Create declarations and buffers only the first time + + assert(s->vertexBuffer == nil); + s->offset = 0; + s->managed = 1; + s->geometryFlags = 0; + s->dynamicLock = 0; + + dcl[i].stream = 0; + dcl[i].offset = 0; + dcl[i].type = D3DDECLTYPE_FLOAT3; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_POSITION; + dcl[i].usageIndex = 0; + i++; + uint16 stride = 12; + s->geometryFlags |= 0x2; + + if(isPrelit){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_D3DCOLOR; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_COLOR; + dcl[i].usageIndex = 0; + i++; + s->geometryFlags |= 0x8; + stride += 4; + } + + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT2; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_TEXCOORD; + dcl[i].usageIndex = (uint8)n; + i++; + s->geometryFlags |= 0x10 << n; + stride += 8; + } + + if(hasNormals){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT3; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_NORMAL; + dcl[i].usageIndex = 0; + i++; + s->geometryFlags |= 0x4; + stride += 12; + } + + // We expect some attributes to always be there, use the constant buffer as fallback + if(!isPrelit){ + dcl[i].stream = 2; + dcl[i].offset = offsetof(VertexConstantData, color); + dcl[i].type = D3DDECLTYPE_D3DCOLOR; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_COLOR; + dcl[i].usageIndex = 0; + i++; + } + if(geo->numTexCoordSets == 0){ + dcl[i].stream = 2; + dcl[i].offset = offsetof(VertexConstantData, texCoors[0]); + dcl[i].type = D3DDECLTYPE_FLOAT2; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_TEXCOORD; + dcl[i].usageIndex = 0; + i++; + } + + dcl[i] = D3DDECL_END(); + s->stride = stride; + + assert(header->vertexDeclaration == nil); + header->vertexDeclaration = createVertexDeclaration((VertexElement*)dcl); + + assert(s->vertexBuffer == nil); + s->vertexBuffer = createVertexBuffer(header->totalNumVertex*s->stride, 0, false); + }else + getDeclaration(header->vertexDeclaration, dcl); + + uint8 *verts = lockVertices(s->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + + // Instance vertices + if(!reinstance || geo->lockedSinceInst&Geometry::LOCKVERTICES){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_POSITION || dcl[i].usageIndex != 0; i++) + ; + instV3d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + geo->morphTargets[0].vertices, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + // Instance prelight colors + if(isPrelit && (!reinstance || geo->lockedSinceInst&Geometry::LOCKPRELIGHT)){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_COLOR || dcl[i].usageIndex != 0; i++) + ; + InstanceData *inst = header->inst; + uint32 n = header->numMeshes; + while(n--){ + uint32 stride = header->vertexStream[dcl[i].stream].stride; + inst->vertexAlpha = instColor(vertFormatMap[dcl[i].type], + verts + dcl[i].offset + stride*inst->minVert, + geo->colors + inst->minVert, + inst->numVertices, + stride); + inst++; + } + } + + // Instance tex coords + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + if(!reinstance || geo->lockedSinceInst&(Geometry::LOCKTEXCOORDS<texCoords[n], + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + } + + // Instance normals + if(hasNormals && (!reinstance || geo->lockedSinceInst&Geometry::LOCKNORMALS)){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_NORMAL || dcl[i].usageIndex != 0; i++) + ; + instV3d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + geo->morphTargets[0].normals, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + unlockVertices(s->vertexBuffer); +} + +void +defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + VertexElement dcl[NUMDECLELT]; + + uint8 *verts[2]; + verts[0] = lockVertices(header->vertexStream[0].vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + verts[1] = lockVertices(header->vertexStream[1].vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + getDeclaration(header->vertexDeclaration, dcl); + + int i; + for(i = 0; dcl[i].usage != D3DDECLUSAGE_POSITION || dcl[i].usageIndex != 0; i++) + ; + uninstV3d(vertFormatMap[dcl[i].type], + geo->morphTargets[0].vertices, + verts[dcl[i].stream] + dcl[i].offset, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + + if(geo->flags & Geometry::PRELIT){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_COLOR || dcl[i].usageIndex != 0; i++) + ; + uninstColor(vertFormatMap[dcl[i].type], + geo->colors, + verts[dcl[i].stream] + dcl[i].offset, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_TEXCOORD || dcl[i].usageIndex != n; i++) + ; + uninstTexCoords(vertFormatMap[dcl[i].type], + geo->texCoords[n], + verts[dcl[i].stream] + dcl[i].offset, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + if(geo->flags & Geometry::NORMALS){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_NORMAL || dcl[i].usageIndex != 0; i++) + ; + uninstV3d(vertFormatMap[dcl[i].type], + geo->morphTargets[0].normals, + verts[dcl[i].stream] + dcl[i].offset, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + unlockVertices(verts[0]); + unlockVertices(verts[1]); +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB_Shader; + return pipe; +} + +// Native Texture and Raster + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_D3D9){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + // Texture + tex->filterAddressing = stream->readU32(); + stream->read8(tex->name, 32); + stream->read8(tex->mask, 32); + + // Raster + int32 format = stream->readI32(); + int32 d3dformat = stream->readI32(); + int32 width = stream->readU16(); + int32 height = stream->readU16(); + int32 depth = stream->readU8(); + int32 numLevels = stream->readU8(); + int32 type = stream->readU8(); +/* +#define HAS_ALPHA (1<<0) +#define IS_CUBE (1<<1) +#define USE_AUTOMIPMAPGEN (1<<2) +#define IS_COMPRESSED (1<<3) +*/ + int32 flags = stream->readU8(); + + Raster *raster; + D3dRaster *ext; + + if(flags & 8){ + // is compressed + assert((flags & 2) == 0 && "Can't have cube maps yet"); + raster = Raster::create(width, height, depth, format | type | Raster::DONTALLOCATE, PLATFORM_D3D9); + assert(raster); + ext = GETD3DRASTEREXT(raster); + ext->format = d3dformat; + ext->hasAlpha = flags & 1; + ext->texture = createTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? numLevels : 1, + 0, + ext->format); + assert(ext->texture); + raster->flags &= ~Raster::DONTALLOCATE; + ext->customFormat = 1; + }else if(flags & 2){ + assert(0 && "Can't have cube maps yet"); + }else{ + raster = Raster::create(width, height, depth, format | type, PLATFORM_D3D9); + assert(raster); + ext = GETD3DRASTEREXT(raster); + } + tex->raster = raster; + + // TODO: check if format supported and convert if necessary + + if(raster->format & Raster::PAL4) + stream->read8(ext->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->read8(ext->palette, 4*256); + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = stream->readU32(); + if(i < raster->getNumLevels()){ + data = raster->lock(i, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + stream->read8(data, size); + raster->unlock(i); + }else + stream->seek(size); + } + return tex; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + int32 chunksize = getSizeNativeTexture(tex); + writeChunkHeader(stream, ID_STRUCT, chunksize-12); + stream->writeU32(PLATFORM_D3D9); + + // Texture + stream->writeU32(tex->filterAddressing); + stream->write8(tex->name, 32); + stream->write8(tex->mask, 32); + + // Raster + Raster *raster = tex->raster; + D3dRaster *ext = GETD3DRASTEREXT(raster); + int32 numLevels = raster->getNumLevels(); + stream->writeI32(raster->format); + stream->writeU32(ext->format); + stream->writeU16(raster->width); + stream->writeU16(raster->height); + stream->writeU8(raster->depth); + stream->writeU8(numLevels); + stream->writeU8(raster->type); + uint8 flags = 0; + if(ext->hasAlpha) + flags |= 1; + // no cube supported yet + if(ext->autogenMipmap) + flags |= 4; + if(ext->customFormat) + flags |= 8; + stream->writeU8(flags); + + if(raster->format & Raster::PAL4) + stream->write8(ext->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->write8(ext->palette, 4*256); + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = getLevelSize(raster, i); + stream->writeU32(size); + data = raster->lock(i, Raster::LOCKREAD); + stream->write8(data, size); + raster->unlock(i); + } +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 72 + 16; + int32 levels = tex->raster->getNumLevels(); + if(tex->raster->format & Raster::PAL4) + size += 4*32; + else if(tex->raster->format & Raster::PAL8) + size += 4*256; + for(int32 i = 0; i < levels; i++) + size += 4 + getLevelSize(tex->raster, i); + return size; +} + +} +} diff --git a/vendor/librw/src/d3d/d3d9matfx.cpp b/vendor/librw/src/d3d/d3d9matfx.cpp new file mode 100644 index 0000000..d544e65 --- /dev/null +++ b/vendor/librw/src/d3d/d3d9matfx.cpp @@ -0,0 +1,313 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwrender.h" +#include "../rwplugins.h" +#include "rwd3d.h" +#include "rwd3d9.h" + +namespace rw { +namespace d3d9 { +using namespace d3d; + +#ifndef RW_D3D9 +void matfxRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header) {} +#else + +static void *matfx_env_amb_VS; +static void *matfx_env_amb_dir_VS; +static void *matfx_env_all_VS; +static void *matfx_env_PS; +static void *matfx_env_tex_PS; + +enum +{ + VSLOC_texMat = VSLOC_afterLights, + VSLOC_colorClamp = VSLOC_texMat + 4, + VSLOC_envColor, + + PSLOC_shininess = 1, +}; + +void +matfxRender_Default(InstanceDataHeader *header, InstanceData *inst, int32 lightBits) +{ + Material *m = inst->material; + + // Pick a shader + if((lightBits & VSLIGHT_MASK) == 0) + setVertexShader(default_amb_VS); + else if((lightBits & VSLIGHT_MASK) == VSLIGHT_DIRECT) + setVertexShader(default_amb_dir_VS); + else + setVertexShader(default_all_VS); + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + if(inst->material->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); +} + +static Frame *lastEnvFrame; + +static RawMatrix normal2texcoord = { + { 0.5f, 0.0f, 0.0f }, 0.0f, + { 0.0f, -0.5f, 0.0f }, 0.0f, + { 0.0f, 0.0f, 1.0f }, 0.0f, + { 0.5f, 0.5f, 0.0f }, 1.0f +}; + +void +uploadEnvMatrix(Frame *frame) +{ + Matrix invMat; + if(frame == nil) + frame = engine->currentCamera->getFrame(); + + // cache the matrix across multiple meshes +// can't do it, frame matrix may change +// if(frame == lastEnvFrame) +// return; +// lastEnvFrame = frame; + + RawMatrix envMtx, invMtx; + Matrix::invert(&invMat, frame->getLTM()); + convMatrix(&invMtx, &invMat); + invMtx.pos.set(0.0f, 0.0f, 0.0f); + float uscale = fabs(normal2texcoord.right.x); + normal2texcoord.right.x = MatFX::envMapFlipU ? -uscale : uscale; + RawMatrix::mult(&envMtx, &invMtx, &normal2texcoord); + d3ddevice->SetVertexShaderConstantF(VSLOC_texMat, (float*)&envMtx, 4); +} + +void +matfxRender_EnvMap(InstanceDataHeader *header, InstanceData *inst, int32 lightBits, MatFX::Env *env) +{ + Material *m = inst->material; + + if(env->tex == nil || env->coefficient == 0.0f){ + matfxRender_Default(header, inst, lightBits); + return; + } + + d3d::setTexture(1, env->tex); + uploadEnvMatrix(env->frame); + + SetRenderState(SRCBLEND, BLENDONE); + + static float zero[4]; + static float one[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + struct { + float shininess; + float disableFBA; + float unused[2]; + } fxparams; + fxparams.shininess = env->coefficient; + fxparams.disableFBA = env->fbAlpha ? 0.0f : 1.0f; + d3ddevice->SetPixelShaderConstantF(PSLOC_shininess, (float*)&fxparams, 1); + // This clamps the vertex color below. With it we can achieve both PC and PS2 style matfx + if(MatFX::envMapApplyLight) + d3ddevice->SetVertexShaderConstantF(VSLOC_colorClamp, zero, 1); + else + d3ddevice->SetVertexShaderConstantF(VSLOC_colorClamp, one, 1); + RGBAf envcol[4]; + if(MatFX::envMapUseMatColor) + convColor(envcol, &m->color); + else + convColor(envcol, &MatFX::envMapColor); + d3ddevice->SetVertexShaderConstantF(VSLOC_envColor, (float*)&envcol, 1); + + // Pick a shader + if((lightBits & VSLIGHT_MASK) == 0) + setVertexShader(matfx_env_amb_VS); + else if((lightBits & VSLIGHT_MASK) == VSLIGHT_DIRECT) + setVertexShader(matfx_env_amb_dir_VS); + else + setVertexShader(matfx_env_all_VS); + + bool32 texAlpha = GETD3DRASTEREXT(env->tex->raster)->hasAlpha; + + if(inst->material->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(matfx_env_tex_PS); + }else + setPixelShader(matfx_env_PS); + + SetRenderState(VERTEXALPHA, texAlpha || inst->vertexAlpha || m->color.alpha != 255); + + drawInst(header, inst); + + SetRenderState(SRCBLEND, BLENDSRCALPHA); +} + +void +matfxRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header) +{ + int vsBits; + uint32 flags = atomic->geometry->flags; + setStreamSource(0, (IDirect3DVertexBuffer9*)header->vertexStream[0].vertexBuffer, + 0, header->vertexStream[0].stride); + setIndices((IDirect3DIndexBuffer9*)header->indexBuffer); + setVertexDeclaration((IDirect3DVertexDeclaration9*)header->vertexDeclaration); + + lastEnvFrame = nil; + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + bool normals = !!(atomic->geometry->flags & Geometry::NORMALS); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + MatFX *matfx = MatFX::get(m); + if(matfx == nil) + matfxRender_Default(header, inst, vsBits); + else switch(matfx->type){ + case MatFX::ENVMAP: + if(normals) + matfxRender_EnvMap(header, inst, vsBits, &matfx->fx[0].env); + else + matfxRender_Default(header, inst, vsBits); + break; + case MatFX::NOTHING: + case MatFX::BUMPMAP: + case MatFX::BUMPENVMAP: + case MatFX::DUAL: + case MatFX::UVTRANSFORM: + case MatFX::DUALUVTRANSFORM: + // not supported yet + matfxRender_Default(header, inst, vsBits); + break; + } + + inst++; + } + d3d::setTexture(1, nil); +} + +#define VS_NAME g_vs20_main +#define PS_NAME g_ps20_main + +void +createMatFXShaders(void) +{ + { + static +#include "shaders/matfx_env_amb_VS.h" + matfx_env_amb_VS = createVertexShader((void*)VS_NAME); + assert(matfx_env_amb_VS); + } + { + static +#include "shaders/matfx_env_amb_dir_VS.h" + matfx_env_amb_dir_VS = createVertexShader((void*)VS_NAME); + assert(matfx_env_amb_dir_VS); + } + { + static +#include "shaders/matfx_env_all_VS.h" + matfx_env_all_VS = createVertexShader((void*)VS_NAME); + assert(matfx_env_all_VS); + } + + + { + static +#include "shaders/matfx_env_PS.h" + matfx_env_PS = createPixelShader((void*)PS_NAME); + assert(matfx_env_PS); + } + { + static +#include "shaders/matfx_env_tex_PS.h" + matfx_env_tex_PS = createPixelShader((void*)PS_NAME); + assert(matfx_env_tex_PS); + } + +} + +void +destroyMatFXShaders(void) +{ + destroyVertexShader(matfx_env_amb_VS); + matfx_env_amb_VS = nil; + + destroyVertexShader(matfx_env_amb_dir_VS); + matfx_env_amb_dir_VS = nil; + + destroyVertexShader(matfx_env_all_VS); + matfx_env_all_VS = nil; + + + destroyPixelShader(matfx_env_PS); + matfx_env_PS = nil; + + destroyPixelShader(matfx_env_tex_PS); + matfx_env_tex_PS = nil; +} + +#endif + +static void* +matfxOpen(void *o, int32, int32) +{ +#ifdef RW_D3D9 + createMatFXShaders(); +#endif + + matFXGlobals.pipelines[PLATFORM_D3D9] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ +#ifdef RW_D3D9 + destroyMatFXShaders(); +#endif + + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_D3D9])->destroy(); + matFXGlobals.pipelines[PLATFORM_D3D9] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_D3D9, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = matfxRenderCB_Shader; + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +} +} diff --git a/vendor/librw/src/d3d/d3d9render.cpp b/vendor/librw/src/d3d/d3d9render.cpp new file mode 100644 index 0000000..89d937f --- /dev/null +++ b/vendor/librw/src/d3d/d3d9render.cpp @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "../rwrender.h" +#include "rwd3d.h" +#include "rwd3d9.h" + +namespace rw { +namespace d3d9 { +using namespace d3d; + +#ifndef RW_D3D9 +void defaultRenderCB(Atomic*, InstanceDataHeader*) {} +void defaultRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header) {} +#else + +void +drawInst_simple(d3d9::InstanceDataHeader *header, d3d9::InstanceData *inst) +{ + d3d::flushCache(); + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)header->primType, inst->baseIndex, + 0, inst->numVertices, + inst->startIndex, inst->numPrimitives); +} + +// Emulate PS2 GS alpha test FB_ONLY case: failed alpha writes to frame- but not to depth buffer +void +drawInst_GSemu(d3d9::InstanceDataHeader *header, InstanceData *inst) +{ + uint32 hasAlpha; + int alphafunc, alpharef, gsalpharef; + int zwrite; + d3d::getRenderState(D3DRS_ALPHABLENDENABLE, &hasAlpha); + if(hasAlpha){ + zwrite = rw::GetRenderState(rw::ZWRITEENABLE); + alphafunc = rw::GetRenderState(rw::ALPHATESTFUNC); + if(zwrite){ + alpharef = rw::GetRenderState(rw::ALPHATESTREF); + gsalpharef = rw::GetRenderState(rw::GSALPHATESTREF); + + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAGREATEREQUAL); + SetRenderState(rw::ALPHATESTREF, gsalpharef); + drawInst_simple(header, inst); + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHALESS); + SetRenderState(rw::ZWRITEENABLE, 0); + drawInst_simple(header, inst); + SetRenderState(rw::ZWRITEENABLE, 1); + SetRenderState(rw::ALPHATESTFUNC, alphafunc); + SetRenderState(rw::ALPHATESTREF, alpharef); + }else{ + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAALWAYS); + drawInst_simple(header, inst); + SetRenderState(rw::ALPHATESTFUNC, alphafunc); + } + }else + drawInst_simple(header, inst); +} + +void +drawInst(d3d9::InstanceDataHeader *header, d3d9::InstanceData *inst) +{ + if(rw::GetRenderState(rw::GSALPHATEST)) + drawInst_GSemu(header, inst); + else + drawInst_simple(header, inst); +} + +/* +void +defaultRenderCB_Fix(Atomic *atomic, InstanceDataHeader *header) +{ + RawMatrix world; + Geometry *geo = atomic->geometry; + + int lighting = !!(geo->flags & rw::Geometry::LIGHT); + if(lighting) + d3d::lightingCB_Fix(atomic); + + d3d::setRenderState(D3DRS_LIGHTING, lighting); + + Frame *f = atomic->getFrame(); + convMatrix(&world, f->getLTM()); + d3ddevice->SetTransform(D3DTS_WORLD, (D3DMATRIX*)&world); + + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + SetRenderState(VERTEXALPHA, inst->vertexAlpha || inst->material->color.alpha != 255); + const static rw::RGBA white = { 255, 255, 255, 255 }; + d3d::setMaterial(white, inst->material->surfaceProps); + + d3d::setRenderState(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL); + if(geo->flags & Geometry::PRELIT) + d3d::setRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1); + else + d3d::setRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + d3d::setRenderState(D3DRS_DIFFUSEMATERIALSOURCE, inst->vertexAlpha ? D3DMCS_COLOR1 : D3DMCS_MATERIAL); + + if(inst->material->texture){ + // Texture + d3d::setTexture(0, inst->material->texture); + d3d::setTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + d3d::setTextureStageState(0, D3DTSS_COLORARG1, D3DTA_CURRENT); + d3d::setTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE); + d3d::setTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + d3d::setTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + d3d::setTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE); + }else{ + d3d::setTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + d3d::setTextureStageState(0, D3DTSS_COLORARG1, D3DTA_CURRENT); + d3d::setTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + d3d::setTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + } + + // Material colour + const rw::RGBA *col = &inst->material->color; + d3d::setTextureStageState(1, D3DTSS_CONSTANT, D3DCOLOR_ARGB(col->alpha,col->red,col->green,col->blue)); + d3d::setTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE); + d3d::setTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + d3d::setTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CONSTANT); + d3d::setTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + d3d::setTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + d3d::setTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CONSTANT); + + drawInst(header, inst); + inst++; + } + d3d::setTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + d3d::setTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); +} +*/ + +void +defaultRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header) +{ + int vsBits; + uint32 flags = atomic->geometry->flags; + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + // Pick a shader + if((vsBits & VSLIGHT_MASK) == 0) + setVertexShader(default_amb_VS); + else if((vsBits & VSLIGHT_MASK) == VSLIGHT_DIRECT) + setVertexShader(default_amb_dir_VS); + else + setVertexShader(default_all_VS); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + setMaterial(flags, m->color, m->surfaceProps); + + if(m->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); + inst++; + } +} + +#endif +} +} diff --git a/vendor/librw/src/d3d/d3d9skin.cpp b/vendor/librw/src/d3d/d3d9skin.cpp new file mode 100644 index 0000000..86c79ab --- /dev/null +++ b/vendor/librw/src/d3d/d3d9skin.cpp @@ -0,0 +1,418 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwd3d.h" +#include "rwd3d9.h" + +namespace rw { +namespace d3d9 { +using namespace d3d; + +#ifndef RW_D3D9 +void skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) {} +void skinRenderCB(Atomic *atomic, InstanceDataHeader *header) {} +#else + + +static void *skin_amb_VS; +static void *skin_amb_dir_VS; +static void *skin_all_VS; + +#define NUMDECLELT 14 + +void +skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) +{ + int i = 0; + VertexElement dcl[NUMDECLELT]; + VertexStream *s = &header->vertexStream[0]; + + bool isPrelit = (geo->flags & Geometry::PRELIT) != 0; + bool hasNormals = (geo->flags & Geometry::NORMALS) != 0; + + // TODO: support both vertex buffers + + if(!reinstance){ + // Create declarations and buffers only the first time + + assert(s->vertexBuffer == nil); + s->offset = 0; + s->managed = 1; + s->geometryFlags = 0; + s->dynamicLock = 0; + + dcl[i].stream = 0; + dcl[i].offset = 0; + dcl[i].type = D3DDECLTYPE_FLOAT3; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_POSITION; + dcl[i].usageIndex = 0; + i++; + uint16 stride = 12; + s->geometryFlags |= 0x2; + + if(isPrelit){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_D3DCOLOR; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_COLOR; + dcl[i].usageIndex = 0; + i++; + s->geometryFlags |= 0x8; + stride += 4; + } + + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT2; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_TEXCOORD; + dcl[i].usageIndex = (uint8)n; + i++; + s->geometryFlags |= 0x10 << n; + stride += 8; + } + + if(hasNormals){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT3; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_NORMAL; + dcl[i].usageIndex = 0; + i++; + s->geometryFlags |= 0x4; + stride += 12; + } + + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT4; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_BLENDWEIGHT; + dcl[i].usageIndex = 0; + i++; + stride += 16; + + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_UBYTE4; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_BLENDINDICES; + dcl[i].usageIndex = 0; + i++; + stride += 4; + + // We expect some attributes to always be there, use the constant buffer as fallback + if(!isPrelit){ + dcl[i].stream = 2; + dcl[i].offset = offsetof(VertexConstantData, color); + dcl[i].type = D3DDECLTYPE_D3DCOLOR; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_COLOR; + dcl[i].usageIndex = 0; + i++; + } + if(geo->numTexCoordSets == 0){ + dcl[i].stream = 2; + dcl[i].offset = offsetof(VertexConstantData, texCoors[0]); + dcl[i].type = D3DDECLTYPE_FLOAT2; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_TEXCOORD; + dcl[i].usageIndex = 0; + i++; + } + + dcl[i] = D3DDECL_END(); + s->stride = stride; + + assert(header->vertexDeclaration == nil); + header->vertexDeclaration = createVertexDeclaration((VertexElement*)dcl); + + assert(s->vertexBuffer == nil); + s->vertexBuffer = createVertexBuffer(header->totalNumVertex*s->stride, 0, false); + }else + getDeclaration(header->vertexDeclaration, dcl); + + Skin *skin = Skin::get(geo); + uint8 *verts = lockVertices(s->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + + // Instance vertices + if(!reinstance || geo->lockedSinceInst&Geometry::LOCKVERTICES){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_POSITION || dcl[i].usageIndex != 0; i++) + ; + instV3d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + geo->morphTargets[0].vertices, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + // Instance prelight colors + if(isPrelit && (!reinstance || geo->lockedSinceInst&Geometry::LOCKPRELIGHT)){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_COLOR || dcl[i].usageIndex != 0; i++) + ; + InstanceData *inst = header->inst; + uint32 n = header->numMeshes; + while(n--){ + uint32 stride = header->vertexStream[dcl[i].stream].stride; + inst->vertexAlpha = instColor(vertFormatMap[dcl[i].type], + verts + dcl[i].offset + stride*inst->minVert, + geo->colors + inst->minVert, + inst->numVertices, + stride); + inst++; + } + } + + // Instance tex coords + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + if(!reinstance || geo->lockedSinceInst&(Geometry::LOCKTEXCOORDS<texCoords[n], + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + } + + // Instance normals + if(hasNormals && (!reinstance || geo->lockedSinceInst&Geometry::LOCKNORMALS)){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_NORMAL || dcl[i].usageIndex != 0; i++) + ; + instV3d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + geo->morphTargets[0].normals, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + // Instance skin weights + if(!reinstance){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_BLENDWEIGHT || dcl[i].usageIndex != 0; i++) + ; + instV4d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + (V4d*)skin->weights, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + // Instance skin indices + if(!reinstance){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_BLENDINDICES || dcl[i].usageIndex != 0; i++) + ; + // not really colors of course but what the heck + instColor(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + (RGBA*)skin->indices, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + unlockVertices(s->vertexBuffer); +} + +enum +{ + VSLOC_boneMatrices = VSLOC_afterLights +}; + +static float skinMatrices[64*16]; + +void +uploadSkinMatrices(Atomic *a) +{ + int i; + Skin *skin = Skin::get(a->geometry); + float *m = skinMatrices; + HAnimHierarchy *hier = Skin::getHierarchy(a); + + if(hier){ + Matrix *invMats = (Matrix*)skin->inverseMatrices; + Matrix tmp, tmp2; + + assert(skin->numBones == hier->numNodes); + if(hier->flags & HAnimHierarchy::LOCALSPACEMATRICES){ + for(i = 0; i < hier->numNodes; i++){ + invMats[i].flags = 0; + Matrix::mult(&tmp, &invMats[i], &hier->matrices[i]); + RawMatrix::transpose((RawMatrix*)m, (RawMatrix*)&tmp); + m += 12; + } + }else{ + Matrix invAtmMat; + Matrix::invert(&invAtmMat, a->getFrame()->getLTM()); + for(i = 0; i < hier->numNodes; i++){ + invMats[i].flags = 0; + Matrix::mult(&tmp, &hier->matrices[i], &invAtmMat); + Matrix::mult(&tmp2, &invMats[i], &tmp); + RawMatrix::transpose((RawMatrix*)m, (RawMatrix*)&tmp2); + m += 12; + } + } + }else{ + for(i = 0; i < skin->numBones; i++){ + m[0] = 1.0f; + m[1] = 0.0f; + m[2] = 0.0f; + m[3] = 0.0f; + + m[4] = 0.0f; + m[5] = 1.0f; + m[6] = 0.0f; + m[7] = 0.0f; + + m[8] = 0.0f; + m[9] = 0.0f; + m[10] = 1.0f; + m[11] = 0.0f; + + m += 12; + } + } + d3ddevice->SetVertexShaderConstantF(VSLOC_boneMatrices, skinMatrices, skin->numBones*3); +} + +void +skinRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + int vsBits; + uint32 flags = atomic->geometry->flags; + setStreamSource(0, (IDirect3DVertexBuffer9*)header->vertexStream[0].vertexBuffer, + 0, header->vertexStream[0].stride); + setIndices((IDirect3DIndexBuffer9*)header->indexBuffer); + setVertexDeclaration((IDirect3DVertexDeclaration9*)header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + uploadSkinMatrices(atomic); + + // Pick a shader + if((vsBits & VSLIGHT_MASK) == 0) + setVertexShader(skin_amb_VS); + else if((vsBits & VSLIGHT_MASK) == VSLIGHT_DIRECT) + setVertexShader(skin_amb_dir_VS); + else + setVertexShader(skin_all_VS); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + setMaterial(flags, m->color, m->surfaceProps); + + if(inst->material->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); + inst++; + } +} + +#define VS_NAME g_vs20_main +#define PS_NAME g_ps20_main + +void +createSkinShaders(void) +{ + { + static +#include "shaders/skin_amb_VS.h" + skin_amb_VS = createVertexShader((void*)VS_NAME); + assert(skin_amb_VS); + } + { + static +#include "shaders/skin_amb_dir_VS.h" + skin_amb_dir_VS = createVertexShader((void*)VS_NAME); + assert(skin_amb_dir_VS); + } + // Skinning takes a lot of instructions....lighting may be not possible + // TODO: should do something about this + { + static +#include "shaders/skin_all_VS.h" + skin_all_VS = createVertexShader((void*)VS_NAME); +// assert(skin_all_VS); + } +} + +void +destroySkinShaders(void) +{ + destroyVertexShader(skin_amb_VS); + skin_amb_VS = nil; + + destroyVertexShader(skin_amb_dir_VS); + skin_amb_dir_VS = nil; + + if(skin_all_VS){ + destroyVertexShader(skin_all_VS); + skin_all_VS = nil; + } +} + +#endif + +static void* +skinOpen(void *o, int32, int32) +{ +#ifdef RW_D3D9 + createSkinShaders(); +#endif + + skinGlobals.pipelines[PLATFORM_D3D9] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ +#ifdef RW_D3D9 + destroySkinShaders(); +#endif + + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_D3D9])->destroy(); + skinGlobals.pipelines[PLATFORM_D3D9] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_D3D9, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = skinRenderCB; + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + return pipe; +} + +} +} diff --git a/vendor/librw/src/d3d/d3ddevice.cpp b/vendor/librw/src/d3d/d3ddevice.cpp new file mode 100644 index 0000000..1f39f18 --- /dev/null +++ b/vendor/librw/src/d3d/d3ddevice.cpp @@ -0,0 +1,2023 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwerror.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "rwd3d.h" +#include "rwd3dimpl.h" + +#define PLUGIN_ID 0 + +namespace rw { +namespace d3d { + +#ifdef RW_D3D9 + +D3d9Globals d3d9Globals; + +// Keep track of rasters exclusively in video memory +// as they need special treatment sometimes +struct VidmemRaster +{ + Raster *raster; + VidmemRaster *next; +}; +static VidmemRaster *vidmemRasters; +void addVidmemRaster(Raster *raster); +void removeVidmemRaster(Raster *raster); + +// Same thing for dynamic vertex buffers +struct DynamicVB +{ + uint32 length; + uint32 fvf; + IDirect3DVertexBuffer9 **buf; + DynamicVB *next; +}; +static DynamicVB *dynamicVBs; +void addDynamicVB(uint32 length, uint32 fvf, IDirect3DVertexBuffer9 **buf); +void removeDynamicVB(IDirect3DVertexBuffer9 **buf); + +// Same thing for dynamic index buffers +struct DynamicIB +{ + uint32 length; + IDirect3DIndexBuffer9 **buf; + DynamicIB *next; +}; +static DynamicIB *dynamicIBs; +void addDynamicIB(uint32 length, IDirect3DIndexBuffer9 **buf); +void removeDynamicIB(IDirect3DIndexBuffer9 **buf); + + +struct RwRasterStateCache { + Raster *raster; + Texture::Addressing addressingU; + Texture::Addressing addressingV; + Texture::FilterMode filter; + int32 maxAniso; +}; + +#define MAXNUMSTAGES 8 + +// cached RW render states +struct RwStateCache { + bool32 vertexAlpha; + bool32 textureAlpha; + uint32 srcblend, destblend; + uint32 zwrite; + uint32 ztest; + uint32 fogenable; + RGBA fogcolor; + uint32 cullmode; + uint32 stencilenable; + uint32 stencilpass; + uint32 stencilfail; + uint32 stencilzfail; + uint32 stencilfunc; + uint32 stencilref; + uint32 stencilmask; + uint32 stencilwritemask; + uint32 alphafunc; + uint32 alpharef; + + // emulation of PS2 GS + bool32 gsalpha; + uint32 gsalpharef; + + RwRasterStateCache texstage[MAXNUMSTAGES]; +}; +static RwStateCache rwStateCache; + +void *constantVertexStream; +static IDirect3DTexture9 *whiteTex; + +D3dShaderState d3dShaderState; + +#define MAXNUMSTATES (D3DRS_BLENDOPALPHA+1) +#define MAXNUMTEXSTATES (D3DTSS_CONSTANT+1) +#define MAXNUMSAMPLERSTATES (D3DSAMP_DMAPOFFSET+1) +#define MAXNUMSTREAMS (3) +#define MAXNUMRENDERTARGETS (4) + +static int32 numDirtyStates; +static uint32 dirtyStates[MAXNUMSTATES]; +static struct { + uint32 value; + bool32 dirty; +} stateCache[MAXNUMSTATES]; +static uint32 d3dStates[MAXNUMSTATES]; + +// Things that aren't just integers +struct D3dDeviceCache { + IDirect3DVertexShader9 *vertexShader; + IDirect3DPixelShader9 *pixelShader; + IDirect3DVertexDeclaration9 *vertexDeclaration; + IDirect3DIndexBuffer9 *indices; + struct { + IDirect3DVertexBuffer9 *buffer; + uint32 offset; + uint32 stride; + } vertexStreams[MAXNUMSTREAMS]; + IDirect3DSurface9 *renderTargets[MAXNUMRENDERTARGETS]; + IDirect3DSurface9 *depthSurface; +}; +static D3dDeviceCache deviceCache; + +static int32 numDirtyTextureStageStates; +static struct { + uint32 stage; + uint32 type; +} dirtyTextureStageStates[MAXNUMTEXSTATES*MAXNUMSTAGES]; +static struct { + uint32 value; + bool32 dirty; +} textureStageStateCache[MAXNUMTEXSTATES][MAXNUMSTAGES]; +static uint32 d3dTextureStageStates[MAXNUMTEXSTATES][MAXNUMSTAGES]; + +static uint32 d3dSamplerStates[MAXNUMSAMPLERSTATES][MAXNUMSTAGES]; + + +static bool validStates[MAXNUMSTATES]; +static bool validTexStates[MAXNUMTEXSTATES]; + +static D3DMATERIAL9 d3dmaterial; + + +static uint32 blendMap[] = { + D3DBLEND_ZERO, // actually invalid + D3DBLEND_ZERO, + D3DBLEND_ONE, + D3DBLEND_SRCCOLOR, + D3DBLEND_INVSRCCOLOR, + D3DBLEND_SRCALPHA, + D3DBLEND_INVSRCALPHA, + D3DBLEND_DESTALPHA, + D3DBLEND_INVDESTALPHA, + D3DBLEND_DESTCOLOR, + D3DBLEND_INVDESTCOLOR, + D3DBLEND_SRCALPHASAT +}; + +static uint32 stencilOpMap[] = { + D3DSTENCILOP_KEEP, // actually invalid + D3DSTENCILOP_KEEP, + D3DSTENCILOP_ZERO, + D3DSTENCILOP_REPLACE, + D3DSTENCILOP_INCRSAT, + D3DSTENCILOP_DECRSAT, + D3DSTENCILOP_INVERT, + D3DSTENCILOP_INCR, + D3DSTENCILOP_DECR +}; + +static uint32 stencilFuncMap[] = { + D3DCMP_NEVER, // actually invalid + D3DCMP_NEVER, + D3DCMP_LESS, + D3DCMP_EQUAL, + D3DCMP_LESSEQUAL, + D3DCMP_GREATER, + D3DCMP_NOTEQUAL, + D3DCMP_GREATEREQUAL, + D3DCMP_ALWAYS +}; + +static uint32 alphafuncMap[] = { + D3DCMP_ALWAYS, + D3DCMP_GREATEREQUAL, + D3DCMP_LESS +}; + +static uint32 cullmodeMap[] = { + D3DCULL_NONE, // actually invalid + D3DCULL_NONE, + D3DCULL_CW, + D3DCULL_CCW +}; + +static uint32 filterConvMap[] = { + 0, D3DTEXF_POINT, D3DTEXF_LINEAR, + D3DTEXF_POINT, D3DTEXF_LINEAR, + D3DTEXF_POINT, D3DTEXF_LINEAR +}; +static uint32 filterConvMap_MIP[] = { + 0, D3DTEXF_NONE, D3DTEXF_NONE, + D3DTEXF_POINT, D3DTEXF_POINT, + D3DTEXF_LINEAR, D3DTEXF_LINEAR +}; +static uint32 addressConvMap[] = { + 0, D3DTADDRESS_WRAP, D3DTADDRESS_MIRROR, + D3DTADDRESS_CLAMP, D3DTADDRESS_BORDER +}; + +// D3D render state + +void +setRenderState(uint32 state, uint32 value) +{ + if(stateCache[state].value != value){ + stateCache[state].value = value; + if(!stateCache[state].dirty){ + stateCache[state].dirty = 1; + dirtyStates[numDirtyStates++] = state; + } + } +} + +void +getRenderState(uint32 state, uint32 *value) +{ + *value = stateCache[state].value; +} + +void +setRenderTarget(int n, void *surf) +{ + if(surf != deviceCache.renderTargets[n]){ + deviceCache.renderTargets[n] = (IDirect3DSurface9*)surf; + d3ddevice->SetRenderTarget(n, deviceCache.renderTargets[n]); + } +} + +void +setDepthSurface(void *surf) +{ + if(surf != deviceCache.depthSurface){ + deviceCache.depthSurface = (IDirect3DSurface9*)surf; + d3ddevice->SetDepthStencilSurface(deviceCache.depthSurface); + } +} + +void +setTextureStageState(uint32 stage, uint32 type, uint32 value) +{ + if(textureStageStateCache[type][stage].value != value){ + textureStageStateCache[type][stage].value = value; + if(!textureStageStateCache[type][stage].dirty){ + textureStageStateCache[type][stage].dirty = 1; + dirtyTextureStageStates[numDirtyTextureStageStates].stage = stage; + dirtyTextureStageStates[numDirtyTextureStageStates].type = type; + numDirtyTextureStageStates++; + } + } +} + +void +getTextureStageState(uint32 stage, uint32 type, uint32 *value) +{ + *value = textureStageStateCache[type][stage].value; +} + +void +flushCache(void) +{ + uint32 s, t; + uint32 v; + for(int32 i = 0; i < numDirtyStates; i++){ + s = dirtyStates[i]; + v = stateCache[s].value; + stateCache[s].dirty = 0; + if(d3dStates[s] != v){ + d3ddevice->SetRenderState((D3DRENDERSTATETYPE)s, v); + d3dStates[s] = v; + } + } + numDirtyStates = 0; + for(int32 i = 0; i < numDirtyTextureStageStates; i++){ + s = dirtyTextureStageStates[i].stage; + t = dirtyTextureStageStates[i].type; + v = textureStageStateCache[t][s].value; + textureStageStateCache[t][s].dirty = 0; + if(d3dTextureStageStates[t][s] != v){ + d3ddevice->SetTextureStageState(s, (D3DTEXTURESTAGESTATETYPE)t, v); + d3dTextureStageStates[t][s] = v; + } + } + numDirtyTextureStageStates = 0; + + if(d3dShaderState.fogDirty){ + d3ddevice->SetVertexShaderConstantF(VSLOC_fogData, (float*)&d3dShaderState.fogData, 1); + d3ddevice->SetPixelShaderConstantF(PSLOC_fogColor, (float*)&d3dShaderState.fogColor, 1); + d3dShaderState.fogDirty = false; + } +} + +void +setSamplerState(uint32 stage, uint32 type, uint32 value) +{ + if(d3dSamplerStates[type][stage] != value){ + d3ddevice->SetSamplerState(stage, (D3DSAMPLERSTATETYPE)type, value); + d3dSamplerStates[type][stage] = value; + } +} + +void +getSamplerState(uint32 stage, uint32 type, uint32 *value) +{ + *value = d3dSamplerStates[type][stage]; +} + +// Bring D3D device in accordance with saved render states (after a reset) +static void +restoreD3d9Device(void) +{ + int32 i; + uint32 s, t; + + for(i = 0; i < MAXNUMSTAGES; i++){ + Raster *raster = rwStateCache.texstage[i].raster; + if(raster){ + D3dRaster *d3draster = GETD3DRASTEREXT(raster); + d3ddevice->SetTexture(i, (IDirect3DTexture9*)d3draster->texture); + }else + d3ddevice->SetTexture(i, nil); + setSamplerState(i, D3DSAMP_ADDRESSU, addressConvMap[rwStateCache.texstage[i].addressingU]); + setSamplerState(i, D3DSAMP_ADDRESSV, addressConvMap[rwStateCache.texstage[i].addressingV]); + setSamplerState(i, D3DSAMP_MAGFILTER, filterConvMap[rwStateCache.texstage[i].filter]); + if(rwStateCache.texstage[i].maxAniso == 1) + setSamplerState(i, D3DSAMP_MINFILTER, filterConvMap[rwStateCache.texstage[i].filter]); + else + setSamplerState(i, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC); + setSamplerState(i, D3DSAMP_MIPFILTER, filterConvMap_MIP[rwStateCache.texstage[i].filter]); + } + for(s = 0; s < MAXNUMSTATES; s++) + if(validStates[s]) + d3ddevice->SetRenderState((D3DRENDERSTATETYPE)s, d3dStates[s]); + for(t = 0; t < MAXNUMTEXSTATES; t++) + if(validTexStates[t]) + for(s = 0; s < MAXNUMSTAGES; s++) + d3ddevice->SetTextureStageState(s, (D3DTEXTURESTAGESTATETYPE)t, d3dTextureStageStates[t][s]); + for(t = 1; t < MAXNUMSAMPLERSTATES; t++) + for(s = 0; s < MAXNUMSTAGES; s++) + d3ddevice->SetSamplerState(s, (D3DSAMPLERSTATETYPE)t, d3dSamplerStates[t][s]); + d3ddevice->SetMaterial(&d3dmaterial); + + d3ddevice->SetVertexShader(deviceCache.vertexShader); + d3ddevice->SetPixelShader(deviceCache.pixelShader); + d3ddevice->SetVertexDeclaration(deviceCache.vertexDeclaration); + d3ddevice->SetIndices(deviceCache.indices); + for(i = 0; i < MAXNUMSTREAMS; i++) + d3ddevice->SetStreamSource(i, deviceCache.vertexStreams[i].buffer, deviceCache.vertexStreams[i].offset, deviceCache.vertexStreams[i].stride); + + // shader constants are zero now + d3dShaderState.fogDirty = true; + d3dShaderState.matColor.red = 0; + d3dShaderState.matColor.green = 0; + d3dShaderState.matColor.blue = 0; + d3dShaderState.matColor.alpha = 0; + d3dShaderState.surfProps.ambient = 0.0f; + d3dShaderState.surfProps.specular = 0.0f; + d3dShaderState.surfProps.diffuse = 0.0f; + d3dShaderState.extraSurfProp = 0.0f; + d3dShaderState.numDir = 0; + d3dShaderState.numPoint = 0; + d3dShaderState.numSpot = 0; + d3dShaderState.ambient.red = 0.0f; + d3dShaderState.ambient.green = 0.0f; + d3dShaderState.ambient.blue = 0.0f; + d3dShaderState.ambient.alpha = 0.0f; +} + +void +evictD3D9Raster(Raster *raster) +{ + int i; + // Make sure we're not still referencing this raster + D3dRaster *natras = GETD3DRASTEREXT(raster); + switch(raster->type){ + case Raster::CAMERATEXTURE: + for(i = 0; i < MAXNUMRENDERTARGETS; i++) + if(deviceCache.renderTargets[i] == natras->texture) + setRenderTarget(i, i == 0 ? d3d9Globals.defaultRenderTarget : nil); + // fall through + case Raster::NORMAL: + case Raster::TEXTURE: + for(i = 0; i < MAXNUMSTAGES; i++) + if(rwStateCache.texstage[i].raster == raster) + rwStateCache.texstage[i].raster = nil; + break; + case Raster::ZBUFFER: + if(natras->texture == deviceCache.depthSurface) + setDepthSurface(d3d9Globals.defaultDepthSurf); + break; + } +} + +// RW render state + +static void +setDepthTest(bool32 enable) +{ + if(rwStateCache.ztest != enable){ + rwStateCache.ztest = enable; + if(rwStateCache.zwrite && !enable){ + // If we still want to write, enable but set mode to always + setRenderState(D3DRS_ZENABLE, TRUE); + setRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); + }else{ + setRenderState(D3DRS_ZENABLE, rwStateCache.ztest); + setRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); + } + } +} + +static void +setDepthWrite(bool32 enable) +{ + if(rwStateCache.zwrite != enable){ + rwStateCache.zwrite = enable; + if(enable && !rwStateCache.ztest){ + // Have to switch on ztest so writing can work + setRenderState(D3DRS_ZENABLE, TRUE); + setRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); + } + setRenderState(D3DRS_ZWRITEENABLE, rwStateCache.zwrite); + } +} + +static void +setVertexAlpha(bool32 enable) +{ + if(rwStateCache.vertexAlpha != enable){ + if(!rwStateCache.textureAlpha){ + setRenderState(D3DRS_ALPHABLENDENABLE, enable); + setRenderState(D3DRS_ALPHATESTENABLE, enable); + } + rwStateCache.vertexAlpha = enable; + } +} + +void +setRasterStage(uint32 stage, Raster *raster) +{ + bool32 alpha; + D3dRaster *d3draster = nil; + if(raster != rwStateCache.texstage[stage].raster){ + rwStateCache.texstage[stage].raster = raster; + if(raster){ + assert(raster->platform == PLATFORM_D3D8 || + raster->platform == PLATFORM_D3D9); + d3draster = GETD3DRASTEREXT(raster); + d3ddevice->SetTexture(stage, (IDirect3DTexture9*)d3draster->texture); + alpha = d3draster->hasAlpha; + }else{ + d3ddevice->SetTexture(stage, whiteTex); + alpha = 0; + } + if(stage == 0){ + if(rwStateCache.textureAlpha != alpha){ + rwStateCache.textureAlpha = alpha; + if(!rwStateCache.vertexAlpha){ + setRenderState(D3DRS_ALPHABLENDENABLE, alpha); + setRenderState(D3DRS_ALPHATESTENABLE, alpha); + } + } + } + } +} + +static void +setFilterMode(uint32 stage, int32 filter, int32 maxAniso = 1) +{ + if(rwStateCache.texstage[stage].filter != (Texture::FilterMode)filter){ + rwStateCache.texstage[stage].filter = (Texture::FilterMode)filter; + setSamplerState(stage, D3DSAMP_MAGFILTER, filterConvMap[filter]); + if(maxAniso == 1) + setSamplerState(stage, D3DSAMP_MINFILTER, filterConvMap[filter]); + else + setSamplerState(stage, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC); + setSamplerState(stage, D3DSAMP_MIPFILTER, filterConvMap_MIP[filter]); + } + if(rwStateCache.texstage[stage].maxAniso != maxAniso){ + rwStateCache.texstage[stage].maxAniso = maxAniso; + if(maxAniso == 1) + setSamplerState(stage, D3DSAMP_MINFILTER, filterConvMap[filter]); + else + setSamplerState(stage, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC); + setSamplerState(stage, D3DSAMP_MAXANISOTROPY, maxAniso); + } +} + +static void +setAddressU(uint32 stage, int32 addressing) +{ + if(rwStateCache.texstage[stage].addressingU != (Texture::Addressing)addressing){ + rwStateCache.texstage[stage].addressingU = (Texture::Addressing)addressing; + setSamplerState(stage, D3DSAMP_ADDRESSU, addressConvMap[addressing]); + } +} + +static void +setAddressV(uint32 stage, int32 addressing) +{ + if(rwStateCache.texstage[stage].addressingV != (Texture::Addressing)addressing){ + rwStateCache.texstage[stage].addressingV = (Texture::Addressing)addressing; + setSamplerState(stage, D3DSAMP_ADDRESSV, addressConvMap[addressing]); + } +} + +void +setTexture(uint32 stage, Texture *tex) +{ + if(tex == nil){ + setRasterStage(stage, nil); + return; + } + if(tex->raster){ + setFilterMode(stage, tex->getFilter(), tex->getMaxAnisotropy()); + setAddressU(stage, tex->getAddressU()); + setAddressV(stage, tex->getAddressV()); + } + setRasterStage(stage, tex->raster); +} + +void +setD3dMaterial(D3DMATERIAL9 *mat9) +{ + if(d3dmaterial.Diffuse.r != mat9->Diffuse.r || + d3dmaterial.Diffuse.g != mat9->Diffuse.g || + d3dmaterial.Diffuse.b != mat9->Diffuse.b || + d3dmaterial.Diffuse.a != mat9->Diffuse.a || + d3dmaterial.Ambient.r != mat9->Ambient.r || + d3dmaterial.Ambient.g != mat9->Ambient.g || + d3dmaterial.Ambient.b != mat9->Ambient.b || + d3dmaterial.Ambient.a != mat9->Ambient.a || + d3dmaterial.Specular.r != mat9->Specular.r || + d3dmaterial.Specular.g != mat9->Specular.g || + d3dmaterial.Specular.b != mat9->Specular.b || + d3dmaterial.Specular.a != mat9->Specular.a || + d3dmaterial.Emissive.r != mat9->Emissive.r || + d3dmaterial.Emissive.g != mat9->Emissive.g || + d3dmaterial.Emissive.b != mat9->Emissive.b || + d3dmaterial.Emissive.a != mat9->Emissive.a || + d3dmaterial.Power != mat9->Power){ + d3ddevice->SetMaterial(mat9); + d3dmaterial = *mat9; + } +} + +void +setMaterial_fix(const RGBA &color, const SurfaceProperties &surfProps) +{ + D3DMATERIAL9 mat9; + D3DCOLORVALUE black = { 0, 0, 0, 0 }; + float ambmult = surfProps.ambient/255.0f; + float diffmult = surfProps.diffuse/255.0f; + mat9.Ambient.r = color.red*ambmult; + mat9.Ambient.g = color.green*ambmult; + mat9.Ambient.b = color.blue*ambmult; + mat9.Ambient.a = color.alpha; + mat9.Diffuse.r = color.red*diffmult; + mat9.Diffuse.g = color.green*diffmult; + mat9.Diffuse.b = color.blue*diffmult; + mat9.Diffuse.a = color.alpha; + mat9.Power = 0.0f; + mat9.Emissive = black; + mat9.Specular = black; + setD3dMaterial(&mat9); +} + + +void +setMaterial(const RGBA &color, const SurfaceProperties &surfaceprops, float extraSurfProp) +{ + if(!equal(d3dShaderState.matColor, color)){ + rw::RGBAf col; + convColor(&col, &color); + d3ddevice->SetVertexShaderConstantF(VSLOC_matColor, (float*)&col, 1); + d3dShaderState.matColor = color; + } + + if(d3dShaderState.surfProps.ambient != surfaceprops.ambient || + d3dShaderState.surfProps.specular != surfaceprops.specular || + d3dShaderState.surfProps.diffuse != surfaceprops.diffuse || + d3dShaderState.extraSurfProp != extraSurfProp){ + float surfProps[4]; + surfProps[0] = surfaceprops.ambient; + surfProps[1] = surfaceprops.specular; + surfProps[2] = surfaceprops.diffuse; + surfProps[3] = extraSurfProp; + d3ddevice->SetVertexShaderConstantF(VSLOC_surfProps, surfProps, 1); + d3dShaderState.surfProps = surfaceprops; + d3dShaderState.extraSurfProp = extraSurfProp; + } +} + +static void +setRwRenderState(int32 state, void *pvalue) +{ + uint32 value = (uint32)(uintptr)pvalue; + uint32 bval = value ? TRUE : FALSE; + switch(state){ + case TEXTURERASTER: + setRasterStage(0, (Raster*)pvalue); + break; + case TEXTUREADDRESS: + setAddressU(0, value); + setAddressV(0, value); + break; + case TEXTUREADDRESSU: + setAddressU(0, value); + break; + case TEXTUREADDRESSV: + setAddressV(0, value); + break; + case TEXTUREFILTER: + setFilterMode(0, value); + break; + case VERTEXALPHA: + setVertexAlpha(bval); + break; + case SRCBLEND: + if(rwStateCache.srcblend != value){ + rwStateCache.srcblend = value; + setRenderState(D3DRS_SRCBLEND, blendMap[value]); + } + break; + case DESTBLEND: + if(rwStateCache.destblend != value){ + rwStateCache.destblend = value; + setRenderState(D3DRS_DESTBLEND, blendMap[value]); + } + break; + case ZTESTENABLE: + setDepthTest(bval); + break; + case ZWRITEENABLE: + setDepthWrite(bval); + break; + case FOGENABLE: + if(rwStateCache.fogenable != bval){ + rwStateCache.fogenable = bval; +// setRenderState(D3DRS_FOGENABLE, rwStateCache.fogenable); + d3dShaderState.fogData.disable = bval ? 0.0f : 1.0f; + d3dShaderState.fogDirty = true; + }; + break; + case FOGCOLOR:{ + RGBA c; + c.red = value; + c.green = value>>8; + c.blue = value>>16; + c.alpha = value>>24; + if(!equal(rwStateCache.fogcolor, c)){ + rwStateCache.fogcolor = c; + setRenderState(D3DRS_FOGCOLOR, D3DCOLOR_RGBA(c.red, c.green, c.blue, c.alpha)); + convColor(&d3dShaderState.fogColor, &c); + d3dShaderState.fogDirty = true; + }} break; + case CULLMODE: + if(rwStateCache.cullmode != value){ + rwStateCache.cullmode = value; + setRenderState(D3DRS_CULLMODE, cullmodeMap[value]); + } + break; + + case STENCILENABLE: + if(rwStateCache.stencilenable != bval){ + rwStateCache.stencilenable = bval; + setRenderState(D3DRS_STENCILENABLE, bval); + } + break; + case STENCILFAIL: + if(rwStateCache.stencilfail != value){ + rwStateCache.stencilfail = value; + setRenderState(D3DRS_STENCILFAIL, stencilOpMap[value]); + } + break; + case STENCILZFAIL: + if(rwStateCache.stencilzfail != value){ + rwStateCache.stencilzfail = value; + setRenderState(D3DRS_STENCILZFAIL, stencilOpMap[value]); + } + break; + case STENCILPASS: + if(rwStateCache.stencilpass != value){ + rwStateCache.stencilpass = value; + setRenderState(D3DRS_STENCILPASS, stencilOpMap[value]); + } + break; + case STENCILFUNCTION: + if(rwStateCache.stencilfunc != value){ + rwStateCache.stencilfunc = value; + setRenderState(D3DRS_STENCILFUNC, stencilFuncMap[value]); + } + break; + case STENCILFUNCTIONREF: + if(rwStateCache.stencilref != value){ + rwStateCache.stencilref = value; + setRenderState(D3DRS_STENCILREF, value); + } + break; + case STENCILFUNCTIONMASK: + if(rwStateCache.stencilmask != value){ + rwStateCache.stencilmask = value; + setRenderState(D3DRS_STENCILMASK, value); + } + break; + case STENCILFUNCTIONWRITEMASK: + if(rwStateCache.stencilwritemask != value){ + rwStateCache.stencilwritemask = value; + setRenderState(D3DRS_STENCILWRITEMASK, value); + } + break; + + case ALPHATESTFUNC: + if(rwStateCache.alphafunc != value){ + rwStateCache.alphafunc = value; + setRenderState(D3DRS_ALPHAFUNC, alphafuncMap[rwStateCache.alphafunc]); + } + break; + case ALPHATESTREF: + if(rwStateCache.alpharef != value){ + rwStateCache.alpharef = value; + setRenderState(D3DRS_ALPHAREF, rwStateCache.alpharef); + } + break; + case GSALPHATEST: + rwStateCache.gsalpha = value; + break; + case GSALPHATESTREF: + rwStateCache.gsalpharef = value; + break; + } +} + +static void* +getRwRenderState(int32 state) +{ + uint32 val; + switch(state){ + case TEXTURERASTER: + return rwStateCache.texstage[0].raster; + case TEXTUREADDRESS: + if(rwStateCache.texstage[0].addressingU == rwStateCache.texstage[0].addressingV) + val = rwStateCache.texstage[0].addressingU; + else + val = 0; // invalid + break; + case TEXTUREADDRESSU: + val = rwStateCache.texstage[0].addressingU; + break; + case TEXTUREADDRESSV: + val = rwStateCache.texstage[0].addressingV; + break; + case TEXTUREFILTER: + val = rwStateCache.texstage[0].filter; + break; + + case VERTEXALPHA: + val = rwStateCache.vertexAlpha; + break; + case SRCBLEND: + val = rwStateCache.srcblend; + break; + case DESTBLEND: + val = rwStateCache.destblend; + break; + case ZTESTENABLE: + val = rwStateCache.ztest; + break; + case ZWRITEENABLE: + val = rwStateCache.zwrite; + break; + case FOGENABLE: + val = rwStateCache.fogenable; + break; + case FOGCOLOR: + val = RWRGBAINT(rwStateCache.fogcolor.red, rwStateCache.fogcolor.green, + rwStateCache.fogcolor.blue, rwStateCache.fogcolor.alpha); + break; + case CULLMODE: + val = rwStateCache.cullmode; + break; + + case STENCILENABLE: + val = rwStateCache.stencilenable; + break; + case STENCILFAIL: + val = rwStateCache.stencilfail; + break; + case STENCILZFAIL: + val = rwStateCache.stencilzfail; + break; + case STENCILPASS: + val = rwStateCache.stencilpass; + break; + case STENCILFUNCTION: + val = rwStateCache.stencilfunc; + break; + case STENCILFUNCTIONREF: + val = rwStateCache.stencilref; + break; + case STENCILFUNCTIONMASK: + val = rwStateCache.stencilmask; + break; + case STENCILFUNCTIONWRITEMASK: + val = rwStateCache.stencilwritemask; + break; + + case ALPHATESTFUNC: + val = rwStateCache.alphafunc; + break; + case ALPHATESTREF: + val = rwStateCache.alpharef; + break; + case GSALPHATEST: + val = rwStateCache.gsalpha; + break; + case GSALPHATESTREF: + val = rwStateCache.gsalpharef; + break; + default: + val = 0; + } + return (void*)(uintptr)val; +} + +// Shaders + +void +setVertexShader(void *vs) +{ + if(deviceCache.vertexShader != vs){ + deviceCache.vertexShader = (IDirect3DVertexShader9*)vs; + d3ddevice->SetVertexShader(deviceCache.vertexShader); + } +} + +void +setPixelShader(void *ps) +{ + if(deviceCache.pixelShader != ps){ + deviceCache.pixelShader = (IDirect3DPixelShader9*)ps; + d3ddevice->SetPixelShader(deviceCache.pixelShader); + } +} + +void +setIndices(void *indexBuffer) +{ + if(deviceCache.indices != indexBuffer){ + deviceCache.indices = (IDirect3DIndexBuffer9*)indexBuffer; + d3ddevice->SetIndices(deviceCache.indices); + } +} + +void +setStreamSource(int n, void *buffer, uint32 offset, uint32 stride) +{ + if(deviceCache.vertexStreams[n].buffer != buffer || + deviceCache.vertexStreams[n].offset != offset || + deviceCache.vertexStreams[n].stride != stride){ + deviceCache.vertexStreams[n].buffer = (IDirect3DVertexBuffer9*)buffer; + deviceCache.vertexStreams[n].offset = offset; + deviceCache.vertexStreams[n].stride = stride; + d3ddevice->SetStreamSource(n, deviceCache.vertexStreams[n].buffer, offset, stride); + } +} + +void +setVertexDeclaration(void *declaration) +{ + if(deviceCache.vertexDeclaration != declaration){ + deviceCache.vertexDeclaration = (IDirect3DVertexDeclaration9*)declaration; + d3ddevice->SetVertexDeclaration(deviceCache.vertexDeclaration); + } +} + +void* +createVertexShader(void *csosrc) +{ + void *shdr; + if(d3ddevice->CreateVertexShader((DWORD*)csosrc, (IDirect3DVertexShader9**)&shdr) == D3D_OK){ + d3d9Globals.numVertexShaders++; + return shdr; + } + return nil; +} + +void* +createPixelShader(void *csosrc) +{ + void *shdr; + if(d3ddevice->CreatePixelShader((DWORD*)csosrc, (IDirect3DPixelShader9**)&shdr) == D3D_OK){ + d3d9Globals.numPixelShaders++; + return shdr; + } + return nil; +} + +void +destroyVertexShader(void *shader) +{ + if(shader){ + ((IDirect3DVertexShader9*)shader)->Release(); + d3d9Globals.numVertexShaders--; + } +} + +void +destroyPixelShader(void *shader) +{ + if(shader){ + ((IDirect3DPixelShader9*)shader)->Release(); + d3d9Globals.numPixelShaders--; + } +} + + +// Camera + +static void +setRenderSurfaces(Camera *cam) +{ + Raster *fbuf = cam->frameBuffer; + assert(fbuf); + { + if(fbuf->parent) + fbuf = fbuf->parent; + + D3dRaster *natras = GETD3DRASTEREXT(fbuf); + assert(fbuf->type == Raster::CAMERA || fbuf->type == Raster::CAMERATEXTURE); + if(natras->texture == nil) + setRenderTarget(0, d3d9Globals.defaultRenderTarget); + else{ + assert(fbuf->type == Raster::CAMERATEXTURE); + IDirect3DSurface9 *surf; + ((IDirect3DTexture9*)natras->texture)->GetSurfaceLevel(0, &surf); + setRenderTarget(0, surf); + surf->Release(); + } + } + + Raster *zbuf = cam->zBuffer; + if(zbuf){ + if(zbuf->parent) + zbuf = zbuf->parent; + + D3dRaster *natras = GETD3DRASTEREXT(zbuf); + assert(zbuf->type == Raster::ZBUFFER); + setDepthSurface(natras->texture); + }else + setDepthSurface(nil); + +} + +static void +setViewport(Raster *fb) +{ + D3DVIEWPORT9 vp; + vp.MinZ = 0.0f; + vp.MaxZ = 1.0f; + vp.X = fb->offsetX; + vp.Y = fb->offsetY; + vp.Width = fb->width; + vp.Height = fb->height; + d3ddevice->SetViewport(&vp); +} + +static void +beginUpdate(Camera *cam) +{ + float view[16], proj[16]; + + // View Matrix + Matrix inv; + Matrix::invert(&inv, cam->getFrame()->getLTM()); + // Since we're looking into positive Z, + // flip X to ge a left handed view space. + view[0] = -inv.right.x; + view[1] = inv.right.y; + view[2] = inv.right.z; + view[3] = 0.0f; + view[4] = -inv.up.x; + view[5] = inv.up.y; + view[6] = inv.up.z; + view[7] = 0.0f; + view[8] = -inv.at.x; + view[9] = inv.at.y; + view[10] = inv.at.z; + view[11] = 0.0f; + view[12] = -inv.pos.x; + view[13] = inv.pos.y; + view[14] = inv.pos.z; + view[15] = 1.0f; + memcpy(&cam->devView, view, sizeof(RawMatrix)); +// d3ddevice->SetTransform(D3DTS_VIEW, (D3DMATRIX*)view); + + // Projection Matrix + float32 invwx = 1.0f/cam->viewWindow.x; + float32 invwy = 1.0f/cam->viewWindow.y; + float32 invz = 1.0f/(cam->farPlane-cam->nearPlane); + + proj[0] = invwx; + proj[1] = 0.0f; + proj[2] = 0.0f; + proj[3] = 0.0f; + + proj[4] = 0.0f; + proj[5] = invwy; + proj[6] = 0.0f; + proj[7] = 0.0f; + + proj[8] = cam->viewOffset.x*invwx; + proj[9] = cam->viewOffset.y*invwy; + proj[12] = -proj[8]; + proj[13] = -proj[9]; + if(cam->projection == Camera::PERSPECTIVE){ + proj[10] = cam->farPlane*invz; + proj[11] = 1.0f; + + proj[15] = 0.0f; + }else{ + proj[10] = invz; + proj[11] = 0.0f; + + proj[15] = 1.0f; + } + proj[14] = -cam->nearPlane*proj[10]; + memcpy(&cam->devProj, proj, sizeof(RawMatrix)); +// d3ddevice->SetTransform(D3DTS_PROJECTION, (D3DMATRIX*)proj); + + // TODO: figure out where this is really done +// setRenderState(D3DRS_FOGSTART, *(uint32*)&cam->fogPlane); +// setRenderState(D3DRS_FOGEND, *(uint32*)&cam->farPlane); + d3dShaderState.fogData.start = cam->fogPlane; + d3dShaderState.fogData.end = cam->farPlane; + d3dShaderState.fogData.range = 1.0f/(cam->fogPlane - cam->farPlane); + // TODO: not quite sure this is the right place to do this... + d3dShaderState.fogData.disable = rwStateCache.fogenable ? 0.0f : 1.0f; + d3dShaderState.fogDisable.start = 0.0f; + d3dShaderState.fogDisable.end = 0.0f; + d3dShaderState.fogDisable.range = 0.0f; + d3dShaderState.fogDisable.disable = 1.0f; + d3dShaderState.fogDirty = true; + + setRenderSurfaces(cam); + + setViewport(cam->frameBuffer); + + d3ddevice->BeginScene(); +} + +static void +endUpdate(Camera *cam) +{ + d3ddevice->EndScene(); +} + +// Manage video memory + +void +addVidmemRaster(Raster *raster) +{ + VidmemRaster *vmr = rwNewT(VidmemRaster, 1, ID_DRIVER | MEMDUR_EVENT); + vmr->raster = raster; + vmr->next = vidmemRasters; + vidmemRasters = vmr; +} + +void +removeVidmemRaster(Raster *raster) +{ + VidmemRaster **p, *vmr; + for(p = &vidmemRasters; *p; p = &(*p)->next) + if((*p)->raster == raster) + goto found; + return; +found: + vmr = *p; + *p = vmr->next; + rwFree(vmr); +} + +static void +releaseVidmemRasters(void) +{ + VidmemRaster *vmr; + Raster *raster; + D3dRaster *natras; + for(vmr = vidmemRasters; vmr; vmr = vmr->next){ + raster = vmr->raster; + natras = GETD3DRASTEREXT(raster); + switch(raster->type){ + case Raster::CAMERATEXTURE: + destroyTexture(natras->texture); + natras->texture = nil; + break; + + case Raster::ZBUFFER: + // we'll leave the default surface dangling so we can tell the difference + if(natras->texture != d3d9Globals.defaultDepthSurf){ + ((IDirect3DSurface9*)natras->texture)->Release(); + natras->texture = nil; + } + break; + } + } +} + +static void +recreateVidmemRasters(void) +{ + VidmemRaster *vmr; + Raster *raster; + D3dRaster *natras; + for(vmr = vidmemRasters; vmr; vmr = vmr->next){ + raster = vmr->raster; + natras = GETD3DRASTEREXT(raster); + switch(raster->type){ + case Raster::CAMERATEXTURE: { + int32 levels = Raster::calculateNumLevels(raster->width, raster->height); + IDirect3DTexture9 *tex = nil; + d3ddevice->CreateTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? levels : 1, + D3DUSAGE_RENDERTARGET, + (D3DFORMAT)natras->format, D3DPOOL_DEFAULT, &tex, nil); + natras->texture = tex; + if(natras->texture) + d3d9Globals.numTextures++; + break; + } + + case Raster::ZBUFFER: + if(natras->texture){ + RECT rect; + GetClientRect(d3d9Globals.window, &rect); + raster->width = rect.right; + raster->height = rect.bottom; + natras->texture = d3d9Globals.defaultDepthSurf; + natras->format = d3d9Globals.present.AutoDepthStencilFormat; + raster->depth = findFormatDepth(natras->format); + }else{ + IDirect3DSurface9 *surf = nil; + d3ddevice->CreateDepthStencilSurface(raster->width, raster->height, (D3DFORMAT)natras->format, + d3d9Globals.present.MultiSampleType, d3d9Globals.present.MultiSampleQuality, + FALSE, &surf, nil); + natras->texture = surf; + } + break; + } + } +} + +void +addDynamicVB(uint32 length, uint32 fvf, IDirect3DVertexBuffer9 **buf) +{ + DynamicVB *dvb = rwNewT(DynamicVB, 1, ID_DRIVER | MEMDUR_EVENT); + dvb->length = length; + dvb->fvf = fvf; + dvb->buf = buf; + dvb->next = dynamicVBs; + dynamicVBs = dvb; +} + +void +removeDynamicVB(IDirect3DVertexBuffer9 **buf) +{ + DynamicVB **p, *dvb; + for(p = &dynamicVBs; *p; p = &(*p)->next) + if((*p)->buf == buf) + goto found; + return; +found: + dvb = *p; + *p = dvb->next; + rwFree(dvb); +} + +static void +releaseDynamicVBs(void) +{ + DynamicVB *dvb; + int i; + for(dvb = dynamicVBs; dvb; dvb = dvb->next){ + for(i = 0; i < MAXNUMSTREAMS; i++) + if(deviceCache.vertexStreams[i].buffer == *dvb->buf) + deviceCache.vertexStreams[i].buffer = nil; + destroyVertexBuffer(*dvb->buf); + *dvb->buf = nil; + } +} + +static void +recreateDynamicVBs(void) +{ + DynamicVB *dvb; + for(dvb = dynamicVBs; dvb; dvb = dvb->next){ + assert(*dvb->buf == nil); + *dvb->buf = (IDirect3DVertexBuffer9*)createVertexBuffer(dvb->length, dvb->fvf, true); + } +} + + +void +addDynamicIB(uint32 length, IDirect3DIndexBuffer9 **buf) +{ + DynamicIB *ivb = rwNewT(DynamicIB, 1, ID_DRIVER | MEMDUR_EVENT); + ivb->length = length; + ivb->buf = buf; + ivb->next = dynamicIBs; + dynamicIBs = ivb; +} + +void +removeDynamicIB(IDirect3DIndexBuffer9 **buf) +{ + DynamicIB **p, *ivb; + for(p = &dynamicIBs; *p; p = &(*p)->next) + if((*p)->buf == buf) + goto found; + return; +found: + ivb = *p; + *p = ivb->next; + rwFree(ivb); +} + +static void +releaseDynamicIBs(void) +{ + DynamicIB *ivb; + for(ivb = dynamicIBs; ivb; ivb = ivb->next){ + if(deviceCache.indices == *ivb->buf) + deviceCache.indices = nil; + destroyIndexBuffer(*ivb->buf); + *ivb->buf = nil; + } +} + +static void +recreateDynamicIBs(void) +{ + DynamicIB *ivb; + for(ivb = dynamicIBs; ivb; ivb = ivb->next){ + assert(*ivb->buf == nil); + *ivb->buf = (IDirect3DIndexBuffer9*)createIndexBuffer(ivb->length, true); + } +} + +static void +releaseVideoMemory(void) +{ + int32 i; + for(i = 0; i < MAXNUMSTAGES; i++) + d3ddevice->SetTexture(i, nil); + d3ddevice->SetVertexDeclaration(nil); + d3ddevice->SetVertexShader(nil); + d3ddevice->SetPixelShader(nil); + d3ddevice->SetIndices(nil); + for(i = 0; i < MAXNUMSTREAMS; i++) + d3ddevice->SetStreamSource(0, nil, 0, 0); + + setRenderTarget(0, d3d9Globals.defaultRenderTarget); + for(i = 1; i < MAXNUMRENDERTARGETS; i++) + setRenderTarget(i, nil); + setDepthSurface(d3d9Globals.defaultDepthSurf); + + releaseVidmemRasters(); + releaseDynamicVBs(); + releaseDynamicIBs(); +} + +static void +restoreVideoMemory(void) +{ + // Have to get these back before recreating rasters + d3ddevice->GetRenderTarget(0, &d3d9Globals.defaultRenderTarget); + d3d9Globals.defaultRenderTarget->Release(); // refcount increased by Get + deviceCache.renderTargets[0] = d3d9Globals.defaultRenderTarget; + d3ddevice->GetDepthStencilSurface(&d3d9Globals.defaultDepthSurf); + d3d9Globals.defaultDepthSurf->Release(); // refcount increased by Get + deviceCache.depthSurface = d3d9Globals.defaultDepthSurf; + + recreateDynamicIBs(); + recreateDynamicVBs(); + // important that we get all raster back before restoring state + recreateVidmemRasters(); + + restoreD3d9Device(); +} + +static void +clearCamera(Camera *cam, RGBA *col, uint32 mode) +{ + int flags = 0; + if(mode & Camera::CLEARIMAGE) + mode |= D3DCLEAR_TARGET; + if(mode & Camera::CLEARZ) + mode |= D3DCLEAR_ZBUFFER; + if(mode & Camera::CLEARSTENCIL) + mode |= D3DCLEAR_STENCIL; + D3DCOLOR c = D3DCOLOR_RGBA(col->red, col->green, col->blue, col->alpha); + + RECT r; + GetClientRect(d3d9Globals.window, &r); + BOOL icon = IsIconic(d3d9Globals.window); + if(!icon && + (r.right != d3d9Globals.present.BackBufferWidth || r.bottom != d3d9Globals.present.BackBufferHeight)){ + + d3d9Globals.present.BackBufferWidth = r.right; + d3d9Globals.present.BackBufferHeight = r.bottom; + + releaseVideoMemory(); + d3d::d3ddevice->Reset(&d3d9Globals.present); + restoreVideoMemory(); + } + + setRenderSurfaces(cam); + + setViewport(cam->frameBuffer); // need to set this for the clear to work correctly + d3ddevice->Clear(0, nil, mode, c, 1.0f, 0); +} + +static void +showRaster(Raster *raster, uint32 flag) +{ + UINT interval = flag & Raster::FLIPWAITVSYNCH ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE; + if(d3d9Globals.present.PresentationInterval != interval){ + d3d9Globals.present.PresentationInterval = interval; + releaseVideoMemory(); + d3d::d3ddevice->Reset(&d3d9Globals.present); + restoreVideoMemory(); + } + + // not used but we want cameras to have rasters + assert(raster); + HRESULT res = d3ddevice->Present(nil, nil, 0, nil); + + if(res == D3DERR_DEVICELOST){ + res = d3ddevice->TestCooperativeLevel(); + // lost while being minimized, not reset once we're back + if(res == D3DERR_DEVICENOTRESET){ + releaseVideoMemory(); + d3d::d3ddevice->Reset(&d3d9Globals.present); + restoreVideoMemory(); + } + } +} + +static bool32 +rasterRenderFast(Raster *raster, int32 x, int32 y) +{ + IDirect3DTexture9 *dsttex; + IDirect3DSurface9 *srcsurf, *dstsurf; + D3DSURFACE_DESC srcdesc, dstdesc; + RECT rect = { x, y, x, y }; + + Raster *src = raster; + Raster *dst = Raster::getCurrentContext(); + D3dRaster *natdst = GETD3DRASTEREXT(dst); + D3dRaster *natsrc = GETD3DRASTEREXT(src); + + switch(dst->type){ + case Raster::CAMERATEXTURE: + switch(src->type){ + case Raster::CAMERA: + dsttex = (IDirect3DTexture9*)natdst->texture; + dsttex->GetSurfaceLevel(0, &dstsurf); + assert(dstsurf); + dstsurf->GetDesc(&dstdesc); + + d3ddevice->GetRenderTarget(0, &srcsurf); + assert(srcsurf); + srcsurf->GetDesc(&srcdesc); + + rect.right += srcdesc.Width; + rect.bottom += srcdesc.Height; + + d3ddevice->StretchRect(srcsurf, &rect, dstsurf, &rect, D3DTEXF_NONE); + dstsurf->Release(); + srcsurf->Release(); + return 1; + } + break; + } + + return 0; +} + + +// +// Device +// + +int +findFormatDepth(uint32 format) +{ + // not all formats actually + switch(format){ + case D3DFMT_R8G8B8: return 24; + case D3DFMT_A8R8G8B8: return 32; + case D3DFMT_X8R8G8B8: return 32; + case D3DFMT_R5G6B5: return 16; + case D3DFMT_X1R5G5B5: return 16; + case D3DFMT_A1R5G5B5: return 16; + case D3DFMT_A4R4G4B4: return 16; + case D3DFMT_R3G3B2: return 8; + case D3DFMT_A8: return 8; + case D3DFMT_A8R3G3B2: return 16; + case D3DFMT_X4R4G4B4: return 16; + case D3DFMT_A2B10G10R10: return 32; + case D3DFMT_A8B8G8R8: return 32; + case D3DFMT_X8B8G8R8: return 32; + case D3DFMT_G16R16: return 32; + case D3DFMT_A2R10G10B10: return 32; + case D3DFMT_A16B16G16R16: return 64; + + case D3DFMT_L8: return 8; + case D3DFMT_D16: return 16; + case D3DFMT_D24S8: return 32; + case D3DFMT_D24X8: return 32; + case D3DFMT_D24X4S4: return 32; + case D3DFMT_D32: return 32; + + default: return 0; + } +} + +// the commented ones don't "work" +static D3DFORMAT fbFormats[] = { +// D3DFMT_A1R5G5B5, +/// D3DFMT_A2R10G10B10, // works but let's not use it... +// D3DFMT_A8R8G8B8, + D3DFMT_X8R8G8B8, +// D3DFMT_X1R5G5B5, + D3DFMT_R5G6B5 +}; + +static void +addVideoMode(D3DDISPLAYMODE *mode) +{ + int i; + + for(i = 1; i < d3d9Globals.numModes; i++){ + if(d3d9Globals.modes[i].mode.Width == mode->Width && + d3d9Globals.modes[i].mode.Height == mode->Height && + d3d9Globals.modes[i].mode.Format == mode->Format){ + // had this format already, remember highest refresh rate + if(mode->RefreshRate > d3d9Globals.modes[i].mode.RefreshRate) + d3d9Globals.modes[i].mode.RefreshRate = mode->RefreshRate; + return; + } + } + + // none found, add + d3d9Globals.modes[d3d9Globals.numModes].mode = *mode; + d3d9Globals.modes[d3d9Globals.numModes].flags = VIDEOMODEEXCLUSIVE; + d3d9Globals.numModes++; +} + +static void +makeVideoModeList(void) +{ + int i, j; + D3DDISPLAYMODE mode; + + d3d9Globals.numModes = 1; + for(i = 0; i < nelem(fbFormats); i++) + d3d9Globals.numModes += d3d9Globals.d3d9->GetAdapterModeCount(d3d9Globals.adapter, fbFormats[i]); + + rwFree(d3d9Globals.modes); + d3d9Globals.modes = rwNewT(DisplayMode, d3d9Globals.numModes, ID_DRIVER | MEMDUR_EVENT); + + // first mode is current mode as windowed + d3d9Globals.d3d9->GetAdapterDisplayMode(d3d9Globals.adapter, &d3d9Globals.modes[0].mode); + d3d9Globals.modes[0].flags = 0; + d3d9Globals.numModes = 1; + + for(i = 0; i < nelem(fbFormats); i++){ + int n = d3d9Globals.d3d9->GetAdapterModeCount(d3d9Globals.adapter, fbFormats[i]); + for(j = 0; j < n; j++){ + d3d9Globals.d3d9->EnumAdapterModes(d3d9Globals.adapter, fbFormats[i], j, &mode); + addVideoMode(&mode); + } + } +} + +static int +openD3D(EngineOpenParams *params) +{ + HWND win = params->window; + + d3d9Globals.window = win; + d3d9Globals.numAdapters = 0; + d3d9Globals.modes = nil; + d3d9Globals.numModes = 0; + d3d9Globals.currentMode = 0; + + d3d9Globals.d3d9 = Direct3DCreate9(D3D_SDK_VERSION); + if(d3d9Globals.d3d9 == nil){ + RWERROR((ERR_GENERAL, "Direct3DCreate9() failed")); + return 0; + } + + d3d9Globals.numAdapters = d3d9Globals.d3d9->GetAdapterCount(); + d3d9Globals.adapter = 0; + + for(d3d9Globals.adapter = 0; d3d9Globals.adapter < d3d9Globals.numAdapters; d3d9Globals.adapter++) + if(d3d9Globals.d3d9->GetDeviceCaps(d3d9Globals.adapter, D3DDEVTYPE_HAL, &d3d9Globals.caps) == D3D_OK) + goto found; + // no adapter + d3d9Globals.d3d9->Release(); + d3d9Globals.d3d9 = nil; + RWERROR((ERR_GENERAL, "Direct3DCreate9() failed")); + return 0; + +found: + makeVideoModeList(); + return 1; +} + +static int +closeD3D(void) +{ + ULONG ref = d3d9Globals.d3d9->Release(); + if(ref != 0) + printf("IDirect3D9_Release did not destroy\n"); + d3d9Globals.d3d9 = nil; + rwFree(d3d9Globals.modes); + d3d9Globals.modes = nil; + d3d9Globals.numModes = 0; + d3d9Globals.currentMode = 0; + return 1; +} + +static int +startD3D(void) +{ + HRESULT hr; + int vp; + if(d3d9Globals.caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) + vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; + else + vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; + + uint32 width, height, depth; + D3DFORMAT format, zformat; + d3d9Globals.startMode = d3d9Globals.modes[d3d9Globals.currentMode]; + format = d3d9Globals.startMode.mode.Format; + + bool windowed = !(d3d9Globals.startMode.flags & VIDEOMODEEXCLUSIVE); + + // Use window size in windowed mode, otherwise get size from video mode + if(windowed){ + RECT rect; + GetClientRect(d3d9Globals.window, &rect); + width = rect.right - rect.left; + height = rect.bottom - rect.top; + }else{ + // this will be much better for restoring after iconification + SetWindowLong(d3d9Globals.window, GWL_STYLE, WS_POPUP); + width = d3d9Globals.startMode.mode.Width; + height = d3d9Globals.startMode.mode.Height; + } + + // See if we can get an alpha channel + if(format == D3DFMT_X8R8G8B8){ + if(d3d9Globals.d3d9->CheckDeviceType(d3d9Globals.adapter, D3DDEVTYPE_HAL, format, D3DFMT_A8R8G8B8, windowed) == D3D_OK) + format = D3DFMT_A8R8G8B8; + } + + depth = findFormatDepth(format); + + // TOOD: use something more sophisticated maybe? + if(depth == 32) + zformat = D3DFMT_D24S8; + else + zformat = D3DFMT_D16; + + d3d9Globals.present.BackBufferWidth = width; + d3d9Globals.present.BackBufferHeight = height; + d3d9Globals.present.BackBufferFormat = format; + d3d9Globals.present.BackBufferCount = 1; + d3d9Globals.present.MultiSampleType = d3d9Globals.msLevel == 1 ? + D3DMULTISAMPLE_NONE : (D3DMULTISAMPLE_TYPE)d3d9Globals.msLevel; + d3d9Globals.present.MultiSampleQuality = 0; + d3d9Globals.present.SwapEffect = D3DSWAPEFFECT_DISCARD; + d3d9Globals.present.hDeviceWindow = d3d9Globals.window; + d3d9Globals.present.Windowed = windowed; + d3d9Globals.present.EnableAutoDepthStencil = true; + d3d9Globals.present.AutoDepthStencilFormat = zformat; + d3d9Globals.present.Flags = 0; + d3d9Globals.present.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; +// d3d9Globals.present.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + d3d9Globals.present.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + + rw::d3d::isP8supported = 0; + + assert(d3d::d3ddevice == nil); + + BOOL icon = IsIconic(d3d9Globals.window); + IDirect3DDevice9 *dev; + hr = d3d9Globals.d3d9->CreateDevice(d3d9Globals.adapter, D3DDEVTYPE_HAL, + d3d9Globals.window, vp, &d3d9Globals.present, &dev); + if(FAILED(hr)){ + RWERROR((ERR_GENERAL, "CreateDevice() failed")); + return 0; + } + d3d::d3ddevice = dev; + return 1; +} + +static int +initD3D(void) +{ + int32 s, t; + + memset(&deviceCache, 0, sizeof(deviceCache)); + d3ddevice->GetRenderTarget(0, &d3d9Globals.defaultRenderTarget); + d3d9Globals.defaultRenderTarget->Release(); // refcount increased by Get + deviceCache.renderTargets[0] = d3d9Globals.defaultRenderTarget; + d3ddevice->GetDepthStencilSurface(&d3d9Globals.defaultDepthSurf); + d3d9Globals.defaultDepthSurf->Release(); // refcount increased by Get + deviceCache.depthSurface = d3d9Globals.defaultDepthSurf; + + d3d9Globals.numTextures = 0; + d3d9Globals.numVertexShaders = 0; + d3d9Globals.numPixelShaders = 0; + d3d9Globals.numVertexBuffers = 0; + d3d9Globals.numIndexBuffers = 0; + d3d9Globals.numVertexDeclarations = 0; + + VertexConstantData constants; + constants.normal.x = 0.0f; + constants.normal.y = 0.0f; + constants.normal.z = 0.0f; + constants.color.red = 0; + constants.color.green = 0; + constants.color.blue = 0; + constants.color.alpha = 255; + for(s = 0; s < 8; s++){ + constants.texCoors[s].u = 0.0f; + constants.texCoors[s].v = 0.0f; + } + assert(constantVertexStream == nil); + constantVertexStream = createVertexBuffer(sizeof(constants)*10000, 0, false); + assert(constantVertexStream); + uint8 *lockedvertices = lockVertices(constantVertexStream, 0, sizeof(constants), D3DLOCK_NOSYSLOCK); + assert(lockedvertices); + memcpy(lockedvertices, &constants, sizeof(constants)); + unlockVertices(constantVertexStream); + setStreamSource(2, constantVertexStream, 0, 0); + + d3ddevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL); + rwStateCache.alphafunc = ALPHAGREATEREQUAL; + d3ddevice->SetRenderState(D3DRS_ALPHAREF, 10); + rwStateCache.alpharef = 10; + + rwStateCache.gsalpha = 0; + rwStateCache.gsalpharef = 128; + + d3ddevice->SetRenderState(D3DRS_FOGENABLE, FALSE); + rwStateCache.fogenable = 0; + d3ddevice->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_LINEAR); + + d3ddevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + rwStateCache.cullmode = CULLNONE; + + d3ddevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); + d3ddevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + rwStateCache.srcblend = BLENDSRCALPHA; + d3ddevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + rwStateCache.destblend = BLENDINVSRCALPHA; + d3ddevice->SetRenderState(D3DRS_ALPHABLENDENABLE, 0); + rwStateCache.vertexAlpha = 0; + rwStateCache.textureAlpha = 0; + + rwStateCache.stencilenable = 0; + d3ddevice->SetRenderState(D3DRS_STENCILENABLE, FALSE); + rwStateCache.stencilfail = STENCILKEEP; + d3ddevice->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP); + rwStateCache.stencilzfail = STENCILKEEP; + d3ddevice->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP); + rwStateCache.stencilpass = STENCILKEEP; + d3ddevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP); + rwStateCache.stencilfunc = STENCILALWAYS; + d3ddevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); + rwStateCache.stencilref = 0; + d3ddevice->SetRenderState(D3DRS_STENCILREF, 0); + rwStateCache.stencilmask = 0xFFFFFFFF; + d3ddevice->SetRenderState(D3DRS_STENCILMASK, 0xFFFFFFFF); + rwStateCache.stencilwritemask = 0xFFFFFFFF; + d3ddevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xFFFFFFFF); + + setTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); +// setTextureStageState(0, D3DTSS_CONSTANT, 0xFFFFFFFF); +// setTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); +// setTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_CONSTANT); +// setTextureStageState(0, D3DTSS_COLOROP, D3DTA_CONSTANT); + + // These states exist, not all do + validStates[D3DRS_ZENABLE] = 1; + validStates[D3DRS_FILLMODE] = 1; + validStates[D3DRS_SHADEMODE] = 1; + validStates[D3DRS_ZWRITEENABLE] = 1; + validStates[D3DRS_ALPHATESTENABLE] = 1; + validStates[D3DRS_LASTPIXEL] = 1; + validStates[D3DRS_SRCBLEND] = 1; + validStates[D3DRS_DESTBLEND] = 1; + validStates[D3DRS_CULLMODE] = 1; + validStates[D3DRS_ZFUNC] = 1; + validStates[D3DRS_ALPHAREF] = 1; + validStates[D3DRS_ALPHAFUNC] = 1; + validStates[D3DRS_DITHERENABLE] = 1; + validStates[D3DRS_ALPHABLENDENABLE] = 1; + validStates[D3DRS_FOGENABLE] = 1; + validStates[D3DRS_SPECULARENABLE] = 1; + validStates[D3DRS_FOGCOLOR] = 1; + validStates[D3DRS_FOGTABLEMODE] = 1; + validStates[D3DRS_FOGSTART] = 1; + validStates[D3DRS_FOGEND] = 1; + validStates[D3DRS_FOGDENSITY] = 1; + validStates[D3DRS_RANGEFOGENABLE] = 1; + validStates[D3DRS_STENCILENABLE] = 1; + validStates[D3DRS_STENCILFAIL] = 1; + validStates[D3DRS_STENCILZFAIL] = 1; + validStates[D3DRS_STENCILPASS] = 1; + validStates[D3DRS_STENCILFUNC] = 1; + validStates[D3DRS_STENCILREF] = 1; + validStates[D3DRS_STENCILMASK] = 1; + validStates[D3DRS_STENCILWRITEMASK] = 1; + validStates[D3DRS_TEXTUREFACTOR] = 1; + validStates[D3DRS_WRAP0] = 1; + validStates[D3DRS_WRAP1] = 1; + validStates[D3DRS_WRAP2] = 1; + validStates[D3DRS_WRAP3] = 1; + validStates[D3DRS_WRAP4] = 1; + validStates[D3DRS_WRAP5] = 1; + validStates[D3DRS_WRAP6] = 1; + validStates[D3DRS_WRAP7] = 1; + validStates[D3DRS_CLIPPING] = 1; + validStates[D3DRS_LIGHTING] = 1; + validStates[D3DRS_AMBIENT] = 1; + validStates[D3DRS_FOGVERTEXMODE] = 1; + validStates[D3DRS_COLORVERTEX] = 1; + validStates[D3DRS_LOCALVIEWER] = 1; + validStates[D3DRS_NORMALIZENORMALS] = 1; + validStates[D3DRS_DIFFUSEMATERIALSOURCE] = 1; + validStates[D3DRS_SPECULARMATERIALSOURCE] = 1; + validStates[D3DRS_AMBIENTMATERIALSOURCE] = 1; + validStates[D3DRS_EMISSIVEMATERIALSOURCE] = 1; + validStates[D3DRS_VERTEXBLEND] = 1; + validStates[D3DRS_CLIPPLANEENABLE] = 1; + validStates[D3DRS_POINTSIZE] = 1; + validStates[D3DRS_POINTSIZE_MIN] = 1; + validStates[D3DRS_POINTSPRITEENABLE] = 1; + validStates[D3DRS_POINTSCALEENABLE] = 1; + validStates[D3DRS_POINTSCALE_A] = 1; + validStates[D3DRS_POINTSCALE_B] = 1; + validStates[D3DRS_POINTSCALE_C] = 1; + validStates[D3DRS_MULTISAMPLEANTIALIAS] = 1; + validStates[D3DRS_MULTISAMPLEMASK] = 1; + validStates[D3DRS_PATCHEDGESTYLE] = 1; + validStates[D3DRS_DEBUGMONITORTOKEN] = 1; + validStates[D3DRS_POINTSIZE_MAX] = 1; + validStates[D3DRS_INDEXEDVERTEXBLENDENABLE] = 1; + validStates[D3DRS_COLORWRITEENABLE] = 1; + validStates[D3DRS_TWEENFACTOR] = 1; + validStates[D3DRS_BLENDOP] = 1; + validStates[D3DRS_POSITIONDEGREE] = 1; + validStates[D3DRS_NORMALDEGREE] = 1; + validStates[D3DRS_SCISSORTESTENABLE] = 1; + validStates[D3DRS_SLOPESCALEDEPTHBIAS] = 1; + validStates[D3DRS_ANTIALIASEDLINEENABLE] = 1; + validStates[D3DRS_MINTESSELLATIONLEVEL] = 1; + validStates[D3DRS_MAXTESSELLATIONLEVEL] = 1; + validStates[D3DRS_ADAPTIVETESS_X] = 1; + validStates[D3DRS_ADAPTIVETESS_Y] = 1; + validStates[D3DRS_ADAPTIVETESS_Z] = 1; + validStates[D3DRS_ADAPTIVETESS_W] = 1; + validStates[D3DRS_ENABLEADAPTIVETESSELLATION] = 1; + validStates[D3DRS_TWOSIDEDSTENCILMODE] = 1; + validStates[D3DRS_CCW_STENCILFAIL] = 1; + validStates[D3DRS_CCW_STENCILZFAIL] = 1; + validStates[D3DRS_CCW_STENCILPASS] = 1; + validStates[D3DRS_CCW_STENCILFUNC] = 1; + validStates[D3DRS_COLORWRITEENABLE1] = 1; + validStates[D3DRS_COLORWRITEENABLE2] = 1; + validStates[D3DRS_COLORWRITEENABLE3] = 1; + validStates[D3DRS_BLENDFACTOR] = 1; + validStates[D3DRS_SRGBWRITEENABLE] = 1; + validStates[D3DRS_DEPTHBIAS] = 1; + validStates[D3DRS_WRAP8] = 1; + validStates[D3DRS_WRAP9] = 1; + validStates[D3DRS_WRAP10] = 1; + validStates[D3DRS_WRAP11] = 1; + validStates[D3DRS_WRAP12] = 1; + validStates[D3DRS_WRAP13] = 1; + validStates[D3DRS_WRAP14] = 1; + validStates[D3DRS_WRAP15] = 1; + validStates[D3DRS_SEPARATEALPHABLENDENABLE] = 1; + validStates[D3DRS_SRCBLENDALPHA] = 1; + validStates[D3DRS_DESTBLENDALPHA] = 1; + validStates[D3DRS_BLENDOPALPHA] = 1; + + validTexStates[D3DTSS_COLOROP] = 1; + validTexStates[D3DTSS_COLORARG1] = 1; + validTexStates[D3DTSS_COLORARG2] = 1; + validTexStates[D3DTSS_ALPHAOP] = 1; + validTexStates[D3DTSS_ALPHAARG1] = 1; + validTexStates[D3DTSS_ALPHAARG2] = 1; + validTexStates[D3DTSS_BUMPENVMAT00] = 1; + validTexStates[D3DTSS_BUMPENVMAT01] = 1; + validTexStates[D3DTSS_BUMPENVMAT10] = 1; + validTexStates[D3DTSS_BUMPENVMAT11] = 1; + validTexStates[D3DTSS_TEXCOORDINDEX] = 1; + validTexStates[D3DTSS_BUMPENVLSCALE] = 1; + validTexStates[D3DTSS_BUMPENVLOFFSET] = 1; + validTexStates[D3DTSS_TEXTURETRANSFORMFLAGS] = 1; + validTexStates[D3DTSS_COLORARG0] = 1; + validTexStates[D3DTSS_ALPHAARG0] = 1; + validTexStates[D3DTSS_RESULTARG] = 1; + validTexStates[D3DTSS_CONSTANT] = 1; + + // Save the current states + for(s = 0; s < MAXNUMSTATES; s++) + if(validStates[s]){ + d3ddevice->GetRenderState((D3DRENDERSTATETYPE)s, (DWORD*)&d3dStates[s]); + stateCache[s].value = d3dStates[s]; + } + for(t = 0; t < MAXNUMTEXSTATES; t++) + if(validTexStates[t]) + for(s = 0; s < MAXNUMSTAGES; s++){ + d3ddevice->GetTextureStageState(s, (D3DTEXTURESTAGESTATETYPE)t, (DWORD*)&d3dTextureStageStates[t][s]); + textureStageStateCache[t][s].value = d3dTextureStageStates[t][s]; + } + for(t = 1; t < MAXNUMSAMPLERSTATES; t++) + for(s = 0; s < MAXNUMSTAGES; s++){ + d3ddevice->GetSamplerState(s, (D3DSAMPLERSTATETYPE)t, (DWORD*)&d3dSamplerStates[t][s]); + d3dSamplerStates[t][s] = d3dSamplerStates[t][s]; + } + // init rw cache + for(t = 0; t < MAXNUMSTAGES; t++){ + setFilterMode(t, Texture::NEAREST); + setAddressU(t, Texture::WRAP); + setAddressV(t, Texture::WRAP); + } + + IDirect3DSurface9 *surf; + D3DLOCKED_RECT lr; + uint8 whitepixel[4] = {0xFF, 0xFF, 0xFF, 0xFF}; + whiteTex = (IDirect3DTexture9*)createTexture(1, 1, 1, 0, D3DFMT_X8R8G8B8); + whiteTex->GetSurfaceLevel(0, &surf); + HRESULT res = surf->LockRect(&lr, 0, D3DLOCK_NOSYSLOCK); + assert(res == D3D_OK); + memcpy(lr.pBits, whitepixel, 4); + surf->UnlockRect(); + surf->Release(); + + openIm2D(); + openIm3D(); + + return 1; +} + +static int +termD3D(void) +{ + destroyVertexBuffer(constantVertexStream); + constantVertexStream = nil; + + destroyTexture(whiteTex); + whiteTex = nil; + + closeIm3D(); + closeIm2D(); + + releaseVideoMemory(); + + ULONG ref = d3d::d3ddevice->Release(); + if(ref != 0) + printf("IDirect3D9Device_Release did not destroy\n"); + d3d::d3ddevice = nil; + return 1; +} + +static int +finalizeD3D(void) +{ + return 1; +} + +static int +deviceSystem(DeviceReq req, void *arg, int32 n) +{ + D3DADAPTER_IDENTIFIER9 adapter; + VideoMode *rwmode; + + switch(req){ + case DEVICEOPEN: + return openD3D((EngineOpenParams*)arg); + case DEVICECLOSE: + return closeD3D(); + + case DEVICEINIT: + return startD3D() && initD3D(); + case DEVICETERM: + return termD3D(); + + case DEVICEFINALIZE: + return finalizeD3D(); + + + case DEVICEGETNUMSUBSYSTEMS: + return d3d9Globals.numAdapters; + + case DEVICEGETCURRENTSUBSYSTEM: + return d3d9Globals.adapter; + + case DEVICESETSUBSYSTEM: + if(n >= d3d9Globals.numAdapters) + return 0; + d3d9Globals.adapter = n; + if(d3d9Globals.d3d9->GetDeviceCaps(d3d9Globals.adapter, D3DDEVTYPE_HAL, &d3d9Globals.caps) != D3D_OK) + return 0; + makeVideoModeList(); + return 1; + + case DEVICEGETSUBSSYSTEMINFO: + if(n >= d3d9Globals.numAdapters) + return 0; + if(d3d9Globals.d3d9->GetAdapterIdentifier(d3d9Globals.adapter, 0, &adapter) != D3D_OK) + return 0; + strncpy(((SubSystemInfo*)arg)->name, adapter.Description, sizeof(SubSystemInfo::name)); + return 1; + + + case DEVICEGETNUMVIDEOMODES: + return d3d9Globals.numModes; + + case DEVICEGETCURRENTVIDEOMODE: + return d3d9Globals.currentMode; + + case DEVICESETVIDEOMODE: + if(n >= d3d9Globals.numModes) + return 0; + d3d9Globals.currentMode = n; + return 1; + + case DEVICEGETVIDEOMODEINFO: + rwmode = (VideoMode*)arg; + rwmode->width = d3d9Globals.modes[n].mode.Width; + rwmode->height = d3d9Globals.modes[n].mode.Height; + rwmode->depth = findFormatDepth(d3d9Globals.modes[n].mode.Format); + rwmode->flags = d3d9Globals.modes[n].flags; + return 1; + case DEVICEGETMAXMULTISAMPLINGLEVELS: + { + assert(d3d9Globals.d3d9 != nil); + uint32 level; + DWORD quality; + for (level = D3DMULTISAMPLE_16_SAMPLES; level > D3DMULTISAMPLE_NONMASKABLE; level--) { + if (SUCCEEDED(d3d9Globals.d3d9->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3d9Globals.startMode.mode.Format, + !(d3d9Globals.startMode.flags & VIDEOMODEEXCLUSIVE), (D3DMULTISAMPLE_TYPE)level, + &quality))) + return level; + } + } + return 1; + case DEVICEGETMULTISAMPLINGLEVELS: + if(d3d9Globals.msLevel == 0) + return 1; + return d3d9Globals.msLevel; + case DEVICESETMULTISAMPLINGLEVELS: + d3d9Globals.msLevel = (uint32)n; + return 1; + } + return 1; +} + +Device renderdevice = { + 0.0f, 1.0f, + d3d::beginUpdate, + d3d::endUpdate, + d3d::clearCamera, + d3d::showRaster, + d3d::rasterRenderFast, + d3d::setRwRenderState, + d3d::getRwRenderState, + d3d::im2DRenderLine, + d3d::im2DRenderTriangle, + d3d::im2DRenderPrimitive, + d3d::im2DRenderIndexedPrimitive, + d3d::im3DTransform, + d3d::im3DRenderPrimitive, + d3d::im3DRenderIndexedPrimitive, + d3d::im3DEnd, + d3d::deviceSystem, +}; + +#endif +} +} diff --git a/vendor/librw/src/d3d/d3dimmed.cpp b/vendor/librw/src/d3d/d3dimmed.cpp new file mode 100644 index 0000000..331434b --- /dev/null +++ b/vendor/librw/src/d3d/d3dimmed.cpp @@ -0,0 +1,386 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3d9.h" +#include "rwd3dimpl.h" + +namespace rw { +namespace d3d { + +#ifdef RW_D3D9 + +// might want to tweak this +#define NUMINDICES 10000 +#define NUMVERTICES 10000 + +static int primTypeMap[] = { + D3DPT_POINTLIST, // invalid + D3DPT_LINELIST, + D3DPT_LINESTRIP, + D3DPT_TRIANGLELIST, + D3DPT_TRIANGLESTRIP, + D3DPT_TRIANGLEFAN, + D3DPT_POINTLIST, // actually not supported! +}; + +static IDirect3DVertexDeclaration9 *im2ddecl; +static IDirect3DVertexBuffer9 *im2dvertbuf; +static IDirect3DIndexBuffer9 *im2dindbuf; + +void *im2dOverridePS; + +void +openIm2D(void) +{ + D3DVERTEXELEMENT9 elements[4] = { +// can't get proper fog with this :( +// { 0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT, 0 }, + { 0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, + { 0, offsetof(Im2DVertex, color), D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 }, + { 0, offsetof(Im2DVertex, u), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, + D3DDECL_END() + }; + assert(im2ddecl == nil); + im2ddecl = (IDirect3DVertexDeclaration9*)d3d9::createVertexDeclaration((d3d9::VertexElement*)elements); + assert(im2ddecl); + + assert(im2dvertbuf == nil); + im2dvertbuf = (IDirect3DVertexBuffer9*)createVertexBuffer(NUMVERTICES*sizeof(Im2DVertex), 0, true); + assert(im2dvertbuf); + addDynamicVB(NUMVERTICES*sizeof(Im2DVertex), 0, &im2dvertbuf); + + assert(im2dindbuf == nil); + im2dindbuf = (IDirect3DIndexBuffer9*)createIndexBuffer(NUMINDICES*sizeof(uint16), true); + assert(im2dindbuf); + addDynamicIB(NUMINDICES*sizeof(uint16), &im2dindbuf); +} + +void +closeIm2D(void) +{ + d3d9::destroyVertexDeclaration(im2ddecl); + im2ddecl = nil; + + removeDynamicVB(&im2dvertbuf); + destroyVertexBuffer(im2dvertbuf); + im2dvertbuf = nil; + + removeDynamicIB(&im2dindbuf); + destroyIndexBuffer(im2dindbuf); + im2dindbuf = nil; +} + +static Im2DVertex tmpprimbuf[3]; + +void +im2DRenderLine(void *vertices, int32 numVertices, int32 vert1, int32 vert2) +{ + Im2DVertex *verts = (Im2DVertex*)vertices; + tmpprimbuf[0] = verts[vert1]; + tmpprimbuf[1] = verts[vert2]; + im2DRenderPrimitive(PRIMTYPELINELIST, tmpprimbuf, 2); +} + +void +im2DRenderTriangle(void *vertices, int32 numVertices, int32 vert1, int32 vert2, int32 vert3) +{ + Im2DVertex *verts = (Im2DVertex*)vertices; + tmpprimbuf[0] = verts[vert1]; + tmpprimbuf[1] = verts[vert2]; + tmpprimbuf[2] = verts[vert3]; + im2DRenderPrimitive(PRIMTYPETRILIST, tmpprimbuf, 3); +} + +void +im2DSetXform(void) +{ + float xform[4]; + Camera *cam; + cam = (Camera*)engine->currentCamera; + xform[0] = 2.0f/cam->frameBuffer->width; + xform[1] = -2.0f/cam->frameBuffer->height; + xform[2] = -1.0f; + xform[3] = 1.0f; + // TODO: should cache this... + d3ddevice->SetVertexShaderConstantF(VSLOC_afterLights, xform, 1); +} + +void +im2DRenderPrimitive(PrimitiveType primType, void *vertices, int32 numVertices) +{ + if(numVertices > NUMVERTICES){ + // TODO: error + return; + } + uint8 *lockedvertices = lockVertices(im2dvertbuf, 0, numVertices*sizeof(Im2DVertex), D3DLOCK_DISCARD); + memcpy(lockedvertices, vertices, numVertices*sizeof(Im2DVertex)); + unlockVertices(im2dvertbuf); + + setStreamSource(0, im2dvertbuf, 0, sizeof(Im2DVertex)); + setVertexDeclaration(im2ddecl); + + im2DSetXform(); + + setVertexShader(im2d_VS); + if(im2dOverridePS) + setPixelShader(im2dOverridePS); + else if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(im2d_tex_PS); + else + setPixelShader(im2d_PS); + + d3d::flushCache(); + + uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = numVertices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = numVertices-1; + break; + case PRIMTYPETRILIST: + primCount = numVertices/3; + break; + case PRIMTYPETRISTRIP: + primCount = numVertices-2; + break; + case PRIMTYPETRIFAN: + primCount = numVertices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = numVertices; + break; + } + d3ddevice->DrawPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, primCount); +} + +void +im2DRenderIndexedPrimitive(PrimitiveType primType, + void *vertices, int32 numVertices, void *indices, int32 numIndices) +{ + if(numVertices > NUMVERTICES || + numIndices > NUMINDICES){ + // TODO: error + return; + } + uint16 *lockedindices = lockIndices(im2dindbuf, 0, numIndices*sizeof(uint16), D3DLOCK_DISCARD); + memcpy(lockedindices, indices, numIndices*sizeof(uint16)); + unlockIndices(im2dindbuf); + + uint8 *lockedvertices = lockVertices(im2dvertbuf, 0, numVertices*sizeof(Im2DVertex), D3DLOCK_DISCARD); + memcpy(lockedvertices, vertices, numVertices*sizeof(Im2DVertex)); + unlockVertices(im2dvertbuf); + + setStreamSource(0, im2dvertbuf, 0, sizeof(Im2DVertex)); + setIndices(im2dindbuf); + setVertexDeclaration(im2ddecl); + + im2DSetXform(); + + setVertexShader(im2d_VS); + if(im2dOverridePS) + setPixelShader(im2dOverridePS); + else if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(im2d_tex_PS); + else + setPixelShader(im2d_PS); + + d3d::flushCache(); + + uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = numIndices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = numIndices-1; + break; + case PRIMTYPETRILIST: + primCount = numIndices/3; + break; + case PRIMTYPETRISTRIP: + primCount = numIndices-2; + break; + case PRIMTYPETRIFAN: + primCount = numIndices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = numIndices; + break; + } + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, + 0, numVertices, + 0, primCount); +} + + +// Im3D + + +static IDirect3DVertexDeclaration9 *im3ddecl; +static IDirect3DVertexBuffer9 *im3dvertbuf; +static IDirect3DIndexBuffer9 *im3dindbuf; +static int32 num3DVertices; + +void +openIm3D(void) +{ + D3DVERTEXELEMENT9 elements[4] = { + { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, + { 0, offsetof(Im3DVertex, color), D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 }, + { 0, offsetof(Im3DVertex, u), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, + D3DDECL_END() + }; + + assert(im3ddecl == nil); + im3ddecl = (IDirect3DVertexDeclaration9*)d3d9::createVertexDeclaration((d3d9::VertexElement*)elements); + assert(im3ddecl); + + assert(im3dvertbuf == nil); + im3dvertbuf = (IDirect3DVertexBuffer9*)createVertexBuffer(NUMVERTICES*sizeof(Im3DVertex), 0, true); + assert(im3dvertbuf); + addDynamicVB(NUMVERTICES*sizeof(Im3DVertex), 0, &im3dvertbuf); + + assert(im3dindbuf == nil); + im3dindbuf = (IDirect3DIndexBuffer9*)createIndexBuffer(NUMINDICES*sizeof(uint16), true); + assert(im3dindbuf); + addDynamicIB(NUMINDICES*sizeof(uint16), &im3dindbuf); +} + +void +closeIm3D(void) +{ + d3d9::destroyVertexDeclaration(im3ddecl); + im3ddecl = nil; + + removeDynamicVB(&im3dvertbuf); + destroyVertexBuffer(im3dvertbuf); + im3dvertbuf = nil; + + removeDynamicIB(&im3dindbuf); + destroyIndexBuffer(im3dindbuf); + im3dindbuf = nil; +} + +void +im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags) +{ + if(world == nil) + uploadMatrices(); + else + uploadMatrices(world); + + if((flags & im3d::VERTEXUV) == 0) + SetRenderStatePtr(TEXTURERASTER, nil); + + static RGBA white = { 255, 255, 255, 255 }; + static SurfaceProperties surfprops = { 0.0f, 0.0f, 0.0f }; + setMaterial(white, surfprops); + + uint8 *lockedvertices = lockVertices(im3dvertbuf, 0, numVertices*sizeof(Im3DVertex), D3DLOCK_DISCARD); + memcpy(lockedvertices, vertices, numVertices*sizeof(Im3DVertex)); + unlockVertices(im3dvertbuf); + + setStreamSource(0, im3dvertbuf, 0, sizeof(Im3DVertex)); + setVertexDeclaration(im3ddecl); + + setVertexShader(default_amb_VS); + + num3DVertices = numVertices; +} + +void +im3DRenderPrimitive(PrimitiveType primType) +{ + if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(default_tex_PS); + else + setPixelShader(default_PS); + + d3d::flushCache(); + + uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = num3DVertices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = num3DVertices-1; + break; + case PRIMTYPETRILIST: + primCount = num3DVertices/3; + break; + case PRIMTYPETRISTRIP: + primCount = num3DVertices-2; + break; + case PRIMTYPETRIFAN: + primCount = num3DVertices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = num3DVertices; + break; + } + d3ddevice->DrawPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, primCount); +} + +void +im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices) +{ + uint16 *lockedindices = lockIndices(im3dindbuf, 0, numIndices*sizeof(uint16), D3DLOCK_DISCARD); + memcpy(lockedindices, indices, numIndices*sizeof(uint16)); + unlockIndices(im3dindbuf); + + setIndices(im3dindbuf); + + if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(default_tex_PS); + else + setPixelShader(default_PS); + + d3d::flushCache(); + + uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = numIndices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = numIndices-1; + break; + case PRIMTYPETRILIST: + primCount = numIndices/3; + break; + case PRIMTYPETRISTRIP: + primCount = numIndices-2; + break; + case PRIMTYPETRIFAN: + primCount = numIndices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = numIndices; + break; + } + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, + 0, num3DVertices, + 0, primCount); +} + +void +im3DEnd(void) +{ +} + +#endif +} +} diff --git a/vendor/librw/src/d3d/d3drender.cpp b/vendor/librw/src/d3d/d3drender.cpp new file mode 100644 index 0000000..86b7edc --- /dev/null +++ b/vendor/librw/src/d3d/d3drender.cpp @@ -0,0 +1,413 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" + +namespace rw { +namespace d3d { + +#ifdef RW_D3D9 +IDirect3DDevice9 *d3ddevice = nil; + +#define MAX_LIGHTS 8 + + +#define VS_NAME g_vs20_main +#define PS_NAME g_ps20_main +void *default_amb_VS; +void *default_amb_dir_VS; +void *default_all_VS; +void *default_PS; +void *default_tex_PS; +void *im2d_VS; +void *im2d_PS; +void *im2d_tex_PS; + + +void +createDefaultShaders(void) +{ + { + static +#include "shaders/default_amb_VS.h" + default_amb_VS = createVertexShader((void*)VS_NAME); + assert(default_amb_VS); + } + { + static +#include "shaders/default_amb_dir_VS.h" + default_amb_dir_VS = createVertexShader((void*)VS_NAME); + assert(default_amb_dir_VS); + } + { + static +#include "shaders/default_all_VS.h" + default_all_VS = createVertexShader((void*)VS_NAME); + assert(default_all_VS); + } + + { + static +#include "shaders/default_PS.h" + default_PS = createPixelShader((void*)PS_NAME); + assert(default_PS); + } + { + static +#include "shaders/default_tex_PS.h" + default_tex_PS = createPixelShader((void*)PS_NAME); + assert(default_tex_PS); + } + + { + static +#include "shaders/im2d_VS.h" + im2d_VS = createVertexShader((void*)VS_NAME); + assert(im2d_VS); + } + { + static +#include "shaders/im2d_PS.h" + im2d_PS = createPixelShader((void*)PS_NAME); + assert(im2d_PS); + } + { + static +#include "shaders/im2d_tex_PS.h" + im2d_tex_PS = createPixelShader((void*)PS_NAME); + assert(im2d_tex_PS); + } +} + +void +destroyDefaultShaders(void) +{ + destroyVertexShader(default_amb_VS); + default_amb_VS = nil; + destroyVertexShader(default_amb_dir_VS); + default_amb_dir_VS = nil; + destroyVertexShader(default_all_VS); + default_all_VS = nil; + + destroyPixelShader(default_PS); + default_PS = nil; + destroyPixelShader(default_tex_PS); + default_tex_PS = nil; + + destroyVertexShader(im2d_VS); + im2d_VS = nil; + destroyPixelShader(im2d_PS); + im2d_PS = nil; + destroyPixelShader(im2d_tex_PS); + im2d_tex_PS = nil; +} + + +void +lightingCB_Fix(Atomic *atomic) +{ + WorldLights lightData; + Light *directionals[8]; + Light *locals[8]; + lightData.directionals = directionals; + lightData.numDirectionals = 8; + lightData.locals = locals; + lightData.numLocals = 8; + + ((World*)engine->currentWorld)->enumerateLights(atomic, &lightData); + + int i, n; + RGBA amb; + D3DLIGHT9 light; + light.Type = D3DLIGHT_DIRECTIONAL; + //light.Diffuse = { 0.8f, 0.8f, 0.8f, 1.0f }; + light.Specular = { 0.0f, 0.0f, 0.0f, 0.0f }; + light.Ambient = { 0.0f, 0.0f, 0.0f, 0.0f }; + light.Position = { 0.0f, 0.0f, 0.0f }; + //light.Direction = { 0.0f, 0.0f, -1.0f }; + light.Range = 0.0f; + light.Falloff = 0.0f; + light.Attenuation0 = 0.0f; + light.Attenuation1 = 0.0f; + light.Attenuation2 = 0.0f; + light.Theta = 0.0f; + light.Phi = 0.0f; + + convColor(&amb, &lightData.ambient); + d3d::setRenderState(D3DRS_AMBIENT, D3DCOLOR_RGBA(amb.red, amb.green, amb.blue, amb.alpha)); + + n = 0; + for(i = 0; i < lightData.numDirectionals; i++){ + if(n >= MAX_LIGHTS) + return; + Light *l = lightData.directionals[i]; + light.Type = D3DLIGHT_DIRECTIONAL; + light.Diffuse = *(D3DCOLORVALUE*)&l->color; + light.Direction = *(D3DVECTOR*)&l->getFrame()->getLTM()->at; + d3ddevice->SetLight(n, &light); + d3ddevice->LightEnable(n, TRUE); + n++; + } + + for(i = 0; i < lightData.numLocals; i++){ + if(n >= MAX_LIGHTS) + return; + Light *l = lightData.locals[i]; + switch(l->getType()){ + case Light::POINT: + light.Type = D3DLIGHT_POINT; + light.Diffuse = *(D3DCOLORVALUE*)&l->color; + light.Position = *(D3DVECTOR*)&l->getFrame()->getLTM()->pos; + light.Direction.x = 0.0f; + light.Direction.y = 0.0f; + light.Direction.z = 0.0f; + light.Range = l->radius; + light.Falloff = 1.0f; + light.Attenuation0 = 1.0f; + light.Attenuation1 = 0.0f/l->radius; + light.Attenuation2 = 5.0f/(l->radius*l->radius); + d3ddevice->SetLight(n, &light); + d3ddevice->LightEnable(n, TRUE); + n++; + break; + + case Light::SPOT: + light.Type = D3DLIGHT_SPOT; + light.Diffuse = *(D3DCOLORVALUE*)&l->color; + light.Position = *(D3DVECTOR*)&l->getFrame()->getLTM()->pos; + light.Direction = *(D3DVECTOR*)&l->getFrame()->getLTM()->at; + light.Range = l->radius; + light.Falloff = 1.0f; + light.Attenuation0 = 1.0f; + light.Attenuation1 = 0.0f/l->radius; + light.Attenuation2 = 5.0f/(l->radius*l->radius); + light.Theta = l->getAngle()*2.0f; + light.Phi = light.Theta; + d3ddevice->SetLight(n, &light); + d3ddevice->LightEnable(n, TRUE); + n++; + break; + + case Light::SOFTSPOT: + light.Type = D3DLIGHT_SPOT; + light.Diffuse = *(D3DCOLORVALUE*)&l->color; + light.Position = *(D3DVECTOR*)&l->getFrame()->getLTM()->pos; + light.Direction = *(D3DVECTOR*)&l->getFrame()->getLTM()->at; + light.Range = l->radius; + light.Falloff = 1.0f; + light.Attenuation0 = 1.0f; + light.Attenuation1 = 0.0f/l->radius; + light.Attenuation2 = 5.0f/(l->radius*l->radius); + light.Theta = 0.0f; + light.Phi = l->getAngle()*2.0f; + d3ddevice->SetLight(n, &light); + d3ddevice->LightEnable(n, TRUE); + n++; + break; + } + } + + for(; n < MAX_LIGHTS; n++) + d3ddevice->LightEnable(n, FALSE); +} + + +struct LightVS +{ + V3d color; float param0; + V3d position; float param1; + V3d direction; float param2; +}; + +void +setAmbient(const RGBAf &color) +{ + if(!equal(d3dShaderState.ambient, color)){ + d3dShaderState.ambient = color; + d3ddevice->SetVertexShaderConstantF(VSLOC_ambLight, (float*)&color, 1); + } +} + +void +setNumLights(int numDir, int numPoint, int numSpot) +{ + static int32 numLights[4*3]; + if(d3dShaderState.numDir != numDir || + d3dShaderState.numPoint != numPoint || + d3dShaderState.numSpot != numSpot){ + numLights[0] = d3dShaderState.numDir = numDir; + numLights[4] = d3dShaderState.numPoint = numPoint; + numLights[8] = d3dShaderState.numSpot = numSpot; + d3ddevice->SetVertexShaderConstantI(VSLOC_numLights, numLights, 3); + } +} + +int32 +uploadLights(WorldLights *lightData) +{ + int i; + int bits = 0; + float32 firstLight[4]; + firstLight[0] = 0; // directional + firstLight[1] = 0; // point + firstLight[2] = 0; // spot + firstLight[3] = 0; + + if(lightData->numAmbients) + bits |= VSLIGHT_AMBIENT; + + LightVS directionals[8]; + LightVS points[8]; + LightVS spots[8]; + for(i = 0; i < lightData->numDirectionals; i++){ + Light *l = lightData->directionals[i]; + directionals[i].color.x = l->color.red; + directionals[i].color.y = l->color.green; + directionals[i].color.z = l->color.blue; + directionals[i].direction = l->getFrame()->getLTM()->at; + bits |= VSLIGHT_DIRECT; + } + + int np = 0; + int ns = 0; + for(i = 0; i < lightData->numLocals; i++){ + Light *l = lightData->locals[i]; + + switch(l->getType()){ + case Light::POINT: + points[np].color.x = l->color.red; + points[np].color.y = l->color.green; + points[np].color.z = l->color.blue; + points[np].param0 = l->radius; + points[np].position = l->getFrame()->getLTM()->pos; + np++; + bits |= VSLIGHT_POINT; + break; + case Light::SPOT: + case Light::SOFTSPOT: + spots[ns].color.x = l->color.red; + spots[ns].color.y = l->color.green; + spots[ns].color.z = l->color.blue; + spots[ns].param0 = l->radius; + spots[ns].position = l->getFrame()->getLTM()->pos; + spots[ns].direction = l->getFrame()->getLTM()->at; + spots[ns].param1 = l->minusCosAngle; + // lower bound of falloff + if(l->getType() == Light::SOFTSPOT) + spots[ns].param2 = 0.0f; + else + spots[ns].param2 = 1.0f; + bits |= VSLIGHT_SPOT; + ns++; + break; + } + } + + firstLight[0] = 0; + int numDir = lightData->numDirectionals; + firstLight[1] = numDir + firstLight[0]; + int numPoint = np; + firstLight[2] = numPoint + firstLight[1]; + int numSpot = ns; + + setNumLights(numDir, numPoint, numSpot); + if(d3dShaderState.lightOffset[0] != firstLight[0] || + d3dShaderState.lightOffset[1] != firstLight[1] || + d3dShaderState.lightOffset[2] != firstLight[2]){ + d3dShaderState.lightOffset[0] = firstLight[0]; + d3dShaderState.lightOffset[1] = firstLight[1]; + d3dShaderState.lightOffset[2] = firstLight[2]; + d3ddevice->SetVertexShaderConstantF(VSLOC_lightOffset, firstLight, 1); + } + + int32 off = VSLOC_lights; + if(numDir) + d3ddevice->SetVertexShaderConstantF(off, (float*)&directionals, numDir*3); + off += numDir*3; + + if(numPoint) + d3ddevice->SetVertexShaderConstantF(off, (float*)&points, numPoint*3); + off += numPoint*3; + + if(numSpot) + d3ddevice->SetVertexShaderConstantF(off, (float*)&spots, numSpot*3); + + return bits; +} + +int32 +lightingCB_Shader(Atomic *atomic) +{ + WorldLights lightData; + Light *directionals[8]; + Light *locals[8]; + lightData.directionals = directionals; + lightData.numDirectionals = 8; + lightData.locals = locals; + lightData.numLocals = 8; + + if(atomic->geometry->flags & rw::Geometry::LIGHT){ + ((World*)engine->currentWorld)->enumerateLights(atomic, &lightData); + setAmbient(lightData.ambient); + if((atomic->geometry->flags & rw::Geometry::NORMALS) == 0){ + // Get rid of lights that need normals when we don't have any + lightData.numDirectionals = 0; + lightData.numLocals = 0; + } + return uploadLights(&lightData); + }else{ + static const RGBAf black = { 0.0f, 0.0f, 0.0f, 0.0f }; + setAmbient(black); + setNumLights(0, 0, 0); + return 0; + } +} + +static RawMatrix identityXform = { + { 1.0f, 0.0f, 0.0f }, 0.0f, + { 0.0f, 1.0f, 0.0f }, 0.0f, + { 0.0f, 0.0f, 1.0f }, 0.0f, + { 0.0f, 0.0f, 0.0f }, 1.0f +}; + +void +uploadMatrices(void) +{ + RawMatrix combined; + Camera *cam = engine->currentCamera; + d3ddevice->SetVertexShaderConstantF(VSLOC_world, (float*)&identityXform, 4); + d3ddevice->SetVertexShaderConstantF(VSLOC_normal, (float*)&identityXform, 4); + + RawMatrix::mult(&combined, &cam->devView, &cam->devProj); + d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&combined, 4); +} + +void +uploadMatrices(Matrix *worldMat) +{ + RawMatrix combined, world, worldview; + Camera *cam = engine->currentCamera; + convMatrix(&world, worldMat); + d3ddevice->SetVertexShaderConstantF(VSLOC_world, (float*)&world, 4); + // TODO: inverse transpose + d3ddevice->SetVertexShaderConstantF(VSLOC_normal, (float*)&world, 4); + + RawMatrix::mult(&worldview, &world, &cam->devView); + RawMatrix::mult(&combined, &worldview, &cam->devProj); + d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&combined, 4); +} + + + +#endif + +} +} diff --git a/vendor/librw/src/d3d/rwd3d.h b/vendor/librw/src/d3d/rwd3d.h new file mode 100644 index 0000000..4ce29be --- /dev/null +++ b/vendor/librw/src/d3d/rwd3d.h @@ -0,0 +1,411 @@ +#ifdef RW_D3D9 +#ifdef WITH_D3D +#include +#endif +#endif + +namespace rw { + +#ifdef RW_D3D9 + +#ifdef _WINDOWS_ +struct EngineOpenParams +{ + HWND window; +}; +#else +struct EngineOpenParams +{ + uint32 please_include_windows_h; +}; +#endif +#else +#ifdef _D3D9_H_ +#error "please don't include d3d9.h for non-d3d9 platforms" +#endif +#endif + +namespace d3d { + +extern bool32 isP8supported; + +extern Device renderdevice; + +#ifdef RW_D3D9 +#ifdef _D3D9_H_ +extern IDirect3DDevice9 *d3ddevice; +void setD3dMaterial(D3DMATERIAL9 *mat9); +#endif + +#define COLOR_ARGB(a, r, g, b) ((rw::uint32)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff))) + +struct Im3DVertex +{ + V3d position; + uint32 color; + float32 u, v; + + void setX(float32 x) { this->position.x = x; } + void setY(float32 y) { this->position.y = y; } + void setZ(float32 z) { this->position.z = z; } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { this->color = COLOR_ARGB(a, r, g, b); } + void setU(float32 u) { this->u = u; } + void setV(float32 v) { this->v = v; } + + float getX(void) { return this->position.x; } + float getY(void) { return this->position.y; } + float getZ(void) { return this->position.z; } + RGBA getColor(void) { return makeRGBA(this->color>>16 & 0xFF, this->color>>8 & 0xFF, + this->color & 0xFF, this->color>>24 & 0xFF); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + +struct Im2DVertex +{ + float32 x, y, z; + //float32 q; // recipz no longer used because we have a vertex stage now + float32 w; + uint32 color; + float32 u, v; + + void setScreenX(float32 x) { this->x = x; } + void setScreenY(float32 y) { this->y = y; } + void setScreenZ(float32 z) { this->z = z; } + void setCameraZ(float32 z) { this->w = z; } +// void setRecipCameraZ(float32 recipz) { this->q = recipz; } + void setRecipCameraZ(float32 recipz) { this->w = 1.0f/recipz; } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { this->color = COLOR_ARGB(a, r, g, b); } + void setU(float32 u, float recipZ) { this->u = u; } + void setV(float32 v, float recipZ) { this->v = v; } + + float getScreenX(void) { return this->x; } + float getScreenY(void) { return this->y; } + float getScreenZ(void) { return this->z; } +// float getCameraZ(void) { return 1.0f/this->q; } +// float getRecipCameraZ(void) { return this->q; } + float getCameraZ(void) { return this->w; } + float getRecipCameraZ(void) { return 1.0f/this->w; } + RGBA getColor(void) { return makeRGBA(this->color>>16 & 0xFF, this->color>>8 & 0xFF, + this->color & 0xFF, this->color>>24 & 0xFF); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + +#else +#ifndef MAKEFOURCC +#define MAKEFOURCC(ch0, ch1, ch2, ch3) \ + ((uint32)(uint8)(ch0) | ((uint32)(uint8)(ch1) << 8) | \ + ((uint32)(uint8)(ch2) << 16) | ((uint32)(uint8)(ch3) << 24 )) +#endif +enum { + D3DFMT_UNKNOWN = 0, + + D3DFMT_R8G8B8 = 20, + D3DFMT_A8R8G8B8 = 21, + D3DFMT_X8R8G8B8 = 22, + D3DFMT_R5G6B5 = 23, + D3DFMT_X1R5G5B5 = 24, + D3DFMT_A1R5G5B5 = 25, + D3DFMT_A4R4G4B4 = 26, + D3DFMT_R3G3B2 = 27, + D3DFMT_A8 = 28, + D3DFMT_A8R3G3B2 = 29, + D3DFMT_X4R4G4B4 = 30, + D3DFMT_A2B10G10R10 = 31, + D3DFMT_A8B8G8R8 = 32, + D3DFMT_X8B8G8R8 = 33, + D3DFMT_G16R16 = 34, + D3DFMT_A2R10G10B10 = 35, + D3DFMT_A16B16G16R16 = 36, + + D3DFMT_A8P8 = 40, + D3DFMT_P8 = 41, + + D3DFMT_L8 = 50, + D3DFMT_A8L8 = 51, + D3DFMT_A4L4 = 52, + + D3DFMT_V8U8 = 60, + D3DFMT_L6V5U5 = 61, + D3DFMT_X8L8V8U8 = 62, + D3DFMT_Q8W8V8U8 = 63, + D3DFMT_V16U16 = 64, + D3DFMT_A2W10V10U10 = 67, + + D3DFMT_UYVY = MAKEFOURCC('U', 'Y', 'V', 'Y'), + D3DFMT_R8G8_B8G8 = MAKEFOURCC('R', 'G', 'B', 'G'), + D3DFMT_YUY2 = MAKEFOURCC('Y', 'U', 'Y', '2'), + D3DFMT_G8R8_G8B8 = MAKEFOURCC('G', 'R', 'G', 'B'), + D3DFMT_DXT1 = MAKEFOURCC('D', 'X', 'T', '1'), + D3DFMT_DXT2 = MAKEFOURCC('D', 'X', 'T', '2'), + D3DFMT_DXT3 = MAKEFOURCC('D', 'X', 'T', '3'), + D3DFMT_DXT4 = MAKEFOURCC('D', 'X', 'T', '4'), + D3DFMT_DXT5 = MAKEFOURCC('D', 'X', 'T', '5'), + + D3DFMT_D16_LOCKABLE = 70, + D3DFMT_D32 = 71, + D3DFMT_D15S1 = 73, + D3DFMT_D24S8 = 75, + D3DFMT_D24X8 = 77, + D3DFMT_D24X4S4 = 79, + D3DFMT_D16 = 80, + + D3DFMT_D32F_LOCKABLE = 82, + D3DFMT_D24FS8 = 83, + + // d3d9ex only + /* Z-Stencil formats valid for CPU access */ + D3DFMT_D32_LOCKABLE = 84, + D3DFMT_S8_LOCKABLE = 85, + + D3DFMT_L16 = 81, + + D3DFMT_VERTEXDATA =100, + D3DFMT_INDEX16 =101, + D3DFMT_INDEX32 =102, + + D3DFMT_Q16W16V16U16 =110, + + D3DFMT_MULTI2_ARGB8 = MAKEFOURCC('M','E','T','1'), + + // Floating point surface formats + + // s10e5 formats (16-bits per channel) + D3DFMT_R16F = 111, + D3DFMT_G16R16F = 112, + D3DFMT_A16B16G16R16F = 113, + + // IEEE s23e8 formats (32-bits per channel) + D3DFMT_R32F = 114, + D3DFMT_G32R32F = 115, + D3DFMT_A32B32G32R32F = 116, + + D3DFMT_CxV8U8 = 117, + + // d3d9ex only + // Monochrome 1 bit per pixel format + D3DFMT_A1 = 118, + // 2.8 biased fixed point + D3DFMT_A2B10G10R10_XR_BIAS = 119, + // Binary format indicating that the data has no inherent type + D3DFMT_BINARYBUFFER = 199 +}; + +enum { + D3DLOCK_NOSYSLOCK = 0, // ignored + D3DPOOL_MANAGED = 0, // ignored + D3DPT_TRIANGLELIST = 4, + D3DPT_TRIANGLESTRIP = 5, + + + D3DDECLTYPE_FLOAT1 = 0, // 1D float expanded to (value, 0., 0., 1.) + D3DDECLTYPE_FLOAT2 = 1, // 2D float expanded to (value, value, 0., 1.) + D3DDECLTYPE_FLOAT3 = 2, // 3D float expanded to (value, value, value, 1.) + D3DDECLTYPE_FLOAT4 = 3, // 4D float + D3DDECLTYPE_D3DCOLOR = 4, // 4D packed unsigned bytes mapped to 0. to 1. range + // Input is in D3DCOLOR format (ARGB) expanded to (R, G, B, A) + D3DDECLTYPE_UBYTE4 = 5, // 4D unsigned byte + D3DDECLTYPE_SHORT2 = 6, // 2D signed short expanded to (value, value, 0., 1.) + D3DDECLTYPE_SHORT4 = 7, // 4D signed short + + D3DDECLTYPE_UBYTE4N = 8, // Each of 4 bytes is normalized by dividing to 255.0 + D3DDECLTYPE_SHORT2N = 9, // 2D signed short normalized (v[0]/32767.0,v[1]/32767.0,0,1) + D3DDECLTYPE_SHORT4N = 10, // 4D signed short normalized (v[0]/32767.0,v[1]/32767.0,v[2]/32767.0,v[3]/32767.0) + D3DDECLTYPE_USHORT2N = 11, // 2D unsigned short normalized (v[0]/65535.0,v[1]/65535.0,0,1) + D3DDECLTYPE_USHORT4N = 12, // 4D unsigned short normalized (v[0]/65535.0,v[1]/65535.0,v[2]/65535.0,v[3]/65535.0) + D3DDECLTYPE_UDEC3 = 13, // 3D unsigned 10 10 10 format expanded to (value, value, value, 1) + D3DDECLTYPE_DEC3N = 14, // 3D signed 10 10 10 format normalized and expanded to (v[0]/511.0, v[1]/511.0, v[2]/511.0, 1) + D3DDECLTYPE_FLOAT16_2 = 15, // Two 16-bit floating point values, expanded to (value, value, 0, 1) + D3DDECLTYPE_FLOAT16_4 = 16, // Four 16-bit floating point values + D3DDECLTYPE_UNUSED = 17, // When the type field in a decl is unused. + + + D3DDECLMETHOD_DEFAULT = 0, + + + D3DDECLUSAGE_POSITION = 0, + D3DDECLUSAGE_BLENDWEIGHT, // 1 + D3DDECLUSAGE_BLENDINDICES, // 2 + D3DDECLUSAGE_NORMAL, // 3 + D3DDECLUSAGE_PSIZE, // 4 + D3DDECLUSAGE_TEXCOORD, // 5 + D3DDECLUSAGE_TANGENT, // 6 + D3DDECLUSAGE_BINORMAL, // 7 + D3DDECLUSAGE_TESSFACTOR, // 8 + D3DDECLUSAGE_POSITIONT, // 9 + D3DDECLUSAGE_COLOR, // 10 + D3DDECLUSAGE_FOG, // 11 + D3DDECLUSAGE_DEPTH, // 12 + D3DDECLUSAGE_SAMPLE // 13 + , + + D3DUSAGE_AUTOGENMIPMAP = 0x400 +}; +#endif + +extern int vertFormatMap[]; + +void *createIndexBuffer(uint32 length, bool dynamic); +void destroyIndexBuffer(void *indexBuffer); +uint16 *lockIndices(void *indexBuffer, uint32 offset, uint32 size, uint32 flags); +void unlockIndices(void *indexBuffer); + +void *createVertexBuffer(uint32 length, uint32 fvf, bool dynamic); +void destroyVertexBuffer(void *vertexBuffer); +uint8 *lockVertices(void *vertexBuffer, uint32 offset, uint32 size, uint32 flags); +void unlockVertices(void *vertexBuffer); + +void *createTexture(int32 width, int32 height, int32 levels, uint32 usage, uint32 format); +void destroyTexture(void *texture); +uint8 *lockTexture(void *texture, int32 level); +void unlockTexture(void *texture, int32 level); + +// Native Texture and Raster + +struct D3dRaster +{ + void *texture; + void *palette; + void *lockedSurf; + uint32 format; + uint32 bpp; // bytes per pixel + bool hasAlpha; + bool customFormat; + bool autogenMipmap; +}; + +int32 getLevelSize(Raster *raster, int32 level); +void allocateDXT(Raster *raster, int32 dxt, int32 numLevels, bool32 hasAlpha); +void setPalette(Raster *raster, void *palette, int32 size); +void setTexels(Raster *raster, void *texels, int32 level); + +extern int32 nativeRasterOffset; +void registerNativeRaster(void); +#define GETD3DRASTEREXT(raster) PLUGINOFFSET(rw::d3d::D3dRaster, raster, rw::d3d::nativeRasterOffset) + +// Rendering + +void setRenderState(uint32 state, uint32 value); +void getRenderState(uint32 state, uint32 *value); +void setTextureStageState(uint32 stage, uint32 type, uint32 value); +void getTextureStageState(uint32 stage, uint32 type, uint32 *value); +void setSamplerState(uint32 stage, uint32 type, uint32 value); +void getSamplerState(uint32 stage, uint32 type, uint32 *value); +void flushCache(void); + +void setTexture(uint32 stage, Texture *tex); +void setMaterial(const RGBA &color, const SurfaceProperties &surfaceprops, float extraSurfProp = 0.0f); +inline void setMaterial(uint32 flags, const RGBA &color, const SurfaceProperties &surfaceprops, float extraSurfProp = 0.0f) +{ + static RGBA white = { 255, 255, 255, 255 }; + if(flags & Geometry::MODULATE) + setMaterial(color, surfaceprops, extraSurfProp); + else + setMaterial(white, surfaceprops, extraSurfProp); +} + +void setVertexShader(void *vs); +void setPixelShader(void *ps); +void setIndices(void *indexBuffer); +void setStreamSource(int n, void *buffer, uint32 offset, uint32 stride); +void setVertexDeclaration(void *declaration); + +void *createVertexShader(void *csosrc); +void *createPixelShader(void *csosrc); +void destroyVertexShader(void *shader); +void destroyPixelShader(void *shader); + + +/* + * Vertex shaders and common pipeline stuff + */ + +// This data will be available in vertex stream 2 +struct VertexConstantData +{ + V3d normal; + RGBA color; + TexCoords texCoors[8]; +}; +extern void *constantVertexStream; + +// TODO: figure out why this even still exists... +struct D3dShaderState +{ + // for VS + struct { + float32 start; + float32 end; + float32 range; // 1/(start-end) + float32 disable; // lower clamp + } fogData, fogDisable; + RGBA matColor; + SurfaceProperties surfProps; + float extraSurfProp; + float lightOffset[3]; + int32 numDir, numPoint, numSpot; + RGBAf ambient; + // for PS + RGBAf fogColor; + + bool fogDirty; +}; +extern D3dShaderState d3dShaderState; + +// Standard Vertex shader locations +enum +{ + VSLOC_combined = 0, + VSLOC_world = 4, + VSLOC_normal = 8, + VSLOC_matColor = 12, + VSLOC_surfProps = 13, + VSLOC_fogData = 14, + VSLOC_ambLight = 15, + VSLOC_lightOffset = 16, + VSLOC_lights = 17, + VSLOC_afterLights = VSLOC_lights + 8*3, + + VSLOC_numLights = 0, + + PSLOC_fogColor = 0 +}; + +// Vertex shader bits +enum +{ + // These should be low so they could be used as indices + VSLIGHT_DIRECT = 1, + VSLIGHT_POINT = 2, + VSLIGHT_SPOT = 4, + VSLIGHT_MASK = 7, // all the above + // less critical + VSLIGHT_AMBIENT = 8, +}; + +void lightingCB_Fix(Atomic *atomic); +int32 lightingCB_Shader(Atomic *atomic); +// for VS +void uploadMatrices(void); // no world transform +void uploadMatrices(Matrix *worldMat); +void setAmbient(const RGBAf &color); +void setNumLights(int numDir, int numPoint, int numSpot); +int32 uploadLights(WorldLights *lightData); // called by lightingCB_Shader + +extern void *im2dOverridePS; + +extern void *default_amb_VS; +extern void *default_amb_dir_VS; +extern void *default_all_VS; +extern void *default_PS; +extern void *default_tex_PS; +extern void *im2d_VS; +extern void *im2d_PS; +extern void *im2d_tex_PS; +void createDefaultShaders(void); +void destroyDefaultShaders(void); + + +} +} diff --git a/vendor/librw/src/d3d/rwd3d8.h b/vendor/librw/src/d3d/rwd3d8.h new file mode 100644 index 0000000..422379d --- /dev/null +++ b/vendor/librw/src/d3d/rwd3d8.h @@ -0,0 +1,74 @@ +namespace rw { +namespace d3d8 { + +void registerPlatformPlugins(void); + +struct InstanceData +{ + uint32 minVert; + int32 stride; + int32 numVertices; + int32 numIndices; + Material *material; + uint32 vertexShader; + uint32 primType; + void *indexBuffer; + void *vertexBuffer; + uint32 baseIndex; + uint8 vertexAlpha; + uint8 managed; + uint8 remapped; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + uint16 serialNumber; + uint16 numMeshes; + + InstanceData *inst; +}; + +uint32 makeFVFDeclaration(uint32 flags, int32 numTex); +int32 getStride(uint32 flags, int32 numTex); + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + void (*instanceCB)(Geometry *geo, InstanceData *header); + void (*uninstanceCB)(Geometry *geo, InstanceData *header); + void (*renderCB)(Atomic *atomic, InstanceDataHeader *header); +}; + +void defaultInstanceCB(Geometry *geo, InstanceData *header); +void defaultUninstanceCB(Geometry *geo, InstanceData *header); +void defaultRenderCB(Atomic *atomic, InstanceDataHeader *header); + +ObjPipeline *makeDefaultPipeline(void); + +// Skin plugin + +void initSkin(void); +ObjPipeline *makeSkinPipeline(void); + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Native Texture and Raster + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +} +} diff --git a/vendor/librw/src/d3d/rwd3d9.h b/vendor/librw/src/d3d/rwd3d9.h new file mode 100644 index 0000000..03eccd5 --- /dev/null +++ b/vendor/librw/src/d3d/rwd3d9.h @@ -0,0 +1,112 @@ +namespace rw { +namespace d3d9 { + +void registerPlatformPlugins(void); + +struct VertexElement +{ + uint16 stream; + uint16 offset; + uint8 type; + uint8 method; + uint8 usage; + uint8 usageIndex; +}; + +struct VertexStream +{ + void *vertexBuffer; + uint32 offset; + uint32 stride; + uint16 geometryFlags; + uint8 managed; + uint8 dynamicLock; +}; + +struct InstanceData +{ + uint32 numIndex; + uint32 minVert; + Material *material; + bool32 vertexAlpha; + void *vertexShader; + uint32 baseIndex; + uint32 numVertices; + uint32 startIndex; + uint32 numPrimitives; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + uint32 serialNumber; + uint32 numMeshes; + void *indexBuffer; + uint32 primType; + VertexStream vertexStream[2]; + bool32 useOffsets; + void *vertexDeclaration; + uint32 totalNumIndex; + uint32 totalNumVertex; + + InstanceData *inst; +}; + +void *createVertexDeclaration(VertexElement *elements); +void destroyVertexDeclaration(void *delaration); +uint32 getDeclaration(void *declaration, VertexElement *elements); + +void drawInst_simple(d3d9::InstanceDataHeader *header, d3d9::InstanceData *inst); +// Emulate PS2 GS alpha test FB_ONLY case: failed alpha writes to frame- but not to depth buffer +void drawInst_GSemu(d3d9::InstanceDataHeader *header, InstanceData *inst); +// This one switches between the above two depending on render state; +void drawInst(d3d9::InstanceDataHeader *header, d3d9::InstanceData *inst); + + + + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + void (*instanceCB)(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); + void (*uninstanceCB)(Geometry *geo, InstanceDataHeader *header); + void (*renderCB)(Atomic *atomic, InstanceDataHeader *header); +}; + +void defaultInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); +void defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header); +void defaultRenderCB_Fix(Atomic *atomic, InstanceDataHeader *header); +void defaultRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header); + +ObjPipeline *makeDefaultPipeline(void); + + +// Skin plugin + +void initSkin(void); +void uploadSkinMatrices(Atomic *atomic); +void skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); +void skinRenderCB(Atomic *atomic, InstanceDataHeader *header); +ObjPipeline *makeSkinPipeline(void); + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Native Texture and Raster + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +} +} diff --git a/vendor/librw/src/d3d/rwd3dimpl.h b/vendor/librw/src/d3d/rwd3dimpl.h new file mode 100644 index 0000000..82f8a13 --- /dev/null +++ b/vendor/librw/src/d3d/rwd3dimpl.h @@ -0,0 +1,81 @@ +namespace rw { +namespace d3d { + +#ifdef RW_D3D9 +void openIm2D(void); +void closeIm2D(void); +void im2DRenderLine(void *vertices, int32 numVertices, int32 vert1, int32 vert2); +void im2DRenderTriangle(void *vertices, int32 numVertices, int32 vert1, int32 vert2, int32 vert3); +void im2DRenderPrimitive(PrimitiveType primType, void *vertices, int32 numVertices); +void im2DRenderIndexedPrimitive(PrimitiveType primType, void *vertices, int32 numVertices, void *indices, int32 numIndices); + +void openIm3D(void); +void closeIm3D(void); +void im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags); +void im3DRenderPrimitive(PrimitiveType primType); +void im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices); +void im3DEnd(void); + + +struct DisplayMode +{ + D3DDISPLAYMODE mode; + uint32 flags; +}; + +struct D3d9Globals +{ + HWND window; + + IDirect3D9 *d3d9; + int numAdapters; + int adapter; + D3DCAPS9 caps; + DisplayMode *modes; + int numModes; + int currentMode; + DisplayMode startMode; + + uint32 msLevel; + + D3DPRESENT_PARAMETERS present; + + IDirect3DSurface9 *defaultRenderTarget; + IDirect3DSurface9 *defaultDepthSurf; + + int numTextures; + int numVertexShaders; + int numPixelShaders; + int numVertexBuffers; + int numIndexBuffers; + int numVertexDeclarations; +}; + +extern D3d9Globals d3d9Globals; + +void addVidmemRaster(Raster *raster); +void removeVidmemRaster(Raster *raster); + +void addDynamicVB(uint32 length, uint32 fvf, IDirect3DVertexBuffer9 **buf); // NB: don't share this pointer +void removeDynamicVB(IDirect3DVertexBuffer9 **buf); + +void addDynamicIB(uint32 length, IDirect3DIndexBuffer9 **buf); // NB: don't share this pointer +void removeDynamicIB(IDirect3DIndexBuffer9 **buf); + + +int findFormatDepth(uint32 format); +void evictD3D9Raster(Raster *raster); + +#endif + +Raster *rasterCreate(Raster *raster); +uint8 *rasterLock(Raster *raster, int32 level, int32 lockMode); +void rasterUnlock(Raster *raster, int32 level); +int32 rasterNumLevels(Raster *raster); +bool32 imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); +bool32 rasterFromImage(Raster *raster, Image *image); +Image *rasterToImage(Raster *raster); + +} +} diff --git a/vendor/librw/src/d3d/rwxbox.h b/vendor/librw/src/d3d/rwxbox.h new file mode 100644 index 0000000..1abebe1 --- /dev/null +++ b/vendor/librw/src/d3d/rwxbox.h @@ -0,0 +1,195 @@ +namespace rw { +namespace xbox { + +void registerPlatformPlugins(void); + +extern int v3dFormatMap[6]; +extern int v2dFormatMap[6]; + +struct InstanceData +{ + uint32 minVert; + int32 numVertices; + int32 numIndices; + void *indexBuffer; + Material *material; + uint32 vertexShader; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + int32 size; + uint16 serialNumber; + uint16 numMeshes; + uint32 primType; + int32 numVertices; + int32 stride; + void *vertexBuffer; + bool32 vertexAlpha; + InstanceData *begin; + InstanceData *end; + + uint8 *data; +}; + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + void (*instanceCB)(Geometry *geo, InstanceDataHeader *header); + void (*uninstanceCB)(Geometry *geo, InstanceDataHeader *header); +}; + +ObjPipeline *makeDefaultPipeline(void); + +void defaultInstanceCB(Geometry *geo, InstanceDataHeader *header); +void defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header); + +// Skin plugin + +Stream *readNativeSkin(Stream *stream, int32, void *object, int32 offset); +Stream *writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset); +int32 getSizeNativeSkin(void *object, int32 offset); + +void initSkin(void); +ObjPipeline *makeSkinPipeline(void); + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Vertex Format plugin + +extern uint32 vertexFormatSizes[6]; + +uint32 *getVertexFmt(Geometry *g); +uint32 makeVertexFmt(int32 flags, uint32 numTexSets); +uint32 getVertexFmtStride(uint32 fmt); + +void registerVertexFormatPlugin(void); + +// Native Texture and Raster + +struct XboxRaster +{ + void *texture; + void *palette; + uint32 format; + uint32 bpp; // bytes per pixel + bool hasAlpha; + bool customFormat; + bool32 unknownFlag; +}; + +int32 getLevelSize(Raster *raster, int32 level); + +extern int32 nativeRasterOffset; +void registerNativeRaster(void); +#define GETXBOXRASTEREXT(raster) PLUGINOFFSET(rw::xbox::XboxRaster, raster, rw::xbox::nativeRasterOffset) + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +enum { + D3DFMT_UNKNOWN = 0xFFFFFFFF, + + /* Swizzled formats */ + + D3DFMT_A8R8G8B8 = 0x00000006, + D3DFMT_X8R8G8B8 = 0x00000007, + D3DFMT_R5G6B5 = 0x00000005, + D3DFMT_R6G5B5 = 0x00000027, + D3DFMT_X1R5G5B5 = 0x00000003, + D3DFMT_A1R5G5B5 = 0x00000002, + D3DFMT_A4R4G4B4 = 0x00000004, + D3DFMT_A8 = 0x00000019, + D3DFMT_A8B8G8R8 = 0x0000003A, + D3DFMT_B8G8R8A8 = 0x0000003B, + D3DFMT_R4G4B4A4 = 0x00000039, + D3DFMT_R5G5B5A1 = 0x00000038, + D3DFMT_R8G8B8A8 = 0x0000003C, + D3DFMT_R8B8 = 0x00000029, + D3DFMT_G8B8 = 0x00000028, + + D3DFMT_P8 = 0x0000000B, + + D3DFMT_L8 = 0x00000000, + D3DFMT_A8L8 = 0x0000001A, + D3DFMT_AL8 = 0x00000001, + D3DFMT_L16 = 0x00000032, + + D3DFMT_V8U8 = 0x00000028, + D3DFMT_L6V5U5 = 0x00000027, + D3DFMT_X8L8V8U8 = 0x00000007, + D3DFMT_Q8W8V8U8 = 0x0000003A, + D3DFMT_V16U16 = 0x00000033, + + D3DFMT_D16_LOCKABLE = 0x0000002C, + D3DFMT_D16 = 0x0000002C, + D3DFMT_D24S8 = 0x0000002A, + D3DFMT_F16 = 0x0000002D, + D3DFMT_F24S8 = 0x0000002B, + + /* YUV formats */ + + D3DFMT_YUY2 = 0x00000024, + D3DFMT_UYVY = 0x00000025, + + /* Compressed formats */ + + D3DFMT_DXT1 = 0x0000000C, + D3DFMT_DXT2 = 0x0000000E, + D3DFMT_DXT3 = 0x0000000E, + D3DFMT_DXT4 = 0x0000000F, + D3DFMT_DXT5 = 0x0000000F, + + /* Linear formats */ + + D3DFMT_LIN_A1R5G5B5 = 0x00000010, + D3DFMT_LIN_A4R4G4B4 = 0x0000001D, + D3DFMT_LIN_A8 = 0x0000001F, + D3DFMT_LIN_A8B8G8R8 = 0x0000003F, + D3DFMT_LIN_A8R8G8B8 = 0x00000012, + D3DFMT_LIN_B8G8R8A8 = 0x00000040, + D3DFMT_LIN_G8B8 = 0x00000017, + D3DFMT_LIN_R4G4B4A4 = 0x0000003E, + D3DFMT_LIN_R5G5B5A1 = 0x0000003D, + D3DFMT_LIN_R5G6B5 = 0x00000011, + D3DFMT_LIN_R6G5B5 = 0x00000037, + D3DFMT_LIN_R8B8 = 0x00000016, + D3DFMT_LIN_R8G8B8A8 = 0x00000041, + D3DFMT_LIN_X1R5G5B5 = 0x0000001C, + D3DFMT_LIN_X8R8G8B8 = 0x0000001E, + + D3DFMT_LIN_A8L8 = 0x00000020, + D3DFMT_LIN_AL8 = 0x0000001B, + D3DFMT_LIN_L16 = 0x00000035, + D3DFMT_LIN_L8 = 0x00000013, + + D3DFMT_LIN_V16U16 = 0x00000036, + D3DFMT_LIN_V8U8 = 0x00000017, + D3DFMT_LIN_L6V5U5 = 0x00000037, + D3DFMT_LIN_X8L8V8U8 = 0x0000001E, + D3DFMT_LIN_Q8W8V8U8 = 0x00000012, + + D3DFMT_LIN_D24S8 = 0x0000002E, + D3DFMT_LIN_F24S8 = 0x0000002F, + D3DFMT_LIN_D16 = 0x00000030, + D3DFMT_LIN_F16 = 0x00000031, + + D3DFMT_VERTEXDATA = 100, + D3DFMT_INDEX16 = 101 +}; + +} +} diff --git a/vendor/librw/src/d3d/rwxboximpl.h b/vendor/librw/src/d3d/rwxboximpl.h new file mode 100644 index 0000000..524ec28 --- /dev/null +++ b/vendor/librw/src/d3d/rwxboximpl.h @@ -0,0 +1,11 @@ +namespace rw { +namespace xbox { + +Raster *rasterCreate(Raster *raster); +uint8 *rasterLock(Raster *raster, int32 level, int32 lockMode); +void rasterUnlock(Raster*, int32); +int32 rasterNumLevels(Raster *raster); +Image *rasterToImage(Raster *raster); + +} +} diff --git a/vendor/librw/src/d3d/shaders/default_PS.h b/vendor/librw/src/d3d/shaders/default_PS.h new file mode 100644 index 0000000..9f2cb31 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/default_PS.h @@ -0,0 +1,72 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /Fh default_PS.h default_PS.hlsl +// +// +// Parameters: +// +// float4 fogColor; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// + + ps_2_0 + dcl t0.xyz + dcl v0 + add r0.xyz, v0, -c0 + mad r0.xyz, t0.z, r0, c0 + mov r0.w, v0.w + mov oC0, r0 + +// approximately 4 instruction slots used +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 34, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 83, 0, + 0, 0, 0, 2, 255, 255, + 1, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 76, 0, 0, 0, 48, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 2, 0, 60, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 67, 111, 108, + 111, 114, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 115, + 95, 50, 95, 48, 0, 77, + 105, 99, 114, 111, 115, 111, + 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 57, 46, + 50, 57, 46, 57, 53, 50, + 46, 51, 49, 49, 49, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 7, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 2, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 228, 144, + 0, 0, 228, 161, 4, 0, + 0, 4, 0, 0, 7, 128, + 0, 0, 170, 176, 0, 0, + 228, 128, 0, 0, 228, 160, + 1, 0, 0, 2, 0, 0, + 8, 128, 0, 0, 255, 144, + 1, 0, 0, 2, 0, 8, + 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/default_PS.hlsl b/vendor/librw/src/d3d/shaders/default_PS.hlsl new file mode 100644 index 0000000..eaf279d --- /dev/null +++ b/vendor/librw/src/d3d/shaders/default_PS.hlsl @@ -0,0 +1,19 @@ +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float4 Color : COLOR0; +}; + +sampler2D tex0 : register(s0); + +float4 fogColor : register(c0); + +float4 main(VS_out input) : COLOR +{ + float4 color = input.Color; +#ifdef TEX + color *= tex2D(tex0, input.TexCoord0.xy); +#endif + color.rgb = lerp(fogColor.rgb, color.rgb, input.TexCoord0.z); + return color; +} diff --git a/vendor/librw/src/d3d/shaders/default_VS.hlsl b/vendor/librw/src/d3d/shaders/default_VS.hlsl new file mode 100644 index 0000000..d2f5452 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/default_VS.hlsl @@ -0,0 +1,51 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float4 Color : COLOR0; +}; + + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + float3 Normal = mul(normalMat, input.Normal); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +#endif +#ifdef POINTLIGHTS + for(i = 0; i < numPointLights; i++) + output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +#endif +#ifdef SPOTLIGHTS + for(i = 0; i < numSpotLights; i++) + output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +#endif + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/vendor/librw/src/d3d/shaders/default_all_VS.h b/vendor/librw/src/d3d/shaders/default_all_VS.h new file mode 100644 index 0000000..e6afa5d --- /dev/null +++ b/vendor/librw/src/d3d/shaders/default_all_VS.h @@ -0,0 +1,491 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh +// default_all_VS.h default_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// int numPointLights; +// int numSpotLights; +// float4 surfProps; +// float4x4 worldMat; +// +// +// Registers: +// +// Name Reg Size +// -------------- ----- ---- +// numDirLights i0 1 +// numPointLights i1 1 +// numSpotLights i2 1 +// combinedMat c0 4 +// worldMat c4 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// + + vs_2_0 + def c11, 0, 3, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + mov oPos, r0 + mul r0.xyz, v0.y, c5 + mad r0.xyz, c4, v0.x, r0 + mad r0.xyz, c6, v0.z, r0 + mad r0.xyz, c7, v0.w, r0 + mul r1.xyz, v1.y, c9 + mad r1.xyz, c8, v1.x, r1 + mad r1.xyz, c10, v1.z, r1 + mov r2.x, c13.x + mad r2.xyz, c15, r2.x, v3 + mov r3.xyz, r2 + mov r1.w, c11.x + rep i0 + add r2.w, r1.w, c16.x + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + dp3 r2.w, r1, -c19[a0.x] + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, c17[a0.x] + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c11.z + endrep + mov r2.xyz, r3 + mov r1.w, c11.x + rep i1 + add r2.w, r1.w, c16.y + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + add r4.xyz, r0, -c18[a0.x] + dp3 r2.w, r4, r4 + rsq r2.w, r2.w + mul r4.xyz, r2.w, r4 + dp3 r3.w, r1, -r4 + max r3.w, r3.w, c11.x + mul r4.xyz, r3.w, c17[a0.x] + rcp r2.w, r2.w + rcp r3.w, c17[a0.x].w + mad r2.w, r2.w, -r3.w, c11.z + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, r4 + mad r2.xyz, r4, c13.z, r2 + add r1.w, r1.w, c11.z + endrep + mov r3.xyz, r2 + mov r1.w, c11.x + rep i2 + add r2.w, r1.w, c16.z + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + add r4.xyz, r0, -c18[a0.x] + dp3 r2.w, r4, r4 + rsq r2.w, r2.w + mul r4.xyz, r2.w, r4 + dp3 r4.w, r1, -r4 + dp3 r4.x, r4, c19[a0.x] + max r4.y, r4.w, c11.x + mov r4.z, c11.z + add r4.xz, r4, c18[a0.x].w + rcp r4.z, r4.z + mul r4.x, r4.z, r4.x + slt r4.z, r4.x, c11.x + mad r4.y, r4.z, -r4.y, r4.y + max r4.x, r4.x, c19[a0.x].w + mul r4.x, r4.x, r4.y + mul r4.xyz, r4.x, c17[a0.x] + rcp r2.w, r2.w + rcp r4.w, c17[a0.x].w + mad r2.w, r2.w, -r4.w, c11.z + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, r4 + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c11.z + endrep + mov r3.w, v3.w + max r1, r3, c11.x + min r1, r1, c11.z + mul oD0, r1, c12 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c11.z + mov oT0.xy, v2 + +// approximately 95 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 158, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 67, 2, + 0, 0, 0, 2, 254, 255, + 12, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 60, 2, 0, 0, 12, 1, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 28, 1, + 0, 0, 0, 0, 0, 0, + 44, 1, 0, 0, 2, 0, + 0, 0, 4, 0, 2, 0, + 56, 1, 0, 0, 0, 0, + 0, 0, 72, 1, 0, 0, + 2, 0, 16, 0, 1, 0, + 66, 0, 84, 1, 0, 0, + 0, 0, 0, 0, 100, 1, + 0, 0, 2, 0, 14, 0, + 1, 0, 58, 0, 28, 1, + 0, 0, 0, 0, 0, 0, + 108, 1, 0, 0, 2, 0, + 17, 0, 24, 0, 70, 0, + 184, 1, 0, 0, 0, 0, + 0, 0, 200, 1, 0, 0, + 2, 0, 12, 0, 1, 0, + 50, 0, 28, 1, 0, 0, + 0, 0, 0, 0, 207, 1, + 0, 0, 2, 0, 8, 0, + 3, 0, 34, 0, 220, 1, + 0, 0, 0, 0, 0, 0, + 236, 1, 0, 0, 1, 0, + 0, 0, 1, 0, 2, 0, + 252, 1, 0, 0, 0, 0, + 0, 0, 12, 2, 0, 0, + 1, 0, 1, 0, 1, 0, + 6, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 27, 2, + 0, 0, 1, 0, 2, 0, + 1, 0, 10, 0, 252, 1, + 0, 0, 0, 0, 0, 0, + 41, 2, 0, 0, 2, 0, + 13, 0, 1, 0, 54, 0, + 28, 1, 0, 0, 0, 0, + 0, 0, 51, 2, 0, 0, + 2, 0, 4, 0, 4, 0, + 18, 0, 56, 1, 0, 0, + 0, 0, 0, 0, 97, 109, + 98, 105, 101, 110, 116, 76, + 105, 103, 104, 116, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 99, 111, 109, 98, 105, 110, + 101, 100, 77, 97, 116, 0, + 3, 0, 3, 0, 4, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 105, + 114, 115, 116, 76, 105, 103, + 104, 116, 0, 171, 1, 0, + 2, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 68, + 97, 116, 97, 0, 108, 105, + 103, 104, 116, 115, 0, 99, + 111, 108, 111, 114, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 111, 115, 105, 116, 105, + 111, 110, 0, 100, 105, 114, + 101, 99, 116, 105, 111, 110, + 0, 171, 115, 1, 0, 0, + 124, 1, 0, 0, 140, 1, + 0, 0, 124, 1, 0, 0, + 149, 1, 0, 0, 124, 1, + 0, 0, 5, 0, 0, 0, + 1, 0, 12, 0, 8, 0, + 3, 0, 160, 1, 0, 0, + 109, 97, 116, 67, 111, 108, + 0, 110, 111, 114, 109, 97, + 108, 77, 97, 116, 0, 171, + 171, 171, 3, 0, 3, 0, + 3, 0, 3, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 110, 117, 109, 68, 105, 114, + 76, 105, 103, 104, 116, 115, + 0, 171, 171, 171, 0, 0, + 2, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 110, 117, 109, 80, + 111, 105, 110, 116, 76, 105, + 103, 104, 116, 115, 0, 110, + 117, 109, 83, 112, 111, 116, + 76, 105, 103, 104, 116, 115, + 0, 115, 117, 114, 102, 80, + 114, 111, 112, 115, 0, 119, + 111, 114, 108, 100, 77, 97, + 116, 0, 118, 115, 95, 50, + 95, 48, 0, 77, 105, 99, + 114, 111, 115, 111, 102, 116, + 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, + 114, 32, 57, 46, 50, 57, + 46, 57, 53, 50, 46, 51, + 49, 49, 49, 0, 81, 0, + 0, 5, 11, 0, 15, 160, + 0, 0, 0, 0, 0, 0, + 64, 64, 0, 0, 128, 63, + 0, 0, 0, 0, 31, 0, + 0, 2, 0, 0, 0, 128, + 0, 0, 15, 144, 31, 0, + 0, 2, 3, 0, 0, 128, + 1, 0, 15, 144, 31, 0, + 0, 2, 5, 0, 0, 128, + 2, 0, 15, 144, 31, 0, + 0, 2, 10, 0, 0, 128, + 3, 0, 15, 144, 5, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 85, 144, 1, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 3, 0, 228, 160, + 0, 0, 255, 144, 0, 0, + 228, 128, 1, 0, 0, 2, + 0, 0, 15, 192, 0, 0, + 228, 128, 5, 0, 0, 3, + 0, 0, 7, 128, 0, 0, + 85, 144, 5, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 4, 0, 228, 160, + 0, 0, 0, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 7, 128, 6, 0, + 228, 160, 0, 0, 170, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 7, 128, + 7, 0, 228, 160, 0, 0, + 255, 144, 0, 0, 228, 128, + 5, 0, 0, 3, 1, 0, + 7, 128, 1, 0, 85, 144, + 9, 0, 228, 160, 4, 0, + 0, 4, 1, 0, 7, 128, + 8, 0, 228, 160, 1, 0, + 0, 144, 1, 0, 228, 128, + 4, 0, 0, 4, 1, 0, + 7, 128, 10, 0, 228, 160, + 1, 0, 170, 144, 1, 0, + 228, 128, 1, 0, 0, 2, + 2, 0, 1, 128, 13, 0, + 0, 160, 4, 0, 0, 4, + 2, 0, 7, 128, 15, 0, + 228, 160, 2, 0, 0, 128, + 3, 0, 228, 144, 1, 0, + 0, 2, 3, 0, 7, 128, + 2, 0, 228, 128, 1, 0, + 0, 2, 1, 0, 8, 128, + 11, 0, 0, 160, 38, 0, + 0, 1, 0, 0, 228, 240, + 2, 0, 0, 3, 2, 0, + 8, 128, 1, 0, 255, 128, + 16, 0, 0, 160, 5, 0, + 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 11, 0, + 85, 160, 46, 0, 0, 2, + 0, 0, 1, 176, 2, 0, + 255, 128, 8, 0, 0, 4, + 2, 0, 8, 128, 1, 0, + 228, 128, 19, 32, 228, 161, + 0, 0, 0, 176, 11, 0, + 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 11, 0, + 0, 160, 5, 0, 0, 4, + 4, 0, 7, 128, 2, 0, + 255, 128, 17, 32, 228, 160, + 0, 0, 0, 176, 4, 0, + 0, 4, 3, 0, 7, 128, + 4, 0, 228, 128, 13, 0, + 170, 160, 3, 0, 228, 128, + 2, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 2, 0, 7, 128, 3, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 8, 128, 11, 0, + 0, 160, 38, 0, 0, 1, + 1, 0, 228, 240, 2, 0, + 0, 3, 2, 0, 8, 128, + 1, 0, 255, 128, 16, 0, + 85, 160, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 85, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 2, 0, 255, 128, + 2, 0, 0, 4, 4, 0, + 7, 128, 0, 0, 228, 128, + 18, 32, 228, 161, 0, 0, + 0, 176, 8, 0, 0, 3, + 2, 0, 8, 128, 4, 0, + 228, 128, 4, 0, 228, 128, + 7, 0, 0, 2, 2, 0, + 8, 128, 2, 0, 255, 128, + 5, 0, 0, 3, 4, 0, + 7, 128, 2, 0, 255, 128, + 4, 0, 228, 128, 8, 0, + 0, 3, 3, 0, 8, 128, + 1, 0, 228, 128, 4, 0, + 228, 129, 11, 0, 0, 3, + 3, 0, 8, 128, 3, 0, + 255, 128, 11, 0, 0, 160, + 5, 0, 0, 4, 4, 0, + 7, 128, 3, 0, 255, 128, + 17, 32, 228, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 2, 0, 8, 128, 2, 0, + 255, 128, 6, 0, 0, 3, + 3, 0, 8, 128, 17, 32, + 255, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 2, 0, + 8, 128, 2, 0, 255, 128, + 3, 0, 255, 129, 11, 0, + 170, 160, 11, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 0, 160, + 5, 0, 0, 3, 4, 0, + 7, 128, 2, 0, 255, 128, + 4, 0, 228, 128, 4, 0, + 0, 4, 2, 0, 7, 128, + 4, 0, 228, 128, 13, 0, + 170, 160, 2, 0, 228, 128, + 2, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 3, 0, 7, 128, 2, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 8, 128, 11, 0, + 0, 160, 38, 0, 0, 1, + 2, 0, 228, 240, 2, 0, + 0, 3, 2, 0, 8, 128, + 1, 0, 255, 128, 16, 0, + 170, 160, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 85, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 2, 0, 255, 128, + 2, 0, 0, 4, 4, 0, + 7, 128, 0, 0, 228, 128, + 18, 32, 228, 161, 0, 0, + 0, 176, 8, 0, 0, 3, + 2, 0, 8, 128, 4, 0, + 228, 128, 4, 0, 228, 128, + 7, 0, 0, 2, 2, 0, + 8, 128, 2, 0, 255, 128, + 5, 0, 0, 3, 4, 0, + 7, 128, 2, 0, 255, 128, + 4, 0, 228, 128, 8, 0, + 0, 3, 4, 0, 8, 128, + 1, 0, 228, 128, 4, 0, + 228, 129, 8, 0, 0, 4, + 4, 0, 1, 128, 4, 0, + 228, 128, 19, 32, 228, 160, + 0, 0, 0, 176, 11, 0, + 0, 3, 4, 0, 2, 128, + 4, 0, 255, 128, 11, 0, + 0, 160, 1, 0, 0, 2, + 4, 0, 4, 128, 11, 0, + 170, 160, 2, 0, 0, 4, + 4, 0, 5, 128, 4, 0, + 228, 128, 18, 32, 255, 160, + 0, 0, 0, 176, 6, 0, + 0, 2, 4, 0, 4, 128, + 4, 0, 170, 128, 5, 0, + 0, 3, 4, 0, 1, 128, + 4, 0, 170, 128, 4, 0, + 0, 128, 12, 0, 0, 3, + 4, 0, 4, 128, 4, 0, + 0, 128, 11, 0, 0, 160, + 4, 0, 0, 4, 4, 0, + 2, 128, 4, 0, 170, 128, + 4, 0, 85, 129, 4, 0, + 85, 128, 11, 0, 0, 4, + 4, 0, 1, 128, 4, 0, + 0, 128, 19, 32, 255, 160, + 0, 0, 0, 176, 5, 0, + 0, 3, 4, 0, 1, 128, + 4, 0, 0, 128, 4, 0, + 85, 128, 5, 0, 0, 4, + 4, 0, 7, 128, 4, 0, + 0, 128, 17, 32, 228, 160, + 0, 0, 0, 176, 6, 0, + 0, 2, 2, 0, 8, 128, + 2, 0, 255, 128, 6, 0, + 0, 3, 4, 0, 8, 128, + 17, 32, 255, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 2, 0, 8, 128, 2, 0, + 255, 128, 4, 0, 255, 129, + 11, 0, 170, 160, 11, 0, + 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 11, 0, + 0, 160, 5, 0, 0, 3, + 4, 0, 7, 128, 2, 0, + 255, 128, 4, 0, 228, 128, + 4, 0, 0, 4, 3, 0, + 7, 128, 4, 0, 228, 128, + 13, 0, 170, 160, 3, 0, + 228, 128, 2, 0, 0, 3, + 1, 0, 8, 128, 1, 0, + 255, 128, 11, 0, 170, 160, + 39, 0, 0, 0, 1, 0, + 0, 2, 3, 0, 8, 128, + 3, 0, 255, 144, 11, 0, + 0, 3, 1, 0, 15, 128, + 3, 0, 228, 128, 11, 0, + 0, 160, 10, 0, 0, 3, + 1, 0, 15, 128, 1, 0, + 228, 128, 11, 0, 170, 160, + 5, 0, 0, 3, 0, 0, + 15, 208, 1, 0, 228, 128, + 12, 0, 228, 160, 2, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 11, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 2, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/default_amb_VS.h b/vendor/librw/src/d3d/shaders/default_amb_VS.h new file mode 100644 index 0000000..6c22e23 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/default_amb_VS.h @@ -0,0 +1,156 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /Fh default_amb_VS.h default_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x4 combinedMat; +// float4 fogData; +// float4 matCol; +// float4 surfProps; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// combinedMat c0 4 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// + + vs_2_0 + def c4, 0, 1, 0, 0 + dcl_position v0 + dcl_texcoord v1 + dcl_color v2 + mov r0.xyz, c15 + mad r0.xyz, r0, c13.x, v2 + mov r0.w, v2.w + max r0, r0, c4.x + min r0, r0, c4.y + mul oD0, r0, c12 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + add r1.x, r0.w, -c14.y + mov oPos, r0 + mul r0.x, r1.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.y + mov oT0.xy, v1 + +// approximately 16 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 69, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 220, 0, + 0, 0, 0, 2, 254, 255, + 5, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 213, 0, 0, 0, 128, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 144, 0, + 0, 0, 0, 0, 0, 0, + 160, 0, 0, 0, 2, 0, + 0, 0, 4, 0, 2, 0, + 172, 0, 0, 0, 0, 0, + 0, 0, 188, 0, 0, 0, + 2, 0, 14, 0, 1, 0, + 58, 0, 144, 0, 0, 0, + 0, 0, 0, 0, 196, 0, + 0, 0, 2, 0, 12, 0, + 1, 0, 50, 0, 144, 0, + 0, 0, 0, 0, 0, 0, + 203, 0, 0, 0, 2, 0, + 13, 0, 1, 0, 54, 0, + 144, 0, 0, 0, 0, 0, + 0, 0, 97, 109, 98, 105, + 101, 110, 116, 76, 105, 103, + 104, 116, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 99, 111, + 109, 98, 105, 110, 101, 100, + 77, 97, 116, 0, 3, 0, + 3, 0, 4, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 68, + 97, 116, 97, 0, 109, 97, + 116, 67, 111, 108, 0, 115, + 117, 114, 102, 80, 114, 111, + 112, 115, 0, 118, 115, 95, + 50, 95, 48, 0, 77, 105, + 99, 114, 111, 115, 111, 102, + 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, + 67, 111, 109, 112, 105, 108, + 101, 114, 32, 57, 46, 50, + 57, 46, 57, 53, 50, 46, + 51, 49, 49, 49, 0, 171, + 171, 171, 81, 0, 0, 5, + 4, 0, 15, 160, 0, 0, + 0, 0, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 2, 0, + 15, 144, 1, 0, 0, 2, + 0, 0, 7, 128, 15, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 7, 128, 0, 0, + 228, 128, 13, 0, 0, 160, + 2, 0, 228, 144, 1, 0, + 0, 2, 0, 0, 8, 128, + 2, 0, 255, 144, 11, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 4, 0, + 0, 160, 10, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 4, 0, 85, 160, + 5, 0, 0, 3, 0, 0, + 15, 208, 0, 0, 228, 128, + 12, 0, 228, 160, 5, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 85, 144, 1, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 3, 0, 228, 160, + 0, 0, 255, 144, 0, 0, + 228, 128, 2, 0, 0, 3, + 1, 0, 1, 128, 0, 0, + 255, 128, 14, 0, 85, 161, + 1, 0, 0, 2, 0, 0, + 15, 192, 0, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 1, 128, 1, 0, 0, 128, + 14, 0, 170, 160, 11, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 0, 128, 14, 0, + 255, 160, 10, 0, 0, 3, + 0, 0, 4, 224, 0, 0, + 0, 128, 4, 0, 85, 160, + 1, 0, 0, 2, 0, 0, + 3, 224, 1, 0, 228, 144, + 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/default_amb_dir_VS.h b/vendor/librw/src/d3d/shaders/default_amb_dir_VS.h new file mode 100644 index 0000000..768042f --- /dev/null +++ b/vendor/librw/src/d3d/shaders/default_amb_dir_VS.h @@ -0,0 +1,272 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /Fh default_amb_dir_VS.h +// default_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// float4 surfProps; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// numDirLights i0 1 +// combinedMat c0 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// + + vs_2_0 + def c4, 0, 3, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + mov oPos, r0 + mul r0.xyz, v1.y, c9 + mad r0.xyz, c8, v1.x, r0 + mad r0.xyz, c10, v1.z, r0 + mov r1.x, c13.x + mad r1.xyz, c15, r1.x, v3 + mov r2.xyz, r1 + mov r1.w, c4.x + rep i0 + add r3.x, r1.w, c16.x + mul r3.x, r3.x, c4.y + mova a0.x, r3.x + dp3 r3.x, r0, -c19[a0.x] + max r3.x, r3.x, c4.x + mul r3.xyz, r3.x, c17[a0.x] + mad r2.xyz, r3, c13.z, r2 + add r1.w, r1.w, c4.z + endrep + mov r2.w, v3.w + max r1, r2, c4.x + min r1, r1, c4.z + mul oD0, r1, c12 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.z + mov oT0.xy, v2 + +// approximately 34 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 134, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 225, 1, + 0, 0, 0, 2, 254, 255, + 9, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 218, 1, 0, 0, 208, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 224, 0, + 0, 0, 0, 0, 0, 0, + 240, 0, 0, 0, 2, 0, + 0, 0, 4, 0, 2, 0, + 252, 0, 0, 0, 0, 0, + 0, 0, 12, 1, 0, 0, + 2, 0, 16, 0, 1, 0, + 66, 0, 24, 1, 0, 0, + 0, 0, 0, 0, 40, 1, + 0, 0, 2, 0, 14, 0, + 1, 0, 58, 0, 224, 0, + 0, 0, 0, 0, 0, 0, + 48, 1, 0, 0, 2, 0, + 17, 0, 24, 0, 70, 0, + 124, 1, 0, 0, 0, 0, + 0, 0, 140, 1, 0, 0, + 2, 0, 12, 0, 1, 0, + 50, 0, 224, 0, 0, 0, + 0, 0, 0, 0, 147, 1, + 0, 0, 2, 0, 8, 0, + 3, 0, 34, 0, 160, 1, + 0, 0, 0, 0, 0, 0, + 176, 1, 0, 0, 1, 0, + 0, 0, 1, 0, 2, 0, + 192, 1, 0, 0, 0, 0, + 0, 0, 208, 1, 0, 0, + 2, 0, 13, 0, 1, 0, + 54, 0, 224, 0, 0, 0, + 0, 0, 0, 0, 97, 109, + 98, 105, 101, 110, 116, 76, + 105, 103, 104, 116, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 99, 111, 109, 98, 105, 110, + 101, 100, 77, 97, 116, 0, + 3, 0, 3, 0, 4, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 105, + 114, 115, 116, 76, 105, 103, + 104, 116, 0, 171, 1, 0, + 2, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 68, + 97, 116, 97, 0, 108, 105, + 103, 104, 116, 115, 0, 99, + 111, 108, 111, 114, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 111, 115, 105, 116, 105, + 111, 110, 0, 100, 105, 114, + 101, 99, 116, 105, 111, 110, + 0, 171, 55, 1, 0, 0, + 64, 1, 0, 0, 80, 1, + 0, 0, 64, 1, 0, 0, + 89, 1, 0, 0, 64, 1, + 0, 0, 5, 0, 0, 0, + 1, 0, 12, 0, 8, 0, + 3, 0, 100, 1, 0, 0, + 109, 97, 116, 67, 111, 108, + 0, 110, 111, 114, 109, 97, + 108, 77, 97, 116, 0, 171, + 171, 171, 3, 0, 3, 0, + 3, 0, 3, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 110, 117, 109, 68, 105, 114, + 76, 105, 103, 104, 116, 115, + 0, 171, 171, 171, 0, 0, + 2, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 115, 117, 114, 102, + 80, 114, 111, 112, 115, 0, + 118, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 57, 46, 57, + 53, 50, 46, 51, 49, 49, + 49, 0, 171, 171, 81, 0, + 0, 5, 4, 0, 15, 160, + 0, 0, 0, 0, 0, 0, + 64, 64, 0, 0, 128, 63, + 0, 0, 0, 0, 31, 0, + 0, 2, 0, 0, 0, 128, + 0, 0, 15, 144, 31, 0, + 0, 2, 3, 0, 0, 128, + 1, 0, 15, 144, 31, 0, + 0, 2, 5, 0, 0, 128, + 2, 0, 15, 144, 31, 0, + 0, 2, 10, 0, 0, 128, + 3, 0, 15, 144, 5, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 85, 144, 1, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 3, 0, 228, 160, + 0, 0, 255, 144, 0, 0, + 228, 128, 1, 0, 0, 2, + 0, 0, 15, 192, 0, 0, + 228, 128, 5, 0, 0, 3, + 0, 0, 7, 128, 1, 0, + 85, 144, 9, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 8, 0, 228, 160, + 1, 0, 0, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 7, 128, 10, 0, + 228, 160, 1, 0, 170, 144, + 0, 0, 228, 128, 1, 0, + 0, 2, 1, 0, 1, 128, + 13, 0, 0, 160, 4, 0, + 0, 4, 1, 0, 7, 128, + 15, 0, 228, 160, 1, 0, + 0, 128, 3, 0, 228, 144, + 1, 0, 0, 2, 2, 0, + 7, 128, 1, 0, 228, 128, + 1, 0, 0, 2, 1, 0, + 8, 128, 4, 0, 0, 160, + 38, 0, 0, 1, 0, 0, + 228, 240, 2, 0, 0, 3, + 3, 0, 1, 128, 1, 0, + 255, 128, 16, 0, 0, 160, + 5, 0, 0, 3, 3, 0, + 1, 128, 3, 0, 0, 128, + 4, 0, 85, 160, 46, 0, + 0, 2, 0, 0, 1, 176, + 3, 0, 0, 128, 8, 0, + 0, 4, 3, 0, 1, 128, + 0, 0, 228, 128, 19, 32, + 228, 161, 0, 0, 0, 176, + 11, 0, 0, 3, 3, 0, + 1, 128, 3, 0, 0, 128, + 4, 0, 0, 160, 5, 0, + 0, 4, 3, 0, 7, 128, + 3, 0, 0, 128, 17, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 2, 0, + 7, 128, 3, 0, 228, 128, + 13, 0, 170, 160, 2, 0, + 228, 128, 2, 0, 0, 3, + 1, 0, 8, 128, 1, 0, + 255, 128, 4, 0, 170, 160, + 39, 0, 0, 0, 1, 0, + 0, 2, 2, 0, 8, 128, + 3, 0, 255, 144, 11, 0, + 0, 3, 1, 0, 15, 128, + 2, 0, 228, 128, 4, 0, + 0, 160, 10, 0, 0, 3, + 1, 0, 15, 128, 1, 0, + 228, 128, 4, 0, 170, 160, + 5, 0, 0, 3, 0, 0, + 15, 208, 1, 0, 228, 128, + 12, 0, 228, 160, 2, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 4, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 2, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/default_tex_PS.h b/vendor/librw/src/d3d/shaders/default_tex_PS.h new file mode 100644 index 0000000..a25f918 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/default_tex_PS.h @@ -0,0 +1,89 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /DTEX /Fh default_tex_PS.h default_PS.hlsl +// +// +// Parameters: +// +// float4 fogColor; +// sampler2D tex0; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// tex0 s0 1 +// + + ps_2_0 + dcl t0.xyz + dcl v0 + dcl_2d s0 + texld r0, t0, s0 + mad r0.xyz, v0, r0, -c0 + mul r1.w, r0.w, v0.w + mad r1.xyz, t0.z, r0, c0 + mov oC0, r1 + +// approximately 5 instruction slots used (1 texture, 4 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 45, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 127, 0, + 0, 0, 0, 2, 255, 255, + 2, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 120, 0, 0, 0, 68, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 2, 0, 80, 0, + 0, 0, 0, 0, 0, 0, + 96, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 2, 0, + 104, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 67, + 111, 108, 111, 114, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 48, 0, 171, + 171, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 57, 46, 57, + 53, 50, 46, 51, 49, 49, + 49, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 7, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, + 15, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 176, 0, 8, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 0, 0, 228, 144, + 0, 0, 228, 128, 0, 0, + 228, 161, 5, 0, 0, 3, + 1, 0, 8, 128, 0, 0, + 255, 128, 0, 0, 255, 144, + 4, 0, 0, 4, 1, 0, + 7, 128, 0, 0, 170, 176, + 0, 0, 228, 128, 0, 0, + 228, 160, 1, 0, 0, 2, + 0, 8, 15, 128, 1, 0, + 228, 128, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/im2d_PS.h b/vendor/librw/src/d3d/shaders/im2d_PS.h new file mode 100644 index 0000000..71f8b28 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/im2d_PS.h @@ -0,0 +1,72 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /Fh im2d_PS.h im2d_PS.hlsl +// +// +// Parameters: +// +// float4 fogColor; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// + + ps_2_0 + dcl t0.xyz + dcl v0 + add r0.xyz, v0, -c0 + mad r0.xyz, t0.z, r0, c0 + mov r0.w, v0.w + mov oC0, r0 + +// approximately 4 instruction slots used +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 34, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 83, 0, + 0, 0, 0, 2, 255, 255, + 1, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 76, 0, 0, 0, 48, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 2, 0, 60, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 67, 111, 108, + 111, 114, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 115, + 95, 50, 95, 48, 0, 77, + 105, 99, 114, 111, 115, 111, + 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 57, 46, + 50, 57, 46, 57, 53, 50, + 46, 51, 49, 49, 49, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 7, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 2, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 228, 144, + 0, 0, 228, 161, 4, 0, + 0, 4, 0, 0, 7, 128, + 0, 0, 170, 176, 0, 0, + 228, 128, 0, 0, 228, 160, + 1, 0, 0, 2, 0, 0, + 8, 128, 0, 0, 255, 144, + 1, 0, 0, 2, 0, 8, + 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/im2d_PS.hlsl b/vendor/librw/src/d3d/shaders/im2d_PS.hlsl new file mode 100644 index 0000000..eaf279d --- /dev/null +++ b/vendor/librw/src/d3d/shaders/im2d_PS.hlsl @@ -0,0 +1,19 @@ +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float4 Color : COLOR0; +}; + +sampler2D tex0 : register(s0); + +float4 fogColor : register(c0); + +float4 main(VS_out input) : COLOR +{ + float4 color = input.Color; +#ifdef TEX + color *= tex2D(tex0, input.TexCoord0.xy); +#endif + color.rgb = lerp(fogColor.rgb, color.rgb, input.TexCoord0.z); + return color; +} diff --git a/vendor/librw/src/d3d/shaders/im2d_VS.h b/vendor/librw/src/d3d/shaders/im2d_VS.h new file mode 100644 index 0000000..bc36b1f --- /dev/null +++ b/vendor/librw/src/d3d/shaders/im2d_VS.h @@ -0,0 +1,107 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /Fh im2d_VS.h im2d_VS.hlsl +// +// +// Parameters: +// +// float4 fogData; +// float4 xform; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogData c14 1 +// xform c41 1 +// + + vs_2_0 + def c0, 1, 0, 0, 0 + dcl_position v0 + dcl_texcoord v1 + dcl_color v2 + add r0.x, v0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c0.x + mad r0.xy, v0, c41, c41.zwzw + mov r0.z, v0.z + mul oPos.xyz, r0, v0.w + mov oPos.w, v0.w + mov oT0.xy, v1 + mov oD0, v2 + +// approximately 10 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 40, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 105, 0, + 0, 0, 0, 2, 254, 255, + 2, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 98, 0, 0, 0, 68, 0, + 0, 0, 2, 0, 14, 0, + 1, 0, 58, 0, 76, 0, + 0, 0, 0, 0, 0, 0, + 92, 0, 0, 0, 2, 0, + 41, 0, 1, 0, 166, 0, + 76, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 68, + 97, 116, 97, 0, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 120, 102, 111, 114, + 109, 0, 118, 115, 95, 50, + 95, 48, 0, 77, 105, 99, + 114, 111, 115, 111, 102, 116, + 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, + 114, 32, 57, 46, 50, 57, + 46, 57, 53, 50, 46, 51, + 49, 49, 49, 0, 171, 171, + 81, 0, 0, 5, 0, 0, + 15, 160, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 5, 0, + 0, 128, 1, 0, 15, 144, + 31, 0, 0, 2, 10, 0, + 0, 128, 2, 0, 15, 144, + 2, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 255, 144, + 14, 0, 85, 161, 5, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 0, 128, 14, 0, + 170, 160, 11, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 14, 0, 255, 160, + 10, 0, 0, 3, 0, 0, + 4, 224, 0, 0, 0, 128, + 0, 0, 0, 160, 4, 0, + 0, 4, 0, 0, 3, 128, + 0, 0, 228, 144, 41, 0, + 228, 160, 41, 0, 238, 160, + 1, 0, 0, 2, 0, 0, + 4, 128, 0, 0, 170, 144, + 5, 0, 0, 3, 0, 0, + 7, 192, 0, 0, 228, 128, + 0, 0, 255, 144, 1, 0, + 0, 2, 0, 0, 8, 192, + 0, 0, 255, 144, 1, 0, + 0, 2, 0, 0, 3, 224, + 1, 0, 228, 144, 1, 0, + 0, 2, 0, 0, 15, 208, + 2, 0, 228, 144, 255, 255, + 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/im2d_VS.hlsl b/vendor/librw/src/d3d/shaders/im2d_VS.hlsl new file mode 100644 index 0000000..20cf3ce --- /dev/null +++ b/vendor/librw/src/d3d/shaders/im2d_VS.hlsl @@ -0,0 +1,30 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float2 TexCoord : TEXCOORD0; + float4 Color : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float4 Color : COLOR0; +}; + +float4 xform : register(c41); + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = input.Position; + output.Position.xy = output.Position.xy * xform.xy + xform.zw; + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + output.Position.xyz *= output.Position.w; + output.Color = input.Color; + output.TexCoord0.xy = input.TexCoord; + + return output; +} diff --git a/vendor/librw/src/d3d/shaders/im2d_tex_PS.h b/vendor/librw/src/d3d/shaders/im2d_tex_PS.h new file mode 100644 index 0000000..a91888b --- /dev/null +++ b/vendor/librw/src/d3d/shaders/im2d_tex_PS.h @@ -0,0 +1,89 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /DTEX /Fh im2d_tex_PS.h im2d_PS.hlsl +// +// +// Parameters: +// +// float4 fogColor; +// sampler2D tex0; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// tex0 s0 1 +// + + ps_2_0 + dcl t0.xyz + dcl v0 + dcl_2d s0 + texld r0, t0, s0 + mad r0.xyz, v0, r0, -c0 + mul r1.w, r0.w, v0.w + mad r1.xyz, t0.z, r0, c0 + mov oC0, r1 + +// approximately 5 instruction slots used (1 texture, 4 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 45, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 127, 0, + 0, 0, 0, 2, 255, 255, + 2, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 120, 0, 0, 0, 68, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 2, 0, 80, 0, + 0, 0, 0, 0, 0, 0, + 96, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 2, 0, + 104, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 67, + 111, 108, 111, 114, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 48, 0, 171, + 171, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 57, 46, 57, + 53, 50, 46, 51, 49, 49, + 49, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 7, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, + 15, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 176, 0, 8, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 0, 0, 228, 144, + 0, 0, 228, 128, 0, 0, + 228, 161, 5, 0, 0, 3, + 1, 0, 8, 128, 0, 0, + 255, 128, 0, 0, 255, 144, + 4, 0, 0, 4, 1, 0, + 7, 128, 0, 0, 170, 176, + 0, 0, 228, 128, 0, 0, + 228, 160, 1, 0, 0, 2, + 0, 8, 15, 128, 1, 0, + 228, 128, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/lighting.h b/vendor/librw/src/d3d/shaders/lighting.h new file mode 100644 index 0000000..4b08196 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/lighting.h @@ -0,0 +1,44 @@ +struct Light +{ + float4 color; // and radius + float4 position; // and -cos(angle) + float4 direction; // and falloff clamp +}; + +float3 DoDirLight(Light L, float3 N) +{ + float l = max(0.0, dot(N, -L.direction.xyz)); + return l*L.color.xyz; +} + +float3 DoDirLightSpec(Light L, float3 N, float3 V, float power) +{ + return pow(saturate(dot(N, normalize(V + -L.direction.xyz))), power)*L.color.xyz; +} + +float3 DoPointLight(Light L, float3 V, float3 N) +{ + // As on PS2 + float3 dir = V - L.position.xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/L.color.w)); + float l = max(0.0, dot(N, -normalize(dir))); + return l*L.color.xyz*atten; +} + +float3 DoSpotLight(Light L, float3 V, float3 N) +{ + // As on PS2 + float3 dir = V - L.position.xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/L.color.w)); + dir /= dist; + float l = max(0.0, dot(N, -dir)); + float pcos = dot(dir, L.direction.xyz); // cos to point + float ccos = -L.position.w; // cos of cone + float falloff = (pcos-ccos)/(1.0-ccos); + if(falloff < 0) // outside of cone + l = 0; + l *= max(falloff, L.direction.w); // falloff clamp + return l*L.color.xyz*atten; +} diff --git a/vendor/librw/src/d3d/shaders/make_default.cmd b/vendor/librw/src/d3d/shaders/make_default.cmd new file mode 100644 index 0000000..bc44429 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/make_default.cmd @@ -0,0 +1,11 @@ +@echo off +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /Fh default_amb_VS.h default_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /Fh default_amb_dir_VS.h default_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh default_all_VS.h default_VS.hlsl + +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /Fh default_PS.h default_PS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /DTEX /Fh default_tex_PS.h default_PS.hlsl + +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /Fh im2d_VS.h im2d_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /Fh im2d_PS.h im2d_PS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /DTEX /Fh im2d_tex_PS.h im2d_PS.hlsl diff --git a/vendor/librw/src/d3d/shaders/make_matfx.cmd b/vendor/librw/src/d3d/shaders/make_matfx.cmd new file mode 100644 index 0000000..d0bc4fa --- /dev/null +++ b/vendor/librw/src/d3d/shaders/make_matfx.cmd @@ -0,0 +1,7 @@ +@echo off +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /Fh matfx_env_amb_VS.h matfx_env_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /Fh matfx_env_amb_dir_VS.h matfx_env_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh matfx_env_all_VS.h matfx_env_VS.hlsl + +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /Fh matfx_env_PS.h matfx_env_PS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /DTEX /Fh matfx_env_tex_PS.h matfx_env_PS.hlsl diff --git a/vendor/librw/src/d3d/shaders/make_skin.cmd b/vendor/librw/src/d3d/shaders/make_skin.cmd new file mode 100644 index 0000000..b7822ca --- /dev/null +++ b/vendor/librw/src/d3d/shaders/make_skin.cmd @@ -0,0 +1,4 @@ +@echo off +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /Fh skin_amb_VS.h skin_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /Fh skin_amb_dir_VS.h skin_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh skin_all_VS.h skin_VS.hlsl diff --git a/vendor/librw/src/d3d/shaders/matfx_env_PS.h b/vendor/librw/src/d3d/shaders/matfx_env_PS.h new file mode 100644 index 0000000..e39a1f5 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/matfx_env_PS.h @@ -0,0 +1,124 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /Fh matfx_env_PS.h matfx_env_PS.hlsl +// +// +// Parameters: +// +// sampler2D envTex; +// float4 fogColor; +// float4 fxparams; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// fxparams c1 1 +// envTex s1 1 +// + + ps_2_0 + dcl t0.xyz + dcl t1.xy + dcl v0 + dcl v1.xyz + dcl_2d s1 + texld r0, t1, s1 + mul r1.xyz, v1, c1.x + mul r0.xyz, r0, r1 + mul r0.xyz, r0, t0.z + max r0.w, v0.w, c1.y + mul r0.xyz, r0.w, r0 + add r1.xyz, v0, -c0 + mad r1.xyz, t0.z, r1, c0 + mad r0.xyz, r1, v0.w, r0 + mov r0.w, v0.w + mov oC0, r0 + +// approximately 11 instruction slots used (1 texture, 10 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 53, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 156, 0, + 0, 0, 0, 2, 255, 255, + 3, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 149, 0, 0, 0, 88, 0, + 0, 0, 3, 0, 1, 0, + 1, 0, 6, 0, 96, 0, + 0, 0, 0, 0, 0, 0, + 112, 0, 0, 0, 2, 0, + 0, 0, 1, 0, 2, 0, + 124, 0, 0, 0, 0, 0, + 0, 0, 140, 0, 0, 0, + 2, 0, 1, 0, 1, 0, + 6, 0, 124, 0, 0, 0, + 0, 0, 0, 0, 101, 110, + 118, 84, 101, 120, 0, 171, + 4, 0, 12, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 111, + 103, 67, 111, 108, 111, 114, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 120, 112, 97, + 114, 97, 109, 115, 0, 112, + 115, 95, 50, 95, 48, 0, + 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, + 32, 83, 104, 97, 100, 101, + 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 57, + 46, 50, 57, 46, 57, 53, + 50, 46, 51, 49, 49, 49, + 0, 171, 171, 171, 31, 0, + 0, 2, 0, 0, 0, 128, + 0, 0, 7, 176, 31, 0, + 0, 2, 0, 0, 0, 128, + 1, 0, 3, 176, 31, 0, + 0, 2, 0, 0, 0, 128, + 0, 0, 15, 144, 31, 0, + 0, 2, 0, 0, 0, 128, + 1, 0, 7, 144, 31, 0, + 0, 2, 0, 0, 0, 144, + 1, 8, 15, 160, 66, 0, + 0, 3, 0, 0, 15, 128, + 1, 0, 228, 176, 1, 8, + 228, 160, 5, 0, 0, 3, + 1, 0, 7, 128, 1, 0, + 228, 144, 1, 0, 0, 160, + 5, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 228, 128, + 1, 0, 228, 128, 5, 0, + 0, 3, 0, 0, 7, 128, + 0, 0, 228, 128, 0, 0, + 170, 176, 11, 0, 0, 3, + 0, 0, 8, 128, 0, 0, + 255, 144, 1, 0, 85, 160, + 5, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 255, 128, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 7, 128, + 0, 0, 228, 144, 0, 0, + 228, 161, 4, 0, 0, 4, + 1, 0, 7, 128, 0, 0, + 170, 176, 1, 0, 228, 128, + 0, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 7, 128, + 1, 0, 228, 128, 0, 0, + 255, 144, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 8, 128, 0, 0, 255, 144, + 1, 0, 0, 2, 0, 8, + 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/matfx_env_PS.hlsl b/vendor/librw/src/d3d/shaders/matfx_env_PS.hlsl new file mode 100644 index 0000000..eb5187d --- /dev/null +++ b/vendor/librw/src/d3d/shaders/matfx_env_PS.hlsl @@ -0,0 +1,42 @@ +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; + float4 EnvColor : COLOR1; +}; + +sampler2D diffTex : register(s0); +sampler2D envTex : register(s1); + +float4 fogColor : register(c0); + +float4 fxparams : register(c1); + +#define shininess (fxparams.x) +#define disableFBA (fxparams.y) + +float4 main(VS_out input) : COLOR +{ + float4 pass1 = input.Color; +#ifdef TEX + pass1 *= tex2D(diffTex, input.TexCoord0.xy); +#endif + + float4 pass2 = input.EnvColor*shininess*tex2D(envTex, input.TexCoord1.xy); + + pass1.rgb = lerp(fogColor.rgb, pass1.rgb, input.TexCoord0.z); + pass2.rgb = lerp(float3(0.0, 0.0, 0.0), pass2.rgb, input.TexCoord0.z); + + // We simulate drawing this in two passes. + // First pass with standard blending, second with addition + // We premultiply alpha so render state should be one. + // For FB alpha rendering assume that diffuse alpha (pass1.a) was + // written to framebuffer, so just multiply pass2 by it as well then. + float fba = max(pass1.a, disableFBA); + float4 color; + color.rgb = pass1.rgb*pass1.a + pass2.rgb*fba; + color.a = pass1.a; + + return color; +} diff --git a/vendor/librw/src/d3d/shaders/matfx_env_VS.hlsl b/vendor/librw/src/d3d/shaders/matfx_env_VS.hlsl new file mode 100644 index 0000000..31b674c --- /dev/null +++ b/vendor/librw/src/d3d/shaders/matfx_env_VS.hlsl @@ -0,0 +1,59 @@ +#include "standardConstants.h" + +float4x4 texMat : register(c41); +float4 colorClamp : register(c45); +float4 envColor : register(c46); + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; + float4 EnvColor : COLOR1; +}; + + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 V = mul(worldMat, input.Position).xyz; + float3 N = mul(normalMat, input.Normal); + + output.TexCoord0.xy = input.TexCoord; + output.TexCoord1 = mul(texMat, float4(N, 1.0)).xy; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], N)*surfDiffuse; +#endif +#ifdef POINTLIGHTS + for(i = 0; i < numPointLights; i++) + output.Color.xyz += DoPointLight(lights[i+firstPointLight], V, N)*surfDiffuse; +#endif +#ifdef SPOTLIGHTS + for(i = 0; i < numSpotLights; i++) + output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], V, N)*surfDiffuse; +#endif + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.EnvColor = max(output.Color, colorClamp) * envColor; + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/vendor/librw/src/d3d/shaders/matfx_env_all_VS.h b/vendor/librw/src/d3d/shaders/matfx_env_all_VS.h new file mode 100644 index 0000000..65e1c15 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/matfx_env_all_VS.h @@ -0,0 +1,535 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh +// matfx_env_all_VS.h matfx_env_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4 colorClamp; +// float4x4 combinedMat; +// float4 envColor; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// int numPointLights; +// int numSpotLights; +// float4 surfProps; +// float4x4 texMat; +// float4x4 worldMat; +// +// +// Registers: +// +// Name Reg Size +// -------------- ----- ---- +// numDirLights i0 1 +// numPointLights i1 1 +// numSpotLights i2 1 +// combinedMat c0 4 +// worldMat c4 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// texMat c41 4 +// colorClamp c45 1 +// envColor c46 1 +// + + vs_2_0 + def c11, 0, 3, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + mov oPos, r0 + mul r0.xyz, v0.y, c5 + mad r0.xyz, c4, v0.x, r0 + mad r0.xyz, c6, v0.z, r0 + mad r0.xyz, c7, v0.w, r0 + mul r1.xyz, v1.y, c9 + mad r1.xyz, c8, v1.x, r1 + mad r1.xyz, c10, v1.z, r1 + mov r2.x, c13.x + mad r2.xyz, c15, r2.x, v3 + mov r3.xyz, r2 + mov r1.w, c11.x + rep i0 + add r2.w, r1.w, c16.x + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + dp3 r2.w, r1, -c19[a0.x] + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, c17[a0.x] + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c11.z + endrep + mov r2.xyz, r3 + mov r1.w, c11.x + rep i1 + add r2.w, r1.w, c16.y + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + add r4.xyz, r0, -c18[a0.x] + dp3 r2.w, r4, r4 + rsq r2.w, r2.w + mul r4.xyz, r2.w, r4 + dp3 r3.w, r1, -r4 + max r3.w, r3.w, c11.x + mul r4.xyz, r3.w, c17[a0.x] + rcp r2.w, r2.w + rcp r3.w, c17[a0.x].w + mad r2.w, r2.w, -r3.w, c11.z + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, r4 + mad r2.xyz, r4, c13.z, r2 + add r1.w, r1.w, c11.z + endrep + mov r3.xyz, r2 + mov r1.w, c11.x + rep i2 + add r2.w, r1.w, c16.z + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + add r4.xyz, r0, -c18[a0.x] + dp3 r2.w, r4, r4 + rsq r2.w, r2.w + mul r4.xyz, r2.w, r4 + dp3 r4.w, r1, -r4 + dp3 r4.x, r4, c19[a0.x] + max r4.y, r4.w, c11.x + mov r4.z, c11.z + add r4.xz, r4, c18[a0.x].w + rcp r4.z, r4.z + mul r4.x, r4.z, r4.x + slt r4.z, r4.x, c11.x + mad r4.y, r4.z, -r4.y, r4.y + max r4.x, r4.x, c19[a0.x].w + mul r4.x, r4.x, r4.y + mul r4.xyz, r4.x, c17[a0.x] + rcp r2.w, r2.w + rcp r4.w, c17[a0.x].w + mad r2.w, r2.w, -r4.w, c11.z + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, r4 + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c11.z + endrep + mov r3.w, v3.w + max r2, r3, c11.x + min r2, r2, c11.z + mul oD0, r2, c12 + mul r0.xy, r1.y, c42 + mad r0.xy, c41, r1.x, r0 + mad r0.xy, c43, r1.z, r0 + add oT1.xy, r0, c44 + max r1, r2, c45 + mul oD1, r1, c46 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c11.z + mov oT0.xy, v2 + +// approximately 101 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 180, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 154, 2, + 0, 0, 0, 2, 254, 255, + 15, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 147, 2, 0, 0, 72, 1, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 88, 1, + 0, 0, 0, 0, 0, 0, + 104, 1, 0, 0, 2, 0, + 45, 0, 1, 0, 182, 0, + 88, 1, 0, 0, 0, 0, + 0, 0, 115, 1, 0, 0, + 2, 0, 0, 0, 4, 0, + 2, 0, 128, 1, 0, 0, + 0, 0, 0, 0, 144, 1, + 0, 0, 2, 0, 46, 0, + 1, 0, 186, 0, 88, 1, + 0, 0, 0, 0, 0, 0, + 153, 1, 0, 0, 2, 0, + 16, 0, 1, 0, 66, 0, + 164, 1, 0, 0, 0, 0, + 0, 0, 180, 1, 0, 0, + 2, 0, 14, 0, 1, 0, + 58, 0, 88, 1, 0, 0, + 0, 0, 0, 0, 188, 1, + 0, 0, 2, 0, 17, 0, + 24, 0, 70, 0, 8, 2, + 0, 0, 0, 0, 0, 0, + 24, 2, 0, 0, 2, 0, + 12, 0, 1, 0, 50, 0, + 88, 1, 0, 0, 0, 0, + 0, 0, 31, 2, 0, 0, + 2, 0, 8, 0, 3, 0, + 34, 0, 44, 2, 0, 0, + 0, 0, 0, 0, 60, 2, + 0, 0, 1, 0, 0, 0, + 1, 0, 2, 0, 76, 2, + 0, 0, 0, 0, 0, 0, + 92, 2, 0, 0, 1, 0, + 1, 0, 1, 0, 6, 0, + 76, 2, 0, 0, 0, 0, + 0, 0, 107, 2, 0, 0, + 1, 0, 2, 0, 1, 0, + 10, 0, 76, 2, 0, 0, + 0, 0, 0, 0, 121, 2, + 0, 0, 2, 0, 13, 0, + 1, 0, 54, 0, 88, 1, + 0, 0, 0, 0, 0, 0, + 131, 2, 0, 0, 2, 0, + 41, 0, 4, 0, 166, 0, + 128, 1, 0, 0, 0, 0, + 0, 0, 138, 2, 0, 0, + 2, 0, 4, 0, 4, 0, + 18, 0, 128, 1, 0, 0, + 0, 0, 0, 0, 97, 109, + 98, 105, 101, 110, 116, 76, + 105, 103, 104, 116, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 99, 111, 108, 111, 114, 67, + 108, 97, 109, 112, 0, 99, + 111, 109, 98, 105, 110, 101, + 100, 77, 97, 116, 0, 171, + 3, 0, 3, 0, 4, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 101, 110, + 118, 67, 111, 108, 111, 114, + 0, 102, 105, 114, 115, 116, + 76, 105, 103, 104, 116, 0, + 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 111, + 103, 68, 97, 116, 97, 0, + 108, 105, 103, 104, 116, 115, + 0, 99, 111, 108, 111, 114, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 111, 115, 105, + 116, 105, 111, 110, 0, 100, + 105, 114, 101, 99, 116, 105, + 111, 110, 0, 171, 195, 1, + 0, 0, 204, 1, 0, 0, + 220, 1, 0, 0, 204, 1, + 0, 0, 229, 1, 0, 0, + 204, 1, 0, 0, 5, 0, + 0, 0, 1, 0, 12, 0, + 8, 0, 3, 0, 240, 1, + 0, 0, 109, 97, 116, 67, + 111, 108, 0, 110, 111, 114, + 109, 97, 108, 77, 97, 116, + 0, 171, 171, 171, 3, 0, + 3, 0, 3, 0, 3, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 110, 117, 109, 68, + 105, 114, 76, 105, 103, 104, + 116, 115, 0, 171, 171, 171, + 0, 0, 2, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 110, 117, + 109, 80, 111, 105, 110, 116, + 76, 105, 103, 104, 116, 115, + 0, 110, 117, 109, 83, 112, + 111, 116, 76, 105, 103, 104, + 116, 115, 0, 115, 117, 114, + 102, 80, 114, 111, 112, 115, + 0, 116, 101, 120, 77, 97, + 116, 0, 119, 111, 114, 108, + 100, 77, 97, 116, 0, 118, + 115, 95, 50, 95, 48, 0, + 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, + 32, 83, 104, 97, 100, 101, + 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 57, + 46, 50, 57, 46, 57, 53, + 50, 46, 51, 49, 49, 49, + 0, 171, 81, 0, 0, 5, + 11, 0, 15, 160, 0, 0, + 0, 0, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 3, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 2, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 3, 0, + 15, 144, 5, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 85, 144, 1, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 15, 128, 0, 0, 228, 160, + 0, 0, 0, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 15, 128, 2, 0, + 228, 160, 0, 0, 170, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 3, 0, 228, 160, 0, 0, + 255, 144, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 15, 192, 0, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 85, 144, + 5, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 7, 128, + 4, 0, 228, 160, 0, 0, + 0, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 7, 128, 6, 0, 228, 160, + 0, 0, 170, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 7, 128, 7, 0, + 228, 160, 0, 0, 255, 144, + 0, 0, 228, 128, 5, 0, + 0, 3, 1, 0, 7, 128, + 1, 0, 85, 144, 9, 0, + 228, 160, 4, 0, 0, 4, + 1, 0, 7, 128, 8, 0, + 228, 160, 1, 0, 0, 144, + 1, 0, 228, 128, 4, 0, + 0, 4, 1, 0, 7, 128, + 10, 0, 228, 160, 1, 0, + 170, 144, 1, 0, 228, 128, + 1, 0, 0, 2, 2, 0, + 1, 128, 13, 0, 0, 160, + 4, 0, 0, 4, 2, 0, + 7, 128, 15, 0, 228, 160, + 2, 0, 0, 128, 3, 0, + 228, 144, 1, 0, 0, 2, + 3, 0, 7, 128, 2, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 8, 128, 11, 0, + 0, 160, 38, 0, 0, 1, + 0, 0, 228, 240, 2, 0, + 0, 3, 2, 0, 8, 128, + 1, 0, 255, 128, 16, 0, + 0, 160, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 85, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 2, 0, 255, 128, + 8, 0, 0, 4, 2, 0, + 8, 128, 1, 0, 228, 128, + 19, 32, 228, 161, 0, 0, + 0, 176, 11, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 0, 160, + 5, 0, 0, 4, 4, 0, + 7, 128, 2, 0, 255, 128, + 17, 32, 228, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 3, 0, 7, 128, 4, 0, + 228, 128, 13, 0, 170, 160, + 3, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 170, 160, 39, 0, 0, 0, + 1, 0, 0, 2, 2, 0, + 7, 128, 3, 0, 228, 128, + 1, 0, 0, 2, 1, 0, + 8, 128, 11, 0, 0, 160, + 38, 0, 0, 1, 1, 0, + 228, 240, 2, 0, 0, 3, + 2, 0, 8, 128, 1, 0, + 255, 128, 16, 0, 85, 160, + 5, 0, 0, 3, 2, 0, + 8, 128, 2, 0, 255, 128, + 11, 0, 85, 160, 46, 0, + 0, 2, 0, 0, 1, 176, + 2, 0, 255, 128, 2, 0, + 0, 4, 4, 0, 7, 128, + 0, 0, 228, 128, 18, 32, + 228, 161, 0, 0, 0, 176, + 8, 0, 0, 3, 2, 0, + 8, 128, 4, 0, 228, 128, + 4, 0, 228, 128, 7, 0, + 0, 2, 2, 0, 8, 128, + 2, 0, 255, 128, 5, 0, + 0, 3, 4, 0, 7, 128, + 2, 0, 255, 128, 4, 0, + 228, 128, 8, 0, 0, 3, + 3, 0, 8, 128, 1, 0, + 228, 128, 4, 0, 228, 129, + 11, 0, 0, 3, 3, 0, + 8, 128, 3, 0, 255, 128, + 11, 0, 0, 160, 5, 0, + 0, 4, 4, 0, 7, 128, + 3, 0, 255, 128, 17, 32, + 228, 160, 0, 0, 0, 176, + 6, 0, 0, 2, 2, 0, + 8, 128, 2, 0, 255, 128, + 6, 0, 0, 3, 3, 0, + 8, 128, 17, 32, 255, 160, + 0, 0, 0, 176, 4, 0, + 0, 4, 2, 0, 8, 128, + 2, 0, 255, 128, 3, 0, + 255, 129, 11, 0, 170, 160, + 11, 0, 0, 3, 2, 0, + 8, 128, 2, 0, 255, 128, + 11, 0, 0, 160, 5, 0, + 0, 3, 4, 0, 7, 128, + 2, 0, 255, 128, 4, 0, + 228, 128, 4, 0, 0, 4, + 2, 0, 7, 128, 4, 0, + 228, 128, 13, 0, 170, 160, + 2, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 170, 160, 39, 0, 0, 0, + 1, 0, 0, 2, 3, 0, + 7, 128, 2, 0, 228, 128, + 1, 0, 0, 2, 1, 0, + 8, 128, 11, 0, 0, 160, + 38, 0, 0, 1, 2, 0, + 228, 240, 2, 0, 0, 3, + 2, 0, 8, 128, 1, 0, + 255, 128, 16, 0, 170, 160, + 5, 0, 0, 3, 2, 0, + 8, 128, 2, 0, 255, 128, + 11, 0, 85, 160, 46, 0, + 0, 2, 0, 0, 1, 176, + 2, 0, 255, 128, 2, 0, + 0, 4, 4, 0, 7, 128, + 0, 0, 228, 128, 18, 32, + 228, 161, 0, 0, 0, 176, + 8, 0, 0, 3, 2, 0, + 8, 128, 4, 0, 228, 128, + 4, 0, 228, 128, 7, 0, + 0, 2, 2, 0, 8, 128, + 2, 0, 255, 128, 5, 0, + 0, 3, 4, 0, 7, 128, + 2, 0, 255, 128, 4, 0, + 228, 128, 8, 0, 0, 3, + 4, 0, 8, 128, 1, 0, + 228, 128, 4, 0, 228, 129, + 8, 0, 0, 4, 4, 0, + 1, 128, 4, 0, 228, 128, + 19, 32, 228, 160, 0, 0, + 0, 176, 11, 0, 0, 3, + 4, 0, 2, 128, 4, 0, + 255, 128, 11, 0, 0, 160, + 1, 0, 0, 2, 4, 0, + 4, 128, 11, 0, 170, 160, + 2, 0, 0, 4, 4, 0, + 5, 128, 4, 0, 228, 128, + 18, 32, 255, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 4, 0, 4, 128, 4, 0, + 170, 128, 5, 0, 0, 3, + 4, 0, 1, 128, 4, 0, + 170, 128, 4, 0, 0, 128, + 12, 0, 0, 3, 4, 0, + 4, 128, 4, 0, 0, 128, + 11, 0, 0, 160, 4, 0, + 0, 4, 4, 0, 2, 128, + 4, 0, 170, 128, 4, 0, + 85, 129, 4, 0, 85, 128, + 11, 0, 0, 4, 4, 0, + 1, 128, 4, 0, 0, 128, + 19, 32, 255, 160, 0, 0, + 0, 176, 5, 0, 0, 3, + 4, 0, 1, 128, 4, 0, + 0, 128, 4, 0, 85, 128, + 5, 0, 0, 4, 4, 0, + 7, 128, 4, 0, 0, 128, + 17, 32, 228, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 2, 0, 8, 128, 2, 0, + 255, 128, 6, 0, 0, 3, + 4, 0, 8, 128, 17, 32, + 255, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 2, 0, + 8, 128, 2, 0, 255, 128, + 4, 0, 255, 129, 11, 0, + 170, 160, 11, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 0, 160, + 5, 0, 0, 3, 4, 0, + 7, 128, 2, 0, 255, 128, + 4, 0, 228, 128, 4, 0, + 0, 4, 3, 0, 7, 128, + 4, 0, 228, 128, 13, 0, + 170, 160, 3, 0, 228, 128, + 2, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 3, 0, 8, 128, 3, 0, + 255, 144, 11, 0, 0, 3, + 2, 0, 15, 128, 3, 0, + 228, 128, 11, 0, 0, 160, + 10, 0, 0, 3, 2, 0, + 15, 128, 2, 0, 228, 128, + 11, 0, 170, 160, 5, 0, + 0, 3, 0, 0, 15, 208, + 2, 0, 228, 128, 12, 0, + 228, 160, 5, 0, 0, 3, + 0, 0, 3, 128, 1, 0, + 85, 128, 42, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 3, 128, 41, 0, 228, 160, + 1, 0, 0, 128, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 3, 128, 43, 0, + 228, 160, 1, 0, 170, 128, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 3, 224, + 0, 0, 228, 128, 44, 0, + 228, 160, 11, 0, 0, 3, + 1, 0, 15, 128, 2, 0, + 228, 128, 45, 0, 228, 160, + 5, 0, 0, 3, 1, 0, + 15, 208, 1, 0, 228, 128, + 46, 0, 228, 160, 2, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 11, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 2, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/matfx_env_amb_VS.h b/vendor/librw/src/d3d/shaders/matfx_env_amb_VS.h new file mode 100644 index 0000000..0367d5e --- /dev/null +++ b/vendor/librw/src/d3d/shaders/matfx_env_amb_VS.h @@ -0,0 +1,225 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /Fh matfx_env_amb_VS.h matfx_env_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4 colorClamp; +// float4x4 combinedMat; +// float4 envColor; +// float4 fogData; +// float4 matCol; +// float3x3 normalMat; +// float4 surfProps; +// float4x4 texMat; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// combinedMat c0 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// texMat c41 4 +// colorClamp c45 1 +// envColor c46 1 +// + + vs_2_0 + def c4, 0, 1, 0, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0.xyz, v1.y, c9 + mad r0.xyz, c8, v1.x, r0 + mad r0.xyz, c10, v1.z, r0 + mul r0.yw, r0.y, c42.xxzy + mad r0.xy, c41, r0.x, r0.ywzw + mad r0.xy, c43, r0.z, r0 + add oT1.xy, r0, c44 + mov r0.xyz, c15 + mad r0.xyz, r0, c13.x, v3 + mov r0.w, v3.w + max r0, r0, c4.x + min r0, r0, c4.y + max r1, r0, c45 + mul oD0, r0, c12 + mul oD1, r1, c46 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + add r1.x, r0.w, -c14.y + mov oPos, r0 + mul r0.x, r1.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.y + mov oT0.xy, v2 + +// approximately 25 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 103, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 100, 1, + 0, 0, 0, 2, 254, 255, + 9, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 93, 1, 0, 0, 208, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 224, 0, + 0, 0, 0, 0, 0, 0, + 240, 0, 0, 0, 2, 0, + 45, 0, 1, 0, 182, 0, + 224, 0, 0, 0, 0, 0, + 0, 0, 251, 0, 0, 0, + 2, 0, 0, 0, 4, 0, + 2, 0, 8, 1, 0, 0, + 0, 0, 0, 0, 24, 1, + 0, 0, 2, 0, 46, 0, + 1, 0, 186, 0, 224, 0, + 0, 0, 0, 0, 0, 0, + 33, 1, 0, 0, 2, 0, + 14, 0, 1, 0, 58, 0, + 224, 0, 0, 0, 0, 0, + 0, 0, 41, 1, 0, 0, + 2, 0, 12, 0, 1, 0, + 50, 0, 224, 0, 0, 0, + 0, 0, 0, 0, 48, 1, + 0, 0, 2, 0, 8, 0, + 3, 0, 34, 0, 60, 1, + 0, 0, 0, 0, 0, 0, + 76, 1, 0, 0, 2, 0, + 13, 0, 1, 0, 54, 0, + 224, 0, 0, 0, 0, 0, + 0, 0, 86, 1, 0, 0, + 2, 0, 41, 0, 4, 0, + 166, 0, 8, 1, 0, 0, + 0, 0, 0, 0, 97, 109, + 98, 105, 101, 110, 116, 76, + 105, 103, 104, 116, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 99, 111, 108, 111, 114, 67, + 108, 97, 109, 112, 0, 99, + 111, 109, 98, 105, 110, 101, + 100, 77, 97, 116, 0, 171, + 3, 0, 3, 0, 4, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 101, 110, + 118, 67, 111, 108, 111, 114, + 0, 102, 111, 103, 68, 97, + 116, 97, 0, 109, 97, 116, + 67, 111, 108, 0, 110, 111, + 114, 109, 97, 108, 77, 97, + 116, 0, 171, 171, 3, 0, + 3, 0, 3, 0, 3, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 115, 117, 114, 102, + 80, 114, 111, 112, 115, 0, + 116, 101, 120, 77, 97, 116, + 0, 118, 115, 95, 50, 95, + 48, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 57, 46, 50, 57, 46, + 57, 53, 50, 46, 51, 49, + 49, 49, 0, 171, 171, 171, + 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 3, 0, + 0, 128, 1, 0, 15, 144, + 31, 0, 0, 2, 5, 0, + 0, 128, 2, 0, 15, 144, + 31, 0, 0, 2, 10, 0, + 0, 128, 3, 0, 15, 144, + 5, 0, 0, 3, 0, 0, + 7, 128, 1, 0, 85, 144, + 9, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 7, 128, + 8, 0, 228, 160, 1, 0, + 0, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 7, 128, 10, 0, 228, 160, + 1, 0, 170, 144, 0, 0, + 228, 128, 5, 0, 0, 3, + 0, 0, 10, 128, 0, 0, + 85, 128, 42, 0, 96, 160, + 4, 0, 0, 4, 0, 0, + 3, 128, 41, 0, 228, 160, + 0, 0, 0, 128, 0, 0, + 237, 128, 4, 0, 0, 4, + 0, 0, 3, 128, 43, 0, + 228, 160, 0, 0, 170, 128, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 3, 224, + 0, 0, 228, 128, 44, 0, + 228, 160, 1, 0, 0, 2, + 0, 0, 7, 128, 15, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 7, 128, 0, 0, + 228, 128, 13, 0, 0, 160, + 3, 0, 228, 144, 1, 0, + 0, 2, 0, 0, 8, 128, + 3, 0, 255, 144, 11, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 4, 0, + 0, 160, 10, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 4, 0, 85, 160, + 11, 0, 0, 3, 1, 0, + 15, 128, 0, 0, 228, 128, + 45, 0, 228, 160, 5, 0, + 0, 3, 0, 0, 15, 208, + 0, 0, 228, 128, 12, 0, + 228, 160, 5, 0, 0, 3, + 1, 0, 15, 208, 1, 0, + 228, 128, 46, 0, 228, 160, + 5, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 85, 144, + 1, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 15, 128, + 0, 0, 228, 160, 0, 0, + 0, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 2, 0, 228, 160, + 0, 0, 170, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 15, 128, 3, 0, + 228, 160, 0, 0, 255, 144, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 1, 0, 0, 2, + 0, 0, 15, 192, 0, 0, + 228, 128, 5, 0, 0, 3, + 0, 0, 1, 128, 1, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 4, 0, + 85, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 2, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/matfx_env_amb_dir_VS.h b/vendor/librw/src/d3d/shaders/matfx_env_amb_dir_VS.h new file mode 100644 index 0000000..b93e572 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/matfx_env_amb_dir_VS.h @@ -0,0 +1,316 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /Fh matfx_env_amb_dir_VS.h +// matfx_env_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4 colorClamp; +// float4x4 combinedMat; +// float4 envColor; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// float4 surfProps; +// float4x4 texMat; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// numDirLights i0 1 +// combinedMat c0 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// texMat c41 4 +// colorClamp c45 1 +// envColor c46 1 +// + + vs_2_0 + def c4, 0, 3, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + mov oPos, r0 + mul r0.xyz, v1.y, c9 + mad r0.xyz, c8, v1.x, r0 + mad r0.xyz, c10, v1.z, r0 + mov r1.x, c13.x + mad r1.xyz, c15, r1.x, v3 + mov r2.xyz, r1 + mov r1.w, c4.x + rep i0 + add r3.x, r1.w, c16.x + mul r3.x, r3.x, c4.y + mova a0.x, r3.x + dp3 r3.x, r0, -c19[a0.x] + max r3.x, r3.x, c4.x + mul r3.xyz, r3.x, c17[a0.x] + mad r2.xyz, r3, c13.z, r2 + add r1.w, r1.w, c4.z + endrep + mov r2.w, v3.w + max r1, r2, c4.x + min r1, r1, c4.z + mul oD0, r1, c12 + mul r2.xy, r0.y, c42 + mad r0.xy, c41, r0.x, r2 + mad r0.xy, c43, r0.z, r0 + add oT1.xy, r0, c44 + max r1, r1, c45 + mul oD1, r1, c46 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.z + mov oT0.xy, v2 + +// approximately 40 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 156, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 56, 2, + 0, 0, 0, 2, 254, 255, + 12, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 49, 2, 0, 0, 12, 1, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 28, 1, + 0, 0, 0, 0, 0, 0, + 44, 1, 0, 0, 2, 0, + 45, 0, 1, 0, 182, 0, + 28, 1, 0, 0, 0, 0, + 0, 0, 55, 1, 0, 0, + 2, 0, 0, 0, 4, 0, + 2, 0, 68, 1, 0, 0, + 0, 0, 0, 0, 84, 1, + 0, 0, 2, 0, 46, 0, + 1, 0, 186, 0, 28, 1, + 0, 0, 0, 0, 0, 0, + 93, 1, 0, 0, 2, 0, + 16, 0, 1, 0, 66, 0, + 104, 1, 0, 0, 0, 0, + 0, 0, 120, 1, 0, 0, + 2, 0, 14, 0, 1, 0, + 58, 0, 28, 1, 0, 0, + 0, 0, 0, 0, 128, 1, + 0, 0, 2, 0, 17, 0, + 24, 0, 70, 0, 204, 1, + 0, 0, 0, 0, 0, 0, + 220, 1, 0, 0, 2, 0, + 12, 0, 1, 0, 50, 0, + 28, 1, 0, 0, 0, 0, + 0, 0, 227, 1, 0, 0, + 2, 0, 8, 0, 3, 0, + 34, 0, 240, 1, 0, 0, + 0, 0, 0, 0, 0, 2, + 0, 0, 1, 0, 0, 0, + 1, 0, 2, 0, 16, 2, + 0, 0, 0, 0, 0, 0, + 32, 2, 0, 0, 2, 0, + 13, 0, 1, 0, 54, 0, + 28, 1, 0, 0, 0, 0, + 0, 0, 42, 2, 0, 0, + 2, 0, 41, 0, 4, 0, + 166, 0, 68, 1, 0, 0, + 0, 0, 0, 0, 97, 109, + 98, 105, 101, 110, 116, 76, + 105, 103, 104, 116, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 99, 111, 108, 111, 114, 67, + 108, 97, 109, 112, 0, 99, + 111, 109, 98, 105, 110, 101, + 100, 77, 97, 116, 0, 171, + 3, 0, 3, 0, 4, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 101, 110, + 118, 67, 111, 108, 111, 114, + 0, 102, 105, 114, 115, 116, + 76, 105, 103, 104, 116, 0, + 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 111, + 103, 68, 97, 116, 97, 0, + 108, 105, 103, 104, 116, 115, + 0, 99, 111, 108, 111, 114, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 111, 115, 105, + 116, 105, 111, 110, 0, 100, + 105, 114, 101, 99, 116, 105, + 111, 110, 0, 171, 135, 1, + 0, 0, 144, 1, 0, 0, + 160, 1, 0, 0, 144, 1, + 0, 0, 169, 1, 0, 0, + 144, 1, 0, 0, 5, 0, + 0, 0, 1, 0, 12, 0, + 8, 0, 3, 0, 180, 1, + 0, 0, 109, 97, 116, 67, + 111, 108, 0, 110, 111, 114, + 109, 97, 108, 77, 97, 116, + 0, 171, 171, 171, 3, 0, + 3, 0, 3, 0, 3, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 110, 117, 109, 68, + 105, 114, 76, 105, 103, 104, + 116, 115, 0, 171, 171, 171, + 0, 0, 2, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 115, 117, + 114, 102, 80, 114, 111, 112, + 115, 0, 116, 101, 120, 77, + 97, 116, 0, 118, 115, 95, + 50, 95, 48, 0, 77, 105, + 99, 114, 111, 115, 111, 102, + 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, + 67, 111, 109, 112, 105, 108, + 101, 114, 32, 57, 46, 50, + 57, 46, 57, 53, 50, 46, + 51, 49, 49, 49, 0, 171, + 171, 171, 81, 0, 0, 5, + 4, 0, 15, 160, 0, 0, + 0, 0, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 3, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 2, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 3, 0, + 15, 144, 5, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 85, 144, 1, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 15, 128, 0, 0, 228, 160, + 0, 0, 0, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 15, 128, 2, 0, + 228, 160, 0, 0, 170, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 3, 0, 228, 160, 0, 0, + 255, 144, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 15, 192, 0, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 7, 128, 1, 0, 85, 144, + 9, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 7, 128, + 8, 0, 228, 160, 1, 0, + 0, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 7, 128, 10, 0, 228, 160, + 1, 0, 170, 144, 0, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 1, 128, 13, 0, + 0, 160, 4, 0, 0, 4, + 1, 0, 7, 128, 15, 0, + 228, 160, 1, 0, 0, 128, + 3, 0, 228, 144, 1, 0, + 0, 2, 2, 0, 7, 128, + 1, 0, 228, 128, 1, 0, + 0, 2, 1, 0, 8, 128, + 4, 0, 0, 160, 38, 0, + 0, 1, 0, 0, 228, 240, + 2, 0, 0, 3, 3, 0, + 1, 128, 1, 0, 255, 128, + 16, 0, 0, 160, 5, 0, + 0, 3, 3, 0, 1, 128, + 3, 0, 0, 128, 4, 0, + 85, 160, 46, 0, 0, 2, + 0, 0, 1, 176, 3, 0, + 0, 128, 8, 0, 0, 4, + 3, 0, 1, 128, 0, 0, + 228, 128, 19, 32, 228, 161, + 0, 0, 0, 176, 11, 0, + 0, 3, 3, 0, 1, 128, + 3, 0, 0, 128, 4, 0, + 0, 160, 5, 0, 0, 4, + 3, 0, 7, 128, 3, 0, + 0, 128, 17, 32, 228, 160, + 0, 0, 0, 176, 4, 0, + 0, 4, 2, 0, 7, 128, + 3, 0, 228, 128, 13, 0, + 170, 160, 2, 0, 228, 128, + 2, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 4, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 2, 0, 8, 128, 3, 0, + 255, 144, 11, 0, 0, 3, + 1, 0, 15, 128, 2, 0, + 228, 128, 4, 0, 0, 160, + 10, 0, 0, 3, 1, 0, + 15, 128, 1, 0, 228, 128, + 4, 0, 170, 160, 5, 0, + 0, 3, 0, 0, 15, 208, + 1, 0, 228, 128, 12, 0, + 228, 160, 5, 0, 0, 3, + 2, 0, 3, 128, 0, 0, + 85, 128, 42, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 3, 128, 41, 0, 228, 160, + 0, 0, 0, 128, 2, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 3, 128, 43, 0, + 228, 160, 0, 0, 170, 128, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 3, 224, + 0, 0, 228, 128, 44, 0, + 228, 160, 11, 0, 0, 3, + 1, 0, 15, 128, 1, 0, + 228, 128, 45, 0, 228, 160, + 5, 0, 0, 3, 1, 0, + 15, 208, 1, 0, 228, 128, + 46, 0, 228, 160, 2, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 4, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 2, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/matfx_env_tex_PS.h b/vendor/librw/src/d3d/shaders/matfx_env_tex_PS.h new file mode 100644 index 0000000..e2e7429 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/matfx_env_tex_PS.h @@ -0,0 +1,141 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /DTEX /Fh matfx_env_tex_PS.h matfx_env_PS.hlsl +// +// +// Parameters: +// +// sampler2D diffTex; +// sampler2D envTex; +// float4 fogColor; +// float4 fxparams; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// fxparams c1 1 +// diffTex s0 1 +// envTex s1 1 +// + + ps_2_0 + dcl t0.xyz + dcl t1.xy + dcl v0 + dcl v1.xyz + dcl_2d s0 + dcl_2d s1 + texld r0, t1, s1 + texld r1, t0, s0 + mul r2.xyz, v1, c1.x + mul r0.xyz, r0, r2 + mul r0.xyz, r0, t0.z + mul r2.w, r1.w, v0.w + mad r1.xyz, v0, r1, -c0 + mad r1.xyz, t0.z, r1, c0 + max r0.w, r2.w, c1.y + mul r0.xyz, r0.w, r0 + mad r2.xyz, r1, r2.w, r0 + mov oC0, r2 + +// approximately 12 instruction slots used (2 texture, 10 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 64, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 200, 0, + 0, 0, 0, 2, 255, 255, + 4, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 193, 0, 0, 0, 108, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 2, 0, 116, 0, + 0, 0, 0, 0, 0, 0, + 132, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 6, 0, + 140, 0, 0, 0, 0, 0, + 0, 0, 156, 0, 0, 0, + 2, 0, 0, 0, 1, 0, + 2, 0, 168, 0, 0, 0, + 0, 0, 0, 0, 184, 0, + 0, 0, 2, 0, 1, 0, + 1, 0, 6, 0, 168, 0, + 0, 0, 0, 0, 0, 0, + 100, 105, 102, 102, 84, 101, + 120, 0, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 101, 110, 118, 84, 101, 120, + 0, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 67, 111, 108, + 111, 114, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 120, + 112, 97, 114, 97, 109, 115, + 0, 112, 115, 95, 50, 95, + 48, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 57, 46, 50, 57, 46, + 57, 53, 50, 46, 51, 49, + 49, 49, 0, 171, 171, 171, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 7, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 1, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 0, 0, + 0, 128, 1, 0, 7, 144, + 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, + 31, 0, 0, 2, 0, 0, + 0, 144, 1, 8, 15, 160, + 66, 0, 0, 3, 0, 0, + 15, 128, 1, 0, 228, 176, + 1, 8, 228, 160, 66, 0, + 0, 3, 1, 0, 15, 128, + 0, 0, 228, 176, 0, 8, + 228, 160, 5, 0, 0, 3, + 2, 0, 7, 128, 1, 0, + 228, 144, 1, 0, 0, 160, + 5, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 228, 128, + 2, 0, 228, 128, 5, 0, + 0, 3, 0, 0, 7, 128, + 0, 0, 228, 128, 0, 0, + 170, 176, 5, 0, 0, 3, + 2, 0, 8, 128, 1, 0, + 255, 128, 0, 0, 255, 144, + 4, 0, 0, 4, 1, 0, + 7, 128, 0, 0, 228, 144, + 1, 0, 228, 128, 0, 0, + 228, 161, 4, 0, 0, 4, + 1, 0, 7, 128, 0, 0, + 170, 176, 1, 0, 228, 128, + 0, 0, 228, 160, 11, 0, + 0, 3, 0, 0, 8, 128, + 2, 0, 255, 128, 1, 0, + 85, 160, 5, 0, 0, 3, + 0, 0, 7, 128, 0, 0, + 255, 128, 0, 0, 228, 128, + 4, 0, 0, 4, 2, 0, + 7, 128, 1, 0, 228, 128, + 2, 0, 255, 128, 0, 0, + 228, 128, 1, 0, 0, 2, + 0, 8, 15, 128, 2, 0, + 228, 128, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/skin_VS.hlsl b/vendor/librw/src/d3d/shaders/skin_VS.hlsl new file mode 100644 index 0000000..bede50f --- /dev/null +++ b/vendor/librw/src/d3d/shaders/skin_VS.hlsl @@ -0,0 +1,63 @@ +#include "standardConstants.h" + +float4x3 boneMatrices[64] : register(c41); + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; + float4 Weights : BLENDWEIGHT; + int4 Indices : BLENDINDICES; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float4 Color : COLOR0; +}; + + +VS_out main(in VS_in input) +{ + VS_out output; + + int j; + float3 SkinVertex = float3(0.0, 0.0, 0.0); + float3 SkinNormal = float3(0.0, 0.0, 0.0); + for(j = 0; j < 4; j++){ + SkinVertex += mul(input.Position, boneMatrices[input.Indices[j]]).xyz * input.Weights[j]; + SkinNormal += mul(input.Normal, (float3x3)boneMatrices[input.Indices[j]]).xyz * input.Weights[j]; + } + + output.Position = mul(combinedMat, float4(SkinVertex, 1.0)); + float3 Vertex = mul(worldMat, float4(SkinVertex, 1.0)).xyz; + float3 Normal = mul(normalMat, SkinNormal); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +#endif +#ifdef POINTLIGHTS + for(i = 0; i < numPointLights; i++) + output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +#endif +#ifdef SPOTLIGHTS + for(i = 0; i < numSpotLights; i++) + output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +#endif + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/vendor/librw/src/d3d/shaders/skin_all_VS.h b/vendor/librw/src/d3d/shaders/skin_all_VS.h new file mode 100644 index 0000000..67f89f3 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/skin_all_VS.h @@ -0,0 +1,664 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh +// skin_all_VS.h skin_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x3 boneMatrices[64]; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// int numPointLights; +// int numSpotLights; +// float4 surfProps; +// float4x4 worldMat; +// +// +// Registers: +// +// Name Reg Size +// -------------- ----- ---- +// numDirLights i0 1 +// numPointLights i1 1 +// numSpotLights i2 1 +// combinedMat c0 4 +// worldMat c4 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// boneMatrices c41 192 +// + + vs_2_0 + def c11, 3, 0, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + dcl_blendweight v4 + dcl_blendindices v5 + mul r0, v5, c11.x + mova a0.x, r0.x + dp3 r1.z, v1, c43[a0.x] + dp4 r2.x, v0, c41[a0.x] + dp4 r2.y, v0, c42[a0.x] + dp4 r2.z, v0, c43[a0.x] + dp3 r1.x, v1, c41[a0.x] + dp3 r1.y, v1, c42[a0.x] + mova a0.x, r0.y + dp3 r3.z, v1, c43[a0.x] + dp4 r4.x, v0, c41[a0.x] + dp4 r4.y, v0, c42[a0.x] + dp4 r4.z, v0, c43[a0.x] + mul r4.xyz, r4, v4.y + mad r2.xyz, r2, v4.x, r4 + dp3 r3.x, v1, c41[a0.x] + dp3 r3.y, v1, c42[a0.x] + mul r3.xyz, r3, v4.y + mad r1.xyz, r1, v4.x, r3 + mova a0.x, r0.z + dp3 r0.z, v1, c43[a0.x] + dp4 r3.x, v0, c41[a0.x] + dp4 r3.y, v0, c42[a0.x] + dp4 r3.z, v0, c43[a0.x] + mad r2.xyz, r3, v4.z, r2 + dp3 r0.x, v1, c41[a0.x] + dp3 r0.y, v1, c42[a0.x] + mad r0.xyz, r0, v4.z, r1 + mova a0.x, r0.w + dp3 r1.z, v1, c43[a0.x] + dp4 r3.x, v0, c41[a0.x] + dp4 r3.y, v0, c42[a0.x] + dp4 r3.z, v0, c43[a0.x] + mad r2.xyz, r3, v4.w, r2 + dp3 r1.x, v1, c41[a0.x] + dp3 r1.y, v1, c42[a0.x] + mad r0.xyz, r1, v4.w, r0 + mul r1.xyz, r2.y, c5 + mad r1.xyz, c4, r2.x, r1 + mad r1.xyz, c6, r2.z, r1 + add r1.xyz, r1, c7 + mul r3.xyz, r0.y, c9 + mad r0.xyw, c8.xyzz, r0.x, r3.xyzz + mad r0.xyz, c10, r0.z, r0.xyww + mov r3.x, c13.x + mad r3.xyz, c15, r3.x, v3 + mov r4.xyz, r3 + mov r0.w, c11.y + rep i0 + add r1.w, r0.w, c16.x + mul r1.w, r1.w, c11.x + mova a0.x, r1.w + dp3 r1.w, r0, -c19[a0.x] + max r1.w, r1.w, c11.y + mul r5.xyz, r1.w, c17[a0.x] + mad r4.xyz, r5, c13.z, r4 + add r0.w, r0.w, c11.z + endrep + mov r3.xyz, r4 + mov r0.w, c11.y + rep i1 + add r1.w, r0.w, c16.y + mul r1.w, r1.w, c11.x + mova a0.x, r1.w + add r5.xyz, r1, -c18[a0.x] + dp3 r1.w, r5, r5 + rsq r1.w, r1.w + mul r5.xyz, r1.w, r5 + dp3 r2.w, r0, -r5 + max r2.w, r2.w, c11.y + mul r5.xyz, r2.w, c17[a0.x] + rcp r1.w, r1.w + rcp r2.w, c17[a0.x].w + mad r1.w, r1.w, -r2.w, c11.z + max r1.w, r1.w, c11.y + mul r5.xyz, r1.w, r5 + mad r3.xyz, r5, c13.z, r3 + add r0.w, r0.w, c11.z + endrep + mov r4.xyz, r3 + mov r0.w, c11.y + rep i2 + add r1.w, r0.w, c16.z + mul r1.w, r1.w, c11.x + mova a0.x, r1.w + add r5.xyz, r1, -c18[a0.x] + dp3 r1.w, r5, r5 + rsq r1.w, r1.w + mul r5.xyz, r1.w, r5 + dp3 r2.w, r0, -r5 + dp3 r3.w, r5, c19[a0.x] + max r2.w, r2.w, c11.y + add r3.w, r3.w, c18[a0.x].w + mov r5.z, c11.z + add r5.x, r5.z, c18[a0.x].w + rcp r5.x, r5.x + mul r3.w, r3.w, r5.x + slt r5.x, r3.w, c11.y + mad r2.w, r5.x, -r2.w, r2.w + max r3.w, r3.w, c19[a0.x].w + mul r2.w, r2.w, r3.w + mul r5.xyz, r2.w, c17[a0.x] + rcp r1.w, r1.w + rcp r2.w, c17[a0.x].w + mad r1.w, r1.w, -r2.w, c11.z + max r1.w, r1.w, c11.y + mul r5.xyz, r1.w, r5 + mad r4.xyz, r5, c13.z, r4 + add r0.w, r0.w, c11.z + endrep + mov r4.w, v3.w + max r0, r4, c11.y + min r0, r0, c11.z + mul oD0, r0, c12 + mul r0, r2.y, c1 + mad r0, c0, r2.x, r0 + mad r0, c2, r2.z, r0 + add r0, r0, c3 + mov oPos, r0 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c11.z + mov oT0.xy, v2 + +// approximately 133 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 171, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 119, 2, + 0, 0, 0, 2, 254, 255, + 13, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 112, 2, 0, 0, 32, 1, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 48, 1, + 0, 0, 0, 0, 0, 0, + 64, 1, 0, 0, 2, 0, + 41, 0, 192, 0, 166, 0, + 80, 1, 0, 0, 0, 0, + 0, 0, 96, 1, 0, 0, + 2, 0, 0, 0, 4, 0, + 2, 0, 108, 1, 0, 0, + 0, 0, 0, 0, 124, 1, + 0, 0, 2, 0, 16, 0, + 1, 0, 66, 0, 136, 1, + 0, 0, 0, 0, 0, 0, + 152, 1, 0, 0, 2, 0, + 14, 0, 1, 0, 58, 0, + 48, 1, 0, 0, 0, 0, + 0, 0, 160, 1, 0, 0, + 2, 0, 17, 0, 24, 0, + 70, 0, 236, 1, 0, 0, + 0, 0, 0, 0, 252, 1, + 0, 0, 2, 0, 12, 0, + 1, 0, 50, 0, 48, 1, + 0, 0, 0, 0, 0, 0, + 3, 2, 0, 0, 2, 0, + 8, 0, 3, 0, 34, 0, + 16, 2, 0, 0, 0, 0, + 0, 0, 32, 2, 0, 0, + 1, 0, 0, 0, 1, 0, + 2, 0, 48, 2, 0, 0, + 0, 0, 0, 0, 64, 2, + 0, 0, 1, 0, 1, 0, + 1, 0, 6, 0, 48, 2, + 0, 0, 0, 0, 0, 0, + 79, 2, 0, 0, 1, 0, + 2, 0, 1, 0, 10, 0, + 48, 2, 0, 0, 0, 0, + 0, 0, 93, 2, 0, 0, + 2, 0, 13, 0, 1, 0, + 54, 0, 48, 1, 0, 0, + 0, 0, 0, 0, 103, 2, + 0, 0, 2, 0, 4, 0, + 4, 0, 18, 0, 108, 1, + 0, 0, 0, 0, 0, 0, + 97, 109, 98, 105, 101, 110, + 116, 76, 105, 103, 104, 116, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 98, 111, 110, 101, + 77, 97, 116, 114, 105, 99, + 101, 115, 0, 171, 171, 171, + 3, 0, 3, 0, 4, 0, + 3, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 99, 111, + 109, 98, 105, 110, 101, 100, + 77, 97, 116, 0, 3, 0, + 3, 0, 4, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 105, 114, 115, + 116, 76, 105, 103, 104, 116, + 0, 171, 1, 0, 2, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 68, 97, 116, + 97, 0, 108, 105, 103, 104, + 116, 115, 0, 99, 111, 108, + 111, 114, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 111, + 115, 105, 116, 105, 111, 110, + 0, 100, 105, 114, 101, 99, + 116, 105, 111, 110, 0, 171, + 167, 1, 0, 0, 176, 1, + 0, 0, 192, 1, 0, 0, + 176, 1, 0, 0, 201, 1, + 0, 0, 176, 1, 0, 0, + 5, 0, 0, 0, 1, 0, + 12, 0, 8, 0, 3, 0, + 212, 1, 0, 0, 109, 97, + 116, 67, 111, 108, 0, 110, + 111, 114, 109, 97, 108, 77, + 97, 116, 0, 171, 171, 171, + 3, 0, 3, 0, 3, 0, + 3, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 110, 117, + 109, 68, 105, 114, 76, 105, + 103, 104, 116, 115, 0, 171, + 171, 171, 0, 0, 2, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 110, 117, 109, 80, 111, 105, + 110, 116, 76, 105, 103, 104, + 116, 115, 0, 110, 117, 109, + 83, 112, 111, 116, 76, 105, + 103, 104, 116, 115, 0, 115, + 117, 114, 102, 80, 114, 111, + 112, 115, 0, 119, 111, 114, + 108, 100, 77, 97, 116, 0, + 118, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 57, 46, 57, + 53, 50, 46, 51, 49, 49, + 49, 0, 81, 0, 0, 5, + 11, 0, 15, 160, 0, 0, + 64, 64, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 3, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 2, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 3, 0, + 15, 144, 31, 0, 0, 2, + 1, 0, 0, 128, 4, 0, + 15, 144, 31, 0, 0, 2, + 2, 0, 0, 128, 5, 0, + 15, 144, 5, 0, 0, 3, + 0, 0, 15, 128, 5, 0, + 228, 144, 11, 0, 0, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 0, 0, 0, 128, + 8, 0, 0, 4, 1, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 2, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 2, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 2, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 8, 0, 0, 4, + 1, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 8, 0, + 0, 4, 1, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 46, 0, 0, 2, 0, 0, + 1, 176, 0, 0, 85, 128, + 8, 0, 0, 4, 3, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 4, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 4, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 4, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 5, 0, 0, 3, + 4, 0, 7, 128, 4, 0, + 228, 128, 4, 0, 85, 144, + 4, 0, 0, 4, 2, 0, + 7, 128, 2, 0, 228, 128, + 4, 0, 0, 144, 4, 0, + 228, 128, 8, 0, 0, 4, + 3, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 8, 0, + 0, 4, 3, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 5, 0, 0, 3, 3, 0, + 7, 128, 3, 0, 228, 128, + 4, 0, 85, 144, 4, 0, + 0, 4, 1, 0, 7, 128, + 1, 0, 228, 128, 4, 0, + 0, 144, 3, 0, 228, 128, + 46, 0, 0, 2, 0, 0, + 1, 176, 0, 0, 170, 128, + 8, 0, 0, 4, 0, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 3, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 3, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 3, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 2, 0, 7, 128, 3, 0, + 228, 128, 4, 0, 170, 144, + 2, 0, 228, 128, 8, 0, + 0, 4, 0, 0, 1, 128, + 1, 0, 228, 144, 41, 32, + 228, 160, 0, 0, 0, 176, + 8, 0, 0, 4, 0, 0, + 2, 128, 1, 0, 228, 144, + 42, 32, 228, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 0, 0, 7, 128, 0, 0, + 228, 128, 4, 0, 170, 144, + 1, 0, 228, 128, 46, 0, + 0, 2, 0, 0, 1, 176, + 0, 0, 255, 128, 8, 0, + 0, 4, 1, 0, 4, 128, + 1, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 3, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 3, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 3, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 2, 0, + 7, 128, 3, 0, 228, 128, + 4, 0, 255, 144, 2, 0, + 228, 128, 8, 0, 0, 4, + 1, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 8, 0, + 0, 4, 1, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 0, 0, + 7, 128, 1, 0, 228, 128, + 4, 0, 255, 144, 0, 0, + 228, 128, 5, 0, 0, 3, + 1, 0, 7, 128, 2, 0, + 85, 128, 5, 0, 228, 160, + 4, 0, 0, 4, 1, 0, + 7, 128, 4, 0, 228, 160, + 2, 0, 0, 128, 1, 0, + 228, 128, 4, 0, 0, 4, + 1, 0, 7, 128, 6, 0, + 228, 160, 2, 0, 170, 128, + 1, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 7, 128, + 1, 0, 228, 128, 7, 0, + 228, 160, 5, 0, 0, 3, + 3, 0, 7, 128, 0, 0, + 85, 128, 9, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 11, 128, 8, 0, 164, 160, + 0, 0, 0, 128, 3, 0, + 164, 128, 4, 0, 0, 4, + 0, 0, 7, 128, 10, 0, + 228, 160, 0, 0, 170, 128, + 0, 0, 244, 128, 1, 0, + 0, 2, 3, 0, 1, 128, + 13, 0, 0, 160, 4, 0, + 0, 4, 3, 0, 7, 128, + 15, 0, 228, 160, 3, 0, + 0, 128, 3, 0, 228, 144, + 1, 0, 0, 2, 4, 0, + 7, 128, 3, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 8, 128, 11, 0, 85, 160, + 38, 0, 0, 1, 0, 0, + 228, 240, 2, 0, 0, 3, + 1, 0, 8, 128, 0, 0, + 255, 128, 16, 0, 0, 160, + 5, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 0, 160, 46, 0, + 0, 2, 0, 0, 1, 176, + 1, 0, 255, 128, 8, 0, + 0, 4, 1, 0, 8, 128, + 0, 0, 228, 128, 19, 32, + 228, 161, 0, 0, 0, 176, + 11, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 85, 160, 5, 0, + 0, 4, 5, 0, 7, 128, + 1, 0, 255, 128, 17, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 4, 0, + 7, 128, 5, 0, 228, 128, + 13, 0, 170, 160, 4, 0, + 228, 128, 2, 0, 0, 3, + 0, 0, 8, 128, 0, 0, + 255, 128, 11, 0, 170, 160, + 39, 0, 0, 0, 1, 0, + 0, 2, 3, 0, 7, 128, + 4, 0, 228, 128, 1, 0, + 0, 2, 0, 0, 8, 128, + 11, 0, 85, 160, 38, 0, + 0, 1, 1, 0, 228, 240, + 2, 0, 0, 3, 1, 0, + 8, 128, 0, 0, 255, 128, + 16, 0, 85, 160, 5, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 0, 160, 46, 0, 0, 2, + 0, 0, 1, 176, 1, 0, + 255, 128, 2, 0, 0, 4, + 5, 0, 7, 128, 1, 0, + 228, 128, 18, 32, 228, 161, + 0, 0, 0, 176, 8, 0, + 0, 3, 1, 0, 8, 128, + 5, 0, 228, 128, 5, 0, + 228, 128, 7, 0, 0, 2, + 1, 0, 8, 128, 1, 0, + 255, 128, 5, 0, 0, 3, + 5, 0, 7, 128, 1, 0, + 255, 128, 5, 0, 228, 128, + 8, 0, 0, 3, 2, 0, + 8, 128, 0, 0, 228, 128, + 5, 0, 228, 129, 11, 0, + 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 11, 0, + 85, 160, 5, 0, 0, 4, + 5, 0, 7, 128, 2, 0, + 255, 128, 17, 32, 228, 160, + 0, 0, 0, 176, 6, 0, + 0, 2, 1, 0, 8, 128, + 1, 0, 255, 128, 6, 0, + 0, 3, 2, 0, 8, 128, + 17, 32, 255, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 1, 0, 8, 128, 1, 0, + 255, 128, 2, 0, 255, 129, + 11, 0, 170, 160, 11, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 85, 160, 5, 0, 0, 3, + 5, 0, 7, 128, 1, 0, + 255, 128, 5, 0, 228, 128, + 4, 0, 0, 4, 3, 0, + 7, 128, 5, 0, 228, 128, + 13, 0, 170, 160, 3, 0, + 228, 128, 2, 0, 0, 3, + 0, 0, 8, 128, 0, 0, + 255, 128, 11, 0, 170, 160, + 39, 0, 0, 0, 1, 0, + 0, 2, 4, 0, 7, 128, + 3, 0, 228, 128, 1, 0, + 0, 2, 0, 0, 8, 128, + 11, 0, 85, 160, 38, 0, + 0, 1, 2, 0, 228, 240, + 2, 0, 0, 3, 1, 0, + 8, 128, 0, 0, 255, 128, + 16, 0, 170, 160, 5, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 0, 160, 46, 0, 0, 2, + 0, 0, 1, 176, 1, 0, + 255, 128, 2, 0, 0, 4, + 5, 0, 7, 128, 1, 0, + 228, 128, 18, 32, 228, 161, + 0, 0, 0, 176, 8, 0, + 0, 3, 1, 0, 8, 128, + 5, 0, 228, 128, 5, 0, + 228, 128, 7, 0, 0, 2, + 1, 0, 8, 128, 1, 0, + 255, 128, 5, 0, 0, 3, + 5, 0, 7, 128, 1, 0, + 255, 128, 5, 0, 228, 128, + 8, 0, 0, 3, 2, 0, + 8, 128, 0, 0, 228, 128, + 5, 0, 228, 129, 8, 0, + 0, 4, 3, 0, 8, 128, + 5, 0, 228, 128, 19, 32, + 228, 160, 0, 0, 0, 176, + 11, 0, 0, 3, 2, 0, + 8, 128, 2, 0, 255, 128, + 11, 0, 85, 160, 2, 0, + 0, 4, 3, 0, 8, 128, + 3, 0, 255, 128, 18, 32, + 255, 160, 0, 0, 0, 176, + 1, 0, 0, 2, 5, 0, + 4, 128, 11, 0, 170, 160, + 2, 0, 0, 4, 5, 0, + 1, 128, 5, 0, 170, 128, + 18, 32, 255, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 5, 0, 1, 128, 5, 0, + 0, 128, 5, 0, 0, 3, + 3, 0, 8, 128, 3, 0, + 255, 128, 5, 0, 0, 128, + 12, 0, 0, 3, 5, 0, + 1, 128, 3, 0, 255, 128, + 11, 0, 85, 160, 4, 0, + 0, 4, 2, 0, 8, 128, + 5, 0, 0, 128, 2, 0, + 255, 129, 2, 0, 255, 128, + 11, 0, 0, 4, 3, 0, + 8, 128, 3, 0, 255, 128, + 19, 32, 255, 160, 0, 0, + 0, 176, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 3, 0, 255, 128, + 5, 0, 0, 4, 5, 0, + 7, 128, 2, 0, 255, 128, + 17, 32, 228, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 1, 0, 8, 128, 1, 0, + 255, 128, 6, 0, 0, 3, + 2, 0, 8, 128, 17, 32, + 255, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 1, 0, + 8, 128, 1, 0, 255, 128, + 2, 0, 255, 129, 11, 0, + 170, 160, 11, 0, 0, 3, + 1, 0, 8, 128, 1, 0, + 255, 128, 11, 0, 85, 160, + 5, 0, 0, 3, 5, 0, + 7, 128, 1, 0, 255, 128, + 5, 0, 228, 128, 4, 0, + 0, 4, 4, 0, 7, 128, + 5, 0, 228, 128, 13, 0, + 170, 160, 4, 0, 228, 128, + 2, 0, 0, 3, 0, 0, + 8, 128, 0, 0, 255, 128, + 11, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 4, 0, 8, 128, 3, 0, + 255, 144, 11, 0, 0, 3, + 0, 0, 15, 128, 4, 0, + 228, 128, 11, 0, 85, 160, + 10, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 11, 0, 170, 160, 5, 0, + 0, 3, 0, 0, 15, 208, + 0, 0, 228, 128, 12, 0, + 228, 160, 5, 0, 0, 3, + 0, 0, 15, 128, 2, 0, + 85, 128, 1, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 15, 128, 0, 0, 228, 160, + 2, 0, 0, 128, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 15, 128, 2, 0, + 228, 160, 2, 0, 170, 128, + 0, 0, 228, 128, 2, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 3, 0, + 228, 160, 1, 0, 0, 2, + 0, 0, 15, 192, 0, 0, + 228, 128, 2, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 255, 128, 14, 0, 85, 161, + 5, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 170, 160, 11, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 0, 128, 14, 0, + 255, 160, 10, 0, 0, 3, + 0, 0, 4, 224, 0, 0, + 0, 128, 11, 0, 170, 160, + 1, 0, 0, 2, 0, 0, + 3, 224, 2, 0, 228, 144, + 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/skin_amb_VS.h b/vendor/librw/src/d3d/shaders/skin_amb_VS.h new file mode 100644 index 0000000..e0a5548 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/skin_amb_VS.h @@ -0,0 +1,253 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /Fh skin_amb_VS.h skin_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x3 boneMatrices[64]; +// float4x4 combinedMat; +// float4 fogData; +// float4 matCol; +// float4 surfProps; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// combinedMat c0 4 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// boneMatrices c41 192 +// + + vs_2_0 + def c4, 3, 0, 1, 0 + dcl_position v0 + dcl_texcoord v1 + dcl_color v2 + dcl_blendweight v3 + dcl_blendindices v4 + mov r0.xyz, c15 + mad r0.xyz, r0, c13.x, v2 + mov r0.w, v2.w + max r0, r0, c4.y + min r0, r0, c4.z + mul oD0, r0, c12 + mul r0, v4, c4.x + mova a0.x, r0.y + dp4 r1.x, v0, c41[a0.x] + dp4 r1.y, v0, c42[a0.x] + dp4 r1.z, v0, c43[a0.x] + mul r1.xyz, r1, v3.y + mova a0.x, r0.x + dp4 r2.x, v0, c41[a0.x] + dp4 r2.y, v0, c42[a0.x] + dp4 r2.z, v0, c43[a0.x] + mad r1.xyz, r2, v3.x, r1 + mova a0.xy, r0.zwzw + dp4 r0.x, v0, c41[a0.x] + dp4 r0.y, v0, c42[a0.x] + dp4 r0.z, v0, c43[a0.x] + mad r0.xyz, r0, v3.z, r1 + dp4 r1.x, v0, c41[a0.y] + dp4 r1.y, v0, c42[a0.y] + dp4 r1.z, v0, c43[a0.y] + mad r0.xyz, r1, v3.w, r0 + mul r1, r0.y, c1 + mad r1, c0, r0.x, r1 + mad r0, c2, r0.z, r1 + add r0, r0, c3 + add r1.x, r0.w, -c14.y + mov oPos, r0 + mul r0.x, r1.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.z + mov oT0.xy, v1 + +// approximately 36 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 82, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 16, 1, + 0, 0, 0, 2, 254, 255, + 6, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 9, 1, 0, 0, 148, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 164, 0, + 0, 0, 0, 0, 0, 0, + 180, 0, 0, 0, 2, 0, + 41, 0, 192, 0, 166, 0, + 196, 0, 0, 0, 0, 0, + 0, 0, 212, 0, 0, 0, + 2, 0, 0, 0, 4, 0, + 2, 0, 224, 0, 0, 0, + 0, 0, 0, 0, 240, 0, + 0, 0, 2, 0, 14, 0, + 1, 0, 58, 0, 164, 0, + 0, 0, 0, 0, 0, 0, + 248, 0, 0, 0, 2, 0, + 12, 0, 1, 0, 50, 0, + 164, 0, 0, 0, 0, 0, + 0, 0, 255, 0, 0, 0, + 2, 0, 13, 0, 1, 0, + 54, 0, 164, 0, 0, 0, + 0, 0, 0, 0, 97, 109, + 98, 105, 101, 110, 116, 76, + 105, 103, 104, 116, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 98, 111, 110, 101, 77, 97, + 116, 114, 105, 99, 101, 115, + 0, 171, 171, 171, 3, 0, + 3, 0, 4, 0, 3, 0, + 64, 0, 0, 0, 0, 0, + 0, 0, 99, 111, 109, 98, + 105, 110, 101, 100, 77, 97, + 116, 0, 3, 0, 3, 0, + 4, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 68, 97, 116, + 97, 0, 109, 97, 116, 67, + 111, 108, 0, 115, 117, 114, + 102, 80, 114, 111, 112, 115, + 0, 118, 115, 95, 50, 95, + 48, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 57, 46, 50, 57, 46, + 57, 53, 50, 46, 51, 49, + 49, 49, 0, 171, 171, 171, + 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 64, 64, + 0, 0, 0, 0, 0, 0, + 128, 63, 0, 0, 0, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 5, 0, + 0, 128, 1, 0, 15, 144, + 31, 0, 0, 2, 10, 0, + 0, 128, 2, 0, 15, 144, + 31, 0, 0, 2, 1, 0, + 0, 128, 3, 0, 15, 144, + 31, 0, 0, 2, 2, 0, + 0, 128, 4, 0, 15, 144, + 1, 0, 0, 2, 0, 0, + 7, 128, 15, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 0, 0, 228, 128, + 13, 0, 0, 160, 2, 0, + 228, 144, 1, 0, 0, 2, + 0, 0, 8, 128, 2, 0, + 255, 144, 11, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 4, 0, 85, 160, + 10, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 4, 0, 170, 160, 5, 0, + 0, 3, 0, 0, 15, 208, + 0, 0, 228, 128, 12, 0, + 228, 160, 5, 0, 0, 3, + 0, 0, 15, 128, 4, 0, + 228, 144, 4, 0, 0, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 0, 0, 85, 128, + 9, 0, 0, 4, 1, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 1, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 1, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 0, 176, + 5, 0, 0, 3, 1, 0, + 7, 128, 1, 0, 228, 128, + 3, 0, 85, 144, 46, 0, + 0, 2, 0, 0, 1, 176, + 0, 0, 0, 128, 9, 0, + 0, 4, 2, 0, 1, 128, + 0, 0, 228, 144, 41, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 2, 0, + 2, 128, 0, 0, 228, 144, + 42, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 2, 0, 4, 128, 0, 0, + 228, 144, 43, 32, 228, 160, + 0, 0, 0, 176, 4, 0, + 0, 4, 1, 0, 7, 128, + 2, 0, 228, 128, 3, 0, + 0, 144, 1, 0, 228, 128, + 46, 0, 0, 2, 0, 0, + 3, 176, 0, 0, 238, 128, + 9, 0, 0, 4, 0, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 0, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 0, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 0, 0, + 7, 128, 0, 0, 228, 128, + 3, 0, 170, 144, 1, 0, + 228, 128, 9, 0, 0, 4, + 1, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 85, 176, 9, 0, + 0, 4, 1, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 85, 176, + 9, 0, 0, 4, 1, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 85, 176, 4, 0, 0, 4, + 0, 0, 7, 128, 1, 0, + 228, 128, 3, 0, 255, 144, + 0, 0, 228, 128, 5, 0, + 0, 3, 1, 0, 15, 128, + 0, 0, 85, 128, 1, 0, + 228, 160, 4, 0, 0, 4, + 1, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 128, + 1, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 128, 1, 0, 228, 128, + 2, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 3, 0, 228, 160, 2, 0, + 0, 3, 1, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 1, 0, 0, 2, + 0, 0, 15, 192, 0, 0, + 228, 128, 5, 0, 0, 3, + 0, 0, 1, 128, 1, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 4, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 1, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/skin_amb_dir_VS.h b/vendor/librw/src/d3d/shaders/skin_amb_dir_VS.h new file mode 100644 index 0000000..7949749 --- /dev/null +++ b/vendor/librw/src/d3d/shaders/skin_amb_dir_VS.h @@ -0,0 +1,503 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /Fh skin_amb_dir_VS.h skin_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x3 boneMatrices[64]; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// float4 surfProps; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// numDirLights i0 1 +// combinedMat c0 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// boneMatrices c41 192 +// + + vs_2_0 + def c4, 3, 0, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + dcl_blendweight v4 + dcl_blendindices v5 + mul r0, v5, c4.x + mova a0.w, r0.x + dp3 r1.z, v1, c43[a0.w] + mova a0.w, r0.x + dp3 r1.x, v1, c41[a0.w] + mova a0.w, r0.x + dp3 r1.y, v1, c42[a0.w] + mova a0.w, r0.y + dp3 r2.z, v1, c43[a0.w] + mova a0.w, r0.y + dp3 r2.x, v1, c41[a0.w] + mova a0.w, r0.y + dp3 r2.y, v1, c42[a0.w] + mul r2.xyz, r2, v4.y + mad r1.xyz, r1, v4.x, r2 + mova a0.w, r0.z + dp3 r2.z, v1, c43[a0.w] + mova a0.w, r0.z + dp3 r2.x, v1, c41[a0.w] + mova a0.w, r0.z + dp3 r2.y, v1, c42[a0.w] + mad r1.xyz, r2, v4.z, r1 + mova a0.w, r0.w + dp3 r2.z, v1, c43[a0.w] + mova a0.w, r0.w + dp3 r2.x, v1, c41[a0.w] + mova a0.w, r0.w + dp3 r2.y, v1, c42[a0.w] + mad r1.xyz, r2, v4.w, r1 + mul r2.xyz, r1.y, c9 + mad r1.xyw, c8.xyzz, r1.x, r2.xyzz + mad r1.xyz, c10, r1.z, r1.xyww + mov r2.x, c13.x + mad r2.xyz, c15, r2.x, v3 + mov r3.xyz, r2 + mov r1.w, c4.y + rep i0 + add r2.w, r1.w, c16.x + mul r2.w, r2.w, c4.x + mova a0.w, r2.w + dp3 r4.x, r1, -c19[a0.w] + max r4.x, r4.x, c4.y + mova a0.w, r2.w + mul r4.xyz, r4.x, c17[a0.w] + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c4.z + endrep + mov r3.w, v3.w + max r1, r3, c4.y + min r1, r1, c4.z + mul oD0, r1, c12 + mova a0.w, r0.y + dp4 r1.x, v0, c41[a0.w] + mova a0.w, r0.y + dp4 r1.y, v0, c42[a0.w] + mova a0.w, r0.y + dp4 r1.z, v0, c43[a0.w] + mul r1.xyz, r1, v4.y + mova a0.w, r0.x + dp4 r2.x, v0, c41[a0.w] + mova a0.w, r0.x + dp4 r2.y, v0, c42[a0.w] + mova a0.w, r0.x + dp4 r2.z, v0, c43[a0.w] + mad r1.xyz, r2, v4.x, r1 + mova a0.w, r0.z + dp4 r2.x, v0, c41[a0.w] + mova a0.w, r0.z + dp4 r2.y, v0, c42[a0.w] + mova a0.w, r0.z + dp4 r2.z, v0, c43[a0.w] + mad r0.xyz, r2, v4.z, r1 + mova a0.w, r0.w + dp4 r1.x, v0, c41[a0.w] + mova a0.w, r0.w + dp4 r1.y, v0, c42[a0.w] + mova a0.w, r0.w + dp4 r1.z, v0, c43[a0.w] + mad r0.xyz, r1, v4.w, r0 + mul r1, r0.y, c1 + mad r1, c0, r0.x, r1 + mad r0, c2, r0.z, r1 + add r0, r0, c3 + mov oPos, r0 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.z + mov oT0.xy, v2 + +// approximately 92 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 147, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 21, 2, + 0, 0, 0, 2, 254, 255, + 10, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 14, 2, 0, 0, 228, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 244, 0, + 0, 0, 0, 0, 0, 0, + 4, 1, 0, 0, 2, 0, + 41, 0, 192, 0, 166, 0, + 20, 1, 0, 0, 0, 0, + 0, 0, 36, 1, 0, 0, + 2, 0, 0, 0, 4, 0, + 2, 0, 48, 1, 0, 0, + 0, 0, 0, 0, 64, 1, + 0, 0, 2, 0, 16, 0, + 1, 0, 66, 0, 76, 1, + 0, 0, 0, 0, 0, 0, + 92, 1, 0, 0, 2, 0, + 14, 0, 1, 0, 58, 0, + 244, 0, 0, 0, 0, 0, + 0, 0, 100, 1, 0, 0, + 2, 0, 17, 0, 24, 0, + 70, 0, 176, 1, 0, 0, + 0, 0, 0, 0, 192, 1, + 0, 0, 2, 0, 12, 0, + 1, 0, 50, 0, 244, 0, + 0, 0, 0, 0, 0, 0, + 199, 1, 0, 0, 2, 0, + 8, 0, 3, 0, 34, 0, + 212, 1, 0, 0, 0, 0, + 0, 0, 228, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 2, 0, 244, 1, 0, 0, + 0, 0, 0, 0, 4, 2, + 0, 0, 2, 0, 13, 0, + 1, 0, 54, 0, 244, 0, + 0, 0, 0, 0, 0, 0, + 97, 109, 98, 105, 101, 110, + 116, 76, 105, 103, 104, 116, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 98, 111, 110, 101, + 77, 97, 116, 114, 105, 99, + 101, 115, 0, 171, 171, 171, + 3, 0, 3, 0, 4, 0, + 3, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 99, 111, + 109, 98, 105, 110, 101, 100, + 77, 97, 116, 0, 3, 0, + 3, 0, 4, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 105, 114, 115, + 116, 76, 105, 103, 104, 116, + 0, 171, 1, 0, 2, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 68, 97, 116, + 97, 0, 108, 105, 103, 104, + 116, 115, 0, 99, 111, 108, + 111, 114, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 111, + 115, 105, 116, 105, 111, 110, + 0, 100, 105, 114, 101, 99, + 116, 105, 111, 110, 0, 171, + 107, 1, 0, 0, 116, 1, + 0, 0, 132, 1, 0, 0, + 116, 1, 0, 0, 141, 1, + 0, 0, 116, 1, 0, 0, + 5, 0, 0, 0, 1, 0, + 12, 0, 8, 0, 3, 0, + 152, 1, 0, 0, 109, 97, + 116, 67, 111, 108, 0, 110, + 111, 114, 109, 97, 108, 77, + 97, 116, 0, 171, 171, 171, + 3, 0, 3, 0, 3, 0, + 3, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 110, 117, + 109, 68, 105, 114, 76, 105, + 103, 104, 116, 115, 0, 171, + 171, 171, 0, 0, 2, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 115, 117, 114, 102, 80, 114, + 111, 112, 115, 0, 118, 115, + 95, 50, 95, 48, 0, 77, + 105, 99, 114, 111, 115, 111, + 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 57, 46, + 50, 57, 46, 57, 53, 50, + 46, 51, 49, 49, 49, 0, + 171, 171, 81, 0, 0, 5, + 4, 0, 15, 160, 0, 0, + 64, 64, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 3, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 2, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 3, 0, + 15, 144, 31, 0, 0, 2, + 1, 0, 0, 128, 4, 0, + 15, 144, 31, 0, 0, 2, + 2, 0, 0, 128, 5, 0, + 15, 144, 5, 0, 0, 3, + 0, 0, 15, 128, 5, 0, + 228, 144, 4, 0, 0, 160, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 0, 128, + 8, 0, 0, 4, 1, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 0, 128, 8, 0, 0, 4, + 1, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 0, 128, 8, 0, + 0, 4, 1, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 255, 176, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 85, 128, + 8, 0, 0, 4, 2, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 85, 128, 8, 0, 0, 4, + 2, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 85, 128, 8, 0, + 0, 4, 2, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 255, 176, + 5, 0, 0, 3, 2, 0, + 7, 128, 2, 0, 228, 128, + 4, 0, 85, 144, 4, 0, + 0, 4, 1, 0, 7, 128, + 1, 0, 228, 128, 4, 0, + 0, 144, 2, 0, 228, 128, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 170, 128, + 8, 0, 0, 4, 2, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 170, 128, 8, 0, 0, 4, + 2, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 170, 128, 8, 0, + 0, 4, 2, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 255, 176, + 4, 0, 0, 4, 1, 0, + 7, 128, 2, 0, 228, 128, + 4, 0, 170, 144, 1, 0, + 228, 128, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 255, 128, 8, 0, 0, 4, + 2, 0, 4, 128, 1, 0, + 228, 144, 43, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 255, 128, 8, 0, + 0, 4, 2, 0, 1, 128, + 1, 0, 228, 144, 41, 32, + 228, 160, 0, 0, 255, 176, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 255, 128, + 8, 0, 0, 4, 2, 0, + 2, 128, 1, 0, 228, 144, + 42, 32, 228, 160, 0, 0, + 255, 176, 4, 0, 0, 4, + 1, 0, 7, 128, 2, 0, + 228, 128, 4, 0, 255, 144, + 1, 0, 228, 128, 5, 0, + 0, 3, 2, 0, 7, 128, + 1, 0, 85, 128, 9, 0, + 228, 160, 4, 0, 0, 4, + 1, 0, 11, 128, 8, 0, + 164, 160, 1, 0, 0, 128, + 2, 0, 164, 128, 4, 0, + 0, 4, 1, 0, 7, 128, + 10, 0, 228, 160, 1, 0, + 170, 128, 1, 0, 244, 128, + 1, 0, 0, 2, 2, 0, + 1, 128, 13, 0, 0, 160, + 4, 0, 0, 4, 2, 0, + 7, 128, 15, 0, 228, 160, + 2, 0, 0, 128, 3, 0, + 228, 144, 1, 0, 0, 2, + 3, 0, 7, 128, 2, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 8, 128, 4, 0, + 85, 160, 38, 0, 0, 1, + 0, 0, 228, 240, 2, 0, + 0, 3, 2, 0, 8, 128, + 1, 0, 255, 128, 16, 0, + 0, 160, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 4, 0, 0, 160, + 46, 0, 0, 2, 0, 0, + 8, 176, 2, 0, 255, 128, + 8, 0, 0, 4, 4, 0, + 1, 128, 1, 0, 228, 128, + 19, 32, 228, 161, 0, 0, + 255, 176, 11, 0, 0, 3, + 4, 0, 1, 128, 4, 0, + 0, 128, 4, 0, 85, 160, + 46, 0, 0, 2, 0, 0, + 8, 176, 2, 0, 255, 128, + 5, 0, 0, 4, 4, 0, + 7, 128, 4, 0, 0, 128, + 17, 32, 228, 160, 0, 0, + 255, 176, 4, 0, 0, 4, + 3, 0, 7, 128, 4, 0, + 228, 128, 13, 0, 170, 160, + 3, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 4, 0, + 170, 160, 39, 0, 0, 0, + 1, 0, 0, 2, 3, 0, + 8, 128, 3, 0, 255, 144, + 11, 0, 0, 3, 1, 0, + 15, 128, 3, 0, 228, 128, + 4, 0, 85, 160, 10, 0, + 0, 3, 1, 0, 15, 128, + 1, 0, 228, 128, 4, 0, + 170, 160, 5, 0, 0, 3, + 0, 0, 15, 208, 1, 0, + 228, 128, 12, 0, 228, 160, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 85, 128, + 9, 0, 0, 4, 1, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 85, 128, 9, 0, 0, 4, + 1, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 85, 128, 9, 0, + 0, 4, 1, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 255, 176, + 5, 0, 0, 3, 1, 0, + 7, 128, 1, 0, 228, 128, + 4, 0, 85, 144, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 0, 128, 9, 0, + 0, 4, 2, 0, 1, 128, + 0, 0, 228, 144, 41, 32, + 228, 160, 0, 0, 255, 176, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 0, 128, + 9, 0, 0, 4, 2, 0, + 2, 128, 0, 0, 228, 144, + 42, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 0, 128, 9, 0, 0, 4, + 2, 0, 4, 128, 0, 0, + 228, 144, 43, 32, 228, 160, + 0, 0, 255, 176, 4, 0, + 0, 4, 1, 0, 7, 128, + 2, 0, 228, 128, 4, 0, + 0, 144, 1, 0, 228, 128, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 170, 128, + 9, 0, 0, 4, 2, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 170, 128, 9, 0, 0, 4, + 2, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 170, 128, 9, 0, + 0, 4, 2, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 255, 176, + 4, 0, 0, 4, 0, 0, + 7, 128, 2, 0, 228, 128, + 4, 0, 170, 144, 1, 0, + 228, 128, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 255, 128, 9, 0, 0, 4, + 1, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 255, 128, 9, 0, + 0, 4, 1, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 255, 176, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 255, 128, + 9, 0, 0, 4, 1, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 255, 176, 4, 0, 0, 4, + 0, 0, 7, 128, 1, 0, + 228, 128, 4, 0, 255, 144, + 0, 0, 228, 128, 5, 0, + 0, 3, 1, 0, 15, 128, + 0, 0, 85, 128, 1, 0, + 228, 160, 4, 0, 0, 4, + 1, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 128, + 1, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 128, 1, 0, 228, 128, + 2, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 3, 0, 228, 160, 1, 0, + 0, 2, 0, 0, 15, 192, + 0, 0, 228, 128, 2, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 4, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 2, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/vendor/librw/src/d3d/shaders/standardConstants.h b/vendor/librw/src/d3d/shaders/standardConstants.h new file mode 100644 index 0000000..088df7d --- /dev/null +++ b/vendor/librw/src/d3d/shaders/standardConstants.h @@ -0,0 +1,28 @@ +float4x4 combinedMat : register(c0); +float4x4 worldMat : register(c4); +float3x3 normalMat : register(c8); +float4 matCol : register(c12); +float4 surfProps : register(c13); +float4 fogData : register(c14); +float4 ambientLight : register(c15); + +#define surfAmbient (surfProps.x) +#define surfSpecular (surfProps.y) +#define surfDiffuse (surfProps.z) + +#define fogStart (fogData.x) +#define fogEnd (fogData.y) +#define fogRange (fogData.z) +#define fogDisable (fogData.w) + +#include "lighting.h" + +int numDirLights : register(i0); +int numPointLights : register(i1); +int numSpotLights : register(i2); +int4 firstLight : register(c16); +Light lights[8] : register(c17); + +#define firstDirLight (firstLight.x) +#define firstPointLight (firstLight.y) +#define firstSpotLight (firstLight.z) diff --git a/vendor/librw/src/d3d/xbox.cpp b/vendor/librw/src/d3d/xbox.cpp new file mode 100644 index 0000000..62fab0c --- /dev/null +++ b/vendor/librw/src/d3d/xbox.cpp @@ -0,0 +1,1005 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwxbox.h" + +#include "rwxboximpl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace xbox { + +static void* +driverOpen(void *o, int32, int32) +{ + engine->driver[PLATFORM_XBOX]->defaultPipeline = makeDefaultPipeline(); + + engine->driver[PLATFORM_XBOX]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_XBOX]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_XBOX]->rasterLock = rasterLock; + engine->driver[PLATFORM_XBOX]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_XBOX]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_XBOX]->rasterToImage = rasterToImage; + + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_XBOX, 0, PLATFORM_XBOX, + driverOpen, driverClose); + registerNativeRaster(); +} + +void* +destroyNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_XBOX) + return object; + InstanceDataHeader *header = + (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + rwFree(header->vertexBuffer); + rwFree(header->begin); + rwFree(header->data); + rwFree(header); + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 vers; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, &vers)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_XBOX){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + if(vers < 0x35000){ + RWERROR((ERR_VERSION, vers)); + return nil; + } + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_XBOX; + + int32 size = stream->readI32(); + // The 0x18 byte are the resentryheader. + // We don't have it but it's used for alignment. + header->data = rwNewT(uint8, size + 0x18, MEMDUR_EVENT | ID_GEOMETRY); + uint8 *p = header->data+0x18+4; + stream->read8(p, size-4); + + header->size = size; + header->serialNumber = *(uint16*)p; p += 2; + header->numMeshes = *(uint16*)p; p += 2; + header->primType = *(uint32*)p; p += 4; + header->numVertices = *(uint32*)p; p += 4; + header->stride = *(uint32*)p; p += 4; + // RxXboxVertexFormat in 3.3 here + p += 4; // skip vertexBuffer pointer + header->vertexAlpha = *(bool32*)p; p += 4; + p += 8; // skip begin, end pointers + + InstanceData *inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + header->begin = inst; + for(int i = 0; i < header->numMeshes; i++){ + inst->minVert = *(uint32*)p; p += 4; + inst->numVertices = *(int32*)p; p += 4; + inst->numIndices = *(int32*)p; p += 4; + inst->indexBuffer = header->data + *(uint32*)p; p += 4; + p += 8; // skip material and vertexShader + inst->vertexShader = 0; + // pixelShader in 3.3 here + inst++; + } + header->end = inst; + + header->vertexBuffer = rwNewT(uint8, header->stride*header->numVertices, MEMDUR_EVENT | ID_GEOMETRY); + stream->read8(header->vertexBuffer, header->stride*header->numVertices); + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_STRUCT, len-12); + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_XBOX) + return stream; + stream->writeU32(PLATFORM_XBOX); + assert(rw::version >= 0x35000 && "can't write native Xbox data < 0x35000"); + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + + // we just fill header->data and write that + uint8 *p = header->data+0x18; + *(int32*)p = header->size; p += 4; + *(uint16*)p = header->serialNumber; p += 2; + *(uint16*)p = header->numMeshes; p += 2; + *(uint32*)p = header->primType; p += 4; + *(uint32*)p = header->numVertices; p += 4; + *(uint32*)p = header->stride; p += 4; + // RxXboxVertexFormat in 3.3 here + p += 4; // skip vertexBuffer pointer + *(bool32*)p = header->vertexAlpha; p += 4; + p += 8; // skip begin, end pointers + + InstanceData *inst = header->begin; + for(int i = 0; i < header->numMeshes; i++){ + *(uint32*)p = inst->minVert; p += 4; + *(int32*)p = inst->numVertices; p += 4; + *(int32*)p = inst->numIndices; p += 4; + *(uint32*)p = (uint8*)inst->indexBuffer - header->data; p += 4; + p += 8; // skip material and vertexShader + // pixelShader in 3.3 here + inst++; + } + + stream->write8(header->data+0x18, header->size); + stream->write8(header->vertexBuffer, header->stride*header->numVertices); + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_XBOX) + return 0; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + return 12 + 4 + header->size + header->stride*header->numVertices; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +enum { + D3DPT_TRIANGLELIST = 5, + D3DPT_TRIANGLESTRIP = 6 +}; + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE (or not, xbox can't render) + if(geo->instData) + return; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + MeshHeader *meshh = geo->meshHeader; + geo->instData = header; + header->platform = PLATFORM_XBOX; + + header->size = 0x24 + meshh->numMeshes*0x18 + 0x10; + Mesh *mesh = meshh->getMeshes(); + for(uint32 i = 0; i < meshh->numMeshes; i++) + header->size += (mesh++->numIndices*2 + 0xF) & ~0xF; + // The 0x18 byte are the resentryheader. + // We don't have it but it's used for alignment. + header->data = rwNewT(uint8, header->size + 0x18, MEMDUR_EVENT | ID_GEOMETRY); + header->serialNumber = meshh->serialNum; + header->numMeshes = meshh->numMeshes; + header->primType = meshh->flags == MeshHeader::TRISTRIP ? + D3DPT_TRIANGLESTRIP : D3DPT_TRIANGLELIST; + header->numVertices = geo->numVertices; + header->vertexAlpha = 0; + // set by the instanceCB + header->stride = 0; + header->vertexBuffer = nil; + + InstanceData *inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + header->begin = inst; + mesh = meshh->getMeshes(); + uint8 *indexbuf = (uint8*)header->data + ((0x18 + 0x24 + header->numMeshes*0x18 + 0xF)&~0xF); + for(uint32 i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &inst->minVert, &inst->numVertices); + inst->numIndices = mesh->numIndices; + inst->indexBuffer = indexbuf; + memcpy(inst->indexBuffer, mesh->indices, inst->numIndices*sizeof(uint16)); + indexbuf += (inst->numIndices*2 + 0xF) & ~0xF; + inst->material = mesh->material; + inst->vertexShader = 0; // TODO? + mesh++; + inst++; + } + header->end = inst; + + pipe->instanceCB(geo, header); +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_XBOX); + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + InstanceData *inst = header->begin; + Mesh *mesh = geo->meshHeader->getMeshes(); + // For some reason numIndices in mesh and instance data are not always equal + // And primitive isn't always correct either. Maybe some internal conversion... + geo->meshHeader->totalIndices = 0; + for(uint32 i = 0; i < header->numMeshes; i++){ + mesh[i].numIndices = inst[i].numIndices; + geo->meshHeader->totalIndices += mesh[i].numIndices; + } + geo->meshHeader->flags = header->primType == D3DPT_TRIANGLESTRIP ? + MeshHeader::TRISTRIP : 0; + + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + geo->allocateMeshes(geo->meshHeader->numMeshes, geo->meshHeader->totalIndices, 0); + + mesh = geo->meshHeader->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + uint16 *indices = (uint16*)inst->indexBuffer; + memcpy(mesh->indices, indices, inst->numIndices*2); + mesh++; + inst++; + } + + pipe->uninstanceCB(geo, header); + geo->generateTriangles(); + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_XBOX); + this->impl.instance = xbox::instance; + this->impl.uninstance = xbox::uninstance; + this->instanceCB = nil; + this->uninstanceCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +int v3dFormatMap[] = { + -1, VERT_BYTE3, VERT_SHORT3, VERT_NORMSHORT3, VERT_COMPNORM, VERT_FLOAT3 +}; + +int v2dFormatMap[] = { + -1, VERT_BYTE2, VERT_SHORT2, VERT_NORMSHORT2, VERT_COMPNORM, VERT_FLOAT2 +}; + +void +defaultInstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + uint32 *vertexFmt = getVertexFmt(geo); + if(*vertexFmt == 0) + *vertexFmt = makeVertexFmt(geo->flags, geo->numTexCoordSets); + header->stride = getVertexFmtStride(*vertexFmt); + header->vertexBuffer = rwNewT(uint8, header->stride*header->numVertices, MEMDUR_EVENT | ID_GEOMETRY); + uint8 *dst = (uint8*)header->vertexBuffer; + + uint32 fmt = *vertexFmt; + uint32 sel = fmt & 0xF; + instV3d(v3dFormatMap[sel], dst, geo->morphTargets[0].vertices, + header->numVertices, header->stride); + dst += sel == 4 ? 4 : 3*vertexFormatSizes[sel]; + + sel = (fmt >> 4) & 0xF; + if(sel){ + instV3d(v3dFormatMap[sel], dst, geo->morphTargets[0].normals, + header->numVertices, header->stride); + dst += sel == 4 ? 4 : 3*vertexFormatSizes[sel]; + } + + if(fmt & 0x1000000){ + header->vertexAlpha = instColor(VERT_ARGB, dst, geo->colors, + header->numVertices, header->stride); + dst += 4; + } + + for(int i = 0; i < 4; i++){ + sel = (fmt >> (i*4 + 8)) & 0xF; + if(sel == 0) + break; + instTexCoords(v2dFormatMap[sel], dst, geo->texCoords[i], + header->numVertices, header->stride); + dst += sel == 4 ? 4 : 2*vertexFormatSizes[sel]; + } + + if(fmt & 0xE000000) + assert(0 && "can't instance tangents or whatever it is"); +} + +void +defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + uint32 *vertexFmt = getVertexFmt(geo); + uint32 fmt = *vertexFmt; + assert(fmt != 0); + uint8 *src = (uint8*)header->vertexBuffer; + + uint32 sel = fmt & 0xF; + uninstV3d(v3dFormatMap[sel], geo->morphTargets[0].vertices, src, + header->numVertices, header->stride); + src += sel == 4 ? 4 : 3*vertexFormatSizes[sel]; + + sel = (fmt >> 4) & 0xF; + if(sel){ + uninstV3d(v3dFormatMap[sel], geo->morphTargets[0].normals, src, + header->numVertices, header->stride); + src += sel == 4 ? 4 : 3*vertexFormatSizes[sel]; + } + + if(fmt & 0x1000000){ + uninstColor(VERT_ARGB, geo->colors, src, + header->numVertices, header->stride); + src += 4; + } + + for(int i = 0; i < 4; i++){ + sel = (fmt >> (i*4 + 8)) & 0xF; + if(sel == 0) + break; + uninstTexCoords(v2dFormatMap[sel], geo->texCoords[i], src, + header->numVertices, header->stride); + src += sel == 4 ? 4 : 2*vertexFormatSizes[sel]; + } +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + return pipe; +} + +// Native Texture and Raster + +int32 nativeRasterOffset; + +static uint32 +calculateTextureSize(uint32 width, uint32 height, uint32 depth, uint32 format) +{ +#define D3DFMT_W11V11U10 65 + switch(format){ + default: + case D3DFMT_UNKNOWN: + return 0; + case D3DFMT_A8: + case D3DFMT_P8: + case D3DFMT_L8: + case D3DFMT_AL8: + case D3DFMT_LIN_A8: + case D3DFMT_LIN_AL8: + case D3DFMT_LIN_L8: + return width * height * depth; + case D3DFMT_R5G6B5: + case D3DFMT_R6G5B5: + case D3DFMT_X1R5G5B5: + case D3DFMT_A1R5G5B5: + case D3DFMT_A4R4G4B4: + case D3DFMT_R4G4B4A4: + case D3DFMT_R5G5B5A1: + case D3DFMT_R8B8: + case D3DFMT_G8B8: + case D3DFMT_A8L8: + case D3DFMT_L16: + //case D3DFMT_V8U8: + //case D3DFMT_L6V5U5: + case D3DFMT_D16_LOCKABLE: + //case D3DFMT_D16: + case D3DFMT_F16: + case D3DFMT_YUY2: + case D3DFMT_UYVY: + case D3DFMT_LIN_A1R5G5B5: + case D3DFMT_LIN_A4R4G4B4: + case D3DFMT_LIN_G8B8: + case D3DFMT_LIN_R4G4B4A4: + case D3DFMT_LIN_R5G5B5A1: + case D3DFMT_LIN_R5G6B5: + case D3DFMT_LIN_R6G5B5: + case D3DFMT_LIN_R8B8: + case D3DFMT_LIN_X1R5G5B5: + case D3DFMT_LIN_A8L8: + case D3DFMT_LIN_L16: + //case D3DFMT_LIN_V8U8: + //case D3DFMT_LIN_L6V5U5: + case D3DFMT_LIN_D16: + case D3DFMT_LIN_F16: + return width * 2 * height * depth; + case D3DFMT_A8R8G8B8: + case D3DFMT_X8R8G8B8: + case D3DFMT_A8B8G8R8: + case D3DFMT_B8G8R8A8: + case D3DFMT_R8G8B8A8: + //case D3DFMT_X8L8V8U8: + //case D3DFMT_Q8W8V8U8: + case D3DFMT_V16U16: + case D3DFMT_D24S8: + case D3DFMT_F24S8: + case D3DFMT_LIN_A8B8G8R8: + case D3DFMT_LIN_A8R8G8B8: + case D3DFMT_LIN_B8G8R8A8: + case D3DFMT_LIN_R8G8B8A8: + case D3DFMT_LIN_X8R8G8B8: + case D3DFMT_LIN_V16U16: + //case D3DFMT_LIN_X8L8V8U8: + //case D3DFMT_LIN_Q8W8V8U8: + case D3DFMT_LIN_D24S8: + case D3DFMT_LIN_F24S8: + return width * 4 * height * depth; + case D3DFMT_DXT1: + assert(depth <= 1); + return ((width + 3) >> 2) * ((height + 3) >> 2) * 8; + //case D3DFMT_DXT2: + case D3DFMT_DXT3: + //case D3DFMT_DXT4: + case D3DFMT_DXT5: + assert(depth <= 1); + return ((width + 3) >> 2) * ((height + 3) >> 2) * 16; + } +} + +static void* +createTexture(int32 width, int32 height, int32 numlevels, uint32 format) +{ + int32 w = width; + int32 h = height; + int32 size = 0; + for(int32 i = 0; i < numlevels; i++){ + size += calculateTextureSize(w, h, 1, format); + w /= 2; + if(w == 0) w = 1; + h /= 2; + if(h == 0) h = 1; + } + size = (size+3)&~3; + uint8 *data = (uint8*)rwNew(sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(numlevels-1)+size, + MEMDUR_EVENT | ID_DRIVER); + RasterLevels *levels = (RasterLevels*)data; + data += sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(numlevels-1); + levels->numlevels = numlevels; + levels->format = format; + w = width; + h = height; + for(int32 i = 0; i < numlevels; i++){ + levels->levels[i].width = w; + levels->levels[i].height = h; + levels->levels[i].data = data; + levels->levels[i].size = calculateTextureSize(w, h, 1, format); + data += levels->levels[i].size; + w /= 2; + if(w == 0) w = 1; + h /= 2; + if(h == 0) h = 1; + } + return levels; +} + +struct RasterFormatInfo +{ + uint32 d3dformat; + int32 depth; + bool32 hasAlpha; + uint32 rwFormat; +}; + +// indexed directly by RW format +static RasterFormatInfo formatInfoRW[16] = { + { 0, 0, 0, 0}, + { D3DFMT_A1R5G5B5, 16, 1, Raster::C1555 }, + { D3DFMT_R5G6B5, 16, 0, Raster::C565 }, + { D3DFMT_A4R4G4B4, 16, 1, Raster::C4444 }, + { D3DFMT_L8, 8, 0, Raster::LUM8 }, + { D3DFMT_A8R8G8B8, 32, 1, Raster::C8888 }, + { D3DFMT_X8R8G8B8, 32, 0, Raster::C888 }, + { D3DFMT_UNKNOWN, 16, 0, Raster::D16 }, + { D3DFMT_UNKNOWN, 32, 0, Raster::D24 }, + { D3DFMT_UNKNOWN, 32, 0, Raster::D32 }, + { D3DFMT_X1R5G5B5, 16, 0, Raster::C555 } +}; + +static void +rasterSetFormat(Raster *raster) +{ + assert(raster->format != 0); // no default yet + + XboxRaster *natras = GETXBOXRASTEREXT(raster); + if(raster->format & (Raster::PAL4 | Raster::PAL8)){ + natras->format = D3DFMT_P8; + raster->depth = 8; + }else{ + natras->format = formatInfoRW[(raster->format >> 8) & 0xF].d3dformat; + raster->depth = formatInfoRW[(raster->format >> 8) & 0xF].depth; + } + natras->bpp = raster->depth/8; + natras->hasAlpha = formatInfoRW[(raster->format >> 8) & 0xF].hasAlpha; + raster->stride = raster->width&natras->bpp; +} + +static Raster* +rasterCreateTexture(Raster *raster) +{ + XboxRaster *natras = GETXBOXRASTEREXT(raster); + int32 levels; + + if(natras->format == D3DFMT_P8) + natras->palette = (uint8*)rwNew(4*256, MEMDUR_EVENT | ID_DRIVER); + levels = Raster::calculateNumLevels(raster->width, raster->height); + assert(natras->texture == nil); + natras->texture = createTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? levels : 1, + natras->format); + if(natras->texture == nil){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + return raster; +} + +Raster* +rasterCreate(Raster *raster) +{ + rasterSetFormat(raster); + + Raster *ret = raster; + + // Dummy to use as subraster + if(raster->width == 0 || raster->height == 0){ + raster->flags |= Raster::DONTALLOCATE; + raster->stride = 0; + goto ret; + } + if(raster->flags & Raster::DONTALLOCATE) + goto ret; + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + ret = rasterCreateTexture(raster); + break; + default: + RWERROR((ERR_INVRASTER)); + return nil; + } + +ret: + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->originalStride = raster->stride; + raster->originalPixels = raster->pixels; + return ret; +} + +uint8* +rasterLock(Raster *raster, int32 level, int32 lockMode) +{ + XboxRaster *natras = GETXBOXRASTEREXT(raster); + + // check if already locked + if(raster->privateFlags & (Raster::PRIVATELOCK_READ|Raster::PRIVATELOCK_WRITE)) + return nil; + + RasterLevels *levels = (RasterLevels*)natras->texture; + raster->pixels = levels->levels[level].data; + raster->width = levels->levels[level].width; + raster->height = levels->levels[level].height; + raster->stride = raster->width*natras->bpp; + + if(lockMode & Raster::LOCKREAD) raster->privateFlags |= Raster::PRIVATELOCK_READ; + if(lockMode & Raster::LOCKWRITE) raster->privateFlags |= Raster::PRIVATELOCK_WRITE; + + return raster->pixels; +} + +void +rasterUnlock(Raster *raster, int32 level) +{ + raster->width = raster->originalWidth; + raster->height = raster->originalHeight; + raster->stride = raster->originalStride; + raster->pixels = raster->originalPixels; + + raster->privateFlags &= ~(Raster::PRIVATELOCK_READ|Raster::PRIVATELOCK_WRITE); +} + +int32 +rasterNumLevels(Raster *raster) +{ + XboxRaster *natras = GETXBOXRASTEREXT(raster); + RasterLevels *levels = (RasterLevels*)natras->texture; + return levels->numlevels; +} + +static void +unswizzle(uint8 *dst, uint8 *src, int32 w, int32 h, int32 bpp) +{ + uint32 maskU = 0; + uint32 maskV = 0; + int32 i = 1; + int32 j = 1; + int32 c; + do{ + c = 0; + if(i < w){ + maskU |= j; + j <<= 1; + c = j; + } + if(i < h){ + maskV |= j; + j <<= 1; + c = j; + } + i <<= 1; + }while(c); + int32 x, y, u, v; + v = 0; + for(y = 0; y < h; y++){ + u = 0; + for(x = 0; x < w; x++){ + memcpy(&dst[(y*w + x)*bpp], &src[(u|v)*bpp], bpp); + u = (u - maskU) & maskU; + } + v = (v - maskV) & maskV; + } +} + +Image* +rasterToImage(Raster *raster) +{ + int32 depth; + Image *image; + + bool unlock = false; + if(raster->pixels == nil){ + raster->lock(0, Raster::LOCKREAD); + unlock = true; + } + + XboxRaster *natras = GETXBOXRASTEREXT(raster); + if(natras->customFormat){ + int w = raster->width; + int h = raster->height; + // pixels are in the upper right corner + if(w < 4) w = 4; + if(h < 4) h = 4; + image = Image::create(w, h, 32); + image->allocate(); + uint8 *pix = raster->pixels; + switch(natras->format){ + case D3DFMT_DXT1: + image->setPixelsDXT(1, pix); + // TODO: is this correct? + if(!natras->hasAlpha) + image->removeMask(); + break; + case D3DFMT_DXT3: + image->setPixelsDXT(3, pix); + break; + case D3DFMT_DXT5: + image->setPixelsDXT(5, pix); + break; + default: + assert(0 && "unknown format"); + image->destroy(); + if(unlock) + raster->unlock(0); + return nil; + } + // fix it up again + image->width = raster->width; + image->height = raster->height; + + if(unlock) + raster->unlock(0); + return image; + } + + switch(raster->format & 0xF00){ + case Raster::C1555: + depth = 16; + break; + case Raster::C8888: + depth = 32; + break; + case Raster::C888: + depth = 24; + break; + case Raster::C555: + depth = 16; + break; + + default: + case Raster::C565: + case Raster::C4444: + case Raster::LUM8: + assert(0 && "unsupported raster format"); + } + int32 pallength = 0; + if((raster->format & Raster::PAL4) == Raster::PAL4){ + depth = 4; + pallength = 16; + }else if((raster->format & Raster::PAL8) == Raster::PAL8){ + depth = 8; + pallength = 256; + } + + image = Image::create(raster->width, raster->height, depth); + image->allocate(); + + if(pallength){ + uint8 *out = image->palette; + uint8 *in = (uint8*)natras->palette; + // bytes are BGRA unlike regular d3d! + for(int32 i = 0; i < pallength; i++){ + conv_BGRA8888_from_RGBA8888(out, in); + in += 4; + out += 4; + } + } + + uint8 *imgpixels = image->pixels; + uint8 *pixels = raster->pixels; + + // NB: + assert(image->bpp == (int)natras->bpp); + assert(image->stride == raster->stride); + unswizzle(imgpixels, pixels, image->width, image->height, image->bpp); + // Fix RGB order + uint8 tmp; + if(depth > 8) + for(int32 y = 0; y < image->height; y++){ + uint8 *imgrow = imgpixels; + // uint8 *rasrow = pixels; + for(int32 x = 0; x < image->width; x++){ + switch(raster->format & 0xF00){ + case Raster::C8888: + case Raster::C888: + tmp = imgrow[0]; + imgrow[0] = imgrow[2]; + imgrow[2] = tmp; + imgrow += image->bpp; + break; + } + } + imgpixels += image->stride; + // pixels += raster->stride; + } + image->compressPalette(); + + if(unlock) + raster->unlock(0); + + return image; +} + +int32 +getLevelSize(Raster *raster, int32 level) +{ + XboxRaster *ras = GETXBOXRASTEREXT(raster); + RasterLevels *levels = (RasterLevels*)ras->texture; + return levels->levels[level].size; +} + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + XboxRaster *raster = PLUGINOFFSET(XboxRaster, object, offset); + raster->texture = nil; + raster->palette = nil; + raster->format = 0; + raster->hasAlpha = 0; + raster->customFormat = 0; + raster->unknownFlag = 0; + return object; +} + +static void* +destroyNativeRaster(void *object, int32, int32) +{ + // TODO: + return object; +} + +static void* +copyNativeRaster(void *dst, void *, int32 offset, int32) +{ + XboxRaster *raster = PLUGINOFFSET(XboxRaster, dst, offset); + raster->texture = nil; + raster->palette = nil; + raster->format = 0; + raster->hasAlpha = 0; + raster->unknownFlag = 0; + return dst; +} + +void +registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(XboxRaster), + ID_RASTERXBOX, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); +} + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 vers, platform; + if(!findChunk(stream, ID_STRUCT, nil, &vers)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_XBOX){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + if(vers < 0x34001){ + RWERROR((ERR_VERSION, vers)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + + // Texture + tex->filterAddressing = stream->readU32(); + stream->read8(tex->name, 32); + stream->read8(tex->mask, 32); + +//if(strcmp(tex->name, "bluallu") == 0) +//__debugbreak(); + + // Raster + int32 format = stream->readI32(); + bool32 hasAlpha = stream->readI16(); + bool32 unknownFlag = stream->readI16(); + int32 width = stream->readU16(); + int32 height = stream->readU16(); + int32 depth = stream->readU8(); + int32 numLevels = stream->readU8(); + int32 type = stream->readU8(); + int32 compression = stream->readU8(); + int32 totalSize = stream->readI32(); + + // isn't this the cube map flag? + assert(unknownFlag == 0); + Raster *raster; + if(compression){ + raster = Raster::create(width, height, depth, format | type | Raster::DONTALLOCATE, PLATFORM_XBOX); + XboxRaster *ras = GETXBOXRASTEREXT(raster); + ras->format = compression; + ras->hasAlpha = hasAlpha; + ras->texture = createTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? numLevels : 1, + ras->format); + ras->customFormat = 1; + raster->flags &= ~Raster::DONTALLOCATE; + }else + raster = Raster::create(width, height, depth, format | type, PLATFORM_XBOX); + XboxRaster *ras = GETXBOXRASTEREXT(raster); + tex->raster = raster; + + if(raster->format & Raster::PAL4) + stream->read8(ras->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->read8(ras->palette, 4*256); + + // exploit the fact that mipmaps are allocated consecutively + uint8 *data = raster->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + stream->read8(data, totalSize); + raster->unlock(0); + + return tex; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + int32 chunksize = getSizeNativeTexture(tex); + writeChunkHeader(stream, ID_STRUCT, chunksize-12); + stream->writeU32(PLATFORM_XBOX); + + // Texture + stream->writeU32(tex->filterAddressing); + stream->write8(tex->name, 32); + stream->write8(tex->mask, 32); + + // Raster + Raster *raster = tex->raster; + XboxRaster *ras = GETXBOXRASTEREXT(raster); + int32 numLevels = raster->getNumLevels(); + stream->writeI32(raster->format); + stream->writeI16(ras->hasAlpha); + stream->writeI16(ras->unknownFlag); + stream->writeU16(raster->width); + stream->writeU16(raster->height); + stream->writeU8(raster->depth); + stream->writeU8(numLevels); + stream->writeU8(raster->type); + stream->writeU8(ras->format); + + int32 totalSize = 0; + for(int32 i = 0; i < numLevels; i++) + totalSize += getLevelSize(tex->raster, i); + totalSize = (totalSize+3)&~3; + stream->writeI32(totalSize); + + if(raster->format & Raster::PAL4) + stream->write8(ras->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->write8(ras->palette, 4*256); + + // exploit the fact that mipmaps are allocated consecutively + uint8 *data = raster->lock(0, Raster::LOCKREAD); + stream->write8(data, totalSize); + raster->unlock(0); +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 72 + 16 + 4; + int32 levels = tex->raster->getNumLevels(); + for(int32 i = 0; i < levels; i++) + size += getLevelSize(tex->raster, i); + size = (size+3)&~3; + if(tex->raster->format & Raster::PAL4) + size += 4*32; + else if(tex->raster->format & Raster::PAL8) + size += 4*256; + return size; +} + +} +} diff --git a/vendor/librw/src/d3d/xboxmatfx.cpp b/vendor/librw/src/d3d/xboxmatfx.cpp new file mode 100644 index 0000000..a6b8718 --- /dev/null +++ b/vendor/librw/src/d3d/xboxmatfx.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwxbox.h" + +namespace rw { +namespace xbox { + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_XBOX] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_XBOX])->destroy(); + matFXGlobals.pipelines[PLATFORM_XBOX] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_XBOX, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +} +} diff --git a/vendor/librw/src/d3d/xboxskin.cpp b/vendor/librw/src/d3d/xboxskin.cpp new file mode 100644 index 0000000..0ce5cba --- /dev/null +++ b/vendor/librw/src/d3d/xboxskin.cpp @@ -0,0 +1,252 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwxbox.h" + +#define PLUGIN_ID ID_SKIN + +namespace rw { +namespace xbox { + +struct NativeSkin +{ + int32 table1[256]; // maps indices to bones + int32 table2[256]; // maps bones to indices + int32 numUsedBones; + void *vertexBuffer; + int32 stride; +}; + +Stream* +readNativeSkin(Stream *stream, int32, void *object, int32 offset) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 vers, platform; + if(!findChunk(stream, ID_STRUCT, nil, &vers)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_XBOX){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + if(vers < 0x35000){ + RWERROR((ERR_VERSION, vers)); + return nil; + } + + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, offset) = skin; + + int32 numBones = stream->readI32(); + skin->init(numBones, 0, 0); + NativeSkin *natskin = rwNewT(NativeSkin, 1, MEMDUR_EVENT | ID_SKIN); + skin->platformData = natskin; + stream->read32(natskin->table1, 256*sizeof(int32)); + stream->read32(natskin->table2, 256*sizeof(int32)); + natskin->numUsedBones = stream->readI32(); + skin->numWeights = stream->readI32(); + stream->seek(4); // skip pointer to vertexBuffer + natskin->stride = stream->readI32(); + int32 size = geometry->numVertices*natskin->stride; + natskin->vertexBuffer = rwNewT(uint8, size, MEMDUR_EVENT | ID_SKIN); + stream->read8(natskin->vertexBuffer, size); + stream->read32(skin->inverseMatrices, skin->numBones*64); + + readSkinSplitData(stream, skin); + return stream; +} + +Stream* +writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + assert(skin->platformData); + assert(rw::version >= 0x35000 && "can't handle native xbox skin < 0x35000"); + NativeSkin *natskin = (NativeSkin*)skin->platformData; + + writeChunkHeader(stream, ID_STRUCT, len-12); + stream->writeU32(PLATFORM_XBOX); + stream->writeI32(skin->numBones); + stream->write32(natskin->table1, 256*sizeof(int32)); + stream->write32(natskin->table2, 256*sizeof(int32)); + stream->writeI32(natskin->numUsedBones); + stream->writeI32(skin->numWeights); + stream->writeU32(0xBADEAFFE); // pointer to vertexBuffer + stream->writeI32(natskin->stride); + stream->write8(natskin->vertexBuffer, + geometry->numVertices*natskin->stride); + stream->write32(skin->inverseMatrices, skin->numBones*64); + + writeSkinSplitData(stream, skin); + return stream; +} + +int32 +getSizeNativeSkin(void *object, int32 offset) +{ + Geometry *geometry = (Geometry*)object; + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin == nil) + return -1; + if(skin->platformData == nil) + return -1; + NativeSkin *natskin = (NativeSkin*)skin->platformData; + return 12 + 8 + 2*256*4 + 4*4 + + natskin->stride*geometry->numVertices + skin->numBones*64 + + skinSplitDataSize(skin); +} + +void +skinInstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + defaultInstanceCB(geo, header); + + Skin *skin = Skin::get(geo); + if(skin == nil) + return; + NativeSkin *natskin = rwNewT(NativeSkin, 1, MEMDUR_EVENT | ID_SKIN); + skin->platformData = natskin; + + natskin->numUsedBones = skin->numUsedBones; + memset(natskin->table1, 0xFF, sizeof(natskin->table1)); + memset(natskin->table2, 0x00, sizeof(natskin->table2)); + for(int32 i = 0; i < skin->numUsedBones; i++){ + natskin->table1[i] = skin->usedBones[i]; + natskin->table2[skin->usedBones[i]] = i; + } + + natskin->stride = 3*skin->numWeights; + uint8 *vbuf = rwNewT(uint8, header->numVertices*natskin->stride, MEMDUR_EVENT | ID_SKIN); + natskin->vertexBuffer = vbuf; + + int32 w[4]; + int sum; + float32 *weights = skin->weights; + uint8 *p = vbuf; + int32 numVertices = header->numVertices; + while(numVertices--){ + sum = 0; + for(int i = 1; i < skin->numWeights; i++){ + w[i] = (int32)(weights[i]*255.0f + 0.5f); + sum += w[i]; + } + w[0] = 255 - sum; + for(int i = 0; i < skin->numWeights; i++) + p[i] = w[i]; + p += natskin->stride; + weights += 4; + } + + numVertices = header->numVertices; + p = vbuf + skin->numWeights; + uint8 *indices = skin->indices; + uint16 *idx; + while(numVertices--){ + idx = (uint16*)p; + for(int i = 0; i < skin->numWeights; i++) + idx[i] = 3*natskin->table2[indices[i]]; + p += natskin->stride; + indices += 4; + } +} + +void +skinUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + defaultUninstanceCB(geo, header); + + Skin *skin = Skin::get(geo); + if(skin == nil) + return; + NativeSkin *natskin = (NativeSkin*)skin->platformData; + + uint8 *data = skin->data; + float *invMats = skin->inverseMatrices; + skin->init(skin->numBones, natskin->numUsedBones, geo->numVertices); + memcpy(skin->inverseMatrices, invMats, skin->numBones*64); + rwFree(data); + + for(int32 j = 0; j < skin->numUsedBones; j++) + skin->usedBones[j] = natskin->table1[j]; + + float *weights = skin->weights; + uint8 *indices = skin->indices; + uint8 *p = (uint8*)natskin->vertexBuffer; + int32 numVertices = header->numVertices; + float w[4]; + uint8 i[4]; + uint16 *ip; + while(numVertices--){ + w[0] = w[1] = w[2] = w[3] = 0.0f; + i[0] = i[1] = i[2] = i[3] = 0; + + for(int32 j = 0; j < skin->numWeights; j++) + w[j] = *p++/255.0f; + + ip = (uint16*)p; + for(int32 j = 0; j < skin->numWeights; j++){ + i[j] = natskin->table1[*ip++/3]; + if(w[j] == 0.0f) i[j] = 0; // clean up a bit + } + p = (uint8*)ip; + + for(int32 j = 0; j < 4; j++){ + *weights++ = w[j]; + *indices++ = i[j]; + } + } + + rwFree(natskin->vertexBuffer); + rwFree(natskin); +} + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_XBOX] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_XBOX])->destroy(); + skinGlobals.pipelines[PLATFORM_XBOX] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_XBOX, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = skinUninstanceCB; + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + return pipe; +} + +} +} diff --git a/vendor/librw/src/d3d/xboxvfmt.cpp b/vendor/librw/src/d3d/xboxvfmt.cpp new file mode 100644 index 0000000..b6569dc --- /dev/null +++ b/vendor/librw/src/d3d/xboxvfmt.cpp @@ -0,0 +1,115 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwxbox.h" + +namespace rw { +namespace xbox { + +static int32 vertexFmtOffset; + +uint32 vertexFormatSizes[6] = { + 0, 1, 2, 2, 4, 4 +}; + +uint32* +getVertexFmt(Geometry *g) +{ + return PLUGINOFFSET(uint32, g, vertexFmtOffset); +} + +uint32 +makeVertexFmt(int32 flags, uint32 numTexSets) +{ + if(numTexSets > 4) + numTexSets = 4; + uint32 fmt = 0x5; // FLOAT3 + if(flags & Geometry::NORMALS) + fmt |= 0x40; // NORMPACKED3 + for(uint32 i = 0; i < numTexSets; i++) + fmt |= 0x500 << i*4; // FLOAT2 + if(flags & Geometry::PRELIT) + fmt |= 0x1000000; // D3DCOLOR + return fmt; +} + +uint32 +getVertexFmtStride(uint32 fmt) +{ + uint32 stride = 0; + uint32 v = fmt & 0xF; + uint32 n = (fmt >> 4) & 0xF; + stride += v == 4 ? 4 : 3*vertexFormatSizes[v]; + stride += n == 4 ? 4 : 3*vertexFormatSizes[n]; + if(fmt & 0x1000000) + stride += 4; + for(int i = 0; i < 4; i++){ + uint32 t = (fmt >> (i*4 + 8)) & 0xF; + stride += t == 4 ? 4 : 2*vertexFormatSizes[t]; + } + if(fmt & 0xE000000) + stride += 8; + return stride; +} + +static void* +createVertexFmt(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(uint32, object, offset) = 0; + return object; +} + +static void* +copyVertexFmt(void *dst, void *src, int32 offset, int32) +{ + *PLUGINOFFSET(uint32, dst, offset) = *PLUGINOFFSET(uint32, src, offset); + return dst; +} + +static Stream* +readVertexFmt(Stream *stream, int32, void *object, int32 offset, int32) +{ + uint32 fmt = stream->readU32(); + *PLUGINOFFSET(uint32, object, offset) = fmt; + // TODO: ? create and attach "vertex shader" + return stream; +} + +static Stream* +writeVertexFmt(Stream *stream, int32, void *object, int32 offset, int32) +{ + stream->writeI32(*PLUGINOFFSET(uint32, object, offset)); + return stream; +} + +static int32 +getSizeVertexFmt(void*, int32, int32) +{ + if(rw::platform != PLATFORM_XBOX) + return -1; + return 4; +} + +void +registerVertexFormatPlugin(void) +{ + vertexFmtOffset = Geometry::registerPlugin(sizeof(uint32), ID_VERTEXFMT, + createVertexFmt, nil, copyVertexFmt); + Geometry::registerPluginStream(ID_VERTEXFMT, + readVertexFmt, + writeVertexFmt, + getSizeVertexFmt); +} + +} +} diff --git a/vendor/librw/src/engine.cpp b/vendor/librw/src/engine.cpp new file mode 100644 index 0000000..74dae02 --- /dev/null +++ b/vendor/librw/src/engine.cpp @@ -0,0 +1,566 @@ +#include +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "ps2/rwps2.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwgl3.h" +#include "gl/rwwdgl.h" + +#define PLUGIN_ID 0 + +// on windows +//#ifdef DEBUG +//#include +//#define free(p) _free_dbg(p, _NORMAL_BLOCK); +//#define malloc(sz) _malloc_dbg(sz, _NORMAL_BLOCK, __FILE__, __LINE__) +//#define realloc(p, sz) _realloc_dbg(p, sz, _NORMAL_BLOCK, __FILE__, __LINE__) +//#endif + +namespace rw { + +Engine *engine; +PluginList Engine::s_plglist(sizeof(Engine)); +Engine::State Engine::state = Dead; +MemoryFunctions Engine::memfuncs; +PluginList Driver::s_plglist[NUM_PLATFORMS]; + +const char *allocLocation; + +void *malloc_h(size_t sz, uint32 hint) { if(sz == 0) return nil; return malloc(sz); } +void *realloc_h(void *p, size_t sz, uint32 hint) { return realloc(p, sz); } + +struct MemoryBlock +{ + size_t sz; + uint32 hint; + void *origPtr; + const char *codeline; + LLLink inAllocList; +}; +LinkList allocations; +size_t totalMemoryAllocated; + +// We align managed memory blocks on a 16 byte boundary + +#define ALIGN16(x) ((x) + 0xF & ~0xF) +void* +malloc_managed(size_t sz, uint32 hint) +{ + void *origPtr; + uint8 *data; + MemoryBlock *mem; + + if(sz == 0) return nil; + origPtr = malloc(sz + sizeof(MemoryBlock) + 15); + if(origPtr == nil) + return nil; + totalMemoryAllocated += sz; + data = (uint8*)origPtr; + data += sizeof(MemoryBlock); + data = (uint8*)ALIGN16((uintptr)data); + mem = (MemoryBlock*)(data-sizeof(MemoryBlock)); + + mem->sz = sz; + mem->hint = hint; + mem->origPtr = origPtr; + mem->codeline = allocLocation; + allocations.add(&mem->inAllocList); + + return data; +} + +void* +realloc_managed(void *p, size_t sz, uint32 hint) +{ + void *origPtr; + MemoryBlock *mem; + uint32 offset; + + if(p == nil) + return malloc_managed(sz, hint); + + mem = (MemoryBlock*)((uint8*)p-sizeof(MemoryBlock)); + offset = (uint8*)p - (uint8*)mem->origPtr; + + mem->inAllocList.remove(); + + origPtr = realloc(mem->origPtr, sz + sizeof(MemoryBlock) + 15); + if(origPtr == nil){ + allocations.add(&mem->inAllocList); + return nil; + } + p = (uint8*)origPtr + offset; + mem = (MemoryBlock*)((uint8*)p-sizeof(MemoryBlock)); + totalMemoryAllocated -= mem->sz; + mem->sz = sz; + mem->hint = hint; + mem->origPtr = origPtr; + mem->codeline = allocLocation; + allocations.add(&mem->inAllocList); + totalMemoryAllocated += mem->sz; + + return p; +} + +void +free_managed(void *p) +{ + MemoryBlock *mem; + if(p == nil) + return; + mem = (MemoryBlock*)((uint8*)p-sizeof(MemoryBlock)); + mem->inAllocList.remove(); + totalMemoryAllocated -= mem->sz; + free(mem->origPtr); +} + +void +printleaks(void) +{ + FORLIST(lnk, allocations){ + MemoryBlock *mem = LLLinkGetData(lnk, MemoryBlock, inAllocList); + printf("sz %zu hint %X\n %s\n", mem->sz, mem->hint, mem->codeline); + } +} + +// TODO: make the debug out configurable +void *mustmalloc_h(size_t sz, uint32 hint) +{ + void *ret; + ret = Engine::memfuncs.rwmalloc(sz, hint); + if(ret || sz == 0) + return ret; + fprintf(stderr, "Error: out of memory\n"); + exit(1); + return nil; +} +void *mustrealloc_h(void *p, size_t sz, uint32 hint) +{ + void *ret; + ret = Engine::memfuncs.rwrealloc(p, sz, hint); + if(ret || sz == 0) + return ret; + fprintf(stderr, "Error: out of memory\n"); + exit(1); + return nil; +} + +char *strdup_LOC(const char *s, uint32 hint, const char *here) { + char *t; + size_t sz = strlen(s)+1; + t = (char*)malloc_LOC(sz, hint, here); + if(t) + memcpy(t, s, sz); + return t; +} + +MemoryFunctions defaultMemfuncs = { + malloc_h, + realloc_h, + free, + nil, + nil +}; + +MemoryFunctions managedMemfuncs = { + malloc_managed, + realloc_managed, + free_managed, + nil, + nil +}; + +// This function mainly registers engine plugins +bool32 +Engine::init(MemoryFunctions *memfuncs) +{ + if(engine || Engine::state != Dead){ + RWERROR((ERR_ENGINEINIT)); + return 0; + } + + totalMemoryAllocated = 0; + allocations.init(); + + if(memfuncs) + Engine::memfuncs = *memfuncs; + else + Engine::memfuncs = defaultMemfuncs; + + if(Engine::memfuncs.rwmustmalloc == nil) + Engine::memfuncs.rwmustmalloc = mustmalloc_h; + if(Engine::memfuncs.rwmustrealloc == nil) + Engine::memfuncs.rwmustrealloc = mustrealloc_h; + + PluginList::open(); + + for(uint i = 0; i < NUM_PLATFORMS; i++) + new (&Driver::s_plglist[i]) PluginList(sizeof(Driver)); + + // core plugin attach here + Frame::registerModule(); + Image::registerModule(); + Raster::registerModule(); + Texture::registerModule(); + + // TODO: reset all allocation counts here. or maybe do that in modules? + Frame::numAllocated = 0; + Image::numAllocated = 0; + Raster::numAllocated = 0; + Texture::numAllocated = 0; + TexDictionary::numAllocated = 0; + Geometry::numAllocated = 0; + Material::numAllocated = 0; + Atomic::numAllocated = 0; + Light::numAllocated = 0; + Camera::numAllocated = 0; + Clump::numAllocated = 0; + World::numAllocated = 0; + + // driver plugin attach + ps2::registerPlatformPlugins(); + xbox::registerPlatformPlugins(); + d3d8::registerPlatformPlugins(); + d3d9::registerPlatformPlugins(); + wdgl::registerPlatformPlugins(); + gl3::registerPlatformPlugins(); + + Engine::state = Initialized; + return 1; +} + +// This is where RW allocates the engine and e.g. opens d3d +bool32 +Engine::open(EngineOpenParams *p) +{ + if(engine || Engine::state != Initialized){ + RWERROR((ERR_ENGINEOPEN)); + return 0; + } + + // Allocate engine + engine = (Engine*)rwNew(Engine::s_plglist.size, MEMDUR_GLOBAL); + engine->currentCamera = nil; + engine->currentWorld = nil; + + // Initialize device + // Device and possibly OS specific! +#ifdef RW_PS2 + engine->device = ps2::renderdevice; +#elif RW_GL3 + engine->device = gl3::renderdevice; +#elif RW_D3D9 + engine->device = d3d::renderdevice; +#else + engine->device = null::renderdevice; +#endif + + engine->device.system(DEVICEOPEN, (void*)p, 0); + + engine->dummyDefaultPipeline = ObjPipeline::create(); + for(uint i = 0; i < NUM_PLATFORMS; i++){ + rw::engine->driver[i] = (Driver*)rwNew(Driver::s_plglist[i].size, + MEMDUR_GLOBAL); + + engine->driver[i]->defaultPipeline = engine->dummyDefaultPipeline; + + engine->driver[i]->rasterCreate = null::rasterCreate; + engine->driver[i]->rasterLock = null::rasterLock; + engine->driver[i]->rasterUnlock = null::rasterUnlock; + engine->driver[i]->rasterLockPalette = null::rasterLockPalette; + engine->driver[i]->rasterUnlockPalette = null::rasterUnlockPalette; + engine->driver[i]->rasterNumLevels = null::rasterNumLevels; + engine->driver[i]->imageFindRasterFormat = null::imageFindRasterFormat; + engine->driver[i]->rasterFromImage = null::rasterFromImage; + engine->driver[i]->rasterToImage = null::rasterToImage; + } + + Engine::state = Opened; + return 1; +} + +// This is where RW creates the actual rendering device +// and calls the engine plugin ctors +bool32 +Engine::start(void) +{ + if(engine == nil || Engine::state != Opened){ + RWERROR((ERR_ENGINESTART)); + return 0; + } + + engine->device.system(DEVICEINIT, nil, 0); + + Engine::s_plglist.construct(engine); + for(uint i = 0; i < NUM_PLATFORMS; i++) + Driver::s_plglist[i].construct(rw::engine->driver[i]); + + engine->device.system(DEVICEFINALIZE, nil, 0); + + // Register some image formats. Or should we leave that to the user? + Image::registerFileFormat("tga", readTGA, writeTGA); + Image::registerFileFormat("bmp", readBMP, writeBMP); + Image::registerFileFormat("png", readPNG, writePNG); + + Engine::state = Started; + return 1; +} + +void +Engine::term(void) +{ + if(engine || Engine::state != Initialized){ + RWERROR((ERR_GENERAL)); + return; + } + + PluginList::close(); + + // This has to be reset because it won't be opened again otherwise + // TODO: maybe reset more stuff here? + d3d::nativeRasterOffset = 0; + + Engine::state = Dead; +} + +void +Engine::close(void) +{ + if(engine == nil || Engine::state != Opened){ + RWERROR((ERR_GENERAL)); + return; + } + + engine->device.system(DEVICECLOSE, nil, 0); + for(uint i = 0; i < NUM_PLATFORMS; i++) + rwFree(rw::engine->driver[i]); + engine->dummyDefaultPipeline->destroy(); + rwFree(engine); + engine = nil; + Engine::state = Initialized; +} + +void +Engine::stop(void) +{ + if(engine == nil || Engine::state != Started){ + RWERROR((ERR_GENERAL)); + return; + } + + for(uint i = 0; i < NUM_PLATFORMS; i++) + Driver::s_plglist[i].destruct(rw::engine->driver[i]); + Engine::s_plglist.destruct(engine); + + engine->device.system(DEVICETERM, nil, 0); + + Engine::state = Opened; +} + + +int32 +Engine::getNumSubSystems(void) +{ + return engine->device.system(DEVICEGETNUMSUBSYSTEMS, nil, 0); +} + +int32 +Engine::getCurrentSubSystem(void) +{ + return engine->device.system(DEVICEGETCURRENTSUBSYSTEM, nil, 0); +} + +bool32 +Engine::setSubSystem(int32 subsys) +{ + return engine->device.system(DEVICESETSUBSYSTEM, nil, subsys); +} + +SubSystemInfo* +Engine::getSubSystemInfo(SubSystemInfo *info, int32 subsys) +{ + if(engine->device.system(DEVICEGETSUBSSYSTEMINFO, info, subsys)) + return info; + return nil; +} + + +int32 +Engine::getNumVideoModes(void) +{ + return engine->device.system(DEVICEGETNUMVIDEOMODES, nil, 0); +} + +int32 +Engine::getCurrentVideoMode(void) +{ + return engine->device.system(DEVICEGETCURRENTVIDEOMODE, nil, 0); +} + +bool32 +Engine::setVideoMode(int32 mode) +{ + return engine->device.system(DEVICESETVIDEOMODE, nil, mode); +} + +VideoMode* +Engine::getVideoModeInfo(VideoMode *info, int32 mode) +{ + if(engine->device.system(DEVICEGETVIDEOMODEINFO, info, mode)) + return info; + return nil; +} + + +uint32 +Engine::getMaxMultiSamplingLevels(void) +{ + return engine->device.system(DEVICEGETMAXMULTISAMPLINGLEVELS, nil, 0); +} + +uint32 +Engine::getMultiSamplingLevels(void) +{ + return engine->device.system(DEVICEGETMULTISAMPLINGLEVELS, nil, 0); +} + +bool32 +Engine::setMultiSamplingLevels(uint32 levels) +{ + return engine->device.system(DEVICESETMULTISAMPLINGLEVELS, nil, levels); +} + + +namespace null { + +void beginUpdate(Camera*) { } +void endUpdate(Camera*) { } +void clearCamera(Camera*,RGBA*,uint32) { } +void showRaster(Raster*,uint32) { } + +void setRenderState(int32, void*) { } +void *getRenderState(int32) { return 0; } + +bool32 rasterRenderFast(Raster *raster, int32 x, int32 y) { return 0; } + +void im2DRenderLine(void*, int32, int32, int32) { } +void im2DRenderTriangle(void*, int32, int32, int32, int32) { } +void im2DRenderPrimitive(PrimitiveType, void*, int32) { } +void im2DRenderIndexedPrimitive(PrimitiveType, void*, int32, void*, int32) { } + +void im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags) { } +void im3DRenderPrimitive(PrimitiveType primType) { } +void im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices) { } +void im3DEnd(void) { } + +Raster* +rasterCreate(Raster*) +{ + assert(0 && "rasterCreate not implemented"); + return nil; +} + +uint8* +rasterLock(Raster*, int32, int32) +{ + assert(0 && "lockRaster not implemented"); + return nil; +} + +void +rasterUnlock(Raster*, int32) +{ + assert(0 && "unlockRaster not implemented"); +} + +uint8* +rasterLockPalette(Raster*, int32) +{ + assert(0 && "rasterLockPalette not implemented"); + return nil; +} + +void +rasterUnlockPalette(Raster*) +{ + assert(0 && "rasterUnlockPalette not implemented"); +} + +int32 +rasterNumLevels(Raster*) +{ + assert(0 && "rasterNumLevels not implemented"); + return 0; +} + +bool32 +imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format) +{ + assert(0 && "imageFindRasterFormat not implemented"); + return 0; +} + +bool32 +rasterFromImage(Raster*, Image*) +{ + assert(0 && "rasterFromImage not implemented"); + return 0; +} + +Image* +rasterToImage(Raster*) +{ + assert(0 && "rasterToImage not implemented"); + return nil; +} + +int +deviceSystem(DeviceReq req, void *arg0, int32 n) +{ + switch(req){ + case DEVICEGETNUMSUBSYSTEMS: + return 0; + case DEVICEGETCURRENTSUBSYSTEM: + return 0; + case DEVICEGETSUBSSYSTEMINFO: + return 0; + default: break; + } + return 1; +} + +Device renderdevice = { + 0.0f, 1.0f, + null::beginUpdate, + null::endUpdate, + null::clearCamera, + null::showRaster, + null::rasterRenderFast, + null::setRenderState, + null::getRenderState, + null::im2DRenderLine, + null::im2DRenderTriangle, + null::im2DRenderPrimitive, + null::im2DRenderIndexedPrimitive, + null::im3DTransform, + null::im3DRenderPrimitive, + null::im3DRenderIndexedPrimitive, + null::im3DEnd, + null::deviceSystem +}; + +} +} diff --git a/vendor/librw/src/error.cpp b/vendor/librw/src/error.cpp new file mode 100644 index 0000000..bdf4080 --- /dev/null +++ b/vendor/librw/src/error.cpp @@ -0,0 +1,49 @@ +#include +#include + +#include "rwbase.h" +#include "rwerror.h" + +namespace rw { + +static Error error; + +void +setError(Error *e) +{ + error = *e; +} + +Error* +getError(Error *e) +{ + *e = error; + error.plugin = 0; + error.code = 0; + return e; +} + +#define ECODE(c, s) s + +const char *errstrs[] = { + "No error", +#include "base.err" +}; + +#undef ECODE + +char* +dbgsprint(uint32 code, ...) +{ + va_list ap; + static char strbuf[512]; + + if(code & 0x80000000) + code &= ~0x80000000; + va_start(ap, code); + vsprintf(strbuf, errstrs[code], ap); + va_end(ap); + return strbuf; +} + +} diff --git a/vendor/librw/src/frame.cpp b/vendor/librw/src/frame.cpp new file mode 100644 index 0000000..c8dabd5 --- /dev/null +++ b/vendor/librw/src/frame.cpp @@ -0,0 +1,463 @@ +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_FRAMELIST + +namespace rw { + +int32 Frame::numAllocated; + +PluginList Frame::s_plglist(sizeof(Frame)); +static void *frameOpen(void *object, int32 offset, int32 size) { engine->frameDirtyList.init(); return object; } +static void *frameClose(void *object, int32 offset, int32 size) { return object; } + +void +Frame::registerModule(void) +{ + Engine::registerPlugin(0, ID_FRAMEMODULE, frameOpen, frameClose); +} + +Frame* +Frame::create(void) +{ + Frame *f = (Frame*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_FRAMELIST); + if(f == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + f->object.init(Frame::ID, 0); + f->objectList.init(); + f->child = nil; + f->next = nil; + f->root = f; + f->matrix.setIdentity(); + f->ltm.setIdentity(); + s_plglist.construct(f); + return f; +} + +Frame* +Frame::cloneHierarchy(void) +{ + Frame *frame = this->cloneAndLink(); + this->purgeClone(); + return frame; +} +void +Frame::destroy(void) +{ + FORLIST(lnk, this->objectList) + ObjectWithFrame::fromFrame(lnk)->setFrame(nil); + s_plglist.destruct(this); + if(this->getParent()) + this->removeChild(); + if(this->object.privateFlags & Frame::HIERARCHYSYNC) + this->inDirtyList.remove(); + for(Frame *f = this->child; f; f = f->next) + f->object.parent = nil; + rwFree(this); + numAllocated--; +} + +void +Frame::destroyHierarchy(void) +{ + Frame *next; + for(Frame *child = this->child; child; child = next){ + next = child->next; + child->destroyHierarchy(); + } + assert(this->objectList.isEmpty()); + s_plglist.destruct(this); + if(this->object.privateFlags & Frame::HIERARCHYSYNC) + this->inDirtyList.remove(); + rwFree(this); +} + +Frame* +Frame::addChild(Frame *child, bool32 append) +{ + Frame *c; + if(child->getParent()) + child->removeChild(); + if(append){ + if(this->child == nil) + this->child = child; + else{ + for(c = this->child; c->next; c = c->next); + c->next = child; + } + child->next = nil; + }else{ + child->next = this->child; + this->child = child; + } + child->object.parent = this; + child->root = this->root; + for(c = child->child; c; c = c->next) + c->setHierarchyRoot(this); + // If the child was a root, remove from dirty list + if(child->object.privateFlags & Frame::HIERARCHYSYNC){ + child->inDirtyList.remove(); + child->object.privateFlags &= ~Frame::HIERARCHYSYNC; + } + this->updateObjects(); + return this; +} + +Frame* +Frame::removeChild(void) +{ + Frame *parent = this->getParent(); + Frame *child = parent->child; + if(child == this) + parent->child = this->next; + else{ + while(child->next != this) + child = child->next; + child->next = this->next; + } + this->object.parent = this->next = nil; + // give the hierarchy a new root + this->setHierarchyRoot(this); + this->updateObjects(); + return this; +} + +Frame* +Frame::forAllChildren(Callback cb, void *data) +{ + Frame *next; + for(Frame *f = this->child; f; f = next){ + next = f->next; + if(cb(f, data) == nil) + return this; + } + return this; +} + +static Frame* +countCB(Frame *f, void *count) +{ + (*(int32*)count)++; + f->forAllChildren(countCB, count); + return f; +} + +int32 +Frame::count(void) +{ + int32 count = 1; + this->forAllChildren(countCB, (void*)&count); + return count; +} + +/* + * Synching is a bit complicated. If anything in the hierarchy is not synched, + * the root of the hierarchy is marked with the HIERARCHYSYNC flags. + * Every unsynched frame is marked with the SUBTREESYNC flags. + * If the LTM is not synched, the LTM flags are set. + * If attached objects need synching, the OBJ flags are set. + */ + +/* Synch just LTM matrices in a hierarchy */ +static void +syncLTMRecurse(Frame *frame, uint8 hierarchyFlags) +{ + for(; frame; frame = frame->next){ + // If frame is dirty or any parent was dirty, update LTM + hierarchyFlags |= frame->object.privateFlags; + if(hierarchyFlags & Frame::SUBTREESYNCLTM){ + Matrix::mult(&frame->ltm, &frame->matrix, + &frame->getParent()->ltm); + frame->object.privateFlags &= ~Frame::SUBTREESYNCLTM; + } + // And synch all children + syncLTMRecurse(frame->child, hierarchyFlags); + } +} + +/* Synch just objects in a hierarchy */ +static void +syncObjRecurse(Frame *frame) +{ + for(; frame; frame = frame->next){ + // Synch attached objects + FORLIST(lnk, frame->objectList) + ObjectWithFrame::fromFrame(lnk)->sync(); + frame->object.privateFlags &= ~Frame::SUBTREESYNCOBJ; + // And synch all children + syncObjRecurse(frame->child); + } +} + +/* Synch LTM and objects */ +static void +syncRecurse(Frame *frame, uint8 hierarchyFlags) +{ + for(; frame; frame = frame->next){ + // If frame is dirty or any parent was dirty, update LTM + hierarchyFlags |= frame->object.privateFlags; + if(hierarchyFlags & Frame::SUBTREESYNCLTM) + Matrix::mult(&frame->ltm, &frame->matrix, + &frame->getParent()->ltm); + // Synch attached objects + FORLIST(lnk, frame->objectList) + ObjectWithFrame::fromFrame(lnk)->sync(); + frame->object.privateFlags &= ~Frame::SUBTREESYNC; + // And synch all children + syncRecurse(frame->child, hierarchyFlags); + } +} + +/* Sync the LTMs of the hierarchy of which 'this' is the root */ +void +Frame::syncHierarchyLTM(void) +{ + // Sync root's LTM + if(this->object.privateFlags & Frame::SUBTREESYNCLTM) + this->ltm = this->matrix; + // ...and children + syncLTMRecurse(this->child, this->object.privateFlags); + // all clean now + this->object.privateFlags &= ~Frame::SYNCLTM; +} + +Matrix* +Frame::getLTM(void) +{ + if(this->root->object.privateFlags & Frame::HIERARCHYSYNCLTM) + this->root->syncHierarchyLTM(); + return &this->ltm; +} + +/* Synch all dirty frames; LTMs and objects */ +void +Frame::syncDirty(void) +{ + Frame *frame; + FORLIST(lnk, engine->frameDirtyList){ + frame = LLLinkGetData(lnk, Frame, inDirtyList); + if(frame->object.privateFlags & Frame::HIERARCHYSYNCLTM){ + // Sync root's LTM + if(frame->object.privateFlags & Frame::SUBTREESYNCLTM) + frame->ltm = frame->matrix; + // Synch attached objects + FORLIST(lnk, frame->objectList) + ObjectWithFrame::fromFrame(lnk)->sync(); + // ...and children + syncRecurse(frame->child, frame->object.privateFlags); + }else{ + // LTMs are clean, just synch objects + FORLIST(lnk, frame->objectList) + ObjectWithFrame::fromFrame(lnk)->sync(); + syncObjRecurse(frame->child); + } + // all clean now + frame->object.privateFlags &= ~(Frame::SYNCLTM | Frame::SYNCOBJ); + } + engine->frameDirtyList.init(); +} + +void +Frame::rotate(const V3d *axis, float32 angle, CombineOp op) +{ + this->matrix.rotate(axis, angle, op); + updateObjects(); +} + +void +Frame::translate(const V3d *trans, CombineOp op) +{ + this->matrix.translate(trans, op); + updateObjects(); +} + +void +Frame::scale(const V3d *scl, CombineOp op) +{ + this->matrix.scale(scl, op); + updateObjects(); +} + +void +Frame::transform(const Matrix *mat, CombineOp op) +{ + this->matrix.transform(mat, op); + updateObjects(); +} + +void +Frame::updateObjects(void) +{ + // Mark root as dirty and insert into dirty list if necessary + if((this->root->object.privateFlags & HIERARCHYSYNC) == 0) + engine->frameDirtyList.add(&this->root->inDirtyList); + this->root->object.privateFlags |= HIERARCHYSYNC; + // Mark subtree as dirty as well + this->object.privateFlags |= SUBTREESYNC; +} + +void +Frame::setHierarchyRoot(Frame *root) +{ + this->root = root; + for(Frame *child = this->child; child; child = child->next) + child->setHierarchyRoot(root); +} + +static Frame* +cloneRecurse(Frame *old, Frame *newroot) +{ + Frame *frame = Frame::create(); + if(newroot == nil) + newroot = frame; + frame->object.copy(&old->object); + frame->matrix = old->matrix; + frame->root = newroot; + old->root = frame; // Remember cloned frame + for(Frame *child = old->child; child; child = child->next){ + Frame *clonedchild = cloneRecurse(child, newroot); + clonedchild->next = frame->child; + frame->child = clonedchild; + clonedchild->object.parent = frame; + } + Frame::s_plglist.copy(frame, old); + return frame; +} + +// Clone a frame hierarchy. Link cloned frames into Frame::root of the originals. +Frame* +Frame::cloneAndLink(void) +{ + Frame *newhier = cloneRecurse(this, nil); + if(newhier){ + // frame is not in dirty list so important to get this flag right + newhier->object.privateFlags &= ~HIERARCHYSYNC; + newhier->updateObjects(); + } + return newhier; +} + +// Remove links to cloned frames from hierarchy. +void +Frame::purgeClone(void) +{ + Frame *parent = this->getParent(); + this->setHierarchyRoot(parent ? parent->root : this); +} + +struct FrameStreamData +{ + V3d right, up, at, pos; + int32 parent; + int32 matflag; +}; + +FrameList_* +FrameList_::streamRead(Stream *stream) +{ + FrameStreamData buf; + this->numFrames = 0; + this->frames = nil; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + this->numFrames = stream->readI32(); + this->frames = (Frame**)rwMalloc(this->numFrames*sizeof(Frame*), MEMDUR_EVENT | ID_FRAMELIST); + if(this->frames == nil){ + RWERROR((ERR_ALLOC, this->numFrames*sizeof(Frame*))); + return nil; + } + for(int32 i = 0; i < this->numFrames; i++){ + Frame *f; + stream->read32(&buf, sizeof(buf)); + this->frames[i] = f = Frame::create(); + if(f == nil){ + // TODO: clean up frames? + rwFree(this->frames); + return nil; + } + f->matrix.right = buf.right; + f->matrix.up = buf.up; + f->matrix.at = buf.at; + f->matrix.pos = buf.pos; + f->matrix.optimize(); + // RW always removes identity flag + f->matrix.flags &= ~Matrix::IDENTITY; + if(buf.parent >= 0) + this->frames[buf.parent]->addChild(f, rw::streamAppendFrames); + } + for(int32 i = 0; i < this->numFrames; i++) + Frame::s_plglist.streamRead(stream, this->frames[i]); + return this; +} + +void +FrameList_::streamWrite(Stream *stream) +{ + FrameStreamData buf; + + int size = 0, structsize = 0; + structsize = 4 + this->numFrames*sizeof(FrameStreamData); + size += 12 + structsize; + for(int32 i = 0; i < this->numFrames; i++) + size += 12 + Frame::s_plglist.streamGetSize(this->frames[i]); + + writeChunkHeader(stream, ID_FRAMELIST, size); + writeChunkHeader(stream, ID_STRUCT, structsize); + stream->writeU32(this->numFrames); + for(int32 i = 0; i < this->numFrames; i++){ + Frame *f = this->frames[i]; + buf.right = f->matrix.right; + buf.up = f->matrix.up; + buf.at = f->matrix.at; + buf.pos = f->matrix.pos; + buf.parent = findPointer(f->getParent(), (void**)this->frames, + this->numFrames); + buf.matflag = 0; //f->matflag; + stream->write32(&buf, sizeof(buf)); + } + for(int32 i = 0; i < this->numFrames; i++) + Frame::s_plglist.streamWrite(stream, this->frames[i]); +} + +static Frame* +sizeCB(Frame *f, void *size) +{ + *(int32*)size += Frame::s_plglist.streamGetSize(f); + f->forAllChildren(sizeCB, size); + return f; +} + +uint32 +FrameList_::streamGetSize(Frame *f) +{ + int32 numFrames = f->count(); + uint32 size = 12 + 12 + 4 + numFrames*(sizeof(FrameStreamData)+12); + sizeCB(f, (void*)&size); + return size; +} + +Frame** +makeFrameList(Frame *frame, Frame **flist) +{ + *flist++ = frame; + if(frame->next) + flist = makeFrameList(frame->next, flist); + if(frame->child) + flist = makeFrameList(frame->child, flist); + return flist; +} + +} diff --git a/vendor/librw/src/geometry.cpp b/vendor/librw/src/geometry.cpp new file mode 100644 index 0000000..5d31c34 --- /dev/null +++ b/vendor/librw/src/geometry.cpp @@ -0,0 +1,1052 @@ +#include +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_GEOMETRY + +namespace rw { + +int32 Geometry::numAllocated; +int32 Material::numAllocated; + +PluginList Geometry::s_plglist(sizeof(Geometry)); +PluginList Material::s_plglist(sizeof(Material)); + +static SurfaceProperties defaultSurfaceProps = { 1.0f, 1.0f, 1.0f }; + +// We allocate twice because we have to allocate the data separately for uninstancing +Geometry* +Geometry::create(int32 numVerts, int32 numTris, uint32 flags) +{ + Geometry *geo = (Geometry*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_GEOMETRY); + if(geo == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + geo->object.init(Geometry::ID, 0); + geo->flags = flags & 0xFF00FFFF; + geo->numTexCoordSets = (flags & 0xFF0000) >> 16; + if(geo->numTexCoordSets == 0) + geo->numTexCoordSets = (geo->flags & TEXTURED) ? 1 : + (geo->flags & TEXTURED2) ? 2 : 0; + geo->numTriangles = numTris; + geo->numVertices = numVerts; + + geo->colors = nil; + for(int32 i = 0; i < 8; i++) + geo->texCoords[i] = nil; + geo->triangles = nil; + // Allocate all attributes at once. The triangle pointer + // will hold the first address (even when there are no triangles) + // so we can free easily. + if(!(geo->flags & NATIVE)){ + int32 sz = geo->numTriangles*sizeof(Triangle); + if(geo->flags & PRELIT) + sz += geo->numVertices*sizeof(RGBA); + sz += geo->numTexCoordSets*geo->numVertices*sizeof(TexCoords); + + uint8 *data = (uint8*)rwNew(sz, MEMDUR_EVENT | ID_GEOMETRY); + geo->triangles = (Triangle*)data; + data += geo->numTriangles*sizeof(Triangle); + if(geo->flags & PRELIT && geo->numVertices){ + geo->colors = (RGBA*)data; + data += geo->numVertices*sizeof(RGBA); + } + if(geo->numVertices) + for(int32 i = 0; i < geo->numTexCoordSets; i++){ + geo->texCoords[i] = (TexCoords*)data; + data += geo->numVertices*sizeof(TexCoords); + } + + // init triangles + for(int32 i = 0; i < geo->numTriangles; i++) + geo->triangles[i].matId = 0xFFFF; + } + geo->numMorphTargets = 0; + geo->morphTargets = nil; + geo->addMorphTargets(1); + + geo->matList.init(); + geo->lockedSinceInst = 0; + geo->meshHeader = nil; + geo->instData = nil; + geo->refCount = 1; + + s_plglist.construct(geo); + return geo; +} + +void +Geometry::destroy(void) +{ + this->refCount--; + if(this->refCount <= 0){ + s_plglist.destruct(this); + // Also frees colors and tex coords + rwFree(this->triangles); + // Also frees their data + rwFree(this->morphTargets); + // Also frees indices + rwFree(this->meshHeader); + this->matList.deinit(); + rwFree(this); + numAllocated--; + } +} + +void +Geometry::lock(int32 lockFlags) +{ + lockedSinceInst |= lockFlags; + if(lockFlags & LOCKPOLYGONS){ + rwFree(this->meshHeader); + this->meshHeader = nil; + } +} + +void +Geometry::unlock(void) +{ + if(this->meshHeader == nil) + this->buildMeshes(); + +} + +struct GeoStreamData +{ + uint32 flags; + int32 numTriangles; + int32 numVertices; + int32 numMorphTargets; +}; + +Geometry* +Geometry::streamRead(Stream *stream) +{ + uint32 version; + GeoStreamData buf; + SurfaceProperties surfProps; + MaterialList *ret; + static SurfaceProperties reset = { 1.0f, 1.0f, 1.0f }; + + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(&buf, sizeof(buf)); + Geometry *geo = Geometry::create(buf.numVertices, + buf.numTriangles, buf.flags); + if(geo == nil) + return nil; + geo->addMorphTargets(buf.numMorphTargets-1); + if(version < 0x34000) + stream->read32(&surfProps, 12); + + if(!(geo->flags & NATIVE)){ + if(geo->flags & PRELIT) + stream->read8(geo->colors, 4*geo->numVertices); + for(int32 i = 0; i < geo->numTexCoordSets; i++) + stream->read32(geo->texCoords[i], + 2*geo->numVertices*4); + for(int32 i = 0; i < geo->numTriangles; i++){ + uint32 tribuf[2]; + stream->read32(tribuf, 8); + geo->triangles[i].v[0] = tribuf[0] >> 16; + geo->triangles[i].v[1] = tribuf[0]; + geo->triangles[i].v[2] = tribuf[1] >> 16; + geo->triangles[i].matId = tribuf[1]; + } + } + + for(int32 i = 0; i < geo->numMorphTargets; i++){ + MorphTarget *m = &geo->morphTargets[i]; + stream->read32(&m->boundingSphere, 4*4); + int32 hasVertices = stream->readI32(); + int32 hasNormals = stream->readI32(); + if(hasVertices) + stream->read32(m->vertices, 3*geo->numVertices*4); + if(hasNormals) + stream->read32(m->normals, 3*geo->numVertices*4); + } + + if(!findChunk(stream, ID_MATLIST, nil, nil)){ + RWERROR((ERR_CHUNK, "MATLIST")); + goto fail; + } + if(version < 0x34000) + defaultSurfaceProps = surfProps; + + ret = MaterialList::streamRead(stream, &geo->matList); + if(version < 0x34000) + defaultSurfaceProps = reset; + if(ret == nil) + goto fail; + if(s_plglist.streamRead(stream, geo)) + return geo; + +fail: + geo->destroy(); + return nil; +} + +static uint32 +geoStructSize(Geometry *geo) +{ + uint32 size = 0; + size += sizeof(GeoStreamData); + if(version < 0x34000) + size += 12; // surface properties + if(!(geo->flags & Geometry::NATIVE)){ + if(geo->flags&geo->PRELIT) + size += 4*geo->numVertices; + for(int32 i = 0; i < geo->numTexCoordSets; i++) + size += 2*geo->numVertices*4; + size += 4*geo->numTriangles*2; + } + for(int32 i = 0; i < geo->numMorphTargets; i++){ + MorphTarget *m = &geo->morphTargets[i]; + size += 4*4 + 2*4; // bounding sphere and bools + if(!(geo->flags & Geometry::NATIVE)){ + if(m->vertices) + size += 3*geo->numVertices*4; + if(m->normals) + size += 3*geo->numVertices*4; + } + } + return size; +} + +bool +Geometry::streamWrite(Stream *stream) +{ + GeoStreamData buf; + static float32 fbuf[3] = { 1.0f, 1.0f, 1.0f }; + + writeChunkHeader(stream, ID_GEOMETRY, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, geoStructSize(this)); + + buf.flags = this->flags | this->numTexCoordSets << 16; + buf.numTriangles = this->numTriangles; + buf.numVertices = this->numVertices; + buf.numMorphTargets = this->numMorphTargets; + stream->write32(&buf, sizeof(buf)); + if(version < 0x34000) + stream->write32(fbuf, sizeof(fbuf)); + + if(!(this->flags & NATIVE)){ + if(this->flags & PRELIT) + stream->write8(this->colors, 4*this->numVertices); + for(int32 i = 0; i < this->numTexCoordSets; i++) + stream->write32(this->texCoords[i], + 2*this->numVertices*4); + for(int32 i = 0; i < this->numTriangles; i++){ + uint32 tribuf[2]; + tribuf[0] = this->triangles[i].v[0] << 16 | + this->triangles[i].v[1]; + tribuf[1] = this->triangles[i].v[2] << 16 | + this->triangles[i].matId; + stream->write32(tribuf, 8); + } + } + + for(int32 i = 0; i < this->numMorphTargets; i++){ + MorphTarget *m = &this->morphTargets[i]; + stream->write32(&m->boundingSphere, 4*4); + if(!(this->flags & NATIVE)){ + stream->writeI32(m->vertices != nil); + stream->writeI32(m->normals != nil); + if(m->vertices) + stream->write32(m->vertices, + 3*this->numVertices*4); + if(m->normals) + stream->write32(m->normals, + 3*this->numVertices*4); + }else{ + stream->writeI32(0); + stream->writeI32(0); + } + } + + this->matList.streamWrite(stream); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Geometry::streamGetSize(void) +{ + uint32 size = 0; + size += 12 + geoStructSize(this); + size += 12 + this->matList.streamGetSize(); + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +void +Geometry::addMorphTargets(int32 n) +{ + if(n == 0) + return; + n += this->numMorphTargets; + + int32 sz; + sz = sizeof(MorphTarget); + if(!(this->flags & NATIVE)){ + sz += this->numVertices*sizeof(V3d); + if(this->flags & NORMALS) + sz += this->numVertices*sizeof(V3d); + } + + // Memory layout: MorphTarget[n]; (vertices and normals)[n] + MorphTarget *mts; + if(this->numMorphTargets){ + mts = (MorphTarget*)rwResize(this->morphTargets, n*sz, MEMDUR_EVENT | ID_GEOMETRY); + this->morphTargets = mts; + // Since we now have more morph targets than before, move the vertex data up + uint8 *src = (uint8*)mts + sz*this->numMorphTargets; + uint8 *dst = (uint8*)mts + sz*n; + uint32 len = (sz-sizeof(MorphTarget))*this->numMorphTargets; + while(len--) + *--dst = *--src; + }else{ + mts = (MorphTarget*)rwNew(n*sz, MEMDUR_EVENT | ID_GEOMETRY); + this->morphTargets = mts; + } + + // Set up everything and initialize the bounding sphere for new morph targets + V3d *data = (V3d*)&mts[n]; + for(int32 i = 0; i < n; i++){ + mts->parent = this; + mts->vertices = nil; + mts->normals = nil; + if(i >= this->numMorphTargets){ + mts->boundingSphere.center.x = 0.0f; + mts->boundingSphere.center.y = 0.0f; + mts->boundingSphere.center.z = 0.0f; + mts->boundingSphere.radius = 0.0f; + } + if(!(this->flags & NATIVE) && this->numVertices){ + mts->vertices = data; + data += this->numVertices; + if(this->flags & NORMALS){ + mts->normals = data; + data += this->numVertices; + } + } + mts++; + } + this->numMorphTargets = n; +} + +void +Geometry::calculateBoundingSphere(void) +{ + for(int32 i = 0; i < this->numMorphTargets; i++){ + MorphTarget *m = &this->morphTargets[i]; + m->boundingSphere = m->calculateBoundingSphere(); + } +} + +bool32 +Geometry::hasColoredMaterial(void) +{ + for(int32 i = 0; i < this->matList.numMaterials; i++) + if(this->matList.materials[i]->color.red != 255 || + this->matList.materials[i]->color.green != 255 || + this->matList.materials[i]->color.blue != 255 || + this->matList.materials[i]->color.alpha != 255) + return 1; + return 0; +} + +// Force allocate data, even when native flag is set +void +Geometry::allocateData(void) +{ + // Geometry data + // Pretty much copy pasted from ::create above + int32 sz = this->numTriangles*sizeof(Triangle); + if(this->flags & PRELIT) + sz += this->numVertices*sizeof(RGBA); + sz += this->numTexCoordSets*this->numVertices*sizeof(TexCoords); + + uint8 *data = (uint8*)rwNew(sz, MEMDUR_EVENT | ID_GEOMETRY); + this->triangles = (Triangle*)data; + data += this->numTriangles*sizeof(Triangle); + for(int32 i = 0; i < this->numTriangles; i++) + this->triangles[i].matId = 0xFFFF; + if(this->flags & PRELIT){ + this->colors = (RGBA*)data; + data += this->numVertices*sizeof(RGBA); + } + for(int32 i = 0; i < this->numTexCoordSets; i++){ + this->texCoords[i] = (TexCoords*)data; + data += this->numVertices*sizeof(TexCoords); + } + + // MorphTarget data + // Bounding sphere is copied by realloc. + sz = sizeof(MorphTarget) + this->numVertices*sizeof(V3d); + if(this->flags & NORMALS) + sz += this->numVertices*sizeof(V3d); + + MorphTarget *mt = (MorphTarget*)rwResize(this->morphTargets, + sz*this->numMorphTargets, MEMDUR_EVENT | ID_GEOMETRY); + this->morphTargets = mt; + V3d *vdata = (V3d*)&mt[this->numMorphTargets]; + for(int32 i = 0; i < this->numMorphTargets; i++){ + mt->parent = this; + mt->vertices = nil; + mt->normals = nil; + if(this->numVertices){ + mt->vertices = vdata; + vdata += this->numVertices; + if(this->flags & NORMALS){ + mt->normals = vdata; + vdata += this->numVertices; + } + } + mt++; + } +} + +static int +isDegenerate(uint16 *idx) +{ + return idx[0] == idx[1] || + idx[0] == idx[2] || + idx[1] == idx[2]; +} + +// This functions assumes there is enough space allocated +// for triangles. Use MeshHeader::guessNumTriangles() and +// Geometry::allocateData() +void +Geometry::generateTriangles(int8 *adc) +{ + MeshHeader *header = this->meshHeader; + assert(header != nil); + + this->numTriangles = 0; + Mesh *m = header->getMeshes(); + int8 *adcbits = adc; + for(uint32 i = 0; i < header->numMeshes; i++){ + if(m->numIndices < 3){ + // shouldn't happen but it does + adcbits += m->numIndices; + m++; + continue; + } + if(header->flags == MeshHeader::TRISTRIP){ + for(uint32 j = 0; j < m->numIndices-2; j++){ + if(!(adc && adcbits[j+2]) && + !isDegenerate(&m->indices[j])) + this->numTriangles++; + } + }else + this->numTriangles += m->numIndices/3; + adcbits += m->numIndices; + m++; + } + + Triangle *tri = this->triangles; + m = header->getMeshes(); + adcbits = adc; + for(uint32 i = 0; i < header->numMeshes; i++){ + if(m->numIndices < 3){ + adcbits += m->numIndices; + m++; + continue; + } + int32 matid = this->matList.findIndex(m->material); + if(header->flags == MeshHeader::TRISTRIP) + for(uint32 j = 0; j < m->numIndices-2; j++){ + if((adc && adcbits[j+2]) || + isDegenerate(&m->indices[j])) + continue; + tri->v[0] = m->indices[j+0]; + tri->v[1] = m->indices[j+1 + (j%2)]; + tri->v[2] = m->indices[j+2 - (j%2)]; + tri->matId = matid; + tri++; + } + else + for(uint32 j = 0; j < m->numIndices-2; j+=3){ + tri->v[0] = m->indices[j+0]; + tri->v[1] = m->indices[j+1]; + tri->v[2] = m->indices[j+2]; + tri->matId = matid; + tri++; + } + adcbits += m->numIndices; + m++; + } +} + +static void +dumpMesh(Mesh *m) +{ + for(uint32 i = 0; i < m->numIndices-2; i++) +// if(i % 2) +// printf("%3d %3d %3d\n", +// m->indices[i+1], +// m->indices[i], +// m->indices[i+2]); +// else + printf("%d %d %d\n", + m->indices[i], + m->indices[i+1], + m->indices[i+2]); +} + +void +Geometry::buildMeshes(void) +{ + Triangle *tri; + Mesh *mesh; + + if(this->flags & Geometry::NATIVE){ + fprintf(stderr, "WARNING: trying Geometry::buildMeshes() on pre-instanced geometry\n"); + return; + } + + rwFree(this->meshHeader); + this->meshHeader = nil; + int32 numMeshes = this->matList.numMaterials; + if((this->flags & Geometry::TRISTRIP) == 0){ + int32 *numIndices = rwNewT(int32, numMeshes, + MEMDUR_FUNCTION | ID_GEOMETRY); + memset(numIndices, 0, numMeshes*sizeof(int32)); + + // count indices per mesh + tri = this->triangles; + for(int32 i = 0; i < this->numTriangles; i++){ + assert(tri->matId < numMeshes); + numIndices[tri->matId] += 3; + tri++; + } + // setup meshes + this->allocateMeshes(numMeshes, this->numTriangles*3, 0); + mesh = this->meshHeader->getMeshes(); + for(int32 i = 0; i < numMeshes; i++){ + mesh[i].material = this->matList.materials[i]; + mesh[i].numIndices = numIndices[i]; + } + this->meshHeader->setupIndices(); + rwFree(numIndices); + + // now fill in the indices + for(int32 i = 0; i < numMeshes; i++) + mesh[i].numIndices = 0; + tri = this->triangles; + for(int32 i = 0; i < this->numTriangles; i++){ + uint32 idx = mesh[tri->matId].numIndices; + mesh[tri->matId].indices[idx++] = tri->v[0]; + mesh[tri->matId].indices[idx++] = tri->v[1]; + mesh[tri->matId].indices[idx++] = tri->v[2]; + mesh[tri->matId].numIndices = idx; + tri++; + } + }else + this->buildTristrips(); +} + +/* The idea is that even in meshes where winding is not preserved + * every tristrip starts at an even vertex. So find the start of + * strips and insert duplicate vertices if necessary. */ +void +Geometry::correctTristripWinding(void) +{ + MeshHeader *header = this->meshHeader; + if(this->flags & NATIVE || header == nil || + header->flags != MeshHeader::TRISTRIP) + return; + this->meshHeader = nil; + // Allocate no indices, we realloc later + MeshHeader *newhead = this->allocateMeshes(header->numMeshes, 0, 1); + newhead->flags = header->flags; + /* get a temporary working buffer */ + uint16 *indices = rwNewT(uint16, header->totalIndices*2, + MEMDUR_FUNCTION | ID_GEOMETRY); + + Mesh *mesh = header->getMeshes(); + Mesh *newmesh = newhead->getMeshes(); + for(uint16 i = 0; i < header->numMeshes; i++){ + newmesh->numIndices = 0; + newmesh->indices = &indices[newhead->totalIndices]; + newmesh->material = mesh->material; + + bool inStrip = 0; + uint32 j; + for(j = 0; j < mesh->numIndices-2; j++){ + /* Duplicate vertices indicate end of strip */ + if(mesh->indices[j] == mesh->indices[j+1] || + mesh->indices[j+1] == mesh->indices[j+2]) + inStrip = 0; + else if(!inStrip){ + /* Entering strip now, + * make sure winding is correct */ + inStrip = 1; + if(newmesh->numIndices % 2){ + newmesh->indices[newmesh->numIndices] = + newmesh->indices[newmesh->numIndices-1]; + newmesh->numIndices++; + } + } + newmesh->indices[newmesh->numIndices++] = mesh->indices[j]; + } + for(; j < mesh->numIndices; j++) + newmesh->indices[newmesh->numIndices++] = mesh->indices[j]; + newhead->totalIndices += newmesh->numIndices; + + mesh++; + newmesh++; + } + rwFree(header); + // Now allocate indices and copy them + this->allocateMeshes(newhead->numMeshes, newhead->totalIndices, 0); + memcpy(this->meshHeader->getMeshes()->indices, indices, this->meshHeader->totalIndices*2); + rwFree(indices); +} + +void +Geometry::removeUnusedMaterials(void) +{ + if(this->meshHeader == nil) + return; + MeshHeader *mh = this->meshHeader; + Mesh *m = mh->getMeshes(); + for(uint32 i = 0; i < mh->numMeshes; i++) + if(m[i].indices == nil) + return; + + int32 *map = rwNewT(int32, this->matList.numMaterials, + MEMDUR_FUNCTION | ID_GEOMETRY); + Material **materials = rwNewT(Material*,this->matList.numMaterials, + MEMDUR_EVENT | ID_MATERIAL); + int32 numMaterials = 0; + /* Build new material list and map */ + for(uint32 i = 0; i < mh->numMeshes; i++){ + if(m[i].numIndices <= 0) + continue; + materials[numMaterials] = m[i].material; + m[i].material->addRef(); + int32 oldid = this->matList.findIndex(m[i].material); + map[oldid] = numMaterials; + numMaterials++; + } + for(int32 i = 0; i < this->matList.numMaterials; i++) + this->matList.materials[i]->destroy(); + rwFree(this->matList.materials); + this->matList.materials = materials; + this->matList.space = this->matList.numMaterials; + this->matList.numMaterials = numMaterials; + + /* Build new meshes */ + this->meshHeader = nil; + MeshHeader *newmh = this->allocateMeshes(numMaterials, mh->totalIndices, 0); + newmh->flags = mh->flags; + Mesh *newm = newmh->getMeshes(); + for(uint32 i = 0; i < mh->numMeshes; i++){ + if(m[i].numIndices <= 0) + continue; + newm->numIndices = m[i].numIndices; + newm->material = m[i].material; + newm++; + } + newmh->setupIndices(); + /* Copy indices */ + newm = newmh->getMeshes();; + for(uint32 i = 0; i < mh->numMeshes; i++){ + if(m[i].numIndices <= 0) + continue; + memcpy(newm->indices, m[i].indices, + m[i].numIndices*sizeof(*m[i].indices)); + newm++; + } + rwFree(mh); + + /* Remap triangle material IDs */ + for(int32 i = 0; i < this->numTriangles; i++) + this->triangles[i].matId = map[this->triangles[i].matId]; + rwFree(map); +} + +Sphere +MorphTarget::calculateBoundingSphere(void) const +{ + Sphere sphere; + V3d min = { 1000000.0f, 1000000.0f, 1000000.0f }; + V3d max = { -1000000.0f, -1000000.0f, -1000000.0f }; + V3d *v = this->vertices; + for(int32 j = 0; j < this->parent->numVertices; j++){ + if(v->x > max.x) max.x = v->x; + if(v->x < min.x) min.x = v->x; + if(v->y > max.y) max.y = v->y; + if(v->y < min.y) min.y = v->y; + if(v->z > max.z) max.z = v->z; + if(v->z < min.z) min.z = v->z; + v++; + } + sphere.center = scale(add(min, max), 1/2.0f); + max = sub(max, sphere.center); + sphere.radius = length(max); + return sphere; +} + + +// +// MaterialList +// +#undef PLUGIN_ID +#define PLUGIN_ID ID_MATERIAL + +void +MaterialList::init(void) +{ + this->materials = nil; + this->numMaterials = 0; + this->space = 0; +} + +void +MaterialList::deinit(void) +{ + if(this->materials){ + for(int32 i = 0; i < this->numMaterials; i++) + this->materials[i]->destroy(); + rwFree(this->materials); + } +} + +int32 +MaterialList::appendMaterial(Material *mat) +{ + Material **ml; + int32 space; + if(this->numMaterials >= this->space){ + space = this->space + 20; + if(this->materials) + ml = rwReallocT(Material*, this->materials, space, + MEMDUR_EVENT | ID_MATERIAL); + else + ml = rwMallocT(Material*, space, MEMDUR_EVENT | ID_MATERIAL); + if(ml == nil) + return -1; + this->space = space; + this->materials = ml; + } + this->materials[this->numMaterials++] = mat; + mat->addRef(); + return this->numMaterials-1; +} + +int32 +MaterialList::findIndex(Material *mat) +{ + for(int32 i = 0; i < this->numMaterials; i++) + if(this->materials[i] == mat) + return i; + return -1; +} + +MaterialList* +MaterialList::streamRead(Stream *stream, MaterialList *matlist) +{ + int32 *indices = nil; + int32 numMat; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + matlist->init(); + numMat = stream->readI32(); + if(numMat == 0) + return matlist; + matlist->materials = rwMallocT(Material*,numMat, MEMDUR_EVENT | ID_MATERIAL); + if(matlist->materials == nil) + goto fail; + matlist->space = numMat; + + indices = (int32*)rwMalloc(numMat*4, MEMDUR_FUNCTION | ID_MATERIAL); + stream->read32(indices, numMat*4); + + Material *m; + for(int32 i = 0; i < numMat; i++){ + if(indices[i] >= 0){ + m = matlist->materials[indices[i]]; + m->addRef(); + }else{ + if(!findChunk(stream, ID_MATERIAL, nil, nil)){ + RWERROR((ERR_CHUNK, "MATERIAL")); + goto fail; + } + m = Material::streamRead(stream); + if(m == nil) + goto fail; + } + matlist->appendMaterial(m); + m->destroy(); + } + rwFree(indices); + return matlist; +fail: + rwFree(indices); + matlist->deinit(); + return nil; +} + +bool +MaterialList::streamWrite(Stream *stream) +{ + uint32 size = this->streamGetSize(); + writeChunkHeader(stream, ID_MATLIST, size); + writeChunkHeader(stream, ID_STRUCT, 4 + this->numMaterials*4); + stream->writeI32(this->numMaterials); + + int32 idx; + for(int32 i = 0; i < this->numMaterials; i++){ + idx = -1; + for(int32 j = i-1; j >= 0; j--) + if(this->materials[i] == this->materials[j]){ + idx = j; + break; + } + stream->writeI32(idx); + } + for(int32 i = 0; i < this->numMaterials; i++){ + for(int32 j = i-1; j >= 0; j--) + if(this->materials[i] == this->materials[j]) + goto found; + this->materials[i]->streamWrite(stream); + found:; + } + return true; +} + +uint32 +MaterialList::streamGetSize(void) +{ + uint32 size = 12 + 4 + this->numMaterials*4; + for(int32 i = 0; i < this->numMaterials; i++){ + for(int32 j = i-1; j >= 0; j--) + if(this->materials[i] == this->materials[j]) + goto found; + size += 12 + this->materials[i]->streamGetSize(); + found:; + } + return size; +} + +// +// Material +// + +Material* +Material::create(void) +{ + Material *mat = (Material*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_MATERIAL); + if(mat == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + mat->texture = nil; + memset(&mat->color, 0xFF, 4); + mat->surfaceProps = defaultSurfaceProps; + mat->pipeline = nil; + mat->refCount = 1; + s_plglist.construct(mat); + return mat; +} + +Material* +Material::clone(void) +{ + Material *mat = Material::create(); + if(mat == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + mat->color = this->color; + mat->surfaceProps = this->surfaceProps; + if(this->texture) + mat->setTexture(this->texture); + mat->pipeline = this->pipeline; + s_plglist.copy(mat, this); + return mat; +} + +void +Material::destroy(void) +{ + this->refCount--; + if(this->refCount <= 0){ + s_plglist.destruct(this); + if(this->texture) + this->texture->destroy(); + rwFree(this); + numAllocated--; + } +} + +void +Material::setTexture(Texture *tex) +{ + if(this->texture) + this->texture->destroy(); + if(tex) + tex->addRef(); + this->texture = tex; +} + +struct MatStreamData +{ + int32 flags; // unused according to RW + RGBA color; + int32 unused; + int32 textured; +}; + +static uint32 materialRights[2]; + +Material* +Material::streamRead(Stream *stream) +{ + uint32 length, version; + MatStreamData buf; + + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read8(&buf, sizeof(buf)); + RGBA col = buf.color; + memNative32(&buf, sizeof(buf)); + buf.color = col; + Material *mat = Material::create(); + if(mat == nil) + return nil; + mat->color = buf.color; + if(version < 0x30400) + mat->surfaceProps = defaultSurfaceProps; + else + stream->read32(&mat->surfaceProps, sizeof(SurfaceProperties)); + if(buf.textured){ + if(!findChunk(stream, ID_TEXTURE, &length, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + goto fail; + } + mat->texture = Texture::streamRead(stream); + } + + materialRights[0] = 0; + if(!s_plglist.streamRead(stream, mat)) + goto fail; + if(materialRights[0]) + s_plglist.assertRights(mat, materialRights[0], materialRights[1]); + return mat; + +fail: + mat->destroy(); + return nil; +} + +bool +Material::streamWrite(Stream *stream) +{ + MatStreamData buf; + + writeChunkHeader(stream, ID_MATERIAL, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, sizeof(MatStreamData) + + (rw::version >= 0x30400 ? 12 : 0)); + + buf.color = this->color; + buf.flags = 0; + buf.unused = 0; + buf.textured = this->texture != nil; + memLittle32(&buf, sizeof(buf)); + buf.color = this->color; + stream->write8(&buf, sizeof(buf)); + + if(rw::version >= 0x30400){ + float32 surfaceProps[3]; + surfaceProps[0] = this->surfaceProps.ambient; + surfaceProps[1] = this->surfaceProps.specular; + surfaceProps[2] = this->surfaceProps.diffuse; + stream->write32(surfaceProps, sizeof(surfaceProps)); + } + + if(this->texture) + this->texture->streamWrite(stream); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Material::streamGetSize(void) +{ + uint32 size = 0; + size += 12 + sizeof(MatStreamData); + if(rw::version >= 0x30400) + size += 12; + if(this->texture) + size += 12 + this->texture->streamGetSize(); + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +// Material Rights plugin + +static Stream* +readMaterialRights(Stream *stream, int32, void *, int32, int32) +{ + stream->read32(materialRights, 8); +// printf("materialrights: %X %X\n", materialRights[0], materialRights[1]); + return stream; +} + +static Stream* +writeMaterialRights(Stream *stream, int32, void *object, int32, int32) +{ + Material *material = (Material*)object; + uint32 buffer[2]; + buffer[0] = material->pipeline->pluginID; + buffer[1] = material->pipeline->pluginData; + stream->write32(buffer, 8); + return stream; +} + +static int32 +getSizeMaterialRights(void *object, int32, int32) +{ + Material *material = (Material*)object; + if(material->pipeline == nil || material->pipeline->pluginID == 0) + return 0; + return 8; +} + +void +registerMaterialRightsPlugin(void) +{ + Material::registerPlugin(0, ID_RIGHTTORENDER, nil, nil, nil); + Material::registerPluginStream(ID_RIGHTTORENDER, + readMaterialRights, + writeMaterialRights, + getSizeMaterialRights); +} + + +} diff --git a/vendor/librw/src/geoplg.cpp b/vendor/librw/src/geoplg.cpp new file mode 100644 index 0000000..c1e1356 --- /dev/null +++ b/vendor/librw/src/geoplg.cpp @@ -0,0 +1,339 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" +#include "ps2/rwps2.h" +#include "ps2/rwps2plg.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwwdgl.h" +#include "gl/rwgl3.h" + +#define PLUGIN_ID 2 + +namespace rw { + +static uint16 nextSerialNum = 1; + +// Mesh + +// Allocate a mesh header, meshes and optionally indices. +// If existing meshes already exist, retain their information. +MeshHeader* +Geometry::allocateMeshes(int32 numMeshes, uint32 numIndices, bool32 noIndices) +{ + uint32 sz; + MeshHeader *mh; + Mesh *m; + uint16 *indices; + int32 oldNumMeshes; + int32 i; + sz = sizeof(MeshHeader) + numMeshes*sizeof(Mesh); + if(!noIndices) + sz += numIndices*sizeof(uint16); + if(this->meshHeader){ + oldNumMeshes = this->meshHeader->numMeshes; + mh = (MeshHeader*)rwResize(this->meshHeader, sz, MEMDUR_EVENT | ID_GEOMETRY); + this->meshHeader = mh; + }else{ + oldNumMeshes = 0; + mh = (MeshHeader*)rwNew(sz, MEMDUR_EVENT | ID_GEOMETRY); + mh->flags = 0; + this->meshHeader = mh; + } + mh->numMeshes = numMeshes; + mh->serialNum = nextSerialNum++; + mh->totalIndices = numIndices; + m = mh->getMeshes(); + indices = (uint16*)&m[numMeshes]; + for(i = 0; i < mh->numMeshes; i++){ + // keep these + if(i >= oldNumMeshes){ + m->material = nil; + m->numIndices = 0; + } + // always init indices + if(noIndices) + m->indices = nil; + else{ + m->indices = indices; + indices += m->numIndices; + } + m++; + } + return mh; +} + +void +MeshHeader::setupIndices(void) +{ + int32 i; + uint16 *indices; + Mesh *m; + m = this->getMeshes(); + indices = m->indices; + // return if native + if(indices == nil) + return; + for(i = 0; i < this->numMeshes; i++){ + m->indices = indices; + indices += m->numIndices; + m++; + } +} + +struct MeshHeaderStream +{ + uint32 flags; + uint32 numMeshes; + uint32 totalIndices; +}; + +struct MeshStream +{ + uint32 numIndices; + int32 matIndex; +}; + +static Stream* +readMesh(Stream *stream, int32 len, void *object, int32, int32) +{ + MeshHeaderStream mhs; + MeshStream ms; + MeshHeader *mh; + Mesh *mesh; + int32 indbuf[256]; + uint16 *indices; + Geometry *geo = (Geometry*)object; + + stream->read32(&mhs, sizeof(MeshHeaderStream)); + // Have to do this dance for War Drum's meshes + bool32 hasData = len > int32(sizeof(MeshHeaderStream)+mhs.numMeshes*sizeof(MeshStream)); + assert(geo->meshHeader == nil); + geo->meshHeader = nil; + mh = geo->allocateMeshes(mhs.numMeshes, mhs.totalIndices, + geo->flags & Geometry::NATIVE && !hasData); + mh->flags = mhs.flags; + + mesh = mh->getMeshes(); + indices = mesh->indices; + for(uint32 i = 0; i < mh->numMeshes; i++){ + stream->read32(&ms, sizeof(MeshStream)); + mesh->numIndices = ms.numIndices; + mesh->material = geo->matList.materials[ms.matIndex]; + if(geo->flags & Geometry::NATIVE){ + // War Drum OpenGL stores uint16 indices here + if(hasData){ + mesh->indices = indices; + indices += mesh->numIndices; + stream->read16(mesh->indices, + mesh->numIndices*2); + } + }else{ + mesh->indices = indices; + indices += mesh->numIndices; + uint16 *ind = mesh->indices; + int32 numIndices = mesh->numIndices; + for(; numIndices > 0; numIndices -= 256){ + int32 n = numIndices < 256 ? numIndices : 256; + stream->read32(indbuf, n*4); + for(int32 j = 0; j < n; j++) + ind[j] = indbuf[j]; + ind += n; + } + } + mesh++; + } + return stream; +} + +static Stream* +writeMesh(Stream *stream, int32, void *object, int32, int32) +{ + MeshHeaderStream mhs; + MeshStream ms; + int32 indbuf[256]; + Geometry *geo = (Geometry*)object; + mhs.flags = geo->meshHeader->flags; + mhs.numMeshes = geo->meshHeader->numMeshes; + mhs.totalIndices = geo->meshHeader->totalIndices; + stream->write32(&mhs, sizeof(MeshHeaderStream)); + Mesh *mesh = geo->meshHeader->getMeshes(); + for(uint32 i = 0; i < geo->meshHeader->numMeshes; i++){ + ms.numIndices = mesh->numIndices; + ms.matIndex = geo->matList.findIndex(mesh->material); + stream->write32(&ms, sizeof(MeshStream)); + if(geo->flags & Geometry::NATIVE){ + assert(geo->instData != nil); + if(geo->instData->platform == PLATFORM_WDGL) + stream->write16(mesh->indices, + mesh->numIndices*2); + }else{ + uint16 *ind = mesh->indices; + int32 numIndices = mesh->numIndices; + for(; numIndices > 0; numIndices -= 256){ + int32 n = numIndices < 256 ? numIndices : 256; + for(int32 j = 0; j < n; j++) + indbuf[j] = ind[j]; + stream->write32(indbuf, n*4); + ind += n; + } + } + mesh++; + } + return stream; +} + +static int32 +getSizeMesh(void *object, int32, int32) +{ + Geometry *geo = (Geometry*)object; + if(geo->meshHeader == nil) + return -1; + int32 size = 12 + geo->meshHeader->numMeshes*8; + if(geo->flags & Geometry::NATIVE){ + assert(geo->instData != nil); + if(geo->instData->platform == PLATFORM_WDGL) + size += geo->meshHeader->totalIndices*2; + }else{ + size += geo->meshHeader->totalIndices*4; + } + return size; +} + +void +registerMeshPlugin(void) +{ + Geometry::registerPlugin(0, ID_MESH, nil, nil, nil); + Geometry::registerPluginStream(ID_MESH, readMesh, writeMesh, getSizeMesh); +} + +// Returns the maximum number of triangles. Just so +// we can allocate enough before instancing. This does not +// take into account degerate triangles or ADC bits as +// we don't look at the data. +uint32 +MeshHeader::guessNumTriangles(void) +{ + if(this->flags == MeshHeader::TRISTRIP) + return this->totalIndices - 2*this->numMeshes; + else + return this->totalIndices/3; +} + +// Native Data + +static void* +destroyNativeData(void *object, int32 offset, int32 size) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil) + return object; + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_D3D8) + return d3d8::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_D3D9) + return d3d9::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_GL3) + return gl3::destroyNativeData(object, offset, size); + return object; +} + +static Stream* +readNativeData(Stream *stream, int32 len, void *object, int32 o, int32 s) +{ + ChunkHeaderInfo header; + uint32 libid; + uint32 platform; + // ugly hack to find out platform + stream->seek(-4); + libid = stream->readU32(); + readChunkHeaderInfo(stream, &header); + if(header.type == ID_STRUCT && + libraryIDPack(header.version, header.build) == libid){ + platform = stream->readU32(); + stream->seek(-16); + if(platform == PLATFORM_PS2) + return ps2::readNativeData(stream, len, object, o, s); + else if(platform == PLATFORM_XBOX) + return xbox::readNativeData(stream, len, object, o, s); + else if(platform == PLATFORM_D3D8) + return d3d8::readNativeData(stream, len, object, o, s); + else if(platform == PLATFORM_D3D9) + return d3d9::readNativeData(stream, len, object, o, s); + else{ + fprintf(stderr, "unknown platform %d\n", platform); + stream->seek(len); + } + }else{ + stream->seek(-12); + wdgl::readNativeData(stream, len, object, o, s); + } + return stream; +} + +static Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32 o, int32 s) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil) + return stream; + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::writeNativeData(stream, len, object, o, s); + else if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::writeNativeData(stream, len, object, o, s); + else if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::writeNativeData(stream, len, object, o, s); + else if(geometry->instData->platform == PLATFORM_D3D8) + return d3d8::writeNativeData(stream, len, object, o, s); + else if(geometry->instData->platform == PLATFORM_D3D9) + return d3d9::writeNativeData(stream, len, object, o, s); + return stream; +} + +static int32 +getSizeNativeData(void *object, int32 offset, int32 size) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil) + return 0; + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::getSizeNativeData(object, offset, size); + else if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::getSizeNativeData(object, offset, size); + else if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::getSizeNativeData(object, offset, size); + else if(geometry->instData->platform == PLATFORM_D3D8) + return d3d8::getSizeNativeData(object, offset, size); + else if(geometry->instData->platform == PLATFORM_D3D9) + return d3d9::getSizeNativeData(object, offset, size); + return 0; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +} diff --git a/vendor/librw/src/gl/gl3.cpp b/vendor/librw/src/gl/gl3.cpp new file mode 100644 index 0000000..b876fc9 --- /dev/null +++ b/vendor/librw/src/gl/gl3.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" + +#include "rwgl3.h" +#include "rwgl3shader.h" + +#include "rwgl3impl.h" + +namespace rw { +namespace gl3 { + +// TODO: make some of these things platform-independent + +static void* +driverOpen(void *o, int32, int32) +{ +#ifdef RW_OPENGL + engine->driver[PLATFORM_GL3]->defaultPipeline = makeDefaultPipeline(); +#endif + engine->driver[PLATFORM_GL3]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_GL3]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_GL3]->rasterLock = rasterLock; + engine->driver[PLATFORM_GL3]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_GL3]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_GL3]->imageFindRasterFormat = imageFindRasterFormat; + engine->driver[PLATFORM_GL3]->rasterFromImage = rasterFromImage; + engine->driver[PLATFORM_GL3]->rasterToImage = rasterToImage; + + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_GL3, 0, PLATFORM_GL3, + driverOpen, driverClose); + registerNativeRaster(); +} + +} +} diff --git a/vendor/librw/src/gl/gl3device.cpp b/vendor/librw/src/gl/gl3device.cpp new file mode 100644 index 0000000..9df47ee --- /dev/null +++ b/vendor/librw/src/gl/gl3device.cpp @@ -0,0 +1,2111 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#ifdef RW_OPENGL + +#include "rwgl3.h" +#include "rwgl3shader.h" +#include "rwgl3impl.h" + +#define PLUGIN_ID 0 + +namespace rw { +namespace gl3 { + +GlGlobals glGlobals; + +Gl3Caps gl3Caps; +// terrible hack for GLES +bool32 needToReadBackTextures; + +int32 alphaFunc; +float32 alphaRef; + +struct UniformState +{ + float32 alphaRefLow; + float32 alphaRefHigh; + int32 pad[2]; + + float32 fogStart; + float32 fogEnd; + float32 fogRange; + float32 fogDisable; + + RGBAf fogColor; +}; + +struct UniformScene +{ + float32 proj[16]; + float32 view[16]; +}; + +#define MAX_LIGHTS 8 + +struct UniformObject +{ + RawMatrix world; + RGBAf ambLight; + struct { + float type; + float radius; + float minusCosAngle; + float hardSpot; + } lightParams[MAX_LIGHTS]; + V4d lightPosition[MAX_LIGHTS]; + V4d lightDirection[MAX_LIGHTS]; + RGBAf lightColor[MAX_LIGHTS]; +}; + +const char *shaderDecl120 = +"#version 120\n" +"#define GL2\n" +"#define texture texture2D\n" +"#define VSIN(index) attribute\n" +"#define VSOUT varying\n" +"#define FSIN varying\n" +"#define FRAGCOLOR(c) (gl_FragColor = c)\n"; +const char *shaderDecl330 = +"#version 330\n" +"#define VSIN(index) layout(location = index) in\n" +"#define VSOUT out\n" +"#define FSIN in\n" +"#define FRAGCOLOR(c) (fragColor = c)\n"; +const char *shaderDecl100es = +"#version 100\n" +"#define GL2\n" +"#define texture texture2D\n" +"#define VSIN(index) attribute\n" +"#define VSOUT varying\n" +"#define FSIN varying\n" +"#define FRAGCOLOR(c) (gl_FragColor = c)\n" +"precision highp float;\n" +"precision highp int;\n"; +const char *shaderDecl310es = +"#version 310 es\n" +"#define VSIN(index) layout(location = index) in\n" +"#define VSOUT out\n" +"#define FSIN in\n" +"#define FRAGCOLOR(c) (fragColor = c)\n" +"precision highp float;\n" +"precision highp int;\n"; + +const char *shaderDecl; + +// this needs a define in the shaders as well! +//#define RW_GL_USE_UBOS + +static GLuint vao; +#ifdef RW_GL_USE_UBOS +static GLuint ubo_state, ubo_scene, ubo_object; +#endif +static GLuint whitetex; +static UniformState uniformState; +static UniformScene uniformScene; +static UniformObject uniformObject; + +#ifndef RW_GL_USE_UBOS +// State +int32 u_alphaRef; +int32 u_fogData; +int32 u_fogColor; + +// Scene +int32 u_proj; +int32 u_view; + +// Object +int32 u_world; +int32 u_ambLight; +int32 u_lightParams; +int32 u_lightPosition; +int32 u_lightDirection; +int32 u_lightColor; +#endif + +int32 u_matColor; +int32 u_surfProps; + +Shader *defaultShader, *defaultShader_noAT; +Shader *defaultShader_fullLight, *defaultShader_fullLight_noAT; + +static bool32 stateDirty = 1; +static bool32 sceneDirty = 1; +static bool32 objectDirty = 1; + +struct RwRasterStateCache { + Raster *raster; + Texture::Addressing addressingU; + Texture::Addressing addressingV; + Texture::FilterMode filter; +}; + +#define MAXNUMSTAGES 8 + +// cached RW render states +struct RwStateCache { + bool32 vertexAlpha; + uint32 alphaTestEnable; + uint32 alphaFunc; + bool32 textureAlpha; + bool32 blendEnable; + uint32 srcblend, destblend; + uint32 zwrite; + uint32 ztest; + uint32 cullmode; + uint32 stencilenable; + uint32 stencilpass; + uint32 stencilfail; + uint32 stencilzfail; + uint32 stencilfunc; + uint32 stencilref; + uint32 stencilmask; + uint32 stencilwritemask; + uint32 fogEnable; + float32 fogStart; + float32 fogEnd; + + // emulation of PS2 GS + bool32 gsalpha; + uint32 gsalpharef; + + RwRasterStateCache texstage[MAXNUMSTAGES]; +}; +static RwStateCache rwStateCache; + +enum +{ + // actual gl states + RWGL_BLEND, + RWGL_SRCBLEND, + RWGL_DESTBLEND, + RWGL_DEPTHTEST, + RWGL_DEPTHFUNC, + RWGL_DEPTHMASK, + RWGL_CULL, + RWGL_CULLFACE, + RWGL_STENCIL, + RWGL_STENCILFUNC, + RWGL_STENCILFAIL, + RWGL_STENCILZFAIL, + RWGL_STENCILPASS, + RWGL_STENCILREF, + RWGL_STENCILMASK, + RWGL_STENCILWRITEMASK, + + // uniforms + RWGL_ALPHAFUNC, + RWGL_ALPHAREF, + RWGL_FOG, + RWGL_FOGSTART, + RWGL_FOGEND, + RWGL_FOGCOLOR, + + RWGL_NUM_STATES +}; +static bool uniformStateDirty[RWGL_NUM_STATES]; + +struct GlState { + bool32 blendEnable; + uint32 srcblend, destblend; + + bool32 depthTest; + uint32 depthFunc; + + uint32 depthMask; + + bool32 cullEnable; + uint32 cullFace; + + bool32 stencilEnable; + // glStencilFunc + uint32 stencilFunc; + uint32 stencilRef; + uint32 stencilMask; + // glStencilOp + uint32 stencilPass; + uint32 stencilFail; + uint32 stencilZFail; + // glStencilMask + uint32 stencilWriteMask; +}; +static GlState curGlState, oldGlState; + +static int32 activeTexture; +static uint32 boundTexture[MAXNUMSTAGES]; + +static uint32 currentFramebuffer; + +static uint32 blendMap[] = { + GL_ZERO, // actually invalid + GL_ZERO, + GL_ONE, + GL_SRC_COLOR, + GL_ONE_MINUS_SRC_COLOR, + GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, + GL_ONE_MINUS_DST_ALPHA, + GL_DST_COLOR, + GL_ONE_MINUS_DST_COLOR, + GL_SRC_ALPHA_SATURATE, +}; + +static uint32 stencilOpMap[] = { + GL_KEEP, // actually invalid + GL_KEEP, + GL_ZERO, + GL_REPLACE, + GL_INCR, + GL_DECR, + GL_INVERT, + GL_INCR_WRAP, + GL_DECR_WRAP +}; + +static uint32 stencilFuncMap[] = { + GL_NEVER, // actually invalid + GL_NEVER, + GL_LESS, + GL_EQUAL, + GL_LEQUAL, + GL_GREATER, + GL_NOTEQUAL, + GL_GEQUAL, + GL_ALWAYS +}; + +static float maxAnisotropy; + +/* + * GL state cache + */ + +void +setGlRenderState(uint32 state, uint32 value) +{ + switch(state){ + case RWGL_BLEND: curGlState.blendEnable = value; break; + case RWGL_SRCBLEND: curGlState.srcblend = value; break; + case RWGL_DESTBLEND: curGlState.destblend = value; break; + case RWGL_DEPTHTEST: curGlState.depthTest = value; break; + case RWGL_DEPTHFUNC: curGlState.depthFunc = value; break; + case RWGL_DEPTHMASK: curGlState.depthMask = value; break; + case RWGL_CULL: curGlState.cullEnable = value; break; + case RWGL_CULLFACE: curGlState.cullFace = value; break; + case RWGL_STENCIL: curGlState.stencilEnable = value; break; + case RWGL_STENCILFUNC: curGlState.stencilFunc = value; break; + case RWGL_STENCILFAIL: curGlState.stencilFail = value; break; + case RWGL_STENCILZFAIL: curGlState.stencilZFail = value; break; + case RWGL_STENCILPASS: curGlState.stencilPass = value; break; + case RWGL_STENCILREF: curGlState.stencilRef = value; break; + case RWGL_STENCILMASK: curGlState.stencilMask = value; break; + case RWGL_STENCILWRITEMASK: curGlState.stencilWriteMask = value; break; + } +} + +void +flushGlRenderState(void) +{ + if(oldGlState.blendEnable != curGlState.blendEnable){ + oldGlState.blendEnable = curGlState.blendEnable; + (oldGlState.blendEnable ? glEnable : glDisable)(GL_BLEND); + } + + if(oldGlState.srcblend != curGlState.srcblend || + oldGlState.destblend != curGlState.destblend){ + oldGlState.srcblend = curGlState.srcblend; + oldGlState.destblend = curGlState.destblend; + glBlendFunc(oldGlState.srcblend, oldGlState.destblend); + } + + if(oldGlState.depthTest != curGlState.depthTest){ + oldGlState.depthTest = curGlState.depthTest; + (oldGlState.depthTest ? glEnable : glDisable)(GL_DEPTH_TEST); + } + if(oldGlState.depthFunc != curGlState.depthFunc){ + oldGlState.depthFunc = curGlState.depthFunc; + glDepthFunc(oldGlState.depthFunc); + } + if(oldGlState.depthMask != curGlState.depthMask){ + oldGlState.depthMask = curGlState.depthMask; + glDepthMask(oldGlState.depthMask); + } + + if(oldGlState.stencilEnable != curGlState.stencilEnable){ + oldGlState.stencilEnable = curGlState.stencilEnable; + (oldGlState.stencilEnable ? glEnable : glDisable)(GL_STENCIL_TEST); + } + if(oldGlState.stencilFunc != curGlState.stencilFunc || + oldGlState.stencilRef != curGlState.stencilRef || + oldGlState.stencilMask != curGlState.stencilMask){ + oldGlState.stencilFunc = curGlState.stencilFunc; + oldGlState.stencilRef = curGlState.stencilRef; + oldGlState.stencilMask = curGlState.stencilMask; + glStencilFunc(oldGlState.stencilFunc, oldGlState.stencilRef, oldGlState.stencilMask); + } + if(oldGlState.stencilPass != curGlState.stencilPass || + oldGlState.stencilFail != curGlState.stencilFail || + oldGlState.stencilZFail != curGlState.stencilZFail){ + oldGlState.stencilPass = curGlState.stencilPass; + oldGlState.stencilFail = curGlState.stencilFail; + oldGlState.stencilZFail = curGlState.stencilZFail; + glStencilOp(oldGlState.stencilFail, oldGlState.stencilZFail, oldGlState.stencilPass); + } + if(oldGlState.stencilWriteMask != curGlState.stencilWriteMask){ + oldGlState.stencilWriteMask = curGlState.stencilWriteMask; + glStencilMask(oldGlState.stencilWriteMask); + } + + if(oldGlState.cullEnable != curGlState.cullEnable){ + oldGlState.cullEnable = curGlState.cullEnable; + (oldGlState.cullEnable ? glEnable : glDisable)(GL_CULL_FACE); + } + if(oldGlState.cullFace != curGlState.cullFace){ + oldGlState.cullFace = curGlState.cullFace; + glCullFace(oldGlState.cullFace); + } +} + + + +void +setAlphaBlend(bool32 enable) +{ + if(rwStateCache.blendEnable != enable){ + rwStateCache.blendEnable = enable; + setGlRenderState(RWGL_BLEND, enable); + } +} + +bool32 +getAlphaBlend(void) +{ + return rwStateCache.blendEnable; +} + +bool32 getAlphaTest(void) { return rwStateCache.alphaTestEnable; } + +static void +setDepthTest(bool32 enable) +{ + if(rwStateCache.ztest != enable){ + rwStateCache.ztest = enable; + if(rwStateCache.zwrite && !enable){ + // If we still want to write, enable but set mode to always + setGlRenderState(RWGL_DEPTHTEST, true); + setGlRenderState(RWGL_DEPTHFUNC, GL_ALWAYS); + }else{ + setGlRenderState(RWGL_DEPTHTEST, rwStateCache.ztest); + setGlRenderState(RWGL_DEPTHFUNC, GL_LEQUAL); + } + } +} + +static void +setDepthWrite(bool32 enable) +{ + enable = enable ? GL_TRUE : GL_FALSE; + if(rwStateCache.zwrite != enable){ + rwStateCache.zwrite = enable; + if(enable && !rwStateCache.ztest){ + // Have to switch on ztest so writing can work + setGlRenderState(RWGL_DEPTHTEST, true); + setGlRenderState(RWGL_DEPTHFUNC, GL_ALWAYS); + } + setGlRenderState(RWGL_DEPTHMASK, rwStateCache.zwrite); + } +} + +static void +setAlphaTest(bool32 enable) +{ + uint32 shaderfunc; + if(rwStateCache.alphaTestEnable != enable){ + rwStateCache.alphaTestEnable = enable; + shaderfunc = rwStateCache.alphaTestEnable ? rwStateCache.alphaFunc : ALPHAALWAYS; + if(alphaFunc != shaderfunc){ + alphaFunc = shaderfunc; + uniformStateDirty[RWGL_ALPHAFUNC] = true; + stateDirty = 1; + } + } +} + +static void +setAlphaTestFunction(uint32 function) +{ + uint32 shaderfunc; + if(rwStateCache.alphaFunc != function){ + rwStateCache.alphaFunc = function; + shaderfunc = rwStateCache.alphaTestEnable ? rwStateCache.alphaFunc : ALPHAALWAYS; + if(alphaFunc != shaderfunc){ + alphaFunc = shaderfunc; + uniformStateDirty[RWGL_ALPHAFUNC] = true; + stateDirty = 1; + } + } +} + +static void +setVertexAlpha(bool32 enable) +{ + if(rwStateCache.vertexAlpha != enable){ + if(!rwStateCache.textureAlpha){ + setAlphaBlend(enable); + setAlphaTest(enable); + } + rwStateCache.vertexAlpha = enable; + } +} + +static void +setActiveTexture(int32 n) +{ + if(activeTexture != n){ + activeTexture = n; + glActiveTexture(GL_TEXTURE0+n); + } +} + +uint32 +bindTexture(uint32 texid) +{ + uint32 prev = boundTexture[activeTexture]; + if(prev != texid){ + boundTexture[activeTexture] = texid; + glBindTexture(GL_TEXTURE_2D, texid); + } + return prev; +} + +void +bindFramebuffer(uint32 fbo) +{ + if(currentFramebuffer != fbo){ + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + currentFramebuffer = fbo; + } +} + +static GLint filterConvMap_NoMIP[] = { + 0, GL_NEAREST, GL_LINEAR, + GL_NEAREST, GL_LINEAR, + GL_NEAREST, GL_LINEAR +}; +static GLint filterConvMap_MIP[] = { + 0, GL_NEAREST, GL_LINEAR, + GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, + GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR +}; + +static GLint addressConvMap[] = { + 0, GL_REPEAT, GL_MIRRORED_REPEAT, + GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER +}; + +static void +setFilterMode(uint32 stage, int32 filter, int32 maxAniso = 1) +{ + if(rwStateCache.texstage[stage].filter != (Texture::FilterMode)filter){ + rwStateCache.texstage[stage].filter = (Texture::FilterMode)filter; + Raster *raster = rwStateCache.texstage[stage].raster; + if(raster){ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, rwStateCache.texstage[stage].raster, nativeRasterOffset); + if(natras->filterMode != filter){ + setActiveTexture(stage); + if(natras->autogenMipmap || natras->numLevels > 1){ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterConvMap_MIP[filter]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterConvMap_NoMIP[filter]); + }else{ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterConvMap_NoMIP[filter]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterConvMap_NoMIP[filter]); + } + natras->filterMode = filter; + } + if(natras->maxAnisotropy != maxAniso){ + setActiveTexture(stage); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)maxAniso); + natras->maxAnisotropy = maxAniso; + } + } + } +} + +static void +setAddressU(uint32 stage, int32 addressing) +{ + if(rwStateCache.texstage[stage].addressingU != (Texture::Addressing)addressing){ + rwStateCache.texstage[stage].addressingU = (Texture::Addressing)addressing; + Raster *raster = rwStateCache.texstage[stage].raster; + if(raster){ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + if(natras->addressU == addressing){ + setActiveTexture(stage); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, addressConvMap[addressing]); + natras->addressU = addressing; + } + } + } +} + +static void +setAddressV(uint32 stage, int32 addressing) +{ + if(rwStateCache.texstage[stage].addressingV != (Texture::Addressing)addressing){ + rwStateCache.texstage[stage].addressingV = (Texture::Addressing)addressing; + Raster *raster = rwStateCache.texstage[stage].raster; + if(raster){ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, rwStateCache.texstage[stage].raster, nativeRasterOffset); + if(natras->addressV == addressing){ + setActiveTexture(stage); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, addressConvMap[addressing]); + natras->addressV = addressing; + } + } + } +} + +static void +setRasterStageOnly(uint32 stage, Raster *raster) +{ + bool32 alpha; + if(raster != rwStateCache.texstage[stage].raster){ + rwStateCache.texstage[stage].raster = raster; + setActiveTexture(stage); + if(raster){ + assert(raster->platform == PLATFORM_GL3); + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + bindTexture(natras->texid); + + rwStateCache.texstage[stage].filter = (rw::Texture::FilterMode)natras->filterMode; + rwStateCache.texstage[stage].addressingU = (rw::Texture::Addressing)natras->addressU; + rwStateCache.texstage[stage].addressingV = (rw::Texture::Addressing)natras->addressV; + + alpha = natras->hasAlpha; + }else{ + bindTexture(whitetex); + alpha = 0; + } + + if(stage == 0){ + if(alpha != rwStateCache.textureAlpha){ + rwStateCache.textureAlpha = alpha; + if(!rwStateCache.vertexAlpha){ + setAlphaBlend(alpha); + setAlphaTest(alpha); + } + } + } + } +} + +static void +setRasterStage(uint32 stage, Raster *raster) +{ + bool32 alpha; + if(raster != rwStateCache.texstage[stage].raster){ + rwStateCache.texstage[stage].raster = raster; + setActiveTexture(stage); + if(raster){ + assert(raster->platform == PLATFORM_GL3); + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + bindTexture(natras->texid); + uint32 filter = rwStateCache.texstage[stage].filter; + uint32 addrU = rwStateCache.texstage[stage].addressingU; + uint32 addrV = rwStateCache.texstage[stage].addressingV; + if(natras->filterMode != filter){ + if(natras->autogenMipmap || natras->numLevels > 1){ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterConvMap_MIP[filter]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterConvMap_NoMIP[filter]); + }else{ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterConvMap_NoMIP[filter]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterConvMap_NoMIP[filter]); + } + natras->filterMode = filter; + } + if(natras->addressU != addrU){ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, addressConvMap[addrU]); + natras->addressU = addrU; + } + if(natras->addressV != addrV){ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, addressConvMap[addrV]); + natras->addressV = addrV; + } + alpha = natras->hasAlpha; + }else{ + bindTexture(whitetex); + alpha = 0; + } + + if(stage == 0){ + if(alpha != rwStateCache.textureAlpha){ + rwStateCache.textureAlpha = alpha; + if(!rwStateCache.vertexAlpha){ + setAlphaBlend(alpha); + setAlphaTest(alpha); + } + } + } + } +} + +void +evictRaster(Raster *raster) +{ + int i; + for(i = 0; i < MAXNUMSTAGES; i++){ + //assert(rwStateCache.texstage[i].raster != raster); + if(rwStateCache.texstage[i].raster != raster) + continue; + setRasterStage(i, nil); + } +} + +void +setTexture(int32 stage, Texture *tex) +{ + if(tex == nil || tex->raster == nil){ + setRasterStage(stage, nil); + return; + } + setRasterStageOnly(stage, tex->raster); + setFilterMode(stage, tex->getFilter(), tex->getMaxAnisotropy()); + setAddressU(stage, tex->getAddressU()); + setAddressV(stage, tex->getAddressV()); +} + +static void +setRenderState(int32 state, void *pvalue) +{ + uint32 value = (uint32)(uintptr)pvalue; + switch(state){ + case TEXTURERASTER: + setRasterStage(0, (Raster*)pvalue); + break; + case TEXTUREADDRESS: + setAddressU(0, value); + setAddressV(0, value); + break; + case TEXTUREADDRESSU: + setAddressU(0, value); + break; + case TEXTUREADDRESSV: + setAddressV(0, value); + break; + case TEXTUREFILTER: + setFilterMode(0, value); + break; + case VERTEXALPHA: + setVertexAlpha(value); + break; + case SRCBLEND: + if(rwStateCache.srcblend != value){ + rwStateCache.srcblend = value; + setGlRenderState(RWGL_SRCBLEND, blendMap[rwStateCache.srcblend]); + } + break; + case DESTBLEND: + if(rwStateCache.destblend != value){ + rwStateCache.destblend = value; + setGlRenderState(RWGL_DESTBLEND, blendMap[rwStateCache.destblend]); + } + break; + case ZTESTENABLE: + setDepthTest(value); + break; + case ZWRITEENABLE: + setDepthWrite(value); + break; + case FOGENABLE: + if(rwStateCache.fogEnable != value){ + rwStateCache.fogEnable = value; + uniformStateDirty[RWGL_FOG] = true; + stateDirty = 1; + } + break; + case FOGCOLOR: + // no cache check here...too lazy + RGBA c; + c.red = value; + c.green = value>>8; + c.blue = value>>16; + c.alpha = value>>24; + convColor(&uniformState.fogColor, &c); + uniformStateDirty[RWGL_FOGCOLOR] = true; + stateDirty = 1; + break; + case CULLMODE: + if(rwStateCache.cullmode != value){ + rwStateCache.cullmode = value; + if(rwStateCache.cullmode == CULLNONE) + setGlRenderState(RWGL_CULL, false); + else{ + setGlRenderState(RWGL_CULL, true); + setGlRenderState(RWGL_CULLFACE, rwStateCache.cullmode == CULLBACK ? GL_BACK : GL_FRONT); + } + } + break; + + case STENCILENABLE: + if(rwStateCache.stencilenable != value){ + rwStateCache.stencilenable = value; + setGlRenderState(RWGL_STENCIL, value); + } + break; + case STENCILFAIL: + if(rwStateCache.stencilfail != value){ + rwStateCache.stencilfail = value; + setGlRenderState(RWGL_STENCILFAIL, stencilOpMap[value]); + } + break; + case STENCILZFAIL: + if(rwStateCache.stencilzfail != value){ + rwStateCache.stencilzfail = value; + setGlRenderState(RWGL_STENCILZFAIL, stencilOpMap[value]); + } + break; + case STENCILPASS: + if(rwStateCache.stencilpass != value){ + rwStateCache.stencilpass = value; + setGlRenderState(RWGL_STENCILPASS, stencilOpMap[value]); + } + break; + case STENCILFUNCTION: + if(rwStateCache.stencilfunc != value){ + rwStateCache.stencilfunc = value; + setGlRenderState(RWGL_STENCILFUNC, stencilFuncMap[value]); + } + break; + case STENCILFUNCTIONREF: + if(rwStateCache.stencilref != value){ + rwStateCache.stencilref = value; + setGlRenderState(RWGL_STENCILREF, value); + } + break; + case STENCILFUNCTIONMASK: + if(rwStateCache.stencilmask != value){ + rwStateCache.stencilmask = value; + setGlRenderState(RWGL_STENCILMASK, value); + } + break; + case STENCILFUNCTIONWRITEMASK: + if(rwStateCache.stencilwritemask != value){ + rwStateCache.stencilwritemask = value; + setGlRenderState(RWGL_STENCILWRITEMASK, value); + } + break; + + case ALPHATESTFUNC: + setAlphaTestFunction(value); + break; + case ALPHATESTREF: + if(alphaRef != value/255.0f){ + alphaRef = value/255.0f; + uniformStateDirty[RWGL_ALPHAREF] = true; + stateDirty = 1; + } + break; + case GSALPHATEST: + rwStateCache.gsalpha = value; + break; + case GSALPHATESTREF: + rwStateCache.gsalpharef = value; + } +} + +static void* +getRenderState(int32 state) +{ + uint32 val; + RGBA rgba; + switch(state){ + case TEXTURERASTER: + return rwStateCache.texstage[0].raster; + case TEXTUREADDRESS: + if(rwStateCache.texstage[0].addressingU == rwStateCache.texstage[0].addressingV) + val = rwStateCache.texstage[0].addressingU; + else + val = 0; // invalid + break; + case TEXTUREADDRESSU: + val = rwStateCache.texstage[0].addressingU; + break; + case TEXTUREADDRESSV: + val = rwStateCache.texstage[0].addressingV; + break; + case TEXTUREFILTER: + val = rwStateCache.texstage[0].filter; + break; + + case VERTEXALPHA: + val = rwStateCache.vertexAlpha; + break; + case SRCBLEND: + val = rwStateCache.srcblend; + break; + case DESTBLEND: + val = rwStateCache.destblend; + break; + case ZTESTENABLE: + val = rwStateCache.ztest; + break; + case ZWRITEENABLE: + val = rwStateCache.zwrite; + break; + case FOGENABLE: + val = rwStateCache.fogEnable; + break; + case FOGCOLOR: + convColor(&rgba, &uniformState.fogColor); + val = RWRGBAINT(rgba.red, rgba.green, rgba.blue, rgba.alpha); + break; + case CULLMODE: + val = rwStateCache.cullmode; + break; + + case STENCILENABLE: + val = rwStateCache.stencilenable; + break; + case STENCILFAIL: + val = rwStateCache.stencilfail; + break; + case STENCILZFAIL: + val = rwStateCache.stencilzfail; + break; + case STENCILPASS: + val = rwStateCache.stencilpass; + break; + case STENCILFUNCTION: + val = rwStateCache.stencilfunc; + break; + case STENCILFUNCTIONREF: + val = rwStateCache.stencilref; + break; + case STENCILFUNCTIONMASK: + val = rwStateCache.stencilmask; + break; + case STENCILFUNCTIONWRITEMASK: + val = rwStateCache.stencilwritemask; + break; + + case ALPHATESTFUNC: + val = rwStateCache.alphaFunc; + break; + case ALPHATESTREF: + val = (uint32)(alphaRef*255.0f); + break; + case GSALPHATEST: + val = rwStateCache.gsalpha; + break; + case GSALPHATESTREF: + val = rwStateCache.gsalpharef; + break; + default: + val = 0; + } + return (void*)(uintptr)val; +} + +static void +resetRenderState(void) +{ + rwStateCache.alphaFunc = ALPHAGREATEREQUAL; + alphaFunc = 0; + alphaRef = 10.0f/255.0f; + uniformState.fogDisable = 1.0f; + uniformState.fogStart = 0.0f; + uniformState.fogEnd = 0.0f; + uniformState.fogRange = 0.0f; + uniformState.fogColor = { 1.0f, 1.0f, 1.0f, 1.0f }; + rwStateCache.gsalpha = 0; + rwStateCache.gsalpharef = 128; + stateDirty = 1; + + rwStateCache.vertexAlpha = 0; + rwStateCache.textureAlpha = 0; + rwStateCache.alphaTestEnable = 0; + + memset(&oldGlState, 0xFE, sizeof(oldGlState)); + + rwStateCache.blendEnable = 0; + setGlRenderState(RWGL_BLEND, false); + rwStateCache.srcblend = BLENDSRCALPHA; + rwStateCache.destblend = BLENDINVSRCALPHA; + setGlRenderState(RWGL_SRCBLEND, blendMap[rwStateCache.srcblend]); + setGlRenderState(RWGL_DESTBLEND, blendMap[rwStateCache.destblend]); + + rwStateCache.zwrite = GL_TRUE; + setGlRenderState(RWGL_DEPTHMASK, rwStateCache.zwrite); + + rwStateCache.ztest = 1; + setGlRenderState(RWGL_DEPTHTEST, true); + setGlRenderState(RWGL_DEPTHFUNC, GL_LEQUAL); + + rwStateCache.cullmode = CULLNONE; + setGlRenderState(RWGL_CULL, false); + setGlRenderState(RWGL_CULLFACE, GL_BACK); + + rwStateCache.stencilenable = 0; + setGlRenderState(RWGL_STENCIL, GL_FALSE); + rwStateCache.stencilfail = STENCILKEEP; + setGlRenderState(RWGL_STENCILFAIL, GL_KEEP); + rwStateCache.stencilzfail = STENCILKEEP; + setGlRenderState(RWGL_STENCILZFAIL, GL_KEEP); + rwStateCache.stencilpass = STENCILKEEP; + setGlRenderState(RWGL_STENCILPASS, GL_KEEP); + rwStateCache.stencilfunc = STENCILALWAYS; + setGlRenderState(RWGL_STENCILFUNC, GL_ALWAYS); + rwStateCache.stencilref = 0; + setGlRenderState(RWGL_STENCILREF, 0); + rwStateCache.stencilmask = 0xFFFFFFFF; + setGlRenderState(RWGL_STENCILMASK, 0xFFFFFFFF); + rwStateCache.stencilwritemask = 0xFFFFFFFF; + setGlRenderState(RWGL_STENCILWRITEMASK, 0xFFFFFFFF); + + activeTexture = -1; + for(int i = 0; i < MAXNUMSTAGES; i++){ + setActiveTexture(i); + bindTexture(whitetex); + } + setActiveTexture(0); +} + +void +setWorldMatrix(Matrix *mat) +{ + convMatrix(&uniformObject.world, mat); + setUniform(u_world, &uniformObject.world); + objectDirty = 1; +} + +int32 +setLights(WorldLights *lightData) +{ + int i, n; + Light *l; + int32 bits; + + uniformObject.ambLight = lightData->ambient; + + bits = 0; + + if(lightData->numAmbients) + bits |= VSLIGHT_AMBIENT; + + n = 0; + for(i = 0; i < lightData->numDirectionals && i < 8; i++){ + l = lightData->directionals[i]; + uniformObject.lightParams[n].type = 1.0f; + uniformObject.lightColor[n] = l->color; + memcpy(&uniformObject.lightDirection[n], &l->getFrame()->getLTM()->at, sizeof(V3d)); + bits |= VSLIGHT_DIRECT; + n++; + if(n >= MAX_LIGHTS) + goto out; + } + + for(i = 0; i < lightData->numLocals; i++){ + Light *l = lightData->locals[i]; + + switch(l->getType()){ + case Light::POINT: + uniformObject.lightParams[n].type = 2.0f; + uniformObject.lightParams[n].radius = l->radius; + uniformObject.lightColor[n] = l->color; + memcpy(&uniformObject.lightPosition[n], &l->getFrame()->getLTM()->pos, sizeof(V3d)); + bits |= VSLIGHT_POINT; + n++; + if(n >= MAX_LIGHTS) + goto out; + break; + case Light::SPOT: + case Light::SOFTSPOT: + uniformObject.lightParams[n].type = 3.0f; + uniformObject.lightParams[n].minusCosAngle = l->minusCosAngle; + uniformObject.lightParams[n].radius = l->radius; + uniformObject.lightColor[n] = l->color; + memcpy(&uniformObject.lightPosition[n], &l->getFrame()->getLTM()->pos, sizeof(V3d)); + memcpy(&uniformObject.lightDirection[n], &l->getFrame()->getLTM()->at, sizeof(V3d)); + // lower bound of falloff + if(l->getType() == Light::SOFTSPOT) + uniformObject.lightParams[n].hardSpot = 0.0f; + else + uniformObject.lightParams[n].hardSpot = 1.0f; + bits |= VSLIGHT_SPOT; + n++; + if(n >= MAX_LIGHTS) + goto out; + break; + } + } + + uniformObject.lightParams[n].type = 0.0f; + + setUniform(u_ambLight, &uniformObject.ambLight); + setUniform(u_lightParams, uniformObject.lightParams); + setUniform(u_lightPosition, uniformObject.lightPosition); + setUniform(u_lightDirection, uniformObject.lightDirection); + setUniform(u_lightColor, uniformObject.lightColor); +out: + objectDirty = 1; + return bits; +} + +void +setProjectionMatrix(float32 *mat) +{ + memcpy(&uniformScene.proj, mat, 64); + setUniform(u_proj, uniformScene.proj); + sceneDirty = 1; +} + +void +setViewMatrix(float32 *mat) +{ + memcpy(&uniformScene.view, mat, 64); + setUniform(u_view, uniformScene.view); + sceneDirty = 1; +} + +Shader *lastShaderUploaded; + +void +setMaterial(const RGBA &color, const SurfaceProperties &surfaceprops, float extraSurfProp) +{ + rw::RGBAf col; + convColor(&col, &color); + setUniform(u_matColor, &col); + + float surfProps[4]; + surfProps[0] = surfaceprops.ambient; + surfProps[1] = surfaceprops.specular; + surfProps[2] = surfaceprops.diffuse; + surfProps[3] = extraSurfProp; + setUniform(u_surfProps, surfProps); +} + +void +flushCache(void) +{ + flushGlRenderState(); + +#ifndef RW_GL_USE_UBOS + + // what's this doing here?? + uniformState.fogDisable = rwStateCache.fogEnable ? 0.0f : 1.0f; + uniformState.fogStart = rwStateCache.fogStart; + uniformState.fogEnd = rwStateCache.fogEnd; + uniformState.fogRange = 1.0f/(rwStateCache.fogStart - rwStateCache.fogEnd); + + if(uniformStateDirty[RWGL_ALPHAFUNC] || uniformStateDirty[RWGL_ALPHAREF]){ + float alphaTest[4]; + switch(alphaFunc){ + case ALPHAALWAYS: + default: + alphaTest[0] = -1000.0f; + alphaTest[1] = 1000.0f; + break; + case ALPHAGREATEREQUAL: + alphaTest[0] = alphaRef; + alphaTest[1] = 1000.0f; + break; + case ALPHALESS: + alphaTest[0] = -1000.0f; + alphaTest[1] = alphaRef; + break; + } + setUniform(u_alphaRef, alphaTest); + uniformStateDirty[RWGL_ALPHAFUNC] = false; + uniformStateDirty[RWGL_ALPHAREF] = false; + } + + if(uniformStateDirty[RWGL_FOG] || + uniformStateDirty[RWGL_FOGSTART] || + uniformStateDirty[RWGL_FOGEND]){ + float fogData[4] = { + uniformState.fogStart, + uniformState.fogEnd, + uniformState.fogRange, + uniformState.fogDisable + }; + setUniform(u_fogData, fogData); + uniformStateDirty[RWGL_FOG] = false; + uniformStateDirty[RWGL_FOGSTART] = false; + uniformStateDirty[RWGL_FOGEND] = false; + } + + if(uniformStateDirty[RWGL_FOGCOLOR]){ + setUniform(u_fogColor, &uniformState.fogColor); + uniformStateDirty[RWGL_FOGCOLOR] = false; + } + +#else + if(objectDirty){ + glBindBuffer(GL_UNIFORM_BUFFER, ubo_object); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformObject), nil, GL_STREAM_DRAW); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformObject), &uniformObject, GL_STREAM_DRAW); + objectDirty = 0; + } + if(sceneDirty){ + glBindBuffer(GL_UNIFORM_BUFFER, ubo_scene); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformScene), nil, GL_STREAM_DRAW); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformScene), &uniformScene, GL_STREAM_DRAW); + sceneDirty = 0; + } + if(stateDirty){ + switch(alphaFunc){ + case ALPHAALWAYS: + default: + uniformState.alphaRefLow = -1000.0f; + uniformState.alphaRefHigh = 1000.0f; + break; + case ALPHAGREATEREQUAL: + uniformState.alphaRefLow = alphaRef; + uniformState.alphaRefHigh = 1000.0f; + break; + case ALPHALESS: + uniformState.alphaRefLow = -1000.0f; + uniformState.alphaRefHigh = alphaRef; + break; + } + uniformState.fogDisable = rwStateCache.fogEnable ? 0.0f : 1.0f; + uniformState.fogStart = rwStateCache.fogStart; + uniformState.fogEnd = rwStateCache.fogEnd; + uniformState.fogRange = 1.0f/(rwStateCache.fogStart - rwStateCache.fogEnd); + glBindBuffer(GL_UNIFORM_BUFFER, ubo_state); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformState), nil, GL_STREAM_DRAW); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformState), &uniformState, GL_STREAM_DRAW); + stateDirty = 0; + } +#endif + flushUniforms(); +} + +static void +setFrameBuffer(Camera *cam) +{ + Raster *fbuf = cam->frameBuffer->parent; + Raster *zbuf = cam->zBuffer->parent; + assert(fbuf); + + Gl3Raster *natfb = PLUGINOFFSET(Gl3Raster, fbuf, nativeRasterOffset); + Gl3Raster *natzb = PLUGINOFFSET(Gl3Raster, zbuf, nativeRasterOffset); + assert(fbuf->type == Raster::CAMERA || fbuf->type == Raster::CAMERATEXTURE); + + // Have to make sure depth buffer is attached to FB's fbo + bindFramebuffer(natfb->fbo); + if(zbuf){ + if(natfb->fboMate == zbuf){ + // all good + assert(natzb->fboMate == fbuf); + }else{ + if(natzb->fboMate){ + // have to detatch from fbo first! + Gl3Raster *oldfb = PLUGINOFFSET(Gl3Raster, natzb->fboMate, nativeRasterOffset); + if(oldfb->fbo){ + bindFramebuffer(oldfb->fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + bindFramebuffer(natfb->fbo); + } + oldfb->fboMate = nil; + } + natfb->fboMate = zbuf; + natzb->fboMate = fbuf; + if(natfb->fbo){ + if(gl3Caps.gles) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, natzb->texid); + else + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, natzb->texid, 0); + } + } + }else{ + // remove z-buffer + if(natfb->fboMate && natfb->fbo) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + natfb->fboMate = nil; + } +} + +static Rect +getFramebufferRect(Raster *frameBuffer) +{ + Rect r; + Raster *fb = frameBuffer->parent; + if(fb->type == Raster::CAMERA){ +#ifdef LIBRW_SDL2 + SDL_GetWindowSize(glGlobals.window, &r.w, &r.h); +#else + glfwGetFramebufferSize(glGlobals.window, &r.w, &r.h); +#endif + }else{ + r.w = fb->width; + r.h = fb->height; + } + r.x = 0; + r.y = 0; + + // Got a subraster + if(frameBuffer != fb){ + r.x = frameBuffer->offsetX; + // GL y offset is from bottom + r.y = r.h - frameBuffer->height - frameBuffer->offsetY; + r.w = frameBuffer->width; + r.h = frameBuffer->height; + } + + return r; +} + +static void +setViewport(Raster *frameBuffer) +{ + Rect r = getFramebufferRect(frameBuffer); + if(r.w != glGlobals.presentWidth || r.h != glGlobals.presentHeight || + r.x != glGlobals.presentOffX || r.y != glGlobals.presentOffY){ + glViewport(r.x, r.y, r.w, r.h); + glGlobals.presentWidth = r.w; + glGlobals.presentHeight = r.h; + glGlobals.presentOffX = r.x; + glGlobals.presentOffY = r.y; + } +} + +static void +beginUpdate(Camera *cam) +{ + float view[16], proj[16]; + // View Matrix + Matrix inv; + Matrix::invert(&inv, cam->getFrame()->getLTM()); + // Since we're looking into positive Z, + // flip X to ge a left handed view space. + view[0] = -inv.right.x; + view[1] = inv.right.y; + view[2] = inv.right.z; + view[3] = 0.0f; + view[4] = -inv.up.x; + view[5] = inv.up.y; + view[6] = inv.up.z; + view[7] = 0.0f; + view[8] = -inv.at.x; + view[9] = inv.at.y; + view[10] = inv.at.z; + view[11] = 0.0f; + view[12] = -inv.pos.x; + view[13] = inv.pos.y; + view[14] = inv.pos.z; + view[15] = 1.0f; + memcpy(&cam->devView, &view, sizeof(RawMatrix)); + setViewMatrix(view); + + // Projection Matrix + float32 invwx = 1.0f/cam->viewWindow.x; + float32 invwy = 1.0f/cam->viewWindow.y; + float32 invz = 1.0f/(cam->farPlane-cam->nearPlane); + + proj[0] = invwx; + proj[1] = 0.0f; + proj[2] = 0.0f; + proj[3] = 0.0f; + + proj[4] = 0.0f; + proj[5] = invwy; + proj[6] = 0.0f; + proj[7] = 0.0f; + + proj[8] = cam->viewOffset.x*invwx; + proj[9] = cam->viewOffset.y*invwy; + proj[12] = -proj[8]; + proj[13] = -proj[9]; + if(cam->projection == Camera::PERSPECTIVE){ + proj[10] = (cam->farPlane+cam->nearPlane)*invz; + proj[11] = 1.0f; + + proj[14] = -2.0f*cam->nearPlane*cam->farPlane*invz; + proj[15] = 0.0f; + }else{ + proj[10] = 2.0f*invz; + proj[11] = 0.0f; + + proj[14] = -(cam->farPlane+cam->nearPlane)*invz; + proj[15] = 1.0f; + } + memcpy(&cam->devProj, &proj, sizeof(RawMatrix)); + setProjectionMatrix(proj); + + if(rwStateCache.fogStart != cam->fogPlane){ + rwStateCache.fogStart = cam->fogPlane; + uniformStateDirty[RWGL_FOGSTART] = true; + stateDirty = 1; + } + if(rwStateCache.fogEnd != cam->farPlane){ + rwStateCache.fogEnd = cam->farPlane; + uniformStateDirty[RWGL_FOGEND] = true; + stateDirty = 1; + } + + setFrameBuffer(cam); + + setViewport(cam->frameBuffer); +} + +static void +endUpdate(Camera *cam) +{ +} + +static void +clearCamera(Camera *cam, RGBA *col, uint32 mode) +{ + RGBAf colf; + GLbitfield mask; + + setFrameBuffer(cam); + + // make sure we're only clearing the part of the framebuffer + // that is subrastered + bool setScissor = cam->frameBuffer != cam->frameBuffer->parent; + if(setScissor){ + Rect r = getFramebufferRect(cam->frameBuffer); + glScissor(r.x, r.y, r.w, r.h); + glEnable(GL_SCISSOR_TEST); + } + + convColor(&colf, col); + glClearColor(colf.red, colf.green, colf.blue, colf.alpha); + mask = 0; + if(mode & Camera::CLEARIMAGE) + mask |= GL_COLOR_BUFFER_BIT; + if(mode & Camera::CLEARZ) + mask |= GL_DEPTH_BUFFER_BIT; + if(mode & Camera::CLEARSTENCIL) + mask |= GL_STENCIL_BUFFER_BIT; + glDepthMask(GL_TRUE); + glClear(mask); + glDepthMask(rwStateCache.zwrite); + + if(setScissor) + glDisable(GL_SCISSOR_TEST); +} + +static void +showRaster(Raster *raster, uint32 flags) +{ +// glViewport(raster->offsetX, raster->offsetY, +// raster->width, raster->height); + +#ifdef LIBRW_SDL2 + if(flags & Raster::FLIPWAITVSYNCH) + SDL_GL_SetSwapInterval(1); + else + SDL_GL_SetSwapInterval(0); + SDL_GL_SwapWindow(glGlobals.window); +#else + if(flags & Raster::FLIPWAITVSYNCH) + glfwSwapInterval(1); + else + glfwSwapInterval(0); + glfwSwapBuffers(glGlobals.window); +#endif +} + +static bool32 +rasterRenderFast(Raster *raster, int32 x, int32 y) +{ + Raster *src = raster; + Raster *dst = Raster::getCurrentContext(); + Gl3Raster *natdst = PLUGINOFFSET(Gl3Raster, dst, nativeRasterOffset); + Gl3Raster *natsrc = PLUGINOFFSET(Gl3Raster, src, nativeRasterOffset); + + switch(dst->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + case Raster::CAMERATEXTURE: + switch(src->type){ + case Raster::CAMERA: + setActiveTexture(0); + glBindTexture(GL_TEXTURE_2D, natdst->texid); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x, (dst->height-src->height)-y, + 0, 0, src->width, src->height); + glBindTexture(GL_TEXTURE_2D, boundTexture[0]); + return 1; + } + break; + } + return 0; +} + +#ifdef LIBRW_SDL2 + +static void +addVideoMode(int displayIndex, int modeIndex) +{ + int i; + SDL_DisplayMode mode; + + SDL_GetDisplayMode(displayIndex, modeIndex, &mode); + + for(i = 1; i < glGlobals.numModes; i++){ + if(glGlobals.modes[i].mode.w == mode.w && + glGlobals.modes[i].mode.h == mode.h && + glGlobals.modes[i].mode.format == mode.format){ + // had this mode already, remember highest refresh rate + if(mode.refresh_rate > glGlobals.modes[i].mode.refresh_rate) + glGlobals.modes[i].mode.refresh_rate = mode.refresh_rate; + return; + } + } + + // none found, add + glGlobals.modes[glGlobals.numModes].mode = mode; + glGlobals.modes[glGlobals.numModes].flags = VIDEOMODEEXCLUSIVE; + glGlobals.numModes++; +} + +static void +makeVideoModeList(int displayIndex) +{ + int i, num, depth; + + num = SDL_GetNumDisplayModes(displayIndex); + rwFree(glGlobals.modes); + glGlobals.modes = rwNewT(DisplayMode, num+1, ID_DRIVER | MEMDUR_EVENT); + + SDL_GetCurrentDisplayMode(displayIndex, &glGlobals.modes[0].mode); + glGlobals.modes[0].flags = 0; + glGlobals.numModes = 1; + + for(i = 0; i < num; i++) + addVideoMode(displayIndex, i); + + for(i = 0; i < glGlobals.numModes; i++){ + depth = SDL_BITSPERPIXEL(glGlobals.modes[i].mode.format); + // set depth to power of two + for(glGlobals.modes[i].depth = 1; glGlobals.modes[i].depth < depth; glGlobals.modes[i].depth <<= 1); + } +} + +static int +openSDL2(EngineOpenParams *openparams) +{ + glGlobals.winWidth = openparams->width; + glGlobals.winHeight = openparams->height; + glGlobals.winTitle = openparams->windowtitle; + glGlobals.pWindow = openparams->window; + + memset(&gl3Caps, 0, sizeof(gl3Caps)); + + /* Init SDL */ + if(SDL_InitSubSystem(SDL_INIT_VIDEO)){ + RWERROR((ERR_GENERAL, SDL_GetError())); + return 0; + } + + makeVideoModeList(0); + + return 1; +} + +static int +closeSDL2(void) +{ + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return 1; +} + +static struct { + int gl; + int major, minor; +} profiles[] = { + { SDL_GL_CONTEXT_PROFILE_CORE, 3, 3 }, + { SDL_GL_CONTEXT_PROFILE_CORE, 2, 1 }, + { SDL_GL_CONTEXT_PROFILE_ES, 3, 1 }, + { SDL_GL_CONTEXT_PROFILE_ES, 2, 0 }, + { 0, 0, 0 }, +}; + +static int +startSDL2(void) +{ + SDL_Window *win; + SDL_GLContext ctx; + DisplayMode *mode; + + mode = &glGlobals.modes[glGlobals.currentMode]; + + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, glGlobals.numSamples); + + int i; + for(i = 0; profiles[i].gl; i++){ + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profiles[i].gl); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, profiles[i].major); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, profiles[i].minor); + + if(mode->flags & VIDEOMODEEXCLUSIVE) { + win = SDL_CreateWindow(glGlobals.winTitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, mode->mode.w, mode->mode.h, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN); + if (win) + SDL_SetWindowDisplayMode(win, &mode->mode); + } else { + win = SDL_CreateWindow(glGlobals.winTitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, glGlobals.winWidth, glGlobals.winHeight, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL); + if (win) + SDL_SetWindowDisplayMode(win, NULL); + } + if(win){ + gl3Caps.gles = profiles[i].gl == SDL_GL_CONTEXT_PROFILE_ES; + gl3Caps.glversion = profiles[i].major*10 + profiles[i].minor; + break; + } + } + if(win == nil){ + RWERROR((ERR_GENERAL, SDL_GetError())); + return 0; + } + ctx = SDL_GL_CreateContext(win); + + if (!((gl3Caps.gles ? gladLoadGLES2Loader : gladLoadGLLoader) ((GLADloadproc) SDL_GL_GetProcAddress, gl3Caps.glversion)) ) { + RWERROR((ERR_GENERAL, "gladLoadGLLoader failed")); + SDL_GL_DeleteContext(ctx); + SDL_DestroyWindow(win); + return 0; + } + + printf("OpenGL version: %s\n", glGetString(GL_VERSION)); + + glGlobals.window = win; + glGlobals.glcontext = ctx; + *glGlobals.pWindow = win; + glGlobals.presentWidth = 0; + glGlobals.presentHeight = 0; + glGlobals.presentOffX = 0; + glGlobals.presentOffY = 0; + return 1; +} + +static int +stopSDL2(void) +{ + SDL_GL_DeleteContext(glGlobals.glcontext); + SDL_DestroyWindow(glGlobals.window); + return 1; +} +#else + +static void +addVideoMode(const GLFWvidmode *mode) +{ + int i; + + for(i = 1; i < glGlobals.numModes; i++){ + if(glGlobals.modes[i].mode.width == mode->width && + glGlobals.modes[i].mode.height == mode->height && + glGlobals.modes[i].mode.redBits == mode->redBits && + glGlobals.modes[i].mode.greenBits == mode->greenBits && + glGlobals.modes[i].mode.blueBits == mode->blueBits){ + // had this mode already, remember highest refresh rate + if(mode->refreshRate > glGlobals.modes[i].mode.refreshRate) + glGlobals.modes[i].mode.refreshRate = mode->refreshRate; + return; + } + } + + // none found, add + glGlobals.modes[glGlobals.numModes].mode = *mode; + glGlobals.modes[glGlobals.numModes].flags = VIDEOMODEEXCLUSIVE; + glGlobals.numModes++; +} + +static void +makeVideoModeList(void) +{ + int i, num; + const GLFWvidmode *modes; + + modes = glfwGetVideoModes(glGlobals.monitor, &num); + rwFree(glGlobals.modes); + glGlobals.modes = rwNewT(DisplayMode, num+1, ID_DRIVER | MEMDUR_EVENT); + + glGlobals.modes[0].mode = *glfwGetVideoMode(glGlobals.monitor); + glGlobals.modes[0].flags = 0; + glGlobals.numModes = 1; + + for(i = 0; i < num; i++) + addVideoMode(&modes[i]); + + for(i = 0; i < glGlobals.numModes; i++){ + num = glGlobals.modes[i].mode.redBits + + glGlobals.modes[i].mode.greenBits + + glGlobals.modes[i].mode.blueBits; + // set depth to power of two + for(glGlobals.modes[i].depth = 1; glGlobals.modes[i].depth < num; glGlobals.modes[i].depth <<= 1); + } +} + +static int +openGLFW(EngineOpenParams *openparams) +{ + glGlobals.winWidth = openparams->width; + glGlobals.winHeight = openparams->height; + glGlobals.winTitle = openparams->windowtitle; + glGlobals.pWindow = openparams->window; + + memset(&gl3Caps, 0, sizeof(gl3Caps)); + + /* Init GLFW */ + if(!glfwInit()){ + RWERROR((ERR_GENERAL, "glfwInit() failed")); + return 0; + } + + glGlobals.monitor = glfwGetMonitors(&glGlobals.numMonitors)[0]; + + makeVideoModeList(); + + return 1; +} + +static int +closeGLFW(void) +{ + glfwTerminate(); + return 1; +} + +static void +glfwerr(int error, const char *desc) +{ + fprintf(stderr, "GLFW Error: %s\n", desc); +} + +static struct { + int gl; + int major, minor; +} profiles[] = { + { GLFW_OPENGL_API, 3, 3 }, + { GLFW_OPENGL_API, 2, 1 }, + { GLFW_OPENGL_ES_API, 3, 1 }, + { GLFW_OPENGL_ES_API, 2, 0 }, + { 0, 0, 0 }, +}; + +static int +startGLFW(void) +{ + GLFWwindow *win; + DisplayMode *mode; + + mode = &glGlobals.modes[glGlobals.currentMode]; + + glfwSetErrorCallback(glfwerr); + glfwWindowHint(GLFW_RED_BITS, mode->mode.redBits); + glfwWindowHint(GLFW_GREEN_BITS, mode->mode.greenBits); + glfwWindowHint(GLFW_BLUE_BITS, mode->mode.blueBits); + glfwWindowHint(GLFW_REFRESH_RATE, mode->mode.refreshRate); + + // GLX will round up to 2x or 4x if you ask for multisampling on with 1 sample + // So only apply the SAMPLES hint if we actually want multisampling + if (glGlobals.numSamples > 1) + glfwWindowHint(GLFW_SAMPLES, glGlobals.numSamples); + + int i; + for(i = 0; profiles[i].gl; i++){ + glfwWindowHint(GLFW_CLIENT_API, profiles[i].gl); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, profiles[i].major); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, profiles[i].minor); + + if(mode->flags & VIDEOMODEEXCLUSIVE) + win = glfwCreateWindow(mode->mode.width, mode->mode.height, glGlobals.winTitle, glGlobals.monitor, nil); + else + win = glfwCreateWindow(glGlobals.winWidth, glGlobals.winHeight, glGlobals.winTitle, nil, nil); + if(win){ + gl3Caps.gles = profiles[i].gl == GLFW_OPENGL_ES_API; + gl3Caps.glversion = profiles[i].major*10 + profiles[i].minor; + break; + } + } + if(win == nil){ + RWERROR((ERR_GENERAL, "glfwCreateWindow() failed")); + return 0; + } + glfwMakeContextCurrent(win); + + /* Init GLAD */ + if (!((gl3Caps.gles ? gladLoadGLES2Loader : gladLoadGLLoader) ((GLADloadproc) glfwGetProcAddress, gl3Caps.glversion)) ) { + RWERROR((ERR_GENERAL, "gladLoadGLLoader failed")); + glfwDestroyWindow(win); + return 0; + } + + printf("OpenGL version: %s\n", glGetString(GL_VERSION)); + + glGlobals.window = win; + *glGlobals.pWindow = win; + glGlobals.presentWidth = 0; + glGlobals.presentHeight = 0; + glGlobals.presentOffX = 0; + glGlobals.presentOffY = 0; + return 1; +} + +static int +stopGLFW(void) +{ + glfwDestroyWindow(glGlobals.window); + return 1; +} +#endif + +static int +initOpenGL(void) +{ +/* + // this only works from 3.0 onward, + // but luckily GLAD has already taken care of extensions for us + int numExt; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + for(int i = 0; i < numExt; i++){ + const char *ext = (const char*)glGetStringi(GL_EXTENSIONS, i); + if(ext == nil) + continue; // apparently that can happen... + if(strcmp(ext, "GL_EXT_texture_compression_s3tc") == 0) + gl3Caps.dxtSupported = true; + else if(strcmp(ext, "GL_KHR_texture_compression_astc_ldr") == 0) + gl3Caps.astcSupported = true; +// printf("%d %s\n", i, ext); + } +*/ + gl3Caps.dxtSupported = !!GLAD_GL_EXT_texture_compression_s3tc; + gl3Caps.astcSupported = !!GLAD_GL_KHR_texture_compression_astc_ldr; + + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl3Caps.maxAnisotropy); + + if(gl3Caps.gles){ + if(gl3Caps.glversion >= 30) + shaderDecl = shaderDecl310es; + else + shaderDecl = shaderDecl100es; + }else{ + if(gl3Caps.glversion >= 30) + shaderDecl = shaderDecl330; + else + shaderDecl = shaderDecl120; + } + +#ifndef RW_GL_USE_UBOS + u_alphaRef = registerUniform("u_alphaRef", UNIFORM_VEC4); + u_fogData = registerUniform("u_fogData", UNIFORM_VEC4); + u_fogColor = registerUniform("u_fogColor", UNIFORM_VEC4); + u_proj = registerUniform("u_proj", UNIFORM_MAT4); + u_view = registerUniform("u_view", UNIFORM_MAT4); + u_world = registerUniform("u_world", UNIFORM_MAT4); + u_ambLight = registerUniform("u_ambLight", UNIFORM_VEC4); + u_lightParams = registerUniform("u_lightParams", UNIFORM_VEC4, MAX_LIGHTS); + u_lightPosition = registerUniform("u_lightPosition", UNIFORM_VEC4, MAX_LIGHTS); + u_lightDirection = registerUniform("u_lightDirection", UNIFORM_VEC4, MAX_LIGHTS); + u_lightColor = registerUniform("u_lightColor", UNIFORM_VEC4, MAX_LIGHTS); + lastShaderUploaded = nil; +#else + registerBlock("Scene"); + registerBlock("Object"); + registerBlock("State"); +#endif + u_matColor = registerUniform("u_matColor", UNIFORM_VEC4); + u_surfProps = registerUniform("u_surfProps", UNIFORM_VEC4); + + // for im2d + registerUniform("u_xform", UNIFORM_VEC4); + + glClearColor(0.25, 0.25, 0.25, 1.0); + + byte whitepixel[4] = {0xFF, 0xFF, 0xFF, 0xFF}; + glGenTextures(1, &whitetex); + glBindTexture(GL_TEXTURE_2D, whitetex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, + 0, GL_RGBA, GL_UNSIGNED_BYTE, &whitepixel); + + resetRenderState(); + + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy); + + if(gl3Caps.glversion >= 30){ + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + } + +#ifdef RW_GL_USE_UBOS + glGenBuffers(1, &ubo_state); + glBindBuffer(GL_UNIFORM_BUFFER, ubo_state); + glBindBufferBase(GL_UNIFORM_BUFFER, gl3::findBlock("State"), ubo_state); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformState), &uniformState, + GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + glGenBuffers(1, &ubo_scene); + glBindBuffer(GL_UNIFORM_BUFFER, ubo_scene); + glBindBufferBase(GL_UNIFORM_BUFFER, gl3::findBlock("Scene"), ubo_scene); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformScene), &uniformScene, + GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + glGenBuffers(1, &ubo_object); + glBindBuffer(GL_UNIFORM_BUFFER, ubo_object); + glBindBufferBase(GL_UNIFORM_BUFFER, gl3::findBlock("Object"), ubo_object); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformObject), &uniformObject, + GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); +#endif + +#include "shaders/default_vs_gl.inc" +#include "shaders/simple_fs_gl.inc" + const char *vs[] = { shaderDecl, header_vert_src, default_vert_src, nil }; + const char *vs_fullLight[] = { shaderDecl, "#define DIRECTIONALS\n#define POINTLIGHTS\n#define SPOTLIGHTS\n", header_vert_src, default_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + const char *fs_noAT[] = { shaderDecl, "#define NO_ALPHATEST\n", header_frag_src, simple_frag_src, nil }; + + defaultShader = Shader::create(vs, fs); + assert(defaultShader); + defaultShader_noAT = Shader::create(vs, fs_noAT); + assert(defaultShader_noAT); + + defaultShader_fullLight = Shader::create(vs_fullLight, fs); + assert(defaultShader_fullLight); + defaultShader_fullLight_noAT = Shader::create(vs_fullLight, fs_noAT); + assert(defaultShader_fullLight_noAT); + + openIm2D(); + openIm3D(); + + return 1; +} + +static int +termOpenGL(void) +{ + closeIm3D(); + closeIm2D(); + + defaultShader->destroy(); + defaultShader = nil; + defaultShader_noAT->destroy(); + defaultShader_noAT = nil; + defaultShader_fullLight->destroy(); + defaultShader_fullLight = nil; + defaultShader_fullLight_noAT->destroy(); + defaultShader_fullLight_noAT = nil; + + glDeleteTextures(1, &whitetex); + whitetex = 0; + + return 1; +} + +static int +finalizeOpenGL(void) +{ + return 1; +} + +#ifdef LIBRW_SDL2 +static int +deviceSystemSDL2(DeviceReq req, void *arg, int32 n) +{ + VideoMode *rwmode; + + switch(req){ + case DEVICEOPEN: + return openSDL2((EngineOpenParams*)arg); + case DEVICECLOSE: + return closeSDL2(); + + case DEVICEINIT: + return startSDL2() && initOpenGL(); + case DEVICETERM: + return termOpenGL() && stopSDL2(); + + case DEVICEFINALIZE: + return finalizeOpenGL(); + + // TODO: implement subsystems + + case DEVICEGETNUMVIDEOMODES: + return glGlobals.numModes; + + case DEVICEGETCURRENTVIDEOMODE: + return glGlobals.currentMode; + + case DEVICESETVIDEOMODE: + if(n >= glGlobals.numModes) + return 0; + glGlobals.currentMode = n; + return 1; + + case DEVICEGETVIDEOMODEINFO: + rwmode = (VideoMode*)arg; + rwmode->width = glGlobals.modes[n].mode.w; + rwmode->height = glGlobals.modes[n].mode.h; + rwmode->depth = glGlobals.modes[n].depth; + rwmode->flags = glGlobals.modes[n].flags; + return 1; + + case DEVICEGETMAXMULTISAMPLINGLEVELS: + { + GLint maxSamples; + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + if(maxSamples == 0) + return 1; + return maxSamples; + } + case DEVICEGETMULTISAMPLINGLEVELS: + if(glGlobals.numSamples == 0) + return 1; + return glGlobals.numSamples; + case DEVICESETMULTISAMPLINGLEVELS: + glGlobals.numSamples = (uint32)n; + return 1; + default: + assert(0 && "not implemented"); + return 0; + } + return 1; +} + +#else + +static int +deviceSystemGLFW(DeviceReq req, void *arg, int32 n) +{ + GLFWmonitor **monitors; + VideoMode *rwmode; + + switch(req){ + case DEVICEOPEN: + return openGLFW((EngineOpenParams*)arg); + case DEVICECLOSE: + return closeGLFW(); + + case DEVICEINIT: + return startGLFW() && initOpenGL(); + case DEVICETERM: + return termOpenGL() && stopGLFW(); + + case DEVICEFINALIZE: + return finalizeOpenGL(); + + + case DEVICEGETNUMSUBSYSTEMS: + return glGlobals.numMonitors; + + case DEVICEGETCURRENTSUBSYSTEM: + return glGlobals.currentMonitor; + + case DEVICESETSUBSYSTEM: + monitors = glfwGetMonitors(&glGlobals.numMonitors); + if(n >= glGlobals.numMonitors) + return 0; + glGlobals.currentMonitor = n; + glGlobals.monitor = monitors[glGlobals.currentMonitor]; + return 1; + + case DEVICEGETSUBSSYSTEMINFO: + monitors = glfwGetMonitors(&glGlobals.numMonitors); + if(n >= glGlobals.numMonitors) + return 0; + strncpy(((SubSystemInfo*)arg)->name, glfwGetMonitorName(monitors[n]), sizeof(SubSystemInfo::name)); + return 1; + + + case DEVICEGETNUMVIDEOMODES: + return glGlobals.numModes; + + case DEVICEGETCURRENTVIDEOMODE: + return glGlobals.currentMode; + + case DEVICESETVIDEOMODE: + if(n >= glGlobals.numModes) + return 0; + glGlobals.currentMode = n; + return 1; + + case DEVICEGETVIDEOMODEINFO: + rwmode = (VideoMode*)arg; + rwmode->width = glGlobals.modes[n].mode.width; + rwmode->height = glGlobals.modes[n].mode.height; + rwmode->depth = glGlobals.modes[n].depth; + rwmode->flags = glGlobals.modes[n].flags; + return 1; + + case DEVICEGETMAXMULTISAMPLINGLEVELS: + { + GLint maxSamples; + glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); + if(maxSamples == 0) + return 1; + return maxSamples; + } + case DEVICEGETMULTISAMPLINGLEVELS: + if(glGlobals.numSamples == 0) + return 1; + return glGlobals.numSamples; + case DEVICESETMULTISAMPLINGLEVELS: + glGlobals.numSamples = (uint32)n; + return 1; + default: + assert(0 && "not implemented"); + return 0; + } + return 1; +} + +#endif + +Device renderdevice = { + -1.0f, 1.0f, + gl3::beginUpdate, + gl3::endUpdate, + gl3::clearCamera, + gl3::showRaster, + gl3::rasterRenderFast, + gl3::setRenderState, + gl3::getRenderState, + gl3::im2DRenderLine, + gl3::im2DRenderTriangle, + gl3::im2DRenderPrimitive, + gl3::im2DRenderIndexedPrimitive, + gl3::im3DTransform, + gl3::im3DRenderPrimitive, + gl3::im3DRenderIndexedPrimitive, + gl3::im3DEnd, +#ifdef LIBRW_SDL2 + gl3::deviceSystemSDL2 +#else + gl3::deviceSystemGLFW +#endif +}; + +} +} + +#else +// urgh, probably should get rid of that eventually +#include "rwgl3.h" +namespace rw { +namespace gl3 { +Gl3Caps gl3Caps; +bool32 needToReadBackTextures; +} +} +#endif diff --git a/vendor/librw/src/gl/gl3immed.cpp b/vendor/librw/src/gl/gl3immed.cpp new file mode 100644 index 0000000..3b4aae4 --- /dev/null +++ b/vendor/librw/src/gl/gl3immed.cpp @@ -0,0 +1,306 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#ifdef RW_OPENGL +#include "rwgl3.h" +#include "rwgl3impl.h" +#include "rwgl3shader.h" + +namespace rw { +namespace gl3 { + +uint32 im2DVbo, im2DIbo; +#ifdef RW_GL_USE_VAOS +uint32 im2DVao; +#endif + +Shader *im2dOverrideShader; + +static int32 u_xform; + +#define STARTINDICES 10000 +#define STARTVERTICES 10000 + +static Shader *im2dShader; +static AttribDesc im2dattribDesc[3] = { + { ATTRIB_POS, GL_FLOAT, GL_FALSE, 4, + sizeof(Im2DVertex), 0 }, + { ATTRIB_COLOR, GL_UNSIGNED_BYTE, GL_TRUE, 4, + sizeof(Im2DVertex), offsetof(Im2DVertex, r) }, + { ATTRIB_TEXCOORDS0, GL_FLOAT, GL_FALSE, 2, + sizeof(Im2DVertex), offsetof(Im2DVertex, u) }, +}; + +static int primTypeMap[] = { + GL_POINTS, // invalid + GL_LINES, + GL_LINE_STRIP, + GL_TRIANGLES, + GL_TRIANGLE_STRIP, + GL_TRIANGLE_FAN, + GL_POINTS +}; + +void +openIm2D(void) +{ + // must already be registered by device. we just need the value + u_xform = registerUniform("u_xform", UNIFORM_VEC4); + +#include "shaders/im2d_gl.inc" +#include "shaders/simple_fs_gl.inc" + const char *vs[] = { shaderDecl, header_vert_src, im2d_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + im2dShader = Shader::create(vs, fs); + assert(im2dShader); + + glGenBuffers(1, &im2DIbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im2DIbo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, STARTINDICES*2, nil, GL_STREAM_DRAW); + + glGenBuffers(1, &im2DVbo); + glBindBuffer(GL_ARRAY_BUFFER, im2DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im2DVertex), nil, GL_STREAM_DRAW); + +#ifdef RW_GL_USE_VAOS + glGenVertexArrays(1, &im2DVao); + glBindVertexArray(im2DVao); + setAttribPointers(im2dattribDesc, 3); +#endif +} + +void +closeIm2D(void) +{ + glDeleteBuffers(1, &im2DIbo); + glDeleteBuffers(1, &im2DVbo); +#ifdef RW_GL_USE_VAOS + glDeleteVertexArrays(1, &im2DVao); +#endif + im2dShader->destroy(); + im2dShader = nil; +} + +static Im2DVertex tmpprimbuf[3]; + +void +im2DRenderLine(void *vertices, int32 numVertices, int32 vert1, int32 vert2) +{ + Im2DVertex *verts = (Im2DVertex*)vertices; + tmpprimbuf[0] = verts[vert1]; + tmpprimbuf[1] = verts[vert2]; + im2DRenderPrimitive(PRIMTYPELINELIST, tmpprimbuf, 2); +} + +void +im2DRenderTriangle(void *vertices, int32 numVertices, int32 vert1, int32 vert2, int32 vert3) +{ + Im2DVertex *verts = (Im2DVertex*)vertices; + tmpprimbuf[0] = verts[vert1]; + tmpprimbuf[1] = verts[vert2]; + tmpprimbuf[2] = verts[vert3]; + im2DRenderPrimitive(PRIMTYPETRILIST, tmpprimbuf, 3); +} + +void +im2DSetXform(void) +{ + GLfloat xform[4]; + Camera *cam; + cam = (Camera*)engine->currentCamera; + xform[0] = 2.0f/cam->frameBuffer->width; + xform[1] = -2.0f/cam->frameBuffer->height; + xform[2] = -1.0f; + xform[3] = 1.0f; + setUniform(u_xform, xform); +// glUniform4fv(currentShader->uniformLocations[u_xform], 1, xform); +} + +void +im2DRenderPrimitive(PrimitiveType primType, void *vertices, int32 numVertices) +{ +#ifdef RW_GL_USE_VAOS + glBindVertexArray(im2DVao); +#endif + + glBindBuffer(GL_ARRAY_BUFFER, im2DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im2DVertex), nil, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices*sizeof(Im2DVertex), vertices); + + if(im2dOverrideShader) + im2dOverrideShader->use(); + else + im2dShader->use(); +#ifndef RW_GL_USE_VAOS + setAttribPointers(im2dattribDesc, 3); +#endif + + im2DSetXform(); + + flushCache(); + glDrawArrays(primTypeMap[primType], 0, numVertices); +#ifndef RW_GL_USE_VAOS + disableAttribPointers(im2dattribDesc, 3); +#endif +} + +void +im2DRenderIndexedPrimitive(PrimitiveType primType, + void *vertices, int32 numVertices, + void *indices, int32 numIndices) +{ +#ifdef RW_GL_USE_VAOS + glBindVertexArray(im2DVao); +#endif + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im2DIbo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, STARTINDICES*2, nil, GL_STREAM_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numIndices*2, indices); + + glBindBuffer(GL_ARRAY_BUFFER, im2DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im2DVertex), nil, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices*sizeof(Im2DVertex), vertices); + + if(im2dOverrideShader) + im2dOverrideShader->use(); + else + im2dShader->use(); +#ifndef RW_GL_USE_VAOS + setAttribPointers(im2dattribDesc, 3); +#endif + + im2DSetXform(); + + flushCache(); + glDrawElements(primTypeMap[primType], numIndices, + GL_UNSIGNED_SHORT, nil); +#ifndef RW_GL_USE_VAOS + disableAttribPointers(im2dattribDesc, 3); +#endif +} + + +// Im3D + + +static Shader *im3dShader; +static AttribDesc im3dattribDesc[3] = { + { ATTRIB_POS, GL_FLOAT, GL_FALSE, 3, + sizeof(Im3DVertex), 0 }, + { ATTRIB_COLOR, GL_UNSIGNED_BYTE, GL_TRUE, 4, + sizeof(Im3DVertex), offsetof(Im3DVertex, r) }, + { ATTRIB_TEXCOORDS0, GL_FLOAT, GL_FALSE, 2, + sizeof(Im3DVertex), offsetof(Im3DVertex, u) }, +}; +static uint32 im3DVbo, im3DIbo; +#ifdef RW_GL_USE_VAOS +static uint32 im3DVao; +#endif +static int32 num3DVertices; // not actually needed here + +void +openIm3D(void) +{ +#include "shaders/im3d_gl.inc" +#include "shaders/simple_fs_gl.inc" + const char *vs[] = { shaderDecl, header_vert_src, im3d_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + im3dShader = Shader::create(vs, fs); + assert(im3dShader); + + glGenBuffers(1, &im3DIbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im3DIbo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, STARTINDICES*2, nil, GL_STREAM_DRAW); + + glGenBuffers(1, &im3DVbo); + glBindBuffer(GL_ARRAY_BUFFER, im3DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im3DVertex), nil, GL_STREAM_DRAW); + +#ifdef RW_GL_USE_VAOS + glGenVertexArrays(1, &im3DVao); + glBindVertexArray(im3DVao); + setAttribPointers(im3dattribDesc, 3); +#endif +} + +void +closeIm3D(void) +{ + glDeleteBuffers(1, &im3DIbo); + glDeleteBuffers(1, &im3DVbo); +#ifdef RW_GL_USE_VAOS + glDeleteVertexArrays(1, &im3DVao); +#endif + im3dShader->destroy(); + im3dShader = nil; +} + +void +im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags) +{ + if(world == nil){ + static Matrix ident; + ident.setIdentity(); + world = &ident; + } + setWorldMatrix(world); + im3dShader->use(); + + if((flags & im3d::VERTEXUV) == 0) + SetRenderStatePtr(TEXTURERASTER, nil); + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(im2DVao); +#endif + + glBindBuffer(GL_ARRAY_BUFFER, im3DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im3DVertex), nil, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices*sizeof(Im3DVertex), vertices); +#ifndef RW_GL_USE_VAOS + setAttribPointers(im3dattribDesc, 3); +#endif + num3DVertices = numVertices; +} + +void +im3DRenderPrimitive(PrimitiveType primType) +{ + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im3DIbo); + + flushCache(); + glDrawArrays(primTypeMap[primType], 0, num3DVertices); +} + +void +im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices) +{ + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im3DIbo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, STARTINDICES*2, nil, GL_STREAM_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numIndices*2, indices); + + flushCache(); + glDrawElements(primTypeMap[primType], numIndices, + GL_UNSIGNED_SHORT, nil); +} + +void +im3DEnd(void) +{ +#ifndef RW_GL_USE_VAOS + disableAttribPointers(im3dattribDesc, 3); +#endif +} + +} +} + +#endif diff --git a/vendor/librw/src/gl/gl3matfx.cpp b/vendor/librw/src/gl/gl3matfx.cpp new file mode 100644 index 0000000..06b01c1 --- /dev/null +++ b/vendor/librw/src/gl/gl3matfx.cpp @@ -0,0 +1,258 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwplugins.h" + +#include "rwgl3.h" +#include "rwgl3shader.h" +#include "rwgl3plg.h" + +#include "rwgl3impl.h" + +namespace rw { +namespace gl3 { + +#ifdef RW_OPENGL + +static Shader *envShader, *envShader_noAT; +static Shader *envShader_fullLight, *envShader_fullLight_noAT; +static int32 u_texMatrix; +static int32 u_fxparams; +static int32 u_colorClamp; +static int32 u_envColor; + +void +matfxDefaultRender(InstanceDataHeader *header, InstanceData *inst, int32 vsBits, uint32 flags) +{ + Material *m; + m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + if((vsBits & VSLIGHT_MASK) == 0){ + if(getAlphaTest()) + defaultShader->use(); + else + defaultShader_noAT->use(); + }else{ + if(getAlphaTest()) + defaultShader_fullLight->use(); + else + defaultShader_fullLight_noAT->use(); + } + + drawInst(header, inst); +} + +static Frame *lastEnvFrame; + +static RawMatrix normal2texcoord = { + { 0.5f, 0.0f, 0.0f }, 0.0f, + { 0.0f, -0.5f, 0.0f }, 0.0f, + { 0.0f, 0.0f, 1.0f }, 0.0f, + { 0.5f, 0.5f, 0.0f }, 1.0f +}; + +void +uploadEnvMatrix(Frame *frame) +{ + Matrix invMat; + if(frame == nil) + frame = engine->currentCamera->getFrame(); + + // cache the matrix across multiple meshes + static RawMatrix envMtx; +// can't do it, frame matrix may change +// if(frame != lastEnvFrame){ +// lastEnvFrame = frame; + { + + RawMatrix invMtx; + Matrix::invert(&invMat, frame->getLTM()); + convMatrix(&invMtx, &invMat); + invMtx.pos.set(0.0f, 0.0f, 0.0f); + float uscale = fabs(normal2texcoord.right.x); + normal2texcoord.right.x = MatFX::envMapFlipU ? -uscale : uscale; + RawMatrix::mult(&envMtx, &invMtx, &normal2texcoord); + } + setUniform(u_texMatrix, &envMtx); +} + +void +matfxEnvRender(InstanceDataHeader *header, InstanceData *inst, int32 vsBits, uint32 flags, MatFX::Env *env) +{ + Material *m; + m = inst->material; + + if(env->tex == nil || env->coefficient == 0.0f){ + matfxDefaultRender(header, inst, vsBits, flags); + return; + } + + setTexture(0, m->texture); + setTexture(1, env->tex); + uploadEnvMatrix(env->frame); + + setMaterial(flags, m->color, m->surfaceProps); + + float fxparams[4]; + fxparams[0] = env->coefficient; + fxparams[1] = env->fbAlpha ? 0.0f : 1.0f; + fxparams[2] = fxparams[3] = 0.0f; + + setUniform(u_fxparams, fxparams); + static float zero[4]; + static float one[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + // This clamps the vertex color below. With it we can achieve both PC and PS2 style matfx + if(MatFX::envMapApplyLight) + setUniform(u_colorClamp, zero); + else + setUniform(u_colorClamp, one); + RGBAf envcol[4]; + if(MatFX::envMapUseMatColor) + convColor(envcol, &m->color); + else + convColor(envcol, &MatFX::envMapColor); + setUniform(u_envColor, envcol); + + rw::SetRenderState(VERTEXALPHA, 1); + rw::SetRenderState(SRCBLEND, BLENDONE); + + if((vsBits & VSLIGHT_MASK) == 0){ + if(getAlphaTest()) + envShader->use(); + else + envShader_noAT->use(); + }else{ + if(getAlphaTest()) + envShader_fullLight->use(); + else + envShader_fullLight_noAT->use(); + } + + drawInst(header, inst); + + rw::SetRenderState(SRCBLEND, BLENDSRCALPHA); +} + +void +matfxRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + uint32 flags = atomic->geometry->flags; + setWorldMatrix(atomic->getFrame()->getLTM()); + int32 vsBits = lightingCB(atomic); + + setupVertexInput(header); + + lastEnvFrame = nil; + + InstanceData *inst = header->inst; + int32 n = header->numMeshes; + + while(n--){ + MatFX *matfx = MatFX::get(inst->material); + + if(matfx == nil) + matfxDefaultRender(header, inst, vsBits, flags); + else switch(matfx->type){ + case MatFX::ENVMAP: + matfxEnvRender(header, inst, vsBits, flags, &matfx->fx[0].env); + break; + default: + matfxDefaultRender(header, inst, vsBits, flags); + break; + } + inst++; + } + teardownVertexInput(header); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = matfxRenderCB; + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_GL3] = makeMatFXPipeline(); + +#include "shaders/matfx_gl.inc" + const char *vs[] = { shaderDecl, header_vert_src, matfx_env_vert_src, nil }; + const char *vs_fullLight[] = { shaderDecl, "#define DIRECTIONALS\n#define POINTLIGHTS\n#define SPOTLIGHTS\n", header_vert_src, matfx_env_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, matfx_env_frag_src, nil }; + const char *fs_noAT[] = { shaderDecl, "#define NO_ALPHATEST\n", header_frag_src, matfx_env_frag_src, nil }; + + envShader = Shader::create(vs, fs); + assert(envShader); + envShader_noAT = Shader::create(vs, fs_noAT); + assert(envShader_noAT); + + envShader_fullLight = Shader::create(vs_fullLight, fs); + assert(envShader_fullLight); + envShader_fullLight_noAT = Shader::create(vs_fullLight, fs_noAT); + assert(envShader_fullLight_noAT); + + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_GL3])->destroy(); + matFXGlobals.pipelines[PLATFORM_GL3] = nil; + + envShader->destroy(); + envShader = nil; + envShader_noAT->destroy(); + envShader_noAT = nil; + envShader_fullLight->destroy(); + envShader_fullLight = nil; + envShader_fullLight_noAT->destroy(); + envShader_fullLight_noAT = nil; + + return o; +} + +void +initMatFX(void) +{ + u_texMatrix = registerUniform("u_texMatrix", UNIFORM_MAT4); + u_fxparams = registerUniform("u_fxparams", UNIFORM_VEC4); + u_colorClamp = registerUniform("u_colorClamp", UNIFORM_VEC4); + u_envColor = registerUniform("u_envColor", UNIFORM_VEC4); + + Driver::registerPlugin(PLATFORM_GL3, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +#else + +void initMatFX(void) { } + +#endif + +} +} + diff --git a/vendor/librw/src/gl/gl3pipe.cpp b/vendor/librw/src/gl/gl3pipe.cpp new file mode 100644 index 0000000..4df0b7c --- /dev/null +++ b/vendor/librw/src/gl/gl3pipe.cpp @@ -0,0 +1,333 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" + +#include "rwgl3.h" +#include "rwgl3shader.h" + +namespace rw { +namespace gl3 { + +// TODO: make some of these things platform-independent + +#ifdef RW_OPENGL + +void +freeInstanceData(Geometry *geometry) +{ + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_GL3) + return; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + glDeleteBuffers(1, &header->ibo); + glDeleteBuffers(1, &header->vbo); +#ifdef RW_GL_USE_VAOS + glDeleteBuffers(1, &header->vao); +#endif + rwFree(header->indexBuffer); + rwFree(header->vertexBuffer); + rwFree(header->attribDesc); + rwFree(header->inst); + rwFree(header); +} + +void* +destroyNativeData(void *object, int32, int32) +{ + freeInstanceData((Geometry*)object); + return object; +} + +static InstanceDataHeader* +instanceMesh(rw::ObjPipeline *rwpipe, Geometry *geo) +{ + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + MeshHeader *meshh = geo->meshHeader; + geo->instData = header; + header->platform = PLATFORM_GL3; + + header->serialNumber = meshh->serialNum; + header->numMeshes = meshh->numMeshes; + header->primType = meshh->flags == 1 ? GL_TRIANGLE_STRIP : GL_TRIANGLES; + header->totalNumVertex = geo->numVertices; + header->totalNumIndex = meshh->totalIndices; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + header->indexBuffer = rwNewT(uint16, header->totalNumIndex, MEMDUR_EVENT | ID_GEOMETRY); + InstanceData *inst = header->inst; + Mesh *mesh = meshh->getMeshes(); + uint32 offset = 0; + for(uint32 i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &inst->minVert, &inst->numVertices); + assert(inst->minVert != 0xFFFFFFFF); + inst->numIndex = mesh->numIndices; + inst->material = mesh->material; + inst->vertexAlpha = 0; + inst->program = 0; + inst->offset = offset; + memcpy((uint8*)header->indexBuffer + inst->offset, + mesh->indices, inst->numIndex*2); + offset += inst->numIndex*2; + mesh++; + inst++; + } + + header->vertexBuffer = nil; + header->numAttribs = 0; + header->attribDesc = nil; + header->ibo = 0; + header->vbo = 0; + +#ifdef RW_GL_USE_VAOS + glGenVertexArrays(1, &header->vao); + glBindVertexArray(header->vao); +#endif + glGenBuffers(1, &header->ibo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, header->totalNumIndex*2, + header->indexBuffer, GL_STATIC_DRAW); + + return header; +} + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // don't try to (re)instance native data + if(geo->flags & Geometry::NATIVE) + return; + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + if(geo->instData){ + // Already have instanced data, so check if we have to reinstance + assert(header->platform == PLATFORM_GL3); + if(header->serialNumber != geo->meshHeader->serialNum){ + // Mesh changed, so reinstance everything + freeInstanceData(geo); + } + } + + // no instance or complete reinstance + if(geo->instData == nil){ + geo->instData = instanceMesh(rwpipe, geo); + pipe->instanceCB(geo, (InstanceDataHeader*)geo->instData, 0); + }else if(geo->lockedSinceInst) + pipe->instanceCB(geo, (InstanceDataHeader*)geo->instData, 1); + + geo->lockedSinceInst = 0; +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + assert(0 && "can't uninstance"); +} + +static void +render(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + pipe->instance(atomic); + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_GL3); + if(pipe->renderCB) + pipe->renderCB(atomic, (InstanceDataHeader*)geo->instData); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_GL3); + this->impl.instance = gl3::instance; + this->impl.uninstance = gl3::uninstance; + this->impl.render = gl3::render; + this->instanceCB = nil; + this->uninstanceCB = nil; + this->renderCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +defaultInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) +{ + AttribDesc *attribs, *a; + + bool isPrelit = !!(geo->flags & Geometry::PRELIT); + bool hasNormals = !!(geo->flags & Geometry::NORMALS); + + if(!reinstance){ + AttribDesc tmpAttribs[12]; + uint32 stride; + + // + // Create attribute descriptions + // + a = tmpAttribs; + stride = 0; + + // Positions + a->index = ATTRIB_POS; + a->size = 3; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 12; + a++; + + // Normals + // TODO: compress + if(hasNormals){ + a->index = ATTRIB_NORMAL; + a->size = 3; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 12; + a++; + } + + // Prelighting + if(isPrelit){ + a->index = ATTRIB_COLOR; + a->size = 4; + a->type = GL_UNSIGNED_BYTE; + a->normalized = GL_TRUE; + a->offset = stride; + stride += 4; + a++; + } + + // Texture coordinates + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + a->index = ATTRIB_TEXCOORDS0+n; + a->size = 2; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 8; + a++; + } + + header->numAttribs = a - tmpAttribs; + for(a = tmpAttribs; a != &tmpAttribs[header->numAttribs]; a++) + a->stride = stride; + header->attribDesc = rwNewT(AttribDesc, header->numAttribs, MEMDUR_EVENT | ID_GEOMETRY); + memcpy(header->attribDesc, tmpAttribs, + header->numAttribs*sizeof(AttribDesc)); + + // + // Allocate vertex buffer + // + header->vertexBuffer = rwNewT(uint8, header->totalNumVertex*stride, MEMDUR_EVENT | ID_GEOMETRY); + assert(header->vbo == 0); + glGenBuffers(1, &header->vbo); + } + + attribs = header->attribDesc; + + // + // Fill vertex buffer + // + + uint8 *verts = header->vertexBuffer; + + // Positions + if(!reinstance || geo->lockedSinceInst&Geometry::LOCKVERTICES){ + for(a = attribs; a->index != ATTRIB_POS; a++) + ; + instV3d(VERT_FLOAT3, verts + a->offset, + geo->morphTargets[0].vertices, + header->totalNumVertex, a->stride); + } + + // Normals + if(hasNormals && (!reinstance || geo->lockedSinceInst&Geometry::LOCKNORMALS)){ + for(a = attribs; a->index != ATTRIB_NORMAL; a++) + ; + instV3d(VERT_FLOAT3, verts + a->offset, + geo->morphTargets[0].normals, + header->totalNumVertex, a->stride); + } + + // Prelighting + if(isPrelit && (!reinstance || geo->lockedSinceInst&Geometry::LOCKPRELIGHT)){ + for(a = attribs; a->index != ATTRIB_COLOR; a++) + ; + int n = header->numMeshes; + InstanceData *inst = header->inst; + while(n--){ + assert(inst->minVert != 0xFFFFFFFF); + inst->vertexAlpha = instColor(VERT_RGBA, + verts + a->offset + a->stride*inst->minVert, + geo->colors + inst->minVert, + inst->numVertices, a->stride); + inst++; + } + } + + // Texture coordinates + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + if(!reinstance || geo->lockedSinceInst&(Geometry::LOCKTEXCOORDS<index != ATTRIB_TEXCOORDS0+n; a++) + ; + instTexCoords(VERT_FLOAT2, verts + a->offset, + geo->texCoords[n], + header->totalNumVertex, a->stride); + } + } + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(header->vao); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); +#endif + glBindBuffer(GL_ARRAY_BUFFER, header->vbo); + glBufferData(GL_ARRAY_BUFFER, header->totalNumVertex*attribs[0].stride, + header->vertexBuffer, GL_STATIC_DRAW); +#ifdef RW_GL_USE_VAOS + setAttribPointers(header->attribDesc, header->numAttribs); + glBindVertexArray(0); +#endif +} + +void +defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + assert(0 && "can't uninstance"); +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB; + return pipe; +} + +#else +void *destroyNativeData(void *object, int32, int32) { return object; } +#endif + +} +} diff --git a/vendor/librw/src/gl/gl3raster.cpp b/vendor/librw/src/gl/gl3raster.cpp new file mode 100644 index 0000000..b7c3941 --- /dev/null +++ b/vendor/librw/src/gl/gl3raster.cpp @@ -0,0 +1,1045 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" + +#include "rwgl3.h" +#include "rwgl3shader.h" +#include "rwgl3impl.h" + +#define PLUGIN_ID ID_DRIVER + +namespace rw { +namespace gl3 { + +int32 nativeRasterOffset; + +static uint32 +getLevelSize(Raster *raster, int32 level) +{ + int i; + Gl3Raster *natras = GETGL3RASTEREXT(raster); + + int w = raster->originalWidth; + int h = raster->originalHeight; + int s = raster->originalStride; + int minDim = 1; + +#ifdef RW_OPENGL + switch(natras->internalFormat){ + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + minDim = 4; + break; + } +#endif + + for(i = 0; i < level; i++){ + if(w > minDim){ + w /= 2; + s /= 2; + } + if(h > minDim) + h /= 2; + } + + return s*h; +} + +#ifdef RW_OPENGL + +static Raster* +rasterCreateTexture(Raster *raster) +{ + if(raster->format & (Raster::PAL4 | Raster::PAL8)){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + + Gl3Raster *natras = GETGL3RASTEREXT(raster); + switch(raster->format & 0xF00){ + case Raster::C8888: + natras->internalFormat = GL_RGBA8; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = 1; + natras->bpp = 4; + raster->depth = 32; + break; + case Raster::C888: + natras->internalFormat = GL_RGB8; + natras->format = GL_RGB; + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = 0; + natras->bpp = 3; + raster->depth = 24; + break; + case Raster::C1555: + natras->internalFormat = GL_RGB5_A1; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_SHORT_5_5_5_1; + natras->hasAlpha = 1; + natras->bpp = 2; + raster->depth = 16; + break; + default: + RWERROR((ERR_INVRASTER)); + return nil; + } + + if(gl3Caps.gles){ + // glReadPixels only supports GL_RGBA + natras->internalFormat = GL_RGBA8; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_BYTE; + natras->bpp = 4; + } + + raster->stride = raster->width*natras->bpp; + + if(raster->format & Raster::MIPMAP){ + int w = raster->width; + int h = raster->height; + natras->numLevels = 1; + while(w != 1 || h != 1){ + natras->numLevels++; + if(w > 1) w /= 2; + if(h > 1) h /= 2; + } + } + natras->autogenMipmap = (raster->format & (Raster::MIPMAP|Raster::AUTOMIPMAP)) == (Raster::MIPMAP|Raster::AUTOMIPMAP); + if(natras->autogenMipmap) + natras->numLevels = 1; + + glGenTextures(1, &natras->texid); + uint32 prev = bindTexture(natras->texid); + glTexImage2D(GL_TEXTURE_2D, 0, natras->internalFormat, + raster->width, raster->height, + 0, natras->format, natras->type, nil); + // TODO: allocate other levels...probably + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, natras->numLevels-1); + natras->filterMode = 0; + natras->addressU = 0; + natras->addressV = 0; + natras->maxAnisotropy = 1; + + bindTexture(prev); + return raster; +} + +static Raster* +rasterCreateCameraTexture(Raster *raster) +{ + if(raster->format & (Raster::PAL4 | Raster::PAL8)){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + + // TODO: figure out what the backbuffer is and use that as a default + Gl3Raster *natras = GETGL3RASTEREXT(raster); + switch(raster->format & 0xF00){ + case Raster::C8888: + natras->internalFormat = GL_RGBA8; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = 1; + natras->bpp = 4; + break; + case Raster::C888: + default: + natras->internalFormat = GL_RGB8; + natras->format = GL_RGB; + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = 0; + natras->bpp = 3; + break; + case Raster::C1555: + natras->internalFormat = GL_RGB5_A1; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_SHORT_5_5_5_1; + natras->hasAlpha = 1; + natras->bpp = 2; + break; + } + + // i don't remember why this was once here... + if(gl3Caps.gles){ + // glReadPixels only supports GL_RGBA +// natras->internalFormat = GL_RGBA8; +// natras->format = GL_RGBA; +// natras->type = GL_UNSIGNED_BYTE; +// natras->bpp = 4; + } + + raster->stride = raster->width*natras->bpp; + + natras->autogenMipmap = (raster->format & (Raster::MIPMAP|Raster::AUTOMIPMAP)) == (Raster::MIPMAP|Raster::AUTOMIPMAP); + + glGenTextures(1, &natras->texid); + uint32 prev = bindTexture(natras->texid); + glTexImage2D(GL_TEXTURE_2D, 0, natras->internalFormat, + raster->width, raster->height, + 0, natras->format, natras->type, nil); + natras->filterMode = 0; + natras->addressU = 0; + natras->addressV = 0; + natras->maxAnisotropy = 1; + + bindTexture(prev); + + + glGenFramebuffers(1, &natras->fbo); + bindFramebuffer(natras->fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, natras->texid, 0); + bindFramebuffer(0); + natras->fboMate = nil; + + return raster; +} + +static Raster* +rasterCreateCamera(Raster *raster) +{ + Gl3Raster *natras = GETGL3RASTEREXT(raster); + + // TODO: set/check width, height, depth, format? + + // used for locking right now + raster->format = Raster::C888; + natras->internalFormat = GL_RGB8; + natras->format = GL_RGB; + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = 0; + natras->bpp = 3; + + natras->autogenMipmap = 0; + + natras->texid = 0; + natras->fbo = 0; + natras->fboMate = nil; + + return raster; +} + +static Raster* +rasterCreateZbuffer(Raster *raster) +{ + Gl3Raster *natras = GETGL3RASTEREXT(raster); + + if(gl3Caps.gles){ + // have to use RBO on GLES!! + glGenRenderbuffers(1, &natras->texid); + glBindRenderbuffer(GL_RENDERBUFFER, natras->texid); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, raster->width, raster->height); + }else{ + // TODO: set/check width, height, depth, format? + natras->internalFormat = GL_DEPTH_STENCIL; + natras->format = GL_DEPTH_STENCIL; + natras->type = GL_UNSIGNED_INT_24_8; + + natras->autogenMipmap = 0; + + glGenTextures(1, &natras->texid); + uint32 prev = bindTexture(natras->texid); + glTexImage2D(GL_TEXTURE_2D, 0, natras->internalFormat, + raster->width, raster->height, + 0, natras->format, natras->type, nil); + natras->filterMode = 0; + natras->addressU = 0; + natras->addressV = 0; + natras->maxAnisotropy = 1; + + bindTexture(prev); + } + natras->fbo = 0; + natras->fboMate = nil; + + return raster; +} + + +#endif + + +void +allocateDXT(Raster *raster, int32 dxt, int32 numLevels, bool32 hasAlpha) +{ +#ifdef RW_OPENGL + assert(raster->type == Raster::TEXTURE); + + Gl3Raster *natras = GETGL3RASTEREXT(raster); + switch(dxt){ + case 1: + if(hasAlpha){ + natras->internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + natras->format = GL_RGBA; + }else{ + natras->internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + natras->format = GL_RGB; + } + // bogus, but stride*height should be the size of the image + // 4x4 in 8 bytes + raster->stride = raster->width/2; + break; + case 3: + natras->internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + natras->format = GL_RGBA; + // 4x4 in 16 bytes + raster->stride = raster->width; + break; + case 5: + natras->internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + natras->format = GL_RGBA; + // 4x4 in 16 bytes + raster->stride = raster->width; + break; + default: + assert(0 && "invalid DXT format"); + } + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = hasAlpha; + natras->bpp = 2; + raster->depth = 16; + + natras->isCompressed = 1; + if(raster->format & Raster::MIPMAP) + natras->numLevels = numLevels; + natras->autogenMipmap = (raster->format & (Raster::MIPMAP|Raster::AUTOMIPMAP)) == (Raster::MIPMAP|Raster::AUTOMIPMAP); + if(natras->autogenMipmap) + natras->numLevels = 1; + + glGenTextures(1, &natras->texid); + uint32 prev = bindTexture(natras->texid); + glTexImage2D(GL_TEXTURE_2D, 0, natras->internalFormat, + raster->width, raster->height, + 0, natras->format, natras->type, nil); + // TODO: allocate other levels...probably + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, natras->numLevels-1); + natras->filterMode = 0; + natras->addressU = 0; + natras->addressV = 0; + natras->maxAnisotropy = 1; + + bindTexture(prev); + + raster->originalStride = raster->stride; + + if(gl3Caps.gles && needToReadBackTextures){ + // Uh oh, need to keep a copy in cpu memory + int32 i; + int32 size = 0; + for(i = 0; i < natras->numLevels; i++) + size += getLevelSize(raster, i); + uint8 *data = (uint8*)rwNew(sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(natras->numLevels-1)+size, + MEMDUR_EVENT | ID_DRIVER); + RasterLevels *levels = (RasterLevels*)data; + data += sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(natras->numLevels-1); + levels->numlevels = natras->numLevels; + levels->format = 0; // not needed + for(i = 0; i < natras->numLevels; i++){ + levels->levels[i].data = data; + levels->levels[i].size = getLevelSize(raster, i); + levels->levels[i].width = 0; // we don't need those here, maybe later... + levels->levels[i].height = 0; + data += levels->levels[i].size; + } + natras->backingStore = levels; + } + + raster->flags &= ~Raster::DONTALLOCATE; +#endif +} + +/* +{ 0, 0, 0 }, +{ 16, 4, GL_RGBA }, // 1555 +{ 16, 3, GL_RGB }, // 565 +{ 16, 4, GL_RGBA }, // 4444 +{ 0, 0, 0 }, // LUM8 +{ 32, 4, GL_RGBA }, // 8888 +{ 24, 3, GL_RGB }, // 888 +{ 16, 3, GL_RGB }, // D16 +{ 24, 3, GL_RGB }, // D24 +{ 32, 4, GL_RGBA }, // D32 +{ 16, 3, GL_RGB }, // 555 + +0, +GL_RGB5_A1, +GL_RGB5, +GL_RGBA4, +0, +GL_RGBA8, +GL_RGB8, +GL_RGB5, +GL_RGB8, +GL_RGBA8, +GL_RGB5 +*/ + +Raster* +rasterCreate(Raster *raster) +{ + Gl3Raster *natras = GETGL3RASTEREXT(raster); + + natras->isCompressed = 0; + natras->hasAlpha = 0; + natras->numLevels = 1; + + Raster *ret = raster; + + if(raster->width == 0 || raster->height == 0){ + raster->flags |= Raster::DONTALLOCATE; + raster->stride = 0; + goto ret; + } + if(raster->flags & Raster::DONTALLOCATE) + goto ret; + + switch(raster->type){ +#ifdef RW_OPENGL + case Raster::NORMAL: + case Raster::TEXTURE: + ret = rasterCreateTexture(raster); + break; + case Raster::CAMERATEXTURE: + ret = rasterCreateCameraTexture(raster); + break; + case Raster::ZBUFFER: + ret = rasterCreateZbuffer(raster); + break; + case Raster::CAMERA: + ret = rasterCreateCamera(raster); + break; +#endif + + default: + RWERROR((ERR_INVRASTER)); + return nil; + } + +ret: + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->originalStride = raster->stride; + raster->originalPixels = raster->pixels; + return ret; +} + +uint8* +rasterLock(Raster *raster, int32 level, int32 lockMode) +{ +#ifdef RW_OPENGL + Gl3Raster *natras GETGL3RASTEREXT(raster); + uint8 *px; + uint32 allocSz; + int i; + + assert(raster->privateFlags == 0); + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + case Raster::CAMERATEXTURE: + for(i = 0; i < level; i++){ + if(raster->width > 1){ + raster->width /= 2; + raster->stride /= 2; + } + if(raster->height > 1) + raster->height /= 2; + } + + allocSz = getLevelSize(raster, level); + px = (uint8*)rwMalloc(allocSz, MEMDUR_EVENT | ID_DRIVER); + assert(raster->pixels == nil); + raster->pixels = px; + + if(lockMode & Raster::LOCKREAD || !(lockMode & Raster::LOCKNOFETCH)){ + if(natras->isCompressed){ + if(natras->backingStore){ + assert(level < natras->backingStore->numlevels); + assert(allocSz >= natras->backingStore->levels[level].size); + memcpy(px, natras->backingStore->levels[level].data, allocSz); + }else{ + // GLES is losing here + uint32 prev = bindTexture(natras->texid); + glGetCompressedTexImage(GL_TEXTURE_2D, level, px); + bindTexture(prev); + } + }else if(gl3Caps.gles){ + GLuint fbo; + glGenFramebuffers(1, &fbo); + bindFramebuffer(fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, natras->texid, 0); + GLenum e = glCheckFramebufferStatus(GL_FRAMEBUFFER); +assert(natras->format == GL_RGBA); + glReadPixels(0, 0, raster->width, raster->height, natras->format, natras->type, px); +//e = glGetError(); printf("GL err4 %x (%x)\n", e, natras->format); + bindFramebuffer(0); + glDeleteFramebuffers(1, &fbo); + }else{ + uint32 prev = bindTexture(natras->texid); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glGetTexImage(GL_TEXTURE_2D, level, natras->format, natras->type, px); + bindTexture(prev); + } + } + + raster->privateFlags = lockMode; + break; + + case Raster::CAMERA: + if(lockMode & Raster::PRIVATELOCK_WRITE) + assert(0 && "can't lock framebuffer for writing"); + raster->width = glGlobals.presentWidth; + raster->height = glGlobals.presentHeight; + raster->stride = raster->width*natras->bpp; + assert(natras->bpp == 3); + allocSz = raster->height*raster->stride; + px = (uint8*)rwMalloc(allocSz, MEMDUR_EVENT | ID_DRIVER); + assert(raster->pixels == nil); + raster->pixels = px; + glReadBuffer(GL_BACK); + glReadPixels(0, 0, raster->width, raster->height, GL_RGB, GL_UNSIGNED_BYTE, px); + + raster->privateFlags = lockMode; + break; + + default: + assert(0 && "cannot lock this type of raster yet"); + return nil; + } + + return px; +#else + return nil; +#endif +} + +void +rasterUnlock(Raster *raster, int32 level) +{ +#ifdef RW_OPENGL + Gl3Raster *natras = GETGL3RASTEREXT(raster); + + assert(raster->pixels); + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + case Raster::CAMERATEXTURE: + if(raster->privateFlags & Raster::LOCKWRITE){ + uint32 prev = bindTexture(natras->texid); + if(natras->isCompressed){ + glCompressedTexImage2D(GL_TEXTURE_2D, level, natras->internalFormat, + raster->width, raster->height, 0, + getLevelSize(raster, level), + raster->pixels); + if(natras->backingStore){ + assert(level < natras->backingStore->numlevels); + memcpy(natras->backingStore->levels[level].data, raster->pixels, + natras->backingStore->levels[level].size); + } + }else{ + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, level, natras->internalFormat, + raster->width, raster->height, + 0, natras->format, natras->type, raster->pixels); + } + if(level == 0 && natras->autogenMipmap) + glGenerateMipmap(GL_TEXTURE_2D); + bindTexture(prev); + } + break; + + case Raster::CAMERA: + // TODO: write? + break; + } + + rwFree(raster->pixels); + raster->pixels = nil; +#endif + raster->width = raster->originalWidth; + raster->height = raster->originalHeight; + raster->stride = raster->originalStride; + raster->pixels = raster->originalPixels; + raster->privateFlags = 0; +} + +int32 +rasterNumLevels(Raster *raster) +{ + return GETGL3RASTEREXT(raster)->numLevels; +} + +// Almost the same as d3d9 and ps2 function +bool32 +imageFindRasterFormat(Image *img, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat) +{ + int32 width, height, depth, format; + + assert((type&0xF) == Raster::TEXTURE); + +// for(width = 1; width < img->width; width <<= 1); +// for(height = 1; height < img->height; height <<= 1); + // Perhaps non-power-of-2 textures are acceptable? + width = img->width; + height = img->height; + + depth = img->depth; + + if(depth <= 8) + depth = 32; + + switch(depth){ + case 32: + if(img->hasAlpha()) + format = Raster::C8888; + else{ + format = Raster::C888; + depth = 24; + } + break; + case 24: + format = Raster::C888; + break; + case 16: + format = Raster::C1555; + break; + + case 8: + case 4: + default: + RWERROR((ERR_INVRASTER)); + return 0; + } + + format |= type; + + *pWidth = width; + *pHeight = height; + *pDepth = depth; + *pFormat = format; + + return 1; +} + +bool32 +rasterFromImage(Raster *raster, Image *image) +{ + if((raster->type&0xF) != Raster::TEXTURE) + return 0; + + void (*conv)(uint8 *out, uint8 *in) = nil; + + // Unpalettize image if necessary but don't change original + Image *truecolimg = nil; + if(image->depth <= 8){ + truecolimg = Image::create(image->width, image->height, image->depth); + truecolimg->pixels = image->pixels; + truecolimg->stride = image->stride; + truecolimg->palette = image->palette; + truecolimg->unpalettize(); + image = truecolimg; + } + + Gl3Raster *natras = GETGL3RASTEREXT(raster); + int32 format = raster->format&0xF00; + assert(!natras->isCompressed); + switch(image->depth){ + case 32: + if(gl3Caps.gles) + conv = conv_RGBA8888_from_RGBA8888; + else if(format == Raster::C8888) + conv = conv_RGBA8888_from_RGBA8888; + else if(format == Raster::C888) + conv = conv_RGB888_from_RGB888; + else + goto err; + break; + case 24: + if(gl3Caps.gles) + conv = conv_RGBA8888_from_RGB888; + else if(format == Raster::C8888) + conv = conv_RGBA8888_from_RGB888; + else if(format == Raster::C888) + conv = conv_RGB888_from_RGB888; + else + goto err; + break; + case 16: + if(gl3Caps.gles) + conv = conv_RGBA8888_from_ARGB1555; + else if(format == Raster::C1555) + conv = conv_RGBA5551_from_ARGB1555; + else + goto err; + break; + + case 8: + case 4: + default: + err: + RWERROR((ERR_INVRASTER)); + return 0; + } + + natras->hasAlpha = image->hasAlpha(); + + bool unlock = false; + if(raster->pixels == nil){ + raster->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + unlock = true; + } + + uint8 *pixels = raster->pixels; + assert(pixels); + uint8 *imgpixels = image->pixels + (image->height-1)*image->stride; + + int x, y; + assert(image->width == raster->width); + assert(image->height == raster->height); + for(y = 0; y < image->height; y++){ + uint8 *imgrow = imgpixels; + uint8 *rasrow = pixels; + for(x = 0; x < image->width; x++){ + conv(rasrow, imgrow); + imgrow += image->bpp; + rasrow += natras->bpp; + } + imgpixels -= image->stride; + pixels += raster->stride; + } + if(unlock) + raster->unlock(0); + + if(truecolimg) + truecolimg->destroy(); + + return 1; +} + +Image* +rasterToImage(Raster *raster) +{ + int32 depth; + Image *image; + + bool unlock = false; + if(raster->pixels == nil){ + raster->lock(0, Raster::LOCKREAD); + unlock = true; + } + + Gl3Raster *natras = GETGL3RASTEREXT(raster); + if(natras->isCompressed){ + // TODO + RWERROR((ERR_INVRASTER)); + return nil; + } + + void (*conv)(uint8 *out, uint8 *in) = nil; + switch(raster->format & 0xF00){ + case Raster::C1555: + depth = 16; + conv = conv_ARGB1555_from_RGBA5551; + break; + case Raster::C8888: + depth = 32; + conv = conv_RGBA8888_from_RGBA8888; + break; + case Raster::C888: + depth = 24; + conv = conv_RGB888_from_RGB888; + break; + + default: + case Raster::C555: + case Raster::C565: + case Raster::C4444: + case Raster::LUM8: + RWERROR((ERR_INVRASTER)); + return nil; + } + + if(raster->format & Raster::PAL4 || + raster->format & Raster::PAL8){ + RWERROR((ERR_INVRASTER)); + return nil; + } + + uint8 *in, *out; + image = Image::create(raster->width, raster->height, depth); + image->allocate(); + + uint8 *imgpixels = image->pixels + (image->height-1)*image->stride; + uint8 *pixels = raster->pixels; + + int x, y; + assert(image->width == raster->width); + assert(image->height == raster->height); + for(y = 0; y < image->height; y++){ + uint8 *imgrow = imgpixels; + uint8 *rasrow = pixels; + for(x = 0; x < image->width; x++){ + conv(imgrow, rasrow); + imgrow += image->bpp; + rasrow += natras->bpp; + } + imgpixels -= image->stride; + pixels += raster->stride; + } + + if(unlock) + raster->unlock(0); + + return image; +} + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + Gl3Raster *ras = PLUGINOFFSET(Gl3Raster, object, offset); + ras->texid = 0; + ras->fbo = 0; + ras->fboMate = nil; + ras->backingStore = nil; + return object; +} + +void evictRaster(Raster *raster); + +static void* +destroyNativeRaster(void *object, int32 offset, int32) +{ + Raster *raster = (Raster*)object; + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, object, offset); +#ifdef RW_OPENGL + evictRaster(raster); + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + glDeleteTextures(1, &natras->texid); + break; + + case Raster::CAMERATEXTURE: + if(natras->fboMate){ + // Break apart from currently associated zbuffer + Gl3Raster *zras = GETGL3RASTEREXT(natras->fboMate); + zras->fboMate = nil; + natras->fboMate = nil; + } + glDeleteFramebuffers(1, &natras->fbo); + glDeleteTextures(1, &natras->texid); + break; + + case Raster::ZBUFFER: + if(natras->fboMate){ + // Detatch from FBO we may be attached to + Gl3Raster *oldfb = GETGL3RASTEREXT(natras->fboMate); + if(oldfb->fbo){ + bindFramebuffer(oldfb->fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } + oldfb->fboMate = nil; + } + if(gl3Caps.gles) + glDeleteRenderbuffers(1, &natras->texid); + else + glDeleteTextures(1, &natras->texid); + break; + + case Raster::CAMERA: + if(natras->fboMate){ + // Break apart from currently associated zbuffer + Gl3Raster *zras = GETGL3RASTEREXT(natras->fboMate); + zras->fboMate = nil; + natras->fboMate = nil; + } + break; + } + natras->texid = 0; + natras->fbo = 0; + + free(natras->backingStore); + +#endif + return object; +} + +static void* +copyNativeRaster(void *dst, void *, int32 offset, int32) +{ + Gl3Raster *d = PLUGINOFFSET(Gl3Raster, dst, offset); + d->texid = 0; + d->fbo = 0; + d->fboMate = nil; + d->backingStore = nil; + return dst; +} + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_GL3){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + // Texture + tex->filterAddressing = stream->readU32(); + stream->read8(tex->name, 32); + stream->read8(tex->mask, 32); + + // Raster + uint32 format = stream->readU32(); + int32 width = stream->readI32(); + int32 height = stream->readI32(); + int32 depth = stream->readI32(); + int32 numLevels = stream->readI32(); + + // Native raster + int32 subplatform = stream->readI32(); + int32 flags = stream->readI32(); + int32 compression = stream->readI32(); + + if(subplatform != gl3Caps.gles){ + tex->destroy(); + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + + Raster *raster; + Gl3Raster *natras; + if(flags & 2){ + if(!gl3Caps.dxtSupported){ + tex->destroy(); + RWERROR((ERR_FORMAT_UNSUPPORTED)); + return nil; + } + raster = Raster::create(width, height, depth, format | Raster::TEXTURE | Raster::DONTALLOCATE, PLATFORM_GL3); + allocateDXT(raster, compression, numLevels, flags & 1); + }else{ + raster = Raster::create(width, height, depth, format | Raster::TEXTURE, PLATFORM_GL3); + } + assert(raster); + natras = GETGL3RASTEREXT(raster); + tex->raster = raster; + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = stream->readU32(); + data = raster->lock(i, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + stream->read8(data, size); + raster->unlock(i); + } + return tex; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + Raster *raster = tex->raster; + Gl3Raster *natras = GETGL3RASTEREXT(raster); + + int32 chunksize = getSizeNativeTexture(tex); + writeChunkHeader(stream, ID_STRUCT, chunksize-12); + stream->writeU32(PLATFORM_GL3); + + // Texture + stream->writeU32(tex->filterAddressing); + stream->write8(tex->name, 32); + stream->write8(tex->mask, 32); + + // Raster + int32 numLevels = natras->numLevels; + stream->writeI32(raster->format); + stream->writeI32(raster->width); + stream->writeI32(raster->height); + stream->writeI32(raster->depth); + stream->writeI32(numLevels); + + // Native raster + int32 flags = 0; + int32 compression = 0; + if(natras->hasAlpha) + flags |= 1; + if(natras->isCompressed){ + flags |= 2; + switch(natras->internalFormat){ +#ifdef RW_OPENGL + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + compression = 1; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + compression = 3; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + compression = 5; + break; +#endif + default: + assert(0 && "unknown compression"); + } + } + stream->writeI32(gl3Caps.gles); + stream->writeI32(flags); + stream->writeI32(compression); + // TODO: auto mipmaps? + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = getLevelSize(raster, i); + stream->writeU32(size); + data = raster->lock(i, Raster::LOCKREAD); + stream->write8(data, size); + raster->unlock(i); + } +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 72 + 32; + int32 levels = tex->raster->getNumLevels(); + for(int32 i = 0; i < levels; i++) + size += 4 + getLevelSize(tex->raster, i); + return size; +} + + + +void registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(Gl3Raster), + ID_RASTERGL3, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); +} + +} +} diff --git a/vendor/librw/src/gl/gl3render.cpp b/vendor/librw/src/gl/gl3render.cpp new file mode 100644 index 0000000..4dd79ba --- /dev/null +++ b/vendor/librw/src/gl/gl3render.cpp @@ -0,0 +1,184 @@ +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#ifdef RW_OPENGL +#include "rwgl3.h" +#include "rwgl3shader.h" + +#include "rwgl3impl.h" + +namespace rw { +namespace gl3 { + +#define MAX_LIGHTS + +void +drawInst_simple(InstanceDataHeader *header, InstanceData *inst) +{ + flushCache(); + glDrawElements(header->primType, inst->numIndex, + GL_UNSIGNED_SHORT, (void*)(uintptr)inst->offset); +} + +// Emulate PS2 GS alpha test FB_ONLY case: failed alpha writes to frame- but not to depth buffer +void +drawInst_GSemu(InstanceDataHeader *header, InstanceData *inst) +{ + uint32 hasAlpha; + int alphafunc, alpharef, gsalpharef; + int zwrite; + hasAlpha = getAlphaBlend(); + if(hasAlpha){ + zwrite = rw::GetRenderState(rw::ZWRITEENABLE); + alphafunc = rw::GetRenderState(rw::ALPHATESTFUNC); + if(zwrite){ + alpharef = rw::GetRenderState(rw::ALPHATESTREF); + gsalpharef = rw::GetRenderState(rw::GSALPHATESTREF); + + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAGREATEREQUAL); + SetRenderState(rw::ALPHATESTREF, gsalpharef); + drawInst_simple(header, inst); + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHALESS); + SetRenderState(rw::ZWRITEENABLE, 0); + drawInst_simple(header, inst); + SetRenderState(rw::ZWRITEENABLE, 1); + SetRenderState(rw::ALPHATESTFUNC, alphafunc); + SetRenderState(rw::ALPHATESTREF, alpharef); + }else{ + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAALWAYS); + drawInst_simple(header, inst); + SetRenderState(rw::ALPHATESTFUNC, alphafunc); + } + }else + drawInst_simple(header, inst); +} + +void +drawInst(InstanceDataHeader *header, InstanceData *inst) +{ + if(rw::GetRenderState(rw::GSALPHATEST)) + drawInst_GSemu(header, inst); + else + drawInst_simple(header, inst); +} + + +void +setAttribPointers(AttribDesc *attribDescs, int32 numAttribs) +{ + AttribDesc *a; + for(a = attribDescs; a != &attribDescs[numAttribs]; a++){ + glEnableVertexAttribArray(a->index); + glVertexAttribPointer(a->index, a->size, a->type, a->normalized, + a->stride, (void*)(uint64)a->offset); + } +} + +void +disableAttribPointers(AttribDesc *attribDescs, int32 numAttribs) +{ + AttribDesc *a; + for(a = attribDescs; a != &attribDescs[numAttribs]; a++) + glDisableVertexAttribArray(a->index); +} + +void +setupVertexInput(InstanceDataHeader *header) +{ +#ifdef RW_GL_USE_VAOS + glBindVertexArray(header->vao); +#else + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); + glBindBuffer(GL_ARRAY_BUFFER, header->vbo); + setAttribPointers(header->attribDesc, header->numAttribs); +#endif +} + +void +teardownVertexInput(InstanceDataHeader *header) +{ +#ifndef RW_GL_USE_VAOS + disableAttribPointers(header->attribDesc, header->numAttribs); +#endif +} + +int32 +lightingCB(Atomic *atomic) +{ + WorldLights lightData; + Light *directionals[8]; + Light *locals[8]; + lightData.directionals = directionals; + lightData.numDirectionals = 8; + lightData.locals = locals; + lightData.numLocals = 8; + + if(atomic->geometry->flags & rw::Geometry::LIGHT){ + ((World*)engine->currentWorld)->enumerateLights(atomic, &lightData); + if((atomic->geometry->flags & rw::Geometry::NORMALS) == 0){ + // Get rid of lights that need normals when we don't have any + lightData.numDirectionals = 0; + lightData.numLocals = 0; + } + return setLights(&lightData); + }else{ + memset(&lightData, 0, sizeof(lightData)); + return setLights(&lightData); + } +} + +void +defaultRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + Material *m; + + uint32 flags = atomic->geometry->flags; + setWorldMatrix(atomic->getFrame()->getLTM()); + int32 vsBits = lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + int32 n = header->numMeshes; + + while(n--){ + m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + if((vsBits & VSLIGHT_MASK) == 0){ + if(getAlphaTest()) + defaultShader->use(); + else + defaultShader_noAT->use(); + }else{ + if(getAlphaTest()) + defaultShader_fullLight->use(); + else + defaultShader_fullLight_noAT->use(); + } + + drawInst(header, inst); + inst++; + } + teardownVertexInput(header); +} + + +} +} + +#endif + diff --git a/vendor/librw/src/gl/gl3shader.cpp b/vendor/librw/src/gl/gl3shader.cpp new file mode 100644 index 0000000..73c344e --- /dev/null +++ b/vendor/librw/src/gl/gl3shader.cpp @@ -0,0 +1,355 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#ifdef RW_OPENGL +#include "rwgl3.h" +#include "rwgl3shader.h" + +namespace rw { +namespace gl3 { + +#include "shaders/header_vs.inc" +#include "shaders/header_fs.inc" + +UniformRegistry uniformRegistry; +static char nameBuffer[(MAX_UNIFORMS + MAX_BLOCKS)*32]; // static because memory system isn't up yet when we register +static uint32 nameBufPtr; +static float uniformData[512*4]; // seems enough +static uint32 dataPtr; + +static int uniformTypesize[] = { + 0, 4, 4, 16 +}; + +static char* +shader_strdup(const char *name) +{ + size_t len = strlen(name)+1; + char *s = &nameBuffer[nameBufPtr]; + nameBufPtr += len; + assert(nameBufPtr <= nelem(nameBuffer)); + memcpy(s, name, len); + return s; +} + +int32 +registerUniform(const char *name, UniformType type, int32 num) +{ + int i; + i = findUniform(name); + if(i >= 0){ + Uniform *u = &uniformRegistry.uniforms[i]; + assert(u->type == type); + assert(u->num == num); + return i; + } + // TODO: print error + if(uniformRegistry.numUniforms+1 >= MAX_UNIFORMS){ + assert(0 && "no space for uniform"); + return -1; + } + Uniform *u = &uniformRegistry.uniforms[uniformRegistry.numUniforms]; + u->name = shader_strdup(name); + u->type = type; + u->serialNum = 0; + if(type == UNIFORM_NA){ + u->num = 0; + u->data = nil; + }else{ + u->num = num; + u->data = &uniformData[dataPtr]; + dataPtr += uniformTypesize[type]*num; + assert(dataPtr <= nelem(uniformData)); + } + + return uniformRegistry.numUniforms++; +} + +int32 +findUniform(const char *name) +{ + int i; + for(i = 0; i < uniformRegistry.numUniforms; i++) + if(strcmp(name, uniformRegistry.uniforms[i].name) == 0) + return i; + return -1; +} + +int32 +registerBlock(const char *name) +{ + int i; + i = findBlock(name); + if(i >= 0) return i; + // TODO: print error + if(uniformRegistry.numBlocks+1 >= MAX_BLOCKS) + return -1; + uniformRegistry.blockNames[uniformRegistry.numBlocks] = shader_strdup(name); + return uniformRegistry.numBlocks++; +} + +int32 +findBlock(const char *name) +{ + int i; + for(i = 0; i < uniformRegistry.numBlocks; i++) + if(strcmp(name, uniformRegistry.blockNames[i]) == 0) + return i; + return -1; +} + +void +setUniform(int32 id, void *data) +{ + Uniform *u = &uniformRegistry.uniforms[id]; + assert(u->type != UNIFORM_NA); + if(memcmp(u->data, data, uniformTypesize[u->type]*u->num * sizeof(float)) != 0){ + memcpy(u->data, data, uniformTypesize[u->type]*u->num * sizeof(float)); + //u->dirty = true; + u->serialNum++; + } +} + +void +flushUniforms(void) +{ + for(int i = 0; i < uniformRegistry.numUniforms; i++){ + // this is bad! + if(i >= currentShader->numUniforms){ + printf("trying to set uniform %d %s that doesn't exist!\n", i, uniformRegistry.uniforms[i].name); + continue; + } + + int32 loc = currentShader->uniformLocations[i]; + if(loc == -1) + continue; + + Uniform *u = &uniformRegistry.uniforms[i]; + if(currentShader->serialNums[i] != u->serialNum) + switch(u->type){ + case UNIFORM_NA: + break; + case UNIFORM_VEC4: + glUniform4fv(loc, u->num, (GLfloat*)u->data); + break; + case UNIFORM_IVEC4: + glUniform4iv(loc, u->num, (GLint*)u->data); + break; + case UNIFORM_MAT4: + glUniformMatrix4fv(loc, u->num, GL_FALSE, (GLfloat*)u->data); + break; + } + currentShader->serialNums[i] = u->serialNum; + } +} + +Shader *currentShader; + +static void +printShaderSource(const char **src) +{ + int f; + const char *file; + bool printline; + int line = 1; + for(f = 0; src[f]; f++){ + int fileline = 1; + char c; + file = src[f]; + printline = true; + while(c = *file++, c != '\0'){ + if(printline) + printf("%.4d/%d:%.4d: ", line++, f, fileline++); + putchar(c); + printline = c == '\n'; + } + putchar('\n'); + } +} + +static int +compileshader(GLenum type, const char **src, GLuint *shader) +{ + GLint n; + GLint shdr, success; + GLint len; + char *log; + + for(n = 0; src[n]; n++); + + shdr = glCreateShader(type); + glShaderSource(shdr, n, src, nil); + glCompileShader(shdr); + glGetShaderiv(shdr, GL_COMPILE_STATUS, &success); + if(!success){ + printShaderSource(src); + fprintf(stderr, "Error in %s shader\n", + type == GL_VERTEX_SHADER ? "vertex" : "fragment"); + glGetShaderiv(shdr, GL_INFO_LOG_LENGTH, &len); + log = (char*)rwMalloc(len, MEMDUR_FUNCTION); + glGetShaderInfoLog(shdr, len, nil, log); + fprintf(stderr, "%s\n", log); + rwFree(log); + return 1; + } + *shader = shdr; + return 0; +} + +static int +linkprogram(GLint vs, GLint fs, GLuint *program) +{ + GLint prog, success; + GLint len; + char *log; + + prog = glCreateProgram(); + + if(gl3Caps.glversion < 30){ + // TODO: perhaps just do this always and get rid of the layout stuff? + glBindAttribLocation(prog, ATTRIB_POS, "in_pos"); + glBindAttribLocation(prog, ATTRIB_NORMAL, "in_normal"); + glBindAttribLocation(prog, ATTRIB_COLOR, "in_color"); + glBindAttribLocation(prog, ATTRIB_WEIGHTS, "in_weights"); + glBindAttribLocation(prog, ATTRIB_INDICES, "in_indices"); + glBindAttribLocation(prog, ATTRIB_TEXCOORDS0, "in_tex0"); + glBindAttribLocation(prog, ATTRIB_TEXCOORDS1, "in_tex1"); + } + + glAttachShader(prog, vs); + glAttachShader(prog, fs); + glLinkProgram(prog); + glGetProgramiv(prog, GL_LINK_STATUS, &success); + if(!success){ + fprintf(stderr, "Error in program\n"); + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len); + log = (char*)rwMalloc(len, MEMDUR_FUNCTION); + glGetProgramInfoLog(prog, len, nil, log); + fprintf(stderr, "%s\n", log); + rwFree(log); + return 1; + } + *program = prog; + return 0; +} + +Shader* +Shader::create(const char **vsrc, const char **fsrc) +{ + GLuint vs, fs, program; + int i; + int fail; + + fail = compileshader(GL_VERTEX_SHADER, vsrc, &vs); + if(fail) + return nil; + + fail = compileshader(GL_FRAGMENT_SHADER, fsrc, &fs); + if(fail){ + glDeleteShader(vs); + return nil; + } + + fail = linkprogram(vs, fs, &program); + + glDeleteShader(vs); + glDeleteShader(fs); + if(fail){ + return nil; + } + + Shader *sh = rwNewT(Shader, 1, MEMDUR_EVENT | ID_DRIVER); // or global? + +#ifdef xxxRW_GLES2 + int numUniforms; + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numUniforms); + for(i = 0; i < numUniforms; i++){ + GLint size; + GLenum type; + char name[100]; + glGetActiveUniform(program, i, 100, nil, &size, &type, name); + printf("%d %d %s\n", size, type, name); + } + printf("\n"); +#endif + +#ifdef xxxRW_GLES2 + int numAttribs; + glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &numAttribs); + for(i = 0; i < numAttribs; i++){ + GLint size; + GLenum type; + char name[100]; + glGetActiveAttrib(program, i, 100, nil, &size, &type, name); + GLint bind = glGetAttribLocation(program, name); + printf("%d %d %s. %d\n", size, type, name, bind); + } + printf("\n"); +#endif + + // set uniform block binding + for(i = 0; i < uniformRegistry.numBlocks; i++){ + int idx = glGetUniformBlockIndex(program, + uniformRegistry.blockNames[i]); + if(idx >= 0) + glUniformBlockBinding(program, idx, i); + } + + // query uniform locations + sh->program = program; + sh->numUniforms = uniformRegistry.numUniforms; + sh->uniformLocations = rwNewT(GLint, uniformRegistry.numUniforms, MEMDUR_EVENT | ID_DRIVER); + sh->serialNums = rwNewT(uint32, uniformRegistry.numUniforms, MEMDUR_EVENT | ID_DRIVER); + for(i = 0; i < uniformRegistry.numUniforms; i++){ + sh->uniformLocations[i] = glGetUniformLocation(program, + uniformRegistry.uniforms[i].name); + sh->serialNums[i] = ~0; // let's hope this means dirty + } + + // set samplers + glUseProgram(program); + char name[64]; + GLint loc; + for(i = 0; i < 4; i++){ + sprintf(name, "tex%d", i); + loc = glGetUniformLocation(program, name); + glUniform1i(loc, i); + } + + // reset program + if(currentShader) + glUseProgram(currentShader->program); + + return sh; +} + +void +Shader::use(void) +{ + if(currentShader != this){ + glUseProgram(this->program); + currentShader = this; + } +} + +void +Shader::destroy(void) +{ + glDeleteProgram(this->program); + rwFree(this->uniformLocations); + rwFree(this->serialNums); + rwFree(this); +} + +} +} + +#endif diff --git a/vendor/librw/src/gl/gl3skin.cpp b/vendor/librw/src/gl/gl3skin.cpp new file mode 100644 index 0000000..6b69bbd --- /dev/null +++ b/vendor/librw/src/gl/gl3skin.cpp @@ -0,0 +1,366 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwplugins.h" + +#include "rwgl3.h" +#include "rwgl3shader.h" +#include "rwgl3plg.h" + +#include "rwgl3impl.h" + +namespace rw { +namespace gl3 { + +#ifdef RW_OPENGL + +static Shader *skinShader, *skinShader_noAT; +static Shader *skinShader_fullLight, *skinShader_fullLight_noAT; +static int32 u_boneMatrices; + +void +skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) +{ + AttribDesc *attribs, *a; + + bool isPrelit = !!(geo->flags & Geometry::PRELIT); + bool hasNormals = !!(geo->flags & Geometry::NORMALS); + + if(!reinstance){ + AttribDesc tmpAttribs[14]; + uint32 stride; + + // + // Create attribute descriptions + // + a = tmpAttribs; + stride = 0; + + // Positions + a->index = ATTRIB_POS; + a->size = 3; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 12; + a++; + + // Normals + // TODO: compress + if(hasNormals){ + a->index = ATTRIB_NORMAL; + a->size = 3; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 12; + a++; + } + + // Prelighting + if(isPrelit){ + a->index = ATTRIB_COLOR; + a->size = 4; + a->type = GL_UNSIGNED_BYTE; + a->normalized = GL_TRUE; + a->offset = stride; + stride += 4; + a++; + } + + // Texture coordinates + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + a->index = ATTRIB_TEXCOORDS0+n; + a->size = 2; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 8; + a++; + } + + // Weights + a->index = ATTRIB_WEIGHTS; + a->size = 4; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 16; + a++; + + // Indices + a->index = ATTRIB_INDICES; + a->size = 4; + a->type = GL_UNSIGNED_BYTE; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 4; + a++; + + header->numAttribs = a - tmpAttribs; + for(a = tmpAttribs; a != &tmpAttribs[header->numAttribs]; a++) + a->stride = stride; + header->attribDesc = rwNewT(AttribDesc, header->numAttribs, MEMDUR_EVENT | ID_GEOMETRY); + memcpy(header->attribDesc, tmpAttribs, + header->numAttribs*sizeof(AttribDesc)); + + // + // Allocate vertex buffer + // + header->vertexBuffer = rwNewT(uint8, header->totalNumVertex*stride, MEMDUR_EVENT | ID_GEOMETRY); + assert(header->vbo == 0); + glGenBuffers(1, &header->vbo); + } + + Skin *skin = Skin::get(geo); + attribs = header->attribDesc; + + // + // Fill vertex buffer + // + + uint8 *verts = header->vertexBuffer; + + // Positions + if(!reinstance || geo->lockedSinceInst&Geometry::LOCKVERTICES){ + for(a = attribs; a->index != ATTRIB_POS; a++) + ; + instV3d(VERT_FLOAT3, verts + a->offset, + geo->morphTargets[0].vertices, + header->totalNumVertex, a->stride); + } + + // Normals + if(hasNormals && (!reinstance || geo->lockedSinceInst&Geometry::LOCKNORMALS)){ + for(a = attribs; a->index != ATTRIB_NORMAL; a++) + ; + instV3d(VERT_FLOAT3, verts + a->offset, + geo->morphTargets[0].normals, + header->totalNumVertex, a->stride); + } + + // Prelighting + if(isPrelit && (!reinstance || geo->lockedSinceInst&Geometry::LOCKPRELIGHT)){ + for(a = attribs; a->index != ATTRIB_COLOR; a++) + ; + instColor(VERT_RGBA, verts + a->offset, + geo->colors, + header->totalNumVertex, a->stride); + } + + // Texture coordinates + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + if(!reinstance || geo->lockedSinceInst&(Geometry::LOCKTEXCOORDS<index != ATTRIB_TEXCOORDS0+n; a++) + ; + instTexCoords(VERT_FLOAT2, verts + a->offset, + geo->texCoords[n], + header->totalNumVertex, a->stride); + } + } + + // Weights + if(!reinstance){ + for(a = attribs; a->index != ATTRIB_WEIGHTS; a++) + ; + float *w = skin->weights; + instV4d(VERT_FLOAT4, verts + a->offset, + (V4d*)w, + header->totalNumVertex, a->stride); + } + + // Indices + if(!reinstance){ + for(a = attribs; a->index != ATTRIB_INDICES; a++) + ; + // not really colors of course but what the heck + instColor(VERT_RGBA, verts + a->offset, + (RGBA*)skin->indices, + header->totalNumVertex, a->stride); + } + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(header->vao); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); +#endif + glBindBuffer(GL_ARRAY_BUFFER, header->vbo); + glBufferData(GL_ARRAY_BUFFER, header->totalNumVertex*attribs[0].stride, + header->vertexBuffer, GL_STATIC_DRAW); +#ifdef RW_GL_USE_VAOS + setAttribPointers(header->attribDesc, header->numAttribs); + glBindVertexArray(0); +#endif +} + +void +skinUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + assert(0 && "can't uninstance"); +} + +static float skinMatrices[64*16]; + +void +uploadSkinMatrices(Atomic *a) +{ + int i; + Skin *skin = Skin::get(a->geometry); + Matrix *m = (Matrix*)skinMatrices; + HAnimHierarchy *hier = Skin::getHierarchy(a); + + if(hier){ + Matrix *invMats = (Matrix*)skin->inverseMatrices; + Matrix tmp; + + assert(skin->numBones == hier->numNodes); + if(hier->flags & HAnimHierarchy::LOCALSPACEMATRICES){ + for(i = 0; i < hier->numNodes; i++){ + invMats[i].flags = 0; + Matrix::mult(m, &invMats[i], &hier->matrices[i]); + m++; + } + }else{ + Matrix invAtmMat; + Matrix::invert(&invAtmMat, a->getFrame()->getLTM()); + for(i = 0; i < hier->numNodes; i++){ + invMats[i].flags = 0; + Matrix::mult(&tmp, &hier->matrices[i], &invAtmMat); + Matrix::mult(m, &invMats[i], &tmp); + m++; + } + } + }else{ + for(i = 0; i < skin->numBones; i++){ + m->setIdentity(); + m++; + } + } + setUniform(u_boneMatrices, skinMatrices); +} + +void +skinRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + Material *m; + + uint32 flags = atomic->geometry->flags; + setWorldMatrix(atomic->getFrame()->getLTM()); + int32 vsBits = lightingCB(atomic); + + setupVertexInput(header); + + InstanceData *inst = header->inst; + int32 n = header->numMeshes; + + uploadSkinMatrices(atomic); + + while(n--){ + m = inst->material; + + setMaterial(flags, m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + if((vsBits & VSLIGHT_MASK) == 0){ + if(getAlphaTest()) + skinShader->use(); + else + skinShader_noAT->use(); + }else{ + if(getAlphaTest()) + skinShader_fullLight->use(); + else + skinShader_fullLight_noAT->use(); + } + + drawInst(header, inst); + inst++; + } + teardownVertexInput(header); +} + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_GL3] = makeSkinPipeline(); + +#include "shaders/simple_fs_gl.inc" +#include "shaders/skin_gl.inc" + const char *vs[] = { shaderDecl, header_vert_src, skin_vert_src, nil }; + const char *vs_fullLight[] = { shaderDecl, "#define DIRECTIONALS\n#define POINTLIGHTS\n#define SPOTLIGHTS\n", header_vert_src, skin_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + const char *fs_noAT[] = { shaderDecl, "#define NO_ALPHATEST\n", header_frag_src, simple_frag_src, nil }; + + skinShader = Shader::create(vs, fs); + assert(skinShader); + skinShader_noAT = Shader::create(vs, fs_noAT); + assert(skinShader_noAT); + + skinShader_fullLight = Shader::create(vs_fullLight, fs); + assert(skinShader_fullLight); + skinShader_fullLight_noAT = Shader::create(vs_fullLight, fs_noAT); + assert(skinShader_fullLight_noAT); + + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_GL3])->destroy(); + skinGlobals.pipelines[PLATFORM_GL3] = nil; + + skinShader->destroy(); + skinShader = nil; + skinShader_noAT->destroy(); + skinShader_noAT = nil; + skinShader_fullLight->destroy(); + skinShader_fullLight = nil; + skinShader_fullLight_noAT->destroy(); + skinShader_fullLight_noAT = nil; + + return o; +} + +void +initSkin(void) +{ + u_boneMatrices = registerUniform("u_boneMatrices", UNIFORM_MAT4, 64); + + Driver::registerPlugin(PLATFORM_GL3, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = skinUninstanceCB; + pipe->renderCB = skinRenderCB; + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + return pipe; +} + +#else + +void initSkin(void) { } + +#endif + +} +} + diff --git a/vendor/librw/src/gl/glad/glad.c b/vendor/librw/src/gl/glad/glad.c new file mode 100644 index 0000000..05161d1 --- /dev/null +++ b/vendor/librw/src/gl/glad/glad.c @@ -0,0 +1,1590 @@ +/* + + OpenGL, OpenGL ES loader generated by glad 0.1.34 on Wed Feb 17 15:35:25 2021. + + Language/Generator: C/C++ + Specification: gl + APIs: gl=3.3, gles2=3.1 + Profile: core + Extensions: + GL_EXT_framebuffer_object, + GL_EXT_texture_compression_s3tc, + GL_EXT_texture_filter_anisotropic, + GL_KHR_debug, + GL_KHR_texture_compression_astc_ldr + Loader: False + Local files: False + Omit khrplatform: False + Reproducible: False + + Commandline: + --profile="core" --api="gl=3.3,gles2=3.1" --generator="c" --spec="gl" --no-loader --extensions="GL_EXT_framebuffer_object,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic,GL_KHR_debug,GL_KHR_texture_compression_astc_ldr" + Online: + https://glad.dav1d.de/#profile=core&language=c&specification=gl&api=gl%3D3.3&api=gles2%3D3.1&extensions=GL_EXT_framebuffer_object&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_EXT_texture_filter_anisotropic&extensions=GL_KHR_debug&extensions=GL_KHR_texture_compression_astc_ldr +*/ + +#include +#include +#include +#include "glad.h" + +struct gladGLversionStruct GLVersion = { 0, 0 }; + +static int requested_major_version; +static int requested_minor_version; + +#if defined(GL_ES_VERSION_3_0) || defined(GL_VERSION_3_0) +#define _GLAD_IS_SOME_NEW_VERSION 1 +#endif + +static int max_loaded_major; +static int max_loaded_minor; + +static const char *exts = NULL; +static int num_exts_i = 0; +static char **exts_i = NULL; + +static int get_exts(void) { +#ifdef _GLAD_IS_SOME_NEW_VERSION + if(max_loaded_major < 3) { +#endif + exts = (const char *)glGetString(GL_EXTENSIONS); +#ifdef _GLAD_IS_SOME_NEW_VERSION + } else { + unsigned int index; + + num_exts_i = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts_i); + if (num_exts_i > 0) { + exts_i = (char **)malloc((size_t)num_exts_i * (sizeof *exts_i)); + } + + if (exts_i == NULL) { + return 0; + } + + for(index = 0; index < (unsigned)num_exts_i; index++) { + const char *gl_str_tmp = (const char*)glGetStringi(GL_EXTENSIONS, index); + size_t len = strlen(gl_str_tmp); + + char *local_str = (char*)malloc((len+1) * sizeof(char)); + if(local_str != NULL) { + memcpy(local_str, gl_str_tmp, (len+1) * sizeof(char)); + } + exts_i[index] = local_str; + } + } +#endif + return 1; +} + +static void free_exts(void) { + if (exts_i != NULL) { + int index; + for(index = 0; index < num_exts_i; index++) { + free((char *)exts_i[index]); + } + free((void *)exts_i); + exts_i = NULL; + } +} + +static int has_ext(const char *ext) { +#ifdef _GLAD_IS_SOME_NEW_VERSION + if(max_loaded_major < 3) { +#endif + const char *extensions; + const char *loc; + const char *terminator; + extensions = exts; + if(extensions == NULL || ext == NULL) { + return 0; + } + + while(1) { + loc = strstr(extensions, ext); + if(loc == NULL) { + return 0; + } + + terminator = loc + strlen(ext); + if((loc == extensions || *(loc - 1) == ' ') && + (*terminator == ' ' || *terminator == '\0')) { + return 1; + } + extensions = terminator; + } +#ifdef _GLAD_IS_SOME_NEW_VERSION + } else { + int index; + if(exts_i == NULL) return 0; + for(index = 0; index < num_exts_i; index++) { + const char *e = exts_i[index]; + + if(exts_i[index] != NULL && strcmp(e, ext) == 0) { + return 1; + } + } + } +#endif + + return 0; +} +int GLAD_GL_VERSION_1_0 = 0; +int GLAD_GL_VERSION_1_1 = 0; +int GLAD_GL_VERSION_1_2 = 0; +int GLAD_GL_VERSION_1_3 = 0; +int GLAD_GL_VERSION_1_4 = 0; +int GLAD_GL_VERSION_1_5 = 0; +int GLAD_GL_VERSION_2_0 = 0; +int GLAD_GL_VERSION_2_1 = 0; +int GLAD_GL_VERSION_3_0 = 0; +int GLAD_GL_VERSION_3_1 = 0; +int GLAD_GL_VERSION_3_2 = 0; +int GLAD_GL_VERSION_3_3 = 0; +int GLAD_GL_ES_VERSION_2_0 = 0; +int GLAD_GL_ES_VERSION_3_0 = 0; +int GLAD_GL_ES_VERSION_3_1 = 0; +PFNGLACTIVESHADERPROGRAMPROC glad_glActiveShaderProgram = NULL; +PFNGLACTIVETEXTUREPROC glad_glActiveTexture = NULL; +PFNGLATTACHSHADERPROC glad_glAttachShader = NULL; +PFNGLBEGINCONDITIONALRENDERPROC glad_glBeginConditionalRender = NULL; +PFNGLBEGINQUERYPROC glad_glBeginQuery = NULL; +PFNGLBEGINTRANSFORMFEEDBACKPROC glad_glBeginTransformFeedback = NULL; +PFNGLBINDATTRIBLOCATIONPROC glad_glBindAttribLocation = NULL; +PFNGLBINDBUFFERPROC glad_glBindBuffer = NULL; +PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase = NULL; +PFNGLBINDBUFFERRANGEPROC glad_glBindBufferRange = NULL; +PFNGLBINDFRAGDATALOCATIONPROC glad_glBindFragDataLocation = NULL; +PFNGLBINDFRAGDATALOCATIONINDEXEDPROC glad_glBindFragDataLocationIndexed = NULL; +PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer = NULL; +PFNGLBINDIMAGETEXTUREPROC glad_glBindImageTexture = NULL; +PFNGLBINDPROGRAMPIPELINEPROC glad_glBindProgramPipeline = NULL; +PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer = NULL; +PFNGLBINDSAMPLERPROC glad_glBindSampler = NULL; +PFNGLBINDTEXTUREPROC glad_glBindTexture = NULL; +PFNGLBINDTRANSFORMFEEDBACKPROC glad_glBindTransformFeedback = NULL; +PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray = NULL; +PFNGLBINDVERTEXBUFFERPROC glad_glBindVertexBuffer = NULL; +PFNGLBLENDCOLORPROC glad_glBlendColor = NULL; +PFNGLBLENDEQUATIONPROC glad_glBlendEquation = NULL; +PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate = NULL; +PFNGLBLENDFUNCPROC glad_glBlendFunc = NULL; +PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL; +PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer = NULL; +PFNGLBUFFERDATAPROC glad_glBufferData = NULL; +PFNGLBUFFERSUBDATAPROC glad_glBufferSubData = NULL; +PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus = NULL; +PFNGLCLAMPCOLORPROC glad_glClampColor = NULL; +PFNGLCLEARPROC glad_glClear = NULL; +PFNGLCLEARBUFFERFIPROC glad_glClearBufferfi = NULL; +PFNGLCLEARBUFFERFVPROC glad_glClearBufferfv = NULL; +PFNGLCLEARBUFFERIVPROC glad_glClearBufferiv = NULL; +PFNGLCLEARBUFFERUIVPROC glad_glClearBufferuiv = NULL; +PFNGLCLEARCOLORPROC glad_glClearColor = NULL; +PFNGLCLEARDEPTHPROC glad_glClearDepth = NULL; +PFNGLCLEARDEPTHFPROC glad_glClearDepthf = NULL; +PFNGLCLEARSTENCILPROC glad_glClearStencil = NULL; +PFNGLCLIENTWAITSYNCPROC glad_glClientWaitSync = NULL; +PFNGLCOLORMASKPROC glad_glColorMask = NULL; +PFNGLCOLORMASKIPROC glad_glColorMaski = NULL; +PFNGLCOLORP3UIPROC glad_glColorP3ui = NULL; +PFNGLCOLORP3UIVPROC glad_glColorP3uiv = NULL; +PFNGLCOLORP4UIPROC glad_glColorP4ui = NULL; +PFNGLCOLORP4UIVPROC glad_glColorP4uiv = NULL; +PFNGLCOMPILESHADERPROC glad_glCompileShader = NULL; +PFNGLCOMPRESSEDTEXIMAGE1DPROC glad_glCompressedTexImage1D = NULL; +PFNGLCOMPRESSEDTEXIMAGE2DPROC glad_glCompressedTexImage2D = NULL; +PFNGLCOMPRESSEDTEXIMAGE3DPROC glad_glCompressedTexImage3D = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_glCompressedTexSubImage1D = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_glCompressedTexSubImage2D = NULL; +PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_glCompressedTexSubImage3D = NULL; +PFNGLCOPYBUFFERSUBDATAPROC glad_glCopyBufferSubData = NULL; +PFNGLCOPYTEXIMAGE1DPROC glad_glCopyTexImage1D = NULL; +PFNGLCOPYTEXIMAGE2DPROC glad_glCopyTexImage2D = NULL; +PFNGLCOPYTEXSUBIMAGE1DPROC glad_glCopyTexSubImage1D = NULL; +PFNGLCOPYTEXSUBIMAGE2DPROC glad_glCopyTexSubImage2D = NULL; +PFNGLCOPYTEXSUBIMAGE3DPROC glad_glCopyTexSubImage3D = NULL; +PFNGLCREATEPROGRAMPROC glad_glCreateProgram = NULL; +PFNGLCREATESHADERPROC glad_glCreateShader = NULL; +PFNGLCREATESHADERPROGRAMVPROC glad_glCreateShaderProgramv = NULL; +PFNGLCULLFACEPROC glad_glCullFace = NULL; +PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers = NULL; +PFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers = NULL; +PFNGLDELETEPROGRAMPROC glad_glDeleteProgram = NULL; +PFNGLDELETEPROGRAMPIPELINESPROC glad_glDeleteProgramPipelines = NULL; +PFNGLDELETEQUERIESPROC glad_glDeleteQueries = NULL; +PFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers = NULL; +PFNGLDELETESAMPLERSPROC glad_glDeleteSamplers = NULL; +PFNGLDELETESHADERPROC glad_glDeleteShader = NULL; +PFNGLDELETESYNCPROC glad_glDeleteSync = NULL; +PFNGLDELETETEXTURESPROC glad_glDeleteTextures = NULL; +PFNGLDELETETRANSFORMFEEDBACKSPROC glad_glDeleteTransformFeedbacks = NULL; +PFNGLDELETEVERTEXARRAYSPROC glad_glDeleteVertexArrays = NULL; +PFNGLDEPTHFUNCPROC glad_glDepthFunc = NULL; +PFNGLDEPTHMASKPROC glad_glDepthMask = NULL; +PFNGLDEPTHRANGEPROC glad_glDepthRange = NULL; +PFNGLDEPTHRANGEFPROC glad_glDepthRangef = NULL; +PFNGLDETACHSHADERPROC glad_glDetachShader = NULL; +PFNGLDISABLEPROC glad_glDisable = NULL; +PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray = NULL; +PFNGLDISABLEIPROC glad_glDisablei = NULL; +PFNGLDISPATCHCOMPUTEPROC glad_glDispatchCompute = NULL; +PFNGLDISPATCHCOMPUTEINDIRECTPROC glad_glDispatchComputeIndirect = NULL; +PFNGLDRAWARRAYSPROC glad_glDrawArrays = NULL; +PFNGLDRAWARRAYSINDIRECTPROC glad_glDrawArraysIndirect = NULL; +PFNGLDRAWARRAYSINSTANCEDPROC glad_glDrawArraysInstanced = NULL; +PFNGLDRAWBUFFERPROC glad_glDrawBuffer = NULL; +PFNGLDRAWBUFFERSPROC glad_glDrawBuffers = NULL; +PFNGLDRAWELEMENTSPROC glad_glDrawElements = NULL; +PFNGLDRAWELEMENTSBASEVERTEXPROC glad_glDrawElementsBaseVertex = NULL; +PFNGLDRAWELEMENTSINDIRECTPROC glad_glDrawElementsIndirect = NULL; +PFNGLDRAWELEMENTSINSTANCEDPROC glad_glDrawElementsInstanced = NULL; +PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC glad_glDrawElementsInstancedBaseVertex = NULL; +PFNGLDRAWRANGEELEMENTSPROC glad_glDrawRangeElements = NULL; +PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC glad_glDrawRangeElementsBaseVertex = NULL; +PFNGLENABLEPROC glad_glEnable = NULL; +PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray = NULL; +PFNGLENABLEIPROC glad_glEnablei = NULL; +PFNGLENDCONDITIONALRENDERPROC glad_glEndConditionalRender = NULL; +PFNGLENDQUERYPROC glad_glEndQuery = NULL; +PFNGLENDTRANSFORMFEEDBACKPROC glad_glEndTransformFeedback = NULL; +PFNGLFENCESYNCPROC glad_glFenceSync = NULL; +PFNGLFINISHPROC glad_glFinish = NULL; +PFNGLFLUSHPROC glad_glFlush = NULL; +PFNGLFLUSHMAPPEDBUFFERRANGEPROC glad_glFlushMappedBufferRange = NULL; +PFNGLFRAMEBUFFERPARAMETERIPROC glad_glFramebufferParameteri = NULL; +PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer = NULL; +PFNGLFRAMEBUFFERTEXTUREPROC glad_glFramebufferTexture = NULL; +PFNGLFRAMEBUFFERTEXTURE1DPROC glad_glFramebufferTexture1D = NULL; +PFNGLFRAMEBUFFERTEXTURE2DPROC glad_glFramebufferTexture2D = NULL; +PFNGLFRAMEBUFFERTEXTURE3DPROC glad_glFramebufferTexture3D = NULL; +PFNGLFRAMEBUFFERTEXTURELAYERPROC glad_glFramebufferTextureLayer = NULL; +PFNGLFRONTFACEPROC glad_glFrontFace = NULL; +PFNGLGENBUFFERSPROC glad_glGenBuffers = NULL; +PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers = NULL; +PFNGLGENPROGRAMPIPELINESPROC glad_glGenProgramPipelines = NULL; +PFNGLGENQUERIESPROC glad_glGenQueries = NULL; +PFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers = NULL; +PFNGLGENSAMPLERSPROC glad_glGenSamplers = NULL; +PFNGLGENTEXTURESPROC glad_glGenTextures = NULL; +PFNGLGENTRANSFORMFEEDBACKSPROC glad_glGenTransformFeedbacks = NULL; +PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays = NULL; +PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap = NULL; +PFNGLGETACTIVEATTRIBPROC glad_glGetActiveAttrib = NULL; +PFNGLGETACTIVEUNIFORMPROC glad_glGetActiveUniform = NULL; +PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC glad_glGetActiveUniformBlockName = NULL; +PFNGLGETACTIVEUNIFORMBLOCKIVPROC glad_glGetActiveUniformBlockiv = NULL; +PFNGLGETACTIVEUNIFORMNAMEPROC glad_glGetActiveUniformName = NULL; +PFNGLGETACTIVEUNIFORMSIVPROC glad_glGetActiveUniformsiv = NULL; +PFNGLGETATTACHEDSHADERSPROC glad_glGetAttachedShaders = NULL; +PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation = NULL; +PFNGLGETBOOLEANI_VPROC glad_glGetBooleani_v = NULL; +PFNGLGETBOOLEANVPROC glad_glGetBooleanv = NULL; +PFNGLGETBUFFERPARAMETERI64VPROC glad_glGetBufferParameteri64v = NULL; +PFNGLGETBUFFERPARAMETERIVPROC glad_glGetBufferParameteriv = NULL; +PFNGLGETBUFFERPOINTERVPROC glad_glGetBufferPointerv = NULL; +PFNGLGETBUFFERSUBDATAPROC glad_glGetBufferSubData = NULL; +PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage = NULL; +PFNGLGETDOUBLEVPROC glad_glGetDoublev = NULL; +PFNGLGETERRORPROC glad_glGetError = NULL; +PFNGLGETFLOATVPROC glad_glGetFloatv = NULL; +PFNGLGETFRAGDATAINDEXPROC glad_glGetFragDataIndex = NULL; +PFNGLGETFRAGDATALOCATIONPROC glad_glGetFragDataLocation = NULL; +PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_glGetFramebufferAttachmentParameteriv = NULL; +PFNGLGETFRAMEBUFFERPARAMETERIVPROC glad_glGetFramebufferParameteriv = NULL; +PFNGLGETINTEGER64I_VPROC glad_glGetInteger64i_v = NULL; +PFNGLGETINTEGER64VPROC glad_glGetInteger64v = NULL; +PFNGLGETINTEGERI_VPROC glad_glGetIntegeri_v = NULL; +PFNGLGETINTEGERVPROC glad_glGetIntegerv = NULL; +PFNGLGETINTERNALFORMATIVPROC glad_glGetInternalformativ = NULL; +PFNGLGETMULTISAMPLEFVPROC glad_glGetMultisamplefv = NULL; +PFNGLGETPROGRAMBINARYPROC glad_glGetProgramBinary = NULL; +PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog = NULL; +PFNGLGETPROGRAMINTERFACEIVPROC glad_glGetProgramInterfaceiv = NULL; +PFNGLGETPROGRAMPIPELINEINFOLOGPROC glad_glGetProgramPipelineInfoLog = NULL; +PFNGLGETPROGRAMPIPELINEIVPROC glad_glGetProgramPipelineiv = NULL; +PFNGLGETPROGRAMRESOURCEINDEXPROC glad_glGetProgramResourceIndex = NULL; +PFNGLGETPROGRAMRESOURCELOCATIONPROC glad_glGetProgramResourceLocation = NULL; +PFNGLGETPROGRAMRESOURCENAMEPROC glad_glGetProgramResourceName = NULL; +PFNGLGETPROGRAMRESOURCEIVPROC glad_glGetProgramResourceiv = NULL; +PFNGLGETPROGRAMIVPROC glad_glGetProgramiv = NULL; +PFNGLGETQUERYOBJECTI64VPROC glad_glGetQueryObjecti64v = NULL; +PFNGLGETQUERYOBJECTIVPROC glad_glGetQueryObjectiv = NULL; +PFNGLGETQUERYOBJECTUI64VPROC glad_glGetQueryObjectui64v = NULL; +PFNGLGETQUERYOBJECTUIVPROC glad_glGetQueryObjectuiv = NULL; +PFNGLGETQUERYIVPROC glad_glGetQueryiv = NULL; +PFNGLGETRENDERBUFFERPARAMETERIVPROC glad_glGetRenderbufferParameteriv = NULL; +PFNGLGETSAMPLERPARAMETERIIVPROC glad_glGetSamplerParameterIiv = NULL; +PFNGLGETSAMPLERPARAMETERIUIVPROC glad_glGetSamplerParameterIuiv = NULL; +PFNGLGETSAMPLERPARAMETERFVPROC glad_glGetSamplerParameterfv = NULL; +PFNGLGETSAMPLERPARAMETERIVPROC glad_glGetSamplerParameteriv = NULL; +PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog = NULL; +PFNGLGETSHADERPRECISIONFORMATPROC glad_glGetShaderPrecisionFormat = NULL; +PFNGLGETSHADERSOURCEPROC glad_glGetShaderSource = NULL; +PFNGLGETSHADERIVPROC glad_glGetShaderiv = NULL; +PFNGLGETSTRINGPROC glad_glGetString = NULL; +PFNGLGETSTRINGIPROC glad_glGetStringi = NULL; +PFNGLGETSYNCIVPROC glad_glGetSynciv = NULL; +PFNGLGETTEXIMAGEPROC glad_glGetTexImage = NULL; +PFNGLGETTEXLEVELPARAMETERFVPROC glad_glGetTexLevelParameterfv = NULL; +PFNGLGETTEXLEVELPARAMETERIVPROC glad_glGetTexLevelParameteriv = NULL; +PFNGLGETTEXPARAMETERIIVPROC glad_glGetTexParameterIiv = NULL; +PFNGLGETTEXPARAMETERIUIVPROC glad_glGetTexParameterIuiv = NULL; +PFNGLGETTEXPARAMETERFVPROC glad_glGetTexParameterfv = NULL; +PFNGLGETTEXPARAMETERIVPROC glad_glGetTexParameteriv = NULL; +PFNGLGETTRANSFORMFEEDBACKVARYINGPROC glad_glGetTransformFeedbackVarying = NULL; +PFNGLGETUNIFORMBLOCKINDEXPROC glad_glGetUniformBlockIndex = NULL; +PFNGLGETUNIFORMINDICESPROC glad_glGetUniformIndices = NULL; +PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation = NULL; +PFNGLGETUNIFORMFVPROC glad_glGetUniformfv = NULL; +PFNGLGETUNIFORMIVPROC glad_glGetUniformiv = NULL; +PFNGLGETUNIFORMUIVPROC glad_glGetUniformuiv = NULL; +PFNGLGETVERTEXATTRIBIIVPROC glad_glGetVertexAttribIiv = NULL; +PFNGLGETVERTEXATTRIBIUIVPROC glad_glGetVertexAttribIuiv = NULL; +PFNGLGETVERTEXATTRIBPOINTERVPROC glad_glGetVertexAttribPointerv = NULL; +PFNGLGETVERTEXATTRIBDVPROC glad_glGetVertexAttribdv = NULL; +PFNGLGETVERTEXATTRIBFVPROC glad_glGetVertexAttribfv = NULL; +PFNGLGETVERTEXATTRIBIVPROC glad_glGetVertexAttribiv = NULL; +PFNGLHINTPROC glad_glHint = NULL; +PFNGLINVALIDATEFRAMEBUFFERPROC glad_glInvalidateFramebuffer = NULL; +PFNGLINVALIDATESUBFRAMEBUFFERPROC glad_glInvalidateSubFramebuffer = NULL; +PFNGLISBUFFERPROC glad_glIsBuffer = NULL; +PFNGLISENABLEDPROC glad_glIsEnabled = NULL; +PFNGLISENABLEDIPROC glad_glIsEnabledi = NULL; +PFNGLISFRAMEBUFFERPROC glad_glIsFramebuffer = NULL; +PFNGLISPROGRAMPROC glad_glIsProgram = NULL; +PFNGLISPROGRAMPIPELINEPROC glad_glIsProgramPipeline = NULL; +PFNGLISQUERYPROC glad_glIsQuery = NULL; +PFNGLISRENDERBUFFERPROC glad_glIsRenderbuffer = NULL; +PFNGLISSAMPLERPROC glad_glIsSampler = NULL; +PFNGLISSHADERPROC glad_glIsShader = NULL; +PFNGLISSYNCPROC glad_glIsSync = NULL; +PFNGLISTEXTUREPROC glad_glIsTexture = NULL; +PFNGLISTRANSFORMFEEDBACKPROC glad_glIsTransformFeedback = NULL; +PFNGLISVERTEXARRAYPROC glad_glIsVertexArray = NULL; +PFNGLLINEWIDTHPROC glad_glLineWidth = NULL; +PFNGLLINKPROGRAMPROC glad_glLinkProgram = NULL; +PFNGLLOGICOPPROC glad_glLogicOp = NULL; +PFNGLMAPBUFFERPROC glad_glMapBuffer = NULL; +PFNGLMAPBUFFERRANGEPROC glad_glMapBufferRange = NULL; +PFNGLMEMORYBARRIERPROC glad_glMemoryBarrier = NULL; +PFNGLMEMORYBARRIERBYREGIONPROC glad_glMemoryBarrierByRegion = NULL; +PFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays = NULL; +PFNGLMULTIDRAWELEMENTSPROC glad_glMultiDrawElements = NULL; +PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC glad_glMultiDrawElementsBaseVertex = NULL; +PFNGLMULTITEXCOORDP1UIPROC glad_glMultiTexCoordP1ui = NULL; +PFNGLMULTITEXCOORDP1UIVPROC glad_glMultiTexCoordP1uiv = NULL; +PFNGLMULTITEXCOORDP2UIPROC glad_glMultiTexCoordP2ui = NULL; +PFNGLMULTITEXCOORDP2UIVPROC glad_glMultiTexCoordP2uiv = NULL; +PFNGLMULTITEXCOORDP3UIPROC glad_glMultiTexCoordP3ui = NULL; +PFNGLMULTITEXCOORDP3UIVPROC glad_glMultiTexCoordP3uiv = NULL; +PFNGLMULTITEXCOORDP4UIPROC glad_glMultiTexCoordP4ui = NULL; +PFNGLMULTITEXCOORDP4UIVPROC glad_glMultiTexCoordP4uiv = NULL; +PFNGLNORMALP3UIPROC glad_glNormalP3ui = NULL; +PFNGLNORMALP3UIVPROC glad_glNormalP3uiv = NULL; +PFNGLPAUSETRANSFORMFEEDBACKPROC glad_glPauseTransformFeedback = NULL; +PFNGLPIXELSTOREFPROC glad_glPixelStoref = NULL; +PFNGLPIXELSTOREIPROC glad_glPixelStorei = NULL; +PFNGLPOINTPARAMETERFPROC glad_glPointParameterf = NULL; +PFNGLPOINTPARAMETERFVPROC glad_glPointParameterfv = NULL; +PFNGLPOINTPARAMETERIPROC glad_glPointParameteri = NULL; +PFNGLPOINTPARAMETERIVPROC glad_glPointParameteriv = NULL; +PFNGLPOINTSIZEPROC glad_glPointSize = NULL; +PFNGLPOLYGONMODEPROC glad_glPolygonMode = NULL; +PFNGLPOLYGONOFFSETPROC glad_glPolygonOffset = NULL; +PFNGLPRIMITIVERESTARTINDEXPROC glad_glPrimitiveRestartIndex = NULL; +PFNGLPROGRAMBINARYPROC glad_glProgramBinary = NULL; +PFNGLPROGRAMPARAMETERIPROC glad_glProgramParameteri = NULL; +PFNGLPROGRAMUNIFORM1FPROC glad_glProgramUniform1f = NULL; +PFNGLPROGRAMUNIFORM1FVPROC glad_glProgramUniform1fv = NULL; +PFNGLPROGRAMUNIFORM1IPROC glad_glProgramUniform1i = NULL; +PFNGLPROGRAMUNIFORM1IVPROC glad_glProgramUniform1iv = NULL; +PFNGLPROGRAMUNIFORM1UIPROC glad_glProgramUniform1ui = NULL; +PFNGLPROGRAMUNIFORM1UIVPROC glad_glProgramUniform1uiv = NULL; +PFNGLPROGRAMUNIFORM2FPROC glad_glProgramUniform2f = NULL; +PFNGLPROGRAMUNIFORM2FVPROC glad_glProgramUniform2fv = NULL; +PFNGLPROGRAMUNIFORM2IPROC glad_glProgramUniform2i = NULL; +PFNGLPROGRAMUNIFORM2IVPROC glad_glProgramUniform2iv = NULL; +PFNGLPROGRAMUNIFORM2UIPROC glad_glProgramUniform2ui = NULL; +PFNGLPROGRAMUNIFORM2UIVPROC glad_glProgramUniform2uiv = NULL; +PFNGLPROGRAMUNIFORM3FPROC glad_glProgramUniform3f = NULL; +PFNGLPROGRAMUNIFORM3FVPROC glad_glProgramUniform3fv = NULL; +PFNGLPROGRAMUNIFORM3IPROC glad_glProgramUniform3i = NULL; +PFNGLPROGRAMUNIFORM3IVPROC glad_glProgramUniform3iv = NULL; +PFNGLPROGRAMUNIFORM3UIPROC glad_glProgramUniform3ui = NULL; +PFNGLPROGRAMUNIFORM3UIVPROC glad_glProgramUniform3uiv = NULL; +PFNGLPROGRAMUNIFORM4FPROC glad_glProgramUniform4f = NULL; +PFNGLPROGRAMUNIFORM4FVPROC glad_glProgramUniform4fv = NULL; +PFNGLPROGRAMUNIFORM4IPROC glad_glProgramUniform4i = NULL; +PFNGLPROGRAMUNIFORM4IVPROC glad_glProgramUniform4iv = NULL; +PFNGLPROGRAMUNIFORM4UIPROC glad_glProgramUniform4ui = NULL; +PFNGLPROGRAMUNIFORM4UIVPROC glad_glProgramUniform4uiv = NULL; +PFNGLPROGRAMUNIFORMMATRIX2FVPROC glad_glProgramUniformMatrix2fv = NULL; +PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC glad_glProgramUniformMatrix2x3fv = NULL; +PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC glad_glProgramUniformMatrix2x4fv = NULL; +PFNGLPROGRAMUNIFORMMATRIX3FVPROC glad_glProgramUniformMatrix3fv = NULL; +PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC glad_glProgramUniformMatrix3x2fv = NULL; +PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC glad_glProgramUniformMatrix3x4fv = NULL; +PFNGLPROGRAMUNIFORMMATRIX4FVPROC glad_glProgramUniformMatrix4fv = NULL; +PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC glad_glProgramUniformMatrix4x2fv = NULL; +PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC glad_glProgramUniformMatrix4x3fv = NULL; +PFNGLPROVOKINGVERTEXPROC glad_glProvokingVertex = NULL; +PFNGLQUERYCOUNTERPROC glad_glQueryCounter = NULL; +PFNGLREADBUFFERPROC glad_glReadBuffer = NULL; +PFNGLREADPIXELSPROC glad_glReadPixels = NULL; +PFNGLRELEASESHADERCOMPILERPROC glad_glReleaseShaderCompiler = NULL; +PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage = NULL; +PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = NULL; +PFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback = NULL; +PFNGLSAMPLECOVERAGEPROC glad_glSampleCoverage = NULL; +PFNGLSAMPLEMASKIPROC glad_glSampleMaski = NULL; +PFNGLSAMPLERPARAMETERIIVPROC glad_glSamplerParameterIiv = NULL; +PFNGLSAMPLERPARAMETERIUIVPROC glad_glSamplerParameterIuiv = NULL; +PFNGLSAMPLERPARAMETERFPROC glad_glSamplerParameterf = NULL; +PFNGLSAMPLERPARAMETERFVPROC glad_glSamplerParameterfv = NULL; +PFNGLSAMPLERPARAMETERIPROC glad_glSamplerParameteri = NULL; +PFNGLSAMPLERPARAMETERIVPROC glad_glSamplerParameteriv = NULL; +PFNGLSCISSORPROC glad_glScissor = NULL; +PFNGLSECONDARYCOLORP3UIPROC glad_glSecondaryColorP3ui = NULL; +PFNGLSECONDARYCOLORP3UIVPROC glad_glSecondaryColorP3uiv = NULL; +PFNGLSHADERBINARYPROC glad_glShaderBinary = NULL; +PFNGLSHADERSOURCEPROC glad_glShaderSource = NULL; +PFNGLSTENCILFUNCPROC glad_glStencilFunc = NULL; +PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate = NULL; +PFNGLSTENCILMASKPROC glad_glStencilMask = NULL; +PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate = NULL; +PFNGLSTENCILOPPROC glad_glStencilOp = NULL; +PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate = NULL; +PFNGLTEXBUFFERPROC glad_glTexBuffer = NULL; +PFNGLTEXCOORDP1UIPROC glad_glTexCoordP1ui = NULL; +PFNGLTEXCOORDP1UIVPROC glad_glTexCoordP1uiv = NULL; +PFNGLTEXCOORDP2UIPROC glad_glTexCoordP2ui = NULL; +PFNGLTEXCOORDP2UIVPROC glad_glTexCoordP2uiv = NULL; +PFNGLTEXCOORDP3UIPROC glad_glTexCoordP3ui = NULL; +PFNGLTEXCOORDP3UIVPROC glad_glTexCoordP3uiv = NULL; +PFNGLTEXCOORDP4UIPROC glad_glTexCoordP4ui = NULL; +PFNGLTEXCOORDP4UIVPROC glad_glTexCoordP4uiv = NULL; +PFNGLTEXIMAGE1DPROC glad_glTexImage1D = NULL; +PFNGLTEXIMAGE2DPROC glad_glTexImage2D = NULL; +PFNGLTEXIMAGE2DMULTISAMPLEPROC glad_glTexImage2DMultisample = NULL; +PFNGLTEXIMAGE3DPROC glad_glTexImage3D = NULL; +PFNGLTEXIMAGE3DMULTISAMPLEPROC glad_glTexImage3DMultisample = NULL; +PFNGLTEXPARAMETERIIVPROC glad_glTexParameterIiv = NULL; +PFNGLTEXPARAMETERIUIVPROC glad_glTexParameterIuiv = NULL; +PFNGLTEXPARAMETERFPROC glad_glTexParameterf = NULL; +PFNGLTEXPARAMETERFVPROC glad_glTexParameterfv = NULL; +PFNGLTEXPARAMETERIPROC glad_glTexParameteri = NULL; +PFNGLTEXPARAMETERIVPROC glad_glTexParameteriv = NULL; +PFNGLTEXSTORAGE2DPROC glad_glTexStorage2D = NULL; +PFNGLTEXSTORAGE2DMULTISAMPLEPROC glad_glTexStorage2DMultisample = NULL; +PFNGLTEXSTORAGE3DPROC glad_glTexStorage3D = NULL; +PFNGLTEXSUBIMAGE1DPROC glad_glTexSubImage1D = NULL; +PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D = NULL; +PFNGLTEXSUBIMAGE3DPROC glad_glTexSubImage3D = NULL; +PFNGLTRANSFORMFEEDBACKVARYINGSPROC glad_glTransformFeedbackVaryings = NULL; +PFNGLUNIFORM1FPROC glad_glUniform1f = NULL; +PFNGLUNIFORM1FVPROC glad_glUniform1fv = NULL; +PFNGLUNIFORM1IPROC glad_glUniform1i = NULL; +PFNGLUNIFORM1IVPROC glad_glUniform1iv = NULL; +PFNGLUNIFORM1UIPROC glad_glUniform1ui = NULL; +PFNGLUNIFORM1UIVPROC glad_glUniform1uiv = NULL; +PFNGLUNIFORM2FPROC glad_glUniform2f = NULL; +PFNGLUNIFORM2FVPROC glad_glUniform2fv = NULL; +PFNGLUNIFORM2IPROC glad_glUniform2i = NULL; +PFNGLUNIFORM2IVPROC glad_glUniform2iv = NULL; +PFNGLUNIFORM2UIPROC glad_glUniform2ui = NULL; +PFNGLUNIFORM2UIVPROC glad_glUniform2uiv = NULL; +PFNGLUNIFORM3FPROC glad_glUniform3f = NULL; +PFNGLUNIFORM3FVPROC glad_glUniform3fv = NULL; +PFNGLUNIFORM3IPROC glad_glUniform3i = NULL; +PFNGLUNIFORM3IVPROC glad_glUniform3iv = NULL; +PFNGLUNIFORM3UIPROC glad_glUniform3ui = NULL; +PFNGLUNIFORM3UIVPROC glad_glUniform3uiv = NULL; +PFNGLUNIFORM4FPROC glad_glUniform4f = NULL; +PFNGLUNIFORM4FVPROC glad_glUniform4fv = NULL; +PFNGLUNIFORM4IPROC glad_glUniform4i = NULL; +PFNGLUNIFORM4IVPROC glad_glUniform4iv = NULL; +PFNGLUNIFORM4UIPROC glad_glUniform4ui = NULL; +PFNGLUNIFORM4UIVPROC glad_glUniform4uiv = NULL; +PFNGLUNIFORMBLOCKBINDINGPROC glad_glUniformBlockBinding = NULL; +PFNGLUNIFORMMATRIX2FVPROC glad_glUniformMatrix2fv = NULL; +PFNGLUNIFORMMATRIX2X3FVPROC glad_glUniformMatrix2x3fv = NULL; +PFNGLUNIFORMMATRIX2X4FVPROC glad_glUniformMatrix2x4fv = NULL; +PFNGLUNIFORMMATRIX3FVPROC glad_glUniformMatrix3fv = NULL; +PFNGLUNIFORMMATRIX3X2FVPROC glad_glUniformMatrix3x2fv = NULL; +PFNGLUNIFORMMATRIX3X4FVPROC glad_glUniformMatrix3x4fv = NULL; +PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv = NULL; +PFNGLUNIFORMMATRIX4X2FVPROC glad_glUniformMatrix4x2fv = NULL; +PFNGLUNIFORMMATRIX4X3FVPROC glad_glUniformMatrix4x3fv = NULL; +PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer = NULL; +PFNGLUSEPROGRAMPROC glad_glUseProgram = NULL; +PFNGLUSEPROGRAMSTAGESPROC glad_glUseProgramStages = NULL; +PFNGLVALIDATEPROGRAMPROC glad_glValidateProgram = NULL; +PFNGLVALIDATEPROGRAMPIPELINEPROC glad_glValidateProgramPipeline = NULL; +PFNGLVERTEXATTRIB1DPROC glad_glVertexAttrib1d = NULL; +PFNGLVERTEXATTRIB1DVPROC glad_glVertexAttrib1dv = NULL; +PFNGLVERTEXATTRIB1FPROC glad_glVertexAttrib1f = NULL; +PFNGLVERTEXATTRIB1FVPROC glad_glVertexAttrib1fv = NULL; +PFNGLVERTEXATTRIB1SPROC glad_glVertexAttrib1s = NULL; +PFNGLVERTEXATTRIB1SVPROC glad_glVertexAttrib1sv = NULL; +PFNGLVERTEXATTRIB2DPROC glad_glVertexAttrib2d = NULL; +PFNGLVERTEXATTRIB2DVPROC glad_glVertexAttrib2dv = NULL; +PFNGLVERTEXATTRIB2FPROC glad_glVertexAttrib2f = NULL; +PFNGLVERTEXATTRIB2FVPROC glad_glVertexAttrib2fv = NULL; +PFNGLVERTEXATTRIB2SPROC glad_glVertexAttrib2s = NULL; +PFNGLVERTEXATTRIB2SVPROC glad_glVertexAttrib2sv = NULL; +PFNGLVERTEXATTRIB3DPROC glad_glVertexAttrib3d = NULL; +PFNGLVERTEXATTRIB3DVPROC glad_glVertexAttrib3dv = NULL; +PFNGLVERTEXATTRIB3FPROC glad_glVertexAttrib3f = NULL; +PFNGLVERTEXATTRIB3FVPROC glad_glVertexAttrib3fv = NULL; +PFNGLVERTEXATTRIB3SPROC glad_glVertexAttrib3s = NULL; +PFNGLVERTEXATTRIB3SVPROC glad_glVertexAttrib3sv = NULL; +PFNGLVERTEXATTRIB4NBVPROC glad_glVertexAttrib4Nbv = NULL; +PFNGLVERTEXATTRIB4NIVPROC glad_glVertexAttrib4Niv = NULL; +PFNGLVERTEXATTRIB4NSVPROC glad_glVertexAttrib4Nsv = NULL; +PFNGLVERTEXATTRIB4NUBPROC glad_glVertexAttrib4Nub = NULL; +PFNGLVERTEXATTRIB4NUBVPROC glad_glVertexAttrib4Nubv = NULL; +PFNGLVERTEXATTRIB4NUIVPROC glad_glVertexAttrib4Nuiv = NULL; +PFNGLVERTEXATTRIB4NUSVPROC glad_glVertexAttrib4Nusv = NULL; +PFNGLVERTEXATTRIB4BVPROC glad_glVertexAttrib4bv = NULL; +PFNGLVERTEXATTRIB4DPROC glad_glVertexAttrib4d = NULL; +PFNGLVERTEXATTRIB4DVPROC glad_glVertexAttrib4dv = NULL; +PFNGLVERTEXATTRIB4FPROC glad_glVertexAttrib4f = NULL; +PFNGLVERTEXATTRIB4FVPROC glad_glVertexAttrib4fv = NULL; +PFNGLVERTEXATTRIB4IVPROC glad_glVertexAttrib4iv = NULL; +PFNGLVERTEXATTRIB4SPROC glad_glVertexAttrib4s = NULL; +PFNGLVERTEXATTRIB4SVPROC glad_glVertexAttrib4sv = NULL; +PFNGLVERTEXATTRIB4UBVPROC glad_glVertexAttrib4ubv = NULL; +PFNGLVERTEXATTRIB4UIVPROC glad_glVertexAttrib4uiv = NULL; +PFNGLVERTEXATTRIB4USVPROC glad_glVertexAttrib4usv = NULL; +PFNGLVERTEXATTRIBBINDINGPROC glad_glVertexAttribBinding = NULL; +PFNGLVERTEXATTRIBDIVISORPROC glad_glVertexAttribDivisor = NULL; +PFNGLVERTEXATTRIBFORMATPROC glad_glVertexAttribFormat = NULL; +PFNGLVERTEXATTRIBI1IPROC glad_glVertexAttribI1i = NULL; +PFNGLVERTEXATTRIBI1IVPROC glad_glVertexAttribI1iv = NULL; +PFNGLVERTEXATTRIBI1UIPROC glad_glVertexAttribI1ui = NULL; +PFNGLVERTEXATTRIBI1UIVPROC glad_glVertexAttribI1uiv = NULL; +PFNGLVERTEXATTRIBI2IPROC glad_glVertexAttribI2i = NULL; +PFNGLVERTEXATTRIBI2IVPROC glad_glVertexAttribI2iv = NULL; +PFNGLVERTEXATTRIBI2UIPROC glad_glVertexAttribI2ui = NULL; +PFNGLVERTEXATTRIBI2UIVPROC glad_glVertexAttribI2uiv = NULL; +PFNGLVERTEXATTRIBI3IPROC glad_glVertexAttribI3i = NULL; +PFNGLVERTEXATTRIBI3IVPROC glad_glVertexAttribI3iv = NULL; +PFNGLVERTEXATTRIBI3UIPROC glad_glVertexAttribI3ui = NULL; +PFNGLVERTEXATTRIBI3UIVPROC glad_glVertexAttribI3uiv = NULL; +PFNGLVERTEXATTRIBI4BVPROC glad_glVertexAttribI4bv = NULL; +PFNGLVERTEXATTRIBI4IPROC glad_glVertexAttribI4i = NULL; +PFNGLVERTEXATTRIBI4IVPROC glad_glVertexAttribI4iv = NULL; +PFNGLVERTEXATTRIBI4SVPROC glad_glVertexAttribI4sv = NULL; +PFNGLVERTEXATTRIBI4UBVPROC glad_glVertexAttribI4ubv = NULL; +PFNGLVERTEXATTRIBI4UIPROC glad_glVertexAttribI4ui = NULL; +PFNGLVERTEXATTRIBI4UIVPROC glad_glVertexAttribI4uiv = NULL; +PFNGLVERTEXATTRIBI4USVPROC glad_glVertexAttribI4usv = NULL; +PFNGLVERTEXATTRIBIFORMATPROC glad_glVertexAttribIFormat = NULL; +PFNGLVERTEXATTRIBIPOINTERPROC glad_glVertexAttribIPointer = NULL; +PFNGLVERTEXATTRIBP1UIPROC glad_glVertexAttribP1ui = NULL; +PFNGLVERTEXATTRIBP1UIVPROC glad_glVertexAttribP1uiv = NULL; +PFNGLVERTEXATTRIBP2UIPROC glad_glVertexAttribP2ui = NULL; +PFNGLVERTEXATTRIBP2UIVPROC glad_glVertexAttribP2uiv = NULL; +PFNGLVERTEXATTRIBP3UIPROC glad_glVertexAttribP3ui = NULL; +PFNGLVERTEXATTRIBP3UIVPROC glad_glVertexAttribP3uiv = NULL; +PFNGLVERTEXATTRIBP4UIPROC glad_glVertexAttribP4ui = NULL; +PFNGLVERTEXATTRIBP4UIVPROC glad_glVertexAttribP4uiv = NULL; +PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer = NULL; +PFNGLVERTEXBINDINGDIVISORPROC glad_glVertexBindingDivisor = NULL; +PFNGLVERTEXP2UIPROC glad_glVertexP2ui = NULL; +PFNGLVERTEXP2UIVPROC glad_glVertexP2uiv = NULL; +PFNGLVERTEXP3UIPROC glad_glVertexP3ui = NULL; +PFNGLVERTEXP3UIVPROC glad_glVertexP3uiv = NULL; +PFNGLVERTEXP4UIPROC glad_glVertexP4ui = NULL; +PFNGLVERTEXP4UIVPROC glad_glVertexP4uiv = NULL; +PFNGLVIEWPORTPROC glad_glViewport = NULL; +PFNGLWAITSYNCPROC glad_glWaitSync = NULL; +int GLAD_GL_EXT_framebuffer_object = 0; +int GLAD_GL_EXT_texture_compression_s3tc = 0; +int GLAD_GL_EXT_texture_filter_anisotropic = 0; +int GLAD_GL_KHR_debug = 0; +int GLAD_GL_KHR_texture_compression_astc_ldr = 0; +PFNGLDEBUGMESSAGECONTROLPROC glad_glDebugMessageControl = NULL; +PFNGLDEBUGMESSAGEINSERTPROC glad_glDebugMessageInsert = NULL; +PFNGLDEBUGMESSAGECALLBACKPROC glad_glDebugMessageCallback = NULL; +PFNGLGETDEBUGMESSAGELOGPROC glad_glGetDebugMessageLog = NULL; +PFNGLPUSHDEBUGGROUPPROC glad_glPushDebugGroup = NULL; +PFNGLPOPDEBUGGROUPPROC glad_glPopDebugGroup = NULL; +PFNGLOBJECTLABELPROC glad_glObjectLabel = NULL; +PFNGLGETOBJECTLABELPROC glad_glGetObjectLabel = NULL; +PFNGLOBJECTPTRLABELPROC glad_glObjectPtrLabel = NULL; +PFNGLGETOBJECTPTRLABELPROC glad_glGetObjectPtrLabel = NULL; +PFNGLGETPOINTERVPROC glad_glGetPointerv = NULL; +PFNGLDEBUGMESSAGECONTROLKHRPROC glad_glDebugMessageControlKHR = NULL; +PFNGLDEBUGMESSAGEINSERTKHRPROC glad_glDebugMessageInsertKHR = NULL; +PFNGLDEBUGMESSAGECALLBACKKHRPROC glad_glDebugMessageCallbackKHR = NULL; +PFNGLGETDEBUGMESSAGELOGKHRPROC glad_glGetDebugMessageLogKHR = NULL; +PFNGLPUSHDEBUGGROUPKHRPROC glad_glPushDebugGroupKHR = NULL; +PFNGLPOPDEBUGGROUPKHRPROC glad_glPopDebugGroupKHR = NULL; +PFNGLOBJECTLABELKHRPROC glad_glObjectLabelKHR = NULL; +PFNGLGETOBJECTLABELKHRPROC glad_glGetObjectLabelKHR = NULL; +PFNGLOBJECTPTRLABELKHRPROC glad_glObjectPtrLabelKHR = NULL; +PFNGLGETOBJECTPTRLABELKHRPROC glad_glGetObjectPtrLabelKHR = NULL; +PFNGLGETPOINTERVKHRPROC glad_glGetPointervKHR = NULL; +static void load_GL_VERSION_1_0(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_0) return; + glad_glCullFace = (PFNGLCULLFACEPROC)load("glCullFace"); + glad_glFrontFace = (PFNGLFRONTFACEPROC)load("glFrontFace"); + glad_glHint = (PFNGLHINTPROC)load("glHint"); + glad_glLineWidth = (PFNGLLINEWIDTHPROC)load("glLineWidth"); + glad_glPointSize = (PFNGLPOINTSIZEPROC)load("glPointSize"); + glad_glPolygonMode = (PFNGLPOLYGONMODEPROC)load("glPolygonMode"); + glad_glScissor = (PFNGLSCISSORPROC)load("glScissor"); + glad_glTexParameterf = (PFNGLTEXPARAMETERFPROC)load("glTexParameterf"); + glad_glTexParameterfv = (PFNGLTEXPARAMETERFVPROC)load("glTexParameterfv"); + glad_glTexParameteri = (PFNGLTEXPARAMETERIPROC)load("glTexParameteri"); + glad_glTexParameteriv = (PFNGLTEXPARAMETERIVPROC)load("glTexParameteriv"); + glad_glTexImage1D = (PFNGLTEXIMAGE1DPROC)load("glTexImage1D"); + glad_glTexImage2D = (PFNGLTEXIMAGE2DPROC)load("glTexImage2D"); + glad_glDrawBuffer = (PFNGLDRAWBUFFERPROC)load("glDrawBuffer"); + glad_glClear = (PFNGLCLEARPROC)load("glClear"); + glad_glClearColor = (PFNGLCLEARCOLORPROC)load("glClearColor"); + glad_glClearStencil = (PFNGLCLEARSTENCILPROC)load("glClearStencil"); + glad_glClearDepth = (PFNGLCLEARDEPTHPROC)load("glClearDepth"); + glad_glStencilMask = (PFNGLSTENCILMASKPROC)load("glStencilMask"); + glad_glColorMask = (PFNGLCOLORMASKPROC)load("glColorMask"); + glad_glDepthMask = (PFNGLDEPTHMASKPROC)load("glDepthMask"); + glad_glDisable = (PFNGLDISABLEPROC)load("glDisable"); + glad_glEnable = (PFNGLENABLEPROC)load("glEnable"); + glad_glFinish = (PFNGLFINISHPROC)load("glFinish"); + glad_glFlush = (PFNGLFLUSHPROC)load("glFlush"); + glad_glBlendFunc = (PFNGLBLENDFUNCPROC)load("glBlendFunc"); + glad_glLogicOp = (PFNGLLOGICOPPROC)load("glLogicOp"); + glad_glStencilFunc = (PFNGLSTENCILFUNCPROC)load("glStencilFunc"); + glad_glStencilOp = (PFNGLSTENCILOPPROC)load("glStencilOp"); + glad_glDepthFunc = (PFNGLDEPTHFUNCPROC)load("glDepthFunc"); + glad_glPixelStoref = (PFNGLPIXELSTOREFPROC)load("glPixelStoref"); + glad_glPixelStorei = (PFNGLPIXELSTOREIPROC)load("glPixelStorei"); + glad_glReadBuffer = (PFNGLREADBUFFERPROC)load("glReadBuffer"); + glad_glReadPixels = (PFNGLREADPIXELSPROC)load("glReadPixels"); + glad_glGetBooleanv = (PFNGLGETBOOLEANVPROC)load("glGetBooleanv"); + glad_glGetDoublev = (PFNGLGETDOUBLEVPROC)load("glGetDoublev"); + glad_glGetError = (PFNGLGETERRORPROC)load("glGetError"); + glad_glGetFloatv = (PFNGLGETFLOATVPROC)load("glGetFloatv"); + glad_glGetIntegerv = (PFNGLGETINTEGERVPROC)load("glGetIntegerv"); + glad_glGetString = (PFNGLGETSTRINGPROC)load("glGetString"); + glad_glGetTexImage = (PFNGLGETTEXIMAGEPROC)load("glGetTexImage"); + glad_glGetTexParameterfv = (PFNGLGETTEXPARAMETERFVPROC)load("glGetTexParameterfv"); + glad_glGetTexParameteriv = (PFNGLGETTEXPARAMETERIVPROC)load("glGetTexParameteriv"); + glad_glGetTexLevelParameterfv = (PFNGLGETTEXLEVELPARAMETERFVPROC)load("glGetTexLevelParameterfv"); + glad_glGetTexLevelParameteriv = (PFNGLGETTEXLEVELPARAMETERIVPROC)load("glGetTexLevelParameteriv"); + glad_glIsEnabled = (PFNGLISENABLEDPROC)load("glIsEnabled"); + glad_glDepthRange = (PFNGLDEPTHRANGEPROC)load("glDepthRange"); + glad_glViewport = (PFNGLVIEWPORTPROC)load("glViewport"); +} +static void load_GL_VERSION_1_1(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_1) return; + glad_glDrawArrays = (PFNGLDRAWARRAYSPROC)load("glDrawArrays"); + glad_glDrawElements = (PFNGLDRAWELEMENTSPROC)load("glDrawElements"); + glad_glPolygonOffset = (PFNGLPOLYGONOFFSETPROC)load("glPolygonOffset"); + glad_glCopyTexImage1D = (PFNGLCOPYTEXIMAGE1DPROC)load("glCopyTexImage1D"); + glad_glCopyTexImage2D = (PFNGLCOPYTEXIMAGE2DPROC)load("glCopyTexImage2D"); + glad_glCopyTexSubImage1D = (PFNGLCOPYTEXSUBIMAGE1DPROC)load("glCopyTexSubImage1D"); + glad_glCopyTexSubImage2D = (PFNGLCOPYTEXSUBIMAGE2DPROC)load("glCopyTexSubImage2D"); + glad_glTexSubImage1D = (PFNGLTEXSUBIMAGE1DPROC)load("glTexSubImage1D"); + glad_glTexSubImage2D = (PFNGLTEXSUBIMAGE2DPROC)load("glTexSubImage2D"); + glad_glBindTexture = (PFNGLBINDTEXTUREPROC)load("glBindTexture"); + glad_glDeleteTextures = (PFNGLDELETETEXTURESPROC)load("glDeleteTextures"); + glad_glGenTextures = (PFNGLGENTEXTURESPROC)load("glGenTextures"); + glad_glIsTexture = (PFNGLISTEXTUREPROC)load("glIsTexture"); +} +static void load_GL_VERSION_1_2(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_2) return; + glad_glDrawRangeElements = (PFNGLDRAWRANGEELEMENTSPROC)load("glDrawRangeElements"); + glad_glTexImage3D = (PFNGLTEXIMAGE3DPROC)load("glTexImage3D"); + glad_glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC)load("glTexSubImage3D"); + glad_glCopyTexSubImage3D = (PFNGLCOPYTEXSUBIMAGE3DPROC)load("glCopyTexSubImage3D"); +} +static void load_GL_VERSION_1_3(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_3) return; + glad_glActiveTexture = (PFNGLACTIVETEXTUREPROC)load("glActiveTexture"); + glad_glSampleCoverage = (PFNGLSAMPLECOVERAGEPROC)load("glSampleCoverage"); + glad_glCompressedTexImage3D = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)load("glCompressedTexImage3D"); + glad_glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)load("glCompressedTexImage2D"); + glad_glCompressedTexImage1D = (PFNGLCOMPRESSEDTEXIMAGE1DPROC)load("glCompressedTexImage1D"); + glad_glCompressedTexSubImage3D = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)load("glCompressedTexSubImage3D"); + glad_glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)load("glCompressedTexSubImage2D"); + glad_glCompressedTexSubImage1D = (PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)load("glCompressedTexSubImage1D"); + glad_glGetCompressedTexImage = (PFNGLGETCOMPRESSEDTEXIMAGEPROC)load("glGetCompressedTexImage"); +} +static void load_GL_VERSION_1_4(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_4) return; + glad_glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)load("glBlendFuncSeparate"); + glad_glMultiDrawArrays = (PFNGLMULTIDRAWARRAYSPROC)load("glMultiDrawArrays"); + glad_glMultiDrawElements = (PFNGLMULTIDRAWELEMENTSPROC)load("glMultiDrawElements"); + glad_glPointParameterf = (PFNGLPOINTPARAMETERFPROC)load("glPointParameterf"); + glad_glPointParameterfv = (PFNGLPOINTPARAMETERFVPROC)load("glPointParameterfv"); + glad_glPointParameteri = (PFNGLPOINTPARAMETERIPROC)load("glPointParameteri"); + glad_glPointParameteriv = (PFNGLPOINTPARAMETERIVPROC)load("glPointParameteriv"); + glad_glBlendColor = (PFNGLBLENDCOLORPROC)load("glBlendColor"); + glad_glBlendEquation = (PFNGLBLENDEQUATIONPROC)load("glBlendEquation"); +} +static void load_GL_VERSION_1_5(GLADloadproc load) { + if(!GLAD_GL_VERSION_1_5) return; + glad_glGenQueries = (PFNGLGENQUERIESPROC)load("glGenQueries"); + glad_glDeleteQueries = (PFNGLDELETEQUERIESPROC)load("glDeleteQueries"); + glad_glIsQuery = (PFNGLISQUERYPROC)load("glIsQuery"); + glad_glBeginQuery = (PFNGLBEGINQUERYPROC)load("glBeginQuery"); + glad_glEndQuery = (PFNGLENDQUERYPROC)load("glEndQuery"); + glad_glGetQueryiv = (PFNGLGETQUERYIVPROC)load("glGetQueryiv"); + glad_glGetQueryObjectiv = (PFNGLGETQUERYOBJECTIVPROC)load("glGetQueryObjectiv"); + glad_glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC)load("glGetQueryObjectuiv"); + glad_glBindBuffer = (PFNGLBINDBUFFERPROC)load("glBindBuffer"); + glad_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)load("glDeleteBuffers"); + glad_glGenBuffers = (PFNGLGENBUFFERSPROC)load("glGenBuffers"); + glad_glIsBuffer = (PFNGLISBUFFERPROC)load("glIsBuffer"); + glad_glBufferData = (PFNGLBUFFERDATAPROC)load("glBufferData"); + glad_glBufferSubData = (PFNGLBUFFERSUBDATAPROC)load("glBufferSubData"); + glad_glGetBufferSubData = (PFNGLGETBUFFERSUBDATAPROC)load("glGetBufferSubData"); + glad_glMapBuffer = (PFNGLMAPBUFFERPROC)load("glMapBuffer"); + glad_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)load("glUnmapBuffer"); + glad_glGetBufferParameteriv = (PFNGLGETBUFFERPARAMETERIVPROC)load("glGetBufferParameteriv"); + glad_glGetBufferPointerv = (PFNGLGETBUFFERPOINTERVPROC)load("glGetBufferPointerv"); +} +static void load_GL_VERSION_2_0(GLADloadproc load) { + if(!GLAD_GL_VERSION_2_0) return; + glad_glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC)load("glBlendEquationSeparate"); + glad_glDrawBuffers = (PFNGLDRAWBUFFERSPROC)load("glDrawBuffers"); + glad_glStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)load("glStencilOpSeparate"); + glad_glStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEPROC)load("glStencilFuncSeparate"); + glad_glStencilMaskSeparate = (PFNGLSTENCILMASKSEPARATEPROC)load("glStencilMaskSeparate"); + glad_glAttachShader = (PFNGLATTACHSHADERPROC)load("glAttachShader"); + glad_glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)load("glBindAttribLocation"); + glad_glCompileShader = (PFNGLCOMPILESHADERPROC)load("glCompileShader"); + glad_glCreateProgram = (PFNGLCREATEPROGRAMPROC)load("glCreateProgram"); + glad_glCreateShader = (PFNGLCREATESHADERPROC)load("glCreateShader"); + glad_glDeleteProgram = (PFNGLDELETEPROGRAMPROC)load("glDeleteProgram"); + glad_glDeleteShader = (PFNGLDELETESHADERPROC)load("glDeleteShader"); + glad_glDetachShader = (PFNGLDETACHSHADERPROC)load("glDetachShader"); + glad_glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)load("glDisableVertexAttribArray"); + glad_glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)load("glEnableVertexAttribArray"); + glad_glGetActiveAttrib = (PFNGLGETACTIVEATTRIBPROC)load("glGetActiveAttrib"); + glad_glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC)load("glGetActiveUniform"); + glad_glGetAttachedShaders = (PFNGLGETATTACHEDSHADERSPROC)load("glGetAttachedShaders"); + glad_glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)load("glGetAttribLocation"); + glad_glGetProgramiv = (PFNGLGETPROGRAMIVPROC)load("glGetProgramiv"); + glad_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)load("glGetProgramInfoLog"); + glad_glGetShaderiv = (PFNGLGETSHADERIVPROC)load("glGetShaderiv"); + glad_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)load("glGetShaderInfoLog"); + glad_glGetShaderSource = (PFNGLGETSHADERSOURCEPROC)load("glGetShaderSource"); + glad_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)load("glGetUniformLocation"); + glad_glGetUniformfv = (PFNGLGETUNIFORMFVPROC)load("glGetUniformfv"); + glad_glGetUniformiv = (PFNGLGETUNIFORMIVPROC)load("glGetUniformiv"); + glad_glGetVertexAttribdv = (PFNGLGETVERTEXATTRIBDVPROC)load("glGetVertexAttribdv"); + glad_glGetVertexAttribfv = (PFNGLGETVERTEXATTRIBFVPROC)load("glGetVertexAttribfv"); + glad_glGetVertexAttribiv = (PFNGLGETVERTEXATTRIBIVPROC)load("glGetVertexAttribiv"); + glad_glGetVertexAttribPointerv = (PFNGLGETVERTEXATTRIBPOINTERVPROC)load("glGetVertexAttribPointerv"); + glad_glIsProgram = (PFNGLISPROGRAMPROC)load("glIsProgram"); + glad_glIsShader = (PFNGLISSHADERPROC)load("glIsShader"); + glad_glLinkProgram = (PFNGLLINKPROGRAMPROC)load("glLinkProgram"); + glad_glShaderSource = (PFNGLSHADERSOURCEPROC)load("glShaderSource"); + glad_glUseProgram = (PFNGLUSEPROGRAMPROC)load("glUseProgram"); + glad_glUniform1f = (PFNGLUNIFORM1FPROC)load("glUniform1f"); + glad_glUniform2f = (PFNGLUNIFORM2FPROC)load("glUniform2f"); + glad_glUniform3f = (PFNGLUNIFORM3FPROC)load("glUniform3f"); + glad_glUniform4f = (PFNGLUNIFORM4FPROC)load("glUniform4f"); + glad_glUniform1i = (PFNGLUNIFORM1IPROC)load("glUniform1i"); + glad_glUniform2i = (PFNGLUNIFORM2IPROC)load("glUniform2i"); + glad_glUniform3i = (PFNGLUNIFORM3IPROC)load("glUniform3i"); + glad_glUniform4i = (PFNGLUNIFORM4IPROC)load("glUniform4i"); + glad_glUniform1fv = (PFNGLUNIFORM1FVPROC)load("glUniform1fv"); + glad_glUniform2fv = (PFNGLUNIFORM2FVPROC)load("glUniform2fv"); + glad_glUniform3fv = (PFNGLUNIFORM3FVPROC)load("glUniform3fv"); + glad_glUniform4fv = (PFNGLUNIFORM4FVPROC)load("glUniform4fv"); + glad_glUniform1iv = (PFNGLUNIFORM1IVPROC)load("glUniform1iv"); + glad_glUniform2iv = (PFNGLUNIFORM2IVPROC)load("glUniform2iv"); + glad_glUniform3iv = (PFNGLUNIFORM3IVPROC)load("glUniform3iv"); + glad_glUniform4iv = (PFNGLUNIFORM4IVPROC)load("glUniform4iv"); + glad_glUniformMatrix2fv = (PFNGLUNIFORMMATRIX2FVPROC)load("glUniformMatrix2fv"); + glad_glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)load("glUniformMatrix3fv"); + glad_glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)load("glUniformMatrix4fv"); + glad_glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)load("glValidateProgram"); + glad_glVertexAttrib1d = (PFNGLVERTEXATTRIB1DPROC)load("glVertexAttrib1d"); + glad_glVertexAttrib1dv = (PFNGLVERTEXATTRIB1DVPROC)load("glVertexAttrib1dv"); + glad_glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC)load("glVertexAttrib1f"); + glad_glVertexAttrib1fv = (PFNGLVERTEXATTRIB1FVPROC)load("glVertexAttrib1fv"); + glad_glVertexAttrib1s = (PFNGLVERTEXATTRIB1SPROC)load("glVertexAttrib1s"); + glad_glVertexAttrib1sv = (PFNGLVERTEXATTRIB1SVPROC)load("glVertexAttrib1sv"); + glad_glVertexAttrib2d = (PFNGLVERTEXATTRIB2DPROC)load("glVertexAttrib2d"); + glad_glVertexAttrib2dv = (PFNGLVERTEXATTRIB2DVPROC)load("glVertexAttrib2dv"); + glad_glVertexAttrib2f = (PFNGLVERTEXATTRIB2FPROC)load("glVertexAttrib2f"); + glad_glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC)load("glVertexAttrib2fv"); + glad_glVertexAttrib2s = (PFNGLVERTEXATTRIB2SPROC)load("glVertexAttrib2s"); + glad_glVertexAttrib2sv = (PFNGLVERTEXATTRIB2SVPROC)load("glVertexAttrib2sv"); + glad_glVertexAttrib3d = (PFNGLVERTEXATTRIB3DPROC)load("glVertexAttrib3d"); + glad_glVertexAttrib3dv = (PFNGLVERTEXATTRIB3DVPROC)load("glVertexAttrib3dv"); + glad_glVertexAttrib3f = (PFNGLVERTEXATTRIB3FPROC)load("glVertexAttrib3f"); + glad_glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC)load("glVertexAttrib3fv"); + glad_glVertexAttrib3s = (PFNGLVERTEXATTRIB3SPROC)load("glVertexAttrib3s"); + glad_glVertexAttrib3sv = (PFNGLVERTEXATTRIB3SVPROC)load("glVertexAttrib3sv"); + glad_glVertexAttrib4Nbv = (PFNGLVERTEXATTRIB4NBVPROC)load("glVertexAttrib4Nbv"); + glad_glVertexAttrib4Niv = (PFNGLVERTEXATTRIB4NIVPROC)load("glVertexAttrib4Niv"); + glad_glVertexAttrib4Nsv = (PFNGLVERTEXATTRIB4NSVPROC)load("glVertexAttrib4Nsv"); + glad_glVertexAttrib4Nub = (PFNGLVERTEXATTRIB4NUBPROC)load("glVertexAttrib4Nub"); + glad_glVertexAttrib4Nubv = (PFNGLVERTEXATTRIB4NUBVPROC)load("glVertexAttrib4Nubv"); + glad_glVertexAttrib4Nuiv = (PFNGLVERTEXATTRIB4NUIVPROC)load("glVertexAttrib4Nuiv"); + glad_glVertexAttrib4Nusv = (PFNGLVERTEXATTRIB4NUSVPROC)load("glVertexAttrib4Nusv"); + glad_glVertexAttrib4bv = (PFNGLVERTEXATTRIB4BVPROC)load("glVertexAttrib4bv"); + glad_glVertexAttrib4d = (PFNGLVERTEXATTRIB4DPROC)load("glVertexAttrib4d"); + glad_glVertexAttrib4dv = (PFNGLVERTEXATTRIB4DVPROC)load("glVertexAttrib4dv"); + glad_glVertexAttrib4f = (PFNGLVERTEXATTRIB4FPROC)load("glVertexAttrib4f"); + glad_glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC)load("glVertexAttrib4fv"); + glad_glVertexAttrib4iv = (PFNGLVERTEXATTRIB4IVPROC)load("glVertexAttrib4iv"); + glad_glVertexAttrib4s = (PFNGLVERTEXATTRIB4SPROC)load("glVertexAttrib4s"); + glad_glVertexAttrib4sv = (PFNGLVERTEXATTRIB4SVPROC)load("glVertexAttrib4sv"); + glad_glVertexAttrib4ubv = (PFNGLVERTEXATTRIB4UBVPROC)load("glVertexAttrib4ubv"); + glad_glVertexAttrib4uiv = (PFNGLVERTEXATTRIB4UIVPROC)load("glVertexAttrib4uiv"); + glad_glVertexAttrib4usv = (PFNGLVERTEXATTRIB4USVPROC)load("glVertexAttrib4usv"); + glad_glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)load("glVertexAttribPointer"); +} +static void load_GL_VERSION_2_1(GLADloadproc load) { + if(!GLAD_GL_VERSION_2_1) return; + glad_glUniformMatrix2x3fv = (PFNGLUNIFORMMATRIX2X3FVPROC)load("glUniformMatrix2x3fv"); + glad_glUniformMatrix3x2fv = (PFNGLUNIFORMMATRIX3X2FVPROC)load("glUniformMatrix3x2fv"); + glad_glUniformMatrix2x4fv = (PFNGLUNIFORMMATRIX2X4FVPROC)load("glUniformMatrix2x4fv"); + glad_glUniformMatrix4x2fv = (PFNGLUNIFORMMATRIX4X2FVPROC)load("glUniformMatrix4x2fv"); + glad_glUniformMatrix3x4fv = (PFNGLUNIFORMMATRIX3X4FVPROC)load("glUniformMatrix3x4fv"); + glad_glUniformMatrix4x3fv = (PFNGLUNIFORMMATRIX4X3FVPROC)load("glUniformMatrix4x3fv"); +} +static void load_GL_VERSION_3_0(GLADloadproc load) { + if(!GLAD_GL_VERSION_3_0) return; + glad_glColorMaski = (PFNGLCOLORMASKIPROC)load("glColorMaski"); + glad_glGetBooleani_v = (PFNGLGETBOOLEANI_VPROC)load("glGetBooleani_v"); + glad_glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC)load("glGetIntegeri_v"); + glad_glEnablei = (PFNGLENABLEIPROC)load("glEnablei"); + glad_glDisablei = (PFNGLDISABLEIPROC)load("glDisablei"); + glad_glIsEnabledi = (PFNGLISENABLEDIPROC)load("glIsEnabledi"); + glad_glBeginTransformFeedback = (PFNGLBEGINTRANSFORMFEEDBACKPROC)load("glBeginTransformFeedback"); + glad_glEndTransformFeedback = (PFNGLENDTRANSFORMFEEDBACKPROC)load("glEndTransformFeedback"); + glad_glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC)load("glBindBufferRange"); + glad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)load("glBindBufferBase"); + glad_glTransformFeedbackVaryings = (PFNGLTRANSFORMFEEDBACKVARYINGSPROC)load("glTransformFeedbackVaryings"); + glad_glGetTransformFeedbackVarying = (PFNGLGETTRANSFORMFEEDBACKVARYINGPROC)load("glGetTransformFeedbackVarying"); + glad_glClampColor = (PFNGLCLAMPCOLORPROC)load("glClampColor"); + glad_glBeginConditionalRender = (PFNGLBEGINCONDITIONALRENDERPROC)load("glBeginConditionalRender"); + glad_glEndConditionalRender = (PFNGLENDCONDITIONALRENDERPROC)load("glEndConditionalRender"); + glad_glVertexAttribIPointer = (PFNGLVERTEXATTRIBIPOINTERPROC)load("glVertexAttribIPointer"); + glad_glGetVertexAttribIiv = (PFNGLGETVERTEXATTRIBIIVPROC)load("glGetVertexAttribIiv"); + glad_glGetVertexAttribIuiv = (PFNGLGETVERTEXATTRIBIUIVPROC)load("glGetVertexAttribIuiv"); + glad_glVertexAttribI1i = (PFNGLVERTEXATTRIBI1IPROC)load("glVertexAttribI1i"); + glad_glVertexAttribI2i = (PFNGLVERTEXATTRIBI2IPROC)load("glVertexAttribI2i"); + glad_glVertexAttribI3i = (PFNGLVERTEXATTRIBI3IPROC)load("glVertexAttribI3i"); + glad_glVertexAttribI4i = (PFNGLVERTEXATTRIBI4IPROC)load("glVertexAttribI4i"); + glad_glVertexAttribI1ui = (PFNGLVERTEXATTRIBI1UIPROC)load("glVertexAttribI1ui"); + glad_glVertexAttribI2ui = (PFNGLVERTEXATTRIBI2UIPROC)load("glVertexAttribI2ui"); + glad_glVertexAttribI3ui = (PFNGLVERTEXATTRIBI3UIPROC)load("glVertexAttribI3ui"); + glad_glVertexAttribI4ui = (PFNGLVERTEXATTRIBI4UIPROC)load("glVertexAttribI4ui"); + glad_glVertexAttribI1iv = (PFNGLVERTEXATTRIBI1IVPROC)load("glVertexAttribI1iv"); + glad_glVertexAttribI2iv = (PFNGLVERTEXATTRIBI2IVPROC)load("glVertexAttribI2iv"); + glad_glVertexAttribI3iv = (PFNGLVERTEXATTRIBI3IVPROC)load("glVertexAttribI3iv"); + glad_glVertexAttribI4iv = (PFNGLVERTEXATTRIBI4IVPROC)load("glVertexAttribI4iv"); + glad_glVertexAttribI1uiv = (PFNGLVERTEXATTRIBI1UIVPROC)load("glVertexAttribI1uiv"); + glad_glVertexAttribI2uiv = (PFNGLVERTEXATTRIBI2UIVPROC)load("glVertexAttribI2uiv"); + glad_glVertexAttribI3uiv = (PFNGLVERTEXATTRIBI3UIVPROC)load("glVertexAttribI3uiv"); + glad_glVertexAttribI4uiv = (PFNGLVERTEXATTRIBI4UIVPROC)load("glVertexAttribI4uiv"); + glad_glVertexAttribI4bv = (PFNGLVERTEXATTRIBI4BVPROC)load("glVertexAttribI4bv"); + glad_glVertexAttribI4sv = (PFNGLVERTEXATTRIBI4SVPROC)load("glVertexAttribI4sv"); + glad_glVertexAttribI4ubv = (PFNGLVERTEXATTRIBI4UBVPROC)load("glVertexAttribI4ubv"); + glad_glVertexAttribI4usv = (PFNGLVERTEXATTRIBI4USVPROC)load("glVertexAttribI4usv"); + glad_glGetUniformuiv = (PFNGLGETUNIFORMUIVPROC)load("glGetUniformuiv"); + glad_glBindFragDataLocation = (PFNGLBINDFRAGDATALOCATIONPROC)load("glBindFragDataLocation"); + glad_glGetFragDataLocation = (PFNGLGETFRAGDATALOCATIONPROC)load("glGetFragDataLocation"); + glad_glUniform1ui = (PFNGLUNIFORM1UIPROC)load("glUniform1ui"); + glad_glUniform2ui = (PFNGLUNIFORM2UIPROC)load("glUniform2ui"); + glad_glUniform3ui = (PFNGLUNIFORM3UIPROC)load("glUniform3ui"); + glad_glUniform4ui = (PFNGLUNIFORM4UIPROC)load("glUniform4ui"); + glad_glUniform1uiv = (PFNGLUNIFORM1UIVPROC)load("glUniform1uiv"); + glad_glUniform2uiv = (PFNGLUNIFORM2UIVPROC)load("glUniform2uiv"); + glad_glUniform3uiv = (PFNGLUNIFORM3UIVPROC)load("glUniform3uiv"); + glad_glUniform4uiv = (PFNGLUNIFORM4UIVPROC)load("glUniform4uiv"); + glad_glTexParameterIiv = (PFNGLTEXPARAMETERIIVPROC)load("glTexParameterIiv"); + glad_glTexParameterIuiv = (PFNGLTEXPARAMETERIUIVPROC)load("glTexParameterIuiv"); + glad_glGetTexParameterIiv = (PFNGLGETTEXPARAMETERIIVPROC)load("glGetTexParameterIiv"); + glad_glGetTexParameterIuiv = (PFNGLGETTEXPARAMETERIUIVPROC)load("glGetTexParameterIuiv"); + glad_glClearBufferiv = (PFNGLCLEARBUFFERIVPROC)load("glClearBufferiv"); + glad_glClearBufferuiv = (PFNGLCLEARBUFFERUIVPROC)load("glClearBufferuiv"); + glad_glClearBufferfv = (PFNGLCLEARBUFFERFVPROC)load("glClearBufferfv"); + glad_glClearBufferfi = (PFNGLCLEARBUFFERFIPROC)load("glClearBufferfi"); + glad_glGetStringi = (PFNGLGETSTRINGIPROC)load("glGetStringi"); + glad_glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)load("glIsRenderbuffer"); + glad_glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)load("glBindRenderbuffer"); + glad_glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)load("glDeleteRenderbuffers"); + glad_glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)load("glGenRenderbuffers"); + glad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)load("glRenderbufferStorage"); + glad_glGetRenderbufferParameteriv = (PFNGLGETRENDERBUFFERPARAMETERIVPROC)load("glGetRenderbufferParameteriv"); + glad_glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)load("glIsFramebuffer"); + glad_glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)load("glBindFramebuffer"); + glad_glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)load("glDeleteFramebuffers"); + glad_glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)load("glGenFramebuffers"); + glad_glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)load("glCheckFramebufferStatus"); + glad_glFramebufferTexture1D = (PFNGLFRAMEBUFFERTEXTURE1DPROC)load("glFramebufferTexture1D"); + glad_glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)load("glFramebufferTexture2D"); + glad_glFramebufferTexture3D = (PFNGLFRAMEBUFFERTEXTURE3DPROC)load("glFramebufferTexture3D"); + glad_glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)load("glFramebufferRenderbuffer"); + glad_glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)load("glGetFramebufferAttachmentParameteriv"); + glad_glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)load("glGenerateMipmap"); + glad_glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)load("glBlitFramebuffer"); + glad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)load("glRenderbufferStorageMultisample"); + glad_glFramebufferTextureLayer = (PFNGLFRAMEBUFFERTEXTURELAYERPROC)load("glFramebufferTextureLayer"); + glad_glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC)load("glMapBufferRange"); + glad_glFlushMappedBufferRange = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC)load("glFlushMappedBufferRange"); + glad_glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)load("glBindVertexArray"); + glad_glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)load("glDeleteVertexArrays"); + glad_glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)load("glGenVertexArrays"); + glad_glIsVertexArray = (PFNGLISVERTEXARRAYPROC)load("glIsVertexArray"); +} +static void load_GL_VERSION_3_1(GLADloadproc load) { + if(!GLAD_GL_VERSION_3_1) return; + glad_glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDPROC)load("glDrawArraysInstanced"); + glad_glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDPROC)load("glDrawElementsInstanced"); + glad_glTexBuffer = (PFNGLTEXBUFFERPROC)load("glTexBuffer"); + glad_glPrimitiveRestartIndex = (PFNGLPRIMITIVERESTARTINDEXPROC)load("glPrimitiveRestartIndex"); + glad_glCopyBufferSubData = (PFNGLCOPYBUFFERSUBDATAPROC)load("glCopyBufferSubData"); + glad_glGetUniformIndices = (PFNGLGETUNIFORMINDICESPROC)load("glGetUniformIndices"); + glad_glGetActiveUniformsiv = (PFNGLGETACTIVEUNIFORMSIVPROC)load("glGetActiveUniformsiv"); + glad_glGetActiveUniformName = (PFNGLGETACTIVEUNIFORMNAMEPROC)load("glGetActiveUniformName"); + glad_glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC)load("glGetUniformBlockIndex"); + glad_glGetActiveUniformBlockiv = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC)load("glGetActiveUniformBlockiv"); + glad_glGetActiveUniformBlockName = (PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)load("glGetActiveUniformBlockName"); + glad_glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC)load("glUniformBlockBinding"); + glad_glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC)load("glBindBufferRange"); + glad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)load("glBindBufferBase"); + glad_glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC)load("glGetIntegeri_v"); +} +static void load_GL_VERSION_3_2(GLADloadproc load) { + if(!GLAD_GL_VERSION_3_2) return; + glad_glDrawElementsBaseVertex = (PFNGLDRAWELEMENTSBASEVERTEXPROC)load("glDrawElementsBaseVertex"); + glad_glDrawRangeElementsBaseVertex = (PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC)load("glDrawRangeElementsBaseVertex"); + glad_glDrawElementsInstancedBaseVertex = (PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC)load("glDrawElementsInstancedBaseVertex"); + glad_glMultiDrawElementsBaseVertex = (PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC)load("glMultiDrawElementsBaseVertex"); + glad_glProvokingVertex = (PFNGLPROVOKINGVERTEXPROC)load("glProvokingVertex"); + glad_glFenceSync = (PFNGLFENCESYNCPROC)load("glFenceSync"); + glad_glIsSync = (PFNGLISSYNCPROC)load("glIsSync"); + glad_glDeleteSync = (PFNGLDELETESYNCPROC)load("glDeleteSync"); + glad_glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)load("glClientWaitSync"); + glad_glWaitSync = (PFNGLWAITSYNCPROC)load("glWaitSync"); + glad_glGetInteger64v = (PFNGLGETINTEGER64VPROC)load("glGetInteger64v"); + glad_glGetSynciv = (PFNGLGETSYNCIVPROC)load("glGetSynciv"); + glad_glGetInteger64i_v = (PFNGLGETINTEGER64I_VPROC)load("glGetInteger64i_v"); + glad_glGetBufferParameteri64v = (PFNGLGETBUFFERPARAMETERI64VPROC)load("glGetBufferParameteri64v"); + glad_glFramebufferTexture = (PFNGLFRAMEBUFFERTEXTUREPROC)load("glFramebufferTexture"); + glad_glTexImage2DMultisample = (PFNGLTEXIMAGE2DMULTISAMPLEPROC)load("glTexImage2DMultisample"); + glad_glTexImage3DMultisample = (PFNGLTEXIMAGE3DMULTISAMPLEPROC)load("glTexImage3DMultisample"); + glad_glGetMultisamplefv = (PFNGLGETMULTISAMPLEFVPROC)load("glGetMultisamplefv"); + glad_glSampleMaski = (PFNGLSAMPLEMASKIPROC)load("glSampleMaski"); +} +static void load_GL_VERSION_3_3(GLADloadproc load) { + if(!GLAD_GL_VERSION_3_3) return; + glad_glBindFragDataLocationIndexed = (PFNGLBINDFRAGDATALOCATIONINDEXEDPROC)load("glBindFragDataLocationIndexed"); + glad_glGetFragDataIndex = (PFNGLGETFRAGDATAINDEXPROC)load("glGetFragDataIndex"); + glad_glGenSamplers = (PFNGLGENSAMPLERSPROC)load("glGenSamplers"); + glad_glDeleteSamplers = (PFNGLDELETESAMPLERSPROC)load("glDeleteSamplers"); + glad_glIsSampler = (PFNGLISSAMPLERPROC)load("glIsSampler"); + glad_glBindSampler = (PFNGLBINDSAMPLERPROC)load("glBindSampler"); + glad_glSamplerParameteri = (PFNGLSAMPLERPARAMETERIPROC)load("glSamplerParameteri"); + glad_glSamplerParameteriv = (PFNGLSAMPLERPARAMETERIVPROC)load("glSamplerParameteriv"); + glad_glSamplerParameterf = (PFNGLSAMPLERPARAMETERFPROC)load("glSamplerParameterf"); + glad_glSamplerParameterfv = (PFNGLSAMPLERPARAMETERFVPROC)load("glSamplerParameterfv"); + glad_glSamplerParameterIiv = (PFNGLSAMPLERPARAMETERIIVPROC)load("glSamplerParameterIiv"); + glad_glSamplerParameterIuiv = (PFNGLSAMPLERPARAMETERIUIVPROC)load("glSamplerParameterIuiv"); + glad_glGetSamplerParameteriv = (PFNGLGETSAMPLERPARAMETERIVPROC)load("glGetSamplerParameteriv"); + glad_glGetSamplerParameterIiv = (PFNGLGETSAMPLERPARAMETERIIVPROC)load("glGetSamplerParameterIiv"); + glad_glGetSamplerParameterfv = (PFNGLGETSAMPLERPARAMETERFVPROC)load("glGetSamplerParameterfv"); + glad_glGetSamplerParameterIuiv = (PFNGLGETSAMPLERPARAMETERIUIVPROC)load("glGetSamplerParameterIuiv"); + glad_glQueryCounter = (PFNGLQUERYCOUNTERPROC)load("glQueryCounter"); + glad_glGetQueryObjecti64v = (PFNGLGETQUERYOBJECTI64VPROC)load("glGetQueryObjecti64v"); + glad_glGetQueryObjectui64v = (PFNGLGETQUERYOBJECTUI64VPROC)load("glGetQueryObjectui64v"); + glad_glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISORPROC)load("glVertexAttribDivisor"); + glad_glVertexAttribP1ui = (PFNGLVERTEXATTRIBP1UIPROC)load("glVertexAttribP1ui"); + glad_glVertexAttribP1uiv = (PFNGLVERTEXATTRIBP1UIVPROC)load("glVertexAttribP1uiv"); + glad_glVertexAttribP2ui = (PFNGLVERTEXATTRIBP2UIPROC)load("glVertexAttribP2ui"); + glad_glVertexAttribP2uiv = (PFNGLVERTEXATTRIBP2UIVPROC)load("glVertexAttribP2uiv"); + glad_glVertexAttribP3ui = (PFNGLVERTEXATTRIBP3UIPROC)load("glVertexAttribP3ui"); + glad_glVertexAttribP3uiv = (PFNGLVERTEXATTRIBP3UIVPROC)load("glVertexAttribP3uiv"); + glad_glVertexAttribP4ui = (PFNGLVERTEXATTRIBP4UIPROC)load("glVertexAttribP4ui"); + glad_glVertexAttribP4uiv = (PFNGLVERTEXATTRIBP4UIVPROC)load("glVertexAttribP4uiv"); + glad_glVertexP2ui = (PFNGLVERTEXP2UIPROC)load("glVertexP2ui"); + glad_glVertexP2uiv = (PFNGLVERTEXP2UIVPROC)load("glVertexP2uiv"); + glad_glVertexP3ui = (PFNGLVERTEXP3UIPROC)load("glVertexP3ui"); + glad_glVertexP3uiv = (PFNGLVERTEXP3UIVPROC)load("glVertexP3uiv"); + glad_glVertexP4ui = (PFNGLVERTEXP4UIPROC)load("glVertexP4ui"); + glad_glVertexP4uiv = (PFNGLVERTEXP4UIVPROC)load("glVertexP4uiv"); + glad_glTexCoordP1ui = (PFNGLTEXCOORDP1UIPROC)load("glTexCoordP1ui"); + glad_glTexCoordP1uiv = (PFNGLTEXCOORDP1UIVPROC)load("glTexCoordP1uiv"); + glad_glTexCoordP2ui = (PFNGLTEXCOORDP2UIPROC)load("glTexCoordP2ui"); + glad_glTexCoordP2uiv = (PFNGLTEXCOORDP2UIVPROC)load("glTexCoordP2uiv"); + glad_glTexCoordP3ui = (PFNGLTEXCOORDP3UIPROC)load("glTexCoordP3ui"); + glad_glTexCoordP3uiv = (PFNGLTEXCOORDP3UIVPROC)load("glTexCoordP3uiv"); + glad_glTexCoordP4ui = (PFNGLTEXCOORDP4UIPROC)load("glTexCoordP4ui"); + glad_glTexCoordP4uiv = (PFNGLTEXCOORDP4UIVPROC)load("glTexCoordP4uiv"); + glad_glMultiTexCoordP1ui = (PFNGLMULTITEXCOORDP1UIPROC)load("glMultiTexCoordP1ui"); + glad_glMultiTexCoordP1uiv = (PFNGLMULTITEXCOORDP1UIVPROC)load("glMultiTexCoordP1uiv"); + glad_glMultiTexCoordP2ui = (PFNGLMULTITEXCOORDP2UIPROC)load("glMultiTexCoordP2ui"); + glad_glMultiTexCoordP2uiv = (PFNGLMULTITEXCOORDP2UIVPROC)load("glMultiTexCoordP2uiv"); + glad_glMultiTexCoordP3ui = (PFNGLMULTITEXCOORDP3UIPROC)load("glMultiTexCoordP3ui"); + glad_glMultiTexCoordP3uiv = (PFNGLMULTITEXCOORDP3UIVPROC)load("glMultiTexCoordP3uiv"); + glad_glMultiTexCoordP4ui = (PFNGLMULTITEXCOORDP4UIPROC)load("glMultiTexCoordP4ui"); + glad_glMultiTexCoordP4uiv = (PFNGLMULTITEXCOORDP4UIVPROC)load("glMultiTexCoordP4uiv"); + glad_glNormalP3ui = (PFNGLNORMALP3UIPROC)load("glNormalP3ui"); + glad_glNormalP3uiv = (PFNGLNORMALP3UIVPROC)load("glNormalP3uiv"); + glad_glColorP3ui = (PFNGLCOLORP3UIPROC)load("glColorP3ui"); + glad_glColorP3uiv = (PFNGLCOLORP3UIVPROC)load("glColorP3uiv"); + glad_glColorP4ui = (PFNGLCOLORP4UIPROC)load("glColorP4ui"); + glad_glColorP4uiv = (PFNGLCOLORP4UIVPROC)load("glColorP4uiv"); + glad_glSecondaryColorP3ui = (PFNGLSECONDARYCOLORP3UIPROC)load("glSecondaryColorP3ui"); + glad_glSecondaryColorP3uiv = (PFNGLSECONDARYCOLORP3UIVPROC)load("glSecondaryColorP3uiv"); +} +static void load_GL_EXT_framebuffer_object(GLADloadproc load) { + if(!GLAD_GL_EXT_framebuffer_object || GLAD_GL_VERSION_3_0) return; + glad_glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)load("glIsRenderbufferEXT"); + glad_glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)load("glBindRenderbufferEXT"); + glad_glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)load("glDeleteRenderbuffersEXT"); + glad_glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)load("glGenRenderbuffersEXT"); + glad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)load("glRenderbufferStorageEXT"); + glad_glGetRenderbufferParameteriv = (PFNGLGETRENDERBUFFERPARAMETERIVPROC)load("glGetRenderbufferParameterivEXT"); + glad_glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)load("glIsFramebufferEXT"); + glad_glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)load("glBindFramebufferEXT"); + glad_glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)load("glDeleteFramebuffersEXT"); + glad_glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)load("glGenFramebuffersEXT"); + glad_glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)load("glCheckFramebufferStatusEXT"); + glad_glFramebufferTexture1D = (PFNGLFRAMEBUFFERTEXTURE1DPROC)load("glFramebufferTexture1DEXT"); + glad_glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)load("glFramebufferTexture2DEXT"); + glad_glFramebufferTexture3D = (PFNGLFRAMEBUFFERTEXTURE3DPROC)load("glFramebufferTexture3DEXT"); + glad_glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)load("glFramebufferRenderbufferEXT"); + glad_glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)load("glGetFramebufferAttachmentParameterivEXT"); + glad_glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)load("glGenerateMipmapEXT"); +} +static void load_GL_KHR_debug(GLADloadproc load) { + if(!GLAD_GL_KHR_debug) return; + glad_glDebugMessageControl = (PFNGLDEBUGMESSAGECONTROLPROC)load("glDebugMessageControl"); + glad_glDebugMessageInsert = (PFNGLDEBUGMESSAGEINSERTPROC)load("glDebugMessageInsert"); + glad_glDebugMessageCallback = (PFNGLDEBUGMESSAGECALLBACKPROC)load("glDebugMessageCallback"); + glad_glGetDebugMessageLog = (PFNGLGETDEBUGMESSAGELOGPROC)load("glGetDebugMessageLog"); + glad_glPushDebugGroup = (PFNGLPUSHDEBUGGROUPPROC)load("glPushDebugGroup"); + glad_glPopDebugGroup = (PFNGLPOPDEBUGGROUPPROC)load("glPopDebugGroup"); + glad_glObjectLabel = (PFNGLOBJECTLABELPROC)load("glObjectLabel"); + glad_glGetObjectLabel = (PFNGLGETOBJECTLABELPROC)load("glGetObjectLabel"); + glad_glObjectPtrLabel = (PFNGLOBJECTPTRLABELPROC)load("glObjectPtrLabel"); + glad_glGetObjectPtrLabel = (PFNGLGETOBJECTPTRLABELPROC)load("glGetObjectPtrLabel"); + glad_glGetPointerv = (PFNGLGETPOINTERVPROC)load("glGetPointerv"); + glad_glDebugMessageControlKHR = (PFNGLDEBUGMESSAGECONTROLKHRPROC)load("glDebugMessageControlKHR"); + glad_glDebugMessageInsertKHR = (PFNGLDEBUGMESSAGEINSERTKHRPROC)load("glDebugMessageInsertKHR"); + glad_glDebugMessageCallbackKHR = (PFNGLDEBUGMESSAGECALLBACKKHRPROC)load("glDebugMessageCallbackKHR"); + glad_glGetDebugMessageLogKHR = (PFNGLGETDEBUGMESSAGELOGKHRPROC)load("glGetDebugMessageLogKHR"); + glad_glPushDebugGroupKHR = (PFNGLPUSHDEBUGGROUPKHRPROC)load("glPushDebugGroupKHR"); + glad_glPopDebugGroupKHR = (PFNGLPOPDEBUGGROUPKHRPROC)load("glPopDebugGroupKHR"); + glad_glObjectLabelKHR = (PFNGLOBJECTLABELKHRPROC)load("glObjectLabelKHR"); + glad_glGetObjectLabelKHR = (PFNGLGETOBJECTLABELKHRPROC)load("glGetObjectLabelKHR"); + glad_glObjectPtrLabelKHR = (PFNGLOBJECTPTRLABELKHRPROC)load("glObjectPtrLabelKHR"); + glad_glGetObjectPtrLabelKHR = (PFNGLGETOBJECTPTRLABELKHRPROC)load("glGetObjectPtrLabelKHR"); + glad_glGetPointervKHR = (PFNGLGETPOINTERVKHRPROC)load("glGetPointervKHR"); +} +static int find_extensionsGL(void) { + if (!get_exts()) return 0; + GLAD_GL_EXT_framebuffer_object = has_ext("GL_EXT_framebuffer_object"); + GLAD_GL_EXT_texture_compression_s3tc = has_ext("GL_EXT_texture_compression_s3tc"); + GLAD_GL_EXT_texture_filter_anisotropic = has_ext("GL_EXT_texture_filter_anisotropic"); + GLAD_GL_KHR_debug = has_ext("GL_KHR_debug"); + GLAD_GL_KHR_texture_compression_astc_ldr = has_ext("GL_KHR_texture_compression_astc_ldr"); + free_exts(); + return 1; +} + +static void find_coreGL(void) { + + /* Thank you @elmindreda + * https://github.com/elmindreda/greg/blob/master/templates/greg.c.in#L176 + * https://github.com/glfw/glfw/blob/master/src/context.c#L36 + */ + int i, major, minor; + + const char* version; + const char* prefixes[] = { + "OpenGL ES-CM ", + "OpenGL ES-CL ", + "OpenGL ES ", + NULL + }; + + version = (const char*) glGetString(GL_VERSION); + if (!version) return; + + for (i = 0; prefixes[i]; i++) { + const size_t length = strlen(prefixes[i]); + if (strncmp(version, prefixes[i], length) == 0) { + version += length; + break; + } + } + +/* PR #18 */ +#ifdef _MSC_VER + sscanf_s(version, "%d.%d", &major, &minor); +#else + sscanf(version, "%d.%d", &major, &minor); +#endif + + if(major > requested_major_version) { + major = requested_major_version; + } + + if(major == requested_major_version && minor > requested_minor_version) { + minor = requested_minor_version; + } + + GLVersion.major = major; GLVersion.minor = minor; + max_loaded_major = major; max_loaded_minor = minor; + GLAD_GL_VERSION_1_0 = (major == 1 && minor >= 0) || major > 1; + GLAD_GL_VERSION_1_1 = (major == 1 && minor >= 1) || major > 1; + GLAD_GL_VERSION_1_2 = (major == 1 && minor >= 2) || major > 1; + GLAD_GL_VERSION_1_3 = (major == 1 && minor >= 3) || major > 1; + GLAD_GL_VERSION_1_4 = (major == 1 && minor >= 4) || major > 1; + GLAD_GL_VERSION_1_5 = (major == 1 && minor >= 5) || major > 1; + GLAD_GL_VERSION_2_0 = (major == 2 && minor >= 0) || major > 2; + GLAD_GL_VERSION_2_1 = (major == 2 && minor >= 1) || major > 2; + GLAD_GL_VERSION_3_0 = (major == 3 && minor >= 0) || major > 3; + GLAD_GL_VERSION_3_1 = (major == 3 && minor >= 1) || major > 3; + GLAD_GL_VERSION_3_2 = (major == 3 && minor >= 2) || major > 3; + GLAD_GL_VERSION_3_3 = (major == 3 && minor >= 3) || major > 3; + if (GLVersion.major > 3 || (GLVersion.major >= 3 && GLVersion.minor >= 3)) { + max_loaded_major = 3; + max_loaded_minor = 3; + } +} + +int gladLoadGLLoader(GLADloadproc load, int requestedVersion) { + requested_major_version = requestedVersion / 10; + requested_minor_version = requestedVersion % 10; + GLVersion.major = 0; GLVersion.minor = 0; + glGetString = (PFNGLGETSTRINGPROC)load("glGetString"); + if(glGetString == NULL) return 0; + if(glGetString(GL_VERSION) == NULL) return 0; + find_coreGL(); + load_GL_VERSION_1_0(load); + load_GL_VERSION_1_1(load); + load_GL_VERSION_1_2(load); + load_GL_VERSION_1_3(load); + load_GL_VERSION_1_4(load); + load_GL_VERSION_1_5(load); + load_GL_VERSION_2_0(load); + load_GL_VERSION_2_1(load); + load_GL_VERSION_3_0(load); + load_GL_VERSION_3_1(load); + load_GL_VERSION_3_2(load); + load_GL_VERSION_3_3(load); + + if (!find_extensionsGL()) return 0; + load_GL_EXT_framebuffer_object(load); + load_GL_KHR_debug(load); + return GLVersion.major != 0 || GLVersion.minor != 0; +} + +static void load_GL_ES_VERSION_2_0(GLADloadproc load) { + if(!GLAD_GL_ES_VERSION_2_0) return; + glad_glActiveTexture = (PFNGLACTIVETEXTUREPROC)load("glActiveTexture"); + glad_glAttachShader = (PFNGLATTACHSHADERPROC)load("glAttachShader"); + glad_glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC)load("glBindAttribLocation"); + glad_glBindBuffer = (PFNGLBINDBUFFERPROC)load("glBindBuffer"); + glad_glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)load("glBindFramebuffer"); + glad_glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)load("glBindRenderbuffer"); + glad_glBindTexture = (PFNGLBINDTEXTUREPROC)load("glBindTexture"); + glad_glBlendColor = (PFNGLBLENDCOLORPROC)load("glBlendColor"); + glad_glBlendEquation = (PFNGLBLENDEQUATIONPROC)load("glBlendEquation"); + glad_glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC)load("glBlendEquationSeparate"); + glad_glBlendFunc = (PFNGLBLENDFUNCPROC)load("glBlendFunc"); + glad_glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)load("glBlendFuncSeparate"); + glad_glBufferData = (PFNGLBUFFERDATAPROC)load("glBufferData"); + glad_glBufferSubData = (PFNGLBUFFERSUBDATAPROC)load("glBufferSubData"); + glad_glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)load("glCheckFramebufferStatus"); + glad_glClear = (PFNGLCLEARPROC)load("glClear"); + glad_glClearColor = (PFNGLCLEARCOLORPROC)load("glClearColor"); + glad_glClearDepthf = (PFNGLCLEARDEPTHFPROC)load("glClearDepthf"); + glad_glClearStencil = (PFNGLCLEARSTENCILPROC)load("glClearStencil"); + glad_glColorMask = (PFNGLCOLORMASKPROC)load("glColorMask"); + glad_glCompileShader = (PFNGLCOMPILESHADERPROC)load("glCompileShader"); + glad_glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC)load("glCompressedTexImage2D"); + glad_glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)load("glCompressedTexSubImage2D"); + glad_glCopyTexImage2D = (PFNGLCOPYTEXIMAGE2DPROC)load("glCopyTexImage2D"); + glad_glCopyTexSubImage2D = (PFNGLCOPYTEXSUBIMAGE2DPROC)load("glCopyTexSubImage2D"); + glad_glCreateProgram = (PFNGLCREATEPROGRAMPROC)load("glCreateProgram"); + glad_glCreateShader = (PFNGLCREATESHADERPROC)load("glCreateShader"); + glad_glCullFace = (PFNGLCULLFACEPROC)load("glCullFace"); + glad_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)load("glDeleteBuffers"); + glad_glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)load("glDeleteFramebuffers"); + glad_glDeleteProgram = (PFNGLDELETEPROGRAMPROC)load("glDeleteProgram"); + glad_glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)load("glDeleteRenderbuffers"); + glad_glDeleteShader = (PFNGLDELETESHADERPROC)load("glDeleteShader"); + glad_glDeleteTextures = (PFNGLDELETETEXTURESPROC)load("glDeleteTextures"); + glad_glDepthFunc = (PFNGLDEPTHFUNCPROC)load("glDepthFunc"); + glad_glDepthMask = (PFNGLDEPTHMASKPROC)load("glDepthMask"); + glad_glDepthRangef = (PFNGLDEPTHRANGEFPROC)load("glDepthRangef"); + glad_glDetachShader = (PFNGLDETACHSHADERPROC)load("glDetachShader"); + glad_glDisable = (PFNGLDISABLEPROC)load("glDisable"); + glad_glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)load("glDisableVertexAttribArray"); + glad_glDrawArrays = (PFNGLDRAWARRAYSPROC)load("glDrawArrays"); + glad_glDrawElements = (PFNGLDRAWELEMENTSPROC)load("glDrawElements"); + glad_glEnable = (PFNGLENABLEPROC)load("glEnable"); + glad_glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)load("glEnableVertexAttribArray"); + glad_glFinish = (PFNGLFINISHPROC)load("glFinish"); + glad_glFlush = (PFNGLFLUSHPROC)load("glFlush"); + glad_glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)load("glFramebufferRenderbuffer"); + glad_glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)load("glFramebufferTexture2D"); + glad_glFrontFace = (PFNGLFRONTFACEPROC)load("glFrontFace"); + glad_glGenBuffers = (PFNGLGENBUFFERSPROC)load("glGenBuffers"); + glad_glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)load("glGenerateMipmap"); + glad_glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)load("glGenFramebuffers"); + glad_glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)load("glGenRenderbuffers"); + glad_glGenTextures = (PFNGLGENTEXTURESPROC)load("glGenTextures"); + glad_glGetActiveAttrib = (PFNGLGETACTIVEATTRIBPROC)load("glGetActiveAttrib"); + glad_glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC)load("glGetActiveUniform"); + glad_glGetAttachedShaders = (PFNGLGETATTACHEDSHADERSPROC)load("glGetAttachedShaders"); + glad_glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)load("glGetAttribLocation"); + glad_glGetBooleanv = (PFNGLGETBOOLEANVPROC)load("glGetBooleanv"); + glad_glGetBufferParameteriv = (PFNGLGETBUFFERPARAMETERIVPROC)load("glGetBufferParameteriv"); + glad_glGetError = (PFNGLGETERRORPROC)load("glGetError"); + glad_glGetFloatv = (PFNGLGETFLOATVPROC)load("glGetFloatv"); + glad_glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)load("glGetFramebufferAttachmentParameteriv"); + glad_glGetIntegerv = (PFNGLGETINTEGERVPROC)load("glGetIntegerv"); + glad_glGetProgramiv = (PFNGLGETPROGRAMIVPROC)load("glGetProgramiv"); + glad_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)load("glGetProgramInfoLog"); + glad_glGetRenderbufferParameteriv = (PFNGLGETRENDERBUFFERPARAMETERIVPROC)load("glGetRenderbufferParameteriv"); + glad_glGetShaderiv = (PFNGLGETSHADERIVPROC)load("glGetShaderiv"); + glad_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)load("glGetShaderInfoLog"); + glad_glGetShaderPrecisionFormat = (PFNGLGETSHADERPRECISIONFORMATPROC)load("glGetShaderPrecisionFormat"); + glad_glGetShaderSource = (PFNGLGETSHADERSOURCEPROC)load("glGetShaderSource"); + glad_glGetString = (PFNGLGETSTRINGPROC)load("glGetString"); + glad_glGetTexParameterfv = (PFNGLGETTEXPARAMETERFVPROC)load("glGetTexParameterfv"); + glad_glGetTexParameteriv = (PFNGLGETTEXPARAMETERIVPROC)load("glGetTexParameteriv"); + glad_glGetUniformfv = (PFNGLGETUNIFORMFVPROC)load("glGetUniformfv"); + glad_glGetUniformiv = (PFNGLGETUNIFORMIVPROC)load("glGetUniformiv"); + glad_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)load("glGetUniformLocation"); + glad_glGetVertexAttribfv = (PFNGLGETVERTEXATTRIBFVPROC)load("glGetVertexAttribfv"); + glad_glGetVertexAttribiv = (PFNGLGETVERTEXATTRIBIVPROC)load("glGetVertexAttribiv"); + glad_glGetVertexAttribPointerv = (PFNGLGETVERTEXATTRIBPOINTERVPROC)load("glGetVertexAttribPointerv"); + glad_glHint = (PFNGLHINTPROC)load("glHint"); + glad_glIsBuffer = (PFNGLISBUFFERPROC)load("glIsBuffer"); + glad_glIsEnabled = (PFNGLISENABLEDPROC)load("glIsEnabled"); + glad_glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)load("glIsFramebuffer"); + glad_glIsProgram = (PFNGLISPROGRAMPROC)load("glIsProgram"); + glad_glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)load("glIsRenderbuffer"); + glad_glIsShader = (PFNGLISSHADERPROC)load("glIsShader"); + glad_glIsTexture = (PFNGLISTEXTUREPROC)load("glIsTexture"); + glad_glLineWidth = (PFNGLLINEWIDTHPROC)load("glLineWidth"); + glad_glLinkProgram = (PFNGLLINKPROGRAMPROC)load("glLinkProgram"); + glad_glPixelStorei = (PFNGLPIXELSTOREIPROC)load("glPixelStorei"); + glad_glPolygonOffset = (PFNGLPOLYGONOFFSETPROC)load("glPolygonOffset"); + glad_glReadPixels = (PFNGLREADPIXELSPROC)load("glReadPixels"); + glad_glReleaseShaderCompiler = (PFNGLRELEASESHADERCOMPILERPROC)load("glReleaseShaderCompiler"); + glad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)load("glRenderbufferStorage"); + glad_glSampleCoverage = (PFNGLSAMPLECOVERAGEPROC)load("glSampleCoverage"); + glad_glScissor = (PFNGLSCISSORPROC)load("glScissor"); + glad_glShaderBinary = (PFNGLSHADERBINARYPROC)load("glShaderBinary"); + glad_glShaderSource = (PFNGLSHADERSOURCEPROC)load("glShaderSource"); + glad_glStencilFunc = (PFNGLSTENCILFUNCPROC)load("glStencilFunc"); + glad_glStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEPROC)load("glStencilFuncSeparate"); + glad_glStencilMask = (PFNGLSTENCILMASKPROC)load("glStencilMask"); + glad_glStencilMaskSeparate = (PFNGLSTENCILMASKSEPARATEPROC)load("glStencilMaskSeparate"); + glad_glStencilOp = (PFNGLSTENCILOPPROC)load("glStencilOp"); + glad_glStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)load("glStencilOpSeparate"); + glad_glTexImage2D = (PFNGLTEXIMAGE2DPROC)load("glTexImage2D"); + glad_glTexParameterf = (PFNGLTEXPARAMETERFPROC)load("glTexParameterf"); + glad_glTexParameterfv = (PFNGLTEXPARAMETERFVPROC)load("glTexParameterfv"); + glad_glTexParameteri = (PFNGLTEXPARAMETERIPROC)load("glTexParameteri"); + glad_glTexParameteriv = (PFNGLTEXPARAMETERIVPROC)load("glTexParameteriv"); + glad_glTexSubImage2D = (PFNGLTEXSUBIMAGE2DPROC)load("glTexSubImage2D"); + glad_glUniform1f = (PFNGLUNIFORM1FPROC)load("glUniform1f"); + glad_glUniform1fv = (PFNGLUNIFORM1FVPROC)load("glUniform1fv"); + glad_glUniform1i = (PFNGLUNIFORM1IPROC)load("glUniform1i"); + glad_glUniform1iv = (PFNGLUNIFORM1IVPROC)load("glUniform1iv"); + glad_glUniform2f = (PFNGLUNIFORM2FPROC)load("glUniform2f"); + glad_glUniform2fv = (PFNGLUNIFORM2FVPROC)load("glUniform2fv"); + glad_glUniform2i = (PFNGLUNIFORM2IPROC)load("glUniform2i"); + glad_glUniform2iv = (PFNGLUNIFORM2IVPROC)load("glUniform2iv"); + glad_glUniform3f = (PFNGLUNIFORM3FPROC)load("glUniform3f"); + glad_glUniform3fv = (PFNGLUNIFORM3FVPROC)load("glUniform3fv"); + glad_glUniform3i = (PFNGLUNIFORM3IPROC)load("glUniform3i"); + glad_glUniform3iv = (PFNGLUNIFORM3IVPROC)load("glUniform3iv"); + glad_glUniform4f = (PFNGLUNIFORM4FPROC)load("glUniform4f"); + glad_glUniform4fv = (PFNGLUNIFORM4FVPROC)load("glUniform4fv"); + glad_glUniform4i = (PFNGLUNIFORM4IPROC)load("glUniform4i"); + glad_glUniform4iv = (PFNGLUNIFORM4IVPROC)load("glUniform4iv"); + glad_glUniformMatrix2fv = (PFNGLUNIFORMMATRIX2FVPROC)load("glUniformMatrix2fv"); + glad_glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC)load("glUniformMatrix3fv"); + glad_glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)load("glUniformMatrix4fv"); + glad_glUseProgram = (PFNGLUSEPROGRAMPROC)load("glUseProgram"); + glad_glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)load("glValidateProgram"); + glad_glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC)load("glVertexAttrib1f"); + glad_glVertexAttrib1fv = (PFNGLVERTEXATTRIB1FVPROC)load("glVertexAttrib1fv"); + glad_glVertexAttrib2f = (PFNGLVERTEXATTRIB2FPROC)load("glVertexAttrib2f"); + glad_glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC)load("glVertexAttrib2fv"); + glad_glVertexAttrib3f = (PFNGLVERTEXATTRIB3FPROC)load("glVertexAttrib3f"); + glad_glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC)load("glVertexAttrib3fv"); + glad_glVertexAttrib4f = (PFNGLVERTEXATTRIB4FPROC)load("glVertexAttrib4f"); + glad_glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC)load("glVertexAttrib4fv"); + glad_glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)load("glVertexAttribPointer"); + glad_glViewport = (PFNGLVIEWPORTPROC)load("glViewport"); +} +static void load_GL_ES_VERSION_3_0(GLADloadproc load) { + if(!GLAD_GL_ES_VERSION_3_0) return; + glad_glReadBuffer = (PFNGLREADBUFFERPROC)load("glReadBuffer"); + glad_glDrawRangeElements = (PFNGLDRAWRANGEELEMENTSPROC)load("glDrawRangeElements"); + glad_glTexImage3D = (PFNGLTEXIMAGE3DPROC)load("glTexImage3D"); + glad_glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC)load("glTexSubImage3D"); + glad_glCopyTexSubImage3D = (PFNGLCOPYTEXSUBIMAGE3DPROC)load("glCopyTexSubImage3D"); + glad_glCompressedTexImage3D = (PFNGLCOMPRESSEDTEXIMAGE3DPROC)load("glCompressedTexImage3D"); + glad_glCompressedTexSubImage3D = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)load("glCompressedTexSubImage3D"); + glad_glGenQueries = (PFNGLGENQUERIESPROC)load("glGenQueries"); + glad_glDeleteQueries = (PFNGLDELETEQUERIESPROC)load("glDeleteQueries"); + glad_glIsQuery = (PFNGLISQUERYPROC)load("glIsQuery"); + glad_glBeginQuery = (PFNGLBEGINQUERYPROC)load("glBeginQuery"); + glad_glEndQuery = (PFNGLENDQUERYPROC)load("glEndQuery"); + glad_glGetQueryiv = (PFNGLGETQUERYIVPROC)load("glGetQueryiv"); + glad_glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC)load("glGetQueryObjectuiv"); + glad_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)load("glUnmapBuffer"); + glad_glGetBufferPointerv = (PFNGLGETBUFFERPOINTERVPROC)load("glGetBufferPointerv"); + glad_glDrawBuffers = (PFNGLDRAWBUFFERSPROC)load("glDrawBuffers"); + glad_glUniformMatrix2x3fv = (PFNGLUNIFORMMATRIX2X3FVPROC)load("glUniformMatrix2x3fv"); + glad_glUniformMatrix3x2fv = (PFNGLUNIFORMMATRIX3X2FVPROC)load("glUniformMatrix3x2fv"); + glad_glUniformMatrix2x4fv = (PFNGLUNIFORMMATRIX2X4FVPROC)load("glUniformMatrix2x4fv"); + glad_glUniformMatrix4x2fv = (PFNGLUNIFORMMATRIX4X2FVPROC)load("glUniformMatrix4x2fv"); + glad_glUniformMatrix3x4fv = (PFNGLUNIFORMMATRIX3X4FVPROC)load("glUniformMatrix3x4fv"); + glad_glUniformMatrix4x3fv = (PFNGLUNIFORMMATRIX4X3FVPROC)load("glUniformMatrix4x3fv"); + glad_glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)load("glBlitFramebuffer"); + glad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)load("glRenderbufferStorageMultisample"); + glad_glFramebufferTextureLayer = (PFNGLFRAMEBUFFERTEXTURELAYERPROC)load("glFramebufferTextureLayer"); + glad_glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC)load("glMapBufferRange"); + glad_glFlushMappedBufferRange = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC)load("glFlushMappedBufferRange"); + glad_glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)load("glBindVertexArray"); + glad_glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)load("glDeleteVertexArrays"); + glad_glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)load("glGenVertexArrays"); + glad_glIsVertexArray = (PFNGLISVERTEXARRAYPROC)load("glIsVertexArray"); + glad_glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC)load("glGetIntegeri_v"); + glad_glBeginTransformFeedback = (PFNGLBEGINTRANSFORMFEEDBACKPROC)load("glBeginTransformFeedback"); + glad_glEndTransformFeedback = (PFNGLENDTRANSFORMFEEDBACKPROC)load("glEndTransformFeedback"); + glad_glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC)load("glBindBufferRange"); + glad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)load("glBindBufferBase"); + glad_glTransformFeedbackVaryings = (PFNGLTRANSFORMFEEDBACKVARYINGSPROC)load("glTransformFeedbackVaryings"); + glad_glGetTransformFeedbackVarying = (PFNGLGETTRANSFORMFEEDBACKVARYINGPROC)load("glGetTransformFeedbackVarying"); + glad_glVertexAttribIPointer = (PFNGLVERTEXATTRIBIPOINTERPROC)load("glVertexAttribIPointer"); + glad_glGetVertexAttribIiv = (PFNGLGETVERTEXATTRIBIIVPROC)load("glGetVertexAttribIiv"); + glad_glGetVertexAttribIuiv = (PFNGLGETVERTEXATTRIBIUIVPROC)load("glGetVertexAttribIuiv"); + glad_glVertexAttribI4i = (PFNGLVERTEXATTRIBI4IPROC)load("glVertexAttribI4i"); + glad_glVertexAttribI4ui = (PFNGLVERTEXATTRIBI4UIPROC)load("glVertexAttribI4ui"); + glad_glVertexAttribI4iv = (PFNGLVERTEXATTRIBI4IVPROC)load("glVertexAttribI4iv"); + glad_glVertexAttribI4uiv = (PFNGLVERTEXATTRIBI4UIVPROC)load("glVertexAttribI4uiv"); + glad_glGetUniformuiv = (PFNGLGETUNIFORMUIVPROC)load("glGetUniformuiv"); + glad_glGetFragDataLocation = (PFNGLGETFRAGDATALOCATIONPROC)load("glGetFragDataLocation"); + glad_glUniform1ui = (PFNGLUNIFORM1UIPROC)load("glUniform1ui"); + glad_glUniform2ui = (PFNGLUNIFORM2UIPROC)load("glUniform2ui"); + glad_glUniform3ui = (PFNGLUNIFORM3UIPROC)load("glUniform3ui"); + glad_glUniform4ui = (PFNGLUNIFORM4UIPROC)load("glUniform4ui"); + glad_glUniform1uiv = (PFNGLUNIFORM1UIVPROC)load("glUniform1uiv"); + glad_glUniform2uiv = (PFNGLUNIFORM2UIVPROC)load("glUniform2uiv"); + glad_glUniform3uiv = (PFNGLUNIFORM3UIVPROC)load("glUniform3uiv"); + glad_glUniform4uiv = (PFNGLUNIFORM4UIVPROC)load("glUniform4uiv"); + glad_glClearBufferiv = (PFNGLCLEARBUFFERIVPROC)load("glClearBufferiv"); + glad_glClearBufferuiv = (PFNGLCLEARBUFFERUIVPROC)load("glClearBufferuiv"); + glad_glClearBufferfv = (PFNGLCLEARBUFFERFVPROC)load("glClearBufferfv"); + glad_glClearBufferfi = (PFNGLCLEARBUFFERFIPROC)load("glClearBufferfi"); + glad_glGetStringi = (PFNGLGETSTRINGIPROC)load("glGetStringi"); + glad_glCopyBufferSubData = (PFNGLCOPYBUFFERSUBDATAPROC)load("glCopyBufferSubData"); + glad_glGetUniformIndices = (PFNGLGETUNIFORMINDICESPROC)load("glGetUniformIndices"); + glad_glGetActiveUniformsiv = (PFNGLGETACTIVEUNIFORMSIVPROC)load("glGetActiveUniformsiv"); + glad_glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC)load("glGetUniformBlockIndex"); + glad_glGetActiveUniformBlockiv = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC)load("glGetActiveUniformBlockiv"); + glad_glGetActiveUniformBlockName = (PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)load("glGetActiveUniformBlockName"); + glad_glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC)load("glUniformBlockBinding"); + glad_glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDPROC)load("glDrawArraysInstanced"); + glad_glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDPROC)load("glDrawElementsInstanced"); + glad_glFenceSync = (PFNGLFENCESYNCPROC)load("glFenceSync"); + glad_glIsSync = (PFNGLISSYNCPROC)load("glIsSync"); + glad_glDeleteSync = (PFNGLDELETESYNCPROC)load("glDeleteSync"); + glad_glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)load("glClientWaitSync"); + glad_glWaitSync = (PFNGLWAITSYNCPROC)load("glWaitSync"); + glad_glGetInteger64v = (PFNGLGETINTEGER64VPROC)load("glGetInteger64v"); + glad_glGetSynciv = (PFNGLGETSYNCIVPROC)load("glGetSynciv"); + glad_glGetInteger64i_v = (PFNGLGETINTEGER64I_VPROC)load("glGetInteger64i_v"); + glad_glGetBufferParameteri64v = (PFNGLGETBUFFERPARAMETERI64VPROC)load("glGetBufferParameteri64v"); + glad_glGenSamplers = (PFNGLGENSAMPLERSPROC)load("glGenSamplers"); + glad_glDeleteSamplers = (PFNGLDELETESAMPLERSPROC)load("glDeleteSamplers"); + glad_glIsSampler = (PFNGLISSAMPLERPROC)load("glIsSampler"); + glad_glBindSampler = (PFNGLBINDSAMPLERPROC)load("glBindSampler"); + glad_glSamplerParameteri = (PFNGLSAMPLERPARAMETERIPROC)load("glSamplerParameteri"); + glad_glSamplerParameteriv = (PFNGLSAMPLERPARAMETERIVPROC)load("glSamplerParameteriv"); + glad_glSamplerParameterf = (PFNGLSAMPLERPARAMETERFPROC)load("glSamplerParameterf"); + glad_glSamplerParameterfv = (PFNGLSAMPLERPARAMETERFVPROC)load("glSamplerParameterfv"); + glad_glGetSamplerParameteriv = (PFNGLGETSAMPLERPARAMETERIVPROC)load("glGetSamplerParameteriv"); + glad_glGetSamplerParameterfv = (PFNGLGETSAMPLERPARAMETERFVPROC)load("glGetSamplerParameterfv"); + glad_glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISORPROC)load("glVertexAttribDivisor"); + glad_glBindTransformFeedback = (PFNGLBINDTRANSFORMFEEDBACKPROC)load("glBindTransformFeedback"); + glad_glDeleteTransformFeedbacks = (PFNGLDELETETRANSFORMFEEDBACKSPROC)load("glDeleteTransformFeedbacks"); + glad_glGenTransformFeedbacks = (PFNGLGENTRANSFORMFEEDBACKSPROC)load("glGenTransformFeedbacks"); + glad_glIsTransformFeedback = (PFNGLISTRANSFORMFEEDBACKPROC)load("glIsTransformFeedback"); + glad_glPauseTransformFeedback = (PFNGLPAUSETRANSFORMFEEDBACKPROC)load("glPauseTransformFeedback"); + glad_glResumeTransformFeedback = (PFNGLRESUMETRANSFORMFEEDBACKPROC)load("glResumeTransformFeedback"); + glad_glGetProgramBinary = (PFNGLGETPROGRAMBINARYPROC)load("glGetProgramBinary"); + glad_glProgramBinary = (PFNGLPROGRAMBINARYPROC)load("glProgramBinary"); + glad_glProgramParameteri = (PFNGLPROGRAMPARAMETERIPROC)load("glProgramParameteri"); + glad_glInvalidateFramebuffer = (PFNGLINVALIDATEFRAMEBUFFERPROC)load("glInvalidateFramebuffer"); + glad_glInvalidateSubFramebuffer = (PFNGLINVALIDATESUBFRAMEBUFFERPROC)load("glInvalidateSubFramebuffer"); + glad_glTexStorage2D = (PFNGLTEXSTORAGE2DPROC)load("glTexStorage2D"); + glad_glTexStorage3D = (PFNGLTEXSTORAGE3DPROC)load("glTexStorage3D"); + glad_glGetInternalformativ = (PFNGLGETINTERNALFORMATIVPROC)load("glGetInternalformativ"); +} +static void load_GL_ES_VERSION_3_1(GLADloadproc load) { + if(!GLAD_GL_ES_VERSION_3_1) return; + glad_glDispatchCompute = (PFNGLDISPATCHCOMPUTEPROC)load("glDispatchCompute"); + glad_glDispatchComputeIndirect = (PFNGLDISPATCHCOMPUTEINDIRECTPROC)load("glDispatchComputeIndirect"); + glad_glDrawArraysIndirect = (PFNGLDRAWARRAYSINDIRECTPROC)load("glDrawArraysIndirect"); + glad_glDrawElementsIndirect = (PFNGLDRAWELEMENTSINDIRECTPROC)load("glDrawElementsIndirect"); + glad_glFramebufferParameteri = (PFNGLFRAMEBUFFERPARAMETERIPROC)load("glFramebufferParameteri"); + glad_glGetFramebufferParameteriv = (PFNGLGETFRAMEBUFFERPARAMETERIVPROC)load("glGetFramebufferParameteriv"); + glad_glGetProgramInterfaceiv = (PFNGLGETPROGRAMINTERFACEIVPROC)load("glGetProgramInterfaceiv"); + glad_glGetProgramResourceIndex = (PFNGLGETPROGRAMRESOURCEINDEXPROC)load("glGetProgramResourceIndex"); + glad_glGetProgramResourceName = (PFNGLGETPROGRAMRESOURCENAMEPROC)load("glGetProgramResourceName"); + glad_glGetProgramResourceiv = (PFNGLGETPROGRAMRESOURCEIVPROC)load("glGetProgramResourceiv"); + glad_glGetProgramResourceLocation = (PFNGLGETPROGRAMRESOURCELOCATIONPROC)load("glGetProgramResourceLocation"); + glad_glUseProgramStages = (PFNGLUSEPROGRAMSTAGESPROC)load("glUseProgramStages"); + glad_glActiveShaderProgram = (PFNGLACTIVESHADERPROGRAMPROC)load("glActiveShaderProgram"); + glad_glCreateShaderProgramv = (PFNGLCREATESHADERPROGRAMVPROC)load("glCreateShaderProgramv"); + glad_glBindProgramPipeline = (PFNGLBINDPROGRAMPIPELINEPROC)load("glBindProgramPipeline"); + glad_glDeleteProgramPipelines = (PFNGLDELETEPROGRAMPIPELINESPROC)load("glDeleteProgramPipelines"); + glad_glGenProgramPipelines = (PFNGLGENPROGRAMPIPELINESPROC)load("glGenProgramPipelines"); + glad_glIsProgramPipeline = (PFNGLISPROGRAMPIPELINEPROC)load("glIsProgramPipeline"); + glad_glGetProgramPipelineiv = (PFNGLGETPROGRAMPIPELINEIVPROC)load("glGetProgramPipelineiv"); + glad_glProgramUniform1i = (PFNGLPROGRAMUNIFORM1IPROC)load("glProgramUniform1i"); + glad_glProgramUniform2i = (PFNGLPROGRAMUNIFORM2IPROC)load("glProgramUniform2i"); + glad_glProgramUniform3i = (PFNGLPROGRAMUNIFORM3IPROC)load("glProgramUniform3i"); + glad_glProgramUniform4i = (PFNGLPROGRAMUNIFORM4IPROC)load("glProgramUniform4i"); + glad_glProgramUniform1ui = (PFNGLPROGRAMUNIFORM1UIPROC)load("glProgramUniform1ui"); + glad_glProgramUniform2ui = (PFNGLPROGRAMUNIFORM2UIPROC)load("glProgramUniform2ui"); + glad_glProgramUniform3ui = (PFNGLPROGRAMUNIFORM3UIPROC)load("glProgramUniform3ui"); + glad_glProgramUniform4ui = (PFNGLPROGRAMUNIFORM4UIPROC)load("glProgramUniform4ui"); + glad_glProgramUniform1f = (PFNGLPROGRAMUNIFORM1FPROC)load("glProgramUniform1f"); + glad_glProgramUniform2f = (PFNGLPROGRAMUNIFORM2FPROC)load("glProgramUniform2f"); + glad_glProgramUniform3f = (PFNGLPROGRAMUNIFORM3FPROC)load("glProgramUniform3f"); + glad_glProgramUniform4f = (PFNGLPROGRAMUNIFORM4FPROC)load("glProgramUniform4f"); + glad_glProgramUniform1iv = (PFNGLPROGRAMUNIFORM1IVPROC)load("glProgramUniform1iv"); + glad_glProgramUniform2iv = (PFNGLPROGRAMUNIFORM2IVPROC)load("glProgramUniform2iv"); + glad_glProgramUniform3iv = (PFNGLPROGRAMUNIFORM3IVPROC)load("glProgramUniform3iv"); + glad_glProgramUniform4iv = (PFNGLPROGRAMUNIFORM4IVPROC)load("glProgramUniform4iv"); + glad_glProgramUniform1uiv = (PFNGLPROGRAMUNIFORM1UIVPROC)load("glProgramUniform1uiv"); + glad_glProgramUniform2uiv = (PFNGLPROGRAMUNIFORM2UIVPROC)load("glProgramUniform2uiv"); + glad_glProgramUniform3uiv = (PFNGLPROGRAMUNIFORM3UIVPROC)load("glProgramUniform3uiv"); + glad_glProgramUniform4uiv = (PFNGLPROGRAMUNIFORM4UIVPROC)load("glProgramUniform4uiv"); + glad_glProgramUniform1fv = (PFNGLPROGRAMUNIFORM1FVPROC)load("glProgramUniform1fv"); + glad_glProgramUniform2fv = (PFNGLPROGRAMUNIFORM2FVPROC)load("glProgramUniform2fv"); + glad_glProgramUniform3fv = (PFNGLPROGRAMUNIFORM3FVPROC)load("glProgramUniform3fv"); + glad_glProgramUniform4fv = (PFNGLPROGRAMUNIFORM4FVPROC)load("glProgramUniform4fv"); + glad_glProgramUniformMatrix2fv = (PFNGLPROGRAMUNIFORMMATRIX2FVPROC)load("glProgramUniformMatrix2fv"); + glad_glProgramUniformMatrix3fv = (PFNGLPROGRAMUNIFORMMATRIX3FVPROC)load("glProgramUniformMatrix3fv"); + glad_glProgramUniformMatrix4fv = (PFNGLPROGRAMUNIFORMMATRIX4FVPROC)load("glProgramUniformMatrix4fv"); + glad_glProgramUniformMatrix2x3fv = (PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC)load("glProgramUniformMatrix2x3fv"); + glad_glProgramUniformMatrix3x2fv = (PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC)load("glProgramUniformMatrix3x2fv"); + glad_glProgramUniformMatrix2x4fv = (PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC)load("glProgramUniformMatrix2x4fv"); + glad_glProgramUniformMatrix4x2fv = (PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC)load("glProgramUniformMatrix4x2fv"); + glad_glProgramUniformMatrix3x4fv = (PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC)load("glProgramUniformMatrix3x4fv"); + glad_glProgramUniformMatrix4x3fv = (PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC)load("glProgramUniformMatrix4x3fv"); + glad_glValidateProgramPipeline = (PFNGLVALIDATEPROGRAMPIPELINEPROC)load("glValidateProgramPipeline"); + glad_glGetProgramPipelineInfoLog = (PFNGLGETPROGRAMPIPELINEINFOLOGPROC)load("glGetProgramPipelineInfoLog"); + glad_glBindImageTexture = (PFNGLBINDIMAGETEXTUREPROC)load("glBindImageTexture"); + glad_glGetBooleani_v = (PFNGLGETBOOLEANI_VPROC)load("glGetBooleani_v"); + glad_glMemoryBarrier = (PFNGLMEMORYBARRIERPROC)load("glMemoryBarrier"); + glad_glMemoryBarrierByRegion = (PFNGLMEMORYBARRIERBYREGIONPROC)load("glMemoryBarrierByRegion"); + glad_glTexStorage2DMultisample = (PFNGLTEXSTORAGE2DMULTISAMPLEPROC)load("glTexStorage2DMultisample"); + glad_glGetMultisamplefv = (PFNGLGETMULTISAMPLEFVPROC)load("glGetMultisamplefv"); + glad_glSampleMaski = (PFNGLSAMPLEMASKIPROC)load("glSampleMaski"); + glad_glGetTexLevelParameteriv = (PFNGLGETTEXLEVELPARAMETERIVPROC)load("glGetTexLevelParameteriv"); + glad_glGetTexLevelParameterfv = (PFNGLGETTEXLEVELPARAMETERFVPROC)load("glGetTexLevelParameterfv"); + glad_glBindVertexBuffer = (PFNGLBINDVERTEXBUFFERPROC)load("glBindVertexBuffer"); + glad_glVertexAttribFormat = (PFNGLVERTEXATTRIBFORMATPROC)load("glVertexAttribFormat"); + glad_glVertexAttribIFormat = (PFNGLVERTEXATTRIBIFORMATPROC)load("glVertexAttribIFormat"); + glad_glVertexAttribBinding = (PFNGLVERTEXATTRIBBINDINGPROC)load("glVertexAttribBinding"); + glad_glVertexBindingDivisor = (PFNGLVERTEXBINDINGDIVISORPROC)load("glVertexBindingDivisor"); +} +static int find_extensionsGLES2(void) { + if (!get_exts()) return 0; + GLAD_GL_EXT_texture_compression_s3tc = has_ext("GL_EXT_texture_compression_s3tc"); + GLAD_GL_EXT_texture_filter_anisotropic = has_ext("GL_EXT_texture_filter_anisotropic"); + GLAD_GL_KHR_debug = has_ext("GL_KHR_debug"); + GLAD_GL_KHR_texture_compression_astc_ldr = has_ext("GL_KHR_texture_compression_astc_ldr"); + free_exts(); + return 1; +} + +static void find_coreGLES2(void) { + + /* Thank you @elmindreda + * https://github.com/elmindreda/greg/blob/master/templates/greg.c.in#L176 + * https://github.com/glfw/glfw/blob/master/src/context.c#L36 + */ + int i, major, minor; + + const char* version; + const char* prefixes[] = { + "OpenGL ES-CM ", + "OpenGL ES-CL ", + "OpenGL ES ", + NULL + }; + + version = (const char*) glGetString(GL_VERSION); + if (!version) return; + + for (i = 0; prefixes[i]; i++) { + const size_t length = strlen(prefixes[i]); + if (strncmp(version, prefixes[i], length) == 0) { + version += length; + break; + } + } + +/* PR #18 */ +#ifdef _MSC_VER + sscanf_s(version, "%d.%d", &major, &minor); +#else + sscanf(version, "%d.%d", &major, &minor); +#endif + + if(major > requested_major_version) { + major = requested_major_version; + } + + if(major == requested_major_version && minor > requested_minor_version) { + minor = requested_minor_version; + } + + GLVersion.major = major; GLVersion.minor = minor; + max_loaded_major = major; max_loaded_minor = minor; + GLAD_GL_ES_VERSION_2_0 = (major == 2 && minor >= 0) || major > 2; + GLAD_GL_ES_VERSION_3_0 = (major == 3 && minor >= 0) || major > 3; + GLAD_GL_ES_VERSION_3_1 = (major == 3 && minor >= 1) || major > 3; + if (GLVersion.major > 3 || (GLVersion.major >= 3 && GLVersion.minor >= 1)) { + max_loaded_major = 3; + max_loaded_minor = 1; + } +} + +int gladLoadGLES2Loader(GLADloadproc load, int requestedVersion) { + requested_major_version = requestedVersion / 10; + requested_minor_version = requestedVersion % 10; + GLVersion.major = 0; GLVersion.minor = 0; + glGetString = (PFNGLGETSTRINGPROC)load("glGetString"); + if(glGetString == NULL) return 0; + if(glGetString(GL_VERSION) == NULL) return 0; + find_coreGLES2(); + load_GL_ES_VERSION_2_0(load); + load_GL_ES_VERSION_3_0(load); + load_GL_ES_VERSION_3_1(load); + + if (!find_extensionsGLES2()) return 0; + load_GL_KHR_debug(load); + return GLVersion.major != 0 || GLVersion.minor != 0; +} diff --git a/vendor/librw/src/gl/glad/glad.h b/vendor/librw/src/gl/glad/glad.h new file mode 100644 index 0000000..8f785d5 --- /dev/null +++ b/vendor/librw/src/gl/glad/glad.h @@ -0,0 +1,2808 @@ +/* + + OpenGL, OpenGL ES loader generated by glad 0.1.34 on Wed Feb 17 15:35:25 2021. + + Language/Generator: C/C++ + Specification: gl + APIs: gl=3.3, gles2=3.1 + Profile: core + Extensions: + GL_EXT_framebuffer_object, + GL_EXT_texture_compression_s3tc, + GL_EXT_texture_filter_anisotropic, + GL_KHR_debug, + GL_KHR_texture_compression_astc_ldr + Loader: False + Local files: False + Omit khrplatform: False + Reproducible: False + + Commandline: + --profile="core" --api="gl=3.3,gles2=3.1" --generator="c" --spec="gl" --no-loader --extensions="GL_EXT_framebuffer_object,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic,GL_KHR_debug,GL_KHR_texture_compression_astc_ldr" + Online: + https://glad.dav1d.de/#profile=core&language=c&specification=gl&api=gl%3D3.3&api=gles2%3D3.1&extensions=GL_EXT_framebuffer_object&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_EXT_texture_filter_anisotropic&extensions=GL_KHR_debug&extensions=GL_KHR_texture_compression_astc_ldr +*/ + + +#ifndef __glad_h_ +#define __glad_h_ + +#ifdef __gl_h_ +#error OpenGL header already included, remove this include, glad already provides it +#endif +#define __gl_h_ + +#ifdef __gl2_h_ +#error OpenGL ES 2 header already included, remove this include, glad already provides it +#endif +#define __gl2_h_ + +#ifdef __gl3_h_ +#error OpenGL ES 3 header already included, remove this include, glad already provides it +#endif +#define __gl3_h_ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define APIENTRY __stdcall +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif + +#ifndef GLAPIENTRY +#define GLAPIENTRY APIENTRY +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct gladGLversionStruct { + int major; + int minor; +}; + +typedef void* (* GLADloadproc)(const char *name); + +#ifndef GLAPI +# if defined(GLAD_GLAPI_EXPORT) +# if defined(_WIN32) || defined(__CYGWIN__) +# if defined(GLAD_GLAPI_EXPORT_BUILD) +# if defined(__GNUC__) +# define GLAPI __attribute__ ((dllexport)) extern +# else +# define GLAPI __declspec(dllexport) extern +# endif +# else +# if defined(__GNUC__) +# define GLAPI __attribute__ ((dllimport)) extern +# else +# define GLAPI __declspec(dllimport) extern +# endif +# endif +# elif defined(__GNUC__) && defined(GLAD_GLAPI_EXPORT_BUILD) +# define GLAPI __attribute__ ((visibility ("default"))) extern +# else +# define GLAPI extern +# endif +# else +# define GLAPI extern +# endif +#endif + +GLAPI struct gladGLversionStruct GLVersion; +GLAPI int gladLoadGLLoader(GLADloadproc, int requestedVersion); + +GLAPI int gladLoadGLES2Loader(GLADloadproc, int requestedVersion); + +#include "khrplatform.h" +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +typedef khronos_int8_t GLbyte; +typedef khronos_uint8_t GLubyte; +typedef khronos_int16_t GLshort; +typedef khronos_uint16_t GLushort; +typedef int GLint; +typedef unsigned int GLuint; +typedef khronos_int32_t GLclampx; +typedef int GLsizei; +typedef khronos_float_t GLfloat; +typedef khronos_float_t GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void *GLeglClientBufferEXT; +typedef void *GLeglImageOES; +typedef char GLchar; +typedef char GLcharARB; +#ifdef __APPLE__ +typedef void *GLhandleARB; +#else +typedef unsigned int GLhandleARB; +#endif +typedef khronos_uint16_t GLhalf; +typedef khronos_uint16_t GLhalfARB; +typedef khronos_int32_t GLfixed; +typedef khronos_intptr_t GLintptr; +typedef khronos_intptr_t GLintptrARB; +typedef khronos_ssize_t GLsizeiptr; +typedef khronos_ssize_t GLsizeiptrARB; +typedef khronos_int64_t GLint64; +typedef khronos_int64_t GLint64EXT; +typedef khronos_uint64_t GLuint64; +typedef khronos_uint64_t GLuint64EXT; +typedef struct __GLsync *GLsync; +struct _cl_context; +struct _cl_event; +typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +typedef void (APIENTRY *GLDEBUGPROCKHR)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +typedef void (APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,void *userParam); +typedef unsigned short GLhalfNV; +typedef GLintptr GLvdpauSurfaceNV; +typedef void (APIENTRY *GLVULKANPROCNV)(void); +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_OUT_OF_MEMORY 0x0505 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_VIEWPORT 0x0BA2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F +#define GL_TEXTURE 0x1702 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_REPEAT 0x2901 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_DOUBLE 0x140A +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_BLEND_COLOR 0x8005 +#define GL_BLEND_EQUATION 0x8009 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_FUNC_ADD 0x8006 +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_FUNC_SUBTRACT 0x800A +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_SRC1_ALPHA 0x8589 +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_FLOAT_MAT2x3 0x8B65 +#define GL_FLOAT_MAT2x4 0x8B66 +#define GL_FLOAT_MAT3x2 0x8B67 +#define GL_FLOAT_MAT3x4 0x8B68 +#define GL_FLOAT_MAT4x2 0x8B69 +#define GL_FLOAT_MAT4x3 0x8B6A +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_COMPRESSED_SRGB 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 +#define GL_COMPARE_REF_TO_TEXTURE 0x884E +#define GL_CLIP_DISTANCE0 0x3000 +#define GL_CLIP_DISTANCE1 0x3001 +#define GL_CLIP_DISTANCE2 0x3002 +#define GL_CLIP_DISTANCE3 0x3003 +#define GL_CLIP_DISTANCE4 0x3004 +#define GL_CLIP_DISTANCE5 0x3005 +#define GL_CLIP_DISTANCE6 0x3006 +#define GL_CLIP_DISTANCE7 0x3007 +#define GL_MAX_CLIP_DISTANCES 0x0D32 +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_CONTEXT_FLAGS 0x821E +#define GL_COMPRESSED_RED 0x8225 +#define GL_COMPRESSED_RG 0x8226 +#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001 +#define GL_RGBA32F 0x8814 +#define GL_RGB32F 0x8815 +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 +#define GL_CLAMP_READ_COLOR 0x891C +#define GL_FIXED_ONLY 0x891D +#define GL_MAX_VARYING_COMPONENTS 0x8B4B +#define GL_TEXTURE_1D_ARRAY 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D +#define GL_R11F_G11F_B10F 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B +#define GL_RGB9_E5 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E +#define GL_TEXTURE_SHARED_SIZE 0x8C3F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 +#define GL_PRIMITIVES_GENERATED 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 +#define GL_RASTERIZER_DISCARD 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B +#define GL_INTERLEAVED_ATTRIBS 0x8C8C +#define GL_SEPARATE_ATTRIBS 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F +#define GL_RGBA32UI 0x8D70 +#define GL_RGB32UI 0x8D71 +#define GL_RGBA16UI 0x8D76 +#define GL_RGB16UI 0x8D77 +#define GL_RGBA8UI 0x8D7C +#define GL_RGB8UI 0x8D7D +#define GL_RGBA32I 0x8D82 +#define GL_RGB32I 0x8D83 +#define GL_RGBA16I 0x8D88 +#define GL_RGB16I 0x8D89 +#define GL_RGBA8I 0x8D8E +#define GL_RGB8I 0x8D8F +#define GL_RED_INTEGER 0x8D94 +#define GL_GREEN_INTEGER 0x8D95 +#define GL_BLUE_INTEGER 0x8D96 +#define GL_RGB_INTEGER 0x8D98 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGR_INTEGER 0x8D9A +#define GL_BGRA_INTEGER 0x8D9B +#define GL_SAMPLER_1D_ARRAY 0x8DC0 +#define GL_SAMPLER_2D_ARRAY 0x8DC1 +#define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW 0x8DC5 +#define GL_UNSIGNED_INT_VEC2 0x8DC6 +#define GL_UNSIGNED_INT_VEC3 0x8DC7 +#define GL_UNSIGNED_INT_VEC4 0x8DC8 +#define GL_INT_SAMPLER_1D 0x8DC9 +#define GL_INT_SAMPLER_2D 0x8DCA +#define GL_INT_SAMPLER_3D 0x8DCB +#define GL_INT_SAMPLER_CUBE 0x8DCC +#define GL_INT_SAMPLER_1D_ARRAY 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF +#define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 +#define GL_QUERY_WAIT 0x8E13 +#define GL_QUERY_NO_WAIT 0x8E14 +#define GL_QUERY_BY_REGION_WAIT 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 +#define GL_BUFFER_ACCESS_FLAGS 0x911F +#define GL_BUFFER_MAP_LENGTH 0x9120 +#define GL_BUFFER_MAP_OFFSET 0x9121 +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#define GL_TEXTURE_RED_TYPE 0x8C10 +#define GL_TEXTURE_GREEN_TYPE 0x8C11 +#define GL_TEXTURE_BLUE_TYPE 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE 0x8C13 +#define GL_TEXTURE_DEPTH_TYPE 0x8C16 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_COLOR_ATTACHMENT16 0x8CF0 +#define GL_COLOR_ATTACHMENT17 0x8CF1 +#define GL_COLOR_ATTACHMENT18 0x8CF2 +#define GL_COLOR_ATTACHMENT19 0x8CF3 +#define GL_COLOR_ATTACHMENT20 0x8CF4 +#define GL_COLOR_ATTACHMENT21 0x8CF5 +#define GL_COLOR_ATTACHMENT22 0x8CF6 +#define GL_COLOR_ATTACHMENT23 0x8CF7 +#define GL_COLOR_ATTACHMENT24 0x8CF8 +#define GL_COLOR_ATTACHMENT25 0x8CF9 +#define GL_COLOR_ATTACHMENT26 0x8CFA +#define GL_COLOR_ATTACHMENT27 0x8CFB +#define GL_COLOR_ATTACHMENT28 0x8CFC +#define GL_COLOR_ATTACHMENT29 0x8CFD +#define GL_COLOR_ATTACHMENT30 0x8CFE +#define GL_COLOR_ATTACHMENT31 0x8CFF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_SAMPLES 0x8D57 +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_HALF_FLOAT 0x140B +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#define GL_RG 0x8227 +#define GL_RG_INTEGER 0x8228 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 +#define GL_R8I 0x8231 +#define GL_R8UI 0x8232 +#define GL_R16I 0x8233 +#define GL_R16UI 0x8234 +#define GL_R32I 0x8235 +#define GL_R32UI 0x8236 +#define GL_RG8I 0x8237 +#define GL_RG8UI 0x8238 +#define GL_RG16I 0x8239 +#define GL_RG16UI 0x823A +#define GL_RG32I 0x823B +#define GL_RG32UI 0x823C +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +#define GL_SAMPLER_2D_RECT 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 +#define GL_SAMPLER_BUFFER 0x8DC2 +#define GL_INT_SAMPLER_2D_RECT 0x8DCD +#define GL_INT_SAMPLER_BUFFER 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 +#define GL_TEXTURE_BUFFER 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D +#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 +#define GL_R8_SNORM 0x8F94 +#define GL_RG8_SNORM 0x8F95 +#define GL_RGB8_SNORM 0x8F96 +#define GL_RGBA8_SNORM 0x8F97 +#define GL_R16_SNORM 0x8F98 +#define GL_RG16_SNORM 0x8F99 +#define GL_RGB16_SNORM 0x8F9A +#define GL_RGBA16_SNORM 0x8F9B +#define GL_SIGNED_NORMALIZED 0x8F9C +#define GL_PRIMITIVE_RESTART 0x8F9D +#define GL_PRIMITIVE_RESTART_INDEX 0x8F9E +#define GL_COPY_READ_BUFFER 0x8F36 +#define GL_COPY_WRITE_BUFFER 0x8F37 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_BINDING 0x8A28 +#define GL_UNIFORM_BUFFER_START 0x8A29 +#define GL_UNIFORM_BUFFER_SIZE 0x8A2A +#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C +#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D +#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E +#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F +#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 +#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 +#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 +#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 +#define GL_UNIFORM_TYPE 0x8A37 +#define GL_UNIFORM_SIZE 0x8A38 +#define GL_UNIFORM_NAME_LENGTH 0x8A39 +#define GL_UNIFORM_BLOCK_INDEX 0x8A3A +#define GL_UNIFORM_OFFSET 0x8A3B +#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C +#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D +#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E +#define GL_UNIFORM_BLOCK_BINDING 0x8A3F +#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 +#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 +#define GL_INVALID_INDEX 0xFFFFFFFF +#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define GL_LINES_ADJACENCY 0x000A +#define GL_LINE_STRIP_ADJACENCY 0x000B +#define GL_TRIANGLES_ADJACENCY 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY 0x000D +#define GL_PROGRAM_POINT_SIZE 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS 0x8DA8 +#define GL_GEOMETRY_SHADER 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT 0x8916 +#define GL_GEOMETRY_INPUT_TYPE 0x8917 +#define GL_GEOMETRY_OUTPUT_TYPE 0x8918 +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 0x8DE1 +#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 +#define GL_MAX_GEOMETRY_INPUT_COMPONENTS 0x9123 +#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS 0x9124 +#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 +#define GL_CONTEXT_PROFILE_MASK 0x9126 +#define GL_DEPTH_CLAMP 0x864F +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION 0x8E4D +#define GL_LAST_VERTEX_CONVENTION 0x8E4E +#define GL_PROVOKING_VERTEX 0x8E4F +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 +#define GL_OBJECT_TYPE 0x9112 +#define GL_SYNC_CONDITION 0x9113 +#define GL_SYNC_STATUS 0x9114 +#define GL_SYNC_FLAGS 0x9115 +#define GL_SYNC_FENCE 0x9116 +#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 +#define GL_UNSIGNALED 0x9118 +#define GL_SIGNALED 0x9119 +#define GL_ALREADY_SIGNALED 0x911A +#define GL_TIMEOUT_EXPIRED 0x911B +#define GL_CONDITION_SATISFIED 0x911C +#define GL_WAIT_FAILED 0x911D +#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFF +#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 +#define GL_SAMPLE_POSITION 0x8E50 +#define GL_SAMPLE_MASK 0x8E51 +#define GL_SAMPLE_MASK_VALUE 0x8E52 +#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59 +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE 0x9101 +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9103 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE 0x9104 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY 0x9105 +#define GL_TEXTURE_SAMPLES 0x9106 +#define GL_TEXTURE_FIXED_SAMPLE_LOCATIONS 0x9107 +#define GL_SAMPLER_2D_MULTISAMPLE 0x9108 +#define GL_INT_SAMPLER_2D_MULTISAMPLE 0x9109 +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE 0x910A +#define GL_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910B +#define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910C +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910D +#define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E +#define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F +#define GL_MAX_INTEGER_SAMPLES 0x9110 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE +#define GL_SRC1_COLOR 0x88F9 +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB +#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC +#define GL_ANY_SAMPLES_PASSED 0x8C2F +#define GL_SAMPLER_BINDING 0x8919 +#define GL_RGB10_A2UI 0x906F +#define GL_TEXTURE_SWIZZLE_R 0x8E42 +#define GL_TEXTURE_SWIZZLE_G 0x8E43 +#define GL_TEXTURE_SWIZZLE_B 0x8E44 +#define GL_TEXTURE_SWIZZLE_A 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 +#define GL_TIME_ELAPSED 0x88BF +#define GL_TIMESTAMP 0x8E28 +#define GL_INT_2_10_10_10_REV 0x8D9F +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_FIXED 0x140C +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB +#define GL_MAX_VARYING_VECTORS 0x8DFC +#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD +#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B +#define GL_SHADER_COMPILER 0x8DFA +#define GL_SHADER_BINARY_FORMATS 0x8DF8 +#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 +#define GL_LOW_FLOAT 0x8DF0 +#define GL_MEDIUM_FLOAT 0x8DF1 +#define GL_HIGH_FLOAT 0x8DF2 +#define GL_LOW_INT 0x8DF3 +#define GL_MEDIUM_INT 0x8DF4 +#define GL_HIGH_INT 0x8DF5 +#define GL_RGB565 0x8D62 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9 +#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 +#define GL_COPY_READ_BUFFER_BINDING 0x8F36 +#define GL_COPY_WRITE_BUFFER_BINDING 0x8F37 +#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE 0x8D6A +#define GL_TRANSFORM_FEEDBACK 0x8E22 +#define GL_TRANSFORM_FEEDBACK_PAUSED 0x8E23 +#define GL_TRANSFORM_FEEDBACK_ACTIVE 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25 +#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 +#define GL_PROGRAM_BINARY_LENGTH 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE +#define GL_PROGRAM_BINARY_FORMATS 0x87FF +#define GL_COMPRESSED_R11_EAC 0x9270 +#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 +#define GL_COMPRESSED_RG11_EAC 0x9272 +#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 +#define GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GL_COMPRESSED_SRGB8_ETC2 0x9275 +#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 +#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 +#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F +#define GL_MAX_ELEMENT_INDEX 0x8D6B +#define GL_NUM_SAMPLE_COUNTS 0x9380 +#define GL_TEXTURE_IMMUTABLE_LEVELS 0x82DF +#define GL_COMPUTE_SHADER 0x91B9 +#define GL_MAX_COMPUTE_UNIFORM_BLOCKS 0x91BB +#define GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS 0x91BC +#define GL_MAX_COMPUTE_IMAGE_UNIFORMS 0x91BD +#define GL_MAX_COMPUTE_SHARED_MEMORY_SIZE 0x8262 +#define GL_MAX_COMPUTE_UNIFORM_COMPONENTS 0x8263 +#define GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS 0x8264 +#define GL_MAX_COMPUTE_ATOMIC_COUNTERS 0x8265 +#define GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS 0x8266 +#define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB +#define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE +#define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF +#define GL_COMPUTE_WORK_GROUP_SIZE 0x8267 +#define GL_DISPATCH_INDIRECT_BUFFER 0x90EE +#define GL_DISPATCH_INDIRECT_BUFFER_BINDING 0x90EF +#define GL_COMPUTE_SHADER_BIT 0x00000020 +#define GL_DRAW_INDIRECT_BUFFER 0x8F3F +#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 +#define GL_MAX_UNIFORM_LOCATIONS 0x826E +#define GL_FRAMEBUFFER_DEFAULT_WIDTH 0x9310 +#define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311 +#define GL_FRAMEBUFFER_DEFAULT_SAMPLES 0x9313 +#define GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS 0x9314 +#define GL_MAX_FRAMEBUFFER_WIDTH 0x9315 +#define GL_MAX_FRAMEBUFFER_HEIGHT 0x9316 +#define GL_MAX_FRAMEBUFFER_SAMPLES 0x9318 +#define GL_UNIFORM 0x92E1 +#define GL_UNIFORM_BLOCK 0x92E2 +#define GL_PROGRAM_INPUT 0x92E3 +#define GL_PROGRAM_OUTPUT 0x92E4 +#define GL_BUFFER_VARIABLE 0x92E5 +#define GL_SHADER_STORAGE_BLOCK 0x92E6 +#define GL_ATOMIC_COUNTER_BUFFER 0x92C0 +#define GL_TRANSFORM_FEEDBACK_VARYING 0x92F4 +#define GL_ACTIVE_RESOURCES 0x92F5 +#define GL_MAX_NAME_LENGTH 0x92F6 +#define GL_MAX_NUM_ACTIVE_VARIABLES 0x92F7 +#define GL_NAME_LENGTH 0x92F9 +#define GL_TYPE 0x92FA +#define GL_ARRAY_SIZE 0x92FB +#define GL_OFFSET 0x92FC +#define GL_BLOCK_INDEX 0x92FD +#define GL_ARRAY_STRIDE 0x92FE +#define GL_MATRIX_STRIDE 0x92FF +#define GL_IS_ROW_MAJOR 0x9300 +#define GL_ATOMIC_COUNTER_BUFFER_INDEX 0x9301 +#define GL_BUFFER_BINDING 0x9302 +#define GL_BUFFER_DATA_SIZE 0x9303 +#define GL_NUM_ACTIVE_VARIABLES 0x9304 +#define GL_ACTIVE_VARIABLES 0x9305 +#define GL_REFERENCED_BY_VERTEX_SHADER 0x9306 +#define GL_REFERENCED_BY_FRAGMENT_SHADER 0x930A +#define GL_REFERENCED_BY_COMPUTE_SHADER 0x930B +#define GL_TOP_LEVEL_ARRAY_SIZE 0x930C +#define GL_TOP_LEVEL_ARRAY_STRIDE 0x930D +#define GL_LOCATION 0x930E +#define GL_VERTEX_SHADER_BIT 0x00000001 +#define GL_FRAGMENT_SHADER_BIT 0x00000002 +#define GL_ALL_SHADER_BITS 0xFFFFFFFF +#define GL_PROGRAM_SEPARABLE 0x8258 +#define GL_ACTIVE_PROGRAM 0x8259 +#define GL_PROGRAM_PIPELINE_BINDING 0x825A +#define GL_ATOMIC_COUNTER_BUFFER_BINDING 0x92C1 +#define GL_ATOMIC_COUNTER_BUFFER_START 0x92C2 +#define GL_ATOMIC_COUNTER_BUFFER_SIZE 0x92C3 +#define GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS 0x92CC +#define GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS 0x92D0 +#define GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS 0x92D1 +#define GL_MAX_VERTEX_ATOMIC_COUNTERS 0x92D2 +#define GL_MAX_FRAGMENT_ATOMIC_COUNTERS 0x92D6 +#define GL_MAX_COMBINED_ATOMIC_COUNTERS 0x92D7 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE 0x92D8 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS 0x92DC +#define GL_ACTIVE_ATOMIC_COUNTER_BUFFERS 0x92D9 +#define GL_UNSIGNED_INT_ATOMIC_COUNTER 0x92DB +#define GL_MAX_IMAGE_UNITS 0x8F38 +#define GL_MAX_VERTEX_IMAGE_UNIFORMS 0x90CA +#define GL_MAX_FRAGMENT_IMAGE_UNIFORMS 0x90CE +#define GL_MAX_COMBINED_IMAGE_UNIFORMS 0x90CF +#define GL_IMAGE_BINDING_NAME 0x8F3A +#define GL_IMAGE_BINDING_LEVEL 0x8F3B +#define GL_IMAGE_BINDING_LAYERED 0x8F3C +#define GL_IMAGE_BINDING_LAYER 0x8F3D +#define GL_IMAGE_BINDING_ACCESS 0x8F3E +#define GL_IMAGE_BINDING_FORMAT 0x906E +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 +#define GL_COMMAND_BARRIER_BIT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT 0x00001000 +#define GL_ALL_BARRIER_BITS 0xFFFFFFFF +#define GL_IMAGE_2D 0x904D +#define GL_IMAGE_3D 0x904E +#define GL_IMAGE_CUBE 0x9050 +#define GL_IMAGE_2D_ARRAY 0x9053 +#define GL_INT_IMAGE_2D 0x9058 +#define GL_INT_IMAGE_3D 0x9059 +#define GL_INT_IMAGE_CUBE 0x905B +#define GL_INT_IMAGE_2D_ARRAY 0x905E +#define GL_UNSIGNED_INT_IMAGE_2D 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D 0x9064 +#define GL_UNSIGNED_INT_IMAGE_CUBE 0x9066 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069 +#define GL_IMAGE_FORMAT_COMPATIBILITY_TYPE 0x90C7 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE 0x90C8 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS 0x90C9 +#define GL_SHADER_STORAGE_BUFFER 0x90D2 +#define GL_SHADER_STORAGE_BUFFER_BINDING 0x90D3 +#define GL_SHADER_STORAGE_BUFFER_START 0x90D4 +#define GL_SHADER_STORAGE_BUFFER_SIZE 0x90D5 +#define GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS 0x90D6 +#define GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS 0x90DA +#define GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS 0x90DB +#define GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS 0x90DC +#define GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS 0x90DD +#define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE +#define GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT 0x90DF +#define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000 +#define GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES 0x8F39 +#define GL_DEPTH_STENCIL_TEXTURE_MODE 0x90EA +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5F +#define GL_VERTEX_ATTRIB_BINDING 0x82D4 +#define GL_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D5 +#define GL_VERTEX_BINDING_DIVISOR 0x82D6 +#define GL_VERTEX_BINDING_OFFSET 0x82D7 +#define GL_VERTEX_BINDING_STRIDE 0x82D8 +#define GL_VERTEX_BINDING_BUFFER 0x8F4F +#define GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D9 +#define GL_MAX_VERTEX_ATTRIB_BINDINGS 0x82DA +#define GL_MAX_VERTEX_ATTRIB_STRIDE 0x82E5 +#ifndef GL_VERSION_1_0 +#define GL_VERSION_1_0 1 +GLAPI int GLAD_GL_VERSION_1_0; +typedef void (APIENTRYP PFNGLCULLFACEPROC)(GLenum mode); +GLAPI PFNGLCULLFACEPROC glad_glCullFace; +#define glCullFace glad_glCullFace +typedef void (APIENTRYP PFNGLFRONTFACEPROC)(GLenum mode); +GLAPI PFNGLFRONTFACEPROC glad_glFrontFace; +#define glFrontFace glad_glFrontFace +typedef void (APIENTRYP PFNGLHINTPROC)(GLenum target, GLenum mode); +GLAPI PFNGLHINTPROC glad_glHint; +#define glHint glad_glHint +typedef void (APIENTRYP PFNGLLINEWIDTHPROC)(GLfloat width); +GLAPI PFNGLLINEWIDTHPROC glad_glLineWidth; +#define glLineWidth glad_glLineWidth +typedef void (APIENTRYP PFNGLPOINTSIZEPROC)(GLfloat size); +GLAPI PFNGLPOINTSIZEPROC glad_glPointSize; +#define glPointSize glad_glPointSize +typedef void (APIENTRYP PFNGLPOLYGONMODEPROC)(GLenum face, GLenum mode); +GLAPI PFNGLPOLYGONMODEPROC glad_glPolygonMode; +#define glPolygonMode glad_glPolygonMode +typedef void (APIENTRYP PFNGLSCISSORPROC)(GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI PFNGLSCISSORPROC glad_glScissor; +#define glScissor glad_glScissor +typedef void (APIENTRYP PFNGLTEXPARAMETERFPROC)(GLenum target, GLenum pname, GLfloat param); +GLAPI PFNGLTEXPARAMETERFPROC glad_glTexParameterf; +#define glTexParameterf glad_glTexParameterf +typedef void (APIENTRYP PFNGLTEXPARAMETERFVPROC)(GLenum target, GLenum pname, const GLfloat *params); +GLAPI PFNGLTEXPARAMETERFVPROC glad_glTexParameterfv; +#define glTexParameterfv glad_glTexParameterfv +typedef void (APIENTRYP PFNGLTEXPARAMETERIPROC)(GLenum target, GLenum pname, GLint param); +GLAPI PFNGLTEXPARAMETERIPROC glad_glTexParameteri; +#define glTexParameteri glad_glTexParameteri +typedef void (APIENTRYP PFNGLTEXPARAMETERIVPROC)(GLenum target, GLenum pname, const GLint *params); +GLAPI PFNGLTEXPARAMETERIVPROC glad_glTexParameteriv; +#define glTexParameteriv glad_glTexParameteriv +typedef void (APIENTRYP PFNGLTEXIMAGE1DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXIMAGE1DPROC glad_glTexImage1D; +#define glTexImage1D glad_glTexImage1D +typedef void (APIENTRYP PFNGLTEXIMAGE2DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXIMAGE2DPROC glad_glTexImage2D; +#define glTexImage2D glad_glTexImage2D +typedef void (APIENTRYP PFNGLDRAWBUFFERPROC)(GLenum buf); +GLAPI PFNGLDRAWBUFFERPROC glad_glDrawBuffer; +#define glDrawBuffer glad_glDrawBuffer +typedef void (APIENTRYP PFNGLCLEARPROC)(GLbitfield mask); +GLAPI PFNGLCLEARPROC glad_glClear; +#define glClear glad_glClear +typedef void (APIENTRYP PFNGLCLEARCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI PFNGLCLEARCOLORPROC glad_glClearColor; +#define glClearColor glad_glClearColor +typedef void (APIENTRYP PFNGLCLEARSTENCILPROC)(GLint s); +GLAPI PFNGLCLEARSTENCILPROC glad_glClearStencil; +#define glClearStencil glad_glClearStencil +typedef void (APIENTRYP PFNGLCLEARDEPTHPROC)(GLdouble depth); +GLAPI PFNGLCLEARDEPTHPROC glad_glClearDepth; +#define glClearDepth glad_glClearDepth +typedef void (APIENTRYP PFNGLSTENCILMASKPROC)(GLuint mask); +GLAPI PFNGLSTENCILMASKPROC glad_glStencilMask; +#define glStencilMask glad_glStencilMask +typedef void (APIENTRYP PFNGLCOLORMASKPROC)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +GLAPI PFNGLCOLORMASKPROC glad_glColorMask; +#define glColorMask glad_glColorMask +typedef void (APIENTRYP PFNGLDEPTHMASKPROC)(GLboolean flag); +GLAPI PFNGLDEPTHMASKPROC glad_glDepthMask; +#define glDepthMask glad_glDepthMask +typedef void (APIENTRYP PFNGLDISABLEPROC)(GLenum cap); +GLAPI PFNGLDISABLEPROC glad_glDisable; +#define glDisable glad_glDisable +typedef void (APIENTRYP PFNGLENABLEPROC)(GLenum cap); +GLAPI PFNGLENABLEPROC glad_glEnable; +#define glEnable glad_glEnable +typedef void (APIENTRYP PFNGLFINISHPROC)(void); +GLAPI PFNGLFINISHPROC glad_glFinish; +#define glFinish glad_glFinish +typedef void (APIENTRYP PFNGLFLUSHPROC)(void); +GLAPI PFNGLFLUSHPROC glad_glFlush; +#define glFlush glad_glFlush +typedef void (APIENTRYP PFNGLBLENDFUNCPROC)(GLenum sfactor, GLenum dfactor); +GLAPI PFNGLBLENDFUNCPROC glad_glBlendFunc; +#define glBlendFunc glad_glBlendFunc +typedef void (APIENTRYP PFNGLLOGICOPPROC)(GLenum opcode); +GLAPI PFNGLLOGICOPPROC glad_glLogicOp; +#define glLogicOp glad_glLogicOp +typedef void (APIENTRYP PFNGLSTENCILFUNCPROC)(GLenum func, GLint ref, GLuint mask); +GLAPI PFNGLSTENCILFUNCPROC glad_glStencilFunc; +#define glStencilFunc glad_glStencilFunc +typedef void (APIENTRYP PFNGLSTENCILOPPROC)(GLenum fail, GLenum zfail, GLenum zpass); +GLAPI PFNGLSTENCILOPPROC glad_glStencilOp; +#define glStencilOp glad_glStencilOp +typedef void (APIENTRYP PFNGLDEPTHFUNCPROC)(GLenum func); +GLAPI PFNGLDEPTHFUNCPROC glad_glDepthFunc; +#define glDepthFunc glad_glDepthFunc +typedef void (APIENTRYP PFNGLPIXELSTOREFPROC)(GLenum pname, GLfloat param); +GLAPI PFNGLPIXELSTOREFPROC glad_glPixelStoref; +#define glPixelStoref glad_glPixelStoref +typedef void (APIENTRYP PFNGLPIXELSTOREIPROC)(GLenum pname, GLint param); +GLAPI PFNGLPIXELSTOREIPROC glad_glPixelStorei; +#define glPixelStorei glad_glPixelStorei +typedef void (APIENTRYP PFNGLREADBUFFERPROC)(GLenum src); +GLAPI PFNGLREADBUFFERPROC glad_glReadBuffer; +#define glReadBuffer glad_glReadBuffer +typedef void (APIENTRYP PFNGLREADPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +GLAPI PFNGLREADPIXELSPROC glad_glReadPixels; +#define glReadPixels glad_glReadPixels +typedef void (APIENTRYP PFNGLGETBOOLEANVPROC)(GLenum pname, GLboolean *data); +GLAPI PFNGLGETBOOLEANVPROC glad_glGetBooleanv; +#define glGetBooleanv glad_glGetBooleanv +typedef void (APIENTRYP PFNGLGETDOUBLEVPROC)(GLenum pname, GLdouble *data); +GLAPI PFNGLGETDOUBLEVPROC glad_glGetDoublev; +#define glGetDoublev glad_glGetDoublev +typedef GLenum (APIENTRYP PFNGLGETERRORPROC)(void); +GLAPI PFNGLGETERRORPROC glad_glGetError; +#define glGetError glad_glGetError +typedef void (APIENTRYP PFNGLGETFLOATVPROC)(GLenum pname, GLfloat *data); +GLAPI PFNGLGETFLOATVPROC glad_glGetFloatv; +#define glGetFloatv glad_glGetFloatv +typedef void (APIENTRYP PFNGLGETINTEGERVPROC)(GLenum pname, GLint *data); +GLAPI PFNGLGETINTEGERVPROC glad_glGetIntegerv; +#define glGetIntegerv glad_glGetIntegerv +typedef const GLubyte * (APIENTRYP PFNGLGETSTRINGPROC)(GLenum name); +GLAPI PFNGLGETSTRINGPROC glad_glGetString; +#define glGetString glad_glGetString +typedef void (APIENTRYP PFNGLGETTEXIMAGEPROC)(GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +GLAPI PFNGLGETTEXIMAGEPROC glad_glGetTexImage; +#define glGetTexImage glad_glGetTexImage +typedef void (APIENTRYP PFNGLGETTEXPARAMETERFVPROC)(GLenum target, GLenum pname, GLfloat *params); +GLAPI PFNGLGETTEXPARAMETERFVPROC glad_glGetTexParameterfv; +#define glGetTexParameterfv glad_glGetTexParameterfv +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETTEXPARAMETERIVPROC glad_glGetTexParameteriv; +#define glGetTexParameteriv glad_glGetTexParameteriv +typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERFVPROC)(GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI PFNGLGETTEXLEVELPARAMETERFVPROC glad_glGetTexLevelParameterfv; +#define glGetTexLevelParameterfv glad_glGetTexLevelParameterfv +typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERIVPROC)(GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI PFNGLGETTEXLEVELPARAMETERIVPROC glad_glGetTexLevelParameteriv; +#define glGetTexLevelParameteriv glad_glGetTexLevelParameteriv +typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC)(GLenum cap); +GLAPI PFNGLISENABLEDPROC glad_glIsEnabled; +#define glIsEnabled glad_glIsEnabled +typedef void (APIENTRYP PFNGLDEPTHRANGEPROC)(GLdouble n, GLdouble f); +GLAPI PFNGLDEPTHRANGEPROC glad_glDepthRange; +#define glDepthRange glad_glDepthRange +typedef void (APIENTRYP PFNGLVIEWPORTPROC)(GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI PFNGLVIEWPORTPROC glad_glViewport; +#define glViewport glad_glViewport +#endif +#ifndef GL_VERSION_1_1 +#define GL_VERSION_1_1 1 +GLAPI int GLAD_GL_VERSION_1_1; +typedef void (APIENTRYP PFNGLDRAWARRAYSPROC)(GLenum mode, GLint first, GLsizei count); +GLAPI PFNGLDRAWARRAYSPROC glad_glDrawArrays; +#define glDrawArrays glad_glDrawArrays +typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC)(GLenum mode, GLsizei count, GLenum type, const void *indices); +GLAPI PFNGLDRAWELEMENTSPROC glad_glDrawElements; +#define glDrawElements glad_glDrawElements +typedef void (APIENTRYP PFNGLPOLYGONOFFSETPROC)(GLfloat factor, GLfloat units); +GLAPI PFNGLPOLYGONOFFSETPROC glad_glPolygonOffset; +#define glPolygonOffset glad_glPolygonOffset +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DPROC)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI PFNGLCOPYTEXIMAGE1DPROC glad_glCopyTexImage1D; +#define glCopyTexImage1D glad_glCopyTexImage1D +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DPROC)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI PFNGLCOPYTEXIMAGE2DPROC glad_glCopyTexImage2D; +#define glCopyTexImage2D glad_glCopyTexImage2D +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI PFNGLCOPYTEXSUBIMAGE1DPROC glad_glCopyTexSubImage1D; +#define glCopyTexSubImage1D glad_glCopyTexSubImage1D +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI PFNGLCOPYTEXSUBIMAGE2DPROC glad_glCopyTexSubImage2D; +#define glCopyTexSubImage2D glad_glCopyTexSubImage2D +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXSUBIMAGE1DPROC glad_glTexSubImage1D; +#define glTexSubImage1D glad_glTexSubImage1D +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D; +#define glTexSubImage2D glad_glTexSubImage2D +typedef void (APIENTRYP PFNGLBINDTEXTUREPROC)(GLenum target, GLuint texture); +GLAPI PFNGLBINDTEXTUREPROC glad_glBindTexture; +#define glBindTexture glad_glBindTexture +typedef void (APIENTRYP PFNGLDELETETEXTURESPROC)(GLsizei n, const GLuint *textures); +GLAPI PFNGLDELETETEXTURESPROC glad_glDeleteTextures; +#define glDeleteTextures glad_glDeleteTextures +typedef void (APIENTRYP PFNGLGENTEXTURESPROC)(GLsizei n, GLuint *textures); +GLAPI PFNGLGENTEXTURESPROC glad_glGenTextures; +#define glGenTextures glad_glGenTextures +typedef GLboolean (APIENTRYP PFNGLISTEXTUREPROC)(GLuint texture); +GLAPI PFNGLISTEXTUREPROC glad_glIsTexture; +#define glIsTexture glad_glIsTexture +#endif +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +GLAPI int GLAD_GL_VERSION_1_2; +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +GLAPI PFNGLDRAWRANGEELEMENTSPROC glad_glDrawRangeElements; +#define glDrawRangeElements glad_glDrawRangeElements +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXIMAGE3DPROC glad_glTexImage3D; +#define glTexImage3D glad_glTexImage3D +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI PFNGLTEXSUBIMAGE3DPROC glad_glTexSubImage3D; +#define glTexSubImage3D glad_glTexSubImage3D +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI PFNGLCOPYTEXSUBIMAGE3DPROC glad_glCopyTexSubImage3D; +#define glCopyTexSubImage3D glad_glCopyTexSubImage3D +#endif +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +GLAPI int GLAD_GL_VERSION_1_3; +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC)(GLenum texture); +GLAPI PFNGLACTIVETEXTUREPROC glad_glActiveTexture; +#define glActiveTexture glad_glActiveTexture +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC)(GLfloat value, GLboolean invert); +GLAPI PFNGLSAMPLECOVERAGEPROC glad_glSampleCoverage; +#define glSampleCoverage glad_glSampleCoverage +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXIMAGE3DPROC glad_glCompressedTexImage3D; +#define glCompressedTexImage3D glad_glCompressedTexImage3D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXIMAGE2DPROC glad_glCompressedTexImage2D; +#define glCompressedTexImage2D glad_glCompressedTexImage2D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXIMAGE1DPROC glad_glCompressedTexImage1D; +#define glCompressedTexImage1D glad_glCompressedTexImage1D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_glCompressedTexSubImage3D; +#define glCompressedTexSubImage3D glad_glCompressedTexSubImage3D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_glCompressedTexSubImage2D; +#define glCompressedTexSubImage2D glad_glCompressedTexSubImage2D +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_glCompressedTexSubImage1D; +#define glCompressedTexSubImage1D glad_glCompressedTexSubImage1D +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC)(GLenum target, GLint level, void *img); +GLAPI PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage; +#define glGetCompressedTexImage glad_glGetCompressedTexImage +#endif +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +GLAPI int GLAD_GL_VERSION_1_4; +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC)(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GLAPI PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate; +#define glBlendFuncSeparate glad_glBlendFuncSeparate +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC)(GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); +GLAPI PFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays; +#define glMultiDrawArrays glad_glMultiDrawArrays +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC)(GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); +GLAPI PFNGLMULTIDRAWELEMENTSPROC glad_glMultiDrawElements; +#define glMultiDrawElements glad_glMultiDrawElements +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC)(GLenum pname, GLfloat param); +GLAPI PFNGLPOINTPARAMETERFPROC glad_glPointParameterf; +#define glPointParameterf glad_glPointParameterf +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC)(GLenum pname, const GLfloat *params); +GLAPI PFNGLPOINTPARAMETERFVPROC glad_glPointParameterfv; +#define glPointParameterfv glad_glPointParameterfv +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC)(GLenum pname, GLint param); +GLAPI PFNGLPOINTPARAMETERIPROC glad_glPointParameteri; +#define glPointParameteri glad_glPointParameteri +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC)(GLenum pname, const GLint *params); +GLAPI PFNGLPOINTPARAMETERIVPROC glad_glPointParameteriv; +#define glPointParameteriv glad_glPointParameteriv +typedef void (APIENTRYP PFNGLBLENDCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI PFNGLBLENDCOLORPROC glad_glBlendColor; +#define glBlendColor glad_glBlendColor +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC)(GLenum mode); +GLAPI PFNGLBLENDEQUATIONPROC glad_glBlendEquation; +#define glBlendEquation glad_glBlendEquation +#endif +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +GLAPI int GLAD_GL_VERSION_1_5; +typedef void (APIENTRYP PFNGLGENQUERIESPROC)(GLsizei n, GLuint *ids); +GLAPI PFNGLGENQUERIESPROC glad_glGenQueries; +#define glGenQueries glad_glGenQueries +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC)(GLsizei n, const GLuint *ids); +GLAPI PFNGLDELETEQUERIESPROC glad_glDeleteQueries; +#define glDeleteQueries glad_glDeleteQueries +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC)(GLuint id); +GLAPI PFNGLISQUERYPROC glad_glIsQuery; +#define glIsQuery glad_glIsQuery +typedef void (APIENTRYP PFNGLBEGINQUERYPROC)(GLenum target, GLuint id); +GLAPI PFNGLBEGINQUERYPROC glad_glBeginQuery; +#define glBeginQuery glad_glBeginQuery +typedef void (APIENTRYP PFNGLENDQUERYPROC)(GLenum target); +GLAPI PFNGLENDQUERYPROC glad_glEndQuery; +#define glEndQuery glad_glEndQuery +typedef void (APIENTRYP PFNGLGETQUERYIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETQUERYIVPROC glad_glGetQueryiv; +#define glGetQueryiv glad_glGetQueryiv +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC)(GLuint id, GLenum pname, GLint *params); +GLAPI PFNGLGETQUERYOBJECTIVPROC glad_glGetQueryObjectiv; +#define glGetQueryObjectiv glad_glGetQueryObjectiv +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC)(GLuint id, GLenum pname, GLuint *params); +GLAPI PFNGLGETQUERYOBJECTUIVPROC glad_glGetQueryObjectuiv; +#define glGetQueryObjectuiv glad_glGetQueryObjectuiv +typedef void (APIENTRYP PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer); +GLAPI PFNGLBINDBUFFERPROC glad_glBindBuffer; +#define glBindBuffer glad_glBindBuffer +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint *buffers); +GLAPI PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers; +#define glDeleteBuffers glad_glDeleteBuffers +typedef void (APIENTRYP PFNGLGENBUFFERSPROC)(GLsizei n, GLuint *buffers); +GLAPI PFNGLGENBUFFERSPROC glad_glGenBuffers; +#define glGenBuffers glad_glGenBuffers +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC)(GLuint buffer); +GLAPI PFNGLISBUFFERPROC glad_glIsBuffer; +#define glIsBuffer glad_glIsBuffer +typedef void (APIENTRYP PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void *data, GLenum usage); +GLAPI PFNGLBUFFERDATAPROC glad_glBufferData; +#define glBufferData glad_glBufferData +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI PFNGLBUFFERSUBDATAPROC glad_glBufferSubData; +#define glBufferSubData glad_glBufferSubData +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, void *data); +GLAPI PFNGLGETBUFFERSUBDATAPROC glad_glGetBufferSubData; +#define glGetBufferSubData glad_glGetBufferSubData +typedef void * (APIENTRYP PFNGLMAPBUFFERPROC)(GLenum target, GLenum access); +GLAPI PFNGLMAPBUFFERPROC glad_glMapBuffer; +#define glMapBuffer glad_glMapBuffer +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC)(GLenum target); +GLAPI PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer; +#define glUnmapBuffer glad_glUnmapBuffer +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETBUFFERPARAMETERIVPROC glad_glGetBufferParameteriv; +#define glGetBufferParameteriv glad_glGetBufferParameteriv +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC)(GLenum target, GLenum pname, void **params); +GLAPI PFNGLGETBUFFERPOINTERVPROC glad_glGetBufferPointerv; +#define glGetBufferPointerv glad_glGetBufferPointerv +#endif +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 +GLAPI int GLAD_GL_VERSION_2_0; +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC)(GLenum modeRGB, GLenum modeAlpha); +GLAPI PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate; +#define glBlendEquationSeparate glad_glBlendEquationSeparate +typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC)(GLsizei n, const GLenum *bufs); +GLAPI PFNGLDRAWBUFFERSPROC glad_glDrawBuffers; +#define glDrawBuffers glad_glDrawBuffers +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC)(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate; +#define glStencilOpSeparate glad_glStencilOpSeparate +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC)(GLenum face, GLenum func, GLint ref, GLuint mask); +GLAPI PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate; +#define glStencilFuncSeparate glad_glStencilFuncSeparate +typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC)(GLenum face, GLuint mask); +GLAPI PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate; +#define glStencilMaskSeparate glad_glStencilMaskSeparate +typedef void (APIENTRYP PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader); +GLAPI PFNGLATTACHSHADERPROC glad_glAttachShader; +#define glAttachShader glad_glAttachShader +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC)(GLuint program, GLuint index, const GLchar *name); +GLAPI PFNGLBINDATTRIBLOCATIONPROC glad_glBindAttribLocation; +#define glBindAttribLocation glad_glBindAttribLocation +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC)(GLuint shader); +GLAPI PFNGLCOMPILESHADERPROC glad_glCompileShader; +#define glCompileShader glad_glCompileShader +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC)(void); +GLAPI PFNGLCREATEPROGRAMPROC glad_glCreateProgram; +#define glCreateProgram glad_glCreateProgram +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC)(GLenum type); +GLAPI PFNGLCREATESHADERPROC glad_glCreateShader; +#define glCreateShader glad_glCreateShader +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC)(GLuint program); +GLAPI PFNGLDELETEPROGRAMPROC glad_glDeleteProgram; +#define glDeleteProgram glad_glDeleteProgram +typedef void (APIENTRYP PFNGLDELETESHADERPROC)(GLuint shader); +GLAPI PFNGLDELETESHADERPROC glad_glDeleteShader; +#define glDeleteShader glad_glDeleteShader +typedef void (APIENTRYP PFNGLDETACHSHADERPROC)(GLuint program, GLuint shader); +GLAPI PFNGLDETACHSHADERPROC glad_glDetachShader; +#define glDetachShader glad_glDetachShader +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC)(GLuint index); +GLAPI PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray; +#define glDisableVertexAttribArray glad_glDisableVertexAttribArray +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index); +GLAPI PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray; +#define glEnableVertexAttribArray glad_glEnableVertexAttribArray +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI PFNGLGETACTIVEATTRIBPROC glad_glGetActiveAttrib; +#define glGetActiveAttrib glad_glGetActiveAttrib +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI PFNGLGETACTIVEUNIFORMPROC glad_glGetActiveUniform; +#define glGetActiveUniform glad_glGetActiveUniform +typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC)(GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +GLAPI PFNGLGETATTACHEDSHADERSPROC glad_glGetAttachedShaders; +#define glGetAttachedShaders glad_glGetAttachedShaders +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC)(GLuint program, const GLchar *name); +GLAPI PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation; +#define glGetAttribLocation glad_glGetAttribLocation +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, GLint *params); +GLAPI PFNGLGETPROGRAMIVPROC glad_glGetProgramiv; +#define glGetProgramiv glad_glGetProgramiv +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC)(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog; +#define glGetProgramInfoLog glad_glGetProgramInfoLog +typedef void (APIENTRYP PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, GLint *params); +GLAPI PFNGLGETSHADERIVPROC glad_glGetShaderiv; +#define glGetShaderiv glad_glGetShaderiv +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog; +#define glGetShaderInfoLog glad_glGetShaderInfoLog +typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +GLAPI PFNGLGETSHADERSOURCEPROC glad_glGetShaderSource; +#define glGetShaderSource glad_glGetShaderSource +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar *name); +GLAPI PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation; +#define glGetUniformLocation glad_glGetUniformLocation +typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC)(GLuint program, GLint location, GLfloat *params); +GLAPI PFNGLGETUNIFORMFVPROC glad_glGetUniformfv; +#define glGetUniformfv glad_glGetUniformfv +typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC)(GLuint program, GLint location, GLint *params); +GLAPI PFNGLGETUNIFORMIVPROC glad_glGetUniformiv; +#define glGetUniformiv glad_glGetUniformiv +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC)(GLuint index, GLenum pname, GLdouble *params); +GLAPI PFNGLGETVERTEXATTRIBDVPROC glad_glGetVertexAttribdv; +#define glGetVertexAttribdv glad_glGetVertexAttribdv +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC)(GLuint index, GLenum pname, GLfloat *params); +GLAPI PFNGLGETVERTEXATTRIBFVPROC glad_glGetVertexAttribfv; +#define glGetVertexAttribfv glad_glGetVertexAttribfv +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC)(GLuint index, GLenum pname, GLint *params); +GLAPI PFNGLGETVERTEXATTRIBIVPROC glad_glGetVertexAttribiv; +#define glGetVertexAttribiv glad_glGetVertexAttribiv +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC)(GLuint index, GLenum pname, void **pointer); +GLAPI PFNGLGETVERTEXATTRIBPOINTERVPROC glad_glGetVertexAttribPointerv; +#define glGetVertexAttribPointerv glad_glGetVertexAttribPointerv +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC)(GLuint program); +GLAPI PFNGLISPROGRAMPROC glad_glIsProgram; +#define glIsProgram glad_glIsProgram +typedef GLboolean (APIENTRYP PFNGLISSHADERPROC)(GLuint shader); +GLAPI PFNGLISSHADERPROC glad_glIsShader; +#define glIsShader glad_glIsShader +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC)(GLuint program); +GLAPI PFNGLLINKPROGRAMPROC glad_glLinkProgram; +#define glLinkProgram glad_glLinkProgram +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC)(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +GLAPI PFNGLSHADERSOURCEPROC glad_glShaderSource; +#define glShaderSource glad_glShaderSource +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC)(GLuint program); +GLAPI PFNGLUSEPROGRAMPROC glad_glUseProgram; +#define glUseProgram glad_glUseProgram +typedef void (APIENTRYP PFNGLUNIFORM1FPROC)(GLint location, GLfloat v0); +GLAPI PFNGLUNIFORM1FPROC glad_glUniform1f; +#define glUniform1f glad_glUniform1f +typedef void (APIENTRYP PFNGLUNIFORM2FPROC)(GLint location, GLfloat v0, GLfloat v1); +GLAPI PFNGLUNIFORM2FPROC glad_glUniform2f; +#define glUniform2f glad_glUniform2f +typedef void (APIENTRYP PFNGLUNIFORM3FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI PFNGLUNIFORM3FPROC glad_glUniform3f; +#define glUniform3f glad_glUniform3f +typedef void (APIENTRYP PFNGLUNIFORM4FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI PFNGLUNIFORM4FPROC glad_glUniform4f; +#define glUniform4f glad_glUniform4f +typedef void (APIENTRYP PFNGLUNIFORM1IPROC)(GLint location, GLint v0); +GLAPI PFNGLUNIFORM1IPROC glad_glUniform1i; +#define glUniform1i glad_glUniform1i +typedef void (APIENTRYP PFNGLUNIFORM2IPROC)(GLint location, GLint v0, GLint v1); +GLAPI PFNGLUNIFORM2IPROC glad_glUniform2i; +#define glUniform2i glad_glUniform2i +typedef void (APIENTRYP PFNGLUNIFORM3IPROC)(GLint location, GLint v0, GLint v1, GLint v2); +GLAPI PFNGLUNIFORM3IPROC glad_glUniform3i; +#define glUniform3i glad_glUniform3i +typedef void (APIENTRYP PFNGLUNIFORM4IPROC)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI PFNGLUNIFORM4IPROC glad_glUniform4i; +#define glUniform4i glad_glUniform4i +typedef void (APIENTRYP PFNGLUNIFORM1FVPROC)(GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLUNIFORM1FVPROC glad_glUniform1fv; +#define glUniform1fv glad_glUniform1fv +typedef void (APIENTRYP PFNGLUNIFORM2FVPROC)(GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLUNIFORM2FVPROC glad_glUniform2fv; +#define glUniform2fv glad_glUniform2fv +typedef void (APIENTRYP PFNGLUNIFORM3FVPROC)(GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLUNIFORM3FVPROC glad_glUniform3fv; +#define glUniform3fv glad_glUniform3fv +typedef void (APIENTRYP PFNGLUNIFORM4FVPROC)(GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLUNIFORM4FVPROC glad_glUniform4fv; +#define glUniform4fv glad_glUniform4fv +typedef void (APIENTRYP PFNGLUNIFORM1IVPROC)(GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLUNIFORM1IVPROC glad_glUniform1iv; +#define glUniform1iv glad_glUniform1iv +typedef void (APIENTRYP PFNGLUNIFORM2IVPROC)(GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLUNIFORM2IVPROC glad_glUniform2iv; +#define glUniform2iv glad_glUniform2iv +typedef void (APIENTRYP PFNGLUNIFORM3IVPROC)(GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLUNIFORM3IVPROC glad_glUniform3iv; +#define glUniform3iv glad_glUniform3iv +typedef void (APIENTRYP PFNGLUNIFORM4IVPROC)(GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLUNIFORM4IVPROC glad_glUniform4iv; +#define glUniform4iv glad_glUniform4iv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX2FVPROC glad_glUniformMatrix2fv; +#define glUniformMatrix2fv glad_glUniformMatrix2fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX3FVPROC glad_glUniformMatrix3fv; +#define glUniformMatrix3fv glad_glUniformMatrix3fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv; +#define glUniformMatrix4fv glad_glUniformMatrix4fv +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC)(GLuint program); +GLAPI PFNGLVALIDATEPROGRAMPROC glad_glValidateProgram; +#define glValidateProgram glad_glValidateProgram +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC)(GLuint index, GLdouble x); +GLAPI PFNGLVERTEXATTRIB1DPROC glad_glVertexAttrib1d; +#define glVertexAttrib1d glad_glVertexAttrib1d +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC)(GLuint index, const GLdouble *v); +GLAPI PFNGLVERTEXATTRIB1DVPROC glad_glVertexAttrib1dv; +#define glVertexAttrib1dv glad_glVertexAttrib1dv +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC)(GLuint index, GLfloat x); +GLAPI PFNGLVERTEXATTRIB1FPROC glad_glVertexAttrib1f; +#define glVertexAttrib1f glad_glVertexAttrib1f +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC)(GLuint index, const GLfloat *v); +GLAPI PFNGLVERTEXATTRIB1FVPROC glad_glVertexAttrib1fv; +#define glVertexAttrib1fv glad_glVertexAttrib1fv +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC)(GLuint index, GLshort x); +GLAPI PFNGLVERTEXATTRIB1SPROC glad_glVertexAttrib1s; +#define glVertexAttrib1s glad_glVertexAttrib1s +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB1SVPROC glad_glVertexAttrib1sv; +#define glVertexAttrib1sv glad_glVertexAttrib1sv +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC)(GLuint index, GLdouble x, GLdouble y); +GLAPI PFNGLVERTEXATTRIB2DPROC glad_glVertexAttrib2d; +#define glVertexAttrib2d glad_glVertexAttrib2d +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC)(GLuint index, const GLdouble *v); +GLAPI PFNGLVERTEXATTRIB2DVPROC glad_glVertexAttrib2dv; +#define glVertexAttrib2dv glad_glVertexAttrib2dv +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC)(GLuint index, GLfloat x, GLfloat y); +GLAPI PFNGLVERTEXATTRIB2FPROC glad_glVertexAttrib2f; +#define glVertexAttrib2f glad_glVertexAttrib2f +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC)(GLuint index, const GLfloat *v); +GLAPI PFNGLVERTEXATTRIB2FVPROC glad_glVertexAttrib2fv; +#define glVertexAttrib2fv glad_glVertexAttrib2fv +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC)(GLuint index, GLshort x, GLshort y); +GLAPI PFNGLVERTEXATTRIB2SPROC glad_glVertexAttrib2s; +#define glVertexAttrib2s glad_glVertexAttrib2s +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB2SVPROC glad_glVertexAttrib2sv; +#define glVertexAttrib2sv glad_glVertexAttrib2sv +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI PFNGLVERTEXATTRIB3DPROC glad_glVertexAttrib3d; +#define glVertexAttrib3d glad_glVertexAttrib3d +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC)(GLuint index, const GLdouble *v); +GLAPI PFNGLVERTEXATTRIB3DVPROC glad_glVertexAttrib3dv; +#define glVertexAttrib3dv glad_glVertexAttrib3dv +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC)(GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI PFNGLVERTEXATTRIB3FPROC glad_glVertexAttrib3f; +#define glVertexAttrib3f glad_glVertexAttrib3f +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC)(GLuint index, const GLfloat *v); +GLAPI PFNGLVERTEXATTRIB3FVPROC glad_glVertexAttrib3fv; +#define glVertexAttrib3fv glad_glVertexAttrib3fv +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC)(GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI PFNGLVERTEXATTRIB3SPROC glad_glVertexAttrib3s; +#define glVertexAttrib3s glad_glVertexAttrib3s +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB3SVPROC glad_glVertexAttrib3sv; +#define glVertexAttrib3sv glad_glVertexAttrib3sv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC)(GLuint index, const GLbyte *v); +GLAPI PFNGLVERTEXATTRIB4NBVPROC glad_glVertexAttrib4Nbv; +#define glVertexAttrib4Nbv glad_glVertexAttrib4Nbv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC)(GLuint index, const GLint *v); +GLAPI PFNGLVERTEXATTRIB4NIVPROC glad_glVertexAttrib4Niv; +#define glVertexAttrib4Niv glad_glVertexAttrib4Niv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB4NSVPROC glad_glVertexAttrib4Nsv; +#define glVertexAttrib4Nsv glad_glVertexAttrib4Nsv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI PFNGLVERTEXATTRIB4NUBPROC glad_glVertexAttrib4Nub; +#define glVertexAttrib4Nub glad_glVertexAttrib4Nub +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC)(GLuint index, const GLubyte *v); +GLAPI PFNGLVERTEXATTRIB4NUBVPROC glad_glVertexAttrib4Nubv; +#define glVertexAttrib4Nubv glad_glVertexAttrib4Nubv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC)(GLuint index, const GLuint *v); +GLAPI PFNGLVERTEXATTRIB4NUIVPROC glad_glVertexAttrib4Nuiv; +#define glVertexAttrib4Nuiv glad_glVertexAttrib4Nuiv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC)(GLuint index, const GLushort *v); +GLAPI PFNGLVERTEXATTRIB4NUSVPROC glad_glVertexAttrib4Nusv; +#define glVertexAttrib4Nusv glad_glVertexAttrib4Nusv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC)(GLuint index, const GLbyte *v); +GLAPI PFNGLVERTEXATTRIB4BVPROC glad_glVertexAttrib4bv; +#define glVertexAttrib4bv glad_glVertexAttrib4bv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI PFNGLVERTEXATTRIB4DPROC glad_glVertexAttrib4d; +#define glVertexAttrib4d glad_glVertexAttrib4d +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC)(GLuint index, const GLdouble *v); +GLAPI PFNGLVERTEXATTRIB4DVPROC glad_glVertexAttrib4dv; +#define glVertexAttrib4dv glad_glVertexAttrib4dv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC)(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI PFNGLVERTEXATTRIB4FPROC glad_glVertexAttrib4f; +#define glVertexAttrib4f glad_glVertexAttrib4f +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC)(GLuint index, const GLfloat *v); +GLAPI PFNGLVERTEXATTRIB4FVPROC glad_glVertexAttrib4fv; +#define glVertexAttrib4fv glad_glVertexAttrib4fv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC)(GLuint index, const GLint *v); +GLAPI PFNGLVERTEXATTRIB4IVPROC glad_glVertexAttrib4iv; +#define glVertexAttrib4iv glad_glVertexAttrib4iv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC)(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI PFNGLVERTEXATTRIB4SPROC glad_glVertexAttrib4s; +#define glVertexAttrib4s glad_glVertexAttrib4s +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIB4SVPROC glad_glVertexAttrib4sv; +#define glVertexAttrib4sv glad_glVertexAttrib4sv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC)(GLuint index, const GLubyte *v); +GLAPI PFNGLVERTEXATTRIB4UBVPROC glad_glVertexAttrib4ubv; +#define glVertexAttrib4ubv glad_glVertexAttrib4ubv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC)(GLuint index, const GLuint *v); +GLAPI PFNGLVERTEXATTRIB4UIVPROC glad_glVertexAttrib4uiv; +#define glVertexAttrib4uiv glad_glVertexAttrib4uiv +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC)(GLuint index, const GLushort *v); +GLAPI PFNGLVERTEXATTRIB4USVPROC glad_glVertexAttrib4usv; +#define glVertexAttrib4usv glad_glVertexAttrib4usv +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +GLAPI PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer; +#define glVertexAttribPointer glad_glVertexAttribPointer +#endif +#ifndef GL_VERSION_2_1 +#define GL_VERSION_2_1 1 +GLAPI int GLAD_GL_VERSION_2_1; +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX2X3FVPROC glad_glUniformMatrix2x3fv; +#define glUniformMatrix2x3fv glad_glUniformMatrix2x3fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX3X2FVPROC glad_glUniformMatrix3x2fv; +#define glUniformMatrix3x2fv glad_glUniformMatrix3x2fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX2X4FVPROC glad_glUniformMatrix2x4fv; +#define glUniformMatrix2x4fv glad_glUniformMatrix2x4fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX4X2FVPROC glad_glUniformMatrix4x2fv; +#define glUniformMatrix4x2fv glad_glUniformMatrix4x2fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX3X4FVPROC glad_glUniformMatrix3x4fv; +#define glUniformMatrix3x4fv glad_glUniformMatrix3x4fv +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLUNIFORMMATRIX4X3FVPROC glad_glUniformMatrix4x3fv; +#define glUniformMatrix4x3fv glad_glUniformMatrix4x3fv +#endif +#ifndef GL_VERSION_3_0 +#define GL_VERSION_3_0 1 +GLAPI int GLAD_GL_VERSION_3_0; +typedef void (APIENTRYP PFNGLCOLORMASKIPROC)(GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GLAPI PFNGLCOLORMASKIPROC glad_glColorMaski; +#define glColorMaski glad_glColorMaski +typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC)(GLenum target, GLuint index, GLboolean *data); +GLAPI PFNGLGETBOOLEANI_VPROC glad_glGetBooleani_v; +#define glGetBooleani_v glad_glGetBooleani_v +typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC)(GLenum target, GLuint index, GLint *data); +GLAPI PFNGLGETINTEGERI_VPROC glad_glGetIntegeri_v; +#define glGetIntegeri_v glad_glGetIntegeri_v +typedef void (APIENTRYP PFNGLENABLEIPROC)(GLenum target, GLuint index); +GLAPI PFNGLENABLEIPROC glad_glEnablei; +#define glEnablei glad_glEnablei +typedef void (APIENTRYP PFNGLDISABLEIPROC)(GLenum target, GLuint index); +GLAPI PFNGLDISABLEIPROC glad_glDisablei; +#define glDisablei glad_glDisablei +typedef GLboolean (APIENTRYP PFNGLISENABLEDIPROC)(GLenum target, GLuint index); +GLAPI PFNGLISENABLEDIPROC glad_glIsEnabledi; +#define glIsEnabledi glad_glIsEnabledi +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC)(GLenum primitiveMode); +GLAPI PFNGLBEGINTRANSFORMFEEDBACKPROC glad_glBeginTransformFeedback; +#define glBeginTransformFeedback glad_glBeginTransformFeedback +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC)(void); +GLAPI PFNGLENDTRANSFORMFEEDBACKPROC glad_glEndTransformFeedback; +#define glEndTransformFeedback glad_glEndTransformFeedback +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEPROC)(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI PFNGLBINDBUFFERRANGEPROC glad_glBindBufferRange; +#define glBindBufferRange glad_glBindBufferRange +typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC)(GLenum target, GLuint index, GLuint buffer); +GLAPI PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase; +#define glBindBufferBase glad_glBindBufferBase +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSPROC)(GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +GLAPI PFNGLTRANSFORMFEEDBACKVARYINGSPROC glad_glTransformFeedbackVaryings; +#define glTransformFeedbackVaryings glad_glTransformFeedbackVaryings +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI PFNGLGETTRANSFORMFEEDBACKVARYINGPROC glad_glGetTransformFeedbackVarying; +#define glGetTransformFeedbackVarying glad_glGetTransformFeedbackVarying +typedef void (APIENTRYP PFNGLCLAMPCOLORPROC)(GLenum target, GLenum clamp); +GLAPI PFNGLCLAMPCOLORPROC glad_glClampColor; +#define glClampColor glad_glClampColor +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC)(GLuint id, GLenum mode); +GLAPI PFNGLBEGINCONDITIONALRENDERPROC glad_glBeginConditionalRender; +#define glBeginConditionalRender glad_glBeginConditionalRender +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC)(void); +GLAPI PFNGLENDCONDITIONALRENDERPROC glad_glEndConditionalRender; +#define glEndConditionalRender glad_glEndConditionalRender +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTERPROC)(GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI PFNGLVERTEXATTRIBIPOINTERPROC glad_glVertexAttribIPointer; +#define glVertexAttribIPointer glad_glVertexAttribIPointer +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVPROC)(GLuint index, GLenum pname, GLint *params); +GLAPI PFNGLGETVERTEXATTRIBIIVPROC glad_glGetVertexAttribIiv; +#define glGetVertexAttribIiv glad_glGetVertexAttribIiv +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVPROC)(GLuint index, GLenum pname, GLuint *params); +GLAPI PFNGLGETVERTEXATTRIBIUIVPROC glad_glGetVertexAttribIuiv; +#define glGetVertexAttribIuiv glad_glGetVertexAttribIuiv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IPROC)(GLuint index, GLint x); +GLAPI PFNGLVERTEXATTRIBI1IPROC glad_glVertexAttribI1i; +#define glVertexAttribI1i glad_glVertexAttribI1i +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IPROC)(GLuint index, GLint x, GLint y); +GLAPI PFNGLVERTEXATTRIBI2IPROC glad_glVertexAttribI2i; +#define glVertexAttribI2i glad_glVertexAttribI2i +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IPROC)(GLuint index, GLint x, GLint y, GLint z); +GLAPI PFNGLVERTEXATTRIBI3IPROC glad_glVertexAttribI3i; +#define glVertexAttribI3i glad_glVertexAttribI3i +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IPROC)(GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI PFNGLVERTEXATTRIBI4IPROC glad_glVertexAttribI4i; +#define glVertexAttribI4i glad_glVertexAttribI4i +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIPROC)(GLuint index, GLuint x); +GLAPI PFNGLVERTEXATTRIBI1UIPROC glad_glVertexAttribI1ui; +#define glVertexAttribI1ui glad_glVertexAttribI1ui +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIPROC)(GLuint index, GLuint x, GLuint y); +GLAPI PFNGLVERTEXATTRIBI2UIPROC glad_glVertexAttribI2ui; +#define glVertexAttribI2ui glad_glVertexAttribI2ui +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIPROC)(GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI PFNGLVERTEXATTRIBI3UIPROC glad_glVertexAttribI3ui; +#define glVertexAttribI3ui glad_glVertexAttribI3ui +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIPROC)(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI PFNGLVERTEXATTRIBI4UIPROC glad_glVertexAttribI4ui; +#define glVertexAttribI4ui glad_glVertexAttribI4ui +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVPROC)(GLuint index, const GLint *v); +GLAPI PFNGLVERTEXATTRIBI1IVPROC glad_glVertexAttribI1iv; +#define glVertexAttribI1iv glad_glVertexAttribI1iv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVPROC)(GLuint index, const GLint *v); +GLAPI PFNGLVERTEXATTRIBI2IVPROC glad_glVertexAttribI2iv; +#define glVertexAttribI2iv glad_glVertexAttribI2iv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVPROC)(GLuint index, const GLint *v); +GLAPI PFNGLVERTEXATTRIBI3IVPROC glad_glVertexAttribI3iv; +#define glVertexAttribI3iv glad_glVertexAttribI3iv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVPROC)(GLuint index, const GLint *v); +GLAPI PFNGLVERTEXATTRIBI4IVPROC glad_glVertexAttribI4iv; +#define glVertexAttribI4iv glad_glVertexAttribI4iv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVPROC)(GLuint index, const GLuint *v); +GLAPI PFNGLVERTEXATTRIBI1UIVPROC glad_glVertexAttribI1uiv; +#define glVertexAttribI1uiv glad_glVertexAttribI1uiv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVPROC)(GLuint index, const GLuint *v); +GLAPI PFNGLVERTEXATTRIBI2UIVPROC glad_glVertexAttribI2uiv; +#define glVertexAttribI2uiv glad_glVertexAttribI2uiv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVPROC)(GLuint index, const GLuint *v); +GLAPI PFNGLVERTEXATTRIBI3UIVPROC glad_glVertexAttribI3uiv; +#define glVertexAttribI3uiv glad_glVertexAttribI3uiv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVPROC)(GLuint index, const GLuint *v); +GLAPI PFNGLVERTEXATTRIBI4UIVPROC glad_glVertexAttribI4uiv; +#define glVertexAttribI4uiv glad_glVertexAttribI4uiv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVPROC)(GLuint index, const GLbyte *v); +GLAPI PFNGLVERTEXATTRIBI4BVPROC glad_glVertexAttribI4bv; +#define glVertexAttribI4bv glad_glVertexAttribI4bv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVPROC)(GLuint index, const GLshort *v); +GLAPI PFNGLVERTEXATTRIBI4SVPROC glad_glVertexAttribI4sv; +#define glVertexAttribI4sv glad_glVertexAttribI4sv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVPROC)(GLuint index, const GLubyte *v); +GLAPI PFNGLVERTEXATTRIBI4UBVPROC glad_glVertexAttribI4ubv; +#define glVertexAttribI4ubv glad_glVertexAttribI4ubv +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVPROC)(GLuint index, const GLushort *v); +GLAPI PFNGLVERTEXATTRIBI4USVPROC glad_glVertexAttribI4usv; +#define glVertexAttribI4usv glad_glVertexAttribI4usv +typedef void (APIENTRYP PFNGLGETUNIFORMUIVPROC)(GLuint program, GLint location, GLuint *params); +GLAPI PFNGLGETUNIFORMUIVPROC glad_glGetUniformuiv; +#define glGetUniformuiv glad_glGetUniformuiv +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC)(GLuint program, GLuint color, const GLchar *name); +GLAPI PFNGLBINDFRAGDATALOCATIONPROC glad_glBindFragDataLocation; +#define glBindFragDataLocation glad_glBindFragDataLocation +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONPROC)(GLuint program, const GLchar *name); +GLAPI PFNGLGETFRAGDATALOCATIONPROC glad_glGetFragDataLocation; +#define glGetFragDataLocation glad_glGetFragDataLocation +typedef void (APIENTRYP PFNGLUNIFORM1UIPROC)(GLint location, GLuint v0); +GLAPI PFNGLUNIFORM1UIPROC glad_glUniform1ui; +#define glUniform1ui glad_glUniform1ui +typedef void (APIENTRYP PFNGLUNIFORM2UIPROC)(GLint location, GLuint v0, GLuint v1); +GLAPI PFNGLUNIFORM2UIPROC glad_glUniform2ui; +#define glUniform2ui glad_glUniform2ui +typedef void (APIENTRYP PFNGLUNIFORM3UIPROC)(GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI PFNGLUNIFORM3UIPROC glad_glUniform3ui; +#define glUniform3ui glad_glUniform3ui +typedef void (APIENTRYP PFNGLUNIFORM4UIPROC)(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI PFNGLUNIFORM4UIPROC glad_glUniform4ui; +#define glUniform4ui glad_glUniform4ui +typedef void (APIENTRYP PFNGLUNIFORM1UIVPROC)(GLint location, GLsizei count, const GLuint *value); +GLAPI PFNGLUNIFORM1UIVPROC glad_glUniform1uiv; +#define glUniform1uiv glad_glUniform1uiv +typedef void (APIENTRYP PFNGLUNIFORM2UIVPROC)(GLint location, GLsizei count, const GLuint *value); +GLAPI PFNGLUNIFORM2UIVPROC glad_glUniform2uiv; +#define glUniform2uiv glad_glUniform2uiv +typedef void (APIENTRYP PFNGLUNIFORM3UIVPROC)(GLint location, GLsizei count, const GLuint *value); +GLAPI PFNGLUNIFORM3UIVPROC glad_glUniform3uiv; +#define glUniform3uiv glad_glUniform3uiv +typedef void (APIENTRYP PFNGLUNIFORM4UIVPROC)(GLint location, GLsizei count, const GLuint *value); +GLAPI PFNGLUNIFORM4UIVPROC glad_glUniform4uiv; +#define glUniform4uiv glad_glUniform4uiv +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVPROC)(GLenum target, GLenum pname, const GLint *params); +GLAPI PFNGLTEXPARAMETERIIVPROC glad_glTexParameterIiv; +#define glTexParameterIiv glad_glTexParameterIiv +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVPROC)(GLenum target, GLenum pname, const GLuint *params); +GLAPI PFNGLTEXPARAMETERIUIVPROC glad_glTexParameterIuiv; +#define glTexParameterIuiv glad_glTexParameterIuiv +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETTEXPARAMETERIIVPROC glad_glGetTexParameterIiv; +#define glGetTexParameterIiv glad_glGetTexParameterIiv +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVPROC)(GLenum target, GLenum pname, GLuint *params); +GLAPI PFNGLGETTEXPARAMETERIUIVPROC glad_glGetTexParameterIuiv; +#define glGetTexParameterIuiv glad_glGetTexParameterIuiv +typedef void (APIENTRYP PFNGLCLEARBUFFERIVPROC)(GLenum buffer, GLint drawbuffer, const GLint *value); +GLAPI PFNGLCLEARBUFFERIVPROC glad_glClearBufferiv; +#define glClearBufferiv glad_glClearBufferiv +typedef void (APIENTRYP PFNGLCLEARBUFFERUIVPROC)(GLenum buffer, GLint drawbuffer, const GLuint *value); +GLAPI PFNGLCLEARBUFFERUIVPROC glad_glClearBufferuiv; +#define glClearBufferuiv glad_glClearBufferuiv +typedef void (APIENTRYP PFNGLCLEARBUFFERFVPROC)(GLenum buffer, GLint drawbuffer, const GLfloat *value); +GLAPI PFNGLCLEARBUFFERFVPROC glad_glClearBufferfv; +#define glClearBufferfv glad_glClearBufferfv +typedef void (APIENTRYP PFNGLCLEARBUFFERFIPROC)(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +GLAPI PFNGLCLEARBUFFERFIPROC glad_glClearBufferfi; +#define glClearBufferfi glad_glClearBufferfi +typedef const GLubyte * (APIENTRYP PFNGLGETSTRINGIPROC)(GLenum name, GLuint index); +GLAPI PFNGLGETSTRINGIPROC glad_glGetStringi; +#define glGetStringi glad_glGetStringi +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFERPROC)(GLuint renderbuffer); +GLAPI PFNGLISRENDERBUFFERPROC glad_glIsRenderbuffer; +#define glIsRenderbuffer glad_glIsRenderbuffer +typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC)(GLenum target, GLuint renderbuffer); +GLAPI PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer; +#define glBindRenderbuffer glad_glBindRenderbuffer +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC)(GLsizei n, const GLuint *renderbuffers); +GLAPI PFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers; +#define glDeleteRenderbuffers glad_glDeleteRenderbuffers +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC)(GLsizei n, GLuint *renderbuffers); +GLAPI PFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers; +#define glGenRenderbuffers glad_glGenRenderbuffers +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage; +#define glRenderbufferStorage glad_glRenderbufferStorage +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETRENDERBUFFERPARAMETERIVPROC glad_glGetRenderbufferParameteriv; +#define glGetRenderbufferParameteriv glad_glGetRenderbufferParameteriv +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFERPROC)(GLuint framebuffer); +GLAPI PFNGLISFRAMEBUFFERPROC glad_glIsFramebuffer; +#define glIsFramebuffer glad_glIsFramebuffer +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC)(GLenum target, GLuint framebuffer); +GLAPI PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer; +#define glBindFramebuffer glad_glBindFramebuffer +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC)(GLsizei n, const GLuint *framebuffers); +GLAPI PFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers; +#define glDeleteFramebuffers glad_glDeleteFramebuffers +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC)(GLsizei n, GLuint *framebuffers); +GLAPI PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers; +#define glGenFramebuffers glad_glGenFramebuffers +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC)(GLenum target); +GLAPI PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus; +#define glCheckFramebufferStatus glad_glCheckFramebufferStatus +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI PFNGLFRAMEBUFFERTEXTURE1DPROC glad_glFramebufferTexture1D; +#define glFramebufferTexture1D glad_glFramebufferTexture1D +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI PFNGLFRAMEBUFFERTEXTURE2DPROC glad_glFramebufferTexture2D; +#define glFramebufferTexture2D glad_glFramebufferTexture2D +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI PFNGLFRAMEBUFFERTEXTURE3DPROC glad_glFramebufferTexture3D; +#define glFramebufferTexture3D glad_glFramebufferTexture3D +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer; +#define glFramebufferRenderbuffer glad_glFramebufferRenderbuffer +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)(GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_glGetFramebufferAttachmentParameteriv; +#define glGetFramebufferAttachmentParameteriv glad_glGetFramebufferAttachmentParameteriv +typedef void (APIENTRYP PFNGLGENERATEMIPMAPPROC)(GLenum target); +GLAPI PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap; +#define glGenerateMipmap glad_glGenerateMipmap +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer; +#define glBlitFramebuffer glad_glBlitFramebuffer +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample; +#define glRenderbufferStorageMultisample glad_glRenderbufferStorageMultisample +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERPROC)(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI PFNGLFRAMEBUFFERTEXTURELAYERPROC glad_glFramebufferTextureLayer; +#define glFramebufferTextureLayer glad_glFramebufferTextureLayer +typedef void * (APIENTRYP PFNGLMAPBUFFERRANGEPROC)(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI PFNGLMAPBUFFERRANGEPROC glad_glMapBufferRange; +#define glMapBufferRange glad_glMapBufferRange +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEPROC)(GLenum target, GLintptr offset, GLsizeiptr length); +GLAPI PFNGLFLUSHMAPPEDBUFFERRANGEPROC glad_glFlushMappedBufferRange; +#define glFlushMappedBufferRange glad_glFlushMappedBufferRange +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC)(GLuint array); +GLAPI PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray; +#define glBindVertexArray glad_glBindVertexArray +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC)(GLsizei n, const GLuint *arrays); +GLAPI PFNGLDELETEVERTEXARRAYSPROC glad_glDeleteVertexArrays; +#define glDeleteVertexArrays glad_glDeleteVertexArrays +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint *arrays); +GLAPI PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays; +#define glGenVertexArrays glad_glGenVertexArrays +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYPROC)(GLuint array); +GLAPI PFNGLISVERTEXARRAYPROC glad_glIsVertexArray; +#define glIsVertexArray glad_glIsVertexArray +#endif +#ifndef GL_VERSION_3_1 +#define GL_VERSION_3_1 1 +GLAPI int GLAD_GL_VERSION_3_1; +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDPROC)(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +GLAPI PFNGLDRAWARRAYSINSTANCEDPROC glad_glDrawArraysInstanced; +#define glDrawArraysInstanced glad_glDrawArraysInstanced +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDPROC)(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); +GLAPI PFNGLDRAWELEMENTSINSTANCEDPROC glad_glDrawElementsInstanced; +#define glDrawElementsInstanced glad_glDrawElementsInstanced +typedef void (APIENTRYP PFNGLTEXBUFFERPROC)(GLenum target, GLenum internalformat, GLuint buffer); +GLAPI PFNGLTEXBUFFERPROC glad_glTexBuffer; +#define glTexBuffer glad_glTexBuffer +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXPROC)(GLuint index); +GLAPI PFNGLPRIMITIVERESTARTINDEXPROC glad_glPrimitiveRestartIndex; +#define glPrimitiveRestartIndex glad_glPrimitiveRestartIndex +typedef void (APIENTRYP PFNGLCOPYBUFFERSUBDATAPROC)(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI PFNGLCOPYBUFFERSUBDATAPROC glad_glCopyBufferSubData; +#define glCopyBufferSubData glad_glCopyBufferSubData +typedef void (APIENTRYP PFNGLGETUNIFORMINDICESPROC)(GLuint program, GLsizei uniformCount, const GLchar *const*uniformNames, GLuint *uniformIndices); +GLAPI PFNGLGETUNIFORMINDICESPROC glad_glGetUniformIndices; +#define glGetUniformIndices glad_glGetUniformIndices +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMSIVPROC)(GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +GLAPI PFNGLGETACTIVEUNIFORMSIVPROC glad_glGetActiveUniformsiv; +#define glGetActiveUniformsiv glad_glGetActiveUniformsiv +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMNAMEPROC)(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +GLAPI PFNGLGETACTIVEUNIFORMNAMEPROC glad_glGetActiveUniformName; +#define glGetActiveUniformName glad_glGetActiveUniformName +typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC)(GLuint program, const GLchar *uniformBlockName); +GLAPI PFNGLGETUNIFORMBLOCKINDEXPROC glad_glGetUniformBlockIndex; +#define glGetUniformBlockIndex glad_glGetUniformBlockIndex +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKIVPROC)(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +GLAPI PFNGLGETACTIVEUNIFORMBLOCKIVPROC glad_glGetActiveUniformBlockiv; +#define glGetActiveUniformBlockiv glad_glGetActiveUniformBlockiv +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +GLAPI PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC glad_glGetActiveUniformBlockName; +#define glGetActiveUniformBlockName glad_glGetActiveUniformBlockName +typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC)(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +GLAPI PFNGLUNIFORMBLOCKBINDINGPROC glad_glUniformBlockBinding; +#define glUniformBlockBinding glad_glUniformBlockBinding +#endif +#ifndef GL_VERSION_3_2 +#define GL_VERSION_3_2 1 +GLAPI int GLAD_GL_VERSION_3_2; +typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC)(GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GLAPI PFNGLDRAWELEMENTSBASEVERTEXPROC glad_glDrawElementsBaseVertex; +#define glDrawElementsBaseVertex glad_glDrawElementsBaseVertex +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GLAPI PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC glad_glDrawRangeElementsBaseVertex; +#define glDrawRangeElementsBaseVertex glad_glDrawRangeElementsBaseVertex +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC)(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +GLAPI PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC glad_glDrawElementsInstancedBaseVertex; +#define glDrawElementsInstancedBaseVertex glad_glDrawElementsInstancedBaseVertex +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC)(GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); +GLAPI PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC glad_glMultiDrawElementsBaseVertex; +#define glMultiDrawElementsBaseVertex glad_glMultiDrawElementsBaseVertex +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXPROC)(GLenum mode); +GLAPI PFNGLPROVOKINGVERTEXPROC glad_glProvokingVertex; +#define glProvokingVertex glad_glProvokingVertex +typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC)(GLenum condition, GLbitfield flags); +GLAPI PFNGLFENCESYNCPROC glad_glFenceSync; +#define glFenceSync glad_glFenceSync +typedef GLboolean (APIENTRYP PFNGLISSYNCPROC)(GLsync sync); +GLAPI PFNGLISSYNCPROC glad_glIsSync; +#define glIsSync glad_glIsSync +typedef void (APIENTRYP PFNGLDELETESYNCPROC)(GLsync sync); +GLAPI PFNGLDELETESYNCPROC glad_glDeleteSync; +#define glDeleteSync glad_glDeleteSync +typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC)(GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI PFNGLCLIENTWAITSYNCPROC glad_glClientWaitSync; +#define glClientWaitSync glad_glClientWaitSync +typedef void (APIENTRYP PFNGLWAITSYNCPROC)(GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI PFNGLWAITSYNCPROC glad_glWaitSync; +#define glWaitSync glad_glWaitSync +typedef void (APIENTRYP PFNGLGETINTEGER64VPROC)(GLenum pname, GLint64 *data); +GLAPI PFNGLGETINTEGER64VPROC glad_glGetInteger64v; +#define glGetInteger64v glad_glGetInteger64v +typedef void (APIENTRYP PFNGLGETSYNCIVPROC)(GLsync sync, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +GLAPI PFNGLGETSYNCIVPROC glad_glGetSynciv; +#define glGetSynciv glad_glGetSynciv +typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC)(GLenum target, GLuint index, GLint64 *data); +GLAPI PFNGLGETINTEGER64I_VPROC glad_glGetInteger64i_v; +#define glGetInteger64i_v glad_glGetInteger64i_v +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERI64VPROC)(GLenum target, GLenum pname, GLint64 *params); +GLAPI PFNGLGETBUFFERPARAMETERI64VPROC glad_glGetBufferParameteri64v; +#define glGetBufferParameteri64v glad_glGetBufferParameteri64v +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREPROC)(GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI PFNGLFRAMEBUFFERTEXTUREPROC glad_glFramebufferTexture; +#define glFramebufferTexture glad_glFramebufferTexture +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI PFNGLTEXIMAGE2DMULTISAMPLEPROC glad_glTexImage2DMultisample; +#define glTexImage2DMultisample glad_glTexImage2DMultisample +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI PFNGLTEXIMAGE3DMULTISAMPLEPROC glad_glTexImage3DMultisample; +#define glTexImage3DMultisample glad_glTexImage3DMultisample +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVPROC)(GLenum pname, GLuint index, GLfloat *val); +GLAPI PFNGLGETMULTISAMPLEFVPROC glad_glGetMultisamplefv; +#define glGetMultisamplefv glad_glGetMultisamplefv +typedef void (APIENTRYP PFNGLSAMPLEMASKIPROC)(GLuint maskNumber, GLbitfield mask); +GLAPI PFNGLSAMPLEMASKIPROC glad_glSampleMaski; +#define glSampleMaski glad_glSampleMaski +#endif +#ifndef GL_VERSION_3_3 +#define GL_VERSION_3_3 1 +GLAPI int GLAD_GL_VERSION_3_3; +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDPROC)(GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +GLAPI PFNGLBINDFRAGDATALOCATIONINDEXEDPROC glad_glBindFragDataLocationIndexed; +#define glBindFragDataLocationIndexed glad_glBindFragDataLocationIndexed +typedef GLint (APIENTRYP PFNGLGETFRAGDATAINDEXPROC)(GLuint program, const GLchar *name); +GLAPI PFNGLGETFRAGDATAINDEXPROC glad_glGetFragDataIndex; +#define glGetFragDataIndex glad_glGetFragDataIndex +typedef void (APIENTRYP PFNGLGENSAMPLERSPROC)(GLsizei count, GLuint *samplers); +GLAPI PFNGLGENSAMPLERSPROC glad_glGenSamplers; +#define glGenSamplers glad_glGenSamplers +typedef void (APIENTRYP PFNGLDELETESAMPLERSPROC)(GLsizei count, const GLuint *samplers); +GLAPI PFNGLDELETESAMPLERSPROC glad_glDeleteSamplers; +#define glDeleteSamplers glad_glDeleteSamplers +typedef GLboolean (APIENTRYP PFNGLISSAMPLERPROC)(GLuint sampler); +GLAPI PFNGLISSAMPLERPROC glad_glIsSampler; +#define glIsSampler glad_glIsSampler +typedef void (APIENTRYP PFNGLBINDSAMPLERPROC)(GLuint unit, GLuint sampler); +GLAPI PFNGLBINDSAMPLERPROC glad_glBindSampler; +#define glBindSampler glad_glBindSampler +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIPROC)(GLuint sampler, GLenum pname, GLint param); +GLAPI PFNGLSAMPLERPARAMETERIPROC glad_glSamplerParameteri; +#define glSamplerParameteri glad_glSamplerParameteri +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIVPROC)(GLuint sampler, GLenum pname, const GLint *param); +GLAPI PFNGLSAMPLERPARAMETERIVPROC glad_glSamplerParameteriv; +#define glSamplerParameteriv glad_glSamplerParameteriv +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFPROC)(GLuint sampler, GLenum pname, GLfloat param); +GLAPI PFNGLSAMPLERPARAMETERFPROC glad_glSamplerParameterf; +#define glSamplerParameterf glad_glSamplerParameterf +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFVPROC)(GLuint sampler, GLenum pname, const GLfloat *param); +GLAPI PFNGLSAMPLERPARAMETERFVPROC glad_glSamplerParameterfv; +#define glSamplerParameterfv glad_glSamplerParameterfv +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIIVPROC)(GLuint sampler, GLenum pname, const GLint *param); +GLAPI PFNGLSAMPLERPARAMETERIIVPROC glad_glSamplerParameterIiv; +#define glSamplerParameterIiv glad_glSamplerParameterIiv +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIUIVPROC)(GLuint sampler, GLenum pname, const GLuint *param); +GLAPI PFNGLSAMPLERPARAMETERIUIVPROC glad_glSamplerParameterIuiv; +#define glSamplerParameterIuiv glad_glSamplerParameterIuiv +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIVPROC)(GLuint sampler, GLenum pname, GLint *params); +GLAPI PFNGLGETSAMPLERPARAMETERIVPROC glad_glGetSamplerParameteriv; +#define glGetSamplerParameteriv glad_glGetSamplerParameteriv +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIIVPROC)(GLuint sampler, GLenum pname, GLint *params); +GLAPI PFNGLGETSAMPLERPARAMETERIIVPROC glad_glGetSamplerParameterIiv; +#define glGetSamplerParameterIiv glad_glGetSamplerParameterIiv +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERFVPROC)(GLuint sampler, GLenum pname, GLfloat *params); +GLAPI PFNGLGETSAMPLERPARAMETERFVPROC glad_glGetSamplerParameterfv; +#define glGetSamplerParameterfv glad_glGetSamplerParameterfv +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVPROC)(GLuint sampler, GLenum pname, GLuint *params); +GLAPI PFNGLGETSAMPLERPARAMETERIUIVPROC glad_glGetSamplerParameterIuiv; +#define glGetSamplerParameterIuiv glad_glGetSamplerParameterIuiv +typedef void (APIENTRYP PFNGLQUERYCOUNTERPROC)(GLuint id, GLenum target); +GLAPI PFNGLQUERYCOUNTERPROC glad_glQueryCounter; +#define glQueryCounter glad_glQueryCounter +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VPROC)(GLuint id, GLenum pname, GLint64 *params); +GLAPI PFNGLGETQUERYOBJECTI64VPROC glad_glGetQueryObjecti64v; +#define glGetQueryObjecti64v glad_glGetQueryObjecti64v +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VPROC)(GLuint id, GLenum pname, GLuint64 *params); +GLAPI PFNGLGETQUERYOBJECTUI64VPROC glad_glGetQueryObjectui64v; +#define glGetQueryObjectui64v glad_glGetQueryObjectui64v +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORPROC)(GLuint index, GLuint divisor); +GLAPI PFNGLVERTEXATTRIBDIVISORPROC glad_glVertexAttribDivisor; +#define glVertexAttribDivisor glad_glVertexAttribDivisor +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI PFNGLVERTEXATTRIBP1UIPROC glad_glVertexAttribP1ui; +#define glVertexAttribP1ui glad_glVertexAttribP1ui +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIVPROC)(GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI PFNGLVERTEXATTRIBP1UIVPROC glad_glVertexAttribP1uiv; +#define glVertexAttribP1uiv glad_glVertexAttribP1uiv +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI PFNGLVERTEXATTRIBP2UIPROC glad_glVertexAttribP2ui; +#define glVertexAttribP2ui glad_glVertexAttribP2ui +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIVPROC)(GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI PFNGLVERTEXATTRIBP2UIVPROC glad_glVertexAttribP2uiv; +#define glVertexAttribP2uiv glad_glVertexAttribP2uiv +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI PFNGLVERTEXATTRIBP3UIPROC glad_glVertexAttribP3ui; +#define glVertexAttribP3ui glad_glVertexAttribP3ui +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIVPROC)(GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI PFNGLVERTEXATTRIBP3UIVPROC glad_glVertexAttribP3uiv; +#define glVertexAttribP3uiv glad_glVertexAttribP3uiv +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI PFNGLVERTEXATTRIBP4UIPROC glad_glVertexAttribP4ui; +#define glVertexAttribP4ui glad_glVertexAttribP4ui +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIVPROC)(GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI PFNGLVERTEXATTRIBP4UIVPROC glad_glVertexAttribP4uiv; +#define glVertexAttribP4uiv glad_glVertexAttribP4uiv +typedef void (APIENTRYP PFNGLVERTEXP2UIPROC)(GLenum type, GLuint value); +GLAPI PFNGLVERTEXP2UIPROC glad_glVertexP2ui; +#define glVertexP2ui glad_glVertexP2ui +typedef void (APIENTRYP PFNGLVERTEXP2UIVPROC)(GLenum type, const GLuint *value); +GLAPI PFNGLVERTEXP2UIVPROC glad_glVertexP2uiv; +#define glVertexP2uiv glad_glVertexP2uiv +typedef void (APIENTRYP PFNGLVERTEXP3UIPROC)(GLenum type, GLuint value); +GLAPI PFNGLVERTEXP3UIPROC glad_glVertexP3ui; +#define glVertexP3ui glad_glVertexP3ui +typedef void (APIENTRYP PFNGLVERTEXP3UIVPROC)(GLenum type, const GLuint *value); +GLAPI PFNGLVERTEXP3UIVPROC glad_glVertexP3uiv; +#define glVertexP3uiv glad_glVertexP3uiv +typedef void (APIENTRYP PFNGLVERTEXP4UIPROC)(GLenum type, GLuint value); +GLAPI PFNGLVERTEXP4UIPROC glad_glVertexP4ui; +#define glVertexP4ui glad_glVertexP4ui +typedef void (APIENTRYP PFNGLVERTEXP4UIVPROC)(GLenum type, const GLuint *value); +GLAPI PFNGLVERTEXP4UIVPROC glad_glVertexP4uiv; +#define glVertexP4uiv glad_glVertexP4uiv +typedef void (APIENTRYP PFNGLTEXCOORDP1UIPROC)(GLenum type, GLuint coords); +GLAPI PFNGLTEXCOORDP1UIPROC glad_glTexCoordP1ui; +#define glTexCoordP1ui glad_glTexCoordP1ui +typedef void (APIENTRYP PFNGLTEXCOORDP1UIVPROC)(GLenum type, const GLuint *coords); +GLAPI PFNGLTEXCOORDP1UIVPROC glad_glTexCoordP1uiv; +#define glTexCoordP1uiv glad_glTexCoordP1uiv +typedef void (APIENTRYP PFNGLTEXCOORDP2UIPROC)(GLenum type, GLuint coords); +GLAPI PFNGLTEXCOORDP2UIPROC glad_glTexCoordP2ui; +#define glTexCoordP2ui glad_glTexCoordP2ui +typedef void (APIENTRYP PFNGLTEXCOORDP2UIVPROC)(GLenum type, const GLuint *coords); +GLAPI PFNGLTEXCOORDP2UIVPROC glad_glTexCoordP2uiv; +#define glTexCoordP2uiv glad_glTexCoordP2uiv +typedef void (APIENTRYP PFNGLTEXCOORDP3UIPROC)(GLenum type, GLuint coords); +GLAPI PFNGLTEXCOORDP3UIPROC glad_glTexCoordP3ui; +#define glTexCoordP3ui glad_glTexCoordP3ui +typedef void (APIENTRYP PFNGLTEXCOORDP3UIVPROC)(GLenum type, const GLuint *coords); +GLAPI PFNGLTEXCOORDP3UIVPROC glad_glTexCoordP3uiv; +#define glTexCoordP3uiv glad_glTexCoordP3uiv +typedef void (APIENTRYP PFNGLTEXCOORDP4UIPROC)(GLenum type, GLuint coords); +GLAPI PFNGLTEXCOORDP4UIPROC glad_glTexCoordP4ui; +#define glTexCoordP4ui glad_glTexCoordP4ui +typedef void (APIENTRYP PFNGLTEXCOORDP4UIVPROC)(GLenum type, const GLuint *coords); +GLAPI PFNGLTEXCOORDP4UIVPROC glad_glTexCoordP4uiv; +#define glTexCoordP4uiv glad_glTexCoordP4uiv +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIPROC)(GLenum texture, GLenum type, GLuint coords); +GLAPI PFNGLMULTITEXCOORDP1UIPROC glad_glMultiTexCoordP1ui; +#define glMultiTexCoordP1ui glad_glMultiTexCoordP1ui +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIVPROC)(GLenum texture, GLenum type, const GLuint *coords); +GLAPI PFNGLMULTITEXCOORDP1UIVPROC glad_glMultiTexCoordP1uiv; +#define glMultiTexCoordP1uiv glad_glMultiTexCoordP1uiv +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIPROC)(GLenum texture, GLenum type, GLuint coords); +GLAPI PFNGLMULTITEXCOORDP2UIPROC glad_glMultiTexCoordP2ui; +#define glMultiTexCoordP2ui glad_glMultiTexCoordP2ui +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIVPROC)(GLenum texture, GLenum type, const GLuint *coords); +GLAPI PFNGLMULTITEXCOORDP2UIVPROC glad_glMultiTexCoordP2uiv; +#define glMultiTexCoordP2uiv glad_glMultiTexCoordP2uiv +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIPROC)(GLenum texture, GLenum type, GLuint coords); +GLAPI PFNGLMULTITEXCOORDP3UIPROC glad_glMultiTexCoordP3ui; +#define glMultiTexCoordP3ui glad_glMultiTexCoordP3ui +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIVPROC)(GLenum texture, GLenum type, const GLuint *coords); +GLAPI PFNGLMULTITEXCOORDP3UIVPROC glad_glMultiTexCoordP3uiv; +#define glMultiTexCoordP3uiv glad_glMultiTexCoordP3uiv +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIPROC)(GLenum texture, GLenum type, GLuint coords); +GLAPI PFNGLMULTITEXCOORDP4UIPROC glad_glMultiTexCoordP4ui; +#define glMultiTexCoordP4ui glad_glMultiTexCoordP4ui +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIVPROC)(GLenum texture, GLenum type, const GLuint *coords); +GLAPI PFNGLMULTITEXCOORDP4UIVPROC glad_glMultiTexCoordP4uiv; +#define glMultiTexCoordP4uiv glad_glMultiTexCoordP4uiv +typedef void (APIENTRYP PFNGLNORMALP3UIPROC)(GLenum type, GLuint coords); +GLAPI PFNGLNORMALP3UIPROC glad_glNormalP3ui; +#define glNormalP3ui glad_glNormalP3ui +typedef void (APIENTRYP PFNGLNORMALP3UIVPROC)(GLenum type, const GLuint *coords); +GLAPI PFNGLNORMALP3UIVPROC glad_glNormalP3uiv; +#define glNormalP3uiv glad_glNormalP3uiv +typedef void (APIENTRYP PFNGLCOLORP3UIPROC)(GLenum type, GLuint color); +GLAPI PFNGLCOLORP3UIPROC glad_glColorP3ui; +#define glColorP3ui glad_glColorP3ui +typedef void (APIENTRYP PFNGLCOLORP3UIVPROC)(GLenum type, const GLuint *color); +GLAPI PFNGLCOLORP3UIVPROC glad_glColorP3uiv; +#define glColorP3uiv glad_glColorP3uiv +typedef void (APIENTRYP PFNGLCOLORP4UIPROC)(GLenum type, GLuint color); +GLAPI PFNGLCOLORP4UIPROC glad_glColorP4ui; +#define glColorP4ui glad_glColorP4ui +typedef void (APIENTRYP PFNGLCOLORP4UIVPROC)(GLenum type, const GLuint *color); +GLAPI PFNGLCOLORP4UIVPROC glad_glColorP4uiv; +#define glColorP4uiv glad_glColorP4uiv +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIPROC)(GLenum type, GLuint color); +GLAPI PFNGLSECONDARYCOLORP3UIPROC glad_glSecondaryColorP3ui; +#define glSecondaryColorP3ui glad_glSecondaryColorP3ui +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIVPROC)(GLenum type, const GLuint *color); +GLAPI PFNGLSECONDARYCOLORP3UIVPROC glad_glSecondaryColorP3uiv; +#define glSecondaryColorP3uiv glad_glSecondaryColorP3uiv +#endif +#ifndef GL_ES_VERSION_2_0 +#define GL_ES_VERSION_2_0 1 +GLAPI int GLAD_GL_ES_VERSION_2_0; +typedef void (APIENTRYP PFNGLCLEARDEPTHFPROC)(GLfloat d); +GLAPI PFNGLCLEARDEPTHFPROC glad_glClearDepthf; +#define glClearDepthf glad_glClearDepthf +typedef void (APIENTRYP PFNGLDEPTHRANGEFPROC)(GLfloat n, GLfloat f); +GLAPI PFNGLDEPTHRANGEFPROC glad_glDepthRangef; +#define glDepthRangef glad_glDepthRangef +typedef void (APIENTRYP PFNGLGETSHADERPRECISIONFORMATPROC)(GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +GLAPI PFNGLGETSHADERPRECISIONFORMATPROC glad_glGetShaderPrecisionFormat; +#define glGetShaderPrecisionFormat glad_glGetShaderPrecisionFormat +typedef void (APIENTRYP PFNGLRELEASESHADERCOMPILERPROC)(void); +GLAPI PFNGLRELEASESHADERCOMPILERPROC glad_glReleaseShaderCompiler; +#define glReleaseShaderCompiler glad_glReleaseShaderCompiler +typedef void (APIENTRYP PFNGLSHADERBINARYPROC)(GLsizei count, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLsizei length); +GLAPI PFNGLSHADERBINARYPROC glad_glShaderBinary; +#define glShaderBinary glad_glShaderBinary +#endif +#ifndef GL_ES_VERSION_3_0 +#define GL_ES_VERSION_3_0 1 +GLAPI int GLAD_GL_ES_VERSION_3_0; +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKPROC)(GLenum target, GLuint id); +GLAPI PFNGLBINDTRANSFORMFEEDBACKPROC glad_glBindTransformFeedback; +#define glBindTransformFeedback glad_glBindTransformFeedback +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSPROC)(GLsizei n, const GLuint *ids); +GLAPI PFNGLDELETETRANSFORMFEEDBACKSPROC glad_glDeleteTransformFeedbacks; +#define glDeleteTransformFeedbacks glad_glDeleteTransformFeedbacks +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSPROC)(GLsizei n, GLuint *ids); +GLAPI PFNGLGENTRANSFORMFEEDBACKSPROC glad_glGenTransformFeedbacks; +#define glGenTransformFeedbacks glad_glGenTransformFeedbacks +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKPROC)(GLuint id); +GLAPI PFNGLISTRANSFORMFEEDBACKPROC glad_glIsTransformFeedback; +#define glIsTransformFeedback glad_glIsTransformFeedback +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKPROC)(void); +GLAPI PFNGLPAUSETRANSFORMFEEDBACKPROC glad_glPauseTransformFeedback; +#define glPauseTransformFeedback glad_glPauseTransformFeedback +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKPROC)(void); +GLAPI PFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback; +#define glResumeTransformFeedback glad_glResumeTransformFeedback +typedef void (APIENTRYP PFNGLGETPROGRAMBINARYPROC)(GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); +GLAPI PFNGLGETPROGRAMBINARYPROC glad_glGetProgramBinary; +#define glGetProgramBinary glad_glGetProgramBinary +typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC)(GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); +GLAPI PFNGLPROGRAMBINARYPROC glad_glProgramBinary; +#define glProgramBinary glad_glProgramBinary +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIPROC)(GLuint program, GLenum pname, GLint value); +GLAPI PFNGLPROGRAMPARAMETERIPROC glad_glProgramParameteri; +#define glProgramParameteri glad_glProgramParameteri +typedef void (APIENTRYP PFNGLINVALIDATEFRAMEBUFFERPROC)(GLenum target, GLsizei numAttachments, const GLenum *attachments); +GLAPI PFNGLINVALIDATEFRAMEBUFFERPROC glad_glInvalidateFramebuffer; +#define glInvalidateFramebuffer glad_glInvalidateFramebuffer +typedef void (APIENTRYP PFNGLINVALIDATESUBFRAMEBUFFERPROC)(GLenum target, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI PFNGLINVALIDATESUBFRAMEBUFFERPROC glad_glInvalidateSubFramebuffer; +#define glInvalidateSubFramebuffer glad_glInvalidateSubFramebuffer +typedef void (APIENTRYP PFNGLTEXSTORAGE2DPROC)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI PFNGLTEXSTORAGE2DPROC glad_glTexStorage2D; +#define glTexStorage2D glad_glTexStorage2D +typedef void (APIENTRYP PFNGLTEXSTORAGE3DPROC)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI PFNGLTEXSTORAGE3DPROC glad_glTexStorage3D; +#define glTexStorage3D glad_glTexStorage3D +typedef void (APIENTRYP PFNGLGETINTERNALFORMATIVPROC)(GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint *params); +GLAPI PFNGLGETINTERNALFORMATIVPROC glad_glGetInternalformativ; +#define glGetInternalformativ glad_glGetInternalformativ +#endif +#ifndef GL_ES_VERSION_3_1 +#define GL_ES_VERSION_3_1 1 +GLAPI int GLAD_GL_ES_VERSION_3_1; +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEPROC)(GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); +GLAPI PFNGLDISPATCHCOMPUTEPROC glad_glDispatchCompute; +#define glDispatchCompute glad_glDispatchCompute +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEINDIRECTPROC)(GLintptr indirect); +GLAPI PFNGLDISPATCHCOMPUTEINDIRECTPROC glad_glDispatchComputeIndirect; +#define glDispatchComputeIndirect glad_glDispatchComputeIndirect +typedef void (APIENTRYP PFNGLDRAWARRAYSINDIRECTPROC)(GLenum mode, const void *indirect); +GLAPI PFNGLDRAWARRAYSINDIRECTPROC glad_glDrawArraysIndirect; +#define glDrawArraysIndirect glad_glDrawArraysIndirect +typedef void (APIENTRYP PFNGLDRAWELEMENTSINDIRECTPROC)(GLenum mode, GLenum type, const void *indirect); +GLAPI PFNGLDRAWELEMENTSINDIRECTPROC glad_glDrawElementsIndirect; +#define glDrawElementsIndirect glad_glDrawElementsIndirect +typedef void (APIENTRYP PFNGLFRAMEBUFFERPARAMETERIPROC)(GLenum target, GLenum pname, GLint param); +GLAPI PFNGLFRAMEBUFFERPARAMETERIPROC glad_glFramebufferParameteri; +#define glFramebufferParameteri glad_glFramebufferParameteri +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname, GLint *params); +GLAPI PFNGLGETFRAMEBUFFERPARAMETERIVPROC glad_glGetFramebufferParameteriv; +#define glGetFramebufferParameteriv glad_glGetFramebufferParameteriv +typedef void (APIENTRYP PFNGLGETPROGRAMINTERFACEIVPROC)(GLuint program, GLenum programInterface, GLenum pname, GLint *params); +GLAPI PFNGLGETPROGRAMINTERFACEIVPROC glad_glGetProgramInterfaceiv; +#define glGetProgramInterfaceiv glad_glGetProgramInterfaceiv +typedef GLuint (APIENTRYP PFNGLGETPROGRAMRESOURCEINDEXPROC)(GLuint program, GLenum programInterface, const GLchar *name); +GLAPI PFNGLGETPROGRAMRESOURCEINDEXPROC glad_glGetProgramResourceIndex; +#define glGetProgramResourceIndex glad_glGetProgramResourceIndex +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCENAMEPROC)(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +GLAPI PFNGLGETPROGRAMRESOURCENAMEPROC glad_glGetProgramResourceName; +#define glGetProgramResourceName glad_glGetProgramResourceName +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCEIVPROC)(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLint *params); +GLAPI PFNGLGETPROGRAMRESOURCEIVPROC glad_glGetProgramResourceiv; +#define glGetProgramResourceiv glad_glGetProgramResourceiv +typedef GLint (APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONPROC)(GLuint program, GLenum programInterface, const GLchar *name); +GLAPI PFNGLGETPROGRAMRESOURCELOCATIONPROC glad_glGetProgramResourceLocation; +#define glGetProgramResourceLocation glad_glGetProgramResourceLocation +typedef void (APIENTRYP PFNGLUSEPROGRAMSTAGESPROC)(GLuint pipeline, GLbitfield stages, GLuint program); +GLAPI PFNGLUSEPROGRAMSTAGESPROC glad_glUseProgramStages; +#define glUseProgramStages glad_glUseProgramStages +typedef void (APIENTRYP PFNGLACTIVESHADERPROGRAMPROC)(GLuint pipeline, GLuint program); +GLAPI PFNGLACTIVESHADERPROGRAMPROC glad_glActiveShaderProgram; +#define glActiveShaderProgram glad_glActiveShaderProgram +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMVPROC)(GLenum type, GLsizei count, const GLchar *const*strings); +GLAPI PFNGLCREATESHADERPROGRAMVPROC glad_glCreateShaderProgramv; +#define glCreateShaderProgramv glad_glCreateShaderProgramv +typedef void (APIENTRYP PFNGLBINDPROGRAMPIPELINEPROC)(GLuint pipeline); +GLAPI PFNGLBINDPROGRAMPIPELINEPROC glad_glBindProgramPipeline; +#define glBindProgramPipeline glad_glBindProgramPipeline +typedef void (APIENTRYP PFNGLDELETEPROGRAMPIPELINESPROC)(GLsizei n, const GLuint *pipelines); +GLAPI PFNGLDELETEPROGRAMPIPELINESPROC glad_glDeleteProgramPipelines; +#define glDeleteProgramPipelines glad_glDeleteProgramPipelines +typedef void (APIENTRYP PFNGLGENPROGRAMPIPELINESPROC)(GLsizei n, GLuint *pipelines); +GLAPI PFNGLGENPROGRAMPIPELINESPROC glad_glGenProgramPipelines; +#define glGenProgramPipelines glad_glGenProgramPipelines +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPIPELINEPROC)(GLuint pipeline); +GLAPI PFNGLISPROGRAMPIPELINEPROC glad_glIsProgramPipeline; +#define glIsProgramPipeline glad_glIsProgramPipeline +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEIVPROC)(GLuint pipeline, GLenum pname, GLint *params); +GLAPI PFNGLGETPROGRAMPIPELINEIVPROC glad_glGetProgramPipelineiv; +#define glGetProgramPipelineiv glad_glGetProgramPipelineiv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IPROC)(GLuint program, GLint location, GLint v0); +GLAPI PFNGLPROGRAMUNIFORM1IPROC glad_glProgramUniform1i; +#define glProgramUniform1i glad_glProgramUniform1i +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IPROC)(GLuint program, GLint location, GLint v0, GLint v1); +GLAPI PFNGLPROGRAMUNIFORM2IPROC glad_glProgramUniform2i; +#define glProgramUniform2i glad_glProgramUniform2i +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IPROC)(GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI PFNGLPROGRAMUNIFORM3IPROC glad_glProgramUniform3i; +#define glProgramUniform3i glad_glProgramUniform3i +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IPROC)(GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI PFNGLPROGRAMUNIFORM4IPROC glad_glProgramUniform4i; +#define glProgramUniform4i glad_glProgramUniform4i +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIPROC)(GLuint program, GLint location, GLuint v0); +GLAPI PFNGLPROGRAMUNIFORM1UIPROC glad_glProgramUniform1ui; +#define glProgramUniform1ui glad_glProgramUniform1ui +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIPROC)(GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI PFNGLPROGRAMUNIFORM2UIPROC glad_glProgramUniform2ui; +#define glProgramUniform2ui glad_glProgramUniform2ui +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIPROC)(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI PFNGLPROGRAMUNIFORM3UIPROC glad_glProgramUniform3ui; +#define glProgramUniform3ui glad_glProgramUniform3ui +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIPROC)(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI PFNGLPROGRAMUNIFORM4UIPROC glad_glProgramUniform4ui; +#define glProgramUniform4ui glad_glProgramUniform4ui +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FPROC)(GLuint program, GLint location, GLfloat v0); +GLAPI PFNGLPROGRAMUNIFORM1FPROC glad_glProgramUniform1f; +#define glProgramUniform1f glad_glProgramUniform1f +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FPROC)(GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI PFNGLPROGRAMUNIFORM2FPROC glad_glProgramUniform2f; +#define glProgramUniform2f glad_glProgramUniform2f +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FPROC)(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI PFNGLPROGRAMUNIFORM3FPROC glad_glProgramUniform3f; +#define glProgramUniform3f glad_glProgramUniform3f +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FPROC)(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI PFNGLPROGRAMUNIFORM4FPROC glad_glProgramUniform4f; +#define glProgramUniform4f glad_glProgramUniform4f +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVPROC)(GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLPROGRAMUNIFORM1IVPROC glad_glProgramUniform1iv; +#define glProgramUniform1iv glad_glProgramUniform1iv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVPROC)(GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLPROGRAMUNIFORM2IVPROC glad_glProgramUniform2iv; +#define glProgramUniform2iv glad_glProgramUniform2iv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVPROC)(GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLPROGRAMUNIFORM3IVPROC glad_glProgramUniform3iv; +#define glProgramUniform3iv glad_glProgramUniform3iv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVPROC)(GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI PFNGLPROGRAMUNIFORM4IVPROC glad_glProgramUniform4iv; +#define glProgramUniform4iv glad_glProgramUniform4iv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVPROC)(GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI PFNGLPROGRAMUNIFORM1UIVPROC glad_glProgramUniform1uiv; +#define glProgramUniform1uiv glad_glProgramUniform1uiv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVPROC)(GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI PFNGLPROGRAMUNIFORM2UIVPROC glad_glProgramUniform2uiv; +#define glProgramUniform2uiv glad_glProgramUniform2uiv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVPROC)(GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI PFNGLPROGRAMUNIFORM3UIVPROC glad_glProgramUniform3uiv; +#define glProgramUniform3uiv glad_glProgramUniform3uiv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVPROC)(GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI PFNGLPROGRAMUNIFORM4UIVPROC glad_glProgramUniform4uiv; +#define glProgramUniform4uiv glad_glProgramUniform4uiv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVPROC)(GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORM1FVPROC glad_glProgramUniform1fv; +#define glProgramUniform1fv glad_glProgramUniform1fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVPROC)(GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORM2FVPROC glad_glProgramUniform2fv; +#define glProgramUniform2fv glad_glProgramUniform2fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVPROC)(GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORM3FVPROC glad_glProgramUniform3fv; +#define glProgramUniform3fv glad_glProgramUniform3fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVPROC)(GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORM4FVPROC glad_glProgramUniform4fv; +#define glProgramUniform4fv glad_glProgramUniform4fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVPROC)(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORMMATRIX2FVPROC glad_glProgramUniformMatrix2fv; +#define glProgramUniformMatrix2fv glad_glProgramUniformMatrix2fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVPROC)(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORMMATRIX3FVPROC glad_glProgramUniformMatrix3fv; +#define glProgramUniformMatrix3fv glad_glProgramUniformMatrix3fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVPROC)(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORMMATRIX4FVPROC glad_glProgramUniformMatrix4fv; +#define glProgramUniformMatrix4fv glad_glProgramUniformMatrix4fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC)(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC glad_glProgramUniformMatrix2x3fv; +#define glProgramUniformMatrix2x3fv glad_glProgramUniformMatrix2x3fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC)(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC glad_glProgramUniformMatrix3x2fv; +#define glProgramUniformMatrix3x2fv glad_glProgramUniformMatrix3x2fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC)(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC glad_glProgramUniformMatrix2x4fv; +#define glProgramUniformMatrix2x4fv glad_glProgramUniformMatrix2x4fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC)(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC glad_glProgramUniformMatrix4x2fv; +#define glProgramUniformMatrix4x2fv glad_glProgramUniformMatrix4x2fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC)(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC glad_glProgramUniformMatrix3x4fv; +#define glProgramUniformMatrix3x4fv glad_glProgramUniformMatrix3x4fv +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC)(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC glad_glProgramUniformMatrix4x3fv; +#define glProgramUniformMatrix4x3fv glad_glProgramUniformMatrix4x3fv +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEPROC)(GLuint pipeline); +GLAPI PFNGLVALIDATEPROGRAMPIPELINEPROC glad_glValidateProgramPipeline; +#define glValidateProgramPipeline glad_glValidateProgramPipeline +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGPROC)(GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI PFNGLGETPROGRAMPIPELINEINFOLOGPROC glad_glGetProgramPipelineInfoLog; +#define glGetProgramPipelineInfoLog glad_glGetProgramPipelineInfoLog +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC)(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); +GLAPI PFNGLBINDIMAGETEXTUREPROC glad_glBindImageTexture; +#define glBindImageTexture glad_glBindImageTexture +typedef void (APIENTRYP PFNGLMEMORYBARRIERPROC)(GLbitfield barriers); +GLAPI PFNGLMEMORYBARRIERPROC glad_glMemoryBarrier; +#define glMemoryBarrier glad_glMemoryBarrier +typedef void (APIENTRYP PFNGLMEMORYBARRIERBYREGIONPROC)(GLbitfield barriers); +GLAPI PFNGLMEMORYBARRIERBYREGIONPROC glad_glMemoryBarrierByRegion; +#define glMemoryBarrierByRegion glad_glMemoryBarrierByRegion +typedef void (APIENTRYP PFNGLTEXSTORAGE2DMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI PFNGLTEXSTORAGE2DMULTISAMPLEPROC glad_glTexStorage2DMultisample; +#define glTexStorage2DMultisample glad_glTexStorage2DMultisample +typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERPROC)(GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +GLAPI PFNGLBINDVERTEXBUFFERPROC glad_glBindVertexBuffer; +#define glBindVertexBuffer glad_glBindVertexBuffer +typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATPROC)(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +GLAPI PFNGLVERTEXATTRIBFORMATPROC glad_glVertexAttribFormat; +#define glVertexAttribFormat glad_glVertexAttribFormat +typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATPROC)(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI PFNGLVERTEXATTRIBIFORMATPROC glad_glVertexAttribIFormat; +#define glVertexAttribIFormat glad_glVertexAttribIFormat +typedef void (APIENTRYP PFNGLVERTEXATTRIBBINDINGPROC)(GLuint attribindex, GLuint bindingindex); +GLAPI PFNGLVERTEXATTRIBBINDINGPROC glad_glVertexAttribBinding; +#define glVertexAttribBinding glad_glVertexAttribBinding +typedef void (APIENTRYP PFNGLVERTEXBINDINGDIVISORPROC)(GLuint bindingindex, GLuint divisor); +GLAPI PFNGLVERTEXBINDINGDIVISORPROC glad_glVertexBindingDivisor; +#define glVertexBindingDivisor glad_glVertexBindingDivisor +#endif +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM 0x8245 +#define GL_DEBUG_SOURCE_API 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION 0x824A +#define GL_DEBUG_SOURCE_OTHER 0x824B +#define GL_DEBUG_TYPE_ERROR 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E +#define GL_DEBUG_TYPE_PORTABILITY 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE 0x8250 +#define GL_DEBUG_TYPE_OTHER 0x8251 +#define GL_DEBUG_TYPE_MARKER 0x8268 +#define GL_DEBUG_TYPE_PUSH_GROUP 0x8269 +#define GL_DEBUG_TYPE_POP_GROUP 0x826A +#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B +#define GL_MAX_DEBUG_GROUP_STACK_DEPTH 0x826C +#define GL_DEBUG_GROUP_STACK_DEPTH 0x826D +#define GL_BUFFER 0x82E0 +#define GL_SHADER 0x82E1 +#define GL_PROGRAM 0x82E2 +#define GL_VERTEX_ARRAY 0x8074 +#define GL_QUERY 0x82E3 +#define GL_PROGRAM_PIPELINE 0x82E4 +#define GL_SAMPLER 0x82E6 +#define GL_MAX_LABEL_LENGTH 0x82E8 +#define GL_MAX_DEBUG_MESSAGE_LENGTH 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES 0x9145 +#define GL_DEBUG_SEVERITY_HIGH 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM 0x9147 +#define GL_DEBUG_SEVERITY_LOW 0x9148 +#define GL_DEBUG_OUTPUT 0x92E0 +#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_KHR 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_KHR 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_KHR 0x8245 +#define GL_DEBUG_SOURCE_API_KHR 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_KHR 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_KHR 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_KHR 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_KHR 0x824A +#define GL_DEBUG_SOURCE_OTHER_KHR 0x824B +#define GL_DEBUG_TYPE_ERROR_KHR 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_KHR 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_KHR 0x8250 +#define GL_DEBUG_TYPE_OTHER_KHR 0x8251 +#define GL_DEBUG_TYPE_MARKER_KHR 0x8268 +#define GL_DEBUG_TYPE_PUSH_GROUP_KHR 0x8269 +#define GL_DEBUG_TYPE_POP_GROUP_KHR 0x826A +#define GL_DEBUG_SEVERITY_NOTIFICATION_KHR 0x826B +#define GL_MAX_DEBUG_GROUP_STACK_DEPTH_KHR 0x826C +#define GL_DEBUG_GROUP_STACK_DEPTH_KHR 0x826D +#define GL_BUFFER_KHR 0x82E0 +#define GL_SHADER_KHR 0x82E1 +#define GL_PROGRAM_KHR 0x82E2 +#define GL_VERTEX_ARRAY_KHR 0x8074 +#define GL_QUERY_KHR 0x82E3 +#define GL_PROGRAM_PIPELINE_KHR 0x82E4 +#define GL_SAMPLER_KHR 0x82E6 +#define GL_MAX_LABEL_LENGTH_KHR 0x82E8 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_KHR 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_KHR 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_KHR 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_KHR 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_KHR 0x9147 +#define GL_DEBUG_SEVERITY_LOW_KHR 0x9148 +#define GL_DEBUG_OUTPUT_KHR 0x92E0 +#define GL_CONTEXT_FLAG_DEBUG_BIT_KHR 0x00000002 +#define GL_STACK_OVERFLOW_KHR 0x0503 +#define GL_STACK_UNDERFLOW_KHR 0x0504 +#define GL_DISPLAY_LIST 0x82E7 +#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0 +#define GL_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1 +#define GL_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2 +#define GL_COMPRESSED_RGBA_ASTC_6x5_KHR 0x93B3 +#define GL_COMPRESSED_RGBA_ASTC_6x6_KHR 0x93B4 +#define GL_COMPRESSED_RGBA_ASTC_8x5_KHR 0x93B5 +#define GL_COMPRESSED_RGBA_ASTC_8x6_KHR 0x93B6 +#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93B7 +#define GL_COMPRESSED_RGBA_ASTC_10x5_KHR 0x93B8 +#define GL_COMPRESSED_RGBA_ASTC_10x6_KHR 0x93B9 +#define GL_COMPRESSED_RGBA_ASTC_10x8_KHR 0x93BA +#define GL_COMPRESSED_RGBA_ASTC_10x10_KHR 0x93BB +#define GL_COMPRESSED_RGBA_ASTC_12x10_KHR 0x93BC +#define GL_COMPRESSED_RGBA_ASTC_12x12_KHR 0x93BD +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR 0x93D1 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR 0x93D2 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR 0x93D3 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR 0x93D4 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR 0x93D5 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR 0x93D6 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR 0x93D7 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR 0x93D8 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR 0x93D9 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR 0x93DA +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR 0x93DB +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR 0x93DC +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR 0x93DD +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object 1 +GLAPI int GLAD_GL_EXT_framebuffer_object; +#endif +#ifndef GL_EXT_texture_compression_s3tc +#define GL_EXT_texture_compression_s3tc 1 +GLAPI int GLAD_GL_EXT_texture_compression_s3tc; +#endif +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +GLAPI int GLAD_GL_EXT_texture_filter_anisotropic; +#endif +#ifndef GL_KHR_debug +#define GL_KHR_debug 1 +GLAPI int GLAD_GL_KHR_debug; +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLPROC)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI PFNGLDEBUGMESSAGECONTROLPROC glad_glDebugMessageControl; +#define glDebugMessageControl glad_glDebugMessageControl +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI PFNGLDEBUGMESSAGEINSERTPROC glad_glDebugMessageInsert; +#define glDebugMessageInsert glad_glDebugMessageInsert +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKPROC)(GLDEBUGPROC callback, const void *userParam); +GLAPI PFNGLDEBUGMESSAGECALLBACKPROC glad_glDebugMessageCallback; +#define glDebugMessageCallback glad_glDebugMessageCallback +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGPROC)(GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +GLAPI PFNGLGETDEBUGMESSAGELOGPROC glad_glGetDebugMessageLog; +#define glGetDebugMessageLog glad_glGetDebugMessageLog +typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPPROC)(GLenum source, GLuint id, GLsizei length, const GLchar *message); +GLAPI PFNGLPUSHDEBUGGROUPPROC glad_glPushDebugGroup; +#define glPushDebugGroup glad_glPushDebugGroup +typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC)(void); +GLAPI PFNGLPOPDEBUGGROUPPROC glad_glPopDebugGroup; +#define glPopDebugGroup glad_glPopDebugGroup +typedef void (APIENTRYP PFNGLOBJECTLABELPROC)(GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +GLAPI PFNGLOBJECTLABELPROC glad_glObjectLabel; +#define glObjectLabel glad_glObjectLabel +typedef void (APIENTRYP PFNGLGETOBJECTLABELPROC)(GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +GLAPI PFNGLGETOBJECTLABELPROC glad_glGetObjectLabel; +#define glGetObjectLabel glad_glGetObjectLabel +typedef void (APIENTRYP PFNGLOBJECTPTRLABELPROC)(const void *ptr, GLsizei length, const GLchar *label); +GLAPI PFNGLOBJECTPTRLABELPROC glad_glObjectPtrLabel; +#define glObjectPtrLabel glad_glObjectPtrLabel +typedef void (APIENTRYP PFNGLGETOBJECTPTRLABELPROC)(const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +GLAPI PFNGLGETOBJECTPTRLABELPROC glad_glGetObjectPtrLabel; +#define glGetObjectPtrLabel glad_glGetObjectPtrLabel +typedef void (APIENTRYP PFNGLGETPOINTERVPROC)(GLenum pname, void **params); +GLAPI PFNGLGETPOINTERVPROC glad_glGetPointerv; +#define glGetPointerv glad_glGetPointerv +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLKHRPROC)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI PFNGLDEBUGMESSAGECONTROLKHRPROC glad_glDebugMessageControlKHR; +#define glDebugMessageControlKHR glad_glDebugMessageControlKHR +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTKHRPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI PFNGLDEBUGMESSAGEINSERTKHRPROC glad_glDebugMessageInsertKHR; +#define glDebugMessageInsertKHR glad_glDebugMessageInsertKHR +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKKHRPROC)(GLDEBUGPROCKHR callback, const void *userParam); +GLAPI PFNGLDEBUGMESSAGECALLBACKKHRPROC glad_glDebugMessageCallbackKHR; +#define glDebugMessageCallbackKHR glad_glDebugMessageCallbackKHR +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGKHRPROC)(GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +GLAPI PFNGLGETDEBUGMESSAGELOGKHRPROC glad_glGetDebugMessageLogKHR; +#define glGetDebugMessageLogKHR glad_glGetDebugMessageLogKHR +typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPKHRPROC)(GLenum source, GLuint id, GLsizei length, const GLchar *message); +GLAPI PFNGLPUSHDEBUGGROUPKHRPROC glad_glPushDebugGroupKHR; +#define glPushDebugGroupKHR glad_glPushDebugGroupKHR +typedef void (APIENTRYP PFNGLPOPDEBUGGROUPKHRPROC)(void); +GLAPI PFNGLPOPDEBUGGROUPKHRPROC glad_glPopDebugGroupKHR; +#define glPopDebugGroupKHR glad_glPopDebugGroupKHR +typedef void (APIENTRYP PFNGLOBJECTLABELKHRPROC)(GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +GLAPI PFNGLOBJECTLABELKHRPROC glad_glObjectLabelKHR; +#define glObjectLabelKHR glad_glObjectLabelKHR +typedef void (APIENTRYP PFNGLGETOBJECTLABELKHRPROC)(GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +GLAPI PFNGLGETOBJECTLABELKHRPROC glad_glGetObjectLabelKHR; +#define glGetObjectLabelKHR glad_glGetObjectLabelKHR +typedef void (APIENTRYP PFNGLOBJECTPTRLABELKHRPROC)(const void *ptr, GLsizei length, const GLchar *label); +GLAPI PFNGLOBJECTPTRLABELKHRPROC glad_glObjectPtrLabelKHR; +#define glObjectPtrLabelKHR glad_glObjectPtrLabelKHR +typedef void (APIENTRYP PFNGLGETOBJECTPTRLABELKHRPROC)(const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +GLAPI PFNGLGETOBJECTPTRLABELKHRPROC glad_glGetObjectPtrLabelKHR; +#define glGetObjectPtrLabelKHR glad_glGetObjectPtrLabelKHR +typedef void (APIENTRYP PFNGLGETPOINTERVKHRPROC)(GLenum pname, void **params); +GLAPI PFNGLGETPOINTERVKHRPROC glad_glGetPointervKHR; +#define glGetPointervKHR glad_glGetPointervKHR +#endif +#ifndef GL_KHR_texture_compression_astc_ldr +#define GL_KHR_texture_compression_astc_ldr 1 +GLAPI int GLAD_GL_KHR_texture_compression_astc_ldr; +#endif +#ifndef GL_EXT_texture_compression_s3tc +#define GL_EXT_texture_compression_s3tc 1 +GLAPI int GLAD_GL_EXT_texture_compression_s3tc; +#endif +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +GLAPI int GLAD_GL_EXT_texture_filter_anisotropic; +#endif +#ifndef GL_KHR_debug +#define GL_KHR_debug 1 +GLAPI int GLAD_GL_KHR_debug; +#endif +#ifndef GL_KHR_texture_compression_astc_ldr +#define GL_KHR_texture_compression_astc_ldr 1 +GLAPI int GLAD_GL_KHR_texture_compression_astc_ldr; +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/vendor/librw/src/gl/glad/khrplatform.h b/vendor/librw/src/gl/glad/khrplatform.h new file mode 100644 index 0000000..dd22d92 --- /dev/null +++ b/vendor/librw/src/gl/glad/khrplatform.h @@ -0,0 +1,290 @@ +#ifndef __khrplatform_h_ +#define __khrplatform_h_ + +/* +** Copyright (c) 2008-2018 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +/* Khronos platform-specific types and definitions. + * + * The master copy of khrplatform.h is maintained in the Khronos EGL + * Registry repository at https://github.com/KhronosGroup/EGL-Registry + * The last semantic modification to khrplatform.h was at commit ID: + * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 + * + * Adopters may modify this file to suit their platform. Adopters are + * encouraged to submit platform specific modifications to the Khronos + * group so that they can be included in future versions of this file. + * Please submit changes by filing pull requests or issues on + * the EGL Registry repository linked above. + * + * + * See the Implementer's Guidelines for information about where this file + * should be located on your system and for more details of its use: + * http://www.khronos.org/registry/implementers_guide.pdf + * + * This file should be included as + * #include + * by Khronos client API header files that use its types and defines. + * + * The types in khrplatform.h should only be used to define API-specific types. + * + * Types defined in khrplatform.h: + * khronos_int8_t signed 8 bit + * khronos_uint8_t unsigned 8 bit + * khronos_int16_t signed 16 bit + * khronos_uint16_t unsigned 16 bit + * khronos_int32_t signed 32 bit + * khronos_uint32_t unsigned 32 bit + * khronos_int64_t signed 64 bit + * khronos_uint64_t unsigned 64 bit + * khronos_intptr_t signed same number of bits as a pointer + * khronos_uintptr_t unsigned same number of bits as a pointer + * khronos_ssize_t signed size + * khronos_usize_t unsigned size + * khronos_float_t signed 32 bit floating point + * khronos_time_ns_t unsigned 64 bit time in nanoseconds + * khronos_utime_nanoseconds_t unsigned time interval or absolute time in + * nanoseconds + * khronos_stime_nanoseconds_t signed time interval in nanoseconds + * khronos_boolean_enum_t enumerated boolean type. This should + * only be used as a base type when a client API's boolean type is + * an enum. Client APIs which use an integer or other type for + * booleans cannot use this as the base type for their boolean. + * + * Tokens defined in khrplatform.h: + * + * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. + * + * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. + * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. + * + * Calling convention macros defined in this file: + * KHRONOS_APICALL + * KHRONOS_APIENTRY + * KHRONOS_APIATTRIBUTES + * + * These may be used in function prototypes as: + * + * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( + * int arg1, + * int arg2) KHRONOS_APIATTRIBUTES; + */ + +#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) +# define KHRONOS_STATIC 1 +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APICALL + *------------------------------------------------------------------------- + * This precedes the return type of the function in the function prototype. + */ +#if defined(KHRONOS_STATIC) + /* If the preprocessor constant KHRONOS_STATIC is defined, make the + * header compatible with static linking. */ +# define KHRONOS_APICALL +#elif defined(_WIN32) +# define KHRONOS_APICALL __declspec(dllimport) +#elif defined (__SYMBIAN32__) +# define KHRONOS_APICALL IMPORT_C +#elif defined(__ANDROID__) +# define KHRONOS_APICALL __attribute__((visibility("default"))) +#else +# define KHRONOS_APICALL +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIENTRY + *------------------------------------------------------------------------- + * This follows the return type of the function and precedes the function + * name in the function prototype. + */ +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) + /* Win32 but not WinCE */ +# define KHRONOS_APIENTRY __stdcall +#else +# define KHRONOS_APIENTRY +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIATTRIBUTES + *------------------------------------------------------------------------- + * This follows the closing parenthesis of the function prototype arguments. + */ +#if defined (__ARMCC_2__) +#define KHRONOS_APIATTRIBUTES __softfp +#else +#define KHRONOS_APIATTRIBUTES +#endif + +/*------------------------------------------------------------------------- + * basic type definitions + *-----------------------------------------------------------------------*/ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) + + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__VMS ) || defined(__sgi) + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) + +/* + * Win32 + */ +typedef __int32 khronos_int32_t; +typedef unsigned __int32 khronos_uint32_t; +typedef __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__sun__) || defined(__digital__) + +/* + * Sun or Digital + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#if defined(__arch64__) || defined(_LP64) +typedef long int khronos_int64_t; +typedef unsigned long int khronos_uint64_t; +#else +typedef long long int khronos_int64_t; +typedef unsigned long long int khronos_uint64_t; +#endif /* __arch64__ */ +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif 0 + +/* + * Hypothetical platform with no float or int64 support + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#define KHRONOS_SUPPORT_INT64 0 +#define KHRONOS_SUPPORT_FLOAT 0 + +#else + +/* + * Generic fallback + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#endif + + +/* + * Types that are (so far) the same on all platforms + */ +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; + +/* + * Types that differ between LLP64 and LP64 architectures - in LLP64, + * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears + * to be the only LLP64 architecture in current use. + */ +#ifdef _WIN64 +typedef signed long long int khronos_intptr_t; +typedef unsigned long long int khronos_uintptr_t; +typedef signed long long int khronos_ssize_t; +typedef unsigned long long int khronos_usize_t; +#else +typedef signed long int khronos_intptr_t; +typedef unsigned long int khronos_uintptr_t; +typedef signed long int khronos_ssize_t; +typedef unsigned long int khronos_usize_t; +#endif + +#if KHRONOS_SUPPORT_FLOAT +/* + * Float type + */ +typedef float khronos_float_t; +#endif + +#if KHRONOS_SUPPORT_INT64 +/* Time types + * + * These types can be used to represent a time interval in nanoseconds or + * an absolute Unadjusted System Time. Unadjusted System Time is the number + * of nanoseconds since some arbitrary system event (e.g. since the last + * time the system booted). The Unadjusted System Time is an unsigned + * 64 bit value that wraps back to 0 every 584 years. Time intervals + * may be either signed or unsigned. + */ +typedef khronos_uint64_t khronos_utime_nanoseconds_t; +typedef khronos_int64_t khronos_stime_nanoseconds_t; +#endif + +/* + * Dummy value used to pad enum types to 32 bits. + */ +#ifndef KHRONOS_MAX_ENUM +#define KHRONOS_MAX_ENUM 0x7FFFFFFF +#endif + +/* + * Enumerated boolean type + * + * Values other than zero should be considered to be true. Therefore + * comparisons should not be made against KHRONOS_TRUE. + */ +typedef enum { + KHRONOS_FALSE = 0, + KHRONOS_TRUE = 1, + KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM +} khronos_boolean_enum_t; + +#endif /* __khrplatform_h_ */ diff --git a/vendor/librw/src/gl/rwgl3.h b/vendor/librw/src/gl/rwgl3.h new file mode 100644 index 0000000..4b138ac --- /dev/null +++ b/vendor/librw/src/gl/rwgl3.h @@ -0,0 +1,288 @@ +#ifdef RW_GL3 +#include "glad/glad.h" +#ifdef LIBRW_SDL2 +#include +#else +#include +#endif +#endif + +namespace rw { + +#ifdef RW_GL3 +struct EngineOpenParams +{ +#ifdef LIBRW_SDL2 + SDL_Window **window; + bool32 fullscreen; +#else + GLFWwindow **window; +#endif + int width, height; + const char *windowtitle; +}; +#endif + +namespace gl3 { + +void registerPlatformPlugins(void); + +extern Device renderdevice; + +// arguments to glVertexAttribPointer basically +struct AttribDesc +{ + uint32 index; + int32 type; + bool32 normalized; + int32 size; + uint32 stride; + uint32 offset; +}; + +enum AttribIndices +{ + ATTRIB_POS = 0, + ATTRIB_NORMAL, + ATTRIB_COLOR, + ATTRIB_WEIGHTS, + ATTRIB_INDICES, + ATTRIB_TEXCOORDS0, + ATTRIB_TEXCOORDS1, + ATTRIB_TEXCOORDS2, + ATTRIB_TEXCOORDS3, + ATTRIB_TEXCOORDS4, + ATTRIB_TEXCOORDS5, + ATTRIB_TEXCOORDS6, + ATTRIB_TEXCOORDS7, +}; + +// default uniform indices +extern int32 u_matColor; +extern int32 u_surfProps; + +struct InstanceData +{ + uint32 numIndex; + uint32 minVert; // not used for rendering + int32 numVertices; // + Material *material; + bool32 vertexAlpha; + uint32 program; + uint32 offset; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + uint32 serialNumber; + uint32 numMeshes; + uint16 *indexBuffer; + uint32 primType; + uint8 *vertexBuffer; + int32 numAttribs; + AttribDesc *attribDesc; + uint32 totalNumIndex; + uint32 totalNumVertex; + + uint32 ibo; + uint32 vbo; // or 2? +#ifdef RW_GL_USE_VAOS + uint32 vao; +#endif + + InstanceData *inst; +}; + +#ifdef RW_GL3 + +struct Shader; + +extern Shader *defaultShader, *defaultShader_noAT; +extern Shader *defaultShader_fullLight, *defaultShader_fullLight_noAT; + +struct Im3DVertex +{ + V3d position; + uint8 r, g, b, a; + float32 u, v; + + void setX(float32 x) { this->position.x = x; } + void setY(float32 y) { this->position.y = y; } + void setZ(float32 z) { this->position.z = z; } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { + this->r = r; this->g = g; this->b = b; this->a = a; } + void setU(float32 u) { this->u = u; } + void setV(float32 v) { this->v = v; } + + float getX(void) { return this->position.x; } + float getY(void) { return this->position.y; } + float getZ(void) { return this->position.z; } + RGBA getColor(void) { return makeRGBA(this->r, this->g, this->b, this->a); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + +struct Im2DVertex +{ + float32 x, y, z, w; + uint8 r, g, b, a; + float32 u, v; + + void setScreenX(float32 x) { this->x = x; } + void setScreenY(float32 y) { this->y = y; } + void setScreenZ(float32 z) { this->z = z; } + // This is a bit unefficient but we have to counteract GL's divide, so multiply + void setCameraZ(float32 z) { this->w = z; } + void setRecipCameraZ(float32 recipz) { this->w = 1.0f/recipz; } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { + this->r = r; this->g = g; this->b = b; this->a = a; } + void setU(float32 u, float recipz) { this->u = u; } + void setV(float32 v, float recipz) { this->v = v; } + + float getScreenX(void) { return this->x; } + float getScreenY(void) { return this->y; } + float getScreenZ(void) { return this->z; } + float getCameraZ(void) { return this->w; } + float getRecipCameraZ(void) { return 1.0f/this->w; } + RGBA getColor(void) { return makeRGBA(this->r, this->g, this->b, this->a); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + +void setAttribPointers(AttribDesc *attribDescs, int32 numAttribs); +void disableAttribPointers(AttribDesc *attribDescs, int32 numAttribs); +void setupVertexInput(InstanceDataHeader *header); +void teardownVertexInput(InstanceDataHeader *header); + +// Render state + +// Vertex shader bits +enum +{ + // These should be low so they could be used as indices + VSLIGHT_DIRECT = 1, + VSLIGHT_POINT = 2, + VSLIGHT_SPOT = 4, + VSLIGHT_MASK = 7, // all the above + // less critical + VSLIGHT_AMBIENT = 8, +}; + +extern const char *shaderDecl; // #version stuff +extern const char *header_vert_src; +extern const char *header_frag_src; + +extern Shader *im2dOverrideShader; + +// per Scene +void setProjectionMatrix(float32*); +void setViewMatrix(float32*); + +// per Object +void setWorldMatrix(Matrix*); +int32 setLights(WorldLights *lightData); + +// per Mesh +void setTexture(int32 n, Texture *tex); +void setMaterial(const RGBA &color, const SurfaceProperties &surfaceprops, float extraSurfProp = 0.0f); +inline void setMaterial(uint32 flags, const RGBA &color, const SurfaceProperties &surfaceprops, float extraSurfProp = 0.0f) +{ + static RGBA white = { 255, 255, 255, 255 }; + if(flags & Geometry::MODULATE) + setMaterial(color, surfaceprops, extraSurfProp); + else + setMaterial(white, surfaceprops, extraSurfProp); +} + +void setAlphaBlend(bool32 enable); +bool32 getAlphaBlend(void); + +bool32 getAlphaTest(void); + +void bindFramebuffer(uint32 fbo); +uint32 bindTexture(uint32 texid); + +void flushCache(void); + +#endif + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + void (*instanceCB)(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); + void (*uninstanceCB)(Geometry *geo, InstanceDataHeader *header); + void (*renderCB)(Atomic *atomic, InstanceDataHeader *header); +}; + +void defaultInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); +void defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header); +void defaultRenderCB(Atomic *atomic, InstanceDataHeader *header); +int32 lightingCB(Atomic *atomic); + +void drawInst_simple(InstanceDataHeader *header, InstanceData *inst); +// Emulate PS2 GS alpha test FB_ONLY case: failed alpha writes to frame- but not to depth buffer +void drawInst_GSemu(InstanceDataHeader *header, InstanceData *inst); +// This one switches between the above two depending on render state; +void drawInst(InstanceDataHeader *header, InstanceData *inst); + + +void *destroyNativeData(void *object, int32, int32); + +ObjPipeline *makeDefaultPipeline(void); + +// Native Texture and Raster + +struct Gl3Raster +{ + // arguments to glTexImage2D + int32 internalFormat; + int32 type; + int32 format; + int32 bpp; // bytes per pixel + // texture object + uint32 texid; + + bool isCompressed; + bool hasAlpha; + bool autogenMipmap; + int8 numLevels; + // cached filtermode and addressing + uint8 filterMode; + uint8 addressU; + uint8 addressV; + int32 maxAnisotropy; + + uint32 fbo; // used for camera texture only! + Raster *fboMate; // color or zbuffer raster mate of this one + RasterLevels *backingStore; // if we can't read back GPU memory but have to +}; + +struct Gl3Caps +{ + int gles; + int glversion; + bool dxtSupported; + bool astcSupported; // not used yet + float maxAnisotropy; +}; +extern Gl3Caps gl3Caps; +// GLES can't read back textures very nicely. +// In most cases that's not an issue, but when it is, +// this has to be set before the texture is filled: +extern bool32 needToReadBackTextures; + +void allocateDXT(Raster *raster, int32 dxt, int32 numLevels, bool32 hasAlpha); + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +extern int32 nativeRasterOffset; +void registerNativeRaster(void); +#define GETGL3RASTEREXT(raster) PLUGINOFFSET(Gl3Raster, raster, rw::gl3::nativeRasterOffset) + +} +} diff --git a/vendor/librw/src/gl/rwgl3impl.h b/vendor/librw/src/gl/rwgl3impl.h new file mode 100644 index 0000000..39f0050 --- /dev/null +++ b/vendor/librw/src/gl/rwgl3impl.h @@ -0,0 +1,76 @@ +namespace rw { +namespace gl3 { + +#ifdef RW_OPENGL + +extern uint32 im2DVbo, im2DIbo; +void openIm2D(void); +void closeIm2D(void); +void im2DRenderLine(void *vertices, int32 numVertices, + int32 vert1, int32 vert2); +void im2DRenderTriangle(void *vertices, int32 numVertices, + int32 vert1, int32 vert2, int32 vert3); +void im2DRenderPrimitive(PrimitiveType primType, + void *vertices, int32 numVertices); +void im2DRenderIndexedPrimitive(PrimitiveType primType, + void *vertices, int32 numVertices, void *indices, int32 numIndices); + +void openIm3D(void); +void closeIm3D(void); +void im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags); +void im3DRenderPrimitive(PrimitiveType primType); +void im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices); +void im3DEnd(void); + +struct DisplayMode +{ +#ifdef LIBRW_SDL2 + SDL_DisplayMode mode; +#else + GLFWvidmode mode; +#endif + int32 depth; + uint32 flags; +}; + +struct GlGlobals +{ +#ifdef LIBRW_SDL2 + SDL_Window **pWindow; + SDL_Window *window; + SDL_GLContext glcontext; +#else + GLFWwindow **pWindow; + GLFWwindow *window; + + GLFWmonitor *monitor; + int numMonitors; + int currentMonitor; +#endif + + DisplayMode *modes; + int numModes; + int currentMode; + int presentWidth, presentHeight; + int presentOffX, presentOffY; + + // for opening the window + int winWidth, winHeight; + const char *winTitle; + uint32 numSamples; +}; + +extern GlGlobals glGlobals; +#endif + +Raster *rasterCreate(Raster *raster); +uint8 *rasterLock(Raster*, int32 level, int32 lockMode); +void rasterUnlock(Raster*, int32); +int32 rasterNumLevels(Raster*); +bool32 imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); +bool32 rasterFromImage(Raster *raster, Image *image); +Image *rasterToImage(Raster *raster); + +} +} diff --git a/vendor/librw/src/gl/rwgl3plg.h b/vendor/librw/src/gl/rwgl3plg.h new file mode 100644 index 0000000..5374b3e --- /dev/null +++ b/vendor/librw/src/gl/rwgl3plg.h @@ -0,0 +1,16 @@ +namespace rw { +namespace gl3 { + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); +void matfxRenderCB(Atomic *atomic, InstanceDataHeader *header); + +void initSkin(void); +ObjPipeline *makeSkinPipeline(void); +void skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); +void skinRenderCB(Atomic *atomic, InstanceDataHeader *header); +void uploadSkinMatrices(Atomic *atomic); + + +} +} diff --git a/vendor/librw/src/gl/rwgl3shader.h b/vendor/librw/src/gl/rwgl3shader.h new file mode 100644 index 0000000..890f12b --- /dev/null +++ b/vendor/librw/src/gl/rwgl3shader.h @@ -0,0 +1,69 @@ +#ifdef RW_OPENGL + +namespace rw { +namespace gl3 { + +// TODO: make this dynamic +enum { + MAX_UNIFORMS = 40, + MAX_BLOCKS = 20 +}; + +enum UniformType +{ + UNIFORM_NA, // managed by the user + UNIFORM_VEC4, + UNIFORM_IVEC4, + UNIFORM_MAT4 +}; + +struct Uniform +{ + char *name; + UniformType type; + //bool dirty; + uint32 serialNum; + int32 num; + void *data; +}; + +struct UniformRegistry +{ + int32 numUniforms; + Uniform uniforms[MAX_UNIFORMS]; + + int32 numBlocks; + char *blockNames[MAX_BLOCKS]; +}; + +int32 registerUniform(const char *name, UniformType type = UNIFORM_NA, int32 num = 1); +int32 findUniform(const char *name); +int32 registerBlock(const char *name); +int32 findBlock(const char *name); + +void setUniform(int32 id, void *data); +void flushUniforms(void); + +extern UniformRegistry uniformRegistry; + +struct Shader +{ + GLuint program; + // same number of elements as UniformRegistry::numUniforms + GLint *uniformLocations; + uint32 *serialNums; + int32 numUniforms; // just to be sure! + + static Shader *create(const char **vsrc, const char **fsrc); +// static Shader *fromFiles(const char *vs, const char *fs); +// static Shader *fromStrings(const char *vsrc, const char *fsrc); + void use(void); + void destroy(void); +}; + +extern Shader *currentShader; + +} +} + +#endif diff --git a/vendor/librw/src/gl/rwwdgl.h b/vendor/librw/src/gl/rwwdgl.h new file mode 100644 index 0000000..b336ba3 --- /dev/null +++ b/vendor/librw/src/gl/rwwdgl.h @@ -0,0 +1,88 @@ + +namespace rw { +namespace wdgl { + +// NOTE: This is not really RW OpenGL! It's specific to WarDrum's GTA ports + +void registerPlatformPlugins(void); + +struct AttribDesc +{ + // arguments to glVertexAttribPointer (should use OpenGL types here) + // Vertex = 0, TexCoord, Normal, Color, Weight, Bone Index, Extra Color + uint32 index; + // float = 0, byte, ubyte, short, ushort + int32 type; + bool32 normalized; + int32 size; + uint32 stride; + uint32 offset; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + int32 numAttribs; + AttribDesc *attribs; + uint32 dataSize; + uint8 *data; + + // needed for rendering + uint32 vbo; + uint32 ibo; +}; + +// only RW_OPENGL +void uploadGeo(Geometry *geo); +void setAttribPointers(InstanceDataHeader *inst); + +void packattrib(uint8 *dst, float32 *src, AttribDesc *a, float32 scale); +void unpackattrib(float *dst, uint8 *src, AttribDesc *a, float32 scale); + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +void printPipeinfo(Atomic *a); + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + uint32 numCustomAttribs; + uint32 (*instanceCB)(Geometry *g, int32 i, uint32 offset); + void (*uninstanceCB)(Geometry *g); +}; + +ObjPipeline *makeDefaultPipeline(void); + +// Skin plugin + +void initSkin(void); +Stream *readNativeSkin(Stream *stream, int32, void *object, int32 offset); +Stream *writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset); +int32 getSizeNativeSkin(void *object, int32 offset); + +ObjPipeline *makeSkinPipeline(void); + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Raster + +struct Texture : rw::Texture +{ + void upload(void); + void bind(int n); +}; + +extern int32 nativeRasterOffset; +void registerNativeRaster(void); + +} +} diff --git a/vendor/librw/src/gl/shaders/Makefile b/vendor/librw/src/gl/shaders/Makefile new file mode 100644 index 0000000..6471f17 --- /dev/null +++ b/vendor/librw/src/gl/shaders/Makefile @@ -0,0 +1,45 @@ +all: header_vs.inc header_fs.inc im2d_gl.inc im3d_gl.inc default_vs_gl.inc simple_fs_gl.inc matfx_gl.inc skin_gl.inc + +header_vs.inc: header.vert + (echo 'const char *header_vert_src =';\ + sed 's/..*/"&\\n"/' header.vert;\ + echo ';') >header_vs.inc + +header_fs.inc: header.frag + (echo 'const char *header_frag_src =';\ + sed 's/..*/"&\\n"/' header.frag;\ + echo ';') >header_fs.inc + +im2d_gl.inc: im2d.vert + (echo 'const char *im2d_vert_src =';\ + sed 's/..*/"&\\n"/' im2d.vert;\ + echo ';') >im2d_gl.inc + +im3d_gl.inc: im3d.vert + (echo 'const char *im3d_vert_src =';\ + sed 's/..*/"&\\n"/' im3d.vert;\ + echo ';') >im3d_gl.inc + +default_vs_gl.inc: default.vert + (echo 'const char *default_vert_src =';\ + sed 's/..*/"&\\n"/' default.vert;\ + echo ';') >default_vs_gl.inc + +simple_fs_gl.inc: simple.frag + (echo 'const char *simple_frag_src =';\ + sed 's/..*/"&\\n"/' simple.frag;\ + echo ';') >simple_fs_gl.inc + +matfx_gl.inc: matfx_env.frag matfx_env.vert + (echo 'const char *matfx_env_vert_src =';\ + sed 's/..*/"&\\n"/' matfx_env.vert;\ + echo ';';\ + echo 'const char *matfx_env_frag_src =';\ + sed 's/..*/"&\\n"/' matfx_env.frag;\ + echo ';') >matfx_gl.inc + +skin_gl.inc: skin.vert + (echo 'const char *skin_vert_src =';\ + sed 's/..*/"&\\n"/' skin.vert;\ + echo ';') >skin_gl.inc + diff --git a/vendor/librw/src/gl/shaders/default.vert b/vendor/librw/src/gl/shaders/default.vert new file mode 100644 index 0000000..8e3fad7 --- /dev/null +++ b/vendor/librw/src/gl/shaders/default.vert @@ -0,0 +1,23 @@ +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/vendor/librw/src/gl/shaders/default_vs_gl.inc b/vendor/librw/src/gl/shaders/default_vs_gl.inc new file mode 100644 index 0000000..6e3d6c4 --- /dev/null +++ b/vendor/librw/src/gl/shaders/default_vs_gl.inc @@ -0,0 +1,25 @@ +const char *default_vert_src = +"VSIN(ATTRIB_POS) vec3 in_pos;\n" + +"VSOUT vec4 v_color;\n" +"VSOUT vec2 v_tex0;\n" +"VSOUT float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 Vertex = u_world * vec4(in_pos, 1.0);\n" +" gl_Position = u_proj * u_view * Vertex;\n" +" vec3 Normal = mat3(u_world) * in_normal;\n" + +" v_tex0 = in_tex0;\n" + +" v_color = in_color;\n" +" v_color.rgb += u_ambLight.rgb*surfAmbient;\n" +" v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse;\n" +" v_color = clamp(v_color, 0.0, 1.0);\n" +" v_color *= u_matColor;\n" + +" v_fog = DoFog(gl_Position.w);\n" +"}\n" +; diff --git a/vendor/librw/src/gl/shaders/header.frag b/vendor/librw/src/gl/shaders/header.frag new file mode 100644 index 0000000..ba50165 --- /dev/null +++ b/vendor/librw/src/gl/shaders/header.frag @@ -0,0 +1,30 @@ +#ifdef USE_UBOS +layout(std140) uniform State +{ + vec2 u_alphaRef; + vec4 u_fogData; + vec4 u_fogColor; +}; +#else +uniform vec4 u_alphaRef; + +uniform vec4 u_fogData; +uniform vec4 u_fogColor; +#endif + +#define u_fogStart (u_fogData.x) +#define u_fogEnd (u_fogData.y) +#define u_fogRange (u_fogData.z) +#define u_fogDisable (u_fogData.w) + +#ifndef GL2 +out vec4 fragColor; +#endif + +void DoAlphaTest(float a) +{ +#ifndef NO_ALPHATEST + if(a < u_alphaRef.x || a >= u_alphaRef.y) + discard; +#endif +} diff --git a/vendor/librw/src/gl/shaders/header.vert b/vendor/librw/src/gl/shaders/header.vert new file mode 100644 index 0000000..bb9881f --- /dev/null +++ b/vendor/librw/src/gl/shaders/header.vert @@ -0,0 +1,128 @@ + +//#define DIRECTIONALS +//#define POINTLIGHTS +//#define SPOTLIGHTS + +#define ATTRIB_POS 0 +#define ATTRIB_NORMAL 1 +#define ATTRIB_COLOR 2 +#define ATTRIB_WEIGHTS 3 +#define ATTRIB_INDICES 4 +#define ATTRIB_TEXCOORDS0 5 +#define ATTRIB_TEXCOORDS1 6 + + +VSIN(ATTRIB_NORMAL) vec3 in_normal; +VSIN(ATTRIB_COLOR) vec4 in_color; +VSIN(ATTRIB_WEIGHTS) vec4 in_weights; +VSIN(ATTRIB_INDICES) vec4 in_indices; +VSIN(ATTRIB_TEXCOORDS0) vec2 in_tex0; +VSIN(ATTRIB_TEXCOORDS1) vec2 in_tex1; + + +#ifdef USE_UBOS +layout(std140) uniform State +{ + vec2 u_alphaRef; + vec4 u_fogData; + vec4 u_fogColor; +}; +#else +uniform vec4 u_alphaRef; +uniform vec4 u_fogData; +uniform vec4 u_fogColor; +#endif + +#define u_fogStart (u_fogData.x) +#define u_fogEnd (u_fogData.y) +#define u_fogRange (u_fogData.z) +#define u_fogDisable (u_fogData.w) + +#ifdef USE_UBOS +layout(std140) uniform Scene +{ + mat4 u_proj; + mat4 u_view; +}; +#else +uniform mat4 u_proj; +uniform mat4 u_view; +#endif + +#define MAX_LIGHTS 8 + +#ifdef USE_UBOS +layout(std140) uniform Object +{ + mat4 u_world; + vec4 u_ambLight; + vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot + vec4 u_lightPosition[MAX_LIGHTS]; + vec4 u_lightDirection[MAX_LIGHTS]; + vec4 u_lightColor[MAX_LIGHTS]; +}; +#else +uniform mat4 u_world; +uniform vec4 u_ambLight; +uniform vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot +uniform vec4 u_lightPosition[MAX_LIGHTS]; +uniform vec4 u_lightDirection[MAX_LIGHTS]; +uniform vec4 u_lightColor[MAX_LIGHTS]; +#endif + +uniform vec4 u_matColor; +uniform vec4 u_surfProps; // amb, spec, diff, extra + +#define surfAmbient (u_surfProps.x) +#define surfSpecular (u_surfProps.y) +#define surfDiffuse (u_surfProps.z) + +vec3 DoDynamicLight(vec3 V, vec3 N) +{ + vec3 color = vec3(0.0, 0.0, 0.0); + for(int i = 0; i < MAX_LIGHTS; i++){ + if(u_lightParams[i].x == 0.0) + break; +#ifdef DIRECTIONALS + if(u_lightParams[i].x == 1.0){ + // direct + float l = max(0.0, dot(N, -u_lightDirection[i].xyz)); + color += l*u_lightColor[i].rgb; + }else +#endif +#ifdef POINTLIGHTS + if(u_lightParams[i].x == 2.0){ + // point + vec3 dir = V - u_lightPosition[i].xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/u_lightParams[i].y)); + float l = max(0.0, dot(N, -normalize(dir))); + color += l*u_lightColor[i].rgb*atten; + }else +#endif +#ifdef SPOTLIGHTS + if(u_lightParams[i].x == 3.0){ + // spot + vec3 dir = V - u_lightPosition[i].xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/u_lightParams[i].y)); + dir /= dist; + float l = max(0.0, dot(N, -dir)); + float pcos = dot(dir, u_lightDirection[i].xyz); // cos to point + float ccos = -u_lightParams[i].z; + float falloff = (pcos-ccos)/(1.0-ccos); + if(falloff < 0.0) // outside of cone + l = 0.0; + l *= max(falloff, u_lightParams[i].w); + return l*u_lightColor[i].rgb*atten; + }else +#endif + ; + } + return color; +} + +float DoFog(float w) +{ + return clamp((w - u_fogEnd)*u_fogRange, u_fogDisable, 1.0); +} diff --git a/vendor/librw/src/gl/shaders/header_fs.inc b/vendor/librw/src/gl/shaders/header_fs.inc new file mode 100644 index 0000000..802bb4f --- /dev/null +++ b/vendor/librw/src/gl/shaders/header_fs.inc @@ -0,0 +1,32 @@ +const char *header_frag_src = +"#ifdef USE_UBOS\n" +"layout(std140) uniform State\n" +"{\n" +" vec2 u_alphaRef;\n" +" vec4 u_fogData;\n" +" vec4 u_fogColor;\n" +"};\n" +"#else\n" +"uniform vec4 u_alphaRef;\n" + +"uniform vec4 u_fogData;\n" +"uniform vec4 u_fogColor;\n" +"#endif\n" + +"#define u_fogStart (u_fogData.x)\n" +"#define u_fogEnd (u_fogData.y)\n" +"#define u_fogRange (u_fogData.z)\n" +"#define u_fogDisable (u_fogData.w)\n" + +"#ifndef GL2\n" +"out vec4 fragColor;\n" +"#endif\n" + +"void DoAlphaTest(float a)\n" +"{\n" +"#ifndef NO_ALPHATEST\n" +" if(a < u_alphaRef.x || a >= u_alphaRef.y)\n" +" discard;\n" +"#endif\n" +"}\n" +; diff --git a/vendor/librw/src/gl/shaders/header_vs.inc b/vendor/librw/src/gl/shaders/header_vs.inc new file mode 100644 index 0000000..ffa4683 --- /dev/null +++ b/vendor/librw/src/gl/shaders/header_vs.inc @@ -0,0 +1,130 @@ +const char *header_vert_src = + +"//#define DIRECTIONALS\n" +"//#define POINTLIGHTS\n" +"//#define SPOTLIGHTS\n" + +"#define ATTRIB_POS 0\n" +"#define ATTRIB_NORMAL 1\n" +"#define ATTRIB_COLOR 2\n" +"#define ATTRIB_WEIGHTS 3\n" +"#define ATTRIB_INDICES 4\n" +"#define ATTRIB_TEXCOORDS0 5\n" +"#define ATTRIB_TEXCOORDS1 6\n" + + +"VSIN(ATTRIB_NORMAL) vec3 in_normal;\n" +"VSIN(ATTRIB_COLOR) vec4 in_color;\n" +"VSIN(ATTRIB_WEIGHTS) vec4 in_weights;\n" +"VSIN(ATTRIB_INDICES) vec4 in_indices;\n" +"VSIN(ATTRIB_TEXCOORDS0) vec2 in_tex0;\n" +"VSIN(ATTRIB_TEXCOORDS1) vec2 in_tex1;\n" + + +"#ifdef USE_UBOS\n" +"layout(std140) uniform State\n" +"{\n" +" vec2 u_alphaRef;\n" +" vec4 u_fogData;\n" +" vec4 u_fogColor;\n" +"};\n" +"#else\n" +"uniform vec4 u_alphaRef;\n" +"uniform vec4 u_fogData;\n" +"uniform vec4 u_fogColor;\n" +"#endif\n" + +"#define u_fogStart (u_fogData.x)\n" +"#define u_fogEnd (u_fogData.y)\n" +"#define u_fogRange (u_fogData.z)\n" +"#define u_fogDisable (u_fogData.w)\n" + +"#ifdef USE_UBOS\n" +"layout(std140) uniform Scene\n" +"{\n" +" mat4 u_proj;\n" +" mat4 u_view;\n" +"};\n" +"#else\n" +"uniform mat4 u_proj;\n" +"uniform mat4 u_view;\n" +"#endif\n" + +"#define MAX_LIGHTS 8\n" + +"#ifdef USE_UBOS\n" +"layout(std140) uniform Object\n" +"{\n" +" mat4 u_world;\n" +" vec4 u_ambLight;\n" +" vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot\n" +" vec4 u_lightPosition[MAX_LIGHTS];\n" +" vec4 u_lightDirection[MAX_LIGHTS];\n" +" vec4 u_lightColor[MAX_LIGHTS];\n" +"};\n" +"#else\n" +"uniform mat4 u_world;\n" +"uniform vec4 u_ambLight;\n" +"uniform vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot\n" +"uniform vec4 u_lightPosition[MAX_LIGHTS];\n" +"uniform vec4 u_lightDirection[MAX_LIGHTS];\n" +"uniform vec4 u_lightColor[MAX_LIGHTS];\n" +"#endif\n" + +"uniform vec4 u_matColor;\n" +"uniform vec4 u_surfProps; // amb, spec, diff, extra\n" + +"#define surfAmbient (u_surfProps.x)\n" +"#define surfSpecular (u_surfProps.y)\n" +"#define surfDiffuse (u_surfProps.z)\n" + +"vec3 DoDynamicLight(vec3 V, vec3 N)\n" +"{\n" +" vec3 color = vec3(0.0, 0.0, 0.0);\n" +" for(int i = 0; i < MAX_LIGHTS; i++){\n" +" if(u_lightParams[i].x == 0.0)\n" +" break;\n" +"#ifdef DIRECTIONALS\n" +" if(u_lightParams[i].x == 1.0){\n" +" // direct\n" +" float l = max(0.0, dot(N, -u_lightDirection[i].xyz));\n" +" color += l*u_lightColor[i].rgb;\n" +" }else\n" +"#endif\n" +"#ifdef POINTLIGHTS\n" +" if(u_lightParams[i].x == 2.0){\n" +" // point\n" +" vec3 dir = V - u_lightPosition[i].xyz;\n" +" float dist = length(dir);\n" +" float atten = max(0.0, (1.0 - dist/u_lightParams[i].y));\n" +" float l = max(0.0, dot(N, -normalize(dir)));\n" +" color += l*u_lightColor[i].rgb*atten;\n" +" }else\n" +"#endif\n" +"#ifdef SPOTLIGHTS\n" +" if(u_lightParams[i].x == 3.0){\n" +" // spot\n" +" vec3 dir = V - u_lightPosition[i].xyz;\n" +" float dist = length(dir);\n" +" float atten = max(0.0, (1.0 - dist/u_lightParams[i].y));\n" +" dir /= dist;\n" +" float l = max(0.0, dot(N, -dir));\n" +" float pcos = dot(dir, u_lightDirection[i].xyz); // cos to point\n" +" float ccos = -u_lightParams[i].z;\n" +" float falloff = (pcos-ccos)/(1.0-ccos);\n" +" if(falloff < 0.0) // outside of cone\n" +" l = 0.0;\n" +" l *= max(falloff, u_lightParams[i].w);\n" +" return l*u_lightColor[i].rgb*atten;\n" +" }else\n" +"#endif\n" +" ;\n" +" }\n" +" return color;\n" +"}\n" + +"float DoFog(float w)\n" +"{\n" +" return clamp((w - u_fogEnd)*u_fogRange, u_fogDisable, 1.0);\n" +"}\n" +; diff --git a/vendor/librw/src/gl/shaders/im2d.vert b/vendor/librw/src/gl/shaders/im2d.vert new file mode 100644 index 0000000..cdb9da8 --- /dev/null +++ b/vendor/librw/src/gl/shaders/im2d.vert @@ -0,0 +1,18 @@ +uniform vec4 u_xform; + +VSIN(ATTRIB_POS) vec4 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + gl_Position = in_pos; + gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw; + v_fog = DoFog(gl_Position.w); + gl_Position.xyz *= gl_Position.w; + v_color = in_color; + v_tex0 = in_tex0; +} diff --git a/vendor/librw/src/gl/shaders/im2d_gl.inc b/vendor/librw/src/gl/shaders/im2d_gl.inc new file mode 100644 index 0000000..4e1d631 --- /dev/null +++ b/vendor/librw/src/gl/shaders/im2d_gl.inc @@ -0,0 +1,20 @@ +const char *im2d_vert_src = +"uniform vec4 u_xform;\n" + +"VSIN(ATTRIB_POS) vec4 in_pos;\n" + +"VSOUT vec4 v_color;\n" +"VSOUT vec2 v_tex0;\n" +"VSOUT float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" gl_Position = in_pos;\n" +" gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw;\n" +" v_fog = DoFog(gl_Position.w);\n" +" gl_Position.xyz *= gl_Position.w;\n" +" v_color = in_color;\n" +" v_tex0 = in_tex0;\n" +"}\n" +; diff --git a/vendor/librw/src/gl/shaders/im3d.vert b/vendor/librw/src/gl/shaders/im3d.vert new file mode 100644 index 0000000..7088352 --- /dev/null +++ b/vendor/librw/src/gl/shaders/im3d.vert @@ -0,0 +1,16 @@ +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + vec4 CamVertex = u_view * Vertex; + gl_Position = u_proj * CamVertex; + v_color = in_color; + v_tex0 = in_tex0; + v_fog = DoFog(gl_Position.w); +} diff --git a/vendor/librw/src/gl/shaders/im3d_gl.inc b/vendor/librw/src/gl/shaders/im3d_gl.inc new file mode 100644 index 0000000..389589b --- /dev/null +++ b/vendor/librw/src/gl/shaders/im3d_gl.inc @@ -0,0 +1,18 @@ +const char *im3d_vert_src = +"VSIN(ATTRIB_POS) vec3 in_pos;\n" + +"VSOUT vec4 v_color;\n" +"VSOUT vec2 v_tex0;\n" +"VSOUT float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 Vertex = u_world * vec4(in_pos, 1.0);\n" +" vec4 CamVertex = u_view * Vertex;\n" +" gl_Position = u_proj * CamVertex;\n" +" v_color = in_color;\n" +" v_tex0 = in_tex0;\n" +" v_fog = DoFog(gl_Position.w);\n" +"}\n" +; diff --git a/vendor/librw/src/gl/shaders/matfx_env.frag b/vendor/librw/src/gl/shaders/matfx_env.frag new file mode 100644 index 0000000..0ef20d1 --- /dev/null +++ b/vendor/librw/src/gl/shaders/matfx_env.frag @@ -0,0 +1,34 @@ +uniform sampler2D tex0; +uniform sampler2D tex1; + +uniform vec4 u_fxparams; + +#define shininess (u_fxparams.x) +#define disableFBA (u_fxparams.y) + +FSIN vec4 v_color; +FSIN vec4 v_envColor; +FSIN vec2 v_tex0; +FSIN vec2 v_tex1; +FSIN float v_fog; + +void +main(void) +{ + vec4 pass1 = v_color; + pass1 *= texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + + vec4 pass2 = v_envColor*shininess*texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y)); + + pass1.rgb = mix(u_fogColor.rgb, pass1.rgb, v_fog); + pass2.rgb = mix(vec3(0.0, 0.0, 0.0), pass2.rgb, v_fog); + + float fba = max(pass1.a, disableFBA); + vec4 color; + color.rgb = pass1.rgb*pass1.a + pass2.rgb*fba; + color.a = pass1.a; + + DoAlphaTest(color.a); + + FRAGCOLOR(color); +} diff --git a/vendor/librw/src/gl/shaders/matfx_env.vert b/vendor/librw/src/gl/shaders/matfx_env.vert new file mode 100644 index 0000000..580a49f --- /dev/null +++ b/vendor/librw/src/gl/shaders/matfx_env.vert @@ -0,0 +1,31 @@ +uniform mat4 u_texMatrix; +uniform vec4 u_colorClamp; +uniform vec4 u_envColor; + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec4 v_envColor; +VSOUT vec2 v_tex0; +VSOUT vec2 v_tex1; +VSOUT float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + v_tex1 = (u_texMatrix * vec4(Normal, 1.0)).xy; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_envColor = max(v_color, u_colorClamp) * u_envColor; + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/vendor/librw/src/gl/shaders/matfx_gl.inc b/vendor/librw/src/gl/shaders/matfx_gl.inc new file mode 100644 index 0000000..a1dcaf8 --- /dev/null +++ b/vendor/librw/src/gl/shaders/matfx_gl.inc @@ -0,0 +1,69 @@ +const char *matfx_env_vert_src = +"uniform mat4 u_texMatrix;\n" +"uniform vec4 u_colorClamp;\n" +"uniform vec4 u_envColor;\n" + +"VSIN(ATTRIB_POS) vec3 in_pos;\n" + +"VSOUT vec4 v_color;\n" +"VSOUT vec4 v_envColor;\n" +"VSOUT vec2 v_tex0;\n" +"VSOUT vec2 v_tex1;\n" +"VSOUT float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 Vertex = u_world * vec4(in_pos, 1.0);\n" +" gl_Position = u_proj * u_view * Vertex;\n" +" vec3 Normal = mat3(u_world) * in_normal;\n" + +" v_tex0 = in_tex0;\n" +" v_tex1 = (u_texMatrix * vec4(Normal, 1.0)).xy;\n" + +" v_color = in_color;\n" +" v_color.rgb += u_ambLight.rgb*surfAmbient;\n" +" v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse;\n" +" v_color = clamp(v_color, 0.0, 1.0);\n" +" v_envColor = max(v_color, u_colorClamp) * u_envColor;\n" +" v_color *= u_matColor;\n" + +" v_fog = DoFog(gl_Position.w);\n" +"}\n" +; +const char *matfx_env_frag_src = +"uniform sampler2D tex0;\n" +"uniform sampler2D tex1;\n" + +"uniform vec4 u_fxparams;\n" + +"#define shininess (u_fxparams.x)\n" +"#define disableFBA (u_fxparams.y)\n" + +"FSIN vec4 v_color;\n" +"FSIN vec4 v_envColor;\n" +"FSIN vec2 v_tex0;\n" +"FSIN vec2 v_tex1;\n" +"FSIN float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 pass1 = v_color;\n" +" pass1 *= texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y));\n" + +" vec4 pass2 = v_envColor*shininess*texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y));\n" + +" pass1.rgb = mix(u_fogColor.rgb, pass1.rgb, v_fog);\n" +" pass2.rgb = mix(vec3(0.0, 0.0, 0.0), pass2.rgb, v_fog);\n" + +" float fba = max(pass1.a, disableFBA);\n" +" vec4 color;\n" +" color.rgb = pass1.rgb*pass1.a + pass2.rgb*fba;\n" +" color.a = pass1.a;\n" + +" DoAlphaTest(color.a);\n" + +" FRAGCOLOR(color);\n" +"}\n" +; diff --git a/vendor/librw/src/gl/shaders/simple.frag b/vendor/librw/src/gl/shaders/simple.frag new file mode 100644 index 0000000..32b2afb --- /dev/null +++ b/vendor/librw/src/gl/shaders/simple.frag @@ -0,0 +1,15 @@ +uniform sampler2D tex0; + +FSIN vec4 v_color; +FSIN vec2 v_tex0; +FSIN float v_fog; + +void +main(void) +{ + vec4 color = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog); + DoAlphaTest(color.a); + FRAGCOLOR(color); +} + diff --git a/vendor/librw/src/gl/shaders/simple_fs_gl.inc b/vendor/librw/src/gl/shaders/simple_fs_gl.inc new file mode 100644 index 0000000..a9216ca --- /dev/null +++ b/vendor/librw/src/gl/shaders/simple_fs_gl.inc @@ -0,0 +1,17 @@ +const char *simple_frag_src = +"uniform sampler2D tex0;\n" + +"FSIN vec4 v_color;\n" +"FSIN vec2 v_tex0;\n" +"FSIN float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 color = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y));\n" +" color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog);\n" +" DoAlphaTest(color.a);\n" +" FRAGCOLOR(color);\n" +"}\n" + +; diff --git a/vendor/librw/src/gl/shaders/skin.vert b/vendor/librw/src/gl/shaders/skin.vert new file mode 100644 index 0000000..7408542 --- /dev/null +++ b/vendor/librw/src/gl/shaders/skin.vert @@ -0,0 +1,32 @@ +uniform mat4 u_boneMatrices[64]; + +VSIN(ATTRIB_POS) vec3 in_pos; + +VSOUT vec4 v_color; +VSOUT vec2 v_tex0; +VSOUT float v_fog; + +void +main(void) +{ + vec3 SkinVertex = vec3(0.0, 0.0, 0.0); + vec3 SkinNormal = vec3(0.0, 0.0, 0.0); + for(int i = 0; i < 4; i++){ + SkinVertex += (u_boneMatrices[int(in_indices[i])] * vec4(in_pos, 1.0)).xyz * in_weights[i]; + SkinNormal += (mat3(u_boneMatrices[int(in_indices[i])]) * in_normal) * in_weights[i]; + } + + vec4 Vertex = u_world * vec4(SkinVertex, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * SkinNormal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.z); +} diff --git a/vendor/librw/src/gl/shaders/skin_gl.inc b/vendor/librw/src/gl/shaders/skin_gl.inc new file mode 100644 index 0000000..7d1268e --- /dev/null +++ b/vendor/librw/src/gl/shaders/skin_gl.inc @@ -0,0 +1,34 @@ +const char *skin_vert_src = +"uniform mat4 u_boneMatrices[64];\n" + +"VSIN(ATTRIB_POS) vec3 in_pos;\n" + +"VSOUT vec4 v_color;\n" +"VSOUT vec2 v_tex0;\n" +"VSOUT float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec3 SkinVertex = vec3(0.0, 0.0, 0.0);\n" +" vec3 SkinNormal = vec3(0.0, 0.0, 0.0);\n" +" for(int i = 0; i < 4; i++){\n" +" SkinVertex += (u_boneMatrices[int(in_indices[i])] * vec4(in_pos, 1.0)).xyz * in_weights[i];\n" +" SkinNormal += (mat3(u_boneMatrices[int(in_indices[i])]) * in_normal) * in_weights[i];\n" +" }\n" + +" vec4 Vertex = u_world * vec4(SkinVertex, 1.0);\n" +" gl_Position = u_proj * u_view * Vertex;\n" +" vec3 Normal = mat3(u_world) * SkinNormal;\n" + +" v_tex0 = in_tex0;\n" + +" v_color = in_color;\n" +" v_color.rgb += u_ambLight.rgb*surfAmbient;\n" +" v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse;\n" +" v_color = clamp(v_color, 0.0, 1.0);\n" +" v_color *= u_matColor;\n" + +" v_fog = DoFog(gl_Position.z);\n" +"}\n" +; diff --git a/vendor/librw/src/gl/wdgl.cpp b/vendor/librw/src/gl/wdgl.cpp new file mode 100644 index 0000000..d1ac4b8 --- /dev/null +++ b/vendor/librw/src/gl/wdgl.cpp @@ -0,0 +1,875 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwwdgl.h" + +#ifdef RW_OPENGL +#include "glad/glad.h" +#endif + +#define PLUGIN_ID 2 + +namespace rw { +namespace wdgl { + +static void* +driverOpen(void *o, int32, int32) +{ + engine->driver[PLATFORM_WDGL]->defaultPipeline = makeDefaultPipeline(); + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_WDGL, 0, PLATFORM_WDGL, + driverOpen, driverClose); +} + + +// VC +// 8733 0 0 0 3 +// 45 1 0 0 2 +// 8657 1 3 0 2 +// 4610 2 1 1 3 +// 4185 3 2 1 4 +// 256 4 2 1 4 +// 201 4 4 1 4 +// 457 5 2 0 4 + +// SA +// 20303 0 0 0 3 vertices: 3 floats +// 53 1 0 0 2 texCoords: 2 floats +// 20043 1 3 0 2 texCoords: 2 shorts +// 6954 2 1 1 3 normal: 3 bytes normalized +// 13527 3 2 1 4 color: 4 ubytes normalized +// 196 4 2 1 4 weight: 4 ubytes normalized +// 225 4 4 1 4 weight: 4 ushorts normalized +// 421 5 2 0 4 indices: 4 ubytes +// 12887 6 2 1 4 extracolor:4 ubytes normalized + +/* +static void +printAttribInfo(AttribDesc *attribs, int n) +{ + for(int i = 0; i < n; i++) + printf("%x %x %x %x\n", + attribs[i].index, + attribs[i].type, + attribs[i].normalized, + attribs[i].size); +} +*/ + +#ifdef RW_OPENGL +void +uploadGeo(Geometry *geo) +{ + InstanceDataHeader *inst = (InstanceDataHeader*)geo->instData; + MeshHeader *meshHeader = geo->meshHeader; + + glGenBuffers(1, &inst->vbo); + glBindBuffer(GL_ARRAY_BUFFER, inst->vbo); + glBufferData(GL_ARRAY_BUFFER, inst->dataSize, + inst->data, GL_STATIC_DRAW); + + glGenBuffers(1, &inst->ibo); + glBindBuffer(GL_ARRAY_BUFFER, inst->ibo); + glBufferData(GL_ARRAY_BUFFER, meshHeader->totalIndices*2, + 0, GL_STATIC_DRAW); + GLintptr offset = 0; + for(uint32 i = 0; i < meshHeader->numMeshes; i++){ + Mesh *mesh = &meshHeader->getMeshes()[i]; + glBufferSubData(GL_ARRAY_BUFFER, offset, mesh->numIndices*2, + mesh->indices); + offset += mesh->numIndices*2; + } + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void +setAttribPointers(InstanceDataHeader *inst) +{ + static GLenum attribType[] = { + GL_FLOAT, + GL_BYTE, GL_UNSIGNED_BYTE, + GL_SHORT, GL_UNSIGNED_SHORT + }; + for(int32 i = 0; i < inst->numAttribs; i++){ + AttribDesc *a = &inst->attribs[i]; + glEnableVertexAttribArray(a->index); + glVertexAttribPointer(a->index, a->size, attribType[a->type], + a->normalized, a->stride, + (void*)(uint64)a->offset); + } +} +#endif + +void +packattrib(uint8 *dst, float32 *src, AttribDesc *a, float32 scale=1.0f) +{ + int8 *i8dst; + uint16 *u16dst; + int16 *i16dst; + + switch(a->type){ + case 0: // float + memcpy(dst, src, a->size*4); + break; + + // TODO: maybe have loop inside if? + case 1: // byte + i8dst = (int8*)dst; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + i8dst[i] = src[i]*scale; + else + i8dst[i] = src[i]*127.0f; + } + break; + + case 2: // ubyte + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = src[i]*scale; + else + dst[i] = src[i]*255.0f; + } + break; + + case 3: // short + i16dst = (int16*)dst; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + i16dst[i] = src[i]*scale; + else + i16dst[i] = src[i]*32767.0f; + } + break; + + case 4: // ushort + u16dst = (uint16*)dst; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + u16dst[i] = src[i]*scale; + else + u16dst[i] = src[i]*65535.0f; + } + break; + } +} + +void +unpackattrib(float *dst, uint8 *src, AttribDesc *a, float32 scale=1.0f) +{ + int8 *i8src; + uint16 *u16src; + int16 *i16src; + + switch(a->type){ + case 0: // float + memcpy(dst, src, a->size*4); + break; + + // TODO: maybe have loop inside if? + case 1: // byte + i8src = (int8*)src; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = i8src[i]/scale; + else + dst[i] = i8src[i]/127.0f; + } + break; + + case 2: // ubyte + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = src[i]/scale; + else + dst[i] = src[i]/255.0f; + } + break; + + case 3: // short + i16src = (int16*)src; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = i16src[i]/scale; + else + dst[i] = i16src[i]/32767.0f; + } + break; + + case 4: // ushort + u16src = (uint16*)src; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = u16src[i]/scale; + else + dst[i] = u16src[i]/65435.0f; + } + break; + } +} + +void* +destroyNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_WDGL) + return object; + InstanceDataHeader *header = + (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + // TODO: delete ibo and vbo + rwFree(header->attribs); + rwFree(header->data); + rwFree(header); + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_WDGL; + header->vbo = 0; + header->ibo = 0; + header->numAttribs = stream->readU32(); + header->attribs = rwNewT(AttribDesc, header->numAttribs, MEMDUR_EVENT | ID_GEOMETRY); + stream->read32(header->attribs, + header->numAttribs*sizeof(AttribDesc)); + header->dataSize = header->attribs[0].stride*geometry->numVertices; + header->data = rwNewT(uint8, header->dataSize, MEMDUR_EVENT | ID_GEOMETRY); + ASSERTLITTLE; + stream->read8(header->data, header->dataSize); + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32, void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_WDGL) + return stream; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + stream->writeU32(header->numAttribs); + stream->write32(header->attribs, header->numAttribs*sizeof(AttribDesc)); + ASSERTLITTLE; + stream->write8(header->data, header->dataSize); + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_WDGL) + return 0; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + return 4 + header->numAttribs*sizeof(AttribDesc) + header->dataSize; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +void +printPipeinfo(Atomic *a) +{ + Geometry *g = a->geometry; + if(g->instData == nil || g->instData->platform != PLATFORM_WDGL) + return; + int32 plgid = 0; + if(a->pipeline) + plgid = a->pipeline->pluginID; + printf("%s %x: ", debugFile, plgid); + InstanceDataHeader *h = (InstanceDataHeader*)g->instData; + for(int i = 0; i < h->numAttribs; i++) + printf("%x(%x) ", h->attribs[i].index, h->attribs[i].type); + printf("\n"); +} + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE (or not, wdgl can't render) + if(geo->instData) + return; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geo->instData = header; + header->platform = PLATFORM_WDGL; + header->vbo = 0; + header->ibo = 0; + header->numAttribs = + pipe->numCustomAttribs + 1 + (geo->numTexCoordSets > 0); + if(geo->flags & Geometry::PRELIT) + header->numAttribs++; + if(geo->flags & Geometry::NORMALS) + header->numAttribs++; + int32 offset = 0; + header->attribs = rwNewT(AttribDesc, header->numAttribs, MEMDUR_EVENT | ID_GEOMETRY); + + AttribDesc *a = header->attribs; + // Vertices + a->index = 0; + a->type = 0; + a->normalized = 0; + a->size = 3; + a->offset = offset; + offset += 12; + a++; + int32 firstCustom = 1; + + // texCoords, only one set here + if(geo->numTexCoordSets){ + a->index = 1; + a->type = 3; + a->normalized = 0; + a->size = 2; + a->offset = offset; + offset += 4; + a++; + firstCustom++; + } + + if(geo->flags & Geometry::NORMALS){ + a->index = 2; + a->type = 1; + a->normalized = 1; + a->size = 3; + a->offset = offset; + offset += 4; + a++; + firstCustom++; + } + + if(geo->flags & Geometry::PRELIT){ + a->index = 3; + a->type = 2; + a->normalized = 1; + a->size = 4; + a->offset = offset; + offset += 4; + a++; + firstCustom++; + } + + if(pipe->instanceCB) + offset += pipe->instanceCB(geo, firstCustom, offset); + else{ + header->dataSize = offset*geo->numVertices; + header->data = rwNewT(uint8, header->dataSize, MEMDUR_EVENT | ID_GEOMETRY); + } + + a = header->attribs; + for(int32 i = 0; i < header->numAttribs; i++) + a[i].stride = offset; + + uint8 *p = header->data + a->offset; + V3d *vert = geo->morphTargets->vertices; + for(int32 i = 0; i < geo->numVertices; i++){ + packattrib(p, (float32*)vert, a); + vert++; + p += a->stride; + } + a++; + + if(geo->numTexCoordSets){ + p = header->data + a->offset; + TexCoords *texcoord = geo->texCoords[0]; + for(int32 i = 0; i < geo->numVertices; i++){ + packattrib(p, (float32*)texcoord, a, 512.0f); + texcoord++; + p += a->stride; + } + a++; + } + + if(geo->flags & Geometry::NORMALS){ + p = header->data + a->offset; + V3d *norm = geo->morphTargets->normals; + for(int32 i = 0; i < geo->numVertices; i++){ + packattrib(p, (float32*)norm, a); + norm++; + p += a->stride; + } + a++; + } + + if(geo->flags & Geometry::PRELIT){ + // TODO: this seems too complicated + p = header->data + a->offset; + RGBA *color = geo->colors; + float32 f[4]; + for(int32 i = 0; i < geo->numVertices; i++){ + f[0] = color->red/255.0f; + f[1] = color->green/255.0f; + f[2] = color->blue/255.0f; + f[3] = color->alpha/255.0f; + packattrib(p, (float32*)f, a); + color++; + p += a->stride; + } + a++; + } +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_WDGL); + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + + uint8 *p; + TexCoords *texcoord = geo->texCoords[0]; + RGBA *color = geo->colors; + V3d *vert = geo->morphTargets->vertices; + V3d *norm = geo->morphTargets->normals; + float32 f[4]; + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + for(int i = 0; i < header->numAttribs; i++){ + AttribDesc *a = &header->attribs[i]; + p = header->data + a->offset; + + switch(a->index){ + case 0: // Vertices + for(int32 i = 0; i < geo->numVertices; i++){ + unpackattrib((float32*)vert, p, a); + vert++; + p += a->stride; + } + break; + + case 1: // texCoords + for(int32 i = 0; i < geo->numVertices; i++){ + unpackattrib((float32*)texcoord, p, a, 512.0f); + texcoord++; + p += a->stride; + } + break; + + case 2: // normals + for(int32 i = 0; i < geo->numVertices; i++){ + unpackattrib((float32*)norm, p, a); + norm++; + p += a->stride; + } + break; + + case 3: // colors + for(int32 i = 0; i < geo->numVertices; i++){ + // TODO: this seems too complicated + unpackattrib(f, p, a); + color->red = f[0]*255.0f; + color->green = f[1]*255.0f; + color->blue = f[2]*255.0f; + color->alpha = f[3]*255.0f; + color++; + p += a->stride; + } + break; + } + } + + if(pipe->uninstanceCB) + pipe->uninstanceCB(geo); + + geo->generateTriangles(); + + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_GL3); + this->numCustomAttribs = 0; + this->impl.instance = wdgl::instance; + this->impl.uninstance = wdgl::uninstance; + this->instanceCB = nil; + this->uninstanceCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + return pipe; +} + +// Skin + +Stream* +readNativeSkin(Stream *stream, int32, void *object, int32 offset) +{ + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_GL){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, offset) = skin; + + int32 numBones = stream->readI32(); + skin->init(numBones, 0, 0); + stream->read32(skin->inverseMatrices, skin->numBones*64); + return stream; +} + +Stream* +writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset) +{ + writeChunkHeader(stream, ID_STRUCT, len-12); + stream->writeU32(PLATFORM_GL); + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + stream->writeI32(skin->numBones); + stream->write32(skin->inverseMatrices, skin->numBones*64); + return stream; +} + +int32 +getSizeNativeSkin(void *object, int32 offset) +{ + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin == nil) + return -1; + int32 size = 12 + 4 + 4 + skin->numBones*64; + return size; +} + +uint32 +skinInstanceCB(Geometry *g, int32 i, uint32 offset) +{ + InstanceDataHeader *header = (InstanceDataHeader*)g->instData; + AttribDesc *a = &header->attribs[i]; + // weights + a->index = 4; + a->type = 2; /* but also short o_O */ + a->normalized = 1; + a->size = 4; + a->offset = offset; + offset += 4; + a++; + + // indices + a->index = 5; + a->type = 2; + a->normalized = 0; + a->size = 4; + a->offset = offset; + offset += 4; + + header->dataSize = offset*g->numVertices; + header->data = rwNewT(uint8, header->dataSize, MEMDUR_EVENT | ID_GEOMETRY); + + Skin *skin = Skin::get(g); + if(skin == nil) + return 8; + + a = &header->attribs[i]; + uint8 *wgt = header->data + a[0].offset; + uint8 *idx = header->data + a[1].offset; + uint8 *indices = skin->indices; + float32 *weights = skin->weights; + for(int32 i = 0; i < g->numVertices; i++){ + packattrib(wgt, weights, a); + weights += 4; + wgt += offset; + idx[0] = *indices++; + idx[1] = *indices++; + idx[2] = *indices++; + idx[3] = *indices++; + idx += offset; + } + + return 8; +} + +void +skinUninstanceCB(Geometry *geo) +{ + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + + Skin *skin = Skin::get(geo); + if(skin == nil) + return; + + uint8 *data = skin->data; + float *invMats = skin->inverseMatrices; + skin->init(skin->numBones, skin->numBones, geo->numVertices); + memcpy(skin->inverseMatrices, invMats, skin->numBones*64); + rwFree(data); + + uint8 *p; + float *weights = skin->weights; + uint8 *indices = skin->indices; + for(int i = 0; i < header->numAttribs; i++){ + AttribDesc *a = &header->attribs[i]; + p = header->data + a->offset; + + switch(a->index){ + case 4: // weights + for(int32 i = 0; i < geo->numVertices; i++){ + unpackattrib(weights, p, a); +float sum = weights[0] + weights[1] + weights[2] + weights[3]; +if(sum){ + weights[0] /= sum; + weights[1] /= sum; + weights[2] /= sum; + weights[3] /= sum; +} + weights += 4; + p += a->stride; + } + break; + + case 5: // indices + for(int32 i = 0; i < geo->numVertices; i++){ + *indices++ = p[0]; + *indices++ = p[1]; + *indices++ = p[2]; + *indices++ = p[3]; + p += a->stride; + } + break; + } + } + + skin->findNumWeights(geo->numVertices); + skin->findUsedBones(geo->numVertices); +} + +// Skin + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_WDGL] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_WDGL])->destroy(); + skinGlobals.pipelines[PLATFORM_WDGL] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_WDGL, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + pipe->numCustomAttribs = 2; + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = skinUninstanceCB; + return pipe; +} + +// MatFX + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_WDGL] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_WDGL])->destroy(); + matFXGlobals.pipelines[PLATFORM_WDGL] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_WDGL, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +// Raster + +int32 nativeRasterOffset; + +#ifdef RW_OPENGL +struct GlRaster { + GLuint id; +}; + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + GlRaster *raster = PLUGINOFFSET(GlRaster, object, offset); + raster->id = 0; + return object; +} + +static void* +destroyNativeRaster(void *object, int32 offset, int32) +{ + // TODO: + return object; +} + +static void* +copyNativeRaster(void *dst, void *, int32 offset, int32) +{ + GlRaster *raster = PLUGINOFFSET(GlRaster, dst, offset); + raster->id = 0; + return dst; +} + +void +registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(GlRaster), + ID_RASTERWDGL, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); +} + +void +Texture::upload(void) +{ + GLuint id; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + Raster *r = this->raster; + if(r->palette){ + printf("can't upload paletted raster\n"); + return; + } + + static GLenum filter[] = { + 0, GL_NEAREST, GL_LINEAR, + GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, + GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR + }; + static GLenum filternomip[] = { + 0, GL_NEAREST, GL_LINEAR, + GL_NEAREST, GL_LINEAR, + GL_NEAREST, GL_LINEAR + }; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + filternomip[this->filterAddressing & 0xFF]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + filternomip[this->filterAddressing & 0xFF]); + + static GLenum wrap[] = { + 0, GL_REPEAT, GL_MIRRORED_REPEAT, + GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER + }; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, + wrap[(this->filterAddressing >> 8) & 0xF]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, + wrap[(this->filterAddressing >> 12) & 0xF]); + + switch(r->format & 0xF00){ + case Raster::C8888: + glTexImage2D(GL_TEXTURE_2D, 0, 4, r->width, r->height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, r->pixels); + break; + default: + printf("unsupported raster format: %x\n", r->format); + break; + } + glBindTexture(GL_TEXTURE_2D, 0); + GlRaster *glr = PLUGINOFFSET(GlRaster, r, nativeRasterOffset); + glr->id = id; +} + +void +Texture::bind(int n) +{ + Raster *r = this->raster; + GlRaster *glr = PLUGINOFFSET(GlRaster, r, nativeRasterOffset); + glActiveTexture(GL_TEXTURE0+n); + if(r){ + if(glr->id == 0) + this->upload(); + glBindTexture(GL_TEXTURE_2D, glr->id); + }else + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + +} +#endif + +} +} diff --git a/vendor/librw/src/hanim.cpp b/vendor/librw/src/hanim.cpp new file mode 100644 index 0000000..6bc7c5e --- /dev/null +++ b/vendor/librw/src/hanim.cpp @@ -0,0 +1,414 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" +#include "ps2/rwps2.h" +#include "ps2/rwps2plg.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwwdgl.h" +#include "gl/rwgl3.h" + +#define PLUGIN_ID ID_HANIM + +namespace rw { + +int32 hAnimOffset; +bool32 hAnimDoStream = 1; + +HAnimHierarchy* +HAnimHierarchy::create(int32 numNodes, int32 *nodeFlags, int32 *nodeIDs, + int32 flags, int32 maxKeySize) +{ + HAnimHierarchy *hier = (HAnimHierarchy*)rwMalloc(sizeof(*hier), MEMDUR_EVENT | ID_HANIM); + if(hier == nil){ + RWERROR((ERR_ALLOC, sizeof(*hier))); + return nil; + } + hier->interpolator = AnimInterpolator::create(numNodes, maxKeySize); + + hier->numNodes = numNodes; + hier->flags = flags; + hier->parentFrame = nil; + hier->parentHierarchy = hier; + if(hier->flags & NOMATRICES){ + hier->matrices = nil; + hier->matricesUnaligned = nil; + }else{ + hier->matricesUnaligned = rwNew(hier->numNodes*64 + 0xF, MEMDUR_EVENT | ID_HANIM); + hier->matrices = + (Matrix*)(((uintptr)hier->matricesUnaligned + 0xF) & ~0xF); + } + hier->nodeInfo = rwNewT(HAnimNodeInfo, hier->numNodes, MEMDUR_EVENT | ID_HANIM); + for(int32 i = 0; i < hier->numNodes; i++){ + if(nodeIDs) + hier->nodeInfo[i].id = nodeIDs[i]; + else + hier->nodeInfo[i].id = 0; + hier->nodeInfo[i].index = i; + if(nodeFlags) + hier->nodeInfo[i].flags = nodeFlags[i]; + else + hier->nodeInfo[i].flags = 0; + hier->nodeInfo[i].frame = nil; + } + return hier; +} + +void +HAnimHierarchy::destroy(void) +{ + this->interpolator->destroy(); + rwFree(this->matricesUnaligned); + rwFree(this->nodeInfo); + rwFree(this); +} + +static Frame* +findById(Frame *f, int32 id) +{ + if(f == nil) return nil; + HAnimData *hanim = HAnimData::get(f); + if(hanim->id >= 0 && hanim->id == id) return f; + Frame *ff = findById(f->next, id); + if(ff) return ff; + return findById(f->child, id); +} + +static Frame* +findUnattachedById(HAnimHierarchy *hier, Frame *f, int32 id) +{ + if(f == nil) return nil; + HAnimData *hanim = HAnimData::get(f); + if(hanim->id >= 0 && hanim->id == id && hier->getIndex(f) == -1) return f; + Frame *ff = findUnattachedById(hier, f->next, id); + if(ff) return ff; + return findUnattachedById(hier, f->child, id); +} + +void +HAnimHierarchy::attachByIndex(int32 idx) +{ + int32 id = this->nodeInfo[idx].id; +// Frame *f = findById(this->parentFrame, id); + Frame *f = findUnattachedById(this, this->parentFrame, id); + if(f) + this->nodeInfo[idx].frame = f; +} + +void +HAnimHierarchy::attach(void) +{ + for(int32 i = 0; i < this->numNodes; i++) + this->attachByIndex(i); +} + +int32 +HAnimHierarchy::getIndex(int32 id) +{ + for(int32 i = 0; i < this->numNodes; i++) + if(this->nodeInfo[i].id == id) + return i; + return -1; +} + +int32 +HAnimHierarchy::getIndex(Frame *f) +{ + for(int32 i = 0; i < this->numNodes; i++) + if(this->nodeInfo[i].frame == f) + return i; + return -1; +} + +HAnimHierarchy* +HAnimHierarchy::get(Frame *f) +{ + return HAnimData::get(f)->hierarchy; +} + +HAnimHierarchy* +HAnimHierarchy::find(Frame *f) +{ + if(f == nil) return nil; + HAnimHierarchy *hier = HAnimHierarchy::get(f); + if(hier) return hier; + hier = HAnimHierarchy::find(f->next); + if(hier) return hier; + return HAnimHierarchy::find(f->child); +} + +void +HAnimHierarchy::updateMatrices(void) +{ + // TODO: handle more (all!) cases + + Matrix rootMat, animMat; + Matrix *curMat, *parentMat; + Matrix **sp, *stack[64]; + Frame *frm, *parfrm; + int32 i; + AnimInterpolator *anim = this->interpolator; + + sp = stack; + curMat = this->matrices; + + frm = this->parentFrame; + if(frm && (parfrm = frm->getParent()) && !(this->flags&LOCALSPACEMATRICES)) + rootMat = *parfrm->getLTM(); + else + rootMat.setIdentity(); + parentMat = &rootMat; + *sp++ = parentMat; + HAnimNodeInfo *node = this->nodeInfo; + for(i = 0; i < this->numNodes; i++){ + anim->applyCB(&animMat, anim->getInterpFrame(i)); + + // TODO: here we could update local matrices + + Matrix::mult(curMat, &animMat, parentMat); + + // TODO: here we could update LTM + + if(node->flags & PUSH) + *sp++ = parentMat; + parentMat = curMat; + if(node->flags & POP) + parentMat = *--sp; + assert(sp >= stack); + assert(sp <= &stack[64]); + + node++; + curMat++; + } +} + +HAnimData* +HAnimData::get(Frame *f) +{ + return PLUGINOFFSET(HAnimData, f, hAnimOffset); +} + +static void* +createHAnim(void *object, int32 offset, int32) +{ + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + hanim->id = -1; + hanim->hierarchy = nil; + return object; +} + +static void* +destroyHAnim(void *object, int32 offset, int32) +{ + int i; + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + if(hanim->hierarchy){ + for(i = 0; i < hanim->hierarchy->numNodes; i++) + hanim->hierarchy->nodeInfo[i].frame = nil; + if(object == hanim->hierarchy->parentFrame) + hanim->hierarchy->destroy(); + } + hanim->id = -1; + hanim->hierarchy = nil; + return object; +} + +static void* +copyHAnim(void *dst, void *src, int32 offset, int32) +{ + int i; + HAnimData *dsthanim = PLUGINOFFSET(HAnimData, dst, offset); + HAnimData *srchanim = PLUGINOFFSET(HAnimData, src, offset); + HAnimHierarchy *srchier, *dsthier; + dsthanim->id = srchanim->id; + dsthanim->hierarchy = nil; + srchier = srchanim->hierarchy; + if(srchier && !(srchier->flags & HAnimHierarchy::SUBHIERARCHY)){ + dsthier = HAnimHierarchy::create(srchier->numNodes, nil, nil, srchier->flags, srchier->interpolator->maxInterpKeyFrameSize); + for(i = 0; i < dsthier->numNodes; i++){ + dsthier->nodeInfo[i].frame = nil; + dsthier->nodeInfo[i].flags = srchier->nodeInfo[i].flags; + dsthier->nodeInfo[i].index = srchier->nodeInfo[i].index; + dsthier->nodeInfo[i].id = srchier->nodeInfo[i].id; + } + dsthanim->hierarchy = dsthier; + dsthier->parentFrame = (Frame*)dst; + } + return dst; +} + +static Stream* +readHAnim(Stream *stream, int32, void *object, int32 offset, int32) +{ + int32 ver, numNodes; + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + ver = stream->readI32(); + assert(ver == 0x100); + hanim->id = stream->readI32(); + numNodes = stream->readI32(); + if(numNodes != 0){ + int32 flags = stream->readI32(); + int32 maxKeySize = stream->readI32(); + // Sizes are fucked for 64 bit pointers but + // AnimInterpolator::create() will take care of that + int32 *nodeFlags = rwNewT(int32, numNodes, + MEMDUR_FUNCTION | ID_HANIM); + int32 *nodeIDs = rwNewT(int32, numNodes, + MEMDUR_FUNCTION | ID_HANIM); + for(int32 i = 0; i < numNodes; i++){ + nodeIDs[i] = stream->readI32(); + stream->readI32(); // index...unused + nodeFlags[i] = stream->readI32(); + } + hanim->hierarchy = HAnimHierarchy::create(numNodes, + nodeFlags, nodeIDs, flags, maxKeySize); + hanim->hierarchy->parentFrame = (Frame*)object; + rwFree(nodeFlags); + rwFree(nodeIDs); + } + return stream; +} + +static Stream* +writeHAnim(Stream *stream, int32, void *object, int32 offset, int32) +{ + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + stream->writeI32(256); + stream->writeI32(hanim->id); + if(hanim->hierarchy == nil){ + stream->writeI32(0); + return stream; + } + HAnimHierarchy *hier = hanim->hierarchy; + stream->writeI32(hier->numNodes); + stream->writeI32(hier->flags); + stream->writeI32(hier->interpolator->maxInterpKeyFrameSize); + for(int32 i = 0; i < hier->numNodes; i++){ + stream->writeI32(hier->nodeInfo[i].id); + stream->writeI32(hier->nodeInfo[i].index); + stream->writeI32(hier->nodeInfo[i].flags); + } + return stream; +} + +static int32 +getSizeHAnim(void *object, int32 offset, int32) +{ + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + if(!hAnimDoStream || + (version >= 0x35000 && hanim->id == -1 && hanim->hierarchy == nil)) + return 0; + if(hanim->hierarchy) + return 12 + 8 + hanim->hierarchy->numNodes*12; + return 12; +} + +static void +hAnimFrameRead(Stream *stream, Animation *anim) +{ + HAnimKeyFrame *frames = (HAnimKeyFrame*)anim->keyframes; + for(int32 i = 0; i < anim->numFrames; i++){ + frames[i].time = stream->readF32(); + stream->read32(&frames[i].q, 4*4); + stream->read32(&frames[i].t, 3*4); + int32 prev = stream->readI32()/0x24; + frames[i].prev = &frames[prev]; + } +} + +static void +hAnimFrameWrite(Stream *stream, Animation *anim) +{ + HAnimKeyFrame *frames = (HAnimKeyFrame*)anim->keyframes; + for(int32 i = 0; i < anim->numFrames; i++){ + stream->writeF32(frames[i].time); + stream->write32(&frames[i].q, 4*4); + stream->write32(&frames[i].t, 3*4); + stream->writeI32((frames[i].prev - frames)*0x24); + } +} + +static uint32 +hAnimFrameGetSize(Animation *anim) +{ + return anim->numFrames*(4 + 4*4 + 3*4 + 4); +} + +//void hanimBlendCB(void *out, void *in1, void *in2, float32 a); +//void hanimAddCB(void *out, void *in1, void *in2); +//void hanimMulRecipCB(void *frame, void *start); + +static void +hanimApplyCB(void *result, void *frame) +{ + Matrix *m = (Matrix*)result; + HAnimInterpFrame *f = (HAnimInterpFrame*)frame; + m->rotate(f->q, COMBINEREPLACE); + m->pos = f->t; +} + +static void +hanimInterpCB(void *vout, void *vin1, void *vin2, float32 t, void*) +{ + HAnimInterpFrame *out = (HAnimInterpFrame*)vout; + HAnimKeyFrame *in1 = (HAnimKeyFrame*)vin1; + HAnimKeyFrame *in2 = (HAnimKeyFrame*)vin2; +assert(t >= in1->time && t <= in2->time); + float32 a = (t - in1->time)/(in2->time - in1->time); + out->t = lerp(in1->t, in2->t, a); + out->q = slerp(in1->q, in2->q, a); +} + +static void* +hanimOpen(void *object, int32 offset, int32 size) +{ + AnimInterpolatorInfo *info = rwNewT(AnimInterpolatorInfo, 1, MEMDUR_GLOBAL | ID_HANIM); + info->id = 1; + info->interpKeyFrameSize = sizeof(HAnimInterpFrame); + info->animKeyFrameSize = sizeof(HAnimKeyFrame); + info->customDataSize = 0; + info->applyCB = hanimApplyCB; + info->blendCB = nil; + info->interpCB = hanimInterpCB; + info->addCB = nil; + info->mulRecipCB = nil; + info->streamRead = hAnimFrameRead; + info->streamWrite = hAnimFrameWrite; + info->streamGetSize = hAnimFrameGetSize; + AnimInterpolatorInfo::registerInterp(info); + return object; +} + +static void* +hanimClose(void *object, int32 offset, int32 size) +{ + AnimInterpolatorInfo::unregisterInterp(AnimInterpolatorInfo::find(1)); + return object; +} + + +void +registerHAnimPlugin(void) +{ + Engine::registerPlugin(0, ID_HANIM, hanimOpen, hanimClose); + hAnimOffset = Frame::registerPlugin(sizeof(HAnimData), ID_HANIM, + createHAnim, + destroyHAnim, copyHAnim); + Frame::registerPluginStream(ID_HANIM, + readHAnim, + writeHAnim, + getSizeHAnim); +} + +} diff --git a/vendor/librw/src/image.cpp b/vendor/librw/src/image.cpp new file mode 100644 index 0000000..77adfda --- /dev/null +++ b/vendor/librw/src/image.cpp @@ -0,0 +1,1324 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "ps2/rwps2.h" +#include "d3d/rwd3d.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" + +#define PLUGIN_ID ID_IMAGE + +namespace rw { + +int32 Image::numAllocated; + +struct FileAssociation +{ + char *extension; + Image *(*read)(const char *afilename); + void (*write)(Image *image, const char *filename); +}; + +struct ImageGlobals +{ + char *searchPaths; + int numSearchPaths; + FileAssociation fileFormats[10]; + int numFileFormats; +}; +int32 imageModuleOffset; + +#define IMAGEGLOBAL(v) (PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset)->v) + +// Image formats are as follows: +// 32 bit has 4 bytes: 8888 RGBA +// 24 bit has 3 bytes: 888 RGB +// 16 bit has 2 bytes: 1555 ARGB stored in platform native order (TODO?) +// palettes always have 4 bytes: r, g, b, a +// 8 bit has 1 byte: x +// 4 bit has 1 byte per two pixels: 0xLR, where L and R are the left and right pixel resp. + +Image* +Image::create(int32 width, int32 height, int32 depth) +{ + Image *img = (Image*)rwMalloc(sizeof(Image), MEMDUR_EVENT | ID_IMAGE); + if(img == nil){ + RWERROR((ERR_ALLOC, sizeof(Image))); + return nil; + } + numAllocated++; + img->flags = 0; + img->width = width; + img->height = height; + img->depth = depth; + img->bpp = depth < 8 ? 1 : depth/8; + img->stride = 0; + img->pixels = nil; + img->palette = nil; + return img; +} + +void +Image::destroy(void) +{ + this->free(); + rwFree(this); + numAllocated--; +} + +void +Image::allocate(void) +{ + if(this->pixels == nil){ + this->stride = this->width*this->bpp; + this->pixels = rwNewT(uint8, this->stride*this->height, MEMDUR_EVENT | ID_IMAGE); + this->flags |= 1; + } + if(this->palette == nil){ + if(this->depth == 4 || this->depth == 8) + this->palette = rwNewT(uint8, (1 << this->depth)*4, MEMDUR_EVENT | ID_IMAGE); + this->flags |= 2; + } +} + +void +Image::free(void) +{ + if(this->flags&1){ + rwFree(this->pixels); + this->pixels = nil; + } + if(this->flags&2){ + rwFree(this->palette); + this->palette = nil; + } + this->flags = 0; +} + +void +Image::setPixels(uint8 *pixels) +{ + this->pixels = pixels; + this->flags |= 1; +} + +void +decompressDXT1(uint8 *adst, int32 w, int32 h, uint8 *src) +{ + /* j loops through old texels + * x and y loop through new texels */ + int32 x = 0, y = 0; + uint32 c[4][4]; + uint8 idx[16]; + uint8 (*dst)[4] = (uint8(*)[4])adst; + for(int32 j = 0; j < w*h/2; j += 8){ + /* calculate colors */ + uint32 col0 = *((uint16*)&src[j+0]); + uint32 col1 = *((uint16*)&src[j+2]); + c[0][0] = ((col0>>11) & 0x1F)*0xFF/0x1F; + c[0][1] = ((col0>> 5) & 0x3F)*0xFF/0x3F; + c[0][2] = ( col0 & 0x1F)*0xFF/0x1F; + c[0][3] = 0xFF; + + c[1][0] = ((col1>>11) & 0x1F)*0xFF/0x1F; + c[1][1] = ((col1>> 5) & 0x3F)*0xFF/0x3F; + c[1][2] = ( col1 & 0x1F)*0xFF/0x1F; + c[1][3] = 0xFF; + if(col0 > col1){ + c[2][0] = (2*c[0][0] + 1*c[1][0])/3; + c[2][1] = (2*c[0][1] + 1*c[1][1])/3; + c[2][2] = (2*c[0][2] + 1*c[1][2])/3; + c[2][3] = 0xFF; + + c[3][0] = (1*c[0][0] + 2*c[1][0])/3; + c[3][1] = (1*c[0][1] + 2*c[1][1])/3; + c[3][2] = (1*c[0][2] + 2*c[1][2])/3; + c[3][3] = 0xFF; + }else{ + c[2][0] = (c[0][0] + c[1][0])/2; + c[2][1] = (c[0][1] + c[1][1])/2; + c[2][2] = (c[0][2] + c[1][2])/2; + c[2][3] = 0xFF; + + c[3][0] = 0x00; + c[3][1] = 0x00; + c[3][2] = 0x00; + c[3][3] = 0x00; + } + + /* make index list */ + uint32 indices = *((uint32*)&src[j+4]); + for(int32 k = 0; k < 16; k++){ + idx[k] = indices & 0x3; + indices >>= 2; + } + + /* write bytes */ + for(uint32 l = 0; l < 4; l++) + for(uint32 k = 0; k < 4; k++){ + dst[(y+l)*w + x+k][0] = c[idx[l*4+k]][0]; + dst[(y+l)*w + x+k][1] = c[idx[l*4+k]][1]; + dst[(y+l)*w + x+k][2] = c[idx[l*4+k]][2]; + dst[(y+l)*w + x+k][3] = c[idx[l*4+k]][3]; + } + x += 4; + if(x >= w){ + y += 4; + x = 0; + } + } +} + +void +decompressDXT3(uint8 *adst, int32 w, int32 h, uint8 *src) +{ + /* j loops through old texels + * x and y loop through new texels */ + int32 x = 0, y = 0; + uint32 c[4][4]; + uint8 idx[16]; + uint8 a[16]; + uint8 (*dst)[4] = (uint8(*)[4])adst; + for(int32 j = 0; j < w*h; j += 16){ + /* calculate colors */ + uint32 col0 = *((uint16*)&src[j+8]); + uint32 col1 = *((uint16*)&src[j+10]); + c[0][0] = ((col0>>11) & 0x1F)*0xFF/0x1F; + c[0][1] = ((col0>> 5) & 0x3F)*0xFF/0x3F; + c[0][2] = ( col0 & 0x1F)*0xFF/0x1F; + + c[1][0] = ((col1>>11) & 0x1F)*0xFF/0x1F; + c[1][1] = ((col1>> 5) & 0x3F)*0xFF/0x3F; + c[1][2] = ( col1 & 0x1F)*0xFF/0x1F; + + c[2][0] = (2*c[0][0] + 1*c[1][0])/3; + c[2][1] = (2*c[0][1] + 1*c[1][1])/3; + c[2][2] = (2*c[0][2] + 1*c[1][2])/3; + + c[3][0] = (1*c[0][0] + 2*c[1][0])/3; + c[3][1] = (1*c[0][1] + 2*c[1][1])/3; + c[3][2] = (1*c[0][2] + 2*c[1][2])/3; + + /* make index list */ + uint32 indices = *((uint32*)&src[j+12]); + for(int32 k = 0; k < 16; k++){ + idx[k] = indices & 0x3; + indices >>= 2; + } + uint64 alphas = *((uint64*)&src[j+0]); + for(int32 k = 0; k < 16; k++){ + a[k] = (alphas & 0xF)*17; + alphas >>= 4; + } + + /* write bytes */ + for(uint32 l = 0; l < 4; l++) + for(uint32 k = 0; k < 4; k++){ + dst[(y+l)*w + x+k][0] = c[idx[l*4+k]][0]; + dst[(y+l)*w + x+k][1] = c[idx[l*4+k]][1]; + dst[(y+l)*w + x+k][2] = c[idx[l*4+k]][2]; + dst[(y+l)*w + x+k][3] = a[l*4+k]; + } + x += 4; + if(x >= w){ + y += 4; + x = 0; + } + } +} + +void +decompressDXT5(uint8 *adst, int32 w, int32 h, uint8 *src) +{ + /* j loops through old texels + * x and y loop through new texels */ + int32 x = 0, y = 0; + uint32 c[4][4]; + uint32 a[8]; + uint8 idx[16]; + uint8 aidx[16]; + uint8 (*dst)[4] = (uint8(*)[4])adst; + for(int32 j = 0; j < w*h; j += 16){ + /* calculate colors */ + uint32 col0 = *((uint16*)&src[j+8]); + uint32 col1 = *((uint16*)&src[j+10]); + c[0][0] = ((col0>>11) & 0x1F)*0xFF/0x1F; + c[0][1] = ((col0>> 5) & 0x3F)*0xFF/0x3F; + c[0][2] = ( col0 & 0x1F)*0xFF/0x1F; + + c[1][0] = ((col1>>11) & 0x1F)*0xFF/0x1F; + c[1][1] = ((col1>> 5) & 0x3F)*0xFF/0x3F; + c[1][2] = ( col1 & 0x1F)*0xFF/0x1F; + if(col0 > col1){ + c[2][0] = (2*c[0][0] + 1*c[1][0])/3; + c[2][1] = (2*c[0][1] + 1*c[1][1])/3; + c[2][2] = (2*c[0][2] + 1*c[1][2])/3; + + c[3][0] = (1*c[0][0] + 2*c[1][0])/3; + c[3][1] = (1*c[0][1] + 2*c[1][1])/3; + c[3][2] = (1*c[0][2] + 2*c[1][2])/3; + }else{ + c[2][0] = (c[0][0] + c[1][0])/2; + c[2][1] = (c[0][1] + c[1][1])/2; + c[2][2] = (c[0][2] + c[1][2])/2; + + c[3][0] = 0x00; + c[3][1] = 0x00; + c[3][2] = 0x00; + } + + a[0] = src[j+0]; + a[1] = src[j+1]; + if(a[0] > a[1]){ + a[2] = (6*a[0] + 1*a[1])/7; + a[3] = (5*a[0] + 2*a[1])/7; + a[4] = (4*a[0] + 3*a[1])/7; + a[5] = (3*a[0] + 4*a[1])/7; + a[6] = (2*a[0] + 5*a[1])/7; + a[7] = (1*a[0] + 6*a[1])/7; + }else{ + a[2] = (4*a[0] + 1*a[1])/5; + a[3] = (3*a[0] + 2*a[1])/5; + a[4] = (2*a[0] + 3*a[1])/5; + a[5] = (1*a[0] + 4*a[1])/5; + a[6] = 0; + a[7] = 0xFF; + } + + /* make index list */ + uint32 indices = *((uint32*)&src[j+12]); + for(int32 k = 0; k < 16; k++){ + idx[k] = indices & 0x3; + indices >>= 2; + } + // only 6 indices + uint64 alphas = *((uint64*)&src[j+2]); + for(int32 k = 0; k < 16; k++){ + aidx[k] = alphas & 0x7; + alphas >>= 3; + } + + /* write bytes */ + for(uint32 l = 0; l < 4; l++) + for(uint32 k = 0; k < 4; k++){ + dst[(y+l)*w + x+k][0] = c[idx[l*4+k]][0]; + dst[(y+l)*w + x+k][1] = c[idx[l*4+k]][1]; + dst[(y+l)*w + x+k][2] = c[idx[l*4+k]][2]; + dst[(y+l)*w + x+k][3] = a[aidx[l*4+k]]; + } + x += 4; + if(x >= w){ + y += 4; + x = 0; + } + } +} + +// not strictly image but related + +// flip a DXT 2-bit block +static void +flipBlock(uint8 *dst, uint8 *src) +{ + // color + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + // bits + dst[4] = src[7]; + dst[5] = src[6]; + dst[6] = src[5]; + dst[7] = src[4]; +} + +// flip top 2 rows of a DXT 2-bit block +static void +flipBlock_half(uint8 *dst, uint8 *src) +{ + // color + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + // bits + dst[4] = src[5]; + dst[5] = src[4]; + dst[6] = src[6]; + dst[7] = src[7]; +} + +// flip a DXT3 4-bit alpha block +static void +flipAlphaBlock3(uint8 *dst, uint8 *src) +{ + dst[6] = src[0]; + dst[7] = src[1]; + dst[4] = src[2]; + dst[5] = src[3]; + dst[2] = src[4]; + dst[3] = src[5]; + dst[0] = src[6]; + dst[1] = src[7]; +} + +// flip top 2 rows of a DXT3 4-bit alpha block +static void +flipAlphaBlock3_half(uint8 *dst, uint8 *src) +{ + dst[0] = src[2]; + dst[1] = src[3]; + dst[2] = src[0]; + dst[3] = src[1]; + dst[4] = src[4]; + dst[5] = src[5]; + dst[6] = src[6]; + dst[7] = src[7]; +} + +// flip a DXT5 3-bit alpha block +static void +flipAlphaBlock5(uint8 *dst, uint8 *src) +{ + // color + dst[0] = src[0]; + dst[1] = src[1]; + // bits + uint64 bits = *(uint64*)&src[2]; + uint64 flipbits = 0; + for(int i = 0; i < 4; i++){ + flipbits <<= 12; + flipbits |= bits & 0xFFF; + bits >>= 12; + } + memcpy(dst+2, &flipbits, 6); +} + +// flip top 2 rows of a DXT5 3-bit alpha block +static void +flipAlphaBlock5_half(uint8 *dst, uint8 *src) +{ + // color + dst[0] = src[0]; + dst[1] = src[1]; + // bits + uint64 bits = *(uint64*)&src[2]; + uint64 flipbits = bits & 0xFFFFFF000000; + flipbits |= (bits>>12) & 0xFFF; + flipbits |= (bits<<12) & 0xFFF000; + memcpy(dst+2, &flipbits, 6); +} + +void +flipDXT1(uint8 *dst, uint8 *src, uint32 width, uint32 height) +{ + int x, y; + int bw = (width+3)/4; + int bh = (height+3)/4; + if(height < 4){ + // used pixels are always at the top + // so don't swap the full 4 rows + if(height == 2){ + uint8 *s = src; + uint8 *d = dst; + for(x = 0; x < bw; x++){ + flipBlock_half(dst, src); + s += 8; + d += 8; + } + }else + memcpy(dst, src, 8*bw); + return; + } + dst += 8*bw*bh; + for(y = 0; y < bh; y++){ + dst -= 8*bw; + uint8 *s = src; + uint8 *d = dst; + for(x = 0; x < bw; x++){ + flipBlock(d, s); + s += 8; + d += 8; + } + src += 8*bw; + } +} + +void +flipDXT3(uint8 *dst, uint8 *src, uint32 width, uint32 height) +{ + int x, y; + int bw = (width+3)/4; + int bh = (height+3)/4; + if(height < 4){ + // used pixels are always at the top + // so don't swap the full 4 rows + if(height == 2){ + uint8 *s = src; + uint8 *d = dst; + for(x = 0; x < bw; x++){ + flipAlphaBlock3_half(d, s); + flipBlock_half(d+8, s+8); + s += 16; + d += 16; + } + }else + memcpy(dst, src, 16*bw); + return; + } + dst += 16*bw*bh; + for(y = 0; y < bh; y++){ + dst -= 16*bw; + uint8 *s = src; + uint8 *d = dst; + for(x = 0; x < bw; x++){ + flipAlphaBlock3(d, s); + flipBlock(d+8, s+8); + s += 16; + d += 16; + } + src += 16*bw; + } +} + +void +flipDXT5(uint8 *dst, uint8 *src, uint32 width, uint32 height) +{ + int x, y; + int bw = (width+3)/4; + int bh = (height+3)/4; + if(height < 4){ + // used pixels are always at the top + // so don't swap the full 4 rows + if(height == 2){ + uint8 *s = src; + uint8 *d = dst; + for(x = 0; x < bw; x++){ + flipAlphaBlock5_half(d, s); + flipBlock_half(d+8, s+8); + s += 16; + d += 16; + } + }else + memcpy(dst, src, 16*bw); + return; + } + dst += 16*bw*bh; + for(y = 0; y < bh; y++){ + dst -= 16*bw; + uint8 *s = src; + uint8 *d = dst; + for(x = 0; x < bw; x++){ + flipAlphaBlock5(d, s); + flipBlock(d+8, s+8); + s += 16; + d += 16; + } + src += 16*bw; + } +} + +void +flipDXT(int32 type, uint8 *dst, uint8 *src, uint32 width, uint32 height) +{ + switch(type){ + case 1: + flipDXT1(dst, src, width, height); + break; + case 3: + flipDXT3(dst, src, width, height); + break; + case 5: + flipDXT5(dst, src, width, height); + break; + } +} + +void +Image::setPixelsDXT(int32 type, uint8 *pixels) +{ + switch(type){ + case 1: + decompressDXT1(this->pixels, this->width, this->height, pixels); + break; + case 3: + decompressDXT3(this->pixels, this->width, this->height, pixels); + break; + case 5: + decompressDXT5(this->pixels, this->width, this->height, pixels); + break; + } +} + +void +Image::setPalette(uint8 *palette) +{ + this->palette = palette; + this->flags |= 2; +} + +void +Image::compressPalette(void) +{ + if(this->depth != 8) + return; + uint8 *pixels = this->pixels; + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < this->width; x++){ + if(*line > 0xF) return; + line += this->bpp; + } + pixels += this->stride; + } + this->depth = 4; +} + +bool32 +Image::hasAlpha(void) +{ + uint8 ret = 0xFF; + uint8 *pixels = this->pixels; + if(this->depth == 32){ + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < this->width; x++){ + ret &= line[3]; + line += this->bpp; + } + pixels += this->stride; + } + }else if(this->depth == 24){ + return 0; + }else if(this->depth == 16){ + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < this->width; x++){ + ret &= line[1] & 0x80; + line += this->bpp; + } + pixels += this->stride; + } + return ret != 0x80; + }else if(this->depth <= 8){ + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < this->width; x++){ + ret &= this->palette[*line*4+3]; + line += this->bpp; + } + pixels += this->stride; + } + } + return ret != 0xFF; +} + +void +Image::convertTo32(void) +{ + assert(this->pixels); + uint8 *pixels = this->pixels; + int32 newstride = this->width*4; + uint8 *newpixels; + + void (*fun)(uint8 *out, uint8 *in) = nil; + switch(this->depth){ + case 4: + case 8: + assert(this->palette); + this->unpalettize(true); + return; + case 16: + fun = conv_RGBA8888_from_ARGB1555; + break; + case 24: + fun = conv_RGBA8888_from_RGB888; + break; + default: + return; + } + + newpixels = rwNewT(uint8, newstride*this->height, MEMDUR_EVENT | ID_IMAGE); + uint8 *pixels32 = newpixels; + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + uint8 *newline = newpixels; + for(int x = 0; x < this->width; x++){ + fun(newline, line); + line += this->bpp; + newline += 4; + } + pixels += this->stride; + newpixels += newstride; + } + + this->free(); + this->depth = 32; + this->bpp = 4; + this->stride = newstride; + this->pixels = nil; + this->palette = nil; + this->setPixels(pixels32); +} + +void +Image::palettize(int32 depth) +{ + RGBA colors[256]; + ColorQuant quant; + uint8 *newpixels; + uint32 newstride; + + quant.init(); + quant.addImage(this); + assert(depth <= 8); + quant.makePalette(1<width; + newpixels = rwNewT(uint8, newstride*this->height, MEMDUR_EVENT | ID_IMAGE); + // TODO: maybe do floyd-steinberg dithering? + quant.matchImage(newpixels, newstride, this); + + this->free(); + this->depth = depth; + this->bpp = depth < 8 ? 1 : depth/8; + this->stride = newstride; + this->pixels = nil; + this->palette = nil; + this->setPixels(newpixels); + this->allocate(); + memcpy(this->palette, colors, 4*(1<depth > 8) + return; + assert(this->pixels); + assert(this->palette); + + int32 ndepth = (forceAlpha || this->hasAlpha()) ? 32 : 24; + int32 nstride = this->width*ndepth/8; + uint8 *npixels = rwNewT(uint8, nstride*this->height, MEMDUR_EVENT | ID_IMAGE); + + uint8 *line = this->pixels; + uint8 *nline = npixels; + uint8 *p, *np; + for(int32 y = 0; y < this->height; y++){ + p = line; + np = nline; + for(int32 x = 0; x < this->width; x++){ + np[0] = this->palette[*p*4+0]; + np[1] = this->palette[*p*4+1]; + np[2] = this->palette[*p*4+2]; + np += 3; + if(ndepth == 32) + *np++ = this->palette[*p*4+3]; + p++; + } + line += this->stride; + nline += nstride; + } + this->free(); + this->depth = ndepth; + this->bpp = ndepth < 8 ? 1 : ndepth/8; + this->stride = nstride; + this->setPixels(npixels); +} + +// Copy the biggest channel value to alpha +void +Image::makeMask(void) +{ + int32 maxcol; + switch(this->depth){ + case 4: + case 8: { + assert(this->palette); + int32 pallen = 1 << this->depth; + for(int32 i = 0; i < pallen; i++){ + maxcol = this->palette[i*4+0]; + if(this->palette[i*4+1] > maxcol) maxcol = this->palette[i*4+1]; + if(this->palette[i*4+2] > maxcol) maxcol = this->palette[i*4+2]; + this->palette[i*4+3] = maxcol; + } + break; + } + + case 16: + case 24: + this->convertTo32(); + // fallthrough + + case 32: { + assert(this->pixels); + uint8 *line = this->pixels; + uint8 *p; + for(int32 y = 0; y < this->height; y++){ + p = line; + for(int32 x = 0; x < this->width; x++){ + maxcol = p[0]; + if(p[1] > maxcol) maxcol = p[1]; + if(p[2] > maxcol) maxcol = p[2]; + p[3] = maxcol; + p += this->bpp; + } + line += this->stride; + } + break; + } + } +} + +void +Image::applyMask(Image *mask) +{ + if(this->width != mask->width || this->height != mask->height) + return; // TODO: set an error + // we could use alpha with 16 bits but what's the point? + if(mask->depth == 16 || mask->depth == 24) + return; + + this->convertTo32(); + assert(this->depth == 32); + + uint8 *line = this->pixels; + uint8 *mline = mask->pixels; + uint8 *p, *m; + for(int32 y = 0; y < this->height; y++){ + p = line; + m = mline; + for(int32 x = 0; x < this->width; x++){ + if(mask->depth == 32) + p[3] = m[3]; + else if(mask->depth <= 8) + p[3] = mask->palette[m[0]*4+3]; + p += this->bpp; + m += mask->bpp; + } + line += this->stride; + mline += mask->stride; + } +} + +void +Image::removeMask(void) +{ + if(this->depth <= 8){ + assert(this->palette); + int32 pallen = 4*(1 << this->depth); + for(int32 i = 0; i < pallen; i += 4) + this->palette[i+3] = 0xFF; + return; + } + if(this->depth == 24) + return; + assert(this->pixels); + uint8 *line = this->pixels; + uint8 *p; + for(int32 y = 0; y < this->height; y++){ + p = line; + for(int32 x = 0; x < this->width; x++){ + switch(this->depth){ + case 16: + p[1] |= 0x80; + p += 2; + break; + case 32: + p[3] = 0xFF; + p += 4; + break; + } + } + line += this->stride; + } +} + +Image* +Image::extractMask(void) +{ + Image *img = Image::create(this->width, this->height, 8); + img->allocate(); + + // use an 8bit palette to store all shades of grey + for(int32 i = 0; i < 256; i++){ + img->palette[i*4+0] = i; + img->palette[i*4+1] = i; + img->palette[i*4+2] = i; + img->palette[i*4+3] = 0xFF; + } + + // Then use the alpha value as palette index + uint8 *line = this->pixels; + uint8 *nline = img->pixels; + uint8 *p, *np; + for(int32 y = 0; y < this->height; y++){ + p = line; + np = nline; + for(int32 x = 0; x < this->width; x++){ + switch(this->depth){ + case 4: + case 8: + *np++ = this->palette[*p*4+3]; + p++; + break; + case 16: + *np++ = 0xFF*!!(p[1]&0x80); + p += 2; + break; + case 24: + *np++ = 0xFF; + p += 3; + break; + case 32: + *np++ = p[3]; + p += 4; + break; + } + } + line += this->stride; + nline += img->stride; + } + return img; +} + +void +Image::setSearchPath(const char *path) +{ + char *p, *end; + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + rwFree(g->searchPaths); + g->numSearchPaths = 0; + if(path) + g->searchPaths = p = rwStrdup(path, MEMDUR_EVENT); + else{ + g->searchPaths = nil; + return; + } + while(p && *p){ + end = strchr(p, ';'); + if(end) + *end++ = '\0'; + g->numSearchPaths++; + p = end; + } +} + +void +Image::printSearchPath(void) +{ + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + char *p = g->searchPaths; + for(int i = 0; i < g->numSearchPaths; i++){ + printf("%s\n", p); + p += strlen(p) + 1; + } +} + +char* +Image::getFilename(const char *name) +{ + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + FILE *f; + char *s, *p = g->searchPaths; + size_t len = strlen(name)+1; + if(g->numSearchPaths == 0){ + s = rwStrdup(name, MEMDUR_EVENT); + makePath(s); + f = fopen(s, "rb"); + if(f){ + fclose(f); + printf("found %s\n", s); + return s; + } + rwFree(s); + return nil; + }else + for(int i = 0; i < g->numSearchPaths; i++){ + s = (char*)rwMalloc(strlen(p)+len, MEMDUR_EVENT | ID_IMAGE); + if(s == nil){ + RWERROR((ERR_ALLOC, strlen(p)+len)); + return nil; + } + strcpy(s, p); + strcat(s, name); + makePath(s); + f = fopen(s, "r"); + if(f){ + fclose(f); + printf("found %s\n", name); + return s; + } + rwFree(s); + p += strlen(p) + 1; + } + return nil; +} + +Image* +Image::readMasked(const char *imageName, const char *maskName) +{ + Image *img, *mask; + + img = read(imageName); + if(img == nil) + return nil; + if(maskName && maskName[0]){ + mask = read(maskName); + if(mask == nil) + return img; + mask->makeMask(); + int32 origDepth = img->depth; + img->applyMask(mask); + mask->destroy(); + if(origDepth <= 8 && img->depth != origDepth) + img->palettize(origDepth); + } + return img; +} + +Image* +Image::read(const char *imageName) +{ + int i; + char *filename, *ext, *found; + Image *img; + + filename = rwNewT(char, strlen(imageName) + 20, MEMDUR_FUNCTION | ID_IMAGE); + strcpy(filename, imageName); + ext = filename + strlen(filename); + *ext++ = '.'; + // Try all supported extensions + for(i = 0; i < IMAGEGLOBAL(numFileFormats); i++){ + if(IMAGEGLOBAL(fileFormats)[i].read == nil) + continue; + strncpy(ext, IMAGEGLOBAL(fileFormats)[i].extension, 19); + found = getFilename(filename); + // Found a file + if(found){ + img = IMAGEGLOBAL(fileFormats)[i].read(found); + rwFree(found); + // It was a valid image of that format + if(img){ + rwFree(filename); + return img; + } + } + } + rwFree(filename); + return nil; +} + +bool32 +Image::registerFileFormat(const char *ext, fileRead read, fileWrite write) +{ + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + if(g->numFileFormats >= (int)nelem(g->fileFormats)) + return 0; + g->fileFormats[g->numFileFormats].extension = rwStrdup(ext, MEMDUR_EVENT); + g->fileFormats[g->numFileFormats].read = read; + g->fileFormats[g->numFileFormats].write = write; + g->numFileFormats++; + return 1; +} + +static void* +imageOpen(void *object, int32 offset, int32 size) +{ + imageModuleOffset = offset; + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + g->searchPaths = nil; + g->numSearchPaths = 0; + g->numFileFormats = 0; + return object; +} + +static void* +imageClose(void *object, int32 offset, int32 size) +{ + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + int i; + rwFree(g->searchPaths); + g->searchPaths = nil; + g->numSearchPaths = 0; + for(i = 0; i < g->numFileFormats; i++) + rwFree(g->fileFormats[i].extension); + g->numFileFormats = 0; + return object; +} + +void +Image::registerModule(void) +{ + Engine::registerPlugin(sizeof(ImageGlobals), ID_IMAGEMODULE, imageOpen, imageClose); +} + + + +/* + * Color Quantization + */ + +// An address for a single level is 4 bits. +// Since we have 8 bpp that is 32 bits to address any tree node. +// The lower bits address the higher level tree nodes. +// This is essentially a bit reverse and swizzle. +static uint32 +makeTreeAddr(RGBA color) +{ + int32 i; + uint32 addr = 0; + uint32 r = 1; + uint32 g = 2; + uint32 b = 4; + uint32 a = 8; + for(i = 0; i < 8; i++){ + uint32 mask = 0x80>>i; + if(color.red & mask) addr |= r; + if(color.green & mask) addr |= g; + if(color.blue & mask) addr |= b; + if(color.alpha & mask) addr |= a; + r <<= 4; + g <<= 4; + b <<= 4; + a <<= 4; + } + return addr; +} + +void +ColorQuant::Node::destroy(void) +{ + int i; + for(i = 0; i < 16; i++) + if(this->children[i]) + this->children[i]->destroy(); + if(this->link.next) + this->link.remove(); + rwFree(this); +} + +ColorQuant::Node* +ColorQuant::createNode(int32 level) +{ + int i; + ColorQuant::Node *node = rwNewT(ColorQuant::Node, 1, MEMDUR_EVENT | ID_IMAGE); + node->parent = nil; + for(i = 0; i < 16; i++) + node->children[i] = nil; + node->r = 0; + node->g = 0; + node->b = 0; + node->a = 0; + node->numPixels = 0; + node->link.init(); + + if(level == 0) + this->leaves.append(&node->link); + + return node; +} + +ColorQuant::Node* +ColorQuant::getNode(ColorQuant::Node *root, uint32 addr, int32 level) +{ + if(level == 0) + return root; + + uint32 a = addr & 0xF; + if(root->children[a] == nil){ + root->children[a] = this->createNode(level-1); + root->children[a]->parent = root; + } + + return this->getNode(root->children[a], addr>>4, level-1); +} + +ColorQuant::Node* +ColorQuant::findNode(ColorQuant::Node *root, uint32 addr, int32 level) +{ + if(level == 0) + return root; + + uint32 a = addr & 0xF; + if(root->children[a] == nil) + return root; + + return this->findNode(root->children[a], addr>>4, level-1); +} + +void +ColorQuant::reduceNode(Node *node) +{ + int i; + assert(node->numPixels == 0); + for(i = 0; i < 16; i++) + if(node->children[i]){ + node->r += node->children[i]->r; + node->g += node->children[i]->g; + node->b += node->children[i]->b; + node->a += node->children[i]->a; + node->numPixels += node->children[i]->numPixels; + node->children[i]->destroy(); + node->children[i] = nil; + } + assert(node->link.next == nil); + assert(node->link.prev == nil); + this->leaves.append(&node->link); +} + +void +ColorQuant::Node::addColor(RGBA color) +{ + this->r += color.red; + this->g += color.green; + this->b += color.blue; + this->a += color.alpha; + this->numPixels++; +} + +void +ColorQuant::init(void) +{ + this->leaves.init(); + this->root = this->createNode(QUANTDEPTH); +} + +void +ColorQuant::destroy(void) +{ + this->root->destroy(); +} + +void +ColorQuant::addColor(RGBA color) +{ + uint32 addr = makeTreeAddr(color); + ColorQuant::Node *node = this->getNode(root, addr, QUANTDEPTH); + node->addColor(color); +} + +uint8 +ColorQuant::findColor(RGBA color) +{ + uint32 addr = makeTreeAddr(color); + ColorQuant::Node *node = this->findNode(root, addr, QUANTDEPTH); + return node->numPixels; +} + +void +ColorQuant::addImage(Image *img) +{ + RGBA col; + uint8 rgba[4]; + uint8 *pixels = img->pixels; + for(int y = 0; y < img->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < img->width; x++){ + uint8 *p = line; + switch(img->depth){ + case 4: case 8: + conv_RGBA8888_from_RGBA8888(rgba, &img->palette[p[0]*4]); + break; + case 32: + conv_RGBA8888_from_RGBA8888(rgba, p); + break; + case 24: + conv_RGBA8888_from_RGB888(rgba, p); + break; + case 16: + conv_RGBA8888_from_ARGB1555(rgba, p); + break; + default: assert(0 && "invalid depth"); + } + col.red = rgba[0]; + col.green = rgba[1]; + col.blue = rgba[2]; + col.alpha = rgba[3]; + this->addColor(col); + line += img->bpp; + } + pixels += img->stride; + } +} + +void +ColorQuant::makePalette(int32 numColors, RGBA *colors) +{ + while(this->leaves.count() > numColors){ + Node *n = LLLinkGetData(this->leaves.link.next, Node, link); + this->reduceNode(n->parent); + } + + int i = 0; + FORLIST(lnk, this->leaves){ + Node *n = LLLinkGetData(lnk, Node, link); + n->r /= n->numPixels; + n->g /= n->numPixels; + n->b /= n->numPixels; + n->a /= n->numPixels; + colors[i].red = n->r; + colors[i].green = n->g; + colors[i].blue = n->b; + colors[i].alpha = n->a; + n->numPixels = i++; + } +} + +void +ColorQuant::matchImage(uint8 *dstPixels, uint32 dstStride, Image *img) +{ + RGBA col; + uint8 rgba[4]; + uint8 *pixels = img->pixels; + for(int y = 0; y < img->height; y++){ + uint8 *line = pixels; + uint8 *dline = dstPixels; + for(int x = 0; x < img->width; x++){ + uint8 *p = line; + uint8 *d = dline; + switch(img->depth){ + case 4: case 8: + conv_RGBA8888_from_RGBA8888(rgba, &img->palette[p[0]*4]); + break; + case 32: + conv_RGBA8888_from_RGBA8888(rgba, p); + break; + case 24: + conv_RGBA8888_from_RGB888(rgba, p); + break; + case 16: + conv_RGBA8888_from_ARGB1555(rgba, p); + break; + default: assert(0 && "invalid depth"); + } + + col.red = rgba[0]; + col.green = rgba[1]; + col.blue = rgba[2]; + col.alpha = rgba[3]; + *d = this->findColor(col); + + line += img->bpp; + dline++; + } + pixels += img->stride; + dstPixels += dstStride; + } +} + + + +} diff --git a/vendor/librw/src/light.cpp b/vendor/librw/src/light.cpp new file mode 100644 index 0000000..ca68cda --- /dev/null +++ b/vendor/librw/src/light.cpp @@ -0,0 +1,163 @@ +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_LIGHT + +namespace rw { + +int32 Light::numAllocated; + +PluginList Light::s_plglist(sizeof(Light)); + +static void +lightSync(ObjectWithFrame*) +{ +} + +static void +worldLightSync(ObjectWithFrame *obj) +{ + Light *light = (Light*)obj; + light->originalSync(obj); +} + +Light* +Light::create(int32 type) +{ + Light *light = (Light*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_LIGHT); + if(light == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + light->object.object.init(Light::ID, type); + light->object.syncCB = lightSync; + light->radius = 0.0f; + light->color.red = 1.0f; + light->color.green = 1.0f; + light->color.blue = 1.0f; + light->color.alpha = 1.0f; + light->minusCosAngle = 1.0f; + light->object.object.privateFlags = 1; + light->object.object.flags = LIGHTATOMICS | LIGHTWORLD; + light->inWorld.init(); + + // clump extension + light->clump = nil; + light->inClump.init(); + + // world extension + light->world = nil; + light->originalSync = light->object.syncCB; + light->object.syncCB = worldLightSync; + + s_plglist.construct(light); + return light; +} + +void +Light::destroy(void) +{ + s_plglist.destruct(this); + assert(this->clump == nil); + assert(this->world == nil); + this->setFrame(nil); + rwFree(this); + numAllocated--; +} + +void +Light::setAngle(float32 angle) +{ + this->minusCosAngle = -cosf(angle); +} + +float32 +Light::getAngle(void) +{ + return acosf(-this->minusCosAngle); +} + +void +Light::setColor(float32 r, float32 g, float32 b) +{ + this->color.red = r; + this->color.green = g; + this->color.blue = b; + this->object.object.privateFlags = r == g && r == b; +} + +struct LightChunkData +{ + float32 radius; + float32 red, green, blue; + float32 minusCosAngle; + uint32 type_flags; +}; + +Light* +Light::streamRead(Stream *stream) +{ + uint32 version; + LightChunkData buf; + + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(&buf, sizeof(LightChunkData)); + Light *light = Light::create(buf.type_flags>>16); + if(light == nil) + return nil; + light->radius = buf.radius; + light->setColor(buf.red, buf.green, buf.blue); + float32 a = buf.minusCosAngle; + if(version >= 0x30300) + light->minusCosAngle = a; + else + // tan -> -cos + light->minusCosAngle = -1.0f/sqrtf(a*a+1.0f); + light->object.object.flags = (uint8)buf.type_flags; + if(s_plglist.streamRead(stream, light)) + return light; + light->destroy(); + return nil; +} + +bool +Light::streamWrite(Stream *stream) +{ + LightChunkData buf; + writeChunkHeader(stream, ID_LIGHT, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, sizeof(LightChunkData)); + buf.radius = this->radius; + buf.red = this->color.red; + buf.green = this->color.green; + buf.blue = this->color.blue; + if(version >= 0x30300) + buf.minusCosAngle = this->minusCosAngle; + else + buf.minusCosAngle = tanf(acosf(-this->minusCosAngle)); + buf.type_flags = (uint32)this->object.object.flags | + (uint32)this->object.object.subType << 16; + stream->write32(&buf, sizeof(LightChunkData)); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Light::streamGetSize(void) +{ + return 12 + sizeof(LightChunkData) + 12 + s_plglist.streamGetSize(this); +} + +} diff --git a/vendor/librw/src/lodepng/lodepng.cpp b/vendor/librw/src/lodepng/lodepng.cpp new file mode 100644 index 0000000..ee8cf33 --- /dev/null +++ b/vendor/librw/src/lodepng/lodepng.cpp @@ -0,0 +1,6410 @@ +/* +LodePNG version 20200306 + +Copyright (c) 2005-2020 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#ifdef LODEPNG_COMPILE_DISK +#include /* LONG_MAX */ +#include /* file handling */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +#include /* allocations */ +#endif /* LODEPNG_COMPILE_ALLOCATORS */ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20200306"; + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) { +#ifdef LODEPNG_MAX_ALLOC + if(size > LODEPNG_MAX_ALLOC) return 0; +#endif + return malloc(size); +} + +/* NOTE: when realloc returns NULL, it leaves the original memory untouched */ +static void* lodepng_realloc(void* ptr, size_t new_size) { +#ifdef LODEPNG_MAX_ALLOC + if(new_size > LODEPNG_MAX_ALLOC) return 0; +#endif + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) { + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +/* TODO: support giving additional void* payload to the custom allocators */ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* convince the compiler to inline a function, for use when this measurably improves performance */ +/* inline is not available in C90, but use it when supported by the compiler */ +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) +#define LODEPNG_INLINE inline +#else +#define LODEPNG_INLINE /* not available */ +#endif + +/* restrict is not available in C90, but use it when supported by the compiler */ +#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ + (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ + (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) +#define LODEPNG_RESTRICT __restrict +#else +#define LODEPNG_RESTRICT /* not available */ +#endif + +/* Replacements for C library functions such as memcpy and strlen, to support platforms +where a full C library is not available. The compiler can recognize them and compile +to something as fast. */ + +static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, + const void* LODEPNG_RESTRICT src, size_t size) { + size_t i; + for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; +} + +static void lodepng_memset(void* LODEPNG_RESTRICT dst, + int value, size_t num) { + size_t i; + for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value; +} + +/* does not check memory out of bounds, do not use on untrusted data */ +static size_t lodepng_strlen(const char* a) { + const char* orig = a; + /* avoid warning about unused function in case of disabled COMPILE... macros */ + (void)(&lodepng_strlen); + while(*a) a++; + return (size_t)(a - orig); +} + +#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ + +#ifdef LODEPNG_COMPILE_DECODER +/* Safely check if multiplying two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) { + *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ + return (a != 0 && *result / a != b); +} + +#ifdef LODEPNG_COMPILE_ZLIB +/* Safely check if a + b > c, even if overflow could happen. */ +static int lodepng_gtofl(size_t a, size_t b, size_t c) { + size_t d; + if(lodepng_addofl(a, b, &d)) return 1; + return d > c; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code){\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code){\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call){\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code){\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*dynamic vector of unsigned ints*/ +typedef struct uivector { + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) { + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) { + size_t allocsize = size * sizeof(unsigned); + if(allocsize > p->allocsize) { + size_t newsize = allocsize + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static void uivector_init(uivector* p) { + p->data = NULL; + p->size = p->allocsize = 0; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) { + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector { + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) { + if(size > p->allocsize) { + size_t newsize = size + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static ucvector ucvector_init(unsigned char* buffer, size_t size) { + ucvector v; + v.data = buffer; + v.allocsize = v.size = size; + return v; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +/*free string pointer and set it to NULL*/ +static void string_cleanup(char** out) { + lodepng_free(*out); + *out = NULL; +} + +static char* alloc_string_sized(const char* in, size_t insize) { + char* out = (char*)lodepng_malloc(insize + 1); + if(out) { + lodepng_memcpy(out, in, insize); + out[insize] = 0; + } + return out; +} + +/* dynamically allocates a new string with a copy of the null terminated input text */ +static char* alloc_string(const char* in) { + return alloc_string_sized(in, lodepng_strlen(in)); +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) +static unsigned lodepng_read32bitInt(const unsigned char* buffer) { + return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | + ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); +} +#endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) { + FILE* file; + long size; + file = fopen(filename, "rb"); + if(!file) return -1; + + if(fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return -1; + } + + size = ftell(file); + /* It may give LONG_MAX as directory size, this is invalid for us. */ + if(size == LONG_MAX) size = -1; + + fclose(file); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { + FILE* file; + size_t readsize; + file = fopen(filename, "rb"); + if(!file) return 78; + + readsize = fread(out, 1, size, file); + fclose(file); + + if(readsize != size) return 78; + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { + long size = lodepng_filesize(filename); + if(size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite(buffer, 1, buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct { + ucvector* data; + unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ +} LodePNGBitWriter; + +static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { + writer->data = data; + writer->bp = 0; +} + +/*TODO: this ignores potential out of memory errors*/ +#define WRITEBIT(writer, bit){\ + /* append new byte */\ + if(((writer->bp) & 7u) == 0) {\ + if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ + writer->data->data[writer->data->size - 1] = 0;\ + }\ + (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ + ++writer->bp;\ +} + +/* LSB of value is written first, and LSB of bytes is used first */ +static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ + WRITEBIT(writer, value); + } else { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + size_t i; + for(i = 0; i != nbits; ++i) { + WRITEBIT(writer, (unsigned char)((value >> i) & 1)); + } + } +} + +/* This one is to use for adding huffman symbol, the value bits are written MSB first */ +static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + size_t i; + for(i = 0; i != nbits; ++i) { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); + } +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +typedef struct { + const unsigned char* data; + size_t size; /*size of data in bytes*/ + size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ + size_t bp; + unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ +} LodePNGBitReader; + +/* data size argument is in bytes. Returns error if size too large causing overflow */ +static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { + size_t temp; + reader->data = data; + reader->size = size; + /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ + if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; + /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and + trying to ensure 32 more bits*/ + if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; + reader->bp = 0; + reader->buffer = 0; + return 0; /*ok*/ +} + +/* +ensureBits functions: +Ensures the reader can at least read nbits bits in one or more readBits calls, +safely even if not enough bits are available. +Returns 1 if there are enough bits available, 0 if not. +*/ + +/*See ensureBits documentation above. This one ensures exactly 1 bit */ +/*static unsigned ensureBits1(LodePNGBitReader* reader) { + if(reader->bp >= reader->bitsize) return 0; + reader->buffer = (unsigned)reader->data[reader->bp >> 3u] >> (reader->bp & 7u); + return 1; +}*/ + +/*See ensureBits documentation above. This one ensures up to 9 bits */ +static unsigned ensureBits9(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 1u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 17 bits */ +static unsigned ensureBits17(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 2u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 25 bits */ +static LODEPNG_INLINE unsigned ensureBits25(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 3u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 32 bits */ +static LODEPNG_INLINE unsigned ensureBits32(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 4u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ +static unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { + /* The shift allows nbits to be only up to 31. */ + return reader->buffer & ((1u << nbits) - 1u); +} + +/* Must have enough bits available with ensureBits */ +static void advanceBits(LodePNGBitReader* reader, size_t nbits) { + reader->buffer >>= nbits; + reader->bp += nbits; +} + +/* Must have enough bits available with ensureBits */ +static unsigned readBits(LodePNGBitReader* reader, size_t nbits) { + unsigned result = peekBits(reader, nbits); + advanceBits(reader, nbits); + return result; +} + +/* Public for testing only. steps and result must have numsteps values. */ +unsigned lode_png_test_bitreader(const unsigned char* data, size_t size, + size_t numsteps, const size_t* steps, unsigned* result) { + size_t i; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, data, size); + if(error) return 0; + for(i = 0; i < numsteps; i++) { + size_t step = steps[i]; + unsigned ok; + if(step > 25) ok = ensureBits32(&reader, step); + else if(step > 17) ok = ensureBits25(&reader, step); + else if(step > 9) ok = ensureBits17(&reader, step); + else ok = ensureBits9(&reader, step); + if(!ok) return 0; + result[i] = readBits(&reader, step); + } + return 1; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static unsigned reverseBits(unsigned bits, unsigned num) { + /*TODO: implement faster lookup table based version when needed*/ + unsigned i, result = 0; + for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; + return result; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman +tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree { + unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ + unsigned* lengths; /*the lengths of the huffman codes*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ + /* for reading only */ + unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ + unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ +} HuffmanTree; + +static void HuffmanTree_init(HuffmanTree* tree) { + tree->codes = 0; + tree->lengths = 0; + tree->table_len = 0; + tree->table_value = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) { + lodepng_free(tree->codes); + lodepng_free(tree->lengths); + lodepng_free(tree->table_len); + lodepng_free(tree->table_value); +} + +/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ +/* values 8u and 9u work the fastest */ +#define FIRSTBITS 9u + +/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, +which is possible in case of only 0 or 1 present symbols. */ +#define INVALIDSYMBOL 65535u + +/* make table for huffman decoding */ +static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { + static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ + static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; + size_t i, numpresent, pointer, size; /*total table size*/ + unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); + if(!maxlens) return 83; /*alloc fail*/ + + /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ + lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); + for(i = 0; i < tree->numcodes; i++) { + unsigned symbol = tree->codes[i]; + unsigned l = tree->lengths[i]; + unsigned index; + if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ + /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ + index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); + maxlens[index] = LODEPNG_MAX(maxlens[index], l); + } + /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ + size = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l > FIRSTBITS) size += (1u << (l - FIRSTBITS)); + } + tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); + tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); + if(!tree->table_len || !tree->table_value) { + lodepng_free(maxlens); + /* freeing tree->table values is done at a higher scope */ + return 83; /*alloc fail*/ + } + /*initialize with an invalid length to indicate unused entries*/ + for(i = 0; i < size; ++i) tree->table_len[i] = 16; + + /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ + pointer = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l <= FIRSTBITS) continue; + tree->table_len[i] = l; + tree->table_value[i] = pointer; + pointer += (1u << (l - FIRSTBITS)); + } + lodepng_free(maxlens); + + /*fill in the first table for short symbols, or secondary table for long symbols*/ + numpresent = 0; + for(i = 0; i < tree->numcodes; ++i) { + unsigned l = tree->lengths[i]; + unsigned symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ + /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ + unsigned reverse = reverseBits(symbol, l); + if(l == 0) continue; + numpresent++; + + if(l <= FIRSTBITS) { + /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ + unsigned num = 1u << (FIRSTBITS - l); + unsigned j; + for(j = 0; j < num; ++j) { + /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ + unsigned index = reverse | (j << l); + if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + tree->table_len[index] = l; + tree->table_value[index] = i; + } + } else { + /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ + /*the FIRSTBITS MSBs of the symbol are the first table index*/ + unsigned index = reverse & mask; + unsigned maxlen = tree->table_len[index]; + /*log2 of secondary table length, should be >= l - FIRSTBITS*/ + unsigned tablelen = maxlen - FIRSTBITS; + unsigned start = tree->table_value[index]; /*starting index in secondary table*/ + unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ + unsigned j; + if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + for(j = 0; j < num; ++j) { + unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ + unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); + tree->table_len[index2] = l; + tree->table_value[index2] = i; + } + } + } + + if(numpresent < 2) { + /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, + but deflate uses 1 bit instead. In case of 0 symbols, no symbols can + appear at all, but such huffman tree could still exist (e.g. if distance + codes are never used). In both cases, not all symbols of the table will be + filled in. Fill them in with an invalid symbol value so returning them from + huffmanDecodeSymbol will cause error. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) { + /* As length, use a value smaller than FIRSTBITS for the head table, + and a value larger than FIRSTBITS for the secondary table, to ensure + valid behavior for advanceBits when reading this symbol. */ + tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); + tree->table_value[i] = INVALIDSYMBOL; + } + } + } else { + /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + If that is not the case (due to too long length codes), the table will not + have been fully used, and this is an error (not all bit combinations can be + decoded): an oversubscribed huffman tree, indicated by error 55. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) return 55; + } + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { + unsigned* blcount; + unsigned* nextcode; + unsigned error = 0; + unsigned bits, n; + + tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ + + if(!error) { + for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) { + if(tree->lengths[n] != 0) { + tree->codes[n] = nextcode[tree->lengths[n]]++; + /*remove superfluous bits from the code*/ + tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); + } + } + } + + lodepng_free(blcount); + lodepng_free(nextcode); + + if(!error) error = HuffmanTree_makeTable(tree); + return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) { + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode { + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists { + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) { + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } else { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) { + if(frequencies[i] > 0) { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } else if(numpresent == 1) { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } else { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code. The bit reader must already have been ensured at least 15 bits +*/ +static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { + unsigned short code = peekBits(reader, FIRSTBITS); + unsigned short l = codetree->table_len[code]; + unsigned short value = codetree->table_value[code]; + if(l <= FIRSTBITS) { + advanceBits(reader, l); + return value; + } else { + unsigned index2; + advanceBits(reader, FIRSTBITS); + index2 = value + peekBits(reader, l - FIRSTBITS); + advanceBits(reader, codetree->table_len[index2] - FIRSTBITS); + return codetree->table_value[index2]; + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification +Returns error code.*/ +static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { + unsigned error = generateFixedLitLenTree(tree_ll); + if(error) return error; + return generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + LodePNGBitReader* reader) { + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if(!ensureBits17(reader, 14)) return 49; /*error: the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBits(reader, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBits(reader, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBits(reader, 4) + 4; + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) return 83 /*alloc fail*/; + + HuffmanTree_init(&tree_cl); + + while(!error) { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { + ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ + } + for(i = 0; i != HCLEN; ++i) { + ensureBits9(reader, 3); /*out of bounds already checked above */ + bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); + } + for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { + bitlen_cl[CLCL_ORDER[i]] = 0; + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); + lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) { + unsigned code; + ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ + code = huffmanDecodeSymbol(reader, &tree_cl); + if(code <= 15) /*a length code*/ { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } else if(code == 16) /*repeat previous*/ { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBits(reader, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } else if(code == 17) /*repeat "0" 3-10 times*/ { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else if(code == 18) /*repeat "0" 11-138 times*/ { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else /*if(code == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ +static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, + unsigned btype) { + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); + else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); + + while(!error) /*decode all symbols until end reached, breaks at end code*/ { + /*code_ll is literal, length or end code*/ + unsigned code_ll; + ensureBits25(reader, 20); /* up to 15 for the huffman symbol, up to 5 for the length extra bits */ + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + if(code_ll <= 255) /*literal symbol*/ { + if(!ucvector_resize(out, out->size + 1)) ERROR_BREAK(83 /*alloc fail*/); + out->data[out->size - 1] = (unsigned char)code_ll; + } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(numextrabits_l != 0) { + /* bits already ensured above */ + length += readBits(reader, numextrabits_l); + } + + /*part 3: get distance code*/ + ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ + code_d = huffmanDecodeSymbol(reader, &tree_d); + if(code_d > 29) { + if(code_d <= 31) { + ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ + } else /* if(code_d == INVALIDSYMBOL) */{ + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(numextrabits_d != 0) { + /* bits already ensured above */ + distance += readBits(reader, numextrabits_d); + } + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = out->size; + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + if(!ucvector_resize(out, out->size + length)) ERROR_BREAK(83 /*alloc fail*/); + if(distance < length) { + size_t forward; + lodepng_memcpy(out->data + start, out->data + backward, distance); + start += distance; + for(forward = distance; forward < length; ++forward) { + out->data[start++] = out->data[backward++]; + } + } else { + lodepng_memcpy(out->data + start, out->data + backward, length); + } + } else if(code_ll == 256) { + break; /*end code, break the loop*/ + } else /*if(code_ll == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, + const LodePNGDecompressSettings* settings) { + size_t bytepos; + size_t size = reader->size; + unsigned LEN, NLEN, error = 0; + + /*go to first boundary of byte*/ + bytepos = (reader->bp + 7u) >> 3u; + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ + LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(!settings->ignore_nlen && LEN + NLEN != 65535) { + return 21; /*error: NLEN is not one's complement of LEN*/ + } + + if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ + + lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); + bytepos += LEN; + + reader->bp = bytepos << 3u; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned BFINAL = 0; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, in, insize); + + if(error) return error; + + while(!BFINAL) { + unsigned BTYPE; + if(!ensureBits9(&reader, 3)) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBits(&reader, 1); + BTYPE = readBits(&reader, 2); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ + else error = inflateHuffmanBlock(out, &reader, BTYPE); /*compression, BTYPE 01 or 10*/ + + if(error) return error; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + if(settings->custom_inflate) { + unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); + out->allocsize = out->size; + return error; + } else { + return lodepng_inflatev(out, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if(array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) { + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + size_t pos = values->size; + /*TODO: return error when this fails (out of memory)*/ + unsigned ok = uivector_resize(values, values->size + 4); + if(ok) { + values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; + values->data[pos + 1] = extra_length; + values->data[pos + 2] = dist_code; + values->data[pos + 3] = extra_distance; + } +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash { + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) { + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) { + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { + unsigned result = 0; + if(pos + 2 < size) { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= ((unsigned)data[pos + 0] << 0u); + result ^= ((unsigned)data[pos + 1] << 4u); + result ^= ((unsigned)data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = (int)wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = (int)wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) { + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) { + if(chainlength++ >= maxchainlength) break; + current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } else { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } else { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else if(length < minmatch || (length == 3 && offset > 4096)) { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + size_t pos = out->size; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + LEN = 65535; + if(datasize - datapos < 65535u) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); + out->data[pos + 0] = firstbyte; + out->data[pos + 1] = (unsigned char)(LEN & 255); + out->data[pos + 2] = (unsigned char)(LEN >> 8u); + out->data[pos + 3] = (unsigned char)(NLEN & 255); + out->data[pos + 4] = (unsigned char)(NLEN >> 8u); + lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); + datapos += LEN; + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) { + unsigned val = lz77_encoded->data[i]; + writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); + if(val > 256) /*for a length code, 3 more things have to be added*/ { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + writeBits(writer, length_extra_bits, n_length_extra_bits); + writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); + writeBits(writer, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lengths used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ + unsigned* frequencies_d = 0; /*frequency of dist codes*/ + unsigned* frequencies_cl = 0; /*frequency of code length codes*/ + unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ + unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ + size_t datasize = dataend - datapos; + + /* + If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent + tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are + some analogies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t i; + size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + /* could fit on stack, but >1KB is on the larger side so allocate instead */ + frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); + frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); + frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) { + lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); + lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); + lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(settings->use_lz77) { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } else { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll[symbol]; + if(symbol > 256) { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d[dist]; + i += 3; + } + } + frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); + if(error) break; + + numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); + numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); + /*store the code lengths of both generated trees in bitlen_lld*/ + numcodes_lld = numcodes_ll + numcodes_d; + bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); + /*numcodes_lld_e never needs more size than bitlen_lld*/ + bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); + if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ + numcodes_lld_e = 0; + + for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; + for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != numcodes_lld; ++i) { + unsigned j = 0; /*amount of repetitions*/ + while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; + + if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { + bitlen_lld_e[numcodes_lld_e++] = 17; + bitlen_lld_e[numcodes_lld_e++] = j - 3; + } else /*repeat code 18 supports max 138 zeroes*/ { + if(j > 138) j = 138; + bitlen_lld_e[numcodes_lld_e++] = 18; + bitlen_lld_e[numcodes_lld_e++] = j - 11; + } + i += (j - 1); + } else if(j >= 3) /*repeat code for value other than zero*/ { + size_t k; + unsigned num = j / 6u, rest = j % 6u; + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + for(k = 0; k < num; ++k) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = 6 - 3; + } + if(rest >= 3) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = rest - 3; + } + else j -= rest; + i += j; + } else /*too short to benefit from repeat code*/ { + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + for(i = 0; i != numcodes_lld_e; ++i) { + ++frequencies_cl[bitlen_lld_e[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, + NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*compute amount of code-length-code-lengths to output*/ + numcodes_cl = NUM_CODE_LENGTH_CODES; + /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ + while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { + numcodes_cl--; + } + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + writeBits(writer, BFINAL, 1); + writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ + writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies + or in the loop for numcodes_cl above, which saves space. */ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)(numcodes_cl - 4); + writeBits(writer, HLIT, 5); + writeBits(writer, HDIST, 5); + writeBits(writer, HCLEN, 4); + + /*write the code lengths of the code length alphabet ("bitlen_cl")*/ + for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); + + /*write the lengths of the lit/len AND the dist alphabet*/ + for(i = 0; i != numcodes_lld_e; ++i) { + writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); + /*extra bits of repeat codes*/ + if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); + else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); + else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); + + /*write the end code*/ + writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + lodepng_free(frequencies_ll); + lodepng_free(frequencies_d); + lodepng_free(frequencies_cl); + lodepng_free(bitlen_lld); + lodepng_free(bitlen_lld_e); + + return error; +} + +static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + error = generateFixedLitLenTree(&tree_ll); + if(!error) error = generateFixedDistanceTree(&tree_d); + + if(!error) { + writeBits(writer, BFINAL, 1); + writeBits(writer, 1, 1); /*first bit of BTYPE*/ + writeBits(writer, 0, 1); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } else /*no LZ77, but still will be Huffman compressed*/ { + for(i = datapos; i < dataend; ++i) { + writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); + } + } + /*add END code*/ + if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); + } + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + Hash hash; + LodePNGBitWriter writer; + + LodePNGBitWriter_init(&writer, out); + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8u + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + + if(!error) { + for(i = 0; i != numdeflateblocks && !error; ++i) { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); + } + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + if(settings->custom_deflate) { + return settings->custom_deflate(out, outsize, in, insize, settings); + } else { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { + unsigned s1 = adler & 0xffffu; + unsigned s2 = (adler >> 16u) & 0xffffu; + + while(len != 0u) { + unsigned i; + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552u ? 5552u : len; + len -= amount; + for(i = 0; i != amount; ++i) { + s1 += (*data++); + s2 += s1; + } + s1 %= 65521u; + s2 %= 65521u; + } + + return (s2 << 16u) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) { + return update_adler32(1u, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +static unsigned lodepng_zlib_decompressv(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflatev(out, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(out->data, (unsigned)(out->size)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(settings->custom_zlib) { + return settings->custom_zlib(out, outsize, in, insize, settings); + } else { + unsigned error; + ucvector v = ucvector_init(*out, *outsize); + if(expected_size) { + /*reserve the memory to avoid intermediate reallocations*/ + ucvector_resize(&v, *outsize + expected_size); + v.size = *outsize; + } + error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + *out = NULL; + *outsize = 0; + if(!error) { + *outsize = deflatesize + 6; + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!*out) error = 83; /*alloc fail*/ + } + + if(!error) { + unsigned ADLER32 = adler32(in, (unsigned)insize); + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + (*out)[0] = (unsigned char)(CMFFLG >> 8); + (*out)[1] = (unsigned char)(CMFFLG & 255); + for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; + lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); + } + + lodepng_free(deflatedata); + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(settings->custom_zlib) { + return settings->custom_zlib(out, outsize, in, insize, settings); + } else { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + (void)expected_size; + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { + settings->ignore_adler32 = 0; + settings->ignore_nlen = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifndef LODEPNG_NO_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) { + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) { + r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); + } + return r ^ 0xffffffffu; +} +#else /* !LODEPNG_NO_COMPILE_CRC */ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* !LODEPNG_NO_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing PNG color channel bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, +so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +/* TODO: make this faster */ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { + unsigned result = 0; + size_t i; + for(i = 0 ; i < nbits; ++i) { + result <<= 1u; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); + else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) { + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) { + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { + if(lodepng_strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) { + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) { + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + const unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next(chunk, end); + } +} + +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next_const(chunk, end); + } +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { + unsigned i; + size_t total_chunk_length, new_length; + unsigned char *chunk_start, *new_buffer; + + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; + if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outsize) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +/*Sets length and name and allocates the space for data and crc but does not +set data or crc yet. Returns the start of the chunk in chunk. The start of +the data is at chunk + 8. To finalize chunk, add the data, then use +lodepng_chunk_generate_crc */ +static unsigned lodepng_chunk_init(unsigned char** chunk, + ucvector* out, + unsigned length, const char* type) { + size_t new_length = out->size; + if(lodepng_addofl(new_length, length, &new_length)) return 77; + if(lodepng_addofl(new_length, 12, &new_length)) return 77; + if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ + *chunk = out->data + new_length - length - 12u; + + /*1: length*/ + lodepng_set32bitInt(*chunk, length); + + /*2: chunk name (4 letters)*/ + lodepng_memcpy(*chunk + 4, type, 4); + + return 0; +} + +/* like lodepng_chunk_create but with custom allocsize */ +static unsigned lodepng_chunk_createv(ucvector* out, + unsigned length, const char* type, const unsigned char* data) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); + + /*3: the data*/ + lodepng_memcpy(chunk + 8, data, length); + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, + unsigned length, const char* type, const unsigned char* data) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_chunk_createv(&v, length, type, data); + *out = v.data; + *outsize = v.size; + return error; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types, channels, bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. +Return value is a LodePNG error code.*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { + switch(colortype) { + case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; + case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; + case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ + default: return 31; /* invalid color type */ + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) { + switch(colortype) { + case LCT_GREY: return 1; + case LCT_RGB: return 3; + case LCT_PALETTE: return 1; + case LCT_GREY_ALPHA: return 2; + case LCT_RGBA: return 4; + case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ + default: return 0; /*invalid color type*/ + } +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) { + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +/*allocates palette memory if needed, and initializes all colors to black*/ +static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { + size_t i; + /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ + /*the palette must have room for up to 256 colors with 4 bytes each.*/ + if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); + if(!info->palette) return; /*alloc fail*/ + for(i = 0; i != 256; ++i) { + /*Initialize all unused colors with black, the value used for invalid palette indices. + This is an error according to the PNG spec, but common PNG decoders make it black instead. + That makes color conversion slightly faster due to no error handling needed.*/ + info->palette[i * 4 + 0] = 0; + info->palette[i * 4 + 1] = 0; + info->palette[i * 4 + 2] = 0; + info->palette[i * 4 + 3] = 255; + } +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) { + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { + lodepng_color_mode_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); + if(source->palette) { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); + } + return 0; +} + +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { + LodePNGColorMode result; + lodepng_color_mode_init(&result); + result.colortype = colortype; + result.bitdepth = bitdepth; + return result; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) { + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(!info->palette) /*allocate palette if empty*/ { + lodepng_color_mode_alloc_palette(info); + if(!info->palette) return 83; /*alloc fail*/ + } + if(info->palettesize >= 256) { + return 108; /*too many palette values*/ + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +/*calculate bits per pixel out of colortype and bitdepth*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info) { + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) { + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { + size_t i; + for(i = 0; i != info->palettesize; ++i) { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = (size_t)w * (size_t)h; + return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { + return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); +} + + +#ifdef LODEPNG_COMPILE_PNG + +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, +and in addition has one extra byte per line: the filter byte. So this gives a larger +result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { + /* + 1 for the filter byte, and possibly plus padding bits per line. */ + /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ + size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; + return (size_t)h * line; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*Safely checks whether size_t overflow can be caused due to amount of pixels. +This check is overcautious rather than precise. If this check indicates no overflow, +you can safely compute in a size_t (but not an unsigned): +-(size_t)w * (size_t)h * 8 +-amount of bytes in IDAT (including filter, padding and Adam7 bytes) +-amount of bytes in raw color model +Returns 1 if overflow possible, 0 if not. +*/ +static int lodepng_pixel_overflow(unsigned w, unsigned h, + const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { + size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); + size_t numpixels, total; + size_t line; /* bytes per line in worst case */ + + if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; + if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ + + /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ + if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; + if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; + + if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ + if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ + + return 0; /* no overflow */ +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) { + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->text_num; ++i) { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->text_keys = 0; + dest->text_strings = 0; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + + if(new_keys) info->text_keys = new_keys; + if(new_strings) info->text_strings = new_strings; + + if(!new_keys || !new_strings) return 83; /*alloc fail*/ + + ++info->text_num; + info->text_keys[info->text_num - 1] = alloc_string(key); + info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); + if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ + + return 0; +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { + return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); +} + +void lodepng_clear_text(LodePNGInfo* info) { + LodePNGText_cleanup(info); +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) { + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->itext_num; ++i) { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->itext_keys = 0; + dest->itext_langtags = 0; + dest->itext_transkeys = 0; + dest->itext_strings = 0; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) { + LodePNGIText_cleanup(info); +} + +static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + + if(new_keys) info->itext_keys = new_keys; + if(new_langtags) info->itext_langtags = new_langtags; + if(new_transkeys) info->itext_transkeys = new_transkeys; + if(new_strings) info->itext_strings = new_strings; + + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ + + ++info->itext_num; + + info->itext_keys[info->itext_num - 1] = alloc_string(key); + info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); + info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); + info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); + + return 0; +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) { + return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); +} + +/* same as set but does not delete */ +static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(profile_size == 0) return 100; /*invalid ICC profile size*/ + + info->iccp_name = alloc_string(name); + info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); + + if(!info->iccp_name || !info->iccp_profile) return 83; /*alloc fail*/ + + lodepng_memcpy(info->iccp_profile, profile, profile_size); + info->iccp_profile_size = profile_size; + + return 0; /*ok*/ +} + +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(info->iccp_name) lodepng_clear_icc(info); + info->iccp_defined = 1; + + return lodepng_assign_icc(info, name, profile, profile_size); +} + +void lodepng_clear_icc(LodePNGInfo* info) { + string_cleanup(&info->iccp_name); + lodepng_free(info->iccp_profile); + info->iccp_profile = NULL; + info->iccp_profile_size = 0; + info->iccp_defined = 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) { + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + info->gama_defined = 0; + info->chrm_defined = 0; + info->srgb_defined = 0; + info->iccp_defined = 0; + info->iccp_name = NULL; + info->iccp_profile = NULL; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) { + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + lodepng_clear_icc(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + lodepng_info_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + if(source->iccp_defined) { + CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); + } + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8u] = in; + else out[index * bits / 8u] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree { + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) { + lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) { + int i; + for(i = 0; i != 16; ++i) { + if(tree->children[i]) { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + int bit = 0; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") +Returns error code, or 0 if ok*/ +static unsigned color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { + int bit; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + if(!tree->children[i]) return 83; /*alloc fail*/ + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; + return 0; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(mode->colortype == LCT_GREY) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) out[i] = gray; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; + else { + /*take the most significant bits of gray*/ + gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); + addColorBits(out, i, mode->bitdepth, gray); + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } else { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } else if(mode->colortype == LCT_PALETTE) { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) { + out[i * 2 + 0] = gray; + out[i * 2 + 1] = a; + } else if(mode->bitdepth == 16) { + out[i * 4 + 0] = out[i * 4 + 1] = gray; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } else { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) { + if(mode->colortype == LCT_GREY) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 2 + 0] = (gray >> 8) & 255; + out[i * 2 + 1] = gray & 255; + } else if(mode->colortype == LCT_RGB) { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 4 + 0] = (gray >> 8) & 255; + out[i * 4 + 1] = gray & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } else if(mode->colortype == LCT_RGBA) { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } else if(mode->bitdepth == 16) { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } else { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_PALETTE) { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } else { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } else { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to the common case of RGBA with 8 bit per channel. buffer must be RGBA with +enough memory.*/ +static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + unsigned num_channels = 4; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r) buffer[3] = 0; + } + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 3], 3); + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; + } + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + buffer[3] = in[i * 2 + 1]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + buffer[3] = in[i * 4 + 2]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 4); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ +static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + const unsigned num_channels = 3; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 3); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 4], 3); + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_RGB) { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_GREY_ALPHA) { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } else if(mode->colortype == LCT_RGBA) { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { + return 107; /* error: must provide palette if input mode is palette */ + } + + if(lodepng_color_mode_equal(mode_out, mode_in)) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palettesize == 0) { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /*if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + } + if(palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) { + const unsigned char* p = &palette[i * 4]; + error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); + if(error) break; + } + } + + if(!error) { + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for(i = 0; i != numpixels; ++i) { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { + getPixelColorsRGBA8(out, numpixels, in, mode_in); + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { + getPixelColorsRGB8(out, numpixels, in, mode_in); + } else { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if(error) break; + } + } + } + + if(mode_out->colortype == LCT_PALETTE) { + color_tree_cleanup(&tree); + } + + return error; +} + + +/* Converts a single rgb color without alpha from one type to another, color bits truncated to +their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow +function, do not use to process all pixels of an image. Alpha channel not supported on purpose: +this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the +specification it looks like bKGD should ignore the alpha values of the palette since it can use +any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ +unsigned lodepng_convert_rgb( + unsigned* r_out, unsigned* g_out, unsigned* b_out, + unsigned r_in, unsigned g_in, unsigned b_in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { + unsigned r = 0, g = 0, b = 0; + unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ + unsigned shift = 16 - mode_out->bitdepth; + + if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { + r = g = b = r_in * mul; + } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { + r = r_in * mul; + g = g_in * mul; + b = b_in * mul; + } else if(mode_in->colortype == LCT_PALETTE) { + if(r_in >= mode_in->palettesize) return 82; + r = mode_in->palette[r_in * 4 + 0] * 257u; + g = mode_in->palette[r_in * 4 + 1] * 257u; + b = mode_in->palette[r_in * 4 + 2] * 257u; + } else { + return 31; + } + + /* now convert to output format */ + if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { + *r_out = r >> shift ; + } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { + *r_out = r >> shift ; + *g_out = g >> shift ; + *b_out = b >> shift ; + } else if(mode_out->colortype == LCT_PALETTE) { + unsigned i; + /* a 16-bit color cannot be in the palette */ + if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; + for(i = 0; i < mode_out->palettesize; i++) { + unsigned j = i * 4; + if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && + (b >> 8) == mode_out->palette[j + 2]) { + *r_out = i; + return 0; + } + } + return 82; + } else { + return 31; + } + + return 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_stats_init(LodePNGColorStats* stats) { + /*stats*/ + stats->colored = 0; + stats->key = 0; + stats->key_r = stats->key_g = stats->key_b = 0; + stats->alpha = 0; + stats->numcolors = 0; + stats->bits = 1; + stats->numpixels = 0; + /*settings*/ + stats->allow_palette = 1; + stats->allow_greyscale = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorStats(LodePNGColorStats* p) { + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) { + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*stats must already have been inited. */ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + /* mark things as done already if it would be impossible to have a more expensive case */ + unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode_in); + unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; + unsigned sixteen = 0; /* whether the input image is 16 bit */ + unsigned maxnumcolors = 257; + if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); + + stats->numpixels += numpixels; + + /*if palette not allowed, no need to compute numcolors*/ + if(!stats->allow_palette) numcolors_done = 1; + + color_tree_init(&tree); + + /*If the stats was already filled in from previous data, fill its palette in tree + and mark things as done already if we know they are the most expensive case already*/ + if(stats->alpha) alpha_done = 1; + if(stats->colored) colored_done = 1; + if(stats->bits == 16) numcolors_done = 1; + if(stats->bits >= bpp) bits_done = 1; + if(stats->numcolors >= maxnumcolors) numcolors_done = 1; + + if(!numcolors_done) { + for(i = 0; i < stats->numcolors; i++) { + const unsigned char* color = &stats->palette[i * 4]; + error = color_tree_add(&tree, color[0], color[1], color[2], color[3], i); + if(error) goto cleanup; + } + } + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode_in->bitdepth == 16 && !sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { + stats->bits = 16; + sixteen = 1; + bits_done = 1; + numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + break; + } + } + } + + if(sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 65535 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + } + } else /* < 16-bit */ { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + + if(!bits_done && stats->bits < 8) { + /*only r is checked, < 8 bits is only relevant for grayscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > stats->bits) stats->bits = bits; + } + bits_done = (stats->bits >= bpp); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 255 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 255 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) { + if(!color_tree_has(&tree, r, g, b, a)) { + error = color_tree_add(&tree, r, g, b, a, stats->numcolors); + if(error) goto cleanup; + if(stats->numcolors < 256) { + unsigned char* p = stats->palette; + unsigned n = stats->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++stats->numcolors; + numcolors_done = stats->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + } + + /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ + stats->key_r += (stats->key_r << 8); + stats->key_g += (stats->key_g << 8); + stats->key_b += (stats->key_b << 8); + } + +cleanup: + color_tree_cleanup(&tree); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit +(with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for +all pixels of an image but only for a few additional values. */ +static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, + unsigned r, unsigned g, unsigned b, unsigned a) { + unsigned error = 0; + unsigned char image[8]; + LodePNGColorMode mode; + lodepng_color_mode_init(&mode); + image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; + image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; + mode.bitdepth = 16; + mode.colortype = LCT_RGBA; + error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); + lodepng_color_mode_cleanup(&mode); + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Computes a minimal PNG color model that can contain all colors as indicated by the stats. +The stats should be computed with lodepng_compute_color_stats. +mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. +Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, +e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... +This is used if auto_convert is enabled (it is by default). +*/ +static unsigned auto_choose_color(LodePNGColorMode* mode_out, + const LodePNGColorMode* mode_in, + const LodePNGColorStats* stats) { + unsigned error = 0; + unsigned palettebits; + size_t i, n; + size_t numpixels = stats->numpixels; + unsigned palette_ok, gray_ok; + + unsigned alpha = stats->alpha; + unsigned key = stats->key; + unsigned bits = stats->bits; + + mode_out->key_defined = 0; + + if(key && numpixels <= 16) { + alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + key = 0; + if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + + gray_ok = !stats->colored; + if(!stats->allow_greyscale) gray_ok = 0; + if(!gray_ok && bits < 8) bits = 8; + + n = stats->numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ + if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ + if(!stats->allow_palette) palette_ok = 0; + + if(palette_ok) { + const unsigned char* p = stats->palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != stats->numcolors; ++i) { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); + lodepng_color_mode_copy(mode_out, mode_in); + } + } else /*8-bit or 16-bit per channel*/ { + mode_out->bitdepth = bits; + mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) + : (gray_ok ? LCT_GREY : LCT_RGB); + if(key) { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ + mode_out->key_r = stats->key_r & mask; + mode_out->key_g = stats->key_g & mask; + mode_out->key_b = stats->key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predictor, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) { + short pa = LODEPNG_ABS(b - c); + short pb = LODEPNG_ABS(a - c); + short pc = LODEPNG_ABS(a + b - c - c); + /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ + if(pb < pa) { a = b; pa = pb; } + return (pc < pa) ? c : a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned width, height; + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + /* TODO: remove this. One should use a new LodePNGState for new sessions */ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + width = lodepng_read32bitInt(&in[16]); + height = lodepng_read32bitInt(&in[20]); + /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ + if(w) *w = width; + if(h) *h = height; + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + /*errors returned only after the parsing so other values are still output*/ + + /*error: invalid image size*/ + if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); + /*error: invalid colortype or bitdepth combination*/ + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + if(state->error) return state->error; + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + if(!state->decoder.ignore_crc) { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) { + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } else { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) >> 1u); + } else { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + (recon[i - bytewidth] >> 1u); + } + break; + case 4: + if(precon) { + for(i = 0; i != bytewidth; ++i) { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + + /* Unroll independent paths of the paeth predictor. A 6x and 8x version would also be possible but that + adds too much code. Whether this actually speeds anything up at all depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + recon[i + 3] = s3 + paethPredictor(r3, p3, q3); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + } + } + + for(; i != length; ++i) { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) { + recon[i] = scanline[i]; + } + for(i = bytewidth; i < length; ++i) { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[i - bytewidth]); + } + } + break; + default: return 36; /*error: invalid filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + for(y = 0; y < h; ++y) { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) { + size_t x; + for(x = 0; x < olinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) { + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) { + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned pos = 0, i; + color->palettesize = chunkLength / 3u; + if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ + lodepng_color_mode_alloc_palette(color); + if(!color->palette && color->palettesize) { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + + for(i = 0; i != color->palettesize; ++i) { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned i; + if(color->colortype == LCT_PALETTE) { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 39; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } else if(color->colortype == LCT_GREY) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } else if(color->colortype == LCT_RGB) { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ + if(data[0] >= info->color.palettesize) return 103; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 44; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 6 bytes for grayscale image*/ + if(chunkLength != 6) return 45; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + char *key = 0, *str = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(str, data + string2_begin, length); + str[length] = 0; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + + unsigned length, string2_begin; + char *key = 0; + unsigned char* str = 0; + size_t size = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[string2_begin], + length, zlibsettings); + if(error) break; + error = lodepng_add_text_sized(info, key, (char*)str, size); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(langtag, data + begin, length); + langtag[length] = 0; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(transkey, data + begin, length); + transkey[length] = 0; + + /*read the actual text*/ + begin += length + 1; + + length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; + + if(compressed) { + unsigned char* str = 0; + size_t size = 0; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[begin], + length, zlibsettings); + if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); + lodepng_free(str); + } else { + error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)(data + begin), length); + } + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} + +static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ + + info->gama_defined = 1; + info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + + return 0; /* OK */ +} + +static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ + + info->chrm_defined = 1; + info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; + info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; + info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; + info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; + info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; + info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; + info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; + info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; + + return 0; /* OK */ +} + +static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ + + info->srgb_defined = 1; + info->srgb_intent = data[0]; + + return 0; /* OK */ +} + +static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + size_t size = 0; + + unsigned length, string2_begin; + + info->iccp_defined = 1; + if(info->iccp_name) lodepng_clear_icc(info); + + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ + if(length < 1 || length > 79) return 89; /*keyword too short or long*/ + + info->iccp_name = (char*)lodepng_malloc(length + 1); + if(!info->iccp_name) return 83; /*alloc fail*/ + + info->iccp_name[length] = 0; + for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; + + if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + error = zlib_decompress(&info->iccp_profile, &size, 0, + &data[string2_begin], + length, zlibsettings); + info->iccp_profile_size = size; + if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/ + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize) { + const unsigned char* chunk = in + pos; + unsigned chunkLength; + const unsigned char* data; + unsigned unhandled = 0; + unsigned error = 0; + + if(pos + 4 > insize) return 30; + chunkLength = lodepng_chunk_length(chunk); + if(chunkLength > 2147483647) return 63; + data = lodepng_chunk_data_const(chunk); + if(data + chunkLength + 4 > in + insize) return 30; + + if(lodepng_chunk_type_equals(chunk, "PLTE")) { + error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + error = readChunk_tRNS(&state->info_png.color, data, chunkLength); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + error = readChunk_bKGD(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + error = readChunk_tEXt(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + error = readChunk_tIME(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + error = readChunk_pHYs(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + error = readChunk_gAMA(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + error = readChunk_cHRM(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + error = readChunk_sRGB(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + error = readChunk_iCCP(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else { + /* unhandled chunk is ok (is not an error) */ + unhandled = 1; + } + + if(!error && !unhandled && !state->decoder.ignore_crc) { + if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned char IEND = 0; + const unsigned char* chunk; + unsigned char* idat; /*the data from idat chunks, zlib compressed*/ + size_t idatsize = 0; + unsigned char* scanlines = 0; + size_t scanlines_size = 0, expected_size = 0; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { + CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ + } + + /*the input filesize is a safe upper bound for the sum of idat chunks size*/ + idat = (unsigned char*)lodepng_malloc(insize); + if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + unknown = 0; + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) { + size_t newsize; + if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); + if(newsize > insize) CERROR_BREAK(state->error, 95); + lodepng_memcpy(idat + idatsize, data, chunkLength); + idatsize += chunkLength; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ + IEND = 1; + } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + /*text chunk (tEXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + /*compressed text chunk (zTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + /*international text chunk (iTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + state->error = readChunk_gAMA(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + state->error = readChunk_cHRM(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + state->error = readChunk_sRGB(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + state->error = readChunk_iCCP(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { + CERROR_BREAK(state->error, 69); + } + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); + } + + if(state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { + state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ + } + + if(!state->error) { + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); + } else { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ + expected_size = 0; + expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); + if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); + if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); + if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); + } + + state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); + } + if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ + lodepng_free(idat); + + if(!state->error) { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + if(!state->error) { + lodepng_memset(*out, 0, outsize); + state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); + } + lodepng_free(scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } else { /*color conversion needed*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) { +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) { + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +static unsigned writeSignature(ucvector* out) { + size_t pos = out->size; + const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; + /*8 bytes PNG signature, aka the magic bytes*/ + if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ + lodepng_memcpy(out->data + pos, signature, 8); + return 0; +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { + unsigned char *chunk, *data; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); + data = chunk + 8; + + lodepng_set32bitInt(data + 0, w); /*width*/ + lodepng_set32bitInt(data + 4, h); /*height*/ + data[8] = (unsigned char)bitdepth; /*bit depth*/ + data[9] = (unsigned char)colortype; /*color type*/ + data[10] = 0; /*compression method*/ + data[11] = 0; /*filter method*/ + data[12] = interlace_method; /*interlace method*/ + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +/* only adds the chunk if needed (there is a key or palette with alpha) */ +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk; + size_t i, j = 8; + + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); + + for(i = 0; i != info->palettesize; ++i) { + /*add all channels except alpha channel*/ + chunk[j++] = info->palette[i * 4 + 0]; + chunk[j++] = info->palette[i * 4 + 1]; + chunk[j++] = info->palette[i * 4 + 2]; + } + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk = 0; + + if(info->colortype == LCT_PALETTE) { + size_t i, amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) { + if(info->palette[4 * (i - 1) + 3] != 255) break; + --amount; + } + if(amount) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); + /*add the alpha channel values from the palette*/ + for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; + } + } else if(info->colortype == LCT_GREY) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + } + } else if(info->colortype == LCT_RGB) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + chunk[10] = (unsigned char)(info->key_g >> 8); + chunk[11] = (unsigned char)(info->key_g & 255); + chunk[12] = (unsigned char)(info->key_b >> 8); + chunk[13] = (unsigned char)(info->key_b & 255); + } + } + + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* zlib = 0; + size_t zlibsize = 0; + + error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); + if(!error) { + error = lodepng_chunk_createv(out, zlibsize, "IDAT", zlib); + } + lodepng_free(zlib); + return error; +} + +static unsigned addChunk_IEND(ucvector* out) { + return lodepng_chunk_createv(out, 0, "IEND", 0); +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { + unsigned char* chunk = 0; + size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); + size_t size = keysize + 1 + textsize; + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword); + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "zTXt"); + } + if(!error) { + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + if(compress) { + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + } + if(!error) { + size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); + error = lodepng_chunk_init(&chunk, out, size, "iTXt"); + } + if(!error) { + size_t pos = 8; + lodepng_memcpy(chunk + pos, keyword, keysize); + pos += keysize; + chunk[pos++] = 0; /*null termination char*/ + chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ + chunk[pos++] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + pos, langtag, langsize); + pos += langsize; + chunk[pos++] = 0; /*null termination char*/ + lodepng_memcpy(chunk + pos, transkey, transsize); + pos += transsize; + chunk[pos++] = 0; /*null termination char*/ + if(compress) { + lodepng_memcpy(chunk + pos, compressed, compressedsize); + } else { + lodepng_memcpy(chunk + pos, textstring, textsize); + } + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + chunk[10] = (unsigned char)(info->background_g >> 8); + chunk[11] = (unsigned char)(info->background_g & 255); + chunk[12] = (unsigned char)(info->background_b >> 8); + chunk[13] = (unsigned char)(info->background_b & 255); + } else if(info->color.colortype == LCT_PALETTE) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); + chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); + chunk[8] = (unsigned char)(time->year >> 8); + chunk[9] = (unsigned char)(time->year & 255); + chunk[10] = (unsigned char)time->month; + chunk[11] = (unsigned char)time->day; + chunk[12] = (unsigned char)time->hour; + chunk[13] = (unsigned char)time->minute; + chunk[14] = (unsigned char)time->second; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); + lodepng_set32bitInt(chunk + 8, info->phys_x); + lodepng_set32bitInt(chunk + 12, info->phys_y); + chunk[16] = info->phys_unit; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); + lodepng_set32bitInt(chunk + 8, info->gama_gamma); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); + lodepng_set32bitInt(chunk + 8, info->chrm_white_x); + lodepng_set32bitInt(chunk + 12, info->chrm_white_y); + lodepng_set32bitInt(chunk + 16, info->chrm_red_x); + lodepng_set32bitInt(chunk + 20, info->chrm_red_y); + lodepng_set32bitInt(chunk + 24, info->chrm_green_x); + lodepng_set32bitInt(chunk + 28, info->chrm_green_y); + lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); + lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { + unsigned char data = info->srgb_intent; + return lodepng_chunk_createv(out, 1, "sRGB", &data); +} + +static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t keysize = lodepng_strlen(info->iccp_name); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + error = zlib_compress(&compressed, &compressedsize, + info->iccp_profile, info->iccp_profile_size, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "iCCP"); + } + if(!error) { + lodepng_memcpy(chunk + 8, info->iccp_name, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) { + size_t i; + switch(filterType) { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } else { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*invalid filter type given*/ + } +} + +/* integer binary logarithm, max return value is 31 */ +static size_t ilog2(size_t i) { + size_t result = 0; + if(i >= 65536) { result += 16; i >>= 16; } + if(i >= 256) { result += 8; i >>= 8; } + if(i >= 16) { result += 4; i >>= 4; } + if(i >= 4) { result += 2; i >>= 2; } + if(i >= 2) { result += 1; /*i >>= 1;*/ } + return result; +} + +/* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ +static size_t ilog2i(size_t i) { + size_t l; + if(i == 0) return 0; + l = ilog2(i); + /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) + linearly approximates the missing fractional part multiplied by i */ + return i * l + ((i - (1u << l)) << 1u); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(color); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (color->colortype == LCT_PALETTE || color->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { + unsigned char type = (unsigned char)strategy; + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_MINSUM) { + /*adaptive filtering*/ + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + if(type == 0) { + for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); + } else { + for(x = 0; x != linebytes; ++x) { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum < smallest) { + bestType = type; + smallest = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_ENTROPY) { + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t bestSum = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + lodepng_memset(count, 0, 256 * sizeof(*count)); + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + for(x = 0; x != 256; ++x) { + sum += ilog2i(count[x]); + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum > bestSum) { + bestType = type; + bestSum = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_PREDEFINED) { + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_BRUTE_FORCE) { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings; + lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + if(!error) { + for(y = 0; y != h; ++y) /*try the 5 filter types*/ { + for(type = 0; type != 5; ++type) { + unsigned testsize = (unsigned)linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) { + size_t x; + for(x = 0; x < ilinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) { + *outsize = h + (h * ((w * bpp + 7u) / 8u)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) { + addPaddingBits(padded, in, ((w * bpp + 7u) / 8u) * 8u, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } else { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) { + if(bpp < 8) { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7u) / 8u) * 8u, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } else { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk, data + datasize); + } + return 0; +} + +static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { + /* + It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 + are "RGB ". We do not perform any full parsing of the ICC profile here, other + than check those 4 bytes to grayscale profile. Other than that, validity of + the profile is not checked. This is needed only because the PNG specification + requires using a non-gray color model if there is an ICC profile with "RGB " + (sadly limiting compression opportunities if the input data is grayscale RGB + data), and requires using a gray color model if it is "GRAY". + */ + if(size < 20) return 0; + return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; +} + +static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { + /* See comment in isGrayICCProfile*/ + if(size < 20) return 0; + return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) { + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + ucvector outv = ucvector_init(NULL, 0); + LodePNGInfo info; + const LodePNGInfo* info_png = &state->info_png; + + lodepng_info_init(&info); + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + /*check input values validity*/ + if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + goto cleanup; + } + if(state->encoder.zlibsettings.btype > 2) { + state->error = 61; /*error: invalid btype*/ + goto cleanup; + } + if(info_png->interlace_method > 1) { + state->error = 71; /*error: invalid interlace mode*/ + goto cleanup; + } + state->error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + + /* color convert and compute scanline filter types */ + lodepng_info_copy(&info, &state->info_png); + if(state->encoder.auto_convert) { + LodePNGColorStats stats; + lodepng_color_stats_init(&stats); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined && + isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use palette with a GRAY ICC profile, even + if the palette has only gray colors, so disallow it.*/ + stats.allow_palette = 0; + } + if(info_png->iccp_defined && + isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ + stats.allow_greyscale = 0; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->background_defined) { + /*the background chunk's color must be taken into account as well*/ + unsigned r = 0, g = 0, b = 0; + LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); + lodepng_convert_rgb(&r, &g, &b, info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); + state->error = lodepng_color_stats_add(&stats, r, g, b, 65535); + if(state->error) goto cleanup; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = auto_choose_color(&info.color, &state->info_raw, &stats); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*also convert the background chunk*/ + if(info_png->background_defined) { + if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, + info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { + state->error = 104; + goto cleanup; + } + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined) { + unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; + if(!gray_icc && !rgb_icc) { + state->error = 100; /* Disallowed profile color type for PNG */ + goto cleanup; + } + if(gray_icc != gray_png) { + /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, + or in case of auto_convert, it wasn't possible to find appropriate model*/ + state->error = state->encoder.auto_convert ? 102 : 101; + goto cleanup; + } + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { + unsigned char* converted; + size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) { + state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + } + lodepng_free(converted); + if(state->error) goto cleanup; + } else { + state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + if(state->error) goto cleanup; + } + + /* output all PNG chunks */ { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + state->error = writeSignature(&outv); + if(state->error) goto cleanup; + /*IHDR*/ + state->error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) goto cleanup; + } + /*color profile chunks must come before PLTE */ + if(info.iccp_defined) { + state->error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + if(info.srgb_defined) { + state->error = addChunk_sRGB(&outv, &info); + if(state->error) goto cleanup; + } + if(info.gama_defined) { + state->error = addChunk_gAMA(&outv, &info); + if(state->error) goto cleanup; + } + if(info.chrm_defined) { + state->error = addChunk_cHRM(&outv, &info); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) { + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { + /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + /*tRNS (this will only add if when necessary) */ + state->error = addChunk_tRNS(&outv, &info.color); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) { + state->error = addChunk_bKGD(&outv, &info); + if(state->error) goto cleanup; + } + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) { + state->error = addChunk_pHYs(&outv, &info); + if(state->error) goto cleanup; + } + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) { + state->error = addChunk_tIME(&outv, &info.time); + if(state->error) goto cleanup; + } + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) { + if(lodepng_strlen(info.text_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.text_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + if(state->encoder.text_compression) { + state->error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } else { + state->error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + if(state->error) goto cleanup; + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) { + unsigned already_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) { + const char* k = info.text_keys[i]; + /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ + if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && + k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { + already_added_id_text = 1; + break; + } + } + if(already_added_id_text == 0) { + state->error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + if(state->error) goto cleanup; + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) { + if(lodepng_strlen(info.itext_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.itext_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + state->error = addChunk_iTXt( + &outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + state->error = addChunk_IEND(&outv); + if(state->error) goto cleanup; + } + +cleanup: + lodepng_info_cleanup(&info); + lodepng_free(data); + + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) { + switch(code) { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ + case 16: return "invalid code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ + case 39: return "tRNS chunk before PLTE or has more entries than palette size"; + case 40: return "tRNS chunk has wrong size for grayscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for grayscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ + case 62: return "conversion from color to grayscale not supported"; + /*(2^31-1)*/ + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, invalid compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "integer overflow due to too many pixels"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + case 95: return "integer overflow with combined idat chunk size"; + case 96: return "invalid gAMA chunk size"; + case 97: return "invalid cHRM chunk size"; + case 98: return "invalid sRGB chunk size"; + case 99: return "invalid sRGB rendering intent"; + case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; + case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; + case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; + case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; + case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; + case 105: return "integer overflow of bitsize"; + case 106: return "PNG file must have PLTE chunk if color type is palette"; + case 107: return "color convert from palette mode requested without setting the palette data in it"; + case 108: return "tried to add more than 256 values to a palette"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { + +#ifdef LODEPNG_COMPILE_DISK +unsigned load_file(std::vector& buffer, const std::string& filename) { + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector& buffer, const std::string& filename) { + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) { + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) { + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() { + lodepng_state_init(this); +} + +State::State(const State& other) { + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() { + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) { + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) { + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) { + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + /* safe output values in case error happens */ + w = h = 0; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) { + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ diff --git a/vendor/librw/src/lodepng/lodepng.h b/vendor/librw/src/lodepng/lodepng.h new file mode 100644 index 0000000..a386459 --- /dev/null +++ b/vendor/librw/src/lodepng/lodepng.h @@ -0,0 +1,1945 @@ +/* +LodePNG version 20200306 + +Copyright (c) 2005-2020 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to +allow implementing a custom lodepng_crc32. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif + +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif + +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_COMPILE_DECODER +#endif + +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif + +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +#define LODEPNG_COMPILE_DISK +#endif + +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif + +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif + +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +#define LODEPNG_COMPILE_ALLOCATORS +#endif + +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw image).*/ +typedef enum LodePNGColorType { + LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ + LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ + /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid + byte value from 0 to 255 that could be present in an invalid PNG file header. Do + not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use + the valid color type names above, or numeric values like 1 or 7 when checking for + particular disallowed color type byte values, or cast to integer to print it.*/ + LCT_MAX_OCTET_VALUE = 255 +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings { + /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ + + /*use custom zlib decoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate)*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ { + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode { + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + This field may not be allocated directly, use lodepng_color_mode_init first, + then lodepng_palette_add per color to correctly initialize it (to ensure size + of exactly 1024 bytes). + + The alpha channels must be set as well, set them to 255 for opaque images. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ + size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For grayscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/grayscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); +/* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a grayscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime { + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo { + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + Suggested background color chunk (bKGD) + + This uses the same color mode and bit depth as the PNG (except no alpha channel), + with values truncated to the bit depth in the unsigned integer. + + For grayscale and palette PNGs, the value is stored in background_r. The values + in background_g and background_b are then unused. + + So when decoding, you may get these in a different color mode than the one you requested + for the raw pixels. + + When encoding with auto_convert, you must use the color model defined in info_png.color for + these values. The encoder normally ignores info_png.color when auto_convert is on, but will + use it to interpret these values (and convert copies of them to its chosen color model). + + When encoding, avoid setting this to an expensive color, such as a non-gray value + when the image is gray, or the compression will be worse since it will be forced to + write the PNG with a more expensive color mode (when auto_convert is on). + + The decoder does not use this background color to edit the color of pixels. This is a + completely optional metadata feature. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red/gray/palette component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + All the string fields below including keys, names and language tags are null terminated. + The PNG specification uses null characters for the keys, names and tags, and forbids null + characters to appear in the main text which is why we can use null termination everywhere here. + + A keyword is minimum 1 character and maximum 79 characters long. It's + discouraged to use a single line length longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + international text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys". + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + Color profile related chunks: gAMA, cHRM, sRGB, iCPP + + LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color + profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please + use these values with a color management library. + + See the PNG, ICC and sRGB specifications for more information about the meaning of these values. + */ + + /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ + unsigned gama_gamma; /* Gamma exponent times 100000 */ + + /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ + unsigned chrm_white_x; /* White Point x times 100000 */ + unsigned chrm_white_y; /* White Point y times 100000 */ + unsigned chrm_red_x; /* Red x times 100000 */ + unsigned chrm_red_y; /* Red y times 100000 */ + unsigned chrm_green_x; /* Green x times 100000 */ + unsigned chrm_green_y; /* Green y times 100000 */ + unsigned chrm_blue_x; /* Blue x times 100000 */ + unsigned chrm_blue_y; /* Blue y times 100000 */ + + /* + sRGB chunk: optional. May not appear at the same time as iCCP. + If gAMA is also present gAMA must contain value 45455. + If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. + */ + unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ + unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ + + /* + iCCP chunk: optional. May not appear at the same time as sRGB. + + LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a + separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color + management and conversions. + + For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC + profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and + enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile. + + For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray + PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure + the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is + enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder + error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel + data if the pixels could be encoded as grayscale but the ICC profile is RGB. + + To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so + make sure you compute it carefully to avoid the above problems. + */ + unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ + char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ + /* + The ICC profile in iccp_profile_size bytes. + Don't allocate this buffer yourself. Use the init/cleanup functions + correctly and use lodepng_set_icc and lodepng_clear_icc. + */ + unsigned char* iccp_profile; + unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ + + /* End of color profile related chunks */ + + + /* + unknown chunks: chunks not known by LodePNG, passed on byte for byte. + + There are 3 buffers, one for each position in the PNG where unknown chunks can appear. + Each buffer contains all unknown chunks for that position consecutively. + The 3 positions are: + 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. + + For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag + above in here, since the encoder will blindly follow this and could then encode an invalid PNG file + (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use + this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), + or any non-standard PNG chunk. + + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ + +/*replaces if exists*/ +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); +void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings { + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable + errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some + strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters + in string keys, etc... */ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy { + /*every filter at zero*/ + LFS_ZERO = 0, + /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ + LFS_ONE = 1, + LFS_TWO = 2, + LFS_THREE = 3, + LFS_FOUR = 4, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), +which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorStats { + unsigned colored; /*not grayscale*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ + size_t numpixels; + + /*user settings for computing/using the stats*/ + unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ + unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ +} LodePNGColorStats; + +void lodepng_color_stats_init(LodePNGColorStats* stats); + +/*Get a LodePNGColorStats of the image. The stats must already have been inited. +Returns error code (e.g. alloc fail) or 0 if ok.*/ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings { + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState { +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the IHDR chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* +Reads one metadata chunk (other than IHDR) of the PNG file and outputs what it +read in the state. Returns error code on failure. +Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const +to find the desired chunk type, and if non null use lodepng_inspect_chunk (with +chunk_pointer - start_of_file as pos). +Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). +Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). +Requirements: &in[pos] must point to start of a chunk, must use regular +lodepng_inspect first since format of most other chunks depends on IHDR, and if +there is a PLTE chunk, that one must be inspected before tRNS or bKGD. +*/ +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +The chunk pointer always points to the beginning of the chunk itself, that is +the first byte of the 4 length bytes. + +In the PNG file format, chunks have the following format: +-4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) +-4 bytes chunk type (ASCII a-z,A-Z only, see below) +-length bytes of data (may be 0 bytes if length was 0) +-4 bytes of CRC, computed on chunk name + data + +The first chunk starts at the 8th byte of the PNG file, the entire rest of the file +exists out of concatenated chunks with the above format. + +PNG standard chunk ASCII naming conventions: +-First byte: uppercase = critical, lowercase = ancillary +-Second byte: uppercase = public, lowercase = private +-Third byte: must be uppercase +-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/* +Iterate to next chunks, allows iterating through all chunks of the PNG file. +Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, +or the 8th byte of a PNG file which always has the first chunk), or alternatively may +point to the first byte of the PNG file (which is not a chunk but the magic header, the +function will then skip over it and return the first real chunk). +Will output pointer to the start of the next chunk, or at or beyond end of the file if there +is no more chunk after this or possibly if the chunk is corrupt. +Start this process at the 8th byte of the PNG file. +In a non-corrupt PNG file, the last chunk should have name "IEND". +*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); + +/*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng { +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState { + public: + State(); + State(const State& other); + ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) +*/ +unsigned load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. +*/ +unsigned save_file(const std::vector& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[X] support color profile chunk types (but never let them touch RGB values by default) +[ ] support all public PNG chunk types (almost done except sBIT, sPLT and hIST) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) +[ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... +[ ] error messages with line numbers (and version) +[ ] errors in state instead of as return code? +[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +[X] provide alternatives for C library functions not present on some platforms (memcpy, ...) +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, + plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + cHRM: RGB chromaticities + gAMA: RGB gamma correction + iCCP: ICC color profile + sRGB: rendering intent + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG: + sBIT + hIST + sPLT + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to grayscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, grayscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to grayscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: grayscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: grayscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +Non supported color conversions: +-color to grayscale when non-gray pixels are present: no error is thrown, but +the result will look ugly because only the red channel is taken (it assumes all +three channels are the same in this case so ignores green and blue). The reason +no error is given is to allow converting from three-channel grayscale images to +one-channel even if there are numerical imprecisions. +-anything to palette when the palette does not have an exact match for a from-color +in it: in this case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any gray or gray+alpha, to gray or gray+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outsize. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distinction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards compliant. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) { + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) { + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.ignore_critical: ignore unknown critical chunks +state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +Not all changes are listed here, the commit history in github lists more: +https://github.com/lvandeve/lodepng + +*) 06 mar 2020: simplified some of the dynamic memory allocations. +*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct + overflow checks. +*) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. +*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette + if gray ICC profile) and non-ICC LodePNGColorProfile renamed to + LodePNGColorStats. +*) 30 dec 2018: code style changes only: removed newlines before opening braces. +*) 10 sep 2018: added way to inspect metadata chunks without full decoding. +*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use + palette index in case of palette. +*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This + change is backwards compatible unless you relied on unknown_chunks for those. +*) 11 jun 2018: less restrictive check for pixel size integer overflow +*) 14 jan 2018: allow optionally ignoring a few more recoverable errors +*) 17 sep 2017: fix memory leak for some encoder input error cases +*) 27 nov 2016: grey+alpha auto color model detection bugfix +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 24 aug 2014: Moved to github +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011: (!) changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2020 Lode Vandevenne +*/ diff --git a/vendor/librw/src/matfx.cpp b/vendor/librw/src/matfx.cpp new file mode 100644 index 0000000..c143fc1 --- /dev/null +++ b/vendor/librw/src/matfx.cpp @@ -0,0 +1,641 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" +#include "ps2/rwps2.h" +#include "ps2/rwps2plg.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwwdgl.h" +#include "gl/rwgl3.h" +#include "gl/rwgl3plg.h" + +#define PLUGIN_ID ID_MATFX + +namespace rw { + +bool32 MatFX::envMapFlipU; +bool32 MatFX::envMapApplyLight; +bool32 MatFX::envMapUseMatColor; +RGBA MatFX::envMapColor = { 255, 255, 255, 255 }; + + +// Atomic + +static void* +createAtomicMatFX(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(int32, object, offset) = 0; + return object; +} + +static void* +copyAtomicMatFX(void *dst, void *src, int32 offset, int32) +{ + // don't call seteffects, it will override the pipeline + if(*PLUGINOFFSET(int32, src, offset)) + *PLUGINOFFSET(int32, dst, offset) = 1; + return dst; +} + +static Stream* +readAtomicMatFX(Stream *stream, int32, void *object, int32, int32) +{ + if(stream->readI32()) + MatFX::enableEffects((Atomic*)object); + return stream; +} + +static Stream* +writeAtomicMatFX(Stream *stream, int32, void *object, int32 offset, int32) +{ + stream->writeI32(*PLUGINOFFSET(int32, object, offset)); + return stream; +} + +static int32 +getSizeAtomicMatFX(void *object, int32 offset, int32) +{ + int32 flag = *PLUGINOFFSET(int32, object, offset); + // TODO: not sure which version + return flag || rw::version < 0x34000 ? 4 : 0; +} + +// Material + +MatFXGlobals matFXGlobals = { 0, 0, { nil }, nil }; + +// TODO: Frames and Matrices? +static void +clearMatFX(MatFX *matfx) +{ + for(int i = 0; i < 2; i++) + switch(matfx->fx[i].type){ + case MatFX::BUMPMAP: + if(matfx->fx[i].bump.bumpedTex) + matfx->fx[i].bump.bumpedTex->destroy(); + if(matfx->fx[i].bump.tex) + matfx->fx[i].bump.tex->destroy(); + break; + + case MatFX::ENVMAP: + if(matfx->fx[i].env.tex) + matfx->fx[i].env.tex->destroy(); + break; + + case MatFX::DUAL: + if(matfx->fx[i].dual.tex) + matfx->fx[i].dual.tex->destroy(); + break; + } + memset(matfx, 0, sizeof(MatFX)); +} + +void +MatFX::setEffects(Material *mat, uint32 type) +{ + MatFX *matfx; + + matfx = MatFX::get(mat); + if(matfx == nil){ + matfx = rwNewT(MatFX, 1, MEMDUR_EVENT | ID_MATFX); + memset(matfx, 0, sizeof(MatFX)); + *PLUGINOFFSET(MatFX*, mat, matFXGlobals.materialOffset) = matfx; + } + + if(matfx->type != 0 && matfx->type != type) + clearMatFX(matfx); + matfx->type = type; + switch(type){ + case BUMPMAP: + case ENVMAP: + case DUAL: + case UVTRANSFORM: + matfx->fx[0].type = type; + matfx->fx[1].type = NOTHING; + break; + + case BUMPENVMAP: + matfx->fx[0].type = BUMPMAP; + matfx->fx[1].type = ENVMAP; + break; + + case DUALUVTRANSFORM: + matfx->fx[0].type = UVTRANSFORM; + matfx->fx[1].type = DUAL; + break; + } +} + +uint32 +MatFX::getEffects(const Material *m) +{ + MatFX *fx = *PLUGINOFFSET(MatFX*, m, matFXGlobals.materialOffset); + if(fx) + return fx->type; + return 0; +} + +MatFX* +MatFX::get(const Material *m) +{ + return *PLUGINOFFSET(MatFX*, m, matFXGlobals.materialOffset); +} + +int32 +MatFX::getEffectIndex(uint32 type) +{ + for(int i = 0; i < 2; i++) + if(this->fx[i].type == type) + return i; + return -1; +} + +void +MatFX::setBumpTexture(Texture *t) +{ + int32 i = this->getEffectIndex(BUMPMAP); + if(i >= 0){ + if(this->fx[i].bump.tex) + this->fx[i].bump.tex->destroy(); + this->fx[i].bump.tex = t; + if(t) + t->addRef(); + } +} + +void +MatFX::setBumpCoefficient(float32 coef) +{ + int32 i = this->getEffectIndex(BUMPMAP); + if(i >= 0) + this->fx[i].bump.coefficient = coef; +} + +Texture* +MatFX::getBumpTexture(void) +{ + int32 i = this->getEffectIndex(BUMPMAP); + if(i >= 0) + return this->fx[i].bump.tex; + return nil; +} + +float32 +MatFX::getBumpCoefficient(void) +{ + int32 i = this->getEffectIndex(BUMPMAP); + if(i >= 0) + return this->fx[i].bump.coefficient; + return 0.0f; +} + +void +MatFX::setEnvTexture(Texture *t) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0){ + if(this->fx[i].env.tex) + this->fx[i].env.tex->destroy(); + this->fx[i].env.tex = t; + if(t) + t->addRef(); + } +} + +void +MatFX::setEnvFrame(Frame *f) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + this->fx[i].env.frame = f; +} + +void +MatFX::setEnvCoefficient(float32 coef) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + this->fx[i].env.coefficient = coef; +} + +void +MatFX::setEnvFBAlpha(bool32 useFBAlpha) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + this->fx[i].env.fbAlpha = useFBAlpha; +} + +Texture* +MatFX::getEnvTexture(void) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + return this->fx[i].env.tex; + return nil; +} + +Frame* +MatFX::getEnvFrame(void) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + return this->fx[i].env.frame; + return nil; +} + +float32 +MatFX::getEnvCoefficient(void) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + return this->fx[i].env.coefficient; + return 0.0f; +} + +bool32 +MatFX::getEnvFBAlpha(void) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + return this->fx[i].env.fbAlpha; + return 0; +} + + +void +MatFX::setDualTexture(Texture *t) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0){ + if(this->fx[i].dual.tex) + this->fx[i].dual.tex->destroy(); + this->fx[i].dual.tex = t; + if(t) + t->addRef(); + } +} + +void +MatFX::setDualSrcBlend(int32 blend) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + this->fx[i].dual.srcBlend = blend; +} + +void +MatFX::setDualDestBlend(int32 blend) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + this->fx[i].dual.dstBlend = blend; +} + +Texture* +MatFX::getDualTexture(void) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + return this->fx[i].dual.tex; + return nil; +} + +int32 +MatFX::getDualSrcBlend(void) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + return this->fx[i].dual.srcBlend; + return 0; +} + +int32 +MatFX::getDualDestBlend(void) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + return this->fx[i].dual.dstBlend; + return 0; +} + +void +MatFX::setUVTransformMatrices(Matrix *base, Matrix *dual) +{ + int32 i = this->getEffectIndex(UVTRANSFORM); + if(i >= 0){ + this->fx[i].uvtransform.baseTransform = base; + this->fx[i].uvtransform.dualTransform = dual; + } +} + +void +MatFX::getUVTransformMatrices(Matrix **base, Matrix **dual) +{ + int32 i = this->getEffectIndex(UVTRANSFORM); + if(i >= 0){ + if(base) *base = this->fx[i].uvtransform.baseTransform; + if(dual) *dual = this->fx[i].uvtransform.dualTransform; + return; + } + if(base) *base = nil; + if(dual) *dual = nil; +} + +static void* +createMaterialMatFX(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(MatFX*, object, offset) = nil; + return object; +} + +static void* +destroyMaterialMatFX(void *object, int32 offset, int32) +{ + MatFX *matfx = *PLUGINOFFSET(MatFX*, object, offset); + if(matfx){ + clearMatFX(matfx); + rwFree(matfx); + } + return object; +} + +static void* +copyMaterialMatFX(void *dst, void *src, int32 offset, int32) +{ + MatFX *srcfx = *PLUGINOFFSET(MatFX*, src, offset); + if(srcfx == nil) + return dst; + MatFX *dstfx = rwNewT(MatFX, 1, MEMDUR_EVENT | ID_MATFX); + *PLUGINOFFSET(MatFX*, dst, offset) = dstfx; + memcpy(dstfx, srcfx, sizeof(MatFX)); + for(int i = 0; i < 2; i++) + switch(dstfx->fx[i].type){ + case MatFX::BUMPMAP: + if(dstfx->fx[i].bump.bumpedTex) + dstfx->fx[i].bump.bumpedTex->addRef(); + if(dstfx->fx[i].bump.tex) + dstfx->fx[i].bump.tex->addRef(); + break; + + case MatFX::ENVMAP: + if(dstfx->fx[i].env.tex) + dstfx->fx[i].env.tex->addRef(); + break; + + case MatFX::DUAL: + if(dstfx->fx[i].dual.tex) + dstfx->fx[i].dual.tex->addRef(); + break; + } + return dst; +} + +static Stream* +readMaterialMatFX(Stream *stream, int32, void *object, int32 offset, int32) +{ + Material *mat; + MatFX *matfx; + Texture *tex, *bumpedTex; + float coefficient; + int32 fbAlpha; + int32 srcBlend, dstBlend; + int32 idx; + + mat = (Material*)object; + MatFX::setEffects(mat, stream->readU32()); + matfx = MatFX::get(mat); + + for(int i = 0; i < 2; i++){ + uint32 type = stream->readU32(); + switch(type){ + case MatFX::BUMPMAP: + coefficient = stream->readF32(); + bumpedTex = tex = nil; + if(stream->readI32()){ + if(!findChunk(stream, ID_TEXTURE, + nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + return nil; + } + bumpedTex = Texture::streamRead(stream); + } + if(stream->readI32()){ + if(!findChunk(stream, ID_TEXTURE, + nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + return nil; + } + tex = Texture::streamRead(stream); + } + idx = matfx->getEffectIndex(type); + assert(idx >= 0); + matfx->fx[idx].bump.bumpedTex = bumpedTex; + matfx->fx[idx].bump.tex = tex; + matfx->fx[idx].bump.coefficient = coefficient; + break; + + case MatFX::ENVMAP: + coefficient = stream->readF32(); + fbAlpha = stream->readI32(); + tex = nil; + if(stream->readI32()){ + if(!findChunk(stream, ID_TEXTURE, + nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + return nil; + } + tex = Texture::streamRead(stream); + } + idx = matfx->getEffectIndex(type); + assert(idx >= 0); + matfx->fx[idx].env.tex = tex; + matfx->fx[idx].env.fbAlpha = fbAlpha; + matfx->fx[idx].env.coefficient = coefficient; + break; + + case MatFX::DUAL: + srcBlend = stream->readI32(); + dstBlend = stream->readI32(); + tex = nil; + if(stream->readI32()){ + if(!findChunk(stream, ID_TEXTURE, + nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + return nil; + } + tex = Texture::streamRead(stream); + } + idx = matfx->getEffectIndex(type); + assert(idx >= 0); + matfx->fx[idx].dual.tex = tex; + matfx->fx[idx].dual.srcBlend = srcBlend; + matfx->fx[idx].dual.dstBlend = dstBlend; + break; + } + } + return stream; +} + +static Stream* +writeMaterialMatFX(Stream *stream, int32, void *object, int32 offset, int32) +{ + MatFX *matfx = *PLUGINOFFSET(MatFX*, object, offset); + + stream->writeU32(matfx->type); + for(int i = 0; i < 2; i++){ + stream->writeU32(matfx->fx[i].type); + switch(matfx->fx[i].type){ + case MatFX::BUMPMAP: + stream->writeF32(matfx->fx[i].bump.coefficient); + stream->writeI32(matfx->fx[i].bump.bumpedTex != nil); + if(matfx->fx[i].bump.bumpedTex) + matfx->fx[i].bump.bumpedTex->streamWrite(stream); + stream->writeI32(matfx->fx[i].bump.tex != nil); + if(matfx->fx[i].bump.tex) + matfx->fx[i].bump.tex->streamWrite(stream); + break; + + case MatFX::ENVMAP: + stream->writeF32(matfx->fx[i].env.coefficient); + stream->writeI32(matfx->fx[i].env.fbAlpha); + stream->writeI32(matfx->fx[i].env.tex != nil); + if(matfx->fx[i].env.tex) + matfx->fx[i].env.tex->streamWrite(stream); + break; + + case MatFX::DUAL: + stream->writeI32(matfx->fx[i].dual.srcBlend); + stream->writeI32(matfx->fx[i].dual.dstBlend); + stream->writeI32(matfx->fx[i].dual.tex != nil); + if(matfx->fx[i].dual.tex) + matfx->fx[i].dual.tex->streamWrite(stream); + break; + } + } + return stream; +} + +static int32 +getSizeMaterialMatFX(void *object, int32 offset, int32) +{ + MatFX *matfx = *PLUGINOFFSET(MatFX*, object, offset); + if(matfx == nil) + return -1; + int32 size = 4 + 4 + 4; + + for(int i = 0; i < 2; i++){ + switch(matfx->fx[i].type){ + case MatFX::BUMPMAP: + size += 4 + 4 + 4; + if(matfx->fx[i].bump.bumpedTex) + size += 12 + + matfx->fx[i].bump.bumpedTex->streamGetSize(); + if(matfx->fx[i].bump.tex) + size += 12 + + matfx->fx[i].bump.tex->streamGetSize(); + break; + + case MatFX::ENVMAP: + size += 4 + 4 + 4; + if(matfx->fx[i].env.tex) + size += 12 + + matfx->fx[i].env.tex->streamGetSize(); + break; + + case MatFX::DUAL: + size += 4 + 4 + 4; + if(matfx->fx[i].dual.tex) + size += 12 + + matfx->fx[i].dual.tex->streamGetSize(); + break; + } + } + return size; +} + +void +MatFX::enableEffects(Atomic *atomic) +{ + *PLUGINOFFSET(int32, atomic, matFXGlobals.atomicOffset) = 1; + atomic->pipeline = matFXGlobals.pipelines[rw::platform]; +} + +// This prevents setting the pipeline on clone +void +MatFX::disableEffects(Atomic *atomic) +{ + *PLUGINOFFSET(int32, atomic, matFXGlobals.atomicOffset) = 0; +} + +bool32 +MatFX::getEffects(Atomic *atomic) +{ + return *PLUGINOFFSET(int32, atomic, matFXGlobals.atomicOffset); +} + +static void* +matfxOpen(void *o, int32, int32) +{ + // init dummy pipelines + matFXGlobals.dummypipe = ObjPipeline::create(); + matFXGlobals.dummypipe->pluginID = 0; //ID_MATFX; + matFXGlobals.dummypipe->pluginData = 0; + for(uint i = 0; i < nelem(matFXGlobals.pipelines); i++) + matFXGlobals.pipelines[i] = matFXGlobals.dummypipe; + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + for(uint i = 0; i < nelem(matFXGlobals.pipelines); i++) + if(matFXGlobals.pipelines[i] == matFXGlobals.dummypipe) + matFXGlobals.pipelines[i] = nil; + matFXGlobals.dummypipe->destroy(); + matFXGlobals.dummypipe = nil; + return o; +} + +void +registerMatFXPlugin(void) +{ + Driver::registerPlugin(PLATFORM_NULL, 0, ID_MATFX, + matfxOpen, matfxClose); + ps2::initMatFX(); + xbox::initMatFX(); + d3d8::initMatFX(); + d3d9::initMatFX(); + wdgl::initMatFX(); + gl3::initMatFX(); + + matFXGlobals.atomicOffset = + Atomic::registerPlugin(sizeof(int32), ID_MATFX, + createAtomicMatFX, nil, copyAtomicMatFX); + Atomic::registerPluginStream(ID_MATFX, + readAtomicMatFX, + writeAtomicMatFX, + getSizeAtomicMatFX); + + matFXGlobals.materialOffset = + Material::registerPlugin(sizeof(MatFX*), ID_MATFX, + createMaterialMatFX, destroyMaterialMatFX, + copyMaterialMatFX); + Material::registerPluginStream(ID_MATFX, + readMaterialMatFX, + writeMaterialMatFX, + getSizeMaterialMatFX); +} + +} diff --git a/vendor/librw/src/pipeline.cpp b/vendor/librw/src/pipeline.cpp new file mode 100644 index 0000000..6284f68 --- /dev/null +++ b/vendor/librw/src/pipeline.cpp @@ -0,0 +1,198 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define COLOR_ARGB(a,r,g,b) \ + ((uint32)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff))) + +namespace rw { + +static void nothing(ObjPipeline *, Atomic*) {} + +void +ObjPipeline::init(uint32 platform) +{ + Pipeline::init(platform); + this->impl.instance = nothing; + this->impl.uninstance = nothing; + this->impl.render = nothing; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(PLATFORM_NULL); + return pipe; +} + +void +ObjPipeline::destroy(void) +{ + rwFree(this); +} + +// helper functions + +void +findMinVertAndNumVertices(uint16 *indices, uint32 numIndices, uint32 *minVert, int32 *numVertices) +{ + uint32 min = 0xFFFFFFFF; + uint32 max = 0; + while(numIndices--){ + if(*indices < min) + min = *indices; + if(*indices > max) + max = *indices; + indices++; + } + uint32 num = max - min + 1; + // if mesh is empty, this can happen + if(min > max){ + min = 0; + num = 0; + } + if(minVert) + *minVert = min; + if(numVertices) + *numVertices = num; +} + +void +instV4d(int type, uint8 *dst, V4d *src, uint32 numVertices, uint32 stride) +{ + if(type == VERT_FLOAT4) + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 16); + dst += stride; + src++; + } + else + assert(0 && "unsupported instV4d type"); +} + +void +instV3d(int type, uint8 *dst, V3d *src, uint32 numVertices, uint32 stride) +{ + if(type == VERT_FLOAT3) + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 12); + dst += stride; + src++; + } + else if(type == VERT_COMPNORM) + for(uint32 i = 0; i < numVertices; i++){ + uint32 n = ((((uint32)(src->z * 511.0f)) & 0x3ff) << 22) | + ((((uint32)(src->y * 1023.0f)) & 0x7ff) << 11) | + ((((uint32)(src->x * 1023.0f)) & 0x7ff) << 0); + *(uint32*)dst = n; + dst += stride; + src++; + } + else + assert(0 && "unsupported instV3d type"); +} + +void +uninstV3d(int type, V3d *dst, uint8 *src, uint32 numVertices, uint32 stride) +{ + if(type == VERT_FLOAT3) + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 12); + src += stride; + dst++; + } + else if(type == VERT_COMPNORM) + for(uint32 i = 0; i < numVertices; i++){ + uint32 n = *(uint32*)src; + int32 normal[3]; + normal[0] = n & 0x7FF; + normal[1] = (n >> 11) & 0x7FF; + normal[2] = (n >> 22) & 0x3FF; + // sign extend + if(normal[0] & 0x400) normal[0] |= ~0x7FF; + if(normal[1] & 0x400) normal[1] |= ~0x7FF; + if(normal[2] & 0x200) normal[2] |= ~0x3FF; + dst->x = normal[0] / 1023.0f; + dst->y = normal[1] / 1023.0f; + dst->z = normal[2] / 511.0f; + src += stride; + dst++; + } + else + assert(0 && "unsupported uninstV3d type"); +} + +void +instTexCoords(int type, uint8 *dst, TexCoords *src, uint32 numVertices, uint32 stride) +{ + assert(type == VERT_FLOAT2); + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 8); + dst += stride; + src++; + } +} + +void +uninstTexCoords(int type, TexCoords *dst, uint8 *src, uint32 numVertices, uint32 stride) +{ + assert(type == VERT_FLOAT2); + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 8); + src += stride; + dst++; + } +} + +bool32 +instColor(int type, uint8 *dst, RGBA *src, uint32 numVertices, uint32 stride) +{ + uint8 alpha = 0xFF; + if(type == VERT_ARGB){ + for(uint32 i = 0; i < numVertices; i++){ + dst[0] = src->blue; + dst[1] = src->green; + dst[2] = src->red; + dst[3] = src->alpha; + alpha &= src->alpha; + dst += stride; + src++; + } + }else if(type == VERT_RGBA){ + for(uint32 i = 0; i < numVertices; i++){ + dst[0] = src->red; + dst[1] = src->green; + dst[2] = src->blue; + dst[3] = src->alpha; + alpha &= src->alpha; + dst += stride; + src++; + } + }else + assert(0 && "unsupported color type"); + return alpha != 0xFF; +} + +void +uninstColor(int type, RGBA *dst, uint8 *src, uint32 numVertices, uint32 stride) +{ + assert(type == VERT_ARGB); + for(uint32 i = 0; i < numVertices; i++){ + dst->red = src[2]; + dst->green = src[1]; + dst->blue = src[0]; + dst->alpha = src[3]; + src += stride; + dst++; + } +} + +} diff --git a/vendor/librw/src/plg.cpp b/vendor/librw/src/plg.cpp new file mode 100644 index 0000000..c5c3782 --- /dev/null +++ b/vendor/librw/src/plg.cpp @@ -0,0 +1,246 @@ +#include +#include +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +namespace rw { + +static void *defCtor(void *object, int32, int32) { return object; } +static void *defDtor(void *object, int32, int32) { return object; } +static void *defCopy(void *dst, void*, int32, int32) { return dst; } + +static LinkList allPlugins; + +#define PLG(lnk) LLLinkGetData(lnk, Plugin, inParentList) + +void +PluginList::open(void) +{ + allPlugins.init(); +} + +void +PluginList::close(void) +{ + PluginList *l; + Plugin *p; + FORLIST(lnk, allPlugins){ + p = LLLinkGetData(lnk, Plugin, inGlobalList); + l = p->parentList; + p->inParentList.remove(); + p->inGlobalList.remove(); + rwFree(p); + if(l->plugins.isEmpty()) + l->size = l->defaultSize; + } + assert(allPlugins.isEmpty()); +} + +void +PluginList::construct(void *object) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + p->constructor(object, p->offset, p->size); + } +} + +void +PluginList::destruct(void *object) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + p->destructor(object, p->offset, p->size); + } +} + +void +PluginList::copy(void *dst, void *src) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + p->copy(dst, src, p->offset, p->size); + } +} + +bool +PluginList::streamRead(Stream *stream, void *object) +{ + int32 length; + ChunkHeaderInfo header; + if(!findChunk(stream, ID_EXTENSION, (uint32*)&length, nil)) + return false; + while(length > 0){ + if(!readChunkHeaderInfo(stream, &header)) + return false; + length -= 12; + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == header.type && p->read){ + p->read(stream, header.length, + object, p->offset, p->size); + goto cont; + } + } + stream->seek(header.length); +cont: + length -= header.length; + } + + // now the always callbacks + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->alwaysCallback) + p->alwaysCallback(object, p->offset, p->size); + } + return true; +} + +void +PluginList::streamWrite(Stream *stream, void *object) +{ + int size = this->streamGetSize(object); + writeChunkHeader(stream, ID_EXTENSION, size); + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->getSize == nil || + (size = p->getSize(object, p->offset, p->size)) <= 0) + continue; + writeChunkHeader(stream, p->id, size); + p->write(stream, size, object, p->offset, p->size); + } +} + +int +PluginList::streamGetSize(void *object) +{ + int32 size = 0; + int32 plgsize; + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->getSize && + (plgsize = p->getSize(object, p->offset, p->size)) > 0) + size += 12 + plgsize; + } + return size; +} + +void +PluginList::streamSkip(Stream *stream) +{ + int32 length; + ChunkHeaderInfo header; + if(!findChunk(stream, ID_EXTENSION, (uint32*)&length, nil)) + return; + while(length > 0){ + if(!readChunkHeaderInfo(stream, &header)) + return; + stream->seek(header.length); + length -= 12 + header.length; + } +} + +void +PluginList::assertRights(void *object, uint32 pluginID, uint32 data) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == pluginID){ + if(p->rightsCallback) + p->rightsCallback(object, + p->offset, p->size, data); + return; + } + } +} + + +int32 +PluginList::registerPlugin(int32 size, uint32 id, + Constructor ctor, Destructor dtor, CopyConstructor copy) +{ + Plugin *p = (Plugin*)rwMalloc(sizeof(Plugin), MEMDUR_GLOBAL); + p->offset = this->size; + this->size += size; + int32 round = sizeof(void*)-1; + this->size = (this->size + round)&~round; + + p->size = size; + p->id = id; + p->constructor = ctor ? ctor : defCtor; + p->destructor = dtor ? dtor : defDtor; + p->copy = copy ? copy : defCopy; + p->read = nil; + p->write = nil; + p->getSize = nil; + p->rightsCallback = nil; + p->alwaysCallback = nil; + p->parentList = this; + this->plugins.add(&p->inParentList); + allPlugins.add(&p->inGlobalList); + return p->offset; +} + +int32 +PluginList::registerStream(uint32 id, + StreamRead read, StreamWrite write, StreamGetSize getSize) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == id){ + p->read = read; + p->write = write; + p->getSize = getSize; + return p->offset; + } + } + return -1; +} + +int32 +PluginList::setStreamRightsCallback(uint32 id, RightsCallback cb) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == id){ + p->rightsCallback = cb; + return p->offset; + } + } + return -1; +} + +int32 +PluginList::setStreamAlwaysCallback(uint32 id, AlwaysCallback cb) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == id){ + p->alwaysCallback = cb; + return p->offset; + } + } + return -1; +} + +int32 +PluginList::getPluginOffset(uint32 id) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == id) + return p->offset; + } + return -1; +} + +} diff --git a/vendor/librw/src/png.cpp b/vendor/librw/src/png.cpp new file mode 100644 index 0000000..45aa102 --- /dev/null +++ b/vendor/librw/src/png.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#include "lodepng/lodepng.h" + +#define PLUGIN_ID 0 + +namespace rw { + + +Image* +readPNG(const char *filename) +{ + Image *image = nil; + uint32 length; + uint8 *data = getFileContents(filename, &length); + assert(data != nil); + + LodePNGState state; + lodepng_state_init(&state); + uint8 *raw = nil; + uint32 w, h; + + // First try: decode without conversion to see if we understand the format + state.decoder.color_convert = 0; + uint32 error = lodepng_decode(&raw, &w, &h, &state, data, length); + if(error){ + RWERROR((ERR_GENERAL, lodepng_error_text(error))); + return nil; + } + + if(state.info_raw.bitdepth == 4 && state.info_raw.colortype == LCT_PALETTE){ + image = Image::create(w, h, 4); + image->allocate(); + memcpy(image->palette, state.info_raw.palette, state.info_raw.palettesize*4); + expandPal4_BE(image->pixels, image->stride, raw, w/2, w, h); + }else if(state.info_raw.bitdepth == 8){ + switch(state.info_raw.colortype){ + case LCT_PALETTE: + image = Image::create(w, h, state.info_raw.palettesize <= 16 ? 4 : 8); + image->allocate(); + memcpy(image->palette, state.info_raw.palette, state.info_raw.palettesize*4); + memcpy(image->pixels, raw, w*h); + break; + case LCT_RGB: + image = Image::create(w, h, 24); + image->allocate(); + memcpy(image->pixels, raw, w*h*3); + break; + default: + // Second try: just load as 32 bit + free(raw); + lodepng_state_init(&state); + error = lodepng_decode(&raw, &w, &h, &state, data, length); + if(error){ + RWERROR((ERR_GENERAL, lodepng_error_text(error))); + return nil; + } + // fall through + case LCT_RGBA: + image = Image::create(w, h, 32); + image->allocate(); + memcpy(image->pixels, raw, w*h*4); + break; + } + } + + free(raw); // TODO: maybe override lodepng allocator + + return image; +} + +void +writePNG(Image *image, const char *filename) +{ + int32 i; + StreamFile file; + if(!file.open(filename, "wb")){ + RWERROR((ERR_FILE, filename)); + return; + } + + size_t rawsize; + uint8 *raw = nil; + uint8 *pixels; + LodePNGState state; + lodepng_state_init(&state); + + pixels = image->pixels; + switch(image->depth){ + case 4: + state.info_raw.bitdepth = 4; + state.info_raw.colortype = LCT_PALETTE; + state.info_png.color.bitdepth = 4; + state.info_png.color.colortype = LCT_PALETTE; + state.encoder.auto_convert = 0; + for(i = 0; i < (1<depth); i++){ + uint8 *col = &image->palette[i*4]; + lodepng_palette_add(&state.info_png.color, col[0], col[1], col[2], col[3]); + lodepng_palette_add(&state.info_raw, col[0], col[1], col[2], col[3]); + } + pixels = rwNewT(uint8, image->width/2*image->height, ID_IMAGE | MEMDUR_FUNCTION); + compressPal4_BE(pixels, image->width/2, image->pixels, image->width, image->width, image->height); + break; + case 8: + state.info_raw.colortype = LCT_PALETTE; + state.info_png.color.colortype = LCT_PALETTE; + state.encoder.auto_convert = 0; + for(i = 0; i < (1<depth); i++){ + uint8 *col = &image->palette[i*4]; + lodepng_palette_add(&state.info_png.color, col[0], col[1], col[2], col[3]); + lodepng_palette_add(&state.info_raw, col[0], col[1], col[2], col[3]); + } + break; + case 16: + // Don't think we can have 16 bits with PNG + // TODO: don't change original image + image->convertTo32(); + break; + case 24: + state.info_raw.colortype = LCT_RGB; + state.info_png.color.colortype = LCT_RGB; + state.encoder.auto_convert = 0; + break; + case 32: + // already done + break; + } + + uint32 error = lodepng_encode(&raw, &rawsize, pixels, image->width, image->height, &state); + if(error){ + RWERROR((ERR_GENERAL, lodepng_error_text(error))); + return; + } + if(pixels != image->pixels) + rwFree(pixels); + file.write8(raw, rawsize); + file.close(); + free(raw); +} + +} diff --git a/vendor/librw/src/prim.cpp b/vendor/librw/src/prim.cpp new file mode 100644 index 0000000..eb353d9 --- /dev/null +++ b/vendor/librw/src/prim.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 0 + +namespace rw { + + +void +BBox::initialize(V3d *point) +{ + this->inf = *point; + this->sup = *point; +} + +void +BBox::addPoint(V3d *point) +{ + if(point->x < this->inf.x) + this->inf.x = point->x; + if(point->y < this->inf.y) + this->inf.y = point->y; + if(point->z < this->inf.z) + this->inf.z = point->z; + if(point->x > this->sup.x) + this->sup.x = point->x; + if(point->y > this->sup.y) + this->sup.y = point->y; + if(point->z > this->sup.z) + this->sup.z = point->z; +} + +void +BBox::calculate(V3d *points, int32 n) +{ + this->inf = points[0]; + this->sup = points[0]; + while(--n){ + points++; + if(points->x < this->inf.x) + this->inf.x = points->x; + if(points->y < this->inf.y) + this->inf.y = points->y; + if(points->z < this->inf.z) + this->inf.z = points->z; + if(points->x > this->sup.x) + this->sup.x = points->x; + if(points->y > this->sup.y) + this->sup.y = points->y; + if(points->z > this->sup.z) + this->sup.z = points->z; + } +} + +bool +BBox::containsPoint(V3d *point) +{ + return point->x >= this->inf.x && point->x <= this->sup.x && + point->y >= this->inf.y && point->y <= this->sup.y && + point->z >= this->inf.z && point->z <= this->sup.z; +} + +} diff --git a/vendor/librw/src/ps2/pds.cpp b/vendor/librw/src/ps2/pds.cpp new file mode 100644 index 0000000..afdc6e8 --- /dev/null +++ b/vendor/librw/src/ps2/pds.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "../rwanim.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +namespace rw { +namespace ps2 { + +struct PdsGlobals +{ + Pipeline **pipes; + int32 maxPipes; + int32 numPipes; +}; +static PdsGlobals pdsGlobals; + +Pipeline* +getPDSPipe(uint32 data) +{ + for(int32 i = 0; i < pdsGlobals.numPipes; i++) + if(pdsGlobals.pipes[i]->pluginData == data) + return pdsGlobals.pipes[i]; + return nil; +} + +void +registerPDSPipe(Pipeline *pipe) +{ + if(pdsGlobals.pipes == nil) + pdsGlobals.pipes = rwNewT(Pipeline*, pdsGlobals.maxPipes, MEMDUR_GLOBAL | ID_PDS); + assert(pdsGlobals.numPipes < pdsGlobals.maxPipes); + pdsGlobals.pipes[pdsGlobals.numPipes++] = pipe; +} + +static void +atomicPDSRights(void *object, int32, int32, uint32 data) +{ + Atomic *a = (Atomic*)object; + a->pipeline = (ObjPipeline*)getPDSPipe(data); +// printf("atm pds: %x %x %x\n", data, a->pipeline->pluginID, a->pipeline->pluginData); +} + +static void +materialPDSRights(void *object, int32, int32, uint32 data) +{ + Material *m = (Material*)object; + m->pipeline = (ObjPipeline*)getPDSPipe(data); +// printf("mat pds: %x %x %x\n", data, m->pipeline->pluginID, m->pipeline->pluginData); +} + +static void *pdsOpen(void *object, int32 offset, int32 size) { return object; } +static void* +pdsClose(void *object, int32 offset, int32 size) +{ + // TODO MEMORY: free registered pipelines + rwFree(pdsGlobals.pipes); + return object; +} + +void +registerPDSPlugin(int32 n) +{ + pdsGlobals.maxPipes = n; + pdsGlobals.numPipes = 0; + pdsGlobals.pipes = nil; + Engine::registerPlugin(0, ID_PDS, pdsOpen, pdsClose); + Atomic::registerPlugin(0, ID_PDS, nil, nil, nil); + Atomic::setStreamRightsCallback(ID_PDS, atomicPDSRights); + + Material::registerPlugin(0, ID_PDS, nil, nil, nil); + Material::setStreamRightsCallback(ID_PDS, materialPDSRights); +} + +void +registerPluginPDSPipes(void) +{ + // TODO: how do we destroy them? + + // rwPDS_G3_Skin_GrpMatPipeID + MatPipeline *pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x11001; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + pipe->attribs[AT_NORMAL+1] = &attribWeights; + uint32 vertCount = MatPipeline::getVertCount(VU_Lights-0x100, 5, 3, 2); + pipe->setTriBufferSizes(5, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = genericUninstanceCB; + pipe->preUninstCB = skinPreCB; + pipe->postUninstCB = skinPostCB; + registerPDSPipe(pipe); + + // rwPDS_G3_Skin_GrpAtmPipeID + ObjPipeline *opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x11002; + opipe->groupPipeline = pipe; + registerPDSPipe(opipe); + + // rwPDS_G3_MatfxUV1_GrpMatPipeID + pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x1100b; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + vertCount = MatPipeline::getVertCount(0x3C5, 4, 3, 3); + pipe->setTriBufferSizes(4, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->uninstanceCB = genericUninstanceCB; + registerPDSPipe(pipe); + + // rwPDS_G3_MatfxUV1_GrpAtmPipeID + opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x1100d; + opipe->groupPipeline = pipe; + registerPDSPipe(opipe); + + // rwPDS_G3_MatfxUV2_GrpMatPipeID + pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x1100c; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV2; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + vertCount = MatPipeline::getVertCount(0x3C5, 4, 3, 3); + pipe->setTriBufferSizes(4, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->uninstanceCB = genericUninstanceCB; + registerPDSPipe(pipe); + + // rwPDS_G3_MatfxUV2_GrpAtmPipeID + opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x1100e; + opipe->groupPipeline = pipe; + registerPDSPipe(opipe); + + // RW World plugin + + // rwPDS_G3x_Generic_AtmPipeID + opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x50001; + registerPDSPipe(opipe); + + // rwPDS_G3x_Skin_AtmPipeID + opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x5000b; + registerPDSPipe(opipe); + + // rwPDS_G3xd_A4D_MatPipeID + pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x5002f; + pipe->attribs[0] = &attribXYZW; + pipe->attribs[1] = &attribUV; + pipe->attribs[2] = &attribNormal; + vertCount = 0x50; + pipe->setTriBufferSizes(3, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; // 0xF0 + pipe->uninstanceCB = genericUninstanceCB; + pipe->preUninstCB = genericPreCB; + registerPDSPipe(pipe); + + // rwPDS_G3xd_A4DSkin_MatPipeID + pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x5003e; + pipe->attribs[0] = &attribXYZW; + pipe->attribs[1] = &attribUV; + pipe->attribs[2] = &attribNormal; + pipe->attribs[3] = &attribWeights; + vertCount = 0x30; + pipe->setTriBufferSizes(4, vertCount); // 0xC0 + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = genericUninstanceCB; + pipe->preUninstCB = genericPreCB; + pipe->postUninstCB = skinPostCB; + registerPDSPipe(pipe); +} + +} +} diff --git a/vendor/librw/src/ps2/ps2.cpp b/vendor/librw/src/ps2/ps2.cpp new file mode 100644 index 0000000..bd0f54b --- /dev/null +++ b/vendor/librw/src/ps2/ps2.cpp @@ -0,0 +1,1573 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "../rwanim.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +#include "rwps2impl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace ps2 { + +#define ALIGNPTR(p,a) ((uint8*)(((uintptr)(p)+a-1) & ~(uintptr)(a-1))) + +static void* +driverOpen(void *o, int32, int32) +{ + engine->driver[PLATFORM_PS2]->defaultPipeline = makeDefaultPipeline(); + + engine->driver[PLATFORM_PS2]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_PS2]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_PS2]->rasterLock = rasterLock; + engine->driver[PLATFORM_PS2]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_PS2]->rasterLockPalette = rasterLockPalette; + engine->driver[PLATFORM_PS2]->rasterUnlockPalette = rasterUnlockPalette; + engine->driver[PLATFORM_PS2]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_PS2]->imageFindRasterFormat = imageFindRasterFormat; + engine->driver[PLATFORM_PS2]->rasterFromImage = rasterFromImage; + engine->driver[PLATFORM_PS2]->rasterToImage = rasterToImage; + + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_PS2, 0, PLATFORM_PS2, + driverOpen, driverClose); + + registerNativeRaster(); +} + +ObjPipeline *defaultObjPipe; +MatPipeline *defaultMatPipe; + +void* +destroyNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_PS2) + return object; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + for(uint32 i = 0; i < header->numMeshes; i++) + rwFree(header->instanceMeshes[i].dataRaw); + rwFree(header->instanceMeshes); + rwFree(header); + geometry->instData = nil; + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_PS2){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_PS2; + assert(geometry->meshHeader != nil); + header->numMeshes = geometry->meshHeader->numMeshes; + header->instanceMeshes = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + Mesh *m = geometry->meshHeader->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + InstanceData *instance = &header->instanceMeshes[i]; + uint32 buf[2]; + stream->read32(buf, 8); + instance->dataSize = buf[0]; + instance->dataRaw = rwNewT(uint8, instance->dataSize+0x7F, MEMDUR_EVENT | ID_GEOMETRY); + instance->data = ALIGNPTR(instance->dataRaw, 0x80); +#ifdef RW_PS2 + uint32 a = (uint32)instance->data; + assert(a % 0x10 == 0); +#endif + stream->read8(instance->data, instance->dataSize); +#ifdef RW_PS2 + if(!buf[1]) + fixDmaOffsets(instance); +#endif + instance->material = m->material; +// sizedebug(instance); + m++; + } + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_STRUCT, len-12); + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_PS2) + return stream; + stream->writeU32(PLATFORM_PS2); + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + for(uint32 i = 0; i < header->numMeshes; i++){ + InstanceData *instance = &header->instanceMeshes[i]; + uint32 buf[2]; + buf[0] = instance->dataSize; + buf[1] = unfixDmaOffsets(instance); + stream->write32(buf, 8); + stream->write8(instance->data, instance->dataSize); +#ifdef RW_PS2 + if(!buf[1]) + fixDmaOffsets(instance); +#endif + } + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + int32 size = 16; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_PS2) + return 0; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + for(uint32 i = 0; i < header->numMeshes; i++){ + InstanceData *instance = &header->instanceMeshes[i]; + size += 8; + size += instance->dataSize; + } + return size; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +// Patch DMA ref ADDR fields to point to the actual data. +#ifdef RW_PS2 +void +fixDmaOffsets(InstanceData *inst) +{ + uint32 base = (uint32)inst->data; + uint32 *tag = (uint32*)inst->data; + for(;;){ + switch(tag[0]&0x70000000){ + // DMAcnt + case 0x10000000: + // no need to fix + tag += (1+(tag[0]&0xFFFF))*4; + break; + + // DMAref + case 0x30000000: + // fix address and jump to next + tag[1] = base + tag[1]<<4; + tag += 4; + break; + + // DMAret + case 0x60000000: + // we're done + return; + + default: + fprintf(stderr, "error: unknown DMAtag %X\n", tag[0]); + return; + } + } +} +#endif + +// Patch DMA ref ADDR fields to qword offsets and return whether +// no ref tags were found. +// Only under RW_PS2 are the addresses actually patched but we need +// the return value for streaming out. +bool32 +unfixDmaOffsets(InstanceData *inst) +{ + bool32 norefs = 1; +#ifdef RW_PS2 + uint32 base = (uint32)inst->data; +#endif + uint32 *tag = (uint32*)inst->data; + for(;;){ + switch(tag[0]&0x70000000){ + // DMAcnt + case 0x10000000: + // no need to unfix + tag += (1+(tag[0]&0xFFFF))*4; + break; + + // DMAref + case 0x30000000: + norefs = 0; + // unfix address and jump to next +#ifdef RW_PS2 + tag[1] = (tag[1] - base)>>4; +#endif + tag += 4; + break; + + // DMAret + case 0x60000000: + return norefs; + + default: + fprintf(stderr, "error: unknown DMAtag %X\n", tag[0]); + return norefs; + } + } +} + +// Pipeline + +PipeAttribute attribXYZ = { + "XYZ", + AT_V3_32 +}; + +PipeAttribute attribXYZW = { + "XYZW", + AT_V4_32 +}; + +PipeAttribute attribUV = { + "UV", + AT_V2_32 +}; + +PipeAttribute attribUV2 = { + "UV2", + AT_V4_32 +}; + +PipeAttribute attribRGBA = { + "RGBA", + AT_V4_8 | AT_UNSGN +}; + +PipeAttribute attribNormal = { + "Normal", + AT_V3_8 // RW has V4_8 but uses V3_8, wtf? +}; + +PipeAttribute attribWeights = { + "Weights", + AT_V4_32 | AT_RW +}; + +static uint32 +attribSize(uint32 unpack) +{ + static uint32 size[] = { 32, 16, 8, 16 }; + return ((unpack>>26 & 3)+1)*size[unpack>>24 & 3]/8; +} + +#define QWC(x) (((x)+0xF)>>4) + +static uint32 +getBatchSize(MatPipeline *pipe, uint32 vertCount) +{ + PipeAttribute *a; + if(vertCount == 0) + return 0; + uint32 size = 1; // ITOP &c. at the end + for(uint i = 0; i < nelem(pipe->attribs); i++) + if((a = pipe->attribs[i]) && (a->attrib & AT_RW) == 0){ + size++; // UNPACK &c. + size += QWC(vertCount*attribSize(a->attrib)); + } + return size; +} + +uint32* +instanceXYZ(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d = (uint32*)g->morphTargets[0].vertices; + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j*3+0]; + *p++ = d[j*3+1]; + *p++ = d[j*3+2]; + } + while((uintptr)p % 0x10) + *p++ = 0; + return p; +} + +uint32* +instanceXYZW(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d = (uint32*)g->morphTargets[0].vertices; + int8 *adcbits = getADCbitsForMesh(g, m); + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j*3+0]; + *p++ = d[j*3+1]; + *p++ = d[j*3+2]; + *p++ = adcbits && adcbits[i] ? 0x8000 : 0; + } + // don't need to pad + return p; +} + +uint32* +instanceUV(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d = (uint32*)g->texCoords[0]; + if((g->flags & Geometry::TEXTURED) || + (g->flags & Geometry::TEXTURED2)) + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j*2+0]; + *p++ = d[j*2+1]; + } + else + for(uint32 i = idx; i < idx+n; i++){ + *p++ = 0; + *p++ = 0; + } + while((uintptr)p % 0x10) + *p++ = 0; + return p; +} + +uint32* +instanceUV2(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d0 = (uint32*)g->texCoords[0]; + uint32 *d1 = (uint32*)g->texCoords[1]; + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + if(g->numTexCoordSets > 0){ + *p++ = d0[j*2+0]; + *p++ = d0[j*2+1]; + }else{ + *p++ = 0; + *p++ = 0; + } + if(g->numTexCoordSets > 1){ + *p++ = d1[j*2+0]; + *p++ = d1[j*2+1]; + }else{ + *p++ = 0; + *p++ = 0; + } + } + while((uintptr)p % 0x10) + *p++ = 0; + return p; +} + +uint32* +instanceRGBA(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d = (uint32*)g->colors; + if((g->flags & Geometry::PRELIT)) + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j]; + } + else + for(uint32 i = idx; i < idx+n; i++) + *p++ = 0xFF000000; + while((uintptr)p % 0x10) + *p++ = 0; + return p; +} + +uint32* +instanceNormal(uint32 *wp, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + V3d *d = g->morphTargets[0].normals; + uint8 *p = (uint8*)wp; + if((g->flags & Geometry::NORMALS)) + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j].x*127.0f; + *p++ = d[j].y*127.0f; + *p++ = d[j].z*127.0f; + } + else + for(uint32 i = idx; i < idx+n; i++){ + *p++ = 0; + *p++ = 0; + *p++ = 0; + } + while((uintptr)p % 0x10) + *p++ = 0; + return (uint32*)p; +} + +void +MatPipeline::init(void) +{ + this->rw::Pipeline::init(PLATFORM_PS2); + for(int i = 0; i < 10; i++) + this->attribs[i] = nil; + this->instanceCB = nil; + this->uninstanceCB = nil; + this->preUninstCB = nil; + this->postUninstCB = nil; +} + +MatPipeline* +MatPipeline::create(void) +{ + MatPipeline *pipe = rwNewT(MatPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +MatPipeline::destroy(void) +{ + rwFree(this); +} + +void +MatPipeline::dump(void) +{ + if(this->platform != PLATFORM_PS2) + return; + PipeAttribute *a; + printf("%x %x\n", this->pluginID, this->pluginData); + for(uint i = 0; i < nelem(this->attribs); i++){ + a = this->attribs[i]; + if(a) + printf("%d %s: %x\n", i, a->name, a->attrib); + } + printf("stride: %x\n", this->inputStride); + printf("vertcount: %x\n", this->vifOffset/this->inputStride); + printf("triSCount: %x\n", this->triStripCount); + printf("triLCount: %x\n", this->triListCount); + printf("vifOffset: %x\n", this->vifOffset); + printf("\n"); +} + +void +MatPipeline::setTriBufferSizes(uint32 inputStride, uint32 bufferSize) +{ + PipeAttribute *a; + + this->inputStride = inputStride; + uint32 numTLtris = bufferSize/3; + this->triListCount = (numTLtris & ~3) * 3; + this->triStripCount = bufferSize & ~3; + for(uint i = 0; i < nelem(this->attribs); i++){ + a = this->attribs[i]; + if(a && a->attrib & AT_RW){ + // broken out attribs have different requirement + // because we have to be able to restart a strip + // at an aligned offset + this->triStripCount = ((bufferSize-2) & ~3)+2; + return; + } + } +} + +// Instance format: +// no broken out clusters +// ====================== +// DMAret [FLUSH; MSKPATH3 || FLUSH; FLUSH] { +// foreach batch { +// foreach cluster { +// MARK/0; STMOD; STCYCL; UNPACK +// unpack-data +// } +// ITOP; MSCALF/MSCNT; // if first/not-first +// 0/FLUSH; 0/MSKPATH3 || 0/FLUSH; 0/FLUSH // if not-last/last +// } +// } +// +// broken out clusters +// =================== +// foreach batch { +// foreach broken out cluster { +// DMAref [STCYCL; UNPACK] -> pointer into unpack-data +// DMAcnt (empty) +// } +// DMAcnt/ret { +// foreach cluster { +// MARK/0; STMOD; STCYCL; UNPACK +// unpack-data +// } +// ITOP; MSCALF/MSCNT; // if first/not-first +// 0/FLUSH; 0/MSKPATH3 || 0/FLUSH; 0/FLUSH // if not-last/last +// } +// } +// unpack-data for broken out clusters + +uint32 markcnt = 0; + +enum { + DMAcnt = 0x10000000, + DMAref = 0x30000000, + DMAret = 0x60000000, + + VIF_NOP = 0, + VIF_STCYCL = 0x01000000, + VIF_STCYCL1 = 0x01000100, // WL = 1 + VIF_OFFSET = 0x02000000, + VIF_BASE = 0x03000000, + VIF_ITOP = 0x04000000, + VIF_STMOD = 0x05000000, + VIF_MSKPATH3 = 0x06000000, + VIF_MARK = 0x07000000, + VIF_FLUSHE = 0x10000000, + VIF_FLUSH = 0x11000000, + VIF_FLUSHA = 0x13000000, + VIF_MSCAL = 0x14000000, + VIF_MSCALF = 0x15000000, + VIF_MSCNT = 0x17000000, + VIF_STMASK = 0x20000000, + VIF_STROW = 0x30000000, + VIF_STCOL = 0x31000000, + VIF_MPG = 0x4A000000, + VIF_DIRECT = 0x50000000, + VIF_DIRECTHL = 0x51000000, + VIF_UNPACK = 0x60000000 // no mode encoded +}; + +struct InstMeshInfo +{ + uint32 numAttribs, numBrokenAttribs; + uint32 batchVertCount, lastBatchVertCount; + uint32 numBatches; + uint32 batchSize, lastBatchSize; + uint32 size; // size of DMA chain without broken out data + uint32 size2; // size of broken out data + uint32 vertexSize; + uint32 attribPos[10]; +}; + +InstMeshInfo +getInstMeshInfo(MatPipeline *pipe, Geometry *g, Mesh *m) +{ + PipeAttribute *a; + InstMeshInfo im; + im.numAttribs = 0; + im.numBrokenAttribs = 0; + im.vertexSize = 0; + for(uint i = 0; i < nelem(pipe->attribs); i++) + if((a = pipe->attribs[i])) { + if(a->attrib & AT_RW) + im.numBrokenAttribs++; + else{ + im.vertexSize += attribSize(a->attrib); + im.numAttribs++; + } + } + if(g->meshHeader->flags == MeshHeader::TRISTRIP){ + im.numBatches = (m->numIndices-2) / (pipe->triStripCount-2); + im.batchVertCount = pipe->triStripCount; + im.lastBatchVertCount = (m->numIndices-2) % (pipe->triStripCount-2); + if(im.lastBatchVertCount){ + im.numBatches++; + im.lastBatchVertCount += 2; + } + }else{ // TRILIST; nothing else supported yet + im.numBatches = (m->numIndices+pipe->triListCount-1) / + pipe->triListCount; + im.batchVertCount = pipe->triListCount; + im.lastBatchVertCount = m->numIndices % pipe->triListCount; + } + if(im.lastBatchVertCount == 0) + im.lastBatchVertCount = im.batchVertCount; + + im.batchSize = getBatchSize(pipe, im.batchVertCount); + im.lastBatchSize = getBatchSize(pipe, im.lastBatchVertCount); + if(im.numBrokenAttribs == 0) + im.size = 1 + im.batchSize*(im.numBatches-1) + im.lastBatchSize; + else + im.size = 2*im.numBrokenAttribs*im.numBatches + + (1+im.batchSize)*(im.numBatches-1) + 1+im.lastBatchSize; + + /* figure out size and addresses of broken out sections */ + im.size2 = 0; + for(uint i = 0; i < nelem(im.attribPos); i++) + if((a = pipe->attribs[i]) && a->attrib & AT_RW){ + im.attribPos[i] = im.size2 + im.size; + im.size2 += QWC(m->numIndices*attribSize(a->attrib)); + } + + return im; +} + +void +MatPipeline::instance(Geometry *g, InstanceData *inst, Mesh *m) +{ + PipeAttribute *a; + InstMeshInfo im = getInstMeshInfo(this, g, m); + + inst->dataSize = (im.size+im.size2)<<4; + // TODO: do this properly, just a test right now + inst->dataSize += 0x7F; + inst->dataRaw = rwNewT(uint8, inst->dataSize, MEMDUR_EVENT | ID_GEOMETRY); + inst->data = ALIGNPTR(inst->dataRaw, 0x80); + + /* make array of addresses of broken out sections */ + uint8 *datap[nelem(this->attribs)]; + uint8 **dp = datap; + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && a->attrib & AT_RW) + dp[i] = inst->data + im.attribPos[i]*0x10; + + // TODO: not sure if this is correct + uint32 msk_flush = rw::version >= 0x35000 ? VIF_FLUSH : VIF_MSKPATH3; + + uint32 idx = 0; + uint32 *p = (uint32*)inst->data; + if(im.numBrokenAttribs == 0){ + *p++ = DMAret | im.size-1; + *p++ = 0; + *p++ = VIF_FLUSH; + *p++ = msk_flush; + } + for(uint32 j = 0; j < im.numBatches; j++){ + uint32 nverts, bsize; + if(j < im.numBatches-1){ + bsize = im.batchSize; + nverts = im.batchVertCount; + }else{ + bsize = im.lastBatchSize; + nverts = im.lastBatchVertCount; + } + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && a->attrib & AT_RW){ + uint32 atsz = attribSize(a->attrib); + *p++ = DMAref | QWC(nverts*atsz); + *p++ = im.attribPos[i]; + *p++ = VIF_STCYCL1 | this->inputStride; + // Round up nverts so UNPACK will fit exactly into the DMA packet + // (can't pad with zeroes in broken out sections). + int num = (QWC(nverts*atsz)<<4)/atsz; + *p++ = (a->attrib&0xFF004000) + | 0x8000 | num << 16 | i; // UNPACK + // This probably shouldn't happen. + if(num*this->inputStride > this->vifOffset) + fprintf(stderr, "WARNING: PS2 instance data over vifOffset %08X, %X-> %X %X\n", + p[-1], num, + num*this->inputStride, this->vifOffset); + + *p++ = DMAcnt; + *p++ = 0x0; + *p++ = VIF_NOP; + *p++ = VIF_NOP; + + im.attribPos[i] += g->meshHeader->flags == 1 ? + QWC((im.batchVertCount-2)*atsz) : + QWC(im.batchVertCount*atsz); + } + if(im.numBrokenAttribs){ + *p++ = (j < im.numBatches-1 ? DMAcnt : DMAret) | bsize; + *p++ = 0x0; + *p++ = VIF_NOP; + *p++ = VIF_NOP; + } + + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && (a->attrib & AT_RW) == 0){ + if(rw::version >= 0x35000) + *p++ = VIF_NOP; + else + *p++ = VIF_MARK | markcnt++; + *p++ = VIF_STMOD; + *p++ = VIF_STCYCL1 | this->inputStride; + *p++ = (a->attrib&0xFF004000) + | 0x8000 | nverts << 16 | i; // UNPACK + + if(a == &attribXYZ) + p = instanceXYZ(p, g, m, idx, nverts); + else if(a == &attribXYZW) + p = instanceXYZW(p, g, m, idx, nverts); + else if(a == &attribUV) + p = instanceUV(p, g, m, idx, nverts); + else if(a == &attribUV2) + p = instanceUV2(p, g, m, idx, nverts); + else if(a == &attribRGBA) + p = instanceRGBA(p, g, m, idx, nverts); + else if(a == &attribNormal) + p = instanceNormal(p, g, m, idx, nverts); + } + idx += g->meshHeader->flags == 1 + ? im.batchVertCount-2 : im.batchVertCount; + + *p++ = VIF_ITOP | nverts; + *p++ = j == 0 ? VIF_MSCALF : VIF_MSCNT; + if(j < im.numBatches-1){ + *p++ = VIF_NOP; + *p++ = VIF_NOP; + }else{ + *p++ = VIF_FLUSH; + *p++ = msk_flush; + } + } + + if(this->instanceCB) + this->instanceCB(this, g, m, datap); +#ifdef RW_PS2 + if(im.numBrokenAttribs) + fixDmaOffsets(inst); +#endif +} + +uint8* +MatPipeline::collectData(Geometry *g, InstanceData *inst, Mesh *m, uint8 *data[]) +{ + PipeAttribute *a; + InstMeshInfo im = getInstMeshInfo(this, g, m); + + uint8 *raw = rwNewT(uint8, im.vertexSize*m->numIndices, MEMDUR_EVENT | ID_GEOMETRY); + uint8 *dp = raw; + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i])) { + if(a->attrib & AT_RW){ + data[i] = inst->data + im.attribPos[i]*0x10; + }else{ + data[i] = dp; + dp += m->numIndices*attribSize(a->attrib); + } + } + + uint8 *datap[nelem(this->attribs)]; + memcpy(datap, data, sizeof(datap)); + + uint32 overlap = g->meshHeader->flags == 1 ? 2 : 0; + uint32 *p = (uint32*)inst->data; + if(im.numBrokenAttribs == 0) + p += 4; + for(uint32 j = 0; j < im.numBatches; j++){ + uint32 nverts = j < im.numBatches-1 ? im.batchVertCount : + im.lastBatchVertCount; + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && a->attrib & AT_RW) + p += 8; + if(im.numBrokenAttribs) + p += 4; + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && (a->attrib & AT_RW) == 0){ + uint32 asz = attribSize(a->attrib); + p += 4; + if((p[-1] & 0xff004000) != a->attrib){ + fprintf(stderr, "unexpected unpack: %08x %08x\n", p[-1], a->attrib); + assert(0 && "unexpected unpack\n"); + } + memcpy(datap[i], p, asz*nverts); + datap[i] += asz*(nverts-overlap); + p += QWC(asz*nverts)*4; + } + p += 4; + } + return raw; +} + +static void +objInstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE + if(geo->instData) + return; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geo->instData = header; + header->platform = PLATFORM_PS2; + assert(geo->meshHeader != nil); + header->numMeshes = geo->meshHeader->numMeshes; + header->instanceMeshes = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->getMeshes()[i]; + InstanceData *instance = &header->instanceMeshes[i]; + + MatPipeline *m; + m = pipe->groupPipeline ? + pipe->groupPipeline : + (MatPipeline*)mesh->material->pipeline; + if(m == nil) + m = defaultMatPipe; + m->instance(geo, instance, mesh); + instance->material = mesh->material; + } +} + +/* +static void +printVertCounts(InstanceData *inst, int flag) +{ + uint32 *d = (uint32*)inst->data; + uint32 id = 0; + if(inst->material->pipeline) + id = inst->material->pipeline->pluginData; + int stride; + if(inst->arePointersFixed){ + d += 4; + while(d[3]&0x60000000){ // skip UNPACKs + stride = d[2]&0xFF; + d += 4 + 4*QWC(attribSize(d[3])*((d[3]>>16)&0xFF)); + } + if(d[2] == 0) + printf("ITOP %x %d (%d) %x\n", *d, stride, flag, id); + }else{ + while((*d&0x70000000) == 0x30000000){ + stride = d[2]&0xFF; + printf("UNPACK %x %d (%d) %x\n", d[3], stride, flag, id); + d += 8; + } + if((*d&0x70000000) == 0x10000000){ + d += (*d&0xFFFF)*4; + printf("ITOP %x %d (%d) %x\n", *d, stride, flag, id); + } + } +} +*/ + +static void +objUninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_PS2); + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + // highest possible number of vertices + geo->numVertices = geo->meshHeader->totalIndices; + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + geo->allocateMeshes(geo->meshHeader->numMeshes, geo->meshHeader->totalIndices, 0); + uint32 *flags = rwNewT(uint32, geo->numVertices, + MEMDUR_FUNCTION | ID_GEOMETRY); + memset(flags, 0, 4*geo->numVertices); + memset(geo->meshHeader->getMeshes()->indices, 0, 2*geo->meshHeader->totalIndices); + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->getMeshes()[i]; + MatPipeline *m; + m = pipe->groupPipeline ? + pipe->groupPipeline : + (MatPipeline*)mesh->material->pipeline; + if(m == nil) m = defaultMatPipe; + if(m->preUninstCB) m->preUninstCB(m, geo); + } + geo->numVertices = 0; + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->getMeshes()[i]; + InstanceData *instance = &header->instanceMeshes[i]; + MatPipeline *m; + m = pipe->groupPipeline ? + pipe->groupPipeline : + (MatPipeline*)mesh->material->pipeline; + if(m == nil) m = defaultMatPipe; + + //printDMAVIF(instance); + uint8 *data[nelem(m->attribs)] = { nil }; + uint8 *raw = m->collectData(geo, instance, mesh, data); + assert(m->uninstanceCB); + m->uninstanceCB(m, geo, flags, mesh, data); + rwFree(raw); + } + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->getMeshes()[i]; + MatPipeline *m; + m = pipe->groupPipeline ? + pipe->groupPipeline : + (MatPipeline*)mesh->material->pipeline; + if(m == nil) m = defaultMatPipe; + if(m->postUninstCB) m->postUninstCB(m, geo); + } + + int8 *bits = getADCbits(geo); + geo->generateTriangles(bits); + rwFree(flags); + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +/* + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->mesh[i]; + InstanceData *instance = &header->instanceMeshes[i]; +// printf("numIndices: %d\n", mesh->numIndices); +// printDMA(instance); + printVertCounts(instance, geo->meshHeader->flags); + } +*/ +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_PS2); + this->groupPipeline = nil; + this->impl.instance = objInstance; + this->impl.uninstance = objUninstance; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +insertVertex(Geometry *geo, int32 i, uint32 mask, Vertex *v) +{ + if(mask & 0x1) + geo->morphTargets[0].vertices[i] = v->p; + if(mask & 0x10) + geo->morphTargets[0].normals[i] = v->n; + if(mask & 0x100) + geo->colors[i] = v->c; + if(mask & 0x1000) + geo->texCoords[0][i] = v->t; + if(mask & 0x2000) + geo->texCoords[1][i] = v->t1; +} + +void +genericPreCB(MatPipeline *pipe, Geometry *geo) +{ + PipeAttribute *a; + for(int32 i = 0; i < (int)nelem(pipe->attribs); i++) + if((a = pipe->attribs[i])) + if(a == &attribXYZW){ + allocateADC(geo); + break; + } + skinPreCB(pipe, geo); +} + +void +genericUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]) +{ + float32 *xyz = nil, *xyzw = nil; + float32 *uv = nil, *uv2 = nil; + uint8 *rgba = nil; + int8 *normals = nil; + uint32 *weights = nil; + int8 *adc = nil; + Skin *skin = nil; + if(skinGlobals.geoOffset) + skin = Skin::get(geo); + + PipeAttribute *a; + for(int32 i = 0; i < (int)nelem(pipe->attribs); i++) + if((a = pipe->attribs[i])){ + if(a == &attribXYZ) xyz = (float32*)data[i]; + else if(a == &attribXYZW) xyzw = (float32*)data[i]; + else if(a == &attribUV) uv = (float32*)data[i]; + else if(a == &attribUV2) uv2 = (float32*)data[i]; + else if(a == &attribRGBA) rgba = data[i]; + else if(a == &attribNormal) normals = (int8*)data[i]; + else if(a == &attribWeights) weights = (uint32*)data[i]; + } + + uint32 mask = 0x1; // vertices + if(normals && geo->flags & Geometry::NORMALS) + mask |= 0x10; + if(rgba && geo->flags & Geometry::PRELIT) + mask |= 0x100; + if((uv || uv2) && geo->numTexCoordSets > 0) + mask |= 0x1000; + if(uv2 && geo->numTexCoordSets > 1) + mask |= 0x2000; + if(weights && skin) + mask |= 0x10000; + if(xyzw) + adc = getADCbitsForMesh(geo, mesh); + + Vertex v; + for(uint32 i = 0; i < mesh->numIndices; i++){ + if(mask & 0x1) + memcpy(&v.p, xyz ? xyz : xyzw, 12); + if(mask & 0x10){ + // TODO: figure out scaling :/ + v.n.x = normals[0]/128.0f; + v.n.y = normals[1]/128.0f; + v.n.z = normals[2]/128.0f; + } + if(mask & 0x100) + memcpy(&v.c, rgba, 4); + if(mask & 0x1000) + memcpy(&v.t, uv ? uv : uv2, 8); + if(mask & 0x2000) + memcpy(&v.t1, uv2 + 2, 8); + if(mask & 0x10000) + for(int j = 0; j < 4; j++){ + ((uint32*)v.w)[j] = weights[j] & ~0x3FF; + v.i[j] = (weights[j] & 0x3FF) >> 2; + if(v.i[j]) v.i[j]--; + if(v.w[j] == 0.0f) v.i[j] = 0; + } + int32 idx = findVertexSkin(geo, flags, mask, &v); + if(idx < 0) + idx = geo->numVertices++; + mesh->indices[i] = idx; + if(adc) + adc[i] = xyzw[3] != 0.0f; + flags[idx] = mask; + insertVertexSkin(geo, idx, mask, &v); + if(xyz) xyz += 3; + if(xyzw) xyzw += 4; + if(uv) uv += 2; + if(uv2) uv2 += 4; + rgba += 4; + normals += 3; + weights += 4; + } +} + +/* +void +defaultUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]) +{ + float32 *verts = (float32*)data[AT_XYZ]; + float32 *texcoords = (float32*)data[AT_UV]; + uint8 *colors = (uint8*)data[AT_RGBA]; + int8 *norms = (int8*)data[AT_NORMAL]; + uint32 mask = 0x1; // vertices + if(geo->flags & Geometry::NORMALS) + mask |= 0x10; + if(geo->flags & Geometry::PRELIT) + mask |= 0x100; + for(int32 i = 0; i < geo->numTexCoordSets; i++) + mask |= 0x1000 << i; + int numUV = pipe->attribs[AT_UV] == &attribUV2 ? 2 : 1; + + Vertex v; + for(uint32 i = 0; i < mesh->numIndices; i++){ + if(mask & 0x1) + memcpy(&v.p, verts, 12); + if(mask & 0x10){ + v.n[0] = norms[0]/127.0f; + v.n[1] = norms[1]/127.0f; + v.n[2] = norms[2]/127.0f; + } + if(mask & 0x100){ + memcpy(&v.c, colors, 4); + //v.c[3] = 0xFF; + } + if(mask & 0x1000) + memcpy(&v.t, texcoords, 8); + if(mask & 0x2000) + memcpy(&v.t1, texcoords+2, 8); + + int32 idx = findVertex(geo, flags, mask, &v); + if(idx < 0) + idx = geo->numVertices++; + mesh->indices[i] = idx; + flags[idx] = mask; + insertVertex(geo, idx, mask, &v); + verts += 3; + texcoords += 2*numUV; + colors += 4; + norms += 3; + } +} +*/ + +#undef QWC + +ObjPipeline* +makeDefaultPipeline(void) +{ + if(defaultMatPipe == nil){ + MatPipeline *pipe = MatPipeline::create(); + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + uint32 vertCount = MatPipeline::getVertCount(VU_Lights,4,3,2); + pipe->setTriBufferSizes(4, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->uninstanceCB = genericUninstanceCB; + defaultMatPipe = pipe; + } + + if(defaultObjPipe == nil){ + ObjPipeline *opipe = ObjPipeline::create(); + defaultObjPipe = opipe; + } + return defaultObjPipe; +} + +// ADC + +int32 adcOffset; + +int8* +getADCbits(Geometry *geo) +{ + int8 *bits = nil; + if(adcOffset){ + ADCData *adc = PLUGINOFFSET(ADCData, geo, adcOffset); + if(adc->adcFormatted) + bits = adc->adcBits; + } + return bits; +} + +int8* +getADCbitsForMesh(Geometry *geo, Mesh *mesh) +{ + int8 *bits = getADCbits(geo); + if(bits == nil) + return nil; + int32 n = mesh - geo->meshHeader->getMeshes(); + for(int32 i = 0; i < n; i++) + bits += geo->meshHeader->getMeshes()[i].numIndices; + return bits; +} + +// TODO +void +convertADC(Geometry*) +{ +} + +// Not optimal but works +void +unconvertADC(Geometry *g) +{ + ADCData *adc = PLUGINOFFSET(ADCData, g, adcOffset); + if(!adc->adcFormatted) + return; + int8 *b = adc->adcBits; + + MeshHeader *oldmh = g->meshHeader; + g->meshHeader = nil; + // Don't allocate indices for now + MeshHeader *newmh = g->allocateMeshes(oldmh->numMeshes, 0, 1); + newmh->flags = oldmh->flags; // should be tristrip + Mesh *oldm = oldmh->getMeshes(); + Mesh *newm = newmh->getMeshes(); + for(int32 i = 0; i < newmh->numMeshes; i++){ + newm->material = oldm->material; + newm->numIndices = oldm->numIndices; + for(uint32 j = 0; j < oldm->numIndices; j++) + if(*b++) + newm->numIndices += 2; + newmh->totalIndices += newm->numIndices; + newm++; + oldm++; + } + // Now re-allocate with indices + newmh = g->allocateMeshes(newmh->numMeshes, newmh->totalIndices, 0); + b = adc->adcBits; + oldm = oldmh->getMeshes(); + newm = newmh->getMeshes(); + for(int32 i = 0; i < newmh->numMeshes; i++){ + int32 n = 0; + for(uint32 j = 0; j < oldm->numIndices; j++){ + if(*b++){ + newm->indices[n++] = oldm->indices[j-1]; + newm->indices[n++] = oldm->indices[j-1]; + } + newm->indices[n++] = oldm->indices[j]; + } + newm++; + oldm++; + } + rwFree(oldmh); + adc->adcFormatted = 0; + rwFree(adc->adcBits); + adc->adcBits = nil; + adc->numBits = 0; +} + +void +allocateADC(Geometry *geo) +{ + ADCData *adc = PLUGINOFFSET(ADCData, geo, adcOffset); + adc->adcFormatted = 1; + adc->numBits = geo->meshHeader->totalIndices; + int32 size = adc->numBits+3 & ~3; + adc->adcBits = rwNewT(int8, size, MEMDUR_EVENT | ID_ADC); + memset(adc->adcBits, 0, size); +} + +static void* +createADC(void *object, int32 offset, int32) +{ + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + adc->adcFormatted = 0; + return object; +} + +static void* +copyADC(void *dst, void *src, int32 offset, int32) +{ + ADCData *dstadc = PLUGINOFFSET(ADCData, dst, offset); + ADCData *srcadc = PLUGINOFFSET(ADCData, src, offset); + dstadc->adcFormatted = srcadc->adcFormatted; + if(!dstadc->adcFormatted) + return dst; + dstadc->numBits = srcadc->numBits; + int32 size = dstadc->numBits+3 & ~3; + dstadc->adcBits = rwNewT(int8, size, MEMDUR_EVENT | ID_ADC); + memcpy(dstadc->adcBits, srcadc->adcBits, size); + return dst; +} + +static void* +destroyADC(void *object, int32 offset, int32) +{ + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + if(adc->adcFormatted) + rwFree(adc->adcBits); + return object; +} + +static Stream* +readADC(Stream *stream, int32, void *object, int32 offset, int32) +{ + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + if(!findChunk(stream, ID_ADC, nil, nil)){ + RWERROR((ERR_CHUNK, "ADC")); + return nil; + } + adc->numBits = stream->readI32(); + adc->adcFormatted = 1; + if(adc->numBits == 0){ + adc->adcBits = nil; + adc->numBits = 0; + return stream; + } + int32 size = adc->numBits+3 & ~3; + adc->adcBits = rwNewT(int8, size, MEMDUR_EVENT | ID_ADC); + stream->read8(adc->adcBits, size); + return stream; +} + +static Stream* +writeADC(Stream *stream, int32 len, void *object, int32 offset, int32) +{ + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_ADC, len-12); + if(geometry->flags & Geometry::NATIVE){ + stream->writeI32(0); + return stream; + } + stream->writeI32(adc->numBits); + int32 size = adc->numBits+3 & ~3; + stream->write8(adc->adcBits, size); + return stream; +} + +static int32 +getSizeADC(void *object, int32 offset, int32) +{ + Geometry *geometry = (Geometry*)object; + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + if(!adc->adcFormatted) + return 0; + if(geometry->flags & Geometry::NATIVE) + return 16; + return 16 + (adc->numBits+3 & ~3); +} + +void +registerADCPlugin(void) +{ + adcOffset = Geometry::registerPlugin(sizeof(ADCData), ID_ADC, + createADC, destroyADC, copyADC); + Geometry::registerPluginStream(ID_ADC, + readADC, + writeADC, + getSizeADC); +} + +// misc stuff + +static uint32 +unpackSize(uint32 unpack) +{ + static uint32 size[] = { 32, 16, 8, 4 }; + return ((unpack>>26 & 3)+1)*size[unpack>>24 & 3]/8; +} + +/* A little dumb VIF interpreter */ +static void +sendVIF(uint32 w) +{ + enum VIFstate { + VST_cmd, + VST_stmask, + VST_strow, + VST_stcol, + VST_mpg, + VST_direct, + VST_unpack + }; +// static uint32 buf[256 * 16]; // maximum unpack size + static VIFstate state = VST_cmd; + static uint32 n; + static uint32 code; + uint32 imm, num; + + imm = w & 0xFFFF; + num = (w>>16) & 0xFF; + switch(state){ + case VST_cmd: + code = w; + if((code & 0x60000000) == VIF_UNPACK){ + printf("\t%08X VIF_UNPACK\n", code); + printf("\t...skipping...\n"); + state = VST_unpack; + n = (unpackSize(code)*num + 3) >> 2; + }else switch(code & 0x7F000000){ + case VIF_NOP: + printf("\t%08X VIF_NOP\n", code); + break; + case VIF_STCYCL: + printf("\t%08X VIF_STCYCL\n", code); + break; + case VIF_OFFSET: + printf("\t%08X VIF_OFFSET\n", code); + break; + case VIF_BASE: + printf("\t%08X VIF_BASE\n", code); + break; + case VIF_ITOP: + printf("\t%08X VIF_ITOP\n", code); + break; + case VIF_STMOD: + printf("\t%08X VIF_STMOD\n", code); + break; + case VIF_MSKPATH3: + printf("\t%08X VIF_MSKPATH3\n", code); + break; + case VIF_MARK: + printf("\t%08X VIF_MARK\n", code); + break; + case VIF_FLUSHE: + printf("\t%08X VIF_FLUSHE\n", code); + break; + case VIF_FLUSH: + printf("\t%08X VIF_FLUSH\n", code); + break; + case VIF_FLUSHA: + printf("\t%08X VIF_FLUSHA\n", code); + break; + case VIF_MSCAL: + printf("\t%08X VIF_MSCAL\n", code); + break; + case VIF_MSCALF: + printf("\t%08X VIF_MSCALF\n", code); + break; + case VIF_MSCNT: + printf("\t%08X VIF_MSCNT\n", code); + break; + case VIF_STMASK: + printf("\t%08X VIF_STMASK\n", code); + printf("\t...skipping...\n"); + state = VST_stmask; + n = 1; + break; + case VIF_STROW: + printf("\t%08X VIF_STROW\n", code); + printf("\t...skipping...\n"); + state = VST_strow; + n = 4; + break; + case VIF_STCOL: + printf("\t%08X VIF_STCOL\n", code); + printf("\t...skipping...\n"); + state = VST_stcol; + n = 4; + break; + case VIF_MPG: + printf("\t%08X VIF_MPG\n", code); + state = VST_mpg; + n = num*2; + break; + case VIF_DIRECT: + printf("\t%08X VIF_DIRECT\n", code); + printf("\t...skipping...\n"); + state = VST_direct; + n = imm*4; + break; + case VIF_DIRECTHL: + printf("\t%08X VIF_DIRECTHL\n", code); + printf("\t...skipping...\n"); + state = VST_direct; + n = imm*4; + break; + default: + printf("\tUnknown VIFcode %08X\n", code); + } + break; + /* TODO: actually do something here */ + case VST_stmask: + n--; + break; + case VST_strow: + n--; + break; + case VST_stcol: + n--; + break; + case VST_mpg: + n--; + break; + case VST_direct: + n--; + break; + case VST_unpack: + n--; + break; + } + if(n == 0) + state = VST_cmd; +} + +static void +dmaVIF(int32 qwc, uint32 *data) +{ + qwc *= 4; + while(qwc--) + sendVIF(*data++); +} + +void +printDMAVIF(InstanceData *inst) +{ + uint32 *tag = (uint32*)inst->data; + uint32 *base = (uint32*)inst->data; + uint32 qwc; + + for(;;){ + qwc = tag[0]&0xFFFF; + switch(tag[0]&0x70000000){ + case DMAcnt: + printf("DMAcnt %04x %08x\n", qwc, tag[1]); + sendVIF(tag[2]); + sendVIF(tag[3]); + dmaVIF(qwc, tag+4); + tag += (1+qwc)*4; + break; + + case DMAref: + printf("DMAref %04x %08x\n", qwc, tag[1]); + sendVIF(tag[2]); + sendVIF(tag[3]); + dmaVIF(qwc, base + tag[1]*4); + tag += 4; + break; + + case DMAret: + printf("DMAret %04x %08x\n", qwc, tag[1]); + sendVIF(tag[2]); + sendVIF(tag[3]); + dmaVIF(qwc, tag+4); + printf("\n"); + return; + } + } +} + +void +printDMA(InstanceData *inst) +{ + uint32 *tag = (uint32*)inst->data; + uint32 qwc; + for(;;){ + qwc = tag[0]&0xFFFF; + switch(tag[0]&0x70000000){ + case DMAcnt: + printf("CNT %04x %08x\n", qwc, tag[1]); + tag += (1+qwc)*4; + break; + + case DMAref: + printf("REF %04x %08x\n", qwc, tag[1]); + tag += 4; + break; + + case DMAret: + printf("RET %04x %08x\n\n", qwc, tag[1]); + return; + } + } +} + +/* +void +sizedebug(InstanceData *inst) +{ + if(inst->arePointersFixed == 2) + return; + uint32 *base = (uint32*)inst->data; + uint32 *tag = (uint32*)inst->data; + uint32 *last = nil; + for(;;){ + switch(tag[0]&0x70000000){ + case DMAcnt: + tag += (1+(tag[0]&0xFFFF))*4; + break; + + case DMAref: + last = base + tag[1]*4 + (tag[0]&0xFFFF)*4; + tag += 4; + break; + + case DMAret: + tag += (1+(tag[0]&0xFFFF))*4; + uint32 diff; + if(!last) + diff = (uint8*)tag - (uint8*)base; + else + diff = (uint8*)last - (uint8*)base; + printf("%x %x %x\n", inst->dataSize-diff, diff, inst->dataSize); + return; + + default: + printf("unkown DMAtag: %X %X\n", tag[0], tag[1]); + break; + } + } +} +*/ + +} +} diff --git a/vendor/librw/src/ps2/ps2device.cpp b/vendor/librw/src/ps2/ps2device.cpp new file mode 100644 index 0000000..98f9914 --- /dev/null +++ b/vendor/librw/src/ps2/ps2device.cpp @@ -0,0 +1,49 @@ +#ifdef RW_PS2 + +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "../rwanim.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +#include "rwps2impl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace ps2 { + +Device renderdevice = { + 16777215.0f, 0.0f, + null::beginUpdate, + null::endUpdate, + null::clearCamera, + null::showRaster, + null::rasterRenderFast, + null::setRenderState, + null::getRenderState, + null::im2DRenderLine, + null::im2DRenderTriangle, + null::im2DRenderPrimitive, + null::im2DRenderIndexedPrimitive, + null::im3DTransform, + null::im3DRenderPrimitive, + null::im3DRenderIndexedPrimitive, + null::im3DEnd, + null::deviceSystem +}; + +} +} + +#endif diff --git a/vendor/librw/src/ps2/ps2matfx.cpp b/vendor/librw/src/ps2/ps2matfx.cpp new file mode 100644 index 0000000..6972a05 --- /dev/null +++ b/vendor/librw/src/ps2/ps2matfx.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +#define PLUGIN_ID ID_MATFX + +namespace rw { +namespace ps2 { + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_PS2] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_PS2])->groupPipeline->destroy(); + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_PS2])->destroy(); + matFXGlobals.pipelines[PLATFORM_PS2] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_PS2, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + MatPipeline *pipe = MatPipeline::create(); + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + uint32 vertCount = MatPipeline::getVertCount(0x3C5, 4, 3, 3); + pipe->setTriBufferSizes(4, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->uninstanceCB = genericUninstanceCB; + + ObjPipeline *opipe = ObjPipeline::create(); + opipe->pluginID = ID_MATFX; + opipe->pluginData = 0; + opipe->groupPipeline = pipe; + return opipe; +} + +} +} diff --git a/vendor/librw/src/ps2/ps2raster.cpp b/vendor/librw/src/ps2/ps2raster.cpp new file mode 100644 index 0000000..2b76b8b --- /dev/null +++ b/vendor/librw/src/ps2/ps2raster.cpp @@ -0,0 +1,2238 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwps2.h" + +#define PLUGIN_ID ID_DRIVER + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) + +namespace rw { +namespace ps2 { + +int32 nativeRasterOffset; + +#define MAXLEVEL(r) ((r)->tex1low >> 2) +static bool32 noNewStyleRasters; + +enum Psm { + PSMCT32 = 0x0, + PSMCT24 = 0x1, + PSMCT16 = 0x2, + PSMCT16S = 0xA, + PSMT8 = 0x13, + PSMT4 = 0x14, + PSMT8H = 0x1B, + PSMT4HL = 0x24, + PSMT4HH = 0x2C, + PSMZ32 = 0x30, + PSMZ24 = 0x31, + PSMZ16 = 0x32, + PSMZ16S = 0x3A +}; + +// i don't really understand this, stolen from RW +static void +transferMinSize(int32 psm, int32 flags, int32 *minw, int32 *minh) +{ + *minh = 1; + switch(psm){ + case PSMCT32: + case PSMZ32: + *minw = 2; // 32 bit + break; + case PSMCT16: + case PSMCT16S: + case PSMZ16: + case PSMZ16S: + *minw = 4; // 16 bit + break; + case PSMCT24: + case PSMT8: + case PSMT4: + case PSMT8H: + case PSMT4HL: + case PSMT4HH: + case PSMZ24: + *minw = 8; // everything else + break; + } + if(flags & 0x2 && psm == PSMT8){ + *minw = 16; + *minh = 4; + } + if(flags & 0x4 && psm == PSMT4){ + *minw = 32; + *minh = 4; + } +} + +#define ALIGN(x,a) ((x) + (a)-1 & ~((a)-1)) +#define ALIGN16(x) ((x) + 0xF & ~0xF) +#define ALIGN64(x) ((x) + 0x3F & ~0x3F) +#define NSIZE(dim,pagedim) (((dim) + (pagedim)-1)/(pagedim)) + +void* +mallocalign(size_t size, int32 alignment) +{ + void *p; + void **pp; + p = rwMalloc(size + alignment + sizeof(void*), MEMDUR_EVENT | ID_RASTERPS2); + if(p == nil) return nil; + pp = (void**)(((uintptr)p + sizeof(void*) + alignment)&~(alignment-1)); + pp[-1] = p; + return (void*)pp; +} + +void +freealign(void *p) +{ + void *pp; + if(p == nil) return; + pp = ((void**)p)[-1]; + rwFree(pp); +} + +// TODO: these depend on video mode, set in deviceSystem! +int32 cameraFormat = Raster::C8888; +int32 cameraDepth = 32; +int32 cameraZDepth = 16; + +int32 defaultMipMapKL = 0xFC0; +int32 maxMipLevels = 7; + +int32 +getRasterFormat(Raster *raster) +{ + int32 palformat, pixelformat, mipmapflags; + pixelformat = raster->format & 0xF00; + palformat = raster->format & 0x6000; + mipmapflags = raster->format & 0x9000; + switch(raster->type){ + case Raster::ZBUFFER: + if(palformat || mipmapflags){ + RWERROR((ERR_INVRASTER)); + return 0; + } + if(raster->depth && raster->depth != cameraZDepth){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = cameraZDepth; + if(pixelformat){ + if((raster->depth == 16 && pixelformat != Raster::D16) || + (raster->depth == 32 && pixelformat != Raster::D32)){ + RWERROR((ERR_INVRASTER)); + return 0; + } + } + pixelformat = raster->depth == 16 ? Raster::D16 : Raster::D32; + raster->format = pixelformat; + break; + case Raster::CAMERA: + if(palformat || mipmapflags){ + RWERROR((ERR_INVRASTER)); + return 0; + } + if(raster->depth && raster->depth != cameraDepth){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = cameraDepth; + if(pixelformat && pixelformat != cameraFormat){ + RWERROR((ERR_INVRASTER)); + return 0; + } + pixelformat = cameraFormat; + raster->format = pixelformat; + break; + case Raster::NORMAL: + case Raster::CAMERATEXTURE: + if(palformat || mipmapflags){ + RWERROR((ERR_INVRASTER)); + return 0; + } + /* fallthrough */ + case Raster::TEXTURE: + // Find raster format by depth if none was given + if(pixelformat == 0) + switch(raster->depth){ + case 4: + pixelformat = Raster::C1555; + palformat = Raster::PAL4; + break; + case 8: + pixelformat = Raster::C1555; + palformat = Raster::PAL8; + break; + case 24: + // unsafe + // pixelformat = Raster::C888; + // palformat = 0; + // break; + case 32: + pixelformat = Raster::C8888; + palformat = 0; + break; + default: + pixelformat = Raster::C1555; + palformat = 0; + break; + } + raster->format = pixelformat | palformat | mipmapflags; + // Sanity check raster format and depth; set depth if none given + if(palformat){ + if(palformat == Raster::PAL8){ + if(raster->depth && raster->depth != 8){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 8; + if(pixelformat != Raster::C1555 && pixelformat != Raster::C8888){ + RWERROR((ERR_INVRASTER)); + return 0; + } + }else if(palformat == Raster::PAL4){ + if(raster->depth && raster->depth != 4){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 4; + if(pixelformat != Raster::C1555 && pixelformat != Raster::C8888){ + RWERROR((ERR_INVRASTER)); + return 0; + } + }else{ + RWERROR((ERR_INVRASTER)); + return 0; + } + }else if(pixelformat == Raster::C1555){ + if(raster->depth && raster->depth != 16){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 16; + }else if(pixelformat == Raster::C8888){ + if(raster->depth && raster->depth != 32){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 32; + }else if(pixelformat == Raster::C888){ + assert(0 && "24 bit rasters not supported"); + if(raster->depth && raster->depth != 24){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 24; + }else{ + RWERROR((ERR_INVRASTER)); + return 0; + } + break; + default: + RWERROR((ERR_INVRASTER)); + return 0; + } + return 1; +} + +/* + * Memory units: + * Column: 64 bytes (single cycle access) + * Block: 256 bytes, 64 words, 4 columns. texture base pointers + * Page: 8 kbytes, 2 kwords, 128 columns, 32 blocks. frame buffer base pointers + * entire memory: 4 mbytes, 64k columns, 16k blocks, 512 pages + * + * PSMT4: 128x128 pixels, 4x8 blocks per page, 32x16 pixels per block + * PSMT8: 128x64 pixels, 8x4 blocks per page, 16x16 pixels per block + * PSMCT16(S): 64x64 pixels, 4x8 blocks per page, 16x8 pixels per block + * PSMCT24: 64x32 pixels, 8x4 blocks per page, 8x8 pixels per block + * PSMCT32: 64x32 pixels, 8x4 blocks per page, 8x8 pixels per block + * + * Layout of blocks in page: + * + * PSMCT24, PSMCT32, PSMT8 + * +----+----+----+----+----+----+----+----+ + * | 0 | 1 | 4 | 5 | 16 | 17 | 20 | 21 | + * +----+----+----+----+----+----+----+----+ + * | 2 | 3 | 6 | 7 | 18 | 19 | 22 | 23 | + * +----+----+----+----+----+----+----+----+ + * | 8 | 9 | 12 | 13 | 24 | 25 | 28 | 29 | + * +----+----+----+----+----+----+----+----+ + * | 10 | 11 | 14 | 15 | 26 | 27 | 30 | 31 | + * +----+----+----+----+----+----+----+----+ + * + * PSMCT16, PSMT4 + * +----+----+----+----+ + * | 0 | 2 | 8 | 10 | + * +----+----+----+----+ + * | 1 | 3 | 9 | 11 | + * +----+----+----+----+ + * | 4 | 6 | 12 | 14 | + * +----+----+----+----+ + * | 5 | 7 | 13 | 15 | + * +----+----+----+----+ + * | 16 | 18 | 24 | 26 | + * +----+----+----+----+ + * | 17 | 19 | 25 | 27 | + * +----+----+----+----+ + * | 20 | 22 | 28 | 30 | + * +----+----+----+----+ + * | 21 | 23 | 29 | 31 | + * +----+----+----+----+ + * + * PSMCT16S + * +----+----+----+----+ + * | 0 | 2 | 16 | 18 | + * +----+----+----+----+ + * | 1 | 3 | 17 | 19 | + * +----+----+----+----+ + * | 8 | 10 | 24 | 26 | + * +----+----+----+----+ + * | 9 | 11 | 25 | 27 | + * +----+----+----+----+ + * | 4 | 6 | 20 | 22 | + * +----+----+----+----+ + * | 5 | 7 | 21 | 23 | + * +----+----+----+----+ + * | 12 | 14 | 28 | 30 | + * +----+----+----+----+ + * | 13 | 15 | 29 | 31 | + * +----+----+----+----+ + * + */ + +static uint8 blockmap_PSMCT32[32] = { + 0, 1, 4, 5, 16, 17, 20, 21, + 2, 3, 6, 7, 18, 19, 22, 23, + 8, 9, 12, 13, 24, 25, 28, 29, + 10, 11, 14, 15, 26, 27, 30, 31, +}; +static uint8 blockmap_PSMCT16[32] = { + 0, 2, 8, 10, + 1, 3, 9, 11, + 4, 6, 12, 14, + 5, 7, 13, 15, + 16, 18, 24, 26, + 17, 19, 25, 27, + 20, 22, 28, 30, + 21, 23, 29, 31, +}; +static uint8 blockmap_PSMCT16S[32] = { + 0, 2, 16, 18, + 1, 3, 17, 19, + 8, 10, 24, 26, + 9, 11, 25, 27, + 4, 6, 20, 22, + 5, 7, 21, 23, + 12, 14, 28, 30, + 13, 15, 29, 31, +}; +static uint8 blockmap_PSMZ32[32] = { + 24, 25, 28, 29, 8, 9, 12, 13, + 26, 27, 30, 31, 10, 11, 14, 15, + 16, 17, 20, 21, 0, 1, 4, 5, + 18, 19, 22, 23, 2, 3, 6, 7, +}; +static uint8 blockmap_PSMZ16[32] = { + 24, 26, 16, 18, + 25, 27, 17, 19, + 28, 30, 20, 22, + 29, 31, 21, 23, + 8, 10, 0, 2, + 9, 11, 1, 3, + 12, 14, 4, 6, + 13, 15, 5, 7, +}; +static uint8 blockmap_PSMZ16S[32] = { + 24, 26, 8, 10, + 25, 27, 9, 11, + 16, 18, 0, 2, + 17, 19, 1, 3, + 28, 30, 12, 14, + 29, 31, 13, 15, + 20, 22, 4, 6, + 21, 23, 5, 7, +}; + +static uint8 blockmaprev_PSMCT32[32] = { + 0, 1, 8, 9, 2, 3, 10, 11, + 16, 17, 24, 25, 18, 19, 26, 27, + 4, 5, 12, 13, 6, 7, 14, 15, + 20, 21, 28, 29, 22, 23, 30, 31, +}; +static uint8 blockmaprev_PSMCT16[32] = { + 0, 4, 1, 5, + 8, 12, 9, 13, + 2, 6, 3, 7, + 10, 14, 11, 15, + 16, 20, 17, 21, + 24, 28, 25, 29, + 18, 22, 19, 23, + 26, 30, 27, 31, +}; + +/* Suffixes used: + * _Px - pixels + * _W - width units (pixels/64) + * _B - blocks + * _P - pages + */ + +/* Layout mipmaps and palette in GS memory */ +static void +calcOffsets(int32 width_Px, int32 height_Px, int32 psm, uint64 *bufferBase_B, uint64 *bufferWidth_W, uint32 *trxpos, uint32 *totalSize, uint32 *paletteBase) +{ + uint32 pageWidth_Px, pageHeight_Px; + uint32 blockWidth_Px, blockHeight_Px; + uint32 mindim_Px; + int32 nlevels; + int32 n; + uint32 mipw_Px, miph_Px; + uint32 lastmipw_Px, lastmiph_Px; + uint32 bufferHeight_P[8]; + uint32 bufferPage_B[8]; // address of page in which the level is allocated + uint32 xoff_Px, yoff_Px; // x/y offset the last level starts at + // Whenever we allocate horizontally inside a page, + // keep track of the region below it on this stack. + uint32 sp; + uint32 xoffstack_Px[8]; // actually unused... + uint32 widthstack_Px[8]; + uint32 heightstack_Px[8]; + uint32 basestack_B[8]; + uint32 flag; + + switch(psm){ + case PSMCT32: + case PSMCT24: + case PSMT8H: + case PSMT4HL: + case PSMT4HH: + case PSMZ32: + case PSMZ24: + pageWidth_Px = 64; + pageHeight_Px = 32; + blockWidth_Px = 8; + blockHeight_Px = 8; + break; + case PSMT8: + pageWidth_Px = 128; + pageHeight_Px = 64; + blockWidth_Px = 16; + blockHeight_Px = 16; + break; + case PSMT4: + pageWidth_Px = 128; + pageHeight_Px = 128; + blockWidth_Px = 32; + blockHeight_Px = 16; + break; + case PSMCT16: + case PSMCT16S: + case PSMZ16: + case PSMZ16S: + default: + pageWidth_Px = 64; + pageHeight_Px = 64; + blockWidth_Px = 16; + blockHeight_Px = 8; + break; + } + + mindim_Px = min(width_Px, height_Px); + for(nlevels = 1; mindim_Px > 8; nlevels++){ + if(nlevels >= maxMipLevels) + break; + mindim_Px /= 2; + } + +#define PAGEWIDTH_B (pageWidth_Px/blockWidth_Px) // number of horizontal blocks per page +#define NBLKX(dim) (NSIZE((dim), blockWidth_Px)) +#define NBLKY(dim) (NSIZE((dim), blockHeight_Px)) +#define NPGX(dim) (NSIZE((dim), pageWidth_Px)) +#define NPGY(dim) (NSIZE((dim), pageHeight_Px)) +#define REALWIDTH(w) (max((w), blockWidth_Px)) +#define REALHEIGHT(w) (max((w), blockHeight_Px)) + + bufferBase_B[0] = 0; + bufferWidth_W[0] = NPGX(width_Px)*pageWidth_Px/64; + bufferHeight_P[0] = NPGY(height_Px); + bufferPage_B[0] = 0; + lastmipw_Px = width_Px; + lastmiph_Px = height_Px; + sp = 0; + xoff_Px = 0; + yoff_Px = 0; + flag = 0; + // Calculate info for all mipmap levels. + // mipwidth/height are actually the dimensions of level n-1! + // This code was reversed from RW and is rather complicated... + // partially because it's not clear what the assumptions are, + // can width/height be non-powers of 2? + for(n = 1; n < nlevels; n++){ + mipw_Px = lastmipw_Px/2; + miph_Px = lastmiph_Px/2; + if(lastmipw_Px >= pageWidth_Px){ + if(lastmiph_Px >= pageHeight_Px){ + // CASE 0 + // We allocate full pages + // This is the only place bufferWidth can change. Similarly bufferBase_2, which is related + bufferBase_B[n] = bufferBase_B[n-1] + (lastmipw_Px/blockWidth_Px)*(lastmiph_Px/blockHeight_Px); + bufferPage_B[n] = bufferBase_B[n]; + bufferWidth_W[n] = NPGX(mipw_Px)*pageWidth_Px/64; + bufferHeight_P[n] = NPGY(miph_Px); + xoff_Px = 0; + yoff_Px = 0; + }else{ + // CASE 1 + // Allocate vertically in the current page + bufferPage_B[n] = bufferPage_B[n-1]; + bufferHeight_P[n] = bufferHeight_P[n-1]; + bufferWidth_W[n] = bufferWidth_W[n-1]; + // How do we know pageHeight - yoff - REALHEIGHT(lastmiph) >= miph? + // And how is this condition ever false? + // Assuming lastmipw >= pageWidth for any number of levels, lastmiph must be pageHeight/2 + // or lower to reach this code. No dimension is lower than 8. Then consequent mipmaps + // will have heights halved but even with PSMT4 we will only (vertically) fill the + // page with the last mipmap and not go beyond... + if(REALHEIGHT(lastmiph_Px) + yoff_Px < pageHeight_Px){ + // CASE 2 + yoff_Px += REALHEIGHT(lastmiph_Px); + bufferBase_B[n] = bufferBase_B[n-1] + + PAGEWIDTH_B * NBLKY(lastmiph_Px) * + bufferWidth_W[n]*64/pageWidth_Px; // number of horizontal pages for level + }else{ + // CASE 3 + // Can this happen? + xoff_Px += REALWIDTH(lastmipw_Px); + bufferBase_B[n] = bufferBase_B[n-1] + NBLKX(lastmipw_Px); + } + } + }else if(lastmiph_Px >= pageHeight_Px){ + // CASE 4 + // Allocate horizontally + bufferPage_B[n] = bufferPage_B[n-1]; + bufferHeight_P[n] = bufferHeight_P[n-1]; + bufferWidth_W[n] = bufferWidth_W[n-1]; + if(REALWIDTH(lastmipw_Px) + xoff_Px < pageWidth_Px){ + // CASE 5 + xoffstack_Px[sp] = xoff_Px; // unused... + heightstack_Px[sp] = REALHEIGHT(lastmiph_Px); + widthstack_Px[sp] = REALWIDTH(lastmipw_Px); + basestack_B[sp] = bufferBase_B[n-1] + NBLKY(lastmiph_Px) * PAGEWIDTH_B; + sp++; + xoff_Px += REALWIDTH(lastmipw_Px); + bufferBase_B[n] = bufferBase_B[n-1] + NBLKX(lastmipw_Px); + }else if(sp){ + // CASE 7 + bufferBase_B[n] = basestack_B[sp-1]; + if(REALWIDTH(mipw_Px) < widthstack_Px[sp-1]){ + // CASE 9 + basestack_B[sp-1] += NBLKX(mipw_Px); + widthstack_Px[sp-1] -= REALWIDTH(mipw_Px); + }else if(REALHEIGHT(miph_Px) < heightstack_Px[sp-1]){ + // CASE 8 + basestack_B[sp-1] += NBLKY(miph_Px) * PAGEWIDTH_B; + heightstack_Px[sp-1] -= REALHEIGHT(miph_Px); + }else{ + // CASE 10 + sp--; + } + flag = 1; + }else{ + // CASE 6 + yoff_Px += REALHEIGHT(lastmiph_Px); + bufferBase_B[n] = bufferBase_B[n-1] + PAGEWIDTH_B*NBLKY(lastmiph_Px); + } + }else{ + // CASE 11 + bufferHeight_P[n] = bufferHeight_P[n-1]; + bufferPage_B[n] = bufferPage_B[n-1]; + bufferWidth_W[n] = bufferWidth_W[n-1]; + if(REALWIDTH(lastmipw_Px) + xoff_Px < bufferWidth_W[n-1]*64){ + // CASE 12 + xoffstack_Px[sp] = xoff_Px; // unused... + widthstack_Px[sp] = REALWIDTH(lastmipw_Px); + heightstack_Px[sp] = REALHEIGHT(lastmiph_Px); + basestack_B[sp] = bufferBase_B[n-1] + PAGEWIDTH_B * NBLKY(lastmiph_Px); + sp++; + xoff_Px += REALWIDTH(lastmipw_Px); + bufferBase_B[n] = bufferBase_B[n-1] + NBLKX(lastmipw_Px); + }else if(REALHEIGHT(lastmiph_Px) + yoff_Px < pageHeight_Px*bufferHeight_P[n] && flag == 0){ + // CASE 13 + bufferBase_B[n] = bufferBase_B[n-1] + PAGEWIDTH_B * NBLKY(lastmiph_Px); + yoff_Px += blockHeight_Px ? lastmiph_Px : 0; // how exactly can blockHeight be 0?? This looks wrong... + flag = n; + }else{ + // CASE 14 + bufferBase_B[n] = basestack_B[sp-1]; + if(REALWIDTH(mipw_Px) < widthstack_Px[sp-1]){ + // CASE 15 + basestack_B[sp-1] += NBLKX(mipw_Px); + widthstack_Px[sp-1] -= REALWIDTH(mipw_Px); + }else if(REALHEIGHT(miph_Px) < heightstack_Px[sp-1]){ + // CASE 16 + basestack_B[sp-1] += PAGEWIDTH_B * NBLKY(miph_Px); + heightstack_Px[sp-1] -= REALHEIGHT(miph_Px); + }else{ + // CASE 17 + sp--; + } + } + } + lastmipw_Px = mipw_Px; + lastmiph_Px = miph_Px; + } + + // Calculate position of palette. + uint32 paletteBase_B = 0; + uint64 bufwidth_Px = bufferWidth_W[nlevels-1]*64; + uint64 bufheight_Px = bufferHeight_P[nlevels-1]*pageHeight_Px; + // != means > really + if(bufwidth_Px != lastmipw_Px || bufheight_Px != lastmiph_Px){ + if(psm == PSMT8){ + // 2x2 blocks at the end of the page (even for PSMCT16S) + paletteBase_B = bufferPage_B[nlevels-1] + + ((bufwidth_Px/pageWidth_Px)*bufferHeight_P[nlevels-1] << 5) // total number of blocks + - (bufheight_Px/pageWidth_Px) * PAGEWIDTH_B // one block up + - 2; // two blocks left + }else if(psm == PSMT4){ + // One block at the end of the page + paletteBase_B = bufferPage_B[nlevels-1] + + ((bufwidth_Px/pageWidth_Px) * bufferHeight_P[nlevels-1] << 5) + - 1; + } + }else{ + if(psm == PSMT8 || psm == PSMT4){ + paletteBase_B = bufferPage_B[nlevels-1] + + (bufwidth_Px/blockWidth_Px) * (bufheight_Px/blockHeight_Px); + } + } + + uint32 bufwidth_W = bufferWidth_W[0]; + uint32 bufpage_B = bufferPage_B[0]; + uint32 pixeloff; + for(n = 0; n < nlevels; n++){ + // Calculate TRXPOS register (DSAX and DSAY, shifted up later) + // Start of buffer on current page (x in pixels, y in blocks) + pixeloff = (bufferBase_B[n] - bufpage_B) * blockWidth_Px; + // y coordinate of first pixel + yoff_Px = (pixeloff / (bufwidth_W*64)) * blockHeight_Px; + // x coordinate of first pixel + xoff_Px = pixeloff % (bufwidth_W*64); + if(bufferWidth_W[n] == bufwidth_W && + // Not quite sure what's the meaning of this. + // DSAY is 11 bits, but so is DSAX and it is not checked? + yoff_Px < 0x800){ + trxpos[n] = yoff_Px<<16 | xoff_Px; + }else{ + bufwidth_W = bufferWidth_W[n]; + bufpage_B = bufferPage_B[n]; + trxpos[n] = 0; + } + + // If using more than one page we have to swizzle rows inside page rows + if(bufwidth_W*64 / pageWidth_Px > 1){ + uint32 bufpagestride_B = bufwidth_W*64 * 32 / pageWidth_Px; // one row of pages + uint32 bufwidth_B = bufwidth_W*64 / blockWidth_Px; // one row of blocks + // To illustrate assume: + // - 8x4 block pages + // - texture is 4 pages wide + // Then the lower bits of an input block address look like: RRRPPCC + // where the C bits are the block's column inside a page + // the P bits are the block's page horizontally + // the R bits are the block's row in a row of pages + // We want to swap P and R: PPRRRCC + bufferBase_B[n] = + (bufferBase_B[n] & ~((uint64)bufpagestride_B - PAGEWIDTH_B)) // mask out R and P + | ((bufferBase_B[n] & (bufwidth_B - PAGEWIDTH_B)) * (bufpagestride_B/bufwidth_B)) // extract P and shift left + | ((bufferBase_B[n] & (bufpagestride_B - bufwidth_B)) / (bufwidth_B/PAGEWIDTH_B)); // extract R and shift right + } + + // Always have to swizzle blocks inside pages. We use a lookup, RW does bit operations + switch(psm){ + case PSMCT32: + case PSMCT24: + case PSMT8: + case PSMT8H: + case PSMT4HL: + case PSMT4HH: + // ABCDE -> CADBE + bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMCT32[bufferBase_B[n]&0x1F]; + break; + case PSMT4: + case PSMCT16: + // ABCDE -> ADBEC + bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMCT16[bufferBase_B[n]&0x1F]; + break; + case PSMCT16S: + // ABCDE -> DBAEC + bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMCT16S[bufferBase_B[n]&0x1F]; + break; + case PSMZ32: + case PSMZ24: + // ABCDE -> ~C~ADBE + bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMZ32[bufferBase_B[n]&0x1F]; + break; + case PSMZ16: + // ABCDE -> ~A~DBEC + bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMZ16[bufferBase_B[n]&0x1F]; + break; + case PSMZ16S: + // ABCDE -> ~D~BAEC + bufferBase_B[n] = (bufferBase_B[n]&~0x1F) | (uint64)blockmap_PSMZ16S[bufferBase_B[n]&0x1F]; + break; + default: break; + } + } + + // Same dance as above, with the palette + if(bufwidth_W*64 / pageWidth_Px > 1){ + uint32 bufpagestride_B = bufwidth_W*64 * 32 / pageWidth_Px; // one row of pages + uint32 bufwidth_B = bufwidth_W*64 / blockWidth_Px; // one row of blocks + paletteBase_B = + (paletteBase_B & ~(bufpagestride_B - PAGEWIDTH_B)) // mask out R and P + | ((paletteBase_B & (bufwidth_B - PAGEWIDTH_B)) * (bufpagestride_B/bufwidth_B)) // extract P and shift left + | ((paletteBase_B & (bufpagestride_B - bufwidth_B)) / (bufwidth_B/PAGEWIDTH_B)); // extract R and shift right + } + switch(psm){ + case PSMCT32: + case PSMCT24: + case PSMT8: + case PSMT8H: + case PSMT4HL: + case PSMT4HH: + paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMCT32[paletteBase_B&0x1F]; + break; + case PSMT4: + case PSMCT16: + paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMCT16[paletteBase_B&0x1F]; + break; + case PSMCT16S: + paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMCT16S[paletteBase_B&0x1F]; + break; + case PSMZ32: + case PSMZ24: + paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMZ32[paletteBase_B&0x1F]; + break; + case PSMZ16: + paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMZ16[paletteBase_B&0x1F]; + break; + case PSMZ16S: + paletteBase_B = (paletteBase_B&~0x1F) | (uint64)blockmap_PSMZ16S[paletteBase_B&0x1F]; + break; + default: break; + } + *paletteBase = paletteBase_B; + *totalSize = bufferPage_B[nlevels-1] + // start of last buffer` + bufferWidth_W[nlevels-1]*64/blockWidth_Px * // number of horizontal blocks in last level + pageHeight_Px*bufferHeight_P[nlevels-1]/blockHeight_Px; // number of vertical blocks in last level + *totalSize *= 64; // to words + +#undef BLKSTRIDE +#undef NBLKX +#undef NBLKY +#undef NPGX +#undef NPGY +#undef REALWIDTH +#undef REALHEIGHT +} + +static Raster* +rasterCreateTexture(Raster *raster) +{ + // We use a map for fast lookup, even for impossible depths + static int32 pageWidths[32] = { + 128, 128, 128, 128, + 128, 128, 128, 128, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + }; + static int32 pageHeights[32] = { + 128, 128, 128, 128, + 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, + }; + enum { + TCC_RGBA = 1 << 2, + CLD_1 = 1 << 29, + + WD2BLK = 64, // words per block + WD2PG = 2048, // words per page + }; + int32 pixelformat, palformat; + // TEX0 fields (not all) + int64 tbw = 0; // texture buffer width, texels/64 + int64 psm = 0; // pixel storage mode + int64 tw = 0; // texture width exponent, width = 2^tw + int64 th = 0; // texture height exponent, height = 2^th + int64 tcc = 0; // texture color component, 0 = rgb, 1 = rgba + int64 cpsm = 0; // CLUT pixel storage mode + int64 cld = 0; // CLUT buffer load control + + uint64 bufferWidth[7]; // in number of pixels / 64 + uint64 bufferBase[7]; // block address + uint32 trxpos_hi[8]; + int32 width, height, depth; + int32 pageWidth, pageHeight; + int32 paletteWidth, paletteHeight, paletteDepth; + int32 palettePagewidth, palettePageheight; + + + Ps2Raster *ras = GETPS2RASTEREXT(raster); + pixelformat = raster->format & 0xF00; + palformat = raster->format & 0x6000; + width = raster->width; + height = raster->height; + depth = raster->depth; + // RW's code does not seem to quite work with 24 bit rasters + // so make sure we're not generating them for safety + assert(depth != 24); + + ras->flags = 0; + ras->data = nil; + ras->dataSize = 0; + + // RW doesn't seem to check this, hm... + if(raster->flags & Raster::DONTALLOCATE) + return raster; + + //printf("%x %x %x %x\n", raster->format, raster->flags, raster->type, noNewStyleRasters); + pageWidth = pageWidths[depth-1]; + pageHeight = pageHeights[depth-1]; + + int32 s; + tw = 0; + for(s = 1; s < width; s *= 2) + tw++; + th = 0; + for(s = 1; s < height; s *= 2) + th++; + ras->kl = defaultMipMapKL; + // unk2[0] = 1 + //printf("%d %d %d %d\n", raster->width, logw, raster->height, logh); + + // round up to page width, set TBW, TW, TH + tbw = max(width,pageWidth)/64; + + // set PSM, TCC, CLD, CPSM and figure out palette format + if(palformat){ + if(palformat == Raster::PAL8){ + psm = PSMT8; + paletteWidth = 16; + paletteHeight = 16; + }else if(palformat == Raster::PAL4){ + psm = PSMT4; + paletteWidth = 8; + paletteHeight = 2; + }else{ + // can't happen, sanity check in getRasterFormat + return nil;; + } + tcc = 1; // RGBA + cld = 1; + if(pixelformat == Raster::C1555){ + paletteDepth = 2; + cpsm = PSMCT16S; + palettePagewidth = 64; + palettePageheight = 64; + }else if(pixelformat == Raster::C8888){ + paletteDepth = 4; + cpsm = PSMCT32; + palettePagewidth = 64; + palettePageheight = 32; + }else + // can't happen, sanity check in getRasterFormat + return nil;; + }else{ + paletteWidth = 0; + paletteHeight = 0; + paletteDepth = 0; + palettePagewidth = 0; + palettePageheight = 0; + if(pixelformat == Raster::C8888){ + psm = PSMCT32; + tcc = 1; // RGBA + }else if(pixelformat == Raster::C888){ + psm = PSMCT24; + tcc = 0; // RGB + }else if(pixelformat == Raster::C1555){ + psm = PSMCT16S; + tcc = 1; // RGBA + }else + // can't happen, sanity check in getRasterFormat + return nil;; + } + + for(int i = 0; i < 7; i++){ + bufferWidth[i] = 1; + bufferBase[i] = 0; + } + + int32 mipw, miph; + int32 w, h; + int32 n; + int32 nPagW, nPagH; + raster->stride = width*depth/8; + + if(raster->format & Raster::MIPMAP){ + // NOTE: much of this code seems to be totally useless. + // calcOffsets overwrites what we calculate here. I wonder + // why this code even is in RW. Maybe it's older code that used + // the GS' automatic base pointer calculation? + + // see the left columns in the maps above + static uint32 blockOffset32_24_8[8] = { 0, 2, 2, 8, 8, 10, 10, 32 }; + static uint32 blockOffset16_4[8] = { 0, 1, 4, 5, 16, 17, 20, 21 }; + static uint32 blockOffset16S[8] = { 0, 1, 8, 9, 4, 5, 12, 13 }; + uint64 lastBufferWidth; + mipw = width; + miph = height; + lastBufferWidth = max(pageWidth, width)/64; + ras->pixelSize = 0; + int32 lastaddress = 0; // word address + int32 nextaddress = 0; // word address + int32 stride; // in bytes + for(n = 0; mipw != 0 && miph != 0 && n < maxMipLevels; n++){ + if(width >= 8 && height >= 8 && (mipw < 8 || miph < 8)) + break; + ras->pixelSize += ALIGN64(mipw*miph*depth/8); + bufferWidth[n] = max(pageWidth, mipw)/64; + stride = bufferWidth[n]*64*depth/8; + + // If buffer width changes, align next address to page + if(bufferWidth[n] != lastBufferWidth){ + nPagW = ((width >> (n-1)) + pageWidth-1)/pageWidth; + nPagH = ((height >> (n-1)) + pageHeight-1)/pageHeight; + nextaddress = (lastaddress + nPagW*nPagH*WD2PG) & ~(WD2PG-1); + } + lastBufferWidth = bufferWidth[n]; + nextaddress = ALIGN64(nextaddress); // this should already be the case... + uint32 b = nextaddress>>(11-3) & 7; // upper three bits of block-in-page address + switch(psm){ + case PSMCT32: + case PSMCT24: + case PSMT8: + b = blockOffset32_24_8[b]; + break; + case PSMCT16: + case PSMT4: + b = blockOffset16_4[b]; + break; + case PSMCT16S: + b = blockOffset16S[b]; + break; + default: + // can't happen + break; + } + // shift to page address, then to block address and add offset inside page + bufferBase[n] = b + (nextaddress>>11 << 5); + + lastaddress = nextaddress; + nextaddress = ALIGN64(miph*stride/4 + lastaddress); + + mipw /= 2; + miph /= 2; + } + + // Do the real work here + uint32 paletteBase; + uint32 totalSize; + calcOffsets(width, height, psm, bufferBase, bufferWidth, trxpos_hi, &totalSize, &paletteBase); + + ras->paletteSize = paletteWidth*paletteHeight*paletteDepth; + ras->miptbp1 = + bufferWidth[1]<<14 | (bufferBase[1] & 0x3FFF)<<0 + | bufferWidth[2]<<34 | (bufferBase[2] & 0x3FFF)<<20 + | bufferWidth[3]<<54 | (bufferBase[3] & 0x3FFF)<<40; + ras->miptbp2 = + bufferWidth[4]<<14 | (bufferBase[4] & 0x3FFF)<<0 + | bufferWidth[5]<<34 | (bufferBase[5] & 0x3FFF)<<20 + | bufferWidth[6]<<54 | (bufferBase[6] & 0x3FFF)<<40; + ras->tex1low = (n-1)<<2; + ras->totalSize = totalSize; + if(ras->paletteSize){ + ras->paletteBase = paletteBase; + if(ras->paletteBase*64 == ras->totalSize) + ras->totalSize += WD2PG; + }else + ras->paletteBase = 0; + }else{ + // No mipmaps + + ras->pixelSize = ALIGN16(raster->stride*raster->height); + ras->paletteSize = paletteWidth*paletteHeight*paletteDepth; + ras->miptbp1 = 1ULL<<54 | 1ULL<<34 | 1ULL<<14; + ras->miptbp2 = 1ULL<<54 | 1ULL<<34 | 1ULL<<14; + ras->tex1low = 0; // one mipmap level + + // find out number of pages needed + nPagW = (width + pageWidth-1)/pageWidth; + nPagH = (height + pageHeight-1)/pageHeight; + + // calculate buffer width in units of pixels/64 + bufferBase[0] = 0; + trxpos_hi[0] = 0; + bufferWidth[0] = nPagW*pageWidth / 64; + + // calculate whole buffer size in words + ras->totalSize = nPagW*nPagH*WD2PG; + + // calculate palette offset on GS in units of words/64 + if(ras->paletteSize){ + // Maximum palette size is 256 words. + // If there is still room, use it! + // If dimensions don't fill a page, we have at least + // half a page left, enough for any palette +//TODO: this was not always done it seems but even gta3's 3.1 seems to?? + if(pageWidth*nPagW > width || + pageHeight*nPagH > height){ + ras->paletteBase = (ras->totalSize - 256) / WD2BLK; + }else{ + // Otherwise allocate more space... + ras->paletteBase = ras->totalSize / WD2BLK; + // ...using the same calculation as above. + // WHY? we never need more than one page! + nPagW = (paletteWidth + palettePagewidth-1)/palettePagewidth; + nPagH = (paletteHeight + palettePageheight-1)/palettePageheight; + ras->totalSize += nPagW*nPagH*WD2PG; + } + }else + ras->paletteBase = 0; + } + ras->tex0 = tbw << 14 | + psm << 20 | + tw << 26 | + th << 30 | + tcc << 34 | + cpsm << 51 | + 0ULL << 55 | // csm0 + 0ULL << 56 | // entry offset + cld << 61; + + + + // allocate data and fill with GIF packets + ras->pixelSize = ALIGN16(ras->pixelSize); + int32 numLevels = MAXLEVEL(ras)+1; + // No GIF packet because we either don't want it (pre 0x310 rasters) + // or the data wouldn't fit into a DMA packet + if(noNewStyleRasters || + (raster->width*raster->height*raster->depth/8/0x10) >= 0x7FFF){ + ras->dataSize = ras->paletteSize+ras->pixelSize; + uint8 *data = (uint8*)mallocalign(ras->dataSize, 0x40); + assert(data); + ras->data = data; + raster->pixels = data; + if(ras->paletteSize) + raster->palette = data + ras->pixelSize; + if(raster->depth == 8) + ras->flags |= Ps2Raster::SWIZZLED8; + }else{ + ras->flags |= Ps2Raster::NEWSTYLE; + uint64 paltrxpos = 0; + uint32 dsax = trxpos_hi[numLevels-1] & 0x7FF; + uint32 dsay = trxpos_hi[numLevels-1]>>16 & 0x7FF; + // Set swizzle flags and calculate TRXPOS for palette + if(psm == PSMT8){ + ras->flags |= Ps2Raster::SWIZZLED8; + if(cpsm == PSMCT32 && bufferWidth[numLevels-1] == 2){ // one page + // unswizzle the starting block of the last buffer and palette + uint32 bufbase_B = (bufferBase[numLevels-1]&~0x1F) | (uint64)blockmaprev_PSMCT32[bufferBase[numLevels-1]&0x1F]; + uint32 palbase_B = (ras->paletteBase&~0x1F) | (uint64)blockmaprev_PSMCT32[ras->paletteBase&0x1F]; + // find start of page of last level (16,16 are PSMT8 block dimensions) + uint32 page_B = bufbase_B - 8*(dsay/16) - dsax/16; + // find palette DSAX/Y (in PSMCT32!) + dsay = (palbase_B - page_B)/8 * 8; // block/blocksPerPageX * blockHeight + dsax = (palbase_B - page_B)*8 % 64; // block*blockWidth % pageWidth + if(dsay < 0x800) + paltrxpos = dsay<<16 | dsax; + } + } + if(psm == PSMT4){ + // swizzle flag depends on version :/ + // but which version? .... + if(rw::version > 0x31000){ + ras->flags |= Ps2Raster::SWIZZLED4; + // Where can this come from? if anything we're using PSMCT16S + // Looks like they wanted to swizzle palettes too... + if(cpsm == PSMCT16){ + // unswizzle the starting block of the last buffer and palette + uint32 bufbase_B = (bufferBase[numLevels-1]&~0x1F) | (uint64)blockmaprev_PSMCT16[bufferBase[numLevels-1]&0x1F]; + uint32 palbase_B = (ras->paletteBase&~0x1F) | (uint64)blockmaprev_PSMCT16[ras->paletteBase&0x1F]; + // find start of page of last level (32,16 are PSMT4 block dimensions) + uint32 page_B = bufbase_B - 4*(dsay/32) - dsax/16; + // find palette DSAX/Y (in PSMCT16!) + dsay = (palbase_B - page_B)/4 * 8; // block/blocksPerPageX * blockHeight + dsax = (palbase_B - page_B)*16 % 128; // block*blockWidth % pageWidth + if(dsay < 0x800) + paltrxpos = dsay<<16 | dsax; + } + } + } + ras->pixelSize = 0x50*numLevels; // GIF packets + int32 minW, minH; + transferMinSize(psm, ras->flags, &minW, &minH); + w = raster->width; + h = raster->height; + n = numLevels; + while(n--){ + mipw = max(w, minW); + miph = max(h, minH); + ras->pixelSize += ALIGN16(mipw*miph*raster->depth/8); + w /= 2; + h /= 2; + } + if(ras->paletteSize){ + if(rw::version > 0x31000 && paletteHeight == 2) + paletteHeight = 3; + ras->paletteSize = 0x50 + + paletteDepth*paletteWidth*paletteHeight; + } + // One transfer per buffer width, 4 qwords: + // DMAcnt(2) [NOP, DIRECT] + // GIF tag A+D + // BITBLTBUF + // DMAref(pixel data) [NOP, DIRECT] + uint32 extrasize = 0x10; // PixelPtr + int32 numTransfers = 0; + for(n = 0; n < numLevels; n++) + if(trxpos_hi[n] == 0){ + extrasize += 0x40; + numTransfers++; + } + if(ras->paletteSize){ + extrasize += 0x40; + numTransfers++; + } + // What happens here? + if(ras->paletteSize && paltrxpos == 0) + ras->dataSize = ALIGN(ras->pixelSize,128) + ALIGN(ras->paletteSize,64) + extrasize + 0x70; + else + ras->dataSize = ALIGN(ras->paletteSize+ras->pixelSize,64) + extrasize + 0x70; + uint8 *data = (uint8*)mallocalign(ras->dataSize, 0x40); + uint32 *xferchain = (uint32*)(data + 0x10); + assert(data); + ras->data = data; + Ps2Raster::PixelPtr *pp = (Ps2Raster::PixelPtr*)data; + pp->numTransfers = numTransfers; + pp->numTotalTransfers = numTransfers; + pp->pixels = (uint8*)ALIGN((uintptr)data + extrasize, 128); + raster->pixels = (uint8*)pp; + if(ras->paletteSize) + raster->palette = pp->pixels + ALIGN(ras->pixelSize, 128) + 0x50; + uint32 *p = (uint32*)pp->pixels; + w = raster->width; + h = raster->height; + for(n = 0; n < numLevels; n++){ + mipw = max(w, minW); + miph = max(h, minH); + + // GIF tag + *p++ = 3; // NLOOP = 3 + *p++ = 0x10000000; // NREG = 1 + *p++ = 0xE; // A+D + *p++ = 0; + + // TRXPOS + if((ras->flags & Ps2Raster::SWIZZLED8 && psm == PSMT8) || + (ras->flags & Ps2Raster::SWIZZLED4 && psm == PSMT4)){ + *p++ = 0; // SSAX/Y is always 0 + *p++ = (trxpos_hi[n] & ~0x10001)/2; // divide both DSAX/Y by 2 + }else{ + *p++ = 0; + *p++ = trxpos_hi[n]; + } + *p++ = 0x51; + *p++ = 0; + + // TRXREG + if((ras->flags & Ps2Raster::SWIZZLED8 && psm == PSMT8) || + (ras->flags & Ps2Raster::SWIZZLED4 && psm == PSMT4)){ + *p++ = mipw/2; + *p++ = miph/2; + }else{ + *p++ = mipw; + *p++ = miph; + } + *p++ = 0x52; + *p++ = 0; + + // TRXDIR + *p++ = 0; // host -> local + *p++ = 0; + *p++ = 0x53; + *p++ = 0; + + // GIF tag + uint32 sz = ALIGN16(mipw*miph*raster->depth/8)/16; + *p++ = sz & 0x7FFF; + *p++ = 0x08000000; // IMAGE + *p++ = 0; + *p++ = 0; + + if(trxpos_hi[n] == 0){ + // Add a transfer, see above for layout + + *xferchain++ = 0x10000002; // DMAcnt, 2 qwords + *xferchain++ = 0; + *xferchain++ = 0; // VIF nop + *xferchain++ = 0x50000002; // VIF DIRECT 2 qwords + + // GIF tag + *xferchain++ = 1; // NLOOP = 1 + *xferchain++ = 0x10000000; // NREG = 1 + *xferchain++ = 0xE; // A+D + *xferchain++ = 0; + + // BITBLTBUF + if(ras->flags & Ps2Raster::SWIZZLED8 && psm == PSMT8){ + // PSMT8 is swizzled to PSMCT32 and dimensions are halved + *xferchain++ = PSMCT32<<24 | bufferWidth[n]/2<<16; // src buffer + *xferchain++ = PSMCT32<<24 | bufferWidth[n]/2<<16 | bufferBase[n]; // dst buffer + }else if(ras->flags & Ps2Raster::SWIZZLED4 && psm == PSMT4){ + // PSMT4 is swizzled to PSMCT16 and dimensions are halved + *xferchain++ = PSMCT16<<24 | bufferWidth[n]/2<<16; // src buffer + *xferchain++ = PSMCT16<<24 | bufferWidth[n]/2<<16 | bufferBase[n]; // dst buffer + }else{ + *xferchain++ = psm<<24 | bufferWidth[n]<<16; // src buffer + *xferchain++ = psm<<24 | bufferWidth[n]<<16 | bufferBase[n]; // dst buffer + } + *xferchain++ = 0x50; + *xferchain++ = 0; + + *xferchain++ = 0x30000000 | sz+5; // DMAref + // this obviously only works with 32 bit pointers, but it's only needed on the PS2 anyway + *xferchain++ = (uint32)(uintptr)p - 0x50; + *xferchain++ = 0; // VIF nop + *xferchain++ = 0x50000000 | sz+5; // VIF DIRECT 2 qwords + }else{ + // Add to existing transfer + xferchain[-4] = 0x30000000 | (xferchain[-4]&0xFFFF) + sz+5; // last DMAref + xferchain[-1] = 0x50000000 | (xferchain[-1]&0xFFFF) + sz+5; // last DIRECT + } + + p += sz*4; + w /= 2; + h /= 2; + } + + if(ras->paletteSize){ + // huh? + if(paltrxpos) + raster->palette = (uint8*)p + 0x50; + p = (uint32*)(raster->palette - 0x50); + // GIF tag + *p++ = 3; // NLOOP = 3 + *p++ = 0x10000000; // NREG = 1 + *p++ = 0xE; // A+D + *p++ = 0; + + // TRXPOS + *(uint64*)p = paltrxpos; + p += 2; + *p++ = 0x51; + *p++ = 0; + + // TRXREG + *p++ = paletteWidth; + *p++ = paletteHeight; + *p++ = 0x52; + *p++ = 0; + + // TRXDIR + *p++ = 0; // host -> local + *p++ = 0; + *p++ = 0x53; + *p++ = 0; + + // GIF tag + uint32 sz = ALIGN16(ras->paletteSize - 0x50)/16; + *p++ = sz & 0x7FFF; + *p++ = 0x08000000; // IMAGE + *p++ = 0; + *p++ = 0; + + // Transfer + *xferchain++ = 0x10000002; // DMAcnt, 2 qwords + *xferchain++ = 0; + *xferchain++ = 0; // VIF nop + *xferchain++ = 0x50000002; // VIF DIRECT 2 qwords + + // GIF tag + *xferchain++ = 1; // NLOOP = 1 + *xferchain++ = 0x10000000; // NREG = 1 + *xferchain++ = 0xE; // A+D + *xferchain++ = 0; + + // BITBLTBUF + if(paltrxpos == 0){ + *xferchain++ = cpsm<<24 | 1<<16; // src buffer + *xferchain++ = cpsm<<24 | 1<<16 | ras->paletteBase; // dst buffer + *xferchain++ = 0x50; + *xferchain++ = 0; + }else{ + // copy last pixel bitbltbuf...if uploading palette separately it's still the same buffer + xferchain[0] = xferchain[-16]; + xferchain[1] = xferchain[-15]; + xferchain[2] = xferchain[-14]; + xferchain[3] = xferchain[-13]; + // Add to last transfer + xferchain[-16] = 0x30000000 | (xferchain[-16]&0xFFFF) + sz+5; // last DMAref + xferchain[-13] = 0x50000000 | (xferchain[-13]&0xFFFF) + sz+5; // last DIRECT + xferchain += 4; + pp->numTransfers--; + } + + *xferchain++ = 0x30000000 | sz+5; // DMAref + // this obviously only works with 32 bit pointers, but it's only needed on the PS2 anyway + *xferchain++ = (uint32)(uintptr)p - 0x50; + *xferchain++ = 0; // VIF nop + *xferchain++ = 0x50000000 | sz+5; // VIF DIRECT 2 qwords + } + } + raster->originalPixels = raster->pixels; + raster->originalStride = raster->stride; + if(ras->flags & Ps2Raster::NEWSTYLE) + raster->pixels = ((Ps2Raster::PixelPtr*)raster->pixels)->pixels + 0x50; + return raster; +} + +Raster* +rasterCreate(Raster *raster) +{ + if(!getRasterFormat(raster)) + return nil; + + // init raster + raster->pixels = nil; + raster->palette = nil; + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->originalPixels = raster->pixels; + if(raster->width == 0 || raster->height == 0){ + raster->flags = Raster::DONTALLOCATE; + raster->stride = 0; + raster->originalStride = 0; + return raster; + } + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + return rasterCreateTexture(raster); + case Raster::ZBUFFER: + // TODO. only RW_PS2 + // get info from video mode + raster->flags = Raster::DONTALLOCATE; + return raster; + case Raster::CAMERA: + // TODO. only RW_PS2 + // get info from video mode + raster->flags = Raster::DONTALLOCATE; + return raster; + case Raster::CAMERATEXTURE: + // TODO. only RW_PS2 + // check width/height and fall through to texture + return nil; + } + return nil; +} + +static uint32 +swizzle(uint32 x, uint32 y, uint32 logw) +{ +#define X(n) ((x>>(n))&1) +#define Y(n) ((y>>(n))&1) + + uint32 nx, ny, n; + x ^= (Y(1)^Y(2))<<2; + nx = (x&7) | ((x>>1)&~7); + ny = (y&1) | ((y>>1)&~1); + n = Y(1) | X(3)<<1; + return n | nx<<2 | ny<<(logw-1+2); +} + +void +unswizzleRaster(Raster *raster) +{ + uint8 tmpbuf[1024*4]; // 1024x4px, maximum possible width + uint32 mask; + int32 x, y, w, h; + int32 i; + int32 logw; + Ps2Raster *natras = GETPS2RASTEREXT(raster); + uint8 *px; + + if((raster->format & (Raster::PAL4|Raster::PAL8)) == 0) + return; + + int minw, minh; + transferMinSize(raster->format & Raster::PAL4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + w = max(raster->width, minw); + h = max(raster->height, minh); + px = raster->pixels; + logw = 0; + for(i = 1; i < w; i *= 2) logw++; + mask = (1<<(logw+2))-1; + + if(raster->format & Raster::PAL4 && natras->flags & Ps2Raster::SWIZZLED4){ + for(y = 0; y < h; y += 4){ + memcpy(tmpbuf, &px[y<<(logw-1)], 2*w); + for(i = 0; i < 4; i++) + for(x = 0; x < w; x++){ + uint32 a = ((y+i)<>1] >> 4 : tmpbuf[s>>1] & 0xF; + px[a>>1] = a & 1 ? (px[a>>1]&0xF) | c<<4 : (px[a>>1]&0xF0) | c; + } + } + }else if(raster->format & Raster::PAL8 && natras->flags & Ps2Raster::SWIZZLED8){ + for(y = 0; y < h; y += 4){ + memcpy(tmpbuf, &px[y<format & (Raster::PAL4|Raster::PAL8)) == 0) + return; + + int minw, minh; + transferMinSize(raster->format & Raster::PAL4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + w = max(raster->width, minw); + h = max(raster->height, minh); + px = raster->pixels; + logw = 0; + for(i = 1; i < raster->width; i *= 2) logw++; + mask = (1<<(logw+2))-1; + + if(raster->format & Raster::PAL4 && natras->flags & Ps2Raster::SWIZZLED4){ + for(y = 0; y < h; y += 4){ + for(i = 0; i < 4; i++) + for(x = 0; x < w; x++){ + uint32 a = ((y+i)<>1] >> 4 : px[a>>1] & 0xF; + tmpbuf[s>>1] = s & 1 ? (tmpbuf[s>>1]&0xF) | c<<4 : (tmpbuf[s>>1]&0xF0) | c; + } + memcpy(&px[y<<(logw-1)], tmpbuf, 2*w); + } + }else if(raster->format & Raster::PAL8 && natras->flags & Ps2Raster::SWIZZLED8){ + for(y = 0; y < h; y += 4){ + for(i = 0; i < 4; i++) + for(x = 0; x < w; x++){ + uint32 a = ((y+i)<depth != 24); + + if(level > 0){ + int32 minw, minh; + int32 mipw, miph; + transferMinSize(raster->format & Raster::PAL4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + while(level--){ + mipw = max(raster->width, minw); + miph = max(raster->height, minh); + raster->pixels += ALIGN16(mipw*miph*raster->depth/8) + 0x50; + raster->width /= 2; + raster->height /= 2; + } + } + + if((lockMode & Raster::LOCKNOFETCH) == 0) + unswizzleRaster(raster); + if(lockMode & Raster::LOCKREAD) raster->privateFlags |= Raster::PRIVATELOCK_READ; + if(lockMode & Raster::LOCKWRITE) raster->privateFlags |= Raster::PRIVATELOCK_WRITE; + return raster->pixels; +} + +void +rasterUnlock(Raster *raster, int32 level) +{ + Ps2Raster *natras = GETPS2RASTEREXT(raster); + if(raster->format & (Raster::PAL4 | Raster::PAL8)) + swizzleRaster(raster); + + raster->width = raster->originalWidth; + raster->height = raster->originalHeight; + raster->pixels = raster->originalPixels; + raster->stride = raster->originalStride; + if(natras->flags & Ps2Raster::NEWSTYLE) + raster->pixels = ((Ps2Raster::PixelPtr*)raster->pixels)->pixels + 0x50; + + raster->privateFlags &= ~(Raster::PRIVATELOCK_READ|Raster::PRIVATELOCK_WRITE); + // TODO: generate mipmaps +} + +static void +convertCSM1_16(uint32 *pal) +{ + int i, j; + uint32 tmp; + for(i = 0; i < 256; i++) + // palette index bits 0x08 and 0x10 are flipped + if((i & 0x18) == 0x10){ + j = i ^ 0x18; + tmp = pal[i]; + pal[i] = pal[j]; + pal[j] = tmp; + } +} + +static void +convertCSM1_32(uint32 *pal) +{ + int i, j; + uint32 tmp; + for(i = 0; i < 256; i++) + // palette index bits 0x08 and 0x10 are flipped + if((i & 0x18) == 0x10){ + j = i ^ 0x18; + tmp = pal[i]; + pal[i] = pal[j]; + pal[j] = tmp; + } +} + +static void +convertPalette(Raster *raster) +{ + if(raster->format & Raster::PAL8){ + if((raster->format & 0xF00) == Raster::C8888) + convertCSM1_32((uint32*)raster->palette); + else if((raster->format & 0xF00) == Raster::C8888) + convertCSM1_16((uint32*)raster->palette); + } +} + +// NB: RW doesn't convert the palette when locking/unlocking +uint8* +rasterLockPalette(Raster *raster, int32 lockMode) +{ + if((raster->format & (Raster::PAL4 | Raster::PAL8)) == 0) + return nil; + if((lockMode & Raster::LOCKNOFETCH) == 0) + convertPalette(raster); + if(lockMode & Raster::LOCKREAD) raster->privateFlags |= Raster::PRIVATELOCK_READ_PALETTE; + if(lockMode & Raster::LOCKWRITE) raster->privateFlags |= Raster::PRIVATELOCK_WRITE_PALETTE; + return raster->palette; +} + +void +rasterUnlockPalette(Raster *raster) +{ + if(raster->format & (Raster::PAL4 | Raster::PAL8)) + convertPalette(raster); + raster->privateFlags &= ~(Raster::PRIVATELOCK_READ_PALETTE|Raster::PRIVATELOCK_WRITE_PALETTE); +} + +// Almost the same as d3d9 and gl3 function +bool32 +imageFindRasterFormat(Image *img, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat) +{ + int32 width, height, depth, format; + + assert((type&0xF) == Raster::TEXTURE); + + for(width = 1; width < img->width; width <<= 1); + for(height = 1; height < img->height; height <<= 1); + + depth = img->depth; + + switch(depth){ + case 32: + case 24: + // C888 24 bit is unsafe + format = Raster::C8888; + depth = 32; + break; + case 16: + format = Raster::C1555; + break; + case 8: + format = Raster::PAL8 | Raster::C8888; + break; + case 4: + format = Raster::PAL4 | Raster::C8888; + break; + default: + RWERROR((ERR_INVRASTER)); + return 0; + } + + format |= type; + + *pWidth = width; + *pHeight = height; + *pDepth = depth; + *pFormat = format; + + return 1; +} + +bool32 +rasterFromImage(Raster *raster, Image *image) +{ + Ps2Raster *natras = GETPS2RASTEREXT(raster); + + int32 pallength = 0; + switch(image->depth){ + case 24: + case 32: + if(raster->format != Raster::C8888 && + raster->format != Raster::C888) // unsafe already + goto err; + break; + case 16: + if(raster->format != Raster::C1555) goto err; + break; + case 8: + if(raster->format != (Raster::PAL8 | Raster::C8888)) goto err; + pallength = 256; + break; + case 4: + if(raster->format != (Raster::PAL4 | Raster::C8888)) goto err; + pallength = 16; + break; + default: + err: + RWERROR((ERR_INVRASTER)); + return 0; + } + + // unsafe + if((raster->format&0xF00) == Raster::C888){ + RWERROR((ERR_INVRASTER)); + return 0; + } + + uint8 *in, *out; + if(image->depth <= 8){ + in = image->palette; + out = raster->lockPalette(Raster::LOCKWRITE|Raster::LOCKNOFETCH); + memcpy(out, in, 4*pallength); + for(int32 i = 0; i < pallength; i++){ + out[3] = out[3]*128/255; + out += 4; + } + raster->unlockPalette(); + } + + int minw, minh; + int tw; + transferMinSize(image->depth == 4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + tw = max(image->width, minw); + uint8 *src = image->pixels; + out = raster->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + if(image->depth == 4){ + compressPal4(out, tw/2, src, image->stride, image->width, image->height); + }else if(image->depth == 8){ + copyPal8(out, tw, src, image->stride, image->width, image->height); + }else{ + for(int32 y = 0; y < image->height; y++){ + in = src; + for(int32 x = 0; x < image->width; x++){ + switch(image->depth){ + case 16: + conv_ARGB1555_from_ABGR1555(out, in); + out += 2; + break; + case 24: + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = 0x80; + out += 4; + break; + case 32: + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]*128/255; + out += 4; + break; + } + in += image->bpp; + } + src += image->stride; + } + } + raster->unlock(0); + return 1; +} + +Image* +rasterToImage(Raster *raster) +{ + Image *image; + int depth; + Ps2Raster *natras = GETPS2RASTEREXT(raster); + + int32 rasterFormat = raster->format & 0xF00; + switch(rasterFormat){ + case Raster::C1555: + depth = 16; + break; + case Raster::C8888: + depth = 32; + break; + case Raster::C888: + depth = 24; + break; + case Raster::C555: + depth = 16; + break; + + default: + case Raster::C565: + case Raster::C4444: + case Raster::LUM8: + assert(0 && "unsupported ps2 raster format"); + } + int32 pallength = 0; + if((raster->format & Raster::PAL4) == Raster::PAL4){ + depth = 4; + pallength = 16; + }else if((raster->format & Raster::PAL8) == Raster::PAL8){ + depth = 8; + pallength = 256; + } + + uint8 *in, *out; + image = Image::create(raster->width, raster->height, depth); + image->allocate(); + + if(pallength){ + out = image->palette; + in = raster->lockPalette(Raster::LOCKREAD); + if(rasterFormat == Raster::C8888){ + memcpy(out, in, pallength*4); + for(int32 i = 0; i < pallength; i++){ + out[3] = out[3]*255/128; + out += 4; + } + }else + memcpy(out, in, pallength*2); + raster->unlockPalette(); + } + + int minw, minh; + int tw; + transferMinSize(depth == 4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + tw = max(raster->width, minw); + uint8 *dst = image->pixels; + in = raster->lock(0, Raster::LOCKREAD); + if(depth == 4){ + expandPal4(dst, image->stride, in, tw/2, raster->width, raster->height); + }else if(depth == 8){ + copyPal8(dst, image->stride, in, tw, raster->width, raster->height); + }else{ + for(int32 y = 0; y < image->height; y++){ + out = dst; + for(int32 x = 0; x < image->width; x++){ + switch(raster->format & 0xF00){ + case Raster::C8888: + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]*255/128; + in += 4; + break; + case Raster::C888: + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + in += 4; + break; + case Raster::C1555: + conv_ARGB1555_from_ABGR1555(out, in); + in += 2; + break; + case Raster::C555: + conv_ARGB1555_from_ABGR1555(out, in); + out[1] |= 0x80; + in += 2; + break; + default: + assert(0 && "unknown ps2 raster format"); + break; + } + out += image->bpp; + } + dst += image->stride; + } + } + raster->unlock(0); + + return image; +} + +int32 +rasterNumLevels(Raster *raster) +{ + Ps2Raster *ras = GETPS2RASTEREXT(raster); + if(raster->pixels == nil) return 0; + if(raster->format & Raster::MIPMAP) + return MAXLEVEL(ras)+1; + return 1; +} + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + Ps2Raster *raster = GETPS2RASTEREXT(object); + raster->tex0 = 0; + raster->paletteBase = 0; + raster->kl = defaultMipMapKL; + raster->tex1low = 0; + raster->unk2 = 0; + raster->miptbp1 = 0; + raster->miptbp2 = 0; + raster->pixelSize = 0; + raster->paletteSize = 0; + raster->totalSize = 0; + raster->flags = 0; + + raster->dataSize = 0; + raster->data = nil; + return object; +} + +static void* +destroyNativeRaster(void *object, int32 offset, int32) +{ + Ps2Raster *raster = GETPS2RASTEREXT(object); + freealign(raster->data); + return object; +} + +static void* +copyNativeRaster(void *dst, void *src, int32 offset, int32) +{ + Ps2Raster *dstraster = GETPS2RASTEREXT(dst); + Ps2Raster *srcraster = GETPS2RASTEREXT(src); + *dstraster = *srcraster; + return dst; +} + +static Stream* +readMipmap(Stream *stream, int32, void *object, int32 offset, int32) +{ + uint16 val = stream->readI32(); + Texture *tex = (Texture*)object; + if(tex->raster == nil) + return stream; + Ps2Raster *raster = GETPS2RASTEREXT(tex->raster); + raster->kl = val; + return stream; +} + +static Stream* +writeMipmap(Stream *stream, int32, void *object, int32 offset, int32) +{ + Texture *tex = (Texture*)object; + if(tex->raster){ + stream->writeI32(defaultMipMapKL); + return stream; + } + Ps2Raster *raster = GETPS2RASTEREXT(tex->raster); + stream->writeI32(raster->kl); + return stream; +} + +static int32 +getSizeMipmap(void*, int32, int32) +{ + return rw::platform == PLATFORM_PS2 ? 4 : 0; +} + +void +registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(Ps2Raster), + ID_RASTERPS2, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); + + Texture::registerPlugin(0, ID_SKYMIPMAP, nil, nil, nil); + Texture::registerPluginStream(ID_SKYMIPMAP, readMipmap, writeMipmap, getSizeMipmap); +} + +void +printTEX0(uint64 tex0) +{ + printf("%016lX ", tex0); + uint32 tbp0 = tex0 & 0x3FFF; tex0 >>= 14; + uint32 tbw = tex0 & 0x3F; tex0 >>= 6; + uint32 psm = tex0 & 0x3F; tex0 >>= 6; + uint32 tw = tex0 & 0xF; tex0 >>= 4; + uint32 th = tex0 & 0xF; tex0 >>= 4; + uint32 tcc = tex0 & 0x1; tex0 >>= 1; + uint32 tfx = tex0 & 0x3; tex0 >>= 2; + uint32 cbp = tex0 & 0x3FFF; tex0 >>= 14; + uint32 cpsm = tex0 & 0xF; tex0 >>= 4; + uint32 csm = tex0 & 0x1; tex0 >>= 1; + uint32 csa = tex0 & 0x1F; tex0 >>= 5; + uint32 cld = tex0 & 0x7; + printf("TBP0:%4X TBW:%2X PSM:%2X TW:%X TH:%X TCC:%X TFX:%X CBP:%4X CPSM:%X CSM:%X CSA:%2X CLD:%X\n", + tbp0, tbw, psm, tw, th, tcc, tfx, cbp, cpsm, csm, csa, cld); +} + +void +printTEX1(uint64 tex1) +{ + printf("%016lX ", tex1); + uint32 lcm = tex1 & 0x1; tex1 >>= 2; + uint32 mxl = tex1 & 0x7; tex1 >>= 3; + uint32 mmag = tex1 & 0x1; tex1 >>= 1; + uint32 mmin = tex1 & 0x7; tex1 >>= 3; + uint32 mtba = tex1 & 0x1; tex1 >>= 10; + uint32 l = tex1 & 0x3; tex1 >>= 13; + uint32 k = tex1 & 0xFFF; + printf("LCM:%X MXL:%X MMAG:%X MMIN:%X MTBA:%X L:%X K:%X\n", + lcm, mxl, mmag, mmin, mtba, l, k); +} + +void +calcTEX1(Raster *raster, uint64 *tex1, int32 filter) +{ + enum { + NEAREST = 0, + LINEAR, + NEAREST_MIPMAP_NEAREST, + NEAREST_MIPMAP_LINEAR, + LINEAR_MIPMAP_NEAREST, + LINEAR_MIPMAP_LINEAR, + }; + Ps2Raster *natras = GETPS2RASTEREXT(raster); + uint64 t1 = natras->tex1low; + uint64 k = natras->kl & 0xFFF; + uint64 l = (natras->kl >> 12) & 0x3; + t1 |= k << 32; + t1 |= l << 19; + switch(filter){ + case Texture::NEAREST: + t1 |= (NEAREST << 5) | + (NEAREST << 6); + break; + case Texture::LINEAR: + t1 |= (LINEAR << 5) | + (LINEAR << 6); + break; + case Texture::MIPNEAREST: + t1 |= (NEAREST << 5) | + (NEAREST_MIPMAP_NEAREST << 6); + break; + case Texture::MIPLINEAR: + t1 |= (LINEAR << 5) | + (LINEAR_MIPMAP_NEAREST << 6); + break; + case Texture::LINEARMIPNEAREST: + t1 |= (NEAREST << 5) | + (NEAREST_MIPMAP_LINEAR << 6); + break; + case Texture::LINEARMIPLINEAR: + t1 |= (LINEAR << 5) | + (LINEAR_MIPMAP_LINEAR << 6); + break; + } + *tex1 = t1; +} + +struct StreamRasterExt +{ + int32 width; + int32 height; + int32 depth; + uint16 rasterFormat; + int16 version; + uint64 tex0; + uint32 paletteOffset; + uint32 tex1low; + uint64 miptbp1; + uint64 miptbp2; + uint32 pixelSize; + uint32 paletteSize; + uint32 totalSize; + uint32 mipmapVal; +}; + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 length, oldversion, version; + uint32 fourcc; + Raster *raster; + Ps2Raster *natras; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + fourcc = stream->readU32(); + if(fourcc != FOURCC_PS2){ + RWERROR((ERR_PLATFORM, fourcc)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + // Texture + tex->filterAddressing = stream->readU32(); + if(!findChunk(stream, ID_STRING, &length, nil)){ + RWERROR((ERR_CHUNK, "STRING")); + goto fail; + } + stream->read8(tex->name, length); + if(!findChunk(stream, ID_STRING, &length, nil)){ + RWERROR((ERR_CHUNK, "STRING")); + goto fail; + } + stream->read8(tex->mask, length); + + // Raster + StreamRasterExt streamExt; + oldversion = rw::version; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + ASSERTLITTLE; + stream->read8(&streamExt, 0x40); +/* +printf("%X %X %X %X %X %016llX %X %X %016llX %016llX %X %X %X %X\n", +streamExt.width, +streamExt.height, +streamExt.depth, +streamExt.rasterFormat, +streamExt.version, +streamExt.tex0, +streamExt.paletteOffset, +streamExt.tex1low, +streamExt.miptbp1, +streamExt.miptbp2, +streamExt.pixelSize, +streamExt.paletteSize, +streamExt.totalSize, +streamExt.mipmapVal); +*/ + noNewStyleRasters = streamExt.version < 2; + rw::version = version; + raster = Raster::create(streamExt.width, streamExt.height, + streamExt.depth, streamExt.rasterFormat, + PLATFORM_PS2); + noNewStyleRasters = 0; + rw::version = oldversion; + tex->raster = raster; + natras = GETPS2RASTEREXT(raster); +//printf("%X %X\n", natras->paletteBase, natras->tex1low); +// printf("%08X%08X %08X%08X %08X%08X\n", +// (uint32)natras->tex0, (uint32)(natras->tex0>>32), +// (uint32)natras->miptbp1, (uint32)(natras->miptbp1>>32), +// (uint32)natras->miptbp2, (uint32)(natras->miptbp2>>32)); +// printTEX0(natras->tex0); + uint64 tex1; + calcTEX1(raster, &tex1, tex->filterAddressing & 0xF); +// printTEX1(tex1); + + // TODO: GTA SA LD_OTB.txd loses here + assert(natras->pixelSize >= streamExt.pixelSize); + assert(natras->paletteSize >= streamExt.paletteSize); + +//if(natras->tex0 != streamExt.tex0){ +//printf("TEX0: %016llX\n %016llX\n", natras->tex0, streamExt.tex0); +//printTEX0(natras->tex0); +//printTEX0(streamExt.tex0); +//fflush(stdout); +//} +//if(natras->tex1low != streamExt.tex1low) +//printf("TEX1: %08X\n %08X\n", natras->tex1low, streamExt.tex1low); +//if(natras->miptbp1 != streamExt.miptbp1) +//printf("MIP1: %016llX\n %016llX\n", natras->miptbp1, streamExt.miptbp1); +//if(natras->miptbp2 != streamExt.miptbp2) +//printf("MIP2: %016llX\n %016llX\n", natras->miptbp2, streamExt.miptbp2); +//if(natras->paletteBase != streamExt.paletteOffset) +//printf("PAL: %08X\n %08X\n", natras->paletteBase, streamExt.paletteOffset); +//if(natras->pixelSize != streamExt.pixelSize) +//printf("PXS: %08X\n %08X\n", natras->pixelSize, streamExt.pixelSize); +//if(natras->paletteSize != streamExt.paletteSize) +//printf("PLS: %08X\n %08X\n", natras->paletteSize, streamExt.paletteSize); +//if(natras->totalSize != streamExt.totalSize) +//printf("TSZ: %08X\n %08X\n", natras->totalSize, streamExt.totalSize); + + // junk addresses, no need to store them + streamExt.tex0 &= ~0x3FFFULL; + streamExt.tex0 &= ~(0x3FFFULL << 37); + + assert(natras->tex0 == streamExt.tex0); + natras->tex0 = streamExt.tex0; + assert(natras->paletteBase == streamExt.paletteOffset); + natras->paletteBase = streamExt.paletteOffset; + assert(natras->tex1low == streamExt.tex1low); + natras->tex1low = streamExt.tex1low; + assert(natras->miptbp1 == streamExt.miptbp1); + natras->miptbp1 = streamExt.miptbp1; + assert(natras->miptbp2 == streamExt.miptbp2); + natras->miptbp2 = streamExt.miptbp2; + assert(natras->pixelSize == streamExt.pixelSize); + natras->pixelSize = streamExt.pixelSize; + assert(natras->paletteSize == streamExt.paletteSize); + natras->paletteSize = streamExt.paletteSize; + assert(natras->totalSize == streamExt.totalSize); + natras->totalSize = streamExt.totalSize; + natras->kl = streamExt.mipmapVal; +//printf("%X %X\n", natras->paletteBase, natras->tex1low); +// printf("%08X%08X %08X%08X %08X%08X\n", +// (uint32)natras->tex0, (uint32)(natras->tex0>>32), +// (uint32)natras->miptbp1, (uint32)(natras->miptbp1>>32), +// (uint32)natras->miptbp2, (uint32)(natras->miptbp2>>32)); +// printTEX0(natras->tex0); + calcTEX1(raster, &tex1, tex->filterAddressing & 0xF); +// printTEX1(tex1); + + // this is weird stuff + if(streamExt.version < 2){ + if(streamExt.version == 1){ + // Version 1 has swizzled 8 bit textures + if(!(natras->flags & Ps2Raster::NEWSTYLE)) + natras->flags |= Ps2Raster::SWIZZLED8; + else + assert(0 && "can't happen"); + }else{ + // Version 0 has no swizzling at all + if(!(natras->flags & Ps2Raster::NEWSTYLE)) + natras->flags &= ~Ps2Raster::SWIZZLED8; + else + assert(0 && "can't happen"); + } + } + + if(!findChunk(stream, ID_STRUCT, &length, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + if(streamExt.version < 2){ + stream->read8(raster->pixels, length); + }else{ + stream->read8(((Ps2Raster::PixelPtr*)raster->originalPixels)->pixels, natras->pixelSize); + stream->read8(raster->palette-0x50, natras->paletteSize); + } +//printf("\n"); + return tex; + +fail: + tex->destroy(); + return nil; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + Raster *raster = tex->raster; + Ps2Raster *ras = GETPS2RASTEREXT(raster); + writeChunkHeader(stream, ID_STRUCT, 8); + stream->writeU32(FOURCC_PS2); + stream->writeU32(tex->filterAddressing); + int32 len = strlen(tex->name)+4 & ~3; + writeChunkHeader(stream, ID_STRING, len); + stream->write8(tex->name, len); + len = strlen(tex->mask)+4 & ~3; + writeChunkHeader(stream, ID_STRING, len); + stream->write8(tex->mask, len); + + int32 sz = ras->pixelSize + ras->paletteSize; + writeChunkHeader(stream, ID_STRUCT, 12 + 64 + 12 + sz); + writeChunkHeader(stream, ID_STRUCT, 64); + StreamRasterExt streamExt; + streamExt.width = raster->width; + streamExt.height = raster->height; + streamExt.depth = raster->depth; + streamExt.rasterFormat = raster->format | raster->type; + streamExt.version = 0; + if(ras->flags == Ps2Raster::SWIZZLED8 && raster->depth == 8) + streamExt.version = 1; + if(ras->flags & Ps2Raster::NEWSTYLE) + streamExt.version = 2; + streamExt.tex0 = ras->tex0; + streamExt.paletteOffset = ras->paletteBase; + streamExt.tex1low = ras->tex1low; + streamExt.miptbp1 = ras->miptbp1; + streamExt.miptbp2 = ras->miptbp2; + streamExt.pixelSize = ras->pixelSize; + streamExt.paletteSize = ras->paletteSize; + streamExt.totalSize = ras->totalSize; + streamExt.mipmapVal = ras->kl; + ASSERTLITTLE; + stream->write8(&streamExt, 64); + + writeChunkHeader(stream, ID_STRUCT, sz); + if(streamExt.version < 2){ + stream->write8(raster->pixels, sz); + }else{ + stream->write8(((Ps2Raster::PixelPtr*)raster->originalPixels)->pixels, ras->pixelSize); + stream->write8(raster->palette-0x50, ras->paletteSize); + } +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 8; + size += 12 + strlen(tex->name)+4 & ~3; + size += 12 + strlen(tex->mask)+4 & ~3; + size += 12; + size += 12 + 64; + Ps2Raster *ras = GETPS2RASTEREXT(tex->raster); + size += 12 + ras->pixelSize + ras->paletteSize; + return size; +} + +} +} diff --git a/vendor/librw/src/ps2/ps2skin.cpp b/vendor/librw/src/ps2/ps2skin.cpp new file mode 100644 index 0000000..d4c9d79 --- /dev/null +++ b/vendor/librw/src/ps2/ps2skin.cpp @@ -0,0 +1,334 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +#include "rwps2impl.h" + +#define PLUGIN_ID ID_SKIN + +namespace rw { +namespace ps2 { + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_PS2] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_PS2])->groupPipeline->destroy(); + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_PS2])->groupPipeline = nil; + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_PS2])->destroy(); + skinGlobals.pipelines[PLATFORM_PS2] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_PS2, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + MatPipeline *pipe = MatPipeline::create(); + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + pipe->attribs[AT_NORMAL+1] = &attribWeights; + uint32 vertCount = MatPipeline::getVertCount(VU_Lights-0x100, 5, 3, 2); + pipe->setTriBufferSizes(5, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = genericUninstanceCB; + pipe->preUninstCB = skinPreCB; + pipe->postUninstCB = skinPostCB; + + ObjPipeline *opipe = ObjPipeline::create(); + opipe->pluginID = ID_SKIN; + opipe->pluginData = 1; + opipe->groupPipeline = pipe; + return opipe; +} + +Stream* +readNativeSkin(Stream *stream, int32, void *object, int32 offset) +{ + uint8 header[4]; + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_PS2){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + stream->read8(header, 4); + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, offset) = skin; + + // numUsedBones and numWeights appear in/after 34003 + // but not in/before 33002 (probably rw::version >= 0x34000) + bool oldFormat = header[1] == 0; + + // Use numBones for numUsedBones to allocate data + if(oldFormat) + skin->init(header[0], header[0], 0); + else + skin->init(header[0], header[1], 0); + skin->numWeights = header[2]; + + if(!oldFormat) + stream->read8(skin->usedBones, skin->numUsedBones); + if(skin->numBones) + stream->read32(skin->inverseMatrices, skin->numBones*64); + + // dummy data in case we need to write data in the new format + if(oldFormat){ + skin->numWeights = 4; + for(int32 i = 0; i < skin->numUsedBones; i++) + skin->usedBones[i] = i; + } + + if(!oldFormat){ + // TODO: what is this? + stream->seek(4*4); + + readSkinSplitData(stream, skin); + } + return stream; +} + +Stream* +writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset) +{ + uint8 header[4]; + + writeChunkHeader(stream, ID_STRUCT, len-12); + stream->writeU32(PLATFORM_PS2); + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + // not sure which version introduced the new format + bool oldFormat = version < 0x34000; + header[0] = skin->numBones; + if(oldFormat){ + header[1] = 0; + header[2] = 0; + }else{ + header[1] = skin->numUsedBones; + header[2] = skin->numWeights; + } + header[3] = 0; + stream->write8(header, 4); + + if(!oldFormat) + stream->write8(skin->usedBones, skin->numUsedBones); + stream->write32(skin->inverseMatrices, skin->numBones*64); + if(!oldFormat){ + uint32 buffer[4] = { 0, 0, 0, 0, }; + stream->write32(buffer, 4*4); + + writeSkinSplitData(stream, skin); + } + return stream; +} + +int32 +getSizeNativeSkin(void *object, int32 offset) +{ + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin == nil) + return -1; + int32 size = 12 + 4 + 4 + skin->numBones*64; + // not sure which version introduced the new format + if(version >= 0x34000) + size += skin->numUsedBones + 16 + skinSplitDataSize(skin); + return size; +} + +void +instanceSkinData(Geometry*, Mesh *m, Skin *skin, uint32 *data) +{ + uint16 j; + float32 *weights = (float32*)data; + uint32 *indices = data; + for(uint32 i = 0; i < m->numIndices; i++){ + j = m->indices[i]; + for(int32 k = 0; k < 4; k++){ + *weights++ = skin->weights[j*4+k]; + *indices &= ~0x3FF; + *indices++ |= skin->indices[j*4+k] && skin->weights[j*4+k] ? + (skin->indices[j*4+k]+1) << 2 : 0; + } + } +} + +void +skinInstanceCB(MatPipeline *, Geometry *g, Mesh *m, uint8 **data) +{ + Skin *skin = Skin::get(g); + if(skin == nil) + return; + instanceSkinData(g, m, skin, (uint32*)data[4]); +} + +// TODO: call base function perhaps? +int32 +findVertexSkin(Geometry *g, uint32 flags[], uint32 mask, Vertex *v) +{ + Skin *skin = Skin::get(g); + float32 *wghts = nil; + uint8 *inds = nil; + if(skin){ + wghts = skin->weights; + inds = skin->indices; + } + + V3d *verts = g->morphTargets[0].vertices; + TexCoords *tex = g->texCoords[0]; + TexCoords *tex1 = g->texCoords[1]; + V3d *norms = g->morphTargets[0].normals; + RGBA *cols = g->colors; + + for(int32 i = 0; i < g->numVertices; i++){ + uint32 flag = flags ? flags[i] : ~0; + if(mask & flag & 0x1 && !equal(*verts, v->p)) + goto cont; + if(mask & flag & 0x10 && !equal(*norms, v->n)) + goto cont; + if(mask & flag & 0x100 && !equal(*cols, v->c)) + goto cont; + if(mask & flag & 0x1000 && !equal(*tex, v->t)) + goto cont; + if(mask & flag & 0x2000 && !equal(*tex1, v->t1)) + goto cont; + if(mask & flag & 0x10000 && + !(wghts[0] == v->w[0] && wghts[1] == v->w[1] && + wghts[2] == v->w[2] && wghts[3] == v->w[3] && + inds[0] == v->i[0] && inds[1] == v->i[1] && + inds[2] == v->i[2] && inds[3] == v->i[3])) + goto cont; + return i; + cont: + verts++; + tex++; + tex1++; + norms++; + cols++; + wghts += 4; + inds += 4; + } + return -1; +} + +void +insertVertexSkin(Geometry *geo, int32 i, uint32 mask, Vertex *v) +{ + Skin *skin = Skin::get(geo); + insertVertex(geo, i, mask, v); + if(mask & 0x10000){ + memcpy(&skin->weights[i*4], v->w, 16); + memcpy(&skin->indices[i*4], v->i, 4); + } +} + +/* +void +skinUninstanceCB(MatPipeline*, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]) +{ + float32 *verts = (float32*)data[AT_XYZ]; + float32 *texcoords = (float32*)data[AT_UV]; + uint8 *colors = (uint8*)data[AT_RGBA]; + int8 *norms = (int8*)data[AT_NORMAL]; + uint32 *wghts = (uint32*)data[AT_NORMAL+1]; + uint32 mask = 0x1; // vertices + if(geo->flags & Geometry::NORMALS) + mask |= 0x10; + if(geo->flags & Geometry::PRELIT) + mask |= 0x100; + if(geo->numTexCoordSets > 0) + mask |= 0x1000; + mask |= 0x10000; + + Vertex v; + for(uint32 i = 0; i < mesh->numIndices; i++){ + if(mask & 0x1) + memcpy(&v.p, verts, 12); + if(mask & 0x10){ + v.n[0] = norms[0]/127.0f; + v.n[1] = norms[1]/127.0f; + v.n[2] = norms[2]/127.0f; + } + if(mask & 0x100) + memcpy(&v.c, colors, 4); + if(mask & 0x1000) + memcpy(&v.t, texcoords, 8); + for(int j = 0; j < 4; j++){ + ((uint32*)v.w)[j] = wghts[j] & ~0x3FF; + v.i[j] = (wghts[j] & 0x3FF) >> 2; + if(v.i[j]) v.i[j]--; + if(v.w[j] == 0.0f) v.i[j] = 0; + } + int32 idx = findVertexSkin(geo, flags, mask, &v); + if(idx < 0) + idx = geo->numVertices++; + mesh->indices[i] = idx; + flags[idx] = mask; + insertVertexSkin(geo, idx, mask, &v); + verts += 3; + texcoords += 2; + colors += 4; + norms += 3; + wghts += 4; + } +} +*/ + +void +skinPreCB(MatPipeline*, Geometry *geo) +{ + Skin *skin = Skin::get(geo); + if(skin == nil) + return; + uint8 *data = skin->data; + float *invMats = skin->inverseMatrices; + // meshHeader->totalIndices is highest possible number of vertices again + skin->init(skin->numBones, skin->numBones, geo->meshHeader->totalIndices); + memcpy(skin->inverseMatrices, invMats, skin->numBones*64); + rwFree(data); +} + +void +skinPostCB(MatPipeline*, Geometry *geo) +{ + Skin *skin = Skin::get(geo); + if(skin){ + skin->findNumWeights(geo->numVertices); + skin->findUsedBones(geo->numVertices); + } +} + +} +} diff --git a/vendor/librw/src/ps2/rwps2.h b/vendor/librw/src/ps2/rwps2.h new file mode 100644 index 0000000..899bb86 --- /dev/null +++ b/vendor/librw/src/ps2/rwps2.h @@ -0,0 +1,258 @@ +namespace rw { + +#ifdef RW_PS2 +struct EngineOpenParams +{ +}; +#endif + +namespace ps2 { + +void registerPlatformPlugins(void); + +extern Device renderdevice; + +struct Im2DVertex +{ + float32 x, y, z, w; + uint32 r, g, b, a; + float32 u, v, q, PAD; + + void setScreenX(float32 x) { this->x = x; } + void setScreenY(float32 y) { this->y = y; } + void setScreenZ(float32 z) { this->z = z; } + void setCameraZ(float32 z) { this->w = z; } + void setRecipCameraZ(float32 recipz) { this->q = recipz; } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { + this->r = r; this->g = g; this->b = b; this->a = a; } + void setU(float32 u, float recipz) { this->u = u; } + void setV(float32 v, float recipz) { this->v = v; } + + float getScreenX(void) { return this->x; } + float getScreenY(void) { return this->y; } + float getScreenZ(void) { return this->z; } + float getCameraZ(void) { return this->w; } + float getRecipCameraZ(void) { return this->q; } + RGBA getColor(void) { return makeRGBA(this->r, this->g, this->b, this->a); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + + +struct InstanceData +{ + uint32 dataSize; + uint8 *dataRaw; + uint8 *data; + Material *material; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + uint32 numMeshes; + InstanceData *instanceMeshes; +}; + +enum { + VU_Lights = 0x3d0 +}; + +enum PS2Attribs { + AT_V2_32 = 0x64000000, + AT_V2_16 = 0x65000000, + AT_V2_8 = 0x66000000, + AT_V3_32 = 0x68000000, + AT_V3_16 = 0x69000000, + AT_V3_8 = 0x6A000000, + AT_V4_32 = 0x6C000000, + AT_V4_16 = 0x6D000000, + AT_V4_8 = 0x6E000000, + AT_UNSGN = 0x00004000, + + AT_RW = 0x6 +}; + +// Not really types as in RW but offsets +enum PS2AttibTypes { + AT_XYZ = 0, + AT_UV = 1, + AT_RGBA = 2, + AT_NORMAL = 3 +}; + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +void printDMA(InstanceData *inst); +void printDMAVIF(InstanceData *inst); +void sizedebug(InstanceData *inst); + +void fixDmaOffsets(InstanceData *inst); // only RW_PS2 +int32 unfixDmaOffsets(InstanceData *inst); + +struct PipeAttribute +{ + const char *name; + uint32 attrib; +}; + +extern PipeAttribute attribXYZ; +extern PipeAttribute attribXYZW; +extern PipeAttribute attribUV; +extern PipeAttribute attribUV2; +extern PipeAttribute attribRGBA; +extern PipeAttribute attribNormal; +extern PipeAttribute attribWeights; + +class MatPipeline : public rw::Pipeline +{ +public: + uint32 vifOffset; + uint32 inputStride; + // number of vertices for tri strips and lists + uint32 triStripCount, triListCount; + PipeAttribute *attribs[10]; + void (*instanceCB)(MatPipeline*, Geometry*, Mesh*, uint8**); + void (*uninstanceCB)(MatPipeline*, Geometry*, uint32*, Mesh*, uint8**); + void (*preUninstCB)(MatPipeline*, Geometry*); + void (*postUninstCB)(MatPipeline*, Geometry*); + // RW has more: + // instanceTestCB() + // resEntryAllocCB() + // bridgeCB() + // postMeshCB() + // vu1code + // primtype + + static uint32 getVertCount(uint32 top, uint32 inAttribs, + uint32 outAttribs, uint32 outBufs) { + return (top-outBufs)/(inAttribs*2+outAttribs*outBufs); + } + + void init(void); + static MatPipeline *create(void); + void destroy(void); + void dump(void); + void setTriBufferSizes(uint32 inputStride, uint32 bufferSize); + void instance(Geometry *g, InstanceData *inst, Mesh *m); + uint8 *collectData(Geometry *g, InstanceData *inst, Mesh *m, uint8 *data[]); +}; + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + MatPipeline *groupPipeline; + // RW has more: + // setupCB() + // finalizeCB() + // lightOffset + // lightSize +}; + +struct Vertex { + V3d p; + TexCoords t; + TexCoords t1; + RGBA c; + V3d n; + // skin + float32 w[4]; + uint8 i[4]; +}; + +void insertVertex(Geometry *geo, int32 i, uint32 mask, Vertex *v); + +extern ObjPipeline *defaultObjPipe; +extern MatPipeline *defaultMatPipe; + +void genericUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]); +void genericPreCB(MatPipeline *pipe, Geometry *geo); // skin and ADC +//void defaultUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]); +void skinInstanceCB(MatPipeline *, Geometry *g, Mesh *m, uint8 **data); +//void skinUninstanceCB(MatPipeline*, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]); + +ObjPipeline *makeDefaultPipeline(void); +void dumpPipeline(rw::Pipeline *pipe); + +// ADC plugin + +// Each element in adcBits corresponds to an index in Mesh->indices, +// this assumes the Mesh indices are ADC formatted. +// ADCData->numBits != Mesh->numIndices. ADCData->numBits is probably +// equal to Mesh->numIndices before the Mesh gets ADC formatted. +// +// Can't convert between ADC-formatted and non-ADC-formatted yet :( + +struct ADCData +{ + bool32 adcFormatted; + int8 *adcBits; + int32 numBits; +}; +extern int32 adcOffset; +void registerADCPlugin(void); + +int8 *getADCbits(Geometry *geo); +int8 *getADCbitsForMesh(Geometry *geo, Mesh *mesh); +void convertADC(Geometry *g); +void unconvertADC(Geometry *geo); +void allocateADC(Geometry *geo); + +// PDS plugin + +Pipeline *getPDSPipe(uint32 data); +void registerPDSPipe(Pipeline *pipe); +void registerPDSPlugin(int32 n); +void registerPluginPDSPipes(void); + +// Native Texture and Raster + +struct Ps2Raster +{ + enum Flags { + NEWSTYLE = 0x1, // has GIF tags and transfer DMA chain + SWIZZLED8 = 0x2, + SWIZZLED4 = 0x4 + }; + struct PixelPtr { + // RW has pixels as second element but we don't want this struct + // to be longer than 16 bytes + uint8 *pixels; + // palette can be allocated in last level, in that case numTransfers is + // one less than numTotalTransfers. + int32 numTransfers; + int32 numTotalTransfers; + }; + + uint64 tex0; + uint32 paletteBase; // block address from beginning of GS data (words/64) + uint16 kl; + uint8 tex1low; // MXL and LCM of TEX1 + uint8 unk2; + uint64 miptbp1; + uint64 miptbp2; + uint32 pixelSize; // in bytes + uint32 paletteSize; // in bytes + uint32 totalSize; // total size of texture on GS in words + int8 flags; + + uint8 *data; //tmp + uint32 dataSize; +}; + +extern int32 nativeRasterOffset; +void registerNativeRaster(void); +#define GETPS2RASTEREXT(raster) PLUGINOFFSET(rw::ps2::Ps2Raster, raster, rw::ps2::nativeRasterOffset) + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +} +} diff --git a/vendor/librw/src/ps2/rwps2impl.h b/vendor/librw/src/ps2/rwps2impl.h new file mode 100644 index 0000000..f455bd2 --- /dev/null +++ b/vendor/librw/src/ps2/rwps2impl.h @@ -0,0 +1,16 @@ +namespace rw { +namespace ps2 { + +Raster *rasterCreate(Raster *raster); +uint8 *rasterLock(Raster*, int32 level, int32 lockMode); +void rasterUnlock(Raster*, int32 level); +uint8 *rasterLockPalette(Raster*, int32 lockMode); +void rasterUnlockPalette(Raster*); +int32 rasterNumLevels(Raster*); +bool32 imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); +bool32 rasterFromImage(Raster *raster, Image *image); +Image *rasterToImage(Raster *raster); + +} +} diff --git a/vendor/librw/src/ps2/rwps2plg.h b/vendor/librw/src/ps2/rwps2plg.h new file mode 100644 index 0000000..8776a51 --- /dev/null +++ b/vendor/librw/src/ps2/rwps2plg.h @@ -0,0 +1,27 @@ +namespace rw { +namespace ps2 { + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Skin plugin + +void initSkin(void); +ObjPipeline *makeSkinPipeline(void); + +void insertVertexSkin(Geometry *geo, int32 i, uint32 mask, Vertex *v); +int32 findVertexSkin(Geometry *g, uint32 flags[], uint32 mask, Vertex *v); + +Stream *readNativeSkin(Stream *stream, int32, void *object, int32 offset); +Stream *writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset); +int32 getSizeNativeSkin(void *object, int32 offset); + +void instanceSkinData(Geometry *g, Mesh *m, Skin *skin, uint32 *data); + +void skinPreCB(MatPipeline*, Geometry*); +void skinPostCB(MatPipeline*, Geometry*); + +} +} diff --git a/vendor/librw/src/raster.cpp b/vendor/librw/src/raster.cpp new file mode 100644 index 0000000..c7ebffe --- /dev/null +++ b/vendor/librw/src/raster.cpp @@ -0,0 +1,559 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +//#include "ps2/rwps2.h" +#include "d3d/rwd3d.h" +#include "d3d/rwxbox.h" +//#include "d3d/rwd3d8.h" +//#include "d3d/rwd3d9.h" +#include "gl/rwgl3.h" + +#define PLUGIN_ID 0 + +namespace rw { + +int32 Raster::numAllocated; + +struct RasterGlobals +{ + int32 sp; + Raster *stack[32]; +}; +int32 rasterModuleOffset; + +#define RASTERGLOBAL(v) (PLUGINOFFSET(RasterGlobals, engine, rasterModuleOffset)->v) + +static void* +rasterOpen(void *object, int32 offset, int32 size) +{ + int i; + rasterModuleOffset = offset; + RASTERGLOBAL(sp) = -1; + for(i = 0; i < (int)nelem(RASTERGLOBAL(stack)); i++) + RASTERGLOBAL(stack)[i] = nil; + return object; +} + +static void* +rasterClose(void *object, int32 offset, int32 size) +{ + return object; +} + +void +Raster::registerModule(void) +{ + Engine::registerPlugin(sizeof(RasterGlobals), ID_RASTERMODULE, rasterOpen, rasterClose); +} + +Raster* +Raster::create(int32 width, int32 height, int32 depth, int32 format, int32 platform) +{ + // TODO: pass arguments through to the driver and create the raster there + Raster *raster = (Raster*)rwMalloc(s_plglist.size, MEMDUR_EVENT); // TODO + assert(raster != nil); + numAllocated++; + raster->parent = raster; + raster->offsetX = 0; + raster->offsetY = 0; + raster->platform = platform ? platform : rw::platform; + raster->type = format & 0x7; + raster->flags = format & 0xF8; + raster->privateFlags = 0; + raster->format = format & 0xFF00; + raster->width = width; + raster->height = height; + raster->depth = depth; + raster->stride = 0; + raster->pixels = raster->palette = nil; + s_plglist.construct(raster); + +// printf("%d %d %d %d\n", raster->type, raster->width, raster->height, raster->depth); + return engine->driver[raster->platform]->rasterCreate(raster); +} + +void +Raster::subRaster(Raster *parent, Rect *r) +{ + if((this->flags & DONTALLOCATE) == 0) + return; + this->width = r->w; + this->height = r->h; + this->offsetX = parent->offsetX + r->x; + this->offsetY = parent->offsetY + r->y; + this->parent = parent->parent; +} + +void +Raster::destroy(void) +{ + s_plglist.destruct(this); + rwFree(this); + numAllocated--; +} + +uint8* +Raster::lock(int32 level, int32 lockMode) +{ + return engine->driver[this->platform]->rasterLock(this, level, lockMode); +} + +void +Raster::unlock(int32 level) +{ + engine->driver[this->platform]->rasterUnlock(this, level); +} + +uint8* +Raster::lockPalette(int32 lockMode) +{ + return engine->driver[this->platform]->rasterLockPalette(this, lockMode); +} + +void +Raster::unlockPalette(void) +{ + engine->driver[this->platform]->rasterUnlockPalette(this); +} + +int32 +Raster::getNumLevels(void) +{ + return engine->driver[this->platform]->rasterNumLevels(this); +} + +int32 +Raster::calculateNumLevels(int32 width, int32 height) +{ + int32 size = width >= height ? width : height; + int32 n; + for(n = 0; size != 0; n++) + size /= 2; + return n; +} + +bool +Raster::formatHasAlpha(int32 format) +{ + return (format & 0xF00) == Raster::C8888 || + (format & 0xF00) == Raster::C1555 || + (format & 0xF00) == Raster::C4444; +} + +bool32 +Raster::imageFindRasterFormat(Image *image, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat, + int32 platform) +{ + return engine->driver[platform ? platform : rw::platform]->imageFindRasterFormat( + image, type, pWidth, pHeight, pDepth, pFormat); +} + +Raster* +Raster::setFromImage(Image *image, int32 platform) +{ + if(engine->driver[platform ? platform : rw::platform]->rasterFromImage(this, image)) + return this; + return nil; +} + +Raster* +Raster::createFromImage(Image *image, int32 platform) +{ + Raster *raster; + int32 width, height, depth, format; + if(!imageFindRasterFormat(image, TEXTURE, &width, &height, &depth, &format, platform)) + return nil; + raster = Raster::create(width, height, depth, format, platform); + if(raster == nil) + return nil; + return raster->setFromImage(image, platform); +} + +Image* +Raster::toImage(void) +{ + return engine->driver[this->platform]->rasterToImage(this); +} + +void +Raster::show(uint32 flags) +{ + engine->device.showRaster(this, flags); +} + +Raster* +Raster::pushContext(Raster *raster) +{ + RasterGlobals *g = PLUGINOFFSET(RasterGlobals, engine, rasterModuleOffset); + if(g->sp >= (int32)nelem(g->stack)-1) + return nil; + return g->stack[++g->sp] = raster; +} + +Raster* +Raster::popContext(void) +{ + RasterGlobals *g = PLUGINOFFSET(RasterGlobals, engine, rasterModuleOffset); + if(g->sp < 0) + return nil; + return g->stack[g->sp--]; +} + +Raster* +Raster::getCurrentContext(void) +{ + RasterGlobals *g = PLUGINOFFSET(RasterGlobals, engine, rasterModuleOffset); + if(g->sp < 0 || g->sp >= (int32)nelem(g->stack)) + return nil; + return g->stack[g->sp]; +} + +bool32 +Raster::renderFast(int32 x, int32 y) +{ + return engine->device.rasterRenderFast(this,x, y); +} + +void +conv_RGBA8888_from_RGBA8888(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; +} + +void +conv_BGRA8888_from_RGBA8888(uint8 *out, uint8 *in) +{ + out[2] = in[0]; + out[1] = in[1]; + out[0] = in[2]; + out[3] = in[3]; +} + +void +conv_RGBA8888_from_RGB888(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = 0xFF; +} + +void +conv_BGRA8888_from_RGB888(uint8 *out, uint8 *in) +{ + out[2] = in[0]; + out[1] = in[1]; + out[0] = in[2]; + out[3] = 0xFF; +} + +void +conv_RGB888_from_RGB888(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void +conv_BGR888_from_RGB888(uint8 *out, uint8 *in) +{ + out[2] = in[0]; + out[1] = in[1]; + out[0] = in[2]; +} + +void +conv_ARGB1555_from_ARGB1555(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1]; +} + +void +conv_ARGB1555_from_RGB555(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1] | 0x80; +} + +void +conv_RGBA5551_from_ARGB1555(uint8 *out, uint8 *in) +{ + uint32 r, g, b, a; + a = (in[1]>>7) & 1; + r = (in[1]>>2) & 0x1F; + g = (in[1]&3)<<3 | ((in[0]>>5)&7); + b = in[0] & 0x1F; + out[0] = a | b<<1 | g<<6; + out[1] = g>>2 | r<<3; +} + +void +conv_ARGB1555_from_RGBA5551(uint8 *out, uint8 *in) +{ + uint32 r, g, b, a; + a = in[0] & 1; + b = (in[0]>>1) & 0x1F; + g = (in[1]&7)<<2 | ((in[0]>>6)&3); + r = (in[1]>>3) & 0x1F; + out[0] = b | g<<5; + out[1] = g>>3 | r<<2 | a<<7; +} + +void +conv_RGBA8888_from_ARGB1555(uint8 *out, uint8 *in) +{ + uint32 r, g, b, a; + a = (in[1]>>7) & 1; + r = (in[1]>>2) & 0x1F; + g = (in[1]&3)<<3 | ((in[0]>>5)&7); + b = in[0] & 0x1F; + out[0] = r*0xFF/0x1f; + out[1] = g*0xFF/0x1f; + out[2] = b*0xFF/0x1f; + out[3] = a*0xFF; +} + +void +conv_ABGR1555_from_ARGB1555(uint8 *out, uint8 *in) +{ + uint32 r, b; + r = (in[1]>>2) & 0x1F; + b = in[0] & 0x1F; + out[1] = (in[1]&0x83) | b<<2; + out[0] = (in[0]&0xE0) | r; +} + +void +expandPal4(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w/2; x++){ + dst[y*dststride + x*2 + 0] = src[y*srcstride + x] & 0xF; + dst[y*dststride + x*2 + 1] = src[y*srcstride + x] >> 4; + } +} +void +compressPal4(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w/2; x++) + dst[y*dststride + x] = src[y*srcstride + x*2 + 0] | src[y*srcstride + x*2 + 1] << 4; +} + +void +expandPal4_BE(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w/2; x++){ + dst[y*dststride + x*2 + 1] = src[y*srcstride + x] & 0xF; + dst[y*dststride + x*2 + 0] = src[y*srcstride + x] >> 4; + } +} +void +compressPal4_BE(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w/2; x++) + dst[y*dststride + x] = src[y*srcstride + x*2 + 1] | src[y*srcstride + x*2 + 0] << 4; +} + +void +copyPal8(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w; x++) + dst[y*dststride + x] = src[y*srcstride + x]; +} + + + +// Platform conversion + +static rw::Raster* +xbox_to_d3d(rw::Raster *ras) +{ + using namespace rw; + + int dxt = 0; + xbox::XboxRaster *xboxras = GETXBOXRASTEREXT(ras); + if(xboxras->customFormat){ + switch(xboxras->format){ + case xbox::D3DFMT_DXT1: dxt = 1; break; + case xbox::D3DFMT_DXT3: dxt = 3; break; + case xbox::D3DFMT_DXT5: dxt = 5; break; + } + } + if(dxt == 0) + return nil; + + Raster *newras = Raster::create(ras->width, ras->height, ras->depth, + ras->format | Raster::TEXTURE | Raster::DONTALLOCATE); + int numLevels = ras->getNumLevels(); + d3d::allocateDXT(newras, dxt, numLevels, xboxras->hasAlpha); + for(int i = 0; i < numLevels; i++){ + uint8 *srcpx = ras->lock(i, Raster::LOCKREAD); + // uint8 *dstpx = newras->lock(i, Raster::LOCKWRITE | Raster::LOCKNOFETCH); + d3d::setTexels(newras, srcpx, i); + // flipDXT(dxt, dstpx, srcpx, ras->width, ras->height); + ras->unlock(i); + // newras->unlock(i); + } + + return newras; +} + +static rw::Raster* +d3d_to_gl3(rw::Raster *ras) +{ +#ifdef RW_GL3 + using namespace rw; + + if(!gl3::gl3Caps.dxtSupported) + return nil; + + int dxt = 0; + d3d::D3dRaster *d3dras = GETD3DRASTEREXT(ras); + if(d3dras->customFormat){ + switch(d3dras->format){ + case d3d::D3DFMT_DXT1: dxt = 1; break; + case d3d::D3DFMT_DXT3: dxt = 3; break; + case d3d::D3DFMT_DXT5: dxt = 5; break; + } + } + if(dxt == 0) + return nil; + + Raster *newras = Raster::create(ras->width, ras->height, ras->depth, + ras->format | Raster::TEXTURE | Raster::DONTALLOCATE); + int numLevels = ras->getNumLevels(); + gl3::allocateDXT(newras, dxt, numLevels, d3dras->hasAlpha); + for(int i = 0; i < numLevels; i++){ + uint8 *srcpx = ras->lock(i, Raster::LOCKREAD); + uint8 *dstpx = newras->lock(i, Raster::LOCKWRITE | Raster::LOCKNOFETCH); + flipDXT(dxt, dstpx, srcpx, ras->width, ras->height); + ras->unlock(i); + newras->unlock(i); + } + + return newras; +#else + return nil; +#endif +} + +static rw::Raster* +xbox_to_gl3(rw::Raster *ras) +{ +#ifdef RW_GL3 + using namespace rw; + + int dxt = 0; + xbox::XboxRaster *xboxras = GETXBOXRASTEREXT(ras); + if(xboxras->customFormat){ + switch(xboxras->format){ + case xbox::D3DFMT_DXT1: dxt = 1; break; + case xbox::D3DFMT_DXT3: dxt = 3; break; + case xbox::D3DFMT_DXT5: dxt = 5; break; + } + } + if(dxt == 0) + return nil; + + Raster *newras = Raster::create(ras->width, ras->height, ras->depth, + ras->format | Raster::TEXTURE | Raster::DONTALLOCATE); + int numLevels = ras->getNumLevels(); + gl3::allocateDXT(newras, dxt, numLevels, xboxras->hasAlpha); + for(int i = 0; i < numLevels; i++){ + uint8 *srcpx = ras->lock(i, Raster::LOCKREAD); + uint8 *dstpx = newras->lock(i, Raster::LOCKWRITE | Raster::LOCKNOFETCH); + flipDXT(dxt, dstpx, srcpx, ras->width, ras->height); + ras->unlock(i); + newras->unlock(i); + } + + return newras; +#else + return nil; +#endif +} + +rw::Raster* +Raster::convertTexToCurrentPlatform(rw::Raster *ras) +{ + using namespace rw; + + if(ras->platform == rw::platform) + return ras; + // compatible platforms + if((ras->platform == PLATFORM_D3D8 && rw::platform == PLATFORM_D3D9) || + (ras->platform == PLATFORM_D3D9 && rw::platform == PLATFORM_D3D8)) + return ras; + + // special cased conversion for DXT + if((ras->platform == PLATFORM_D3D8 || ras->platform == PLATFORM_D3D9) && rw::platform == PLATFORM_GL3){ + Raster *newras = d3d_to_gl3(ras); + if(newras){ + ras->destroy(); + return newras; + } + }else if(ras->platform == PLATFORM_XBOX && (rw::platform == PLATFORM_D3D9 || rw::platform == PLATFORM_D3D8)){ + Raster *newras = xbox_to_d3d(ras); + if(newras){ + ras->destroy(); + return newras; + } + }else if(ras->platform == PLATFORM_XBOX && rw::platform == PLATFORM_GL3){ + Raster *newras = xbox_to_gl3(ras); + if(newras){ + ras->destroy(); + return newras; + } + } + + // fall back to going through Image directly + int32 width, height, depth, format; + Image *img = ras->toImage(); + // TODO: maybe don't *always* do this? + img->unpalettize(); + Raster::imageFindRasterFormat(img, Raster::TEXTURE, &width, &height, &depth, &format); + format |= ras->format & (Raster::MIPMAP | Raster::AUTOMIPMAP); + Raster *newras = Raster::create(width, height, depth, format); + newras->setFromImage(img); + img->destroy(); + int numLevels = ras->getNumLevels(); + for(int i = 1; i < numLevels; i++){ + ras->lock(i, Raster::LOCKREAD); + img = ras->toImage(); + // TODO: maybe don't *always* do this? + img->unpalettize(); + newras->lock(i, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + newras->setFromImage(img); + newras->unlock(i); + ras->unlock(i); + } + ras->destroy(); + ras = newras; + return ras; +} + + +} diff --git a/vendor/librw/src/render.cpp b/vendor/librw/src/render.cpp new file mode 100644 index 0000000..6822c24 --- /dev/null +++ b/vendor/librw/src/render.cpp @@ -0,0 +1,96 @@ +#include + +#include "rwbase.h" +#include "rwplg.h" +#include "rwengine.h" +#include "rwrender.h" + +namespace rw { + +void SetRenderState(int32 state, uint32 value){ + engine->device.setRenderState(state, (void*)(uintptr)value); } + +void SetRenderStatePtr(int32 state, void *value){ + engine->device.setRenderState(state, value); } + +uint32 GetRenderState(int32 state){ + return (uint32)(uintptr)engine->device.getRenderState(state); } + +void *GetRenderStatePtr(int32 state){ + return engine->device.getRenderState(state); } + +// Im2D + +namespace im2d { + +float32 GetNearZ(void) { return engine->device.zNear; } +float32 GetFarZ(void) { return engine->device.zFar; } +void +RenderLine(void *verts, int32 numVerts, int32 vert1, int32 vert2) +{ + engine->device.im2DRenderLine(verts, numVerts, vert1, vert2); +} +void +RenderTriangle(void *verts, int32 numVerts, int32 vert1, int32 vert2, int32 vert3) +{ + engine->device.im2DRenderTriangle(verts, numVerts, vert1, vert2, vert3); +} +void +RenderIndexedPrimitive(PrimitiveType type, void *verts, int32 numVerts, void *indices, int32 numIndices) +{ + engine->device.im2DRenderIndexedPrimitive(type, verts, numVerts, indices, numIndices); +} +void +RenderPrimitive(PrimitiveType type, void *verts, int32 numVerts) +{ + engine->device.im2DRenderPrimitive(type, verts, numVerts); +} + +} + +// Im3D + +namespace im3d { + +void +Transform(void *vertices, int32 numVertices, Matrix *world, uint32 flags) +{ + engine->device.im3DTransform(vertices, numVertices, world, flags); +} +void +RenderLine(int32 vert1, int32 vert2) +{ + int16 indices[2]; + indices[0] = vert1; + indices[1] = vert2; + RenderIndexedPrimitive(rw::PRIMTYPELINELIST, indices, 2); +} +void +RenderTriangle(int32 vert1, int32 vert2, int32 vert3) +{ + int16 indices[3]; + indices[0] = vert1; + indices[1] = vert2; + indices[2] = vert3; + RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indices, 3); +} +void +RenderPrimitive(PrimitiveType primType) +{ + engine->device.im3DRenderPrimitive(primType); +} +void +RenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices) +{ + engine->device.im3DRenderIndexedPrimitive(primType, indices, numIndices); +} +void +End(void) +{ + engine->device.im3DEnd(); +} + +} + +} + diff --git a/vendor/librw/src/rwanim.h b/vendor/librw/src/rwanim.h new file mode 100644 index 0000000..e6dd1d5 --- /dev/null +++ b/vendor/librw/src/rwanim.h @@ -0,0 +1,195 @@ +#include + +namespace rw { + +struct Animation; + +// These sizes of these are sadly not platform independent +// because pointer sizes can vary. + +struct KeyFrameHeader +{ + KeyFrameHeader *prev; + float32 time; + + KeyFrameHeader *next(int32 sz){ + return (KeyFrameHeader*)((uint8*)this + sz); } +}; + +struct InterpFrameHeader +{ + KeyFrameHeader *keyFrame1; + KeyFrameHeader *keyFrame2; +}; + +struct AnimInterpolatorInfo +{ + typedef void (*ApplyCB)(void *result, void *frame); + typedef void (*BlendCB)(void *out, void *in1, void *in2, float32 a); + typedef void (*InterpCB)(void *out, void *in1, void *in2, float32 t, + void *custom); + typedef void (*AddCB)(void *out, void *in1, void *in2); + typedef void (*MulRecipCB)(void *frame, void *start); + + int32 id; + int32 interpKeyFrameSize; + int32 animKeyFrameSize; + int32 customDataSize; + + ApplyCB applyCB; + BlendCB blendCB; + InterpCB interpCB; + AddCB addCB; + MulRecipCB mulRecipCB; + void (*streamRead)(Stream *stream, Animation *anim); + void (*streamWrite)(Stream *stream, Animation *anim); + uint32 (*streamGetSize)(Animation *anim); + + static void registerInterp(AnimInterpolatorInfo *interpInfo); + static void unregisterInterp(AnimInterpolatorInfo *interpInfo); + static AnimInterpolatorInfo *find(int32 id); +}; + +struct Animation +{ + AnimInterpolatorInfo *interpInfo; + int32 numFrames; + int32 flags; + float32 duration; + void *keyframes; + void *customData; + + static Animation *create(AnimInterpolatorInfo*, int32 numFrames, + int32 flags, float duration); + void destroy(void); + int32 getNumNodes(void); + KeyFrameHeader *getAnimFrame(int32 n){ + return (KeyFrameHeader*)((uint8*)this->keyframes + + n*this->interpInfo->animKeyFrameSize); + } + static Animation *streamRead(Stream *stream); + static Animation *streamReadLegacy(Stream *stream); + bool streamWrite(Stream *stream); + bool streamWriteLegacy(Stream *stream); + uint32 streamGetSize(void); +}; + +struct AnimInterpolator +{ + Animation *currentAnim; + float32 currentTime; + void *nextFrame; + int32 maxInterpKeyFrameSize; + int32 currentInterpKeyFrameSize; + int32 currentAnimKeyFrameSize; + int32 numNodes; + // TODO some callbacks, parent/sub + // cached from the InterpolatorInfo + AnimInterpolatorInfo::ApplyCB applyCB; + AnimInterpolatorInfo::BlendCB blendCB; + AnimInterpolatorInfo::InterpCB interpCB; + AnimInterpolatorInfo::AddCB addCB; + // after this interpolated frames + + static AnimInterpolator *create(int32 numNodes, int32 maxKeyFrameSize); + void destroy(void); + bool32 setCurrentAnim(Animation *anim); + void addTime(float32 t); + void *getFrames(void){ return this+1;} + InterpFrameHeader *getInterpFrame(int32 n){ + return (InterpFrameHeader*)((uint8*)getFrames() + + n*currentInterpKeyFrameSize); + } + KeyFrameHeader *getAnimFrame(int32 n){ + return (KeyFrameHeader*)((uint8*)currentAnim->keyframes + + n*currentAnimKeyFrameSize); + } +}; + +// +// UV anim +// + +struct UVAnimParamData +{ + float32 theta; // rotation + float32 s0; // scale x + float32 s1; // scale y + float32 skew; // skew + float32 x; // x pos + float32 y; // y pos +}; + +struct UVAnimKeyFrame +{ + UVAnimKeyFrame *prev; + float32 time; + float32 uv[6]; +}; + +struct UVAnimInterpFrame +{ + UVAnimKeyFrame *keyFrame1; + UVAnimKeyFrame *keyFrame2; + float32 uv[6]; +}; + +struct UVAnimDictionary; + +// RW does it differently...maybe we should implement RtDict +// and make it more general? + +struct UVAnimCustomData +{ + char name[32]; + int32 nodeToUVChannel[8]; + int32 refCount; + + void destroy(Animation *anim); + static UVAnimCustomData *get(Animation *anim){ + return (UVAnimCustomData*)anim->customData; } +}; + +// This should be more general probably +struct UVAnimDictEntry +{ + Animation *anim; + LLLink inDict; + static UVAnimDictEntry *fromDict(LLLink *lnk){ + return LLLinkGetData(lnk, UVAnimDictEntry, inDict); } +}; + +// This too +struct UVAnimDictionary +{ + LinkList animations; + + static UVAnimDictionary *create(void); + void destroy(void); + int32 count(void) { return this->animations.count(); } + void add(Animation *anim); + Animation *find(const char *name); + + static UVAnimDictionary *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); +}; + +extern UVAnimDictionary *currentUVAnimDictionary; + +// Material plugin +struct UVAnim +{ + Matrix *uv[2]; + AnimInterpolator *interp[8]; + + static bool32 exists(Material *mat); + static void addTime(Material *mat, float32 t); + static void applyUpdate(Material *mat); +}; + +extern int32 uvAnimOffset; + +void registerUVAnimPlugin(void); + +} diff --git a/vendor/librw/src/rwbase.h b/vendor/librw/src/rwbase.h new file mode 100644 index 0000000..13891ca --- /dev/null +++ b/vendor/librw/src/rwbase.h @@ -0,0 +1,713 @@ +#ifndef RW_PS2 +#include +#endif +#include +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +// TODO: clean up the opengl defines +// and figure out what we even want here... +#ifdef RW_GL3 +#define RW_OPENGL +#define RWDEVICE gl3 +// doesn't help +//#define RW_GL_USE_VAOS +#endif + +#ifdef RW_GLES2 +#define RW_GLES +#endif +#ifdef RW_GLES3 +#define RW_GLES +#endif + +#ifdef RW_D3D9 +#define RWDEVICE d3d +#define RWHALFPIXEL +#endif + +#ifdef RW_D3D8 +#define RWDEVICE d3d +#endif + +#ifdef RW_PS2 +#define RWHALFPIXEL +#define RWDEVICE ps2 +#endif + +#ifdef RW_WDGL +#define RW_OPENGL +#endif + +namespace rw { + +#ifdef RW_PS2 + typedef char int8; + typedef short int16; + typedef int int32; + typedef long long int64; + typedef unsigned char uint8; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef unsigned long long uint64; + typedef unsigned int uintptr; +#else + /* get rid of the stupid _t */ + typedef int8_t int8; + typedef int16_t int16; + typedef int32_t int32; + typedef int64_t int64; + typedef uint8_t uint8; + typedef uint16_t uint16; + typedef uint32_t uint32; + typedef uint64_t uint64; + typedef uintptr_t uintptr; +#endif + +typedef float float32; +typedef int32 bool32; +typedef uint8 byte; +typedef uint32 uint; + +#define nil NULL + +#define nelem(A) (sizeof(A) / sizeof A[0]) + +#ifdef __GNUC__ +#define RWALIGN(n) __attribute__ ((aligned (n))) +#else +#ifdef _MSC_VER +#define RWALIGN(n) __declspec(align(n)) +#else +#define RWALIGN(n) // unknown compiler...ignore +#endif +#endif + +// Lists + +struct LLLink +{ + LLLink *next; + LLLink *prev; + void init(void){ + this->next = nil; + this->prev = nil; + } + void remove(void){ + this->prev->next = this->next; + this->next->prev = this->prev; + } +}; + +#define LLLinkGetData(linkvar,type,entry) \ + ((type*)(((rw::uint8*)(linkvar))-offsetof(type,entry))) + +// Have to be careful since the link might be deleted. +#define FORLIST(_link, _list) \ + for(rw::LLLink *_next = nil, *_link = (_list).link.next; \ + _next = (_link)->next, (_link) != (_list).end(); \ + (_link) = _next) + +struct LinkList +{ + LLLink link; + void init(void){ + this->link.next = &this->link; + this->link.prev = &this->link; + } + bool32 isEmpty(void){ + return this->link.next == &this->link; + } + void add(LLLink *link){ + link->next = this->link.next; + link->prev = &this->link; + this->link.next->prev = link; + this->link.next = link; + } + void append(LLLink *link){ + link->next = &this->link; + link->prev = this->link.prev; + this->link.prev->next = link; + this->link.prev = link; + } + LLLink *end(void){ + return &this->link; + } + int32 count(void){ + int32 n = 0; + FORLIST(lnk, (*this)) + n++; + return n; + } +}; + +// Mathematical types + +struct RGBA +{ + uint8 red; + uint8 green; + uint8 blue; + uint8 alpha; +}; +inline RGBA makeRGBA(uint8 r, uint8 g, uint8 b, uint8 a) { RGBA c = { r, g, b, a }; return c; } +inline bool32 equal(const RGBA &c1, const RGBA &c2) { return c1.red == c2.red && c1.green == c2.green && c1.blue == c2.blue && c1.alpha == c2.alpha; } +#define RWRGBAINT(r, g, b, a) ((uint32)((((a)&0xff)<<24)|(((b)&0xff)<<16)|(((g)&0xff)<<8)|((r)&0xff))) + +struct RGBAf +{ + float32 red; + float32 green; + float32 blue; + float32 alpha; +}; +inline RGBAf makeRGBAf(float32 r, float32 g, float32 b, float32 a) { RGBAf c = { r, g, b, a }; return c; } +inline bool32 equal(const RGBAf &c1, const RGBAf &c2) { return c1.red == c2.red && c1.green == c2.green && c1.blue == c2.blue && c1.alpha == c2.alpha; } +inline RGBAf add(const RGBAf &a, const RGBAf &b) { return makeRGBAf(a.red+b.red, a.green+b.green, a.blue+b.blue, a.alpha+b.alpha); } +inline RGBAf modulate(const RGBAf &a, const RGBAf &b) { return makeRGBAf(a.red*b.red, a.green*b.green, a.blue*b.blue, a.alpha*b.alpha); } +inline RGBAf scale(const RGBAf &a, float32 f) { return makeRGBAf(a.red*f, a.green*f, a.blue*f, a.alpha*f); } +inline void clamp(RGBAf *a) { + if(a->red > 1.0f) a->red = 1.0f; + if(a->red < 0.0f) a->red = 0.0f; + if(a->green > 1.0f) a->green = 1.0f; + if(a->green < 0.0f) a->green = 0.0f; + if(a->blue > 1.0f) a->blue = 1.0f; + if(a->blue < 0.0f) a->blue = 0.0f; + if(a->alpha > 1.0f) a->alpha = 1.0f; + if(a->alpha < 0.0f) a->alpha = 0.0f; +} + +inline void convColor(RGBA *i, const RGBAf *f){ + int32 c; + c = (int32)(f->red*255.0f + 0.5f); + i->red = (uint8)c; + c = (int32)(f->green*255.0f + 0.5f); + i->green = (uint8)c; + c = (int32)(f->blue*255.0f + 0.5f); + i->blue = (uint8)c; + c = (int32)(f->alpha*255.0f + 0.5f); + i->alpha = (uint8)c; +} + +inline void convColor(RGBAf *f, const RGBA *i){ + f->red = i->red/255.0f; + f->green = i->green/255.0f; + f->blue = i->blue/255.0f; + f->alpha = i->alpha/255.0f; +} + +struct TexCoords +{ + float32 u, v; +}; +inline bool32 equal(const TexCoords &t1, const TexCoords &t2) { return t1.u == t2.u && t1.v == t2.v; } + +struct V2d; +struct V3d; +struct Quat; +struct Matrix; + +struct V2d +{ + float32 x, y; + void set(float32 x, float32 y){ + this->x = x; this->y = y; } +}; + +inline V2d makeV2d(float32 x, float32 y) { V2d v = { x, y }; return v; } +inline bool32 equal(const V2d &v1, const V2d &v2) { return v1.x == v2.x && v1.y == v2.y; } +inline V2d neg(const V2d &a) { return makeV2d(-a.x, -a.y); } +inline V2d add(const V2d &a, const V2d &b) { return makeV2d(a.x+b.x, a.y+b.y); } +inline V2d sub(const V2d &a, const V2d &b) { return makeV2d(a.x-b.x, a.y-b.y); } +inline V2d scale(const V2d &a, float32 r) { return makeV2d(a.x*r, a.y*r); } +inline float32 length(const V2d &v) { return sqrtf(v.x*v.x + v.y*v.y); } +inline V2d normalize(const V2d &v) { return scale(v, 1.0f/length(v)); } + +struct V3d +{ + float32 x, y, z; + void set(float32 x, float32 y, float32 z){ + this->x = x; this->y = y; this->z = z; } + static void transformPoints(V3d *out, const V3d *in, int32 n, const Matrix *m); + static void transformVectors(V3d *out, const V3d *in, int32 n, const Matrix *m); +}; + +inline V3d makeV3d(float32 x, float32 y, float32 z) { V3d v = { x, y, z }; return v; } +inline bool32 equal(const V3d &v1, const V3d &v2) { return v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; } +inline V3d neg(const V3d &a) { return makeV3d(-a.x, -a.y, -a.z); } +inline V3d add(const V3d &a, const V3d &b) { return makeV3d(a.x+b.x, a.y+b.y, a.z+b.z); } +inline V3d sub(const V3d &a, const V3d &b) { return makeV3d(a.x-b.x, a.y-b.y, a.z-b.z); } +inline V3d scale(const V3d &a, float32 r) { return makeV3d(a.x*r, a.y*r, a.z*r); } +inline float32 length(const V3d &v) { return sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); } +inline V3d normalize(const V3d &v) { return scale(v, 1.0f/length(v)); } +inline V3d setlength(const V3d &v, float32 l) { return scale(v, l/length(v)); } +V3d cross(const V3d &a, const V3d &b); +inline float32 dot(const V3d &a, const V3d &b) { return a.x*b.x + a.y*b.y + a.z*b.z; } +inline V3d lerp(const V3d &a, const V3d &b, float32 r){ + return makeV3d(a.x + r*(b.x - a.x), + a.y + r*(b.y - a.y), + a.z + r*(b.z - a.z)); +}; + +struct V4d +{ + float32 x, y, z, w; +}; +inline bool32 equal(const V4d &v1, const V4d &v2) { return v1.x == v2.x && v1.y == v2.y && v1.z == v2.z && v1.w == v2.w; } + +enum CombineOp +{ + COMBINEREPLACE, + COMBINEPRECONCAT, + COMBINEPOSTCONCAT +}; + + +Quat makeQuat(float32 w, float32 x, float32 y, float32 z); +Quat makeQuat(float32 w, const V3d &vec); + +struct Quat +{ + // order is important for streaming and RW compatibility + float32 x, y, z, w; + + static Quat rotation(float32 angle, const V3d &axis){ + return makeQuat(cosf(angle/2.0f), scale(normalize(axis), sinf(angle/2.0f))); } + void set(float32 w, float32 x, float32 y, float32 z){ + this->w = w; this->x = x; this->y = y; this->z = z; } + V3d vec(void){ return makeV3d(x, y, z); } + + Quat *rotate(const V3d *axis, float32 angle, CombineOp op = rw::COMBINEPOSTCONCAT); +}; + +inline Quat makeQuat(float32 w, float32 x, float32 y, float32 z) { Quat q = { x, y, z, w }; return q; } +inline Quat makeQuat(float32 w, const V3d &vec) { Quat q = { vec.x, vec.y, vec.z, w }; return q; } +inline Quat add(const Quat &q, const Quat &p) { return makeQuat(q.w+p.w, q.x+p.x, q.y+p.y, q.z+p.z); } +inline Quat sub(const Quat &q, const Quat &p) { return makeQuat(q.w-p.w, q.x-p.x, q.y-p.y, q.z-p.z); } +inline Quat negate(const Quat &q) { return makeQuat(-q.w, -q.x, -q.y, -q.z); } +inline float32 dot(const Quat &q, const Quat &p) { return q.w*p.w + q.x*p.x + q.y*p.y + q.z*p.z; } +inline Quat scale(const Quat &q, float32 r) { return makeQuat(q.w*r, q.x*r, q.y*r, q.z*r); } +inline float32 length(const Quat &q) { return sqrtf(q.w*q.w + q.x*q.x + q.y*q.y + q.z*q.z); } +inline Quat normalize(const Quat &q) { return scale(q, 1.0f/length(q)); } +inline Quat conj(const Quat &q) { return makeQuat(q.w, -q.x, -q.y, -q.z); } +Quat mult(const Quat &q, const Quat &p); +inline V3d rotate(const V3d &v, const Quat &q) { return mult(mult(q, makeQuat(0.0f, v)), conj(q)).vec(); } +Quat lerp(const Quat &q, const Quat &p, float32 r); +Quat slerp(const Quat &q, const Quat &p, float32 a); + +struct RawMatrix +{ + V3d right; + float32 rightw; + V3d up; + float32 upw; + V3d at; + float32 atw; + V3d pos; + float32 posw; + + // NB: this is dst = src2*src1, i.e. src1 is applied first, then src2 + static void mult(RawMatrix *dst, RawMatrix *src1, RawMatrix *src2); + static void transpose(RawMatrix *dst, RawMatrix *src); + static void setIdentity(RawMatrix *dst); +}; + +struct Matrix +{ + enum Type { + TYPENORMAL = 1, + TYPEORTHOGONAL = 2, + TYPEORTHONORMAL = 3, + TYPEMASK = 3 + }; + enum Flags { + IDENTITY = 0x20000 + }; + struct Tolerance { + float32 normal; + float32 orthogonal; + float32 identity; + }; + + V3d right; + uint32 flags; + V3d up; + uint32 pad1; + V3d at; + uint32 pad2; + V3d pos; + uint32 pad3; + + static Matrix *create(void); + void destroy(void); + void setIdentity(void); + void optimize(Tolerance *tolerance = nil); + void update(void) { flags &= ~(IDENTITY|TYPEMASK); } + static Matrix *mult(Matrix *dst, const Matrix *src1, const Matrix *src2); + static Matrix *invert(Matrix *dst, const Matrix *src); + static Matrix *transpose(Matrix *dst, const Matrix *src); + Matrix *rotate(const V3d *axis, float32 angle, CombineOp op = rw::COMBINEPOSTCONCAT); + Matrix *rotate(const Quat &q, CombineOp op = rw::COMBINEPOSTCONCAT); + Matrix *translate(const V3d *translation, CombineOp op = rw::COMBINEPOSTCONCAT); + Matrix *scale(const V3d *scl, CombineOp op = rw::COMBINEPOSTCONCAT); + Matrix *transform(const Matrix *mat, CombineOp op = rw::COMBINEPOSTCONCAT); + Quat getRotation(void); + void lookAt(const V3d &dir, const V3d &up); + + // helper functions. consider private + static void mult_(Matrix *dst, const Matrix *src1, const Matrix *src2); + static void invertOrthonormal(Matrix *dst, const Matrix *src); + static Matrix *invertGeneral(Matrix *dst, const Matrix *src); + static void makeRotation(Matrix *dst, const V3d *axis, float32 angle); + static void makeRotation(Matrix *dst, const Quat &q); +private: + float32 normalError(void); + float32 orthogonalError(void); + float32 identityError(void); +}; + +inline void convMatrix(Matrix *dst, RawMatrix *src){ + *dst = *(Matrix*)src; + dst->optimize(); +} + +inline void convMatrix(RawMatrix *dst, Matrix *src){ + *dst = *(RawMatrix*)src; + dst->rightw = 0.0; + dst->upw = 0.0; + dst->atw = 0.0; + dst->posw = 1.0; +} + +struct Line +{ + V3d start; + V3d end; +}; + +struct Rect +{ + int32 x, y; + int32 w, h; +}; + +struct Sphere +{ + V3d center; + float32 radius; +}; + +struct Plane +{ + V3d normal; + float32 distance; +}; + +struct BBox +{ + V3d sup; + V3d inf; + + void initialize(V3d *point); + void addPoint(V3d *point); + void calculate(V3d *points, int32 n); + bool containsPoint(V3d *point); +}; + +enum PrimitiveType +{ + PRIMTYPENONE = 0, + PRIMTYPELINELIST, + PRIMTYPEPOLYLINE, + PRIMTYPETRILIST, + PRIMTYPETRISTRIP, + PRIMTYPETRIFAN, + PRIMTYPEPOINTLIST +}; + +/* + * Memory + */ + +void memNative32_func(void *data, uint32 size); +void memNative16_func(void *data, uint32 size); +void memLittle32_func(void *data, uint32 size); +void memLittle16_func(void *data, uint32 size); + +#ifdef BIGENDIAN +inline void memNative32(void *data, uint32 size) { memNative32_func(data, size); } +inline void memNative16(void *data, uint32 size) { memNative16_func(data, size); } +inline void memLittle32(void *data, uint32 size) { memLittle32_func(data, size); } +inline void memLittle16(void *data, uint32 size) { memLittle16_func(data, size); } +#define ASSERTLITTLE assert(0 && "unsafe code on big-endian") +#else +inline void memNative32(void *data, uint32 size) { } +inline void memNative16(void *data, uint32 size) { } +inline void memLittle32(void *data, uint32 size) { } +inline void memLittle16(void *data, uint32 size) { } +#define ASSERTLITTLE +#endif + +/* + * Streams + */ + +void makePath(char *filename); + +class Stream +{ +public: + virtual ~Stream(void) { close(); } + virtual void close(void) {} + virtual uint32 write8(const void *data, uint32 length) = 0; + virtual uint32 read8(void *data, uint32 length) = 0; + virtual void seek(int32 offset, int32 whence = 1) = 0; + virtual uint32 tell(void) = 0; + virtual bool eof(void) = 0; + uint32 write32(const void *data, uint32 length); + uint32 write16(const void *data, uint32 length); + uint32 read32(void *data, uint32 length); + uint32 read16(void *data, uint32 length); + int32 writeI8(int8 val); + int32 writeU8(uint8 val); + int32 writeI16(int16 val); + int32 writeU16(uint16 val); + int32 writeI32(int32 val); + int32 writeU32(uint32 val); + int32 writeF32(float32 val); + int8 readI8(void); + uint8 readU8(void); + int16 readI16(void); + uint16 readU16(void); + int32 readI32(void); + uint32 readU32(void); + float32 readF32(void); +}; + +class StreamMemory : public Stream +{ +public: + uint8 *data; + uint32 length; + uint32 capacity; + uint32 position; + + void close(void); + uint32 write8(const void *data, uint32 length); + uint32 read8(void *data, uint32 length); + void seek(int32 offset, int32 whence = 1); + uint32 tell(void); + bool eof(void); + StreamMemory *open(uint8 *data, uint32 length, uint32 capacity = 0); + uint32 getLength(void); + + enum { + S_EOF = 0xFFFFFFFF + }; +}; + +class StreamFile : public Stream +{ +public: + FILE *file; + + StreamFile(void) { file = nil; } + void close(void); + uint32 write8(const void *data, uint32 length); + uint32 read8(void *data, uint32 length); + void seek(int32 offset, int32 whence = 1); + uint32 tell(void); + bool eof(void); + StreamFile *open(const char *path, const char *mode); +}; + +enum Platform +{ + PLATFORM_NULL = 0, + // D3D7 + PLATFORM_GL = 2, + // MAC + PLATFORM_PS2 = 4, + PLATFORM_XBOX = 5, + // GAMECUBE + // SOFTRAS + PLATFORM_D3D8 = 8, + PLATFORM_D3D9 = 9, + // PSP + + // non-stock-RW platforms + + PLATFORM_WDGL = 11, // WarDrum OpenGL + PLATFORM_GL3 = 12, // my GL3 implementation + + NUM_PLATFORMS, + + FOURCC_PS2 = 0x00325350 // 'PS2\0' +}; + +#define MAKEPLUGINID(v, id) (((v & 0xFFFFFF) << 8) | (id & 0xFF)) +#define MAKEPIPEID(v, id) (((v & 0xFFFF) << 16) | (id & 0xFFFF)) + +enum VendorID +{ + VEND_CORE = 0, + VEND_CRITERIONTK = 1, + VEND_CRITERIONINT = 4, + VEND_CRITERIONWORLD = 5, + // Used for rasters (platform-specific) + VEND_RASTER = 10, + // Used for driver/device allocation tags + VEND_DRIVER = 11 +}; + +// TODO: modules (VEND_CRITERIONINT) + +enum PluginID +{ + // Core + ID_NAOBJECT = MAKEPLUGINID(VEND_CORE, 0x00), + ID_STRUCT = MAKEPLUGINID(VEND_CORE, 0x01), + ID_STRING = MAKEPLUGINID(VEND_CORE, 0x02), + ID_EXTENSION = MAKEPLUGINID(VEND_CORE, 0x03), + ID_CAMERA = MAKEPLUGINID(VEND_CORE, 0x05), + ID_TEXTURE = MAKEPLUGINID(VEND_CORE, 0x06), + ID_MATERIAL = MAKEPLUGINID(VEND_CORE, 0x07), + ID_MATLIST = MAKEPLUGINID(VEND_CORE, 0x08), + ID_WORLD = MAKEPLUGINID(VEND_CORE, 0x0B), + ID_MATRIX = MAKEPLUGINID(VEND_CORE, 0x0D), + ID_FRAMELIST = MAKEPLUGINID(VEND_CORE, 0x0E), + ID_GEOMETRY = MAKEPLUGINID(VEND_CORE, 0x0F), + ID_CLUMP = MAKEPLUGINID(VEND_CORE, 0x10), + ID_LIGHT = MAKEPLUGINID(VEND_CORE, 0x12), + ID_ATOMIC = MAKEPLUGINID(VEND_CORE, 0x14), + ID_TEXTURENATIVE = MAKEPLUGINID(VEND_CORE, 0x15), + ID_TEXDICTIONARY = MAKEPLUGINID(VEND_CORE, 0x16), + ID_IMAGE = MAKEPLUGINID(VEND_CORE, 0x18), + ID_GEOMETRYLIST = MAKEPLUGINID(VEND_CORE, 0x1A), + ID_ANIMANIMATION = MAKEPLUGINID(VEND_CORE, 0x1B), + ID_RIGHTTORENDER = MAKEPLUGINID(VEND_CORE, 0x1F), + ID_UVANIMDICT = MAKEPLUGINID(VEND_CORE, 0x2B), + + // Toolkit + ID_SKYMIPMAP = MAKEPLUGINID(VEND_CRITERIONTK, 0x10), + ID_SKIN = MAKEPLUGINID(VEND_CRITERIONTK, 0x16), + ID_HANIM = MAKEPLUGINID(VEND_CRITERIONTK, 0x1E), + ID_USERDATA = MAKEPLUGINID(VEND_CRITERIONTK, 0x1F), + ID_MATFX = MAKEPLUGINID(VEND_CRITERIONTK, 0x20), + ID_ANISOT = MAKEPLUGINID(VEND_CRITERIONTK, 0x27), + ID_PDS = MAKEPLUGINID(VEND_CRITERIONTK, 0x31), + ID_ADC = MAKEPLUGINID(VEND_CRITERIONTK, 0x34), + ID_UVANIMATION = MAKEPLUGINID(VEND_CRITERIONTK, 0x35), + + // World + ID_MESH = MAKEPLUGINID(VEND_CRITERIONWORLD, 0x0E), + ID_NATIVEDATA = MAKEPLUGINID(VEND_CRITERIONWORLD, 0x10), + ID_VERTEXFMT = MAKEPLUGINID(VEND_CRITERIONWORLD, 0x11), + + // custom native raster + ID_RASTERGL = MAKEPLUGINID(VEND_RASTER, PLATFORM_GL), + ID_RASTERPS2 = MAKEPLUGINID(VEND_RASTER, PLATFORM_PS2), + ID_RASTERXBOX = MAKEPLUGINID(VEND_RASTER, PLATFORM_XBOX), + ID_RASTERD3D8 = MAKEPLUGINID(VEND_RASTER, PLATFORM_D3D8), + ID_RASTERD3D9 = MAKEPLUGINID(VEND_RASTER, PLATFORM_D3D9), + ID_RASTERWDGL = MAKEPLUGINID(VEND_RASTER, PLATFORM_WDGL), + ID_RASTERGL3 = MAKEPLUGINID(VEND_RASTER, PLATFORM_GL3), + + // anything driver/device related (only as allocation tag) + ID_DRIVER = MAKEPLUGINID(VEND_DRIVER, 0) +}; + +enum CoreModuleID +{ + ID_NAMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x00), + // vector + // matrix + ID_FRAMEMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x03), + // stream + // camera + ID_IMAGEMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x06), + ID_RASTERMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x07), + ID_TEXTUREMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x08) + // pip + // immediate + // resources + // device + // color + // unused + // error + // metrics + // driver + // chunk group +}; + +#define ECODE(c, s) c + +enum Errors +{ + ERR_NONE = 0x80000000, +#include "base.err" +}; + +#undef ECODE + +extern int32 version; +extern int32 build; +extern int32 platform; +extern bool32 streamAppendFrames; +extern char *debugFile; + +int strcmp_ci(const char *s1, const char *s2); +int strncmp_ci(const char *s1, const char *s2, int n); + +// 0x04000000 3.1 +// 0x08000000 3.2 +// 0x0C000000 3.3 +// 0x10000000 3.4 +// 0x14000000 3.5 +// 0x18000000 3.6 +// 0x1C000000 3.7 + +inline uint32 +libraryIDPack(int version, int build) +{ + if(version <= 0x31000) + return version>>8; + return ((version-0x30000) & 0x3FF00) << 14 | (version&0x3F) << 16 | + (build & 0xFFFF); +} + +inline int +libraryIDUnpackVersion(uint32 libid) +{ + if(libid & 0xFFFF0000) + return ((libid>>14 & 0x3FF00) + 0x30000) | + (libid>>16 & 0x3F); + else + return libid<<8; +} + +inline int +libraryIDUnpackBuild(uint32 libid) +{ + if(libid & 0xFFFF0000) + return libid & 0xFFFF; + else + return 0; +} + +struct ChunkHeaderInfo +{ + uint32 type; + uint32 length; + uint32 version, build; +}; + +// TODO?: make these methods of ChunkHeaderInfo? +bool writeChunkHeader(Stream *s, int32 type, int32 size); +bool readChunkHeaderInfo(Stream *s, ChunkHeaderInfo *header); +bool findChunk(Stream *s, uint32 type, uint32 *length, uint32 *version); + +int32 findPointer(void *p, void **list, int32 num); +uint8 *getFileContents(const char *name, uint32 *len); +} diff --git a/vendor/librw/src/rwcharset.h b/vendor/librw/src/rwcharset.h new file mode 100644 index 0000000..3988654 --- /dev/null +++ b/vendor/librw/src/rwcharset.h @@ -0,0 +1,25 @@ +namespace rw { + +struct Charset +{ + struct Desc { + int32 count; // num of chars + int32 tileWidth, tileHeight; // chars in raster + int32 width, height; // of char + int32 width_internal, height_internal; // + border + } desc; + Raster *raster; + + static bool32 open(void); + static void close(void); + static Charset *create(const RGBA *foreground, const RGBA *background); + void destroy(void); + Charset *setColors(const RGBA *foreground, const RGBA *background); + void print(const char *str, int32 x, int32 y, bool32 hideSpaces); + void printBuffered(const char *str, int32 x, int32 y, bool32 hideSpaces); + static void flushBuffer(void); +private: + void printChar(int32 c, int32 x, int32 y); +}; + +} diff --git a/vendor/librw/src/rwengine.h b/vendor/librw/src/rwengine.h new file mode 100644 index 0000000..a84aa3a --- /dev/null +++ b/vendor/librw/src/rwengine.h @@ -0,0 +1,263 @@ +namespace rw { + +// uhhhm..... why are these not actual functions? +enum DeviceReq +{ + // Device initialization before Engine/Driver plugins are opened + DEVICEINIT, + // Device de-initialization after Engine/Driver plugins are closed + DEVICETERM, + + // Device/Context creation + DEVICEOPEN, + // Device/Context shutdown + DEVICECLOSE, + + // Device initialization after Engine/Driver plugins are opened + DEVICEFINALIZE, + // TODO? counterpart to FINALIZE? + + // Video adapters + DEVICEGETNUMSUBSYSTEMS, + DEVICEGETCURRENTSUBSYSTEM, + DEVICESETSUBSYSTEM, + DEVICEGETSUBSSYSTEMINFO, + + // Video modes + DEVICEGETNUMVIDEOMODES, + DEVICEGETCURRENTVIDEOMODE, + DEVICESETVIDEOMODE, + DEVICEGETVIDEOMODEINFO, + + // Multisampling + DEVICEGETMAXMULTISAMPLINGLEVELS, + DEVICEGETMULTISAMPLINGLEVELS, + DEVICESETMULTISAMPLINGLEVELS, + +}; + +typedef int DeviceSystem(DeviceReq req, void *arg, int32 n); + +struct Camera; +struct Image; +struct Texture; +struct Raster; +class ObjPipeline; + +// This is for the render device, we only have one +struct Device +{ + float32 zNear, zFar; + void (*beginUpdate)(Camera*); + void (*endUpdate)(Camera*); + void (*clearCamera)(Camera*, RGBA *col, uint32 mode); + void (*showRaster)(Raster *raster, uint32 flags); + bool32 (*rasterRenderFast)(Raster *raster, int32 x, int32 y); + void (*setRenderState)(int32 state, void *value); + void *(*getRenderState)(int32 state); + + void (*im2DRenderLine)(void *vertices, int32 numVertices, int32 vert1, int32 vert2); + void (*im2DRenderTriangle)(void *vertices, int32 numVertices, int32 vert1, int32 vert2, int32 vert3); + void (*im2DRenderPrimitive)(PrimitiveType primType, void *vertices, int32 numVertices); + void (*im2DRenderIndexedPrimitive)(PrimitiveType primType, void *vertices, int32 numVertices, void *indices, int32 numIndices); + + void (*im3DTransform)(void *vertices, int32 numVertices, Matrix *world, uint32 flags); + void (*im3DRenderPrimitive)(PrimitiveType primType); + void (*im3DRenderIndexedPrimitive)(PrimitiveType primType, void *indices, int32 numIndices); + void (*im3DEnd)(void); + + DeviceSystem *system; +}; + +// This is for platform-dependent but portable things +// so the engine has one for every platform +struct Driver +{ + ObjPipeline *defaultPipeline; + int32 rasterNativeOffset; + + Raster* (*rasterCreate)(Raster*); + uint8 *(*rasterLock)(Raster*, int32 level, int32 lockMode); + void (*rasterUnlock)(Raster*, int32 level); + uint8 *(*rasterLockPalette)(Raster*, int32 lockMode); + void (*rasterUnlockPalette)(Raster*); + int32 (*rasterNumLevels)(Raster*); + bool32 (*imageFindRasterFormat)(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); + bool32 (*rasterFromImage)(Raster*, Image*); + Image *(*rasterToImage)(Raster*); + + static PluginList s_plglist[NUM_PLATFORMS]; + static int32 registerPlugin(int32 platform, int32 size, uint32 id, + Constructor ctor, Destructor dtor){ + return s_plglist[platform].registerPlugin(size, id, + ctor, dtor, nil); + } +}; + +struct EngineOpenParams; + +enum MemHint +{ + MEMDUR_NA = 0, + // used inside function + MEMDUR_FUNCTION = 0x10000, + // used for one frame + MEMDUR_FRAME = 0x20000, + // used for longer time + MEMDUR_EVENT = 0x30000, + // used while the engine is running + MEMDUR_GLOBAL = 0x40000 +}; + +struct MemoryFunctions +{ + void *(*rwmalloc)(size_t sz, uint32 hint); + void *(*rwrealloc)(void *p, size_t sz, uint32 hint); + void (*rwfree)(void *p); + // These are temporary until we have better error handling + // TODO: Maybe don't put them here since they shouldn't really be switched out + void *(*rwmustmalloc)(size_t sz, uint32 hint); + void *(*rwmustrealloc)(void *p, size_t sz, uint32 hint); +}; + +struct SubSystemInfo +{ + char name[80]; +}; + +enum VideoModeFlags +{ + VIDEOMODEEXCLUSIVE = 1 +}; + +struct VideoMode +{ + int32 width; + int32 height; + int32 depth; + uint32 flags; +}; + +struct Camera; +struct World; + +// This is for platform independent things +// TODO: move more stuff into this +struct Engine +{ + enum State { + Dead = 0, + Initialized, + Opened, + Started + }; + Camera *currentCamera; + World *currentWorld; + LinkList frameDirtyList; + + // Dynamically allocated because of plugins + Driver *driver[NUM_PLATFORMS]; + Device device; + ObjPipeline *dummyDefaultPipeline; + + // These must always be available + static MemoryFunctions memfuncs; + static State state; + + static bool32 init(MemoryFunctions *memfuncs = nil); + static bool32 open(EngineOpenParams*); + static bool32 start(void); + static void term(void); + static void close(void); + static void stop(void); + + static int32 getNumSubSystems(void); + static int32 getCurrentSubSystem(void); + static bool32 setSubSystem(int32 subsys); + static SubSystemInfo *getSubSystemInfo(SubSystemInfo *info, int32 subsys); + + static int32 getNumVideoModes(void); + static int32 getCurrentVideoMode(void); + static bool32 setVideoMode(int32 mode); + static VideoMode *getVideoModeInfo(VideoMode *info, int32 mode); + + static uint32 getMaxMultiSamplingLevels(void); + static uint32 getMultiSamplingLevels(void); + static bool32 setMultiSamplingLevels(uint32 levels); + + static PluginList s_plglist; + static int32 registerPlugin(int32 size, uint32 id, + Constructor ctor, Destructor dtor){ + return s_plglist.registerPlugin(size, id, ctor, dtor, nil); + } +}; + +extern Engine *engine; + +#define RWTOSTR_(X) #X +#define RWTOSTR(X) RWTOSTR_(X) +#define RWHERE "file: " __FILE__ " line: " RWTOSTR(__LINE__) + +extern const char *allocLocation; + +inline void *malloc_LOC(size_t sz, uint32 hint, const char *here) { allocLocation = here; return rw::Engine::memfuncs.rwmalloc(sz,hint); } +inline void *realloc_LOC(void *p, size_t sz, uint32 hint, const char *here) { allocLocation = here; return rw::Engine::memfuncs.rwrealloc(p,sz,hint); } +inline void *mustmalloc_LOC(size_t sz, uint32 hint, const char *here) { allocLocation = here; return rw::Engine::memfuncs.rwmustmalloc(sz,hint); } +inline void *mustrealloc_LOC(void *p, size_t sz, uint32 hint, const char *here) { allocLocation = here; return rw::Engine::memfuncs.rwmustrealloc(p,sz,hint); } + +char *strdup_LOC(const char *s, uint32 hint, const char *here); + +#define rwMalloc(s, h) rw::malloc_LOC(s,h,RWHERE) +#define rwMallocT(t, s, h) (t*)rw::malloc_LOC((s)*sizeof(t),h,RWHERE) +#define rwRealloc(p, s, h) rw::realloc_LOC(p,s,h,RWHERE) +#define rwReallocT(t, p, s, h) (t*)rw::realloc_LOC(p,(s)*sizeof(t),h,RWHERE) +#define rwFree(p) rw::Engine::memfuncs.rwfree(p) +#define rwNew(s, h) rw::mustmalloc_LOC(s,h,RWHERE) +#define rwNewT(t, s, h) (t*)rw::mustmalloc_LOC((s)*sizeof(t),h,RWHERE) +#define rwResize(p, s, h) rw::mustrealloc_LOC(p,s,h,RWHERE) +#define rwResizeT(t, p, s, h) (t*)rw::mustrealloc_LOC(p,(s)*sizeof(t),h,RWHERE) +#define rwStrdup(s, h) rw::strdup_LOC(s,h,RWHERE) + +extern MemoryFunctions defaultMemfuncs; +extern MemoryFunctions managedMemfuncs; +void printleaks(void); // when using managed mem funcs + +namespace null { + void beginUpdate(Camera*); + void endUpdate(Camera*); + void clearCamera(Camera*, RGBA *col, uint32 mode); + void showRaster(Raster*, uint32 flags); + + void setRenderState(int32 state, void *value); + void *getRenderState(int32 state); + + bool32 rasterRenderFast(Raster *raster, int32 x, int32 y); + + Raster *rasterCreate(Raster*); + uint8 *rasterLock(Raster*, int32 level, int32 lockMode); + void rasterUnlock(Raster*, int32 level); + uint8 *rasterLockPalette(Raster*, int32 lockMode); + void rasterUnlockPalette(Raster*); + int32 rasterNumLevels(Raster*); + bool32 imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); + bool32 rasterFromImage(Raster*, Image*); + Image *rasterToImage(Raster*); + + void im2DRenderLine(void*, int32, int32, int32); + void im2DRenderTriangle(void*, int32, int32, int32, int32); + void im2DRenderPrimitive(PrimitiveType, void*, int32); + void im2DRenderIndexedPrimitive(PrimitiveType, void*, int32, void*, int32); + + void im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags); + void im3DRenderPrimitive(PrimitiveType primType); + void im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices); + void im3DEnd(void); + + int deviceSystem(DeviceReq req, void *arg0, int32 n); + + extern Device renderdevice; +} + +} diff --git a/vendor/librw/src/rwerror.h b/vendor/librw/src/rwerror.h new file mode 100644 index 0000000..2575626 --- /dev/null +++ b/vendor/librw/src/rwerror.h @@ -0,0 +1,25 @@ +namespace rw { + +struct Error +{ + uint32 plugin; + uint32 code; +}; + +void setError(Error *e); +Error *getError(Error *e); + +#define _ERRORCODE(code, ...) code +char *dbgsprint(uint32 code, ...); + +/* ecode is supposed to be in format "(errorcode, printf-arguments..)" */ +#define RWERROR(ecode) do{ \ + rw::Error _e; \ + _e.plugin = PLUGIN_ID; \ + _e.code = _ERRORCODE ecode; \ + fprintf(stderr, "%s:%d: ", __FILE__, __LINE__); \ + fprintf(stderr, "%s\n", rw::dbgsprint ecode); \ + rw::setError(&_e); \ +}while(0) + +} diff --git a/vendor/librw/src/rwobjects.h b/vendor/librw/src/rwobjects.h new file mode 100644 index 0000000..5cd833f --- /dev/null +++ b/vendor/librw/src/rwobjects.h @@ -0,0 +1,904 @@ +#include + +namespace rw { + +struct Object +{ + uint8 type; + uint8 subType; + uint8 flags; + uint8 privateFlags; + void *parent; + + void init(uint8 type, uint8 subType){ + this->type = type; + this->subType = subType; + this->flags = 0; + this->privateFlags = 0; + this->parent = nil; + } + void copy(Object *o){ + this->type = o->type; + this->subType = o->subType; + this->flags = o->flags; + this->privateFlags = o->privateFlags; + this->parent = nil; + } +}; + +struct Frame +{ + PLUGINBASE + typedef Frame *(*Callback)(Frame *f, void *data); + enum { ID = 0 }; + enum { // private flags + // The hierarchy has unsynched frames + HIERARCHYSYNCLTM = 0x01, // LTM not synched + HIERARCHYSYNCOBJ = 0x02, // attached objects not synched + HIERARCHYSYNC = HIERARCHYSYNCLTM | HIERARCHYSYNCOBJ, + // This frame is not synched + SUBTREESYNCLTM = 0x04, + SUBTREESYNCOBJ = 0x08, + SUBTREESYNC = SUBTREESYNCLTM | SUBTREESYNCOBJ, + SYNCLTM = HIERARCHYSYNCLTM | SUBTREESYNCLTM, + SYNCOBJ = HIERARCHYSYNCOBJ | SUBTREESYNCOBJ + // STATIC = 0x10 + }; + + Object object; + LLLink inDirtyList; + LinkList objectList; + Matrix matrix; + Matrix ltm; + + Frame *child; + Frame *next; + Frame *root; + + static int32 numAllocated; + + static Frame *create(void); + Frame *cloneHierarchy(void); + void destroy(void); + void destroyHierarchy(void); + Frame *addChild(Frame *f, bool32 append = 0); + Frame *removeChild(void); + Frame *forAllChildren(Callback cb, void *data); + Frame *getParent(void) const { + return (Frame*)this->object.parent; } + int32 count(void); + bool32 dirty(void) const { + return !!(this->root->object.privateFlags & HIERARCHYSYNC); } + Matrix *getLTM(void); + void rotate(const V3d *axis, float32 angle, CombineOp op = rw::COMBINEPOSTCONCAT); + void translate(const V3d *trans, CombineOp op = rw::COMBINEPOSTCONCAT); + void scale(const V3d *scale, CombineOp op = rw::COMBINEPOSTCONCAT); + void transform(const Matrix *mat, CombineOp op = rw::COMBINEPOSTCONCAT); + void updateObjects(void); + + + void syncHierarchyLTM(void); + void setHierarchyRoot(Frame *root); + Frame *cloneAndLink(void); + void purgeClone(void); + +#ifndef RWPUBLIC + static void registerModule(void); +#endif + static void syncDirty(void); +}; + +struct FrameList_ +{ + int32 numFrames; + Frame **frames; + + FrameList_ *streamRead(Stream *stream); + void streamWrite(Stream *stream); + static uint32 streamGetSize(Frame *f); +}; +Frame **makeFrameList(Frame *frame, Frame **flist); + +struct ObjectWithFrame +{ + typedef void (*Sync)(ObjectWithFrame*); + + Object object; + LLLink inFrame; + Sync syncCB; + + void setFrame(Frame *f){ + if(this->object.parent) + this->inFrame.remove(); + this->object.parent = f; + if(f){ + f->objectList.add(&this->inFrame); + f->updateObjects(); + } + } + void sync(void){ this->syncCB(this); } + static ObjectWithFrame *fromFrame(LLLink *lnk){ + return LLLinkGetData(lnk, ObjectWithFrame, inFrame); + } +}; + +struct Image +{ + int32 flags; + int32 width, height; + int32 depth; + int32 bpp; // bytes per pixel + int32 stride; + uint8 *pixels; + uint8 *palette; + + static int32 numAllocated; + + static Image *create(int32 width, int32 height, int32 depth); + void destroy(void); + void allocate(void); + void free(void); + void setPixels(uint8 *pixels); + void setPixelsDXT(int32 type, uint8 *pixels); + void setPalette(uint8 *palette); + void compressPalette(void); // turn 8 bit into 4 bit if possible + bool32 hasAlpha(void); + void convertTo32(void); + void palettize(int32 depth); + void unpalettize(bool forceAlpha = false); + void makeMask(void); + void applyMask(Image *mask); + void removeMask(void); + Image *extractMask(void); + + static void setSearchPath(const char*); + static void printSearchPath(void); + static char *getFilename(const char*); + static Image *read(const char *imageName); + static Image *readMasked(const char *imageName, const char *maskName); + + + typedef Image *(*fileRead)(const char *afilename); + typedef void (*fileWrite)(Image *image, const char *filename); + static bool32 registerFileFormat(const char *ext, fileRead read, fileWrite write); + +#ifndef RWPUBLIC + static void registerModule(void); +#endif +}; + +Image *readTGA(const char *filename); +void writeTGA(Image *image, const char *filename); +Image *readBMP(const char *filename); +void writeBMP(Image *image, const char *filename); +Image *readPNG(const char *filename); +void writePNG(Image *image, const char *filename); + +enum { QUANTDEPTH = 8 }; + +struct ColorQuant +{ + struct Node { + uint32 r, g, b, a; + int32 numPixels; + Node *parent; + Node *children[16]; + LLLink link; + + void destroy(void); + void addColor(RGBA color); + bool isLeaf(void) { for(int32 i = 0; i < 16; i++) if(this->children[i]) return false; return true; } + }; + + Node *root; + LinkList leaves; + + void init(void); + void destroy(void); + Node *createNode(int32 level); + Node *getNode(Node *root, uint32 addr, int32 level); + Node *findNode(Node *root, uint32 addr, int32 level); + void reduceNode(Node *node); + void addColor(RGBA color); + uint8 findColor(RGBA color); + void addImage(Image *img); + void makePalette(int32 numColors, RGBA *colors); + void matchImage(uint8 *dstPixels, uint32 dstStride, Image *src); +}; + +// used to emulate d3d and xbox textures +struct RasterLevels +{ + int32 numlevels; + uint32 format; + struct Level { + int32 width, height, size; + uint8 *data; + } levels[1]; // 0 is illegal :/ +}; + +struct Raster +{ + enum { FLIPWAITVSYNCH = 1 }; + + PLUGINBASE + int32 platform; + + // TODO: use bytes + int32 type; + int32 flags; + int32 privateFlags; + int32 format; + int32 width, height, depth; + int32 stride; + uint8 *pixels; + uint8 *palette; + // remember for locked rasters + uint8 *originalPixels; + int32 originalWidth; + int32 originalHeight; + int32 originalStride; + // subraster + Raster *parent; + int32 offsetX, offsetY; + + static int32 numAllocated; + + static Raster *create(int32 width, int32 height, int32 depth, + int32 format, int32 platform = 0); + void subRaster(Raster *parent, Rect *r); + void destroy(void); + static bool32 imageFindRasterFormat(Image *image, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat, int32 platform = 0); + Raster *setFromImage(Image *image, int32 platform = 0); + static Raster *createFromImage(Image *image, int32 platform = 0); + Image *toImage(void); + uint8 *lock(int32 level, int32 lockMode); + void unlock(int32 level); + uint8 *lockPalette(int32 lockMode); + void unlockPalette(void); + int32 getNumLevels(void); + static int32 calculateNumLevels(int32 width, int32 height); + static bool formatHasAlpha(int32 format); + + void show(uint32 flags); + + static Raster *pushContext(Raster *raster); + static Raster *popContext(void); + static Raster *getCurrentContext(void); + bool32 renderFast(int32 x, int32 y); + + static Raster *convertTexToCurrentPlatform(Raster *ras); +#ifndef RWPUBLIC + static void registerModule(void); +#endif + + enum Format { + DEFAULT = 0, + C1555 = 0x0100, + C565 = 0x0200, + C4444 = 0x0300, + LUM8 = 0x0400, + C8888 = 0x0500, + C888 = 0x0600, + D16 = 0x0700, + D24 = 0x0800, + D32 = 0x0900, + C555 = 0x0A00, + AUTOMIPMAP = 0x1000, + PAL8 = 0x2000, + PAL4 = 0x4000, + MIPMAP = 0x8000 + }; + enum Type { + NORMAL = 0x00, + ZBUFFER = 0x01, + CAMERA = 0x02, + TEXTURE = 0x04, + CAMERATEXTURE = 0x05, + DONTALLOCATE = 0x80 + }; + enum LockMode { + LOCKWRITE = 1, + LOCKREAD = 2, + LOCKNOFETCH = 4, // don't fetch pixel data + LOCKRAW = 8, + }; + + enum + { + // from RW + PRIVATELOCK_READ = 0x02, + PRIVATELOCK_WRITE = 0x04, + PRIVATELOCK_READ_PALETTE = 0x08, + PRIVATELOCK_WRITE_PALETTE = 0x10, + }; +}; + +void conv_RGBA8888_from_RGBA8888(uint8 *out, uint8 *in); +void conv_BGRA8888_from_RGBA8888(uint8 *out, uint8 *in); +void conv_RGBA8888_from_RGB888(uint8 *out, uint8 *in); +void conv_BGRA8888_from_RGB888(uint8 *out, uint8 *in); +void conv_RGB888_from_RGB888(uint8 *out, uint8 *in); +void conv_BGR888_from_RGB888(uint8 *out, uint8 *in); +void conv_ARGB1555_from_ARGB1555(uint8 *out, uint8 *in); +void conv_ARGB1555_from_RGB555(uint8 *out, uint8 *in); +void conv_RGBA5551_from_ARGB1555(uint8 *out, uint8 *in); +void conv_ARGB1555_from_RGBA5551(uint8 *out, uint8 *in); +void conv_RGBA8888_from_ARGB1555(uint8 *out, uint8 *in); +void conv_ABGR1555_from_ARGB1555(uint8 *out, uint8 *in); +inline void conv_8_from_8(uint8 *out, uint8 *in) { *out = *in; } +// some swaps are the same, so these are just more descriptive names +inline void conv_RGBA8888_from_BGRA8888(uint8 *out, uint8 *in) { conv_BGRA8888_from_RGBA8888(out, in); } +inline void conv_RGB888_from_BGR888(uint8 *out, uint8 *in) { conv_BGR888_from_RGB888(out, in); } +inline void conv_ARGB1555_from_ABGR1555(uint8 *out, uint8 *in) { conv_ABGR1555_from_ARGB1555(out, in); } + +void expandPal4(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); +void compressPal4(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); +void expandPal4_BE(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); +void compressPal4_BE(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); +void copyPal8(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); + +void flipDXT(int32 type, uint8 *dst, uint8 *src, uint32 width, uint32 height); + + +#define IGNORERASTERIMP 0 + +struct TexDictionary; + +struct Texture +{ + enum FilterMode { + NEAREST = 1, + LINEAR, + MIPNEAREST, // one mipmap + MIPLINEAR, + LINEARMIPNEAREST, // mipmap interpolated + LINEARMIPLINEAR + }; + enum Addressing { + WRAP = 1, + MIRROR, + CLAMP, + BORDER + }; + + PLUGINBASE + Raster *raster; + TexDictionary *dict; + LLLink inDict; + char name[32]; + char mask[32]; + uint32 filterAddressing; // VVVVUUUU FFFFFFFF + int32 refCount; + + LLLink inGlobalList; // actually not in RW + + static int32 numAllocated; + + static Texture *create(Raster *raster); + void addRef(void) { this->refCount++; } + void destroy(void); + static Texture *fromDict(LLLink *lnk){ + return LLLinkGetData(lnk, Texture, inDict); } + FilterMode getFilter(void) { return (FilterMode)(filterAddressing & 0xFF); } + void setFilter(FilterMode f) { filterAddressing = (filterAddressing & ~0xFF) | f; } + Addressing getAddressU(void) { return (Addressing)((filterAddressing >> 8) & 0xF); } + Addressing getAddressV(void) { return (Addressing)((filterAddressing >> 12) & 0xF); } + void setAddressU(Addressing u) { filterAddressing = (filterAddressing & ~0xF00) | u<<8; } + void setAddressV(Addressing v) { filterAddressing = (filterAddressing & ~0xF000) | v<<12; } + static Texture *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + static Texture *read(const char *name, const char *mask); + static Texture *streamReadNative(Stream *stream); + void streamWriteNative(Stream *stream); + uint32 streamGetSizeNative(void); + + static Texture *(*findCB)(const char *name); + static Texture *(*readCB)(const char *name, const char *mask); + static void setLoadTextures(bool32); // default: true + static void setCreateDummies(bool32); // default: false + static void setMipmapping(bool32); // default: false + static void setAutoMipmapping(bool32); // default: false + static bool32 getMipmapping(void); + static bool32 getAutoMipmapping(void); + + void setMaxAnisotropy(int32 maxaniso); // only if plugin is attached + int32 getMaxAnisotropy(void); + +#ifndef RWPUBLIC + static void registerModule(void); +#endif +}; + +extern int32 anisotOffset; +#define GETANISOTROPYEXT(texture) PLUGINOFFSET(int32, texture, rw::anisotOffset) +void registerAnisotropyPlugin(void); +int32 getMaxSupportedMaxAnisotropy(void); + +struct SurfaceProperties +{ + float32 ambient; + float32 specular; + float32 diffuse; +}; + +struct Material +{ + PLUGINBASE + Texture *texture; + RGBA color; + SurfaceProperties surfaceProps; + Pipeline *pipeline; + int32 refCount; + + static int32 numAllocated; + + static Material *create(void); + void addRef(void) { this->refCount++; } + Material *clone(void); + void destroy(void); + void setTexture(Texture *tex); + static Material *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); +}; + +void registerMaterialRightsPlugin(void); + +struct Mesh +{ + uint16 *indices; + uint32 numIndices; + Material *material; +}; + +struct MeshHeader +{ + enum { + TRISTRIP = 1 + }; + uint32 flags; + uint16 numMeshes; + uint16 serialNum; + uint32 totalIndices; + uint32 pad; // needed for alignment of Meshes + // after this the meshes + + Mesh *getMeshes(void) { return (Mesh*)(this+1); } + void setupIndices(void); + uint32 guessNumTriangles(void); +}; + +struct Geometry; + +struct MorphTarget +{ + Geometry *parent; + Sphere boundingSphere; + V3d *vertices; + V3d *normals; + + Sphere calculateBoundingSphere(void) const; +}; + +struct InstanceDataHeader +{ + uint32 platform; +}; + +struct Triangle +{ + uint16 v[3]; + uint16 matId; +}; + +struct MaterialList +{ + Material **materials; + int32 numMaterials; + int32 space; + + void init(void); + void deinit(void); + int32 appendMaterial(Material *mat); + int32 findIndex(Material *mat); + static MaterialList *streamRead(Stream *stream, MaterialList *matlist); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); +}; + +struct Geometry +{ + PLUGINBASE + enum { ID = 8 }; + Object object; + uint32 flags; + uint16 lockedSinceInst; + int32 numTriangles; + int32 numVertices; + int32 numMorphTargets; + int32 numTexCoordSets; + + Triangle *triangles; + RGBA *colors; + TexCoords *texCoords[8]; + + MorphTarget *morphTargets; + MaterialList matList; + + MeshHeader *meshHeader; + InstanceDataHeader *instData; + + int32 refCount; + + static int32 numAllocated; + + static Geometry *create(int32 numVerts, int32 numTris, uint32 flags); + void addRef(void) { this->refCount++; } + void destroy(void); + void lock(int32 lockFlags); + void unlock(void); + void addMorphTargets(int32 n); + void calculateBoundingSphere(void); + bool32 hasColoredMaterial(void); + void allocateData(void); + MeshHeader *allocateMeshes(int32 numMeshes, uint32 numIndices, bool32 noIndices); + void generateTriangles(int8 *adc = nil); + void buildMeshes(void); + void buildTristrips(void); // private, used by buildMeshes + void correctTristripWinding(void); + void removeUnusedMaterials(void); + static Geometry *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + + enum Flags + { + TRISTRIP = 0x01, + POSITIONS = 0x02, + TEXTURED = 0x04, + PRELIT = 0x08, + NORMALS = 0x10, + LIGHT = 0x20, + MODULATE = 0x40, + TEXTURED2 = 0x80, + // When this flag is set the geometry has + // native geometry. When streamed out this geometry + // is written out instead of the platform independent data. + // When streamed in with this flag, the geometry is mostly empty. + NATIVE = 0x01000000, + // Just for documentation: RW sets this flag + // to prevent rendering when executing a pipeline, + // so only instancing will occur. + // librw's pipelines are different so it's unused here. + NATIVEINSTANCE = 0x02000000 + }; + + enum LockFlags + { + LOCKPOLYGONS = 0x0001, + LOCKVERTICES = 0x0002, + LOCKNORMALS = 0x0004, + LOCKPRELIGHT = 0x0008, + + LOCKTEXCOORDS = 0x0010, + LOCKTEXCOORDS1 = 0x0010, + LOCKTEXCOORDS2 = 0x0020, + LOCKTEXCOORDS3 = 0x0040, + LOCKTEXCOORDS4 = 0x0080, + LOCKTEXCOORDS5 = 0x0100, + LOCKTEXCOORDS6 = 0x0200, + LOCKTEXCOORDS7 = 0x0400, + LOCKTEXCOORDS8 = 0x0800, + LOCKTEXCOORDSALL = 0x0ff0, + + LOCKALL = 0x0fff + }; +}; + +void registerMeshPlugin(void); +void registerNativeDataPlugin(void); + +struct Clump; +struct World; + +struct Atomic +{ + PLUGINBASE + typedef void (*RenderCB)(Atomic *atomic); + enum { ID = 1 }; + enum { + // flags + COLLISIONTEST = 0x01, // unused here + RENDER = 0x04, + // private flags + WORLDBOUNDDIRTY = 0x01, + // for setGeometry + SAMEBOUNDINGSPHERE = 0x01 + }; + + ObjectWithFrame object; + Geometry *geometry; + Sphere boundingSphere; + Sphere worldBoundingSphere; + Clump *clump; + LLLink inClump; + ObjPipeline *pipeline; + RenderCB renderCB; + + World *world; + ObjectWithFrame::Sync originalSync; + + static int32 numAllocated; + + static Atomic *create(void); + Atomic *clone(void); + void destroy(void); + void setFrame(Frame *f) { + this->object.setFrame(f); + this->object.object.privateFlags |= WORLDBOUNDDIRTY; + } + Frame *getFrame(void) const { return (Frame*)this->object.object.parent; } + static Atomic *fromClump(LLLink *lnk){ + return LLLinkGetData(lnk, Atomic, inClump); } + void setGeometry(Geometry *geo, uint32 flags); + Sphere *getWorldBoundingSphere(void); + ObjPipeline *getPipeline(void); + void instance(void); + void uninstance(void); + void render(void) { this->renderCB(this); } + void setRenderCB(RenderCB renderCB){ + this->renderCB = renderCB; + if(this->renderCB == nil) + this->renderCB = defaultRenderCB; + }; + void setFlags(uint32 flags) { this->object.object.flags = flags; } + uint32 getFlags(void) const { return this->object.object.flags; } + static Atomic *streamReadClump(Stream *stream, + FrameList_ *frameList, Geometry **geometryList); + bool streamWriteClump(Stream *stream, FrameList_ *frmlst); + uint32 streamGetSize(void); + + static void defaultRenderCB(Atomic *atomic); +}; + +void registerAtomicRightsPlugin(void); + +struct Light +{ + PLUGINBASE + enum { ID = 3 }; + ObjectWithFrame object; + float32 radius; + RGBAf color; + float32 minusCosAngle; + LLLink inWorld; + + // clump extension + Clump *clump; + LLLink inClump; + + // world extension + World *world; + ObjectWithFrame::Sync originalSync; + + static int32 numAllocated; + + static Light *create(int32 type); + void destroy(void); + void setFrame(Frame *f) { this->object.setFrame(f); } + Frame *getFrame(void) const { return (Frame*)this->object.object.parent; } + static Light *fromClump(LLLink *lnk){ + return LLLinkGetData(lnk, Light, inClump); } + static Light *fromWorld(LLLink *lnk){ + return LLLinkGetData(lnk, Light, inWorld); } + void setAngle(float32 angle); + float32 getAngle(void); + void setColor(float32 r, float32 g, float32 b); + int32 getType(void){ return this->object.object.subType; } + void setFlags(uint32 flags) { this->object.object.flags = flags; } + uint32 getFlags(void) { return this->object.object.flags; } + static Light *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + + enum Type { + DIRECTIONAL = 1, + AMBIENT, + POINT = 0x80, // positioned + SPOT, + SOFTSPOT + }; + enum Flags { + LIGHTATOMICS = 1, + LIGHTWORLD = 2 + }; +}; + +struct FrustumPlane +{ + Plane plane; + /* Used for BBox tests: + * 0 = inf is closer to normal direction + * 1 = sup is closer to normal direction */ + uint8 closestX; + uint8 closestY; + uint8 closestZ; +}; + +struct Camera +{ + PLUGINBASE + enum { ID = 4 }; + enum { PERSPECTIVE = 1, PARALLEL }; + enum { CLEARIMAGE = 0x1, CLEARZ = 0x2, CLEARSTENCIL = 0x4 }; + // return value of frustumTestSphere + enum { SPHEREOUTSIDE, SPHEREBOUNDARY, SPHEREINSIDE }; + + ObjectWithFrame object; + void (*beginUpdateCB)(Camera*); + void (*endUpdateCB)(Camera*); + V2d viewWindow; + V2d viewOffset; + float32 nearPlane, farPlane; + float32 fogPlane; + int32 projection; + + Matrix viewMatrix; + float32 zScale, zShift; + + FrustumPlane frustumPlanes[6]; + V3d frustumCorners[8]; + BBox frustumBoundBox; + + Raster *frameBuffer; + Raster *zBuffer; + + // Device dependent view and projection matrices + // optional + RawMatrix devView; + RawMatrix devProj; + + // clump link handled by plugin in RW + Clump *clump; + LLLink inClump; + + // world extension + /* RW: frustum sectors, space, position */ + World *world; + ObjectWithFrame::Sync originalSync; + void (*originalBeginUpdate)(Camera*); + void (*originalEndUpdate)(Camera*); + + static int32 numAllocated; + + static Camera *create(void); + Camera *clone(void); + void destroy(void); + void setFrame(Frame *f) { this->object.setFrame(f); } + Frame *getFrame(void)const { return (Frame*)this->object.object.parent; } + static Camera *fromClump(LLLink *lnk){ + return LLLinkGetData(lnk, Camera, inClump); } + void beginUpdate(void) { this->beginUpdateCB(this); } + void endUpdate(void) { this->endUpdateCB(this); } + void clear(RGBA *col, uint32 mode); + void showRaster(uint32 flags); + void setNearPlane(float32); + void setFarPlane(float32); + void setViewWindow(const V2d *window); + void setViewOffset(const V2d *offset); + void setProjection(int32 proj); + int32 frustumTestSphere(const Sphere *s) const; + static Camera *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + + // fov in degrees + void setFOV(float32 fov, float32 ratio); +}; + +struct Clump +{ + PLUGINBASE + enum { ID = 2 }; + Object object; + LinkList atomics; + LinkList lights; + LinkList cameras; + + World *world; + LLLink inWorld; + + static int32 numAllocated; + + static Clump *create(void); + Clump *clone(void); + void destroy(void); + static Clump *fromWorld(LLLink *lnk){ + return LLLinkGetData(lnk, Clump, inWorld); } + int32 countAtomics(void) { return this->atomics.count(); } + void addAtomic(Atomic *a); + void removeAtomic(Atomic *a); + int32 countLights(void) { return this->lights.count(); } + void addLight(Light *l); + void removeLight(Light *l); + int32 countCameras(void) { return this->cameras.count(); } + void addCamera(Camera *c); + void removeCamera(Camera *c); + void setFrame(Frame *f){ + this->object.parent = f; } + Frame *getFrame(void) const { + return (Frame*)this->object.parent; } + static Clump *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + void render(void); +}; + +// used by enumerateLights for lighting callback +struct WorldLights +{ + int32 numAmbients; + RGBAf ambient; // all ambients added + int32 numDirectionals; + Light **directionals; // only directionals + int32 numLocals; + Light **locals; // points, (soft)spots +}; + +// A bit of a stub right now +struct World +{ + PLUGINBASE + enum { ID = 7 }; + Object object; + LinkList localLights; // these have positions (type >= 0x80) + LinkList globalLights; // these do not (type < 0x80) + LinkList clumps; + + static int32 numAllocated; + + static World *create(BBox *bbox = nil); // TODO: should probably make this non-optional + void destroy(void); + void addLight(Light *light); + void removeLight(Light *light); + void addCamera(Camera *cam); + void removeCamera(Camera *cam); + void addAtomic(Atomic *atomic); + void removeAtomic(Atomic *atomic); + void addClump(Clump *clump); + void removeClump(Clump *clump); + void render(void); + void enumerateLights(Atomic *atomic, WorldLights *lightData); +}; + +struct TexDictionary +{ + PLUGINBASE + enum { ID = 6 }; + Object object; + LinkList textures; + LLLink inGlobalList; + + static int32 numAllocated; + + static TexDictionary *create(void); + static TexDictionary *fromLink(LLLink *lnk){ + return LLLinkGetData(lnk, TexDictionary, inGlobalList); } + void destroy(void); + int32 count(void) { return this->textures.count(); } + void add(Texture *t); + void addFront(Texture *t); + void remove(Texture *t); + Texture *find(const char *name); + static TexDictionary *streamRead(Stream *stream); + void streamWrite(Stream *stream); + uint32 streamGetSize(void); + + static void setCurrent(TexDictionary *txd); + static TexDictionary *getCurrent(void); +}; + +} diff --git a/vendor/librw/src/rwpipeline.h b/vendor/librw/src/rwpipeline.h new file mode 100644 index 0000000..9a0410a --- /dev/null +++ b/vendor/librw/src/rwpipeline.h @@ -0,0 +1,65 @@ +namespace rw { + +struct Atomic; + +class Pipeline +{ +public: + uint32 pluginID; + uint32 pluginData; + int32 platform; + + void init(uint32 platform) { + this->pluginID = 0; + this->pluginData = 0; + this->platform = platform; + } +}; + +class ObjPipeline : public Pipeline +{ +public: + void init(uint32 platform); + static ObjPipeline *create(void); // always PLATFORM_NULL + void destroy(void); + + // not the most beautiful way of doing things but still + // better than virtual methods (i hope?). + struct { + void (*instance)(ObjPipeline *pipe, Atomic *atomic); + void (*uninstance)(ObjPipeline *pipe, Atomic *atomic); + void (*render)(ObjPipeline *pipe, Atomic *atomic); + } impl; + // just for convenience + void instance(Atomic *atomic) { this->impl.instance(this, atomic); } + void uninstance(Atomic *atomic) { this->impl.uninstance(this, atomic); } + void render(Atomic *atomic) { this->impl.render(this, atomic); } +}; + +void findMinVertAndNumVertices(uint16 *indices, uint32 numIndices, uint32 *minVert, int32 *numVertices); + +// everything xbox, d3d8 and d3d9 may want to use +enum { + VERT_BYTE2 = 1, + VERT_BYTE3, + VERT_SHORT2, + VERT_SHORT3, + VERT_NORMSHORT2, + VERT_NORMSHORT3, + VERT_FLOAT2, + VERT_FLOAT3, + VERT_FLOAT4, + VERT_ARGB, + VERT_RGBA, + VERT_COMPNORM +}; + +void instV4d(int type, uint8 *dst, V4d *src, uint32 numVertices, uint32 stride); +void instV3d(int type, uint8 *dst, V3d *src, uint32 numVertices, uint32 stride); +void uninstV3d(int type, V3d *dst, uint8 *src, uint32 numVertices, uint32 stride); +void instTexCoords(int type, uint8 *dst, TexCoords *src, uint32 numVertices, uint32 stride); +void uninstTexCoords(int type, TexCoords *dst, uint8 *src, uint32 numVertices, uint32 stride); +bool32 instColor(int type, uint8 *dst, RGBA *src, uint32 numVertices, uint32 stride); +void uninstColor(int type, RGBA *dst, uint8 *src, uint32 numVertices, uint32 stride); + +} diff --git a/vendor/librw/src/rwplg.h b/vendor/librw/src/rwplg.h new file mode 100644 index 0000000..ad2116a --- /dev/null +++ b/vendor/librw/src/rwplg.h @@ -0,0 +1,85 @@ +namespace rw { + +#define PLUGINOFFSET(type, base, offset) \ + ((type*)((char*)(base) + (offset))) + +typedef void *(*Constructor)(void *object, int32 offset, int32 size); +typedef void *(*Destructor)(void *object, int32 offset, int32 size); +typedef void *(*CopyConstructor)(void *dst, void *src, int32 offset, int32 size); +typedef Stream *(*StreamRead)(Stream *stream, int32 length, void *object, int32 offset, int32 size); +typedef Stream *(*StreamWrite)(Stream *stream, int32 length, void *object, int32 offset, int32 size); +typedef int32 (*StreamGetSize)(void *object, int32 offset, int32 size); +typedef void (*RightsCallback)(void *object, int32 offset, int32 size, uint32 data); +typedef void (*AlwaysCallback)(void *object, int32 offset, int32 size); + +struct PluginList +{ + int32 size; + int32 defaultSize; + LinkList plugins; + + PluginList(void) {} + PluginList(int32 defSize) + : size(defSize), defaultSize(defSize) + { plugins.init(); } + + static void open(void); + static void close(void); + + void construct(void *); + void destruct(void *); + void copy(void *dst, void *src); + bool streamRead(Stream *stream, void *); + void streamWrite(Stream *stream, void *); + int streamGetSize(void *); + void streamSkip(Stream *stream); + void assertRights(void *, uint32 pluginID, uint32 data); + + int32 registerPlugin(int32 size, uint32 id, + Constructor, Destructor, CopyConstructor); + int32 registerStream(uint32 id, StreamRead, StreamWrite, StreamGetSize); + int32 setStreamRightsCallback(uint32 id, RightsCallback cb); + int32 setStreamAlwaysCallback(uint32 id, AlwaysCallback cb); + int32 getPluginOffset(uint32 id); +}; + +struct Plugin +{ + int32 offset; + int32 size; + uint32 id; + Constructor constructor; + Destructor destructor; + CopyConstructor copy; + StreamRead read; + StreamWrite write; + StreamGetSize getSize; + RightsCallback rightsCallback; + AlwaysCallback alwaysCallback; + PluginList *parentList; + LLLink inParentList; + LLLink inGlobalList; +}; + +#define PLUGINBASE \ + static PluginList s_plglist; \ + static int32 registerPlugin(int32 size, uint32 id, Constructor ctor, \ + Destructor dtor, CopyConstructor copy){ \ + return s_plglist.registerPlugin(size, id, ctor, dtor, copy); \ + } \ + static int32 registerPluginStream(uint32 id, StreamRead read, \ + StreamWrite write, StreamGetSize getSize){ \ + return s_plglist.registerStream(id, read, write, getSize); \ + } \ + static int32 setStreamRightsCallback(uint32 id, RightsCallback cb){ \ + return s_plglist.setStreamRightsCallback(id, cb); \ + } \ + static int32 setStreamAlwaysCallback(uint32 id, AlwaysCallback cb){ \ + return s_plglist.setStreamAlwaysCallback(id, cb); \ + } \ + static int32 getPluginOffset(uint32 id){ \ + return s_plglist.getPluginOffset(id); \ + } + + +} diff --git a/vendor/librw/src/rwplugins.h b/vendor/librw/src/rwplugins.h new file mode 100644 index 0000000..51696b6 --- /dev/null +++ b/vendor/librw/src/rwplugins.h @@ -0,0 +1,252 @@ +namespace rw { + +/* + * HAnim + */ + +struct HAnimKeyFrame +{ + HAnimKeyFrame *prev; + float32 time; + Quat q; + V3d t; +}; + +struct HAnimInterpFrame +{ + HAnimKeyFrame *keyFrame1; + HAnimKeyFrame *keyFrame2; + Quat q; + V3d t; +}; + +struct HAnimNodeInfo +{ + int32 id; + int32 index; + int32 flags; + Frame *frame; +}; + +struct HAnimHierarchy +{ + int32 flags; + int32 numNodes; + Matrix *matrices; + void *matricesUnaligned; + HAnimNodeInfo *nodeInfo; + Frame *parentFrame; + HAnimHierarchy *parentHierarchy; // mostly unused + AnimInterpolator *interpolator; + + static HAnimHierarchy *create(int32 numNodes, int32 *nodeFlags, + int32 *nodeIDs, int32 flags, int32 maxKeySize); + void destroy(void); + void attachByIndex(int32 id); + void attach(void); + int32 getIndex(int32 id); + int32 getIndex(Frame *f); + void updateMatrices(void); + + static HAnimHierarchy *get(Frame *f); + static HAnimHierarchy *get(Clump *c){ + return find(c->getFrame()); } + static HAnimHierarchy *find(Frame *f); + + enum Flags { + SUBHIERARCHY = 0x1, + NOMATRICES = 0x2, + + UPDATEMODELLINGMATRICES = 0x1000, + UPDATELTMS = 0x2000, + LOCALSPACEMATRICES = 0x4000 + }; + enum NodeFlag { + POP = 1, + PUSH + }; +}; + +struct HAnimData +{ + int32 id; + HAnimHierarchy *hierarchy; + + static HAnimData *get(Frame *f); +}; + +extern int32 hAnimOffset; +extern bool32 hAnimDoStream; +void registerHAnimPlugin(void); + + +/* + * MatFX + */ + +struct MatFX +{ + enum { + NOTHING = 0, + BUMPMAP, + ENVMAP, + BUMPENVMAP, // BUMP | ENV + DUAL, + UVTRANSFORM, + DUALUVTRANSFORM + }; + struct Bump { + Frame *frame; + Texture *bumpedTex; + Texture *tex; + float coefficient; + }; + struct Env { + Frame *frame; + Texture *tex; + float coefficient; + int32 fbAlpha; + }; + struct Dual { + Texture *tex; + int32 srcBlend; + int32 dstBlend; + }; + struct UVtransform { + Matrix *baseTransform; + Matrix *dualTransform; + }; + struct { + uint32 type; + union { + Bump bump; + Env env; + Dual dual; + UVtransform uvtransform; + }; + } fx[2]; + uint32 type; + + static void setEffects(Material *m, uint32 flags); + static uint32 getEffects(const Material *m); + static MatFX *get(const Material *m); + int32 getEffectIndex(uint32 type); + // Bump + void setBumpTexture(Texture *t); + void setBumpCoefficient(float32 coef); + Texture *getBumpTexture(void); + float32 getBumpCoefficient(void); + // Env + void setEnvTexture(Texture *t); + void setEnvFrame(Frame *f); + void setEnvCoefficient(float32 coef); + void setEnvFBAlpha(bool32 useFBAlpha); + Texture *getEnvTexture(void); + Frame *getEnvFrame(void); + float32 getEnvCoefficient(void); + bool32 getEnvFBAlpha(void); + // Dual + void setDualTexture(Texture *t); + void setDualSrcBlend(int32 blend); + void setDualDestBlend(int32 blend); + Texture *getDualTexture(void); + int32 getDualSrcBlend(void); + int32 getDualDestBlend(void); + // UV transform + void setUVTransformMatrices(Matrix *base, Matrix *dual); + void getUVTransformMatrices(Matrix **base, Matrix **dual); + + static void enableEffects(Atomic *atomic); + static void disableEffects(Atomic *atomic); + static bool32 getEffects(Atomic *atomic); + + static bool32 envMapFlipU; // PS2 does this for some reason + static bool32 envMapApplyLight; // modulate env map by lighting + static bool32 envMapUseMatColor; // modulate env map by material color + static RGBA envMapColor; // if !envMapUseMatColor, use this +}; + +struct MatFXGlobals +{ + int32 atomicOffset; + int32 materialOffset; + ObjPipeline *pipelines[NUM_PLATFORMS]; + ObjPipeline *dummypipe; +}; +extern MatFXGlobals matFXGlobals; +void registerMatFXPlugin(void); + + +/* + * Skin + */ + +struct SkinGlobals +{ + int32 geoOffset; + int32 atomicOffset; + ObjPipeline *pipelines[NUM_PLATFORMS]; + ObjPipeline *dummypipe; +}; +extern SkinGlobals skinGlobals; + +struct Skin +{ + int32 numBones; + int32 numUsedBones; + int32 numWeights; + uint8 *usedBones; + float *inverseMatrices; + uint8 *indices; + float *weights; + + // split skin + + // points into rle for each mesh + struct RLEcount { + uint8 start; + uint8 size; + }; + // run length encoded used bones + struct RLE { + uint8 startbone; // into remapIndices + uint8 n; + }; + int32 boneLimit; + int32 numMeshes; + int32 rleSize; + int8 *remapIndices; + RLEcount *rleCount; + RLE *rle; + + uint8 *data; // only used by delete + void *platformData; // a place to store platform specific stuff + bool32 legacyType; // old skin attached to atomic, needed for always CB + + void init(int32 numBones, int32 numUsedBones, int32 numVertices); + void findNumWeights(int32 numVertices); + void findUsedBones(int32 numVertices); + + static void setPipeline(Atomic *a, int32 type); + static Skin *get(const Geometry *geo){ + return *PLUGINOFFSET(Skin*, geo, skinGlobals.geoOffset); + } + static void set(Geometry *geo, Skin *skin){ + *PLUGINOFFSET(Skin*, geo, skinGlobals.geoOffset) = skin; + } + static void setHierarchy(Atomic *atomic, HAnimHierarchy *hier){ + *PLUGINOFFSET(HAnimHierarchy*, atomic, + skinGlobals.atomicOffset) = hier; + } + static HAnimHierarchy *getHierarchy(const Atomic *atomic){ + return *PLUGINOFFSET(HAnimHierarchy*, atomic, + skinGlobals.atomicOffset); + } +}; + +Stream *readSkinSplitData(Stream *stream, Skin *skin); +Stream *writeSkinSplitData(Stream *stream, Skin *skin); +int32 skinSplitDataSize(Skin *skin); +void registerSkinPlugin(void); + +} diff --git a/vendor/librw/src/rwrender.h b/vendor/librw/src/rwrender.h new file mode 100644 index 0000000..200837a --- /dev/null +++ b/vendor/librw/src/rwrender.h @@ -0,0 +1,139 @@ +namespace rw { + +// Render states + +enum RenderState +{ + TEXTURERASTER, + TEXTUREADDRESS, + TEXTUREADDRESSU, + TEXTUREADDRESSV, + TEXTUREFILTER, + VERTEXALPHA, + SRCBLEND, + DESTBLEND, + ZTESTENABLE, + ZWRITEENABLE, + FOGENABLE, + FOGCOLOR, + CULLMODE, + // TODO: + // fog type, density ? + // ? shademode + + STENCILENABLE, + STENCILFAIL, + STENCILZFAIL, + STENCILPASS, + STENCILFUNCTION, + STENCILFUNCTIONREF, + STENCILFUNCTIONMASK, + STENCILFUNCTIONWRITEMASK, + + // platform specific or opaque? + ALPHATESTFUNC, + ALPHATESTREF, + + // emulation of PS2 GS alpha test + // in the mode where it still writes color but nor depth + GSALPHATEST, + GSALPHATESTREF +}; + +enum AlphaTestFunc +{ + ALPHAALWAYS, + ALPHAGREATEREQUAL, + ALPHALESS +}; + +enum StencilOp +{ + STENCILKEEP = 1, + STENCILZERO, + STENCILREPLACE, + STENCILINCSAT, + STENCILDECSAT, + STENCILINVERT, + STENCILINC, + STENCILDEC +}; + +enum StencilFunc +{ + STENCILNEVER = 1, + STENCILLESS, + STENCILEQUAL, + STENCILLESSEQUAL, + STENCILGREATER, + STENCILNOTEQUAL, + STENCILGREATEREQUAL, + STENCILALWAYS +}; + +enum CullMode +{ + CULLNONE = 1, + CULLBACK, + CULLFRONT +}; + +enum BlendFunction +{ + BLENDZERO = 1, + BLENDONE, + BLENDSRCCOLOR, + BLENDINVSRCCOLOR, + BLENDSRCALPHA, + BLENDINVSRCALPHA, + BLENDDESTALPHA, + BLENDINVDESTALPHA, + BLENDDESTCOLOR, + BLENDINVDESTCOLOR, + BLENDSRCALPHASAT + // TODO: add more perhaps +}; + +void SetRenderState(int32 state, uint32 value); +void SetRenderStatePtr(int32 state, void *value); +uint32 GetRenderState(int32 state); +void *GetRenderStatePtr(int32 state); + +// Im2D + +namespace im2d { + +float32 GetNearZ(void); +float32 GetFarZ(void); +void RenderLine(void *verts, int32 numVerts, int32 vert1, int32 vert2); +void RenderTriangle(void *verts, int32 numVerts, int32 vert1, int32 vert2, int32 vert3); +void RenderIndexedPrimitive(PrimitiveType, void *verts, int32 numVerts, void *indices, int32 numIndices); +void RenderPrimitive(PrimitiveType type, void *verts, int32 numVerts); + +} + +// Im3D + +namespace im3d { + +enum TransformFlags +{ + VERTEXUV = 1, // has tex Coords + ALLOPAQUE = 2, // no vertex alpha + NOCLIP = 4, // don't frustum clip + VERTEXXYZ = 8, // has position + VERTEXRGBA = 16, // has color + EVERYTHING = VERTEXUV|VERTEXXYZ|VERTEXRGBA +}; + +void Transform(void *vertices, int32 numVertices, Matrix *world, uint32 flags); +void RenderLine(int32 vert1, int32 vert2); +void RenderTriangle(int32 vert1, int32 vert2, int32 vert3); +void RenderPrimitive(PrimitiveType primType); +void RenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices); +void End(void); + +} + +} + diff --git a/vendor/librw/src/rwuserdata.h b/vendor/librw/src/rwuserdata.h new file mode 100644 index 0000000..00d7355 --- /dev/null +++ b/vendor/librw/src/rwuserdata.h @@ -0,0 +1,94 @@ +namespace rw { + +enum UserDataType +{ + USERDATANA = 0, + USERDATAINT = 1, + USERDATAFLOAT = 2, + USERDATASTRING = 3 +}; + +struct UserDataArray +{ + char *name; + uint32 datatype; + int32 numElements; + void *data; + + int32 getInt(int32 n) { return ((int32*)this->data)[n]; } + float32 getFloat(int32 n) { return ((float32*)this->data)[n]; } + char *getString(int32 n) { return ((char**)this->data)[n]; } + void setInt(int32 n, int32 i) { ((int32*)this->data)[n] = i; } + void setFloat(int32 n, float32 f) { ((float32*)this->data)[n] = f; } + void setString(int32 n, const char *s); + + static int32 geometryAdd(Geometry *g, const char *name, int32 datatype, int32 numElements); + static void geometryRemove(Geometry *g, int32 n); + static int32 geometryGetCount(Geometry *g); + static UserDataArray *geometryGet(Geometry *g, int32 n); + static int32 geometryFindIndex(Geometry *g, const char *name); + + static int32 frameAdd(Frame *f, const char *name, int32 datatype, int32 numElements); + static void frameRemove(Frame *f, int32 n); + static int32 frameGetCount(Frame *f); + static UserDataArray *frameGet(Frame *f, int32 n); + static int32 frameFindIndex(Frame *f, const char *name); + + static int32 cameraAdd(Camera *c, const char *name, int32 datatype, int32 numElements); + static void cameraRemove(Camera *c, int32 n); + static int32 cameraGetCount(Camera *c); + static UserDataArray *cameraGet(Camera *c, int32 n); + static int32 cameraFindIndex(Camera *c, const char *name); + + static int32 lightAdd(Light *l, const char *name, int32 datatype, int32 numElements); + static void lightRemove(Light *l, int32 n); + static int32 lightGetCount(Light *l); + static UserDataArray *lightGet(Light *l, int32 n); + static int32 lightFindIndex(Light *l, const char *name); + + static int32 materialAdd(Material *m, const char *name, int32 datatype, int32 numElements); + static void materialRemove(Material *m, int32 n); + static int32 materialGetCount(Material *m); + static UserDataArray *materialGet(Material *m, int32 n); + static int32 materialFindIndex(Material *m, const char *name); + + static int32 textureAdd(Texture *t, const char *name, int32 datatype, int32 numElements); + static void textureRemove(Texture *t, int32 n); + static int32 textureGetCount(Texture *t); + static UserDataArray *textureGet(Texture *t, int32 n); + static int32 textureFindIndex(Texture *t, const char *name); +}; + +struct UserDataExtension +{ + int32 numArrays; + UserDataArray *arrays; + + int32 add(const char *name, int32 datatype, int32 numElements); + void remove(int32 n); + int32 getCount(void) { return numArrays; } + UserDataArray *get(int32 n) { return n >= numArrays ? nil : &arrays[n]; } + int32 findIndex(const char *name); + + static UserDataExtension *get(Geometry *geo); + static UserDataExtension *get(Frame *frame); + static UserDataExtension *get(Camera *cam); + static UserDataExtension *get(Light *light); + static UserDataExtension *get(Material *mat); + static UserDataExtension *get(Texture *tex); +}; + +struct UserDataGlobals +{ + int32 geometryOffset; + int32 frameOffset; + int32 cameraOffset; + int32 lightOffset; + int32 materialOffset; + int32 textureOffset; +}; +extern UserDataGlobals userDataGlobals; + +void registerUserDataPlugin(void); + +} diff --git a/vendor/librw/src/skin.cpp b/vendor/librw/src/skin.cpp new file mode 100644 index 0000000..abaa548 --- /dev/null +++ b/vendor/librw/src/skin.cpp @@ -0,0 +1,486 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwanim.h" +#include "rwengine.h" +#include "rwplugins.h" +#include "ps2/rwps2.h" +#include "ps2/rwps2plg.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwwdgl.h" +#include "gl/rwgl3.h" +#include "gl/rwgl3plg.h" + +#define PLUGIN_ID ID_SKIN + +namespace rw { + +SkinGlobals skinGlobals = { 0, 0, { nil }, nil }; + +static void* +createSkin(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(Skin*, object, offset) = nil; + return object; +} + +static void* +destroySkin(void *object, int32 offset, int32) +{ + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin){ + rwFree(skin->data); + rwFree(skin->remapIndices); +// delete[] skin->platformData; + } + rwFree(skin); + return object; +} + +static void* +copySkin(void *dst, void *src, int32 offset, int32) +{ + Skin *srcskin = *PLUGINOFFSET(Skin*, src, offset); + if(srcskin == nil) + return dst; + Geometry *geometry = (Geometry*)src; + assert(geometry->instData == nil); + assert(((Geometry*)src)->numVertices == ((Geometry*)dst)->numVertices); + Skin *dstskin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, dst, offset) = dstskin; + dstskin->numBones = srcskin->numBones; + dstskin->numUsedBones = srcskin->numUsedBones; + dstskin->numWeights = srcskin->numWeights; + + assert(0 && "can't copy skin yet"); + dstskin->init(srcskin->numBones, srcskin->numUsedBones, + geometry->numVertices); + memcpy(dstskin->usedBones, srcskin->usedBones, srcskin->numUsedBones); + memcpy(dstskin->inverseMatrices, srcskin->inverseMatrices, + srcskin->numBones*64); + memcpy(dstskin->indices, srcskin->indices, geometry->numVertices*4); + memcpy(dstskin->weights, srcskin->weights, geometry->numVertices*16); + return dst; +} + +Stream* +readSkinSplitData(Stream *stream, Skin *skin) +{ + uint32 sz; + int8 *data; + + skin->boneLimit = stream->readI32(); + skin->numMeshes = stream->readI32(); + skin->rleSize = stream->readI32(); + if(skin->numMeshes){ + sz = skin->numBones + 2*(skin->numMeshes+skin->rleSize); + data = (int8*)rwMalloc(sz, MEMDUR_EVENT | ID_SKIN); + stream->read8(data, sz); + skin->remapIndices = data; + skin->rleCount = (Skin::RLEcount*)(data + skin->numBones); + skin->rle = (Skin::RLE*)(data + skin->numBones + 2*skin->numMeshes); + } + return stream; +} + +Stream* +writeSkinSplitData(Stream *stream, Skin *skin) +{ + stream->writeI32(skin->boneLimit); + stream->writeI32(skin->numMeshes); + stream->writeI32(skin->rleSize); + if(skin->numMeshes) + stream->write8(skin->remapIndices, + skin->numBones + 2*(skin->numMeshes+skin->rleSize)); + return stream; +} + +int32 +skinSplitDataSize(Skin *skin) +{ + if(skin->numMeshes == 0) + return 12; + return 12 + skin->numBones + 2*(skin->numMeshes+skin->rleSize); +} + +static Stream* +readSkin(Stream *stream, int32 len, void *object, int32 offset, int32) +{ + uint8 header[4]; + Geometry *geometry = (Geometry*)object; + + if(geometry->instData){ + // TODO: function pointers + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::readNativeSkin(stream, len, object, offset); + else if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::readNativeSkin(stream, len, object, offset); + else if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::readNativeSkin(stream, len, object, offset); + else{ + assert(0 && "unsupported native skin platform"); + return nil; + } + } + + stream->read8(header, 4); // numBones, numUsedBones, + // numWeights, unused + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, offset) = skin; + + // numUsedBones and numWeights appear in/after 34003 + // but not in/before 33002 (probably rw::version >= 0x34000) + bool oldFormat = header[1] == 0; + + // Use numBones for numUsedBones to allocate data, + // find out the correct value later + if(oldFormat) + skin->init(header[0], header[0], geometry->numVertices); + else + skin->init(header[0], header[1], geometry->numVertices); + skin->numWeights = header[2]; + + if(!oldFormat) + stream->read8(skin->usedBones, skin->numUsedBones); + if(skin->indices) + stream->read8(skin->indices, geometry->numVertices*4); + if(skin->weights) + stream->read32(skin->weights, geometry->numVertices*16); + for(int32 i = 0; i < skin->numBones; i++){ + if(oldFormat) + stream->seek(4); // skip 0xdeaddead + stream->read32(&skin->inverseMatrices[i*16], 64); + } + + if(oldFormat){ + skin->findNumWeights(geometry->numVertices); + skin->findUsedBones(geometry->numVertices); + } + + if(!oldFormat) + readSkinSplitData(stream, skin); + + return stream; +} + +static Stream* +writeSkin(Stream *stream, int32 len, void *object, int32 offset, int32) +{ + uint8 header[4]; + Geometry *geometry = (Geometry*)object; + + if(geometry->instData){ + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::writeNativeSkin(stream, len, object, offset); + else if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::writeNativeSkin(stream, len, object, offset); + else if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::writeNativeSkin(stream, len, object, offset); + else{ + assert(0 && "unsupported native skin platform"); + return nil; + } + } + + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + // not sure which version introduced the new format + bool oldFormat = version < 0x34000; + header[0] = skin->numBones; + if(oldFormat){ + header[1] = 0; + header[2] = 0; + }else{ + header[1] = skin->numUsedBones; + header[2] = skin->numWeights; + } + header[3] = 0; + stream->write8(header, 4); + if(!oldFormat) + stream->write8(skin->usedBones, skin->numUsedBones); + stream->write8(skin->indices, geometry->numVertices*4); + stream->write32(skin->weights, geometry->numVertices*16); + for(int32 i = 0; i < skin->numBones; i++){ + if(oldFormat) + stream->writeU32(0xdeaddead); + stream->write32(&skin->inverseMatrices[i*16], 64); + } + + if(!oldFormat) + writeSkinSplitData(stream, skin); + return stream; +} + +static int32 +getSizeSkin(void *object, int32 offset, int32) +{ + Geometry *geometry = (Geometry*)object; + + if(geometry->instData){ + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::getSizeNativeSkin(object, offset); + if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::getSizeNativeSkin(object, offset); + if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::getSizeNativeSkin(object, offset); + if(geometry->instData->platform == PLATFORM_D3D8) + return -1; + if(geometry->instData->platform == PLATFORM_D3D9) + return -1; + assert(0 && "unsupported native skin platform"); + } + + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin == nil) + return -1; + + int32 size = 4 + geometry->numVertices*(16+4) + + skin->numBones*64; + // not sure which version introduced the new format + if(version < 0x34000) + size += skin->numBones*4; + else + size += skin->numUsedBones + skinSplitDataSize(skin); + return size; +} + +static Stream* +readSkinLegacy(Stream *stream, int32 len, void *object, int32, int32) +{ + Atomic *atomic = (Atomic*)object; + Geometry *geometry = atomic->geometry; + + int32 numBones = stream->readI32(); + int32 numVertices = stream->readI32(); + assert(numVertices == geometry->numVertices); + + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, skinGlobals.geoOffset) = skin; + skin->init(numBones, numBones, numVertices); + skin->legacyType = 1; + skin->numWeights = 4; + + stream->read8(skin->indices, numVertices*4); + stream->read32(skin->weights, numVertices*16); + + HAnimHierarchy *hier = HAnimHierarchy::create(numBones, nil, nil, 0, 36); + + for(int i = 0; i < numBones; i++){ + hier->nodeInfo[i].id = stream->readI32(); + hier->nodeInfo[i].index = stream->readI32(); + hier->nodeInfo[i].flags = stream->readI32() & 3; +// printf("%d %d %d %d\n", i, hier->nodeInfo[i].id, hier->nodeInfo[i].index, hier->nodeInfo[i].flags); + stream->read32(&skin->inverseMatrices[i*16], 64); + + Matrix mat; + Matrix::invert(&mat, (Matrix*)&skin->inverseMatrices[i*16]); +// printf("[ [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" +// " [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" +// " [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" +// " [ %8.4f, %8.4f, %8.4f, %8.4f ] ]\n" +// " %08x == flags\n", +// mat.right.x, mat.up.x, mat.at.x, mat.pos.x, +// mat.right.y, mat.up.y, mat.at.y, mat.pos.y, +// mat.right.z, mat.up.z, mat.at.z, mat.pos.z, +// 0.0f, 0.0f, 0.0f, 1.0f, +// mat.flags); + } + Frame *frame = atomic->getFrame()->child; + assert(frame->next == nil); // in old files atomic is above hierarchy it seems + assert(frame->count() == numBones); // assuming one frame per node this should also be true + HAnimData::get(frame)->hierarchy = hier; + hier->parentFrame = frame; + + return stream; +} + +static void +skinRights(void *object, int32, int32, uint32) +{ + Skin::setPipeline((Atomic*)object, 1); +} + +static void +skinAlways(void *object, int32, int32) +{ + Atomic *atomic = (Atomic*)object; + Geometry *geo = atomic->geometry; + if(geo == nil) return; + Skin *skin = Skin::get(geo); + if(skin == nil) return; + Skin::setPipeline((Atomic*)object, 1); +} + +static void* +createSkinAtm(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(void*, object, offset) = nil; + return object; +} + +static void* +destroySkinAtm(void *object, int32 offset, int32) +{ + return object; +} + +static void* +copySkinAtm(void *dst, void *src, int32 offset, int32) +{ + *PLUGINOFFSET(void*, dst, offset) = *PLUGINOFFSET(void*, src, offset); + return dst; +} + +static void* +skinOpen(void *o, int32, int32) +{ + // init dummy pipelines + skinGlobals.dummypipe = ObjPipeline::create(); + skinGlobals.dummypipe->pluginID = ID_SKIN; + skinGlobals.dummypipe->pluginData = 1; + for(uint i = 0; i < nelem(skinGlobals.pipelines); i++) + skinGlobals.pipelines[i] = skinGlobals.dummypipe; + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + for(uint i = 0; i < nelem(skinGlobals.pipelines); i++) + if(skinGlobals.pipelines[i] == skinGlobals.dummypipe) + matFXGlobals.pipelines[i] = nil; + skinGlobals.dummypipe->destroy(); + skinGlobals.dummypipe = nil; + return o; +} + +void +registerSkinPlugin(void) +{ + Driver::registerPlugin(PLATFORM_NULL, 0, ID_SKIN, + skinOpen, skinClose); + ps2::initSkin(); + xbox::initSkin(); + d3d8::initSkin(); + d3d9::initSkin(); + wdgl::initSkin(); + gl3::initSkin(); + + int32 o; + o = Geometry::registerPlugin(sizeof(Skin*), ID_SKIN, + createSkin, destroySkin, copySkin); + Geometry::registerPluginStream(ID_SKIN, + readSkin, writeSkin, getSizeSkin); + skinGlobals.geoOffset = o; + o = Atomic::registerPlugin(sizeof(HAnimHierarchy*),ID_SKIN, + createSkinAtm, destroySkinAtm, copySkinAtm); + skinGlobals.atomicOffset = o; + Atomic::registerPluginStream(ID_SKIN, readSkinLegacy, nil, nil); + Atomic::setStreamRightsCallback(ID_SKIN, skinRights); + Atomic::setStreamAlwaysCallback(ID_SKIN, skinAlways); +} + +void +Skin::init(int32 numBones, int32 numUsedBones, int32 numVertices) +{ + this->numBones = numBones; + this->numUsedBones = numUsedBones; + uint32 size = this->numUsedBones + + this->numBones*64 + + numVertices*(16+4) + 0xF; + this->data = rwNewT(uint8, size, MEMDUR_EVENT | ID_SKIN); + uint8 *p = this->data; + + this->usedBones = nil; + if(this->numUsedBones){ + this->usedBones = p; + p += this->numUsedBones; + } + + p = (uint8*)(((uintptr)p + 0xF) & ~0xF); + this->inverseMatrices = nil; + if(this->numBones){ + this->inverseMatrices = (float*)p; + p += 64*this->numBones; + } + + this->indices = nil; + if(numVertices){ + this->indices = p; + p += 4*numVertices; + } + + this->weights = nil; + if(numVertices) + this->weights = (float*)p; + + this->boneLimit = 0; + this->numMeshes = 0; + this->rleSize = 0; + this->remapIndices = nil; + this->rleCount = nil; + this->rle = nil; + + this->platformData = nil; + this->legacyType = 0; +} + + + +//static_assert(sizeof(Skin::RLEcount) == 2, "RLEcount size"); +//static_assert(sizeof(Skin::RLE) == 2, "RLE size"); + +void +Skin::findNumWeights(int32 numVertices) +{ + this->numWeights = 1; + float *w = this->weights; + while(numVertices--){ + while(w[this->numWeights] != 0.0f){ + this->numWeights++; + if(this->numWeights == 4) + return; + } + w += 4; + } +} + +void +Skin::findUsedBones(int32 numVertices) +{ + uint8 usedTab[256]; + uint8 *indices = this->indices; + float *weights = this->weights; + memset(usedTab, 0, 256); + while(numVertices--){ + for(int32 i = 0; i < this->numWeights; i++){ + if(weights[i] == 0.0f) + continue; // TODO: this could probably be optimized + if(usedTab[indices[i]] == 0) + usedTab[indices[i]]++; + } + indices += 4; + weights += 4; + } + this->numUsedBones = 0; + for(int32 i = 0; i < 256; i++) + if(usedTab[i]) + this->usedBones[this->numUsedBones++] = i; +} + +void +Skin::setPipeline(Atomic *a, int32 type) +{ + (void)type; + a->pipeline = skinGlobals.pipelines[rw::platform]; +} + +} diff --git a/vendor/librw/src/texture.cpp b/vendor/librw/src/texture.cpp new file mode 100644 index 0000000..958ae08 --- /dev/null +++ b/vendor/librw/src/texture.cpp @@ -0,0 +1,596 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "ps2/rwps2.h" +#include "d3d/rwd3d.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "d3d/rwd3dimpl.h" +#include "gl/rwgl3.h" + +#define PLUGIN_ID 0 + +namespace rw { + +int32 Texture::numAllocated; +int32 TexDictionary::numAllocated; + +PluginList TexDictionary::s_plglist(sizeof(TexDictionary)); +PluginList Texture::s_plglist(sizeof(Texture)); +PluginList Raster::s_plglist(sizeof(Raster)); + +struct TextureGlobals +{ + TexDictionary *initialTexDict; + TexDictionary *currentTexDict; + // load textures from files + bool32 loadTextures; + // create dummy textures to store just names + bool32 makeDummies; + bool32 mipmapping; + bool32 autoMipmapping; + LinkList texDicts; + + LinkList textures; +}; +int32 textureModuleOffset; + +#define TEXTUREGLOBAL(v) (PLUGINOFFSET(TextureGlobals, engine, textureModuleOffset)->v) + +static void* +textureOpen(void *object, int32 offset, int32 size) +{ + TexDictionary *texdict; + textureModuleOffset = offset; + TEXTUREGLOBAL(texDicts).init(); + TEXTUREGLOBAL(textures).init(); + texdict = TexDictionary::create(); + TEXTUREGLOBAL(initialTexDict) = texdict; + TexDictionary::setCurrent(texdict); + TEXTUREGLOBAL(loadTextures) = 1; + TEXTUREGLOBAL(makeDummies) = 0; + TEXTUREGLOBAL(mipmapping) = 0; + TEXTUREGLOBAL(autoMipmapping) = 0; + return object; +} +static void* +textureClose(void *object, int32 offset, int32 size) +{ + FORLIST(lnk, TEXTUREGLOBAL(texDicts)) + TexDictionary::fromLink(lnk)->destroy(); + TEXTUREGLOBAL(initialTexDict) = nil; + TEXTUREGLOBAL(currentTexDict) = nil; + + FORLIST(lnk, TEXTUREGLOBAL(textures)){ + Texture *tex = LLLinkGetData(lnk, Texture, inGlobalList); + printf("Tex still allocated: %d %s %s\n", tex->refCount, tex->name, tex->mask); + assert(tex->dict == nil); + tex->destroy(); + } + return object; +} + +void +Texture::registerModule(void) +{ + Engine::registerPlugin(sizeof(TextureGlobals), ID_TEXTUREMODULE, textureOpen, textureClose); +} + +void +Texture::setLoadTextures(bool32 b) +{ + TEXTUREGLOBAL(loadTextures) = b; +} + +void +Texture::setCreateDummies(bool32 b) +{ + TEXTUREGLOBAL(makeDummies) = b; +} + +void Texture::setMipmapping(bool32 b) { TEXTUREGLOBAL(mipmapping) = b; } +void Texture::setAutoMipmapping(bool32 b) { TEXTUREGLOBAL(autoMipmapping) = b; } +bool32 Texture::getMipmapping(void) { return TEXTUREGLOBAL(mipmapping); } +bool32 Texture::getAutoMipmapping(void) { return TEXTUREGLOBAL(autoMipmapping); } + +// +// TexDictionary +// + +TexDictionary* +TexDictionary::create(void) +{ + TexDictionary *dict = (TexDictionary*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_TEXDICTIONARY); + if(dict == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + dict->object.init(TexDictionary::ID, 0); + dict->textures.init(); + TEXTUREGLOBAL(texDicts).add(&dict->inGlobalList); + s_plglist.construct(dict); + return dict; +} + +void +TexDictionary::destroy(void) +{ + if(TEXTUREGLOBAL(currentTexDict) == this) + TEXTUREGLOBAL(currentTexDict) = nil; + FORLIST(lnk, this->textures){ + Texture *tex = Texture::fromDict(lnk); + this->remove(tex); + tex->destroy(); + } + s_plglist.destruct(this); + this->inGlobalList.remove(); + rwFree(this); + numAllocated--; +} + +void +TexDictionary::add(Texture *t) +{ + if(t->dict) + t->inDict.remove(); + t->dict = this; + this->textures.append(&t->inDict); +} + +void +TexDictionary::remove(Texture *t) +{ + assert(t->dict == this); + t->inDict.remove(); + t->dict = nil; +} + +void +TexDictionary::addFront(Texture *t) +{ + if(t->dict) + t->inDict.remove(); + t->dict = this; + this->textures.add(&t->inDict); +} + +Texture* +TexDictionary::find(const char *name) +{ + FORLIST(lnk, this->textures){ + Texture *tex = Texture::fromDict(lnk); + if(strncmp_ci(tex->name, name, 32) == 0) + return tex; + } + return nil; +} + +TexDictionary* +TexDictionary::streamRead(Stream *stream) +{ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + int32 numTex = stream->readI16(); + stream->readI16(); // device id (0 = unknown, 1 = d3d8, 2 = d3d9, + // 3 = gcn, 4 = null, 5 = opengl, + // 6 = ps2, 7 = softras, 8 = xbox, 9 = psp) + TexDictionary *txd = TexDictionary::create(); + if(txd == nil) + return nil; + Texture *tex; + for(int32 i = 0; i < numTex; i++){ + if(!findChunk(stream, ID_TEXTURENATIVE, nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURENATIVE")); + goto fail; + } + tex = Texture::streamReadNative(stream); + if(tex == nil) + goto fail; + Texture::s_plglist.streamRead(stream, tex); + txd->add(tex); + } + if(s_plglist.streamRead(stream, txd)) + return txd; +fail: + txd->destroy(); + return nil; +} + +void +TexDictionary::streamWrite(Stream *stream) +{ + writeChunkHeader(stream, ID_TEXDICTIONARY, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, 4); + int32 numTex = this->count(); + stream->writeI16(numTex); + stream->writeI16(0); + FORLIST(lnk, this->textures){ + Texture *tex = Texture::fromDict(lnk); + uint32 sz = tex->streamGetSizeNative(); + sz += 12 + Texture::s_plglist.streamGetSize(tex); + writeChunkHeader(stream, ID_TEXTURENATIVE, sz); + tex->streamWriteNative(stream); + Texture::s_plglist.streamWrite(stream, tex); + } + s_plglist.streamWrite(stream, this); +} + +uint32 +TexDictionary::streamGetSize(void) +{ + uint32 size = 12 + 4; + FORLIST(lnk, this->textures){ + Texture *tex = Texture::fromDict(lnk); + size += 12 + tex->streamGetSizeNative(); + size += 12 + Texture::s_plglist.streamGetSize(tex); + } + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +void +TexDictionary::setCurrent(TexDictionary *txd) +{ + PLUGINOFFSET(TextureGlobals, engine, textureModuleOffset)->currentTexDict = txd; +} + +TexDictionary* +TexDictionary::getCurrent(void) +{ + return PLUGINOFFSET(TextureGlobals, engine, textureModuleOffset)->currentTexDict; +} + +// +// Texture +// + +static Texture *defaultFindCB(const char *name); +static Texture *defaultReadCB(const char *name, const char *mask); + +Texture *(*Texture::findCB)(const char *name) = defaultFindCB; +Texture *(*Texture::readCB)(const char *name, const char *mask) = defaultReadCB; + +Texture* +Texture::create(Raster *raster) +{ + Texture *tex = (Texture*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_TEXTURE); + if(tex == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + tex->dict = nil; + tex->inDict.init(); + memset(tex->name, 0, 32); + memset(tex->mask, 0, 32); + tex->filterAddressing = (WRAP << 12) | (WRAP << 8) | NEAREST; + tex->raster = raster; + tex->refCount = 1; + TEXTUREGLOBAL(textures).add(&tex->inGlobalList); + s_plglist.construct(tex); + return tex; +} + +void +Texture::destroy(void) +{ + this->refCount--; + if(this->refCount <= 0){ + s_plglist.destruct(this); + if(this->dict) + this->inDict.remove(); + if(this->raster) + this->raster->destroy(); + this->inGlobalList.remove(); + rwFree(this); + numAllocated--; + } +} + +static Texture* +defaultFindCB(const char *name) +{ + if(TEXTUREGLOBAL(currentTexDict)) + return TEXTUREGLOBAL(currentTexDict)->find(name); + // TODO: RW searches *all* TXDs otherwise + return nil; +} + + +static Texture* +defaultReadCB(const char *name, const char *mask) +{ + Texture *tex; + Image *img; + + img = Image::readMasked(name, mask); + if(img){ + tex = Texture::create(Raster::createFromImage(img)); + strncpy(tex->name, name, 32); + if(mask) + strncpy(tex->mask, mask, 32); + img->destroy(); + return tex; + }else + return nil; +} + +Texture* +Texture::read(const char *name, const char *mask) +{ + (void)mask; + Raster *raster = nil; + Texture *tex; + + if(tex = Texture::findCB(name), tex){ + tex->addRef(); + return tex; + } + if(TEXTUREGLOBAL(loadTextures)){ + tex = Texture::readCB(name, mask); + if(tex == nil) + goto dummytex; + }else dummytex: if(TEXTUREGLOBAL(makeDummies)){ +//printf("missing texture %s %s\n", name ? name : "", mask ? mask : ""); + tex = Texture::create(nil); + if(tex == nil) + return nil; + strncpy(tex->name, name, 32); + if(mask) + strncpy(tex->mask, mask, 32); + raster = Raster::create(0, 0, 0, Raster::DONTALLOCATE); + tex->raster = raster; + } + if(tex && TEXTUREGLOBAL(currentTexDict)){ + if(tex->dict) + tex->inDict.remove(); + TEXTUREGLOBAL(currentTexDict)->add(tex); + } + return tex; +} + +Texture* +Texture::streamRead(Stream *stream) +{ + uint32 length; + char name[128], mask[128]; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + uint32 filterAddressing = stream->readU32(); + // if V addressing is 0, copy U + if((filterAddressing & 0xF000) == 0) + filterAddressing |= (filterAddressing&0xF00) << 4; + + // if using mipmap filter mode, set automipmapping, + // if 0x10000 is set, set mipmapping + + if(!findChunk(stream, ID_STRING, &length, nil)){ + RWERROR((ERR_CHUNK, "STRING")); + return nil; + } + stream->read8(name, length); + + if(!findChunk(stream, ID_STRING, &length, nil)){ + RWERROR((ERR_CHUNK, "STRING")); + return nil; + } + stream->read8(mask, length); + + bool32 mipState = getMipmapping(); + bool32 autoMipState = getAutoMipmapping(); + int32 filter = filterAddressing&0xFF; + if(filter == MIPNEAREST || filter == MIPLINEAR || + filter == LINEARMIPNEAREST || filter == LINEARMIPLINEAR){ + setMipmapping(1); + setAutoMipmapping((filterAddressing&0x10000) == 0); + }else{ + setMipmapping(0); + setAutoMipmapping(0); + } + + Texture *tex = Texture::read(name, mask); + + setMipmapping(mipState); + setAutoMipmapping(autoMipState); + + if(tex == nil){ + s_plglist.streamSkip(stream); + return nil; + } + if(tex->refCount == 1) + tex->filterAddressing = filterAddressing&0xFFFF; + + if(s_plglist.streamRead(stream, tex)) + return tex; + + tex->destroy(); + return nil; +} + +bool +Texture::streamWrite(Stream *stream) +{ + int size; + char buf[36]; + writeChunkHeader(stream, ID_TEXTURE, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, 4); + uint32 filterAddressing = this->filterAddressing; + if(this->raster && (raster->format & Raster::AUTOMIPMAP) == 0) + filterAddressing |= 0x10000; + stream->writeU32(filterAddressing); + + memset(buf, 0, 36); + strncpy(buf, this->name, 32); + size = strlen(buf)+4 & ~3; + writeChunkHeader(stream, ID_STRING, size); + stream->write8(buf, size); + + memset(buf, 0, 36); + strncpy(buf, this->mask, 32); + size = strlen(buf)+4 & ~3; + writeChunkHeader(stream, ID_STRING, size); + stream->write8(buf, size); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Texture::streamGetSize(void) +{ + uint32 size = 0; + size += 12 + 4; + size += 12 + 12; + size += strlen(this->name)+4 & ~3; + size += strlen(this->mask)+4 & ~3; + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +Texture* +Texture::streamReadNative(Stream *stream) +{ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + uint32 platform = stream->readU32(); + stream->seek(-16); + if(platform == FOURCC_PS2) + return ps2::readNativeTexture(stream); + if(platform == PLATFORM_D3D8) + return d3d8::readNativeTexture(stream); + if(platform == PLATFORM_D3D9) + return d3d9::readNativeTexture(stream); + if(platform == PLATFORM_XBOX) + return xbox::readNativeTexture(stream); + if(platform == PLATFORM_GL3) + return gl3::readNativeTexture(stream); + assert(0 && "unsupported platform"); + return nil; +} + +void +Texture::streamWriteNative(Stream *stream) +{ + if(this->raster->platform == PLATFORM_PS2) + ps2::writeNativeTexture(this, stream); + else if(this->raster->platform == PLATFORM_D3D8) + d3d8::writeNativeTexture(this, stream); + else if(this->raster->platform == PLATFORM_D3D9) + d3d9::writeNativeTexture(this, stream); + else if(this->raster->platform == PLATFORM_XBOX) + xbox::writeNativeTexture(this, stream); + else if(this->raster->platform == PLATFORM_GL3) + gl3::writeNativeTexture(this, stream); + else + assert(0 && "unsupported platform"); +} + +uint32 +Texture::streamGetSizeNative(void) +{ + if(this->raster->platform == PLATFORM_PS2) + return ps2::getSizeNativeTexture(this); + if(this->raster->platform == PLATFORM_D3D8) + return d3d8::getSizeNativeTexture(this); + if(this->raster->platform == PLATFORM_D3D9) + return d3d9::getSizeNativeTexture(this); + if(this->raster->platform == PLATFORM_XBOX) + return xbox::getSizeNativeTexture(this); + if(this->raster->platform == PLATFORM_GL3) + return gl3::getSizeNativeTexture(this); + assert(0 && "unsupported platform"); + return 0; +} + + + +int32 anisotOffset; + +static void* +createAnisot(void *object, int32 offset, int32) +{ + *GETANISOTROPYEXT(object) = 1; + return object; +} + +static void* +copyAnisot(void *dst, void *src, int32 offset, int32) +{ + *GETANISOTROPYEXT(dst) = *GETANISOTROPYEXT(src); + return dst; +} + +static Stream* +readAnisot(Stream *stream, int32, void *object, int32 offset, int32) +{ + *GETANISOTROPYEXT(object) = stream->readI32(); + return stream; +} + +static Stream* +writeAnisot(Stream *stream, int32, void *object, int32 offset, int32) +{ + stream->writeI32(*GETANISOTROPYEXT(object)); + return stream; +} + +static int32 +getSizeAnisot(void *object, int32 offset, int32) +{ + if(*GETANISOTROPYEXT(object) == 1) + return 0; + return sizeof(int32); +} + +void +registerAnisotropyPlugin(void) +{ + anisotOffset = Texture::registerPlugin(sizeof(int32), ID_ANISOT, createAnisot, nil, copyAnisot); + Texture::registerPluginStream(ID_ANISOT, readAnisot, writeAnisot, getSizeAnisot); +} + +void +Texture::setMaxAnisotropy(int32 maxaniso) +{ + if(anisotOffset > 0) + *GETANISOTROPYEXT(this) = maxaniso; +} + +int32 +Texture::getMaxAnisotropy(void) +{ + if(anisotOffset > 0) + return *GETANISOTROPYEXT(this); + return 1; +} + +int32 +getMaxSupportedMaxAnisotropy(void) +{ +#ifdef RW_D3D9 + return d3d::d3d9Globals.caps.MaxAnisotropy; +#endif +#ifdef RW_GL3 + return (int32)gl3::gl3Caps.maxAnisotropy; +#endif + return 1; +} + +} diff --git a/vendor/librw/src/tga.cpp b/vendor/librw/src/tga.cpp new file mode 100644 index 0000000..cbe845f --- /dev/null +++ b/vendor/librw/src/tga.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 0 + +namespace rw { + + +// NB: this has padding and cannot be streamed directly! +struct TGAHeader +{ + int8 IDlen; + int8 colorMapType; + int8 imageType; + int16 colorMapOrigin; + int16 colorMapLength; + int8 colorMapDepth; + int16 xOrigin, yOrigin; + int16 width, height; + uint8 depth; + uint8 descriptor; + + void read(Stream *stream); + void write(Stream *stream); +}; + +void +TGAHeader::read(Stream *stream) +{ + IDlen = stream->readI8(); + colorMapType = stream->readI8(); + imageType = stream->readI8(); + colorMapOrigin = stream->readI16(); + colorMapLength = stream->readI16(); + colorMapDepth = stream->readI8(); + xOrigin = stream->readI16(); + yOrigin = stream->readI16(); + width = stream->readI16(); + height = stream->readI16(); + depth = stream->readU8(); + descriptor = stream->readU8(); +} + +void +TGAHeader::write(Stream *stream) +{ + stream->writeI8(IDlen); + stream->writeI8(colorMapType); + stream->writeI8(imageType); + stream->writeI16(colorMapOrigin); + stream->writeI16(colorMapLength); + stream->writeI8(colorMapDepth); + stream->writeI16(xOrigin); + stream->writeI16(yOrigin); + stream->writeI16(width); + stream->writeI16(height); + stream->writeU8(depth); + stream->writeU8(descriptor); +} + +Image* +readTGA(const char *filename) +{ + TGAHeader header; + Image *image; + int depth = 0, palDepth = 0; + uint32 length; + uint8 *data = getFileContents(filename, &length); + assert(data != nil); + StreamMemory file; + file.open(data, length); + header.read(&file); + + assert(header.imageType == 1 || header.imageType == 2); + file.seek(header.IDlen); + if(header.colorMapType){ + assert(header.colorMapOrigin == 0); + depth = (header.colorMapLength <= 16) ? 4 : 8; + palDepth = header.colorMapDepth; + assert(palDepth == 24 || palDepth == 32); + }else{ + depth = header.depth; + assert(depth == 16 || depth == 24 || depth == 32); + } + + image = Image::create(header.width, header.height, depth); + image->allocate(); + uint8 *palette = header.colorMapType ? image->palette : nil; + uint8 (*color)[4] = nil; + if(palette){ + int maxlen = depth == 4 ? 16 : 256; + color = (uint8(*)[4])palette; + int i; + for(i = 0; i < header.colorMapLength; i++){ + color[i][2] = file.readU8(); + color[i][1] = file.readU8(); + color[i][0] = file.readU8(); + color[i][3] = 0xFF; + if(palDepth == 32) + color[i][3] = file.readU8(); + } + for(; i < maxlen; i++){ + color[i][0] = color[i][1] = color[i][2] = 0; + color[i][3] = 0xFF; + } + } + + uint8 *pixels = image->pixels; + if(!(header.descriptor & 0x20)) + pixels += (image->height-1)*image->stride; + for(int y = 0; y < image->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < image->width; x++){ + switch(image->depth){ + case 4: + case 8: + line[0] = file.readU8(); + break; + case 16: + line[0] = file.readU8(); + line[1] = file.readU8(); + break; + case 24: + line[2] = file.readU8(); + line[1] = file.readU8(); + line[0] = file.readU8(); + break; + case 32: + line[2] = file.readU8(); + line[1] = file.readU8(); + line[0] = file.readU8(); + line[3] = file.readU8(); + break; + } + line += image->bpp; + } + pixels += (header.descriptor&0x20) ? + image->stride : -image->stride; + } + + file.close(); + rwFree(data); + return image; +} + +void +writeTGA(Image *image, const char *filename) +{ + TGAHeader header; + StreamFile file; + if(!file.open(filename, "wb")){ + RWERROR((ERR_FILE, filename)); + return; + } + header.IDlen = 0; + header.imageType = image->palette != nil ? 1 : 2; + header.colorMapType = image->palette != nil; + header.colorMapOrigin = 0; + header.colorMapLength = image->depth == 4 ? 16 : + image->depth == 8 ? 256 : 0; + header.colorMapDepth = image->palette ? 32 : 0; + header.xOrigin = 0; + header.yOrigin = 0; + header.width = image->width; + header.height = image->height; + header.depth = image->depth == 4 ? 8 : image->depth; + header.descriptor = 0x20 | (image->depth == 32 ? 8 : 0); + header.write(&file); + + uint8 *palette = header.colorMapType ? image->palette : nil; + uint8 (*color)[4] = (uint8(*)[4])palette;; + if(palette) + for(int i = 0; i < header.colorMapLength; i++){ + file.writeU8(color[i][2]); + file.writeU8(color[i][1]); + file.writeU8(color[i][0]); + file.writeU8(color[i][3]); + } + + uint8 *line = image->pixels; + uint8 *p; + for(int y = 0; y < image->height; y++){ + p = line; + for(int x = 0; x < image->width; x++){ + switch(image->depth){ + case 4: + case 8: + file.writeU8(p[0]); + break; + case 16: + file.writeU8(p[0]); + file.writeU8(p[1]); + break; + case 24: + file.writeU8(p[2]); + file.writeU8(p[1]); + file.writeU8(p[0]); + break; + case 32: + file.writeU8(p[2]); + file.writeU8(p[1]); + file.writeU8(p[0]); + file.writeU8(p[3]); + break; + } + p += image->bpp; + } + line += image->stride; + } + file.close(); +} + +} diff --git a/vendor/librw/src/tristrip.cpp b/vendor/librw/src/tristrip.cpp new file mode 100644 index 0000000..bd9123f --- /dev/null +++ b/vendor/librw/src/tristrip.cpp @@ -0,0 +1,682 @@ +#include +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 2 + +namespace rw { + +struct GraphEdge +{ + int32 node; /* index of the connected node */ + uint32 isConnected : 1; /* is connected to other node */ + uint32 otherEdge : 2; /* edge number on connected node */ + uint32 isStrip : 1; /* is strip edge */ +}; + +struct StripNode +{ + uint16 v[3]; /* vertex indices */ + uint8 parent : 2; /* tunnel parent node (edge index) */ + uint8 visited : 1; /* visited in breadth first search */ + uint8 stripVisited : 1; /* strip starting at this node was visited during search */ + uint8 isEnd : 1; /* is in end list */ + GraphEdge e[3]; + int32 stripId; /* index of start node */ + LLLink inlist; +}; + +struct StripMesh +{ + int32 numNodes; + StripNode *nodes; + LinkList loneNodes; /* nodes not connected to any others */ + LinkList endNodes; /* strip start/end nodes */ +}; + +//#define trace(...) printf(__VA_ARGS__) +#define trace(...) + +static void +printNode(StripMesh *sm, StripNode *n) +{ + trace("%3ld: %3d %3d.%d %3d.%d %3d.%d || %3d %3d %3d\n", + n - sm->nodes, + n->stripId, + n->e[0].node, + n->e[0].isStrip, + n->e[1].node, + n->e[1].isStrip, + n->e[2].node, + n->e[2].isStrip, + n->v[0], + n->v[1], + n->v[2]); +} + +static void +printLone(StripMesh *sm) +{ + FORLIST(lnk, sm->loneNodes) + printNode(sm, LLLinkGetData(lnk, StripNode, inlist)); +} + +static void +printEnds(StripMesh *sm) +{ + FORLIST(lnk, sm->endNodes) + printNode(sm, LLLinkGetData(lnk, StripNode, inlist)); +} + +static void +printSmesh(StripMesh *sm) +{ + for(int32 i = 0; i < sm->numNodes; i++) + printNode(sm, &sm->nodes[i]); +} + +static void +collectFaces(Geometry *geo, StripMesh *sm, uint16 m) +{ + StripNode *n; + Triangle *t; + sm->numNodes = 0; + for(int32 i = 0; i < geo->numTriangles; i++){ + t = &geo->triangles[i]; + if(t->matId == m){ + n = &sm->nodes[sm->numNodes++]; + n->v[0] = t->v[0]; + n->v[1] = t->v[1]; + n->v[2] = t->v[2]; + assert(t->v[0] < geo->numVertices); + assert(t->v[1] < geo->numVertices); + assert(t->v[2] < geo->numVertices); + n->e[0].node = 0; + n->e[1].node = 0; + n->e[2].node = 0; + n->e[0].isConnected = 0; + n->e[1].isConnected = 0; + n->e[2].isConnected = 0; + n->e[0].isStrip = 0; + n->e[1].isStrip = 0; + n->e[2].isStrip = 0; + n->parent = 0; + n->visited = 0; + n->stripVisited = 0; + n->isEnd = 0; + n->stripId = -1; + n->inlist.init(); + } + } +} + +/* Find Triangle that has edge e that is not connected yet. */ +static GraphEdge +findEdge(StripMesh *sm, int32 e[2]) +{ + StripNode *n; + GraphEdge ge = { 0, 0, 0, 0 }; + for(int32 i = 0; i < sm->numNodes; i++){ + n = &sm->nodes[i]; + for(int32 j = 0; j < 3; j++){ + if(n->e[j].isConnected) + continue; + if(e[0] == n->v[j] && + e[1] == n->v[(j+1) % 3]){ + ge.node = i; + // signal success + ge.isConnected = 1; + ge.otherEdge = j; + return ge; + } + } + } + return ge; +} + +/* Connect nodes sharing an edge, preserving winding */ +static void +connectNodesPreserve(StripMesh *sm) +{ + StripNode *n, *nn; + int32 e[2]; + GraphEdge ge; + for(int32 i = 0; i < sm->numNodes; i++){ + n = &sm->nodes[i]; + for(int32 j = 0; j < 3; j++){ + if(n->e[j].isConnected) + continue; + + /* flip edge and search for node */ + e[1] = n->v[j]; + e[0] = n->v[(j+1) % 3]; + ge = findEdge(sm, e); + if(ge.isConnected){ + /* found node, now connect */ + n->e[j].node = ge.node; + n->e[j].isConnected = 1; + n->e[j].otherEdge = ge.otherEdge; + n->e[j].isStrip = 0; + nn = &sm->nodes[ge.node]; + nn->e[ge.otherEdge].node = i; + nn->e[ge.otherEdge].isConnected = 1; + nn->e[ge.otherEdge].otherEdge = j; + nn->e[ge.otherEdge].isStrip = 0; + } + } + } +} + +static int32 +numConnections(StripNode *n) +{ + return n->e[0].isConnected + + n->e[1].isConnected + + n->e[2].isConnected; +} + +static int32 +numStripEdges(StripNode *n) +{ + return n->e[0].isStrip + + n->e[1].isStrip + + n->e[2].isStrip; +} + +#define IsEnd(n) (numConnections(n) > 0 && numStripEdges(n) < 2) + +/* Complement the strip-ness of an edge */ +static void +complementEdge(StripMesh *sm, GraphEdge *e) +{ + e->isStrip = !e->isStrip; + e = &sm->nodes[e->node].e[e->otherEdge]; + e->isStrip = !e->isStrip; +} + +/* While possible extend a strip from a starting node until + * we find a node already in a strip. N.B. this function + * makes no attempts to connect to an already existing strip. + * It also doesn't try to alternate between left and right. */ +static void +extendStrip(StripMesh *sm, StripNode *start) +{ + StripNode *n, *nn; + n = start; + if(numConnections(n) == 0){ + sm->loneNodes.append(&n->inlist); + return; + } + sm->endNodes.append(&n->inlist); + n->isEnd = 1; +loop: + /* Find the next node to connect to on any of the three edges */ + for(int32 i = 0; i < 3; i++){ + if(!n->e[i].isConnected) + continue; + nn = &sm->nodes[n->e[i].node]; + if(nn->stripId >= 0) + continue; + + /* found one */ + nn->stripId = n->stripId; + /* We know it's not a strip edge yet, + * so complementing it will make it one. */ + complementEdge(sm, &n->e[i]); + n = nn; + goto loop; + } + if(n != start){ + sm->endNodes.append(&n->inlist); + n->isEnd = 1; + } +} + +static void +buildStrips(StripMesh *sm) +{ + StripNode *n; + for(int32 i = 0; i < sm->numNodes; i++){ + n = &sm->nodes[i]; + if(n->stripId >= 0) + continue; + n->stripId = i; + extendStrip(sm, n); + } +} + +static StripNode* +findTunnel(StripMesh *sm, StripNode *n) +{ + LinkList searchNodes; + StripNode *nn; + int edgetype; + int isEnd; + + searchNodes.init(); + edgetype = 0; + for(;;){ + for(int32 i = 0; i < 3; i++){ + /* Find a node connected by the right edgetype */ + if(!n->e[i].isConnected || + n->e[i].isStrip != edgetype) + continue; + nn = &sm->nodes[n->e[i].node]; + + /* If the node has been visited already, + * there's a shorter path. */ + if(nn->visited) + continue; + + /* Don't allow non-strip edge between nodes of the same + * strip to prevent loops. + * Actually these edges are allowed under certain + * circumstances, but they require complex checks. */ + if(edgetype == 0 && + n->stripId == nn->stripId) + continue; + + isEnd = IsEnd(nn); + + /* Can't add end nodes to two lists, so skip. */ + if(isEnd && edgetype == 1) + continue; + + nn->parent = n->e[i].otherEdge; + nn->visited = 1; + sm->nodes[nn->stripId].stripVisited = 1; + + /* Search complete. */ + if(isEnd && edgetype == 0) + return nn; + + /* Found a valid node. */ + searchNodes.append(&nn->inlist); + } + if(searchNodes.isEmpty()) + return nil; + n = LLLinkGetData(searchNodes.link.next, StripNode, inlist); + n->inlist.remove(); + edgetype = !edgetype; + } +} + +static void +resetGraph(StripMesh *sm) +{ + StripNode *n; + for(int32 i = 0; i < sm->numNodes; i++){ + n = &sm->nodes[i]; + n->visited = 0; + n->stripVisited = 0; + } +} + +static StripNode* +walkStrip(StripMesh *sm, StripNode *start) +{ + StripNode *n, *nn; + int32 last; + +//trace("stripend: "); +//printNode(sm, start); + + n = start; + last = -1; + for(;;n = nn){ + n->visited = 0; + n->stripVisited = 0; + if(n->isEnd) + n->inlist.remove(); + n->isEnd = 0; + + if(IsEnd(n) && n != start) + return n; + + /* find next node */ + nn = nil; + for(int32 i = 0; i < 3; i++){ + if(!n->e[i].isStrip || i == last) + continue; + nn = &sm->nodes[n->e[i].node]; + last = n->e[i].otherEdge; + nn->stripId = n->stripId; + break; + } +//trace(" next: "); +//printNode(sm, nn); + if(nn == nil) + return nil; + } +} + +static void +applyTunnel(StripMesh *sm, StripNode *end, StripNode *start) +{ + LinkList tmplist; + StripNode *n, *nn; + + for(n = end; n != start; n = &sm->nodes[n->e[n->parent].node]){ +//trace(" "); +//printNode(sm, n); + complementEdge(sm, &n->e[n->parent]); + } +//trace(" "); +//printNode(sm, start); + +//printSmesh(sm); +//trace("-------\n"); + tmplist.init(); + while(!sm->endNodes.isEmpty()){ + n = LLLinkGetData(sm->endNodes.link.next, StripNode, inlist); + /* take out of end list */ + n->inlist.remove(); + n->isEnd = 0; + /* no longer an end node */ + if(!IsEnd(n)) + continue; + // TODO: only walk strip if it was touched + /* set new id, walk strip and find other end */ + n->stripId = n - sm->nodes; + nn = walkStrip(sm, n); + tmplist.append(&n->inlist); + n->isEnd = 1; + if(nn && n != nn){ + tmplist.append(&nn->inlist); + nn->isEnd = 1; + } + } + /* Move new end nodes to the real list. */ + sm->endNodes = tmplist; + sm->endNodes.link.next->prev = &sm->endNodes.link; + sm->endNodes.link.prev->next = &sm->endNodes.link; +} + +static void +tunnel(StripMesh *sm) +{ + StripNode *n, *nn; + +again: + FORLIST(lnk, sm->endNodes){ + n = LLLinkGetData(lnk, StripNode, inlist); +// trace("searching %p %d\n", n, numStripEdges(n)); + nn = findTunnel(sm, n); +// trace(" %p %p\n", n, nn); + + if(nn){ + applyTunnel(sm, nn, n); + resetGraph(sm); + /* applyTunnel changes sm->endNodes, so we have to + * jump out of the loop. */ + goto again; + } + resetGraph(sm); + } + trace("tunneling done!\n"); +} + +/* Get next edge in strip. + * Last is the edge index whence we came lest we go back. */ +static int +getNextEdge(StripNode *n, int32 last) +{ + int32 i; + for(i = 0; i < 3; i++) + if(n->e[i].isStrip && i != last) + return i; + return -1; +} + +#define NEXT(x) (((x)+1) % 3) +#define PREV(x) (((x)+2) % 3) +#define RIGHT(x) NEXT(x) +#define LEFT(x) PREV(x) + +/* Generate mesh indices for all strips in a StripMesh */ +static void +makeMesh(StripMesh *sm, Mesh *m) +{ + int32 i, j; + int32 rightturn, lastrightturn; + int32 seam; + int32 even; + StripNode *n; + + /* three indices + two for stitch per triangle must be enough */ + m->indices = rwNewT(uint16, sm->numNodes*5, MEMDUR_FUNCTION | ID_GEOMETRY); + memset(m->indices, 0xFF, sm->numNodes*5*sizeof(uint16)); + + even = 1; + FORLIST(lnk, sm->endNodes){ + n = LLLinkGetData(lnk, StripNode, inlist); + /* only interested in start nodes, not the ends */ + if(n->stripId != (n - sm->nodes)) + continue; + + /* i is the edge we enter this triangle from. + * j is the edge we exit. */ + j = getNextEdge(n, -1); + /* starting triangle must have connection */ + if(j < 0) + continue; + /* Space to stitch together strips */ + seam = m->numIndices; + if(seam) + m->numIndices += 2; + /* Start ccw for even tris */ + if(even){ + /* Start with a right turn */ + i = LEFT(j); + m->indices[m->numIndices++] = n->v[i]; + m->indices[m->numIndices++] = n->v[NEXT(i)]; + }else{ + /* Start with a left turn */ + i = RIGHT(j); + m->indices[m->numIndices++] = n->v[NEXT(i)]; + m->indices[m->numIndices++] = n->v[i]; + } +trace("\nstart %d %d\n", numStripEdges(n), m->numIndices-2); + lastrightturn = -1; + + while(j >= 0){ + rightturn = RIGHT(i) == j; + if(rightturn == lastrightturn){ + // insert a swap if we're not alternating + m->indices[m->numIndices] = m->indices[m->numIndices-2]; +trace("SWAP\n"); + m->numIndices++; + even = !even; + } +trace("%d:%d%c %d %d %d\n", n-sm->nodes, m->numIndices, even ? ' ' : '.', n->v[0], n->v[1], n->v[2]); + lastrightturn = rightturn; + if(rightturn) + m->indices[m->numIndices++] = n->v[NEXT(j)]; + else + m->indices[m->numIndices++] = n->v[j]; + even = !even; + + /* go to next triangle */ + i = n->e[j].otherEdge; + n = &sm->nodes[n->e[j].node]; + j = getNextEdge(n, i); + } + + /* finish strip */ +trace("%d:%d%c %d %d %d\nend\n", n-sm->nodes, m->numIndices, even ? ' ' : '.', n->v[0], n->v[1], n->v[2]); + m->indices[m->numIndices++] = n->v[LEFT(i)]; + even = !even; + if(seam){ + m->indices[seam] = m->indices[seam-1]; + m->indices[seam+1] = m->indices[seam+2]; +trace("STITCH %d: %d %d\n", seam, m->indices[seam], m->indices[seam+1]); + } + } + + /* Add all unconnected and lonely triangles */ + FORLIST(lnk, sm->endNodes){ + n = LLLinkGetData(lnk, StripNode, inlist); + if(numStripEdges(n) != 0) + continue; + if(m->numIndices != 0){ + m->indices[m->numIndices] = m->indices[m->numIndices-1]; + m->numIndices++; + m->indices[m->numIndices++] = n->v[!even]; + } + m->indices[m->numIndices++] = n->v[!even]; + m->indices[m->numIndices++] = n->v[even]; + m->indices[m->numIndices++] = n->v[2]; + even = !even; + } + FORLIST(lnk, sm->loneNodes){ + n = LLLinkGetData(lnk, StripNode, inlist); + if(m->numIndices != 0){ + m->indices[m->numIndices] = m->indices[m->numIndices-1]; + m->numIndices++; + m->indices[m->numIndices++] = n->v[!even]; + } + m->indices[m->numIndices++] = n->v[!even]; + m->indices[m->numIndices++] = n->v[even]; + m->indices[m->numIndices++] = n->v[2]; + even = !even; + } +} + +static void verifyMesh(Geometry *geo); + +/* + * For each material: + * 1. build dual graph (collectFaces, connectNodes) + * 2. make some simple strip (buildStrips) + * 3. apply tunnel operator (tunnel) + */ +void +Geometry::buildTristrips(void) +{ + int32 i; + uint16 *indices; + MeshHeader *header; + Mesh *ms, *md; + StripMesh smesh; + +// trace("%ld\n", sizeof(StripNode)); + + this->allocateMeshes(matList.numMaterials, 0, 1); + + smesh.nodes = rwNewT(StripNode, this->numTriangles, MEMDUR_FUNCTION | ID_GEOMETRY); + ms = this->meshHeader->getMeshes(); + for(int32 i = 0; i < this->matList.numMaterials; i++){ + smesh.loneNodes.init(); + smesh.endNodes.init(); + collectFaces(this, &smesh, i); + connectNodesPreserve(&smesh); + buildStrips(&smesh); +printSmesh(&smesh); +//trace("-------\n"); +//printLone(&smesh); +//trace("-------\n"); +//printEnds(&smesh); +//trace("-------\n"); + // TODO: make this work +// tunnel(&smesh); +//trace("-------\n"); +//printEnds(&smesh); + + ms[i].material = this->matList.materials[i]; + makeMesh(&smesh, &ms[i]); + this->meshHeader->totalIndices += ms[i].numIndices; + } + rwFree(smesh.nodes); + + /* Now re-allocate and copy data */ + header = this->meshHeader; + this->meshHeader = nil; + this->allocateMeshes(header->numMeshes, header->totalIndices, 0); + this->meshHeader->flags = MeshHeader::TRISTRIP; + md = this->meshHeader->getMeshes(); + indices = md->indices; + for(i = 0; i < header->numMeshes; i++){ + md[i].material = ms[i].material; + md[i].numIndices = ms[i].numIndices; + md[i].indices = indices; + indices += md[i].numIndices; + memcpy(md[i].indices, ms[i].indices, md[i].numIndices*sizeof(uint16)); + rwFree(ms[i].indices); + } + rwFree(header); + + verifyMesh(this); +} + +/* Check that tristripped mesh and geometry triangles are actually the same. */ +static void +verifyMesh(Geometry *geo) +{ + int32 i, k; + uint32 j; + int32 x; + int32 a, b, c, m; + Mesh *mesh; + Triangle *t; + uint8 *seen; + + seen = rwNewT(uint8, geo->numTriangles, MEMDUR_FUNCTION | ID_GEOMETRY); + memset(seen, 0, geo->numTriangles); + + mesh = geo->meshHeader->getMeshes(); + for(i = 0; i < geo->meshHeader->numMeshes; i++){ + m = geo->matList.findIndex(mesh->material); + x = 0; + for(j = 0; j < mesh->numIndices-2; j++){ + a = mesh->indices[j+x]; + x = !x; + b = mesh->indices[j+x]; + c = mesh->indices[j+2]; + if(a >= geo->numVertices || + b >= geo->numVertices || + c >= geo->numVertices){ + fprintf(stderr, "triangle %d %d %d out of range (%d)\n", a, b, c, geo->numVertices); + goto loss; + } + if(a == b || a == c || b == c) + continue; +trace("%d %d %d\n", a, b, c); + + /* now that we have a triangle, try to find it */ + for(k = 0; k < geo->numTriangles; k++){ + t = &geo->triangles[k]; + if(seen[k] || t->matId != m) continue; + if((t->v[0] == a && t->v[1] == b && t->v[2] == c) || + (t->v[1] == a && t->v[2] == b && t->v[0] == c) || + (t->v[2] == a && t->v[0] == b && t->v[1] == c)){ + seen[k] = 1; + goto found; + } + } + goto loss; + found: ; + } + mesh++; + } + + /* Also check that all triangles are in the mesh */ + for(i = 0; i < geo->numTriangles; i++) + if(!seen[i]){ + loss: + fprintf(stderr, "TRISTRIP verify failed\n"); + exit(1); + } + + rwFree(seen); +} + +} diff --git a/vendor/librw/src/userdata.cpp b/vendor/librw/src/userdata.cpp new file mode 100644 index 0000000..e17a3d5 --- /dev/null +++ b/vendor/librw/src/userdata.cpp @@ -0,0 +1,364 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwuserdata.h" + +#define PLUGIN_ID ID_USERDATA + +namespace rw { + +UserDataGlobals userDataGlobals; + +#define udMalloc(sz) rwMalloc(sz, MEMDUR_EVENT | ID_USERDATA) + +void +UserDataArray::setString(int32 n, const char *s) +{ + int32 len; + char **sp = &((char**)this->data)[n]; + rwFree(*sp); + len = (int32)strlen(s)+1; + *sp = (char*)udMalloc(len); + if(*sp) + strncpy(*sp, s, len); +} + +static void* +createUserData(void *object, int32 offset, int32) +{ + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + ext->numArrays = 0; + ext->arrays = nil; + return object; +} + +static void* +destroyUserData(void *object, int32 offset, int32) +{ + int32 i, j; + char **strar; + UserDataArray *a; + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + a = ext->arrays; + for(i = 0; i < ext->numArrays; i++){ + rwFree(a->name); + switch(a->datatype){ + case USERDATASTRING: + strar = (char**)a->data; + for(j = 0; j < a->numElements; j++) + rwFree(strar[j]); + /* fallthrough */ + case USERDATAINT: + case USERDATAFLOAT: + rwFree(a->data); + break; + } + a++; + } + rwFree(ext->arrays); + ext->numArrays = 0; + ext->arrays = nil; + return object; +} + +static void* +copyUserData(void *dst, void *src, int32 offset, int32) +{ + int32 i, j; + char **srcstrar, **dststrar; + UserDataArray *srca, *dsta; + UserDataExtension *srcext = PLUGINOFFSET(UserDataExtension, src, offset); + UserDataExtension *dstext = PLUGINOFFSET(UserDataExtension, dst, offset); + dstext->numArrays = srcext->numArrays; + dstext->arrays = (UserDataArray*)udMalloc(dstext->numArrays*sizeof(UserDataArray)); + srca = srcext->arrays; + dsta = srcext->arrays; + for(i = 0; i < srcext->numArrays; i++){ + int32 len = (int32)strlen(srca->name)+1; + dsta->name = (char*)udMalloc(len); + strncpy(dsta->name, srca->name, len); + dsta->datatype = srca->datatype; + dsta->numElements = srca->numElements; + switch(srca->datatype){ + case USERDATAINT: + dsta->data = (int32*)udMalloc(sizeof(int32)*dsta->numElements); + memcpy(dsta->data, srca->data, sizeof(int32)*dsta->numElements); + break; + case USERDATAFLOAT: + dsta->data = (float32*)udMalloc(sizeof(float32)*dsta->numElements); + memcpy(dsta->data, srca->data, sizeof(float32)*dsta->numElements); + break; + case USERDATASTRING: + dststrar = (char**)udMalloc(sizeof(char*)*dsta->numElements); + dsta->data = dststrar; + srcstrar = (char**)srca->data; + for(j = 0; j < dsta->numElements; j++){ + len = (int32)strlen(srcstrar[j])+1; + dststrar[j] = (char*)udMalloc(len); + strncpy(dststrar[j], srcstrar[j], len); + } + break; + } + srca++; + dsta++; + } + return dst; +} + +static Stream* +readUserData(Stream *stream, int32, void *object, int32 offset, int32) +{ + int32 i, j; + char **strar; + UserDataArray *a; + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + ext->numArrays = stream->readI32(); + ext->arrays = (UserDataArray*)udMalloc(ext->numArrays*sizeof(UserDataArray)); + a = ext->arrays; + for(i = 0; i < ext->numArrays; i++){ + int32 len = stream->readI32(); + a->name = (char*)udMalloc(len); + stream->read8(a->name, len); + a->datatype = stream->readU32(); + a->numElements = stream->readI32(); + switch(a->datatype){ + case USERDATAINT: + a->data = (int32*)udMalloc(sizeof(int32)*a->numElements); + stream->read32(a->data, sizeof(int32)*a->numElements); + break; + case USERDATAFLOAT: + a->data = (float32*)udMalloc(sizeof(float32)*a->numElements); + stream->read32(a->data, sizeof(float32)*a->numElements); + break; + case USERDATASTRING: + strar = (char**)udMalloc(sizeof(char*)*a->numElements); + a->data = strar; + for(j = 0; j < a->numElements; j++){ + len = stream->readI32(); + strar[j] = (char*)udMalloc(len); + stream->read8(strar[j], len); + } + break; + } + a++; + } + return stream; +} + +static Stream* +writeUserData(Stream *stream, int32, void *object, int32 offset, int32) +{ + int32 len; + int32 i, j; + char **strar; + UserDataArray *a; + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + stream->writeI32(ext->numArrays); + a = ext->arrays; + for(i = 0; i < ext->numArrays; i++){ + len = (int32)strlen(a->name)+1; + stream->writeI32(len); + stream->write8(a->name, len); + stream->writeU32(a->datatype); + stream->writeI32(a->numElements); + switch(a->datatype){ + case USERDATAINT: + stream->write32(a->data, sizeof(int32)*a->numElements); + break; + case USERDATAFLOAT: + stream->write32(a->data, sizeof(float32)*a->numElements); + break; + case USERDATASTRING: + strar = (char**)a->data; + for(j = 0; j < a->numElements; j++){ + len = (int32)strlen(strar[j])+1; + stream->writeI32(len); + stream->write8(strar[j], len); + } + break; + } + a++; + } + return stream; +} + +static int32 +getSizeUserData(void *object, int32 offset, int32) +{ + int32 len; + int32 i, j; + char **strar; + int32 size; + UserDataArray *a; + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + if(ext->numArrays == 0) + return 0; + size = 4; // numArrays + a = ext->arrays; + for(i = 0; i < ext->numArrays; i++){ + len = (int32)strlen(a->name)+1; + size += 4 + len + 4 + 4; // name len, name, type, numElements + switch(a->datatype){ + case USERDATAINT: + size += sizeof(int32)*a->numElements; + break; + case USERDATAFLOAT: + size += sizeof(float32)*a->numElements; + break; + case USERDATASTRING: + strar = (char**)a->data; + for(j = 0; j < a->numElements; j++){ + len = (int32)strlen(strar[j])+1; + size += 4 + len; // len and string + } + break; + } + a++; + } + return size; +} + +int32 +UserDataExtension::add(const char *name, int32 datatype, int32 numElements) +{ + int32 i; + int32 len; + int32 typesz; + UserDataArray *a; + // try to find empty slot + for(i = 0; i < this->numArrays; i++) + if(this->arrays[i].datatype == USERDATANA) + goto alloc; + // have to realloc + a = (UserDataArray*)udMalloc((this->numArrays+1)*sizeof(UserDataArray)); + if(a == nil) + return -1; + memcpy(a, this->arrays, this->numArrays*sizeof(UserDataArray)); + rwFree(this->arrays); + this->arrays = a; + i = this->numArrays++; +alloc: + a = &this->arrays[i]; + len = (int32)strlen(name)+1; + a->name = (char*)udMalloc(len+1); + assert(a->name); + strncpy(a->name, name, len); + a->datatype = datatype; + a->numElements = numElements; + typesz = datatype == USERDATAINT ? sizeof(int32) : + datatype == USERDATAFLOAT ? sizeof(float32) : + datatype == USERDATASTRING ? sizeof(char*) : 0; + a->data = udMalloc(typesz*numElements); + assert(a->data); + memset(a->data, 0, typesz*numElements); + return i; +} + +void +UserDataExtension::remove(int32 n) +{ + int32 i; + UserDataArray *a = &this->arrays[n]; + if(a->name){ + rwFree(a->name); + a->name = nil; + } + if(a->datatype == USERDATASTRING) + for(i = 0; i < a->numElements; i++) + rwFree(((char**)a->data)[i]); + if(a->data){ + rwFree(a->data); + a->data = nil; + } + a->datatype = USERDATANA; + a->numElements = 0; +} + +int32 +UserDataExtension::findIndex(const char *name) { + for(int32 i = 0; i < this->numArrays; i++) + if(strcmp(this->arrays[i].name, name) == 0) + return i; + return -1; +} + +#define ACCESSOR(TYPE, NAME) \ +int32 \ +UserDataArray::NAME##Add(TYPE *t, const char *name, int32 datatype, int32 numElements) \ +{ \ + return PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->add(name, datatype, numElements); \ +} \ +void \ +UserDataArray::NAME##Remove(TYPE *t, int32 n) \ +{ \ + PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->remove(n); \ +} \ +int32 \ +UserDataArray::NAME##GetCount(TYPE *t) \ +{ \ + return PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->getCount(); \ +} \ +UserDataArray* \ +UserDataArray::NAME##Get(TYPE *t, int32 n) \ +{ \ + return PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->get(n); \ +} \ +int32 \ +UserDataArray::NAME##FindIndex(TYPE *t, const char *name) \ +{ \ + return PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->findIndex(name); \ +} + +ACCESSOR(Geometry, geometry) +ACCESSOR(Frame, frame) +ACCESSOR(Camera, camera) +ACCESSOR(Light, light) +ACCESSOR(Material, material) +ACCESSOR(Texture, texture) + +UserDataExtension *UserDataExtension::get(Geometry *geo) { return PLUGINOFFSET(UserDataExtension, geo, userDataGlobals.geometryOffset); } +UserDataExtension *UserDataExtension::get(Frame *frame) { return PLUGINOFFSET(UserDataExtension, frame, userDataGlobals.frameOffset); } +UserDataExtension *UserDataExtension::get(Camera *cam) { return PLUGINOFFSET(UserDataExtension, cam, userDataGlobals.cameraOffset); } +UserDataExtension *UserDataExtension::get(Light *light) { return PLUGINOFFSET(UserDataExtension, light, userDataGlobals.lightOffset); } +UserDataExtension *UserDataExtension::get(Material *mat) { return PLUGINOFFSET(UserDataExtension, mat, userDataGlobals.materialOffset); } +UserDataExtension *UserDataExtension::get(Texture *tex) { return PLUGINOFFSET(UserDataExtension, tex, userDataGlobals.textureOffset); } + +void +registerUserDataPlugin(void) +{ + // TODO: World Sector + + userDataGlobals.geometryOffset = Geometry::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Geometry::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.frameOffset = Frame::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Frame::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.cameraOffset = Camera::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Camera::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.lightOffset = Light::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Light::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.materialOffset = Material::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Material::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.textureOffset = Texture::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Texture::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); +} + +} diff --git a/vendor/librw/src/uvanim.cpp b/vendor/librw/src/uvanim.cpp new file mode 100644 index 0000000..f197b28 --- /dev/null +++ b/vendor/librw/src/uvanim.cpp @@ -0,0 +1,502 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" + +#define PLUGIN_ID ID_UVANIMATION + +namespace rw { + +// +// UVAnim +// + +void +UVAnimCustomData::destroy(Animation *anim) +{ + this->refCount--; + if(this->refCount <= 0) + anim->destroy(); +} + +UVAnimDictionary *currentUVAnimDictionary; + +UVAnimDictionary* +UVAnimDictionary::create(void) +{ + UVAnimDictionary *dict = (UVAnimDictionary*)rwMalloc(sizeof(UVAnimDictionary), MEMDUR_EVENT | ID_UVANIMATION); + if(dict == nil){ + RWERROR((ERR_ALLOC, sizeof(UVAnimDictionary))); + return nil; + } + dict->animations.init(); + return dict; +} + +void +UVAnimDictionary::destroy(void) +{ + FORLIST(lnk, this->animations){ + UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk); + UVAnimCustomData *cust = UVAnimCustomData::get(de->anim); + cust->destroy(de->anim); + rwFree(de); + } + rwFree(this); +} + +void +UVAnimDictionary::add(Animation *anim) +{ + UVAnimDictEntry *de = rwNewT(UVAnimDictEntry, 1, MEMDUR_EVENT | ID_UVANIMDICT); + de->anim = anim; + this->animations.append(&de->inDict); +} + +UVAnimDictionary* +UVAnimDictionary::streamRead(Stream *stream) +{ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + UVAnimDictionary *dict = UVAnimDictionary::create(); + if(dict == nil) + return nil; + int32 numAnims = stream->readI32(); + Animation *anim; + for(int32 i = 0; i < numAnims; i++){ + if(!findChunk(stream, ID_ANIMANIMATION, nil, nil)){ + RWERROR((ERR_CHUNK, "ANIMANIMATION")); + goto fail; + } + anim = Animation::streamRead(stream); + if(anim == nil) + goto fail; + dict->add(anim); + } + return dict; +fail: + dict->destroy(); + return nil; +} + +bool +UVAnimDictionary::streamWrite(Stream *stream) +{ + uint32 size = this->streamGetSize(); + writeChunkHeader(stream, ID_UVANIMDICT, size); + writeChunkHeader(stream, ID_STRUCT, 4); + int32 numAnims = this->count(); + stream->writeI32(numAnims); + FORLIST(lnk, this->animations){ + UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk); + de->anim->streamWrite(stream); + } + return true; +} + +uint32 +UVAnimDictionary::streamGetSize(void) +{ + uint32 size = 12 + 4; + FORLIST(lnk, this->animations){ + UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk); + size += 12 + de->anim->streamGetSize(); + } + return size; +} + +Animation* +UVAnimDictionary::find(const char *name) +{ + FORLIST(lnk, this->animations){ + Animation *anim = UVAnimDictEntry::fromDict(lnk)->anim; + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + if(strncmp_ci(custom->name, name, 32) == 0) + return anim; + } + return nil; +} + +static void +uvAnimStreamRead(Stream *stream, Animation *anim) +{ + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes; + stream->readI32(); + stream->read8(custom->name, 32); + stream->read32(custom->nodeToUVChannel, 8*4); + custom->refCount = 1; + + for(int32 i = 0; i < anim->numFrames; i++){ + frames[i].time = stream->readF32(); + stream->read32(frames[i].uv, 6*4); + int32 prev = stream->readI32(); + frames[i].prev = &frames[prev]; + } +} + +static void +uvAnimStreamWrite(Stream *stream, Animation *anim) +{ + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes; + stream->writeI32(0); + stream->write8(custom->name, 32); + stream->write32(custom->nodeToUVChannel, 8*4); + + for(int32 i = 0; i < anim->numFrames; i++){ + stream->writeF32(frames[i].time); + stream->write32(frames[i].uv, 6*4); + stream->writeI32(frames[i].prev - frames); + } +} + +static uint32 +uvAnimStreamGetSize(Animation *anim) +{ + return 4 + 32 + 8*4 + anim->numFrames*(4 + 6*4 + 4); +} + +static void +uvAnimLinearApplyCB(void *result, void *frame) +{ + Matrix *m = (Matrix*)result; + UVAnimInterpFrame *f = (UVAnimInterpFrame*)frame; + m->right.x = f->uv[0]; + m->right.y = f->uv[1]; + m->right.z = 0.0f; + m->up.x = f->uv[2]; + m->up.y = f->uv[3]; + m->up.z = 0.0f; + m->at.x = 0.0f; + m->at.y = 0.0f; + m->at.z = 0.0f; + m->pos.x = f->uv[4]; + m->pos.y = f->uv[5]; + m->pos.z = 0.0f; + m->update(); +} + +static void +uvAnimLinearInterpCB(void *out, void *in1, void *in2, float32 t, void *custom) +{ + UVAnimInterpFrame *intf = (UVAnimInterpFrame*)out; + UVAnimKeyFrame *kf1 = (UVAnimKeyFrame*)in1; + UVAnimKeyFrame *kf2 = (UVAnimKeyFrame*)in2; + float32 f = (t - kf1->time) / (kf2->time - kf1->time); + intf->uv[0] = (kf2->uv[0] - kf1->uv[0])*f + kf1->uv[0]; + intf->uv[1] = (kf2->uv[1] - kf1->uv[1])*f + kf1->uv[1]; + intf->uv[2] = (kf2->uv[2] - kf1->uv[2])*f + kf1->uv[2]; + intf->uv[3] = (kf2->uv[3] - kf1->uv[3])*f + kf1->uv[3]; + intf->uv[4] = (kf2->uv[4] - kf1->uv[4])*f + kf1->uv[4]; + intf->uv[5] = (kf2->uv[5] - kf1->uv[5])*f + kf1->uv[5]; +} + +static void +uvAnimParamApplyCB(void *result, void *frame) +{ + Matrix *m = (Matrix*)result; + UVAnimInterpFrame *f = (UVAnimInterpFrame*)frame; + UVAnimParamData *p = (UVAnimParamData*)f->uv; + + m->right.x = p->s0; + m->right.y = p->skew; + m->right.z = 0.0f; + m->up.x = 0.0f; + m->up.y = p->s1; + m->up.z = 0.0f; + m->at.x = 0.0f; + m->at.y = 0.0f; + m->at.z = 0.0f; + m->pos.x = p->x; + m->pos.y = p->y; + m->pos.z = 0.0f; + m->update(); + static V3d xlat1 = { -0.5f, -0.5f, 0.0f }; + static V3d xlat2 = { 0.5f, 0.5f, 0.0f }; + static V3d axis = { 0.0f, 0.0f, 1.0f }; + m->translate(&xlat1, COMBINEPOSTCONCAT); + m->rotate(&axis, p->theta*180.0f/(float)M_PI, COMBINEPOSTCONCAT); + m->translate(&xlat2, COMBINEPOSTCONCAT); +} + +static void +uvAnimParamInterpCB(void *out, void *in1, void *in2, float32 t, void *custom) +{ + UVAnimInterpFrame *intf = (UVAnimInterpFrame*)out; + UVAnimKeyFrame *kf1 = (UVAnimKeyFrame*)in1; + UVAnimKeyFrame *kf2 = (UVAnimKeyFrame*)in2; + float32 f = (t - kf1->time) / (kf2->time - kf1->time); + + float32 a = kf2->uv[0] - kf1->uv[0]; + while(a < (float)M_PI) a += 2 * (float)M_PI; + while(a > (float)M_PI) a -= 2 * (float)M_PI; + intf->uv[0] = a*f + kf1->uv[0]; + intf->uv[1] = (kf2->uv[1] - kf1->uv[1])*f + kf1->uv[1]; + intf->uv[2] = (kf2->uv[2] - kf1->uv[2])*f + kf1->uv[2]; + intf->uv[3] = (kf2->uv[3] - kf1->uv[3])*f + kf1->uv[3]; + intf->uv[4] = (kf2->uv[4] - kf1->uv[4])*f + kf1->uv[4]; + intf->uv[5] = (kf2->uv[5] - kf1->uv[5])*f + kf1->uv[5]; +} + + +static void +registerUVAnimInterpolator(void) +{ + // TODO: implement this fully + + // Linear + AnimInterpolatorInfo *info = rwNewT(AnimInterpolatorInfo, 1, MEMDUR_GLOBAL | ID_UVANIMATION); + info->id = 0x1C0; + info->interpKeyFrameSize = sizeof(UVAnimInterpFrame); + info->animKeyFrameSize = sizeof(UVAnimKeyFrame); + info->customDataSize = sizeof(UVAnimCustomData); + info->applyCB = uvAnimLinearApplyCB; + info->blendCB = nil; + info->interpCB = uvAnimLinearInterpCB; + info->addCB = nil; + info->mulRecipCB = nil; + info->streamRead = uvAnimStreamRead; + info->streamWrite = uvAnimStreamWrite; + info->streamGetSize = uvAnimStreamGetSize; + AnimInterpolatorInfo::registerInterp(info); + + // Param + info = rwNewT(AnimInterpolatorInfo, 1, MEMDUR_GLOBAL | ID_UVANIMATION); + info->id = 0x1C1; + info->interpKeyFrameSize = sizeof(UVAnimInterpFrame); + info->animKeyFrameSize = sizeof(UVAnimKeyFrame); + info->customDataSize = sizeof(UVAnimCustomData); + info->applyCB = uvAnimParamApplyCB; + info->blendCB = nil; + info->interpCB = uvAnimParamInterpCB; + info->addCB = nil; + info->mulRecipCB = nil; + info->streamRead = uvAnimStreamRead; + info->streamWrite = uvAnimStreamWrite; + info->streamGetSize = uvAnimStreamGetSize; + AnimInterpolatorInfo::registerInterp(info); +} + +int32 uvAnimOffset; + +static void* +createUVAnim(void *object, int32 offset, int32) +{ + UVAnim *uvanim; + uvanim = PLUGINOFFSET(UVAnim, object, offset); + memset(uvanim, 0, sizeof(*uvanim)); + return object; +} + +static void* +destroyUVAnim(void *object, int32 offset, int32) +{ + UVAnim *uvanim; + uvanim = PLUGINOFFSET(UVAnim, object, offset); + for(int32 i = 0; i < 8; i++){ + AnimInterpolator *ip = uvanim->interp[i]; + if(ip){ + UVAnimCustomData *custom = + UVAnimCustomData::get(ip->currentAnim); + custom->destroy(ip->currentAnim); + rwFree(ip); + } + } + return object; +} + +static void* +copyUVAnim(void *dst, void *src, int32 offset, int32) +{ + UVAnim *srcuvanim, *dstuvanim; + dstuvanim = PLUGINOFFSET(UVAnim, dst, offset); + srcuvanim = PLUGINOFFSET(UVAnim, src, offset); + for(int32 i = 0; i < 8; i++){ + AnimInterpolator *srcip = srcuvanim->interp[i]; + AnimInterpolator *dstip; + if(srcip){ + Animation *anim = srcip->currentAnim; + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + dstip = AnimInterpolator::create(anim->getNumNodes(), + anim->interpInfo->interpKeyFrameSize); + dstip->setCurrentAnim(anim); + custom->refCount++; + dstuvanim->interp[i] = dstip; + } + } + return dst; +} + +Animation* +makeDummyAnimation(const char *name) +{ + AnimInterpolatorInfo *interpInfo = AnimInterpolatorInfo::find(0x1C0); + Animation *anim = Animation::create(interpInfo, 2, 0, 1.0f); + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + strncpy(custom->name, name, 32); + memset(custom->nodeToUVChannel, 0, sizeof(custom->nodeToUVChannel)); + custom->refCount = 1; + UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes; + frames[0].time = 0.0; + frames[0].prev = nil; + frames[1].time = 1.0; + frames[1].prev = &frames[0]; + return anim; +} + +static Stream* +readUVAnim(Stream *stream, int32, void *object, int32 offset, int32) +{ + UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset); + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + char name[32]; + uint32 mask = stream->readI32(); + uint32 bit = 1; + for(int32 i = 0; i < 8; i++){ + if(mask & bit){ + stream->read8(name, 32); + Animation *anim = nil; + if(currentUVAnimDictionary) + anim = currentUVAnimDictionary->find(name); + if(anim == nil){ + anim = makeDummyAnimation(name); + if(currentUVAnimDictionary) + currentUVAnimDictionary->add(anim); + } + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + AnimInterpolator *interp; + interp = AnimInterpolator::create(anim->getNumNodes(), + anim->interpInfo->interpKeyFrameSize); + interp->setCurrentAnim(anim); + custom->refCount++; + uvanim->interp[i] = interp; + } + bit <<= 1; + } + // TEMP + if(uvanim->uv[0] == nil) + uvanim->uv[0] = Matrix::create(); + if(uvanim->uv[1] == nil) + uvanim->uv[1] = Matrix::create(); + return stream; +} + +static Stream* +writeUVAnim(Stream *stream, int32 size, void *object, int32 offset, int32) +{ + UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset); + writeChunkHeader(stream, ID_STRUCT, size-12); + uint32 mask = 0; + uint32 bit = 1; + for(int32 i = 0; i < 8; i++){ + if(uvanim->interp[i]) + mask |= bit; + bit <<= 1; + } + stream->writeI32(mask); + for(int32 i = 0; i < 8; i++){ + if(uvanim->interp[i]){ + UVAnimCustomData *custom = + UVAnimCustomData::get(uvanim->interp[i]->currentAnim); + stream->write8(custom->name, 32); + } + } + return stream; +} + +static int32 +getSizeUVAnim(void *object, int32 offset, int32) +{ + UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset); + int32 size = 0; + for(int32 i = 0; i < 8; i++) + if(uvanim->interp[i]) + size += 32; + return size ? size + 12 + 4 : 0; +} + +static void* +uvanimOpen(void *object, int32 offset, int32 size) +{ + registerUVAnimInterpolator(); + return object; +} +static void *uvanimClose(void *object, int32 offset, int32 size) { return object; } + +void +registerUVAnimPlugin(void) +{ + Engine::registerPlugin(0, ID_UVANIMATION, uvanimOpen, uvanimClose); + uvAnimOffset = Material::registerPlugin(sizeof(UVAnim), ID_UVANIMATION, + createUVAnim, destroyUVAnim, copyUVAnim); + Material::registerPluginStream(ID_UVANIMATION, + readUVAnim, writeUVAnim, getSizeUVAnim); +} + +bool32 +UVAnim::exists(Material *mat) +{ + int32 i; + UVAnim *uvanim = PLUGINOFFSET(UVAnim, mat, uvAnimOffset); + for(i = 0; i < 8; i++) + if(uvanim->interp[i]) + return 1; + return 0; +} + +void +UVAnim::addTime(Material *mat, float32 t) +{ + int32 i; + UVAnim *uvanim = PLUGINOFFSET(UVAnim, mat, uvAnimOffset); + for(i = 0; i < 8; i++) + if(uvanim->interp[i]) + uvanim->interp[i]->addTime(t); +} + +void +UVAnim::applyUpdate(Material *mat) +{ + int32 i, j; + int32 uv; + Matrix m; + UVAnim *uvanim = PLUGINOFFSET(UVAnim, mat, uvAnimOffset); + for(i = 0; i < 2; i++) + if(uvanim->uv[i]) + uvanim->uv[i]->setIdentity(); + m.setIdentity(); + + for(i = 0; i < 8; i++){ + AnimInterpolator *ip = uvanim->interp[i]; + if(ip == nil) + continue; + UVAnimCustomData *custom = UVAnimCustomData::get(ip->currentAnim); + for(j = 0; j < ip->numNodes; j++){ + InterpFrameHeader *f = ip->getInterpFrame(j); + uv = custom->nodeToUVChannel[j]; + if(uv < 2 && uvanim->uv[uv]){ + ip->applyCB(&m, f); + uvanim->uv[uv]->transform(&m, COMBINEPRECONCAT); + } + } + } + MatFX *mfx = MatFX::get(mat); + if(mfx) mfx->setUVTransformMatrices(uvanim->uv[0], uvanim->uv[1]); +} + +} diff --git a/vendor/librw/src/vgafont.inc b/vendor/librw/src/vgafont.inc new file mode 100644 index 0000000..110d824 --- /dev/null +++ b/vendor/librw/src/vgafont.inc @@ -0,0 +1,256 @@ +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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 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, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 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, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 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, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 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, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 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, 1, 1, 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, 1, 1, 1, 1, 1, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 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, 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, 1, 1, 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, 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, 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, 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, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 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, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 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, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 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, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 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, 1, 1, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 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, 1, 1, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 1, 1, 1, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 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, 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, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 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, +1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, +1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 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, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 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, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 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, 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, 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, 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, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 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, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 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, 1, 1, 1, 1, 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, +1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 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, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 1, 1, 1, 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, 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, +1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 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, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 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, 1, 1, 1, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 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, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 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, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 1, 1, 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, 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, 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, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 1, 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, 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, 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, 0, 0, 0, 0, 1, 1, 1, 1, 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, 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, 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, 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, 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, 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, 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, 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, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 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, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, +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, 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, 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, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 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, 1, 1, 1, 1, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 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, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 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, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 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, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 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, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 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, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 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, 1, 1, 1, 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, 1, 1, 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, 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, 0, 0, 0, 1, 1, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 1, 1, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 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, 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, 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, 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, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 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, 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, 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, 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, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 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, 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, 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, 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, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 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, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 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, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 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, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 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, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 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, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 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, 0, 0, 0, 1, 1, 1, 1, 1, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, +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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, +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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, +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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, +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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, diff --git a/vendor/librw/src/world.cpp b/vendor/librw/src/world.cpp new file mode 100644 index 0000000..6432b37 --- /dev/null +++ b/vendor/librw/src/world.cpp @@ -0,0 +1,198 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_WORLD + +namespace rw { + +int32 World::numAllocated = 0; + +PluginList World::s_plglist(sizeof(World)); + +World* +World::create(BBox *bbox) +{ + World *world = (World*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_WORLD); + if(world == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + world->object.init(World::ID, 0); + world->localLights.init(); + world->globalLights.init(); + world->clumps.init(); + s_plglist.construct(world); + return world; +} + +void +World::destroy(void) +{ + s_plglist.destruct(this); + rwFree(this); + numAllocated--; +} + +void +World::addLight(Light *light) +{ + assert(light->world == nil); + light->world = this; + if(light->getType() < Light::POINT){ + this->globalLights.append(&light->inWorld); + }else{ + this->localLights.append(&light->inWorld); + if(light->getFrame()) + light->getFrame()->updateObjects(); + } +} + +void +World::removeLight(Light *light) +{ + assert(light->world == this); + light->inWorld.remove(); + light->world = nil; +} + +void +World::addCamera(Camera *cam) +{ + assert(cam->world == nil); + cam->world = this; + if(cam->getFrame()) + cam->getFrame()->updateObjects(); +} + +void +World::removeCamera(Camera *cam) +{ + assert(cam->world == this); + cam->world = nil; +} + +void +World::addAtomic(Atomic *atomic) +{ + assert(atomic->world == nil); + atomic->world = this; + if(atomic->getFrame()) + atomic->getFrame()->updateObjects(); +} + +void +World::removeAtomic(Atomic *atomic) +{ + assert(atomic->world == this); + atomic->world = nil; +} + +void +World::addClump(Clump *clump) +{ + assert(clump->world == nil); + clump->world = this; + this->clumps.add(&clump->inWorld); + FORLIST(lnk, clump->atomics) + this->addAtomic(Atomic::fromClump(lnk)); + FORLIST(lnk, clump->lights) + this->addLight(Light::fromClump(lnk)); + FORLIST(lnk, clump->cameras) + this->addCamera(Camera::fromClump(lnk)); + + if(clump->getFrame()){ + clump->getFrame()->matrix.optimize(); + clump->getFrame()->updateObjects(); + } +} + +void +World::removeClump(Clump *clump) +{ + assert(clump->world == this); + clump->inWorld.remove(); + FORLIST(lnk, clump->atomics) + this->removeAtomic(Atomic::fromClump(lnk)); + FORLIST(lnk, clump->lights) + this->removeLight(Light::fromClump(lnk)); + FORLIST(lnk, clump->cameras) + this->removeCamera(Camera::fromClump(lnk)); + clump->world = nil; +} + +void +World::render(void) +{ + // this is very wrong, we really want world sectors + FORLIST(lnk, this->clumps) + Clump::fromWorld(lnk)->render(); +} + +// Find lights that illuminate an atomic +void +World::enumerateLights(Atomic *atomic, WorldLights *lightData) +{ + int32 maxDirectionals, maxLocals; + + maxDirectionals = lightData->numDirectionals; + maxLocals = lightData->numLocals; + + lightData->numDirectionals = 0; + lightData->numLocals = 0; + lightData->numAmbients = 0; + lightData->ambient.red = 0.0f; + lightData->ambient.green = 0.0f; + lightData->ambient.blue = 0.0f; + lightData->ambient.alpha = 1.0f; + + bool32 normals = atomic->geometry->flags & Geometry::NORMALS; + + FORLIST(lnk, this->globalLights){ + Light *l = Light::fromWorld(lnk); + if((l->getFlags() & Light::LIGHTATOMICS) == 0) + continue; + if(l->getType() == Light::AMBIENT){ + lightData->ambient.red += l->color.red; + lightData->ambient.green += l->color.green; + lightData->ambient.blue += l->color.blue; + lightData->numAmbients++; + }else if(normals && l->getType() == Light::DIRECTIONAL){ + if(lightData->numDirectionals < maxDirectionals) + lightData->directionals[lightData->numDirectionals++] = l; + } + } + + if(atomic->world != this) + return; + + if(!normals) + return; + + // TODO: for this we would use an atomic's world sectors, but we don't have those yet + FORLIST(lnk, this->localLights){ + if(lightData->numLocals >= maxLocals) + return; + + Light *l = Light::fromWorld(lnk); + if((l->getFlags() & Light::LIGHTATOMICS) == 0) + continue; + + // check if spheres are intersecting + Sphere *atomsphere = atomic->getWorldBoundingSphere(); + V3d dist = sub(l->getFrame()->getLTM()->pos, atomsphere->center); + if(length(dist) < atomsphere->radius + l->radius) + lightData->locals[lightData->numLocals++] = l; + } +} + +} diff --git a/vendor/librw/tools/CMakeLists.txt b/vendor/librw/tools/CMakeLists.txt new file mode 100644 index 0000000..be7ea66 --- /dev/null +++ b/vendor/librw/tools/CMakeLists.txt @@ -0,0 +1,20 @@ +if(LIBRW_TOOLS AND NOT LIBRW_PLATFORM_PS2) + add_subdirectory(dumprwtree) + add_subdirectory(ska2anm) +endif() + +if(LIBRW_EXAMPLES) + if(TARGET librw::skeleton) + add_subdirectory(imguitest) + add_subdirectory(playground) + add_subdirectory(lights) + add_subdirectory(subrast) + add_subdirectory(camera) + add_subdirectory(im2d) + add_subdirectory(im3d) + endif() + + if(LIBRW_PLATFORM_PS2) + add_subdirectory(ps2test) + endif() +endif() diff --git a/vendor/librw/tools/camera/CMakeLists.txt b/vendor/librw/tools/camera/CMakeLists.txt new file mode 100644 index 0000000..6f0dd5f --- /dev/null +++ b/vendor/librw/tools/camera/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(camera WIN32 + main.cpp + camexamp.cpp + viewer.cpp +) + +target_link_libraries(camera + PRIVATE + librw::skeleton + librw::librw +) + +add_custom_command( + TARGET camera POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory "$/files" + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/files" "$/files" +) + +librw_platform_target(camera) diff --git a/vendor/librw/tools/camera/camexamp.cpp b/vendor/librw/tools/camera/camexamp.cpp new file mode 100644 index 0000000..5f072c6 --- /dev/null +++ b/vendor/librw/tools/camera/camexamp.cpp @@ -0,0 +1,395 @@ +#include +#include + +#include "viewer.h" +#include "camexamp.h" + +#define TEXSIZE 256 + +rw::Camera *MainCamera; +rw::Camera *SubCamera; + +rw::Raster *SubCameraRaster; +rw::Raster *SubCameraZRaster; +rw::Raster *SubCameraMainCameraSubRaster; +rw::Raster *SubCameraMainCameraSubZRaster; + +TextureCamera CameraTexture; + +rw::int32 CameraSelected = 0; +rw::int32 ProjectionIndex = 0; +bool SubCameraMiniView = true; + +CameraData SubCameraData; + +void +CameraQueryData(CameraData *data, CameraDataType type, rw::Camera *camera) +{ + data->camera = camera; + if(type & FARCLIPPLANE) data->farClipPlane = camera->farPlane; + if(type & NEARCLIPPLANE) data->nearClipPlane = camera->nearPlane; + if(type & PROJECTION) data->projection = camera->projection; + if(type & OFFSET) data->offset = camera->viewOffset; + if(type & VIEWWINDOW) data->viewWindow = camera->viewWindow; + if(type & MATRIX) data->matrix = &camera->getFrame()->matrix; +} + +void +CameraSetData(CameraData *data, CameraDataType type) +{ + if(type & FARCLIPPLANE) data->camera->setFarPlane(data->farClipPlane); + if(type & NEARCLIPPLANE) data->camera->setNearPlane(data->nearClipPlane); + if(type & PROJECTION) data->camera->setProjection(data->projection); + if(type & OFFSET) data->camera->setViewOffset(&data->offset); + if(type & VIEWWINDOW) data->camera->setViewWindow(&data->viewWindow); +} + +void +ProjectionCallback(void) +{ + if(ProjectionIndex == 0) + SubCameraData.projection = rw::Camera::PERSPECTIVE; + else + SubCameraData.projection = rw::Camera::PARALLEL; + CameraSetData(&SubCameraData, PROJECTION); +} + +void +ClipPlaneCallback(void) +{ + CameraSetData(&SubCameraData, (CameraDataType)(NEARCLIPPLANE | FARCLIPPLANE)); +} + +void +ChangeViewOffset(float deltaX, float deltaY) +{ + SubCameraData.offset.x += deltaX; + SubCameraData.offset.y += deltaY; + if(SubCameraData.offset.x > 5.0f) + SubCameraData.offset.x = 5.0f; + if(SubCameraData.offset.x < -5.0f) + SubCameraData.offset.x = -5.0f; + if(SubCameraData.offset.y > 5.0f) + SubCameraData.offset.y = 5.0f; + if(SubCameraData.offset.y < -5.0f) + SubCameraData.offset.y = -5.0f; + CameraSetData(&SubCameraData, OFFSET); +} + +void +ChangeViewWindow(float deltaX, float deltaY) +{ + SubCameraData.viewWindow.x += deltaX; + SubCameraData.viewWindow.y += deltaY; + if(SubCameraData.viewWindow.x > 5.0f) + SubCameraData.viewWindow.x = 5.0f; + if(SubCameraData.viewWindow.x < 0.01f) + SubCameraData.viewWindow.x = 0.01f; + if(SubCameraData.viewWindow.y > 5.0f) + SubCameraData.viewWindow.y = 5.0f; + if(SubCameraData.viewWindow.y < 0.01f) + SubCameraData.viewWindow.y = 0.01f; + CameraSetData(&SubCameraData, VIEWWINDOW); +} + +void +CamerasCreate(rw::World *world) +{ + rw::V3d offset = { 3.0f, 0.0f, 8.0f }; + float rotate = -90.0f; + + SubCamera = ViewerCreate(world); + ViewerMove(SubCamera, &offset); + ViewerRotate(SubCamera, rotate, 0.0f); + + MainCamera = ViewerCreate(world); + + CameraQueryData(&SubCameraData, ALL, SubCamera); + + SubCameraData.nearClipPlane = 0.3f; + CameraSetData(&SubCameraData, NEARCLIPPLANE); + + SubCameraData.farClipPlane = 5.0f; + CameraSetData(&SubCameraData, FARCLIPPLANE); + + CameraTexture.camera = SubCamera; + CameraTextureInit(&CameraTexture); + + SubCameraData.cameraTexture = &CameraTexture; + + + SubCameraMainCameraSubRaster = rw::Raster::create(0, 0, 0, rw::Raster::CAMERA); + SubCameraMainCameraSubZRaster = rw::Raster::create(0, 0, 0, rw::Raster::ZBUFFER); +} + +void +CamerasDestroy(rw::World *world) +{ + SubCameraMiniViewSelect(false); + + if(MainCamera){ + ViewerDestroy(MainCamera, world); + MainCamera = nil; + } + + if(SubCamera){ + ViewerDestroy(SubCamera, world); + SubCamera = nil; + } + + CameraTextureTerm(&CameraTexture); + + if(SubCameraMainCameraSubRaster){ + SubCameraMainCameraSubRaster->destroy(); + SubCameraMainCameraSubRaster = nil; + } + + if(SubCameraMainCameraSubZRaster){ + SubCameraMainCameraSubZRaster->destroy(); + SubCameraMainCameraSubZRaster = nil; + } +} + +void +UpdateSubRaster(rw::Camera *camera, rw::Rect *rect) +{ + rw::Rect subRect; + + subRect.x = rect->w * 0.75f; + subRect.y = 0; + + subRect.w = rect->w * 0.25f; + subRect.h = rect->h * 0.25f; + + SubCameraMainCameraSubRaster->subRaster(camera->frameBuffer, &subRect); + SubCameraMainCameraSubZRaster->subRaster(camera->zBuffer, &subRect); +} + +void +CameraSizeUpdate(rw::Rect *rect, float viewWindow, float aspectRatio) +{ + static bool RasterInit; + + if(MainCamera == nil) + return; + + sk::CameraSize(MainCamera, rect, viewWindow, aspectRatio); + + UpdateSubRaster(MainCamera, rect); + + if(RasterInit) + SubCameraMiniViewSelect(false); + + sk::CameraSize(SubCamera, rect, viewWindow, aspectRatio); + + SubCameraRaster = SubCamera->frameBuffer; + SubCameraZRaster = SubCamera->zBuffer; + + RasterInit = true; + SubCameraMiniViewSelect(CameraSelected == 0); + + CameraQueryData(&SubCameraData, VIEWWINDOW, SubCamera); +} + +void +RenderSubCamera(rw::RGBA *backgroundColor, rw::int32 clearMode, rw::World *world) +{ + SubCamera->clear(backgroundColor, clearMode); + SubCamera->beginUpdate(); + world->render(); + SubCamera->endUpdate(); +} + +void +RenderTextureCamera(rw::RGBA *foregroundColor, rw::int32 clearMode, rw::World *world) +{ + rw::Raster *saveRaster, *saveZRaster; + + saveRaster = CameraTexture.camera->frameBuffer; + saveZRaster = CameraTexture.camera->zBuffer; + CameraTexture.camera->frameBuffer = CameraTexture.raster; + CameraTexture.camera->zBuffer = CameraTexture.zRaster; + + CameraTexture.camera->clear(foregroundColor, clearMode); + CameraTexture.camera->beginUpdate(); + world->render(); + CameraTexture.camera->endUpdate(); + + + CameraTexture.camera->frameBuffer = saveRaster; + CameraTexture.camera->zBuffer = saveZRaster; +} + +void +SubCameraMiniViewSelect(bool select) +{ + if(select){ + SubCamera->frameBuffer = SubCameraMainCameraSubRaster; + SubCamera->zBuffer = SubCameraMainCameraSubZRaster; + }else{ + SubCamera->frameBuffer = SubCameraRaster; + SubCamera->zBuffer = SubCameraZRaster; + } +} + + + +void +CameraTextureInit(TextureCamera *ct) +{ + ct->raster = rw::Raster::create(TEXSIZE, TEXSIZE, 0, rw::Raster::CAMERATEXTURE); + assert(ct->raster); + ct->zRaster = rw::Raster::create(TEXSIZE, TEXSIZE, 0, rw::Raster::ZBUFFER); + assert(ct->zRaster); + + ct->texture = rw::Texture::create(ct->raster); + ct->texture->setFilter(rw::Texture::FilterMode::LINEAR); +} + +void +CameraTextureTerm(TextureCamera *ct) +{ + if(ct->raster){ + ct->raster->destroy(); + ct->raster = nil; + } + + if(ct->zRaster){ + ct->zRaster->destroy(); + ct->zRaster = nil; + } + + if(ct->texture){ + ct->texture->raster = nil; + ct->texture->destroy(); + ct->texture = nil; + } +} + + + +void +DrawCameraFrustum(CameraData *c) +{ + rw::RGBA yellow = { 255, 255, 0, 64 }; + rw::RGBA red = { 255, 0, 0, 255 }; + rw::RWDEVICE::Im3DVertex frustum[13]; + // lines + rw::uint16 indicesL[] = { + 1, 2, 2, 3, 3, 4, 4, 1, + 5, 6, 6, 7, 7, 8, 8, 5, + 9, 10, 10, 11, 11, 12, 12, 9, + 5, 9, 6, 10, 7, 11, 8, 12, + 0, 0 + }; + // triangles + rw::uint16 indicesT[] = { + 5, 6, 10, + 10, 9, 5, + 6, 7, 11, + 11, 10, 6, + 7, 8, 12, + 12, 11, 7, + 8, 5, 9, + 9, 12, 8, + + 7, 6, 5, + 5, 8, 7, + 9, 10, 11, + 11, 12, 9 + }; + float signs[4][2] = { + { 1, 1 }, + { -1, 1 }, + { -1, -1 }, + { 1, -1 } + }; + + float depth[3]; + depth[0] = 1.0f; // view window + depth[1] = c->nearClipPlane; + depth[2] = c->farClipPlane; + + int k = 0; + frustum[k].setX(c->offset.x); + frustum[k].setY(c->offset.y); + frustum[k].setZ(0.0f); + k++; + + for(int i = 0; i < 3; i++) // depths + for(int j = 0; j < 4; j++){ // planes + if(c->projection == rw::Camera::PERSPECTIVE){ + frustum[k].setX(-c->offset.x + depth[i]*(signs[j][0]*c->viewWindow.x + c->offset.x)); + frustum[k].setY(c->offset.y + depth[i]*(signs[j][1]*c->viewWindow.y - c->offset.y)); + frustum[k].setZ(depth[i]); + }else{ + frustum[k].setX(-c->offset.x + signs[j][0]*c->viewWindow.x + depth[i]*c->offset.x); + frustum[k].setY(c->offset.y + signs[j][1]*c->viewWindow.y - depth[i]*c->offset.y); + frustum[k].setZ(depth[i]); + } + k++; + } + + for(int i = 0; i < 5; i++) + frustum[i].setColor(red.red, red.green, red.blue, red.alpha); + for(int i = 5; i < 13; i++) + frustum[i].setColor(yellow.red, yellow.green, yellow.blue, 255); + + rw::SetRenderStatePtr(rw::TEXTURERASTER, nil); + + rw::im3d::Transform(frustum, 13, c->camera->getFrame()->getLTM(), rw::im3d::ALLOPAQUE); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPELINELIST, indicesL, 34); + rw::im3d::End(); + + for(int i = 5; i < 13; i++) + frustum[i].setColor(yellow.red, yellow.green, yellow.blue, yellow.alpha); + + rw::SetRenderState(rw::VERTEXALPHA, 1); + + rw::im3d::Transform(frustum, 13, c->camera->getFrame()->getLTM(), rw::im3d::ALLOPAQUE); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indicesT, 36); + rw::im3d::End(); +} + +void +DrawCameraViewplaneTexture(CameraData *c) +{ + rw::RGBA white = { 255, 255, 255, 255 }; + rw::RWDEVICE::Im3DVertex frustum[4]; + rw::uint16 indicesV[] = { + 2, 1, 0, + 0, 3, 2, + 0, 1, 2, + 2, 3, 0 + }; + float uvValues[4][2] = { + { 0.0f, 0.0f }, + { 1.0f, 0.0f }, + { 1.0f, 1.0f }, + { 0.0f, 1.0f } + }; + float signs[4][2] = { + { 1, 1 }, + { -1, 1 }, + { -1, -1 }, + { 1, -1 } + }; + + for(int j = 0; j < 4; j++){ + frustum[j].setX(signs[j][0]*c->viewWindow.x); + frustum[j].setY(signs[j][1]*c->viewWindow.y); + frustum[j].setZ(1.0f); + } + for(int i = 0; i < 4; i++){ + frustum[i].setColor(white.red, white.green, white.blue, white.alpha); + frustum[i].setU(uvValues[i][0]); + frustum[i].setV(uvValues[i][1]); + } + + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::SetRenderStatePtr(rw::TEXTURERASTER, c->cameraTexture->texture->raster); + + rw::im3d::Transform(frustum, 4, c->camera->getFrame()->getLTM(), rw::im3d::VERTEXUV); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indicesV, 12); + rw::im3d::End(); +} diff --git a/vendor/librw/tools/camera/camexamp.h b/vendor/librw/tools/camera/camexamp.h new file mode 100644 index 0000000..7df301f --- /dev/null +++ b/vendor/librw/tools/camera/camexamp.h @@ -0,0 +1,62 @@ +struct TextureCamera +{ + rw::Raster *raster; + rw::Raster *zRaster; + rw::Camera *camera; + rw::Texture *texture; +}; + +struct CameraData +{ + float farClipPlane; + float nearClipPlane; + rw::uint32 projection; + rw::V2d offset; + rw::V2d viewWindow; + rw::Camera *camera; + TextureCamera *cameraTexture; + rw::Matrix *matrix; +}; + +enum CameraDataType +{ + NONE = 0x00, + FARCLIPPLANE = 0x01, + NEARCLIPPLANE = 0x02, + PROJECTION = 0x04, + OFFSET = 0x08, + VIEWWINDOW = 0x10, + MATRIX = 0x20, + ALL = 0xFF +}; + +extern rw::Camera *MainCamera; +extern rw::Camera *SubCamera; + +extern rw::int32 CameraSelected; +extern rw::int32 ProjectionIndex; + +extern CameraData SubCameraData; + +void CameraQueryData(CameraData *data, CameraDataType type, rw::Camera *camera); +void CameraSetData(CameraData *data, CameraDataType type); + +void ChangeViewOffset(float deltaX, float deltaY); +void ChangeViewWindow(float deltaX, float deltaY); +void ProjectionCallback(void); +void ClipPlaneCallback(void); + +void CamerasCreate(rw::World *world); +void CamerasDestroy(rw::World *world); +void CameraSizeUpdate(rw::Rect *rect, float viewWindow, float aspectRatio); +void RenderSubCamera(rw::RGBA *foregroundColor, rw::int32 clearMode, rw::World *world); +void RenderTextureCamera(rw::RGBA *foregroundColor, rw::int32 clearMode, rw::World *world); +void SubCameraMiniViewSelect(bool select); + +void CameraTextureInit(TextureCamera *ct); +void CameraTextureTerm(TextureCamera *ct); +void DrawCameraFrustum(CameraData *c); +void DrawCameraViewplaneTexture(CameraData *c); + +void ViewerRotate(rw::Camera *camera, float deltaX, float deltaY); +void ViewerTranslate(rw::Camera *camera, float deltaX, float deltaY); diff --git a/vendor/librw/tools/camera/files/clump.dff b/vendor/librw/tools/camera/files/clump.dff new file mode 100644 index 0000000..960354f Binary files /dev/null and b/vendor/librw/tools/camera/files/clump.dff differ diff --git a/vendor/librw/tools/camera/files/clump/shinarm.png b/vendor/librw/tools/camera/files/clump/shinarm.png new file mode 100644 index 0000000..1dbc402 Binary files /dev/null and b/vendor/librw/tools/camera/files/clump/shinarm.png differ diff --git a/vendor/librw/tools/camera/files/clump/shinbody.png b/vendor/librw/tools/camera/files/clump/shinbody.png new file mode 100644 index 0000000..645b198 Binary files /dev/null and b/vendor/librw/tools/camera/files/clump/shinbody.png differ diff --git a/vendor/librw/tools/camera/files/clump/shinface.png b/vendor/librw/tools/camera/files/clump/shinface.png new file mode 100644 index 0000000..014ca32 Binary files /dev/null and b/vendor/librw/tools/camera/files/clump/shinface.png differ diff --git a/vendor/librw/tools/camera/files/clump/shinleg.png b/vendor/librw/tools/camera/files/clump/shinleg.png new file mode 100644 index 0000000..e56c1c3 Binary files /dev/null and b/vendor/librw/tools/camera/files/clump/shinleg.png differ diff --git a/vendor/librw/tools/camera/main.cpp b/vendor/librw/tools/camera/main.cpp new file mode 100644 index 0000000..b50b40e --- /dev/null +++ b/vendor/librw/tools/camera/main.cpp @@ -0,0 +1,508 @@ +#include +#include +#include + +#include "viewer.h" +#include "camexamp.h" + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +rw::EngineOpenParams engineOpenParams; +float FOV = 70.0f; + +rw::RGBA ForegroundColor = { 200, 200, 200, 255 }; +rw::RGBA BackgroundColor = { 64, 64, 64, 0 }; +rw::RGBA BackgroundColorSub = { 74, 74, 74, 0 }; + +rw::World *World; +rw::Charset *Charset; + +rw::V3d Xaxis = { 1.0f, 0.0, 0.0f }; +rw::V3d Yaxis = { 0.0f, 1.0, 0.0f }; +rw::V3d Zaxis = { 0.0f, 0.0, 1.0f }; + +float TimeDelta; + +rw::Clump *Clump; + +rw::World* +CreateWorld(void) +{ + rw::BBox bb; + + bb.inf.x = bb.inf.y = bb.inf.z = -100.0f; + bb.sup.x = bb.sup.y = bb.sup.z = 100.0f; + + return rw::World::create(&bb); +} + +void +LightsCreate(rw::World *world) +{ + rw::Light *light = rw::Light::create(rw::Light::AMBIENT); + assert(light); + World->addLight(light); + + light = rw::Light::create(rw::Light::DIRECTIONAL); + assert(light); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 30.0f, rw::COMBINEREPLACE); + frame->rotate(&Yaxis, 30.0f, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + World->addLight(light); +} + +rw::Clump* +ClumpCreate(rw::World *world) +{ + rw::Clump *clump; + rw::StreamFile in; + + rw::Image::setSearchPath("files/clump/"); + const char *filename = "files/clump.dff"; + if(in.open(filename, "rb") == NULL){ + printf("couldn't open file\n"); + return nil; + } + if(!rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL)) + return nil; + clump = rw::Clump::streamRead(&in); + in.close(); + if(clump == nil) + return nil; + + rw::Frame *frame = clump->getFrame(); + rw::V3d pos = { 0.0f, 0.0f, 8.0f }; + frame->translate(&pos, rw::COMBINEREPLACE); + World->addClump(clump); + return clump; +} + +void +ClumpRotate(rw::Clump *clump, rw::Camera *camera, float xAngle, float yAngle) +{ + rw::Matrix *cameraMatrix = &camera->getFrame()->matrix; + rw::Frame *clumpFrame = clump->getFrame(); + rw::V3d pos = clumpFrame->matrix.pos; + + pos = rw::scale(pos, -1.0f); + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + clumpFrame->rotate(&cameraMatrix->up, xAngle, rw::COMBINEPOSTCONCAT); + clumpFrame->rotate(&cameraMatrix->right, yAngle, rw::COMBINEPOSTCONCAT); + + pos = rw::scale(pos, -1.0f); + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); +} + +void +ClumpTranslate(rw::Clump *clump, rw::Camera *camera, float xDelta, float yDelta) +{ + rw::Matrix *cameraMatrix = &camera->getFrame()->matrix; + rw::Frame *clumpFrame = clump->getFrame(); + + rw::V3d deltaX = rw::scale(cameraMatrix->right, xDelta); + rw::V3d deltaZ = rw::scale(cameraMatrix->at, yDelta); + rw::V3d delta = rw::add(deltaX, deltaZ); + + clumpFrame->translate(&delta, rw::COMBINEPOSTCONCAT); +} + +void +ClumpSetPosition(rw::Clump *clump, rw::V3d *position) +{ + clump->getFrame()->translate(position, rw::COMBINEREPLACE); +} + +void +Initialize(void) +{ + sk::globals.windowtitle = "Camera example"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +Initialize3D(void) +{ + if(!sk::InitRW()) + return false; + + Charset = rw::Charset::create(&ForegroundColor, &BackgroundColor); + + World = CreateWorld(); + + CamerasCreate(World); + LightsCreate(World); + + Clump = ClumpCreate(World); + + rw::SetRenderState(rw::CULLMODE, rw::CULLBACK); + rw::SetRenderState(rw::ZTESTENABLE, 1); + rw::SetRenderState(rw::ZWRITEENABLE, 1); + + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); + + rw::Rect r; + r.x = 0; + r.y = 0; + r.w = sk::globals.width; + r.h = sk::globals.height; + CameraSizeUpdate(&r, 0.5f, 4.0f/3.0f); + + return true; +} + +void +DestroyLight(rw::Light *light, rw::World *world) +{ + world->removeLight(light); + rw::Frame *frame = light->getFrame(); + if(frame){ + light->setFrame(nil); + frame->destroy(); + } + light->destroy(); +} + +void +Terminate3D(void) +{ + if(Clump){ + World->removeClump(Clump); + Clump->destroy(); + Clump = nil; + } + + FORLIST(lnk, World->globalLights){ + rw::Light *light = rw::Light::fromWorld(lnk); + DestroyLight(light, World); + } + FORLIST(lnk, World->localLights){ + rw::Light *light = rw::Light::fromWorld(lnk); + DestroyLight(light, World); + } + + CamerasDestroy(World); + + if(World){ + World->destroy(); + World = nil; + } + + if(Charset){ + Charset->destroy(); + Charset = nil; + } + + sk::TerminateRW(); +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +void +DisplayOnScreenInfo(void) +{ + char str[256]; + sprintf(str, "View window (%.2f, %.2f)", SubCameraData.viewWindow.x, SubCameraData.viewWindow.y); + Charset->print(str, 100, 100, 0); + sprintf(str, "View offset (%.2f, %.2f)", SubCameraData.offset.x, SubCameraData.offset.y); + Charset->print(str, 100, 120, 0); +} + +void +ResetCameraAndClump(void) +{ + SubCameraData.nearClipPlane = 0.3f; + SubCameraData.farClipPlane = 5.0f; + SubCameraData.projection = rw::Camera::PERSPECTIVE; + SubCameraData.offset.x = 0.0f; + SubCameraData.offset.y = 0.0f; + SubCameraData.viewWindow.x = 0.5f; + SubCameraData.viewWindow.y = 0.38f; + CameraSetData(&SubCameraData, ALL); + ProjectionIndex = 0; + + rw::V3d position = { 3.0f, 0.0f, 8.0f }; + rw::V3d point = { 0.0f, 0.0f, 8.0f }; + ViewerSetPosition(SubCameraData.camera, &position); + ViewerRotate(SubCamera, -90.0f, 0.0f); + + ClumpSetPosition(Clump, &point); +} + +void +Gui(void) +{ + static bool showCameraWindow = true; + ImGui::Begin("Camera", &showCameraWindow); + + ImGui::RadioButton("Main camera", &CameraSelected, 0); + ImGui::RadioButton("Sub camera", &CameraSelected, 1); + + if(ImGui::RadioButton("Perspective", &ProjectionIndex, 0)) + ProjectionCallback(); + if(ImGui::RadioButton("Parallel", &ProjectionIndex, 1)) + ProjectionCallback(); + + if(ImGui::SliderFloat("Near clip-plane", &SubCameraData.nearClipPlane, 0.1f, SubCameraData.farClipPlane-0.1f)) + ClipPlaneCallback(); + if(ImGui::SliderFloat("Far clip-plane", &SubCameraData.farClipPlane, SubCameraData.nearClipPlane+0.1f, 20.0f)) + ClipPlaneCallback(); + + if(ImGui::Button("Reset")) + ResetCameraAndClump(); + + ImGui::End(); +} + +void +MainCameraRender(rw::Camera *camera) +{ + RenderTextureCamera(&BackgroundColorSub, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ, World); + + camera->clear(&BackgroundColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + + camera->beginUpdate(); + + ImGui_ImplRW_NewFrame(TimeDelta); + + World->render(); + + DrawCameraViewplaneTexture(&SubCameraData); + DrawCameraFrustum(&SubCameraData); + + DisplayOnScreenInfo(); + + Gui(); + ImGui::EndFrame(); + ImGui::Render(); + + ImGui_ImplRW_RenderDrawLists(ImGui::GetDrawData()); + + camera->endUpdate(); + + + RenderSubCamera(&BackgroundColorSub, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ, World); +} + +void +SubCameraRender(rw::Camera *camera) +{ + camera->clear(&BackgroundColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + + camera->beginUpdate(); + + ImGui_ImplRW_NewFrame(TimeDelta); + + World->render(); + + DisplayOnScreenInfo(); + + Gui(); + ImGui::EndFrame(); + ImGui::Render(); + + ImGui_ImplRW_RenderDrawLists(ImGui::GetDrawData()); + + camera->endUpdate(); +} + +void +Render(void) +{ + rw::Camera *camera; + + SubCameraMiniViewSelect(CameraSelected == 0); + + switch(CameraSelected){ + default: + case 0: + camera = MainCamera; + MainCameraRender(camera); + break; + case 1: + camera = SubCamera; + SubCameraRender(camera); + break; + } + camera->showRaster(0); +} + +void +Idle(float timeDelta) +{ + TimeDelta = timeDelta; + Render(); +} + +int MouseX, MouseY; +int MouseDeltaX, MouseDeltaY; +int MouseButtons; + +bool RotateClump; +bool TranslateClump; +bool RotateCamera; +bool TranslateCamera; +bool ViewXWindow; +bool ViewYWindow; +bool ViewXOffset; +bool ViewYOffset; + +bool Ctrl, Alt, Shift; + +void +KeyUp(int key) +{ + switch(key){ + case sk::KEY_LCTRL: + case sk::KEY_RCTRL: + Ctrl = false; + break; + case sk::KEY_LALT: + case sk::KEY_RALT: + Alt = false; + break; + case sk::KEY_LSHIFT: + case sk::KEY_RSHIFT: + Shift = false; + break; + } +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_LCTRL: + case sk::KEY_RCTRL: + Ctrl = true; + break; + case sk::KEY_LALT: + case sk::KEY_RALT: + Alt = true; + break; + case sk::KEY_LSHIFT: + case sk::KEY_RSHIFT: + Shift = true; + break; + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +void +MouseBtn(sk::MouseState *mouse) +{ + MouseButtons = mouse->buttons; + RotateClump = !Ctrl && !Alt && !Shift && !!(MouseButtons&1); + TranslateClump = !Ctrl && !Alt && !Shift && !!(MouseButtons&4); + RotateCamera = Ctrl && !!(MouseButtons&1); + TranslateCamera = Ctrl && !!(MouseButtons&4); + ViewXWindow = Shift && !!(MouseButtons&1); + ViewYWindow = Shift && !!(MouseButtons&4); + ViewXOffset = Alt && !!(MouseButtons&1); + ViewYOffset = Alt && !!(MouseButtons&4); +} + +void +MouseMove(sk::MouseState *mouse) +{ + MouseDeltaX = mouse->posx - MouseX; + MouseDeltaY = mouse->posy - MouseY; + MouseX = mouse->posx; + MouseY = mouse->posy; + + if(RotateClump) + ClumpRotate(Clump, MainCamera, MouseDeltaX, -MouseDeltaY); + if(TranslateClump) + ClumpTranslate(Clump, MainCamera, -MouseDeltaX*0.01f, -MouseDeltaY*0.1f); + if(RotateCamera) + ViewerRotate(SubCamera, -MouseDeltaX*0.1f, MouseDeltaY*0.1f); + if(TranslateCamera) + ViewerTranslate(SubCamera, -MouseDeltaX*0.01f, -MouseDeltaY*0.01f); + if(ViewXWindow) + ChangeViewWindow(-MouseDeltaY*0.01f, 0.0f); + if(ViewYWindow) + ChangeViewWindow(0.0f, -MouseDeltaY*0.01f); + if(ViewXOffset) + ChangeViewOffset(-MouseDeltaY*0.01f, 0.0f); + if(ViewYOffset) + ChangeViewOffset(0.0f, -MouseDeltaY*0.01f); +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + MouseState *ms; + + ImGuiEventHandler(e, param); + ImGuiIO &io = ImGui::GetIO(); + + switch(e){ + case INITIALIZE: + Initialize(); + return EVENTPROCESSED; + case RWINITIALIZE: + return Initialize3D() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + Terminate3D(); + return EVENTPROCESSED; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case MOUSEBTN: + if(!io.WantCaptureMouse){ + ms = (MouseState*)param; + MouseBtn(ms); + }else + MouseButtons = 0; + return EVENTPROCESSED; + case MOUSEMOVE: + MouseMove((MouseState*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + + CameraSizeUpdate(r, 0.5f, 4.0f/3.0f); + break; + case IDLE: + Idle(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/vendor/librw/tools/camera/viewer.cpp b/vendor/librw/tools/camera/viewer.cpp new file mode 100644 index 0000000..b9732d3 --- /dev/null +++ b/vendor/librw/tools/camera/viewer.cpp @@ -0,0 +1,51 @@ +#include +#include + +rw::Camera* +ViewerCreate(rw::World *world) +{ + rw::Camera *camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + assert(camera); + camera->setNearPlane(0.1f); + camera->setFarPlane(500.0f); + world->addCamera(camera); + return camera; +} + +void +ViewerDestroy(rw::Camera *camera, rw::World *world) +{ + if(camera && world){ + world->removeCamera(camera); + sk::CameraDestroy(camera); + } +} + +void +ViewerMove(rw::Camera *camera, rw::V3d *offset) +{ + sk::CameraMove(camera, offset); +} + +void +ViewerRotate(rw::Camera *camera, float deltaX, float deltaY) +{ + sk::CameraTilt(camera, nil, deltaY); + sk::CameraPan(camera, nil, deltaX); +} + +void +ViewerTranslate(rw::Camera *camera, float deltaX, float deltaY) +{ + rw::V3d offset; + offset.x = deltaX; + offset.y = deltaY; + offset.z = 0.0f; + sk::CameraMove(camera, &offset); +} + +void +ViewerSetPosition(rw::Camera *camera, rw::V3d *position) +{ + camera->getFrame()->translate(position, rw::COMBINEREPLACE); +} diff --git a/vendor/librw/tools/camera/viewer.h b/vendor/librw/tools/camera/viewer.h new file mode 100644 index 0000000..acd5d79 --- /dev/null +++ b/vendor/librw/tools/camera/viewer.h @@ -0,0 +1,6 @@ +rw::Camera *ViewerCreate(rw::World *world); +void ViewerDestroy(rw::Camera *camera, rw::World *world); +void ViewerMove(rw::Camera *camera, rw::V3d *offset); +void ViewerRotate(rw::Camera *camera, float deltaX, float deltaY); +void ViewerTranslate(rw::Camera *camera, float deltaX, float deltaY); +void ViewerSetPosition(rw::Camera *camera, rw::V3d *position); diff --git a/vendor/librw/tools/dumprwtree/CMakeLists.txt b/vendor/librw/tools/dumprwtree/CMakeLists.txt new file mode 100644 index 0000000..fdd0a23 --- /dev/null +++ b/vendor/librw/tools/dumprwtree/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable(dumprwtree + dumprwtree.cpp +) + +target_link_libraries(dumprwtree + PRIVATE + librw::librw +) + +if(LIBRW_INSTALL) + install(TARGETS dumprwtree + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) +endif() + +librw_platform_target(dumprwtree INSTALL) diff --git a/vendor/librw/tools/dumprwtree/dumprwtree.cpp b/vendor/librw/tools/dumprwtree/dumprwtree.cpp new file mode 100644 index 0000000..ca86d3c --- /dev/null +++ b/vendor/librw/tools/dumprwtree/dumprwtree.cpp @@ -0,0 +1,145 @@ +#include +#include +#include +#include + +#include + +using namespace std; +using namespace rw; + +const char *chunks[] = { "None", "Struct", "String", "Extension", "Unknown", + "Camera", "Texture", "Material", "Material List", "Atomic Section", + "Plane Section", "World", "Spline", "Matrix", "Frame List", + "Geometry", "Clump", "Unknown", "Light", "Unicode String", "Atomic", + "Texture Native", "Texture Dictionary", "Animation Database", + "Image", "Skin Animation", "Geometry List", "Anim Animation", + "Team", "Crowd", "Delta Morph Animation", "Right To Render", + "MultiTexture Effect Native", "MultiTexture Effect Dictionary", + "Team Dictionary", "Platform Independet Texture Dictionary", + "Table of Contents", "Particle Standard Global Data", "AltPipe", + "Platform Independet Peds", "Patch Mesh", "Chunk Group Start", + "Chunk Group End", "UV Animation Dictionary", "Coll Tree" +}; + +/* From 0x0101 through 0x0135 */ +const char *toolkitchunks0[] = { "Metrics PLG", "Spline PLG", "Stereo PLG", + "VRML PLG", "Morph PLG", "PVS PLG", "Memory Leak PLG", "Animation PLG", + "Gloss PLG", "Logo PLG", "Memory Info PLG", "Random PLG", + "PNG Image PLG", "Bone PLG", "VRML Anim PLG", "Sky Mipmap Val", + "MRM PLG", "LOD Atomic PLG", "ME PLG", "Lightmap PLG", + "Refine PLG", "Skin PLG", "Label PLG", "Particles PLG", "GeomTX PLG", + "Synth Core PLG", "STQPP PLG", + "Part PP PLG", "Collision PLG", "HAnim PLG", "User Data PLG", + "Material Effects PLG", "Particle System PLG", "Delta Morph PLG", + "Patch PLG", "Team PLG", "Crowd PP PLG", "Mip Split PLG", + "Anisotrophy PLG", "Not used", "GCN Material PLG", "Geometric PVS PLG", + "XBOX Material PLG", "Multi Texture PLG", "Chain PLG", "Toon PLG", + "PTank PLG", "Particle Standard PLG", "PDS PLG", "PrtAdv PLG", + "Normal Map PLG", "ADC PLG", "UV Animation PLG" +}; + +/* From 0x0180 through 0x01c1 */ +const char *toolkitchunks1[] = { + "Character Set PLG", "NOHS World PLG", "Import Util PLG", + "Slerp PLG", "Optim PLG", "TL World PLG", "Database PLG", + "Raytrace PLG", "Ray PLG", "Library PLG", + "Not used", "Not used", "Not used", "Not used", "Not used", "Not used", + "2D PLG", "Tile Render PLG", "JPEG Image PLG", "TGA Image PLG", + "GIF Image PLG", "Quat PLG", "Spline PVS PLG", "Mipmap PLG", + "MipmapK PLG", "2D Font", "Intersection PLG", "TIFF Image PLG", + "Pick PLG", "BMP Image PLG", "RAS Image PLG", "Skin FX PLG", + "VCAT PLG", "2D Path", "2D Brush", "2D Object", "2D Shape", "2D Scene", + "2D Pick Region", "2D Object String", "2D Animation PLG", + "2D Animation", + "Not used", "Not used", "Not used", "Not used", "Not used", "Not used", + "2D Keyframe", "2D Maestro", "Barycentric", + "Platform Independent Texture Dictionary TK", "TOC TK", "TPL TK", + "AltPipe TK", "Animation TK", "Skin Split Tookit", "Compressed Key TK", + "Geometry Conditioning PLG", "Wing PLG", "Generic Pipeline TK", + "Lightmap Conversion TK", "Filesystem PLG", "Dictionary TK", + "UV Animation Linear", "UV Animation Parameter" +}; + +const char *RSchunks[] = { "Unused 1", "Unused 2", "Extra Normals", + "Pipeline Set", "Unused 5", "Unused 6", "Specular Material", + "Unused 8", "2dfx", "Extra Colors", "Collision Model", + "Unused 12", "Environment Material", "Breakable", "Node Name", + "Unused 16" +}; + +const char* +getChunkName(uint32 id) +{ + switch(id){ + case 0x50E: + return "Bin Mesh PLG"; + case 0x510: + return "Native Data PLG"; + case 0x511: + return "Vertex Format PLG"; + case 0xF21E: + return "ZModeler Lock"; + } + + if(id <= 45) + return chunks[id]; + else if(id <= 0x0253F2FF && id >= 0x0253F2F0) + return RSchunks[id-0x0253F2F0]; + else if(id <= 0x0135 && id >= 0x0101) + return toolkitchunks0[id-0x0101]; + else if(id <= 0x01C0 && id >= 0x0181) + return toolkitchunks1[id-0x0181]; + else + return "Unknown"; +} + +void +readchunk(StreamFile *s, ChunkHeaderInfo *h, int level) +{ + for(int i = 0; i < level; i++) + printf(" "); + const char *name = getChunkName(h->type); + printf("%s (%x bytes @ 0x%x/0x%x) - [0x%x]\n", + name, h->length, s->tell()-12, s->tell(), h->type); + + uint32 end = s->tell() + h->length; + while(s->tell() < end){ + ChunkHeaderInfo nh; + readChunkHeaderInfo(s, &nh); + if(nh.version == h->version && nh.build == h->build){ + readchunk(s, &nh, level+1); + if(h->type == 0x510) + s->seek(end, 0); + }else{ + s->seek(h->length-12); + break; + } + } +} + +int +main(int argc, char *argv[]) +{ + if(argc < 2){ + fprintf(stderr, "usage: %s rwStreamFile\n", argv[0]); + return 0; + } + StreamFile s; + s.open(argv[1], "rb"); + + ChunkHeaderInfo header, last; + while(readChunkHeaderInfo(&s, &header)){ + if(header.type == 0) + break; + last = header; + if(argc == 2) + readchunk(&s, &header, 0); + } + + printf("%x %x %x\n", last.version, last.build, + libraryIDPack(last.version, last.build)); + + s.close(); + return 0; +} diff --git a/vendor/librw/tools/im2d/CMakeLists.txt b/vendor/librw/tools/im2d/CMakeLists.txt new file mode 100644 index 0000000..d30325a --- /dev/null +++ b/vendor/librw/tools/im2d/CMakeLists.txt @@ -0,0 +1,23 @@ +add_executable(im2d WIN32 + im2d.cpp + linelist.cpp + main.cpp + polyline.cpp + trifan.cpp + trilist.cpp + tristrip.cpp +) + +target_link_libraries(im2d + PRIVATE + librw::skeleton + librw::librw +) + +add_custom_command( + TARGET im2d POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory "$/files" + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/files" "$/files" +) + +librw_platform_target(im2d) diff --git a/vendor/librw/tools/im2d/files/whiteash.png b/vendor/librw/tools/im2d/files/whiteash.png new file mode 100644 index 0000000..aa60569 Binary files /dev/null and b/vendor/librw/tools/im2d/files/whiteash.png differ diff --git a/vendor/librw/tools/im2d/im2d.cpp b/vendor/librw/tools/im2d/im2d.cpp new file mode 100644 index 0000000..da4c084 --- /dev/null +++ b/vendor/librw/tools/im2d/im2d.cpp @@ -0,0 +1,134 @@ +#include +#include + +#include "im2d.h" + +bool Im2DColored = true; +bool Im2DTextured; + +rw::int32 Im2DPrimType; + +rw::V2d ScreenSize; +float Scale; + +rw::RGBA SolidWhite = {255, 255, 255, 255}; +rw::RGBA SolidBlack = {0, 0, 0, 255}; +rw::RGBA SolidRed = {200, 64, 64, 255}; +rw::RGBA SolidGreen = {64, 200, 64, 255}; +rw::RGBA SolidBlue = {64, 64, 200, 255}; +rw::RGBA SolidYellow = {200, 200, 64, 255}; +rw::RGBA SolidPurple = {200, 64, 200, 255}; +rw::RGBA SolidCyan = {64, 200, 200, 255}; + +rw::Texture *Im2DTexture; + +void +Im2DInitialize(rw::Camera *camera) +{ + ScreenSize.x = camera->frameBuffer->width; + ScreenSize.y = camera->frameBuffer->height; + + Scale = ScreenSize.y / 3.0f; + + rw::Image::setSearchPath("files/"); + Im2DTexture = rw::Texture::read("whiteash", nil); + + LineListCreate(camera); + LineListSetColor(!Im2DColored); + + IndexedLineListCreate(camera); + IndexedLineListSetColor(!Im2DColored); + + PolyLineCreate(camera); + PolyLineSetColor(!Im2DColored); + + IndexedPolyLineCreate(camera); + IndexedPolyLineSetColor(!Im2DColored); + + TriListCreate(camera); + TriListSetColor(!Im2DColored); + + IndexedTriListCreate(camera); + IndexedTriListSetColor(!Im2DColored); + + TriStripCreate(camera); + TriStripSetColor(!Im2DColored); + + IndexedTriStripCreate(camera); + IndexedTriStripSetColor(!Im2DColored); + + TriFanCreate(camera); + TriFanSetColor(!Im2DColored); + + IndexedTriFanCreate(camera); + IndexedTriFanSetColor(!Im2DColored); +} + +void +Im2DTerminate(void) +{ + if(Im2DTexture){ + Im2DTexture->destroy(); + Im2DTexture = nil; + } +} + +void +Im2DRender(void) +{ + rw::SetRenderState(rw::ZTESTENABLE, 0); + rw::SetRenderState(rw::ZWRITEENABLE, 0); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDSRCALPHA); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDINVSRCALPHA); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::FilterMode::LINEAR); + + if(Im2DTextured) + rw::SetRenderStatePtr(rw::TEXTURERASTER, Im2DTexture->raster); + else + rw::SetRenderStatePtr(rw::TEXTURERASTER, nil); + + switch(Im2DPrimType){ + case 0: LineListRender(); break; + case 1: IndexedLineListRender(); break; + case 2: PolyLineRender(); break; + case 3: IndexedPolyLineRender(); break; + case 4: TriListRender(); break; + case 5: IndexedTriListRender(); break; + case 6: TriStripRender(); break; + case 7: IndexedTriStripRender(); break; + case 8: TriFanRender(); break; + case 9: IndexedTriFanRender(); break; + } +} + +void +Im2DSize(rw::Camera *camera, rw::int32 w, rw::int32 h) +{ + ScreenSize.x = w; + ScreenSize.y = h; + + if(ScreenSize.x > ScreenSize.y) + Scale = ScreenSize.y / 3.0f; + else + Scale = ScreenSize.x / 3.0f; + + LineListCreate(camera); + + IndexedLineListCreate(camera); + + PolyLineCreate(camera); + + IndexedPolyLineCreate(camera); + + TriListCreate(camera); + + IndexedTriListCreate(camera); + + TriStripCreate(camera); + + IndexedTriStripCreate(camera); + + TriFanCreate(camera); + + IndexedTriFanCreate(camera); +} diff --git a/vendor/librw/tools/im2d/im2d.h b/vendor/librw/tools/im2d/im2d.h new file mode 100644 index 0000000..de82e03 --- /dev/null +++ b/vendor/librw/tools/im2d/im2d.h @@ -0,0 +1,62 @@ +extern bool Im2DColored; +extern bool Im2DTextured; + +extern rw::int32 Im2DPrimType; + +extern rw::V2d ScreenSize; +extern float Scale; + +extern rw::RGBA SolidWhite; +extern rw::RGBA SolidBlack; +extern rw::RGBA SolidRed; +extern rw::RGBA SolidGreen; +extern rw::RGBA SolidBlue; +extern rw::RGBA SolidYellow; +extern rw::RGBA SolidPurple; +extern rw::RGBA SolidCyan; + + +void Im2DInitialize(rw::Camera *camera); +void Im2DTerminate(void); +void Im2DRender(void); +void Im2DSize(rw::Camera *camera, rw::int32 w, rw::int32 h); + +void LineListCreate(rw::Camera *camera); +void LineListSetColor(bool white); +void LineListRender(void); + +void IndexedLineListCreate(rw::Camera *camera); +void IndexedLineListSetColor(bool white); +void IndexedLineListRender(void); + +void PolyLineCreate(rw::Camera *camera); +void PolyLineSetColor(bool white); +void PolyLineRender(void); + +void IndexedPolyLineCreate(rw::Camera *camera); +void IndexedPolyLineSetColor(bool white); +void IndexedPolyLineRender(void); + +void TriListCreate(rw::Camera *camera); +void TriListSetColor(bool white); +void TriListRender(void); + +void IndexedTriListCreate(rw::Camera *camera); +void IndexedTriListSetColor(bool white); +void IndexedTriListRender(void); + +void TriStripCreate(rw::Camera *camera); +void TriStripSetColor(bool white); +void TriStripRender(void); + +void IndexedTriStripCreate(rw::Camera *camera); +void IndexedTriStripSetColor(bool white); +void IndexedTriStripRender(void); + +void TriFanCreate(rw::Camera *camera); +void TriFanSetColor(bool white); +void TriFanRender(void); + +void IndexedTriFanCreate(rw::Camera *camera); +void IndexedTriFanSetColor(bool white); +void IndexedTriFanRender(void); diff --git a/vendor/librw/tools/im2d/linelist.cpp b/vendor/librw/tools/im2d/linelist.cpp new file mode 100644 index 0000000..6d76fb2 --- /dev/null +++ b/vendor/librw/tools/im2d/linelist.cpp @@ -0,0 +1,159 @@ +#include +#include + +#include "im2d.h" + +float LineListData[32][4] = { + { 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.000f, 1.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.383f, 0.924f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.707f, 0.707f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.924f, 0.383f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + { 1.000f, 0.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.924f, -0.383f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.707f, -0.707f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.383f, -0.924f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.000f, -1.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.383f, -0.924f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.707f, -0.707f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.924f, -0.383f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + {-1.000f, 0.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.924f, 0.383f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.707f, 0.707f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.383f, 0.924f, 1.000f, 1.000f} +}; + +float IndexedLineListData[16][4] = { + {-1.000f, 1.000f, 0.000f, 1.000f}, + {-0.500f, 1.000f, 0.250f, 1.000f}, + { 0.000f, 1.000f, 0.500f, 1.000f}, + { 0.500f, 1.000f, 0.750f, 1.000f}, + { 1.000f, 1.000f, 1.000f, 1.000f}, + + {-1.000f, 0.500f, 0.000f, 0.750f}, + {-1.000f, 0.000f, 0.000f, 0.500f}, + {-1.000f, -0.500f, 0.000f, 0.250f}, + + { 1.000f, 0.500f, 1.000f, 0.750f}, + { 1.000f, 0.000f, 1.000f, 0.500f}, + { 1.000f, -0.500f, 1.000f, 0.250f}, + + {-1.000f, -1.000f, 0.000f, 0.000f}, + {-0.500f, -1.000f, 0.250f, 0.000f}, + { 0.000f, -1.000f, 0.500f, 0.000f}, + { 0.500f, -1.000f, 0.750f, 0.000f}, + { 1.000f, -1.000f, 1.000f, 0.000f} +}; + +rw::uint16 IndexedLineListIndices[20] = { + 0, 11, 1, 12, 2, 13, 3, 14, 4, 15, + 0, 4, 5, 8, 6, 9, 7, 10, 11, 15 +}; + +rw::RWDEVICE::Im2DVertex LineList[32]; +rw::RWDEVICE::Im2DVertex IndexedLineList[16]; + + +void +LineListCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 32; i++){ + LineList[i].setScreenX(ScreenSize.x/2.0f + LineListData[i][0]*Scale); + LineList[i].setScreenY(ScreenSize.y/2.0f - LineListData[i][1]*Scale); + LineList[i].setScreenZ(rw::im2d::GetNearZ()); + LineList[i].setRecipCameraZ(recipZ); + LineList[i].setU(LineListData[i][2], recipZ); + LineList[i].setV(LineListData[i][3], recipZ); + } +} + +void +LineListSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidRed; + rw::RGBA SolidColor2 = SolidWhite; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + } + + for(int i = 0; i < 32; i += 2){ + LineList[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + LineList[i+1].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + } +} + +void +LineListRender(void) +{ + rw::im2d::RenderPrimitive(rw::PRIMTYPELINELIST, LineList, 32); +} + + +void +IndexedLineListCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 16; i++){ + IndexedLineList[i].setScreenX(ScreenSize.x/2.0f + IndexedLineListData[i][0]*Scale); + IndexedLineList[i].setScreenY(ScreenSize.y/2.0f - IndexedLineListData[i][1]*Scale); + IndexedLineList[i].setScreenZ(rw::im2d::GetNearZ()); + IndexedLineList[i].setRecipCameraZ(recipZ); + IndexedLineList[i].setU(IndexedLineListData[i][2], recipZ); + IndexedLineList[i].setV(IndexedLineListData[i][3], recipZ); + } +} + +void +IndexedLineListSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidRed; + + if(white) + SolidColor1 = SolidWhite; + + for(int i = 0; i < 16; i++) + IndexedLineList[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); +} + +void +IndexedLineListRender(void) +{ + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPELINELIST, + IndexedLineList, 16, IndexedLineListIndices, 20); +} diff --git a/vendor/librw/tools/im2d/main.cpp b/vendor/librw/tools/im2d/main.cpp new file mode 100644 index 0000000..e37dfd4 --- /dev/null +++ b/vendor/librw/tools/im2d/main.cpp @@ -0,0 +1,254 @@ +#include +#include +#include + +#include "im2d.h" + +rw::EngineOpenParams engineOpenParams; + +rw::RGBA ForegroundColor = { 200, 200, 200, 255 }; +rw::RGBA BackgroundColor = { 64, 64, 64, 0 }; + +rw::Camera *Camera; +rw::Charset *Charset; + +float TimeDelta; + +rw::Camera* +CreateCamera(void) +{ + Camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + assert(Camera); + + Camera->setNearPlane(0.1f); + Camera->setFarPlane(10.0f); + + return Camera; +} + +void +Initialize(void) +{ + sk::globals.windowtitle = "Im2D example"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +Initialize3D(void) +{ + if(!sk::InitRW()) + return false; + + Charset = rw::Charset::create(&ForegroundColor, &BackgroundColor); + + Camera = CreateCamera(); + + Im2DInitialize(Camera); + + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); + + return true; +} + +void +Terminate3D(void) +{ + Im2DTerminate(); + + if(Camera){ + Camera->destroy(); + Camera = nil; + } + + if(Charset){ + Charset->destroy(); + Charset = nil; + } + + sk::TerminateRW(); +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +void +DisplayOnScreenInfo(void) +{ +} + +void +Gui(void) +{ + static bool showWindow = true; + ImGui::Begin("Im2D", &showWindow); + + ImGui::RadioButton("Line-list", &Im2DPrimType, 0); + ImGui::RadioButton("Line-list indexed", &Im2DPrimType, 1); + ImGui::RadioButton("Poly-line", &Im2DPrimType, 2); + ImGui::RadioButton("Poly-line indexed", &Im2DPrimType, 3); + ImGui::RadioButton("Tri-list", &Im2DPrimType, 4); + ImGui::RadioButton("Tri-list indexed", &Im2DPrimType, 5); + ImGui::RadioButton("Tri-strip", &Im2DPrimType, 6); + ImGui::RadioButton("Tri-strip indexed", &Im2DPrimType, 7); + ImGui::RadioButton("Tri-fan", &Im2DPrimType, 8); + ImGui::RadioButton("Tri-fan indexed", &Im2DPrimType, 9); + + ImGui::NewLine(); + + ImGui::Checkbox("Textured", &Im2DTextured); + if(ImGui::Checkbox("Colored", &Im2DColored)){ + LineListSetColor(!Im2DColored); + IndexedLineListSetColor(!Im2DColored); + PolyLineSetColor(!Im2DColored); + IndexedPolyLineSetColor(!Im2DColored); + TriListSetColor(!Im2DColored); + IndexedTriListSetColor(!Im2DColored); + TriStripSetColor(!Im2DColored); + IndexedTriStripSetColor(!Im2DColored); + TriFanSetColor(!Im2DColored); + IndexedTriFanSetColor(!Im2DColored); + } + + ImGui::End(); +} + +void +Render(void) +{ + Camera->clear(&BackgroundColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + + Camera->beginUpdate(); + + ImGui_ImplRW_NewFrame(TimeDelta); + + Im2DRender(); + DisplayOnScreenInfo(); + + Gui(); + ImGui::EndFrame(); + ImGui::Render(); + + ImGui_ImplRW_RenderDrawLists(ImGui::GetDrawData()); + + Camera->endUpdate(); + + Camera->showRaster(0); +} + +void +Idle(float timeDelta) +{ + TimeDelta = timeDelta; + Render(); +} + +int MouseX, MouseY; +int MouseDeltaX, MouseDeltaY; +int MouseButtons; + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +void +MouseBtn(sk::MouseState *mouse) +{ + MouseButtons = mouse->buttons; +} + +void +MouseMove(sk::MouseState *mouse) +{ + MouseDeltaX = mouse->posx - MouseX; + MouseDeltaY = mouse->posy - MouseY; + MouseX = mouse->posx; + MouseY = mouse->posy; +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + MouseState *ms; + + ImGuiEventHandler(e, param); + ImGuiIO &io = ImGui::GetIO(); + + switch(e){ + case INITIALIZE: + Initialize(); + return EVENTPROCESSED; + case RWINITIALIZE: + return Initialize3D() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + Terminate3D(); + return EVENTPROCESSED; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case MOUSEBTN: + if(!io.WantCaptureMouse){ + ms = (MouseState*)param; + MouseBtn(ms); + }else + MouseButtons = 0; + return EVENTPROCESSED; + case MOUSEMOVE: + MouseMove((MouseState*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + if(::Camera){ + sk::CameraSize(::Camera, r, 0.5f, 4.0f/3.0f); + Im2DSize(::Camera, r->w, r->h); + } + break; + case IDLE: + Idle(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/vendor/librw/tools/im2d/polyline.cpp b/vendor/librw/tools/im2d/polyline.cpp new file mode 100644 index 0000000..5379117 --- /dev/null +++ b/vendor/librw/tools/im2d/polyline.cpp @@ -0,0 +1,132 @@ +#include +#include + +#include "im2d.h" + +float PolyLineData[16][4] = { + { 0.000f, 1.000f, 0.500f, 1.000f}, + { 0.672f, 0.672f, 0.836f, 0.836f}, + { 0.900f, 0.000f, 0.950f, 0.500f}, + { 0.601f, -0.601f, 0.800f, 0.200f}, + { 0.000f, -0.800f, 0.500f, 0.100f}, + {-0.530f, -0.530f, 0.245f, 0.245f}, + {-0.700f, 0.000f, 0.150f, 0.500f}, + {-0.460f, 0.460f, 0.270f, 0.770f}, + { 0.000f, 0.600f, 0.500f, 0.800f}, + { 0.389f, 0.389f, 0.695f, 0.695f}, + { 0.500f, 0.000f, 0.750f, 0.500f}, + { 0.318f, -0.318f, 0.659f, 0.341f}, + { 0.000f, -0.400f, 0.500f, 0.300f}, + {-0.247f, -0.247f, 0.376f, 0.376f}, + {-0.300f, 0.000f, 0.350f, 0.500f}, + {-0.177f, 0.177f, 0.411f, 0.589f} +}; + +float IndexedPolyLineData[21][4] = { + { 0.000f, 1.000f, 0.500f, 1.000f}, + + {-0.200f, 0.600f, 0.400f, 0.800f}, + { 0.200f, 0.600f, 0.600f, 0.800f}, + + {-0.400f, 0.200f, 0.300f, 0.600f}, + { 0.000f, 0.200f, 0.500f, 0.600f}, + { 0.400f, 0.200f, 0.700f, 0.600f}, + + {-0.600f, -0.200f, 0.200f, 0.400f}, + {-0.200f, -0.200f, 0.400f, 0.400f}, + { 0.200f, -0.200f, 0.600f, 0.400f}, + { 0.600f, -0.200f, 0.800f, 0.400f}, + + {-0.800f, -0.600f, 0.100f, 0.200f}, + {-0.400f, -0.600f, 0.300f, 0.200f}, + { 0.000f, -0.600f, 0.500f, 0.200f}, + { 0.400f, -0.600f, 0.700f, 0.200f}, + { 0.800f, -0.600f, 0.900f, 0.200f}, + + {-1.000f, -1.000f, 0.000f, 0.000f}, + {-0.600f, -1.000f, 0.200f, 0.000f}, + {-0.200f, -1.000f, 0.400f, 0.000f}, + { 0.200f, -1.000f, 0.600f, 0.000f}, + { 0.600f, -1.000f, 0.800f, 0.000f}, + { 1.000f, -1.000f, 1.000f, 0.000f} +}; + +rw::uint16 IndexedPolyLineIndices[46] = { + 0, 2, 5, 4, 8, 5, 9, 8, 13, 9, + 14, 13, 19, 14, 20, 19, 18, 13, 12, 8, + 7, 12, 18, 17, 12, 11, 17, 16, 15, 10, + 16, 11, 10, 6, 11, 7, 6, 3, 7, 4, + 3, 1, 4, 2, 1, 0 +}; + +rw::RWDEVICE::Im2DVertex PolyLine[16]; +rw::RWDEVICE::Im2DVertex IndexedPolyLine[21]; + + +void +PolyLineCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 16; i++){ + PolyLine[i].setScreenX(ScreenSize.x/2.0f + PolyLineData[i][0]*Scale); + PolyLine[i].setScreenY(ScreenSize.y/2.0f - PolyLineData[i][1]*Scale); + PolyLine[i].setScreenZ(rw::im2d::GetNearZ()); + PolyLine[i].setRecipCameraZ(recipZ); + PolyLine[i].setU(PolyLineData[i][2], recipZ); + PolyLine[i].setV(PolyLineData[i][3], recipZ); + } +} + +void +PolyLineSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidBlue; + + if(white) + SolidColor1 = SolidWhite; + + for(int i = 0; i < 16; i++) + PolyLine[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); +} + +void +PolyLineRender(void) +{ + rw::im2d::RenderPrimitive(rw::PRIMTYPEPOLYLINE, PolyLine, 16); +} + + +void +IndexedPolyLineCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 21; i++){ + IndexedPolyLine[i].setScreenX(ScreenSize.x/2.0f + IndexedPolyLineData[i][0]*Scale); + IndexedPolyLine[i].setScreenY(ScreenSize.y/2.0f - IndexedPolyLineData[i][1]*Scale); + IndexedPolyLine[i].setScreenZ(rw::im2d::GetNearZ()); + IndexedPolyLine[i].setRecipCameraZ(recipZ); + IndexedPolyLine[i].setU(IndexedPolyLineData[i][2], recipZ); + IndexedPolyLine[i].setV(IndexedPolyLineData[i][3], recipZ); + } +} + +void +IndexedPolyLineSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidBlue; + + if(white) + SolidColor1 = SolidWhite; + + for(int i = 0; i < 21; i++) + IndexedPolyLine[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); +} + +void +IndexedPolyLineRender(void) +{ + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPEPOLYLINE, + IndexedPolyLine, 21, IndexedPolyLineIndices, 46); +} diff --git a/vendor/librw/tools/im2d/trifan.cpp b/vendor/librw/tools/im2d/trifan.cpp new file mode 100644 index 0000000..b1a703f --- /dev/null +++ b/vendor/librw/tools/im2d/trifan.cpp @@ -0,0 +1,119 @@ +#include +#include + +#include "im2d.h" + +float TriFanData[17][4] = { + { 0.000f, 1.000f, 0.500f, 1.000f}, + {-0.383f, 0.924f, 0.308f, 0.962f}, + {-0.707f, 0.707f, 0.146f, 0.854f}, + {-0.924f, 0.383f, 0.038f, 0.692f}, + {-1.000f, 0.000f, 0.000f, 0.500f}, + {-0.924f, -0.383f, 0.038f, 0.308f}, + {-0.707f, -0.707f, 0.146f, 0.146f}, + {-0.383f, -0.924f, 0.308f, 0.038f}, + { 0.000f, -1.000f, 0.500f, 0.000f}, + { 0.383f, -0.924f, 0.692f, 0.038f}, + { 0.707f, -0.707f, 0.854f, 0.146f}, + { 0.924f, -0.383f, 0.962f, 0.308f}, + { 1.000f, 0.000f, 1.000f, 0.500f}, + { 0.924f, 0.383f, 0.962f, 0.692f}, + { 0.707f, 0.707f, 0.854f, 0.854f}, + { 0.383f, 0.924f, 0.692f, 0.962f}, + { 0.000f, 1.000f, 0.500f, 1.000f} +}; + +float IndexedTriFanData[16][4] = { + { 0.000f, 1.000f, 0.500f, 1.000f}, + {-0.383f, 0.924f, 0.308f, 0.962f}, + {-0.707f, 0.707f, 0.146f, 0.854f}, + {-0.924f, 0.383f, 0.038f, 0.692f}, + {-1.000f, 0.000f, 0.000f, 0.500f}, + {-0.924f, -0.383f, 0.038f, 0.308f}, + {-0.707f, -0.707f, 0.146f, 0.146f}, + {-0.383f, -0.924f, 0.308f, 0.038f}, + { 0.000f, -1.000f, 0.500f, 0.000f}, + { 0.383f, -0.924f, 0.692f, 0.038f}, + { 0.707f, -0.707f, 0.854f, 0.146f}, + { 0.924f, -0.383f, 0.962f, 0.308f}, + { 1.000f, 0.000f, 1.000f, 0.500f}, + { 0.924f, 0.383f, 0.962f, 0.692f}, + { 0.707f, 0.707f, 0.854f, 0.854f}, + { 0.383f, 0.924f, 0.692f, 0.962f} +}; + +rw::uint16 IndexedTriFanIndices[17] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0 +}; + +rw::RWDEVICE::Im2DVertex TriFan[17]; +rw::RWDEVICE::Im2DVertex IndexedTriFan[16]; + + +void +TriFanCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 17; i++){ + TriFan[i].setScreenX(ScreenSize.x/2.0f + TriFanData[i][0]*Scale); + TriFan[i].setScreenY(ScreenSize.y/2.0f - TriFanData[i][1]*Scale); + TriFan[i].setScreenZ(rw::im2d::GetNearZ()); + TriFan[i].setRecipCameraZ(recipZ); + TriFan[i].setU(TriFanData[i][2], recipZ); + TriFan[i].setV(TriFanData[i][3], recipZ); + } +} + +void +TriFanSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidYellow; + + if(white) + SolidColor1 = SolidWhite; + + for(int i = 0; i < 17; i++) + TriFan[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); +} + +void +TriFanRender(void) +{ + rw::im2d::RenderPrimitive(rw::PRIMTYPETRIFAN, TriFan, 17); +} + + +void +IndexedTriFanCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 16; i++){ + IndexedTriFan[i].setScreenX(ScreenSize.x/2.0f + IndexedTriFanData[i][0]*Scale); + IndexedTriFan[i].setScreenY(ScreenSize.y/2.0f - IndexedTriFanData[i][1]*Scale); + IndexedTriFan[i].setScreenZ(rw::im2d::GetNearZ()); + IndexedTriFan[i].setRecipCameraZ(recipZ); + IndexedTriFan[i].setU(IndexedTriFanData[i][2], recipZ); + IndexedTriFan[i].setV(IndexedTriFanData[i][3], recipZ); + } +} + +void +IndexedTriFanSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidGreen; + + if(white) + SolidColor1 = SolidWhite; + + for(int i = 0; i < 16; i++) + IndexedTriFan[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); +} + +void +IndexedTriFanRender(void) +{ + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRIFAN, + IndexedTriFan, 16, IndexedTriFanIndices, 17); +} diff --git a/vendor/librw/tools/im2d/trilist.cpp b/vendor/librw/tools/im2d/trilist.cpp new file mode 100644 index 0000000..64ee65f --- /dev/null +++ b/vendor/librw/tools/im2d/trilist.cpp @@ -0,0 +1,166 @@ +#include +#include + +#include "im2d.h" + +float TriListData[18][4] = { + { 0.000f, 1.000f, 0.500f, 1.000f}, + {-0.500f, 0.500f, 0.250f, 0.750f}, + { 0.500f, 0.500f, 0.750f, 0.750f}, + + {-0.500f, 0.500f, 0.250f, 0.750f}, + { 0.500f, -0.500f, 0.750f, 0.250f}, + { 0.500f, 0.500f, 0.750f, 0.750f}, + + { 0.500f, 0.500f, 0.750f, 0.750f}, + { 0.500f, -0.500f, 0.750f, 0.250f}, + { 1.000f, 0.000f, 1.000f, 0.500f}, + + { 0.500f, -0.500f, 0.750f, 0.250f}, + {-0.500f, -0.500f, 0.250f, 0.250f}, + { 0.000f, -1.000f, 0.500f, 0.000f}, + + { 0.500f, -0.500f, 0.750f, 1.250f}, + {-0.500f, 0.500f, 0.250f, 1.750f}, + {-0.500f, -0.500f, 0.250f, 1.250f}, + + {-0.500f, -0.500f, 0.250f, 0.250f}, + {-0.500f, 0.500f, 0.250f, 0.750f}, + {-1.000f, 0.000f, 0.000f, 0.500f} +}; + +float IndexedTriListData[21][4] = { + { 0.000f, 1.000f, 0.500f, 1.000f}, + + {-0.200f, 0.600f, 0.400f, 0.800f}, + { 0.200f, 0.600f, 0.600f, 0.800f}, + + {-0.400f, 0.200f, 0.300f, 0.600f}, + { 0.000f, 0.200f, 0.500f, 0.600f}, + { 0.400f, 0.200f, 0.300f, 0.600f}, + + {-0.600f, -0.200f, 0.200f, 0.400f}, + {-0.200f, -0.200f, 0.400f, 0.400f}, + { 0.200f, -0.200f, 0.600f, 0.400f}, + { 0.600f, -0.200f, 0.800f, 0.400f}, + + {-0.800f, -0.600f, 0.100f, 0.200f}, + {-0.400f, -0.600f, 0.300f, 0.200f}, + { 0.000f, -0.600f, 0.500f, 0.200f}, + { 0.400f, -0.600f, 0.700f, 0.200f}, + { 0.800f, -0.600f, 0.900f, 0.200f}, + + {-1.000f, -1.000f, 0.000f, 0.000f}, + {-0.600f, -1.000f, 0.200f, 0.000f}, + {-0.200f, -1.000f, 0.400f, 0.000f}, + { 0.200f, -1.000f, 0.600f, 0.000f}, + { 0.600f, -1.000f, 0.800f, 0.000f}, + { 1.000f, -1.000f, 1.000f, 0.000f} +}; + +rw::uint16 IndexedTriListIndices[45] = { + 0, 1, 2, + 1, 3, 4, 2, 4, 5, + 3, 6, 7, 4, 7, 8, 5, 8, 9, + 6, 10, 11, 7, 11, 12, 8, 12, 13, 9, 13, 14, + 10, 15, 16, 11, 16, 17, 12, 17, 18, 13, 18, 19, 14, 19, 20 +}; + +rw::RWDEVICE::Im2DVertex TriList[18]; +rw::RWDEVICE::Im2DVertex IndexedTriList[21]; + + +void +TriListCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 18; i++){ + TriList[i].setScreenX(ScreenSize.x/2.0f + TriListData[i][0]*Scale); + TriList[i].setScreenY(ScreenSize.y/2.0f - TriListData[i][1]*Scale); + TriList[i].setScreenZ(rw::im2d::GetNearZ()); + TriList[i].setRecipCameraZ(recipZ); + TriList[i].setU(TriListData[i][2], recipZ); + TriList[i].setV(TriListData[i][3], recipZ); + } +} + +void +TriListSetColor(bool white) +{ + int i; + rw::RGBA SolidColor1 = SolidBlue; + rw::RGBA SolidColor2 = SolidRed; + rw::RGBA SolidColor3 = SolidGreen; + rw::RGBA SolidColor4 = SolidYellow; + rw::RGBA SolidColor5 = SolidPurple; + rw::RGBA SolidColor6 = SolidCyan; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + SolidColor3 = SolidWhite; + SolidColor4 = SolidWhite; + SolidColor5 = SolidWhite; + SolidColor6 = SolidWhite; + } + + for(i = 0; i < 3; i++) + TriList[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + for(; i < 6; i++) + TriList[i].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + for(; i < 9; i++) + TriList[i].setColor(SolidColor3.red, SolidColor3.green, + SolidColor3.blue, SolidColor3.alpha); + for(; i < 12; i++) + TriList[i].setColor(SolidColor4.red, SolidColor4.green, + SolidColor4.blue, SolidColor4.alpha); + for(; i < 15; i++) + TriList[i].setColor(SolidColor5.red, SolidColor5.green, + SolidColor5.blue, SolidColor5.alpha); + for(; i < 18; i++) + TriList[i].setColor(SolidColor6.red, SolidColor6.green, + SolidColor6.blue, SolidColor6.alpha); +} + +void +TriListRender(void) +{ + rw::im2d::RenderPrimitive(rw::PRIMTYPETRILIST, TriList, 18); +} + + +void +IndexedTriListCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 21; i++){ + IndexedTriList[i].setScreenX(ScreenSize.x/2.0f + IndexedTriListData[i][0]*Scale); + IndexedTriList[i].setScreenY(ScreenSize.y/2.0f - IndexedTriListData[i][1]*Scale); + IndexedTriList[i].setScreenZ(rw::im2d::GetNearZ()); + IndexedTriList[i].setRecipCameraZ(recipZ); + IndexedTriList[i].setU(IndexedTriListData[i][2], recipZ); + IndexedTriList[i].setV(IndexedTriListData[i][3], recipZ); + } +} + +void +IndexedTriListSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidBlue; + + if(white) + SolidColor1 = SolidWhite; + + for(int i = 0; i < 21; i++) + IndexedTriList[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); +} + +void +IndexedTriListRender(void) +{ + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, + IndexedTriList, 21, IndexedTriListIndices, 45); +} diff --git a/vendor/librw/tools/im2d/tristrip.cpp b/vendor/librw/tools/im2d/tristrip.cpp new file mode 100644 index 0000000..41f2d3a --- /dev/null +++ b/vendor/librw/tools/im2d/tristrip.cpp @@ -0,0 +1,130 @@ +#include +#include + +#include "im2d.h" + +float TriStripData[18][4] = { + { 0.000f, 1.000f, 0.500f, 1.000f}, + { 0.000f, 0.500f, 0.500f, 0.750f}, + + { 0.707f, 0.707f, 0.853f, 0.853f}, + { 0.354f, 0.354f, 0.677f, 0.677f}, + + { 1.000f, 0.000f, 1.000f, 0.500f}, + { 0.500f, 0.000f, 0.750f, 0.500f}, + + { 0.707f, -0.707f, 0.853f, 0.147f}, + { 0.354f, -0.354f, 0.677f, 0.323f}, + + { 0.000f, -1.000f, 0.500f, 0.000f}, + { 0.000f, -0.500f, 0.500f, 0.250f}, + + {-0.707f, -0.707f, 0.147f, 0.147f}, + {-0.354f, -0.354f, 0.323f, 0.323f}, + + {-1.000f, 0.000f, 0.000f, 0.500f}, + {-0.500f, 0.000f, 0.250f, 0.500f}, + + {-0.707f, 0.707f, 0.147f, 0.853f}, + {-0.354f, 0.354f, 0.323f, 0.677f}, + + { 0.000f, 1.000f, 0.500f, 1.000f}, + { 0.000f, 0.500f, 0.500f, 0.750f} +}; + +float IndexedTriStripData[16][4] = { + { 0.000f, 1.000f, 0.500f, 1.000f}, + { 0.707f, 0.707f, 0.853f, 0.853f}, + { 1.000f, 0.000f, 1.000f, 0.500f}, + { 0.707f, -0.707f, 0.853f, 0.147f}, + { 0.000f, -1.000f, 0.500f, 0.000f}, + {-0.707f, -0.707f, 0.147f, 0.147f}, + {-1.000f, 0.000f, 0.000f, 0.500f}, + {-0.707f, 0.707f, 0.147f, 0.853f}, + + { 0.000f, 0.500f, 0.500f, 0.750f}, + { 0.354f, 0.354f, 0.677f, 0.677f}, + { 0.500f, 0.000f, 0.750f, 0.500f}, + { 0.354f, -0.354f, 0.677f, 0.323f}, + { 0.000f, -0.500f, 0.500f, 0.250f}, + {-0.354f, -0.354f, 0.323f, 0.323f}, + {-0.500f, 0.000f, 0.250f, 0.500f}, + {-0.354f, 0.354f, 0.323f, 0.677f} +}; + +rw::uint16 IndexedTriStripIndices[18] = { + 0, 8, 1, 9, 2, 10, 3, 11, + 4, 12, 5, 13, 6, 14, 7, 15, 0, 8 +}; + +rw::RWDEVICE::Im2DVertex TriStrip[18]; +rw::RWDEVICE::Im2DVertex IndexedTriStrip[16]; + + +void +TriStripCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 18; i++){ + TriStrip[i].setScreenX(ScreenSize.x/2.0f + TriStripData[i][0]*Scale); + TriStrip[i].setScreenY(ScreenSize.y/2.0f - TriStripData[i][1]*Scale); + TriStrip[i].setScreenZ(rw::im2d::GetNearZ()); + TriStrip[i].setRecipCameraZ(recipZ); + TriStrip[i].setU(TriStripData[i][2], recipZ); + TriStrip[i].setV(TriStripData[i][3], recipZ); + } +} + +void +TriStripSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidPurple; + + if(white) + SolidColor1 = SolidWhite; + + for(int i = 0; i < 18; i++) + TriStrip[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); +} + +void +TriStripRender(void) +{ + rw::im2d::RenderPrimitive(rw::PRIMTYPETRISTRIP, TriStrip, 18); +} + + +void +IndexedTriStripCreate(rw::Camera *camera) +{ + float recipZ = 1.0f/camera->nearPlane; + for(int i = 0; i < 16; i++){ + IndexedTriStrip[i].setScreenX(ScreenSize.x/2.0f + IndexedTriStripData[i][0]*Scale); + IndexedTriStrip[i].setScreenY(ScreenSize.y/2.0f - IndexedTriStripData[i][1]*Scale); + IndexedTriStrip[i].setScreenZ(rw::im2d::GetNearZ()); + IndexedTriStrip[i].setRecipCameraZ(recipZ); + IndexedTriStrip[i].setU(IndexedTriStripData[i][2], recipZ); + IndexedTriStrip[i].setV(IndexedTriStripData[i][3], recipZ); + } +} + +void +IndexedTriStripSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidCyan; + + if(white) + SolidColor1 = SolidWhite; + + for(int i = 0; i < 16; i++) + IndexedTriStrip[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); +} + +void +IndexedTriStripRender(void) +{ + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRISTRIP, + IndexedTriStrip, 16, IndexedTriStripIndices, 18); +} diff --git a/vendor/librw/tools/im3d/CMakeLists.txt b/vendor/librw/tools/im3d/CMakeLists.txt new file mode 100644 index 0000000..1a49114 --- /dev/null +++ b/vendor/librw/tools/im3d/CMakeLists.txt @@ -0,0 +1,23 @@ +add_executable(im3d WIN32 + im3d.cpp + linelist.cpp + main.cpp + polyline.cpp + trifan.cpp + trilist.cpp + tristrip.cpp +) + +target_link_libraries(im3d + PRIVATE + librw::skeleton + librw::librw +) + +add_custom_command( + TARGET im3d POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory "$/files" + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/files" "$/files" +) + +librw_platform_target(im3d) diff --git a/vendor/librw/tools/im3d/files/whiteash.png b/vendor/librw/tools/im3d/files/whiteash.png new file mode 100644 index 0000000..aa60569 Binary files /dev/null and b/vendor/librw/tools/im3d/files/whiteash.png differ diff --git a/vendor/librw/tools/im3d/im3d.cpp b/vendor/librw/tools/im3d/im3d.cpp new file mode 100644 index 0000000..4588bc1 --- /dev/null +++ b/vendor/librw/tools/im3d/im3d.cpp @@ -0,0 +1,138 @@ +#include +#include + +#include "im3d.h" + +bool Im3DColored = true; +bool Im3DTextured; + +rw::int32 Im3DPrimType; + +rw::RGBA SolidWhite = {255, 255, 255, 255}; +rw::RGBA SolidBlack = {0, 0, 0, 255}; +rw::RGBA SolidRed = {200, 64, 64, 255}; +rw::RGBA SolidGreen = {64, 200, 64, 255}; +rw::RGBA SolidBlue = {64, 64, 200, 255}; +rw::RGBA SolidYellow = {200, 200, 64, 255}; +rw::RGBA SolidPurple = {200, 64, 200, 255}; +rw::RGBA SolidCyan = {64, 200, 200, 255}; + +rw::Matrix *Im3DMatrix; +rw::Texture *Im3DTexture; + +void +Im3DInitialize(void) +{ + Im3DMatrix = rw::Matrix::create(); + assert(Im3DMatrix); + + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::V3d pos = rw::scale(cameraMatrix->at, 6.0f); + Im3DMatrix->rotate(&cameraMatrix->up, 30.0f); + Im3DMatrix->translate(&pos); + + rw::Image::setSearchPath("files/"); + Im3DTexture = rw::Texture::read("whiteash", nil); + + LineListCreate(); + LineListSetColor(!Im3DColored); + + IndexedLineListCreate(); + IndexedLineListSetColor(!Im3DColored); + + PolyLineCreate(); + PolyLineSetColor(!Im3DColored); + + IndexedPolyLineCreate(); + IndexedPolyLineSetColor(!Im3DColored); + + TriListCreate(); + TriListSetColor(!Im3DColored); + + IndexedTriListCreate(); + IndexedTriListSetColor(!Im3DColored); + + TriStripCreate(); + TriStripSetColor(!Im3DColored); + + IndexedTriStripCreate(); + IndexedTriStripSetColor(!Im3DColored); + + TriFanCreate(); + TriFanSetColor(!Im3DColored); + + IndexedTriFanCreate(); + IndexedTriFanSetColor(!Im3DColored); +} + +void +Im3DTerminate(void) +{ + if(Im3DMatrix){ + Im3DMatrix->destroy(); + Im3DMatrix = nil; + } + + if(Im3DTexture){ + Im3DTexture->destroy(); + Im3DTexture = nil; + } +} + +void +Im3DRender(void) +{ + rw::SetRenderState(rw::ZTESTENABLE, 1); + rw::SetRenderState(rw::ZWRITEENABLE, 1); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDONE); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDZERO); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::FilterMode::LINEAR); + rw::SetRenderState(rw::CULLMODE, rw::CULLBACK); + + rw::uint32 flags; + if(Im3DTextured){ + rw::SetRenderStatePtr(rw::TEXTURERASTER, Im3DTexture->raster); + flags = rw::im3d::VERTEXUV | rw::im3d::ALLOPAQUE; + }else{ + rw::SetRenderStatePtr(rw::TEXTURERASTER, nil); + flags = rw::im3d::ALLOPAQUE; + } + + switch(Im3DPrimType){ + case 0: LineListRender(Im3DMatrix, flags); break; + case 1: IndexedLineListRender(Im3DMatrix, flags); break; + case 2: PolyLineRender(Im3DMatrix, flags); break; + case 3: IndexedPolyLineRender(Im3DMatrix, flags); break; + case 4: TriListRender(Im3DMatrix, flags); break; + case 5: IndexedTriListRender(Im3DMatrix, flags); break; + case 6: TriStripRender(Im3DMatrix, flags); break; + case 7: IndexedTriStripRender(Im3DMatrix, flags); break; + case 8: TriFanRender(Im3DMatrix, flags); break; + case 9: IndexedTriFanRender(Im3DMatrix, flags); break; + } +} + +void +Im3DRotate(float xAngle, float yAngle) +{ + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::V3d pos = Im3DMatrix->pos; + + pos = rw::scale(pos, -1.0f); + Im3DMatrix->translate(&pos); + + Im3DMatrix->rotate(&cameraMatrix->up, xAngle); + Im3DMatrix->rotate(&cameraMatrix->right, yAngle); + + pos = rw::scale(pos, -1.0f); + Im3DMatrix->translate(&pos); +} + +void +Im3DTranslateZ(float zDelta) +{ + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::V3d delta = rw::scale(cameraMatrix->at, zDelta); + Im3DMatrix->translate(&delta); +} + diff --git a/vendor/librw/tools/im3d/im3d.h b/vendor/librw/tools/im3d/im3d.h new file mode 100644 index 0000000..a9651cf --- /dev/null +++ b/vendor/librw/tools/im3d/im3d.h @@ -0,0 +1,62 @@ +extern rw::Camera *Camera; + +extern bool Im3DColored; +extern bool Im3DTextured; + +extern rw::int32 Im3DPrimType; + +extern rw::RGBA SolidWhite; +extern rw::RGBA SolidBlack; +extern rw::RGBA SolidRed; +extern rw::RGBA SolidGreen; +extern rw::RGBA SolidBlue; +extern rw::RGBA SolidYellow; +extern rw::RGBA SolidPurple; +extern rw::RGBA SolidCyan; + + +void Im3DInitialize(void); +void Im3DTerminate(void); +void Im3DRender(void); +void Im3DRotate(float xAngle, float yAngle); +void Im3DTranslateZ(float zDelta); + +void LineListCreate(void); +void LineListSetColor(bool white); +void LineListRender(rw::Matrix *transform, rw::uint32 transformFlags); + +void IndexedLineListCreate(void); +void IndexedLineListSetColor(bool white); +void IndexedLineListRender(rw::Matrix *transform, rw::uint32 transformFlags); + +void PolyLineCreate(void); +void PolyLineSetColor(bool white); +void PolyLineRender(rw::Matrix *transform, rw::uint32 transformFlags); + +void IndexedPolyLineCreate(void); +void IndexedPolyLineSetColor(bool white); +void IndexedPolyLineRender(rw::Matrix *transform, rw::uint32 transformFlags); + +void TriListCreate(void); +void TriListSetColor(bool white); +void TriListRender(rw::Matrix *transform, rw::uint32 transformFlags); + +void IndexedTriListCreate(void); +void IndexedTriListSetColor(bool white); +void IndexedTriListRender(rw::Matrix *transform, rw::uint32 transformFlags); + +void TriStripCreate(void); +void TriStripSetColor(bool white); +void TriStripRender(rw::Matrix *transform, rw::uint32 transformFlags); + +void IndexedTriStripCreate(void); +void IndexedTriStripSetColor(bool white); +void IndexedTriStripRender(rw::Matrix *transform, rw::uint32 transformFlags); + +void TriFanCreate(void); +void TriFanSetColor(bool white); +void TriFanRender(rw::Matrix *transform, rw::uint32 transformFlags); + +void IndexedTriFanCreate(void); +void IndexedTriFanSetColor(bool white); +void IndexedTriFanRender(rw::Matrix *transform, rw::uint32 transformFlags); diff --git a/vendor/librw/tools/im3d/linelist.cpp b/vendor/librw/tools/im3d/linelist.cpp new file mode 100644 index 0000000..768e87a --- /dev/null +++ b/vendor/librw/tools/im3d/linelist.cpp @@ -0,0 +1,176 @@ +#include +#include + +#include "im3d.h" + +float LineListData[28][5] = { + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.000f, 1.000f, 0.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.000f, -1.000f, 0.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.000f, 0.000f, 1.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.000f, 0.000f, -1.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 1.000f, 0.000f, 0.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + {-1.000f, 0.000f, 0.000f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.577f, 0.577f, 0.577f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.577f, -0.577f, 0.577f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.577f, 0.577f, -0.577f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.577f, -0.577f, -0.577f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.577f, -0.577f, -0.577f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.577f, 0.577f, -0.577f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.577f, -0.577f, 0.577f, 1.000f, 1.000f}, + + { 0.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.577f, 0.577f, 0.577f, 1.000f, 1.000f} +}; + +float IndexedLineListData[18][5] = { + { 0.000f, 1.000f, 0.000f, 0.000f, 0.000f}, + + { 0.577f, 0.577f, 0.577f, 0.000f, 0.000f}, + { 0.577f, 0.577f, -0.577f, 0.000f, 0.000f}, + {-0.577f, 0.577f, -0.577f, 0.000f, 0.000f}, + {-0.577f, 0.577f, 0.577f, 0.000f, 0.000f}, + + { 0.000f, 0.000f, 1.000f, 0.000f, 0.000f}, + { 0.707f, 0.000f, 0.707f, 0.000f, 0.000f}, + { 1.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + { 0.707f, 0.000f, -0.707f, 0.000f, 0.000f}, + { 0.000f, 0.000f, -1.000f, 0.000f, 0.000f}, + {-0.707f, 0.000f, -0.707f, 0.000f, 0.000f}, + {-1.000f, 0.000f, 0.000f, 0.000f, 0.000f}, + {-0.707f, 0.000f, 0.707f, 0.000f, 0.000f}, + + { 0.577f, -0.577f, 0.577f, 0.000f, 0.000f}, + { 0.577f, -0.577f, -0.577f, 0.000f, 0.000f}, + {-0.577f, -0.577f, -0.577f, 0.000f, 0.000f}, + {-0.577f, -0.577f, 0.577f, 0.000f, 0.000f}, + + { 0.000f, -1.000f, 0.000f, 0.000f, 0.000f} +}; + +rw::uint16 IndexedLineListIndices[96] = { + 0,1, 0,2, 0,3, 0,4, 1,2, 2,3, 3,4, 4,1, + + 1,5, 1,6, 1,7, 2,7, 2,8, 2,9, 3,9, 3,10, 3,11, 4,11, 4,12, 4,5, + 5,6, 6,7, 7,8, 8,9, 9,10, 10,11, 11,12, 12,5, + 13,5, 13,6, 13,7, 14,7, 14,8, 14,9, 15,9, 15,10, 15,11, 16,11, 16,12, 16,5, + + 17,13, 17,14, 17,15, 17,16, 13,14, 14,15, 15,16, 16,13 +}; + +rw::RWDEVICE::Im3DVertex LineList[28]; +rw::RWDEVICE::Im3DVertex IndexedLineList[18]; + + +void +LineListCreate(void) +{ + for(int i = 0; i < 28; i++){ + LineList[i].setX(LineListData[i][0]); + LineList[i].setY(LineListData[i][1]); + LineList[i].setZ(LineListData[i][2]); + LineList[i].setU(LineListData[i][3]); + LineList[i].setV(LineListData[i][4]); + } +} + +void +LineListSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidRed; + rw::RGBA SolidColor2 = SolidWhite; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + } + + for(int i = 0; i < 28; i += 2){ + LineList[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + LineList[i+1].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + } +} + +void +LineListRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(LineList, 28, transform, transformFlags); + rw::im3d::RenderPrimitive(rw::PRIMTYPELINELIST); + rw::im3d::End(); +} + + +void +IndexedLineListCreate(void) +{ + for(int i = 0; i < 18; i++){ + IndexedLineList[i].setX(IndexedLineListData[i][0]); + IndexedLineList[i].setY(IndexedLineListData[i][1]); + IndexedLineList[i].setZ(IndexedLineListData[i][2]); + IndexedLineList[i].setU(IndexedLineListData[i][3]); + IndexedLineList[i].setV(IndexedLineListData[i][4]); + } +} + +void +IndexedLineListSetColor(bool white) +{ + int i; + rw::RGBA SolidColor1 = SolidRed; + rw::RGBA SolidColor2 = SolidGreen; + rw::RGBA SolidColor3 = SolidBlue; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + SolidColor3 = SolidWhite; + } + + IndexedLineList[0].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + for(i = 1; i < 5; i++) + IndexedLineList[i].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + for(; i < 13; i++) + IndexedLineList[i].setColor(SolidColor3.red, SolidColor3.green, + SolidColor3.blue, SolidColor3.alpha); + for(; i < 17; i++) + IndexedLineList[i].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + IndexedLineList[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); +} + +void +IndexedLineListRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(IndexedLineList, 18, transform, transformFlags); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPELINELIST, IndexedLineListIndices, 96); + rw::im3d::End(); +} diff --git a/vendor/librw/tools/im3d/main.cpp b/vendor/librw/tools/im3d/main.cpp new file mode 100644 index 0000000..4949d2e --- /dev/null +++ b/vendor/librw/tools/im3d/main.cpp @@ -0,0 +1,268 @@ +#include +#include +#include + +#include "im3d.h" + +rw::EngineOpenParams engineOpenParams; + +rw::RGBA ForegroundColor = { 200, 200, 200, 255 }; +rw::RGBA BackgroundColor = { 64, 64, 64, 0 }; + +rw::Camera *Camera; +rw::Charset *Charset; + +float TimeDelta; + +rw::Camera* +CreateCamera(void) +{ + Camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + assert(Camera); + + Camera->setNearPlane(0.1f); + Camera->setFarPlane(50.0f); + + return Camera; +} + +void +Initialize(void) +{ + sk::globals.windowtitle = "Im3D example"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +Initialize3D(void) +{ + if(!sk::InitRW()) + return false; + + Charset = rw::Charset::create(&ForegroundColor, &BackgroundColor); + + Camera = CreateCamera(); + + Im3DInitialize(); + + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); + + rw::Rect r; + r.x = 0; + r.y = 0; + r.w = sk::globals.width; + r.h = sk::globals.height; + sk::CameraSize(::Camera, &r, 0.5f, 4.0f/3.0f); + + return true; +} + +void +Terminate3D(void) +{ + Im3DTerminate(); + + if(Camera){ + Camera->destroy(); + Camera = nil; + } + + if(Charset){ + Charset->destroy(); + Charset = nil; + } + + sk::TerminateRW(); +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +void +DisplayOnScreenInfo(void) +{ +} + +void +Gui(void) +{ + static bool showWindow = true; + ImGui::Begin("Im2D", &showWindow); + + ImGui::RadioButton("Line-list", &Im3DPrimType, 0); + ImGui::RadioButton("Line-list indexed", &Im3DPrimType, 1); + ImGui::RadioButton("Poly-line", &Im3DPrimType, 2); + ImGui::RadioButton("Poly-line indexed", &Im3DPrimType, 3); + ImGui::RadioButton("Tri-list", &Im3DPrimType, 4); + ImGui::RadioButton("Tri-list indexed", &Im3DPrimType, 5); + ImGui::RadioButton("Tri-strip", &Im3DPrimType, 6); + ImGui::RadioButton("Tri-strip indexed", &Im3DPrimType, 7); + ImGui::RadioButton("Tri-fan", &Im3DPrimType, 8); + ImGui::RadioButton("Tri-fan indexed", &Im3DPrimType, 9); + + ImGui::NewLine(); + + ImGui::Checkbox("Textured", &Im3DTextured); + if(ImGui::Checkbox("Colored", &Im3DColored)){ + LineListSetColor(!Im3DColored); + IndexedLineListSetColor(!Im3DColored); + PolyLineSetColor(!Im3DColored); + IndexedPolyLineSetColor(!Im3DColored); + TriListSetColor(!Im3DColored); + IndexedTriListSetColor(!Im3DColored); + TriStripSetColor(!Im3DColored); + IndexedTriStripSetColor(!Im3DColored); + TriFanSetColor(!Im3DColored); + IndexedTriFanSetColor(!Im3DColored); + } + + ImGui::End(); +} + +void +Render(void) +{ + Camera->clear(&BackgroundColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + + Camera->beginUpdate(); + + ImGui_ImplRW_NewFrame(TimeDelta); + + Im3DRender(); + DisplayOnScreenInfo(); + + Gui(); + ImGui::EndFrame(); + ImGui::Render(); + + ImGui_ImplRW_RenderDrawLists(ImGui::GetDrawData()); + + Camera->endUpdate(); + + Camera->showRaster(0); +} + +void +Idle(float timeDelta) +{ + TimeDelta = timeDelta; + Render(); +} + +int MouseX, MouseY; +int MouseDeltaX, MouseDeltaY; +int MouseButtons; + +bool Rotate, Translate; + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +void +MouseBtn(sk::MouseState *mouse) +{ + MouseButtons = mouse->buttons; + Rotate = !!(MouseButtons&1); + Translate = !!(MouseButtons&4); +} + +void +MouseMove(sk::MouseState *mouse) +{ + MouseDeltaX = mouse->posx - MouseX; + MouseDeltaY = mouse->posy - MouseY; + MouseX = mouse->posx; + MouseY = mouse->posy; + if(Rotate) + Im3DRotate(MouseDeltaX, -MouseDeltaY); + if(Translate) + Im3DTranslateZ(-MouseDeltaY*0.1f); +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + MouseState *ms; + + ImGuiEventHandler(e, param); + ImGuiIO &io = ImGui::GetIO(); + + switch(e){ + case INITIALIZE: + Initialize(); + return EVENTPROCESSED; + case RWINITIALIZE: + return Initialize3D() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + Terminate3D(); + return EVENTPROCESSED; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case MOUSEBTN: + if(!io.WantCaptureMouse){ + ms = (MouseState*)param; + MouseBtn(ms); + }else + MouseButtons = 0; + return EVENTPROCESSED; + case MOUSEMOVE: + MouseMove((MouseState*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + if(::Camera){ + sk::CameraSize(::Camera, r, 0.5f, 4.0f/3.0f); + } + break; + case IDLE: + Idle(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/vendor/librw/tools/im3d/polyline.cpp b/vendor/librw/tools/im3d/polyline.cpp new file mode 100644 index 0000000..1a206b0 --- /dev/null +++ b/vendor/librw/tools/im3d/polyline.cpp @@ -0,0 +1,157 @@ +#include +#include + +#include "im3d.h" + +float PolyLineData[21][5] = { + { 0.000f, 1.000f, 1.000f, 0.500f, 1.000f}, + { 0.707f, 0.707f, 0.900f, 0.854f, 0.854f}, + { 1.000f, 0.000f, 0.800f, 1.000f, 0.500f}, + { 0.707f, -0.707f, 0.700f, 0.854f, 0.146f}, + { 0.000f, -1.000f, 0.600f, 0.500f, 0.000f}, + {-0.707f, -0.707f, 0.500f, 0.146f, 0.146f}, + {-1.000f, -0.000f, 0.400f, 0.000f, 0.500f}, + {-0.707f, 0.707f, 0.300f, 0.146f, 0.854f}, + + { 0.000f, 1.000f, 0.200f, 0.500f, 1.000f}, + { 0.707f, 0.707f, 0.100f, 0.854f, 0.854f}, + { 1.000f, 0.000f, 0.000f, 1.000f, 0.500f}, + { 0.707f, -0.707f, -0.100f, 0.854f, 0.146f}, + { 0.000f, -1.000f, -0.200f, 0.500f, 0.000f}, + {-0.707f, -0.707f, -0.300f, 0.146f, 0.146f}, + {-1.000f, -0.000f, -0.400f, 0.000f, 0.500f}, + {-0.707f, 0.707f, -0.500f, 0.146f, 0.854f}, + + { 0.000f, 1.000f, -0.600f, 0.500f, 1.000f}, + { 0.707f, 0.707f, -0.700f, 0.854f, 0.854f}, + { 1.000f, 0.000f, -0.800f, 1.000f, 0.500f}, + { 0.707f, -0.707f, -0.900f, 0.854f, 0.146f}, + { 0.000f, -1.000f, -1.000f, 0.500f, 0.000f} +}; + +float IndexedPolyLineData[8][5] = { + { 1.000f, 1.000f, 1.000f, 1.000f, 0.000f}, + {-1.000f, 1.000f, 1.000f, 1.000f, 1.000f}, + {-1.000f, -1.000f, 1.000f, 0.500f, 1.000f}, + { 1.000f, -1.000f, 1.000f, 0.500f, 0.000f}, + + { 1.000f, 1.000f, -1.000f, 0.500f, 0.000f}, + {-1.000f, 1.000f, -1.000f, 0.500f, 1.000f}, + {-1.000f, -1.000f, -1.000f, 0.000f, 1.000f}, + { 1.000f, -1.000f, -1.000f, 0.000f, 0.000f} +}; + +rw::uint16 IndexedPolyLineIndices[25] = { + 0, 1, 2, 3, 0, 2, 6, 5, 1, 3, 7, 4, 0, 5, 4, 6, 1, 4, 3, 6, 7, 5, 2, 7, 0 +}; + +rw::RWDEVICE::Im3DVertex PolyLine[21]; +rw::RWDEVICE::Im3DVertex IndexedPolyLine[8]; + + +void +PolyLineCreate(void) +{ + for(int i = 0; i < 21; i++){ + PolyLine[i].setX(PolyLineData[i][0]); + PolyLine[i].setY(PolyLineData[i][1]); + PolyLine[i].setZ(PolyLineData[i][2]); + PolyLine[i].setU(PolyLineData[i][3]); + PolyLine[i].setV(PolyLineData[i][4]); + } +} + +void +PolyLineSetColor(bool white) +{ + int i; + rw::RGBA SolidColor1 = SolidRed; + rw::RGBA SolidColor2 = SolidGreen; + rw::RGBA SolidColor3 = SolidBlue; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + SolidColor3 = SolidWhite; + } + + for(i = 0; i < 7; i++) + PolyLine[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + for(; i < 14; i++) + PolyLine[i].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + for(; i < 21; i++) + PolyLine[i].setColor(SolidColor3.red, SolidColor3.green, + SolidColor3.blue, SolidColor3.alpha); +} + +void +PolyLineRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(PolyLine, 21, transform, transformFlags); + rw::im3d::RenderPrimitive(rw::PRIMTYPEPOLYLINE); + rw::im3d::End(); +} + + +void +IndexedPolyLineCreate(void) +{ + for(int i = 0; i < 8; i++){ + IndexedPolyLine[i].setX(IndexedPolyLineData[i][0]); + IndexedPolyLine[i].setY(IndexedPolyLineData[i][1]); + IndexedPolyLine[i].setZ(IndexedPolyLineData[i][2]); + IndexedPolyLine[i].setU(IndexedPolyLineData[i][3]); + IndexedPolyLine[i].setV(IndexedPolyLineData[i][4]); + } +} + +void +IndexedPolyLineSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidRed; + rw::RGBA SolidColor2 = SolidYellow; + rw::RGBA SolidColor3 = SolidBlack; + rw::RGBA SolidColor4 = SolidPurple; + rw::RGBA SolidColor5 = SolidGreen; + rw::RGBA SolidColor6 = SolidCyan; + rw::RGBA SolidColor7 = SolidBlue; + rw::RGBA SolidColor8 = SolidWhite; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + SolidColor3 = SolidWhite; + SolidColor4 = SolidWhite; + SolidColor5 = SolidWhite; + SolidColor6 = SolidWhite; + SolidColor7 = SolidWhite; + SolidColor8 = SolidWhite; + } + + IndexedPolyLine[0].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + IndexedPolyLine[1].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + IndexedPolyLine[2].setColor(SolidColor3.red, SolidColor3.green, + SolidColor3.blue, SolidColor3.alpha); + IndexedPolyLine[3].setColor(SolidColor4.red, SolidColor4.green, + SolidColor4.blue, SolidColor4.alpha); + IndexedPolyLine[4].setColor(SolidColor5.red, SolidColor5.green, + SolidColor5.blue, SolidColor5.alpha); + IndexedPolyLine[5].setColor(SolidColor6.red, SolidColor6.green, + SolidColor6.blue, SolidColor6.alpha); + IndexedPolyLine[6].setColor(SolidColor7.red, SolidColor7.green, + SolidColor7.blue, SolidColor7.alpha); + IndexedPolyLine[7].setColor(SolidColor8.red, SolidColor8.green, + SolidColor8.blue, SolidColor8.alpha); +} + +void +IndexedPolyLineRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(IndexedPolyLine, 8, transform, transformFlags); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPEPOLYLINE, IndexedPolyLineIndices, 25); + rw::im3d::End(); +} diff --git a/vendor/librw/tools/im3d/trifan.cpp b/vendor/librw/tools/im3d/trifan.cpp new file mode 100644 index 0000000..3bef64b --- /dev/null +++ b/vendor/librw/tools/im3d/trifan.cpp @@ -0,0 +1,152 @@ +#include +#include + +#include "im3d.h" + +float TriFanData[34][5] = { + { 0.000f, 0.000f, -1.000f, 0.500f, 0.500f}, + + { 0.000f, 1.000f, 0.000f, 0.500f, 1.000f}, + { 0.383f, 0.924f, 0.000f, 0.691f, 0.962f}, + { 0.707f, 0.707f, 0.000f, 0.854f, 0.854f}, + { 0.924f, 0.383f, 0.000f, 0.962f, 0.691f}, + { 1.000f, 0.000f, 0.000f, 1.000f, 0.500f}, + { 0.924f, -0.383f, 0.000f, 0.962f, 0.309f}, + { 0.707f, -0.707f, 0.000f, 0.854f, 0.146f}, + { 0.383f, -0.924f, 0.000f, 0.691f, 0.038f}, + { 0.000f, -1.000f, 0.000f, 0.500f, 0.000f}, + {-0.383f, -0.924f, 0.000f, 0.309f, 0.038f}, + {-0.707f, -0.707f, 0.000f, 0.146f, 0.146f}, + {-0.924f, -0.383f, 0.000f, 0.038f, 0.309f}, + {-1.000f, -0.000f, 0.000f, 0.000f, 0.500f}, + {-0.924f, 0.383f, 0.000f, 0.038f, 0.691f}, + {-0.707f, 0.707f, 0.000f, 0.146f, 0.854f}, + {-0.383f, 0.924f, 0.000f, 0.309f, 0.962f}, + + { 0.000f, 1.000f, 0.000f, 0.500f, 1.000f}, + + {-0.383f, 0.924f, 0.000f, 0.309f, 0.962f}, + {-0.707f, 0.707f, 0.000f, 0.146f, 0.854f}, + {-0.924f, 0.383f, 0.000f, 0.038f, 0.691f}, + {-1.000f, -0.000f, 0.000f, 0.000f, 0.500f}, + {-0.924f, -0.383f, 0.000f, 0.038f, 0.309f}, + {-0.707f, -0.707f, 0.000f, 0.146f, 0.146f}, + {-0.383f, -0.924f, 0.000f, 0.309f, 0.038f}, + { 0.000f, -1.000f, 0.000f, 0.500f, 0.000f}, + { 0.383f, -0.924f, 0.000f, 0.691f, 0.038f}, + { 0.707f, -0.707f, 0.000f, 0.854f, 0.146f}, + { 0.924f, -0.383f, 0.000f, 0.962f, 0.309f}, + { 1.000f, 0.000f, 0.000f, 1.000f, 0.500f}, + { 0.924f, 0.383f, 0.000f, 0.962f, 0.691f}, + { 0.707f, 0.707f, 0.000f, 0.854f, 0.854f}, + { 0.383f, 0.924f, 0.000f, 0.691f, 0.962f}, + { 0.000f, 1.000f, 0.000f, 0.500f, 1.000f} +}; + +float IndexedTriFanData[17][5] = { + /* top */ + { 0.000f, 0.000f, -1.000f, 0.500f, 0.500f}, + /* circle */ + { 0.000f, 1.000f, 0.000f, 0.500f, 1.000f}, + { 0.383f, 0.924f, 0.000f, 0.691f, 0.962f}, + { 0.707f, 0.707f, 0.000f, 0.854f, 0.854f}, + { 0.924f, 0.383f, 0.000f, 0.962f, 0.691f}, + { 1.000f, 0.000f, 0.000f, 1.000f, 0.500f}, + { 0.924f, -0.383f, 0.000f, 0.962f, 0.309f}, + { 0.707f, -0.707f, 0.000f, 0.854f, 0.146f}, + { 0.383f, -0.924f, 0.000f, 0.691f, 0.038f}, + { 0.000f, -1.000f, 0.000f, 0.500f, 0.000f}, + {-0.383f, -0.924f, 0.000f, 0.309f, 0.038f}, + {-0.707f, -0.707f, 0.000f, 0.146f, 0.146f}, + {-0.924f, -0.383f, 0.000f, 0.038f, 0.309f}, + {-1.000f, -0.000f, 0.000f, 0.000f, 0.500f}, + {-0.924f, 0.383f, 0.000f, 0.038f, 0.691f}, + {-0.707f, 0.707f, 0.000f, 0.146f, 0.854f}, + {-0.383f, 0.924f, 0.000f, 0.309f, 0.962f} +}; + +rw::uint16 IndexedTriFanIndices[34] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 +}; + +rw::RWDEVICE::Im3DVertex TriFan[34]; +rw::RWDEVICE::Im3DVertex IndexedTriFan[17]; + + +void +TriFanCreate(void) +{ + for(int i = 0; i < 34; i++){ + TriFan[i].setX(TriFanData[i][0]); + TriFan[i].setY(TriFanData[i][1]); + TriFan[i].setZ(TriFanData[i][2]); + TriFan[i].setU(TriFanData[i][3]); + TriFan[i].setV(TriFanData[i][4]); + } +} + +void +TriFanSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidYellow; + rw::RGBA SolidColor2 = SolidBlue; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + } + + TriFan[0].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + for(int i = 1; i < 34; i++) + TriFan[i].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); +} + +void +TriFanRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(TriFan, 34, transform, transformFlags); + rw::im3d::RenderPrimitive(rw::PRIMTYPETRIFAN); + rw::im3d::End(); +} + + +void +IndexedTriFanCreate(void) +{ + for(int i = 0; i < 17; i++){ + IndexedTriFan[i].setX(IndexedTriFanData[i][0]); + IndexedTriFan[i].setY(IndexedTriFanData[i][1]); + IndexedTriFan[i].setZ(IndexedTriFanData[i][2]); + IndexedTriFan[i].setU(IndexedTriFanData[i][3]); + IndexedTriFan[i].setV(IndexedTriFanData[i][4]); + } +} + +void +IndexedTriFanSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidGreen; + rw::RGBA SolidColor2 = SolidBlack; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + } + + IndexedTriFan[0].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + for(int i = 1; i < 17; i++) + IndexedTriFan[i].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); +} + +void +IndexedTriFanRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(IndexedTriFan, 17, transform, transformFlags); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRIFAN, IndexedTriFanIndices, 34); + rw::im3d::End(); +} diff --git a/vendor/librw/tools/im3d/trilist.cpp b/vendor/librw/tools/im3d/trilist.cpp new file mode 100644 index 0000000..6fa4014 --- /dev/null +++ b/vendor/librw/tools/im3d/trilist.cpp @@ -0,0 +1,202 @@ +#include +#include + +#include "im3d.h" + +float TriListData[36][5] = { + /* front */ + { 1.000f, 1.000f, 1.000f, 1.000f, 1.000f}, + {-1.000f, -1.000f, 1.000f, 0.000f, 0.000f}, + { 1.000f, -1.000f, 1.000f, 1.000f, 0.000f}, + { 1.000f, 1.000f, 1.000f, 1.000f, 1.000f}, + {-1.000f, 1.000f, 1.000f, 0.000f, 1.000f}, + {-1.000f, -1.000f, 1.000f, 0.000f, 0.000f}, + /* back */ + {-1.000f, -1.000f, -1.000f, 1.000f, 1.000f}, + {-1.000f, 1.000f, -1.000f, 1.000f, 0.000f}, + { 1.000f, 1.000f, -1.000f, 0.000f, 0.000f}, + {-1.000f, -1.000f, -1.000f, 1.000f, 1.000f}, + { 1.000f, 1.000f, -1.000f, 0.000f, 0.000f}, + { 1.000f, -1.000f, -1.000f, 0.000f, 1.000f}, + /* top */ + { 1.000f, 1.000f, 1.000f, 1.000f, 1.000f}, + { 1.000f, 1.000f, -1.000f, 1.000f, 0.000f}, + {-1.000f, 1.000f, -1.000f, 0.000f, 0.000f}, + { 1.000f, 1.000f, 1.000f, 1.000f, 1.000f}, + {-1.000f, 1.000f, -1.000f, 0.000f, 0.000f}, + {-1.000f, 1.000f, 1.000f, 0.000f, 1.000f}, + /* bottom */ + {-1.000f, -1.000f, -1.000f, 1.000f, 1.000f}, + { 1.000f, -1.000f, 1.000f, 0.000f, 0.000f}, + {-1.000f, -1.000f, 1.000f, 1.000f, 0.000f}, + {-1.000f, -1.000f, -1.000f, 1.000f, 1.000f}, + { 1.000f, -1.000f, -1.000f, 0.000f, 1.000f}, + { 1.000f, -1.000f, 1.000f, 0.000f, 0.000f}, + /* left */ + {-1.000f, -1.000f, -1.000f, 1.000f, 1.000f}, + {-1.000f, 1.000f, 1.000f, 0.000f, 0.000f}, + {-1.000f, 1.000f, -1.000f, 1.000f, 0.000f}, + {-1.000f, -1.000f, -1.000f, 1.000f, 1.000f}, + {-1.000f, -1.000f, 1.000f, 0.000f, 1.000f}, + {-1.000f, 1.000f, 1.000f, 0.000f, 0.000f}, + /* right */ + { 1.000f, 1.000f, 1.000f, 1.000f, 1.000f}, + { 1.000f, -1.000f, 1.000f, 1.000f, 0.000f}, + { 1.000f, -1.000f, -1.000f, 0.000f, 0.000f}, + { 1.000f, 1.000f, 1.000f, 1.000f, 1.000f}, + { 1.000f, -1.000f, -1.000f, 0.000f, 0.000f}, + { 1.000f, 1.000f, -1.000f, 0.000f, 1.000f} +}; + +float IndexedTriListData[8][5] = { + { 1.000f, 1.000f, 1.000f, 1.000f, 0.000f}, + {-1.000f, 1.000f, 1.000f, 1.000f, 1.000f}, + {-1.000f, -1.000f, 1.000f, 0.500f, 1.000f}, + { 1.000f, -1.000f, 1.000f, 0.500f, 0.000f}, + + { 1.000f, 1.000f, -1.000f, 0.500f, 0.000f}, + {-1.000f, 1.000f, -1.000f, 0.500f, 1.000f}, + {-1.000f, -1.000f, -1.000f, 0.000f, 1.000f}, + { 1.000f, -1.000f, -1.000f, 0.000f, 0.000f} +}; + +rw::uint16 IndexedTriListIndices[36] = { + /* front */ + 0, 1, 3, 1, 2, 3, + /* back */ + 7, 5, 4, 5, 7, 6, + /* left */ + 6, 2, 1, 1, 5, 6, + /* right */ + 0, 3, 4, 4, 3, 7, + /* top */ + 1, 0, 4, 1, 4, 5, + /* bottom */ + 2, 6, 3, 6, 7, 3 +}; + +rw::RWDEVICE::Im3DVertex TriList[36]; +rw::RWDEVICE::Im3DVertex IndexedTriList[8]; + + +void +TriListCreate(void) +{ + for(int i = 0; i < 36; i++){ + TriList[i].setX(TriListData[i][0]); + TriList[i].setY(TriListData[i][1]); + TriList[i].setZ(TriListData[i][2]); + TriList[i].setU(TriListData[i][3]); + TriList[i].setV(TriListData[i][4]); + } +} + +void +TriListSetColor(bool white) +{ + int i; + rw::RGBA SolidColor1 = SolidRed; + rw::RGBA SolidColor2 = SolidBlue; + rw::RGBA SolidColor3 = SolidGreen; + rw::RGBA SolidColor4 = SolidYellow; + rw::RGBA SolidColor5 = SolidCyan; + rw::RGBA SolidColor6 = SolidPurple; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + SolidColor3 = SolidWhite; + SolidColor4 = SolidWhite; + SolidColor5 = SolidWhite; + SolidColor6 = SolidWhite; + } + + for(i = 0; i < 6; i++) + TriList[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + for(; i < 12; i++) + TriList[i].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + for(; i < 18; i++) + TriList[i].setColor(SolidColor3.red, SolidColor3.green, + SolidColor3.blue, SolidColor3.alpha); + for(; i < 24; i++) + TriList[i].setColor(SolidColor4.red, SolidColor4.green, + SolidColor4.blue, SolidColor4.alpha); + for(; i < 30; i++) + TriList[i].setColor(SolidColor5.red, SolidColor5.green, + SolidColor5.blue, SolidColor5.alpha); + for(; i < 36; i++) + TriList[i].setColor(SolidColor6.red, SolidColor6.green, + SolidColor6.blue, SolidColor6.alpha); +} + +void +TriListRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(TriList, 36, transform, transformFlags); + rw::im3d::RenderPrimitive(rw::PRIMTYPETRILIST); + rw::im3d::End(); +} + + +void +IndexedTriListCreate(void) +{ + for(int i = 0; i < 8; i++){ + IndexedTriList[i].setX(IndexedTriListData[i][0]); + IndexedTriList[i].setY(IndexedTriListData[i][1]); + IndexedTriList[i].setZ(IndexedTriListData[i][2]); + IndexedTriList[i].setU(IndexedTriListData[i][3]); + IndexedTriList[i].setV(IndexedTriListData[i][4]); + } +} + +void +IndexedTriListSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidRed; + rw::RGBA SolidColor2 = SolidYellow; + rw::RGBA SolidColor3 = SolidBlack; + rw::RGBA SolidColor4 = SolidPurple; + rw::RGBA SolidColor5 = SolidGreen; + rw::RGBA SolidColor6 = SolidCyan; + rw::RGBA SolidColor7 = SolidBlue; + rw::RGBA SolidColor8 = SolidWhite; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + SolidColor3 = SolidWhite; + SolidColor4 = SolidWhite; + SolidColor5 = SolidWhite; + SolidColor6 = SolidWhite; + SolidColor7 = SolidWhite; + SolidColor8 = SolidWhite; + } + + IndexedTriList[0].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + IndexedTriList[1].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + IndexedTriList[2].setColor(SolidColor3.red, SolidColor3.green, + SolidColor3.blue, SolidColor3.alpha); + IndexedTriList[3].setColor(SolidColor4.red, SolidColor4.green, + SolidColor4.blue, SolidColor4.alpha); + IndexedTriList[4].setColor(SolidColor5.red, SolidColor5.green, + SolidColor5.blue, SolidColor5.alpha); + IndexedTriList[5].setColor(SolidColor6.red, SolidColor6.green, + SolidColor6.blue, SolidColor6.alpha); + IndexedTriList[6].setColor(SolidColor7.red, SolidColor7.green, + SolidColor7.blue, SolidColor7.alpha); + IndexedTriList[7].setColor(SolidColor8.red, SolidColor8.green, + SolidColor8.blue, SolidColor8.alpha); +} + +void +IndexedTriListRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(IndexedTriList, 8, transform, transformFlags); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, IndexedTriListIndices, 36); + rw::im3d::End(); +} diff --git a/vendor/librw/tools/im3d/tristrip.cpp b/vendor/librw/tools/im3d/tristrip.cpp new file mode 100644 index 0000000..93c1496 --- /dev/null +++ b/vendor/librw/tools/im3d/tristrip.cpp @@ -0,0 +1,169 @@ +#include +#include + +#include "im3d.h" + +float TriStripData[36][5] = { + { 0.000f, 1.000f, -1.000f, 0.000f, 0.000f}, + { 0.000f, 1.000f, 1.000f, 0.000f, 1.000f}, + + { 0.707f, 0.707f, -1.000f, 0.125f, 0.000f}, + { 0.707f, 0.707f, 1.000f, 0.125f, 1.000f}, + + { 1.000f, 0.000f, -1.000f, 0.250f, 0.000f}, + { 1.000f, 0.000f, 1.000f, 0.250f, 1.000f}, + + { 0.707f, -0.707f, -1.000f, 0.375f, 0.000f}, + { 0.707f, -0.707f, 1.000f, 0.375f, 1.000f}, + + { 0.000f, -1.000f, -1.000f, 0.500f, 0.000f}, + { 0.000f, -1.000f, 1.000f, 0.500f, 1.000f}, + + {-0.707f, -0.707f, -1.000f, 0.625f, 0.000f}, + {-0.707f, -0.707f, 1.000f, 0.625f, 1.000f}, + + {-1.000f, -0.000f, -1.000f, 0.750f, 0.000f}, + {-1.000f, -0.000f, 1.000f, 0.750f, 1.000f}, + + {-0.707f, 0.707f, -1.000f, 0.875f, 0.000f}, + {-0.707f, 0.707f, 1.000f, 0.875f, 1.000f}, + + { 0.000f, 1.000f, -1.000f, 1.000f, 0.000f}, + { 0.000f, 1.000f, 1.000f, 1.000f, 1.000f}, + + { 0.000f, 1.000f, 1.000f, 0.000f, 0.000f}, + { 0.000f, 1.000f, -1.000f, 0.000f, 1.000f}, + + { 0.707f, 0.707f, 1.000f, 0.125f, 0.000f}, + { 0.707f, 0.707f, -1.000f, 0.125f, 1.000f}, + + { 1.000f, 0.000f, 1.000f, 0.250f, 0.000f}, + { 1.000f, 0.000f, -1.000f, 0.250f, 1.000f}, + + { 0.707f, -0.707f, 1.000f, 0.375f, 0.000f}, + { 0.707f, -0.707f, -1.000f, 0.375f, 1.000f}, + + { 0.000f, -1.000f, 1.000f, 0.500f, 0.000f}, + { 0.000f, -1.000f, -1.000f, 0.500f, 1.000f}, + + {-0.707f, -0.707f, 1.000f, 0.625f, 0.000f}, + {-0.707f, -0.707f, -1.000f, 0.625f, 1.000f}, + + {-1.000f, -0.000f, 1.000f, 0.750f, 0.000f}, + {-1.000f, -0.000f, -1.000f, 0.750f, 1.000f}, + + {-0.707f, 0.707f, 1.000f, 0.875f, 0.000f}, + {-0.707f, 0.707f, -1.000f, 0.875f, 1.000f}, + + { 0.000f, 1.000f, 1.000f, 1.000f, 0.000f}, + { 0.000f, 1.000f, -1.000f, 1.000f, 1.000f} +}; + +float IndexedTriStripData[16][5] = { + { 0.000f, 1.000f, 1.000f, 0.000f, 0.000f}, + { 0.707f, 0.707f, 1.000f, 0.250f, 0.000f}, + { 1.000f, 0.000f, 1.000f, 0.500f, 0.000f}, + { 0.707f, -0.707f, 1.000f, 0.750f, 0.000f}, + { 0.000f, -1.000f, 1.000f, 1.000f, 0.000f}, + {-0.707f, -0.707f, 1.000f, 0.750f, 0.000f}, + {-1.000f, -0.000f, 1.000f, 0.500f, 0.000f}, + {-0.707f, 0.707f, 1.000f, 0.250f, 0.000f}, + + { 0.000f, 1.000f, -1.000f, 0.000f, 1.000f}, + { 0.707f, 0.707f, -1.000f, 0.250f, 1.000f}, + { 1.000f, 0.000f, -1.000f, 0.500f, 1.000f}, + { 0.707f, -0.707f, -1.000f, 0.750f, 1.000f}, + { 0.000f, -1.000f, -1.000f, 1.000f, 1.000f}, + {-0.707f, -0.707f, -1.000f, 0.750f, 1.000f}, + {-1.000f, -0.000f, -1.000f, 0.500f, 1.000f}, + {-0.707f, 0.707f, -1.000f, 0.250f, 1.000f}, +}; + +rw::uint16 IndexedTriStripIndices[36] = { + 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15, 0, 8, + 8, 0, 9, 1, 10, 2, 11, 3, 12, 4, 13, 5, 14, 6, 15, 7, 8, 0 +}; + +rw::RWDEVICE::Im3DVertex TriStrip[36]; +rw::RWDEVICE::Im3DVertex IndexedTriStrip[16]; + + +void +TriStripCreate(void) +{ + for(int i = 0; i < 36; i++){ + TriStrip[i].setX(TriStripData[i][0]); + TriStrip[i].setY(TriStripData[i][1]); + TriStrip[i].setZ(TriStripData[i][2]); + TriStrip[i].setU(TriStripData[i][3]); + TriStrip[i].setV(TriStripData[i][4]); + } +} + +void +TriStripSetColor(bool white) +{ + rw::RGBA SolidColor1 = SolidRed; + rw::RGBA SolidColor2 = SolidYellow; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + } + + for(int i = 0; i < 36; i += 2){ + TriStrip[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + TriStrip[i+1].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); + } +} + +void +TriStripRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(TriStrip, 36, transform, transformFlags); + rw::im3d::RenderPrimitive(rw::PRIMTYPETRISTRIP); + rw::im3d::End(); +} + + +void +IndexedTriStripCreate(void) +{ + for(int i = 0; i < 16; i++){ + IndexedTriStrip[i].setX(IndexedTriStripData[i][0]); + IndexedTriStrip[i].setY(IndexedTriStripData[i][1]); + IndexedTriStrip[i].setZ(IndexedTriStripData[i][2]); + IndexedTriStrip[i].setU(IndexedTriStripData[i][3]); + IndexedTriStrip[i].setV(IndexedTriStripData[i][4]); + } +} + +void +IndexedTriStripSetColor(bool white) +{ + int i; + rw::RGBA SolidColor1 = SolidBlue; + rw::RGBA SolidColor2 = SolidGreen; + + if(white){ + SolidColor1 = SolidWhite; + SolidColor2 = SolidWhite; + } + + for(i = 0; i < 8; i++) + IndexedTriStrip[i].setColor(SolidColor1.red, SolidColor1.green, + SolidColor1.blue, SolidColor1.alpha); + for(; i < 16; i++) + IndexedTriStrip[i].setColor(SolidColor2.red, SolidColor2.green, + SolidColor2.blue, SolidColor2.alpha); +} + +void +IndexedTriStripRender(rw::Matrix *transform, rw::uint32 transformFlags) +{ + rw::im3d::Transform(IndexedTriStrip, 16, transform, transformFlags); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRISTRIP, IndexedTriStripIndices, 36); + rw::im3d::End(); +} diff --git a/vendor/librw/tools/imguitest/CMakeLists.txt b/vendor/librw/tools/imguitest/CMakeLists.txt new file mode 100644 index 0000000..4f2e31d --- /dev/null +++ b/vendor/librw/tools/imguitest/CMakeLists.txt @@ -0,0 +1,17 @@ +add_executable(imguitest WIN32 + main.cpp +) + +target_link_libraries(imguitest + PUBLIC + librw::skeleton + librw::librw +) + +if(LIBRW_INSTALL) + install(TARGETS imguitest + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) +endif() + +librw_platform_target(imguitest INSTALL) diff --git a/vendor/librw/tools/imguitest/main.cpp b/vendor/librw/tools/imguitest/main.cpp new file mode 100644 index 0000000..749c06f --- /dev/null +++ b/vendor/librw/tools/imguitest/main.cpp @@ -0,0 +1,175 @@ +#include +#include +#include + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +struct SceneGlobals { + rw::World *world; + rw::Camera *camera; +} Scene; +rw::EngineOpenParams engineOpenParams; + +void +Init(void) +{ + sk::globals.windowtitle = "ImGui test"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +bool +InitRW(void) +{ +// rw::platform = rw::PLATFORM_D3D8; + if(!sk::InitRW()) + return false; + + Scene.world = rw::World::create(); + + rw::Light *ambient = rw::Light::create(rw::Light::AMBIENT); + ambient->setColor(0.2f, 0.2f, 0.2f); + Scene.world->addLight(ambient); + + rw::V3d xaxis = { 1.0f, 0.0f, 0.0f }; + rw::Light *direct = rw::Light::create(rw::Light::DIRECTIONAL); + direct->setColor(0.8f, 0.8f, 0.8f); + direct->setFrame(rw::Frame::create()); + direct->getFrame()->rotate(&xaxis, 180.0f, rw::COMBINEREPLACE); + Scene.world->addLight(direct); + + Scene.camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + Scene.world->addCamera(Scene.camera); + + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); + + return true; +} + +void +Draw(float timeDelta) +{ + static bool show_demo_window = true; + static bool show_another_window = false; + static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + rw::RGBA clearcol = rw::makeRGBA(clear_color.x*255, clear_color.y*255, clear_color.z*255, clear_color.w*255); + Scene.camera->clear(&clearcol, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + Scene.camera->beginUpdate(); + + ImGui_ImplRW_NewFrame(timeDelta); + + // 1. Show a simple window. + // Tip: if we don't call ImGui::Begin()/ImGui::End() the widgets automatically appears in a window called "Debug". + { + static float f = 0.0f; + ImGui::Text("Hello, world!"); // Some text (you can use a format string too) + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float as a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats as a color + if(ImGui::Button("Demo Window")) // Use buttons to toggle our bools. We could use Checkbox() as well. + show_demo_window ^= 1; + if(ImGui::Button("Another Window")) + show_another_window ^= 1; + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + } + + // 2. Show another simple window. In most cases you will use an explicit Begin/End pair to name the window. + if(show_another_window){ + ImGui::Begin("Another Window", &show_another_window); + ImGui::Text("Hello from another window!"); + ImGui::End(); + } + + // 3. Show the ImGui demo window. Most of the sample code is in ImGui::ShowDemoWindow(). + if(show_demo_window){ + ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver); // Normally user code doesn't need/want to call this because positions are saved in .ini file anyway. Here we just want to make the demo initial state a bit more friendly! + ImGui::ShowDemoWindow(&show_demo_window); + } + + + ImGui::EndFrame(); + ImGui::Render(); + + ImGui_ImplRW_RenderDrawLists(ImGui::GetDrawData()); + + Scene.camera->endUpdate(); + Scene.camera->showRaster(0); +} + + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + + ImGuiEventHandler(e, param); + + switch(e){ + case INITIALIZE: + Init(); + return EVENTPROCESSED; + case RWINITIALIZE: + return ::InitRW() ? EVENTPROCESSED : EVENTERROR; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + // TODO: set aspect ratio + if(Scene.camera) + sk::CameraSize(Scene.camera, r); + break; + case IDLE: + Draw(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/vendor/librw/tools/lights/CMakeLists.txt b/vendor/librw/tools/lights/CMakeLists.txt new file mode 100644 index 0000000..c51a2e3 --- /dev/null +++ b/vendor/librw/tools/lights/CMakeLists.txt @@ -0,0 +1,18 @@ +add_executable(lights WIN32 + main.cpp + lights.cpp +) + +target_link_libraries(lights + PRIVATE + librw::skeleton + librw::librw +) + +add_custom_command( + TARGET lights POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory "$/files" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/checker.dff" "$" +) + +librw_platform_target(lights) diff --git a/vendor/librw/tools/lights/checker.dff b/vendor/librw/tools/lights/checker.dff new file mode 100644 index 0000000..f955e8c Binary files /dev/null and b/vendor/librw/tools/lights/checker.dff differ diff --git a/vendor/librw/tools/lights/lights.cpp b/vendor/librw/tools/lights/lights.cpp new file mode 100644 index 0000000..78cdfc1 --- /dev/null +++ b/vendor/librw/tools/lights/lights.cpp @@ -0,0 +1,463 @@ +#include +#include + +#include "main.h" + +rw::Light *BaseAmbientLight; +bool BaseAmbientLightOn; + +rw::Light *CurrentLight; +rw::Light *AmbientLight; +rw::Light *PointLight; +rw::Light *DirectLight; +rw::Light *SpotLight; +rw::Light *SpotSoftLight; + +float LightRadius = 100.0f; +float LightConeAngle = 45.0f; +rw::RGBAf LightColor = { 1.0f, 1.0f, 1.0f, 1.0f }; + +rw::RGBA LightSolidColor = { 255, 255, 0, 255 }; +bool LightOn = true; +bool LightDrawOn = true; +rw::V3d LightPos = {0.0f, 0.0f, 75.0f}; +rw::int32 LightTypeIndex = 1; + +rw::BBox RoomBBox; + +rw::Light* +CreateBaseAmbientLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::AMBIENT); + assert(light); + light->setColor(0.5f, 0.5f, 0.5f); + return light; +} + +rw::Light* +CreateAmbientLight(void) +{ + return rw::Light::create(rw::Light::AMBIENT); +} + +rw::Light* +CreateDirectLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::DIRECTIONAL); + assert(light); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 45.0f, rw::COMBINEREPLACE); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + return light; +} + +rw::Light* +CreatePointLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::POINT); + assert(light); + light->radius = LightRadius; + rw::Frame *frame = rw::Frame::create(); + assert(frame); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEREPLACE); + light->setFrame(frame); + return light; +} + +rw::Light* +CreateSpotLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::SPOT); + assert(light); + light->radius = LightRadius; + light->setAngle(LightConeAngle/180.0f*M_PI); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 45.0f, rw::COMBINEREPLACE); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + return light; +} + +rw::Light* +CreateSpotSoftLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::SOFTSPOT); + assert(light); + light->radius = LightRadius; + light->setAngle(LightConeAngle/180.0f*M_PI); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 45.0f, rw::COMBINEREPLACE); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + return light; +} + +void +DestroyLight(rw::Light **light) +{ + if(*light == nil) + return; + rw::World *world = (*light)->world; + if(world) + world->removeLight(*light); + rw::Frame *frame = (*light)->getFrame(); + if(frame){ + (*light)->setFrame(nil); + frame->destroy(); + } + + (*light)->destroy(); + *light = nil; +} + +void +LightsDestroy(void) +{ + DestroyLight(&SpotSoftLight); + DestroyLight(&SpotLight); + DestroyLight(&PointLight); + DestroyLight(&DirectLight); + DestroyLight(&AmbientLight); + DestroyLight(&BaseAmbientLight); +} + +void +LightsUpdate(void) +{ + static rw::int32 oldLightTypeIndex = -1; + + // Switch to a different light + if((LightOn && oldLightTypeIndex != LightTypeIndex) || CurrentLight == nil){ + oldLightTypeIndex = LightTypeIndex; + + // remove first + if(CurrentLight) + CurrentLight->world->removeLight(CurrentLight); + + switch(LightTypeIndex){ + case 0: CurrentLight = AmbientLight; break; + case 1: CurrentLight = PointLight; break; + case 2: CurrentLight = DirectLight; break; + case 3: CurrentLight = SpotLight; break; + case 4: CurrentLight = SpotSoftLight; break; + } + World->addLight(CurrentLight); + } + + if(CurrentLight){ + CurrentLight->setColor(LightColor.red, LightColor.green, LightColor.blue); + CurrentLight->radius = LightRadius; + CurrentLight->setAngle(LightConeAngle / 180.0f * M_PI); + } + + // Remove light from world if not used + if(!LightOn && CurrentLight){ + CurrentLight->world->removeLight(CurrentLight); + CurrentLight = nil; + } +} + +#define POINT_LIGHT_RADIUS_FACTOR 0.05f + +void +DrawPointLight(void) +{ + enum { NUMVERTS = 50 }; + rw::RWDEVICE::Im3DVertex shape[NUMVERTS]; + rw::int32 i; + rw::V3d point; + + rw::V3d *pos = &CurrentLight->getFrame()->getLTM()->pos; + for(i = 0; i < NUMVERTS; i++){ + point.x = pos->x + + cosf(i/(NUMVERTS/2.0f) * M_PI) * LightRadius * POINT_LIGHT_RADIUS_FACTOR; + point.y = pos->y + + sinf(i/(NUMVERTS/2.0f) * M_PI) * LightRadius * POINT_LIGHT_RADIUS_FACTOR; + point.z = pos->z; + + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, LightSolidColor.alpha); + shape[i].setX(point.x); + shape[i].setY(point.y); + shape[i].setZ(point.z); + } + + rw::im3d::Transform(shape, NUMVERTS, nil, rw::im3d::ALLOPAQUE); + rw::im3d::RenderPrimitive(rw::PRIMTYPEPOLYLINE); + rw::im3d::RenderLine(NUMVERTS-1, 0); + rw::im3d::End(); +} + +void +DrawCone(float coneAngle, float coneSize, float coneRatio) +{ + enum { NUMVERTS = 10 }; + rw::RWDEVICE::Im3DVertex shape[NUMVERTS+1]; + rw::int16 indices[NUMVERTS*3]; + rw::int32 i; + + rw::Matrix *matrix = CurrentLight->getFrame()->getLTM(); + rw::V3d *pos = &matrix->pos; + + // cone + for(i = 1; i < NUMVERTS+1; i++){ + float cosValue = cosf(i/(NUMVERTS/2.0f) * M_PI) * + sinf(coneAngle/180.0f*M_PI); + float sinValue = sinf(i/(NUMVERTS/2.0f) * M_PI) * + sinf(coneAngle/180.0f*M_PI); + + float coneAngleD = cosf(coneAngle/180.0f*M_PI); + + rw::V3d up = rw::scale(matrix->up, sinValue*coneSize); + rw::V3d right = rw::scale(matrix->right, cosValue*coneSize); + rw::V3d at = rw::scale(matrix->at, coneAngleD*coneSize*coneRatio); + + shape[i].setX(pos->x + at.x + up.x + right.x); + shape[i].setY(pos->y + at.y + up.y + right.y); + shape[i].setZ(pos->z + at.z + up.z + right.z); + } + + for(i = 0; i < NUMVERTS; i++){ + indices[i*3 + 0] = 0; + indices[i*3 + 1] = i+2; + indices[i*3 + 2] = i+1; + } + indices[NUMVERTS*3-2] = 1; + + for(i = 0; i < NUMVERTS+1; i++) + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, 128); + + shape[0].setX(pos->x); + shape[0].setY(pos->y); + shape[0].setZ(pos->z); + + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDSRCALPHA); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDINVSRCALPHA); + + rw::im3d::Transform(shape, NUMVERTS+1, nil, 0); + rw::im3d::RenderPrimitive(rw::PRIMTYPETRIFAN); + rw::im3d::RenderTriangle(0, NUMVERTS, 1); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indices, NUMVERTS*3); + rw::im3d::End(); + + + for(i = 0; i < NUMVERTS+1; i++) + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, 255); + + float coneAngleD = cosf(coneAngle/180.0f*M_PI); + rw::V3d at = rw::scale(matrix->at, coneAngleD*coneSize*coneRatio); + shape[0].setX(pos->x + at.x); + shape[0].setY(pos->y + at.y); + shape[0].setZ(pos->z + at.z); + + rw::im3d::Transform(shape, NUMVERTS+1, nil, rw::im3d::ALLOPAQUE); + if(coneRatio > 0.0f){ + rw::im3d::RenderPrimitive(rw::PRIMTYPETRIFAN); + rw::im3d::RenderTriangle(0, NUMVERTS, 1); + }else + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRIFAN, indices, NUMVERTS*3); + rw::im3d::End(); + + + // lines + at = rw::scale(matrix->at, -0.05f); + shape[0].setX(pos->x + at.x); + shape[0].setY(pos->y + at.y); + shape[0].setZ(pos->z + at.z); + rw::im3d::Transform(shape, NUMVERTS+1, nil, rw::im3d::ALLOPAQUE); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPEPOLYLINE, indices, NUMVERTS*3); + rw::im3d::End(); +} + +void +DrawDirectLight(void) +{ + enum { NUMVERTS = 20 }; + const float DIAMETER = 1.5f; + const float CONE_ANGLE = 45.0f; + const float CONE_SIZE = 3.0f; + const float LENGTH = 5.0f; + rw::RWDEVICE::Im3DVertex shape[NUMVERTS*2+1]; + rw::int16 indices[NUMVERTS*3]; + rw::int32 i; + + rw::Matrix *matrix = CurrentLight->getFrame()->getLTM(); + rw::V3d *pos = &matrix->pos; + + // cylinder + for(i = 0; i < NUMVERTS*2; i += 2){ + float cosValue = cosf(i/(NUMVERTS/2.0f) * M_PI); + float sinValue = sinf(i/(NUMVERTS/2.0f) * M_PI); + + rw::V3d up = rw::scale(matrix->up, sinValue*DIAMETER); + rw::V3d right = rw::scale(matrix->right, cosValue*DIAMETER); + rw::V3d at = rw::scale(matrix->at, -(CONE_SIZE + 1.0f)); + + shape[i].setX(pos->x + at.x + up.x + right.x); + shape[i].setY(pos->y + at.y + up.y + right.y); + shape[i].setZ(pos->z + at.z + up.z + right.z); + + + at = rw::scale(matrix->at, -(LENGTH + CONE_SIZE)); + + shape[i+1].setX(pos->x + at.x + up.x + right.x); + shape[i+1].setY(pos->y + at.y + up.y + right.y); + shape[i+1].setZ(pos->z + at.z + up.z + right.z); + } + + for(i = 0; i < NUMVERTS*2+1; i++) + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, 128); + + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDSRCALPHA); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDINVSRCALPHA); + + rw::im3d::Transform(shape, NUMVERTS*2, nil, 0); + rw::im3d::RenderPrimitive(rw::PRIMTYPETRISTRIP); + rw::im3d::RenderTriangle(2*NUMVERTS-2, 2*NUMVERTS-1, 0); + rw::im3d::RenderTriangle(2*NUMVERTS-1, 1, 0); + rw::im3d::End(); + + + // bottom cap + for(i = 0; i < NUMVERTS*2+1; i++) + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, 255); + + rw::V3d at = rw::scale(matrix->at, -(LENGTH + CONE_SIZE)); + shape[NUMVERTS*2].setX(pos->x + at.x); + shape[NUMVERTS*2].setY(pos->y + at.y); + shape[NUMVERTS*2].setZ(pos->z + at.z); + + for(i = 0; i < NUMVERTS; i++){ + indices[i*3+0] = NUMVERTS*2; + indices[i*3+1] = (i+1)*2 + 1; + indices[i*3+2] = i*2 + 1; + } + indices[NUMVERTS*3-2] = 1; + + rw::im3d::Transform(shape, NUMVERTS*2+1, nil, rw::im3d::ALLOPAQUE); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indices, NUMVERTS*3); + rw::im3d::End(); + + + // top cap + at = rw::scale(matrix->at, -(CONE_SIZE + 1.0f)); + shape[NUMVERTS*2].setX(pos->x + at.x); + shape[NUMVERTS*2].setY(pos->y + at.y); + shape[NUMVERTS*2].setZ(pos->z + at.z); + + for(i = 0; i < NUMVERTS; i++){ + indices[i*3+0] = NUMVERTS*2; + indices[i*3+1] = i*2; + indices[i*3+2] = (i+1)*2; + } + + rw::im3d::Transform(shape, NUMVERTS*2+1, nil, rw::im3d::ALLOPAQUE); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indices, NUMVERTS*3); + rw::im3d::End(); + + + // cone + DrawCone(CONE_ANGLE, CONE_SIZE, -2.0f); +} + +void +DrawCurrentLight(void) +{ + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::CULLMODE, rw::CULLBACK); + rw::SetRenderState(rw::ZTESTENABLE, 1); + + switch(LightTypeIndex){ + case 1: DrawPointLight(); break; + case 2: DrawDirectLight(); break; + case 3: + case 4: DrawCone(LightConeAngle, LightRadius*POINT_LIGHT_RADIUS_FACTOR, 1.0f); break; + } +} + + + +void +LightRotate(float xAngle, float yAngle) +{ + if(CurrentLight == nil || CurrentLight == AmbientLight || CurrentLight == PointLight) + return; + + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::Frame *lightFrame = CurrentLight->getFrame(); + rw::V3d pos = lightFrame->matrix.pos; + + pos = rw::scale(pos, -1.0f); + lightFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + lightFrame->rotate(&cameraMatrix->up, xAngle, rw::COMBINEPOSTCONCAT); + lightFrame->rotate(&cameraMatrix->right, yAngle, rw::COMBINEPOSTCONCAT); + + pos = rw::scale(pos, -1.0f); + lightFrame->translate(&pos, rw::COMBINEPOSTCONCAT); +} + +void +ClampPosition(rw::V3d *pos, rw::V3d *delta, rw::BBox *bbox) +{ + if(pos->x + delta->x < bbox->inf.x) + delta->x = bbox->inf.x - pos->x; + else if(pos->x + delta->x > bbox->sup.x) + delta->x = bbox->sup.x - pos->x; + + if(pos->y + delta->y < bbox->inf.y) + delta->y = bbox->inf.y - pos->y; + else if(pos->y + delta->y > bbox->sup.y) + delta->y = bbox->sup.y - pos->y; + + if(pos->z + delta->z < bbox->inf.z) + delta->z = bbox->inf.z - pos->z; + else if(pos->z + delta->z > bbox->sup.z) + delta->z = bbox->sup.z - pos->z; +} + +void +LightTranslateXY(float xDelta, float yDelta) +{ + if(CurrentLight == nil || CurrentLight == AmbientLight || CurrentLight == DirectLight) + return; + + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::Frame *lightFrame = CurrentLight->getFrame(); + rw::V3d right = rw::scale(cameraMatrix->right, xDelta); + rw::V3d up = rw::scale(cameraMatrix->up, yDelta); + rw::V3d delta = rw::add(right, up); + + ClampPosition(&lightFrame->matrix.pos, &delta, &RoomBBox); + + lightFrame->translate(&delta, rw::COMBINEPOSTCONCAT); +} + +void +LightTranslateZ(float zDelta) +{ + if(CurrentLight == nil || CurrentLight == AmbientLight || CurrentLight == DirectLight) + return; + + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::Frame *lightFrame = CurrentLight->getFrame(); + rw::V3d delta = rw::scale(cameraMatrix->at, zDelta); + + ClampPosition(&lightFrame->matrix.pos, &delta, &RoomBBox); + + lightFrame->translate(&delta, rw::COMBINEPOSTCONCAT); +} diff --git a/vendor/librw/tools/lights/lights.h b/vendor/librw/tools/lights/lights.h new file mode 100644 index 0000000..5400a61 --- /dev/null +++ b/vendor/librw/tools/lights/lights.h @@ -0,0 +1,37 @@ + + +extern rw::Light *BaseAmbientLight; +extern bool BaseAmbientLightOn; + +extern rw::Light *CurrentLight; +extern rw::Light *AmbientLight; +extern rw::Light *PointLight; +extern rw::Light *DirectLight; +extern rw::Light *SpotLight; +extern rw::Light *SpotSoftLight; + +extern float LightRadius; +extern float LightConeAngle; +extern rw::RGBAf LightColor; + +extern bool LightOn; +extern bool LightDrawOn; +extern rw::V3d LightPos; +extern rw::int32 LightTypeIndex; + +extern rw::BBox RoomBBox; + +rw::Light *CreateBaseAmbientLight(void); +rw::Light *CreateAmbientLight(void); +rw::Light *CreateDirectLight(void); +rw::Light *CreatePointLight(void); +rw::Light *CreateSpotLight(void); +rw::Light *CreateSpotSoftLight(void); + +void LightsDestroy(void); +void LightsUpdate(void); +void DrawCurrentLight(void); + +void LightRotate(float xAngle, float yAngle); +void LightTranslateXY(float xDelta, float yDelta); +void LightTranslateZ(float zDelta); diff --git a/vendor/librw/tools/lights/main.cpp b/vendor/librw/tools/lights/main.cpp new file mode 100644 index 0000000..3589d05 --- /dev/null +++ b/vendor/librw/tools/lights/main.cpp @@ -0,0 +1,396 @@ +#include +#include +#include + +#include "main.h" +#include "lights.h" + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +rw::EngineOpenParams engineOpenParams; +float FOV = 70.0f; + +rw::RGBA ForegroundColor = { 200, 200, 200, 255 }; +rw::RGBA BackgroundColor = { 64, 64, 64, 0 }; + +rw::World *World; +rw::Camera *Camera; + +rw::V3d Xaxis = { 1.0f, 0.0, 0.0f }; +rw::V3d Yaxis = { 0.0f, 1.0, 0.0f }; +rw::V3d Zaxis = { 0.0f, 0.0, 1.0f }; + +rw::World* +CreateWorld(void) +{ + rw::BBox bb; + + bb.inf.x = bb.inf.y = bb.inf.z = -100.0f; + bb.sup.x = bb.sup.y = bb.sup.z = 100.0f; + + return rw::World::create(&bb); +} + +rw::Camera* +CreateCamera(rw::World *world) +{ + rw::Camera *camera; + camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + assert(camera); + camera->setNearPlane(0.1f); + camera->setFarPlane(300.0f); + camera->setFOV(FOV, (float)sk::globals.width/sk::globals.height); + world->addCamera(camera); + return camera; +} + +bool +CreateTestScene(rw::World *world) +{ + rw::Clump *clump; + rw::StreamFile in; + const char *filename = "checker.dff"; + if(in.open(filename, "rb") == NULL){ + printf("couldn't open file\n"); + return false; + } + if(!rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL)) + return false; + clump = rw::Clump::streamRead(&in); + in.close(); + if(clump == nil) + return false; + + rw::Clump *clone; + rw::Frame *clumpFrame; + rw::V3d pos; + float zOffset = 75.0f; + + // Bottom panel + clumpFrame = clump->getFrame(); + clumpFrame->rotate(&Xaxis, 90.0f, rw::COMBINEREPLACE); + + pos.x = 0.0f; + pos.y = -25.0f; + pos.z = zOffset; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // only need to add once + world->addClump(clump); + + // Top panel + clone = clump->clone(); + clumpFrame = clone->getFrame(); + clumpFrame->rotate(&Xaxis, -90.0f, rw::COMBINEREPLACE); + + pos.x = 0.0f; + pos.y = 25.0f; + pos.z = zOffset; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // Left panel + clone = clump->clone(); + clumpFrame = clone->getFrame(); + clumpFrame->rotate(&Xaxis, 0.0f, rw::COMBINEREPLACE); + clumpFrame->rotate(&Yaxis, 90.0f, rw::COMBINEPOSTCONCAT); + + pos.x = 25.0f; + pos.y = 0.0f; + pos.z = zOffset; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // Right panel + clone = clump->clone(); + clumpFrame = clone->getFrame(); + clumpFrame->rotate(&Xaxis, 0.0f, rw::COMBINEREPLACE); + clumpFrame->rotate(&Yaxis, -90.0f, rw::COMBINEPOSTCONCAT); + + pos.x = -25.0f; + pos.y = 0.0f; + pos.z = zOffset; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // Back panel + clone = clump->clone(); + clumpFrame = clone->getFrame(); + clumpFrame->rotate(&Xaxis, 0.0f, rw::COMBINEREPLACE); + + pos.x = 0.0f; + pos.y = 0.0f; + pos.z = zOffset + 25.0f; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // BBox + pos.x = 25.0f; + pos.y = 25.0f; + pos.z = zOffset - 25.0f; + RoomBBox.initialize(&pos); + pos.x = -25.0f; + pos.y = -25.0f; + pos.z = zOffset + 25.0f; + RoomBBox.addPoint(&pos); + + return 1; +} + +void +Initialize(void) +{ + sk::globals.windowtitle = "Lights example"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +Initialize3D(void) +{ + if(!sk::InitRW()) + return false; + + World = CreateWorld(); + + BaseAmbientLight = CreateBaseAmbientLight(); + AmbientLight = CreateAmbientLight(); + DirectLight = CreateDirectLight(); + PointLight = CreatePointLight(); + SpotLight = CreateSpotLight(); + SpotSoftLight = CreateSpotSoftLight(); + + Camera = CreateCamera(World); + + CreateTestScene(World); + + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); + + return true; +} + +void +Terminate3D(void) +{ + FORLIST(lnk, World->clumps){ + rw::Clump *clump = rw::Clump::fromWorld(lnk); + World->removeClump(clump); + clump->destroy(); + } + + if(Camera){ + World->removeCamera(Camera); + sk::CameraDestroy(Camera); + Camera = nil; + } + + LightsDestroy(); + + if(World){ + World->destroy(); + World = nil; + } + + sk::TerminateRW(); +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +void +Gui(void) +{ +// ImGui::ShowDemoWindow(nil); + + static bool showLightWindow = true; + ImGui::Begin("Lights", &showLightWindow); + + ImGui::Checkbox("Light", &LightOn); + ImGui::Checkbox("Draw Light", &LightDrawOn); + if(ImGui::Checkbox("Base Ambient", &BaseAmbientLightOn)){ + if(BaseAmbientLightOn){ + if(BaseAmbientLight->world == nil) + World->addLight(BaseAmbientLight); + }else{ + if(BaseAmbientLight->world) + World->removeLight(BaseAmbientLight); + } + } + + ImGui::RadioButton("Ambient Light", &LightTypeIndex, 0); + ImGui::RadioButton("Point Light", &LightTypeIndex, 1); + ImGui::RadioButton("Directional Light", &LightTypeIndex, 2); + ImGui::RadioButton("Spot Light", &LightTypeIndex, 3); + ImGui::RadioButton("Soft Spot Light", &LightTypeIndex, 4); + + ImGui::ColorEdit3("Color", (float*)&LightColor); + float radAngle = LightConeAngle/180.0f*M_PI; + ImGui::SliderAngle("Cone angle", &radAngle, 0.0f, 180.0f); + LightConeAngle = radAngle/M_PI*180.0f; + ImGui::SliderFloat("Radius", &LightRadius, 0.1f, 500.0f); + + ImGui::End(); +} + +void +Render(float timeDelta) +{ + Camera->clear(&BackgroundColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + Camera->beginUpdate(); + + ImGui_ImplRW_NewFrame(timeDelta); + + World->render(); + + if(LightDrawOn && CurrentLight) + DrawCurrentLight(); + + Gui(); + + ImGui::EndFrame(); + ImGui::Render(); + + ImGui_ImplRW_RenderDrawLists(ImGui::GetDrawData()); + + Camera->endUpdate(); + Camera->showRaster(0); +} + +void +Idle(float timeDelta) +{ + LightsUpdate(); + + Render(timeDelta); +} + +int MouseX, MouseY; +int MouseDeltaX, MouseDeltaY; +int MouseButtons; + +int CtrlDown; + +bool RotateLight; +bool TranslateLightXY; +bool TranslateLightZ; + +void +KeyUp(int key) +{ + switch(key){ + case sk::KEY_LCTRL: + case sk::KEY_RCTRL: + CtrlDown = 0; + break; + } +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_LCTRL: + case sk::KEY_RCTRL: + CtrlDown = 1; + break; + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +void +MouseBtn(sk::MouseState *mouse) +{ + MouseButtons = mouse->buttons; + RotateLight = !CtrlDown && !!(MouseButtons&1); + TranslateLightXY = CtrlDown && !!(MouseButtons&1); + TranslateLightZ = CtrlDown && !!(MouseButtons&4); +} + +void +MouseMove(sk::MouseState *mouse) +{ + MouseDeltaX = mouse->posx - MouseX; + MouseDeltaY = mouse->posy - MouseY; + MouseX = mouse->posx; + MouseY = mouse->posy; + + if(RotateLight) + LightRotate(-MouseDeltaX, MouseDeltaY); + if(TranslateLightXY) + LightTranslateXY(-MouseDeltaX*0.1f, -MouseDeltaY*0.1f); + if(TranslateLightZ) + LightTranslateZ(-MouseDeltaY*0.1f); +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + MouseState *ms; + + ImGuiEventHandler(e, param); + ImGuiIO &io = ImGui::GetIO(); + + switch(e){ + case INITIALIZE: + Initialize(); + return EVENTPROCESSED; + case RWINITIALIZE: + return Initialize3D() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + Terminate3D(); + return EVENTPROCESSED; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case MOUSEBTN: + if(!io.WantCaptureMouse){ + ms = (MouseState*)param; + MouseBtn(ms); + }else + MouseButtons = 0; + return EVENTPROCESSED; + case MOUSEMOVE: + MouseMove((MouseState*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + if(::Camera){ + sk::CameraSize(::Camera, r); + ::Camera->setFOV(FOV, (float)sk::globals.width/sk::globals.height); + } + break; + case IDLE: + Idle(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/vendor/librw/tools/lights/main.h b/vendor/librw/tools/lights/main.h new file mode 100644 index 0000000..6358f2d --- /dev/null +++ b/vendor/librw/tools/lights/main.h @@ -0,0 +1,7 @@ + +extern rw::World *World; +extern rw::Camera *Camera; + +extern rw::V3d Xaxis; +extern rw::V3d Yaxis; +extern rw::V3d Zaxis; diff --git a/vendor/librw/tools/playground/CMakeLists.txt b/vendor/librw/tools/playground/CMakeLists.txt new file mode 100644 index 0000000..120f7bc --- /dev/null +++ b/vendor/librw/tools/playground/CMakeLists.txt @@ -0,0 +1,22 @@ +add_executable(playground WIN32 + camera.cpp + font.cpp + main.cpp + ras_test.cpp + splines.cpp + tl_tests.cpp +) + +target_link_libraries(playground + PRIVATE + librw::skeleton + librw::librw +) + +add_custom_command( + TARGET playground POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory "$/files" + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/files" "$/files" +) + +librw_platform_target(playground) diff --git a/vendor/librw/tools/playground/camera.cpp b/vendor/librw/tools/playground/camera.cpp new file mode 100644 index 0000000..cf45f5f --- /dev/null +++ b/vendor/librw/tools/playground/camera.cpp @@ -0,0 +1,134 @@ +#include +#include + +#include + +#define PI 3.14159265359f +#include "camera.h" + +using rw::Quat; +using rw::V3d; + +void +Camera::update(void) +{ + if(m_rwcam){ + m_rwcam->setNearPlane(m_near); + m_rwcam->setFarPlane(m_far); + m_rwcam->setFOV(m_fov, m_aspectRatio); + + rw::Frame *f = m_rwcam->getFrame(); + if(f){ + V3d forward = normalize(sub(m_target, m_position)); + V3d left = normalize(cross(m_up, forward)); + V3d nup = cross(forward, left); + f->matrix.right = left; // lol + f->matrix.up = nup; + f->matrix.at = forward; + f->matrix.pos = m_position; + f->matrix.optimize(); + f->updateObjects(); + } + } +} + +void +Camera::setTarget(V3d target) +{ + m_position = sub(m_position, sub(m_target, target)); + m_target = target; +} + +float +Camera::getHeading(void) +{ + V3d dir = sub(m_target, m_position); + float a = atan2(dir.y, dir.x)-PI/2.0f; + return m_localup.z < 0.0f ? a-PI : a; +} + +void +Camera::turn(float yaw, float pitch) +{ + V3d dir = sub(m_target, m_position); + Quat r = Quat::rotation(yaw, rw::makeV3d(0.0f, 0.0f, 1.0f)); + dir = rotate(dir, r); + m_localup = rotate(m_localup, r); + + V3d right = normalize(cross(dir, m_localup)); + r = Quat::rotation(pitch, right); + dir = rotate(dir, r); + m_localup = normalize(cross(right, dir)); + if(m_localup.z >= 0.0) m_up.z = 1.0; + else m_up.z = -1.0f; + + m_target = add(m_position, dir); +} + +void +Camera::orbit(float yaw, float pitch) +{ + V3d dir = sub(m_target, m_position); + Quat r = Quat::rotation(yaw, rw::makeV3d(0.0f, 0.0f, 1.0f)); + dir = rotate(dir, r); + m_localup = rotate(m_localup, r); + + V3d right = normalize(cross(dir, m_localup)); + r = Quat::rotation(-pitch, right); + dir = rotate(dir, r); + m_localup = normalize(cross(right, dir)); + if(m_localup.z >= 0.0) m_up.z = 1.0; + else m_up.z = -1.0f; + + m_position = sub(m_target, dir); +} + +void +Camera::dolly(float dist) +{ + V3d dir = setlength(sub(m_target, m_position), dist); + m_position = add(m_position, dir); + m_target = add(m_target, dir); +} + +void +Camera::zoom(float dist) +{ + V3d dir = sub(m_target, m_position); + float curdist = length(dir); + if(dist >= curdist) + dist = curdist-0.01f; + dir = setlength(dir, dist); + m_position = add(m_position, dir); +} + +void +Camera::pan(float x, float y) +{ + V3d dir = normalize(sub(m_target, m_position)); + V3d right = normalize(cross(dir, m_up)); + V3d localup = normalize(cross(right, dir)); + dir = add(scale(right, x), scale(localup, y)); + m_position = add(m_position, dir); + m_target = add(m_target, dir); +} + +float +Camera::distanceTo(V3d v) +{ + return length(sub(m_position, v)); +} + +Camera::Camera() +{ + m_position.set(0.0f, 6.0f, 0.0f); + m_target.set(0.0f, 0.0f, 0.0f); + m_up.set(0.0f, 0.0f, 1.0f); + m_localup = m_up; + m_fov = 70.0f; + m_aspectRatio = 1.0f; + m_near = 0.1f; + m_far = 100.0f; + m_rwcam = NULL; +} + diff --git a/vendor/librw/tools/playground/camera.h b/vendor/librw/tools/playground/camera.h new file mode 100644 index 0000000..8a0315d --- /dev/null +++ b/vendor/librw/tools/playground/camera.h @@ -0,0 +1,26 @@ +class Camera +{ +public: + rw::Camera *m_rwcam; + rw::V3d m_position; + rw::V3d m_target; + rw::V3d m_up; + rw::V3d m_localup; + + float m_fov, m_aspectRatio; + float m_near, m_far; + + + void setTarget(rw::V3d target); + float getHeading(void); + + void turn(float yaw, float pitch); + void orbit(float yaw, float pitch); + void dolly(float dist); + void zoom(float dist); + void pan(float x, float y); + + void update(void); + float distanceTo(rw::V3d v); + Camera(void); +}; diff --git a/vendor/librw/tools/playground/files/Bm437_IBM_BIOS.FON b/vendor/librw/tools/playground/files/Bm437_IBM_BIOS.FON new file mode 100644 index 0000000..be2443f Binary files /dev/null and b/vendor/librw/tools/playground/files/Bm437_IBM_BIOS.FON differ diff --git a/vendor/librw/tools/playground/files/Bm437_IBM_BIOS.tga b/vendor/librw/tools/playground/files/Bm437_IBM_BIOS.tga new file mode 100644 index 0000000..50034b4 Binary files /dev/null and b/vendor/librw/tools/playground/files/Bm437_IBM_BIOS.tga differ diff --git a/vendor/librw/tools/playground/files/Bm437_IBM_VGA8.FON b/vendor/librw/tools/playground/files/Bm437_IBM_VGA8.FON new file mode 100644 index 0000000..6ffa09a Binary files /dev/null and b/vendor/librw/tools/playground/files/Bm437_IBM_VGA8.FON differ diff --git a/vendor/librw/tools/playground/files/Bm437_IBM_VGA8.tga b/vendor/librw/tools/playground/files/Bm437_IBM_VGA8.tga new file mode 100644 index 0000000..521fb40 Binary files /dev/null and b/vendor/librw/tools/playground/files/Bm437_IBM_VGA8.tga differ diff --git a/vendor/librw/tools/playground/files/foobar.tga b/vendor/librw/tools/playground/files/foobar.tga new file mode 100644 index 0000000..895bae0 Binary files /dev/null and b/vendor/librw/tools/playground/files/foobar.tga differ diff --git a/vendor/librw/tools/playground/files/maze.tga b/vendor/librw/tools/playground/files/maze.tga new file mode 100644 index 0000000..4714c42 Binary files /dev/null and b/vendor/librw/tools/playground/files/maze.tga differ diff --git a/vendor/librw/tools/playground/files/teapot.dff b/vendor/librw/tools/playground/files/teapot.dff new file mode 100644 index 0000000..7a65176 Binary files /dev/null and b/vendor/librw/tools/playground/files/teapot.dff differ diff --git a/vendor/librw/tools/playground/font.cpp b/vendor/librw/tools/playground/font.cpp new file mode 100644 index 0000000..b001250 --- /dev/null +++ b/vendor/librw/tools/playground/font.cpp @@ -0,0 +1,181 @@ +#include +#include + +using namespace rw; + +struct Font +{ + Texture *tex; + int32 glyphwidth, glyphheight; + int32 numglyphs; +}; +Font vga = { nil, 8, 16, 256 }; +Font bios = { nil, 8, 8, 256 }; +Font *curfont = &bios; + +#define NUMCHARS 100 +uint16 indices[NUMCHARS*6]; +RWDEVICE::Im2DVertex vertices[NUMCHARS*4]; +int32 curVert; +int32 curIndex; + +void +printScreen(const char *s, float32 x, float32 y) +{ + char c; + Camera *cam; + RWDEVICE::Im2DVertex *vert; + uint16 *ix; + curVert = 0; + curIndex = 0; + float32 u, v, du, dv; + float recipZ; + + cam = (Camera*)engine->currentCamera; + vert = &vertices[curVert]; + ix = &indices[curIndex]; + du = curfont->glyphwidth/(float32)curfont->tex->raster->width; + dv = curfont->glyphheight/(float32)curfont->tex->raster->height; + recipZ = 1.0f/cam->nearPlane; + while(c = *s){ + if(c >= curfont->numglyphs) + c = 0; + u = (c % 16)*curfont->glyphwidth / (float32)curfont->tex->raster->width; + v = (c / 16)*curfont->glyphheight / (float32)curfont->tex->raster->height; + + vert->setScreenX(x); + vert->setScreenY(y); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u, recipZ); + vert->setV(v, recipZ); + vert++; + + vert->setScreenX(x+curfont->glyphwidth); + vert->setScreenY(y); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u+du, recipZ); + vert->setV(v, recipZ); + vert++; + + vert->setScreenX(x); + vert->setScreenY(y+curfont->glyphheight); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u, recipZ); + vert->setV(v+dv, recipZ); + vert++; + + vert->setScreenX(x+curfont->glyphwidth); + vert->setScreenY(y+curfont->glyphheight); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u+du, recipZ); + vert->setV(v+dv, recipZ); + vert++; + + *ix++ = curVert; + *ix++ = curVert+1; + *ix++ = curVert+2; + *ix++ = curVert+2; + *ix++ = curVert+1; + *ix++ = curVert+3; + + curVert += 4; + curIndex += 6; + x += curfont->glyphwidth+1; + + s++; + } + + rw::SetRenderStatePtr(rw::TEXTURERASTER, curfont->tex->raster); + rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + + im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, + vertices, curVert, indices, curIndex); + +} + +void +initFont(void) +{ + vga.tex = Texture::read("files/Bm437_IBM_VGA8", ""); + bios.tex = Texture::read("files/Bm437_IBM_BIOS", ""); + +/* + FILE *foo = fopen("font.c", "w"); + assert(foo); + int x, y; + rw::Image *img = rw::readTGA("vga_font.tga"); + assert(img); + for(y = 0; y < img->height; y++){ + for(x = 0; x < img->width; x++) + fprintf(foo, "%d, ", !!img->pixels[y*img->width + x]); + fprintf(foo, "\n"); + } +*/ +} + +/* +#define NUMGLYPHS 256 +#define GLYPHWIDTH 8 +#define GLYPHHEIGHT 16 + + +void +convertFont(void) +{ + FILE *f; + Image *img; + uint8 data[NUMGLYPHS*GLYPHHEIGHT]; + int32 i, x, y; + uint8 *px, *line, *glyph; +// f = fopen("font0.bin", "rb"); + f = fopen("files/Bm437_IBM_VGA8.FON", "rb"); +// f = fopen("files/Bm437_IBM_BIOS.FON", "rb"); + if(f == nil) + return; +fseek(f, 0x65A, 0); + fread(data, 1, NUMGLYPHS*GLYPHHEIGHT, f); + fclose(f); + + img = Image::create(16*GLYPHWIDTH, NUMGLYPHS/16*GLYPHHEIGHT, 32); + img->allocate(); + for(i = 0; i < NUMGLYPHS; i++){ + glyph = &data[i*GLYPHHEIGHT]; + x = (i % 16)*GLYPHWIDTH; + y = (i / 16)*GLYPHHEIGHT; + line = &img->pixels[x*4 + y*img->stride]; + for(y = 0; y < GLYPHHEIGHT; y++){ + px = line; + for(x = 0; x < 8; x++){ + if(*glyph & 1<<(8-x)){ + *px++ = 255; + *px++ = 255; + *px++ = 255; + *px++ = 255; + }else{ + *px++ = 0; + *px++ = 0; + *px++ = 0; + *px++ = 0; + } + } + glyph++; + line += img->stride; + } + } +// writeTGA(img, "files/Bm437_IBM_BIOS.tga"); + writeTGA(img, "files/Bm437_IBM_VGA8.tga"); +} +*/ \ No newline at end of file diff --git a/vendor/librw/tools/playground/main.cpp b/vendor/librw/tools/playground/main.cpp new file mode 100644 index 0000000..ba396b6 --- /dev/null +++ b/vendor/librw/tools/playground/main.cpp @@ -0,0 +1,641 @@ +#include +#include +#include "camera.h" +#include + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +Camera *camera; +struct SceneGlobals { + rw::World *world; + rw::Camera *camera; + rw::Clump *clump; +} Scene; +rw::Texture *tex, *tex2; +rw::Raster *testras; +rw::EngineOpenParams engineOpenParams; + +rw::Texture *frontbuffer; + +bool dosoftras = 0; + +namespace gen { +void tlTest(rw::Clump *clump); +} +void genIm3DTransform(void *vertices, rw::int32 numVertices, rw::Matrix *xform); +void genIm3DRenderIndexed(rw::PrimitiveType prim, void *indices, rw::int32 numIndices); +void genIm3DEnd(void); +void initFont(void); +void printScreen(const char *s, float x, float y); + +void initsplines(void); +void rendersplines(void); + +rw::Charset *testfont; + +//#include + +void +Init(void) +{ +// AllocConsole(); +// freopen("CONIN$", "r", stdin); +// freopen("CONOUT$", "w", stdout); +// freopen("CONOUT$", "w", stderr); + + sk::globals.windowtitle = "Clump viewer"; + sk::globals.width = 640; + sk::globals.height = 448; + sk::globals.quit = 0; +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +void +dumpUserData(rw::UserDataArray *ar) +{ + int i; + printf("name: %s\n", ar->name); + for(i = 0; i < ar->numElements; i++){ + switch(ar->datatype){ + case rw::USERDATAINT: + printf(" %d\n", ar->getInt(i)); + break; + case rw::USERDATAFLOAT: + printf(" %f\n", ar->getFloat(i)); + break; + case rw::USERDATASTRING: + printf(" %s\n", ar->getString(i)); + break; + } + } +} + +static rw::Frame* +dumpFrameUserDataCB(rw::Frame *f, void*) +{ + using namespace rw; + int32 i; + UserDataArray *ar; + int32 n = UserDataArray::frameGetCount(f); + for(i = 0; i < n; i++){ + ar = UserDataArray::frameGet(f, i); + dumpUserData(ar); + } + f->forAllChildren(dumpFrameUserDataCB, nil); + return f; +} + +void +dumpUserData(rw::Clump *clump) +{ + printf("Frames\n"); + dumpFrameUserDataCB(clump->getFrame(), nil); +} + +static rw::Frame* +getHierCB(rw::Frame *f, void *data) +{ + using namespace rw; + HAnimData *hd = rw::HAnimData::get(f); + if(hd->hierarchy){ + *(HAnimHierarchy**)data = hd->hierarchy; + return nil; + } + f->forAllChildren(getHierCB, data); + return f; +} + +rw::HAnimHierarchy* +getHAnimHierarchyFromClump(rw::Clump *clump) +{ + using namespace rw; + HAnimHierarchy *hier = nil; + getHierCB(clump->getFrame(), &hier); + return hier; +} + +void +setupAtomic(rw::Atomic *atomic) +{ + using namespace rw; + // just remove pipelines that we can't handle for now +// if(atomic->pipeline && atomic->pipeline->platform != rw::platform) + atomic->pipeline = NULL; + + // Attach hierarchy to atomic if we're skinned + HAnimHierarchy *hier = getHAnimHierarchyFromClump(atomic->clump); + if(hier) + Skin::setHierarchy(atomic, hier); +} + +static void +initHierFromFrames(rw::HAnimHierarchy *hier) +{ + using namespace rw; + int32 i; + for(i = 0; i < hier->numNodes; i++){ + if(hier->nodeInfo[i].frame){ + hier->matrices[hier->nodeInfo[i].index] = *hier->nodeInfo[i].frame->getLTM(); + }else + assert(0); + } +} + +void +setupClump(rw::Clump *clump) +{ + using namespace rw; + HAnimHierarchy *hier = getHAnimHierarchyFromClump(clump); + if(hier){ + hier->attach(); + initHierFromFrames(hier); + } + + FORLIST(lnk, clump->atomics){ + rw::Atomic *a = rw::Atomic::fromClump(lnk); + setupAtomic(a); + } +} + +#define MUL(x, y) ((x)*(y)/255) + +int +calcVCfx(int fb, int col, int a, int iter) +{ + int prev = fb; + int col2 = col*2; + if(col2 > 255) col2 = 255; + for(int i = 0; i < iter; i++){ + int tmp = MUL(fb, 255-a) + MUL(MUL(prev, col2), a); + tmp += MUL(prev, col); + tmp += MUL(prev, col); + prev = tmp > 255 ? 255 : tmp; + } + return prev; +} + +int +calcIIIfx(int fb, int col, int a, int iter) +{ + int prev = fb; + for(int i = 0; i < iter; i++){ + int tmp = MUL(fb, 255-a) + MUL(MUL(prev, col), a); + prev = tmp > 255 ? 255 : tmp; + } + return prev; +} + +void +postfxtest(void) +{ + rw::Image *img = rw::Image::create(256, 256, 32); + img->allocate(); + int x, y; + int iter; + static char filename[100]; + for(iter = 0; iter < 10; iter++){ + for(y = 0; y < 256; y++) + for(x = 0; x < 256; x++){ + int res = calcVCfx(y, x, 30, iter); +// int res = calcIIIfx(y, x, 30, iter); + if(0 && res == y){ + img->pixels[y*img->stride + x*img->bpp + 0] = 255; + img->pixels[y*img->stride + x*img->bpp + 1] = 0; + img->pixels[y*img->stride + x*img->bpp + 2] = 0; + }else{ + img->pixels[y*img->stride + x*img->bpp + 0] = res; + img->pixels[y*img->stride + x*img->bpp + 1] = res; + img->pixels[y*img->stride + x*img->bpp + 2] = res; + } + img->pixels[y*img->stride + x*img->bpp + 3] = 255; + } + sprintf(filename, "vcfx_%02d.bmp", iter); +// sprintf(filename, "iiifx_%02d.bmp", iter); + rw::writeBMP(img, filename); + } + exit(0); +} + +bool +InitRW(void) +{ +// rw::platform = rw::PLATFORM_D3D8; + if(!sk::InitRW()) + return false; + + rw::d3d::isP8supported = false; + +// postfxtest(); + + initFont(); + + rw::RGBA foreground = { 255, 255, 0, 255 }; + rw::RGBA background = { 0, 0, 0, 0 }; + rw::Charset::open(); + testfont = rw::Charset::create(&foreground, &background); + assert(testfont); + foreground.blue = 255.0f; + testfont->setColors(&foreground, &background); + + tex = rw::Texture::read("files/maze", nil); + tex2 = rw::Texture::read("files/checkers", nil); + + const char *filename = "files/teapot.dff"; + if(sk::args.argc > 1) + filename = sk::args.argv[1]; + rw::StreamFile in; + if(in.open(filename, "rb") == NULL){ + printf("couldn't open file\n"); + return false; + } + rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL); + Scene.clump = rw::Clump::streamRead(&in); + assert(Scene.clump); + in.close(); + + // TEST - Set texture to the all materials of the clump +// FORLIST(lnk, Scene.clump->atomics){ +// rw::Atomic *a = rw::Atomic::fromClump(lnk); +// for(int i = 0; i < a->geometry->matList.numMaterials; i++) +// a->geometry->matList.materials[i]->setTexture(tex); +// } + + Scene.clump->getFrame()->translate(&zero, rw::COMBINEREPLACE); + + dumpUserData(Scene.clump); + setupClump(Scene.clump); + + Scene.world = rw::World::create(); + + rw::Light *ambient = rw::Light::create(rw::Light::AMBIENT); + ambient->setColor(0.3f, 0.3f, 0.3f); + Scene.world->addLight(ambient); + + rw::V3d xaxis = { 1.0f, 0.0f, 0.0f }; + rw::Light *direct = rw::Light::create(rw::Light::DIRECTIONAL); + direct->setColor(0.8f, 0.8f, 0.8f); + direct->setFrame(rw::Frame::create()); + direct->getFrame()->rotate(&xaxis, 180.0f, rw::COMBINEREPLACE); + Scene.world->addLight(direct); + + camera = new Camera; + Scene.camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + camera->m_rwcam = Scene.camera; + camera->m_aspectRatio = 640.0f/448.0f; +// camera->m_near = 0.5f; + camera->m_near = 1.5f; +// camera->m_far = 450.0f; + camera->m_far = 15.0f; + camera->m_target.set(0.0f, 0.0f, 0.0f); + camera->m_position.set(0.0f, -10.0f, 0.0f); +// camera->setPosition(Vec3(0.0f, 5.0f, 0.0f)); +// camera->setPosition(Vec3(0.0f, -70.0f, 0.0f)); +// camera->setPosition(Vec3(0.0f, -1.0f, 3.0f)); + camera->update(); + + Scene.world->addCamera(camera->m_rwcam); + + initsplines(); + + return true; +} + +void +im2dtest(void) +{ + using namespace rw::RWDEVICE; + int i; + static struct + { + float x, y; + rw::uint8 r, g, b, a; + float u, v; + } vs[4] = { + { 0.0f, 0.0f, 255, 0, 0, 128, 0.0f, 0.0f }, + { 640.0f, 0.0f, 0, 255, 0, 128, 1.0f, 0.0f }, + { 0.0f, 448.0f, 0, 0, 255, 128, 0.0f, 1.0f }, + { 640.0f, 448.0f, 0, 255, 255, 128, 1.0f, 1.0f }, +/* + { 0.0f, 0.0f, 255, 0, 0, 128, 0.0f, 1.0f }, + { 640.0f, 0.0f, 0, 255, 0, 128, 0.0f, 0.0f }, + { 0.0f, 448.0f, 0, 0, 255, 128, 1.0f, 1.0f }, + { 640.0f, 448.0f, 0, 255, 255, 128, 1.0f, 0.0f }, +*/ + }; + Im2DVertex verts[4]; + static short indices[] = { + 0, 1, 2, 3 + }; + + float recipZ = 1.0f/Scene.camera->nearPlane; + for(i = 0; i < 4; i++){ + verts[i].setScreenX(vs[i].x); + verts[i].setScreenY(vs[i].y); + verts[i].setScreenZ(rw::im2d::GetNearZ()); + verts[i].setCameraZ(Scene.camera->nearPlane); + verts[i].setRecipCameraZ(recipZ); + verts[i].setColor(vs[i].r, vs[i].g, vs[i].b, vs[i].a); + if(dosoftras) + verts[i].setColor(255, 255, 255, 255); + verts[i].setU(vs[i].u + 0.5f/640.0f, recipZ); + verts[i].setV(vs[i].v + 0.5f/448.0f, recipZ); + } + + rw::SetRenderStatePtr(rw::TEXTURERASTER, tex->raster); + if(dosoftras) + rw::SetRenderStatePtr(rw::TEXTURERASTER, testras); + rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRISTRIP, + &verts, 4, &indices, 4); +} + +void +im2dtest2(void) +{ + using namespace rw::RWDEVICE; + int i; + rw::Camera *cam = Scene.camera; + float n = cam->nearPlane; + float f = cam->farPlane; + float mid = (n+f)/4.0f; + struct + { + float x, y, z; + rw::uint8 r, g, b, a; + float u, v; + } vs[4] = { + { 0.5f, 0.5f, n, 255, 255, 255, 255, 0.0f, 0.0f }, + { 0.5f, 0.5f, mid, 255, 255, 255, 255, 1.0f, 0.0f }, + { 0.5f, -0.5f, n, 255, 255, 255, 255, 0.0f, 1.0f }, + { 0.5f, -0.5f, mid, 255, 255, 255, 255, 1.0f, 1.0f }, + }; + Im2DVertex verts[4]; + static short indices[] = { + 0, 1, 2, 3 + }; + + for(i = 0; i < 4; i++){ + float recipZ = 1.0f/vs[i].z; + verts[i].setScreenX((vs[i].x*recipZ + 0.5f) * 640.0f); + verts[i].setScreenY((vs[i].y*recipZ + 0.5f) * 448.0f); + verts[i].setScreenZ(recipZ * cam->zScale + cam->zShift); +// verts[i].setCameraZ(vs[i].z); + verts[i].setRecipCameraZ(recipZ); + verts[i].setColor(vs[i].r, vs[i].g, vs[i].b, vs[i].a); + if(dosoftras) + verts[i].setColor(255, 255, 255, 255); + verts[i].setU(vs[i].u + 0.5f/640.0f, recipZ); + verts[i].setV(vs[i].v + 0.5f/448.0f, recipZ); + } + + rw::SetRenderStatePtr(rw::TEXTURERASTER, tex->raster); + rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRISTRIP, + &verts, 4, &indices, 4); +} + +void +im3dtest(void) +{ + using namespace rw::RWDEVICE; + int i; + static struct + { + float x, y, z; + rw::uint8 r, g, b, a; + float u, v; + } vs[8] = { + { -1.0f, -1.0f, -1.0f, 255, 0, 0, 128, 0.0f, 0.0f }, + { -1.0f, 1.0f, -1.0f, 0, 255, 0, 128, 0.0f, 1.0f }, + { 1.0f, -1.0f, -1.0f, 0, 0, 255, 128, 1.0f, 0.0f }, + { 1.0f, 1.0f, -1.0f, 255, 0, 255, 128, 1.0f, 1.0f }, + + { -1.0f, -1.0f, 1.0f, 255, 0, 0, 128, 0.0f, 0.0f }, + { -1.0f, 1.0f, 1.0f, 0, 255, 0, 128, 0.0f, 1.0f }, + { 1.0f, -1.0f, 1.0f, 0, 0, 255, 128, 1.0f, 0.0f }, + { 1.0f, 1.0f, 1.0f, 255, 0, 255, 128, 1.0f, 1.0f }, + }; + Im3DVertex verts[8]; + static short indices[2*6] = { + 0, 1, 2, 2, 1, 3, + 4, 5, 6, 6, 5, 7 + }; + + for(i = 0; i < 8; i++){ + verts[i].setX(vs[i].x); + verts[i].setY(vs[i].y); + verts[i].setZ(vs[i].z); + verts[i].setColor(vs[i].r, vs[i].g, vs[i].b, vs[i].a); + verts[i].setU(vs[i].u); + verts[i].setV(vs[i].v); + } + + rw::SetRenderStatePtr(rw::TEXTURERASTER, tex->raster); +// rw::SetRenderStatePtr(rw::TEXTURERASTER, testfont->raster); +// rw::SetRenderStatePtr(rw::TEXTURERASTER, frontbuffer->raster); + rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + +/* + genIm3DTransform(verts, 8, nil); + genIm3DRenderIndexed(rw::PRIMTYPETRILIST, indices, 12); + genIm3DEnd(); +*/ + rw::im3d::Transform(verts, 8, nil, rw::im3d::EVERYTHING); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indices, 12); + rw::im3d::End(); +} + +void +getFrontBuffer(void) +{ + rw::Raster *fb = Scene.camera->frameBuffer; + + if(frontbuffer == nil || fb->width > frontbuffer->raster->width || fb->height > frontbuffer->raster->height){ + int w, h; + for(w = 1; w < fb->width; w <<= 1); + for(h = 1; h < fb->height; h <<= 1); + rw::Raster *ras = rw::Raster::create(w, h, fb->depth, rw::Raster::CAMERATEXTURE); + if(frontbuffer){ + frontbuffer->raster->destroy(); + frontbuffer->raster = ras; + }else + frontbuffer = rw::Texture::create(ras); + printf("created FB with %d %d %d\n", ras->width, ras->height, ras->depth); + } + + rw::Raster::pushContext(frontbuffer->raster); + fb->renderFast(0, 0); + rw::Raster::popContext(); +} + +void +Draw(float timeDelta) +{ + getFrontBuffer(); + + rw::SetRenderState(rw::FOGCOLOR, 0xFF0000FF); + rw::SetRenderState(rw::FOGENABLE, 1); + camera->m_rwcam->fogPlane = camera->m_rwcam->nearPlane; + + static rw::RGBA clearcol = { 161, 161, 161, 0xFF }; + camera->m_rwcam->clear(&clearcol, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + camera->update(); + camera->m_rwcam->beginUpdate(); + +extern void beginSoftras(void); + beginSoftras(); + +// gen::tlTest(Scene.clump); +void drawtest(void); +// drawtest(); + +extern void endSoftras(void); + if(dosoftras){ + endSoftras(); + } + //im2dtest(); + im2dtest2(); + +// Scene.clump->render(); +// im3dtest(); +// printScreen("Hello, World!", 10, 10); + +// testfont->print("foo ABC", 200, 200, true); + +// rendersplines(); + + camera->m_rwcam->endUpdate(); + + camera->m_rwcam->showRaster(0); +} + + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case 'W': + camera->orbit(0.0f, 0.1f); + break; + case 'S': + camera->orbit(0.0f, -0.1f); + break; + case 'A': + camera->orbit(-0.1f, 0.0f); + break; + case 'D': + camera->orbit(0.1f, 0.0f); + break; + case sk::KEY_UP: + camera->turn(0.0f, 0.1f); + break; + case sk::KEY_DOWN: + camera->turn(0.0f, -0.1f); + break; + case sk::KEY_LEFT: + camera->turn(0.1f, 0.0f); + break; + case sk::KEY_RIGHT: + camera->turn(-0.1f, 0.0f); + break; + case 'R': + camera->zoom(0.1f); + break; + case 'F': + camera->zoom(-0.1f); + break; + case 'V': + dosoftras = !dosoftras; + break; + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +void +MouseMove(int x, int y) +{ +} + +void +MouseButton(int buttons) +{ +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + MouseState *ms; + + switch(e){ + case INITIALIZE: + Init(); + return EVENTPROCESSED; + case RWINITIALIZE: + return ::InitRW() ? EVENTPROCESSED : EVENTERROR; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case MOUSEBTN: + ms = (MouseState*)param; + MouseButton(ms->buttons); + return EVENTPROCESSED; + case MOUSEMOVE: + ms = (MouseState*)param; + MouseMove(ms->posx, ms->posy); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + if(camera) + camera->m_aspectRatio = (float)r->w/r->h; + if(Scene.camera) + sk::CameraSize(Scene.camera, r); + break; + case IDLE: + Draw(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/vendor/librw/tools/playground/ras_test.cpp b/vendor/librw/tools/playground/ras_test.cpp new file mode 100644 index 0000000..185fdec --- /dev/null +++ b/vendor/librw/tools/playground/ras_test.cpp @@ -0,0 +1,902 @@ +#include +#include + +namespace rs { + +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint16_t u16; +typedef int32_t i32; +typedef uint32_t u32; +typedef int64_t i64; +typedef uint64_t u64; + +typedef struct Canvas Canvas; +struct Canvas +{ + u8 *fb; + u32 *zbuf; + int w, h; +}; +extern Canvas *canvas; + +typedef struct Texture Texture; +struct Texture +{ + u8 *pixels; + int w, h; + int wrap; +}; + +typedef struct Point3 Point3; +struct Point3 +{ + int x, y, z; +}; + +typedef struct Color Color; +struct Color +{ + u8 r, g, b, a; +}; + +typedef struct Vertex Vertex; +struct Vertex +{ + i32 x, y, z; + float q; // 1/z + u8 r, g, b, a; + u8 f; // fog + float s, t; +}; + +Canvas *makecanvas(int w, int h); +Texture *maketexture(int w, int h); +void putpixel(Canvas *canvas, Point3 p, Color c); +void clearcanvas(Canvas *canvas); +void drawTriangle(Canvas *canvas, Vertex p1, Vertex p2, Vertex p3); + +// not good +void drawRect(Canvas *canvas, Point3 p1, Point3 p2, Color c); +void drawLine(Canvas *canvas, Point3 p1, Point3 p2, Color c); + +//#define trace(...) printf(__VA_ARGS__) +#define trace(...) + + +int clamp(int x); + + +/* + * Render States + */ +enum TextureWrap { + WRAP_REPEAT, + WRAP_CLAMP, + WRAP_BORDER, +}; + +enum TextureFunction { + TFUNC_MODULATE, + TFUNC_DECAL, + TFUNC_HIGHLIGHT, + TFUNC_HIGHLIGHT2, +}; + +enum AlphaTestFunc { + ALPHATEST_NEVER, + ALPHATEST_ALWAYS, + ALPHATEST_LESS, + ALPHATEST_LEQUAL, + ALPHATEST_EQUAL, + ALPHATEST_GEQUAL, + ALPHATEST_GREATER, + ALPHATEST_NOTEQUAL, +}; + +enum AlphaTestFail { + ALPHAFAIL_KEEP, + ALPHAFAIL_FB_ONLY, + ALPHAFAIL_ZB_ONLY, +}; + +enum DepthTestFunc { + DEPTHTEST_NEVER, + DEPTHTEST_ALWAYS, + DEPTHTEST_GEQUAL, + DEPTHTEST_GREATER, +}; + +// The blend equation is +// out = ((A - B) * C >> 7) + D +// A, B and D select the color, C the alpha value +enum AlphaBlendOp { + ALPHABLEND_SRC, + ALPHABLEND_DST, + ALPHABLEND_ZERO, + ALPHABLEND_FIX = ALPHABLEND_ZERO, +}; + +extern int srScissorX0, srScissorX1; +extern int srScissorY0, srScissorY1; +extern int srDepthTestEnable; +extern int srDepthTestFunction; +extern int srWriteZ; +extern int srAlphaTestEnable; +extern int srAlphaTestFunction; +extern int srAlphaTestReference; +extern int srAlphaTestFail; +extern int srAlphaBlendEnable; +extern int srAlphaBlendA; +extern int srAlphaBlendB; +extern int srAlphaBlendC; +extern int srAlphaBlendD; +extern int srAlphaBlendFix; +extern int srTexEnable; +extern Texture *srTexture; +extern int srWrapU; +extern int srWrapV; +extern Color srBorder; +extern int srTexUseAlpha; +extern int srTexFunc; +extern int srFogEnable; +extern Color srFogCol; + + + +// end header + + + + +#define CEIL(p) (((p)+15) >> 4) + +// render states +int srScissorX0, srScissorX1; +int srScissorY0, srScissorY1; +int srDepthTestEnable = 1; +int srDepthTestFunction = DEPTHTEST_GEQUAL; +int srWriteZ = 1; +int srAlphaTestEnable = 1; +int srAlphaTestFunction = ALPHATEST_ALWAYS; +int srAlphaTestReference; +int srAlphaTestFail = ALPHAFAIL_FB_ONLY; +int srAlphaBlendEnable = 1; +int srAlphaBlendA = ALPHABLEND_SRC; +int srAlphaBlendB = ALPHABLEND_DST; +int srAlphaBlendC = ALPHABLEND_SRC; +int srAlphaBlendD = ALPHABLEND_DST; +int srAlphaBlendFix = 0x80; +int srTexEnable = 0; +Texture *srTexture; +int srWrapU = WRAP_REPEAT; +int srWrapV = WRAP_REPEAT; +Color srBorder = { 255, 0, 0, 255 }; +int srTexUseAlpha = 1; +int srTexFunc = TFUNC_MODULATE; +int srFogEnable = 0; +Color srFogCol = { 0, 0, 0, 0 }; + +int clamp(int x) { if(x < 0) return 0; if(x > 255) return 255; return x; } + +Canvas* +makecanvas(int w, int h) +{ + Canvas *canv; + canv = (Canvas*)malloc(sizeof(*canv) + w*h*(4+4)); + canv->w = w; + canv->h = h; + canv->fb = ((u8*)canv + sizeof(*canv)); + canv->zbuf = (u32*)(canv->fb + w*h*4); + return canv; +} + +Texture* +maketexture(int w, int h) +{ + Texture *t; + t = (Texture*)malloc(sizeof(*t) + w*h*4); + t->w = w; + t->h = h; + t->pixels = (u8*)t + sizeof(*t); + t->wrap = 0x11; // wrap u and v + return t; +} + +void +clearcanvas(Canvas *canvas) +{ + memset(canvas->fb, 0, canvas->w*canvas->h*4); + memset(canvas->zbuf, 0, canvas->w*canvas->h*4); +} + +void +writefb(Canvas *canvas, int x, int y, Color c) +{ + u8 *px = &canvas->fb[(y*canvas->w + x)*4]; + u32 *z = &canvas->zbuf[y*canvas->w + x]; + + px[3] = c.r; + px[2] = c.g; + px[1] = c.b; + px[0] = c.a; +} + +void +putpixel(Canvas *canvas, Point3 p, Color c) +{ + // scissor test + if(p.x < srScissorX0 || p.x > srScissorX1 || + p.y < srScissorY0 || p.y > srScissorY1) + return; + + u8 *px = &canvas->fb[(p.y*canvas->w + p.x)*4]; + u32 *z = &canvas->zbuf[p.y*canvas->w + p.x]; + + int fbwrite = 1; + int zbwrite = srWriteZ; + + // alpha test + if(srAlphaTestEnable){ + int fail; + switch(srAlphaTestFunction){ + case ALPHATEST_NEVER: + fail = 1; + break; + case ALPHATEST_ALWAYS: + fail = 0; + break; + case ALPHATEST_LESS: + fail = c.a >= srAlphaTestReference; + break; + case ALPHATEST_LEQUAL: + fail = c.a > srAlphaTestReference; + break; + case ALPHATEST_EQUAL: + fail = c.a != srAlphaTestReference; + break; + case ALPHATEST_GEQUAL: + fail = c.a < srAlphaTestReference; + break; + case ALPHATEST_GREATER: + fail = c.a <= srAlphaTestReference; + break; + case ALPHATEST_NOTEQUAL: + fail = c.a == srAlphaTestReference; + break; + } + if(fail){ + switch(srAlphaTestFail){ + case ALPHAFAIL_KEEP: + return; + case ALPHAFAIL_FB_ONLY: + zbwrite = 0; + break; + case ALPHAFAIL_ZB_ONLY: + fbwrite = 0; + } + } + } + + // ztest + if(srDepthTestEnable){ + switch(srDepthTestFunction){ + case DEPTHTEST_NEVER: + return; + case DEPTHTEST_ALWAYS: + break; + case DEPTHTEST_GEQUAL: + if((u32)p.z < *z) + return; + break; + case DEPTHTEST_GREATER: + if((u32)p.z <= *z) + return; + break; + } + } + + Color d = { px[3], px[2], px[1], px[0] }; + + // blend + if(srAlphaBlendEnable){ + int ar, ag, ab; + int br, bg, bb; + int dr, dg, db; + int ca; + switch(srAlphaBlendA){ + case ALPHABLEND_SRC: + ar = c.r; + ag = c.g; + ab = c.b; + break; + case ALPHABLEND_DST: + ar = d.r; + ag = d.g; + ab = d.b; + break; + case ALPHABLEND_ZERO: + ar = 0; + ag = 0; + ab = 0; + break; + default: assert(0); + } + switch(srAlphaBlendB){ + case ALPHABLEND_SRC: + br = c.r; + bg = c.g; + bb = c.b; + break; + case ALPHABLEND_DST: + br = d.r; + bg = d.g; + bb = d.b; + break; + case ALPHABLEND_ZERO: + br = 0; + bg = 0; + bb = 0; + break; + default: assert(0); + } + switch(srAlphaBlendC){ + case ALPHABLEND_SRC: + ca = c.a; + break; + case ALPHABLEND_DST: + ca = d.a; + break; + case ALPHABLEND_FIX: + ca = srAlphaBlendFix; + break; + default: assert(0); + } + switch(srAlphaBlendD){ + case ALPHABLEND_SRC: + dr = c.r; + dg = c.g; + db = c.b; + break; + case ALPHABLEND_DST: + dr = d.r; + dg = d.g; + db = d.b; + break; + case ALPHABLEND_ZERO: + dr = 0; + dg = 0; + db = 0; + break; + default: assert(0); + } + + int r, g, b; + r = ((ar - br) * ca >> 7) + dr; + g = ((ag - bg) * ca >> 7) + dg; + b = ((ab - bb) * ca >> 7) + db; + + c.r = clamp(r); + c.g = clamp(g); + c.b = clamp(b); + } + + if(fbwrite) + writefb(canvas, p.x, p.y, c); + if(zbwrite) + *z = p.z; +} + +Color +sampletex_nearest(int u, int v) +{ + Texture *tex = srTexture; + + const int usize = tex->w; + const int vsize = tex->h; + + int iu = u >> 4; + int iv = v >> 4; + + switch(srWrapU){ + case WRAP_REPEAT: + iu %= usize; + break; + case WRAP_CLAMP: + if(iu < 0) iu = 0; + if(iu >= usize) iu = usize-1; + break; + case WRAP_BORDER: + if(iu < 0 || iu >= usize) + return srBorder; + } + + switch(srWrapV){ + case WRAP_REPEAT: + iv %= vsize; + break; + case WRAP_CLAMP: + if(iv < 0) iv = 0; + if(iv >= vsize) iv = vsize-1; + break; + case WRAP_BORDER: + if(iv < 0 || iv >= vsize) + return srBorder; + } + + u8 *cp = &tex->pixels[(iv*tex->w + iu)*4]; + Color c = { cp[0], cp[1], cp[2], cp[3] }; + return c; +} + +// t is texture, f is fragment +Color +texfunc(Color t, Color f) +{ + int r, g, b, a; + switch(srTexFunc){ + case TFUNC_MODULATE: + r = t.r * f.r >> 7; + g = t.g * f.g >> 7; + b = t.b * f.b >> 7; + a = srTexUseAlpha ? + t.a * f.a >> 7 : + f.a; + break; + case TFUNC_DECAL: + r = t.r; + g = t.g; + b = t.b; + a = srTexUseAlpha ? t.a : f.a; + break; + case TFUNC_HIGHLIGHT: + r = (t.r * f.r >> 7) + f.a; + g = (t.g * f.g >> 7) + f.a; + b = (t.b * f.b >> 7) + f.a; + a = srTexUseAlpha ? + t.a + f.a : + f.a; + break; + case TFUNC_HIGHLIGHT2: + r = (t.r * f.r >> 7) + f.a; + g = (t.g * f.g >> 7) + f.a; + b = (t.b * f.b >> 7) + f.a; + a = srTexUseAlpha ? t.a : f.a; + break; + } + Color v; + v.r = clamp(r); + v.g = clamp(g); + v.b = clamp(b); + v.a = clamp(a); + return v; +} + +Point3 mkpnt(int x, int y, int z) { Point3 p = { x, y, z}; return p; } + +void +drawRect(Canvas *canvas, Point3 p1, Point3 p2, Color c) +{ + int x, y; + for(y = p1.y; y <= p2.y; y++) + for(x = p1.x; x <= p2.x; x++) + putpixel(canvas, mkpnt(x, y, 0), c); +} + +void +drawLine(Canvas *canvas, Point3 p1, Point3 p2, Color c) +{ + int dx, dy; + int incx, incy; + int e; + int x, y; + + dx = abs(p2.x-p1.x); + incx = p2.x > p1.x ? 1 : -1; + dy = abs(p2.y-p1.y); + incy = p2.y > p1.y ? 1 : -1; + e = 0; + if(dx == 0){ + for(y = p1.y; y != p2.y; y += incy) + putpixel(canvas, mkpnt(p1.x, y, 0), c); + }else if(dx > dy){ + y = p1.y; + for(x = p1.x; x != p2.x; x += incx){ + putpixel(canvas, mkpnt(x, y, 0), c); + e += dy; + if(2*e >= dx){ + e -= dx; + y += incy; + } + } + }else{ + x = p1.x; + for(y = p1.y; y != p2.y; y += incy){ + putpixel(canvas, mkpnt(x, y, 0), c); + e += dx; + if(2*e >= dy){ + e -= dy; + x += incx; + } + } + } +} + +/* + attibutes we want to interpolate: + R G B A + U V / S T Q + X Y Z F +*/ + +struct TriAttribs +{ + i64 z; + i32 r, g, b, a; + i32 f; + float s, t; + float q; +}; + +static void +add1(struct TriAttribs *a, struct TriAttribs *b) +{ + a->z += b->z; + a->r += b->r; + a->g += b->g; + a->b += b->b; + a->a += b->a; + a->f += b->f; + a->s += b->s; + a->t += b->t; + a->q += b->q; +} + +static void +sub1(struct TriAttribs *a, struct TriAttribs *b) +{ + a->z -= b->z; + a->r -= b->r; + a->g -= b->g; + a->b -= b->b; + a->a -= b->a; + a->f -= b->f; + a->s -= b->s; + a->t -= b->t; + a->q -= b->q; +} + +static void +guard(struct TriAttribs *a) +{ + if(a->z < 0) a->z = 0; + else if(a->z > 0x3FFFFFFFC000LL) a->z = 0x3FFFFFFFC000LL; + if(a->r < 0) a->r = 0; + else if(a->r > 0xFF000) a->r = 0xFF000; + if(a->g < 0) a->g = 0; + else if(a->g > 0xFF000) a->g = 0xFF000; + if(a->b < 0) a->b = 0; + else if(a->b > 0xFF000) a->b = 0xFF000; + if(a->a < 0) a->a = 0; + else if(a->a > 0xFF000) a->a = 0xFF000; + if(a->f < 0) a->f = 0; + else if(a->f > 0xFF000) a->f = 0xFF000; +} + +struct RasTri +{ + int x, y; + int ymid, yend; + int right; + int e[2], dx[3], dy[3]; + struct TriAttribs gx, gy, v, s; +}; + +static int +triangleSetup(struct RasTri *tri, Vertex v1, Vertex v2, Vertex v3) +{ + int dx1, dx2, dx3; + int dy1, dy2, dy3; + + dy1 = v3.y - v1.y; // long edge + if(dy1 == 0) return 1; + dx1 = v3.x - v1.x; + dx2 = v2.x - v1.x; // first small edge + dy2 = v2.y - v1.y; + dx3 = v3.x - v2.x; // second small edge + dy3 = v3.y - v2.y; + + // this is twice the triangle area + const int area = dx2*dy1 - dx1*dy2; + if(area == 0) return 1; + // figure out if 0 or 1 is the right edge + tri->right = area < 0; + + /* The gradients are to step whole pixels, + * so they are pre-multiplied by 16. */ + + float denom = 16.0f/area; + // gradients x +#define GX(p) ((v2.p - v1.p)*dy1 - (v3.p - v1.p)*dy2) + tri->gx.z = GX(z)*denom * 16384; + tri->gx.r = GX(r)*denom * 4096; + tri->gx.g = GX(g)*denom * 4096; + tri->gx.b = GX(b)*denom * 4096; + tri->gx.a = GX(a)*denom * 4096; + tri->gx.f = GX(f)*denom * 4096; + tri->gx.s = GX(s)*denom; + tri->gx.t = GX(t)*denom; + tri->gx.q = GX(q)*denom; + + // gradients y + denom = -denom; +#define GY(p) ((v2.p - v1.p)*dx1 - (v3.p - v1.p)*dx2) + tri->gy.z = GY(z)*denom * 16384; + tri->gy.r = GY(r)*denom * 4096; + tri->gy.g = GY(g)*denom * 4096; + tri->gy.b = GY(b)*denom * 4096; + tri->gy.a = GY(a)*denom * 4096; + tri->gy.f = GY(f)*denom * 4096; + tri->gy.s = GY(s)*denom; + tri->gy.t = GY(t)*denom; + tri->gy.q = GY(q)*denom; + + tri->ymid = CEIL(v2.y); + tri->yend = CEIL(v3.y); + + tri->y = CEIL(v1.y); + tri->x = CEIL(v1.x); + + tri->dy[0] = dy2<<4; // upper edge + tri->dy[1] = dy1<<4; // lower edge + tri->dy[2] = dy3<<4; // long edge + tri->dx[0] = dx2<<4; + tri->dx[1] = dx1<<4; + tri->dx[2] = dx3<<4; + + // prestep to land on pixel center + + int stepx = v1.x - (tri->x<<4); + int stepy = v1.y - (tri->y<<4); + tri->e[0] = (-stepy*tri->dx[0] + stepx*tri->dy[0]) >> 4; + tri->e[1] = (-stepy*tri->dx[1] + stepx*tri->dy[1]) >> 4; + + // attributes along interpolated edge + // why is this cast needed? (mingw) + tri->v.z = (i64)v1.z*16384 - (stepy*tri->gy.z + stepx*tri->gx.z)/16; + tri->v.r = v1.r*4096 - (stepy*tri->gy.r + stepx*tri->gx.r)/16; + tri->v.g = v1.g*4096 - (stepy*tri->gy.g + stepx*tri->gx.g)/16; + tri->v.b = v1.b*4096 - (stepy*tri->gy.b + stepx*tri->gx.b)/16; + tri->v.a = v1.a*4096 - (stepy*tri->gy.a + stepx*tri->gx.a)/16; + tri->v.f = v1.f*4096 - (stepy*tri->gy.f + stepx*tri->gx.f)/16; + tri->v.s = v1.s - (stepy*tri->gy.s + stepx*tri->gx.s)/16.0f; + tri->v.t = v1.t - (stepy*tri->gy.t + stepx*tri->gx.t)/16.0f; + tri->v.q = v1.q - (stepy*tri->gy.q + stepx*tri->gx.q)/16.0f; + + return 0; +} + +void +drawTriangle(Canvas *canvas, Vertex v1, Vertex v2, Vertex v3) +{ + Color c; + struct RasTri tri; + int stepx, stepy; + + // Sort such that we have from top to bottom v1,v2,v3 + if(v2.y < v1.y){ Vertex tmp = v1; v1 = v2; v2 = tmp; } + if(v3.y < v1.y){ Vertex tmp = v1; v1 = v3; v3 = tmp; } + if(v3.y < v2.y){ Vertex tmp = v2; v2 = v3; v3 = tmp; } + + if(triangleSetup(&tri, v1, v2, v3)) + return; + + // Current scanline start and end + int xn[2] = { tri.x, tri.x }; + int a = !tri.right; // left edge + int b = tri.right; // right edge + + // If upper triangle has no height, only do the lower part + if(tri.dy[0] == 0) + goto secondtri; + while(tri.y < tri.yend){ + /* TODO: is this the righ way to step the edges? */ + + /* Step x and interpolated value down left edge */ + while(tri.e[a] <= -tri.dy[a]){ + xn[a]--; + tri.e[a] += tri.dy[a]; + sub1(&tri.v, &tri.gx); + } + while(tri.e[a] > 0){ + xn[a]++; + tri.e[a] -= tri.dy[a]; + add1(&tri.v, &tri.gx); + } + + /* Step x down right edge */ + while(tri.e[b] <= -tri.dy[b]){ + xn[b]--; + tri.e[b] += tri.dy[b]; + } + while(tri.e[b] > 0){ + xn[b]++; + tri.e[b] -= tri.dy[b]; + } + + // When we reach the mid vertex, change state and jump to start of loop again + // TODO: this is a bit ugly in here...can we fix it? + if(tri.y == tri.ymid){ + secondtri: + tri.dx[0] = tri.dx[2]; + tri.dy[0] = tri.dy[2]; + // Either the while prevents this or we returned early because dy1 == 0 + assert(tri.dy[0] != 0); + stepx = v2.x - (xn[0]<<4); + stepy = v2.y - (tri.y<<4); + tri.e[0] = (-stepy*tri.dx[0] + stepx*tri.dy[0]) >> 4; + + tri.ymid = -1; // so we don't do this again + continue; + } + + /* Rasterize one line */ + tri.s = tri.v; + for(tri.x = xn[a]; tri.x < xn[b]; tri.x++){ + guard(&tri.s); + c.r = tri.s.r >> 12; + c.g = tri.s.g >> 12; + c.b = tri.s.b >> 12; + c.a = tri.s.a >> 12; + if(srTexEnable && srTexture){ + float w = 1.0f/tri.s.q; + float s = tri.s.s * w; + float t = tri.s.t * w; + int u = s * srTexture->w * 16; + int v = t * srTexture->h * 16; + Color texc = sampletex_nearest(u, v); + c = texfunc(texc, c); + } + if(srFogEnable){ + const int f = tri.s.f >> 12; + c.r = (f*c.r >> 8) + ((255 - f)*srFogCol.r >> 8); + c.g = (f*c.g >> 8) + ((255 - f)*srFogCol.g >> 8); + c.b = (f*c.b >> 8) + ((255 - f)*srFogCol.b >> 8); + } + putpixel(canvas, mkpnt(tri.x, tri.y, tri.s.z>>14), c); + add1(&tri.s, &tri.gx); + } + + /* Step in y */ + tri.y++; + tri.e[a] += tri.dx[a]; + tri.e[b] += tri.dx[b]; + add1(&tri.v, &tri.gy); + } +} + +Canvas *canvas; + +} + +using namespace rw; + +void +rastest_renderTriangles(RWDEVICE::Im2DVertex *scrverts, int32 numVerts, uint16 *indices, int32 numTris) +{ + int i; + RGBA col; + rs::Vertex v[3]; + RWDEVICE::Im2DVertex *iv; + + rs::srDepthTestEnable = 1; + rs::srAlphaTestEnable = 0; + rs::srTexEnable = 0; + rs::srAlphaBlendEnable = 0; + + while(numTris--){ + for(i = 0; i < 3; i++){ + iv = &scrverts[indices[i]]; + v[i].x = iv->getScreenX() * 16.0f; + v[i].y = iv->getScreenY() * 16.0f; + v[i].z = 16777216*(1.0f-iv->getScreenZ()); + v[i].q = iv->getRecipCameraZ(); + col = iv->getColor(); + v[i].r = col.red; + v[i].g = col.green; + v[i].b = col.blue; + v[i].a = col.alpha; + v[i].f = 0; + v[i].s = iv->u*iv->getRecipCameraZ(); + v[i].t = iv->v*iv->getRecipCameraZ(); + } + drawTriangle(rs::canvas, v[0], v[1], v[2]); + + indices += 3; + } +} + +extern rw::Raster *testras; + +void +beginSoftras(void) +{ + Camera *cam = (Camera*)engine->currentCamera; + + if(rs::canvas == nil || + cam->frameBuffer->width != rs::canvas->w || + cam->frameBuffer->height != rs::canvas->h){ + rs::canvas = rs::makecanvas(cam->frameBuffer->width, cam->frameBuffer->height); + testras = rw::Raster::create(rs::canvas->w, rs::canvas->h, 32, rw::Raster::C8888); + } + + clearcanvas(rs::canvas); + rs::srScissorX0 = 0; + rs::srScissorX1 = rs::canvas->w-1; + rs::srScissorY0 = 0; + rs::srScissorY1 = rs::canvas->h-1; +} + +void +endSoftras(void) +{ + int i; + uint8 *dst = testras->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + if(dst == nil) + return; + uint8 *src = rs::canvas->fb; + for(i = 0; i < rs::canvas->w*rs::canvas->h; i++){ + dst[0] = src[1]; + dst[1] = src[2]; + dst[2] = src[3]; + dst[3] = src[0]; + dst += 4; + src += 4; + } + // abgr in canvas + // bgra in raster + testras->unlock(0); +} + + +/* +typedef struct PixVert PixVert; +struct PixVert +{ + float x, y, z, q; + int r, g, b, a; + float u, v; +}; +#include "test.inc" + +void +drawtest(void) +{ + int i, j; + rs::Vertex v[3]; + + rs::srDepthTestEnable = 1; + rs::srAlphaTestEnable = 0; + rs::srTexEnable = 0; + rs::srAlphaBlendEnable = 0; + + for(i = 0; i < nelem(verts); i += 3){ + for(j = 0; j < 3; j++){ + v[j].x = verts[i+j].x * 16.0f; + v[j].y = verts[i+j].y * 16.0f; + v[j].z = 16777216*(1.0f - verts[i+j].z); + v[j].q = verts[i+j].q; + v[j].r = verts[i+j].r; + v[j].g = verts[i+j].g; + v[j].b = verts[i+j].b; + v[j].a = verts[i+j].a; + v[j].f = 0; + v[j].s = verts[i+j].u*v[j].q; + v[j].t = verts[i+j].v*v[j].q; + } + drawTriangle(rs::canvas, v[0], v[1], v[2]); + } +//exit(0); +} +*/ diff --git a/vendor/librw/tools/playground/splines.cpp b/vendor/librw/tools/playground/splines.cpp new file mode 100644 index 0000000..73d32eb --- /dev/null +++ b/vendor/librw/tools/playground/splines.cpp @@ -0,0 +1,520 @@ +#include +#include + +#include + +using namespace rw; +using namespace RWDEVICE; + +static Im3DVertex im3dVerts[1024]; +static int numImVerts; +static rw::PrimitiveType imPrim; +static Im3DVertex imVert; + +void +BeginIm3D(rw::PrimitiveType prim) +{ + numImVerts = 0; + imPrim = prim; +} + +void +EndIm3D(void) +{ + rw::im3d::Transform(im3dVerts, numImVerts, nil, rw::im3d::EVERYTHING); + rw::im3d::RenderPrimitive(imPrim); + rw::im3d::End(); +} + +void +AddVertex(const rw::V3d &vert) +{ + if(numImVerts >= 1024){ + EndIm3D(); + switch(imPrim){ + case PRIMTYPEPOLYLINE: + im3dVerts[0] = im3dVerts[numImVerts-1]; + numImVerts = 1; + break; + case PRIMTYPETRISTRIP: + // TODO: winding? + im3dVerts[0] = im3dVerts[numImVerts-2]; + im3dVerts[1] = im3dVerts[numImVerts-1]; + numImVerts = 2; + break; + case PRIMTYPETRIFAN: + im3dVerts[1] = im3dVerts[numImVerts-1]; + numImVerts = 2; + break; + default: + numImVerts = 0; + } + } + + imVert.setX(vert.x); + imVert.setY(vert.y); + imVert.setZ(vert.z); + im3dVerts[numImVerts++] = imVert; +} + +float epsilon = 0.000001; + +class RBCurve +{ +public: + int degree; + std::vector verts; + std::vector weights; // for rational + std::vector knots; + + V3d eval(float u); + void drawHull(void); + void drawSpline(void); +}; + +class RBSurf +{ +public: + int degreeU, degreeV; + int numU, numV; + std::vector verts; + std::vector weights; + std::vector knotsU, knotsV; + + void update(void); + V3d eval(float u, float v); + void drawHull(void); + void drawIsoparms(void); + void drawShaded(void); +}; + +float div0(float a, float b) { return b == 0.0f ? a : a/b; } + +// naive algorithm +float +evalBasis(int i, int d, float u, float knots[]) +{ + if(d == 0){ + if(knots[i] <= u && u < knots[i+1]) + return 1.0f; + return 0.0f; + } + + float b0 = evalBasis(i, d-1, u, knots); + float b1 = evalBasis(i+1, d-1, u, knots); + return b0*div0(u-knots[i], knots[i+d] - knots[i]) + b1*div0(knots[i+d+1]-u, knots[i+d+1] - knots[i+1]); +} + +float +evalBasisFast(int i, int d, float u, float knots[]) +{ + int r, j; + float tmp[10]; + + // degree 0 values + for(j = 0; j < d+1; j++) + tmp[j] = knots[i+j] <= u && u < knots[i+j+1] ? 1.0f : 0.0f; + + // build up from degree zero + for(r = d, d = 1; r > 0; r--, d++){ + for(j = 0; j < r; j++){ + float t1 = div0(u-knots[i+j], knots[i+j + d] - knots[i+j]); + float t2 = div0(knots[i+j + d+1]-u, knots[i+j + d+1] - knots[i+j + 1]); + tmp[j] = tmp[j]*t1 + tmp[j+1]*t2; + } + } + return tmp[0]; +} + +V3d +RBCurve::eval(float u) +{ + int i; + V3d vert = { 0.0f, 0.0f, 0.0f }; + float w = 0.0f; + + // Find knots we're interested in + for(i = 0; i < knots.size(); i++) + if(knots[i] <= u && u < knots[i+1]) + break; + int startI = i-degree; + int endI = i+1; + + for(i = startI; i < endI; i++){ + float r = evalBasisFast(i, degree, u, &knots[0]); + w += weights[i]*r; + vert = add(vert, scale(verts[i], weights[i]*r)); + } + return scale(vert, div0(1.0f,w)); +} + +void +RBCurve::drawHull(void) +{ + int i; + + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + + BeginIm3D(rw::PRIMTYPEPOLYLINE); + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(138, 72, 51, 255); +// imVert.setColor(228, 172, 121, 255); + for(i = 0; i < verts.size(); i++) + AddVertex(verts[i]); + EndIm3D(); +} + +void +RBCurve::drawSpline(void) +{ + int i; + float u, endu; + V3d vert; + + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + BeginIm3D(rw::PRIMTYPEPOLYLINE); + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(0, 4, 96, 255); + + u = knots[0]; + endu = knots[knots.size()-1] - epsilon; + float uinc = (endu-knots[0])/40.0f; + for(;; u += uinc){ + if(u > endu) + u = endu; + AddVertex(eval(u)); + if(u >= endu) + break; + } + + EndIm3D(); +} + +void +RBSurf::update(void) +{ + numU = knotsU.size() - degreeU - 1; + numV = knotsV.size() - degreeV - 1; +} + +V3d +RBSurf::eval(float u, float v) +{ + int i, j; + V3d vert = { 0.0f, 0.0f, 0.0f }; + float w = 0.0f; + + float basisU[10], basisV[10]; + + // Find knots we're interested in + int k; + for(k = 0; k < knotsU.size(); k++) + if(knotsU[k] <= u && u < knotsU[k+1]) + break; + int startI = k-degreeU; + int endI = k+1; + for(k = 0; k < knotsV.size(); k++) + if(knotsV[k] <= v && v < knotsV[k+1]) + break; + int startJ = k-degreeV; + int endJ = k+1; + + for(i = startI; i < endI; i++) basisU[i-startI] = evalBasisFast(i, degreeU, u, &knotsU[0]); + for(j = startJ; j < endJ; j++) basisV[j-startJ] = evalBasisFast(j, degreeV, v, &knotsV[0]); + + for(j = startJ; j < endJ; j++) + for(i = startI; i < endI; i++){ + float r = basisV[j-startJ]*basisU[i-startI]; + w += weights[j*numU + i]*r; + vert = add(vert, scale(verts[j*numU + i], weights[j*numU + i]*r)); + } + return scale(vert, div0(1.0f,w)); +} + +void +RBSurf::drawHull(void) +{ + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(138, 72, 51, 255); +// imVert.setColor(228, 172, 121, 255); + + int iu, iv; + for(iv = 0; iv < numV; iv++){ + BeginIm3D(rw::PRIMTYPEPOLYLINE); + for(iu = 0; iu < numU; iu++) + AddVertex(verts[iu + iv*numU]); + EndIm3D(); + } + + for(iu = 0; iu < numU; iu++){ + BeginIm3D(rw::PRIMTYPEPOLYLINE); + for(iv = 0; iv < numV; iv++) + AddVertex(verts[iu + iv*numU]); + EndIm3D(); + } +} + +void +RBSurf::drawIsoparms(void) +{ + V3d vert; + int iu, iv; + float u, v; + + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(0, 4, 96, 255); + + float endu = knotsU[knotsU.size()-1] - epsilon; + float endv = knotsU[knotsV.size()-1] - epsilon; + float uinc = (endu-knotsU[0])/40.0f; + float vinc = (endv-knotsV[0])/40.0f; + + v = -100000.0f; + for(iv = 0; iv < knotsV.size(); iv++){ + if(knotsV[iv] <= v) continue; + v = knotsV[iv]; + if(v > endv) v = endv; + + BeginIm3D(rw::PRIMTYPEPOLYLINE); + for(u = knotsU[0];; u += uinc){ + if(u > endu) + u = endu; + AddVertex(eval(u, v)); + if(u >= endu) + break; + } + EndIm3D(); + } + + u = -100000.0f; + for(iu = 0; iu < knotsU.size(); iu++){ + if(knotsU[iu] <= u) continue; + u = knotsU[iu]; + if(u > endu) u = endu; + + BeginIm3D(rw::PRIMTYPEPOLYLINE); + for(v = knotsV[0];; v += vinc){ + if(v > endv) + v = endv; + AddVertex(eval(u, v)); + if(v >= endv) + break; + } + EndIm3D(); + } +} + +void +RBSurf::drawShaded(void) +{ + V3d vert; + int iu, iv; + float u, v; + + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(0, 128, 240, 255); + + float endu = knotsU[knotsU.size()-1] - epsilon; + float endv = knotsU[knotsV.size()-1] - epsilon; + float uinc = (endu-knotsU[0])/40.0f; + float vinc = (endv-knotsV[0])/40.0f; + + float vnext; + for(v = knotsV[0];; v = vnext){ + if(v > endv) + v = endv; + vnext = v + vinc; + + BeginIm3D(rw::PRIMTYPETRISTRIP); + for(u = knotsU[0];; u += uinc){ + if(u > endu) + u = endu; + + AddVertex(eval(u, v)); + AddVertex(eval(u, vnext)); + + if(u >= endu) + break; + } + EndIm3D(); + if(vnext >= endv) + break; + } +} + + +RBCurve testspline1, testspline2; +RBSurf testsurf; + +void +initsplines(void) +{ + V3d vert; + + testspline1.degree = 3; + testspline1.verts.clear(); + testspline1.weights.clear(); + vert.set(-30.63383, 22.65459, 0); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + vert.set(13.50783, 33.01786, 15.06403); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + vert.set(34.252, -10.36327, 15.06403); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + vert.set(-7.959972, -1.205032, 0); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + vert.set(6.995127, -41.32158, -18.19684); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + + testspline1.knots.clear(); + testspline1.knots.push_back(0); + testspline1.knots.push_back(0); + testspline1.knots.push_back(0); + testspline1.knots.push_back(0); + testspline1.knots.push_back(1); + testspline1.knots.push_back(2); + testspline1.knots.push_back(2); + testspline1.knots.push_back(2); + testspline1.knots.push_back(2); + + + testspline2.degree = 2; + testspline2.verts.clear(); +#define V(x, y, z) \ + vert.set(x, y, z); \ + testspline2.verts.push_back(scale(vert, 1.0f/20.0f)); \ + testspline2.weights.push_back(1.0f); + V(-61.9913, 9.158239, 0); + V(-32.32231, 27.23371, 0); + V(25.80961, -4.820126, 0); + testspline2.knots.clear(); + testspline2.knots.push_back(0); + testspline2.knots.push_back(0); + testspline2.knots.push_back(0); + testspline2.knots.push_back(1); + testspline2.knots.push_back(1); + testspline2.knots.push_back(1); + +/* + testspline2 = testspline1; +// testspline2.knots.clear(); +// testspline2.knots.push_back(0); +// testspline2.knots.push_back(0); +// testspline2.knots.push_back(0); +// testspline2.knots.push_back(0); +// testspline2.knots.push_back(3); +// testspline2.knots.push_back(4); +// testspline2.knots.push_back(4); +// testspline2.knots.push_back(4); +// testspline2.knots.push_back(4); + testspline1.weights[2] = 5.0f; +*/ + +#define V(x, y, z) \ + vert.set(x, y, z); \ + testsurf.verts.push_back(scale(vert, 1.0f/20.0f)); \ + testsurf.weights.push_back(1.0f); + + testsurf.degreeU = 3; + testsurf.degreeV = 3; + testsurf.verts.clear(); + testsurf.weights.clear(); + V(-69.22764, -0, 12.77366); + V(-48.72468, 0, 29.16251); + V(-24.84476, 0, 39.52605); + V(22.43265, -0, 45.79238); + V(36.4229, 0, 28.9215); + V(61.02645, 0, 6.74835); + V(86.35364, -0, 20.96809); + V(-69.22764, 9.286676, 12.77366); + V(-48.72468, 9.286676, 29.16251); + V(-24.84476, 9.286676, 39.52605); + V(22.43265, 9.286676, 45.79238); + V(36.4229, 9.286676, 28.9215); + V(61.02645, 9.286676, 6.74835); + V(86.35364, 9.286676, 20.96809); + V(-68.13416, 23.51821, 6.114491); + V(-48.19925, 23.51821, 22.27994); + V(-26.91943, 23.51821, 33.20763); + V(18.69658, 23.51821, 39.0488); + V(27.88844, 23.51821, 30.80604); + V(57.49908, 23.51821, -0.6488895); + V(86.35364, 23.51821, 14.21974); + V(-67.00163, 52.46369, -0.7825046); + V(-47.65504, 52.46369, 15.15157); + V(-29.06818, 52.46369, 26.66356); + V(14.82709, 52.46369, 32.06438); + V(19.04919, 52.46369, 32.75788); + V(53.84574, 52.46369, -8.310311); + V(86.35364, 52.46369, 7.230374); + V(-67.86079, 70.07219, 4.449699); + V(-48.06789, 70.07219, 20.5593); + V(-27.43809, 70.07219, 31.62803); + V(17.76257, 70.07219, 37.36291); + V(25.75483, 70.07219, 31.27717); + V(56.61724, 70.07219, -2.498198); + V(86.35364, 70.07219, 12.53265); + testsurf.knotsU.clear(); + testsurf.knotsU.push_back(0); + testsurf.knotsU.push_back(0); + testsurf.knotsU.push_back(0); + testsurf.knotsU.push_back(0); + testsurf.knotsU.push_back(0.25); + testsurf.knotsU.push_back(0.5); + testsurf.knotsU.push_back(0.75); + testsurf.knotsU.push_back(1); + testsurf.knotsU.push_back(1); + testsurf.knotsU.push_back(1); + testsurf.knotsU.push_back(1); + testsurf.knotsV.clear(); + testsurf.knotsV.push_back(0); + testsurf.knotsV.push_back(0); + testsurf.knotsV.push_back(0); + testsurf.knotsV.push_back(0); + testsurf.knotsV.push_back(0.5); + testsurf.knotsV.push_back(1); + testsurf.knotsV.push_back(1); + testsurf.knotsV.push_back(1); + testsurf.knotsV.push_back(1); + + testsurf.update(); +} + +void +rendersplines(void) +{ + testspline1.drawHull(); + testspline1.drawSpline(); + +// testspline2.drawHull(); +// testspline2.drawSpline(); + + testsurf.drawHull(); + testsurf.drawShaded(); + testsurf.drawIsoparms(); +} diff --git a/vendor/librw/tools/playground/tl_tests.cpp b/vendor/librw/tools/playground/tl_tests.cpp new file mode 100644 index 0000000..7cfc59a --- /dev/null +++ b/vendor/librw/tools/playground/tl_tests.cpp @@ -0,0 +1,766 @@ +#include +#include + +extern bool dosoftras; + +using namespace rw; +using namespace RWDEVICE; + +void rastest_renderTriangles(RWDEVICE::Im2DVertex *scrverts, int32 verts, uint16 *indices, int32 numTris); + +// +// This is a test to implement T&L in software and render with Im2D +// + +namespace gen { + +#define MAX_LIGHTS 8 + +struct Directional { + V3d at; + RGBAf color; +}; +static Directional directionals[MAX_LIGHTS]; +static int32 numDirectionals; +static RGBAf ambLight; + +static void +enumLights(Matrix *lightmat) +{ + int32 n; + World *world; + + world = (World*)engine->currentWorld; + ambLight.red = 0.0; + ambLight.green = 0.0; + ambLight.blue = 0.0; + ambLight.alpha = 0.0; + numDirectionals = 0; + // only unpositioned lights right now + FORLIST(lnk, world->globalLights){ + Light *l = Light::fromWorld(lnk); + if(l->getType() == Light::DIRECTIONAL){ + if(numDirectionals >= MAX_LIGHTS) + continue; + n = numDirectionals++; + V3d::transformVectors(&directionals[n].at, &l->getFrame()->getLTM()->at, 1, lightmat); + directionals[n].color = l->color; + directionals[n].color.alpha = 0.0f; + }else if(l->getType() == Light::AMBIENT){ + ambLight.red += l->color.red; + ambLight.green += l->color.green; + ambLight.blue += l->color.blue; + } + } +} + +struct ObjSpace3DVertex +{ + V3d objVertex; + V3d objNormal; + RGBA color; + TexCoords texCoords; +}; + +enum { + CLIPXLO = 0x01, + CLIPXHI = 0x02, + CLIPX = 0x03, + CLIPYLO = 0x04, + CLIPYHI = 0x08, + CLIPY = 0x0C, + CLIPZLO = 0x10, + CLIPZHI = 0x20, + CLIPZ = 0x30, +}; + +struct CamSpace3DVertex +{ + V3d camVertex; + uint8 clipFlags; + RGBAf color; + TexCoords texCoords; +}; + +struct InstanceData +{ + uint16 *indices; + int32 numIndices; + ObjSpace3DVertex *vertices; + int32 numVertices; + // int vertStride; // not really needed right now + Material *material; + Mesh *mesh; +}; + +struct InstanceDataHeader : public rw::InstanceDataHeader +{ + uint32 serialNumber; + ObjSpace3DVertex *vertices; + uint16 *indices; + InstanceData *inst; +}; + +static void +instanceAtomic(Atomic *atomic) +{ + static V3d zeroNorm = { 0.0f, 0.0f, 0.0f }; + static RGBA black = { 0, 0, 0, 255 }; + static TexCoords zeroTex = { 0.0f, 0.0f }; + int i; + uint j; + int x, x1, x2, x3; + Geometry *geo; + MeshHeader *header; + Mesh *mesh; + InstanceDataHeader *insthead; + InstanceData *inst; + uint32 firstVert; + uint16 *srcindices, *dstindices; + + geo = atomic->geometry; + if(geo->instData) + return; + header = geo->meshHeader; + int numTris; + if(header->flags & MeshHeader::TRISTRIP) + numTris = header->totalIndices - 2*header->numMeshes; + else + numTris = header->totalIndices / 3; + int size; + size = sizeof(InstanceDataHeader) + header->numMeshes*sizeof(InstanceData) + + geo->numVertices*sizeof(ObjSpace3DVertex) + numTris*6*sizeof(uint16); + insthead = (InstanceDataHeader*)rwNew(size, ID_GEOMETRY); + geo->instData = insthead; + insthead->platform = 0; + insthead->serialNumber = header->serialNum; + inst = (InstanceData*)(insthead+1); + insthead->inst = inst; + insthead->vertices = (ObjSpace3DVertex*)(inst+header->numMeshes); + dstindices = (uint16*)(insthead->vertices+geo->numVertices); + insthead->indices = dstindices; + + // TODO: morphing + MorphTarget *mt = geo->morphTargets; + for(i = 0; i < geo->numVertices; i++){ + insthead->vertices[i].objVertex = mt->vertices[i]; + if(geo->flags & Geometry::NORMALS) + insthead->vertices[i].objNormal = mt->normals[i]; + else + insthead->vertices[i].objNormal = zeroNorm; + if(geo->flags & Geometry::PRELIT) + insthead->vertices[i].color = geo->colors[i]; + else + insthead->vertices[i].color = black; + if(geo->numTexCoordSets > 0) + insthead->vertices[i].texCoords = geo->texCoords[0][i]; + else + insthead->vertices[i].texCoords = zeroTex; + } + + mesh = header->getMeshes(); + for(i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &firstVert, &inst->numVertices); + inst->indices = dstindices; + inst->vertices = &insthead->vertices[firstVert]; + inst->mesh = mesh; + inst->material = mesh->material; + srcindices = mesh->indices; + if(header->flags & MeshHeader::TRISTRIP){ + inst->numIndices = 0; + x = 0; + for(j = 0; j < mesh->numIndices-2; j++){ + x1 = srcindices[j+x]; + x ^= 1; + x2 = srcindices[j+x]; + x3 = srcindices[j+2]; + if(x1 != x2 && x2 != x3 && x1 != x3){ + dstindices[0] = x1; + dstindices[1] = x2; + dstindices[2] = x3; + dstindices += 3; + inst->numIndices += 3; + } + } + }else{ + inst->numIndices = mesh->numIndices; + for(j = 0; j < mesh->numIndices; j += 3){ + dstindices[0] = srcindices[j+0] - firstVert; + dstindices[1] = srcindices[j+1] - firstVert; + dstindices[2] = srcindices[j+2] - firstVert; + dstindices += 3; + } + } + + inst++; + mesh++; + } +} + +struct MeshState +{ + int32 flags; + Matrix obj2cam; + Matrix obj2world; + int32 numVertices; + int32 numPrimitives; + SurfaceProperties surfProps; + RGBA matCol; +}; + +static void +cam2screen(Im2DVertex *scrvert, CamSpace3DVertex *camvert) +{ + RGBA col; + float32 recipZ; + Camera *cam = (Camera*)engine->currentCamera; + int32 width = cam->frameBuffer->width; + int32 height = cam->frameBuffer->height; + recipZ = 1.0f/camvert->camVertex.z; + +// scrvert->setScreenX(camvert->camVertex.x * recipZ * width); +// scrvert->setScreenY(camvert->camVertex.y * recipZ * height); + scrvert->setScreenX(camvert->camVertex.x * recipZ * width/2 + width/4); + scrvert->setScreenY(camvert->camVertex.y * recipZ * height/2 + height/4); + scrvert->setScreenZ(recipZ * cam->zScale + cam->zShift); + scrvert->setCameraZ(camvert->camVertex.z); + scrvert->setRecipCameraZ(recipZ); + scrvert->setU(camvert->texCoords.u, recipZ); + scrvert->setV(camvert->texCoords.v, recipZ); + convColor(&col, &camvert->color); + scrvert->setColor(col.red, col.green, col.blue, col.alpha); +} + +static void +transform(MeshState *mstate, ObjSpace3DVertex *objverts, CamSpace3DVertex *camverts, Im2DVertex *scrverts) +{ + int32 i; + float32 z; + Camera *cam = (Camera*)engine->currentCamera; + + for(i = 0; i < mstate->numVertices; i++){ + V3d::transformPoints(&camverts[i].camVertex, &objverts[i].objVertex, 1, &mstate->obj2cam); + convColor(&camverts[i].color, &objverts[i].color); + camverts[i].texCoords = objverts[i].texCoords; + + camverts[i].clipFlags = 0; + z = camverts[i].camVertex.z; + // 0 < x < z + if(camverts[i].camVertex.x >= z) camverts[i].clipFlags |= CLIPXHI; + if(camverts[i].camVertex.x <= 0) camverts[i].clipFlags |= CLIPXLO; + // 0 < y < z + if(camverts[i].camVertex.y >= z) camverts[i].clipFlags |= CLIPYHI; + if(camverts[i].camVertex.y <= 0) camverts[i].clipFlags |= CLIPYLO; + // near < z < far + if(z >= cam->farPlane) camverts[i].clipFlags |= CLIPZHI; + if(z <= cam->nearPlane) camverts[i].clipFlags |= CLIPZLO; + + cam2screen(&scrverts[i], &camverts[i]); + } +} + +static void +light(MeshState *mstate, ObjSpace3DVertex *objverts, CamSpace3DVertex *camverts) +{ + int32 i; + RGBAf colf; + RGBAf amb = ambLight; + amb = scale(ambLight, mstate->surfProps.ambient); + for(i = 0; i < mstate->numVertices; i++){ + camverts[i].color = add(camverts[i].color, amb); + if((mstate->flags & Geometry::NORMALS) == 0) + continue; + for(int32 k = 0; k < numDirectionals; k++){ + float32 f = dot(objverts[i].objNormal, neg(directionals[k].at)); + if(f <= 0.0f) continue; + f *= mstate->surfProps.diffuse; + colf = scale(directionals[k].color, f); + camverts[i].color = add(camverts[i].color, colf); + } + } +} + +static void +postlight(MeshState *mstate, CamSpace3DVertex *camverts, Im2DVertex *scrverts) +{ + int32 i; + RGBA col; + RGBAf colf; + for(i = 0; i < mstate->numVertices; i++){ + convColor(&colf, &mstate->matCol); + camverts[i].color = modulate(camverts[i].color, colf); + clamp(&camverts[i].color); + convColor(&col, &camverts[i].color); + scrverts[i].setColor(col.red, col.green, col.blue, col.alpha); + } +} + +static int32 +cullTriangles(MeshState *mstate, CamSpace3DVertex *camverts, uint16 *indices, uint16 *clipindices) +{ + int32 i; + int32 x1, x2, x3; + int32 newNumPrims; + int32 numClip; + + newNumPrims = 0; + numClip = 0; + for(i = 0; i < mstate->numPrimitives; i++, indices += 3){ + x1 = indices[0]; + x2 = indices[1]; + x3 = indices[2]; + // Only a simple frustum call + if(camverts[x1].clipFlags & + camverts[x2].clipFlags & + camverts[x3].clipFlags) + continue; + if(camverts[x1].clipFlags | + camverts[x2].clipFlags | + camverts[x3].clipFlags) + numClip++; + // The Triangle is in, probably + clipindices[0] = x1; + clipindices[1] = x2; + clipindices[2] = x3; + clipindices += 3; + newNumPrims++; + } + mstate->numPrimitives = newNumPrims; + return numClip; +} + +static void +interpVertex(CamSpace3DVertex *out, CamSpace3DVertex *v1, CamSpace3DVertex *v2, float32 t) +{ + float32 z; + float32 invt; + Camera *cam = (Camera*)engine->currentCamera; + + invt = 1.0f - t; + out->camVertex = add(scale(v1->camVertex, invt), scale(v2->camVertex, t)); + out->color = add(scale(v1->color, invt), scale(v2->color, t)); + out->texCoords.u = v1->texCoords.u*invt + v2->texCoords.u*t; + out->texCoords.v = v1->texCoords.v*invt + v2->texCoords.v*t; + + out->clipFlags = 0; + z = out->camVertex.z; + // 0 < x < z + if(out->camVertex.x >= z) out->clipFlags |= CLIPXHI; + if(out->camVertex.x <= 0) out->clipFlags |= CLIPXLO; + // 0 < y < z + if(out->camVertex.y >= z) out->clipFlags |= CLIPYHI; + if(out->camVertex.y <= 0) out->clipFlags |= CLIPYLO; + // near < z < far + if(z >= cam->farPlane) out->clipFlags |= CLIPZHI; + if(z <= cam->nearPlane) out->clipFlags |= CLIPZLO; +} + +static void +clipTriangles(MeshState *mstate, CamSpace3DVertex *camverts, Im2DVertex *scrverts, uint16 *indices, uint16 *clipindices) +{ + int32 i, j; + int32 x1, x2, x3; + int32 newNumPrims; + CamSpace3DVertex buf[18]; + CamSpace3DVertex *in, *out, *tmp; + int32 nin, nout; + float32 t; + Camera *cam = (Camera*)engine->currentCamera; + + newNumPrims = 0; + for(i = 0; i < mstate->numPrimitives; i++, indices += 3){ + x1 = indices[0]; + x2 = indices[1]; + x3 = indices[2]; + + if((camverts[x1].clipFlags | + camverts[x2].clipFlags | + camverts[x3].clipFlags) == 0){ + // all inside + clipindices[0] = x1; + clipindices[1] = x2; + clipindices[2] = x3; + clipindices += 3; + newNumPrims++; + continue; + } + + // set up triangle + in = &buf[0]; + out = &buf[9]; + in[0] = camverts[x1]; + in[1] = camverts[x2]; + in[2] = camverts[x3]; + nin = 3; + nout = 0; + +#define V(a) in[a].camVertex. + + // clip z near + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPZLO){ + t = (cam->nearPlane - V(x1)z)/(V(x2)z - V(x1)z); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPZLO) == 0) + out[nout++] = in[x2]; + } + // clip z far + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPZHI){ + t = (cam->farPlane - V(x1)z)/(V(x2)z - V(x1)z); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPZHI) == 0) + out[nout++] = in[x2]; + } + // clip y 0 + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPYLO){ + t = -V(x1)y/(V(x2)y - V(x1)y); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPYLO) == 0) + out[nout++] = in[x2]; + } + // clip y z + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPYHI){ + t = (V(x1)z - V(x1)y)/(V(x1)z - V(x1)y + V(x2)y - V(x2)z); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPYHI) == 0) + out[nout++] = in[x2]; + } + // clip x 0 + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPXLO){ + t = -V(x1)x/(V(x2)x - V(x1)x); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPXLO) == 0) + out[nout++] = in[x2]; + } + // clip x z + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPXHI){ + t = (V(x1)z - V(x1)x)/(V(x1)z - V(x1)x + V(x2)x - V(x2)z); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPXHI) == 0) + out[nout++] = in[x2]; + } + + // Insert new triangles + x1 = mstate->numVertices; + for(j = 0; j < nout; j++){ + x2 = mstate->numVertices++; + camverts[x2] = out[j]; + cam2screen(&scrverts[x2], &camverts[x2]); + } + x2 = x1+1; + for(j = 0; j < nout-2; j++){ + clipindices[0] = x1; + clipindices[1] = x2++; + clipindices[2] = x2; + clipindices += 3; + newNumPrims++; + } + } + mstate->numPrimitives = newNumPrims; +} + +static int32 +clipPoly(CamSpace3DVertex *in, int32 nin, CamSpace3DVertex *out, Plane *plane) +{ + int32 j; + int32 nout; + int32 x1, x2; + float32 d1, d2, t; + + nout = 0; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + + d1 = dot(plane->normal, in[x1].camVertex) + plane->distance; + d2 = dot(plane->normal, in[x2].camVertex) + plane->distance; + if(d1*d2 < 0.0f){ + t = d1/(d1 - d2); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if(d2 >= 0.0f) + out[nout++] = in[x2]; + } + return nout; +} + +static void +clipTriangles2(MeshState *mstate, CamSpace3DVertex *camverts, Im2DVertex *scrverts, uint16 *indices, uint16 *clipindices) +{ + int32 i, j; + int32 x1, x2, x3; + int32 newNumPrims; + CamSpace3DVertex buf[18]; + CamSpace3DVertex *in, *out; + int32 nout; + Camera *cam = (Camera*)engine->currentCamera; + + Plane planes[6] = { + { { 0.0f, 0.0f, 1.0f }, -cam->nearPlane }, // z = near + { { 0.0f, 0.0f, -1.0f }, cam->farPlane }, // z = far + + { { -1.0f, 0.0f, 1.0f }, 0.0f }, // x = w +// { { 1.0f, 0.0f, 1.0f }, 0.0f }, // x = -w + { { 1.0f, 0.0f, 0.0f }, 0.0f }, // x = 0 + + { { 0.0f, -1.0f, 1.0f }, 0.0f }, // y = w +// { { 0.0f, 1.0f, 1.0f }, 0.0f } // y = -1 + { { 0.0f, 1.0f, 0.0f }, 0.0f } // y = 0 + }; + + newNumPrims = 0; + for(i = 0; i < mstate->numPrimitives; i++, indices += 3){ + x1 = indices[0]; + x2 = indices[1]; + x3 = indices[2]; + + if((camverts[x1].clipFlags | + camverts[x2].clipFlags | + camverts[x3].clipFlags) == 0){ + // all inside + clipindices[0] = x1; + clipindices[1] = x2; + clipindices[2] = x3; + clipindices += 3; + newNumPrims++; + continue; + } + + // set up triangle + in = &buf[0]; + out = &buf[9]; + in[0] = camverts[x1]; + in[1] = camverts[x2]; + in[2] = camverts[x3]; + nout = 0; + + // clip here + if(nout = clipPoly(in, 3, out, &planes[0]), nout == 0) continue; + if(nout = clipPoly(out, nout, in, &planes[1]), nout == 0) continue; + if(nout = clipPoly(in, nout, out, &planes[2]), nout == 0) continue; + if(nout = clipPoly(out, nout, in, &planes[3]), nout == 0) continue; + if(nout = clipPoly(in, nout, out, &planes[4]), nout == 0) continue; + if(nout = clipPoly(out, nout, in, &planes[5]), nout == 0) continue; + out = in; + + // Insert new triangles + x1 = mstate->numVertices; + for(j = 0; j < nout; j++){ + x2 = mstate->numVertices++; + camverts[x2] = out[j]; + cam2screen(&scrverts[x2], &camverts[x2]); + } + x2 = x1+1; + for(j = 0; j < nout-2; j++){ + clipindices[0] = x1; + clipindices[1] = x2++; + clipindices[2] = x2; + clipindices += 3; + newNumPrims++; + } + } + mstate->numPrimitives = newNumPrims; +} + +static void +submitTriangles(RWDEVICE::Im2DVertex *scrverts, int32 numVerts, uint16 *indices, int32 numTris) +{ + rw::SetRenderStatePtr(rw::TEXTURERASTER, nil); + if(dosoftras) + rastest_renderTriangles(scrverts, numVerts, indices, numTris); + else{ + //int i; + //for(i = 0; i < numVerts; i++){ + // scrverts[i].x = (int)(scrverts[i].x*16.0f) / 16.0f; + // scrverts[i].y = (int)(scrverts[i].y*16.0f) / 16.0f; + //} + im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, scrverts, numVerts, + indices, numTris*3); + } +} + + +static void +drawMesh(MeshState *mstate, ObjSpace3DVertex *objverts, uint16 *indices) +{ + CamSpace3DVertex *camverts; + Im2DVertex *scrverts; + uint16 *cullindices, *clipindices; + uint32 numClip; + + camverts = rwNewT(CamSpace3DVertex, mstate->numVertices, MEMDUR_FUNCTION); + scrverts = rwNewT(Im2DVertex, mstate->numVertices, MEMDUR_FUNCTION); + cullindices = rwNewT(uint16, mstate->numPrimitives*3, MEMDUR_FUNCTION); + + transform(mstate, objverts, camverts, scrverts); + + numClip = cullTriangles(mstate, camverts, indices, cullindices); + +// int32 i; +// for(i = 0; i < mstate->numVertices; i++){ +// if(camverts[i].clipFlags & CLIPX) +// camverts[i].color.red = 255; +// if(camverts[i].clipFlags & CLIPY) +// camverts[i].color.green = 255; +// if(camverts[i].clipFlags & CLIPZ) +// camverts[i].color.blue = 255; +// } + + light(mstate, objverts, camverts); + +// mstate->matCol.red = 255; +// mstate->matCol.green = 255; +// mstate->matCol.blue = 255; + + postlight(mstate, camverts, scrverts); + + // each triangle can have a maximum of 9 vertices (7 triangles) after clipping + // so resize to whatever we may need + camverts = rwResizeT(CamSpace3DVertex, camverts, mstate->numVertices + numClip*9, MEMDUR_FUNCTION); + scrverts = rwResizeT(Im2DVertex, scrverts, mstate->numVertices + numClip*9, MEMDUR_FUNCTION); + clipindices = rwNewT(uint16, mstate->numPrimitives*3 + numClip*7*3, MEMDUR_FUNCTION); + +// clipTriangles(mstate, camverts, scrverts, cullindices, clipindices); + clipTriangles2(mstate, camverts, scrverts, cullindices, clipindices); + + submitTriangles(scrverts, mstate->numVertices, clipindices, mstate->numPrimitives); + + rwFree(camverts); + rwFree(scrverts); + rwFree(cullindices); + rwFree(clipindices); +} + +static void +drawAtomic(Atomic *atomic) +{ + MeshState mstate; + Matrix lightmat; + Geometry *geo; + MeshHeader *header; + InstanceData *inst; + int i; + Camera *cam = (Camera*)engine->currentCamera; + + instanceAtomic(atomic); + + mstate.obj2world = *atomic->getFrame()->getLTM(); + mstate.obj2cam = mstate.obj2world; + mstate.obj2cam.transform(&cam->viewMatrix, COMBINEPOSTCONCAT); + Matrix::invert(&lightmat, &mstate.obj2world); + enumLights(&lightmat); + + geo = atomic->geometry; + header = geo->meshHeader; + inst = ((InstanceDataHeader*)geo->instData)->inst; + for(i = 0; i < header->numMeshes; i++){ + mstate.flags = geo->flags; + mstate.numVertices = inst->numVertices; + mstate.numPrimitives = inst->numIndices / 3; + mstate.surfProps = inst->material->surfaceProps; + mstate.matCol = inst->material->color; + drawMesh(&mstate, inst->vertices, inst->indices); + inst++; + } +} + +void +tlTest(Clump *clump) +{ + FORLIST(lnk, clump->atomics){ + Atomic *a = Atomic::fromClump(lnk); + drawAtomic(a); + } +} + +} + +static Im2DVertex *clipverts; +static int32 numClipverts; + +void +genIm3DTransform(void *vertices, int32 numVertices, Matrix *world) +{ + Im3DVertex *objverts; + V3d pos; + Matrix xform; + Camera *cam; + int32 i; + objverts = (Im3DVertex*)vertices; + + cam = (Camera*)engine->currentCamera; + int32 width = cam->frameBuffer->width; + int32 height = cam->frameBuffer->height; + + + xform = cam->viewMatrix; + if(world) + xform.transform(world, COMBINEPRECONCAT); + + clipverts = rwNewT(Im2DVertex, numVertices, MEMDUR_EVENT); + numClipverts = numVertices; + + for(i = 0; i < numVertices; i++){ + V3d::transformPoints(&pos, &objverts[i].position, 1, &xform); + + float32 recipZ = 1.0f/pos.z; + RGBA c = objverts[i].getColor(); + + clipverts[i].setScreenX(pos.x * recipZ * width); + clipverts[i].setScreenY(pos.y * recipZ * height); + clipverts[i].setScreenZ(recipZ * cam->zScale + cam->zShift); + clipverts[i].setCameraZ(pos.z); + clipverts[i].setRecipCameraZ(recipZ); + clipverts[i].setColor(c.red, c.green, c.blue, c.alpha); + clipverts[i].setU(objverts[i].u, recipZ); + clipverts[i].setV(objverts[i].v, recipZ); + } +} + +void +genIm3DRenderIndexed(PrimitiveType prim, void *indices, int32 numIndices) +{ + im2d::RenderIndexedPrimitive(prim, clipverts, numClipverts, indices, numIndices); +} + +void +genIm3DEnd(void) +{ + rwFree(clipverts); + clipverts = nil; + numClipverts = 0; +} diff --git a/vendor/librw/tools/ps2test/CMakeLists.txt b/vendor/librw/tools/ps2test/CMakeLists.txt new file mode 100644 index 0000000..5195a19 --- /dev/null +++ b/vendor/librw/tools/ps2test/CMakeLists.txt @@ -0,0 +1,26 @@ +add_executable(ps2test + gs.h + main.cpp + mem.h + ps2.h + + vu/defaultpipe.dsm + vu/skinpipe.dsm + + vu/light.vu + vu/setup_persp.vu +) + +target_link_libraries(ps2test + PRIVATE + librw::librw + kernel +) + +librw_platform_target(ps2test INSTALL) + +if(LIBRW_INSTALL) + install(TARGETS ps2test + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) +endif() diff --git a/vendor/librw/tools/ps2test/gs.h b/vendor/librw/tools/ps2test/gs.h new file mode 100644 index 0000000..361ad8c --- /dev/null +++ b/vendor/librw/tools/ps2test/gs.h @@ -0,0 +1,437 @@ +#define GS_NONINTERLACED 0 +#define GS_INTERLACED 1 + +#define GS_NTSC 2 +#define GS_PAL 3 +#define GS_VESA1A 0x1a +#define GS_VESA1B 0x1b +#define GS_VESA1C 0x1c +#define GS_VESA1D 0x1d +#define GS_VESA2A 0x2a +#define GS_VESA2B 0x2b +#define GS_VESA2C 0x2c +#define GS_VESA2D 0x2d +#define GS_VESA2E 0x2e +#define GS_VESA3B 0x3b +#define GS_VESA3C 0x3c +#define GS_VESA3D 0x3d +#define GS_VESA3E 0x3e +#define GS_VESA4A 0x4a +#define GS_VESA4B 0x4b +#define GS_DTV480P 0x50 + +#define GS_FIELD 0 +#define GS_FRAME 1 + +#define GS_PSMCT32 0 +#define GS_PSMCT24 1 +#define GS_PSMCT16 2 +#define GS_PSMCT16S 10 +#define GS_PS_GPU24 18 + +#define GS_PSMZ32 0 +#define GS_PSMZ24 1 +#define GS_PSMZ16 2 +#define GS_PSMZ16S 10 + +#define GS_ZTST_NEVER 0 +#define GS_ZTST_ALWAYS 1 +#define GS_ZTST_GREATER 2 +#define GS_ZTST_GEQUAL 3 + +#define GS_PRIM_POINT 0 +#define GS_PRIM_LINE 1 +#define GS_PRIM_LINE_STRIP 2 +#define GS_PRIM_TRI 3 +#define GS_PRIM_TRI_STRIP 4 +#define GS_PRIM_TRI_FAN 5 +#define GS_PRIM_SPRITE 6 +#define GS_PRIM_NO_SPEC 7 +#define GS_IIP_FLAT 0 +#define GS_IIP_GOURAUD 1 + +/* GS general purpose registers */ + +#define GS_PRIM 0x00 +#define GS_RGBAQ 0x01 +#define GS_ST 0x02 +#define GS_UV 0x03 +#define GS_XYZF2 0x04 +#define GS_XYZ2 0x05 +#define GS_TEX0_1 0x06 +#define GS_TEX0_2 0x07 +#define GS_CLAMP_1 0x08 +#define GS_CLAMP_2 0x09 +#define GS_FOG 0x0a +#define GS_XYZF3 0x0c +#define GS_XYZ3 0x0d +#define GS_TEX1_1 0x14 +#define GS_TEX1_2 0x15 +#define GS_TEX2_1 0x16 +#define GS_TEX2_2 0x17 +#define GS_XYOFFSET_1 0x18 +#define GS_XYOFFSET_2 0x19 +#define GS_PRMODECONT 0x1a +#define GS_PRMODE 0x1b +#define GS_TEXCLUT 0x1c +#define GS_SCANMSK 0x22 +#define GS_MIPTBP1_1 0x34 +#define GS_MIPTBP1_2 0x35 +#define GS_MIPTBP2_1 0x36 +#define GS_MIPTBP2_2 0x37 +#define GS_TEXA 0x3b +#define GS_FOGCOL 0x3d +#define GS_TEXFLUSH 0x3f +#define GS_SCISSOR_1 0x40 +#define GS_SCISSOR_2 0x41 +#define GS_ALPHA_1 0x42 +#define GS_ALPHA_2 0x43 +#define GS_DIMX 0x44 +#define GS_DTHE 0x45 +#define GS_COLCLAMP 0x46 +#define GS_TEST_1 0x47 +#define GS_TEST_2 0x48 +#define GS_PABE 0x49 +#define GS_FBA_1 0x4a +#define GS_FBA_2 0x4b +#define GS_FRAME_1 0x4c +#define GS_FRAME_2 0x4d +#define GS_ZBUF_1 0x4e +#define GS_ZBUF_2 0x4f +#define GS_BITBLTBUF 0x50 +#define GS_TRXPOS 0x51 +#define GS_TRXREG 0x52 +#define GS_TRXDIR 0x53 +#define GS_HWREG 0x54 +#define GS_SIGNAL 0x60 +#define GS_FINISH 0x61 +#define GS_LABEL 0x62 + +typedef union +{ + struct { + uint64 EN1 : 1; + uint64 EN2 : 1; + uint64 CRTMD : 3; + uint64 MMOD : 1; + uint64 AMOD : 1; + uint64 SLBG : 1; + uint64 ALP : 8; + } f; + uint64 d; +} GsPmode; + +#define GS_MAKE_PMODE(EN1,EN2,MMOD,AMOD,SLBG,ALP) \ + (BIT64(EN1,0) | BIT64(EN2,1) | BIT64(1,2) | \ + BIT64(MMOD,5) | BIT64(AMOD,6) | BIT64(SLBG,7) | BIT64(ALP,8)) + +typedef union +{ + struct { + uint64 INT : 1; + uint64 FFMD : 1; + uint64 DPMS : 2; + } f; + uint64 d; +} GsSmode2; + +#define GS_MAKE_SMODE2(INT,FFMD,DPMS) \ + (BIT64(INT,0) | BIT64(FFMD,1) | BIT64(DPMS,2)) + +typedef union +{ + struct { + uint64 FBP : 9; + uint64 FBW : 6; + uint64 PSM : 5; + uint64 : 12; + uint64 DBX : 11; + uint64 DBY : 11; + } f; + uint64 d; +} GsDispfb; + +#define GS_MAKE_DISPFB(FBP,FBW,PSM,DBX,DBY) \ + (BIT64(FBP,0) | BIT64(FBW,9) | BIT64(PSM,15) | \ + BIT64(DBX,32) | BIT64(DBY,43)) + +typedef union +{ + struct { + uint64 DX : 12; + uint64 DY : 11; + uint64 MAGH : 4; + uint64 MAGV : 2; + uint64 : 3; + uint64 DW : 12; + uint64 DH : 11; + } f; + uint64 d; +} GsDisplay; + +#define GS_MAKE_DISPLAY(DX,DY,MAGH,MAGV,DW,DH) \ + (BIT64(DX,0) | BIT64(DY,12) | BIT64(MAGH,23) | \ + BIT64(MAGV,27) | BIT64(DW,32) | BIT64(DH,44)) + +typedef union +{ + struct { + uint64 EXBP : 14; + uint64 EXBW : 6; + uint64 FBIN : 2; + uint64 WFFMD : 1; + uint64 EMODA : 2; + uint64 EMODC : 2; + uint64 : 5; + uint64 WDX : 11; + uint64 WDY : 11; + } f; + uint64 d; +} GsExtbuf; + +#define GS_MAKE_EXTBUF(EXBP,EXBW,FBIN,WFFMD,EMODA,EMODC,WDX,WDY) \ + (BIT64(EXBP,0) | BIT64(EXBW,14) | BIT64(FBIN,20) | \ + BIT64(WFFMD,22) | BIT64(EMODA,23) | BIT64(EMODC,25) | \ + BIT64(WDX,32) | BIT64(WDY,43)) + +typedef union +{ + struct { + uint64 SX : 12; + uint64 SY : 11; + uint64 SMPH : 4; + uint64 SMPV : 2; + uint64 : 3; + uint64 WW : 12; + uint64 WH : 11; + } f; + uint64 d; +} GsExtdata; + +#define GS_MAKE_EXTDATA(SX,SY,SMPH,SMPV,WW,WH) \ + (BIT64(SX,0) | BIT64(SY,12) | BIT64(SMPH,23) | \ + BIT64(SMPV,27) | BIT64(WW,32) | BIT64(WH,44)) + +typedef union +{ + struct { + uint64 WRITE : 1; + } f; + uint64 d; +} GsExtwrite; + +typedef union +{ + struct { + uint64 R : 8; + uint64 G : 8; + uint64 B : 8; + } f; + uint64 d; +} GsBgcolor; + +#define GS_MAKE_BGCOLOR(R,G,B) \ + (BIT64(R,0) | BIT64(G,8) | BIT64(B,16)) + +typedef union +{ + struct { + uint64 SIGNAL : 1; + uint64 FINISH : 1; + uint64 HSINT : 1; + uint64 VSINT : 1; + uint64 EDWINT : 1; + uint64 : 3; + uint64 FLUSH : 1; + uint64 RESET : 1; + uint64 : 2; + uint64 NFIELD : 1; + uint64 FIELD : 1; + uint64 FIFO : 2; + uint64 REV : 8; + uint64 ID : 8; + } f; + uint64 d; +} GsCsr; + +#define GS_CSR_SIGNAL_O 0 +#define GS_CSR_FINISH_O 1 +#define GS_CSR_HSINT_O 2 +#define GS_CSR_VSINT_O 3 +#define GS_CSR_EDWINT_O 4 +#define GS_CSR_FLUSH_O 8 +#define GS_CSR_RESET_O 9 +#define GS_CSR_NFIELD_O 12 +#define GS_CSR_FIELD_O 13 +#define GS_CSR_FIFO_O 14 +#define GS_CSR_REV_O 16 +#define GS_CSR_ID_O 24 + +typedef union +{ + struct { + uint64 : 8; + uint64 SIGMSK : 1; + uint64 FINISHMSK : 1; + uint64 HSMSKMSK : 1; + uint64 VSMSKMSK : 1; + uint64 EDWMSKMSK : 1; + } f; + uint64 d; +} GsImr; + +typedef union +{ + struct { + uint64 DIR : 1; + } f; + uint64 d; +} GsBusdir; + +typedef union +{ + struct { + uint64 SIGID : 32; + uint64 LBLID : 32; + } f; + uint64 d; +} GsSiglblid; + + +typedef union +{ + struct { + uint64 FBP : 9; + uint64 : 7; + uint64 FBW : 6; + uint64 : 2; + uint64 PSM : 6; + uint64 : 2; + uint64 FBMSK : 32; + } f; + uint64 d; +} GsFrame; + +#define GS_MAKE_FRAME(FBP,FBW,PSM,FBMASK) \ + (BIT64(FBP,0) | BIT64(FBW,16) | BIT64(PSM,24) | BIT64(FBMASK,32)) + +typedef union +{ + struct { + uint64 ZBP : 9; + uint64 : 15; + uint64 PSM : 4; + uint64 : 4; + uint64 ZMSDK : 1; + } f; + uint64 d; +} GsZbuf; + +#define GS_MAKE_ZBUF(ZBP,PSM,ZMSK) \ + (BIT64(ZBP,0) | BIT64(PSM,24) | BIT64(ZMSK,32)) + +typedef union +{ + struct { + uint64 OFX : 16; + uint64 : 16; + uint64 OFY : 16; + } f; + uint64 d; +} GsXyOffset; + +#define GS_MAKE_XYOFFSET(OFX,OFY) \ + (BIT64(OFX,0) | BIT64(OFY,32)) + +typedef union +{ + struct { + uint64 SCAX0 : 11; + uint64 : 5; + uint64 SCAX1 : 11; + uint64 : 5; + uint64 SCAY0 : 11; + uint64 : 5; + uint64 SCAY1 : 11; + } f; + uint64 d; +} GsScissor; + +#define GS_MAKE_SCISSOR(SCAX0,SCAX1,SCAY0,SCAY1) \ + (BIT64(SCAX0,0) | BIT64(SCAX1,16) | BIT64(SCAY0,32) | BIT64(SCAY1,48)) + +#define GS_MAKE_TEST(ATE,ATST,AREF,AFAIL,DATE,DATM,ZTE,ZTST) \ + (BIT64(ATE,0) | BIT64(ATST,1) | BIT64(AREF,4) | BIT64(AFAIL,12) | \ + BIT64(DATE,14) | BIT64(DATM,15) | BIT64(ZTE,16) | BIT64(ZTST,17)) + +#define GS_MAKE_PRIM(PRIM,IIP,TME,FGE,ABE,AA1,FST,CTXT,FIX) \ + (BIT64(PRIM,0) | BIT64(IIP,3) | BIT64(TME,4) | BIT64(FGE,5) | \ + BIT64(ABE,6) | BIT64(AA1,7) | BIT64(FST,8) | BIT64(CTXT,9) | BIT64(FIX,10)) + +#define GS_MAKE_RGBAQ(R,G,B,A,Q) \ + (BIT64(R,0) | BIT64(G,8) | BIT64(B,16) | BIT64(A,24) | BIT64(Q,32)) + +#define GS_MAKE_XYZ(X,Y,Z) \ + (BIT64(X,0) | BIT64(Y,16) | BIT64(Z,32)) + +#define GIF_PACKED 0 +#define GIF_REGLIST 1 +#define GIF_IMAGE 2 + +#define GIF_MAKE_TAG(NLOOP,EOP,PRE,PRIM,FLG,NREG) \ + (BIT64(NLOOP,0) | BIT64(EOP,15) | BIT64(PRE,46) | \ + BIT64(PRIM,47) | BIT64(FLG,58) | BIT64(NREG,60)) + +/* This is global and not tied to a user context because + * it is set up by kernel functions and not really changed + * afterwards. */ +typedef struct GsCrtState GsCrtState; +struct GsCrtState +{ + short inter, mode, ff; +}; +extern GsCrtState gsCrtState; + +typedef struct GsDispCtx GsDispCtx; +struct GsDispCtx +{ + // two circuits + GsPmode pmode; + GsDispfb dispfb1; + GsDispfb dispfb2; + GsDisplay display1; + GsDisplay display2; + GsBgcolor bgcolor; +}; + +typedef struct GsDrawCtx GsDrawCtx; +struct GsDrawCtx +{ + //two contexts + uint128 gifTag; + GsFrame frame1; + uint64 ad_frame1; + GsFrame frame2; + uint64 ad_frame2; + GsZbuf zbuf1; + uint64 ad_zbuf1; + GsZbuf zbuf2; + uint64 ad_zbuf2; + GsXyOffset xyoffset1; + uint64 ad_xyoffset1; + GsXyOffset xyoffset2; + uint64 ad_xyoffset2; + GsScissor scissor1; + uint64 ad_scissor1; + GsScissor scissor2; + uint64 ad_scissor2; +}; + +typedef struct GsCtx GsCtx; +struct GsCtx +{ + // display context; two buffers + GsDispCtx disp[2]; + // draw context; two buffers + GsDrawCtx draw[2]; +}; diff --git a/vendor/librw/tools/ps2test/main.cpp b/vendor/librw/tools/ps2test/main.cpp new file mode 100644 index 0000000..5e58558 --- /dev/null +++ b/vendor/librw/tools/ps2test/main.cpp @@ -0,0 +1,787 @@ +#include +#include + +#define PAL + +#include +using rw::uint8; +using rw::uint16; +using rw::uint32; +using rw::uint64; +using rw::int8; +using rw::int16; +using rw::int32; +using rw::int64; +using rw::bool32; +using rw::float32; +typedef uint8 uchar; +typedef uint16 ushort; +typedef uint32 uint; + +#define WIDTH 640 +#ifdef PAL +#define HEIGHT 512 +#define VMODE GS_PAL +#else +#define HEIGHT 448 +#define VMODE GS_NTSC +#endif + +#include "ps2.h" + +// getting undefined references otherwise :/ +int *__errno() { return &errno; } + +// NONINTERLACED and FRAME have half of the FIELD vertical resolution! +// NONINTERLACED has half the vertical units + +uint128 packetbuf[128]; +uint128 vuXYZScale; +uint128 vuXYZOffset; +extern uint32 geometryCall[]; +extern uint32 skinPipe[]; + +uint128 *curVifPtr; +uint128 lightpacket[128]; +int32 numLightQ; + + + +rw::World *world; +rw::Camera *camera; + + +int frames; + +void +printquad(uint128 p) +{ + uint64 *lp; + lp = (uint64*)&p; + printf("%016lx %016lx\n", lp[1], lp[0]); +} + +void +printquad4(uint128 p) +{ + uint32 *lp; + lp = (uint32*)&p; + printf("%08x %08x %08x %08x\n", lp[0], lp[1], lp[2], lp[3]); +} + +void +dump4(uint128 *p, int n) +{ +printf("data at %p\n", p); + while(n--) + printquad4(*p++); +} + +struct DmaChannel { + uint32 chcr; uint32 pad0[3]; + uint32 madr; uint32 pad1[3]; + uint32 qwc; uint32 pad2[3]; + uint32 tadr; uint32 pad3[3]; + uint32 asr0; uint32 pad4[3]; + uint32 asr1; uint32 pad5[3]; + uint32 pad6[8]; + uint32 sadr; +}; + +static struct DmaChannel *dmaChannels[] = { + (struct DmaChannel *) &D0_CHCR, + (struct DmaChannel *) &D1_CHCR, + (struct DmaChannel *) &D2_CHCR, + (struct DmaChannel *) &D3_CHCR, + (struct DmaChannel *) &D4_CHCR, + (struct DmaChannel *) &D5_CHCR, + (struct DmaChannel *) &D6_CHCR, + (struct DmaChannel *) &D7_CHCR, + (struct DmaChannel *) &D8_CHCR, + (struct DmaChannel *) &D9_CHCR +}; + +void +dmaReset(void) +{ + /* don't clear the SIF channels */ + int doclear[] = { 1, 1, 1, 1, 1, 0, 0, 0, 1, 1 }; + int i; + + D_CTRL = 0; + for(i = 0; i < 10; i++) + if(doclear[i]){ + dmaChannels[i]->chcr = 0; + dmaChannels[i]->madr = 0; + dmaChannels[i]->qwc = 0; + dmaChannels[i]->tadr = 0; + dmaChannels[i]->asr0 = 0; + dmaChannels[i]->asr1 = 0; + dmaChannels[i]->sadr = 0; + } + D_CTRL = 1; +} + +void +waitDMA(volatile uint32 *chcr) +{ + while(*chcr & (1<<8)); +} + +void +qwcpy(uint128 *dst, uint128 *src, int n) +{ + while(n--) *dst++ = *src++; +} + +void +toGIF(void *src, int n) +{ + FlushCache(0); + D2_QWC = n; + D2_MADR = (uint32)src; + D2_CHCR = 1<<8; + waitDMA(&D2_CHCR); +} + +void +toGIFchain(void *src) +{ + FlushCache(0); + D2_QWC = 0; + D2_TADR = (uint32)src & 0x0FFFFFFF; + D2_CHCR = 1<<0 | 1<<2 | 1<<6 | 1<<8; + waitDMA(&D2_CHCR); +} + +void +toVIF1chain(void *src) +{ + FlushCache(0); + D1_QWC = 0; + D1_TADR = (uint32)src & 0x0FFFFFFF; + D1_CHCR = 1<<0 | 1<<2 | 1<<6 | 1<<8; + waitDMA(&D1_CHCR); +} + + +GsCrtState gsCrtState; + +int psmsizemap[64] = { + 4, // GS_PSMCT32 + 4, // GS_PSMCT24 + 2, // GS_PSMCT16 + 0, 0, 0, 0, 0, 0, 0, + 2, // GS_PSMCT16S + 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, + 4, // GS_PSMZ32 + 4, // GS_PSMZ24 + 2, // GS_PSMZ16 + 2, // GS_PSMZ16S + 0, 0, 0, 0, 0 +}; + +void +GsResetCrt(uchar inter, uchar mode, uchar ff) +{ + gsCrtState.inter = inter; + gsCrtState.mode = mode; + gsCrtState.ff = ff; + GS_CSR = 1 << GS_CSR_RESET_O; + __asm__("sync.p; nop"); + GsPutIMR(0xff00); + SetGsCrt(gsCrtState.inter, gsCrtState.mode, gsCrtState.ff); +} + +uint gsAllocPtr = 0; + +void +GsInitDispCtx(GsDispCtx *disp, int width, int height, int psm) +{ + int magh, magv; + int dx, dy; + int dw, dh; + + dx = gsCrtState.mode == GS_NTSC ? 636 : 656; + dy = gsCrtState.mode == GS_NTSC ? 25 : 36; + magh = 2560/width - 1; + magv = 0; + dw = 2560-1; + dh = height-1; + + if(gsCrtState.inter == GS_INTERLACED){ + dy *= 2; + if(gsCrtState.ff == GS_FRAME) + dh = (dh+1)*2-1; + } + + disp->pmode.d = GS_MAKE_PMODE(0, 1, 1, 1, 0, 0x00); +// disp->bgcolor.d = 0x404040; + disp->bgcolor.d = 0x000000; + disp->dispfb1.d = 0; + disp->dispfb2.d = GS_MAKE_DISPFB(0, width/64, psm, 0, 0); + disp->display1.d = 0; + disp->display2.d = GS_MAKE_DISPLAY(dx, dy, magh, magv, dw, dh); +} + +void +GsPutDispCtx(GsDispCtx *disp) +{ + GS_PMODE = disp->pmode.d; + GS_DISPFB1 = disp->dispfb1.d; + GS_DISPLAY1 = disp->display1.d; + GS_DISPFB2 = disp->dispfb2.d; + GS_DISPLAY2 = disp->display2.d; + GS_BGCOLOR = disp->bgcolor.d; +} + +void +GsInitDrawCtx(GsDrawCtx *draw, int width, int height, int psm, int zpsm) +{ + MAKE128(draw->gifTag, 0xe, + GIF_MAKE_TAG(8, 1, 0, 0, GIF_PACKED, 1)); + draw->frame1.d = GS_MAKE_FRAME(0, width/64, psm, 0); + draw->ad_frame1 = GS_FRAME_1; + draw->frame2.d = draw->frame1.d; + draw->ad_frame2 = GS_FRAME_2; + draw->zbuf1.d = GS_MAKE_ZBUF(0, zpsm, 0); + draw->ad_zbuf1 = GS_ZBUF_1; + draw->zbuf2.d = draw->zbuf1.d; + draw->ad_zbuf2 = GS_ZBUF_2; + draw->xyoffset1.d = GS_MAKE_XYOFFSET(2048<<4, 2048<<4); + draw->ad_xyoffset1 = GS_XYOFFSET_1; + draw->xyoffset2.d = draw->xyoffset1.d; + draw->ad_xyoffset2 = GS_XYOFFSET_2; + draw->scissor1.d = GS_MAKE_SCISSOR(0, width-1, 0, height-1); + draw->ad_scissor1 = GS_SCISSOR_1; + draw->scissor2.d = draw->scissor1.d; + draw->ad_scissor2 = GS_SCISSOR_2; +} + +void +GsPutDrawCtx(GsDrawCtx *draw) +{ +// printquad(*(uint128*)&draw->frame1); + toGIF(draw, 9); +} + +void +GsInitCtx(GsCtx *ctx, int width, int height, int psm, int zpsm) +{ + uint fbsz, zbsz; + uint fbp, zbp; + fbsz = (width*height*psmsizemap[psm] + 2047)/2048; + zbsz = (width*height*psmsizemap[0x30|zpsm] + 2047)/2048; + gsAllocPtr = 2*fbsz + zbsz; + fbp = fbsz; + zbp = fbsz*2; + + GsInitDispCtx(&ctx->disp[0], width, height, psm); + GsInitDispCtx(&ctx->disp[1], width, height, psm); + GsInitDrawCtx(&ctx->draw[0], width, height, psm, zpsm); + GsInitDrawCtx(&ctx->draw[1], width, height, psm, zpsm); + ctx->disp[1].dispfb2.f.FBP = fbp/4; + ctx->draw[0].frame1.f.FBP = fbp/4; + ctx->draw[0].frame2.f.FBP = fbp/4; + ctx->draw[0].zbuf1.f.ZBP = zbp/4; + ctx->draw[0].zbuf2.f.ZBP = zbp/4; + ctx->draw[1].zbuf1.f.ZBP = zbp/4; + ctx->draw[1].zbuf2.f.ZBP = zbp/4; +} + +void +initrender(void) +{ + uint128 *p, tmp; + p = packetbuf; + MAKE128(tmp, 0xe, GIF_MAKE_TAG(2, 1, 0, 0, GIF_PACKED, 1)); + *p++ = tmp; + MAKE128(tmp, GS_PRMODECONT, 1); + *p++ = tmp; + MAKE128(tmp, GS_COLCLAMP, 1); + *p++ = tmp; + toGIF(packetbuf, 3); +} + +void +clearscreen(int r, int g, int b) +{ + int x, y; + uint128 *p, tmp; + p = packetbuf; + + x = (2048 + WIDTH)<<4; + y = (2048 + HEIGHT)<<4; + + MAKE128(tmp, 0xe, GIF_MAKE_TAG(5, 1, 0, 0, GIF_PACKED, 1)); + *p++ = tmp; + MAKE128(tmp, GS_TEST_1, GS_MAKE_TEST(0, 0, 0, 0, 0, 0, 1, 1)); + *p++ = tmp; + MAKE128(tmp, GS_PRIM, GS_MAKE_PRIM(GS_PRIM_SPRITE,0,0,0,0,0,0,0,0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(r, g, b, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(2048<<4, 2048<<4, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x, y, 0)); + *p++ = tmp; + toGIF(packetbuf, 6); +} + +void +drawtest(void) +{ + int x0, x1, x2, x3; + int y0, y1, y2; + uint128 *p, tmp; + int n; + + x0 = 2048<<4; + x1 = (2048 + 210)<<4; + x2 = (2048 + 430)<<4; + x3 = (2048 + 640)<<4; + y0 = 2048<<4; + y1 = (2048 + 224)<<4; + y2 = (2048 + 448)<<4; + + n = 2 + 3*7; + p = packetbuf; + MAKEQ(tmp, 0x70000000 | n+1, 0, 0, 0); + *p++ = tmp; + MAKE128(tmp, 0xe, GIF_MAKE_TAG(n, 1, 0, 0, GIF_PACKED, 1)); + *p++ = tmp; + MAKE128(tmp, GS_TEST_1, GS_MAKE_TEST(0, 0, 0, 0, 0, 0, 1, 1)); + *p++ = tmp; + MAKE128(tmp, GS_PRIM, GS_MAKE_PRIM(GS_PRIM_SPRITE,0,0,0,0,0,0,0,0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(255, 0, 0, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x0, y0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x1, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(0, 255, 0, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x1, y0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x2, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(0, 0, 255, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x2, y0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x3, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(0, 255, 255, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x0, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x1, y2, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(255, 0, 255, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x1, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x2, y2, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(255, 255, 0, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x2, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x3, y2, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(255, 255, 255, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ((2048+20)<<4, y0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x3, (2048+20)<<4, 0)); + *p++ = tmp; + toGIFchain(packetbuf); +} + +void +drawtri(void) +{ + uint128 *p, tmp; + uint32 *ip; + int nverts, n; + + nverts = 3; + n = 2*nverts; + p = packetbuf; + MAKEQ(tmp, 0x70000000 | n+1, 0, 0, 0); + *p++ = tmp; + MAKE128(tmp, (0x5<<4) | 0x1, + GIF_MAKE_TAG(nverts, 1, 1, GS_MAKE_PRIM(GS_PRIM_TRI, 1, 0, 0, 0, 0, 0, 0, 0), GIF_PACKED, 2)); + *p++ = tmp; + MAKEQ(tmp, 255, 0, 0, 0); + *p++ = tmp; + MAKEQ(tmp, (2048+85)<<4, (2048+70)<<4, 0, 0); + *p++ = tmp; + MAKEQ(tmp, 0, 255, 0, 0); + *p++ = tmp; + MAKEQ(tmp, (2048+260)<<4, (2048+200)<<4, 0, 0); + *p++ = tmp; + MAKEQ(tmp, 0, 0, 255, 0); + *p++ = tmp; + MAKEQ(tmp, (2048+180)<<4, (2048+350)<<4, 0, 0); + *p++ = tmp; + toGIFchain(packetbuf); +} + +void +printMatrix(rw::Matrix *m) +{ + rw::V3d *x = &m->right; + rw::V3d *y = &m->up; + rw::V3d *z = &m->at; + rw::V3d *w = &m->pos; + printf( + "[ [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" + " [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" + " [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" + " [ %8.4f, %8.4f, %8.4f, %8.4f ] ]\n" + " %08x == flags\n", + x->x, y->x, z->x, w->x, + x->y, y->y, z->y, w->y, + x->z, y->z, z->z, w->z, + 0.0f, 0.0f, 0.0f, 1.0f, + m->flags); +} + +// This is not proper data, just for testing +void +setupLight(rw::Atomic *atomic) +{ + using namespace rw; + Matrix *lightmat; + float32 *lp; + + numLightQ = 0; + lp = (float32*)lightpacket; + + // TODO: this is the wrong matrix. we actually want to + // transform the light, not all normals. + lightmat = atomic->getFrame()->getLTM(); + *lp++ = lightmat->right.x; + *lp++ = lightmat->right.y; + *lp++ = lightmat->right.z; + *lp++ = 0.0f; + *lp++ = lightmat->up.x; + *lp++ = lightmat->up.y; + *lp++ = lightmat->up.z; + *lp++ = 0.0f; + *lp++ = lightmat->at.x; + *lp++ = lightmat->at.y; + *lp++ = lightmat->at.z; + *lp++ = 0.0f; + *lp++ = lightmat->pos.x; + *lp++ = lightmat->pos.y; + *lp++ = lightmat->pos.z; + *lp++ = 1.0f; + // TODO: make a proper light block + // ambient + *lp++ = 80.0f; + *lp++ = 80.0f; + *lp++ = 80.0f; + *lp++ = 0.0f; + // directional + *lp++ = 0.5f; + *lp++ = -0.5f; + *lp++ = -0.7071f; + *lp++ = 0.0f; + numLightQ = 6; +} + +void +setupTransform(rw::Atomic *atomic, rw::Matrix *trans) +{ + rw::Matrix::mult(trans, atomic->getFrame()->getLTM(), &camera->viewMatrix); +} + +enum { + DMAcnt = 0x10000000, + DMAref = 0x30000000, + DMAcall = 0x50000000, + DMAret = 0x60000000, + DMAend = 0x70000000, + + V4_32 = 0x6C +}; + +#define UNPACK(type, nq, offset) ((type)<<24 | (nq)<<16 | (offset)) +#define STCYCL(WL,CL) (0x01000000 | (WL)<<8 | (CL)) + +void +drawAtomic(rw::Atomic *atomic) +{ + using namespace rw; + + Matrix trans; + Geometry *geo; + ps2::ObjPipeline *pipe; + ps2::MatPipeline *matpipe; + Material *material; + uint128 tmp, *lp; + uint32 *vec; + RGBAf color; + int i; + + geo = atomic->geometry; + pipe = (ps2::ObjPipeline*)atomic->getPipeline(); + if(pipe->platform != PLATFORM_PS2) + return; + + setupLight(atomic); + setupTransform(atomic, &trans); + + curVifPtr = packetbuf; + // upload lights + MAKEQ(tmp, DMAcnt | numLightQ+8, 0, STCYCL(4,4), UNPACK(V4_32, numLightQ, 0x3d0)); + *curVifPtr++ = tmp; + for(lp = lightpacket; numLightQ--;) + *curVifPtr++ = *lp++; + + // upload transformation matrix + MAKEQ(tmp, 0, 0, STCYCL(4,4), UNPACK(V4_32, 4, 0x3f0)); + *curVifPtr++ = tmp; + vec = (uint32*)&trans.right; + MAKEQ(tmp, vec[0], vec[1], vec[2], vec[2]); + *curVifPtr++ = tmp; + vec = (uint32*)&trans.up; + MAKEQ(tmp, vec[0], vec[1], vec[2], vec[2]); + *curVifPtr++ = tmp; + vec = (uint32*)&trans.at; + MAKEQ(tmp, vec[0], vec[1], vec[2], vec[2]); + *curVifPtr++ = tmp; + vec = (uint32*)&trans.pos; + MAKEQ(tmp, vec[0], vec[1], vec[2], vec[2]); + *curVifPtr++ = tmp; + + // upload camera/screen info + MAKEQ(tmp, 0, 0, STCYCL(4,4), UNPACK(V4_32, 2, 0x3f7)); + *curVifPtr++ = tmp; + *curVifPtr++ = vuXYZScale; + *curVifPtr++ = vuXYZOffset; + + assert(geo->instData != NULL); + rw::ps2::InstanceDataHeader *instData = + (rw::ps2::InstanceDataHeader*)geo->instData; + rw::MeshHeader *meshHeader = geo->meshHeader; + rw::Mesh *mesh; + for(i = 0; i < instData->numMeshes; i++){ + material = instData->instanceMeshes[i].material; + matpipe = pipe->groupPipeline; + if(matpipe == nil) + matpipe = (ps2::MatPipeline*)material->pipeline; + if(matpipe == nil) + matpipe = ps2::defaultMatPipe; + + // call vu code + MAKEQ(tmp, DMAcall, (uint32)skinPipe, 0, 0); + *curVifPtr++ = tmp; + // unpack GIF tag, material color, surface properties + MAKEQ(tmp, DMAcnt | 3, 0, STCYCL(4,4), UNPACK(V4_32, 3, 0x3fa)); + *curVifPtr++ = tmp; + MAKE128(tmp, 0x412, + GIF_MAKE_TAG(0, 1, 1, GS_MAKE_PRIM(GS_PRIM_TRI_STRIP,1,0,0,0,0,0,0,0), GIF_PACKED, 3)); + *curVifPtr++ = tmp; + convColor(&color, &material->color); + color.alpha *= 128.0f/255.0f; + MAKEQ(tmp, *(uint32*)&color.red, *(uint32*)&color.green, + *(uint32*)&color.blue, *(uint32*)&color.alpha); + *curVifPtr++ = tmp; + MAKEQ(tmp, *(uint32*)&material->surfaceProps.ambient, + *(uint32*)&material->surfaceProps.specular, + *(uint32*)&material->surfaceProps.diffuse, + 0.0f); // extra + *curVifPtr++ = tmp; + // call geometry + MAKEQ(tmp, DMAcall, (uint32)instData->instanceMeshes[i].data, 0x03000000, 0x02000000 | matpipe->vifOffset); + *curVifPtr++ = tmp; + } + MAKEQ(tmp, DMAend, 0, 0, 0); + *curVifPtr++ = tmp; +// for(lp = packetbuf; lp < curVifPtr; lp++) +// printquad4(*lp); + toVIF1chain(packetbuf); +} + +void +beginCamera(void) +{ + uint128 *p, tmp; + float32 *f; + + p = packetbuf; + MAKE128(tmp, 0xe, GIF_MAKE_TAG(2, 1, 0, 0, GIF_PACKED, 1)); + *p++ = tmp; + MAKE128(tmp, GS_XYOFFSET_1, GS_MAKE_XYOFFSET(2048-WIDTH/2 <<4, 2048-HEIGHT/2 <<4)); + *p++ = tmp; + MAKE128(tmp, GS_TEST_1, GS_MAKE_TEST(0, 0, 0, 0, 0, 0, 1, 2)); + *p++ = tmp; + toGIF(packetbuf, 3); + f = (float32*)&vuXYZScale; + f[0] = WIDTH; + f[1] = HEIGHT; + f[2] = camera->zScale; + f[3] = 0.0f; + f = (float32*)&vuXYZOffset; + f[0] = 2048.0f; + f[1] = 2048.0f; + f[2] = camera->zShift; + f[3] = 0.0f; +} + +rw::EngineOpenParams engineOpenParams; + +void +pluginattach(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); +} + +bool32 +initrw(void) +{ + rw::version = 0x34000; + rw::platform = rw::PLATFORM_PS2; + if(!rw::Engine::init()) + return 0; + pluginattach(); + if(!rw::Engine::open(&engineOpenParams)) + return 0; + if(!rw::Engine::start()) + return 0; + rw::Texture::setLoadTextures(0); + + rw::TexDictionary::setCurrent(rw::TexDictionary::create()); + rw::Image::setSearchPath("."); + + world = rw::World::create(); + camera = rw::Camera::create(); + camera->frameBuffer = rw::Raster::create(WIDTH, HEIGHT, 0, rw::Raster::CAMERA); + camera->zBuffer = rw::Raster::create(WIDTH, HEIGHT, 0, rw::Raster::ZBUFFER); + camera->setFrame(rw::Frame::create()); + rw::V3d t = { 0.0f, 0.0f, -4.0f }; +// rw::V3d t = { 0.0f, 0.0f, -40.0f }; + camera->getFrame()->translate(&t, rw::COMBINEPOSTCONCAT); + rw::V3d axis = { 0.0f, 1.0f, 0.0f }; + camera->getFrame()->rotate(&axis, 40.0f, rw::COMBINEPOSTCONCAT); + camera->setNearPlane(0.1f); + camera->setFarPlane(450.0f); + camera->setFOV(60.0f, 4.0f/3.0f); + world->addCamera(camera); + return 1; +} + +int vsynchInt = 0; + +int +vsynch(int id) +{ + vsynchInt = 1; + frames++; + ExitHandler(); + return 0; +} + +int +main() +{ + FlushCache(0); + if(!initrw()){ + printf("init failed!\n"); + for(;;); + } + + rw::uint32 len; + rw::uint8 *data = rw::getFileContents("host:player.DFF", &len); +// rw::uint8 *data = rw::getFileContents("host:od_newscafe_dy.dff", &len); + rw::StreamMemory in; + in.open(data, len); + rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL); + rw::Clump *clump = rw::Clump::streamRead(&in); + in.close(); + rwFree(data); + + GsCtx gsCtx; + + dmaReset(); + +// GsResetCrt(GS_NONINTERLACED, GS_NTSC, 0); +// GsInitCtx(&gsCtx, 640, 224, GS_PSMCT32, GS_PSMZ32); + +// GsResetCrt(GS_INTERLACED, GS_NTSC, GS_FRAME); +// GsInitCtx(&gsCtx, 640, 224, GS_PSMCT32, GS_PSMZ32); + + GsResetCrt(GS_INTERLACED, VMODE, GS_FIELD); + GsInitCtx(&gsCtx, WIDTH, HEIGHT, GS_PSMCT32, GS_PSMZ32); + + initrender(); + + AddIntcHandler(2, vsynch, 0); + EnableIntc(2); + + GsPutDrawCtx(&gsCtx.draw[0]); + GsPutDispCtx(&gsCtx.disp[1]); + // PCSX2 needs a delay for some reason + { int i; for(i = 0; i < 1000000; i++); } + clearscreen(0x80, 0x80, 0x80); +// drawtest(); +// drawtri(); + + float angle = 40.0f; + int drawbuf = 0; + int dispbuf = 1; + for(;;){ + clearscreen(0x80, 0x80, 0x80); + + rw::V3d t = { 0.0f, 0.0f, -4.0f }; + camera->getFrame()->translate(&t, rw::COMBINEREPLACE); + rw::V3d axis = { 0.0f, 1.0f, 0.0f }; + camera->getFrame()->rotate(&axis, angle, rw::COMBINEPOSTCONCAT); + angle += 1.0f; + + camera->beginUpdate(); + beginCamera(); + FORLIST(lnk, clump->atomics) + drawAtomic(rw::Atomic::fromClump(lnk)); + camera->endUpdate(); + printf(""); + + while(vsynchInt == 0); + GsPutDrawCtx(&gsCtx.draw[drawbuf]); + GsPutDispCtx(&gsCtx.disp[dispbuf]); + drawbuf = !drawbuf; + dispbuf = !dispbuf; + vsynchInt = 0; + } +/* + camera->beginUpdate(); + beginCamera(); + FORLIST(lnk, clump->atomics) + drawAtomic(rw::Atomic::fromClump(lnk)); + camera->endUpdate(); + + printf("hello %p\n", clump); + for(;;) + printf(""); +*/ + + return 0; +} diff --git a/vendor/librw/tools/ps2test/mem.h b/vendor/librw/tools/ps2test/mem.h new file mode 100644 index 0000000..088c1d4 --- /dev/null +++ b/vendor/librw/tools/ps2test/mem.h @@ -0,0 +1,95 @@ +/* FIFOs */ +#define VIF0_FIFO (*(volatile uint128*)0x10004000) +#define VIF1_FIFO (*(volatile uint128*)0x10005000) +#define GIF_FIFO (*(volatile uint128*)0x10006000) +#define IPU_out_FIFO (*(volatile uint128*)0x10007000) +#define IPU_in_FIFO (*(volatile uint128*)0x10007010) + +/* DMA channels */ +// to VIF0 +#define D0_CHCR (*(volatile uint32*)0x10008000) +#define D0_MADR (*(volatile uint32*)0x10008010) +#define D0_QWC (*(volatile uint32*)0x10008020) +#define D0_TADR (*(volatile uint32*)0x10008030) +#define D0_ASR0 (*(volatile uint32*)0x10008040) +#define D0_ASR1 (*(volatile uint32*)0x10008050) +// VIF1 +#define D1_CHCR (*(volatile uint32*)0x10009000) +#define D1_MADR (*(volatile uint32*)0x10009010) +#define D1_QWC (*(volatile uint32*)0x10009020) +#define D1_TADR (*(volatile uint32*)0x10009030) +#define D1_ASR0 (*(volatile uint32*)0x10009040) +#define D1_ASR1 (*(volatile uint32*)0x10009050) +// to GIF +#define D2_CHCR (*(volatile uint32*)0x1000a000) +#define D2_MADR (*(volatile uint32*)0x1000a010) +#define D2_QWC (*(volatile uint32*)0x1000a020) +#define D2_TADR (*(volatile uint32*)0x1000a030) +#define D2_ASR0 (*(volatile uint32*)0x1000a040) +#define D2_ASR1 (*(volatile uint32*)0x1000a050) +// fromIPU +#define D3_CHCR (*(volatile uint32*)0x1000b000) +#define D3_MADR (*(volatile uint32*)0x1000b010) +#define D3_QWC (*(volatile uint32*)0x1000b020) +// toIPU +#define D4_CHCR (*(volatile uint32*)0x1000b400) +#define D4_MADR (*(volatile uint32*)0x1000b410) +#define D4_QWC (*(volatile uint32*)0x1000b420) +#define D4_TADR (*(volatile uint32*)0x1000b430) +// from SIF0 +#define D5_CHCR (*(volatile uint32*)0x1000c000) +#define D5_MADR (*(volatile uint32*)0x1000c010) +#define D5_QWC (*(volatile uint32*)0x1000c020) +// to SIF1 +#define D6_CHCR (*(volatile uint32*)0x1000c400) +#define D6_MADR (*(volatile uint32*)0x1000c410) +#define D6_QWC (*(volatile uint32*)0x1000c420) +#define D6_TADR (*(volatile uint32*)0x1000c430) +// SIF2 +#define D7_CHCR (*(volatile uint32*)0x1000c800) +#define D7_MADR (*(volatile uint32*)0x1000c810) +#define D7_QWC (*(volatile uint32*)0x1000c820) +// fromSPR +#define D8_CHCR (*(volatile uint32*)0x1000d000) +#define D8_MADR (*(volatile uint32*)0x1000d010) +#define D8_QWC (*(volatile uint32*)0x1000d020) +#define D8_SADR (*(volatile uint32*)0x1000d080) +// toSPR +#define D9_CHCR (*(volatile uint32*)0x1000d400) +#define D9_MADR (*(volatile uint32*)0x1000d410) +#define D9_QWC (*(volatile uint32*)0x1000d420) +#define D9_TADR (*(volatile uint32*)0x1000d430) +#define D9_SADR (*(volatile uint32*)0x1000d480) + +/* DMA controller */ +#define D_CTRL (*(volatile uint32*)0x1000e000) +#define D_STAT (*(volatile uint32*)0x1000e010) +#define D_PCR (*(volatile uint32*)0x1000e020) +#define D_SQWC (*(volatile uint32*)0x1000e030) +#define D_RBSR (*(volatile uint32*)0x1000e040) +#define D_RBOR (*(volatile uint32*)0x1000e050) +#define D_STADR (*(volatile uint32*)0x1000e060) +#define D_ENABLER (*(volatile uint32*)0x1000f520) +#define D_ENABLEW (*(volatile uint32*)0x1000f590) + + +/* GS privileged registers */ +#define GS_PMODE (*(volatile uint64*)0x12000000) +#define GS_SMODE1 (*(volatile uint64*)0x12000010) +#define GS_SMODE2 (*(volatile uint64*)0x12000020) +#define GS_SRFSH (*(volatile uint64*)0x12000030) +#define GS_SYNCH1 (*(volatile uint64*)0x12000040) +#define GS_SYNCH2 (*(volatile uint64*)0x12000050) +#define GS_SYNCV (*(volatile uint64*)0x12000060) +#define GS_DISPFB1 (*(volatile uint64*)0x12000070) +#define GS_DISPLAY1 (*(volatile uint64*)0x12000080) +#define GS_DISPFB2 (*(volatile uint64*)0x12000090) +#define GS_DISPLAY2 (*(volatile uint64*)0x120000a0) +#define GS_EXTBUF (*(volatile uint64*)0x120000b0) +#define GS_EXTDATA (*(volatile uint64*)0x120000c0) +#define GS_EXTWRITE (*(volatile uint64*)0x120000d0) +#define GS_BGCOLOR (*(volatile uint64*)0x120000e0) +#define GS_CSR (*(volatile uint64*)0x12001000) +#define GS_IMR (*(volatile uint64*)0x12001010) +#define GS_BUSDIR (*(volatile uint64*)0x12001040) +#define GS_SIGLBLID (*(volatile uint64*)0x12001080) diff --git a/vendor/librw/tools/ps2test/ps2.h b/vendor/librw/tools/ps2test/ps2.h new file mode 100644 index 0000000..ced015a --- /dev/null +++ b/vendor/librw/tools/ps2test/ps2.h @@ -0,0 +1,23 @@ +#include + +typedef int quad __attribute__((mode(TI))); +typedef int int128 __attribute__((mode(TI))); +typedef unsigned int uquad __attribute__((mode(TI))); +typedef unsigned int uint128 __attribute__((mode(TI))); + +#define MAKE128(RES,MSB,LSB) \ + __asm__ ( "pcpyld %0, %1, %2" : "=r" (RES) : "r" ((uint64)MSB), "r" ((uint64)LSB)) +#define UINT64(LOW,HIGH) (((uint64)HIGH)<<32 | ((uint64)LOW)) +#define MAKEQ(RES,W0,W1,W2,W3) MAKE128(RES,UINT64(W2,W3),UINT64(W0,W1)) + +#define BIT64(v,s) (((uint64)(v)) << (s)) + +#include "mem.h" +#include "gs.h" + +extern uint128 packetbuf[128]; + +void waitDMA(volatile uint32 *chcr); +void toGIF(void *src, int n); + +void drawcube(void); diff --git a/vendor/librw/tools/ps2test/vu/defaultpipe.dsm b/vendor/librw/tools/ps2test/vu/defaultpipe.dsm new file mode 100644 index 0000000..ae3fa39 --- /dev/null +++ b/vendor/librw/tools/ps2test/vu/defaultpipe.dsm @@ -0,0 +1,93 @@ +.global defaultPipe + +.equ vertexTop, 0x3d0 +.equ numInAttribs, 4 +.equ numOutAttribs, 3 +.equ numOutBuf, 2 +.equ vertCount, ((vertexTop-numOutBuf)/(numInAttribs*2+numOutAttribs*numOutBuf)) +.equ offset, (vertCount*numInAttribs) +.equ outBuf1, (2*offset) +.equ outSize, ((vertexTop-outBuf1-2)/2) +.equ outBuf2, (outBuf1+outSize) + +.equ lightMat, 0x3d0 +.equ ambientLight, 0x3d4 +.equ lightDir, 0x3d5 + +.equ matrix, 0x3f0 +.equ XYZScale, 0x3f7 +.equ XYZOffset, 0x3f8 +.equ gifTag, 0x3fa +.equ matColor, 0x3fb +.equ surfProps, 0x3fc + + +.balign 16,0 +defaultPipe: +DMAret * +MPG 0, * +.vu +Start: +#include "setup_persp.vu" +Cnt: + NOP XTOP VI02 ; input pointer + NOP LQ VF01, gifTag(VI00) + NOP XITOP VI01 ; vertex count + NOP IADDIU VI05, VI00, 0x4000 + NOP IADD VI05, VI05, VI05 + NOP IOR VI05, VI05, VI01 + NOP SQ VF01, 0(VI12) + NOP ISW.x VI05, 0(VI12) + NOP IADDIU VI03, VI12, 1 ; output pointer + NOP LQ VF18, lightMat(VI00) + NOP LQ VF19, lightMat+1(VI00) + NOP LQ VF20, lightMat+2(VI00) + +Loop: + NOP LQI VF01, (VI02++) ; vertex + NOP LQI VF02, (VI02++) ; UV + NOP LQI VF03, (VI02++) ; color + NOP LQI VF04, (VI02++) ; normal + + MULAw.xyzw ACC, VF31, VF00w NOP ; transform vertex + MADDAx.xyw ACC, VF28, VF01x NOP + MADDAy.xyw ACC, VF29, VF01y NOP + MADDz.xyzw VF01, VF30, VF01z NOP + ITOF0 VF03, VF03 NOP + ITOF0[I] VF04, VF04 LOI 0.0078125 ; - normal scale + NOP NOP + NOP DIV Q, VF00w, VF01w + NOP WAITQ + MULq VF01, VF01, Q NOP ; perspective division + MULi VF04, VF04, I NOP ; scale normal + NOP MR32.z VF02, VF00 + NOP NOP + SUB.w VF01, VF01, VF01 NOP + MULAx.xyz ACC, VF18, VF04x NOP ; transform normal + MADDAy.xyz ACC, VF19, VF04y NOP + MADDz.xyz VF04, VF20, VF04z NOP + ADD.xyz VF01, VF01, VF25 NOP + MULq VF02, VF02, Q NOP + NOP NOP + FTOI0 VF03, VF03 NOP + FTOI4 VF01, VF01 NOP + NOP SQ VF04, -2(VI02) ; store normal + NOP IADDI VI01, VI01, -1 + NOP SQI VF02, (VI03++) ; STQ + NOP SQI VF03, (VI03++) ; color + NOP SQI VF01, (VI03++) ; vertex + NOP IBNE VI01, VI00, Loop + NOP NOP + +#include "light.vu" + + NOP XGKICK VI12 + NOP IADD VI15,VI00,VI12 + NOP IADD VI12,VI00,VI13 + NOP[E] IADD VI13,VI00,VI15 + NOP NOP + NOP B Cnt + NOP NOP + +.EndMPG +.EndDmaData diff --git a/vendor/librw/tools/ps2test/vu/light.vu b/vendor/librw/tools/ps2test/vu/light.vu new file mode 100644 index 0000000..8f7a31c --- /dev/null +++ b/vendor/librw/tools/ps2test/vu/light.vu @@ -0,0 +1,94 @@ +; Ambient light: + NOP LQ VF26, ambientLight(VI00) + NOP XITOP VI01 + NOP IADDIU VI03, VI12, 2 +Ambloop: + NOP LQ VF03, 0(VI03) ; output color + NOP NOP + NOP NOP + NOP NOP + ITOF0 VF03, VF03 NOP + NOP NOP + NOP NOP + NOP NOP + ADD.xyz VF03, VF03, VF26 NOP + NOP NOP + NOP NOP + NOP NOP + FTOI0 VF03, VF03 NOP + NOP IADDI VI01, VI01, -1 + NOP IADDIU VI03, VI03, numOutAttribs + NOP IBNE VI01, VI00, Ambloop + NOP SQ VF03, -numOutAttribs(VI03) +; end amblight + +; Direct Light + NOP LQ VF26, lightDir(VI00) + NOP XITOP VI01 + NOP XTOP VI02 + NOP IADDIU VI03, VI12, 2 + SUB.xyz VF26, VF00, VF26 NOP +Dirloop: + NOP LQ VF01, 3(VI02); ; normal + NOP LQ VF02, 0(VI03); ; output color + NOP NOP + NOP NOP + MUL VF03, VF01, VF26 NOP + ITOF0 VF02, VF02 NOP + NOP NOP + NOP NOP + ADDy.x VF03, VF03, VF03y NOP + NOP NOP + NOP NOP + NOP NOP + ADDz.x VF03, VF03, VF03z NOP + NOP NOP + NOP NOP + NOP NOP + MAX.x VF03, VF00, VF03 NOP ; clamp to 0 + NOP[I] LOI 255 + NOP NOP + NOP NOP + MULi.x VF03, VF03, I NOP + NOP NOP + NOP NOP + NOP NOP + ADDx.xyz VF02, VF02, VF03x NOP + NOP NOP + NOP NOP + NOP NOP + FTOI0 VF02, VF02 NOP + NOP IADDI VI01, VI01, -1 + NOP IADDIU VI02, VI02, numInAttribs + NOP IADDIU VI03, VI03, numOutAttribs + NOP IBNE VI01, VI00, Dirloop + NOP SQ VF02, -numOutAttribs(VI03) +; end dirlight + +; Material color and clamp + NOP LQ VF27, matColor(VI00) + NOP XITOP VI01 + NOP IADDIU VI03, VI12, 2 +Colorloop: + NOP LQ VF03, 0(VI03) + NOP NOP + NOP NOP + NOP NOP + ITOF0 VF03, VF03 NOP + NOP NOP + NOP NOP + NOP NOP + MUL VF03, VF03, VF27 NOP + NOP[I] LOI 255 + NOP NOP + NOP NOP + MINIi VF03, VF03, I NOP + NOP NOP + NOP NOP + NOP NOP + FTOI0 VF03, VF03 NOP + NOP IADDI VI01, VI01, -1 + NOP IADDIU VI03, VI03, numOutAttribs + NOP IBNE VI01, VI00, Colorloop + NOP SQ VF03, -numOutAttribs(VI03) +; end material color diff --git a/vendor/librw/tools/ps2test/vu/setup_persp.vu b/vendor/librw/tools/ps2test/vu/setup_persp.vu new file mode 100644 index 0000000..1d6a1fb --- /dev/null +++ b/vendor/librw/tools/ps2test/vu/setup_persp.vu @@ -0,0 +1,39 @@ +/* This is the the projection matrix we start with: + * 1/2w 0 ox/2w + 1/2 -ox/2w + * 0 -1/2h -oy/2h + 1/2 oy/2h + * 0 0 1 0 + * 0 0 1 0 + * To get rid of the +1/2 in the combined matrix we + * subtract the z-row/2 from the x- and y-rows. + * + * The z-row is then set to [0 0 0 1] such that multiplication + * by XYZscale gives [0 0 0 zScale]. After perspective division + * and addition of XYZoffset we then get zScale/w + zShift for z. + * + * XYZScale scales xy to the resolution and z by zScale. + * XYZOffset translates xy to the GS coordinate system (where + * [2048, 2048] is the center of the frame buffer) and add zShift to z. + */ + +; constant: +; VF28-VF31 transformation matrix +; VF25 XYZ offset + + + SUB.z VF28, VF28, VF28 LOI 0.5 ; right.z = 0 + SUB.z VF29, VF29, VF29 LQ VF28, matrix(VI00) ; up.z = 0 - load matrix + SUB.z VF30, VF30, VF30 LQ VF29, matrix+1(VI00) ; at.z = 0 - load matrix + ADDw.z VF31, VF00, VF00 LQ VF30, matrix+2(VI00) ; at.z = 1 - load matrix + NOP LQ VF31, matrix+3(VI00) ; - load matrix + MULi.w VF20, VF28, I LQ.xyz VF01, XYZScale(VI00) ; fix matrix - load scale + MULi.w VF21, VF29, I NOP ; fix matrix + MULi.w VF22, VF30, I NOP ; fix matrix + MULi.w VF23, VF31, I NOP ; fix matrix + SUBw.xy VF28, VF28, VF20 NOP ; fix matrix + SUBw.xy VF29, VF29, VF21 NOP ; fix matrix + SUBw.xy VF30, VF30, VF22 NOP ; fix matrix + SUBw.xy VF31, VF31, VF23 NOP ; fix matrix + MUL.xy VF28, VF28, VF01 LQ.xyz VF25, XYZOffset(VI00) ; scale matrix + MUL.xy VF29, VF29, VF01 IADDIU VI12, VI00, outBuf1 ; scale matrix + MUL.xy VF30, VF30, VF01 IADDIU VI13, VI00, outBuf2 ; scale matrix + MUL.xyz VF31, VF31, VF01 NOP ; scale matrix diff --git a/vendor/librw/tools/ps2test/vu/skinpipe.dsm b/vendor/librw/tools/ps2test/vu/skinpipe.dsm new file mode 100644 index 0000000..18536cd --- /dev/null +++ b/vendor/librw/tools/ps2test/vu/skinpipe.dsm @@ -0,0 +1,94 @@ +.global skinPipe + +.equ vertexTop, 0x2d0 +.equ numInAttribs, 5 +.equ numOutAttribs, 3 +.equ numOutBuf, 2 +.equ vertCount, ((vertexTop-numOutBuf)/(numInAttribs*2+numOutAttribs*numOutBuf)) +.equ offset, (vertCount*numInAttribs) +.equ outBuf1, (2*offset) +.equ outSize, ((vertexTop-outBuf1-2)/2) +.equ outBuf2, (outBuf1+outSize) + +.equ lightMat, 0x3d0 +.equ ambientLight, 0x3d4 +.equ lightDir, 0x3d5 + +.equ matrix, 0x3f0 +.equ XYZScale, 0x3f7 +.equ XYZOffset, 0x3f8 +.equ gifTag, 0x3fa +.equ matColor, 0x3fb +.equ surfProps, 0x3fc + + +.balign 16,0 +skinPipe: +DMAret * +MPG 0, * +.vu +Start: +#include "setup_persp.vu" +Cnt: + NOP XTOP VI02 ; input pointer + NOP LQ VF01, gifTag(VI00) + NOP XITOP VI01 ; vertex count + NOP IADDIU VI05, VI00, 0x4000 + NOP IADD VI05, VI05, VI05 + NOP IOR VI05, VI05, VI01 + NOP SQ VF01, 0(VI12) + NOP ISW.x VI05, 0(VI12) + NOP IADDIU VI03, VI12, 1 ; output pointer + NOP LQ VF18, lightMat(VI00) + NOP LQ VF19, lightMat+1(VI00) + NOP LQ VF20, lightMat+2(VI00) + +Loop: + NOP LQI VF01, (VI02++) ; vertex + NOP LQI VF02, (VI02++) ; UV + NOP LQI VF03, (VI02++) ; color + NOP LQI VF04, (VI02++) ; normal + NOP IADDIU VI02, VI02, 1 ; skip weights + + MULAw.xyzw ACC, VF31, VF00w NOP ; transform vertex + MADDAx.xyw ACC, VF28, VF01x NOP + MADDAy.xyw ACC, VF29, VF01y NOP + MADDz.xyzw VF01, VF30, VF01z NOP + ITOF0 VF03, VF03 NOP + ITOF0[I] VF04, VF04 LOI 0.0078125 ; - normal scale + NOP NOP + NOP DIV Q, VF00w, VF01w + NOP WAITQ + MULq VF01, VF01, Q NOP ; perspective division + MULi VF04, VF04, I NOP ; scale normal + NOP MR32.z VF02, VF00 + NOP NOP + SUB.w VF01, VF01, VF01 NOP + MULAx.xyz ACC, VF18, VF04x NOP ; transform normal + MADDAy.xyz ACC, VF19, VF04y NOP + MADDz.xyz VF04, VF20, VF04z NOP + ADD.xyz VF01, VF01, VF25 NOP + MULq VF02, VF02, Q NOP + NOP NOP + FTOI0 VF03, VF03 NOP + FTOI4 VF01, VF01 NOP + NOP SQ VF04, -2(VI02) ; store normal + NOP IADDI VI01, VI01, -1 + NOP SQI VF02, (VI03++) ; STQ + NOP SQI VF03, (VI03++) ; color + NOP SQI VF01, (VI03++) ; vertex + NOP IBNE VI01, VI00, Loop + NOP NOP + +#include "light.vu" + + NOP XGKICK VI12 + NOP IADD VI15,VI00,VI12 + NOP IADD VI12,VI00,VI13 + NOP[E] IADD VI13,VI00,VI15 + NOP NOP + NOP B Cnt + NOP NOP + +.EndMPG +.EndDmaData diff --git a/vendor/librw/tools/ska2anm/CMakeLists.txt b/vendor/librw/tools/ska2anm/CMakeLists.txt new file mode 100644 index 0000000..ffd63d5 --- /dev/null +++ b/vendor/librw/tools/ska2anm/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable(ska2anm + ska2anm.cpp +) + +target_link_libraries(ska2anm + PUBLIC + librw::librw +) + +librw_platform_target(ska2anm INSTALL) + +if(LIBRW_INSTALL) + install(TARGETS ska2anm + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) +endif() diff --git a/vendor/librw/tools/ska2anm/ska2anm.cpp b/vendor/librw/tools/ska2anm/ska2anm.cpp new file mode 100644 index 0000000..900ba82 --- /dev/null +++ b/vendor/librw/tools/ska2anm/ska2anm.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include + +#include +#include + +using namespace rw; + +char *argv0; + +void +usage(void) +{ + fprintf(stderr, "usage: %s in.ska [out.anm]\n", argv0); + fprintf(stderr, " or: %s in.anm [out.ska]\n", argv0); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + rw::Engine::init(); + rw::registerHAnimPlugin(); + rw::Engine::open(nil); + rw::Engine::start(); + + ARGBEGIN{ + case 'v': + sscanf(EARGF(usage()), "%x", &rw::version); + break; + default: + usage(); + }ARGEND; + + if(argc < 1) + usage(); + + StreamFile stream; + if(!stream.open(argv[0], "rb")){ + fprintf(stderr, "Error: couldn't open %s\n", argv[0]); + return 1; + } + + int32 firstword = stream.readU32(); + stream.seek(0, 0); + Animation *anim = nil; + if(firstword == ID_ANIMANIMATION){ + // it's an anm file + if(findChunk(&stream, ID_ANIMANIMATION, nil, nil)) + anim = Animation::streamRead(&stream); + }else{ + // it's a ska file + anim = Animation::streamReadLegacy(&stream); + } + stream.close(); + + if(anim == nil){ + fprintf(stderr, "Error: couldn't read anim file\n"); + return 1; + } + + const char *file; + if(argc > 1) + file = argv[1]; + else if(firstword == ID_ANIMANIMATION) + file = "out.ska"; + else + file = "out.anm"; + if(!stream.open(file, "wb")){ + fprintf(stderr, "Error: couldn't open %s\n", file); + return 1; + } + if(firstword == ID_ANIMANIMATION) + anim->streamWriteLegacy(&stream); + else + anim->streamWrite(&stream); + + anim->destroy(); + + return 0; +} diff --git a/vendor/librw/tools/subrast/CMakeLists.txt b/vendor/librw/tools/subrast/CMakeLists.txt new file mode 100644 index 0000000..6393734 --- /dev/null +++ b/vendor/librw/tools/subrast/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(subrast WIN32 + main.cpp + subrast.cpp + subrast.h +) + +target_link_libraries(subrast + PUBLIC + librw::skeleton + librw::librw +) + +add_custom_command( + TARGET subrast POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory "$/files" + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/files" "$/files" +) + +librw_platform_target(subrast) diff --git a/vendor/librw/tools/subrast/files/clump.dff b/vendor/librw/tools/subrast/files/clump.dff new file mode 100644 index 0000000..b65e41d Binary files /dev/null and b/vendor/librw/tools/subrast/files/clump.dff differ diff --git a/vendor/librw/tools/subrast/files/textures/whiteash.png b/vendor/librw/tools/subrast/files/textures/whiteash.png new file mode 100644 index 0000000..aa60569 Binary files /dev/null and b/vendor/librw/tools/subrast/files/textures/whiteash.png differ diff --git a/vendor/librw/tools/subrast/main.cpp b/vendor/librw/tools/subrast/main.cpp new file mode 100644 index 0000000..5dbde8a --- /dev/null +++ b/vendor/librw/tools/subrast/main.cpp @@ -0,0 +1,354 @@ +#include +#include +#include + +#include "subrast.h" + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +rw::EngineOpenParams engineOpenParams; +float FOV = 70.0f; + +rw::RGBA ForegroundColor = { 200, 200, 200, 255 }; +rw::RGBA BackgroundColor = { 64, 64, 64, 0 }; +rw::RGBA BorderColor = { 128, 128, 128, 0 }; + +rw::Clump *Clump = nil; +rw::Light *MainLight = nil; +rw::Light *AmbientLight = nil; + +rw::World *World; +rw::Charset *Charset; + +const char *SubCameraCaption[4] = { + "Perspective view", + "Parallel view: Z-axis", + "Parallel view: X-axis", + "Parallel view: Y-axis" +}; + +rw::V3d Xaxis = { 1.0f, 0.0, 0.0f }; +rw::V3d Yaxis = { 0.0f, 1.0, 0.0f }; +rw::V3d Zaxis = { 0.0f, 0.0, 1.0f }; + +rw::World* +CreateWorld(void) +{ + rw::BBox bb; + + bb.inf.x = bb.inf.y = bb.inf.z = -100.0f; + bb.sup.x = bb.sup.y = bb.sup.z = 100.0f; + + return rw::World::create(&bb); +} + +rw::Light* +CreateAmbientLight(rw::World *world) +{ + rw::Light *light = rw::Light::create(rw::Light::AMBIENT); + assert(light); + World->addLight(light); + return light; +} + +rw::Light* +CreateMainLight(rw::World *world) +{ + rw::Light *light = rw::Light::create(rw::Light::DIRECTIONAL); + assert(light); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 30.0f, rw::COMBINEREPLACE); + frame->rotate(&Yaxis, 30.0f, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + World->addLight(light); + return light; +} + +rw::Clump* +CreateClump(rw::World *world) +{ + rw::Clump *clump; + rw::StreamFile in; + + rw::Image::setSearchPath("files/textures/"); + const char *filename = "files/clump.dff"; + if(in.open(filename, "rb") == NULL){ + printf("couldn't open file\n"); + return nil; + } + if(!rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL)) + return nil; + clump = rw::Clump::streamRead(&in); + in.close(); + if(clump == nil) + return nil; + + rw::Frame *frame = clump->getFrame(); + frame->rotate(&Xaxis, -120.0f, rw::COMBINEREPLACE); + frame->rotate(&Yaxis, 45.0f, rw::COMBINEPOSTCONCAT); + World->addClump(clump); + return clump; +} + +void +RotateClump(float xAngle, float yAngle) +{ + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::Frame *frame = Clump->getFrame(); + rw::V3d pos = frame->matrix.pos; + + pos = rw::scale(pos, -1.0f); + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + + frame->rotate(&cameraMatrix->up, xAngle, rw::COMBINEPOSTCONCAT); + frame->rotate(&cameraMatrix->right, yAngle, rw::COMBINEPOSTCONCAT); + + pos = rw::scale(pos, -1.0f); + frame->translate(&pos, rw::COMBINEPOSTCONCAT); +} + +void +Initialize(void) +{ + sk::globals.windowtitle = "Sub-raster example"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +Initialize3D(void) +{ + if(!sk::InitRW()) + return false; + + Charset = rw::Charset::create(&ForegroundColor, &BackgroundColor); + + World = CreateWorld(); + + AmbientLight = CreateAmbientLight(World); + MainLight = CreateMainLight(World); + Clump = CreateClump(World); + if (Clump == nil) + return false; + + CreateCameras(World); + UpdateSubRasters(Camera, sk::globals.width, sk::globals.height); + + rw::SetRenderState(rw::CULLMODE, rw::CULLBACK); + rw::SetRenderState(rw::ZTESTENABLE, 1); + rw::SetRenderState(rw::ZWRITEENABLE, 1); + + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); + + return true; +} + +void +Terminate3D(void) +{ + DestroyCameras(World); + + if(AmbientLight){ + World->removeLight(AmbientLight); + AmbientLight->destroy(); + AmbientLight = nil; + } + + if(MainLight){ + World->removeLight(MainLight); + rw::Frame *frame = MainLight->getFrame(); + MainLight->setFrame(nil); + frame->destroy(); + MainLight->destroy(); + MainLight = nil; + } + + if(Clump){ + World->removeClump(Clump); + Clump->destroy(); + Clump = nil; + } + + if(World){ + World->destroy(); + World = nil; + } + + if(Charset){ + Charset->destroy(); + Charset = nil; + } + + sk::TerminateRW(); +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +void +DisplayOnScreenInfo(void) +{ + for(int i = 0; i < 4; i++){ + rw::Raster *scr = SubCameras[i]->frameBuffer; + + rw::int32 scrw = scr->width; + rw::int32 scrh = scr->height; + + rw::int32 captionWidth = strlen(SubCameraCaption[i])*Charset->desc.width; + + if(captionWidth < scrw && scrh > Charset->desc.height*2){ + rw::int32 x = scr->offsetX + (scrw - captionWidth)/2; + rw::int32 y = scr->offsetY + Charset->desc.height; + Charset->print(SubCameraCaption[i], x, y, 0); + } + } +} + +rw::RGBA BackgroundColors[] = { + { 64, 64, 64, 0 }, + { 128, 0, 0, 0 }, + { 0, 128, 0, 0 }, + { 0, 0, 128, 0 }, +}; +void +Render(float timeDelta) +{ + Camera->clear(&BorderColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + + for(int i = 0; i < 4; i++){ + SubCameras[i]->clear(&BackgroundColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); +// SubCameras[i]->clear(&BackgroundColors[i], rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + SubCameras[i]->beginUpdate(); + World->render(); + SubCameras[i]->endUpdate(); + } + + Camera->beginUpdate(); + DisplayOnScreenInfo(); + Camera->endUpdate(); + + Camera->showRaster(0); +} + +void +Idle(float timeDelta) +{ + Render(timeDelta); +} + +int MouseX, MouseY; +int MouseDeltaX, MouseDeltaY; +int MouseButtons; + +bool Rotating; + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +void +MouseBtn(sk::MouseState *mouse) +{ + MouseButtons = mouse->buttons; + Rotating = !!(MouseButtons&1); +} + +void +MouseMove(sk::MouseState *mouse) +{ + MouseDeltaX = mouse->posx - MouseX; + MouseDeltaY = mouse->posy - MouseY; + MouseX = mouse->posx; + MouseY = mouse->posy; + if(Rotating) + RotateClump(-MouseDeltaX, MouseDeltaY); +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + MouseState *ms; + + ImGuiEventHandler(e, param); + ImGuiIO &io = ImGui::GetIO(); + + switch(e){ + case INITIALIZE: + Initialize(); + return EVENTPROCESSED; + case RWINITIALIZE: + return Initialize3D() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + Terminate3D(); + return EVENTPROCESSED; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case MOUSEBTN: + if(!io.WantCaptureMouse){ + ms = (MouseState*)param; + MouseBtn(ms); + }else + MouseButtons = 0; + return EVENTPROCESSED; + case MOUSEMOVE: + MouseMove((MouseState*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + if(::Camera){ + sk::CameraSize(::Camera, r); + ::Camera->setFOV(FOV, (float)sk::globals.width/sk::globals.height); + + UpdateSubRasters(::Camera, r->w, r->h); + } + break; + case IDLE: + Idle(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/vendor/librw/tools/subrast/subrast.cpp b/vendor/librw/tools/subrast/subrast.cpp new file mode 100644 index 0000000..f91298f --- /dev/null +++ b/vendor/librw/tools/subrast/subrast.cpp @@ -0,0 +1,141 @@ +#include +#include + +#include "subrast.h" + +rw::Camera *Camera; +rw::Camera *SubCameras[4]; + +void +CameraSetViewWindow(rw::Camera *camera, float width, float height, float vw) +{ + rw::V2d viewWindow; + + // TODO: aspect ratio when fullscreen + if(width > height){ + viewWindow.x = vw; + viewWindow.y = vw / (width/height); + }else{ + viewWindow.x = vw / (height/width); + viewWindow.y = vw; + } + + camera->setViewWindow(&viewWindow); +} + +void +UpdateSubRasters(rw::Camera *mainCamera, rw::int32 mainWidth, rw::int32 mainHeight) +{ + rw::Rect rect[4]; + float width, height, border; + + border = mainHeight*0.05f; + + width = (mainWidth - border*3.0f) / 2.0f; + height = (mainHeight - border*3.0f) / 2.0f; + + // top left + rect[0].x = border; + rect[0].y = border; + rect[0].w = width; + rect[0].h = height; + + // top right + rect[1].x = border*2 + width; + rect[1].y = border; + rect[1].w = width; + rect[1].h = height; + + // bottom left + rect[2].x = border; + rect[2].y = border*2 + height; + rect[2].w = width; + rect[2].h = height; + + // bottom left + rect[3].x = border*2 + width; + rect[3].y = border*2 + height; + rect[3].w = width; + rect[3].h = height; + + CameraSetViewWindow(SubCameras[0], width, height, 0.5f); + for(int i = 1; i < 4; i++) + CameraSetViewWindow(SubCameras[i], width, height, 0.5f + 0.4f); + + for(int i = 0; i < 4; i++){ + SubCameras[i]->frameBuffer->subRaster(mainCamera->frameBuffer, &rect[i]); + SubCameras[i]->zBuffer->subRaster(mainCamera->zBuffer, &rect[i]); + } +} + +void +PositionSubCameras(void) +{ + rw::Frame *frame; + rw::V3d pos; + const float dist = 2.5f; + + // perspective + pos.x = pos.y = 0.0f; + pos.z = -4.0f; + frame = SubCameras[0]->getFrame(); + frame->translate(&pos, rw::COMBINEREPLACE); + + // look along z + pos.x = pos.y = 0.0f; + pos.z = -dist; + frame = SubCameras[1]->getFrame(); + frame->translate(&pos, rw::COMBINEREPLACE); + + // look along x + pos.x = -dist; + pos.y = pos.z = 0.0f; + frame = SubCameras[2]->getFrame(); + frame->rotate(&Yaxis, 90.0f, rw::COMBINEREPLACE); + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // look along y + pos.x = pos.z = 0.0f; + pos.y = -dist; + frame = SubCameras[3]->getFrame(); + frame->rotate(&Xaxis, -90.0f, rw::COMBINEREPLACE); + frame->translate(&pos, rw::COMBINEPOSTCONCAT); +} + +void +CreateCameras(rw::World *world) +{ + Camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + assert(Camera); + + for(int i = 0; i < 4; i++){ + SubCameras[i] = sk::CameraCreate(0, 0, 1); + assert(SubCameras[i]); + + SubCameras[i]->setNearPlane(0.1f); + SubCameras[i]->setFarPlane(30.0f); + + world->addCamera(SubCameras[i]); + + if(i > 0) + SubCameras[i]->setProjection(rw::Camera::PARALLEL); + } + + PositionSubCameras(); +} + +void +DestroyCameras(rw::World *world) +{ + if(Camera){ + sk::CameraDestroy(Camera); + Camera = nil; + } + + for(int i = 0; i < 4; i++) + if(SubCameras[i]){ + world->removeCamera(SubCameras[i]); + sk::CameraDestroy(SubCameras[i]); + SubCameras[i] = nil; + } +} diff --git a/vendor/librw/tools/subrast/subrast.h b/vendor/librw/tools/subrast/subrast.h new file mode 100644 index 0000000..b7c8537 --- /dev/null +++ b/vendor/librw/tools/subrast/subrast.h @@ -0,0 +1,10 @@ +extern rw::Camera *Camera; +extern rw::Camera *SubCameras[4]; + +void CreateCameras(rw::World *world); +void DestroyCameras(rw::World *world); +void UpdateSubRasters(rw::Camera *mainCamera, rw::int32 mainWidth, rw::int32 mainHeight); + +extern rw::V3d Xaxis; +extern rw::V3d Yaxis; +extern rw::V3d Zaxis;